[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
This commit is contained in:
Elias Nahum
2022-05-24 08:45:17 -04:00
committed by GitHub
parent 11b58c4de3
commit a4e4e18445
14 changed files with 245 additions and 103 deletions

View File

@@ -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<string, string> = {};
const users: UserProfile[] = [];
const updatedChannels = new Set<Channel>();
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<Promise<Model[]>> = [];
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,

View File

@@ -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();

View File

@@ -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<EntryResponse> => {
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<AppEntryData | AppEntryError> => {
@@ -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<Channel>|undefined;
if (chData?.channels?.length && chData.memberships?.length) {
const directChannels = chData.channels.filter(isDMorGM);
const channelsToFetchProfiles = new Set<Channel>(directChannels);
if (channelsToFetchProfiles.size) {
const teammateDisplayNameSetting = getTeammateNameDisplaySetting(preferences || [], config, license);
await fetchMissingSidebarInfo(serverUrl, Array.from(channelsToFetchProfiles), currentUserLocale, teammateDisplayNameSetting, currentUserId);
}
channelsToFetchProfiles = new Set<Channel>(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) => {

View File

@@ -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]);
}
}
}

View File

@@ -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<Channel>(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<Channel>(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);

View File

@@ -110,6 +110,79 @@ export async function fetchProfilesInChannel(serverUrl: string, channelId: strin
}
}
export async function fetchProfilesInGroupChannels(serverUrl: string, groupChannelIds: string[], fetchOnly = false): Promise<ProfilesPerChannelRequest> {
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<Promise<Model[]>> = [];
const users = new Set<UserProfile>();
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<ProfilesPerChannelRequest> {
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);
}

View File

@@ -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;
}

View File

@@ -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;

View File

@@ -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 () => {

View File

@@ -29,6 +29,7 @@ const globalScreen: SelectedView = {id: Screens.GLOBAL_THREADS, Component: Globa
const AdditionalTabletView = ({onTeam, currentChannelId, isCRTEnabled}: Props) => {
const [selected, setSelected] = useState<SelectedView>(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;
}

View File

@@ -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;

View File

@@ -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 <LoadCategoriesError/>;
}
return (
<>
{!switchingTeam && (
{!switchingTeam && !initiaLoad && (
<FlatList
data={categoriesToShow}
ref={listRef}
@@ -108,7 +117,7 @@ const Categories = ({categories, onlyUnreads, unreadsOnTop}: Props) => {
strictMode={true}
/>
)}
{switchingTeam && (
{(switchingTeam || initiaLoad) && (
<View style={styles.loadingView}>
<Loading
size='large'

1
package-lock.json generated
View File

@@ -5,6 +5,7 @@
"requires": true,
"packages": {
"": {
"name": "mattermost-mobile",
"version": "2.0.0",
"hasInstallScript": true,
"license": "Apache 2.0",

View File

@@ -81,5 +81,5 @@ export default class ChannelModel extends Model {
/** categoryChannel: category of this channel */
categoryChannel: Relation<CategoryChannelModel>;
toApi = () => Channel;
toApi(): Channel;
}