diff --git a/app/actions/local/channel.ts b/app/actions/local/channel.ts index 6dc69ad386..f6951fc069 100644 --- a/app/actions/local/channel.ts +++ b/app/actions/local/channel.ts @@ -272,13 +272,13 @@ export async function resetMessageCount(serverUrl: string, channelId: string) { } } -export async function storeMyChannelsForTeam(serverUrl: string, teamId: string, channels: Channel[], memberships: ChannelMembership[], prepareRecordsOnly = false) { +export async function storeMyChannelsForTeam(serverUrl: string, teamId: string, channels: Channel[], memberships: ChannelMembership[], prepareRecordsOnly = false, isCRTEnabled?: boolean) { const operator = DatabaseManager.serverDatabases[serverUrl]?.operator; if (!operator) { return {error: `${serverUrl} database not found`}; } const modelPromises: Array> = [ - ...await prepareMyChannelsForTeam(operator, teamId, channels, memberships), + ...await prepareMyChannelsForTeam(operator, teamId, channels, memberships, isCRTEnabled), ]; const models = await Promise.all(modelPromises); diff --git a/app/actions/remote/channel.ts b/app/actions/remote/channel.ts index 1d2e91e609..13d012904f 100644 --- a/app/actions/remote/channel.ts +++ b/app/actions/remote/channel.ts @@ -342,7 +342,7 @@ export async function fetchChannelStats(serverUrl: string, channelId: string, fe } } -export async function fetchMyChannelsForTeam(serverUrl: string, teamId: string, includeDeleted = true, since = 0, fetchOnly = false, excludeDirect = false): Promise { +export async function fetchMyChannelsForTeam(serverUrl: string, teamId: string, includeDeleted = true, since = 0, fetchOnly = false, excludeDirect = false, isCRTEnabled?: boolean): Promise { const operator = DatabaseManager.serverDatabases[serverUrl]?.operator; if (!operator) { return {error: `${serverUrl} database not found`}; @@ -378,7 +378,7 @@ export async function fetchMyChannelsForTeam(serverUrl: string, teamId: string, }, []); if (!fetchOnly) { - const {models: chModels} = await storeMyChannelsForTeam(serverUrl, teamId, channels, memberships, true); + const {models: chModels} = await storeMyChannelsForTeam(serverUrl, teamId, channels, memberships, true, isCRTEnabled); const {models: catModels} = await storeCategories(serverUrl, categories, true, true); // Re-sync const models = (chModels || []).concat(catModels || []); if (models.length) { diff --git a/app/actions/remote/entry/common.ts b/app/actions/remote/entry/common.ts index 7adb5b5199..ea17a47919 100644 --- a/app/actions/remote/entry/common.ts +++ b/app/actions/remote/entry/common.ts @@ -26,7 +26,7 @@ import {prepareModels} from '@queries/servers/entry'; import {getConfig, getPushVerificationStatus, getWebSocketLastDisconnected} from '@queries/servers/system'; import {deleteMyTeams, getAvailableTeamIds, getNthLastChannelFromTeam, queryMyTeams, queryMyTeamsByIds, queryTeamsById} from '@queries/servers/team'; import {isDMorGM} from '@utils/channel'; -import {isCRTEnabled} from '@utils/thread'; +import {processIsCRTEnabled} from '@utils/thread'; import type ClientError from '@client/rest/error'; @@ -38,6 +38,7 @@ export type AppEntryData = { meData: MyUserRequest; removeTeamIds?: string[]; removeChannelIds?: string[]; + isCRTEnabled: boolean; } export type AppEntryError = { @@ -90,7 +91,7 @@ export const entry = async (serverUrl: string, teamId?: string, channelId?: stri return {error: fetchedData.error}; } - const {initialTeamId, teamData, chData, prefData, meData, removeTeamIds, removeChannelIds} = fetchedData; + const {initialTeamId, teamData, chData, prefData, meData, removeTeamIds, removeChannelIds, isCRTEnabled} = fetchedData; const error = teamData.error || chData?.error || prefData.error || meData.error; if (error) { return {error}; @@ -113,7 +114,7 @@ export const entry = async (serverUrl: string, teamId?: string, channelId?: stri removeChannels = await queryChannelsById(database, removeChannelIds).fetch(); } - const modelPromises = await prepareModels({operator, initialTeamId, removeTeams, removeChannels, teamData, chData, prefData, meData}); + const modelPromises = await prepareModels({operator, initialTeamId, removeTeams, removeChannels, teamData, chData, prefData, meData, isCRTEnabled}); if (rolesData.roles?.length) { modelPromises.push(operator.handleRole({roles: rolesData.roles, prepareRecordsOnly: true})); } @@ -132,19 +133,20 @@ export const fetchAppEntryData = async (serverUrl: string, since: number, initia const includeDeletedChannels = true; const fetchOnly = true; - await fetchConfigAndLicense(serverUrl); + const confReq = await fetchConfigAndLicense(serverUrl); + const prefData = await fetchMyPreferences(serverUrl, fetchOnly); + const isCRTEnabled = Boolean(prefData.preferences && processIsCRTEnabled(prefData.preferences, confReq.config)); // Fetch in parallel teams / team membership / channels for current team / user preferences / user - const promises: [Promise, Promise, Promise, Promise] = [ + const promises: [Promise, Promise, Promise] = [ fetchMyTeams(serverUrl, fetchOnly), - initialTeamId ? fetchMyChannelsForTeam(serverUrl, initialTeamId, includeDeletedChannels, since, fetchOnly) : Promise.resolve(undefined), - fetchMyPreferences(serverUrl, fetchOnly), + initialTeamId ? fetchMyChannelsForTeam(serverUrl, initialTeamId, includeDeletedChannels, since, fetchOnly, false, isCRTEnabled) : Promise.resolve(undefined), fetchMe(serverUrl, fetchOnly), ]; const removeTeamIds: string[] = []; const resolution = await Promise.all(promises); - const [teamData, , prefData, meData] = resolution; + const [teamData, , meData] = resolution; let [, chData] = resolution; if (!initialTeamId && teamData.teams?.length && teamData.memberships?.length) { @@ -155,7 +157,7 @@ export const fetchAppEntryData = async (serverUrl: string, since: number, initia const myTeams = teamData.teams!.filter((t) => teamMembers.has(t.id)); const defaultTeam = selectDefaultTeam(myTeams, meData.user?.locale || DEFAULT_LOCALE, teamOrderPreference, config?.ExperimentalPrimaryTeam); if (defaultTeam?.id) { - chData = await fetchMyChannelsForTeam(serverUrl, defaultTeam.id, includeDeletedChannels, since, fetchOnly); + chData = await fetchMyChannelsForTeam(serverUrl, defaultTeam.id, includeDeletedChannels, since, fetchOnly, false, isCRTEnabled); } } @@ -171,6 +173,7 @@ export const fetchAppEntryData = async (serverUrl: string, since: number, initia prefData, meData, removeTeamIds, + isCRTEnabled, }; if (teamData.teams?.length === 0 && !teamData.error) { @@ -194,7 +197,7 @@ export const fetchAppEntryData = async (serverUrl: string, since: number, initia } const availableTeamIds = await getAvailableTeamIds(database, initialTeamId, teamData.teams, prefData.preferences, meData.user?.locale); - const alternateTeamData = await fetchAlternateTeamData(serverUrl, availableTeamIds, removeTeamIds, includeDeletedChannels, since, fetchOnly); + const alternateTeamData = await fetchAlternateTeamData(serverUrl, availableTeamIds, removeTeamIds, includeDeletedChannels, since, fetchOnly, isCRTEnabled); data = { ...data, @@ -224,13 +227,13 @@ export const fetchAppEntryData = async (serverUrl: string, since: number, initia export const fetchAlternateTeamData = async ( serverUrl: string, availableTeamIds: string[], removeTeamIds: string[], - includeDeleted = true, since = 0, fetchOnly = false) => { + includeDeleted = true, since = 0, fetchOnly = false, isCRTEnabled?: boolean) => { let initialTeamId = ''; let chData; for (const teamId of availableTeamIds) { // eslint-disable-next-line no-await-in-loop - chData = await fetchMyChannelsForTeam(serverUrl, teamId, includeDeleted, since, fetchOnly); + chData = await fetchMyChannelsForTeam(serverUrl, teamId, includeDeleted, since, fetchOnly, false, isCRTEnabled); const chError = chData.error as ClientError | undefined; if (chError?.status_code === 403) { removeTeamIds.push(teamId); @@ -269,7 +272,7 @@ export async function deferredAppEntryActions( await fetchTeamsChannelsAndUnreadPosts(serverUrl, since, teamData.teams, teamData.memberships, initialTeamId); } - if (preferences && isCRTEnabled(preferences, config)) { + if (preferences && processIsCRTEnabled(preferences, config)) { if (initialTeamId) { await fetchNewThreads(serverUrl, initialTeamId, false); } diff --git a/app/database/operator/server_data_operator/handlers/channel.ts b/app/database/operator/server_data_operator/handlers/channel.ts index fc24c564e3..f6ec1d926f 100644 --- a/app/database/operator/server_data_operator/handlers/channel.ts +++ b/app/database/operator/server_data_operator/handlers/channel.ts @@ -36,7 +36,7 @@ export interface ChannelHandlerMix { handleChannelMembership: ({channelMemberships, prepareRecordsOnly}: HandleChannelMembershipArgs) => Promise; handleMyChannelSettings: ({settings, prepareRecordsOnly}: HandleMyChannelSettingsArgs) => Promise; handleChannelInfo: ({channelInfos, prepareRecordsOnly}: HandleChannelInfoArgs) => Promise; - handleMyChannel: ({channels, myChannels, prepareRecordsOnly}: HandleMyChannelArgs) => Promise; + handleMyChannel: ({channels, myChannels, isCRTEnabled, prepareRecordsOnly}: HandleMyChannelArgs) => Promise; } const ChannelHandler = (superclass: any) => class extends superclass { @@ -138,7 +138,7 @@ const ChannelHandler = (superclass: any) => class extends superclass { * @throws DataOperatorException * @returns {Promise} */ - handleMyChannel = async ({channels, myChannels, prepareRecordsOnly = true}: HandleMyChannelArgs): Promise => { + handleMyChannel = async ({channels, myChannels, isCRTEnabled, prepareRecordsOnly = true}: HandleMyChannelArgs): Promise => { if (!myChannels?.length) { // eslint-disable-next-line no-console console.warn( @@ -157,7 +157,7 @@ const ChannelHandler = (superclass: any) => class extends superclass { return []; } - const isCRTEnabled = await getIsCRTEnabled(this.database); + const isCRT = isCRTEnabled ?? await getIsCRTEnabled(this.database); const channelMap = channels.reduce((result: Record, channel) => { result[channel.id] = channel; @@ -167,11 +167,13 @@ const ChannelHandler = (superclass: any) => class extends superclass { for (const my of myChannels) { const channel = channelMap[my.channel_id]; if (channel) { - const total = isCRTEnabled ? channel.total_msg_count_root! : channel.total_msg_count; - const myMsgCount = isCRTEnabled ? my.msg_count_root! : my.msg_count; - const msgCount = Math.max(0, total - myMsgCount); + const totalMsg = isCRT ? channel.total_msg_count_root! : channel.total_msg_count; + const myMsgCount = isCRT ? my.msg_count_root! : my.msg_count; + const msgCount = Math.max(0, totalMsg - myMsgCount); my.msg_count = msgCount; + my.mention_count = isCRT ? my.mention_count_root! : my.mention_count; my.is_unread = msgCount > 0; + my.last_post_at = (isCRT ? (my.last_root_post_at || my.last_post_at) : my.last_post_at) || 0; } } diff --git a/app/database/operator/server_data_operator/transformers/channel.ts b/app/database/operator/server_data_operator/transformers/channel.ts index 3e4df3e217..d2c660f7e3 100644 --- a/app/database/operator/server_data_operator/transformers/channel.ts +++ b/app/database/operator/server_data_operator/transformers/channel.ts @@ -4,7 +4,6 @@ import {MM_TABLES} from '@constants/database'; import {prepareBaseRecord} from '@database/operator/server_data_operator/transformers/index'; import {extractChannelDisplayName} from '@helpers/database'; -import {getIsCRTEnabled} from '@queries/servers/thread'; import {OperationType} from '@typings/database/enums'; import type {TransformerArgs} from '@typings/database/database'; @@ -126,18 +125,16 @@ export const transformMyChannelRecord = async ({action, database, value}: Transf const record = value.record as MyChannelModel; const isCreateAction = action === OperationType.CREATE; - const isCRTEnabled = await getIsCRTEnabled(database); - const fieldsMapper = (myChannel: MyChannelModel) => { myChannel._raw.id = isCreateAction ? (raw.channel_id || myChannel.id) : record.id; myChannel.roles = raw.roles; - // ignoring msg_count_root because msg_count is already calculated in "handleMyChannel" based on CRT is enabled or not + // ignoring msg_count_root because msg_count, mention_count, last_post_at is already calculated in "handleMyChannel" based on CRT is enabled or not myChannel.messageCount = raw.msg_count; + myChannel.mentionsCount = raw.mention_count; + myChannel.lastPostAt = raw.last_post_at || 0; myChannel.isUnread = Boolean(raw.is_unread); - myChannel.mentionsCount = isCRTEnabled ? raw.mention_count_root! : raw.mention_count; - myChannel.lastPostAt = (isCRTEnabled ? (raw.last_root_post_at || raw.last_post_at) : raw.last_post_at) || 0; myChannel.lastViewedAt = raw.last_viewed_at; myChannel.viewedAt = record?.viewedAt || 0; }; diff --git a/app/queries/servers/channel.ts b/app/queries/servers/channel.ts index 183ff2f420..4d8503311e 100644 --- a/app/queries/servers/channel.ts +++ b/app/queries/servers/channel.ts @@ -59,7 +59,7 @@ type MembershipReduce = { membershipsMap: Record; } -export const prepareMyChannelsForTeam = async (operator: ServerDataOperator, teamId: string, channels: Channel[], channelMembers: ChannelMembership[]) => { +export const prepareMyChannelsForTeam = async (operator: ServerDataOperator, teamId: string, channels: Channel[], channelMembers: ChannelMembership[], isCRTEnabled?: boolean) => { const {database} = operator; const allChannelsForTeam = (await queryAllChannelsForTeam(database, teamId).fetch()). reduce((map: Record, channel) => { @@ -115,7 +115,7 @@ export const prepareMyChannelsForTeam = async (operator: ServerDataOperator, tea const channelRecords = operator.handleChannel({channels, prepareRecordsOnly: true}); const channelInfoRecords = operator.handleChannelInfo({channelInfos, prepareRecordsOnly: true}); const membershipRecords = operator.handleChannelMembership({channelMemberships: channelMembers, prepareRecordsOnly: true}); - const myChannelRecords = operator.handleMyChannel({channels, myChannels: memberships, prepareRecordsOnly: true}); + const myChannelRecords = operator.handleMyChannel({channels, myChannels: memberships, prepareRecordsOnly: true, isCRTEnabled}); const myChannelSettingsRecords = operator.handleMyChannelSettings({settings: memberships, prepareRecordsOnly: true}); return [channelRecords, channelInfoRecords, membershipRecords, myChannelRecords, myChannelSettingsRecords]; diff --git a/app/queries/servers/entry.ts b/app/queries/servers/entry.ts index 6f7d603e56..f8e809df76 100644 --- a/app/queries/servers/entry.ts +++ b/app/queries/servers/entry.ts @@ -26,9 +26,10 @@ type PrepareModelsArgs = { chData?: MyChannelsRequest; prefData?: MyPreferencesRequest; meData?: MyUserRequest; + isCRTEnabled?: boolean; } -export async function prepareModels({operator, initialTeamId, removeTeams, removeChannels, teamData, chData, prefData, meData}: PrepareModelsArgs): Promise>> { +export async function prepareModels({operator, initialTeamId, removeTeams, removeChannels, teamData, chData, prefData, meData, isCRTEnabled}: PrepareModelsArgs): Promise>> { const modelPromises: Array> = []; if (removeTeams?.length) { @@ -53,7 +54,7 @@ export async function prepareModels({operator, initialTeamId, removeTeams, remov } if (initialTeamId && chData?.channels?.length && chData.memberships?.length) { - modelPromises.push(...await prepareMyChannelsForTeam(operator, initialTeamId, chData.channels, chData.memberships)); + modelPromises.push(...await prepareMyChannelsForTeam(operator, initialTeamId, chData.channels, chData.memberships, isCRTEnabled)); } if (prefData?.preferences?.length) { diff --git a/app/queries/servers/post.ts b/app/queries/servers/post.ts index 5731f2fea4..b2ba8c0462 100644 --- a/app/queries/servers/post.ts +++ b/app/queries/servers/post.ts @@ -14,7 +14,7 @@ import {queryPreferencesByCategoryAndName} from './preference'; import type PostInChannelModel from '@typings/database/models/servers/posts_in_channel'; import type PostsInThreadModel from '@typings/database/models/servers/posts_in_thread'; -const {SERVER: {CHANNEL, POST, POSTS_IN_CHANNEL, POSTS_IN_THREAD}} = MM_TABLES; +const {SERVER: {POST, POSTS_IN_CHANNEL, POSTS_IN_THREAD}} = MM_TABLES; export const prepareDeletePost = async (post: PostModel): Promise => { const preparedModels: Model[] = [post.prepareDestroyPermanently()]; @@ -178,30 +178,3 @@ export const queryPostsBetween = (database: Database, earliest: number, latest: } return database.get(POST).query(...clauses); }; - -export const observeLastPostAtPerChannelByTeam = (database: Database, teamId: string) => { - return database.get(POSTS_IN_CHANNEL).query( - Q.on(CHANNEL, - Q.and( - Q.or( - Q.where('team_id', Q.eq(teamId)), - Q.where('team_id', Q.eq('')), - ), - Q.where('delete_at', Q.eq(0)), - ), - ), - Q.sortBy('latest', Q.desc), - ).observeWithColumns(['latest']).pipe( - switchMap((pics) => { - return of$(pics.reduce>((result, pic) => { - const id = pic.channelId; - if (result[id]) { - result[id] = Math.max(result[id], pic.latest); - } else { - result[id] = pic.latest; - } - return result; - }, {})); - }), - ); -}; diff --git a/app/queries/servers/thread.ts b/app/queries/servers/thread.ts index 01819d4b7c..9e64ea743a 100644 --- a/app/queries/servers/thread.ts +++ b/app/queries/servers/thread.ts @@ -7,7 +7,7 @@ import {map, switchMap, distinctUntilChanged} from 'rxjs/operators'; import {Preferences} from '@constants'; import {MM_TABLES} from '@constants/database'; -import {isCRTEnabled} from '@utils/thread'; +import {processIsCRTEnabled} from '@utils/thread'; import {queryPreferencesByCategoryAndName} from './preference'; import {getConfig, observeConfig} from './system'; @@ -22,7 +22,7 @@ const {SERVER: {CHANNEL, POST, THREAD, THREADS_IN_TEAM, THREAD_PARTICIPANT, USER export const getIsCRTEnabled = async (database: Database): Promise => { const config = await getConfig(database); const preferences = await queryPreferencesByCategoryAndName(database, Preferences.CATEGORY_DISPLAY_SETTINGS).fetch(); - return isCRTEnabled(preferences, config); + return processIsCRTEnabled(preferences, config); }; export const getThreadById = async (database: Database, threadId: string) => { @@ -39,7 +39,7 @@ export const observeIsCRTEnabled = (database: Database) => { const preferences = queryPreferencesByCategoryAndName(database, Preferences.CATEGORY_DISPLAY_SETTINGS).observeWithColumns(['value']); return combineLatest([config, preferences]).pipe( map( - ([cfg, prefs]) => isCRTEnabled(prefs, cfg), + ([cfg, prefs]) => processIsCRTEnabled(prefs, cfg), ), ); }; diff --git a/app/screens/home/channel_list/categories_list/categories/unreads/index.ts b/app/screens/home/channel_list/categories_list/categories/unreads/index.ts index 163819b74e..a42b1f2255 100644 --- a/app/screens/home/channel_list/categories_list/categories/unreads/index.ts +++ b/app/screens/home/channel_list/categories_list/categories/unreads/index.ts @@ -9,10 +9,8 @@ import {combineLatestWith, concatAll, map, switchMap} from 'rxjs/operators'; import {Preferences} from '@constants'; import {getPreferenceAsBool} from '@helpers/api/preference'; import {getChannelById, observeAllMyChannelNotifyProps, queryMyChannelUnreads} from '@queries/servers/channel'; -import {observeLastPostAtPerChannelByTeam} from '@queries/servers/post'; import {queryPreferencesByCategoryAndName} from '@queries/servers/preference'; import {observeLastUnreadChannelId} from '@queries/servers/system'; -import {observeIsCRTEnabled} from '@queries/servers/thread'; import {getChannelsFromRelation} from '../body'; @@ -42,10 +40,7 @@ type NotifyProps = { [key: string]: Partial; } -const mostRecentFirst = (lastPostAtPerChannel: Record | undefined, a: MyChannelModel, b: MyChannelModel) => { - if (lastPostAtPerChannel && lastPostAtPerChannel[a.id] && lastPostAtPerChannel[b.id]) { - return lastPostAtPerChannel[b.id] - lastPostAtPerChannel[a.id]; - } +const mostRecentFirst = (a: MyChannelModel, b: MyChannelModel) => { return b.lastPostAt - a.lastPostAt; }; @@ -61,10 +56,9 @@ const mostRecentFirst = (lastPostAtPerChannel: Record | undefine type FilterAndSortMyChannelsArgs = [ MyChannelModel[], NotifyProps, - Record | undefined ] -const filterAndSortMyChannels = ([myChannels, notifyProps, lastPostAtPerChannel]: FilterAndSortMyChannelsArgs): MyChannelModel[] => { +const filterAndSortMyChannels = ([myChannels, notifyProps]: FilterAndSortMyChannelsArgs): MyChannelModel[] => { const mentions: MyChannelModel[] = []; const unreads: MyChannelModel[] = []; const mutedMentions: MyChannelModel[] = []; @@ -96,9 +90,9 @@ const filterAndSortMyChannels = ([myChannels, notifyProps, lastPostAtPerChannel] } // Sort - mentions.sort(mostRecentFirst.bind(null, lastPostAtPerChannel)); - unreads.sort(mostRecentFirst.bind(null, lastPostAtPerChannel)); - mutedMentions.sort(mostRecentFirst.bind(null, lastPostAtPerChannel)); + mentions.sort(mostRecentFirst); + unreads.sort(mostRecentFirst); + mutedMentions.sort(mostRecentFirst); return [...mentions, ...unreads, ...mutedMentions]; }; @@ -118,13 +112,9 @@ const enhanced = withObservables(['currentTeamId', 'isTablet', 'onlyUnreads'], ( switchMap(getC), ) : of$(''); const notifyProps = observeAllMyChannelNotifyProps(database); - const crt = observeIsCRTEnabled(database); - const lastPostInChannel = crt.pipe( - switchMap((enabled) => (enabled && onlyUnreads ? observeLastPostAtPerChannelByTeam(database, currentTeamId) : of$(undefined))), - ); const unreads = queryMyChannelUnreads(database, currentTeamId).observeWithColumns(['last_post_at']).pipe( - combineLatestWith(notifyProps, lastPostInChannel), + combineLatestWith(notifyProps), map(filterAndSortMyChannels), map(getChannelsFromRelation), concatAll(), diff --git a/app/utils/thread/index.ts b/app/utils/thread/index.ts index 6946b73f7f..2f2c866871 100644 --- a/app/utils/thread/index.ts +++ b/app/utils/thread/index.ts @@ -6,7 +6,7 @@ import {getPreferenceValue} from '@helpers/api/preference'; import type PreferenceModel from '@typings/database/models/servers/preference'; -export function isCRTEnabled(preferences: PreferenceModel[]|PreferenceType[], config?: ClientConfig): boolean { +export function processIsCRTEnabled(preferences: PreferenceModel[]|PreferenceType[], config?: ClientConfig): boolean { let preferenceDefault = Preferences.COLLAPSED_REPLY_THREADS_OFF; const configValue = config?.CollapsedThreads; if (configValue === Config.DEFAULT_ON) { diff --git a/types/database/database.d.ts b/types/database/database.d.ts index 1ec2870002..e69279a27b 100644 --- a/types/database/database.d.ts +++ b/types/database/database.d.ts @@ -199,6 +199,7 @@ export type HandleSystemArgs = PrepareOnly & { export type HandleMyChannelArgs = PrepareOnly & { channels?: Channel[]; myChannels?: ChannelMembership[]; + isCRTEnabled?: boolean; }; export type HandleChannelInfoArgs = PrepareOnly &{