You've already forked joplin
							
							
				mirror of
				https://github.com/laurent22/joplin.git
				synced 2025-10-31 00:07:48 +02:00 
			
		
		
		
	Mobile: Fixes #11028: Accessibility: Fix sidebar broken in right-to-left mode, improve screen reader accessibility (#11056)
This commit is contained in:
		| @@ -761,6 +761,7 @@ packages/app-mobile/utils/fs-driver/testUtil/createFilesFromPathRecord.js | ||||
| packages/app-mobile/utils/fs-driver/testUtil/verifyDirectoryMatches.js | ||||
| packages/app-mobile/utils/getPackageInfo.js | ||||
| packages/app-mobile/utils/getVersionInfoText.js | ||||
| packages/app-mobile/utils/hooks/useReduceMotionEnabled.js | ||||
| packages/app-mobile/utils/image/fileToImage.web.js | ||||
| packages/app-mobile/utils/image/getImageDimensions.js | ||||
| packages/app-mobile/utils/image/resizeImage.js | ||||
|   | ||||
							
								
								
									
										1
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										1
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							| @@ -738,6 +738,7 @@ packages/app-mobile/utils/fs-driver/testUtil/createFilesFromPathRecord.js | ||||
| packages/app-mobile/utils/fs-driver/testUtil/verifyDirectoryMatches.js | ||||
| packages/app-mobile/utils/getPackageInfo.js | ||||
| packages/app-mobile/utils/getVersionInfoText.js | ||||
| packages/app-mobile/utils/hooks/useReduceMotionEnabled.js | ||||
| packages/app-mobile/utils/image/fileToImage.web.js | ||||
| packages/app-mobile/utils/image/getImageDimensions.js | ||||
| packages/app-mobile/utils/image/resizeImage.js | ||||
|   | ||||
| @@ -1,23 +0,0 @@ | ||||
| const { connect } = require('react-redux'); | ||||
| const SideMenu_ = require('react-native-side-menu-updated').default; | ||||
| import { Dimensions } from 'react-native'; | ||||
| import { State } from '@joplin/lib/reducer'; | ||||
|  | ||||
| class SideMenuComponent extends SideMenu_ { | ||||
| 	// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Old code before rule was applied | ||||
| 	public onLayoutChange(e: any) { | ||||
| 		const { width, height } = e.nativeEvent.layout; | ||||
| 		const openMenuOffsetPercentage = this.props.openMenuOffset / Dimensions.get('window').width; | ||||
| 		const openMenuOffset = width * openMenuOffsetPercentage; | ||||
| 		const hiddenMenuOffset = width * this.state.hiddenMenuOffsetPercentage; | ||||
| 		this.setState({ width, height, openMenuOffset, hiddenMenuOffset }); | ||||
| 	} | ||||
| } | ||||
|  | ||||
| const SideMenu = connect((state: State) => { | ||||
| 	return { | ||||
| 		isOpen: state.showSideMenu, | ||||
| 	}; | ||||
| })(SideMenuComponent); | ||||
|  | ||||
| export default SideMenu; | ||||
							
								
								
									
										326
									
								
								packages/app-mobile/components/SideMenu.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										326
									
								
								packages/app-mobile/components/SideMenu.tsx
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,326 @@ | ||||
| import * as React from 'react'; | ||||
| import { connect } from 'react-redux'; | ||||
| import { AccessibilityInfo, Animated, Dimensions, Easing, I18nManager, LayoutChangeEvent, PanResponder, Pressable, StyleSheet, useWindowDimensions, View } from 'react-native'; | ||||
| import { State } from '@joplin/lib/reducer'; | ||||
| import { useCallback, useEffect, useMemo, useRef, useState } from 'react'; | ||||
| import AccessibleView from './accessibility/AccessibleView'; | ||||
| import { _ } from '@joplin/lib/locale'; | ||||
| import useReduceMotionEnabled from '../utils/hooks/useReduceMotionEnabled'; | ||||
| import { themeStyle } from './global-style'; | ||||
|  | ||||
| export enum SideMenuPosition { | ||||
| 	Left = 'left', | ||||
| 	Right = 'right', | ||||
| } | ||||
|  | ||||
| export type OnChangeCallback = (isOpen: boolean)=> void; | ||||
|  | ||||
| interface Props { | ||||
| 	themeId: number; | ||||
| 	isOpen: boolean; | ||||
|  | ||||
| 	menu: React.ReactNode; | ||||
| 	children: React.ReactNode|React.ReactNode[]; | ||||
| 	edgeHitWidth: number; | ||||
| 	toleranceX: number; | ||||
| 	toleranceY: number; | ||||
| 	openMenuOffset: number; | ||||
| 	menuPosition: SideMenuPosition; | ||||
|  | ||||
| 	onChange: OnChangeCallback; | ||||
| 	disableGestures: boolean; | ||||
| } | ||||
|  | ||||
| interface UseStylesProps { | ||||
| 	themeId: number; | ||||
| 	isLeftMenu: boolean; | ||||
| 	menuWidth: number; | ||||
| 	menuOpenFraction: Animated.AnimatedInterpolation<number>; | ||||
| } | ||||
|  | ||||
| const useStyles = ({ themeId, isLeftMenu, menuWidth, menuOpenFraction }: UseStylesProps) => { | ||||
| 	const { height: windowHeight, width: windowWidth } = useWindowDimensions(); | ||||
| 	return useMemo(() => { | ||||
| 		const theme = themeStyle(themeId); | ||||
| 		return StyleSheet.create({ | ||||
| 			mainContainer: { | ||||
| 				display: 'flex', | ||||
| 				alignContent: 'stretch', | ||||
| 				height: windowHeight, | ||||
| 			}, | ||||
| 			contentOuterWrapper: { | ||||
| 				width: windowWidth, | ||||
| 				height: windowHeight, | ||||
| 				transform: [{ | ||||
| 					translateX: menuOpenFraction.interpolate({ | ||||
| 						inputRange: [0, 1], | ||||
| 						outputRange: [0, isLeftMenu ? menuWidth : -menuWidth], | ||||
| 					}), | ||||
| 					// The RN Animation docs suggests setting "perspective" while setting other transform styles: | ||||
| 					// https://reactnative.dev/docs/animations#bear-in-mind | ||||
| 				}, { perspective: 1000 }], | ||||
| 			}, | ||||
| 			contentWrapper: { | ||||
| 				display: 'flex', | ||||
| 				flexDirection: 'column', | ||||
| 				flexGrow: 1, | ||||
| 			}, | ||||
| 			menuWrapper: { | ||||
| 				position: 'absolute', | ||||
| 				height: windowHeight, | ||||
| 				width: menuWidth, | ||||
|  | ||||
| 				// In React Native, RTL replaces `left` with `right` and `right` with `left`. | ||||
| 				// As such, we need to reverse the normal direction in RTL mode. | ||||
| 				...(isLeftMenu === !I18nManager.isRTL ? { | ||||
| 					left: 0, | ||||
| 				} : { | ||||
| 					right: 0, | ||||
| 				}), | ||||
| 			}, | ||||
| 			closeButtonOverlay: { | ||||
| 				position: 'absolute', | ||||
| 				left: 0, | ||||
| 				right: 0, | ||||
| 				top: 0, | ||||
| 				bottom: 0, | ||||
|  | ||||
| 				zIndex: 1, | ||||
| 				width: windowWidth, | ||||
| 				height: windowHeight, | ||||
|  | ||||
| 				opacity: menuOpenFraction.interpolate({ | ||||
| 					inputRange: [0, 1], | ||||
| 					outputRange: [0, 0.1], | ||||
| 					extrapolate: 'clamp', | ||||
| 				}), | ||||
| 				backgroundColor: theme.colorFaded, | ||||
| 				display: 'flex', | ||||
| 				alignContent: 'stretch', | ||||
| 			}, | ||||
| 			overlayContent: { | ||||
| 				height: windowHeight, | ||||
| 				width: windowWidth, | ||||
| 			}, | ||||
| 		}); | ||||
| 	}, [themeId, isLeftMenu, windowWidth, windowHeight, menuWidth, menuOpenFraction]); | ||||
| }; | ||||
|  | ||||
| interface UseAnimationsProps { | ||||
| 	menuWidth: number; | ||||
| 	isLeftMenu: boolean; | ||||
| 	open: boolean; | ||||
| } | ||||
|  | ||||
| const useAnimations = ({ menuWidth, isLeftMenu, open }: UseAnimationsProps) => { | ||||
| 	const [animating, setIsAnimating] = useState(false); | ||||
| 	const menuDragOffset = useMemo(() => new Animated.Value(0), []); | ||||
| 	const basePositioningFraction = useMemo(() => new Animated.Value(0), []); | ||||
| 	const maximumDragOffsetValue = useMemo(() => new Animated.Value(1), []); | ||||
|  | ||||
| 	// Update the value in a useEffect to prevent delays in applying the animation caused by | ||||
| 	// re-renders. | ||||
| 	useEffect(() => { | ||||
| 		// In a right-side menu, the drag offset increases while the menu is closing. | ||||
| 		// It needs to be inverted in that case: | ||||
| 		// || 1: Prevents division by zero | ||||
| 		maximumDragOffsetValue.setValue((menuWidth || 1) * (isLeftMenu ? 1 : -1)); | ||||
| 	}, [menuWidth, isLeftMenu, maximumDragOffsetValue]); | ||||
|  | ||||
| 	const menuOpenFraction = useMemo(() => { | ||||
| 		const animatedDragFraction = Animated.divide(menuDragOffset, maximumDragOffsetValue); | ||||
|  | ||||
| 		return Animated.add(basePositioningFraction, animatedDragFraction); | ||||
| 	}, [menuDragOffset, basePositioningFraction, maximumDragOffsetValue]); | ||||
|  | ||||
| 	const reduceMotionEnabled = useReduceMotionEnabled(); | ||||
| 	const reduceMotionEnabledRef = useRef(false); | ||||
| 	reduceMotionEnabledRef.current = reduceMotionEnabled; | ||||
|  | ||||
| 	const updateMenuPosition = useCallback(() => { | ||||
| 		const baseAnimationProps = { | ||||
| 			easing: Easing.elastic(0.5), | ||||
| 			duration: reduceMotionEnabledRef.current ? 0 : 200, | ||||
| 			useNativeDriver: true, | ||||
| 		}; | ||||
| 		setIsAnimating(true); | ||||
|  | ||||
| 		const animation = Animated.parallel([ | ||||
| 			Animated.timing(basePositioningFraction, { toValue: open ? 1 : 0, ...baseAnimationProps }), | ||||
| 			Animated.timing(menuDragOffset, { toValue: 0, ...baseAnimationProps }), | ||||
| 		]); | ||||
| 		animation.start((result) => { | ||||
| 			if (result.finished) { | ||||
| 				setIsAnimating(false); | ||||
| 			} | ||||
| 		}); | ||||
| 	}, [open, menuDragOffset, basePositioningFraction]); | ||||
| 	useEffect(() => { | ||||
| 		updateMenuPosition(); | ||||
| 	}, [updateMenuPosition]); | ||||
|  | ||||
| 	return { setIsAnimating, animating, updateMenuPosition, menuOpenFraction, menuDragOffset }; | ||||
| }; | ||||
|  | ||||
| const SideMenuComponent: React.FC<Props> = props => { | ||||
| 	const [open, setIsOpen] = useState(false); | ||||
|  | ||||
| 	useEffect(() => { | ||||
| 		setIsOpen(props.isOpen); | ||||
| 	}, [props.isOpen]); | ||||
|  | ||||
| 	const [menuWidth, setMenuWidth] = useState(0); | ||||
| 	const [contentWidth, setContentWidth] = useState(0); | ||||
|  | ||||
| 	// In right-to-left layout, swap left and right to be consistent with other parts of | ||||
| 	// the app's layout. | ||||
| 	const isLeftMenu = props.menuPosition === (I18nManager.isRTL ? SideMenuPosition.Right : SideMenuPosition.Left); | ||||
|  | ||||
| 	const onLayoutChange = useCallback((e: LayoutChangeEvent) => { | ||||
| 		const { width } = e.nativeEvent.layout; | ||||
| 		const openMenuOffsetPercentage = props.openMenuOffset / Dimensions.get('window').width; | ||||
| 		const menuWidth = Math.floor(width * openMenuOffsetPercentage); | ||||
|  | ||||
| 		setContentWidth(width); | ||||
| 		setMenuWidth(menuWidth); | ||||
| 	}, [props.openMenuOffset]); | ||||
|  | ||||
| 	const { animating, setIsAnimating, menuDragOffset, updateMenuPosition, menuOpenFraction } = useAnimations({ | ||||
| 		isLeftMenu, menuWidth, open, | ||||
| 	}); | ||||
|  | ||||
| 	const panResponder = useMemo(() => { | ||||
| 		return PanResponder.create({ | ||||
| 			onMoveShouldSetPanResponderCapture: (_event, gestureState) => { | ||||
| 				if (props.disableGestures) { | ||||
| 					return false; | ||||
| 				} | ||||
|  | ||||
| 				let startX; | ||||
| 				let dx; | ||||
| 				const dy = gestureState.dy; | ||||
|  | ||||
| 				// Untransformed start position of the gesture -- moveX is the current position of | ||||
| 				// the pointer. Subtracting dx gives us the original start position. | ||||
| 				const gestureStartScreenX = gestureState.moveX - gestureState.dx; | ||||
|  | ||||
| 				// Transform x, dx such that they are relative to the target screen edge -- this simplifies later | ||||
| 				// math. | ||||
| 				if (isLeftMenu) { | ||||
| 					startX = gestureStartScreenX; | ||||
| 					dx = gestureState.dx; | ||||
| 				} else { | ||||
| 					startX = contentWidth - gestureStartScreenX; | ||||
| 					dx = -gestureState.dx; | ||||
| 				} | ||||
|  | ||||
| 				const motionWithinToleranceY = Math.abs(dy) <= props.toleranceY; | ||||
| 				let startWithinTolerance, motionWithinToleranceX; | ||||
| 				if (open) { | ||||
| 					startWithinTolerance = startX >= menuWidth - props.edgeHitWidth; | ||||
| 					motionWithinToleranceX = dx <= -props.toleranceX; | ||||
| 				} else { | ||||
| 					startWithinTolerance = startX <= props.edgeHitWidth; | ||||
| 					motionWithinToleranceX = dx >= props.toleranceX; | ||||
| 				} | ||||
|  | ||||
| 				return startWithinTolerance && motionWithinToleranceX && motionWithinToleranceY; | ||||
| 			}, | ||||
| 			onPanResponderGrant: () => { | ||||
| 				setIsAnimating(true); | ||||
| 			}, | ||||
| 			onPanResponderMove: Animated.event([ | ||||
| 				null, | ||||
| 				// Updates menuDragOffset with the .dx property of the second argument: | ||||
| 				{ dx: menuDragOffset }, | ||||
| 			], { useNativeDriver: false }), | ||||
| 			onPanResponderEnd: (_event, gestureState) => { | ||||
| 				const newOpen = (gestureState.dx > 0) === isLeftMenu; | ||||
| 				if (newOpen === open) { | ||||
| 					updateMenuPosition(); | ||||
| 				} else { | ||||
| 					setIsOpen(newOpen); | ||||
| 				} | ||||
| 			}, | ||||
| 		}); | ||||
| 	}, [isLeftMenu, menuDragOffset, menuWidth, props.toleranceX, props.toleranceY, contentWidth, open, props.disableGestures, props.edgeHitWidth, updateMenuPosition, setIsAnimating]); | ||||
|  | ||||
| 	const onChangeRef = useRef(props.onChange); | ||||
| 	onChangeRef.current = props.onChange; | ||||
| 	useEffect(() => { | ||||
| 		onChangeRef.current(open); | ||||
|  | ||||
| 		AccessibilityInfo.announceForAccessibility( | ||||
| 			open ? _('Side menu opened') : _('Side menu closed'), | ||||
| 		); | ||||
| 	}, [open]); | ||||
|  | ||||
| 	const onCloseButtonPress = useCallback(() => { | ||||
| 		setIsOpen(false); | ||||
| 		// Set isAnimating as soon as possible to avoid components disappearing, then reappearing. | ||||
| 		setIsAnimating(true); | ||||
| 	}, [setIsAnimating]); | ||||
|  | ||||
| 	const styles = useStyles({ themeId: props.themeId, menuOpenFraction, menuWidth, isLeftMenu }); | ||||
|  | ||||
| 	const menuComponent = ( | ||||
| 		<AccessibleView | ||||
| 			inert={!open} | ||||
| 			style={styles.menuWrapper} | ||||
| 		> | ||||
| 			<AccessibleView | ||||
| 				// Auto-focuses an empty view at the beginning of the sidemenu -- if we instead | ||||
| 				// focus the container view, VoiceOver fails to focus to any components within | ||||
| 				// the sidebar. | ||||
| 				refocusCounter={!open ? 1 : undefined} | ||||
| 			/> | ||||
|  | ||||
| 			{props.menu} | ||||
| 		</AccessibleView> | ||||
| 	); | ||||
|  | ||||
| 	const contentComponent = ( | ||||
| 		<AccessibleView | ||||
| 			inert={open} | ||||
| 			style={styles.contentWrapper} | ||||
| 		> | ||||
| 			<AccessibleView refocusCounter={open ? 1 : undefined} /> | ||||
| 			{props.children} | ||||
| 		</AccessibleView> | ||||
| 	); | ||||
| 	const closeButtonOverlay = (open || animating) ? ( | ||||
| 		<Animated.View | ||||
| 			style={styles.closeButtonOverlay} | ||||
| 		> | ||||
| 			<Pressable | ||||
| 				aria-label={_('Close side menu')} | ||||
| 				role='button' | ||||
| 				onPress={onCloseButtonPress} | ||||
| 				style={styles.overlayContent} | ||||
| 			></Pressable> | ||||
| 		</Animated.View> | ||||
| 	) : null; | ||||
|  | ||||
| 	return ( | ||||
| 		<View | ||||
| 			onLayout={onLayoutChange} | ||||
| 			style={styles.mainContainer} | ||||
| 			{...panResponder.panHandlers} | ||||
| 		> | ||||
| 			{menuComponent} | ||||
| 			<Animated.View style={styles.contentOuterWrapper}> | ||||
| 				{contentComponent} | ||||
| 				{closeButtonOverlay} | ||||
| 			</Animated.View> | ||||
| 		</View> | ||||
| 	); | ||||
| }; | ||||
|  | ||||
| const SideMenu = connect((state: State) => { | ||||
| 	return { | ||||
| 		themeId: state.settings.theme, | ||||
| 		isOpen: state.showSideMenu, | ||||
| 	}; | ||||
| })(SideMenuComponent); | ||||
|  | ||||
| export default SideMenu; | ||||
| @@ -19,14 +19,12 @@ import restoreItems from '@joplin/lib/services/trash/restoreItems'; | ||||
| import emptyTrash from '@joplin/lib/services/trash/emptyTrash'; | ||||
| import { ModelType } from '@joplin/lib/BaseModel'; | ||||
| import { DialogContext } from './DialogManager'; | ||||
| import AccessibleView from './accessibility/AccessibleView'; | ||||
| const { TouchableRipple } = require('react-native-paper'); | ||||
| const { substrWithEllipsis } = require('@joplin/lib/string-utils'); | ||||
|  | ||||
| interface Props { | ||||
| 	syncStarted: boolean; | ||||
| 	themeId: number; | ||||
| 	sideMenuVisible: boolean; | ||||
| 	// eslint-disable-next-line @typescript-eslint/ban-types -- Old code before rule was applied | ||||
| 	dispatch: Function; | ||||
| 	collapsedFolderIds: string[]; | ||||
| @@ -583,34 +581,22 @@ const SideMenuContentComponent = (props: Props) => { | ||||
| 		items = items.concat(folderItems); | ||||
| 	} | ||||
|  | ||||
| 	const isHidden = !props.sideMenuVisible; | ||||
|  | ||||
| 	const style = { | ||||
| 		flex: 1, | ||||
| 		borderRightWidth: 1, | ||||
| 		borderRightColor: theme.dividerColor, | ||||
| 		backgroundColor: theme.backgroundColor, | ||||
|  | ||||
| 		// Have the UI reflect whether the View is hidden to the screen reader. | ||||
| 		// This way, there will be visual feedback if isHidden is incorrect. | ||||
| 		opacity: isHidden ? 0.5 : undefined, | ||||
| 	}; | ||||
|  | ||||
| 	return ( | ||||
| 		<AccessibleView | ||||
| 			style={style} | ||||
|  | ||||
| 			// Accessibility, keyboard, and touch hidden. | ||||
| 			inert={isHidden} | ||||
| 			refocusCounter={isHidden ? undefined : 1} | ||||
| 		> | ||||
| 		<View style={style}> | ||||
| 			<View style={{ flex: 1, opacity: props.opacity }}> | ||||
| 				<ScrollView scrollsToTop={false} style={styles_.menu}> | ||||
| 					{items} | ||||
| 				</ScrollView> | ||||
| 				{renderBottomPanel()} | ||||
| 			</View> | ||||
| 		</AccessibleView> | ||||
| 		</View> | ||||
| 	); | ||||
| }; | ||||
|  | ||||
| @@ -624,9 +610,6 @@ export default connect((state: AppState) => { | ||||
| 		notesParentType: state.notesParentType, | ||||
| 		locale: state.settings.locale, | ||||
| 		themeId: state.settings.theme, | ||||
| 		sideMenuVisible: state.showSideMenu, | ||||
| 		// Don't do the opacity animation as it means re-rendering the list multiple times | ||||
| 		// opacity: state.sideMenuOpenPercent, | ||||
| 		collapsedFolderIds: state.collapsedFolderIds, | ||||
| 		decryptionWorker: state.decryptionWorker, | ||||
| 		resourceFetcher: state.resourceFetcher, | ||||
|   | ||||
| @@ -66,7 +66,6 @@ | ||||
|     "react-native-safe-area-context": "4.10.5", | ||||
|     "react-native-securerandom": "1.0.1", | ||||
|     "react-native-share": "10.2.1", | ||||
|     "react-native-side-menu-updated": "1.3.2", | ||||
|     "react-native-sqlite-storage": "6.0.1", | ||||
|     "react-native-url-polyfill": "2.0.0", | ||||
|     "react-native-vector-icons": "10.1.0", | ||||
|   | ||||
| @@ -29,7 +29,7 @@ import SyncTargetOneDrive from '@joplin/lib/SyncTargetOneDrive'; | ||||
| import initProfile from '@joplin/lib/services/profileConfig/initProfile'; | ||||
| const VersionInfo = require('react-native-version-info').default; | ||||
| const { Keyboard, BackHandler, Animated, StatusBar, Platform, Dimensions } = require('react-native'); | ||||
| import { AppState as RNAppState, EmitterSubscription, View, Text, Linking, NativeEventSubscription, Appearance, AccessibilityInfo, ActivityIndicator } from 'react-native'; | ||||
| import { AppState as RNAppState, EmitterSubscription, View, Text, Linking, NativeEventSubscription, Appearance, ActivityIndicator } from 'react-native'; | ||||
| import getResponsiveValue from './components/getResponsiveValue'; | ||||
| import NetInfo from '@react-native-community/netinfo'; | ||||
| const DropdownAlert = require('react-native-dropdownalert').default; | ||||
| @@ -66,7 +66,7 @@ const { OneDriveLoginScreen } = require('./components/screens/onedrive-login.js' | ||||
| import EncryptionConfigScreen from './components/screens/encryption-config'; | ||||
| const { DropboxLoginScreen } = require('./components/screens/dropbox-login.js'); | ||||
| import { MenuProvider } from 'react-native-popup-menu'; | ||||
| import SideMenu from './components/SideMenu'; | ||||
| import SideMenu, { SideMenuPosition } from './components/SideMenu'; | ||||
| import SideMenuContent from './components/side-menu-content'; | ||||
| const { SideMenuContentNote } = require('./components/side-menu-content-note.js'); | ||||
| import { reg } from '@joplin/lib/registry'; | ||||
| @@ -137,8 +137,6 @@ import lockToSingleInstance from './utils/lockToSingleInstance'; | ||||
| import { AppState } from './utils/types'; | ||||
| import { getDisplayParentId } from '@joplin/lib/services/trash'; | ||||
|  | ||||
| type SideMenuPosition = 'left' | 'right'; | ||||
|  | ||||
| const logger = Logger.create('root'); | ||||
|  | ||||
| // eslint-disable-next-line @typescript-eslint/no-explicit-any -- Old code before rule was applied | ||||
| @@ -393,12 +391,6 @@ const appReducer = (state = appDefaultState, action: any) => { | ||||
| 			newState.showSideMenu = false; | ||||
| 			break; | ||||
|  | ||||
| 		case 'SIDE_MENU_OPEN_PERCENT': | ||||
|  | ||||
| 			newState = { ...state }; | ||||
| 			newState.sideMenuOpenPercent = action.value; | ||||
| 			break; | ||||
|  | ||||
| 		case 'SET_PLUGIN_PANELS_DIALOG_VISIBLE': | ||||
| 			newState = { ...state }; | ||||
| 			newState.showPanelsDialog = action.visible; | ||||
| @@ -1188,9 +1180,6 @@ class AppComponent extends React.Component { | ||||
| 		this.props.dispatch({ | ||||
| 			type: isOpen ? 'SIDE_MENU_OPEN' : 'SIDE_MENU_CLOSE', | ||||
| 		}); | ||||
| 		AccessibilityInfo.announceForAccessibility( | ||||
| 			isOpen ? _('Side menu opened') : _('Side menu closed'), | ||||
| 		); | ||||
| 	} | ||||
|  | ||||
| 	private getSideMenuWidth = () => { | ||||
| @@ -1223,12 +1212,12 @@ class AppComponent extends React.Component { | ||||
| 		const theme: Theme = themeStyle(this.props.themeId); | ||||
|  | ||||
| 		let sideMenuContent: ReactNode = null; | ||||
| 		let menuPosition: SideMenuPosition = 'left'; | ||||
| 		let menuPosition = SideMenuPosition.Left; | ||||
| 		let disableSideMenuGestures = this.props.disableSideMenuGestures; | ||||
|  | ||||
| 		if (this.props.routeName === 'Note') { | ||||
| 			sideMenuContent = <SafeAreaView style={{ flex: 1, backgroundColor: theme.backgroundColor }}><SideMenuContentNote options={this.props.noteSideMenuOptions}/></SafeAreaView>; | ||||
| 			menuPosition = 'right'; | ||||
| 			menuPosition = SideMenuPosition.Right; | ||||
| 		} else if (this.props.routeName === 'Config') { | ||||
| 			disableSideMenuGestures = true; | ||||
| 		} else { | ||||
| @@ -1283,12 +1272,6 @@ class AppComponent extends React.Component { | ||||
| 					menuPosition={menuPosition} | ||||
| 					onChange={(isOpen: boolean) => this.sideMenu_change(isOpen)} | ||||
| 					disableGestures={disableSideMenuGestures} | ||||
| 					onSliding={(percent: number) => { | ||||
| 						this.props.dispatch({ | ||||
| 							type: 'SIDE_MENU_OPEN_PERCENT', | ||||
| 							value: percent, | ||||
| 						}); | ||||
| 					}} | ||||
| 				> | ||||
| 					<StatusBar barStyle={statusBarStyle} /> | ||||
| 					<MenuProvider style={{ flex: 1 }}> | ||||
|   | ||||
| @@ -11,7 +11,6 @@ export const DEFAULT_ROUTE = { | ||||
| const appDefaultState: AppState = { | ||||
| 	smartFilterId: undefined, | ||||
| 	...defaultState, | ||||
| 	sideMenuOpenPercent: 0, | ||||
| 	route: DEFAULT_ROUTE, | ||||
| 	noteSelectionEnabled: false, | ||||
| 	noteSideMenuOptions: null, | ||||
|   | ||||
							
								
								
									
										19
									
								
								packages/app-mobile/utils/hooks/useReduceMotionEnabled.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										19
									
								
								packages/app-mobile/utils/hooks/useReduceMotionEnabled.ts
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,19 @@ | ||||
| import useAsyncEffect from '@joplin/lib/hooks/useAsyncEffect'; | ||||
| import { useEffect, useState } from 'react'; | ||||
| import { AccessibilityInfo } from 'react-native'; | ||||
|  | ||||
| const useReduceMotionEnabled = () => { | ||||
| 	const [reduceMotionEnabled, setReduceMotionEnabled] = useState(false); | ||||
| 	useEffect(() => { | ||||
| 		AccessibilityInfo.addEventListener('reduceMotionChanged', (enabled) => { | ||||
| 			setReduceMotionEnabled(enabled); | ||||
| 		}); | ||||
| 	}, []); | ||||
| 	useAsyncEffect(async () => { | ||||
| 		setReduceMotionEnabled(await AccessibilityInfo.isReduceMotionEnabled()); | ||||
| 	}, []); | ||||
|  | ||||
| 	return reduceMotionEnabled; | ||||
| }; | ||||
|  | ||||
| export default useReduceMotionEnabled; | ||||
| @@ -1,7 +1,6 @@ | ||||
| import { State } from '@joplin/lib/reducer'; | ||||
|  | ||||
| export interface AppState extends State { | ||||
| 	sideMenuOpenPercent: number; | ||||
| 	showPanelsDialog: boolean; | ||||
| 	isOnMobileData: boolean; | ||||
| 	// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Old code before rule was applied | ||||
|   | ||||
							
								
								
									
										12
									
								
								yarn.lock
									
									
									
									
									
								
							
							
						
						
									
										12
									
								
								yarn.lock
									
									
									
									
									
								
							| @@ -7535,7 +7535,6 @@ __metadata: | ||||
|     react-native-safe-area-context: 4.10.5 | ||||
|     react-native-securerandom: 1.0.1 | ||||
|     react-native-share: 10.2.1 | ||||
|     react-native-side-menu-updated: 1.3.2 | ||||
|     react-native-sqlite-storage: 6.0.1 | ||||
|     react-native-url-polyfill: 2.0.0 | ||||
|     react-native-vector-icons: 10.1.0 | ||||
| @@ -36465,7 +36464,7 @@ __metadata: | ||||
|   languageName: node | ||||
|   linkType: hard | ||||
| 
 | ||||
| "prop-types@npm:15.8.1, prop-types@npm:^15.5.10, prop-types@npm:^15.8.1": | ||||
| "prop-types@npm:15.8.1, prop-types@npm:^15.8.1": | ||||
|   version: 15.8.1 | ||||
|   resolution: "prop-types@npm:15.8.1" | ||||
|   dependencies: | ||||
| @@ -37402,15 +37401,6 @@ __metadata: | ||||
|   languageName: node | ||||
|   linkType: hard | ||||
| 
 | ||||
| "react-native-side-menu-updated@npm:1.3.2": | ||||
|   version: 1.3.2 | ||||
|   resolution: "react-native-side-menu-updated@npm:1.3.2" | ||||
|   dependencies: | ||||
|     prop-types: ^15.5.10 | ||||
|   checksum: 5d7ae7d2b372c80d9f7a3472f945daa6c11b43f00193ebec92fdb40ee853e86f522686736aa6a510a4fb09479c8eb7e4f14b53ad117d7e23749cd58c3c9d6cb7 | ||||
|   languageName: node | ||||
|   linkType: hard | ||||
| 
 | ||||
| "react-native-sqlite-storage@npm:6.0.1": | ||||
|   version: 6.0.1 | ||||
|   resolution: "react-native-sqlite-storage@npm:6.0.1" | ||||
|   | ||||
		Reference in New Issue
	
	Block a user