Files
superport_v2/lib/widgets/components/keyboard_shortcuts.dart

144 lines
3.5 KiB
Dart

import 'dart:async';
import 'package:flutter/services.dart';
import 'package:flutter/widgets.dart';
class DialogKeyboardShortcuts extends StatefulWidget {
const DialogKeyboardShortcuts({
super.key,
required this.child,
this.onEscape,
this.onSubmit,
this.enableFocusTrap = true,
});
final Widget child;
final VoidCallback? onEscape;
final FutureOr<void> Function()? onSubmit;
final bool enableFocusTrap;
@override
State<DialogKeyboardShortcuts> createState() =>
_DialogKeyboardShortcutsState();
}
class _DialogKeyboardShortcutsState extends State<DialogKeyboardShortcuts> {
late final FocusScopeNode _focusScopeNode;
@override
void initState() {
super.initState();
_focusScopeNode = FocusScopeNode(debugLabel: 'DialogKeyboardShortcuts');
}
@override
void dispose() {
_focusScopeNode.dispose();
super.dispose();
}
bool get _hasSubmitHandler => widget.onSubmit != null;
bool _shouldHandleSubmitKey() {
if (!_hasSubmitHandler) {
return false;
}
final primaryFocus = FocusManager.instance.primaryFocus;
if (primaryFocus == null) {
return true;
}
final context = primaryFocus.context;
if (context == null) {
return true;
}
EditableText? editable;
final widget = context.widget;
if (widget is EditableText) {
editable = widget;
} else {
editable = context.findAncestorWidgetOfExactType<EditableText>();
}
if (editable != null) {
// Multi-line 입력에서는 엔터 키를 입력값으로 전달한다.
final bool isMultiline =
editable.maxLines == null || editable.maxLines! > 1;
if (isMultiline) {
return false;
}
}
return true;
}
Map<ShortcutActivator, Intent> get _shortcuts {
final shortcuts = <ShortcutActivator, Intent>{
LogicalKeySet(LogicalKeyboardKey.escape): const _DismissIntent(),
};
if (_hasSubmitHandler) {
shortcuts[LogicalKeySet(LogicalKeyboardKey.enter)] =
const _SubmitIntent();
shortcuts[LogicalKeySet(LogicalKeyboardKey.numpadEnter)] =
const _SubmitIntent();
}
return shortcuts;
}
Map<Type, Action<Intent>> get _actions {
return <Type, Action<Intent>>{
_DismissIntent: CallbackAction<_DismissIntent>(
onInvoke: (intent) {
widget.onEscape?.call();
return null;
},
),
_SubmitIntent: CallbackAction<_SubmitIntent>(
onInvoke: (intent) {
if (_shouldHandleSubmitKey()) {
final callback = widget.onSubmit;
if (callback != null) {
final result = callback();
if (result is Future<void>) {
return result;
}
}
}
return null;
},
),
};
}
@override
Widget build(BuildContext context) {
Widget content = widget.child;
if (widget.enableFocusTrap) {
content = FocusTraversalGroup(
policy: WidgetOrderTraversalPolicy(),
child: FocusScope(
node: _focusScopeNode,
autofocus: true,
child: content,
),
);
} else {
content = FocusTraversalGroup(
policy: WidgetOrderTraversalPolicy(),
child: FocusScope(autofocus: true, child: content),
);
}
return Shortcuts(
shortcuts: _shortcuts,
child: Actions(actions: _actions, child: content),
);
}
}
class _DismissIntent extends Intent {
const _DismissIntent();
}
class _SubmitIntent extends Intent {
const _SubmitIntent();
}