Add a floating bottom bar style, user-configurable in Settings

Settings > Appearance > "Bottom bar" now switches between the
original edge-to-edge bar and a floating one - inset 16px from both
side edges and clear of the bottom safe area, 28px corners (the same
radius the sheet top/drawer edge use), a 1px line border standing in
for elevation instead of a shadow (product UI stays flat). Persisted
via NooBottomBarStyle, defaulting to the existing attached behavior.

On Android, floating's idle tabs drop their reserved label space -
with no label to leave room for, the icon centers in the whole button
and renders a touch bigger (27 vs 24px) instead of sitting high with a
gap held open under it; the active tab's icon/pill/label position is
unchanged. iOS's tab content is unchanged either way, only the
container.

main.dart sets Scaffold.extendBody while floating so the body scrolls
behind the bar's transparent margin instead of stopping short of it,
matching how the design canvas explored it.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
2026-09-29 16:21:13 -04:00
co-authored by Claude Sonnet 5
parent c18fc98dc3
commit a70be19f80
9 changed files with 280 additions and 46 deletions
+18
View File
@@ -3,6 +3,7 @@ import 'package:shared_preferences/shared_preferences.dart';
import '../models/app_tab.dart';
import '../models/selection_action.dart';
import '../theme/app_theme.dart';
import '../widgets/noo/nav/noo_nav_style.dart';
/// What swiping a Files list-view item left/right does, user-configurable
/// in Settings.
@@ -30,6 +31,7 @@ class SettingsController extends ChangeNotifier {
static const _prefDefaultTab = 'ui_default_tab';
static const _prefSwipeLeftAction = 'ui_swipe_left_action';
static const _prefSwipeRightAction = 'ui_swipe_right_action';
static const _prefBottomBarStyle = 'ui_bottom_bar_style';
static const _prefAmoledDark = 'ui_amoled_dark';
static const _prefMediaProgressBarStyle = 'ui_media_progress_bar_style';
static const _prefTapTabToScrollTop = 'ui_tap_tab_to_scroll_top';
@@ -39,6 +41,7 @@ class SettingsController extends ChangeNotifier {
Color _seedColor = AppTheme.defaultAccent;
ThemeMode _themeMode = ThemeMode.system;
NooBottomBarStyle _bottomBarStyle = NooBottomBarStyle.attached;
bool _useDynamicColor = true;
bool _amoledDark = false;
MediaProgressBarStyle _mediaProgressBarStyle = MediaProgressBarStyle.wavy;
@@ -80,6 +83,7 @@ class SettingsController extends ChangeNotifier {
Color get seedColor => _seedColor;
ThemeMode get themeMode => _themeMode;
NooBottomBarStyle get bottomBarStyle => _bottomBarStyle;
bool get useDynamicColor => _useDynamicColor;
bool get amoledDark => _amoledDark;
MediaProgressBarStyle get mediaProgressBarStyle => _mediaProgressBarStyle;
@@ -115,6 +119,13 @@ class SettingsController extends ChangeNotifier {
orElse: () => ThemeMode.system,
);
}
final bottomBarStyleName = prefs.getString(_prefBottomBarStyle);
if (bottomBarStyleName != null) {
_bottomBarStyle = NooBottomBarStyle.values.firstWhere(
(s) => s.name == bottomBarStyleName,
orElse: () => _bottomBarStyle,
);
}
_useDynamicColor =
prefs.getBool(_prefUseDynamicColor) ?? _useDynamicColor;
_amoledDark = prefs.getBool(_prefAmoledDark) ?? _amoledDark;
@@ -256,6 +267,13 @@ class SettingsController extends ChangeNotifier {
_prefsFuture.then((p) => p.setString(_prefThemeMode, mode.name));
}
void setBottomBarStyle(NooBottomBarStyle style) {
if (_bottomBarStyle == style) return;
_bottomBarStyle = style;
notifyListeners();
_prefsFuture.then((p) => p.setString(_prefBottomBarStyle, style.name));
}
void setTabOrder(List<AppTab> order) {
_tabOrder = order;
notifyListeners();