fix: route user-tag taps to the group member screen for members (#686)
This commit is contained in:
@@ -547,6 +547,7 @@ class ChatScreen extends HookConsumerWidget {
|
||||
: null,
|
||||
onHorizontalDragEnd: isSearchMode ? null : () => input.setReplyingTo(message),
|
||||
mentionDisplayName: resolveMentionDisplayName,
|
||||
groupId: isGroupChat ? groupId : null,
|
||||
onRetry:
|
||||
!isSearchMode && isOwnMessage && message.deliveryStatus is DeliveryStatus_Failed
|
||||
? () async {
|
||||
|
||||
@@ -12,6 +12,7 @@ import 'package:whitenoise/hooks/use_user_metadata.dart';
|
||||
import 'package:whitenoise/l10n/l10n.dart';
|
||||
import 'package:whitenoise/providers/account_pubkey_provider.dart';
|
||||
import 'package:whitenoise/routes.dart';
|
||||
import 'package:whitenoise/screens/start_chat_screen.dart';
|
||||
import 'package:whitenoise/theme.dart';
|
||||
import 'package:whitenoise/utils/avatar_color.dart';
|
||||
import 'package:whitenoise/utils/metadata.dart' show presentName;
|
||||
@@ -87,6 +88,21 @@ class GroupMemberScreen extends HookConsumerWidget {
|
||||
return null;
|
||||
}, [membersState.error]);
|
||||
|
||||
final didRedirect = useRef(false);
|
||||
useEffect(() {
|
||||
if (didRedirect.value) return null;
|
||||
if (membersState.isLoading || membersState.error != null) return null;
|
||||
if (membersState.members.contains(memberPubkey)) return null;
|
||||
didRedirect.value = true;
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||
if (!context.mounted) return;
|
||||
Routes.goBack(context);
|
||||
if (!context.mounted) return;
|
||||
StartChatScreen.show(context, userPubkey: memberPubkey);
|
||||
});
|
||||
return null;
|
||||
}, [membersState.isLoading, membersState.members]);
|
||||
|
||||
final isCurrentUserAdmin = membersState.admins.contains(accountPubkey);
|
||||
final isMemberAdmin = membersState.admins.contains(memberPubkey);
|
||||
final displayName = presentName(metadata) ?? memberPubkey.substring(0, 8);
|
||||
|
||||
@@ -191,8 +191,8 @@ class StartChatScreen extends HookConsumerWidget {
|
||||
child: SafeArea(
|
||||
child: Column(
|
||||
children: [
|
||||
const Spacer(),
|
||||
WnSlate(
|
||||
shrinkWrapContent: true,
|
||||
header: WnSlateNavigationHeader(
|
||||
title: context.l10n.startNewChat,
|
||||
onNavigate: () => Routes.goBack(context),
|
||||
|
||||
@@ -3,6 +3,7 @@ import 'package:go_router/go_router.dart';
|
||||
import 'package:url_launcher/url_launcher.dart';
|
||||
import 'package:whitenoise/hooks/use_chat_messages.dart' show ChatMessageQuoteData;
|
||||
import 'package:whitenoise/l10n/l10n.dart';
|
||||
import 'package:whitenoise/routes.dart';
|
||||
import 'package:whitenoise/screens/start_chat_screen.dart';
|
||||
import 'package:whitenoise/src/rust/api/markdown.dart';
|
||||
import 'package:whitenoise/src/rust/api/messages.dart';
|
||||
@@ -38,6 +39,7 @@ class ChatMessageBubble extends StatelessWidget {
|
||||
final VoidCallback? onHorizontalDragEnd;
|
||||
final VoidCallback? onRetry;
|
||||
final String? Function(String hexPubkey)? mentionDisplayName;
|
||||
final String? groupId;
|
||||
|
||||
const ChatMessageBubble({
|
||||
super.key,
|
||||
@@ -60,6 +62,7 @@ class ChatMessageBubble extends StatelessWidget {
|
||||
this.onHorizontalDragEnd,
|
||||
this.onRetry,
|
||||
this.mentionDisplayName,
|
||||
this.groupId,
|
||||
});
|
||||
|
||||
ChatStatusType? get _deliveryStatusType {
|
||||
@@ -162,7 +165,11 @@ class ChatMessageBubble extends StatelessWidget {
|
||||
onNostrTap: (hrp, bech32) async {
|
||||
if (hrp == MarkdownNostrHrp.npub) {
|
||||
final hex = hexFromNpub(bech32);
|
||||
if (hex != null && context.mounted) {
|
||||
if (hex == null || !context.mounted) return;
|
||||
final gid = groupId;
|
||||
if (gid != null) {
|
||||
Routes.pushToGroupMember(context, gid, hex);
|
||||
} else {
|
||||
await StartChatScreen.show(context, userPubkey: hex);
|
||||
}
|
||||
return;
|
||||
|
||||
@@ -5,6 +5,8 @@ import 'package:flutter_riverpod/flutter_riverpod.dart' show AsyncData;
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:whitenoise/providers/auth_provider.dart';
|
||||
import 'package:whitenoise/routes.dart';
|
||||
import 'package:whitenoise/screens/group_member_screen.dart';
|
||||
import 'package:whitenoise/screens/start_chat_screen.dart';
|
||||
import 'package:whitenoise/src/rust/api/groups.dart';
|
||||
import 'package:whitenoise/src/rust/api/metadata.dart';
|
||||
import 'package:whitenoise/src/rust/frb_generated.dart';
|
||||
@@ -726,5 +728,27 @@ void main() {
|
||||
expect(find.text('Add to group'), findsWidgets);
|
||||
});
|
||||
});
|
||||
|
||||
group('non-member redirect', () {
|
||||
testWidgets('redirects to StartChatScreen when the user is not in the group', (
|
||||
tester,
|
||||
) async {
|
||||
_api.adminsList = [_testPubkey];
|
||||
await pumpGroupMemberScreen(tester, memberPubkey: testPubkeyD);
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
expect(find.byType(GroupMemberScreen), findsNothing);
|
||||
expect(find.byType(StartChatScreen), findsOneWidget);
|
||||
});
|
||||
|
||||
testWidgets('does not redirect when the user is a member', (tester) async {
|
||||
_api.adminsList = [_testPubkey];
|
||||
await pumpGroupMemberScreen(tester, memberPubkey: _memberPubkey);
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
expect(find.byType(GroupMemberScreen), findsOneWidget);
|
||||
expect(find.byType(StartChatScreen), findsNothing);
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
@@ -919,6 +919,42 @@ void main() {
|
||||
);
|
||||
});
|
||||
|
||||
testWidgets('npub mention in a group navigates to the group member screen', (tester) async {
|
||||
await _mountBubbleWithRouter(
|
||||
tester,
|
||||
ChatMessageBubble(
|
||||
message: withDoc(
|
||||
const MarkdownDocument(
|
||||
blocks: [
|
||||
MarkdownBlock.paragraph(
|
||||
inlines: [
|
||||
MarkdownInline.nostrMention(
|
||||
entity: MarkdownNostrEntity(
|
||||
hrp: MarkdownNostrHrp.npub,
|
||||
bech32: testNpubA,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
isOwnMessage: false,
|
||||
groupId: testGroupId,
|
||||
),
|
||||
);
|
||||
final span = tester.widget<RichText>(find.byType(RichText).first).text as TextSpan;
|
||||
final entity = _firstTextSpanWithRecognizer(span);
|
||||
(entity.recognizer as TapGestureRecognizer).onTap!();
|
||||
await tester.pumpAndSettle();
|
||||
expect(launcher.calls, isEmpty);
|
||||
expect(find.byType(StartChatScreen), findsNothing);
|
||||
expect(
|
||||
find.byKey(const ValueKey('group_member_route_${testGroupId}_$testPubkeyA')),
|
||||
findsOneWidget,
|
||||
);
|
||||
});
|
||||
|
||||
testWidgets('whitenoise://chat/<id> tap navigates via GoRouter', (tester) async {
|
||||
await _mountBubbleWithRouter(
|
||||
tester,
|
||||
@@ -1075,6 +1111,16 @@ Future<void> _mountBubbleWithRouter(WidgetTester tester, Widget bubble) async {
|
||||
body: const SizedBox(),
|
||||
),
|
||||
),
|
||||
GoRoute(
|
||||
name: 'groupMember',
|
||||
path: '/group-member/:groupId/:memberPubkey',
|
||||
builder: (_, state) => Scaffold(
|
||||
key: ValueKey(
|
||||
'group_member_route_${state.pathParameters['groupId']}_${state.pathParameters['memberPubkey']}',
|
||||
),
|
||||
body: const SizedBox(),
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
await tester.pumpWidget(
|
||||
|
||||
Reference in New Issue
Block a user