Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions mobile/lib/features/channels/channel_detail_page.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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,
);
},
),
Expand Down
38 changes: 38 additions & 0 deletions mobile/lib/features/channels/channel_link_navigation.dart
Original file line number Diff line number Diff line change
@@ -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({

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Document or privatize the public helper

For this repo, AGENTS.md requires new public APIs to have doc comments. openChannelLink is a new package-public Dart function because it is not underscored, so leaving it undocumented violates that rule; add a doc comment describing the navigation/error behavior or make it private if it should not be exposed.

Useful? React with 👍 / 👎.

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!),
),
);
}
10 changes: 9 additions & 1 deletion mobile/lib/features/channels/thread_detail_page.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -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(
Expand Down
112 changes: 101 additions & 11 deletions mobile/test/features/channels/channel_detail_page_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -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(
Expand Down Expand Up @@ -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);
Expand Down
Loading