mirror of
https://github.com/block/buzz.git
synced 2026-10-29 05:21:31 +00:00
fix(mobile): open Search and profile DMs as ordinary routes
Search and the profile sheet pushed the opened DM with the default lookup placement, so while Solo groups loaded the route showed only a spinner for up to 2s. Pass the ordinary placement, as New message already does. Known Solo placement still takes over once groups load. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Signed-off-by: Tom Brow <tomb@block.xyz>
This commit is contained in:
@@ -49,7 +49,10 @@ void showUserProfileSheet(
|
|||||||
if (channel == null || !context.mounted) return;
|
if (channel == null || !context.mounted) return;
|
||||||
Navigator.of(context).push(
|
Navigator.of(context).push(
|
||||||
MaterialPageRoute<void>(
|
MaterialPageRoute<void>(
|
||||||
builder: (_) => ChannelDetailPage(channel: channel),
|
builder: (_) => ChannelDetailPage(
|
||||||
|
channel: channel,
|
||||||
|
placement: ChannelRoutePlacement.ordinary,
|
||||||
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -587,7 +587,10 @@ class _PeopleSection extends ConsumerWidget {
|
|||||||
if (!context.mounted) return;
|
if (!context.mounted) return;
|
||||||
await Navigator.of(context).push(
|
await Navigator.of(context).push(
|
||||||
MaterialPageRoute<void>(
|
MaterialPageRoute<void>(
|
||||||
builder: (_) => ChannelDetailPage(channel: channel),
|
builder: (_) => ChannelDetailPage(
|
||||||
|
channel: channel,
|
||||||
|
placement: ChannelRoutePlacement.ordinary,
|
||||||
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -0,0 +1,111 @@
|
|||||||
|
import 'package:buzz/features/channels/channel.dart';
|
||||||
|
import 'package:buzz/features/channels/channel_detail_page.dart';
|
||||||
|
import 'package:buzz/features/channels/channel_management_provider.dart';
|
||||||
|
import 'package:buzz/features/channels/compose_bar.dart';
|
||||||
|
import 'package:buzz/features/profile/user_profile_sheet.dart';
|
||||||
|
import 'package:buzz/shared/me/me_sync.dart';
|
||||||
|
import 'package:buzz/shared/profile/user_cache_provider.dart';
|
||||||
|
import 'package:buzz/shared/profile/user_profile.dart';
|
||||||
|
import 'package:buzz/shared/theme/theme.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_test/flutter_test.dart';
|
||||||
|
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||||
|
import 'package:shared_preferences/shared_preferences.dart';
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
final peer = 'a' * 64;
|
||||||
|
|
||||||
|
testWidgets('Message opens the DM without waiting on Solo', (tester) async {
|
||||||
|
final dm = Channel(
|
||||||
|
id: 'dm-peer',
|
||||||
|
name: 'Peer',
|
||||||
|
channelType: 'dm',
|
||||||
|
visibility: 'private',
|
||||||
|
description: '',
|
||||||
|
memberCount: 2,
|
||||||
|
createdBy: 'self',
|
||||||
|
createdAt: DateTime(2025),
|
||||||
|
isMember: true,
|
||||||
|
);
|
||||||
|
tester.view.physicalSize = const Size(390, 844);
|
||||||
|
tester.view.devicePixelRatio = 1;
|
||||||
|
addTearDown(tester.view.reset);
|
||||||
|
final opened = <List<String>>[];
|
||||||
|
SharedPreferences.setMockInitialValues({});
|
||||||
|
final prefs = await SharedPreferences.getInstance();
|
||||||
|
await tester.pumpWidget(
|
||||||
|
ProviderScope(
|
||||||
|
overrides: [
|
||||||
|
userCacheProvider.overrideWith(
|
||||||
|
() => _UserCache({
|
||||||
|
peer: UserProfile(pubkey: peer, displayName: 'Peer'),
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
channelActionsProvider.overrideWith(
|
||||||
|
(ref) => _FakeDmActions(dm, opened),
|
||||||
|
),
|
||||||
|
savedPrefsProvider.overrideWithValue(prefs),
|
||||||
|
// Solo organization is still loading.
|
||||||
|
meGroupsProvider.overrideWith((ref) => const Stream.empty()),
|
||||||
|
],
|
||||||
|
child: MaterialApp(
|
||||||
|
theme: AppTheme.light(),
|
||||||
|
home: Builder(
|
||||||
|
builder: (context) => Scaffold(
|
||||||
|
body: TextButton(
|
||||||
|
onPressed: () => showUserProfileSheet(context, peer),
|
||||||
|
child: const Text('Open profile'),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
await tester.tap(find.text('Open profile'));
|
||||||
|
await tester.pumpAndSettle();
|
||||||
|
await tester.tap(find.text('Message'));
|
||||||
|
await tester.pump();
|
||||||
|
await tester.pump(const Duration(seconds: 1));
|
||||||
|
|
||||||
|
expect(opened, [
|
||||||
|
[peer],
|
||||||
|
]);
|
||||||
|
expect(
|
||||||
|
tester.widget<ChannelDetailPage>(find.byType(ChannelDetailPage)).channel,
|
||||||
|
dm,
|
||||||
|
);
|
||||||
|
// The DM is usable at once, not a placement spinner.
|
||||||
|
expect(find.byType(ComposeBar), findsOneWidget);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
class _UserCache extends UserCacheNotifier {
|
||||||
|
_UserCache(this._initial);
|
||||||
|
|
||||||
|
final Map<String, UserProfile> _initial;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Map<String, UserProfile> build() => _initial;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<bool> preload(List<String> pubkeys) async => true;
|
||||||
|
}
|
||||||
|
|
||||||
|
class _FakeDmActions implements ChannelActions {
|
||||||
|
_FakeDmActions(this.dm, this.opened);
|
||||||
|
|
||||||
|
final Channel dm;
|
||||||
|
final List<List<String>> opened;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<Channel> openDm({
|
||||||
|
required List<String> pubkeys,
|
||||||
|
bool waitForChannelList = true,
|
||||||
|
}) async {
|
||||||
|
opened.add(pubkeys);
|
||||||
|
return dm;
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
dynamic noSuchMethod(Invocation invocation) => super.noSuchMethod(invocation);
|
||||||
|
}
|
||||||
@@ -1,6 +1,8 @@
|
|||||||
import 'package:buzz/shared/widgets/buzz_agent_icon.dart';
|
import 'package:buzz/shared/widgets/buzz_agent_icon.dart';
|
||||||
import 'package:buzz/features/channels/channel.dart';
|
import 'package:buzz/features/channels/channel.dart';
|
||||||
|
import 'package:buzz/features/channels/channel_detail_page.dart';
|
||||||
import 'package:buzz/features/channels/channel_management_provider.dart';
|
import 'package:buzz/features/channels/channel_management_provider.dart';
|
||||||
|
import 'package:buzz/features/channels/compose_bar.dart';
|
||||||
import 'package:buzz/features/channels/channels_provider.dart';
|
import 'package:buzz/features/channels/channels_provider.dart';
|
||||||
import 'package:buzz/features/channels/message_content.dart';
|
import 'package:buzz/features/channels/message_content.dart';
|
||||||
import 'package:buzz/features/channels/small_avatar.dart';
|
import 'package:buzz/features/channels/small_avatar.dart';
|
||||||
@@ -12,12 +14,14 @@ import 'package:buzz/features/search/search_page.dart';
|
|||||||
import 'package:buzz/features/search/search_provider.dart';
|
import 'package:buzz/features/search/search_provider.dart';
|
||||||
import 'package:buzz/shared/theme/theme.dart';
|
import 'package:buzz/shared/theme/theme.dart';
|
||||||
import 'package:buzz/shared/mentions/agent_identity_provider.dart';
|
import 'package:buzz/shared/mentions/agent_identity_provider.dart';
|
||||||
|
import 'package:buzz/shared/me/me_sync.dart';
|
||||||
import 'package:buzz/shared/widgets/concentric_sheet_surface.dart';
|
import 'package:buzz/shared/widgets/concentric_sheet_surface.dart';
|
||||||
import 'package:buzz/shared/widgets/ios_glass_navigation_button.dart';
|
import 'package:buzz/shared/widgets/ios_glass_navigation_button.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter/services.dart';
|
import 'package:flutter/services.dart';
|
||||||
import 'package:flutter/foundation.dart';
|
import 'package:flutter/foundation.dart';
|
||||||
import 'package:flutter_test/flutter_test.dart';
|
import 'package:flutter_test/flutter_test.dart';
|
||||||
|
import 'package:shared_preferences/shared_preferences.dart';
|
||||||
|
|
||||||
import '../../helpers/widget_helpers.dart';
|
import '../../helpers/widget_helpers.dart';
|
||||||
|
|
||||||
@@ -112,6 +116,67 @@ void main() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
testWidgets('a person result opens their DM without waiting on Solo', (
|
||||||
|
tester,
|
||||||
|
) async {
|
||||||
|
final dm = Channel(
|
||||||
|
id: 'dm-maya',
|
||||||
|
name: 'Maya',
|
||||||
|
channelType: 'dm',
|
||||||
|
visibility: 'private',
|
||||||
|
description: '',
|
||||||
|
memberCount: 2,
|
||||||
|
createdBy: 'test',
|
||||||
|
createdAt: DateTime(2025),
|
||||||
|
isMember: true,
|
||||||
|
);
|
||||||
|
final opened = <List<String>>[];
|
||||||
|
SharedPreferences.setMockInitialValues({});
|
||||||
|
final prefs = await SharedPreferences.getInstance();
|
||||||
|
await tester.pumpWidget(
|
||||||
|
WidgetHelpers.testable(
|
||||||
|
overrides: [
|
||||||
|
searchProvider.overrideWith(
|
||||||
|
() => _FakeSearchNotifier(
|
||||||
|
const SearchState(
|
||||||
|
query: 'maya',
|
||||||
|
userResults: [
|
||||||
|
DirectoryUser(pubkey: 'maya', displayName: 'Maya'),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
recentSearchesProvider.overrideWith(
|
||||||
|
() => _FakeRecentSearchesNotifier(const []),
|
||||||
|
),
|
||||||
|
profileProvider.overrideWith(() => _FakeProfileNotifier()),
|
||||||
|
channelsProvider.overrideWith(() => _FakeChannelsNotifier()),
|
||||||
|
channelActionsProvider.overrideWith(
|
||||||
|
(ref) => _FakeDmActions(dm, opened),
|
||||||
|
),
|
||||||
|
savedPrefsProvider.overrideWithValue(prefs),
|
||||||
|
// Solo organization is still loading.
|
||||||
|
meGroupsProvider.overrideWith((ref) => const Stream.empty()),
|
||||||
|
],
|
||||||
|
child: const SearchPage(),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
await tester.pumpAndSettle();
|
||||||
|
await tester.tap(find.byKey(const ValueKey('search-person-row-maya')));
|
||||||
|
await tester.pump();
|
||||||
|
await tester.pump();
|
||||||
|
|
||||||
|
expect(opened, [
|
||||||
|
['maya'],
|
||||||
|
]);
|
||||||
|
final page = tester.widget<ChannelDetailPage>(
|
||||||
|
find.byType(ChannelDetailPage),
|
||||||
|
);
|
||||||
|
expect(page.channel.id, dm.id);
|
||||||
|
// The DM is usable at once, not a placement spinner.
|
||||||
|
expect(find.byType(ComposeBar), findsOneWidget);
|
||||||
|
});
|
||||||
|
|
||||||
testWidgets('iOS native filter press selects Channels and filters results', (
|
testWidgets('iOS native filter press selects Channels and filters results', (
|
||||||
tester,
|
tester,
|
||||||
) async {
|
) async {
|
||||||
@@ -933,6 +998,25 @@ class _FakeChannelsNotifier extends ChannelsNotifier {
|
|||||||
Future<List<Channel>> build() async => channels;
|
Future<List<Channel>> build() async => channels;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class _FakeDmActions implements ChannelActions {
|
||||||
|
_FakeDmActions(this.dm, this.opened);
|
||||||
|
|
||||||
|
final Channel dm;
|
||||||
|
final List<List<String>> opened;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<Channel> openDm({
|
||||||
|
required List<String> pubkeys,
|
||||||
|
bool waitForChannelList = true,
|
||||||
|
}) async {
|
||||||
|
opened.add(pubkeys);
|
||||||
|
return dm;
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
dynamic noSuchMethod(Invocation invocation) => super.noSuchMethod(invocation);
|
||||||
|
}
|
||||||
|
|
||||||
class _FakeUserCacheNotifier extends UserCacheNotifier {
|
class _FakeUserCacheNotifier extends UserCacheNotifier {
|
||||||
_FakeUserCacheNotifier(this.profile);
|
_FakeUserCacheNotifier(this.profile);
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user