Introduces shared seams for paginated views, D-pad reorder, media control routing, async singletons and the device method channel, then points the open-coded copies at them. Also removes unused models and duplicated provider/server plumbing, folds the twice-implemented artifact store in the server, and factors the repeated Flutter toolchain prologue in CI into a composite action.
126 lines
4.8 KiB
Dart
126 lines
4.8 KiB
Dart
import 'package:flutter/material.dart';
|
|
import 'package:flutter/services.dart';
|
|
|
|
import '../../i18n/strings.g.dart';
|
|
import '../../models/hotkey_model.dart';
|
|
import '../../services/keyboard_shortcuts_service.dart';
|
|
import '../../utils/app_logger.dart';
|
|
import '../../services/shortcut_action.dart';
|
|
import '../../utils/dialogs.dart';
|
|
import '../../utils/snackbar_helper.dart';
|
|
import '../../focus/focusable_button.dart';
|
|
import '../../theme/mono_tokens.dart';
|
|
import '../../widgets/focused_scroll_scaffold.dart';
|
|
import '../../widgets/focusable_list_tile.dart';
|
|
import '../../widgets/settings_section.dart';
|
|
import 'hotkey_recorder_widget.dart';
|
|
|
|
class KeyboardShortcutsScreen extends StatelessWidget {
|
|
final KeyboardShortcutsService keyboardService;
|
|
|
|
const KeyboardShortcutsScreen({super.key, required this.keyboardService});
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
return ListenableBuilder(
|
|
listenable: keyboardService,
|
|
builder: (context, _) {
|
|
final hotkeys = keyboardService.hotkeys;
|
|
final actions = hotkeys.keys.where((action) => ShortcutAction.fromId(action)?.isSupported ?? true).toList();
|
|
return FocusedScrollScaffold(
|
|
title: Text(t.settings.keyboardShortcuts),
|
|
slivers: [
|
|
SliverToBoxAdapter(
|
|
child: Padding(
|
|
padding: const EdgeInsets.only(left: 16, top: 16, right: 16),
|
|
child: Align(
|
|
alignment: .centerRight,
|
|
child: FocusableButton(
|
|
onPressed: () => _resetShortcuts(context),
|
|
child: TextButton(onPressed: () => _resetShortcuts(context), child: Text(t.common.reset)),
|
|
),
|
|
),
|
|
),
|
|
),
|
|
SliverToBoxAdapter(
|
|
child: Padding(
|
|
padding: const EdgeInsets.only(top: 8, bottom: 16),
|
|
child: SettingsGroup(
|
|
children: [
|
|
for (final action in actions)
|
|
FocusableListTile(
|
|
title: Text(keyboardService.getActionDisplayName(action)),
|
|
subtitle: Text(action),
|
|
trailing: Container(
|
|
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 6),
|
|
decoration: BoxDecoration(
|
|
border: Border.fromBorderSide(BorderSide(color: Theme.of(context).dividerColor)),
|
|
borderRadius: BorderRadius.circular(tokens(context).radiusSm),
|
|
),
|
|
child: Text(
|
|
keyboardService.formatHotkey(hotkeys[action]),
|
|
style: const TextStyle(fontFamily: 'monospace'),
|
|
),
|
|
),
|
|
onTap: () => _editHotkey(context, action, hotkeys[action]),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
),
|
|
],
|
|
);
|
|
},
|
|
);
|
|
}
|
|
|
|
Future<void> _resetShortcuts(BuildContext context) async {
|
|
await keyboardService.resetToDefaults();
|
|
if (context.mounted) showSuccessSnackBar(context, t.settings.shortcutsReset);
|
|
}
|
|
|
|
void _editHotkey(BuildContext screenContext, String action, HotKey? currentHotkey) {
|
|
final actionId = action;
|
|
final actionName = keyboardService.getActionDisplayName(actionId);
|
|
showScopedDialog<void>(
|
|
context: screenContext,
|
|
builder: (BuildContext context) {
|
|
return HotKeyRecorderWidget(
|
|
actionName: actionName,
|
|
currentHotKey: currentHotkey,
|
|
onHotKeyRecorded: (newHotkey) async {
|
|
final navigator = Navigator.of(context);
|
|
|
|
if (newHotkey != null) {
|
|
final existingAction = keyboardService.getActionForHotkey(newHotkey);
|
|
if (existingAction != null && existingAction != actionId) {
|
|
navigator.pop();
|
|
showErrorSnackBar(
|
|
screenContext,
|
|
t.settings.shortcutAlreadyAssigned(action: keyboardService.getActionDisplayName(existingAction)),
|
|
);
|
|
return;
|
|
}
|
|
}
|
|
|
|
try {
|
|
await keyboardService.setHotkey(actionId, newHotkey);
|
|
} on PlatformException catch (error, stackTrace) {
|
|
appLogger.e('Failed to update keyboard shortcut', error: error, stackTrace: stackTrace);
|
|
if (screenContext.mounted) showErrorSnackBar(screenContext, t.common.error);
|
|
return;
|
|
}
|
|
|
|
if (!context.mounted) return;
|
|
navigator.pop();
|
|
if (screenContext.mounted) {
|
|
showSuccessSnackBar(screenContext, t.settings.shortcutUpdated(action: actionName));
|
|
}
|
|
},
|
|
onCancel: () => Navigator.pop(context),
|
|
);
|
|
},
|
|
);
|
|
}
|
|
}
|