diff --git a/apps/bare-expo/android/app/src/main/AndroidManifest.xml b/apps/bare-expo/android/app/src/main/AndroidManifest.xml index b3eec55281562a..2751b41bb5119c 100644 --- a/apps/bare-expo/android/app/src/main/AndroidManifest.xml +++ b/apps/bare-expo/android/app/src/main/AndroidManifest.xml @@ -2,6 +2,7 @@ + @@ -12,6 +13,10 @@ + + + + diff --git a/apps/bare-expo/app.json b/apps/bare-expo/app.json index 12705787dd38db..d88d07d063ef28 100644 --- a/apps/bare-expo/app.json +++ b/apps/bare-expo/app.json @@ -82,6 +82,14 @@ "sounds": ["./assets/notification.wav"] } ], + [ + "expo-media-library", + { + "photosPermission": "Allow $(PRODUCT_NAME) to access your photos", + "savePhotosPermission": "Allow $(PRODUCT_NAME) to save photos", + "isAccessMediaLocationEnabled": true + } + ], [ "expo-tracking-transparency", { diff --git a/apps/bare-expo/ios/Podfile.lock b/apps/bare-expo/ios/Podfile.lock index dd5b21ac9ab29e..df0eedd85dcffd 100644 --- a/apps/bare-expo/ios/Podfile.lock +++ b/apps/bare-expo/ios/Podfile.lock @@ -2231,7 +2231,7 @@ PODS: - ReactCommon/turbomodule/core - ReactNativeDependencies - Yoga - - react-native-keyboard-controller (1.21.9): + - react-native-keyboard-controller (1.22.4): - hermes-engine - RCTRequired - RCTTypeSafety @@ -2243,7 +2243,7 @@ PODS: - React-graphics - React-ImageManager - React-jsi - - react-native-keyboard-controller/common (= 1.21.9) + - react-native-keyboard-controller/common (= 1.22.4) - React-NativeModulesApple - React-RCTFabric - React-renderercss @@ -2254,7 +2254,7 @@ PODS: - ReactCommon/turbomodule/core - ReactNativeDependencies - Yoga - - react-native-keyboard-controller/common (1.21.9): + - react-native-keyboard-controller/common (1.22.4): - hermes-engine - RCTRequired - RCTTypeSafety @@ -3487,7 +3487,7 @@ DEPENDENCIES: - "React-Mapbuffer (from `../../../node_modules/.pnpm/react-native@0.86.0_@babel+core@7.29.7_@react-native+jest-preset@0.86.0_@babel+core@7.2_05fe3c1b42d0a26b4dba0087fc75e51f/node_modules/react-native/ReactCommon`)" - "React-microtasksnativemodule (from `../../../node_modules/.pnpm/react-native@0.86.0_@babel+core@7.29.7_@react-native+jest-preset@0.86.0_@babel+core@7.2_05fe3c1b42d0a26b4dba0087fc75e51f/node_modules/react-native/ReactCommon/react/nativemodule/microtasks`)" - "React-mutationobservernativemodule (from `../../../node_modules/.pnpm/react-native@0.86.0_@babel+core@7.29.7_@react-native+jest-preset@0.86.0_@babel+core@7.2_05fe3c1b42d0a26b4dba0087fc75e51f/node_modules/react-native/ReactCommon/react/nativemodule/mutationobserver`)" - - "react-native-keyboard-controller (from `../../../node_modules/.pnpm/react-native-keyboard-controller@1.21.9_react-native-reanimated@4.5.1_patch_hash=f1adeb_9a349fcb8493a723edbedfbafcf73f19/node_modules/react-native-keyboard-controller`)" + - "react-native-keyboard-controller (from `../../../node_modules/.pnpm/react-native-keyboard-controller@1.22.4_react-native-reanimated@4.5.1_patch_hash=f1adeb_5fc0483b2b4f942879a1a7d7c7eb1c5a/node_modules/react-native-keyboard-controller`)" - "react-native-netinfo (from `../../../node_modules/.pnpm/@react-native-community+netinfo@12.0.1_patch_hash=ced0cb79848978ecc3e780a4d812d94868434_66433ebdfb3ea787f7fc42b0ed8ee206/node_modules/@react-native-community/netinfo`)" - "react-native-pager-view (from `../../../node_modules/.pnpm/react-native-pager-view@8.0.2_react-native@0.86.0_@babel+core@7.29.7_@react-native+jest_050fee3d9ae7682571a8180ab6e58f1e/node_modules/react-native-pager-view`)" - "react-native-safe-area-context (from `../../../node_modules/.pnpm/react-native-safe-area-context@5.7.0_react-native@0.86.0_@babel+core@7.29.7_@react-nati_c28ea83fe184def3347ae322f545fd20/node_modules/react-native-safe-area-context`)" @@ -3904,7 +3904,7 @@ EXTERNAL SOURCES: React-mutationobservernativemodule: :path: "../../../node_modules/.pnpm/react-native@0.86.0_@babel+core@7.29.7_@react-native+jest-preset@0.86.0_@babel+core@7.2_05fe3c1b42d0a26b4dba0087fc75e51f/node_modules/react-native/ReactCommon/react/nativemodule/mutationobserver" react-native-keyboard-controller: - :path: "../../../node_modules/.pnpm/react-native-keyboard-controller@1.21.9_react-native-reanimated@4.5.1_patch_hash=f1adeb_9a349fcb8493a723edbedfbafcf73f19/node_modules/react-native-keyboard-controller" + :path: "../../../node_modules/.pnpm/react-native-keyboard-controller@1.22.4_react-native-reanimated@4.5.1_patch_hash=f1adeb_5fc0483b2b4f942879a1a7d7c7eb1c5a/node_modules/react-native-keyboard-controller" react-native-netinfo: :path: "../../../node_modules/.pnpm/@react-native-community+netinfo@12.0.1_patch_hash=ced0cb79848978ecc3e780a4d812d94868434_66433ebdfb3ea787f7fc42b0ed8ee206/node_modules/@react-native-community/netinfo" react-native-pager-view: @@ -4156,7 +4156,7 @@ SPEC CHECKSUMS: React-Mapbuffer: ce449ccdf3b80384415b925606be8a4bdcfc65d3 React-microtasksnativemodule: 2eb3f49d0d8e77b5343455eccd057010b8d38b6b React-mutationobservernativemodule: f0a0d5ae9b51caf7becbeabf836d716cfedb6bf2 - react-native-keyboard-controller: d4796248609d4c03cd6b5f73a9fd083d7c5f33d6 + react-native-keyboard-controller: d9d240e9c2cfddc41c6de0f70b881cc82eeaadd6 react-native-netinfo: 2f906d5d9a639090ed1c079135d6485a95f749f1 react-native-pager-view: c9f0c94cc64041ac70f28f79527ea2ac664cf25c react-native-safe-area-context: 6b4966397ada0f7dd481e4486a2ef936834861a1 diff --git a/apps/bare-expo/package.json b/apps/bare-expo/package.json index 939310d494761b..31d37dfa37e0d6 100644 --- a/apps/bare-expo/package.json +++ b/apps/bare-expo/package.json @@ -45,6 +45,7 @@ "expo-insights": "workspace:*", "expo-linking": "workspace:*", "expo-location": "workspace:*", + "expo-media-library": "workspace:*", "expo-navigation-bar": "workspace:*", "expo-network-addons": "workspace:*", "expo-notifications": "workspace:*", @@ -62,7 +63,7 @@ "react-dom": "19.2.3", "react-native": "0.86.0", "react-native-gesture-handler": "~3.1.0", - "react-native-keyboard-controller": "^1.21.9", + "react-native-keyboard-controller": "^1.22.4", "react-native-pager-view": "8.0.2", "react-native-reanimated": "4.5.1", "react-native-safe-area-context": "5.7.0", diff --git a/apps/expo-go/ios/Podfile.lock b/apps/expo-go/ios/Podfile.lock index 06073a60e914c7..1e5face53b384e 100644 --- a/apps/expo-go/ios/Podfile.lock +++ b/apps/expo-go/ios/Podfile.lock @@ -2379,7 +2379,7 @@ PODS: - ReactCommon/turbomodule/core - SocketRocket - Yoga - - react-native-keyboard-controller (1.21.9): + - react-native-keyboard-controller (1.22.4): - boost - DoubleConversion - fast_float @@ -2397,7 +2397,7 @@ PODS: - React-graphics - React-ImageManager - React-jsi - - react-native-keyboard-controller/common (= 1.21.9) + - react-native-keyboard-controller/common (= 1.22.4) - React-NativeModulesApple - React-RCTFabric - React-renderercss @@ -2408,7 +2408,7 @@ PODS: - ReactCommon/turbomodule/core - SocketRocket - Yoga - - react-native-keyboard-controller/common (1.21.9): + - react-native-keyboard-controller/common (1.22.4): - boost - DoubleConversion - fast_float @@ -4106,7 +4106,7 @@ DEPENDENCIES: - React-Mapbuffer (from `../../../react-native-lab/react-native/packages/react-native/ReactCommon`) - React-microtasksnativemodule (from `../../../react-native-lab/react-native/packages/react-native/ReactCommon/react/nativemodule/microtasks`) - React-mutationobservernativemodule (from `../../../react-native-lab/react-native/packages/react-native/ReactCommon/react/nativemodule/mutationobserver`) - - "react-native-keyboard-controller (from `../../../node_modules/.pnpm/react-native-keyboard-controller@1.21.9_react-native-reanimated@4.5.1_patch_hash=f1adeb_9a349fcb8493a723edbedfbafcf73f19/node_modules/react-native-keyboard-controller`)" + - "react-native-keyboard-controller (from `../../../node_modules/.pnpm/react-native-keyboard-controller@1.22.4_react-native-reanimated@4.5.1_patch_hash=f1adeb_5fc0483b2b4f942879a1a7d7c7eb1c5a/node_modules/react-native-keyboard-controller`)" - "react-native-maps/Maps (from `../../../node_modules/.pnpm/react-native-maps@1.27.2_react-native-web@0.21.2_encoding@0.1.13_react-dom@19.2.3_react_07670b4144253b002775c6adf1c85a39/node_modules/react-native-maps`)" - "react-native-netinfo (from `../../../node_modules/.pnpm/@react-native-community+netinfo@12.0.1_patch_hash=ced0cb79848978ecc3e780a4d812d94868434_66433ebdfb3ea787f7fc42b0ed8ee206/node_modules/@react-native-community/netinfo`)" - "react-native-pager-view (from `../../../node_modules/.pnpm/react-native-pager-view@8.0.2_react-native@0.86.0_@babel+core@7.29.7_@react-native+jest_050fee3d9ae7682571a8180ab6e58f1e/node_modules/react-native-pager-view`)" @@ -4435,7 +4435,7 @@ EXTERNAL SOURCES: React-mutationobservernativemodule: :path: "../../../react-native-lab/react-native/packages/react-native/ReactCommon/react/nativemodule/mutationobserver" react-native-keyboard-controller: - :path: "../../../node_modules/.pnpm/react-native-keyboard-controller@1.21.9_react-native-reanimated@4.5.1_patch_hash=f1adeb_9a349fcb8493a723edbedfbafcf73f19/node_modules/react-native-keyboard-controller" + :path: "../../../node_modules/.pnpm/react-native-keyboard-controller@1.22.4_react-native-reanimated@4.5.1_patch_hash=f1adeb_5fc0483b2b4f942879a1a7d7c7eb1c5a/node_modules/react-native-keyboard-controller" react-native-maps: :path: "../../../node_modules/.pnpm/react-native-maps@1.27.2_react-native-web@0.21.2_encoding@0.1.13_react-dom@19.2.3_react_07670b4144253b002775c6adf1c85a39/node_modules/react-native-maps" react-native-netinfo: @@ -4683,7 +4683,7 @@ SPEC CHECKSUMS: React-Mapbuffer: 187c03c70cba972bba8786b581897d8e1ae6bdc3 React-microtasksnativemodule: e130d410da41cad9b9776c492e6f8c07ad7b34df React-mutationobservernativemodule: 7f5f4da31e906da2100a432672c55cb2096570e1 - react-native-keyboard-controller: 93e24ebbe21551622cbb0e9c39664dc5539f943c + react-native-keyboard-controller: 1d5ba749e13f7392286934769bfad3ce98e4f519 react-native-maps: b5af575a371e57847c6d8490e726c08036f7c80f react-native-netinfo: a0be8c77f8420a60ddf07eb87be1a36f02a1bd65 react-native-pager-view: 2694b7efa49588b1cc3aecb5f513871135f499cc diff --git a/apps/expo-go/package.json b/apps/expo-go/package.json index de6e12813776ec..16cacd939a4411 100644 --- a/apps/expo-go/package.json +++ b/apps/expo-go/package.json @@ -84,7 +84,7 @@ "react": "19.2.3", "react-native": "0.86.0", "react-native-gesture-handler": "~3.1.0", - "react-native-keyboard-controller": "^1.21.9", + "react-native-keyboard-controller": "^1.22.4", "react-native-maps": "1.27.2", "react-native-pager-view": "8.0.2", "react-native-reanimated": "4.5.1", diff --git a/apps/native-component-list/package.json b/apps/native-component-list/package.json index 5c093e861bbd18..d46df30d64f3ac 100644 --- a/apps/native-component-list/package.json +++ b/apps/native-component-list/package.json @@ -129,7 +129,7 @@ "react-native": "0.86.0", "react-native-dropdown-picker": "^5.3.0", "react-native-gesture-handler": "~3.1.0", - "react-native-keyboard-controller": "^1.21.9", + "react-native-keyboard-controller": "^1.22.4", "react-native-maps": "1.27.2", "react-native-pager-view": "8.0.2", "react-native-reanimated": "4.5.1", diff --git a/apps/native-component-list/src/screens/MediaLibrary/MediaLibraryScreen.tsx b/apps/native-component-list/src/screens/MediaLibrary/MediaLibraryScreen.tsx index 908ff8c686b871..2e5783699b96f8 100644 --- a/apps/native-component-list/src/screens/MediaLibrary/MediaLibraryScreen.tsx +++ b/apps/native-component-list/src/screens/MediaLibrary/MediaLibraryScreen.tsx @@ -1,3 +1,4 @@ +import SegmentedControl from '@react-native-segmented-control/segmented-control'; import * as FileSystem from 'expo-file-system/legacy'; import * as MediaLibrary from 'expo-media-library/legacy'; import { router, useFocusEffect, type NativeStackScreenProps } from 'expo-router'; @@ -19,9 +20,8 @@ import Button from '../../components/Button'; import HeadingText from '../../components/HeadingText'; import Colors from '../../constants/Colors'; import MediaLibraryCell from './MediaLibraryCell'; - const COLUMNS = 3; -const PAGE_SIZE = COLUMNS * 10; +const PAGE_SIZE_OPTIONS = [30, 300, 3000, 30000] as const; const WINDOW_SIZE = Dimensions.get('window'); type MediaTypeWithoutPairedVideo = Exclude; @@ -60,6 +60,10 @@ type FetchState = { assets: MediaLibrary.Asset[]; endCursor: string | null; hasNextPage: boolean; + /** First loaded asset location from getAssetsAsync (iOS batch location debug). */ + batchLocationPreview: string | null; + /** Duration of the most recent getAssetsAsync call, in milliseconds. */ + lastFetchDurationMs: number | null; }; const initialState: FetchState = { @@ -68,6 +72,8 @@ const initialState: FetchState = { assets: [], endCursor: null, hasNextPage: true, + batchLocationPreview: null, + lastFetchDurationMs: null, }; function reducer( @@ -178,7 +184,7 @@ export default function MediaLibraryScreen({ navigation, route }: Props) { } // The fetching and sorting logic is split out from the navigation and permission logic for simplicity. -function MediaLibraryView({ navigation, route, accessPrivileges }: Props) { +function MediaLibraryView({ route, accessPrivileges }: Props) { const albumId = route.params?.albumId; const albumTitle = route.params?.albumTitle; @@ -188,13 +194,15 @@ function MediaLibraryView({ navigation, route, accessPrivileges }: Props) { const [mediaType, setMediaType] = React.useState( MediaLibrary.MediaType.photo ); + const [pageSize, setPageSize] = React.useState(PAGE_SIZE_OPTIONS[0]); + const [resolveWithFullInfo, setResolveWithFullInfo] = React.useState(false); const [state, dispatch] = React.useReducer(reducer, initialState); // Update without showing the refresh indicator whenever the sorting parameters change. React.useEffect(() => { dispatch({ type: 'reset', refreshing: false }); - }, [mediaType, sortBy, albumId]); + }, [mediaType, sortBy, albumId, pageSize, resolveWithFullInfo]); const toggleMediaType = React.useCallback(() => { setMediaType(mediaTypeStates[mediaType]); @@ -204,6 +212,10 @@ function MediaLibraryView({ navigation, route, accessPrivileges }: Props) { setSortBy(sortByStates[sortBy]); }, [setSortBy, sortBy]); + const toggleResolveWithFullInfo = React.useCallback(() => { + setResolveWithFullInfo((current) => !current); + }, []); + const loadMoreAssets = React.useCallback(async () => { if ( // if a fetch operation is still in progress or there are no more assets then bail out. @@ -217,14 +229,27 @@ function MediaLibraryView({ navigation, route, accessPrivileges }: Props) { try { // Make a native request for assets. + if (!state.endCursor) { + dispatch({ type: 'update', refreshing: true }); + } + const fetchStartedAt = performance.now(); const { assets, endCursor, hasNextPage } = await MediaLibrary.getAssetsAsync({ - first: PAGE_SIZE, + first: pageSize, after: state.endCursor ?? undefined, mediaType, mediaSubtypes: [], sortBy, album: albumId, + resolveWithFullInfo, }); + const lastFetchDurationMs = Math.round(performance.now() - fetchStartedAt); + const firstBatchAssetLocation = assets[0]?.location; + const batchLocationPreview = + state.endCursor == null + ? firstBatchAssetLocation != null + ? `${firstBatchAssetLocation.latitude}, ${firstBatchAssetLocation.longitude}` + : null + : state.batchLocationPreview; // Get the last asset currently in the state. const lastAsset = state.assets[state.assets.length - 1]; @@ -239,13 +264,25 @@ function MediaLibraryView({ navigation, route, accessPrivileges }: Props) { assets: ([] as MediaLibrary.Asset[]).concat(state.assets, assets), endCursor, hasNextPage, + lastFetchDurationMs, + batchLocationPreview, }); } } finally { // Toggle this back to false in a finally to ensure we can reload later, even if an error ocurred. isLoadingAssets.current = false; } - }, [state.endCursor, state.hasNextPage, state.assets, mediaType, sortBy, albumId]); + }, [ + state.endCursor, + state.hasNextPage, + state.assets, + state.batchLocationPreview, + mediaType, + sortBy, + albumId, + pageSize, + resolveWithFullInfo, + ]); // Fetch data whenever the state.fetching value is true. React.useEffect(() => { @@ -320,9 +357,18 @@ function MediaLibraryView({ navigation, route, accessPrivileges }: Props) { onPress={toggleMediaType} />