diff --git a/lib/core/wide_screen_wrapper.dart b/lib/core/wide_screen_wrapper.dart new file mode 100644 index 00000000..68e773b3 --- /dev/null +++ b/lib/core/wide_screen_wrapper.dart @@ -0,0 +1,36 @@ +/* + * This file is part of wger Workout Manager . + * Copyright (c) 2020, 2025 wger Team + * + * wger Workout Manager is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +import 'package:flutter/material.dart'; +import 'package:wger/helpers/material.dart'; + +class WidescreenWrapper extends StatelessWidget { + final Widget child; + + const WidescreenWrapper({required this.child, super.key}); + + @override + Widget build(BuildContext context) { + return Center( + child: ConstrainedBox( + constraints: const BoxConstraints(maxWidth: MATERIAL_MD_BREAKPOINT), + child: child, + ), + ); + } +} diff --git a/lib/helpers/material.dart b/lib/helpers/material.dart new file mode 100644 index 00000000..1f83fd71 --- /dev/null +++ b/lib/helpers/material.dart @@ -0,0 +1,23 @@ +/* + * This file is part of wger Workout Manager . + * Copyright (c) 2020, 2025 wger Team + * + * wger Workout Manager is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +// From https://m3.material.io/foundations/layout/applying-layout/window-size-classes +const MATERIAL_XS_BREAKPOINT = 600.0; +const MATERIAL_MD_BREAKPOINT = 840.0; +const MATERIAL_LG_BREAKPOINT = 1200.0; +const MATERIAL_XL_BREAKPOINT = 1600.0; diff --git a/lib/screens/add_exercise_screen.dart b/lib/screens/add_exercise_screen.dart index 83dd447e..c91f74ef 100644 --- a/lib/screens/add_exercise_screen.dart +++ b/lib/screens/add_exercise_screen.dart @@ -1,5 +1,6 @@ import 'package:flutter/material.dart'; import 'package:provider/provider.dart'; +import 'package:wger/core/wide_screen_wrapper.dart'; import 'package:wger/exceptions/http_exception.dart'; import 'package:wger/helpers/consts.dart'; import 'package:wger/helpers/errors.dart'; @@ -151,55 +152,57 @@ class _AddExerciseStepperState extends State { Widget build(BuildContext context) { return Scaffold( appBar: EmptyAppBar(AppLocalizations.of(context).contributeExercise), - body: Stepper( - controlsBuilder: _controlsBuilder, - steps: [ - Step( - title: Text(AppLocalizations.of(context).baseData), - content: Step1Basics(formkey: _keys[0]), - ), - Step( - title: Text(AppLocalizations.of(context).variations), - content: Step2Variations(formkey: _keys[1]), - ), - Step( - title: Text(AppLocalizations.of(context).description), - content: Step3Description(formkey: _keys[2]), - ), - Step( - title: Text(AppLocalizations.of(context).translation), - content: Step4Translation(formkey: _keys[3]), - ), - Step( - title: Text(AppLocalizations.of(context).images), - content: Step5Images(formkey: _keys[4]), - ), - Step(title: Text(AppLocalizations.of(context).overview), content: Step6Overview()), - ], - currentStep: _currentStep, - onStepContinue: () { - if (_keys[_currentStep].currentState?.validate() ?? false) { - _keys[_currentStep].currentState?.save(); + body: WidescreenWrapper( + child: Stepper( + controlsBuilder: _controlsBuilder, + steps: [ + Step( + title: Text(AppLocalizations.of(context).baseData), + content: Step1Basics(formkey: _keys[0]), + ), + Step( + title: Text(AppLocalizations.of(context).variations), + content: Step2Variations(formkey: _keys[1]), + ), + Step( + title: Text(AppLocalizations.of(context).description), + content: Step3Description(formkey: _keys[2]), + ), + Step( + title: Text(AppLocalizations.of(context).translation), + content: Step4Translation(formkey: _keys[3]), + ), + Step( + title: Text(AppLocalizations.of(context).images), + content: Step5Images(formkey: _keys[4]), + ), + Step(title: Text(AppLocalizations.of(context).overview), content: Step6Overview()), + ], + currentStep: _currentStep, + onStepContinue: () { + if (_keys[_currentStep].currentState?.validate() ?? false) { + _keys[_currentStep].currentState?.save(); - if (_currentStep != lastStepIndex) { - setState(() { - _currentStep += 1; - }); + if (_currentStep != lastStepIndex) { + setState(() { + _currentStep += 1; + }); + } } - } - }, - onStepCancel: () => setState(() { - if (_currentStep != 0) { - _currentStep -= 1; - } - }), - /* - onStepTapped: (int index) { - setState(() { - _currentStep = index; - }); - }, - */ + }, + onStepCancel: () => setState(() { + if (_currentStep != 0) { + _currentStep -= 1; + } + }), + /* + onStepTapped: (int index) { + setState(() { + _currentStep = index; + }); + }, + */ + ), ), ); } diff --git a/lib/screens/configure_plates_screen.dart b/lib/screens/configure_plates_screen.dart index d31f1a38..046b2403 100644 --- a/lib/screens/configure_plates_screen.dart +++ b/lib/screens/configure_plates_screen.dart @@ -1,4 +1,5 @@ import 'package:flutter/material.dart'; +import 'package:wger/core/wide_screen_wrapper.dart'; import 'package:wger/l10n/generated/app_localizations.dart'; import 'package:wger/widgets/routines/plate_calculator.dart'; @@ -13,7 +14,7 @@ class ConfigurePlatesScreen extends StatelessWidget { return Scaffold( appBar: AppBar(title: Text(i18n.selectAvailablePlates)), - body: const ConfigureAvailablePlates(), + body: const WidescreenWrapper(child: ConfigureAvailablePlates()), ); } } diff --git a/lib/screens/dashboard.dart b/lib/screens/dashboard.dart index 06bae73e..04c21b87 100644 --- a/lib/screens/dashboard.dart +++ b/lib/screens/dashboard.dart @@ -17,6 +17,7 @@ */ import 'package:flutter/material.dart'; +import 'package:wger/helpers/material.dart'; import 'package:wger/l10n/generated/app_localizations.dart'; import 'package:wger/widgets/core/app_bar.dart'; import 'package:wger/widgets/dashboard/calendar.dart'; @@ -32,18 +33,48 @@ class DashboardScreen extends StatelessWidget { @override Widget build(BuildContext context) { + final width = MediaQuery.sizeOf(context).width; + final isMobile = width < MATERIAL_XS_BREAKPOINT; + + late final int crossAxisCount; + if (width < MATERIAL_XS_BREAKPOINT) { + crossAxisCount = 1; + } else if (width < MATERIAL_MD_BREAKPOINT) { + crossAxisCount = 2; + } else if (width < MATERIAL_LG_BREAKPOINT) { + crossAxisCount = 3; + } else { + crossAxisCount = 4; + } + + final items = [ + const DashboardRoutineWidget(), + const DashboardNutritionWidget(), + const DashboardWeightWidget(), + const DashboardMeasurementWidget(), + const DashboardCalendarWidget(), + ]; + return Scaffold( appBar: MainAppBar(AppLocalizations.of(context).labelDashboard), - body: const SingleChildScrollView( - padding: EdgeInsets.all(10), - child: Column( - children: [ - DashboardRoutineWidget(), - DashboardNutritionWidget(), - DashboardWeightWidget(), - DashboardMeasurementWidget(), - DashboardCalendarWidget(), - ], + body: Center( + child: ConstrainedBox( + constraints: BoxConstraints(maxWidth: MATERIAL_LG_BREAKPOINT.toDouble()), + child: isMobile + ? ListView.builder( + padding: const EdgeInsets.all(10), + itemBuilder: (context, index) => items[index], + itemCount: items.length, + ) + : GridView.builder( + padding: const EdgeInsets.all(10), + itemBuilder: (context, index) => SingleChildScrollView(child: items[index]), + itemCount: items.length, + gridDelegate: SliverGridDelegateWithFixedCrossAxisCount( + crossAxisCount: crossAxisCount, + childAspectRatio: 0.7, + ), + ), ), ), ); diff --git a/lib/screens/exercise_screen.dart b/lib/screens/exercise_screen.dart index 3ee0f208..11b9058f 100644 --- a/lib/screens/exercise_screen.dart +++ b/lib/screens/exercise_screen.dart @@ -17,6 +17,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/exercises/exercises.dart'; @@ -33,9 +34,11 @@ class ExerciseDetailScreen extends StatelessWidget { appBar: AppBar( title: Text(exercise.getTranslation(Localizations.localeOf(context).languageCode).name), ), - body: Padding( - padding: const EdgeInsets.symmetric(horizontal: 10), - child: ExerciseDetail(exercise), + body: WidescreenWrapper( + child: Padding( + padding: const EdgeInsets.symmetric(horizontal: 10), + child: ExerciseDetail(exercise), + ), ), ); } diff --git a/lib/screens/exercises_screen.dart b/lib/screens/exercises_screen.dart index 6c335025..8b37de71 100644 --- a/lib/screens/exercises_screen.dart +++ b/lib/screens/exercises_screen.dart @@ -1,5 +1,6 @@ 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/models/exercises/exercise.dart'; import 'package:wger/providers/exercises.dart'; @@ -23,21 +24,23 @@ class _ExercisesScreenState extends State { return Scaffold( appBar: EmptyAppBar(AppLocalizations.of(context).exercises), - body: Column( - children: [ - const FilterRow(), - Expanded( - child: exercisesList.isEmpty - ? const Center( - child: SizedBox( - height: 30, - width: 30, - child: CircularProgressIndicator(), - ), - ) - : _ExercisesList(exerciseList: exercisesList), - ), - ], + body: WidescreenWrapper( + child: Column( + children: [ + const FilterRow(), + Expanded( + child: exercisesList.isEmpty + ? const Center( + child: SizedBox( + height: 30, + width: 30, + child: CircularProgressIndicator(), + ), + ) + : _ExercisesList(exerciseList: exercisesList), + ), + ], + ), ), ); } diff --git a/lib/screens/form_screen.dart b/lib/screens/form_screen.dart index 1ff100b2..ca63ee54 100644 --- a/lib/screens/form_screen.dart +++ b/lib/screens/form_screen.dart @@ -18,6 +18,7 @@ import 'package:flutter/material.dart'; import 'package:flutter/rendering.dart'; +import 'package:wger/core/wide_screen_wrapper.dart'; /// Arguments passed to the form screen class FormScreenArguments { @@ -54,18 +55,20 @@ class FormScreen extends StatelessWidget { return Scaffold( appBar: AppBar(title: Text(args.title)), - body: args.hasListView - ? Scrollable( - viewportBuilder: (BuildContext context, ViewportOffset position) => Padding( - padding: args.padding, - child: args.widget, + body: WidescreenWrapper( + child: args.hasListView + ? Scrollable( + viewportBuilder: (BuildContext context, ViewportOffset position) => Padding( + padding: args.padding, + child: args.widget, + ), + ) + : Column( + mainAxisSize: MainAxisSize.max, + mainAxisAlignment: MainAxisAlignment.end, + children: [Padding(padding: args.padding, child: args.widget)], ), - ) - : Column( - mainAxisSize: MainAxisSize.max, - mainAxisAlignment: MainAxisAlignment.end, - children: [Padding(padding: args.padding, child: args.widget)], - ), + ), ); } } diff --git a/lib/screens/gallery_screen.dart b/lib/screens/gallery_screen.dart index bebe3986..906bfa35 100644 --- a/lib/screens/gallery_screen.dart +++ b/lib/screens/gallery_screen.dart @@ -18,6 +18,7 @@ import 'package:flutter/material.dart'; import 'package:provider/provider.dart'; +import 'package:wger/core/wide_screen_wrapper.dart'; import 'package:wger/helpers/platform.dart'; import 'package:wger/l10n/generated/app_localizations.dart'; import 'package:wger/providers/gallery.dart'; @@ -52,8 +53,10 @@ class GalleryScreen extends StatelessWidget { ); }, ), - body: Consumer( - builder: (context, workoutProvider, child) => const Gallery(), + body: WidescreenWrapper( + child: Consumer( + builder: (context, workoutProvider, child) => const Gallery(), + ), ), ); } diff --git a/lib/screens/gym_mode.dart b/lib/screens/gym_mode.dart index 8e28b527..70fcacaf 100644 --- a/lib/screens/gym_mode.dart +++ b/lib/screens/gym_mode.dart @@ -18,6 +18,7 @@ import 'package:flutter/material.dart'; import 'package:provider/provider.dart'; +import 'package:wger/core/wide_screen_wrapper.dart'; import 'package:wger/providers/routines.dart'; import 'package:wger/widgets/routines/gym_mode/gym_mode.dart'; @@ -51,8 +52,10 @@ class GymModeScreen extends StatelessWidget { // backgroundColor: Theme.of(context).cardColor, // primary: false, body: SafeArea( - child: Consumer( - builder: (context, value, child) => GymMode(dayDataGym, dayDataDisplay, args.iteration), + child: WidescreenWrapper( + child: Consumer( + builder: (context, value, child) => GymMode(dayDataGym, dayDataDisplay, args.iteration), + ), ), ), ); diff --git a/lib/screens/home_tabs_screen.dart b/lib/screens/home_tabs_screen.dart index b05d9a3a..de3d4248 100644 --- a/lib/screens/home_tabs_screen.dart +++ b/lib/screens/home_tabs_screen.dart @@ -21,6 +21,7 @@ import 'package:font_awesome_flutter/font_awesome_flutter.dart'; import 'package:logging/logging.dart'; import 'package:provider/provider.dart'; import 'package:rive/rive.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'; @@ -51,6 +52,7 @@ class _HomeTabsScreenState extends State with SingleTickerProvid late Future _initialData; bool _errorHandled = false; int _selectedIndex = 0; + bool _isWideScreen = false; @override void initState() { @@ -59,6 +61,14 @@ class _HomeTabsScreenState extends State with SingleTickerProvid _initialData = _loadEntries(); } + @override + void didChangeDependencies() { + super.didChangeDependencies(); + + final double width = MediaQuery.of(context).size.width; + _isWideScreen = width > MATERIAL_XS_BREAKPOINT; + } + void _onItemTapped(int index) { setState(() { _selectedIndex = index; @@ -141,6 +151,57 @@ class _HomeTabsScreenState extends State with SingleTickerProvid @override Widget build(BuildContext context) { + final destinations = [ + NavigationDestination( + icon: const Icon(Icons.home), + label: AppLocalizations.of(context).labelDashboard, + ), + NavigationDestination( + icon: const Icon(Icons.fitness_center), + label: AppLocalizations.of(context).labelBottomNavWorkout, + ), + NavigationDestination( + icon: const Icon(Icons.restaurant), + label: AppLocalizations.of(context).labelBottomNavNutrition, + ), + NavigationDestination( + icon: const FaIcon(FontAwesomeIcons.weightScale, size: 20), + label: AppLocalizations.of(context).weight, + ), + NavigationDestination( + icon: const Icon(Icons.photo_library), + label: AppLocalizations.of(context).gallery, + ), + ]; + + /// Navigation bar for narrow screens + Widget getNavigationBar() { + return NavigationBar( + destinations: destinations, + onDestinationSelected: _onItemTapped, + selectedIndex: _selectedIndex, + labelBehavior: NavigationDestinationLabelBehavior.alwaysHide, + ); + } + + /// Navigation rail for wide screens + Widget getNavigationRail() { + return NavigationRail( + selectedIndex: _selectedIndex, + onDestinationSelected: _onItemTapped, + labelType: NavigationRailLabelType.all, + scrollable: true, + destinations: destinations + .map( + (d) => NavigationRailDestination( + icon: d.icon, + label: Text(d.label), + ), + ) + .toList(), + ); + } + return FutureBuilder( future: _initialData, builder: (context, snapshot) { @@ -173,34 +234,13 @@ class _HomeTabsScreenState extends State with SingleTickerProvid } return Scaffold( - body: _screenList.elementAt(_selectedIndex), - bottomNavigationBar: NavigationBar( - destinations: [ - NavigationDestination( - icon: const Icon(Icons.home), - label: AppLocalizations.of(context).labelDashboard, - ), - NavigationDestination( - icon: const Icon(Icons.fitness_center), - label: AppLocalizations.of(context).labelBottomNavWorkout, - ), - NavigationDestination( - icon: const Icon(Icons.restaurant), - label: AppLocalizations.of(context).labelBottomNavNutrition, - ), - NavigationDestination( - icon: const FaIcon(FontAwesomeIcons.weightScale, size: 20), - label: AppLocalizations.of(context).weight, - ), - NavigationDestination( - icon: const Icon(Icons.photo_library), - label: AppLocalizations.of(context).gallery, - ), + body: Row( + children: [ + if (_isWideScreen) getNavigationRail(), + Expanded(child: _screenList.elementAt(_selectedIndex)), ], - onDestinationSelected: _onItemTapped, - selectedIndex: _selectedIndex, - labelBehavior: NavigationDestinationLabelBehavior.alwaysHide, ), + bottomNavigationBar: _isWideScreen ? null : getNavigationBar(), ); }, ); diff --git a/lib/screens/measurement_categories_screen.dart b/lib/screens/measurement_categories_screen.dart index e336dfed..6feffb47 100644 --- a/lib/screens/measurement_categories_screen.dart +++ b/lib/screens/measurement_categories_screen.dart @@ -18,6 +18,7 @@ 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/measurement.dart'; import 'package:wger/screens/form_screen.dart'; @@ -46,8 +47,10 @@ class MeasurementCategoriesScreen extends StatelessWidget { ); }, ), - body: Consumer( - builder: (context, provider, child) => const CategoriesList(), + body: WidescreenWrapper( + 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 126f345f..688bd868 100644 --- a/lib/screens/measurement_entries_screen.dart +++ b/lib/screens/measurement_entries_screen.dart @@ -18,6 +18,7 @@ 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/measurement.dart'; import 'package:wger/screens/form_screen.dart'; @@ -134,9 +135,11 @@ class MeasurementEntriesScreen extends StatelessWidget { ); }, ), - body: SingleChildScrollView( - child: Consumer( - builder: (context, provider, child) => EntriesList(category), + body: WidescreenWrapper( + child: SingleChildScrollView( + child: Consumer( + builder: (context, provider, child) => EntriesList(category), + ), ), ), ); diff --git a/lib/screens/nutritional_diary_screen.dart b/lib/screens/nutritional_diary_screen.dart index 4e60f44f..11ad28a0 100644 --- a/lib/screens/nutritional_diary_screen.dart +++ b/lib/screens/nutritional_diary_screen.dart @@ -19,6 +19,7 @@ import 'package:flutter/material.dart'; import 'package:intl/intl.dart'; import 'package:provider/provider.dart'; +import 'package:wger/core/wide_screen_wrapper.dart'; import 'package:wger/models/nutrition/nutritional_plan.dart'; import 'package:wger/providers/nutrition.dart'; import 'package:wger/widgets/nutrition/nutritional_diary_detail.dart'; @@ -46,11 +47,13 @@ class NutritionalDiaryScreen extends StatelessWidget { appBar: AppBar( title: Text(DateFormat.yMd(Localizations.localeOf(context).languageCode).format(args.date)), ), - body: Consumer( - builder: (context, nutritionProvider, child) => SingleChildScrollView( - child: Padding( - padding: const EdgeInsets.all(8.0), - child: NutritionalDiaryDetailWidget(args.plan, args.date), + body: WidescreenWrapper( + child: Consumer( + builder: (context, nutritionProvider, child) => SingleChildScrollView( + child: Padding( + padding: const EdgeInsets.all(8.0), + child: NutritionalDiaryDetailWidget(args.plan, args.date), + ), ), ), ), diff --git a/lib/screens/nutritional_plans_screen.dart b/lib/screens/nutritional_plans_screen.dart index b51ae203..d070b1c0 100644 --- a/lib/screens/nutritional_plans_screen.dart +++ b/lib/screens/nutritional_plans_screen.dart @@ -18,6 +18,7 @@ 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/nutrition.dart'; import 'package:wger/screens/form_screen.dart'; @@ -48,8 +49,10 @@ class NutritionalPlansScreen extends StatelessWidget { ); }, ), - body: Consumer( - builder: (context, nutritionProvider, child) => NutritionalPlansList(nutritionProvider), + body: WidescreenWrapper( + child: Consumer( + builder: (context, nutritionProvider, child) => NutritionalPlansList(nutritionProvider), + ), ), ); } diff --git a/lib/screens/routine_edit_screen.dart b/lib/screens/routine_edit_screen.dart index a858a509..ff877e96 100644 --- a/lib/screens/routine_edit_screen.dart +++ b/lib/screens/routine_edit_screen.dart @@ -18,6 +18,7 @@ import 'package:flutter/material.dart'; import 'package:provider/provider.dart'; +import 'package:wger/core/wide_screen_wrapper.dart'; import 'package:wger/providers/routines.dart'; import 'package:wger/widgets/core/app_bar.dart'; import 'package:wger/widgets/routines/routine_edit.dart'; @@ -34,7 +35,7 @@ class RoutineEditScreen extends StatelessWidget { return Scaffold( appBar: EmptyAppBar(routine.name), - body: RoutineEdit(routine), + body: WidescreenWrapper(child: RoutineEdit(routine)), ); } } diff --git a/lib/screens/routine_list_screen.dart b/lib/screens/routine_list_screen.dart index 4400c5ff..e97f37b2 100644 --- a/lib/screens/routine_list_screen.dart +++ b/lib/screens/routine_list_screen.dart @@ -18,6 +18,7 @@ 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/models/workouts/routine.dart'; import 'package:wger/providers/routines.dart'; @@ -49,8 +50,10 @@ class RoutineListScreen extends StatelessWidget { }, child: const Icon(Icons.add, color: Colors.white), ), - body: Consumer( - builder: (context, workoutProvider, child) => RoutinesList(workoutProvider), + body: WidescreenWrapper( + child: Consumer( + builder: (context, workoutProvider, child) => RoutinesList(workoutProvider), + ), ), ); } diff --git a/lib/screens/routine_logs_screen.dart b/lib/screens/routine_logs_screen.dart index ab97ebe4..63e4efaf 100644 --- a/lib/screens/routine_logs_screen.dart +++ b/lib/screens/routine_logs_screen.dart @@ -18,6 +18,7 @@ import 'package:flutter/material.dart'; import 'package:provider/provider.dart'; +import 'package:wger/core/wide_screen_wrapper.dart'; import 'package:wger/providers/routines.dart'; import 'package:wger/widgets/core/app_bar.dart'; import 'package:wger/widgets/routines/workout_logs.dart'; @@ -34,7 +35,7 @@ class WorkoutLogsScreen extends StatelessWidget { return Scaffold( appBar: EmptyAppBar(routine.name), - body: WorkoutLogs(routine), + body: WidescreenWrapper(child: WorkoutLogs(routine)), ); } } diff --git a/lib/screens/routine_screen.dart b/lib/screens/routine_screen.dart index d5a3eb11..7fb50c31 100644 --- a/lib/screens/routine_screen.dart +++ b/lib/screens/routine_screen.dart @@ -18,6 +18,7 @@ import 'package:flutter/material.dart'; import 'package:provider/provider.dart'; +import 'package:wger/core/wide_screen_wrapper.dart'; import 'package:wger/providers/routines.dart'; import 'package:wger/widgets/routines/app_bar.dart'; import 'package:wger/widgets/routines/routine_detail.dart'; @@ -36,9 +37,11 @@ class RoutineScreen extends StatelessWidget { return Scaffold( appBar: RoutineDetailAppBar(routine), - body: SingleChildScrollView( - child: Consumer( - builder: (context, value, child) => RoutineDetail(routine), + body: WidescreenWrapper( + child: SingleChildScrollView( + child: Consumer( + builder: (context, value, child) => RoutineDetail(routine), + ), ), ), ); diff --git a/lib/screens/weight_screen.dart b/lib/screens/weight_screen.dart index 6c91935c..5f41f99a 100644 --- a/lib/screens/weight_screen.dart +++ b/lib/screens/weight_screen.dart @@ -18,6 +18,7 @@ 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/body_weight.dart'; import 'package:wger/screens/form_screen.dart'; @@ -47,9 +48,11 @@ class WeightScreen extends StatelessWidget { ); }, ), - body: SingleChildScrollView( - child: Consumer( - builder: (context, provider, child) => WeightOverview(provider), + body: WidescreenWrapper( + child: SingleChildScrollView( + child: Consumer( + builder: (context, provider, child) => WeightOverview(provider), + ), ), ), ); diff --git a/lib/widgets/core/about.dart b/lib/widgets/core/about.dart index 86348269..6bbd73eb 100644 --- a/lib/widgets/core/about.dart +++ b/lib/widgets/core/about.dart @@ -19,6 +19,7 @@ import 'package:flutter/material.dart'; import 'package:font_awesome_flutter/font_awesome_flutter.dart'; import 'package:provider/provider.dart'; +import 'package:wger/core/wide_screen_wrapper.dart'; import 'package:wger/helpers/consts.dart'; import 'package:wger/helpers/misc.dart'; import 'package:wger/l10n/generated/app_localizations.dart'; @@ -53,164 +54,166 @@ class AboutPage extends StatelessWidget { return Scaffold( appBar: AppBar(title: Text(i18n.aboutPageTitle)), - body: SingleChildScrollView( - padding: const EdgeInsets.all(20.0), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Row( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - Image.asset( - 'assets/images/logo.png', - width: 60, - semanticLabel: 'wger logo', - ), - const SizedBox(width: 20), - Column( - crossAxisAlignment: CrossAxisAlignment.start, - mainAxisAlignment: MainAxisAlignment.center, - children: [ - const Text( - 'wger', - style: TextStyle( - fontSize: 23, - fontWeight: FontWeight.w600, + body: WidescreenWrapper( + child: SingleChildScrollView( + padding: const EdgeInsets.all(20.0), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Image.asset( + 'assets/images/logo.png', + width: 60, + semanticLabel: 'wger logo', + ), + const SizedBox(width: 20), + Column( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisAlignment: MainAxisAlignment.center, + children: [ + const Text( + 'wger', + style: TextStyle( + fontSize: 23, + fontWeight: FontWeight.w600, + ), ), - ), - Text( - 'App: ${authProvider.applicationVersion?.version ?? 'N/A'}\n' - 'Server: ${authProvider.serverVersion ?? 'N/A'}', - style: Theme.of(context).textTheme.bodySmall, - ), - Padding( - padding: const EdgeInsets.only(top: 4.0), - child: Text( - '\u{a9} ${today.year} wger contributors', + Text( + 'App: ${authProvider.applicationVersion?.version ?? 'N/A'}\n' + 'Server: ${authProvider.serverVersion ?? 'N/A'}', style: Theme.of(context).textTheme.bodySmall, ), - ), - ], - ), - ], - ), - - _buildSectionSpacer(), - _buildSectionHeader(context, i18n.aboutWhySupportTitle), - Text(i18n.aboutDescription), - - _buildSectionSpacer(), - _buildSectionHeader(context, i18n.aboutContributeTitle), - Text(i18n.aboutContributeText), - ListTile( - leading: const Icon(Icons.bug_report), - title: Text(i18n.aboutBugsListTitle), - contentPadding: EdgeInsets.zero, - onTap: () => launchURL(GITHUB_ISSUES_URL, context), - ), - ListTile( - leading: const Icon(Icons.translate), - title: Text(i18n.aboutTranslationListTitle), - contentPadding: EdgeInsets.zero, - onTap: () => launchURL(WEBLATE_URL, context), - ), - ListTile( - leading: const Icon(Icons.code), - title: Text(i18n.aboutSourceListTitle), - contentPadding: EdgeInsets.zero, - onTap: () => launchURL(GITHUB_PROJECT_URL, context), - ), - ListTile( - leading: const FaIcon(FontAwesomeIcons.dumbbell, size: 18), - title: Text(i18n.contributeExercise), - contentPadding: EdgeInsets.zero, - onTap: () => Navigator.of(context).pushNamed(AddExerciseScreen.routeName), - ), - - _buildSectionSpacer(), - _buildSectionHeader(context, i18n.aboutDonateTitle), - Text(i18n.aboutDonateText), - const SizedBox(height: 15), - // Using Wrap for buttons to handle different screen sizes potentially - Center( - child: Wrap( - spacing: 10.0, - runSpacing: 10.0, - alignment: WrapAlignment.center, - children: [ - ElevatedButton.icon( - icon: const FaIcon(FontAwesomeIcons.mugHot, size: 18), - label: const Text('Buy me a coffee'), - onPressed: () => launchURL(BUY_ME_A_COFFEE_URL, context), - ), - ElevatedButton.icon( - icon: const FaIcon(FontAwesomeIcons.solidHeart, size: 18), - label: const Text('Liberapay'), - onPressed: () => launchURL(LIBERAPAY_URL, context), - ), - ElevatedButton.icon( - icon: const FaIcon(FontAwesomeIcons.github, size: 18), - label: const Text('GitHub Sponsors'), - onPressed: () => launchURL(GITHUB_SPONSORS_URL, context), + Padding( + padding: const EdgeInsets.only(top: 4.0), + child: Text( + '\u{a9} ${today.year} wger contributors', + style: Theme.of(context).textTheme.bodySmall, + ), + ), + ], ), ], ), - ), - _buildSectionSpacer(), - _buildSectionHeader(context, i18n.aboutJoinCommunityTitle), - ListTile( - leading: const FaIcon(FontAwesomeIcons.discord), - trailing: const Icon(Icons.arrow_outward), - title: Text(i18n.aboutDiscordTitle), - contentPadding: EdgeInsets.zero, - onTap: () => launchURL(DISCORD_URL, context), - ), - ListTile( - leading: const FaIcon(FontAwesomeIcons.mastodon), - trailing: const Icon(Icons.arrow_outward), - title: Text(i18n.aboutMastodonTitle), - contentPadding: EdgeInsets.zero, - onTap: () => launchURL(MASTODON_URL, context), - ), + _buildSectionSpacer(), + _buildSectionHeader(context, i18n.aboutWhySupportTitle), + Text(i18n.aboutDescription), - _buildSectionSpacer(), - _buildSectionHeader(context, i18n.others), + _buildSectionSpacer(), + _buildSectionHeader(context, i18n.aboutContributeTitle), + Text(i18n.aboutContributeText), + ListTile( + leading: const Icon(Icons.bug_report), + title: Text(i18n.aboutBugsListTitle), + contentPadding: EdgeInsets.zero, + onTap: () => launchURL(GITHUB_ISSUES_URL, context), + ), + ListTile( + leading: const Icon(Icons.translate), + title: Text(i18n.aboutTranslationListTitle), + contentPadding: EdgeInsets.zero, + onTap: () => launchURL(WEBLATE_URL, context), + ), + ListTile( + leading: const Icon(Icons.code), + title: Text(i18n.aboutSourceListTitle), + contentPadding: EdgeInsets.zero, + onTap: () => launchURL(GITHUB_PROJECT_URL, context), + ), + ListTile( + leading: const FaIcon(FontAwesomeIcons.dumbbell, size: 18), + title: Text(i18n.contributeExercise), + contentPadding: EdgeInsets.zero, + onTap: () => Navigator.of(context).pushNamed(AddExerciseScreen.routeName), + ), - ListTile( - leading: const Icon(Icons.article), - trailing: const Icon(Icons.chevron_right), - title: Text(i18n.applicationLogs), - contentPadding: EdgeInsets.zero, - onTap: () { - Navigator.of(context).pushNamed(LogOverviewPage.routeName); - }, - ), - ListTile( - leading: const Icon(Icons.article), - trailing: const Icon(Icons.chevron_right), - title: const Text('View Licenses'), - contentPadding: EdgeInsets.zero, - onTap: () { - showLicensePage( - context: context, - applicationName: 'wger', - applicationVersion: - 'App: ${authProvider.applicationVersion?.version ?? 'N/A'} ' - 'Server: ${authProvider.serverVersion ?? 'N/A'}', - applicationLegalese: '\u{a9} ${today.year} wger contributors', - applicationIcon: Padding( - padding: const EdgeInsets.only(top: 10), - child: Image.asset( - 'assets/images/logo.png', - width: 60, - semanticLabel: 'wger logo', + _buildSectionSpacer(), + _buildSectionHeader(context, i18n.aboutDonateTitle), + Text(i18n.aboutDonateText), + const SizedBox(height: 15), + // Using Wrap for buttons to handle different screen sizes potentially + Center( + child: Wrap( + spacing: 10.0, + runSpacing: 10.0, + alignment: WrapAlignment.center, + children: [ + ElevatedButton.icon( + icon: const FaIcon(FontAwesomeIcons.mugHot, size: 18), + label: const Text('Buy me a coffee'), + onPressed: () => launchURL(BUY_ME_A_COFFEE_URL, context), ), - ), - ); - }, - ), - ], + ElevatedButton.icon( + icon: const FaIcon(FontAwesomeIcons.solidHeart, size: 18), + label: const Text('Liberapay'), + onPressed: () => launchURL(LIBERAPAY_URL, context), + ), + ElevatedButton.icon( + icon: const FaIcon(FontAwesomeIcons.github, size: 18), + label: const Text('GitHub Sponsors'), + onPressed: () => launchURL(GITHUB_SPONSORS_URL, context), + ), + ], + ), + ), + + _buildSectionSpacer(), + _buildSectionHeader(context, i18n.aboutJoinCommunityTitle), + ListTile( + leading: const FaIcon(FontAwesomeIcons.discord), + trailing: const Icon(Icons.arrow_outward), + title: Text(i18n.aboutDiscordTitle), + contentPadding: EdgeInsets.zero, + onTap: () => launchURL(DISCORD_URL, context), + ), + ListTile( + leading: const FaIcon(FontAwesomeIcons.mastodon), + trailing: const Icon(Icons.arrow_outward), + title: Text(i18n.aboutMastodonTitle), + contentPadding: EdgeInsets.zero, + onTap: () => launchURL(MASTODON_URL, context), + ), + + _buildSectionSpacer(), + _buildSectionHeader(context, i18n.others), + + ListTile( + leading: const Icon(Icons.article), + trailing: const Icon(Icons.chevron_right), + title: Text(i18n.applicationLogs), + contentPadding: EdgeInsets.zero, + onTap: () { + Navigator.of(context).pushNamed(LogOverviewPage.routeName); + }, + ), + ListTile( + leading: const Icon(Icons.article), + trailing: const Icon(Icons.chevron_right), + title: const Text('View Licenses'), + contentPadding: EdgeInsets.zero, + onTap: () { + showLicensePage( + context: context, + applicationName: 'wger', + applicationVersion: + 'App: ${authProvider.applicationVersion?.version ?? 'N/A'} ' + 'Server: ${authProvider.serverVersion ?? 'N/A'}', + applicationLegalese: '\u{a9} ${today.year} wger contributors', + applicationIcon: Padding( + padding: const EdgeInsets.only(top: 10), + child: Image.asset( + 'assets/images/logo.png', + width: 60, + semanticLabel: 'wger logo', + ), + ), + ); + }, + ), + ], + ), ), ), ); diff --git a/lib/widgets/core/settings.dart b/lib/widgets/core/settings.dart index e310ab03..3562c545 100644 --- a/lib/widgets/core/settings.dart +++ b/lib/widgets/core/settings.dart @@ -18,6 +18,7 @@ //import 'package:drift/drift.dart'; 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/configure_plates_screen.dart'; import 'package:wger/widgets/core/settings/exercise_cache.dart'; @@ -35,23 +36,28 @@ class SettingsPage extends StatelessWidget { 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), - ), - ], + body: WidescreenWrapper( + child: 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), + ), + ], + ), ), ); } diff --git a/macos/Podfile.lock b/macos/Podfile.lock index 38ef6c65..8c3bae66 100644 --- a/macos/Podfile.lock +++ b/macos/Podfile.lock @@ -84,17 +84,17 @@ EXTERNAL SOURCES: :path: Flutter/ephemeral/.symlinks/plugins/video_player_avfoundation/darwin SPEC CHECKSUMS: - file_selector_macos: 6280b52b459ae6c590af5d78fc35c7267a3c4b31 + file_selector_macos: 9e9e068e90ebee155097d00e89ae91edb2374db7 flutter_zxing: 91e9d17c79c60860450e8879cced0ec54f6a2601 FlutterMacOS: d0db08ddef1a9af05a5ec4b724367152bb0500b1 package_info_plus: f0052d280d17aa382b932f399edf32507174e870 - path_provider_foundation: 080d55be775b7414fd5a5ef3ac137b97b097e564 + path_provider_foundation: bb55f6dbba17d0dccd6737fe6f7f34fbd0376880 rive_common: ea79040f86acf053a2d5a75a2506175ee39796a5 - shared_preferences_foundation: 9e1978ff2562383bd5676f64ec4e9aa8fa06a6f7 + shared_preferences_foundation: 7036424c3d8ec98dfe75ff1667cb0cd531ec82bb sqlite3: 73513155ec6979715d3904ef53a8d68892d4032b sqlite3_flutter_libs: 83f8e9f5b6554077f1d93119fe20ebaa5f3a9ef1 - url_launcher_macos: 0fba8ddabfc33ce0a9afe7c5fef5aab3d8d2d673 - video_player_avfoundation: 2cef49524dd1f16c5300b9cd6efd9611ce03639b + url_launcher_macos: f87a979182d112f911de6820aefddaf56ee9fbfd + video_player_avfoundation: dd410b52df6d2466a42d28550e33e4146928280a PODFILE CHECKSUM: 0d3963a09fc94f580682bd88480486da345dc3f0 diff --git a/pubspec.lock b/pubspec.lock index 26f7561e..27575223 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -365,18 +365,18 @@ packages: dependency: "direct main" description: name: flex_color_scheme - sha256: "6e713c27a2ebe63393a44d4bf9cdd2ac81e112724a4c69905fc41cbf231af11d" + sha256: ab854146f201d2d62cc251fd525ef023b84182c4a0bfe4ae4c18ffc505b412d3 url: "https://pub.dev" source: hosted - version: "8.3.1" + version: "8.4.0" flex_seed_scheme: dependency: "direct main" description: name: flex_seed_scheme - sha256: "828291a5a4d4283590541519d8b57821946660ac61d2e07d955f81cfcab22e5d" + sha256: a3183753bbcfc3af106224bff3ab3e1844b73f58062136b7499919f49f3667e7 url: "https://pub.dev" source: hosted - version: "3.6.1" + version: "4.0.1" flutter: dependency: "direct main" description: flutter @@ -1197,10 +1197,10 @@ packages: dependency: "direct main" description: name: sqlite3_flutter_libs - sha256: "69c80d812ef2500202ebd22002cbfc1b6565e9ff56b2f971e757fac5d42294df" + sha256: "1e800ebe7f85a80a66adacaa6febe4d5f4d8b75f244e9838a27cb2ffc7aec08d" url: "https://pub.dev" source: hosted - version: "0.5.40" + version: "0.5.41" sqlparser: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index 8f1c3758..090f23ac 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -40,7 +40,7 @@ dependencies: equatable: ^2.0.7 fl_chart: ^1.1.1 flex_color_scheme: ^8.3.1 - flex_seed_scheme: ^3.6.1 + flex_seed_scheme: ^4.0.1 flutter_html: ^3.0.0 flutter_staggered_grid_view: ^0.7.0 flutter_svg: ^2.2.3 @@ -61,7 +61,7 @@ dependencies: provider: ^6.1.5 rive: ^0.13.20 shared_preferences: ^2.5.3 - sqlite3_flutter_libs: ^0.5.40 + sqlite3_flutter_libs: ^0.5.41 table_calendar: ^3.0.8 url_launcher: ^6.3.2 version: ^3.0.2 diff --git a/test/routine/goldens/routine_logs_screen_detail.png b/test/routine/goldens/routine_logs_screen_detail.png index 0edaa0a2..eb48d474 100644 Binary files a/test/routine/goldens/routine_logs_screen_detail.png and b/test/routine/goldens/routine_logs_screen_detail.png differ diff --git a/test/routine/goldens/routine_screen_detail.png b/test/routine/goldens/routine_screen_detail.png index d3f5cb48..d5de8b9f 100644 Binary files a/test/routine/goldens/routine_screen_detail.png and b/test/routine/goldens/routine_screen_detail.png differ