From 9e24714cbd15b2925a0a4a48b117dd705e034d69 Mon Sep 17 00:00:00 2001 From: Diya Hituvalli Date: Wed, 19 Nov 2025 14:32:02 -0500 Subject: [PATCH 1/3] Add user preference in settings to hide diet plan if not used --- ios/Flutter/ephemeral/flutter_lldb_helper.py | 32 ++++++++++++++++++++ ios/Flutter/ephemeral/flutter_lldbinit | 5 +++ lib/providers/user.dart | 17 +++++++++++ lib/screens/dashboard.dart | 12 +++++--- lib/widgets/core/settings.dart | 15 +++++++++ 5 files changed, 77 insertions(+), 4 deletions(-) create mode 100644 ios/Flutter/ephemeral/flutter_lldb_helper.py create mode 100644 ios/Flutter/ephemeral/flutter_lldbinit diff --git a/ios/Flutter/ephemeral/flutter_lldb_helper.py b/ios/Flutter/ephemeral/flutter_lldb_helper.py new file mode 100644 index 000000000..a88caf99d --- /dev/null +++ b/ios/Flutter/ephemeral/flutter_lldb_helper.py @@ -0,0 +1,32 @@ +# +# Generated file, do not edit. +# + +import lldb + +def handle_new_rx_page(frame: lldb.SBFrame, bp_loc, extra_args, intern_dict): + """Intercept NOTIFY_DEBUGGER_ABOUT_RX_PAGES and touch the pages.""" + base = frame.register["x0"].GetValueAsAddress() + page_len = frame.register["x1"].GetValueAsUnsigned() + + # Note: NOTIFY_DEBUGGER_ABOUT_RX_PAGES will check contents of the + # first page to see if handled it correctly. This makes diagnosing + # misconfiguration (e.g. missing breakpoint) easier. + data = bytearray(page_len) + data[0:8] = b'IHELPED!' + + error = lldb.SBError() + frame.GetThread().GetProcess().WriteMemory(base, data, error) + if not error.Success(): + print(f'Failed to write into {base}[+{page_len}]', error) + return + +def __lldb_init_module(debugger: lldb.SBDebugger, _): + target = debugger.GetDummyTarget() + # Caveat: must use BreakpointCreateByRegEx here and not + # BreakpointCreateByName. For some reasons callback function does not + # get carried over from dummy target for the later. + bp = target.BreakpointCreateByRegex("^NOTIFY_DEBUGGER_ABOUT_RX_PAGES$") + bp.SetScriptCallbackFunction('{}.handle_new_rx_page'.format(__name__)) + bp.SetAutoContinue(True) + print("-- LLDB integration loaded --") diff --git a/ios/Flutter/ephemeral/flutter_lldbinit b/ios/Flutter/ephemeral/flutter_lldbinit new file mode 100644 index 000000000..e3ba6fbed --- /dev/null +++ b/ios/Flutter/ephemeral/flutter_lldbinit @@ -0,0 +1,5 @@ +# +# Generated file, do not edit. +# + +command script import --relative-to-command-file flutter_lldb_helper.py diff --git a/lib/providers/user.dart b/lib/providers/user.dart index 3765bfe26..24c2446d7 100644 --- a/lib/providers/user.dart +++ b/lib/providers/user.dart @@ -29,10 +29,13 @@ class UserProvider with ChangeNotifier { ThemeMode themeMode = ThemeMode.system; final WgerBaseProvider baseProvider; late SharedPreferencesAsync prefs; + bool hideNutrition = false; UserProvider(this.baseProvider, {SharedPreferencesAsync? prefs}) { this.prefs = prefs ?? PreferenceHelper.asyncPref; _loadThemeMode(); + _loadHideNutrition(); + } static const PROFILE_URL = 'userprofile'; @@ -67,6 +70,13 @@ class UserProvider with ChangeNotifier { notifyListeners(); } + Future _loadHideNutrition() async { + final val = await prefs.getBool('hideNutrition'); + hideNutrition = val ?? false; + notifyListeners(); + } + + // Change mode on switch button click void setThemeMode(ThemeMode mode) async { themeMode = mode; @@ -81,6 +91,12 @@ class UserProvider with ChangeNotifier { notifyListeners(); } + void setHideNutrition(bool value) async { + hideNutrition = value; + await prefs.setBool('hideNutrition', value); + notifyListeners(); + } + /// Fetch the current user's profile Future fetchAndSetProfile() async { final userData = await baseProvider.fetch(baseProvider.makeUrl(PROFILE_URL)); @@ -91,6 +107,7 @@ class UserProvider with ChangeNotifier { } } + /// Save the user's profile to the server Future saveProfile() async { await baseProvider.post( diff --git a/lib/screens/dashboard.dart b/lib/screens/dashboard.dart index 06bae73e0..e3ad1b7d3 100644 --- a/lib/screens/dashboard.dart +++ b/lib/screens/dashboard.dart @@ -17,6 +17,7 @@ */ import 'package:flutter/material.dart'; +import 'package:provider/provider.dart'; import 'package:wger/l10n/generated/app_localizations.dart'; import 'package:wger/widgets/core/app_bar.dart'; import 'package:wger/widgets/dashboard/calendar.dart'; @@ -24,6 +25,7 @@ import 'package:wger/widgets/dashboard/widgets/measurements.dart'; import 'package:wger/widgets/dashboard/widgets/nutrition.dart'; import 'package:wger/widgets/dashboard/widgets/routines.dart'; import 'package:wger/widgets/dashboard/widgets/weight.dart'; +import 'package:wger/providers/user.dart'; class DashboardScreen extends StatelessWidget { const DashboardScreen(); @@ -32,20 +34,22 @@ class DashboardScreen extends StatelessWidget { @override Widget build(BuildContext context) { + final user = Provider.of(context); + return Scaffold( appBar: MainAppBar(AppLocalizations.of(context).labelDashboard), - body: const SingleChildScrollView( - padding: EdgeInsets.all(10), + body: SingleChildScrollView( + padding: const EdgeInsets.all(10), child: Column( children: [ DashboardRoutineWidget(), - DashboardNutritionWidget(), DashboardWeightWidget(), DashboardMeasurementWidget(), DashboardCalendarWidget(), + if (!user.hideNutrition) DashboardNutritionWidget(), ], ), ), ); } -} +} \ No newline at end of file diff --git a/lib/widgets/core/settings.dart b/lib/widgets/core/settings.dart index e310ab035..d2e233da5 100644 --- a/lib/widgets/core/settings.dart +++ b/lib/widgets/core/settings.dart @@ -23,6 +23,9 @@ import 'package:wger/screens/configure_plates_screen.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'; +import 'package:provider/provider.dart'; +import 'package:wger/providers/user.dart'; + class SettingsPage extends StatelessWidget { static String routeName = '/SettingsPage'; @@ -44,6 +47,18 @@ class SettingsPage extends StatelessWidget { const SettingsIngredientCache(), ListTile(title: Text(i18n.others, style: Theme.of(context).textTheme.headlineSmall)), const SettingsTheme(), + Consumer( + builder: (context, user, _) { + return SwitchListTile( + title: const Text('Show nutrition section on dashboard'), + value: !user.hideNutrition, + onChanged: (v) { + Provider.of(context, listen: false) + .setHideNutrition(!v); + }, + ); + }, + ), ListTile( title: Text(i18n.selectAvailablePlates), onTap: () { From c0d04cbb11094b87a1c71c53606d9455d6670920 Mon Sep 17 00:00:00 2001 From: Diya Hituvalli Date: Mon, 8 Dec 2025 13:57:42 -0500 Subject: [PATCH 2/3] allow the users to hide / show all widgets in the home screen --- lib/providers/user.dart | 50 ++++++++++++++++++++++++++-------- lib/screens/dashboard.dart | 15 ++++++---- lib/widgets/core/settings.dart | 40 ++++++++++++++++++++++----- 3 files changed, 81 insertions(+), 24 deletions(-) diff --git a/lib/providers/user.dart b/lib/providers/user.dart index 24c2446d7..27f7516d5 100644 --- a/lib/providers/user.dart +++ b/lib/providers/user.dart @@ -17,6 +17,7 @@ */ import 'dart:async'; +import 'dart:convert'; import 'package:flutter/material.dart'; import 'package:shared_preferences/shared_preferences.dart'; @@ -29,13 +30,23 @@ class UserProvider with ChangeNotifier { ThemeMode themeMode = ThemeMode.system; final WgerBaseProvider baseProvider; late SharedPreferencesAsync prefs; - bool hideNutrition = false; + // bool hideNutrition = false; + + // New: visibility state for dashboard widgets + Map dashboardWidgetVisibility = { + 'routines': true, + 'weight': true, + 'measurements': true, + 'calendar': true, + 'nutrition': true, + }; + + static const String PREFS_DASHBOARD_VISIBILITY = 'dashboardWidgetVisibility'; UserProvider(this.baseProvider, {SharedPreferencesAsync? prefs}) { this.prefs = prefs ?? PreferenceHelper.asyncPref; _loadThemeMode(); - _loadHideNutrition(); - + _loadDashboardVisibility(); } static const PROFILE_URL = 'userprofile'; @@ -69,13 +80,34 @@ class UserProvider with ChangeNotifier { notifyListeners(); } + Future _loadDashboardVisibility() async { + final jsonString = await prefs.getString(PREFS_DASHBOARD_VISIBILITY); + + if (jsonString != null) { + try { + final decoded = jsonDecode(jsonString) as Map; + dashboardWidgetVisibility = + decoded.map((k, v) => MapEntry(k, v as bool)); + } catch (_) { + // If parsing fails, keep defaults + } + } - Future _loadHideNutrition() async { - final val = await prefs.getBool('hideNutrition'); - hideNutrition = val ?? false; notifyListeners(); } + bool isDashboardWidgetVisible(String key) { + return dashboardWidgetVisibility[key] ?? true; + } + + Future setDashboardWidgetVisible(String key, bool visible) async { + dashboardWidgetVisibility[key] = visible; + await prefs.setString( + PREFS_DASHBOARD_VISIBILITY, + jsonEncode(dashboardWidgetVisibility), + ); + notifyListeners(); + } // Change mode on switch button click void setThemeMode(ThemeMode mode) async { @@ -91,12 +123,6 @@ class UserProvider with ChangeNotifier { notifyListeners(); } - void setHideNutrition(bool value) async { - hideNutrition = value; - await prefs.setBool('hideNutrition', value); - notifyListeners(); - } - /// Fetch the current user's profile Future fetchAndSetProfile() async { final userData = await baseProvider.fetch(baseProvider.makeUrl(PROFILE_URL)); diff --git a/lib/screens/dashboard.dart b/lib/screens/dashboard.dart index e3ad1b7d3..802a91e69 100644 --- a/lib/screens/dashboard.dart +++ b/lib/screens/dashboard.dart @@ -42,11 +42,16 @@ class DashboardScreen extends StatelessWidget { padding: const EdgeInsets.all(10), child: Column( children: [ - DashboardRoutineWidget(), - DashboardWeightWidget(), - DashboardMeasurementWidget(), - DashboardCalendarWidget(), - if (!user.hideNutrition) DashboardNutritionWidget(), + if (user.isDashboardWidgetVisible('routines')) + const DashboardRoutineWidget(), + if (user.isDashboardWidgetVisible('weight')) + const DashboardWeightWidget(), + if (user.isDashboardWidgetVisible('measurements')) + const DashboardMeasurementWidget(), + if (user.isDashboardWidgetVisible('calendar')) + const DashboardCalendarWidget(), + if (user.isDashboardWidgetVisible('nutrition')) + const DashboardNutritionWidget(), ], ), ), diff --git a/lib/widgets/core/settings.dart b/lib/widgets/core/settings.dart index d2e233da5..a0f551362 100644 --- a/lib/widgets/core/settings.dart +++ b/lib/widgets/core/settings.dart @@ -49,13 +49,39 @@ class SettingsPage extends StatelessWidget { const SettingsTheme(), Consumer( builder: (context, user, _) { - return SwitchListTile( - title: const Text('Show nutrition section on dashboard'), - value: !user.hideNutrition, - onChanged: (v) { - Provider.of(context, listen: false) - .setHideNutrition(!v); - }, + return Column( + children: [ + SwitchListTile( + title: const Text('Show routines on dashboard'), + value: user.isDashboardWidgetVisible('routines'), + onChanged: (v) => + user.setDashboardWidgetVisible('routines', v), + ), + SwitchListTile( + title: const Text('Show weight on dashboard'), + value: user.isDashboardWidgetVisible('weight'), + onChanged: (v) => + user.setDashboardWidgetVisible('weight', v), + ), + SwitchListTile( + title: const Text('Show measurements on dashboard'), + value: user.isDashboardWidgetVisible('measurements'), + onChanged: (v) => + user.setDashboardWidgetVisible('measurements', v), + ), + SwitchListTile( + title: const Text('Show calendar on dashboard'), + value: user.isDashboardWidgetVisible('calendar'), + onChanged: (v) => + user.setDashboardWidgetVisible('calendar', v), + ), + SwitchListTile( + title: const Text('Show nutrition on dashboard'), + value: user.isDashboardWidgetVisible('nutrition'), + onChanged: (v) => + user.setDashboardWidgetVisible('nutrition', v), + ), + ], ); }, ), From fa00cad40615c72c45eb0e3b761173fe924c8cf5 Mon Sep 17 00:00:00 2001 From: Diya Hituvalli Date: Mon, 8 Dec 2025 15:02:51 -0500 Subject: [PATCH 3/3] added test stubs --- test/core/settings_test.dart | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/test/core/settings_test.dart b/test/core/settings_test.dart index 474603c92..b29346827 100644 --- a/test/core/settings_test.dart +++ b/test/core/settings_test.dart @@ -51,6 +51,11 @@ void main() { when(mockUserProvider.themeMode).thenReturn(ThemeMode.system); when(mockExerciseProvider.exercises).thenReturn(getTestExercises()); when(mockNutritionProvider.ingredients).thenReturn([ingredient1, ingredient2]); + when(mockUserProvider.isDashboardWidgetVisible('routines')).thenReturn(true); + when(mockUserProvider.isDashboardWidgetVisible('weight')).thenReturn(true); + when(mockUserProvider.isDashboardWidgetVisible('measurements')).thenReturn(true); + when(mockUserProvider.isDashboardWidgetVisible('calendar')).thenReturn(true); + when(mockUserProvider.isDashboardWidgetVisible('nutrition')).thenReturn(true); }); Widget createSettingsScreen({locale = 'en'}) { @@ -100,18 +105,24 @@ void main() { group('Theme settings', () { test('Default theme is system', () async { when(mockSharedPreferences.getBool(PREFS_USER_DARK_THEME)).thenAnswer((_) async => null); + when(mockSharedPreferences.getString('dashboardWidgetVisibility')) + .thenAnswer((_) async => null); final userProvider = await UserProvider(MockWgerBaseProvider(), prefs: mockSharedPreferences); expect(userProvider.themeMode, ThemeMode.system); }); test('Loads light theme', () async { when(mockSharedPreferences.getBool(PREFS_USER_DARK_THEME)).thenAnswer((_) async => false); + when(mockSharedPreferences.getString('dashboardWidgetVisibility')) + .thenAnswer((_) async => null); final userProvider = await UserProvider(MockWgerBaseProvider(), prefs: mockSharedPreferences); expect(userProvider.themeMode, ThemeMode.light); }); test('Saves theme to prefs', () { when(mockSharedPreferences.getBool(any)).thenAnswer((_) async => null); + when(mockSharedPreferences.getString('dashboardWidgetVisibility')) + .thenAnswer((_) async => null); final userProvider = UserProvider(MockWgerBaseProvider(), prefs: mockSharedPreferences); userProvider.setThemeMode(ThemeMode.dark); verify(mockSharedPreferences.setBool(PREFS_USER_DARK_THEME, true)).called(1);