1
0
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:
Henry Heino 2024-09-16 14:17:12 -07:00 committed by GitHub
parent 2594c1edb1
commit e0daf807a6
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
11 changed files with 354 additions and 77 deletions

View File

@ -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/fs-driver/testUtil/verifyDirectoryMatches.js
packages/app-mobile/utils/getPackageInfo.js packages/app-mobile/utils/getPackageInfo.js
packages/app-mobile/utils/getVersionInfoText.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/fileToImage.web.js
packages/app-mobile/utils/image/getImageDimensions.js packages/app-mobile/utils/image/getImageDimensions.js
packages/app-mobile/utils/image/resizeImage.js packages/app-mobile/utils/image/resizeImage.js

1
.gitignore vendored
View File

@ -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/fs-driver/testUtil/verifyDirectoryMatches.js
packages/app-mobile/utils/getPackageInfo.js packages/app-mobile/utils/getPackageInfo.js
packages/app-mobile/utils/getVersionInfoText.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/fileToImage.web.js
packages/app-mobile/utils/image/getImageDimensions.js packages/app-mobile/utils/image/getImageDimensions.js
packages/app-mobile/utils/image/resizeImage.js packages/app-mobile/utils/image/resizeImage.js

View File

@ -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;

View 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;

View File

@ -19,14 +19,12 @@ import restoreItems from '@joplin/lib/services/trash/restoreItems';
import emptyTrash from '@joplin/lib/services/trash/emptyTrash'; import emptyTrash from '@joplin/lib/services/trash/emptyTrash';
import { ModelType } from '@joplin/lib/BaseModel'; import { ModelType } from '@joplin/lib/BaseModel';
import { DialogContext } from './DialogManager'; import { DialogContext } from './DialogManager';
import AccessibleView from './accessibility/AccessibleView';
const { TouchableRipple } = require('react-native-paper'); const { TouchableRipple } = require('react-native-paper');
const { substrWithEllipsis } = require('@joplin/lib/string-utils'); const { substrWithEllipsis } = require('@joplin/lib/string-utils');
interface Props { interface Props {
syncStarted: boolean; syncStarted: boolean;
themeId: number; themeId: number;
sideMenuVisible: boolean;
// eslint-disable-next-line @typescript-eslint/ban-types -- Old code before rule was applied // eslint-disable-next-line @typescript-eslint/ban-types -- Old code before rule was applied
dispatch: Function; dispatch: Function;
collapsedFolderIds: string[]; collapsedFolderIds: string[];
@ -583,34 +581,22 @@ const SideMenuContentComponent = (props: Props) => {
items = items.concat(folderItems); items = items.concat(folderItems);
} }
const isHidden = !props.sideMenuVisible;
const style = { const style = {
flex: 1, flex: 1,
borderRightWidth: 1, borderRightWidth: 1,
borderRightColor: theme.dividerColor, borderRightColor: theme.dividerColor,
backgroundColor: theme.backgroundColor, 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 ( return (
<AccessibleView <View style={style}>
style={style}
// Accessibility, keyboard, and touch hidden.
inert={isHidden}
refocusCounter={isHidden ? undefined : 1}
>
<View style={{ flex: 1, opacity: props.opacity }}> <View style={{ flex: 1, opacity: props.opacity }}>
<ScrollView scrollsToTop={false} style={styles_.menu}> <ScrollView scrollsToTop={false} style={styles_.menu}>
{items} {items}
</ScrollView> </ScrollView>
{renderBottomPanel()} {renderBottomPanel()}
</View> </View>
</AccessibleView> </View>
); );
}; };
@ -624,9 +610,6 @@ export default connect((state: AppState) => {
notesParentType: state.notesParentType, notesParentType: state.notesParentType,
locale: state.settings.locale, locale: state.settings.locale,
themeId: state.settings.theme, 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, collapsedFolderIds: state.collapsedFolderIds,
decryptionWorker: state.decryptionWorker, decryptionWorker: state.decryptionWorker,
resourceFetcher: state.resourceFetcher, resourceFetcher: state.resourceFetcher,

View File

@ -66,7 +66,6 @@
"react-native-safe-area-context": "4.10.5", "react-native-safe-area-context": "4.10.5",
"react-native-securerandom": "1.0.1", "react-native-securerandom": "1.0.1",
"react-native-share": "10.2.1", "react-native-share": "10.2.1",
"react-native-side-menu-updated": "1.3.2",
"react-native-sqlite-storage": "6.0.1", "react-native-sqlite-storage": "6.0.1",
"react-native-url-polyfill": "2.0.0", "react-native-url-polyfill": "2.0.0",
"react-native-vector-icons": "10.1.0", "react-native-vector-icons": "10.1.0",

View File

@ -29,7 +29,7 @@ import SyncTargetOneDrive from '@joplin/lib/SyncTargetOneDrive';
import initProfile from '@joplin/lib/services/profileConfig/initProfile'; import initProfile from '@joplin/lib/services/profileConfig/initProfile';
const VersionInfo = require('react-native-version-info').default; const VersionInfo = require('react-native-version-info').default;
const { Keyboard, BackHandler, Animated, StatusBar, Platform, Dimensions } = require('react-native'); 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 getResponsiveValue from './components/getResponsiveValue';
import NetInfo from '@react-native-community/netinfo'; import NetInfo from '@react-native-community/netinfo';
const DropdownAlert = require('react-native-dropdownalert').default; 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'; import EncryptionConfigScreen from './components/screens/encryption-config';
const { DropboxLoginScreen } = require('./components/screens/dropbox-login.js'); const { DropboxLoginScreen } = require('./components/screens/dropbox-login.js');
import { MenuProvider } from 'react-native-popup-menu'; 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'; import SideMenuContent from './components/side-menu-content';
const { SideMenuContentNote } = require('./components/side-menu-content-note.js'); const { SideMenuContentNote } = require('./components/side-menu-content-note.js');
import { reg } from '@joplin/lib/registry'; import { reg } from '@joplin/lib/registry';
@ -137,8 +137,6 @@ import lockToSingleInstance from './utils/lockToSingleInstance';
import { AppState } from './utils/types'; import { AppState } from './utils/types';
import { getDisplayParentId } from '@joplin/lib/services/trash'; import { getDisplayParentId } from '@joplin/lib/services/trash';
type SideMenuPosition = 'left' | 'right';
const logger = Logger.create('root'); const logger = Logger.create('root');
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Old code before rule was applied // 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; newState.showSideMenu = false;
break; break;
case 'SIDE_MENU_OPEN_PERCENT':
newState = { ...state };
newState.sideMenuOpenPercent = action.value;
break;
case 'SET_PLUGIN_PANELS_DIALOG_VISIBLE': case 'SET_PLUGIN_PANELS_DIALOG_VISIBLE':
newState = { ...state }; newState = { ...state };
newState.showPanelsDialog = action.visible; newState.showPanelsDialog = action.visible;
@ -1188,9 +1180,6 @@ class AppComponent extends React.Component {
this.props.dispatch({ this.props.dispatch({
type: isOpen ? 'SIDE_MENU_OPEN' : 'SIDE_MENU_CLOSE', type: isOpen ? 'SIDE_MENU_OPEN' : 'SIDE_MENU_CLOSE',
}); });
AccessibilityInfo.announceForAccessibility(
isOpen ? _('Side menu opened') : _('Side menu closed'),
);
} }
private getSideMenuWidth = () => { private getSideMenuWidth = () => {
@ -1223,12 +1212,12 @@ class AppComponent extends React.Component {
const theme: Theme = themeStyle(this.props.themeId); const theme: Theme = themeStyle(this.props.themeId);
let sideMenuContent: ReactNode = null; let sideMenuContent: ReactNode = null;
let menuPosition: SideMenuPosition = 'left'; let menuPosition = SideMenuPosition.Left;
let disableSideMenuGestures = this.props.disableSideMenuGestures; let disableSideMenuGestures = this.props.disableSideMenuGestures;
if (this.props.routeName === 'Note') { if (this.props.routeName === 'Note') {
sideMenuContent = <SafeAreaView style={{ flex: 1, backgroundColor: theme.backgroundColor }}><SideMenuContentNote options={this.props.noteSideMenuOptions}/></SafeAreaView>; 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') { } else if (this.props.routeName === 'Config') {
disableSideMenuGestures = true; disableSideMenuGestures = true;
} else { } else {
@ -1283,12 +1272,6 @@ class AppComponent extends React.Component {
menuPosition={menuPosition} menuPosition={menuPosition}
onChange={(isOpen: boolean) => this.sideMenu_change(isOpen)} onChange={(isOpen: boolean) => this.sideMenu_change(isOpen)}
disableGestures={disableSideMenuGestures} disableGestures={disableSideMenuGestures}
onSliding={(percent: number) => {
this.props.dispatch({
type: 'SIDE_MENU_OPEN_PERCENT',
value: percent,
});
}}
> >
<StatusBar barStyle={statusBarStyle} /> <StatusBar barStyle={statusBarStyle} />
<MenuProvider style={{ flex: 1 }}> <MenuProvider style={{ flex: 1 }}>

View File

@ -11,7 +11,6 @@ export const DEFAULT_ROUTE = {
const appDefaultState: AppState = { const appDefaultState: AppState = {
smartFilterId: undefined, smartFilterId: undefined,
...defaultState, ...defaultState,
sideMenuOpenPercent: 0,
route: DEFAULT_ROUTE, route: DEFAULT_ROUTE,
noteSelectionEnabled: false, noteSelectionEnabled: false,
noteSideMenuOptions: null, noteSideMenuOptions: null,

View 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;

View File

@ -1,7 +1,6 @@
import { State } from '@joplin/lib/reducer'; import { State } from '@joplin/lib/reducer';
export interface AppState extends State { export interface AppState extends State {
sideMenuOpenPercent: number;
showPanelsDialog: boolean; showPanelsDialog: boolean;
isOnMobileData: boolean; isOnMobileData: boolean;
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Old code before rule was applied // eslint-disable-next-line @typescript-eslint/no-explicit-any -- Old code before rule was applied

View File

@ -7535,7 +7535,6 @@ __metadata:
react-native-safe-area-context: 4.10.5 react-native-safe-area-context: 4.10.5
react-native-securerandom: 1.0.1 react-native-securerandom: 1.0.1
react-native-share: 10.2.1 react-native-share: 10.2.1
react-native-side-menu-updated: 1.3.2
react-native-sqlite-storage: 6.0.1 react-native-sqlite-storage: 6.0.1
react-native-url-polyfill: 2.0.0 react-native-url-polyfill: 2.0.0
react-native-vector-icons: 10.1.0 react-native-vector-icons: 10.1.0
@ -36465,7 +36464,7 @@ __metadata:
languageName: node languageName: node
linkType: hard 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 version: 15.8.1
resolution: "prop-types@npm:15.8.1" resolution: "prop-types@npm:15.8.1"
dependencies: dependencies:
@ -37402,15 +37401,6 @@ __metadata:
languageName: node languageName: node
linkType: hard 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": "react-native-sqlite-storage@npm:6.0.1":
version: 6.0.1 version: 6.0.1
resolution: "react-native-sqlite-storage@npm:6.0.1" resolution: "react-native-sqlite-storage@npm:6.0.1"