fix wrapping issues on event previews

This commit is contained in:
Henry Hiles 2026-10-05 11:16:11 -04:00
commit 66cfb0e2c6
Signed by: Henry-Hiles
SSH key fingerprint: SHA256:VKQUdS31Q90KvX7EkKMHMBpUspcmItAh86a+v7PGiIs
5 changed files with 237 additions and 115 deletions

View file

@ -23,10 +23,7 @@ class const EventPreview(final Event event, {super.key})
crossAxisAlignment: .start,
children: [
if (event.content is MessageContent)
DefaultTextHeightBehavior(
textHeightBehavior: .new(),
child: MessageDisplayname(event),
),
MessageDisplayname(event, shouldWrap: false),
EventRenderer(
event.rowId,
roomId: event.roomId,

View file

@ -11,6 +11,7 @@ class const MessageDisplayname(
final Event event, {
final TextStyle? style,
final bool clickable = true,
final bool shouldWrap = true,
super.key,
}) extends ConsumerWidget {
@override
@ -27,10 +28,7 @@ class const MessageDisplayname(
roomId: event.roomId,
)
: null,
child: Wrap(
spacing: 4,
crossAxisAlignment: .center,
children: [
child: switch ([
Text(
value?.displayName ?? event.sender.localpart,
style:
@ -46,8 +44,18 @@ class const MessageDisplayname(
maxLines: 1,
overflow: .ellipsis,
),
],
]) {
final children when shouldWrap => Wrap(
spacing: 4,
crossAxisAlignment: .center,
children: children,
),
final children => Row(
spacing: 4,
mainAxisSize: .min,
children: [for (final child in children) Flexible(child: child)],
),
},
),
};
}

View file

@ -116,39 +116,98 @@ class const EventRenderer(
MembershipContent content => switch (event.previousContent) {
MembershipContent(:final status) =>
status == content.status ? null : MembershipRenderer(event),
_ => MembershipRenderer(event),
status == content.status
? null
: MembershipRenderer(event, maxLines: maxLines),
_ => MembershipRenderer(event, maxLines: maxLines),
},
AvatarContent() => GenericEventRenderer(Icons.interests, [
MessageDisplayname(event),
Text("changed the room avatar"),
]),
AvatarContent() => GenericEventRenderer(
Icons.interests,
maxLines: maxLines,
[
WidgetSpan(
alignment: .middle,
child: MessageDisplayname(
event,
shouldWrap: maxLines == null,
),
),
TextSpan(text: "changed the room avatar"),
],
),
CreateContent() => GenericEventRenderer(Icons.add, [
MessageDisplayname(event),
Text("created the room"),
]),
CreateContent() => GenericEventRenderer(
Icons.add,
maxLines: maxLines,
[
WidgetSpan(
alignment: .middle,
child: MessageDisplayname(
event,
shouldWrap: maxLines == null,
),
),
TextSpan(text: "created the room"),
],
),
PowerLevelsContent() => GenericEventRenderer(Icons.power, [
MessageDisplayname(event),
Text("changed the room's power levels"),
]),
PowerLevelsContent() => GenericEventRenderer(
Icons.power,
maxLines: maxLines,
[
WidgetSpan(
alignment: .middle,
child: MessageDisplayname(
event,
shouldWrap: maxLines == null,
),
),
TextSpan(text: "changed the room's power levels"),
],
),
JoinRulesContent() => GenericEventRenderer(Icons.rule, [
MessageDisplayname(event),
Text("changed the room's join rules"),
]),
JoinRulesContent() => GenericEventRenderer(
Icons.rule,
maxLines: maxLines,
[
WidgetSpan(
alignment: .middle,
child: MessageDisplayname(
event,
shouldWrap: maxLines == null,
),
),
TextSpan(text: "changed the room's join rules"),
],
),
TopicContent() => GenericEventRenderer(Icons.description, [
MessageDisplayname(event),
Text("updated the room topic"),
]),
TopicContent() => GenericEventRenderer(
Icons.description,
maxLines: maxLines,
[
WidgetSpan(
alignment: .middle,
child: MessageDisplayname(
event,
shouldWrap: maxLines == null,
),
),
TextSpan(text: "updated the room topic"),
],
),
HistoryVisibilityContent(:final historyVisibility) =>
GenericEventRenderer(Icons.history, [
MessageDisplayname(event),
Text(
GenericEventRenderer(Icons.history, maxLines: maxLines, [
WidgetSpan(
alignment: .middle,
child: MessageDisplayname(
event,
shouldWrap: maxLines == null,
),
),
TextSpan(
text:
"changed the room's history visibility to ${switch (historyVisibility) {
.invited => "since invited",
.joined => "since joined",
@ -158,20 +217,47 @@ class const EventRenderer(
),
]),
PinnedEventsContent() => GenericEventRenderer(Icons.push_pin, [
MessageDisplayname(event),
Text("pinned/unpinned some events"),
]),
PinnedEventsContent() => GenericEventRenderer(
Icons.push_pin,
maxLines: maxLines,
[
WidgetSpan(
alignment: .middle,
child: MessageDisplayname(
event,
shouldWrap: maxLines == null,
),
),
TextSpan(text: "pinned/unpinned some events"),
],
),
ServerACLContent() => GenericEventRenderer(Icons.list, [
MessageDisplayname(event),
Text("updated the server ban list"),
]),
ServerACLContent() => GenericEventRenderer(
Icons.list,
maxLines: maxLines,
[
WidgetSpan(
alignment: .middle,
child: MessageDisplayname(
event,
shouldWrap: maxLines == null,
),
),
TextSpan(text: "updated the server ban list"),
],
),
CanonicalAliasContent(:final alias, :final altAliases) =>
GenericEventRenderer(Icons.numbers, [
MessageDisplayname(event),
Text(switch ([
GenericEventRenderer(Icons.numbers, maxLines: maxLines, [
WidgetSpan(
alignment: .middle,
child: MessageDisplayname(
event,
shouldWrap: maxLines == null,
),
),
TextSpan(
text: switch ([
if (event.previousContent case CanonicalAliasContent(
alias: final prevAlias,
altAliases: final prevAltAliases,
@ -185,7 +271,9 @@ class const EventRenderer(
if (prevAltAliases
.remove(alias ?? "")
.remove(prevAlias ?? "") !=
altAliases.remove(alias ?? "").remove(prevAlias ?? ""))
altAliases
.remove(alias ?? "")
.remove(prevAlias ?? ""))
"changed the room's aliases",
] else ...[
if (alias != null) "set the room's canonical alias",
@ -194,7 +282,8 @@ class const EventRenderer(
]) {
[] => "did something related to room aliases",
List prev => prev.join(" and "),
}),
},
),
]),
_ => null,
};

View file

@ -2,7 +2,8 @@ import "package:material_ui/material_ui.dart";
class const GenericEventRenderer(
final IconData icon,
final List<Widget> children, {
final List<InlineSpan> children, {
final int? maxLines,
super.key,
}) extends StatelessWidget {
@override
@ -13,7 +14,20 @@ class const GenericEventRenderer(
mainAxisSize: .min,
children: [
Padding(padding: .symmetric(horizontal: 4), child: Icon(icon)),
Flexible(child: Wrap(spacing: 4, children: children)),
Flexible(
child: Text.rich(
TextSpan(
children: [
for (final (index, child) in children.indexed) ...[
if (index > 0) TextSpan(text: " "),
child,
],
],
),
maxLines: maxLines ?? 9007199254740991,
overflow: .ellipsis,
),
),
],
),
);

View file

@ -7,8 +7,11 @@ import "package:nexus/models/event.dart";
import "package:nexus/widgets/lazy_loading/message_displayname.dart";
import "package:nexus/widgets/renderers/generic_event.dart";
class const MembershipRenderer(final Event event, {super.key})
extends StatelessWidget {
class const MembershipRenderer(
final Event event, {
final int? maxLines,
super.key,
}) extends StatelessWidget {
@override
Widget build(BuildContext context) {
assert(
@ -17,8 +20,13 @@ class const MembershipRenderer(final Event event, {super.key})
);
return switch (event.content) {
MembershipContent content => GenericEventRenderer(Icons.people, [
InkWell(
MembershipContent content => GenericEventRenderer(
Icons.people,
maxLines: maxLines,
[
WidgetSpan(
alignment: .middle,
child: InkWell(
onTap: () => context.showUserPopover(
content,
event.stateKey!,
@ -31,9 +39,9 @@ class const MembershipRenderer(final Event event, {super.key})
style: .new(color: event.sender.colorHash, fontWeight: .bold),
),
),
Text(
overflow: .ellipsis,
maxLines: 1,
),
TextSpan(
text:
"${switch (content.status) {
.invite => "was invited to",
.join => "joined",
@ -42,9 +50,15 @@ class const MembershipRenderer(final Event event, {super.key})
.knock => "asked to join",
}} the room${event.sender == event.stateKey ? "" : " by"}",
),
if (event.sender != event.stateKey) MessageDisplayname(event),
if (content.reason != null) Text("for \"${content.reason}\""),
]),
if (event.sender != event.stateKey)
WidgetSpan(
alignment: .middle,
child: MessageDisplayname(event, shouldWrap: maxLines == null),
),
if (content.reason != null)
TextSpan(text: "for \"${content.reason}\""),
],
),
_ => SizedBox.shrink(),
};
}