From 33c3dab76c56a5308319b7e009bf19d05bb457c9 Mon Sep 17 00:00:00 2001 From: PartyDonut <42371342+PartyDonut@users.noreply.github.com> Date: Sun, 9 Nov 2025 10:55:01 +0100 Subject: [PATCH] feat: Add button for opening IME keyboard in custom keyboard overlay (#599) Co-authored-by: PartyDonut --- lib/l10n/app_en.arb | 3 +- .../keyboard/keyboard_localization.dart | 24 +++++-- lib/widgets/keyboard/slide_in_keyboard.dart | 71 +++++++++++++++---- 3 files changed, 79 insertions(+), 19 deletions(-) diff --git a/lib/l10n/app_en.arb b/lib/l10n/app_en.arb index 2363c77..2e7e648 100644 --- a/lib/l10n/app_en.arb +++ b/lib/l10n/app_en.arb @@ -1366,5 +1366,6 @@ "format": "jm" } } - } + }, + "openImeKeyboard": "Open IME keyboard" } \ No newline at end of file diff --git a/lib/widgets/keyboard/keyboard_localization.dart b/lib/widgets/keyboard/keyboard_localization.dart index d769ade..3826a66 100644 --- a/lib/widgets/keyboard/keyboard_localization.dart +++ b/lib/widgets/keyboard/keyboard_localization.dart @@ -13,7 +13,9 @@ class KeyboardLayouts { ['4', '5', '6', '(', ')', 'ABC'], ['7', '8', '9', '@', '!', '?'], ['0', '/', '\$', '%', '+', '[', ']'], - ['.', '-', '_', '"', ':'] + ['.', '-', '_', '"', ':'], + ['{', '}', '\\', '|', '~'], + ['<', '>', '\$', '*', '='] ], }, 'es': { @@ -29,7 +31,9 @@ class KeyboardLayouts { ['4', '5', '6', '(', ')', 'ABC'], ['7', '8', '9', '@', '!', '?'], ['0', '/', '\$', '%', '+', '[', ']'], - ['.', '-', '_', '"', ':'] + ['.', '-', '_', '"', ':'], + ['{', '}', '\\', '|', '~'], + ['<', '>', '\$', '*', '='] ], }, 'de': { @@ -46,7 +50,9 @@ class KeyboardLayouts { ['4', '5', '6', '(', ')', 'ABC'], ['7', '8', '9', '@', '!', '?'], ['0', '/', '\$', '%', '+', '[', ']'], - ['.', '-', '_', '"', ':'] + ['.', '-', '_', '"', ':'], + ['{', '}', '\\', '|', '~'], + ['<', '>', '\$', '*', '='] ], }, 'fr': { @@ -64,7 +70,9 @@ class KeyboardLayouts { ['4', '5', '6', '(', ')', 'ABC'], ['7', '8', '9', '@', '!', '?'], ['0', '/', '\$', '%', '+', '[', ']'], - ['.', '-', '_', '"', ':'] + ['.', '-', '_', '"', ':'], + ['{', '}', '\\', '|', '~'], + ['<', '>', '\$', '*', '='] ], }, 'ja': { @@ -81,7 +89,9 @@ class KeyboardLayouts { ['6', '7', '8', '9', '0', 'ABC'], ['!', '@', '#', '\$', '%'], ['^', '&', '*', '(', ')'], - ['-', '_', '¥', '.', ','] + ['-', '_', '¥', '.', ','], + ['{', '}', '\\', '|', '~'], + ['<', '>', '\$', '*', '='] ], }, 'zh': { @@ -97,7 +107,9 @@ class KeyboardLayouts { ['6', '7', '8', '9', '0', 'ABC'], ['!', '@', '#', '\$', '%'], ['^', '&', '*', '(', ')'], - ['-', '_', '¥', '·', '…'] + ['-', '_', '¥', '·', '…'], + ['{', '}', '\\', '|', '~'], + ['<', '>', '\$', '*', '='] ], }, }; diff --git a/lib/widgets/keyboard/slide_in_keyboard.dart b/lib/widgets/keyboard/slide_in_keyboard.dart index 94a61b1..fb9feb8 100644 --- a/lib/widgets/keyboard/slide_in_keyboard.dart +++ b/lib/widgets/keyboard/slide_in_keyboard.dart @@ -162,6 +162,9 @@ class _CustomKeyboardViewState extends State<_CustomKeyboardView> { ValueNotifier> searchQueryResults = ValueNotifier([]); + final FocusNode internalTextField = FocusNode(); + final FocusNode keyboardOpenFocusNode = FocusNode(); + Future startUpdate(String text) async { final newValues = await widget.searchQuery?.call(widget.controller.text) ?? []; searchQueryResults.value = newValues; @@ -175,6 +178,15 @@ class _CustomKeyboardViewState extends State<_CustomKeyboardView> { }); } + @override + void dispose() { + scope.dispose(); + internalTextField.dispose(); + keyboardOpenFocusNode.dispose(); + searchQueryResults.dispose(); + super.dispose(); + } + @override Widget build(BuildContext context) { if (!scope.hasFocus) { @@ -189,19 +201,54 @@ class _CustomKeyboardViewState extends State<_CustomKeyboardView> { crossAxisAlignment: CrossAxisAlignment.stretch, spacing: 16, children: [ - Card( - child: Padding( - padding: const EdgeInsets.all(12.0), - child: Text( - widget.keyboardType == TextInputType.visiblePassword - ? List.generate( - widget.controller.text.length, - (index) => "*", - ).join() - : widget.controller.text, - style: Theme.of(context).textTheme.titleLarge, + Row( + children: [ + Expanded( + child: ExcludeFocusTraversal( + child: Card( + child: Padding( + padding: const EdgeInsets.all(12.0), + child: TextField( + focusNode: internalTextField, + controller: widget.controller, + showCursor: true, + keyboardType: widget.keyboardType, + textInputAction: widget.keyboardActionType, + obscureText: widget.keyboardType == TextInputType.visiblePassword, + onChanged: (value) { + setState(() { + widget.onChanged(); + startUpdate(value); + }); + }, + onSubmitted: (value) { + internalTextField.unfocus(); + keyboardOpenFocusNode.requestFocus(); + setState(() { + widget.onChanged(); + startUpdate(value); + }); + }, + decoration: const InputDecoration( + border: InputBorder.none, + isDense: true, + contentPadding: EdgeInsets.zero, + ), + style: Theme.of(context).textTheme.titleLarge, + ), + ), + ), + ), ), - ), + IconButton( + focusNode: keyboardOpenFocusNode, + onPressed: () => internalTextField.requestFocus(), + tooltip: context.localized.openImeKeyboard, + icon: const Icon( + IconsaxPlusBold.keyboard_open, + ), + ) + ], ), if (widget.searchQuery != null) ValueListenableBuilder(