[Gekidou] Adding logic to hide the list of teams on the teams sidebar when needed (#5790)

This commit is contained in:
Jesús Espino 2021-12-01 08:07:04 +01:00 committed by GitHub
parent 3a8f1f1946
commit 3f91c3a47d
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
9 changed files with 201 additions and 144 deletions

View file

@ -38,7 +38,21 @@ exports[`components/channel_list should match snapshot 1`] = `
}
}
>
<View>
<View
animatedStyle={
Object {
"value": Object {
"marginLeft": 0,
},
}
}
collapsable={false}
style={
Object {
"marginLeft": 0,
}
}
>
<View
style={
Object {

View file

@ -1,89 +0,0 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import React, {useEffect, useState} from 'react';
import {TouchableOpacity} from 'react-native-gesture-handler';
import Animated, {useAnimatedStyle, useSharedValue, withTiming} from 'react-native-reanimated';
import {TABLET_SIDEBAR_WIDTH, TEAM_SIDEBAR_WIDTH} from '@constants/view';
import {useTheme} from '@context/theme';
import {makeStyleSheetFromTheme} from '@utils/theme';
import Categories from './categories';
import ChannelListHeader from './header';
import LoadingError from './loading_error';
import SearchField from './search';
// import Loading from '@components/loading';
const channels: TempoChannel[] = [
{id: '1', name: 'Just a channel'},
{id: '2', name: 'A Highlighted Channel!!!', highlight: true},
{id: '3', name: 'And a longer channel name.'},
];
const categories: TempoCategory[] = [
{id: '1', title: 'My first Category', channels},
{id: '2', title: 'Another Cat', channels},
];
const getStyleSheet = makeStyleSheetFromTheme((theme: Theme) => ({
container: {
flex: 1,
backgroundColor: theme.sidebarBg,
paddingHorizontal: 20,
paddingVertical: 10,
},
}));
type ChannelListProps = {
iconPad?: boolean;
isTablet: boolean;
teamsCount: number;
}
const ChannelList = ({iconPad, isTablet, teamsCount}: ChannelListProps) => {
const theme = useTheme();
const styles = getStyleSheet(theme);
const tabletWidth = useSharedValue(TABLET_SIDEBAR_WIDTH);
const tabletStyle = useAnimatedStyle(() => {
if (!isTablet) {
return {
maxWidth: undefined,
};
}
return {maxWidth: withTiming(tabletWidth.value, {duration: 250})};
}, []);
useEffect(() => {
if (isTablet) {
tabletWidth.value = TABLET_SIDEBAR_WIDTH - (teamsCount > 1 ? TEAM_SIDEBAR_WIDTH : 0);
}
}, [isTablet, teamsCount]);
// @to-do; remove after testing
const [showCats, setShowCats] = useState<boolean>(true);
return (
<Animated.View style={[styles.container, tabletStyle]}>
<TouchableOpacity onPress={() => setShowCats(!showCats)}>
<ChannelListHeader
iconPad={iconPad}
/>
</TouchableOpacity>
{showCats && (
<>
<SearchField/>
<Categories categories={categories}/>
</>
)}
{/* <Loading/> */}
{!showCats && (<LoadingError/>)}
</Animated.View>
);
};
export default ChannelList;

View file

@ -1,7 +1,21 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`components/channel_list/header Channel List Header Component should match snapshot 1`] = `
<View>
<View
animatedStyle={
Object {
"value": Object {
"marginLeft": 0,
},
}
}
collapsable={false}
style={
Object {
"marginLeft": 0,
}
}
>
<View
style={
Object {

View file

@ -1,8 +1,9 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import React from 'react';
import React, {useEffect} from 'react';
import {Text, View} from 'react-native';
import Animated, {useAnimatedStyle, useSharedValue, withTiming} from 'react-native-reanimated';
import CompassIcon from '@components/compass_icon';
import TouchableWithFeedback from '@components/touchable_with_feedback';
@ -25,9 +26,6 @@ const getStyles = makeStyleSheetFromTheme((theme: Theme) => ({
color: changeOpacity(theme.sidebarText, 0.64),
...typography('Heading', 50),
},
iconPad: {
marginLeft: 44,
},
headerRow: {
flexDirection: 'row',
alignItems: 'center',
@ -57,10 +55,18 @@ const getStyles = makeStyleSheetFromTheme((theme: Theme) => ({
const ChannelListHeader = ({displayName, iconPad}: Props) => {
const theme = useTheme();
const serverDisplayName = useServerDisplayName();
const marginLeft = useSharedValue(iconPad ? 44 : 0);
const styles = getStyles(theme);
const animatedStyle = useAnimatedStyle(() => ({
marginLeft: withTiming(marginLeft.value, {duration: 350}),
}), []);
useEffect(() => {
marginLeft.value = iconPad ? 44 : 0;
}, [iconPad]);
return (
<View style={iconPad && styles.iconPad}>
<Animated.View style={animatedStyle}>
<View style={styles.headerRow}>
<View style={styles.headerRow}>
<Text style={styles.headingStyles}>
@ -83,7 +89,7 @@ const ChannelListHeader = ({displayName, iconPad}: Props) => {
<Text style={styles.subHeadingStyles}>
{serverDisplayName}
</Text>
</View>
</Animated.View>
);
};

View file

@ -9,7 +9,7 @@ import {TeamModel} from '@database/models/server';
import {renderWithEverything} from '@test/intl-test-helper';
import TestHelper from '@test/test_helper';
import ChannelsList from './channel_list';
import ChannelsList from './index';
describe('components/channel_list', () => {
let database: Database;

View file

@ -1,20 +1,88 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import {withDatabase} from '@nozbe/watermelondb/DatabaseProvider';
import withObservables from '@nozbe/with-observables';
import React, {useEffect, useState} from 'react';
import {TouchableOpacity} from 'react-native-gesture-handler';
import Animated, {useAnimatedStyle, useSharedValue, withTiming} from 'react-native-reanimated';
import {MM_TABLES} from '@constants/database';
import MyTeamModel from '@typings/database/models/servers/my_team';
import {TABLET_SIDEBAR_WIDTH, TEAM_SIDEBAR_WIDTH} from '@constants/view';
import {useTheme} from '@context/theme';
import {makeStyleSheetFromTheme} from '@utils/theme';
import ChannelsList from './channel_list';
import Categories from './categories';
import ChannelListHeader from './header';
import LoadingError from './loading_error';
import SearchField from './search';
import type {WithDatabaseArgs} from '@typings/database/database';
// import Loading from '@components/loading';
const {SERVER: {MY_TEAM}} = MM_TABLES;
const channels: TempoChannel[] = [
{id: '1', name: 'Just a channel'},
{id: '2', name: 'A Highlighted Channel!!!', highlight: true},
{id: '3', name: 'And a longer channel name.'},
];
const categories: TempoCategory[] = [
{id: '1', title: 'My first Category', channels},
{id: '2', title: 'Another Cat', channels},
];
const getStyleSheet = makeStyleSheetFromTheme((theme: Theme) => ({
container: {
flex: 1,
backgroundColor: theme.sidebarBg,
paddingHorizontal: 20,
paddingVertical: 10,
},
const enhanced = withObservables([], ({database}: WithDatabaseArgs) => ({
teamsCount: database.get<MyTeamModel>(MY_TEAM).query().observeCount(),
}));
export default withDatabase(enhanced(ChannelsList));
type ChannelListProps = {
iconPad?: boolean;
isTablet: boolean;
teamsCount: number;
}
const ChannelList = ({iconPad, isTablet, teamsCount}: ChannelListProps) => {
const theme = useTheme();
const styles = getStyleSheet(theme);
const tabletWidth = useSharedValue(TABLET_SIDEBAR_WIDTH);
const tabletStyle = useAnimatedStyle(() => {
if (!isTablet) {
return {
maxWidth: undefined,
};
}
return {maxWidth: withTiming(tabletWidth.value, {duration: 350})};
}, []);
useEffect(() => {
if (isTablet) {
tabletWidth.value = TABLET_SIDEBAR_WIDTH - (teamsCount > 1 ? TEAM_SIDEBAR_WIDTH : 0);
}
}, [isTablet, teamsCount]);
const [showCats, setShowCats] = useState<boolean>(true);
return (
<Animated.View style={[styles.container, tabletStyle]}>
<TouchableOpacity onPress={() => setShowCats(!showCats)}>
<ChannelListHeader
iconPad={iconPad}
/>
</TouchableOpacity>
{showCats && (
<>
<SearchField/>
<Categories categories={categories}/>
</>
)}
{/* <Loading/> */}
{!showCats && (<LoadingError/>)}
</Animated.View>
);
};
export default ChannelList;

View file

@ -2,7 +2,7 @@
// See LICENSE.txt for license information.
import React, {useEffect} from 'react';
import {View} from 'react-native';
import Animated, {useAnimatedStyle, useSharedValue, withTiming} from 'react-native-reanimated';
import {fetchAllTeams} from '@actions/remote/team';
import {TEAM_SIDEBAR_WIDTH} from '@constants/view';
@ -17,34 +17,9 @@ import type TeamModel from '@typings/database/models/servers/team';
type Props = {
canCreateTeams: boolean;
otherTeams: TeamModel[];
iconPad?: boolean;
}
export default function TeamSidebar({canCreateTeams, otherTeams, iconPad}: Props) {
const theme = useTheme();
const styles = getStyleSheet(theme);
const serverUrl = useServerUrl();
useEffect(() => {
fetchAllTeams(serverUrl);
}, [serverUrl]);
const showAddTeam = canCreateTeams || otherTeams.length > 0;
return (
<View style={styles.container}>
<View style={[styles.listContainer, iconPad && styles.iconMargin]}>
<TeamList/>
{showAddTeam && (
<AddTeam
canCreateTeams={canCreateTeams}
otherTeams={otherTeams}
/>
)}
</View>
</View>
);
otherTeams: TeamModel[];
teamsCount: number;
}
const getStyleSheet = makeStyleSheetFromTheme((theme: Theme) => {
@ -53,7 +28,6 @@ const getStyleSheet = makeStyleSheetFromTheme((theme: Theme) => {
width: TEAM_SIDEBAR_WIDTH,
height: '100%',
backgroundColor: theme.sidebarBg,
display: 'flex',
paddingTop: 10,
},
listContainer: {
@ -63,7 +37,52 @@ const getStyleSheet = makeStyleSheetFromTheme((theme: Theme) => {
},
iconMargin: {
marginTop: 44,
paddingTop: 0,
},
};
});
export default function TeamSidebar({canCreateTeams, iconPad, otherTeams, teamsCount}: Props) {
const showAddTeam = canCreateTeams || otherTeams.length > 0;
const initialWidth = teamsCount > 1 ? TEAM_SIDEBAR_WIDTH : 0;
const width = useSharedValue(initialWidth);
const marginTop = useSharedValue(iconPad ? 44 : 0);
const theme = useTheme();
const serverUrl = useServerUrl();
const styles = getStyleSheet(theme);
const transform = useAnimatedStyle(() => {
return {
width: withTiming(width.value, {duration: 350}),
};
}, []);
const serverStyle = useAnimatedStyle(() => ({
marginTop: withTiming(marginTop.value, {duration: 350}),
}), []);
useEffect(() => {
marginTop.value = iconPad ? 44 : 0;
}, [iconPad]);
useEffect(() => {
fetchAllTeams(serverUrl);
}, [serverUrl]);
useEffect(() => {
width.value = teamsCount > 1 ? TEAM_SIDEBAR_WIDTH : 0;
}, [teamsCount]);
return (
<Animated.View style={[styles.container, transform]}>
<Animated.View style={[styles.listContainer, serverStyle]}>
<TeamList/>
{showAddTeam && (
<AddTeam
canCreateTeams={canCreateTeams}
otherTeams={otherTeams}
/>
)}
</Animated.View>
</Animated.View>
);
}

View file

@ -1,6 +1,7 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import {useManagedConfig} from '@mattermost/react-native-emm';
import {useIsFocused, useRoute} from '@react-navigation/native';
import React from 'react';
import {View} from 'react-native';
@ -18,6 +19,7 @@ import {makeStyleSheetFromTheme} from '@utils/theme';
import type {LaunchProps} from '@typings/launch';
type ChannelProps = LaunchProps & {
teamsCount: number;
time?: number;
};
@ -43,12 +45,14 @@ const getStyleSheet = makeStyleSheetFromTheme((theme: Theme) => ({
const ChannelListScreen = (props: ChannelProps) => {
const theme = useTheme();
const styles = getStyleSheet(theme);
const managedConfig = useManagedConfig();
const isTablet = useIsTablet();
const route = useRoute();
const isFocused = useIsFocused();
const insets = useSafeAreaInsets();
const params = route.params as {direction: string};
const canAddOtherServers = managedConfig?.allowOtherServers !== 'false';
const animated = useAnimatedStyle(() => {
if (!isFocused) {
@ -74,17 +78,18 @@ const ChannelListScreen = (props: ChannelProps) => {
style={styles.content}
edges={['bottom', 'left', 'right']}
>
<ServerIcon/>
{canAddOtherServers && <ServerIcon/>}
<Animated.View
style={[styles.content, animated]}
>
{/* @to-do: Server Icon requires padding in the team and channel components:
* https://mattermost.atlassian.net/browse/MM-39702
*/}
<TeamSidebar iconPad={true}/>
<TeamSidebar
iconPad={canAddOtherServers}
teamsCount={props.teamsCount}
/>
<ChannelList
iconPad={false}
iconPad={canAddOtherServers && props.teamsCount <= 1}
isTablet={isTablet}
teamsCount={props.teamsCount}
/>
{isTablet &&
<Channel {...props}/>

View file

@ -0,0 +1,20 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import {withDatabase} from '@nozbe/watermelondb/DatabaseProvider';
import withObservables from '@nozbe/with-observables';
import {MM_TABLES} from '@constants/database';
import MyTeamModel from '@typings/database/models/servers/my_team';
import ChannelsList from './channel_list';
import type {WithDatabaseArgs} from '@typings/database/database';
const {SERVER: {MY_TEAM}} = MM_TABLES;
const enhanced = withObservables([], ({database}: WithDatabaseArgs) => ({
teamsCount: database.get<MyTeamModel>(MY_TEAM).query().observeCount(),
}));
export default withDatabase(enhanced(ChannelsList));