diff --git a/lib/screens/discover_screen.dart b/lib/screens/discover_screen.dart index ddd5c556..80537126 100644 --- a/lib/screens/discover_screen.dart +++ b/lib/screens/discover_screen.dart @@ -212,6 +212,7 @@ class _DiscoverScreenState extends State // Hero and app bar focus late FocusNode _heroFocusNode; final _actionBarKey = GlobalKey(); + final _serverActivitiesButtonKey = GlobalKey(); final _userMenuKey = GlobalKey>(); /// Backend-neutral hero client lookup. Returns the actual @@ -1334,7 +1335,10 @@ class _DiscoverScreenState extends State // a permanently empty popover. if (PlatformDetector.isDesktop(context) && context.select((p) => p.hasOnlinePlexServers)) - const FocusableAction(child: ServerActivitiesButton()), + FocusableAction( + onPressed: () => _serverActivitiesButtonKey.currentState?.togglePanel(), + child: ServerActivitiesButton(key: _serverActivitiesButtonKey), + ), // User menu — profiles + sign out _buildUserMenuAction(context), ], diff --git a/lib/widgets/server_activities_button.dart b/lib/widgets/server_activities_button.dart index ce0c5169..9c62a074 100644 --- a/lib/widgets/server_activities_button.dart +++ b/lib/widgets/server_activities_button.dart @@ -16,7 +16,7 @@ class ServerActivitiesButton extends StatefulWidget { const ServerActivitiesButton({super.key}); @override - State createState() => _ServerActivitiesButtonState(); + State createState() => ServerActivitiesButtonState(); } enum _FetchState { loading, loaded, error } @@ -38,7 +38,7 @@ class _PanelData { static const loading = _PanelData(fetchState: _FetchState.loading, results: []); } -class _ServerActivitiesButtonState extends State { +class ServerActivitiesButtonState extends State { final _buttonKey = GlobalKey(); OverlayEntry? _overlayEntry; final _panelNotifier = ValueNotifier<_PanelData>(_PanelData.loading); @@ -64,7 +64,7 @@ class _ServerActivitiesButtonState extends State { _overlayEntry = null; } - void _togglePanel() { + void togglePanel() { if (_overlayEntry != null) { _removeOverlay(); return; @@ -343,7 +343,7 @@ class _ServerActivitiesButtonState extends State { return IconButton( key: _buttonKey, icon: const AppIcon(Symbols.monitor_heart_rounded, color: Colors.white), - onPressed: _togglePanel, + onPressed: togglePanel, tooltip: t.serverTasks.title, ); } diff --git a/test/focus/key_event_utils_test.dart b/test/focus/key_event_utils_test.dart index 0d410527..8323e4f8 100644 --- a/test/focus/key_event_utils_test.dart +++ b/test/focus/key_event_utils_test.dart @@ -194,6 +194,34 @@ void main() { expect(FocusManager.instance.primaryFocus?.debugLabel, 'left-target'); }); + testWidgets('invokes custom child action on select', (tester) async { + final key = GlobalKey(); + var activations = 0; + + await tester.pumpWidget( + MaterialApp( + home: Scaffold( + body: FocusableActionBar( + key: key, + actions: [FocusableAction(onPressed: () => activations++, child: const SizedBox(width: 48, height: 48))], + ), + ), + ), + ); + await tester.pump(); + + key.currentState!.requestFocusOnFirst(); + await tester.pump(); + expect(FocusManager.instance.primaryFocus?.debugLabel, 'ActionBar[0]'); + + await tester.sendKeyDownEvent(LogicalKeyboardKey.select); + await tester.pump(); + await tester.sendKeyUpEvent(LogicalKeyboardKey.select); + await tester.pump(); + + expect(activations, 1); + }); + testWidgets('moves through detail actions when trailer is inserted before shuffle', (tester) async { final play = FocusNode(debugLabel: 'detail_play'); final outside = FocusNode(debugLabel: 'outside'); diff --git a/test/widgets/server_activities_button_test.dart b/test/widgets/server_activities_button_test.dart new file mode 100644 index 00000000..ed799831 --- /dev/null +++ b/test/widgets/server_activities_button_test.dart @@ -0,0 +1,50 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:plezy/i18n/strings.g.dart'; +import 'package:plezy/providers/multi_server_provider.dart'; +import 'package:plezy/services/data_aggregation_service.dart'; +import 'package:plezy/services/multi_server_manager.dart'; +import 'package:plezy/widgets/server_activities_button.dart'; +import 'package:provider/provider.dart'; + +void main() { + TestWidgetsFlutterBinding.ensureInitialized(); + + setUp(() { + LocaleSettings.setLocaleSync(AppLocale.en); + }); + + testWidgets('togglePanel opens and closes the server activities overlay', (tester) async { + final manager = MultiServerManager(); + final multiServerProvider = MultiServerProvider(manager, DataAggregationService(manager)); + final buttonKey = GlobalKey(); + + addTearDown(() { + multiServerProvider.dispose(); + manager.dispose(); + }); + + await tester.pumpWidget( + TranslationProvider( + child: ChangeNotifierProvider.value( + value: multiServerProvider, + child: MaterialApp( + home: Scaffold(body: ServerActivitiesButton(key: buttonKey)), + ), + ), + ), + ); + + buttonKey.currentState!.togglePanel(); + await tester.pump(); + await tester.pump(); + + expect(find.text(t.serverTasks.title), findsOneWidget); + expect(find.text(t.serverTasks.noTasks), findsOneWidget); + + buttonKey.currentState!.togglePanel(); + await tester.pump(); + + expect(find.text(t.serverTasks.title), findsNothing); + }); +}