fix(tv): keep alpha bar alphabet visible

This commit is contained in:
edde746
2026-05-25 16:17:27 +02:00
parent 8d4ef2757a
commit 9365fcf246
2 changed files with 69 additions and 9 deletions
+18 -9
View File
@@ -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<LibraryFirstCharacter> firstCharacters;
@@ -63,6 +63,10 @@ class _AlphaJumpBarState extends State<AlphaJumpBar> {
/// 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<AlphaJumpBar> {
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<AlphaJumpBar> {
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<AlphaJumpBar> {
}
},
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<AlphaJumpBar> {
}
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,
),
@@ -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);