diff --git a/lib/pages/chat/chat.dart b/lib/pages/chat/chat.dart index 673d230771..9af39f1725 100644 --- a/lib/pages/chat/chat.dart +++ b/lib/pages/chat/chat.dart @@ -136,6 +136,7 @@ class ChatController extends State final ValueNotifier showScrollDownButtonNotifier = ValueNotifier(false); final ValueNotifier showEmojiPickerNotifier = ValueNotifier(false); FocusNode inputFocus = FocusNode(); + FocusNode keyboardFocus = FocusNode(); Timer? typingCoolDown; Timer? typingTimeout; @@ -1178,8 +1179,9 @@ class ChatController extends State return index + 1; } - void onInputBarSubmitted(_) { - send(); + void onInputBarSubmitted(_) async { + await send(); + await Future.delayed(const Duration(milliseconds: 100)); FocusScope.of(context).requestFocus(inputFocus); } diff --git a/lib/pages/chat/chat_input_row.dart b/lib/pages/chat/chat_input_row.dart index 702d1579e2..cdb2731d3e 100644 --- a/lib/pages/chat/chat_input_row.dart +++ b/lib/pages/chat/chat_input_row.dart @@ -159,6 +159,7 @@ class ChatInputRow extends StatelessWidget { textInputAction: AppConfig.sendOnEnter ? TextInputAction.send : null, onSubmitted: controller.onInputBarSubmitted, focusNode: controller.inputFocus, + keyboardFocusNode: controller.keyboardFocus, controller: controller.sendController, decoration: InputDecoration( hintText: L10n.of(context)!.chatMessage, diff --git a/lib/pages/chat/input_bar.dart b/lib/pages/chat/input_bar.dart index 803df0ffe0..bce81fe17c 100644 --- a/lib/pages/chat/input_bar.dart +++ b/lib/pages/chat/input_bar.dart @@ -1,7 +1,7 @@ +import 'package:fluffychat/utils/extension/raw_key_event_extension.dart'; import 'package:fluffychat/widgets/avatar/avatar.dart'; import 'package:fluffychat/widgets/matrix.dart'; import 'package:flutter/material.dart'; -import 'package:flutter/services.dart'; import 'package:emojis/emoji.dart'; import 'package:flutter_gen/gen_l10n/l10n.dart'; @@ -23,6 +23,7 @@ class InputBar extends StatelessWidget { final TextInputAction? textInputAction; final ValueChanged? onSubmitted; final FocusNode? focusNode; + final FocusNode keyboardFocusNode; final TextEditingController? controller; final InputDecoration? decoration; final ValueChanged? onChanged; @@ -36,6 +37,7 @@ class InputBar extends StatelessWidget { this.keyboardType, this.onSubmitted, this.focusNode, + required this.keyboardFocusNode, this.controller, this.decoration, this.onChanged, @@ -302,82 +304,54 @@ class InputBar extends StatelessWidget { final useShortCuts = (PlatformInfos.isWeb || PlatformInfos.isDesktop || AppConfig.sendOnEnter); - return Shortcuts( - shortcuts: !useShortCuts - ? {} - : { - LogicalKeySet(LogicalKeyboardKey.shift, LogicalKeyboardKey.enter): - NewLineIntent(), - LogicalKeySet(LogicalKeyboardKey.enter): SubmitLineIntent(), - }, - 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; - }, - ), - }, - child: TypeAheadField>( - direction: AxisDirection.up, - hideOnEmpty: true, - hideOnLoading: true, - keepSuggestionsOnSuggestionSelected: true, - debounceDuration: const Duration(milliseconds: 50), - // show suggestions after 50ms idle time (default is 300) - textFieldConfiguration: TextFieldConfiguration( - minLines: minLines, - maxLines: maxLines, - keyboardType: keyboardType!, - textInputAction: textInputAction, - autofocus: autofocus!, - style: InputBarStyle.getTypeAheadTextStyle(context), - onSubmitted: (text) { - // fix for library for now - // it sets the types for the callback incorrectly - onSubmitted!(text); - }, - controller: controller, - decoration: decoration!, - focusNode: focusNode, - onChanged: (text) { - // fix for the library for now - // it sets the types for the callback incorrectly - onChanged!(text); - }, - textCapitalization: TextCapitalization.sentences, - ), - suggestionsCallback: getSuggestions, - itemBuilder: (context, suggestion) => SuggestionTile( - suggestion: suggestion, - client: Matrix.of(context).client, - ), - onSuggestionSelected: (Map suggestion) => - insertSuggestion(context, suggestion), - errorBuilder: (BuildContext context, Object? error) => Container(), - loadingBuilder: (BuildContext context) => Container(), - // fix loading briefly flickering a dark box - noItemsFoundBuilder: (BuildContext context) => - Container(), // fix loading briefly showing no suggestions + return RawKeyboardListener( + focusNode: keyboardFocusNode, + onKey: (event) { + if (useShortCuts && event.isEnter) { + onSubmitted?.call(controller?.text ?? ''); + } + }, + child: TypeAheadField>( + direction: AxisDirection.up, + hideOnEmpty: true, + hideOnLoading: true, + keepSuggestionsOnSuggestionSelected: true, + debounceDuration: const Duration(milliseconds: 50), + // show suggestions after 50ms idle time (default is 300) + textFieldConfiguration: TextFieldConfiguration( + minLines: minLines, + maxLines: maxLines, + keyboardType: keyboardType!, + textInputAction: textInputAction, + autofocus: autofocus!, + style: InputBarStyle.getTypeAheadTextStyle(context), + onSubmitted: (text) { + // fix for library for now + // it sets the types for the callback incorrectly + onSubmitted!(text); + }, + controller: controller, + decoration: decoration!, + focusNode: focusNode, + onChanged: (text) { + // fix for the library for now + // it sets the types for the callback incorrectly + onChanged!(text); + }, + textCapitalization: TextCapitalization.sentences, + ), + suggestionsCallback: getSuggestions, + itemBuilder: (context, suggestion) => SuggestionTile( + suggestion: suggestion, + client: Matrix.of(context).client, ), + onSuggestionSelected: (Map suggestion) => + insertSuggestion(context, suggestion), + errorBuilder: (BuildContext context, Object? error) => Container(), + loadingBuilder: (BuildContext context) => Container(), + // fix loading briefly flickering a dark box + noItemsFoundBuilder: (BuildContext context) => + Container(), // fix loading briefly showing no suggestions ), ); } diff --git a/lib/pages/chat_draft/draft_chat.dart b/lib/pages/chat_draft/draft_chat.dart index 72e4262670..61a148596c 100644 --- a/lib/pages/chat_draft/draft_chat.dart +++ b/lib/pages/chat_draft/draft_chat.dart @@ -52,6 +52,7 @@ class DraftChatController extends State final AutoScrollController forwardListController = AutoScrollController(); FocusNode inputFocus = FocusNode(); + FocusNode keyboardFocus = FocusNode(); bool showScrollDownButton = false; diff --git a/lib/pages/chat_draft/draft_chat_view.dart b/lib/pages/chat_draft/draft_chat_view.dart index 2e4cf08428..7d4b2e7d71 100644 --- a/lib/pages/chat_draft/draft_chat_view.dart +++ b/lib/pages/chat_draft/draft_chat_view.dart @@ -128,6 +128,8 @@ class DraftChatView extends StatelessWidget { onSubmitted: controller.onInputBarSubmitted, focusNode: controller.inputFocus, + keyboardFocusNode: + controller.keyboardFocus, controller: controller.sendController, decoration: DraftChatViewStyle .bottomBarInputDecoration(context), diff --git a/lib/utils/extension/raw_key_event_extension.dart b/lib/utils/extension/raw_key_event_extension.dart new file mode 100644 index 0000000000..37916a9ff3 --- /dev/null +++ b/lib/utils/extension/raw_key_event_extension.dart @@ -0,0 +1,24 @@ +import 'package:flutter/services.dart'; + +// Refer: https://github.com/flutter/flutter/issues/35435#issuecomment-540582796 +extension RawKeyEventExtension on RawKeyEvent { + bool get isEnter { + if (this is! RawKeyUpEvent) { + return false; + } + if (logicalKey == LogicalKeyboardKey.enter) { + return true; + } + if (data is RawKeyEventDataWeb) { + if ((data as RawKeyEventDataWeb).keyLabel == 'Enter') { + return true; + } + } + if (data is RawKeyEventDataAndroid) { + if ((data as RawKeyEventDataAndroid).keyCode == 13) { + return true; + } + } + return false; + } +}