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, crossAxisAlignment: .start,
children: [ children: [
if (event.content is MessageContent) if (event.content is MessageContent)
DefaultTextHeightBehavior( MessageDisplayname(event, shouldWrap: false),
textHeightBehavior: .new(),
child: MessageDisplayname(event),
),
EventRenderer( EventRenderer(
event.rowId, event.rowId,
roomId: event.roomId, roomId: event.roomId,

View file

@ -11,6 +11,7 @@ class const MessageDisplayname(
final Event event, { final Event event, {
final TextStyle? style, final TextStyle? style,
final bool clickable = true, final bool clickable = true,
final bool shouldWrap = true,
super.key, super.key,
}) extends ConsumerWidget { }) extends ConsumerWidget {
@override @override
@ -27,10 +28,7 @@ class const MessageDisplayname(
roomId: event.roomId, roomId: event.roomId,
) )
: null, : null,
child: Wrap( child: switch ([
spacing: 4,
crossAxisAlignment: .center,
children: [
Text( Text(
value?.displayName ?? event.sender.localpart, value?.displayName ?? event.sender.localpart,
style: style:
@ -46,8 +44,18 @@ class const MessageDisplayname(
maxLines: 1, maxLines: 1,
overflow: .ellipsis, 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 content => switch (event.previousContent) {
MembershipContent(:final status) => MembershipContent(:final status) =>
status == content.status ? null : MembershipRenderer(event), status == content.status
_ => MembershipRenderer(event), ? null
: MembershipRenderer(event, maxLines: maxLines),
_ => MembershipRenderer(event, maxLines: maxLines),
}, },
AvatarContent() => GenericEventRenderer(Icons.interests, [ AvatarContent() => GenericEventRenderer(
MessageDisplayname(event), Icons.interests,
Text("changed the room avatar"), maxLines: maxLines,
]), [
WidgetSpan(
alignment: .middle,
child: MessageDisplayname(
event,
shouldWrap: maxLines == null,
),
),
TextSpan(text: "changed the room avatar"),
],
),
CreateContent() => GenericEventRenderer(Icons.add, [ CreateContent() => GenericEventRenderer(
MessageDisplayname(event), Icons.add,
Text("created the room"), maxLines: maxLines,
]), [
WidgetSpan(
alignment: .middle,
child: MessageDisplayname(
event,
shouldWrap: maxLines == null,
),
),
TextSpan(text: "created the room"),
],
),
PowerLevelsContent() => GenericEventRenderer(Icons.power, [ PowerLevelsContent() => GenericEventRenderer(
MessageDisplayname(event), Icons.power,
Text("changed the room's power levels"), maxLines: maxLines,
]), [
WidgetSpan(
alignment: .middle,
child: MessageDisplayname(
event,
shouldWrap: maxLines == null,
),
),
TextSpan(text: "changed the room's power levels"),
],
),
JoinRulesContent() => GenericEventRenderer(Icons.rule, [ JoinRulesContent() => GenericEventRenderer(
MessageDisplayname(event), Icons.rule,
Text("changed the room's join rules"), maxLines: maxLines,
]), [
WidgetSpan(
alignment: .middle,
child: MessageDisplayname(
event,
shouldWrap: maxLines == null,
),
),
TextSpan(text: "changed the room's join rules"),
],
),
TopicContent() => GenericEventRenderer(Icons.description, [ TopicContent() => GenericEventRenderer(
MessageDisplayname(event), Icons.description,
Text("updated the room topic"), maxLines: maxLines,
]), [
WidgetSpan(
alignment: .middle,
child: MessageDisplayname(
event,
shouldWrap: maxLines == null,
),
),
TextSpan(text: "updated the room topic"),
],
),
HistoryVisibilityContent(:final historyVisibility) => HistoryVisibilityContent(:final historyVisibility) =>
GenericEventRenderer(Icons.history, [ GenericEventRenderer(Icons.history, maxLines: maxLines, [
MessageDisplayname(event), WidgetSpan(
Text( alignment: .middle,
child: MessageDisplayname(
event,
shouldWrap: maxLines == null,
),
),
TextSpan(
text:
"changed the room's history visibility to ${switch (historyVisibility) { "changed the room's history visibility to ${switch (historyVisibility) {
.invited => "since invited", .invited => "since invited",
.joined => "since joined", .joined => "since joined",
@ -158,20 +217,47 @@ class const EventRenderer(
), ),
]), ]),
PinnedEventsContent() => GenericEventRenderer(Icons.push_pin, [ PinnedEventsContent() => GenericEventRenderer(
MessageDisplayname(event), Icons.push_pin,
Text("pinned/unpinned some events"), maxLines: maxLines,
]), [
WidgetSpan(
alignment: .middle,
child: MessageDisplayname(
event,
shouldWrap: maxLines == null,
),
),
TextSpan(text: "pinned/unpinned some events"),
],
),
ServerACLContent() => GenericEventRenderer(Icons.list, [ ServerACLContent() => GenericEventRenderer(
MessageDisplayname(event), Icons.list,
Text("updated the server ban list"), maxLines: maxLines,
]), [
WidgetSpan(
alignment: .middle,
child: MessageDisplayname(
event,
shouldWrap: maxLines == null,
),
),
TextSpan(text: "updated the server ban list"),
],
),
CanonicalAliasContent(:final alias, :final altAliases) => CanonicalAliasContent(:final alias, :final altAliases) =>
GenericEventRenderer(Icons.numbers, [ GenericEventRenderer(Icons.numbers, maxLines: maxLines, [
MessageDisplayname(event), WidgetSpan(
Text(switch ([ alignment: .middle,
child: MessageDisplayname(
event,
shouldWrap: maxLines == null,
),
),
TextSpan(
text: switch ([
if (event.previousContent case CanonicalAliasContent( if (event.previousContent case CanonicalAliasContent(
alias: final prevAlias, alias: final prevAlias,
altAliases: final prevAltAliases, altAliases: final prevAltAliases,
@ -185,7 +271,9 @@ class const EventRenderer(
if (prevAltAliases if (prevAltAliases
.remove(alias ?? "") .remove(alias ?? "")
.remove(prevAlias ?? "") != .remove(prevAlias ?? "") !=
altAliases.remove(alias ?? "").remove(prevAlias ?? "")) altAliases
.remove(alias ?? "")
.remove(prevAlias ?? ""))
"changed the room's aliases", "changed the room's aliases",
] else ...[ ] else ...[
if (alias != null) "set the room's canonical alias", if (alias != null) "set the room's canonical alias",
@ -194,7 +282,8 @@ class const EventRenderer(
]) { ]) {
[] => "did something related to room aliases", [] => "did something related to room aliases",
List prev => prev.join(" and "), List prev => prev.join(" and "),
}), },
),
]), ]),
_ => null, _ => null,
}; };

View file

@ -2,7 +2,8 @@ import "package:material_ui/material_ui.dart";
class const GenericEventRenderer( class const GenericEventRenderer(
final IconData icon, final IconData icon,
final List<Widget> children, { final List<InlineSpan> children, {
final int? maxLines,
super.key, super.key,
}) extends StatelessWidget { }) extends StatelessWidget {
@override @override
@ -13,7 +14,20 @@ class const GenericEventRenderer(
mainAxisSize: .min, mainAxisSize: .min,
children: [ children: [
Padding(padding: .symmetric(horizontal: 4), child: Icon(icon)), 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/lazy_loading/message_displayname.dart";
import "package:nexus/widgets/renderers/generic_event.dart"; import "package:nexus/widgets/renderers/generic_event.dart";
class const MembershipRenderer(final Event event, {super.key}) class const MembershipRenderer(
extends StatelessWidget { final Event event, {
final int? maxLines,
super.key,
}) extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
assert( assert(
@ -17,8 +20,13 @@ class const MembershipRenderer(final Event event, {super.key})
); );
return switch (event.content) { return switch (event.content) {
MembershipContent content => GenericEventRenderer(Icons.people, [ MembershipContent content => GenericEventRenderer(
InkWell( Icons.people,
maxLines: maxLines,
[
WidgetSpan(
alignment: .middle,
child: InkWell(
onTap: () => context.showUserPopover( onTap: () => context.showUserPopover(
content, content,
event.stateKey!, event.stateKey!,
@ -31,20 +39,26 @@ class const MembershipRenderer(final Event event, {super.key})
style: .new(color: event.sender.colorHash, fontWeight: .bold), style: .new(color: event.sender.colorHash, fontWeight: .bold),
), ),
), ),
Text( ),
overflow: .ellipsis, TextSpan(
maxLines: 1, text:
"${switch (content.status) { "${switch (content.status) {
.invite => "was invited to", .invite => "was invited to",
.join => "joined", .join => "joined",
.leave => event.sender == event.stateKey ? "left" : (event.unsigned["prev_content"]?["membership"] == "ban" ? "was unbanned from" : "was kicked from"), .leave => event.sender == event.stateKey ? "left" : (event.unsigned["prev_content"]?["membership"] == "ban" ? "was unbanned from" : "was kicked from"),
.ban => "was banned from", .ban => "was banned from",
.knock => "asked to join", .knock => "asked to join",
}} the room${event.sender == event.stateKey ? "" : " by "}", }} the room${event.sender == event.stateKey ? "" : " by"}",
),
if (event.sender != event.stateKey)
WidgetSpan(
alignment: .middle,
child: MessageDisplayname(event, shouldWrap: maxLines == null),
),
if (content.reason != null)
TextSpan(text: "for \"${content.reason}\""),
],
), ),
if (event.sender != event.stateKey) MessageDisplayname(event),
if (content.reason != null) Text("for \"${content.reason}\""),
]),
_ => SizedBox.shrink(), _ => SizedBox.shrink(),
}; };
} }