Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 13 additions & 0 deletions lib/src/widgets/solid_scaffold.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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,
Expand Down
56 changes: 56 additions & 0 deletions lib/src/widgets/solid_scaffold_last_index.dart
Original file line number Diff line number Diff line change
@@ -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<int?> 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<void> save(int index) async {
try {
final prefs = await SharedPreferences.getInstance();
await prefs.setInt(_key, index);
} on Exception catch (e) {
debugPrint('[SolidScaffoldLastIndex] save failed: $e');
}
}
}
31 changes: 27 additions & 4 deletions lib/src/widgets/solid_scaffold_state.dart
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,18 @@ class SolidScaffoldState extends State<SolidScaffold> {
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();
Expand Down Expand Up @@ -174,10 +186,21 @@ class SolidScaffoldState extends State<SolidScaffold> {
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);
}
}

Expand Down
Loading