fix: dpad back navigation for settings sub-screens
This commit is contained in:
@@ -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,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
]),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -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),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -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),
|
||||
]),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -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)])),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -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(
|
||||
|
||||
Reference in New Issue
Block a user