From 276172b56d960f081ae4d291fb91a92559cfb86f Mon Sep 17 00:00:00 2001 From: Diego Mello Date: Thu, 18 Jul 2024 16:46:58 -0300 Subject: [PATCH] feat(a11y): turn dropdowns into action sheets (#5798) --- app/actions/actionsTypes.ts | 2 - app/actions/rooms.ts | 12 -- app/i18n/locales/en.json | 3 - app/lib/methods/helpers/log/events.ts | 1 - app/lib/methods/helpers/navigation/index.ts | 4 - app/reducers/rooms.test.ts | 16 +- app/reducers/rooms.ts | 14 -- .../DepartmentFilter/DepartmentItemFilter.tsx | 47 ++++++ .../DepartmentFilter/index.tsx | 42 +++++ .../Dropdown/DropdownItem.tsx | 46 ------ .../Dropdown/DropdownItemFilter.tsx | 20 --- .../Dropdown/DropdownItemHeader.tsx | 15 -- .../Dropdown/index.tsx | 94 ----------- app/views/CannedResponsesListView/index.tsx | 58 ++----- app/views/CannedResponsesListView/styles.ts | 9 -- app/views/DirectoryView/Options.tsx | 105 ++++-------- app/views/DirectoryView/index.tsx | 108 +++++-------- app/views/DirectoryView/styles.ts | 59 +------ app/views/RoomsListView/Header/Header.tsx | 14 +- app/views/RoomsListView/Header/index.tsx | 18 +-- .../{ServerDropdown.tsx => ServersList.tsx} | 151 +++++------------- app/views/RoomsListView/index.tsx | 17 +- app/views/RoomsListView/styles.ts | 11 +- .../Dropdown/DropdownItem.tsx | 42 ----- .../Dropdown/DropdownItemFilter.tsx | 17 -- .../Dropdown/DropdownItemHeader.tsx | 28 ---- .../ThreadMessagesView/Dropdown/index.tsx | 92 ----------- app/views/ThreadMessagesView/index.tsx | 64 ++++---- app/views/ThreadMessagesView/styles.ts | 9 -- e2e/tests/assorted/01-e2eencryption.spec.ts | 8 +- e2e/tests/assorted/07-changeserver.spec.ts | 17 +- .../assorted/09-joinfromdirectory.spec.ts | 4 +- e2e/tests/assorted/10-deleteserver.spec.ts | 12 +- e2e/tests/assorted/11-deeplinking.spec.ts | 4 +- 34 files changed, 292 insertions(+), 871 deletions(-) create mode 100644 app/views/CannedResponsesListView/DepartmentFilter/DepartmentItemFilter.tsx create mode 100644 app/views/CannedResponsesListView/DepartmentFilter/index.tsx delete mode 100644 app/views/CannedResponsesListView/Dropdown/DropdownItem.tsx delete mode 100644 app/views/CannedResponsesListView/Dropdown/DropdownItemFilter.tsx delete mode 100644 app/views/CannedResponsesListView/Dropdown/DropdownItemHeader.tsx delete mode 100644 app/views/CannedResponsesListView/Dropdown/index.tsx rename app/views/RoomsListView/{ServerDropdown.tsx => ServersList.tsx} (50%) delete mode 100644 app/views/ThreadMessagesView/Dropdown/DropdownItem.tsx delete mode 100644 app/views/ThreadMessagesView/Dropdown/DropdownItemFilter.tsx delete mode 100644 app/views/ThreadMessagesView/Dropdown/DropdownItemHeader.tsx delete mode 100644 app/views/ThreadMessagesView/Dropdown/index.tsx diff --git a/app/actions/actionsTypes.ts b/app/actions/actionsTypes.ts index d5399d4577..fe6fb521e8 100644 --- a/app/actions/actionsTypes.ts +++ b/app/actions/actionsTypes.ts @@ -16,8 +16,6 @@ export const ROOMS = createRequestTypes('ROOMS', [ ...defaultTypes, 'REFRESH', 'SET_SEARCH', - 'CLOSE_SERVER_DROPDOWN', - 'TOGGLE_SERVER_DROPDOWN', 'OPEN_SEARCH_HEADER', 'CLOSE_SEARCH_HEADER' ]); diff --git a/app/actions/rooms.ts b/app/actions/rooms.ts index 514582ff62..7956f90870 100644 --- a/app/actions/rooms.ts +++ b/app/actions/rooms.ts @@ -53,18 +53,6 @@ export function setSearch(searchText: string): ISetSearch { }; } -export function closeServerDropdown(): Action { - return { - type: ROOMS.CLOSE_SERVER_DROPDOWN - }; -} - -export function toggleServerDropdown(): Action { - return { - type: ROOMS.TOGGLE_SERVER_DROPDOWN - }; -} - export function openSearchHeader(): Action { return { type: ROOMS.OPEN_SEARCH_HEADER diff --git a/app/i18n/locales/en.json b/app/i18n/locales/en.json index 556eb457e7..a9c3e7d668 100644 --- a/app/i18n/locales/en.json +++ b/app/i18n/locales/en.json @@ -740,9 +740,6 @@ "This_will_remove_all_data_from_this_server": "This will remove all data from this workspace.", "Thread": "Thread", "Threads": "Threads", - "Threads_displaying_all": "Displaying all", - "Threads_displaying_following": "Displaying following", - "Threads_displaying_unread": "Displaying unread", "Timezone": "Timezone", "To_continue_using_RocketChat": "To continue using the mobile app, you need to change your password.", "Token_expired": "Your session has expired. Please log in again.", diff --git a/app/lib/methods/helpers/log/events.ts b/app/lib/methods/helpers/log/events.ts index f028d0e41b..2b7884334c 100644 --- a/app/lib/methods/helpers/log/events.ts +++ b/app/lib/methods/helpers/log/events.ts @@ -54,7 +54,6 @@ export default { SET_STATUS_FAIL: 'set_status_fail', // ROOMS LIST VIEW - RL_TOGGLE_SERVER_DROPDOWN: 'rl_toggle_server_dropdown', RL_ADD_SERVER: 'rl_add_server', RL_CHANGE_SERVER: 'rl_change_server', RL_GO_NEW_MSG: 'rl_go_new_msg', diff --git a/app/lib/methods/helpers/navigation/index.ts b/app/lib/methods/helpers/navigation/index.ts index 676b4fb4f4..e20101c47f 100644 --- a/app/lib/methods/helpers/navigation/index.ts +++ b/app/lib/methods/helpers/navigation/index.ts @@ -3,7 +3,6 @@ import { DarkTheme, DefaultTheme } from '@react-navigation/native'; import { themes } from '../../../constants'; import { TSupportedThemes } from '../../../../theme'; -import { isIOS } from '../deviceInfo'; import sharedStyles from '../../../../views/Styles'; export * from './animations'; @@ -29,9 +28,6 @@ export const drawerStyle = { width: 320 }; -// TODO: Remove it once we migrate dropdowns to action sheet -export const headerHeight = isIOS ? 50 : 56; - export const themedHeader = (theme: TSupportedThemes) => ({ headerStyle: { ...borderBottom(theme), diff --git a/app/reducers/rooms.test.ts b/app/reducers/rooms.test.ts index 4bf1312ec8..7e2945bd1a 100644 --- a/app/reducers/rooms.test.ts +++ b/app/reducers/rooms.test.ts @@ -1,13 +1,11 @@ import { closeSearchHeader, - closeServerDropdown, openSearchHeader, roomsFailure, roomsRefresh, roomsRequest, roomsSuccess, - setSearch, - toggleServerDropdown + setSearch } from '../actions/rooms'; import { mockedStore } from './mockedStore'; import { initialState } from './rooms'; @@ -45,18 +43,6 @@ describe('test selectedUsers reducer', () => { expect(state.searchText).toEqual('dog'); }); - it('should return modified store after call closeServerDropdown', () => { - mockedStore.dispatch(closeServerDropdown()); - const state = mockedStore.getState().rooms; - expect(state.closeServerDropdown).toEqual(!initialState.closeServerDropdown); - }); - - it('should return modified store after call toggleServerDropdown', () => { - mockedStore.dispatch(toggleServerDropdown()); - const state = mockedStore.getState().rooms; - expect(state.showServerDropdown).toEqual(!initialState.showServerDropdown); - }); - it('should return modified store after call openSearchHeader', () => { mockedStore.dispatch(openSearchHeader()); const state = mockedStore.getState().rooms; diff --git a/app/reducers/rooms.ts b/app/reducers/rooms.ts index 1a67050865..6b309c2ac4 100644 --- a/app/reducers/rooms.ts +++ b/app/reducers/rooms.ts @@ -7,8 +7,6 @@ export interface IRooms { failure: boolean; errorMessage: Record | string; searchText: string; - showServerDropdown: boolean; - closeServerDropdown: boolean; showSearchHeader: boolean; } @@ -18,8 +16,6 @@ export const initialState: IRooms = { failure: false, errorMessage: {}, searchText: '', - showServerDropdown: false, - closeServerDropdown: false, showSearchHeader: false }; @@ -57,16 +53,6 @@ export default function rooms(state = initialState, action: IRoomsAction): IRoom ...state, searchText: action.searchText }; - case ROOMS.CLOSE_SERVER_DROPDOWN: - return { - ...state, - closeServerDropdown: !state.closeServerDropdown - }; - case ROOMS.TOGGLE_SERVER_DROPDOWN: - return { - ...state, - showServerDropdown: !state.showServerDropdown - }; case ROOMS.OPEN_SEARCH_HEADER: return { ...state, diff --git a/app/views/CannedResponsesListView/DepartmentFilter/DepartmentItemFilter.tsx b/app/views/CannedResponsesListView/DepartmentFilter/DepartmentItemFilter.tsx new file mode 100644 index 0000000000..2b67684907 --- /dev/null +++ b/app/views/CannedResponsesListView/DepartmentFilter/DepartmentItemFilter.tsx @@ -0,0 +1,47 @@ +import React from 'react'; +import { StyleSheet, Text, View } from 'react-native'; + +import { ILivechatDepartment } from '../../../definitions/ILivechatDepartment'; +import { useTheme } from '../../../theme'; +import Touch from '../../../containers/Touch'; +import { CustomIcon } from '../../../containers/CustomIcon'; +import sharedStyles from '../../Styles'; + +interface IDepartmentItemFilter { + currentDepartment: ILivechatDepartment; + value: ILivechatDepartment; + onPress: (value: ILivechatDepartment) => void; +} + +export const ROW_HEIGHT = 44; + +const styles = StyleSheet.create({ + container: { + paddingVertical: 11, + height: ROW_HEIGHT, + paddingHorizontal: 16, + flexDirection: 'row', + alignItems: 'center' + }, + text: { + flex: 1, + fontSize: 16, + ...sharedStyles.textRegular + } +}); + +const DepartmentItemFilter = ({ currentDepartment, value, onPress }: IDepartmentItemFilter): JSX.Element => { + const { colors } = useTheme(); + const iconName = currentDepartment?._id === value?._id ? 'check' : null; + + return ( + onPress(value)} style={{ backgroundColor: colors.surfaceRoom }}> + + {value?.name} + {iconName ? : null} + + + ); +}; + +export default DepartmentItemFilter; diff --git a/app/views/CannedResponsesListView/DepartmentFilter/index.tsx b/app/views/CannedResponsesListView/DepartmentFilter/index.tsx new file mode 100644 index 0000000000..00eb33f693 --- /dev/null +++ b/app/views/CannedResponsesListView/DepartmentFilter/index.tsx @@ -0,0 +1,42 @@ +import React from 'react'; +import { FlatList, View } from 'react-native'; +import { useSafeAreaInsets } from 'react-native-safe-area-context'; + +import { useTheme } from '../../../theme'; +import * as List from '../../../containers/List'; +import DepartmentItemFilter, { ROW_HEIGHT } from './DepartmentItemFilter'; +import { ILivechatDepartment } from '../../../definitions/ILivechatDepartment'; + +const MAX_ROWS = 5; + +interface IDepartmentFilterProps { + currentDepartment: ILivechatDepartment; + onDepartmentSelected: (value: ILivechatDepartment) => void; + departments: ILivechatDepartment[]; +} + +const DepartmentFilter = ({ currentDepartment, onDepartmentSelected, departments }: IDepartmentFilterProps) => { + const { colors } = useTheme(); + const insets = useSafeAreaInsets(); + + return ( + + item._id} + renderItem={({ item }) => ( + + )} + ItemSeparatorComponent={List.Separator} + /> + + ); +}; + +export default DepartmentFilter; diff --git a/app/views/CannedResponsesListView/Dropdown/DropdownItem.tsx b/app/views/CannedResponsesListView/Dropdown/DropdownItem.tsx deleted file mode 100644 index 840bd07ea3..0000000000 --- a/app/views/CannedResponsesListView/Dropdown/DropdownItem.tsx +++ /dev/null @@ -1,46 +0,0 @@ -import React from 'react'; -import { StyleSheet, Text, View } from 'react-native'; - -import { themes } from '../../../lib/constants'; -import { useTheme } from '../../../theme'; -import Touch from '../../../containers/Touch'; -import { CustomIcon, TIconsName } from '../../../containers/CustomIcon'; -import sharedStyles from '../../Styles'; - -export const ROW_HEIGHT = 44; - -const styles = StyleSheet.create({ - container: { - paddingVertical: 11, - height: ROW_HEIGHT, - paddingHorizontal: 16, - flexDirection: 'row', - alignItems: 'center' - }, - text: { - flex: 1, - fontSize: 16, - ...sharedStyles.textRegular - } -}); - -interface IDropdownItem { - text: string; - iconName: TIconsName | null; - onPress: () => void; -} - -const DropdownItem = React.memo(({ onPress, iconName, text }: IDropdownItem) => { - const { theme } = useTheme(); - - return ( - - - {text} - {iconName ? : null} - - - ); -}); - -export default DropdownItem; diff --git a/app/views/CannedResponsesListView/Dropdown/DropdownItemFilter.tsx b/app/views/CannedResponsesListView/Dropdown/DropdownItemFilter.tsx deleted file mode 100644 index fa165c67c5..0000000000 --- a/app/views/CannedResponsesListView/Dropdown/DropdownItemFilter.tsx +++ /dev/null @@ -1,20 +0,0 @@ -import React from 'react'; - -import { ILivechatDepartment } from '../../../definitions/ILivechatDepartment'; -import DropdownItem from './DropdownItem'; - -interface IDropdownItemFilter { - currentDepartment: ILivechatDepartment; - value: ILivechatDepartment; - onPress: (value: ILivechatDepartment) => void; -} - -const DropdownItemFilter = ({ currentDepartment, value, onPress }: IDropdownItemFilter): JSX.Element => ( - onPress(value)} - /> -); - -export default DropdownItemFilter; diff --git a/app/views/CannedResponsesListView/Dropdown/DropdownItemHeader.tsx b/app/views/CannedResponsesListView/Dropdown/DropdownItemHeader.tsx deleted file mode 100644 index 5ebcaf8601..0000000000 --- a/app/views/CannedResponsesListView/Dropdown/DropdownItemHeader.tsx +++ /dev/null @@ -1,15 +0,0 @@ -import React from 'react'; - -import { ILivechatDepartment } from '../../../definitions/ILivechatDepartment'; -import DropdownItem from './DropdownItem'; - -interface IDropdownItemHeader { - department: ILivechatDepartment; - onPress: () => void; -} - -const DropdownItemHeader = ({ department, onPress }: IDropdownItemHeader): React.ReactElement => ( - -); - -export default DropdownItemHeader; diff --git a/app/views/CannedResponsesListView/Dropdown/index.tsx b/app/views/CannedResponsesListView/Dropdown/index.tsx deleted file mode 100644 index b50c394ed7..0000000000 --- a/app/views/CannedResponsesListView/Dropdown/index.tsx +++ /dev/null @@ -1,94 +0,0 @@ -import React, { useEffect, useRef } from 'react'; -import { Animated, Easing, FlatList, TouchableWithoutFeedback } from 'react-native'; - -import styles from '../styles'; -import { useTheme } from '../../../theme'; -import * as List from '../../../containers/List'; -import DropdownItemFilter from './DropdownItemFilter'; -import DropdownItemHeader from './DropdownItemHeader'; -import { ROW_HEIGHT } from './DropdownItem'; -import { ILivechatDepartment } from '../../../definitions/ILivechatDepartment'; - -const ANIMATION_DURATION = 200; -const HEIGHT_DESTINATION = 0; -const MAX_ROWS = 5; - -interface IDropdownProps { - currentDepartment: ILivechatDepartment; - onClose: () => void; - onDepartmentSelected: (value: ILivechatDepartment) => void; - departments: ILivechatDepartment[]; -} - -const Dropdown = ({ currentDepartment, onClose, onDepartmentSelected, departments }: IDropdownProps) => { - const animatedValue = useRef(new Animated.Value(0)).current; - const { colors } = useTheme(); - - useEffect(() => { - Animated.timing(animatedValue, { - toValue: 1, - duration: ANIMATION_DURATION, - easing: Easing.inOut(Easing.quad), - useNativeDriver: true - }).start(); - }, [animatedValue]); - - const close = () => { - Animated.timing(animatedValue, { - toValue: 0, - duration: ANIMATION_DURATION, - easing: Easing.inOut(Easing.quad), - useNativeDriver: true - }).start(() => onClose()); - }; - - const translateY = animatedValue.interpolate({ - inputRange: [0, 1], - outputRange: [-300, HEIGHT_DESTINATION] // approximated height of the component when closed/open - }); - - const backdropOpacity = animatedValue.interpolate({ - inputRange: [0, 1], - outputRange: [0, colors.backdropOpacity] - }); - - return ( - <> - - - - - - - item._id} - renderItem={({ item }) => ( - - )} - keyboardShouldPersistTaps='always' - /> - - - ); -}; - -export default Dropdown; diff --git a/app/views/CannedResponsesListView/index.tsx b/app/views/CannedResponsesListView/index.tsx index 7815a25e6e..ebf3886c5a 100644 --- a/app/views/CannedResponsesListView/index.tsx +++ b/app/views/CannedResponsesListView/index.tsx @@ -6,6 +6,7 @@ import { HeaderBackButton } from '@react-navigation/elements'; import database from '../../lib/database'; import I18n from '../../i18n'; +import { hideActionSheetRef, showActionSheetRef } from '../../containers/ActionSheet'; import SafeAreaView from '../../containers/SafeAreaView'; import StatusBar from '../../containers/StatusBar'; import ActivityIndicator from '../../containers/ActivityIndicator'; @@ -18,8 +19,7 @@ import * as List from '../../containers/List'; import { themes } from '../../lib/constants'; import log from '../../lib/methods/helpers/log'; import CannedResponseItem from './CannedResponseItem'; -import Dropdown from './Dropdown'; -import DropdownItemHeader from './Dropdown/DropdownItemHeader'; +import DepartmentFilter from './DepartmentFilter'; import styles from './styles'; import { ICannedResponse } from '../../definitions/ICannedResponse'; import { ChatsStackParamList } from '../../stacks/types'; @@ -57,11 +57,8 @@ const CannedResponsesListView = ({ navigation, route }: ICannedResponsesListView const [cannedResponses, setCannedResponses] = useState([]); const [cannedResponsesScopeName, setCannedResponsesScopeName] = useState([]); const [departments, setDepartments] = useState([]); - - // states used by the filter in Header and Dropdown const [isSearching, setIsSearching] = useState(false); const [currentDepartment, setCurrentDepartment] = useState(fixedScopes[0]); - const [showFilterDropdown, setShowFilterDropDown] = useState(false); // states used to do a fetch by onChangeText, onDepartmentSelect and onEndReached const [searchText, setSearchText] = useState(''); @@ -200,8 +197,8 @@ const CannedResponsesListView = ({ navigation, route }: ICannedResponsesListView setCurrentDepartment(value); setScope(department); setDepartmentId(depId); - setShowFilterDropDown(false); searchCallback(searchText, department, depId); + hideActionSheetRef(); }; const onEndReached = async () => { @@ -249,6 +246,7 @@ const CannedResponsesListView = ({ navigation, route }: ICannedResponsesListView ), headerRight: () => ( + setIsSearching(true)} /> ) @@ -268,36 +266,24 @@ const CannedResponsesListView = ({ navigation, route }: ICannedResponsesListView useEffect(() => { setHeader(); - }, [isSearching]); - - const showDropdown = () => { - if (isSearching) { - setSearchText(''); - setIsSearching(false); - } - setShowFilterDropDown(true); - }; + }, [isSearching, departments, currentDepartment]); - const renderFlatListHeader = () => { - if (!departments.length) { - return null; - } - return ( - <> - - - - ); + const showFilters = () => { + showActionSheetRef({ + children: ( + + ), + enableContentPanningGesture: false + }); }; const renderContent = () => { if (!cannedResponsesScopeName.length && !loading) { - return ( - <> - {renderFlatListHeader()} - - - ); + return ; } return ( )} keyExtractor={item => item._id || item.shortcut} - ListHeaderComponent={renderFlatListHeader} - stickyHeaderIndices={[0]} onEndReached={onEndReached} onEndReachedThreshold={0.5} ItemSeparatorComponent={List.Separator} @@ -330,14 +314,6 @@ const CannedResponsesListView = ({ navigation, route }: ICannedResponsesListView {renderContent()} - {showFilterDropdown ? ( - setShowFilterDropDown(false)} - /> - ) : null} ); }; diff --git a/app/views/CannedResponsesListView/styles.ts b/app/views/CannedResponsesListView/styles.ts index f0e9718671..c148743605 100644 --- a/app/views/CannedResponsesListView/styles.ts +++ b/app/views/CannedResponsesListView/styles.ts @@ -6,15 +6,6 @@ export default StyleSheet.create({ list: { flex: 1 }, - dropdownContainer: { - width: '100%', - position: 'absolute', - top: 0, - borderBottomWidth: StyleSheet.hairlineWidth - }, - backdrop: { - ...StyleSheet.absoluteFillObject - }, wrapCannedItem: { minHeight: 117, maxHeight: 141, diff --git a/app/views/DirectoryView/Options.tsx b/app/views/DirectoryView/Options.tsx index 6b64340d86..53530f95e3 100644 --- a/app/views/DirectoryView/Options.tsx +++ b/app/views/DirectoryView/Options.tsx @@ -1,26 +1,20 @@ -import React, { useEffect, useRef } from 'react'; -import { Animated, Easing, Text, TouchableWithoutFeedback, View } from 'react-native'; +import React from 'react'; +import { Text, View } from 'react-native'; +import { useSafeAreaInsets } from 'react-native-safe-area-context'; import Touch from '../../containers/Touch'; import { CustomIcon, TIconsName } from '../../containers/CustomIcon'; import Check from '../../containers/Check'; +import * as List from '../../containers/List'; import I18n from '../../i18n'; import styles from './styles'; import Switch from '../../containers/Switch'; import { useTheme } from '../../theme'; -const ANIMATION_DURATION = 200; -const ANIMATION_PROPS = { - duration: ANIMATION_DURATION, - easing: Easing.inOut(Easing.quad), - useNativeDriver: true -}; - interface IDirectoryOptionsProps { type: string; globalUsers: boolean; isFederationEnabled: boolean; - close: Function; changeType: Function; toggleWorkspace(): void; } @@ -29,26 +23,11 @@ const DirectoryOptions = ({ type: propType, globalUsers, isFederationEnabled, - close: onClose, changeType, toggleWorkspace }: IDirectoryOptionsProps) => { - const animatedValue = useRef(new Animated.Value(0)).current; const { colors } = useTheme(); - - useEffect(() => { - Animated.timing(animatedValue, { - toValue: 1, - ...ANIMATION_PROPS - }).start(); - }, [animatedValue]); - - const close = () => { - Animated.timing(animatedValue, { - toValue: 0, - ...ANIMATION_PROPS - }).start(() => onClose()); - }; + const insets = useSafeAreaInsets(); const renderItem = (itemType: string) => { let text = 'Users'; @@ -64,62 +43,40 @@ const DirectoryOptions = ({ } return ( - changeType(itemType)} style={styles.dropdownItemButton} accessibilityLabel={I18n.t(text)} accessible> - - - {I18n.t(text)} + changeType(itemType)} style={styles.filterItemButton} accessibilityLabel={I18n.t(text)} accessible> + + + {I18n.t(text)} {propType === itemType ? : null} ); }; - 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')} - - - + + + {renderItem('channels')} + + {renderItem('users')} + + {renderItem('teams')} + + {isFederationEnabled ? ( + <> + + + + {I18n.t('Search_global_users')} + + {I18n.t('Search_global_users_description')} + - - ) : null} - - + + + + ) : null} + ); }; diff --git a/app/views/DirectoryView/index.tsx b/app/views/DirectoryView/index.tsx index 5a085e05b5..7c872a363b 100644 --- a/app/views/DirectoryView/index.tsx +++ b/app/views/DirectoryView/index.tsx @@ -1,18 +1,17 @@ import React from 'react'; -import { FlatList, ListRenderItem, Text, View } from 'react-native'; +import { FlatList, ListRenderItem } from 'react-native'; import { connect } from 'react-redux'; import { StackNavigationOptions, StackNavigationProp } from '@react-navigation/stack'; import { CompositeNavigationProp } from '@react-navigation/native'; +import { hideActionSheetRef, showActionSheetRef } from '../../containers/ActionSheet'; import { ChatsStackParamList } from '../../stacks/types'; import { MasterDetailInsideStackParamList } from '../../stacks/MasterDetailStack/types'; import * as List from '../../containers/List'; -import Touch from '../../containers/Touch'; import DirectoryItem from '../../containers/DirectoryItem'; import sharedStyles from '../Styles'; import I18n from '../../i18n'; import SearchBox from '../../containers/SearchBox'; -import { CustomIcon, TIconsName } from '../../containers/CustomIcon'; import StatusBar from '../../containers/StatusBar'; import ActivityIndicator from '../../containers/ActivityIndicator'; import * as HeaderButton from '../../containers/HeaderButton'; @@ -47,22 +46,11 @@ interface IDirectoryViewState { loading: boolean; text: string; total: number; - showOptionsDropdown: boolean; globalUsers: boolean; type: string; } class DirectoryView extends React.Component { - static navigationOptions = ({ navigation, isMasterDetail }: IDirectoryViewProps) => { - const options: StackNavigationOptions = { - title: I18n.t('Directory') - }; - if (isMasterDetail) { - options.headerLeft = () => ; - } - return options; - }; - constructor(props: IDirectoryViewProps) { super(props); this.state = { @@ -70,16 +58,33 @@ class DirectoryView extends React.Component { + const { navigation, isMasterDetail } = this.props; + const options: StackNavigationOptions = { + title: I18n.t('Directory'), + headerRight: () => ( + + + + ) + }; + if (isMasterDetail) { + options.headerLeft = () => ; + } + + navigation.setOptions(options); + }; + onSearchChangeText = (text: string) => { this.setState({ text }, this.search); }; @@ -139,7 +144,7 @@ class DirectoryView extends React.Component { @@ -149,8 +154,20 @@ class DirectoryView extends React.Component { - this.setState(({ showOptionsDropdown }) => ({ showOptionsDropdown: !showOptionsDropdown })); + showFilters = () => { + const { type, globalUsers } = this.state; + const { isFederationEnabled } = this.props; + showActionSheetRef({ + children: ( + + ) + }); }; goRoom = (item: TGoRoomItem) => { @@ -195,41 +212,12 @@ class DirectoryView extends React.Component { - const { type } = this.state; - const { theme } = this.props; - let text = 'Users'; - let icon: TIconsName = 'user'; - - if (type === 'channels') { - text = 'Channels'; - icon = 'channel-public'; - } - - if (type === 'teams') { - text = 'Teams'; - icon = 'teams'; - } - - return ( - <> - - - - - {I18n.t(text)} - - - - - ); - }; + renderHeader = () => ( + <> + + + + ); renderItem: ListRenderItem = ({ item, index }) => { const { data, type } = this.state; @@ -290,8 +278,8 @@ class DirectoryView extends React.Component { - const { data, loading, showOptionsDropdown, type, globalUsers } = this.state; - const { isFederationEnabled, theme } = this.props; + const { data, loading } = this.state; + const { theme } = this.props; return ( @@ -308,16 +296,6 @@ class DirectoryView extends React.Component : null} onEndReached={() => this.load({})} /> - {showOptionsDropdown ? ( - - ) : null} ); }; diff --git a/app/views/DirectoryView/styles.ts b/app/views/DirectoryView/styles.ts index 543e6babba..d9e998d0c7 100644 --- a/app/views/DirectoryView/styles.ts +++ b/app/views/DirectoryView/styles.ts @@ -9,80 +9,31 @@ export default StyleSheet.create({ listContainer: { paddingBottom: 30 }, - separator: { - marginLeft: 60 - }, - toggleDropdownContainer: { - height: 46, - flexDirection: 'row', - alignItems: 'center' - }, - toggleDropdownIcon: { - marginLeft: 20, - marginRight: 17 - }, - toggleDropdownText: { - flex: 1, - fontSize: 17, - ...sharedStyles.textRegular - }, - toggleDropdownArrow: { - marginRight: 15 - }, - dropdownContainer: { - width: '100%', - position: 'absolute', - top: 0 - }, - backdrop: { - // @ts-ignore - ...StyleSheet.absoluteFill - }, - dropdownContainerHeader: { - height: 46, - borderBottomWidth: StyleSheet.hairlineWidth, - alignItems: 'center', - flexDirection: 'row' - }, - dropdownItemButton: { + filterItemButton: { height: 46, justifyContent: 'center' }, - dropdownItemContainer: { + filterItemContainer: { flex: 1, flexDirection: 'row', alignItems: 'center' }, - dropdownItemText: { + filterItemText: { fontSize: 18, flex: 1, ...sharedStyles.textRegular }, - dropdownItemDescription: { + filterItemDescription: { fontSize: 14, flex: 1, marginTop: 2, ...sharedStyles.textRegular }, - dropdownToggleText: { - fontSize: 15, - flex: 1, - marginLeft: 15, - ...sharedStyles.textRegular - }, - dropdownItemIcon: { + filterItemIcon: { width: 22, height: 22, marginHorizontal: 15 }, - dropdownSeparator: { - height: StyleSheet.hairlineWidth, - marginHorizontal: 15, - flex: 1 - }, - inverted: { - transform: [{ scaleY: -1 }] - }, globalUsersContainer: { padding: 15 }, diff --git a/app/views/RoomsListView/Header/Header.tsx b/app/views/RoomsListView/Header/Header.tsx index 1ff837ec0e..d09be58c57 100644 --- a/app/views/RoomsListView/Header/Header.tsx +++ b/app/views/RoomsListView/Header/Header.tsx @@ -3,7 +3,6 @@ import { StyleSheet, Text, TextInputProps, TouchableOpacity, TouchableOpacityPro import I18n from '../../../i18n'; import sharedStyles from '../../Styles'; -import { CustomIcon } from '../../../containers/CustomIcon'; import { useTheme } from '../../../theme'; import SearchHeader from '../../../containers/SearchHeader'; import { useAppSelector } from '../../../lib/hooks'; @@ -25,9 +24,6 @@ const styles = StyleSheet.create({ subtitle: { fontSize: 14, ...sharedStyles.textRegular - }, - upsideDown: { - transform: [{ scaleY: -1 }] } }); @@ -37,7 +33,6 @@ interface IRoomHeader { isFetching: boolean; serverName: string; server: string; - showServerDropdown: boolean; showSearchHeader: boolean; onSearchChangeText: TextInputProps['onChangeText']; onPress: TouchableOpacityProps['onPress']; @@ -50,7 +45,6 @@ const Header = React.memo( isFetching, serverName = 'Rocket.Chat', server, - showServerDropdown, showSearchHeader, onSearchChangeText, onPress @@ -76,17 +70,11 @@ const Header = React.memo( // improve copy return ( - + {serverName} - {subtitle ? ( }; onPress = () => { - logEvent(events.RL_TOGGLE_SERVER_DROPDOWN); - const { showServerDropdown, dispatch } = this.props; - if (showServerDropdown) { - dispatch(closeServerDropdown()); - } else { - dispatch(toggleServerDropdown()); - } + showActionSheetRef({ children: , enableContentPanningGesture: false }); }; render() { - const { serverName, showServerDropdown, showSearchHeader, connecting, connected, isFetching, server } = this.props; + const { serverName, showSearchHeader, connecting, connected, isFetching, server } = this.props; return (
} const mapStateToProps = (state: IApplicationState) => ({ - showServerDropdown: state.rooms.showServerDropdown, showSearchHeader: state.rooms.showSearchHeader, connecting: state.meteor.connecting || state.server.loading, connected: state.meteor.connected, diff --git a/app/views/RoomsListView/ServerDropdown.tsx b/app/views/RoomsListView/ServersList.tsx similarity index 50% rename from app/views/RoomsListView/ServerDropdown.tsx rename to app/views/RoomsListView/ServersList.tsx index 0527d84a9a..b98eb41a78 100644 --- a/app/views/RoomsListView/ServerDropdown.tsx +++ b/app/views/RoomsListView/ServersList.tsx @@ -1,12 +1,12 @@ import React, { useEffect, useRef, useState } from 'react'; -import { View, Text, Animated, Easing, TouchableWithoutFeedback, TouchableOpacity, FlatList, Linking } from 'react-native'; +import { View, Text, TouchableOpacity, FlatList, Linking } from 'react-native'; import { batch, useDispatch } from 'react-redux'; import { Subscription } from 'rxjs'; import { useSafeAreaInsets } from 'react-native-safe-area-context'; import * as List from '../../containers/List'; import Button from '../../containers/Button'; -import { toggleServerDropdown } from '../../actions/rooms'; +import { hideActionSheetRef } from '../../containers/ActionSheet'; import { selectServerRequest, serverInitAdd } from '../../actions/server'; import { appStart } from '../../actions/app'; import I18n from '../../i18n'; @@ -18,7 +18,6 @@ import { useTheme } from '../../theme'; import { localAuthenticate } from '../../lib/methods/helpers/localAuthentication'; import { showConfirmationAlert } from '../../lib/methods/helpers/info'; import log, { events, logEvent } from '../../lib/methods/helpers/log'; -import { headerHeight } from '../../lib/methods/helpers/navigation'; import { goRoom } from '../../lib/methods/helpers/goRoom'; import UserPreferences from '../../lib/methods/userPreferences'; import { RootEnum, TServerModel } from '../../definitions'; @@ -27,29 +26,18 @@ import { removeServer } from '../../lib/methods'; import { useAppSelector } from '../../lib/hooks'; const ROW_HEIGHT = 68; -const ANIMATION_DURATION = 200; -const MAX_ROWS = 4; +const MAX_ROWS = 4.5; -const ServerDropdown = () => { - const animatedValue = useRef(new Animated.Value(0)).current; +const ServersList = () => { 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(); useEffect(() => { - if (isMounted.current) close(); - }, [closeServerDropdown]); - - useEffect(() => { - isMounted.current = true; - const init = async () => { const serversDB = database.servers; const observable = await serversDB.get('servers').query().observeWithColumns(['name']); @@ -57,21 +45,10 @@ const ServerDropdown = () => { subscription.current = observable.subscribe(data => { setServers(data); }); - - Animated.timing(animatedValue, { - toValue: 1, - duration: ANIMATION_DURATION, - easing: Easing.inOut(Easing.quad), - useNativeDriver: true - }).start(); }; init(); return () => { - if (newServerTimeout.current) { - clearTimeout(newServerTimeout.current); - newServerTimeout.current = false; - } if (subscription.current && subscription.current.unsubscribe) { subscription.current.unsubscribe(); } @@ -79,12 +56,7 @@ const ServerDropdown = () => { }, []); const close = () => { - Animated.timing(animatedValue, { - toValue: 0, - duration: ANIMATION_DURATION, - easing: Easing.inOut(Easing.quad), - useNativeDriver: true - }).start(() => dispatch(toggleServerDropdown())); + hideActionSheetRef(); }; const createWorkspace = async () => { @@ -106,9 +78,7 @@ const ServerDropdown = () => { const addServer = () => { logEvent(events.RL_ADD_SERVER); close(); - setTimeout(() => { - navToNewServer(server); - }, ANIMATION_DURATION); + navToNewServer(server); }; const select = async (serverParam: string, version?: string) => { @@ -120,12 +90,11 @@ const ServerDropdown = () => { goRoom({ item: {}, isMasterDetail }); } if (!userId) { + navToNewServer(server); + // Intentionally not cleared, because it needs to trigger the emitter even after unmount setTimeout(() => { - navToNewServer(server); - newServerTimeout.current = setTimeout(() => { - EventEmitter.emit('NewServer', { server: serverParam }); - }, ANIMATION_DURATION); - }, ANIMATION_DURATION); + EventEmitter.emit('NewServer', { server: serverParam }); + }, 300); } else { await localAuthenticate(serverParam); dispatch(selectServerRequest(serverParam, version, true, true)); @@ -156,73 +125,41 @@ const ServerDropdown = () => { /> ); - const initialTop = 87 + Math.min(servers.length, MAX_ROWS) * ROW_HEIGHT; - const statusBarHeight = insets?.top ?? 0; - const heightDestination = isMasterDetail ? headerHeight + statusBarHeight : 0; - - 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={renderItem} - ItemSeparatorComponent={List.Separator} - keyboardShouldPersistTaps='always' - /> - -