feat: customizable keyboard shortcuts

This commit is contained in:
2023-10-12 01:56:05 +03:00
parent 2db3da1438
commit a6532ae3c2
11 changed files with 613 additions and 95 deletions

View File

@@ -23,7 +23,7 @@ class MUDAction {
for (var i = 0; i < matches.length; i++) {
content = content.replaceAll('%$i', matches[i]);
}
content = _doSpecialReplacements(store, content);
content = doVariableReplacements(store, content);
debugPrint('MUDAction.invoking: $content');
switch (target) {
@@ -76,7 +76,7 @@ class MUDAction {
'target': target.name,
};
String _doSpecialReplacements(GameStore store, String content) {
static String doVariableReplacements(GameStore store, String content) {
debugPrint('MUDAction._doSpecialReplacements: $content');
content = content
.replaceAll('%PASSWORD', store.currentProfile.password)

View File

@@ -97,6 +97,7 @@ class Automation {
void invokeEffect(GameStore store, String line) {
invokeCount++;
line = MUDAction.doVariableReplacements(store, line);
action.invoke(store, this, allMatches(line));
}

View File

@@ -2,6 +2,7 @@ import 'package:encrypt/encrypt.dart' as enc;
import 'package:flutter/foundation.dart';
import '../consts.dart';
import '../keyboard_shortcuts.dart';
import '../secrets.dart';
import '../storage.dart';
import '../string_utils.dart';
@@ -149,6 +150,16 @@ class MUDProfile {
return buttonSetFiles.map((e) => GameButtonSetData.fromJson(e)).toList();
}
Future<KeyboardShortcuts> loadKeyboardShortcuts() async {
debugPrint('MUDProfile.loadKeyboardShortcuts: $id');
final shortcuts =
await ProfileStorage.readProfileFile(id, 'keyboard_shortcuts');
if (shortcuts == null) {
return KeyboardShortcuts.empty();
}
return KeyboardShortcuts.fromJson(shortcuts);
}
Future<void> saveAlias(Alias alias) async {
debugPrint('MUDProfile.saveAlias: $id/aliases/${alias.id}');
return ProfileStorage.writeProfileFile(
@@ -177,6 +188,12 @@ class MUDProfile {
id, 'button_sets/${buttonSet.id}', buttonSet.toJson());
}
Future<void> saveKeyboardShortcuts(KeyboardShortcuts shortcuts) async {
debugPrint('MUDProfile.saveKeyboardShortcuts: $id');
return ProfileStorage.writeProfileFile(
id, 'keyboard_shortcuts', shortcuts.toJson());
}
Future<void> deleteButtonSet(GameButtonSetData buttonSet) async {
debugPrint('MUDProfile.deleteButtonSet: $id/button_sets/${buttonSet.id}');
return ProfileStorage.deleteProfileFile(id, 'button_sets/${buttonSet.id}');

View File

@@ -0,0 +1,282 @@
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'store.dart';
class KeyboardIntent extends Intent {
const KeyboardIntent(this.key, this.context);
final NumpadKey key;
final BuildContext context;
}
class KeyboardAction extends Action<KeyboardIntent> with GameStoreMixin {
@override
void invoke(covariant KeyboardIntent intent) {
final store = storeOf(intent.context);
store.onShortcut(intent.key, intent.context);
}
}
enum NumpadKey {
numpad0,
numpad1,
numpad2,
numpad3,
numpad4,
numpad5,
numpad6,
numpad7,
numpad8,
numpad9,
numpadEnter,
numpadDecimal,
numpadAdd,
numpadSubtract,
numpadMultiply,
numpadDivide,
numpadEqual,
}
class KeyboardShortcuts {
String numpad0;
String numpad1;
String numpad2;
String numpad3;
String numpad4;
String numpad5;
String numpad6;
String numpad7;
String numpad8;
String numpad9;
String numpadEnter;
String numpadDecimal;
String numpadAdd;
String numpadSubtract;
String numpadMultiply;
String numpadDivide;
String numpadEqual;
KeyboardShortcuts({
this.numpad0 = '',
this.numpad1 = '',
this.numpad2 = '',
this.numpad3 = '',
this.numpad4 = '',
this.numpad5 = '',
this.numpad6 = '',
this.numpad7 = '',
this.numpad8 = '',
this.numpad9 = '',
this.numpadEnter = '',
this.numpadDecimal = '',
this.numpadAdd = '',
this.numpadSubtract = '',
this.numpadMultiply = '',
this.numpadDivide = '',
this.numpadEqual = '',
});
factory KeyboardShortcuts.empty() => KeyboardShortcuts(
numpad0: '',
numpad1: '',
numpad2: '',
numpad3: '',
numpad4: '',
numpad5: '',
numpad6: '',
numpad7: '',
numpad8: '',
numpad9: '',
numpadEnter: '',
numpadDecimal: '',
numpadAdd: '',
numpadSubtract: '',
numpadMultiply: '',
numpadDivide: '',
numpadEqual: '',
);
String get(NumpadKey key) =>
{
NumpadKey.numpad0: numpad0,
NumpadKey.numpad1: numpad1,
NumpadKey.numpad2: numpad2,
NumpadKey.numpad3: numpad3,
NumpadKey.numpad4: numpad4,
NumpadKey.numpad5: numpad5,
NumpadKey.numpad6: numpad6,
NumpadKey.numpad7: numpad7,
NumpadKey.numpad8: numpad8,
NumpadKey.numpad9: numpad9,
NumpadKey.numpadEnter: numpadEnter,
NumpadKey.numpadDecimal: numpadDecimal,
NumpadKey.numpadAdd: numpadAdd,
NumpadKey.numpadSubtract: numpadSubtract,
NumpadKey.numpadMultiply: numpadMultiply,
NumpadKey.numpadDivide: numpadDivide,
NumpadKey.numpadEqual: numpadEqual,
}[key] ??
'';
factory KeyboardShortcuts.fromJson(Map<String, dynamic> json) {
return KeyboardShortcuts(
numpad0: json['numpad0'] ?? '',
numpad1: json['numpad1'] ?? '',
numpad2: json['numpad2'] ?? '',
numpad3: json['numpad3'] ?? '',
numpad4: json['numpad4'] ?? '',
numpad5: json['numpad5'] ?? '',
numpad6: json['numpad6'] ?? '',
numpad7: json['numpad7'] ?? '',
numpad8: json['numpad8'] ?? '',
numpad9: json['numpad9'] ?? '',
numpadEnter: json['numpadEnter'] ?? '',
numpadDecimal: json['numpadDecimal'] ?? '',
numpadAdd: json['numpadAdd'] ?? '',
numpadSubtract: json['numpadSubtract'] ?? '',
numpadMultiply: json['numpadMultiply'] ?? '',
numpadDivide: json['numpadDivide'] ?? '',
numpadEqual: json['numpadEqual'] ?? '',
);
}
Map<String, dynamic> toJson() {
return {
'numpad0': numpad0,
'numpad1': numpad1,
'numpad2': numpad2,
'numpad3': numpad3,
'numpad4': numpad4,
'numpad5': numpad5,
'numpad6': numpad6,
'numpad7': numpad7,
'numpad8': numpad8,
'numpad9': numpad9,
'numpadEnter': numpadEnter,
'numpadDecimal': numpadDecimal,
'numpadAdd': numpadAdd,
'numpadSubtract': numpadSubtract,
'numpadMultiply': numpadMultiply,
'numpadDivide': numpadDivide,
'numpadEqual': numpadEqual,
};
}
KeyboardShortcuts copyWith({
String? numpad0,
String? numpad1,
String? numpad2,
String? numpad3,
String? numpad4,
String? numpad5,
String? numpad6,
String? numpad7,
String? numpad8,
String? numpad9,
String? numpadEnter,
String? numpadDecimal,
String? numpadAdd,
String? numpadSubtract,
String? numpadMultiply,
String? numpadDivide,
String? numpadEqual,
}) =>
KeyboardShortcuts(
numpad0: numpad0 ?? this.numpad0,
numpad1: numpad1 ?? this.numpad1,
numpad2: numpad2 ?? this.numpad2,
numpad3: numpad3 ?? this.numpad3,
numpad4: numpad4 ?? this.numpad4,
numpad5: numpad5 ?? this.numpad5,
numpad6: numpad6 ?? this.numpad6,
numpad7: numpad7 ?? this.numpad7,
numpad8: numpad8 ?? this.numpad8,
numpad9: numpad9 ?? this.numpad9,
numpadEnter: numpadEnter ?? this.numpadEnter,
numpadDecimal: numpadDecimal ?? this.numpadDecimal,
numpadAdd: numpadAdd ?? this.numpadAdd,
numpadSubtract: numpadSubtract ?? this.numpadSubtract,
numpadMultiply: numpadMultiply ?? this.numpadMultiply,
numpadDivide: numpadDivide ?? this.numpadDivide,
numpadEqual: numpadEqual ?? this.numpadEqual,
);
KeyboardShortcuts copyWithMap(Map<NumpadKey, String> map) => copyWith(
numpad0: map[NumpadKey.numpad0],
numpad1: map[NumpadKey.numpad1],
numpad2: map[NumpadKey.numpad2],
numpad3: map[NumpadKey.numpad3],
numpad4: map[NumpadKey.numpad4],
numpad5: map[NumpadKey.numpad5],
numpad6: map[NumpadKey.numpad6],
numpad7: map[NumpadKey.numpad7],
numpad8: map[NumpadKey.numpad8],
numpad9: map[NumpadKey.numpad9],
numpadEnter: map[NumpadKey.numpadEnter],
numpadDecimal: map[NumpadKey.numpadDecimal],
numpadAdd: map[NumpadKey.numpadAdd],
numpadSubtract: map[NumpadKey.numpadSubtract],
numpadMultiply: map[NumpadKey.numpadMultiply],
numpadDivide: map[NumpadKey.numpadDivide],
numpadEqual: map[NumpadKey.numpadEqual],
);
}
Map<ShortcutActivator, Intent> numpadKeysIntentMap(BuildContext context) =>
<ShortcutActivator, Intent>{
// SingleActivator(LogicalKeyboardKey.enter): ActivateIntent(),
const SingleActivator(LogicalKeyboardKey.numpad0):
KeyboardIntent(NumpadKey.numpad0, context),
const SingleActivator(LogicalKeyboardKey.numpad1):
KeyboardIntent(NumpadKey.numpad1, context),
const SingleActivator(LogicalKeyboardKey.numpad2):
KeyboardIntent(NumpadKey.numpad2, context),
const SingleActivator(LogicalKeyboardKey.numpad3):
KeyboardIntent(NumpadKey.numpad3, context),
const SingleActivator(LogicalKeyboardKey.numpad4):
KeyboardIntent(NumpadKey.numpad4, context),
const SingleActivator(LogicalKeyboardKey.numpad5):
KeyboardIntent(NumpadKey.numpad5, context),
const SingleActivator(LogicalKeyboardKey.numpad6):
KeyboardIntent(NumpadKey.numpad6, context),
const SingleActivator(LogicalKeyboardKey.numpad7):
KeyboardIntent(NumpadKey.numpad7, context),
const SingleActivator(LogicalKeyboardKey.numpad8):
KeyboardIntent(NumpadKey.numpad8, context),
const SingleActivator(LogicalKeyboardKey.numpad9):
KeyboardIntent(NumpadKey.numpad9, context),
const SingleActivator(LogicalKeyboardKey.numpadDivide):
KeyboardIntent(NumpadKey.numpadDivide, context),
const SingleActivator(LogicalKeyboardKey.numpadMultiply):
KeyboardIntent(NumpadKey.numpadMultiply, context),
const SingleActivator(LogicalKeyboardKey.numpadSubtract):
KeyboardIntent(NumpadKey.numpadSubtract, context),
const SingleActivator(LogicalKeyboardKey.numpadAdd):
KeyboardIntent(NumpadKey.numpadAdd, context),
const SingleActivator(LogicalKeyboardKey.numpadDecimal):
KeyboardIntent(NumpadKey.numpadDecimal, context),
const SingleActivator(LogicalKeyboardKey.numpadEnter):
KeyboardIntent(NumpadKey.numpadEnter, context),
};
final numpadKeyLabels = {
NumpadKey.numpad0: '0',
NumpadKey.numpad1: '1',
NumpadKey.numpad2: '2',
NumpadKey.numpad3: '3',
NumpadKey.numpad4: '4',
NumpadKey.numpad5: '5',
NumpadKey.numpad6: '6',
NumpadKey.numpad7: '7',
NumpadKey.numpad8: '8',
NumpadKey.numpad9: '9',
NumpadKey.numpadEnter: 'Enter',
NumpadKey.numpadDecimal: '.',
NumpadKey.numpadAdd: '+',
NumpadKey.numpadSubtract: '-',
NumpadKey.numpadMultiply: '*',
NumpadKey.numpadDivide: '/',
NumpadKey.numpadEqual: '=',
};

View File

@@ -1,4 +1,5 @@
import 'package:flutter/material.dart';
import 'package:mudblock/pages/keyboard_shortcuts_page.dart';
import '../core/features/alias.dart';
import '../core/features/trigger.dart';
@@ -15,7 +16,6 @@ import '../pages/trigger_list_page.dart';
import '../pages/trigger_page.dart';
import '../pages/variable_list_page.dart';
import '../pages/variable_page.dart';
import 'consts.dart';
import 'features/game_button_set.dart';
import 'features/profile.dart';
import 'features/variable.dart';
@@ -39,6 +39,8 @@ class Paths {
static const buttonSet = '/button-set';
static const button = '/button';
static const shortcuts = '/shortcuts';
static const settings = '/settings';
}
@@ -85,9 +87,21 @@ final routes = <String, Widget Function(BuildContext)>{
ModalRoute.of(context)!.settings.arguments as GameButtonSetData?;
return GameButtonSetPage(buttonSet: buttonSet);
},
Paths.shortcuts: (context) {
return GameStore.consumer(builder: (context, store, child) {
return KeyboardShortcutsPage(
shortcuts: store.keyboardShortcuts,
onSave: (shortcuts) {
store.keyboardShortcuts = shortcuts;
store.currentProfile.saveKeyboardShortcuts(shortcuts);
},
);
});
},
Paths.home: (context) => HomeScaffold(
builder: (context, _) {
return const HomePage();
},
),
};

View File

@@ -60,15 +60,18 @@ class FileStorage {
}
class ProfileStorage {
static const encoder = JsonEncoder.withIndent(' ');
static const decoder = JsonDecoder();
static Future<Map<String, dynamic>?> readProfileFile(
String profile, String filename) async {
final data = await FileStorage.readFile('profiles/$profile/$filename.json');
return data != null ? jsonDecode(data) : null;
return data != null ? decoder.convert(data) : null;
}
static Future<void> writeProfileFile(
String profile, String filename, dynamic data) async {
data = jsonEncode(data);
data = encoder.convert(data);
await FileStorage.writeFile('profiles/$profile/$filename.json', data);
}

View File

@@ -16,6 +16,7 @@ import 'features/game_button_set.dart';
import 'features/profile.dart';
import 'features/trigger.dart';
import 'features/variable.dart';
import 'keyboard_shortcuts.dart';
const maxLines = 2000;
@@ -48,6 +49,7 @@ class GameStore extends ChangeNotifier {
final List<Alias> aliases = [];
final Map<String, Variable> variables = {};
final List<GameButtonSetData> buttonSets = [];
KeyboardShortcuts keyboardShortcuts = KeyboardShortcuts();
MUDProfile get currentProfile => _currentProfile!;
@@ -83,6 +85,7 @@ class GameStore extends ChangeNotifier {
loadAliases(),
loadVariables(),
loadButtonSets(),
loadKeyboardShortcuts(),
]);
_client.connect();
}
@@ -120,6 +123,13 @@ class GameStore extends ChangeNotifier {
debugPrint('ButtonSets: ${buttonSets.length}');
}
Future<void> loadKeyboardShortcuts() async {
final shortcuts = await currentProfile.loadKeyboardShortcuts();
keyboardShortcuts = shortcuts;
notifyListeners();
debugPrint('KeyboardShortcuts loaded');
}
bool processTriggers(String line) {
bool showLine = true;
final str = ColorUtils.stripColor(line);
@@ -422,6 +432,13 @@ class GameStore extends ChangeNotifier {
debugPrint('profiles: ${profiles.map((e) => [e.name, e.password])}');
notifyListeners();
}
void onShortcut(NumpadKey key, BuildContext context) {
final action = keyboardShortcuts.get(key);
if (action.isNotEmpty) {
submitInput(action);
}
}
}
mixin GameStoreMixin {
@@ -434,3 +451,4 @@ mixin GameStoreStateMixin<T extends StatefulWidget> on State<T> {
}
final gameStore = GameStore();

View File

@@ -1,6 +1,8 @@
import 'package:flutter/material.dart';
import '../core/features/game_button_set.dart';
import '../core/platform_utils.dart';
import '../core/string_utils.dart';
import '../widgets/button_set_editor.dart';
class GameButtonSetPage extends StatefulWidget {
@@ -24,6 +26,7 @@ class _GameButtonSetPageState extends State<GameButtonSetPage> {
@override
Widget build(BuildContext context) {
final platformWindowName = PlatformUtils.isDesktop ? 'window' : 'screen';
return Scaffold(
appBar: AppBar(
title: const Text('Button Set'),
@@ -74,7 +77,7 @@ class _GameButtonSetPageState extends State<GameButtonSetPage> {
.map(
(e) => DropdownMenuEntry(
value: e,
label: e.name,
label: capitalize(e.name),
),
)
.toList(),
@@ -85,12 +88,40 @@ class _GameButtonSetPageState extends State<GameButtonSetPage> {
},
),
const SizedBox(height: 16),
DropdownMenu(
label: Text('Position on $platformWindowName'),
initialSelection: buttonSet.alignment,
dropdownMenuEntries: [
Alignment.topLeft,
Alignment.topCenter,
Alignment.topRight,
Alignment.centerLeft,
Alignment.center,
Alignment.centerRight,
Alignment.bottomLeft,
Alignment.bottomCenter,
Alignment.bottomRight,
]
.map(
(e) => DropdownMenuEntry(
value: e,
label: capitalize(e.toString().split('.')[1]),
),
)
.toList(),
onSelected: (value) {
setState(() {
buttonSet.alignment = value as Alignment;
});
},
),
const SizedBox(height: 16),
ButtonSetEditor(
key: Key(buttonSet.type.name),
data: buttonSet,
onUpdate: (data) {
setState(() {
buttonSet = data;
buttonSet = buttonSet.copyWith(buttons: data.buttons);
});
},
),

View File

@@ -9,6 +9,7 @@ import 'package:window_manager/window_manager.dart';
import '../core/consts.dart';
import '../core/features/game_button_set.dart';
import '../core/keyboard_shortcuts.dart';
import '../core/store.dart';
class HomePage extends StatefulWidget {
@@ -43,99 +44,109 @@ class HomePageState extends State<HomePage>
);
final inputStyle = consoleStyle.copyWith(color: Colors.grey);
return Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
Expanded(
child: Material(
color: Colors.black,
child: Consumer<GameStore>(
builder: (context, store, child) {
final lines = store.lines;
return Theme(
data: Theme.of(context).copyWith(
textSelectionTheme: TextSelectionThemeData(
cursorColor: Colors.white,
selectionColor: Colors.white.withOpacity(0.3),
selectionHandleColor: Colors.white,
),
),
child: Stack(
children: [
GestureDetector(
onTap: store.selectInput,
child: SingleChildScrollView(
controller: store.scrollController,
child: Padding(
padding: const EdgeInsets.all(8.0),
child: SelectableText.rich(
TextSpan(
children: [
for (final line in lines) ...[
for (final segment
in ColorUtils.split(line))
TextSpan(
text: segment.text,
style: consoleStyle.copyWith(
color: Color(segment.themedFgColor),
backgroundColor:
Color(segment.themedBgColor),
fontWeight: segment.bold
? FontWeight.w800
: null,
fontStyle: segment.italic
? FontStyle.italic
: null,
decoration: segment.underline
? TextDecoration.underline
: null,
),
),
const TextSpan(
text: newline,
style:
consoleStyle, // .copyWith(fontSize: 1),
),
],
],
),
enableInteractiveSelection: true,
selectionWidthStyle: BoxWidthStyle.tight,
selectionHeightStyle: BoxHeightStyle.max,
),
return Shortcuts(
shortcuts: numpadKeysIntentMap(context),
child: Actions(
actions: {
KeyboardIntent: KeyboardAction(),
},
child: Focus(
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
Expanded(
child: Material(
color: Colors.black,
child: Consumer<GameStore>(
builder: (context, store, child) {
final lines = store.lines;
return Theme(
data: Theme.of(context).copyWith(
textSelectionTheme: TextSelectionThemeData(
cursorColor: Colors.white,
selectionColor: Colors.white.withOpacity(0.3),
selectionHandleColor: Colors.white,
),
),
),
for (final buttonSet
in store.buttonSets.where((b) => b.enabled))
Padding(
padding: const EdgeInsets.all(8.0),
child: GameButtonSet(data: buttonSet),
)
],
child: Stack(
children: [
GestureDetector(
onTap: store.selectInput,
child: SingleChildScrollView(
controller: store.scrollController,
child: Padding(
padding: const EdgeInsets.all(8.0),
child: SelectableText.rich(
TextSpan(
children: [
for (final line in lines) ...[
for (final segment
in ColorUtils.split(line))
TextSpan(
text: segment.text,
style: consoleStyle.copyWith(
color: Color(segment.themedFgColor),
backgroundColor:
Color(segment.themedBgColor),
fontWeight: segment.bold
? FontWeight.w800
: null,
fontStyle: segment.italic
? FontStyle.italic
: null,
decoration: segment.underline
? TextDecoration.underline
: null,
),
),
const TextSpan(
text: newline,
style:
consoleStyle, // .copyWith(fontSize: 1),
),
],
],
),
enableInteractiveSelection: true,
selectionWidthStyle: BoxWidthStyle.tight,
selectionHeightStyle: BoxHeightStyle.max,
),
),
),
),
for (final buttonSet
in store.buttonSets.where((b) => b.enabled))
Padding(
padding: const EdgeInsets.all(8.0),
child: GameButtonSet(data: buttonSet),
)
],
),
);
},
),
);
},
),
),
),
Padding(
padding: const EdgeInsets.all(8.0),
child: TextField(
autofocus: true,
focusNode: store.inputFocus,
controller: store.input,
onSubmitted: store.submitInput,
onTap: store.selectInput,
style: consoleStyle.copyWith(color: Colors.black),
decoration: InputDecoration(
hintText: 'Enter command',
border: const OutlineInputBorder(),
hintStyle: inputStyle,
),
),
),
],
),
),
Padding(
padding: const EdgeInsets.all(8.0),
child: TextField(
autofocus: true,
focusNode: store.inputFocus,
controller: store.input,
onSubmitted: store.submitInput,
onTap: store.selectInput,
style: consoleStyle.copyWith(color: Colors.black),
decoration: InputDecoration(
hintText: 'Enter command',
border: const OutlineInputBorder(),
hintStyle: inputStyle,
),
),
),
],
),
);
}

View File

@@ -92,6 +92,11 @@ class HomeScaffold extends StatelessWidget with GameStoreMixin {
leading: const Icon(Variable.iconData),
onTap: () => Navigator.pushNamed(context, Paths.variables),
),
ListTile(
title: const Text('Keyboard Shortcuts'),
leading: const Icon(Icons.keyboard),
onTap: () => Navigator.pushNamed(context, Paths.shortcuts),
),
const Divider(),
ListTile(
title: const Text('Settings'),
@@ -115,3 +120,4 @@ class HomeScaffold extends StatelessWidget with GameStoreMixin {
);
}
}

View File

@@ -0,0 +1,135 @@
import 'package:flutter/material.dart';
import '../core/keyboard_shortcuts.dart';
class KeyboardShortcutsPage extends StatefulWidget {
const KeyboardShortcutsPage({super.key, required this.shortcuts, required this.onSave});
final KeyboardShortcuts shortcuts;
final void Function(KeyboardShortcuts) onSave;
@override
State<KeyboardShortcutsPage> createState() => _KeyboardShortcutsPageState();
}
class _KeyboardShortcutsPageState extends State<KeyboardShortcutsPage> {
late KeyboardShortcuts shortucts;
final _controllers = {
NumpadKey.numpad0: TextEditingController(),
NumpadKey.numpad1: TextEditingController(),
NumpadKey.numpad2: TextEditingController(),
NumpadKey.numpad3: TextEditingController(),
NumpadKey.numpad4: TextEditingController(),
NumpadKey.numpad5: TextEditingController(),
NumpadKey.numpad6: TextEditingController(),
NumpadKey.numpad7: TextEditingController(),
NumpadKey.numpad8: TextEditingController(),
NumpadKey.numpad9: TextEditingController(),
NumpadKey.numpadEnter: TextEditingController(),
NumpadKey.numpadDecimal: TextEditingController(),
NumpadKey.numpadAdd: TextEditingController(),
NumpadKey.numpadSubtract: TextEditingController(),
NumpadKey.numpadMultiply: TextEditingController(),
NumpadKey.numpadDivide: TextEditingController(),
NumpadKey.numpadEqual: TextEditingController(),
};
@override
void initState() {
super.initState();
shortucts = widget.shortcuts.copyWith();
_controllers.forEach((key, controller) {
controller.text = shortucts.get(key);
controller.addListener(() {
setState(() {
shortucts = shortucts.copyWithMap({key: controller.text});
});
});
});
}
@override
Widget build(BuildContext context) {
final ordered = <List<NumpadKey?>>[
[
null,
null,
NumpadKey.numpadDivide,
NumpadKey.numpadMultiply,
],
[
NumpadKey.numpad7,
NumpadKey.numpad8,
NumpadKey.numpad9,
NumpadKey.numpadSubtract
],
[
NumpadKey.numpad4,
NumpadKey.numpad5,
NumpadKey.numpad6,
NumpadKey.numpadAdd
],
[
NumpadKey.numpad1,
NumpadKey.numpad2,
NumpadKey.numpad3,
NumpadKey.numpadEqual
],
[
NumpadKey.numpad0,
null,
NumpadKey.numpadDecimal,
null,
],
];
return Scaffold(
appBar: AppBar(
title: const Text('Keyboard Shortcuts'),
),
floatingActionButton: FloatingActionButton(
onPressed: () {
widget.onSave(shortucts);
Navigator.of(context).pop();
},
child: const Icon(Icons.save),
),
body: Center(
child: SizedBox(
width: 800,
child: Column(
children: ordered
.map(
(row) => Row(
children: row
.map(
(key) => Expanded(
child: key != null
? Padding(
padding: const EdgeInsets.all(16.0),
child: TextField(
controller: _controllers[key],
decoration: InputDecoration(
border: const OutlineInputBorder(),
labelText:
numpadKeyLabels[key] ?? key.name,
floatingLabelBehavior:
FloatingLabelBehavior.always,
),
),
)
: Container(),
),
)
.toList(),
),
)
.toList(),
),
),
),
);
}
}