mirror of
https://github.com/block/buzz.git
synced 2026-08-18 06:50:31 +02:00
fix tablet workspace review findings
Signed-off-by: kenny lopez <klopez4212@gmail.com>
This commit is contained in:
@@ -60,6 +60,10 @@ class ActivityPage extends HookConsumerWidget {
|
||||
final filter = useState(InboxFilter.all);
|
||||
final unreadOnly = useState(false);
|
||||
final selectedItemId = useState<String?>(null);
|
||||
// Retain the event selected before its row is marked read. A grouped row
|
||||
// otherwise recomputes its deep link after the read marker changes and
|
||||
// opens the latest event instead of the oldest unread one the user chose.
|
||||
final selectedItemTarget = useState<FeedItem?>(null);
|
||||
final isWideInbox =
|
||||
MediaQuery.sizeOf(context).width >= _wideInboxBreakpoint;
|
||||
final headerTitleStyle = context.textTheme.titleMedium?.copyWith(
|
||||
@@ -103,7 +107,10 @@ class ActivityPage extends HookConsumerWidget {
|
||||
final hasSelectedItem = visibleItems.any(
|
||||
(item) => item.id == selectedItemId.value,
|
||||
);
|
||||
if (!hasSelectedItem) selectedItemId.value = visibleItems.first.id;
|
||||
if (!hasSelectedItem) {
|
||||
selectedItemId.value = visibleItems.first.id;
|
||||
selectedItemTarget.value = null;
|
||||
}
|
||||
return null;
|
||||
}, [isWideInbox, visibleItemIdsKey]);
|
||||
final selectedItem = visibleItems.cast<InboxItem?>().firstWhere(
|
||||
@@ -189,6 +196,7 @@ class ActivityPage extends HookConsumerWidget {
|
||||
|
||||
if (isWideInbox) {
|
||||
selectedItemId.value = item.id;
|
||||
selectedItemTarget.value = target;
|
||||
markItemRead(item);
|
||||
return;
|
||||
}
|
||||
@@ -335,6 +343,7 @@ class ActivityPage extends HookConsumerWidget {
|
||||
draftCount: drafts.length,
|
||||
onChanged: (nextFilter) {
|
||||
selectedItemId.value = null;
|
||||
selectedItemTarget.value = null;
|
||||
filter.value = nextFilter;
|
||||
},
|
||||
),
|
||||
@@ -369,7 +378,8 @@ class ActivityPage extends HookConsumerWidget {
|
||||
);
|
||||
}
|
||||
|
||||
final selectedTarget = detailTarget(selectedItem);
|
||||
final selectedTarget =
|
||||
selectedItemTarget.value ?? detailTarget(selectedItem);
|
||||
final selectedChannelId = selectedTarget?.channelId;
|
||||
final selectedChannel = selectedChannelId == null
|
||||
? null
|
||||
|
||||
@@ -17,7 +17,6 @@ import '../../shared/widgets/skeleton.dart';
|
||||
import '../activity/activity_page.dart';
|
||||
import '../channels/channel.dart';
|
||||
import '../channels/channel_detail_page.dart';
|
||||
import '../channels/channel_management_provider.dart';
|
||||
import '../channels/channels_page.dart';
|
||||
import '../channels/channels_provider.dart';
|
||||
import '../channels/dm_channel_labels.dart';
|
||||
@@ -90,7 +89,20 @@ class HomePage extends HookConsumerWidget {
|
||||
final tabIndex = useState(isWide ? 1 : 0);
|
||||
final selectedChannel = useState<Channel?>(null);
|
||||
final pendingCommunityId = useState<String?>(null);
|
||||
final activeCommunityId = ref.watch(activeCommunityProvider).value?.id;
|
||||
// Clear the tablet selection as soon as the active-community state begins
|
||||
// changing. The channel provider intentionally retains its last result
|
||||
// while the next relay connects, so retaining the selection here could
|
||||
// briefly render (and query) an old-community channel in the new workspace.
|
||||
final activeCommunityId = ref.watch(
|
||||
activeCommunityProvider.select(
|
||||
(value) => value.unwrapPrevious().value?.id,
|
||||
),
|
||||
);
|
||||
final selectedChannelCommunityId = useRef<String?>(activeCommunityId);
|
||||
if (selectedChannelCommunityId.value != activeCommunityId) {
|
||||
selectedChannel.value = null;
|
||||
selectedChannelCommunityId.value = activeCommunityId;
|
||||
}
|
||||
final channelsAsync = ref.watch(channelsProvider);
|
||||
final hasActivatedPendingCommunity =
|
||||
pendingCommunityId.value != null &&
|
||||
|
||||
@@ -26,7 +26,10 @@ class _WideNavigationSidebar extends HookConsumerWidget {
|
||||
final colors = context.colors;
|
||||
final sidebarGradient = context.appColors.topSectionGradient;
|
||||
final sidebarPalette = _wideSidebarPalette(context);
|
||||
final currentPubkey = ref.watch(currentPubkeyProvider);
|
||||
// The workspace sidebar only needs the signed-in profile identity to
|
||||
// label direct-message participants. Keep that navigation concern out of
|
||||
// the channel-management implementation.
|
||||
final currentPubkey = ref.watch(profileProvider).value?.pubkey;
|
||||
final profiles = ref.watch(userCacheProvider);
|
||||
final channelsAsync = ref.watch(channelsProvider);
|
||||
final channels = (channelsAsync.asData?.value ?? const <Channel>[])
|
||||
|
||||
@@ -209,6 +209,67 @@ void main() {
|
||||
);
|
||||
});
|
||||
|
||||
testWidgets(
|
||||
'keeps the oldest unread deep link after a wide inbox row is read',
|
||||
(tester) async {
|
||||
tester.view.physicalSize = const Size(1180, 820);
|
||||
tester.view.devicePixelRatio = 1;
|
||||
addTearDown(() {
|
||||
tester.view.resetPhysicalSize();
|
||||
tester.view.resetDevicePixelRatio();
|
||||
});
|
||||
final oldestUnread = FeedItem(
|
||||
id: 'oldest-unread',
|
||||
kind: 9,
|
||||
pubkey: 'alice_pk',
|
||||
content: 'First unread reply',
|
||||
createdAt: now - 120,
|
||||
channelId: 'ch1',
|
||||
channelName: 'general',
|
||||
tags: const [
|
||||
['e', 'thread-root', '', 'root'],
|
||||
['e', 'oldest-unread', '', 'reply'],
|
||||
],
|
||||
category: 'activity',
|
||||
);
|
||||
final latestUnread = FeedItem(
|
||||
id: 'latest-unread',
|
||||
kind: 9,
|
||||
pubkey: 'alice_pk',
|
||||
content: 'Latest unread reply',
|
||||
createdAt: now - 60,
|
||||
channelId: 'ch1',
|
||||
channelName: 'general',
|
||||
tags: const [
|
||||
['e', 'thread-root', '', 'root'],
|
||||
['e', 'latest-unread', '', 'reply'],
|
||||
],
|
||||
category: 'activity',
|
||||
);
|
||||
final feed = HomeFeedResponse(
|
||||
mentions: const [],
|
||||
needsAction: const [],
|
||||
activity: [oldestUnread, latestUnread],
|
||||
agentActivity: const [],
|
||||
);
|
||||
|
||||
await tester.pumpWidget(
|
||||
await buildTestable(
|
||||
feed: feed,
|
||||
readContexts: {'thread:thread-root': now - 180},
|
||||
),
|
||||
);
|
||||
await tester.pumpAndSettle();
|
||||
await tester.tap(find.byKey(const ValueKey('inbox-row-latest-unread')));
|
||||
await tester.pump();
|
||||
|
||||
final detail = tester.widget<ChannelDetailPage>(
|
||||
find.byType(ChannelDetailPage),
|
||||
);
|
||||
expect(detail.initialMessageId, 'oldest-unread');
|
||||
},
|
||||
);
|
||||
|
||||
testWidgets('keeps bottom clearance for the floating tab bar', (
|
||||
tester,
|
||||
) async {
|
||||
|
||||
@@ -25,6 +25,7 @@ void main() {
|
||||
CommunityListNotifier? communityListNotifier,
|
||||
Community? activeCommunity,
|
||||
List<Channel>? channels,
|
||||
bool usesMutableActiveCommunity = false,
|
||||
}) async {
|
||||
SharedPreferences.setMockInitialValues({});
|
||||
final prefs = await SharedPreferences.getInstance();
|
||||
@@ -37,7 +38,11 @@ void main() {
|
||||
),
|
||||
if (communityListNotifier != null)
|
||||
communityListProvider.overrideWith(() => communityListNotifier),
|
||||
if (activeCommunity != null)
|
||||
if (usesMutableActiveCommunity)
|
||||
activeCommunityProvider.overrideWith(
|
||||
(ref) async => ref.watch(_mutableActiveCommunityProvider),
|
||||
)
|
||||
else if (activeCommunity != null)
|
||||
activeCommunityProvider.overrideWith((ref) async => activeCommunity),
|
||||
if (channels != null)
|
||||
channelsProvider.overrideWith(() => _FakeChannelsNotifier(channels)),
|
||||
@@ -303,6 +308,85 @@ void main() {
|
||||
expect(find.byKey(const Key('community-switcher-sheet')), findsOneWidget);
|
||||
});
|
||||
|
||||
testWidgets('clears a selected tablet channel when the community changes', (
|
||||
tester,
|
||||
) async {
|
||||
tester.view.physicalSize = const Size(1180, 820);
|
||||
tester.view.devicePixelRatio = 1;
|
||||
addTearDown(() {
|
||||
tester.view.resetPhysicalSize();
|
||||
tester.view.resetDevicePixelRatio();
|
||||
});
|
||||
|
||||
await tester.pumpWidget(
|
||||
await buildHome(
|
||||
usesMutableActiveCommunity: true,
|
||||
channels: [
|
||||
Channel(
|
||||
id: 'alpha-channel',
|
||||
name: 'alpha',
|
||||
channelType: 'stream',
|
||||
visibility: 'open',
|
||||
description: '',
|
||||
createdBy: 'me',
|
||||
createdAt: DateTime(2026),
|
||||
memberCount: 1,
|
||||
isMember: true,
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
final container = ProviderScope.containerOf(
|
||||
tester.element(find.byType(HomePage)),
|
||||
);
|
||||
container
|
||||
.read(_mutableActiveCommunityProvider.notifier)
|
||||
.select(
|
||||
Community(
|
||||
id: 'alpha',
|
||||
name: 'Alpha',
|
||||
relayUrl: 'wss://alpha.example.com',
|
||||
addedAt: DateTime(2026),
|
||||
),
|
||||
);
|
||||
await tester.pumpAndSettle();
|
||||
await tester.tap(find.byKey(const ValueKey('wide-channel-alpha-channel')));
|
||||
await tester.pump();
|
||||
|
||||
expect(
|
||||
tester
|
||||
.getSemantics(
|
||||
find.byKey(const ValueKey('wide-channel-alpha-channel')),
|
||||
)
|
||||
.flagsCollection
|
||||
.isSelected,
|
||||
Tristate.isTrue,
|
||||
);
|
||||
|
||||
container
|
||||
.read(_mutableActiveCommunityProvider.notifier)
|
||||
.select(
|
||||
Community(
|
||||
id: 'bravo',
|
||||
name: 'Bravo',
|
||||
relayUrl: 'wss://bravo.example.com',
|
||||
addedAt: DateTime(2026),
|
||||
),
|
||||
);
|
||||
await tester.pump();
|
||||
|
||||
expect(
|
||||
tester
|
||||
.getSemantics(
|
||||
find.byKey(const ValueKey('wide-channel-alpha-channel')),
|
||||
)
|
||||
.flagsCollection
|
||||
.isSelected,
|
||||
Tristate.isFalse,
|
||||
);
|
||||
expect(find.byKey(const Key('wide-navigation-inbox')), findsOneWidget);
|
||||
});
|
||||
|
||||
testWidgets('extends the Buzz gradient through the iPad sidebar', (
|
||||
tester,
|
||||
) async {
|
||||
@@ -655,6 +739,18 @@ void main() {
|
||||
|
||||
Widget _buildSettingsPage(BuildContext context) => const SizedBox.shrink();
|
||||
|
||||
final _mutableActiveCommunityProvider =
|
||||
NotifierProvider<_MutableActiveCommunityNotifier, Community?>(
|
||||
_MutableActiveCommunityNotifier.new,
|
||||
);
|
||||
|
||||
class _MutableActiveCommunityNotifier extends Notifier<Community?> {
|
||||
@override
|
||||
Community? build() => null;
|
||||
|
||||
void select(Community? community) => state = community;
|
||||
}
|
||||
|
||||
class _FakeUserStatusNotifier extends UserStatusNotifier {
|
||||
_FakeUserStatusNotifier(this._status);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user