Skip to content

Commit

Permalink
feat(camera): Return if image was saved to gallery (#599)
Browse files Browse the repository at this point in the history
  • Loading branch information
jcesarmobile committed Sep 9, 2021
1 parent 698350e commit 594af3b
Show file tree
Hide file tree
Showing 8 changed files with 88 additions and 20 deletions.
17 changes: 9 additions & 8 deletions camera/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -143,14 +143,15 @@ Request camera and photo album permissions

#### Photo

| Prop | Type | Description | Since |
| ------------------ | ------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----- |
| **`base64String`** | <code>string</code> | The base64 encoded string representation of the image, if using <a href="#cameraresulttype">CameraResultType.Base64</a>. | 1.0.0 |
| **`dataUrl`** | <code>string</code> | The url starting with 'data:image/jpeg;base64,' and the base64 encoded string representation of the image, if using <a href="#cameraresulttype">CameraResultType.DataUrl</a>. | 1.0.0 |
| **`path`** | <code>string</code> | If using <a href="#cameraresulttype">CameraResultType.Uri</a>, the path will contain a full, platform-specific file URL that can be read later using the Filsystem API. | 1.0.0 |
| **`webPath`** | <code>string</code> | webPath returns a path that can be used to set the src attribute of an image for efficient loading and rendering. | 1.0.0 |
| **`exif`** | <code>any</code> | Exif data, if any, retrieved from the image | 1.0.0 |
| **`format`** | <code>string</code> | The format of the image, ex: jpeg, png, gif. iOS and Android only support jpeg. Web supports jpeg and png. gif is only supported if using file input. | 1.0.0 |
| Prop | Type | Description | Since |
| ------------------ | -------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----- |
| **`base64String`** | <code>string</code> | The base64 encoded string representation of the image, if using <a href="#cameraresulttype">CameraResultType.Base64</a>. | 1.0.0 |
| **`dataUrl`** | <code>string</code> | The url starting with 'data:image/jpeg;base64,' and the base64 encoded string representation of the image, if using <a href="#cameraresulttype">CameraResultType.DataUrl</a>. | 1.0.0 |
| **`path`** | <code>string</code> | If using <a href="#cameraresulttype">CameraResultType.Uri</a>, the path will contain a full, platform-specific file URL that can be read later using the Filsystem API. | 1.0.0 |
| **`webPath`** | <code>string</code> | webPath returns a path that can be used to set the src attribute of an image for efficient loading and rendering. | 1.0.0 |
| **`exif`** | <code>any</code> | Exif data, if any, retrieved from the image | 1.0.0 |
| **`format`** | <code>string</code> | The format of the image, ex: jpeg, png, gif. iOS and Android only support jpeg. Web supports jpeg and png. gif is only supported if using file input. | 1.0.0 |
| **`saved`** | <code>boolean</code> | Whether if the image was saved to the gallery or not. On Android and iOS, saving to the gallery can fail if the user didn't grant the required permissions. On Web there is no gallery, so always returns false. | 1.1.0 |


#### ImageOptions
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,8 @@ public class CameraPlugin extends Plugin {
private Uri imageFileUri;
private Uri imagePickedContentUri;
private boolean isEdited = false;
private boolean isFirstRequest = true;
private boolean isSaved = false;

private CameraSettings settings = new CameraSettings();

Expand Down Expand Up @@ -145,7 +147,8 @@ private boolean checkCameraPermissions(PluginCall call) {
boolean hasPhotoPerms = getPermissionState(PHOTOS) == PermissionState.GRANTED;

// If we want to save to the gallery, we need two permissions
if (settings.isSaveToGallery() && !(hasCameraPerms && hasPhotoPerms)) {
if (settings.isSaveToGallery() && !(hasCameraPerms && hasPhotoPerms) && isFirstRequest) {
isFirstRequest = false;
String[] aliases;
if (needCameraPerms) {
aliases = new String[] { CAMERA, PHOTOS };
Expand Down Expand Up @@ -391,11 +394,21 @@ private void returnResult(PluginCall call, Bitmap bitmap, Uri u) {

boolean saveToGallery = call.getBoolean("saveToGallery", CameraSettings.DEFAULT_SAVE_IMAGE_TO_GALLERY);
if (saveToGallery && (imageEditedFileSavePath != null || imageFileSavePath != null)) {
isSaved = true;
try {
String fileToSavePath = imageEditedFileSavePath != null ? imageEditedFileSavePath : imageFileSavePath;
File fileToSave = new File(fileToSavePath);
MediaStore.Images.Media.insertImage(getContext().getContentResolver(), fileToSavePath, fileToSave.getName(), "");
String inserted = MediaStore.Images.Media.insertImage(
getContext().getContentResolver(),
fileToSavePath,
fileToSave.getName(),
""
);
if (inserted == null) {
isSaved = false;
}
} catch (FileNotFoundException e) {
isSaved = false;
Logger.error(getLogTag(), IMAGE_GALLERY_SAVE_ERROR, e);
}
}
Expand Down Expand Up @@ -437,6 +450,7 @@ private void returnFileURI(PluginCall call, ExifWrapper exif, Bitmap bitmap, Uri
ret.put("exif", exif.toJson());
ret.put("path", newUri.toString());
ret.put("webPath", FileUtils.getPortablePath(getContext(), bridge.getLocalUrl(), newUri));
ret.put("saved", isSaved);
call.resolve(ret);
} else {
call.reject(UNABLE_TO_PROCESS_IMAGE);
Expand Down
4 changes: 4 additions & 0 deletions camera/ios/Plugin.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
/* Begin PBXBuildFile section */
03FC29A292ACC40490383A1F /* Pods_Plugin.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 3B2A61DA5A1F2DD4F959604D /* Pods_Plugin.framework */; };
20C0B05DCFC8E3958A738AF2 /* Pods_PluginTests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = F6753A823D3815DB436415E3 /* Pods_PluginTests.framework */; };
2FA5008E26E9143C00127B0B /* ImageSaver.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2FA5008D26E9143C00127B0B /* ImageSaver.swift */; };
50ADFF92201F53D600D50D53 /* Plugin.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 50ADFF88201F53D600D50D53 /* Plugin.framework */; };
50ADFF97201F53D600D50D53 /* CameraPluginTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50ADFF96201F53D600D50D53 /* CameraPluginTests.swift */; };
50ADFF99201F53D600D50D53 /* CameraPlugin.h in Headers */ = {isa = PBXBuildFile; fileRef = 50ADFF8B201F53D600D50D53 /* CameraPlugin.h */; settings = {ATTRIBUTES = (Public, ); }; };
Expand All @@ -30,6 +31,7 @@
/* End PBXContainerItemProxy section */

/* Begin PBXFileReference section */
2FA5008D26E9143C00127B0B /* ImageSaver.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ImageSaver.swift; sourceTree = "<group>"; };
3B2A61DA5A1F2DD4F959604D /* Pods_Plugin.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Plugin.framework; sourceTree = BUILT_PRODUCTS_DIR; };
50ADFF88201F53D600D50D53 /* Plugin.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Plugin.framework; sourceTree = BUILT_PRODUCTS_DIR; };
50ADFF8B201F53D600D50D53 /* CameraPlugin.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = CameraPlugin.h; sourceTree = "<group>"; };
Expand Down Expand Up @@ -94,6 +96,7 @@
50ADFF8A201F53D600D50D53 /* Plugin */ = {
isa = PBXGroup;
children = (
2FA5008D26E9143C00127B0B /* ImageSaver.swift */,
50ADFF8B201F53D600D50D53 /* CameraPlugin.h */,
50ADFFA72020EE4F00D50D53 /* CameraPlugin.m */,
50E1A94720377CB70090CE1A /* CameraPlugin.swift */,
Expand Down Expand Up @@ -311,6 +314,7 @@
files = (
50E1A94820377CB70090CE1A /* CameraPlugin.swift in Sources */,
50ADFFA82020EE4F00D50D53 /* CameraPlugin.m in Sources */,
2FA5008E26E9143C00127B0B /* ImageSaver.swift in Sources */,
6276AAD7255B3E1400097815 /* CameraTypes.swift in Sources */,
6276AAD3255B3E0E00097815 /* CameraExtensions.swift in Sources */,
);
Expand Down
35 changes: 25 additions & 10 deletions camera/ios/Plugin/CameraPlugin.swift
Original file line number Diff line number Diff line change
Expand Up @@ -186,23 +186,25 @@ extension CameraPlugin: PHPickerViewControllerDelegate {
}

private extension CameraPlugin {
func returnProcessedImage(_ processedImage: ProcessedImage) {
func returnImage(_ processedImage: ProcessedImage, isSaved: Bool) {
guard let jpeg = processedImage.generateJPEG(with: settings.jpegQuality) else {
self.call?.reject("Unable to convert image to jpeg")
return
}

if settings.resultType == CameraResultType.base64 {
call?.resolve([
self.call?.resolve([
"base64String": jpeg.base64EncodedString(),
"exif": processedImage.exifData,
"format": "jpeg"
"format": "jpeg",
"saved": isSaved
])
} else if settings.resultType == CameraResultType.dataURL {
call?.resolve([
"dataUrl": "data:image/jpeg;base64," + jpeg.base64EncodedString(),
"exif": processedImage.exifData,
"format": "jpeg"
"format": "jpeg",
"saved": isSaved
])
} else if settings.resultType == CameraResultType.uri {
guard let fileURL = try? saveTemporaryImage(jpeg),
Expand All @@ -214,11 +216,27 @@ private extension CameraPlugin {
"path": fileURL.absoluteString,
"exif": processedImage.exifData,
"webPath": webURL.absoluteString,
"format": "jpeg"
"format": "jpeg",
"saved": isSaved
])
}
}

func returnProcessedImage(_ processedImage: ProcessedImage) {
// conditionally save the image
if settings.saveToGallery && (processedImage.flags.contains(.edited) == true || processedImage.flags.contains(.gallery) == false) {
_ = ImageSaver(image: processedImage.image) { error in
var isSaved = false
if error == nil {
isSaved = true
}
self.returnImage(processedImage, isSaved: isSaved)
}
} else {
self.returnImage(processedImage, isSaved: false)
}
}

func showPrompt() {
// Build the action sheet
let alert = UIAlertController(title: settings.userPromptText.title, message: nil, preferredStyle: UIAlertController.Style.actionSheet)
Expand Down Expand Up @@ -380,11 +398,8 @@ private extension CameraPlugin {
metadata = asset.imageData
}
// get the result
let result = processedImage(from: image, with: metadata)
// conditionally save the image
if settings.saveToGallery && (flags.contains(.edited) == true || flags.contains(.gallery) == false) {
UIImageWriteToSavedPhotosAlbum(result.image, nil, nil, nil)
}
var result = processedImage(from: image, with: metadata)
result.flags = flags
return result
}

Expand Down
1 change: 1 addition & 0 deletions camera/ios/Plugin/CameraTypes.swift
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,7 @@ internal struct PhotoFlags: OptionSet {
internal struct ProcessedImage {
var image: UIImage
var metadata: [String: Any]
var flags: PhotoFlags = []

var exifData: [String: Any] {
var exifData = metadata["{Exif}"] as? [String: Any]
Expand Down
20 changes: 20 additions & 0 deletions camera/ios/Plugin/ImageSaver.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import UIKit

class ImageSaver: NSObject {

var onResult: ((Error?)->Void) = {_ in }

init(image: UIImage, onResult:@escaping ((Error?)->Void)) {
self.onResult = onResult
super.init()
UIImageWriteToSavedPhotosAlbum(image, self, #selector(saveResult), nil)
}

@objc func saveResult(_ image: UIImage, didFinishSavingWithError error: Error?, contextInfo: UnsafeRawPointer) {
if let error = error {
onResult(error)
} else {
onResult(nil)
}
}
}
10 changes: 10 additions & 0 deletions camera/src/definitions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -208,6 +208,16 @@ export interface Photo {
* @since 1.0.0
*/
format: string;
/**
* Whether if the image was saved to the gallery or not.
*
* On Android and iOS, saving to the gallery can fail if the user didn't
* grant the required permissions.
* On Web there is no gallery, so always returns false.
*
* @since 1.1.0
*/
saved: boolean;
}

export enum CameraSource {
Expand Down
3 changes: 3 additions & 0 deletions camera/src/web.ts
Original file line number Diff line number Diff line change
Expand Up @@ -161,6 +161,7 @@ export class CameraWeb extends WebPlugin implements CameraPlugin {
resolve({
webPath: URL.createObjectURL(photo),
format: format,
saved: false,
});
} else {
reader.readAsDataURL(photo);
Expand All @@ -170,11 +171,13 @@ export class CameraWeb extends WebPlugin implements CameraPlugin {
resolve({
dataUrl: r,
format: format,
saved: false,
});
} else {
resolve({
base64String: r.split(',')[1],
format: format,
saved: false,
});
}
};
Expand Down

0 comments on commit 594af3b

Please sign in to comment.