From dda45f783f2bf699e2a6612cec611c4868c0a2eb Mon Sep 17 00:00:00 2001 From: Krille Date: Mon, 10 Mar 2025 08:27:23 +0100 Subject: [PATCH] refactor: Easier shift enter logic for text input --- lib/pages/chat/chat.dart | 20 +++- lib/pages/chat/input_bar.dart | 176 +++++++++++----------------------- 2 files changed, 76 insertions(+), 120 deletions(-) diff --git a/lib/pages/chat/chat.dart b/lib/pages/chat/chat.dart index 5a3a8e27b..aec917074 100644 --- a/lib/pages/chat/chat.dart +++ b/lib/pages/chat/chat.dart @@ -110,7 +110,7 @@ class ChatController extends State final AutoScrollController scrollController = AutoScrollController(); - FocusNode inputFocus = FocusNode(); + late final FocusNode inputFocus; StreamSubscription? onFocusSub; Timer? typingCoolDown; @@ -275,8 +275,26 @@ class ChatController extends State ); } + KeyEventResult _shiftEnterKeyHandling(FocusNode node, KeyEvent evt) { + if (!HardwareKeyboard.instance.isShiftPressed && + evt.logicalKey.keyLabel == 'Enter') { + if (evt is KeyDownEvent) { + send(); + } + return KeyEventResult.handled; + } else { + return KeyEventResult.ignored; + } + } + @override void initState() { + inputFocus = FocusNode( + onKeyEvent: (AppConfig.sendOnEnter ?? !PlatformInfos.isMobile) + ? _shiftEnterKeyHandling + : null, + ); + scrollController.addListener(_updateScrollController); inputFocus.addListener(_inputFocusListener); diff --git a/lib/pages/chat/input_bar.dart b/lib/pages/chat/input_bar.dart index a6ef1022e..42b88b0b7 100644 --- a/lib/pages/chat/input_bar.dart +++ b/lib/pages/chat/input_bar.dart @@ -5,12 +5,9 @@ import 'package:emojis/emoji.dart'; import 'package:flutter_gen/gen_l10n/l10n.dart'; import 'package:flutter_typeahead/flutter_typeahead.dart'; import 'package:matrix/matrix.dart'; -import 'package:pasteboard/pasteboard.dart'; import 'package:slugify/slugify.dart'; -import 'package:fluffychat/config/app_config.dart'; import 'package:fluffychat/utils/markdown_context_builder.dart'; -import 'package:fluffychat/utils/platform_infos.dart'; import 'package:fluffychat/widgets/mxc_image.dart'; import '../../widgets/avatar.dart'; import '../../widgets/matrix.dart'; @@ -397,125 +394,66 @@ class InputBar extends StatelessWidget { @override Widget build(BuildContext context) { - final useShortCuts = (AppConfig.sendOnEnter ?? !PlatformInfos.isMobile); - return Shortcuts( - shortcuts: !useShortCuts - ? {} - : { - LogicalKeySet(LogicalKeyboardKey.shift, LogicalKeyboardKey.enter): - NewLineIntent(), - LogicalKeySet(LogicalKeyboardKey.enter): SubmitLineIntent(), - LogicalKeySet( - LogicalKeyboardKey.controlLeft, - LogicalKeyboardKey.keyM, - ): PasteLineIntent(), - }, - child: Actions( - actions: !useShortCuts - ? {} - : { - NewLineIntent: CallbackAction( - onInvoke: (i) { - final val = controller!.value; - final selection = val.selection.start; - final messageWithoutNewLine = - '${controller!.text.substring(0, val.selection.start)}\n${controller!.text.substring(val.selection.end)}'; - controller!.value = TextEditingValue( - text: messageWithoutNewLine, - selection: TextSelection.fromPosition( - TextPosition(offset: selection + 1), - ), - ); - return null; - }, - ), - SubmitLineIntent: CallbackAction( - onInvoke: (i) { - onSubmitted!(controller!.text); - return null; - }, - ), - PasteLineIntent: CallbackAction( - onInvoke: (i) async { - final image = await Pasteboard.image; - if (image != null) { - onSubmitImage!(image); - return null; - } - return null; - }, - ), - }, - child: TypeAheadField>( - direction: VerticalDirection.up, - hideOnEmpty: true, - hideOnLoading: true, - controller: controller, - focusNode: focusNode, - hideOnSelect: false, - debounceDuration: const Duration(milliseconds: 50), - // show suggestions after 50ms idle time (default is 300) - builder: (context, controller, focusNode) => TextField( - controller: controller, - focusNode: focusNode, - contextMenuBuilder: (c, e) => - markdownContextBuilder(c, e, controller), - contentInsertionConfiguration: ContentInsertionConfiguration( - onContentInserted: (KeyboardInsertedContent content) { - final data = content.data; - if (data == null) return; + return TypeAheadField>( + direction: VerticalDirection.up, + hideOnEmpty: true, + hideOnLoading: true, + controller: controller, + focusNode: focusNode, + hideOnSelect: false, + debounceDuration: const Duration(milliseconds: 50), + // show suggestions after 50ms idle time (default is 300) + builder: (context, controller, focusNode) => TextField( + controller: controller, + focusNode: focusNode, + contextMenuBuilder: (c, e) => markdownContextBuilder(c, e, controller), + contentInsertionConfiguration: ContentInsertionConfiguration( + onContentInserted: (KeyboardInsertedContent content) { + final data = content.data; + if (data == null) return; - final file = MatrixFile( - mimeType: content.mimeType, - bytes: data, - name: content.uri.split('/').last, - ); - room.sendFileEvent( - file, - shrinkImageMaxDimension: 1600, - ); - }, - ), - minLines: minLines, - maxLines: maxLines, - keyboardType: keyboardType!, - textInputAction: textInputAction, - autofocus: autofocus!, - inputFormatters: [ - LengthLimitingTextInputFormatter((maxPDUSize / 3).floor()), - ], - onSubmitted: (text) { - // fix for library for now - // it sets the types for the callback incorrectly - onSubmitted!(text); - }, - decoration: decoration!, - onChanged: (text) { - // fix for the library for now - // it sets the types for the callback incorrectly - onChanged!(text); - }, - textCapitalization: TextCapitalization.sentences, - ), - suggestionsCallback: getSuggestions, - itemBuilder: (c, s) => - buildSuggestion(c, s, Matrix.of(context).client), - onSelected: (Map suggestion) => - insertSuggestion(context, suggestion), - errorBuilder: (BuildContext context, Object? error) => - const SizedBox.shrink(), - loadingBuilder: (BuildContext context) => const SizedBox.shrink(), - // fix loading briefly flickering a dark box - emptyBuilder: (BuildContext context) => const SizedBox - .shrink(), // fix loading briefly showing no suggestions + final file = MatrixFile( + mimeType: content.mimeType, + bytes: data, + name: content.uri.split('/').last, + ); + room.sendFileEvent( + file, + shrinkImageMaxDimension: 1600, + ); + }, ), + minLines: minLines, + maxLines: maxLines, + keyboardType: keyboardType!, + textInputAction: textInputAction, + autofocus: autofocus!, + inputFormatters: [ + LengthLimitingTextInputFormatter((maxPDUSize / 3).floor()), + ], + onSubmitted: (text) { + // fix for library for now + // it sets the types for the callback incorrectly + onSubmitted!(text); + }, + decoration: decoration!, + onChanged: (text) { + // fix for the library for now + // it sets the types for the callback incorrectly + onChanged!(text); + }, + textCapitalization: TextCapitalization.sentences, ), + suggestionsCallback: getSuggestions, + itemBuilder: (c, s) => buildSuggestion(c, s, Matrix.of(context).client), + onSelected: (Map suggestion) => + insertSuggestion(context, suggestion), + errorBuilder: (BuildContext context, Object? error) => + const SizedBox.shrink(), + loadingBuilder: (BuildContext context) => const SizedBox.shrink(), + // fix loading briefly flickering a dark box + emptyBuilder: (BuildContext context) => + const SizedBox.shrink(), // fix loading briefly showing no suggestions ); } } - -class NewLineIntent extends Intent {} - -class SubmitLineIntent extends Intent {} - -class PasteLineIntent extends Intent {}