mirror of
https://github.com/block/buzz.git
synced 2026-10-27 12:40:30 +00:00
🤖 ## Summary The web and desktop app (`buzz-app`) saves a "caught up" mark when you scroll to the bottom of a channel and stay there, and another one for a thread. Mobile did not understand these marks. It relied on the separate per-message read marks that `buzz-app` also kept. `buzz-app` is about to stop keeping those per-message marks under the "caught up" mark (block/buzz-app#590), to save space in the synced read state. With this change, mobile reads the "caught up" marks with the same rules as `buzz-app`: - `activity:<channel>` reads ordinary top-level messages up to its time. It does not read thread replies, mentions, broadcasts or DM messages, because reaching the bottom of the channel does not prove you saw those. - `thread-activity:<root>` reads the replies in that thread up to its time. So a message you read in `buzz-app` stays read on mobile: in the app badge, the channel list unread state, the "new messages" divider when you open a channel, and the Mark read / Mark unread action. Mobile does not write these marks. It only reads them. ### Related issue None found. Companion to block/buzz-app#590. The old desktop app in this repo is intentionally not changed. ### Testing - New badge tests: `activity:` clears an ordinary top-level message, and does not clear a mention, a thread reply or a DM. `thread-activity:` clears replies in its own thread only. The two "clears" tests fail without this change. - New tests for the per-message read check and the "ordinary message" rule. - `flutter analyze`, `dart format` check and the full mobile test suite pass locally. - Not yet exercised on a device. --------- Signed-off-by: Larry <627498bd4bd1f281a16431e3c6cce3b5c25b6692798c78672298aefbf2f8f8b5@buzz.block.builderlab.xyz> Co-authored-by: Larry <627498bd4bd1f281a16431e3c6cce3b5c25b6692798c78672298aefbf2f8f8b5@buzz.block.builderlab.xyz>
53 lines
1.9 KiB
Dart
53 lines
1.9 KiB
Dart
import 'read_state_format.dart';
|
|
import 'read_state_provider.dart';
|
|
|
|
/// Effective read timestamp for a single message: the newest of the channel
|
|
/// marker, the message's own `msg:` marker, for thread replies the thread's
|
|
/// `thread:` and `thread-activity:` markers, and for messages that channel
|
|
/// catch-up reads (see `readByChannelCatchUp`) the `activity:` marker — the
|
|
/// same precedence the unread badge uses via `observedUnreadEventReadAt`.
|
|
int? effectiveMessageReadAt(
|
|
ReadStateState readState, {
|
|
required String channelId,
|
|
required String messageId,
|
|
String? threadRootId,
|
|
bool channelCatchUp = false,
|
|
}) {
|
|
return maxReadAt([
|
|
readState.effectiveTimestamp(channelId),
|
|
readState.effectiveTimestamp(msgContextKey(messageId)),
|
|
if (threadRootId != null) ...[
|
|
readState.effectiveTimestamp(threadContextKey(threadRootId)),
|
|
readState.effectiveTimestamp(threadActivityContextKey(threadRootId)),
|
|
],
|
|
if (channelCatchUp)
|
|
readState.effectiveTimestamp(activityContextKey(channelId)),
|
|
]);
|
|
}
|
|
|
|
/// Whether a message should currently show as unread in the actions menu.
|
|
///
|
|
/// A message-level forced unread (from this menu) wins; otherwise the
|
|
/// timestamp precedence decides. A channel-level forced unread (from the
|
|
/// channel tile) is deliberately not consulted: it is a channel-scoped
|
|
/// choice, and letting it leak in here would make the message-level toggle
|
|
/// unable to round-trip without clearing the channel-level choice.
|
|
bool isMessageUnread(
|
|
ReadStateState readState, {
|
|
required String channelId,
|
|
required String messageId,
|
|
required int createdAt,
|
|
String? threadRootId,
|
|
bool channelCatchUp = false,
|
|
}) {
|
|
if (readState.isForcedUnread(msgContextKey(messageId))) return true;
|
|
final readAt = effectiveMessageReadAt(
|
|
readState,
|
|
channelId: channelId,
|
|
messageId: messageId,
|
|
threadRootId: threadRootId,
|
|
channelCatchUp: channelCatchUp,
|
|
);
|
|
return readAt == null || createdAt > readAt;
|
|
}
|