easy widgets ported to use new event format

This commit is contained in:
Henry Hiles 2026-05-16 16:22:49 -04:00
commit 49c09b3c35
Signed by: Henry-Hiles
SSH key fingerprint: SHA256:VKQUdS31Q90KvX7EkKMHMBpUspcmItAh86a+v7PGiIs
8 changed files with 132 additions and 94 deletions

View file

@ -5,13 +5,14 @@ import "package:fluttertagger/fluttertagger.dart";
import "package:hooks_riverpod/hooks_riverpod.dart"; import "package:hooks_riverpod/hooks_riverpod.dart";
import "package:nexus/controllers/power_level_controller.dart"; import "package:nexus/controllers/power_level_controller.dart";
import "package:nexus/models/configs/power_level_config.dart"; import "package:nexus/models/configs/power_level_config.dart";
import "package:nexus/models/event.dart";
import "package:nexus/models/relation_type.dart"; import "package:nexus/models/relation_type.dart";
import "package:nexus/widgets/chat_page/composer/mention_overlay.dart"; import "package:nexus/widgets/chat_page/composer/mention_overlay.dart";
import "package:nexus/widgets/chat_page/composer/relation_preview.dart"; import "package:nexus/widgets/chat_page/composer/relation_preview.dart";
import "package:nexus/widgets/chat_page/emoji_picker_button.dart"; import "package:nexus/widgets/chat_page/emoji_picker_button.dart";
class ChatBox extends HookConsumerWidget { class ChatBox extends HookConsumerWidget {
final Message? relatedMessage; final Event? relatedEvent;
final RelationType relationType; final RelationType relationType;
final VoidCallback onDismiss; final VoidCallback onDismiss;
final FocusNode? node; final FocusNode? node;
@ -22,7 +23,7 @@ class ChatBox extends HookConsumerWidget {
}) })
onSend; onSend;
const ChatBox({ const ChatBox({
required this.relatedMessage, required this.relatedEvent,
required this.relationType, required this.relationType,
required this.onDismiss, required this.onDismiss,
required this.onSend, required this.onSend,
@ -38,10 +39,8 @@ class ChatBox extends HookConsumerWidget {
final shouldMention = useState(true); final shouldMention = useState(true);
final query = useState(""); final query = useState("");
if (relationType == RelationType.edit && if (relationType == RelationType.edit && controller.value.text.isEmpty) {
relatedMessage is TextMessage && controller.value.text = relatedEvent?.localContent?.editSource ?? "";
controller.value.text.isEmpty) {
controller.value.text = relatedMessage?.metadata?["editSource"] ?? "";
} }
void send() { void send() {
@ -72,7 +71,7 @@ class ChatBox extends HookConsumerWidget {
child: Column( child: Column(
children: [ children: [
RelationPreview( RelationPreview(
relatedMessage, relatedEvent,
shouldMention: shouldMention.value, shouldMention: shouldMention.value,
toggleShouldMention: () => toggleShouldMention: () =>
shouldMention.value = !shouldMention.value, shouldMention.value = !shouldMention.value,

View file

@ -4,6 +4,7 @@ import "package:nexus/controllers/members_by_type_controller.dart";
import "package:nexus/controllers/rooms_controller.dart"; import "package:nexus/controllers/rooms_controller.dart";
import "package:nexus/controllers/via_controller.dart"; import "package:nexus/controllers/via_controller.dart";
import "package:nexus/helpers/extensions/better_when.dart"; import "package:nexus/helpers/extensions/better_when.dart";
import "package:nexus/models/content/membership.dart";
import "package:nexus/models/membership_status.dart"; import "package:nexus/models/membership_status.dart";
import "package:nexus/widgets/avatar_or_hash.dart"; import "package:nexus/widgets/avatar_or_hash.dart";
import "package:nexus/widgets/loading.dart"; import "package:nexus/widgets/loading.dart";
@ -43,33 +44,48 @@ class MentionOverlay extends ConsumerWidget {
? members ? members
: members.where( : members.where(
(member) => (member) =>
member.userId.toLowerCase().contains( member.stateKey
query.toLowerCase(), ?.toLowerCase()
) ==
true ||
member.displayName
.toLowerCase()
.contains( .contains(
query.toLowerCase(), query.toLowerCase(),
) == ) ==
true, true ||
switch (member.content) {
MembershipContent(
:final displayName,
) =>
displayName
.toLowerCase()
.contains(
query.toLowerCase(),
) ==
true,
_ => false,
},
)) ))
.map( .map(
(member) => ListTile( (member) => switch (member.content) {
leading: AvatarOrHash( MembershipContent(
member.avatarUrl, :final displayName,
member.displayName, :final avatarUrl,
), ) =>
title: Text(member.displayName), ListTile(
subtitle: Text(member.userId), leading: AvatarOrHash(
onTap: () => addTag( avatarUrl,
id: "[@${member.displayName}](matrix:u/${member.userId.substring(1)})", displayName,
name: member.userId ),
.substring(1) title: Text(displayName),
.split(":") subtitle: Text(member.stateKey!),
.first, onTap: () => addTag(
), id: "[@$displayName](matrix:u/${member.stateKey!.substring(1)})",
), name: member.stateKey!
.substring(1)
.split(":")
.first,
),
),
_ => SizedBox.shrink(),
},
) )
.toList(), .toList(),
), ),

View file

@ -1,18 +1,19 @@
import "package:flutter/material.dart"; import "package:flutter/material.dart";
import "package:hooks_riverpod/hooks_riverpod.dart"; import "package:hooks_riverpod/hooks_riverpod.dart";
import "package:nexus/models/event.dart";
import "package:nexus/models/relation_type.dart"; import "package:nexus/models/relation_type.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";
class RelationPreview extends ConsumerWidget { class RelationPreview extends ConsumerWidget {
final Message? relatedMessage; final Event? relatedEvent;
final RelationType relationType; final RelationType relationType;
final VoidCallback onDismiss; final VoidCallback onDismiss;
final bool shouldMention; final bool shouldMention;
final VoidCallback toggleShouldMention; final VoidCallback toggleShouldMention;
const RelationPreview( const RelationPreview(
this.relatedMessage, { this.relatedEvent, {
required this.relationType, required this.relationType,
required this.onDismiss, required this.onDismiss,
required this.shouldMention, required this.shouldMention,
@ -22,7 +23,7 @@ class RelationPreview extends ConsumerWidget {
@override @override
Widget build(BuildContext context, WidgetRef ref) { Widget build(BuildContext context, WidgetRef ref) {
if (relatedMessage == null) return SizedBox.shrink(); if (relatedEvent == null) return SizedBox.shrink();
final theme = Theme.of(context); final theme = Theme.of(context);
return Container( return Container(
@ -37,7 +38,7 @@ class RelationPreview extends ConsumerWidget {
style: TextStyle(fontWeight: FontWeight.bold), style: TextStyle(fontWeight: FontWeight.bold),
), ),
MessageAvatar(relatedMessage!), MessageAvatar(relatedEvent!),
Expanded( Expanded(
child: Row( child: Row(
@ -45,16 +46,20 @@ class RelationPreview extends ConsumerWidget {
children: [ children: [
Flexible( Flexible(
child: MessageDisplayname( child: MessageDisplayname(
relatedMessage!, relatedEvent!,
style: theme.textTheme.labelMedium?.copyWith( style: theme.textTheme.labelMedium?.copyWith(
fontWeight: FontWeight.bold, fontWeight: FontWeight.bold,
), ),
), ),
), ),
Expanded( Expanded(
child: Text( child: Text(switch (relatedEvent?.content) {
relatedMessage?.metadata?["body"] ??
relatedMessage?.metadata?["eventType"] ?? _ => ""
}
relatedEvent?.metadata?["body"] ??
relatedEvent?.metadata?["eventType"] ??
"", "",
maxLines: 1, maxLines: 1,
overflow: TextOverflow.ellipsis, overflow: TextOverflow.ellipsis,

View file

@ -10,35 +10,38 @@ class MentionChip extends ConsumerWidget {
@override @override
Widget build(BuildContext context, WidgetRef ref) { Widget build(BuildContext context, WidgetRef ref) {
final membership = content.mention!.startsWith("@") == true final mention = content.mention;
final membership = mention?.startsWith("@") == true
? ref ? ref
.watch(UserController.provider(content.mention!)) .watch(UserController.provider(mention!))
.whenOrNull(data: (data) => data) .whenOrNull(data: (data) => data)
: null; : null;
return InkWell( return mention == null
onTapUp: (details) { ? SizedBox.shrink()
content.mention; : InkWell(
if (membership != null) { onTapUp: (details) {
context.showUserPopover( if (membership != null) {
membership, context.showUserPopover(
globalPosition: details.globalPosition, membership,
); mention,
} globalPosition: details.globalPosition,
}, );
child: IgnorePointer( }
child: Chip( },
label: Text( child: IgnorePointer(
(membership == null ? null : "@${membership.displayName}") ?? child: Chip(
content.mention!, label: Text(
style: TextStyle( (membership == null ? null : "@${membership.displayName}") ??
fontWeight: FontWeight.bold, mention,
color: Theme.of(context).colorScheme.onPrimary, style: TextStyle(
fontWeight: FontWeight.bold,
color: Theme.of(context).colorScheme.onPrimary,
),
),
backgroundColor: Theme.of(context).colorScheme.primary,
),
), ),
), );
backgroundColor: Theme.of(context).colorScheme.primary,
),
),
);
} }
} }

View file

@ -3,22 +3,29 @@ import "package:flutter_riverpod/flutter_riverpod.dart";
import "package:nexus/controllers/author_controller.dart"; import "package:nexus/controllers/author_controller.dart";
import "package:nexus/helpers/extensions/better_when.dart"; import "package:nexus/helpers/extensions/better_when.dart";
import "package:nexus/helpers/extensions/show_user_popover.dart"; import "package:nexus/helpers/extensions/show_user_popover.dart";
import "package:nexus/models/content/membership.dart";
import "package:nexus/models/event.dart";
import "package:nexus/widgets/avatar_or_hash.dart"; import "package:nexus/widgets/avatar_or_hash.dart";
class MessageAvatar extends ConsumerWidget { class MessageAvatar extends ConsumerWidget {
final Message message; final Event event;
final double height; final double height;
const MessageAvatar(this.message, {this.height = 16, super.key}); const MessageAvatar(this.event, {this.height = 16, super.key});
@override @override
Widget build(BuildContext context, WidgetRef ref) => ref Widget build(BuildContext context, WidgetRef ref) => ref
.watch(AuthorController.provider(message)) .watch(AuthorController.provider(event))
.betterWhen( .betterWhen(
data: (membership) => InkWell( data: (membership) => InkWell(
onTapUp: (details) => context.showUserPopover( onTapUp: (details) {
membership, if (event.content is MembershipContent) {
globalPosition: details.globalPosition, context.showUserPopover(
), event.content as MembershipContent,
event.stateKey!,
globalPosition: details.globalPosition,
);
}
},
child: AvatarOrHash( child: AvatarOrHash(
membership.avatarUrl, membership.avatarUrl,
membership.displayName, membership.displayName,
@ -26,6 +33,6 @@ class MessageAvatar extends ConsumerWidget {
), ),
), ),
loading: () => loading: () =>
AvatarOrHash(null, message.sender.substring(1), height: height), AvatarOrHash(null, event.stateKey!.substring(1), height: height),
); );
} }

View file

@ -3,13 +3,14 @@ import "package:flutter_riverpod/flutter_riverpod.dart";
import "package:nexus/controllers/author_controller.dart"; import "package:nexus/controllers/author_controller.dart";
import "package:nexus/helpers/extensions/better_when.dart"; import "package:nexus/helpers/extensions/better_when.dart";
import "package:nexus/helpers/extensions/show_user_popover.dart"; import "package:nexus/helpers/extensions/show_user_popover.dart";
import "package:nexus/models/event.dart";
class MessageDisplayname extends ConsumerWidget { class MessageDisplayname extends ConsumerWidget {
final Message message; final Event event;
final TextStyle? style; final TextStyle? style;
final bool clickable; final bool clickable;
const MessageDisplayname( const MessageDisplayname(
this.message, { this.event, {
this.clickable = true, this.clickable = true,
this.style, this.style,
super.key, super.key,
@ -17,17 +18,18 @@ class MessageDisplayname extends ConsumerWidget {
@override @override
Widget build(BuildContext context, WidgetRef ref) => ref Widget build(BuildContext context, WidgetRef ref) => ref
.watch(AuthorController.provider(message)) .watch(AuthorController.provider(event))
.betterWhen( .betterWhen(
data: (membership) => InkWell( data: (membership) => InkWell(
onTapUp: clickable onTapUp: clickable
? (details) => context.showUserPopover( ? (details) => context.showUserPopover(
membership, membership,
event.stateKey!,
globalPosition: details.globalPosition, globalPosition: details.globalPosition,
) )
: null, : null,
child: Text( child: Text(
"${membership.displayName}${message.metadata?["pmp"] == null ? "" : " (via ${message.sender})"}", "${membership.displayName}${event.pmp == null ? "" : " (via ${event.stateKey})"}",
style: style, style: style,
overflow: TextOverflow.ellipsis, overflow: TextOverflow.ellipsis,
), ),

View file

@ -4,6 +4,7 @@ import "package:hooks_riverpod/hooks_riverpod.dart";
import "package:nexus/controllers/members_by_type_controller.dart"; import "package:nexus/controllers/members_by_type_controller.dart";
import "package:nexus/helpers/extensions/better_when.dart"; import "package:nexus/helpers/extensions/better_when.dart";
import "package:nexus/helpers/extensions/show_user_popover.dart"; import "package:nexus/helpers/extensions/show_user_popover.dart";
import "package:nexus/models/content/membership.dart";
import "package:nexus/models/membership_status.dart"; import "package:nexus/models/membership_status.dart";
import "package:nexus/widgets/avatar_or_hash.dart"; import "package:nexus/widgets/avatar_or_hash.dart";
@ -62,26 +63,31 @@ class MemberList extends HookConsumerWidget {
child: ListView( child: ListView(
children: members children: members
.map( .map(
(member) => InkWell( (member) => switch (member.content) {
onTapUp: (details) => context.showUserPopover( MembershipContent(
member, :final avatarUrl,
globalPosition: details.globalPosition, :final displayName,
), ) =>
child: ListTile( InkWell(
leading: AvatarOrHash( onTapUp: (details) => context.showUserPopover(
member.avatarUrl, member.content as MembershipContent,
member.displayName, member.stateKey!,
globalPosition: details.globalPosition,
),
child: ListTile(
leading: AvatarOrHash(avatarUrl, displayName),
title: Text(
displayName,
overflow: TextOverflow.ellipsis,
),
subtitle: Text(
member.stateKey!,
overflow: TextOverflow.ellipsis,
),
),
), ),
title: Text( _ => SizedBox.shrink(),
member.displayName, },
overflow: TextOverflow.ellipsis,
),
subtitle: Text(
member.userId,
overflow: TextOverflow.ellipsis,
),
),
),
) )
.toList(), .toList(),
), ),

View file

@ -371,7 +371,7 @@ class RoomChat extends HookConsumerWidget {
) )
.onError(showError), .onError(showError),
relationType: relationType.value, relationType: relationType.value,
relatedMessage: relatedMessage.value, relatedEvent: relatedMessage.value,
onDismiss: () => relatedMessage.value = null, onDismiss: () => relatedMessage.value = null,
), ),