diff --git a/.maestro/tests/assorted/changeserver.yaml b/.maestro/tests/assorted/changeserver.yaml index 8280eaf3371..287bbeacdc1 100644 --- a/.maestro/tests/assorted/changeserver.yaml +++ b/.maestro/tests/assorted/changeserver.yaml @@ -32,10 +32,6 @@ tags: visible: id: 'rooms-list-header-server-add' timeout: 60000 -- extendedWaitUntil: - visible: - id: 'rooms-list-header-create-workspace-button' - timeout: 60000 # should login to server, add new server, close the app, open the app and show previous logged server - tapOn: @@ -53,6 +49,8 @@ tags: id: 'workspace-view' timeout: 60000 - launchApp +- waitForAnimationToEnd: + timeout: 500 - extendedWaitUntil: visible: id: 'rooms-list-view' @@ -73,6 +71,8 @@ tags: visible: id: 'server-item-${output.data.alternateServer}' timeout: 60000 +- waitForAnimationToEnd: + timeout: 5000 - tapOn: id: 'server-item-${output.data.alternateServer}' - extendedWaitUntil: @@ -111,6 +111,8 @@ tags: visible: id: 'rooms-list-header-servers-list-button' timeout: 60000 +- waitForAnimationToEnd: + timeout: 5000 - tapOn: id: 'rooms-list-header-servers-list-button' - extendedWaitUntil: @@ -123,6 +125,8 @@ tags: timeout: 60000 - tapOn: id: 'server-item-${output.data.server}' +- waitForAnimationToEnd: + timeout: 500 - extendedWaitUntil: visible: id: 'rooms-list-view' @@ -138,6 +142,8 @@ tags: # should reopen the app and show main server - launchApp +- waitForAnimationToEnd: + timeout: 500 - extendedWaitUntil: visible: id: 'rooms-list-view' @@ -146,3 +152,51 @@ tags: file: './utils/check-server.yaml' env: server: ${output.data.server} + +# should test swipe to delete on alternate server +- extendedWaitUntil: + visible: + id: 'rooms-list-header-servers-list-button' + timeout: 60000 +- tapOn: + id: 'rooms-list-header-servers-list-button' +- extendedWaitUntil: + visible: + id: 'server-item-${output.data.alternateServer}' + timeout: 60000 + +# swipe left to reveal delete button +- swipe: + direction: LEFT + from: + id: 'server-item-${output.data.alternateServer}' + +# tap delete button +- tapOn: + id: 'server-item-${output.data.alternateServer}-delete' + +# confirm deletion +- extendedWaitUntil: + visible: + text: '.*Delete.*' + timeout: 60000 +- tapOn: + text: 'Delete' + +# verify alternate server is deleted +- waitForAnimationToEnd: + timeout: 500 +- extendedWaitUntil: + visible: + id: 'rooms-list-header-servers-list-button' + timeout: 60000 +- tapOn: + id: 'rooms-list-header-servers-list-button' +- extendedWaitUntil: + notVisible: + id: 'server-item-${output.data.alternateServer}' + timeout: 60000 + +# verify main server still exists +- assertVisible: + id: 'server-item-${output.data.server}' diff --git a/.maestro/tests/assorted/delete-server.yaml b/.maestro/tests/assorted/delete-server.yaml index a16091baaea..1802d8b2428 100644 --- a/.maestro/tests/assorted/delete-server.yaml +++ b/.maestro/tests/assorted/delete-server.yaml @@ -77,14 +77,26 @@ tags: visible: id: 'server-item-${output.data.server}' timeout: 60000 -- longPressOn: - id: 'server-item-${output.data.server}' + +# swipe left to reveal delete button +- swipe: + direction: LEFT + from: + id: 'server-item-${output.data.server}' + +# tap delete button +- tapOn: + id: 'server-item-${output.data.server}-delete' + +# confirm deletion - extendedWaitUntil: visible: text: '.*Delete.*' timeout: 60000 - tapOn: text: 'Delete' +- waitForAnimationToEnd: + timeout: 300 - extendedWaitUntil: visible: id: 'rooms-list-header-servers-list-button' diff --git a/.maestro/tests/onboarding/server-history.yaml b/.maestro/tests/onboarding/server-history.yaml index e57a0bc4f61..d05d5567904 100644 --- a/.maestro/tests/onboarding/server-history.yaml +++ b/.maestro/tests/onboarding/server-history.yaml @@ -26,7 +26,8 @@ tags: id: 'servers-history-https://mobile.rocket.chat' # should tap on a server history and navigate to login -- tapOn: 'Connect to https://mobile.rocket.chat as ${output.user.username}' +- tapOn: + id: 'servers-history-https://mobile.rocket.chat' - assertVisible: id: 'login-view-submit' - assertVisible: ${output.user.username} @@ -42,10 +43,17 @@ tags: - tapOn: id: 'servers-history-button' - assertVisible: - id: 'servers-history-delete-https://mobile.rocket.chat' -- tapOn: - id: 'servers-history-delete-https://mobile.rocket.chat' + id: 'servers-history-https://mobile.rocket.chat' +# swipe left to reveal delete button and delete the server history +- swipe: + direction: LEFT + from: + id: 'servers-history-https://mobile.rocket.chat' +- waitForAnimationToEnd: + timeout: 500 +- tapOn: + id: 'servers-history-https://mobile.rocket.chat-delete' - extendedWaitUntil: notVisible: id: 'servers-history-button' diff --git a/app/containers/ServerItem/ServerItem.stories.tsx b/app/containers/ServerItem/ServerItem.stories.tsx index 4d0d0b0a1f1..94c86a668e5 100644 --- a/app/containers/ServerItem/ServerItem.stories.tsx +++ b/app/containers/ServerItem/ServerItem.stories.tsx @@ -18,13 +18,13 @@ const ServerItem = ({ item, theme = 'light', onPress = () => alert('Press'), - onLongPress, + onDeletePress, hasCheck }: { item?: Partial; theme?: TSupportedThemes; onPress?: IServerItem['onPress']; - onLongPress?: IServerItem['onLongPress']; + onDeletePress?: IServerItem['onDeletePress']; hasCheck?: IServerItem['hasCheck']; }) => ( - + ); @@ -53,9 +53,10 @@ export const Content = () => ( ); -export const Touchable = () => ( +export const SwipeActions = () => ( <> - alert('Long Press')} /> + alert('Delete Server')} /> + alert('Delete Server')} /> ); diff --git a/app/containers/ServerItem/SwipeableDeleteItem/Actions.tsx b/app/containers/ServerItem/SwipeableDeleteItem/Actions.tsx new file mode 100644 index 00000000000..d011e6fb5e8 --- /dev/null +++ b/app/containers/ServerItem/SwipeableDeleteItem/Actions.tsx @@ -0,0 +1,147 @@ +import React from 'react'; +import { View, StyleSheet } from 'react-native'; +import Animated, { + useAnimatedStyle, + interpolate, + withSpring, + runOnJS, + useAnimatedReaction, + useSharedValue, + type SharedValue +} from 'react-native-reanimated'; +import { RectButton } from 'react-native-gesture-handler'; +import * as Haptics from 'expo-haptics'; + +import { CustomIcon } from '../../CustomIcon'; +import { useTheme } from '../../../theme'; +import I18n from '../../../i18n'; + +export interface IDeleteActionProps { + transX: SharedValue; + width: number; + rowHeight: number; + actionWidth: number; + longSwipe: number; + onDeletePress(): void; + testID?: string; +} + +const SERVER_ITEM_PADDING_VERTICAL = 12; + +export const DeleteAction = React.memo( + ({ transX, width, rowHeight, actionWidth, longSwipe, onDeletePress, testID }: IDeleteActionProps) => { + const { colors } = useTheme(); + + const translateXDelete = useSharedValue(0); + + const triggerDeleteAnimation = (toValue: number) => { + Haptics.impactAsync(Haptics.ImpactFeedbackStyle.Light); + translateXDelete.value = withSpring(toValue, { overshootClamping: true, mass: 0.7 }); + }; + + useAnimatedReaction( + () => transX.value, + (currentTransX, previousTransX) => { + if (I18n.isRTL) { + if (previousTransX && currentTransX > longSwipe && previousTransX <= longSwipe) { + runOnJS(triggerDeleteAnimation)(actionWidth); + } else if (previousTransX && currentTransX <= longSwipe && previousTransX > longSwipe) { + runOnJS(triggerDeleteAnimation)(0); + } + } else if (previousTransX && currentTransX < -longSwipe && previousTransX >= -longSwipe) { + runOnJS(triggerDeleteAnimation)(-actionWidth); + } else if (previousTransX && currentTransX >= -longSwipe && previousTransX < -longSwipe) { + runOnJS(triggerDeleteAnimation)(0); + } + } + ); + + const animatedDeleteButtonStyles = useAnimatedStyle(() => { + if (I18n.isRTL) { + // RTL: delete button appears from the left when swiping right + if (transX.value > longSwipe && transX.value >= 2 * actionWidth) { + const parallaxSwipe = interpolate( + transX.value, + [2 * actionWidth, longSwipe], + [-actionWidth, -actionWidth - 0.1 * transX.value] + ); + return { + transform: [{ translateX: parallaxSwipe - translateXDelete.value }], + left: 0, + right: undefined + }; + } + return { + transform: [{ translateX: transX.value - actionWidth - translateXDelete.value }], + left: 0, + right: undefined + }; + } + // LTR: delete button appears from the right when swiping left + if (transX.value < -longSwipe && transX.value <= -2 * actionWidth) { + const parallaxSwipe = interpolate( + transX.value, + [-2 * actionWidth, -longSwipe], + [actionWidth, actionWidth + 0.1 * transX.value] + ); + return { + transform: [{ translateX: parallaxSwipe + translateXDelete.value }], + right: 0, + left: undefined + }; + } + return { + transform: [{ translateX: transX.value + actionWidth + translateXDelete.value }], + right: 0, + left: undefined + }; + }); + const viewHeight = { height: rowHeight + SERVER_ITEM_PADDING_VERTICAL }; + + return ( + + + + + + + + ); + } +); + +const styles = StyleSheet.create({ + actionsLeftContainer: { + flexDirection: 'row', + position: 'absolute', + left: 0, + right: 0 + }, + actionRightButtonContainer: { + position: 'absolute', + justifyContent: 'center', + top: 0, + alignItems: 'flex-end' + }, + actionButton: { + width: 80, + height: '100%', + alignItems: 'center', + justifyContent: 'center' + } +}); diff --git a/app/containers/ServerItem/SwipeableDeleteItem/Touchable.tsx b/app/containers/ServerItem/SwipeableDeleteItem/Touchable.tsx new file mode 100644 index 00000000000..898187f1129 --- /dev/null +++ b/app/containers/ServerItem/SwipeableDeleteItem/Touchable.tsx @@ -0,0 +1,216 @@ +import React, { useRef, memo } from 'react'; +import Animated, { useSharedValue, useAnimatedStyle, withSpring, runOnJS } from 'react-native-reanimated'; +import { + Gesture, + GestureDetector, + type GestureUpdateEvent, + type PanGestureHandlerEventPayload +} from 'react-native-gesture-handler'; +import { View, type AccessibilityActionEvent } from 'react-native'; + +import Touch from '../../Touch'; +import { DeleteAction } from './Actions'; +import { useTheme } from '../../../theme'; +import I18n from '../../../i18n'; + +export interface ISwipeableDeleteTouchableProps { + children: JSX.Element; + testID: string; + width: number; + rowHeight: number; + actionWidth: number; + longSwipe: number; + smallSwipe: number; + backgroundColor: string; + onPress(): void; + onDeletePress(): void; + accessibilityLabel?: string; + accessibilityHint?: string; +} + +const SwipeableDeleteTouchable = ({ + width, + children, + testID, + rowHeight, + actionWidth, + longSwipe, + smallSwipe, + backgroundColor, + onPress, + onDeletePress, + accessibilityLabel, + accessibilityHint +}: ISwipeableDeleteTouchableProps): React.ReactElement => { + const { colors } = useTheme(); + + const transX = useSharedValue(0); + const rowOffSet = useSharedValue(0); + const rowState = useSharedValue(0); + const valueRef = useRef(0); + + const handlePress = () => { + if (rowState.value !== 0) { + close(); + return; + } + + if (onPress) { + onPress(); + } + }; + + const close = () => { + rowState.value = 0; + transX.value = withSpring(0, { overshootClamping: true }); + rowOffSet.value = 0; + valueRef.current = 0; + }; + + const handleDeletePress = () => { + close(); + if (onDeletePress) { + onDeletePress(); + } + }; + + const onAccessibilityAction = (event: AccessibilityActionEvent) => { + switch (event.nativeEvent.actionName) { + case 'delete': + handleDeletePress(); + break; + } + }; + + const handleRelease = (event: GestureUpdateEvent) => { + const { translationX } = event; + valueRef.current += translationX; + let toValue = 0; + + if (rowState.value === 0) { + // if no option is opened + if (I18n.isRTL) { + // RTL: swipe right (positive translationX) to show delete + if (translationX > 0 && translationX < longSwipe) { + // open delete action if swipe right + toValue = actionWidth; + rowState.value = 1; + } else if (translationX >= longSwipe) { + // long swipe right - trigger delete immediately + toValue = 0; + rowState.value = 1; + handleDeletePress(); + } else { + // any other gesture (including left swipes) - stay closed + toValue = 0; + } + } else if (translationX < 0 && translationX > -longSwipe) { + // LTR: open delete action if swipe left + toValue = -actionWidth; + rowState.value = 1; + } else if (translationX <= -longSwipe) { + // LTR: long swipe left - trigger delete immediately + toValue = 0; + rowState.value = 1; + handleDeletePress(); + } else { + // LTR: any other gesture (including right swipes) - stay closed + toValue = 0; + } + } else if (rowState.value === 1) { + // if delete option is opened + if (I18n.isRTL) { + // RTL: delete is on the left (positive translation) + if (valueRef.current < smallSwipe) { + toValue = 0; + rowState.value = 0; + } else if (valueRef.current > longSwipe) { + handleDeletePress(); + } else { + toValue = actionWidth; + } + } else if (valueRef.current > -smallSwipe) { + // LTR: close if swipe back right + toValue = 0; + rowState.value = 0; + } else if (valueRef.current < -longSwipe) { + // LTR: trigger delete on long swipe + handleDeletePress(); + } else { + // LTR: keep delete action open + toValue = -actionWidth; + } + } + + // Use spring animation exactly like RoomItem + transX.value = withSpring(toValue, { overshootClamping: true }); + rowOffSet.value = toValue; + valueRef.current = toValue; + }; + + const panGesture = Gesture.Pan() + .activeOffsetX([-10, 10]) // More sensitive horizontal detection + .failOffsetY([-20, 20]) // Fail on vertical movement to distinguish scrolling + .onUpdate(event => { + const newValue = event.translationX + rowOffSet.value; + + if (I18n.isRTL) { + // RTL: allow right swipes (positive values), prevent left swipes + if (newValue < 0) { + transX.value = 0; + } else { + transX.value = newValue; + // Limit how far right it can stretch + if (transX.value > width) transX.value = width; + } + } else if (newValue > 0) { + // LTR: prevent right swipes + transX.value = 0; + } else { + // LTR: allow left swipes (negative values) + transX.value = newValue; + // Limit how far left it can stretch + if (transX.value < -width) transX.value = -width; + } + }) + .onEnd(event => { + runOnJS(handleRelease)(event); + }); + + const animatedStyles = useAnimatedStyle(() => ({ + transform: [{ translateX: transX.value }] + })); + + return ( + + + + + + {children} + + + + + ); +}; + +export default memo(SwipeableDeleteTouchable); diff --git a/app/containers/ServerItem/Touchable.tsx b/app/containers/ServerItem/Touchable.tsx new file mode 100644 index 00000000000..ccf68681245 --- /dev/null +++ b/app/containers/ServerItem/Touchable.tsx @@ -0,0 +1,61 @@ +import React, { memo } from 'react'; + +import SwipeableDeleteTouchable from './SwipeableDeleteItem/Touchable'; +import Touch from '../Touch'; +import { ACTION_WIDTH, LONG_SWIPE, SMALL_SWIPE, ROW_HEIGHT } from './styles'; +import { useTheme } from '../../theme'; + +export interface IServerItemTouchableProps { + children: JSX.Element; + testID: string; + width: number; + onPress(): void; + onDeletePress?(): void; + accessibilityLabel?: string; + accessibilityHint?: string; +} + +const Touchable = ({ + width, + children, + testID, + onPress, + onDeletePress, + accessibilityLabel, + accessibilityHint +}: IServerItemTouchableProps): React.ReactElement => { + const { colors } = useTheme(); + + if (onDeletePress) { + return ( + + {children} + + ); + } + + return ( + + {children} + + ); +}; + +export default memo(Touchable); diff --git a/app/containers/ServerItem/__snapshots__/ServerItem.test.tsx.snap b/app/containers/ServerItem/__snapshots__/ServerItem.test.tsx.snap index 0f992950f1d..b02764e50e8 100644 --- a/app/containers/ServerItem/__snapshots__/ServerItem.test.tsx.snap +++ b/app/containers/ServerItem/__snapshots__/ServerItem.test.tsx.snap @@ -12,7 +12,6 @@ exports[`Story Snapshots: Content should match snapshot 1`] = ` onActiveStateChange={[Function]} onGestureHandlerEvent={[Function]} onGestureHandlerStateChange={[Function]} - onLongPress={[Function]} onPress={[Function]} rippleColor="#E4E7EA" style={ @@ -58,6 +57,9 @@ exports[`Story Snapshots: Content should match snapshot 1`] = ` } /> -  +  @@ -198,7 +200,6 @@ exports[`Story Snapshots: Content should match snapshot 1`] = ` onActiveStateChange={[Function]} onGestureHandlerEvent={[Function]} onGestureHandlerStateChange={[Function]} - onLongPress={[Function]} onPress={[Function]} rippleColor="#E4E7EA" style={ @@ -244,6 +245,9 @@ exports[`Story Snapshots: Content should match snapshot 1`] = ` } /> + +  + , @@ -358,7 +388,6 @@ exports[`Story Snapshots: Content should match snapshot 1`] = ` onActiveStateChange={[Function]} onGestureHandlerEvent={[Function]} onGestureHandlerStateChange={[Function]} - onLongPress={[Function]} onPress={[Function]} rippleColor="#E4E7EA" style={ @@ -404,6 +433,9 @@ exports[`Story Snapshots: Content should match snapshot 1`] = ` } /> + +  + , ] `; +exports[`Story Snapshots: SwipeActions should match snapshot 1`] = ` +[ + + + + + + +  + + + + + + + + + + + + + Rocket.Chat + + + https://open.rocket.chat/ + + + +  + + + + + + , + + + + + + +  + + + + + + + + + + + + + Another Server + + + https://example.com/ + + + +  + + + + + + , +] +`; + exports[`Story Snapshots: Themes should match snapshot 1`] = ` [ + +  + , @@ -677,19 +1442,18 @@ exports[`Story Snapshots: Themes should match snapshot 1`] = ` activeOpacity={1} collapsable={false} delayLongPress={600} - handlerTag={5} + handlerTag={11} handlerType="NativeViewGestureHandler" innerRef={null} onActiveStateChange={[Function]} onGestureHandlerEvent={[Function]} onGestureHandlerStateChange={[Function]} - onLongPress={[Function]} onPress={[Function]} rippleColor="#2D3039" style={ [ { - "backgroundColor": "#1F2329", + "backgroundColor": "#262931", "borderRadius": undefined, "margin": undefined, "marginBottom": undefined, @@ -729,6 +1493,9 @@ exports[`Story Snapshots: Themes should match snapshot 1`] = ` } /> + +  + , @@ -837,19 +1630,18 @@ exports[`Story Snapshots: Themes should match snapshot 1`] = ` activeOpacity={1} collapsable={false} delayLongPress={600} - handlerTag={6} + handlerTag={12} handlerType="NativeViewGestureHandler" innerRef={null} onActiveStateChange={[Function]} onGestureHandlerEvent={[Function]} onGestureHandlerStateChange={[Function]} - onLongPress={[Function]} onPress={[Function]} rippleColor="#16181a" style={ [ { - "backgroundColor": "#000000", + "backgroundColor": "#0d0d0d", "borderRadius": undefined, "margin": undefined, "marginBottom": undefined, @@ -889,6 +1681,9 @@ exports[`Story Snapshots: Themes should match snapshot 1`] = ` } /> - - - , -] -`; - -exports[`Story Snapshots: Touchable should match snapshot 1`] = ` - - - - - - - Rocket.Chat - - - https://open.rocket.chat/ +  - - + , +] `; diff --git a/app/containers/ServerItem/index.tsx b/app/containers/ServerItem/index.tsx index 03d38ddbdac..1e68e0b6fef 100644 --- a/app/containers/ServerItem/index.tsx +++ b/app/containers/ServerItem/index.tsx @@ -2,12 +2,16 @@ import React from 'react'; import { Text, View } from 'react-native'; import { Image } from 'expo-image'; -import Check from '../Check'; +import Radio from '../Radio'; import styles, { ROW_HEIGHT } from './styles'; import { useTheme } from '../../theme'; -import Touch from '../Touch'; +import Touchable from './Touchable'; +import I18n from '../../i18n'; +import { useResponsiveLayout } from '../../lib/hooks/useResponsiveLayout/useResponsiveLayout'; export { ROW_HEIGHT }; +export { default as ServerItemTouchable } from './Touchable'; +export type { IServerItemTouchableProps } from './Touchable'; export interface IServerItem { item: { @@ -17,20 +21,30 @@ export interface IServerItem { useRealName?: boolean; }; onPress(): void; - onLongPress?(): void; + onDeletePress?(): void; hasCheck?: boolean; } const defaultLogo = require('../../static/images/logo.png'); -const ServerItem = React.memo(({ item, onPress, onLongPress, hasCheck }: IServerItem) => { +const ServerItem = React.memo(({ item, onPress, onDeletePress, hasCheck }: IServerItem) => { const { colors } = useTheme(); + const { width } = useResponsiveLayout(); + + const serverName = item.name || item.id; + const accessibilityLabel = `${serverName}, ${item.id}`; + const accessibilityHint = onDeletePress + ? I18n.t('Activate_to_select_server_Available_actions_delete') + : I18n.t('Activate_to_select_server'); + return ( - onLongPress?.()} + onDeletePress={onDeletePress} testID={`server-item-${item.id}`} - style={{ backgroundColor: colors.surfaceRoom }}> + width={width} + accessibilityLabel={accessibilityLabel} + accessibilityHint={accessibilityHint}> {item.iconURL ? ( - {hasCheck ? : null} + - + ); }); diff --git a/app/containers/ServerItem/styles.ts b/app/containers/ServerItem/styles.ts index f34c6459e36..30768f1e73f 100644 --- a/app/containers/ServerItem/styles.ts +++ b/app/containers/ServerItem/styles.ts @@ -3,6 +3,9 @@ import { StyleSheet } from 'react-native'; import sharedStyles from '../../views/Styles'; export const ROW_HEIGHT = 56; +export const ACTION_WIDTH = 80; +export const SMALL_SWIPE = ACTION_WIDTH / 2; +export const LONG_SWIPE = ACTION_WIDTH * 2.5; export default StyleSheet.create({ serverItemContainer: { @@ -29,5 +32,22 @@ export default StyleSheet.create({ serverUrl: { fontSize: 16, ...sharedStyles.textRegular + }, + actionsLeftContainer: { + flexDirection: 'row', + position: 'absolute', + left: 0, + right: 0 + }, + actionRightButtonContainer: { + position: 'absolute', + justifyContent: 'center', + top: 0 + }, + actionButton: { + width: ACTION_WIDTH, + height: '100%', + alignItems: 'center', + justifyContent: 'center' } }); diff --git a/app/containers/Touch.tsx b/app/containers/Touch.tsx index c62f83ef756..7017ad340a5 100644 --- a/app/containers/Touch.tsx +++ b/app/containers/Touch.tsx @@ -1,6 +1,13 @@ import React from 'react'; import { RectButton, type RectButtonProps } from 'react-native-gesture-handler'; -import { View, StyleSheet, type ViewStyle, type StyleProp } from 'react-native'; +import { + View, + StyleSheet, + type ViewStyle, + type StyleProp, + type AccessibilityActionEvent, + type AccessibilityActionInfo +} from 'react-native'; import { useTheme } from '../theme'; @@ -8,12 +15,30 @@ export interface ITouchProps extends RectButtonProps { children: React.ReactNode; accessible?: boolean; accessibilityLabel?: string; + accessibilityHint?: string; + accessibilityActions?: AccessibilityActionInfo[]; + onAccessibilityAction?: (event: AccessibilityActionEvent) => void; testID?: string; rectButtonStyle?: StyleProp; } const Touch = React.forwardRef, ITouchProps>( - ({ children, onPress, underlayColor, accessible, accessibilityLabel, style, rectButtonStyle, ...props }, ref) => { + ( + { + children, + onPress, + underlayColor, + accessible, + accessibilityLabel, + accessibilityHint, + accessibilityActions, + onAccessibilityAction, + style, + rectButtonStyle, + ...props + }, + ref + ) => { const { colors } = useTheme(); // The background color must be applied to the RectButton, not the View. // If set on the View, the touch opacity animation won't work properly. @@ -54,7 +79,13 @@ const Touch = React.forwardRef, ITouchProps> rippleColor={colors.surfaceNeutral} style={[rectButtonStyle, marginStyles, { backgroundColor, borderRadius }]} {...props}> - + {children} diff --git a/app/definitions/IServerHistory.ts b/app/definitions/IServerHistory.ts index d714c8626b1..00d2ce1a149 100644 --- a/app/definitions/IServerHistory.ts +++ b/app/definitions/IServerHistory.ts @@ -5,6 +5,7 @@ export interface IServerHistory { url: string; username: string; updatedAt: Date; + iconURL?: string; } export type TServerHistoryModel = IServerHistory & Model; diff --git a/app/i18n/locales/ar.json b/app/i18n/locales/ar.json index 5cb73c6062b..f2016e8a40b 100644 --- a/app/i18n/locales/ar.json +++ b/app/i18n/locales/ar.json @@ -12,6 +12,8 @@ "Accessibility_and_Appearance": "إمكانية الوصول والمظهر", "Accessibility_statement": "بيان الوصول", "Actions": "الإجراءات", + "Activate_to_select_server": "فعّل لاختيار الخادم", + "Activate_to_select_server_Available_actions_delete": "تفعيل لاختيار الخادم. الإجراءات المتاحة: حذف", "Activity": "النشاط", "Add_Server": "إضافة خادم", "Add_server": "إضافة خادم", @@ -605,6 +607,7 @@ "Without_Servers": "بدون خوادم", "Workspace_URL": "عنوان URL لمساحة العمل", "Workspace_URL_Example": "open.rocket.chat", + "Workspaces": "مساحات العمل", "Would_you_like_to_return_the_inquiry": "هل ترغب بالرد على السؤال؟", "Write_External_Permission": "إذن معرض", "Write_External_Permission_Message": "يحتاج Rocket.Chat للوصول إلى معرض الصور الخاص بك حتى تتمكن من حفظ الصور", diff --git a/app/i18n/locales/bn-IN.json b/app/i18n/locales/bn-IN.json index e316d311acc..820c9d3aa78 100644 --- a/app/i18n/locales/bn-IN.json +++ b/app/i18n/locales/bn-IN.json @@ -18,6 +18,8 @@ "Accessibility_statement": "অ্যাক্সেসিবিলিটি বিবৃতি", "Accounts_Default_User_Preferences_alsoSendThreadToChannel_Description": "ব্যবহারকারীদের 'সেইমসঙ্গে চ্যানেলে প্রেরণ করুন' আচরণ নির্ধারণ করার অনুমতি দিন", "Actions": "ক্রিয়াবলী", + "Activate_to_select_server": "সার্ভার নির্বাচন করতে সক্রিয় করুন", + "Activate_to_select_server_Available_actions_delete": "সার্ভার নির্বাচন করতে সক্রিয় করুন। উপলব্ধ ক্রিয়াকলাপ: {{delete}}", "Activity": "ক্রিয়া", "Add_Channel_to_Team": "দলে চ্যানেল যোগ করুন", "Add_Existing": "বিদ্যমান যোগ করুন", @@ -862,7 +864,7 @@ "Without_Servers": "ওয়ার্কস্পেস ছাড়া", "Workspace_URL": "ওয়ার্কস্পেস URL", "Workspace_URL_Example": "open.rocket.chat", - "Workspaces": "ওয়ার্কস্পেস", + "Workspaces": "কর্মস্থানগুলি", "Would_like_to_place_on_hold": "আপনি কি এই চ্যাটটি হোল্ড করতে চান?", "Would_you_like_to_return_the_inquiry": "আপনি কি অনুসন্ধান ফিরিয়ে আসতে চান?", "Write_External_Permission": "গ্যালারি অনুমতি", diff --git a/app/i18n/locales/cs.json b/app/i18n/locales/cs.json index 6f6fb1a6d4e..b569c3747a6 100644 --- a/app/i18n/locales/cs.json +++ b/app/i18n/locales/cs.json @@ -18,6 +18,8 @@ "Accessibility_statement": "Prohlášení o přístupnosti", "Accounts_Default_User_Preferences_alsoSendThreadToChannel_Description": "Povolit uživatelům vybrat chování 'Také odeslat do kanálu'", "Actions": "Akce", + "Activate_to_select_server": "Aktivujte pro výběr serveru", + "Activate_to_select_server_Available_actions_delete": "Aktivovat pro výběr serveru. Dostupné akce: smazat", "Activity": "Aktivita", "Add_Channel_to_Team": "Přidat kanál do týmu", "Add_Existing": "Přidat existující", diff --git a/app/i18n/locales/de.json b/app/i18n/locales/de.json index 76a85ade2a2..5bbb075ae68 100644 --- a/app/i18n/locales/de.json +++ b/app/i18n/locales/de.json @@ -18,6 +18,8 @@ "Accessibility_statement": "Barrierefreiheits-Erklärung", "Accounts_Default_User_Preferences_alsoSendThreadToChannel_Description": "Nutzern erlauben das Verhalten für \"auch an den Kanal senden\" zu bestimmen.", "Actions": "Aktionen", + "Activate_to_select_server": "Aktivieren, um den Server auszuwählen", + "Activate_to_select_server_Available_actions_delete": "Aktivieren, um Server auszuwählen. Verfügbare Aktionen: löschen", "Activity": "Aktivität", "Add_Channel_to_Team": "Kanal zum Team hinzufügen", "Add_Existing": "Vorhandenes hinzufügen", diff --git a/app/i18n/locales/en.json b/app/i18n/locales/en.json index 839b8a47e0b..a57075ded04 100644 --- a/app/i18n/locales/en.json +++ b/app/i18n/locales/en.json @@ -18,6 +18,8 @@ "Accessibility_statement": "Accessibility statement", "Accounts_Default_User_Preferences_alsoSendThreadToChannel_Description": "Allow users to select the 'Also send to channel' behavior", "Actions": "Actions", + "Activate_to_select_server": "Activate to select server", + "Activate_to_select_server_Available_actions_delete": "Activate to select server. Available actions: delete", "Activity": "Activity", "Add_Channel_to_Team": "Add channel to team", "Add_Existing": "Add existing", @@ -964,6 +966,7 @@ "Workspace_consumption_description": "There’s a set amount of push notifications per month", "Workspace_URL": "Workspace URL", "Workspace_URL_Example": "open.rocket.chat", + "Workspaces": "Workspaces", "Would_like_to_place_on_hold": "Would you like to place this chat on hold?", "Would_you_like_to_return_the_inquiry": "Would you like to return the inquiry?", "Write_External_Permission": "Gallery permission", diff --git a/app/i18n/locales/es.json b/app/i18n/locales/es.json index f5291db6f20..08a5e4f0e28 100644 --- a/app/i18n/locales/es.json +++ b/app/i18n/locales/es.json @@ -12,6 +12,8 @@ "Accessibility_and_Appearance": "Accesibilidad y apariencia", "Accessibility_statement": "Declaración de accesibilidad", "Actions": "Acciones", + "Activate_to_select_server": "Activar para seleccionar servidor", + "Activate_to_select_server_Available_actions_delete": "Activar para seleccionar servidor. Acciones disponibles: eliminar", "Activity": "Actividad", "Add_Server": "Añadir servidor", "Add_server": "Añadir servidor", @@ -439,6 +441,7 @@ "Without_Servers": "Sin servidores", "Workspace_URL": "URL del espacio de trabajo", "Workspace_URL_Example": "open.rocket.chat", + "Workspaces": "Espacios de trabajo", "Yes_action_it": "Sí, ¡{{action}}!", "Yesterday": "Ayer", "You": "Tú", diff --git a/app/i18n/locales/fi.json b/app/i18n/locales/fi.json index 92c6dbf8442..b7e3c6e34dc 100644 --- a/app/i18n/locales/fi.json +++ b/app/i18n/locales/fi.json @@ -17,6 +17,8 @@ "Accessibility_statement": "Saavutettavuusilmoitus", "Accounts_Default_User_Preferences_alsoSendThreadToChannel_Description": "Salli käyttäjien valita Lähetä myös kanavalle -toiminta", "Actions": "Toimet", + "Activate_to_select_server": "Aktivoi valitaksesi palvelimen", + "Activate_to_select_server_Available_actions_delete": "Aktivoi valitaksesi palvelimen. Käytettävissä olevat toiminnot: poista", "Activity": "Toiminta", "Add_Channel_to_Team": "Lisää kanava tiimille", "Add_Existing": "Lisää olemassa oleva", diff --git a/app/i18n/locales/fr.json b/app/i18n/locales/fr.json index 6243192aad0..429d91a908a 100644 --- a/app/i18n/locales/fr.json +++ b/app/i18n/locales/fr.json @@ -12,6 +12,8 @@ "Accessibility_and_Appearance": "Accessibilité et apparence", "Accessibility_statement": "Déclaration d'accessibilité", "Actions": "Actions", + "Activate_to_select_server": "Activer pour sélectionner le serveur", + "Activate_to_select_server_Available_actions_delete": "Activer pour sélectionner le serveur. Actions disponibles : supprimer", "Activity": "Activité", "Add_Channel_to_Team": "Ajouter un canal à l'équipe", "Add_Existing": "Ajouter existant", diff --git a/app/i18n/locales/hi-IN.json b/app/i18n/locales/hi-IN.json index 8606ce73def..ff3b67aa18c 100644 --- a/app/i18n/locales/hi-IN.json +++ b/app/i18n/locales/hi-IN.json @@ -18,6 +18,8 @@ "Accessibility_statement": "पहुंच उपलब्धता विवरण", "Accounts_Default_User_Preferences_alsoSendThreadToChannel_Description": "उपयोगकर्ताओं को 'चैनल में भी भेजें' व्यवहार का चयन करने की अनुमति दें", "Actions": "क्रियाएँ", + "Activate_to_select_server": "सर्वर चयन करने के लिए सक्रिय करें", + "Activate_to_select_server_Available_actions_delete": "सर्वर का चयन करने के लिए सक्रिय करें। उपलब्ध क्रियाएँ: हटाएं", "Activity": "गतिविधि", "Add_Channel_to_Team": "टीम में चैनल जोड़ें", "Add_Existing": "मौजूद को जोड़ें", @@ -862,7 +864,7 @@ "Without_Servers": "कार्यस्थान रहित", "Workspace_URL": "कार्यस्थल URL", "Workspace_URL_Example": "open.rocket.chat", - "Workspaces": "कार्यस्थान", + "Workspaces": "वर्कस्पेसेस", "Would_like_to_place_on_hold": "क्या आप इस चैट को होल्ड पर रखना चाहेंगे?", "Would_you_like_to_return_the_inquiry": "क्या आप जांच को लौटना चाहेंगे?", "Write_External_Permission": "गैलरी अनुमति", diff --git a/app/i18n/locales/hu.json b/app/i18n/locales/hu.json index d2fc84ca052..ba0b5395b0b 100644 --- a/app/i18n/locales/hu.json +++ b/app/i18n/locales/hu.json @@ -18,6 +18,8 @@ "Accessibility_statement": "Hozzáférhetőségi nyilatkozat", "Accounts_Default_User_Preferences_alsoSendThreadToChannel_Description": "Engedélyezze a felhasználóknak a \"Küldés a csatornára is” viselkedés kiválasztását", "Actions": "Tevékenységek", + "Activate_to_select_server": "Aktíválás a szerver kiválasztásához", + "Activate_to_select_server_Available_actions_delete": "Aktiválás a szerver kiválasztásához. Elérhető műveletek: {{delete}}", "Activity": "Aktivitás", "Add_Channel_to_Team": "Csatorna hozzáadása a csapathoz", "Add_Existing": "Hozzáadás meglévőhöz", diff --git a/app/i18n/locales/it.json b/app/i18n/locales/it.json index 37556c0cad7..2dace2cdb0c 100644 --- a/app/i18n/locales/it.json +++ b/app/i18n/locales/it.json @@ -16,6 +16,8 @@ "Accessibility_and_Appearance": "Accessibilità e aspetto", "Accessibility_statement": "Dichiarazione di accessibilità", "Actions": "Azioni", + "Activate_to_select_server": "Attiva per selezionare il server", + "Activate_to_select_server_Available_actions_delete": "Attiva per selezionare il server. Azioni disponibili: elimina", "Activity": "Attività", "Add_Server": "Aggiungi server", "Add_server": "Aggiungi server", @@ -652,7 +654,7 @@ "Without_Servers": "Senza server", "Workspace_URL": "URL dello spazio di lavoro", "Workspace_URL_Example": "open.rocket.chat", - "Workspaces": "Workspace", + "Workspaces": "Spazi di lavoro", "Would_you_like_to_return_the_inquiry": "Vorresti ritirare la tua domanda?", "Write_External_Permission": "Permesso galleria", "Write_External_Permission_Message": "Rocket.Chat ha bisogno dell'accesso alla galleria per salvare le immagini.", diff --git a/app/i18n/locales/ja.json b/app/i18n/locales/ja.json index 491fb6e6e0a..9b396de29a6 100644 --- a/app/i18n/locales/ja.json +++ b/app/i18n/locales/ja.json @@ -12,6 +12,8 @@ "Accessibility_and_Appearance": "アクセシビリティと外観", "Accessibility_statement": "アクセシビリティ声明", "Actions": "アクション", + "Activate_to_select_server": "サーバーを選択するには有効にします", + "Activate_to_select_server_Available_actions_delete": "サーバーを選択するには有効化します。使用可能な操作: 削除", "Activity": "アクティビティ順", "Add_Server": "サーバーを追加", "Add_server": "サーバーを追加", @@ -532,6 +534,7 @@ "Without_Servers": "サーバーを除く", "Workspace_URL": "ワークスペースのURL", "Workspace_URL_Example": "open.rocket.chat", + "Workspaces": "ワークスペース", "Write_External_Permission": "ギャラリーへのアクセス許可", "Write_External_Permission_Message": "Rocket.Chatは画像を保存するためにギャラリーへのアクセスを求めています。", "Yes_action_it": "はい、{{action}}します!", diff --git a/app/i18n/locales/nl.json b/app/i18n/locales/nl.json index 770c1ab9342..209badc2947 100644 --- a/app/i18n/locales/nl.json +++ b/app/i18n/locales/nl.json @@ -12,6 +12,8 @@ "Accessibility_and_Appearance": "Toegankelijkheid & uiterlijk", "Accessibility_statement": "Toegankelijkheidsverklaring", "Actions": "Acties", + "Activate_to_select_server": "Activeren om server te selecteren", + "Activate_to_select_server_Available_actions_delete": "Activeren om server te selecteren. Beschikbare acties: verwijderen", "Activity": "Activiteit", "Add_Channel_to_Team": "Kanaal toevoegen aan team", "Add_Existing": "Voeg bestaande", @@ -745,7 +747,7 @@ "Without_Servers": "Zonder servers", "Workspace_URL": "Werkruimte-URL", "Workspace_URL_Example": "open.rocket.chat", - "Workspaces": "Werkruimten", + "Workspaces": "Werkruimtes", "Would_like_to_place_on_hold": "Wil je deze chat in de wacht zetten?", "Would_you_like_to_return_the_inquiry": "Wil je de aanvraag retourneren?", "Write_External_Permission": "Galerij toestemming", diff --git a/app/i18n/locales/nn.json b/app/i18n/locales/nn.json index c13ed1ffa03..40d9cdb9825 100644 --- a/app/i18n/locales/nn.json +++ b/app/i18n/locales/nn.json @@ -11,6 +11,8 @@ "Accessibility": "Tilgjengelighet", "Accessibility_and_Appearance": "Tilgjengelighet og utseende", "Accessibility_statement": "Tilgjengelighetserklæring", + "Activate_to_select_server": "Aktiver for å velje server", + "Activate_to_select_server_Available_actions_delete": "Aktiver for å velje tenar. Tilgjengelege handlingar: slett", "Activity": "Aktivitet", "Add_users": "Legg til brukere", "added__roomName__to_this_team": "la #{{roomName}} til dette teamet", @@ -390,6 +392,7 @@ "Wait_activation_warning": "Før du kan logge inn, må kontoen din aktiveres manuelt av en administrator.", "What_are_you_doing_right_now": "Hva gjør du akkurat nå?", "Why_do_you_want_to_report": "Hvorfor vil du rapportere?", + "Workspaces": "Arbeidsområder", "Would_you_like_to_return_the_inquiry": "Vil du returnere forespørselen?", "Yes": "Ja", "Yes_remove_user": "Ja, fjern bruker!", diff --git a/app/i18n/locales/no.json b/app/i18n/locales/no.json index f37b0b392ad..bfd1ad97c35 100644 --- a/app/i18n/locales/no.json +++ b/app/i18n/locales/no.json @@ -18,6 +18,8 @@ "Accessibility_statement": "Tilgjengelighetserklæring", "Accounts_Default_User_Preferences_alsoSendThreadToChannel_Description": "Tillat brukere å velge «Send også til kanal»-atferd", "Actions": "Handlinger", + "Activate_to_select_server": "Aktiver for å velge server", + "Activate_to_select_server_Available_actions_delete": "Aktiver for å velge server. Tilgjengelige handlinger: slett", "Activity": "Aktivitet", "Add_Channel_to_Team": "Legg til kanal i arbeidsområde", "Add_Existing": "Legg til eksisterende", @@ -910,6 +912,7 @@ "Workspace_consumption_description": "Det er et bestemt antall push-varsler per måned", "Workspace_URL": "Server URL", "Workspace_URL_Example": "open.rocket.chat", + "Workspaces": "Arbeidsområder", "Would_like_to_place_on_hold": "Vil du sette denne chatten på vent?", "Would_you_like_to_return_the_inquiry": "Vil du returnere henvendelsen?", "Write_External_Permission": "Galleritillatelse", diff --git a/app/i18n/locales/pt-BR.json b/app/i18n/locales/pt-BR.json index 31283e380c2..206fe2ab531 100644 --- a/app/i18n/locales/pt-BR.json +++ b/app/i18n/locales/pt-BR.json @@ -18,6 +18,8 @@ "Accessibility_statement": "Declaração de acessibilidade", "Accounts_Default_User_Preferences_alsoSendThreadToChannel_Description": "Permitir que os usuários selecionem o comportamento Também enviar para o canal", "Actions": "Ações", + "Activate_to_select_server": "Ativar para selecionar servidor", + "Activate_to_select_server_Available_actions_delete": "Ativar para selecionar o servidor. Ações disponíveis: excluir", "Activity": "Atividade", "Add_Channel_to_Team": "Adicionar canal ao time", "Add_Existing": "Adicionar", diff --git a/app/i18n/locales/pt-PT.json b/app/i18n/locales/pt-PT.json index 8611217a889..b1fa4aab9d5 100644 --- a/app/i18n/locales/pt-PT.json +++ b/app/i18n/locales/pt-PT.json @@ -12,6 +12,8 @@ "Accessibility_and_Appearance": "Acessibilidade e aparência", "Accessibility_statement": "Declaração de acessibilidade", "Actions": "Acções", + "Activate_to_select_server": "Ativar para selecionar o servidor", + "Activate_to_select_server_Available_actions_delete": "Ativar para selecionar o servidor. Ações disponíveis: eliminar", "Activity": "Actividade", "Add_Server": "Adicionar Servidor", "Add_users": "Adicionar utilizadores", @@ -502,6 +504,7 @@ "Whats_the_password_for_your_certificate": "Qual é a senha para o seu certificado?", "Workspace_URL": "URL do espaço de trabalho", "Workspace_URL_Example": "open.rocket.chat", + "Workspaces": "Espaços de Trabalho", "Yes_action_it": "Sim, {{action}}!", "Yesterday": "Ontem", "You": "Você", diff --git a/app/i18n/locales/ru.json b/app/i18n/locales/ru.json index 5d4f49b3f39..029cc749e5e 100644 --- a/app/i18n/locales/ru.json +++ b/app/i18n/locales/ru.json @@ -17,6 +17,8 @@ "Accessibility_statement": "Заявление о доступности", "Accounts_Default_User_Preferences_alsoSendThreadToChannel_Description": "Разрешить пользователям выбирать поведение \"Также отправить в чат\"", "Actions": "Действия", + "Activate_to_select_server": "Активировать для выбора сервера", + "Activate_to_select_server_Available_actions_delete": "Активируйте для выбора сервера. Доступные действия: удалить", "Activity": "Активность", "Add_Channel_to_Team": "Добавить канал в Команду", "Add_Existing": "Добавить существующее", @@ -792,7 +794,7 @@ "Without_Servers": "Без серверов", "Workspace_URL": "URL рабочего пространства", "Workspace_URL_Example": "open.rocket.chat", - "Workspaces": "Серверы", + "Workspaces": "Рабочие пространства", "Would_like_to_place_on_hold": "Вы хотите поставить этот чат на удержание?", "Would_you_like_to_return_the_inquiry": "Вы хотите отозвать запрос?", "Write_External_Permission": "Разрешения на запись в Галерею", diff --git a/app/i18n/locales/sl-SI.json b/app/i18n/locales/sl-SI.json index 86bc5751648..0aa9829bcec 100644 --- a/app/i18n/locales/sl-SI.json +++ b/app/i18n/locales/sl-SI.json @@ -16,6 +16,8 @@ "Accessibility_and_Appearance": "Dostopnost in videz", "Accessibility_statement": "Izjava o dostopnosti", "Actions": "Dejanja", + "Activate_to_select_server": "Aktiviraj za izbiro strežnika", + "Activate_to_select_server_Available_actions_delete": "Aktiviraj za izbor strežnika. Na voljo dejanja: izbriši", "Activity": "Aktivnost", "Add_Channel_to_Team": "Dodajte kanal v ekipo", "Add_Existing": "Dodaj obstoječe", diff --git a/app/i18n/locales/sv.json b/app/i18n/locales/sv.json index d115516e563..3841aae26f8 100644 --- a/app/i18n/locales/sv.json +++ b/app/i18n/locales/sv.json @@ -17,6 +17,8 @@ "Accessibility_statement": "Tillgänglighetsredogörelse", "Accounts_Default_User_Preferences_alsoSendThreadToChannel_Description": "Tillåt användare att välja alternativet Skicka även till kanal", "Actions": "Åtgärder", + "Activate_to_select_server": "Aktivera för att välja server", + "Activate_to_select_server_Available_actions_delete": "Aktivera för att välja server. Tillgängliga åtgärder: radera", "Activity": "Aktivitet", "Add_Channel_to_Team": "Lägg till kanal för team", "Add_Existing": "Lägg till befintlig", diff --git a/app/i18n/locales/ta-IN.json b/app/i18n/locales/ta-IN.json index c25ebdde78a..84705c17aee 100644 --- a/app/i18n/locales/ta-IN.json +++ b/app/i18n/locales/ta-IN.json @@ -18,6 +18,8 @@ "Accessibility_statement": "அணுகல் கூற்று", "Accounts_Default_User_Preferences_alsoSendThreadToChannel_Description": "பயனர்களுக்கு 'சேனலுக்கும் அனுப்புக' செய்தி நடவடிக்கையை தேர்ந்தெடுத்துக் கொள்ள அனுமதிக்கு", "Actions": "செயல்கள்", + "Activate_to_select_server": "சேவையகத்தைத் தேர்ந்தெடுக்க செயல்படுத்தவும்", + "Activate_to_select_server_Available_actions_delete": "சர்வரை தேர்ந்தெடுக்க செயல்படுத்தவும். கிடைப்பில் செயல்கள்: delete", "Activity": "செயல்திருத்தம்", "Add_Channel_to_Team": "குழுக்கு சேனல் சேர்க்க", "Add_Existing": "ஏற்கனவே உள்ளதைச் சேர்க்கவும்", @@ -862,7 +864,7 @@ "Without_Servers": "பணிகளில் இல்லை", "Workspace_URL": "வேலைத்தள URL", "Workspace_URL_Example": "open.rocket.chat", - "Workspaces": "பணிகள்", + "Workspaces": "வேலைத்தளங்கள்", "Would_like_to_place_on_hold": "இந்த உரையாடலை அழிக்க விரும்புகிறீர்களா?", "Would_you_like_to_return_the_inquiry": "கேட்கையை பின்கொள்ள விரும்புகிறீர்களா?", "Write_External_Permission": "கேலரி அனுமதி", diff --git a/app/i18n/locales/te-IN.json b/app/i18n/locales/te-IN.json index 30faeb01f4b..df35ece0023 100644 --- a/app/i18n/locales/te-IN.json +++ b/app/i18n/locales/te-IN.json @@ -18,6 +18,8 @@ "Accessibility_statement": "పేరుబట్టి వివరణ", "Accounts_Default_User_Preferences_alsoSendThreadToChannel_Description": "వాడాకు 'కూడా చానల్‌కు పంపండి' పనికి అనుమతిస్తుంది", "Actions": "చర్యలు", + "Activate_to_select_server": "సర్వర్‌ను ఎంచుకోవడానికి యాక్టివేట్ చేయండి", + "Activate_to_select_server_Available_actions_delete": "సర్వర్‌ను ఎంచుకోవడానికి ఆಕ್ಟివేట్ చేయండి. లభ్యమయ్యే చర్యలు: {{delete}}", "Activity": "చట్టం", "Add_Channel_to_Team": "ఛానల్‌ను టీమ్‌కు జోడించండి", "Add_Existing": "ఇప్పటికే ఉన్నవిని జోడించండి", @@ -861,7 +863,7 @@ "Without_Servers": "పనితనం లేదా", "Workspace_URL": "వర్క్‌స్పేస్ URL", "Workspace_URL_Example": "open.rocket.chat", - "Workspaces": "పనితనాలు", + "Workspaces": "వర్క్‌స్పేస్‌లు", "Would_like_to_place_on_hold": "ఈ చాట్‌ను ఆఫ్‌లోడ్ చేయాలా?", "Would_you_like_to_return_the_inquiry": "మీరు ప్రశ్నను వీక్షించాలా?", "Write_External_Permission": "గ్యాలరీ అనుమతి", diff --git a/app/i18n/locales/tr.json b/app/i18n/locales/tr.json index 5d0391180c3..f8a3db7606d 100644 --- a/app/i18n/locales/tr.json +++ b/app/i18n/locales/tr.json @@ -12,6 +12,8 @@ "Accessibility_and_Appearance": "Erişilebilirlik ve görünüm", "Accessibility_statement": "Erişilebilirlik beyanı", "Actions": "İşlemler", + "Activate_to_select_server": "Sunucuyu seçmek için etkinleştir", + "Activate_to_select_server_Available_actions_delete": "Sunucuyu seçmek için etkinleştir. Mevcut eylemler: sil", "Activity": "Etkinlik", "Add_Server": "Sunucu ekle", "Add_server": "Sunucu ekle", @@ -635,7 +637,7 @@ "Without_Servers": "Sunucusuz", "Workspace_URL": "Çalışma Alanı URL'si", "Workspace_URL_Example": "open.rocket.chat", - "Workspaces": "Çalışma alanları", + "Workspaces": "Çalışma Alanları", "Would_you_like_to_return_the_inquiry": "Başvuruyu geri çevirmek ister misiniz?", "Write_External_Permission": "Galeri İzni", "Write_External_Permission_Message": "Rocket.Chat'in galerinize erişmesi gerekiyor, böylece resimleri kaydedebilirsiniz.", diff --git a/app/i18n/locales/zh-CN.json b/app/i18n/locales/zh-CN.json index 1faba8802a0..5b06b34b077 100644 --- a/app/i18n/locales/zh-CN.json +++ b/app/i18n/locales/zh-CN.json @@ -12,6 +12,8 @@ "Accessibility_and_Appearance": "辅助功能和外观", "Accessibility_statement": "无障碍声明", "Actions": "操作", + "Activate_to_select_server": "激活以选择服务器", + "Activate_to_select_server_Available_actions_delete": "激活以选择服务器。可用操作:删除", "Activity": "按活动时间排列", "Add_Server": "創建服务器", "Add_server": "創建服务器", diff --git a/app/i18n/locales/zh-TW.json b/app/i18n/locales/zh-TW.json index 91fbb7ddc8c..2b38fa80778 100644 --- a/app/i18n/locales/zh-TW.json +++ b/app/i18n/locales/zh-TW.json @@ -12,6 +12,8 @@ "Accessibility_and_Appearance": "無障礙功能與外觀", "Accessibility_statement": "无障碍声明", "Actions": "操作", + "Activate_to_select_server": "啟用以選擇伺服器", + "Activate_to_select_server_Available_actions_delete": "啟動以選擇伺服器。可用操作:delete", "Activity": "以活動時間排序", "Add_Server": "新增伺服器", "Add_server": "新增伺服器", diff --git a/app/lib/constants/colors.ts b/app/lib/constants/colors.ts index e8525c3f862..2db1dbc6807 100644 --- a/app/lib/constants/colors.ts +++ b/app/lib/constants/colors.ts @@ -249,7 +249,7 @@ const black = { buttonBackgroundPrimaryPress: '#245399', buttonBackgroundPrimaryDisabled: '#1D3963', - buttonBackgroundSecondaryDefault: '#0E0D0D', + buttonBackgroundSecondaryDefault: '#353B45', buttonBackgroundSecondaryPress: '#454C59', buttonBackgroundSecondaryDisabled: '#2F343D', diff --git a/app/lib/database/model/ServersHistory.js b/app/lib/database/model/ServersHistory.js index 1684037829c..c8651d226ca 100644 --- a/app/lib/database/model/ServersHistory.js +++ b/app/lib/database/model/ServersHistory.js @@ -11,4 +11,6 @@ export default class ServersHistory extends Model { @field('username') username; @readonly @date('updated_at') updatedAt; + + @field('icon_url') iconURL; } diff --git a/app/lib/database/model/servers/migrations.js b/app/lib/database/model/servers/migrations.js index a6ca25b72b8..62fbea318b2 100644 --- a/app/lib/database/model/servers/migrations.js +++ b/app/lib/database/model/servers/migrations.js @@ -155,6 +155,15 @@ export default schemaMigrations({ columns: [{ name: 'require_password_change', type: 'string', isOptional: true }] }) ] + }, + { + toVersion: 17, + steps: [ + addColumns({ + table: 'servers_history', + columns: [{ name: 'icon_url', type: 'string', isOptional: true }] + }) + ] } ] }); diff --git a/app/lib/database/schema/servers.js b/app/lib/database/schema/servers.js index 5708b018544..0fd15e9b517 100644 --- a/app/lib/database/schema/servers.js +++ b/app/lib/database/schema/servers.js @@ -1,7 +1,7 @@ import { appSchema, tableSchema } from '@nozbe/watermelondb'; export default appSchema({ - version: 16, + version: 17, tables: [ tableSchema({ name: 'users', @@ -50,7 +50,8 @@ export default appSchema({ columns: [ { name: 'url', type: 'string', isIndexed: true }, { name: 'username', type: 'string', isOptional: true }, - { name: 'updated_at', type: 'number' } + { name: 'updated_at', type: 'number' }, + { name: 'icon_url', type: 'string', isOptional: true } ] }) ] diff --git a/app/sagas/login.js b/app/sagas/login.js index 0a37ea1cdb6..d3e37b0e2fa 100644 --- a/app/sagas/login.js +++ b/app/sagas/login.js @@ -92,15 +92,27 @@ const handleLoginRequest = function* handleLoginRequest({ credentials, logoutOnE // Saves username on server history const serversDB = database.servers; const serversHistoryCollection = serversDB.get('servers_history'); + const serversCollection = serversDB.get('servers'); yield serversDB.write(async () => { try { const serversHistory = await serversHistoryCollection.query(Q.where('url', server)).fetch(); if (serversHistory?.length) { const serverHistoryRecord = serversHistory[0]; + // Get server iconURL from servers table + let iconURL = null; + try { + const serverRecord = await serversCollection.find(server); + iconURL = serverRecord.iconURL; + } catch (e) { + // Server record might not exist yet + } // this is updating on every login just to save `updated_at` // keeping this server as the most recent on autocomplete order await serverHistoryRecord.update((s) => { s.username = result.username; + if (iconURL) { + s.iconURL = iconURL; + } }); } } catch (e) { diff --git a/app/sagas/selectServer.ts b/app/sagas/selectServer.ts index cd4a8238466..5373f6b0fcf 100644 --- a/app/sagas/selectServer.ts +++ b/app/sagas/selectServer.ts @@ -248,6 +248,7 @@ const handleServerRequest = function* handleServerRequest({ server, username, fr if (!serversHistory?.length) { await serversHistoryCollection.create(s => { s.url = server; + s.iconURL = serverInfo.iconURL; }); } } catch (e) { diff --git a/app/views/NewServerView/components/ServersHistoryActionSheetContent/index.tsx b/app/views/NewServerView/components/ServersHistoryActionSheetContent/index.tsx index 42c74e4aae2..bf0a3d57a66 100644 --- a/app/views/NewServerView/components/ServersHistoryActionSheetContent/index.tsx +++ b/app/views/NewServerView/components/ServersHistoryActionSheetContent/index.tsx @@ -1,14 +1,29 @@ import React from 'react'; -import { View } from 'react-native'; +import { View, Text, StyleSheet } from 'react-native'; import { useSafeAreaInsets } from 'react-native-safe-area-context'; import { A11y } from 'react-native-a11y-order'; import * as List from '../../../../containers/List'; -import Touch from '../../../../containers/Touch'; -import { CustomIcon } from '../../../../containers/CustomIcon'; import { useTheme } from '../../../../theme'; import { type TServerHistoryModel } from '../../../../definitions'; -import i18n from '../../../../i18n'; +import ServersHistoryItem from '../ServersHistoryItem'; +import I18n from '../../../../i18n'; +import sharedStyles from '../../../Styles'; + +const styles = StyleSheet.create({ + header: { + height: 41, + borderBottomWidth: StyleSheet.hairlineWidth, + alignItems: 'center', + flexDirection: 'row', + justifyContent: 'space-between' + }, + headerText: { + fontSize: 16, + marginLeft: 12, + ...sharedStyles.textRegular + } +}); interface IServersHistoryActionSheetContent { serversHistory: TServerHistoryModel[]; @@ -25,43 +40,21 @@ export const ServersHistoryActionSheetContent = ({ const { bottom } = useSafeAreaInsets(); return ( - - - - <> - {serversHistory.map(item => ( - <> - - - onPressServerHistory(item)} - right={() => ( - - onDelete(item)}> - - - - )} - styleTitle={{ fontSize: 18 }} - translateTitle={false} - translateSubtitle={false} - title={item.url} - subtitle={item.username} - /> - - - - - - ))} - - + + + {I18n.t('Workspaces')} + + + {serversHistory.map(item => ( + + + + onPressServerHistory(item)} onDeletePress={() => onDelete(item)} /> + + + + + ))} ); }; diff --git a/app/views/NewServerView/components/ServersHistoryItem/ServersHistoryItem.stories.tsx b/app/views/NewServerView/components/ServersHistoryItem/ServersHistoryItem.stories.tsx new file mode 100644 index 00000000000..c3435796f02 --- /dev/null +++ b/app/views/NewServerView/components/ServersHistoryItem/ServersHistoryItem.stories.tsx @@ -0,0 +1,96 @@ +import React from 'react'; + +import { themes } from '../../../../lib/constants/colors'; +import ServersHistoryItemComponent, { type IServersHistoryItem } from '.'; +import { ThemeContext, type TSupportedThemes } from '../../../../theme'; +import { type TServerHistoryModel } from '../../../../definitions'; +import { + BASE_ROW_HEIGHT, + BASE_ROW_HEIGHT_CONDENSED, + ResponsiveLayoutContext +} from '../../../../lib/hooks/useResponsiveLayout/useResponsiveLayout'; + +export default { + title: 'ServersHistoryItem' +}; + +const defaultItem = { + id: '1', + url: 'https://open.rocket.chat', + username: 'john.doe', + updatedAt: new Date(), + iconURL: 'https://open.rocket.chat/images/logo/android-chrome-512x512.png' +} as TServerHistoryModel; + +const responsiveLayoutProviderValue = { + fontScale: 1, + fontScaleLimited: 1, + isLargeFontScale: false, + rowHeight: BASE_ROW_HEIGHT, + rowHeightCondensed: BASE_ROW_HEIGHT_CONDENSED, + width: 350, + height: 800 +}; + +const ServersHistoryItem = ({ + item, + theme = 'light', + onPress = () => alert('Press'), + onDeletePress +}: { + item?: Partial; + theme?: TSupportedThemes; + onPress?: IServersHistoryItem['onPress']; + onDeletePress?: IServersHistoryItem['onDeletePress']; +}) => ( + + + alert('Delete'))} + /> + + +); + +export const Content = () => ( + <> + + + + +); + +export const SwipeActions = () => ( + <> + alert('Delete Server History')} /> + alert('Delete Server History')} + /> + +); + +export const Themes = () => ( + <> + + + + +); diff --git a/app/views/NewServerView/components/ServersHistoryItem/ServersHistoryItem.test.tsx b/app/views/NewServerView/components/ServersHistoryItem/ServersHistoryItem.test.tsx new file mode 100644 index 00000000000..30724276cb4 --- /dev/null +++ b/app/views/NewServerView/components/ServersHistoryItem/ServersHistoryItem.test.tsx @@ -0,0 +1,6 @@ +import { generateSnapshots } from '../../../../../.rnstorybook/generateSnapshots'; +import * as stories from './ServersHistoryItem.stories'; + +jest.unmock('../../../../lib/hooks/useResponsiveLayout/useResponsiveLayout'); + +generateSnapshots(stories); diff --git a/app/views/NewServerView/components/ServersHistoryItem/__snapshots__/ServersHistoryItem.test.tsx.snap b/app/views/NewServerView/components/ServersHistoryItem/__snapshots__/ServersHistoryItem.test.tsx.snap new file mode 100644 index 00000000000..d8ea0e5ee46 --- /dev/null +++ b/app/views/NewServerView/components/ServersHistoryItem/__snapshots__/ServersHistoryItem.test.tsx.snap @@ -0,0 +1,2464 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`Story Snapshots: Content should match snapshot 1`] = ` +[ + + + + + + +  + + + + + + + + + + + + + https://open.rocket.chat + + + john.doe + + + + + + + , + + + + + + +  + + + + + + + + + + + + + https://superlongservername.tologintoasuperlongservername.rocket.chat + + + very.long.username.here + + + + + + + , + + + + + + +  + + + + + + + + + + + + + https://stable.rocket.chat + + + admin + + + + + + + , +] +`; + +exports[`Story Snapshots: SwipeActions should match snapshot 1`] = ` +[ + + + + + + +  + + + + + + + + + + + + + https://open.rocket.chat + + + john.doe + + + + + + + , + + + + + + +  + + + + + + + + + + + + + https://example.com + + + user123 + + + + + + + , +] +`; + +exports[`Story Snapshots: Themes should match snapshot 1`] = ` +[ + + + + + + +  + + + + + + + + + + + + + https://open.rocket.chat + + + john.doe + + + + + + + , + + + + + + +  + + + + + + + + + + + + + https://open.rocket.chat + + + john.doe + + + + + + + , + + + + + + +  + + + + + + + + + + + + + https://open.rocket.chat + + + john.doe + + + + + + + , +] +`; diff --git a/app/views/NewServerView/components/ServersHistoryItem/index.tsx b/app/views/NewServerView/components/ServersHistoryItem/index.tsx new file mode 100644 index 00000000000..550910a610d --- /dev/null +++ b/app/views/NewServerView/components/ServersHistoryItem/index.tsx @@ -0,0 +1,53 @@ +import React from 'react'; +import { Text, View } from 'react-native'; +import { Image } from 'expo-image'; + +import styles, { ROW_HEIGHT } from './styles'; +import { useTheme } from '../../../../theme'; +import { ServerItemTouchable as Touchable } from '../../../../containers/ServerItem'; +import { type TServerHistoryModel } from '../../../../definitions'; +import I18n from '../../../../i18n'; +import { useResponsiveLayout } from '../../../../lib/hooks/useResponsiveLayout/useResponsiveLayout'; + +export { ROW_HEIGHT }; + +export interface IServersHistoryItem { + item: TServerHistoryModel; + onPress(): void; + onDeletePress(): void; +} + +const defaultLogo = require('../../../../static/images/logo.png'); + +const ServersHistoryItem = React.memo(({ item, onPress, onDeletePress }: IServersHistoryItem) => { + const { colors } = useTheme(); + const { width } = useResponsiveLayout(); + + const accessibilityLabel = item.username ? `${item.url}, ${item.username}` : item.url; + const accessibilityHint = I18n.t('Activate_to_select_server_Available_actions_delete'); + + return ( + + + + + + + {item.url} + + + {item.username} + + + + + ); +}); + +export default ServersHistoryItem; diff --git a/app/views/NewServerView/components/ServersHistoryItem/styles.ts b/app/views/NewServerView/components/ServersHistoryItem/styles.ts new file mode 100644 index 00000000000..dedbea25fdc --- /dev/null +++ b/app/views/NewServerView/components/ServersHistoryItem/styles.ts @@ -0,0 +1,34 @@ +import { StyleSheet } from 'react-native'; + +import sharedStyles from '../../../Styles'; + +export const ROW_HEIGHT = 56; + +export default StyleSheet.create({ + container: { + flexDirection: 'row', + alignItems: 'center', + padding: 12, + minHeight: ROW_HEIGHT + }, + serverIcon: { + width: 44, + height: 44, + borderRadius: 4 + }, + textContainer: { + flex: 1, + flexDirection: 'column', + justifyContent: 'center', + paddingRight: 18, + paddingLeft: 12 + }, + title: { + fontSize: 18, + ...sharedStyles.textSemibold + }, + subtitle: { + fontSize: 16, + ...sharedStyles.textRegular + } +}); diff --git a/app/views/RoomsListView/components/ServersList.tsx b/app/views/RoomsListView/components/ServersList.tsx index 245183e8d53..850eea2375e 100644 --- a/app/views/RoomsListView/components/ServersList.tsx +++ b/app/views/RoomsListView/components/ServersList.tsx @@ -1,5 +1,5 @@ import React, { memo, useEffect, useRef, useState } from 'react'; -import { FlatList, Linking, Text, TouchableOpacity, View } from 'react-native'; +import { FlatList, Text, View } from 'react-native'; import { useSafeAreaInsets } from 'react-native-safe-area-context'; import { batch, useDispatch } from 'react-redux'; import { type Subscription } from 'rxjs'; @@ -20,7 +20,7 @@ import EventEmitter from '../../../lib/methods/helpers/events'; import { goRoom } from '../../../lib/methods/helpers/goRoom'; import { showConfirmationAlert } from '../../../lib/methods/helpers/info'; import { localAuthenticate } from '../../../lib/methods/helpers/localAuthentication'; -import log, { events, logEvent } from '../../../lib/methods/helpers/log'; +import { events, logEvent } from '../../../lib/methods/helpers/log'; import UserPreferences from '../../../lib/methods/userPreferences'; import { useTheme } from '../../../theme'; import styles from '../styles'; @@ -61,15 +61,6 @@ const ServersList = () => { hideActionSheetRef(); }; - const createWorkspace = async () => { - logEvent(events.RL_CREATE_NEW_WORKSPACE); - try { - await Linking.openURL('https://cloud.rocket.chat/trial'); - } catch (e) { - log(e); - } - }; - const navToNewServer = (previousServer: string) => { batch(() => { dispatch(appStart({ root: RootEnum.ROOT_OUTSIDE })); @@ -124,7 +115,7 @@ const ServersList = () => { select(item.id, item.version)} - onLongPress={() => item.id === server || remove(item.id)} + onDeletePress={() => item.id === server || remove(item.id)} hasCheck={item.id === server} /> ); @@ -132,16 +123,13 @@ const ServersList = () => { return ( - {I18n.t('Server')} - - {I18n.t('Add_Server')} - + {I18n.t('Workspaces')} { keyboardShouldPersistTaps='always' /> -