diff --git a/app/views/CannedResponsesListView/Dropdown/index.tsx b/app/views/CannedResponsesListView/Dropdown/index.tsx index a2f9156d7..9da55f407 100644 --- a/app/views/CannedResponsesListView/Dropdown/index.tsx +++ b/app/views/CannedResponsesListView/Dropdown/index.tsx @@ -1,10 +1,8 @@ -import React from 'react'; +import React, { useEffect, useRef } from 'react'; import { Animated, Easing, FlatList, TouchableWithoutFeedback } from 'react-native'; -import { withSafeAreaInsets } from 'react-native-safe-area-context'; import styles from '../styles'; -import { themes } from '../../../lib/constants'; -import { TSupportedThemes, withTheme } from '../../../theme'; +import { useTheme } from '../../../theme'; import * as List from '../../../containers/List'; import DropdownItemFilter from './DropdownItemFilter'; import DropdownItemHeader from './DropdownItemHeader'; @@ -12,35 +10,31 @@ import { ROW_HEIGHT } from './DropdownItem'; import { ILivechatDepartment } from '../../../definitions/ILivechatDepartment'; const ANIMATION_DURATION = 200; +const HEIGHT_DESTINATION = 0; +const MAX_ROWS = 5; interface IDropdownProps { - theme?: TSupportedThemes; currentDepartment: ILivechatDepartment; onClose: () => void; onDepartmentSelected: (value: ILivechatDepartment) => void; departments: ILivechatDepartment[]; } -class Dropdown extends React.Component { - private animatedValue: Animated.Value; +const Dropdown = ({ currentDepartment, onClose, onDepartmentSelected, departments }: IDropdownProps) => { + const animatedValue = useRef(new Animated.Value(0)).current; + const { colors } = useTheme(); - constructor(props: IDropdownProps) { - super(props); - this.animatedValue = new Animated.Value(0); - } - - componentDidMount() { - Animated.timing(this.animatedValue, { + useEffect(() => { + Animated.timing(animatedValue, { toValue: 1, duration: ANIMATION_DURATION, easing: Easing.inOut(Easing.quad), useNativeDriver: true }).start(); - } + }, [animatedValue]); - close = () => { - const { onClose } = this.props; - Animated.timing(this.animatedValue, { + const close = () => { + Animated.timing(animatedValue, { toValue: 0, duration: ANIMATION_DURATION, easing: Easing.inOut(Easing.quad), @@ -48,58 +42,54 @@ class Dropdown extends React.Component { }).start(() => onClose()); }; - render() { - const { theme, currentDepartment, onDepartmentSelected, departments } = this.props; - const heightDestination = 0; - const translateY = this.animatedValue.interpolate({ - inputRange: [0, 1], - outputRange: [-300, heightDestination] // approximated height of the component when closed/open - }); - const backdropOpacity = this.animatedValue.interpolate({ - inputRange: [0, 1], - outputRange: [0, themes[theme!].backdropOpacity] - }); + const translateY = animatedValue.interpolate({ + inputRange: [0, 1], + outputRange: [-300, HEIGHT_DESTINATION] // approximated height of the component when closed/open + }); - const maxRows = 5; - return ( - <> - - - + const backdropOpacity = animatedValue.interpolate({ + inputRange: [0, 1], + outputRange: [0, colors.backdropOpacity] + }); + + return ( + <> + - - - item._id} - renderItem={({ item }) => ( - - )} - keyboardShouldPersistTaps='always' - /> - - - ); - } -} + /> + + + + + item._id} + renderItem={({ item }) => ( + + )} + keyboardShouldPersistTaps='always' + /> + + + ); +}; -export default withTheme(withSafeAreaInsets(Dropdown)); +export default Dropdown; diff --git a/app/views/DirectoryView/Options.tsx b/app/views/DirectoryView/Options.tsx index 43c84fc09..4003dc13f 100644 --- a/app/views/DirectoryView/Options.tsx +++ b/app/views/DirectoryView/Options.tsx @@ -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 Touch from '../../containers/Touch'; import { CustomIcon, TIconsName } from '../../containers/CustomIcon'; import Check from '../../containers/Check'; import I18n from '../../i18n'; -import { SWITCH_TRACK_COLOR, themes } from '../../lib/constants'; +import { SWITCH_TRACK_COLOR } from '../../lib/constants'; import styles from './styles'; -import { TSupportedThemes } from '../../theme'; +import { useTheme } from '../../theme'; const ANIMATION_DURATION = 200; const ANIMATION_PROPS = { @@ -23,34 +23,34 @@ interface IDirectoryOptionsProps { close: Function; changeType: Function; toggleWorkspace(): void; - theme: TSupportedThemes; } -export default class DirectoryOptions extends PureComponent { - private animatedValue: Animated.Value; +const DirectoryOptions = ({ + type: propType, + globalUsers, + isFederationEnabled, + close: onClose, + changeType, + toggleWorkspace +}: IDirectoryOptionsProps) => { + const animatedValue = useRef(new Animated.Value(0)).current; + const { colors } = useTheme(); - constructor(props: IDirectoryOptionsProps) { - super(props); - this.animatedValue = new Animated.Value(0); - } - - componentDidMount() { - Animated.timing(this.animatedValue, { + useEffect(() => { + Animated.timing(animatedValue, { toValue: 1, ...ANIMATION_PROPS }).start(); - } + }, [animatedValue]); - close = () => { - const { close } = this.props; - Animated.timing(this.animatedValue, { + const close = () => { + Animated.timing(animatedValue, { toValue: 0, ...ANIMATION_PROPS - }).start(() => close()); + }).start(() => onClose()); }; - renderItem = (itemType: string) => { - const { changeType, type: propType, theme } = this.props; + const renderItem = (itemType: string) => { let text = 'Users'; let icon: TIconsName = 'user'; if (itemType === 'channels') { @@ -66,70 +66,61 @@ export default class DirectoryOptions extends PureComponent changeType(itemType)} style={styles.dropdownItemButton} accessibilityLabel={I18n.t(text)}> - - {I18n.t(text)} + + {I18n.t(text)} {propType === itemType ? : null} ); }; - render() { - const translateY = this.animatedValue.interpolate({ - inputRange: [0, 1], - outputRange: [-326, 0] - }); - const { globalUsers, toggleWorkspace, isFederationEnabled, theme } = this.props; - const backdropOpacity = this.animatedValue.interpolate({ - inputRange: [0, 1], - outputRange: [0, themes[theme].backdropOpacity] - }); - return ( - <> - - - - - - - {I18n.t('Search_by')} - - - - {this.renderItem('channels')} - {this.renderItem('users')} - {this.renderItem('teams')} - {isFederationEnabled ? ( - <> - - - - - {I18n.t('Search_global_users')} - - - {I18n.t('Search_global_users_description')} - - - + const translateY = animatedValue.interpolate({ + inputRange: [0, 1], + outputRange: [-326, 0] + }); + + const backdropOpacity = animatedValue.interpolate({ + inputRange: [0, 1], + outputRange: [0, colors.backdropOpacity] + }); + + return ( + <> + + + + + + + {I18n.t('Search_by')} + + + + {renderItem('channels')} + {renderItem('users')} + {renderItem('teams')} + {isFederationEnabled ? ( + <> + + + + {I18n.t('Search_global_users')} + + {I18n.t('Search_global_users_description')} + - - ) : null} - - - ); - } -} + + + + ) : null} + + + ); +}; + +export default DirectoryOptions; diff --git a/app/views/DirectoryView/index.tsx b/app/views/DirectoryView/index.tsx index ee5f9a357..131ef2cb4 100644 --- a/app/views/DirectoryView/index.tsx +++ b/app/views/DirectoryView/index.tsx @@ -315,7 +315,6 @@ class DirectoryView extends React.Component {showOptionsDropdown ? ( { - insets?: { - top: number; - }; - closeServerDropdown: boolean; - server: string; - isMasterDetail: boolean; -} +const ServerDropdown = () => { + const animatedValue = useRef(new Animated.Value(0)).current; + const subscription = useRef(); + const newServerTimeout = useRef | false>(); + const isMounted = useRef(false); + const [servers, setServers] = useState([]); + 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 { - servers: TServerModel[]; -} + useEffect(() => { + if (isMounted.current) close(); + }, [closeServerDropdown]); -class ServerDropdown extends Component { - private animatedValue: Animated.Value; - private subscription?: Subscription; - private newServerTimeout?: ReturnType | false; + useEffect(() => { + isMounted.current = true; - constructor(props: IServerDropdownProps) { - super(props); - this.state = { servers: [] }; - this.animatedValue = new Animated.Value(0); - } + const init = async () => { + const serversDB = database.servers; + const observable = await serversDB.get('servers').query().observeWithColumns(['name']); - async componentDidMount() { - const serversDB = database.servers; - const observable = await serversDB.get('servers').query().observeWithColumns(['name']); + subscription.current = observable.subscribe(data => { + setServers(data); + }); - this.subscription = observable.subscribe(data => { - this.setState({ servers: data }); - }); + Animated.timing(animatedValue, { + toValue: 1, + duration: ANIMATION_DURATION, + easing: Easing.inOut(Easing.quad), + useNativeDriver: true + }).start(); + }; + init(); - Animated.timing(this.animatedValue, { - toValue: 1, - duration: ANIMATION_DURATION, - easing: Easing.inOut(Easing.quad), - useNativeDriver: true - }).start(); - } + return () => { + if (newServerTimeout.current) { + clearTimeout(newServerTimeout.current); + newServerTimeout.current = false; + } + if (subscription.current && subscription.current.unsubscribe) { + subscription.current.unsubscribe(); + } + }; + }, []); - componentDidUpdate(prevProps: IServerDropdownProps) { - const { closeServerDropdown } = this.props; - if (prevProps.closeServerDropdown !== closeServerDropdown) { - this.close(); - } - } - - componentWillUnmount() { - if (this.newServerTimeout) { - clearTimeout(this.newServerTimeout); - this.newServerTimeout = false; - } - if (this.subscription && this.subscription.unsubscribe) { - this.subscription.unsubscribe(); - } - } - - close = () => { - const { dispatch } = this.props; - Animated.timing(this.animatedValue, { + const close = () => { + Animated.timing(animatedValue, { toValue: 0, duration: ANIMATION_DURATION, easing: Easing.inOut(Easing.quad), @@ -96,7 +87,7 @@ class ServerDropdown extends Component dispatch(toggleServerDropdown())); }; - createWorkspace = async () => { + const createWorkspace = async () => { logEvent(events.RL_CREATE_NEW_WORKSPACE); try { await Linking.openURL('https://cloud.rocket.chat/trial'); @@ -105,52 +96,49 @@ class ServerDropdown extends Component { - const { dispatch } = this.props; + const navToNewServer = (previousServer: string) => { batch(() => { dispatch(appStart({ root: RootEnum.ROOT_OUTSIDE })); dispatch(serverInitAdd(previousServer)); }); }; - addServer = () => { + const addServer = () => { logEvent(events.RL_ADD_SERVER); - const { server } = this.props; - this.close(); + close(); setTimeout(() => { - this.navToNewServer(server); + navToNewServer(server); }, ANIMATION_DURATION); }; - select = async (server: string, version?: string) => { - const { server: currentServer, dispatch, isMasterDetail } = this.props; - this.close(); - if (currentServer !== server) { + const select = async (serverParam: string, version?: string) => { + close(); + if (server !== serverParam) { logEvent(events.RL_CHANGE_SERVER); - const userId = UserPreferences.getString(`${TOKEN_KEY}-${server}`); + const userId = UserPreferences.getString(`${TOKEN_KEY}-${serverParam}`); if (isMasterDetail) { goRoom({ item: {}, isMasterDetail }); } if (!userId) { setTimeout(() => { - this.navToNewServer(currentServer); - this.newServerTimeout = setTimeout(() => { - EventEmitter.emit('NewServer', { server }); + navToNewServer(server); + newServerTimeout.current = setTimeout(() => { + EventEmitter.emit('NewServer', { server: serverParam }); }, ANIMATION_DURATION); }, ANIMATION_DURATION); } else { - await localAuthenticate(server); - dispatch(selectServerRequest(server, version, true, true)); + await localAuthenticate(serverParam); + dispatch(selectServerRequest(serverParam, version, true, true)); } } }; - remove = (server: string) => + const remove = (server: string) => showConfirmationAlert({ message: I18n.t('This_will_remove_all_data_from_this_server'), confirmationText: I18n.t('Delete'), onPress: async () => { - this.close(); + close(); try { await removeServer({ server }); } catch { @@ -159,93 +147,81 @@ class ServerDropdown extends Component { - const { server } = this.props; + const renderItem = ({ item }: { item: { id: string; iconURL: string; name: string; version: string } }) => ( + select(item.id, item.version)} + onLongPress={() => item.id === server || remove(item.id)} + hasCheck={item.id === server} + /> + ); - return ( - this.select(item.id, item.version)} - onLongPress={() => item.id === server || this.remove(item.id)} - hasCheck={item.id === server} - /> - ); - }; + const initialTop = 87 + Math.min(servers.length, MAX_ROWS) * ROW_HEIGHT; + const statusBarHeight = insets?.top ?? 0; + const heightDestination = isMasterDetail ? headerHeight + statusBarHeight : 0; - render() { - 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 heightDestination = isMasterDetail ? headerHeight + statusBarHeight : 0; - const translateY = this.animatedValue.interpolate({ - inputRange: [0, 1], - outputRange: [-initialTop, heightDestination] - }); - const backdropOpacity = this.animatedValue.interpolate({ - inputRange: [0, 1], - outputRange: [0, themes[theme].backdropOpacity] - }); - return ( - <> - - - + const translateY = animatedValue.interpolate({ + inputRange: [0, 1], + outputRange: [-initialTop, heightDestination] + }); + + const backdropOpacity = animatedValue.interpolate({ + inputRange: [0, 1], + outputRange: [0, colors.backdropOpacity] + }); + + return ( + <> + - - {I18n.t('Server')} - - {I18n.t('Add_Server')} - - - item.id} - renderItem={this.renderServer} - ItemSeparatorComponent={List.Separator} - keyboardShouldPersistTaps='always' - /> - -