From a6b4925ff576f885acea9f7644aba08e9d273a57 Mon Sep 17 00:00:00 2001 From: jcesarmobile Date: Thu, 28 May 2026 11:29:28 +0200 Subject: [PATCH] chore: remove camera code --- .github/workflows/publish-ios.yml | 2 +- camera/.eslintignore | 2 - camera/.gitignore | 69 -- camera/CHANGELOG.md | 471 --------- camera/CapacitorCamera.podspec | 17 - camera/LICENSE | 23 - camera/Package.swift | 27 - camera/README.md | 358 ------- camera/android/.gitignore | 1 - camera/android/build.gradle | 83 -- camera/android/gradle.properties | 22 - .../android/gradle/wrapper/gradle-wrapper.jar | Bin 43764 -> 0 bytes .../gradle/wrapper/gradle-wrapper.properties | 7 - camera/android/gradlew | 251 ----- camera/android/gradlew.bat | 94 -- camera/android/proguard-rules.pro | 21 - camera/android/settings.gradle | 2 - .../android/ExampleInstrumentedTest.java | 26 - camera/android/src/main/AndroidManifest.xml | 7 - .../CameraBottomSheetDialogFragment.java | 122 --- .../plugins/camera/CameraPlugin.java | 911 ------------------ .../plugins/camera/CameraResultType.java | 17 - .../plugins/camera/CameraSettings.java | 90 -- .../plugins/camera/CameraSource.java | 17 - .../plugins/camera/CameraUtils.java | 34 - .../plugins/camera/ExifWrapper.java | 205 ---- .../plugins/camera/ImageUtils.java | 124 --- .../com/getcapacitor/ExampleUnitTest.java | 18 - camera/ios/.gitignore | 8 - .../CameraPlugin/CameraExtensions.swift | 100 -- .../Sources/CameraPlugin/CameraPlugin.swift | 553 ----------- .../Sources/CameraPlugin/CameraTypes.swift | 142 --- .../ios/Sources/CameraPlugin/ImageSaver.swift | 20 - .../CameraPluginTests/CameraPluginTests.swift | 12 - camera/package.json | 83 -- camera/rollup.config.mjs | 22 - camera/src/definitions.ts | 362 ------- camera/src/index.ts | 11 - camera/src/web.ts | 277 ------ camera/tsconfig.json | 20 - 40 files changed, 1 insertion(+), 4630 deletions(-) delete mode 100644 camera/.eslintignore delete mode 100644 camera/.gitignore delete mode 100644 camera/CHANGELOG.md delete mode 100644 camera/CapacitorCamera.podspec delete mode 100644 camera/LICENSE delete mode 100644 camera/Package.swift delete mode 100644 camera/README.md delete mode 100644 camera/android/.gitignore delete mode 100644 camera/android/build.gradle delete mode 100644 camera/android/gradle.properties delete mode 100644 camera/android/gradle/wrapper/gradle-wrapper.jar delete mode 100644 camera/android/gradle/wrapper/gradle-wrapper.properties delete mode 100755 camera/android/gradlew delete mode 100644 camera/android/gradlew.bat delete mode 100644 camera/android/proguard-rules.pro delete mode 100644 camera/android/settings.gradle delete mode 100644 camera/android/src/androidTest/java/com/getcapacitor/android/ExampleInstrumentedTest.java delete mode 100644 camera/android/src/main/AndroidManifest.xml delete mode 100644 camera/android/src/main/java/com/capacitorjs/plugins/camera/CameraBottomSheetDialogFragment.java delete mode 100644 camera/android/src/main/java/com/capacitorjs/plugins/camera/CameraPlugin.java delete mode 100644 camera/android/src/main/java/com/capacitorjs/plugins/camera/CameraResultType.java delete mode 100644 camera/android/src/main/java/com/capacitorjs/plugins/camera/CameraSettings.java delete mode 100644 camera/android/src/main/java/com/capacitorjs/plugins/camera/CameraSource.java delete mode 100644 camera/android/src/main/java/com/capacitorjs/plugins/camera/CameraUtils.java delete mode 100644 camera/android/src/main/java/com/capacitorjs/plugins/camera/ExifWrapper.java delete mode 100644 camera/android/src/main/java/com/capacitorjs/plugins/camera/ImageUtils.java delete mode 100644 camera/android/src/test/java/com/getcapacitor/ExampleUnitTest.java delete mode 100644 camera/ios/.gitignore delete mode 100644 camera/ios/Sources/CameraPlugin/CameraExtensions.swift delete mode 100644 camera/ios/Sources/CameraPlugin/CameraPlugin.swift delete mode 100644 camera/ios/Sources/CameraPlugin/CameraTypes.swift delete mode 100644 camera/ios/Sources/CameraPlugin/ImageSaver.swift delete mode 100644 camera/ios/Tests/CameraPluginTests/CameraPluginTests.swift delete mode 100644 camera/package.json delete mode 100644 camera/rollup.config.mjs delete mode 100644 camera/src/definitions.ts delete mode 100644 camera/src/index.ts delete mode 100644 camera/src/web.ts delete mode 100644 camera/tsconfig.json diff --git a/.github/workflows/publish-ios.yml b/.github/workflows/publish-ios.yml index c0cbb5ae5..867f76a93 100644 --- a/.github/workflows/publish-ios.yml +++ b/.github/workflows/publish-ios.yml @@ -4,7 +4,7 @@ on: workflow_dispatch: inputs: plugins: - description: 'Specify a set of plugins to publish (as a JSON array, ex: [\"camera\", \"browser\"])' + description: 'Specify a set of plugins to publish (as a JSON array, ex: [\"app\", \"browser\"])' required: true jobs: publish-ios: diff --git a/camera/.eslintignore b/camera/.eslintignore deleted file mode 100644 index 9d0b71a3c..000000000 --- a/camera/.eslintignore +++ /dev/null @@ -1,2 +0,0 @@ -build -dist diff --git a/camera/.gitignore b/camera/.gitignore deleted file mode 100644 index 681763795..000000000 --- a/camera/.gitignore +++ /dev/null @@ -1,69 +0,0 @@ -# node files -dist -node_modules - -# iOS files -Pods -Podfile.lock -Package.resolved -Build -xcuserdata -/.build -/Packages -xcuserdata/ -DerivedData/ -.swiftpm/configuration/registries.json -.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata -.netrc - -# macOS files -.DS_Store - - - -# Based on Android gitignore template: https://github.com/github/gitignore/blob/HEAD/Android.gitignore - -# Built application files -*.apk -*.ap_ - -# Files for the ART/Dalvik VM -*.dex - -# Java class files -*.class - -# Generated files -bin -gen -out - -# Gradle files -.gradle -build - -# Local configuration file (sdk path, etc) -local.properties - -# Proguard folder generated by Eclipse -proguard - -# Log Files -*.log - -# Android Studio Navigation editor temp files -.navigation - -# Android Studio captures folder -captures - -# IntelliJ -*.iml -.idea - -# Keystore files -# Uncomment the following line if you do not want to check your keystore files in. -#*.jks - -# External native build folder generated in Android Studio 2.2 and later -.externalNativeBuild diff --git a/camera/CHANGELOG.md b/camera/CHANGELOG.md deleted file mode 100644 index a4757fa74..000000000 --- a/camera/CHANGELOG.md +++ /dev/null @@ -1,471 +0,0 @@ -# ⓘ Plugin migrated - -**From version 8.1.0 onwards, this plugin is now hosted in a separate repository. Refer to the [updated CHANGELOG](https://github.com/ionic-team/capacitor-camera/blob/main/CHANGELOG.md).** - -# Change Log - -All notable changes to this project will be documented in this file. -See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. - -## [8.0.2](https://github.com/ionic-team/capacitor-plugins/compare/@capacitor/camera@8.0.1...@capacitor/camera@8.0.2) (2026-03-06) - -### Bug Fixes - -- **camera:** Allow cancelation of sheet in Web, requires pwa-elements 3.4.0 or higher ([#2284](https://github.com/ionic-team/capacitor-plugins/issues/2284)) ([06cf611](https://github.com/ionic-team/capacitor-plugins/commit/06cf611be3822cd41be8a4e314bf4a1b4e9b551d)) - -## [8.0.1](https://github.com/ionic-team/capacitor-plugins/compare/@capacitor/camera@8.0.0...@capacitor/camera@8.0.1) (2026-02-12) - -### Bug Fixes - -- AGP 9.0 no longer supporting `proguard-android.txt` ([#2468](https://github.com/ionic-team/capacitor-plugins/issues/2468)) ([a8760a9](https://github.com/ionic-team/capacitor-plugins/commit/a8760a989f594bc406d0ec7da58125d17447cae4)) - -# [8.0.0](https://github.com/ionic-team/capacitor-plugins/compare/@capacitor/camera@8.0.0-beta.0...@capacitor/camera@8.0.0) (2025-12-08) - -**Note:** Version bump only for package @capacitor/camera - -# [8.0.0-beta.0](https://github.com/ionic-team/capacitor-plugins/compare/@capacitor/camera@8.0.0-alpha.1...@capacitor/camera@8.0.0-beta.0) (2025-11-14) - -**Note:** Version bump only for package @capacitor/camera - -# [8.0.0-alpha.1](https://github.com/ionic-team/capacitor-plugins/compare/@capacitor/camera@7.0.2...@capacitor/camera@8.0.0-alpha.1) (2025-09-08) - -**Note:** Version bump only for package @capacitor/camera - -## [7.0.2](https://github.com/ionic-team/capacitor-plugins/compare/@capacitor/camera@7.0.1...@capacitor/camera@7.0.2) (2025-08-05) - -### Bug Fixes - -- **camera:** requestPermissions on Android 13+ ([#2393](https://github.com/ionic-team/capacitor-plugins/issues/2393)) ([4d707a7](https://github.com/ionic-team/capacitor-plugins/commit/4d707a7353de44c9663f661ce869cb99429b1ca5)) - -## [7.0.1](https://github.com/ionic-team/capacitor-plugins/compare/@capacitor/camera@7.0.0...@capacitor/camera@7.0.1) (2025-04-02) - -**Note:** Version bump only for package @capacitor/camera - -# [7.0.0](https://github.com/ionic-team/capacitor-plugins/compare/@capacitor/camera@7.0.0-rc.0...@capacitor/camera@7.0.0) (2025-01-20) - -**Note:** Version bump only for package @capacitor/camera - -# [7.0.0-rc.0](https://github.com/ionic-team/capacitor-plugins/compare/@capacitor/camera@7.0.0-alpha.2...@capacitor/camera@7.0.0-rc.0) (2025-01-13) - -**Note:** Version bump only for package @capacitor/camera - -# [7.0.0-alpha.2](https://github.com/ionic-team/capacitor-plugins/compare/@capacitor/camera@7.0.0-alpha.1...@capacitor/camera@7.0.0-alpha.2) (2024-12-19) - -**Note:** Version bump only for package @capacitor/camera - -# [7.0.0-alpha.1](https://github.com/ionic-team/capacitor-plugins/compare/@capacitor/camera@6.0.2...@capacitor/camera@7.0.0-alpha.1) (2024-12-16) - -### Bug Fixes - -- **camera:** Android dialog not fully showing when in landscape ([#2276](https://github.com/ionic-team/capacitor-plugins/issues/2276)) ([123193b](https://github.com/ionic-team/capacitor-plugins/commit/123193b9cf19784c69e1fd382516a319779e36a5)) - -### Features - -- **camera:** only request permission to save to the gallery for Android <= 9 ([#2222](https://github.com/ionic-team/capacitor-plugins/issues/2222)) ([30da38e](https://github.com/ionic-team/capacitor-plugins/commit/30da38ee9a92da1c5bbeb301fa6371a43365dfeb)) - -## [6.0.2](https://github.com/ionic-team/capacitor-plugins/compare/@capacitor/camera@6.0.1...@capacitor/camera@6.0.2) (2024-08-08) - -**Note:** Version bump only for package @capacitor/camera - -## [6.0.1](https://github.com/ionic-team/capacitor-plugins/compare/@capacitor/camera@6.0.0...@capacitor/camera@6.0.1) (2024-06-13) - -### Bug Fixes - -- **ios:** iOS panorama photos selected through CameraPlugin are corrupted ([#2090](https://github.com/ionic-team/capacitor-plugins/issues/2090)) ([998e495](https://github.com/ionic-team/capacitor-plugins/commit/998e4950d539cb2ebe38818d7f6046ac4b4b8950)) -- **ios:** Picking ProRAW pictures from Gallery ([#2098](https://github.com/ionic-team/capacitor-plugins/issues/2098)) ([20b9e26](https://github.com/ionic-team/capacitor-plugins/commit/20b9e26b1b0b10228c85049b4093a5ed9cfe64c5)) - -# [6.0.0](https://github.com/ionic-team/capacitor-plugins/compare/@capacitor/camera@6.0.0-rc.1...@capacitor/camera@6.0.0) (2024-04-15) - -**Note:** Version bump only for package @capacitor/camera - -# [6.0.0-rc.1](https://github.com/ionic-team/capacitor-plugins/compare/@capacitor/camera@6.0.0-rc.0...@capacitor/camera@6.0.0-rc.1) (2024-03-25) - -**Note:** Version bump only for package @capacitor/camera - -# [6.0.0-rc.0](https://github.com/ionic-team/capacitor-plugins/compare/@capacitor/camera@6.0.0-beta.1...@capacitor/camera@6.0.0-rc.0) (2024-02-07) - -**Note:** Version bump only for package @capacitor/camera - -# [6.0.0-beta.1](https://github.com/ionic-team/capacitor-plugins/compare/@capacitor/camera@6.0.0-beta.0...@capacitor/camera@6.0.0-beta.1) (2023-12-14) - -### Bug Fixes - -- **camera:** reject promise on web input cancel event ([#1958](https://github.com/ionic-team/capacitor-plugins/issues/1958)) ([d218ba6](https://github.com/ionic-team/capacitor-plugins/commit/d218ba6c53250b990f4913bd121915cfd3d65d72)) - -# [6.0.0-beta.0](https://github.com/ionic-team/capacitor-plugins/compare/@capacitor/camera@6.0.0-alpha.2...@capacitor/camera@6.0.0-beta.0) (2023-12-13) - -**Note:** Version bump only for package @capacitor/camera - -# [6.0.0-alpha.2](https://github.com/ionic-team/capacitor-plugins/compare/@capacitor/camera@6.0.0-alpha.1...@capacitor/camera@6.0.0-alpha.2) (2023-11-15) - -**Note:** Version bump only for package @capacitor/camera - -# [6.0.0-alpha.1](https://github.com/ionic-team/capacitor-plugins/compare/@capacitor/camera@5.0.7...@capacitor/camera@6.0.0-alpha.1) (2023-11-08) - -**Note:** Version bump only for package @capacitor/camera - -## [5.0.8](https://github.com/ionic-team/capacitor-plugins/compare/@capacitor/camera@5.0.7...@capacitor/camera@5.0.8) (2023-12-15) - -### Bug Fixes - -- **camera:** reject promise on web input cancel event ([#1964](https://github.com/ionic-team/capacitor-plugins/issues/1964)) ([77dc373](https://github.com/ionic-team/capacitor-plugins/commit/77dc373b41fb5fee9fb7acdb511564043494bb10)) - -## [5.0.7](https://github.com/ionic-team/capacitor-plugins/compare/@capacitor/camera@5.0.6...@capacitor/camera@5.0.7) (2023-08-09) - -### Bug Fixes - -- **camera:** Request only the permissions needed by Android version ([#1713](https://github.com/ionic-team/capacitor-plugins/issues/1713)) ([f1585d6](https://github.com/ionic-team/capacitor-plugins/commit/f1585d661b2234e0c63afa098c92618dd630282e)) - -## [5.0.6](https://github.com/ionic-team/capacitor-plugins/compare/@capacitor/camera@5.0.5...@capacitor/camera@5.0.6) (2023-07-12) - -**Note:** Version bump only for package @capacitor/camera - -## [5.0.5](https://github.com/ionic-team/capacitor-plugins/compare/@capacitor/camera@5.0.4...@capacitor/camera@5.0.5) (2023-06-29) - -**Note:** Version bump only for package @capacitor/camera - -## [5.0.4](https://github.com/ionic-team/capacitor-plugins/compare/@capacitor/camera@5.0.3...@capacitor/camera@5.0.4) (2023-06-08) - -**Note:** Version bump only for package @capacitor/camera - -## [5.0.3](https://github.com/ionic-team/capacitor-plugins/compare/@capacitor/camera@5.0.2...@capacitor/camera@5.0.3) (2023-06-08) - -**Note:** Version bump only for package @capacitor/camera - -## [5.0.2](https://github.com/ionic-team/capacitor-plugins/compare/@capacitor/camera@5.0.1...@capacitor/camera@5.0.2) (2023-05-09) - -**Note:** Version bump only for package @capacitor/camera - -## [5.0.1](https://github.com/ionic-team/capacitor-plugins/compare/@capacitor/camera@5.0.0...@capacitor/camera@5.0.1) (2023-05-05) - -### Bug Fixes - -- Use Capacitor 5 final ([#1574](https://github.com/ionic-team/capacitor-plugins/issues/1574)) ([139c18b](https://github.com/ionic-team/capacitor-plugins/commit/139c18b86a11d31246e952d1a74335ff8ce5dbc2)) - -# [5.0.0](https://github.com/ionic-team/capacitor-plugins/compare/@capacitor/camera@5.0.0-beta.1...@capacitor/camera@5.0.0) (2023-05-03) - -**Note:** Version bump only for package @capacitor/camera - -# [5.0.0-beta.1](https://github.com/ionic-team/capacitor-plugins/compare/@capacitor/camera@5.0.0-beta.0...@capacitor/camera@5.0.0-beta.1) (2023-04-21) - -### Features - -- Update gradle to 8.0.2 and gradle plugin to 8.0.0 ([#1542](https://github.com/ionic-team/capacitor-plugins/issues/1542)) ([e7210b4](https://github.com/ionic-team/capacitor-plugins/commit/e7210b47867644f5983e37acdbf0247214ec232d)) - -# [5.0.0-beta.0](https://github.com/ionic-team/capacitor-plugins/compare/@capacitor/camera@5.0.0-alpha.1...@capacitor/camera@5.0.0-beta.0) (2023-03-31) - -### Bug Fixes - -- **camera:** add proper permissions for Android 13 ([#1509](https://github.com/ionic-team/capacitor-plugins/issues/1509)) ([0dcbe56](https://github.com/ionic-team/capacitor-plugins/commit/0dcbe56bc554b1919c3e26d2f6b7ff8e5b7a0f5e)) -- **camera:** prevent iOS crash with 0 limited images selected ([#1495](https://github.com/ionic-team/capacitor-plugins/issues/1495)) ([33f5c8e](https://github.com/ionic-team/capacitor-plugins/commit/33f5c8ebc7705c0e697caee4b2177ebc27d46311)) - -# [5.0.0-alpha.1](https://github.com/ionic-team/capacitor-plugins/compare/@capacitor/camera@4.1.4...@capacitor/camera@5.0.0-alpha.1) (2023-03-16) - -### Bug Fixes - -- **camera:** Handle null permissions list ([#1457](https://github.com/ionic-team/capacitor-plugins/issues/1457)) ([fcd28e9](https://github.com/ionic-team/capacitor-plugins/commit/fcd28e95420207f0e194f9fecab12415c400cba5)) - -### Features - -- **android:** Removing enableJetifier ([d66f9cb](https://github.com/ionic-team/capacitor-plugins/commit/d66f9cbd9da7e3b1d8c64ca6a5b45156867d4a04)) - -## [4.1.4](https://github.com/ionic-team/capacitor-plugins/compare/@capacitor/camera@4.1.3...@capacitor/camera@4.1.4) (2022-11-16) - -**Note:** Version bump only for package @capacitor/camera - -## [4.1.3](https://github.com/ionic-team/capacitor-plugins/compare/@capacitor/camera@4.1.2...@capacitor/camera@4.1.3) (2022-10-21) - -**Note:** Version bump only for package @capacitor/camera - -## [4.1.2](https://github.com/ionic-team/capacitor-plugins/compare/@capacitor/camera@4.1.1...@capacitor/camera@4.1.2) (2022-09-29) - -### Bug Fixes - -- **camera:** make pickLimitedLibraryPhotos return photos on iOS 15+ ([#1191](https://github.com/ionic-team/capacitor-plugins/issues/1191)) ([a65c8ca](https://github.com/ionic-team/capacitor-plugins/commit/a65c8ca8582c15e16ece369b77d1eac5df43e60e)) - -## [4.1.1](https://github.com/ionic-team/capacitor-plugins/compare/@capacitor/camera@4.1.0...@capacitor/camera@4.1.1) (2022-09-12) - -**Note:** Version bump only for package @capacitor/camera - -# [4.1.0](https://github.com/ionic-team/capacitor-plugins/compare/@capacitor/camera@1.3.1...@capacitor/camera@4.1.0) (2022-08-24) - -### Features - -- **camera:** Add support for iOS limited photo library mode ([#1125](https://github.com/ionic-team/capacitor-plugins/issues/1125)) ([cc5e4e6](https://github.com/ionic-team/capacitor-plugins/commit/cc5e4e683a2b9a9d216fac9ee88e8653a7ca68c6)) - -## [4.0.1](https://github.com/ionic-team/capacitor-plugins/compare/4.0.0...4.0.1) (2022-07-28) - -**Note:** Version bump only for package @capacitor/camera - -# [4.0.0](https://github.com/ionic-team/capacitor-plugins/compare/4.0.0-beta.2...4.0.0) (2022-07-27) - -**Note:** Version bump only for package @capacitor/camera - -# [4.0.0-beta.2](https://github.com/ionic-team/capacitor-plugins/compare/4.0.0-beta.0...4.0.0-beta.2) (2022-07-08) - -**Note:** Version bump only for package @capacitor/camera - -# 4.0.0-beta.0 (2022-06-27) - -### Bug Fixes - -- **camera:** Append change listener only once ([#486](https://github.com/ionic-team/capacitor-plugins/issues/486)) ([5b7021e](https://github.com/ionic-team/capacitor-plugins/commit/5b7021e210649f8501a20ba6549903ecb6d42dcd)) -- **camera:** Append exif to android images ([#480](https://github.com/ionic-team/capacitor-plugins/issues/480)) ([cad8a30](https://github.com/ionic-team/capacitor-plugins/commit/cad8a30c562202fb819a4d260d5307f1b6b8fa44)) -- **camera:** avoid error if image has no orientation ([#554](https://github.com/ionic-team/capacitor-plugins/issues/554)) ([dc8a55a](https://github.com/ionic-team/capacitor-plugins/commit/dc8a55a71cdaaf7ad86aee8470a0c7b8284653c4)) -- **camera:** cleanup camera images if not needed ([#563](https://github.com/ionic-team/capacitor-plugins/issues/563)) ([a2e4f43](https://github.com/ionic-team/capacitor-plugins/commit/a2e4f4339119698e8dd066a5f2f8f065ab2e4727)) -- **camera:** correct photo resizing on iOS ([#460](https://github.com/ionic-team/capacitor-plugins/issues/460)) ([bc56e03](https://github.com/ionic-team/capacitor-plugins/commit/bc56e034c711b172a7ff503cabd2970adbc14b86)) -- **camera:** decode content uri when retrieving image from gallery ([#277](https://github.com/ionic-team/capacitor-plugins/issues/277)) ([a6cd1ad](https://github.com/ionic-team/capacitor-plugins/commit/a6cd1adc241bf21e4f7f06d24c0db4a4d7382dbc)) -- **camera:** Don't save gallery images on iOS 14+ ([#696](https://github.com/ionic-team/capacitor-plugins/issues/696)) ([7b2cc88](https://github.com/ionic-team/capacitor-plugins/commit/7b2cc88f6e83265c991ae9f81cfc3f6bed346250)) -- **camera:** fix camera source on Android ([#164](https://github.com/ionic-team/capacitor-plugins/issues/164)) ([e67f7c6](https://github.com/ionic-team/capacitor-plugins/commit/e67f7c6b06b20d7c3e8f0925c40fd75d23d9d717)) -- **camera:** Make allowEdit work on all devices ([#552](https://github.com/ionic-team/capacitor-plugins/issues/552)) ([5224177](https://github.com/ionic-team/capacitor-plugins/commit/5224177f77bdce1c8f028e2cef41614fa687502f)) -- **camera:** Make input file hidden ([#484](https://github.com/ionic-team/capacitor-plugins/issues/484)) ([cdc1835](https://github.com/ionic-team/capacitor-plugins/commit/cdc1835f3bbfb8db8e18fccace6103d83dd9edaa)) -- **camera:** Make web use source options ([#487](https://github.com/ionic-team/capacitor-plugins/issues/487)) ([7870e6b](https://github.com/ionic-team/capacitor-plugins/commit/7870e6b6ca196265640fc0ba3c1f52ddca075607)) -- **camera:** process picked image only once ([#782](https://github.com/ionic-team/capacitor-plugins/issues/782)) ([897dcaf](https://github.com/ionic-team/capacitor-plugins/commit/897dcaf839a6cb83256485c32df2ca0e7b439124)) -- **camera:** Properly reset orientation exif if corrected ([#545](https://github.com/ionic-team/capacitor-plugins/issues/545)) ([ad8c325](https://github.com/ionic-team/capacitor-plugins/commit/ad8c325af0a2459f5a7788be08a8da4118717671)) -- **camera:** query IMAGE_CAPTURE intent required by SDK 30 ([#160](https://github.com/ionic-team/capacitor-plugins/issues/160)) ([6484991](https://github.com/ionic-team/capacitor-plugins/commit/6484991d76d57bac0cbc82b9f050e146ec4732da)) -- **camera:** Remove capture attribute from multiple photo picker ([#687](https://github.com/ionic-team/capacitor-plugins/issues/687)) ([e551ef7](https://github.com/ionic-team/capacitor-plugins/commit/e551ef77eebe331cc7bf13c9c0eab5a0bd2da0d1)) -- **camera:** Remove unused saveCall ([#401](https://github.com/ionic-team/capacitor-plugins/issues/401)) ([95920da](https://github.com/ionic-team/capacitor-plugins/commit/95920da4d1844ed76a162651d5492a22a4038d26)) -- **camera:** Reset exif orientation if corrected ([#510](https://github.com/ionic-team/capacitor-plugins/issues/510)) ([a65c05e](https://github.com/ionic-team/capacitor-plugins/commit/a65c05e0de8f53e7371c194047a75797d53879b5)) -- **camera:** Resize not respecting aspect ratio on iOS ([#568](https://github.com/ionic-team/capacitor-plugins/issues/568)) ([ea2b801](https://github.com/ionic-team/capacitor-plugins/commit/ea2b8012aab7e5ea34cfa34735f7f55ba76a3882)) -- **camera:** return original image if editing is cancelled ([#566](https://github.com/ionic-team/capacitor-plugins/issues/566)) ([4786841](https://github.com/ionic-team/capacitor-plugins/commit/4786841099403a4d3d59aaf9103e8fa02aa8e4e2)) -- **camera:** Return proper exif when picking multiple images ([#712](https://github.com/ionic-team/capacitor-plugins/issues/712)) ([8451237](https://github.com/ionic-team/capacitor-plugins/commit/8451237e46f24c59e74e350eaa9b31e6d99a68a0)) -- **camera:** return single picture on pickImages ([#783](https://github.com/ionic-team/capacitor-plugins/issues/783)) ([9d65db1](https://github.com/ionic-team/capacitor-plugins/commit/9d65db1e74117fd1c1e7cd9bbba7efaeb4c13e0c)) -- **camera:** saveToGallery for edited images ([#602](https://github.com/ionic-team/capacitor-plugins/issues/602)) ([b5ac27d](https://github.com/ionic-team/capacitor-plugins/commit/b5ac27d59181ec3acc2909b2569d8ab45a829b1c)) -- **camera:** set camera direction for web ([#665](https://github.com/ionic-team/capacitor-plugins/issues/665)) ([4afedb9](https://github.com/ionic-team/capacitor-plugins/commit/4afedb96f3b745a86d9cacd33ca71c42ae3fb8d4)) -- **camera:** Use Locale.ROOT on toUpperCase ([#812](https://github.com/ionic-team/capacitor-plugins/issues/812)) ([6d689ac](https://github.com/ionic-team/capacitor-plugins/commit/6d689acc48e3746ddd35bd5e1e8d7f239cb7f8df)) -- add es2017 lib to tsconfig ([#180](https://github.com/ionic-team/capacitor-plugins/issues/180)) ([2c3776c](https://github.com/ionic-team/capacitor-plugins/commit/2c3776c38ca025c5ee965dec10ccf1cdb6c02e2f)) -- correct addListeners links ([#655](https://github.com/ionic-team/capacitor-plugins/issues/655)) ([f9871e7](https://github.com/ionic-team/capacitor-plugins/commit/f9871e7bd53478addb21155e148829f550c0e457)) -- Correct missing source_files path ([#590](https://github.com/ionic-team/capacitor-plugins/issues/590)) ([24e0fc2](https://github.com/ionic-team/capacitor-plugins/commit/24e0fc27cc314049012ab9915fa5e7bfb03313e1)) -- inline source code in esm map files ([#760](https://github.com/ionic-team/capacitor-plugins/issues/760)) ([a960489](https://github.com/ionic-team/capacitor-plugins/commit/a960489a19db0182b90d187a50deff9dfbe51038)) -- remove postpublish scripts ([#656](https://github.com/ionic-team/capacitor-plugins/issues/656)) ([ed6ac49](https://github.com/ionic-team/capacitor-plugins/commit/ed6ac499ebf4a47525071ccbfc36c27503e11f60)) -- **camera:** return file URL for path, not system path ([#170](https://github.com/ionic-team/capacitor-plugins/issues/170)) ([8a9e5c3](https://github.com/ionic-team/capacitor-plugins/commit/8a9e5c3dba3b232a1cca9f9a1e9b4520022abc09)) -- **camera:** Return the full webPath ([#502](https://github.com/ionic-team/capacitor-plugins/issues/502)) ([e849732](https://github.com/ionic-team/capacitor-plugins/commit/e849732dbcf5e85d1df09835c53ff5738fbb4ded)) -- **camera:** set settings again on callbacks ([#595](https://github.com/ionic-team/capacitor-plugins/issues/595)) ([908bd68](https://github.com/ionic-team/capacitor-plugins/commit/908bd688767e374cf8e96b3def08bd33dcdfd2aa)) -- support deprecated types from Capacitor 2 ([#139](https://github.com/ionic-team/capacitor-plugins/issues/139)) ([2d7127a](https://github.com/ionic-team/capacitor-plugins/commit/2d7127a488e26f0287951921a6db47c49d817336)) - -### Features - -- set targetSDK default value to 31 ([#824](https://github.com/ionic-team/capacitor-plugins/issues/824)) ([3ee10de](https://github.com/ionic-team/capacitor-plugins/commit/3ee10de98067984c1a4e75295d001c5a895c47f4)) -- set targetSDK default value to 32 ([#970](https://github.com/ionic-team/capacitor-plugins/issues/970)) ([fa70d96](https://github.com/ionic-team/capacitor-plugins/commit/fa70d96f141af751aae53ceb5642c46b204f5958)) -- Upgrade gradle to 7.4 ([#826](https://github.com/ionic-team/capacitor-plugins/issues/826)) ([5db0906](https://github.com/ionic-team/capacitor-plugins/commit/5db0906f6264287c4f8e69dbaecf19d4d387824b)) -- Use java 11 ([#910](https://github.com/ionic-team/capacitor-plugins/issues/910)) ([5acb2a2](https://github.com/ionic-team/capacitor-plugins/commit/5acb2a288a413492b163e4e97da46a085d9e4be0)) -- **android:** implements Activity Result API changes for permissions and activity results ([#222](https://github.com/ionic-team/capacitor-plugins/issues/222)) ([f671b9f](https://github.com/ionic-team/capacitor-plugins/commit/f671b9f4b472806ef43db6dcf302d4503cf1828c)) -- **camera:** Add new method for multiple image picking from gallery ([#671](https://github.com/ionic-team/capacitor-plugins/issues/671)) ([a49c590](https://github.com/ionic-team/capacitor-plugins/commit/a49c5901683da12438fbafbd1bf6ae91133d18ed)) -- **camera:** Return if image was saved to gallery ([#599](https://github.com/ionic-team/capacitor-plugins/issues/599)) ([594af3b](https://github.com/ionic-team/capacitor-plugins/commit/594af3be0982371e6c61e4bdb830c6bbb3963913)) -- **camera:** Support for 1 Gallery app ([#791](https://github.com/ionic-team/capacitor-plugins/issues/791)) ([77e8c97](https://github.com/ionic-team/capacitor-plugins/commit/77e8c979394d5fb1804fc097ecaeee46a973e640)) -- **camera:** Support for Samsung Gallery app on pickImages ([#706](https://github.com/ionic-team/capacitor-plugins/issues/706)) ([fd059fc](https://github.com/ionic-team/capacitor-plugins/commit/fd059fcd2e53661e95e230f684a6d32408db6787)) -- **camera:** use a distinguishable permission denied string for camera and photos ([#379](https://github.com/ionic-team/capacitor-plugins/issues/379)) ([c71657f](https://github.com/ionic-team/capacitor-plugins/commit/c71657f7e14eae4efd4d2c7d00d77a7b329a7920)) -- **camera:** Use same error messages for permission deny ([#404](https://github.com/ionic-team/capacitor-plugins/issues/404)) ([fffcd47](https://github.com/ionic-team/capacitor-plugins/commit/fffcd47f0237b6997bfa4ce430ef29392047ea0e)) -- add commonjs output format ([#179](https://github.com/ionic-team/capacitor-plugins/issues/179)) ([8e9e098](https://github.com/ionic-team/capacitor-plugins/commit/8e9e09862064b3f6771d7facbc4008e995d9b463)) -- Camera plugin ([#33](https://github.com/ionic-team/capacitor-plugins/issues/33)) ([4864928](https://github.com/ionic-team/capacitor-plugins/commit/48649288b1ba45e1901ad077b3b7b7314de04d4a)) - -## [1.3.1](https://github.com/ionic-team/capacitor-plugins/compare/@capacitor/camera@1.3.0...@capacitor/camera@1.3.1) (2022-03-03) - -### Bug Fixes - -- **camera:** Return the image on dismiss completion ([#849](https://github.com/ionic-team/capacitor-plugins/issues/849)) ([f083841](https://github.com/ionic-team/capacitor-plugins/commit/f0838416c6cf731aaae83fcb4986568357878b41)) - -# [1.3.0](https://github.com/ionic-team/capacitor-plugins/compare/@capacitor/camera@1.2.4...@capacitor/camera@1.3.0) (2022-02-10) - -### Bug Fixes - -- **camera:** process picked image only once ([#782](https://github.com/ionic-team/capacitor-plugins/issues/782)) ([897dcaf](https://github.com/ionic-team/capacitor-plugins/commit/897dcaf839a6cb83256485c32df2ca0e7b439124)) -- **camera:** return single picture on pickImages ([#783](https://github.com/ionic-team/capacitor-plugins/issues/783)) ([9d65db1](https://github.com/ionic-team/capacitor-plugins/commit/9d65db1e74117fd1c1e7cd9bbba7efaeb4c13e0c)) -- **camera:** Use Locale.ROOT on toUpperCase ([#812](https://github.com/ionic-team/capacitor-plugins/issues/812)) ([6d689ac](https://github.com/ionic-team/capacitor-plugins/commit/6d689acc48e3746ddd35bd5e1e8d7f239cb7f8df)) - -### Features - -- **camera:** Support for 1 Gallery app ([#791](https://github.com/ionic-team/capacitor-plugins/issues/791)) ([77e8c97](https://github.com/ionic-team/capacitor-plugins/commit/77e8c979394d5fb1804fc097ecaeee46a973e640)) -- **camera:** Support for Samsung Gallery app on pickImages ([#706](https://github.com/ionic-team/capacitor-plugins/issues/706)) ([fd059fc](https://github.com/ionic-team/capacitor-plugins/commit/fd059fcd2e53661e95e230f684a6d32408db6787)) - -## [1.2.4](https://github.com/ionic-team/capacitor-plugins/compare/@capacitor/camera@1.2.3...@capacitor/camera@1.2.4) (2022-01-19) - -### Bug Fixes - -- inline source code in esm map files ([#760](https://github.com/ionic-team/capacitor-plugins/issues/760)) ([a960489](https://github.com/ionic-team/capacitor-plugins/commit/a960489a19db0182b90d187a50deff9dfbe51038)) - -## [1.2.3](https://github.com/ionic-team/capacitor-plugins/compare/@capacitor/camera@1.2.2...@capacitor/camera@1.2.3) (2022-01-10) - -### Bug Fixes - -- **camera:** set camera direction for web ([#665](https://github.com/ionic-team/capacitor-plugins/issues/665)) ([4afedb9](https://github.com/ionic-team/capacitor-plugins/commit/4afedb96f3b745a86d9cacd33ca71c42ae3fb8d4)) - -## [1.2.2](https://github.com/ionic-team/capacitor-plugins/compare/@capacitor/camera@1.2.1...@capacitor/camera@1.2.2) (2021-12-08) - -### Bug Fixes - -- **camera:** Return proper exif when picking multiple images ([#712](https://github.com/ionic-team/capacitor-plugins/issues/712)) ([8451237](https://github.com/ionic-team/capacitor-plugins/commit/8451237e46f24c59e74e350eaa9b31e6d99a68a0)) - -## [1.2.1](https://github.com/ionic-team/capacitor-plugins/compare/@capacitor/camera@1.2.0...@capacitor/camera@1.2.1) (2021-11-17) - -### Bug Fixes - -- **camera:** Don't save gallery images on iOS 14+ ([#696](https://github.com/ionic-team/capacitor-plugins/issues/696)) ([7b2cc88](https://github.com/ionic-team/capacitor-plugins/commit/7b2cc88f6e83265c991ae9f81cfc3f6bed346250)) -- **camera:** Remove capture attribute from multiple photo picker ([#687](https://github.com/ionic-team/capacitor-plugins/issues/687)) ([e551ef7](https://github.com/ionic-team/capacitor-plugins/commit/e551ef77eebe331cc7bf13c9c0eab5a0bd2da0d1)) - -# [1.2.0](https://github.com/ionic-team/capacitor-plugins/compare/@capacitor/camera@1.1.2...@capacitor/camera@1.2.0) (2021-11-03) - -### Features - -- **camera:** Add new method for multiple image picking from gallery ([#671](https://github.com/ionic-team/capacitor-plugins/issues/671)) ([a49c590](https://github.com/ionic-team/capacitor-plugins/commit/a49c5901683da12438fbafbd1bf6ae91133d18ed)) - -## [1.1.2](https://github.com/ionic-team/capacitor-plugins/compare/@capacitor/camera@1.1.1...@capacitor/camera@1.1.2) (2021-10-14) - -### Bug Fixes - -- remove postpublish scripts ([#656](https://github.com/ionic-team/capacitor-plugins/issues/656)) ([ed6ac49](https://github.com/ionic-team/capacitor-plugins/commit/ed6ac499ebf4a47525071ccbfc36c27503e11f60)) - -## [1.1.1](https://github.com/ionic-team/capacitor-plugins/compare/@capacitor/camera@1.1.0...@capacitor/camera@1.1.1) (2021-10-13) - -### Bug Fixes - -- correct addListeners links ([#655](https://github.com/ionic-team/capacitor-plugins/issues/655)) ([f9871e7](https://github.com/ionic-team/capacitor-plugins/commit/f9871e7bd53478addb21155e148829f550c0e457)) - -# [1.1.0](https://github.com/ionic-team/capacitor-plugins/compare/@capacitor/camera@1.0.5...@capacitor/camera@1.1.0) (2021-09-15) - -### Bug Fixes - -- **camera:** saveToGallery for edited images ([#602](https://github.com/ionic-team/capacitor-plugins/issues/602)) ([b5ac27d](https://github.com/ionic-team/capacitor-plugins/commit/b5ac27d59181ec3acc2909b2569d8ab45a829b1c)) -- **camera:** set settings again on callbacks ([#595](https://github.com/ionic-team/capacitor-plugins/issues/595)) ([908bd68](https://github.com/ionic-team/capacitor-plugins/commit/908bd688767e374cf8e96b3def08bd33dcdfd2aa)) - -### Features - -- **camera:** Return if image was saved to gallery ([#599](https://github.com/ionic-team/capacitor-plugins/issues/599)) ([594af3b](https://github.com/ionic-team/capacitor-plugins/commit/594af3be0982371e6c61e4bdb830c6bbb3963913)) - -## [1.0.5](https://github.com/ionic-team/capacitor-plugins/compare/@capacitor/camera@1.0.4...@capacitor/camera@1.0.5) (2021-09-01) - -### Bug Fixes - -- Correct missing source_files path ([#590](https://github.com/ionic-team/capacitor-plugins/issues/590)) ([24e0fc2](https://github.com/ionic-team/capacitor-plugins/commit/24e0fc27cc314049012ab9915fa5e7bfb03313e1)) -- **camera:** cleanup camera images if not needed ([#563](https://github.com/ionic-team/capacitor-plugins/issues/563)) ([a2e4f43](https://github.com/ionic-team/capacitor-plugins/commit/a2e4f4339119698e8dd066a5f2f8f065ab2e4727)) -- **camera:** Resize not respecting aspect ratio on iOS ([#568](https://github.com/ionic-team/capacitor-plugins/issues/568)) ([ea2b801](https://github.com/ionic-team/capacitor-plugins/commit/ea2b8012aab7e5ea34cfa34735f7f55ba76a3882)) -- **camera:** return original image if editing is cancelled ([#566](https://github.com/ionic-team/capacitor-plugins/issues/566)) ([4786841](https://github.com/ionic-team/capacitor-plugins/commit/4786841099403a4d3d59aaf9103e8fa02aa8e4e2)) - -## [1.0.4](https://github.com/ionic-team/capacitor-plugins/compare/@capacitor/camera@1.0.3...@capacitor/camera@1.0.4) (2021-08-18) - -### Bug Fixes - -- **camera:** avoid error if image has no orientation ([#554](https://github.com/ionic-team/capacitor-plugins/issues/554)) ([dc8a55a](https://github.com/ionic-team/capacitor-plugins/commit/dc8a55a71cdaaf7ad86aee8470a0c7b8284653c4)) -- **camera:** Make allowEdit work on all devices ([#552](https://github.com/ionic-team/capacitor-plugins/issues/552)) ([5224177](https://github.com/ionic-team/capacitor-plugins/commit/5224177f77bdce1c8f028e2cef41614fa687502f)) -- **camera:** Properly reset orientation exif if corrected ([#545](https://github.com/ionic-team/capacitor-plugins/issues/545)) ([ad8c325](https://github.com/ionic-team/capacitor-plugins/commit/ad8c325af0a2459f5a7788be08a8da4118717671)) - -## [1.0.3](https://github.com/ionic-team/capacitor-plugins/compare/@capacitor/camera@1.0.2...@capacitor/camera@1.0.3) (2021-07-07) - -### Bug Fixes - -- **camera:** Reset exif orientation if corrected ([#510](https://github.com/ionic-team/capacitor-plugins/issues/510)) ([a65c05e](https://github.com/ionic-team/capacitor-plugins/commit/a65c05e0de8f53e7371c194047a75797d53879b5)) -- **camera:** Return the full webPath ([#502](https://github.com/ionic-team/capacitor-plugins/issues/502)) ([e849732](https://github.com/ionic-team/capacitor-plugins/commit/e849732dbcf5e85d1df09835c53ff5738fbb4ded)) - -## [1.0.2](https://github.com/ionic-team/capacitor-plugins/compare/@capacitor/camera@1.0.1...@capacitor/camera@1.0.2) (2021-06-23) - -### Bug Fixes - -- **camera:** Append change listener only once ([#486](https://github.com/ionic-team/capacitor-plugins/issues/486)) ([5b7021e](https://github.com/ionic-team/capacitor-plugins/commit/5b7021e210649f8501a20ba6549903ecb6d42dcd)) -- **camera:** Append exif to android images ([#480](https://github.com/ionic-team/capacitor-plugins/issues/480)) ([cad8a30](https://github.com/ionic-team/capacitor-plugins/commit/cad8a30c562202fb819a4d260d5307f1b6b8fa44)) -- **camera:** correct photo resizing on iOS ([#460](https://github.com/ionic-team/capacitor-plugins/issues/460)) ([bc56e03](https://github.com/ionic-team/capacitor-plugins/commit/bc56e034c711b172a7ff503cabd2970adbc14b86)) -- **camera:** Make input file hidden ([#484](https://github.com/ionic-team/capacitor-plugins/issues/484)) ([cdc1835](https://github.com/ionic-team/capacitor-plugins/commit/cdc1835f3bbfb8db8e18fccace6103d83dd9edaa)) -- **camera:** Make web use source options ([#487](https://github.com/ionic-team/capacitor-plugins/issues/487)) ([7870e6b](https://github.com/ionic-team/capacitor-plugins/commit/7870e6b6ca196265640fc0ba3c1f52ddca075607)) - -## [1.0.1](https://github.com/ionic-team/capacitor-plugins/compare/@capacitor/camera@1.0.0...@capacitor/camera@1.0.1) (2021-06-09) - -**Note:** Version bump only for package @capacitor/camera - -# [1.0.0](https://github.com/ionic-team/capacitor-plugins/compare/@capacitor/camera@0.4.7...@capacitor/camera@1.0.0) (2021-05-19) - -### Bug Fixes - -- **camera:** decode content uri when retrieving image from gallery ([#277](https://github.com/ionic-team/capacitor-plugins/issues/277)) ([a6cd1ad](https://github.com/ionic-team/capacitor-plugins/commit/a6cd1adc241bf21e4f7f06d24c0db4a4d7382dbc)) -- **camera:** Remove unused saveCall ([#401](https://github.com/ionic-team/capacitor-plugins/issues/401)) ([95920da](https://github.com/ionic-team/capacitor-plugins/commit/95920da4d1844ed76a162651d5492a22a4038d26)) - -### Features - -- **camera:** use a distinguishable permission denied string for camera and photos ([#379](https://github.com/ionic-team/capacitor-plugins/issues/379)) ([c71657f](https://github.com/ionic-team/capacitor-plugins/commit/c71657f7e14eae4efd4d2c7d00d77a7b329a7920)) -- **camera:** Use same error messages for permission deny ([#404](https://github.com/ionic-team/capacitor-plugins/issues/404)) ([fffcd47](https://github.com/ionic-team/capacitor-plugins/commit/fffcd47f0237b6997bfa4ce430ef29392047ea0e)) - -## [0.4.7](https://github.com/ionic-team/capacitor-plugins/compare/@capacitor/camera@0.4.6...@capacitor/camera@0.4.7) (2021-05-11) - -**Note:** Version bump only for package @capacitor/camera - -## [0.4.6](https://github.com/ionic-team/capacitor-plugins/compare/@capacitor/camera@0.4.5...@capacitor/camera@0.4.6) (2021-05-10) - -**Note:** Version bump only for package @capacitor/camera - -## [0.4.5](https://github.com/ionic-team/capacitor-plugins/compare/@capacitor/camera@0.4.4...@capacitor/camera@0.4.5) (2021-05-07) - -**Note:** Version bump only for package @capacitor/camera - -## [0.4.4](https://github.com/ionic-team/capacitor-plugins/compare/@capacitor/camera@0.4.3...@capacitor/camera@0.4.4) (2021-04-29) - -**Note:** Version bump only for package @capacitor/camera - -## [0.4.3](https://github.com/ionic-team/capacitor-plugins/compare/@capacitor/camera@0.4.2...@capacitor/camera@0.4.3) (2021-03-10) - -**Note:** Version bump only for package @capacitor/camera - -## [0.4.2](https://github.com/ionic-team/capacitor-plugins/compare/@capacitor/camera@0.4.1...@capacitor/camera@0.4.2) (2021-03-02) - -**Note:** Version bump only for package @capacitor/camera - -## [0.4.1](https://github.com/ionic-team/capacitor-plugins/compare/@capacitor/camera@0.4.0...@capacitor/camera@0.4.1) (2021-02-27) - -**Note:** Version bump only for package @capacitor/camera - -# [0.4.0](https://github.com/ionic-team/capacitor-plugins/compare/@capacitor/camera@0.3.2...@capacitor/camera@0.4.0) (2021-02-10) - -### Features - -- **android:** implements Activity Result API changes for permissions and activity results ([#222](https://github.com/ionic-team/capacitor-plugins/issues/222)) ([f671b9f](https://github.com/ionic-team/capacitor-plugins/commit/f671b9f4b472806ef43db6dcf302d4503cf1828c)) - -## [0.3.2](https://github.com/ionic-team/capacitor-plugins/compare/@capacitor/camera@0.3.1...@capacitor/camera@0.3.2) (2021-02-05) - -**Note:** Version bump only for package @capacitor/camera - -## [0.3.1](https://github.com/ionic-team/capacitor-plugins/compare/@capacitor/camera@0.3.0...@capacitor/camera@0.3.1) (2021-01-26) - -**Note:** Version bump only for package @capacitor/camera - -# [0.3.0](https://github.com/ionic-team/capacitor-plugins/compare/@capacitor/camera@0.2.0...@capacitor/camera@0.3.0) (2021-01-14) - -**Note:** Version bump only for package @capacitor/camera - -# [0.2.0](https://github.com/ionic-team/capacitor-plugins/compare/@capacitor/camera@0.1.4...@capacitor/camera@0.2.0) (2021-01-13) - -### Bug Fixes - -- add es2017 lib to tsconfig ([#180](https://github.com/ionic-team/capacitor-plugins/issues/180)) ([2c3776c](https://github.com/ionic-team/capacitor-plugins/commit/2c3776c38ca025c5ee965dec10ccf1cdb6c02e2f)) - -### Features - -- add commonjs output format ([#179](https://github.com/ionic-team/capacitor-plugins/issues/179)) ([8e9e098](https://github.com/ionic-team/capacitor-plugins/commit/8e9e09862064b3f6771d7facbc4008e995d9b463)) - -## [0.1.4](https://github.com/ionic-team/capacitor-plugins/compare/@capacitor/camera@0.1.3...@capacitor/camera@0.1.4) (2021-01-13) - -**Note:** Version bump only for package @capacitor/camera - -## [0.1.3](https://github.com/ionic-team/capacitor-plugins/compare/@capacitor/camera@0.1.2...@capacitor/camera@0.1.3) (2021-01-08) - -### Bug Fixes - -- **camera:** return file URL for path, not system path ([#170](https://github.com/ionic-team/capacitor-plugins/issues/170)) ([8a9e5c3](https://github.com/ionic-team/capacitor-plugins/commit/8a9e5c3dba3b232a1cca9f9a1e9b4520022abc09)) - -## [0.1.2](https://github.com/ionic-team/capacitor-plugins/compare/@capacitor/camera@0.1.1...@capacitor/camera@0.1.2) (2020-12-28) - -### Bug Fixes - -- **camera:** fix camera source on Android ([#164](https://github.com/ionic-team/capacitor-plugins/issues/164)) ([e67f7c6](https://github.com/ionic-team/capacitor-plugins/commit/e67f7c6b06b20d7c3e8f0925c40fd75d23d9d717)) - -## [0.1.1](https://github.com/ionic-team/capacitor-plugins/compare/@capacitor/camera@0.1.0...@capacitor/camera@0.1.1) (2020-12-27) - -### Bug Fixes - -- **camera:** query IMAGE_CAPTURE intent required by SDK 30 ([#160](https://github.com/ionic-team/capacitor-plugins/issues/160)) ([6484991](https://github.com/ionic-team/capacitor-plugins/commit/6484991d76d57bac0cbc82b9f050e146ec4732da)) - -# 0.1.0 (2020-12-20) - -### Bug Fixes - -- support deprecated types from Capacitor 2 ([#139](https://github.com/ionic-team/capacitor-plugins/issues/139)) ([2d7127a](https://github.com/ionic-team/capacitor-plugins/commit/2d7127a488e26f0287951921a6db47c49d817336)) - -### Features - -- Camera plugin ([#33](https://github.com/ionic-team/capacitor-plugins/issues/33)) ([4864928](https://github.com/ionic-team/capacitor-plugins/commit/48649288b1ba45e1901ad077b3b7b7314de04d4a)) diff --git a/camera/CapacitorCamera.podspec b/camera/CapacitorCamera.podspec deleted file mode 100644 index 83ea9132c..000000000 --- a/camera/CapacitorCamera.podspec +++ /dev/null @@ -1,17 +0,0 @@ -require 'json' - -package = JSON.parse(File.read(File.join(__dir__, 'package.json'))) - -Pod::Spec.new do |s| - s.name = 'CapacitorCamera' - s.version = package['version'] - s.summary = package['description'] - s.license = package['license'] - s.homepage = 'https://capacitorjs.com' - s.author = package['author'] - s.source = { :git => 'https://github.com/ionic-team/capacitor-plugins.git', :tag => package['name'] + '@' + package['version'] } - s.source_files = 'ios/Sources/**/*.{swift,h,m,c,cc,mm,cpp}', 'camera/ios/Sources/**/*.{swift,h,m,c,cc,mm,cpp}' - s.ios.deployment_target = '15.0' - s.dependency 'Capacitor' - s.swift_version = '5.1' -end diff --git a/camera/LICENSE b/camera/LICENSE deleted file mode 100644 index e73c9ca0d..000000000 --- a/camera/LICENSE +++ /dev/null @@ -1,23 +0,0 @@ -Copyright 2020-present Ionic -https://ionic.io - -MIT License - -Permission is hereby granted, free of charge, to any person obtaining -a copy of this software and associated documentation files (the -"Software"), to deal in the Software without restriction, including -without limitation the rights to use, copy, modify, merge, publish, -distribute, sublicense, and/or sell copies of the Software, and to -permit persons to whom the Software is furnished to do so, subject to -the following conditions: - -The above copyright notice and this permission notice shall be -included in all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND -NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE -LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION -OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION -WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. \ No newline at end of file diff --git a/camera/Package.swift b/camera/Package.swift deleted file mode 100644 index db49e0d73..000000000 --- a/camera/Package.swift +++ /dev/null @@ -1,27 +0,0 @@ -// swift-tools-version: 5.9 -import PackageDescription - -let package = Package( - name: "CapacitorCamera", - platforms: [.iOS(.v15)], - products: [ - .library( - name: "CapacitorCamera", - targets: ["CameraPlugin"]) - ], - dependencies: [ - .package(url: "https://github.com/ionic-team/capacitor-swift-pm.git", from: "9.0.0-alpha.0") - ], - targets: [ - .target( - name: "CameraPlugin", - dependencies: [ - .product(name: "Capacitor", package: "capacitor-swift-pm") - ], - path: "ios/Sources/CameraPlugin"), - .testTarget( - name: "CameraPluginTests", - dependencies: ["CameraPlugin"], - path: "ios/Tests/CameraPluginTests") - ] -) diff --git a/camera/README.md b/camera/README.md deleted file mode 100644 index 66cd066d2..000000000 --- a/camera/README.md +++ /dev/null @@ -1,358 +0,0 @@ -# ⓘ Plugin migrated - -**From version 8.1.0 onwards, this plugin is now hosted in a separate repository. Refer to [capacitor-camera repository](https://github.com/ionic-team/capacitor-camera).** - -This file remains here to serve as documentation for version 8.0.2. - -# @capacitor/camera - -The Camera API provides the ability to take a photo with the camera or choose an existing one from the photo album. - -## Install - -```bash -npm install @capacitor/camera -npx cap sync -``` - -## iOS - -iOS requires the following usage description be added and filled out for your app in `Info.plist`: - -- `NSCameraUsageDescription` (`Privacy - Camera Usage Description`) -- `NSPhotoLibraryAddUsageDescription` (`Privacy - Photo Library Additions Usage Description`) -- `NSPhotoLibraryUsageDescription` (`Privacy - Photo Library Usage Description`) - -Read about [Configuring `Info.plist`](https://capacitorjs.com/docs/ios/configuration#configuring-infoplist) in the [iOS Guide](https://capacitorjs.com/docs/ios) for more information on setting iOS permissions in Xcode - -## Android - -When picking existing images from the device gallery, the Android Photo Picker component is now used. The Photo Picker is available on devices that meet the following criteria: - -- Run Android 11 (API level 30) or higher -- Receive changes to Modular System Components through Google System Updates - -Older devices and Android Go devices running Android 11 or 12 that support Google Play services can install a backported version of the photo picker. To enable the automatic installation of the backported photo picker module through Google Play services, add the following entry to the `` tag in your `AndroidManifest.xml` file: - -```xml - - - - - - - - -``` - -If that entry is not added, the devices that don't support the Photo Picker, the Photo Picker component fallbacks to `Intent.ACTION_OPEN_DOCUMENT`. - -The Camera plugin requires no permissions, unless using `saveToGallery: true`, in that case the following permissions should be added to your `AndroidManifest.xml`: - -```xml - - -``` - -You can also specify those permissions only for the Android versions where they will be requested: - -```xml - - -``` - -The storage permissions are for reading/saving photo files. - -Read about [Setting Permissions](https://capacitorjs.com/docs/android/configuration#setting-permissions) in the [Android Guide](https://capacitorjs.com/docs/android) for more information on setting Android permissions. - -Additionally, because the Camera API launches a separate Activity to handle taking the photo, you should listen for `appRestoredResult` in the `App` plugin to handle any camera data that was sent in the case your app was terminated by the operating system while the Activity was running. - -### Variables - -This plugin will use the following project variables (defined in your app's `variables.gradle` file): - -- `androidxExifInterfaceVersion`: version of `androidx.exifinterface:exifinterface` (default: `1.4.1`) -- `androidxMaterialVersion`: version of `com.google.android.material:material` (default: `1.13.0`) - -## PWA Notes - -[PWA Elements](https://capacitorjs.com/docs/web/pwa-elements) are required for Camera plugin to work. - -## Example - -```typescript -import { Camera, CameraResultType } from '@capacitor/camera'; - -const takePicture = async () => { - const image = await Camera.getPhoto({ - quality: 90, - allowEditing: true, - resultType: CameraResultType.Uri - }); - - // image.webPath will contain a path that can be set as an image src. - // You can access the original file using image.path, which can be - // passed to the Filesystem API to read the raw data of the image, - // if desired (or pass resultType: CameraResultType.Base64 to getPhoto) - var imageUrl = image.webPath; - - // Can be set to the src of an image now - imageElement.src = imageUrl; -}; -``` - -## API - - - -* [`getPhoto(...)`](#getphoto) -* [`pickImages(...)`](#pickimages) -* [`pickLimitedLibraryPhotos()`](#picklimitedlibraryphotos) -* [`getLimitedLibraryPhotos()`](#getlimitedlibraryphotos) -* [`checkPermissions()`](#checkpermissions) -* [`requestPermissions(...)`](#requestpermissions) -* [Interfaces](#interfaces) -* [Type Aliases](#type-aliases) -* [Enums](#enums) - - - - - - -### getPhoto(...) - -```typescript -getPhoto(options: ImageOptions) => Promise -``` - -Prompt the user to pick a photo from an album, or take a new photo -with the camera. - -| Param | Type | -| ------------- | ----------------------------------------------------- | -| **`options`** | ImageOptions | - -**Returns:** Promise<Photo> - -**Since:** 1.0.0 - --------------------- - - -### pickImages(...) - -```typescript -pickImages(options: GalleryImageOptions) => Promise -``` - -Allows the user to pick multiple pictures from the photo gallery. - -| Param | Type | -| ------------- | ------------------------------------------------------------------- | -| **`options`** | GalleryImageOptions | - -**Returns:** Promise<GalleryPhotos> - -**Since:** 1.2.0 - --------------------- - - -### pickLimitedLibraryPhotos() - -```typescript -pickLimitedLibraryPhotos() => Promise -``` - -Allows the user to update their limited photo library selection. -Returns all the limited photos after the picker dismissal. -If instead the user gave full access to the photos it returns an empty array. - -**Returns:** Promise<GalleryPhotos> - -**Since:** 4.1.0 - --------------------- - - -### getLimitedLibraryPhotos() - -```typescript -getLimitedLibraryPhotos() => Promise -``` - -Return an array of photos selected from the limited photo library. - -**Returns:** Promise<GalleryPhotos> - -**Since:** 4.1.0 - --------------------- - - -### checkPermissions() - -```typescript -checkPermissions() => Promise -``` - -Check camera and photo album permissions - -**Returns:** Promise<PermissionStatus> - -**Since:** 1.0.0 - --------------------- - - -### requestPermissions(...) - -```typescript -requestPermissions(permissions?: CameraPluginPermissions | undefined) => Promise -``` - -Request camera and photo album permissions - -| Param | Type | -| ----------------- | --------------------------------------------------------------------------- | -| **`permissions`** | CameraPluginPermissions | - -**Returns:** Promise<PermissionStatus> - -**Since:** 1.0.0 - --------------------- - - -### Interfaces - - -#### Photo - -| Prop | Type | Description | Since | -| ------------------ | -------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | ----- | -| **`base64String`** | string | The base64 encoded string representation of the image, if using CameraResultType.Base64. | 1.0.0 | -| **`dataUrl`** | string | The url starting with 'data:image/jpeg;base64,' and the base64 encoded string representation of the image, if using CameraResultType.DataUrl. Note: On web, the file format could change depending on the browser. | 1.0.0 | -| **`path`** | string | If using CameraResultType.Uri, the path will contain a full, platform-specific file URL that can be read later using the Filesystem API. | 1.0.0 | -| **`webPath`** | string | webPath returns a path that can be used to set the src attribute of an image for efficient loading and rendering. | 1.0.0 | -| **`exif`** | any | Exif data, if any, retrieved from the image | 1.0.0 | -| **`format`** | string | The format of the image, ex: jpeg, png, gif. iOS and Android only support jpeg. Web supports jpeg, png and gif, but the exact availability may vary depending on the browser. gif is only supported if `webUseInput` is set to `true` or if `source` is set to `Photos`. | 1.0.0 | -| **`saved`** | boolean | Whether if the image was saved to the gallery or not. On Android and iOS, saving to the gallery can fail if the user didn't grant the required permissions. On Web there is no gallery, so always returns false. | 1.1.0 | - - -#### ImageOptions - -| Prop | Type | Description | Default | Since | -| ------------------------ | ------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------- | ----- | -| **`quality`** | number | The quality of image to return as JPEG, from 0-100 Note: This option is only supported on Android and iOS | | 1.0.0 | -| **`allowEditing`** | boolean | Whether to allow the user to crop or make small edits (platform specific). On iOS it's only supported for CameraSource.Camera, but not for CameraSource.Photos. | | 1.0.0 | -| **`resultType`** | CameraResultType | How the data should be returned. Currently, only 'Base64', 'DataUrl' or 'Uri' is supported | | 1.0.0 | -| **`saveToGallery`** | boolean | Whether to save the photo to the gallery. If the photo was picked from the gallery, it will only be saved if edited. | : false | 1.0.0 | -| **`width`** | number | The desired maximum width of the saved image. The aspect ratio is respected. | | 1.0.0 | -| **`height`** | number | The desired maximum height of the saved image. The aspect ratio is respected. | | 1.0.0 | -| **`correctOrientation`** | boolean | Whether to automatically rotate the image "up" to correct for orientation in portrait mode | : true | 1.0.0 | -| **`source`** | CameraSource | The source to get the photo from. By default this prompts the user to select either the photo album or take a photo. | : CameraSource.Prompt | 1.0.0 | -| **`direction`** | CameraDirection | iOS and Web only: The camera direction. | : CameraDirection.Rear | 1.0.0 | -| **`presentationStyle`** | 'fullscreen' \| 'popover' | iOS only: The presentation style of the Camera. | : 'fullscreen' | 1.0.0 | -| **`webUseInput`** | boolean | Web only: Whether to use the PWA Element experience or file input. The default is to use PWA Elements if installed and fall back to file input. To always use file input, set this to `true`. Learn more about PWA Elements: https://capacitorjs.com/docs/web/pwa-elements | | 1.0.0 | -| **`promptLabelHeader`** | string | Text value to use when displaying the prompt. | : 'Photo' | 1.0.0 | -| **`promptLabelCancel`** | string | Text value to use when displaying the prompt. iOS only: The label of the 'cancel' button. | : 'Cancel' | 1.0.0 | -| **`promptLabelPhoto`** | string | Text value to use when displaying the prompt. The label of the button to select a saved image. | : 'From Photos' | 1.0.0 | -| **`promptLabelPicture`** | string | Text value to use when displaying the prompt. The label of the button to open the camera. | : 'Take Picture' | 1.0.0 | - - -#### GalleryPhotos - -| Prop | Type | Description | Since | -| ------------ | --------------------------- | ------------------------------- | ----- | -| **`photos`** | GalleryPhoto[] | Array of all the picked photos. | 1.2.0 | - - -#### GalleryPhoto - -| Prop | Type | Description | Since | -| ------------- | ------------------- | ----------------------------------------------------------------------------------------------------------------- | ----- | -| **`path`** | string | Full, platform-specific file URL that can be read later using the Filesystem API. | 1.2.0 | -| **`webPath`** | string | webPath returns a path that can be used to set the src attribute of an image for efficient loading and rendering. | 1.2.0 | -| **`exif`** | any | Exif data, if any, retrieved from the image | 1.2.0 | -| **`format`** | string | The format of the image, ex: jpeg, png, gif. iOS and Android only support jpeg. Web supports jpeg, png and gif. | 1.2.0 | - - -#### GalleryImageOptions - -| Prop | Type | Description | Default | Since | -| ------------------------ | -------------------------------------- | ----------------------------------------------------------------------------------------------------------------------- | --------------------------- | ----- | -| **`quality`** | number | The quality of image to return as JPEG, from 0-100 Note: This option is only supported on Android and iOS. | | 1.2.0 | -| **`width`** | number | The desired maximum width of the saved image. The aspect ratio is respected. | | 1.2.0 | -| **`height`** | number | The desired maximum height of the saved image. The aspect ratio is respected. | | 1.2.0 | -| **`correctOrientation`** | boolean | Whether to automatically rotate the image "up" to correct for orientation in portrait mode | : true | 1.2.0 | -| **`presentationStyle`** | 'fullscreen' \| 'popover' | iOS only: The presentation style of the Camera. | : 'fullscreen' | 1.2.0 | -| **`limit`** | number | Maximum number of pictures the user will be able to choose. Note: This option is only supported on Android 13+ and iOS. | 0 (unlimited) | 1.2.0 | - - -#### PermissionStatus - -| Prop | Type | -| ------------ | ----------------------------------------------------------------------- | -| **`camera`** | CameraPermissionState | -| **`photos`** | CameraPermissionState | - - -#### CameraPluginPermissions - -| Prop | Type | -| ----------------- | ----------------------------------- | -| **`permissions`** | CameraPermissionType[] | - - -### Type Aliases - - -#### CameraPermissionState - -PermissionState | 'limited' - - -#### PermissionState - -'prompt' | 'prompt-with-rationale' | 'granted' | 'denied' - - -#### CameraPermissionType - -'camera' | 'photos' - - -### Enums - - -#### CameraResultType - -| Members | Value | -| ------------- | ---------------------- | -| **`Uri`** | 'uri' | -| **`Base64`** | 'base64' | -| **`DataUrl`** | 'dataUrl' | - - -#### CameraSource - -| Members | Value | Description | -| ------------ | --------------------- | ------------------------------------------------------------------ | -| **`Prompt`** | 'PROMPT' | Prompts the user to select either the photo album or take a photo. | -| **`Camera`** | 'CAMERA' | Take a new photo using the camera. | -| **`Photos`** | 'PHOTOS' | Pick an existing photo from the gallery or photo album. | - - -#### CameraDirection - -| Members | Value | -| ----------- | -------------------- | -| **`Rear`** | 'REAR' | -| **`Front`** | 'FRONT' | - - diff --git a/camera/android/.gitignore b/camera/android/.gitignore deleted file mode 100644 index 796b96d1c..000000000 --- a/camera/android/.gitignore +++ /dev/null @@ -1 +0,0 @@ -/build diff --git a/camera/android/build.gradle b/camera/android/build.gradle deleted file mode 100644 index 18f377148..000000000 --- a/camera/android/build.gradle +++ /dev/null @@ -1,83 +0,0 @@ -ext { - capacitorVersion = System.getenv('CAPACITOR_VERSION') - junitVersion = project.hasProperty('junitVersion') ? rootProject.ext.junitVersion : '4.13.2' - androidxAppCompatVersion = project.hasProperty('androidxAppCompatVersion') ? rootProject.ext.androidxAppCompatVersion : '1.7.1' - androidxEspressoCoreVersion = project.hasProperty('androidxEspressoCoreVersion') ? rootProject.ext.androidxEspressoCoreVersion : '3.7.0' - androidxExifInterfaceVersion = project.hasProperty('androidxExifInterfaceVersion') ? rootProject.ext.androidxExifInterfaceVersion : '1.4.1' - androidxJunitVersion = project.hasProperty('androidxJunitVersion') ? rootProject.ext.androidxJunitVersion : '1.3.0' - androidxMaterialVersion = project.hasProperty('androidxMaterialVersion') ? rootProject.ext.androidxMaterialVersion : '1.13.0' -} - -buildscript { - repositories { - google() - mavenCentral() - maven { - url = "https://plugins.gradle.org/m2/" - } - } - dependencies { - classpath 'com.android.tools.build:gradle:8.13.0' - if (System.getenv("CAP_PLUGIN_PUBLISH") == "true") { - classpath 'io.github.gradle-nexus:publish-plugin:1.3.0' - } - } -} - -apply plugin: 'com.android.library' -if (System.getenv("CAP_PLUGIN_PUBLISH") == "true") { - apply plugin: 'io.github.gradle-nexus.publish-plugin' - apply from: file('../../scripts/android/publish-root.gradle') - apply from: file('../../scripts/android/publish-module.gradle') -} - -android { - namespace = "com.capacitorjs.plugins.camera" - compileSdk = project.hasProperty('compileSdkVersion') ? rootProject.ext.compileSdkVersion : 36 - defaultConfig { - minSdkVersion project.hasProperty('minSdkVersion') ? rootProject.ext.minSdkVersion : 24 - targetSdkVersion project.hasProperty('targetSdkVersion') ? rootProject.ext.targetSdkVersion : 36 - versionCode 1 - versionName "1.0" - testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" - } - buildTypes { - release { - minifyEnabled false - proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' - } - } - lintOptions { - abortOnError = false - } - compileOptions { - sourceCompatibility JavaVersion.VERSION_21 - targetCompatibility JavaVersion.VERSION_21 - } - publishing { - singleVariant("release") - } -} - -repositories { - google() - mavenCentral() -} - - -dependencies { - implementation fileTree(dir: 'libs', include: ['*.jar']) - - if (System.getenv("CAP_PLUGIN_PUBLISH") == "true") { - implementation "com.capacitorjs:core:$capacitorVersion" - } else { - implementation project(':capacitor-android') - } - - implementation "androidx.exifinterface:exifinterface:$androidxExifInterfaceVersion" - implementation "androidx.appcompat:appcompat:$androidxAppCompatVersion" - implementation "com.google.android.material:material:$androidxMaterialVersion" - testImplementation "junit:junit:$junitVersion" - androidTestImplementation "androidx.test.ext:junit:$androidxJunitVersion" - androidTestImplementation "androidx.test.espresso:espresso-core:$androidxEspressoCoreVersion" -} diff --git a/camera/android/gradle.properties b/camera/android/gradle.properties deleted file mode 100644 index 2e87c52f8..000000000 --- a/camera/android/gradle.properties +++ /dev/null @@ -1,22 +0,0 @@ -# Project-wide Gradle settings. - -# IDE (e.g. Android Studio) users: -# Gradle settings configured through the IDE *will override* -# any settings specified in this file. - -# For more details on how to configure your build environment visit -# http://www.gradle.org/docs/current/userguide/build_environment.html - -# Specifies the JVM arguments used for the daemon process. -# The setting is particularly useful for tweaking memory settings. -org.gradle.jvmargs=-Xmx1536m - -# When configured, Gradle will run in incubating parallel mode. -# This option should only be used with decoupled projects. More details, visit -# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects -# org.gradle.parallel=true - -# AndroidX package structure to make it clearer which packages are bundled with the -# Android operating system, and which are packaged with your app's APK -# https://developer.android.com/topic/libraries/support-library/androidx-rn -android.useAndroidX=true diff --git a/camera/android/gradle/wrapper/gradle-wrapper.jar b/camera/android/gradle/wrapper/gradle-wrapper.jar deleted file mode 100644 index 1b33c55baabb587c669f562ae36f953de2481846..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 43764 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 '} - case $link in #( - /*) app_path=$link ;; #( - *) app_path=$APP_HOME$link ;; - esac -done - -# This is normally unused -# shellcheck disable=SC2034 -APP_BASE_NAME=${0##*/} -# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036) -APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s\n' "$PWD" ) || exit - -# Use the maximum available, or set MAX_FD != -1 to use that value. -MAX_FD=maximum - -warn () { - echo "$*" -} >&2 - -die () { - echo - echo "$*" - echo - exit 1 -} >&2 - -# OS specific support (must be 'true' or 'false'). -cygwin=false -msys=false -darwin=false -nonstop=false -case "$( uname )" in #( - CYGWIN* ) cygwin=true ;; #( - Darwin* ) darwin=true ;; #( - MSYS* | MINGW* ) msys=true ;; #( - NONSTOP* ) nonstop=true ;; -esac - -CLASSPATH="\\\"\\\"" - - -# Determine the Java command to use to start the JVM. -if [ -n "$JAVA_HOME" ] ; then - if [ -x "$JAVA_HOME/jre/sh/java" ] ; then - # IBM's JDK on AIX uses strange locations for the executables - JAVACMD=$JAVA_HOME/jre/sh/java - else - JAVACMD=$JAVA_HOME/bin/java - fi - if [ ! -x "$JAVACMD" ] ; then - die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME - -Please set the JAVA_HOME variable in your environment to match the -location of your Java installation." - fi -else - JAVACMD=java - if ! command -v java >/dev/null 2>&1 - then - die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. - -Please set the JAVA_HOME variable in your environment to match the -location of your Java installation." - fi -fi - -# Increase the maximum file descriptors if we can. -if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then - case $MAX_FD in #( - max*) - # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked. - # shellcheck disable=SC2039,SC3045 - MAX_FD=$( ulimit -H -n ) || - warn "Could not query maximum file descriptor limit" - esac - case $MAX_FD in #( - '' | soft) :;; #( - *) - # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked. - # shellcheck disable=SC2039,SC3045 - ulimit -n "$MAX_FD" || - warn "Could not set maximum file descriptor limit to $MAX_FD" - esac -fi - -# Collect all arguments for the java command, stacking in reverse order: -# * args from the command line -# * the main class name -# * -classpath -# * -D...appname settings -# * --module-path (only if needed) -# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables. - -# For Cygwin or MSYS, switch paths to Windows format before running java -if "$cygwin" || "$msys" ; then - APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) - CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" ) - - JAVACMD=$( cygpath --unix "$JAVACMD" ) - - # Now convert the arguments - kludge to limit ourselves to /bin/sh - for arg do - if - case $arg in #( - -*) false ;; # don't mess with options #( - /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath - [ -e "$t" ] ;; #( - *) false ;; - esac - then - arg=$( cygpath --path --ignore --mixed "$arg" ) - fi - # Roll the args list around exactly as many times as the number of - # args, so each arg winds up back in the position where it started, but - # possibly modified. - # - # NB: a `for` loop captures its iteration list before it begins, so - # changing the positional parameters here affects neither the number of - # iterations, nor the values presented in `arg`. - shift # remove old arg - set -- "$@" "$arg" # push replacement arg - done -fi - - -# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. -DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' - -# Collect all arguments for the java command: -# * DEFAULT_JVM_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, -# and any embedded shellness will be escaped. -# * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be -# treated as '${Hostname}' itself on the command line. - -set -- \ - "-Dorg.gradle.appname=$APP_BASE_NAME" \ - -classpath "$CLASSPATH" \ - -jar "$APP_HOME/gradle/wrapper/gradle-wrapper.jar" \ - "$@" - -# Stop when "xargs" is not available. -if ! command -v xargs >/dev/null 2>&1 -then - die "xargs is not available" -fi - -# Use "xargs" to parse quoted args. -# -# With -n1 it outputs one arg per line, with the quotes and backslashes removed. -# -# In Bash we could simply go: -# -# readarray ARGS < <( xargs -n1 <<<"$var" ) && -# set -- "${ARGS[@]}" "$@" -# -# but POSIX shell has neither arrays nor command substitution, so instead we -# post-process each arg (as a line of input to sed) to backslash-escape any -# character that might be a shell metacharacter, then use eval to reverse -# that process (while maintaining the separation between arguments), and wrap -# the whole thing up as a single "set" statement. -# -# This will of course break if any of these variables contains a newline or -# an unmatched quote. -# - -eval "set -- $( - printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" | - xargs -n1 | - sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' | - tr '\n' ' ' - )" '"$@"' - -exec "$JAVACMD" "$@" diff --git a/camera/android/gradlew.bat b/camera/android/gradlew.bat deleted file mode 100644 index 5eed7ee84..000000000 --- a/camera/android/gradlew.bat +++ /dev/null @@ -1,94 +0,0 @@ -@rem -@rem Copyright 2015 the original author or authors. -@rem -@rem Licensed under the Apache License, Version 2.0 (the "License"); -@rem you may not use this file except in compliance with the License. -@rem You may obtain a copy of the License at -@rem -@rem https://www.apache.org/licenses/LICENSE-2.0 -@rem -@rem Unless required by applicable law or agreed to in writing, software -@rem distributed under the License is distributed on an "AS IS" BASIS, -@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -@rem See the License for the specific language governing permissions and -@rem limitations under the License. -@rem -@rem SPDX-License-Identifier: Apache-2.0 -@rem - -@if "%DEBUG%"=="" @echo off -@rem ########################################################################## -@rem -@rem Gradle startup script for Windows -@rem -@rem ########################################################################## - -@rem Set local scope for the variables with windows NT shell -if "%OS%"=="Windows_NT" setlocal - -set DIRNAME=%~dp0 -if "%DIRNAME%"=="" set DIRNAME=. -@rem This is normally unused -set APP_BASE_NAME=%~n0 -set APP_HOME=%DIRNAME% - -@rem Resolve any "." and ".." in APP_HOME to make it shorter. -for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi - -@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. -set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" - -@rem Find java.exe -if defined JAVA_HOME goto findJavaFromJavaHome - -set JAVA_EXE=java.exe -%JAVA_EXE% -version >NUL 2>&1 -if %ERRORLEVEL% equ 0 goto execute - -echo. 1>&2 -echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2 -echo. 1>&2 -echo Please set the JAVA_HOME variable in your environment to match the 1>&2 -echo location of your Java installation. 1>&2 - -goto fail - -:findJavaFromJavaHome -set JAVA_HOME=%JAVA_HOME:"=% -set JAVA_EXE=%JAVA_HOME%/bin/java.exe - -if exist "%JAVA_EXE%" goto execute - -echo. 1>&2 -echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2 -echo. 1>&2 -echo Please set the JAVA_HOME variable in your environment to match the 1>&2 -echo location of your Java installation. 1>&2 - -goto fail - -:execute -@rem Setup the command line - -set CLASSPATH= - - -@rem Execute Gradle -"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %* - -:end -@rem End local scope for the variables with windows NT shell -if %ERRORLEVEL% equ 0 goto mainEnd - -:fail -rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of -rem the _cmd.exe /c_ return code! -set EXIT_CODE=%ERRORLEVEL% -if %EXIT_CODE% equ 0 set EXIT_CODE=1 -if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE% -exit /b %EXIT_CODE% - -:mainEnd -if "%OS%"=="Windows_NT" endlocal - -:omega diff --git a/camera/android/proguard-rules.pro b/camera/android/proguard-rules.pro deleted file mode 100644 index f1b424510..000000000 --- a/camera/android/proguard-rules.pro +++ /dev/null @@ -1,21 +0,0 @@ -# Add project specific ProGuard rules here. -# You can control the set of applied configuration files using the -# proguardFiles setting in build.gradle. -# -# For more details, see -# http://developer.android.com/guide/developing/tools/proguard.html - -# If your project uses WebView with JS, uncomment the following -# and specify the fully qualified class name to the JavaScript interface -# class: -#-keepclassmembers class fqcn.of.javascript.interface.for.webview { -# public *; -#} - -# Uncomment this to preserve the line number information for -# debugging stack traces. -#-keepattributes SourceFile,LineNumberTable - -# If you keep the line number information, uncomment this to -# hide the original source file name. -#-renamesourcefileattribute SourceFile diff --git a/camera/android/settings.gradle b/camera/android/settings.gradle deleted file mode 100644 index 1e5b8431f..000000000 --- a/camera/android/settings.gradle +++ /dev/null @@ -1,2 +0,0 @@ -include ':capacitor-android' -project(':capacitor-android').projectDir = new File('../node_modules/@capacitor/android/capacitor') \ No newline at end of file diff --git a/camera/android/src/androidTest/java/com/getcapacitor/android/ExampleInstrumentedTest.java b/camera/android/src/androidTest/java/com/getcapacitor/android/ExampleInstrumentedTest.java deleted file mode 100644 index 58020e16c..000000000 --- a/camera/android/src/androidTest/java/com/getcapacitor/android/ExampleInstrumentedTest.java +++ /dev/null @@ -1,26 +0,0 @@ -package com.getcapacitor.android; - -import static org.junit.Assert.*; - -import android.content.Context; -import androidx.test.ext.junit.runners.AndroidJUnit4; -import androidx.test.platform.app.InstrumentationRegistry; -import org.junit.Test; -import org.junit.runner.RunWith; - -/** - * Instrumented test, which will execute on an Android device. - * - * @see Testing documentation - */ -@RunWith(AndroidJUnit4.class) -public class ExampleInstrumentedTest { - - @Test - public void useAppContext() throws Exception { - // Context of the app under test. - Context appContext = InstrumentationRegistry.getInstrumentation().getTargetContext(); - - assertEquals("com.getcapacitor.android", appContext.getPackageName()); - } -} diff --git a/camera/android/src/main/AndroidManifest.xml b/camera/android/src/main/AndroidManifest.xml deleted file mode 100644 index 2898a91dd..000000000 --- a/camera/android/src/main/AndroidManifest.xml +++ /dev/null @@ -1,7 +0,0 @@ - - - - - - - diff --git a/camera/android/src/main/java/com/capacitorjs/plugins/camera/CameraBottomSheetDialogFragment.java b/camera/android/src/main/java/com/capacitorjs/plugins/camera/CameraBottomSheetDialogFragment.java deleted file mode 100644 index 7507881b3..000000000 --- a/camera/android/src/main/java/com/capacitorjs/plugins/camera/CameraBottomSheetDialogFragment.java +++ /dev/null @@ -1,122 +0,0 @@ -package com.capacitorjs.plugins.camera; - -import android.annotation.SuppressLint; -import android.app.Dialog; -import android.content.DialogInterface; -import android.graphics.Color; -import android.view.View; -import android.view.Window; -import android.widget.LinearLayout; -import android.widget.TextView; -import androidx.annotation.NonNull; -import androidx.coordinatorlayout.widget.CoordinatorLayout; -import com.google.android.material.bottomsheet.BottomSheetBehavior; -import com.google.android.material.bottomsheet.BottomSheetDialogFragment; -import java.util.List; - -public class CameraBottomSheetDialogFragment extends BottomSheetDialogFragment { - - interface BottomSheetOnSelectedListener { - void onSelected(int index); - } - - interface BottomSheetOnCanceledListener { - void onCanceled(); - } - - private BottomSheetOnSelectedListener selectedListener; - private BottomSheetOnCanceledListener canceledListener; - private List options; - private String title; - - void setTitle(String title) { - this.title = title; - } - - void setOptions(List options, BottomSheetOnSelectedListener selectedListener, BottomSheetOnCanceledListener canceledListener) { - this.options = options; - this.selectedListener = selectedListener; - this.canceledListener = canceledListener; - } - - @Override - public void onCancel(DialogInterface dialog) { - super.onCancel(dialog); - if (canceledListener != null) { - this.canceledListener.onCanceled(); - } - } - - private BottomSheetBehavior.BottomSheetCallback mBottomSheetBehaviorCallback = new BottomSheetBehavior.BottomSheetCallback() { - @Override - public void onStateChanged(@NonNull View bottomSheet, int newState) { - if (newState == BottomSheetBehavior.STATE_HIDDEN) { - dismiss(); - } - } - - @Override - public void onSlide(@NonNull View bottomSheet, float slideOffset) {} - }; - - @Override - @SuppressLint("RestrictedApi") - public void setupDialog(Dialog dialog, int style) { - super.setupDialog(dialog, style); - - if (options == null || options.size() == 0) { - return; - } - - Window w = dialog.getWindow(); - - final float scale = getResources().getDisplayMetrics().density; - - float layoutPaddingDp16 = 16.0f; - float layoutPaddingDp12 = 12.0f; - float layoutPaddingDp8 = 8.0f; - int layoutPaddingPx16 = (int) (layoutPaddingDp16 * scale + 0.5f); - int layoutPaddingPx12 = (int) (layoutPaddingDp12 * scale + 0.5f); - int layoutPaddingPx8 = (int) (layoutPaddingDp8 * scale + 0.5f); - - CoordinatorLayout parentLayout = new CoordinatorLayout(getContext()); - - LinearLayout layout = new LinearLayout(getContext()); - layout.setOrientation(LinearLayout.VERTICAL); - layout.setPadding(layoutPaddingPx16, layoutPaddingPx16, layoutPaddingPx16, layoutPaddingPx16); - TextView ttv = new TextView(getContext()); - ttv.setTextColor(Color.parseColor("#757575")); - ttv.setPadding(layoutPaddingPx8, layoutPaddingPx8, layoutPaddingPx8, layoutPaddingPx8); - ttv.setText(title); - layout.addView(ttv); - - for (int i = 0; i < options.size(); i++) { - final int optionIndex = i; - - TextView tv = new TextView(getContext()); - tv.setTextColor(Color.parseColor("#000000")); - tv.setPadding(layoutPaddingPx12, layoutPaddingPx12, layoutPaddingPx12, layoutPaddingPx12); - tv.setText(options.get(i)); - tv.setOnClickListener((view) -> { - if (selectedListener != null) { - selectedListener.onSelected(optionIndex); - } - dismiss(); - }); - layout.addView(tv); - } - - parentLayout.addView(layout.getRootView()); - - dialog.setContentView(parentLayout.getRootView()); - - CoordinatorLayout.LayoutParams params = (CoordinatorLayout.LayoutParams) ((View) parentLayout.getParent()).getLayoutParams(); - CoordinatorLayout.Behavior behavior = params.getBehavior(); - - if (behavior instanceof BottomSheetBehavior) { - BottomSheetBehavior bottomSheetBehavior = (BottomSheetBehavior) behavior; - bottomSheetBehavior.addBottomSheetCallback(mBottomSheetBehaviorCallback); - bottomSheetBehavior.setState(BottomSheetBehavior.STATE_EXPANDED); - } - } -} diff --git a/camera/android/src/main/java/com/capacitorjs/plugins/camera/CameraPlugin.java b/camera/android/src/main/java/com/capacitorjs/plugins/camera/CameraPlugin.java deleted file mode 100644 index a5598e39d..000000000 --- a/camera/android/src/main/java/com/capacitorjs/plugins/camera/CameraPlugin.java +++ /dev/null @@ -1,911 +0,0 @@ -package com.capacitorjs.plugins.camera; - -import android.Manifest; -import android.annotation.SuppressLint; -import android.app.Activity; -import android.content.ActivityNotFoundException; -import android.content.ContentResolver; -import android.content.ContentValues; -import android.content.Intent; -import android.content.pm.PackageManager; -import android.content.pm.ResolveInfo; -import android.graphics.Bitmap; -import android.graphics.BitmapFactory; -import android.net.Uri; -import android.os.Build; -import android.os.Bundle; -import android.os.Environment; -import android.os.Parcelable; -import android.provider.MediaStore; -import android.util.Base64; -import androidx.activity.result.ActivityResult; -import androidx.activity.result.ActivityResultCallback; -import androidx.activity.result.ActivityResultLauncher; -import androidx.activity.result.ActivityResultRegistryOwner; -import androidx.activity.result.PickVisualMediaRequest; -import androidx.activity.result.contract.ActivityResultContract; -import androidx.activity.result.contract.ActivityResultContracts; -import androidx.annotation.NonNull; -import androidx.core.content.FileProvider; -import com.getcapacitor.FileUtils; -import com.getcapacitor.JSArray; -import com.getcapacitor.JSObject; -import com.getcapacitor.Logger; -import com.getcapacitor.PermissionState; -import com.getcapacitor.Plugin; -import com.getcapacitor.PluginCall; -import com.getcapacitor.PluginMethod; -import com.getcapacitor.annotation.ActivityCallback; -import com.getcapacitor.annotation.CapacitorPlugin; -import com.getcapacitor.annotation.Permission; -import com.getcapacitor.annotation.PermissionCallback; -import java.io.ByteArrayInputStream; -import java.io.ByteArrayOutputStream; -import java.io.File; -import java.io.FileNotFoundException; -import java.io.FileOutputStream; -import java.io.IOException; -import java.io.InputStream; -import java.io.OutputStream; -import java.util.ArrayList; -import java.util.List; -import java.util.Locale; -import java.util.Map; -import java.util.concurrent.Executor; -import java.util.concurrent.Executors; -import java.util.concurrent.atomic.AtomicInteger; -import org.json.JSONException; - -/** - * The Camera plugin makes it easy to take a photo or have the user select a photo - * from their albums. - * - * On Android, this plugin sends an intent that opens the stock Camera app. - * - * Adapted from https://developer.android.com/training/camera/photobasics.html - */ -@SuppressLint("InlinedApi") -@CapacitorPlugin( - name = "Camera", - permissions = { - @Permission(strings = { Manifest.permission.CAMERA }, alias = CameraPlugin.CAMERA), - @Permission(strings = {}, alias = CameraPlugin.PHOTOS), - // SDK VERSIONS 29 AND BELOW - @Permission( - strings = { Manifest.permission.READ_EXTERNAL_STORAGE, Manifest.permission.WRITE_EXTERNAL_STORAGE }, - alias = CameraPlugin.SAVE_GALLERY - ), - /* - SDK VERSIONS 30-32 - This alias is a placeholder and the SAVE_GALLERY alias will be updated to use this permission - so that the end user does not need to explicitly use separate aliases depending - on the SDK version. - */ - @Permission(strings = { Manifest.permission.READ_EXTERNAL_STORAGE }, alias = CameraPlugin.READ_EXTERNAL_STORAGE) - } -) -public class CameraPlugin extends Plugin { - - // Permission alias constants - static final String CAMERA = "camera"; - static final String PHOTOS = "photos"; - static final String SAVE_GALLERY = "saveGallery"; - static final String READ_EXTERNAL_STORAGE = "readExternalStorage"; - - // Message constants - private static final String INVALID_RESULT_TYPE_ERROR = "Invalid resultType option"; - private static final String PERMISSION_DENIED_ERROR_CAMERA = "User denied access to camera"; - private static final String NO_CAMERA_ERROR = "Device doesn't have a camera available"; - private static final String NO_CAMERA_ACTIVITY_ERROR = "Unable to resolve camera activity"; - private static final String NO_PHOTO_ACTIVITY_ERROR = "Unable to resolve photo activity"; - private static final String IMAGE_FILE_SAVE_ERROR = "Unable to create photo on disk"; - private static final String IMAGE_PROCESS_NO_FILE_ERROR = "Unable to process image, file not found on disk"; - private static final String UNABLE_TO_PROCESS_IMAGE = "Unable to process image"; - private static final String IMAGE_EDIT_ERROR = "Unable to edit image"; - private static final String IMAGE_GALLERY_SAVE_ERROR = "Unable to save the image in the gallery"; - private static final String USER_CANCELLED = "User cancelled photos app"; - - private String imageFileSavePath; - private String imageEditedFileSavePath; - private Uri imageFileUri; - private Uri imagePickedContentUri; - private boolean isEdited = false; - private boolean isFirstRequest = true; - private boolean isSaved = false; - private ActivityResultLauncher pickMultipleMedia = null; - private ActivityResultLauncher pickMedia = null; - - private final AtomicInteger mNextLocalRequestCode = new AtomicInteger(); - - private CameraSettings settings = new CameraSettings(); - - @Override - public void load() { - super.load(); - } - - @PluginMethod - public void getPhoto(PluginCall call) { - isEdited = false; - settings = getSettings(call); - doShow(call); - } - - @PluginMethod - public void pickImages(PluginCall call) { - settings = getSettings(call); - openPhotos(call, true); - } - - @PluginMethod - public void pickLimitedLibraryPhotos(PluginCall call) { - call.unimplemented("not supported on android"); - } - - @PluginMethod - public void getLimitedLibraryPhotos(PluginCall call) { - call.unimplemented("not supported on android"); - } - - private void doShow(PluginCall call) { - switch (settings.getSource()) { - case CAMERA: - showCamera(call); - break; - case PHOTOS: - showPhotos(call); - break; - default: - showPrompt(call); - break; - } - } - - private void showPrompt(final PluginCall call) { - // We have all necessary permissions, open the camera - List options = new ArrayList<>(); - options.add(call.getString("promptLabelPhoto", "From Photos")); - options.add(call.getString("promptLabelPicture", "Take Picture")); - - final CameraBottomSheetDialogFragment fragment = new CameraBottomSheetDialogFragment(); - fragment.setTitle(call.getString("promptLabelHeader", "Photo")); - fragment.setOptions( - options, - (index) -> { - if (index == 0) { - settings.setSource(CameraSource.PHOTOS); - openPhotos(call); - } else if (index == 1) { - settings.setSource(CameraSource.CAMERA); - openCamera(call); - } - }, - () -> call.reject(USER_CANCELLED) - ); - fragment.show(getActivity().getSupportFragmentManager(), "capacitorModalsActionSheet"); - } - - private void showCamera(final PluginCall call) { - if (!getContext().getPackageManager().hasSystemFeature(PackageManager.FEATURE_CAMERA_ANY)) { - call.reject(NO_CAMERA_ERROR); - return; - } - openCamera(call); - } - - private void showPhotos(final PluginCall call) { - openPhotos(call); - } - - private boolean checkCameraPermissions(PluginCall call) { - // if the manifest does not contain the camera permissions key, we don't need to ask the user - boolean needCameraPerms = isPermissionDeclared(CAMERA); - boolean hasCameraPerms = !needCameraPerms || getPermissionState(CAMERA) == PermissionState.GRANTED; - boolean hasGalleryPerms = getPermissionState(SAVE_GALLERY) == PermissionState.GRANTED; - - // If we want to save to the gallery, we need two permissions - // actually we only need permissions to save to gallery for Android <= 9 (API 28) - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { - // we might still need to request permission for the camera - if (!hasCameraPerms) { - requestPermissionForAlias(CAMERA, call, "cameraPermissionsCallback"); - return false; - } - return true; - } - - // we need to request permissions to save to gallery for Android <= 9 - if (settings.isSaveToGallery() && !(hasCameraPerms && hasGalleryPerms) && isFirstRequest) { - isFirstRequest = false; - String[] aliases; - if (needCameraPerms) { - aliases = new String[] { CAMERA, SAVE_GALLERY }; - } else { - aliases = new String[] { SAVE_GALLERY }; - } - requestPermissionForAliases(aliases, call, "cameraPermissionsCallback"); - return false; - } - // If we don't need to save to the gallery, we can just ask for camera permissions - else if (!hasCameraPerms) { - requestPermissionForAlias(CAMERA, call, "cameraPermissionsCallback"); - return false; - } - return true; - } - - /** - * Completes the plugin call after a camera permission request - * - * @see #getPhoto(PluginCall) - * @param call the plugin call - */ - @PermissionCallback - private void cameraPermissionsCallback(PluginCall call) { - if (call.getMethodName().equals("pickImages")) { - openPhotos(call, true); - } else { - if (settings.getSource() == CameraSource.CAMERA && getPermissionState(CAMERA) != PermissionState.GRANTED) { - Logger.debug(getLogTag(), "User denied camera permission: " + getPermissionState(CAMERA).toString()); - call.reject(PERMISSION_DENIED_ERROR_CAMERA); - return; - } - doShow(call); - } - } - - @Override - protected void requestPermissionForAliases(@NonNull String[] aliases, @NonNull PluginCall call, @NonNull String callbackName) { - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) { - for (int i = 0; i < aliases.length; i++) { - if (aliases[i].equals(SAVE_GALLERY)) { - aliases[i] = READ_EXTERNAL_STORAGE; - } - } - } - super.requestPermissionForAliases(aliases, call, callbackName); - } - - private CameraSettings getSettings(PluginCall call) { - CameraSettings settings = new CameraSettings(); - settings.setResultType(getResultType(call.getString("resultType"))); - settings.setSaveToGallery(call.getBoolean("saveToGallery", CameraSettings.DEFAULT_SAVE_IMAGE_TO_GALLERY)); - settings.setAllowEditing(call.getBoolean("allowEditing", false)); - settings.setQuality(call.getInt("quality", CameraSettings.DEFAULT_QUALITY)); - settings.setWidth(call.getInt("width", 0)); - settings.setHeight(call.getInt("height", 0)); - settings.setShouldResize(settings.getWidth() > 0 || settings.getHeight() > 0); - settings.setShouldCorrectOrientation(call.getBoolean("correctOrientation", CameraSettings.DEFAULT_CORRECT_ORIENTATION)); - try { - settings.setSource(CameraSource.valueOf(call.getString("source", CameraSource.PROMPT.getSource()))); - } catch (IllegalArgumentException ex) { - settings.setSource(CameraSource.PROMPT); - } - return settings; - } - - private CameraResultType getResultType(String resultType) { - if (resultType == null) { - return null; - } - try { - return CameraResultType.valueOf(resultType.toUpperCase(Locale.ROOT)); - } catch (IllegalArgumentException ex) { - Logger.debug(getLogTag(), "Invalid result type \"" + resultType + "\", defaulting to base64"); - return CameraResultType.BASE64; - } - } - - public void openCamera(final PluginCall call) { - if (checkCameraPermissions(call)) { - Intent takePictureIntent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE); - if (takePictureIntent.resolveActivity(getContext().getPackageManager()) != null) { - // If we will be saving the photo, send the target file along - try { - String appId = getAppId(); - File photoFile = CameraUtils.createImageFile(getActivity()); - imageFileSavePath = photoFile.getAbsolutePath(); - // TODO: Verify provider config exists - imageFileUri = FileProvider.getUriForFile(getActivity(), appId + ".fileprovider", photoFile); - takePictureIntent.putExtra(MediaStore.EXTRA_OUTPUT, imageFileUri); - } catch (Exception ex) { - call.reject(IMAGE_FILE_SAVE_ERROR, ex); - return; - } - - startActivityForResult(call, takePictureIntent, "processCameraImage"); - } else { - call.reject(NO_CAMERA_ACTIVITY_ERROR); - } - } - } - - public void openPhotos(final PluginCall call) { - openPhotos(call, false); - } - - private ActivityResultLauncher registerActivityResultLauncher( - ActivityResultContract contract, - ActivityResultCallback callback - ) { - String key = "cap_activity_rq#" + mNextLocalRequestCode.getAndIncrement(); - if (bridge.getFragment() != null) { - Object host = bridge.getFragment().getHost(); - if (host instanceof ActivityResultRegistryOwner) { - return ((ActivityResultRegistryOwner) host).getActivityResultRegistry().register(key, contract, callback); - } - return bridge.getFragment().requireActivity().getActivityResultRegistry().register(key, contract, callback); - } - return bridge.getActivity().getActivityResultRegistry().register(key, contract, callback); - } - - private ActivityResultContract> getContractForCall(final PluginCall call) { - int limit = call.getInt("limit", 0); - if (limit > 1) { - return new ActivityResultContracts.PickMultipleVisualMedia(limit); - } else { - return new ActivityResultContracts.PickMultipleVisualMedia(); - } - } - - private void openPhotos(final PluginCall call, boolean multiple) { - try { - if (multiple) { - pickMultipleMedia = registerActivityResultLauncher(getContractForCall(call), (uris) -> { - if (!uris.isEmpty()) { - Executor executor = Executors.newSingleThreadExecutor(); - executor.execute(() -> { - JSObject ret = new JSObject(); - JSArray photos = new JSArray(); - for (Uri imageUri : uris) { - try { - JSObject processResult = processPickedImages(imageUri); - if (processResult.getString("error") != null && !processResult.getString("error").isEmpty()) { - call.reject(processResult.getString("error")); - return; - } else { - photos.put(processResult); - } - } catch (SecurityException ex) { - call.reject("SecurityException"); - } - } - ret.put("photos", photos); - call.resolve(ret); - }); - } else { - call.reject(USER_CANCELLED); - } - pickMultipleMedia.unregister(); - }); - pickMultipleMedia.launch( - new PickVisualMediaRequest.Builder().setMediaType(ActivityResultContracts.PickVisualMedia.ImageOnly.INSTANCE).build() - ); - } else { - pickMedia = registerActivityResultLauncher(new ActivityResultContracts.PickVisualMedia(), (uri) -> { - if (uri != null) { - imagePickedContentUri = uri; - processPickedImage(uri, call); - } else { - call.reject(USER_CANCELLED); - } - pickMedia.unregister(); - }); - pickMedia.launch( - new PickVisualMediaRequest.Builder().setMediaType(ActivityResultContracts.PickVisualMedia.ImageOnly.INSTANCE).build() - ); - } - } catch (ActivityNotFoundException ex) { - call.reject(NO_PHOTO_ACTIVITY_ERROR); - } - } - - @ActivityCallback - public void processCameraImage(PluginCall call, ActivityResult result) { - settings = getSettings(call); - if (imageFileSavePath == null) { - call.reject(IMAGE_PROCESS_NO_FILE_ERROR); - return; - } - // Load the image as a Bitmap - File f = new File(imageFileSavePath); - BitmapFactory.Options bmOptions = new BitmapFactory.Options(); - Uri contentUri = Uri.fromFile(f); - Bitmap bitmap = BitmapFactory.decodeFile(imageFileSavePath, bmOptions); - - if (bitmap == null) { - call.reject(USER_CANCELLED); - return; - } - - returnResult(call, bitmap, contentUri); - } - - public void processPickedImage(PluginCall call, ActivityResult result) { - settings = getSettings(call); - Intent data = result.getData(); - if (data == null) { - call.reject(USER_CANCELLED); - return; - } - - Uri u = data.getData(); - - imagePickedContentUri = u; - - processPickedImage(u, call); - } - - @SuppressWarnings("deprecation") - private ArrayList getLegacyParcelableArrayList(Bundle bundle, String key) { - return bundle.getParcelableArrayList(key); - } - - private void processPickedImage(Uri imageUri, PluginCall call) { - InputStream imageStream = null; - - try { - imageStream = getContext().getContentResolver().openInputStream(imageUri); - Bitmap bitmap = BitmapFactory.decodeStream(imageStream); - - if (bitmap == null) { - call.reject("Unable to process bitmap"); - return; - } - - returnResult(call, bitmap, imageUri); - } catch (OutOfMemoryError err) { - call.reject("Out of memory"); - } catch (FileNotFoundException ex) { - call.reject("No such image found", ex); - } finally { - if (imageStream != null) { - try { - imageStream.close(); - } catch (IOException e) { - Logger.error(getLogTag(), UNABLE_TO_PROCESS_IMAGE, e); - } - } - } - } - - private JSObject processPickedImages(Uri imageUri) { - InputStream imageStream = null; - JSObject ret = new JSObject(); - try { - imageStream = getContext().getContentResolver().openInputStream(imageUri); - Bitmap bitmap = BitmapFactory.decodeStream(imageStream); - - if (bitmap == null) { - ret.put("error", "Unable to process bitmap"); - return ret; - } - - ExifWrapper exif = ImageUtils.getExifData(getContext(), bitmap, imageUri); - try { - bitmap = prepareBitmap(bitmap, imageUri, exif); - } catch (IOException e) { - ret.put("error", UNABLE_TO_PROCESS_IMAGE); - return ret; - } - // Compress the final image and prepare for output to client - ByteArrayOutputStream bitmapOutputStream = new ByteArrayOutputStream(); - bitmap.compress(Bitmap.CompressFormat.JPEG, settings.getQuality(), bitmapOutputStream); - - Uri newUri = getTempImage(imageUri, bitmapOutputStream); - exif.copyExif(newUri.getPath()); - if (newUri != null) { - ret.put("format", "jpeg"); - ret.put("exif", exif.toJson()); - ret.put("path", newUri.toString()); - ret.put("webPath", FileUtils.getPortablePath(getContext(), bridge.getLocalUrl(), newUri)); - } else { - ret.put("error", UNABLE_TO_PROCESS_IMAGE); - } - return ret; - } catch (OutOfMemoryError err) { - ret.put("error", "Out of memory"); - } catch (FileNotFoundException ex) { - ret.put("error", "No such image found"); - Logger.error(getLogTag(), "No such image found", ex); - } finally { - if (imageStream != null) { - try { - imageStream.close(); - } catch (IOException e) { - Logger.error(getLogTag(), UNABLE_TO_PROCESS_IMAGE, e); - } - } - } - return ret; - } - - @ActivityCallback - private void processEditedImage(PluginCall call, ActivityResult result) { - isEdited = true; - settings = getSettings(call); - if (result.getResultCode() == Activity.RESULT_CANCELED) { - // User cancelled the edit operation, if this file was picked from photos, - // process the original picked image, otherwise process it as a camera photo - if (imagePickedContentUri != null) { - processPickedImage(imagePickedContentUri, call); - } else { - processCameraImage(call, result); - } - } else { - processPickedImage(call, result); - } - } - - /** - * Save the modified image on the same path, - * or on a temporary location if it's a content url - * @param uri - * @param is - * @return - * @throws IOException - */ - private Uri saveImage(Uri uri, InputStream is) throws IOException { - File outFile = null; - if (uri.getScheme().equals("content")) { - outFile = getTempFile(uri); - } else { - outFile = new File(uri.getPath()); - } - try { - writePhoto(outFile, is); - } catch (FileNotFoundException ex) { - // Some gallery apps return read only file url, create a temporary file for modifications - outFile = getTempFile(uri); - writePhoto(outFile, is); - } - return Uri.fromFile(outFile); - } - - private void writePhoto(File outFile, InputStream is) throws IOException { - FileOutputStream fos = new FileOutputStream(outFile); - byte[] buffer = new byte[1024]; - int len; - while ((len = is.read(buffer)) != -1) { - fos.write(buffer, 0, len); - } - fos.close(); - } - - private File getTempFile(Uri uri) { - String filename = Uri.parse(Uri.decode(uri.toString())).getLastPathSegment(); - if (!filename.contains(".jpg") && !filename.contains(".jpeg")) { - filename += "." + (new java.util.Date()).getTime() + ".jpeg"; - } - File cacheDir = getContext().getCacheDir(); - return new File(cacheDir, filename); - } - - /** - * After processing the image, return the final result back to the caller. - * @param call - * @param bitmap - * @param u - */ - @SuppressWarnings("deprecation") - private void returnResult(PluginCall call, Bitmap bitmap, Uri u) { - ExifWrapper exif = ImageUtils.getExifData(getContext(), bitmap, u); - try { - bitmap = prepareBitmap(bitmap, u, exif); - } catch (IOException e) { - call.reject(UNABLE_TO_PROCESS_IMAGE); - return; - } - // Compress the final image and prepare for output to client - ByteArrayOutputStream bitmapOutputStream = new ByteArrayOutputStream(); - bitmap.compress(Bitmap.CompressFormat.JPEG, settings.getQuality(), bitmapOutputStream); - - if (settings.isAllowEditing() && !isEdited) { - editImage(call, u, bitmapOutputStream); - return; - } - - boolean saveToGallery = call.getBoolean("saveToGallery", CameraSettings.DEFAULT_SAVE_IMAGE_TO_GALLERY); - if (saveToGallery && (imageEditedFileSavePath != null || imageFileSavePath != null)) { - isSaved = true; - try { - String fileToSavePath = imageEditedFileSavePath != null ? imageEditedFileSavePath : imageFileSavePath; - File fileToSave = new File(fileToSavePath); - - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { - ContentResolver resolver = getContext().getContentResolver(); - ContentValues values = new ContentValues(); - values.put(MediaStore.MediaColumns.DISPLAY_NAME, fileToSave.getName()); - values.put(MediaStore.MediaColumns.MIME_TYPE, "image/jpeg"); - values.put(MediaStore.MediaColumns.RELATIVE_PATH, Environment.DIRECTORY_DCIM); - - final Uri contentUri = MediaStore.Images.Media.EXTERNAL_CONTENT_URI; - Uri uri = resolver.insert(contentUri, values); - - if (uri == null) { - throw new IOException("Failed to create new MediaStore record."); - } - - OutputStream stream = resolver.openOutputStream(uri); - if (stream == null) { - throw new IOException("Failed to open output stream."); - } - - Boolean inserted = bitmap.compress(Bitmap.CompressFormat.JPEG, settings.getQuality(), stream); - - if (!inserted) { - isSaved = false; - } - } else { - String inserted = MediaStore.Images.Media.insertImage( - getContext().getContentResolver(), - fileToSavePath, - fileToSave.getName(), - "" - ); - - if (inserted == null) { - isSaved = false; - } - } - } catch (FileNotFoundException e) { - isSaved = false; - Logger.error(getLogTag(), IMAGE_GALLERY_SAVE_ERROR, e); - } catch (IOException e) { - isSaved = false; - Logger.error(getLogTag(), IMAGE_GALLERY_SAVE_ERROR, e); - } - } - - if (settings.getResultType() == CameraResultType.BASE64) { - returnBase64(call, exif, bitmapOutputStream); - } else if (settings.getResultType() == CameraResultType.URI) { - returnFileURI(call, exif, bitmap, u, bitmapOutputStream); - } else if (settings.getResultType() == CameraResultType.DATAURL) { - returnDataUrl(call, exif, bitmapOutputStream); - } else { - call.reject(INVALID_RESULT_TYPE_ERROR); - } - // Result returned, clear stored paths and images - if (settings.getResultType() != CameraResultType.URI) { - deleteImageFile(); - } - imageFileSavePath = null; - imageFileUri = null; - imagePickedContentUri = null; - imageEditedFileSavePath = null; - } - - private void deleteImageFile() { - if (imageFileSavePath != null && !settings.isSaveToGallery()) { - File photoFile = new File(imageFileSavePath); - if (photoFile.exists()) { - photoFile.delete(); - } - } - } - - private void returnFileURI(PluginCall call, ExifWrapper exif, Bitmap bitmap, Uri u, ByteArrayOutputStream bitmapOutputStream) { - Uri newUri = getTempImage(u, bitmapOutputStream); - exif.copyExif(newUri.getPath()); - if (newUri != null) { - JSObject ret = new JSObject(); - ret.put("format", "jpeg"); - ret.put("exif", exif.toJson()); - ret.put("path", newUri.toString()); - ret.put("webPath", FileUtils.getPortablePath(getContext(), bridge.getLocalUrl(), newUri)); - ret.put("saved", isSaved); - call.resolve(ret); - } else { - call.reject(UNABLE_TO_PROCESS_IMAGE); - } - } - - private Uri getTempImage(Uri u, ByteArrayOutputStream bitmapOutputStream) { - ByteArrayInputStream bis = null; - Uri newUri = null; - try { - bis = new ByteArrayInputStream(bitmapOutputStream.toByteArray()); - newUri = saveImage(u, bis); - } catch (IOException ex) { - } finally { - if (bis != null) { - try { - bis.close(); - } catch (IOException e) { - Logger.error(getLogTag(), UNABLE_TO_PROCESS_IMAGE, e); - } - } - } - return newUri; - } - - /** - * Apply our standard processing of the bitmap, returning a new one and - * recycling the old one in the process - * @param bitmap - * @param imageUri - * @param exif - * @return - */ - private Bitmap prepareBitmap(Bitmap bitmap, Uri imageUri, ExifWrapper exif) throws IOException { - if (settings.isShouldCorrectOrientation()) { - final Bitmap newBitmap = ImageUtils.correctOrientation(getContext(), bitmap, imageUri, exif); - bitmap = replaceBitmap(bitmap, newBitmap); - } - - if (settings.isShouldResize()) { - final Bitmap newBitmap = ImageUtils.resize(bitmap, settings.getWidth(), settings.getHeight()); - bitmap = replaceBitmap(bitmap, newBitmap); - } - - return bitmap; - } - - private Bitmap replaceBitmap(Bitmap bitmap, final Bitmap newBitmap) { - if (bitmap != newBitmap) { - bitmap.recycle(); - } - bitmap = newBitmap; - return bitmap; - } - - private void returnDataUrl(PluginCall call, ExifWrapper exif, ByteArrayOutputStream bitmapOutputStream) { - byte[] byteArray = bitmapOutputStream.toByteArray(); - String encoded = Base64.encodeToString(byteArray, Base64.NO_WRAP); - - JSObject data = new JSObject(); - data.put("format", "jpeg"); - data.put("dataUrl", "data:image/jpeg;base64," + encoded); - data.put("exif", exif.toJson()); - call.resolve(data); - } - - private void returnBase64(PluginCall call, ExifWrapper exif, ByteArrayOutputStream bitmapOutputStream) { - byte[] byteArray = bitmapOutputStream.toByteArray(); - String encoded = Base64.encodeToString(byteArray, Base64.NO_WRAP); - - JSObject data = new JSObject(); - data.put("format", "jpeg"); - data.put("base64String", encoded); - data.put("exif", exif.toJson()); - call.resolve(data); - } - - @Override - @PluginMethod - public void requestPermissions(PluginCall call) { - // If the camera permission is defined in the manifest, then we have to prompt the user - // or else we will get a security exception when trying to present the camera. If, however, - // it is not defined in the manifest then we don't need to prompt and it will just work. - if (isPermissionDeclared(CAMERA)) { - // just request normally - super.requestPermissions(call); - } else { - // the manifest does not define camera permissions, so we need to decide what to do - // first, extract the permissions being requested - JSArray providedPerms = call.getArray("permissions"); - List permsList = null; - if (providedPerms != null) { - try { - permsList = providedPerms.toList(); - } catch (JSONException e) {} - } - - if ( - Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU || - (permsList != null && permsList.size() == 1 && (permsList.contains(CAMERA) || permsList.contains(PHOTOS))) - ) { - // either we're on Android 13+ (storage permissions do not apply) - // or the only thing being asked for was the camera so we can just return the current state - checkPermissions(call); - } else { - requestPermissionForAlias(SAVE_GALLERY, call, "checkPermissions"); - } - } - } - - @Override - public Map getPermissionStates() { - Map permissionStates = super.getPermissionStates(); - - // If Camera is not in the manifest and therefore not required, say the permission is granted - if (!isPermissionDeclared(CAMERA)) { - permissionStates.put(CAMERA, PermissionState.GRANTED); - } - - if (permissionStates.containsKey(PHOTOS)) { - permissionStates.put(PHOTOS, PermissionState.GRANTED); - } - - // If the SDK version is 30 or higher, update the SAVE_GALLERY state to match the READ_EXTERNAL_STORAGE state. - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) { - String alias = READ_EXTERNAL_STORAGE; - if (permissionStates.containsKey(alias)) { - permissionStates.put(SAVE_GALLERY, permissionStates.get(alias)); - } - } - - return permissionStates; - } - - private void editImage(PluginCall call, Uri uri, ByteArrayOutputStream bitmapOutputStream) { - try { - Uri tempImage = getTempImage(uri, bitmapOutputStream); - Intent editIntent = createEditIntent(tempImage); - if (editIntent != null) { - startActivityForResult(call, editIntent, "processEditedImage"); - } else { - call.reject(IMAGE_EDIT_ERROR); - } - } catch (Exception ex) { - call.reject(IMAGE_EDIT_ERROR, ex); - } - } - - private Intent createEditIntent(Uri origPhotoUri) { - try { - File editFile = new File(origPhotoUri.getPath()); - Uri editUri = FileProvider.getUriForFile(getActivity(), getContext().getPackageName() + ".fileprovider", editFile); - Intent editIntent = new Intent(Intent.ACTION_EDIT); - editIntent.setDataAndType(editUri, "image/*"); - imageEditedFileSavePath = editFile.getAbsolutePath(); - int flags = Intent.FLAG_GRANT_READ_URI_PERMISSION | Intent.FLAG_GRANT_WRITE_URI_PERMISSION; - editIntent.addFlags(flags); - editIntent.putExtra(MediaStore.EXTRA_OUTPUT, editUri); - - List resInfoList; - - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { - resInfoList = getContext() - .getPackageManager() - .queryIntentActivities(editIntent, PackageManager.ResolveInfoFlags.of(PackageManager.MATCH_DEFAULT_ONLY)); - } else { - resInfoList = legacyQueryIntentActivities(editIntent); - } - - for (ResolveInfo resolveInfo : resInfoList) { - String packageName = resolveInfo.activityInfo.packageName; - getContext().grantUriPermission(packageName, editUri, flags); - } - return editIntent; - } catch (Exception ex) { - return null; - } - } - - @SuppressWarnings("deprecation") - private List legacyQueryIntentActivities(Intent intent) { - return getContext().getPackageManager().queryIntentActivities(intent, PackageManager.MATCH_DEFAULT_ONLY); - } - - @Override - protected Bundle saveInstanceState() { - Bundle bundle = super.saveInstanceState(); - if (bundle != null) { - bundle.putString("cameraImageFileSavePath", imageFileSavePath); - } - return bundle; - } - - @Override - protected void restoreState(Bundle state) { - String storedImageFileSavePath = state.getString("cameraImageFileSavePath"); - if (storedImageFileSavePath != null) { - imageFileSavePath = storedImageFileSavePath; - } - } - - /** - * Unregister activity result launches to prevent leaks. - */ - @Override - protected void handleOnDestroy() { - if (pickMedia != null) { - pickMedia.unregister(); - } - if (pickMultipleMedia != null) { - pickMultipleMedia.unregister(); - } - } -} diff --git a/camera/android/src/main/java/com/capacitorjs/plugins/camera/CameraResultType.java b/camera/android/src/main/java/com/capacitorjs/plugins/camera/CameraResultType.java deleted file mode 100644 index 5d050590a..000000000 --- a/camera/android/src/main/java/com/capacitorjs/plugins/camera/CameraResultType.java +++ /dev/null @@ -1,17 +0,0 @@ -package com.capacitorjs.plugins.camera; - -public enum CameraResultType { - BASE64("base64"), - URI("uri"), - DATAURL("dataUrl"); - - private String type; - - CameraResultType(String type) { - this.type = type; - } - - public String getType() { - return type; - } -} diff --git a/camera/android/src/main/java/com/capacitorjs/plugins/camera/CameraSettings.java b/camera/android/src/main/java/com/capacitorjs/plugins/camera/CameraSettings.java deleted file mode 100644 index 93dbdb4b9..000000000 --- a/camera/android/src/main/java/com/capacitorjs/plugins/camera/CameraSettings.java +++ /dev/null @@ -1,90 +0,0 @@ -package com.capacitorjs.plugins.camera; - -public class CameraSettings { - - public static final int DEFAULT_QUALITY = 90; - public static final boolean DEFAULT_SAVE_IMAGE_TO_GALLERY = false; - public static final boolean DEFAULT_CORRECT_ORIENTATION = true; - - private CameraResultType resultType = CameraResultType.BASE64; - private int quality = DEFAULT_QUALITY; - private boolean shouldResize = false; - private boolean shouldCorrectOrientation = DEFAULT_CORRECT_ORIENTATION; - private boolean saveToGallery = DEFAULT_SAVE_IMAGE_TO_GALLERY; - private boolean allowEditing = false; - private int width = 0; - private int height = 0; - private CameraSource source = CameraSource.PROMPT; - - public CameraResultType getResultType() { - return resultType; - } - - public void setResultType(CameraResultType resultType) { - this.resultType = resultType; - } - - public int getQuality() { - return quality; - } - - public void setQuality(int quality) { - this.quality = quality; - } - - public boolean isShouldResize() { - return shouldResize; - } - - public void setShouldResize(boolean shouldResize) { - this.shouldResize = shouldResize; - } - - public boolean isShouldCorrectOrientation() { - return shouldCorrectOrientation; - } - - public void setShouldCorrectOrientation(boolean shouldCorrectOrientation) { - this.shouldCorrectOrientation = shouldCorrectOrientation; - } - - public boolean isSaveToGallery() { - return saveToGallery; - } - - public void setSaveToGallery(boolean saveToGallery) { - this.saveToGallery = saveToGallery; - } - - public boolean isAllowEditing() { - return allowEditing; - } - - public void setAllowEditing(boolean allowEditing) { - this.allowEditing = allowEditing; - } - - public int getWidth() { - return width; - } - - public void setWidth(int width) { - this.width = width; - } - - public int getHeight() { - return height; - } - - public void setHeight(int height) { - this.height = height; - } - - public CameraSource getSource() { - return source; - } - - public void setSource(CameraSource source) { - this.source = source; - } -} diff --git a/camera/android/src/main/java/com/capacitorjs/plugins/camera/CameraSource.java b/camera/android/src/main/java/com/capacitorjs/plugins/camera/CameraSource.java deleted file mode 100644 index 2624c6b39..000000000 --- a/camera/android/src/main/java/com/capacitorjs/plugins/camera/CameraSource.java +++ /dev/null @@ -1,17 +0,0 @@ -package com.capacitorjs.plugins.camera; - -public enum CameraSource { - PROMPT("PROMPT"), - CAMERA("CAMERA"), - PHOTOS("PHOTOS"); - - private String source; - - CameraSource(String source) { - this.source = source; - } - - public String getSource() { - return this.source; - } -} diff --git a/camera/android/src/main/java/com/capacitorjs/plugins/camera/CameraUtils.java b/camera/android/src/main/java/com/capacitorjs/plugins/camera/CameraUtils.java deleted file mode 100644 index dc7455d9c..000000000 --- a/camera/android/src/main/java/com/capacitorjs/plugins/camera/CameraUtils.java +++ /dev/null @@ -1,34 +0,0 @@ -package com.capacitorjs.plugins.camera; - -import android.app.Activity; -import android.net.Uri; -import android.os.Environment; -import androidx.core.content.FileProvider; -import com.getcapacitor.Logger; -import java.io.File; -import java.io.IOException; -import java.text.SimpleDateFormat; -import java.util.Date; - -public class CameraUtils { - - public static Uri createImageFileUri(Activity activity, String appId) throws IOException { - File photoFile = CameraUtils.createImageFile(activity); - return FileProvider.getUriForFile(activity, appId + ".fileprovider", photoFile); - } - - public static File createImageFile(Activity activity) throws IOException { - // Create an image file name - String timeStamp = new SimpleDateFormat("yyyyMMdd_HHmmss").format(new Date()); - String imageFileName = "JPEG_" + timeStamp + "_"; - File storageDir = activity.getExternalFilesDir(Environment.DIRECTORY_PICTURES); - - File image = File.createTempFile(imageFileName, /* prefix */ ".jpg", /* suffix */ storageDir /* directory */); - - return image; - } - - protected static String getLogTag() { - return Logger.tags("CameraUtils"); - } -} diff --git a/camera/android/src/main/java/com/capacitorjs/plugins/camera/ExifWrapper.java b/camera/android/src/main/java/com/capacitorjs/plugins/camera/ExifWrapper.java deleted file mode 100644 index 8cf1c250f..000000000 --- a/camera/android/src/main/java/com/capacitorjs/plugins/camera/ExifWrapper.java +++ /dev/null @@ -1,205 +0,0 @@ -package com.capacitorjs.plugins.camera; - -import static androidx.exifinterface.media.ExifInterface.*; - -import androidx.exifinterface.media.ExifInterface; -import com.getcapacitor.JSObject; - -public class ExifWrapper { - - private final ExifInterface exif; - private final String[] attributes = new String[] { - TAG_APERTURE_VALUE, - TAG_ARTIST, - TAG_BITS_PER_SAMPLE, - TAG_BODY_SERIAL_NUMBER, - TAG_BRIGHTNESS_VALUE, - TAG_CAMERA_OWNER_NAME, - TAG_CFA_PATTERN, - TAG_COLOR_SPACE, - TAG_COMPONENTS_CONFIGURATION, - TAG_COMPRESSED_BITS_PER_PIXEL, - TAG_COMPRESSION, - TAG_CONTRAST, - TAG_COPYRIGHT, - TAG_CUSTOM_RENDERED, - TAG_DATETIME, - TAG_DATETIME_DIGITIZED, - TAG_DATETIME_ORIGINAL, - TAG_DEFAULT_CROP_SIZE, - TAG_DEVICE_SETTING_DESCRIPTION, - TAG_DIGITAL_ZOOM_RATIO, - TAG_DNG_VERSION, - TAG_EXIF_VERSION, - TAG_EXPOSURE_BIAS_VALUE, - TAG_EXPOSURE_INDEX, - TAG_EXPOSURE_MODE, - TAG_EXPOSURE_PROGRAM, - TAG_EXPOSURE_TIME, - TAG_FILE_SOURCE, - TAG_FLASH, - TAG_FLASHPIX_VERSION, - TAG_FLASH_ENERGY, - TAG_FOCAL_LENGTH, - TAG_FOCAL_LENGTH_IN_35MM_FILM, - TAG_FOCAL_PLANE_RESOLUTION_UNIT, - TAG_FOCAL_PLANE_X_RESOLUTION, - TAG_FOCAL_PLANE_Y_RESOLUTION, - TAG_F_NUMBER, - TAG_GAIN_CONTROL, - TAG_GAMMA, - TAG_GPS_ALTITUDE, - TAG_GPS_ALTITUDE_REF, - TAG_GPS_AREA_INFORMATION, - TAG_GPS_DATESTAMP, - TAG_GPS_DEST_BEARING, - TAG_GPS_DEST_BEARING_REF, - TAG_GPS_DEST_DISTANCE, - TAG_GPS_DEST_DISTANCE_REF, - TAG_GPS_DEST_LATITUDE, - TAG_GPS_DEST_LATITUDE_REF, - TAG_GPS_DEST_LONGITUDE, - TAG_GPS_DEST_LONGITUDE_REF, - TAG_GPS_DIFFERENTIAL, - TAG_GPS_DOP, - TAG_GPS_H_POSITIONING_ERROR, - TAG_GPS_IMG_DIRECTION, - TAG_GPS_IMG_DIRECTION_REF, - TAG_GPS_LATITUDE, - TAG_GPS_LATITUDE_REF, - TAG_GPS_LONGITUDE, - TAG_GPS_LONGITUDE_REF, - TAG_GPS_MAP_DATUM, - TAG_GPS_MEASURE_MODE, - TAG_GPS_PROCESSING_METHOD, - TAG_GPS_SATELLITES, - TAG_GPS_SPEED, - TAG_GPS_SPEED_REF, - TAG_GPS_STATUS, - TAG_GPS_TIMESTAMP, - TAG_GPS_TRACK, - TAG_GPS_TRACK_REF, - TAG_GPS_VERSION_ID, - TAG_IMAGE_DESCRIPTION, - TAG_IMAGE_LENGTH, - TAG_IMAGE_UNIQUE_ID, - TAG_IMAGE_WIDTH, - TAG_INTEROPERABILITY_INDEX, - TAG_ISO_SPEED, - TAG_ISO_SPEED_LATITUDE_YYY, - TAG_ISO_SPEED_LATITUDE_ZZZ, - TAG_JPEG_INTERCHANGE_FORMAT, - TAG_JPEG_INTERCHANGE_FORMAT_LENGTH, - TAG_LENS_MAKE, - TAG_LENS_MODEL, - TAG_LENS_SERIAL_NUMBER, - TAG_LENS_SPECIFICATION, - TAG_LIGHT_SOURCE, - TAG_MAKE, - TAG_MAKER_NOTE, - TAG_MAX_APERTURE_VALUE, - TAG_METERING_MODE, - TAG_MODEL, - TAG_NEW_SUBFILE_TYPE, - TAG_OECF, - TAG_OFFSET_TIME, - TAG_OFFSET_TIME_DIGITIZED, - TAG_OFFSET_TIME_ORIGINAL, - TAG_ORF_ASPECT_FRAME, - TAG_ORF_PREVIEW_IMAGE_LENGTH, - TAG_ORF_PREVIEW_IMAGE_START, - TAG_ORF_THUMBNAIL_IMAGE, - TAG_ORIENTATION, - TAG_PHOTOGRAPHIC_SENSITIVITY, - TAG_PHOTOMETRIC_INTERPRETATION, - TAG_PIXEL_X_DIMENSION, - TAG_PIXEL_Y_DIMENSION, - TAG_PLANAR_CONFIGURATION, - TAG_PRIMARY_CHROMATICITIES, - TAG_RECOMMENDED_EXPOSURE_INDEX, - TAG_REFERENCE_BLACK_WHITE, - TAG_RELATED_SOUND_FILE, - TAG_RESOLUTION_UNIT, - TAG_ROWS_PER_STRIP, - TAG_RW2_ISO, - TAG_RW2_JPG_FROM_RAW, - TAG_RW2_SENSOR_BOTTOM_BORDER, - TAG_RW2_SENSOR_LEFT_BORDER, - TAG_RW2_SENSOR_RIGHT_BORDER, - TAG_RW2_SENSOR_TOP_BORDER, - TAG_SAMPLES_PER_PIXEL, - TAG_SATURATION, - TAG_SCENE_CAPTURE_TYPE, - TAG_SCENE_TYPE, - TAG_SENSING_METHOD, - TAG_SENSITIVITY_TYPE, - TAG_SHARPNESS, - TAG_SHUTTER_SPEED_VALUE, - TAG_SOFTWARE, - TAG_SPATIAL_FREQUENCY_RESPONSE, - TAG_SPECTRAL_SENSITIVITY, - TAG_STANDARD_OUTPUT_SENSITIVITY, - TAG_STRIP_BYTE_COUNTS, - TAG_STRIP_OFFSETS, - TAG_SUBFILE_TYPE, - TAG_SUBJECT_AREA, - TAG_SUBJECT_DISTANCE, - TAG_SUBJECT_DISTANCE_RANGE, - TAG_SUBJECT_LOCATION, - TAG_SUBSEC_TIME, - TAG_SUBSEC_TIME_DIGITIZED, - TAG_SUBSEC_TIME_ORIGINAL, - TAG_THUMBNAIL_IMAGE_LENGTH, - TAG_THUMBNAIL_IMAGE_WIDTH, - TAG_TRANSFER_FUNCTION, - TAG_USER_COMMENT, - TAG_WHITE_BALANCE, - TAG_WHITE_POINT, - TAG_XMP, - TAG_X_RESOLUTION, - TAG_Y_CB_CR_COEFFICIENTS, - TAG_Y_CB_CR_POSITIONING, - TAG_Y_CB_CR_SUB_SAMPLING, - TAG_Y_RESOLUTION - }; - - public ExifWrapper(ExifInterface exif) { - this.exif = exif; - } - - public JSObject toJson() { - JSObject ret = new JSObject(); - - if (this.exif == null) { - return ret; - } - - for (int i = 0; i < attributes.length; i++) { - p(ret, attributes[i]); - } - - return ret; - } - - public void p(JSObject o, String tag) { - String val = exif.getAttribute(tag); - o.put(tag, val); - } - - public void copyExif(String destFile) { - try { - ExifInterface destExif = new ExifInterface(destFile); - for (int i = 0; i < attributes.length; i++) { - String value = exif.getAttribute(attributes[i]); - if (value != null) { - destExif.setAttribute(attributes[i], value); - } - } - destExif.saveAttributes(); - } catch (Exception ex) {} - } - - public void resetOrientation() { - exif.resetOrientation(); - } -} diff --git a/camera/android/src/main/java/com/capacitorjs/plugins/camera/ImageUtils.java b/camera/android/src/main/java/com/capacitorjs/plugins/camera/ImageUtils.java deleted file mode 100644 index 82d82aad0..000000000 --- a/camera/android/src/main/java/com/capacitorjs/plugins/camera/ImageUtils.java +++ /dev/null @@ -1,124 +0,0 @@ -package com.capacitorjs.plugins.camera; - -import android.content.Context; -import android.database.Cursor; -import android.graphics.Bitmap; -import android.graphics.Matrix; -import android.net.Uri; -import android.os.Build; -import android.provider.MediaStore; -import androidx.exifinterface.media.ExifInterface; -import com.getcapacitor.Logger; -import java.io.IOException; -import java.io.InputStream; - -public class ImageUtils { - - /** - * Resize an image to the given max width and max height. Constraint can be put - * on one dimension, or both. Resize will always preserve aspect ratio. - * @param bitmap - * @param desiredMaxWidth - * @param desiredMaxHeight - * @return a new, scaled Bitmap - */ - public static Bitmap resize(Bitmap bitmap, final int desiredMaxWidth, final int desiredMaxHeight) { - return ImageUtils.resizePreservingAspectRatio(bitmap, desiredMaxWidth, desiredMaxHeight); - } - - /** - * Resize an image to the given max width and max height. Constraint can be put - * on one dimension, or both. Resize will always preserve aspect ratio. - * @param bitmap - * @param desiredMaxWidth - * @param desiredMaxHeight - * @return a new, scaled Bitmap - */ - private static Bitmap resizePreservingAspectRatio(Bitmap bitmap, final int desiredMaxWidth, final int desiredMaxHeight) { - int width = bitmap.getWidth(); - int height = bitmap.getHeight(); - - // 0 is treated as 'no restriction' - int maxHeight = desiredMaxHeight == 0 ? height : desiredMaxHeight; - int maxWidth = desiredMaxWidth == 0 ? width : desiredMaxWidth; - - // resize with preserved aspect ratio - float newWidth = Math.min(width, maxWidth); - float newHeight = (height * newWidth) / width; - - if (newHeight > maxHeight) { - newWidth = (width * maxHeight) / height; - newHeight = maxHeight; - } - return Bitmap.createScaledBitmap(bitmap, Math.round(newWidth), Math.round(newHeight), false); - } - - /** - * Transform an image with the given matrix - * @param bitmap - * @param matrix - * @return - */ - private static Bitmap transform(final Bitmap bitmap, final Matrix matrix) { - return Bitmap.createBitmap(bitmap, 0, 0, bitmap.getWidth(), bitmap.getHeight(), matrix, true); - } - - /** - * Correct the orientation of an image by reading its exif information and rotating - * the appropriate amount for portrait mode - * @param bitmap - * @param imageUri - * @param exif - * @return - */ - public static Bitmap correctOrientation(final Context c, final Bitmap bitmap, final Uri imageUri, ExifWrapper exif) throws IOException { - final int orientation = getOrientation(c, imageUri); - if (orientation != 0) { - Matrix matrix = new Matrix(); - matrix.postRotate(orientation); - exif.resetOrientation(); - return transform(bitmap, matrix); - } else { - return bitmap; - } - } - - private static int getOrientation(final Context c, final Uri imageUri) throws IOException { - int result = 0; - - try (InputStream iStream = c.getContentResolver().openInputStream(imageUri)) { - final ExifInterface exifInterface = new ExifInterface(iStream); - - final int orientation = exifInterface.getAttributeInt(ExifInterface.TAG_ORIENTATION, ExifInterface.ORIENTATION_NORMAL); - - if (orientation == ExifInterface.ORIENTATION_ROTATE_90) { - result = 90; - } else if (orientation == ExifInterface.ORIENTATION_ROTATE_180) { - result = 180; - } else if (orientation == ExifInterface.ORIENTATION_ROTATE_270) { - result = 270; - } - } - - return result; - } - - public static ExifWrapper getExifData(final Context c, final Bitmap bitmap, final Uri imageUri) { - InputStream stream = null; - try { - stream = c.getContentResolver().openInputStream(imageUri); - final ExifInterface exifInterface = new ExifInterface(stream); - - return new ExifWrapper(exifInterface); - } catch (IOException ex) { - Logger.error("Error loading exif data from image", ex); - } finally { - if (stream != null) { - try { - stream.close(); - } catch (IOException ignored) {} - } - } - return new ExifWrapper(null); - } -} diff --git a/camera/android/src/test/java/com/getcapacitor/ExampleUnitTest.java b/camera/android/src/test/java/com/getcapacitor/ExampleUnitTest.java deleted file mode 100644 index a0fed0cfb..000000000 --- a/camera/android/src/test/java/com/getcapacitor/ExampleUnitTest.java +++ /dev/null @@ -1,18 +0,0 @@ -package com.getcapacitor; - -import static org.junit.Assert.*; - -import org.junit.Test; - -/** - * Example local unit test, which will execute on the development machine (host). - * - * @see Testing documentation - */ -public class ExampleUnitTest { - - @Test - public void addition_isCorrect() throws Exception { - assertEquals(4, 2 + 2); - } -} diff --git a/camera/ios/.gitignore b/camera/ios/.gitignore deleted file mode 100644 index 0023a5340..000000000 --- a/camera/ios/.gitignore +++ /dev/null @@ -1,8 +0,0 @@ -.DS_Store -/.build -/Packages -xcuserdata/ -DerivedData/ -.swiftpm/configuration/registries.json -.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata -.netrc diff --git a/camera/ios/Sources/CameraPlugin/CameraExtensions.swift b/camera/ios/Sources/CameraPlugin/CameraExtensions.swift deleted file mode 100644 index 25cc8a2b1..000000000 --- a/camera/ios/Sources/CameraPlugin/CameraExtensions.swift +++ /dev/null @@ -1,100 +0,0 @@ -import UIKit -import Photos - -internal protocol CameraAuthorizationState { - var authorizationState: String { get } -} - -extension AVAuthorizationStatus: CameraAuthorizationState { - var authorizationState: String { - switch self { - case .denied, .restricted: - return "denied" - case .authorized: - return "granted" - case .notDetermined: - fallthrough - @unknown default: - return "prompt" - } - } -} - -extension PHAuthorizationStatus: CameraAuthorizationState { - var authorizationState: String { - switch self { - case .denied, .restricted: - return "denied" - case .authorized: - return "granted" - case .limited: - return "limited" - case .notDetermined: - fallthrough - @unknown default: - return "prompt" - } - } -} - -internal extension PHAsset { - /** - Retrieves the image metadata for the asset. - */ - var imageData: [String: Any] { - let options = PHImageRequestOptions() - options.isSynchronous = true - options.resizeMode = .none - options.isNetworkAccessAllowed = false - options.version = .current - - var result: [String: Any] = [:] - _ = PHCachingImageManager().requestImageDataAndOrientation(for: self, options: options) { (data, _, _, _) in - if let data = data as NSData? { - let options = [kCGImageSourceShouldCache as String: kCFBooleanFalse] as CFDictionary - if let imgSrc = CGImageSourceCreateWithData(data, options), - let metadata = CGImageSourceCopyPropertiesAtIndex(imgSrc, 0, options) as? [String: Any] { - result = metadata - } - } - } - return result - } -} - -internal extension UIImage { - /** - Generates a new image from the existing one, implicitly resetting any orientation. - Dimensions greater than 0 will resize the image while preserving the aspect ratio. - */ - func reformat(to size: CGSize? = nil) -> UIImage { - let imageHeight = self.size.height - let imageWidth = self.size.width - // determine the max dimensions, 0 is treated as 'no restriction' - var maxWidth: CGFloat - if let size = size, size.width > 0 { - maxWidth = size.width - } else { - maxWidth = imageWidth - } - let maxHeight: CGFloat - if let size = size, size.height > 0 { - maxHeight = size.height - } else { - maxHeight = imageHeight - } - // adjust to preserve aspect ratio - var targetWidth = min(imageWidth, maxWidth) - var targetHeight = (imageHeight * targetWidth) / imageWidth - if targetHeight > maxHeight { - targetWidth = (imageWidth * maxHeight) / imageHeight - targetHeight = maxHeight - } - // generate the new image and return - UIGraphicsBeginImageContextWithOptions(.init(width: targetWidth, height: targetHeight), false, 1.0) // size, opaque and scale - self.draw(in: .init(origin: .zero, size: .init(width: targetWidth, height: targetHeight))) - let resizedImage = UIGraphicsGetImageFromCurrentImageContext() - UIGraphicsEndImageContext() - return resizedImage ?? self - } -} diff --git a/camera/ios/Sources/CameraPlugin/CameraPlugin.swift b/camera/ios/Sources/CameraPlugin/CameraPlugin.swift deleted file mode 100644 index 251b8dd32..000000000 --- a/camera/ios/Sources/CameraPlugin/CameraPlugin.swift +++ /dev/null @@ -1,553 +0,0 @@ -import Foundation -import Capacitor -import Photos -import PhotosUI - -@objc(CAPCameraPlugin) -public class CameraPlugin: CAPPlugin, CAPBridgedPlugin { - public let identifier = "CAPCameraPlugin" - public let jsName = "Camera" - public let pluginMethods: [CAPPluginMethod] = [ - CAPPluginMethod(name: "getPhoto", returnType: CAPPluginReturnPromise), - CAPPluginMethod(name: "pickImages", returnType: CAPPluginReturnPromise), - CAPPluginMethod(name: "checkPermissions", returnType: CAPPluginReturnPromise), - CAPPluginMethod(name: "requestPermissions", returnType: CAPPluginReturnPromise), - CAPPluginMethod(name: "pickLimitedLibraryPhotos", returnType: CAPPluginReturnPromise), - CAPPluginMethod(name: "getLimitedLibraryPhotos", returnType: CAPPluginReturnPromise) - ] - private var call: CAPPluginCall? - private var settings = CameraSettings() - private let defaultSource = CameraSource.prompt - private let defaultDirection = CameraDirection.rear - private var multiple = false - - private var imageCounter = 0 - - @objc override public func checkPermissions(_ call: CAPPluginCall) { - var result: [String: Any] = [:] - for permission in CameraPermissionType.allCases { - let state: String - switch permission { - case .camera: - state = AVCaptureDevice.authorizationStatus(for: .video).authorizationState - case .photos: - state = PHPhotoLibrary.authorizationStatus(for: .readWrite).authorizationState - } - result[permission.rawValue] = state - } - call.resolve(result) - } - - @objc override public func requestPermissions(_ call: CAPPluginCall) { - // get the list of desired types, if passed - let typeList = call.getArray("permissions", String.self)?.compactMap({ (type) -> CameraPermissionType? in - return CameraPermissionType(rawValue: type) - }) ?? [] - // otherwise check everything - let permissions: [CameraPermissionType] = (typeList.count > 0) ? typeList : CameraPermissionType.allCases - // request the permissions - let group = DispatchGroup() - for permission in permissions { - switch permission { - case .camera: - group.enter() - AVCaptureDevice.requestAccess(for: .video) { _ in - group.leave() - } - case .photos: - group.enter() - PHPhotoLibrary.requestAuthorization(for: .readWrite) { (_) in - group.leave() - } - } - } - group.notify(queue: DispatchQueue.main) { [weak self] in - self?.checkPermissions(call) - } - } - - @objc func pickLimitedLibraryPhotos(_ call: CAPPluginCall) { - PHPhotoLibrary.requestAuthorization(for: .readWrite) { (granted) in - if granted == .limited { - if let viewController = self.bridge?.viewController { - PHPhotoLibrary.shared().presentLimitedLibraryPicker(from: viewController) { _ in - self.getLimitedLibraryPhotos(call) - } - } - } else { - call.resolve([ - "photos": [] - ]) - } - } - } - - @objc func getLimitedLibraryPhotos(_ call: CAPPluginCall) { - PHPhotoLibrary.requestAuthorization(for: .readWrite) { (granted) in - if granted == .limited { - - self.call = call - - DispatchQueue.global(qos: .utility).async { - let assets = PHAsset.fetchAssets(with: .image, options: nil) - var processedImages: [ProcessedImage] = [] - - let imageManager = PHImageManager.default() - let options = PHImageRequestOptions() - options.deliveryMode = .highQualityFormat - - let group = DispatchGroup() - if assets.count > 0 { - for index in 0...(assets.count - 1) { - let asset = assets.object(at: index) - let fullSize = CGSize(width: asset.pixelWidth, height: asset.pixelHeight) - - group.enter() - imageManager.requestImage(for: asset, targetSize: fullSize, contentMode: .default, options: options) { image, _ in - guard let image = image else { - group.leave() - return - } - processedImages.append(self.processedImage(from: image, with: asset.imageData)) - group.leave() - } - } - } - - group.notify(queue: .global(qos: .utility)) { [weak self] in - self?.returnImages(processedImages) - } - } - } else { - call.resolve([ - "photos": [] - ]) - } - } - } - - @objc func getPhoto(_ call: CAPPluginCall) { - self.multiple = false - self.call = call - self.settings = cameraSettings(from: call) - - // Make sure they have all the necessary info.plist settings - if let missingUsageDescription = checkUsageDescriptions() { - CAPLog.print("⚡️ ", self.pluginId, "-", missingUsageDescription) - call.reject(missingUsageDescription) - return - } - - DispatchQueue.main.async { - switch self.settings.source { - case .prompt: - self.showPrompt() - case .camera: - self.showCamera() - case .photos: - self.showPhotos() - } - } - } - - @objc func pickImages(_ call: CAPPluginCall) { - self.multiple = true - self.call = call - self.settings = cameraSettings(from: call) - DispatchQueue.main.async { - self.showPhotos() - } - } - - private func checkUsageDescriptions() -> String? { - if let dict = Bundle.main.infoDictionary { - for key in CameraPropertyListKeys.allCases where dict[key.rawValue] == nil { - return key.missingMessage - } - } - return nil - } - - private func cameraSettings(from call: CAPPluginCall) -> CameraSettings { - var settings = CameraSettings() - settings.jpegQuality = min(abs(CGFloat(call.getFloat("quality") ?? 100.0)) / 100.0, 1.0) - settings.allowEditing = call.getBool("allowEditing") ?? false - settings.source = CameraSource(rawValue: call.getString("source") ?? defaultSource.rawValue) ?? defaultSource - settings.direction = CameraDirection(rawValue: call.getString("direction") ?? defaultDirection.rawValue) ?? defaultDirection - if let typeString = call.getString("resultType"), let type = CameraResultType(rawValue: typeString) { - settings.resultType = type - } - settings.saveToGallery = call.getBool("saveToGallery") ?? false - - // Get the new image dimensions if provided - settings.width = CGFloat(call.getInt("width") ?? 0) - settings.height = CGFloat(call.getInt("height") ?? 0) - if settings.width > 0 || settings.height > 0 { - // We resize only if a dimension was provided - settings.shouldResize = true - } - settings.shouldCorrectOrientation = call.getBool("correctOrientation") ?? true - settings.userPromptText = CameraPromptText(title: call.getString("promptLabelHeader"), - photoAction: call.getString("promptLabelPhoto"), - cameraAction: call.getString("promptLabelPicture"), - cancelAction: call.getString("promptLabelCancel")) - if let styleString = call.getString("presentationStyle"), styleString == "popover" { - settings.presentationStyle = .popover - } else { - settings.presentationStyle = .fullScreen - } - - return settings - } -} - -// public delegate methods -extension CameraPlugin: UIImagePickerControllerDelegate, UINavigationControllerDelegate, UIPopoverPresentationControllerDelegate { - public func imagePickerControllerDidCancel(_ picker: UIImagePickerController) { - picker.dismiss(animated: true) - self.call?.reject("User cancelled photos app") - } - - public func popoverPresentationControllerDidDismissPopover(_ popoverPresentationController: UIPopoverPresentationController) { - self.call?.reject("User cancelled photos app") - } - - public func presentationControllerDidDismiss(_ presentationController: UIPresentationController) { - self.call?.reject("User cancelled photos app") - } - - public func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey: Any]) { - picker.dismiss(animated: true) { - if let processedImage = self.processImage(from: info) { - self.returnProcessedImage(processedImage) - } else { - self.call?.reject("Error processing image") - } - } - } -} - -extension CameraPlugin: PHPickerViewControllerDelegate { - public func picker(_ picker: PHPickerViewController, didFinishPicking results: [PHPickerResult]) { - picker.dismiss(animated: true, completion: nil) - - guard !results.isEmpty else { - self.call?.reject("User cancelled photos app") - return - } - - self.fetchProcessedImages(from: results) { [weak self] processedImageArray in - guard let processedImageArray else { - self?.call?.reject("Error loading image") - return - } - - if self?.multiple == true { - self?.returnImages(processedImageArray) - } else if var processedImage = processedImageArray.first { - processedImage.flags = .gallery - self?.returnProcessedImage(processedImage) - } - } - } - - private func fetchProcessedImages(from pickerResultArray: [PHPickerResult], accumulating: [ProcessedImage] = [], _ completionHandler: @escaping ([ProcessedImage]?) -> Void) { - func loadImage(from pickerResult: PHPickerResult, _ completionHandler: @escaping (UIImage?) -> Void) { - let itemProvider = pickerResult.itemProvider - if itemProvider.canLoadObject(ofClass: UIImage.self) { - // extract the image - itemProvider.loadObject(ofClass: UIImage.self) { itemProviderReading, _ in - completionHandler(itemProviderReading as? UIImage) - } - } else { - // extract the image's data representation - itemProvider.loadDataRepresentation(forTypeIdentifier: UTType.image.identifier) { data, _ in - guard let data else { - return completionHandler(nil) - } - completionHandler(UIImage(data: data)) - } - } - } - - guard let currentPickerResult = pickerResultArray.first else { return completionHandler(accumulating) } - - loadImage(from: currentPickerResult) { [weak self] loadedImage in - guard let self, let loadedImage else { return completionHandler(nil) } - var asset: PHAsset? - if let assetId = currentPickerResult.assetIdentifier { - asset = PHAsset.fetchAssets(withLocalIdentifiers: [assetId], options: nil).firstObject - } - let newElement = self.processedImage(from: loadedImage, with: asset?.imageData) - self.fetchProcessedImages( - from: Array(pickerResultArray.dropFirst()), - accumulating: accumulating + [newElement], - completionHandler - ) - } - } -} - -private extension CameraPlugin { - func returnImage(_ processedImage: ProcessedImage, isSaved: Bool) { - guard let jpeg = processedImage.generateJPEG(with: settings.jpegQuality) else { - self.call?.reject("Unable to convert image to jpeg") - return - } - - if settings.resultType == CameraResultType.uri || multiple { - guard let fileURL = try? saveTemporaryImage(jpeg), - let webURL = bridge?.portablePath(fromLocalURL: fileURL) else { - call?.reject("Unable to get portable path to file") - return - } - if self.multiple { - call?.resolve([ - "photos": [[ - "path": fileURL.absoluteString, - "exif": processedImage.exifData, - "webPath": webURL.absoluteString, - "format": "jpeg" - ]] - ]) - return - } - call?.resolve([ - "path": fileURL.absoluteString, - "exif": processedImage.exifData, - "webPath": webURL.absoluteString, - "format": "jpeg", - "saved": isSaved - ]) - } else if settings.resultType == CameraResultType.base64 { - self.call?.resolve([ - "base64String": jpeg.base64EncodedString(), - "exif": processedImage.exifData, - "format": "jpeg", - "saved": isSaved - ]) - } else if settings.resultType == CameraResultType.dataURL { - call?.resolve([ - "dataUrl": "data:image/jpeg;base64," + jpeg.base64EncodedString(), - "exif": processedImage.exifData, - "format": "jpeg", - "saved": isSaved - ]) - } - } - - func returnImages(_ processedImages: [ProcessedImage]) { - var photos: [PluginCallResultData] = [] - for processedImage in processedImages { - guard let jpeg = processedImage.generateJPEG(with: settings.jpegQuality) else { - self.call?.reject("Unable to convert image to jpeg") - return - } - - guard let fileURL = try? saveTemporaryImage(jpeg), - let webURL = bridge?.portablePath(fromLocalURL: fileURL) else { - call?.reject("Unable to get portable path to file") - return - } - - photos.append([ - "path": fileURL.absoluteString, - "exif": processedImage.exifData, - "webPath": webURL.absoluteString, - "format": "jpeg" - ]) - } - call?.resolve([ - "photos": photos - ]) - } - - func returnProcessedImage(_ processedImage: ProcessedImage) { - // conditionally save the image - if settings.saveToGallery && (processedImage.flags.contains(.edited) == true || processedImage.flags.contains(.gallery) == false) { - _ = ImageSaver(image: processedImage.image) { error in - var isSaved = false - if error == nil { - isSaved = true - } - self.returnImage(processedImage, isSaved: isSaved) - } - } else { - self.returnImage(processedImage, isSaved: false) - } - } - - func showPrompt() { - // Build the action sheet - let alert = UIAlertController(title: settings.userPromptText.title, message: nil, preferredStyle: UIAlertController.Style.actionSheet) - alert.addAction(UIAlertAction(title: settings.userPromptText.photoAction, style: .default, handler: { [weak self] (_: UIAlertAction) in - self?.showPhotos() - })) - - alert.addAction(UIAlertAction(title: settings.userPromptText.cameraAction, style: .default, handler: { [weak self] (_: UIAlertAction) in - self?.showCamera() - })) - - alert.addAction(UIAlertAction(title: settings.userPromptText.cancelAction, style: .cancel, handler: { [weak self] (_: UIAlertAction) in - self?.call?.reject("User cancelled photos app") - })) - self.setCenteredPopover(alert) - self.bridge?.viewController?.present(alert, animated: true, completion: nil) - } - - func showCamera() { - // check if we have a camera - if (bridge?.isSimEnvironment ?? false) || !UIImagePickerController.isSourceTypeAvailable(UIImagePickerController.SourceType.camera) { - CAPLog.print("⚡️ ", self.pluginId, "-", "Camera not available in simulator") - call?.reject("Camera not available while running in Simulator") - return - } - // check for permission - let authStatus = AVCaptureDevice.authorizationStatus(for: .video) - if authStatus == .restricted || authStatus == .denied { - call?.reject("User denied access to camera") - return - } - // we either already have permission or can prompt - AVCaptureDevice.requestAccess(for: .video) { [weak self] granted in - if granted { - DispatchQueue.main.async { - self?.presentCameraPicker() - } - } else { - self?.call?.reject("User denied access to camera") - } - } - } - - func showPhotos() { - // check for permission - let authStatus = PHPhotoLibrary.authorizationStatus() - if authStatus == .restricted || authStatus == .denied { - call?.reject("User denied access to photos") - return - } - // we either already have permission or can prompt - if authStatus == .authorized { - presentSystemAppropriateImagePicker() - } else { - PHPhotoLibrary.requestAuthorization({ [weak self] (status) in - if status == PHAuthorizationStatus.authorized { - DispatchQueue.main.async { [weak self] in - self?.presentSystemAppropriateImagePicker() - } - } else { - self?.call?.reject("User denied access to photos") - } - }) - } - } - - func presentCameraPicker() { - let picker = UIImagePickerController() - picker.delegate = self - picker.allowsEditing = self.settings.allowEditing - // select the input - picker.sourceType = .camera - if settings.direction == .rear, UIImagePickerController.isCameraDeviceAvailable(.rear) { - picker.cameraDevice = .rear - } else if settings.direction == .front, UIImagePickerController.isCameraDeviceAvailable(.front) { - picker.cameraDevice = .front - } - // present - picker.modalPresentationStyle = settings.presentationStyle - if settings.presentationStyle == .popover { - picker.popoverPresentationController?.delegate = self - setCenteredPopover(picker) - } - bridge?.viewController?.present(picker, animated: true, completion: nil) - } - - func presentSystemAppropriateImagePicker() { - presentPhotoPicker() - } - - func presentImagePicker() { - let picker = UIImagePickerController() - picker.delegate = self - picker.allowsEditing = self.settings.allowEditing - // select the input - picker.sourceType = .photoLibrary - // present - picker.modalPresentationStyle = settings.presentationStyle - if settings.presentationStyle == .popover { - picker.popoverPresentationController?.delegate = self - setCenteredPopover(picker) - } - bridge?.viewController?.present(picker, animated: true, completion: nil) - } - - func presentPhotoPicker() { - var configuration = PHPickerConfiguration(photoLibrary: PHPhotoLibrary.shared()) - configuration.selectionLimit = self.multiple ? (self.call?.getInt("limit") ?? 0) : 1 - configuration.filter = .images - let picker = PHPickerViewController(configuration: configuration) - picker.delegate = self - // present - picker.modalPresentationStyle = settings.presentationStyle - if settings.presentationStyle == .popover { - picker.popoverPresentationController?.delegate = self - setCenteredPopover(picker) - } - bridge?.viewController?.present(picker, animated: true, completion: nil) - } - - func saveTemporaryImage(_ data: Data) throws -> URL { - var url: URL - repeat { - imageCounter += 1 - url = URL(fileURLWithPath: NSTemporaryDirectory()).appendingPathComponent("photo-\(imageCounter).jpg") - } while FileManager.default.fileExists(atPath: url.path) - - try data.write(to: url, options: .atomic) - return url - } - - func processImage(from info: [UIImagePickerController.InfoKey: Any]) -> ProcessedImage? { - var selectedImage: UIImage? - var flags: PhotoFlags = [] - // get the image - if let edited = info[UIImagePickerController.InfoKey.editedImage] as? UIImage { - selectedImage = edited // use the edited version - flags = flags.union([.edited]) - } else if let original = info[UIImagePickerController.InfoKey.originalImage] as? UIImage { - selectedImage = original // use the original version - } - guard let image = selectedImage else { - return nil - } - var metadata: [String: Any] = [:] - // get the image's metadata from the picker or from the photo album - if let photoMetadata = info[UIImagePickerController.InfoKey.mediaMetadata] as? [String: Any] { - metadata = photoMetadata - } else { - flags = flags.union([.gallery]) - } - if let asset = info[UIImagePickerController.InfoKey.phAsset] as? PHAsset { - metadata = asset.imageData - } - // get the result - var result = processedImage(from: image, with: metadata) - result.flags = flags - return result - } - - func processedImage(from image: UIImage, with metadata: [String: Any]?) -> ProcessedImage { - var result = ProcessedImage(image: image, metadata: metadata ?? [:]) - // resizing the image only makes sense if we have real values to which to constrain it - if settings.shouldResize, settings.width > 0 || settings.height > 0 { - result.image = result.image.reformat(to: CGSize(width: settings.width, height: settings.height)) - result.overwriteMetadataOrientation(to: 1) - } else if settings.shouldCorrectOrientation { - // resizing implicitly reformats the image so this is only needed if we aren't resizing - result.image = result.image.reformat() - result.overwriteMetadataOrientation(to: 1) - } - return result - } -} diff --git a/camera/ios/Sources/CameraPlugin/CameraTypes.swift b/camera/ios/Sources/CameraPlugin/CameraTypes.swift deleted file mode 100644 index 382d216e5..000000000 --- a/camera/ios/Sources/CameraPlugin/CameraTypes.swift +++ /dev/null @@ -1,142 +0,0 @@ -import UIKit - -// MARK: - Public - -public enum CameraSource: String { - case prompt = "PROMPT" - case camera = "CAMERA" - case photos = "PHOTOS" -} - -public enum CameraDirection: String { - case rear = "REAR" - case front = "FRONT" -} - -public enum CameraResultType: String { - case base64 - case uri - case dataURL = "dataUrl" -} - -struct CameraPromptText { - let title: String - let photoAction: String - let cameraAction: String - let cancelAction: String - - init(title: String? = nil, photoAction: String? = nil, cameraAction: String? = nil, cancelAction: String? = nil) { - self.title = title ?? "Photo" - self.photoAction = photoAction ?? "From Photos" - self.cameraAction = cameraAction ?? "Take Picture" - self.cancelAction = cancelAction ?? "Cancel" - } -} - -public struct CameraSettings { - var source: CameraSource = CameraSource.prompt - var direction: CameraDirection = CameraDirection.rear - var resultType = CameraResultType.base64 - var userPromptText = CameraPromptText() - var jpegQuality: CGFloat = 1.0 - var width: CGFloat = 0 - var height: CGFloat = 0 - var allowEditing = false - var shouldResize = false - var shouldCorrectOrientation = true - var saveToGallery = false - var presentationStyle = UIModalPresentationStyle.fullScreen -} - -public struct CameraResult { - let image: UIImage? - let metadata: [AnyHashable: Any] -} - -// MARK: - Internal - -internal enum CameraPermissionType: String, CaseIterable { - case camera - case photos -} - -internal enum CameraPropertyListKeys: String, CaseIterable { - case photoLibraryAddUsage = "NSPhotoLibraryAddUsageDescription" - case photoLibraryUsage = "NSPhotoLibraryUsageDescription" - case cameraUsage = "NSCameraUsageDescription" - - var link: String { - switch self { - case .photoLibraryAddUsage: - return "https://developer.apple.com/library/content/documentation/General/Reference/InfoPlistKeyReference/Articles/CocoaKeys.html#//apple_ref/doc/uid/TP40009251-SW73" - case .photoLibraryUsage: - return "https://developer.apple.com/library/content/documentation/General/Reference/InfoPlistKeyReference/Articles/CocoaKeys.html#//apple_ref/doc/uid/TP40009251-SW17" - case .cameraUsage: - return "https://developer.apple.com/library/content/documentation/General/Reference/InfoPlistKeyReference/Articles/CocoaKeys.html#//apple_ref/doc/uid/TP40009251-SW24" - } - } - - var missingMessage: String { - return "You are missing \(self.rawValue) in your Info.plist file." + - " Camera will not function without it. Learn more: \(self.link)" - } -} - -internal struct PhotoFlags: OptionSet { - let rawValue: Int - - static let edited = PhotoFlags(rawValue: 1 << 0) - static let gallery = PhotoFlags(rawValue: 1 << 1) - - static let all: PhotoFlags = [.edited, .gallery] -} - -internal struct ProcessedImage { - var image: UIImage - var metadata: [String: Any] - var flags: PhotoFlags = [] - - var exifData: [String: Any] { - var exifData = metadata["{Exif}"] as? [String: Any] - exifData?["Orientation"] = metadata["Orientation"] - exifData?["GPS"] = metadata["{GPS}"] - return exifData ?? [:] - } - - mutating func overwriteMetadataOrientation(to orientation: Int) { - replaceDictionaryOrientation(atNode: &metadata, to: orientation) - } - - func replaceDictionaryOrientation(atNode node: inout [String: Any], to orientation: Int) { - for key in node.keys { - if key == "Orientation", (node[key] as? Int) != nil { - node[key] = orientation - } else if var child = node[key] as? [String: Any] { - replaceDictionaryOrientation(atNode: &child, to: orientation) - node[key] = child - } - } - } - - func generateJPEG(with quality: CGFloat) -> Data? { - // convert the UIImage to a jpeg - guard let data = self.image.jpegData(compressionQuality: quality) else { - return nil - } - // define our jpeg data as an image source and get its type - guard let source = CGImageSourceCreateWithData(data as CFData, nil), let type = CGImageSourceGetType(source) else { - return data - } - // allocate an output buffer and create the destination to receive the new data - guard let output = NSMutableData(capacity: data.count), let destination = CGImageDestinationCreateWithData(output, type, 1, nil) else { - return data - } - // pipe the source into the destination while overwriting the metadata, this encodes the metadata information into the image - CGImageDestinationAddImageFromSource(destination, source, 0, self.metadata as CFDictionary) - // finish - guard CGImageDestinationFinalize(destination) else { - return data - } - return output as Data - } -} diff --git a/camera/ios/Sources/CameraPlugin/ImageSaver.swift b/camera/ios/Sources/CameraPlugin/ImageSaver.swift deleted file mode 100644 index a811d889f..000000000 --- a/camera/ios/Sources/CameraPlugin/ImageSaver.swift +++ /dev/null @@ -1,20 +0,0 @@ -import UIKit - -class ImageSaver: NSObject { - - var onResult: ((Error?) -> Void) = {_ in } - - init(image: UIImage, onResult: @escaping ((Error?) -> Void)) { - self.onResult = onResult - super.init() - UIImageWriteToSavedPhotosAlbum(image, self, #selector(saveResult), nil) - } - - @objc func saveResult(_ image: UIImage, didFinishSavingWithError error: Error?, contextInfo: UnsafeRawPointer) { - if let error = error { - onResult(error) - } else { - onResult(nil) - } - } -} diff --git a/camera/ios/Tests/CameraPluginTests/CameraPluginTests.swift b/camera/ios/Tests/CameraPluginTests/CameraPluginTests.swift deleted file mode 100644 index abb98193e..000000000 --- a/camera/ios/Tests/CameraPluginTests/CameraPluginTests.swift +++ /dev/null @@ -1,12 +0,0 @@ -import XCTest -@testable import CameraPlugin - -final class CameraPluginTests: XCTestCase { - func testExample() throws { - // XCTest Documentation - // https://developer.apple.com/documentation/xctest - - // Defining Test Cases and Test Methods - // https://developer.apple.com/documentation/xctest/defining_test_cases_and_test_methods - } -} diff --git a/camera/package.json b/camera/package.json deleted file mode 100644 index 33ec4851a..000000000 --- a/camera/package.json +++ /dev/null @@ -1,83 +0,0 @@ -{ - "name": "@capacitor/camera", - "version": "8.0.2", - "description": "The Camera API provides the ability to take a photo with the camera or choose an existing one from the photo album.", - "main": "dist/plugin.cjs.js", - "module": "dist/esm/index.js", - "types": "dist/esm/index.d.ts", - "unpkg": "dist/plugin.js", - "files": [ - "android/src/main/", - "android/build.gradle", - "dist/", - "ios/Sources", - "ios/Tests", - "Package.swift", - "CapacitorCamera.podspec" - ], - "author": "Ionic ", - "license": "MIT", - "repository": { - "type": "git", - "url": "git+https://github.com/ionic-team/capacitor-plugins.git" - }, - "bugs": { - "url": "https://github.com/ionic-team/capacitor-plugins/issues" - }, - "keywords": [ - "capacitor", - "plugin", - "native" - ], - "scripts": { - "verify": "npm run verify:ios && npm run verify:android && npm run verify:web", - "verify:ios": "xcodebuild build -scheme CapacitorCamera -destination generic/platform=iOS", - "verify:android": "cd android && ./gradlew clean build test && cd ..", - "verify:web": "npm run build", - "lint": "npm run eslint && npm run prettier -- --check && npm run swiftlint -- lint", - "fmt": "npm run eslint -- --fix && npm run prettier -- --write && npm run swiftlint -- --fix --format", - "eslint": "eslint . --ext ts", - "prettier": "prettier \"**/*.{css,html,ts,js,java}\" --plugin=prettier-plugin-java", - "swiftlint": "node-swiftlint", - "docgen": "docgen --api CameraPlugin --output-readme README.md --output-json dist/docs.json", - "build": "npm run clean && npm run docgen && tsc && rollup -c rollup.config.mjs", - "clean": "rimraf ./dist", - "watch": "tsc --watch", - "prepublishOnly": "npm run build" - }, - "devDependencies": { - "@capacitor/android": "next", - "@capacitor/core": "next", - "@capacitor/docgen": "0.3.0", - "@capacitor/ios": "next", - "@ionic/eslint-config": "^0.4.0", - "@ionic/prettier-config": "^4.0.0", - "@ionic/swiftlint-config": "^2.0.0", - "eslint": "^8.57.1", - "prettier": "^3.6.2", - "prettier-plugin-java": "^2.7.7", - "rimraf": "^6.1.0", - "rollup": "^4.53.2", - "swiftlint": "^2.0.0", - "typescript": "^5.9.3" - }, - "peerDependencies": { - "@capacitor/core": ">=9.0.0-alpha.0" - }, - "prettier": "@ionic/prettier-config", - "swiftlint": "@ionic/swiftlint-config", - "eslintConfig": { - "extends": "@ionic/eslint-config/recommended" - }, - "capacitor": { - "ios": { - "src": "ios" - }, - "android": { - "src": "android" - } - }, - "publishConfig": { - "access": "public" - } -} diff --git a/camera/rollup.config.mjs b/camera/rollup.config.mjs deleted file mode 100644 index 0904b0b71..000000000 --- a/camera/rollup.config.mjs +++ /dev/null @@ -1,22 +0,0 @@ -export default { - input: 'dist/esm/index.js', - output: [ - { - file: 'dist/plugin.js', - format: 'iife', - name: 'capacitorCamera', - globals: { - '@capacitor/core': 'capacitorExports', - }, - sourcemap: true, - inlineDynamicImports: true, - }, - { - file: 'dist/plugin.cjs.js', - format: 'cjs', - sourcemap: true, - inlineDynamicImports: true, - }, - ], - external: ['@capacitor/core'], -}; diff --git a/camera/src/definitions.ts b/camera/src/definitions.ts deleted file mode 100644 index dd93b70b9..000000000 --- a/camera/src/definitions.ts +++ /dev/null @@ -1,362 +0,0 @@ -import type { PermissionState } from '@capacitor/core'; - -export type CameraPermissionState = PermissionState | 'limited'; - -export type CameraPermissionType = 'camera' | 'photos'; - -export interface PermissionStatus { - camera: CameraPermissionState; - photos: CameraPermissionState; -} - -export interface CameraPluginPermissions { - permissions: CameraPermissionType[]; -} - -export interface CameraPlugin { - /** - * Prompt the user to pick a photo from an album, or take a new photo - * with the camera. - * - * @since 1.0.0 - */ - getPhoto(options: ImageOptions): Promise; - - /** - * Allows the user to pick multiple pictures from the photo gallery. - * - * @since 1.2.0 - */ - pickImages(options: GalleryImageOptions): Promise; - - /** - * Allows the user to update their limited photo library selection. - * Returns all the limited photos after the picker dismissal. - * If instead the user gave full access to the photos it returns an empty array. - * - * @since 4.1.0 - */ - pickLimitedLibraryPhotos(): Promise; - /** - * Return an array of photos selected from the limited photo library. - * - * @since 4.1.0 - */ - getLimitedLibraryPhotos(): Promise; - - /** - * Check camera and photo album permissions - * - * @since 1.0.0 - */ - checkPermissions(): Promise; - - /** - * Request camera and photo album permissions - * - * @since 1.0.0 - */ - requestPermissions(permissions?: CameraPluginPermissions): Promise; -} - -export interface ImageOptions { - /** - * The quality of image to return as JPEG, from 0-100 - * Note: This option is only supported on Android and iOS - * - * @since 1.0.0 - */ - quality?: number; - /** - * Whether to allow the user to crop or make small edits (platform specific). - * On iOS it's only supported for CameraSource.Camera, but not for CameraSource.Photos. - * - * @since 1.0.0 - */ - allowEditing?: boolean; - /** - * How the data should be returned. Currently, only 'Base64', 'DataUrl' or 'Uri' is supported - * - * @since 1.0.0 - */ - resultType: CameraResultType; - /** - * Whether to save the photo to the gallery. - * If the photo was picked from the gallery, it will only be saved if edited. - * @default: false - * - * @since 1.0.0 - */ - saveToGallery?: boolean; - /** - * The desired maximum width of the saved image. The aspect ratio is respected. - * - * @since 1.0.0 - */ - width?: number; - /** - * The desired maximum height of the saved image. The aspect ratio is respected. - * - * @since 1.0.0 - */ - height?: number; - /** - * Whether to automatically rotate the image "up" to correct for orientation - * in portrait mode - * @default: true - * - * @since 1.0.0 - */ - correctOrientation?: boolean; - /** - * The source to get the photo from. By default this prompts the user to select - * either the photo album or take a photo. - * @default: CameraSource.Prompt - * - * @since 1.0.0 - */ - source?: CameraSource; - /** - * iOS and Web only: The camera direction. - * @default: CameraDirection.Rear - * - * @since 1.0.0 - */ - direction?: CameraDirection; - - /** - * iOS only: The presentation style of the Camera. - * @default: 'fullscreen' - * - * @since 1.0.0 - */ - presentationStyle?: 'fullscreen' | 'popover'; - - /** - * Web only: Whether to use the PWA Element experience or file input. The - * default is to use PWA Elements if installed and fall back to file input. - * To always use file input, set this to `true`. - * - * Learn more about PWA Elements: https://capacitorjs.com/docs/web/pwa-elements - * - * @since 1.0.0 - */ - webUseInput?: boolean; - - /** - * Text value to use when displaying the prompt. - * @default: 'Photo' - * - * @since 1.0.0 - * - */ - promptLabelHeader?: string; - - /** - * Text value to use when displaying the prompt. - * iOS only: The label of the 'cancel' button. - * @default: 'Cancel' - * - * @since 1.0.0 - */ - promptLabelCancel?: string; - - /** - * Text value to use when displaying the prompt. - * The label of the button to select a saved image. - * @default: 'From Photos' - * - * @since 1.0.0 - */ - promptLabelPhoto?: string; - - /** - * Text value to use when displaying the prompt. - * The label of the button to open the camera. - * @default: 'Take Picture' - * - * @since 1.0.0 - */ - promptLabelPicture?: string; -} - -export interface Photo { - /** - * The base64 encoded string representation of the image, if using CameraResultType.Base64. - * - * @since 1.0.0 - */ - base64String?: string; - /** - * The url starting with 'data:image/jpeg;base64,' and the base64 encoded string representation of the image, if using CameraResultType.DataUrl. - * - * Note: On web, the file format could change depending on the browser. - * @since 1.0.0 - */ - dataUrl?: string; - /** - * If using CameraResultType.Uri, the path will contain a full, - * platform-specific file URL that can be read later using the Filesystem API. - * - * @since 1.0.0 - */ - path?: string; - /** - * webPath returns a path that can be used to set the src attribute of an image for efficient - * loading and rendering. - * - * @since 1.0.0 - */ - webPath?: string; - /** - * Exif data, if any, retrieved from the image - * - * @since 1.0.0 - */ - exif?: any; - /** - * The format of the image, ex: jpeg, png, gif. - * - * iOS and Android only support jpeg. - * Web supports jpeg, png and gif, but the exact availability may vary depending on the browser. - * gif is only supported if `webUseInput` is set to `true` or if `source` is set to `Photos`. - * - * @since 1.0.0 - */ - format: string; - /** - * Whether if the image was saved to the gallery or not. - * - * On Android and iOS, saving to the gallery can fail if the user didn't - * grant the required permissions. - * On Web there is no gallery, so always returns false. - * - * @since 1.1.0 - */ - saved: boolean; -} - -export interface GalleryPhotos { - /** - * Array of all the picked photos. - * - * @since 1.2.0 - */ - photos: GalleryPhoto[]; -} - -export interface GalleryPhoto { - /** - * Full, platform-specific file URL that can be read later using the Filesystem API. - * - * @since 1.2.0 - */ - path?: string; - /** - * webPath returns a path that can be used to set the src attribute of an image for efficient - * loading and rendering. - * - * @since 1.2.0 - */ - webPath: string; - /** - * Exif data, if any, retrieved from the image - * - * @since 1.2.0 - */ - exif?: any; - /** - * The format of the image, ex: jpeg, png, gif. - * - * iOS and Android only support jpeg. - * Web supports jpeg, png and gif. - * - * @since 1.2.0 - */ - format: string; -} -export interface GalleryImageOptions { - /** - * The quality of image to return as JPEG, from 0-100 - * Note: This option is only supported on Android and iOS. - * - * @since 1.2.0 - */ - quality?: number; - /** - * The desired maximum width of the saved image. The aspect ratio is respected. - * - * @since 1.2.0 - */ - width?: number; - /** - * The desired maximum height of the saved image. The aspect ratio is respected. - * - * @since 1.2.0 - */ - height?: number; - /** - * Whether to automatically rotate the image "up" to correct for orientation - * in portrait mode - * @default: true - * - * @since 1.2.0 - */ - correctOrientation?: boolean; - - /** - * iOS only: The presentation style of the Camera. - * @default: 'fullscreen' - * - * @since 1.2.0 - */ - presentationStyle?: 'fullscreen' | 'popover'; - - /** - * Maximum number of pictures the user will be able to choose. - * Note: This option is only supported on Android 13+ and iOS. - * - * @default 0 (unlimited) - * - * @since 1.2.0 - */ - limit?: number; -} - -export enum CameraSource { - /** - * Prompts the user to select either the photo album or take a photo. - */ - Prompt = 'PROMPT', - /** - * Take a new photo using the camera. - */ - Camera = 'CAMERA', - /** - * Pick an existing photo from the gallery or photo album. - */ - Photos = 'PHOTOS', -} - -export enum CameraDirection { - Rear = 'REAR', - Front = 'FRONT', -} - -export enum CameraResultType { - Uri = 'uri', - Base64 = 'base64', - DataUrl = 'dataUrl', -} - -/** - * @deprecated Use `Photo`. - * @since 1.0.0 - */ -export type CameraPhoto = Photo; - -/** - * @deprecated Use `ImageOptions`. - * @since 1.0.0 - */ -export type CameraOptions = ImageOptions; diff --git a/camera/src/index.ts b/camera/src/index.ts deleted file mode 100644 index 1eaeeb464..000000000 --- a/camera/src/index.ts +++ /dev/null @@ -1,11 +0,0 @@ -import { registerPlugin } from '@capacitor/core'; - -import type { CameraPlugin } from './definitions'; -import { CameraWeb } from './web'; - -const Camera = registerPlugin('Camera', { - web: () => new CameraWeb(), -}); - -export * from './definitions'; -export { Camera }; diff --git a/camera/src/web.ts b/camera/src/web.ts deleted file mode 100644 index 3d0ec934b..000000000 --- a/camera/src/web.ts +++ /dev/null @@ -1,277 +0,0 @@ -import { WebPlugin, CapacitorException } from '@capacitor/core'; - -import { CameraSource, CameraDirection } from './definitions'; -import type { - CameraPlugin, - GalleryImageOptions, - GalleryPhotos, - ImageOptions, - PermissionStatus, - Photo, -} from './definitions'; - -export class CameraWeb extends WebPlugin implements CameraPlugin { - async getPhoto(options: ImageOptions): Promise { - // eslint-disable-next-line no-async-promise-executor - return new Promise(async (resolve, reject) => { - if (options.webUseInput || options.source === CameraSource.Photos) { - this.fileInputExperience(options, resolve, reject); - } else if (options.source === CameraSource.Prompt) { - let actionSheet: any = document.querySelector('pwa-action-sheet'); - if (!actionSheet) { - actionSheet = document.createElement('pwa-action-sheet'); - document.body.appendChild(actionSheet); - } - actionSheet.header = options.promptLabelHeader || 'Photo'; - actionSheet.cancelable = true; - actionSheet.options = [ - { title: options.promptLabelPhoto || 'From Photos' }, - { title: options.promptLabelPicture || 'Take Picture' }, - ]; - actionSheet.addEventListener('onSelection', async (e: any) => { - const selection = e.detail; - if (selection === 0) { - this.fileInputExperience(options, resolve, reject); - } else { - this.cameraExperience(options, resolve, reject); - } - }); - actionSheet.addEventListener('onCanceled', async () => { - reject(new CapacitorException('User cancelled photos app')); - }); - } else { - this.cameraExperience(options, resolve, reject); - } - }); - } - - async pickImages(_options: GalleryImageOptions): Promise { - // eslint-disable-next-line no-async-promise-executor - return new Promise(async (resolve, reject) => { - this.multipleFileInputExperience(resolve, reject); - }); - } - - private async cameraExperience(options: ImageOptions, resolve: any, reject: any) { - if (customElements.get('pwa-camera-modal')) { - const cameraModal: any = document.createElement('pwa-camera-modal'); - cameraModal.facingMode = options.direction === CameraDirection.Front ? 'user' : 'environment'; - document.body.appendChild(cameraModal); - try { - await cameraModal.componentOnReady(); - cameraModal.addEventListener('onPhoto', async (e: any) => { - const photo = e.detail; - - if (photo === null) { - reject(new CapacitorException('User cancelled photos app')); - } else if (photo instanceof Error) { - reject(photo); - } else { - resolve(await this._getCameraPhoto(photo, options)); - } - - cameraModal.dismiss(); - document.body.removeChild(cameraModal); - }); - - cameraModal.present(); - } catch (e) { - this.fileInputExperience(options, resolve, reject); - } - } else { - console.error( - `Unable to load PWA Element 'pwa-camera-modal'. See the docs: https://capacitorjs.com/docs/web/pwa-elements.`, - ); - this.fileInputExperience(options, resolve, reject); - } - } - - private fileInputExperience(options: ImageOptions, resolve: any, reject: any) { - let input = document.querySelector('#_capacitor-camera-input') as HTMLInputElement; - - const cleanup = () => { - input.parentNode?.removeChild(input); - }; - - if (!input) { - input = document.createElement('input') as HTMLInputElement; - input.id = '_capacitor-camera-input'; - input.type = 'file'; - input.hidden = true; - document.body.appendChild(input); - input.addEventListener('change', (_e: any) => { - const file = input.files![0]; - let format = 'jpeg'; - - if (file.type === 'image/png') { - format = 'png'; - } else if (file.type === 'image/gif') { - format = 'gif'; - } - - if (options.resultType === 'dataUrl' || options.resultType === 'base64') { - const reader = new FileReader(); - - reader.addEventListener('load', () => { - if (options.resultType === 'dataUrl') { - resolve({ - dataUrl: reader.result, - format, - } as Photo); - } else if (options.resultType === 'base64') { - const b64 = (reader.result as string).split(',')[1]; - resolve({ - base64String: b64, - format, - } as Photo); - } - - cleanup(); - }); - - reader.readAsDataURL(file); - } else { - resolve({ - webPath: URL.createObjectURL(file), - format: format, - }); - cleanup(); - } - }); - input.addEventListener('cancel', (_e: any) => { - reject(new CapacitorException('User cancelled photos app')); - cleanup(); - }); - } - - input.accept = 'image/*'; - (input as any).capture = true; - - if (options.source === CameraSource.Photos || options.source === CameraSource.Prompt) { - input.removeAttribute('capture'); - } else if (options.direction === CameraDirection.Front) { - (input as any).capture = 'user'; - } else if (options.direction === CameraDirection.Rear) { - (input as any).capture = 'environment'; - } - - input.click(); - } - - private multipleFileInputExperience(resolve: any, reject: any) { - let input = document.querySelector('#_capacitor-camera-input-multiple') as HTMLInputElement; - - const cleanup = () => { - input.parentNode?.removeChild(input); - }; - - if (!input) { - input = document.createElement('input') as HTMLInputElement; - input.id = '_capacitor-camera-input-multiple'; - input.type = 'file'; - input.hidden = true; - input.multiple = true; - document.body.appendChild(input); - input.addEventListener('change', (_e: any) => { - const photos = []; - // eslint-disable-next-line @typescript-eslint/prefer-for-of - for (let i = 0; i < input.files!.length; i++) { - const file = input.files![i]; - let format = 'jpeg'; - - if (file.type === 'image/png') { - format = 'png'; - } else if (file.type === 'image/gif') { - format = 'gif'; - } - photos.push({ - webPath: URL.createObjectURL(file), - format: format, - }); - } - resolve({ photos }); - cleanup(); - }); - input.addEventListener('cancel', (_e: any) => { - reject(new CapacitorException('User cancelled photos app')); - cleanup(); - }); - } - - input.accept = 'image/*'; - - input.click(); - } - - private _getCameraPhoto(photo: Blob, options: ImageOptions) { - return new Promise((resolve, reject) => { - const reader = new FileReader(); - const format = photo.type.split('/')[1]; - if (options.resultType === 'uri') { - resolve({ - webPath: URL.createObjectURL(photo), - format: format, - saved: false, - }); - } else { - reader.readAsDataURL(photo); - reader.onloadend = () => { - const r = reader.result as string; - if (options.resultType === 'dataUrl') { - resolve({ - dataUrl: r, - format: format, - saved: false, - }); - } else { - resolve({ - base64String: r.split(',')[1], - format: format, - saved: false, - }); - } - }; - reader.onerror = (e) => { - reject(e); - }; - } - }); - } - - async checkPermissions(): Promise { - if (typeof navigator === 'undefined' || !navigator.permissions) { - throw this.unavailable('Permissions API not available in this browser'); - } - - try { - // https://developer.mozilla.org/en-US/docs/Web/API/Permissions/query - // the specific permissions that are supported varies among browsers that implement the - // permissions API, so we need a try/catch in case 'camera' is invalid - const permission = await window.navigator.permissions.query({ - name: 'camera', - }); - return { - camera: permission.state, - photos: 'granted', - }; - } catch { - throw this.unavailable('Camera permissions are not available in this browser'); - } - } - - async requestPermissions(): Promise { - throw this.unimplemented('Not implemented on web.'); - } - - async pickLimitedLibraryPhotos(): Promise { - throw this.unavailable('Not implemented on web.'); - } - - async getLimitedLibraryPhotos(): Promise { - throw this.unavailable('Not implemented on web.'); - } -} - -const Camera = new CameraWeb(); - -export { Camera }; diff --git a/camera/tsconfig.json b/camera/tsconfig.json deleted file mode 100644 index f2e88e6a0..000000000 --- a/camera/tsconfig.json +++ /dev/null @@ -1,20 +0,0 @@ -{ - "compilerOptions": { - "allowUnreachableCode": false, - "declaration": true, - "esModuleInterop": true, - "inlineSources": true, - "lib": ["dom", "es2017"], - "module": "esnext", - "moduleResolution": "node", - "noFallthroughCasesInSwitch": true, - "noUnusedLocals": true, - "noUnusedParameters": true, - "outDir": "dist/esm", - "pretty": true, - "sourceMap": true, - "strict": true, - "target": "es2017" - }, - "files": ["src/index.ts"] -}