Improvements on team switch performance (#6255)
* Improvements on channel switch performance * Revert removal from channel observables * Fix team switch dependancies * Fix lint * Use events to signal channel switch * Add check for hasMembership * Address feedback * add useTeamSwitch hook * Fix team switch perceived performance on tablets * align custom status in channel list Co-authored-by: Elias Nahum <nahumhbl@gmail.com>
This commit is contained in:
parent
ba976dadc2
commit
67461322a2
26 changed files with 197 additions and 86 deletions
|
|
@ -269,6 +269,7 @@ export async function handleTeamChange(serverUrl: string, teamId: string) {
|
|||
}
|
||||
|
||||
let channelId = '';
|
||||
DeviceEventEmitter.emit(Events.TEAM_SWITCH, true);
|
||||
if (await isTablet()) {
|
||||
channelId = await getNthLastChannelFromTeam(database, teamId);
|
||||
if (channelId) {
|
||||
|
|
@ -277,6 +278,7 @@ export async function handleTeamChange(serverUrl: string, teamId: string) {
|
|||
} else {
|
||||
await switchToChannelById(serverUrl, channelId, teamId);
|
||||
}
|
||||
DeviceEventEmitter.emit(Events.TEAM_SWITCH, false);
|
||||
completeTeamChange(serverUrl, teamId, channelId);
|
||||
return;
|
||||
}
|
||||
|
|
@ -295,6 +297,7 @@ export async function handleTeamChange(serverUrl: string, teamId: string) {
|
|||
if (models.length) {
|
||||
await operator.batchRecords(models);
|
||||
}
|
||||
DeviceEventEmitter.emit(Events.TEAM_SWITCH, false);
|
||||
|
||||
completeTeamChange(serverUrl, teamId, channelId);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -35,11 +35,13 @@ describe('components/channel_list/categories/body/channel_item', () => {
|
|||
hasDraft={false}
|
||||
isActive={false}
|
||||
membersCount={0}
|
||||
myChannel={myChannel}
|
||||
isMuted={false}
|
||||
currentUserId={'id'}
|
||||
testID='channel_list_item'
|
||||
onPress={() => undefined}
|
||||
isUnread={myChannel.isUnread}
|
||||
mentionsCount={myChannel.mentionsCount}
|
||||
hasMember={Boolean(myChannel)}
|
||||
/>,
|
||||
);
|
||||
|
||||
|
|
@ -53,11 +55,13 @@ describe('components/channel_list/categories/body/channel_item', () => {
|
|||
hasDraft={true}
|
||||
isActive={false}
|
||||
membersCount={3}
|
||||
myChannel={myChannel}
|
||||
isMuted={false}
|
||||
currentUserId={'id'}
|
||||
testID='channel_list_item'
|
||||
onPress={() => undefined}
|
||||
isUnread={myChannel.isUnread}
|
||||
mentionsCount={myChannel.mentionsCount}
|
||||
hasMember={Boolean(myChannel)}
|
||||
/>,
|
||||
);
|
||||
|
||||
|
|
|
|||
|
|
@ -17,7 +17,6 @@ import {getUserIdFromChannelName} from '@utils/user';
|
|||
import CustomStatus from './custom_status';
|
||||
|
||||
import type ChannelModel from '@typings/database/models/servers/channel';
|
||||
import type MyChannelModel from '@typings/database/models/servers/my_channel';
|
||||
|
||||
type Props = {
|
||||
channel: ChannelModel;
|
||||
|
|
@ -27,8 +26,10 @@ type Props = {
|
|||
isInfo?: boolean;
|
||||
isMuted: boolean;
|
||||
membersCount: number;
|
||||
myChannel?: MyChannelModel;
|
||||
isUnread: boolean;
|
||||
mentionsCount: number;
|
||||
onPress: (channelId: string) => void;
|
||||
hasMember: boolean;
|
||||
teamDisplayName?: string;
|
||||
testID?: string;
|
||||
}
|
||||
|
|
@ -114,15 +115,15 @@ export const textStyle = StyleSheet.create({
|
|||
|
||||
const ChannelListItem = ({
|
||||
channel, currentUserId, hasDraft,
|
||||
isActive, isInfo, isMuted, membersCount,
|
||||
myChannel, onPress, teamDisplayName, testID}: Props) => {
|
||||
isActive, isInfo, isMuted, membersCount, hasMember,
|
||||
isUnread, mentionsCount, onPress, teamDisplayName, testID}: Props) => {
|
||||
const {formatMessage} = useIntl();
|
||||
const theme = useTheme();
|
||||
const isTablet = useIsTablet();
|
||||
const styles = getStyleSheet(theme);
|
||||
|
||||
// Make it brighter if it's not muted, and highlighted or has unreads
|
||||
const isBright = myChannel && (myChannel.isUnread || myChannel.mentionsCount > 0);
|
||||
const isBright = isUnread || mentionsCount > 0;
|
||||
|
||||
const height = useMemo(() => {
|
||||
let h = 40;
|
||||
|
|
@ -133,8 +134,8 @@ const ChannelListItem = ({
|
|||
}, [teamDisplayName, isInfo, isTablet]);
|
||||
|
||||
const handleOnPress = useCallback(() => {
|
||||
onPress(myChannel?.id || channel.id);
|
||||
}, [channel.id, myChannel?.id]);
|
||||
onPress(channel.id);
|
||||
}, [channel.id]);
|
||||
|
||||
const textStyles = useMemo(() => [
|
||||
isBright ? textStyle.bright : textStyle.regular,
|
||||
|
|
@ -153,7 +154,7 @@ const ChannelListItem = ({
|
|||
],
|
||||
[height, isActive, isInfo, styles]);
|
||||
|
||||
if (!myChannel) {
|
||||
if (!hasMember) {
|
||||
return null;
|
||||
}
|
||||
|
||||
|
|
@ -196,14 +197,14 @@ const ChannelListItem = ({
|
|||
{displayName}
|
||||
</Text>
|
||||
{isInfo && Boolean(teamDisplayName) && !isTablet &&
|
||||
<Text
|
||||
ellipsizeMode='tail'
|
||||
numberOfLines={1}
|
||||
testID={`${testID}.${teamDisplayName}.display_name`}
|
||||
style={styles.teamName}
|
||||
>
|
||||
{teamDisplayName}
|
||||
</Text>
|
||||
<Text
|
||||
ellipsizeMode='tail'
|
||||
numberOfLines={1}
|
||||
testID={`${testID}.${teamDisplayName}.display_name`}
|
||||
style={styles.teamName}
|
||||
>
|
||||
{teamDisplayName}
|
||||
</Text>
|
||||
}
|
||||
</View>
|
||||
{Boolean(teammateId) &&
|
||||
|
|
@ -224,8 +225,8 @@ const ChannelListItem = ({
|
|||
}
|
||||
</View>
|
||||
<Badge
|
||||
visible={myChannel.mentionsCount > 0}
|
||||
value={myChannel.mentionsCount}
|
||||
visible={mentionsCount > 0}
|
||||
value={mentionsCount}
|
||||
style={[styles.badge, isMuted && styles.mutedBadge, isInfo && styles.infoBadge]}
|
||||
/>
|
||||
</View>
|
||||
|
|
|
|||
|
|
@ -16,7 +16,7 @@ type Props = {
|
|||
const style = StyleSheet.create({
|
||||
customStatusEmoji: {
|
||||
color: '#000',
|
||||
marginHorizontal: 5,
|
||||
marginHorizontal: -5,
|
||||
top: 3,
|
||||
},
|
||||
info: {
|
||||
|
|
|
|||
|
|
@ -32,9 +32,13 @@ const enhance = withObservables(['channel', 'showTeamName', 'isCategoryMuted'],
|
|||
|
||||
const hasDraft = queryDraft(database, channel.id).observeWithColumns(['message', 'files']).pipe(
|
||||
switchMap((draft) => of$(draft.length > 0)),
|
||||
distinctUntilChanged(),
|
||||
);
|
||||
|
||||
const isActive = observeCurrentChannelId(database).pipe(switchMap((id) => of$(id ? id === channel.id : false)), distinctUntilChanged());
|
||||
const isActive = observeCurrentChannelId(database).pipe(
|
||||
switchMap((id) => of$(id ? id === channel.id : false)),
|
||||
distinctUntilChanged(),
|
||||
);
|
||||
|
||||
const isMuted = myChannel.pipe(
|
||||
switchMap((mc) => {
|
||||
|
|
@ -50,6 +54,7 @@ const enhance = withObservables(['channel', 'showTeamName', 'isCategoryMuted'],
|
|||
if (channel.teamId && showTeamName) {
|
||||
teamDisplayName = channel.team.observe().pipe(
|
||||
switchMap((team) => of$(team?.displayName || '')),
|
||||
distinctUntilChanged(),
|
||||
);
|
||||
}
|
||||
|
||||
|
|
@ -58,6 +63,21 @@ const enhance = withObservables(['channel', 'showTeamName', 'isCategoryMuted'],
|
|||
membersCount = channel.members.observeCount();
|
||||
}
|
||||
|
||||
const isUnread = myChannel.pipe(
|
||||
switchMap((mc) => of$(mc?.isUnread)),
|
||||
distinctUntilChanged(),
|
||||
);
|
||||
|
||||
const mentionsCount = myChannel.pipe(
|
||||
switchMap((mc) => of$(mc?.mentionsCount)),
|
||||
distinctUntilChanged(),
|
||||
);
|
||||
|
||||
const hasMember = myChannel.pipe(
|
||||
switchMap((mc) => of$(Boolean(mc))),
|
||||
distinctUntilChanged(),
|
||||
);
|
||||
|
||||
return {
|
||||
channel: channel.observe(),
|
||||
currentUserId,
|
||||
|
|
@ -65,8 +85,10 @@ const enhance = withObservables(['channel', 'showTeamName', 'isCategoryMuted'],
|
|||
isActive,
|
||||
isMuted,
|
||||
membersCount,
|
||||
myChannel,
|
||||
isUnread,
|
||||
mentionsCount,
|
||||
teamDisplayName,
|
||||
hasMember,
|
||||
};
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -3,7 +3,8 @@
|
|||
|
||||
import {withDatabase} from '@nozbe/watermelondb/DatabaseProvider';
|
||||
import withObservables from '@nozbe/with-observables';
|
||||
import {combineLatestWith, map} from 'rxjs/operators';
|
||||
import {of as of$} from 'rxjs';
|
||||
import {combineLatestWith, map, switchMap, distinctUntilChanged} from 'rxjs/operators';
|
||||
|
||||
import {observeAllMyChannelNotifyProps, queryMyChannelsByTeam} from '@queries/servers/channel';
|
||||
import {observeCurrentTeamId} from '@queries/servers/system';
|
||||
|
|
@ -30,8 +31,13 @@ const enhance = withObservables(['myTeam'], ({myTeam, database}: WithTeamsArgs)
|
|||
}, false)),
|
||||
);
|
||||
|
||||
const selected = observeCurrentTeamId(database).pipe(
|
||||
switchMap((ctid) => of$(ctid === myTeam.id)),
|
||||
distinctUntilChanged(),
|
||||
);
|
||||
|
||||
return {
|
||||
currentTeamId: observeCurrentTeamId(database),
|
||||
selected,
|
||||
team: myTeam.team.observe(),
|
||||
mentionCount: observeMentionCount(database, myTeam.id, false),
|
||||
hasUnreads,
|
||||
|
|
|
|||
|
|
@ -19,7 +19,7 @@ type Props = {
|
|||
team: TeamModel;
|
||||
hasUnreads: boolean;
|
||||
mentionCount: number;
|
||||
currentTeamId: string;
|
||||
selected: boolean;
|
||||
}
|
||||
|
||||
const getStyleSheet = makeStyleSheetFromTheme((theme: Theme) => {
|
||||
|
|
@ -57,11 +57,10 @@ const getStyleSheet = makeStyleSheetFromTheme((theme: Theme) => {
|
|||
};
|
||||
});
|
||||
|
||||
export default function TeamItem({team, hasUnreads, mentionCount, currentTeamId}: Props) {
|
||||
export default function TeamItem({team, hasUnreads, mentionCount, selected}: Props) {
|
||||
const theme = useTheme();
|
||||
const styles = getStyleSheet(theme);
|
||||
const serverUrl = useServerUrl();
|
||||
const selected = team.id === currentTeamId;
|
||||
|
||||
const hasBadge = Boolean(mentionCount || hasUnreads);
|
||||
let badgeStyle = styles.unread;
|
||||
|
|
|
|||
|
|
@ -19,6 +19,7 @@ export default keyMirror({
|
|||
SERVER_VERSION_CHANGED: null,
|
||||
TAB_BAR_VISIBLE: null,
|
||||
TEAM_LOAD_ERROR: null,
|
||||
TEAM_SWITCH: null,
|
||||
USER_TYPING: null,
|
||||
USER_STOP_TYPING: null,
|
||||
POST_LIST_SCROLL_TO_BOTTOM: null,
|
||||
|
|
|
|||
33
app/hooks/team_switch.ts
Normal file
33
app/hooks/team_switch.ts
Normal 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 useTeamSwitch = () => {
|
||||
const [loading, setLoading] = useState(false);
|
||||
useEffect(() => {
|
||||
let time: NodeJS.Timeout | undefined;
|
||||
const l = DeviceEventEmitter.addListener(Events.TEAM_SWITCH, (switching: boolean) => {
|
||||
if (time) {
|
||||
clearTimeout(time);
|
||||
}
|
||||
if (switching) {
|
||||
setLoading(true);
|
||||
} else {
|
||||
// eslint-disable-next-line max-nested-callbacks
|
||||
time = setTimeout(() => setLoading(false), 200);
|
||||
}
|
||||
});
|
||||
return () => {
|
||||
l.remove();
|
||||
if (time) {
|
||||
clearTimeout(time);
|
||||
}
|
||||
};
|
||||
}, []);
|
||||
|
||||
return loading;
|
||||
};
|
||||
|
|
@ -174,6 +174,12 @@ export const queryAllMyChannel = (database: Database) => {
|
|||
return database.get<MyChannelModel>(MY_CHANNEL).query();
|
||||
};
|
||||
|
||||
export const queryAllMyChannelsForTeam = (database: Database, teamId: string) => {
|
||||
return database.get<ChannelModel>(MY_CHANNEL).query(
|
||||
Q.on(CHANNEL, Q.where('team_id', Q.oneOf([teamId, '']))),
|
||||
);
|
||||
};
|
||||
|
||||
export const getMyChannel = async (database: Database, channelId: string) => {
|
||||
try {
|
||||
const member = await database.get<MyChannelModel>(MY_CHANNEL).find(channelId);
|
||||
|
|
|
|||
|
|
@ -12,6 +12,7 @@ import {Events} from '@constants';
|
|||
import {ACCESSORIES_CONTAINER_NATIVE_ID} from '@constants/post_draft';
|
||||
import {useAppState, useIsTablet} from '@hooks/device';
|
||||
import {useDefaultHeaderHeight} from '@hooks/header';
|
||||
import {useTeamSwitch} from '@hooks/team_switch';
|
||||
|
||||
import ChannelPostList from './channel_post_list';
|
||||
import ChannelHeader from './header';
|
||||
|
|
@ -34,6 +35,7 @@ const Channel = ({channelId, componentId}: ChannelProps) => {
|
|||
const isTablet = useIsTablet();
|
||||
const insets = useSafeAreaInsets();
|
||||
const [shouldRenderPosts, setShouldRenderPosts] = useState(false);
|
||||
const switchingTeam = useTeamSwitch();
|
||||
const defaultHeight = useDefaultHeaderHeight();
|
||||
const postDraftRef = useRef<KeyboardTrackingViewRef>(null);
|
||||
|
||||
|
|
@ -70,7 +72,7 @@ const Channel = ({channelId, componentId}: ChannelProps) => {
|
|||
testID='channel.screen'
|
||||
>
|
||||
<ChannelHeader componentId={componentId}/>
|
||||
{shouldRenderPosts && Boolean(channelId) &&
|
||||
{!switchingTeam && shouldRenderPosts && Boolean(channelId) &&
|
||||
<>
|
||||
<View style={[styles.flex, {marginTop}]}>
|
||||
<ChannelPostList
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ import {Edge, SafeAreaView, useSafeAreaInsets} from 'react-native-safe-area-cont
|
|||
import NavigationHeader from '@components/navigation_header';
|
||||
import {useAppState, useIsTablet} from '@hooks/device';
|
||||
import {useDefaultHeaderHeight} from '@hooks/header';
|
||||
import {useTeamSwitch} from '@hooks/team_switch';
|
||||
import {popTopScreen} from '@screens/navigation';
|
||||
|
||||
import ThreadsList from './threads_list';
|
||||
|
|
@ -29,6 +30,7 @@ const GlobalThreads = ({componentId}: Props) => {
|
|||
const appState = useAppState();
|
||||
const intl = useIntl();
|
||||
const insets = useSafeAreaInsets();
|
||||
const switchingTeam = useTeamSwitch();
|
||||
const isTablet = useIsTablet();
|
||||
|
||||
const defaultHeight = useDefaultHeaderHeight();
|
||||
|
|
@ -63,6 +65,7 @@ const GlobalThreads = ({componentId}: Props) => {
|
|||
})
|
||||
}
|
||||
/>
|
||||
{!switchingTeam &&
|
||||
<View style={containerStyle}>
|
||||
<ThreadsList
|
||||
forceQueryAfterAppState={appState}
|
||||
|
|
@ -71,6 +74,7 @@ const GlobalThreads = ({componentId}: Props) => {
|
|||
testID={'global_threads.threads_list'}
|
||||
/>
|
||||
</View>
|
||||
}
|
||||
</SafeAreaView>
|
||||
);
|
||||
};
|
||||
|
|
|
|||
|
|
@ -14,6 +14,7 @@ type SelectedView = {
|
|||
}
|
||||
|
||||
type Props = {
|
||||
onTeam: boolean;
|
||||
currentChannelId: string;
|
||||
isCRTEnabled: boolean;
|
||||
}
|
||||
|
|
@ -26,7 +27,7 @@ const ComponentsList: Record<string, React.ReactNode> = {
|
|||
const channelScreen: SelectedView = {id: Screens.CHANNEL, Component: Channel};
|
||||
const globalScreen: SelectedView = {id: Screens.GLOBAL_THREADS, Component: GlobalThreads};
|
||||
|
||||
const AdditionalTabletView = ({currentChannelId, isCRTEnabled}: Props) => {
|
||||
const AdditionalTabletView = ({onTeam, currentChannelId, isCRTEnabled}: Props) => {
|
||||
const [selected, setSelected] = useState<SelectedView>(isCRTEnabled && !currentChannelId ? globalScreen : channelScreen);
|
||||
|
||||
useEffect(() => {
|
||||
|
|
@ -43,7 +44,7 @@ const AdditionalTabletView = ({currentChannelId, isCRTEnabled}: Props) => {
|
|||
return () => listener.remove();
|
||||
}, []);
|
||||
|
||||
if (!selected) {
|
||||
if (!selected || !onTeam) {
|
||||
return null;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -3,8 +3,10 @@
|
|||
|
||||
import {withDatabase} from '@nozbe/watermelondb/DatabaseProvider';
|
||||
import withObservables from '@nozbe/with-observables';
|
||||
import {of as of$} from 'rxjs';
|
||||
import {switchMap, distinctUntilChanged} from 'rxjs/operators';
|
||||
|
||||
import {observeCurrentChannelId} from '@queries/servers/system';
|
||||
import {observeCurrentChannelId, observeCurrentTeamId} from '@queries/servers/system';
|
||||
import {observeIsCRTEnabled} from '@queries/servers/thread';
|
||||
|
||||
import AdditionalTabletView from './additional_tablet_view';
|
||||
|
|
@ -12,6 +14,10 @@ import AdditionalTabletView from './additional_tablet_view';
|
|||
import type {WithDatabaseArgs} from '@typings/database/database';
|
||||
|
||||
const enhanced = withObservables([], ({database}: WithDatabaseArgs) => ({
|
||||
onTeam: observeCurrentTeamId(database).pipe(
|
||||
switchMap((id) => of$(Boolean(id))),
|
||||
distinctUntilChanged(),
|
||||
),
|
||||
currentChannelId: observeCurrentChannelId(database),
|
||||
isCRTEnabled: observeIsCRTEnabled(database),
|
||||
}));
|
||||
|
|
|
|||
|
|
@ -18,7 +18,7 @@ describe('components/channel_list/categories', () => {
|
|||
|
||||
it('render without error', () => {
|
||||
const wrapper = renderWithEverything(
|
||||
<Categories currentTeamId={TestHelper.basicTeam!.id}/>,
|
||||
<Categories/>,
|
||||
{database},
|
||||
);
|
||||
|
||||
|
|
|
|||
|
|
@ -1,13 +1,15 @@
|
|||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import React, {useCallback, useEffect, useMemo, useRef} from 'react';
|
||||
import React, {useCallback, useMemo, useRef} from 'react';
|
||||
import {useIntl} from 'react-intl';
|
||||
import {FlatList, StyleSheet} from 'react-native';
|
||||
import {FlatList, StyleSheet, View} from 'react-native';
|
||||
|
||||
import {switchToChannelById} from '@actions/remote/channel';
|
||||
import Loading from '@components/loading';
|
||||
import {useServerUrl} from '@context/server';
|
||||
import {useIsTablet} from '@hooks/device';
|
||||
import {useTeamSwitch} from '@hooks/team_switch';
|
||||
|
||||
import CategoryBody from './body';
|
||||
import LoadCategoriesError from './error';
|
||||
|
|
@ -18,7 +20,6 @@ import type CategoryModel from '@typings/database/models/servers/category';
|
|||
|
||||
type Props = {
|
||||
categories: CategoryModel[];
|
||||
currentTeamId: string;
|
||||
onlyUnreads: boolean;
|
||||
unreadsOnTop: boolean;
|
||||
}
|
||||
|
|
@ -29,15 +30,27 @@ const styles = StyleSheet.create({
|
|||
marginLeft: -18,
|
||||
marginRight: -20,
|
||||
},
|
||||
loadingView: {
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
flex: 1,
|
||||
},
|
||||
loading: {
|
||||
justifyContent: 'center',
|
||||
height: 32,
|
||||
width: 32,
|
||||
},
|
||||
});
|
||||
|
||||
const extractKey = (item: CategoryModel | 'UNREADS') => (item === 'UNREADS' ? 'UNREADS' : item.id);
|
||||
|
||||
const Categories = ({categories, currentTeamId, onlyUnreads, unreadsOnTop}: Props) => {
|
||||
const Categories = ({categories, onlyUnreads, unreadsOnTop}: Props) => {
|
||||
const intl = useIntl();
|
||||
const listRef = useRef<FlatList>(null);
|
||||
const serverUrl = useServerUrl();
|
||||
const isTablet = useIsTablet();
|
||||
const switchingTeam = useTeamSwitch();
|
||||
const teamId = categories[0]?.teamId;
|
||||
|
||||
const onChannelSwitch = useCallback(async (channelId: string) => {
|
||||
switchToChannelById(serverUrl, channelId);
|
||||
|
|
@ -47,7 +60,7 @@ const Categories = ({categories, currentTeamId, onlyUnreads, unreadsOnTop}: Prop
|
|||
if (data.item === 'UNREADS') {
|
||||
return (
|
||||
<UnreadCategories
|
||||
currentTeamId={currentTeamId}
|
||||
currentTeamId={teamId}
|
||||
isTablet={isTablet}
|
||||
onChannelSwitch={onChannelSwitch}
|
||||
onlyUnreads={onlyUnreads}
|
||||
|
|
@ -65,11 +78,7 @@ const Categories = ({categories, currentTeamId, onlyUnreads, unreadsOnTop}: Prop
|
|||
/>
|
||||
</>
|
||||
);
|
||||
}, [currentTeamId, intl.locale, isTablet, onChannelSwitch, onlyUnreads]);
|
||||
|
||||
useEffect(() => {
|
||||
listRef.current?.scrollToOffset({animated: false, offset: 0});
|
||||
}, [currentTeamId]);
|
||||
}, [teamId, intl.locale, isTablet, onChannelSwitch, onlyUnreads]);
|
||||
|
||||
const categoriesToShow = useMemo(() => {
|
||||
if (onlyUnreads && !unreadsOnTop) {
|
||||
|
|
@ -88,19 +97,28 @@ const Categories = ({categories, currentTeamId, onlyUnreads, unreadsOnTop}: Prop
|
|||
}
|
||||
|
||||
return (
|
||||
<FlatList
|
||||
data={categoriesToShow}
|
||||
ref={listRef}
|
||||
renderItem={renderCategory}
|
||||
style={styles.mainList}
|
||||
showsHorizontalScrollIndicator={false}
|
||||
showsVerticalScrollIndicator={false}
|
||||
keyExtractor={extractKey}
|
||||
initialNumToRender={categoriesToShow.length}
|
||||
<>
|
||||
{!switchingTeam && (
|
||||
<FlatList
|
||||
data={categoriesToShow}
|
||||
ref={listRef}
|
||||
renderItem={renderCategory}
|
||||
style={styles.mainList}
|
||||
showsHorizontalScrollIndicator={false}
|
||||
showsVerticalScrollIndicator={false}
|
||||
keyExtractor={extractKey}
|
||||
initialNumToRender={categoriesToShow.length}
|
||||
|
||||
// @ts-expect-error strictMode not included in the types
|
||||
strictMode={true}
|
||||
/>
|
||||
// @ts-expect-error strictMode not included in the types
|
||||
strictMode={true}
|
||||
/>
|
||||
)}
|
||||
{switchingTeam && (
|
||||
<View style={styles.loadingView}>
|
||||
<Loading style={styles.loading}/>
|
||||
</View>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -10,19 +10,18 @@ import {Preferences} from '@constants';
|
|||
import {getPreferenceAsBool} from '@helpers/api/preference';
|
||||
import {queryCategoriesByTeamIds} from '@queries/servers/categories';
|
||||
import {queryPreferencesByCategoryAndName} from '@queries/servers/preference';
|
||||
import {observeOnlyUnreads} from '@queries/servers/system';
|
||||
import {observeCurrentTeamId, observeOnlyUnreads} from '@queries/servers/system';
|
||||
|
||||
import Categories from './categories';
|
||||
|
||||
import type {WithDatabaseArgs} from '@typings/database/database';
|
||||
import type PreferenceModel from '@typings/database/models/servers/preference';
|
||||
|
||||
type WithDatabaseProps = { currentTeamId: string } & WithDatabaseArgs
|
||||
|
||||
const enhanced = withObservables(
|
||||
['currentTeamId'],
|
||||
({currentTeamId, database}: WithDatabaseProps) => {
|
||||
const categories = queryCategoriesByTeamIds(database, [currentTeamId]).observeWithColumns(['sort_order']);
|
||||
[],
|
||||
({database}: WithDatabaseArgs) => {
|
||||
const currentTeamId = observeCurrentTeamId(database);
|
||||
const categories = currentTeamId.pipe(switchMap((ctid) => queryCategoriesByTeamIds(database, [ctid]).observeWithColumns(['sort_order'])));
|
||||
|
||||
const unreadsOnTop = queryPreferencesByCategoryAndName(database, Preferences.CATEGORY_SIDEBAR_SETTINGS, Preferences.CHANNEL_SIDEBAR_GROUP_UNREADS).
|
||||
observeWithColumns(['value']).
|
||||
|
|
|
|||
|
|
@ -6,7 +6,6 @@ import withObservables from '@nozbe/with-observables';
|
|||
import {of as of$, combineLatest} from 'rxjs';
|
||||
import {combineLatestWith, concatAll, map, switchMap} from 'rxjs/operators';
|
||||
|
||||
import {MyChannelModel} from '@app/database/models/server';
|
||||
import {Preferences} from '@constants';
|
||||
import {getPreferenceAsBool} from '@helpers/api/preference';
|
||||
import {getChannelById, observeAllMyChannelNotifyProps, queryMyChannelUnreads} from '@queries/servers/channel';
|
||||
|
|
@ -19,6 +18,7 @@ import UnreadCategories from './unreads';
|
|||
|
||||
import type {WithDatabaseArgs} from '@typings/database/database';
|
||||
import type ChannelModel from '@typings/database/models/servers/channel';
|
||||
import type MyChannelModel from '@typings/database/models/servers/my_channel';
|
||||
import type PreferenceModel from '@typings/database/models/servers/preference';
|
||||
|
||||
type WithDatabaseProps = WithDatabaseArgs & {
|
||||
|
|
|
|||
|
|
@ -27,6 +27,8 @@ type UnreadCategoriesProps = {
|
|||
onChannelSwitch: (channelId: string) => void;
|
||||
}
|
||||
|
||||
const extractKey = (item: ChannelModel) => item.id;
|
||||
|
||||
const UnreadCategories = ({onChannelSwitch, unreadChannels}: UnreadCategoriesProps) => {
|
||||
const theme = useTheme();
|
||||
const styles = getStyleSheet(theme);
|
||||
|
|
@ -54,6 +56,8 @@ const UnreadCategories = ({onChannelSwitch, unreadChannels}: UnreadCategoriesPro
|
|||
<FlatList
|
||||
data={unreadChannels}
|
||||
renderItem={renderItem}
|
||||
keyExtractor={extractKey}
|
||||
removeClippedSubviews={true}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -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} from 'rxjs/operators';
|
||||
import {switchMap, distinctUntilChanged} from 'rxjs/operators';
|
||||
|
||||
import {Permissions} from '@constants';
|
||||
import {observePermissionForTeam} from '@queries/servers/role';
|
||||
|
|
@ -23,14 +23,17 @@ const enhanced = withObservables([], ({database}: WithDatabaseArgs) => {
|
|||
|
||||
const canJoinChannels = combineLatest([currentUser, team]).pipe(
|
||||
switchMap(([u, t]) => (t && u ? observePermissionForTeam(t, u, Permissions.JOIN_PUBLIC_CHANNELS, true) : of$(false))),
|
||||
distinctUntilChanged(),
|
||||
);
|
||||
|
||||
const canCreatePublicChannels = combineLatest([currentUser, team]).pipe(
|
||||
switchMap(([u, t]) => (t && u ? observePermissionForTeam(t, u, Permissions.CREATE_PUBLIC_CHANNEL, true) : of$(false))),
|
||||
distinctUntilChanged(),
|
||||
);
|
||||
|
||||
const canCreatePrivateChannels = combineLatest([currentUser, team]).pipe(
|
||||
switchMap(([u, t]) => (t && u ? observePermissionForTeam(t, u, Permissions.CREATE_PRIVATE_CHANNEL, false) : of$(false))),
|
||||
distinctUntilChanged(),
|
||||
);
|
||||
|
||||
const canCreateChannels = combineLatest([canCreatePublicChannels, canCreatePrivateChannels]).pipe(
|
||||
|
|
|
|||
|
|
@ -33,7 +33,6 @@ describe('components/categories_list', () => {
|
|||
<CategoriesList
|
||||
isTablet={false}
|
||||
teamsCount={1}
|
||||
currentTeamId={TestHelper.basicTeam!.id}
|
||||
channelsCount={1}
|
||||
/>,
|
||||
{database},
|
||||
|
|
@ -47,7 +46,6 @@ describe('components/categories_list', () => {
|
|||
isCRTEnabled={true}
|
||||
isTablet={false}
|
||||
teamsCount={1}
|
||||
currentTeamId={TestHelper.basicTeam!.id}
|
||||
channelsCount={1}
|
||||
/>,
|
||||
{database},
|
||||
|
|
@ -65,7 +63,6 @@ describe('components/categories_list', () => {
|
|||
<CategoriesList
|
||||
isTablet={false}
|
||||
teamsCount={0}
|
||||
currentTeamId='TestHelper.basicTeam!.id'
|
||||
channelsCount={1}
|
||||
/>,
|
||||
{database},
|
||||
|
|
@ -84,7 +81,6 @@ describe('components/categories_list', () => {
|
|||
<CategoriesList
|
||||
isTablet={false}
|
||||
teamsCount={1}
|
||||
currentTeamId={TestHelper.basicTeam!.id}
|
||||
channelsCount={0}
|
||||
/>,
|
||||
{database},
|
||||
|
|
|
|||
|
|
@ -11,7 +11,6 @@ import {makeStyleSheetFromTheme} from '@utils/theme';
|
|||
import Categories from './categories';
|
||||
import ChannelListHeader from './header';
|
||||
import LoadChannelsError from './load_channels_error';
|
||||
import LoadTeamsError from './load_teams_error';
|
||||
import SubHeader from './subheader';
|
||||
import ThreadsButton from './threads_button';
|
||||
|
||||
|
|
@ -27,7 +26,6 @@ const getStyleSheet = makeStyleSheetFromTheme((theme: Theme) => ({
|
|||
|
||||
type ChannelListProps = {
|
||||
channelsCount: number;
|
||||
currentTeamId?: string;
|
||||
iconPad?: boolean;
|
||||
isCRTEnabled?: boolean;
|
||||
isTablet: boolean;
|
||||
|
|
@ -38,7 +36,7 @@ const getTabletWidth = (teamsCount: number) => {
|
|||
return TABLET_SIDEBAR_WIDTH - (teamsCount > 1 ? TEAM_SIDEBAR_WIDTH : 0);
|
||||
};
|
||||
|
||||
const CategoriesList = ({channelsCount, currentTeamId, iconPad, isCRTEnabled, isTablet, teamsCount}: ChannelListProps) => {
|
||||
const CategoriesList = ({channelsCount, iconPad, isCRTEnabled, isTablet, teamsCount}: ChannelListProps) => {
|
||||
const theme = useTheme();
|
||||
const styles = getStyleSheet(theme);
|
||||
const tabletWidth = useSharedValue(isTablet ? getTabletWidth(teamsCount) : 0);
|
||||
|
|
@ -61,18 +59,14 @@ const CategoriesList = ({channelsCount, currentTeamId, iconPad, isCRTEnabled, is
|
|||
|
||||
let content;
|
||||
|
||||
if (!currentTeamId) {
|
||||
content = (<LoadTeamsError/>);
|
||||
} else if (channelsCount < 1) {
|
||||
content = (<LoadChannelsError teamId={currentTeamId}/>);
|
||||
if (channelsCount < 1) {
|
||||
content = (<LoadChannelsError/>);
|
||||
} else {
|
||||
content = (
|
||||
<>
|
||||
<SubHeader/>
|
||||
{isCRTEnabled && <ThreadsButton/>}
|
||||
<Categories
|
||||
currentTeamId={currentTeamId}
|
||||
/>
|
||||
<Categories/>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,19 +6,22 @@ import withObservables from '@nozbe/with-observables';
|
|||
import {of as of$} from 'rxjs';
|
||||
import {switchMap} from 'rxjs/operators';
|
||||
|
||||
import {observeTeam} from '@queries/servers/team';
|
||||
import {observeCurrentTeam} from '@queries/servers/team';
|
||||
|
||||
import LoadChannelsError from './load_channel_error';
|
||||
|
||||
import type {WithDatabaseArgs} from '@typings/database/database';
|
||||
|
||||
const enhanced = withObservables(['teamId'], ({database, teamId}: {teamId: string} & WithDatabaseArgs) => {
|
||||
const team = observeTeam(database, teamId);
|
||||
const enhanced = withObservables([], ({database}: WithDatabaseArgs) => {
|
||||
const team = observeCurrentTeam(database);
|
||||
|
||||
return {
|
||||
teamDisplayName: team.pipe(
|
||||
switchMap((t) => of$(t?.displayName)),
|
||||
),
|
||||
teamId: team.pipe(
|
||||
switchMap((t) => of$(t?.id)),
|
||||
),
|
||||
};
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -8,6 +8,8 @@ import {retryInitialChannel} from '@actions/remote/retry';
|
|||
import LoadingError from '@components/loading_error';
|
||||
import {useServerUrl} from '@context/server';
|
||||
|
||||
import LoadTeamsError from '../load_teams_error';
|
||||
|
||||
type Props = {
|
||||
teamDisplayName: string;
|
||||
teamId: string;
|
||||
|
|
@ -27,6 +29,9 @@ const LoadChannelsError = ({teamDisplayName, teamId}: Props) => {
|
|||
}
|
||||
}, [teamId]);
|
||||
|
||||
if (!teamId) {
|
||||
<LoadTeamsError/>;
|
||||
}
|
||||
return (
|
||||
<LoadingError
|
||||
loading={loading}
|
||||
|
|
|
|||
|
|
@ -20,7 +20,6 @@ import Servers from './servers';
|
|||
|
||||
type ChannelProps = {
|
||||
channelsCount: number;
|
||||
currentTeamId?: string;
|
||||
isCRTEnabled: boolean;
|
||||
teamsCount: number;
|
||||
time?: number;
|
||||
|
|
@ -95,9 +94,8 @@ const ChannelListScreen = (props: ChannelProps) => {
|
|||
isTablet={isTablet}
|
||||
teamsCount={props.teamsCount}
|
||||
channelsCount={props.channelsCount}
|
||||
currentTeamId={props.currentTeamId}
|
||||
/>
|
||||
{isTablet && Boolean(props.currentTeamId) &&
|
||||
{isTablet &&
|
||||
<AdditionalTabletView/>
|
||||
}
|
||||
</Animated.View>
|
||||
|
|
|
|||
|
|
@ -3,8 +3,10 @@
|
|||
|
||||
import {withDatabase} from '@nozbe/watermelondb/DatabaseProvider';
|
||||
import withObservables from '@nozbe/with-observables';
|
||||
import {of as of$} from 'rxjs';
|
||||
import {switchMap} from 'rxjs/operators';
|
||||
|
||||
import {queryAllMyChannel} from '@queries/servers/channel';
|
||||
import {queryAllMyChannelsForTeam} from '@queries/servers/channel';
|
||||
import {observeCurrentTeamId} from '@queries/servers/system';
|
||||
import {queryMyTeams} from '@queries/servers/team';
|
||||
import {observeIsCRTEnabled} from '@queries/servers/thread';
|
||||
|
|
@ -14,10 +16,11 @@ import ChannelsList from './channel_list';
|
|||
import type {WithDatabaseArgs} from '@typings/database/database';
|
||||
|
||||
const enhanced = withObservables([], ({database}: WithDatabaseArgs) => ({
|
||||
currentTeamId: observeCurrentTeamId(database),
|
||||
isCRTEnabled: observeIsCRTEnabled(database),
|
||||
teamsCount: queryMyTeams(database).observeCount(false),
|
||||
channelsCount: queryAllMyChannel(database).observeCount(),
|
||||
channelsCount: observeCurrentTeamId(database).pipe(
|
||||
switchMap((id) => (id ? queryAllMyChannelsForTeam(database, id).observeCount() : of$(0))),
|
||||
),
|
||||
}));
|
||||
|
||||
export default withDatabase(enhanced(ChannelsList));
|
||||
|
|
|
|||
Loading…
Reference in a new issue