Add minor fixes and performance improvements on channel switch (#6469)

* Add minor fixes and performance improvements

* Add comment
This commit is contained in:
Daniel Espino García
2022-07-15 16:04:58 +02:00
committed by GitHub
parent 52d5f903f3
commit 3abaf8893d
18 changed files with 115 additions and 61 deletions

View File

@@ -4,11 +4,12 @@
/* eslint-disable max-lines */
import {Model} from '@nozbe/watermelondb';
import {IntlShape} from 'react-intl';
import {DeviceEventEmitter} from 'react-native';
import {addChannelToDefaultCategory, storeCategories} from '@actions/local/category';
import {removeCurrentUserFromChannel, setChannelDeleteAt, storeMyChannelsForTeam, switchToChannel} from '@actions/local/channel';
import {switchToGlobalThreads} from '@actions/local/thread';
import {General, Preferences, Screens} from '@constants';
import {Events, General, Preferences, Screens} from '@constants';
import DatabaseManager from '@database/manager';
import {privateChannelJoinPrompt} from '@helpers/api/channel';
import {getTeammateNameDisplaySetting} from '@helpers/api/preference';
@@ -1101,12 +1102,16 @@ export async function switchToChannelById(serverUrl: string, channelId: string,
return {error: `${serverUrl} database not found`};
}
DeviceEventEmitter.emit(Events.CHANNEL_SWITCH, true);
fetchPostsForChannel(serverUrl, channelId);
await switchToChannel(serverUrl, channelId, teamId, skipLastUnread);
setDirectChannelVisible(serverUrl, channelId);
markChannelAsRead(serverUrl, channelId);
fetchChannelStats(serverUrl, channelId);
DeviceEventEmitter.emit(Events.CHANNEL_SWITCH, false);
return {};
}

View File

