parent
47645ea327
commit
7f7bdbf525
5 changed files with 1477 additions and 577 deletions
|
|
@ -44,6 +44,7 @@ import "package:nexus/widgets/renderers/power_levels_diff.dart";
|
|||
import "package:nexus/widgets/renderers/pinned_events_diff.dart";
|
||||
import "package:nexus/widgets/renderers/server_acl_diff.dart";
|
||||
import "package:nexus/widgets/swipe_to_reply.dart";
|
||||
import "package:nexus/widgets/thread_summary.dart";
|
||||
import "package:nexus/widgets/timestamp.dart";
|
||||
import "package:nexus/widgets/user_span.dart";
|
||||
|
||||
|
|
@ -54,6 +55,7 @@ class const EventRenderer(
|
|||
final bool textOnly = false,
|
||||
final int? maxLines,
|
||||
final JumpToEvent? jumpToEvent,
|
||||
final void Function(String threadRoot)? onOpenThread,
|
||||
final IList<PopupMenuEntry> Function(Event event)? getEventOptions,
|
||||
|
||||
/// If set, events can be swiped, calling this
|
||||
|
|
@ -106,18 +108,18 @@ class const EventRenderer(
|
|||
(settings.hideMembershipEvents, settings.hideServerAclEvents),
|
||||
);
|
||||
|
||||
// Knocks and their outcomes stay visible, as moderators need to act on them
|
||||
if (hideMembershipEvents &&
|
||||
event.content is MembershipContent &&
|
||||
![event.content, event.previousContent].any(
|
||||
(content) => content is MembershipContent && content.status == .knock,
|
||||
)) {
|
||||
return SizedBox.shrink();
|
||||
}
|
||||
final isHidden =
|
||||
// Knocks and their outcomes stay visible, as moderators need to act
|
||||
// on them
|
||||
(hideMembershipEvents &&
|
||||
event.content is MembershipContent &&
|
||||
![event.content, event.previousContent].any(
|
||||
(content) =>
|
||||
content is MembershipContent && content.status == .knock,
|
||||
)) ||
|
||||
(hideServerAclEvents && event.content is ServerACLContent);
|
||||
|
||||
if (hideServerAclEvents && event.content is ServerACLContent) {
|
||||
return SizedBox.shrink();
|
||||
}
|
||||
if (isHidden && !textOnly) return SizedBox.shrink();
|
||||
|
||||
final isGrouped =
|
||||
(previousEvent?.isShownAsMessage ?? false) &&
|
||||
|
|
@ -170,15 +172,18 @@ class const EventRenderer(
|
|||
onTapReply: jumpToEvent == null
|
||||
? null
|
||||
: () async {
|
||||
final replyEvent = await ref.read(
|
||||
final replyRowId = await ref.read(
|
||||
EventController.provider(
|
||||
.new(roomId: roomId, eventId: event.replyTo!),
|
||||
).future,
|
||||
);
|
||||
if (replyEvent != null) {
|
||||
await jumpToEvent!(replyEvent.rowId);
|
||||
if (replyRowId != null) {
|
||||
await jumpToEvent!(replyRowId);
|
||||
}
|
||||
},
|
||||
onTapThread: onOpenThread == null
|
||||
? null
|
||||
: () => onOpenThread!(event.eventId),
|
||||
isGrouped: isGrouped,
|
||||
maxLines: maxLines,
|
||||
textOnly: textOnly,
|
||||
|
|
@ -236,11 +241,11 @@ class const EventRenderer(
|
|||
),
|
||||
)
|
||||
.value) {
|
||||
final poll?
|
||||
final pollRowId?
|
||||
when ref
|
||||
.watch(
|
||||
PollVotesController.provider(
|
||||
.new(roomId: roomId, eventRowId: poll.rowId),
|
||||
.new(roomId: roomId, eventRowId: pollRowId),
|
||||
),
|
||||
)
|
||||
.value
|
||||
|
|
@ -430,6 +435,15 @@ class const EventRenderer(
|
|||
globalPosition: details.globalPosition,
|
||||
);
|
||||
|
||||
final threadSummary = switch (onOpenThread) {
|
||||
final onOpenThread? when !textOnly && child is! MessageRenderer =>
|
||||
ThreadSummary(event, onTap: () => onOpenThread(event.eventId)),
|
||||
_ => null,
|
||||
};
|
||||
final belowIndent = child is MessageRenderer
|
||||
? 4.0
|
||||
: 8 + GenericEventRenderer.textIndent(context);
|
||||
|
||||
return Column(
|
||||
crossAxisAlignment: .start,
|
||||
children: [
|
||||
|
|
@ -492,6 +506,7 @@ class const EventRenderer(
|
|||
|
||||
...[
|
||||
if (child is! MessageRenderer) ReactionRow(event),
|
||||
?threadSummary,
|
||||
|
||||
// Outside the message itself, so it doesn't move when swiped
|
||||
if (readReceiptsChat case final chat? when event.isShownAsMessage)
|
||||
|
|
@ -523,6 +538,7 @@ class const EventRenderer(
|
|||
RoomChatController.provider((
|
||||
roomId: roomId,
|
||||
contextualEvent: null,
|
||||
threadRoot: null,
|
||||
)).notifier,
|
||||
)
|
||||
.hideClientSticky(event.rowId),
|
||||
|
|
@ -530,13 +546,20 @@ class const EventRenderer(
|
|||
],
|
||||
),
|
||||
),
|
||||
].map((child) => Padding(padding: .only(left: 4), child: child)),
|
||||
].map(
|
||||
(child) => Padding(
|
||||
padding: .only(left: belowIndent),
|
||||
child: child,
|
||||
),
|
||||
),
|
||||
]
|
||||
else if (textOnly)
|
||||
Text(
|
||||
event.redactedBy == null ? "Unknown event type" : "Redacted event",
|
||||
style: errorStyle,
|
||||
),
|
||||
)
|
||||
else if (threadSummary != null)
|
||||
Padding(padding: .only(left: 4), child: threadSummary),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -13,6 +13,14 @@ class const GenericEventRenderer(
|
|||
final List<Widget> details = const [],
|
||||
super.key,
|
||||
}) extends StatelessWidget {
|
||||
static const _iconPadding = 4.0;
|
||||
static const _spacing = 8.0;
|
||||
|
||||
/// How far the text is from the start, past the icon, so what's shown under
|
||||
/// the event can line up with it.
|
||||
static double textIndent(BuildContext context) =>
|
||||
(IconTheme.of(context).size ?? 24) + _iconPadding * 2 + _spacing;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
// Only previewing the event, so it should stay short
|
||||
|
|
@ -22,12 +30,15 @@ class const GenericEventRenderer(
|
|||
return Padding(
|
||||
padding: .symmetric(vertical: 4),
|
||||
child: Row(
|
||||
spacing: 8,
|
||||
spacing: _spacing,
|
||||
mainAxisSize: .min,
|
||||
// At the top, by the text, rather than the middle of any details
|
||||
crossAxisAlignment: .start,
|
||||
children: [
|
||||
Padding(padding: .symmetric(horizontal: 4), child: Icon(icon)),
|
||||
Padding(
|
||||
padding: .symmetric(horizontal: _iconPadding),
|
||||
child: Icon(icon),
|
||||
),
|
||||
Flexible(
|
||||
child: Column(
|
||||
mainAxisSize: .min,
|
||||
|
|
|
|||
|
|
@ -27,10 +27,15 @@ import "package:nexus/widgets/event_preview.dart";
|
|||
import "package:nexus/widgets/players/video.dart";
|
||||
import "package:nexus/widgets/players/audio.dart";
|
||||
import "package:nexus/widgets/renderers/poll.dart";
|
||||
import "package:nexus/widgets/thread_summary.dart";
|
||||
|
||||
class const MessageRenderer(
|
||||
final Event event, {
|
||||
final VoidCallback? onTapReply,
|
||||
|
||||
/// If set, thread roots show a summary of their thread, calling this when
|
||||
/// tapped.
|
||||
final VoidCallback? onTapThread,
|
||||
final bool textOnly = false,
|
||||
final bool isGrouped = false,
|
||||
final int? maxLines,
|
||||
|
|
@ -255,9 +260,10 @@ class const MessageRenderer(
|
|||
),
|
||||
)) {
|
||||
AsyncData(:final value?) ||
|
||||
AsyncLoading(
|
||||
:final value?,
|
||||
) => EventPreview(value),
|
||||
AsyncLoading(:final value?) => EventPreview(
|
||||
value,
|
||||
roomId: event.roomId,
|
||||
),
|
||||
AsyncError _ => Text(
|
||||
"An error occurred while fetching the reply",
|
||||
style: errorStyle,
|
||||
|
|
@ -270,6 +276,8 @@ class const MessageRenderer(
|
|||
),
|
||||
rendered,
|
||||
if (!textOnly) ReactionRow(event),
|
||||
if (onTapThread case final onTapThread? when !textOnly)
|
||||
ThreadSummary(event, onTap: onTapThread),
|
||||
],
|
||||
),
|
||||
),
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ import "package:fast_immutable_collections/fast_immutable_collections.dart";
|
|||
import "package:hooks_riverpod/hooks_riverpod.dart";
|
||||
import "package:material_ui/material_ui.dart";
|
||||
import "package:nexus/controllers/event.dart";
|
||||
import "package:nexus/controllers/rooms.dart";
|
||||
import "package:nexus/helpers/hooks/chat_scroll.dart";
|
||||
import "package:nexus/helpers/extensions/success_scheme.dart";
|
||||
import "package:nexus/models/content/message.dart";
|
||||
|
|
@ -54,17 +55,21 @@ class const PinnedEventsDiff(
|
|||
),
|
||||
)) {
|
||||
// Keeps showing the event while it reloads
|
||||
AsyncValue(value: final event?) => InkWell(
|
||||
AsyncValue(value: final rowId?) => InkWell(
|
||||
borderRadius: .circular(4),
|
||||
hoverColor: color.withValues(alpha: 0.12),
|
||||
onTap: jumpToEvent == null
|
||||
? null
|
||||
: () => jumpToEvent!(event.rowId),
|
||||
onTap: jumpToEvent == null ? null : () => jumpToEvent!(rowId),
|
||||
child: Row(
|
||||
mainAxisSize: .min,
|
||||
children: [
|
||||
// Other events already say who sent them
|
||||
if (event.content is MessageContent) ...[
|
||||
if (ref.watch(
|
||||
RoomsController.provider.select(
|
||||
(rooms) => rooms[roomId]?.events[rowId],
|
||||
),
|
||||
)
|
||||
case final event?
|
||||
when event.content is MessageContent) ...[
|
||||
MessageDisplayname(
|
||||
event,
|
||||
clickable: false,
|
||||
|
|
@ -81,8 +86,8 @@ class const PinnedEventsDiff(
|
|||
Flexible(
|
||||
child: IgnorePointer(
|
||||
child: EventRenderer(
|
||||
event.rowId,
|
||||
roomId: event.roomId,
|
||||
rowId,
|
||||
roomId: roomId,
|
||||
textOnly: true,
|
||||
maxLines: 1,
|
||||
),
|
||||
|
|
|
|||
|
|
@ -66,6 +66,7 @@ class const PollRenderer(final Event event, final PollStart poll, {super.key})
|
|||
final notifier = RoomChatController.provider((
|
||||
roomId: event.roomId,
|
||||
contextualEvent: null,
|
||||
threadRoot: null,
|
||||
)).notifier;
|
||||
|
||||
Future<void> run(
|
||||
|
|
|
|||
Loading…
Reference in a new issue