clickable user mentions
This commit is contained in:
parent
5796d250c7
commit
d70c439278
5 changed files with 83 additions and 26 deletions
1
.vscode/settings.json
vendored
1
.vscode/settings.json
vendored
|
|
@ -3,6 +3,7 @@
|
||||||
"Appbar",
|
"Appbar",
|
||||||
"Displayname",
|
"Displayname",
|
||||||
"Homeserver",
|
"Homeserver",
|
||||||
|
"localpart",
|
||||||
"prefs",
|
"prefs",
|
||||||
"vodozemac"
|
"vodozemac"
|
||||||
]
|
]
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,10 @@
|
||||||
import "dart:async";
|
import "dart:async";
|
||||||
import "package:collection/collection.dart";
|
|
||||||
import "package:fast_immutable_collections/fast_immutable_collections.dart";
|
import "package:fast_immutable_collections/fast_immutable_collections.dart";
|
||||||
import "package:flutter_chat_core/flutter_chat_core.dart";
|
import "package:flutter_chat_core/flutter_chat_core.dart";
|
||||||
import "package:flutter_riverpod/flutter_riverpod.dart";
|
import "package:flutter_riverpod/flutter_riverpod.dart";
|
||||||
import "package:nexus/controllers/client_state_controller.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.dart";
|
||||||
import "package:nexus/models/membership_status.dart";
|
import "package:nexus/models/membership_status.dart";
|
||||||
|
|
||||||
|
|
@ -15,11 +15,7 @@ class AuthorController extends AsyncNotifier<Membership> {
|
||||||
@override
|
@override
|
||||||
Future<Membership> build() async {
|
Future<Membership> build() async {
|
||||||
final member = await ref.watch(
|
final member = await ref.watch(
|
||||||
MembersController.provider.selectAsync(
|
UserController.provider(message.authorId).future,
|
||||||
(value) => value.firstWhereOrNull(
|
|
||||||
(membership) => membership.userId == message.authorId,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
);
|
||||||
|
|
||||||
final pmp = message.metadata?["pmp"] == null
|
final pmp = message.metadata?["pmp"] == null
|
||||||
|
|
@ -39,9 +35,7 @@ class AuthorController extends AsyncNotifier<Membership> {
|
||||||
status: member?.status ?? MembershipStatus.leave,
|
status: member?.status ?? MembershipStatus.leave,
|
||||||
avatarUrl: pmp?.avatarUrl ?? member?.avatarUrl,
|
avatarUrl: pmp?.avatarUrl ?? member?.avatarUrl,
|
||||||
displayName:
|
displayName:
|
||||||
pmp?.displayName ??
|
pmp?.displayName ?? member?.displayName ?? message.authorId.localpart,
|
||||||
member?.displayName ??
|
|
||||||
message.authorId.substring(1).split(":").first,
|
|
||||||
userId: message.authorId,
|
userId: message.authorId,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
40
lib/controllers/user_controller.dart
Normal file
40
lib/controllers/user_controller.dart
Normal file
|
|
@ -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<Membership?> {
|
||||||
|
final String userId;
|
||||||
|
UserController(this.userId);
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<Membership?> 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, Membership?, String>(
|
||||||
|
UserController.new,
|
||||||
|
);
|
||||||
|
}
|
||||||
3
lib/helpers/extensions/get_localpart.dart
Normal file
3
lib/helpers/extensions/get_localpart.dart
Normal file
|
|
@ -0,0 +1,3 @@
|
||||||
|
extension GetLocalpart on String {
|
||||||
|
String get localpart => substring(1).split(":").first;
|
||||||
|
}
|
||||||
|
|
@ -1,25 +1,44 @@
|
||||||
import "package:flutter/material.dart";
|
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/link_to_mention.dart";
|
||||||
|
import "package:nexus/helpers/extensions/show_user_popover.dart";
|
||||||
|
|
||||||
class MentionChip extends StatelessWidget {
|
class MentionChip extends ConsumerWidget {
|
||||||
final String label;
|
final String label;
|
||||||
const MentionChip(this.label, {super.key});
|
const MentionChip(this.label, {super.key});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) => ActionChip(
|
Widget build(BuildContext context, WidgetRef ref) {
|
||||||
label: Text(
|
final mention = label.mention;
|
||||||
label.mention ?? label,
|
final membership =
|
||||||
style: TextStyle(
|
mention?.startsWith("@") == true || label.startsWith("@") == true
|
||||||
fontWeight: FontWeight.bold,
|
? ref
|
||||||
color: Theme.of(context).colorScheme.onPrimary,
|
.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"),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue