feat(chat): Improve chat input semantics and keyboard interactions
This commit is contained in:
@@ -1293,157 +1293,163 @@ class _ModernChatInputState extends ConsumerState<ModernChatInput>
|
|||||||
} catch (_) {}
|
} catch (_) {}
|
||||||
_ensureFocusedIfEnabled();
|
_ensureFocusedIfEnabled();
|
||||||
},
|
},
|
||||||
child: Semantics(
|
child: MergeSemantics(
|
||||||
textField: true,
|
child: Semantics(
|
||||||
label: AppLocalizations.of(context)!.messageInputLabel,
|
label: AppLocalizations.of(context)!.messageInputLabel,
|
||||||
hint: AppLocalizations.of(context)!.messageInputHint,
|
hint: AppLocalizations.of(context)!.messageInputHint,
|
||||||
child: Shortcuts(
|
child: Shortcuts(
|
||||||
shortcuts: () {
|
shortcuts: () {
|
||||||
final map = <LogicalKeySet, Intent>{
|
final map = <LogicalKeySet, Intent>{
|
||||||
LogicalKeySet(LogicalKeyboardKey.meta, LogicalKeyboardKey.enter):
|
LogicalKeySet(
|
||||||
const _SendMessageIntent(),
|
LogicalKeyboardKey.meta,
|
||||||
LogicalKeySet(
|
LogicalKeyboardKey.enter,
|
||||||
LogicalKeyboardKey.control,
|
): const _SendMessageIntent(),
|
||||||
LogicalKeyboardKey.enter,
|
LogicalKeySet(
|
||||||
): const _SendMessageIntent(),
|
LogicalKeyboardKey.control,
|
||||||
};
|
LogicalKeyboardKey.enter,
|
||||||
if (sendOnEnter) {
|
): const _SendMessageIntent(),
|
||||||
map[LogicalKeySet(LogicalKeyboardKey.enter)] =
|
};
|
||||||
const _SendMessageIntent();
|
if (sendOnEnter) {
|
||||||
map[LogicalKeySet(
|
map[LogicalKeySet(LogicalKeyboardKey.enter)] =
|
||||||
LogicalKeyboardKey.shift,
|
const _SendMessageIntent();
|
||||||
LogicalKeyboardKey.enter,
|
map[LogicalKeySet(
|
||||||
)] =
|
LogicalKeyboardKey.shift,
|
||||||
const _InsertNewlineIntent();
|
LogicalKeyboardKey.enter,
|
||||||
}
|
)] =
|
||||||
if (_showPromptOverlay) {
|
const _InsertNewlineIntent();
|
||||||
map[LogicalKeySet(LogicalKeyboardKey.arrowDown)] =
|
}
|
||||||
const _SelectNextPromptIntent();
|
if (_showPromptOverlay) {
|
||||||
map[LogicalKeySet(LogicalKeyboardKey.arrowUp)] =
|
map[LogicalKeySet(LogicalKeyboardKey.arrowDown)] =
|
||||||
const _SelectPreviousPromptIntent();
|
const _SelectNextPromptIntent();
|
||||||
map[LogicalKeySet(LogicalKeyboardKey.escape)] =
|
map[LogicalKeySet(LogicalKeyboardKey.arrowUp)] =
|
||||||
const _DismissPromptIntent();
|
const _SelectPreviousPromptIntent();
|
||||||
}
|
map[LogicalKeySet(LogicalKeyboardKey.escape)] =
|
||||||
return map;
|
const _DismissPromptIntent();
|
||||||
}(),
|
}
|
||||||
child: Actions(
|
return map;
|
||||||
actions: <Type, Action<Intent>>{
|
}(),
|
||||||
_SendMessageIntent: CallbackAction<_SendMessageIntent>(
|
child: Actions(
|
||||||
onInvoke: (intent) {
|
actions: <Type, Action<Intent>>{
|
||||||
if (_showPromptOverlay) {
|
_SendMessageIntent: CallbackAction<_SendMessageIntent>(
|
||||||
_confirmPromptSelection();
|
onInvoke: (intent) {
|
||||||
return null;
|
if (_showPromptOverlay) {
|
||||||
}
|
_confirmPromptSelection();
|
||||||
_sendMessage();
|
|
||||||
return null;
|
|
||||||
},
|
|
||||||
),
|
|
||||||
_InsertNewlineIntent: CallbackAction<_InsertNewlineIntent>(
|
|
||||||
onInvoke: (intent) {
|
|
||||||
_insertNewline();
|
|
||||||
return null;
|
|
||||||
},
|
|
||||||
),
|
|
||||||
_SelectNextPromptIntent: CallbackAction<_SelectNextPromptIntent>(
|
|
||||||
onInvoke: (intent) {
|
|
||||||
_movePromptSelection(1);
|
|
||||||
return null;
|
|
||||||
},
|
|
||||||
),
|
|
||||||
_SelectPreviousPromptIntent:
|
|
||||||
CallbackAction<_SelectPreviousPromptIntent>(
|
|
||||||
onInvoke: (intent) {
|
|
||||||
_movePromptSelection(-1);
|
|
||||||
return null;
|
return null;
|
||||||
},
|
}
|
||||||
),
|
_sendMessage();
|
||||||
_DismissPromptIntent: CallbackAction<_DismissPromptIntent>(
|
return null;
|
||||||
onInvoke: (intent) {
|
},
|
||||||
_hidePromptOverlay();
|
),
|
||||||
return null;
|
_InsertNewlineIntent: CallbackAction<_InsertNewlineIntent>(
|
||||||
},
|
onInvoke: (intent) {
|
||||||
),
|
_insertNewline();
|
||||||
},
|
return null;
|
||||||
child: Builder(
|
},
|
||||||
builder: (context) {
|
),
|
||||||
final double factor = isActive ? 1.0 : 0.0;
|
_SelectNextPromptIntent:
|
||||||
final Color animatedPlaceholder = Color.lerp(
|
CallbackAction<_SelectNextPromptIntent>(
|
||||||
placeholderBase,
|
onInvoke: (intent) {
|
||||||
placeholderFocused,
|
_movePromptSelection(1);
|
||||||
factor,
|
return null;
|
||||||
)!;
|
},
|
||||||
final Color animatedTextColor = Color.lerp(
|
),
|
||||||
context.conduitTheme.inputText.withValues(alpha: 0.88),
|
_SelectPreviousPromptIntent:
|
||||||
context.conduitTheme.inputText,
|
CallbackAction<_SelectPreviousPromptIntent>(
|
||||||
factor,
|
onInvoke: (intent) {
|
||||||
)!;
|
_movePromptSelection(-1);
|
||||||
|
return null;
|
||||||
|
},
|
||||||
|
),
|
||||||
|
_DismissPromptIntent: CallbackAction<_DismissPromptIntent>(
|
||||||
|
onInvoke: (intent) {
|
||||||
|
_hidePromptOverlay();
|
||||||
|
return null;
|
||||||
|
},
|
||||||
|
),
|
||||||
|
},
|
||||||
|
child: Builder(
|
||||||
|
builder: (context) {
|
||||||
|
final double factor = isActive ? 1.0 : 0.0;
|
||||||
|
final Color animatedPlaceholder = Color.lerp(
|
||||||
|
placeholderBase,
|
||||||
|
placeholderFocused,
|
||||||
|
factor,
|
||||||
|
)!;
|
||||||
|
final Color animatedTextColor = Color.lerp(
|
||||||
|
context.conduitTheme.inputText.withValues(alpha: 0.88),
|
||||||
|
context.conduitTheme.inputText,
|
||||||
|
factor,
|
||||||
|
)!;
|
||||||
|
|
||||||
final FontWeight recordingWeight = _isRecording
|
final FontWeight recordingWeight = _isRecording
|
||||||
? FontWeight.w500
|
? FontWeight.w500
|
||||||
: FontWeight.w400;
|
: FontWeight.w400;
|
||||||
final TextStyle baseChatStyle = AppTypography.chatMessageStyle;
|
final TextStyle baseChatStyle =
|
||||||
|
AppTypography.chatMessageStyle;
|
||||||
|
|
||||||
return TextField(
|
return TextField(
|
||||||
controller: _controller,
|
controller: _controller,
|
||||||
focusNode: _focusNode,
|
focusNode: _focusNode,
|
||||||
enabled: widget.enabled,
|
enabled: widget.enabled,
|
||||||
autofocus: false,
|
autofocus: false,
|
||||||
minLines: 1,
|
minLines: 1,
|
||||||
maxLines: null,
|
maxLines: null,
|
||||||
keyboardType: TextInputType.multiline,
|
keyboardType: TextInputType.multiline,
|
||||||
textCapitalization: TextCapitalization.sentences,
|
textCapitalization: TextCapitalization.sentences,
|
||||||
textInputAction: sendOnEnter
|
textInputAction: sendOnEnter
|
||||||
? TextInputAction.send
|
? TextInputAction.send
|
||||||
: TextInputAction.newline,
|
: TextInputAction.newline,
|
||||||
autofillHints: const <String>[],
|
autofillHints: const <String>[],
|
||||||
showCursor: true,
|
showCursor: true,
|
||||||
scrollPadding: const EdgeInsets.only(bottom: 80),
|
scrollPadding: const EdgeInsets.only(bottom: 80),
|
||||||
keyboardAppearance: brightness,
|
keyboardAppearance: brightness,
|
||||||
cursorColor: animatedTextColor,
|
cursorColor: animatedTextColor,
|
||||||
style: baseChatStyle.copyWith(
|
style: baseChatStyle.copyWith(
|
||||||
color: animatedTextColor,
|
color: animatedTextColor,
|
||||||
fontStyle: _isRecording
|
|
||||||
? FontStyle.italic
|
|
||||||
: FontStyle.normal,
|
|
||||||
fontWeight: recordingWeight,
|
|
||||||
),
|
|
||||||
decoration: InputDecoration(
|
|
||||||
hintText: AppLocalizations.of(context)!.messageHintText,
|
|
||||||
hintStyle: baseChatStyle.copyWith(
|
|
||||||
color: animatedPlaceholder,
|
|
||||||
fontWeight: recordingWeight,
|
|
||||||
fontStyle: _isRecording
|
fontStyle: _isRecording
|
||||||
? FontStyle.italic
|
? FontStyle.italic
|
||||||
: FontStyle.normal,
|
: FontStyle.normal,
|
||||||
|
fontWeight: recordingWeight,
|
||||||
),
|
),
|
||||||
filled: false,
|
decoration: InputDecoration(
|
||||||
border: InputBorder.none,
|
hintText: AppLocalizations.of(context)!.messageHintText,
|
||||||
enabledBorder: InputBorder.none,
|
hintStyle: baseChatStyle.copyWith(
|
||||||
focusedBorder: InputBorder.none,
|
color: animatedPlaceholder,
|
||||||
errorBorder: InputBorder.none,
|
fontWeight: recordingWeight,
|
||||||
disabledBorder: InputBorder.none,
|
fontStyle: _isRecording
|
||||||
contentPadding: contentPadding,
|
? FontStyle.italic
|
||||||
isDense: true,
|
: FontStyle.normal,
|
||||||
alignLabelWithHint: true,
|
),
|
||||||
),
|
filled: false,
|
||||||
// Enable pasting images and files from clipboard
|
border: InputBorder.none,
|
||||||
contentInsertionConfiguration: ContentInsertionConfiguration(
|
enabledBorder: InputBorder.none,
|
||||||
allowedMimeTypes: ClipboardAttachmentService
|
focusedBorder: InputBorder.none,
|
||||||
.supportedImageMimeTypes
|
errorBorder: InputBorder.none,
|
||||||
.toList(),
|
disabledBorder: InputBorder.none,
|
||||||
onContentInserted: _handleContentInserted,
|
contentPadding: contentPadding,
|
||||||
),
|
isDense: true,
|
||||||
onSubmitted: (_) {
|
alignLabelWithHint: true,
|
||||||
if (sendOnEnter) {
|
),
|
||||||
_sendMessage();
|
// Enable pasting images and files from clipboard
|
||||||
}
|
contentInsertionConfiguration:
|
||||||
},
|
ContentInsertionConfiguration(
|
||||||
onTap: () {
|
allowedMimeTypes: ClipboardAttachmentService
|
||||||
if (!widget.enabled) return;
|
.supportedImageMimeTypes
|
||||||
_ensureFocusedIfEnabled();
|
.toList(),
|
||||||
},
|
onContentInserted: _handleContentInserted,
|
||||||
);
|
),
|
||||||
},
|
onSubmitted: (_) {
|
||||||
|
if (sendOnEnter) {
|
||||||
|
_sendMessage();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onTap: () {
|
||||||
|
if (!widget.enabled) return;
|
||||||
|
_ensureFocusedIfEnabled();
|
||||||
|
},
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|||||||
Reference in New Issue
Block a user