Autofocus chatbox on edit/reply

This commit is contained in:
Henry Hiles 2026-04-05 11:21:15 -04:00
commit 92f6b2fbba
Signed by: Henry-Hiles
SSH key fingerprint: SHA256:VKQUdS31Q90KvX7EkKMHMBpUspcmItAh86a+v7PGiIs
3 changed files with 66 additions and 50 deletions

View file

@ -1,6 +1,5 @@
import "package:fast_immutable_collections/fast_immutable_collections.dart"; import "package:fast_immutable_collections/fast_immutable_collections.dart";
import "package:flutter/material.dart"; import "package:flutter/material.dart";
import "package:flutter/services.dart";
import "package:flutter_chat_core/flutter_chat_core.dart"; import "package:flutter_chat_core/flutter_chat_core.dart";
import "package:flutter_hooks/flutter_hooks.dart"; import "package:flutter_hooks/flutter_hooks.dart";
import "package:fluttertagger/fluttertagger.dart"; import "package:fluttertagger/fluttertagger.dart";
@ -15,6 +14,7 @@ class ChatBox extends HookConsumerWidget {
final Message? relatedMessage; final Message? relatedMessage;
final RelationType relationType; final RelationType relationType;
final VoidCallback onDismiss; final VoidCallback onDismiss;
final FocusNode? node;
final Future<void> Function( final Future<void> Function(
String text, { String text, {
required bool shouldMention, required bool shouldMention,
@ -26,6 +26,7 @@ class ChatBox extends HookConsumerWidget {
required this.relationType, required this.relationType,
required this.onDismiss, required this.onDismiss,
required this.onSend, required this.onSend,
this.node,
super.key, super.key,
}); });
@ -55,18 +56,6 @@ class ChatBox extends HookConsumerWidget {
controller.value.text = ""; controller.value.text = "";
} }
final node = useFocusNode(
onKeyEvent: (_, event) {
if (event is KeyDownEvent &&
event.logicalKey == LogicalKeyboardKey.escape) {
onDismiss();
return KeyEventResult.handled;
}
return KeyEventResult.ignored;
},
);
final style = TextStyle( final style = TextStyle(
color: theme.colorScheme.primary, color: theme.colorScheme.primary,
fontWeight: FontWeight.bold, fontWeight: FontWeight.bold,
@ -135,7 +124,7 @@ class ChatBox extends HookConsumerWidget {
triggerCharacter: triggerCharacter.value, triggerCharacter: triggerCharacter.value,
addTag: ({required id, required name}) { addTag: ({required id, required name}) {
controller.value.addTag(id: id, name: name); controller.value.addTag(id: id, name: name);
node.requestFocus(); node?.requestFocus();
}, },
), ),
controller: controller.value, controller: controller.value,

View file

@ -9,12 +9,12 @@ import "package:nexus/widgets/chat_page/room_menu.dart";
class RoomAppbar extends ConsumerWidget implements PreferredSizeWidget { class RoomAppbar extends ConsumerWidget implements PreferredSizeWidget {
final bool isDesktop; final bool isDesktop;
final void Function(BuildContext context) onOpenMemberList; final void Function(BuildContext context)? onOpenMemberList;
final void Function(BuildContext context) onOpenDrawer; final void Function(BuildContext context) onOpenDrawer;
const RoomAppbar({ const RoomAppbar({
required this.isDesktop, required this.isDesktop,
required this.onOpenMemberList,
required this.onOpenDrawer, required this.onOpenDrawer,
this.onOpenMemberList,
super.key, super.key,
}); });
@ -23,10 +23,12 @@ class RoomAppbar extends ConsumerWidget implements PreferredSizeWidget {
@override @override
Widget build(BuildContext context, WidgetRef ref) { Widget build(BuildContext context, WidgetRef ref) {
final room = ref.watch(SelectedRoomController.provider)!; final room = ref.watch(SelectedRoomController.provider);
return Appbar( return Appbar(
leading: isDesktop leading: isDesktop
? ExpandableImage( ? room == null
? null
: ExpandableImage(
room.metadata?.avatar?.toString(), room.metadata?.avatar?.toString(),
child: AvatarOrHash( child: AvatarOrHash(
room.metadata?.avatar, room.metadata?.avatar,
@ -37,7 +39,9 @@ class RoomAppbar extends ConsumerWidget implements PreferredSizeWidget {
) )
: DrawerButton(onPressed: () => onOpenDrawer(context)), : DrawerButton(onPressed: () => onOpenDrawer(context)),
scrolledUnderElevation: 0, scrolledUnderElevation: 0,
title: Column( title: room == null
? null
: Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
Text( Text(
@ -63,11 +67,11 @@ class RoomAppbar extends ConsumerWidget implements PreferredSizeWidget {
tooltip: "Open pinned messages", tooltip: "Open pinned messages",
), ),
IconButton( IconButton(
onPressed: () => onOpenMemberList(context), onPressed: () => onOpenMemberList?.call(context),
tooltip: "Open member list", tooltip: "Open member list",
icon: Icon(Icons.people), icon: Icon(Icons.people),
), ),
RoomMenu(room), if (room != null) RoomMenu(room),
].toIList(), ].toIList(),
); );
} }

View file

@ -1,4 +1,5 @@
import "package:flutter/material.dart"; import "package:flutter/material.dart";
import "package:flutter/services.dart";
import "package:flutter_chat_core/flutter_chat_core.dart"; import "package:flutter_chat_core/flutter_chat_core.dart";
import "package:flutter_chat_ui/flutter_chat_ui.dart"; import "package:flutter_chat_ui/flutter_chat_ui.dart";
import "package:flutter_hooks/flutter_hooks.dart"; import "package:flutter_hooks/flutter_hooks.dart";
@ -47,17 +48,36 @@ class RoomChat extends HookConsumerWidget {
final danger = theme.colorScheme.error; final danger = theme.colorScheme.error;
if (roomId == null || userId == null) { if (roomId == null || userId == null) {
return Center( return Scaffold(
appBar: RoomAppbar(
isDesktop: isDesktop,
onOpenDrawer: (_) => Scaffold.of(context).openDrawer(),
onOpenMemberList: null,
),
body: Center(
child: Text( child: Text(
"Nothing to see here...", "Nothing to see here...",
style: theme.textTheme.headlineMedium, style: theme.textTheme.headlineMedium,
), ),
),
); );
} }
final controllerProvider = RoomChatController.provider(roomId); final controllerProvider = RoomChatController.provider(roomId);
final notifier = ref.watch(controllerProvider.notifier); final notifier = ref.watch(controllerProvider.notifier);
final composerNode = useFocusNode(
onKeyEvent: (_, event) {
if (event is KeyDownEvent &&
event.logicalKey == LogicalKeyboardKey.escape) {
relatedMessage.value = null;
return KeyEventResult.handled;
}
return KeyEventResult.ignored;
},
);
List<PopupMenuEntry> getMessageOptions(Message message) { List<PopupMenuEntry> getMessageOptions(Message message) {
final isSentByMe = message.authorId == userId; final isSentByMe = message.authorId == userId;
return [ return [
@ -65,6 +85,7 @@ class RoomChat extends HookConsumerWidget {
onTap: () { onTap: () {
relatedMessage.value = message; relatedMessage.value = message;
relationType.value = RelationType.reply; relationType.value = RelationType.reply;
composerNode.requestFocus();
}, },
child: ListTile(leading: Icon(Icons.reply), title: Text("Reply")), child: ListTile(leading: Icon(Icons.reply), title: Text("Reply")),
), ),
@ -73,6 +94,7 @@ class RoomChat extends HookConsumerWidget {
onTap: () { onTap: () {
relatedMessage.value = message; relatedMessage.value = message;
relationType.value = RelationType.edit; relationType.value = RelationType.edit;
composerNode.requestFocus();
}, },
child: ListTile(leading: Icon(Icons.edit), title: Text("Edit")), child: ListTile(leading: Icon(Icons.edit), title: Text("Edit")),
), ),
@ -259,6 +281,7 @@ class RoomChat extends HookConsumerWidget {
), ),
composerBuilder: (_) => ChatBox( composerBuilder: (_) => ChatBox(
node: composerNode,
onSend: onSend:
( (
text, { text, {