fix: don't ask for PHOTO LIBRARY permission if user can't change their mind (#2020)

* stop asking for PHOTO LIBRARY permission before import, b/c it's not
  necessary for importing
* use a forked version of react-native-cameraroll that allows us to write
  location metadata to newly-created photos, which was the really important
  thing we were using the PHOTO LIBRARY permission for

Closes #1612
This commit is contained in:
Ken-ichi
2024-08-30 09:55:45 -07:00
committed by GitHub
parent 71629ef8aa
commit f4c7ee3f86
6 changed files with 43 additions and 61 deletions

View File

@@ -899,7 +899,7 @@ PODS:
- React-Mapbuffer (0.73.7):
- glog
- React-debug
- react-native-cameraroll (7.5.2):
- react-native-cameraroll (7.8.3):
- glog
- RCT-Folly (= 2022.05.16.00)
- React-Core
@@ -1505,7 +1505,7 @@ SPEC CHECKSUMS:
React-jsinspector: f356e49aa086380d3a4892708ca173ad31ac69c1
React-logger: 7b19bdfb254772a0332d6cd4d66eceb0678b6730
React-Mapbuffer: 6f392912435adb8fbf4c3eee0e79a0a0b4e4b717
react-native-cameraroll: af8eec1e585d053ff485d98ec837f9a8a11b5745
react-native-cameraroll: 0fe31282894817a82b5991dd0d78dc04c5a6ed35
react-native-config: 86038147314e2e6d10ea9972022aa171e6b1d4d8
react-native-exif-reader: fe4678df00e36e1aba6ade9013fd1d35c78c8381
react-native-geocoder-reborn: c31cbc630d9307ebbceea1dea2746d0054be35c4

8
package-lock.json generated
View File

@@ -13,7 +13,7 @@
"@candlefinance/faster-image": "^1.4.3",
"@formidable-webview/webshell": "^2.6.0",
"@gorhom/bottom-sheet": "^4.6.1",
"@react-native-camera-roll/camera-roll": "^7.5.2",
"@react-native-camera-roll/camera-roll": "github:inaturalist/react-native-cameraroll",
"@react-native-clipboard/clipboard": "^1.13.2",
"@react-native-community/datetimepicker": "^7.6.3",
"@react-native-community/geolocation": "^3.2.1",
@@ -3834,9 +3834,9 @@
}
},
"node_modules/@react-native-camera-roll/camera-roll": {
"version": "7.5.2",
"resolved": "https://registry.npmjs.org/@react-native-camera-roll/camera-roll/-/camera-roll-7.5.2.tgz",
"integrity": "sha512-XiVIrW17EFXrFzqB48q6cQOaYeVnw0iC3tH+Jhl+MAHDYGLJp+ulzxCNNwngaMvnVAA5Q2mUMzRocUiJPy8q0g==",
"version": "7.8.3",
"resolved": "git+ssh://git@github.com/inaturalist/react-native-cameraroll.git#c49afe441e1465f2eb1c25c3a4654f3c4ee5457c",
"license": "MIT",
"engines": {
"node": ">= 18.17.0"
},

View File

@@ -42,7 +42,7 @@
"@candlefinance/faster-image": "^1.4.3",
"@formidable-webview/webshell": "^2.6.0",
"@gorhom/bottom-sheet": "^4.6.1",
"@react-native-camera-roll/camera-roll": "^7.5.2",
"@react-native-camera-roll/camera-roll": "github:inaturalist/react-native-cameraroll",
"@react-native-clipboard/clipboard": "^1.13.2",
"@react-native-community/datetimepicker": "^7.6.3",
"@react-native-community/geolocation": "^3.2.1",

View File

@@ -1,31 +0,0 @@
diff --git a/node_modules/@react-native-camera-roll/camera-roll/ios/RNCCameraRoll.mm b/node_modules/@react-native-camera-roll/camera-roll/ios/RNCCameraRoll.mm
index b8f2aa2..aa0df68 100644
--- a/node_modules/@react-native-camera-roll/camera-roll/ios/RNCCameraRoll.mm
+++ b/node_modules/@react-native-camera-roll/camera-roll/ios/RNCCameraRoll.mm
@@ -207,6 +207,26 @@ RCT_EXPORT_METHOD(saveToCameraRoll:(NSURLRequest *)request
}
} completionHandler:^(BOOL success, NSError *error) {
if (success) {
+ // If the write succeeded but we don't have readwrite permission, that
+ // means we have addonly permission and we cannot read the file we
+ // just created to construct a response
+ if (@available(iOS 14, *)) {
+ PHAuthorizationStatus readWriteAuthStatus = [PHPhotoLibrary authorizationStatusForAccessLevel:PHAccessLevelReadWrite];
+ if (readWriteAuthStatus != PHAuthorizationStatusAuthorized) {
+ NSDictionary *addOnlyResponse = @{
+ @"node": @{
+ @"id": placeholder.localIdentifier,
+ @"type": options[@"type"],
+ @"image": @{
+ @"uri": @"placeholder/readWritePermissionNotGranted"
+ }
+ }
+ };
+ resolve(addOnlyResponse);
+ return;
+ }
+ }
+
PHFetchOptions *options = [PHFetchOptions new];
options.includeHiddenAssets = YES;
options.includeAllBurstAssets = YES;

View File

@@ -35,7 +35,8 @@ type Options = {
// $FlowIgnore
export async function savePhotosToCameraGallery(
uris: [string],
onEachSuccess: Function
onEachSuccess: Function,
location: Object
) {
const readWritePermissionResult = permissionResultFromMultiple(
await checkMultiple( READ_WRITE_MEDIA_PERMISSIONS )
@@ -43,20 +44,22 @@ export async function savePhotosToCameraGallery(
uris.reduce(
async ( memo, uri ) => {
try {
let savedPhotoUri;
const saveOptions = {};
// One quirk of CameraRoll is that if you want to write to an album, you
// need readwrite permission, but we don't want to ask for that here
// b/c it might come immediately after asking for *add only*
// permission, so we're checking to see if we have that permission
// and skipping the album if we don't
if ( readWritePermissionResult === RESULTS.GRANTED ) {
savedPhotoUri = await CameraRoll.save( uri, {
type: "photo",
album: "iNaturalist Next"
} );
} else {
savedPhotoUri = await CameraRoll.save( uri );
saveOptions.type = "photo";
saveOptions.album = "iNaturalist Next";
}
if ( location ) {
saveOptions.latitude = location.latitude;
saveOptions.longitude = location.longitude;
saveOptions.horizontalAccuracy = location.positional_accuracy;
}
const savedPhotoUri = await CameraRoll.save( uri, saveOptions );
// Save these camera roll URIs, so later on observation editor can update
// the EXIF metadata of these photos, once we retrieve a location.
@@ -122,7 +125,7 @@ const usePrepareStoreAndNavigate = ( options: Options ): Function => {
} );
setObservations( [newObservation] );
if ( addPhotoPermissionResult !== RESULTS.GRANTED ) return Promise.resolve( );
return savePhotosToCameraGallery( cameraUris, addCameraRollUri );
return savePhotosToCameraGallery( cameraUris, addCameraRollUri, userLocation );
}, [
addCameraRollUri,
addPhotoPermissionResult,

View File

@@ -9,8 +9,7 @@ import { Heading4 } from "components/SharedComponents";
import Mortal from "components/SharedComponents/Mortal";
import PermissionGateContainer, {
AUDIO_PERMISSIONS,
CAMERA_PERMISSIONS,
READ_WRITE_MEDIA_PERMISSIONS
CAMERA_PERMISSIONS
} from "components/SharedComponents/PermissionGateContainer.tsx";
import SoundRecorder from "components/SoundRecorder/SoundRecorder";
import { t } from "i18next";
@@ -70,19 +69,30 @@ const CameraContainerWithPermission = ( ) => (
</Mortal>
);
// const GalleryContainerWithPermission = ( ) => (
// <PermissionGateContainer
// permissions={READ_WRITE_MEDIA_PERMISSIONS}
// title={t( "Observe-and-identify-organisms-from-your-gallery" )}
// titleDenied={t( "Please-Allow-Gallery-Access" )}
// body={t( "Upload-photos-from-your-gallery-and-create-observations" )}
// blockedPrompt={t( "Youve-previously-denied-gallery-permissions" )}
// buttonText={t( "CHOOSE-PHOTOS" )}
// icon="gallery"
// image={require( "images/background/viviana-rishe-j2330n6bg3I-unsplash.jpg" )}
// >
// <PhotoGallery />
// </PermissionGateContainer>
// );
// On iOS we don't actually need PHOTO LIBRARY permission to import photos,
// and in fact, if we ask for it and the user denies it after already
// granting add-only permission, the user can never grant it again until they
// uninstall the app. We *may* want to bring this back to handle writing to
// albums, but for now this works. ~~~~kueda20240829
// TODO verify this is true for Android
const GalleryContainerWithPermission = ( ) => (
<PermissionGateContainer
permissions={READ_WRITE_MEDIA_PERMISSIONS}
title={t( "Observe-and-identify-organisms-from-your-gallery" )}
titleDenied={t( "Please-Allow-Gallery-Access" )}
body={t( "Upload-photos-from-your-gallery-and-create-observations" )}
blockedPrompt={t( "Youve-previously-denied-gallery-permissions" )}
buttonText={t( "CHOOSE-PHOTOS" )}
icon="gallery"
image={require( "images/background/viviana-rishe-j2330n6bg3I-unsplash.jpg" )}
>
<PhotoGallery />
</PermissionGateContainer>
<PhotoGallery />
);
const SoundRecorderWithPermission = ( ) => (