Files
MWClash/lib/views/hotkey.dart
chen08209 ed7868282a Add android separates the core process
Support core status check and force restart

Optimize proxies page and access page

Update flutter and pub dependencies

Update go version

Optimize more details
2025-09-23 15:23:58 +08:00

227 lines
6.6 KiB
Dart

import 'package:fl_clash/common/common.dart';
import 'package:fl_clash/enum/enum.dart';
import 'package:fl_clash/models/models.dart';
import 'package:fl_clash/providers/providers.dart';
import 'package:fl_clash/state.dart';
import 'package:fl_clash/widgets/card.dart';
import 'package:fl_clash/widgets/dialog.dart';
import 'package:fl_clash/widgets/list.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:intl/intl.dart';
extension IntlExt on Intl {
static String actionMessage(String messageText) =>
Intl.message('action_$messageText');
}
class HotKeyView extends StatelessWidget {
const HotKeyView({super.key});
String getSubtitle(HotKeyAction hotKeyAction) {
final key = hotKeyAction.key;
if (key == null) {
return appLocalizations.noHotKey;
}
final modifierLabels = hotKeyAction.modifiers.map(
(item) => item.physicalKeys.first.label,
);
var text = '';
if (modifierLabels.isNotEmpty) {
text += "${modifierLabels.join(" ")}+";
}
text += PhysicalKeyboardKey(key).label;
return text;
}
@override
Widget build(BuildContext context) {
return ListView.builder(
itemCount: HotAction.values.length,
itemBuilder: (_, index) {
final hotAction = HotAction.values[index];
return Consumer(
builder: (_, ref, _) {
final hotKeyAction = ref.watch(getHotKeyActionProvider(hotAction));
return ListItem(
title: Text(IntlExt.actionMessage(hotAction.name)),
subtitle: Text(
getSubtitle(hotKeyAction),
style: context.textTheme.bodyMedium?.copyWith(
color: context.colorScheme.primary,
),
),
onTap: () {
globalState.showCommonDialog(
child: HotKeyRecorder(hotKeyAction: hotKeyAction),
);
},
);
},
);
},
);
}
}
class HotKeyRecorder extends StatefulWidget {
final HotKeyAction hotKeyAction;
const HotKeyRecorder({super.key, required this.hotKeyAction});
@override
State<HotKeyRecorder> createState() => _HotKeyRecorderState();
}
class _HotKeyRecorderState extends State<HotKeyRecorder> {
late ValueNotifier<HotKeyAction> hotKeyActionNotifier;
@override
void initState() {
super.initState();
hotKeyActionNotifier = ValueNotifier<HotKeyAction>(
widget.hotKeyAction.copyWith(),
);
HardwareKeyboard.instance.addHandler(_handleKeyEvent);
}
bool _handleKeyEvent(KeyEvent keyEvent) {
if (keyEvent is KeyUpEvent) return false;
final keys = HardwareKeyboard.instance.physicalKeysPressed;
final key = keyEvent.physicalKey;
final modifiers = KeyboardModifier.values
.where(
(e) =>
e.physicalKeys.any(keys.contains) &&
!e.physicalKeys.contains(key),
)
.toSet();
hotKeyActionNotifier.value = hotKeyActionNotifier.value.copyWith(
modifiers: modifiers,
key: key.usbHidUsage,
);
return false;
}
@override
void dispose() {
HardwareKeyboard.instance.removeHandler(_handleKeyEvent);
super.dispose();
}
void _handleRemove() {
Navigator.of(context).pop();
globalState.appController.updateOrAddHotKeyAction(
hotKeyActionNotifier.value.copyWith(modifiers: {}, key: null),
);
}
void _handleConfirm() {
Navigator.of(context).pop();
final config = globalState.config;
final currentHotkeyAction = hotKeyActionNotifier.value;
if (currentHotkeyAction.key == null ||
currentHotkeyAction.modifiers.isEmpty) {
globalState.showMessage(
title: appLocalizations.tip,
message: TextSpan(text: appLocalizations.inputCorrectHotkey),
);
return;
}
final hotKeyActions = config.hotKeyActions;
final index = hotKeyActions.indexWhere(
(item) =>
item.key == currentHotkeyAction.key &&
keyboardModifierListEquality.equals(
item.modifiers,
currentHotkeyAction.modifiers,
),
);
if (index != -1) {
globalState.showMessage(
title: appLocalizations.tip,
message: TextSpan(text: appLocalizations.hotkeyConflict),
);
return;
}
globalState.appController.updateOrAddHotKeyAction(currentHotkeyAction);
}
@override
Widget build(BuildContext context) {
return Focus(
onKeyEvent: (_, _) {
return KeyEventResult.handled;
},
autofocus: true,
child: CommonDialog(
title: IntlExt.actionMessage(widget.hotKeyAction.action.name),
actions: [
TextButton(
onPressed: () {
_handleRemove();
},
child: Text(appLocalizations.remove),
),
const SizedBox(width: 8),
TextButton(
onPressed: () {
_handleConfirm();
},
child: Text(appLocalizations.confirm),
),
],
child: ValueListenableBuilder(
valueListenable: hotKeyActionNotifier,
builder: (_, hotKeyAction, _) {
final key = hotKeyAction.key;
final modifiers = hotKeyAction.modifiers;
return SizedBox(
width: dialogCommonWidth,
child: key != null
? Wrap(
spacing: 8,
crossAxisAlignment: WrapCrossAlignment.center,
children: [
for (final modifier in modifiers)
KeyboardKeyBox(
keyboardKey: modifier.physicalKeys.first,
),
if (modifiers.isNotEmpty)
Text('+', style: context.textTheme.titleMedium),
KeyboardKeyBox(keyboardKey: PhysicalKeyboardKey(key)),
],
)
: Text(
appLocalizations.pressKeyboard,
style: context.textTheme.titleMedium,
),
);
},
),
),
);
}
}
class KeyboardKeyBox extends StatelessWidget {
final KeyboardKey keyboardKey;
const KeyboardKeyBox({super.key, required this.keyboardKey});
@override
Widget build(BuildContext context) {
return CommonCard(
type: CommonCardType.filled,
child: Padding(
padding: const EdgeInsets.all(12),
child: Text(keyboardKey.label, style: const TextStyle(fontSize: 16)),
),
onPressed: () {},
);
}
}