diff --git a/app/components/channel_icon/index.tsx b/app/components/channel_icon/index.tsx index 3d37f8f303..04aaed4eb4 100644 --- a/app/components/channel_icon/index.tsx +++ b/app/components/channel_icon/index.tsx @@ -196,4 +196,4 @@ const ChannelIcon = ({ ); }; -export default ChannelIcon; +export default React.memo(ChannelIcon); diff --git a/app/components/channel_list/categories/body/__snapshots__/category_body.test.tsx.snap b/app/components/channel_list/categories/body/__snapshots__/category_body.test.tsx.snap index ebb430c566..0a23da2e35 100644 --- a/app/components/channel_list/categories/body/__snapshots__/category_body.test.tsx.snap +++ b/app/components/channel_list/categories/body/__snapshots__/category_body.test.tsx.snap @@ -27,97 +27,93 @@ Object { } } > - - - - - - Channel - + testID="undefined.public" + /> + + Channel + - + , diff --git a/app/components/channel_list/categories/body/category_body.test.tsx b/app/components/channel_list/categories/body/category_body.test.tsx index 3a32e3bf20..cd356efbb8 100644 --- a/app/components/channel_list/categories/body/category_body.test.tsx +++ b/app/components/channel_list/categories/body/category_body.test.tsx @@ -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( { />, {database}, ); - expect(wrapper.toJSON()).toMatchSnapshot({ - props: {data: expect.anything()}, + + setTimeout(() => { + expect(wrapper.toJSON()).toMatchSnapshot({ + props: {data: expect.anything()}, + }); + done(); }); }); }); diff --git a/app/components/channel_list/categories/body/category_body.tsx b/app/components/channel_list/categories/body/category_body.tsx index 2961cf21fe..ac7fb2f739 100644 --- a/app/components/channel_list/categories/body/category_body.tsx +++ b/app/components/channel_list/categories/body/category_body.tsx @@ -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 ( ); diff --git a/app/components/channel_list/categories/body/channel/__snapshots__/channel_list_item.test.tsx.snap b/app/components/channel_list/categories/body/channel/__snapshots__/channel_list_item.test.tsx.snap index 21c182dc8e..3a0156ef24 100644 --- a/app/components/channel_list/categories/body/channel/__snapshots__/channel_list_item.test.tsx.snap +++ b/app/components/channel_list/categories/body/channel/__snapshots__/channel_list_item.test.tsx.snap @@ -20,34 +20,47 @@ exports[`components/channel_list/categories/body/channel/item should match snaps } } > - - - - 1 - - + 1 + - - Hello! - + + Hello! + - + `; diff --git a/app/components/channel_list/categories/body/channel/channel_list_item.test.tsx b/app/components/channel_list/categories/body/channel/channel_list_item.test.tsx index 38e2972845..e6a1764f22 100644 --- a/app/components/channel_list/categories/body/channel/channel_list_item.test.tsx +++ b/app/components/channel_list/categories/body/channel/channel_list_item.test.tsx @@ -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( , ); diff --git a/app/components/channel_list/categories/body/channel/channel_list_item.tsx b/app/components/channel_list/categories/body/channel/channel_list_item.tsx index 230fc9fc29..4642418299 100644 --- a/app/components/channel_list/categories/body/channel/channel_list_item.tsx +++ b/app/components/channel_list/categories/body/channel/channel_list_item.tsx @@ -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; + 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]); diff --git a/app/components/channel_list/categories/body/channel/index.ts b/app/components/channel_list/categories/body/channel/index.ts index 4aeca82c5f..13301b2be1 100644 --- a/app/components/channel_list/categories/body/channel/index.ts +++ b/app/components/channel_list/categories/body/channel/index.ts @@ -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(), }; }); diff --git a/app/components/channel_list/categories/body/index.ts b/app/components/channel_list/categories/body/index.ts index 578e21a8b6..dc94303f3c 100644 --- a/app/components/channel_list/categories/body/index.ts +++ b/app/components/channel_list/categories/body/index.ts @@ -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, }; });