diff --git a/__tests__/HomeScreen.test.tsx b/__tests__/HomeScreen.test.tsx index bfa8e21..c831f98 100644 --- a/__tests__/HomeScreen.test.tsx +++ b/__tests__/HomeScreen.test.tsx @@ -1,13 +1,15 @@ import React from 'react'; +import { configureStore } from '@reduxjs/toolkit'; import { Provider } from 'react-redux'; import { NavigationContainer } from '@react-navigation/native'; import { useSafeAreaInsets } from 'react-native-safe-area-context'; -import { render } from '@testing-library/react-native'; -import configureStore from 'redux-mock-store'; +import { fireEvent, render } from '@testing-library/react-native'; + +import foldersReducer from '@store/foldersSlice'; +import foldersMockData from '@store/mockData/folders.mockData'; import HomeScreen from '@screens/HomeScreen'; jest.mock('@screens/HomeScreen/components/FolderList', () => 'FolderList'); -jest.mock('@screens/HomeScreen/components/SearchBox', () => 'SearchBox'); jest.mock( '@screens/HomeScreen/components/NotesResultsList', () => 'NotesResultsList', @@ -23,9 +25,8 @@ jest.mock('react-native-safe-area-context', () => ({ })); describe('HomeScreen', () => { - const mockStore = configureStore(); const initialState = { - folders: { folders: [{ id: 1, name: 'Test Folder', notes: [] }] }, + folders: foldersMockData, }; beforeEach(() => { @@ -41,8 +42,16 @@ describe('HomeScreen', () => { jest.clearAllMocks(); }); + const createTestStore = (preloadedState = initialState) => + configureStore({ + reducer: { + folders: foldersReducer, + }, + preloadedState, + }); + const renderWithProviders = (component: React.ReactNode) => { - const store = mockStore(initialState); + const store = createTestStore(initialState); return render( @@ -58,9 +67,7 @@ describe('HomeScreen', () => { it('renders the container with correct insets', () => { const { getByTestId } = renderWithProviders(); - const container = getByTestId('container'); - expect(container.props.style.paddingTop).toBe(20); expect(container.props.style.paddingLeft).toBe(0); expect(container.props.style.paddingRight).toBe(-2); @@ -70,4 +77,13 @@ describe('HomeScreen', () => { const { getByTestId } = renderWithProviders(); expect(getByTestId('folder-list')).toBeTruthy(); }); + + it('renders the notes results list when there is a search query', () => { + const { getByTestId } = renderWithProviders(); + + const searchInput = getByTestId('search-input'); + fireEvent.changeText(searchInput, 'Sample Note'); + + expect(getByTestId('notes-results')).toBeTruthy(); + }); }); diff --git a/__tests__/NoteDetailsScreen.test.tsx b/__tests__/NoteDetailsScreen.test.tsx index 75ded66..21d3f59 100644 --- a/__tests__/NoteDetailsScreen.test.tsx +++ b/__tests__/NoteDetailsScreen.test.tsx @@ -1,20 +1,21 @@ import React from 'react'; import { render, fireEvent, waitFor } from '@testing-library/react-native'; -import configureStore from 'redux-mock-store'; import { Provider } from 'react-redux'; +import { configureStore } from '@reduxjs/toolkit'; import { useSafeAreaInsets } from 'react-native-safe-area-context'; -import { - useFetchNoteDetailsQuery, - useUpdateNoteMutation, -} from '@store/queries/notes'; -import NoteDetailsScreen from '@screens/NoteDetailsScreen'; import { NavigationContainer, RouteProp, useNavigation, } from '@react-navigation/native'; + +import foldersReducer from '@store/foldersSlice'; +import { + useFetchNoteDetailsQuery, + useUpdateNoteMutation, +} from '@store/queries/notes'; +import NoteDetailsScreen from '@screens/NoteDetailsScreen'; import { RootStackParamList } from '@navigation/types'; -import { formatTimestampToDateTime } from '@root/src/utils'; jest.mock('@store/queries/notes', () => ({ useFetchNoteDetailsQuery: jest.fn(), @@ -26,12 +27,29 @@ jest.mock('react-native-safe-area-context', () => ({ useSafeAreaInsets: jest.fn(), })); -jest.mock('@react-navigation/native', () => ({ - ...jest.requireActual('@react-navigation/native'), - useNavigation: jest.fn(), +jest.mock('@store/queries/notes', () => ({ + useFetchNoteDetailsQuery: jest.fn(), + useUpdateNoteMutation: jest.fn(() => [jest.fn()]), + useCreateNoteMutation: jest.fn(() => [jest.fn().mockResolvedValue({})]), })); -const mockStore = configureStore([]); +jest.mock('@react-navigation/native', () => { + const actual = jest.requireActual('@react-navigation/native'); + return { + ...actual, + useNavigation: jest.fn(), + }; +}); + +const createTestStore = (preloadedState = {}) => { + return configureStore({ + reducer: { + folders: foldersReducer, + }, + preloadedState, + }); +}; + const mockNavigate = jest.fn(); const mockNavigation = { navigate: mockNavigate }; const mockRoute: RouteProp = { @@ -40,7 +58,7 @@ const mockRoute: RouteProp = { params: { noteId: 1, isNew: false }, }; -describe('NoteDetailScreen', () => { +describe('NoteDetailsScreen', () => { const mockedDate = new Date('2024-10-13T11:34:00').getTime(); beforeEach(() => { @@ -66,17 +84,16 @@ describe('NoteDetailScreen', () => { isLoading: true, }); - const store = mockStore({ - folders: { - folders: [ - { - id: 1, - folderName: 'Test Folder', - notes: [], - }, - ], - }, + const store = createTestStore({ + folders: [ + { + id: 1, + folderName: 'Test Folder', + notes: [], + }, + ], }); + const { getByText } = render( @@ -96,23 +113,22 @@ describe('NoteDetailScreen', () => { data: { id: '1', title: 'Test Note', - modifiedDate: Date.now(), + modifiedDate: mockedDate, content: 'This is the content of the note', }, isLoading: false, }); - const store = mockStore({ - folders: { - folders: [ - { - id: 1, - folderName: 'Test Folder', - notes: [], - }, - ], - }, + const store = createTestStore({ + folders: [ + { + id: 1, + folderName: 'Test Folder', + notes: [], + }, + ], }); + const { getByDisplayValue } = render( { (useUpdateNoteMutation as jest.Mock).mockReturnValue([mockUpdateNote]); - const store = mockStore({ - folders: { - folders: [ - { - id: 1, - folderName: 'Test Folder', - notes: [], - }, - ], - }, + const store = createTestStore({ + folders: [ + { + id: 1, + folderName: 'Test Folder', + notes: [], + }, + ], }); const { getByDisplayValue, getByTestId } = render( @@ -188,16 +202,14 @@ describe('NoteDetailScreen', () => { isLoading: false, }); - const store = mockStore({ - folders: { - folders: [ - { - id: 1, - folderName: 'Test Folder', - notes: [], - }, - ], - }, + const store = createTestStore({ + folders: [ + { + id: 1, + folderName: 'Test Folder', + notes: [], + }, + ], }); const { getByText } = render( @@ -209,6 +221,6 @@ describe('NoteDetailScreen', () => { , ); - expect(getByText(formatTimestampToDateTime(mockedDate))).toBeTruthy(); + expect(getByText('Oct 13, 2024 at 11:34 AM')).toBeTruthy(); }); }); diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml index e189252..bbb130a 100755 --- a/android/app/src/main/AndroidManifest.xml +++ b/android/app/src/main/AndroidManifest.xml @@ -1,6 +1,9 @@ + + + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/ios/kiByte/Info.plist b/ios/kiByte/Info.plist index 99691b7..365fe88 100755 --- a/ios/kiByte/Info.plist +++ b/ios/kiByte/Info.plist @@ -34,6 +34,8 @@ NSLocationWhenInUseUsageDescription + NSMicrophoneUsageDescription + Give $(PRODUCT_NAME) permission to use your microphone. Your record wont be shared without your permission. UILaunchStoryboardName LaunchScreen UIRequiredDeviceCapabilities diff --git a/package.json b/package.json index 0bab830..1a49bdb 100755 --- a/package.json +++ b/package.json @@ -21,8 +21,10 @@ "eslint-plugin-prettier": "^5.2.1", "react": "18.3.1", "react-native": "0.75.3", + "react-native-audio-recorder-player": "^3.6.12", "react-native-gesture-handler": "^2.19.0", "react-native-paper": "^5.12.5", + "react-native-permissions": "^5.2.1", "react-native-safe-area-context": "^4.11.0", "react-native-safe-area-view": "^1.1.1", "react-native-screens": "^3.34.0", @@ -47,7 +49,6 @@ "@types/react": "^18.3.5", "@types/react-native-vector-icons": "^6.4.18", "@types/react-test-renderer": "^18.3.0", - "@types/redux-mock-store": "^1.5.0", "@typescript-eslint/eslint-plugin": "canary", "@typescript-eslint/parser": "canary", "babel-jest": "^29.7.0", @@ -60,7 +61,6 @@ "metro-react-native-babel-preset": "^0.77.0", "prettier": "^3.3.3", "react-test-renderer": "^18.3.1", - "redux-mock-store": "^1.5.5", "typescript": "^5.6.2", "typescript-eslint": "^8.14.0" }, diff --git a/src/screens/HomeScreen/components/FloatingButton/hooks/useAudioRecorder.ts b/src/screens/HomeScreen/components/FloatingButton/hooks/useAudioRecorder.ts new file mode 100644 index 0000000..8e4eefa --- /dev/null +++ b/src/screens/HomeScreen/components/FloatingButton/hooks/useAudioRecorder.ts @@ -0,0 +1,57 @@ +import { useEffect, useMemo, useState } from 'react'; +import AudioRecorderPlayer from 'react-native-audio-recorder-player'; + +import { usePermissions } from '@root/src/utils/hooks/usePermissions'; + +export const useAudioRecorder = () => { + const audioRecorderPlayer = useMemo(() => new AudioRecorderPlayer(), []); + const [isRecording, setIsRecording] = useState(false); + const [recordingResult, setRecordingResult] = useState(null); + const { requestAudioPermissions } = usePermissions(); + + useEffect(() => { + return () => { + audioRecorderPlayer.removeRecordBackListener(); + }; + }, [audioRecorderPlayer]); + + const startRecording = async () => { + try { + const hasPermissions = await requestAudioPermissions(); + if (!hasPermissions) { + console.log('Permissions not granted'); + return; + } + + setIsRecording(true); + const result = await audioRecorderPlayer.startRecorder(); + + audioRecorderPlayer.addRecordBackListener(e => { + console.log(e); + return; + }); + + console.log('🚀 ~ startRecording ~ result:', result); + } catch (error) { + console.error('Failed to start recording:', error); + setIsRecording(false); + } + }; + + const stopRecording = async () => { + if (!isRecording) return; + + try { + const result = await audioRecorderPlayer.stopRecorder(); + audioRecorderPlayer.removeRecordBackListener(); + + setIsRecording(false); + setRecordingResult(result); + console.log('🚀 ~ stopRecording ~ result:', result); + } catch (error) { + console.error('Failed to stop recording:', error); + } + }; + + return { startRecording, stopRecording, isRecording, recordingResult }; +}; diff --git a/src/screens/HomeScreen/components/FloatingButton/hooks/useFloatingButtonHandlers.ts b/src/screens/HomeScreen/components/FloatingButton/hooks/useFloatingButtonHandlers.ts index 15fa96b..7e6b410 100644 --- a/src/screens/HomeScreen/components/FloatingButton/hooks/useFloatingButtonHandlers.ts +++ b/src/screens/HomeScreen/components/FloatingButton/hooks/useFloatingButtonHandlers.ts @@ -1,5 +1,6 @@ import { useRef, useState } from 'react'; import { Animated } from 'react-native'; +import { useAudioRecorder } from './useAudioRecorder'; const pressAnimationDuration = 190; const releaseAnimationDuration = 150; @@ -13,6 +14,8 @@ export const useFloatingButtonHandlers = ({ onPress, onLongPress, }: UseFloatingButtonHandlersProps) => { + const { startRecording, stopRecording, isRecording } = useAudioRecorder(); + const [isLongPressed, setIsLongPressed] = useState(false); const bottomPositionAnim = useRef(new Animated.Value(25)).current; @@ -28,7 +31,7 @@ export const useFloatingButtonHandlers = ({ }).start(); }; - const handleLongPress = () => { + const handleLongPress = async () => { setIsLongPressed(true); Animated.parallel([ Animated.timing(bottomPositionAnim, { @@ -53,11 +56,14 @@ export const useFloatingButtonHandlers = ({ }), ]).start(); - onLongPress(); + if (onLongPress) onLongPress(); + await startRecording(); }; - const handlePressOut = () => { - if (!isLongPressed) onPress(); + const handlePressOut = async () => { + if (isLongPressed) { + await stopRecording(); + } else onPress(); Animated.parallel([ Animated.timing(bottomPositionAnim, { @@ -94,6 +100,7 @@ export const useFloatingButtonHandlers = ({ return { isLongPressed, + isRecording, animatedStyles, handlePressIn, handlePressOut, diff --git a/src/screens/HomeScreen/components/FloatingButton/styles.tsx b/src/screens/HomeScreen/components/FloatingButton/styles.tsx index 7ebbb2b..8fcd0fe 100644 --- a/src/screens/HomeScreen/components/FloatingButton/styles.tsx +++ b/src/screens/HomeScreen/components/FloatingButton/styles.tsx @@ -17,7 +17,7 @@ const PlusIcon = ({ color }: { color: string }) => ( x2="15" y2="25" stroke={color} - strokeWidth="3 " + strokeWidth="3" strokeLinecap="round" /> = ({ )} { const insets = useSafeAreaInsets(); - const folders = useSelector((state: RootState) => state.folders.folders); + const folders = useSelector((state: RootState) => state.folders); const navigation = useNavigation(); const [searchQuery, setSearchQuery] = useState(''); diff --git a/src/screens/NoteDetailsScreen/index.tsx b/src/screens/NoteDetailsScreen/index.tsx index ad5b1cc..b35fa52 100644 --- a/src/screens/NoteDetailsScreen/index.tsx +++ b/src/screens/NoteDetailsScreen/index.tsx @@ -33,7 +33,7 @@ type NoteDetailsScreenProps = StackScreenProps< const NoteDetailsScreen: React.FC = ({ route }) => { const { noteId = 0, isNew } = route.params; const insets = useSafeAreaInsets(); - const folders = useSelector((state: RootState) => state.folders.folders); + const folders = useSelector((state: RootState) => state.folders); const dispatch = useDispatch(); const { data: note, isLoading } = useFetchNoteDetailsQuery(noteId, { @@ -120,6 +120,7 @@ const NoteDetailsScreen: React.FC = ({ route }) => { } } }; + if (!isNew && isLoading) { return Loading...; } diff --git a/src/store/foldersSlice.ts b/src/store/foldersSlice.ts index 6fdb6c0..2c9d208 100644 --- a/src/store/foldersSlice.ts +++ b/src/store/foldersSlice.ts @@ -7,39 +7,30 @@ export interface Folder { notes: NoteListItem[]; } -interface FoldersState { - folders: Folder[]; -} +type FoldersState = Folder[]; -const initialState: FoldersState = { - folders: [], -}; +const initialState: FoldersState = []; const foldersSlice = createSlice({ name: 'folders', initialState, reducers: { - setFolders: (state, action: PayloadAction) => { - state.folders = action.payload; - }, + setFolders: (state, action: PayloadAction) => action.payload, addFolder: (state, action: PayloadAction) => { - state.folders.push(action.payload); + state.push(action.payload); }, addNoteToFolder: ( state, action: PayloadAction<{ folderId: number; note: NoteListItem }>, ) => { const { folderId, note } = action.payload; - const folder = state.folders.find(f => f.id === folderId); + const folder = state.find(f => f.id === folderId); if (folder) { folder.notes.push(note); } }, - removeFolder: (state, action: PayloadAction) => { - state.folders = state.folders.filter( - folder => folder.id !== action.payload, - ); - }, + removeFolder: (state, action: PayloadAction) => + state.filter(folder => folder.id !== action.payload), }, }); diff --git a/src/store/queries/notes/index.ts b/src/store/queries/notes/index.ts index defd9e9..16d2ab2 100644 --- a/src/store/queries/notes/index.ts +++ b/src/store/queries/notes/index.ts @@ -34,11 +34,9 @@ export const noteApi = createApi({ const newId = notesMockData.length ? Math.max(...notesMockData.map(n => n.id)) + 1 : 1; - const createdNote = { ...newNote, id: newId, modifiedDate: Date.now() }; - - notesMockData.push(createdNote); - - return { data: createdNote }; + const note = { ...newNote, id: newId, modifiedDate: Date.now() }; + notesMockData.push(note); + return { data: note }; }, }), updateNote: builder.mutation & { id?: number }>({ diff --git a/src/utils/hooks/usePermissions.ts b/src/utils/hooks/usePermissions.ts new file mode 100644 index 0000000..2f7feb5 --- /dev/null +++ b/src/utils/hooks/usePermissions.ts @@ -0,0 +1,33 @@ +import { PermissionsAndroid, Platform } from 'react-native'; + +export const usePermissions = () => { + const requestAudioPermissions = async (): Promise => { + if (Platform.OS === 'android') { + try { + const grants = await PermissionsAndroid.requestMultiple([ + PermissionsAndroid.PERMISSIONS.WRITE_EXTERNAL_STORAGE, + PermissionsAndroid.PERMISSIONS.READ_EXTERNAL_STORAGE, + PermissionsAndroid.PERMISSIONS.RECORD_AUDIO, + ]); + + console.log('Permissions status:', grants); + + return ( + grants['android.permission.WRITE_EXTERNAL_STORAGE'] === + PermissionsAndroid.RESULTS.GRANTED && + grants['android.permission.READ_EXTERNAL_STORAGE'] === + PermissionsAndroid.RESULTS.GRANTED && + grants['android.permission.RECORD_AUDIO'] === + PermissionsAndroid.RESULTS.GRANTED + ); + } catch (err) { + console.warn('Permission request failed:', err); + return false; + } + } + + return true; + }; + + return { requestAudioPermissions }; +}; diff --git a/src/utils/index.ts b/src/utils/index.ts index dc31a10..bd2fc55 100644 --- a/src/utils/index.ts +++ b/src/utils/index.ts @@ -1,27 +1 @@ -function formatTimestampToDate(timestamp: number): string { - const date = new Date(timestamp); - return new Intl.DateTimeFormat('en-US', { - month: 'short', // "Jan" - day: 'numeric', // "6" - year: 'numeric', // "2023" - }).format(date); -} - -function formatTimestampToDateTime(timestamp: number): string { - const date = new Date(timestamp); - const formattedDate = new Intl.DateTimeFormat('en-US', { - month: 'short', // "Jan" - day: 'numeric', // "6" - year: 'numeric', // "2023" - }).format(date); - - const formattedTime = new Intl.DateTimeFormat('en-US', { - hour: 'numeric', // "6" - minute: 'numeric', // "49" - hour12: true, // "AM/PM" - }).format(date); - - return `${formattedDate} at ${formattedTime}`; -} - -export { formatTimestampToDate, formatTimestampToDateTime }; +export { formatTimestampToDate, formatTimestampToDateTime } from './time'; diff --git a/src/utils/time.ts b/src/utils/time.ts new file mode 100644 index 0000000..dc31a10 --- /dev/null +++ b/src/utils/time.ts @@ -0,0 +1,27 @@ +function formatTimestampToDate(timestamp: number): string { + const date = new Date(timestamp); + return new Intl.DateTimeFormat('en-US', { + month: 'short', // "Jan" + day: 'numeric', // "6" + year: 'numeric', // "2023" + }).format(date); +} + +function formatTimestampToDateTime(timestamp: number): string { + const date = new Date(timestamp); + const formattedDate = new Intl.DateTimeFormat('en-US', { + month: 'short', // "Jan" + day: 'numeric', // "6" + year: 'numeric', // "2023" + }).format(date); + + const formattedTime = new Intl.DateTimeFormat('en-US', { + hour: 'numeric', // "6" + minute: 'numeric', // "49" + hour12: true, // "AM/PM" + }).format(date); + + return `${formattedDate} at ${formattedTime}`; +} + +export { formatTimestampToDate, formatTimestampToDateTime }; diff --git a/yarn.lock b/yarn.lock index e00b1e7..149bfb8 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1636,15 +1636,6 @@ __metadata: languageName: node linkType: hard -"@babel/runtime@npm:^7.9.2": - version: 7.26.0 - resolution: "@babel/runtime@npm:7.26.0" - dependencies: - regenerator-runtime: ^0.14.0 - checksum: c8e2c0504ab271b3467a261a8f119bf2603eb857a0d71e37791f4e3fae00f681365073cc79f141ddaa90c6077c60ba56448004ad5429d07ac73532be9f7cf28a - languageName: node - linkType: hard - "@babel/template@npm:^7.0.0, @babel/template@npm:^7.24.7, @babel/template@npm:^7.25.0, @babel/template@npm:^7.3.3": version: 7.25.0 resolution: "@babel/template@npm:7.25.0" @@ -3009,15 +3000,6 @@ __metadata: languageName: node linkType: hard -"@types/redux-mock-store@npm:^1.5.0": - version: 1.5.0 - resolution: "@types/redux-mock-store@npm:1.5.0" - dependencies: - redux: ^4.0.5 - checksum: 82248c276fed1303c4c85b38cdb9604b6ea1118b5c9a6968e84dd9820ea8c08dd1d8d2fcbf940ac2f6b379d88d87aaa05d2787e6841b65ccfee09cdf23b4c32b - languageName: node - linkType: hard - "@types/semver@npm:^7.3.12": version: 7.5.8 resolution: "@types/semver@npm:7.5.8" @@ -6228,6 +6210,15 @@ __metadata: languageName: node linkType: hard +"hyochan-welcome@npm:^1.0.0": + version: 1.0.1 + resolution: "hyochan-welcome@npm:1.0.1" + bin: + hyochan-welcome: bin/hello.js + checksum: 2ea7093b54eefcde4c65e4a8ffd3f3926a55ff0bce20a3f5142fe1108e76c0dfcb98517b47714c536f9de8302b92ae02bf240891d3114d527ccd497e2ea9478e + languageName: node + linkType: hard + "iconv-lite@npm:^0.6.2": version: 0.6.3 resolution: "iconv-lite@npm:0.6.3" @@ -7487,7 +7478,6 @@ __metadata: "@types/react": ^18.3.5 "@types/react-native-vector-icons": ^6.4.18 "@types/react-test-renderer": ^18.3.0 - "@types/redux-mock-store": ^1.5.0 "@types/styled-components": ^5.1.34 "@typescript-eslint/eslint-plugin": canary "@typescript-eslint/parser": canary @@ -7504,8 +7494,10 @@ __metadata: prettier: ^3.3.3 react: 18.3.1 react-native: 0.75.3 + react-native-audio-recorder-player: ^3.6.12 react-native-gesture-handler: ^2.19.0 react-native-paper: ^5.12.5 + react-native-permissions: ^5.2.1 react-native-safe-area-context: ^4.11.0 react-native-safe-area-view: ^1.1.1 react-native-screens: ^3.34.0 @@ -7514,7 +7506,6 @@ __metadata: react-native-vector-icons: ^10.2.0 react-redux: ^9.1.2 react-test-renderer: ^18.3.1 - redux-mock-store: ^1.5.5 styled-components: ^6.1.13 typescript: ^5.6.2 typescript-eslint: ^8.14.0 @@ -7604,13 +7595,6 @@ __metadata: languageName: node linkType: hard -"lodash.isplainobject@npm:^4.0.6": - version: 4.0.6 - resolution: "lodash.isplainobject@npm:4.0.6" - checksum: 29c6351f281e0d9a1d58f1a4c8f4400924b4c79f18dfc4613624d7d54784df07efaff97c1ff2659f3e085ecf4fff493300adc4837553104cef2634110b0d5337 - languageName: node - linkType: hard - "lodash.merge@npm:^4.6.2": version: 4.6.2 resolution: "lodash.merge@npm:4.6.2" @@ -9035,6 +9019,18 @@ __metadata: languageName: node linkType: hard +"react-native-audio-recorder-player@npm:^3.6.12": + version: 3.6.12 + resolution: "react-native-audio-recorder-player@npm:3.6.12" + dependencies: + hyochan-welcome: ^1.0.0 + peerDependencies: + react: "*" + react-native: "*" + checksum: a0c74694fe54eacd8f705b38d77d8ee35ddb9e964243ac0a727483d001e7ad24178b5ed3edcb0b4ddccf87d65af3233a823d5dd73fc98099ddbe0602134586e7 + languageName: node + linkType: hard + "react-native-gesture-handler@npm:^2.19.0": version: 2.19.0 resolution: "react-native-gesture-handler@npm:2.19.0" @@ -9066,6 +9062,20 @@ __metadata: languageName: node linkType: hard +"react-native-permissions@npm:^5.2.1": + version: 5.2.1 + resolution: "react-native-permissions@npm:5.2.1" + peerDependencies: + react: ">=18.1.0" + react-native: ">=0.70.0" + react-native-windows: ">=0.70.0" + peerDependenciesMeta: + react-native-windows: + optional: true + checksum: a57b0a6c71c38bb126e48e76b8eef7e62bee8083d748ec1cf7e7b5699d46867b10e96a02837c45a045b77dab37e45a5c20b1deb3637ad5cee963c9e92c6ee05a + languageName: node + linkType: hard + "react-native-safe-area-context@npm:^4.11.0": version: 4.11.0 resolution: "react-native-safe-area-context@npm:4.11.0" @@ -9318,17 +9328,6 @@ __metadata: languageName: node linkType: hard -"redux-mock-store@npm:^1.5.5": - version: 1.5.5 - resolution: "redux-mock-store@npm:1.5.5" - dependencies: - lodash.isplainobject: ^4.0.6 - peerDependencies: - redux: "*" - checksum: a6e1dda7abc96066344b7f9fa8726c40dff6ea31b8393ead26c0525663528af257bfef85c38af8beb4e549c189bd90fde5dcb5729b16057de7fffc0c43881ca6 - languageName: node - linkType: hard - "redux-thunk@npm:^3.1.0": version: 3.1.0 resolution: "redux-thunk@npm:3.1.0" @@ -9338,15 +9337,6 @@ __metadata: languageName: node linkType: hard -"redux@npm:^4.0.5": - version: 4.2.1 - resolution: "redux@npm:4.2.1" - dependencies: - "@babel/runtime": ^7.9.2 - checksum: f63b9060c3a1d930ae775252bb6e579b42415aee7a23c4114e21a0b4ba7ec12f0ec76936c00f546893f06e139819f0e2855e0d55ebfce34ca9c026241a6950dd - languageName: node - linkType: hard - "redux@npm:^5.0.1": version: 5.0.1 resolution: "redux@npm:5.0.1"