implemented keyboard-aware back navigation and refined message sending logic to prevent phantom drafts and handle mid-send navigation

This commit is contained in:
2026-05-13 18:37:14 +02:00
parent 6c7d217463
commit 37dbb7b374
2 changed files with 114 additions and 92 deletions
+23 -11
View File
@@ -189,9 +189,7 @@ class _ChatViewState extends State<ChatView> with RouteAware {
setState(() {
_activeMatchIndex = (_activeMatchIndex + 1) % _matches.length;
});
WidgetsBinding.instance.addPostFrameCallback(
(_) => _scrollToActiveMatch(),
);
WidgetsBinding.instance.addPostFrameCallback((_) => _scrollToActiveMatch());
}
void _goToNextMatch() {
@@ -200,9 +198,7 @@ class _ChatViewState extends State<ChatView> with RouteAware {
_activeMatchIndex =
(_activeMatchIndex - 1 + _matches.length) % _matches.length;
});
WidgetsBinding.instance.addPostFrameCallback(
(_) => _scrollToActiveMatch(),
);
WidgetsBinding.instance.addPostFrameCallback((_) => _scrollToActiveMatch());
}
void _scrollToActiveMatch() {
@@ -230,8 +226,9 @@ class _ChatViewState extends State<ChatView> with RouteAware {
final activeId = _matches.isNotEmpty
? _matches[_activeMatchIndex].messageId
: null;
final highlightQuery =
_searchActive && _searchQuery.trim().isNotEmpty ? _searchQuery : null;
final highlightQuery = _searchActive && _searchQuery.trim().isNotEmpty
? _searchQuery
: null;
final messages = <Widget>[];
final chronologicalMatchIndex = <int, int>{};
@@ -320,7 +317,19 @@ class _ChatViewState extends State<ChatView> with RouteAware {
}
@override
Widget build(BuildContext context) => Scaffold(
Widget build(BuildContext context) {
// Swallow the first back gesture while the keyboard is visible so it
// dismisses the IME instead of popping the chat — matches platform UX
// expectations (e.g. WhatsApp/Telegram) and prevents accidental exits
// mid-typing.
final keyboardOpen = MediaQuery.viewInsetsOf(context).bottom > 0;
return PopScope(
canPop: !keyboardOpen,
onPopInvokedWithResult: (didPop, _) {
if (didPop) return;
FocusManager.instance.primaryFocus?.unfocus();
},
child: Scaffold(
backgroundColor: const Color(0xffefeae2),
appBar: _searchActive
? ChatSearchAppBar(
@@ -377,8 +386,9 @@ class _ChatViewState extends State<ChatView> with RouteAware {
state.currentToken == widget.room.token,
enablePullToRefresh: false,
child: (state, _) {
final items =
_buildMessages(state.chatResponse!).reversed.toList();
final items = _buildMessages(
state.chatResponse!,
).reversed.toList();
return ScrollablePositionedList.builder(
reverse: true,
itemScrollController: _itemScrollController,
@@ -402,5 +412,7 @@ class _ChatViewState extends State<ChatView> with RouteAware {
],
),
),
),
);
}
}
@@ -110,17 +110,27 @@ class _ChatTextfieldState extends State<ChatTextfield> {
if (_textBoxController.text.isEmpty) return;
final text = _textBoxController.text;
final replyTo = chatBloc.state.data?.referenceMessageId?.toString();
final ownToken = widget.sendToToken;
setState(() => _sendError = null);
await SendMessage(
widget.sendToToken,
ownToken,
SendMessageParams(text, replyTo: replyTo),
).run();
if (!mounted) return;
chatBloc.refresh();
_textBoxController.text = '';
// Reached only on success — SendMessage.run() throws on failure and
// skips this block, leaving the persisted draft as a recovery aid.
// Drafts live on the global SettingsCubit keyed by token, so clear
// them even when the user navigated away mid-send — otherwise the
// sent message lingers as a phantom draft on the chat list.
_setDraft('');
chatBloc.setReferenceMessageId(null);
_setDraftReply(null);
// The global ChatBloc may already point at a different chat (user
// switched mid-send); only touch it while it still references us.
if (chatBloc.state.data?.currentToken == ownToken) {
chatBloc.setReferenceMessageId(null);
chatBloc.refresh();
}
if (!mounted) return;
_textBoxController.text = '';
}
@override