refactor: Easier shift enter logic for text input

pull/1683/head
Krille 4 months ago
parent 8956f81e4e
commit dda45f783f
No known key found for this signature in database
GPG Key ID: E067ECD60F1A0652

@ -110,7 +110,7 @@ class ChatController extends State<ChatPageWithRoom>
final AutoScrollController scrollController = AutoScrollController();
FocusNode inputFocus = FocusNode();
late final FocusNode inputFocus;
StreamSubscription<html.Event>? onFocusSub;
Timer? typingCoolDown;
@ -275,8 +275,26 @@ class ChatController extends State<ChatPageWithRoom>
);
}
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);

@ -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<Map<String, String?>>(
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<Map<String, String?>>(
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<String, String?> 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<String, String?> 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 {}

Loading…
Cancel
Save