forked from Henry-Hiles/nexus
145 lines
4.6 KiB
Dart
145 lines
4.6 KiB
Dart
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<ChatController> {
|
|
final Room room;
|
|
RoomChatController(this.room);
|
|
|
|
@override
|
|
Future<ChatController> 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<void> 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<void> deleteMessage(Message message, {String? reason}) async {
|
|
final controller = await future;
|
|
await controller.removeMessage(message);
|
|
await room.redactEvent(message.id, reason: reason);
|
|
}
|
|
|
|
Future<void> 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<void> markRead() async {
|
|
if (!room.hasNewMessages) return;
|
|
final controller = await future;
|
|
final id = controller.messages.last.id;
|
|
|
|
await room.setReadMarker(id, mRead: id);
|
|
}
|
|
|
|
Future<void> updateMessage(Message message, Message newMessage) async =>
|
|
(await future).updateMessage(message, newMessage);
|
|
|
|
Future<void> send(
|
|
String message, {
|
|
required Iterable<tagger.Tag> 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<chat.User> 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, ChatController, Room>(
|
|
RoomChatController.new,
|
|
);
|
|
}
|