diff --git a/lib/widgets/mornye_bottom_bar.dart b/lib/widgets/mornye_bottom_bar.dart index a9634580..78b13985 100644 --- a/lib/widgets/mornye_bottom_bar.dart +++ b/lib/widgets/mornye_bottom_bar.dart @@ -8,8 +8,8 @@ import 'package:spotiflac_android/theme/mornye_theme.dart'; import 'package:spotiflac_android/widgets/mini_player.dart'; import 'package:spotiflac_android/widgets/mornye_chrome.dart'; -/// Only deliberate vertical drags change the chrome. Horizontal artwork rows, -/// programmatic scrolling and edge bounce must not minimize the navigation. +/// Deliberate vertical drags minimize the chrome until the page returns to its +/// top. Horizontal rows, programmatic scrolling and bounce must not minimize it. class MornyeChromeController extends ValueNotifier { MornyeChromeController() : super(false); @@ -30,16 +30,22 @@ class MornyeChromeController extends ValueNotifier { notification is ScrollEndNotification) { _distance = 0; } - if (notification is! ScrollUpdateNotification || - notification.dragDetails == null) { - return false; - } + if (notification is! ScrollUpdateNotification) return false; final metrics = notification.metrics; if (metrics.pixels <= metrics.minScrollExtent + 12) { + // The Library list can reach its start while its outer header is still + // collapsed. Restore the tabs only when both scroll positions return. + final outer = notification.context + ?.findAncestorStateOfType() + ?.outerController; + if (outer != null && + outer.positions.any((p) => p.pixels > p.minScrollExtent + 12)) { + return false; + } expand(); return false; } - if (metrics.outOfRange) return false; + if (metrics.outOfRange || notification.dragDetails == null) return false; final delta = notification.scrollDelta ?? 0; if (delta == 0) return false; if (_distance.sign != delta.sign) _distance = 0; @@ -47,8 +53,6 @@ class MornyeChromeController extends ValueNotifier { if (_distance >= 28) { value = true; _distance = 0; - } else if (_distance <= -18) { - expand(); } return false; } @@ -199,13 +203,9 @@ class MornyeBottomBar extends ConsumerWidget { width: size, height: size, ), - color: Color.lerp( - index == selectedIndex - ? scheme.primary - : inactiveIconColor, - scheme.primary, - amount, - ), + color: index == selectedIndex + ? scheme.primary + : inactiveIconColor, icon: Opacity( opacity: amount == 0 ? 0 : 1, child: destinations[index].icon, diff --git a/test/mornye_bottom_bar_test.dart b/test/mornye_bottom_bar_test.dart index a401512a..dbbe8cbc 100644 --- a/test/mornye_bottom_bar_test.dart +++ b/test/mornye_bottom_bar_test.dart @@ -136,8 +136,14 @@ void main() { ) async { final activeTab = ValueNotifier(0); addTearDown(activeTab.dispose); - await pumpShell(tester, blur: blur, activeTab: activeTab); - final primary = MornyeTheme.build(Brightness.light).colorScheme.primary; + await pumpShell( + tester, + blur: blur, + activeTab: activeTab, + brightness: Brightness.dark, + ); + final scheme = MornyeTheme.build(Brightness.dark).colorScheme; + final primary = scheme.primary; Color? iconColor(String key, IconData data) { final icon = find.descendant( of: find.byKey(ValueKey(key)), @@ -186,11 +192,11 @@ void main() { 2 => Icons.grid_view, _ => Icons.home, }), - index == 3 ? isNot(primary) : primary, + index == 3 ? scheme.onSurface : primary, ); expect( iconColor('mornye-compact-search', Icons.search), - index == 3 ? primary : isNot(primary), + index == 3 ? primary : scheme.onSurface, ); final movingIcon = switch (index) { 1 => Icons.music_note, @@ -221,6 +227,14 @@ void main() { {index == 3 ? 0 : index, 3}, ); await tester.pumpAndSettle(); + expect( + iconColor('mornye-compact-leading', movingIcon), + index == 3 ? scheme.onSurface : primary, + ); + expect( + iconColor('mornye-compact-search', Icons.search), + index == 3 ? primary : scheme.onSurface, + ); expect( tester.widget(leading).tooltip, index == 3 ? 'Home' : label, @@ -608,7 +622,7 @@ void main() { } testWidgets( - 'scrolling up expands; programmatic and horizontal movement do not collapse', + 'compact tabs survive upward scrolling until the page returns to the top', (tester) async { await pumpShell(tester); scroll.jumpTo(500); @@ -622,11 +636,14 @@ void main() { expect(chrome.value, isTrue); await tester.drag(find.byType(ListView), const Offset(0, 90)); await tester.pumpAndSettle(); + expect(chrome.value, isTrue); + await tester.drag(find.byType(ListView), const Offset(0, 900)); + await tester.pumpAndSettle(); expect(chrome.value, isFalse); }, ); - testWidgets('Library inner scrolling expands before the header returns', ( + testWidgets('Library keeps compact tabs until the outer header returns', ( tester, ) async { await pumpShell( @@ -651,6 +668,9 @@ void main() { expect(chrome.value, isTrue); await tester.drag(find.byType(ListView), const Offset(0, 100)); await tester.pumpAndSettle(); + expect(chrome.value, isTrue); + await tester.drag(find.byType(ListView), const Offset(0, 900)); + await tester.pumpAndSettle(); expect(chrome.value, isFalse); expect(tester.takeException(), isNull); });