Notifications in groups, and what is unread

The Notifications tab reads grouped notifications (/api/v2/notifications):
likes and boosts of one post, or follows of one hour, are one card naming
both accounts of a pair, or the newest and how many others, with their
avatars. A notification the stream tells joins its group and waits on top.
The tab's badge counts what is unread; opening the tab moves the
notifications marker to the newest shown, which reads it on PrivaPub.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LsXgEaXee4GCU1hwYgPJXw
This commit is contained in:
thepraandClaude Opus 5.5 committed 2026-10-05 04:35:23 +02:00
1 parent 963565b316
commit a4d9831691
11 files changed
+263 -12

No files matched your search

+27 -5
View File
@@ -78,25 +78,47 @@ namespace decePubClient.Services
};
}
/// <summary>Notifications folded into groups (/api/v2/notifications): likes and boosts of one post, follows of one
/// hour, each an item with its count and a few of its accounts. Paged by notification ids from the Link header.</summary>
async Task<WebResult> Notifications(string maxId, string minId, CancellationToken cancellationToken)
{
using var response = await httpService.Get(APIs.Mastodon.GET_Notifications, Paging([], maxId, minId), cancellationToken: cancellationToken);
using var response = await httpService.Get(APIs.Mastodon.GET_GroupedNotifications, Paging([], maxId, minId), cancellationToken: cancellationToken);
if (!response.IsSuccessStatusCode)
return await response.Content.ReadWebResult(response.StatusCode, localizer, cancellationToken);
var notifications = await response.MastodonReadFromJsonAsync<List<Notification>>(cancellationToken) ?? [];
var grouped = await response.MastodonReadFromJsonAsync<GroupedNotifications>(cancellationToken) ?? new GroupedNotifications();
var accounts = grouped.Accounts.DistinctBy(a => a.Id).ToDictionary(a => a.Id);
var statuses = grouped.Statuses.DistinctBy(s => s.Id).ToDictionary(s => s.Id);
var (nextMaxId, prevMinId) = response.PageCursors();
return new WebResult
{
Data = new FeedPage
{
Posts = [.. notifications.Select(FeedPost.Of)],
MaxId = notifications.Count < PageSize ? null : nextMaxId ?? notifications.LastOrDefault()?.Id,
MinId = prevMinId ?? notifications.FirstOrDefault()?.Id ?? minId
Posts = [.. grouped.NotificationGroups.Select(g => FeedPost.Of(g, accounts, statuses))],
MaxId = grouped.NotificationGroups.Count == 0 ? null : nextMaxId,
MinId = prevMinId ?? grouped.NotificationGroups.FirstOrDefault()?.MostRecentNotificationId ?? minId
}
};
}
/// <summary>How many groups of notifications are unread (newer than the notifications marker).</summary>
public async Task<int> UnreadNotifications(CancellationToken cancellationToken = default)
{
using var response = await httpService.Get(APIs.Mastodon.GET_UnreadNotifications, cancellationToken: cancellationToken);
return response.IsSuccessStatusCode ? (await response.MastodonReadFromJsonAsync<UnreadCount>(cancellationToken))?.Count ?? 0 : 0;
}
/// <summary>Moves the notifications marker to the newest one seen, which reads everything up to it.</summary>
public async Task<WebResult> ReadNotificationsUpTo(string notificationId, CancellationToken cancellationToken = default)
{
using var response = await httpService.Post(APIs.Mastodon.POST_Markers, new MarkersForm { Notifications = new MarkerForm { LastReadId = notificationId } },
cancellationToken: cancellationToken);
if (!response.IsSuccessStatusCode)
return await response.Content.ReadWebResult(response.StatusCode, localizer, cancellationToken);
return new WebResult();
}
/// <summary>Direct conversations, newest first; PrivaPub answers one page.</summary>
async Task<WebResult> Conversations(CancellationToken cancellationToken)
{