From aeff36ade301d007a2ad4f6bec55665ea8f96f37 Mon Sep 17 00:00:00 2001 From: Graham Williams Date: Thu, 21 May 2026 11:29:32 +1000 Subject: [PATCH] Remember last menu selected - optional --- lib/src/widgets/solid_scaffold.dart | 13 +++++ .../widgets/solid_scaffold_last_index.dart | 56 +++++++++++++++++++ lib/src/widgets/solid_scaffold_state.dart | 31 ++++++++-- 3 files changed, 96 insertions(+), 4 deletions(-) create mode 100644 lib/src/widgets/solid_scaffold_last_index.dart diff --git a/lib/src/widgets/solid_scaffold.dart b/lib/src/widgets/solid_scaffold.dart index 27e33add..6ea6f44b 100644 --- a/lib/src/widgets/solid_scaffold.dart +++ b/lib/src/widgets/solid_scaffold.dart @@ -49,6 +49,7 @@ import 'package:solidui/src/widgets/solid_scaffold_helpers.dart'; import 'package:solidui/src/widgets/solid_scaffold_init_helpers.dart'; import 'package:solidui/src/widgets/solid_scaffold_layout_builder.dart'; import 'package:solidui/src/widgets/solid_scaffold_models.dart'; +import 'package:solidui/src/widgets/solid_scaffold_last_index.dart'; import 'package:solidui/src/widgets/solid_scaffold_state_helpers.dart'; import 'package:solidui/src/widgets/solid_scaffold_widget_builder.dart'; import 'package:solidui/src/widgets/solid_status_bar_models.dart'; @@ -224,6 +225,17 @@ class SolidScaffold extends StatefulWidget { final int initialIndex; + /// When `true` (the default), the scaffold persists the currently + /// selected menu index to shared_preferences and restores it on the + /// next launch. Set to `false` to opt out and always start at + /// [initialIndex]. + /// + /// Persistence is automatic: no setup is required in the host app. + /// The saved value is read once on first frame and applied to the + /// internal `_selectedIndex`; subsequent menu taps overwrite it. + + final bool rememberLastIndex; + /// Optional menu selection callback (for external state management). final void Function(int)? onMenuSelected; @@ -326,6 +338,7 @@ class SolidScaffold extends StatefulWidget { this.endDrawerEnableOpenDragGesture = true, this.restorationId, this.initialIndex = 0, + this.rememberLastIndex = true, this.onMenuSelected, this.selectedIndex, this.themeToggle, diff --git a/lib/src/widgets/solid_scaffold_last_index.dart b/lib/src/widgets/solid_scaffold_last_index.dart new file mode 100644 index 00000000..883d619a --- /dev/null +++ b/lib/src/widgets/solid_scaffold_last_index.dart @@ -0,0 +1,56 @@ +/// Persist and restore the last-selected SolidScaffold menu index. +/// +/// Used internally when [SolidScaffold.rememberLastIndex] is true so the +/// user lands back on the screen they last visited. Backed by +/// shared_preferences. +/// +/// Copyright (C) 2026, Software Innovation Institute, ANU. +/// +/// Licensed under the MIT License (the "License"). +/// +/// License: https://choosealicense.com/licenses/mit/. +/// +/// Authors: Software Innovation Institute, ANU + +library; + +import 'package:flutter/foundation.dart'; + +import 'package:shared_preferences/shared_preferences.dart'; + +/// Storage helpers for the `last selected menu index`. +/// +/// Static-only utility; never instantiated. +class SolidScaffoldLastIndex { + static const String _key = 'solidui_last_menu_index'; + + SolidScaffoldLastIndex._(); + + /// Read the saved index. Returns `null` if no value is stored, the + /// stored value is out of range, or anything throws. Callers pass + /// [menuLength] so a stale saved value (e.g. after removing a menu + /// item) is treated as absent rather than crashing later. + static Future load({required int menuLength}) async { + try { + final prefs = await SharedPreferences.getInstance(); + final value = prefs.getInt(_key); + if (value == null) return null; + if (value < 0 || value >= menuLength) return null; + return value; + } on Exception catch (e) { + debugPrint('[SolidScaffoldLastIndex] load failed: $e'); + return null; + } + } + + /// Persist [index]. Fire-and-forget: failures are logged but never + /// propagated, since saving the position should not block navigation. + static Future save(int index) async { + try { + final prefs = await SharedPreferences.getInstance(); + await prefs.setInt(_key, index); + } on Exception catch (e) { + debugPrint('[SolidScaffoldLastIndex] save failed: $e'); + } + } +} diff --git a/lib/src/widgets/solid_scaffold_state.dart b/lib/src/widgets/solid_scaffold_state.dart index cba53e1a..0544fcbb 100644 --- a/lib/src/widgets/solid_scaffold_state.dart +++ b/lib/src/widgets/solid_scaffold_state.dart @@ -44,6 +44,18 @@ class SolidScaffoldState extends State { void initState() { super.initState(); _selectedIndex = widget.initialIndex; + // Restore last-visited menu position if requested. Runs async so we + // don't block initState; the visible page swaps once the value + // arrives. If no value is saved (first launch) or the saved index + // is out of range, _selectedIndex stays at widget.initialIndex. + if (widget.rememberLastIndex && widget.menu != null) { + SolidScaffoldLastIndex.load(menuLength: widget.menu!.length).then(( + saved, + ) { + if (!mounted || saved == null || saved == _selectedIndex) return; + setState(() => _selectedIndex = saved); + }); + } _initSecurityKey(); if (SolidScaffoldInitHelpers.hasVersionConfig(widget.appBar)) { _loadAppVersion(); @@ -174,10 +186,21 @@ class SolidScaffoldState extends State { widget.controller!.clearSubpage(); } if (widget.bodyOverride != null) widget.onClearBodyOverride?.call(); - if (widget.onMenuSelected != null) { - widget.onMenuSelected!(index); - } else { - setState(() => _selectedIndex = index); + // Always update internal state. When in controlled mode the caller + // passes selectedIndex, which takes precedence in _currentSelectedIndex + // (so updating _selectedIndex here is a harmless no-op for them). + // When in uncontrolled mode this is what actually drives the + // visible page change. Previously this only ran when + // onMenuSelected was null, which broke callers that wanted to + // observe selections (e.g. to persist them) without also taking + // over navigation. + setState(() => _selectedIndex = index); + widget.onMenuSelected?.call(index); + + // If the caller asked solidui to remember the last menu, persist + // the new selection. Fire-and-forget; saving must never block UI. + if (widget.rememberLastIndex) { + SolidScaffoldLastIndex.save(index); } }