fix(catalog): keep the watchlist action focusable while membership loads

Keep the action enabled and let a press retry the snapshot, as the media
detail action bar already does. A disabled sole action left the detail screen
with no initial D-pad focus on TV.
This commit is contained in:
edde746
2026-07-27 17:44:52 +02:00
parent c64e6fc519
commit 088501513a
2 changed files with 13 additions and 15 deletions
+9 -4
View File
@@ -289,8 +289,15 @@ class _CatalogItemDetailScreenState extends State<CatalogItemDetailScreen> {
Future<void> _toggleWatchlist() async {
final source = _watchlistSource;
if (source == null || _mutatingWatchlist) return;
final current = _isOnWatchlist;
if (source == null || current == null || _mutatingWatchlist) return;
// Parity with lib/screens/media_detail/action_buttons.dart: the action
// stays focusable while membership is unknown, and a press kicks the
// snapshot load rather than toggling a state we haven't read yet.
if (current == null) {
unawaited(source.ensureWatchlistLoaded());
return;
}
_mutatingWatchlist = true;
try {
if (current) {
@@ -564,9 +571,7 @@ class _CatalogItemDetailScreenState extends State<CatalogItemDetailScreen> {
tooltip: onWatchlist ?? false
? t.explore.removeFromWatchlist
: t.explore.addToWatchlist,
onPressed: onWatchlist == null
? null
: () => unawaited(_toggleWatchlist()),
onPressed: () => unawaited(_toggleWatchlist()),
),
if (_requestSource case final SeerrCatalogSource seerr)
FocusableAction(
@@ -243,31 +243,24 @@ void main() {
expect(FocusManager.instance.primaryFocus?.debugLabel, 'catalog_library_match_1');
});
testWidgets('loading watchlist action cannot receive focus or activate', (tester) async {
testWidgets('pending watchlist action keeps initial focus and its press retries the snapshot', (tester) async {
final source = _FakeCatalogSource(watchlistLoading: true);
await _pumpDetail(tester, source);
final actionBar = tester.widget<FocusableActionBar>(find.byType(FocusableActionBar));
expect(actionBar.actions.single.onPressed, isNull);
expect(FocusManager.instance.primaryFocus?.debugLabel, 'ActionBar[0]');
final actionNode = tester
.widgetList<Focus>(find.descendant(of: find.byType(FocusableActionBar), matching: find.byType(Focus)))
.map((widget) => widget.focusNode)
.whereType<FocusNode>()
.singleWhere((node) => node.debugLabel == 'ActionBar[0]');
expect(actionNode.canRequestFocus, isFalse);
expect(actionNode.canRequestFocus, isTrue);
actionNode.requestFocus();
await tester.pump();
await tester.sendKeyEvent(LogicalKeyboardKey.select);
expect(actionNode.hasFocus, isFalse);
await tester.pump();
expect(source.addToWatchlistCalls, 0);
source.completeWatchlistLoad();
await tester.pump();
final loadedActionBar = tester.widget<FocusableActionBar>(find.byType(FocusableActionBar));
expect(loadedActionBar.actions.single.onPressed, isNotNull);
actionNode.requestFocus();
await tester.pump();
await tester.sendKeyEvent(LogicalKeyboardKey.select);
await tester.pump();
expect(source.addToWatchlistCalls, 1);