Fix sending permission thingy

This commit is contained in:
Henry Hiles 2025-12-26 15:04:49 -05:00
commit 6215537f8e
No known key found for this signature in database
2 changed files with 148 additions and 137 deletions

View file

@ -7,6 +7,7 @@ import "package:nexus/controllers/avatar_controller.dart";
import "package:nexus/controllers/events_controller.dart"; import "package:nexus/controllers/events_controller.dart";
import "package:nexus/helpers/extensions/event_to_message.dart"; import "package:nexus/helpers/extensions/event_to_message.dart";
import "package:nexus/helpers/extensions/list_to_messages.dart"; import "package:nexus/helpers/extensions/list_to_messages.dart";
import "package:fluttertagger/fluttertagger.dart" as tagger;
class RoomChatController extends AsyncNotifier<ChatController> { class RoomChatController extends AsyncNotifier<ChatController> {
final Room room; final Room room;
@ -93,11 +94,27 @@ class RoomChatController extends AsyncNotifier<ChatController> {
Future<void> updateMessage(Message message, Message newMessage) async => Future<void> updateMessage(Message message, Message newMessage) async =>
(await future).updateMessage(message, newMessage); (await future).updateMessage(message, newMessage);
Future<void> send(String message, {Message? replyTo}) async => Future<void> send(
room.sendTextEvent( String message, {
message, required Iterable<tagger.Tag> tags,
inReplyTo: replyTo == null ? null : await room.getEventById(replyTo.id), Message? replyTo,
}) async {
var taggedMessage = message;
for (final tag in tags) {
final escaped = RegExp.escape(tag.id.substring(1));
final pattern = RegExp(r"@@(" + escaped + r")#[^#]*#");
taggedMessage = taggedMessage.replaceAllMapped(
pattern,
(m) => "@${m.group(1)}",
); );
}
await room.sendTextEvent(
taggedMessage,
inReplyTo: replyTo == null ? null : await room.getEventById(replyTo.id),
);
}
Future<chat.User> resolveUser(String id) async { Future<chat.User> resolveUser(String id) async {
final user = await room.client.getUserProfile(id); final user = await room.client.getUserProfile(id);

View file

@ -33,19 +33,25 @@ class ChatBox extends HookConsumerWidget {
final triggerCharacter = useState(""); final triggerCharacter = useState("");
final query = useState(""); final query = useState("");
Future<void> send() => ref void send() {
.watch(RoomChatController.provider(room).notifier) ref
.send(controller.value.text, replyTo: replyToMessage); .watch(RoomChatController.provider(room).notifier)
.send(
controller.value.formattedText,
replyTo: replyToMessage,
tags: controller.value.tags,
);
controller.value.text = "";
}
final node = useFocusNode( final node = useFocusNode(
onKeyEvent: (_, event) { onKeyEvent: (_, event) {
if (event is! KeyDownEvent || Platform.isAndroid || Platform.isIOS) { if (event is KeyDownEvent && !Platform.isAndroid && !Platform.isIOS) {
if (event.logicalKey == LogicalKeyboardKey.enter && if (event.logicalKey == LogicalKeyboardKey.enter &&
!HardwareKeyboard.instance.isShiftPressed) { !HardwareKeyboard.instance.isShiftPressed) {
send(); send();
return KeyEventResult.handled; return KeyEventResult.handled;
} } else if (event.logicalKey == LogicalKeyboardKey.escape) {
if (event.logicalKey == LogicalKeyboardKey.escape) {
onDismiss(); onDismiss();
return KeyEventResult.handled; return KeyEventResult.handled;
} }
@ -78,139 +84,127 @@ class ChatBox extends HookConsumerWidget {
Container( Container(
color: theme.colorScheme.surfaceContainerHighest, color: theme.colorScheme.surfaceContainerHighest,
padding: EdgeInsets.symmetric(horizontal: 8), padding: EdgeInsets.symmetric(horizontal: 8),
child: room.canSendDefaultMessages child: Row(
? Row( spacing: 8,
spacing: 8, children: [
children: [ PopupMenuButton(
PopupMenuButton( itemBuilder: (context) => [],
itemBuilder: (context) => [], icon: Icon(Icons.add),
icon: Icon(Icons.add), enabled: room.canSendDefaultMessages,
), ),
Expanded( Expanded(
child: FlutterTagger( child: FlutterTagger(
triggerStrategy: TriggerStrategy.eager, triggerStrategy: TriggerStrategy.eager,
overlay: Padding( overlay: Padding(
padding: EdgeInsets.all(8), padding: EdgeInsets.all(8),
child: ClipRRect( child: ClipRRect(
borderRadius: BorderRadius.all( borderRadius: BorderRadius.all(Radius.circular(12)),
Radius.circular(12), child: Container(
), color: theme.colorScheme.surfaceContainerHigh,
child: Container( padding: EdgeInsets.all(8),
color: child: switch (triggerCharacter.value) {
theme.colorScheme.surfaceContainerHigh, "@" =>
padding: EdgeInsets.all(8), ref
child: switch (triggerCharacter.value) { .watch(MembersController.provider(room))
"@" => .betterWhen(
ref data: (members) => ListView(
.watch( children:
MembersController.provider(room), (query.value.isEmpty
) ? members
.betterWhen( : members.where(
data: (members) => ListView( (member) =>
children: member.senderId
(query.value.isEmpty .contains(
? members query.value,
: members.where( ) ||
(member) => (member.content["displayname"]
member as String?)
.senderId ?.contains(
.contains( query
query .value,
.value, ) ==
) || true,
(member.content["displayname"] ))
as String?) .map(
?.contains( (member) => ListTile(
query.value, leading: AvatarOrHash(
) == ref
true, .watch(
)) AvatarController.provider(
.map( member
(member) => ListTile( .content["avatar_url"]
leading: AvatarOrHash( .toString(),
ref ),
.watch( )
AvatarController.provider( .whenOrNull(
member data: (data) =>
.content["avatar_url"] data,
.toString(),
),
)
.whenOrNull(
data:
(
data,
) =>
data,
),
member
.content["displayname"]
.toString(),
headers: room
.client
.headers,
), ),
title: Text( member
member.content["displayname"] .content["displayname"]
as String? ?? .toString(),
member headers:
.senderId, room.client.headers,
), ),
onTap: () => controller title: Text(
.value member.content["displayname"]
.addTag( as String? ??
id: "member", member.senderId,
name: member ),
.senderId onTap: () => controller
.substring( .value
1, .addTag(
) id: member.senderId,
.split( name: member
":", .senderId
) .substring(1)
.first, .split(":")
), .first,
), ),
) ),
.toList(), )
), .toList(),
), ),
"#" => Text("Todo"), ),
_ => Loading(), "#" => Text("Todo"),
}, _ => Loading(),
),
),
),
controller: controller.value,
onSearch: (newQuery, newTriggerCharacter) {
triggerCharacter.value = newTriggerCharacter;
query.value = newQuery;
}, },
triggerCharacterAndStyles: {
"@": style,
"#": style,
":": style,
},
builder: (context, key) => TextFormField(
maxLines: 12,
minLines: 1,
decoration: InputDecoration(
hintText: "Your message here...",
border: InputBorder.none,
),
controller: controller.value,
key: key,
autofocus: true,
focusNode: node,
),
), ),
), ),
IconButton(onPressed: send, icon: Icon(Icons.send)), ),
], controller: controller.value,
) onSearch: (newQuery, newTriggerCharacter) {
: Text( triggerCharacter.value = newTriggerCharacter;
"You don't have permission to send messages here...", query.value = newQuery;
},
triggerCharacterAndStyles: {
"@": style,
"#": style,
":": style,
},
builder: (context, key) => TextFormField(
enabled: room.canSendDefaultMessages,
maxLines: 12,
minLines: 1,
decoration: InputDecoration(
hintText: room.canSendDefaultMessages
? "Your message here..."
: "You don't have permission to send messages in this room...",
border: InputBorder.none,
),
controller: controller.value,
key: key,
autofocus: true,
focusNode: node,
),
), ),
),
IconButton(
onPressed: room.canSendDefaultMessages ? send : null,
icon: Icon(Icons.send),
),
],
),
), ),
], ],
), ),