diff --git a/assets/fonts/InterVariable.ttf b/assets/fonts/InterVariable.ttf new file mode 100644 index 000000000..4ab79e010 Binary files /dev/null and b/assets/fonts/InterVariable.ttf differ diff --git a/assets/fonts/OFL - Inter.txt b/assets/fonts/OFL - Inter.txt new file mode 100644 index 000000000..9b2ca37b3 --- /dev/null +++ b/assets/fonts/OFL - Inter.txt @@ -0,0 +1,92 @@ +Copyright (c) 2016 The Inter Project Authors (https://github.com/rsms/inter) + +This Font Software is licensed under the SIL Open Font License, Version 1.1. +This license is copied below, and is also available with a FAQ at: +http://scripts.sil.org/OFL + +----------------------------------------------------------- +SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007 +----------------------------------------------------------- + +PREAMBLE +The goals of the Open Font License (OFL) are to stimulate worldwide +development of collaborative font projects, to support the font creation +efforts of academic and linguistic communities, and to provide a free and +open framework in which fonts may be shared and improved in partnership +with others. + +The OFL allows the licensed fonts to be used, studied, modified and +redistributed freely as long as they are not sold by themselves. The +fonts, including any derivative works, can be bundled, embedded, +redistributed and/or sold with any software provided that any reserved +names are not used by derivative works. The fonts and derivatives, +however, cannot be released under any other type of license. The +requirement for fonts to remain under this license does not apply +to any document created using the fonts or their derivatives. + +DEFINITIONS +"Font Software" refers to the set of files released by the Copyright +Holder(s) under this license and clearly marked as such. This may +include source files, build scripts and documentation. + +"Reserved Font Name" refers to any names specified as such after the +copyright statement(s). + +"Original Version" refers to the collection of Font Software components as +distributed by the Copyright Holder(s). + +"Modified Version" refers to any derivative made by adding to, deleting, +or substituting -- in part or in whole -- any of the components of the +Original Version, by changing formats or by porting the Font Software to a +new environment. + +"Author" refers to any designer, engineer, programmer, technical +writer or other person who contributed to the Font Software. + +PERMISSION AND CONDITIONS +Permission is hereby granted, free of charge, to any person obtaining +a copy of the Font Software, to use, study, copy, merge, embed, modify, +redistribute, and sell modified and unmodified copies of the Font +Software, subject to the following conditions: + +1) Neither the Font Software nor any of its individual components, +in Original or Modified Versions, may be sold by itself. + +2) Original or Modified Versions of the Font Software may be bundled, +redistributed and/or sold with any software, provided that each copy +contains the above copyright notice and this license. These can be +included either as stand-alone text files, human-readable headers or +in the appropriate machine-readable metadata fields within text or +binary files as long as those fields can be easily viewed by the user. + +3) No Modified Version of the Font Software may use the Reserved Font +Name(s) unless explicit written permission is granted by the corresponding +Copyright Holder. This restriction only applies to the primary font name as +presented to the users. + +4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font +Software shall not be used to promote, endorse or advertise any +Modified Version, except to acknowledge the contribution(s) of the +Copyright Holder(s) and the Author(s) or with their explicit written +permission. + +5) The Font Software, modified or unmodified, in part or in whole, +must be distributed entirely under this license, and must not be +distributed under any other license. The requirement for fonts to +remain under this license does not apply to any document created +using the Font Software. + +TERMINATION +This license becomes null and void if any of the above conditions are +not met. + +DISCLAIMER +THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT +OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE +COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL +DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM +OTHER DEALINGS IN THE FONT SOFTWARE. diff --git a/lib/screens/dashboard.dart b/lib/screens/dashboard.dart index 56a8e5b9b..f70656a79 100644 --- a/lib/screens/dashboard.dart +++ b/lib/screens/dashboard.dart @@ -70,18 +70,31 @@ class DashboardScreen extends StatelessWidget { return Scaffold( appBar: MainAppBar(AppLocalizations.of(context).labelDashboard), + extendBodyBehindAppBar: true, body: Center( child: ConstrainedBox( constraints: const BoxConstraints(maxWidth: MATERIAL_LG_BREAKPOINT), child: isMobile ? ListView.builder( - padding: const EdgeInsets.all(10), + padding: getAppBarBodyPadding( + context, + left: 10, + right: 10, + bottom: 10, + extraTop: 10, + ), itemBuilder: (context, index) => _getDashboardWidget(user.dashboardWidgets[index]), itemCount: user.dashboardWidgets.length, ) : GridView.builder( - padding: const EdgeInsets.all(10), + padding: getAppBarBodyPadding( + context, + left: 10, + right: 10, + bottom: 10, + extraTop: 10, + ), itemBuilder: (context, index) => SingleChildScrollView( child: _getDashboardWidget(user.dashboardWidgets[index]), ), diff --git a/lib/screens/exercise_screen.dart b/lib/screens/exercise_screen.dart index 11b9058fa..ec4ab0a40 100644 --- a/lib/screens/exercise_screen.dart +++ b/lib/screens/exercise_screen.dart @@ -19,6 +19,7 @@ import 'package:flutter/material.dart'; import 'package:wger/core/wide_screen_wrapper.dart'; import 'package:wger/models/exercises/exercise.dart'; +import 'package:wger/widgets/core/app_bar.dart'; import 'package:wger/widgets/exercises/exercises.dart'; class ExerciseDetailScreen extends StatelessWidget { @@ -31,12 +32,13 @@ class ExerciseDetailScreen extends StatelessWidget { final exercise = ModalRoute.of(context)!.settings.arguments as Exercise; return Scaffold( - appBar: AppBar( - title: Text(exercise.getTranslation(Localizations.localeOf(context).languageCode).name), + extendBodyBehindAppBar: true, + appBar: WgerAppBar( + exercise.getTranslation(Localizations.localeOf(context).languageCode).name, ), body: WidescreenWrapper( child: Padding( - padding: const EdgeInsets.symmetric(horizontal: 10), + padding: getAppBarBodyPadding(context, left: 10, right: 10), child: ExerciseDetail(exercise), ), ), diff --git a/lib/screens/form_screen.dart b/lib/screens/form_screen.dart index ca63ee54c..3148a3932 100644 --- a/lib/screens/form_screen.dart +++ b/lib/screens/form_screen.dart @@ -19,6 +19,7 @@ import 'package:flutter/material.dart'; import 'package:flutter/rendering.dart'; import 'package:wger/core/wide_screen_wrapper.dart'; +import 'package:wger/widgets/core/app_bar.dart'; /// Arguments passed to the form screen class FormScreenArguments { @@ -54,19 +55,37 @@ class FormScreen extends StatelessWidget { ModalRoute.of(context)!.settings.arguments as FormScreenArguments; return Scaffold( - appBar: AppBar(title: Text(args.title)), + appBar: WgerAppBar(args.title), + extendBodyBehindAppBar: true, body: WidescreenWrapper( child: args.hasListView ? Scrollable( viewportBuilder: (BuildContext context, ViewportOffset position) => Padding( - padding: args.padding, + padding: getAppBarBodyPadding( + context, + left: args.padding.left, + right: args.padding.right, + bottom: args.padding.bottom, + extraTop: args.padding.top, + ), child: args.widget, ), ) : Column( mainAxisSize: MainAxisSize.max, mainAxisAlignment: MainAxisAlignment.end, - children: [Padding(padding: args.padding, child: args.widget)], + children: [ + Padding( + padding: getAppBarBodyPadding( + context, + left: args.padding.left, + right: args.padding.right, + bottom: args.padding.bottom, + extraTop: args.padding.top, + ), + child: args.widget, + ), + ], ), ), ); diff --git a/lib/screens/gallery_screen.dart b/lib/screens/gallery_screen.dart index 906bfa352..4a717ceca 100644 --- a/lib/screens/gallery_screen.dart +++ b/lib/screens/gallery_screen.dart @@ -37,6 +37,7 @@ class GalleryScreen extends StatelessWidget { Widget build(BuildContext context) { return Scaffold( appBar: EmptyAppBar(AppLocalizations.of(context).gallery), + extendBodyBehindAppBar: true, floatingActionButton: isDesktop ? null : FloatingActionButton( diff --git a/lib/screens/log_meals_screen.dart b/lib/screens/log_meals_screen.dart index 45b8ae4f4..af46fab7e 100644 --- a/lib/screens/log_meals_screen.dart +++ b/lib/screens/log_meals_screen.dart @@ -20,6 +20,7 @@ import 'package:flutter/material.dart'; import 'package:wger/l10n/generated/app_localizations.dart'; import 'package:wger/models/nutrition/nutritional_plan.dart'; import 'package:wger/screens/nutritional_plan_screen.dart'; +import 'package:wger/widgets/core/app_bar.dart'; import 'package:wger/widgets/nutrition/meal.dart'; class LogMealsScreen extends StatefulWidget { @@ -39,58 +40,60 @@ class _LogMealsScreenState extends State { final nutritionalPlan = ModalRoute.of(context)!.settings.arguments as NutritionalPlan; return Scaffold( - appBar: AppBar( - title: Text(AppLocalizations.of(context).selectMealToLog), - ), - body: Column( - children: [ - // Meals list or empty state - Expanded( - child: nutritionalPlan.meals.isEmpty - ? Center( - child: Padding( - padding: const EdgeInsets.all(16.0), - child: Text( - AppLocalizations.of(context).yourCurrentNutritionPlanHasNoMealsDefinedYet, - textAlign: TextAlign.center, - style: Theme.of(context).textTheme.titleMedium, + appBar: WgerAppBar(AppLocalizations.of(context).selectMealToLog), + extendBodyBehindAppBar: true, + body: Padding( + padding: getAppBarBodyPadding(context), + child: Column( + children: [ + // Meals list or empty state + Expanded( + child: nutritionalPlan.meals.isEmpty + ? Center( + child: Padding( + padding: const EdgeInsets.all(16.0), + child: Text( + AppLocalizations.of(context).yourCurrentNutritionPlanHasNoMealsDefinedYet, + textAlign: TextAlign.center, + style: Theme.of(context).textTheme.titleMedium, + ), + ), + ) + : ListView.builder( + itemCount: nutritionalPlan.meals.length, + itemBuilder: (context, index) => MealWidget( + nutritionalPlan.meals[index], + nutritionalPlan.dedupMealItems, + true, + true, ), ), - ) - : ListView.builder( - itemCount: nutritionalPlan.meals.length, - itemBuilder: (context, index) => MealWidget( - nutritionalPlan.meals[index], - nutritionalPlan.dedupMealItems, - true, - true, - ), + ), + const SizedBox(height: 16), + Padding( + padding: const EdgeInsets.only(left: 16.0, right: 16.0, top: 8.0, bottom: 32.0), + child: Column( + children: [ + Text( + AppLocalizations.of(context).toAddMealsToThePlanGoToNutritionalPlanDetails, + textAlign: TextAlign.center, + style: Theme.of(context).textTheme.bodyMedium, + ), + const SizedBox(height: 8), + TextButton( + child: Text(AppLocalizations.of(context).goToDetailPage), + onPressed: () { + Navigator.of(context).pushReplacementNamed( + NutritionalPlanScreen.routeName, + arguments: nutritionalPlan, + ); + }, ), - ), - const SizedBox(height: 16), - Padding( - padding: const EdgeInsets.only(left: 16.0, right: 16.0, top: 8.0, bottom: 32.0), - child: Column( - children: [ - Text( - AppLocalizations.of(context).toAddMealsToThePlanGoToNutritionalPlanDetails, - textAlign: TextAlign.center, - style: Theme.of(context).textTheme.bodyMedium, - ), - const SizedBox(height: 8), - TextButton( - child: Text(AppLocalizations.of(context).goToDetailPage), - onPressed: () { - Navigator.of(context).pushReplacementNamed( - NutritionalPlanScreen.routeName, - arguments: nutritionalPlan, - ); - }, - ), - ], + ], + ), ), - ), - ], + ], + ), ), ); } diff --git a/lib/screens/measurement_categories_screen.dart b/lib/screens/measurement_categories_screen.dart index 6feffb47a..3afa05c96 100644 --- a/lib/screens/measurement_categories_screen.dart +++ b/lib/screens/measurement_categories_screen.dart @@ -22,6 +22,7 @@ import 'package:wger/core/wide_screen_wrapper.dart'; import 'package:wger/l10n/generated/app_localizations.dart'; import 'package:wger/providers/measurement.dart'; import 'package:wger/screens/form_screen.dart'; +import 'package:wger/widgets/core/app_bar.dart'; import 'package:wger/widgets/measurements/categories.dart'; import 'package:wger/widgets/measurements/forms.dart'; @@ -33,7 +34,8 @@ class MeasurementCategoriesScreen extends StatelessWidget { @override Widget build(BuildContext context) { return Scaffold( - appBar: AppBar(title: Text(AppLocalizations.of(context).measurements)), + appBar: WgerAppBar(AppLocalizations.of(context).measurements), + extendBodyBehindAppBar: true, floatingActionButton: FloatingActionButton( child: const Icon(Icons.add, color: Colors.white), onPressed: () { @@ -48,8 +50,11 @@ class MeasurementCategoriesScreen extends StatelessWidget { }, ), body: WidescreenWrapper( - child: Consumer( - builder: (context, provider, child) => const CategoriesList(), + child: Padding( + padding: getAppBarBodyPadding(context), + child: Consumer( + builder: (context, provider, child) => const CategoriesList(), + ), ), ), ); diff --git a/lib/screens/measurement_entries_screen.dart b/lib/screens/measurement_entries_screen.dart index 3f30ed3e3..9001a60e3 100644 --- a/lib/screens/measurement_entries_screen.dart +++ b/lib/screens/measurement_entries_screen.dart @@ -24,6 +24,7 @@ import 'package:wger/l10n/generated/app_localizations.dart'; import 'package:wger/models/measurements/measurement_category.dart'; import 'package:wger/providers/measurement.dart'; import 'package:wger/screens/form_screen.dart'; +import 'package:wger/widgets/core/app_bar.dart'; import 'package:wger/widgets/measurements/entries.dart'; import 'package:wger/widgets/measurements/forms.dart'; @@ -55,11 +56,11 @@ class MeasurementEntriesScreen extends StatelessWidget { } return Scaffold( - appBar: AppBar( - title: Text(category.name), + appBar: WgerAppBar( + category.name, actions: [ PopupMenuButton( - icon: const Icon(Icons.more_vert), + icon: Icon(Icons.more_vert, color: Theme.of(context).colorScheme.onSurface), onSelected: (value) { switch (value) { case MeasurementOptions.edit: @@ -137,6 +138,7 @@ class MeasurementEntriesScreen extends StatelessWidget { ), ], ), + extendBodyBehindAppBar: true, floatingActionButton: FloatingActionButton( child: const Icon(Icons.add, color: Colors.white), onPressed: () { @@ -152,6 +154,7 @@ class MeasurementEntriesScreen extends StatelessWidget { ), body: WidescreenWrapper( child: SingleChildScrollView( + padding: getAppBarBodyPadding(context), child: Consumer( builder: (context, provider, child) => EntriesList(category!), ), diff --git a/lib/screens/nutritional_plan_screen.dart b/lib/screens/nutritional_plan_screen.dart index 6027ee75c..7ee720589 100644 --- a/lib/screens/nutritional_plan_screen.dart +++ b/lib/screens/nutritional_plan_screen.dart @@ -17,6 +17,7 @@ */ import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; import 'package:flutter_svg_icons/flutter_svg_icons.dart'; import 'package:provider/provider.dart'; import 'package:wger/l10n/generated/app_localizations.dart'; @@ -24,6 +25,7 @@ import 'package:wger/models/nutrition/nutritional_plan.dart'; import 'package:wger/providers/nutrition.dart'; import 'package:wger/screens/form_screen.dart'; import 'package:wger/screens/log_meals_screen.dart'; +import 'package:wger/widgets/core/app_bar.dart'; import 'package:wger/widgets/nutrition/forms.dart'; import 'package:wger/widgets/nutrition/nutritional_plan_detail.dart'; @@ -59,9 +61,8 @@ class _NutritionalPlanScreenState extends State { @override Widget build(BuildContext context) { - const appBarForeground = Colors.white; return Scaffold( - //appBar: getAppBar(nutritionalPlan), + extendBodyBehindAppBar: true, floatingActionButton: Row( mainAxisAlignment: MainAxisAlignment.end, children: [ @@ -104,14 +105,22 @@ class _NutritionalPlanScreenState extends State { body: CustomScrollView( slivers: [ SliverAppBar( - foregroundColor: appBarForeground, + systemOverlayStyle: Theme.of(context).brightness == Brightness.light + ? SystemUiOverlayStyle.dark + : SystemUiOverlayStyle.light, + foregroundColor: Theme.of(context).colorScheme.onSurface, + backgroundColor: Colors.transparent, + surfaceTintColor: Colors.transparent, + elevation: 0, pinned: true, - iconTheme: const IconThemeData(color: appBarForeground), + iconTheme: IconThemeData(color: Theme.of(context).colorScheme.onSurface), + flexibleSpace: buildAppBarBlurBackground(context), actions: [ if (!_nutritionalPlan.onlyLogging) IconButton( - icon: const SvgIcon( - icon: SvgIconData('assets/icons/meal-add.svg'), + icon: SvgIcon( + icon: const SvgIconData('assets/icons/meal-add.svg'), + color: Theme.of(context).colorScheme.onSurface, ), onPressed: () { Navigator.pushNamed( @@ -125,7 +134,7 @@ class _NutritionalPlanScreenState extends State { }, ), PopupMenuButton( - icon: const Icon(Icons.more_vert, color: appBarForeground), + icon: Icon(Icons.more_vert, color: Theme.of(context).colorScheme.onSurface), onSelected: (value) { switch (value) { case NutritionalPlanOptions.edit: @@ -169,12 +178,9 @@ class _NutritionalPlanScreenState extends State { }, ), ], - flexibleSpace: FlexibleSpaceBar( - titlePadding: const EdgeInsets.fromLTRB(56, 0, 56, 16), - title: Text( - _nutritionalPlan.getLabel(context), - style: Theme.of(context).textTheme.titleLarge?.copyWith(color: appBarForeground), - ), + title: Text( + _nutritionalPlan.getLabel(context), + style: kAppBarTitleStyle, ), ), FutureBuilder( diff --git a/lib/screens/nutritional_plans_screen.dart b/lib/screens/nutritional_plans_screen.dart index d070b1c0c..335be9c82 100644 --- a/lib/screens/nutritional_plans_screen.dart +++ b/lib/screens/nutritional_plans_screen.dart @@ -35,6 +35,7 @@ class NutritionalPlansScreen extends StatelessWidget { Widget build(BuildContext context) { return Scaffold( appBar: EmptyAppBar(AppLocalizations.of(context).nutritionalPlans), + extendBodyBehindAppBar: true, floatingActionButton: FloatingActionButton( child: const Icon(Icons.add, color: Colors.white), onPressed: () { diff --git a/lib/screens/routine_list_screen.dart b/lib/screens/routine_list_screen.dart index e97f37b2f..712cfafdd 100644 --- a/lib/screens/routine_list_screen.dart +++ b/lib/screens/routine_list_screen.dart @@ -22,8 +22,10 @@ import 'package:wger/core/wide_screen_wrapper.dart'; import 'package:wger/l10n/generated/app_localizations.dart'; import 'package:wger/models/workouts/routine.dart'; import 'package:wger/providers/routines.dart'; +import 'package:wger/screens/add_exercise_screen.dart'; +import 'package:wger/screens/exercises_screen.dart'; import 'package:wger/screens/form_screen.dart'; -import 'package:wger/widgets/routines/app_bar.dart'; +import 'package:wger/widgets/core/app_bar.dart'; import 'package:wger/widgets/routines/forms/routine.dart'; import 'package:wger/widgets/routines/routines_list.dart'; @@ -32,23 +34,78 @@ class RoutineListScreen extends StatelessWidget { static const routeName = '/workout-plans-list'; + void _showExerciseOptions(BuildContext context) { + final i18n = AppLocalizations.of(context); + + showModalBottomSheet( + context: context, + shape: const RoundedRectangleBorder( + borderRadius: BorderRadius.only( + topLeft: Radius.circular(20), + topRight: Radius.circular(20), + ), + ), + builder: (BuildContext context) { + return SafeArea( + child: Padding( + padding: const EdgeInsets.symmetric(vertical: 20), + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + ListTile( + leading: const Icon(Icons.add), + title: Text(i18n.contributeExercise), + onTap: () { + Navigator.of(context).pop(); + Navigator.of(context).pushNamed(AddExerciseScreen.routeName); + }, + ), + ListTile( + leading: const Icon(Icons.list), + title: Text(i18n.exerciseList), + onTap: () { + Navigator.of(context).pop(); + Navigator.of(context).pushNamed(ExercisesScreen.routeName); + }, + ), + ], + ), + ), + ); + }, + ); + } + @override Widget build(BuildContext context) { return Scaffold( - appBar: const RoutineListAppBar(), - floatingActionButton: FloatingActionButton( - onPressed: () { - Navigator.pushNamed( - context, - FormScreen.routeName, - arguments: FormScreenArguments( - AppLocalizations.of(context).newRoutine, - RoutineForm(Routine.empty(), useListView: true), - hasListView: true, - ), - ); - }, - child: const Icon(Icons.add, color: Colors.white), + appBar: EmptyAppBar(AppLocalizations.of(context).routines), + extendBodyBehindAppBar: true, + floatingActionButton: Row( + mainAxisAlignment: MainAxisAlignment.end, + children: [ + FloatingActionButton( + heroTag: 'exerciseOptions', + onPressed: () => _showExerciseOptions(context), + child: const Icon(Icons.fitness_center, color: Colors.white), + ), + const SizedBox(width: 16), + FloatingActionButton( + heroTag: 'addRoutine', + onPressed: () { + Navigator.pushNamed( + context, + FormScreen.routeName, + arguments: FormScreenArguments( + AppLocalizations.of(context).newRoutine, + RoutineForm(Routine.empty(), useListView: true), + hasListView: true, + ), + ); + }, + child: const Icon(Icons.add, color: Colors.white), + ), + ], ), body: WidescreenWrapper( child: Consumer( diff --git a/lib/screens/routine_screen.dart b/lib/screens/routine_screen.dart index 7fb50c315..4df4d5d60 100644 --- a/lib/screens/routine_screen.dart +++ b/lib/screens/routine_screen.dart @@ -19,7 +19,11 @@ import 'package:flutter/material.dart'; import 'package:provider/provider.dart'; import 'package:wger/core/wide_screen_wrapper.dart'; +import 'package:wger/l10n/generated/app_localizations.dart'; import 'package:wger/providers/routines.dart'; +import 'package:wger/screens/add_exercise_screen.dart'; +import 'package:wger/screens/exercises_screen.dart'; +import 'package:wger/widgets/core/app_bar.dart'; import 'package:wger/widgets/routines/app_bar.dart'; import 'package:wger/widgets/routines/routine_detail.dart'; @@ -28,6 +32,48 @@ class RoutineScreen extends StatelessWidget { static const routeName = '/routine-detail'; + void _showExerciseOptions(BuildContext context) { + final i18n = AppLocalizations.of(context); + + showModalBottomSheet( + context: context, + shape: const RoundedRectangleBorder( + borderRadius: BorderRadius.only( + topLeft: Radius.circular(20), + topRight: Radius.circular(20), + ), + ), + builder: (BuildContext context) { + return SafeArea( + child: Padding( + padding: const EdgeInsets.symmetric(vertical: 20), + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + ListTile( + leading: const Icon(Icons.add), + title: Text(i18n.contributeExercise), + onTap: () { + Navigator.of(context).pop(); + Navigator.of(context).pushNamed(AddExerciseScreen.routeName); + }, + ), + ListTile( + leading: const Icon(Icons.list), + title: Text(i18n.exerciseList), + onTap: () { + Navigator.of(context).pop(); + Navigator.of(context).pushNamed(ExercisesScreen.routeName); + }, + ), + ], + ), + ), + ); + }, + ); + } + @override Widget build(BuildContext context) { final routineId = ModalRoute.of(context)!.settings.arguments as int; @@ -37,8 +83,14 @@ class RoutineScreen extends StatelessWidget { return Scaffold( appBar: RoutineDetailAppBar(routine), + extendBodyBehindAppBar: true, + floatingActionButton: FloatingActionButton( + onPressed: () => _showExerciseOptions(context), + child: const Icon(Icons.fitness_center, color: Colors.white), + ), body: WidescreenWrapper( child: SingleChildScrollView( + padding: getAppBarBodyPadding(context), child: Consumer( builder: (context, value, child) => RoutineDetail(routine), ), diff --git a/lib/screens/weight_screen.dart b/lib/screens/weight_screen.dart index 5f41f99a7..16fe21d1e 100644 --- a/lib/screens/weight_screen.dart +++ b/lib/screens/weight_screen.dart @@ -35,6 +35,7 @@ class WeightScreen extends StatelessWidget { Widget build(BuildContext context) { return Scaffold( appBar: EmptyAppBar(AppLocalizations.of(context).weight), + extendBodyBehindAppBar: true, floatingActionButton: FloatingActionButton( child: const Icon(Icons.add, color: Colors.white), onPressed: () { @@ -50,6 +51,7 @@ class WeightScreen extends StatelessWidget { ), body: WidescreenWrapper( child: SingleChildScrollView( + padding: getAppBarBodyPadding(context, extraTop: 10), child: Consumer( builder: (context, provider, child) => WeightOverview(provider), ), diff --git a/lib/theme/theme.dart b/lib/theme/theme.dart index 6a78e3bd7..b05f2ce9e 100644 --- a/lib/theme/theme.dart +++ b/lib/theme/theme.dart @@ -41,8 +41,8 @@ const FlexSubThemesData wgerSubThemeData = FlexSubThemesData( navigationBarHeight: 56, ); -const String wgerDisplayFont = 'RobotoCondensed'; -const List displayFontBoldWeight = [FontVariation('wght', 600)]; +const String wgerDisplayFont = 'Inter'; +const List displayFontBoldWeight = [FontVariation('wght', 700)]; const List displayFontHeavyWeight = [FontVariation('wght', 800)]; // Make a light ColorScheme from the seeds. diff --git a/lib/widgets/core/app_bar.dart b/lib/widgets/core/app_bar.dart index be52f0638..c169d76fe 100644 --- a/lib/widgets/core/app_bar.dart +++ b/lib/widgets/core/app_bar.dart @@ -16,8 +16,12 @@ * along with this program. If not, see . */ +import 'dart:ui'; + import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; import 'package:provider/provider.dart'; +import 'package:wger/helpers/material.dart'; import 'package:wger/l10n/generated/app_localizations.dart'; import 'package:wger/providers/auth.dart'; import 'package:wger/providers/body_weight.dart'; @@ -31,34 +35,76 @@ import 'package:wger/widgets/core/about.dart'; import 'package:wger/widgets/core/settings.dart'; import 'package:wger/widgets/user/forms.dart'; -class MainAppBar extends StatelessWidget implements PreferredSizeWidget { - final String _title; +/// Standard text style for AppBar titles +const kAppBarTitleStyle = TextStyle( + fontFamily: 'Inter', + fontVariations: [FontVariation('wght', 800)], + fontSize: 22, +); - const MainAppBar(this._title); +/// Returns the blur effect widget used in app bars +Widget buildAppBarBlurBackground(BuildContext context) { + return ClipRect( + child: BackdropFilter( + filter: ImageFilter.blur(sigmaX: 10, sigmaY: 10), + child: Container( + color: Theme.of(context).colorScheme.surface.withOpacity(0.2), + ), + ), + ); +} + +/// Returns the padding needed for body content when using extendBodyBehindAppBar +/// Set [includeToolbarHeight] to false for widgets that handle toolbar offset separately +EdgeInsets getAppBarBodyPadding( + BuildContext context, { + double left = 0, + double right = 0, + double bottom = 0, + double extraTop = 0, + bool includeToolbarHeight = true, +}) { + return EdgeInsets.only( + top: + MediaQuery.of(context).padding.top + (includeToolbarHeight ? kToolbarHeight : 0) + extraTop, + left: left, + right: right, + bottom: bottom, + ); +} + +/// A reusable AppBar widget with the standard wger styling. +/// Use this for screens that need a simple title-only app bar. +class WgerAppBar extends StatelessWidget implements PreferredSizeWidget { + final String title; + final List actions; + + const WgerAppBar(this.title, {super.key, this.actions = const []}); @override Widget build(BuildContext context) { + final brightness = Theme.of(context).brightness; + return AppBar( - title: Text(_title), - actions: [ - IconButton( - icon: const Icon(Icons.widgets_outlined), - onPressed: () { - Navigator.of(context).pushNamed(ConfigureDashboardWidgetsScreen.routeName); - }, + systemOverlayStyle: brightness == Brightness.light + ? SystemUiOverlayStyle.dark + : SystemUiOverlayStyle.light, + title: Text( + title, + style: kAppBarTitleStyle.copyWith( + color: Theme.of(context).colorScheme.onSurface, ), - IconButton( - icon: const Icon(Icons.settings), - onPressed: () async { - return showDialog( - context: context, - builder: (BuildContext context) { - return const MainSettingsDialog(); - }, - ); - }, - ), - ], + ), + backgroundColor: Colors.transparent, + foregroundColor: Theme.of(context).colorScheme.onSurface, + iconTheme: IconThemeData( + color: Theme.of(context).colorScheme.onSurface, + ), + elevation: 0, + scrolledUnderElevation: 0, + surfaceTintColor: Colors.transparent, + flexibleSpace: buildAppBarBlurBackground(context), + actions: actions, ); } @@ -66,71 +112,279 @@ class MainAppBar extends StatelessWidget implements PreferredSizeWidget { Size get preferredSize => const Size.fromHeight(kToolbarHeight); } -class MainSettingsDialog extends StatelessWidget { - const MainSettingsDialog({super.key}); +class MainAppBar extends StatelessWidget implements PreferredSizeWidget { + final String _title; - @override - Widget build(BuildContext context) { - return AlertDialog( - title: Text(AppLocalizations.of(context).optionsLabel), - actions: [ - TextButton( - child: Text( - MaterialLocalizations.of(context).closeButtonLabel, + const MainAppBar(this._title); + + /// Show adaptive menu based on screen size + void _showMenu(BuildContext context) { + final isWideScreen = MediaQuery.of(context).size.width > MATERIAL_XS_BREAKPOINT; + + if (isWideScreen) { + _showPopupMenu(context); + } else { + _showBottomSheetMenu(context); + } + } + + /// Modern popup menu for desktop/wide screens + void _showPopupMenu(BuildContext context) { + final RenderBox button = context.findRenderObject() as RenderBox; + final RenderBox overlay = + Navigator.of(context).overlay!.context.findRenderObject() as RenderBox; + final RelativeRect position = RelativeRect.fromRect( + Rect.fromPoints( + button.localToGlobal(button.size.bottomRight(Offset.zero), ancestor: overlay), + button.localToGlobal(button.size.bottomRight(Offset.zero), ancestor: overlay), + ), + Offset.zero & overlay.size, + ); + + showMenu( + context: context, + position: position, + elevation: 8, + shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)), + items: [ + PopupMenuItem( + value: 'profile', + child: Row( + children: [ + const Icon(Icons.person, size: 20), + const SizedBox(width: 12), + Text(AppLocalizations.of(context).userProfile), + ], + ), + ), + PopupMenuItem( + value: 'configure', + child: Row( + children: [ + const Icon(Icons.widgets_outlined, size: 20), + const SizedBox(width: 12), + Text(AppLocalizations.of(context).dashboardWidgets), + ], + ), + ), + PopupMenuItem( + value: 'settings', + child: Row( + children: [ + const Icon(Icons.settings, size: 20), + const SizedBox(width: 12), + Text(AppLocalizations.of(context).settingsTitle), + ], + ), + ), + PopupMenuItem( + value: 'about', + child: Row( + children: [ + const Icon(Icons.info, size: 20), + const SizedBox(width: 12), + Text(AppLocalizations.of(context).aboutPageTitle), + ], + ), + ), + const PopupMenuDivider(), + PopupMenuItem( + value: 'logout', + child: Row( + children: [ + const Icon(Icons.exit_to_app, size: 20), + const SizedBox(width: 12), + Text(AppLocalizations.of(context).logout), + ], ), - onPressed: () => Navigator.of(context).pop(), ), ], - contentPadding: EdgeInsets.zero, - content: Column( - mainAxisSize: MainAxisSize.min, - children: [ - ListTile( - //dense: true, - leading: const Icon(Icons.person), - title: Text(AppLocalizations.of(context).userProfile), - onTap: () => Navigator.pushNamed( - context, - FormScreen.routeName, - arguments: FormScreenArguments( - AppLocalizations.of(context).userProfile, - UserProfileForm( - context.read().profile!, + ).then((value) { + if (context.mounted) { + _handleMenuSelection(context, value); + } + }); + } + + /// Modern bottom sheet menu for mobile/narrow screens + void _showBottomSheetMenu(BuildContext context) { + showModalBottomSheet( + context: context, + backgroundColor: Colors.transparent, + isScrollControlled: true, + builder: (BuildContext context) { + return Container( + decoration: BoxDecoration( + color: Theme.of(context).colorScheme.surface, + borderRadius: const BorderRadius.vertical(top: Radius.circular(20)), + ), + child: SafeArea( + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + // Handle bar + Container( + margin: const EdgeInsets.only(top: 12, bottom: 8), + width: 40, + height: 4, + decoration: BoxDecoration( + color: Theme.of(context).colorScheme.onSurfaceVariant.withOpacity(0.4), + borderRadius: BorderRadius.circular(2), + ), ), - ), + // Menu header + Padding( + padding: const EdgeInsets.all(16.0), + child: Row( + children: [ + CircleAvatar( + radius: 24, + backgroundColor: Theme.of(context).colorScheme.primaryContainer, + child: Icon( + Icons.person, + color: Theme.of(context).colorScheme.onPrimaryContainer, + ), + ), + const SizedBox(width: 16), + Expanded( + child: Text( + context.read().profile?.username ?? + AppLocalizations.of(context).optionsLabel, + style: Theme.of(context).textTheme.titleLarge, + ), + ), + ], + ), + ), + const Divider(height: 1), + // Menu items + ListTile( + leading: const Icon(Icons.person), + title: Text(AppLocalizations.of(context).userProfile), + onTap: () { + Navigator.of(context).pop(); + _handleMenuSelection(context, 'profile'); + }, + ), + ListTile( + leading: const Icon(Icons.widgets_outlined), + title: Text(AppLocalizations.of(context).dashboardWidgets), + onTap: () { + Navigator.of(context).pop(); + _handleMenuSelection(context, 'configure'); + }, + ), + ListTile( + leading: const Icon(Icons.settings), + title: Text(AppLocalizations.of(context).settingsTitle), + onTap: () { + Navigator.of(context).pop(); + _handleMenuSelection(context, 'settings'); + }, + ), + ListTile( + leading: const Icon(Icons.info), + title: Text(AppLocalizations.of(context).aboutPageTitle), + onTap: () { + Navigator.of(context).pop(); + _handleMenuSelection(context, 'about'); + }, + ), + const Divider(height: 1), + ListTile( + leading: const Icon(Icons.exit_to_app), + title: Text(AppLocalizations.of(context).logout), + onTap: () { + Navigator.of(context).pop(); + _handleMenuSelection(context, 'logout'); + }, + ), + const SizedBox(height: 8), + ], ), ), - ListTile( - leading: const Icon(Icons.settings), - onTap: () => Navigator.of(context).pushNamed(SettingsPage.routeName), - title: Text(AppLocalizations.of(context).settingsTitle), - ), - ListTile( - leading: const Icon(Icons.info), - onTap: () => Navigator.of(context).pushNamed(AboutPage.routeName), - title: Text(AppLocalizations.of(context).aboutPageTitle), - ), - const Divider(), - ListTile( - //dense: true, - leading: const Icon(Icons.exit_to_app), - title: Text(AppLocalizations.of(context).logout), - onTap: () { - context.read().logout(); - context.read().clear(); - context.read().clear(); - context.read().clear(); - context.read().clear(); - context.read().clear(); - - Navigator.of(context).pop(); - Navigator.of(context).pushReplacementNamed('/'); - }, + ); + }, + ); + } + + /// Handle menu item selection + void _handleMenuSelection(BuildContext context, String? value) { + if (value == null) return; + + switch (value) { + case 'profile': + Navigator.pushNamed( + context, + FormScreen.routeName, + arguments: FormScreenArguments( + AppLocalizations.of(context).userProfile, + UserProfileForm( + context.read().profile!, + ), ), - ], + ); + break; + case 'configure': + Navigator.of(context).pushNamed(ConfigureDashboardWidgetsScreen.routeName); + break; + case 'settings': + Navigator.of(context).pushNamed(SettingsPage.routeName); + break; + case 'about': + Navigator.of(context).pushNamed(AboutPage.routeName); + break; + case 'logout': + context.read().logout(); + context.read().clear(); + context.read().clear(); + context.read().clear(); + context.read().clear(); + context.read().clear(); + Navigator.of(context).pushReplacementNamed('/'); + break; + } + } + + @override + Widget build(BuildContext context) { + final brightness = Theme.of(context).brightness; + + return AppBar( + systemOverlayStyle: brightness == Brightness.light + ? SystemUiOverlayStyle.dark + : SystemUiOverlayStyle.light, + title: Text( + _title, + style: kAppBarTitleStyle, ), + backgroundColor: Colors.transparent, + foregroundColor: Theme.of(context).colorScheme.onSurface, + elevation: 0, + scrolledUnderElevation: 0, + surfaceTintColor: Colors.transparent, + flexibleSpace: buildAppBarBlurBackground(context), + actions: [ + Padding( + padding: const EdgeInsets.only(right: 8.0), + child: CircleAvatar( + backgroundColor: Theme.of(context).colorScheme.primaryContainer, + child: IconButton( + icon: Icon( + Icons.person, + color: Theme.of(context).colorScheme.onPrimaryContainer, + ), + tooltip: AppLocalizations.of(context).optionsLabel, + onPressed: () => _showMenu(context), + ), + ), + ), + ], ); } + + @override + Size get preferredSize => const Size.fromHeight(kToolbarHeight); } /// App bar that only displays a title @@ -141,7 +395,7 @@ class EmptyAppBar extends StatelessWidget implements PreferredSizeWidget { @override Widget build(BuildContext context) { - return AppBar(title: Text(_title), actions: const []); + return WgerAppBar(_title); } @override diff --git a/lib/widgets/core/settings.dart b/lib/widgets/core/settings.dart index 014b281d9..4edd59023 100644 --- a/lib/widgets/core/settings.dart +++ b/lib/widgets/core/settings.dart @@ -17,12 +17,13 @@ */ import 'package:flutter/material.dart'; +import 'package:wger/core/wide_screen_wrapper.dart'; import 'package:wger/l10n/generated/app_localizations.dart'; import 'package:wger/screens/settings_plates_screen.dart'; - -import './settings/exercise_cache.dart'; -import './settings/ingredient_cache.dart'; -import './settings/theme.dart'; +import 'package:wger/widgets/core/app_bar.dart'; +import 'package:wger/widgets/core/settings/exercise_cache.dart'; +import 'package:wger/widgets/core/settings/ingredient_cache.dart'; +import 'package:wger/widgets/core/settings/theme.dart'; class SettingsPage extends StatelessWidget { static String routeName = '/SettingsPage'; @@ -34,24 +35,31 @@ class SettingsPage extends StatelessWidget { final i18n = AppLocalizations.of(context); return Scaffold( - appBar: AppBar(title: Text(i18n.settingsTitle)), - body: ListView( - children: [ - ListTile( - title: Text(i18n.settingsCacheTitle, style: Theme.of(context).textTheme.headlineSmall), - ), - const SettingsExerciseCache(), - const SettingsIngredientCache(), - ListTile(title: Text(i18n.others, style: Theme.of(context).textTheme.headlineSmall)), - const SettingsTheme(), - ListTile( - title: Text(i18n.selectAvailablePlates), - onTap: () { - Navigator.of(context).pushNamed(ConfigurePlatesScreen.routeName); - }, - trailing: const Icon(Icons.chevron_right), - ), - ], + appBar: WgerAppBar(i18n.settingsTitle), + extendBodyBehindAppBar: true, + body: WidescreenWrapper( + child: ListView( + padding: getAppBarBodyPadding(context), + children: [ + ListTile( + title: Text( + i18n.settingsCacheTitle, + style: Theme.of(context).textTheme.headlineSmall, + ), + ), + const SettingsExerciseCache(), + const SettingsIngredientCache(), + ListTile(title: Text(i18n.others, style: Theme.of(context).textTheme.headlineSmall)), + const SettingsTheme(), + ListTile( + title: Text(i18n.selectAvailablePlates), + onTap: () { + Navigator.of(context).pushNamed(ConfigurePlatesScreen.routeName); + }, + trailing: const Icon(Icons.chevron_right), + ), + ], + ), ), ); } diff --git a/lib/widgets/gallery/overview.dart b/lib/widgets/gallery/overview.dart index 01fd229fb..4fd06be8b 100644 --- a/lib/widgets/gallery/overview.dart +++ b/lib/widgets/gallery/overview.dart @@ -25,6 +25,7 @@ import 'package:wger/l10n/generated/app_localizations.dart'; import 'package:wger/models/gallery/image.dart' as gallery; import 'package:wger/providers/gallery.dart'; import 'package:wger/screens/form_screen.dart'; +import 'package:wger/widgets/core/app_bar.dart'; import 'package:wger/widgets/core/image.dart'; import 'package:wger/widgets/core/text_prompt.dart'; @@ -37,44 +38,50 @@ class Gallery extends StatelessWidget { Widget build(BuildContext context) { final provider = Provider.of(context); - return Padding( - padding: const EdgeInsets.all(5), - child: RefreshIndicator( - onRefresh: () => provider.fetchAndSetGallery(), - child: provider.images.isEmpty - ? const TextPrompt() - : MasonryGridView.count( - crossAxisCount: 2, - mainAxisSpacing: 5, - crossAxisSpacing: 5, - itemCount: provider.images.length, - itemBuilder: (context, index) { - final currentImage = provider.images[index]; + return RefreshIndicator( + onRefresh: () => provider.fetchAndSetGallery(), + edgeOffset: MediaQuery.of(context).padding.top + kToolbarHeight, + child: provider.images.isEmpty + ? const TextPrompt() + : MasonryGridView.count( + padding: getAppBarBodyPadding( + context, + left: 5, + right: 5, + bottom: 5, + extraTop: 10, + includeToolbarHeight: false, + ), + crossAxisCount: 2, + mainAxisSpacing: 5, + crossAxisSpacing: 5, + itemCount: provider.images.length, + itemBuilder: (context, index) { + final currentImage = provider.images[index]; - return GestureDetector( - onTap: () { - showModalBottomSheet( - builder: (context) => ImageDetail(image: currentImage), - context: context, - ); - }, - child: FadeInImage( - key: Key('image-${currentImage.id!}'), - placeholder: const AssetImage('assets/images/placeholder.png'), - image: NetworkImage(currentImage.url!), - fit: BoxFit.cover, - imageSemanticLabel: currentImage.description, - imageErrorBuilder: (context, error, stackTrace) => handleImageError( - context, - error, - stackTrace, - currentImage.url!, - ), + return GestureDetector( + onTap: () { + showModalBottomSheet( + builder: (context) => ImageDetail(image: currentImage), + context: context, + ); + }, + child: FadeInImage( + key: Key('image-${currentImage.id!}'), + placeholder: const AssetImage('assets/images/placeholder.png'), + image: NetworkImage(currentImage.url!), + fit: BoxFit.cover, + imageSemanticLabel: currentImage.description, + imageErrorBuilder: (context, error, stackTrace) => handleImageError( + context, + error, + stackTrace, + currentImage.url!, ), - ); - }, - ), - ), + ), + ); + }, + ), ); } } diff --git a/lib/widgets/nutrition/nutritional_plans_list.dart b/lib/widgets/nutrition/nutritional_plans_list.dart index 3c9d0e95e..2175bc063 100644 --- a/lib/widgets/nutrition/nutritional_plans_list.dart +++ b/lib/widgets/nutrition/nutritional_plans_list.dart @@ -25,6 +25,7 @@ import 'package:wger/providers/body_weight.dart'; import 'package:wger/providers/nutrition.dart'; import 'package:wger/providers/user.dart'; import 'package:wger/screens/nutritional_plan_screen.dart'; +import 'package:wger/widgets/core/app_bar.dart'; import 'package:wger/widgets/core/text_prompt.dart'; import 'package:wger/widgets/measurements/charts.dart'; @@ -90,10 +91,18 @@ class NutritionalPlansList extends StatelessWidget { Widget build(BuildContext context) { return RefreshIndicator( onRefresh: () => _nutritionProvider.fetchAndSetAllPlansSparse(), + edgeOffset: MediaQuery.of(context).padding.top + kToolbarHeight, child: _nutritionProvider.items.isEmpty ? const TextPrompt() : ListView.builder( - padding: const EdgeInsets.all(10.0), + padding: getAppBarBodyPadding( + context, + left: 10, + right: 10, + bottom: 10, + extraTop: 10, + includeToolbarHeight: false, + ), itemCount: _nutritionProvider.items.length, itemBuilder: (context, index) { final currentPlan = _nutritionProvider.items[index]; diff --git a/lib/widgets/routines/app_bar.dart b/lib/widgets/routines/app_bar.dart index 6c4317429..522d33462 100644 --- a/lib/widgets/routines/app_bar.dart +++ b/lib/widgets/routines/app_bar.dart @@ -25,6 +25,7 @@ import 'package:wger/screens/add_exercise_screen.dart'; import 'package:wger/screens/exercises_screen.dart'; import 'package:wger/screens/routine_edit_screen.dart'; import 'package:wger/screens/routine_logs_screen.dart'; +import 'package:wger/widgets/core/app_bar.dart'; enum _RoutineAppBarOptions { list, @@ -44,8 +45,8 @@ class RoutineListAppBar extends StatelessWidget implements PreferredSizeWidget { Widget build(BuildContext context) { final i18n = AppLocalizations.of(context); - return AppBar( - title: Text(i18n.routines), + return WgerAppBar( + i18n.routines, actions: [ PopupMenuButton( itemBuilder: (context) { @@ -89,8 +90,8 @@ class RoutineDetailAppBar extends StatelessWidget implements PreferredSizeWidget final i18n = AppLocalizations.of(context); final provider = context.read(); - return AppBar( - title: Text(routine.name), + return WgerAppBar( + routine.name, actions: [ PopupMenuButton( itemBuilder: (context) { diff --git a/lib/widgets/routines/routines_list.dart b/lib/widgets/routines/routines_list.dart index 151c6417d..19c0690e4 100644 --- a/lib/widgets/routines/routines_list.dart +++ b/lib/widgets/routines/routines_list.dart @@ -22,6 +22,7 @@ import 'package:provider/provider.dart'; import 'package:wger/l10n/generated/app_localizations.dart'; import 'package:wger/providers/routines.dart'; import 'package:wger/screens/routine_screen.dart'; +import 'package:wger/widgets/core/app_bar.dart'; import 'package:wger/widgets/core/text_prompt.dart'; class RoutinesList extends StatefulWidget { @@ -42,10 +43,18 @@ class _RoutinesListState extends State { return RefreshIndicator( onRefresh: () => widget._routineProvider.fetchAndSetAllRoutinesSparse(), + edgeOffset: MediaQuery.of(context).padding.top + kToolbarHeight, child: widget._routineProvider.items.isEmpty ? const TextPrompt() : ListView.builder( - padding: const EdgeInsets.all(10.0), + padding: getAppBarBodyPadding( + context, + left: 10, + right: 10, + bottom: 10, + extraTop: 10, + includeToolbarHeight: false, + ), itemCount: widget._routineProvider.items.length, itemBuilder: (context, index) { final currentRoutine = widget._routineProvider.items[index]; diff --git a/pubspec.yaml b/pubspec.yaml index 31ae15880..ca9735973 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -120,6 +120,9 @@ flutter: # list giving the asset and other descriptors for the font. For # example: fonts: + - family: Inter + fonts: + - asset: assets/fonts/InterVariable.ttf - family: RobotoCondensed fonts: - asset: assets/fonts/RobotoCondensed-VariableFont_wght.ttf diff --git a/test/routine/routines_screen_test.dart b/test/routine/routines_screen_test.dart index 6c7740588..69e13617e 100644 --- a/test/routine/routines_screen_test.dart +++ b/test/routine/routines_screen_test.dart @@ -132,7 +132,7 @@ void main() { await tester.pumpWidget(renderWidget()); expect(find.byType(PlanForm), findsNothing); - await tester.tap(find.byType(FloatingActionButton)); + await tester.tap(find.byIcon(Icons.add)); await tester.pumpAndSettle(); expect(find.byType(RoutineForm), findsOneWidget); });