Reaction support, currently readonly
This commit is contained in:
parent
624127f3a8
commit
5f5ad911c2
3 changed files with 88 additions and 3 deletions
|
|
@ -2,9 +2,11 @@ import "package:collection/collection.dart";
|
||||||
import "package:fast_immutable_collections/fast_immutable_collections.dart";
|
import "package:fast_immutable_collections/fast_immutable_collections.dart";
|
||||||
import "package:flutter_chat_core/flutter_chat_core.dart";
|
import "package:flutter_chat_core/flutter_chat_core.dart";
|
||||||
import "package:flutter_riverpod/flutter_riverpod.dart";
|
import "package:flutter_riverpod/flutter_riverpod.dart";
|
||||||
|
import "package:nexus/controllers/client_controller.dart";
|
||||||
import "package:nexus/controllers/client_state_controller.dart";
|
import "package:nexus/controllers/client_state_controller.dart";
|
||||||
import "package:nexus/helpers/extensions/mxc_to_https.dart";
|
import "package:nexus/helpers/extensions/mxc_to_https.dart";
|
||||||
import "package:nexus/models/configs/message_config.dart";
|
import "package:nexus/models/configs/message_config.dart";
|
||||||
|
import "package:nexus/models/requests/get_related_events_request.dart";
|
||||||
|
|
||||||
class MessageController extends AsyncNotifier<Message?> {
|
class MessageController extends AsyncNotifier<Message?> {
|
||||||
final MessageConfig config;
|
final MessageConfig config;
|
||||||
|
|
@ -13,7 +15,8 @@ class MessageController extends AsyncNotifier<Message?> {
|
||||||
@override
|
@override
|
||||||
Future<Message?> build() async {
|
Future<Message?> build() async {
|
||||||
try {
|
try {
|
||||||
if (config.event.relationType == "m.replace" && !config.includeEdits) {
|
if ((config.event.relationType == "m.replace" && !config.includeEdits) ||
|
||||||
|
config.room.metadata == null) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -65,10 +68,35 @@ class MessageController extends AsyncNotifier<Message?> {
|
||||||
final replyId =
|
final replyId =
|
||||||
config.event.content["m.relates_to"]?["m.in_reply_to"]?["event_id"];
|
config.event.content["m.relates_to"]?["m.in_reply_to"]?["event_id"];
|
||||||
|
|
||||||
|
final reactionEvents = await ref
|
||||||
|
.watch(ClientController.provider.notifier)
|
||||||
|
.getRelatedEvents(
|
||||||
|
GetRelatedEventsRequest(
|
||||||
|
roomId: config.room.metadata!.id,
|
||||||
|
eventId: config.event.eventId,
|
||||||
|
relationType: "m.annotation",
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
final reactions = reactionEvents
|
||||||
|
?.fold<IMap<String, IList<String>>>(IMap(), (acc, event) {
|
||||||
|
final key = event.content["m.relates_to"]?["key"];
|
||||||
|
if (key == null) return acc;
|
||||||
|
|
||||||
|
return acc.update(
|
||||||
|
key,
|
||||||
|
(list) => list.add(event.authorId),
|
||||||
|
ifAbsent: () => IList([event.authorId]),
|
||||||
|
);
|
||||||
|
})
|
||||||
|
.map((key, value) => MapEntry(key, value.unlock))
|
||||||
|
.unlock;
|
||||||
|
|
||||||
final asText =
|
final asText =
|
||||||
Message.text(
|
Message.text(
|
||||||
metadata: metadata,
|
metadata: metadata,
|
||||||
id: config.event.eventId,
|
id: config.event.eventId,
|
||||||
|
reactions: reactions,
|
||||||
authorId: event.authorId,
|
authorId: event.authorId,
|
||||||
text: content["formatted_body"] ?? content["body"] ?? "",
|
text: content["formatted_body"] ?? content["body"] ?? "",
|
||||||
replyToMessageId: replyId,
|
replyToMessageId: replyId,
|
||||||
|
|
@ -80,6 +108,7 @@ class MessageController extends AsyncNotifier<Message?> {
|
||||||
Message toSystemMessage(String content) => Message.system(
|
Message toSystemMessage(String content) => Message.system(
|
||||||
metadata: {...metadata, "body": content},
|
metadata: {...metadata, "body": content},
|
||||||
id: config.event.eventId,
|
id: config.event.eventId,
|
||||||
|
reactions: reactions,
|
||||||
authorId: event.authorId,
|
authorId: event.authorId,
|
||||||
deliveredAt: config.event.timestamp,
|
deliveredAt: config.event.timestamp,
|
||||||
text: content,
|
text: content,
|
||||||
|
|
@ -104,6 +133,7 @@ class MessageController extends AsyncNotifier<Message?> {
|
||||||
null || "m.image" => Message.image(
|
null || "m.image" => Message.image(
|
||||||
id: config.event.eventId,
|
id: config.event.eventId,
|
||||||
authorId: event.authorId,
|
authorId: event.authorId,
|
||||||
|
reactions: reactions,
|
||||||
source: source,
|
source: source,
|
||||||
replyToMessageId: replyId,
|
replyToMessageId: replyId,
|
||||||
metadata: metadata,
|
metadata: metadata,
|
||||||
|
|
@ -116,6 +146,7 @@ class MessageController extends AsyncNotifier<Message?> {
|
||||||
size: content["info"]["size"],
|
size: content["info"]["size"],
|
||||||
metadata: metadata,
|
metadata: metadata,
|
||||||
id: config.event.eventId,
|
id: config.event.eventId,
|
||||||
|
reactions: reactions,
|
||||||
authorId: event.authorId,
|
authorId: event.authorId,
|
||||||
source: source,
|
source: source,
|
||||||
replyToMessageId: replyId,
|
replyToMessageId: replyId,
|
||||||
|
|
@ -159,6 +190,7 @@ class MessageController extends AsyncNotifier<Message?> {
|
||||||
// ignore: dead_code
|
// ignore: dead_code
|
||||||
? Message.unsupported(
|
? Message.unsupported(
|
||||||
metadata: metadata,
|
metadata: metadata,
|
||||||
|
reactions: reactions,
|
||||||
id: config.event.eventId,
|
id: config.event.eventId,
|
||||||
authorId: event.authorId,
|
authorId: event.authorId,
|
||||||
replyToMessageId: replyId,
|
replyToMessageId: replyId,
|
||||||
|
|
|
||||||
|
|
@ -77,6 +77,7 @@ class RoomChatController extends AsyncNotifier<InMemoryChatController> {
|
||||||
ref.onDispose(
|
ref.onDispose(
|
||||||
ref.listen(NewEventsController.provider(roomId), (_, next) async {
|
ref.listen(NewEventsController.provider(roomId), (_, next) async {
|
||||||
for (final event in next) {
|
for (final event in next) {
|
||||||
|
// TODO: Handle new reactions
|
||||||
if (event.type == "m.room.redaction") {
|
if (event.type == "m.room.redaction") {
|
||||||
final controller = await future;
|
final controller = await future;
|
||||||
final message = controller.messages.firstWhereOrNull(
|
final message = controller.messages.firstWhereOrNull(
|
||||||
|
|
|
||||||
|
|
@ -1,19 +1,28 @@
|
||||||
|
import "package:cross_cache/cross_cache.dart";
|
||||||
|
import "package:fast_immutable_collections/fast_immutable_collections.dart";
|
||||||
import "package:flutter/material.dart";
|
import "package:flutter/material.dart";
|
||||||
import "package:flutter_chat_core/flutter_chat_core.dart";
|
import "package:flutter_chat_core/flutter_chat_core.dart";
|
||||||
|
import "package:flutter_riverpod/flutter_riverpod.dart";
|
||||||
|
import "package:nexus/controllers/client_state_controller.dart";
|
||||||
|
import "package:nexus/controllers/cross_cache_controller.dart";
|
||||||
|
import "package:nexus/helpers/extensions/get_headers.dart";
|
||||||
|
import "package:nexus/helpers/extensions/mxc_to_https.dart";
|
||||||
import "package:nexus/widgets/chat_page/lazy_loading/message_avatar.dart";
|
import "package:nexus/widgets/chat_page/lazy_loading/message_avatar.dart";
|
||||||
import "package:nexus/widgets/chat_page/lazy_loading/message_displayname.dart";
|
import "package:nexus/widgets/chat_page/lazy_loading/message_displayname.dart";
|
||||||
import "package:timeago/timeago.dart";
|
import "package:timeago/timeago.dart";
|
||||||
|
|
||||||
class MessageWrapper extends StatelessWidget {
|
class MessageWrapper extends ConsumerWidget {
|
||||||
final Message message;
|
final Message message;
|
||||||
final Widget child;
|
final Widget child;
|
||||||
final MessageGroupStatus? groupStatus;
|
final MessageGroupStatus? groupStatus;
|
||||||
const MessageWrapper(this.message, this.child, this.groupStatus, {super.key});
|
const MessageWrapper(this.message, this.child, this.groupStatus, {super.key});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context, WidgetRef ref) {
|
||||||
final theme = Theme.of(context);
|
final theme = Theme.of(context);
|
||||||
final error = message.metadata?["error"];
|
final error = message.metadata?["error"];
|
||||||
|
final clientState = ref.watch(ClientStateController.provider);
|
||||||
|
|
||||||
return ClipRRect(
|
return ClipRRect(
|
||||||
borderRadius: BorderRadius.all(Radius.circular(12)),
|
borderRadius: BorderRadius.all(Radius.circular(12)),
|
||||||
child: AnimatedContainer(
|
child: AnimatedContainer(
|
||||||
|
|
@ -69,6 +78,49 @@ class MessageWrapper extends StatelessWidget {
|
||||||
color: theme.colorScheme.error,
|
color: theme.colorScheme.error,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
Wrap(
|
||||||
|
spacing: 4,
|
||||||
|
runSpacing: 4,
|
||||||
|
children:
|
||||||
|
clientState?.homeserverUrl == null ||
|
||||||
|
message.reactions == null
|
||||||
|
? []
|
||||||
|
: message.reactions!.mapTo((reaction, reactors) {
|
||||||
|
final selected = reactors.contains(
|
||||||
|
clientState!.userId,
|
||||||
|
);
|
||||||
|
return SizedBox(
|
||||||
|
child: ChoiceChip(
|
||||||
|
showCheckmark: false,
|
||||||
|
selected: selected,
|
||||||
|
label: Row(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
spacing: 8,
|
||||||
|
children: [
|
||||||
|
reaction.startsWith("mxc://")
|
||||||
|
? Image(
|
||||||
|
height: 20,
|
||||||
|
image: CachedNetworkImage(
|
||||||
|
headers: ref.headers,
|
||||||
|
Uri.parse(reaction)
|
||||||
|
.mxcToHttps(
|
||||||
|
clientState.homeserverUrl!,
|
||||||
|
)
|
||||||
|
.toString(),
|
||||||
|
ref.watch(
|
||||||
|
CrossCacheController.provider,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
: Text(reaction),
|
||||||
|
Text(reactors.length.toString()),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
onSelected: (value) {}, // TODO
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}).toList(),
|
||||||
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue