[Gekidou] Address performance improvements on team switch action (#6101)

This commit is contained in:
Daniel Espino García
2022-03-30 13:05:31 +02:00
committed by GitHub
parent a058eddb21
commit 9069048aa7
9 changed files with 194 additions and 208 deletions

View File

@@ -196,4 +196,4 @@ const ChannelIcon = ({
);
};
export default ChannelIcon;
export default React.memo(ChannelIcon);

View File

@@ -27,97 +27,93 @@ Object {
}
}
>
<RNGestureHandlerButton
<View
accessible={true}
collapsable={false}
exclusive={true}
handlerTag={1}
handlerType="NativeViewGestureHandler"
onGestureEvent={[Function]}
onGestureHandlerEvent={[Function]}
onGestureHandlerStateChange={[Function]}
onHandlerStateChange={[Function]}
rippleColor={0}
focusable={true}
onClick={[Function]}
onResponderGrant={[Function]}
onResponderMove={[Function]}
onResponderRelease={[Function]}
onResponderTerminate={[Function]}
onResponderTerminationRequest={[Function]}
onStartShouldSetResponder={[Function]}
style={
Object {
"opacity": 1,
}
}
>
<View
accessible={true}
collapsable={false}
style={
Object {
"opacity": 1,
"alignItems": "center",
"flexDirection": "row",
"height": 40,
"paddingLeft": 2,
}
}
>
<View
style={
Object {
"alignItems": "center",
"flexDirection": "row",
"height": 40,
"paddingLeft": 2,
}
Array [
Object {
"alignItems": "center",
"justifyContent": "center",
},
Object {
"height": 24,
"width": 24,
},
undefined,
undefined,
]
}
>
<View
<Icon
name="globe"
style={
Array [
Object {
"alignItems": "center",
"justifyContent": "center",
},
Object {
"height": 24,
"width": 24,
"color": "rgba(255,255,255,0.4)",
},
undefined,
undefined,
Object {
"fontSize": 24,
"left": 1,
},
]
}
>
<Icon
name="globe"
style={
Array [
Object {
"color": "rgba(255,255,255,0.4)",
},
undefined,
undefined,
Object {
"fontSize": 24,
"left": 1,
},
]
}
testID="undefined.public"
/>
</View>
<Text
ellipsizeMode="tail"
numberOfLines={1}
style={
Array [
Object {
"fontFamily": "OpenSans",
"fontSize": 16,
"fontWeight": "400",
"lineHeight": 24,
},
Object {
"color": "rgba(255,255,255,0.72)",
"flex": 1,
"marginTop": -1,
"paddingLeft": 12,
},
false,
false,
]
}
>
Channel
</Text>
testID="undefined.public"
/>
</View>
<Text
ellipsizeMode="tail"
numberOfLines={1}
style={
Array [
Object {
"fontFamily": "OpenSans",
"fontSize": 16,
"fontWeight": "400",
"lineHeight": 24,
},
Object {
"color": "rgba(255,255,255,0.72)",
"flex": 1,
"marginTop": -1,
"paddingLeft": 12,
},
false,
false,
]
}
>
Channel
</Text>
</View>
</RNGestureHandlerButton>
</View>
</View>
</View>
</View>,

View File

@@ -30,7 +30,7 @@ describe('components/channel_list/categories/body', () => {
category = categories[0];
});
it('should match snapshot', () => {
it('should match snapshot', (done) => {
const wrapper = renderWithEverything(
<CategoryBody
category={category}
@@ -40,8 +40,12 @@ describe('components/channel_list/categories/body', () => {
/>,
{database},
);
expect(wrapper.toJSON()).toMatchSnapshot({
props: {data: expect.anything()},
setTimeout(() => {
expect(wrapper.toJSON()).toMatchSnapshot({
props: {data: expect.anything()},
});
done();
});
});
});

View File

@@ -4,40 +4,42 @@
import React, {useCallback, useMemo} from 'react';
import {FlatList} from 'react-native';
import ChannelModel from '@typings/database/models/servers/channel';
import ChannelListItem from './channel';
import type CategoryModel from '@typings/database/models/servers/category';
type Props = {
currentChannelId: string;
sortedIds: string[];
sortedChannels: ChannelModel[];
hiddenChannelIds: string[];
category: CategoryModel;
limit: number;
};
const extractKey = (item: string) => item;
const extractKey = (item: ChannelModel) => item.id;
const CategoryBody = ({currentChannelId, sortedIds, category, hiddenChannelIds, limit}: Props) => {
const CategoryBody = ({currentChannelId, sortedChannels, category, hiddenChannelIds, limit}: Props) => {
const ids = useMemo(() => {
let filteredIds = sortedIds;
let filteredChannels = sortedChannels;
// Remove all closed gm/dms
if (hiddenChannelIds.length) {
filteredIds = sortedIds.filter((id) => !hiddenChannelIds.includes(id));
filteredChannels = sortedChannels.filter((item) => item && !hiddenChannelIds.includes(item.id));
}
if (category.type === 'direct_messages' && limit > 0) {
return filteredIds.slice(0, limit - 1);
return filteredChannels.slice(0, limit - 1);
}
return filteredIds;
}, [category.type, limit, hiddenChannelIds]);
return filteredChannels;
}, [category.type, limit, hiddenChannelIds, sortedChannels]);
const ChannelItem = useCallback(({item}: {item: string}) => {
const ChannelItem = useCallback(({item}: {item: ChannelModel}) => {
return (
<ChannelListItem
channelId={item}
isActive={item === currentChannelId}
channel={item}
isActive={item.id === currentChannelId}
collapsed={category.collapsed}
/>
);

View File

@@ -20,34 +20,47 @@ exports[`components/channel_list/categories/body/channel/item should match snaps
}
}
>
<RNGestureHandlerButton
<View
accessible={true}
collapsable={false}
exclusive={true}
handlerTag={1}
handlerType="NativeViewGestureHandler"
onGestureEvent={[Function]}
onGestureHandlerEvent={[Function]}
onGestureHandlerStateChange={[Function]}
onHandlerStateChange={[Function]}
rippleColor={0}
focusable={true}
onClick={[Function]}
onResponderGrant={[Function]}
onResponderMove={[Function]}
onResponderRelease={[Function]}
onResponderTerminate={[Function]}
onResponderTerminationRequest={[Function]}
onStartShouldSetResponder={[Function]}
style={
Object {
"opacity": 1,
}
}
>
<View
accessible={true}
collapsable={false}
style={
Object {
"opacity": 1,
"alignItems": "center",
"flexDirection": "row",
"height": 40,
"paddingLeft": 2,
}
}
>
<View
style={
Object {
"alignItems": "center",
"flexDirection": "row",
"height": 40,
"paddingLeft": 2,
}
Array [
Object {
"alignItems": "center",
"justifyContent": "center",
},
Object {
"height": 24,
"width": 24,
},
undefined,
undefined,
]
}
>
<View
@@ -55,84 +68,67 @@ exports[`components/channel_list/categories/body/channel/item should match snaps
Array [
Object {
"alignItems": "center",
"backgroundColor": "rgba(255,255,255,0.16)",
"borderRadius": 4,
"justifyContent": "center",
},
undefined,
undefined,
Object {
"height": 24,
"width": 24,
},
undefined,
undefined,
]
}
>
<View
<Text
style={
Array [
Object {
"alignItems": "center",
"backgroundColor": "rgba(255,255,255,0.16)",
"borderRadius": 4,
"justifyContent": "center",
"color": "#ffffff",
"fontFamily": "OpenSans-SemiBold",
"fontSize": 12,
"fontWeight": "600",
"lineHeight": 16,
},
undefined,
undefined,
Object {
"height": 24,
"width": 24,
"fontSize": 12,
},
]
}
testID="undefined.gm_member_count"
>
<Text
style={
Array [
Object {
"color": "#ffffff",
"fontFamily": "OpenSans-SemiBold",
"fontSize": 12,
"fontWeight": "600",
"lineHeight": 16,
},
undefined,
undefined,
Object {
"fontSize": 12,
},
]
}
testID="undefined.gm_member_count"
>
1
</Text>
</View>
1
</Text>
</View>
<Text
ellipsizeMode="tail"
numberOfLines={1}
style={
Array [
Object {
"fontFamily": "OpenSans",
"fontSize": 16,
"fontWeight": "400",
"lineHeight": 24,
},
Object {
"color": "rgba(255,255,255,0.72)",
"flex": 1,
"marginTop": -1,
"paddingLeft": 12,
},
false,
false,
]
}
>
Hello!
</Text>
</View>
<Text
ellipsizeMode="tail"
numberOfLines={1}
style={
Array [
Object {
"fontFamily": "OpenSans",
"fontSize": 16,
"fontWeight": "400",
"lineHeight": 24,
},
Object {
"color": "rgba(255,255,255,0.72)",
"flex": 1,
"marginTop": -1,
"paddingLeft": 12,
},
false,
false,
]
}
>
Hello!
</Text>
</View>
</RNGestureHandlerButton>
</View>
</View>
`;

View File

@@ -10,6 +10,7 @@ import TestHelper from '@test/test_helper';
import ChannelListItem from './channel_list_item';
import type ChannelModel from '@typings/database/models/servers/channel';
import type MyChannelModel from '@typings/database/models/servers/my_channel';
describe('components/channel_list/categories/body/channel/item', () => {
@@ -30,12 +31,12 @@ describe('components/channel_list/categories/body/channel/item', () => {
it('should match snapshot', () => {
const wrapper = renderWithIntlAndTheme(
<ChannelListItem
channel={{displayName: 'Hello!', type: 'G', shared: false, name: 'hello', deleteAt: 0}}
channel={{displayName: 'Hello!', type: 'G', shared: false, name: 'hello', deleteAt: 0} as ChannelModel}
isActive={false}
isOwnDirectMessage={false}
myChannel={myChannel}
isMuted={false}
collapsed={false}
currentUserId={'id'}
/>,
);

View File

@@ -1,10 +1,9 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import React, {useEffect, useMemo} from 'react';
import React, {useCallback, useEffect, useMemo} from 'react';
import {useIntl} from 'react-intl';
import {StyleSheet, Text, View} from 'react-native';
import {TouchableOpacity} from 'react-native-gesture-handler';
import {StyleSheet, Text, TouchableOpacity, View} from 'react-native';
import Animated, {Easing, useAnimatedStyle, useSharedValue, withTiming} from 'react-native-reanimated';
import {switchToChannelById} from '@actions/remote/channel';
@@ -15,6 +14,7 @@ import {useServerUrl} from '@context/server';
import {useTheme} from '@context/theme';
import {changeOpacity, makeStyleSheetFromTheme} from '@utils/theme';
import {typography} from '@utils/typography';
import {getUserIdFromChannelName} from '@utils/user';
import type ChannelModel from '@typings/database/models/servers/channel';
import type MyChannelModel from '@typings/database/models/servers/my_channel';
@@ -61,20 +61,22 @@ const textStyle = StyleSheet.create({
});
type Props = {
channel: Pick<ChannelModel, 'deleteAt' | 'displayName' | 'name' | 'shared' | 'type'>;
channel: ChannelModel;
isActive: boolean;
isOwnDirectMessage: boolean;
isMuted: boolean;
myChannel?: MyChannelModel;
collapsed: boolean;
currentUserId: string;
}
const ChannelListItem = ({channel, isActive, isOwnDirectMessage, isMuted, myChannel, collapsed}: Props) => {
const ChannelListItem = ({channel, isActive, currentUserId, isMuted, myChannel, collapsed}: Props) => {
const {formatMessage} = useIntl();
const theme = useTheme();
const styles = getStyleSheet(theme);
const serverUrl = useServerUrl();
const isOwnDirectMessage = (channel.type === General.DM_CHANNEL) && currentUserId === getUserIdFromChannelName(currentUserId, channel.name);
// Make it brighter if it's not muted, and highlighted or has unreads
const bright = !isMuted && (isActive || (myChannel && (myChannel.isUnread || myChannel.mentionsCount > 0)));
@@ -82,7 +84,7 @@ const ChannelListItem = ({channel, isActive, isOwnDirectMessage, isMuted, myChan
useEffect(() => {
sharedValue.value = collapsed && !bright;
}, [collapsed, bright]);
}, [collapsed && !bright]);
const animatedStyle = useAnimatedStyle(() => {
return {
@@ -92,14 +94,14 @@ const ChannelListItem = ({channel, isActive, isOwnDirectMessage, isMuted, myChan
};
});
const switchChannels = () => {
const switchChannels = useCallback(() => {
if (myChannel) {
switchToChannelById(serverUrl, myChannel.id);
}
};
}, [myChannel?.id, serverUrl]);
const membersCount = useMemo(() => {
if (channel.type === General.GM_CHANNEL) {
return channel.displayName?.split(',').length;
return channel.displayName.split(',').length;
}
return 0;
}, [channel.type, channel.displayName]);

View File

@@ -3,50 +3,30 @@
import {withDatabase} from '@nozbe/watermelondb/DatabaseProvider';
import withObservables from '@nozbe/with-observables';
import {combineLatest, of as of$} from 'rxjs';
import {of as of$} from 'rxjs';
import {switchMap} from 'rxjs/operators';
import {General} from '@constants';
import {observeMyChannel} from '@queries/servers/channel';
import {observeCurrentUserId} from '@queries/servers/system';
import {getUserIdFromChannelName} from '@utils/user';
import ChannelModel from '@typings/database/models/servers/channel';
import ChannelListItem from './channel_list_item';
import type {WithDatabaseArgs} from '@typings/database/database';
const enhance = withObservables(['channelId'], ({channelId, database}: {channelId: string} & WithDatabaseArgs) => {
const myChannel = observeMyChannel(database, channelId);
const enhance = withObservables(['channel'], ({channel, database}: {channel: ChannelModel} & WithDatabaseArgs) => {
const myChannel = observeMyChannel(database, channel.id);
const currentUserId = observeCurrentUserId(database);
const channel = myChannel.pipe(switchMap((my) => (my ? my.channel.observe() : of$(undefined))));
const settings = channel.pipe(switchMap((c) => (c ? c.settings.observe() : of$(undefined))));
const settings = channel.settings.observe();
const isOwnDirectMessage = combineLatest([currentUserId, channel]).pipe(
switchMap(([userId, ch]) => {
if (ch?.type === General.DM_CHANNEL) {
const teammateId = getUserIdFromChannelName(userId, ch.name);
return of$(userId === teammateId);
}
return of$(false);
}),
);
return {
isOwnDirectMessage,
currentUserId,
isMuted: settings.pipe(
switchMap((s) => of$(s?.notifyProps?.mark_unread === 'mention')),
),
myChannel,
channel: channel.pipe(
switchMap((c) => of$({
deleteAt: c?.deleteAt || 0,
displayName: c?.displayName || '',
name: c?.name || '',
shared: c?.shared || false,
type: c?.type || '',
})),
),
channel: channel.observe(),
};
});

View File

@@ -5,7 +5,7 @@ import {Database} from '@nozbe/watermelondb';
import {withDatabase} from '@nozbe/watermelondb/DatabaseProvider';
import withObservables from '@nozbe/with-observables';
import {combineLatest, of as of$} from 'rxjs';
import {switchMap} from 'rxjs/operators';
import {map, switchMap, concatAll} from 'rxjs/operators';
import {General, Preferences} from '@constants';
import {queryChannelsByNames, queryMyChannelSettingsByIds} from '@queries/servers/channel';
@@ -16,7 +16,9 @@ import {getDirectChannelName} from '@utils/channel';
import CategoryBody from './category_body';
import type CategoryModel from '@typings/database/models/servers/category';
import type CategoryChannelModel from '@typings/database/models/servers/category_channel';
import type ChannelModel from '@typings/database/models/servers/channel';
import type MyChannelModel from '@typings/database/models/servers/my_channel';
import type MyChannelSettingsModel from '@typings/database/models/servers/my_channel_settings';
import type PreferenceModel from '@typings/database/models/servers/preference';
@@ -45,8 +47,7 @@ const buildAlphaData = (channels: ChannelModel[], settings: MyChannelSettingsMod
});
combined.sort(sortAlpha.bind(null, locale));
return of$(combined.map((c) => c.id));
return of$(combined.map((cdata) => channels.find((c) => c.id === cdata.id)));
};
const observeSettings = (database: Database, channels: ChannelModel[]) => {
@@ -54,7 +55,11 @@ const observeSettings = (database: Database, channels: ChannelModel[]) => {
return queryMyChannelSettingsByIds(database, ids).observeWithColumns(['notify_props']);
};
const getSortedIds = (database: Database, category: CategoryModel, locale: string) => {
const getChannelsFromRelation = async (relations: CategoryChannelModel[] | MyChannelModel[]) => {
return Promise.all(relations.map((r) => r.channel?.fetch()));
};
const getSortedChannels = (database: Database, category: CategoryModel, locale: string) => {
switch (category.sorting) {
case 'alpha': {
const channels = category.channels.observeWithColumns(['display_name']);
@@ -67,14 +72,14 @@ const getSortedIds = (database: Database, category: CategoryModel, locale: strin
}
case 'manual': {
return category.categoryChannelsBySortOrder.observeWithColumns(['sort_order']).pipe(
// eslint-disable-next-line max-nested-callbacks
switchMap((cc) => of$(cc.map((c) => c.channelId))),
map(getChannelsFromRelation),
concatAll(),
);
}
default:
return category.myChannels.observeWithColumns(['last_post_at']).pipe(
// eslint-disable-next-line max-nested-callbacks
switchMap((mc) => of$(mc.map((m) => m.id))),
map(getChannelsFromRelation),
concatAll(),
);
}
};
@@ -87,8 +92,8 @@ type EnhanceProps = {category: CategoryModel; locale: string; currentUserId: str
const enhance = withObservables(['category'], ({category, locale, database, currentUserId}: EnhanceProps) => {
const observedCategory = category.observe();
const sortedIds = observedCategory.pipe(
switchMap((c) => getSortedIds(database, c, locale)),
const sortedChannels = observedCategory.pipe(
switchMap((c) => getSortedChannels(database, c, locale)),
);
const dmMap = (p: PreferenceModel) => getDirectChannelName(p.name, currentUserId);
@@ -124,7 +129,7 @@ const enhance = withObservables(['category'], ({category, locale, database, curr
return {
limit,
hiddenChannelIds,
sortedIds,
sortedChannels,
category: observedCategory,
};
});