mirror of
https://github.com/apache/cordova-plugin-camera.git
synced 2026-02-03 00:06:46 +08:00
Compare commits
29 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
90ad137398 | ||
|
|
dc682b2532 | ||
|
|
599954887b | ||
|
|
80a2f18a05 | ||
|
|
8864262022 | ||
|
|
b002b48735 | ||
|
|
979155ee98 | ||
|
|
7f33ef4add | ||
|
|
4c9dc10512 | ||
|
|
de468b6f63 | ||
|
|
48c4cdd47f | ||
|
|
7d159cf3c9 | ||
|
|
c208754c08 | ||
|
|
0d86764b90 | ||
|
|
7adccc8ee9 | ||
|
|
53795454f4 | ||
|
|
2eaa9a3972 | ||
|
|
a672c31efb | ||
|
|
16325102c7 | ||
|
|
36bf8e7331 | ||
|
|
feb7643bc3 | ||
|
|
44480300d9 | ||
|
|
faa4615ee0 | ||
|
|
0a4bfe1a74 | ||
|
|
49438dee6d | ||
|
|
f38aba2b59 | ||
|
|
3b73186b91 | ||
|
|
40a5db10c7 | ||
|
|
9eecbaa1af |
44
.github/workflows/android.yml
vendored
44
.github/workflows/android.yml
vendored
@@ -23,6 +23,7 @@ on:
|
||||
- '**.md'
|
||||
- 'LICENSE'
|
||||
- '.eslint*'
|
||||
|
||||
pull_request:
|
||||
paths-ignore:
|
||||
- '**.md'
|
||||
@@ -32,7 +33,7 @@ on:
|
||||
jobs:
|
||||
test:
|
||||
name: Android ${{ matrix.versions.android }} Test
|
||||
runs-on: macos-latest
|
||||
runs-on: ubuntu-latest
|
||||
continue-on-error: true
|
||||
|
||||
# hoist configurations to top that are expected to be updated
|
||||
@@ -40,12 +41,12 @@ jobs:
|
||||
# Storing a copy of the repo
|
||||
repo: ${{ github.event.pull_request.head.repo.full_name || github.repository }}
|
||||
|
||||
node-version: 16
|
||||
node-version: 20
|
||||
|
||||
# These are the default Java configurations used by most tests.
|
||||
# To customize these options, add "java-distro" or "java-version" to the strategy matrix with its overriding value.
|
||||
default_java-distro: temurin
|
||||
default_java-version: 11
|
||||
default_java-version: 17
|
||||
|
||||
# These are the default Android System Image configurations used by most tests.
|
||||
# To customize these options, add "system-image-arch" or "system-image-target" to the strategy matrix with its overriding value.
|
||||
@@ -56,31 +57,48 @@ jobs:
|
||||
strategy:
|
||||
matrix:
|
||||
versions:
|
||||
# Test the lowest minimum supported APIs
|
||||
- android: 7
|
||||
android-api: 24
|
||||
|
||||
# Test the last 3-4 supported APIs
|
||||
- android: 7.1
|
||||
android-api: 25
|
||||
|
||||
- android: 8
|
||||
android-api: 26
|
||||
|
||||
- android: 8.1
|
||||
android-api: 27
|
||||
system-image-arch: x86
|
||||
|
||||
- android: 9
|
||||
android-api: 28
|
||||
|
||||
- android: 10
|
||||
android-api: 29
|
||||
|
||||
- android: 11
|
||||
android-api: 30
|
||||
|
||||
- android: 12
|
||||
android-api: 31
|
||||
|
||||
- android: 12L
|
||||
android-api: 32
|
||||
|
||||
- android: 13
|
||||
android-api: 33
|
||||
|
||||
- android: 14
|
||||
android-api: 34
|
||||
|
||||
timeout-minutes: 60
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/setup-node@v3
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: ${{ env.node-version }}
|
||||
- uses: actions/setup-java@v3
|
||||
- uses: actions/setup-java@v4
|
||||
env:
|
||||
java-version: ${{ matrix.versions.java-version == '' && env.default_java-version || matrix.versions.java-version }}
|
||||
java-distro: ${{ matrix.versions.java-distro == '' && env.default_java-distro || matrix.versions.java-distro }}
|
||||
@@ -88,6 +106,12 @@ jobs:
|
||||
distribution: ${{ env.java-distro }}
|
||||
java-version: ${{ env.java-version }}
|
||||
|
||||
- name: Enable KVM group perms
|
||||
run: |
|
||||
echo 'KERNEL=="kvm", GROUP="kvm", MODE="0666", OPTIONS+="static_node=kvm"' | sudo tee /etc/udev/rules.d/99-kvm4all.rules
|
||||
sudo udevadm control --reload-rules
|
||||
sudo udevadm trigger --name-match=kvm
|
||||
|
||||
- name: Run Environment Information
|
||||
run: |
|
||||
node --version
|
||||
@@ -105,7 +129,7 @@ jobs:
|
||||
if: ${{ endswith(env.repo, '/cordova-paramedic') != true }}
|
||||
run: npm i -g github:apache/cordova-paramedic
|
||||
|
||||
- uses: reactivecircus/android-emulator-runner@d94c3fbe4fe6a29e4a5ba47c12fb47677c73656b
|
||||
- uses: reactivecircus/android-emulator-runner@v2
|
||||
env:
|
||||
system-image-arch: ${{ matrix.versions.system-image-arch == '' && env.default_system-image-arch || matrix.versions.system-image-arch }}
|
||||
system-image-target: ${{ matrix.versions.system-image-target == '' && env.default_system-image-target || matrix.versions.system-image-target }}
|
||||
@@ -119,7 +143,7 @@ jobs:
|
||||
script: echo "Pregenerate the AVD before running Paramedic"
|
||||
|
||||
- name: Run paramedic tests
|
||||
uses: reactivecircus/android-emulator-runner@d94c3fbe4fe6a29e4a5ba47c12fb47677c73656b
|
||||
uses: reactivecircus/android-emulator-runner@v2
|
||||
env:
|
||||
system-image-arch: ${{ matrix.versions.system-image-arch == '' && env.default_system-image-arch || matrix.versions.system-image-arch }}
|
||||
system-image-target: ${{ matrix.versions.system-image-target == '' && env.default_system-image-target || matrix.versions.system-image-target }}
|
||||
|
||||
6
.github/workflows/chrome.yml
vendored
6
.github/workflows/chrome.yml
vendored
@@ -39,11 +39,11 @@ jobs:
|
||||
# Storing a copy of the repo
|
||||
repo: ${{ github.event.pull_request.head.repo.full_name || github.repository }}
|
||||
|
||||
node-version: 16
|
||||
node-version: 20
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/setup-node@v3
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: ${{ env.node-version }}
|
||||
|
||||
|
||||
36
.github/workflows/ios.yml
vendored
36
.github/workflows/ios.yml
vendored
@@ -40,7 +40,7 @@ jobs:
|
||||
# Storing a copy of the repo
|
||||
repo: ${{ github.event.pull_request.head.repo.full_name || github.repository }}
|
||||
|
||||
node-version: 16
|
||||
node-version: 20
|
||||
|
||||
# > Starting April 26, 2021, all iOS and iPadOS apps submitted to the App Store must be built with Xcode 12 and the iOS 14 SDK.
|
||||
# Because of Apple's requirement, listed above, We will only be using the latest Xcode release for testing.
|
||||
@@ -50,28 +50,32 @@ jobs:
|
||||
strategy:
|
||||
matrix:
|
||||
versions:
|
||||
- os-version: macos-11
|
||||
ios-version: 13.x
|
||||
xcode-version: 11.x
|
||||
|
||||
- os-version: macos-11
|
||||
ios-version: 14.x
|
||||
xcode-version: 12.x
|
||||
|
||||
- os-version: macos-11
|
||||
- os-version: macos-14
|
||||
ios-version: 15.x
|
||||
xcode-version: 13.x
|
||||
xcode-version: 15.x
|
||||
|
||||
- os-version: macos-12
|
||||
- os-version: macos-14
|
||||
ios-version: 16.x
|
||||
xcode-version: 14.x
|
||||
xcode-version: 15.x
|
||||
|
||||
- os-version: macos-14
|
||||
ios-version: 17.x
|
||||
xcode-version: 15.x
|
||||
|
||||
- os-version: macos-15
|
||||
ios-version: 18.x
|
||||
xcode-version: 16.x
|
||||
|
||||
- os-version: macos-26
|
||||
ios-version: 26.x
|
||||
xcode-version: 26.x
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/setup-node@v3
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: ${{ env.node-version }}
|
||||
- uses: maxim-lobanov/setup-xcode@9a697e2b393340c3cacd97468baa318e4c883d98
|
||||
- uses: maxim-lobanov/setup-xcode@60606e260d2fc5762a71e64e74b2174e8ea3c8bd
|
||||
env:
|
||||
xcode-version: ${{ matrix.versions.xcode-version == '' && env.default_xcode-version || matrix.versions.xcode-version }}
|
||||
with:
|
||||
|
||||
6
.github/workflows/lint.yml
vendored
6
.github/workflows/lint.yml
vendored
@@ -34,11 +34,11 @@ jobs:
|
||||
name: Lint Test
|
||||
runs-on: ubuntu-latest
|
||||
env:
|
||||
node-version: 16
|
||||
node-version: 20
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/setup-node@v3
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: ${{ env.node-version }}
|
||||
|
||||
|
||||
405
README.md
405
README.md
@@ -30,11 +30,13 @@ the system's image library.
|
||||
|
||||
Although the object is attached to the global scoped `navigator`, it is not available until after the `deviceready` event.
|
||||
|
||||
document.addEventListener("deviceready", onDeviceReady, false);
|
||||
function onDeviceReady() {
|
||||
console.log(navigator.camera);
|
||||
}
|
||||
```js
|
||||
document.addEventListener("deviceready", onDeviceReady, false);
|
||||
|
||||
function onDeviceReady() {
|
||||
console.log(navigator.camera);
|
||||
}
|
||||
```
|
||||
|
||||
## Installation
|
||||
|
||||
@@ -65,41 +67,37 @@ In order for your changes to be accepted, you need to sign and submit an Apache
|
||||
|
||||
**And don't forget to test and document your code.**
|
||||
|
||||
### iOS Quirks
|
||||
### iOS Specifics
|
||||
|
||||
Since iOS 10 it's mandatory to provide an usage description in the `info.plist` if trying to access privacy-sensitive data. When the system prompts the user to allow access, this usage description string will displayed as part of the permission dialog box, but if you didn't provide the usage description, the app will crash before showing the dialog. Also, Apple will reject apps that access private data but don't provide an usage description.
|
||||
Since iOS 10 it's mandatory to provide a usage description in the `info.plist` when accessing privacy-sensitive data. The required keys depend on how you use the plugin and which iOS versions you support:
|
||||
|
||||
This plugins requires the following usage descriptions:
|
||||
| Key | Description |
|
||||
| ------------------------------ | ----------- |
|
||||
| NSCameraUsageDescription | Required whenever the camera is used (e.g. `Camera.PictureSourceType.CAMERA`). |
|
||||
| NSPhotoLibraryUsageDescription | Required only when your app runs on iOS 13 or older and using as `sourceType` `Camera.PictureSourceType.PHOTOLIBRARY`. On iOS 14+ the plugin uses PHPicker for read-only access, which does not need this key. |
|
||||
| NSPhotoLibraryAddUsageDescription | Required when the plugin writes to the user's library (e.g. `saveToPhotoAlbum=true`). |
|
||||
| NSLocationWhenInUseUsageDescription | Required if `CameraUsesGeolocation` is set to `true`. |
|
||||
|
||||
- `NSCameraUsageDescription` specifies the reason for your app to access the device's camera.
|
||||
- `NSPhotoLibraryUsageDescription` specifies the reason for your app to access the user's photo library.
|
||||
- `NSLocationWhenInUseUsageDescription` specifies the reason for your app to access the user's location information while your app is in use. (Set it if you have `CameraUsesGeolocation` preference set to `true`)
|
||||
- `NSPhotoLibraryAddUsageDescription` specifies the reason for your app to get write-only access to the user's photo library
|
||||
When the system prompts the user to allow access, this usage description string will be displayed as part of the permission dialog box. If you don't provide the required usage description, the app will crash before showing the dialog. Also, Apple will reject apps that access private data but don't provide a usage description.
|
||||
|
||||
To add these entries into the `info.plist`, you can use the `edit-config` tag in the `config.xml` like this:
|
||||
|
||||
```
|
||||
```xml
|
||||
<edit-config target="NSCameraUsageDescription" file="*-Info.plist" mode="merge">
|
||||
<string>need camera access to take pictures</string>
|
||||
</edit-config>
|
||||
```
|
||||
|
||||
```
|
||||
<edit-config target="NSPhotoLibraryUsageDescription" file="*-Info.plist" mode="merge">
|
||||
<string>need photo library access to get pictures from there</string>
|
||||
</edit-config>
|
||||
```
|
||||
|
||||
```
|
||||
<edit-config target="NSLocationWhenInUseUsageDescription" file="*-Info.plist" mode="merge">
|
||||
<string>need location access to find things nearby</string>
|
||||
</edit-config>
|
||||
```
|
||||
|
||||
```
|
||||
<edit-config target="NSPhotoLibraryAddUsageDescription" file="*-Info.plist" mode="merge">
|
||||
<string>need photo library access to save pictures there</string>
|
||||
</edit-config>
|
||||
|
||||
<edit-config target="NSLocationWhenInUseUsageDescription" file="*-Info.plist" mode="merge">
|
||||
<string>need location access to find things nearby</string>
|
||||
</edit-config>
|
||||
```
|
||||
|
||||
---
|
||||
@@ -120,12 +118,8 @@ To add these entries into the `info.plist`, you can use the `edit-config` tag in
|
||||
* [.EncodingType](#module_Camera.EncodingType) : <code>enum</code>
|
||||
* [.MediaType](#module_Camera.MediaType) : <code>enum</code>
|
||||
* [.PictureSourceType](#module_Camera.PictureSourceType) : <code>enum</code>
|
||||
* [.PopoverArrowDirection](#module_Camera.PopoverArrowDirection) : <code>enum</code>
|
||||
* [.Direction](#module_Camera.Direction) : <code>enum</code>
|
||||
|
||||
* [CameraPopoverHandle](#module_CameraPopoverHandle)
|
||||
* [CameraPopoverOptions](#module_CameraPopoverOptions)
|
||||
|
||||
---
|
||||
|
||||
<a name="module_camera"></a>
|
||||
@@ -135,8 +129,31 @@ To add these entries into the `info.plist`, you can use the `edit-config` tag in
|
||||
|
||||
### camera.getPicture(successCallback, errorCallback, options)
|
||||
Takes a photo using the camera, or retrieves a photo from the device's
|
||||
image gallery. The image is passed to the success callback as a
|
||||
Base64-encoded `String`, or as the URI for the image file.
|
||||
image gallery. The result is provided in the first parameter of the `successCallback` as a string.
|
||||
|
||||
As of v8.0.0, the result is formatted as URIs. The scheme will vary depending on settings and platform.
|
||||
|
||||
|Platform|Destination Type|Format|
|
||||
|---|---|---|
|
||||
|Android|FILE_URI|An URI scheme such as `file://...` or `content://...`|
|
||||
||DATA_URL|Base 64 encoded with the proper data URI header|
|
||||
|iOS|FILE_URI|`file://` schemed paths|
|
||||
||DATA_URL|Base 64 encoded with the proper data URI header|
|
||||
|Browser|FILE_URI|Not supported|
|
||||
||DATA_URL|Base 64 encoded with the proper data URI header|
|
||||
|
||||
v7 and earlier versions, the return format is as follows:
|
||||
|
||||
|Platform|Destination Type|Format|
|
||||
|---|---|---|
|
||||
|Android|FILE_URI|Raw file path (unprefixed)|
|
||||
||DATA_URL|Base 64 encoded, without the `data:` prefix
|
||||
|iOS|FILE_URI|`file://` schemed paths|
|
||||
||DATA_URL|Base 64 encoded, without the `data:` prefix
|
||||
|Browser|FILE_URI|Not supported|
|
||||
||DATA_URL|Base 64 encoded, without the `data:` prefix|
|
||||
|
||||
For this reason, upgrading to v8 is strongly recommended as it greatly streamlines the return data.
|
||||
|
||||
The `camera.getPicture` function opens the device's default camera
|
||||
application that allows users to snap pictures by default - this behavior occurs,
|
||||
@@ -149,16 +166,10 @@ that allows users to select an existing image.
|
||||
|
||||
The return value is sent to the [`cameraSuccess`](#module_camera.onSuccess) callback function, in
|
||||
one of the following formats, depending on the specified
|
||||
`cameraOptions`:
|
||||
`cameraOptions`. You can do whatever you want with content:
|
||||
|
||||
- A `String` containing the Base64-encoded photo image.
|
||||
- A `String` representing the image file location on local storage (default).
|
||||
|
||||
You can do whatever you want with the encoded image or URI, for
|
||||
example:
|
||||
|
||||
- Render the image in an `<img>` tag, as in the example below
|
||||
- Save the data locally (`LocalStorage`, [Lawnchair](http://brianleroux.github.com/lawnchair/), etc.)
|
||||
- Render the content in an `<img>` or `<video>` tag
|
||||
- Copy the data to a persistent location
|
||||
- Post the data to a remote server
|
||||
|
||||
__NOTE__: Photo resolution on newer devices is quite good. Photos
|
||||
@@ -167,6 +178,32 @@ quality, even if a `quality` parameter is specified. To avoid common
|
||||
memory problems, set `Camera.destinationType` to `FILE_URI` rather
|
||||
than `DATA_URL`.
|
||||
|
||||
__NOTE__: To use `saveToPhotoAlbum` option on Android 9 (API 28) and lower, the `WRITE_EXTERNAL_STORAGE` permission must be declared.
|
||||
|
||||
To do this, add the following in your `config.xml`:
|
||||
|
||||
```xml
|
||||
<config-file target="AndroidManifest.xml" parent="/*" xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" android:maxSdkVersion="28" />
|
||||
</config-file>
|
||||
```
|
||||
|
||||
Android 10 (API 29) and later devices does not require `WRITE_EXTERNAL_STORAGE` permission. If your application only supports Android 10 or later, then this step is not necessary.
|
||||
|
||||
#### FILE_URI Usage
|
||||
|
||||
When `FILE_URI` is used, the returned path is not directly usable. The file path needs to be resolved into
|
||||
a DOM-usable URL using the [Cordova File Plugin](https://github.com/apache/cordova-plugin-file).
|
||||
|
||||
Additionally, the file URIs returned is a temporary read access grant. The OS reserves the right to revoke permission to access the resource, which typically occurs after the app has been closed. For images captured using the camera, the image is stored in a temporary location which can be cleared at any time, usually after the app exits. It's the application's decision to decide how the content should be used depending on their use cases.
|
||||
|
||||
For persistent access to the content, the resource should be copied to your app's storage container. An example use case is an app allowing an user to select a profile picture from their gallery or camera. The application will need
|
||||
consistent access to that resource, so it's not suitable to retain the temporary access path. So the appplication should copy the resource to a persistent location.
|
||||
|
||||
For use cases that involve temporary use, it is valid and safe to use the temporary file path to display the content. An example of this could be an image editing application, rendering the data into a canvas.
|
||||
|
||||
__NOTE__: The returned schemes is an implementation detail. Do not assume that it will always be a `file://` URI.
|
||||
|
||||
__Supported Platforms__
|
||||
|
||||
- Android
|
||||
@@ -175,7 +212,7 @@ __Supported Platforms__
|
||||
|
||||
More examples [here](#camera-getPicture-examples). Quirks [here](#camera-getPicture-quirks).
|
||||
|
||||
**Kind**: static method of <code>[camera](#module_camera)</code>
|
||||
**Kind**: static method of <code>[camera](#module_camera)</code>
|
||||
|
||||
| Param | Type | Description |
|
||||
| --- | --- | --- |
|
||||
@@ -183,7 +220,7 @@ More examples [here](#camera-getPicture-examples). Quirks [here](#camera-getPict
|
||||
| errorCallback | <code>[onError](#module_camera.onError)</code> | |
|
||||
| options | <code>[CameraOptions](#module_camera.CameraOptions)</code> | CameraOptions |
|
||||
|
||||
**Example**
|
||||
**Example**
|
||||
```js
|
||||
navigator.camera.getPicture(cameraSuccess, cameraError, cameraOptions);
|
||||
```
|
||||
@@ -199,8 +236,8 @@ __Supported Platforms__
|
||||
|
||||
- iOS
|
||||
|
||||
**Kind**: static method of <code>[camera](#module_camera)</code>
|
||||
**Example**
|
||||
**Kind**: static method of <code>[camera](#module_camera)</code>
|
||||
**Example**
|
||||
```js
|
||||
navigator.camera.cleanup(onSuccess, onFail);
|
||||
|
||||
@@ -217,7 +254,7 @@ function onFail(message) {
|
||||
### camera.onError : <code>function</code>
|
||||
Callback function that provides an error message.
|
||||
|
||||
**Kind**: static typedef of <code>[camera](#module_camera)</code>
|
||||
**Kind**: static typedef of <code>[camera](#module_camera)</code>
|
||||
|
||||
| Param | Type | Description |
|
||||
| --- | --- | --- |
|
||||
@@ -228,19 +265,26 @@ Callback function that provides an error message.
|
||||
### camera.onSuccess : <code>function</code>
|
||||
Callback function that provides the image data.
|
||||
|
||||
**Kind**: static typedef of <code>[camera](#module_camera)</code>
|
||||
**Kind**: static typedef of <code>[camera](#module_camera)</code>
|
||||
|
||||
| Param | Type | Description |
|
||||
| --- | --- | --- |
|
||||
| imageData | <code>string</code> | Base64 encoding of the image data, _or_ the image file URI, depending on [`cameraOptions`](#module_camera.CameraOptions) in effect. |
|
||||
| imageData | <code>string</code> | Data URI, _or_ the image file URI, depending on [`cameraOptions`](#module_camera.CameraOptions) in effect. |
|
||||
|
||||
**Example**
|
||||
**Example**
|
||||
```js
|
||||
// Show image
|
||||
//
|
||||
// Show image captured with FILE_URI
|
||||
function cameraCallback(imageData) {
|
||||
window.resolveLocalFileSystemURL(uri, (entry) => {
|
||||
let image = document.getElementById('myImage');
|
||||
image.src = entry.toURL();
|
||||
}, onError);
|
||||
}
|
||||
|
||||
// Show image captured with DATA_URL
|
||||
function cameraCallback(imageData) {
|
||||
var image = document.getElementById('myImage');
|
||||
image.src = "data:image/jpeg;base64," + imageData;
|
||||
image.src = imageData;
|
||||
}
|
||||
```
|
||||
<a name="module_camera.CameraOptions"></a>
|
||||
@@ -249,7 +293,7 @@ function cameraCallback(imageData) {
|
||||
Optional parameters to customize the camera settings.
|
||||
* [Quirks](#CameraOptions-quirks)
|
||||
|
||||
**Kind**: static typedef of <code>[camera](#module_camera)</code>
|
||||
**Kind**: static typedef of <code>[camera](#module_camera)</code>
|
||||
**Properties**
|
||||
|
||||
| Name | Type | Default | Description |
|
||||
@@ -257,14 +301,13 @@ Optional parameters to customize the camera settings.
|
||||
| quality | <code>number</code> | <code>50</code> | Quality of the saved image, expressed as a range of 0-100, where 100 is typically full resolution with no loss from file compression. (Note that information about the camera's resolution is unavailable.) |
|
||||
| destinationType | <code>[DestinationType](#module_Camera.DestinationType)</code> | <code>FILE_URI</code> | Choose the format of the return value. |
|
||||
| sourceType | <code>[PictureSourceType](#module_Camera.PictureSourceType)</code> | <code>CAMERA</code> | Set the source of the picture. |
|
||||
| allowEdit | <code>Boolean</code> | <code>false</code> | Allow simple editing of image before selection. |
|
||||
| ~~allowEdit~~ | <code>Boolean</code> | <code>false</code> | **Deprecated**. Allow simple editing of image before selection. |
|
||||
| encodingType | <code>[EncodingType](#module_Camera.EncodingType)</code> | <code>JPEG</code> | Choose the returned image file's encoding. |
|
||||
| targetWidth | <code>number</code> | | Width in pixels to scale image. Must be used with `targetHeight`. Aspect ratio remains constant. |
|
||||
| targetHeight | <code>number</code> | | Height in pixels to scale image. Must be used with `targetWidth`. Aspect ratio remains constant. |
|
||||
| mediaType | <code>[MediaType](#module_Camera.MediaType)</code> | <code>PICTURE</code> | Set the type of media to select from. Only works when `PictureSourceType` is `PHOTOLIBRARY` or `SAVEDPHOTOALBUM`. |
|
||||
| correctOrientation | <code>Boolean</code> | | Rotate the image to correct for the orientation of the device during capture. |
|
||||
| saveToPhotoAlbum | <code>Boolean</code> | | Save the image to the photo album on the device after capture. |
|
||||
| popoverOptions | <code>[CameraPopoverOptions](#module_CameraPopoverOptions)</code> | | iOS-only options that specify popover location in iPad. |
|
||||
| saveToPhotoAlbum | <code>Boolean</code> | | Save the image to the photo album on the device after capture.<br />See [Android Quirks](#cameragetpicturesuccesscallback-errorcallback-options). |
|
||||
| cameraDirection | <code>[Direction](#module_Camera.Direction)</code> | <code>BACK</code> | Choose the camera to use (front- or back-facing). |
|
||||
|
||||
---
|
||||
@@ -277,18 +320,18 @@ Optional parameters to customize the camera settings.
|
||||
### Camera.DestinationType : <code>enum</code>
|
||||
Defines the output format of `Camera.getPicture` call.
|
||||
|
||||
**Kind**: static enum property of <code>[Camera](#module_Camera)</code>
|
||||
**Kind**: static enum property of <code>[Camera](#module_Camera)</code>
|
||||
**Properties**
|
||||
|
||||
| Name | Type | Default | Description |
|
||||
| --- | --- | --- | --- |
|
||||
| DATA_URL | <code>number</code> | <code>0</code> | Return base64 encoded string. DATA_URL can be very memory intensive and cause app crashes or out of memory errors. Use FILE_URI if possible |
|
||||
| DATA_URL | <code>number</code> | <code>0</code> | Return data uri. DATA_URL can be very memory intensive and cause app crashes or out of memory errors. Use FILE_URI if possible |
|
||||
| FILE_URI | <code>number</code> | <code>1</code> | Return file uri (content://media/external/images/media/2 for Android) |
|
||||
|
||||
<a name="module_Camera.EncodingType"></a>
|
||||
|
||||
### Camera.EncodingType : <code>enum</code>
|
||||
**Kind**: static enum property of <code>[Camera](#module_Camera)</code>
|
||||
**Kind**: static enum property of <code>[Camera](#module_Camera)</code>
|
||||
**Properties**
|
||||
|
||||
| Name | Type | Default | Description |
|
||||
@@ -299,7 +342,7 @@ Defines the output format of `Camera.getPicture` call.
|
||||
<a name="module_Camera.MediaType"></a>
|
||||
|
||||
### Camera.MediaType : <code>enum</code>
|
||||
**Kind**: static enum property of <code>[Camera](#module_Camera)</code>
|
||||
**Kind**: static enum property of <code>[Camera](#module_Camera)</code>
|
||||
**Properties**
|
||||
|
||||
| Name | Type | Default | Description |
|
||||
@@ -313,35 +356,19 @@ Defines the output format of `Camera.getPicture` call.
|
||||
### Camera.PictureSourceType : <code>enum</code>
|
||||
Defines the output format of `Camera.getPicture` call.
|
||||
|
||||
**Kind**: static enum property of <code>[Camera](#module_Camera)</code>
|
||||
**Kind**: static enum property of <code>[Camera](#module_Camera)</code>
|
||||
**Properties**
|
||||
|
||||
| Name | Type | Default | Description |
|
||||
| --- | --- | --- | --- |
|
||||
| PHOTOLIBRARY | <code>number</code> | <code>0</code> | Choose image from the device's photo library (same as SAVEDPHOTOALBUM for Android) |
|
||||
| PHOTOLIBRARY | <code>number</code> | <code>0</code> | Choose image from the device's photo library. |
|
||||
| CAMERA | <code>number</code> | <code>1</code> | Take picture from camera |
|
||||
| SAVEDPHOTOALBUM | <code>number</code> | <code>2</code> | Choose image only from the device's Camera Roll album (same as PHOTOLIBRARY for Android) |
|
||||
|
||||
<a name="module_Camera.PopoverArrowDirection"></a>
|
||||
|
||||
### Camera.PopoverArrowDirection : <code>enum</code>
|
||||
Matches iOS UIPopoverArrowDirection constants to specify arrow location on popover.
|
||||
|
||||
**Kind**: static enum property of <code>[Camera](#module_Camera)</code>
|
||||
**Properties**
|
||||
|
||||
| Name | Type | Default |
|
||||
| --- | --- | --- |
|
||||
| ARROW_UP | <code>number</code> | <code>1</code> |
|
||||
| ARROW_DOWN | <code>number</code> | <code>2</code> |
|
||||
| ARROW_LEFT | <code>number</code> | <code>4</code> |
|
||||
| ARROW_RIGHT | <code>number</code> | <code>8</code> |
|
||||
| ARROW_ANY | <code>number</code> | <code>15</code> |
|
||||
| SAVEDPHOTOALBUM | <code>number</code> | <code>2</code> | Same as `PHOTOLIBRARY`, when running on Android or iOS 14+. On iOS older than 14, an image can only be chosen from the device's Camera Roll album with this setting. |
|
||||
|
||||
<a name="module_Camera.Direction"></a>
|
||||
|
||||
### Camera.Direction : <code>enum</code>
|
||||
**Kind**: static enum property of <code>[Camera](#module_Camera)</code>
|
||||
**Kind**: static enum property of <code>[Camera](#module_Camera)</code>
|
||||
**Properties**
|
||||
|
||||
| Name | Type | Default | Description |
|
||||
@@ -351,96 +378,52 @@ Matches iOS UIPopoverArrowDirection constants to specify arrow location on popov
|
||||
|
||||
---
|
||||
|
||||
<a name="module_CameraPopoverOptions"></a>
|
||||
|
||||
## CameraPopoverOptions
|
||||
iOS-only parameters that specify the anchor element location and arrow
|
||||
direction of the popover when selecting images from an iPad's library
|
||||
or album.
|
||||
Note that the size of the popover may change to adjust to the
|
||||
direction of the arrow and orientation of the screen. Make sure to
|
||||
account for orientation changes when specifying the anchor element
|
||||
location.
|
||||
|
||||
|
||||
| Param | Type | Default | Description |
|
||||
| --- | --- | --- | --- |
|
||||
| [x] | <code>Number</code> | <code>0</code> | x pixel coordinate of screen element onto which to anchor the popover. |
|
||||
| [y] | <code>Number</code> | <code>32</code> | y pixel coordinate of screen element onto which to anchor the popover. |
|
||||
| [width] | <code>Number</code> | <code>320</code> | width, in pixels, of the screen element onto which to anchor the popover. |
|
||||
| [height] | <code>Number</code> | <code>480</code> | height, in pixels, of the screen element onto which to anchor the popover. |
|
||||
| [arrowDir] | <code>[PopoverArrowDirection](#module_Camera.PopoverArrowDirection)</code> | <code>ARROW_ANY</code> | Direction the arrow on the popover should point. |
|
||||
| [popoverWidth] | <code>Number</code> | <code>0</code> | width of the popover (0 or not specified will use apple's default width). |
|
||||
| [popoverHeight] | <code>Number</code> | <code>0</code> | height of the popover (0 or not specified will use apple's default height). |
|
||||
|
||||
---
|
||||
|
||||
<a name="module_CameraPopoverHandle"></a>
|
||||
|
||||
## CameraPopoverHandle
|
||||
A handle to an image picker popover.
|
||||
|
||||
__Supported Platforms__
|
||||
|
||||
- iOS
|
||||
|
||||
**Example**
|
||||
```js
|
||||
navigator.camera.getPicture(onSuccess, onFail,
|
||||
{
|
||||
destinationType: Camera.DestinationType.FILE_URI,
|
||||
sourceType: Camera.PictureSourceType.PHOTOLIBRARY,
|
||||
popoverOptions: new CameraPopoverOptions(300, 300, 100, 100, Camera.PopoverArrowDirection.ARROW_ANY, 300, 600)
|
||||
});
|
||||
|
||||
// Reposition the popover if the orientation changes.
|
||||
window.onorientationchange = function() {
|
||||
var cameraPopoverHandle = new CameraPopoverHandle();
|
||||
var cameraPopoverOptions = new CameraPopoverOptions(0, 0, 100, 100, Camera.PopoverArrowDirection.ARROW_ANY, 400, 500);
|
||||
cameraPopoverHandle.setPosition(cameraPopoverOptions);
|
||||
}
|
||||
```
|
||||
---
|
||||
|
||||
|
||||
## `camera.getPicture` Errata
|
||||
|
||||
#### Example <a name="camera-getPicture-examples"></a>
|
||||
|
||||
Take a photo and retrieve the image's file location:
|
||||
|
||||
navigator.camera.getPicture(onSuccess, onFail, { quality: 50,
|
||||
destinationType: Camera.DestinationType.FILE_URI });
|
||||
```javascript
|
||||
// Don't forget to install cordova-plugin-file for resolveLocalFileSystemURL!
|
||||
|
||||
function onSuccess(imageURI) {
|
||||
var image = document.getElementById('myImage');
|
||||
image.src = imageURI;
|
||||
}
|
||||
navigator.camera.getPicture(onSuccess, onFail, { quality: 50,
|
||||
destinationType: Camera.DestinationType.FILE_URI });
|
||||
|
||||
function onFail(message) {
|
||||
alert('Failed because: ' + message);
|
||||
}
|
||||
function onSuccess(imageURI) {
|
||||
window.resolveLocalFileSystemURL(uri, (entry) => {
|
||||
let img = document.getElementById('image');
|
||||
img.src = entry.toURL();
|
||||
}, onFail);
|
||||
}
|
||||
|
||||
function onFail(message) {
|
||||
alert('Failed because: ' + message);
|
||||
}
|
||||
```
|
||||
|
||||
Take a photo and retrieve it as a Base64-encoded image:
|
||||
|
||||
/**
|
||||
* Warning: Using DATA_URL is not recommended! The DATA_URL destination
|
||||
* type is very memory intensive, even with a low quality setting. Using it
|
||||
* can result in out of memory errors and application crashes. Use FILE_URI
|
||||
* instead.
|
||||
*/
|
||||
navigator.camera.getPicture(onSuccess, onFail, { quality: 25,
|
||||
destinationType: Camera.DestinationType.DATA_URL
|
||||
});
|
||||
```javascript
|
||||
/**
|
||||
* Warning: Using DATA_URL is not recommended! The DATA_URL destination
|
||||
* type is very memory intensive, even with a low quality setting. Using it
|
||||
* can result in out of memory errors and application crashes. Use FILE_URI
|
||||
* instead.
|
||||
*/
|
||||
navigator.camera.getPicture(onSuccess, onFail, { quality: 25,
|
||||
destinationType: Camera.DestinationType.DATA_URL
|
||||
});
|
||||
|
||||
function onSuccess(imageData) {
|
||||
var image = document.getElementById('myImage');
|
||||
image.src = "data:image/jpeg;base64," + imageData;
|
||||
}
|
||||
function onSuccess(imageData) {
|
||||
var image = document.getElementById('myImage');
|
||||
image.src = imageData;
|
||||
}
|
||||
|
||||
function onFail(message) {
|
||||
alert('Failed because: ' + message);
|
||||
}
|
||||
function onFail(message) {
|
||||
alert('Failed because: ' + message);
|
||||
}
|
||||
```
|
||||
|
||||
#### Preferences (iOS)
|
||||
|
||||
@@ -461,18 +444,20 @@ successful.
|
||||
|
||||
#### Browser Quirks
|
||||
|
||||
Can only return photos as Base64-encoded image.
|
||||
Can only return photos as data URI image.
|
||||
|
||||
#### iOS Quirks
|
||||
|
||||
Including a JavaScript `alert()` in either of the callback functions
|
||||
can cause problems. Wrap the alert within a `setTimeout()` to allow
|
||||
the iOS image picker or popover to fully close before the alert
|
||||
the iOS image picker to fully close before the alert
|
||||
displays:
|
||||
|
||||
setTimeout(function() {
|
||||
// do your thing here!
|
||||
}, 0);
|
||||
```javascript
|
||||
setTimeout(function() {
|
||||
// do your thing here!
|
||||
}, 0);
|
||||
```
|
||||
|
||||
## `CameraOptions` Errata <a name="CameraOptions-quirks"></a>
|
||||
|
||||
@@ -486,11 +471,6 @@ displays:
|
||||
|
||||
- Ignores the `encodingType` parameter if the image is unedited (i.e. `quality` is 100, `correctOrientation` is false, and no `targetHeight` or `targetWidth` are specified). The `CAMERA` source will always return the JPEG file given by the native camera and the `PHOTOLIBRARY` and `SAVEDPHOTOALBUM` sources will return the selected file in its existing encoding.
|
||||
|
||||
#### iOS Quirks
|
||||
|
||||
- When using `destinationType.FILE_URI`, photos are saved in the application's temporary directory. The contents of the application's temporary directory is deleted when the application ends.
|
||||
|
||||
|
||||
[android_lifecycle]: http://cordova.apache.org/docs/en/dev/guide/platforms/android/lifecycle.html
|
||||
|
||||
## Sample: Take Pictures, Select Pictures from the Picture Library, and Get Thumbnails <a name="sample"></a>
|
||||
@@ -588,23 +568,25 @@ function openCamera(selection) {
|
||||
|
||||
## Select a File from the Picture Library <a name="selectFile"></a>
|
||||
|
||||
When selecting a file using the file picker, you also need to set the CameraOptions object. In this example, set the `sourceType` to `Camera.PictureSourceType.SAVEDPHOTOALBUM`. To open the file picker, call `getPicture` just as you did in the previous example, passing in the success and error callbacks along with CameraOptions object.
|
||||
When selecting a file using the file picker, you also need to set the CameraOptions object. In this example, set the `sourceType` to `Camera.PictureSourceType.PHOTOLIBRARY`. To open the file picker, call `getPicture` just as you did in the previous example, passing in the success and error callbacks along with CameraOptions object.
|
||||
|
||||
```js
|
||||
function openFilePicker(selection) {
|
||||
|
||||
var srcType = Camera.PictureSourceType.SAVEDPHOTOALBUM;
|
||||
var srcType = Camera.PictureSourceType.PHOTOLIBRARY;
|
||||
var options = setOptions(srcType);
|
||||
var func = createNewFileEntry;
|
||||
|
||||
navigator.camera.getPicture(function cameraSuccess(imageUri) {
|
||||
|
||||
// Do something
|
||||
|
||||
}, function cameraError(error) {
|
||||
console.debug("Unable to obtain picture: " + error, "app");
|
||||
|
||||
}, options);
|
||||
navigator.camera.getPicture(
|
||||
// success callback
|
||||
(imageUri) => {
|
||||
// Do something
|
||||
},
|
||||
// error callback
|
||||
(error) => {
|
||||
console.debug("Unable to obtain picture: " + error, "app");
|
||||
},
|
||||
options);
|
||||
}
|
||||
```
|
||||
|
||||
@@ -615,7 +597,7 @@ Resizing a file selected with the file picker works just like resizing using the
|
||||
```js
|
||||
function openFilePicker(selection) {
|
||||
|
||||
var srcType = Camera.PictureSourceType.SAVEDPHOTOALBUM;
|
||||
var srcType = Camera.PictureSourceType.PHOTOLIBRARY;
|
||||
var options = setOptions(srcType);
|
||||
var func = createNewFileEntry;
|
||||
|
||||
@@ -626,22 +608,24 @@ function openFilePicker(selection) {
|
||||
options.targetWidth = 100;
|
||||
}
|
||||
|
||||
navigator.camera.getPicture(function cameraSuccess(imageUri) {
|
||||
|
||||
// Do something with image
|
||||
|
||||
}, function cameraError(error) {
|
||||
console.debug("Unable to obtain picture: " + error, "app");
|
||||
|
||||
}, options);
|
||||
navigator.camera.getPicture(
|
||||
// success callback
|
||||
(imageUri) {
|
||||
// Do something with image
|
||||
},
|
||||
// error callback
|
||||
(error) => {
|
||||
console.debug("Unable to obtain picture: " + error, "app");
|
||||
},
|
||||
options);
|
||||
}
|
||||
```
|
||||
|
||||
## Take a picture and get a FileEntry Object <a name="convert"></a>
|
||||
|
||||
If you want to do something like copy the image to another location, or upload it somewhere using the FileTransfer plugin, you need to get a FileEntry object for the returned picture. To do that, call `window.resolveLocalFileSystemURL` on the file URI returned by the Camera app. If you need to use a FileEntry object, set the `destinationType` to `Camera.DestinationType.FILE_URI` in your CameraOptions object (this is also the default value).
|
||||
If you want to do something like copy the image to another location, or upload it somewhere, an `FileEntry` is needed for the returned picture. To do this, call `window.resolveLocalFileSystemURL` on the file URI returned by the Camera app. If you need to use a FileEntry object, set the `destinationType` to `Camera.DestinationType.FILE_URI` in your CameraOptions object (this is also the default value).
|
||||
|
||||
>*Note* You need the [File plugin](https://www.npmjs.com/package/cordova-plugin-file) to call `window.resolveLocalFileSystemURL`.
|
||||
__NOTE:__ You need the [File plugin](https://www.npmjs.com/package/cordova-plugin-file) to call `window.resolveLocalFileSystemURL`.
|
||||
|
||||
Here is the call to `window.resolveLocalFileSystemURL`. The image URI is passed to this function from the success callback of `getPicture`. The success handler of `resolveLocalFileSystemURL` receives the FileEntry object.
|
||||
|
||||
@@ -649,39 +633,26 @@ Here is the call to `window.resolveLocalFileSystemURL`. The image URI is passed
|
||||
function getFileEntry(imgUri) {
|
||||
window.resolveLocalFileSystemURL(imgUri, function success(fileEntry) {
|
||||
|
||||
// Do something with the FileEntry object, like write to it, upload it, etc.
|
||||
// writeFile(fileEntry, imgUri);
|
||||
console.log("got file: " + fileEntry.fullPath);
|
||||
// displayFileData(fileEntry.nativeURL, "Native URL");
|
||||
// Example 1: Copy to app data directory
|
||||
window.resolveLocalFileSystemURL(cordova.file.dataDirectory, function (dataDirectoryEntry) {
|
||||
fileEntry.copyTo(dataDirectoryEntry, "profilePic", onSuccess, onError);
|
||||
}, onError);
|
||||
|
||||
}, function () {
|
||||
// If don't get the FileEntry (which may happen when testing
|
||||
// on some emulators), copy to a new FileEntry.
|
||||
createNewFileEntry(imgUri);
|
||||
});
|
||||
}
|
||||
```
|
||||
|
||||
In the example shown in the preceding code, you call the app's `createNewFileEntry` function if you don't get a valid FileEntry object. The image URI returned from the Camera app should result in a valid FileEntry, but platform behavior on some emulators may be different for files returned from the file picker.
|
||||
|
||||
>*Note* To see an example of writing to a FileEntry, see the [File plugin README](https://www.npmjs.com/package/cordova-plugin-file).
|
||||
|
||||
The code shown here creates a file in your app's cache (in sandboxed storage) named `tempFile.jpeg`. With the new FileEntry object, you can copy the image to the file or do something else like upload it.
|
||||
|
||||
```js
|
||||
function createNewFileEntry(imgUri) {
|
||||
window.resolveLocalFileSystemURL(cordova.file.cacheDirectory, function success(dirEntry) {
|
||||
|
||||
// JPEG file
|
||||
dirEntry.getFile("tempFile.jpeg", { create: true, exclusive: false }, function (fileEntry) {
|
||||
|
||||
// Do something with it, like write to it, upload it, etc.
|
||||
// writeFile(fileEntry, imgUri);
|
||||
console.log("got file: " + fileEntry.fullPath);
|
||||
// displayFileData(fileEntry.fullPath, "File copied to");
|
||||
|
||||
}, onErrorCreateFile);
|
||||
|
||||
}, onErrorResolveUrl);
|
||||
// Example 2: Upload it!
|
||||
fileEntry.file(function (file) {
|
||||
var reader = new FileReader();
|
||||
|
||||
reader.onloadend = function() {
|
||||
var xhr = new XMLHttpRequest();
|
||||
xhr.open('POST', 'https://myserver.com/upload');
|
||||
xhr.onload = function () {
|
||||
// All done!
|
||||
};
|
||||
xhr.send(this.result);
|
||||
};
|
||||
|
||||
reader.readAsArrayBuffer(file);
|
||||
}, onError);
|
||||
}, onError);
|
||||
}
|
||||
```
|
||||
|
||||
@@ -20,6 +20,41 @@
|
||||
-->
|
||||
# Release Notes
|
||||
|
||||
### 8.0.0 (Oct 30, 2024)
|
||||
|
||||
**Breaking Changes:**
|
||||
|
||||
* [GH-889](https://github.com/apache/cordova-plugin-camera/pull/889) fix(android): Remove media permissions to make complaint with **Android** 14 requirements (#889)
|
||||
* [GH-902](https://github.com/apache/cordova-plugin-camera/pull/902) fix(android): return content uris when possible when selecting from gallery (#902)
|
||||
* [GH-909](https://github.com/apache/cordova-plugin-camera/pull/909) refactor(android): Make WRITE_EXTERNAL_STORAGE optional (#909)
|
||||
* [GH-910](https://github.com/apache/cordova-plugin-camera/pull/910) fix(android): Return data uris as an URI (#910)
|
||||
* [GH-911](https://github.com/apache/cordova-plugin-camera/pull/911) fix(ios): Sync camera API return to match **Android** changes (#911)
|
||||
* [GH-912](https://github.com/apache/cordova-plugin-camera/pull/912) fix(browser): Make data uri be returned as actual URI strings (#912)
|
||||
|
||||
**Fixes**:
|
||||
|
||||
* [GH-901](https://github.com/apache/cordova-plugin-camera/pull/901) fix(android): Isolate provider access to a subdirectory (#901)
|
||||
* [GH-915](https://github.com/apache/cordova-plugin-camera/pull/903) fix(android): Improper serialization of image uri in save instance state (#903)
|
||||
* [GH-904](https://github.com/apache/cordova-plugin-camera/pull/904) fix(android): Use VERSION_CODES instead of hard-coded API literals (#904)
|
||||
* [GH-915](https://github.com/apache/cordova-plugin-camera/pull/905) fix(android): improper cache path construction during image manipulation (#905)
|
||||
* [GH-906](https://github.com/apache/cordova-plugin-camera/pull/906) refactor(android): replace image path usage with image uris (#906)
|
||||
* [GH-915](https://github.com/apache/cordova-plugin-camera/pull/907) refactor(android): remove query img usage (#907)
|
||||
* [GH-915](https://github.com/apache/cordova-plugin-camera/pull/915) fix!: Remove WRITE_EXTERNAL_PERMISSION (#915)
|
||||
|
||||
**CI**:
|
||||
|
||||
* [GH-890](https://github.com/apache/cordova-plugin-camera/pull/890) ci(android): Update **Android** CI to be compatible with `cordova-android`@13 (#890)
|
||||
* [GH-895](https://github.com/apache/cordova-plugin-camera/pull/895) ci: sync workflow with paramedic (#895)
|
||||
|
||||
**Documentation**:
|
||||
|
||||
* [GH-913](https://github.com/apache/cordova-plugin-camera/pull/913) docs: Revisions for v8 public API changes with the return string formats of getPicture (#913)
|
||||
|
||||
**Other**:
|
||||
|
||||
* [GH-898](https://github.com/apache/cordova-plugin-camera/pull/898) chore: Update eslint config to 5.1.0 (#898)
|
||||
* [GH-914](https://github.com/apache/cordova-plugin-camera/pull/914) deprecation: allowEdit (#914)
|
||||
|
||||
### 7.0.0 (Sep 06, 2023)
|
||||
|
||||
**Breaking Changes:**
|
||||
|
||||
35
package-lock.json
generated
35
package-lock.json
generated
@@ -1,18 +1,21 @@
|
||||
{
|
||||
"name": "cordova-plugin-camera",
|
||||
"version": "7.0.0",
|
||||
"version": "9.0.0-dev",
|
||||
"lockfileVersion": 2,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "cordova-plugin-camera",
|
||||
"version": "7.0.0",
|
||||
"version": "9.0.0-dev",
|
||||
"license": "Apache-2.0",
|
||||
"devDependencies": {
|
||||
"@cordova/eslint-config": "^5.0.0"
|
||||
"@cordova/eslint-config": "^5.1.0"
|
||||
},
|
||||
"engines": {
|
||||
"cordovaDependencies": {
|
||||
"10.0.0": {
|
||||
"cordova": ">100"
|
||||
},
|
||||
"3.0.0": {
|
||||
"cordova-android": ">=6.3.0"
|
||||
},
|
||||
@@ -25,11 +28,6 @@
|
||||
"cordova-android": ">=9.0.0",
|
||||
"cordova-ios": ">=5.1.0"
|
||||
},
|
||||
"5.0.4-dev": {
|
||||
"cordova": ">=9.0.0",
|
||||
"cordova-android": "<10.0.0",
|
||||
"cordova-ios": ">=5.1.0"
|
||||
},
|
||||
"6.0.0": {
|
||||
"cordova": ">=9.0.0",
|
||||
"cordova-android": ">=10.0.0",
|
||||
@@ -41,7 +39,14 @@
|
||||
"cordova-ios": ">=5.1.0"
|
||||
},
|
||||
"8.0.0": {
|
||||
"cordova": ">100"
|
||||
"cordova": ">=9.0.0",
|
||||
"cordova-android": ">=12.0.0",
|
||||
"cordova-ios": ">=5.1.0"
|
||||
},
|
||||
"9.0.0": {
|
||||
"cordova": ">=9.0.0",
|
||||
"cordova-android": ">=12.0.0",
|
||||
"cordova-ios": ">=7.0.0"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -56,9 +61,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@cordova/eslint-config": {
|
||||
"version": "5.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@cordova/eslint-config/-/eslint-config-5.0.0.tgz",
|
||||
"integrity": "sha512-tBSV8LbT6RjWsO2lSp45Y+zU7hfXhGMGhfYTZjDrjzli87WOgE6IAS37k6F45JNoGq1XlCJEVkCWwz4KCF8Scw==",
|
||||
"version": "5.1.0",
|
||||
"resolved": "https://registry.npmjs.org/@cordova/eslint-config/-/eslint-config-5.1.0.tgz",
|
||||
"integrity": "sha512-9Da72mSQli08ylGf6jYKWJo67LSu6HWlDPELJsW+bVVos3b0ZMxXsHUCluwrlmZ+sxCFq7VhxAFjK+2/YQFncw==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"eslint": "^8.31.0",
|
||||
@@ -2494,9 +2499,9 @@
|
||||
"dev": true
|
||||
},
|
||||
"@cordova/eslint-config": {
|
||||
"version": "5.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@cordova/eslint-config/-/eslint-config-5.0.0.tgz",
|
||||
"integrity": "sha512-tBSV8LbT6RjWsO2lSp45Y+zU7hfXhGMGhfYTZjDrjzli87WOgE6IAS37k6F45JNoGq1XlCJEVkCWwz4KCF8Scw==",
|
||||
"version": "5.1.0",
|
||||
"resolved": "https://registry.npmjs.org/@cordova/eslint-config/-/eslint-config-5.1.0.tgz",
|
||||
"integrity": "sha512-9Da72mSQli08ylGf6jYKWJo67LSu6HWlDPELJsW+bVVos3b0ZMxXsHUCluwrlmZ+sxCFq7VhxAFjK+2/YQFncw==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"eslint": "^8.31.0",
|
||||
|
||||
19
package.json
19
package.json
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "cordova-plugin-camera",
|
||||
"version": "7.0.0",
|
||||
"version": "9.0.0-dev",
|
||||
"description": "Cordova Camera Plugin",
|
||||
"types": "./types/index.d.ts",
|
||||
"cordova": {
|
||||
@@ -41,11 +41,6 @@
|
||||
"cordova-ios": ">=5.1.0",
|
||||
"cordova": ">=9.0.0"
|
||||
},
|
||||
"5.0.4-dev": {
|
||||
"cordova-android": "<10.0.0",
|
||||
"cordova-ios": ">=5.1.0",
|
||||
"cordova": ">=9.0.0"
|
||||
},
|
||||
"6.0.0": {
|
||||
"cordova-android": ">=10.0.0",
|
||||
"cordova-ios": ">=5.1.0",
|
||||
@@ -57,11 +52,21 @@
|
||||
"cordova": ">=9.0.0"
|
||||
},
|
||||
"8.0.0": {
|
||||
"cordova-android": ">=12.0.0",
|
||||
"cordova-ios": ">=5.1.0",
|
||||
"cordova": ">=9.0.0"
|
||||
},
|
||||
"9.0.0": {
|
||||
"cordova-android": ">=12.0.0",
|
||||
"cordova-ios": ">=7.0.0",
|
||||
"cordova": ">=9.0.0"
|
||||
},
|
||||
"10.0.0": {
|
||||
"cordova": ">100"
|
||||
}
|
||||
}
|
||||
},
|
||||
"devDependencies": {
|
||||
"@cordova/eslint-config": "^5.0.0"
|
||||
"@cordova/eslint-config": "^5.1.0"
|
||||
}
|
||||
}
|
||||
|
||||
21
plugin.xml
21
plugin.xml
@@ -21,7 +21,7 @@
|
||||
<plugin xmlns="http://apache.org/cordova/ns/plugins/1.0"
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
id="cordova-plugin-camera"
|
||||
version="7.0.0">
|
||||
version="9.0.0-dev">
|
||||
<name>Camera</name>
|
||||
<description>Cordova Camera Plugin</description>
|
||||
<license>Apache 2.0</license>
|
||||
@@ -32,17 +32,13 @@
|
||||
<engines>
|
||||
<engine name="cordova" version=">=9.0.0"/>
|
||||
<engine name="cordova-android" version=">=12.0.0" />
|
||||
<engine name="cordova-ios" version=">=5.1.0" />
|
||||
<engine name="cordova-ios" version=">=7.0.0" />
|
||||
</engines>
|
||||
|
||||
<js-module src="www/CameraConstants.js" name="Camera">
|
||||
<clobbers target="Camera" />
|
||||
</js-module>
|
||||
|
||||
<js-module src="www/CameraPopoverOptions.js" name="CameraPopoverOptions">
|
||||
<clobbers target="CameraPopoverOptions" />
|
||||
</js-module>
|
||||
|
||||
<js-module src="www/Camera.js" name="camera">
|
||||
<clobbers target="navigator.camera" />
|
||||
</js-module>
|
||||
@@ -54,11 +50,6 @@
|
||||
<param name="android-package" value="org.apache.cordova.camera.CameraLauncher"/>
|
||||
</feature>
|
||||
</config-file>
|
||||
<config-file target="AndroidManifest.xml" parent="/*">
|
||||
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" android:maxSdkVersion="32" />
|
||||
<uses-permission android:name="android.permission.READ_MEDIA_IMAGES" />
|
||||
<uses-permission android:name="android.permission.READ_MEDIA_VIDEO" />
|
||||
</config-file>
|
||||
<config-file target="AndroidManifest.xml" parent="application">
|
||||
<provider
|
||||
android:name="org.apache.cordova.camera.FileProvider"
|
||||
@@ -96,10 +87,6 @@
|
||||
|
||||
<preference name="ANDROIDX_CORE_VERSION" default="1.6.+"/>
|
||||
<framework src="androidx.core:core:$ANDROIDX_CORE_VERSION" />
|
||||
|
||||
<js-module src="www/CameraPopoverHandle.js" name="CameraPopoverHandle">
|
||||
<clobbers target="CameraPopoverHandle" />
|
||||
</js-module>
|
||||
</platform>
|
||||
|
||||
<!-- ios -->
|
||||
@@ -111,10 +98,6 @@
|
||||
<preference name="CameraUsesGeolocation" value="false" />
|
||||
</config-file>
|
||||
|
||||
<js-module src="www/ios/CameraPopoverHandle.js" name="CameraPopoverHandle">
|
||||
<clobbers target="CameraPopoverHandle" />
|
||||
</js-module>
|
||||
|
||||
<header-file src="src/ios/UIImage+CropScaleOrientation.h" />
|
||||
<source-file src="src/ios/UIImage+CropScaleOrientation.m" />
|
||||
<header-file src="src/ios/CDVCamera.h" />
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -19,6 +19,7 @@
|
||||
package org.apache.cordova.camera;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
|
||||
import android.media.ExifInterface;
|
||||
|
||||
@@ -56,6 +57,16 @@ public class ExifHelper {
|
||||
this.inFile = new ExifInterface(filePath);
|
||||
}
|
||||
|
||||
/**
|
||||
* The file before it is compressed
|
||||
*
|
||||
* @param input
|
||||
* @throws IOException
|
||||
*/
|
||||
public void createInFile(InputStream input) throws IOException {
|
||||
this.inFile = new ExifInterface(input);
|
||||
}
|
||||
|
||||
/**
|
||||
* The file after it has been compressed
|
||||
*
|
||||
|
||||
@@ -17,5 +17,5 @@
|
||||
-->
|
||||
|
||||
<paths xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<cache-path name="cache_files" path="." />
|
||||
</paths>
|
||||
<cache-path name="cache_files" path="org.apache.cordova.camera/" />
|
||||
</paths>
|
||||
|
||||
@@ -39,7 +39,7 @@ function takePicture (success, error, opts) {
|
||||
|
||||
const imageData = readerEvent.target.result;
|
||||
|
||||
return success(imageData.substr(imageData.indexOf(',') + 1));
|
||||
return success(imageData);
|
||||
};
|
||||
|
||||
reader.readAsDataURL(inputEvent.target.files[0]);
|
||||
@@ -78,8 +78,7 @@ function capture (success, errorCallback, opts) {
|
||||
canvas.getContext('2d').drawImage(video, 0, 0, targetWidth, targetHeight);
|
||||
|
||||
// convert image stored in canvas to base64 encoded image
|
||||
let imageData = canvas.toDataURL('image/png');
|
||||
imageData = imageData.replace('data:image/png;base64,', '');
|
||||
const imageData = canvas.toDataURL('image/png');
|
||||
|
||||
// stop video stream, remove video and button.
|
||||
// Note that MediaStream.stop() is deprecated as of Chrome 47.
|
||||
|
||||
@@ -22,6 +22,26 @@
|
||||
#import <CoreLocation/CLLocationManager.h>
|
||||
#import <Cordova/CDVPlugin.h>
|
||||
|
||||
// Since iOS 14, we can use PHPickerViewController to select images from the photo library
|
||||
//
|
||||
// The following condition checks if the iOS 14 SDK is available for XCode
|
||||
// which is true for XCode 12+. It does not check on runtime, if the device is running iOS 14+.
|
||||
// For that API_AVAILABLE(ios(14)) is used for methods declarations and @available(iOS 14, *) for the code.
|
||||
// The condition here makes just sure that the code can compile in XCode
|
||||
#if __IPHONE_OS_VERSION_MAX_ALLOWED >= 140000 // Always true on XCode12+
|
||||
|
||||
// Import UniformTypeIdentifiers.h for using UTType* things, available since iOS 14,
|
||||
// which replaces for e.g. kUTTypeImage with UTTypeImage, which must be used in the future instead
|
||||
// Currently only used for PHPickerViewController
|
||||
#import <UniformTypeIdentifiers/UniformTypeIdentifiers.h>
|
||||
|
||||
// Import PhotosUI framework for using PHPickerViewController
|
||||
// PhotosUI is already available since iOS 8, but since we need it currently
|
||||
// only for the PHPickerViewController, we import it conditionally here
|
||||
#import <PhotosUI/PhotosUI.h>
|
||||
|
||||
#endif
|
||||
|
||||
enum CDVDestinationType {
|
||||
DestinationTypeDataUrl = 0,
|
||||
DestinationTypeFileUri
|
||||
@@ -52,10 +72,8 @@ typedef NSUInteger CDVMediaType;
|
||||
@property (assign) BOOL allowsEditing;
|
||||
@property (assign) BOOL correctOrientation;
|
||||
@property (assign) BOOL saveToPhotoAlbum;
|
||||
@property (strong) NSDictionary* popoverOptions;
|
||||
@property (assign) UIImagePickerControllerCameraDevice cameraDirection;
|
||||
|
||||
@property (assign) BOOL popoverSupported;
|
||||
@property (assign) BOOL usesGeolocation;
|
||||
@property (assign) BOOL cropToSize;
|
||||
|
||||
@@ -69,7 +87,6 @@ typedef NSUInteger CDVMediaType;
|
||||
|
||||
@property (copy) NSString* callbackId;
|
||||
@property (copy) NSString* postUrl;
|
||||
@property (strong) UIPopoverController* pickerPopoverController;
|
||||
@property (assign) BOOL cropToSize;
|
||||
@property (strong) UIView* webView;
|
||||
|
||||
@@ -78,38 +95,48 @@ typedef NSUInteger CDVMediaType;
|
||||
@end
|
||||
|
||||
// ======================================================================= //
|
||||
|
||||
// Use PHPickerViewController in iOS 14+ to select images from the photo library
|
||||
// PHPickerViewControllerDelegate is only available since iOS 14
|
||||
#if __IPHONE_OS_VERSION_MAX_ALLOWED >= 140000 // Always true on XCode12+
|
||||
@interface CDVCamera : CDVPlugin <UIImagePickerControllerDelegate,
|
||||
UINavigationControllerDelegate,
|
||||
CLLocationManagerDelegate,
|
||||
PHPickerViewControllerDelegate>
|
||||
{}
|
||||
#else
|
||||
@interface CDVCamera : CDVPlugin <UIImagePickerControllerDelegate,
|
||||
UINavigationControllerDelegate,
|
||||
UIPopoverControllerDelegate,
|
||||
CLLocationManagerDelegate>
|
||||
{}
|
||||
#endif
|
||||
|
||||
@property (strong) CDVCameraPicker* pickerController;
|
||||
@property (strong) NSMutableDictionary *metadata;
|
||||
@property (strong, nonatomic) CLLocationManager *locationManager;
|
||||
@property (strong) NSData* data;
|
||||
|
||||
/*
|
||||
* getPicture
|
||||
*
|
||||
* arguments:
|
||||
* 1: this is the javascript function that will be called with the results, the first parameter passed to the
|
||||
* javascript function is the picture as a Base64 encoded string
|
||||
* 2: this is the javascript function to be called if there was an error
|
||||
* options:
|
||||
* quality: integer between 1 and 100
|
||||
*/
|
||||
- (void)takePicture:(CDVInvokedUrlCommand*)command;
|
||||
- (void)cleanup:(CDVInvokedUrlCommand*)command;
|
||||
- (void)repositionPopover:(CDVInvokedUrlCommand*)command;
|
||||
|
||||
// UIImagePickerControllerDelegate methods
|
||||
- (void)imagePickerController:(UIImagePickerController*)picker didFinishPickingMediaWithInfo:(NSDictionary*)info;
|
||||
- (void)imagePickerController:(UIImagePickerController*)picker didFinishPickingImage:(UIImage*)image editingInfo:(NSDictionary*)editingInfo;
|
||||
- (void)imagePickerControllerDidCancel:(UIImagePickerController*)picker;
|
||||
|
||||
// UINavigationControllerDelegate method
|
||||
- (void)navigationController:(UINavigationController *)navigationController willShowViewController:(UIViewController *)viewController animated:(BOOL)animated;
|
||||
|
||||
// CLLocationManagerDelegate methods
|
||||
- (void)locationManager:(CLLocationManager*)manager didUpdateToLocation:(CLLocation*)newLocation fromLocation:(CLLocation*)oldLocation;
|
||||
- (void)locationManager:(CLLocationManager *)manager didFailWithError:(NSError *)error;
|
||||
|
||||
// PHPickerViewController specific methods (iOS 14+)
|
||||
#if __IPHONE_OS_VERSION_MAX_ALLOWED >= 140000 // Always true on XCode12+
|
||||
- (void)showPHPicker:(NSString*)callbackId withOptions:(CDVPictureOptions*)pictureOptions API_AVAILABLE(ios(14));
|
||||
- (void)processPHPickerImage:(UIImage*)image assetIdentifier:(NSString*)assetIdentifier callbackId:(NSString*)callbackId options:(CDVPictureOptions*)options API_AVAILABLE(ios(14));
|
||||
- (void)finalizePHPickerImage:(UIImage*)image metadata:(NSDictionary*)metadata callbackId:(NSString*)callbackId options:(CDVPictureOptions*)options API_AVAILABLE(ios(14));
|
||||
// PHPickerViewControllerDelegate method
|
||||
- (void)picker:(PHPickerViewController *)picker didFinishPicking:(NSArray<PHPickerResult *> *)results API_AVAILABLE(ios(14));
|
||||
#endif
|
||||
|
||||
@end
|
||||
|
||||
@@ -37,9 +37,8 @@
|
||||
|
||||
#define CDV_PHOTO_PREFIX @"cdv_photo_"
|
||||
|
||||
static NSSet* org_apache_cordova_validArrowDirections;
|
||||
|
||||
static NSString* toBase64(NSData* data) {
|
||||
static NSString* toBase64(NSData* data)
|
||||
{
|
||||
SEL s1 = NSSelectorFromString(@"cdv_base64EncodedString");
|
||||
SEL s2 = NSSelectorFromString(@"base64EncodedString");
|
||||
SEL s3 = NSSelectorFromString(@"base64EncodedStringWithOptions:");
|
||||
@@ -58,9 +57,12 @@ static NSString* toBase64(NSData* data) {
|
||||
}
|
||||
}
|
||||
|
||||
static NSString* MIME_PNG = @"image/png";
|
||||
static NSString* MIME_JPEG = @"image/jpeg";
|
||||
|
||||
@implementation CDVPictureOptions
|
||||
|
||||
+ (instancetype) createFromTakePictureArguments:(CDVInvokedUrlCommand*)command
|
||||
+ (instancetype)createFromTakePictureArguments:(CDVInvokedUrlCommand*)command
|
||||
{
|
||||
CDVPictureOptions* pictureOptions = [[CDVPictureOptions alloc] init];
|
||||
|
||||
@@ -80,10 +82,8 @@ static NSString* toBase64(NSData* data) {
|
||||
pictureOptions.allowsEditing = [[command argumentAtIndex:7 withDefault:@(NO)] boolValue];
|
||||
pictureOptions.correctOrientation = [[command argumentAtIndex:8 withDefault:@(NO)] boolValue];
|
||||
pictureOptions.saveToPhotoAlbum = [[command argumentAtIndex:9 withDefault:@(NO)] boolValue];
|
||||
pictureOptions.popoverOptions = [command argumentAtIndex:10 withDefault:nil];
|
||||
pictureOptions.cameraDirection = [[command argumentAtIndex:11 withDefault:@(UIImagePickerControllerCameraDeviceRear)] unsignedIntegerValue];
|
||||
pictureOptions.cameraDirection = [[command argumentAtIndex:10 withDefault:@(UIImagePickerControllerCameraDeviceRear)] unsignedIntegerValue];
|
||||
|
||||
pictureOptions.popoverSupported = NO;
|
||||
pictureOptions.usesGeolocation = NO;
|
||||
|
||||
return pictureOptions;
|
||||
@@ -100,14 +100,9 @@ static NSString* toBase64(NSData* data) {
|
||||
|
||||
@implementation CDVCamera
|
||||
|
||||
+ (void)initialize
|
||||
{
|
||||
org_apache_cordova_validArrowDirections = [[NSSet alloc] initWithObjects:[NSNumber numberWithInt:UIPopoverArrowDirectionUp], [NSNumber numberWithInt:UIPopoverArrowDirectionDown], [NSNumber numberWithInt:UIPopoverArrowDirectionLeft], [NSNumber numberWithInt:UIPopoverArrowDirectionRight], [NSNumber numberWithInt:UIPopoverArrowDirectionAny], nil];
|
||||
}
|
||||
|
||||
@synthesize hasPendingOperation, pickerController, locationManager;
|
||||
|
||||
- (NSURL*) urlTransformer:(NSURL*)url
|
||||
- (NSURL*)urlTransformer:(NSURL*)url
|
||||
{
|
||||
NSURL* urlToTransform = url;
|
||||
|
||||
@@ -131,12 +126,28 @@ static NSString* toBase64(NSData* data) {
|
||||
return [(NSNumber*)useGeo boolValue];
|
||||
}
|
||||
|
||||
- (BOOL)popoverSupported
|
||||
{
|
||||
return (NSClassFromString(@"UIPopoverController") != nil) &&
|
||||
(UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad);
|
||||
}
|
||||
/**
|
||||
Called by JS function navigator.camera.getPicture(cameraSuccess, cameraError, cameraOptions)
|
||||
which will invoke the camera or photo picker to capture or select an image or video.
|
||||
|
||||
@param command A Cordova command whose arguments map to camera options:
|
||||
- index 0 (quality): NSNumber (1–100). JPEG quality when encodingType is JPEG. Default: 50.
|
||||
- index 1 (destinationType): NSNumber (DestinationType). File URI or Data URL. Default: File URI.
|
||||
- index 2 (sourceType): NSNumber (UIImagePickerControllerSourceType). Camera or Photo Library. Default: Camera.
|
||||
- index 3 (targetWidth): NSNumber (optional). Desired width for scaling/cropping.
|
||||
- index 4 (targetHeight): NSNumber (optional). Desired height for scaling/cropping.
|
||||
- index 5 (encodingType): NSNumber (EncodingType). JPEG or PNG. Default: JPEG.
|
||||
- index 6 (mediaType): NSNumber (MediaType). Picture, Video, or All. Default: Picture.
|
||||
- index 7 (allowsEditing): NSNumber(BOOL). Allow user to crop/edit. Default: NO.
|
||||
- index 8 (correctOrientation): NSNumber(BOOL). Fix EXIF orientation. Default: NO.
|
||||
- index 9 (saveToPhotoAlbum): NSNumber(BOOL). Save captured image to Photos. Default: NO.
|
||||
- index 10 (cameraDirection): NSNumber (UIImagePickerControllerCameraDevice). Front/Rear. Default: Rear.
|
||||
|
||||
@discussion
|
||||
This method validates hardware availability and permissions (camera or photo library),
|
||||
then presents the appropriate UI (UIImagePickerController or PHPickerViewController on iOS 14+).
|
||||
The result is returned via the Cordova callback.
|
||||
*/
|
||||
- (void)takePicture:(CDVInvokedUrlCommand*)command
|
||||
{
|
||||
self.hasPendingOperation = YES;
|
||||
@@ -144,98 +155,90 @@ static NSString* toBase64(NSData* data) {
|
||||
|
||||
[self.commandDelegate runInBackground:^{
|
||||
CDVPictureOptions* pictureOptions = [CDVPictureOptions createFromTakePictureArguments:command];
|
||||
pictureOptions.popoverSupported = [weakSelf popoverSupported];
|
||||
pictureOptions.usesGeolocation = [weakSelf usesGeolocation];
|
||||
pictureOptions.cropToSize = NO;
|
||||
|
||||
BOOL hasCamera = [UIImagePickerController isSourceTypeAvailable:pictureOptions.sourceType];
|
||||
if (!hasCamera) {
|
||||
NSLog(@"Camera.getPicture: source type %lu not available.", (unsigned long)pictureOptions.sourceType);
|
||||
CDVPluginResult* result = [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR messageAsString:@"No camera available"];
|
||||
[weakSelf.commandDelegate sendPluginResult:result callbackId:command.callbackId];
|
||||
return;
|
||||
}
|
||||
|
||||
// Validate the app has permission to access the camera
|
||||
// The camera should be used to take a picture
|
||||
if (pictureOptions.sourceType == UIImagePickerControllerSourceTypeCamera) {
|
||||
[AVCaptureDevice requestAccessForMediaType:AVMediaTypeVideo completionHandler:^(BOOL granted)
|
||||
{
|
||||
if (!granted)
|
||||
{
|
||||
// Denied; show an alert
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
UIAlertController *alertController = [UIAlertController alertControllerWithTitle:[[NSBundle mainBundle] objectForInfoDictionaryKey:@"CFBundleDisplayName"] message:NSLocalizedString(@"Access to the camera has been prohibited; please enable it in the Settings app to continue.", nil) preferredStyle:UIAlertControllerStyleAlert];
|
||||
[alertController addAction:[UIAlertAction actionWithTitle:NSLocalizedString(@"OK", nil) style:UIAlertActionStyleDefault handler:^(UIAlertAction * _Nonnull action) {
|
||||
[weakSelf sendNoPermissionResult:command.callbackId];
|
||||
}]];
|
||||
[alertController addAction:[UIAlertAction actionWithTitle:NSLocalizedString(@"Settings", nil) style:UIAlertActionStyleDefault handler:^(UIAlertAction * _Nonnull action) {
|
||||
[[UIApplication sharedApplication] openURL:[NSURL URLWithString:UIApplicationOpenSettingsURLString] options:@{} completionHandler:nil];
|
||||
[weakSelf sendNoPermissionResult:command.callbackId];
|
||||
}]];
|
||||
[weakSelf.viewController presentViewController:alertController animated:YES completion:nil];
|
||||
});
|
||||
// Check if camera is available
|
||||
if (![UIImagePickerController isSourceTypeAvailable:pictureOptions.sourceType]) {
|
||||
NSLog(@"Camera.getPicture: source type %lu not available.", (unsigned long)pictureOptions.sourceType);
|
||||
[weakSelf.commandDelegate sendPluginResult:[CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR messageAsString:@"No camera available"]
|
||||
callbackId:command.callbackId];
|
||||
return;
|
||||
}
|
||||
|
||||
// Validate the app has permission to access the camera
|
||||
[AVCaptureDevice requestAccessForMediaType:AVMediaTypeVideo completionHandler:^(BOOL granted) {
|
||||
// Show an alert if not granted
|
||||
if (!granted) {
|
||||
[weakSelf presentPermissionDeniedAlertWithMessage:@"Access to the camera has been prohibited; please enable it in the Settings app to continue."
|
||||
callbackId:command.callbackId];
|
||||
} else {
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
[weakSelf showCameraPicker:command.callbackId withOptions:pictureOptions];
|
||||
});
|
||||
[weakSelf showCameraPicker:command.callbackId withOptions:pictureOptions];
|
||||
}
|
||||
}];
|
||||
|
||||
// A photo should be picked from the photo library
|
||||
} else {
|
||||
[weakSelf options:pictureOptions requestPhotoPermissions:^(BOOL granted) {
|
||||
if (!granted) {
|
||||
// Denied; show an alert
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
UIAlertController *alertController = [UIAlertController alertControllerWithTitle:[[NSBundle mainBundle] objectForInfoDictionaryKey:@"CFBundleDisplayName"] message:NSLocalizedString(@"Access to the camera roll has been prohibited; please enable it in the Settings to continue.", nil) preferredStyle:UIAlertControllerStyleAlert];
|
||||
[alertController addAction:[UIAlertAction actionWithTitle:NSLocalizedString(@"OK", nil) style:UIAlertActionStyleDefault handler:^(UIAlertAction * _Nonnull action) {
|
||||
[weakSelf sendNoPermissionResult:command.callbackId];
|
||||
}]];
|
||||
[alertController addAction:[UIAlertAction actionWithTitle:NSLocalizedString(@"Settings", nil) style:UIAlertActionStyleDefault handler:^(UIAlertAction * _Nonnull action) {
|
||||
[[UIApplication sharedApplication] openURL:[NSURL URLWithString:UIApplicationOpenSettingsURLString]];
|
||||
[weakSelf sendNoPermissionResult:command.callbackId];
|
||||
}]];
|
||||
[weakSelf.viewController presentViewController:alertController animated:YES completion:nil];
|
||||
});
|
||||
} else {
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
// Use PHPickerViewController on iOS 14+
|
||||
// Doesn't require permissions
|
||||
if (@available(iOS 14, *)) {
|
||||
[weakSelf showCameraPicker:command.callbackId withOptions:pictureOptions];
|
||||
|
||||
// On iOS < 14, use UIImagePickerController and request permissions
|
||||
} else {
|
||||
// Request permission
|
||||
[weakSelf options:pictureOptions requestPhotoPermissions:^(BOOL granted) {
|
||||
if (!granted) {
|
||||
// Denied; show an alert
|
||||
[weakSelf presentPermissionDeniedAlertWithMessage:@"Access to the camera roll has been prohibited; please enable it in the Settings to continue."
|
||||
callbackId:command.callbackId];
|
||||
} else {
|
||||
[weakSelf showCameraPicker:command.callbackId withOptions:pictureOptions];
|
||||
});
|
||||
}
|
||||
}];
|
||||
}
|
||||
}];
|
||||
}
|
||||
}
|
||||
}];
|
||||
}
|
||||
|
||||
- (void)showCameraPicker:(NSString*)callbackId withOptions:(CDVPictureOptions *) pictureOptions
|
||||
/**
|
||||
Presents a permission denial alert with OK and Settings actions.
|
||||
@param message The alert message to show.
|
||||
@param callbackId The Cordova callback identifier to send an error if needed.
|
||||
*/
|
||||
- (void)presentPermissionDeniedAlertWithMessage:(NSString*)message callbackId:(NSString*)callbackId
|
||||
{
|
||||
// Perform UI operations on the main thread
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
CDVCameraPicker* cameraPicker = [CDVCameraPicker createFromPictureOptions:pictureOptions];
|
||||
self.pickerController = cameraPicker;
|
||||
|
||||
NSString *bundleDisplayName = [[NSBundle mainBundle] objectForInfoDictionaryKey:@"CFBundleDisplayName"];
|
||||
UIAlertController *alertController = [UIAlertController alertControllerWithTitle:bundleDisplayName
|
||||
message:NSLocalizedString(message, nil)
|
||||
preferredStyle:UIAlertControllerStyleAlert];
|
||||
|
||||
// Add buttons
|
||||
__weak CDVCamera *weakSelf = self;
|
||||
|
||||
// Ok button
|
||||
[alertController addAction:[UIAlertAction actionWithTitle:NSLocalizedString(@"OK", nil)
|
||||
style:UIAlertActionStyleDefault
|
||||
handler:^(UIAlertAction * _Nonnull action) {
|
||||
[weakSelf sendNoPermissionResult:callbackId];
|
||||
}]];
|
||||
|
||||
// Button for open settings
|
||||
[alertController addAction:[UIAlertAction actionWithTitle:NSLocalizedString(@"Settings", nil)
|
||||
style:UIAlertActionStyleDefault
|
||||
handler:^(UIAlertAction * _Nonnull action) {
|
||||
// Open settings
|
||||
[[UIApplication sharedApplication] openURL:[NSURL URLWithString:UIApplicationOpenSettingsURLString]
|
||||
options:@{}
|
||||
completionHandler:nil];
|
||||
[weakSelf sendNoPermissionResult:callbackId];
|
||||
}]];
|
||||
|
||||
cameraPicker.delegate = self;
|
||||
cameraPicker.callbackId = callbackId;
|
||||
// we need to capture this state for memory warnings that dealloc this object
|
||||
cameraPicker.webView = self.webView;
|
||||
|
||||
// If a popover is already open, close it; we only want one at a time.
|
||||
if (([[self pickerController] pickerPopoverController] != nil) && [[[self pickerController] pickerPopoverController] isPopoverVisible]) {
|
||||
[[[self pickerController] pickerPopoverController] dismissPopoverAnimated:YES];
|
||||
[[[self pickerController] pickerPopoverController] setDelegate:nil];
|
||||
[[self pickerController] setPickerPopoverController:nil];
|
||||
}
|
||||
|
||||
if ([self popoverSupported] && (pictureOptions.sourceType != UIImagePickerControllerSourceTypeCamera)) {
|
||||
if (cameraPicker.pickerPopoverController == nil) {
|
||||
cameraPicker.pickerPopoverController = [[NSClassFromString(@"UIPopoverController") alloc] initWithContentViewController:cameraPicker];
|
||||
}
|
||||
[self displayPopover:pictureOptions.popoverOptions];
|
||||
self.hasPendingOperation = NO;
|
||||
} else {
|
||||
cameraPicker.modalPresentationStyle = UIModalPresentationCurrentContext;
|
||||
[self.viewController presentViewController:cameraPicker animated:YES completion:^{
|
||||
self.hasPendingOperation = NO;
|
||||
}];
|
||||
}
|
||||
[self.viewController presentViewController:alertController animated:YES completion:nil];
|
||||
});
|
||||
}
|
||||
|
||||
@@ -244,22 +247,251 @@ static NSString* toBase64(NSData* data) {
|
||||
CDVPluginResult* result = [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR messageAsString:@"has no access to camera"]; // error callback expects string ATM
|
||||
|
||||
[self.commandDelegate sendPluginResult:result callbackId:callbackId];
|
||||
|
||||
self.hasPendingOperation = NO;
|
||||
self.pickerController = nil;
|
||||
}
|
||||
|
||||
- (void)repositionPopover:(CDVInvokedUrlCommand*)command
|
||||
/**
|
||||
Presents the appropriate UI to capture or select media based on the provided options and OS version.
|
||||
|
||||
On iOS 14 and later, when the source type is PHOTOLIBRARY (or SAVEDPHOTOALBUM), this method presents
|
||||
PHPickerViewController to select media without requiring Photos authorization. Otherwise, it falls back
|
||||
to UIImagePickerController for camera usage or on older iOS versions.
|
||||
|
||||
Threading:
|
||||
- Ensures presentation occurs on the main thread.
|
||||
|
||||
Behavior:
|
||||
- Configures delegates and media types
|
||||
- Updates `hasPendingOperation` to reflect plugin activity state.
|
||||
|
||||
@param callbackId The Cordova callback identifier used to deliver results back to JavaScript.
|
||||
@param pictureOptions Parsed camera options (sourceType, mediaType, allowsEditing, etc.).
|
||||
*/
|
||||
- (void)showCameraPicker:(NSString*)callbackId withOptions:(CDVPictureOptions*)pictureOptions
|
||||
{
|
||||
if (([[self pickerController] pickerPopoverController] != nil) && [[[self pickerController] pickerPopoverController] isPopoverVisible]) {
|
||||
// Perform UI operations on the main thread
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
// Use PHPickerViewController for photo library on iOS 14+
|
||||
if (@available(iOS 14, *)) {
|
||||
// sourceType is PHOTOLIBRARY
|
||||
if (pictureOptions.sourceType == UIImagePickerControllerSourceTypePhotoLibrary ||
|
||||
// sourceType is SAVEDPHOTOALBUM (same as PHOTOLIBRARY)
|
||||
pictureOptions.sourceType == UIImagePickerControllerSourceTypeSavedPhotosAlbum) {
|
||||
[self showPHPicker:callbackId withOptions:pictureOptions];
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Use UIImagePickerController for camera or as image picker for iOS older than 14
|
||||
CDVCameraPicker* cameraPicker = [CDVCameraPicker createFromPictureOptions:pictureOptions];
|
||||
self.pickerController = cameraPicker;
|
||||
|
||||
[[[self pickerController] pickerPopoverController] dismissPopoverAnimated:NO];
|
||||
|
||||
NSDictionary* options = [command argumentAtIndex:0 withDefault:nil];
|
||||
[self displayPopover:options];
|
||||
}
|
||||
cameraPicker.delegate = self;
|
||||
cameraPicker.callbackId = callbackId;
|
||||
// we need to capture this state for memory warnings that dealloc this object
|
||||
cameraPicker.webView = self.webView;
|
||||
cameraPicker.modalPresentationStyle = UIModalPresentationCurrentContext;
|
||||
[self.viewController presentViewController:cameraPicker
|
||||
animated:YES
|
||||
completion:^{
|
||||
self.hasPendingOperation = NO;
|
||||
}];
|
||||
});
|
||||
}
|
||||
|
||||
// Since iOS 14, we can use PHPickerViewController to select images from the photo library
|
||||
#if __IPHONE_OS_VERSION_MAX_ALLOWED >= 140000 // Always true on XCode12+
|
||||
- (void)showPHPicker:(NSString*)callbackId withOptions:(CDVPictureOptions*)pictureOptions API_AVAILABLE(ios(14))
|
||||
{
|
||||
PHPickerConfiguration *config = [[PHPickerConfiguration alloc] init];
|
||||
|
||||
// Configure filter based on media type
|
||||
// Images
|
||||
if (pictureOptions.mediaType == MediaTypePicture) {
|
||||
config.filter = [PHPickerFilter imagesFilter];
|
||||
|
||||
// Videos
|
||||
} else if (pictureOptions.mediaType == MediaTypeVideo) {
|
||||
config.filter = [PHPickerFilter videosFilter];
|
||||
|
||||
// Images and videos
|
||||
} else if (pictureOptions.mediaType == MediaTypeAll) {
|
||||
config.filter = [PHPickerFilter anyFilterMatchingSubfilters:@[
|
||||
[PHPickerFilter imagesFilter],
|
||||
[PHPickerFilter videosFilter]
|
||||
]];
|
||||
}
|
||||
|
||||
config.selectionLimit = 1;
|
||||
config.preferredAssetRepresentationMode = PHPickerConfigurationAssetRepresentationModeCurrent;
|
||||
|
||||
PHPickerViewController *picker = [[PHPickerViewController alloc] initWithConfiguration:config];
|
||||
picker.delegate = self;
|
||||
|
||||
// Store callback ID and options in picker with objc_setAssociatedObject
|
||||
// PHPickerViewController’s delegate method picker:didFinishPicking: only gives you back the picker instance
|
||||
// and the results array. It doesn’t carry arbitrary context. By associating the callbackId and pictureOptions
|
||||
// with the picker, you can retrieve them later inside the delegate method
|
||||
objc_setAssociatedObject(picker, "callbackId", callbackId, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
|
||||
objc_setAssociatedObject(picker, "pictureOptions", pictureOptions, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
|
||||
|
||||
[self.viewController presentViewController:picker animated:YES completion:^{
|
||||
self.hasPendingOperation = NO;
|
||||
}];
|
||||
}
|
||||
|
||||
// PHPickerViewControllerDelegate method
|
||||
- (void)picker:(PHPickerViewController*)picker didFinishPicking:(NSArray<PHPickerResult*>*)results API_AVAILABLE(ios(14))
|
||||
{
|
||||
NSString *callbackId = objc_getAssociatedObject(picker, "callbackId");
|
||||
CDVPictureOptions *pictureOptions = objc_getAssociatedObject(picker, "pictureOptions");
|
||||
|
||||
__weak CDVCamera* weakSelf = self;
|
||||
|
||||
[picker dismissViewControllerAnimated:YES completion:^{
|
||||
if (results.count == 0) {
|
||||
// User cancelled
|
||||
CDVPluginResult* result = [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR messageAsString:@"No Image Selected"];
|
||||
[weakSelf.commandDelegate sendPluginResult:result callbackId:callbackId];
|
||||
weakSelf.hasPendingOperation = NO;
|
||||
return;
|
||||
}
|
||||
|
||||
PHPickerResult *pickerResult = results.firstObject;
|
||||
|
||||
// Check if it's a video
|
||||
if ([pickerResult.itemProvider hasItemConformingToTypeIdentifier:UTTypeMovie.identifier]) {
|
||||
[pickerResult.itemProvider loadFileRepresentationForTypeIdentifier:UTTypeMovie.identifier completionHandler:^(NSURL * _Nullable url, NSError * _Nullable error) {
|
||||
if (error) {
|
||||
CDVPluginResult* result = [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR messageAsString:[error localizedDescription]];
|
||||
[weakSelf.commandDelegate sendPluginResult:result callbackId:callbackId];
|
||||
weakSelf.hasPendingOperation = NO;
|
||||
return;
|
||||
}
|
||||
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
NSString* videoPath = [weakSelf createTmpVideo:[url path]];
|
||||
CDVPluginResult* result = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsString:videoPath];
|
||||
[weakSelf.commandDelegate sendPluginResult:result callbackId:callbackId];
|
||||
weakSelf.hasPendingOperation = NO;
|
||||
});
|
||||
}];
|
||||
|
||||
// Handle image
|
||||
} else if ([pickerResult.itemProvider canLoadObjectOfClass:[UIImage class]]) {
|
||||
[pickerResult.itemProvider loadObjectOfClass:[UIImage class] completionHandler:^(__kindof id<NSItemProviderReading> _Nullable object, NSError * _Nullable error) {
|
||||
if (error) {
|
||||
CDVPluginResult* result = [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR messageAsString:[error localizedDescription]];
|
||||
[weakSelf.commandDelegate sendPluginResult:result callbackId:callbackId];
|
||||
weakSelf.hasPendingOperation = NO;
|
||||
return;
|
||||
}
|
||||
|
||||
UIImage *image = (UIImage *)object;
|
||||
|
||||
// Get asset identifier to fetch metadata
|
||||
NSString *assetIdentifier = pickerResult.assetIdentifier;
|
||||
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
[weakSelf processPHPickerImage:image assetIdentifier:assetIdentifier callbackId:callbackId options:pictureOptions];
|
||||
});
|
||||
}];
|
||||
}
|
||||
}];
|
||||
}
|
||||
|
||||
- (void)processPHPickerImage:(UIImage*)image
|
||||
assetIdentifier:(NSString*)assetIdentifier
|
||||
callbackId:(NSString*)callbackId
|
||||
options:(CDVPictureOptions*)options API_AVAILABLE(ios(14))
|
||||
{
|
||||
__weak CDVCamera* weakSelf = self;
|
||||
|
||||
// Fetch metadata if asset identifier is available
|
||||
if (assetIdentifier) {
|
||||
PHFetchResult *result = [PHAsset fetchAssetsWithLocalIdentifiers:@[assetIdentifier] options:nil];
|
||||
PHAsset *asset = result.firstObject;
|
||||
|
||||
if (asset) {
|
||||
PHImageRequestOptions *imageOptions = [[PHImageRequestOptions alloc] init];
|
||||
imageOptions.synchronous = YES;
|
||||
imageOptions.networkAccessAllowed = YES;
|
||||
|
||||
[[PHImageManager defaultManager] requestImageDataAndOrientationForAsset:asset
|
||||
options:imageOptions
|
||||
resultHandler:^(NSData *_Nullable imageData, NSString *_Nullable dataUTI, CGImagePropertyOrientation orientation, NSDictionary *_Nullable info) {
|
||||
NSDictionary *metadata = imageData ? [weakSelf convertImageMetadata:imageData] : nil;
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
[weakSelf finalizePHPickerImage:image metadata:metadata callbackId:callbackId options:options];
|
||||
});
|
||||
}];
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// No metadata available
|
||||
[self finalizePHPickerImage:image metadata:nil callbackId:callbackId options:options];
|
||||
}
|
||||
|
||||
- (void)finalizePHPickerImage:(UIImage*)image
|
||||
metadata:(NSDictionary*)metadata
|
||||
callbackId:(NSString*)callbackId
|
||||
options:(CDVPictureOptions*)options API_AVAILABLE(ios(14))
|
||||
{
|
||||
// Process image according to options
|
||||
UIImage *processedImage = image;
|
||||
|
||||
if (options.correctOrientation) {
|
||||
processedImage = [processedImage imageCorrectedForCaptureOrientation];
|
||||
}
|
||||
|
||||
if ((options.targetSize.width > 0) && (options.targetSize.height > 0)) {
|
||||
if (options.cropToSize) {
|
||||
processedImage = [processedImage imageByScalingAndCroppingForSize:options.targetSize];
|
||||
} else {
|
||||
processedImage = [processedImage imageByScalingNotCroppingForSize:options.targetSize];
|
||||
}
|
||||
}
|
||||
|
||||
// Create info dictionary similar to UIImagePickerController
|
||||
NSMutableDictionary *info = [NSMutableDictionary dictionary];
|
||||
[info setObject:processedImage forKey:UIImagePickerControllerOriginalImage];
|
||||
if (metadata) {
|
||||
[info setObject:metadata forKey:@"UIImagePickerControllerMediaMetadata"];
|
||||
}
|
||||
|
||||
// Store metadata for processing
|
||||
if (metadata) {
|
||||
self.metadata = [[NSMutableDictionary alloc] init];
|
||||
|
||||
NSMutableDictionary* EXIFDictionary = [[metadata objectForKey:(NSString*)kCGImagePropertyExifDictionary] mutableCopy];
|
||||
if (EXIFDictionary) {
|
||||
[self.metadata setObject:EXIFDictionary forKey:(NSString*)kCGImagePropertyExifDictionary];
|
||||
}
|
||||
|
||||
NSMutableDictionary* TIFFDictionary = [[metadata objectForKey:(NSString*)kCGImagePropertyTIFFDictionary] mutableCopy];
|
||||
if (TIFFDictionary) {
|
||||
[self.metadata setObject:TIFFDictionary forKey:(NSString*)kCGImagePropertyTIFFDictionary];
|
||||
}
|
||||
|
||||
NSMutableDictionary* GPSDictionary = [[metadata objectForKey:(NSString*)kCGImagePropertyGPSDictionary] mutableCopy];
|
||||
if (GPSDictionary) {
|
||||
[self.metadata setObject:GPSDictionary forKey:(NSString*)kCGImagePropertyGPSDictionary];
|
||||
}
|
||||
}
|
||||
|
||||
__weak CDVCamera* weakSelf = self;
|
||||
|
||||
// Process and return result
|
||||
[self resultForImage:options info:info completion:^(CDVPluginResult* res) {
|
||||
[weakSelf.commandDelegate sendPluginResult:res callbackId:callbackId];
|
||||
weakSelf.hasPendingOperation = NO;
|
||||
weakSelf.pickerController = nil;
|
||||
}];
|
||||
}
|
||||
#endif
|
||||
|
||||
- (NSInteger)integerValueForKey:(NSDictionary*)dict key:(NSString*)key defaultValue:(NSInteger)defaultValue
|
||||
{
|
||||
NSInteger value = defaultValue;
|
||||
@@ -272,51 +504,18 @@ static NSString* toBase64(NSData* data) {
|
||||
return value;
|
||||
}
|
||||
|
||||
- (void)displayPopover:(NSDictionary*)options
|
||||
// UINavigationControllerDelegate method
|
||||
- (void)navigationController:(UINavigationController*)navigationController
|
||||
willShowViewController:(UIViewController*)viewController
|
||||
animated:(BOOL)animated
|
||||
{
|
||||
NSInteger x = 0;
|
||||
NSInteger y = 32;
|
||||
NSInteger width = 320;
|
||||
NSInteger height = 480;
|
||||
UIPopoverArrowDirection arrowDirection = UIPopoverArrowDirectionAny;
|
||||
// Backward compatibility for iOS < 14
|
||||
// Set title "Videos", when picking videos with the legacy UIImagePickerController
|
||||
if([navigationController isKindOfClass:[UIImagePickerController class]]) {
|
||||
UIImagePickerController* imagePickerController = (UIImagePickerController*)navigationController;
|
||||
|
||||
if (options) {
|
||||
x = [self integerValueForKey:options key:@"x" defaultValue:0];
|
||||
y = [self integerValueForKey:options key:@"y" defaultValue:32];
|
||||
width = [self integerValueForKey:options key:@"width" defaultValue:320];
|
||||
height = [self integerValueForKey:options key:@"height" defaultValue:480];
|
||||
arrowDirection = [self integerValueForKey:options key:@"arrowDir" defaultValue:UIPopoverArrowDirectionAny];
|
||||
if (![org_apache_cordova_validArrowDirections containsObject:[NSNumber numberWithUnsignedInteger:arrowDirection]]) {
|
||||
arrowDirection = UIPopoverArrowDirectionAny;
|
||||
}
|
||||
}
|
||||
|
||||
[[[self pickerController] pickerPopoverController] setDelegate:self];
|
||||
[[[self pickerController] pickerPopoverController] presentPopoverFromRect:CGRectMake(x, y, width, height)
|
||||
inView:[self.webView superview]
|
||||
permittedArrowDirections:arrowDirection
|
||||
animated:YES];
|
||||
}
|
||||
|
||||
- (void)navigationController:(UINavigationController *)navigationController willShowViewController:(UIViewController *)viewController animated:(BOOL)animated
|
||||
{
|
||||
if([navigationController isKindOfClass:[UIImagePickerController class]]){
|
||||
|
||||
// If popoverWidth and popoverHeight are specified and are greater than 0, then set popover size, else use apple's default popoverSize
|
||||
NSDictionary* options = self.pickerController.pictureOptions.popoverOptions;
|
||||
if(options) {
|
||||
NSInteger popoverWidth = [self integerValueForKey:options key:@"popoverWidth" defaultValue:0];
|
||||
NSInteger popoverHeight = [self integerValueForKey:options key:@"popoverHeight" defaultValue:0];
|
||||
if(popoverWidth > 0 && popoverHeight > 0)
|
||||
{
|
||||
[viewController setPreferredContentSize:CGSizeMake(popoverWidth,popoverHeight)];
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
UIImagePickerController* cameraPicker = (UIImagePickerController*)navigationController;
|
||||
|
||||
if(![cameraPicker.mediaTypes containsObject:(NSString*)kUTTypeImage]){
|
||||
// Set title "Videos" when picking not images
|
||||
if(![imagePickerController.mediaTypes containsObject:(NSString*)kUTTypeImage]) {
|
||||
[viewController.navigationItem setTitle:NSLocalizedString(@"Videos", nil)];
|
||||
}
|
||||
}
|
||||
@@ -357,31 +556,52 @@ static NSString* toBase64(NSData* data) {
|
||||
[self.commandDelegate sendPluginResult:pluginResult callbackId:command.callbackId];
|
||||
}
|
||||
|
||||
- (void)popoverControllerDidDismissPopover:(id)popoverController
|
||||
- (NSString*)getMimeForEncoding:(CDVEncodingType)encoding
|
||||
{
|
||||
UIPopoverController* pc = (UIPopoverController*)popoverController;
|
||||
|
||||
[pc dismissPopoverAnimated:YES];
|
||||
pc.delegate = nil;
|
||||
if (self.pickerController && self.pickerController.callbackId && self.pickerController.pickerPopoverController) {
|
||||
self.pickerController.pickerPopoverController = nil;
|
||||
NSString* callbackId = self.pickerController.callbackId;
|
||||
CDVPluginResult* result = [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR messageAsString:@"no image selected"]; // error callback expects string ATM
|
||||
[self.commandDelegate sendPluginResult:result callbackId:callbackId];
|
||||
switch (encoding) {
|
||||
case EncodingTypePNG: return MIME_PNG;
|
||||
case EncodingTypeJPEG:
|
||||
default:
|
||||
return MIME_JPEG;
|
||||
}
|
||||
self.hasPendingOperation = NO;
|
||||
}
|
||||
|
||||
- (NSString*)formatAsDataURI:(NSData*)data withMIME:(NSString*)mime
|
||||
{
|
||||
NSString* base64 = toBase64(data);
|
||||
|
||||
if (base64 == nil) {
|
||||
return nil;
|
||||
}
|
||||
|
||||
return [NSString stringWithFormat:@"data:%@;base64,%@", mime, base64];
|
||||
}
|
||||
|
||||
- (NSString*)processImageAsDataUri:(UIImage*)image info:(NSDictionary*)info options:(CDVPictureOptions*)options
|
||||
{
|
||||
NSString* mime = nil;
|
||||
NSData* data = [self processImage:image info:info options:options outMime:&mime];
|
||||
|
||||
return [self formatAsDataURI:data withMIME:mime];
|
||||
}
|
||||
|
||||
- (NSData*)processImage:(UIImage*)image info:(NSDictionary*)info options:(CDVPictureOptions*)options
|
||||
{
|
||||
return [self processImage:image info:info options:options outMime:nil];
|
||||
}
|
||||
|
||||
- (NSData*)processImage:(UIImage*)image info:(NSDictionary*)info options:(CDVPictureOptions*)options outMime:(NSString**)outMime
|
||||
{
|
||||
NSData* data = nil;
|
||||
|
||||
switch (options.encodingType) {
|
||||
case EncodingTypePNG:
|
||||
data = UIImagePNGRepresentation(image);
|
||||
if (outMime != nil) *outMime = MIME_PNG;
|
||||
break;
|
||||
case EncodingTypeJPEG:
|
||||
{
|
||||
if (outMime != nil) *outMime = MIME_JPEG;
|
||||
if ((options.allowsEditing == NO) && (options.targetSize.width <= 0) && (options.targetSize.height <= 0) && (options.correctOrientation == NO) && (([options.quality integerValue] == 100) || (options.sourceType != UIImagePickerControllerSourceTypeCamera))){
|
||||
// use image unedited as requested , don't resize
|
||||
data = UIImageJPEGRepresentation(image, 1.0);
|
||||
@@ -439,15 +659,16 @@ static NSString* toBase64(NSData* data) {
|
||||
default:
|
||||
break;
|
||||
};
|
||||
|
||||
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
/* --------------------------------------------------------------
|
||||
-- get the metadata of the image from a PHAsset
|
||||
-------------------------------------------------------------- */
|
||||
- (NSDictionary*)getImageMetadataFromAsset:(PHAsset*)asset {
|
||||
|
||||
- (NSDictionary*)getImageMetadataFromAsset:(PHAsset*)asset
|
||||
{
|
||||
if(asset == nil) {
|
||||
return nil;
|
||||
}
|
||||
@@ -465,7 +686,8 @@ static NSString* toBase64(NSData* data) {
|
||||
return dict;
|
||||
}
|
||||
|
||||
-(NSDictionary*)convertImageMetadata:(NSData*)imageData {
|
||||
- (NSDictionary*)convertImageMetadata:(NSData*)imageData
|
||||
{
|
||||
CGImageSourceRef imageSource = CGImageSourceCreateWithData((__bridge CFDataRef)(imageData), NULL);
|
||||
if (imageSource) {
|
||||
NSDictionary *options = @{(NSString *)kCGImageSourceShouldCache : [NSNumber numberWithBool:NO]};
|
||||
@@ -484,12 +706,20 @@ static NSString* toBase64(NSData* data) {
|
||||
return nil;
|
||||
}
|
||||
|
||||
/**
|
||||
Requests Photos library permissions when needed for picking media from the photo library.
|
||||
This is only needed for iOS 13 and older when using UIImagePickerController for picking an image.
|
||||
On iOS 14 and later, PHPickerViewController is used and does not need extra permissions.
|
||||
@param options The picture options indicating the requested source type.
|
||||
@param completion A block invoked with YES when access is authorized (or not required),
|
||||
or NO when access is denied or restricted.
|
||||
*/
|
||||
- (void)options:(CDVPictureOptions*)options requestPhotoPermissions:(void (^)(BOOL auth))completion
|
||||
{
|
||||
if((unsigned long)options.sourceType == 1){
|
||||
// This is would be no good response
|
||||
if(options.sourceType == UIImagePickerControllerSourceTypeCamera) {
|
||||
completion(YES);
|
||||
}
|
||||
else{
|
||||
} else {
|
||||
PHAuthorizationStatus status = [PHPhotoLibrary authorizationStatus];
|
||||
|
||||
switch (status) {
|
||||
@@ -554,7 +784,9 @@ static NSString* toBase64(NSData* data) {
|
||||
return (scaledImage == nil ? image : scaledImage);
|
||||
}
|
||||
|
||||
- (void)resultForImage:(CDVPictureOptions*)options info:(NSDictionary*)info completion:(void (^)(CDVPluginResult* res))completion
|
||||
- (void)resultForImage:(CDVPictureOptions*)options
|
||||
info:(NSDictionary*)info
|
||||
completion:(void (^)(CDVPluginResult* res))completion
|
||||
{
|
||||
CDVPluginResult* result = nil;
|
||||
BOOL saveToPhotoAlbum = options.saveToPhotoAlbum;
|
||||
@@ -564,9 +796,9 @@ static NSString* toBase64(NSData* data) {
|
||||
case DestinationTypeDataUrl:
|
||||
{
|
||||
image = [self retrieveImage:info options:options];
|
||||
NSData* data = [self processImage:image info:info options:options];
|
||||
NSString* data = [self processImageAsDataUri:image info:info options:options];
|
||||
if (data) {
|
||||
result = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsString:toBase64(data)];
|
||||
result = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsString: data];
|
||||
}
|
||||
}
|
||||
break;
|
||||
@@ -574,6 +806,7 @@ static NSString* toBase64(NSData* data) {
|
||||
{
|
||||
image = [self retrieveImage:info options:options];
|
||||
NSData* data = [self processImage:image info:info options:options];
|
||||
|
||||
if (data) {
|
||||
if (pickerController.sourceType == UIImagePickerControllerSourceTypePhotoLibrary) {
|
||||
NSMutableData *imageDataWithExif = [NSMutableData data];
|
||||
@@ -639,7 +872,8 @@ static NSString* toBase64(NSData* data) {
|
||||
return [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsString:moviePath];
|
||||
}
|
||||
|
||||
- (NSString *) createTmpVideo:(NSString *) moviePath {
|
||||
- (NSString*)createTmpVideo:(NSString*)moviePath
|
||||
{
|
||||
NSString* moviePathExtension = [moviePath pathExtension];
|
||||
NSString* copyMoviePath = [self tempFilePath:moviePathExtension];
|
||||
NSFileManager* fileMgr = [[NSFileManager alloc] init];
|
||||
@@ -648,6 +882,8 @@ static NSString* toBase64(NSData* data) {
|
||||
return [[NSURL fileURLWithPath:copyMoviePath] absoluteString];
|
||||
}
|
||||
|
||||
#pragma mark UIImagePickerControllerDelegate methods
|
||||
|
||||
- (void)imagePickerController:(UIImagePickerController*)picker didFinishPickingMediaWithInfo:(NSDictionary*)info
|
||||
{
|
||||
__weak CDVCameraPicker* cameraPicker = (CDVCameraPicker*)picker;
|
||||
@@ -674,18 +910,13 @@ static NSString* toBase64(NSData* data) {
|
||||
}
|
||||
};
|
||||
|
||||
if (cameraPicker.pictureOptions.popoverSupported && (cameraPicker.pickerPopoverController != nil)) {
|
||||
[cameraPicker.pickerPopoverController dismissPopoverAnimated:YES];
|
||||
cameraPicker.pickerPopoverController.delegate = nil;
|
||||
cameraPicker.pickerPopoverController = nil;
|
||||
invoke();
|
||||
} else {
|
||||
[[cameraPicker presentingViewController] dismissViewControllerAnimated:YES completion:invoke];
|
||||
}
|
||||
[[cameraPicker presentingViewController] dismissViewControllerAnimated:YES completion:invoke];
|
||||
}
|
||||
|
||||
// older api calls newer didFinishPickingMediaWithInfo
|
||||
- (void)imagePickerController:(UIImagePickerController*)picker didFinishPickingImage:(UIImage*)image editingInfo:(NSDictionary*)editingInfo
|
||||
- (void)imagePickerController:(UIImagePickerController*)picker
|
||||
didFinishPickingImage:(UIImage*)image
|
||||
editingInfo:(NSDictionary*)editingInfo
|
||||
{
|
||||
NSDictionary* imageInfo = [NSDictionary dictionaryWithObject:image forKey:UIImagePickerControllerOriginalImage];
|
||||
|
||||
@@ -715,6 +946,8 @@ static NSString* toBase64(NSData* data) {
|
||||
[[cameraPicker presentingViewController] dismissViewControllerAnimated:YES completion:invoke];
|
||||
}
|
||||
|
||||
#pragma mark CLLocationManager
|
||||
|
||||
- (CLLocationManager*)locationManager
|
||||
{
|
||||
if (locationManager != nil) {
|
||||
@@ -728,7 +961,11 @@ static NSString* toBase64(NSData* data) {
|
||||
return locationManager;
|
||||
}
|
||||
|
||||
- (void)locationManager:(CLLocationManager*)manager didUpdateToLocation:(CLLocation*)newLocation fromLocation:(CLLocation*)oldLocation
|
||||
# pragma mark CLLocationManagerDelegate methods
|
||||
|
||||
- (void)locationManager:(CLLocationManager*)manager
|
||||
didUpdateToLocation:(CLLocation*)newLocation
|
||||
fromLocation:(CLLocation*)oldLocation
|
||||
{
|
||||
if (locationManager == nil) {
|
||||
return;
|
||||
@@ -823,7 +1060,9 @@ static NSString* toBase64(NSData* data) {
|
||||
switch (options.destinationType) {
|
||||
case DestinationTypeDataUrl:
|
||||
{
|
||||
result = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsString:toBase64(self.data)];
|
||||
NSString* mime = [self getMimeForEncoding: self.pickerController.pictureOptions.encodingType];
|
||||
NSString* uri = [self formatAsDataURI: self.data withMIME: mime];
|
||||
result = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsString: uri];
|
||||
}
|
||||
break;
|
||||
default: // DestinationTypeFileUri
|
||||
@@ -881,7 +1120,7 @@ static NSString* toBase64(NSData* data) {
|
||||
[super viewWillAppear:animated];
|
||||
}
|
||||
|
||||
+ (instancetype) createFromPictureOptions:(CDVPictureOptions*)pictureOptions;
|
||||
+ (instancetype)createFromPictureOptions:(CDVPictureOptions*)pictureOptions
|
||||
{
|
||||
CDVCameraPicker* cameraPicker = [[CDVCameraPicker alloc] init];
|
||||
cameraPicker.pictureOptions = pictureOptions;
|
||||
|
||||
@@ -37,28 +37,30 @@
|
||||
- (UIImage*)retrieveImage:(NSDictionary*)info options:(CDVPictureOptions*)options;
|
||||
- (CDVPluginResult*)resultForImage:(CDVPictureOptions*)options info:(NSDictionary*)info;
|
||||
- (CDVPluginResult*)resultForVideo:(NSDictionary*)info;
|
||||
- (NSDictionary*)convertImageMetadata:(NSData*)imageData;
|
||||
|
||||
@end
|
||||
|
||||
@implementation CameraTest
|
||||
|
||||
- (void)setUp {
|
||||
- (void)setUp
|
||||
{
|
||||
[super setUp];
|
||||
// Put setup code here. This method is called before the invocation of each test method in the class.
|
||||
|
||||
self.plugin = [[CDVCamera alloc] init];
|
||||
}
|
||||
|
||||
- (void)tearDown {
|
||||
- (void)tearDown
|
||||
{
|
||||
// Put teardown code here. This method is called after the invocation of each test method in the class.
|
||||
[super tearDown];
|
||||
}
|
||||
|
||||
- (void) testPictureOptionsCreate
|
||||
- (void)testPictureOptionsCreate
|
||||
{
|
||||
NSArray* args;
|
||||
CDVPictureOptions* options;
|
||||
NSDictionary* popoverOptions;
|
||||
|
||||
// No arguments, check whether the defaults are set
|
||||
args = @[];
|
||||
@@ -76,14 +78,10 @@
|
||||
XCTAssertEqual(options.allowsEditing, NO);
|
||||
XCTAssertEqual(options.correctOrientation, NO);
|
||||
XCTAssertEqual(options.saveToPhotoAlbum, NO);
|
||||
XCTAssertEqualObjects(options.popoverOptions, nil);
|
||||
XCTAssertEqual(options.cameraDirection, (int)UIImagePickerControllerCameraDeviceRear);
|
||||
XCTAssertEqual(options.popoverSupported, NO);
|
||||
XCTAssertEqual(options.usesGeolocation, NO);
|
||||
|
||||
// Set each argument, check whether they are set. different from defaults
|
||||
popoverOptions = @{ @"x" : @1, @"y" : @2, @"width" : @3, @"height" : @4, @"popoverWidth": @200, @"popoverHeight": @300 };
|
||||
|
||||
args = @[
|
||||
@(49),
|
||||
@(DestinationTypeDataUrl),
|
||||
@@ -95,7 +93,6 @@
|
||||
@YES,
|
||||
@YES,
|
||||
@YES,
|
||||
popoverOptions,
|
||||
@(UIImagePickerControllerCameraDeviceFront),
|
||||
];
|
||||
|
||||
@@ -112,22 +109,17 @@
|
||||
XCTAssertEqual(options.allowsEditing, YES);
|
||||
XCTAssertEqual(options.correctOrientation, YES);
|
||||
XCTAssertEqual(options.saveToPhotoAlbum, YES);
|
||||
XCTAssertEqualObjects(options.popoverOptions, popoverOptions);
|
||||
XCTAssertEqual(options.cameraDirection, (int)UIImagePickerControllerCameraDeviceFront);
|
||||
XCTAssertEqual(options.popoverSupported, NO);
|
||||
XCTAssertEqual(options.usesGeolocation, NO);
|
||||
}
|
||||
|
||||
- (void) testCameraPickerCreate
|
||||
- (void)testCameraPickerCreate
|
||||
{
|
||||
NSDictionary* popoverOptions;
|
||||
NSArray* args;
|
||||
CDVPictureOptions* pictureOptions;
|
||||
CDVCameraPicker* picker;
|
||||
|
||||
// Souce is Camera, and image type
|
||||
|
||||
popoverOptions = @{ @"x" : @1, @"y" : @2, @"width" : @3, @"height" : @4, @"popoverWidth": @200, @"popoverHeight": @300 };
|
||||
// Source is Camera, uses always UIImagePickerController
|
||||
args = @[
|
||||
@(49),
|
||||
@(DestinationTypeDataUrl),
|
||||
@@ -139,7 +131,6 @@
|
||||
@YES,
|
||||
@YES,
|
||||
@YES,
|
||||
popoverOptions,
|
||||
@(UIImagePickerControllerCameraDeviceFront),
|
||||
];
|
||||
|
||||
@@ -157,7 +148,7 @@
|
||||
XCTAssertEqual(picker.cameraDevice, pictureOptions.cameraDirection);
|
||||
}
|
||||
|
||||
// Souce is not Camera, and all media types
|
||||
// Source is Photo Library, and all media types - On iOS 14+ uses PHPicker, below uses UIImagePickerController
|
||||
|
||||
args = @[
|
||||
@(49),
|
||||
@@ -170,7 +161,6 @@
|
||||
@YES,
|
||||
@YES,
|
||||
@YES,
|
||||
popoverOptions,
|
||||
@(UIImagePickerControllerCameraDeviceFront),
|
||||
];
|
||||
|
||||
@@ -187,7 +177,7 @@
|
||||
XCTAssertEqualObjects(picker.mediaTypes, [UIImagePickerController availableMediaTypesForSourceType:picker.sourceType]);
|
||||
}
|
||||
|
||||
// Souce is not Camera, and either Image or Movie media type
|
||||
// Source is Photo Library, and either Image or Movie media type - On iOS 14+ uses PHPicker
|
||||
|
||||
args = @[
|
||||
@(49),
|
||||
@@ -200,7 +190,6 @@
|
||||
@YES,
|
||||
@YES,
|
||||
@YES,
|
||||
popoverOptions,
|
||||
@(UIImagePickerControllerCameraDeviceFront),
|
||||
];
|
||||
|
||||
@@ -218,7 +207,8 @@
|
||||
}
|
||||
}
|
||||
|
||||
- (UIImage*) createImage:(CGRect)rect orientation:(UIImageOrientation)imageOrientation {
|
||||
- (UIImage*)createImage:(CGRect)rect orientation:(UIImageOrientation)imageOrientation
|
||||
{
|
||||
UIGraphicsBeginImageContext(rect.size);
|
||||
CGContextRef context = UIGraphicsGetCurrentContext();
|
||||
|
||||
@@ -233,8 +223,8 @@
|
||||
return image;
|
||||
}
|
||||
|
||||
- (void) testImageScaleCropForSize {
|
||||
|
||||
- (void)testImageScaleCropForSize
|
||||
{
|
||||
UIImage *sourceImagePortrait, *sourceImageLandscape, *targetImage;
|
||||
CGSize targetSize = CGSizeZero;
|
||||
|
||||
@@ -279,7 +269,8 @@
|
||||
XCTAssertEqual(targetImage.size.height, targetSize.height);
|
||||
}
|
||||
|
||||
- (void) testImageScaleNoCropForSize {
|
||||
- (void)testImageScaleNoCropForSize
|
||||
{
|
||||
UIImage *sourceImagePortrait, *sourceImageLandscape, *targetImage;
|
||||
CGSize targetSize = CGSizeZero;
|
||||
|
||||
@@ -330,7 +321,8 @@
|
||||
XCTAssertEqual(targetImage.size.height, targetSize.height);
|
||||
}
|
||||
|
||||
- (void) testImageCorrectedForOrientation {
|
||||
- (void)testImageCorrectedForOrientation
|
||||
{
|
||||
UIImage *sourceImagePortrait, *sourceImageLandscape, *targetImage;
|
||||
CGSize targetSize = CGSizeZero;
|
||||
|
||||
@@ -383,7 +375,7 @@
|
||||
}
|
||||
|
||||
|
||||
- (void) testRetrieveImage
|
||||
- (void)testRetrieveImage
|
||||
{
|
||||
CDVPictureOptions* pictureOptions = [[CDVPictureOptions alloc] init];
|
||||
NSDictionary *infoDict1, *infoDict2;
|
||||
@@ -461,7 +453,7 @@
|
||||
XCTAssertEqual(resultImage.size.height, scaledImageWithCrop.size.height);
|
||||
}
|
||||
|
||||
- (void) testProcessImage
|
||||
- (void)testProcessImage
|
||||
{
|
||||
CDVPictureOptions* pictureOptions = [[CDVPictureOptions alloc] init];
|
||||
NSData* resultData;
|
||||
@@ -508,4 +500,123 @@
|
||||
// TODO: usesGeolocation is not tested
|
||||
}
|
||||
|
||||
#pragma mark - PHPickerViewController Tests (iOS 14+)
|
||||
|
||||
#if __IPHONE_OS_VERSION_MAX_ALLOWED >= 140000 // Always true on XCode12+
|
||||
- (void)testPHPickerAvailability API_AVAILABLE(ios(14))
|
||||
{
|
||||
XCTAssertNotNil([PHPickerViewController class]);
|
||||
|
||||
PHPickerConfiguration *config = [[PHPickerConfiguration alloc] init];
|
||||
XCTAssertNotNil(config);
|
||||
|
||||
config.filter = [PHPickerFilter imagesFilter];
|
||||
XCTAssertNotNil(config.filter);
|
||||
|
||||
PHPickerViewController *picker = [[PHPickerViewController alloc] initWithConfiguration:config];
|
||||
XCTAssertNotNil(picker);
|
||||
}
|
||||
|
||||
- (void)testPHPickerConfiguration API_AVAILABLE(ios(14))
|
||||
{
|
||||
// Test image filter configuration
|
||||
PHPickerConfiguration *imageConfig = [[PHPickerConfiguration alloc] init];
|
||||
imageConfig.filter = [PHPickerFilter imagesFilter];
|
||||
imageConfig.selectionLimit = 1;
|
||||
|
||||
XCTAssertNotNil(imageConfig);
|
||||
XCTAssertEqual(imageConfig.selectionLimit, 1);
|
||||
|
||||
// Test video filter configuration
|
||||
PHPickerConfiguration *videoConfig = [[PHPickerConfiguration alloc] init];
|
||||
videoConfig.filter = [PHPickerFilter videosFilter];
|
||||
|
||||
XCTAssertNotNil(videoConfig.filter);
|
||||
|
||||
// Test all media types configuration
|
||||
PHPickerConfiguration *allConfig = [[PHPickerConfiguration alloc] init];
|
||||
allConfig.filter = [PHPickerFilter anyFilterMatchingSubfilters:@[
|
||||
[PHPickerFilter imagesFilter],
|
||||
[PHPickerFilter videosFilter]
|
||||
]];
|
||||
|
||||
XCTAssertNotNil(allConfig.filter);
|
||||
}
|
||||
|
||||
- (void)testPHPickerDelegateConformance API_AVAILABLE(ios(14))
|
||||
{
|
||||
// Test that CDVCamera conforms to PHPickerViewControllerDelegate
|
||||
XCTAssertTrue([self.plugin conformsToProtocol:@protocol(PHPickerViewControllerDelegate)]);
|
||||
|
||||
// Test that the delegate method is implemented
|
||||
SEL delegateSelector = @selector(picker:didFinishPicking:);
|
||||
XCTAssertTrue([self.plugin respondsToSelector:delegateSelector]);
|
||||
}
|
||||
|
||||
- (void)testShowPHPickerMethod API_AVAILABLE(ios(14))
|
||||
{
|
||||
// Test that showPHPicker method exists
|
||||
SEL showPHPickerSelector = @selector(showPHPicker:withOptions:);
|
||||
XCTAssertTrue([self.plugin respondsToSelector:showPHPickerSelector]);
|
||||
|
||||
// Test that processPHPickerImage method exists
|
||||
SEL processSelector = @selector(processPHPickerImage:assetIdentifier:callbackId:options:);
|
||||
XCTAssertTrue([self.plugin respondsToSelector:processSelector]);
|
||||
|
||||
// Test that finalizePHPickerImage method exists
|
||||
SEL finalizeSelector = @selector(finalizePHPickerImage:metadata:callbackId:options:);
|
||||
XCTAssertTrue([self.plugin respondsToSelector:finalizeSelector]);
|
||||
}
|
||||
#endif
|
||||
|
||||
- (void)testConvertImageMetadata
|
||||
{
|
||||
// Create a test image
|
||||
UIImage* testImage = [self createImage:CGRectMake(0, 0, 100, 100) orientation:UIImageOrientationUp];
|
||||
NSData* imageData = UIImageJPEGRepresentation(testImage, 1.0);
|
||||
|
||||
XCTAssertNotNil(imageData);
|
||||
|
||||
// Test metadata conversion
|
||||
NSDictionary* metadata = [self.plugin convertImageMetadata:imageData];
|
||||
|
||||
// Metadata may be nil for generated images, but the method should not crash
|
||||
// Real camera images would have EXIF data
|
||||
XCTAssertTrue(metadata == nil || [metadata isKindOfClass:[NSDictionary class]]);
|
||||
}
|
||||
|
||||
- (void)testPictureOptionsForPHPicker
|
||||
{
|
||||
NSArray* args;
|
||||
CDVPictureOptions* options;
|
||||
|
||||
// Test options configuration for photo library (which would use PHPicker on iOS 14+)
|
||||
args = @[
|
||||
@(75),
|
||||
@(DestinationTypeFileUri),
|
||||
@(UIImagePickerControllerSourceTypePhotoLibrary),
|
||||
@(800),
|
||||
@(600),
|
||||
@(EncodingTypeJPEG),
|
||||
@(MediaTypePicture),
|
||||
@NO,
|
||||
@YES,
|
||||
@NO,
|
||||
[NSNull null],
|
||||
@(UIImagePickerControllerCameraDeviceRear),
|
||||
];
|
||||
|
||||
CDVInvokedUrlCommand* command = [[CDVInvokedUrlCommand alloc] initWithArguments:args callbackId:@"dummy" className:@"myclassname" methodName:@"mymethodname"];
|
||||
options = [CDVPictureOptions createFromTakePictureArguments:command];
|
||||
|
||||
// Verify options are correctly set for photo library source
|
||||
XCTAssertEqual(options.sourceType, (int)UIImagePickerControllerSourceTypePhotoLibrary);
|
||||
XCTAssertEqual([options.quality intValue], 75);
|
||||
XCTAssertEqual(options.destinationType, (int)DestinationTypeFileUri);
|
||||
XCTAssertEqual(options.targetSize.width, 800);
|
||||
XCTAssertEqual(options.targetSize.height, 600);
|
||||
XCTAssertEqual(options.correctOrientation, YES);
|
||||
XCTAssertEqual(options.mediaType, (int)MediaTypePicture);
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
13
tests/package-lock.json
generated
Normal file
13
tests/package-lock.json
generated
Normal file
@@ -0,0 +1,13 @@
|
||||
{
|
||||
"name": "cordova-plugin-camera-tests",
|
||||
"version": "8.0.1-dev",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "cordova-plugin-camera-tests",
|
||||
"version": "8.0.1-dev",
|
||||
"license": "Apache-2.0"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "cordova-plugin-camera-tests",
|
||||
"version": "7.0.0",
|
||||
"version": "9.0.0-dev",
|
||||
"description": "",
|
||||
"cordova": {
|
||||
"id": "cordova-plugin-camera-tests",
|
||||
|
||||
@@ -22,7 +22,7 @@
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:rim="http://www.blackberry.com/ns/widgets"
|
||||
id="cordova-plugin-camera-tests"
|
||||
version="7.0.0">
|
||||
version="9.0.0-dev">
|
||||
<name>Cordova Camera Plugin Tests</name>
|
||||
<license>Apache 2.0</license>
|
||||
|
||||
|
||||
@@ -19,7 +19,7 @@
|
||||
*
|
||||
*/
|
||||
|
||||
/* globals Camera, resolveLocalFileSystemURL, FileEntry, CameraPopoverOptions, LocalFileSystem */
|
||||
/* globals Camera, resolveLocalFileSystemURL, FileEntry, LocalFileSystem */
|
||||
/* eslint-env jasmine */
|
||||
|
||||
exports.defineAutoTests = function () {
|
||||
@@ -156,13 +156,7 @@ exports.defineManualTests = function (contentEl, createActionButton) {
|
||||
clearStatus();
|
||||
const options = extractOptions();
|
||||
log('Getting picture with options: ' + JSON.stringify(options));
|
||||
const popoverHandle = navigator.camera.getPicture(getPictureWin, onGetPictureError, options);
|
||||
|
||||
// Reposition the popover if the orientation changes.
|
||||
window.onorientationchange = function () {
|
||||
const newPopoverOptions = new CameraPopoverOptions(0, 0, 100, 100, 0, 300, 400);
|
||||
popoverHandle.setPosition(newPopoverOptions);
|
||||
};
|
||||
navigator.camera.getPicture(getPictureWin, onGetPictureError, options);
|
||||
}
|
||||
|
||||
function logCallback (apiName, success) {
|
||||
|
||||
55
types/index.d.ts
vendored
55
types/index.d.ts
vendored
@@ -36,11 +36,6 @@ interface Camera {
|
||||
cameraSuccess: (data: string) => void,
|
||||
cameraError: (message: string) => void,
|
||||
cameraOptions?: CameraOptions): void;
|
||||
// Next will work only on iOS
|
||||
//getPicture(
|
||||
// cameraSuccess: (data: string) => void,
|
||||
// cameraError: (message: string) => void,
|
||||
// cameraOptions?: CameraOptions): CameraPopoverHandle;
|
||||
}
|
||||
|
||||
interface CameraOptions {
|
||||
@@ -58,7 +53,7 @@ interface CameraOptions {
|
||||
* Defined in navigator.camera.PictureSourceType. Default is CAMERA.
|
||||
* PHOTOLIBRARY : 0,
|
||||
* CAMERA : 1,
|
||||
* SAVEDPHOTOALBUM : 2
|
||||
* SAVEDPHOTOALBUM : 2 // same as PHOTOLIBRARY, kept for backward compatibility
|
||||
*/
|
||||
sourceType?: number;
|
||||
/** Allow simple editing of image before selection. */
|
||||
@@ -100,46 +95,6 @@ interface CameraOptions {
|
||||
* BACK: 1
|
||||
*/
|
||||
cameraDirection?: number;
|
||||
/** iOS-only options that specify popover location in iPad. Defined in CameraPopoverOptions. */
|
||||
popoverOptions?: CameraPopoverOptions;
|
||||
}
|
||||
|
||||
/**
|
||||
* A handle to the popover dialog created by navigator.camera.getPicture. Used on iOS only.
|
||||
*/
|
||||
interface CameraPopoverHandle {
|
||||
/**
|
||||
* Set the position of the popover.
|
||||
* @param popoverOptions the CameraPopoverOptions that specify the new position.
|
||||
*/
|
||||
setPosition(popoverOptions: CameraPopoverOptions): void;
|
||||
}
|
||||
|
||||
/**
|
||||
* iOS-only parameters that specify the anchor element location and arrow direction
|
||||
* of the popover when selecting images from an iPad's library or album.
|
||||
*/
|
||||
interface CameraPopoverOptions {
|
||||
x: number;
|
||||
y: number;
|
||||
width: number;
|
||||
height: number;
|
||||
/**
|
||||
* Direction the arrow on the popover should point. Defined in Camera.PopoverArrowDirection
|
||||
* Matches iOS UIPopoverArrowDirection constants.
|
||||
* ARROW_UP : 1,
|
||||
* ARROW_DOWN : 2,
|
||||
* ARROW_LEFT : 4,
|
||||
* ARROW_RIGHT : 8,
|
||||
* ARROW_ANY : 15
|
||||
*/
|
||||
arrowDir : number;
|
||||
popoverWidth: number;
|
||||
popoverHeight: number;
|
||||
}
|
||||
|
||||
declare class CameraPopoverOptions implements CameraPopoverOptions {
|
||||
constructor(x?: number, y?: number, width?: number, height?: number, arrowDir?: number);
|
||||
}
|
||||
|
||||
declare var Camera: {
|
||||
@@ -166,12 +121,4 @@ declare var Camera: {
|
||||
CAMERA: number;
|
||||
SAVEDPHOTOALBUM: number;
|
||||
}
|
||||
// Used only on iOS
|
||||
PopoverArrowDirection: {
|
||||
ARROW_UP: number;
|
||||
ARROW_DOWN: number;
|
||||
ARROW_LEFT: number;
|
||||
ARROW_RIGHT: number;
|
||||
ARROW_ANY: number;
|
||||
}
|
||||
};
|
||||
|
||||
@@ -22,8 +22,6 @@
|
||||
const argscheck = require('cordova/argscheck');
|
||||
const exec = require('cordova/exec');
|
||||
const Camera = require('./Camera');
|
||||
// XXX: commented out
|
||||
// CameraPopoverHandle = require('./CameraPopoverHandle');
|
||||
|
||||
/**
|
||||
* @namespace navigator
|
||||
@@ -73,7 +71,6 @@ for (const key in Camera) {
|
||||
* @property {module:Camera.MediaType} [mediaType=PICTURE] - Set the type of media to select from. Only works when `PictureSourceType` is `PHOTOLIBRARY` or `SAVEDPHOTOALBUM`.
|
||||
* @property {Boolean} [correctOrientation] - Rotate the image to correct for the orientation of the device during capture.
|
||||
* @property {Boolean} [saveToPhotoAlbum] - Save the image to the photo album on the device after capture.
|
||||
* @property {module:CameraPopoverOptions} [popoverOptions] - iOS-only options that specify popover location in iPad.
|
||||
* @property {module:Camera.Direction} [cameraDirection=BACK] - Choose the camera to use (front- or back-facing).
|
||||
*/
|
||||
|
||||
@@ -140,15 +137,16 @@ cameraExport.getPicture = function (successCallback, errorCallback, options) {
|
||||
const allowEdit = !!options.allowEdit;
|
||||
const correctOrientation = !!options.correctOrientation;
|
||||
const saveToPhotoAlbum = !!options.saveToPhotoAlbum;
|
||||
const popoverOptions = getValue(options.popoverOptions, null);
|
||||
const cameraDirection = getValue(options.cameraDirection, Camera.Direction.BACK);
|
||||
|
||||
if (allowEdit) {
|
||||
console.warn('allowEdit is deprecated. It does not work reliably on all platforms. Utilise a dedicated image editing library instead. allowEdit functionality is scheduled to be removed in a future release.');
|
||||
}
|
||||
|
||||
const args = [quality, destinationType, sourceType, targetWidth, targetHeight, encodingType,
|
||||
mediaType, allowEdit, correctOrientation, saveToPhotoAlbum, popoverOptions, cameraDirection];
|
||||
mediaType, allowEdit, correctOrientation, saveToPhotoAlbum, cameraDirection];
|
||||
|
||||
exec(successCallback, errorCallback, 'Camera', 'takePicture', args);
|
||||
// XXX: commented out
|
||||
// return new CameraPopoverHandle();
|
||||
};
|
||||
|
||||
/**
|
||||
|
||||
@@ -62,24 +62,19 @@ module.exports = {
|
||||
* @enum {number}
|
||||
*/
|
||||
PictureSourceType: {
|
||||
/** Choose image from the device's photo library (same as SAVEDPHOTOALBUM for Android) */
|
||||
/**
|
||||
* Choose image from the device's photo library.
|
||||
**/
|
||||
PHOTOLIBRARY: 0,
|
||||
/** Take picture from camera */
|
||||
CAMERA: 1,
|
||||
/** Choose image only from the device's Camera Roll album (same as PHOTOLIBRARY for Android) */
|
||||
/**
|
||||
* Same as PHOTOLIBRARY, when running on Android, or iOS 14+.
|
||||
* On iOS older than 14, an image can only be chosen from the device's Camera Roll album
|
||||
* with this setting.
|
||||
**/
|
||||
SAVEDPHOTOALBUM: 2
|
||||
},
|
||||
/**
|
||||
* Matches iOS UIPopoverArrowDirection constants to specify arrow location on popover.
|
||||
* @enum {number}
|
||||
*/
|
||||
PopoverArrowDirection: {
|
||||
ARROW_UP: 1,
|
||||
ARROW_DOWN: 2,
|
||||
ARROW_LEFT: 4,
|
||||
ARROW_RIGHT: 8,
|
||||
ARROW_ANY: 15
|
||||
},
|
||||
/**
|
||||
* @enum {number}
|
||||
*/
|
||||
|
||||
@@ -1,32 +0,0 @@
|
||||
/*
|
||||
*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*
|
||||
*/
|
||||
|
||||
/**
|
||||
* @ignore in favour of iOS' one
|
||||
* A handle to an image picker popover.
|
||||
*/
|
||||
const CameraPopoverHandle = function () {
|
||||
this.setPosition = function (popoverOptions) {
|
||||
console.log('CameraPopoverHandle.setPosition is only supported on iOS.');
|
||||
};
|
||||
};
|
||||
|
||||
module.exports = CameraPopoverHandle;
|
||||
@@ -1,56 +0,0 @@
|
||||
/*
|
||||
*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*
|
||||
*/
|
||||
|
||||
const Camera = require('./Camera');
|
||||
|
||||
/**
|
||||
* @namespace navigator
|
||||
*/
|
||||
|
||||
/**
|
||||
* iOS-only parameters that specify the anchor element location and arrow
|
||||
* direction of the popover when selecting images from an iPad's library
|
||||
* or album.
|
||||
* Note that the size of the popover may change to adjust to the
|
||||
* direction of the arrow and orientation of the screen. Make sure to
|
||||
* account for orientation changes when specifying the anchor element
|
||||
* location.
|
||||
* @module CameraPopoverOptions
|
||||
* @param {Number} [x=0] - x pixel coordinate of screen element onto which to anchor the popover.
|
||||
* @param {Number} [y=32] - y pixel coordinate of screen element onto which to anchor the popover.
|
||||
* @param {Number} [width=320] - width, in pixels, of the screen element onto which to anchor the popover.
|
||||
* @param {Number} [height=480] - height, in pixels, of the screen element onto which to anchor the popover.
|
||||
* @param {module:Camera.PopoverArrowDirection} [arrowDir=ARROW_ANY] - Direction the arrow on the popover should point.
|
||||
* @param {Number} [popoverWidth=0] - width of the popover (0 or not specified will use apple's default width).
|
||||
* @param {Number} [popoverHeight=0] - height of the popover (0 or not specified will use apple's default height).
|
||||
*/
|
||||
const CameraPopoverOptions = function (x, y, width, height, arrowDir, popoverWidth, popoverHeight) {
|
||||
// information of rectangle that popover should be anchored to
|
||||
this.x = x || 0;
|
||||
this.y = y || 32;
|
||||
this.width = width || 320;
|
||||
this.height = height || 480;
|
||||
this.arrowDir = arrowDir || Camera.PopoverArrowDirection.ARROW_ANY;
|
||||
this.popoverWidth = popoverWidth || 0;
|
||||
this.popoverHeight = popoverHeight || 0;
|
||||
};
|
||||
|
||||
module.exports = CameraPopoverOptions;
|
||||
@@ -1,66 +0,0 @@
|
||||
/*
|
||||
*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*
|
||||
*/
|
||||
|
||||
const exec = require('cordova/exec');
|
||||
|
||||
/**
|
||||
* @namespace navigator
|
||||
*/
|
||||
|
||||
/**
|
||||
* A handle to an image picker popover.
|
||||
*
|
||||
* __Supported Platforms__
|
||||
*
|
||||
* - iOS
|
||||
*
|
||||
* @example
|
||||
* navigator.camera.getPicture(onSuccess, onFail,
|
||||
* {
|
||||
* destinationType: Camera.DestinationType.FILE_URI,
|
||||
* sourceType: Camera.PictureSourceType.PHOTOLIBRARY,
|
||||
* popoverOptions: new CameraPopoverOptions(300, 300, 100, 100, Camera.PopoverArrowDirection.ARROW_ANY, 300, 600)
|
||||
* });
|
||||
*
|
||||
* // Reposition the popover if the orientation changes.
|
||||
* window.onorientationchange = function() {
|
||||
* var cameraPopoverHandle = new CameraPopoverHandle();
|
||||
* var cameraPopoverOptions = new CameraPopoverOptions(0, 0, 100, 100, Camera.PopoverArrowDirection.ARROW_ANY, 400, 500);
|
||||
* cameraPopoverHandle.setPosition(cameraPopoverOptions);
|
||||
* }
|
||||
* @module CameraPopoverHandle
|
||||
*/
|
||||
const CameraPopoverHandle = function () {
|
||||
/**
|
||||
* Can be used to reposition the image selection dialog,
|
||||
* for example, when the device orientation changes.
|
||||
* @memberof CameraPopoverHandle
|
||||
* @instance
|
||||
* @method setPosition
|
||||
* @param {module:CameraPopoverOptions} popoverOptions
|
||||
*/
|
||||
this.setPosition = function (popoverOptions) {
|
||||
const args = [popoverOptions];
|
||||
exec(null, null, 'Camera', 'repositionPopover', args);
|
||||
};
|
||||
};
|
||||
|
||||
module.exports = CameraPopoverHandle;
|
||||
Reference in New Issue
Block a user