better support for iOS 14

Co-Authored-by: Jean-François Puissant <jean-francois.puissant@fleetback.com>
This commit is contained in:
Michele Balistreri 2021-07-14 17:21:44 +02:00
parent 8bb2ea468b
commit 337c5a515e
No known key found for this signature in database
GPG Key ID: E9567DA33A4F791A
4 changed files with 69 additions and 28 deletions

View File

@ -241,6 +241,7 @@ Returns a Promise which when resolved will be of the following shape:
* `has_next_page`: {boolean} * `has_next_page`: {boolean}
* `start_cursor`: {string} * `start_cursor`: {string}
* `end_cursor`: {string} * `end_cursor`: {string}
* `limited` : {boolean | undefined} : true if the app can only access a subset of the gallery pictures (authorization is `PHAuthorizationStatusLimited`), false otherwise (iOS only)
#### Example #### Example

View File

@ -20,6 +20,9 @@ const {
Platform, Platform,
StyleSheet, StyleSheet,
View, View,
TouchableOpacity,
Text,
Linking,
} = ReactNative; } = ReactNative;
import CameraRoll from '../../js/CameraRoll'; import CameraRoll from '../../js/CameraRoll';
@ -61,6 +64,7 @@ class CameraRollView extends React.Component {
lastCursor: null, lastCursor: null,
noMore: false, noMore: false,
loadingMore: false, loadingMore: false,
isLimited: false,
}; };
} }
@ -148,6 +152,16 @@ class CameraRollView extends React.Component {
if (!this.state.noMore) { if (!this.state.noMore) {
return <ActivityIndicator />; return <ActivityIndicator />;
} }
if (this.state.isLimited) {
return (
<TouchableOpacity onPress={Linking.openSettings}>
<Text style={styles.footerText}>
Not all pictures are available. Tap here to go to Settings and
change which media the app is allowed to access.
</Text>
</TouchableOpacity>
);
}
return null; return null;
}; };
@ -162,7 +176,7 @@ class CameraRollView extends React.Component {
_appendAssets(data) { _appendAssets(data) {
const assets = data.edges; const assets = data.edges;
const newState = {loadingMore: false}; const newState = {loadingMore: false, isLimited: data.limited};
if (!data.page_info.has_next_page) { if (!data.page_info.has_next_page) {
newState.noMore = true; newState.noMore = true;
@ -210,6 +224,10 @@ const styles = StyleSheet.create({
container: { container: {
flex: 1, flex: 1,
}, },
footerText: {
padding: 20,
textAlign: 'center',
},
}); });
module.exports = CameraRollView; module.exports = CameraRollView;

View File

@ -96,18 +96,35 @@ static NSString *const kErrorUnableToLoad = @"E_UNABLE_TO_LOAD";
static NSString *const kErrorAuthRestricted = @"E_PHOTO_LIBRARY_AUTH_RESTRICTED"; static NSString *const kErrorAuthRestricted = @"E_PHOTO_LIBRARY_AUTH_RESTRICTED";
static NSString *const kErrorAuthDenied = @"E_PHOTO_LIBRARY_AUTH_DENIED"; static NSString *const kErrorAuthDenied = @"E_PHOTO_LIBRARY_AUTH_DENIED";
typedef void (^PhotosAuthorizedBlock)(void); typedef void (^PhotosAuthorizedBlock)(bool isLimited);
static void requestPhotoLibraryAccess(RCTPromiseRejectBlock reject, PhotosAuthorizedBlock authorizedBlock) { static void requestPhotoLibraryAccess(bool addOnly, RCTPromiseRejectBlock reject, PhotosAuthorizedBlock authorizedBlock) {
PHAuthorizationStatus authStatus = [PHPhotoLibrary authorizationStatus]; PHAuthorizationStatus authStatus;
if (@available(iOS 14, *)) {
authStatus = [PHPhotoLibrary authorizationStatusForAccessLevel:(addOnly ? PHAccessLevelAddOnly : PHAccessLevelReadWrite)];
} else {
authStatus = [PHPhotoLibrary authorizationStatus];
}
if (authStatus == PHAuthorizationStatusRestricted) { if (authStatus == PHAuthorizationStatusRestricted) {
reject(kErrorAuthRestricted, @"Access to photo library is restricted", nil); reject(kErrorAuthRestricted, @"Access to photo library is restricted", nil);
} else if (authStatus == PHAuthorizationStatusAuthorized) { } else if (authStatus == PHAuthorizationStatusAuthorized) {
authorizedBlock(); authorizedBlock(false);
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wunguarded-availability-new"
} else if (authStatus == PHAuthorizationStatusLimited) {
#pragma clang diagnostic pop
authorizedBlock(true);
} else if (authStatus == PHAuthorizationStatusNotDetermined) { } else if (authStatus == PHAuthorizationStatusNotDetermined) {
[PHPhotoLibrary requestAuthorization:^(PHAuthorizationStatus status) { if (@available(iOS 14, *)) {
requestPhotoLibraryAccess(reject, authorizedBlock); [PHPhotoLibrary requestAuthorizationForAccessLevel:(addOnly ? PHAccessLevelAddOnly : PHAccessLevelReadWrite)
handler:^(PHAuthorizationStatus status) {
requestPhotoLibraryAccess(addOnly, reject, authorizedBlock);
}]; }];
} else {
[PHPhotoLibrary requestAuthorization:^(PHAuthorizationStatus status) {
requestPhotoLibraryAccess(addOnly, reject, authorizedBlock);
}];
}
} else { } else {
reject(kErrorAuthDenied, @"Access to photo library was denied", nil); reject(kErrorAuthDenied, @"Access to photo library was denied", nil);
} }
@ -188,12 +205,12 @@ RCT_EXPORT_METHOD(saveToCameraRoll:(NSURLRequest *)request
} }
}; };
void (^loadBlock)(void) = ^void() { void (^loadBlock)(bool isLimited) = ^void(bool isLimited) {
inputURI = request.URL; inputURI = request.URL;
saveWithOptions(); saveWithOptions();
}; };
requestPhotoLibraryAccess(reject, loadBlock); requestPhotoLibraryAccess(true, reject, loadBlock);
} }
RCT_EXPORT_METHOD(getAlbums:(NSDictionary *)params RCT_EXPORT_METHOD(getAlbums:(NSDictionary *)params
@ -220,14 +237,16 @@ RCT_EXPORT_METHOD(getAlbums:(NSDictionary *)params
static void RCTResolvePromise(RCTPromiseResolveBlock resolve, static void RCTResolvePromise(RCTPromiseResolveBlock resolve,
NSArray<NSDictionary<NSString *, id> *> *assets, NSArray<NSDictionary<NSString *, id> *> *assets,
BOOL hasNextPage) BOOL hasNextPage,
bool isLimited)
{ {
if (!assets.count) { if (!assets.count) {
resolve(@{ resolve(@{
@"edges": assets, @"edges": assets,
@"page_info": @{ @"page_info": @{
@"has_next_page": @NO, @"has_next_page": @NO,
} },
@"limited": @(isLimited)
}); });
return; return;
} }
@ -237,7 +256,8 @@ static void RCTResolvePromise(RCTPromiseResolveBlock resolve,
@"start_cursor": assets[0][@"node"][@"image"][@"uri"], @"start_cursor": assets[0][@"node"][@"image"][@"uri"],
@"end_cursor": assets[assets.count - 1][@"node"][@"image"][@"uri"], @"end_cursor": assets[assets.count - 1][@"node"][@"image"][@"uri"],
@"has_next_page": @(hasNextPage), @"has_next_page": @(hasNextPage),
} },
@"limited": @(isLimited)
}); });
} }
@ -301,7 +321,7 @@ RCT_EXPORT_METHOD(getPhotos:(NSDictionary *)params
BOOL __block stopCollections_; BOOL __block stopCollections_;
NSString __block *currentCollectionName; NSString __block *currentCollectionName;
requestPhotoLibraryAccess(reject, ^{ requestPhotoLibraryAccess(false, reject, ^(bool isLimited){
void (^collectAsset)(PHAsset*, NSUInteger, BOOL*) = ^(PHAsset * _Nonnull asset, NSUInteger assetIdx, BOOL * _Nonnull stopAssets) { void (^collectAsset)(PHAsset*, NSUInteger, BOOL*) = ^(PHAsset * _Nonnull asset, NSUInteger assetIdx, BOOL * _Nonnull stopAssets) {
NSString *const uri = [NSString stringWithFormat:@"ph://%@", [asset localIdentifier]]; NSString *const uri = [NSString stringWithFormat:@"ph://%@", [asset localIdentifier]];
NSString *_Nullable originalFilename = NULL; NSString *_Nullable originalFilename = NULL;
@ -354,7 +374,7 @@ RCT_EXPORT_METHOD(getPhotos:(NSDictionary *)params
stopCollections_ = YES; stopCollections_ = YES;
hasNextPage = YES; hasNextPage = YES;
RCTAssert(resolvedPromise == NO, @"Resolved the promise before we finished processing the results."); RCTAssert(resolvedPromise == NO, @"Resolved the promise before we finished processing the results.");
RCTResolvePromise(resolve, assets, hasNextPage); RCTResolvePromise(resolve, assets, hasNextPage, isLimited);
resolvedPromise = YES; resolvedPromise = YES;
return; return;
} }
@ -412,7 +432,7 @@ RCT_EXPORT_METHOD(getPhotos:(NSDictionary *)params
// If we get this far and haven't resolved the promise yet, we reached the end of the list of photos // If we get this far and haven't resolved the promise yet, we reached the end of the list of photos
if (!resolvedPromise) { if (!resolvedPromise) {
hasNextPage = NO; hasNextPage = NO;
RCTResolvePromise(resolve, assets, hasNextPage); RCTResolvePromise(resolve, assets, hasNextPage, isLimited);
resolvedPromise = YES; resolvedPromise = YES;
} }
}); });

View File

@ -122,7 +122,9 @@ export type PhotoIdentifiersPage = {
start_cursor?: string, start_cursor?: string,
end_cursor?: string, end_cursor?: string,
}, },
limited?: boolean,
}; };
export type SaveToCameraRollOptions = { export type SaveToCameraRollOptions = {
type?: 'photo' | 'video' | 'auto', type?: 'photo' | 'video' | 'auto',
album?: string, album?: string,