From 559eb26631b14d3e96db29c8189f4a5d915a0caa Mon Sep 17 00:00:00 2001 From: Roland Geider Date: Mon, 1 Dec 2025 11:52:11 +0100 Subject: [PATCH 1/2] Make the app work better on wider screens --- lib/core/wide_screen_wrapper.dart | 36 +++ lib/helpers/material.dart | 23 ++ lib/screens/add_exercise_screen.dart | 97 +++--- lib/screens/configure_plates_screen.dart | 3 +- lib/screens/dashboard.dart | 51 ++- lib/screens/exercise_screen.dart | 9 +- lib/screens/exercises_screen.dart | 33 +- lib/screens/form_screen.dart | 25 +- lib/screens/gallery_screen.dart | 7 +- lib/screens/gym_mode.dart | 7 +- lib/screens/home_tabs_screen.dart | 92 ++++-- .../measurement_categories_screen.dart | 7 +- lib/screens/measurement_entries_screen.dart | 9 +- lib/screens/nutritional_diary_screen.dart | 13 +- lib/screens/nutritional_plans_screen.dart | 7 +- lib/screens/routine_edit_screen.dart | 3 +- lib/screens/routine_list_screen.dart | 7 +- lib/screens/routine_logs_screen.dart | 3 +- lib/screens/routine_screen.dart | 9 +- lib/screens/weight_screen.dart | 9 +- lib/widgets/core/about.dart | 299 +++++++++--------- lib/widgets/core/settings.dart | 40 ++- macos/Podfile.lock | 10 +- 23 files changed, 490 insertions(+), 309 deletions(-) create mode 100644 lib/core/wide_screen_wrapper.dart create mode 100644 lib/helpers/material.dart 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 From b01a0fe1f79864bfb53a529df4bbc73ad239cc5d Mon Sep 17 00:00:00 2001 From: Roland Geider Date: Mon, 1 Dec 2025 13:14:22 +0000 Subject: [PATCH 2/2] Updated goldens --- .../goldens/routine_logs_screen_detail.png | Bin 3878 -> 10017 bytes .../routine/goldens/routine_screen_detail.png | Bin 3693 -> 9153 bytes 2 files changed, 0 insertions(+), 0 deletions(-) diff --git a/test/routine/goldens/routine_logs_screen_detail.png b/test/routine/goldens/routine_logs_screen_detail.png index 0edaa0a2477d2917fe1f32399aa85728df9db774..eb48d4746b0536f53cf539202afec921dbf33c56 100644 GIT binary patch literal 10017 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 Gm;VR1e~jJ$ literal 3878 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