SHA256
Исправить синхронизацию прочтения каналов
This commit is contained in:
+4
-1
@@ -82,7 +82,10 @@ public class Net_ListSubscriptionsFeed_Handler implements JsonMessageHandler {
|
||||
|
||||
row.setChannel(channelRef);
|
||||
row.setMessagesCount(ChannelsReadSupport.countPosts(c, key.ownerBch, key.rootNumber));
|
||||
row.setUnreadCount(ChannelsReadSupport.countUnreadMessages(c, viewerLogin, key.ownerBch, meta.channelName, row.getMessagesCount()));
|
||||
boolean ownChannel = key.ownerLogin != null && key.ownerLogin.equalsIgnoreCase(viewerLogin);
|
||||
row.setUnreadCount(ownChannel
|
||||
? 0
|
||||
: ChannelsReadSupport.countUnreadMessages(c, viewerLogin, key.ownerBch, meta.channelName, row.getMessagesCount()));
|
||||
|
||||
ChannelsReadSupport.PostBlock lastPost = ChannelsReadSupport.loadLastPost(c, key.ownerBch, key.rootNumber);
|
||||
if (lastPost != null) {
|
||||
|
||||
@@ -24,8 +24,9 @@
|
||||
|
||||
> На первом этапе мы **не используем курсоры** (`nextCursor`) и загружаем полные списки.
|
||||
>
|
||||
> `unreadCount` для канала считается по `user_settings`:
|
||||
> если для пары `ownerBlockchainName/channelName` ещё нет записи, канал считается полностью прочитанным, а UI при первом открытии записывает текущий курсор чтения.
|
||||
> `unreadCount` для канала считается по `user_settings`.
|
||||
> Для собственных каналов владельцу всегда возвращается `unreadCount = 0`, чтобы его собственные публикации не становились «новыми» для него самого.
|
||||
> Если для пары `ownerBlockchainName/channelName` ещё нет записи, канал временно считается полностью прочитанным; UI при загрузке списка каналов создаёт baseline на текущем `messagesCount`. После этого новые публикации увеличивают `unreadCount` до продвижения курсора чтения.
|
||||
|
||||
---
|
||||
|
||||
|
||||
@@ -393,22 +393,11 @@ function createChannelReadTracker({
|
||||
}
|
||||
window.addEventListener('resize', onResize);
|
||||
|
||||
const initialSyncRequired = canWrite && unreadCount <= 0 && safeMessagesCount >= 0;
|
||||
if (initialSyncRequired) {
|
||||
void authService.upsertUserSetting({
|
||||
login,
|
||||
settingType: 1,
|
||||
settingKey,
|
||||
timeMs: Date.now(),
|
||||
valueText: '',
|
||||
valueNum: safeMessagesCount,
|
||||
storagePwd,
|
||||
}).catch(() => {});
|
||||
persistedSeenCount = safeMessagesCount;
|
||||
if (canWrite) {
|
||||
desiredSeenCount = safeMessagesCount;
|
||||
} else {
|
||||
window.setTimeout(() => measure(), 120);
|
||||
void flush();
|
||||
}
|
||||
window.setTimeout(() => measure(), 120);
|
||||
|
||||
const cleanup = () => {
|
||||
disposed = true;
|
||||
|
||||
@@ -27,6 +27,7 @@ const CHANNEL_TYPE_STORIES = 0;
|
||||
const CHANNEL_TYPE_PERSONAL = 100;
|
||||
const DIARY_CHANNEL_NAME = 'diary';
|
||||
const DIARY_DISPLAY_NAME = 'Дневник';
|
||||
const CHANNEL_READ_SETTING_TYPE = 1;
|
||||
|
||||
const CHANNELS_VIEW_ALL = 'all';
|
||||
const CHANNELS_VIEW_OWNED = 'owned';
|
||||
@@ -111,6 +112,58 @@ function isVisibleChannelSummary(summary) {
|
||||
return !!ownerLogin && !!channelName;
|
||||
}
|
||||
|
||||
function channelReadSettingKey(summary) {
|
||||
const ownerBch = String(summary?.channel?.ownerBlockchainName || '').trim();
|
||||
const channelName = String(summary?.channel?.channelName || '').trim();
|
||||
if (!ownerBch || !channelName) return '';
|
||||
return `${ownerBch}/${channelName}`;
|
||||
}
|
||||
|
||||
async function ensureChannelReadBaselines(feed) {
|
||||
const login = String(state.session.login || '').trim();
|
||||
const storagePwd = state.session.storagePwdInMemory;
|
||||
if (!login || !storagePwd) return;
|
||||
|
||||
let settingsPayload;
|
||||
try {
|
||||
settingsPayload = await authService.listUserSettings(login);
|
||||
} catch {
|
||||
return;
|
||||
}
|
||||
|
||||
const existing = new Set(
|
||||
(Array.isArray(settingsPayload?.settings) ? settingsPayload.settings : [])
|
||||
.filter((item) => Number(item?.setting_type) === CHANNEL_READ_SETTING_TYPE)
|
||||
.map((item) => String(item?.setting_key || '').trim())
|
||||
.filter(Boolean),
|
||||
);
|
||||
|
||||
const summaries = [
|
||||
...(Array.isArray(feed?.followedUsersChannels) ? feed.followedUsersChannels : []),
|
||||
...(Array.isArray(feed?.followedChannels) ? feed.followedChannels : []),
|
||||
].filter(isVisibleChannelSummary);
|
||||
|
||||
for (const summary of summaries) {
|
||||
const settingKey = channelReadSettingKey(summary);
|
||||
if (!settingKey || existing.has(settingKey)) continue;
|
||||
|
||||
try {
|
||||
await authService.upsertUserSetting({
|
||||
login,
|
||||
settingType: CHANNEL_READ_SETTING_TYPE,
|
||||
settingKey,
|
||||
timeMs: Date.now(),
|
||||
valueText: '',
|
||||
valueNum: Math.max(0, Number(summary?.messagesCount || 0)),
|
||||
storagePwd,
|
||||
});
|
||||
existing.add(settingKey);
|
||||
} catch {
|
||||
// Не ломаем экран каналов из-за фоновой инициализации read-state.
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function avatarLetterFromName(name = '') {
|
||||
const first = Array.from(String(name || '').trim())[0] || '#';
|
||||
return first.toUpperCase();
|
||||
@@ -1323,6 +1376,7 @@ async function loadFeedAndRender({ screen, listState, contentEl, navigate }) {
|
||||
|
||||
try {
|
||||
const feed = await authService.listSubscriptionsFeed(state.session.login, 200);
|
||||
void ensureChannelReadBaselines(feed);
|
||||
let diaryPayload = null;
|
||||
try {
|
||||
diaryPayload = await authService.getPersonalDiary(state.session.login, 200, 'asc');
|
||||
|
||||
@@ -3030,6 +3030,15 @@ export class AuthService {
|
||||
return response.payload || {};
|
||||
}
|
||||
|
||||
async listUserSettings(login) {
|
||||
const cleanLogin = String(login || '').trim();
|
||||
if (!cleanLogin) throw new Error('Не передан login');
|
||||
|
||||
const response = await this.ws.request('ListUserSettings', { login: cleanLogin });
|
||||
if (response.status !== 200) throw opError('ListUserSettings', response);
|
||||
return response.payload || {};
|
||||
}
|
||||
|
||||
async upsertUserSetting({
|
||||
login,
|
||||
settingType,
|
||||
|
||||
Reference in New Issue
Block a user