From a4e4e1844502355e8c62d913b99be8c46f245352 Mon Sep 17 00:00:00 2001 From: Elias Nahum Date: Tue, 24 May 2022 08:45:17 -0400 Subject: [PATCH] [Gekidou] perf improvement & fix upgrade path (#6302) * Improve loading display name of direct channels * Improve team switching * Improve perceived performance when opening the app with an active session * Fix upgrade path from v1 * Set moment locale while formatting date * feedback review --- app/actions/remote/channel.ts | 115 +++++++++++------- app/actions/remote/entry/app.ts | 24 ++-- app/actions/remote/entry/common.ts | 43 ++++--- app/actions/remote/notifications.ts | 11 +- app/actions/remote/retry.ts | 6 +- app/actions/remote/user.ts | 94 ++++++++++++-- app/actions/websocket/channel.ts | 4 +- app/components/formatted_date/index.tsx | 3 + app/hooks/team_switch.ts | 2 +- .../additional_tablet_view.tsx | 11 +- .../categories/body/category_body.tsx | 17 ++- .../categories_list/categories/categories.tsx | 15 ++- package-lock.json | 1 + types/database/models/servers/channel.d.ts | 2 +- 14 files changed, 245 insertions(+), 103 deletions(-) diff --git a/app/actions/remote/channel.ts b/app/actions/remote/channel.ts index 13d012904f..8e71b361bf 100644 --- a/app/actions/remote/channel.ts +++ b/app/actions/remote/channel.ts @@ -15,7 +15,7 @@ import {getTeammateNameDisplaySetting} from '@helpers/api/preference'; import NetworkManager from '@managers/network_manager'; import {prepareMyChannelsForTeam, getChannelById, getChannelByName, getMyChannel, getChannelInfo, queryMyChannelSettingsByIds} from '@queries/servers/channel'; import {queryPreferencesByCategoryAndName} from '@queries/servers/preference'; -import {getCommonSystemValues, getCurrentTeamId, getCurrentUserId, setCurrentChannelId} from '@queries/servers/system'; +import {getCommonSystemValues, getConfig, getCurrentTeamId, getCurrentUserId, getLicense, setCurrentChannelId} from '@queries/servers/system'; import {prepareMyTeams, getNthLastChannelFromTeam, getMyTeamById, getTeamById, getTeamByName, queryMyTeams} from '@queries/servers/team'; import {getCurrentUser} from '@queries/servers/user'; import EphemeralStore from '@store/ephemeral_store'; @@ -30,7 +30,7 @@ import {setDirectChannelVisible} from './preference'; import {fetchRolesIfNeeded} from './role'; import {forceLogoutIfNecessary} from './session'; import {addUserToTeam, fetchTeamByName, removeUserFromTeam} from './team'; -import {fetchProfilesPerChannels, fetchUsersByIds, updateUsersNoLongerVisible} from './user'; +import {fetchProfilesInGroupChannels, fetchProfilesPerChannels, fetchUsersByIds, updateUsersNoLongerVisible} from './user'; import type {Client} from '@client/rest'; import type ChannelModel from '@typings/database/models/servers/channel'; @@ -426,61 +426,94 @@ export async function fetchMyChannel(serverUrl: string, teamId: string, channelI } } -export async function fetchMissingSidebarInfo(serverUrl: string, directChannels: Channel[], locale?: string, teammateDisplayNameSetting?: string, currentUserId?: string, fetchOnly = false) { - const channelIds = directChannels.sort((a, b) => b.last_post_at - a.last_post_at).map((dc) => dc.id); - const result = await fetchProfilesPerChannels(serverUrl, channelIds, currentUserId, false); - if (result.error) { - return {error: result.error}; +export async function fetchMissingDirectChannelsInfo(serverUrl: string, directChannels: Channel[], locale?: string, teammateDisplayNameSetting?: string, currentUserId?: string, fetchOnly = false) { + const operator = DatabaseManager.serverDatabases[serverUrl]?.operator; + if (!operator) { + return {error: `${serverUrl} database not found`}; } + const {database} = operator; const displayNameByChannel: Record = {}; const users: UserProfile[] = []; + const updatedChannels = new Set(); - if (result.data) { - result.data.forEach((data) => { - if (data.users?.length) { - users.push(...data.users); - if (data.users.length > 1) { - displayNameByChannel[data.channelId] = displayGroupMessageName(data.users, locale, teammateDisplayNameSetting, currentUserId); - } else { - displayNameByChannel[data.channelId] = displayUsername(data.users[0], locale, teammateDisplayNameSetting, false); + const dms: Channel[] = []; + const gms: Channel[] = []; + for (const c of directChannels) { + if (c.type === General.DM_CHANNEL) { + dms.push(c); + continue; + } + gms.push(c); + } + + try { + const currentUser = await getCurrentUser(database); + const results = await Promise.all([ + fetchProfilesPerChannels(serverUrl, dms.map((c) => c.id), currentUserId, false), + fetchProfilesInGroupChannels(serverUrl, gms.map((c) => c.id), false), + ]); + + const profileRequests = results.flat(); + for (const result of profileRequests) { + result.data?.forEach((data) => { + if (data.users?.length) { + users.push(...data.users); + if (data.users.length > 1) { + displayNameByChannel[data.channelId] = displayGroupMessageName(data.users, locale, teammateDisplayNameSetting, currentUserId); + } else { + displayNameByChannel[data.channelId] = displayUsername(data.users[0], locale, teammateDisplayNameSetting, false); + } + } + }); + + directChannels.forEach((c) => { + const displayName = displayNameByChannel[c.id]; + if (displayName) { + c.display_name = displayName; + c.fake = true; + updatedChannels.add(c); + } + }); + + if (currentUserId) { + const ownDirectChannel = dms.find((dm) => dm.name === getDirectChannelName(currentUserId, currentUserId)); + if (ownDirectChannel) { + ownDirectChannel.display_name = displayUsername(currentUser, locale, teammateDisplayNameSetting, false); } } - }); - } - - directChannels.forEach((c) => { - const displayName = displayNameByChannel[c.id]; - if (displayName) { - c.display_name = displayName; - c.fake = true; } - }); - if (currentUserId) { - const ownDirectChannel = directChannels.find((dm) => dm.name === getDirectChannelName(currentUserId, currentUserId)); - const database = DatabaseManager.serverDatabases[serverUrl]?.database; - if (ownDirectChannel && database) { - const currentUser = await getCurrentUser(database); - ownDirectChannel.display_name = displayUsername(currentUser, locale, teammateDisplayNameSetting, false); - } - } - - if (!fetchOnly) { - const operator = DatabaseManager.serverDatabases[serverUrl]?.operator; - if (operator) { + const updatedChannelsArray = Array.from(updatedChannels); + if (!fetchOnly) { const modelPromises: Array> = []; if (users.length) { - modelPromises.push(operator.handleUsers({users, prepareRecordsOnly: true})); - modelPromises.push(operator.handleChannel({channels: directChannels, prepareRecordsOnly: true})); + modelPromises.push(operator.handleChannel({channels: updatedChannelsArray, prepareRecordsOnly: true})); } const models = await Promise.all(modelPromises); await operator.batchRecords(models.flat()); } + + return {directChannels: updatedChannelsArray, users}; + } catch (error) { + return {error}; + } +} + +export async function fetchDirectChannelsInfo(serverUrl: string, directChannels: ChannelModel[]) { + const database = DatabaseManager.serverDatabases[serverUrl]?.database; + if (!database) { + return {error: `${serverUrl} database not found`}; } - return {directChannels, users}; + const preferences = await queryPreferencesByCategoryAndName(database, Preferences.CATEGORY_DISPLAY_SETTINGS).fetch(); + const config = await getConfig(database); + const license = await getLicense(database); + const teammateDisplayNameSetting = getTeammateNameDisplaySetting(preferences, config, license); + const currentUser = await getCurrentUser(database); + const channels = directChannels.map((d) => d.toApi()); + return fetchMissingDirectChannelsInfo(serverUrl, channels, currentUser?.locale, teammateDisplayNameSetting, currentUser?.id); } export async function joinChannel(serverUrl: string, userId: string, teamId: string, channelId?: string, channelName?: string, fetchOnly = false) { @@ -773,7 +806,7 @@ export async function createDirectChannel(serverUrl: string, userId: string, dis const preferences = await queryPreferencesByCategoryAndName(database, Preferences.CATEGORY_DISPLAY_SETTINGS, Preferences.NAME_NAME_FORMAT).fetch(); const system = await getCommonSystemValues(database); const teammateDisplayNameSetting = getTeammateNameDisplaySetting(preferences || [], system.config, system.license); - const {directChannels, users} = await fetchMissingSidebarInfo(serverUrl, [created], currentUser.locale, teammateDisplayNameSetting, currentUser.id, true); + const {directChannels, users} = await fetchMissingDirectChannelsInfo(serverUrl, [created], currentUser.locale, teammateDisplayNameSetting, currentUser.id, true); created.display_name = directChannels?.[0].display_name || created.display_name; if (users?.length) { profiles.push(...users); @@ -917,7 +950,7 @@ export async function createGroupChannel(serverUrl: string, userIds: string[]) { const preferences = await queryPreferencesByCategoryAndName(operator.database, Preferences.CATEGORY_DISPLAY_SETTINGS, Preferences.NAME_NAME_FORMAT).fetch(); const system = await getCommonSystemValues(operator.database); const teammateDisplayNameSetting = getTeammateNameDisplaySetting(preferences || [], system.config, system.license); - const {directChannels, users} = await fetchMissingSidebarInfo(serverUrl, [created], currentUser.locale, teammateDisplayNameSetting, currentUser.id, true); + const {directChannels, users} = await fetchMissingDirectChannelsInfo(serverUrl, [created], currentUser.locale, teammateDisplayNameSetting, currentUser.id, true); const member = { channel_id: created.id, diff --git a/app/actions/remote/entry/app.ts b/app/actions/remote/entry/app.ts index e127768a90..7977fa773b 100644 --- a/app/actions/remote/entry/app.ts +++ b/app/actions/remote/entry/app.ts @@ -11,7 +11,7 @@ import {isTablet} from '@utils/helpers'; import {deferredAppEntryActions, entry, registerDeviceToken, syncOtherServers, verifyPushProxy} from './common'; -export async function appEntry(serverUrl: string, since = 0) { +export async function appEntry(serverUrl: string, since = 0, isUpgrade = false) { const operator = DatabaseManager.serverDatabases[serverUrl]?.operator; if (!operator) { return {error: `${serverUrl} database not found`}; @@ -38,7 +38,13 @@ export async function appEntry(serverUrl: string, since = 0) { if ('error' in entryData) { return {error: entryData.error}; } - const {models, initialTeamId, initialChannelId, prefData, teamData, chData} = entryData; + const {models, initialTeamId, initialChannelId, prefData, teamData, chData, meData} = entryData; + if (isUpgrade && meData?.user) { + const me = await prepareCommonSystemValues(operator, {currentUserId: meData.user.id}); + if (me?.length) { + await operator.batchRecords(me); + } + } let switchToChannel = false; @@ -53,10 +59,9 @@ export async function appEntry(serverUrl: string, since = 0) { await operator.batchRecords(models); - const {id: currentUserId, locale: currentUserLocale} = (await getCurrentUser(database))!; + const {id: currentUserId, locale: currentUserLocale} = meData?.user || (await getCurrentUser(database))!; const {config, license} = await getCommonSystemValues(database); await deferredAppEntryActions(serverUrl, lastDisconnectedAt, currentUserId, currentUserLocale, prefData.preferences, config, license, teamData, chData, initialTeamId, switchToChannel ? initialChannelId : undefined); - if (!since) { // Load data from other servers syncOtherServers(serverUrl); @@ -69,22 +74,13 @@ export async function appEntry(serverUrl: string, since = 0) { export async function upgradeEntry(serverUrl: string) { const dt = Date.now(); - const operator = DatabaseManager.serverDatabases[serverUrl]?.operator; - if (!operator) { - return {error: `${serverUrl} database not found`}; - } try { const configAndLicense = await fetchConfigAndLicense(serverUrl, false); - const entryData = await appEntry(serverUrl); - + const entryData = await appEntry(serverUrl, 0, true); const error = configAndLicense.error || entryData.error; if (!error) { - const models = await prepareCommonSystemValues(operator, {currentUserId: entryData.userId}); - if (models?.length) { - await operator.batchRecords(models); - } DatabaseManager.updateServerIdentifier(serverUrl, configAndLicense.config!.DiagnosticId); DatabaseManager.setActiveServerDatabase(serverUrl); deleteV1Data(); diff --git a/app/actions/remote/entry/common.ts b/app/actions/remote/entry/common.ts index ea17a47919..80198da99a 100644 --- a/app/actions/remote/entry/common.ts +++ b/app/actions/remote/entry/common.ts @@ -3,7 +3,7 @@ import {Model} from '@nozbe/watermelondb'; -import {fetchMissingSidebarInfo, fetchMyChannelsForTeam, MyChannelsRequest} from '@actions/remote/channel'; +import {fetchMissingDirectChannelsInfo, fetchMyChannelsForTeam, MyChannelsRequest} from '@actions/remote/channel'; import {fetchPostsForUnreadChannels} from '@actions/remote/post'; import {MyPreferencesRequest, fetchMyPreferences} from '@actions/remote/preference'; import {fetchRoles} from '@actions/remote/role'; @@ -45,6 +45,20 @@ export type AppEntryError = { error: Error | ClientError | string; } +export type EntryResponse = { + models: Model[]; + initialTeamId: string; + initialChannelId: string; + prefData: MyPreferencesRequest; + teamData: MyTeamsRequest; + chData?: MyChannelsRequest; + meData?: MyUserRequest; +} | { + error: unknown; +} + +const FETCH_MISSING_DM_TIMEOUT = 1000; + export const teamsToRemove = async (serverUrl: string, removeTeamIds?: string[]) => { const operator = DatabaseManager.serverDatabases[serverUrl]?.operator; if (!operator) { @@ -66,16 +80,6 @@ export const teamsToRemove = async (serverUrl: string, removeTeamIds?: string[]) return undefined; }; -export type EntryResponse = { - models: Model[]; - initialTeamId: string; - initialChannelId: string; - prefData: MyPreferencesRequest; - teamData: MyTeamsRequest; - chData?: MyChannelsRequest; -} | { - error: unknown; -} export const entry = async (serverUrl: string, teamId?: string, channelId?: string, since = 0): Promise => { const operator = DatabaseManager.serverDatabases[serverUrl]?.operator; if (!operator) { @@ -121,7 +125,7 @@ export const entry = async (serverUrl: string, teamId?: string, channelId?: stri const models = await Promise.all(modelPromises); - return {models: models.flat(), initialChannelId, initialTeamId, prefData, teamData, chData}; + return {models: models.flat(), initialChannelId, initialTeamId, prefData, teamData, chData, meData}; }; export const fetchAppEntryData = async (serverUrl: string, since: number, initialTeamId = ''): Promise => { @@ -255,13 +259,10 @@ export async function deferredAppEntryActions( config: ClientConfig, license: ClientLicense, teamData: MyTeamsRequest, chData: MyChannelsRequest | undefined, initialTeamId?: string, initialChannelId?: string) { // defer sidebar DM & GM profiles + let channelsToFetchProfiles: Set|undefined; if (chData?.channels?.length && chData.memberships?.length) { const directChannels = chData.channels.filter(isDMorGM); - const channelsToFetchProfiles = new Set(directChannels); - if (channelsToFetchProfiles.size) { - const teammateDisplayNameSetting = getTeammateNameDisplaySetting(preferences || [], config, license); - await fetchMissingSidebarInfo(serverUrl, Array.from(channelsToFetchProfiles), currentUserLocale, teammateDisplayNameSetting, currentUserId); - } + channelsToFetchProfiles = new Set(directChannels); // defer fetching posts for unread channels on initial team fetchPostsForUnreadChannels(serverUrl, chData.channels, chData.memberships, initialChannelId); @@ -269,7 +270,7 @@ export async function deferredAppEntryActions( // defer fetch channels and unread posts for other teams if (teamData.teams?.length && teamData.memberships?.length) { - await fetchTeamsChannelsAndUnreadPosts(serverUrl, since, teamData.teams, teamData.memberships, initialTeamId); + fetchTeamsChannelsAndUnreadPosts(serverUrl, since, teamData.teams, teamData.memberships, initialTeamId); } if (preferences && processIsCRTEnabled(preferences, config)) { @@ -289,6 +290,12 @@ export async function deferredAppEntryActions( fetchAllTeams(serverUrl); updateAllUsersSince(serverUrl, since); + setTimeout(async () => { + if (channelsToFetchProfiles?.size) { + const teammateDisplayNameSetting = getTeammateNameDisplaySetting(preferences || [], config, license); + fetchMissingDirectChannelsInfo(serverUrl, Array.from(channelsToFetchProfiles), currentUserLocale, teammateDisplayNameSetting, currentUserId); + } + }, FETCH_MISSING_DM_TIMEOUT); } export const registerDeviceToken = async (serverUrl: string) => { diff --git a/app/actions/remote/notifications.ts b/app/actions/remote/notifications.ts index 97713d130f..0d7bac29d1 100644 --- a/app/actions/remote/notifications.ts +++ b/app/actions/remote/notifications.ts @@ -4,18 +4,14 @@ import {Platform} from 'react-native'; import {updatePostSinceCache} from '@actions/local/notification'; -import {fetchMissingSidebarInfo, fetchMyChannel, switchToChannelById} from '@actions/remote/channel'; +import {fetchDirectChannelsInfo, fetchMyChannel, switchToChannelById} from '@actions/remote/channel'; import {forceLogoutIfNecessary} from '@actions/remote/session'; import {fetchMyTeam} from '@actions/remote/team'; -import {Preferences} from '@constants'; import DatabaseManager from '@database/manager'; -import {getTeammateNameDisplaySetting} from '@helpers/api/preference'; import {getMyChannel, getChannelById} from '@queries/servers/channel'; -import {queryPreferencesByCategoryAndName} from '@queries/servers/preference'; import {getCommonSystemValues, getWebSocketLastDisconnected} from '@queries/servers/system'; import {getMyTeamById} from '@queries/servers/team'; import {getIsCRTEnabled} from '@queries/servers/thread'; -import {getCurrentUser} from '@queries/servers/user'; import {emitNotificationError} from '@utils/notification'; const fetchNotificationData = async (serverUrl: string, notification: NotificationWithData, skipEvents = false) => { @@ -69,12 +65,9 @@ const fetchNotificationData = async (serverUrl: string, notification: Notificati } if (isDirectChannel) { - const preferences = await queryPreferencesByCategoryAndName(database, Preferences.CATEGORY_DISPLAY_SETTINGS, Preferences.NAME_NAME_FORMAT).fetch(); - const currentUser = await getCurrentUser(database); - const teammateDisplayNameSetting = getTeammateNameDisplaySetting(preferences || [], system.config, system.license); const channel = await getChannelById(database, channelId); if (channel) { - fetchMissingSidebarInfo(serverUrl, [channel.toApi()], currentUser?.locale, teammateDisplayNameSetting, currentUser?.id); + fetchDirectChannelsInfo(serverUrl, [channel]); } } } diff --git a/app/actions/remote/retry.ts b/app/actions/remote/retry.ts index 322b69c5af..e5f006c087 100644 --- a/app/actions/remote/retry.ts +++ b/app/actions/remote/retry.ts @@ -15,7 +15,7 @@ import {getCurrentUser} from '@queries/servers/user'; import TeamModel from '@typings/database/models/servers/team'; import {isDMorGM, selectDefaultChannelForTeam} from '@utils/channel'; -import {fetchMissingSidebarInfo, fetchMyChannelsForTeam, MyChannelsRequest} from './channel'; +import {fetchMissingDirectChannelsInfo, fetchMyChannelsForTeam, MyChannelsRequest} from './channel'; import {fetchPostsForChannel} from './post'; import {fetchMyPreferences, MyPreferencesRequest} from './preference'; import {fetchRolesIfNeeded} from './role'; @@ -122,7 +122,7 @@ export async function retryInitialTeamAndChannel(serverUrl: string) { const channelsToFetchProfiles = new Set(directChannels); if (channelsToFetchProfiles.size) { const teammateDisplayNameSetting = getTeammateNameDisplaySetting(prefData.preferences || [], clData.config, clData.license); - fetchMissingSidebarInfo(serverUrl, Array.from(channelsToFetchProfiles), user.locale, teammateDisplayNameSetting, user.id); + fetchMissingDirectChannelsInfo(serverUrl, Array.from(channelsToFetchProfiles), user.locale, teammateDisplayNameSetting, user.id); } fetchPostsForChannel(serverUrl, initialChannel.id); @@ -201,7 +201,7 @@ export async function retryInitialChannel(serverUrl: string, teamId: string) { const channelsToFetchProfiles = new Set(directChannels); if (channelsToFetchProfiles.size) { const teammateDisplayNameSetting = getTeammateNameDisplaySetting(preferences || [], config, license); - fetchMissingSidebarInfo(serverUrl, Array.from(channelsToFetchProfiles), user.locale, teammateDisplayNameSetting, user.id); + fetchMissingDirectChannelsInfo(serverUrl, Array.from(channelsToFetchProfiles), user.locale, teammateDisplayNameSetting, user.id); } fetchPostsForChannel(serverUrl, initialChannel.id); diff --git a/app/actions/remote/user.ts b/app/actions/remote/user.ts index 7399116e25..69e42b7aa5 100644 --- a/app/actions/remote/user.ts +++ b/app/actions/remote/user.ts @@ -110,6 +110,79 @@ export async function fetchProfilesInChannel(serverUrl: string, channelId: strin } } +export async function fetchProfilesInGroupChannels(serverUrl: string, groupChannelIds: string[], fetchOnly = false): Promise { + const operator = DatabaseManager.serverDatabases[serverUrl]?.operator; + if (!operator) { + return {error: `${serverUrl} database not found`}; + } + + const {database} = operator; + + let client: Client; + try { + client = NetworkManager.getClient(serverUrl); + } catch (error) { + return {error}; + } + + try { + // let's filter those channels that we already have the users + const membersCount = await getMembersCountByChannelsId(database, groupChannelIds); + const channelsToFetch = groupChannelIds.filter((c) => membersCount[c] <= 1); + if (!channelsToFetch.length) { + return {data: []}; + } + + // Batch fetching profiles per channel by chunks of 50 + const gms = chunk(channelsToFetch, 50); + const data: ProfilesInChannelRequest[] = []; + + const requests = gms.map((cIds) => client.getProfilesInGroupChannels(cIds)); + const response = await Promise.all(requests); + for (const r of response) { + for (const id in r) { + if (r[id]) { + data.push({ + channelId: id, + users: r[id], + }); + } + } + } + + if (!fetchOnly) { + const modelPromises: Array> = []; + const users = new Set(); + const memberships: Array<{channel_id: string; user_id: string}> = []; + for (const item of data) { + if (item.users?.length) { + for (const u of item.users) { + users.add(u); + memberships.push({channel_id: item.channelId, user_id: u.id}); + } + } + } + if (memberships.length) { + modelPromises.push(operator.handleChannelMembership({ + channelMemberships: memberships, + prepareRecordsOnly: true, + })); + } + if (users.size) { + const prepare = prepareUsers(operator, Array.from(users)); + modelPromises.push(prepare); + } + + const models = await Promise.all(modelPromises); + await operator.batchRecords(models.flat()); + } + + return {data}; + } catch (error) { + return {error}; + } +} + export async function fetchProfilesPerChannels(serverUrl: string, channelIds: string[], excludeUserId?: string, fetchOnly = false): Promise { try { const operator = DatabaseManager.serverDatabases[serverUrl]?.operator; @@ -121,10 +194,13 @@ export async function fetchProfilesPerChannels(serverUrl: string, channelIds: st // let's filter those channels that we already have the users const membersCount = await getMembersCountByChannelsId(database, channelIds); - const channelsToFetch = channelIds.filter((c) => membersCount[c] <= 1 || membersCount[c] > 2); + const channelsToFetch = channelIds.filter((c) => membersCount[c] <= 1); + if (!channelsToFetch.length) { + return {data: []}; + } - // Batch fetching profiles per channel by chunks of 50 - const channels = chunk(channelsToFetch, 50); + // Batch fetching profiles per channel by chunks of 300 + const channels = chunk(channelsToFetch, 300); const data: ProfilesInChannelRequest[] = []; for await (const cIds of channels) { @@ -139,10 +215,12 @@ export async function fetchProfilesPerChannels(serverUrl: string, channelIds: st const memberships: Array<{channel_id: string; user_id: string}> = []; for (const item of data) { if (item.users?.length) { - item.users.forEach((u) => { - users.add(u); - memberships.push({channel_id: item.channelId, user_id: u.id}); - }); + for (const u of item.users) { + if (u.id !== excludeUserId) { + users.add(u); + memberships.push({channel_id: item.channelId, user_id: u.id}); + } + } } } if (memberships.length) { @@ -152,7 +230,7 @@ export async function fetchProfilesPerChannels(serverUrl: string, channelIds: st })); } if (users.size) { - const prepare = prepareUsers(operator, Array.from(users).filter((u) => u.id !== excludeUserId)); + const prepare = prepareUsers(operator, Array.from(users)); modelPromises.push(prepare); } diff --git a/app/actions/websocket/channel.ts b/app/actions/websocket/channel.ts index 0d7c951cd4..920fc4f98e 100644 --- a/app/actions/websocket/channel.ts +++ b/app/actions/websocket/channel.ts @@ -10,7 +10,7 @@ import { storeMyChannelsForTeam, updateChannelInfoFromChannel, updateMyChannelFromWebsocket, } from '@actions/local/channel'; import {switchToGlobalThreads} from '@actions/local/thread'; -import {fetchMissingSidebarInfo, fetchMyChannel, fetchChannelStats, fetchChannelById, switchToChannelById} from '@actions/remote/channel'; +import {fetchMissingDirectChannelsInfo, fetchMyChannel, fetchChannelStats, fetchChannelById, switchToChannelById} from '@actions/remote/channel'; import {fetchPostsForChannel} from '@actions/remote/post'; import {fetchRolesIfNeeded} from '@actions/remote/role'; import {fetchUsersByIds, updateUsersNoLongerVisible} from '@actions/remote/user'; @@ -205,7 +205,7 @@ export async function handleDirectAddedEvent(serverUrl: string, msg: WebSocketMe const teammateDisplayNameSetting = await getTeammateNameDisplay(database); - const {directChannels, users} = await fetchMissingSidebarInfo(serverUrl, channels, user.locale, teammateDisplayNameSetting, user.id, true); + const {directChannels, users} = await fetchMissingDirectChannelsInfo(serverUrl, channels, user.locale, teammateDisplayNameSetting, user.id, true); if (!directChannels?.[0]) { return; } diff --git a/app/components/formatted_date/index.tsx b/app/components/formatted_date/index.tsx index fff4234103..cd7b7a958e 100644 --- a/app/components/formatted_date/index.tsx +++ b/app/components/formatted_date/index.tsx @@ -3,6 +3,7 @@ import moment from 'moment-timezone'; import React from 'react'; +import {useIntl} from 'react-intl'; import {Text, TextProps} from 'react-native'; type FormattedDateProps = TextProps & { @@ -12,6 +13,8 @@ type FormattedDateProps = TextProps & { } const FormattedDate = ({format = 'MMM DD, YYYY', timezone, value, ...props}: FormattedDateProps) => { + const {locale} = useIntl(); + moment.locale(locale); let formattedDate = moment(value).format(format); if (timezone) { let zone: string; diff --git a/app/hooks/team_switch.ts b/app/hooks/team_switch.ts index 8d70073341..d7e0bb1e63 100644 --- a/app/hooks/team_switch.ts +++ b/app/hooks/team_switch.ts @@ -18,7 +18,7 @@ export const useTeamSwitch = () => { setLoading(true); } else { // eslint-disable-next-line max-nested-callbacks - time = setTimeout(() => setLoading(false), 200); + time = setTimeout(() => setLoading(false), 0); } }); return () => { diff --git a/app/screens/home/channel_list/additional_tablet_view/additional_tablet_view.tsx b/app/screens/home/channel_list/additional_tablet_view/additional_tablet_view.tsx index a41b29a561..92fbce83b1 100644 --- a/app/screens/home/channel_list/additional_tablet_view/additional_tablet_view.tsx +++ b/app/screens/home/channel_list/additional_tablet_view/additional_tablet_view.tsx @@ -29,6 +29,7 @@ const globalScreen: SelectedView = {id: Screens.GLOBAL_THREADS, Component: Globa const AdditionalTabletView = ({onTeam, currentChannelId, isCRTEnabled}: Props) => { const [selected, setSelected] = useState(isCRTEnabled && !currentChannelId ? globalScreen : channelScreen); + const [initiaLoad, setInitialLoad] = useState(true); useEffect(() => { const listener = DeviceEventEmitter.addListener(Navigation.NAVIGATION_HOME, (id: string) => { @@ -44,7 +45,15 @@ const AdditionalTabletView = ({onTeam, currentChannelId, isCRTEnabled}: Props) = return () => listener.remove(); }, []); - if (!selected || !onTeam) { + useEffect(() => { + const t = setTimeout(() => { + setInitialLoad(false); + }, 0); + + return () => clearTimeout(t); + }, []); + + if (!selected || !onTeam || initiaLoad) { return null; } diff --git a/app/screens/home/channel_list/categories_list/categories/body/category_body.tsx b/app/screens/home/channel_list/categories_list/categories/body/category_body.tsx index 0176af07cb..0982839572 100644 --- a/app/screens/home/channel_list/categories_list/categories/body/category_body.tsx +++ b/app/screens/home/channel_list/categories_list/categories/body/category_body.tsx @@ -5,11 +5,14 @@ import React, {useCallback, useEffect, useMemo} from 'react'; import {FlatList} from 'react-native'; import Animated, {Easing, useAnimatedStyle, useSharedValue, withTiming} from 'react-native-reanimated'; +import {fetchDirectChannelsInfo} from '@actions/remote/channel'; +import {useServerUrl} from '@app/context/server'; import ChannelItem from '@components/channel_item'; import {DMS_CATEGORY} from '@constants/categories'; -import ChannelModel from '@typings/database/models/servers/channel'; +import {isDMorGM} from '@utils/channel'; import type CategoryModel from '@typings/database/models/servers/category'; +import type ChannelModel from '@typings/database/models/servers/channel'; type Props = { sortedChannels: ChannelModel[]; @@ -23,6 +26,7 @@ type Props = { const extractKey = (item: ChannelModel) => item.id; const CategoryBody = ({sortedChannels, category, hiddenChannelIds, limit, onChannelSwitch, unreadChannels}: Props) => { + const serverUrl = useServerUrl(); const ids = useMemo(() => { let filteredChannels = sortedChannels; @@ -35,7 +39,11 @@ const CategoryBody = ({sortedChannels, category, hiddenChannelIds, limit, onChan return filteredChannels.slice(0, limit); } return filteredChannels; - }, [category.type, limit, hiddenChannelIds, sortedChannels]); + }, [category.type, limit, hiddenChannelIds, sortedChannels.length]); + + const directChannels = useMemo(() => { + return ids.concat(unreadChannels).filter(isDMorGM); + }, [ids.length, unreadChannels.length]); const renderItem = useCallback(({item}: {item: ChannelModel}) => { return ( @@ -54,6 +62,11 @@ const CategoryBody = ({sortedChannels, category, hiddenChannelIds, limit, onChan sharedValue.value = category.collapsed; }, [category.collapsed]); + useEffect(() => { + const direct = directChannels.filter(isDMorGM); + fetchDirectChannelsInfo(serverUrl, direct); + }, [directChannels.length]); + const height = ids.length ? ids.length * 40 : 0; const unreadHeight = unreadChannels.length ? unreadChannels.length * 40 : 0; diff --git a/app/screens/home/channel_list/categories_list/categories/categories.tsx b/app/screens/home/channel_list/categories_list/categories/categories.tsx index d3308b708e..9297c38a79 100644 --- a/app/screens/home/channel_list/categories_list/categories/categories.tsx +++ b/app/screens/home/channel_list/categories_list/categories/categories.tsx @@ -1,7 +1,7 @@ // Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. // See LICENSE.txt for license information. -import React, {useCallback, useMemo, useRef} from 'react'; +import React, {useCallback, useEffect, useMemo, useRef, useState} from 'react'; import {useIntl} from 'react-intl'; import {FlatList, StyleSheet, View} from 'react-native'; @@ -46,6 +46,7 @@ const Categories = ({categories, onlyUnreads, unreadsOnTop}: Props) => { const isTablet = useIsTablet(); const switchingTeam = useTeamSwitch(); const teamId = categories[0]?.teamId; + const [initiaLoad, setInitialLoad] = useState(true); const onChannelSwitch = useCallback(async (channelId: string) => { switchToChannelById(serverUrl, channelId); @@ -87,13 +88,21 @@ const Categories = ({categories, onlyUnreads, unreadsOnTop}: Props) => { return orderedCategories; }, [categories, onlyUnreads, unreadsOnTop]); + useEffect(() => { + const t = setTimeout(() => { + setInitialLoad(false); + }, 0); + + return () => clearTimeout(t); + }, []); + if (!categories.length) { return ; } return ( <> - {!switchingTeam && ( + {!switchingTeam && !initiaLoad && ( { strictMode={true} /> )} - {switchingTeam && ( + {(switchingTeam || initiaLoad) && ( ; - toApi = () => Channel; + toApi(): Channel; }