@@ -42,9 +42,9 @@ const enhanced = withObservables([], ({database, channelId}: WithDatabaseArgs &
switchMap((c) => of$(Boolean(c?.isGroupConstrained))),
);
useChannelMentions = combineLatest([currentUser, currentChannel]).pipe(switchMap(([u, c]) => (u && c ? observePermissionForChannel(database, c, u, Permissions.USE_CHANNEL_MENTIONS, false) : of$(false))));
useChannelMentions = combineLatest([currentUser, currentChannel]).pipe(switchMap(([u, c]) => observePermissionForChannel(database, c, u, Permissions.USE_CHANNEL_MENTIONS, false)));
useGroupMentions = combineLatest([currentUser, currentChannel, hasLicense]).pipe(
switchMap(([u, c, lcs]) => (lcs && u && c ? observePermissionForChannel(database, c, u, Permissions.USE_GROUP_MENTIONS, false) : of$(false))),
switchMap(([u, c, lcs]) => (lcs ? observePermissionForChannel(database, c, u, Permissions.USE_GROUP_MENTIONS, false) : of$(false))),
);
} else {
useChannelMentions = of$(false);

View File

@@ -7,7 +7,7 @@
import {withDatabase} from '@nozbe/watermelondb/DatabaseProvider';
import withObservables from '@nozbe/with-observables';
import {of as of$} from 'rxjs';
import {switchMap} from 'rxjs/operators';
import {switchMap, distinctUntilChanged} from 'rxjs/operators';
import {observeChannelInfo} from '@queries/servers/channel';
@@ -23,6 +23,11 @@ const enhanced = withObservables(['channelId'], ({channelId, database}: OwnProps
const channelInfo = observeChannelInfo(database, channelId);
const isHeaderSet = channelInfo.pipe(
switchMap((c) => of$(Boolean(c?.header))),
// Channel info is fetched when we switch to the channel, and should update
// the member count whenever a user joins or leaves the channel, so this should
// save us a few renders.
distinctUntilChanged(),
);
return {

View File

@@ -142,7 +142,9 @@ const AtMention = ({
// Effects
useEffect(() => {
// Fetches and updates the local db store with the mention
fetchUserOrGroupsByMentionsInBatch(serverUrl, mentionName);
if (!user.username) {
fetchUserOrGroupsByMentionsInBatch(serverUrl, mentionName);
}
}, []);
const openUserProfile = () => {

View File

@@ -5,9 +5,9 @@ import {withDatabase} from '@nozbe/watermelondb/DatabaseProvider';
import withObservables from '@nozbe/with-observables';
import React from 'react';
import {of as of$} from 'rxjs';
import {switchMap} from 'rxjs/operators';
import {switchMap, distinctUntilChanged} from 'rxjs/operators';
import {observeChannel, observeCurrentChannel} from '@queries/servers/channel';
import {observeChannel} from '@queries/servers/channel';
import {observeConfig} from '@queries/servers/system';
import PostInput from './post_input';
@@ -17,10 +17,9 @@ import type ChannelInfoModel from '@typings/database/models/servers/channel_info
type OwnProps = {
channelId: string;
rootId?: string;
}
const enhanced = withObservables([], ({database, channelId, rootId}: WithDatabaseArgs & OwnProps) => {
const enhanced = withObservables(['channelId'], ({database, channelId}: WithDatabaseArgs & OwnProps) => {
const config = observeConfig(database);
const timeBetweenUserTypingUpdatesMilliseconds = config.pipe(
switchMap((cfg) => of$(parseInt(cfg?.TimeBetweenUserTypingUpdatesMilliseconds || '0', 10))),
@@ -34,12 +33,7 @@ const enhanced = withObservables([], ({database, channelId, rootId}: WithDatabas
switchMap((cfg) => of$(parseInt(cfg?.MaxNotificationsPerChannel || '0', 10))),
);
let channel;
if (rootId) {
channel = observeChannel(database, channelId);
} else {
channel = observeCurrentChannel(database);
}
const channel = observeChannel(database, channelId);
const channelDisplayName = channel.pipe(
switchMap((c) => of$(c?.displayName)),
@@ -47,8 +41,8 @@ const enhanced = withObservables([], ({database, channelId, rootId}: WithDatabas
const membersInChannel = channel.pipe(
switchMap((c) => (c ? c.info.observe() : of$({memberCount: 0}))),
).pipe(
switchMap((i: ChannelInfoModel) => of$(i.memberCount)),
distinctUntilChanged(),
);
return {

View File

@@ -28,7 +28,7 @@ const withCombinedPosts = withObservables(['postId'], ({database, postId}: WithD
const posts = queryPostsById(database, postIds).observe();
const post = posts.pipe(map((ps) => generateCombinedPost(postId, ps)));
const canDelete = combineLatest([posts, currentUser]).pipe(
switchMap(([ps, u]) => (u && ps.length ? observePermissionForPost(database, ps[0], u, Permissions.DELETE_OTHERS_POSTS, false) : of$(false))),
switchMap(([ps, u]) => (ps.length ? observePermissionForPost(database, ps[0], u, Permissions.DELETE_OTHERS_POSTS, false) : of$(false))),
);
const usernamesById = post.pipe(

View File

@@ -23,7 +23,7 @@ import type {SearchPattern} from '@typings/global/markdown';
type BodyProps = {
appsEnabled: boolean;
filesCount: number;
hasFiles: boolean;
hasReactions: boolean;
highlight: boolean;
highlightReplyBar: boolean;
@@ -74,7 +74,7 @@ const getStyleSheet = makeStyleSheetFromTheme((theme: Theme) => {
});
const Body = ({
appsEnabled, filesCount, hasReactions, highlight, highlightReplyBar,
appsEnabled, hasFiles, hasReactions, highlight, highlightReplyBar,
isEphemeral, isFirstReply, isJumboEmoji, isLastReply, isPendingOrFailed, isPostAddChannelMember,
location, post, searchPatterns, showAddReaction, theme,
}: BodyProps) => {
@@ -170,7 +170,7 @@ const Body = ({
theme={theme}
/>
}
{filesCount > 0 &&
{hasFiles &&
<Files
failed={isFailed}
layoutWidth={layoutWidth}

View File

@@ -33,8 +33,8 @@ const withReactions = withObservables(['post'], ({database, post}: WithReactions
map(([u, c, readOnly]) => ((c && c.deleteAt > 0) || (c?.name === General.DEFAULT_CHANNEL && !isSystemAdmin(u?.roles || '') && readOnly))),
);
const canAddReaction = currentUser.pipe(switchMap((u) => (u ? observePermissionForPost(database, post, u, Permissions.ADD_REACTION, true) : of$(true))));
const canRemoveReaction = currentUser.pipe(switchMap((u) => (u ? observePermissionForPost(database, post, u, Permissions.REMOVE_REACTION, true) : of$(true))));
const canAddReaction = currentUser.pipe(switchMap((u) => observePermissionForPost(database, post, u, Permissions.ADD_REACTION, true)));
const canRemoveReaction = currentUser.pipe(switchMap((u) => observePermissionForPost(database, post, u, Permissions.REMOVE_REACTION, true)));
return {
canAddReaction,

View File

@@ -96,7 +96,7 @@ const withSystem = withObservables([], ({database}: WithDatabaseArgs) => ({
const withPost = withObservables(
['currentUser', 'isCRTEnabled', 'post', 'previousPost', 'nextPost'],
({appsEnabled, currentUser, database, isCRTEnabled, post, previousPost, nextPost}: PropsInput) => {
({currentUser, database, isCRTEnabled, post, previousPost, nextPost}: PropsInput) => {
let isJumboEmoji = of$(false);
let isLastReply = of$(true);
let isPostAddChannelMember = of$(false);
@@ -145,11 +145,20 @@ const withPost = withObservables(
distinctUntilChanged(),
);
const hasFiles = post.files.observe().pipe(
switchMap((ff) => of$(Boolean(ff.length))),
distinctUntilChanged(),
);
const hasReactions = post.reactions.observe().pipe(
switchMap((rr) => of$(Boolean(rr.length))),
distinctUntilChanged(),
);
return {
appsEnabled: of$(appsEnabled),
canDelete,
differentThreadSequence: of$(differentThreadSequence),
filesCount: post.files.observeCount(),
hasFiles,
hasReplies,
highlightReplyBar,
isConsecutivePost,
@@ -161,7 +170,7 @@ const withPost = withObservables(
isPostAddChannelMember,
post: post.observe(),
thread: isCRTEnabled ? observeThreadById(database, post.id) : of$(undefined),
reactionsCount: post.reactions.observeCount(),
hasReactions,
};
});

View File

@@ -38,7 +38,7 @@ type PostProps = {
canDelete: boolean;
currentUser: UserModel;
differentThreadSequence: boolean;
filesCount: number;
hasFiles: boolean;
hasReplies: boolean;
highlight?: boolean;
highlightPinnedOrSaved?: boolean;
@@ -54,7 +54,7 @@ type PostProps = {
location: string;
post: PostModel;
previousPost?: PostModel;
reactionsCount: number;
hasReactions: boolean;
searchPatterns?: SearchPattern[];
shouldRenderReplyButton?: boolean;
showAddReaction?: boolean;
@@ -103,9 +103,9 @@ const getStyleSheet = makeStyleSheetFromTheme((theme: Theme) => {
});
const Post = ({
appsEnabled, canDelete, currentUser, differentThreadSequence, filesCount, hasReplies, highlight, highlightPinnedOrSaved = true, highlightReplyBar,
appsEnabled, canDelete, currentUser, differentThreadSequence, hasFiles, hasReplies, highlight, highlightPinnedOrSaved = true, highlightReplyBar,
isCRTEnabled, isConsecutivePost, isEphemeral, isFirstReply, isSaved, isJumboEmoji, isLastReply, isPostAddChannelMember,
location, post, reactionsCount, searchPatterns, shouldRenderReplyButton, skipSavedHeader, skipPinnedHeader, showAddReaction = true, style,
location, post, hasReactions, searchPatterns, shouldRenderReplyButton, skipSavedHeader, skipPinnedHeader, showAddReaction = true, style,
testID, thread, previousPost,
}: PostProps) => {
const pressDetected = useRef(false);
@@ -274,8 +274,8 @@ const Post = ({
body = (
<Body
appsEnabled={appsEnabled}
filesCount={filesCount}
hasReactions={reactionsCount > 0}
hasFiles={hasFiles}
hasReactions={hasReactions}
highlight={Boolean(highlightedStyle)}
highlightReplyBar={highlightReplyBar}
isEphemeral={isEphemeral}

View File

@@ -6,6 +6,7 @@ import keyMirror from '@utils/key_mirror';
export default keyMirror({
ACCOUNT_SELECT_TABLET_VIEW: null,
CHANNEL_ARCHIVED: null,
CHANNEL_SWITCH: null,
CLOSE_BOTTOM_SHEET: null,
CONFIG_CHANGED: null,
FETCHING_POSTS: null,

View File

@@ -0,0 +1,33 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import {useEffect, useState} from 'react';
import {DeviceEventEmitter} from 'react-native';
import {Events} from '@constants';
export const useChannelSwitch = () => {
const [loading, setLoading] = useState(false);
useEffect(() => {
let time: NodeJS.Timeout | undefined;
const l = DeviceEventEmitter.addListener(Events.CHANNEL_SWITCH, (switching: boolean) => {
if (time) {
clearTimeout(time);
}
if (switching) {
setLoading(true);
} else {
// eslint-disable-next-line max-nested-callbacks
time = setTimeout(() => setLoading(false), 0);
}
});
return () => {
l.remove();
if (time) {
clearTimeout(time);
}
};
}, []);
return loading;
};

View File

@@ -37,7 +37,10 @@ export const queryRolesByNames = (database: Database, names: string[]) => {
return database.get<RoleModel>(ROLE).query(Q.where('name', Q.oneOf(names)));
};
export function observePermissionForChannel(database: Database, channel: ChannelModel, user: UserModel, permission: string, defaultValue: boolean) {
export function observePermissionForChannel(database: Database, channel: ChannelModel | null | undefined, user: UserModel | undefined, permission: string, defaultValue: boolean) {
if (!user || !channel) {
return of$(defaultValue);
}
const myChannel = observeMyChannel(database, channel.id);
const myTeam = channel.teamId ? observeMyTeam(database, channel.teamId) : of$(undefined);
@@ -57,7 +60,11 @@ export function observePermissionForChannel(database: Database, channel: Channel
);
}
export function observePermissionForTeam(database: Database, team: TeamModel, user: UserModel, permission: string, defaultValue: boolean) {
export function observePermissionForTeam(database: Database, team: TeamModel | undefined, user: UserModel | undefined, permission: string, defaultValue: boolean) {
if (!team || !user) {
return of$(defaultValue);
}
return observeMyTeam(database, team.id).pipe(
switchMap((myTeam) => {
const rolesArray = [...user.roles.split(' ')];
@@ -74,9 +81,9 @@ export function observePermissionForTeam(database: Database, team: TeamModel, us
);
}
export function observePermissionForPost(database: Database, post: PostModel, user: UserModel, permission: string, defaultValue: boolean) {
export function observePermissionForPost(database: Database, post: PostModel, user: UserModel | undefined, permission: string, defaultValue: boolean) {
return observeChannel(database, post.channelId).pipe(
switchMap((c) => (c ? observePermissionForChannel(database, c, user, permission, defaultValue) : of$(defaultValue))),
switchMap((c) => observePermissionForChannel(database, c, user, permission, defaultValue)),
distinctUntilChanged(),
);
}

View File

@@ -10,6 +10,7 @@ import FreezeScreen from '@components/freeze_screen';
import PostDraft from '@components/post_draft';
import {Events} from '@constants';
import {ACCESSORIES_CONTAINER_NATIVE_ID} from '@constants/post_draft';
import {useChannelSwitch} from '@hooks/channel_switch';
import {useAppState, useIsTablet} from '@hooks/device';
import {useDefaultHeaderHeight} from '@hooks/header';
import {useTeamSwitch} from '@hooks/team_switch';
@@ -39,9 +40,12 @@ const Channel = ({channelId, componentId}: ChannelProps) => {
const insets = useSafeAreaInsets();
const [shouldRenderPosts, setShouldRenderPosts] = useState(false);
const switchingTeam = useTeamSwitch();
const switchingChannels = useChannelSwitch();
const defaultHeight = useDefaultHeaderHeight();
const postDraftRef = useRef<KeyboardTrackingViewRef>(null);
const shouldRender = !switchingTeam && !switchingChannels && shouldRenderPosts && Boolean(channelId);
useEffect(() => {
const listener = DeviceEventEmitter.addListener(Events.PAUSE_KEYBOARD_TRACKING_VIEW, (pause: boolean) => {
if (pause) {
@@ -99,8 +103,11 @@ const Channel = ({channelId, componentId}: ChannelProps) => {
edges={edges}
testID='channel.screen'
>
<ChannelHeader componentId={componentId}/>
{!switchingTeam && shouldRenderPosts && Boolean(channelId) &&
<ChannelHeader
channelId={channelId}
componentId={componentId}
/>
{shouldRender &&
<>
<View style={[styles.flex, {marginTop}]}>
<ChannelPostList

View File

@@ -8,7 +8,7 @@ import {combineLatestWith, switchMap} from 'rxjs/operators';
import {General} from '@constants';
import {observeChannel, observeChannelInfo} from '@queries/servers/channel';
import {observeCurrentChannelId, observeCurrentTeamId, observeCurrentUserId} from '@queries/servers/system';
import {observeCurrentTeamId, observeCurrentUserId} from '@queries/servers/system';
import {observeUser} from '@queries/servers/user';
import {getUserCustomStatus, getUserIdFromChannelName, isCustomStatusExpired as checkCustomStatusIsExpired} from '@utils/user';
@@ -16,19 +16,14 @@ import ChannelHeader from './header';
import type {WithDatabaseArgs} from '@typings/database/database';
const enhanced = withObservables([], ({database}: WithDatabaseArgs) => {
const enhanced = withObservables(['channelId'], ({channelId, database}: WithDatabaseArgs & {channelId: string}) => {
const currentUserId = observeCurrentUserId(database);
const channelId = observeCurrentChannelId(database);
const teamId = observeCurrentTeamId(database);
const channel = channelId.pipe(
switchMap((id) => observeChannel(database, id)),
);
const channel = observeChannel(database, channelId);
const channelType = channel.pipe(switchMap((c) => of$(c?.type)));
const channelInfo = channelId.pipe(
switchMap((id) => observeChannelInfo(database, id)),
);
const channelInfo = observeChannelInfo(database, channelId);
const dmUser = currentUserId.pipe(
combineLatestWith(channel),
@@ -74,7 +69,6 @@ const enhanced = withObservables([], ({database}: WithDatabaseArgs) => {
switchMap(([ci, dm]) => of$(dm ? undefined : ci?.memberCount)));
return {
channelId,
channelType,
customStatus,
displayName,

View File

@@ -21,15 +21,15 @@ const enhanced = withObservables([], ({database}: WithDatabaseArgs) => {
const currentUser = observeCurrentUser(database);
const canJoinChannels = combineLatest([currentUser, team]).pipe(
switchMap(([u, t]) => (t && u ? observePermissionForTeam(database, t, u, Permissions.JOIN_PUBLIC_CHANNELS, true) : of$(false))),
switchMap(([u, t]) => observePermissionForTeam(database, t, u, Permissions.JOIN_PUBLIC_CHANNELS, true)),
);
const canCreatePublicChannels = combineLatest([currentUser, team]).pipe(
switchMap(([u, t]) => (t && u ? observePermissionForTeam(database, t, u, Permissions.CREATE_PUBLIC_CHANNEL, true) : of$(false))),
switchMap(([u, t]) => observePermissionForTeam(database, t, u, Permissions.CREATE_PUBLIC_CHANNEL, true)),
);
const canCreatePrivateChannels = combineLatest([currentUser, team]).pipe(
switchMap(([u, t]) => (t && u ? observePermissionForTeam(database, t, u, Permissions.CREATE_PRIVATE_CHANNEL, false) : of$(false))),
switchMap(([u, t]) => observePermissionForTeam(database, t, u, Permissions.CREATE_PRIVATE_CHANNEL, false)),
);
const canCreateChannels = combineLatest([canCreatePublicChannels, canCreatePrivateChannels]).pipe(

View File

@@ -4,7 +4,7 @@
import {withDatabase} from '@nozbe/watermelondb/DatabaseProvider';
import withObservables from '@nozbe/with-observables';
import {combineLatest, of as of$} from 'rxjs';
import {switchMap, distinctUntilChanged} from 'rxjs/operators';
import {switchMap} from 'rxjs/operators';
import {Permissions} from '@constants';
import {observePermissionForTeam} from '@queries/servers/role';
@@ -22,18 +22,15 @@ const enhanced = withObservables([], ({database}: WithDatabaseArgs) => {
const currentUser = observeCurrentUser(database);
const canJoinChannels = combineLatest([currentUser, team]).pipe(
switchMap(([u, t]) => (t && u ? observePermissionForTeam(database, t, u, Permissions.JOIN_PUBLIC_CHANNELS, true) : of$(false))),
distinctUntilChanged(),
switchMap(([u, t]) => observePermissionForTeam(database, t, u, Permissions.JOIN_PUBLIC_CHANNELS, true)),
);
const canCreatePublicChannels = combineLatest([currentUser, team]).pipe(
switchMap(([u, t]) => (t && u ? observePermissionForTeam(database, t, u, Permissions.CREATE_PUBLIC_CHANNEL, true) : of$(false))),
distinctUntilChanged(),
switchMap(([u, t]) => observePermissionForTeam(database, t, u, Permissions.CREATE_PUBLIC_CHANNEL, true)),
);
const canCreatePrivateChannels = combineLatest([currentUser, team]).pipe(
switchMap(([u, t]) => (t && u ? observePermissionForTeam(database, t, u, Permissions.CREATE_PRIVATE_CHANNEL, false) : of$(false))),
distinctUntilChanged(),
switchMap(([u, t]) => observePermissionForTeam(database, t, u, Permissions.CREATE_PRIVATE_CHANNEL, false)),
);
const canCreateChannels = combineLatest([canCreatePublicChannels, canCreatePrivateChannels]).pipe(

View File

@@ -79,11 +79,11 @@ const enhanced = withObservables([], ({combinedPost, post, showAddReaction, loca
const serverVersion = config.pipe(switchMap((cfg) => of$(cfg?.Version || '')));
const postEditTimeLimit = config.pipe(switchMap((cfg) => of$(parseInt(cfg?.PostEditTimeLimit || '-1', 10))));
const canPostPermission = combineLatest([channel, currentUser]).pipe(switchMap(([c, u]) => ((c && u) ? observePermissionForChannel(database, c, u, Permissions.CREATE_POST, false) : of$(false))));
const hasAddReactionPermission = currentUser.pipe(switchMap((u) => (u ? observePermissionForPost(database, post, u, Permissions.ADD_REACTION, true) : of$(false))));
const canPostPermission = combineLatest([channel, currentUser]).pipe(switchMap(([c, u]) => observePermissionForChannel(database, c, u, Permissions.CREATE_POST, false)));
const hasAddReactionPermission = currentUser.pipe(switchMap((u) => observePermissionForPost(database, post, u, Permissions.ADD_REACTION, true)));
const canDeletePostPermission = currentUser.pipe(switchMap((u) => {
const isOwner = post.userId === u?.id;
return u ? observePermissionForPost(database, post, u, isOwner ? Permissions.DELETE_POST : Permissions.DELETE_OTHERS_POSTS, false) : of$(false);
return observePermissionForPost(database, post, u, isOwner ? Permissions.DELETE_POST : Permissions.DELETE_OTHERS_POSTS, false);
}));
const experimentalTownSquareIsReadOnly = config.pipe(switchMap((value) => of$(value?.ExperimentalTownSquareIsReadOnly === 'true')));