mirror of
https://github.com/laurent22/joplin.git
synced 2025-01-02 12:47:41 +02:00
Mobile: Fixes #11028: Accessibility: Fix sidebar broken in right-to-left mode, improve screen reader accessibility (#11056)
This commit is contained in:
parent
2594c1edb1
commit
e0daf807a6
@ -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"
|
||||
|
Loading…
Reference in New Issue
Block a user