Skip to content

Commit d38bf60

Browse files
authored
Disable Animations when Accessibility configuration is to remove it (#8743)
* feat: add support for reduced motion in BottomSheet and TabBar components * fix: remove reduceMotion option from animation timing in login, onboarding, and server screens * feat: integrate reduced motion support to the entire app and switch accordingly * feat: positions the Login screen differently if animations are disabled * fix: remove mock implementation of useReducedMotion in react-native-reanimated * revert login screen * fix: remove unused effect that resets translateX value in LoginOptions * feat: add reduced motion support to ForgotPassword screen and reset translateX on LoginOptions mount * feat: integrate reduced motion support in Onboarding and Slide components * feat: add reduced motion support to MFA and SSO screens * feat: update ReducedMotionConfig to use system preference in withServerDatabase * refactor: remove ReducedMotionConfig from withServerDatabase component * feat: remove reduced motion configuration from screens and adjust animations accordingly * feat: integrate reduced motion handling in Server component animations * feat: enhance BottomSheet animation with reduced motion support and update test setup for react-native-reanimated * fix: update channel list row snapshots with collapsable and animated props * test: update react-native-reanimated mock setup for improved testing * fix: enhance react-native-reanimated mock to support reduced motion and prevent default call * fix: refactor animationConfigs to use useMemo for improved performance and clarity * feat: implement screen transition animation hook and integrate it into ForgotPassword screen * fix: refactor LoginOptions to utilize useScreenTransitionAnimation for improved animation handling * refactor: streamline MFA component by removing unused imports and integrating useScreenTransitionAnimation for enhanced transitions * refactor: simplify Onboarding component by removing unused imports and integrating useScreenTransitionAnimation for smoother transitions * refactor: enhance useScreenTransitionAnimation hook to support animated transitions and integrate it into Server component * refactor: replace custom animation logic with useScreenTransitionAnimation in SSO component for improved transition handling
1 parent dae4b75 commit d38bf60

File tree

11 files changed

+98
-194
lines changed

11 files changed

+98
-194
lines changed
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
2+
// See LICENSE.txt for license information.
3+
4+
import {useEffect} from 'react';
5+
import {Platform, useWindowDimensions} from 'react-native';
6+
import {Navigation} from 'react-native-navigation';
7+
import {useReducedMotion, useSharedValue, useAnimatedStyle, withTiming} from 'react-native-reanimated';
8+
9+
export const useScreenTransitionAnimation = (componentId: string, animated: boolean = true) => {
10+
const {width} = useWindowDimensions();
11+
const reducedMotion = useReducedMotion();
12+
const shouldAnimate = animated && !reducedMotion;
13+
const translateX = useSharedValue(shouldAnimate ? width : 0);
14+
15+
const animatedStyle = useAnimatedStyle(() => {
16+
const duration = Platform.OS === 'android' ? 250 : 350;
17+
return {
18+
transform: [{translateX: withTiming(translateX.value, {duration})}],
19+
};
20+
}, []);
21+
22+
useEffect(() => {
23+
const listener = {
24+
componentDidAppear: () => {
25+
translateX.value = 0;
26+
},
27+
componentDidDisappear: () => {
28+
translateX.value = shouldAnimate ? -width : 0;
29+
},
30+
};
31+
32+
const unsubscribe = Navigation.events().registerComponentListener(listener, componentId);
33+
34+
return () => unsubscribe.remove();
35+
}, [componentId, translateX, width, reducedMotion, shouldAnimate]);
36+
37+
useEffect(() => {
38+
if (!shouldAnimate) {
39+
translateX.value = 0;
40+
}
41+
}, [translateX, shouldAnimate]);
42+
43+
return animatedStyle;
44+
};

app/screens/bottom_sheet/index.tsx

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
import BottomSheetM, {BottomSheetBackdrop, BottomSheetView, type BottomSheetBackdropProps} from '@gorhom/bottom-sheet';
55
import React, {type ReactNode, useCallback, useEffect, useMemo, useRef} from 'react';
66
import {DeviceEventEmitter, type Handle, InteractionManager, Keyboard, type StyleProp, View, type ViewStyle} from 'react-native';
7-
import {ReduceMotion, type WithSpringConfig} from 'react-native-reanimated';
7+
import {ReduceMotion, useReducedMotion, type WithSpringConfig} from 'react-native-reanimated';
88
import {useSafeAreaInsets} from 'react-native-safe-area-context';
99

1010
import {Events} from '@constants';
@@ -86,7 +86,6 @@ export const animatedConfig: Omit<WithSpringConfig, 'velocity'> = {
8686
overshootClamping: true,
8787
restSpeedThreshold: 0.3,
8888
restDisplacementThreshold: 0.3,
89-
reduceMotion: ReduceMotion.Never,
9089
};
9190

9291
const BottomSheet = ({
@@ -100,6 +99,7 @@ const BottomSheet = ({
10099
testID,
101100
enableDynamicSizing = false,
102101
}: Props) => {
102+
const reducedMotion = useReducedMotion();
103103
const sheetRef = useRef<BottomSheetM>(null);
104104
const isTablet = useIsTablet();
105105
const insets = useSafeAreaInsets();
@@ -108,6 +108,11 @@ const BottomSheet = ({
108108
const interaction = useRef<Handle>();
109109
const timeoutRef = useRef<NodeJS.Timeout>();
110110

111+
const animationConfigs = useMemo(() => ({
112+
...animatedConfig,
113+
reduceMotion: reducedMotion ? ReduceMotion.Always : ReduceMotion.Never,
114+
}), [reducedMotion]);
115+
111116
useEffect(() => {
112117
interaction.current = InteractionManager.createInteractionHandle();
113118
}, []);
@@ -221,7 +226,7 @@ const BottomSheet = ({
221226
backdropComponent={renderBackdrop}
222227
onAnimate={handleAnimationStart}
223228
onChange={handleChange}
224-
animationConfigs={animatedConfig}
229+
animationConfigs={animationConfigs}
225230
handleComponent={Indicator}
226231
style={styles.bottomSheet}
227232
backgroundStyle={bottomSheetBackgroundStyle}

app/screens/forgot_password/index.tsx

Lines changed: 7 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,12 @@
11
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
22
// See LICENSE.txt for license information.
33

4-
import React, {useCallback, useEffect, useRef, useState} from 'react';
4+
import React, {useCallback, useRef, useState} from 'react';
55
import {useIntl} from 'react-intl';
6-
import {Keyboard, Platform, Text, useWindowDimensions, View} from 'react-native';
6+
import {Keyboard, Platform, Text, View} from 'react-native';
77
import {KeyboardAwareScrollView} from 'react-native-keyboard-aware-scroll-view';
88
import {Navigation} from 'react-native-navigation';
9-
import Animated, {useAnimatedStyle, useSharedValue, withTiming} from 'react-native-reanimated';
9+
import Animated from 'react-native-reanimated';
1010
import {SafeAreaView} from 'react-native-safe-area-context';
1111

1212
import {sendPasswordResetEmail} from '@actions/remote/session';
@@ -16,6 +16,7 @@ import FormattedText from '@components/formatted_text';
1616
import {Screens} from '@constants';
1717
import useAndroidHardwareBackHandler from '@hooks/android_back_handler';
1818
import {useAvoidKeyboard} from '@hooks/device';
19+
import {useScreenTransitionAnimation} from '@hooks/screen_transition_animation';
1920
import SecurityManager from '@managers/security_manager';
2021
import Background from '@screens/background';
2122
import {isEmail} from '@utils/helpers';
@@ -91,15 +92,15 @@ const getStyleSheet = makeStyleSheetFromTheme((theme: Theme) => ({
9192
}));
9293

9394
const ForgotPassword = ({componentId, serverUrl, theme}: Props) => {
94-
const dimensions = useWindowDimensions();
95-
const translateX = useSharedValue(dimensions.width);
9695
const [email, setEmail] = useState<string>('');
9796
const [error, setError] = useState<string>('');
9897
const [isPasswordLinkSent, setIsPasswordLinkSent] = useState<boolean>(false);
9998
const {formatMessage} = useIntl();
10099
const keyboardAwareRef = useRef<KeyboardAwareScrollView>(null);
101100
const styles = getStyleSheet(theme);
102101

102+
const animatedStyles = useScreenTransitionAnimation(componentId);
103+
103104
useAvoidKeyboard(keyboardAwareRef);
104105

105106
const changeEmail = useCallback((emailAddress: string) => {
@@ -234,39 +235,14 @@ const ForgotPassword = ({componentId, serverUrl, theme}: Props) => {
234235
);
235236
};
236237

237-
const transform = useAnimatedStyle(() => {
238-
const duration = Platform.OS === 'android' ? 250 : 350;
239-
return {
240-
transform: [{translateX: withTiming(translateX.value, {duration})}],
241-
};
242-
}, []);
243-
244-
useEffect(() => {
245-
const listener = {
246-
componentDidAppear: () => {
247-
translateX.value = 0;
248-
},
249-
componentDidDisappear: () => {
250-
translateX.value = -dimensions.width;
251-
},
252-
};
253-
const unsubscribe = Navigation.events().registerComponentListener(listener, componentId);
254-
255-
return () => unsubscribe.remove();
256-
}, [componentId, dimensions]);
257-
258-
useEffect(() => {
259-
translateX.value = 0;
260-
}, []);
261-
262238
useAndroidHardwareBackHandler(componentId, onReturn);
263239

264240
return (
265241
<View style={styles.flex}>
266242
<Background theme={theme}/>
267243
<AnimatedSafeArea
268244
testID='forgot.password.screen'
269-
style={[styles.container, transform]}
245+
style={[styles.container, animatedStyles]}
270246
>
271247
{getCenterContent()}
272248
</AnimatedSafeArea>

app/screens/login/index.tsx

Lines changed: 4 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import React, {useCallback, useEffect, useMemo, useRef, useState} from 'react';
55
import {Platform, useWindowDimensions, View, type LayoutChangeEvent} from 'react-native';
66
import {KeyboardAwareScrollView} from 'react-native-keyboard-aware-scroll-view';
77
import {Navigation} from 'react-native-navigation';
8-
import Animated, {ReduceMotion, useAnimatedStyle, useSharedValue, withTiming} from 'react-native-reanimated';
8+
import Animated from 'react-native-reanimated';
99
import {SafeAreaView} from 'react-native-safe-area-context';
1010

1111
import FormattedText from '@components/formatted_text';
@@ -14,6 +14,7 @@ import useAndroidHardwareBackHandler from '@hooks/android_back_handler';
1414
import {useIsTablet} from '@hooks/device';
1515
import {useDefaultHeaderHeight} from '@hooks/header';
1616
import useNavButtonPressed from '@hooks/navigation_button_pressed';
17+
import {useScreenTransitionAnimation} from '@hooks/screen_transition_animation';
1718
import NetworkManager from '@managers/network_manager';
1819
import SecurityManager from '@managers/security_manager';
1920
import Background from '@screens/background';
@@ -86,7 +87,6 @@ const LoginOptions = ({
8687
const dimensions = useWindowDimensions();
8788
const defaultHeaderHeight = useDefaultHeaderHeight();
8889
const isTablet = useIsTablet();
89-
const translateX = useSharedValue(dimensions.width);
9090
const [contentFillScreen, setContentFillScreen] = useState(false);
9191
const numberSSOs = useMemo(() => {
9292
return Object.values(ssoOptions).filter((v) => v.enabled).length;
@@ -132,13 +132,6 @@ const LoginOptions = ({
132132
/>
133133
);
134134

135-
const transform = useAnimatedStyle(() => {
136-
const duration = Platform.OS === 'android' ? 250 : 350;
137-
return {
138-
transform: [{translateX: withTiming(translateX.value, {duration, reduceMotion: ReduceMotion.Never})}],
139-
};
140-
}, []);
141-
142135
const dismiss = () => {
143136
dismissModal({componentId});
144137
};
@@ -163,23 +156,7 @@ const LoginOptions = ({
163156
return () => navigationEvents.remove();
164157
}, [closeButtonId, componentId, serverUrl]);
165158

166-
useEffect(() => {
167-
translateX.value = 0;
168-
}, []);
169-
170-
useEffect(() => {
171-
const listener = {
172-
componentDidAppear: () => {
173-
translateX.value = 0;
174-
},
175-
componentDidDisappear: () => {
176-
translateX.value = -dimensions.width;
177-
},
178-
};
179-
const unsubscribe = Navigation.events().registerComponentListener(listener, Screens.LOGIN);
180-
181-
return () => unsubscribe.remove();
182-
}, [dimensions, translateX]);
159+
const animatedStyles = useScreenTransitionAnimation(Screens.LOGIN);
183160

184161
useNavButtonPressed(closeButtonId || '', componentId, dismiss, []);
185162
useAndroidHardwareBackHandler(componentId, pop);
@@ -217,7 +194,7 @@ const LoginOptions = ({
217194
nativeID={SecurityManager.getShieldScreenId(componentId, false, true)}
218195
>
219196
<Background theme={theme}/>
220-
<AnimatedSafeArea style={[styles.container, transform]}>
197+
<AnimatedSafeArea style={[styles.container, animatedStyles]}>
221198
<KeyboardAwareScrollView
222199
bounces={true}
223200
contentContainerStyle={[styles.innerContainer, additionalContainerStyle]}

app/screens/mfa/index.tsx

Lines changed: 6 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,11 @@
11
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
22
// See LICENSE.txt for license information.
33

4-
import React, {useCallback, useEffect, useRef, useState} from 'react';
4+
import React, {useCallback, useRef, useState} from 'react';
55
import {useIntl} from 'react-intl';
6-
import {Keyboard, Platform, useWindowDimensions, View} from 'react-native';
6+
import {Keyboard, Platform, View} from 'react-native';
77
import {KeyboardAwareScrollView} from 'react-native-keyboard-aware-scroll-view';
8-
import {Navigation} from 'react-native-navigation';
9-
import Animated, {useAnimatedStyle, useSharedValue, withTiming} from 'react-native-reanimated';
8+
import Animated from 'react-native-reanimated';
109
import {SafeAreaView} from 'react-native-safe-area-context';
1110

1211
import {login} from '@actions/remote/session';
@@ -15,6 +14,7 @@ import FloatingTextInput from '@components/floating_text_input_label';
1514
import FormattedText from '@components/formatted_text';
1615
import useAndroidHardwareBackHandler from '@hooks/android_back_handler';
1716
import {useAvoidKeyboard} from '@hooks/device';
17+
import {useScreenTransitionAnimation} from '@hooks/screen_transition_animation';
1818
import {usePreventDoubleTap} from '@hooks/utils';
1919
import SecurityManager from '@managers/security_manager';
2020
import Background from '@screens/background';
@@ -83,8 +83,6 @@ const getStyleSheet = makeStyleSheetFromTheme((theme: Theme) => ({
8383
const AnimatedSafeArea = Animated.createAnimatedComponent(SafeAreaView);
8484

8585
const MFA = ({componentId, config, goToHome, license, loginId, password, serverDisplayName, serverUrl, theme}: MFAProps) => {
86-
const dimensions = useWindowDimensions();
87-
const translateX = useSharedValue(dimensions.width);
8886
const keyboardAwareRef = useRef<KeyboardAwareScrollView>(null);
8987
const intl = useIntl();
9088
const [token, setToken] = useState<string>('');
@@ -120,33 +118,10 @@ const MFA = ({componentId, config, goToHome, license, loginId, password, serverD
120118
goToHome(result.error);
121119
}, [config, formatMessage, goToHome, intl, license, loginId, password, serverDisplayName, serverUrl, token]));
122120

123-
const transform = useAnimatedStyle(() => {
124-
const duration = Platform.OS === 'android' ? 250 : 350;
125-
return {
126-
transform: [{translateX: withTiming(translateX.value, {duration})}],
127-
};
128-
}, []);
121+
const animatedStyles = useScreenTransitionAnimation(componentId);
129122

130123
useAvoidKeyboard(keyboardAwareRef, 2);
131124

132-
useEffect(() => {
133-
const listener = {
134-
componentDidAppear: () => {
135-
translateX.value = 0;
136-
},
137-
componentDidDisappear: () => {
138-
translateX.value = -dimensions.width;
139-
},
140-
};
141-
const unsubscribe = Navigation.events().registerComponentListener(listener, componentId);
142-
143-
return () => unsubscribe.remove();
144-
}, [componentId, dimensions, translateX]);
145-
146-
useEffect(() => {
147-
translateX.value = 0;
148-
}, []);
149-
150125
const close = useCallback(() => {
151126
popTopScreen(componentId);
152127
}, [componentId]);
@@ -161,7 +136,7 @@ const MFA = ({componentId, config, goToHome, license, loginId, password, serverD
161136
<Background theme={theme}/>
162137
<AnimatedSafeArea
163138
testID='mfa.screen'
164-
style={[styles.container, transform]}
139+
style={[styles.container, animatedStyles]}
165140
>
166141
<KeyboardAwareScrollView
167142
bounces={false}

0 commit comments

Comments
 (0)