mirror of
https://github.com/block/buzz.git
synced 2026-11-10 11:21:05 +00:00
fix(mobile): keep filtered Activity reads synced
msg: marks are local-only, so a filtered row marked read stayed unread on other devices. Advance the synced thread or channel marker up to just before the first unread event the filter hides, and fall back to local msg: marks only for shown events past that point. Co-authored-by: studio <dc2fc481343672df31a689840c09299972e704dac592e0da6f18486646a7ee3e@buzz.block.builderlab.xyz> Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Signed-off-by: Tom Brow <tomb@squareup.com>
This commit is contained in:
@@ -1,7 +1,7 @@
|
||||
import 'package:buzz/shared/widgets/feedback_snackbar.dart';
|
||||
|
||||
import 'dart:async';
|
||||
import 'dart:math' show max;
|
||||
import 'dart:math' show max, min;
|
||||
|
||||
import '../../shared/widgets/ios_message_create_button.dart';
|
||||
import '../../shared/widgets/ios_glass_navigation_button.dart';
|
||||
@@ -213,18 +213,52 @@ class ActivityPage extends HookConsumerWidget {
|
||||
return null;
|
||||
}, [preloadPubkeysKey]);
|
||||
|
||||
// A filtered row holds only part of its conversation. Advance its synced
|
||||
// thread or channel marker only up to the first unread event the filter
|
||||
// hides, and give shown events past that point local per-message marks.
|
||||
void markPartialRowRead(
|
||||
InboxItem item,
|
||||
String channelId, {
|
||||
required bool isDmRow,
|
||||
}) {
|
||||
final notifier = ref.read(readStateProvider.notifier);
|
||||
final shown = {
|
||||
for (final e in [item.item, ...item.groupItems]) e.id: e,
|
||||
};
|
||||
final rootId = isDmRow ? null : item.threadRootId;
|
||||
bool inContext(FeedItem e) =>
|
||||
e.channelId == channelId &&
|
||||
(rootId == null ||
|
||||
(isThreadReply(e.tags) &&
|
||||
threadReferenceOf(e.tags).rootId == rootId));
|
||||
var cap = item.latestActivityAt;
|
||||
for (final e in allItems.expand((i) => i.groupItems)) {
|
||||
if (shown.containsKey(e.id) ||
|
||||
!inContext(e) ||
|
||||
isInboxEventRead(e, markerOf: markerOf)) {
|
||||
continue;
|
||||
}
|
||||
cap = min(cap, e.createdAt - 1);
|
||||
}
|
||||
notifier.markContextRead(
|
||||
rootId == null ? channelId : threadContextKey(rootId),
|
||||
cap,
|
||||
);
|
||||
for (final e in shown.values) {
|
||||
if (inContext(e) && e.createdAt <= cap) continue;
|
||||
notifier.markContextRead(msgContextKey(e.id), e.createdAt);
|
||||
}
|
||||
}
|
||||
|
||||
void markItemRead(InboxItem item) {
|
||||
final notifier = ref.read(readStateProvider.notifier);
|
||||
ref
|
||||
.read(inboxLocalStateProvider.notifier)
|
||||
.clearUnread(groupedInboxItemIds(item));
|
||||
// A filtered row holds only part of its conversation, so channel and
|
||||
// thread markers would also read events this view hides.
|
||||
final isDmRow = item.conversationId.startsWith('dm:');
|
||||
if (rowsArePartial && item.item.channelId != null) {
|
||||
for (final e in {item.item, ...item.groupItems}) {
|
||||
notifier.markContextRead(msgContextKey(e.id), e.createdAt);
|
||||
}
|
||||
final partialChannelId = rowsArePartial ? item.item.channelId : null;
|
||||
if (partialChannelId != null) {
|
||||
markPartialRowRead(item, partialChannelId, isDmRow: isDmRow);
|
||||
return;
|
||||
}
|
||||
// A DM row can group replies under several thread roots; its channel
|
||||
|
||||
@@ -18,6 +18,7 @@ import 'package:buzz/features/channels/message_content.dart';
|
||||
import 'package:buzz/features/channels/channels_provider.dart';
|
||||
import 'package:buzz/shared/identity_names/identity_names.dart';
|
||||
import 'package:buzz/shared/mentions/agent_identity_provider.dart';
|
||||
import 'package:buzz/shared/read_state/read_state_format.dart';
|
||||
import 'package:buzz/shared/read_state/read_state_provider.dart';
|
||||
import 'package:buzz/shared/profile/user_cache_provider.dart';
|
||||
import 'package:buzz/shared/profile/user_profile.dart';
|
||||
@@ -930,6 +931,21 @@ void main() {
|
||||
|
||||
await chooseSender('Anyone');
|
||||
expect(find.byKey(const ValueKey('inbox-unread-dot-p1')), findsOneWidget);
|
||||
int? threadMarker() => ProviderScope.containerOf(
|
||||
tester.element(find.byType(ActivityPage)),
|
||||
).read(readStateProvider).contexts[threadContextKey(threadIdRoot1)];
|
||||
// The synced marker stops short of the hidden agent reply.
|
||||
expect(threadMarker(), now - 301);
|
||||
|
||||
await chooseSender('Agents');
|
||||
await tester.drag(
|
||||
find.byKey(const ValueKey('inbox-row-g1')),
|
||||
const Offset(-500, 0),
|
||||
);
|
||||
await tester.pumpAndSettle();
|
||||
expect(threadMarker(), now - 300);
|
||||
await chooseSender('Anyone');
|
||||
expect(find.byKey(const ValueKey('inbox-unread-dot-p1')), findsNothing);
|
||||
},
|
||||
);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user