From 1fc0d0458b72929cdb400d3fa2b726c43471b13c Mon Sep 17 00:00:00 2001 From: edde746 <86283021+edde746@users.noreply.github.com> Date: Wed, 18 Feb 2026 08:06:48 +0100 Subject: [PATCH] fix: dpad back navigation for settings sub-screens --- .../settings/external_player_screen.dart | 109 ++++++++++-------- lib/screens/settings/logs_screen.dart | 97 ++++++++-------- lib/screens/settings/mpv_config_screen.dart | 41 ++++--- lib/screens/settings/settings_screen.dart | 5 +- .../settings/subtitle_styling_screen.dart | 29 +++-- lib/widgets/focused_scroll_scaffold.dart | 2 +- 6 files changed, 159 insertions(+), 124 deletions(-) diff --git a/lib/screens/settings/external_player_screen.dart b/lib/screens/settings/external_player_screen.dart index 0a78391b..1aa6e1e4 100644 --- a/lib/screens/settings/external_player_screen.dart +++ b/lib/screens/settings/external_player_screen.dart @@ -7,6 +7,7 @@ import 'package:material_symbols_icons/symbols.dart'; import '../../i18n/strings.g.dart'; import '../../models/external_player_models.dart'; import '../../services/settings_service.dart'; +import '../../focus/key_event_utils.dart'; import '../../widgets/desktop_app_bar.dart'; class ExternalPlayerScreen extends StatefulWidget { @@ -45,64 +46,72 @@ class _ExternalPlayerScreenState extends State { @override Widget build(BuildContext context) { if (_isLoading) { - return const Scaffold(body: Center(child: CircularProgressIndicator())); + return Focus( + autofocus: true, + onKeyEvent: (_, event) => handleBackKeyNavigation(context, event), + child: const Scaffold(body: Center(child: CircularProgressIndicator())), + ); } final knownPlayers = KnownPlayers.getForCurrentPlatform(); - return Scaffold( - body: CustomScrollView( - slivers: [ - CustomAppBar(title: Text(t.externalPlayer.title), pinned: true), - SliverPadding( - padding: const EdgeInsets.all(16), - sliver: SliverList( - delegate: SliverChildListDelegate([ - Card( - child: SwitchListTile( - secondary: const AppIcon(Symbols.open_in_new_rounded, fill: 1), - title: Text(t.externalPlayer.useExternalPlayer), - subtitle: Text(t.externalPlayer.useExternalPlayerDescription), - value: _useExternalPlayer, - onChanged: (value) async { - setState(() => _useExternalPlayer = value); - await _settingsService.setUseExternalPlayer(value); - }, - ), - ), - if (_useExternalPlayer) ...[ - const SizedBox(height: 16), + return Focus( + autofocus: true, + onKeyEvent: (_, event) => handleBackKeyNavigation(context, event), + child: Scaffold( + body: CustomScrollView( + slivers: [ + CustomAppBar(title: Text(t.externalPlayer.title), pinned: true), + SliverPadding( + padding: const EdgeInsets.all(16), + sliver: SliverList( + delegate: SliverChildListDelegate([ Card( - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Padding( - padding: const EdgeInsets.all(16), - child: Text( - t.externalPlayer.selectPlayer, - style: Theme.of(context).textTheme.titleMedium?.copyWith(fontWeight: FontWeight.bold), - ), - ), - // Known players - ...knownPlayers.map((player) => _buildPlayerTile(player)), - // Custom players - if (_customPlayers.isNotEmpty) const Divider(), - ..._customPlayers.map((player) => _buildPlayerTile(player, isCustom: true)), - // Add custom player button - const Divider(), - ListTile( - leading: const AppIcon(Symbols.add_rounded, fill: 1), - title: Text(t.externalPlayer.addCustomPlayer), - onTap: _showAddCustomPlayerDialog, - ), - ], + child: SwitchListTile( + secondary: const AppIcon(Symbols.open_in_new_rounded, fill: 1), + title: Text(t.externalPlayer.useExternalPlayer), + subtitle: Text(t.externalPlayer.useExternalPlayerDescription), + value: _useExternalPlayer, + onChanged: (value) async { + setState(() => _useExternalPlayer = value); + await _settingsService.setUseExternalPlayer(value); + }, ), ), - ], - ]), + if (_useExternalPlayer) ...[ + const SizedBox(height: 16), + Card( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Padding( + padding: const EdgeInsets.all(16), + child: Text( + t.externalPlayer.selectPlayer, + style: Theme.of(context).textTheme.titleMedium?.copyWith(fontWeight: FontWeight.bold), + ), + ), + // Known players + ...knownPlayers.map((player) => _buildPlayerTile(player)), + // Custom players + if (_customPlayers.isNotEmpty) const Divider(), + ..._customPlayers.map((player) => _buildPlayerTile(player, isCustom: true)), + // Add custom player button + const Divider(), + ListTile( + leading: const AppIcon(Symbols.add_rounded, fill: 1), + title: Text(t.externalPlayer.addCustomPlayer), + onTap: _showAddCustomPlayerDialog, + ), + ], + ), + ), + ], + ]), + ), ), - ), - ], + ], + ), ), ); } diff --git a/lib/screens/settings/logs_screen.dart b/lib/screens/settings/logs_screen.dart index 193d8734..cabf5481 100644 --- a/lib/screens/settings/logs_screen.dart +++ b/lib/screens/settings/logs_screen.dart @@ -9,6 +9,7 @@ import 'package:logger/logger.dart'; import '../../i18n/strings.g.dart'; import '../../utils/app_logger.dart'; import '../../utils/snackbar_helper.dart'; +import '../../focus/key_event_utils.dart'; import '../../widgets/desktop_app_bar.dart'; class LogsScreen extends StatefulWidget { @@ -165,53 +166,57 @@ class _LogsScreenState extends State { @override Widget build(BuildContext context) { - return Scaffold( - body: CustomScrollView( - slivers: [ - CustomAppBar( - title: Text(t.screens.logs), - pinned: true, - actions: [ - IconButton( - icon: const AppIcon(Symbols.refresh_rounded, fill: 1), - onPressed: _loadLogs, - tooltip: t.common.refresh, - ), - IconButton( - icon: const AppIcon(Symbols.upload_rounded, fill: 1), - onPressed: _logs.isNotEmpty ? _uploadLogs : null, - tooltip: t.logs.uploadLogs, - ), - IconButton( - icon: const AppIcon(Symbols.content_copy_rounded, fill: 1), - onPressed: _logs.isNotEmpty ? _copyAllLogs : null, - tooltip: t.logs.copyLogs, - ), - IconButton( - icon: const AppIcon(Symbols.delete_outline_rounded, fill: 1), - onPressed: _logs.isNotEmpty ? _clearLogs : null, - tooltip: t.logs.clearLogs, - ), - ], - ), - if (_logs.isEmpty) - SliverFillRemaining(child: Center(child: Text(t.messages.noLogsAvailable))) - else - SliverPadding( - padding: const EdgeInsets.all(8), - sliver: SliverList( - delegate: SliverChildBuilderDelegate((context, index) { - final log = _logs[index]; - return _LogEntryCard( - log: log, - formatTime: _formatTime, - levelColor: _getLevelColor(log.level), - levelIcon: _getLevelIcon(log.level), - ); - }, childCount: _logs.length), - ), + return Focus( + autofocus: true, + onKeyEvent: (_, event) => handleBackKeyNavigation(context, event), + child: Scaffold( + body: CustomScrollView( + slivers: [ + CustomAppBar( + title: Text(t.screens.logs), + pinned: true, + actions: [ + IconButton( + icon: const AppIcon(Symbols.refresh_rounded, fill: 1), + onPressed: _loadLogs, + tooltip: t.common.refresh, + ), + IconButton( + icon: const AppIcon(Symbols.upload_rounded, fill: 1), + onPressed: _logs.isNotEmpty ? _uploadLogs : null, + tooltip: t.logs.uploadLogs, + ), + IconButton( + icon: const AppIcon(Symbols.content_copy_rounded, fill: 1), + onPressed: _logs.isNotEmpty ? _copyAllLogs : null, + tooltip: t.logs.copyLogs, + ), + IconButton( + icon: const AppIcon(Symbols.delete_outline_rounded, fill: 1), + onPressed: _logs.isNotEmpty ? _clearLogs : null, + tooltip: t.logs.clearLogs, + ), + ], ), - ], + if (_logs.isEmpty) + SliverFillRemaining(child: Center(child: Text(t.messages.noLogsAvailable))) + else + SliverPadding( + padding: const EdgeInsets.all(8), + sliver: SliverList( + delegate: SliverChildBuilderDelegate((context, index) { + final log = _logs[index]; + return _LogEntryCard( + log: log, + formatTime: _formatTime, + levelColor: _getLevelColor(log.level), + levelIcon: _getLevelIcon(log.level), + ); + }, childCount: _logs.length), + ), + ), + ], + ), ), ); } diff --git a/lib/screens/settings/mpv_config_screen.dart b/lib/screens/settings/mpv_config_screen.dart index 25b3f490..2689eb03 100644 --- a/lib/screens/settings/mpv_config_screen.dart +++ b/lib/screens/settings/mpv_config_screen.dart @@ -6,6 +6,7 @@ import '../../models/mpv_config_models.dart'; import '../../utils/dialogs.dart'; import '../../utils/snackbar_helper.dart'; import '../../services/settings_service.dart'; +import '../../focus/key_event_utils.dart'; import '../../widgets/desktop_app_bar.dart'; class MpvConfigScreen extends StatefulWidget { @@ -236,25 +237,33 @@ class _MpvConfigScreenState extends State { @override Widget build(BuildContext context) { if (_isLoading) { - return const Scaffold(body: Center(child: CircularProgressIndicator())); + return Focus( + autofocus: true, + onKeyEvent: (_, event) => handleBackKeyNavigation(context, event), + child: const Scaffold(body: Center(child: CircularProgressIndicator())), + ); } - return Scaffold( - body: CustomScrollView( - slivers: [ - CustomAppBar(title: Text(t.screens.mpvConfig), pinned: true), - SliverPadding( - padding: const EdgeInsets.all(16), - sliver: SliverList( - delegate: SliverChildListDelegate([ - _buildPresetsCard(), - const SizedBox(height: 16), - _buildEntriesCard(), - const SizedBox(height: 24), - ]), + return Focus( + autofocus: true, + onKeyEvent: (_, event) => handleBackKeyNavigation(context, event), + child: Scaffold( + body: CustomScrollView( + slivers: [ + CustomAppBar(title: Text(t.screens.mpvConfig), pinned: true), + SliverPadding( + padding: const EdgeInsets.all(16), + sliver: SliverList( + delegate: SliverChildListDelegate([ + _buildPresetsCard(), + const SizedBox(height: 16), + _buildEntriesCard(), + const SizedBox(height: 24), + ]), + ), ), - ), - ], + ], + ), ), ); } diff --git a/lib/screens/settings/settings_screen.dart b/lib/screens/settings/settings_screen.dart index 4c1837f2..551bf7ee 100644 --- a/lib/screens/settings/settings_screen.dart +++ b/lib/screens/settings/settings_screen.dart @@ -10,6 +10,7 @@ import 'package:provider/provider.dart'; import 'package:url_launcher/url_launcher.dart'; import '../../focus/focus_memory_tracker.dart'; +import '../../focus/input_mode_tracker.dart'; import '../../i18n/strings.g.dart'; import '../main_screen.dart'; import '../../mixins/refreshable.dart'; @@ -147,7 +148,9 @@ class _SettingsScreenState extends State with FocusableTab { @override void focusActiveTabIfReady() { - _focusTracker.restoreFocus(fallbackKey: _kTheme); + if (InputModeTracker.isKeyboardMode(context)) { + _focusTracker.restoreFocus(fallbackKey: _kTheme); + } } /// Navigate focus to the sidebar diff --git a/lib/screens/settings/subtitle_styling_screen.dart b/lib/screens/settings/subtitle_styling_screen.dart index 2eec6e68..871781de 100644 --- a/lib/screens/settings/subtitle_styling_screen.dart +++ b/lib/screens/settings/subtitle_styling_screen.dart @@ -5,6 +5,7 @@ import 'package:flex_color_picker/flex_color_picker.dart'; import '../../i18n/strings.g.dart'; import '../../services/settings_service.dart'; import '../../utils/platform_detector.dart'; +import '../../focus/key_event_utils.dart'; import '../../widgets/desktop_app_bar.dart'; import '../../widgets/tv_color_picker.dart'; import '../../widgets/tv_number_spinner.dart'; @@ -282,18 +283,26 @@ class _SubtitleStylingScreenState extends State { @override Widget build(BuildContext context) { if (_isLoading) { - return const Scaffold(body: Center(child: CircularProgressIndicator())); + return Focus( + autofocus: true, + onKeyEvent: (_, event) => handleBackKeyNavigation(context, event), + child: const Scaffold(body: Center(child: CircularProgressIndicator())), + ); } - return Scaffold( - body: CustomScrollView( - slivers: [ - CustomAppBar(title: Text(t.screens.subtitleStyling), pinned: true), - SliverPadding( - padding: const EdgeInsets.all(16), - sliver: SliverList(delegate: SliverChildListDelegate([_buildStylingCard(), const SizedBox(height: 24)])), - ), - ], + return Focus( + autofocus: true, + onKeyEvent: (_, event) => handleBackKeyNavigation(context, event), + child: Scaffold( + body: CustomScrollView( + slivers: [ + CustomAppBar(title: Text(t.screens.subtitleStyling), pinned: true), + SliverPadding( + padding: const EdgeInsets.all(16), + sliver: SliverList(delegate: SliverChildListDelegate([_buildStylingCard(), const SizedBox(height: 24)])), + ), + ], + ), ), ); } diff --git a/lib/widgets/focused_scroll_scaffold.dart b/lib/widgets/focused_scroll_scaffold.dart index 1625460e..4dcea050 100644 --- a/lib/widgets/focused_scroll_scaffold.dart +++ b/lib/widgets/focused_scroll_scaffold.dart @@ -41,7 +41,7 @@ class FocusedScrollScaffold extends StatelessWidget { @override Widget build(BuildContext context) { return Focus( - canRequestFocus: false, + autofocus: true, onKeyEvent: (_, event) => handleBackKeyNavigation(context, event), child: Scaffold( body: CustomScrollView(