fix: dpad back navigation for settings sub-screens

This commit is contained in:
edde746
2026-02-18 08:06:48 +01:00
parent 64c0c9979b
commit 1fc0d0458b
6 changed files with 159 additions and 124 deletions
@@ -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<ExternalPlayerScreen> {
@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,
),
],
),
),
],
]),
),
),
),
],
],
),
),
);
}
+51 -46
View File
@@ -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<LogsScreen> {
@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),
),
),
],
),
),
);
}
+25 -16
View File
@@ -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<MpvConfigScreen> {
@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),
]),
),
),
),
],
],
),
),
);
}
+4 -1
View File
@@ -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<SettingsScreen> with FocusableTab {
@override
void focusActiveTabIfReady() {
_focusTracker.restoreFocus(fallbackKey: _kTheme);
if (InputModeTracker.isKeyboardMode(context)) {
_focusTracker.restoreFocus(fallbackKey: _kTheme);
}
}
/// Navigate focus to the sidebar
@@ -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<SubtitleStylingScreen> {
@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)])),
),
],
),
),
);
}
+1 -1
View File
@@ -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(