diff --git a/.vscode/settings.json b/.vscode/settings.json index 25ea52b..8708bf5 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -3,6 +3,7 @@ "Appbar", "Displayname", "Homeserver", + "localpart", "prefs", "vodozemac" ] diff --git a/lib/controllers/author_controller.dart b/lib/controllers/author_controller.dart index 8b709d3..70b7343 100644 --- a/lib/controllers/author_controller.dart +++ b/lib/controllers/author_controller.dart @@ -1,10 +1,10 @@ import "dart:async"; -import "package:collection/collection.dart"; import "package:fast_immutable_collections/fast_immutable_collections.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/members_controller.dart"; +import "package:nexus/controllers/user_controller.dart"; +import "package:nexus/helpers/extensions/get_localpart.dart"; import "package:nexus/models/membership.dart"; import "package:nexus/models/membership_status.dart"; @@ -15,11 +15,7 @@ class AuthorController extends AsyncNotifier { @override Future build() async { final member = await ref.watch( - MembersController.provider.selectAsync( - (value) => value.firstWhereOrNull( - (membership) => membership.userId == message.authorId, - ), - ), + UserController.provider(message.authorId).future, ); final pmp = message.metadata?["pmp"] == null @@ -39,9 +35,7 @@ class AuthorController extends AsyncNotifier { status: member?.status ?? MembershipStatus.leave, avatarUrl: pmp?.avatarUrl ?? member?.avatarUrl, displayName: - pmp?.displayName ?? - member?.displayName ?? - message.authorId.substring(1).split(":").first, + pmp?.displayName ?? member?.displayName ?? message.authorId.localpart, userId: message.authorId, ); } diff --git a/lib/controllers/user_controller.dart b/lib/controllers/user_controller.dart new file mode 100644 index 0000000..e7ca973 --- /dev/null +++ b/lib/controllers/user_controller.dart @@ -0,0 +1,40 @@ +import "dart:async"; +import "package:collection/collection.dart"; +import "package:flutter_riverpod/flutter_riverpod.dart"; +import "package:nexus/controllers/members_controller.dart"; +import "package:nexus/controllers/profile_controller.dart"; +import "package:nexus/helpers/extensions/get_localpart.dart"; +import "package:nexus/models/membership.dart"; +import "package:nexus/models/membership_status.dart"; + +class UserController extends AsyncNotifier { + final String userId; + UserController(this.userId); + + @override + Future build() async { + final member = await ref.watch( + MembersController.provider.selectAsync( + (value) => + value.firstWhereOrNull((membership) => membership.userId == userId), + ), + ); + + if (member != null) return member; + + final profile = await ref.watch(ProfileController.provider(userId).future); + return Membership( + status: MembershipStatus.leave, + avatarUrl: profile.avatarUrl == null + ? null + : Uri.tryParse(profile.avatarUrl!), + displayName: profile.displayName ?? userId.localpart, + userId: userId, + ); + } + + static final provider = + AsyncNotifierProvider.family( + UserController.new, + ); +} diff --git a/lib/helpers/extensions/get_localpart.dart b/lib/helpers/extensions/get_localpart.dart new file mode 100644 index 0000000..445351f --- /dev/null +++ b/lib/helpers/extensions/get_localpart.dart @@ -0,0 +1,3 @@ +extension GetLocalpart on String { + String get localpart => substring(1).split(":").first; +} diff --git a/lib/widgets/chat_page/html/mention_chip.dart b/lib/widgets/chat_page/html/mention_chip.dart index c2b832d..6e9b741 100644 --- a/lib/widgets/chat_page/html/mention_chip.dart +++ b/lib/widgets/chat_page/html/mention_chip.dart @@ -1,25 +1,44 @@ import "package:flutter/material.dart"; +import "package:flutter_riverpod/flutter_riverpod.dart"; +import "package:nexus/controllers/user_controller.dart"; import "package:nexus/helpers/extensions/link_to_mention.dart"; +import "package:nexus/helpers/extensions/show_user_popover.dart"; -class MentionChip extends StatelessWidget { +class MentionChip extends ConsumerWidget { final String label; const MentionChip(this.label, {super.key}); @override - Widget build(BuildContext context) => ActionChip( - label: Text( - label.mention ?? label, - style: TextStyle( - fontWeight: FontWeight.bold, - color: Theme.of(context).colorScheme.onPrimary, + Widget build(BuildContext context, WidgetRef ref) { + final mention = label.mention; + final membership = + mention?.startsWith("@") == true || label.startsWith("@") == true + ? ref + .watch(UserController.provider(mention ?? label)) + .whenOrNull(data: (data) => data) + : null; + + return InkWell( + onTapUp: (details) { + if (membership != null) { + context.showUserPopover( + membership, + globalPosition: details.globalPosition, + ); + } + }, + child: Chip( + label: Text( + (membership == null ? null : "@${membership.displayName}") ?? + mention ?? + label, + style: TextStyle( + fontWeight: FontWeight.bold, + color: Theme.of(context).colorScheme.onPrimary, + ), + ), + backgroundColor: Theme.of(context).colorScheme.primary, ), - ), - backgroundColor: Theme.of(context).colorScheme.primary, - onPressed: () => showDialog( - context: context, - builder: (_) => Dialog( - child: Text("TODO: Open room or join room dialog, or user popover"), - ), - ), - ); + ); + } }