diff --git a/lib/screens/libraries/alpha_jump_bar.dart b/lib/screens/libraries/alpha_jump_bar.dart index f62710c8..dcdb4632 100644 --- a/lib/screens/libraries/alpha_jump_bar.dart +++ b/lib/screens/libraries/alpha_jump_bar.dart @@ -12,8 +12,8 @@ import 'alpha_jump_helper.dart'; /// /// Pre-computes a cumulative index map from [firstCharacters] data so that /// tapping a letter triggers [onJump] with the item index where that letter -/// begins. When more letters exist than fit vertically, the bar keeps the -/// highest-count letters (by item size) and drops the rest. +/// begins. Alphabet-sized lists stay complete and compact vertically when +/// needed; larger character sets are thinned to the highest-count letters. /// Supports both touch (tap/drag) and D-pad (up/down/select) input. class AlphaJumpBar extends StatefulWidget { final List firstCharacters; @@ -63,6 +63,10 @@ class _AlphaJumpBarState extends State { /// Minimum vertical space per letter slot. static const double _minLetterHeight = 20.0; + /// Keep canonical alphabet bars complete instead of hiding trailing letters + /// on shorter TV viewports. + static const int _maxCompactLetterCount = 27; + @override void initState() { super.initState(); @@ -100,7 +104,9 @@ class _AlphaJumpBarState extends State { final maxLetters = (availableHeight / _minLetterHeight).floor(); if (maxLetters == _lastMaxLetters) return; _lastMaxLetters = maxLetters; - _displayed = _helper.displayLetters(maxLetters); + _displayed = _helper.letters.length <= _maxCompactLetterCount + ? _helper.letters + : _helper.displayLetters(maxLetters); _clampHighlight(); } @@ -209,6 +215,9 @@ class _AlphaJumpBarState extends State { if (_displayed.isEmpty) return const SizedBox.shrink(); final currentLetter = _nearestDisplayed(widget.currentLetter); + final letterSlotHeight = constraints.maxHeight / _displayed.length; + final markerSize = (letterSlotHeight - 2).clamp(10.0, 18.0).toDouble(); + final fontSize = (letterSlotHeight * 0.58).clamp(7.0, 10.0).toDouble(); return ClickableCursor( child: GestureDetector( @@ -226,10 +235,10 @@ class _AlphaJumpBarState extends State { } }, child: Container( - width: 28, + width: 20, decoration: BoxDecoration( color: colorScheme.surface.withValues(alpha: 0.7), - borderRadius: const BorderRadius.all(Radius.circular(14)), + borderRadius: const BorderRadius.all(Radius.circular(10)), ), child: Column( mainAxisAlignment: MainAxisAlignment.spaceEvenly, @@ -258,17 +267,17 @@ class _AlphaJumpBarState extends State { } return SizedBox( - height: constraints.maxHeight / _displayed.length, + height: letterSlotHeight, child: Center( child: Container( - width: 22, - height: 22, + width: markerSize, + height: markerSize, decoration: decoration, alignment: Alignment.center, child: Text( letter, style: TextStyle( - fontSize: 10, + fontSize: fontSize, fontWeight: (isCurrent || isHighlighted) ? FontWeight.bold : FontWeight.normal, color: letterColor, ), diff --git a/test/screens/libraries/alpha_jump_bar_test.dart b/test/screens/libraries/alpha_jump_bar_test.dart index c4b305ab..70700f5e 100644 --- a/test/screens/libraries/alpha_jump_bar_test.dart +++ b/test/screens/libraries/alpha_jump_bar_test.dart @@ -5,6 +5,57 @@ import 'package:plezy/media/library_first_character.dart'; import 'package:plezy/screens/libraries/alpha_jump_bar.dart'; void main() { + const alphabetCharacters = [ + LibraryFirstCharacter(key: '#', title: '#', size: 1), + LibraryFirstCharacter(key: 'A', title: 'A', size: 1), + LibraryFirstCharacter(key: 'B', title: 'B', size: 1), + LibraryFirstCharacter(key: 'C', title: 'C', size: 1), + LibraryFirstCharacter(key: 'D', title: 'D', size: 1), + LibraryFirstCharacter(key: 'E', title: 'E', size: 1), + LibraryFirstCharacter(key: 'F', title: 'F', size: 1), + LibraryFirstCharacter(key: 'G', title: 'G', size: 1), + LibraryFirstCharacter(key: 'H', title: 'H', size: 1), + LibraryFirstCharacter(key: 'I', title: 'I', size: 1), + LibraryFirstCharacter(key: 'J', title: 'J', size: 1), + LibraryFirstCharacter(key: 'K', title: 'K', size: 1), + LibraryFirstCharacter(key: 'L', title: 'L', size: 1), + LibraryFirstCharacter(key: 'M', title: 'M', size: 1), + LibraryFirstCharacter(key: 'N', title: 'N', size: 1), + LibraryFirstCharacter(key: 'O', title: 'O', size: 1), + LibraryFirstCharacter(key: 'P', title: 'P', size: 1), + LibraryFirstCharacter(key: 'Q', title: 'Q', size: 1), + LibraryFirstCharacter(key: 'R', title: 'R', size: 1), + LibraryFirstCharacter(key: 'S', title: 'S', size: 1), + LibraryFirstCharacter(key: 'T', title: 'T', size: 1), + LibraryFirstCharacter(key: 'U', title: 'U', size: 1), + LibraryFirstCharacter(key: 'V', title: 'V', size: 1), + LibraryFirstCharacter(key: 'W', title: 'W', size: 1), + LibraryFirstCharacter(key: 'X', title: 'X', size: 1), + LibraryFirstCharacter(key: 'Y', title: 'Y', size: 1), + LibraryFirstCharacter(key: 'Z', title: 'Z', size: 1), + ]; + + testWidgets('keeps the full alphabet visible in a short TV-height bar', (tester) async { + await tester.pumpWidget( + MaterialApp( + home: Scaffold( + body: SizedBox( + height: 420, + child: AlphaJumpBar(firstCharacters: alphabetCharacters, currentLetter: '#', onJump: (_) {}), + ), + ), + ), + ); + + expect(find.text('#'), findsOneWidget); + expect(find.text('U'), findsOneWidget); + expect(find.text('V'), findsOneWidget); + expect(find.text('W'), findsOneWidget); + expect(find.text('X'), findsOneWidget); + expect(find.text('Y'), findsOneWidget); + expect(find.text('Z'), findsOneWidget); + }); + testWidgets('Enter jumps to the highlighted letter', (tester) async { final focusNode = FocusNode(debugLabel: 'test_alpha_jump_bar'); addTearDown(focusNode.dispose);