[BOT-1264] fix(mobile): #channel tags not tappable (#1695)

Signed-off-by: npub1shglkdhngx3hrnhf4gf8vhpqdrmeludctechdvpwd3988zzs7ncq2cmtxu <85d1fb36f341a371cee9aa12765c2068f79ff1b85e7176b02e6c4a738850f4f0@sprout-oss.stage.blox.sqprod.co>
Co-authored-by: npub1shglkdhngx3hrnhf4gf8vhpqdrmeludctechdvpwd3988zzs7ncq2cmtxu <85d1fb36f341a371cee9aa12765c2068f79ff1b85e7176b02e6c4a738850f4f0@sprout-oss.stage.blox.sqprod.co>
This commit is contained in:
Tom Brow
2026-07-12 08:11:06 -07:00
committed by GitHub
parent 56d822dc2c
commit 2687a2a185
5 changed files with 154 additions and 30 deletions

View File

@@ -15,6 +15,7 @@ import '../profile/user_cache_provider.dart';
import '../profile/user_profile.dart';
import '../forum/forum_posts_view.dart';
import 'channel.dart';
import 'channel_link_navigation.dart';
import 'agent_activity/working_bots_provider.dart';
import 'channel_management_provider.dart';
import 'channel_messages_provider.dart';

View File

@@ -115,24 +115,11 @@ class _MessageBubble extends ConsumerWidget {
channelNames: channelNames,
tags: message.tags,
onChannelTap: (channelId) {
if (channelId == currentChannelId) return;
final channelsAsync = ref.read(channelsProvider);
final channels = channelsAsync.hasValue
? channelsAsync.value
: null;
Channel? targetChannel;
for (final channel in channels ?? const <Channel>[]) {
if (channel.id == channelId) {
targetChannel = channel;
break;
}
}
if (targetChannel == null) return;
Navigator.of(context).push(
MaterialPageRoute<void>(
builder: (_) =>
ChannelDetailPage(channel: targetChannel!),
),
openChannelLink(
context: context,
ref: ref,
channelId: channelId,
currentChannelId: currentChannelId,
);
},
),

View File

@@ -0,0 +1,38 @@
import 'package:flutter/material.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'channel.dart';
import 'channel_detail_page.dart';
import 'channels_provider.dart';
void openChannelLink({
required BuildContext context,
required WidgetRef ref,
required String channelId,
required String currentChannelId,
}) {
if (channelId == currentChannelId) return;
final channelsAsync = ref.read(channelsProvider);
final channels = channelsAsync.hasValue ? channelsAsync.value : null;
Channel? targetChannel;
for (final channel in channels ?? const <Channel>[]) {
if (channel.id == channelId) {
targetChannel = channel;
break;
}
}
if (targetChannel == null) {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(content: Text('Channel could not be opened')),
);
return;
}
Navigator.of(context).push(
MaterialPageRoute<void>(
builder: (_) => ChannelDetailPage(channel: targetChannel!),
),
);
}

View File

@@ -8,6 +8,7 @@ import '../../shared/widgets/frosted_app_bar.dart';
import '../../shared/widgets/frosted_scaffold.dart';
import '../profile/user_cache_provider.dart';
import '../profile/user_profile.dart';
import 'channel_link_navigation.dart';
import 'channel_typing_provider.dart';
import 'thread_replies_provider.dart';
import 'channels_provider.dart';
@@ -468,7 +469,14 @@ class _ThreadMessage extends ConsumerWidget {
mentionNames: mentionNames,
channelNames: channelNames,
tags: message.tags,
onChannelTap: (_) {},
onChannelTap: (targetChannelId) {
openChannelLink(
context: context,
ref: ref,
channelId: targetChannelId,
currentChannelId: channelId,
);
},
),
if (message.reactions.isNotEmpty)
ReactionRow(

View File

@@ -10,6 +10,8 @@ import 'package:buzz/features/channels/channel_detail_page.dart';
import 'package:buzz/features/channels/channel_management_provider.dart';
import 'package:buzz/features/channels/channel_messages_provider.dart';
import 'package:buzz/features/channels/channel_typing_provider.dart';
import 'package:buzz/features/channels/thread_detail_page.dart';
import 'package:buzz/features/channels/timeline_message.dart';
import 'package:buzz/features/channels/channels_provider.dart';
import 'package:buzz/features/channels/read_state/read_state_provider.dart';
import 'package:buzz/features/profile/profile_provider.dart';
@@ -1099,17 +1101,7 @@ void main() {
group('Channel links', () {
testWidgets('tapping a channel link opens that channel', (tester) async {
final randomChannel = Channel(
id: 'random-channel',
name: 'random',
channelType: 'stream',
visibility: 'open',
description: 'Random discussion',
createdBy: 'abc123',
createdAt: DateTime(2025),
memberCount: 3,
isMember: true,
);
final randomChannel = _channel(id: 'random-channel', name: 'random');
final observer = _TestNavigatorObserver();
await tester.pumpWidget(
@@ -1137,9 +1129,107 @@ void main() {
expect(observer.pushCount, initialPushCount + 1);
});
testWidgets('missing channel link shows an error', (tester) async {
final randomChannel = _channel(id: 'random-channel', name: 'random');
final channelsNotifier = _FakeChannelsNotifier([
_testChannel,
randomChannel,
]);
await tester.pumpWidget(
_buildTestable(
messages: [
_textMsg(
id: 'msg1',
pubkey: 'alice',
content: 'Take this to #random',
createdAt: 1000,
),
],
users: {
'alice': const UserProfile(pubkey: 'alice', displayName: 'Alice'),
},
channelsNotifier: channelsNotifier,
),
);
await tester.pumpAndSettle();
channelsNotifier.setChannels([_testChannel]);
await tester.tap(find.text('#random'));
await tester.pump();
expect(find.text('Channel could not be opened'), findsOneWidget);
});
testWidgets('tapping a channel link inside a thread opens that channel', (
tester,
) async {
final randomChannel = _channel(id: 'random-channel', name: 'random');
final observer = _TestNavigatorObserver();
await tester.pumpWidget(
_buildTestable(
messages: [
_textMsg(
id: 'msg1',
pubkey: 'alice',
content: 'Thread root #random',
createdAt: 1000,
),
],
users: {
'alice': const UserProfile(pubkey: 'alice', displayName: 'Alice'),
},
channels: [_testChannel, randomChannel],
navigatorObservers: [observer],
),
);
await tester.pumpAndSettle();
final threadMessages = formatTimeline([
_textMsg(
id: 'msg1',
pubkey: 'alice',
content: 'Thread root #random',
createdAt: 1000,
),
]);
Navigator.of(tester.element(find.byType(ChannelDetailPage))).push(
MaterialPageRoute<void>(
builder: (_) => ThreadDetailPage(
threadHead: threadMessages.single,
allMessages: threadMessages,
channelId: _channelId,
currentPubkey: 'self',
isMember: true,
isArchived: false,
),
),
);
await tester.pumpAndSettle();
final initialPushCount = observer.pushCount;
await tester.tap(find.text('#random').last);
await tester.pumpAndSettle();
expect(observer.pushCount, initialPushCount + 1);
});
});
}
Channel _channel({required String id, required String name}) => Channel(
id: id,
name: name,
channelType: 'stream',
visibility: 'open',
description: '$name discussion',
createdBy: 'abc123',
createdAt: DateTime(2025),
memberCount: 3,
isMember: true,
);
class _FakeMessagesNotifier extends ChannelMessagesNotifier {
List<NostrEvent> _messages;
_FakeMessagesNotifier(this._messages) : super(_channelId);