This commit is contained in:
Forrest 2026-07-29 22:41:38 -04:00
parent 981b4836f9
commit ed910748cb
34 changed files with 2054 additions and 153 deletions

View file

@ -7,11 +7,12 @@ import '../screens/connect_sheet.dart';
import '../screens/home_screen.dart';
import '../screens/now_playing_screen.dart';
import '../screens/settings_screen.dart';
import '../settings/settings_store.dart';
import '../state/providers.dart';
import '../theme/tokens.dart';
import '../widgets/mini_player.dart';
/// Top-level shell: the three Ratune tabs (Home / Browse / Now Playing) with a
/// Top-level shell: the three Timbre tabs (Home / Browse / Now Playing) with a
/// bottom tab bar and a status bar, mirroring the terminal layout.
class AppShell extends ConsumerStatefulWidget {
const AppShell({super.key});
@ -29,12 +30,39 @@ class _AppShellState extends ConsumerState<AppShell> {
final _homeNavKey = GlobalKey<NavigatorState>();
final _browseNavKey = GlobalKey<NavigatorState>();
// Seed the Browse view from the persisted default exactly once, when settings
// finish loading. Guarded so a later change to the default (or a manual browse
// selection) is never overridden mid-session.
bool _browseSeeded = false;
@override
void initState() {
super.initState();
ref.listenManual<AppSettings>(settingsProvider, (prev, next) {
if (_browseSeeded) return;
_browseSeeded = true;
ref.read(browseModeProvider.notifier).state = next.defaultBrowseMode;
});
}
GlobalKey<NavigatorState>? _navKeyForTab(int tab) => switch (tab) {
0 => _homeNavKey,
1 => _browseNavKey,
_ => null,
};
/// Tap on the bottom tab bar. Selecting a different tab just switches; tapping
/// the already-active tab resets it to its root — popping the nested detail
/// stack (e.g. Album → Browse home) so the tab button doubles as "go back to
/// the top", instead of being a no-op.
void _selectTab(int tapped, int current) {
if (tapped == current) {
_navKeyForTab(tapped)?.currentState?.popUntil((r) => r.isFirst);
} else {
ref.read(selectedTabProvider.notifier).state = tapped;
}
}
/// Android system-back: pop the active tab's nested stack first, then fall
/// back to Home, and only exit the app from the Home root.
void _handleBack(int tab) {
@ -88,8 +116,7 @@ class _AppShellState extends ConsumerState<AppShell> {
_TabBar(
tabs: _tabs,
index: index,
onSelect: (i) =>
ref.read(selectedTabProvider.notifier).state = i,
onSelect: (i) => _selectTab(i, index),
),
const _StatusBar(),
],
@ -201,10 +228,10 @@ class _StatusBar extends ConsumerWidget {
color: TimbreColors.surface,
child: Row(
children: [
// Left: connection status — tap to open the connect sheet.
// Left: connection status — tap to open the server switcher.
Expanded(
child: InkWell(
onTap: () => showConnectSheet(context),
onTap: () => showServerSheet(context),
child: Padding(
padding:
const EdgeInsets.symmetric(horizontal: TimbreSpacing.lg),