import "package:collection/collection.dart"; import "package:flutter_chat_core/flutter_chat_core.dart"; import "package:flutter_chat_core/flutter_chat_core.dart" as chat; import "package:flutter_riverpod/flutter_riverpod.dart"; import "package:matrix/matrix.dart"; import "package:nexus/controllers/avatar_controller.dart"; import "package:nexus/controllers/events_controller.dart"; import "package:nexus/helpers/extensions/event_to_message.dart"; import "package:nexus/helpers/extensions/list_to_messages.dart"; import "package:fluttertagger/fluttertagger.dart" as tagger; import "package:nexus/models/relation_type.dart"; class RoomChatController extends AsyncNotifier { final Room room; RoomChatController(this.room); @override Future build() async { final response = await ref.watch(EventsController.provider(room).future); ref.onDispose( room.client.onTimelineEvent.stream.listen((event) async { if (event.roomId != room.id) return; if (event.type == EventTypes.Redaction) { final controller = await future; final message = controller.messages.firstWhereOrNull( (message) => message.id == event.redacts, ); if (message == null) return; await controller.removeMessage(message); } else { final message = await event.toMessage(includeEdits: true); if (event.relationshipType == RelationshipTypes.edit) { final controller = await future; final oldMessage = controller.messages.firstWhereOrNull( (element) => element.id == event.relationshipEventId, ); if (oldMessage == null || message == null) return; return await updateMessage( oldMessage, message.copyWith(id: oldMessage.id), ); } if (message != null) { return await insertMessage(message); } } }).cancel, ); return InMemoryChatController( messages: await response.chunk.toMessages(room), ); } Future insertMessage(Message message) async { final controller = await future; final oldMessage = message.metadata?["txnId"] == null ? null : controller.messages.firstWhereOrNull( (element) => element.metadata?["txnId"] == message.metadata?["txnId"], ); return oldMessage == null ? controller.insertMessage(message) : controller.updateMessage(oldMessage, message); } Future deleteMessage(Message message, {String? reason}) async { final controller = await future; await controller.removeMessage(message); await room.redactEvent(message.id, reason: reason); } Future loadOlder() async { final controller = await future; final response = await ref .watch(EventsController.provider(room).notifier) .prev(); final messages = await response.chunk.toMessages(room); await controller.insertAllMessages(messages, index: 0); ref.notifyListeners(); } Future markRead() async { if (!room.hasNewMessages) return; final controller = await future; final id = controller.messages.last.id; await room.setReadMarker(id, mRead: id); } Future updateMessage(Message message, Message newMessage) async => (await future).updateMessage(message, newMessage); Future send( String message, { required Iterable tags, required RelationType relationType, Message? relation, }) async { var taggedMessage = message; for (final tag in tags) { final escaped = RegExp.escape(tag.id); final pattern = RegExp(r"@+(" + escaped + r")(#[^#]*#)?"); taggedMessage = taggedMessage.replaceAllMapped( pattern, (match) => match.group(1)!, ); } await room.sendTextEvent( taggedMessage, editEventId: relationType == RelationType.edit ? relation?.id : null, inReplyTo: (relationType == RelationType.reply && relation != null) ? await room.getEventById(relation.id) : null, ); } Future resolveUser(String id) async { final user = await room.client.getUserProfile(id); return chat.User( id: id, name: user.displayname, imageSource: user.avatarUrl == null ? null : (await ref.watch( AvatarController.provider(user.avatarUrl!.toString()).future, )).toString(), ); } static final provider = AsyncNotifierProvider.family .autoDispose( RoomChatController.new, ); }