chore: migrate several class-based components to functional components (#5584)

This commit is contained in:
Sathurshan 2024-03-20 19:27:44 +05:30 committed by GitHub
parent 4ca641228c
commit a2975f7c13
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
7 changed files with 320 additions and 380 deletions

View File

@ -1,10 +1,8 @@
import React from 'react'; import React, { useEffect, useRef } from 'react';
import { Animated, Easing, FlatList, TouchableWithoutFeedback } from 'react-native'; import { Animated, Easing, FlatList, TouchableWithoutFeedback } from 'react-native';
import { withSafeAreaInsets } from 'react-native-safe-area-context';
import styles from '../styles'; import styles from '../styles';
import { themes } from '../../../lib/constants'; import { useTheme } from '../../../theme';
import { TSupportedThemes, withTheme } from '../../../theme';
import * as List from '../../../containers/List'; import * as List from '../../../containers/List';
import DropdownItemFilter from './DropdownItemFilter'; import DropdownItemFilter from './DropdownItemFilter';
import DropdownItemHeader from './DropdownItemHeader'; import DropdownItemHeader from './DropdownItemHeader';
@ -12,35 +10,31 @@ import { ROW_HEIGHT } from './DropdownItem';
import { ILivechatDepartment } from '../../../definitions/ILivechatDepartment'; import { ILivechatDepartment } from '../../../definitions/ILivechatDepartment';
const ANIMATION_DURATION = 200; const ANIMATION_DURATION = 200;
const HEIGHT_DESTINATION = 0;
const MAX_ROWS = 5;
interface IDropdownProps { interface IDropdownProps {
theme?: TSupportedThemes;
currentDepartment: ILivechatDepartment; currentDepartment: ILivechatDepartment;
onClose: () => void; onClose: () => void;
onDepartmentSelected: (value: ILivechatDepartment) => void; onDepartmentSelected: (value: ILivechatDepartment) => void;
departments: ILivechatDepartment[]; departments: ILivechatDepartment[];
} }
class Dropdown extends React.Component<IDropdownProps> { const Dropdown = ({ currentDepartment, onClose, onDepartmentSelected, departments }: IDropdownProps) => {
private animatedValue: Animated.Value; const animatedValue = useRef(new Animated.Value(0)).current;
const { colors } = useTheme();
constructor(props: IDropdownProps) { useEffect(() => {
super(props); Animated.timing(animatedValue, {
this.animatedValue = new Animated.Value(0);
}
componentDidMount() {
Animated.timing(this.animatedValue, {
toValue: 1, toValue: 1,
duration: ANIMATION_DURATION, duration: ANIMATION_DURATION,
easing: Easing.inOut(Easing.quad), easing: Easing.inOut(Easing.quad),
useNativeDriver: true useNativeDriver: true
}).start(); }).start();
} }, [animatedValue]);
close = () => { const close = () => {
const { onClose } = this.props; Animated.timing(animatedValue, {
Animated.timing(this.animatedValue, {
toValue: 0, toValue: 0,
duration: ANIMATION_DURATION, duration: ANIMATION_DURATION,
easing: Easing.inOut(Easing.quad), easing: Easing.inOut(Easing.quad),
@ -48,29 +42,26 @@ class Dropdown extends React.Component<IDropdownProps> {
}).start(() => onClose()); }).start(() => onClose());
}; };
render() { const translateY = animatedValue.interpolate({
const { theme, currentDepartment, onDepartmentSelected, departments } = this.props;
const heightDestination = 0;
const translateY = this.animatedValue.interpolate({
inputRange: [0, 1], inputRange: [0, 1],
outputRange: [-300, heightDestination] // approximated height of the component when closed/open outputRange: [-300, HEIGHT_DESTINATION] // approximated height of the component when closed/open
}); });
const backdropOpacity = this.animatedValue.interpolate({
inputRange: [0, 1], const backdropOpacity = animatedValue.interpolate({
outputRange: [0, themes[theme!].backdropOpacity] inputRange: [0, 1],
outputRange: [0, colors.backdropOpacity]
}); });
const maxRows = 5;
return ( return (
<> <>
<TouchableWithoutFeedback onPress={this.close}> <TouchableWithoutFeedback onPress={close}>
<Animated.View <Animated.View
style={[ style={[
styles.backdrop, styles.backdrop,
{ {
backgroundColor: themes[theme!].backdropColor, backgroundColor: colors.backdropColor,
opacity: backdropOpacity, opacity: backdropOpacity,
top: heightDestination top: HEIGHT_DESTINATION
} }
]} ]}
/> />
@ -80,15 +71,15 @@ class Dropdown extends React.Component<IDropdownProps> {
styles.dropdownContainer, styles.dropdownContainer,
{ {
transform: [{ translateY }], transform: [{ translateY }],
backgroundColor: themes[theme!].backgroundColor, backgroundColor: colors.backgroundColor,
borderColor: themes[theme!].separatorColor borderColor: colors.separatorColor
} }
]} ]}
> >
<DropdownItemHeader department={currentDepartment} onPress={this.close} /> <DropdownItemHeader department={currentDepartment} onPress={close} />
<List.Separator /> <List.Separator />
<FlatList <FlatList
style={{ maxHeight: maxRows * ROW_HEIGHT }} style={{ maxHeight: MAX_ROWS * ROW_HEIGHT }}
data={departments} data={departments}
keyExtractor={item => item._id} keyExtractor={item => item._id}
renderItem={({ item }) => ( renderItem={({ item }) => (
@ -99,7 +90,6 @@ class Dropdown extends React.Component<IDropdownProps> {
</Animated.View> </Animated.View>
</> </>
); );
} };
}
export default withTheme(withSafeAreaInsets(Dropdown)); export default Dropdown;

View File

@ -1,13 +1,13 @@
import React, { PureComponent } from 'react'; import React, { useEffect, useRef } from 'react';
import { Animated, Easing, Switch, Text, TouchableWithoutFeedback, View } from 'react-native'; import { Animated, Easing, Switch, Text, TouchableWithoutFeedback, View } from 'react-native';
import Touch from '../../containers/Touch'; import Touch from '../../containers/Touch';
import { CustomIcon, TIconsName } from '../../containers/CustomIcon'; import { CustomIcon, TIconsName } from '../../containers/CustomIcon';
import Check from '../../containers/Check'; import Check from '../../containers/Check';
import I18n from '../../i18n'; import I18n from '../../i18n';
import { SWITCH_TRACK_COLOR, themes } from '../../lib/constants'; import { SWITCH_TRACK_COLOR } from '../../lib/constants';
import styles from './styles'; import styles from './styles';
import { TSupportedThemes } from '../../theme'; import { useTheme } from '../../theme';
const ANIMATION_DURATION = 200; const ANIMATION_DURATION = 200;
const ANIMATION_PROPS = { const ANIMATION_PROPS = {
@ -23,34 +23,34 @@ interface IDirectoryOptionsProps {
close: Function; close: Function;
changeType: Function; changeType: Function;
toggleWorkspace(): void; toggleWorkspace(): void;
theme: TSupportedThemes;
} }
export default class DirectoryOptions extends PureComponent<IDirectoryOptionsProps, any> { const DirectoryOptions = ({
private animatedValue: Animated.Value; type: propType,
globalUsers,
isFederationEnabled,
close: onClose,
changeType,
toggleWorkspace
}: IDirectoryOptionsProps) => {
const animatedValue = useRef(new Animated.Value(0)).current;
const { colors } = useTheme();
constructor(props: IDirectoryOptionsProps) { useEffect(() => {
super(props); Animated.timing(animatedValue, {
this.animatedValue = new Animated.Value(0);
}
componentDidMount() {
Animated.timing(this.animatedValue, {
toValue: 1, toValue: 1,
...ANIMATION_PROPS ...ANIMATION_PROPS
}).start(); }).start();
} }, [animatedValue]);
close = () => { const close = () => {
const { close } = this.props; Animated.timing(animatedValue, {
Animated.timing(this.animatedValue, {
toValue: 0, toValue: 0,
...ANIMATION_PROPS ...ANIMATION_PROPS
}).start(() => close()); }).start(() => onClose());
}; };
renderItem = (itemType: string) => { const renderItem = (itemType: string) => {
const { changeType, type: propType, theme } = this.props;
let text = 'Users'; let text = 'Users';
let icon: TIconsName = 'user'; let icon: TIconsName = 'user';
if (itemType === 'channels') { if (itemType === 'channels') {
@ -66,61 +66,51 @@ export default class DirectoryOptions extends PureComponent<IDirectoryOptionsPro
return ( return (
<Touch onPress={() => changeType(itemType)} style={styles.dropdownItemButton} accessibilityLabel={I18n.t(text)}> <Touch onPress={() => changeType(itemType)} style={styles.dropdownItemButton} accessibilityLabel={I18n.t(text)}>
<View style={styles.dropdownItemContainer}> <View style={styles.dropdownItemContainer}>
<CustomIcon name={icon} size={22} color={themes[theme].bodyText} style={styles.dropdownItemIcon} /> <CustomIcon name={icon} size={22} color={colors.bodyText} style={styles.dropdownItemIcon} />
<Text style={[styles.dropdownItemText, { color: themes[theme].bodyText }]}>{I18n.t(text)}</Text> <Text style={[styles.dropdownItemText, { color: colors.bodyText }]}>{I18n.t(text)}</Text>
{propType === itemType ? <Check /> : null} {propType === itemType ? <Check /> : null}
</View> </View>
</Touch> </Touch>
); );
}; };
render() { const translateY = animatedValue.interpolate({
const translateY = this.animatedValue.interpolate({
inputRange: [0, 1], inputRange: [0, 1],
outputRange: [-326, 0] outputRange: [-326, 0]
}); });
const { globalUsers, toggleWorkspace, isFederationEnabled, theme } = this.props;
const backdropOpacity = this.animatedValue.interpolate({ const backdropOpacity = animatedValue.interpolate({
inputRange: [0, 1], inputRange: [0, 1],
outputRange: [0, themes[theme].backdropOpacity] outputRange: [0, colors.backdropOpacity]
}); });
return ( return (
<> <>
<TouchableWithoutFeedback onPress={this.close}> <TouchableWithoutFeedback onPress={close}>
<Animated.View style={[styles.backdrop, { backgroundColor: themes[theme].backdropColor, opacity: backdropOpacity }]} /> <Animated.View style={[styles.backdrop, { backgroundColor: colors.backdropColor, opacity: backdropOpacity }]} />
</TouchableWithoutFeedback> </TouchableWithoutFeedback>
<Animated.View <Animated.View style={[styles.dropdownContainer, { transform: [{ translateY }], backgroundColor: colors.backgroundColor }]}>
style={[styles.dropdownContainer, { transform: [{ translateY }], backgroundColor: themes[theme].backgroundColor }]} <Touch onPress={close} accessibilityLabel={I18n.t('Search_by')}>
> <View style={[styles.dropdownContainerHeader, styles.dropdownItemContainer, { borderColor: colors.separatorColor }]}>
<Touch onPress={this.close} accessibilityLabel={I18n.t('Search_by')}> <Text style={[styles.dropdownToggleText, { color: colors.auxiliaryText }]}>{I18n.t('Search_by')}</Text>
<View
style={[
styles.dropdownContainerHeader,
styles.dropdownItemContainer,
{ borderColor: themes[theme].separatorColor }
]}
>
<Text style={[styles.dropdownToggleText, { color: themes[theme].auxiliaryText }]}>{I18n.t('Search_by')}</Text>
<CustomIcon <CustomIcon
style={[styles.dropdownItemIcon, styles.inverted]} style={[styles.dropdownItemIcon, styles.inverted]}
size={22} size={22}
name='chevron-down' name='chevron-down'
color={themes[theme].auxiliaryTintColor} color={colors.auxiliaryTintColor}
/> />
</View> </View>
</Touch> </Touch>
{this.renderItem('channels')} {renderItem('channels')}
{this.renderItem('users')} {renderItem('users')}
{this.renderItem('teams')} {renderItem('teams')}
{isFederationEnabled ? ( {isFederationEnabled ? (
<> <>
<View style={[styles.dropdownSeparator, { backgroundColor: themes[theme].separatorColor }]} /> <View style={[styles.dropdownSeparator, { backgroundColor: colors.separatorColor }]} />
<View style={[styles.dropdownItemContainer, styles.globalUsersContainer]}> <View style={[styles.dropdownItemContainer, styles.globalUsersContainer]}>
<View style={styles.globalUsersTextContainer}> <View style={styles.globalUsersTextContainer}>
<Text style={[styles.dropdownItemText, { color: themes[theme].infoText }]}> <Text style={[styles.dropdownItemText, { color: colors.infoText }]}>{I18n.t('Search_global_users')}</Text>
{I18n.t('Search_global_users')} <Text style={[styles.dropdownItemDescription, { color: colors.infoText }]}>
</Text>
<Text style={[styles.dropdownItemDescription, { color: themes[theme].infoText }]}>
{I18n.t('Search_global_users_description')} {I18n.t('Search_global_users_description')}
</Text> </Text>
</View> </View>
@ -131,5 +121,6 @@ export default class DirectoryOptions extends PureComponent<IDirectoryOptionsPro
</Animated.View> </Animated.View>
</> </>
); );
} };
}
export default DirectoryOptions;

View File

@ -315,7 +315,6 @@ class DirectoryView extends React.Component<IDirectoryViewProps, IDirectoryViewS
/> />
{showOptionsDropdown ? ( {showOptionsDropdown ? (
<Options <Options
theme={theme}
type={type} type={type}
globalUsers={globalUsers} globalUsers={globalUsers}
close={this.toggleDropdown} close={this.toggleDropdown}

View File

@ -1,8 +1,8 @@
import React, { Component } from 'react'; import React, { useEffect, useRef, useState } from 'react';
import { View, Text, Animated, Easing, TouchableWithoutFeedback, TouchableOpacity, FlatList, Linking } from 'react-native'; import { View, Text, Animated, Easing, TouchableWithoutFeedback, TouchableOpacity, FlatList, Linking } from 'react-native';
import { batch, connect } from 'react-redux'; import { batch, useDispatch } from 'react-redux';
import { withSafeAreaInsets } from 'react-native-safe-area-context';
import { Subscription } from 'rxjs'; import { Subscription } from 'rxjs';
import { useSafeAreaInsets } from 'react-native-safe-area-context';
import * as List from '../../containers/List'; import * as List from '../../containers/List';
import Button from '../../containers/Button'; import Button from '../../containers/Button';
@ -13,82 +13,73 @@ import I18n from '../../i18n';
import EventEmitter from '../../lib/methods/helpers/events'; import EventEmitter from '../../lib/methods/helpers/events';
import ServerItem from '../../containers/ServerItem'; import ServerItem from '../../containers/ServerItem';
import database from '../../lib/database'; import database from '../../lib/database';
import { themes, TOKEN_KEY } from '../../lib/constants'; import { TOKEN_KEY } from '../../lib/constants';
import { withTheme } from '../../theme'; import { useTheme } from '../../theme';
import { localAuthenticate } from '../../lib/methods/helpers/localAuthentication'; import { localAuthenticate } from '../../lib/methods/helpers/localAuthentication';
import { showConfirmationAlert } from '../../lib/methods/helpers/info'; import { showConfirmationAlert } from '../../lib/methods/helpers/info';
import log, { events, logEvent } from '../../lib/methods/helpers/log'; import log, { events, logEvent } from '../../lib/methods/helpers/log';
import { headerHeight } from '../../lib/methods/helpers/navigation'; import { headerHeight } from '../../lib/methods/helpers/navigation';
import { goRoom } from '../../lib/methods/helpers/goRoom'; import { goRoom } from '../../lib/methods/helpers/goRoom';
import UserPreferences from '../../lib/methods/userPreferences'; import UserPreferences from '../../lib/methods/userPreferences';
import { IApplicationState, IBaseScreen, RootEnum, TServerModel } from '../../definitions'; import { RootEnum, TServerModel } from '../../definitions';
import styles from './styles'; import styles from './styles';
import { ChatsStackParamList } from '../../stacks/types';
import { removeServer } from '../../lib/methods'; import { removeServer } from '../../lib/methods';
import { useAppSelector } from '../../lib/hooks';
const ROW_HEIGHT = 68; const ROW_HEIGHT = 68;
const ANIMATION_DURATION = 200; const ANIMATION_DURATION = 200;
const MAX_ROWS = 4;
interface IServerDropdownProps extends IBaseScreen<ChatsStackParamList, 'RoomsListView'> { const ServerDropdown = () => {
insets?: { const animatedValue = useRef(new Animated.Value(0)).current;
top: number; const subscription = useRef<Subscription>();
}; const newServerTimeout = useRef<ReturnType<typeof setTimeout> | false>();
closeServerDropdown: boolean; const isMounted = useRef(false);
server: string; const [servers, setServers] = useState<TServerModel[]>([]);
isMasterDetail: boolean; const dispatch = useDispatch();
} const closeServerDropdown = useAppSelector(state => state.rooms.closeServerDropdown);
const server = useAppSelector(state => state.server.server);
const isMasterDetail = useAppSelector(state => state.app.isMasterDetail);
const { colors } = useTheme();
const insets = useSafeAreaInsets();
interface IServerDropdownState { useEffect(() => {
servers: TServerModel[]; if (isMounted.current) close();
} }, [closeServerDropdown]);
class ServerDropdown extends Component<IServerDropdownProps, IServerDropdownState> { useEffect(() => {
private animatedValue: Animated.Value; isMounted.current = true;
private subscription?: Subscription;
private newServerTimeout?: ReturnType<typeof setTimeout> | false;
constructor(props: IServerDropdownProps) { const init = async () => {
super(props);
this.state = { servers: [] };
this.animatedValue = new Animated.Value(0);
}
async componentDidMount() {
const serversDB = database.servers; const serversDB = database.servers;
const observable = await serversDB.get('servers').query().observeWithColumns(['name']); const observable = await serversDB.get('servers').query().observeWithColumns(['name']);
this.subscription = observable.subscribe(data => { subscription.current = observable.subscribe(data => {
this.setState({ servers: data }); setServers(data);
}); });
Animated.timing(this.animatedValue, { Animated.timing(animatedValue, {
toValue: 1, toValue: 1,
duration: ANIMATION_DURATION, duration: ANIMATION_DURATION,
easing: Easing.inOut(Easing.quad), easing: Easing.inOut(Easing.quad),
useNativeDriver: true useNativeDriver: true
}).start(); }).start();
} };
init();
componentDidUpdate(prevProps: IServerDropdownProps) { return () => {
const { closeServerDropdown } = this.props; if (newServerTimeout.current) {
if (prevProps.closeServerDropdown !== closeServerDropdown) { clearTimeout(newServerTimeout.current);
this.close(); newServerTimeout.current = false;
} }
if (subscription.current && subscription.current.unsubscribe) {
subscription.current.unsubscribe();
} }
};
}, []);
componentWillUnmount() { const close = () => {
if (this.newServerTimeout) { Animated.timing(animatedValue, {
clearTimeout(this.newServerTimeout);
this.newServerTimeout = false;
}
if (this.subscription && this.subscription.unsubscribe) {
this.subscription.unsubscribe();
}
}
close = () => {
const { dispatch } = this.props;
Animated.timing(this.animatedValue, {
toValue: 0, toValue: 0,
duration: ANIMATION_DURATION, duration: ANIMATION_DURATION,
easing: Easing.inOut(Easing.quad), easing: Easing.inOut(Easing.quad),
@ -96,7 +87,7 @@ class ServerDropdown extends Component<IServerDropdownProps, IServerDropdownStat
}).start(() => dispatch(toggleServerDropdown())); }).start(() => dispatch(toggleServerDropdown()));
}; };
createWorkspace = async () => { const createWorkspace = async () => {
logEvent(events.RL_CREATE_NEW_WORKSPACE); logEvent(events.RL_CREATE_NEW_WORKSPACE);
try { try {
await Linking.openURL('https://cloud.rocket.chat/trial'); await Linking.openURL('https://cloud.rocket.chat/trial');
@ -105,52 +96,49 @@ class ServerDropdown extends Component<IServerDropdownProps, IServerDropdownStat
} }
}; };
navToNewServer = (previousServer: string) => { const navToNewServer = (previousServer: string) => {
const { dispatch } = this.props;
batch(() => { batch(() => {
dispatch(appStart({ root: RootEnum.ROOT_OUTSIDE })); dispatch(appStart({ root: RootEnum.ROOT_OUTSIDE }));
dispatch(serverInitAdd(previousServer)); dispatch(serverInitAdd(previousServer));
}); });
}; };
addServer = () => { const addServer = () => {
logEvent(events.RL_ADD_SERVER); logEvent(events.RL_ADD_SERVER);
const { server } = this.props; close();
this.close();
setTimeout(() => { setTimeout(() => {
this.navToNewServer(server); navToNewServer(server);
}, ANIMATION_DURATION); }, ANIMATION_DURATION);
}; };
select = async (server: string, version?: string) => { const select = async (serverParam: string, version?: string) => {
const { server: currentServer, dispatch, isMasterDetail } = this.props; close();
this.close(); if (server !== serverParam) {
if (currentServer !== server) {
logEvent(events.RL_CHANGE_SERVER); logEvent(events.RL_CHANGE_SERVER);
const userId = UserPreferences.getString(`${TOKEN_KEY}-${server}`); const userId = UserPreferences.getString(`${TOKEN_KEY}-${serverParam}`);
if (isMasterDetail) { if (isMasterDetail) {
goRoom({ item: {}, isMasterDetail }); goRoom({ item: {}, isMasterDetail });
} }
if (!userId) { if (!userId) {
setTimeout(() => { setTimeout(() => {
this.navToNewServer(currentServer); navToNewServer(server);
this.newServerTimeout = setTimeout(() => { newServerTimeout.current = setTimeout(() => {
EventEmitter.emit('NewServer', { server }); EventEmitter.emit('NewServer', { server: serverParam });
}, ANIMATION_DURATION); }, ANIMATION_DURATION);
}, ANIMATION_DURATION); }, ANIMATION_DURATION);
} else { } else {
await localAuthenticate(server); await localAuthenticate(serverParam);
dispatch(selectServerRequest(server, version, true, true)); dispatch(selectServerRequest(serverParam, version, true, true));
} }
} }
}; };
remove = (server: string) => const remove = (server: string) =>
showConfirmationAlert({ showConfirmationAlert({
message: I18n.t('This_will_remove_all_data_from_this_server'), message: I18n.t('This_will_remove_all_data_from_this_server'),
confirmationText: I18n.t('Delete'), confirmationText: I18n.t('Delete'),
onPress: async () => { onPress: async () => {
this.close(); close();
try { try {
await removeServer({ server }); await removeServer({ server });
} catch { } catch {
@ -159,42 +147,37 @@ class ServerDropdown extends Component<IServerDropdownProps, IServerDropdownStat
} }
}); });
renderServer = ({ item }: { item: { id: string; iconURL: string; name: string; version: string } }) => { const renderItem = ({ item }: { item: { id: string; iconURL: string; name: string; version: string } }) => (
const { server } = this.props;
return (
<ServerItem <ServerItem
item={item} item={item}
onPress={() => this.select(item.id, item.version)} onPress={() => select(item.id, item.version)}
onLongPress={() => item.id === server || this.remove(item.id)} onLongPress={() => item.id === server || remove(item.id)}
hasCheck={item.id === server} hasCheck={item.id === server}
/> />
); );
};
render() { const initialTop = 87 + Math.min(servers.length, MAX_ROWS) * ROW_HEIGHT;
const { servers } = this.state;
const { theme, isMasterDetail, insets } = this.props;
const maxRows = 4;
const initialTop = 87 + Math.min(servers.length, maxRows) * ROW_HEIGHT;
const statusBarHeight = insets?.top ?? 0; const statusBarHeight = insets?.top ?? 0;
const heightDestination = isMasterDetail ? headerHeight + statusBarHeight : 0; const heightDestination = isMasterDetail ? headerHeight + statusBarHeight : 0;
const translateY = this.animatedValue.interpolate({
const translateY = animatedValue.interpolate({
inputRange: [0, 1], inputRange: [0, 1],
outputRange: [-initialTop, heightDestination] outputRange: [-initialTop, heightDestination]
}); });
const backdropOpacity = this.animatedValue.interpolate({
const backdropOpacity = animatedValue.interpolate({
inputRange: [0, 1], inputRange: [0, 1],
outputRange: [0, themes[theme].backdropOpacity] outputRange: [0, colors.backdropOpacity]
}); });
return ( return (
<> <>
<TouchableWithoutFeedback onPress={this.close}> <TouchableWithoutFeedback onPress={close}>
<Animated.View <Animated.View
style={[ style={[
styles.backdrop, styles.backdrop,
{ {
backgroundColor: themes[theme].backdropColor, backgroundColor: colors.backdropColor,
opacity: backdropOpacity, opacity: backdropOpacity,
top: heightDestination top: heightDestination
} }
@ -206,23 +189,23 @@ class ServerDropdown extends Component<IServerDropdownProps, IServerDropdownStat
styles.dropdownContainer, styles.dropdownContainer,
{ {
transform: [{ translateY }], transform: [{ translateY }],
backgroundColor: themes[theme].backgroundColor, backgroundColor: colors.backgroundColor,
borderColor: themes[theme].separatorColor borderColor: colors.separatorColor
} }
]} ]}
testID='rooms-list-header-server-dropdown' testID='rooms-list-header-server-dropdown'
> >
<View style={[styles.dropdownContainerHeader, styles.serverHeader, { borderColor: themes[theme].separatorColor }]}> <View style={[styles.dropdownContainerHeader, styles.serverHeader, { borderColor: colors.separatorColor }]}>
<Text style={[styles.serverHeaderText, { color: themes[theme].auxiliaryText }]}>{I18n.t('Server')}</Text> <Text style={[styles.serverHeaderText, { color: colors.auxiliaryText }]}>{I18n.t('Server')}</Text>
<TouchableOpacity onPress={this.addServer} testID='rooms-list-header-server-add'> <TouchableOpacity onPress={addServer} testID='rooms-list-header-server-add'>
<Text style={[styles.serverHeaderAdd, { color: themes[theme].tintColor }]}>{I18n.t('Add_Server')}</Text> <Text style={[styles.serverHeaderAdd, { color: colors.tintColor }]}>{I18n.t('Add_Server')}</Text>
</TouchableOpacity> </TouchableOpacity>
</View> </View>
<FlatList <FlatList
style={{ maxHeight: maxRows * ROW_HEIGHT }} style={{ maxHeight: MAX_ROWS * ROW_HEIGHT }}
data={servers} data={servers}
keyExtractor={item => item.id} keyExtractor={item => item.id}
renderItem={this.renderServer} renderItem={renderItem}
ItemSeparatorComponent={List.Separator} ItemSeparatorComponent={List.Separator}
keyboardShouldPersistTaps='always' keyboardShouldPersistTaps='always'
/> />
@ -230,22 +213,15 @@ class ServerDropdown extends Component<IServerDropdownProps, IServerDropdownStat
<Button <Button
title={I18n.t('Create_a_new_workspace')} title={I18n.t('Create_a_new_workspace')}
type='secondary' type='secondary'
onPress={this.createWorkspace} onPress={createWorkspace}
testID='rooms-list-header-create-workspace-button' testID='rooms-list-header-create-workspace-button'
style={styles.buttonCreateWorkspace} style={styles.buttonCreateWorkspace}
color={themes[theme].tintColor} color={colors.tintColor}
styleText={[styles.serverHeaderAdd, { textAlign: 'center' }]} styleText={[styles.serverHeaderAdd, { textAlign: 'center' }]}
/> />
</Animated.View> </Animated.View>
</> </>
); );
} };
}
const mapStateToProps = (state: IApplicationState) => ({ export default ServerDropdown;
closeServerDropdown: state.rooms.closeServerDropdown,
server: state.server.server,
isMasterDetail: state.app.isMasterDetail
});
export default connect(mapStateToProps)(withSafeAreaInsets(withTheme(ServerDropdown)));

View File

@ -986,16 +986,14 @@ class RoomsListView extends React.Component<IRoomsListViewProps, IRoomsListViewS
render = () => { render = () => {
console.count(`${this.constructor.name}.render calls`); console.count(`${this.constructor.name}.render calls`);
const { showServerDropdown, theme, navigation } = this.props; const { showServerDropdown, theme } = this.props;
return ( return (
<SafeAreaView testID='rooms-list-view' style={{ backgroundColor: themes[theme].backgroundColor }}> <SafeAreaView testID='rooms-list-view' style={{ backgroundColor: themes[theme].backgroundColor }}>
<StatusBar /> <StatusBar />
{this.renderHeader()} {this.renderHeader()}
{this.renderScroll()} {this.renderScroll()}
{/* TODO - this ts-ignore is here because the route props, on IBaseScreen*/} {showServerDropdown ? <ServerDropdown /> : null}
{/* @ts-ignore*/}
{showServerDropdown ? <ServerDropdown navigation={navigation} theme={theme} /> : null}
</SafeAreaView> </SafeAreaView>
); );
}; };

View File

@ -1,47 +1,40 @@
import React from 'react'; import React, { useEffect, useRef } from 'react';
import { Animated, Easing, TouchableWithoutFeedback } from 'react-native'; import { Animated, Easing, TouchableWithoutFeedback } from 'react-native';
import { EdgeInsets, withSafeAreaInsets } from 'react-native-safe-area-context'; import { useSafeAreaInsets } from 'react-native-safe-area-context';
import styles from '../styles'; import styles from '../styles';
import { themes } from '../../../lib/constants';
import { TSupportedThemes, withTheme } from '../../../theme';
import { headerHeight } from '../../../lib/methods/helpers/navigation'; import { headerHeight } from '../../../lib/methods/helpers/navigation';
import * as List from '../../../containers/List'; import * as List from '../../../containers/List';
import { Filter } from '../filters'; import { Filter } from '../filters';
import DropdownItemFilter from './DropdownItemFilter'; import DropdownItemFilter from './DropdownItemFilter';
import DropdownItemHeader from './DropdownItemHeader'; import DropdownItemHeader from './DropdownItemHeader';
import { useTheme } from '../../../theme';
const ANIMATION_DURATION = 200; const ANIMATION_DURATION = 200;
interface IDropdownProps { interface IDropdownProps {
isMasterDetail?: boolean; isMasterDetail?: boolean;
theme: TSupportedThemes;
insets?: EdgeInsets;
currentFilter: Filter; currentFilter: Filter;
onClose: () => void; onClose: () => void;
onFilterSelected: (value: Filter) => void; onFilterSelected: (value: Filter) => void;
} }
class Dropdown extends React.Component<IDropdownProps> { const Dropdown = ({ isMasterDetail, currentFilter, onClose, onFilterSelected }: IDropdownProps) => {
private animatedValue: Animated.Value; const animatedValue = useRef(new Animated.Value(0)).current;
const { colors } = useTheme();
const insets = useSafeAreaInsets();
constructor(props: IDropdownProps) { useEffect(() => {
super(props); Animated.timing(animatedValue, {
this.animatedValue = new Animated.Value(0);
}
componentDidMount() {
Animated.timing(this.animatedValue, {
toValue: 1, toValue: 1,
duration: ANIMATION_DURATION, duration: ANIMATION_DURATION,
easing: Easing.inOut(Easing.quad), easing: Easing.inOut(Easing.quad),
useNativeDriver: true useNativeDriver: true
}).start(); }).start();
} }, [animatedValue]);
close = () => { const close = () => {
const { onClose } = this.props; Animated.timing(animatedValue, {
Animated.timing(this.animatedValue, {
toValue: 0, toValue: 0,
duration: ANIMATION_DURATION, duration: ANIMATION_DURATION,
easing: Easing.inOut(Easing.quad), easing: Easing.inOut(Easing.quad),
@ -49,26 +42,26 @@ class Dropdown extends React.Component<IDropdownProps> {
}).start(() => onClose()); }).start(() => onClose());
}; };
render() { const heightDestination = isMasterDetail ? headerHeight + insets.top : 0;
const { isMasterDetail, insets, theme, currentFilter, onFilterSelected } = this.props;
const statusBarHeight = insets?.top ?? 0; const translateY = animatedValue.interpolate({
const heightDestination = isMasterDetail ? headerHeight + statusBarHeight : 0;
const translateY = this.animatedValue.interpolate({
inputRange: [0, 1], inputRange: [0, 1],
outputRange: [-300, heightDestination] // approximated height of the component when closed/open outputRange: [-300, heightDestination] // approximated height of the component when closed/open
}); });
const backdropOpacity = this.animatedValue.interpolate({
const backdropOpacity = animatedValue.interpolate({
inputRange: [0, 1], inputRange: [0, 1],
outputRange: [0, themes[theme].backdropOpacity] outputRange: [0, colors.backdropOpacity]
}); });
return ( return (
<> <>
<TouchableWithoutFeedback onPress={this.close}> <TouchableWithoutFeedback onPress={close}>
<Animated.View <Animated.View
style={[ style={[
styles.backdrop, styles.backdrop,
{ {
backgroundColor: themes[theme].backdropColor, backgroundColor: colors.backdropColor,
opacity: backdropOpacity, opacity: backdropOpacity,
top: heightDestination top: heightDestination
} }
@ -80,12 +73,12 @@ class Dropdown extends React.Component<IDropdownProps> {
styles.dropdownContainer, styles.dropdownContainer,
{ {
transform: [{ translateY }], transform: [{ translateY }],
backgroundColor: themes[theme].backgroundColor, backgroundColor: colors.backgroundColor,
borderColor: themes[theme].separatorColor borderColor: colors.separatorColor
} }
]} ]}
> >
<DropdownItemHeader currentFilter={currentFilter} onPress={this.close} /> <DropdownItemHeader currentFilter={currentFilter} onPress={close} />
<List.Separator /> <List.Separator />
<DropdownItemFilter currentFilter={currentFilter} value={Filter.All} onPress={onFilterSelected} /> <DropdownItemFilter currentFilter={currentFilter} value={Filter.All} onPress={onFilterSelected} />
<DropdownItemFilter currentFilter={currentFilter} value={Filter.Following} onPress={onFilterSelected} /> <DropdownItemFilter currentFilter={currentFilter} value={Filter.Following} onPress={onFilterSelected} />
@ -93,7 +86,6 @@ class Dropdown extends React.Component<IDropdownProps> {
</Animated.View> </Animated.View>
</> </>
); );
} };
}
export default withTheme(withSafeAreaInsets(Dropdown)); export default Dropdown;

View File

@ -516,19 +516,13 @@ class ThreadMessagesView extends React.Component<IThreadMessagesViewProps, IThre
render() { render() {
console.count(`${this.constructor.name}.render calls`); console.count(`${this.constructor.name}.render calls`);
const { showFilterDropdown, currentFilter } = this.state; const { showFilterDropdown, currentFilter } = this.state;
const { theme } = this.props;
return ( return (
<SafeAreaView testID='thread-messages-view'> <SafeAreaView testID='thread-messages-view'>
<StatusBar /> <StatusBar />
{this.renderContent()} {this.renderContent()}
{showFilterDropdown ? ( {showFilterDropdown ? (
<Dropdown <Dropdown currentFilter={currentFilter} onFilterSelected={this.onFilterSelected} onClose={this.closeFilterDropdown} />
currentFilter={currentFilter}
onFilterSelected={this.onFilterSelected}
onClose={this.closeFilterDropdown}
theme={theme}
/>
) : null} ) : null}
</SafeAreaView> </SafeAreaView>
); );