diff --git a/.github/workflows/bot.yml b/.github/workflows/bot.yml index 96be7328cf..87d895ff9b 100644 --- a/.github/workflows/bot.yml +++ b/.github/workflows/bot.yml @@ -13,7 +13,7 @@ jobs: name: ${{ github.event_name }}/${{ github.event.action }} runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v5 - uses: ionic-team/bot@main with: repo-token: ${{ secrets.BOT_TOKEN }} diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 3980bf3d18..7e8a684aa0 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -22,13 +22,13 @@ jobs: with: access_token: ${{ secrets.GITHUB_TOKEN }} - name: Get Latest - uses: actions/setup-node@v4 + uses: actions/setup-node@v6 with: - node-version: 20 - - uses: actions/checkout@v3 + node-version: 22 + - uses: actions/checkout@v5 - name: Restore Dependency Cache id: cache-modules - uses: actions/cache@v3 + uses: actions/cache@v4 with: path: | node_modules @@ -54,13 +54,13 @@ jobs: needs: - setup steps: - - uses: actions/setup-node@v4 + - uses: actions/setup-node@v6 with: - node-version: 20 - - uses: actions/checkout@v3 + node-version: 22 + - uses: actions/checkout@v5 - name: Restore Dependency Cache id: cache-modules - uses: actions/cache@v3 + uses: actions/cache@v4 with: path: | node_modules @@ -80,13 +80,13 @@ jobs: matrix: plugin: ${{ fromJson(needs.setup.outputs.plugins) }} steps: - - uses: actions/setup-node@v4 + - uses: actions/setup-node@v6 with: - node-version: 20 - - uses: actions/checkout@v3 + node-version: 22 + - uses: actions/checkout@v5 - name: Restore Dependency Cache id: cache-modules - uses: actions/cache@v3 + uses: actions/cache@v4 with: path: | node_modules @@ -111,13 +111,15 @@ jobs: plugin: ${{ fromJson(needs.setup.outputs.plugins) }} steps: - run: sudo xcode-select --switch ${{ matrix.xcode }} - - uses: actions/setup-node@v4 + - run: xcrun simctl list > /dev/null + - run: xcodebuild -downloadPlatform iOS + - uses: actions/setup-node@v6 with: - node-version: 20 - - uses: actions/checkout@v3 + node-version: 22 + - uses: actions/checkout@v5 - name: Restore Dependency Cache id: cache-modules - uses: actions/cache@v3 + uses: actions/cache@v4 with: path: | node_modules @@ -139,18 +141,18 @@ jobs: matrix: plugin: ${{ fromJson(needs.setup.outputs.plugins) }} steps: - - uses: actions/setup-node@v4 + - uses: actions/setup-node@v6 with: - node-version: 20 + node-version: 22 - name: set up JDK 21 - uses: actions/setup-java@v4 + uses: actions/setup-java@v5 with: java-version: '21' distribution: 'zulu' - - uses: actions/checkout@v3 + - uses: actions/checkout@v5 - name: Restore Dependency Cache id: cache-modules - uses: actions/cache@v3 + uses: actions/cache@v4 with: path: | node_modules @@ -173,13 +175,13 @@ jobs: matrix: plugin: ${{ fromJson(needs.setup.outputs.plugins) }} steps: - - uses: actions/setup-node@v4 + - uses: actions/setup-node@v6 with: - node-version: 20 - - uses: actions/checkout@v3 + node-version: 22 + - uses: actions/checkout@v5 - name: Restore Dependency Cache id: cache-modules - uses: actions/cache@v3 + uses: actions/cache@v4 with: path: | node_modules diff --git a/.github/workflows/dev-releases-for-pr.yml b/.github/workflows/dev-releases-for-pr.yml index 262206104b..12c52e1500 100644 --- a/.github/workflows/dev-releases-for-pr.yml +++ b/.github/workflows/dev-releases-for-pr.yml @@ -23,13 +23,13 @@ jobs: with: access_token: ${{ secrets.GITHUB_TOKEN }} - name: Get Latest - uses: actions/setup-node@v4 + uses: actions/setup-node@v6 with: - node-version: 20 - - uses: actions/checkout@v3 + node-version: 22 + - uses: actions/checkout@v5 - name: Restore Dependency Cache id: cache-modules - uses: actions/cache@v3 + uses: actions/cache@v4 with: path: | node_modules @@ -61,13 +61,13 @@ jobs: matrix: plugin: ${{ fromJson(needs.setup.outputs.plugins) }} steps: - - uses: actions/setup-node@v4 + - uses: actions/setup-node@v6 with: - node-version: 20 - - uses: actions/checkout@v3 + node-version: 22 + - uses: actions/checkout@v5 - name: Restore Dependency Cache id: cache-modules - uses: actions/cache@v3 + uses: actions/cache@v4 with: path: | node_modules diff --git a/.github/workflows/publish-android.yml b/.github/workflows/publish-android.yml index 4bd89eb30c..615fe91c43 100644 --- a/.github/workflows/publish-android.yml +++ b/.github/workflows/publish-android.yml @@ -46,12 +46,12 @@ jobs: contents: read packages: write steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v5 with: fetch-depth: 0 token: ${{ secrets.CAP_GH_RELEASE_TOKEN }} - name: set up JDK 21 - uses: actions/setup-java@v4 + uses: actions/setup-java@v5 with: java-version: '21' distribution: 'zulu' diff --git a/.github/workflows/publish-ios.yml b/.github/workflows/publish-ios.yml index 8506fb313c..a2aac234f6 100644 --- a/.github/workflows/publish-ios.yml +++ b/.github/workflows/publish-ios.yml @@ -16,10 +16,12 @@ jobs: plugin: ${{ fromJson(github.event.inputs.plugins) }} steps: - run: sudo xcode-select --switch /Applications/Xcode_16.app - - uses: actions/setup-node@v4 + - run: xcrun simctl list > /dev/null + - run: xcodebuild -downloadPlatform iOS + - uses: actions/setup-node@v6 with: - node-version: 20 - - uses: actions/checkout@v3 + node-version: 22 + - uses: actions/checkout@v5 - name: Install Cocoapods run: | gem install cocoapods diff --git a/.github/workflows/publish-npm-alpha.yml b/.github/workflows/publish-npm-alpha.yml index 96ea717c58..d5a5e10125 100644 --- a/.github/workflows/publish-npm-alpha.yml +++ b/.github/workflows/publish-npm-alpha.yml @@ -11,13 +11,13 @@ jobs: runs-on: macos-15 timeout-minutes: 30 steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v5 with: fetch-depth: 0 token: ${{ secrets.CAP_GH_RELEASE_TOKEN }} - - uses: actions/setup-node@v4 + - uses: actions/setup-node@v6 with: - node-version: 20 + node-version: 22 registry-url: https://registry.npmjs.org/ cache: npm cache-dependency-path: '**/package.json' diff --git a/.github/workflows/publish-npm-beta.yml b/.github/workflows/publish-npm-beta.yml index 072dd1e43e..29a075ef50 100644 --- a/.github/workflows/publish-npm-beta.yml +++ b/.github/workflows/publish-npm-beta.yml @@ -11,13 +11,13 @@ jobs: runs-on: macos-15 timeout-minutes: 30 steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v5 with: fetch-depth: 0 token: ${{ secrets.CAP_GH_RELEASE_TOKEN }} - - uses: actions/setup-node@v4 + - uses: actions/setup-node@v6 with: - node-version: 20 + node-version: 22 registry-url: https://registry.npmjs.org/ cache: npm cache-dependency-path: '**/package.json' diff --git a/.github/workflows/publish-npm-dev.yml b/.github/workflows/publish-npm-dev.yml index ea395eda55..4763797194 100644 --- a/.github/workflows/publish-npm-dev.yml +++ b/.github/workflows/publish-npm-dev.yml @@ -11,13 +11,13 @@ jobs: runs-on: macos-15 timeout-minutes: 30 steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v5 with: fetch-depth: 0 token: ${{ secrets.CAP_GH_RELEASE_TOKEN }} - - uses: actions/setup-node@v4 + - uses: actions/setup-node@v6 with: - node-version: 20 + node-version: 22 registry-url: https://registry.npmjs.org/ cache: npm cache-dependency-path: '**/package.json' diff --git a/.github/workflows/publish-npm-latest-from-pre.yml b/.github/workflows/publish-npm-latest-from-pre.yml index cb1a2f080f..40774817a4 100644 --- a/.github/workflows/publish-npm-latest-from-pre.yml +++ b/.github/workflows/publish-npm-latest-from-pre.yml @@ -19,13 +19,13 @@ jobs: runs-on: macos-15 timeout-minutes: 30 steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v5 with: fetch-depth: 0 token: ${{ secrets.CAP_GH_RELEASE_TOKEN }} - - uses: actions/setup-node@v4 + - uses: actions/setup-node@v6 with: - node-version: 20 + node-version: 22 registry-url: https://registry.npmjs.org/ cache: npm cache-dependency-path: '**/package.json' diff --git a/.github/workflows/publish-npm-latest.yml b/.github/workflows/publish-npm-latest.yml index 504281bcd1..ee73c67773 100644 --- a/.github/workflows/publish-npm-latest.yml +++ b/.github/workflows/publish-npm-latest.yml @@ -19,13 +19,13 @@ jobs: runs-on: macos-15 timeout-minutes: 30 steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v5 with: fetch-depth: 0 token: ${{ secrets.CAP_GH_RELEASE_TOKEN }} - - uses: actions/setup-node@v4 + - uses: actions/setup-node@v6 with: - node-version: 20 + node-version: 22 registry-url: https://registry.npmjs.org/ cache: npm cache-dependency-path: '**/package.json' diff --git a/.github/workflows/publish-npm-nightly.yml b/.github/workflows/publish-npm-nightly.yml index 39b6bfae62..a1f5bc7ba7 100644 --- a/.github/workflows/publish-npm-nightly.yml +++ b/.github/workflows/publish-npm-nightly.yml @@ -14,13 +14,13 @@ jobs: runs-on: macos-15 timeout-minutes: 30 steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v5 with: fetch-depth: 0 token: ${{ secrets.CAP_GH_RELEASE_TOKEN }} - - uses: actions/setup-node@v4 + - uses: actions/setup-node@v6 with: - node-version: 20 + node-version: 22 registry-url: https://registry.npmjs.org/ cache: npm cache-dependency-path: '**/package.json' diff --git a/.github/workflows/publish-npm-rc.yml b/.github/workflows/publish-npm-rc.yml index cb9f71ebff..9dae59b55d 100644 --- a/.github/workflows/publish-npm-rc.yml +++ b/.github/workflows/publish-npm-rc.yml @@ -11,13 +11,13 @@ jobs: runs-on: macos-15 timeout-minutes: 30 steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v5 with: fetch-depth: 0 token: ${{ secrets.CAP_GH_RELEASE_TOKEN }} - - uses: actions/setup-node@v4 + - uses: actions/setup-node@v6 with: - node-version: 20 + node-version: 22 registry-url: https://registry.npmjs.org/ cache: npm cache-dependency-path: '**/package.json' diff --git a/README.md b/README.md index 8097fba9ad..be167704a8 100644 --- a/README.md +++ b/README.md @@ -23,8 +23,6 @@ This repository contains the official Capacitor plugins maintained by the Capaci | [`@capacitor/clipboard`](https://capacitorjs.com/docs/apis/clipboard) | [`./clipboard`](./clipboard) | [![npm badge](https://img.shields.io/npm/v/@capacitor/clipboard?style=flat-square)](https://www.npmjs.com/package/@capacitor/clipboard) | [`@capacitor/device`](https://capacitorjs.com/docs/apis/device) | [`./device`](./device) | [![npm badge](https://img.shields.io/npm/v/@capacitor/device?style=flat-square)](https://www.npmjs.com/package/@capacitor/device) | [`@capacitor/dialog`](https://capacitorjs.com/docs/apis/dialog) | [`./dialog`](./dialog) | [![npm badge](https://img.shields.io/npm/v/@capacitor/dialog?style=flat-square)](https://www.npmjs.com/package/@capacitor/dialog) -| [`@capacitor/haptics`](https://capacitorjs.com/docs/apis/haptics) | [`./haptics`](./haptics) | [![npm badge](https://img.shields.io/npm/v/@capacitor/haptics?style=flat-square)](https://www.npmjs.com/package/@capacitor/haptics) -| [`@capacitor/keyboard`](https://capacitorjs.com/docs/apis/keyboard) | [`./keyboard`](./keyboard) | [![npm badge](https://img.shields.io/npm/v/@capacitor/keyboard?style=flat-square)](https://www.npmjs.com/package/@capacitor/keyboard) | [`@capacitor/local-notifications`](https://capacitorjs.com/docs/apis/local-notifications) | [`./local-notifications`](./local-notifications) | [![npm badge](https://img.shields.io/npm/v/@capacitor/local-notifications?style=flat-square)](https://www.npmjs.com/package/@capacitor/local-notifications) | [`@capacitor/motion`](https://capacitorjs.com/docs/apis/motion) | [`./motion`](./motion) | [![npm badge](https://img.shields.io/npm/v/@capacitor/motion?style=flat-square)](https://www.npmjs.com/package/@capacitor/motion) | [`@capacitor/network`](https://capacitorjs.com/docs/apis/network) | [`./network`](./network) | [![npm badge](https://img.shields.io/npm/v/@capacitor/network?style=flat-square)](https://www.npmjs.com/package/@capacitor/network) @@ -46,13 +44,15 @@ These are official Capacitor plugins that are not contained in this repository. | --- | --- | --- | | [Background Runner](https://github.com/ionic-team/capacitor-background-runner) | [`@capacitor/background-runner`](https://capacitorjs.com/docs/apis/background-runner) | [![npm badge](https://img.shields.io/npm/v/@capacitor/background-runner?style=flat-square)](https://www.npmjs.com/package/@capacitor/background-runner) | | [Barcode Scanner](https://github.com/ionic-team/capacitor-barcode-scanner) | [`@capacitor/barcode-scanner`](https://capacitorjs.com/docs/apis/barcode-scanner) | [![npm badge](https://img.shields.io/npm/v/@capacitor/barcode-scanner?style=flat-square)](https://www.npmjs.com/package/@capacitor/barcode-scanner) | -| [Filesystem](https://github.com/ionic-team/capacitor-filesystem) (*) | [`@capacitor/geolocation`](https://capacitorjs.com/docs/apis/filesystem) | [![npm badge](https://img.shields.io/npm/v/@capacitor/filesystem?style=flat-square)](https://www.npmjs.com/package/@capacitor/filesystem) | -| [File Transfer](https://github.com/ionic-team/capacitor-file-transfer) | [`@capacitor/geolocation`](https://capacitorjs.com/docs/apis/file-transfer) | [![npm badge](https://img.shields.io/npm/v/@capacitor/file-transfer?style=flat-square)](https://www.npmjs.com/package/@capacitor/file-transfer) | -| [File Viewer](https://github.com/ionic-team/capacitor-file-viewer) | [`@capacitor/geolocation`](https://capacitorjs.com/docs/apis/file-viewer) | [![npm badge](https://img.shields.io/npm/v/@capacitor/file-viewer?style=flat-square)](https://www.npmjs.com/package/@capacitor/file-viewer) | +| [Filesystem](https://github.com/ionic-team/capacitor-filesystem) (*) | [`@capacitor/filesystem`](https://capacitorjs.com/docs/apis/filesystem) | [![npm badge](https://img.shields.io/npm/v/@capacitor/filesystem?style=flat-square)](https://www.npmjs.com/package/@capacitor/filesystem) | +| [File Transfer](https://github.com/ionic-team/capacitor-file-transfer) | [`@capacitor/file-transfer`](https://capacitorjs.com/docs/apis/file-transfer) | [![npm badge](https://img.shields.io/npm/v/@capacitor/file-transfer?style=flat-square)](https://www.npmjs.com/package/@capacitor/file-transfer) | +| [File Viewer](https://github.com/ionic-team/capacitor-file-viewer) | [`@capacitor/file-viewer`](https://capacitorjs.com/docs/apis/file-viewer) | [![npm badge](https://img.shields.io/npm/v/@capacitor/file-viewer?style=flat-square)](https://www.npmjs.com/package/@capacitor/file-viewer) | | [Geolocation](https://github.com/ionic-team/capacitor-geolocation) (*) | [`@capacitor/geolocation`](https://capacitorjs.com/docs/apis/geolocation) | [![npm badge](https://img.shields.io/npm/v/@capacitor/geolocation?style=flat-square)](https://www.npmjs.com/package/@capacitor/geolocation) | | [Google Maps](https://github.com/ionic-team/capacitor-google-maps) | [`@capacitor/google-maps`](https://capacitorjs.com/docs/apis/google-maps) | [![npm badge](https://img.shields.io/npm/v/@capacitor/google-maps?style=flat-square)](https://www.npmjs.com/package/@capacitor/google-maps) | +| [Haptics](https://github.com/ionic-team/capacitor-haptics) (*) | [`@capacitor/haptics`](https://capacitorjs.com/docs/apis/haptics) | [![npm badge](https://img.shields.io/npm/v/@capacitor/haptics?style=flat-square)](https://www.npmjs.com/package/@capacitor/haptics) | +| [Keyboard](https://github.com/ionic-team/capacitor-keyboard) (*) | [`@capacitor/haptics`](https://capacitorjs.com/docs/apis/keyboard) | [![npm badge](https://img.shields.io/npm/v/@capacitor/keyboard?style=flat-square)](https://www.npmjs.com/package/@capacitor/keyboard) | -(*) These plugins were once part of this repository, but have been since been revamped and migrated to a separate repository. The code pre-migration still remains in this repository for historic purposes. +(*) These plugins were once part of this repository, but have been since been revamped and/or migrated to a separate repository. The code pre-migration [still remains in this repository](https://github.com/ionic-team/capacitor-plugins/tree/7.x) for historic purposes. ## Capacitor Labs diff --git a/action-sheet/CHANGELOG.md b/action-sheet/CHANGELOG.md index 7bb0d0215d..54f82e6884 100644 --- a/action-sheet/CHANGELOG.md +++ b/action-sheet/CHANGELOG.md @@ -3,6 +3,14 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +# [8.0.0-alpha.1](https://github.com/ionic-team/capacitor-plugins/compare/@capacitor/action-sheet@7.0.2...@capacitor/action-sheet@8.0.0-alpha.1) (2025-09-08) + +**Note:** Version bump only for package @capacitor/action-sheet + +## [7.0.2](https://github.com/ionic-team/capacitor-plugins/compare/@capacitor/action-sheet@7.0.1...@capacitor/action-sheet@7.0.2) (2025-08-05) + +**Note:** Version bump only for package @capacitor/action-sheet + ## [7.0.1](https://github.com/ionic-team/capacitor-plugins/compare/@capacitor/action-sheet@7.0.0...@capacitor/action-sheet@7.0.1) (2025-04-02) **Note:** Version bump only for package @capacitor/action-sheet diff --git a/action-sheet/CapacitorActionSheet.podspec b/action-sheet/CapacitorActionSheet.podspec index e398da599c..72b9c640d9 100644 --- a/action-sheet/CapacitorActionSheet.podspec +++ b/action-sheet/CapacitorActionSheet.podspec @@ -11,7 +11,7 @@ Pod::Spec.new do |s| 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}', 'action-sheet/ios/Sources/**/*.{swift,h,m,c,cc,mm,cpp}' - s.ios.deployment_target = '14.0' + s.ios.deployment_target = '15.0' s.dependency 'Capacitor' s.swift_version = '5.1' end diff --git a/action-sheet/Package.swift b/action-sheet/Package.swift index 14a8fbeac4..daa098c456 100644 --- a/action-sheet/Package.swift +++ b/action-sheet/Package.swift @@ -3,7 +3,7 @@ import PackageDescription let package = Package( name: "CapacitorActionSheet", - platforms: [.iOS(.v14)], + platforms: [.iOS(.v15)], products: [ .library( name: "CapacitorActionSheet", diff --git a/action-sheet/README.md b/action-sheet/README.md index b724984234..8f8656ce31 100644 --- a/action-sheet/README.md +++ b/action-sheet/README.md @@ -13,7 +13,7 @@ npx cap sync This plugin will use the following project variables (defined in your app's `variables.gradle` file): -- `androidxMaterialVersion`: version of `com.google.android.material:material` (default: `1.12.0`) +- `androidxMaterialVersion`: version of `com.google.android.material:material` (default: `1.13.0`) ## PWA Notes diff --git a/action-sheet/android/build.gradle b/action-sheet/android/build.gradle index de5ea4afc7..d3537c7735 100644 --- a/action-sheet/android/build.gradle +++ b/action-sheet/android/build.gradle @@ -1,10 +1,10 @@ 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.0' - androidxMaterialVersion = project.hasProperty('androidxMaterialVersion') ? rootProject.ext.androidxMaterialVersion : '1.12.0' - androidxJunitVersion = project.hasProperty('androidxJunitVersion') ? rootProject.ext.androidxJunitVersion : '1.2.1' - androidxEspressoCoreVersion = project.hasProperty('androidxEspressoCoreVersion') ? rootProject.ext.androidxEspressoCoreVersion : '3.6.1' + androidxAppCompatVersion = project.hasProperty('androidxAppCompatVersion') ? rootProject.ext.androidxAppCompatVersion : '1.7.1' + androidxMaterialVersion = project.hasProperty('androidxMaterialVersion') ? rootProject.ext.androidxMaterialVersion : '1.13.0' + androidxJunitVersion = project.hasProperty('androidxJunitVersion') ? rootProject.ext.androidxJunitVersion : '1.3.0' + androidxEspressoCoreVersion = project.hasProperty('androidxEspressoCoreVersion') ? rootProject.ext.androidxEspressoCoreVersion : '3.7.0' } buildscript { @@ -12,11 +12,11 @@ buildscript { google() mavenCentral() maven { - url "https://plugins.gradle.org/m2/" + url = "https://plugins.gradle.org/m2/" } } dependencies { - classpath 'com.android.tools.build:gradle:8.7.2' + 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' } @@ -31,11 +31,11 @@ if (System.getenv("CAP_PLUGIN_PUBLISH") == "true") { } android { - namespace "com.capacitorjs.plugins.actionsheet" - compileSdk project.hasProperty('compileSdkVersion') ? rootProject.ext.compileSdkVersion : 35 + namespace = "com.capacitorjs.plugins.actionsheet" + compileSdk = project.hasProperty('compileSdkVersion') ? rootProject.ext.compileSdkVersion : 36 defaultConfig { - minSdkVersion project.hasProperty('minSdkVersion') ? rootProject.ext.minSdkVersion : 23 - targetSdkVersion project.hasProperty('targetSdkVersion') ? rootProject.ext.targetSdkVersion : 35 + 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" @@ -47,7 +47,7 @@ android { } } lintOptions { - abortOnError false + abortOnError = false } compileOptions { sourceCompatibility JavaVersion.VERSION_21 diff --git a/action-sheet/android/gradle/wrapper/gradle-wrapper.jar b/action-sheet/android/gradle/wrapper/gradle-wrapper.jar index a4b76b9530..1b33c55baa 100644 Binary files a/action-sheet/android/gradle/wrapper/gradle-wrapper.jar and b/action-sheet/android/gradle/wrapper/gradle-wrapper.jar differ diff --git a/action-sheet/android/gradle/wrapper/gradle-wrapper.properties b/action-sheet/android/gradle/wrapper/gradle-wrapper.properties index c1d5e01859..7705927e94 100644 --- a/action-sheet/android/gradle/wrapper/gradle-wrapper.properties +++ b/action-sheet/android/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-8.11.1-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.14.3-all.zip networkTimeout=10000 validateDistributionUrl=true zipStoreBase=GRADLE_USER_HOME diff --git a/action-sheet/android/gradlew b/action-sheet/android/gradlew index f5feea6d6b..23d15a9367 100755 --- a/action-sheet/android/gradlew +++ b/action-sheet/android/gradlew @@ -86,8 +86,7 @@ done # 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 -' "$PWD" ) || exit +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 @@ -115,7 +114,7 @@ case "$( uname )" in #( NONSTOP* ) nonstop=true ;; esac -CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar +CLASSPATH="\\\"\\\"" # Determine the Java command to use to start the JVM. @@ -206,7 +205,7 @@ fi DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' # Collect all arguments for the java command: -# * DEFAULT_JVM_OPTS, JAVA_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, +# * 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. @@ -214,7 +213,7 @@ DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' set -- \ "-Dorg.gradle.appname=$APP_BASE_NAME" \ -classpath "$CLASSPATH" \ - org.gradle.wrapper.GradleWrapperMain \ + -jar "$APP_HOME/gradle/wrapper/gradle-wrapper.jar" \ "$@" # Stop when "xargs" is not available. diff --git a/action-sheet/android/gradlew.bat b/action-sheet/android/gradlew.bat index 9b42019c79..5eed7ee845 100644 --- a/action-sheet/android/gradlew.bat +++ b/action-sheet/android/gradlew.bat @@ -70,11 +70,11 @@ goto fail :execute @rem Setup the command line -set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar +set CLASSPATH= @rem Execute Gradle -"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* +"%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 diff --git a/action-sheet/package.json b/action-sheet/package.json index 348ebd3f43..c302a68707 100644 --- a/action-sheet/package.json +++ b/action-sheet/package.json @@ -1,6 +1,6 @@ { "name": "@capacitor/action-sheet", - "version": "7.0.1", + "version": "8.0.0-alpha.1", "description": "The Action Sheet API provides access to native Action Sheets, which come up from the bottom of the screen and display actions a user can take.", "main": "dist/plugin.cjs.js", "module": "dist/esm/index.js", @@ -47,10 +47,10 @@ "publish:cocoapod": "pod trunk push ./CapacitorActionSheet.podspec --allow-warnings" }, "devDependencies": { - "@capacitor/android": "^7.0.0", - "@capacitor/core": "^7.0.0", + "@capacitor/android": "next", + "@capacitor/core": "next", "@capacitor/docgen": "0.2.2", - "@capacitor/ios": "^7.0.0", + "@capacitor/ios": "next", "@ionic/eslint-config": "^0.4.0", "@ionic/prettier-config": "~1.0.1", "@ionic/swiftlint-config": "^1.1.2", @@ -63,7 +63,7 @@ "typescript": "~4.1.5" }, "peerDependencies": { - "@capacitor/core": ">=7.0.0" + "@capacitor/core": "next" }, "prettier": "@ionic/prettier-config", "swiftlint": "@ionic/swiftlint-config", diff --git a/app-launcher/CHANGELOG.md b/app-launcher/CHANGELOG.md index 1df7d7c1d2..5257cf55bb 100644 --- a/app-launcher/CHANGELOG.md +++ b/app-launcher/CHANGELOG.md @@ -3,6 +3,14 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +# [8.0.0-alpha.1](https://github.com/ionic-team/capacitor-plugins/compare/@capacitor/app-launcher@7.0.2...@capacitor/app-launcher@8.0.0-alpha.1) (2025-09-08) + +**Note:** Version bump only for package @capacitor/app-launcher + +## [7.0.2](https://github.com/ionic-team/capacitor-plugins/compare/@capacitor/app-launcher@7.0.1...@capacitor/app-launcher@7.0.2) (2025-08-05) + +**Note:** Version bump only for package @capacitor/app-launcher + ## [7.0.1](https://github.com/ionic-team/capacitor-plugins/compare/@capacitor/app-launcher@7.0.0...@capacitor/app-launcher@7.0.1) (2025-04-02) **Note:** Version bump only for package @capacitor/app-launcher diff --git a/app-launcher/CapacitorAppLauncher.podspec b/app-launcher/CapacitorAppLauncher.podspec index 42f821c8df..441b396a1a 100644 --- a/app-launcher/CapacitorAppLauncher.podspec +++ b/app-launcher/CapacitorAppLauncher.podspec @@ -11,7 +11,7 @@ Pod::Spec.new do |s| 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}', 'app-launcher/ios/Sources/**/*.{swift,h,m,c,cc,mm,cpp}' - s.ios.deployment_target = '14.0' + s.ios.deployment_target = '15.0' s.dependency 'Capacitor' s.swift_version = '5.1' end diff --git a/app-launcher/Package.swift b/app-launcher/Package.swift index 6bdacd5ebb..db2b6ff876 100644 --- a/app-launcher/Package.swift +++ b/app-launcher/Package.swift @@ -3,7 +3,7 @@ import PackageDescription let package = Package( name: "CapacitorAppLauncher", - platforms: [.iOS(.v14)], + platforms: [.iOS(.v15)], products: [ .library( name: "CapacitorAppLauncher", diff --git a/app-launcher/android/build.gradle b/app-launcher/android/build.gradle index 2c753b7c98..00d349e229 100644 --- a/app-launcher/android/build.gradle +++ b/app-launcher/android/build.gradle @@ -1,9 +1,9 @@ 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.0' - androidxJunitVersion = project.hasProperty('androidxJunitVersion') ? rootProject.ext.androidxJunitVersion : '1.2.1' - androidxEspressoCoreVersion = project.hasProperty('androidxEspressoCoreVersion') ? rootProject.ext.androidxEspressoCoreVersion : '3.6.1' + androidxAppCompatVersion = project.hasProperty('androidxAppCompatVersion') ? rootProject.ext.androidxAppCompatVersion : '1.7.1' + androidxJunitVersion = project.hasProperty('androidxJunitVersion') ? rootProject.ext.androidxJunitVersion : '1.3.0' + androidxEspressoCoreVersion = project.hasProperty('androidxEspressoCoreVersion') ? rootProject.ext.androidxEspressoCoreVersion : '3.7.0' } buildscript { @@ -11,11 +11,11 @@ buildscript { google() mavenCentral() maven { - url "https://plugins.gradle.org/m2/" + url = "https://plugins.gradle.org/m2/" } } dependencies { - classpath 'com.android.tools.build:gradle:8.7.2' + 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' } @@ -30,11 +30,11 @@ if (System.getenv("CAP_PLUGIN_PUBLISH") == "true") { } android { - namespace "com.capacitorjs.plugins.applauncher" - compileSdk project.hasProperty('compileSdkVersion') ? rootProject.ext.compileSdkVersion : 35 + namespace = "com.capacitorjs.plugins.applauncher" + compileSdk = project.hasProperty('compileSdkVersion') ? rootProject.ext.compileSdkVersion : 36 defaultConfig { - minSdkVersion project.hasProperty('minSdkVersion') ? rootProject.ext.minSdkVersion : 23 - targetSdkVersion project.hasProperty('targetSdkVersion') ? rootProject.ext.targetSdkVersion : 35 + 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" @@ -46,7 +46,7 @@ android { } } lintOptions { - abortOnError false + abortOnError = false } compileOptions { sourceCompatibility JavaVersion.VERSION_21 diff --git a/app-launcher/android/gradle/wrapper/gradle-wrapper.jar b/app-launcher/android/gradle/wrapper/gradle-wrapper.jar index a4b76b9530..1b33c55baa 100644 Binary files a/app-launcher/android/gradle/wrapper/gradle-wrapper.jar and b/app-launcher/android/gradle/wrapper/gradle-wrapper.jar differ diff --git a/app-launcher/android/gradle/wrapper/gradle-wrapper.properties b/app-launcher/android/gradle/wrapper/gradle-wrapper.properties index c1d5e01859..7705927e94 100644 --- a/app-launcher/android/gradle/wrapper/gradle-wrapper.properties +++ b/app-launcher/android/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-8.11.1-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.14.3-all.zip networkTimeout=10000 validateDistributionUrl=true zipStoreBase=GRADLE_USER_HOME diff --git a/app-launcher/android/gradlew b/app-launcher/android/gradlew index f5feea6d6b..23d15a9367 100755 --- a/app-launcher/android/gradlew +++ b/app-launcher/android/gradlew @@ -86,8 +86,7 @@ done # 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 -' "$PWD" ) || exit +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 @@ -115,7 +114,7 @@ case "$( uname )" in #( NONSTOP* ) nonstop=true ;; esac -CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar +CLASSPATH="\\\"\\\"" # Determine the Java command to use to start the JVM. @@ -206,7 +205,7 @@ fi DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' # Collect all arguments for the java command: -# * DEFAULT_JVM_OPTS, JAVA_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, +# * 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. @@ -214,7 +213,7 @@ DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' set -- \ "-Dorg.gradle.appname=$APP_BASE_NAME" \ -classpath "$CLASSPATH" \ - org.gradle.wrapper.GradleWrapperMain \ + -jar "$APP_HOME/gradle/wrapper/gradle-wrapper.jar" \ "$@" # Stop when "xargs" is not available. diff --git a/app-launcher/android/gradlew.bat b/app-launcher/android/gradlew.bat index 9b42019c79..5eed7ee845 100644 --- a/app-launcher/android/gradlew.bat +++ b/app-launcher/android/gradlew.bat @@ -70,11 +70,11 @@ goto fail :execute @rem Setup the command line -set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar +set CLASSPATH= @rem Execute Gradle -"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* +"%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 diff --git a/app-launcher/package.json b/app-launcher/package.json index 0ad49eff87..aaf9ec2702 100644 --- a/app-launcher/package.json +++ b/app-launcher/package.json @@ -1,6 +1,6 @@ { "name": "@capacitor/app-launcher", - "version": "7.0.1", + "version": "8.0.0-alpha.1", "description": "The AppLauncher API allows to open other apps", "main": "dist/plugin.cjs.js", "module": "dist/esm/index.js", @@ -47,10 +47,10 @@ "publish:cocoapod": "pod trunk push ./CapacitorAppLauncher.podspec --allow-warnings" }, "devDependencies": { - "@capacitor/android": "^7.0.0", - "@capacitor/core": "^7.0.0", + "@capacitor/android": "next", + "@capacitor/core": "next", "@capacitor/docgen": "0.2.2", - "@capacitor/ios": "^7.0.0", + "@capacitor/ios": "next", "@ionic/eslint-config": "^0.4.0", "@ionic/prettier-config": "~1.0.1", "@ionic/swiftlint-config": "^1.1.2", @@ -63,7 +63,7 @@ "typescript": "~4.1.5" }, "peerDependencies": { - "@capacitor/core": ">=7.0.0" + "@capacitor/core": "next" }, "prettier": "@ionic/prettier-config", "swiftlint": "@ionic/swiftlint-config", diff --git a/app/CHANGELOG.md b/app/CHANGELOG.md index 1fd16499e5..ae1fa56c4d 100644 --- a/app/CHANGELOG.md +++ b/app/CHANGELOG.md @@ -3,6 +3,20 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +# [8.0.0-alpha.1](https://github.com/ionic-team/capacitor-plugins/compare/@capacitor/app@7.1.0...@capacitor/app@8.0.0-alpha.1) (2025-09-08) + +**Note:** Version bump only for package @capacitor/app + +# [7.1.0](https://github.com/ionic-team/capacitor-plugins/compare/@capacitor/app@7.0.2...@capacitor/app@7.1.0) (2025-09-05) + +### Features + +- **app:** Add support for toggling Android back button handling ([#2390](https://github.com/ionic-team/capacitor-plugins/issues/2390)) ([6c56293](https://github.com/ionic-team/capacitor-plugins/commit/6c56293d65d5359fc2f9cd8491c546c4c91bc0a0)) + +## [7.0.2](https://github.com/ionic-team/capacitor-plugins/compare/@capacitor/app@7.0.1...@capacitor/app@7.0.2) (2025-08-05) + +**Note:** Version bump only for package @capacitor/app + ## [7.0.1](https://github.com/ionic-team/capacitor-plugins/compare/@capacitor/app@7.0.0...@capacitor/app@7.0.1) (2025-04-02) **Note:** Version bump only for package @capacitor/app diff --git a/app/CapacitorApp.podspec b/app/CapacitorApp.podspec index 0c1c9262ff..fd0c4c6f3e 100644 --- a/app/CapacitorApp.podspec +++ b/app/CapacitorApp.podspec @@ -11,7 +11,7 @@ Pod::Spec.new do |s| 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}', 'app/ios/Sources/**/*.{swift,h,m,c,cc,mm,cpp}' - s.ios.deployment_target = '14.0' + s.ios.deployment_target = '15.0' s.dependency 'Capacitor' s.swift_version = '5.1' end diff --git a/app/Package.swift b/app/Package.swift index dbd88d233a..5d92c17acc 100644 --- a/app/Package.swift +++ b/app/Package.swift @@ -3,7 +3,7 @@ import PackageDescription let package = Package( name: "CapacitorApp", - platforms: [.iOS(.v14)], + platforms: [.iOS(.v15)], products: [ .library( name: "CapacitorApp", diff --git a/app/README.md b/app/README.md index aa136f6ae2..a90390bf37 100644 --- a/app/README.md +++ b/app/README.md @@ -67,6 +67,49 @@ const checkAppLaunchUrl = async () => { }; ``` +## Configuration + + + + +| Prop | Type | Description | Default | Since | +| ------------------------------ | -------------------- | ------------------------------------------------------------------------------ | ------------------ | ----- | +| **`disableBackButtonHandler`** | boolean | Disable the plugin's default back button handling. Only available for Android. | false | 7.1.0 | + +### Examples + +In `capacitor.config.json`: + +```json +{ + "plugins": { + "App": { + "disableBackButtonHandler": true + } + } +} +``` + +In `capacitor.config.ts`: + +```ts +/// + +import { CapacitorConfig } from '@capacitor/cli'; + +const config: CapacitorConfig = { + plugins: { + App: { + disableBackButtonHandler: true, + }, + }, +}; + +export default config; +``` + + + ## API @@ -76,6 +119,7 @@ const checkAppLaunchUrl = async () => { * [`getState()`](#getstate) * [`getLaunchUrl()`](#getlaunchurl) * [`minimizeApp()`](#minimizeapp) +* [`toggleBackButtonHandler(...)`](#togglebackbuttonhandler) * [`addListener('appStateChange', ...)`](#addlistenerappstatechange-) * [`addListener('pause', ...)`](#addlistenerpause-) * [`addListener('resume', ...)`](#addlistenerresume-) @@ -167,6 +211,25 @@ Only available for Android. -------------------- +### toggleBackButtonHandler(...) + +```typescript +toggleBackButtonHandler(options: ToggleBackButtonHandlerOptions) => Promise +``` + +Enables or disables the plugin's back button handling during runtime. + +Only available for Android. + +| Param | Type | +| ------------- | ----------------------------------------------------------------------------------------- | +| **`options`** | ToggleBackButtonHandlerOptions | + +**Since:** 7.1.0 + +-------------------- + + ### addListener('appStateChange', ...) ```typescript @@ -364,6 +427,13 @@ Remove all native listeners for this plugin | **`url`** | string | The url used to open the app. | 1.0.0 | +#### ToggleBackButtonHandlerOptions + +| Prop | Type | Description | Since | +| ------------- | -------------------- | -------------------------------------------------------------------- | ----- | +| **`enabled`** | boolean | Indicates whether to enable or disable default back button handling. | 7.1.0 | + + #### PluginListenerHandle | Prop | Type | diff --git a/app/android/build.gradle b/app/android/build.gradle index eb307964ef..5a6657695e 100644 --- a/app/android/build.gradle +++ b/app/android/build.gradle @@ -1,9 +1,9 @@ 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.0' - androidxJunitVersion = project.hasProperty('androidxJunitVersion') ? rootProject.ext.androidxJunitVersion : '1.2.1' - androidxEspressoCoreVersion = project.hasProperty('androidxEspressoCoreVersion') ? rootProject.ext.androidxEspressoCoreVersion : '3.6.1' + androidxAppCompatVersion = project.hasProperty('androidxAppCompatVersion') ? rootProject.ext.androidxAppCompatVersion : '1.7.1' + androidxJunitVersion = project.hasProperty('androidxJunitVersion') ? rootProject.ext.androidxJunitVersion : '1.3.0' + androidxEspressoCoreVersion = project.hasProperty('androidxEspressoCoreVersion') ? rootProject.ext.androidxEspressoCoreVersion : '3.7.0' } buildscript { @@ -11,11 +11,11 @@ buildscript { google() mavenCentral() maven { - url "https://plugins.gradle.org/m2/" + url = "https://plugins.gradle.org/m2/" } } dependencies { - classpath 'com.android.tools.build:gradle:8.7.2' + 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' } @@ -30,11 +30,11 @@ if (System.getenv("CAP_PLUGIN_PUBLISH") == "true") { } android { - namespace "com.capacitorjs.plugins.app" - compileSdk project.hasProperty('compileSdkVersion') ? rootProject.ext.compileSdkVersion : 35 + namespace = "com.capacitorjs.plugins.app" + compileSdk = project.hasProperty('compileSdkVersion') ? rootProject.ext.compileSdkVersion : 36 defaultConfig { - minSdkVersion project.hasProperty('minSdkVersion') ? rootProject.ext.minSdkVersion : 23 - targetSdkVersion project.hasProperty('targetSdkVersion') ? rootProject.ext.targetSdkVersion : 35 + 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" @@ -46,7 +46,7 @@ android { } } lintOptions { - abortOnError false + abortOnError = false } compileOptions { sourceCompatibility JavaVersion.VERSION_21 diff --git a/app/android/gradle/wrapper/gradle-wrapper.jar b/app/android/gradle/wrapper/gradle-wrapper.jar index a4b76b9530..1b33c55baa 100644 Binary files a/app/android/gradle/wrapper/gradle-wrapper.jar and b/app/android/gradle/wrapper/gradle-wrapper.jar differ diff --git a/app/android/gradle/wrapper/gradle-wrapper.properties b/app/android/gradle/wrapper/gradle-wrapper.properties index c1d5e01859..7705927e94 100644 --- a/app/android/gradle/wrapper/gradle-wrapper.properties +++ b/app/android/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-8.11.1-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.14.3-all.zip networkTimeout=10000 validateDistributionUrl=true zipStoreBase=GRADLE_USER_HOME diff --git a/app/android/gradlew b/app/android/gradlew index f5feea6d6b..23d15a9367 100755 --- a/app/android/gradlew +++ b/app/android/gradlew @@ -86,8 +86,7 @@ done # 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 -' "$PWD" ) || exit +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 @@ -115,7 +114,7 @@ case "$( uname )" in #( NONSTOP* ) nonstop=true ;; esac -CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar +CLASSPATH="\\\"\\\"" # Determine the Java command to use to start the JVM. @@ -206,7 +205,7 @@ fi DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' # Collect all arguments for the java command: -# * DEFAULT_JVM_OPTS, JAVA_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, +# * 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. @@ -214,7 +213,7 @@ DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' set -- \ "-Dorg.gradle.appname=$APP_BASE_NAME" \ -classpath "$CLASSPATH" \ - org.gradle.wrapper.GradleWrapperMain \ + -jar "$APP_HOME/gradle/wrapper/gradle-wrapper.jar" \ "$@" # Stop when "xargs" is not available. diff --git a/app/android/gradlew.bat b/app/android/gradlew.bat index 9b42019c79..5eed7ee845 100644 --- a/app/android/gradlew.bat +++ b/app/android/gradlew.bat @@ -70,11 +70,11 @@ goto fail :execute @rem Setup the command line -set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar +set CLASSPATH= @rem Execute Gradle -"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* +"%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 diff --git a/app/android/src/main/java/com/capacitorjs/plugins/app/AppPlugin.java b/app/android/src/main/java/com/capacitorjs/plugins/app/AppPlugin.java index 0ca7fa152b..0b6295b5fc 100644 --- a/app/android/src/main/java/com/capacitorjs/plugins/app/AppPlugin.java +++ b/app/android/src/main/java/com/capacitorjs/plugins/app/AppPlugin.java @@ -25,7 +25,11 @@ public class AppPlugin extends Plugin { private static final String EVENT_RESUME = "resume"; private boolean hasPausedEver = false; + private OnBackPressedCallback onBackPressedCallback; + public void load() { + boolean disableBackButtonHandler = getConfig().getBoolean("disableBackButtonHandler", false); + bridge .getApp() .setStatusChangeListener( @@ -44,22 +48,24 @@ public void load() { notifyListeners(EVENT_RESTORED_RESULT, result.getWrappedResult(), true); } ); - OnBackPressedCallback callback = new OnBackPressedCallback(true) { - @Override - public void handleOnBackPressed() { - if (!hasListeners(EVENT_BACK_BUTTON)) { - if (bridge.getWebView().canGoBack()) { - bridge.getWebView().goBack(); + this.onBackPressedCallback = + new OnBackPressedCallback(!disableBackButtonHandler) { + @Override + public void handleOnBackPressed() { + if (!hasListeners(EVENT_BACK_BUTTON)) { + if (bridge.getWebView().canGoBack()) { + bridge.getWebView().goBack(); + } + } else { + JSObject data = new JSObject(); + data.put("canGoBack", bridge.getWebView().canGoBack()); + notifyListeners(EVENT_BACK_BUTTON, data, true); + bridge.triggerJSEvent("backbutton", "document"); } - } else { - JSObject data = new JSObject(); - data.put("canGoBack", bridge.getWebView().canGoBack()); - notifyListeners(EVENT_BACK_BUTTON, data, true); - bridge.triggerJSEvent("backbutton", "document"); } - } - }; - getActivity().getOnBackPressedDispatcher().addCallback(getActivity(), callback); + }; + + getActivity().getOnBackPressedDispatcher().addCallback(getActivity(), this.onBackPressedCallback); } @PluginMethod @@ -112,6 +118,19 @@ public void minimizeApp(PluginCall call) { call.resolve(); } + @PluginMethod + public void toggleBackButtonHandler(PluginCall call) { + if (this.onBackPressedCallback == null) { + call.reject("onBackPressedCallback is not set"); + return; + } + + Boolean enabled = call.getBoolean("enabled"); + + this.onBackPressedCallback.setEnabled(enabled); + call.resolve(); + } + /** * Handle ACTION_VIEW intents to store a URL that was used to open the app * @param intent diff --git a/app/ios/Sources/AppPlugin/AppPlugin.swift b/app/ios/Sources/AppPlugin/AppPlugin.swift index 0f60865077..a6c4451fd3 100644 --- a/app/ios/Sources/AppPlugin/AppPlugin.swift +++ b/app/ios/Sources/AppPlugin/AppPlugin.swift @@ -10,7 +10,8 @@ public class AppPlugin: CAPPlugin, CAPBridgedPlugin { CAPPluginMethod(name: "getInfo", returnType: CAPPluginReturnPromise), CAPPluginMethod(name: "getLaunchUrl", returnType: CAPPluginReturnPromise), CAPPluginMethod(name: "getState", returnType: CAPPluginReturnPromise), - CAPPluginMethod(name: "minimizeApp", returnType: CAPPluginReturnPromise) + CAPPluginMethod(name: "minimizeApp", returnType: CAPPluginReturnPromise), + CAPPluginMethod(name: "toggleBackButtonHandler", returnType: CAPPluginReturnPromise) ] private var observers: [NSObjectProtocol] = [] @@ -113,4 +114,8 @@ public class AppPlugin: CAPPlugin, CAPBridgedPlugin { @objc func minimizeApp(_ call: CAPPluginCall) { call.unimplemented() } + + @objc func toggleBackButtonHandler(_ call: CAPPluginCall) { + call.unimplemented() + } } diff --git a/app/package.json b/app/package.json index e7948a0e23..90efb395df 100644 --- a/app/package.json +++ b/app/package.json @@ -1,6 +1,6 @@ { "name": "@capacitor/app", - "version": "7.0.1", + "version": "8.0.0-alpha.1", "description": "The App API handles high level App state and events.For example, this API emits events when the app enters and leaves the foreground, handles deeplinks, opens other apps, and manages persisted plugin state.", "main": "dist/plugin.cjs.js", "module": "dist/esm/index.js", @@ -47,10 +47,11 @@ "publish:cocoapod": "pod trunk push ./CapacitorApp.podspec --allow-warnings" }, "devDependencies": { - "@capacitor/android": "^7.0.0", - "@capacitor/core": "^7.0.0", + "@capacitor/android": "next", + "@capacitor/cli": "next", + "@capacitor/core": "next", "@capacitor/docgen": "0.2.2", - "@capacitor/ios": "^7.0.0", + "@capacitor/ios": "next", "@ionic/eslint-config": "^0.4.0", "@ionic/prettier-config": "~1.0.1", "@ionic/swiftlint-config": "^1.1.2", @@ -63,7 +64,7 @@ "typescript": "~4.1.5" }, "peerDependencies": { - "@capacitor/core": ">=7.0.0" + "@capacitor/core": "next" }, "prettier": "@ionic/prettier-config", "swiftlint": "@ionic/swiftlint-config", diff --git a/app/src/definitions.ts b/app/src/definitions.ts index ff8324896b..5745a1d66c 100644 --- a/app/src/definitions.ts +++ b/app/src/definitions.ts @@ -1,5 +1,24 @@ +/// + import type { PluginListenerHandle } from '@capacitor/core'; +declare module '@capacitor/cli' { + export interface PluginsConfig { + App?: { + /** + * Disable the plugin's default back button handling. + * + * Only available for Android. + * + * @since 7.1.0 + * @default false + * @example true + */ + disableBackButtonHandler?: boolean; + }; + } +} + export interface AppInfo { /** * The name of the app. @@ -125,6 +144,15 @@ export interface BackButtonListenerEvent { canGoBack: boolean; } +export interface ToggleBackButtonHandlerOptions { + /** + * Indicates whether to enable or disable default back button handling. + * + * @since 7.1.0 + */ + enabled: boolean; +} + export type StateChangeListener = (state: AppState) => void; export type URLOpenListener = (event: URLOpenListenerEvent) => void; export type RestoredListener = (event: RestoredListenerEvent) => void; @@ -171,6 +199,17 @@ export interface AppPlugin { */ minimizeApp(): Promise; + /** + * Enables or disables the plugin's back button handling during runtime. + * + * Only available for Android. + * + * @since 7.1.0 + */ + toggleBackButtonHandler( + options: ToggleBackButtonHandlerOptions, + ): Promise; + /** * Listen for changes in the app or the activity states. * diff --git a/app/src/web.ts b/app/src/web.ts index 1db1ff356f..62288cc076 100644 --- a/app/src/web.ts +++ b/app/src/web.ts @@ -32,6 +32,10 @@ export class AppWeb extends WebPlugin implements AppPlugin { throw this.unimplemented('Not implemented on web.'); } + async toggleBackButtonHandler(): Promise { + throw this.unimplemented('Not implemented on web.'); + } + private handleVisibilityChange = () => { const data = { isActive: document.hidden !== true, diff --git a/browser/CHANGELOG.md b/browser/CHANGELOG.md index a73cfa7972..b80a02ee3a 100644 --- a/browser/CHANGELOG.md +++ b/browser/CHANGELOG.md @@ -3,6 +3,14 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +# [8.0.0-alpha.1](https://github.com/ionic-team/capacitor-plugins/compare/@capacitor/browser@7.0.2...@capacitor/browser@8.0.0-alpha.1) (2025-09-08) + +**Note:** Version bump only for package @capacitor/browser + +## [7.0.2](https://github.com/ionic-team/capacitor-plugins/compare/@capacitor/browser@7.0.1...@capacitor/browser@7.0.2) (2025-08-05) + +**Note:** Version bump only for package @capacitor/browser + ## [7.0.1](https://github.com/ionic-team/capacitor-plugins/compare/@capacitor/browser@7.0.0...@capacitor/browser@7.0.1) (2025-04-02) **Note:** Version bump only for package @capacitor/browser diff --git a/browser/CapacitorBrowser.podspec b/browser/CapacitorBrowser.podspec index 27b8b74ed3..e5486df1ff 100644 --- a/browser/CapacitorBrowser.podspec +++ b/browser/CapacitorBrowser.podspec @@ -11,7 +11,7 @@ Pod::Spec.new do |s| 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}', 'browser/ios/Sources/**/*.{swift,h,m,c,cc,mm,cpp}' - s.ios.deployment_target = '14.0' + s.ios.deployment_target = '15.0' s.dependency 'Capacitor' s.swift_version = '5.1' end diff --git a/browser/Package.swift b/browser/Package.swift index 0ffbc508ec..4cd76ca98c 100644 --- a/browser/Package.swift +++ b/browser/Package.swift @@ -3,7 +3,7 @@ import PackageDescription let package = Package( name: "CapacitorBrowser", - platforms: [.iOS(.v14)], + platforms: [.iOS(.v15)], products: [ .library( name: "CapacitorBrowser", diff --git a/browser/README.md b/browser/README.md index ac7553a679..325f8b9222 100644 --- a/browser/README.md +++ b/browser/README.md @@ -17,7 +17,7 @@ npx cap sync This plugin will use the following project variables (defined in your app's `variables.gradle` file): -- `androidxBrowserVersion`: version of `androidx.browser:browser` (default: `1.8.0`) +- `androidxBrowserVersion`: version of `androidx.browser:browser` (default: `1.9.0`) ## Example diff --git a/browser/android/build.gradle b/browser/android/build.gradle index baee8e430d..1fad7dcb27 100644 --- a/browser/android/build.gradle +++ b/browser/android/build.gradle @@ -1,10 +1,10 @@ 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.0' - androidxJunitVersion = project.hasProperty('androidxJunitVersion') ? rootProject.ext.androidxJunitVersion : '1.2.1' - androidxEspressoCoreVersion = project.hasProperty('androidxEspressoCoreVersion') ? rootProject.ext.androidxEspressoCoreVersion : '3.6.1' - androidxBrowserVersion = project.hasProperty('androidxBrowserVersion') ? rootProject.ext.androidxBrowserVersion : '1.8.0' + androidxAppCompatVersion = project.hasProperty('androidxAppCompatVersion') ? rootProject.ext.androidxAppCompatVersion : '1.7.1' + androidxJunitVersion = project.hasProperty('androidxJunitVersion') ? rootProject.ext.androidxJunitVersion : '1.3.0' + androidxEspressoCoreVersion = project.hasProperty('androidxEspressoCoreVersion') ? rootProject.ext.androidxEspressoCoreVersion : '3.7.0' + androidxBrowserVersion = project.hasProperty('androidxBrowserVersion') ? rootProject.ext.androidxBrowserVersion : '1.9.0' } buildscript { @@ -12,11 +12,11 @@ buildscript { google() mavenCentral() maven { - url "https://plugins.gradle.org/m2/" + url = "https://plugins.gradle.org/m2/" } } dependencies { - classpath 'com.android.tools.build:gradle:8.7.2' + 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' } @@ -31,11 +31,11 @@ if (System.getenv("CAP_PLUGIN_PUBLISH") == "true") { } android { - namespace "com.capacitorjs.plugins.browser" - compileSdk project.hasProperty('compileSdkVersion') ? rootProject.ext.compileSdkVersion : 35 + namespace = "com.capacitorjs.plugins.browser" + compileSdk = project.hasProperty('compileSdkVersion') ? rootProject.ext.compileSdkVersion : 36 defaultConfig { - minSdkVersion project.hasProperty('minSdkVersion') ? rootProject.ext.minSdkVersion : 23 - targetSdkVersion project.hasProperty('targetSdkVersion') ? rootProject.ext.targetSdkVersion : 35 + 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" @@ -47,7 +47,7 @@ android { } } lintOptions { - abortOnError false + abortOnError = false } compileOptions { sourceCompatibility JavaVersion.VERSION_21 diff --git a/browser/android/gradle/wrapper/gradle-wrapper.jar b/browser/android/gradle/wrapper/gradle-wrapper.jar index a4b76b9530..1b33c55baa 100644 Binary files a/browser/android/gradle/wrapper/gradle-wrapper.jar and b/browser/android/gradle/wrapper/gradle-wrapper.jar differ diff --git a/browser/android/gradle/wrapper/gradle-wrapper.properties b/browser/android/gradle/wrapper/gradle-wrapper.properties index c1d5e01859..7705927e94 100644 --- a/browser/android/gradle/wrapper/gradle-wrapper.properties +++ b/browser/android/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-8.11.1-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.14.3-all.zip networkTimeout=10000 validateDistributionUrl=true zipStoreBase=GRADLE_USER_HOME diff --git a/browser/android/gradlew b/browser/android/gradlew index f5feea6d6b..23d15a9367 100755 --- a/browser/android/gradlew +++ b/browser/android/gradlew @@ -86,8 +86,7 @@ done # 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 -' "$PWD" ) || exit +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 @@ -115,7 +114,7 @@ case "$( uname )" in #( NONSTOP* ) nonstop=true ;; esac -CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar +CLASSPATH="\\\"\\\"" # Determine the Java command to use to start the JVM. @@ -206,7 +205,7 @@ fi DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' # Collect all arguments for the java command: -# * DEFAULT_JVM_OPTS, JAVA_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, +# * 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. @@ -214,7 +213,7 @@ DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' set -- \ "-Dorg.gradle.appname=$APP_BASE_NAME" \ -classpath "$CLASSPATH" \ - org.gradle.wrapper.GradleWrapperMain \ + -jar "$APP_HOME/gradle/wrapper/gradle-wrapper.jar" \ "$@" # Stop when "xargs" is not available. diff --git a/browser/android/gradlew.bat b/browser/android/gradlew.bat index 9b42019c79..5eed7ee845 100644 --- a/browser/android/gradlew.bat +++ b/browser/android/gradlew.bat @@ -70,11 +70,11 @@ goto fail :execute @rem Setup the command line -set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar +set CLASSPATH= @rem Execute Gradle -"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* +"%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 diff --git a/browser/package.json b/browser/package.json index b701efefcd..574b7b2201 100644 --- a/browser/package.json +++ b/browser/package.json @@ -1,6 +1,6 @@ { "name": "@capacitor/browser", - "version": "7.0.1", + "version": "8.0.0-alpha.1", "description": "The Browser API provides the ability to open an in-app browser and subscribe to browser events.", "main": "dist/plugin.cjs.js", "module": "dist/esm/index.js", @@ -47,10 +47,10 @@ "publish:cocoapod": "pod trunk push ./CapacitorBrowser.podspec --allow-warnings" }, "devDependencies": { - "@capacitor/android": "^7.0.0", - "@capacitor/core": "^7.0.0", + "@capacitor/android": "next", + "@capacitor/core": "next", "@capacitor/docgen": "0.2.2", - "@capacitor/ios": "^7.0.0", + "@capacitor/ios": "next", "@ionic/eslint-config": "^0.4.0", "@ionic/prettier-config": "~1.0.1", "@ionic/swiftlint-config": "^1.1.2", @@ -63,7 +63,7 @@ "typescript": "~4.1.5" }, "peerDependencies": { - "@capacitor/core": ">=7.0.0" + "@capacitor/core": "next" }, "prettier": "@ionic/prettier-config", "swiftlint": "@ionic/swiftlint-config", diff --git a/camera/.gitignore b/camera/.gitignore index 6817637958..987fc52ad1 100644 --- a/camera/.gitignore +++ b/camera/.gitignore @@ -1,5 +1,4 @@ # node files -dist node_modules # iOS files diff --git a/camera/CHANGELOG.md b/camera/CHANGELOG.md index 327b198c38..a2842eb383 100644 --- a/camera/CHANGELOG.md +++ b/camera/CHANGELOG.md @@ -3,6 +3,16 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +# [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 diff --git a/camera/CapacitorCamera.podspec b/camera/CapacitorCamera.podspec index 1bcb12d486..83ea9132c3 100644 --- a/camera/CapacitorCamera.podspec +++ b/camera/CapacitorCamera.podspec @@ -11,7 +11,7 @@ Pod::Spec.new do |s| 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 = '14.0' + s.ios.deployment_target = '15.0' s.dependency 'Capacitor' s.swift_version = '5.1' end diff --git a/camera/Package.swift b/camera/Package.swift index ebdf4a6702..1829fc2260 100644 --- a/camera/Package.swift +++ b/camera/Package.swift @@ -3,7 +3,7 @@ import PackageDescription let package = Package( name: "CapacitorCamera", - platforms: [.iOS(.v14)], + platforms: [.iOS(.v15)], products: [ .library( name: "CapacitorCamera", @@ -19,7 +19,10 @@ let package = Package( .product(name: "Capacitor", package: "capacitor-swift-pm"), .product(name: "Cordova", package: "capacitor-swift-pm") ], - path: "ios/Sources/CameraPlugin"), + path: "ios/Sources/CameraPlugin", + resources: [ + .process("Resources") + ]), .testTarget( name: "CameraPluginTests", dependencies: ["CameraPlugin"], diff --git a/camera/README.md b/camera/README.md index 9ad234eeec..a9d75b04d1 100644 --- a/camera/README.md +++ b/camera/README.md @@ -68,8 +68,8 @@ Additionally, because the Camera API launches a separate Activity to handle taki This plugin will use the following project variables (defined in your app's `variables.gradle` file): -- `androidxExifInterfaceVersion`: version of `androidx.exifinterface:exifinterface` (default: `1.3.7`) -- `androidxMaterialVersion`: version of `com.google.android.material:material` (default: `1.12.0`) +- `androidxExifInterfaceVersion`: version of `androidx.exifinterface:exifinterface` (default: `1.4.1`) +- `androidxMaterialVersion`: version of `com.google.android.material:material` (default: `1.13.0`) ## PWA Notes @@ -144,7 +144,6 @@ pickImages(options: GalleryImageOptions) => Promise ``` Allows the user to pick multiple pictures from the photo gallery. -On iOS 13 and older it only allows to pick one picture. | Param | Type | | ------------- | ------------------------------------------------------------------- | @@ -163,9 +162,9 @@ On iOS 13 and older it only allows to pick one picture. pickLimitedLibraryPhotos() => Promise ``` -iOS 14+ Only: Allows the user to update their limited photo library selection. -On iOS 15+ returns all the limited photos after the picker dismissal. -On iOS 14 or if the user gave full access to the photos it returns an empty array. +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> @@ -180,7 +179,7 @@ On iOS 14 or if the user gave full access to the photos it returns an empty arra getLimitedLibraryPhotos() => Promise ``` -iOS 14+ Only: Return an array of photos selected from the limited photo library. +Return an array of photos selected from the limited photo library. **Returns:** Promise<GalleryPhotos> @@ -244,7 +243,7 @@ Request camera and photo album permissions | 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 14+ it's only supported for CameraSource.Camera, but not for CameraSource.Photos. | | 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 | @@ -336,11 +335,12 @@ Request camera and photo album permissions #### 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. | +| 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. | +| **`CameraMulti`** | 'CAMERA_MULTI' | Take multiple photos in a row using the camera. Available on Android and iOS. | +| **`Photos`** | 'PHOTOS' | Pick an existing photo from the gallery or photo album. | #### CameraDirection diff --git a/camera/android/build.gradle b/camera/android/build.gradle index 3b2b8665ff..3aa7daa23f 100644 --- a/camera/android/build.gradle +++ b/camera/android/build.gradle @@ -6,6 +6,7 @@ ext { androidxExifInterfaceVersion = project.hasProperty('androidxExifInterfaceVersion') ? rootProject.ext.androidxExifInterfaceVersion : '1.3.7' androidxJunitVersion = project.hasProperty('androidxJunitVersion') ? rootProject.ext.androidxJunitVersion : '1.2.1' androidxMaterialVersion = project.hasProperty('androidxMaterialVersion') ? rootProject.ext.androidxMaterialVersion : '1.12.0' + cameraxVersion = project.hasProperty('cameraxVersion') ? rootProject.ext.cameraxVersion : '1.3.1' } buildscript { @@ -33,10 +34,10 @@ if (System.getenv("CAP_PLUGIN_PUBLISH") == "true") { android { namespace "com.capacitorjs.plugins.camera" - compileSdk project.hasProperty('compileSdkVersion') ? rootProject.ext.compileSdkVersion : 35 + compileSdk project.hasProperty('compileSdkVersion') ? rootProject.ext.compileSdkVersion : 36 defaultConfig { - minSdkVersion project.hasProperty('minSdkVersion') ? rootProject.ext.minSdkVersion : 23 - targetSdkVersion project.hasProperty('targetSdkVersion') ? rootProject.ext.targetSdkVersion : 35 + 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" @@ -77,6 +78,9 @@ dependencies { implementation "androidx.exifinterface:exifinterface:$androidxExifInterfaceVersion" implementation "androidx.appcompat:appcompat:$androidxAppCompatVersion" implementation "com.google.android.material:material:$androidxMaterialVersion" + implementation "androidx.camera:camera-camera2:${cameraxVersion}" + implementation "androidx.camera:camera-view:${cameraxVersion}" + implementation "androidx.camera:camera-lifecycle:${cameraxVersion}" testImplementation "junit:junit:$junitVersion" androidTestImplementation "androidx.test.ext:junit:$androidxJunitVersion" androidTestImplementation "androidx.test.espresso:espresso-core:$androidxEspressoCoreVersion" diff --git a/camera/android/gradle/wrapper/gradle-wrapper.jar b/camera/android/gradle/wrapper/gradle-wrapper.jar index a4b76b9530..1b33c55baa 100644 Binary files a/camera/android/gradle/wrapper/gradle-wrapper.jar and b/camera/android/gradle/wrapper/gradle-wrapper.jar differ diff --git a/camera/android/gradle/wrapper/gradle-wrapper.properties b/camera/android/gradle/wrapper/gradle-wrapper.properties index c1d5e01859..7705927e94 100644 --- a/camera/android/gradle/wrapper/gradle-wrapper.properties +++ b/camera/android/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-8.11.1-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.14.3-all.zip networkTimeout=10000 validateDistributionUrl=true zipStoreBase=GRADLE_USER_HOME diff --git a/camera/android/gradlew b/camera/android/gradlew index f5feea6d6b..23d15a9367 100755 --- a/camera/android/gradlew +++ b/camera/android/gradlew @@ -86,8 +86,7 @@ done # 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 -' "$PWD" ) || exit +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 @@ -115,7 +114,7 @@ case "$( uname )" in #( NONSTOP* ) nonstop=true ;; esac -CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar +CLASSPATH="\\\"\\\"" # Determine the Java command to use to start the JVM. @@ -206,7 +205,7 @@ fi DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' # Collect all arguments for the java command: -# * DEFAULT_JVM_OPTS, JAVA_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, +# * 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. @@ -214,7 +213,7 @@ DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' set -- \ "-Dorg.gradle.appname=$APP_BASE_NAME" \ -classpath "$CLASSPATH" \ - org.gradle.wrapper.GradleWrapperMain \ + -jar "$APP_HOME/gradle/wrapper/gradle-wrapper.jar" \ "$@" # Stop when "xargs" is not available. diff --git a/camera/android/gradlew.bat b/camera/android/gradlew.bat index 9b42019c79..5eed7ee845 100644 --- a/camera/android/gradlew.bat +++ b/camera/android/gradlew.bat @@ -70,11 +70,11 @@ goto fail :execute @rem Setup the command line -set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar +set CLASSPATH= @rem Execute Gradle -"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* +"%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 diff --git a/camera/android/src/main/AndroidManifest.xml b/camera/android/src/main/AndroidManifest.xml index 2898a91dd9..3f9f145df1 100644 --- a/camera/android/src/main/AndroidManifest.xml +++ b/camera/android/src/main/AndroidManifest.xml @@ -4,4 +4,6 @@ + + diff --git a/camera/android/src/main/java/com/capacitorjs/plugins/camera/CameraFragment.java b/camera/android/src/main/java/com/capacitorjs/plugins/camera/CameraFragment.java new file mode 100644 index 0000000000..dcdbf182f8 --- /dev/null +++ b/camera/android/src/main/java/com/capacitorjs/plugins/camera/CameraFragment.java @@ -0,0 +1,2761 @@ +package com.capacitorjs.plugins.camera; + +import static com.capacitorjs.plugins.camera.DeviceUtils.dpToPx; + +import android.os.VibrationEffect; +import android.os.Vibrator; +import android.view.ViewTreeObserver; + +import android.annotation.SuppressLint; +import android.app.AlertDialog; +import android.content.ContentResolver; +import android.content.ContentValues; +import android.content.Context; +import android.content.res.ColorStateList; +import android.content.res.Configuration; +import android.database.Cursor; +import android.graphics.Bitmap; +import android.graphics.BitmapFactory; +import android.graphics.Color; +import android.graphics.drawable.GradientDrawable; +import android.media.MediaActionSound; +import android.net.Uri; +import android.os.Build; +import android.os.Bundle; +import android.os.Handler; +import android.os.Looper; +import android.provider.MediaStore; +import android.util.DisplayMetrics; +import android.util.Log; +import android.util.Size; +import android.view.Gravity; +import android.view.HapticFeedbackConstants; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.view.Window; +import android.view.WindowInsets; +import android.view.WindowInsetsController; +import android.view.DisplayCutout; +import android.view.animation.AccelerateDecelerateInterpolator; +import android.widget.FrameLayout; +import android.widget.ImageView; +import android.widget.LinearLayout; +import android.widget.ProgressBar; +import android.widget.RelativeLayout; +import android.widget.TextView; + +import androidx.annotation.ColorInt; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.camera.core.CameraSelector; +import androidx.camera.core.ImageCapture; +import androidx.camera.core.ImageCaptureException; +import androidx.camera.core.ZoomState; +import androidx.camera.view.LifecycleCameraController; +import androidx.camera.view.PreviewView; +import androidx.cardview.widget.CardView; +import androidx.core.content.ContextCompat; +import androidx.fragment.app.Fragment; +import androidx.fragment.app.FragmentActivity; +import androidx.recyclerview.widget.LinearLayoutManager; +import androidx.recyclerview.widget.RecyclerView; + +import com.getcapacitor.Logger; +import com.google.android.material.floatingactionbutton.FloatingActionButton; +import com.google.android.material.tabs.TabLayout; + +import java.io.FileNotFoundException; +import java.io.IOException; +import java.io.InputStream; +import java.text.DecimalFormat; +import java.text.SimpleDateFormat; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Locale; +import java.util.Map; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicBoolean; + +@SuppressWarnings("FieldCanBeLocal") +/** + * CameraFragment provides a full-screen camera interface with safe area inset awareness. + * + * Safe Area Inset Implementation: + * - Detects display cutouts and system window insets (Android API 28+) + * - Automatically adjusts UI layout to avoid camera cutouts, especially in landscape right mode + * - Calculates safe margins for shutter button and controls positioning + * - Dynamically updates layout when orientation changes + * - Provides minimum safe margins even on devices without cutouts + * + * Key methods for safe area handling: + * - calculateSafeAreaInsets(): Detects and calculates safe insets + * - getSafeControlMargin(): Returns appropriate margin based on orientation + * - logSafeAreaStatus(): Logs current safe area status for debugging + */ +public class CameraFragment extends Fragment { + + // Constants + private final String TAG = "CameraFragment"; + private final String FILENAME = "yyyy-MM-dd-HH-mm-ss-SSS"; + private final String PHOTO_TYPE = "image/jpeg"; + + private final String CONFIRM_CANCEL_TITLE = "Discard Photos?"; + private final String CONFIRM_CANCEL_MESSAGE = "Are you sure you want to discard all photos?"; + private final String CONFIRM_CANCEL_POSITIVE = "Yes"; + private final String CONFIRM_CANCEL_NEGATIVE = "No"; + + @ColorInt + private final int ZOOM_TAB_LAYOUT_BACKGROUND_COLOR = 0x80000000; + + @ColorInt + private final int ZOOM_BUTTON_COLOR_SELECTED = 0xFFFFFFFF; + + @ColorInt + private final int ZOOM_BUTTON_COLOR_UNSELECTED = 0x80FFFFFF; + + private final AtomicBoolean isSnappingZoom = new AtomicBoolean(false); + // View related variables + private RelativeLayout relativeLayout; + private RelativeLayout bottomBar; + private PreviewView previewView; + private ImageView focusIndicator; + private ThumbnailAdapter thumbnailAdapter; + private RecyclerView filmstripView; + private TabLayout zoomTabLayout; + private LinearLayout verticalZoomContainer; // For vertical zoom buttons in landscape mode + private CardView zoomTabCardView; + private FloatingActionButton takePictureButton; + private FloatingActionButton flipCameraButton; + private FloatingActionButton doneButton; + private FloatingActionButton closeButton; + private FloatingActionButton flashButton; + private RelativeLayout.LayoutParams bottomBarLayoutParams; + private RelativeLayout.LayoutParams cardViewLayoutParams; + private RelativeLayout.LayoutParams tabLayoutParams; + private RelativeLayout.LayoutParams takePictureLayoutParams; + private RelativeLayout.LayoutParams flipButtonLayoutParams; + private RelativeLayout.LayoutParams doneButtonLayoutParams; + private RelativeLayout.LayoutParams closeButtonLayoutParams; + private RelativeLayout.LayoutParams flashButtonLayoutParams; + private DisplayMetrics displayMetrics; + private boolean isLandscape = false; + private RelativeLayout controlsContainer; // Container for camera controls in landscape mode + + // Safe area insets for camera cutout awareness + private int safeInsetTop = 0; + private int safeInsetBottom = 0; + private int safeInsetLeft = 0; + private int safeInsetRight = 0; + // Camera variables + private int lensFacing = CameraSelector.LENS_FACING_BACK; + private int flashMode = ImageCapture.FLASH_MODE_AUTO; + + @SuppressWarnings("unused") + private ZoomState zoomRatio = null; + + private float minZoom = 0f; + + @SuppressWarnings("unused") + private float maxZoom = 1f; + + private ExecutorService cameraExecutor; + private LifecycleCameraController cameraController; + // Utility variables + private HashMap imageCache; + private ArrayList zoomTabs; + + private Handler zoomHandler = null; + private Runnable zoomRunnable = null; + private MediaActionSound mediaActionSound; + + private Vibrator vibrator; + + // Callbacks + private OnImagesCapturedCallback imagesCapturedCallback; + + // Camera settings + private CameraSettings cameraSettings; + + // Processing spinner overlay + private View processingOverlay; + private ProgressBar processingSpinner; + private TextView processingText; + private Handler processingHandler; + private Runnable processingRunnable; + + // ViewTreeObserver listener references for proper cleanup + private ViewTreeObserver.OnGlobalLayoutListener relativeLayoutListener; + private ViewTreeObserver.OnGlobalLayoutListener previewViewListener; + private ViewTreeObserver.OnGlobalLayoutListener previewViewSecondaryListener; + private ViewTreeObserver.OnGlobalLayoutListener zoomTabCardViewListener; + private ViewTreeObserver.OnGlobalLayoutListener zoomTabCardViewSecondaryListener; + private ViewTreeObserver.OnGlobalLayoutListener filmstripViewListener; + private ViewTreeObserver.OnGlobalLayoutListener filmstripViewSecondaryListener; + + @NonNull + private static ColorStateList createButtonColorList() { + int[][] states = new int[][]{ + new int[]{android.R.attr.state_enabled}, // enabled + new int[]{-android.R.attr.state_enabled}, // disabled + new int[]{-android.R.attr.state_checked}, // unchecked + new int[]{android.R.attr.state_pressed} // pressed + }; + + int[] colors = new int[]{Color.DKGRAY, Color.TRANSPARENT, Color.TRANSPARENT, Color.LTGRAY}; + return new ColorStateList(states, colors); + } + + @Override + public void onCreate(@Nullable Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + // Initialize simple HashMap for image storage + imageCache = new HashMap<>(); + + zoomTabs = new ArrayList<>(); + zoomHandler = new Handler(requireActivity().getMainLooper()); + mediaActionSound = new MediaActionSound(); + mediaActionSound.load(MediaActionSound.SHUTTER_CLICK); + + vibrator = (Vibrator) requireContext().getSystemService(Context.VIBRATOR_SERVICE); + + // Register for configuration changes (like orientation changes) + setRetainInstance(true); + } + + @Override + public void onDestroy() { + super.onDestroy(); + // Restore the original system UI settings + Window window = requireActivity().getWindow(); + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) { + final WindowInsetsController insetsController = window.getInsetsController(); + if (insetsController != null) { + insetsController.show(android.view.WindowInsets.Type.statusBars() | android.view.WindowInsets.Type.navigationBars()); + insetsController.setSystemBarsBehavior(WindowInsetsController.BEHAVIOR_DEFAULT); + } + } else { + View decorView = window.getDecorView(); + int flags = View.SYSTEM_UI_FLAG_VISIBLE; + decorView.setSystemUiVisibility(flags); + } + window.setStatusBarColor(requireActivity().getResources().getColor(android.R.color.transparent)); + window.setNavigationBarColor(requireActivity().getResources().getColor(android.R.color.transparent)); + + // Clean up any ViewTreeObserver listeners that might still be active + cleanupViewTreeObservers(); + + // Clear image cache to free memory + if (imageCache != null) { + try { + // Manually recycle all bitmaps before clearing cache + for (Bitmap bitmap : imageCache.values()) { + if (bitmap != null && !bitmap.isRecycled()) { + bitmap.recycle(); + } + } + imageCache.clear(); + imageCache = null; + } catch (Exception e) { + Logger.error(TAG, "Error clearing image cache", e); + imageCache = null; + } + } + + if (mediaActionSound != null) { + mediaActionSound.release(); + mediaActionSound = null; + } + + // Clean up processing handler and runnable + if (processingHandler != null && processingRunnable != null) { + processingHandler.removeCallbacks(processingRunnable); + processingHandler = null; + processingRunnable = null; + } + + if (cameraExecutor != null) { + cameraExecutor.shutdown(); + try { + // Wait for tasks to complete with timeout + if (!cameraExecutor.awaitTermination(500, TimeUnit.MILLISECONDS)) { + cameraExecutor.shutdownNow(); + } + } catch (InterruptedException e) { + cameraExecutor.shutdownNow(); + Thread.currentThread().interrupt(); + } + } + } + + /** + * Cleans up any ViewTreeObserver listeners to prevent memory leaks + */ + private void cleanupViewTreeObservers() { + try { + // Clean up ViewTreeObserver listeners for all views that might have them + if (relativeLayout != null && relativeLayout.getViewTreeObserver().isAlive() && relativeLayoutListener != null) { + relativeLayout.getViewTreeObserver().removeOnGlobalLayoutListener(relativeLayoutListener); + relativeLayoutListener = null; + } + + if (previewView != null && previewView.getViewTreeObserver().isAlive()) { + if (previewViewListener != null) { + previewView.getViewTreeObserver().removeOnGlobalLayoutListener(previewViewListener); + previewViewListener = null; + } + if (previewViewSecondaryListener != null) { + previewView.getViewTreeObserver().removeOnGlobalLayoutListener(previewViewSecondaryListener); + previewViewSecondaryListener = null; + } + } + + if (zoomTabCardView != null && zoomTabCardView.getViewTreeObserver().isAlive()) { + if (zoomTabCardViewListener != null) { + zoomTabCardView.getViewTreeObserver().removeOnGlobalLayoutListener(zoomTabCardViewListener); + zoomTabCardViewListener = null; + } + if (zoomTabCardViewSecondaryListener != null) { + zoomTabCardView.getViewTreeObserver().removeOnGlobalLayoutListener(zoomTabCardViewSecondaryListener); + zoomTabCardViewSecondaryListener = null; + } + } + + if (filmstripView != null && filmstripView.getViewTreeObserver().isAlive()) { + if (filmstripViewListener != null) { + filmstripView.getViewTreeObserver().removeOnGlobalLayoutListener(filmstripViewListener); + filmstripViewListener = null; + } + if (filmstripViewSecondaryListener != null) { + filmstripView.getViewTreeObserver().removeOnGlobalLayoutListener(filmstripViewSecondaryListener); + filmstripViewSecondaryListener = null; + } + } + } catch (Exception e) { + // Log but don't crash if there's an issue cleaning up listeners + Logger.error(TAG, "Error cleaning up ViewTreeObserver listeners", e); + } + } + + @Nullable + /** + * Checks if the device is currently in landscape mode + * + * @param context The context to check orientation + * @return true if in landscape mode, false otherwise + */ + private boolean isLandscapeMode(Context context) { + return context.getResources().getConfiguration().orientation == Configuration.ORIENTATION_LANDSCAPE; + } + + /** + * Calculates safe area insets to avoid camera cutouts and other display features + * This is particularly important for landscape right mode where the shutter button + * could be positioned near the camera cutout. + */ + private void calculateSafeAreaInsets() { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) { + Window window = requireActivity().getWindow(); + WindowInsets rootInsets = window.getDecorView().getRootWindowInsets(); + + if (rootInsets != null) { + DisplayCutout displayCutout = rootInsets.getDisplayCutout(); + + if (displayCutout != null) { + // Get cutout insets + safeInsetTop = Math.max(safeInsetTop, displayCutout.getSafeInsetTop()); + safeInsetBottom = Math.max(safeInsetBottom, displayCutout.getSafeInsetBottom()); + safeInsetLeft = Math.max(safeInsetLeft, displayCutout.getSafeInsetLeft()); + safeInsetRight = Math.max(safeInsetRight, displayCutout.getSafeInsetRight()); + + Logger.debug(TAG, "Display cutout detected - Top: " + safeInsetTop + + ", Bottom: " + safeInsetBottom + + ", Left: " + safeInsetLeft + + ", Right: " + safeInsetRight); + } else { + Logger.debug(TAG, "No display cutout detected"); + } + + // Also check for system window insets as fallback + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) { + android.graphics.Insets systemInsets = rootInsets.getInsets(WindowInsets.Type.systemBars()); + safeInsetTop = Math.max(safeInsetTop, systemInsets.top); + safeInsetBottom = Math.max(safeInsetBottom, systemInsets.bottom); + safeInsetLeft = Math.max(safeInsetLeft, systemInsets.left); + safeInsetRight = Math.max(safeInsetRight, systemInsets.right); + } + } + } + + // Apply minimum safe margins even if no cutout is detected + int minSafeMargin = dpToPx(requireContext(), 16); + safeInsetTop = Math.max(safeInsetTop, minSafeMargin); + safeInsetBottom = Math.max(safeInsetBottom, minSafeMargin); + safeInsetLeft = Math.max(safeInsetLeft, minSafeMargin); + safeInsetRight = Math.max(safeInsetRight, minSafeMargin); + + Logger.debug(TAG, "Final safe area insets - Top: " + safeInsetTop + + ", Bottom: " + safeInsetBottom + + ", Left: " + safeInsetLeft + + ", Right: " + safeInsetRight); + + // Log safe area status for debugging + logSafeAreaStatus(); + } + + /** + * Gets the appropriate margin for controls based on orientation and safe area insets + * In landscape right mode, we need extra margin to avoid camera cutouts + */ + private int getSafeControlMargin(int baseMargin) { + if (!isLandscape) { + return baseMargin; + } + + // In landscape mode, especially landscape right, we need to account for cutouts + // The right side is where the controls container is positioned + return Math.max(baseMargin, safeInsetRight); + } + + /** + * Logs current orientation and safe area inset information for debugging + */ + private void logSafeAreaStatus() { + String orientation = isLandscape ? "LANDSCAPE" : "PORTRAIT"; + Logger.debug(TAG, "Safe Area Status - Orientation: " + orientation + + ", Safe Insets - Top: " + safeInsetTop + + ", Bottom: " + safeInsetBottom + + ", Left: " + safeInsetLeft + + ", Right: " + safeInsetRight); + + if (isLandscape && safeInsetRight > dpToPx(requireContext(), 16)) { + Logger.info(TAG, "Landscape mode with significant right inset detected - likely camera cutout area"); + } + } + + @Override + public void onConfigurationChanged(@NonNull Configuration newConfig) { + super.onConfigurationChanged(newConfig); + + // Check if device is in landscape mode with the new configuration + boolean wasLandscape = isLandscape; + isLandscape = newConfig.orientation == Configuration.ORIENTATION_LANDSCAPE; + + // Recalculate safe area insets for the new orientation + calculateSafeAreaInsets(); + + // Recreate the layout when orientation changes + if (relativeLayout != null) { + // Save the current camera state + int currentLensFacing = lensFacing; + int currentFlashMode = flashMode; + + // Completely recreate the camera controller when switching orientations + if (cameraController != null) { + cameraController.unbind(); + cameraController = null; + } + + // Clear zoom tabs when recreating UI for orientation change + if (!zoomTabs.isEmpty()) { + if (zoomTabLayout != null) { + zoomTabLayout.removeAllTabs(); + } + if (verticalZoomContainer != null) { + verticalZoomContainer.removeAllViews(); + } + zoomTabs.clear(); + } + + // Remove all views + relativeLayout.removeAllViews(); + + // Recreate the UI with the new orientation + FragmentActivity fragmentActivity = requireActivity(); + int margin = (int) (20 * displayMetrics.density); + int barHeight = (int) (100 * displayMetrics.density); + + ColorStateList buttonColors = createButtonColorList(); + + // Create a black background that fills the entire screen + View blackBackground = new View(fragmentActivity); + blackBackground.setId(View.generateViewId()); + blackBackground.setBackgroundColor(Color.BLACK); + RelativeLayout.LayoutParams blackBgParams = new RelativeLayout.LayoutParams( + RelativeLayout.LayoutParams.MATCH_PARENT, + RelativeLayout.LayoutParams.MATCH_PARENT + ); + blackBackground.setLayoutParams(blackBgParams); + relativeLayout.addView(blackBackground, 0); // Add at index 0 to be behind everything + + // Create the preview view first + createPreviewView(fragmentActivity); + + createFocusIndicator(fragmentActivity); + + if (isLandscape) { + // In landscape mode, create a container for controls on the right side + // Use safe margin calculation for orientation change + int safeMargin = getSafeControlMargin(margin); + createControlsContainerForLandscape(fragmentActivity, buttonColors, safeMargin); + } else { + // In portrait mode, create the bottom bar and buttons + createBottomBar(fragmentActivity, barHeight, margin, buttonColors); + + // Set preview view to be above bottom bar in portrait mode + RelativeLayout.LayoutParams previewParams = (RelativeLayout.LayoutParams) previewView.getLayoutParams(); + previewParams.addRule(RelativeLayout.ABOVE, bottomBar.getId()); + previewView.setLayoutParams(previewParams); + + // Zoom bar is above the bottom bar/buttons + createZoomTabLayout(fragmentActivity, margin); + + // Thumbnail images in the filmstrip are above the zoom buttons + createFilmstripView(fragmentActivity); + + // Close button and flash are top left/right corners + createCloseButton(fragmentActivity, margin, buttonColors); + createFlashButton(fragmentActivity, margin, buttonColors); + } + + // Create a new camera controller + cameraController = new LifecycleCameraController(requireActivity()); + cameraController.bindToLifecycle(requireActivity()); + previewView.setController(cameraController); + + // Restore camera settings + lensFacing = currentLensFacing; + flashMode = currentFlashMode; + + // Make sure the camera selector is set correctly + CameraSelector cameraSelector = new CameraSelector.Builder().requireLensFacing(lensFacing).build(); + cameraController.setCameraSelector(cameraSelector); + cameraController.setImageCaptureFlashMode(flashMode); + + // Setup camera to initialize zoom state and other camera features + setupCamera(); + + // Force layout update + relativeLayout.requestLayout(); + relativeLayout.invalidate(); + previewView.requestLayout(); + + // Use ViewTreeObserver to efficiently handle layout updates + relativeLayoutListener = new ViewTreeObserver.OnGlobalLayoutListener() { + @Override + public void onGlobalLayout() { + // Remove the listener to prevent multiple callbacks + relativeLayout.getViewTreeObserver().removeOnGlobalLayoutListener(this); + relativeLayoutListener = null; + + if (isLandscape) { + // Force the preview view to take up the correct width in landscape mode + int containerWidth = (int) (displayMetrics.widthPixels * 0.2); + RelativeLayout.LayoutParams previewParams = (RelativeLayout.LayoutParams) previewView.getLayoutParams(); + + // Clear any existing rules that might be interfering + previewParams.removeRule(RelativeLayout.ABOVE); + previewParams.removeRule(RelativeLayout.BELOW); + previewParams.removeRule(RelativeLayout.RIGHT_OF); + previewParams.removeRule(RelativeLayout.LEFT_OF); + previewParams.removeRule(RelativeLayout.CENTER_IN_PARENT); + previewParams.removeRule(RelativeLayout.CENTER_HORIZONTAL); + previewParams.removeRule(RelativeLayout.CENTER_VERTICAL); + + // Set explicit width to 80% of screen width + previewParams.width = displayMetrics.widthPixels - containerWidth; + previewParams.height = RelativeLayout.LayoutParams.MATCH_PARENT; + + // Set the correct rules for landscape mode + previewParams.addRule(RelativeLayout.ALIGN_PARENT_LEFT); + previewParams.addRule(RelativeLayout.ALIGN_PARENT_TOP); + previewParams.addRule(RelativeLayout.ALIGN_PARENT_BOTTOM); + if (controlsContainer != null) { + previewParams.addRule(RelativeLayout.LEFT_OF, controlsContainer.getId()); + } + + previewParams.setMargins(0, 0, 0, 0); + previewView.setLayoutParams(previewParams); + + // Force update the scale type + previewView.setScaleType(PreviewView.ScaleType.FILL_CENTER); + + // Add a second layout listener for fine-tuning after the initial layout + previewViewSecondaryListener = new ViewTreeObserver.OnGlobalLayoutListener() { + @Override + public void onGlobalLayout() { + // Remove this listener after execution + previewView.getViewTreeObserver().removeOnGlobalLayoutListener(this); + previewViewSecondaryListener = null; + + if (previewView != null && isLandscape) { + previewView.setScaleType(PreviewView.ScaleType.FILL_CENTER); + } + } + }; + previewView.getViewTreeObserver().addOnGlobalLayoutListener(previewViewSecondaryListener); + } + } + }; + relativeLayout.getViewTreeObserver().addOnGlobalLayoutListener(relativeLayoutListener); + } + } + + @Override + public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { + FragmentActivity fragmentActivity = requireActivity(); + displayMetrics = fragmentActivity.getResources().getDisplayMetrics(); + int margin = (int) (20 * displayMetrics.density); + int barHeight = (int) (100 * displayMetrics.density); + + // Check if device is in landscape mode + isLandscape = isLandscapeMode(fragmentActivity); + + // Calculate safe area insets for camera cutout awareness + calculateSafeAreaInsets(); + + relativeLayout = new RelativeLayout(fragmentActivity); + + // Create a black background that fills the entire screen + View blackBackground = new View(fragmentActivity); + blackBackground.setId(View.generateViewId()); + blackBackground.setBackgroundColor(Color.BLACK); + RelativeLayout.LayoutParams blackBgParams = new RelativeLayout.LayoutParams( + RelativeLayout.LayoutParams.MATCH_PARENT, + RelativeLayout.LayoutParams.MATCH_PARENT + ); + blackBackground.setLayoutParams(blackBgParams); + relativeLayout.addView(blackBackground); // Add the background first + + ColorStateList buttonColors = createButtonColorList(); + + // Create the preview view first + createPreviewView(fragmentActivity); + + createFocusIndicator(fragmentActivity); + + if (isLandscape) { + // In landscape mode, create a container for controls on the right side + // Use safe margin calculation for initial creation + int safeMargin = getSafeControlMargin(margin); + createControlsContainerForLandscape(fragmentActivity, buttonColors, safeMargin); + } else { + // In portrait mode, create the bottom bar and buttons + createBottomBar(fragmentActivity, barHeight, margin, buttonColors); + + // Set preview view to be above bottom bar in portrait mode + RelativeLayout.LayoutParams previewParams = (RelativeLayout.LayoutParams) previewView.getLayoutParams(); + previewParams.addRule(RelativeLayout.ABOVE, bottomBar.getId()); + previewView.setLayoutParams(previewParams); + + // Zoom bar is above the bottom bar/buttons + createZoomTabLayout(fragmentActivity, margin); + + // Thumbnail images in the filmstrip are above the zoom buttons + createFilmstripView(fragmentActivity); + + // Close button and flash are top left/right corners + createCloseButton(fragmentActivity, margin, buttonColors); + createFlashButton(fragmentActivity, margin, buttonColors); + } + + // Set a transparent navigation bar + Window window = requireActivity().getWindow(); + window.setStatusBarColor(Color.BLACK); + window.setNavigationBarColor(Color.BLACK); + + // Enable immersive fullscreen mode (hide status and navigation bars) + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) { + final WindowInsetsController insetsController = window.getInsetsController(); + if (insetsController != null) { + insetsController.hide(android.view.WindowInsets.Type.statusBars() | android.view.WindowInsets.Type.navigationBars()); + insetsController.setSystemBarsBehavior(WindowInsetsController.BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE); + } + } else { + View decorView = window.getDecorView(); + int flags = View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY | View.SYSTEM_UI_FLAG_FULLSCREEN | View.SYSTEM_UI_FLAG_HIDE_NAVIGATION; + decorView.setSystemUiVisibility(flags); + } + + // Set up window insets listener to handle safe area changes dynamically + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) { + relativeLayout.setOnApplyWindowInsetsListener((v, insets) -> { + // Recalculate safe area insets when window insets change + calculateSafeAreaInsets(); + return insets; + }); + } + + // Remove edge-to-edge insets handling for true fullscreen + requireActivity().getWindow().setDecorFitsSystemWindows(true); + + // Create processing overlay + createProcessingOverlay(fragmentActivity); + + return relativeLayout; + } + + @Override + public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) { + super.onViewCreated(view, savedInstanceState); + + // Request focus for the root view to ensure it can receive touch events immediately. + view.setFocusableInTouchMode(true); + view.requestFocus(); + + cameraController = new LifecycleCameraController(requireActivity()); + cameraController.bindToLifecycle(requireActivity()); + previewView.setController(cameraController); + cameraExecutor = Executors.newSingleThreadExecutor(); + + // Use ViewTreeObserver to ensure layout is complete before setting up camera + previewViewListener = new ViewTreeObserver.OnGlobalLayoutListener() { + @Override + public void onGlobalLayout() { + // Remove the listener to prevent multiple callbacks + previewView.getViewTreeObserver().removeOnGlobalLayoutListener(this); + previewViewListener = null; + setupCamera(); + } + }; + previewView.getViewTreeObserver().addOnGlobalLayoutListener(previewViewListener); + } + + /** + * Safely adds an image to the cache + * + * @param uri The URI of the image + * @param bitmap The bitmap to cache + */ + private void addImageToCache(Uri uri, Bitmap bitmap) { + if (uri != null && bitmap != null && !bitmap.isRecycled() && imageCache != null) { + try { + imageCache.put(uri, bitmap); + } catch (Exception e) { + Logger.error(TAG, "Error adding image to cache", e); + } + } else { + Logger.warn(TAG, "Failed to add image to cache - uri: " + uri + ", bitmap: " + bitmap + ", imageCache: " + imageCache); + } + } + + /** + * Safely retrieves an image from the cache + * + * @param uri The URI of the image to retrieve + * @return The cached bitmap, or null if not found + */ + private Bitmap getImageFromCache(Uri uri) { + return imageCache != null ? imageCache.get(uri) : null; + } + + /** + * Gets all image URIs currently in the cache + * + * @return A HashMap of all cached images + */ + private HashMap getAllCachedImages() { + HashMap result = new HashMap<>(); + if (imageCache != null) { + // Copy all entries from the cache + for (Map.Entry entry : imageCache.entrySet()) { + result.put(entry.getKey(), entry.getValue()); + } + } + return result; + } + + private void cancel() { + // Stop any ongoing processing and cleanup resources + hideProcessingOverlay(); + + // Cancel any ongoing background processing tasks + if (cameraExecutor != null && !cameraExecutor.isShutdown()) { + // Remove any loading thumbnails to stop processing + if (thumbnailAdapter != null) { + thumbnailAdapter.removeLoadingThumbnails(); + } + } + + // When the user cancels the camera session, it should clean up all the photos that were + // taken. + int failedDeletions = 0; + for (Map.Entry image : getAllCachedImages().entrySet()) { + if (!deleteFile(image.getKey())) { + failedDeletions++; + Logger.warn(TAG, "Failed to delete image during cancel: " + image.getKey()); + } + } + + if (failedDeletions > 0) { + Logger.warn(TAG, "Failed to delete " + failedDeletions + " images during cancel"); + // We still proceed with cancellation even if some deletions failed + } + + if (imagesCapturedCallback != null) { + imagesCapturedCallback.onCaptureCanceled(); + } + closeFragment(); + } + + private void done() { + // Check if there are still images being processed + if (thumbnailAdapter != null && thumbnailAdapter.hasLoadingThumbnails()) { + Logger.debug(TAG, "Images still processing, showing spinner overlay"); + // Show non-dismissable spinner while processing + showProcessingOverlay(); + + // Check periodically if processing is complete + processingHandler = new Handler(Looper.getMainLooper()); + processingRunnable = new Runnable() { + @Override + public void run() { + if (thumbnailAdapter != null && thumbnailAdapter.hasLoadingThumbnails()) { + // Still processing, check again in 500ms + if (processingHandler != null) { + processingHandler.postDelayed(this, 500); + } + } else { + // Processing complete, hide spinner and proceed + hideProcessingOverlay(); + finalizeDone(); + } + } + }; + processingHandler.post(processingRunnable); + } else { + Logger.debug(TAG, "No images processing, proceeding immediately"); + // No processing needed, proceed immediately + finalizeDone(); + } + } + + private void finalizeDone() { + if (imagesCapturedCallback != null) { + imagesCapturedCallback.onCaptureSuccess(getAllCachedImages()); + } + closeFragment(); + } + + /** + * Safely closes the fragment, handling any potential exceptions + */ + private void closeFragment() { + try { + if (getActivity() != null && !getActivity().isFinishing() && isAdded()) { + requireActivity().getSupportFragmentManager().beginTransaction().remove(this).commit(); + } else { + Logger.warn(TAG, "Cannot close fragment: activity is null, finishing, or fragment not added"); + } + } catch (IllegalStateException e) { + // This can happen if the activity is being destroyed + Logger.error(TAG, "Error closing fragment", e); + } catch (Exception e) { + Logger.error(TAG, "Unexpected error closing fragment", e); + } + } + + /** + * Creates the processing overlay with spinner and text + */ + private void createProcessingOverlay(FragmentActivity fragmentActivity) { + // Create the main overlay container with semi-transparent background + processingOverlay = new RelativeLayout(fragmentActivity); + processingOverlay.setId(View.generateViewId()); + processingOverlay.setBackgroundColor(0x80000000); // Semi-transparent black + processingOverlay.setVisibility(View.GONE); + + RelativeLayout.LayoutParams overlayParams = new RelativeLayout.LayoutParams( + RelativeLayout.LayoutParams.MATCH_PARENT, + RelativeLayout.LayoutParams.MATCH_PARENT + ); + processingOverlay.setLayoutParams(overlayParams); + + // Create content container for centering + RelativeLayout contentContainer = new RelativeLayout(fragmentActivity); + contentContainer.setId(View.generateViewId()); + RelativeLayout.LayoutParams contentParams = new RelativeLayout.LayoutParams( + RelativeLayout.LayoutParams.WRAP_CONTENT, + RelativeLayout.LayoutParams.WRAP_CONTENT + ); + contentParams.addRule(RelativeLayout.CENTER_IN_PARENT); + contentContainer.setLayoutParams(contentParams); + + // Create spinner + processingSpinner = new ProgressBar(fragmentActivity); + processingSpinner.setId(View.generateViewId()); + int spinnerSize = (int) (48 * displayMetrics.density); + RelativeLayout.LayoutParams spinnerParams = new RelativeLayout.LayoutParams(spinnerSize, spinnerSize); + spinnerParams.addRule(RelativeLayout.CENTER_HORIZONTAL); + processingSpinner.setLayoutParams(spinnerParams); + + // Create text + processingText = new TextView(fragmentActivity); + processingText.setId(View.generateViewId()); + processingText.setText("Processing images..."); + processingText.setTextColor(Color.WHITE); + processingText.setTextSize(16); + processingText.setGravity(Gravity.CENTER); + RelativeLayout.LayoutParams textParams = new RelativeLayout.LayoutParams( + RelativeLayout.LayoutParams.WRAP_CONTENT, + RelativeLayout.LayoutParams.WRAP_CONTENT + ); + textParams.addRule(RelativeLayout.CENTER_HORIZONTAL); + textParams.addRule(RelativeLayout.BELOW, processingSpinner.getId()); + textParams.setMargins(0, (int) (16 * displayMetrics.density), 0, 0); + processingText.setLayoutParams(textParams); + + // Add spinner and text to content container + contentContainer.addView(processingSpinner); + contentContainer.addView(processingText); + + // Add content container to the main overlay + ((RelativeLayout) processingOverlay).addView(contentContainer); + + // Add the overlay to main layout + relativeLayout.addView(processingOverlay); + } + + /** + * Shows the processing overlay + */ + private void showProcessingOverlay() { + Logger.debug(TAG, "Showing processing overlay"); + if (processingOverlay != null) { + processingOverlay.setVisibility(View.VISIBLE); + processingOverlay.bringToFront(); + } + } + + /** + * Hides the processing overlay and cleans up resources + */ + private void hideProcessingOverlay() { + if (processingOverlay != null) { + processingOverlay.setVisibility(View.GONE); + } + + // Clean up processing handler and runnable + if (processingHandler != null && processingRunnable != null) { + processingHandler.removeCallbacks(processingRunnable); + processingHandler = null; + processingRunnable = null; + } + } + + public void setImagesCapturedCallback(OnImagesCapturedCallback imagesCapturedCallback) { + this.imagesCapturedCallback = imagesCapturedCallback; + } + + public void setCameraSettings(CameraSettings settings) { + this.cameraSettings = settings; + } + + /** + * Process bitmap according to camera settings (quality, resize, orientation) + */ + private Bitmap processBitmap(Bitmap originalBitmap, Uri imageUri) { + if (originalBitmap == null || originalBitmap.isRecycled()) { + Logger.warn(TAG, "Cannot process null or recycled bitmap"); + return null; + } + + // If no settings are available, return original bitmap + if (cameraSettings == null) { + Logger.debug(TAG, "No camera settings available, returning original bitmap"); + return originalBitmap; + } + + Bitmap processedBitmap = originalBitmap; + + try { + ExifWrapper exif = ImageUtils.getExifData(getContext(), processedBitmap, imageUri); + boolean wasProcessed = false; + + // Apply resizing + if (cameraSettings.isShouldResize() && cameraSettings.getWidth() > 0 && cameraSettings.getHeight() > 0) { + Bitmap resizedBitmap = ImageUtils.resize(processedBitmap, cameraSettings.getWidth(), cameraSettings.getHeight()); + if (resizedBitmap != processedBitmap && resizedBitmap != null) { + Logger.debug(TAG, "Applied resizing to " + cameraSettings.getWidth() + "x" + cameraSettings.getHeight()); + if (processedBitmap != originalBitmap) { + processedBitmap.recycle(); + } + processedBitmap = resizedBitmap; + wasProcessed = true; + } + } + + // Apply orientation correction (only if explicitly enabled) + if (cameraSettings.isShouldCorrectOrientation()) { + Bitmap correctedBitmap = ImageUtils.correctOrientation(getContext(), processedBitmap, imageUri, exif); + if (correctedBitmap != processedBitmap && correctedBitmap != null) { + Logger.debug(TAG, "Applied orientation correction"); + if (processedBitmap != originalBitmap) { + processedBitmap.recycle(); + } + processedBitmap = correctedBitmap; + wasProcessed = true; + } + } + + if (wasProcessed) { + Logger.debug(TAG, "Bitmap processed: " + originalBitmap.getWidth() + "x" + originalBitmap.getHeight() + + " -> " + processedBitmap.getWidth() + "x" + processedBitmap.getHeight()); + } + + return processedBitmap; + } catch (Exception e) { + Logger.error(TAG, "Error processing bitmap", e); + // If processing fails, return original bitmap and don't crash + return originalBitmap; + } + } + + private void createBottomBar(FragmentActivity fragmentActivity, int barHeight, int margin, ColorStateList buttonColors) { + bottomBar = new RelativeLayout(fragmentActivity); + bottomBar.setId(View.generateViewId()); + bottomBar.setBackgroundColor(Color.BLACK); + bottomBarLayoutParams = new RelativeLayout.LayoutParams(RelativeLayout.LayoutParams.MATCH_PARENT, barHeight); + bottomBarLayoutParams.addRule(RelativeLayout.ALIGN_PARENT_BOTTOM); + bottomBar.setLayoutParams(bottomBarLayoutParams); + relativeLayout.addView(bottomBar); + + createTakePictureButton(fragmentActivity, margin, buttonColors); + createFlipButton(fragmentActivity, margin, buttonColors); + createDoneButton(fragmentActivity, margin, buttonColors); + } + + private void createTakePictureButtonForLandscape(FragmentActivity fragmentActivity, int margin, ColorStateList buttonColors) { + takePictureButton = new FloatingActionButton(fragmentActivity); + takePictureButton.setId(View.generateViewId()); + takePictureButton.setImageResource(R.drawable.ic_shutter_circle); + takePictureButton.setBackgroundColor(Color.TRANSPARENT); + takePictureButton.setBackgroundTintList(buttonColors); + + int fabSize = dpToPx(fragmentActivity, 84); + int iconSize = (int) (fabSize * 0.9); + takePictureButton.setCustomSize(fabSize); + takePictureButton.setMaxImageSize(iconSize); + + takePictureLayoutParams = new RelativeLayout.LayoutParams( + RelativeLayout.LayoutParams.WRAP_CONTENT, + RelativeLayout.LayoutParams.WRAP_CONTENT + ); + + // Position in the center of the right side controls container + // Add extra margin to ensure it's not too close to potential camera cutouts + takePictureLayoutParams.addRule(RelativeLayout.CENTER_VERTICAL); + takePictureLayoutParams.addRule(RelativeLayout.CENTER_HORIZONTAL); + + // Add safe area margins to ensure the button is positioned away from cutouts + int safeMarginHorizontal = Math.max(margin, safeInsetRight / 2); + int safeMarginVertical = Math.max(margin, Math.max(safeInsetTop, safeInsetBottom) / 4); + takePictureLayoutParams.setMargins(safeMarginHorizontal, safeMarginVertical, safeMarginHorizontal, safeMarginVertical); + + takePictureButton.setLayoutParams(takePictureLayoutParams); + takePictureButton.setStateListAnimator(android.animation.AnimatorInflater.loadStateListAnimator(fragmentActivity, R.animator.button_press_animation)); + takePictureButton.setOnClickListener( + v -> { + + emitCaptureFeedback(v); + + // Add loading thumbnail immediately for visual feedback + if (thumbnailAdapter != null) { + thumbnailAdapter.addLoadingThumbnail(); + // Scroll to show the new loading thumbnail + if (filmstripView != null) { + filmstripView.scrollToPosition(thumbnailAdapter.getItemCount() - 1); + } + } + + var name = new SimpleDateFormat(FILENAME, Locale.US).format(System.currentTimeMillis()); + var contentValues = new ContentValues(); + contentValues.put(MediaStore.MediaColumns.DISPLAY_NAME, name); + contentValues.put(MediaStore.MediaColumns.MIME_TYPE, PHOTO_TYPE); + var outputOptions = new ImageCapture.OutputFileOptions.Builder( + requireContext().getContentResolver(), + MediaStore.Images.Media.EXTERNAL_CONTENT_URI, + contentValues + ) + .build(); + + cameraController.takePicture( + outputOptions, + cameraExecutor, + new ImageCapture.OnImageSavedCallback() { + @Override + public void onImageSaved(@NonNull ImageCapture.OutputFileResults outputFileResults) { + Uri savedImageUri = outputFileResults.getSavedUri(); + if (savedImageUri != null) { + InputStream stream = null; + try { + stream = requireContext().getContentResolver().openInputStream(savedImageUri); + if (stream == null) { + Logger.error(TAG, "Failed to open input stream for saved image: " + savedImageUri, null); + showErrorToast("Failed to process captured image"); + return; + } + + BitmapFactory.Options options = new BitmapFactory.Options(); + options.inPreferredConfig = Bitmap.Config.ARGB_8888; + Bitmap bmp = BitmapFactory.decodeStream(stream, null, options); + + if (bmp == null) { + Logger.error(TAG, "Failed to decode bitmap from saved image: " + savedImageUri, null); + showErrorToast("Failed to process captured image"); + return; + } + + // Process bitmap with quality and size settings + Bitmap processedBmp = processBitmap(bmp, savedImageUri); + if (processedBmp != bmp && bmp != null) { + bmp.recycle(); // Recycle original if it was replaced + } + + addImageToCache(savedImageUri, processedBmp); + + // Generate thumbnail on a background thread to avoid UI jank + if (cameraExecutor != null && !cameraExecutor.isShutdown()) { + cameraExecutor.execute(() -> { + final Bitmap thumbnail = getThumbnail(savedImageUri); + // Update UI on main thread + requireActivity().runOnUiThread(() -> { + if (thumbnailAdapter != null) { + thumbnailAdapter.replaceLoadingThumbnail(savedImageUri, thumbnail); + } + }); + }); + } + } catch (FileNotFoundException e) { + Logger.error(TAG, "File not found for saved image: " + savedImageUri, e); + showErrorToast("Image file not found"); + } catch (OutOfMemoryError e) { + Logger.error(TAG, "Out of memory when processing image: " + savedImageUri, e); + showErrorToast("Not enough memory to process image"); + // Try to recover by clearing the cache + if (imageCache != null) { + imageCache.clear(); + } + System.gc(); // Request garbage collection + } catch (Exception e) { + Logger.error(TAG, "Error processing saved image: " + savedImageUri, e); + showErrorToast("Error processing image"); + } finally { + if (stream != null) { + try { + stream.close(); + } catch (IOException e) { + Logger.error(TAG, "Error closing input stream", e); + } + } + } + } else { + Logger.error(TAG, "Saved image URI is null", null); + showErrorToast("Failed to save image"); + } + } + + @Override + public void onError(@NonNull ImageCaptureException exception) { + int errorCode = exception.getImageCaptureError(); + String errorMessage; + + switch (errorCode) { + case ImageCapture.ERROR_CAMERA_CLOSED: + errorMessage = "Camera was closed during capture"; + break; + case ImageCapture.ERROR_CAPTURE_FAILED: + errorMessage = "Image capture failed"; + break; + case ImageCapture.ERROR_FILE_IO: + errorMessage = "File write operation failed"; + break; + case ImageCapture.ERROR_INVALID_CAMERA: + errorMessage = "Selected camera cannot be found"; + break; + default: + errorMessage = "Unknown error during image capture"; + break; + } + + Logger.error(TAG, "Image capture error: " + errorMessage, exception); + + // Remove any loading thumbnails since capture failed + requireActivity().runOnUiThread(() -> { + if (thumbnailAdapter != null && thumbnailAdapter.hasLoadingThumbnails()) { + thumbnailAdapter.removeLoadingThumbnails(); + } + }); + + showErrorToast(errorMessage); + } + } + ); + } + ); + controlsContainer.addView(takePictureButton); + } + + private void createFlipButtonForLandscape(FragmentActivity fragmentActivity, int margin, ColorStateList buttonColors) { + flipCameraButton = new FloatingActionButton(fragmentActivity); + flipCameraButton.setId(View.generateViewId()); + flipCameraButton.setImageResource(R.drawable.flip_camera_android_24px); + flipCameraButton.setColorFilter(Color.WHITE); + flipCameraButton.setBackgroundTintList(buttonColors); + flipButtonLayoutParams = new RelativeLayout.LayoutParams( + RelativeLayout.LayoutParams.WRAP_CONTENT, + RelativeLayout.LayoutParams.WRAP_CONTENT + ); + // Position at the bottom center of controls container with safe area margins + flipButtonLayoutParams.addRule(RelativeLayout.ALIGN_PARENT_BOTTOM); + flipButtonLayoutParams.addRule(RelativeLayout.CENTER_HORIZONTAL); + + // Use safe area insets to ensure button is not too close to bottom cutouts + int safeBottomMargin = Math.max(margin * 2, safeInsetBottom + margin); + flipButtonLayoutParams.setMargins(0, 0, 0, safeBottomMargin); + flipCameraButton.setLayoutParams(flipButtonLayoutParams); + flipCameraButton.setOnClickListener( + v -> { + v.performHapticFeedback(HapticFeedbackConstants.VIRTUAL_KEY); + + // Clean up any loading thumbnails since camera swap will cancel ongoing captures + if (thumbnailAdapter != null && thumbnailAdapter.hasLoadingThumbnails()) { + thumbnailAdapter.removeLoadingThumbnails(); + showErrorToast("Capture cancelled due to camera switch"); + } + + Logger.debug(TAG, "Switching camera from " + (lensFacing == CameraSelector.LENS_FACING_FRONT ? "FRONT" : "BACK")); + lensFacing = lensFacing == CameraSelector.LENS_FACING_FRONT ? CameraSelector.LENS_FACING_BACK : CameraSelector.LENS_FACING_FRONT; + Logger.debug(TAG, "Switched camera to " + (lensFacing == CameraSelector.LENS_FACING_FRONT ? "FRONT" : "BACK")); + + flashButton.setVisibility(lensFacing == CameraSelector.LENS_FACING_BACK ? View.VISIBLE : View.GONE); + if (!zoomTabs.isEmpty()) { + Logger.debug(TAG, "Clearing " + zoomTabs.size() + " zoom tabs"); + if (zoomTabLayout != null) { + zoomTabLayout.removeAllTabs(); + } + if (verticalZoomContainer != null) { + verticalZoomContainer.removeAllViews(); + } + zoomTabs.clear(); + } + + // Set the camera selector before setting up camera to ensure correct zoom capabilities + CameraSelector cameraSelector = new CameraSelector.Builder().requireLensFacing(lensFacing).build(); + cameraController.setCameraSelector(cameraSelector); + + setupCamera(); + } + ); + controlsContainer.addView(flipCameraButton); + } + + private void createDoneButtonForLandscape(FragmentActivity fragmentActivity, int margin, ColorStateList buttonColors) { + doneButton = new FloatingActionButton(fragmentActivity); + doneButton.setId(View.generateViewId()); + doneButton.setImageResource(R.drawable.done_24px); + doneButton.setColorFilter(Color.WHITE); + doneButton.setBackgroundTintList(buttonColors); + doneButtonLayoutParams = new RelativeLayout.LayoutParams( + RelativeLayout.LayoutParams.WRAP_CONTENT, + RelativeLayout.LayoutParams.WRAP_CONTENT + ); + // Position at the top center of controls container with safe area margins + doneButtonLayoutParams.addRule(RelativeLayout.ALIGN_PARENT_TOP); + doneButtonLayoutParams.addRule(RelativeLayout.CENTER_HORIZONTAL); + + // Use safe area insets to ensure button is not too close to top cutouts + int safeTopMargin = Math.max(margin * 2, safeInsetTop + margin); + doneButtonLayoutParams.setMargins(0, safeTopMargin, 0, 0); + doneButton.setLayoutParams(doneButtonLayoutParams); + doneButton.setOnClickListener( + view -> { + view.performHapticFeedback(HapticFeedbackConstants.VIRTUAL_KEY); + done(); + } + ); + controlsContainer.addView(doneButton); + } + + private void createCloseButtonForLandscape(FragmentActivity fragmentActivity, int margin, ColorStateList buttonColors) { + closeButton = new FloatingActionButton(fragmentActivity); + closeButton.setId(View.generateViewId()); + closeButton.setImageResource(R.drawable.close_24px); + closeButton.setBackgroundTintList(buttonColors); + closeButton.setColorFilter(Color.WHITE); + closeButtonLayoutParams = new RelativeLayout.LayoutParams( + RelativeLayout.LayoutParams.WRAP_CONTENT, + RelativeLayout.LayoutParams.WRAP_CONTENT + ); + // Position at the top left of the preview area with safe area margins + closeButtonLayoutParams.addRule(RelativeLayout.ALIGN_PARENT_TOP); + closeButtonLayoutParams.addRule(RelativeLayout.ALIGN_PARENT_LEFT); + + // Use safe area insets to ensure button is positioned away from cutouts + int safeLeftMargin = Math.max(margin * 2, safeInsetLeft + margin); + int safeTopMargin = Math.max(margin * 2, safeInsetTop + margin); + closeButtonLayoutParams.setMargins(safeLeftMargin, safeTopMargin, 0, 0); + closeButton.setLayoutParams(closeButtonLayoutParams); + closeButton.setOnClickListener( + view -> { + view.performHapticFeedback(HapticFeedbackConstants.VIRTUAL_KEY); + if (imageCache != null && imageCache.size() > 0) { + new AlertDialog.Builder(requireContext()) + .setTitle(CONFIRM_CANCEL_TITLE) + .setMessage(CONFIRM_CANCEL_MESSAGE) + .setPositiveButton(CONFIRM_CANCEL_POSITIVE, (dialogInterface, i) -> cancel()) + .setNegativeButton(CONFIRM_CANCEL_NEGATIVE, (dialogInterface, i) -> dialogInterface.dismiss()) + .create() + .show(); + } else { + cancel(); + } + } + ); + + // Add to the main layout instead of the controls container + relativeLayout.addView(closeButton); + } + + private void createFlashButtonForLandscape(FragmentActivity fragmentActivity, int margin, ColorStateList buttonColors) { + flashButton = new FloatingActionButton(fragmentActivity); + flashButton.setId(View.generateViewId()); + flashButton.setImageResource(R.drawable.flash_auto_24px); + flashButton.setBackgroundTintList(buttonColors); + flashButton.setColorFilter(Color.WHITE); + flashButtonLayoutParams = new RelativeLayout.LayoutParams( + RelativeLayout.LayoutParams.WRAP_CONTENT, + RelativeLayout.LayoutParams.WRAP_CONTENT + ); + // Position at the bottom left of the preview area with safe area margins + flashButtonLayoutParams.addRule(RelativeLayout.ALIGN_PARENT_BOTTOM); + flashButtonLayoutParams.addRule(RelativeLayout.ALIGN_PARENT_LEFT); + + // Use safe area insets to ensure button is positioned away from cutouts + int safeLeftMargin = Math.max(margin * 2, safeInsetLeft + margin); + int safeBottomMargin = Math.max(margin * 2, safeInsetBottom + margin); + flashButtonLayoutParams.setMargins(safeLeftMargin, 0, 0, safeBottomMargin); + flashButton.setLayoutParams(flashButtonLayoutParams); + flashButton.setOnClickListener( + view -> { + view.performHapticFeedback(HapticFeedbackConstants.VIRTUAL_KEY); + flashMode = cameraController.getImageCaptureFlashMode(); + switch (flashMode) { + case ImageCapture.FLASH_MODE_OFF: { + flashMode = ImageCapture.FLASH_MODE_ON; + flashButton.setImageResource(R.drawable.flash_on_24px); + flashButton.setColorFilter(Color.WHITE); + break; + } + case ImageCapture.FLASH_MODE_ON: { + flashMode = ImageCapture.FLASH_MODE_AUTO; + flashButton.setImageResource(R.drawable.flash_auto_24px); + flashButton.setColorFilter(Color.WHITE); + break; + } + case ImageCapture.FLASH_MODE_AUTO: { + flashMode = ImageCapture.FLASH_MODE_OFF; + flashButton.setImageResource(R.drawable.flash_off_24px); + flashButton.setColorFilter(Color.WHITE); + break; + } + default: + throw new IllegalStateException("Unexpected flash mode: " + flashMode); + } + cameraController.setImageCaptureFlashMode(flashMode); + } + ); + + // Add to the main layout instead of the controls container + relativeLayout.addView(flashButton); + } + + private void createFlashButton(FragmentActivity fragmentActivity, int margin, ColorStateList buttonColors) { + flashButton = new FloatingActionButton(fragmentActivity); + flashButton.setId(View.generateViewId()); + flashButton.setImageResource(R.drawable.flash_auto_24px); + flashButton.setBackgroundTintList(buttonColors); + flashButton.setColorFilter(Color.WHITE); + flashButtonLayoutParams = new RelativeLayout.LayoutParams( + RelativeLayout.LayoutParams.WRAP_CONTENT, + RelativeLayout.LayoutParams.WRAP_CONTENT + ); + flashButtonLayoutParams.addRule(RelativeLayout.ALIGN_PARENT_TOP); + flashButtonLayoutParams.addRule(RelativeLayout.ALIGN_PARENT_RIGHT); + int topMargin = (int) (margin * 2.5); + flashButtonLayoutParams.setMargins(0, topMargin, margin, 0); + flashButton.setLayoutParams(flashButtonLayoutParams); + flashButton.setOnClickListener( + view -> { + view.performHapticFeedback(HapticFeedbackConstants.VIRTUAL_KEY); + flashMode = cameraController.getImageCaptureFlashMode(); + switch (flashMode) { + case ImageCapture.FLASH_MODE_OFF: { + flashMode = ImageCapture.FLASH_MODE_ON; + flashButton.setImageResource(R.drawable.flash_on_24px); + flashButton.setColorFilter(Color.WHITE); + break; + } + case ImageCapture.FLASH_MODE_ON: { + flashMode = ImageCapture.FLASH_MODE_AUTO; + flashButton.setImageResource(R.drawable.flash_auto_24px); + flashButton.setColorFilter(Color.WHITE); + break; + } + case ImageCapture.FLASH_MODE_AUTO: { + flashMode = ImageCapture.FLASH_MODE_OFF; + flashButton.setImageResource(R.drawable.flash_off_24px); + flashButton.setColorFilter(Color.WHITE); + break; + } + default: + throw new IllegalStateException("Unexpected flash mode: " + flashMode); + } + cameraController.setImageCaptureFlashMode(flashMode); + } + ); + relativeLayout.addView(flashButton); + } + + /** + * Creates the feedback for capture button + */ + private void emitCaptureFeedback(View view) { + long vibratorTime = 100; + view.performHapticFeedback(HapticFeedbackConstants.VIRTUAL_KEY); + + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + vibrator.vibrate(VibrationEffect.createOneShot(vibratorTime, VibrationEffect.DEFAULT_AMPLITUDE)); + } else { + vibrator.vibrate(vibratorTime); + } + } + + private void createTakePictureButton(FragmentActivity fragmentActivity, int margin, ColorStateList buttonColors) { + takePictureButton = new FloatingActionButton(fragmentActivity); + takePictureButton.setId(View.generateViewId()); + takePictureButton.setImageResource(R.drawable.ic_shutter_circle); + takePictureButton.setBackgroundColor(Color.TRANSPARENT); + takePictureButton.setBackgroundTintList(buttonColors); + + int fabSize = dpToPx(fragmentActivity, 84); + int iconSize = (int) (fabSize * 0.9); + takePictureButton.setCustomSize(fabSize); + takePictureButton.setMaxImageSize(iconSize); + + takePictureLayoutParams = new RelativeLayout.LayoutParams(RelativeLayout.LayoutParams.WRAP_CONTENT, RelativeLayout.LayoutParams.WRAP_CONTENT); + takePictureLayoutParams.addRule(RelativeLayout.CENTER_IN_PARENT); + takePictureButton.setLayoutParams(takePictureLayoutParams); + takePictureButton.setStateListAnimator(android.animation.AnimatorInflater.loadStateListAnimator(fragmentActivity, R.animator.button_press_animation)); + takePictureButton.setOnClickListener( + v -> { + + emitCaptureFeedback(v); + +// mediaActionSound.play(MediaActionSound.SHUTTER_CLICK); + + // Add loading thumbnail immediately for visual feedback + if (thumbnailAdapter != null) { + thumbnailAdapter.addLoadingThumbnail(); + // Scroll to show the new loading thumbnail + if (filmstripView != null) { + filmstripView.scrollToPosition(thumbnailAdapter.getItemCount() - 1); + } + } + + var name = new SimpleDateFormat(FILENAME, Locale.US).format(System.currentTimeMillis()); + var contentValues = new ContentValues(); + contentValues.put(MediaStore.MediaColumns.DISPLAY_NAME, name); + contentValues.put(MediaStore.MediaColumns.MIME_TYPE, PHOTO_TYPE); + var outputOptions = new ImageCapture.OutputFileOptions.Builder( + requireContext().getContentResolver(), + MediaStore.Images.Media.EXTERNAL_CONTENT_URI, + contentValues + ) + .build(); + + cameraController.takePicture( + outputOptions, + cameraExecutor, + new ImageCapture.OnImageSavedCallback() { + @Override + public void onImageSaved(@NonNull ImageCapture.OutputFileResults outputFileResults) { + Uri savedImageUri = outputFileResults.getSavedUri(); + if (savedImageUri != null) { + InputStream stream = null; + try { + stream = requireContext().getContentResolver().openInputStream(savedImageUri); + if (stream == null) { + Logger.error(TAG, "Failed to open input stream for saved image: " + savedImageUri, null); + showErrorToast("Failed to process captured image"); + return; + } + + BitmapFactory.Options options = new BitmapFactory.Options(); + options.inPreferredConfig = Bitmap.Config.ARGB_8888; + Bitmap bmp = BitmapFactory.decodeStream(stream, null, options); + + if (bmp == null) { + Logger.error(TAG, "Failed to decode bitmap from saved image: " + savedImageUri, null); + showErrorToast("Failed to process captured image"); + return; + } + + // Process bitmap with quality and size settings + Bitmap processedBmp = processBitmap(bmp, savedImageUri); + if (processedBmp != bmp && bmp != null) { + bmp.recycle(); // Recycle original if it was replaced + } + + addImageToCache(savedImageUri, processedBmp); + + // Generate thumbnail on a background thread to avoid UI jank + if (cameraExecutor != null && !cameraExecutor.isShutdown()) { + cameraExecutor.execute(() -> { + final Bitmap thumbnail = getThumbnail(savedImageUri); + // Update UI on main thread + requireActivity().runOnUiThread(() -> { + if (thumbnailAdapter != null) { + thumbnailAdapter.replaceLoadingThumbnail(savedImageUri, thumbnail); + } + }); + }); + } + } catch (FileNotFoundException e) { + Logger.error(TAG, "File not found for saved image: " + savedImageUri, e); + showErrorToast("Image file not found"); + } catch (OutOfMemoryError e) { + Logger.error(TAG, "Out of memory when processing image: " + savedImageUri, e); + showErrorToast("Not enough memory to process image"); + // Try to recover by clearing the cache + if (imageCache != null) { + imageCache.clear(); + } + System.gc(); // Request garbage collection + } catch (Exception e) { + Logger.error(TAG, "Error processing saved image: " + savedImageUri, e); + showErrorToast("Error processing image"); + } finally { + if (stream != null) { + try { + stream.close(); + } catch (IOException e) { + Logger.error(TAG, "Error closing input stream", e); + } + } + } + } else { + Logger.error(TAG, "Saved image URI is null", null); + showErrorToast("Failed to save image"); + } + } + + @Override + public void onError(@NonNull ImageCaptureException exception) { + int errorCode = exception.getImageCaptureError(); + String errorMessage; + + switch (errorCode) { + case ImageCapture.ERROR_CAMERA_CLOSED: + errorMessage = "Camera was closed during capture"; + break; + case ImageCapture.ERROR_CAPTURE_FAILED: + errorMessage = "Image capture failed"; + break; + case ImageCapture.ERROR_FILE_IO: + errorMessage = "File write operation failed"; + break; + case ImageCapture.ERROR_INVALID_CAMERA: + errorMessage = "Selected camera cannot be found"; + break; + default: + errorMessage = "Unknown error during image capture"; + break; + } + + Logger.error(TAG, "Image capture error: " + errorMessage, exception); + + // Remove any loading thumbnails since capture failed + requireActivity().runOnUiThread(() -> { + if (thumbnailAdapter != null && thumbnailAdapter.hasLoadingThumbnails()) { + thumbnailAdapter.removeLoadingThumbnails(); + } + }); + + showErrorToast(errorMessage); + } + } + ); + } + ); + bottomBar.addView(takePictureButton); + } + + private void createFlipButton(FragmentActivity fragmentActivity, int margin, ColorStateList buttonColors) { + flipCameraButton = new FloatingActionButton(fragmentActivity); + flipCameraButton.setId(View.generateViewId()); + flipCameraButton.setImageResource(R.drawable.flip_camera_android_24px); + flipCameraButton.setColorFilter(Color.WHITE); + flipCameraButton.setBackgroundTintList(buttonColors); + flipButtonLayoutParams = new RelativeLayout.LayoutParams( + RelativeLayout.LayoutParams.WRAP_CONTENT, + RelativeLayout.LayoutParams.WRAP_CONTENT + ); + flipButtonLayoutParams.addRule(RelativeLayout.ALIGN_PARENT_START); + flipButtonLayoutParams.addRule(RelativeLayout.CENTER_VERTICAL); + flipButtonLayoutParams.setMargins(margin, 0, 0, 0); + flipCameraButton.setLayoutParams(flipButtonLayoutParams); + flipCameraButton.setOnClickListener( + v -> { + v.performHapticFeedback(HapticFeedbackConstants.VIRTUAL_KEY); + + // Clean up any loading thumbnails since camera swap will cancel ongoing captures + if (thumbnailAdapter != null && thumbnailAdapter.hasLoadingThumbnails()) { + thumbnailAdapter.removeLoadingThumbnails(); + showErrorToast("Capture cancelled due to camera switch"); + } + + Logger.debug(TAG, "Switching camera from " + (lensFacing == CameraSelector.LENS_FACING_FRONT ? "FRONT" : "BACK")); + lensFacing = lensFacing == CameraSelector.LENS_FACING_FRONT ? CameraSelector.LENS_FACING_BACK : CameraSelector.LENS_FACING_FRONT; + + Logger.debug(TAG, "Switched camera to " + (lensFacing == CameraSelector.LENS_FACING_FRONT ? "FRONT" : "BACK")); + + flashButton.setVisibility(lensFacing == CameraSelector.LENS_FACING_BACK ? View.VISIBLE : View.GONE); + if (!zoomTabs.isEmpty()) { + Logger.debug(TAG, "Clearing " + zoomTabs.size() + " zoom tabs"); + if (zoomTabLayout != null) { + zoomTabLayout.removeAllTabs(); + } + if (verticalZoomContainer != null) { + verticalZoomContainer.removeAllViews(); + } + zoomTabs.clear(); + } + + // Set the camera selector before setting up camera to ensure correct zoom capabilities + CameraSelector cameraSelector = new CameraSelector.Builder().requireLensFacing(lensFacing).build(); + cameraController.setCameraSelector(cameraSelector); + + setupCamera(); + } + ); + bottomBar.addView(flipCameraButton); + } + + /** + * Creates a container for camera controls in landscape mode + */ + private void createControlsContainerForLandscape(FragmentActivity fragmentActivity, ColorStateList buttonColors, int margin) { + // Create a container for controls on the right side + controlsContainer = new RelativeLayout(fragmentActivity); + controlsContainer.setId(View.generateViewId()); + controlsContainer.setBackgroundColor(Color.BLACK); + + // Calculate safe control margin to avoid camera cutouts + int safeMargin = getSafeControlMargin(margin); + + // Set the container to take up the right portion of the screen, accounting for safe area + // Base width is 20% of screen, but we add safe area insets to ensure controls are visible + int baseContainerWidth = (int) (displayMetrics.widthPixels * 0.2); + int containerWidth = Math.max(baseContainerWidth, safeInsetRight + dpToPx(fragmentActivity, 80)); // 80dp min for controls + + RelativeLayout.LayoutParams containerParams = new RelativeLayout.LayoutParams( + containerWidth, + RelativeLayout.LayoutParams.MATCH_PARENT + ); + containerParams.addRule(RelativeLayout.ALIGN_PARENT_RIGHT); + + // Add top and bottom margins to account for safe area insets + containerParams.setMargins(0, safeInsetTop, 0, safeInsetBottom); + + controlsContainer.setLayoutParams(containerParams); + + // Add the container to the main layout + relativeLayout.addView(controlsContainer); + + // First remove any existing layout rules from the preview view + RelativeLayout.LayoutParams previewParams = (RelativeLayout.LayoutParams) previewView.getLayoutParams(); + previewParams.width = displayMetrics.widthPixels - containerWidth; + previewParams.height = RelativeLayout.LayoutParams.MATCH_PARENT; + + // Clear any existing rules that might be interfering + previewParams.removeRule(RelativeLayout.ABOVE); + previewParams.removeRule(RelativeLayout.BELOW); + previewParams.removeRule(RelativeLayout.RIGHT_OF); + previewParams.removeRule(RelativeLayout.LEFT_OF); + previewParams.removeRule(RelativeLayout.CENTER_IN_PARENT); + previewParams.removeRule(RelativeLayout.CENTER_HORIZONTAL); + previewParams.removeRule(RelativeLayout.CENTER_VERTICAL); + + // Set the correct rules for landscape mode + previewParams.addRule(RelativeLayout.ALIGN_PARENT_LEFT); + previewParams.addRule(RelativeLayout.ALIGN_PARENT_TOP); + previewParams.addRule(RelativeLayout.ALIGN_PARENT_BOTTOM); + previewParams.addRule(RelativeLayout.LEFT_OF, controlsContainer.getId()); + + // Add left margin to account for safe area insets on the left side + previewParams.setMargins(safeInsetLeft, 0, 0, 0); + previewView.setLayoutParams(previewParams); + + // Force the scale type to FILL_CENTER to ensure it fills the available space + previewView.setScaleType(PreviewView.ScaleType.FILL_CENTER); + + // Ensure the preview view has a black background to prevent transparency + previewView.setBackgroundColor(Color.BLACK); + + // Create camera control buttons for landscape mode that go in the right container + createTakePictureButtonForLandscape(fragmentActivity, safeMargin, buttonColors); + createFlipButtonForLandscape(fragmentActivity, safeMargin, buttonColors); + createDoneButtonForLandscape(fragmentActivity, safeMargin, buttonColors); + + // Create buttons that go directly on the main layout (left side) + createCloseButtonForLandscape(fragmentActivity, safeMargin, buttonColors); + createFlashButtonForLandscape(fragmentActivity, safeMargin, buttonColors); + + // Create zoom tabs for landscape mode + createZoomTabLayoutForLandscape(fragmentActivity, safeMargin); + + // Create filmstrip for landscape mode + createFilmstripViewForLandscape(fragmentActivity); + } + + @SuppressLint("ClickableViewAccessibility") + private void createPreviewView(FragmentActivity fragmentActivity) { + previewView = new PreviewView(fragmentActivity); + previewView.setId(View.generateViewId()); + + RelativeLayout.LayoutParams previewLayoutParams; + + if (isLandscape) { + // In landscape mode, explicitly set width to account for controls container + int containerWidth = (int) (displayMetrics.widthPixels * 0.2); + previewLayoutParams = new RelativeLayout.LayoutParams( + RelativeLayout.LayoutParams.MATCH_PARENT, + ViewGroup.LayoutParams.MATCH_PARENT + ); + // Initially set to match_parent, we'll adjust the width after the controls container is created + previewLayoutParams.addRule(RelativeLayout.ALIGN_PARENT_LEFT); + previewLayoutParams.addRule(RelativeLayout.ALIGN_PARENT_TOP); + previewLayoutParams.addRule(RelativeLayout.ALIGN_PARENT_BOTTOM); + previewLayoutParams.setMargins(0, 0, 0, 0); + } else { + // In portrait mode, use match_parent for width + previewLayoutParams = new RelativeLayout.LayoutParams( + ViewGroup.LayoutParams.MATCH_PARENT, + ViewGroup.LayoutParams.MATCH_PARENT + ); + // We'll set the ABOVE rule after bottomBar is created + } + + previewView.setLayoutParams(previewLayoutParams); + + // Set background color to black to prevent transparency + previewView.setBackgroundColor(Color.BLACK); + + // Use FILL_CENTER for both orientations to ensure the preview fills the available space + previewView.setScaleType(PreviewView.ScaleType.FILL_CENTER); + + previewView.setOnTouchListener( + (v, event) -> { + if (focusIndicator != null) { + // Position the focus indicator at the touch point + focusIndicator.setX(event.getX() - (focusIndicator.getWidth() / 2f)); + focusIndicator.setY(event.getY() - (focusIndicator.getHeight() / 2f)); + } + + // Let the PreviewView handle the rest of the touch event. + // Returning false allows the default tap-to-focus behavior to trigger. + return false; + } + ); + + relativeLayout.addView(previewView); + } + + private void createFocusIndicator(Context context) { + focusIndicator = new ImageView(context); + focusIndicator.setImageResource(R.drawable.center_focus_24px); + + int size = dpToPx(context, 72); + RelativeLayout.LayoutParams layoutParams = new RelativeLayout.LayoutParams(size, size); + focusIndicator.setLayoutParams(layoutParams); + + focusIndicator.setColorFilter(Color.WHITE); + focusIndicator.setVisibility(View.INVISIBLE); // Initially hidden + + relativeLayout.addView(focusIndicator); + } + + private void createZoomTabLayout(FragmentActivity fragmentActivity, int margin) { + zoomTabCardView = new CardView(fragmentActivity); + zoomTabCardView.setId(View.generateViewId()); + + // Make the card view rounded corners + GradientDrawable backgroundDrawable = new GradientDrawable(); + backgroundDrawable.setShape(GradientDrawable.RECTANGLE); + backgroundDrawable.setColor(ZOOM_TAB_LAYOUT_BACKGROUND_COLOR); + backgroundDrawable.setCornerRadius(dpToPx(requireContext(), 56 / 2)); + zoomTabCardView.setBackground(backgroundDrawable); + + // Define the LayoutParams for the cardView + cardViewLayoutParams = new RelativeLayout.LayoutParams( + RelativeLayout.LayoutParams.WRAP_CONTENT, + RelativeLayout.LayoutParams.WRAP_CONTENT + ); + cardViewLayoutParams.addRule(RelativeLayout.ABOVE, bottomBar.getId()); + cardViewLayoutParams.addRule(RelativeLayout.CENTER_HORIZONTAL); + cardViewLayoutParams.setMargins(margin, margin, margin, margin); // Adjust bottom margin as needed + zoomTabCardView.setLayoutParams(cardViewLayoutParams); + + relativeLayout.addView(zoomTabCardView); + + zoomTabLayout = new TabLayout(fragmentActivity); + zoomTabLayout.setId(View.generateViewId()); + tabLayoutParams = new RelativeLayout.LayoutParams(RelativeLayout.LayoutParams.MATCH_PARENT, RelativeLayout.LayoutParams.WRAP_CONTENT); + zoomTabLayout.setLayoutParams(tabLayoutParams); + + // Set TabLayout parameters + zoomTabLayout.setTabGravity(TabLayout.GRAVITY_FILL); + zoomTabLayout.setTabMode(TabLayout.MODE_FIXED); + zoomTabLayout.setSelectedTabIndicatorColor(Color.TRANSPARENT); // No indicator color + zoomTabLayout.setSelectedTabIndicator(null); + zoomTabLayout.setBackgroundColor(Color.TRANSPARENT); // Transparent background to let card view color show + zoomTabLayout.setBackground(null); + + // Set the listener for tab selection to change the text color and background + zoomTabLayout.addOnTabSelectedListener( + new TabLayout.OnTabSelectedListener() { + @Override + public void onTabSelected(TabLayout.Tab tab) { + ZoomTab zoomTab = zoomTabs.get(tab.getPosition()); + zoomTab.setSelected(true); + + if (!isSnappingZoom.get()) { + zoomTab.setTransientZoomLevel(null); + if (cameraController != null) { + float zoolLavel = zoomTab.getZoomLevel(); + cameraController.setZoomRatio(zoolLavel); + Log.d(TAG, "onTabSelected: " + zoolLavel); + } + } + } + + @Override + public void onTabUnselected(TabLayout.Tab tab) { + ZoomTab zoomTab = zoomTabs.get(tab.getPosition()); + zoomTab.setSelected(false); + zoomTab.setTransientZoomLevel(null); + } + + @Override + public void onTabReselected(TabLayout.Tab tab) { + ZoomTab zoomTab = zoomTabs.get(tab.getPosition()); + zoomTab.setSelected(true); + if (!isSnappingZoom.get()) { + zoomTab.setTransientZoomLevel(null); + if (cameraController != null) { + cameraController.setZoomRatio(zoomTab.getZoomLevel()); + } + } + } + } + ); + + zoomTabCardView.addView(zoomTabLayout); + + // Use ViewTreeObserver to ensure zoom tab layout is properly laid out + zoomTabCardViewListener = new ViewTreeObserver.OnGlobalLayoutListener() { + @Override + public void onGlobalLayout() { + // Remove the listener to prevent multiple callbacks + zoomTabCardView.getViewTreeObserver().removeOnGlobalLayoutListener(this); + zoomTabCardViewListener = null; + + // Ensure the tab layout has the correct width + if (zoomTabLayout.getWidth() > 0) { + // Distribute tab width evenly + TabLayout.Tab tab = zoomTabLayout.getTabAt(0); + if (tab != null && tab.view != null) { + int tabWidth = zoomTabLayout.getWidth() / zoomTabLayout.getTabCount(); + for (int i = 0; i < zoomTabLayout.getTabCount(); i++) { + TabLayout.Tab currentTab = zoomTabLayout.getTabAt(i); + if (currentTab != null && currentTab.view != null) { + ViewGroup.LayoutParams layoutParams = currentTab.view.getLayoutParams(); + layoutParams.width = tabWidth; + currentTab.view.setLayoutParams(layoutParams); + } + } + } + } + } + }; + zoomTabCardView.getViewTreeObserver().addOnGlobalLayoutListener(zoomTabCardViewListener); + } + + private void createZoomTabLayoutForLandscape(FragmentActivity fragmentActivity, int margin) { + zoomTabCardView = new CardView(fragmentActivity); + zoomTabCardView.setId(View.generateViewId()); + + // Make the card view rounded corners + GradientDrawable backgroundDrawable = new GradientDrawable(); + backgroundDrawable.setShape(GradientDrawable.RECTANGLE); + backgroundDrawable.setColor(ZOOM_TAB_LAYOUT_BACKGROUND_COLOR); + backgroundDrawable.setCornerRadius(dpToPx(requireContext(), 56 / 2)); + zoomTabCardView.setBackground(backgroundDrawable); + + // Define the LayoutParams for the cardView in landscape mode + cardViewLayoutParams = new RelativeLayout.LayoutParams( + RelativeLayout.LayoutParams.WRAP_CONTENT, + RelativeLayout.LayoutParams.WRAP_CONTENT + ); + // Position it to the left of the controls container, centered vertically in the preview area + cardViewLayoutParams.addRule(RelativeLayout.LEFT_OF, controlsContainer.getId()); + cardViewLayoutParams.addRule(RelativeLayout.CENTER_VERTICAL); + cardViewLayoutParams.setMargins(margin, margin, margin, margin); + zoomTabCardView.setLayoutParams(cardViewLayoutParams); + + // Add to the main layout (preview area) instead of the controls container + relativeLayout.addView(zoomTabCardView); + + // Create a LinearLayout with vertical orientation instead of TabLayout for landscape mode + LinearLayout verticalZoomContainer = new LinearLayout(fragmentActivity); + verticalZoomContainer.setId(View.generateViewId()); + verticalZoomContainer.setOrientation(LinearLayout.VERTICAL); + verticalZoomContainer.setGravity(Gravity.CENTER); + + // Use WRAP_CONTENT for both width and height to make the container compact + LinearLayout.LayoutParams containerParams = new LinearLayout.LayoutParams( + LinearLayout.LayoutParams.WRAP_CONTENT, + LinearLayout.LayoutParams.WRAP_CONTENT + ); + verticalZoomContainer.setLayoutParams(containerParams); + + // Add padding between buttons + int buttonSpacing = dpToPx(fragmentActivity, 8); + verticalZoomContainer.setPadding(buttonSpacing, buttonSpacing, buttonSpacing, buttonSpacing); + + zoomTabCardView.addView(verticalZoomContainer); + + // Store the vertical container for use in createZoomTabsForLandscape + zoomTabLayout = null; // We're not using TabLayout in landscape mode + this.verticalZoomContainer = verticalZoomContainer; + } + + private void createZoomTabs(FragmentActivity fragmentActivity, TabLayout tabLayout) { + // This method is for portrait mode with TabLayout + createZoomTabsInternal(fragmentActivity, tabLayout, null); + } + + private void createZoomTabsForLandscape(FragmentActivity fragmentActivity, LinearLayout verticalContainer) { + // This method is for landscape mode with vertical LinearLayout + createZoomTabsInternal(fragmentActivity, null, verticalContainer); + } + + private void createZoomTabsInternal(FragmentActivity fragmentActivity, TabLayout tabLayout, LinearLayout verticalContainer) { + float[] zoomLevels; + + Logger.debug(TAG, "Creating zoom tabs for camera facing: " + (lensFacing == CameraSelector.LENS_FACING_FRONT ? "FRONT" : "BACK") + + ", minZoom: " + minZoom + ", maxZoom: " + maxZoom); + + + // For front camera, don't include ultra-wide (minZoom like 0.6x) as it's not useful + if (lensFacing == CameraSelector.LENS_FACING_FRONT) { + zoomLevels = new float[]{1f, 2f}; + } else { + // For back camera, include minZoom (like 0.6x ultra-wide) if it's less than 1f + if (minZoom < 1f) { + zoomLevels = new float[]{minZoom, 1f, 2f, 5f}; + } else { + zoomLevels = new float[]{1f, 2f, 5f}; + } + } + + Logger.debug(TAG, "Zoom levels to create: " + java.util.Arrays.toString(zoomLevels)); + + int selectedTabIndex = -1; + for (int i = 0; i < zoomLevels.length; i++) { + float zoomLevel = zoomLevels[i]; + // Skip zoom levels that exceed the maximum supported zoom + if (zoomLevel > maxZoom) { + Logger.debug(TAG, "Skipping zoom level " + zoomLevel + " because it exceeds maxZoom " + maxZoom); + continue; + } + + // Use smaller circle size for landscape mode + int circleSize = (verticalContainer != null) ? 32 : 40; + ZoomTab zoomTab = new ZoomTab(fragmentActivity, zoomLevel, circleSize, i); + zoomTabs.add(zoomTab); + + if (tabLayout != null) { + // Portrait mode - add to TabLayout + TabLayout.Tab tab = tabLayout.newTab(); + tab.setCustomView(zoomTab.getView()); + tabLayout.addTab(tab); + } else if (verticalContainer != null) { + // Landscape mode - add to vertical LinearLayout + View zoomView = zoomTab.getView(); + + // Create layout params with margin for vertical spacing + LinearLayout.LayoutParams params = new LinearLayout.LayoutParams( + LinearLayout.LayoutParams.WRAP_CONTENT, + LinearLayout.LayoutParams.WRAP_CONTENT + ); + int margin = dpToPx(fragmentActivity, 4); + params.setMargins(0, margin, 0, margin); + zoomView.setLayoutParams(params); + + // Add click listener for landscape mode + final int finalIndex = zoomTabs.size() - 1; + zoomView.setOnClickListener(v -> { + // Unselect all tabs + for (ZoomTab tab : zoomTabs) { + tab.setSelected(false); + } + // Select this tab + zoomTab.setSelected(true); + // Set zoom level + if (!isSnappingZoom.get()) { + zoomTab.setTransientZoomLevel(null); + if (cameraController != null) { + cameraController.setZoomRatio(zoomTab.getZoomLevel()); + + } + } + }); + + verticalContainer.addView(zoomView); + } + + // Track which should be the default selected tab (1x zoom) + if (Math.abs(zoomLevel - 1f) < 0.01f) { + selectedTabIndex = zoomTabs.size() - 1; + } + } + + // Select the 1x zoom tab + if (selectedTabIndex >= 0) { + if (tabLayout != null && selectedTabIndex < tabLayout.getTabCount()) { + tabLayout.selectTab(tabLayout.getTabAt(selectedTabIndex)); + } else if (verticalContainer != null && selectedTabIndex < zoomTabs.size()) { + // For landscape mode, manually select the 1x zoom tab + zoomTabs.get(selectedTabIndex).setSelected(true); + } + } + } + + private void createFilmstripView(FragmentActivity fragmentActivity) { + filmstripView = new RecyclerView(fragmentActivity); + RelativeLayout.LayoutParams filmstripLayoutParams = new RelativeLayout.LayoutParams( + RelativeLayout.LayoutParams.MATCH_PARENT, + RelativeLayout.LayoutParams.WRAP_CONTENT + ); + filmstripLayoutParams.addRule(RelativeLayout.CENTER_HORIZONTAL); + filmstripLayoutParams.addRule(RelativeLayout.ABOVE, zoomTabCardView.getId()); + filmstripView.setLayoutParams(filmstripLayoutParams); + + // Add padding to the filmstrip to prevent clipping of the remove button + int padding = dpToPx(fragmentActivity, 12); + filmstripView.setPadding(padding, padding, padding, padding); + filmstripView.setClipToPadding(false); + + LinearLayoutManager layoutManager = new LinearLayoutManager(fragmentActivity, LinearLayoutManager.HORIZONTAL, false); + filmstripView.setLayoutManager(layoutManager); + + // If we already have an adapter with thumbnails, preserve them + ThumbnailAdapter existingAdapter = thumbnailAdapter; + thumbnailAdapter = new ThumbnailAdapter(); + + // If we had an existing adapter with thumbnails, transfer them to the new adapter + if (existingAdapter != null && existingAdapter.getItemCount() > 0) { + for (int i = 0; i < existingAdapter.getItemCount(); i++) { + ThumbnailAdapter.ThumbnailItem item = existingAdapter.getThumbnailItem(i); + if (item != null) { + if (!item.isLoading()) { + thumbnailAdapter.addThumbnail(item.getUri(), item.getBitmap()); + } + } + } + } + filmstripView.setAdapter(thumbnailAdapter); + relativeLayout.addView(filmstripView); + + // Use ViewTreeObserver to ensure filmstrip is properly laid out + filmstripViewListener = new ViewTreeObserver.OnGlobalLayoutListener() { + @Override + public void onGlobalLayout() { + // Remove the listener to prevent multiple callbacks + filmstripView.getViewTreeObserver().removeOnGlobalLayoutListener(this); + filmstripViewListener = null; + + // Scroll to the end of the filmstrip to show the most recent thumbnails + if (thumbnailAdapter.getItemCount() > 0) { + filmstripView.scrollToPosition(thumbnailAdapter.getItemCount() - 1); + } + } + }; + filmstripView.getViewTreeObserver().addOnGlobalLayoutListener(filmstripViewListener); + + thumbnailAdapter.setOnThumbnailsChangedCallback( + new ThumbnailAdapter.OnThumbnailsChangedCallback() { + @Override + public void onThumbnailRemoved(Uri uri, Bitmap bmp) { + Bitmap bitmap = getImageFromCache(uri); + if (imageCache != null) { + imageCache.remove(uri); + } + + if (!deleteFile(uri)) { + Logger.warn(TAG, "Failed to delete file after thumbnail removal: " + uri); + // Even if deletion fails, we've already removed it from the UI and cache, + // so we don't need to show an error to the user + } + } + } + ); + + // Set click listener for thumbnails to show preview + thumbnailAdapter.setOnThumbnailClickListener(new ThumbnailAdapter.OnThumbnailClickListener() { + @Override + public void onThumbnailClick(Uri uri, Bitmap bitmap) { + showImagePreview(uri); + } + }); + } + + private void createFilmstripViewForLandscape(FragmentActivity fragmentActivity) { + filmstripView = new RecyclerView(fragmentActivity); + RelativeLayout.LayoutParams filmstripLayoutParams = new RelativeLayout.LayoutParams( + RelativeLayout.LayoutParams.MATCH_PARENT, + RelativeLayout.LayoutParams.WRAP_CONTENT + ); + + // Position the filmstrip at the bottom of the preview area, but to the right of the flash button + filmstripLayoutParams.addRule(RelativeLayout.ALIGN_PARENT_BOTTOM); + filmstripLayoutParams.addRule(RelativeLayout.RIGHT_OF, flashButton.getId()); + + // Calculate width to fill the remaining space (minus controls container and some margin for the flash button) + int flashButtonWidth = dpToPx(fragmentActivity, 56); // Approximate width of FAB + int margin = dpToPx(fragmentActivity, 20); + // Calculate width to use more of the available space + // We're keeping the 20% for controls container but reducing other margins + filmstripLayoutParams.width = displayMetrics.widthPixels - + (int) (displayMetrics.widthPixels * 0.2) - // Controls container + flashButtonWidth - // Flash button width + margin; // Reduced margin to allow more thumbnails + + // Add left margin to create space between flash button and filmstrip + filmstripLayoutParams.setMargins(margin, 0, 0, margin); + + filmstripView.setLayoutParams(filmstripLayoutParams); + + // Add padding to the filmstrip - reduced for landscape mode + int padding = dpToPx(fragmentActivity, 6); + filmstripView.setPadding(padding, padding, padding, padding); + filmstripView.setClipToPadding(false); + + LinearLayoutManager layoutManager = new LinearLayoutManager(fragmentActivity, LinearLayoutManager.HORIZONTAL, false); + filmstripView.setLayoutManager(layoutManager); + + // Create a custom adapter with smaller thumbnails for landscape mode + // If we already have an adapter with thumbnails, preserve them + ThumbnailAdapter existingAdapter = thumbnailAdapter; + thumbnailAdapter = new ThumbnailAdapter() { + @Override + public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) { + Context context = parent.getContext(); + int thumbnailSize = dpToPx(context, 60); // Smaller thumbnail size for landscape mode (was 80dp) + int margin = dpToPx(context, 3); // Smaller margin for landscape mode (was 4dp) + + FrameLayout frameLayout = new FrameLayout(context); + FrameLayout.LayoutParams layoutParams = new FrameLayout.LayoutParams(thumbnailSize, thumbnailSize); + layoutParams.setMargins(margin, margin, margin, margin); + frameLayout.setLayoutParams(layoutParams); + + ImageView imageView = new ImageView(context); + imageView.setScaleType(ImageView.ScaleType.CENTER_CROP); + frameLayout.addView(imageView); + + ImageView removeButton = new ImageView(context); + int buttonSize = dpToPx(context, 20); // Smaller remove button (was 24dp) + FrameLayout.LayoutParams buttonParams = new FrameLayout.LayoutParams(buttonSize, buttonSize); + buttonParams.gravity = Gravity.TOP | Gravity.END; + removeButton.setLayoutParams(buttonParams); + removeButton.setImageResource(R.drawable.ic_cancel_white_24dp); + frameLayout.addView(removeButton); + + // Add progress bar for loading state + ProgressBar progressBar = new ProgressBar(context); + int progressSize = dpToPx(context, 24); // Smaller progress bar for landscape mode + FrameLayout.LayoutParams progressParams = new FrameLayout.LayoutParams(progressSize, progressSize); + progressParams.gravity = Gravity.CENTER; + progressBar.setLayoutParams(progressParams); + progressBar.setVisibility(View.GONE); // Initially hidden + frameLayout.addView(progressBar); + + return new ViewHolder(frameLayout, imageView, removeButton, progressBar); + } + }; + + // If we had an existing adapter with thumbnails, transfer them to the new adapter + if (existingAdapter != null && existingAdapter.getItemCount() > 0) { + for (int i = 0; i < existingAdapter.getItemCount(); i++) { + ThumbnailAdapter.ThumbnailItem item = existingAdapter.getThumbnailItem(i); + if (item != null) { + if (!item.isLoading()) { + thumbnailAdapter.addThumbnail(item.getUri(), item.getBitmap()); + } + } + } + } + filmstripView.setAdapter(thumbnailAdapter); + relativeLayout.addView(filmstripView); + + // Use ViewTreeObserver to ensure filmstrip is properly laid out in landscape mode + filmstripViewSecondaryListener = new ViewTreeObserver.OnGlobalLayoutListener() { + @Override + public void onGlobalLayout() { + // Remove the listener to prevent multiple callbacks + filmstripView.getViewTreeObserver().removeOnGlobalLayoutListener(this); + filmstripViewSecondaryListener = null; + + // Scroll to the end of the filmstrip to show the most recent thumbnails + if (thumbnailAdapter.getItemCount() > 0) { + filmstripView.scrollToPosition(thumbnailAdapter.getItemCount() - 1); + } + } + }; + filmstripView.getViewTreeObserver().addOnGlobalLayoutListener(filmstripViewSecondaryListener); + + thumbnailAdapter.setOnThumbnailsChangedCallback( + new ThumbnailAdapter.OnThumbnailsChangedCallback() { + @Override + public void onThumbnailRemoved(Uri uri, Bitmap bmp) { + if (imageCache != null) { + imageCache.remove(uri); + } + + if (!deleteFile(uri)) { + Logger.warn(TAG, "Failed to delete file after thumbnail removal in landscape mode: " + uri); + // Even if deletion fails, we've already removed it from the UI and cache, + // so we don't need to show an error to the user + } + } + } + ); + + // Set click listener for thumbnails to show preview + thumbnailAdapter.setOnThumbnailClickListener(new ThumbnailAdapter.OnThumbnailClickListener() { + @Override + public void onThumbnailClick(Uri uri, Bitmap bitmap) { + showImagePreview(uri); + } + }); + } + + private void createDoneButton(FragmentActivity fragmentActivity, int margin, ColorStateList buttonColors) { + doneButton = new FloatingActionButton(fragmentActivity); + doneButton.setId(View.generateViewId()); + doneButton.setImageResource(R.drawable.done_24px); + doneButton.setColorFilter(Color.WHITE); + doneButton.setBackgroundTintList(buttonColors); + doneButtonLayoutParams = new RelativeLayout.LayoutParams( + RelativeLayout.LayoutParams.WRAP_CONTENT, + RelativeLayout.LayoutParams.WRAP_CONTENT + ); + doneButtonLayoutParams.addRule(RelativeLayout.ALIGN_PARENT_END); + doneButtonLayoutParams.addRule(RelativeLayout.CENTER_VERTICAL); + doneButtonLayoutParams.setMargins(0, 0, margin, 0); + doneButton.setLayoutParams(doneButtonLayoutParams); + doneButton.setOnClickListener( + view -> { + view.performHapticFeedback(HapticFeedbackConstants.VIRTUAL_KEY); + done(); + } + ); + bottomBar.addView(doneButton); + } + + private void createCloseButton(FragmentActivity fragmentActivity, int margin, ColorStateList buttonColors) { + closeButton = new FloatingActionButton(fragmentActivity); + closeButton.setId(View.generateViewId()); + closeButton.setImageResource(R.drawable.close_24px); + closeButton.setBackgroundTintList(buttonColors); + closeButton.setColorFilter(Color.WHITE); + closeButtonLayoutParams = new RelativeLayout.LayoutParams( + RelativeLayout.LayoutParams.WRAP_CONTENT, + RelativeLayout.LayoutParams.WRAP_CONTENT + ); + closeButtonLayoutParams.addRule(RelativeLayout.ALIGN_PARENT_TOP); + closeButtonLayoutParams.addRule(RelativeLayout.ALIGN_PARENT_LEFT); + // Increase top margin for immersive mode (e.g., 2.5x the original margin) + int topMargin = (int) (margin * 2.5); + closeButtonLayoutParams.setMargins(margin, topMargin, 0, 0); + closeButton.setLayoutParams(closeButtonLayoutParams); + closeButton.setOnClickListener( + view -> { + view.performHapticFeedback(HapticFeedbackConstants.VIRTUAL_KEY); + if (imageCache != null && imageCache.size() > 0) { + new AlertDialog.Builder(requireContext()) + .setTitle(CONFIRM_CANCEL_TITLE) + .setMessage(CONFIRM_CANCEL_MESSAGE) + .setPositiveButton(CONFIRM_CANCEL_POSITIVE, (dialogInterface, i) -> cancel()) + .setNegativeButton(CONFIRM_CANCEL_NEGATIVE, (dialogInterface, i) -> dialogInterface.dismiss()) + .create() + .show(); + } else { + cancel(); + } + } + ); + relativeLayout.addView(closeButton); + } + + /** + * Deletes a file from the device storage + * + * @param fileUri The URI of the file to delete + * @return true if deletion was successful, false otherwise + */ + private boolean deleteFile(Uri fileUri) { + if (fileUri == null) { + Logger.error(TAG, "Cannot delete null URI", null); + return false; + } + + try { + ContentResolver contentResolver = requireContext().getContentResolver(); + int deleted = contentResolver.delete(fileUri, null, null); + + if (deleted == 0) { + // File deletion failed + Logger.error(TAG, "Failed to delete file: " + fileUri, null); + return false; + } else { + // File deletion successful + Logger.info(TAG, "File deleted: " + fileUri); + return true; + } + } catch (SecurityException e) { + // Handle permission issues + Logger.error(TAG, "Security exception when deleting file: " + fileUri, e); + showErrorToast("Permission denied to delete image"); + return false; + } catch (IllegalArgumentException e) { + // Handle invalid URI + Logger.error(TAG, "Invalid URI when deleting file: " + fileUri, e); + return false; + } catch (Exception e) { + // Handle any other exceptions + Logger.error(TAG, "Error deleting file: " + fileUri, e); + return false; + } + } + + /** + * Shows a full-screen preview of the image when a thumbnail is clicked + * + * @param uri The URI of the image to display in the preview + */ + private void showImagePreview(Uri uri) { + if (thumbnailAdapter == null) { + // Fallback to single image preview if no adapter + ImagePreviewFragment previewFragment = ImagePreviewFragment.newInstance(uri); + previewFragment.show(requireActivity().getSupportFragmentManager(), "image_preview"); + return; + } + + // Gather all non-loading image URIs and find the current position + List imageUris = new ArrayList<>(); + int currentPosition = 0; + + for (int i = 0; i < thumbnailAdapter.getItemCount(); i++) { + ThumbnailAdapter.ThumbnailItem item = thumbnailAdapter.getThumbnailItem(i); + if (item != null && !item.isLoading()) { + if (uri.equals(item.getUri())) { + currentPosition = imageUris.size(); + } + imageUris.add(item.getUri()); + } + } + + if (imageUris.isEmpty()) { + // Fallback to single image preview if no images found + ImagePreviewFragment previewFragment = ImagePreviewFragment.newInstance(uri); + previewFragment.show(requireActivity().getSupportFragmentManager(), "image_preview"); + return; + } + + // Show preview with swipe navigation + ImagePreviewFragment previewFragment = ImagePreviewFragment.newInstance(imageUris, currentPosition); + previewFragment.show(requireActivity().getSupportFragmentManager(), "image_preview"); + } + + private void setupCamera() throws IllegalStateException { + cameraController + .getInitializationFuture() + .addListener(() -> { + if (!hasFrontFacingCamera()) { + flipCameraButton.setVisibility(View.GONE); + } + }, ContextCompat.getMainExecutor(requireContext())); + + cameraController + .getZoomState() + .observe( + requireActivity(), + zoomState -> { + zoomRatio = zoomState; + minZoom = zoomState.getMinZoomRatio(); + maxZoom = zoomState.getMaxZoomRatio(); + + Logger.debug(TAG, "Zoom state changed - minZoom: " + minZoom + ", maxZoom: " + maxZoom + ", current zoom tabs: " + zoomTabs.size()); + + if (zoomTabs.isEmpty()) { + Logger.debug(TAG, "Creating zoom tabs because zoomTabs is empty"); + if (isLandscape && verticalZoomContainer != null) { + createZoomTabsForLandscape(requireActivity(), verticalZoomContainer); + } else if (zoomTabLayout != null) { + createZoomTabs(requireActivity(), zoomTabLayout); + } + } else { + Logger.debug(TAG, "Not creating zoom tabs because zoomTabs is not empty (" + zoomTabs.size() + " tabs exist)"); + } + + if (zoomRunnable != null) { + zoomHandler.removeCallbacks(zoomRunnable); + } + + zoomRunnable = + () -> { + float currentZoom = zoomRatio.getZoomRatio(); + ZoomTab closestTab = null; + final float threshold = 0.05f; // Threshold for considering the next zoom level + + for (int i = 0; i < zoomTabs.size(); i++) { + ZoomTab currentTab = zoomTabs.get(i); + // Check if this is the last tab or if the current zoom is less than the next tab's level minus the threshold + if (i == zoomTabs.size() - 1 || currentZoom < zoomTabs.get(i + 1).zoomLevel - threshold) { + closestTab = currentTab; + break; + } + } + + // If we found a closest tab, update its display and select the tab. + if (closestTab != null) { + closestTab.setTransientZoomLevel(currentZoom); // Update the tab's display to show the current zoom level + + if (isLandscape && verticalZoomContainer != null) { + // For landscape mode with vertical container, manually handle selection + isSnappingZoom.set(true); + // Unselect all tabs + for (ZoomTab tab : zoomTabs) { + tab.setSelected(false); + } + // Select the closest tab + closestTab.setSelected(true); + isSnappingZoom.set(false); + } else if (zoomTabLayout != null) { + // For portrait mode with TabLayout + TabLayout.Tab tab = zoomTabLayout.getTabAt(closestTab.getTabIndex()); + if (tab != null) { + isSnappingZoom.set(true); + zoomTabLayout.selectTab(tab); // This will not trigger the camera zoom change due to the isSnappingZoom flag + isSnappingZoom.set(false); + } + } + } + }; + zoomHandler.post(zoomRunnable); + } + ); + + cameraController + .getTapToFocusState() + .observe( + requireActivity(), + tapToFocusState -> { + if (focusIndicator == null) return; + // Show and animate the focus indicator when focusing starts + if (tapToFocusState == LifecycleCameraController.TAP_TO_FOCUS_STARTED) { + focusIndicator.setVisibility(View.VISIBLE); + focusIndicator.setAlpha(0f); // Start fully transparent + focusIndicator.animate().alpha(1f).setDuration(200).setInterpolator(new AccelerateDecelerateInterpolator()).start(); + } else { + // Fade out and hide the focus indicator when focusing ends, regardless of the result + focusIndicator + .animate() + .alpha(0f) + .setDuration(500) + .setInterpolator(new AccelerateDecelerateInterpolator()) + .withEndAction(() -> focusIndicator.setVisibility(View.INVISIBLE)) + .start(); + } + } + ); + + CameraSelector cameraSelector = new CameraSelector.Builder().requireLensFacing(lensFacing).build(); + cameraController.setCameraSelector(cameraSelector); + cameraController.setPinchToZoomEnabled(true); + cameraController.setTapToFocusEnabled(true); + cameraController.setImageCaptureFlashMode(flashMode); + } + + /** + * Shows an error toast message to the user + * + * @param message The error message to display + */ + private void showErrorToast(String message) { + if (getActivity() != null && !getActivity().isFinishing()) { + requireActivity().runOnUiThread(() -> { + try { + android.widget.Toast.makeText( + requireContext(), + message, + android.widget.Toast.LENGTH_SHORT + ).show(); + } catch (Exception e) { + // Fail silently if we can't show a toast + Logger.error(TAG, "Failed to show error toast: " + message, e); + } + }); + } + } + + private boolean hasFrontFacingCamera() { + if (cameraController != null) { + CameraSelector frontFacing = new CameraSelector.Builder().requireLensFacing(CameraSelector.LENS_FACING_FRONT).build(); + + return cameraController.hasCamera(frontFacing); + } + return false; + } + + /** + * Gets a memory-efficient thumbnail for an image URI using downsampling techniques + * + * @param imageUri The URI of the image to create a thumbnail for + * @return A downsampled bitmap thumbnail or null if creation failed + */ + @SuppressWarnings("deprecation") + private Bitmap getThumbnail(Uri imageUri) { + ContentResolver contentResolver = requireContext().getContentResolver(); + InputStream inputStream = null; + + try { + // Target thumbnail size (smaller than the original implementation to save memory) + int targetWidth = (int) (displayMetrics.widthPixels * 0.2); + int targetHeight = (int) (displayMetrics.heightPixels * 0.2); + + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { + try { + // For Android 10+ use the built-in thumbnail loader with our target size + Size size = new Size(targetWidth, targetHeight); + return contentResolver.loadThumbnail(imageUri, size, null); + } catch (IOException e) { + // Fall back to manual downsampling if the built-in method fails + Logger.warn(TAG, "Failed to load thumbnail with system API, falling back to manual downsampling"); + // Continue to manual downsampling below + } + } + + // Manual downsampling approach for pre-Q devices or as fallback + + // FIRST PASS: Decode bounds only to determine dimensions + BitmapFactory.Options boundsOptions = new BitmapFactory.Options(); + boundsOptions.inJustDecodeBounds = true; // Only decode bounds, not the actual bitmap + + inputStream = contentResolver.openInputStream(imageUri); + BitmapFactory.decodeStream(inputStream, null, boundsOptions); + if (inputStream != null) { + inputStream.close(); + } + + // Calculate optimal inSampleSize for downsampling + int inSampleSize = calculateInSampleSize(boundsOptions, targetWidth, targetHeight); + + // SECOND PASS: Decode with calculated inSampleSize + BitmapFactory.Options decodeOptions = new BitmapFactory.Options(); + decodeOptions.inSampleSize = inSampleSize; + decodeOptions.inPreferredConfig = Bitmap.Config.RGB_565; // Use RGB_565 instead of ARGB_8888 to reduce memory usage by half + + inputStream = contentResolver.openInputStream(imageUri); + Bitmap thumbnail = BitmapFactory.decodeStream(inputStream, null, decodeOptions); + + return thumbnail; + + } catch (Exception e) { + Logger.error(TAG, "Error creating thumbnail", e); + + // Last resort fallback for older devices + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q) { + try { + String[] projection = {MediaStore.Images.Media._ID}; + Cursor cursor = contentResolver.query(imageUri, projection, null, null, null); + + if (cursor != null && cursor.moveToFirst()) { + int idColumn = cursor.getColumnIndexOrThrow(MediaStore.Images.Media._ID); + long imageId = cursor.getLong(idColumn); + cursor.close(); + + return MediaStore.Images.Thumbnails.getThumbnail( + contentResolver, + imageId, + MediaStore.Images.Thumbnails.MINI_KIND, + null + ); + } + if (cursor != null) { + cursor.close(); + } + } catch (Exception ex) { + Logger.error(TAG, "Error in thumbnail fallback", ex); + } + } + + return null; + } finally { + // Ensure streams are always closed + if (inputStream != null) { + try { + inputStream.close(); + } catch (IOException e) { + Logger.error(TAG, "Error closing input stream", e); + } + } + } + } + + /** + * Calculates the optimal inSampleSize value for downsampling + * + * @param options BitmapFactory.Options with outWidth and outHeight set + * @param reqWidth Requested width of the resulting bitmap + * @param reqHeight Requested height of the resulting bitmap + * @return The optimal inSampleSize value (power of 2) + */ + private int calculateInSampleSize(BitmapFactory.Options options, int reqWidth, int reqHeight) { + // Raw height and width of image + final int height = options.outHeight; + final int width = options.outWidth; + int inSampleSize = 1; + + if (height > reqHeight || width > reqWidth) { + final int halfHeight = height / 2; + final int halfWidth = width / 2; + + // Calculate the largest inSampleSize value that is a power of 2 and keeps both + // height and width larger than the requested height and width. + while ((halfHeight / inSampleSize) >= reqHeight && (halfWidth / inSampleSize) >= reqWidth) { + inSampleSize *= 2; + } + } + + return inSampleSize; + } + + public abstract static class OnImagesCapturedCallback { + + public void onCaptureSuccess(HashMap images) { + } + + public void onCaptureCanceled() { + } + } + + public class ZoomTab { + + private final float zoomLevel; + private final int tabIndex; + private final int circleSize; + private final TextView textView; + private final GradientDrawable background; + private Float transientZoomLevel; + + public ZoomTab(Context context, float zoomLevel, int circleSize, int tabIndex) { + this.zoomLevel = zoomLevel; + this.transientZoomLevel = null; + this.textView = new TextView(context); + this.background = new GradientDrawable(); + this.circleSize = circleSize; + + setupTextView(); + this.tabIndex = tabIndex; + } + + private void setupTextView() { + String formattedZoom = getFormattedZoom(); + textView.setGravity(Gravity.CENTER); + textView.setText(formattedZoom); + textView.setTextSize(12); + textView.setBackgroundColor(Color.TRANSPARENT); + + int padding = dpToPx(requireContext(), 8); + textView.setPadding(padding, padding, padding, padding); + + int circlePx = dpToPx(requireContext(), circleSize); + background.setShape(GradientDrawable.OVAL); + background.setSize(circlePx, circlePx); // Make it circular + + textView.setBackground(background); + + ViewGroup.LayoutParams layoutParams = new ViewGroup.LayoutParams( + ViewGroup.LayoutParams.WRAP_CONTENT, + ViewGroup.LayoutParams.WRAP_CONTENT + ); + textView.setLayoutParams(layoutParams); + + setSelected(false); + } + + @NonNull + private String getFormattedZoom() { + String formattedZoom; + + float whichZoom = transientZoomLevel == null ? zoomLevel : transientZoomLevel; + + if (whichZoom < 0) { + formattedZoom = "0x"; // Handle zoom levels less than 0 + } else if (whichZoom < 10) { + DecimalFormat formatLessThanTen = new DecimalFormat("#.#x"); + formattedZoom = formatLessThanTen.format(whichZoom); // At most 1 digit after the decimal point if less than 10 + } else { + DecimalFormat formatTenOrMore = new DecimalFormat("#x"); + formattedZoom = formatTenOrMore.format(whichZoom); // No decimal point if 10 or greater + } + return formattedZoom; + } + + public View getView() { + return textView; + } + + public void setSelected(boolean isSelected) { + textView.setTextColor(isSelected ? Color.BLACK : Color.WHITE); + background.setColor(isSelected ? ZOOM_BUTTON_COLOR_SELECTED : ZOOM_BUTTON_COLOR_UNSELECTED); + } + + public float getZoomLevel() { + return zoomLevel; + } + + public int getTabIndex() { + return tabIndex; + } + + public void setTransientZoomLevel(Float zoomLevel) { + transientZoomLevel = zoomLevel; + updateText(); + } + + private void updateText() { + String formattedZoom = getFormattedZoom(); + textView.setText(formattedZoom); + } + } +} + 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 index d2a52ac468..5401f8b5f8 100644 --- a/camera/android/src/main/java/com/capacitorjs/plugins/camera/CameraPlugin.java +++ b/camera/android/src/main/java/com/capacitorjs/plugins/camera/CameraPlugin.java @@ -18,6 +18,7 @@ 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; @@ -27,6 +28,8 @@ import androidx.activity.result.contract.ActivityResultContracts; import androidx.annotation.NonNull; import androidx.core.content.FileProvider; +import androidx.fragment.app.FragmentTransaction; + import com.getcapacitor.FileUtils; import com.getcapacitor.JSArray; import com.getcapacitor.JSObject; @@ -39,6 +42,7 @@ 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; @@ -48,41 +52,43 @@ import java.io.InputStream; import java.io.OutputStream; import java.util.ArrayList; +import java.util.HashMap; 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 - ), + 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) - } + @Permission(strings = {Manifest.permission.READ_EXTERNAL_STORAGE}, alias = CameraPlugin.READ_EXTERNAL_STORAGE) + } ) public class CameraPlugin extends Plugin { @@ -104,6 +110,7 @@ public class CameraPlugin extends Plugin { 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 static final String CAMERA_CAPTURE_CANCELED_ERROR = "User canceled the camera capture session"; private String imageFileSavePath; private String imageEditedFileSavePath; @@ -152,6 +159,9 @@ private void doShow(PluginCall call) { case CAMERA: showCamera(call); break; + case CAMERA_MULTI: + showMultiCamera(call); + break; case PHOTOS: showPhotos(call); break; @@ -166,21 +176,25 @@ private void showPrompt(final PluginCall call) { List options = new ArrayList<>(); options.add(call.getString("promptLabelPhoto", "From Photos")); options.add(call.getString("promptLabelPicture", "Take Picture")); + options.add(call.getString("promptLabelPicture", "Take Multiple Pictures")); 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) + options, + index -> { + if (index == 0) { + settings.setSource(CameraSource.PHOTOS); + openPhotos(call); + } else if (index == 1) { + settings.setSource(CameraSource.CAMERA); + openCamera(call); + } else if (index == 2) { + settings.setSource(CameraSource.CAMERA_MULTI); + openMultiCamera(call); + } + }, + () -> call.reject(USER_CANCELLED) ); fragment.show(getActivity().getSupportFragmentManager(), "capacitorModalsActionSheet"); } @@ -193,6 +207,14 @@ private void showCamera(final PluginCall call) { openCamera(call); } + private void showMultiCamera(final PluginCall call) { + if (!getContext().getPackageManager().hasSystemFeature(PackageManager.FEATURE_CAMERA_ANY)) { + call.reject(NO_CAMERA_ERROR); + return; + } + openMultiCamera(call); + } + private void showPhotos(final PluginCall call) { openPhotos(call); } @@ -219,9 +241,9 @@ private boolean checkCameraPermissions(PluginCall call) { isFirstRequest = false; String[] aliases; if (needCameraPerms) { - aliases = new String[] { CAMERA, SAVE_GALLERY }; + aliases = new String[]{CAMERA, SAVE_GALLERY}; } else { - aliases = new String[] { SAVE_GALLERY }; + aliases = new String[]{SAVE_GALLERY}; } requestPermissionForAliases(aliases, call, "cameraPermissionsCallback"); return false; @@ -237,15 +259,15 @@ else if (!hasCameraPerms) { /** * Completes the plugin call after a camera permission request * - * @see #getPhoto(PluginCall) * @param call the plugin call + * @see #getPhoto(PluginCall) */ @PermissionCallback private void cameraPermissionsCallback(PluginCall call) { if (call.getMethodName().equals("pickImages")) { openPhotos(call, true); } else { - if (settings.getSource() == CameraSource.CAMERA && getPermissionState(CAMERA) != PermissionState.GRANTED) { + if ((settings.getSource() == CameraSource.CAMERA || settings.getSource() == CameraSource.CAMERA_MULTI) && getPermissionState(CAMERA) != PermissionState.GRANTED) { Logger.debug(getLogTag(), "User denied camera permission: " + getPermissionState(CAMERA).toString()); call.reject(PERMISSION_DENIED_ERROR_CAMERA); return; @@ -256,13 +278,7 @@ private void cameraPermissionsCallback(PluginCall call) { @Override protected void requestPermissionForAliases(@NonNull String[] aliases, @NonNull PluginCall call, @NonNull String callbackName) { - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { - for (int i = 0; i < aliases.length; i++) { - if (aliases[i].equals(SAVE_GALLERY)) { - aliases[i] = PHOTOS; - } - } - } else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) { + 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; @@ -326,13 +342,39 @@ public void openCamera(final PluginCall call) { } } + public void openMultiCamera(final PluginCall call) { + if (checkCameraPermissions(call)) { + final CameraFragment fragment = new CameraFragment(); + // Pass camera settings to fragment, but disable orientation correction by default for multi-camera + settings.setShouldCorrectOrientation(call.getBoolean("correctOrientation", CameraSettings.DEFAULT_CORRECT_ORIENTATION)); + CameraSettings multiCameraSettings = settings; + //multiCameraSettings.setShouldCorrectOrientation(false); // Disable to prevent upside-down images + fragment.setCameraSettings(multiCameraSettings); + fragment.setImagesCapturedCallback(new CameraFragment.OnImagesCapturedCallback() { + @Override + public void onCaptureSuccess(HashMap images) { + returnMultiCameraResult(call, images); + } + + @Override + public void onCaptureCanceled() { + call.reject(CAMERA_CAPTURE_CANCELED_ERROR); + } + }); + + FragmentTransaction transaction = getActivity().getSupportFragmentManager().beginTransaction(); + transaction.add(android.R.id.content, fragment); + transaction.commit(); + } + } + public void openPhotos(final PluginCall call) { openPhotos(call, false); } private ActivityResultLauncher registerActivityResultLauncher( - ActivityResultContract contract, - ActivityResultCallback callback + ActivityResultContract contract, + ActivityResultCallback callback ) { String key = "cap_activity_rq#" + mNextLocalRequestCode.getAndIncrement(); if (bridge.getFragment() != null) { @@ -358,59 +400,59 @@ 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); + 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); } - } catch (SecurityException ex) { - call.reject("SecurityException"); - } - } - ret.put("photos", photos); - call.resolve(ret); + ); + } else { + call.reject(USER_CANCELLED); } - ); - } else { - call.reject(USER_CANCELLED); - } - pickMultipleMedia.unregister(); - } - ); + pickMultipleMedia.unregister(); + } + ); pickMultipleMedia.launch( - new PickVisualMediaRequest.Builder().setMediaType(ActivityResultContracts.PickVisualMedia.ImageOnly.INSTANCE).build() + 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(); - } - ); + 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() + new PickVisualMediaRequest.Builder().setMediaType(ActivityResultContracts.PickVisualMedia.ImageOnly.INSTANCE).build() ); } } catch (ActivityNotFoundException ex) { @@ -558,6 +600,7 @@ private void processEditedImage(PluginCall call, ActivityResult 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 @@ -599,20 +642,15 @@ private File getTempFile(Uri uri) { 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) { + private JSObject createReturnFrom(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; + return null; } // Compress the final image and prepare for output to client ByteArrayOutputStream bitmapOutputStream = new ByteArrayOutputStream(); @@ -620,7 +658,7 @@ private void returnResult(PluginCall call, Bitmap bitmap, Uri u) { if (settings.isAllowEditing() && !isEdited) { editImage(call, u, bitmapOutputStream); - return; + return null; } boolean saveToGallery = call.getBoolean("saveToGallery", CameraSettings.DEFAULT_SAVE_IMAGE_TO_GALLERY); @@ -656,10 +694,10 @@ private void returnResult(PluginCall call, Bitmap bitmap, Uri u) { } } else { String inserted = MediaStore.Images.Media.insertImage( - getContext().getContentResolver(), - fileToSavePath, - fileToSave.getName(), - "" + getContext().getContentResolver(), + fileToSavePath, + fileToSave.getName(), + "" ); if (inserted == null) { @@ -675,15 +713,37 @@ private void returnResult(PluginCall call, Bitmap bitmap, Uri u) { } } + JSObject ret = null; if (settings.getResultType() == CameraResultType.BASE64) { - returnBase64(call, exif, bitmapOutputStream); + ret = returnBase64(call, exif, bitmapOutputStream); } else if (settings.getResultType() == CameraResultType.URI) { - returnFileURI(call, exif, bitmap, u, bitmapOutputStream); + ret = returnFileURI(call, exif, bitmap, u, bitmapOutputStream); + if (ret == null) { + call.reject(UNABLE_TO_PROCESS_IMAGE); + } } else if (settings.getResultType() == CameraResultType.DATAURL) { - returnDataUrl(call, exif, bitmapOutputStream); + ret = returnDataUrl(call, exif, bitmapOutputStream); } else { call.reject(INVALID_RESULT_TYPE_ERROR); } + + return ret; + } + + /** + * After processing the image, return the final result back to the caller. + * + * @param call + * @param bitmap + * @param u + */ + private void returnResult(PluginCall call, Bitmap bitmap, Uri u) { + JSObject ret = createReturnFrom(call, bitmap, u); + if (ret != null) { + call.resolve(ret); + } + ; + // Result returned, clear stored paths and images if (settings.getResultType() != CameraResultType.URI) { deleteImageFile(); @@ -694,6 +754,23 @@ private void returnResult(PluginCall call, Bitmap bitmap, Uri u) { imageEditedFileSavePath = null; } + private void returnMultiCameraResult(PluginCall call, HashMap images) { + settings.setAllowEditing(false); // Editing multiple photos would be cumbersome + + JSObject ret = new JSObject(); + JSArray photos = new JSArray(); + for (Map.Entry image : images.entrySet()) { + JSObject single = createReturnFrom(call, image.getValue(), image.getKey()); + if (single != null) { + photos.put(single); + } + ; + } + ret.put("photos", photos); + + call.resolve(ret); + } + private void deleteImageFile() { if (imageFileSavePath != null && !settings.isSaveToGallery()) { File photoFile = new File(imageFileSavePath); @@ -703,7 +780,7 @@ private void deleteImageFile() { } } - private void returnFileURI(PluginCall call, ExifWrapper exif, Bitmap bitmap, Uri u, ByteArrayOutputStream bitmapOutputStream) { + private JSObject returnFileURI(PluginCall call, ExifWrapper exif, Bitmap bitmap, Uri u, ByteArrayOutputStream bitmapOutputStream) { Uri newUri = getTempImage(u, bitmapOutputStream); exif.copyExif(newUri.getPath()); if (newUri != null) { @@ -713,9 +790,9 @@ private void returnFileURI(PluginCall call, ExifWrapper exif, Bitmap bitmap, Uri ret.put("path", newUri.toString()); ret.put("webPath", FileUtils.getPortablePath(getContext(), bridge.getLocalUrl(), newUri)); ret.put("saved", isSaved); - call.resolve(ret); + return ret; } else { - call.reject(UNABLE_TO_PROCESS_IMAGE); + return null; } } @@ -725,7 +802,8 @@ private Uri getTempImage(Uri u, ByteArrayOutputStream bitmapOutputStream) { try { bis = new ByteArrayInputStream(bitmapOutputStream.toByteArray()); newUri = saveImage(u, bis); - } catch (IOException ex) {} finally { + } catch (IOException ex) { + } finally { if (bis != null) { try { bis.close(); @@ -740,16 +818,19 @@ private Uri getTempImage(Uri u, ByteArrayOutputStream bitmapOutputStream) { /** * 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); - } +// TODO: Add support for correct orientation +// We bypass now: +// 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()); @@ -767,7 +848,7 @@ private Bitmap replaceBitmap(Bitmap bitmap, final Bitmap newBitmap) { return bitmap; } - private void returnDataUrl(PluginCall call, ExifWrapper exif, ByteArrayOutputStream bitmapOutputStream) { + private JSObject returnDataUrl(PluginCall call, ExifWrapper exif, ByteArrayOutputStream bitmapOutputStream) { byte[] byteArray = bitmapOutputStream.toByteArray(); String encoded = Base64.encodeToString(byteArray, Base64.NO_WRAP); @@ -775,10 +856,10 @@ private void returnDataUrl(PluginCall call, ExifWrapper exif, ByteArrayOutputStr data.put("format", "jpeg"); data.put("dataUrl", "data:image/jpeg;base64," + encoded); data.put("exif", exif.toJson()); - call.resolve(data); + return data; } - private void returnBase64(PluginCall call, ExifWrapper exif, ByteArrayOutputStream bitmapOutputStream) { + private JSObject returnBase64(PluginCall call, ExifWrapper exif, ByteArrayOutputStream bitmapOutputStream) { byte[] byteArray = bitmapOutputStream.toByteArray(); String encoded = Base64.encodeToString(byteArray, Base64.NO_WRAP); @@ -786,7 +867,7 @@ private void returnBase64(PluginCall call, ExifWrapper exif, ByteArrayOutputStre data.put("format", "jpeg"); data.put("base64String", encoded); data.put("exif", exif.toJson()); - call.resolve(data); + return data; } @Override @@ -806,14 +887,18 @@ public void requestPermissions(PluginCall call) { if (providedPerms != null) { try { permsList = providedPerms.toList(); - } catch (JSONException e) {} + } catch (JSONException e) { + } } - if (permsList != null && permsList.size() == 1 && (permsList.contains(CAMERA) || permsList.contains(PHOTOS))) { - // the only thing being asked for was the camera so we can just return the current state + 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 { - // we need to ask about gallery so request storage permissions requestPermissionForAlias(SAVE_GALLERY, call, "checkPermissions"); } } @@ -872,9 +957,9 @@ private Intent createEditIntent(Uri origPhotoUri) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { resInfoList = - getContext() - .getPackageManager() - .queryIntentActivities(editIntent, PackageManager.ResolveInfoFlags.of(PackageManager.MATCH_DEFAULT_ONLY)); + getContext() + .getPackageManager() + .queryIntentActivities(editIntent, PackageManager.ResolveInfoFlags.of(PackageManager.MATCH_DEFAULT_ONLY)); } else { resInfoList = legacyQueryIntentActivities(editIntent); } 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 index 2624c6b397..b632d43b31 100644 --- a/camera/android/src/main/java/com/capacitorjs/plugins/camera/CameraSource.java +++ b/camera/android/src/main/java/com/capacitorjs/plugins/camera/CameraSource.java @@ -3,6 +3,7 @@ public enum CameraSource { PROMPT("PROMPT"), CAMERA("CAMERA"), + CAMERA_MULTI("CAMERA_MULTI"), PHOTOS("PHOTOS"); private String source; diff --git a/camera/android/src/main/java/com/capacitorjs/plugins/camera/DeviceUtils.java b/camera/android/src/main/java/com/capacitorjs/plugins/camera/DeviceUtils.java new file mode 100644 index 0000000000..b50030285a --- /dev/null +++ b/camera/android/src/main/java/com/capacitorjs/plugins/camera/DeviceUtils.java @@ -0,0 +1,11 @@ +package com.capacitorjs.plugins.camera; + +import android.content.Context; +import android.util.DisplayMetrics; + +public class DeviceUtils { + public static int dpToPx(Context context, int dp) { + DisplayMetrics displayMetrics = context.getResources().getDisplayMetrics(); + return (int) (dp * displayMetrics.density + 0.5f); + } +} diff --git a/camera/android/src/main/java/com/capacitorjs/plugins/camera/ImagePreviewFragment.java b/camera/android/src/main/java/com/capacitorjs/plugins/camera/ImagePreviewFragment.java new file mode 100644 index 0000000000..a861d3ed74 --- /dev/null +++ b/camera/android/src/main/java/com/capacitorjs/plugins/camera/ImagePreviewFragment.java @@ -0,0 +1,362 @@ +package com.capacitorjs.plugins.camera; + +import android.content.res.Configuration; +import android.content.res.ColorStateList; +import android.graphics.Bitmap; +import android.graphics.BitmapFactory; +import android.graphics.Color; +import android.graphics.drawable.GradientDrawable; +import android.net.Uri; +import android.os.Bundle; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.FrameLayout; +import android.widget.ImageView; +import android.widget.ProgressBar; +import android.widget.TextView; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.fragment.app.DialogFragment; +import androidx.fragment.app.Fragment; +import androidx.viewpager2.adapter.FragmentStateAdapter; +import androidx.viewpager2.widget.ViewPager2; +import com.google.android.material.floatingactionbutton.FloatingActionButton; +import java.io.IOException; +import java.io.InputStream; +import java.util.ArrayList; +import java.util.List; + +/** + * A DialogFragment that displays a full-screen preview of an image. + * This is shown when a user taps on a thumbnail in the camera interface. + */ +public class ImagePreviewFragment extends DialogFragment { + + private static final String ARG_IMAGE_URIS = "arg_image_uris"; + private static final String ARG_CURRENT_POSITION = "arg_current_position"; + + private List imageUris; + private int currentPosition; + private ViewPager2 viewPager; + private TextView positionIndicator; + + /** + * Create a new instance of ImagePreviewFragment with the provided image URI + * + * @param uri The URI of the image to display in the preview + * @return A new instance of ImagePreviewFragment + */ + public static ImagePreviewFragment newInstance(Uri uri) { + List singleImageList = new ArrayList<>(); + singleImageList.add(uri); + return newInstance(singleImageList, 0); + } + + /** + * Create a new instance of ImagePreviewFragment with a list of images and starting position + * + * @param imageUris List of image URIs to display + * @param position The starting position in the list + * @return A new instance of ImagePreviewFragment + */ + public static ImagePreviewFragment newInstance(List imageUris, int position) { + ImagePreviewFragment fragment = new ImagePreviewFragment(); + Bundle args = new Bundle(); + args.putParcelableArrayList(ARG_IMAGE_URIS, new ArrayList<>(imageUris)); + args.putInt(ARG_CURRENT_POSITION, position); + fragment.setArguments(args); + return fragment; + } + + @Override + public void onCreate(@Nullable Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setStyle(DialogFragment.STYLE_NORMAL, android.R.style.Theme_Black_NoTitleBar_Fullscreen); + + // Retrieve arguments from Bundle + Bundle args = getArguments(); + if (args != null) { + imageUris = args.getParcelableArrayList(ARG_IMAGE_URIS); + currentPosition = args.getInt(ARG_CURRENT_POSITION, 0); + } else { + // Fallback in case no arguments are provided + imageUris = new ArrayList<>(); + currentPosition = 0; + } + } + + @Nullable + @Override + public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { + // Create the main layout + FrameLayout rootLayout = new FrameLayout(requireContext()); + rootLayout.setLayoutParams(new ViewGroup.LayoutParams( + ViewGroup.LayoutParams.MATCH_PARENT, + ViewGroup.LayoutParams.MATCH_PARENT)); + rootLayout.setBackgroundColor(Color.BLACK); + + // Create ViewPager2 for swipe navigation + viewPager = new ViewPager2(requireContext()); + viewPager.setLayoutParams(new FrameLayout.LayoutParams( + FrameLayout.LayoutParams.MATCH_PARENT, + FrameLayout.LayoutParams.MATCH_PARENT)); + + // Set up the adapter + ImagePagerAdapter adapter = new ImagePagerAdapter(this, imageUris); + viewPager.setAdapter(adapter); + viewPager.setCurrentItem(currentPosition, false); + + rootLayout.addView(viewPager); + + // Create position indicator (only show if more than one image) + if (imageUris.size() > 1) { + positionIndicator = new TextView(requireContext()); + positionIndicator.setTextColor(Color.WHITE); + positionIndicator.setTextSize(16); + + // Create pill-shaped background + GradientDrawable pillBackground = new GradientDrawable(); + pillBackground.setShape(GradientDrawable.RECTANGLE); + pillBackground.setColor(0x80000000); // Semi-transparent black + pillBackground.setCornerRadius(dpToPx(requireContext(), 20)); // Large corner radius for pill shape + positionIndicator.setBackground(pillBackground); + + positionIndicator.setPadding(dpToPx(requireContext(), 16), dpToPx(requireContext(), 8), + dpToPx(requireContext(), 16), dpToPx(requireContext(), 8)); + + FrameLayout.LayoutParams indicatorParams = new FrameLayout.LayoutParams( + FrameLayout.LayoutParams.WRAP_CONTENT, + FrameLayout.LayoutParams.WRAP_CONTENT); + indicatorParams.gravity = android.view.Gravity.TOP | android.view.Gravity.CENTER_HORIZONTAL; + indicatorParams.setMargins(0, dpToPx(requireContext(), 60), 0, 0); + positionIndicator.setLayoutParams(indicatorParams); + + updatePositionIndicator(currentPosition); + rootLayout.addView(positionIndicator); + + // Listen for page changes + viewPager.registerOnPageChangeCallback(new ViewPager2.OnPageChangeCallback() { + @Override + public void onPageSelected(int position) { + updatePositionIndicator(position); + } + }); + } + + // Create the close button with matching CameraFragment styling + FloatingActionButton closeButton = new FloatingActionButton(requireContext()); + closeButton.setImageResource(R.drawable.close_24px); + closeButton.setBackgroundTintList(createButtonColorList()); + closeButton.setColorFilter(Color.WHITE); + FrameLayout.LayoutParams buttonParams = new FrameLayout.LayoutParams( + FrameLayout.LayoutParams.WRAP_CONTENT, + FrameLayout.LayoutParams.WRAP_CONTENT); + buttonParams.setMargins(dpToPx(requireContext(), 20), dpToPx(requireContext(), 40), 0, 0); + closeButton.setLayoutParams(buttonParams); + closeButton.setOnClickListener(v -> dismiss()); + rootLayout.addView(closeButton); + + return rootLayout; + } + + private void updatePositionIndicator(int position) { + if (positionIndicator != null) { + positionIndicator.setText((position + 1) + " / " + imageUris.size()); + } + } + + /** + * Loads the full-resolution image from the given URI + * + * @param uri The URI of the image to load + * @param imageView The ImageView to display the image in + * @param progressBar The ProgressBar to show while loading + */ + private void loadFullResolutionImage(Uri uri, ImageView imageView, ProgressBar progressBar) { + new Thread(() -> { + try { + // Show progress bar while loading + requireActivity().runOnUiThread(() -> progressBar.setVisibility(View.VISIBLE)); + + // Load the full-resolution image + InputStream inputStream = requireContext().getContentResolver().openInputStream(uri); + Bitmap fullResolutionBitmap = BitmapFactory.decodeStream(inputStream); + if (inputStream != null) { + inputStream.close(); + } + + // Get EXIF data and correct orientation + ExifWrapper exifWrapper = ImageUtils.getExifData(requireContext(), fullResolutionBitmap, uri); + try { + fullResolutionBitmap = ImageUtils.correctOrientation(requireContext(), fullResolutionBitmap, uri, exifWrapper); + + // Additional rotation for landscape mode if needed + int orientation = requireContext().getResources().getConfiguration().orientation; + if (orientation == Configuration.ORIENTATION_LANDSCAPE) { + // In landscape mode, ensure the image is properly oriented + // The image is already rotated based on EXIF data, so we don't need additional rotation + // But we ensure it's displayed with the correct aspect ratio + } + } catch (IOException e) { + e.printStackTrace(); + } + + // Use final reference for the bitmap to use in the UI thread + final Bitmap correctedBitmap = fullResolutionBitmap; + + // Update UI on main thread + requireActivity().runOnUiThread(() -> { + imageView.setImageBitmap(correctedBitmap); + progressBar.setVisibility(View.GONE); + }); + } catch (Exception e) { + e.printStackTrace(); + requireActivity().runOnUiThread(() -> { + progressBar.setVisibility(View.GONE); + }); + } + }).start(); + } + + private int dpToPx(android.content.Context context, int dp) { + return (int) (dp * context.getResources().getDisplayMetrics().density); + } + + @NonNull + private static ColorStateList createButtonColorList() { + int[][] states = new int[][] { + new int[] { android.R.attr.state_enabled }, // enabled + new int[] { -android.R.attr.state_enabled }, // disabled + new int[] { -android.R.attr.state_checked }, // unchecked + new int[] { android.R.attr.state_pressed } // pressed + }; + + int[] colors = new int[] { Color.DKGRAY, Color.TRANSPARENT, Color.TRANSPARENT, Color.LTGRAY }; + return new ColorStateList(states, colors); + } + + /** + * Adapter for ViewPager2 to handle image swiping + */ + private static class ImagePagerAdapter extends FragmentStateAdapter { + private final List imageUris; + + public ImagePagerAdapter(@NonNull Fragment fragment, List imageUris) { + super(fragment); + this.imageUris = imageUris; + } + + @NonNull + @Override + public Fragment createFragment(int position) { + return ImagePageFragment.newInstance(imageUris.get(position)); + } + + @Override + public int getItemCount() { + return imageUris.size(); + } + } + + /** + * Fragment for individual image pages in the ViewPager2 + */ + public static class ImagePageFragment extends Fragment { + private static final String ARG_IMAGE_URI = "image_uri"; + private Uri imageUri; + + public static ImagePageFragment newInstance(Uri uri) { + ImagePageFragment fragment = new ImagePageFragment(); + Bundle args = new Bundle(); + args.putParcelable(ARG_IMAGE_URI, uri); + fragment.setArguments(args); + return fragment; + } + + @Nullable + @Override + public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { + // Retrieve the image URI from arguments + Bundle args = getArguments(); + if (args != null) { + imageUri = args.getParcelable(ARG_IMAGE_URI); + } + + // Create the layout for a single image + FrameLayout frameLayout = new FrameLayout(requireContext()); + frameLayout.setLayoutParams(new ViewGroup.LayoutParams( + ViewGroup.LayoutParams.MATCH_PARENT, + ViewGroup.LayoutParams.MATCH_PARENT)); + + // Create the image view + ImageView imageView = new ImageView(requireContext()); + imageView.setLayoutParams(new FrameLayout.LayoutParams( + FrameLayout.LayoutParams.MATCH_PARENT, + FrameLayout.LayoutParams.MATCH_PARENT)); + imageView.setScaleType(ImageView.ScaleType.FIT_CENTER); + + // Create a progress bar + ProgressBar progressBar = new ProgressBar(requireContext()); + FrameLayout.LayoutParams progressParams = new FrameLayout.LayoutParams( + FrameLayout.LayoutParams.WRAP_CONTENT, + FrameLayout.LayoutParams.WRAP_CONTENT); + progressParams.gravity = android.view.Gravity.CENTER; + progressBar.setLayoutParams(progressParams); + + frameLayout.addView(imageView); + frameLayout.addView(progressBar); + + // Load the image + loadFullResolutionImage(imageUri, imageView, progressBar); + + return frameLayout; + } + + private void loadFullResolutionImage(Uri uri, ImageView imageView, ProgressBar progressBar) { + new Thread(() -> { + try { + // Show progress bar while loading + requireActivity().runOnUiThread(() -> progressBar.setVisibility(View.VISIBLE)); + + // Load the full-resolution image + InputStream inputStream = requireContext().getContentResolver().openInputStream(uri); + Bitmap fullResolutionBitmap = BitmapFactory.decodeStream(inputStream); + if (inputStream != null) { + inputStream.close(); + } + + // Get EXIF data and correct orientation + ExifWrapper exifWrapper = ImageUtils.getExifData(requireContext(), fullResolutionBitmap, uri); + try { + fullResolutionBitmap = ImageUtils.correctOrientation(requireContext(), fullResolutionBitmap, uri, exifWrapper); + + // Additional rotation for landscape mode if needed + int orientation = requireContext().getResources().getConfiguration().orientation; + if (orientation == Configuration.ORIENTATION_LANDSCAPE) { + // In landscape mode, ensure the image is properly oriented + // The image is already rotated based on EXIF data, so we don't need additional rotation + // But we ensure it's displayed with the correct aspect ratio + } + } catch (IOException e) { + e.printStackTrace(); + } + + // Use final reference for the bitmap to use in the UI thread + final Bitmap correctedBitmap = fullResolutionBitmap; + + // Update UI on main thread + requireActivity().runOnUiThread(() -> { + imageView.setImageBitmap(correctedBitmap); + progressBar.setVisibility(View.GONE); + }); + } catch (Exception e) { + e.printStackTrace(); + requireActivity().runOnUiThread(() -> { + progressBar.setVisibility(View.GONE); + }); + } + }).start(); + } + } +} \ No newline at end of file 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 index 82d82aad0b..c0dbb3d6ee 100644 --- a/camera/android/src/main/java/com/capacitorjs/plugins/camera/ImageUtils.java +++ b/camera/android/src/main/java/com/capacitorjs/plugins/camera/ImageUtils.java @@ -1,14 +1,16 @@ package com.capacitorjs.plugins.camera; import android.content.Context; +import android.content.res.Configuration; 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; @@ -17,6 +19,7 @@ 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 @@ -29,6 +32,7 @@ public static Bitmap resize(Bitmap bitmap, final int desiredMaxWidth, final int /** * 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 @@ -55,6 +59,7 @@ private static Bitmap resizePreservingAspectRatio(Bitmap bitmap, final int desir /** * Transform an image with the given matrix + * * @param bitmap * @param matrix * @return @@ -66,6 +71,7 @@ private static Bitmap transform(final Bitmap bitmap, final Matrix matrix) { /** * 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 @@ -73,12 +79,30 @@ private static Bitmap transform(final Bitmap bitmap, final Matrix matrix) { */ 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); + + // If in landscape mode, we need to ensure the image is properly oriented + // The EXIF rotation should handle this correctly, but we can add additional + // transformations if needed based on testing + exif.resetOrientation(); return transform(bitmap, matrix); } else { + // If there's no EXIF orientation but we're in landscape mode, + // check the aspect ratio and rotate if needed + int width = bitmap.getWidth(); + int height = bitmap.getHeight(); + if (width > height) { +// TODO: add support for portrait images +// // Landscape image, rotate 90 degrees to portrait +// Matrix matrix = new Matrix(); +// matrix.postRotate(90); +// // Optionally, you may want to flip or further adjust based on your use case +// return transform(bitmap, matrix); + } return bitmap; } } @@ -111,12 +135,13 @@ public static ExifWrapper getExifData(final Context c, final Bitmap bitmap, fina 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) {} + } catch (IOException ignored) { + } } } return new ExifWrapper(null); diff --git a/camera/android/src/main/java/com/capacitorjs/plugins/camera/ThumbnailAdapter.java b/camera/android/src/main/java/com/capacitorjs/plugins/camera/ThumbnailAdapter.java new file mode 100644 index 0000000000..eef9c7bee3 --- /dev/null +++ b/camera/android/src/main/java/com/capacitorjs/plugins/camera/ThumbnailAdapter.java @@ -0,0 +1,228 @@ +package com.capacitorjs.plugins.camera; + +import static com.capacitorjs.plugins.camera.DeviceUtils.dpToPx; + +import android.content.Context; +import android.graphics.Bitmap; +import android.graphics.Color; +import android.net.Uri; +import android.view.Gravity; +import android.view.View; +import android.view.ViewGroup; +import android.widget.FrameLayout; +import android.widget.ImageView; +import android.widget.ProgressBar; +import androidx.annotation.NonNull; +import androidx.recyclerview.widget.RecyclerView; +import java.util.ArrayList; + +public class ThumbnailAdapter extends RecyclerView.Adapter { + + private final ArrayList thumbnails; + private OnThumbnailsChangedCallback thumbnailsChangedCallback = null; + private OnThumbnailClickListener thumbnailClickListener = null; + + ThumbnailAdapter() { + this.thumbnails = new ArrayList<>(); + } + + void addThumbnail(Uri uri, Bitmap thumbnail) { + if (thumbnail == null) return; + ThumbnailItem item = new ThumbnailItem(uri, thumbnail, false); + thumbnails.add(item); + notifyItemInserted(thumbnails.size() - 1); + } + + void addLoadingThumbnail() { + ThumbnailItem item = new ThumbnailItem(null, null, true); + thumbnails.add(item); + notifyItemInserted(thumbnails.size() - 1); + } + + void replaceLoadingThumbnail(Uri uri, Bitmap thumbnail) { + // Find the loading thumbnail (should be the last one) + for (int i = thumbnails.size() - 1; i >= 0; i--) { + ThumbnailItem item = thumbnails.get(i); + if (item.isLoading()) { + thumbnails.set(i, new ThumbnailItem(uri, thumbnail, false)); + notifyItemChanged(i); + break; + } + } + } + + void removeLoadingThumbnails() { + // Remove all loading thumbnails from the end backwards to avoid index issues + for (int i = thumbnails.size() - 1; i >= 0; i--) { + ThumbnailItem item = thumbnails.get(i); + if (item.isLoading()) { + thumbnails.remove(i); + notifyItemRemoved(i); + } + } + } + + boolean hasLoadingThumbnails() { + for (ThumbnailItem item : thumbnails) { + if (item.isLoading()) { + return true; + } + } + return false; + } + + @NonNull + @Override + public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) { + Context context = parent.getContext(); + int thumbnailSize = dpToPx(context, 80); // Thumbnail size + int margin = dpToPx(context, 4); // Margin for each side + + FrameLayout frameLayout = new FrameLayout(context); + FrameLayout.LayoutParams layoutParams = new FrameLayout.LayoutParams(thumbnailSize, thumbnailSize); + layoutParams.setMargins(margin, margin, margin, margin); + frameLayout.setLayoutParams(layoutParams); + + ImageView imageView = new ImageView(context); + imageView.setScaleType(ImageView.ScaleType.CENTER_CROP); + frameLayout.addView(imageView); + + ImageView removeButton = new ImageView(context); + int buttonSize = dpToPx(context, 24); + FrameLayout.LayoutParams buttonParams = new FrameLayout.LayoutParams(buttonSize, buttonSize); + buttonParams.gravity = Gravity.TOP | Gravity.END; + removeButton.setLayoutParams(buttonParams); + removeButton.setImageResource(R.drawable.ic_cancel_white_24dp); + frameLayout.addView(removeButton); + + // Add progress bar for loading state + ProgressBar progressBar = new ProgressBar(context); + int progressSize = dpToPx(context, 32); + FrameLayout.LayoutParams progressParams = new FrameLayout.LayoutParams(progressSize, progressSize); + progressParams.gravity = Gravity.CENTER; + progressBar.setLayoutParams(progressParams); + progressBar.setVisibility(View.GONE); // Initially hidden + frameLayout.addView(progressBar); + + return new ViewHolder(frameLayout, imageView, removeButton, progressBar); + } + + @Override + public void onBindViewHolder(@NonNull ViewHolder holder, int position) { + ThumbnailItem item = thumbnails.get(position); + + if (item.isLoading()) { + // Show loading state + holder.imageView.setImageBitmap(null); + holder.imageView.setBackgroundColor(Color.GRAY); + holder.progressBar.setVisibility(View.VISIBLE); + holder.removeButton.setVisibility(View.GONE); + holder.mainView.setOnClickListener(null); // Disable clicks for loading items + } else { + // Show actual thumbnail + holder.imageView.setImageBitmap(item.bitmap); + holder.imageView.setBackgroundColor(Color.TRANSPARENT); + holder.progressBar.setVisibility(View.GONE); + holder.removeButton.setVisibility(View.VISIBLE); + + // Set click listener for the thumbnail + holder.mainView.setOnClickListener(v -> { + if (thumbnailClickListener != null) { + thumbnailClickListener.onThumbnailClick(item.getUri(), item.getBitmap()); + } + }); + + holder.removeButton.setOnClickListener( + v -> { + int currentPosition = holder.getAdapterPosition(); + if (currentPosition != RecyclerView.NO_POSITION) { + ThumbnailItem removed = thumbnails.remove(currentPosition); + + notifyItemRemoved(currentPosition); + + if (thumbnailsChangedCallback != null) { + thumbnailsChangedCallback.onThumbnailRemoved(removed.getUri(), removed.getBitmap()); + } + } + } + ); + } + } + + @Override + public int getItemCount() { + return thumbnails.size(); + } + + /** + * Get the ThumbnailItem at the specified position + * + * @param position Position of the item to retrieve + * @return The ThumbnailItem at the specified position, or null if position is invalid + */ + public ThumbnailItem getThumbnailItem(int position) { + if (position >= 0 && position < thumbnails.size()) { + return thumbnails.get(position); + } + return null; + } + + public void setOnThumbnailsChangedCallback(OnThumbnailsChangedCallback callback) { + this.thumbnailsChangedCallback = callback; + } + + public void setOnThumbnailClickListener(OnThumbnailClickListener listener) { + this.thumbnailClickListener = listener; + } + + static class ViewHolder extends RecyclerView.ViewHolder { + + ImageView imageView; + ImageView removeButton; + FrameLayout mainView; + ProgressBar progressBar; + + ViewHolder(@NonNull FrameLayout view, @NonNull ImageView imageView, @NonNull ImageView removeButton, @NonNull ProgressBar progressBar) { + super(view); + this.imageView = imageView; + this.mainView = view; + this.removeButton = removeButton; + this.progressBar = progressBar; + } + } + + public abstract static class OnThumbnailsChangedCallback { + + public void onThumbnailRemoved(Uri uri, Bitmap bmp) {} + } + + public interface OnThumbnailClickListener { + void onThumbnailClick(Uri uri, Bitmap bitmap); + } + + public static class ThumbnailItem { + + private final Uri uri; + private final Bitmap bitmap; + private final boolean loading; + + public ThumbnailItem(Uri u, Bitmap bmp, boolean isLoading) { + this.uri = u; + this.bitmap = bmp; + this.loading = isLoading; + } + + public Uri getUri() { + return uri; + } + + public Bitmap getBitmap() { + return bitmap; + } + + public boolean isLoading() { + return loading; + } + } +} + diff --git a/camera/android/src/main/res/animator/button_press_animation.xml b/camera/android/src/main/res/animator/button_press_animation.xml new file mode 100644 index 0000000000..f0731f781b --- /dev/null +++ b/camera/android/src/main/res/animator/button_press_animation.xml @@ -0,0 +1,31 @@ + + + + + + + + + + + + + + + diff --git a/camera/android/src/main/res/animator/button_release_animation.xml b/camera/android/src/main/res/animator/button_release_animation.xml new file mode 100644 index 0000000000..ca50038390 --- /dev/null +++ b/camera/android/src/main/res/animator/button_release_animation.xml @@ -0,0 +1,19 @@ + + + + + + + + + diff --git a/camera/android/src/main/res/drawable/center_focus_24px.xml b/camera/android/src/main/res/drawable/center_focus_24px.xml new file mode 100644 index 0000000000..49d8293b0e --- /dev/null +++ b/camera/android/src/main/res/drawable/center_focus_24px.xml @@ -0,0 +1,9 @@ + + + diff --git a/camera/android/src/main/res/drawable/close_24px.xml b/camera/android/src/main/res/drawable/close_24px.xml new file mode 100644 index 0000000000..893cccdcf8 --- /dev/null +++ b/camera/android/src/main/res/drawable/close_24px.xml @@ -0,0 +1,9 @@ + + + diff --git a/camera/android/src/main/res/drawable/done_24px.xml b/camera/android/src/main/res/drawable/done_24px.xml new file mode 100644 index 0000000000..f97e17d559 --- /dev/null +++ b/camera/android/src/main/res/drawable/done_24px.xml @@ -0,0 +1,9 @@ + + + diff --git a/camera/android/src/main/res/drawable/flash_auto_24px.xml b/camera/android/src/main/res/drawable/flash_auto_24px.xml new file mode 100644 index 0000000000..7c69e1604f --- /dev/null +++ b/camera/android/src/main/res/drawable/flash_auto_24px.xml @@ -0,0 +1,9 @@ + + + diff --git a/camera/android/src/main/res/drawable/flash_off_24px.xml b/camera/android/src/main/res/drawable/flash_off_24px.xml new file mode 100644 index 0000000000..800fa06948 --- /dev/null +++ b/camera/android/src/main/res/drawable/flash_off_24px.xml @@ -0,0 +1,9 @@ + + + diff --git a/camera/android/src/main/res/drawable/flash_on_24px.xml b/camera/android/src/main/res/drawable/flash_on_24px.xml new file mode 100644 index 0000000000..1df46eb1d3 --- /dev/null +++ b/camera/android/src/main/res/drawable/flash_on_24px.xml @@ -0,0 +1,9 @@ + + + diff --git a/camera/android/src/main/res/drawable/flip_camera_android_24px.xml b/camera/android/src/main/res/drawable/flip_camera_android_24px.xml new file mode 100644 index 0000000000..8c6c965943 --- /dev/null +++ b/camera/android/src/main/res/drawable/flip_camera_android_24px.xml @@ -0,0 +1,9 @@ + + + diff --git a/camera/android/src/main/res/drawable/ic_cancel_white_24dp.xml b/camera/android/src/main/res/drawable/ic_cancel_white_24dp.xml new file mode 100644 index 0000000000..fce2f3bd6c --- /dev/null +++ b/camera/android/src/main/res/drawable/ic_cancel_white_24dp.xml @@ -0,0 +1,9 @@ + + + diff --git a/camera/android/src/main/res/drawable/ic_shutter_circle.xml b/camera/android/src/main/res/drawable/ic_shutter_circle.xml new file mode 100644 index 0000000000..6b8af80eb8 --- /dev/null +++ b/camera/android/src/main/res/drawable/ic_shutter_circle.xml @@ -0,0 +1,4 @@ + + + diff --git a/camera/android/src/main/res/drawable/photo_camera_24px.xml b/camera/android/src/main/res/drawable/photo_camera_24px.xml new file mode 100644 index 0000000000..bab79577e8 --- /dev/null +++ b/camera/android/src/main/res/drawable/photo_camera_24px.xml @@ -0,0 +1,9 @@ + + + diff --git a/camera/dist/docs.json b/camera/dist/docs.json new file mode 100644 index 0000000000..c1edbfd3c4 --- /dev/null +++ b/camera/dist/docs.json @@ -0,0 +1,810 @@ +{ + "api": { + "name": "CameraPlugin", + "slug": "cameraplugin", + "docs": "", + "tags": [], + "methods": [ + { + "name": "getPhoto", + "signature": "(options: ImageOptions) => Promise", + "parameters": [ + { + "name": "options", + "docs": "", + "type": "ImageOptions" + } + ], + "returns": "Promise", + "tags": [ + { + "name": "since", + "text": "1.0.0" + } + ], + "docs": "Prompt the user to pick a photo from an album, or take a new photo\nwith the camera.", + "complexTypes": [ + "Photo", + "ImageOptions" + ], + "slug": "getphoto" + }, + { + "name": "pickImages", + "signature": "(options: GalleryImageOptions) => Promise", + "parameters": [ + { + "name": "options", + "docs": "", + "type": "GalleryImageOptions" + } + ], + "returns": "Promise", + "tags": [ + { + "name": "since", + "text": "1.2.0" + } + ], + "docs": "Allows the user to pick multiplef pictures from the photo gallery.", + "complexTypes": [ + "GalleryPhotos", + "GalleryImageOptions" + ], + "slug": "pickimages" + }, + { + "name": "pickLimitedLibraryPhotos", + "signature": "() => Promise", + "parameters": [], + "returns": "Promise", + "tags": [ + { + "name": "since", + "text": "4.1.0" + } + ], + "docs": "Allows the user to update their limited photo library selection.\nReturns all the limited photos after the picker dismissal.\nIf instead the user gave full access to the photos it returns an empty array.", + "complexTypes": [ + "GalleryPhotos" + ], + "slug": "picklimitedlibraryphotos" + }, + { + "name": "getLimitedLibraryPhotos", + "signature": "() => Promise", + "parameters": [], + "returns": "Promise", + "tags": [ + { + "name": "since", + "text": "4.1.0" + } + ], + "docs": "Return an array of photos selected from the limited photo library.", + "complexTypes": [ + "GalleryPhotos" + ], + "slug": "getlimitedlibraryphotos" + }, + { + "name": "checkPermissions", + "signature": "() => Promise", + "parameters": [], + "returns": "Promise", + "tags": [ + { + "name": "since", + "text": "1.0.0" + } + ], + "docs": "Check camera and photo album permissions", + "complexTypes": [ + "PermissionStatus" + ], + "slug": "checkpermissions" + }, + { + "name": "requestPermissions", + "signature": "(permissions?: CameraPluginPermissions | undefined) => Promise", + "parameters": [ + { + "name": "permissions", + "docs": "", + "type": "CameraPluginPermissions | undefined" + } + ], + "returns": "Promise", + "tags": [ + { + "name": "since", + "text": "1.0.0" + } + ], + "docs": "Request camera and photo album permissions", + "complexTypes": [ + "PermissionStatus", + "CameraPluginPermissions" + ], + "slug": "requestpermissions" + } + ], + "properties": [] + }, + "interfaces": [ + { + "name": "Photo", + "slug": "photo", + "docs": "", + "tags": [], + "methods": [], + "properties": [ + { + "name": "base64String", + "tags": [ + { + "text": "1.0.0", + "name": "since" + } + ], + "docs": "The base64 encoded string representation of the image, if using CameraResultType.Base64.", + "complexTypes": [], + "type": "string | undefined" + }, + { + "name": "dataUrl", + "tags": [ + { + "text": "1.0.0", + "name": "since" + } + ], + "docs": "The url starting with 'data:image/jpeg;base64,' and the base64 encoded string representation of the image, if using CameraResultType.DataUrl.\n\nNote: On web, the file format could change depending on the browser.", + "complexTypes": [], + "type": "string | undefined" + }, + { + "name": "path", + "tags": [ + { + "text": "1.0.0", + "name": "since" + } + ], + "docs": "If using CameraResultType.Uri, the path will contain a full,\nplatform-specific file URL that can be read later using the Filesystem API.", + "complexTypes": [], + "type": "string | undefined" + }, + { + "name": "webPath", + "tags": [ + { + "text": "1.0.0", + "name": "since" + } + ], + "docs": "webPath returns a path that can be used to set the src attribute of an image for efficient\nloading and rendering.", + "complexTypes": [], + "type": "string | undefined" + }, + { + "name": "exif", + "tags": [ + { + "text": "1.0.0", + "name": "since" + } + ], + "docs": "Exif data, if any, retrieved from the image", + "complexTypes": [], + "type": "any" + }, + { + "name": "format", + "tags": [ + { + "text": "1.0.0", + "name": "since" + } + ], + "docs": "The format of the image, ex: jpeg, png, gif.\n\niOS and Android only support jpeg.\nWeb supports jpeg, png and gif, but the exact availability may vary depending on the browser.\ngif is only supported if `webUseInput` is set to `true` or if `source` is set to `Photos`.", + "complexTypes": [], + "type": "string" + }, + { + "name": "saved", + "tags": [ + { + "text": "1.1.0", + "name": "since" + } + ], + "docs": "Whether if the image was saved to the gallery or not.\n\nOn Android and iOS, saving to the gallery can fail if the user didn't\ngrant the required permissions.\nOn Web there is no gallery, so always returns false.", + "complexTypes": [], + "type": "boolean" + } + ] + }, + { + "name": "ImageOptions", + "slug": "imageoptions", + "docs": "", + "tags": [], + "methods": [], + "properties": [ + { + "name": "quality", + "tags": [ + { + "text": "1.0.0", + "name": "since" + } + ], + "docs": "The quality of image to return as JPEG, from 0-100\nNote: This option is only supported on Android and iOS", + "complexTypes": [], + "type": "number | undefined" + }, + { + "name": "allowEditing", + "tags": [ + { + "text": "1.0.0", + "name": "since" + } + ], + "docs": "Whether to allow the user to crop or make small edits (platform specific).\nOn iOS it's only supported for CameraSource.Camera, but not for CameraSource.Photos.", + "complexTypes": [], + "type": "boolean | undefined" + }, + { + "name": "resultType", + "tags": [ + { + "text": "1.0.0", + "name": "since" + } + ], + "docs": "How the data should be returned. Currently, only 'Base64', 'DataUrl' or 'Uri' is supported", + "complexTypes": [ + "CameraResultType" + ], + "type": "CameraResultType" + }, + { + "name": "saveToGallery", + "tags": [ + { + "text": ": false", + "name": "default" + }, + { + "text": "1.0.0", + "name": "since" + } + ], + "docs": "Whether to save the photo to the gallery.\nIf the photo was picked from the gallery, it will only be saved if edited.", + "complexTypes": [], + "type": "boolean | undefined" + }, + { + "name": "width", + "tags": [ + { + "text": "1.0.0", + "name": "since" + } + ], + "docs": "The desired maximum width of the saved image. The aspect ratio is respected.", + "complexTypes": [], + "type": "number | undefined" + }, + { + "name": "height", + "tags": [ + { + "text": "1.0.0", + "name": "since" + } + ], + "docs": "The desired maximum height of the saved image. The aspect ratio is respected.", + "complexTypes": [], + "type": "number | undefined" + }, + { + "name": "correctOrientation", + "tags": [ + { + "text": ": true", + "name": "default" + }, + { + "text": "1.0.0", + "name": "since" + } + ], + "docs": "Whether to automatically rotate the image \"up\" to correct for orientation\nin portrait mode", + "complexTypes": [], + "type": "boolean | undefined" + }, + { + "name": "source", + "tags": [ + { + "text": ": CameraSource.Prompt", + "name": "default" + }, + { + "text": "1.0.0", + "name": "since" + } + ], + "docs": "The source to get the photo from. By default this prompts the user to select\neither the photo album or take a photo.", + "complexTypes": [ + "CameraSource" + ], + "type": "CameraSource" + }, + { + "name": "direction", + "tags": [ + { + "text": ": CameraDirection.Rear", + "name": "default" + }, + { + "text": "1.0.0", + "name": "since" + } + ], + "docs": "iOS and Web only: The camera direction.", + "complexTypes": [ + "CameraDirection" + ], + "type": "CameraDirection" + }, + { + "name": "presentationStyle", + "tags": [ + { + "text": ": 'fullscreen'", + "name": "default" + }, + { + "text": "1.0.0", + "name": "since" + } + ], + "docs": "iOS only: The presentation style of the Camera.", + "complexTypes": [], + "type": "'fullscreen' | 'popover' | undefined" + }, + { + "name": "webUseInput", + "tags": [ + { + "text": "1.0.0", + "name": "since" + } + ], + "docs": "Web only: Whether to use the PWA Element experience or file input. The\ndefault is to use PWA Elements if installed and fall back to file input.\nTo always use file input, set this to `true`.\n\nLearn more about PWA Elements: https://capacitorjs.com/docs/web/pwa-elements", + "complexTypes": [], + "type": "boolean | undefined" + }, + { + "name": "promptLabelHeader", + "tags": [ + { + "text": ": 'Photo'", + "name": "default" + }, + { + "text": "1.0.0", + "name": "since" + } + ], + "docs": "Text value to use when displaying the prompt.", + "complexTypes": [], + "type": "string | undefined" + }, + { + "name": "promptLabelCancel", + "tags": [ + { + "text": ": 'Cancel'", + "name": "default" + }, + { + "text": "1.0.0", + "name": "since" + } + ], + "docs": "Text value to use when displaying the prompt.\niOS only: The label of the 'cancel' button.", + "complexTypes": [], + "type": "string | undefined" + }, + { + "name": "promptLabelPhoto", + "tags": [ + { + "text": ": 'From Photos'", + "name": "default" + }, + { + "text": "1.0.0", + "name": "since" + } + ], + "docs": "Text value to use when displaying the prompt.\nThe label of the button to select a saved image.", + "complexTypes": [], + "type": "string | undefined" + }, + { + "name": "promptLabelPicture", + "tags": [ + { + "text": ": 'Take Picture'", + "name": "default" + }, + { + "text": "1.0.0", + "name": "since" + } + ], + "docs": "Text value to use when displaying the prompt.\nThe label of the button to open the camera.", + "complexTypes": [], + "type": "string | undefined" + } + ] + }, + { + "name": "GalleryPhotos", + "slug": "galleryphotos", + "docs": "", + "tags": [], + "methods": [], + "properties": [ + { + "name": "photos", + "tags": [ + { + "text": "1.2.0", + "name": "since" + } + ], + "docs": "Array of all the picked photos.", + "complexTypes": [ + "GalleryPhoto" + ], + "type": "GalleryPhoto[]" + } + ] + }, + { + "name": "GalleryPhoto", + "slug": "galleryphoto", + "docs": "", + "tags": [], + "methods": [], + "properties": [ + { + "name": "path", + "tags": [ + { + "text": "1.2.0", + "name": "since" + } + ], + "docs": "Full, platform-specific file URL that can be read later using the Filesystem API.", + "complexTypes": [], + "type": "string | undefined" + }, + { + "name": "webPath", + "tags": [ + { + "text": "1.2.0", + "name": "since" + } + ], + "docs": "webPath returns a path that can be used to set the src attribute of an image for efficient\nloading and rendering.", + "complexTypes": [], + "type": "string" + }, + { + "name": "exif", + "tags": [ + { + "text": "1.2.0", + "name": "since" + } + ], + "docs": "Exif data, if any, retrieved from the image", + "complexTypes": [], + "type": "any" + }, + { + "name": "format", + "tags": [ + { + "text": "1.2.0", + "name": "since" + } + ], + "docs": "The format of the image, ex: jpeg, png, gif.\n\niOS and Android only support jpeg.\nWeb supports jpeg, png and gif.", + "complexTypes": [], + "type": "string" + } + ] + }, + { + "name": "GalleryImageOptions", + "slug": "galleryimageoptions", + "docs": "", + "tags": [], + "methods": [], + "properties": [ + { + "name": "quality", + "tags": [ + { + "text": "1.2.0", + "name": "since" + } + ], + "docs": "The quality of image to return as JPEG, from 0-100\nNote: This option is only supported on Android and iOS.", + "complexTypes": [], + "type": "number | undefined" + }, + { + "name": "width", + "tags": [ + { + "text": "1.2.0", + "name": "since" + } + ], + "docs": "The desired maximum width of the saved image. The aspect ratio is respected.", + "complexTypes": [], + "type": "number | undefined" + }, + { + "name": "height", + "tags": [ + { + "text": "1.2.0", + "name": "since" + } + ], + "docs": "The desired maximum height of the saved image. The aspect ratio is respected.", + "complexTypes": [], + "type": "number | undefined" + }, + { + "name": "correctOrientation", + "tags": [ + { + "text": ": true", + "name": "default" + }, + { + "text": "1.2.0", + "name": "since" + } + ], + "docs": "Whether to automatically rotate the image \"up\" to correct for orientation\nin portrait mode", + "complexTypes": [], + "type": "boolean | undefined" + }, + { + "name": "presentationStyle", + "tags": [ + { + "text": ": 'fullscreen'", + "name": "default" + }, + { + "text": "1.2.0", + "name": "since" + } + ], + "docs": "iOS only: The presentation style of the Camera.", + "complexTypes": [], + "type": "'fullscreen' | 'popover' | undefined" + }, + { + "name": "limit", + "tags": [ + { + "text": "0 (unlimited)", + "name": "default" + }, + { + "text": "1.2.0", + "name": "since" + } + ], + "docs": "Maximum number of pictures the user will be able to choose.\nNote: This option is only supported on Android 13+ and iOS.", + "complexTypes": [], + "type": "number | undefined" + } + ] + }, + { + "name": "PermissionStatus", + "slug": "permissionstatus", + "docs": "", + "tags": [], + "methods": [], + "properties": [ + { + "name": "camera", + "tags": [], + "docs": "", + "complexTypes": [ + "CameraPermissionState" + ], + "type": "CameraPermissionState" + }, + { + "name": "photos", + "tags": [], + "docs": "", + "complexTypes": [ + "CameraPermissionState" + ], + "type": "CameraPermissionState" + } + ] + }, + { + "name": "CameraPluginPermissions", + "slug": "camerapluginpermissions", + "docs": "", + "tags": [], + "methods": [], + "properties": [ + { + "name": "permissions", + "tags": [], + "docs": "", + "complexTypes": [ + "CameraPermissionType" + ], + "type": "CameraPermissionType[]" + } + ] + } + ], + "enums": [ + { + "name": "CameraResultType", + "slug": "cameraresulttype", + "members": [ + { + "name": "Uri", + "value": "'uri'", + "tags": [], + "docs": "" + }, + { + "name": "Base64", + "value": "'base64'", + "tags": [], + "docs": "" + }, + { + "name": "DataUrl", + "value": "'dataUrl'", + "tags": [], + "docs": "" + } + ] + }, + { + "name": "CameraSource", + "slug": "camerasource", + "members": [ + { + "name": "Prompt", + "value": "'PROMPT'", + "tags": [], + "docs": "Prompts the user to select either the photo album or take a photo." + }, + { + "name": "Camera", + "value": "'CAMERA'", + "tags": [], + "docs": "Take a new photo using the camera." + }, + { + "name": "CameraMulti", + "value": "'CAMERA_MULTI'", + "tags": [], + "docs": "Take multiple photos in a row using the camera.\nAvailable on Android and iOS." + }, + { + "name": "Photos", + "value": "'PHOTOS'", + "tags": [], + "docs": "Pick an existing photo from the gallery or photo album." + } + ] + }, + { + "name": "CameraDirection", + "slug": "cameradirection", + "members": [ + { + "name": "Rear", + "value": "'REAR'", + "tags": [], + "docs": "" + }, + { + "name": "Front", + "value": "'FRONT'", + "tags": [], + "docs": "" + } + ] + } + ], + "typeAliases": [ + { + "name": "CameraPermissionState", + "slug": "camerapermissionstate", + "docs": "", + "types": [ + { + "text": "PermissionState", + "complexTypes": [ + "PermissionState" + ] + }, + { + "text": "'limited'", + "complexTypes": [] + } + ] + }, + { + "name": "PermissionState", + "slug": "permissionstate", + "docs": "", + "types": [ + { + "text": "'prompt'", + "complexTypes": [] + }, + { + "text": "'prompt-with-rationale'", + "complexTypes": [] + }, + { + "text": "'granted'", + "complexTypes": [] + }, + { + "text": "'denied'", + "complexTypes": [] + } + ] + }, + { + "name": "CameraPermissionType", + "slug": "camerapermissiontype", + "docs": "", + "types": [ + { + "text": "'camera'", + "complexTypes": [] + }, + { + "text": "'photos'", + "complexTypes": [] + } + ] + } + ], + "pluginConfigs": [] +} \ No newline at end of file diff --git a/camera/dist/esm/definitions.d.ts b/camera/dist/esm/definitions.d.ts new file mode 100644 index 0000000000..b14a6e97e8 --- /dev/null +++ b/camera/dist/esm/definitions.d.ts @@ -0,0 +1,341 @@ +import type { PermissionState } from '@capacitor/core'; +export declare type CameraPermissionState = PermissionState | 'limited'; +export declare 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 multiplef 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 declare 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", + /** + * Take multiple photos in a row using the camera. + * Available on Android and iOS. + */ + CameraMulti = "CAMERA_MULTI", + /** + * Pick an existing photo from the gallery or photo album. + */ + Photos = "PHOTOS" +} +export declare enum CameraDirection { + Rear = "REAR", + Front = "FRONT" +} +export declare enum CameraResultType { + Uri = "uri", + Base64 = "base64", + DataUrl = "dataUrl" +} +/** + * @deprecated Use `Photo`. + * @since 1.0.0 + */ +export declare type CameraPhoto = Photo; +/** + * @deprecated Use `ImageOptions`. + * @since 1.0.0 + */ +export declare type CameraOptions = ImageOptions; diff --git a/camera/dist/esm/definitions.js b/camera/dist/esm/definitions.js new file mode 100644 index 0000000000..a58e04fe09 --- /dev/null +++ b/camera/dist/esm/definitions.js @@ -0,0 +1,32 @@ +export var CameraSource; +(function (CameraSource) { + /** + * Prompts the user to select either the photo album or take a photo. + */ + CameraSource["Prompt"] = "PROMPT"; + /** + * Take a new photo using the camera. + */ + CameraSource["Camera"] = "CAMERA"; + /** + * Take multiple photos in a row using the camera. + * Available on Android and iOS. + */ + CameraSource["CameraMulti"] = "CAMERA_MULTI"; + /** + * Pick an existing photo from the gallery or photo album. + */ + CameraSource["Photos"] = "PHOTOS"; +})(CameraSource || (CameraSource = {})); +export var CameraDirection; +(function (CameraDirection) { + CameraDirection["Rear"] = "REAR"; + CameraDirection["Front"] = "FRONT"; +})(CameraDirection || (CameraDirection = {})); +export var CameraResultType; +(function (CameraResultType) { + CameraResultType["Uri"] = "uri"; + CameraResultType["Base64"] = "base64"; + CameraResultType["DataUrl"] = "dataUrl"; +})(CameraResultType || (CameraResultType = {})); +//# sourceMappingURL=definitions.js.map \ No newline at end of file diff --git a/camera/dist/esm/definitions.js.map b/camera/dist/esm/definitions.js.map new file mode 100644 index 0000000000..eeadba5712 --- /dev/null +++ b/camera/dist/esm/definitions.js.map @@ -0,0 +1 @@ +{"version":3,"file":"definitions.js","sourceRoot":"","sources":["../../src/definitions.ts"],"names":[],"mappings":"AAuUA,MAAM,CAAN,IAAY,YAkBX;AAlBD,WAAY,YAAY;IACtB;;OAEG;IACH,iCAAiB,CAAA;IACjB;;OAEG;IACH,iCAAiB,CAAA;IACjB;;;OAGG;IACH,4CAA4B,CAAA;IAC5B;;OAEG;IACH,iCAAiB,CAAA;AACnB,CAAC,EAlBW,YAAY,KAAZ,YAAY,QAkBvB;AAED,MAAM,CAAN,IAAY,eAGX;AAHD,WAAY,eAAe;IACzB,gCAAa,CAAA;IACb,kCAAe,CAAA;AACjB,CAAC,EAHW,eAAe,KAAf,eAAe,QAG1B;AAED,MAAM,CAAN,IAAY,gBAIX;AAJD,WAAY,gBAAgB;IAC1B,+BAAW,CAAA;IACX,qCAAiB,CAAA;IACjB,uCAAmB,CAAA;AACrB,CAAC,EAJW,gBAAgB,KAAhB,gBAAgB,QAI3B","sourcesContent":["import type { PermissionState } from '@capacitor/core';\n\nexport type CameraPermissionState = PermissionState | 'limited';\n\nexport type CameraPermissionType = 'camera' | 'photos';\n\nexport interface PermissionStatus {\n camera: CameraPermissionState;\n photos: CameraPermissionState;\n}\n\nexport interface CameraPluginPermissions {\n permissions: CameraPermissionType[];\n}\n\nexport interface CameraPlugin {\n /**\n * Prompt the user to pick a photo from an album, or take a new photo\n * with the camera.\n *\n * @since 1.0.0\n */\n getPhoto(options: ImageOptions): Promise;\n\n /**\n * Allows the user to pick multiplef pictures from the photo gallery.\n *\n * @since 1.2.0\n */\n pickImages(options: GalleryImageOptions): Promise;\n\n /**\n * Allows the user to update their limited photo library selection.\n * Returns all the limited photos after the picker dismissal.\n * If instead the user gave full access to the photos it returns an empty array.\n *\n * @since 4.1.0\n */\n pickLimitedLibraryPhotos(): Promise;\n /**\n * Return an array of photos selected from the limited photo library.\n *\n * @since 4.1.0\n */\n getLimitedLibraryPhotos(): Promise;\n\n /**\n * Check camera and photo album permissions\n *\n * @since 1.0.0\n */\n checkPermissions(): Promise;\n\n /**\n * Request camera and photo album permissions\n *\n * @since 1.0.0\n */\n requestPermissions(\n permissions?: CameraPluginPermissions,\n ): Promise;\n}\n\nexport interface ImageOptions {\n /**\n * The quality of image to return as JPEG, from 0-100\n * Note: This option is only supported on Android and iOS\n *\n * @since 1.0.0\n */\n quality?: number;\n /**\n * Whether to allow the user to crop or make small edits (platform specific).\n * On iOS it's only supported for CameraSource.Camera, but not for CameraSource.Photos.\n *\n * @since 1.0.0\n */\n allowEditing?: boolean;\n /**\n * How the data should be returned. Currently, only 'Base64', 'DataUrl' or 'Uri' is supported\n *\n * @since 1.0.0\n */\n resultType: CameraResultType;\n /**\n * Whether to save the photo to the gallery.\n * If the photo was picked from the gallery, it will only be saved if edited.\n * @default: false\n *\n * @since 1.0.0\n */\n saveToGallery?: boolean;\n /**\n * The desired maximum width of the saved image. The aspect ratio is respected.\n *\n * @since 1.0.0\n */\n width?: number;\n /**\n * The desired maximum height of the saved image. The aspect ratio is respected.\n *\n * @since 1.0.0\n */\n height?: number;\n /**\n * Whether to automatically rotate the image \"up\" to correct for orientation\n * in portrait mode\n * @default: true\n *\n * @since 1.0.0\n */\n correctOrientation?: boolean;\n /**\n * The source to get the photo from. By default this prompts the user to select\n * either the photo album or take a photo.\n * @default: CameraSource.Prompt\n *\n * @since 1.0.0\n */\n source?: CameraSource;\n /**\n * iOS and Web only: The camera direction.\n * @default: CameraDirection.Rear\n *\n * @since 1.0.0\n */\n direction?: CameraDirection;\n\n /**\n * iOS only: The presentation style of the Camera.\n * @default: 'fullscreen'\n *\n * @since 1.0.0\n */\n presentationStyle?: 'fullscreen' | 'popover';\n\n /**\n * Web only: Whether to use the PWA Element experience or file input. The\n * default is to use PWA Elements if installed and fall back to file input.\n * To always use file input, set this to `true`.\n *\n * Learn more about PWA Elements: https://capacitorjs.com/docs/web/pwa-elements\n *\n * @since 1.0.0\n */\n webUseInput?: boolean;\n\n /**\n * Text value to use when displaying the prompt.\n * @default: 'Photo'\n *\n * @since 1.0.0\n *\n */\n promptLabelHeader?: string;\n\n /**\n * Text value to use when displaying the prompt.\n * iOS only: The label of the 'cancel' button.\n * @default: 'Cancel'\n *\n * @since 1.0.0\n */\n promptLabelCancel?: string;\n\n /**\n * Text value to use when displaying the prompt.\n * The label of the button to select a saved image.\n * @default: 'From Photos'\n *\n * @since 1.0.0\n */\n promptLabelPhoto?: string;\n\n /**\n * Text value to use when displaying the prompt.\n * The label of the button to open the camera.\n * @default: 'Take Picture'\n *\n * @since 1.0.0\n */\n promptLabelPicture?: string;\n}\n\nexport interface Photo {\n /**\n * The base64 encoded string representation of the image, if using CameraResultType.Base64.\n *\n * @since 1.0.0\n */\n base64String?: string;\n /**\n * The url starting with 'data:image/jpeg;base64,' and the base64 encoded string representation of the image, if using CameraResultType.DataUrl.\n *\n * Note: On web, the file format could change depending on the browser.\n * @since 1.0.0\n */\n dataUrl?: string;\n /**\n * If using CameraResultType.Uri, the path will contain a full,\n * platform-specific file URL that can be read later using the Filesystem API.\n *\n * @since 1.0.0\n */\n path?: string;\n /**\n * webPath returns a path that can be used to set the src attribute of an image for efficient\n * loading and rendering.\n *\n * @since 1.0.0\n */\n webPath?: string;\n /**\n * Exif data, if any, retrieved from the image\n *\n * @since 1.0.0\n */\n exif?: any;\n /**\n * The format of the image, ex: jpeg, png, gif.\n *\n * iOS and Android only support jpeg.\n * Web supports jpeg, png and gif, but the exact availability may vary depending on the browser.\n * gif is only supported if `webUseInput` is set to `true` or if `source` is set to `Photos`.\n *\n * @since 1.0.0\n */\n format: string;\n /**\n * Whether if the image was saved to the gallery or not.\n *\n * On Android and iOS, saving to the gallery can fail if the user didn't\n * grant the required permissions.\n * On Web there is no gallery, so always returns false.\n *\n * @since 1.1.0\n */\n saved: boolean;\n}\n\nexport interface GalleryPhotos {\n /**\n * Array of all the picked photos.\n *\n * @since 1.2.0\n */\n photos: GalleryPhoto[];\n}\n\nexport interface GalleryPhoto {\n /**\n * Full, platform-specific file URL that can be read later using the Filesystem API.\n *\n * @since 1.2.0\n */\n path?: string;\n /**\n * webPath returns a path that can be used to set the src attribute of an image for efficient\n * loading and rendering.\n *\n * @since 1.2.0\n */\n webPath: string;\n /**\n * Exif data, if any, retrieved from the image\n *\n * @since 1.2.0\n */\n exif?: any;\n /**\n * The format of the image, ex: jpeg, png, gif.\n *\n * iOS and Android only support jpeg.\n * Web supports jpeg, png and gif.\n *\n * @since 1.2.0\n */\n format: string;\n}\nexport interface GalleryImageOptions {\n /**\n * The quality of image to return as JPEG, from 0-100\n * Note: This option is only supported on Android and iOS.\n *\n * @since 1.2.0\n */\n quality?: number;\n /**\n * The desired maximum width of the saved image. The aspect ratio is respected.\n *\n * @since 1.2.0\n */\n width?: number;\n /**\n * The desired maximum height of the saved image. The aspect ratio is respected.\n *\n * @since 1.2.0\n */\n height?: number;\n /**\n * Whether to automatically rotate the image \"up\" to correct for orientation\n * in portrait mode\n * @default: true\n *\n * @since 1.2.0\n */\n correctOrientation?: boolean;\n\n /**\n * iOS only: The presentation style of the Camera.\n * @default: 'fullscreen'\n *\n * @since 1.2.0\n */\n presentationStyle?: 'fullscreen' | 'popover';\n\n /**\n * Maximum number of pictures the user will be able to choose.\n * Note: This option is only supported on Android 13+ and iOS.\n *\n * @default 0 (unlimited)\n *\n * @since 1.2.0\n */\n limit?: number;\n}\n\nexport enum CameraSource {\n /**\n * Prompts the user to select either the photo album or take a photo.\n */\n Prompt = 'PROMPT',\n /**\n * Take a new photo using the camera.\n */\n Camera = 'CAMERA',\n /**\n * Take multiple photos in a row using the camera.\n * Available on Android and iOS.\n */\n CameraMulti = 'CAMERA_MULTI',\n /**\n * Pick an existing photo from the gallery or photo album.\n */\n Photos = 'PHOTOS',\n}\n\nexport enum CameraDirection {\n Rear = 'REAR',\n Front = 'FRONT',\n}\n\nexport enum CameraResultType {\n Uri = 'uri',\n Base64 = 'base64',\n DataUrl = 'dataUrl',\n}\n\n/**\n * @deprecated Use `Photo`.\n * @since 1.0.0\n */\nexport type CameraPhoto = Photo;\n\n/**\n * @deprecated Use `ImageOptions`.\n * @since 1.0.0\n */\nexport type CameraOptions = ImageOptions;\n"]} \ No newline at end of file diff --git a/camera/dist/esm/index.d.ts b/camera/dist/esm/index.d.ts new file mode 100644 index 0000000000..89d586e2ae --- /dev/null +++ b/camera/dist/esm/index.d.ts @@ -0,0 +1,4 @@ +import type { CameraPlugin } from './definitions'; +declare const Camera: CameraPlugin; +export * from './definitions'; +export { Camera }; diff --git a/camera/dist/esm/index.js b/camera/dist/esm/index.js new file mode 100644 index 0000000000..96fec990a7 --- /dev/null +++ b/camera/dist/esm/index.js @@ -0,0 +1,8 @@ +import { registerPlugin } from '@capacitor/core'; +import { CameraWeb } from './web'; +const Camera = registerPlugin('Camera', { + web: () => new CameraWeb(), +}); +export * from './definitions'; +export { Camera }; +//# sourceMappingURL=index.js.map \ No newline at end of file diff --git a/camera/dist/esm/index.js.map b/camera/dist/esm/index.js.map new file mode 100644 index 0000000000..2e62e1607e --- /dev/null +++ b/camera/dist/esm/index.js.map @@ -0,0 +1 @@ +{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,MAAM,iBAAiB,CAAC;AAGjD,OAAO,EAAE,SAAS,EAAE,MAAM,OAAO,CAAC;AAElC,MAAM,MAAM,GAAG,cAAc,CAAe,QAAQ,EAAE;IACpD,GAAG,EAAE,GAAG,EAAE,CAAC,IAAI,SAAS,EAAE;CAC3B,CAAC,CAAC;AAEH,cAAc,eAAe,CAAC;AAC9B,OAAO,EAAE,MAAM,EAAE,CAAC","sourcesContent":["import { registerPlugin } from '@capacitor/core';\n\nimport type { CameraPlugin } from './definitions';\nimport { CameraWeb } from './web';\n\nconst Camera = registerPlugin('Camera', {\n web: () => new CameraWeb(),\n});\n\nexport * from './definitions';\nexport { Camera };\n"]} \ No newline at end of file diff --git a/camera/dist/esm/web.d.ts b/camera/dist/esm/web.d.ts new file mode 100644 index 0000000000..6fbcc40586 --- /dev/null +++ b/camera/dist/esm/web.d.ts @@ -0,0 +1,16 @@ +import { WebPlugin } from '@capacitor/core'; +import type { CameraPlugin, GalleryImageOptions, GalleryPhotos, ImageOptions, PermissionStatus, Photo } from './definitions'; +export declare class CameraWeb extends WebPlugin implements CameraPlugin { + getPhoto(options: ImageOptions): Promise; + pickImages(_options: GalleryImageOptions): Promise; + private cameraExperience; + private fileInputExperience; + private multipleFileInputExperience; + private _getCameraPhoto; + checkPermissions(): Promise; + requestPermissions(): Promise; + pickLimitedLibraryPhotos(): Promise; + getLimitedLibraryPhotos(): Promise; +} +declare const Camera: CameraWeb; +export { Camera }; diff --git a/camera/dist/esm/web.js b/camera/dist/esm/web.js new file mode 100644 index 0000000000..2df609be8e --- /dev/null +++ b/camera/dist/esm/web.js @@ -0,0 +1,254 @@ +import { WebPlugin, CapacitorException } from '@capacitor/core'; +import { CameraSource, CameraDirection } from './definitions'; +export class CameraWeb extends WebPlugin { + async getPhoto(options) { + // 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 = document.querySelector('pwa-action-sheet'); + if (!actionSheet) { + actionSheet = document.createElement('pwa-action-sheet'); + document.body.appendChild(actionSheet); + } + actionSheet.header = options.promptLabelHeader || 'Photo'; + actionSheet.cancelable = false; + actionSheet.options = [ + { title: options.promptLabelPhoto || 'From Photos' }, + { title: options.promptLabelPicture || 'Take Picture' }, + ]; + actionSheet.addEventListener('onSelection', async (e) => { + const selection = e.detail; + if (selection === 0) { + this.fileInputExperience(options, resolve, reject); + } + else { + this.cameraExperience(options, resolve, reject); + } + }); + } + else { + this.cameraExperience(options, resolve, reject); + } + }); + } + async pickImages(_options) { + // eslint-disable-next-line no-async-promise-executor + return new Promise(async (resolve, reject) => { + this.multipleFileInputExperience(resolve, reject); + }); + } + async cameraExperience(options, resolve, reject) { + if (customElements.get('pwa-camera-modal')) { + const cameraModal = 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) => { + 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); + } + } + fileInputExperience(options, resolve, reject) { + let input = document.querySelector('#_capacitor-camera-input'); + const cleanup = () => { + var _a; + (_a = input.parentNode) === null || _a === void 0 ? void 0 : _a.removeChild(input); + }; + if (!input) { + input = document.createElement('input'); + input.id = '_capacitor-camera-input'; + input.type = 'file'; + input.hidden = true; + document.body.appendChild(input); + input.addEventListener('change', (_e) => { + 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, + }); + } + else if (options.resultType === 'base64') { + const b64 = reader.result.split(',')[1]; + resolve({ + base64String: b64, + format, + }); + } + cleanup(); + }); + reader.readAsDataURL(file); + } + else { + resolve({ + webPath: URL.createObjectURL(file), + format: format, + }); + cleanup(); + } + }); + input.addEventListener('cancel', (_e) => { + reject(new CapacitorException('User cancelled photos app')); + cleanup(); + }); + } + input.accept = 'image/*'; + input.capture = true; + if (options.source === CameraSource.Photos || + options.source === CameraSource.Prompt) { + input.removeAttribute('capture'); + } + else if (options.direction === CameraDirection.Front) { + input.capture = 'user'; + } + else if (options.direction === CameraDirection.Rear) { + input.capture = 'environment'; + } + input.click(); + } + multipleFileInputExperience(resolve, reject) { + let input = document.querySelector('#_capacitor-camera-input-multiple'); + const cleanup = () => { + var _a; + (_a = input.parentNode) === null || _a === void 0 ? void 0 : _a.removeChild(input); + }; + if (!input) { + input = document.createElement('input'); + input.id = '_capacitor-camera-input-multiple'; + input.type = 'file'; + input.hidden = true; + input.multiple = true; + document.body.appendChild(input); + input.addEventListener('change', (_e) => { + 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) => { + reject(new CapacitorException('User cancelled photos app')); + cleanup(); + }); + } + input.accept = 'image/*'; + input.click(); + } + _getCameraPhoto(photo, options) { + 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; + 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() { + 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 (_a) { + throw this.unavailable('Camera permissions are not available in this browser'); + } + } + async requestPermissions() { + throw this.unimplemented('Not implemented on web.'); + } + async pickLimitedLibraryPhotos() { + throw this.unavailable('Not implemented on web.'); + } + async getLimitedLibraryPhotos() { + throw this.unavailable('Not implemented on web.'); + } +} +const Camera = new CameraWeb(); +export { Camera }; +//# sourceMappingURL=web.js.map \ No newline at end of file diff --git a/camera/dist/esm/web.js.map b/camera/dist/esm/web.js.map new file mode 100644 index 0000000000..1fb22cc54d --- /dev/null +++ b/camera/dist/esm/web.js.map @@ -0,0 +1 @@ +{"version":3,"file":"web.js","sourceRoot":"","sources":["../../src/web.ts"],"names":[],"mappings":"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","sourcesContent":["import { WebPlugin, CapacitorException } from '@capacitor/core';\n\nimport { CameraSource, CameraDirection } from './definitions';\nimport type {\n CameraPlugin,\n GalleryImageOptions,\n GalleryPhotos,\n ImageOptions,\n PermissionStatus,\n Photo,\n} from './definitions';\n\nexport class CameraWeb extends WebPlugin implements CameraPlugin {\n async getPhoto(options: ImageOptions): Promise {\n // eslint-disable-next-line no-async-promise-executor\n return new Promise(async (resolve, reject) => {\n if (options.webUseInput || options.source === CameraSource.Photos) {\n this.fileInputExperience(options, resolve, reject);\n } else if (options.source === CameraSource.Prompt) {\n let actionSheet: any = document.querySelector('pwa-action-sheet');\n if (!actionSheet) {\n actionSheet = document.createElement('pwa-action-sheet');\n document.body.appendChild(actionSheet);\n }\n actionSheet.header = options.promptLabelHeader || 'Photo';\n actionSheet.cancelable = false;\n actionSheet.options = [\n { title: options.promptLabelPhoto || 'From Photos' },\n { title: options.promptLabelPicture || 'Take Picture' },\n ];\n actionSheet.addEventListener('onSelection', async (e: any) => {\n const selection = e.detail;\n if (selection === 0) {\n this.fileInputExperience(options, resolve, reject);\n } else {\n this.cameraExperience(options, resolve, reject);\n }\n });\n } else {\n this.cameraExperience(options, resolve, reject);\n }\n });\n }\n\n async pickImages(_options: GalleryImageOptions): Promise {\n // eslint-disable-next-line no-async-promise-executor\n return new Promise(async (resolve, reject) => {\n this.multipleFileInputExperience(resolve, reject);\n });\n }\n\n private async cameraExperience(\n options: ImageOptions,\n resolve: any,\n reject: any,\n ) {\n if (customElements.get('pwa-camera-modal')) {\n const cameraModal: any = document.createElement('pwa-camera-modal');\n cameraModal.facingMode =\n options.direction === CameraDirection.Front ? 'user' : 'environment';\n document.body.appendChild(cameraModal);\n try {\n await cameraModal.componentOnReady();\n cameraModal.addEventListener('onPhoto', async (e: any) => {\n const photo = e.detail;\n\n if (photo === null) {\n reject(new CapacitorException('User cancelled photos app'));\n } else if (photo instanceof Error) {\n reject(photo);\n } else {\n resolve(await this._getCameraPhoto(photo, options));\n }\n\n cameraModal.dismiss();\n document.body.removeChild(cameraModal);\n });\n\n cameraModal.present();\n } catch (e) {\n this.fileInputExperience(options, resolve, reject);\n }\n } else {\n console.error(\n `Unable to load PWA Element 'pwa-camera-modal'. See the docs: https://capacitorjs.com/docs/web/pwa-elements.`,\n );\n this.fileInputExperience(options, resolve, reject);\n }\n }\n\n private fileInputExperience(\n options: ImageOptions,\n resolve: any,\n reject: any,\n ) {\n let input = document.querySelector(\n '#_capacitor-camera-input',\n ) as HTMLInputElement;\n\n const cleanup = () => {\n input.parentNode?.removeChild(input);\n };\n\n if (!input) {\n input = document.createElement('input') as HTMLInputElement;\n input.id = '_capacitor-camera-input';\n input.type = 'file';\n input.hidden = true;\n document.body.appendChild(input);\n input.addEventListener('change', (_e: any) => {\n const file = input.files![0];\n let format = 'jpeg';\n\n if (file.type === 'image/png') {\n format = 'png';\n } else if (file.type === 'image/gif') {\n format = 'gif';\n }\n\n if (\n options.resultType === 'dataUrl' ||\n options.resultType === 'base64'\n ) {\n const reader = new FileReader();\n\n reader.addEventListener('load', () => {\n if (options.resultType === 'dataUrl') {\n resolve({\n dataUrl: reader.result,\n format,\n } as Photo);\n } else if (options.resultType === 'base64') {\n const b64 = (reader.result as string).split(',')[1];\n resolve({\n base64String: b64,\n format,\n } as Photo);\n }\n\n cleanup();\n });\n\n reader.readAsDataURL(file);\n } else {\n resolve({\n webPath: URL.createObjectURL(file),\n format: format,\n });\n cleanup();\n }\n });\n input.addEventListener('cancel', (_e: any) => {\n reject(new CapacitorException('User cancelled photos app'));\n cleanup();\n });\n }\n\n input.accept = 'image/*';\n (input as any).capture = true;\n\n if (\n options.source === CameraSource.Photos ||\n options.source === CameraSource.Prompt\n ) {\n input.removeAttribute('capture');\n } else if (options.direction === CameraDirection.Front) {\n (input as any).capture = 'user';\n } else if (options.direction === CameraDirection.Rear) {\n (input as any).capture = 'environment';\n }\n\n input.click();\n }\n\n private multipleFileInputExperience(resolve: any, reject: any) {\n let input = document.querySelector(\n '#_capacitor-camera-input-multiple',\n ) as HTMLInputElement;\n\n const cleanup = () => {\n input.parentNode?.removeChild(input);\n };\n\n if (!input) {\n input = document.createElement('input') as HTMLInputElement;\n input.id = '_capacitor-camera-input-multiple';\n input.type = 'file';\n input.hidden = true;\n input.multiple = true;\n document.body.appendChild(input);\n input.addEventListener('change', (_e: any) => {\n const photos = [];\n // eslint-disable-next-line @typescript-eslint/prefer-for-of\n for (let i = 0; i < input.files!.length; i++) {\n const file = input.files![i];\n let format = 'jpeg';\n\n if (file.type === 'image/png') {\n format = 'png';\n } else if (file.type === 'image/gif') {\n format = 'gif';\n }\n photos.push({\n webPath: URL.createObjectURL(file),\n format: format,\n });\n }\n resolve({ photos });\n cleanup();\n });\n input.addEventListener('cancel', (_e: any) => {\n reject(new CapacitorException('User cancelled photos app'));\n cleanup();\n });\n }\n\n input.accept = 'image/*';\n\n input.click();\n }\n\n private _getCameraPhoto(photo: Blob, options: ImageOptions) {\n return new Promise((resolve, reject) => {\n const reader = new FileReader();\n const format = photo.type.split('/')[1];\n if (options.resultType === 'uri') {\n resolve({\n webPath: URL.createObjectURL(photo),\n format: format,\n saved: false,\n });\n } else {\n reader.readAsDataURL(photo);\n reader.onloadend = () => {\n const r = reader.result as string;\n if (options.resultType === 'dataUrl') {\n resolve({\n dataUrl: r,\n format: format,\n saved: false,\n });\n } else {\n resolve({\n base64String: r.split(',')[1],\n format: format,\n saved: false,\n });\n }\n };\n reader.onerror = e => {\n reject(e);\n };\n }\n });\n }\n\n async checkPermissions(): Promise {\n if (typeof navigator === 'undefined' || !navigator.permissions) {\n throw this.unavailable('Permissions API not available in this browser');\n }\n\n try {\n // https://developer.mozilla.org/en-US/docs/Web/API/Permissions/query\n // the specific permissions that are supported varies among browsers that implement the\n // permissions API, so we need a try/catch in case 'camera' is invalid\n const permission = await window.navigator.permissions.query({\n name: 'camera',\n });\n return {\n camera: permission.state,\n photos: 'granted',\n };\n } catch {\n throw this.unavailable(\n 'Camera permissions are not available in this browser',\n );\n }\n }\n\n async requestPermissions(): Promise {\n throw this.unimplemented('Not implemented on web.');\n }\n\n async pickLimitedLibraryPhotos(): Promise {\n throw this.unavailable('Not implemented on web.');\n }\n\n async getLimitedLibraryPhotos(): Promise {\n throw this.unavailable('Not implemented on web.');\n }\n}\n\nconst Camera = new CameraWeb();\n\nexport { Camera };\n"]} \ No newline at end of file diff --git a/camera/dist/plugin.cjs.js b/camera/dist/plugin.cjs.js new file mode 100644 index 0000000000..3101a95322 --- /dev/null +++ b/camera/dist/plugin.cjs.js @@ -0,0 +1,293 @@ +'use strict'; + +var core = require('@capacitor/core'); + +exports.CameraSource = void 0; +(function (CameraSource) { + /** + * Prompts the user to select either the photo album or take a photo. + */ + CameraSource["Prompt"] = "PROMPT"; + /** + * Take a new photo using the camera. + */ + CameraSource["Camera"] = "CAMERA"; + /** + * Take multiple photos in a row using the camera. + * Available on Android and iOS. + */ + CameraSource["CameraMulti"] = "CAMERA_MULTI"; + /** + * Pick an existing photo from the gallery or photo album. + */ + CameraSource["Photos"] = "PHOTOS"; +})(exports.CameraSource || (exports.CameraSource = {})); +exports.CameraDirection = void 0; +(function (CameraDirection) { + CameraDirection["Rear"] = "REAR"; + CameraDirection["Front"] = "FRONT"; +})(exports.CameraDirection || (exports.CameraDirection = {})); +exports.CameraResultType = void 0; +(function (CameraResultType) { + CameraResultType["Uri"] = "uri"; + CameraResultType["Base64"] = "base64"; + CameraResultType["DataUrl"] = "dataUrl"; +})(exports.CameraResultType || (exports.CameraResultType = {})); + +class CameraWeb extends core.WebPlugin { + async getPhoto(options) { + // eslint-disable-next-line no-async-promise-executor + return new Promise(async (resolve, reject) => { + if (options.webUseInput || options.source === exports.CameraSource.Photos) { + this.fileInputExperience(options, resolve, reject); + } + else if (options.source === exports.CameraSource.Prompt) { + let actionSheet = document.querySelector('pwa-action-sheet'); + if (!actionSheet) { + actionSheet = document.createElement('pwa-action-sheet'); + document.body.appendChild(actionSheet); + } + actionSheet.header = options.promptLabelHeader || 'Photo'; + actionSheet.cancelable = false; + actionSheet.options = [ + { title: options.promptLabelPhoto || 'From Photos' }, + { title: options.promptLabelPicture || 'Take Picture' }, + ]; + actionSheet.addEventListener('onSelection', async (e) => { + const selection = e.detail; + if (selection === 0) { + this.fileInputExperience(options, resolve, reject); + } + else { + this.cameraExperience(options, resolve, reject); + } + }); + } + else { + this.cameraExperience(options, resolve, reject); + } + }); + } + async pickImages(_options) { + // eslint-disable-next-line no-async-promise-executor + return new Promise(async (resolve, reject) => { + this.multipleFileInputExperience(resolve, reject); + }); + } + async cameraExperience(options, resolve, reject) { + if (customElements.get('pwa-camera-modal')) { + const cameraModal = document.createElement('pwa-camera-modal'); + cameraModal.facingMode = + options.direction === exports.CameraDirection.Front ? 'user' : 'environment'; + document.body.appendChild(cameraModal); + try { + await cameraModal.componentOnReady(); + cameraModal.addEventListener('onPhoto', async (e) => { + const photo = e.detail; + if (photo === null) { + reject(new core.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); + } + } + fileInputExperience(options, resolve, reject) { + let input = document.querySelector('#_capacitor-camera-input'); + const cleanup = () => { + var _a; + (_a = input.parentNode) === null || _a === void 0 ? void 0 : _a.removeChild(input); + }; + if (!input) { + input = document.createElement('input'); + input.id = '_capacitor-camera-input'; + input.type = 'file'; + input.hidden = true; + document.body.appendChild(input); + input.addEventListener('change', (_e) => { + 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, + }); + } + else if (options.resultType === 'base64') { + const b64 = reader.result.split(',')[1]; + resolve({ + base64String: b64, + format, + }); + } + cleanup(); + }); + reader.readAsDataURL(file); + } + else { + resolve({ + webPath: URL.createObjectURL(file), + format: format, + }); + cleanup(); + } + }); + input.addEventListener('cancel', (_e) => { + reject(new core.CapacitorException('User cancelled photos app')); + cleanup(); + }); + } + input.accept = 'image/*'; + input.capture = true; + if (options.source === exports.CameraSource.Photos || + options.source === exports.CameraSource.Prompt) { + input.removeAttribute('capture'); + } + else if (options.direction === exports.CameraDirection.Front) { + input.capture = 'user'; + } + else if (options.direction === exports.CameraDirection.Rear) { + input.capture = 'environment'; + } + input.click(); + } + multipleFileInputExperience(resolve, reject) { + let input = document.querySelector('#_capacitor-camera-input-multiple'); + const cleanup = () => { + var _a; + (_a = input.parentNode) === null || _a === void 0 ? void 0 : _a.removeChild(input); + }; + if (!input) { + input = document.createElement('input'); + input.id = '_capacitor-camera-input-multiple'; + input.type = 'file'; + input.hidden = true; + input.multiple = true; + document.body.appendChild(input); + input.addEventListener('change', (_e) => { + 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) => { + reject(new core.CapacitorException('User cancelled photos app')); + cleanup(); + }); + } + input.accept = 'image/*'; + input.click(); + } + _getCameraPhoto(photo, options) { + 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; + 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() { + 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 (_a) { + throw this.unavailable('Camera permissions are not available in this browser'); + } + } + async requestPermissions() { + throw this.unimplemented('Not implemented on web.'); + } + async pickLimitedLibraryPhotos() { + throw this.unavailable('Not implemented on web.'); + } + async getLimitedLibraryPhotos() { + throw this.unavailable('Not implemented on web.'); + } +} +new CameraWeb(); + +const Camera = core.registerPlugin('Camera', { + web: () => new CameraWeb(), +}); + +exports.Camera = Camera; +//# sourceMappingURL=plugin.cjs.js.map diff --git a/camera/dist/plugin.cjs.js.map b/camera/dist/plugin.cjs.js.map new file mode 100644 index 0000000000..5f4e4b51d5 --- /dev/null +++ b/camera/dist/plugin.cjs.js.map @@ -0,0 +1 @@ +{"version":3,"file":"plugin.cjs.js","sources":["esm/definitions.js","esm/web.js","esm/index.js"],"sourcesContent":["export var CameraSource;\n(function (CameraSource) {\n /**\n * Prompts the user to select either the photo album or take a photo.\n */\n CameraSource[\"Prompt\"] = \"PROMPT\";\n /**\n * Take a new photo using the camera.\n */\n CameraSource[\"Camera\"] = \"CAMERA\";\n /**\n * Take multiple photos in a row using the camera.\n * Available on Android and iOS.\n */\n CameraSource[\"CameraMulti\"] = \"CAMERA_MULTI\";\n /**\n * Pick an existing photo from the gallery or photo album.\n */\n CameraSource[\"Photos\"] = \"PHOTOS\";\n})(CameraSource || (CameraSource = {}));\nexport var CameraDirection;\n(function (CameraDirection) {\n CameraDirection[\"Rear\"] = \"REAR\";\n CameraDirection[\"Front\"] = \"FRONT\";\n})(CameraDirection || (CameraDirection = {}));\nexport var CameraResultType;\n(function (CameraResultType) {\n CameraResultType[\"Uri\"] = \"uri\";\n CameraResultType[\"Base64\"] = \"base64\";\n CameraResultType[\"DataUrl\"] = \"dataUrl\";\n})(CameraResultType || (CameraResultType = {}));\n//# sourceMappingURL=definitions.js.map","import { WebPlugin, CapacitorException } from '@capacitor/core';\nimport { CameraSource, CameraDirection } from './definitions';\nexport class CameraWeb extends WebPlugin {\n async getPhoto(options) {\n // eslint-disable-next-line no-async-promise-executor\n return new Promise(async (resolve, reject) => {\n if (options.webUseInput || options.source === CameraSource.Photos) {\n this.fileInputExperience(options, resolve, reject);\n }\n else if (options.source === CameraSource.Prompt) {\n let actionSheet = document.querySelector('pwa-action-sheet');\n if (!actionSheet) {\n actionSheet = document.createElement('pwa-action-sheet');\n document.body.appendChild(actionSheet);\n }\n actionSheet.header = options.promptLabelHeader || 'Photo';\n actionSheet.cancelable = false;\n actionSheet.options = [\n { title: options.promptLabelPhoto || 'From Photos' },\n { title: options.promptLabelPicture || 'Take Picture' },\n ];\n actionSheet.addEventListener('onSelection', async (e) => {\n const selection = e.detail;\n if (selection === 0) {\n this.fileInputExperience(options, resolve, reject);\n }\n else {\n this.cameraExperience(options, resolve, reject);\n }\n });\n }\n else {\n this.cameraExperience(options, resolve, reject);\n }\n });\n }\n async pickImages(_options) {\n // eslint-disable-next-line no-async-promise-executor\n return new Promise(async (resolve, reject) => {\n this.multipleFileInputExperience(resolve, reject);\n });\n }\n async cameraExperience(options, resolve, reject) {\n if (customElements.get('pwa-camera-modal')) {\n const cameraModal = document.createElement('pwa-camera-modal');\n cameraModal.facingMode =\n options.direction === CameraDirection.Front ? 'user' : 'environment';\n document.body.appendChild(cameraModal);\n try {\n await cameraModal.componentOnReady();\n cameraModal.addEventListener('onPhoto', async (e) => {\n const photo = e.detail;\n if (photo === null) {\n reject(new CapacitorException('User cancelled photos app'));\n }\n else if (photo instanceof Error) {\n reject(photo);\n }\n else {\n resolve(await this._getCameraPhoto(photo, options));\n }\n cameraModal.dismiss();\n document.body.removeChild(cameraModal);\n });\n cameraModal.present();\n }\n catch (e) {\n this.fileInputExperience(options, resolve, reject);\n }\n }\n else {\n console.error(`Unable to load PWA Element 'pwa-camera-modal'. See the docs: https://capacitorjs.com/docs/web/pwa-elements.`);\n this.fileInputExperience(options, resolve, reject);\n }\n }\n fileInputExperience(options, resolve, reject) {\n let input = document.querySelector('#_capacitor-camera-input');\n const cleanup = () => {\n var _a;\n (_a = input.parentNode) === null || _a === void 0 ? void 0 : _a.removeChild(input);\n };\n if (!input) {\n input = document.createElement('input');\n input.id = '_capacitor-camera-input';\n input.type = 'file';\n input.hidden = true;\n document.body.appendChild(input);\n input.addEventListener('change', (_e) => {\n const file = input.files[0];\n let format = 'jpeg';\n if (file.type === 'image/png') {\n format = 'png';\n }\n else if (file.type === 'image/gif') {\n format = 'gif';\n }\n if (options.resultType === 'dataUrl' ||\n options.resultType === 'base64') {\n const reader = new FileReader();\n reader.addEventListener('load', () => {\n if (options.resultType === 'dataUrl') {\n resolve({\n dataUrl: reader.result,\n format,\n });\n }\n else if (options.resultType === 'base64') {\n const b64 = reader.result.split(',')[1];\n resolve({\n base64String: b64,\n format,\n });\n }\n cleanup();\n });\n reader.readAsDataURL(file);\n }\n else {\n resolve({\n webPath: URL.createObjectURL(file),\n format: format,\n });\n cleanup();\n }\n });\n input.addEventListener('cancel', (_e) => {\n reject(new CapacitorException('User cancelled photos app'));\n cleanup();\n });\n }\n input.accept = 'image/*';\n input.capture = true;\n if (options.source === CameraSource.Photos ||\n options.source === CameraSource.Prompt) {\n input.removeAttribute('capture');\n }\n else if (options.direction === CameraDirection.Front) {\n input.capture = 'user';\n }\n else if (options.direction === CameraDirection.Rear) {\n input.capture = 'environment';\n }\n input.click();\n }\n multipleFileInputExperience(resolve, reject) {\n let input = document.querySelector('#_capacitor-camera-input-multiple');\n const cleanup = () => {\n var _a;\n (_a = input.parentNode) === null || _a === void 0 ? void 0 : _a.removeChild(input);\n };\n if (!input) {\n input = document.createElement('input');\n input.id = '_capacitor-camera-input-multiple';\n input.type = 'file';\n input.hidden = true;\n input.multiple = true;\n document.body.appendChild(input);\n input.addEventListener('change', (_e) => {\n const photos = [];\n // eslint-disable-next-line @typescript-eslint/prefer-for-of\n for (let i = 0; i < input.files.length; i++) {\n const file = input.files[i];\n let format = 'jpeg';\n if (file.type === 'image/png') {\n format = 'png';\n }\n else if (file.type === 'image/gif') {\n format = 'gif';\n }\n photos.push({\n webPath: URL.createObjectURL(file),\n format: format,\n });\n }\n resolve({ photos });\n cleanup();\n });\n input.addEventListener('cancel', (_e) => {\n reject(new CapacitorException('User cancelled photos app'));\n cleanup();\n });\n }\n input.accept = 'image/*';\n input.click();\n }\n _getCameraPhoto(photo, options) {\n return new Promise((resolve, reject) => {\n const reader = new FileReader();\n const format = photo.type.split('/')[1];\n if (options.resultType === 'uri') {\n resolve({\n webPath: URL.createObjectURL(photo),\n format: format,\n saved: false,\n });\n }\n else {\n reader.readAsDataURL(photo);\n reader.onloadend = () => {\n const r = reader.result;\n if (options.resultType === 'dataUrl') {\n resolve({\n dataUrl: r,\n format: format,\n saved: false,\n });\n }\n else {\n resolve({\n base64String: r.split(',')[1],\n format: format,\n saved: false,\n });\n }\n };\n reader.onerror = e => {\n reject(e);\n };\n }\n });\n }\n async checkPermissions() {\n if (typeof navigator === 'undefined' || !navigator.permissions) {\n throw this.unavailable('Permissions API not available in this browser');\n }\n try {\n // https://developer.mozilla.org/en-US/docs/Web/API/Permissions/query\n // the specific permissions that are supported varies among browsers that implement the\n // permissions API, so we need a try/catch in case 'camera' is invalid\n const permission = await window.navigator.permissions.query({\n name: 'camera',\n });\n return {\n camera: permission.state,\n photos: 'granted',\n };\n }\n catch (_a) {\n throw this.unavailable('Camera permissions are not available in this browser');\n }\n }\n async requestPermissions() {\n throw this.unimplemented('Not implemented on web.');\n }\n async pickLimitedLibraryPhotos() {\n throw this.unavailable('Not implemented on web.');\n }\n async getLimitedLibraryPhotos() {\n throw this.unavailable('Not implemented on web.');\n }\n}\nconst Camera = new CameraWeb();\nexport { Camera };\n//# sourceMappingURL=web.js.map","import { registerPlugin } from '@capacitor/core';\nimport { CameraWeb } from './web';\nconst Camera = registerPlugin('Camera', {\n web: () => new CameraWeb(),\n});\nexport * from './definitions';\nexport { Camera };\n//# sourceMappingURL=index.js.map"],"names":["CameraSource","CameraDirection","CameraResultType","WebPlugin","CapacitorException","registerPlugin"],"mappings":"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"} \ No newline at end of file diff --git a/camera/dist/plugin.js b/camera/dist/plugin.js new file mode 100644 index 0000000000..36c7ffbc03 --- /dev/null +++ b/camera/dist/plugin.js @@ -0,0 +1,296 @@ +var capacitorCamera = (function (exports, core) { + 'use strict'; + + exports.CameraSource = void 0; + (function (CameraSource) { + /** + * Prompts the user to select either the photo album or take a photo. + */ + CameraSource["Prompt"] = "PROMPT"; + /** + * Take a new photo using the camera. + */ + CameraSource["Camera"] = "CAMERA"; + /** + * Take multiple photos in a row using the camera. + * Available on Android and iOS. + */ + CameraSource["CameraMulti"] = "CAMERA_MULTI"; + /** + * Pick an existing photo from the gallery or photo album. + */ + CameraSource["Photos"] = "PHOTOS"; + })(exports.CameraSource || (exports.CameraSource = {})); + exports.CameraDirection = void 0; + (function (CameraDirection) { + CameraDirection["Rear"] = "REAR"; + CameraDirection["Front"] = "FRONT"; + })(exports.CameraDirection || (exports.CameraDirection = {})); + exports.CameraResultType = void 0; + (function (CameraResultType) { + CameraResultType["Uri"] = "uri"; + CameraResultType["Base64"] = "base64"; + CameraResultType["DataUrl"] = "dataUrl"; + })(exports.CameraResultType || (exports.CameraResultType = {})); + + class CameraWeb extends core.WebPlugin { + async getPhoto(options) { + // eslint-disable-next-line no-async-promise-executor + return new Promise(async (resolve, reject) => { + if (options.webUseInput || options.source === exports.CameraSource.Photos) { + this.fileInputExperience(options, resolve, reject); + } + else if (options.source === exports.CameraSource.Prompt) { + let actionSheet = document.querySelector('pwa-action-sheet'); + if (!actionSheet) { + actionSheet = document.createElement('pwa-action-sheet'); + document.body.appendChild(actionSheet); + } + actionSheet.header = options.promptLabelHeader || 'Photo'; + actionSheet.cancelable = false; + actionSheet.options = [ + { title: options.promptLabelPhoto || 'From Photos' }, + { title: options.promptLabelPicture || 'Take Picture' }, + ]; + actionSheet.addEventListener('onSelection', async (e) => { + const selection = e.detail; + if (selection === 0) { + this.fileInputExperience(options, resolve, reject); + } + else { + this.cameraExperience(options, resolve, reject); + } + }); + } + else { + this.cameraExperience(options, resolve, reject); + } + }); + } + async pickImages(_options) { + // eslint-disable-next-line no-async-promise-executor + return new Promise(async (resolve, reject) => { + this.multipleFileInputExperience(resolve, reject); + }); + } + async cameraExperience(options, resolve, reject) { + if (customElements.get('pwa-camera-modal')) { + const cameraModal = document.createElement('pwa-camera-modal'); + cameraModal.facingMode = + options.direction === exports.CameraDirection.Front ? 'user' : 'environment'; + document.body.appendChild(cameraModal); + try { + await cameraModal.componentOnReady(); + cameraModal.addEventListener('onPhoto', async (e) => { + const photo = e.detail; + if (photo === null) { + reject(new core.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); + } + } + fileInputExperience(options, resolve, reject) { + let input = document.querySelector('#_capacitor-camera-input'); + const cleanup = () => { + var _a; + (_a = input.parentNode) === null || _a === void 0 ? void 0 : _a.removeChild(input); + }; + if (!input) { + input = document.createElement('input'); + input.id = '_capacitor-camera-input'; + input.type = 'file'; + input.hidden = true; + document.body.appendChild(input); + input.addEventListener('change', (_e) => { + 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, + }); + } + else if (options.resultType === 'base64') { + const b64 = reader.result.split(',')[1]; + resolve({ + base64String: b64, + format, + }); + } + cleanup(); + }); + reader.readAsDataURL(file); + } + else { + resolve({ + webPath: URL.createObjectURL(file), + format: format, + }); + cleanup(); + } + }); + input.addEventListener('cancel', (_e) => { + reject(new core.CapacitorException('User cancelled photos app')); + cleanup(); + }); + } + input.accept = 'image/*'; + input.capture = true; + if (options.source === exports.CameraSource.Photos || + options.source === exports.CameraSource.Prompt) { + input.removeAttribute('capture'); + } + else if (options.direction === exports.CameraDirection.Front) { + input.capture = 'user'; + } + else if (options.direction === exports.CameraDirection.Rear) { + input.capture = 'environment'; + } + input.click(); + } + multipleFileInputExperience(resolve, reject) { + let input = document.querySelector('#_capacitor-camera-input-multiple'); + const cleanup = () => { + var _a; + (_a = input.parentNode) === null || _a === void 0 ? void 0 : _a.removeChild(input); + }; + if (!input) { + input = document.createElement('input'); + input.id = '_capacitor-camera-input-multiple'; + input.type = 'file'; + input.hidden = true; + input.multiple = true; + document.body.appendChild(input); + input.addEventListener('change', (_e) => { + 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) => { + reject(new core.CapacitorException('User cancelled photos app')); + cleanup(); + }); + } + input.accept = 'image/*'; + input.click(); + } + _getCameraPhoto(photo, options) { + 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; + 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() { + 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 (_a) { + throw this.unavailable('Camera permissions are not available in this browser'); + } + } + async requestPermissions() { + throw this.unimplemented('Not implemented on web.'); + } + async pickLimitedLibraryPhotos() { + throw this.unavailable('Not implemented on web.'); + } + async getLimitedLibraryPhotos() { + throw this.unavailable('Not implemented on web.'); + } + } + new CameraWeb(); + + const Camera = core.registerPlugin('Camera', { + web: () => new CameraWeb(), + }); + + exports.Camera = Camera; + + return exports; + +})({}, capacitorExports); +//# sourceMappingURL=plugin.js.map diff --git a/camera/dist/plugin.js.map b/camera/dist/plugin.js.map new file mode 100644 index 0000000000..ff736918d2 --- /dev/null +++ b/camera/dist/plugin.js.map @@ -0,0 +1 @@ +{"version":3,"file":"plugin.js","sources":["esm/definitions.js","esm/web.js","esm/index.js"],"sourcesContent":["export var CameraSource;\n(function (CameraSource) {\n /**\n * Prompts the user to select either the photo album or take a photo.\n */\n CameraSource[\"Prompt\"] = \"PROMPT\";\n /**\n * Take a new photo using the camera.\n */\n CameraSource[\"Camera\"] = \"CAMERA\";\n /**\n * Take multiple photos in a row using the camera.\n * Available on Android and iOS.\n */\n CameraSource[\"CameraMulti\"] = \"CAMERA_MULTI\";\n /**\n * Pick an existing photo from the gallery or photo album.\n */\n CameraSource[\"Photos\"] = \"PHOTOS\";\n})(CameraSource || (CameraSource = {}));\nexport var CameraDirection;\n(function (CameraDirection) {\n CameraDirection[\"Rear\"] = \"REAR\";\n CameraDirection[\"Front\"] = \"FRONT\";\n})(CameraDirection || (CameraDirection = {}));\nexport var CameraResultType;\n(function (CameraResultType) {\n CameraResultType[\"Uri\"] = \"uri\";\n CameraResultType[\"Base64\"] = \"base64\";\n CameraResultType[\"DataUrl\"] = \"dataUrl\";\n})(CameraResultType || (CameraResultType = {}));\n//# sourceMappingURL=definitions.js.map","import { WebPlugin, CapacitorException } from '@capacitor/core';\nimport { CameraSource, CameraDirection } from './definitions';\nexport class CameraWeb extends WebPlugin {\n async getPhoto(options) {\n // eslint-disable-next-line no-async-promise-executor\n return new Promise(async (resolve, reject) => {\n if (options.webUseInput || options.source === CameraSource.Photos) {\n this.fileInputExperience(options, resolve, reject);\n }\n else if (options.source === CameraSource.Prompt) {\n let actionSheet = document.querySelector('pwa-action-sheet');\n if (!actionSheet) {\n actionSheet = document.createElement('pwa-action-sheet');\n document.body.appendChild(actionSheet);\n }\n actionSheet.header = options.promptLabelHeader || 'Photo';\n actionSheet.cancelable = false;\n actionSheet.options = [\n { title: options.promptLabelPhoto || 'From Photos' },\n { title: options.promptLabelPicture || 'Take Picture' },\n ];\n actionSheet.addEventListener('onSelection', async (e) => {\n const selection = e.detail;\n if (selection === 0) {\n this.fileInputExperience(options, resolve, reject);\n }\n else {\n this.cameraExperience(options, resolve, reject);\n }\n });\n }\n else {\n this.cameraExperience(options, resolve, reject);\n }\n });\n }\n async pickImages(_options) {\n // eslint-disable-next-line no-async-promise-executor\n return new Promise(async (resolve, reject) => {\n this.multipleFileInputExperience(resolve, reject);\n });\n }\n async cameraExperience(options, resolve, reject) {\n if (customElements.get('pwa-camera-modal')) {\n const cameraModal = document.createElement('pwa-camera-modal');\n cameraModal.facingMode =\n options.direction === CameraDirection.Front ? 'user' : 'environment';\n document.body.appendChild(cameraModal);\n try {\n await cameraModal.componentOnReady();\n cameraModal.addEventListener('onPhoto', async (e) => {\n const photo = e.detail;\n if (photo === null) {\n reject(new CapacitorException('User cancelled photos app'));\n }\n else if (photo instanceof Error) {\n reject(photo);\n }\n else {\n resolve(await this._getCameraPhoto(photo, options));\n }\n cameraModal.dismiss();\n document.body.removeChild(cameraModal);\n });\n cameraModal.present();\n }\n catch (e) {\n this.fileInputExperience(options, resolve, reject);\n }\n }\n else {\n console.error(`Unable to load PWA Element 'pwa-camera-modal'. See the docs: https://capacitorjs.com/docs/web/pwa-elements.`);\n this.fileInputExperience(options, resolve, reject);\n }\n }\n fileInputExperience(options, resolve, reject) {\n let input = document.querySelector('#_capacitor-camera-input');\n const cleanup = () => {\n var _a;\n (_a = input.parentNode) === null || _a === void 0 ? void 0 : _a.removeChild(input);\n };\n if (!input) {\n input = document.createElement('input');\n input.id = '_capacitor-camera-input';\n input.type = 'file';\n input.hidden = true;\n document.body.appendChild(input);\n input.addEventListener('change', (_e) => {\n const file = input.files[0];\n let format = 'jpeg';\n if (file.type === 'image/png') {\n format = 'png';\n }\n else if (file.type === 'image/gif') {\n format = 'gif';\n }\n if (options.resultType === 'dataUrl' ||\n options.resultType === 'base64') {\n const reader = new FileReader();\n reader.addEventListener('load', () => {\n if (options.resultType === 'dataUrl') {\n resolve({\n dataUrl: reader.result,\n format,\n });\n }\n else if (options.resultType === 'base64') {\n const b64 = reader.result.split(',')[1];\n resolve({\n base64String: b64,\n format,\n });\n }\n cleanup();\n });\n reader.readAsDataURL(file);\n }\n else {\n resolve({\n webPath: URL.createObjectURL(file),\n format: format,\n });\n cleanup();\n }\n });\n input.addEventListener('cancel', (_e) => {\n reject(new CapacitorException('User cancelled photos app'));\n cleanup();\n });\n }\n input.accept = 'image/*';\n input.capture = true;\n if (options.source === CameraSource.Photos ||\n options.source === CameraSource.Prompt) {\n input.removeAttribute('capture');\n }\n else if (options.direction === CameraDirection.Front) {\n input.capture = 'user';\n }\n else if (options.direction === CameraDirection.Rear) {\n input.capture = 'environment';\n }\n input.click();\n }\n multipleFileInputExperience(resolve, reject) {\n let input = document.querySelector('#_capacitor-camera-input-multiple');\n const cleanup = () => {\n var _a;\n (_a = input.parentNode) === null || _a === void 0 ? void 0 : _a.removeChild(input);\n };\n if (!input) {\n input = document.createElement('input');\n input.id = '_capacitor-camera-input-multiple';\n input.type = 'file';\n input.hidden = true;\n input.multiple = true;\n document.body.appendChild(input);\n input.addEventListener('change', (_e) => {\n const photos = [];\n // eslint-disable-next-line @typescript-eslint/prefer-for-of\n for (let i = 0; i < input.files.length; i++) {\n const file = input.files[i];\n let format = 'jpeg';\n if (file.type === 'image/png') {\n format = 'png';\n }\n else if (file.type === 'image/gif') {\n format = 'gif';\n }\n photos.push({\n webPath: URL.createObjectURL(file),\n format: format,\n });\n }\n resolve({ photos });\n cleanup();\n });\n input.addEventListener('cancel', (_e) => {\n reject(new CapacitorException('User cancelled photos app'));\n cleanup();\n });\n }\n input.accept = 'image/*';\n input.click();\n }\n _getCameraPhoto(photo, options) {\n return new Promise((resolve, reject) => {\n const reader = new FileReader();\n const format = photo.type.split('/')[1];\n if (options.resultType === 'uri') {\n resolve({\n webPath: URL.createObjectURL(photo),\n format: format,\n saved: false,\n });\n }\n else {\n reader.readAsDataURL(photo);\n reader.onloadend = () => {\n const r = reader.result;\n if (options.resultType === 'dataUrl') {\n resolve({\n dataUrl: r,\n format: format,\n saved: false,\n });\n }\n else {\n resolve({\n base64String: r.split(',')[1],\n format: format,\n saved: false,\n });\n }\n };\n reader.onerror = e => {\n reject(e);\n };\n }\n });\n }\n async checkPermissions() {\n if (typeof navigator === 'undefined' || !navigator.permissions) {\n throw this.unavailable('Permissions API not available in this browser');\n }\n try {\n // https://developer.mozilla.org/en-US/docs/Web/API/Permissions/query\n // the specific permissions that are supported varies among browsers that implement the\n // permissions API, so we need a try/catch in case 'camera' is invalid\n const permission = await window.navigator.permissions.query({\n name: 'camera',\n });\n return {\n camera: permission.state,\n photos: 'granted',\n };\n }\n catch (_a) {\n throw this.unavailable('Camera permissions are not available in this browser');\n }\n }\n async requestPermissions() {\n throw this.unimplemented('Not implemented on web.');\n }\n async pickLimitedLibraryPhotos() {\n throw this.unavailable('Not implemented on web.');\n }\n async getLimitedLibraryPhotos() {\n throw this.unavailable('Not implemented on web.');\n }\n}\nconst Camera = new CameraWeb();\nexport { Camera };\n//# sourceMappingURL=web.js.map","import { registerPlugin } from '@capacitor/core';\nimport { CameraWeb } from './web';\nconst Camera = registerPlugin('Camera', {\n web: () => new CameraWeb(),\n});\nexport * from './definitions';\nexport { Camera };\n//# sourceMappingURL=index.js.map"],"names":["CameraSource","CameraDirection","CameraResultType","WebPlugin","CapacitorException","registerPlugin"],"mappings":"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"} \ No newline at end of file diff --git a/camera/ios/Sources/CameraPlugin/CameraPlugin.swift b/camera/ios/Sources/CameraPlugin/CameraPlugin.swift index 67b0fbd41e..dbd1854bc2 100644 --- a/camera/ios/Sources/CameraPlugin/CameraPlugin.swift +++ b/camera/ios/Sources/CameraPlugin/CameraPlugin.swift @@ -31,11 +31,7 @@ public class CameraPlugin: CAPPlugin, CAPBridgedPlugin { case .camera: state = AVCaptureDevice.authorizationStatus(for: .video).authorizationState case .photos: - if #available(iOS 14, *) { - state = PHPhotoLibrary.authorizationStatus(for: .readWrite).authorizationState - } else { - state = PHPhotoLibrary.authorizationStatus().authorizationState - } + state = PHPhotoLibrary.authorizationStatus(for: .readWrite).authorizationState } result[permission.rawValue] = state } @@ -60,14 +56,8 @@ public class CameraPlugin: CAPPlugin, CAPBridgedPlugin { } case .photos: group.enter() - if #available(iOS 14, *) { - PHPhotoLibrary.requestAuthorization(for: .readWrite) { (_) in - group.leave() - } - } else { - PHPhotoLibrary.requestAuthorization({ (_) in - group.leave() - }) + PHPhotoLibrary.requestAuthorization(for: .readWrite) { (_) in + group.leave() } } } @@ -77,77 +67,62 @@ public class CameraPlugin: CAPPlugin, CAPBridgedPlugin { } @objc func pickLimitedLibraryPhotos(_ call: CAPPluginCall) { - if #available(iOS 14, *) { - PHPhotoLibrary.requestAuthorization(for: .readWrite) { (granted) in - if granted == .limited { - if let viewController = self.bridge?.viewController { - if #available(iOS 15, *) { - PHPhotoLibrary.shared().presentLimitedLibraryPicker(from: viewController) { _ in - self.getLimitedLibraryPhotos(call) - } - } else { - PHPhotoLibrary.shared().presentLimitedLibraryPicker(from: viewController) - call.resolve([ - "photos": [] - ]) - } + 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": [] - ]) } + } else { + call.resolve([ + "photos": [] + ]) } - } else { - call.unavailable("Not available on iOS 13") } } @objc func getLimitedLibraryPhotos(_ call: CAPPluginCall) { - if #available(iOS 14, *) { - 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)) + 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) - } + group.notify(queue: .global(qos: .utility)) { [weak self] in + self?.returnImages(processedImages) } - } else { - call.resolve([ - "photos": [] - ]) } + } else { + call.resolve([ + "photos": [] + ]) } - } else { - call.unavailable("Not available on iOS 13") } } @@ -169,6 +144,8 @@ public class CameraPlugin: CAPPlugin, CAPBridgedPlugin { self.showPrompt() case .camera: self.showCamera() + case .cameraMulti: + self.showMultiCamera() case .photos: self.showPhotos() } @@ -252,7 +229,6 @@ extension CameraPlugin: UIImagePickerControllerDelegate, UINavigationControllerD } } -@available(iOS 14, *) extension CameraPlugin: PHPickerViewControllerDelegate { public func picker(_ picker: PHPickerViewController, didFinishPicking results: [PHPickerResult]) { picker.dismiss(animated: true, completion: nil) @@ -414,6 +390,10 @@ private extension CameraPlugin { self?.showCamera() })) + alert.addAction(UIAlertAction(title: "Take Multiple Pictures", style: .default, handler: { [weak self] (_: UIAlertAction) in + self?.showMultiCamera() + })) + alert.addAction(UIAlertAction(title: settings.userPromptText.cancelAction, style: .cancel, handler: { [weak self] (_: UIAlertAction) in self?.call?.reject("User cancelled photos app") })) @@ -490,13 +470,54 @@ private extension CameraPlugin { } func presentSystemAppropriateImagePicker() { - if #available(iOS 14, *) { - presentPhotoPicker() - } else { - presentImagePicker() + presentPhotoPicker() + } + + func showMultiCamera() { + // 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?.presentMultiCameraPicker() + } + } else { + self?.call?.reject("User denied access to camera") + } } } + func presentMultiCameraPicker() { + // Set multiple flag to true for this operation + self.multiple = true + + let multiCameraViewController = MultiCameraViewController() + multiCameraViewController.delegate = self + multiCameraViewController.maxImages = self.call?.getInt("limit") ?? 0 + multiCameraViewController.cameraDirection = settings.direction + + // Present the custom camera UI + multiCameraViewController.modalPresentationStyle = settings.presentationStyle + if settings.presentationStyle == .popover { + multiCameraViewController.popoverPresentationController?.delegate = self + setCenteredPopover(multiCameraViewController) + } + bridge?.viewController?.present(multiCameraViewController, animated: true, completion: nil) + } + func presentImagePicker() { let picker = UIImagePickerController() picker.delegate = self @@ -512,7 +533,6 @@ private extension CameraPlugin { bridge?.viewController?.present(picker, animated: true, completion: nil) } - @available(iOS 14, *) func presentPhotoPicker() { var configuration = PHPickerConfiguration(photoLibrary: PHPhotoLibrary.shared()) configuration.selectionLimit = self.multiple ? (self.call?.getInt("limit") ?? 0) : 1 @@ -582,3 +602,45 @@ private extension CameraPlugin { return result } } + +// MARK: - MultiCameraViewControllerDelegate +extension CameraPlugin: MultiCameraViewControllerDelegate { + func multiCameraViewController(_ viewController: MultiCameraViewController, didFinishWith images: [UIImage], metadata: [[String: Any]]) { + viewController.dismiss(animated: true) { + var processedImages: [ProcessedImage] = [] + + // Process each image + for (index, image) in images.enumerated() { + let meta = index < metadata.count ? metadata[index] : [:] + let processedImage = self.processedImage(from: image, with: meta) + processedImages.append(processedImage) + } + + // Save images to gallery if requested, similar to single photo flow + if self.settings.saveToGallery { + let dispatchGroup = DispatchGroup() + var savedResults: [Bool] = Array(repeating: false, count: processedImages.count) + + for (index, processedImage) in processedImages.enumerated() { + dispatchGroup.enter() + _ = ImageSaver(image: processedImage.image) { error in + savedResults[index] = (error == nil) + dispatchGroup.leave() + } + } + + dispatchGroup.notify(queue: .main) { + self.returnImages(processedImages) + } + } else { + self.returnImages(processedImages) + } + } + } + + func multiCameraViewControllerDidCancel(_ viewController: MultiCameraViewController) { + viewController.dismiss(animated: true) { + self.call?.reject("User cancelled camera") + } + } +} diff --git a/camera/ios/Sources/CameraPlugin/CameraTypes.swift b/camera/ios/Sources/CameraPlugin/CameraTypes.swift index 382d216e52..ee1a417214 100644 --- a/camera/ios/Sources/CameraPlugin/CameraTypes.swift +++ b/camera/ios/Sources/CameraPlugin/CameraTypes.swift @@ -5,6 +5,7 @@ import UIKit public enum CameraSource: String { case prompt = "PROMPT" case camera = "CAMERA" + case cameraMulti = "CAMERA_MULTI" case photos = "PHOTOS" } diff --git a/camera/ios/Sources/CameraPlugin/MultiCameraViewController.swift b/camera/ios/Sources/CameraPlugin/MultiCameraViewController.swift new file mode 100644 index 0000000000..ae5278bdbf --- /dev/null +++ b/camera/ios/Sources/CameraPlugin/MultiCameraViewController.swift @@ -0,0 +1,1457 @@ +import UIKit +import AVFoundation +import Photos +import Capacitor + +// MARK: - ThumbnailCell +class ThumbnailCell: UICollectionViewCell { + var deleteHandler: (() -> Void)? + + private let imageView: UIImageView = { + let imageView = UIImageView() + imageView.contentMode = .scaleAspectFill + imageView.clipsToBounds = true + imageView.layer.cornerRadius = 5 + imageView.translatesAutoresizingMaskIntoConstraints = false + return imageView + }() + + private let loadingIndicator: UIActivityIndicatorView = { + let indicator = UIActivityIndicatorView(style: .medium) + indicator.color = .white + indicator.translatesAutoresizingMaskIntoConstraints = false + indicator.hidesWhenStopped = true + return indicator + }() + + private let deleteButton: UIButton = { + let button = UIButton(type: .system) + button.setImage(UIImage(systemName: "xmark.circle.fill"), for: .normal) + button.tintColor = .white + button.backgroundColor = UIColor.black.withAlphaComponent(0.5) + button.layer.cornerRadius = 10 + button.translatesAutoresizingMaskIntoConstraints = false + return button + }() + + override init(frame: CGRect) { + super.init(frame: frame) + setupUI() + } + + required init?(coder: NSCoder) { + super.init(coder: coder) + setupUI() + } + + private func setupUI() { + contentView.addSubview(imageView) + contentView.addSubview(loadingIndicator) + contentView.addSubview(deleteButton) + + NSLayoutConstraint.activate([ + imageView.topAnchor.constraint(equalTo: contentView.topAnchor), + imageView.leadingAnchor.constraint(equalTo: contentView.leadingAnchor), + imageView.trailingAnchor.constraint(equalTo: contentView.trailingAnchor), + imageView.bottomAnchor.constraint(equalTo: contentView.bottomAnchor), + + loadingIndicator.centerXAnchor.constraint(equalTo: contentView.centerXAnchor), + loadingIndicator.centerYAnchor.constraint(equalTo: contentView.centerYAnchor), + + deleteButton.topAnchor.constraint(equalTo: contentView.topAnchor, constant: 2), + deleteButton.trailingAnchor.constraint( + equalTo: contentView.trailingAnchor, constant: -2), + deleteButton.widthAnchor.constraint(equalToConstant: 20), + deleteButton.heightAnchor.constraint(equalToConstant: 20) + ]) + + deleteButton.addTarget(self, action: #selector(deleteButtonTapped), for: .touchUpInside) + } + + func configure(with image: UIImage) { + imageView.image = image + loadingIndicator.stopAnimating() + deleteButton.isHidden = false + } + + func configureAsLoading() { + imageView.image = nil + imageView.backgroundColor = UIColor.darkGray.withAlphaComponent(0.8) + loadingIndicator.startAnimating() + deleteButton.isHidden = true + } + + @objc private func deleteButtonTapped() { + deleteHandler?() + } +} + +// MARK: - ImagePreviewViewController +class ImagePreviewViewController: UIViewController { + private let images: [UIImage] + private var currentIndex: Int + + private lazy var collectionView: UICollectionView = { + let layout = UICollectionViewFlowLayout() + layout.scrollDirection = .horizontal + layout.minimumLineSpacing = 0 + layout.minimumInteritemSpacing = 0 + + let collectionView = UICollectionView(frame: .zero, collectionViewLayout: layout) + collectionView.backgroundColor = .black + collectionView.isPagingEnabled = true + collectionView.showsHorizontalScrollIndicator = false + collectionView.register( + ImagePreviewCell.self, forCellWithReuseIdentifier: "ImagePreviewCell") + collectionView.dataSource = self + collectionView.delegate = self + collectionView.translatesAutoresizingMaskIntoConstraints = false + return collectionView + }() + + private lazy var positionIndicator: UILabel = { + let label = UILabel() + label.textColor = .white + label.textAlignment = .center + label.font = UIFont.systemFont(ofSize: 14, weight: .medium) + label.backgroundColor = UIColor.black.withAlphaComponent(0.6) + label.layer.cornerRadius = 12 + label.layer.masksToBounds = true + label.translatesAutoresizingMaskIntoConstraints = false + return label + }() + + init(images: [UIImage], startingIndex: Int = 0) { + self.images = images + self.currentIndex = max(0, min(startingIndex, images.count - 1)) + super.init(nibName: nil, bundle: nil) + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + override func viewDidLoad() { + super.viewDidLoad() + + view.backgroundColor = .black + setupUI() + updatePositionIndicator() + } + + override func viewDidLayoutSubviews() { + super.viewDidLayoutSubviews() + + // Scroll to the starting index after layout is complete + if let layout = collectionView.collectionViewLayout as? UICollectionViewFlowLayout { + layout.itemSize = collectionView.bounds.size + collectionView.scrollToItem( + at: IndexPath(item: currentIndex, section: 0), at: .left, animated: false) + } + } + + private func setupUI() { + view.addSubview(collectionView) + view.addSubview(positionIndicator) + + NSLayoutConstraint.activate([ + collectionView.topAnchor.constraint(equalTo: view.topAnchor), + collectionView.leadingAnchor.constraint(equalTo: view.leadingAnchor), + collectionView.trailingAnchor.constraint(equalTo: view.trailingAnchor), + collectionView.bottomAnchor.constraint(equalTo: view.bottomAnchor) + ]) + + // Setup close button + let closeButton = UIButton(type: .system) + closeButton.setImage(UIImage(systemName: "xmark"), for: .normal) + closeButton.tintColor = .white + closeButton.translatesAutoresizingMaskIntoConstraints = false + closeButton.addTarget(self, action: #selector(closeButtonTapped), for: .touchUpInside) + + view.addSubview(closeButton) + NSLayoutConstraint.activate([ + closeButton.topAnchor.constraint( + equalTo: view.safeAreaLayoutGuide.topAnchor, constant: 20), + closeButton.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 20), + closeButton.widthAnchor.constraint(equalToConstant: 44), + closeButton.heightAnchor.constraint(equalToConstant: 44) + ]) + + // Position indicator constraints - only show if more than 1 image + if images.count > 1 { + NSLayoutConstraint.activate([ + positionIndicator.centerXAnchor.constraint(equalTo: view.centerXAnchor), + positionIndicator.topAnchor.constraint( + equalTo: view.safeAreaLayoutGuide.topAnchor, constant: 20), + positionIndicator.widthAnchor.constraint(greaterThanOrEqualToConstant: 40), + positionIndicator.heightAnchor.constraint(equalToConstant: 24) + ]) + } else { + positionIndicator.isHidden = true + } + } + + private func updatePositionIndicator() { + if images.count > 1 { + positionIndicator.text = "\(currentIndex + 1)/\(images.count)" + } + } + + @objc private func closeButtonTapped() { + dismiss(animated: true) + } +} + +// MARK: - ImagePreviewViewController Extensions +extension ImagePreviewViewController: UICollectionViewDataSource { + func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) + -> Int + { + return images.count + } + + func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell { + guard let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "ImagePreviewCell", for: indexPath) as? ImagePreviewCell else { + return UICollectionViewCell() + } + + cell.configure(with: images[indexPath.item]) + return cell + } +} + +extension ImagePreviewViewController: UICollectionViewDelegate { + func scrollViewDidEndDecelerating(_ scrollView: UIScrollView) { + let pageWidth = scrollView.frame.width + let currentPage = Int(scrollView.contentOffset.x / pageWidth) + + if currentPage != currentIndex { + currentIndex = currentPage + updatePositionIndicator() + } + } +} + +// MARK: - ImagePreviewCell +class ImagePreviewCell: UICollectionViewCell { + private let imageView: UIImageView = { + let imageView = UIImageView() + imageView.contentMode = .scaleAspectFit + imageView.translatesAutoresizingMaskIntoConstraints = false + return imageView + }() + + override init(frame: CGRect) { + super.init(frame: frame) + setupUI() + } + + required init?(coder: NSCoder) { + super.init(coder: coder) + setupUI() + } + + private func setupUI() { + contentView.addSubview(imageView) + NSLayoutConstraint.activate([ + imageView.topAnchor.constraint(equalTo: contentView.topAnchor), + imageView.leadingAnchor.constraint(equalTo: contentView.leadingAnchor), + imageView.trailingAnchor.constraint(equalTo: contentView.trailingAnchor), + imageView.bottomAnchor.constraint(equalTo: contentView.bottomAnchor) + ]) + } + + func configure(with image: UIImage) { + imageView.image = image + } +} + +protocol MultiCameraViewControllerDelegate: AnyObject { + func multiCameraViewController(_ viewController: MultiCameraViewController, didFinishWith images: [UIImage], metadata: [[String: Any]]) + func multiCameraViewControllerDidCancel(_ viewController: MultiCameraViewController) +} + +class MultiCameraViewController: UIViewController { + // MARK: - Properties + weak var delegate: MultiCameraViewControllerDelegate? + var maxImages: Int = 0 // 0 means unlimited + var cameraDirection: CameraDirection = .rear + + // Image processing settings + var jpegQuality: CGFloat = 1.0 + var width: CGFloat = 0 + var height: CGFloat = 0 + var shouldResize: Bool { + return width > 0 || height > 0 + } + var shouldCorrectOrientation = true + + private var captureSession: AVCaptureSession? + private var previewLayer: AVCaptureVideoPreviewLayer? + private var photoOutput: AVCapturePhotoOutput? + private var currentCameraPosition: AVCaptureDevice.Position = .back + private var flashMode: AVCaptureDevice.FlashMode = .auto + + // Zoom control properties + private var currentZoomFactor: CGFloat = 1.0 + private var minZoomFactor: CGFloat = 1.0 + private var maxZoomFactor: CGFloat = 10.0 + private var lastZoomFactor: CGFloat = 1.0 + + private var capturedImages: [UIImage] = [] + private var capturedMetadata: [[String: Any]] = [] + private var loadingStates: [Bool] = [] // Track which thumbnails are still loading + + // Track device orientation + private var isLandscape: Bool = false + private var portraitConstraints: [NSLayoutConstraint] = [] + private var landscapeConstraints: [NSLayoutConstraint] = [] + + // MARK: - UI Elements + private lazy var previewView: UIView = { + let view = UIView() + view.backgroundColor = .black + view.contentMode = .scaleAspectFill + view.clipsToBounds = true + return view + }() + + private lazy var bottomBarView: UIView = { + let view = UIView() + view.backgroundColor = .black + return view + }() + + private lazy var takePictureButton: UIButton = { + let button = UIButton(type: .custom) + // Try to load the image from the bundle + if let image = UIImage(named: "camera_capture") { + button.setImage(image, for: .normal) + } else { + // Fallback if image is not found + button.backgroundColor = .white + button.layer.cornerRadius = 35 + button.layer.borderWidth = 3 + button.layer.borderColor = UIColor.lightGray.cgColor + } + button.addTarget(self, action: #selector(takePicture), for: .touchUpInside) + return button + }() + + private lazy var flipCameraButton: UIButton = { + let button = UIButton(type: .system) + button.setImage(UIImage(systemName: "camera.rotate"), for: .normal) + button.tintColor = .white + button.addTarget(self, action: #selector(flipCamera), for: .touchUpInside) + return button + }() + + private lazy var flashButton: UIButton = { + let button = UIButton(type: .system) + button.setImage(UIImage(systemName: "bolt.badge.a"), for: .normal) + button.tintColor = .white + +// Setting Flash only for devices with Flash Hardware + if let device = getCamera(), device.hasFlash { + self.flashMode = .auto + button.addTarget(self, action: #selector(toggleFlash), for: .touchUpInside) + } else { + self.flashMode = .off + button.tintColor = .systemGray + } + + return button + }() + + private lazy var zoomInButton: UIButton = { + let button = UIButton(type: .system) + button.setImage(UIImage(systemName: "plus.magnifyingglass"), for: .normal) + button.tintColor = .white + button.addTarget(self, action: #selector(zoomIn), for: .touchUpInside) + return button + }() + + private lazy var zoomOutButton: UIButton = { + let button = UIButton(type: .system) + button.setImage(UIImage(systemName: "minus.magnifyingglass"), for: .normal) + button.tintColor = .white + button.addTarget(self, action: #selector(zoomOut), for: .touchUpInside) + return button + }() + + private lazy var zoomFactorLabel: UILabel = { + let label = UILabel() + label.textColor = .white + label.textAlignment = .center + label.font = UIFont.systemFont(ofSize: 12) + label.text = "1.0x" + label.backgroundColor = UIColor.black.withAlphaComponent(0.5) + label.layer.cornerRadius = 8 + label.layer.masksToBounds = true + return label + }() + + private lazy var closeButton: UIButton = { + let button = UIButton(type: .system) + button.setImage(UIImage(systemName: "xmark"), for: .normal) + button.tintColor = .white + button.addTarget(self, action: #selector(cancel), for: .touchUpInside) + return button + }() + + private lazy var doneButton: UIButton = { + let button = UIButton(type: .system) + button.setImage(UIImage(systemName: "checkmark"), for: .normal) + button.tintColor = .white + button.addTarget(self, action: #selector(done), for: .touchUpInside) + return button + }() + + private lazy var processingSpinner: UIActivityIndicatorView = { + let spinner = UIActivityIndicatorView(style: .large) + spinner.color = .white + spinner.backgroundColor = UIColor.black.withAlphaComponent(0.7) + spinner.layer.cornerRadius = 10 + spinner.translatesAutoresizingMaskIntoConstraints = false + spinner.hidesWhenStopped = true + return spinner + }() + + private lazy var processingLabel: UILabel = { + let label = UILabel() + label.text = "Processing images..." + label.textColor = .white + label.font = UIFont.systemFont(ofSize: 16, weight: .medium) + label.textAlignment = .center + label.translatesAutoresizingMaskIntoConstraints = false + return label + }() + + private lazy var processingOverlay: UIView = { + let view = UIView() + view.backgroundColor = UIColor.black.withAlphaComponent(0.7) + view.translatesAutoresizingMaskIntoConstraints = false + view.isHidden = true + return view + }() + + private lazy var thumbnailCollectionView: UICollectionView = { + let layout = UICollectionViewFlowLayout() + layout.scrollDirection = .horizontal + layout.itemSize = CGSize(width: 70, height: 70) + layout.minimumLineSpacing = 5 + + let collectionView = UICollectionView(frame: .zero, collectionViewLayout: layout) + collectionView.backgroundColor = .clear + collectionView.showsHorizontalScrollIndicator = false + collectionView.register(ThumbnailCell.self, forCellWithReuseIdentifier: "ThumbnailCell") + collectionView.dataSource = self + collectionView.delegate = self + return collectionView + }() + + // MARK: - Lifecycle + override func viewDidLoad() { + super.viewDidLoad() + setupUI() + checkPermissions() + + // Set background color to ensure we can see if there's an issue with the camera + view.backgroundColor = .black + + // Add pinch gesture for zoom + let pinchGesture = UIPinchGestureRecognizer( + target: self, action: #selector(handlePinchGesture(_:))) + previewView.addGestureRecognizer(pinchGesture) + } + + override func viewDidAppear(_ animated: Bool) { + super.viewDidAppear(animated) + startCaptureSession() + updatePreviewLayerFrame() + } + + override func viewDidLayoutSubviews() { + super.viewDidLayoutSubviews() + updatePreviewLayerFrame() + } + + private func updatePreviewLayerFrame() { + guard let previewLayer = previewLayer else { return } + previewLayer.frame = previewView.bounds + + // Update video orientation based on device orientation + guard let connection = previewLayer.connection else { return } + + if connection.isVideoOrientationSupported { + let orientation = UIDevice.current.orientation + + switch orientation { + case .portrait: + connection.videoOrientation = .portrait + case .landscapeLeft: + connection.videoOrientation = .landscapeRight // Note: device orientation is opposite to video orientation + case .landscapeRight: + connection.videoOrientation = .landscapeLeft // Note: device orientation is opposite to video orientation + case .portraitUpsideDown: + connection.videoOrientation = .portraitUpsideDown + default: + connection.videoOrientation = isLandscape ? .landscapeRight : .portrait + } + } + } + + private func updateConstraintsForOrientation() { + // Deactivate all constraints first + NSLayoutConstraint.deactivate(portraitConstraints) + NSLayoutConstraint.deactivate(landscapeConstraints) + + // Activate the appropriate constraints based on orientation + if isLandscape { + NSLayoutConstraint.activate(landscapeConstraints) + } else { + NSLayoutConstraint.activate(portraitConstraints) + } + + // Force layout update + view.layoutIfNeeded() + } + + override func viewWillDisappear(_ animated: Bool) { + super.viewWillDisappear(animated) + stopCaptureSession() + } + + override func viewWillTransition( + to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator + ) { + super.viewWillTransition(to: size, with: coordinator) + + // Determine if we're switching to landscape or portrait + isLandscape = size.width > size.height + + // Handle orientation changes + coordinator.animate(alongsideTransition: { [weak self] _ in + guard let self = self else { return } + self.updatePreviewLayerFrame() + self.updateConstraintsForOrientation() + }) + } + + // MARK: - Setup + private func setupUI() { + view.backgroundColor = .black + + // Add subviews + view.addSubview(previewView) + view.addSubview(bottomBarView) + view.addSubview(thumbnailCollectionView) + view.addSubview(closeButton) + view.addSubview(flashButton) + view.addSubview(zoomInButton) + view.addSubview(zoomOutButton) + view.addSubview(zoomFactorLabel) + view.addSubview(processingOverlay) + + // Add processing overlay subviews + processingOverlay.addSubview(processingSpinner) + processingOverlay.addSubview(processingLabel) + + bottomBarView.addSubview(takePictureButton) + bottomBarView.addSubview(flipCameraButton) + bottomBarView.addSubview(doneButton) + + // Setup constraints + previewView.translatesAutoresizingMaskIntoConstraints = false + bottomBarView.translatesAutoresizingMaskIntoConstraints = false + thumbnailCollectionView.translatesAutoresizingMaskIntoConstraints = false + takePictureButton.translatesAutoresizingMaskIntoConstraints = false + flipCameraButton.translatesAutoresizingMaskIntoConstraints = false + doneButton.translatesAutoresizingMaskIntoConstraints = false + closeButton.translatesAutoresizingMaskIntoConstraints = false + flashButton.translatesAutoresizingMaskIntoConstraints = false + zoomInButton.translatesAutoresizingMaskIntoConstraints = false + zoomOutButton.translatesAutoresizingMaskIntoConstraints = false + zoomFactorLabel.translatesAutoresizingMaskIntoConstraints = false + + // Setup processing overlay constraints + NSLayoutConstraint.activate([ + processingOverlay.topAnchor.constraint(equalTo: view.topAnchor), + processingOverlay.leadingAnchor.constraint(equalTo: view.leadingAnchor), + processingOverlay.trailingAnchor.constraint(equalTo: view.trailingAnchor), + processingOverlay.bottomAnchor.constraint(equalTo: view.bottomAnchor), + + processingSpinner.centerXAnchor.constraint(equalTo: processingOverlay.centerXAnchor), + processingSpinner.centerYAnchor.constraint( + equalTo: processingOverlay.centerYAnchor, constant: -20), + + processingLabel.topAnchor.constraint(equalTo: processingSpinner.bottomAnchor, constant: 20), + processingLabel.centerXAnchor.constraint(equalTo: processingOverlay.centerXAnchor) + ]) + + // Determine initial orientation + isLandscape = UIDevice.current.orientation.isLandscape + + // Create portrait constraints + setupPortraitConstraints() + + // Create landscape constraints + setupLandscapeConstraints() + + // Activate the appropriate constraints based on current orientation + updateConstraintsForOrientation() + + // Initially hide the done button until we have at least one image + doneButton.isHidden = true + } + + private func setupPortraitConstraints() { + // Clear any existing constraints + portraitConstraints.removeAll() + + // Add common constraints that don't change with orientation + let commonConstraints = [ + // Preview view top, leading, trailing + previewView.topAnchor.constraint(equalTo: view.topAnchor), + previewView.leadingAnchor.constraint(equalTo: view.leadingAnchor), + previewView.trailingAnchor.constraint(equalTo: view.trailingAnchor), + + // Close button + closeButton.topAnchor.constraint( + equalTo: view.safeAreaLayoutGuide.topAnchor, constant: 10), + closeButton.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 20), + closeButton.widthAnchor.constraint(equalToConstant: 44), + closeButton.heightAnchor.constraint(equalToConstant: 44), + + // Flash button + flashButton.topAnchor.constraint( + equalTo: view.safeAreaLayoutGuide.topAnchor, constant: 10), + flashButton.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -20), + flashButton.widthAnchor.constraint(equalToConstant: 44), + flashButton.heightAnchor.constraint(equalToConstant: 44), + + // Button sizes + takePictureButton.widthAnchor.constraint(equalToConstant: 70), + takePictureButton.heightAnchor.constraint(equalToConstant: 70), + flipCameraButton.widthAnchor.constraint(equalToConstant: 50), + flipCameraButton.heightAnchor.constraint(equalToConstant: 50), + doneButton.widthAnchor.constraint(equalToConstant: 50), + doneButton.heightAnchor.constraint(equalToConstant: 50), + zoomInButton.widthAnchor.constraint(equalToConstant: 44), + zoomInButton.heightAnchor.constraint(equalToConstant: 44), + zoomOutButton.widthAnchor.constraint(equalToConstant: 44), + zoomOutButton.heightAnchor.constraint(equalToConstant: 44), + zoomFactorLabel.widthAnchor.constraint(equalToConstant: 50), + zoomFactorLabel.heightAnchor.constraint(equalToConstant: 25) + ] + + portraitConstraints.append(contentsOf: commonConstraints) + + // Portrait-specific constraints + let portraitSpecificConstraints = [ + // Bottom bar - horizontal at bottom + bottomBarView.heightAnchor.constraint(equalToConstant: 100), + bottomBarView.leadingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.leadingAnchor), + bottomBarView.trailingAnchor.constraint( + equalTo: view.safeAreaLayoutGuide.trailingAnchor), + bottomBarView.bottomAnchor.constraint(equalTo: view.safeAreaLayoutGuide.bottomAnchor), + + // Preview view bottom connects to bottom bar top + previewView.bottomAnchor.constraint(equalTo: bottomBarView.topAnchor), + + // Thumbnail collection view + thumbnailCollectionView.heightAnchor.constraint(equalToConstant: 80), + thumbnailCollectionView.leadingAnchor.constraint( + equalTo: view.leadingAnchor, constant: 10), + thumbnailCollectionView.trailingAnchor.constraint( + equalTo: view.trailingAnchor, constant: -10), + thumbnailCollectionView.bottomAnchor.constraint( + equalTo: bottomBarView.topAnchor, constant: -90), // Positioned higher to be above zoom controls + + // Take picture button + takePictureButton.centerXAnchor.constraint(equalTo: bottomBarView.centerXAnchor), + takePictureButton.centerYAnchor.constraint(equalTo: bottomBarView.centerYAnchor), + + // Flip camera button + flipCameraButton.leadingAnchor.constraint( + equalTo: bottomBarView.leadingAnchor, constant: 30), + flipCameraButton.centerYAnchor.constraint(equalTo: bottomBarView.centerYAnchor), + + // Done button + doneButton.trailingAnchor.constraint( + equalTo: bottomBarView.trailingAnchor, constant: -30), + doneButton.centerYAnchor.constraint(equalTo: bottomBarView.centerYAnchor), + + // Zoom buttons - positioned below the film strip + zoomInButton.bottomAnchor.constraint(equalTo: bottomBarView.topAnchor, constant: -20), + zoomInButton.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -20), + + zoomOutButton.bottomAnchor.constraint(equalTo: bottomBarView.topAnchor, constant: -20), + zoomOutButton.trailingAnchor.constraint( + equalTo: zoomInButton.leadingAnchor, constant: -10), + + // Zoom factor label + zoomFactorLabel.centerYAnchor.constraint(equalTo: zoomInButton.centerYAnchor), + zoomFactorLabel.trailingAnchor.constraint(equalTo: zoomOutButton.leadingAnchor, constant: -10) + ] + + portraitConstraints.append(contentsOf: portraitSpecificConstraints) + } + + private func setupLandscapeConstraints() { + // Clear any existing constraints + landscapeConstraints.removeAll() + + // Add common constraints that don't change with orientation + let commonConstraints = [ + // Preview view top, leading + previewView.topAnchor.constraint(equalTo: view.topAnchor), + previewView.leadingAnchor.constraint(equalTo: view.leadingAnchor), + + // Close button + closeButton.topAnchor.constraint( + equalTo: view.safeAreaLayoutGuide.topAnchor, constant: 10), + closeButton.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 20), + closeButton.widthAnchor.constraint(equalToConstant: 44), + closeButton.heightAnchor.constraint(equalToConstant: 44), + + // Flash button + flashButton.topAnchor.constraint( + equalTo: view.safeAreaLayoutGuide.topAnchor, constant: 10), + flashButton.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -20), + flashButton.widthAnchor.constraint(equalToConstant: 44), + flashButton.heightAnchor.constraint(equalToConstant: 44), + + // Button sizes + takePictureButton.widthAnchor.constraint(equalToConstant: 70), + takePictureButton.heightAnchor.constraint(equalToConstant: 70), + flipCameraButton.widthAnchor.constraint(equalToConstant: 50), + flipCameraButton.heightAnchor.constraint(equalToConstant: 50), + doneButton.widthAnchor.constraint(equalToConstant: 50), + doneButton.heightAnchor.constraint(equalToConstant: 50), + zoomInButton.widthAnchor.constraint(equalToConstant: 44), + zoomInButton.heightAnchor.constraint(equalToConstant: 44), + zoomOutButton.widthAnchor.constraint(equalToConstant: 44), + zoomOutButton.heightAnchor.constraint(equalToConstant: 44), + zoomFactorLabel.widthAnchor.constraint(equalToConstant: 50), + zoomFactorLabel.heightAnchor.constraint(equalToConstant: 25) + ] + + landscapeConstraints.append(contentsOf: commonConstraints) + + // Landscape-specific constraints + let landscapeSpecificConstraints = [ + // Bottom bar - vertical on right side + bottomBarView.widthAnchor.constraint(equalToConstant: 120), + bottomBarView.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor), + bottomBarView.bottomAnchor.constraint(equalTo: view.safeAreaLayoutGuide.bottomAnchor), + bottomBarView.trailingAnchor.constraint( + equalTo: view.safeAreaLayoutGuide.trailingAnchor), + + // Preview view connects to bottom bar on right + previewView.trailingAnchor.constraint(equalTo: bottomBarView.leadingAnchor), + previewView.bottomAnchor.constraint(equalTo: view.bottomAnchor), + + // Thumbnail collection view - horizontal at bottom of preview + thumbnailCollectionView.heightAnchor.constraint(equalToConstant: 80), + thumbnailCollectionView.leadingAnchor.constraint( + equalTo: view.leadingAnchor, constant: 100), // Give space from left edge + thumbnailCollectionView.trailingAnchor.constraint( + equalTo: bottomBarView.leadingAnchor, constant: -10), + thumbnailCollectionView.bottomAnchor.constraint( + equalTo: view.safeAreaLayoutGuide.bottomAnchor, constant: -10), + + // Take picture button - centered in the vertical bar + takePictureButton.centerXAnchor.constraint(equalTo: bottomBarView.centerXAnchor), + takePictureButton.centerYAnchor.constraint(equalTo: bottomBarView.centerYAnchor), + + // Flip camera button - above the take picture button + flipCameraButton.centerXAnchor.constraint(equalTo: bottomBarView.centerXAnchor), + flipCameraButton.bottomAnchor.constraint( + equalTo: takePictureButton.topAnchor, constant: -30), + + // Done button - below the take picture button + doneButton.centerXAnchor.constraint(equalTo: bottomBarView.centerXAnchor), + doneButton.topAnchor.constraint(equalTo: takePictureButton.bottomAnchor, constant: 30), + + // Zoom buttons - on the right side of the preview + zoomInButton.trailingAnchor.constraint( + equalTo: bottomBarView.leadingAnchor, constant: -20), + zoomInButton.bottomAnchor.constraint(equalTo: view.bottomAnchor, constant: -20), + + zoomOutButton.trailingAnchor.constraint( + equalTo: zoomInButton.leadingAnchor, constant: -10), + zoomOutButton.centerYAnchor.constraint(equalTo: zoomInButton.centerYAnchor), + + // Zoom factor label + zoomFactorLabel.trailingAnchor.constraint(equalTo: zoomOutButton.leadingAnchor, constant: -10), + zoomFactorLabel.centerYAnchor.constraint(equalTo: zoomInButton.centerYAnchor) + ] + + landscapeConstraints.append(contentsOf: landscapeSpecificConstraints) + } + + private func checkPermissions() { + switch AVCaptureDevice.authorizationStatus(for: .video) { + case .authorized: + setupCaptureSession() + case .notDetermined: + AVCaptureDevice.requestAccess(for: .video) { [weak self] granted in + if granted { + DispatchQueue.main.async { + self?.setupCaptureSession() + } + } else { + DispatchQueue.main.async { + self?.showPermissionAlert() + } + } + } + default: + showPermissionAlert() + } + } + + private func showPermissionAlert() { + let alert = UIAlertController( + title: "Camera Access Required", + message: "Please allow camera access to use this feature", + preferredStyle: .alert + ) + + alert.addAction( + UIAlertAction(title: "Cancel", style: .cancel) { [weak self] _ in + self?.delegate?.multiCameraViewControllerDidCancel(self!) + }) + + alert.addAction( + UIAlertAction(title: "Settings", style: .default) { _ in + if let url = URL(string: UIApplication.openSettingsURLString) { + UIApplication.shared.open(url) + } + }) + + present(alert, animated: true) + } + + // MARK: - Camera Setup + private func setupCaptureSession() { + captureSession = AVCaptureSession() + guard let captureSession = captureSession else { return } + + captureSession.beginConfiguration() + + // Set the quality level + if captureSession.canSetSessionPreset(.photo) { + captureSession.sessionPreset = .photo + } + + // Setup camera input + guard let videoDevice = getCamera() else { + captureSession.commitConfiguration() + return + } + + do { + let videoInput = try AVCaptureDeviceInput(device: videoDevice) + if captureSession.canAddInput(videoInput) { + captureSession.addInput(videoInput) + } else { + captureSession.commitConfiguration() + return + } + } catch { + captureSession.commitConfiguration() + return + } + + // Setup photo output + photoOutput = AVCapturePhotoOutput() + guard let photoOutput = photoOutput else { + captureSession.commitConfiguration() + return + } + + if captureSession.canAddOutput(photoOutput) { + photoOutput.isHighResolutionCaptureEnabled = true + captureSession.addOutput(photoOutput) + } else { + captureSession.commitConfiguration() + return + } + + captureSession.commitConfiguration() + + // Setup preview layer on main thread to ensure UI updates are synchronized + DispatchQueue.main.async { [weak self] in + guard let self = self else { return } + + self.previewLayer = AVCaptureVideoPreviewLayer(session: captureSession) + guard let previewLayer = self.previewLayer else { return } + + previewLayer.videoGravity = .resizeAspectFill + previewLayer.frame = self.previewView.bounds + self.previewView.layer.addSublayer(previewLayer) + + // Make sure the preview layer is properly sized + self.updatePreviewLayerFrame() + } + + // Add tap gesture for focus + let tapGesture = UITapGestureRecognizer(target: self, action: #selector(handleTap(_:))) + previewView.addGestureRecognizer(tapGesture) + + // Get device zoom capabilities + if let device = getCamera() { + minZoomFactor = 1.0 + maxZoomFactor = min(device.activeFormat.videoMaxZoomFactor, 10.0) // Limit max zoom to 10x + } + } + + private func getCamera() -> AVCaptureDevice? { + currentCameraPosition = cameraDirection == .front ? .front : .back + + if let device = AVCaptureDevice.default( + .builtInWideAngleCamera, for: .video, position: currentCameraPosition) + { + return device + } + + // Fallback to any camera if the requested one is not available + return AVCaptureDevice.default(for: .video) + } + + private func startCaptureSession() { + if captureSession?.isRunning == false { + // Start session on background thread + DispatchQueue.global(qos: .userInitiated).async { [weak self] in + self?.captureSession?.startRunning() + + // Update UI on main thread + DispatchQueue.main.async { + self?.updatePreviewLayerFrame() + } + } + } + } + + private func stopCaptureSession() { + if captureSession?.isRunning == true { + captureSession?.stopRunning() + } + } + + // MARK: - Zoom Control + @objc private func handlePinchGesture(_ gesture: UIPinchGestureRecognizer) { + guard (captureSession?.inputs.first as? AVCaptureDeviceInput)?.device != nil else { return } + + // Get the pinch scale + let scale = gesture.scale + + switch gesture.state { + case .began: + // Store the current zoom factor when the pinch begins + lastZoomFactor = currentZoomFactor + case .changed: + // Calculate new zoom factor + let newZoomFactor = max(minZoomFactor, min(lastZoomFactor * scale, maxZoomFactor)) + setZoomFactor(newZoomFactor) + default: + break + } + } + + @objc private func zoomIn() { + let newZoomFactor = min(currentZoomFactor * 1.25, maxZoomFactor) + setZoomFactor(newZoomFactor) + } + + @objc private func zoomOut() { + let newZoomFactor = max(currentZoomFactor / 1.25, minZoomFactor) + setZoomFactor(newZoomFactor) + } + + private func setZoomFactor(_ zoomFactor: CGFloat) { + guard let device = (captureSession?.inputs.first as? AVCaptureDeviceInput)?.device else { + return + } + + do { + try device.lockForConfiguration() + + // Set the zoom factor + device.videoZoomFactor = zoomFactor + currentZoomFactor = zoomFactor + + // Update the zoom factor label + DispatchQueue.main.async { [weak self] in + self?.zoomFactorLabel.text = String(format: "%.1fx", zoomFactor) + } + + device.unlockForConfiguration() + } catch { + CAPLog.print("Could not set zoom factor: \(error.localizedDescription)") + } + } + + // MARK: - Actions + @objc private func takePicture() { + guard let photoOutput = photoOutput else { return } + + // Add loading thumbnail immediately for responsive UI + addLoadingThumbnail() + + // Configure photo settings + let photoSettings = AVCapturePhotoSettings() + + photoSettings.flashMode = flashMode + + if let photoPreviewType = photoSettings.availablePreviewPhotoPixelFormatTypes.first { + photoSettings.previewPhotoFormat = [ + kCVPixelBufferPixelFormatTypeKey as String: photoPreviewType + ] + } + + // Capture the photo + photoOutput.capturePhoto(with: photoSettings, delegate: self) + + // Provide haptic feedback + let generator = UIImpactFeedbackGenerator(style: .medium) + generator.prepare() + generator.impactOccurred() + + // Play shutter sound + AudioServicesPlaySystemSound(1108) // Camera shutter sound + } + + @objc private func flipCamera() { + guard let captureSession = captureSession else { return } + + // Remove existing input + captureSession.beginConfiguration() + if let currentInput = captureSession.inputs.first as? AVCaptureDeviceInput { + captureSession.removeInput(currentInput) + } + + // Toggle camera position + currentCameraPosition = (currentCameraPosition == .back) ? .front : .back + + // Add new input + guard + let videoDevice = AVCaptureDevice.default( + .builtInWideAngleCamera, for: .video, position: currentCameraPosition) + else { + captureSession.commitConfiguration() + return + } + + do { + let videoInput = try AVCaptureDeviceInput(device: videoDevice) + if captureSession.canAddInput(videoInput) { + captureSession.addInput(videoInput) + } + } catch { + captureSession.commitConfiguration() + return + } + + captureSession.commitConfiguration() + + // Keep flash button visible for both cameras (front camera uses screen flash) + flashButton.isHidden = false + + // Reset zoom when switching cameras + currentZoomFactor = 1.0 + zoomFactorLabel.text = "1.0x" + + // Update zoom limits for the new camera + maxZoomFactor = min(videoDevice.activeFormat.videoMaxZoomFactor, 10.0) + } + + @objc private func toggleFlash() { + switch flashMode { + case .auto: + flashMode = .on + flashButton.setImage(UIImage(systemName: "bolt.fill"), for: .normal) + case .on: + flashMode = .off + flashButton.setImage(UIImage(systemName: "bolt.slash"), for: .normal) + case .off: + flashMode = .auto + flashButton.setImage(UIImage(systemName: "bolt.badge.a"), for: .normal) + @unknown default: + flashMode = .auto + flashButton.setImage(UIImage(systemName: "bolt.badge.a"), for: .normal) + } + } + + @objc private func handleTap(_ gesture: UITapGestureRecognizer) { + let touchPoint = gesture.location(in: previewView) + focusAtPoint(touchPoint) + } + + private func focusAtPoint(_ point: CGPoint) { + guard let device = (captureSession?.inputs.first as? AVCaptureDeviceInput)?.device, + device.isFocusPointOfInterestSupported, + device.isFocusModeSupported(.autoFocus) + else { return } + + // Convert the touch point to device coordinates + let focusPoint = + previewLayer?.captureDevicePointConverted(fromLayerPoint: point) + ?? CGPoint(x: 0.5, y: 0.5) + + do { + try device.lockForConfiguration() + device.focusPointOfInterest = focusPoint + device.focusMode = .autoFocus + + if device.isExposurePointOfInterestSupported + && device.isExposureModeSupported(.autoExpose) + { + device.exposurePointOfInterest = focusPoint + device.exposureMode = .autoExpose + } + + device.unlockForConfiguration() + + // Show focus indicator + showFocusIndicator(at: point) + } catch { + CAPLog.print("Could not focus at point: \(error.localizedDescription)") + } + } + + private func showFocusIndicator(at point: CGPoint) { + let focusView = UIView(frame: CGRect(x: 0, y: 0, width: 80, height: 80)) + focusView.layer.borderColor = UIColor.white.cgColor + focusView.layer.borderWidth = 2 + focusView.center = point + focusView.alpha = 0 + + previewView.addSubview(focusView) + + UIView.animate( + withDuration: 0.2, + animations: { + focusView.alpha = 1 + }, + completion: { _ in + UIView.animate( + withDuration: 0.5, delay: 0.5, options: [], + animations: { + focusView.alpha = 0 + }, + completion: { _ in + focusView.removeFromSuperview() + }) + }) + } + + @objc private func cancel() { + // Don't allow canceling while images are processing + if hasProcessingImages() { + return + } + + // If we have images, show confirmation alert + if !capturedImages.isEmpty { + let alert = UIAlertController( + title: "Discard Photos?", + message: "Are you sure you want to discard all photos?", + preferredStyle: .alert + ) + + alert.addAction(UIAlertAction(title: "Cancel", style: .cancel)) + alert.addAction( + UIAlertAction(title: "Discard", style: .destructive) { [weak self] _ in + guard let self = self else { return } + self.delegate?.multiCameraViewControllerDidCancel(self) + }) + + present(alert, animated: true) + } else { + delegate?.multiCameraViewControllerDidCancel(self) + } + } + + @objc private func done() { + // Check if any images are still processing + if hasProcessingImages() { + showProcessingOverlay() + waitForProcessingCompletion() + } else { + finishWithImages() + } + } + + private func hasProcessingImages() -> Bool { + return loadingStates.contains(true) + } + + private func showProcessingOverlay() { + processingOverlay.isHidden = false + processingSpinner.startAnimating() + + // Disable user interaction on the main view + view.isUserInteractionEnabled = false + processingOverlay.isUserInteractionEnabled = true + } + + private func hideProcessingOverlay() { + processingOverlay.isHidden = true + processingSpinner.stopAnimating() + + // Re-enable user interaction + view.isUserInteractionEnabled = true + } + + private func waitForProcessingCompletion() { + // Check every 0.1 seconds if processing is complete + DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) { [weak self] in + guard let self = self else { return } + + if self.hasProcessingImages() { + // Still processing, check again + self.waitForProcessingCompletion() + } else { + // All images processed, hide overlay and finish + self.hideProcessingOverlay() + self.finishWithImages() + } + } + } + + private func waitForProcessingCompletionThenFinish() { + // Check every 0.1 seconds if processing is complete + DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) { [weak self] in + guard let self = self else { return } + + if self.hasProcessingImages() { + // Still processing, check again + self.waitForProcessingCompletionThenFinish() + } else { + // All images processed, hide overlay and finish + self.hideProcessingOverlay() + self.finishWithImages() + } + } + } + + private func finishWithImages() { + delegate?.multiCameraViewController( + self, didFinishWith: capturedImages, metadata: capturedMetadata) + } + + // MARK: - Image Processing + private func processImage(from image: UIImage, with metadata: [String: Any]?) -> UIImage { + var processedImage = image + + // Apply resizing if needed + if shouldResize, width > 0 || height > 0 { + processedImage = processedImage.reformat(to: CGSize(width: width, height: height)) + } else if shouldCorrectOrientation { + // resizing implicitly reformats the image so this is only needed if we aren't resizing + processedImage = processedImage.reformat() + } + + return processedImage + } + + // MARK: - Image Management + private func addLoadingThumbnail() { + // Add placeholder image and loading state + capturedImages.append(UIImage()) // Placeholder + capturedMetadata.append([:]) + loadingStates.append(true) + + // Show the done button once we have at least one image (even loading) + if doneButton.isHidden { + doneButton.isHidden = false + } + + // Update the collection view + thumbnailCollectionView.reloadData() + + // Scroll to the new image + let indexPath = IndexPath(item: capturedImages.count - 1, section: 0) + thumbnailCollectionView.scrollToItem(at: indexPath, at: .right, animated: true) + } + + private func addCapturedImage(_ image: UIImage, metadata: [String: Any]) { + // Find the most recent loading thumbnail and replace it + for i in (0.. 0 && capturedImages.count >= maxImages { + // Wait for processing if needed, then automatically finish + if hasProcessingImages() { + showProcessingOverlay() + waitForProcessingCompletionThenFinish() + } else { + finishWithImages() + } + return + } + } + + private func removeImage(at index: Int) { + guard index < capturedImages.count else { return } + + capturedImages.remove(at: index) + capturedMetadata.remove(at: index) + loadingStates.remove(at: index) + + // Hide the done button if we have no images + if capturedImages.isEmpty { + doneButton.isHidden = true + } + + // Update the collection view + thumbnailCollectionView.reloadData() + } +} + +// MARK: - AVCapturePhotoCaptureDelegate +extension MultiCameraViewController: AVCapturePhotoCaptureDelegate { + func photoOutput( + _ output: AVCapturePhotoOutput, didFinishProcessingPhoto photo: AVCapturePhoto, + error: Error? + ) { + if let error = error { + CAPLog.print("Error capturing photo: \(error.localizedDescription)") + return + } + + guard let imageData = photo.fileDataRepresentation(), + let image = UIImage(data: imageData) + else { + return + } + + // Extract metadata + let metadata = photo.metadata + + // Apply image processing (resizing, quality, etc.) - this also handles orientation correction + let processedImage = processImage(from: image, with: metadata) + + // Apply device orientation correction if we didn't do resizing (since resizing includes orientation correction) + let finalImage = shouldResize ? processedImage : fixImageOrientation(processedImage) + + // Add the captured image + DispatchQueue.main.async { [weak self] in + self?.addCapturedImage(finalImage, metadata: metadata) + } + } + + private func fixImageOrientation(_ image: UIImage) -> UIImage { + // First, normalize the image orientation using reformat() + let normalizedImage = image.reformat() + + // Then apply rotation based on the current device orientation + let orientation = UIDevice.current.orientation + let isUsingFrontCamera = currentCameraPosition == .front + + // Create a new CGImage from the normalized image + guard let cgImage = normalizedImage.cgImage else { return normalizedImage } + + // Determine the correct orientation based on device orientation + var uiOrientation: UIImage.Orientation = .up // Default is no additional rotation + + switch orientation { + case .portrait: + // Portrait is already correct after normalization + return normalizedImage + case .portraitUpsideDown: + // Need 180-degree rotation + uiOrientation = .down + case .landscapeLeft: + // Need counter-clockwise 90-degree rotation for back camera + uiOrientation = isUsingFrontCamera ? .downMirrored : .left + case .landscapeRight: + // Need clockwise 90-degree rotation for back camera + uiOrientation = isUsingFrontCamera ? .upMirrored : .right + default: + // For unknown orientations, use the normalized image + return normalizedImage + } + + // Create a new UIImage with the correct orientation + return UIImage(cgImage: cgImage, scale: normalizedImage.scale, orientation: uiOrientation) + } +} + +// MARK: - UICollectionViewDataSource +extension MultiCameraViewController: UICollectionViewDataSource { + func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) + -> Int + { + return capturedImages.count + } + + func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) + -> UICollectionViewCell + { + guard + let cell = collectionView.dequeueReusableCell( + withReuseIdentifier: "ThumbnailCell", for: indexPath) as? ThumbnailCell + else { + return UICollectionViewCell() + } + + // Check if this thumbnail is still loading + if indexPath.item < loadingStates.count && loadingStates[indexPath.item] { + cell.configureAsLoading() + cell.deleteHandler = nil // Don't allow deletion of loading thumbnails + } else { + cell.configure(with: capturedImages[indexPath.item]) + cell.deleteHandler = { [weak self] in + self?.removeImage(at: indexPath.item) + } + } + + return cell + } +} + +// MARK: - UICollectionViewDelegate +extension MultiCameraViewController: UICollectionViewDelegate { + func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) { + // Don't allow interaction with loading thumbnails + guard indexPath.item < loadingStates.count && !loadingStates[indexPath.item] else { + return + } + + // Filter out any loading images for the preview + let nonLoadingImages = capturedImages.enumerated().compactMap { index, image in + loadingStates.indices.contains(index) && !loadingStates[index] ? image : nil + } + + // Calculate the correct starting index in the filtered array + let nonLoadingIndices = loadingStates.enumerated().compactMap { index, isLoading in + !isLoading ? index : nil + } + + guard let startingIndex = nonLoadingIndices.firstIndex(of: indexPath.item) else { + return + } + + // Create a custom image preview controller with all non-loading images + let previewController = ImagePreviewViewController( + images: nonLoadingImages, startingIndex: startingIndex) + + // Present it modally + present(previewController, animated: true) + } +} diff --git a/camera/ios/Sources/CameraPlugin/Resources/Assets.xcassets/Contents.json b/camera/ios/Sources/CameraPlugin/Resources/Assets.xcassets/Contents.json new file mode 100644 index 0000000000..4aa7c5350b --- /dev/null +++ b/camera/ios/Sources/CameraPlugin/Resources/Assets.xcassets/Contents.json @@ -0,0 +1,6 @@ +{ + "info" : { + "author" : "xcode", + "version" : 1 + } +} \ No newline at end of file diff --git a/camera/ios/Sources/CameraPlugin/Resources/Assets.xcassets/camera_capture.imageset/Contents.json b/camera/ios/Sources/CameraPlugin/Resources/Assets.xcassets/camera_capture.imageset/Contents.json new file mode 100644 index 0000000000..5d64d07a6b --- /dev/null +++ b/camera/ios/Sources/CameraPlugin/Resources/Assets.xcassets/camera_capture.imageset/Contents.json @@ -0,0 +1,20 @@ +{ + "images" : [ + { + "idiom" : "universal", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} \ No newline at end of file diff --git a/camera/package.json b/camera/package.json index b7f7127eda..0ec7ebb959 100644 --- a/camera/package.json +++ b/camera/package.json @@ -1,6 +1,6 @@ { "name": "@capacitor/camera", - "version": "7.0.1", + "version": "8.0.0-alpha.3", "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", @@ -47,10 +47,10 @@ "publish:cocoapod": "pod trunk push ./CapacitorCamera.podspec --allow-warnings" }, "devDependencies": { - "@capacitor/android": "^7.0.0", - "@capacitor/core": "^7.0.0", + "@capacitor/android": "next", + "@capacitor/core": "next", "@capacitor/docgen": "0.2.2", - "@capacitor/ios": "^7.0.0", + "@capacitor/ios": "next", "@ionic/eslint-config": "^0.4.0", "@ionic/prettier-config": "~1.0.1", "@ionic/swiftlint-config": "^1.1.2", @@ -63,7 +63,7 @@ "typescript": "~4.1.5" }, "peerDependencies": { - "@capacitor/core": ">=7.0.0" + "@capacitor/core": "next" }, "prettier": "@ionic/prettier-config", "swiftlint": "@ionic/swiftlint-config", diff --git a/camera/src/definitions.ts b/camera/src/definitions.ts index 83e39aa9ae..e1b433b5e5 100644 --- a/camera/src/definitions.ts +++ b/camera/src/definitions.ts @@ -24,22 +24,21 @@ export interface CameraPlugin { /** * Allows the user to pick multiple pictures from the photo gallery. - * On iOS 13 and older it only allows to pick one picture. * * @since 1.2.0 */ pickImages(options: GalleryImageOptions): Promise; /** - * iOS 14+ Only: Allows the user to update their limited photo library selection. - * On iOS 15+ returns all the limited photos after the picker dismissal. - * On iOS 14 or if the user gave full access to the photos it returns an empty array. + * 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; /** - * iOS 14+ Only: Return an array of photos selected from the limited photo library. + * Return an array of photos selected from the limited photo library. * * @since 4.1.0 */ @@ -72,7 +71,7 @@ export interface ImageOptions { quality?: number; /** * Whether to allow the user to crop or make small edits (platform specific). - * On iOS 14+ it's only supported for CameraSource.Camera, but not for CameraSource.Photos. + * On iOS it's only supported for CameraSource.Camera, but not for CameraSource.Photos. * * @since 1.0.0 */ @@ -335,6 +334,11 @@ export enum CameraSource { * Take a new photo using the camera. */ Camera = 'CAMERA', + /** + * Take multiple photos in a row using the camera. + * Available on Android and iOS. + */ + CameraMulti = 'CAMERA_MULTI', /** * Pick an existing photo from the gallery or photo album. */ diff --git a/clipboard/CHANGELOG.md b/clipboard/CHANGELOG.md index b567724035..4564b34049 100644 --- a/clipboard/CHANGELOG.md +++ b/clipboard/CHANGELOG.md @@ -3,6 +3,14 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +# [8.0.0-alpha.1](https://github.com/ionic-team/capacitor-plugins/compare/@capacitor/clipboard@7.0.2...@capacitor/clipboard@8.0.0-alpha.1) (2025-09-08) + +**Note:** Version bump only for package @capacitor/clipboard + +## [7.0.2](https://github.com/ionic-team/capacitor-plugins/compare/@capacitor/clipboard@7.0.1...@capacitor/clipboard@7.0.2) (2025-08-05) + +**Note:** Version bump only for package @capacitor/clipboard + ## [7.0.1](https://github.com/ionic-team/capacitor-plugins/compare/@capacitor/clipboard@7.0.0...@capacitor/clipboard@7.0.1) (2025-04-02) **Note:** Version bump only for package @capacitor/clipboard diff --git a/clipboard/CapacitorClipboard.podspec b/clipboard/CapacitorClipboard.podspec index 26e48485e4..3a080ee08b 100644 --- a/clipboard/CapacitorClipboard.podspec +++ b/clipboard/CapacitorClipboard.podspec @@ -11,7 +11,7 @@ Pod::Spec.new do |s| 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}', 'clipboard/ios/Sources/**/*.{swift,h,m,c,cc,mm,cpp}' - s.ios.deployment_target = '14.0' + s.ios.deployment_target = '15.0' s.dependency 'Capacitor' s.swift_version = '5.1' end diff --git a/clipboard/Package.swift b/clipboard/Package.swift index 45660134b5..5be07464cd 100644 --- a/clipboard/Package.swift +++ b/clipboard/Package.swift @@ -3,7 +3,7 @@ import PackageDescription let package = Package( name: "CapacitorClipboard", - platforms: [.iOS(.v14)], + platforms: [.iOS(.v15)], products: [ .library( name: "CapacitorClipboard", diff --git a/clipboard/android/build.gradle b/clipboard/android/build.gradle index 2fbdf02c2b..aca40a951a 100644 --- a/clipboard/android/build.gradle +++ b/clipboard/android/build.gradle @@ -1,9 +1,9 @@ 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.0' - androidxJunitVersion = project.hasProperty('androidxJunitVersion') ? rootProject.ext.androidxJunitVersion : '1.2.1' - androidxEspressoCoreVersion = project.hasProperty('androidxEspressoCoreVersion') ? rootProject.ext.androidxEspressoCoreVersion : '3.6.1' + androidxAppCompatVersion = project.hasProperty('androidxAppCompatVersion') ? rootProject.ext.androidxAppCompatVersion : '1.7.1' + androidxJunitVersion = project.hasProperty('androidxJunitVersion') ? rootProject.ext.androidxJunitVersion : '1.3.0' + androidxEspressoCoreVersion = project.hasProperty('androidxEspressoCoreVersion') ? rootProject.ext.androidxEspressoCoreVersion : '3.7.0' } buildscript { @@ -11,11 +11,11 @@ buildscript { google() mavenCentral() maven { - url "https://plugins.gradle.org/m2/" + url = "https://plugins.gradle.org/m2/" } } dependencies { - classpath 'com.android.tools.build:gradle:8.7.2' + 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' } @@ -30,11 +30,11 @@ if (System.getenv("CAP_PLUGIN_PUBLISH") == "true") { } android { - namespace "com.capacitorjs.plugins.clipboard" - compileSdk project.hasProperty('compileSdkVersion') ? rootProject.ext.compileSdkVersion : 35 + namespace = "com.capacitorjs.plugins.clipboard" + compileSdk = project.hasProperty('compileSdkVersion') ? rootProject.ext.compileSdkVersion : 36 defaultConfig { - minSdkVersion project.hasProperty('minSdkVersion') ? rootProject.ext.minSdkVersion : 23 - targetSdkVersion project.hasProperty('targetSdkVersion') ? rootProject.ext.targetSdkVersion : 35 + 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" @@ -46,7 +46,7 @@ android { } } lintOptions { - abortOnError false + abortOnError = false } compileOptions { sourceCompatibility JavaVersion.VERSION_21 diff --git a/clipboard/android/gradle/wrapper/gradle-wrapper.jar b/clipboard/android/gradle/wrapper/gradle-wrapper.jar index a4b76b9530..1b33c55baa 100644 Binary files a/clipboard/android/gradle/wrapper/gradle-wrapper.jar and b/clipboard/android/gradle/wrapper/gradle-wrapper.jar differ diff --git a/clipboard/android/gradle/wrapper/gradle-wrapper.properties b/clipboard/android/gradle/wrapper/gradle-wrapper.properties index c1d5e01859..7705927e94 100644 --- a/clipboard/android/gradle/wrapper/gradle-wrapper.properties +++ b/clipboard/android/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-8.11.1-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.14.3-all.zip networkTimeout=10000 validateDistributionUrl=true zipStoreBase=GRADLE_USER_HOME diff --git a/clipboard/android/gradlew b/clipboard/android/gradlew index f5feea6d6b..23d15a9367 100755 --- a/clipboard/android/gradlew +++ b/clipboard/android/gradlew @@ -86,8 +86,7 @@ done # 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 -' "$PWD" ) || exit +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 @@ -115,7 +114,7 @@ case "$( uname )" in #( NONSTOP* ) nonstop=true ;; esac -CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar +CLASSPATH="\\\"\\\"" # Determine the Java command to use to start the JVM. @@ -206,7 +205,7 @@ fi DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' # Collect all arguments for the java command: -# * DEFAULT_JVM_OPTS, JAVA_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, +# * 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. @@ -214,7 +213,7 @@ DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' set -- \ "-Dorg.gradle.appname=$APP_BASE_NAME" \ -classpath "$CLASSPATH" \ - org.gradle.wrapper.GradleWrapperMain \ + -jar "$APP_HOME/gradle/wrapper/gradle-wrapper.jar" \ "$@" # Stop when "xargs" is not available. diff --git a/clipboard/android/gradlew.bat b/clipboard/android/gradlew.bat index 9b42019c79..5eed7ee845 100644 --- a/clipboard/android/gradlew.bat +++ b/clipboard/android/gradlew.bat @@ -70,11 +70,11 @@ goto fail :execute @rem Setup the command line -set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar +set CLASSPATH= @rem Execute Gradle -"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* +"%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 diff --git a/clipboard/package.json b/clipboard/package.json index 4823bb5f1b..6e0fff752c 100644 --- a/clipboard/package.json +++ b/clipboard/package.json @@ -1,6 +1,6 @@ { "name": "@capacitor/clipboard", - "version": "7.0.1", + "version": "8.0.0-alpha.1", "description": "The Clipboard API enables copy and pasting to/from the system clipboard.", "main": "dist/plugin.cjs.js", "module": "dist/esm/index.js", @@ -47,10 +47,10 @@ "publish:cocoapod": "pod trunk push ./CapacitorClipboard.podspec --allow-warnings" }, "devDependencies": { - "@capacitor/android": "^7.0.0", - "@capacitor/core": "^7.0.0", + "@capacitor/android": "next", + "@capacitor/core": "next", "@capacitor/docgen": "0.2.2", - "@capacitor/ios": "^7.0.0", + "@capacitor/ios": "next", "@ionic/eslint-config": "^0.4.0", "@ionic/prettier-config": "~1.0.1", "@ionic/swiftlint-config": "^1.1.2", @@ -63,7 +63,7 @@ "typescript": "~4.1.5" }, "peerDependencies": { - "@capacitor/core": ">=7.0.0" + "@capacitor/core": "next" }, "prettier": "@ionic/prettier-config", "swiftlint": "@ionic/swiftlint-config", diff --git a/device/CHANGELOG.md b/device/CHANGELOG.md index 548ccd0684..68b0898ba6 100644 --- a/device/CHANGELOG.md +++ b/device/CHANGELOG.md @@ -3,6 +3,14 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +# [8.0.0-alpha.1](https://github.com/ionic-team/capacitor-plugins/compare/@capacitor/device@7.0.2...@capacitor/device@8.0.0-alpha.1) (2025-09-08) + +**Note:** Version bump only for package @capacitor/device + +## [7.0.2](https://github.com/ionic-team/capacitor-plugins/compare/@capacitor/device@7.0.1...@capacitor/device@7.0.2) (2025-08-05) + +**Note:** Version bump only for package @capacitor/device + ## [7.0.1](https://github.com/ionic-team/capacitor-plugins/compare/@capacitor/device@7.0.0...@capacitor/device@7.0.1) (2025-04-02) **Note:** Version bump only for package @capacitor/device diff --git a/device/CapacitorDevice.podspec b/device/CapacitorDevice.podspec index 6ca2e47228..1ce6210c60 100644 --- a/device/CapacitorDevice.podspec +++ b/device/CapacitorDevice.podspec @@ -11,7 +11,7 @@ Pod::Spec.new do |s| 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}', 'device/ios/Sources/**/*.{swift,h,m,c,cc,mm,cpp}' - s.ios.deployment_target = '14.0' + s.ios.deployment_target = '15.0' s.dependency 'Capacitor' s.swift_version = '5.1' end diff --git a/device/Package.swift b/device/Package.swift index 9d28293947..f433af51a6 100644 --- a/device/Package.swift +++ b/device/Package.swift @@ -3,7 +3,7 @@ import PackageDescription let package = Package( name: "CapacitorDevice", - platforms: [.iOS(.v14)], + platforms: [.iOS(.v15)], products: [ .library( name: "CapacitorDevice", diff --git a/device/android/build.gradle b/device/android/build.gradle index c048bab0f9..f70683b577 100644 --- a/device/android/build.gradle +++ b/device/android/build.gradle @@ -1,9 +1,9 @@ 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.0' - androidxJunitVersion = project.hasProperty('androidxJunitVersion') ? rootProject.ext.androidxJunitVersion : '1.2.1' - androidxEspressoCoreVersion = project.hasProperty('androidxEspressoCoreVersion') ? rootProject.ext.androidxEspressoCoreVersion : '3.6.1' + androidxAppCompatVersion = project.hasProperty('androidxAppCompatVersion') ? rootProject.ext.androidxAppCompatVersion : '1.7.1' + androidxJunitVersion = project.hasProperty('androidxJunitVersion') ? rootProject.ext.androidxJunitVersion : '1.3.0' + androidxEspressoCoreVersion = project.hasProperty('androidxEspressoCoreVersion') ? rootProject.ext.androidxEspressoCoreVersion : '3.7.0' } buildscript { @@ -11,11 +11,11 @@ buildscript { google() mavenCentral() maven { - url "https://plugins.gradle.org/m2/" + url = "https://plugins.gradle.org/m2/" } } dependencies { - classpath 'com.android.tools.build:gradle:8.7.2' + 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' } @@ -30,11 +30,11 @@ if (System.getenv("CAP_PLUGIN_PUBLISH") == "true") { } android { - namespace "com.capacitorjs.plugins.device" - compileSdk project.hasProperty('compileSdkVersion') ? rootProject.ext.compileSdkVersion : 35 + namespace = "com.capacitorjs.plugins.device" + compileSdk = project.hasProperty('compileSdkVersion') ? rootProject.ext.compileSdkVersion : 36 defaultConfig { - minSdkVersion project.hasProperty('minSdkVersion') ? rootProject.ext.minSdkVersion : 23 - targetSdkVersion project.hasProperty('targetSdkVersion') ? rootProject.ext.targetSdkVersion : 35 + 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" @@ -46,7 +46,7 @@ android { } } lintOptions { - abortOnError false + abortOnError = false } compileOptions { sourceCompatibility JavaVersion.VERSION_21 diff --git a/device/android/gradle/wrapper/gradle-wrapper.jar b/device/android/gradle/wrapper/gradle-wrapper.jar index a4b76b9530..1b33c55baa 100644 Binary files a/device/android/gradle/wrapper/gradle-wrapper.jar and b/device/android/gradle/wrapper/gradle-wrapper.jar differ diff --git a/device/android/gradle/wrapper/gradle-wrapper.properties b/device/android/gradle/wrapper/gradle-wrapper.properties index c1d5e01859..7705927e94 100644 --- a/device/android/gradle/wrapper/gradle-wrapper.properties +++ b/device/android/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-8.11.1-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.14.3-all.zip networkTimeout=10000 validateDistributionUrl=true zipStoreBase=GRADLE_USER_HOME diff --git a/device/android/gradlew b/device/android/gradlew index f5feea6d6b..23d15a9367 100755 --- a/device/android/gradlew +++ b/device/android/gradlew @@ -86,8 +86,7 @@ done # 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 -' "$PWD" ) || exit +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 @@ -115,7 +114,7 @@ case "$( uname )" in #( NONSTOP* ) nonstop=true ;; esac -CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar +CLASSPATH="\\\"\\\"" # Determine the Java command to use to start the JVM. @@ -206,7 +205,7 @@ fi DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' # Collect all arguments for the java command: -# * DEFAULT_JVM_OPTS, JAVA_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, +# * 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. @@ -214,7 +213,7 @@ DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' set -- \ "-Dorg.gradle.appname=$APP_BASE_NAME" \ -classpath "$CLASSPATH" \ - org.gradle.wrapper.GradleWrapperMain \ + -jar "$APP_HOME/gradle/wrapper/gradle-wrapper.jar" \ "$@" # Stop when "xargs" is not available. diff --git a/device/android/gradlew.bat b/device/android/gradlew.bat index 9b42019c79..5eed7ee845 100644 --- a/device/android/gradlew.bat +++ b/device/android/gradlew.bat @@ -70,11 +70,11 @@ goto fail :execute @rem Setup the command line -set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar +set CLASSPATH= @rem Execute Gradle -"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* +"%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 diff --git a/device/android/src/main/java/com/capacitorjs/plugins/device/Device.java b/device/android/src/main/java/com/capacitorjs/plugins/device/Device.java index 40d997550b..d8be7d405e 100644 --- a/device/android/src/main/java/com/capacitorjs/plugins/device/Device.java +++ b/device/android/src/main/java/com/capacitorjs/plugins/device/Device.java @@ -88,13 +88,8 @@ public String getWebViewVersion() { return android.os.Build.VERSION.RELEASE; } - @SuppressWarnings("deprecation") private PackageInfo getWebViewVersionSubAndroid26() throws PackageManager.NameNotFoundException { - String webViewPackage = "com.google.android.webview"; - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { - webViewPackage = "com.android.chrome"; - } PackageManager pm = this.context.getPackageManager(); - return pm.getPackageInfo(webViewPackage, 0); + return pm.getPackageInfo("com.android.chrome", 0); } } diff --git a/device/package.json b/device/package.json index 8975a81b4f..2c4b4415d5 100644 --- a/device/package.json +++ b/device/package.json @@ -1,6 +1,6 @@ { "name": "@capacitor/device", - "version": "7.0.1", + "version": "8.0.0-alpha.1", "description": "The Device API exposes internal information about the device, such as the model and operating system version, along with user information such as unique ids.", "main": "dist/plugin.cjs.js", "module": "dist/esm/index.js", @@ -30,7 +30,7 @@ "native" ], "scripts": { - "test": "uvu -r esm -r ts-node/register src/__tests__", + "test": "uvu -r tsm src/__tests__", "verify": "npm run verify:ios && npm run verify:android && npm run verify:web", "verify:ios": "xcodebuild build -scheme CapacitorDevice -destination generic/platform=iOS", "verify:android": "cd android && ./gradlew clean build test && cd ..", @@ -48,26 +48,25 @@ "publish:cocoapod": "pod trunk push ./CapacitorDevice.podspec --allow-warnings" }, "devDependencies": { - "@capacitor/android": "^7.0.0", - "@capacitor/core": "^7.0.0", + "@capacitor/android": "next", + "@capacitor/core": "next", "@capacitor/docgen": "0.2.2", - "@capacitor/ios": "^7.0.0", + "@capacitor/ios": "next", "@ionic/eslint-config": "^0.4.0", "@ionic/prettier-config": "~1.0.1", "@ionic/swiftlint-config": "^1.1.2", "eslint": "^8.57.0", - "esm": "^3.2.25", "prettier": "~2.3.0", "prettier-plugin-java": "~1.0.2", "rimraf": "^6.0.1", "rollup": "^4.26.0", "swiftlint": "^1.0.1", - "ts-node": "^9.1.1", + "tsm": "^2.3.0", "typescript": "~4.1.5", "uvu": "^0.5.1" }, "peerDependencies": { - "@capacitor/core": ">=7.0.0" + "@capacitor/core": "next" }, "prettier": "@ionic/prettier-config", "swiftlint": "@ionic/swiftlint-config", diff --git a/dialog/CHANGELOG.md b/dialog/CHANGELOG.md index bab56e2649..a57c72adba 100644 --- a/dialog/CHANGELOG.md +++ b/dialog/CHANGELOG.md @@ -3,6 +3,14 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +# [8.0.0-alpha.1](https://github.com/ionic-team/capacitor-plugins/compare/@capacitor/dialog@7.0.2...@capacitor/dialog@8.0.0-alpha.1) (2025-09-08) + +**Note:** Version bump only for package @capacitor/dialog + +## [7.0.2](https://github.com/ionic-team/capacitor-plugins/compare/@capacitor/dialog@7.0.1...@capacitor/dialog@7.0.2) (2025-08-05) + +**Note:** Version bump only for package @capacitor/dialog + ## [7.0.1](https://github.com/ionic-team/capacitor-plugins/compare/@capacitor/dialog@7.0.0...@capacitor/dialog@7.0.1) (2025-04-02) **Note:** Version bump only for package @capacitor/dialog diff --git a/dialog/CapacitorDialog.podspec b/dialog/CapacitorDialog.podspec index 347563c181..063159bc47 100644 --- a/dialog/CapacitorDialog.podspec +++ b/dialog/CapacitorDialog.podspec @@ -11,7 +11,7 @@ Pod::Spec.new do |s| 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}', 'dialog/ios/Sources/**/*.{swift,h,m,c,cc,mm,cpp}' - s.ios.deployment_target = '14.0' + s.ios.deployment_target = '15.0' s.dependency 'Capacitor' s.swift_version = '5.1' end diff --git a/dialog/Package.swift b/dialog/Package.swift index e1f7b322e6..29cbc62d9f 100644 --- a/dialog/Package.swift +++ b/dialog/Package.swift @@ -3,7 +3,7 @@ import PackageDescription let package = Package( name: "CapacitorDialog", - platforms: [.iOS(.v14)], + platforms: [.iOS(.v15)], products: [ .library( name: "CapacitorDialog", diff --git a/dialog/android/build.gradle b/dialog/android/build.gradle index 4ba77b25f6..e3fe6ace0d 100644 --- a/dialog/android/build.gradle +++ b/dialog/android/build.gradle @@ -1,9 +1,9 @@ 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.0' - androidxJunitVersion = project.hasProperty('androidxJunitVersion') ? rootProject.ext.androidxJunitVersion : '1.2.1' - androidxEspressoCoreVersion = project.hasProperty('androidxEspressoCoreVersion') ? rootProject.ext.androidxEspressoCoreVersion : '3.6.1' + androidxAppCompatVersion = project.hasProperty('androidxAppCompatVersion') ? rootProject.ext.androidxAppCompatVersion : '1.7.1' + androidxJunitVersion = project.hasProperty('androidxJunitVersion') ? rootProject.ext.androidxJunitVersion : '1.3.0' + androidxEspressoCoreVersion = project.hasProperty('androidxEspressoCoreVersion') ? rootProject.ext.androidxEspressoCoreVersion : '3.7.0' } buildscript { @@ -11,11 +11,11 @@ buildscript { google() mavenCentral() maven { - url "https://plugins.gradle.org/m2/" + url = "https://plugins.gradle.org/m2/" } } dependencies { - classpath 'com.android.tools.build:gradle:8.7.2' + 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' } @@ -30,11 +30,11 @@ if (System.getenv("CAP_PLUGIN_PUBLISH") == "true") { } android { - namespace "com.capacitorjs.plugins.dialog" - compileSdk project.hasProperty('compileSdkVersion') ? rootProject.ext.compileSdkVersion : 35 + namespace = "com.capacitorjs.plugins.dialog" + compileSdk = project.hasProperty('compileSdkVersion') ? rootProject.ext.compileSdkVersion : 36 defaultConfig { - minSdkVersion project.hasProperty('minSdkVersion') ? rootProject.ext.minSdkVersion : 23 - targetSdkVersion project.hasProperty('targetSdkVersion') ? rootProject.ext.targetSdkVersion : 35 + 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" @@ -46,7 +46,7 @@ android { } } lintOptions { - abortOnError false + abortOnError = false } compileOptions { sourceCompatibility JavaVersion.VERSION_21 diff --git a/dialog/android/gradle/wrapper/gradle-wrapper.jar b/dialog/android/gradle/wrapper/gradle-wrapper.jar index a4b76b9530..1b33c55baa 100644 Binary files a/dialog/android/gradle/wrapper/gradle-wrapper.jar and b/dialog/android/gradle/wrapper/gradle-wrapper.jar differ diff --git a/dialog/android/gradle/wrapper/gradle-wrapper.properties b/dialog/android/gradle/wrapper/gradle-wrapper.properties index c1d5e01859..7705927e94 100644 --- a/dialog/android/gradle/wrapper/gradle-wrapper.properties +++ b/dialog/android/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-8.11.1-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.14.3-all.zip networkTimeout=10000 validateDistributionUrl=true zipStoreBase=GRADLE_USER_HOME diff --git a/dialog/android/gradlew b/dialog/android/gradlew index f5feea6d6b..23d15a9367 100755 --- a/dialog/android/gradlew +++ b/dialog/android/gradlew @@ -86,8 +86,7 @@ done # 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 -' "$PWD" ) || exit +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 @@ -115,7 +114,7 @@ case "$( uname )" in #( NONSTOP* ) nonstop=true ;; esac -CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar +CLASSPATH="\\\"\\\"" # Determine the Java command to use to start the JVM. @@ -206,7 +205,7 @@ fi DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' # Collect all arguments for the java command: -# * DEFAULT_JVM_OPTS, JAVA_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, +# * 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. @@ -214,7 +213,7 @@ DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' set -- \ "-Dorg.gradle.appname=$APP_BASE_NAME" \ -classpath "$CLASSPATH" \ - org.gradle.wrapper.GradleWrapperMain \ + -jar "$APP_HOME/gradle/wrapper/gradle-wrapper.jar" \ "$@" # Stop when "xargs" is not available. diff --git a/dialog/android/gradlew.bat b/dialog/android/gradlew.bat index 9b42019c79..5eed7ee845 100644 --- a/dialog/android/gradlew.bat +++ b/dialog/android/gradlew.bat @@ -70,11 +70,11 @@ goto fail :execute @rem Setup the command line -set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar +set CLASSPATH= @rem Execute Gradle -"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* +"%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 diff --git a/dialog/package.json b/dialog/package.json index b3973c3daa..6de77e20e8 100644 --- a/dialog/package.json +++ b/dialog/package.json @@ -1,6 +1,6 @@ { "name": "@capacitor/dialog", - "version": "7.0.1", + "version": "8.0.0-alpha.1", "description": "The Dialog API provides methods for triggering native dialog windows for alerts, confirmations, and input prompts", "main": "dist/plugin.cjs.js", "module": "dist/esm/index.js", @@ -47,10 +47,10 @@ "publish:cocoapod": "pod trunk push ./CapacitorDialog.podspec --allow-warnings" }, "devDependencies": { - "@capacitor/android": "^7.0.0", - "@capacitor/core": "^7.0.0", + "@capacitor/android": "next", + "@capacitor/core": "next", "@capacitor/docgen": "0.2.2", - "@capacitor/ios": "^7.0.0", + "@capacitor/ios": "next", "@ionic/eslint-config": "^0.4.0", "@ionic/prettier-config": "~1.0.1", "@ionic/swiftlint-config": "^1.1.2", @@ -63,7 +63,7 @@ "typescript": "~4.1.5" }, "peerDependencies": { - "@capacitor/core": ">=7.0.0" + "@capacitor/core": "next" }, "prettier": "@ionic/prettier-config", "swiftlint": "@ionic/swiftlint-config", diff --git a/dist/docs.json b/dist/docs.json new file mode 100644 index 0000000000..c1edbfd3c4 --- /dev/null +++ b/dist/docs.json @@ -0,0 +1,810 @@ +{ + "api": { + "name": "CameraPlugin", + "slug": "cameraplugin", + "docs": "", + "tags": [], + "methods": [ + { + "name": "getPhoto", + "signature": "(options: ImageOptions) => Promise", + "parameters": [ + { + "name": "options", + "docs": "", + "type": "ImageOptions" + } + ], + "returns": "Promise", + "tags": [ + { + "name": "since", + "text": "1.0.0" + } + ], + "docs": "Prompt the user to pick a photo from an album, or take a new photo\nwith the camera.", + "complexTypes": [ + "Photo", + "ImageOptions" + ], + "slug": "getphoto" + }, + { + "name": "pickImages", + "signature": "(options: GalleryImageOptions) => Promise", + "parameters": [ + { + "name": "options", + "docs": "", + "type": "GalleryImageOptions" + } + ], + "returns": "Promise", + "tags": [ + { + "name": "since", + "text": "1.2.0" + } + ], + "docs": "Allows the user to pick multiplef pictures from the photo gallery.", + "complexTypes": [ + "GalleryPhotos", + "GalleryImageOptions" + ], + "slug": "pickimages" + }, + { + "name": "pickLimitedLibraryPhotos", + "signature": "() => Promise", + "parameters": [], + "returns": "Promise", + "tags": [ + { + "name": "since", + "text": "4.1.0" + } + ], + "docs": "Allows the user to update their limited photo library selection.\nReturns all the limited photos after the picker dismissal.\nIf instead the user gave full access to the photos it returns an empty array.", + "complexTypes": [ + "GalleryPhotos" + ], + "slug": "picklimitedlibraryphotos" + }, + { + "name": "getLimitedLibraryPhotos", + "signature": "() => Promise", + "parameters": [], + "returns": "Promise", + "tags": [ + { + "name": "since", + "text": "4.1.0" + } + ], + "docs": "Return an array of photos selected from the limited photo library.", + "complexTypes": [ + "GalleryPhotos" + ], + "slug": "getlimitedlibraryphotos" + }, + { + "name": "checkPermissions", + "signature": "() => Promise", + "parameters": [], + "returns": "Promise", + "tags": [ + { + "name": "since", + "text": "1.0.0" + } + ], + "docs": "Check camera and photo album permissions", + "complexTypes": [ + "PermissionStatus" + ], + "slug": "checkpermissions" + }, + { + "name": "requestPermissions", + "signature": "(permissions?: CameraPluginPermissions | undefined) => Promise", + "parameters": [ + { + "name": "permissions", + "docs": "", + "type": "CameraPluginPermissions | undefined" + } + ], + "returns": "Promise", + "tags": [ + { + "name": "since", + "text": "1.0.0" + } + ], + "docs": "Request camera and photo album permissions", + "complexTypes": [ + "PermissionStatus", + "CameraPluginPermissions" + ], + "slug": "requestpermissions" + } + ], + "properties": [] + }, + "interfaces": [ + { + "name": "Photo", + "slug": "photo", + "docs": "", + "tags": [], + "methods": [], + "properties": [ + { + "name": "base64String", + "tags": [ + { + "text": "1.0.0", + "name": "since" + } + ], + "docs": "The base64 encoded string representation of the image, if using CameraResultType.Base64.", + "complexTypes": [], + "type": "string | undefined" + }, + { + "name": "dataUrl", + "tags": [ + { + "text": "1.0.0", + "name": "since" + } + ], + "docs": "The url starting with 'data:image/jpeg;base64,' and the base64 encoded string representation of the image, if using CameraResultType.DataUrl.\n\nNote: On web, the file format could change depending on the browser.", + "complexTypes": [], + "type": "string | undefined" + }, + { + "name": "path", + "tags": [ + { + "text": "1.0.0", + "name": "since" + } + ], + "docs": "If using CameraResultType.Uri, the path will contain a full,\nplatform-specific file URL that can be read later using the Filesystem API.", + "complexTypes": [], + "type": "string | undefined" + }, + { + "name": "webPath", + "tags": [ + { + "text": "1.0.0", + "name": "since" + } + ], + "docs": "webPath returns a path that can be used to set the src attribute of an image for efficient\nloading and rendering.", + "complexTypes": [], + "type": "string | undefined" + }, + { + "name": "exif", + "tags": [ + { + "text": "1.0.0", + "name": "since" + } + ], + "docs": "Exif data, if any, retrieved from the image", + "complexTypes": [], + "type": "any" + }, + { + "name": "format", + "tags": [ + { + "text": "1.0.0", + "name": "since" + } + ], + "docs": "The format of the image, ex: jpeg, png, gif.\n\niOS and Android only support jpeg.\nWeb supports jpeg, png and gif, but the exact availability may vary depending on the browser.\ngif is only supported if `webUseInput` is set to `true` or if `source` is set to `Photos`.", + "complexTypes": [], + "type": "string" + }, + { + "name": "saved", + "tags": [ + { + "text": "1.1.0", + "name": "since" + } + ], + "docs": "Whether if the image was saved to the gallery or not.\n\nOn Android and iOS, saving to the gallery can fail if the user didn't\ngrant the required permissions.\nOn Web there is no gallery, so always returns false.", + "complexTypes": [], + "type": "boolean" + } + ] + }, + { + "name": "ImageOptions", + "slug": "imageoptions", + "docs": "", + "tags": [], + "methods": [], + "properties": [ + { + "name": "quality", + "tags": [ + { + "text": "1.0.0", + "name": "since" + } + ], + "docs": "The quality of image to return as JPEG, from 0-100\nNote: This option is only supported on Android and iOS", + "complexTypes": [], + "type": "number | undefined" + }, + { + "name": "allowEditing", + "tags": [ + { + "text": "1.0.0", + "name": "since" + } + ], + "docs": "Whether to allow the user to crop or make small edits (platform specific).\nOn iOS it's only supported for CameraSource.Camera, but not for CameraSource.Photos.", + "complexTypes": [], + "type": "boolean | undefined" + }, + { + "name": "resultType", + "tags": [ + { + "text": "1.0.0", + "name": "since" + } + ], + "docs": "How the data should be returned. Currently, only 'Base64', 'DataUrl' or 'Uri' is supported", + "complexTypes": [ + "CameraResultType" + ], + "type": "CameraResultType" + }, + { + "name": "saveToGallery", + "tags": [ + { + "text": ": false", + "name": "default" + }, + { + "text": "1.0.0", + "name": "since" + } + ], + "docs": "Whether to save the photo to the gallery.\nIf the photo was picked from the gallery, it will only be saved if edited.", + "complexTypes": [], + "type": "boolean | undefined" + }, + { + "name": "width", + "tags": [ + { + "text": "1.0.0", + "name": "since" + } + ], + "docs": "The desired maximum width of the saved image. The aspect ratio is respected.", + "complexTypes": [], + "type": "number | undefined" + }, + { + "name": "height", + "tags": [ + { + "text": "1.0.0", + "name": "since" + } + ], + "docs": "The desired maximum height of the saved image. The aspect ratio is respected.", + "complexTypes": [], + "type": "number | undefined" + }, + { + "name": "correctOrientation", + "tags": [ + { + "text": ": true", + "name": "default" + }, + { + "text": "1.0.0", + "name": "since" + } + ], + "docs": "Whether to automatically rotate the image \"up\" to correct for orientation\nin portrait mode", + "complexTypes": [], + "type": "boolean | undefined" + }, + { + "name": "source", + "tags": [ + { + "text": ": CameraSource.Prompt", + "name": "default" + }, + { + "text": "1.0.0", + "name": "since" + } + ], + "docs": "The source to get the photo from. By default this prompts the user to select\neither the photo album or take a photo.", + "complexTypes": [ + "CameraSource" + ], + "type": "CameraSource" + }, + { + "name": "direction", + "tags": [ + { + "text": ": CameraDirection.Rear", + "name": "default" + }, + { + "text": "1.0.0", + "name": "since" + } + ], + "docs": "iOS and Web only: The camera direction.", + "complexTypes": [ + "CameraDirection" + ], + "type": "CameraDirection" + }, + { + "name": "presentationStyle", + "tags": [ + { + "text": ": 'fullscreen'", + "name": "default" + }, + { + "text": "1.0.0", + "name": "since" + } + ], + "docs": "iOS only: The presentation style of the Camera.", + "complexTypes": [], + "type": "'fullscreen' | 'popover' | undefined" + }, + { + "name": "webUseInput", + "tags": [ + { + "text": "1.0.0", + "name": "since" + } + ], + "docs": "Web only: Whether to use the PWA Element experience or file input. The\ndefault is to use PWA Elements if installed and fall back to file input.\nTo always use file input, set this to `true`.\n\nLearn more about PWA Elements: https://capacitorjs.com/docs/web/pwa-elements", + "complexTypes": [], + "type": "boolean | undefined" + }, + { + "name": "promptLabelHeader", + "tags": [ + { + "text": ": 'Photo'", + "name": "default" + }, + { + "text": "1.0.0", + "name": "since" + } + ], + "docs": "Text value to use when displaying the prompt.", + "complexTypes": [], + "type": "string | undefined" + }, + { + "name": "promptLabelCancel", + "tags": [ + { + "text": ": 'Cancel'", + "name": "default" + }, + { + "text": "1.0.0", + "name": "since" + } + ], + "docs": "Text value to use when displaying the prompt.\niOS only: The label of the 'cancel' button.", + "complexTypes": [], + "type": "string | undefined" + }, + { + "name": "promptLabelPhoto", + "tags": [ + { + "text": ": 'From Photos'", + "name": "default" + }, + { + "text": "1.0.0", + "name": "since" + } + ], + "docs": "Text value to use when displaying the prompt.\nThe label of the button to select a saved image.", + "complexTypes": [], + "type": "string | undefined" + }, + { + "name": "promptLabelPicture", + "tags": [ + { + "text": ": 'Take Picture'", + "name": "default" + }, + { + "text": "1.0.0", + "name": "since" + } + ], + "docs": "Text value to use when displaying the prompt.\nThe label of the button to open the camera.", + "complexTypes": [], + "type": "string | undefined" + } + ] + }, + { + "name": "GalleryPhotos", + "slug": "galleryphotos", + "docs": "", + "tags": [], + "methods": [], + "properties": [ + { + "name": "photos", + "tags": [ + { + "text": "1.2.0", + "name": "since" + } + ], + "docs": "Array of all the picked photos.", + "complexTypes": [ + "GalleryPhoto" + ], + "type": "GalleryPhoto[]" + } + ] + }, + { + "name": "GalleryPhoto", + "slug": "galleryphoto", + "docs": "", + "tags": [], + "methods": [], + "properties": [ + { + "name": "path", + "tags": [ + { + "text": "1.2.0", + "name": "since" + } + ], + "docs": "Full, platform-specific file URL that can be read later using the Filesystem API.", + "complexTypes": [], + "type": "string | undefined" + }, + { + "name": "webPath", + "tags": [ + { + "text": "1.2.0", + "name": "since" + } + ], + "docs": "webPath returns a path that can be used to set the src attribute of an image for efficient\nloading and rendering.", + "complexTypes": [], + "type": "string" + }, + { + "name": "exif", + "tags": [ + { + "text": "1.2.0", + "name": "since" + } + ], + "docs": "Exif data, if any, retrieved from the image", + "complexTypes": [], + "type": "any" + }, + { + "name": "format", + "tags": [ + { + "text": "1.2.0", + "name": "since" + } + ], + "docs": "The format of the image, ex: jpeg, png, gif.\n\niOS and Android only support jpeg.\nWeb supports jpeg, png and gif.", + "complexTypes": [], + "type": "string" + } + ] + }, + { + "name": "GalleryImageOptions", + "slug": "galleryimageoptions", + "docs": "", + "tags": [], + "methods": [], + "properties": [ + { + "name": "quality", + "tags": [ + { + "text": "1.2.0", + "name": "since" + } + ], + "docs": "The quality of image to return as JPEG, from 0-100\nNote: This option is only supported on Android and iOS.", + "complexTypes": [], + "type": "number | undefined" + }, + { + "name": "width", + "tags": [ + { + "text": "1.2.0", + "name": "since" + } + ], + "docs": "The desired maximum width of the saved image. The aspect ratio is respected.", + "complexTypes": [], + "type": "number | undefined" + }, + { + "name": "height", + "tags": [ + { + "text": "1.2.0", + "name": "since" + } + ], + "docs": "The desired maximum height of the saved image. The aspect ratio is respected.", + "complexTypes": [], + "type": "number | undefined" + }, + { + "name": "correctOrientation", + "tags": [ + { + "text": ": true", + "name": "default" + }, + { + "text": "1.2.0", + "name": "since" + } + ], + "docs": "Whether to automatically rotate the image \"up\" to correct for orientation\nin portrait mode", + "complexTypes": [], + "type": "boolean | undefined" + }, + { + "name": "presentationStyle", + "tags": [ + { + "text": ": 'fullscreen'", + "name": "default" + }, + { + "text": "1.2.0", + "name": "since" + } + ], + "docs": "iOS only: The presentation style of the Camera.", + "complexTypes": [], + "type": "'fullscreen' | 'popover' | undefined" + }, + { + "name": "limit", + "tags": [ + { + "text": "0 (unlimited)", + "name": "default" + }, + { + "text": "1.2.0", + "name": "since" + } + ], + "docs": "Maximum number of pictures the user will be able to choose.\nNote: This option is only supported on Android 13+ and iOS.", + "complexTypes": [], + "type": "number | undefined" + } + ] + }, + { + "name": "PermissionStatus", + "slug": "permissionstatus", + "docs": "", + "tags": [], + "methods": [], + "properties": [ + { + "name": "camera", + "tags": [], + "docs": "", + "complexTypes": [ + "CameraPermissionState" + ], + "type": "CameraPermissionState" + }, + { + "name": "photos", + "tags": [], + "docs": "", + "complexTypes": [ + "CameraPermissionState" + ], + "type": "CameraPermissionState" + } + ] + }, + { + "name": "CameraPluginPermissions", + "slug": "camerapluginpermissions", + "docs": "", + "tags": [], + "methods": [], + "properties": [ + { + "name": "permissions", + "tags": [], + "docs": "", + "complexTypes": [ + "CameraPermissionType" + ], + "type": "CameraPermissionType[]" + } + ] + } + ], + "enums": [ + { + "name": "CameraResultType", + "slug": "cameraresulttype", + "members": [ + { + "name": "Uri", + "value": "'uri'", + "tags": [], + "docs": "" + }, + { + "name": "Base64", + "value": "'base64'", + "tags": [], + "docs": "" + }, + { + "name": "DataUrl", + "value": "'dataUrl'", + "tags": [], + "docs": "" + } + ] + }, + { + "name": "CameraSource", + "slug": "camerasource", + "members": [ + { + "name": "Prompt", + "value": "'PROMPT'", + "tags": [], + "docs": "Prompts the user to select either the photo album or take a photo." + }, + { + "name": "Camera", + "value": "'CAMERA'", + "tags": [], + "docs": "Take a new photo using the camera." + }, + { + "name": "CameraMulti", + "value": "'CAMERA_MULTI'", + "tags": [], + "docs": "Take multiple photos in a row using the camera.\nAvailable on Android and iOS." + }, + { + "name": "Photos", + "value": "'PHOTOS'", + "tags": [], + "docs": "Pick an existing photo from the gallery or photo album." + } + ] + }, + { + "name": "CameraDirection", + "slug": "cameradirection", + "members": [ + { + "name": "Rear", + "value": "'REAR'", + "tags": [], + "docs": "" + }, + { + "name": "Front", + "value": "'FRONT'", + "tags": [], + "docs": "" + } + ] + } + ], + "typeAliases": [ + { + "name": "CameraPermissionState", + "slug": "camerapermissionstate", + "docs": "", + "types": [ + { + "text": "PermissionState", + "complexTypes": [ + "PermissionState" + ] + }, + { + "text": "'limited'", + "complexTypes": [] + } + ] + }, + { + "name": "PermissionState", + "slug": "permissionstate", + "docs": "", + "types": [ + { + "text": "'prompt'", + "complexTypes": [] + }, + { + "text": "'prompt-with-rationale'", + "complexTypes": [] + }, + { + "text": "'granted'", + "complexTypes": [] + }, + { + "text": "'denied'", + "complexTypes": [] + } + ] + }, + { + "name": "CameraPermissionType", + "slug": "camerapermissiontype", + "docs": "", + "types": [ + { + "text": "'camera'", + "complexTypes": [] + }, + { + "text": "'photos'", + "complexTypes": [] + } + ] + } + ], + "pluginConfigs": [] +} \ No newline at end of file diff --git a/dist/esm/definitions.d.ts b/dist/esm/definitions.d.ts new file mode 100644 index 0000000000..b14a6e97e8 --- /dev/null +++ b/dist/esm/definitions.d.ts @@ -0,0 +1,341 @@ +import type { PermissionState } from '@capacitor/core'; +export declare type CameraPermissionState = PermissionState | 'limited'; +export declare 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 multiplef 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 declare 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", + /** + * Take multiple photos in a row using the camera. + * Available on Android and iOS. + */ + CameraMulti = "CAMERA_MULTI", + /** + * Pick an existing photo from the gallery or photo album. + */ + Photos = "PHOTOS" +} +export declare enum CameraDirection { + Rear = "REAR", + Front = "FRONT" +} +export declare enum CameraResultType { + Uri = "uri", + Base64 = "base64", + DataUrl = "dataUrl" +} +/** + * @deprecated Use `Photo`. + * @since 1.0.0 + */ +export declare type CameraPhoto = Photo; +/** + * @deprecated Use `ImageOptions`. + * @since 1.0.0 + */ +export declare type CameraOptions = ImageOptions; diff --git a/dist/esm/definitions.js b/dist/esm/definitions.js new file mode 100644 index 0000000000..a58e04fe09 --- /dev/null +++ b/dist/esm/definitions.js @@ -0,0 +1,32 @@ +export var CameraSource; +(function (CameraSource) { + /** + * Prompts the user to select either the photo album or take a photo. + */ + CameraSource["Prompt"] = "PROMPT"; + /** + * Take a new photo using the camera. + */ + CameraSource["Camera"] = "CAMERA"; + /** + * Take multiple photos in a row using the camera. + * Available on Android and iOS. + */ + CameraSource["CameraMulti"] = "CAMERA_MULTI"; + /** + * Pick an existing photo from the gallery or photo album. + */ + CameraSource["Photos"] = "PHOTOS"; +})(CameraSource || (CameraSource = {})); +export var CameraDirection; +(function (CameraDirection) { + CameraDirection["Rear"] = "REAR"; + CameraDirection["Front"] = "FRONT"; +})(CameraDirection || (CameraDirection = {})); +export var CameraResultType; +(function (CameraResultType) { + CameraResultType["Uri"] = "uri"; + CameraResultType["Base64"] = "base64"; + CameraResultType["DataUrl"] = "dataUrl"; +})(CameraResultType || (CameraResultType = {})); +//# sourceMappingURL=definitions.js.map \ No newline at end of file diff --git a/dist/esm/definitions.js.map b/dist/esm/definitions.js.map new file mode 100644 index 0000000000..eeadba5712 --- /dev/null +++ b/dist/esm/definitions.js.map @@ -0,0 +1 @@ +{"version":3,"file":"definitions.js","sourceRoot":"","sources":["../../src/definitions.ts"],"names":[],"mappings":"AAuUA,MAAM,CAAN,IAAY,YAkBX;AAlBD,WAAY,YAAY;IACtB;;OAEG;IACH,iCAAiB,CAAA;IACjB;;OAEG;IACH,iCAAiB,CAAA;IACjB;;;OAGG;IACH,4CAA4B,CAAA;IAC5B;;OAEG;IACH,iCAAiB,CAAA;AACnB,CAAC,EAlBW,YAAY,KAAZ,YAAY,QAkBvB;AAED,MAAM,CAAN,IAAY,eAGX;AAHD,WAAY,eAAe;IACzB,gCAAa,CAAA;IACb,kCAAe,CAAA;AACjB,CAAC,EAHW,eAAe,KAAf,eAAe,QAG1B;AAED,MAAM,CAAN,IAAY,gBAIX;AAJD,WAAY,gBAAgB;IAC1B,+BAAW,CAAA;IACX,qCAAiB,CAAA;IACjB,uCAAmB,CAAA;AACrB,CAAC,EAJW,gBAAgB,KAAhB,gBAAgB,QAI3B","sourcesContent":["import type { PermissionState } from '@capacitor/core';\n\nexport type CameraPermissionState = PermissionState | 'limited';\n\nexport type CameraPermissionType = 'camera' | 'photos';\n\nexport interface PermissionStatus {\n camera: CameraPermissionState;\n photos: CameraPermissionState;\n}\n\nexport interface CameraPluginPermissions {\n permissions: CameraPermissionType[];\n}\n\nexport interface CameraPlugin {\n /**\n * Prompt the user to pick a photo from an album, or take a new photo\n * with the camera.\n *\n * @since 1.0.0\n */\n getPhoto(options: ImageOptions): Promise;\n\n /**\n * Allows the user to pick multiplef pictures from the photo gallery.\n *\n * @since 1.2.0\n */\n pickImages(options: GalleryImageOptions): Promise;\n\n /**\n * Allows the user to update their limited photo library selection.\n * Returns all the limited photos after the picker dismissal.\n * If instead the user gave full access to the photos it returns an empty array.\n *\n * @since 4.1.0\n */\n pickLimitedLibraryPhotos(): Promise;\n /**\n * Return an array of photos selected from the limited photo library.\n *\n * @since 4.1.0\n */\n getLimitedLibraryPhotos(): Promise;\n\n /**\n * Check camera and photo album permissions\n *\n * @since 1.0.0\n */\n checkPermissions(): Promise;\n\n /**\n * Request camera and photo album permissions\n *\n * @since 1.0.0\n */\n requestPermissions(\n permissions?: CameraPluginPermissions,\n ): Promise;\n}\n\nexport interface ImageOptions {\n /**\n * The quality of image to return as JPEG, from 0-100\n * Note: This option is only supported on Android and iOS\n *\n * @since 1.0.0\n */\n quality?: number;\n /**\n * Whether to allow the user to crop or make small edits (platform specific).\n * On iOS it's only supported for CameraSource.Camera, but not for CameraSource.Photos.\n *\n * @since 1.0.0\n */\n allowEditing?: boolean;\n /**\n * How the data should be returned. Currently, only 'Base64', 'DataUrl' or 'Uri' is supported\n *\n * @since 1.0.0\n */\n resultType: CameraResultType;\n /**\n * Whether to save the photo to the gallery.\n * If the photo was picked from the gallery, it will only be saved if edited.\n * @default: false\n *\n * @since 1.0.0\n */\n saveToGallery?: boolean;\n /**\n * The desired maximum width of the saved image. The aspect ratio is respected.\n *\n * @since 1.0.0\n */\n width?: number;\n /**\n * The desired maximum height of the saved image. The aspect ratio is respected.\n *\n * @since 1.0.0\n */\n height?: number;\n /**\n * Whether to automatically rotate the image \"up\" to correct for orientation\n * in portrait mode\n * @default: true\n *\n * @since 1.0.0\n */\n correctOrientation?: boolean;\n /**\n * The source to get the photo from. By default this prompts the user to select\n * either the photo album or take a photo.\n * @default: CameraSource.Prompt\n *\n * @since 1.0.0\n */\n source?: CameraSource;\n /**\n * iOS and Web only: The camera direction.\n * @default: CameraDirection.Rear\n *\n * @since 1.0.0\n */\n direction?: CameraDirection;\n\n /**\n * iOS only: The presentation style of the Camera.\n * @default: 'fullscreen'\n *\n * @since 1.0.0\n */\n presentationStyle?: 'fullscreen' | 'popover';\n\n /**\n * Web only: Whether to use the PWA Element experience or file input. The\n * default is to use PWA Elements if installed and fall back to file input.\n * To always use file input, set this to `true`.\n *\n * Learn more about PWA Elements: https://capacitorjs.com/docs/web/pwa-elements\n *\n * @since 1.0.0\n */\n webUseInput?: boolean;\n\n /**\n * Text value to use when displaying the prompt.\n * @default: 'Photo'\n *\n * @since 1.0.0\n *\n */\n promptLabelHeader?: string;\n\n /**\n * Text value to use when displaying the prompt.\n * iOS only: The label of the 'cancel' button.\n * @default: 'Cancel'\n *\n * @since 1.0.0\n */\n promptLabelCancel?: string;\n\n /**\n * Text value to use when displaying the prompt.\n * The label of the button to select a saved image.\n * @default: 'From Photos'\n *\n * @since 1.0.0\n */\n promptLabelPhoto?: string;\n\n /**\n * Text value to use when displaying the prompt.\n * The label of the button to open the camera.\n * @default: 'Take Picture'\n *\n * @since 1.0.0\n */\n promptLabelPicture?: string;\n}\n\nexport interface Photo {\n /**\n * The base64 encoded string representation of the image, if using CameraResultType.Base64.\n *\n * @since 1.0.0\n */\n base64String?: string;\n /**\n * The url starting with 'data:image/jpeg;base64,' and the base64 encoded string representation of the image, if using CameraResultType.DataUrl.\n *\n * Note: On web, the file format could change depending on the browser.\n * @since 1.0.0\n */\n dataUrl?: string;\n /**\n * If using CameraResultType.Uri, the path will contain a full,\n * platform-specific file URL that can be read later using the Filesystem API.\n *\n * @since 1.0.0\n */\n path?: string;\n /**\n * webPath returns a path that can be used to set the src attribute of an image for efficient\n * loading and rendering.\n *\n * @since 1.0.0\n */\n webPath?: string;\n /**\n * Exif data, if any, retrieved from the image\n *\n * @since 1.0.0\n */\n exif?: any;\n /**\n * The format of the image, ex: jpeg, png, gif.\n *\n * iOS and Android only support jpeg.\n * Web supports jpeg, png and gif, but the exact availability may vary depending on the browser.\n * gif is only supported if `webUseInput` is set to `true` or if `source` is set to `Photos`.\n *\n * @since 1.0.0\n */\n format: string;\n /**\n * Whether if the image was saved to the gallery or not.\n *\n * On Android and iOS, saving to the gallery can fail if the user didn't\n * grant the required permissions.\n * On Web there is no gallery, so always returns false.\n *\n * @since 1.1.0\n */\n saved: boolean;\n}\n\nexport interface GalleryPhotos {\n /**\n * Array of all the picked photos.\n *\n * @since 1.2.0\n */\n photos: GalleryPhoto[];\n}\n\nexport interface GalleryPhoto {\n /**\n * Full, platform-specific file URL that can be read later using the Filesystem API.\n *\n * @since 1.2.0\n */\n path?: string;\n /**\n * webPath returns a path that can be used to set the src attribute of an image for efficient\n * loading and rendering.\n *\n * @since 1.2.0\n */\n webPath: string;\n /**\n * Exif data, if any, retrieved from the image\n *\n * @since 1.2.0\n */\n exif?: any;\n /**\n * The format of the image, ex: jpeg, png, gif.\n *\n * iOS and Android only support jpeg.\n * Web supports jpeg, png and gif.\n *\n * @since 1.2.0\n */\n format: string;\n}\nexport interface GalleryImageOptions {\n /**\n * The quality of image to return as JPEG, from 0-100\n * Note: This option is only supported on Android and iOS.\n *\n * @since 1.2.0\n */\n quality?: number;\n /**\n * The desired maximum width of the saved image. The aspect ratio is respected.\n *\n * @since 1.2.0\n */\n width?: number;\n /**\n * The desired maximum height of the saved image. The aspect ratio is respected.\n *\n * @since 1.2.0\n */\n height?: number;\n /**\n * Whether to automatically rotate the image \"up\" to correct for orientation\n * in portrait mode\n * @default: true\n *\n * @since 1.2.0\n */\n correctOrientation?: boolean;\n\n /**\n * iOS only: The presentation style of the Camera.\n * @default: 'fullscreen'\n *\n * @since 1.2.0\n */\n presentationStyle?: 'fullscreen' | 'popover';\n\n /**\n * Maximum number of pictures the user will be able to choose.\n * Note: This option is only supported on Android 13+ and iOS.\n *\n * @default 0 (unlimited)\n *\n * @since 1.2.0\n */\n limit?: number;\n}\n\nexport enum CameraSource {\n /**\n * Prompts the user to select either the photo album or take a photo.\n */\n Prompt = 'PROMPT',\n /**\n * Take a new photo using the camera.\n */\n Camera = 'CAMERA',\n /**\n * Take multiple photos in a row using the camera.\n * Available on Android and iOS.\n */\n CameraMulti = 'CAMERA_MULTI',\n /**\n * Pick an existing photo from the gallery or photo album.\n */\n Photos = 'PHOTOS',\n}\n\nexport enum CameraDirection {\n Rear = 'REAR',\n Front = 'FRONT',\n}\n\nexport enum CameraResultType {\n Uri = 'uri',\n Base64 = 'base64',\n DataUrl = 'dataUrl',\n}\n\n/**\n * @deprecated Use `Photo`.\n * @since 1.0.0\n */\nexport type CameraPhoto = Photo;\n\n/**\n * @deprecated Use `ImageOptions`.\n * @since 1.0.0\n */\nexport type CameraOptions = ImageOptions;\n"]} \ No newline at end of file diff --git a/dist/esm/index.d.ts b/dist/esm/index.d.ts new file mode 100644 index 0000000000..89d586e2ae --- /dev/null +++ b/dist/esm/index.d.ts @@ -0,0 +1,4 @@ +import type { CameraPlugin } from './definitions'; +declare const Camera: CameraPlugin; +export * from './definitions'; +export { Camera }; diff --git a/dist/esm/index.js b/dist/esm/index.js new file mode 100644 index 0000000000..96fec990a7 --- /dev/null +++ b/dist/esm/index.js @@ -0,0 +1,8 @@ +import { registerPlugin } from '@capacitor/core'; +import { CameraWeb } from './web'; +const Camera = registerPlugin('Camera', { + web: () => new CameraWeb(), +}); +export * from './definitions'; +export { Camera }; +//# sourceMappingURL=index.js.map \ No newline at end of file diff --git a/dist/esm/index.js.map b/dist/esm/index.js.map new file mode 100644 index 0000000000..2e62e1607e --- /dev/null +++ b/dist/esm/index.js.map @@ -0,0 +1 @@ +{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,MAAM,iBAAiB,CAAC;AAGjD,OAAO,EAAE,SAAS,EAAE,MAAM,OAAO,CAAC;AAElC,MAAM,MAAM,GAAG,cAAc,CAAe,QAAQ,EAAE;IACpD,GAAG,EAAE,GAAG,EAAE,CAAC,IAAI,SAAS,EAAE;CAC3B,CAAC,CAAC;AAEH,cAAc,eAAe,CAAC;AAC9B,OAAO,EAAE,MAAM,EAAE,CAAC","sourcesContent":["import { registerPlugin } from '@capacitor/core';\n\nimport type { CameraPlugin } from './definitions';\nimport { CameraWeb } from './web';\n\nconst Camera = registerPlugin('Camera', {\n web: () => new CameraWeb(),\n});\n\nexport * from './definitions';\nexport { Camera };\n"]} \ No newline at end of file diff --git a/dist/esm/web.d.ts b/dist/esm/web.d.ts new file mode 100644 index 0000000000..6fbcc40586 --- /dev/null +++ b/dist/esm/web.d.ts @@ -0,0 +1,16 @@ +import { WebPlugin } from '@capacitor/core'; +import type { CameraPlugin, GalleryImageOptions, GalleryPhotos, ImageOptions, PermissionStatus, Photo } from './definitions'; +export declare class CameraWeb extends WebPlugin implements CameraPlugin { + getPhoto(options: ImageOptions): Promise; + pickImages(_options: GalleryImageOptions): Promise; + private cameraExperience; + private fileInputExperience; + private multipleFileInputExperience; + private _getCameraPhoto; + checkPermissions(): Promise; + requestPermissions(): Promise; + pickLimitedLibraryPhotos(): Promise; + getLimitedLibraryPhotos(): Promise; +} +declare const Camera: CameraWeb; +export { Camera }; diff --git a/dist/esm/web.js b/dist/esm/web.js new file mode 100644 index 0000000000..2df609be8e --- /dev/null +++ b/dist/esm/web.js @@ -0,0 +1,254 @@ +import { WebPlugin, CapacitorException } from '@capacitor/core'; +import { CameraSource, CameraDirection } from './definitions'; +export class CameraWeb extends WebPlugin { + async getPhoto(options) { + // 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 = document.querySelector('pwa-action-sheet'); + if (!actionSheet) { + actionSheet = document.createElement('pwa-action-sheet'); + document.body.appendChild(actionSheet); + } + actionSheet.header = options.promptLabelHeader || 'Photo'; + actionSheet.cancelable = false; + actionSheet.options = [ + { title: options.promptLabelPhoto || 'From Photos' }, + { title: options.promptLabelPicture || 'Take Picture' }, + ]; + actionSheet.addEventListener('onSelection', async (e) => { + const selection = e.detail; + if (selection === 0) { + this.fileInputExperience(options, resolve, reject); + } + else { + this.cameraExperience(options, resolve, reject); + } + }); + } + else { + this.cameraExperience(options, resolve, reject); + } + }); + } + async pickImages(_options) { + // eslint-disable-next-line no-async-promise-executor + return new Promise(async (resolve, reject) => { + this.multipleFileInputExperience(resolve, reject); + }); + } + async cameraExperience(options, resolve, reject) { + if (customElements.get('pwa-camera-modal')) { + const cameraModal = 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) => { + 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); + } + } + fileInputExperience(options, resolve, reject) { + let input = document.querySelector('#_capacitor-camera-input'); + const cleanup = () => { + var _a; + (_a = input.parentNode) === null || _a === void 0 ? void 0 : _a.removeChild(input); + }; + if (!input) { + input = document.createElement('input'); + input.id = '_capacitor-camera-input'; + input.type = 'file'; + input.hidden = true; + document.body.appendChild(input); + input.addEventListener('change', (_e) => { + 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, + }); + } + else if (options.resultType === 'base64') { + const b64 = reader.result.split(',')[1]; + resolve({ + base64String: b64, + format, + }); + } + cleanup(); + }); + reader.readAsDataURL(file); + } + else { + resolve({ + webPath: URL.createObjectURL(file), + format: format, + }); + cleanup(); + } + }); + input.addEventListener('cancel', (_e) => { + reject(new CapacitorException('User cancelled photos app')); + cleanup(); + }); + } + input.accept = 'image/*'; + input.capture = true; + if (options.source === CameraSource.Photos || + options.source === CameraSource.Prompt) { + input.removeAttribute('capture'); + } + else if (options.direction === CameraDirection.Front) { + input.capture = 'user'; + } + else if (options.direction === CameraDirection.Rear) { + input.capture = 'environment'; + } + input.click(); + } + multipleFileInputExperience(resolve, reject) { + let input = document.querySelector('#_capacitor-camera-input-multiple'); + const cleanup = () => { + var _a; + (_a = input.parentNode) === null || _a === void 0 ? void 0 : _a.removeChild(input); + }; + if (!input) { + input = document.createElement('input'); + input.id = '_capacitor-camera-input-multiple'; + input.type = 'file'; + input.hidden = true; + input.multiple = true; + document.body.appendChild(input); + input.addEventListener('change', (_e) => { + 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) => { + reject(new CapacitorException('User cancelled photos app')); + cleanup(); + }); + } + input.accept = 'image/*'; + input.click(); + } + _getCameraPhoto(photo, options) { + 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; + 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() { + 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 (_a) { + throw this.unavailable('Camera permissions are not available in this browser'); + } + } + async requestPermissions() { + throw this.unimplemented('Not implemented on web.'); + } + async pickLimitedLibraryPhotos() { + throw this.unavailable('Not implemented on web.'); + } + async getLimitedLibraryPhotos() { + throw this.unavailable('Not implemented on web.'); + } +} +const Camera = new CameraWeb(); +export { Camera }; +//# sourceMappingURL=web.js.map \ No newline at end of file diff --git a/dist/esm/web.js.map b/dist/esm/web.js.map new file mode 100644 index 0000000000..1fb22cc54d --- /dev/null +++ b/dist/esm/web.js.map @@ -0,0 +1 @@ +{"version":3,"file":"web.js","sourceRoot":"","sources":["../../src/web.ts"],"names":[],"mappings":"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","sourcesContent":["import { WebPlugin, CapacitorException } from '@capacitor/core';\n\nimport { CameraSource, CameraDirection } from './definitions';\nimport type {\n CameraPlugin,\n GalleryImageOptions,\n GalleryPhotos,\n ImageOptions,\n PermissionStatus,\n Photo,\n} from './definitions';\n\nexport class CameraWeb extends WebPlugin implements CameraPlugin {\n async getPhoto(options: ImageOptions): Promise {\n // eslint-disable-next-line no-async-promise-executor\n return new Promise(async (resolve, reject) => {\n if (options.webUseInput || options.source === CameraSource.Photos) {\n this.fileInputExperience(options, resolve, reject);\n } else if (options.source === CameraSource.Prompt) {\n let actionSheet: any = document.querySelector('pwa-action-sheet');\n if (!actionSheet) {\n actionSheet = document.createElement('pwa-action-sheet');\n document.body.appendChild(actionSheet);\n }\n actionSheet.header = options.promptLabelHeader || 'Photo';\n actionSheet.cancelable = false;\n actionSheet.options = [\n { title: options.promptLabelPhoto || 'From Photos' },\n { title: options.promptLabelPicture || 'Take Picture' },\n ];\n actionSheet.addEventListener('onSelection', async (e: any) => {\n const selection = e.detail;\n if (selection === 0) {\n this.fileInputExperience(options, resolve, reject);\n } else {\n this.cameraExperience(options, resolve, reject);\n }\n });\n } else {\n this.cameraExperience(options, resolve, reject);\n }\n });\n }\n\n async pickImages(_options: GalleryImageOptions): Promise {\n // eslint-disable-next-line no-async-promise-executor\n return new Promise(async (resolve, reject) => {\n this.multipleFileInputExperience(resolve, reject);\n });\n }\n\n private async cameraExperience(\n options: ImageOptions,\n resolve: any,\n reject: any,\n ) {\n if (customElements.get('pwa-camera-modal')) {\n const cameraModal: any = document.createElement('pwa-camera-modal');\n cameraModal.facingMode =\n options.direction === CameraDirection.Front ? 'user' : 'environment';\n document.body.appendChild(cameraModal);\n try {\n await cameraModal.componentOnReady();\n cameraModal.addEventListener('onPhoto', async (e: any) => {\n const photo = e.detail;\n\n if (photo === null) {\n reject(new CapacitorException('User cancelled photos app'));\n } else if (photo instanceof Error) {\n reject(photo);\n } else {\n resolve(await this._getCameraPhoto(photo, options));\n }\n\n cameraModal.dismiss();\n document.body.removeChild(cameraModal);\n });\n\n cameraModal.present();\n } catch (e) {\n this.fileInputExperience(options, resolve, reject);\n }\n } else {\n console.error(\n `Unable to load PWA Element 'pwa-camera-modal'. See the docs: https://capacitorjs.com/docs/web/pwa-elements.`,\n );\n this.fileInputExperience(options, resolve, reject);\n }\n }\n\n private fileInputExperience(\n options: ImageOptions,\n resolve: any,\n reject: any,\n ) {\n let input = document.querySelector(\n '#_capacitor-camera-input',\n ) as HTMLInputElement;\n\n const cleanup = () => {\n input.parentNode?.removeChild(input);\n };\n\n if (!input) {\n input = document.createElement('input') as HTMLInputElement;\n input.id = '_capacitor-camera-input';\n input.type = 'file';\n input.hidden = true;\n document.body.appendChild(input);\n input.addEventListener('change', (_e: any) => {\n const file = input.files![0];\n let format = 'jpeg';\n\n if (file.type === 'image/png') {\n format = 'png';\n } else if (file.type === 'image/gif') {\n format = 'gif';\n }\n\n if (\n options.resultType === 'dataUrl' ||\n options.resultType === 'base64'\n ) {\n const reader = new FileReader();\n\n reader.addEventListener('load', () => {\n if (options.resultType === 'dataUrl') {\n resolve({\n dataUrl: reader.result,\n format,\n } as Photo);\n } else if (options.resultType === 'base64') {\n const b64 = (reader.result as string).split(',')[1];\n resolve({\n base64String: b64,\n format,\n } as Photo);\n }\n\n cleanup();\n });\n\n reader.readAsDataURL(file);\n } else {\n resolve({\n webPath: URL.createObjectURL(file),\n format: format,\n });\n cleanup();\n }\n });\n input.addEventListener('cancel', (_e: any) => {\n reject(new CapacitorException('User cancelled photos app'));\n cleanup();\n });\n }\n\n input.accept = 'image/*';\n (input as any).capture = true;\n\n if (\n options.source === CameraSource.Photos ||\n options.source === CameraSource.Prompt\n ) {\n input.removeAttribute('capture');\n } else if (options.direction === CameraDirection.Front) {\n (input as any).capture = 'user';\n } else if (options.direction === CameraDirection.Rear) {\n (input as any).capture = 'environment';\n }\n\n input.click();\n }\n\n private multipleFileInputExperience(resolve: any, reject: any) {\n let input = document.querySelector(\n '#_capacitor-camera-input-multiple',\n ) as HTMLInputElement;\n\n const cleanup = () => {\n input.parentNode?.removeChild(input);\n };\n\n if (!input) {\n input = document.createElement('input') as HTMLInputElement;\n input.id = '_capacitor-camera-input-multiple';\n input.type = 'file';\n input.hidden = true;\n input.multiple = true;\n document.body.appendChild(input);\n input.addEventListener('change', (_e: any) => {\n const photos = [];\n // eslint-disable-next-line @typescript-eslint/prefer-for-of\n for (let i = 0; i < input.files!.length; i++) {\n const file = input.files![i];\n let format = 'jpeg';\n\n if (file.type === 'image/png') {\n format = 'png';\n } else if (file.type === 'image/gif') {\n format = 'gif';\n }\n photos.push({\n webPath: URL.createObjectURL(file),\n format: format,\n });\n }\n resolve({ photos });\n cleanup();\n });\n input.addEventListener('cancel', (_e: any) => {\n reject(new CapacitorException('User cancelled photos app'));\n cleanup();\n });\n }\n\n input.accept = 'image/*';\n\n input.click();\n }\n\n private _getCameraPhoto(photo: Blob, options: ImageOptions) {\n return new Promise((resolve, reject) => {\n const reader = new FileReader();\n const format = photo.type.split('/')[1];\n if (options.resultType === 'uri') {\n resolve({\n webPath: URL.createObjectURL(photo),\n format: format,\n saved: false,\n });\n } else {\n reader.readAsDataURL(photo);\n reader.onloadend = () => {\n const r = reader.result as string;\n if (options.resultType === 'dataUrl') {\n resolve({\n dataUrl: r,\n format: format,\n saved: false,\n });\n } else {\n resolve({\n base64String: r.split(',')[1],\n format: format,\n saved: false,\n });\n }\n };\n reader.onerror = e => {\n reject(e);\n };\n }\n });\n }\n\n async checkPermissions(): Promise {\n if (typeof navigator === 'undefined' || !navigator.permissions) {\n throw this.unavailable('Permissions API not available in this browser');\n }\n\n try {\n // https://developer.mozilla.org/en-US/docs/Web/API/Permissions/query\n // the specific permissions that are supported varies among browsers that implement the\n // permissions API, so we need a try/catch in case 'camera' is invalid\n const permission = await window.navigator.permissions.query({\n name: 'camera',\n });\n return {\n camera: permission.state,\n photos: 'granted',\n };\n } catch {\n throw this.unavailable(\n 'Camera permissions are not available in this browser',\n );\n }\n }\n\n async requestPermissions(): Promise {\n throw this.unimplemented('Not implemented on web.');\n }\n\n async pickLimitedLibraryPhotos(): Promise {\n throw this.unavailable('Not implemented on web.');\n }\n\n async getLimitedLibraryPhotos(): Promise {\n throw this.unavailable('Not implemented on web.');\n }\n}\n\nconst Camera = new CameraWeb();\n\nexport { Camera };\n"]} \ No newline at end of file diff --git a/dist/plugin.cjs.js b/dist/plugin.cjs.js new file mode 100644 index 0000000000..3101a95322 --- /dev/null +++ b/dist/plugin.cjs.js @@ -0,0 +1,293 @@ +'use strict'; + +var core = require('@capacitor/core'); + +exports.CameraSource = void 0; +(function (CameraSource) { + /** + * Prompts the user to select either the photo album or take a photo. + */ + CameraSource["Prompt"] = "PROMPT"; + /** + * Take a new photo using the camera. + */ + CameraSource["Camera"] = "CAMERA"; + /** + * Take multiple photos in a row using the camera. + * Available on Android and iOS. + */ + CameraSource["CameraMulti"] = "CAMERA_MULTI"; + /** + * Pick an existing photo from the gallery or photo album. + */ + CameraSource["Photos"] = "PHOTOS"; +})(exports.CameraSource || (exports.CameraSource = {})); +exports.CameraDirection = void 0; +(function (CameraDirection) { + CameraDirection["Rear"] = "REAR"; + CameraDirection["Front"] = "FRONT"; +})(exports.CameraDirection || (exports.CameraDirection = {})); +exports.CameraResultType = void 0; +(function (CameraResultType) { + CameraResultType["Uri"] = "uri"; + CameraResultType["Base64"] = "base64"; + CameraResultType["DataUrl"] = "dataUrl"; +})(exports.CameraResultType || (exports.CameraResultType = {})); + +class CameraWeb extends core.WebPlugin { + async getPhoto(options) { + // eslint-disable-next-line no-async-promise-executor + return new Promise(async (resolve, reject) => { + if (options.webUseInput || options.source === exports.CameraSource.Photos) { + this.fileInputExperience(options, resolve, reject); + } + else if (options.source === exports.CameraSource.Prompt) { + let actionSheet = document.querySelector('pwa-action-sheet'); + if (!actionSheet) { + actionSheet = document.createElement('pwa-action-sheet'); + document.body.appendChild(actionSheet); + } + actionSheet.header = options.promptLabelHeader || 'Photo'; + actionSheet.cancelable = false; + actionSheet.options = [ + { title: options.promptLabelPhoto || 'From Photos' }, + { title: options.promptLabelPicture || 'Take Picture' }, + ]; + actionSheet.addEventListener('onSelection', async (e) => { + const selection = e.detail; + if (selection === 0) { + this.fileInputExperience(options, resolve, reject); + } + else { + this.cameraExperience(options, resolve, reject); + } + }); + } + else { + this.cameraExperience(options, resolve, reject); + } + }); + } + async pickImages(_options) { + // eslint-disable-next-line no-async-promise-executor + return new Promise(async (resolve, reject) => { + this.multipleFileInputExperience(resolve, reject); + }); + } + async cameraExperience(options, resolve, reject) { + if (customElements.get('pwa-camera-modal')) { + const cameraModal = document.createElement('pwa-camera-modal'); + cameraModal.facingMode = + options.direction === exports.CameraDirection.Front ? 'user' : 'environment'; + document.body.appendChild(cameraModal); + try { + await cameraModal.componentOnReady(); + cameraModal.addEventListener('onPhoto', async (e) => { + const photo = e.detail; + if (photo === null) { + reject(new core.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); + } + } + fileInputExperience(options, resolve, reject) { + let input = document.querySelector('#_capacitor-camera-input'); + const cleanup = () => { + var _a; + (_a = input.parentNode) === null || _a === void 0 ? void 0 : _a.removeChild(input); + }; + if (!input) { + input = document.createElement('input'); + input.id = '_capacitor-camera-input'; + input.type = 'file'; + input.hidden = true; + document.body.appendChild(input); + input.addEventListener('change', (_e) => { + 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, + }); + } + else if (options.resultType === 'base64') { + const b64 = reader.result.split(',')[1]; + resolve({ + base64String: b64, + format, + }); + } + cleanup(); + }); + reader.readAsDataURL(file); + } + else { + resolve({ + webPath: URL.createObjectURL(file), + format: format, + }); + cleanup(); + } + }); + input.addEventListener('cancel', (_e) => { + reject(new core.CapacitorException('User cancelled photos app')); + cleanup(); + }); + } + input.accept = 'image/*'; + input.capture = true; + if (options.source === exports.CameraSource.Photos || + options.source === exports.CameraSource.Prompt) { + input.removeAttribute('capture'); + } + else if (options.direction === exports.CameraDirection.Front) { + input.capture = 'user'; + } + else if (options.direction === exports.CameraDirection.Rear) { + input.capture = 'environment'; + } + input.click(); + } + multipleFileInputExperience(resolve, reject) { + let input = document.querySelector('#_capacitor-camera-input-multiple'); + const cleanup = () => { + var _a; + (_a = input.parentNode) === null || _a === void 0 ? void 0 : _a.removeChild(input); + }; + if (!input) { + input = document.createElement('input'); + input.id = '_capacitor-camera-input-multiple'; + input.type = 'file'; + input.hidden = true; + input.multiple = true; + document.body.appendChild(input); + input.addEventListener('change', (_e) => { + 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) => { + reject(new core.CapacitorException('User cancelled photos app')); + cleanup(); + }); + } + input.accept = 'image/*'; + input.click(); + } + _getCameraPhoto(photo, options) { + 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; + 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() { + 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 (_a) { + throw this.unavailable('Camera permissions are not available in this browser'); + } + } + async requestPermissions() { + throw this.unimplemented('Not implemented on web.'); + } + async pickLimitedLibraryPhotos() { + throw this.unavailable('Not implemented on web.'); + } + async getLimitedLibraryPhotos() { + throw this.unavailable('Not implemented on web.'); + } +} +new CameraWeb(); + +const Camera = core.registerPlugin('Camera', { + web: () => new CameraWeb(), +}); + +exports.Camera = Camera; +//# sourceMappingURL=plugin.cjs.js.map diff --git a/dist/plugin.cjs.js.map b/dist/plugin.cjs.js.map new file mode 100644 index 0000000000..5f4e4b51d5 --- /dev/null +++ b/dist/plugin.cjs.js.map @@ -0,0 +1 @@ +{"version":3,"file":"plugin.cjs.js","sources":["esm/definitions.js","esm/web.js","esm/index.js"],"sourcesContent":["export var CameraSource;\n(function (CameraSource) {\n /**\n * Prompts the user to select either the photo album or take a photo.\n */\n CameraSource[\"Prompt\"] = \"PROMPT\";\n /**\n * Take a new photo using the camera.\n */\n CameraSource[\"Camera\"] = \"CAMERA\";\n /**\n * Take multiple photos in a row using the camera.\n * Available on Android and iOS.\n */\n CameraSource[\"CameraMulti\"] = \"CAMERA_MULTI\";\n /**\n * Pick an existing photo from the gallery or photo album.\n */\n CameraSource[\"Photos\"] = \"PHOTOS\";\n})(CameraSource || (CameraSource = {}));\nexport var CameraDirection;\n(function (CameraDirection) {\n CameraDirection[\"Rear\"] = \"REAR\";\n CameraDirection[\"Front\"] = \"FRONT\";\n})(CameraDirection || (CameraDirection = {}));\nexport var CameraResultType;\n(function (CameraResultType) {\n CameraResultType[\"Uri\"] = \"uri\";\n CameraResultType[\"Base64\"] = \"base64\";\n CameraResultType[\"DataUrl\"] = \"dataUrl\";\n})(CameraResultType || (CameraResultType = {}));\n//# sourceMappingURL=definitions.js.map","import { WebPlugin, CapacitorException } from '@capacitor/core';\nimport { CameraSource, CameraDirection } from './definitions';\nexport class CameraWeb extends WebPlugin {\n async getPhoto(options) {\n // eslint-disable-next-line no-async-promise-executor\n return new Promise(async (resolve, reject) => {\n if (options.webUseInput || options.source === CameraSource.Photos) {\n this.fileInputExperience(options, resolve, reject);\n }\n else if (options.source === CameraSource.Prompt) {\n let actionSheet = document.querySelector('pwa-action-sheet');\n if (!actionSheet) {\n actionSheet = document.createElement('pwa-action-sheet');\n document.body.appendChild(actionSheet);\n }\n actionSheet.header = options.promptLabelHeader || 'Photo';\n actionSheet.cancelable = false;\n actionSheet.options = [\n { title: options.promptLabelPhoto || 'From Photos' },\n { title: options.promptLabelPicture || 'Take Picture' },\n ];\n actionSheet.addEventListener('onSelection', async (e) => {\n const selection = e.detail;\n if (selection === 0) {\n this.fileInputExperience(options, resolve, reject);\n }\n else {\n this.cameraExperience(options, resolve, reject);\n }\n });\n }\n else {\n this.cameraExperience(options, resolve, reject);\n }\n });\n }\n async pickImages(_options) {\n // eslint-disable-next-line no-async-promise-executor\n return new Promise(async (resolve, reject) => {\n this.multipleFileInputExperience(resolve, reject);\n });\n }\n async cameraExperience(options, resolve, reject) {\n if (customElements.get('pwa-camera-modal')) {\n const cameraModal = document.createElement('pwa-camera-modal');\n cameraModal.facingMode =\n options.direction === CameraDirection.Front ? 'user' : 'environment';\n document.body.appendChild(cameraModal);\n try {\n await cameraModal.componentOnReady();\n cameraModal.addEventListener('onPhoto', async (e) => {\n const photo = e.detail;\n if (photo === null) {\n reject(new CapacitorException('User cancelled photos app'));\n }\n else if (photo instanceof Error) {\n reject(photo);\n }\n else {\n resolve(await this._getCameraPhoto(photo, options));\n }\n cameraModal.dismiss();\n document.body.removeChild(cameraModal);\n });\n cameraModal.present();\n }\n catch (e) {\n this.fileInputExperience(options, resolve, reject);\n }\n }\n else {\n console.error(`Unable to load PWA Element 'pwa-camera-modal'. See the docs: https://capacitorjs.com/docs/web/pwa-elements.`);\n this.fileInputExperience(options, resolve, reject);\n }\n }\n fileInputExperience(options, resolve, reject) {\n let input = document.querySelector('#_capacitor-camera-input');\n const cleanup = () => {\n var _a;\n (_a = input.parentNode) === null || _a === void 0 ? void 0 : _a.removeChild(input);\n };\n if (!input) {\n input = document.createElement('input');\n input.id = '_capacitor-camera-input';\n input.type = 'file';\n input.hidden = true;\n document.body.appendChild(input);\n input.addEventListener('change', (_e) => {\n const file = input.files[0];\n let format = 'jpeg';\n if (file.type === 'image/png') {\n format = 'png';\n }\n else if (file.type === 'image/gif') {\n format = 'gif';\n }\n if (options.resultType === 'dataUrl' ||\n options.resultType === 'base64') {\n const reader = new FileReader();\n reader.addEventListener('load', () => {\n if (options.resultType === 'dataUrl') {\n resolve({\n dataUrl: reader.result,\n format,\n });\n }\n else if (options.resultType === 'base64') {\n const b64 = reader.result.split(',')[1];\n resolve({\n base64String: b64,\n format,\n });\n }\n cleanup();\n });\n reader.readAsDataURL(file);\n }\n else {\n resolve({\n webPath: URL.createObjectURL(file),\n format: format,\n });\n cleanup();\n }\n });\n input.addEventListener('cancel', (_e) => {\n reject(new CapacitorException('User cancelled photos app'));\n cleanup();\n });\n }\n input.accept = 'image/*';\n input.capture = true;\n if (options.source === CameraSource.Photos ||\n options.source === CameraSource.Prompt) {\n input.removeAttribute('capture');\n }\n else if (options.direction === CameraDirection.Front) {\n input.capture = 'user';\n }\n else if (options.direction === CameraDirection.Rear) {\n input.capture = 'environment';\n }\n input.click();\n }\n multipleFileInputExperience(resolve, reject) {\n let input = document.querySelector('#_capacitor-camera-input-multiple');\n const cleanup = () => {\n var _a;\n (_a = input.parentNode) === null || _a === void 0 ? void 0 : _a.removeChild(input);\n };\n if (!input) {\n input = document.createElement('input');\n input.id = '_capacitor-camera-input-multiple';\n input.type = 'file';\n input.hidden = true;\n input.multiple = true;\n document.body.appendChild(input);\n input.addEventListener('change', (_e) => {\n const photos = [];\n // eslint-disable-next-line @typescript-eslint/prefer-for-of\n for (let i = 0; i < input.files.length; i++) {\n const file = input.files[i];\n let format = 'jpeg';\n if (file.type === 'image/png') {\n format = 'png';\n }\n else if (file.type === 'image/gif') {\n format = 'gif';\n }\n photos.push({\n webPath: URL.createObjectURL(file),\n format: format,\n });\n }\n resolve({ photos });\n cleanup();\n });\n input.addEventListener('cancel', (_e) => {\n reject(new CapacitorException('User cancelled photos app'));\n cleanup();\n });\n }\n input.accept = 'image/*';\n input.click();\n }\n _getCameraPhoto(photo, options) {\n return new Promise((resolve, reject) => {\n const reader = new FileReader();\n const format = photo.type.split('/')[1];\n if (options.resultType === 'uri') {\n resolve({\n webPath: URL.createObjectURL(photo),\n format: format,\n saved: false,\n });\n }\n else {\n reader.readAsDataURL(photo);\n reader.onloadend = () => {\n const r = reader.result;\n if (options.resultType === 'dataUrl') {\n resolve({\n dataUrl: r,\n format: format,\n saved: false,\n });\n }\n else {\n resolve({\n base64String: r.split(',')[1],\n format: format,\n saved: false,\n });\n }\n };\n reader.onerror = e => {\n reject(e);\n };\n }\n });\n }\n async checkPermissions() {\n if (typeof navigator === 'undefined' || !navigator.permissions) {\n throw this.unavailable('Permissions API not available in this browser');\n }\n try {\n // https://developer.mozilla.org/en-US/docs/Web/API/Permissions/query\n // the specific permissions that are supported varies among browsers that implement the\n // permissions API, so we need a try/catch in case 'camera' is invalid\n const permission = await window.navigator.permissions.query({\n name: 'camera',\n });\n return {\n camera: permission.state,\n photos: 'granted',\n };\n }\n catch (_a) {\n throw this.unavailable('Camera permissions are not available in this browser');\n }\n }\n async requestPermissions() {\n throw this.unimplemented('Not implemented on web.');\n }\n async pickLimitedLibraryPhotos() {\n throw this.unavailable('Not implemented on web.');\n }\n async getLimitedLibraryPhotos() {\n throw this.unavailable('Not implemented on web.');\n }\n}\nconst Camera = new CameraWeb();\nexport { Camera };\n//# sourceMappingURL=web.js.map","import { registerPlugin } from '@capacitor/core';\nimport { CameraWeb } from './web';\nconst Camera = registerPlugin('Camera', {\n web: () => new CameraWeb(),\n});\nexport * from './definitions';\nexport { Camera };\n//# sourceMappingURL=index.js.map"],"names":["CameraSource","CameraDirection","CameraResultType","WebPlugin","CapacitorException","registerPlugin"],"mappings":"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"} \ No newline at end of file diff --git a/dist/plugin.js b/dist/plugin.js new file mode 100644 index 0000000000..36c7ffbc03 --- /dev/null +++ b/dist/plugin.js @@ -0,0 +1,296 @@ +var capacitorCamera = (function (exports, core) { + 'use strict'; + + exports.CameraSource = void 0; + (function (CameraSource) { + /** + * Prompts the user to select either the photo album or take a photo. + */ + CameraSource["Prompt"] = "PROMPT"; + /** + * Take a new photo using the camera. + */ + CameraSource["Camera"] = "CAMERA"; + /** + * Take multiple photos in a row using the camera. + * Available on Android and iOS. + */ + CameraSource["CameraMulti"] = "CAMERA_MULTI"; + /** + * Pick an existing photo from the gallery or photo album. + */ + CameraSource["Photos"] = "PHOTOS"; + })(exports.CameraSource || (exports.CameraSource = {})); + exports.CameraDirection = void 0; + (function (CameraDirection) { + CameraDirection["Rear"] = "REAR"; + CameraDirection["Front"] = "FRONT"; + })(exports.CameraDirection || (exports.CameraDirection = {})); + exports.CameraResultType = void 0; + (function (CameraResultType) { + CameraResultType["Uri"] = "uri"; + CameraResultType["Base64"] = "base64"; + CameraResultType["DataUrl"] = "dataUrl"; + })(exports.CameraResultType || (exports.CameraResultType = {})); + + class CameraWeb extends core.WebPlugin { + async getPhoto(options) { + // eslint-disable-next-line no-async-promise-executor + return new Promise(async (resolve, reject) => { + if (options.webUseInput || options.source === exports.CameraSource.Photos) { + this.fileInputExperience(options, resolve, reject); + } + else if (options.source === exports.CameraSource.Prompt) { + let actionSheet = document.querySelector('pwa-action-sheet'); + if (!actionSheet) { + actionSheet = document.createElement('pwa-action-sheet'); + document.body.appendChild(actionSheet); + } + actionSheet.header = options.promptLabelHeader || 'Photo'; + actionSheet.cancelable = false; + actionSheet.options = [ + { title: options.promptLabelPhoto || 'From Photos' }, + { title: options.promptLabelPicture || 'Take Picture' }, + ]; + actionSheet.addEventListener('onSelection', async (e) => { + const selection = e.detail; + if (selection === 0) { + this.fileInputExperience(options, resolve, reject); + } + else { + this.cameraExperience(options, resolve, reject); + } + }); + } + else { + this.cameraExperience(options, resolve, reject); + } + }); + } + async pickImages(_options) { + // eslint-disable-next-line no-async-promise-executor + return new Promise(async (resolve, reject) => { + this.multipleFileInputExperience(resolve, reject); + }); + } + async cameraExperience(options, resolve, reject) { + if (customElements.get('pwa-camera-modal')) { + const cameraModal = document.createElement('pwa-camera-modal'); + cameraModal.facingMode = + options.direction === exports.CameraDirection.Front ? 'user' : 'environment'; + document.body.appendChild(cameraModal); + try { + await cameraModal.componentOnReady(); + cameraModal.addEventListener('onPhoto', async (e) => { + const photo = e.detail; + if (photo === null) { + reject(new core.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); + } + } + fileInputExperience(options, resolve, reject) { + let input = document.querySelector('#_capacitor-camera-input'); + const cleanup = () => { + var _a; + (_a = input.parentNode) === null || _a === void 0 ? void 0 : _a.removeChild(input); + }; + if (!input) { + input = document.createElement('input'); + input.id = '_capacitor-camera-input'; + input.type = 'file'; + input.hidden = true; + document.body.appendChild(input); + input.addEventListener('change', (_e) => { + 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, + }); + } + else if (options.resultType === 'base64') { + const b64 = reader.result.split(',')[1]; + resolve({ + base64String: b64, + format, + }); + } + cleanup(); + }); + reader.readAsDataURL(file); + } + else { + resolve({ + webPath: URL.createObjectURL(file), + format: format, + }); + cleanup(); + } + }); + input.addEventListener('cancel', (_e) => { + reject(new core.CapacitorException('User cancelled photos app')); + cleanup(); + }); + } + input.accept = 'image/*'; + input.capture = true; + if (options.source === exports.CameraSource.Photos || + options.source === exports.CameraSource.Prompt) { + input.removeAttribute('capture'); + } + else if (options.direction === exports.CameraDirection.Front) { + input.capture = 'user'; + } + else if (options.direction === exports.CameraDirection.Rear) { + input.capture = 'environment'; + } + input.click(); + } + multipleFileInputExperience(resolve, reject) { + let input = document.querySelector('#_capacitor-camera-input-multiple'); + const cleanup = () => { + var _a; + (_a = input.parentNode) === null || _a === void 0 ? void 0 : _a.removeChild(input); + }; + if (!input) { + input = document.createElement('input'); + input.id = '_capacitor-camera-input-multiple'; + input.type = 'file'; + input.hidden = true; + input.multiple = true; + document.body.appendChild(input); + input.addEventListener('change', (_e) => { + 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) => { + reject(new core.CapacitorException('User cancelled photos app')); + cleanup(); + }); + } + input.accept = 'image/*'; + input.click(); + } + _getCameraPhoto(photo, options) { + 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; + 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() { + 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 (_a) { + throw this.unavailable('Camera permissions are not available in this browser'); + } + } + async requestPermissions() { + throw this.unimplemented('Not implemented on web.'); + } + async pickLimitedLibraryPhotos() { + throw this.unavailable('Not implemented on web.'); + } + async getLimitedLibraryPhotos() { + throw this.unavailable('Not implemented on web.'); + } + } + new CameraWeb(); + + const Camera = core.registerPlugin('Camera', { + web: () => new CameraWeb(), + }); + + exports.Camera = Camera; + + return exports; + +})({}, capacitorExports); +//# sourceMappingURL=plugin.js.map diff --git a/dist/plugin.js.map b/dist/plugin.js.map new file mode 100644 index 0000000000..ff736918d2 --- /dev/null +++ b/dist/plugin.js.map @@ -0,0 +1 @@ +{"version":3,"file":"plugin.js","sources":["esm/definitions.js","esm/web.js","esm/index.js"],"sourcesContent":["export var CameraSource;\n(function (CameraSource) {\n /**\n * Prompts the user to select either the photo album or take a photo.\n */\n CameraSource[\"Prompt\"] = \"PROMPT\";\n /**\n * Take a new photo using the camera.\n */\n CameraSource[\"Camera\"] = \"CAMERA\";\n /**\n * Take multiple photos in a row using the camera.\n * Available on Android and iOS.\n */\n CameraSource[\"CameraMulti\"] = \"CAMERA_MULTI\";\n /**\n * Pick an existing photo from the gallery or photo album.\n */\n CameraSource[\"Photos\"] = \"PHOTOS\";\n})(CameraSource || (CameraSource = {}));\nexport var CameraDirection;\n(function (CameraDirection) {\n CameraDirection[\"Rear\"] = \"REAR\";\n CameraDirection[\"Front\"] = \"FRONT\";\n})(CameraDirection || (CameraDirection = {}));\nexport var CameraResultType;\n(function (CameraResultType) {\n CameraResultType[\"Uri\"] = \"uri\";\n CameraResultType[\"Base64\"] = \"base64\";\n CameraResultType[\"DataUrl\"] = \"dataUrl\";\n})(CameraResultType || (CameraResultType = {}));\n//# sourceMappingURL=definitions.js.map","import { WebPlugin, CapacitorException } from '@capacitor/core';\nimport { CameraSource, CameraDirection } from './definitions';\nexport class CameraWeb extends WebPlugin {\n async getPhoto(options) {\n // eslint-disable-next-line no-async-promise-executor\n return new Promise(async (resolve, reject) => {\n if (options.webUseInput || options.source === CameraSource.Photos) {\n this.fileInputExperience(options, resolve, reject);\n }\n else if (options.source === CameraSource.Prompt) {\n let actionSheet = document.querySelector('pwa-action-sheet');\n if (!actionSheet) {\n actionSheet = document.createElement('pwa-action-sheet');\n document.body.appendChild(actionSheet);\n }\n actionSheet.header = options.promptLabelHeader || 'Photo';\n actionSheet.cancelable = false;\n actionSheet.options = [\n { title: options.promptLabelPhoto || 'From Photos' },\n { title: options.promptLabelPicture || 'Take Picture' },\n ];\n actionSheet.addEventListener('onSelection', async (e) => {\n const selection = e.detail;\n if (selection === 0) {\n this.fileInputExperience(options, resolve, reject);\n }\n else {\n this.cameraExperience(options, resolve, reject);\n }\n });\n }\n else {\n this.cameraExperience(options, resolve, reject);\n }\n });\n }\n async pickImages(_options) {\n // eslint-disable-next-line no-async-promise-executor\n return new Promise(async (resolve, reject) => {\n this.multipleFileInputExperience(resolve, reject);\n });\n }\n async cameraExperience(options, resolve, reject) {\n if (customElements.get('pwa-camera-modal')) {\n const cameraModal = document.createElement('pwa-camera-modal');\n cameraModal.facingMode =\n options.direction === CameraDirection.Front ? 'user' : 'environment';\n document.body.appendChild(cameraModal);\n try {\n await cameraModal.componentOnReady();\n cameraModal.addEventListener('onPhoto', async (e) => {\n const photo = e.detail;\n if (photo === null) {\n reject(new CapacitorException('User cancelled photos app'));\n }\n else if (photo instanceof Error) {\n reject(photo);\n }\n else {\n resolve(await this._getCameraPhoto(photo, options));\n }\n cameraModal.dismiss();\n document.body.removeChild(cameraModal);\n });\n cameraModal.present();\n }\n catch (e) {\n this.fileInputExperience(options, resolve, reject);\n }\n }\n else {\n console.error(`Unable to load PWA Element 'pwa-camera-modal'. See the docs: https://capacitorjs.com/docs/web/pwa-elements.`);\n this.fileInputExperience(options, resolve, reject);\n }\n }\n fileInputExperience(options, resolve, reject) {\n let input = document.querySelector('#_capacitor-camera-input');\n const cleanup = () => {\n var _a;\n (_a = input.parentNode) === null || _a === void 0 ? void 0 : _a.removeChild(input);\n };\n if (!input) {\n input = document.createElement('input');\n input.id = '_capacitor-camera-input';\n input.type = 'file';\n input.hidden = true;\n document.body.appendChild(input);\n input.addEventListener('change', (_e) => {\n const file = input.files[0];\n let format = 'jpeg';\n if (file.type === 'image/png') {\n format = 'png';\n }\n else if (file.type === 'image/gif') {\n format = 'gif';\n }\n if (options.resultType === 'dataUrl' ||\n options.resultType === 'base64') {\n const reader = new FileReader();\n reader.addEventListener('load', () => {\n if (options.resultType === 'dataUrl') {\n resolve({\n dataUrl: reader.result,\n format,\n });\n }\n else if (options.resultType === 'base64') {\n const b64 = reader.result.split(',')[1];\n resolve({\n base64String: b64,\n format,\n });\n }\n cleanup();\n });\n reader.readAsDataURL(file);\n }\n else {\n resolve({\n webPath: URL.createObjectURL(file),\n format: format,\n });\n cleanup();\n }\n });\n input.addEventListener('cancel', (_e) => {\n reject(new CapacitorException('User cancelled photos app'));\n cleanup();\n });\n }\n input.accept = 'image/*';\n input.capture = true;\n if (options.source === CameraSource.Photos ||\n options.source === CameraSource.Prompt) {\n input.removeAttribute('capture');\n }\n else if (options.direction === CameraDirection.Front) {\n input.capture = 'user';\n }\n else if (options.direction === CameraDirection.Rear) {\n input.capture = 'environment';\n }\n input.click();\n }\n multipleFileInputExperience(resolve, reject) {\n let input = document.querySelector('#_capacitor-camera-input-multiple');\n const cleanup = () => {\n var _a;\n (_a = input.parentNode) === null || _a === void 0 ? void 0 : _a.removeChild(input);\n };\n if (!input) {\n input = document.createElement('input');\n input.id = '_capacitor-camera-input-multiple';\n input.type = 'file';\n input.hidden = true;\n input.multiple = true;\n document.body.appendChild(input);\n input.addEventListener('change', (_e) => {\n const photos = [];\n // eslint-disable-next-line @typescript-eslint/prefer-for-of\n for (let i = 0; i < input.files.length; i++) {\n const file = input.files[i];\n let format = 'jpeg';\n if (file.type === 'image/png') {\n format = 'png';\n }\n else if (file.type === 'image/gif') {\n format = 'gif';\n }\n photos.push({\n webPath: URL.createObjectURL(file),\n format: format,\n });\n }\n resolve({ photos });\n cleanup();\n });\n input.addEventListener('cancel', (_e) => {\n reject(new CapacitorException('User cancelled photos app'));\n cleanup();\n });\n }\n input.accept = 'image/*';\n input.click();\n }\n _getCameraPhoto(photo, options) {\n return new Promise((resolve, reject) => {\n const reader = new FileReader();\n const format = photo.type.split('/')[1];\n if (options.resultType === 'uri') {\n resolve({\n webPath: URL.createObjectURL(photo),\n format: format,\n saved: false,\n });\n }\n else {\n reader.readAsDataURL(photo);\n reader.onloadend = () => {\n const r = reader.result;\n if (options.resultType === 'dataUrl') {\n resolve({\n dataUrl: r,\n format: format,\n saved: false,\n });\n }\n else {\n resolve({\n base64String: r.split(',')[1],\n format: format,\n saved: false,\n });\n }\n };\n reader.onerror = e => {\n reject(e);\n };\n }\n });\n }\n async checkPermissions() {\n if (typeof navigator === 'undefined' || !navigator.permissions) {\n throw this.unavailable('Permissions API not available in this browser');\n }\n try {\n // https://developer.mozilla.org/en-US/docs/Web/API/Permissions/query\n // the specific permissions that are supported varies among browsers that implement the\n // permissions API, so we need a try/catch in case 'camera' is invalid\n const permission = await window.navigator.permissions.query({\n name: 'camera',\n });\n return {\n camera: permission.state,\n photos: 'granted',\n };\n }\n catch (_a) {\n throw this.unavailable('Camera permissions are not available in this browser');\n }\n }\n async requestPermissions() {\n throw this.unimplemented('Not implemented on web.');\n }\n async pickLimitedLibraryPhotos() {\n throw this.unavailable('Not implemented on web.');\n }\n async getLimitedLibraryPhotos() {\n throw this.unavailable('Not implemented on web.');\n }\n}\nconst Camera = new CameraWeb();\nexport { Camera };\n//# sourceMappingURL=web.js.map","import { registerPlugin } from '@capacitor/core';\nimport { CameraWeb } from './web';\nconst Camera = registerPlugin('Camera', {\n web: () => new CameraWeb(),\n});\nexport * from './definitions';\nexport { Camera };\n//# sourceMappingURL=index.js.map"],"names":["CameraSource","CameraDirection","CameraResultType","WebPlugin","CapacitorException","registerPlugin"],"mappings":"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"} \ No newline at end of file diff --git a/filesystem/.eslintignore b/filesystem/.eslintignore deleted file mode 100644 index 9d0b71a3c7..0000000000 --- a/filesystem/.eslintignore +++ /dev/null @@ -1,2 +0,0 @@ -build -dist diff --git a/filesystem/.gitignore b/filesystem/.gitignore deleted file mode 100644 index 6817637958..0000000000 --- a/filesystem/.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/filesystem/.prettierignore b/filesystem/.prettierignore deleted file mode 100644 index 9d0b71a3c7..0000000000 --- a/filesystem/.prettierignore +++ /dev/null @@ -1,2 +0,0 @@ -build -dist diff --git a/filesystem/CHANGELOG.md b/filesystem/CHANGELOG.md deleted file mode 100644 index 01fc12a385..0000000000 --- a/filesystem/CHANGELOG.md +++ /dev/null @@ -1,375 +0,0 @@ -# ⓘ Plugin migrated - -**From version 7.1.0 onwards, this plugin is now hosted in a separate repository. Refer to the [updated CHANGELOG](https://github.com/ionic-team/capacitor-filesystem/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. - -## [7.0.1](https://github.com/ionic-team/capacitor-plugins/compare/@capacitor/filesystem@7.0.0...@capacitor/filesystem@7.0.1) (2025-04-02) - -**Note:** Version bump only for package @capacitor/filesystem - -# [7.0.0](https://github.com/ionic-team/capacitor-plugins/compare/@capacitor/filesystem@7.0.0-rc.0...@capacitor/filesystem@7.0.0) (2025-01-20) - -**Note:** Version bump only for package @capacitor/filesystem - -# [7.0.0-rc.0](https://github.com/ionic-team/capacitor-plugins/compare/@capacitor/filesystem@7.0.0-alpha.2...@capacitor/filesystem@7.0.0-rc.0) (2025-01-13) - -### Bug Fixes - -- **android:** run downloadFile asynchronously ([3e64606](https://github.com/ionic-team/capacitor-plugins/commit/3e646062ab34351714080618ec5a3a44c4beab46)) - -# [7.0.0-alpha.2](https://github.com/ionic-team/capacitor-plugins/compare/@capacitor/filesystem@7.0.0-alpha.1...@capacitor/filesystem@7.0.0-alpha.2) (2024-12-19) - -**Note:** Version bump only for package @capacitor/filesystem - -# [7.0.0-alpha.1](https://github.com/ionic-team/capacitor-plugins/compare/@capacitor/filesystem@6.0.1...@capacitor/filesystem@7.0.0-alpha.1) (2024-12-16) - -**Note:** Version bump only for package @capacitor/filesystem - -## [6.0.1](https://github.com/ionic-team/capacitor-plugins/compare/@capacitor/filesystem@6.0.0...@capacitor/filesystem@6.0.1) (2024-08-08) - -**Note:** Version bump only for package @capacitor/filesystem - -# [6.0.0](https://github.com/ionic-team/capacitor-plugins/compare/@capacitor/filesystem@6.0.0-rc.1...@capacitor/filesystem@6.0.0) (2024-04-15) - -**Note:** Version bump only for package @capacitor/filesystem - -# [6.0.0-rc.1](https://github.com/ionic-team/capacitor-plugins/compare/@capacitor/filesystem@6.0.0-rc.0...@capacitor/filesystem@6.0.0-rc.1) (2024-03-25) - -**Note:** Version bump only for package @capacitor/filesystem - -# [6.0.0-rc.0](https://github.com/ionic-team/capacitor-plugins/compare/@capacitor/filesystem@6.0.0-beta.1...@capacitor/filesystem@6.0.0-rc.0) (2024-02-07) - -### Bug Fixes - -- **filesystem:** requestPermissions not resolving ([#1990](https://github.com/ionic-team/capacitor-plugins/issues/1990)) ([2fe419b](https://github.com/ionic-team/capacitor-plugins/commit/2fe419b4af67f5ee0350880c62ebeda93badc3d8)) - -# [6.0.0-beta.1](https://github.com/ionic-team/capacitor-plugins/compare/@capacitor/filesystem@6.0.0-beta.0...@capacitor/filesystem@6.0.0-beta.1) (2023-12-14) - -### Bug Fixes - -- **filesystem:** use next tag for capacitor dependencies ([#1963](https://github.com/ionic-team/capacitor-plugins/issues/1963)) ([80e5261](https://github.com/ionic-team/capacitor-plugins/commit/80e5261b4d7af36d6803deef28153e06e79ab214)) - -# [6.0.0-beta.0](https://github.com/ionic-team/capacitor-plugins/compare/@capacitor/filesystem@6.0.0-alpha.2...@capacitor/filesystem@6.0.0-beta.0) (2023-12-13) - -**Note:** Version bump only for package @capacitor/filesystem - -# [6.0.0-alpha.2](https://github.com/ionic-team/capacitor-plugins/compare/@capacitor/filesystem@6.0.0-alpha.1...@capacitor/filesystem@6.0.0-alpha.2) (2023-11-15) - -**Note:** Version bump only for package @capacitor/filesystem - -# [6.0.0-alpha.1](https://github.com/ionic-team/capacitor-plugins/compare/@capacitor/filesystem@5.1.2...@capacitor/filesystem@6.0.0-alpha.1) (2023-11-08) - -### Bug Fixes - -- **filesystem:** accessing security scoped resources on iOS ([#1660](https://github.com/ionic-team/capacitor-plugins/issues/1660)) ([ee6d93b](https://github.com/ionic-team/capacitor-plugins/commit/ee6d93b1a6efb299cb041b21dbccef225af667c4)) -- **filesystem:** catch http errors on downloadFile ([#1742](https://github.com/ionic-team/capacitor-plugins/issues/1742)) ([5ed01d3](https://github.com/ionic-team/capacitor-plugins/commit/5ed01d303686fdab727b0d40a1cf903ba82c0f43)) - -### Features - -- **filesystem:** add removeAllListeners method ([#1862](https://github.com/ionic-team/capacitor-plugins/issues/1862)) ([f5f84ee](https://github.com/ionic-team/capacitor-plugins/commit/f5f84eedd0806382ba35cb1cdc9f104e35e9b61a)) - -# [5.2.0](https://github.com/ionic-team/capacitor-plugins/compare/@capacitor/filesystem@5.1.4...@capacitor/filesystem@5.2.0) (2023-12-15) - -### Features - -- **filesystem:** add removeAllListeners method ([#1868](https://github.com/ionic-team/capacitor-plugins/issues/1868)) ([fb941c7](https://github.com/ionic-team/capacitor-plugins/commit/fb941c7f39c37684c07c5162644756f4b919418d)) - -## [5.1.4](https://github.com/ionic-team/capacitor-plugins/compare/@capacitor/filesystem@5.1.3...@capacitor/filesystem@5.1.4) (2023-09-14) - -**Note:** Version bump only for package @capacitor/filesystem - -## [5.1.3](https://github.com/ionic-team/capacitor-plugins/compare/@capacitor/filesystem@5.1.2...@capacitor/filesystem@5.1.3) (2023-08-23) - -### Bug Fixes - -- **filesystem:** catch http errors on downloadFile ([#1742](https://github.com/ionic-team/capacitor-plugins/issues/1742)) ([5ed01d3](https://github.com/ionic-team/capacitor-plugins/commit/5ed01d303686fdab727b0d40a1cf903ba82c0f43)) - -## [5.1.2](https://github.com/ionic-team/capacitor-plugins/compare/@capacitor/filesystem@5.1.1...@capacitor/filesystem@5.1.2) (2023-08-09) - -### Bug Fixes - -- **filesystem:** download file on web to IDB instead of Downloads folder ([#1724](https://github.com/ionic-team/capacitor-plugins/issues/1724)) ([bf18f82](https://github.com/ionic-team/capacitor-plugins/commit/bf18f820837df148dd4d5ccfd3d38b72cdb1dac7)) -- **filesystem:** show downloaded files in Documents directory ([#1715](https://github.com/ionic-team/capacitor-plugins/issues/1715)) ([66c7217](https://github.com/ionic-team/capacitor-plugins/commit/66c7217cf3a61aef90346efbd0daa17479fd3769)) - -## [5.1.1](https://github.com/ionic-team/capacitor-plugins/compare/@capacitor/filesystem@5.1.0...@capacitor/filesystem@5.1.1) (2023-07-19) - -### Bug Fixes - -- **filesystem:** don't request permissions on 11 or newer ([#1690](https://github.com/ionic-team/capacitor-plugins/issues/1690)) ([56512c9](https://github.com/ionic-team/capacitor-plugins/commit/56512c924ce66c09d2f52629ab17ad9b4a074196)) - -# [5.1.0](https://github.com/ionic-team/capacitor-plugins/compare/@capacitor/filesystem@5.0.5...@capacitor/filesystem@5.1.0) (2023-07-12) - -### Features - -- **filesystem:** download files from server to filesystem ([d16bad6](https://github.com/ionic-team/capacitor-plugins/commit/d16bad67915658464c8dba0d2d0656e3a7894582)) - -## [5.0.5](https://github.com/ionic-team/capacitor-plugins/compare/@capacitor/filesystem@5.0.4...@capacitor/filesystem@5.0.5) (2023-06-29) - -**Note:** Version bump only for package @capacitor/filesystem - -## [5.0.4](https://github.com/ionic-team/capacitor-plugins/compare/@capacitor/filesystem@5.0.3...@capacitor/filesystem@5.0.4) (2023-06-08) - -**Note:** Version bump only for package @capacitor/filesystem - -## [5.0.3](https://github.com/ionic-team/capacitor-plugins/compare/@capacitor/filesystem@5.0.2...@capacitor/filesystem@5.0.3) (2023-06-08) - -**Note:** Version bump only for package @capacitor/filesystem - -## [5.0.2](https://github.com/ionic-team/capacitor-plugins/compare/@capacitor/filesystem@5.0.1...@capacitor/filesystem@5.0.2) (2023-05-09) - -**Note:** Version bump only for package @capacitor/filesystem - -## [5.0.1](https://github.com/ionic-team/capacitor-plugins/compare/@capacitor/filesystem@5.0.0...@capacitor/filesystem@5.0.1) (2023-05-05) - -### Bug Fixes - -- **android:** add appCompat libraries for maven releases ([#1577](https://github.com/ionic-team/capacitor-plugins/issues/1577)) ([8a2e0ea](https://github.com/ionic-team/capacitor-plugins/commit/8a2e0ea96538a46bde299a864dba760c6e2eba68)) -- 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/filesystem@5.0.0-beta.1...@capacitor/filesystem@5.0.0) (2023-05-03) - -**Note:** Version bump only for package @capacitor/filesystem - -# [5.0.0-beta.1](https://github.com/ionic-team/capacitor-plugins/compare/@capacitor/filesystem@5.0.0-beta.0...@capacitor/filesystem@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/filesystem@5.0.0-alpha.1...@capacitor/filesystem@5.0.0-beta.0) (2023-03-31) - -**Note:** Version bump only for package @capacitor/filesystem - -# [5.0.0-alpha.1](https://github.com/ionic-team/capacitor-plugins/compare/@capacitor/filesystem@4.1.4...@capacitor/filesystem@5.0.0-alpha.1) (2023-03-16) - -### Bug Fixes - -- **filesystem:** copy and rename not working on web ([#1479](https://github.com/ionic-team/capacitor-plugins/issues/1479)) ([7349d01](https://github.com/ionic-team/capacitor-plugins/commit/7349d01350d8a435a00b53e98b43c055880f7aa7)) - -### 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/filesystem@4.1.3...@capacitor/filesystem@4.1.4) (2022-11-16) - -**Note:** Version bump only for package @capacitor/filesystem - -## [4.1.3](https://github.com/ionic-team/capacitor-plugins/compare/@capacitor/filesystem@4.1.2...@capacitor/filesystem@4.1.3) (2022-10-21) - -**Note:** Version bump only for package @capacitor/filesystem - -## [4.1.2](https://github.com/ionic-team/capacitor-plugins/compare/@capacitor/filesystem@4.1.1...@capacitor/filesystem@4.1.2) (2022-09-29) - -### Bug Fixes - -- **filesystem:** Avoid max stack size exceeded on base64 check ([#1202](https://github.com/ionic-team/capacitor-plugins/issues/1202)) ([f4ba421](https://github.com/ionic-team/capacitor-plugins/commit/f4ba421b211e78bd205fa737955780a12e86e24f)) - -## [4.1.1](https://github.com/ionic-team/capacitor-plugins/compare/@capacitor/filesystem@4.1.0...@capacitor/filesystem@4.1.1) (2022-09-12) - -**Note:** Version bump only for package @capacitor/filesystem - -# [4.1.0](https://github.com/ionic-team/capacitor-plugins/compare/@capacitor/filesystem@1.1.0...@capacitor/filesystem@4.1.0) (2022-08-24) - -### Bug Fixes - -- **filesystem:** failing to remove folder content on rmdir ([#1112](https://github.com/ionic-team/capacitor-plugins/issues/1112)) ([ae451aa](https://github.com/ionic-team/capacitor-plugins/commit/ae451aa08beb2138ecebdfcdd26101660aa00fde)) -- **filesystem:** make iOS return proper url on readdir ([#1142](https://github.com/ionic-team/capacitor-plugins/issues/1142)) ([77dc02f](https://github.com/ionic-team/capacitor-plugins/commit/77dc02fb829ad3479144368da16f9fff324f2706)) - -## [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/filesystem - -# [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/filesystem - -# [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/filesystem - -# 4.0.0-beta.0 (2022-06-27) - -### Bug Fixes - -- **filesystem:** Prevent android crash on invalid base64 write ([#937](https://github.com/ionic-team/capacitor-plugins/issues/937)) ([1af0bfe](https://github.com/ionic-team/capacitor-plugins/commit/1af0bfe24d2a36bc2949fe52866131c3327b321e)) -- **filesystem:** Throw errors instead of strings ([#746](https://github.com/ionic-team/capacitor-plugins/issues/746)) ([af4b875](https://github.com/ionic-team/capacitor-plugins/commit/af4b8750be512b869af07bcf96c1602eedc6758e)) -- **filesystem:** web appendFile with base64 data ([#928](https://github.com/ionic-team/capacitor-plugins/issues/928)) ([80253cf](https://github.com/ionic-team/capacitor-plugins/commit/80253cf2652bf7fa9c07933989cbdffeadd52a27)) -- correct addListeners links ([#655](https://github.com/ionic-team/capacitor-plugins/issues/655)) ([f9871e7](https://github.com/ionic-team/capacitor-plugins/commit/f9871e7bd53478addb21155e148829f550c0e457)) -- 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)) -- **android:** permissions use "publicStorage" as alias ([#202](https://github.com/ionic-team/capacitor-plugins/issues/202)) ([2dfc7a3](https://github.com/ionic-team/capacitor-plugins/commit/2dfc7a3261a4f98871a86fe6d47fab084a2d1deb)) -- **android:** support writing files without scheme ([#241](https://github.com/ionic-team/capacitor-plugins/issues/241)) ([4285cb1](https://github.com/ionic-team/capacitor-plugins/commit/4285cb1d37ec3361e7ec4da4786502693b04d478)) -- **filesystem:** allow copy if from is not parent of to ([#546](https://github.com/ionic-team/capacitor-plugins/issues/546)) ([a70414e](https://github.com/ionic-team/capacitor-plugins/commit/a70414e79189579ff1a0b5c2a90d12491f5c23cf)) -- **filesystem:** Append doesn't resolve on iOS ([#305](https://github.com/ionic-team/capacitor-plugins/issues/305)) ([98e91cd](https://github.com/ionic-team/capacitor-plugins/commit/98e91cd745fb12bf46f99233bb527f147dbba58b)) -- **filesystem:** Convert stat ctime/mtime timestamp to milliseconds ([#321](https://github.com/ionic-team/capacitor-plugins/issues/321)) ([d978986](https://github.com/ionic-team/capacitor-plugins/commit/d97898662d0ba037e5f8448990a91de5ec6a4234)) -- **filesystem:** copy doesn't resolve on Android ([#233](https://github.com/ionic-team/capacitor-plugins/issues/233)) ([17cbf3b](https://github.com/ionic-team/capacitor-plugins/commit/17cbf3b0ada97f1279fba32b551c380c0e669406)) -- **filesystem:** is not requesting permission on public directories ([#246](https://github.com/ionic-team/capacitor-plugins/issues/246)) ([aa897ab](https://github.com/ionic-team/capacitor-plugins/commit/aa897ab4269e34cd5d762ed645030054ddda7dd6)) -- **filesystem:** Make ctime optional ([#373](https://github.com/ionic-team/capacitor-plugins/issues/373)) ([e3c6212](https://github.com/ionic-team/capacitor-plugins/commit/e3c6212b94c75cf747a8768af5056963683953b2)) -- **filesystem:** rmdir doesn't resolve on iOS ([#239](https://github.com/ionic-team/capacitor-plugins/issues/239)) ([7ca538b](https://github.com/ionic-team/capacitor-plugins/commit/7ca538bb47e2e00080eadfe8d875323c1e198cb2)) -- 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)) -- 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)) -- **filesystem:** Use PermissionState from @capacitor/core ([#148](https://github.com/ionic-team/capacitor-plugins/issues/148)) ([5ce3c5d](https://github.com/ionic-team/capacitor-plugins/commit/5ce3c5d491a35b8771661f3e4eb98aac6df15911)) - -### Features - -- 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)) -- **filesystem:** Make readDir return files information ([#949](https://github.com/ionic-team/capacitor-plugins/issues/949)) ([0a9f43d](https://github.com/ionic-team/capacitor-plugins/commit/0a9f43dffd3815f600c35ed4528c017644fdb55e)) -- **filesystem:** Return path of copied file ([#931](https://github.com/ionic-team/capacitor-plugins/issues/931)) ([310f583](https://github.com/ionic-team/capacitor-plugins/commit/310f583ccec58730ab8046a1618782c950c60656)) -- add commonjs output format ([#179](https://github.com/ionic-team/capacitor-plugins/issues/179)) ([8e9e098](https://github.com/ionic-team/capacitor-plugins/commit/8e9e09862064b3f6771d7facbc4008e995d9b463)) -- Filesystem plugin ([#19](https://github.com/ionic-team/capacitor-plugins/issues/19)) ([3b86a4a](https://github.com/ionic-team/capacitor-plugins/commit/3b86a4a972e00eaed1d078bfcc69af6136222dc4)) -- 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)) -- 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)) -- **filesystem:** Allow the use of absolute urls on iOS and web ([#250](https://github.com/ionic-team/capacitor-plugins/issues/250)) ([03ad97c](https://github.com/ionic-team/capacitor-plugins/commit/03ad97c1b7450e864504198853aac2b3bdc4b8a4)) -- **filesystem:** support Library directory ([#666](https://github.com/ionic-team/capacitor-plugins/issues/666)) ([ce7ee95](https://github.com/ionic-team/capacitor-plugins/commit/ce7ee958b141f1dd4f86493923455f8264d0b6db)) - -# [1.1.0](https://github.com/ionic-team/capacitor-plugins/compare/@capacitor/filesystem@1.0.7...@capacitor/filesystem@1.1.0) (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)) - -### Features - -- **filesystem:** support Library directory ([#666](https://github.com/ionic-team/capacitor-plugins/issues/666)) ([ce7ee95](https://github.com/ionic-team/capacitor-plugins/commit/ce7ee958b141f1dd4f86493923455f8264d0b6db)) - -## [1.0.7](https://github.com/ionic-team/capacitor-plugins/compare/@capacitor/filesystem@1.0.6...@capacitor/filesystem@1.0.7) (2022-01-10) - -### Bug Fixes - -- **filesystem:** Throw errors instead of strings ([#746](https://github.com/ionic-team/capacitor-plugins/issues/746)) ([af4b875](https://github.com/ionic-team/capacitor-plugins/commit/af4b8750be512b869af07bcf96c1602eedc6758e)) - -## [1.0.6](https://github.com/ionic-team/capacitor-plugins/compare/@capacitor/filesystem@1.0.5...@capacitor/filesystem@1.0.6) (2021-11-03) - -**Note:** Version bump only for package @capacitor/filesystem - -## [1.0.5](https://github.com/ionic-team/capacitor-plugins/compare/@capacitor/filesystem@1.0.4...@capacitor/filesystem@1.0.5) (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.0.4](https://github.com/ionic-team/capacitor-plugins/compare/@capacitor/filesystem@1.0.3...@capacitor/filesystem@1.0.4) (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.0.3](https://github.com/ionic-team/capacitor-plugins/compare/@capacitor/filesystem@1.0.2...@capacitor/filesystem@1.0.3) (2021-09-01) - -### Bug Fixes - -- **filesystem:** allow copy if from is not parent of to ([#546](https://github.com/ionic-team/capacitor-plugins/issues/546)) ([a70414e](https://github.com/ionic-team/capacitor-plugins/commit/a70414e79189579ff1a0b5c2a90d12491f5c23cf)) - -## [1.0.2](https://github.com/ionic-team/capacitor-plugins/compare/@capacitor/filesystem@1.0.1...@capacitor/filesystem@1.0.2) (2021-06-23) - -**Note:** Version bump only for package @capacitor/filesystem - -## [1.0.1](https://github.com/ionic-team/capacitor-plugins/compare/@capacitor/filesystem@1.0.0...@capacitor/filesystem@1.0.1) (2021-06-09) - -**Note:** Version bump only for package @capacitor/filesystem - -# [1.0.0](https://github.com/ionic-team/capacitor-plugins/compare/@capacitor/filesystem@0.5.6...@capacitor/filesystem@1.0.0) (2021-05-19) - -**Note:** Version bump only for package @capacitor/filesystem - -## [0.5.6](https://github.com/ionic-team/capacitor-plugins/compare/@capacitor/filesystem@0.5.5...@capacitor/filesystem@0.5.6) (2021-05-11) - -**Note:** Version bump only for package @capacitor/filesystem - -## [0.5.5](https://github.com/ionic-team/capacitor-plugins/compare/@capacitor/filesystem@0.5.4...@capacitor/filesystem@0.5.5) (2021-05-10) - -**Note:** Version bump only for package @capacitor/filesystem - -## [0.5.4](https://github.com/ionic-team/capacitor-plugins/compare/@capacitor/filesystem@0.5.3...@capacitor/filesystem@0.5.4) (2021-05-07) - -### Bug Fixes - -- **filesystem:** Make ctime optional ([#373](https://github.com/ionic-team/capacitor-plugins/issues/373)) ([e3c6212](https://github.com/ionic-team/capacitor-plugins/commit/e3c6212b94c75cf747a8768af5056963683953b2)) - -## [0.5.3](https://github.com/ionic-team/capacitor-plugins/compare/@capacitor/filesystem@0.5.2...@capacitor/filesystem@0.5.3) (2021-04-29) - -### Bug Fixes - -- **filesystem:** Append doesn't resolve on iOS ([#305](https://github.com/ionic-team/capacitor-plugins/issues/305)) ([98e91cd](https://github.com/ionic-team/capacitor-plugins/commit/98e91cd745fb12bf46f99233bb527f147dbba58b)) -- **filesystem:** Convert stat ctime/mtime timestamp to milliseconds ([#321](https://github.com/ionic-team/capacitor-plugins/issues/321)) ([d978986](https://github.com/ionic-team/capacitor-plugins/commit/d97898662d0ba037e5f8448990a91de5ec6a4234)) - -## [0.5.2](https://github.com/ionic-team/capacitor-plugins/compare/@capacitor/filesystem@0.5.1...@capacitor/filesystem@0.5.2) (2021-03-10) - -**Note:** Version bump only for package @capacitor/filesystem - -## [0.5.1](https://github.com/ionic-team/capacitor-plugins/compare/@capacitor/filesystem@0.5.0...@capacitor/filesystem@0.5.1) (2021-03-02) - -**Note:** Version bump only for package @capacitor/filesystem - -# [0.5.0](https://github.com/ionic-team/capacitor-plugins/compare/@capacitor/filesystem@0.4.0...@capacitor/filesystem@0.5.0) (2021-02-27) - -### Bug Fixes - -- **filesystem:** is not requesting permission on public directories ([#246](https://github.com/ionic-team/capacitor-plugins/issues/246)) ([aa897ab](https://github.com/ionic-team/capacitor-plugins/commit/aa897ab4269e34cd5d762ed645030054ddda7dd6)) - -### Features - -- **filesystem:** Allow the use of absolute urls on iOS and web ([#250](https://github.com/ionic-team/capacitor-plugins/issues/250)) ([03ad97c](https://github.com/ionic-team/capacitor-plugins/commit/03ad97c1b7450e864504198853aac2b3bdc4b8a4)) - -# [0.4.0](https://github.com/ionic-team/capacitor-plugins/compare/@capacitor/filesystem@0.3.2...@capacitor/filesystem@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/filesystem@0.3.1...@capacitor/filesystem@0.3.2) (2021-02-05) - -### Bug Fixes - -- **android:** support writing files without scheme ([#241](https://github.com/ionic-team/capacitor-plugins/issues/241)) ([4285cb1](https://github.com/ionic-team/capacitor-plugins/commit/4285cb1d37ec3361e7ec4da4786502693b04d478)) -- **filesystem:** copy doesn't resolve on Android ([#233](https://github.com/ionic-team/capacitor-plugins/issues/233)) ([17cbf3b](https://github.com/ionic-team/capacitor-plugins/commit/17cbf3b0ada97f1279fba32b551c380c0e669406)) -- **filesystem:** rmdir doesn't resolve on iOS ([#239](https://github.com/ionic-team/capacitor-plugins/issues/239)) ([7ca538b](https://github.com/ionic-team/capacitor-plugins/commit/7ca538bb47e2e00080eadfe8d875323c1e198cb2)) - -## [0.3.1](https://github.com/ionic-team/capacitor-plugins/compare/@capacitor/filesystem@0.3.0...@capacitor/filesystem@0.3.1) (2021-01-26) - -### Bug Fixes - -- **android:** permissions use "publicStorage" as alias ([#202](https://github.com/ionic-team/capacitor-plugins/issues/202)) ([2dfc7a3](https://github.com/ionic-team/capacitor-plugins/commit/2dfc7a3261a4f98871a86fe6d47fab084a2d1deb)) - -# [0.3.0](https://github.com/ionic-team/capacitor-plugins/compare/@capacitor/filesystem@0.2.0...@capacitor/filesystem@0.3.0) (2021-01-14) - -**Note:** Version bump only for package @capacitor/filesystem - -# [0.2.0](https://github.com/ionic-team/capacitor-plugins/compare/@capacitor/filesystem@0.1.3...@capacitor/filesystem@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.3](https://github.com/ionic-team/capacitor-plugins/compare/@capacitor/filesystem@0.1.2...@capacitor/filesystem@0.1.3) (2021-01-13) - -**Note:** Version bump only for package @capacitor/filesystem - -## [0.1.2](https://github.com/ionic-team/capacitor-plugins/compare/@capacitor/filesystem@0.1.1...@capacitor/filesystem@0.1.2) (2021-01-08) - -**Note:** Version bump only for package @capacitor/filesystem - -## [0.1.1](https://github.com/ionic-team/capacitor-plugins/compare/@capacitor/filesystem@0.1.0...@capacitor/filesystem@0.1.1) (2020-12-27) - -**Note:** Version bump only for package @capacitor/filesystem - -# 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)) -- **filesystem:** Use PermissionState from @capacitor/core ([#148](https://github.com/ionic-team/capacitor-plugins/issues/148)) ([5ce3c5d](https://github.com/ionic-team/capacitor-plugins/commit/5ce3c5d491a35b8771661f3e4eb98aac6df15911)) - -### Features - -- Filesystem plugin ([#19](https://github.com/ionic-team/capacitor-plugins/issues/19)) ([3b86a4a](https://github.com/ionic-team/capacitor-plugins/commit/3b86a4a972e00eaed1d078bfcc69af6136222dc4)) diff --git a/filesystem/CapacitorFilesystem.podspec b/filesystem/CapacitorFilesystem.podspec deleted file mode 100644 index c2a829104a..0000000000 --- a/filesystem/CapacitorFilesystem.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 = 'CapacitorFilesystem' - 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}', 'filesystem/ios/Sources/**/*.{swift,h,m,c,cc,mm,cpp}' - s.ios.deployment_target = '14.0' - s.dependency 'Capacitor' - s.swift_version = '5.1' -end diff --git a/filesystem/LICENSE b/filesystem/LICENSE deleted file mode 100644 index 6652495cb2..0000000000 --- a/filesystem/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. diff --git a/filesystem/Package.swift b/filesystem/Package.swift deleted file mode 100644 index 4bcee091dd..0000000000 --- a/filesystem/Package.swift +++ /dev/null @@ -1,28 +0,0 @@ -// swift-tools-version: 5.9 -import PackageDescription - -let package = Package( - name: "CapacitorFilesystem", - platforms: [.iOS(.v14)], - products: [ - .library( - name: "CapacitorFilesystem", - targets: ["FilesystemPlugin"]) - ], - dependencies: [ - .package(url: "https://github.com/ionic-team/capacitor-swift-pm.git", from: "7.0.0") - ], - targets: [ - .target( - name: "FilesystemPlugin", - dependencies: [ - .product(name: "Capacitor", package: "capacitor-swift-pm"), - .product(name: "Cordova", package: "capacitor-swift-pm") - ], - path: "ios/Sources/FilesystemPlugin"), - .testTarget( - name: "FilesystemPluginTests", - dependencies: ["FilesystemPlugin"], - path: "ios/Tests/FilesystemPluginTests") - ] -) diff --git a/filesystem/README.md b/filesystem/README.md deleted file mode 100644 index 13912b2e3e..0000000000 --- a/filesystem/README.md +++ /dev/null @@ -1,731 +0,0 @@ -# ⓘ Plugin migrated - -**From version 7.1.0 onwards, this plugin is now hosted in a separate repository. Refer to [capacitor-filesystem repository](https://github.com/ionic-team/capacitor-filesystem).** - -This file remains here to serve as documentation for version 7.0.1. - -# @capacitor/filesystem - -The Filesystem API provides a NodeJS-like API for working with files on the device. - -## Install - -```bash -npm install @capacitor/filesystem -npx cap sync -``` - -## Apple Privacy Manifest Requirements - -Apple mandates that app developers now specify approved reasons for API usage to enhance user privacy. By May 1st, 2024, it's required to include these reasons when submitting apps to the App Store Connect. - -When using this specific plugin in your app, you must create a `PrivacyInfo.xcprivacy` file in `/ios/App` or use the VS Code Extension to generate it, specifying the usage reasons. - -For detailed steps on how to do this, please see the [Capacitor Docs](https://capacitorjs.com/docs/ios/privacy-manifest). - -**For this plugin, the required dictionary key is [NSPrivacyAccessedAPICategoryFileTimestamp](https://developer.apple.com/documentation/bundleresources/privacy_manifest_files/describing_use_of_required_reason_api#4278393) and the recommended reason is [C617.1](https://developer.apple.com/documentation/bundleresources/privacy_manifest_files/describing_use_of_required_reason_api#4278393).** - -### Example PrivacyInfo.xcprivacy - -```xml - - - - - NSPrivacyAccessedAPITypes - - - - NSPrivacyAccessedAPIType - NSPrivacyAccessedAPICategoryFileTimestamp - NSPrivacyAccessedAPITypeReasons - - C617.1 - - - - - -``` - -## Migrating from downloadFile to File Transfer plugin - -As of version 7.1.0, the `downloadFile` functionality in the Filesystem plugin has been deprecated in favor of the new [@capacitor/file-transfer](https://capacitorjs.com/docs/apis/file-transfer) plugin. - -### Installing the File Transfer plugin - -```bash -npm install @capacitor/file-transfer -npx cap sync -``` - -### Migration example - -Before (using Filesystem plugin): - -```typescript -import { Filesystem, Directory } from '@capacitor/filesystem'; - -await Filesystem.downloadFile({ - url: 'https://example.com/file.pdf', - path: 'downloaded-file.pdf', - directory: Directory.Documents, - progress: true -}); - -// Progress events -Filesystem.addListener('progress', (progress) => { - console.log(`Downloaded ${progress.bytes} of ${progress.contentLength}`); -}); -``` - -After (using File Transfer plugin): - -```typescript -import { FileTransfer } from '@capacitor/file-transfer'; -import { Filesystem, Directory } from '@capacitor/filesystem'; - -// First get the full file path using Filesystem -const fileInfo = await Filesystem.getUri({ - directory: Directory.Documents, - path: 'downloaded-file.pdf' -}); - -// Then use the FileTransfer plugin to download -await FileTransfer.downloadFile({ - url: 'https://example.com/file.pdf', - path: fileInfo.uri, - progress: true -}); - -// Progress events -FileTransfer.addListener('progress', (progress) => { - console.log(`Downloaded ${progress.bytes} of ${progress.contentLength}`); -}); -``` - -The File Transfer plugin offers improved reliability, better error handling with specific error codes, and also adds upload functionality. - -## iOS - -To have files appear in the Files app, you must also set the following keys to `YES` in `Info.plist`: - -- `UIFileSharingEnabled` (`Application supports iTunes file sharing`) -- `LSSupportsOpeningDocumentsInPlace` (`Supports opening documents in place`) - -Read about [Configuring iOS](https://capacitorjs.com/docs/ios/configuration) for help. - -## Android - -If using `Directory.Documents` or `Directory.ExternalStorage`, in Android 10 and older, this API requires the following permissions be added to your `AndroidManifest.xml`: - -```xml - - -``` - -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. - -Note that `Directory.ExternalStorage` is only available on Android 9 or older and `Directory.Documents` only allows to access the files/folders created by your app on Android on Android 11 and newer. - -Working with large files may require you to add `android:largeHeap="true"` to the `` tag in `AndroidManifest.xml`. - -## Understanding Directories and Files - -iOS and Android have additional layers of separation between files, such as special directories that are backed up to the Cloud, or ones for storing Documents. The Filesystem API offers a simple way to scope each operation to a specific special directory on the device. - -Additionally, the Filesystem API supports using full `file://` paths, or reading `content://` files on Android. Simply leave out the `directory` param to use a full file path. - -## Example - -```typescript -import { Filesystem, Directory, Encoding } from '@capacitor/filesystem'; - -const writeSecretFile = async () => { - await Filesystem.writeFile({ - path: 'secrets/text.txt', - data: 'This is a test', - directory: Directory.Documents, - encoding: Encoding.UTF8, - }); -}; - -const readSecretFile = async () => { - const contents = await Filesystem.readFile({ - path: 'secrets/text.txt', - directory: Directory.Documents, - encoding: Encoding.UTF8, - }); - - console.log('secrets:', contents); -}; - -const deleteSecretFile = async () => { - await Filesystem.deleteFile({ - path: 'secrets/text.txt', - directory: Directory.Documents, - }); -}; - -const readFilePath = async () => { - // Here's an example of reading a file with a full file path. Use this to - // read binary data (base64 encoded) from plugins that return File URIs, such as - // the Camera. - const contents = await Filesystem.readFile({ - path: 'file:///var/mobile/Containers/Data/Application/22A433FD-D82D-4989-8BE6-9FC49DEA20BB/Documents/text.txt', - }); - - console.log('data:', contents); -}; -``` - -## API - - - -* [`readFile(...)`](#readfile) -* [`writeFile(...)`](#writefile) -* [`appendFile(...)`](#appendfile) -* [`deleteFile(...)`](#deletefile) -* [`mkdir(...)`](#mkdir) -* [`rmdir(...)`](#rmdir) -* [`readdir(...)`](#readdir) -* [`getUri(...)`](#geturi) -* [`stat(...)`](#stat) -* [`rename(...)`](#rename) -* [`copy(...)`](#copy) -* [`checkPermissions()`](#checkpermissions) -* [`requestPermissions()`](#requestpermissions) -* [`downloadFile(...)`](#downloadfile) -* [`addListener('progress', ...)`](#addlistenerprogress-) -* [`removeAllListeners()`](#removealllisteners) -* [Interfaces](#interfaces) -* [Type Aliases](#type-aliases) -* [Enums](#enums) - - - - - - -### readFile(...) - -```typescript -readFile(options: ReadFileOptions) => Promise -``` - -Read a file from disk - -| Param | Type | -| ------------- | ----------------------------------------------------------- | -| **`options`** | ReadFileOptions | - -**Returns:** Promise<ReadFileResult> - -**Since:** 1.0.0 - --------------------- - - -### writeFile(...) - -```typescript -writeFile(options: WriteFileOptions) => Promise -``` - -Write a file to disk in the specified location on device - -| Param | Type | -| ------------- | ------------------------------------------------------------- | -| **`options`** | WriteFileOptions | - -**Returns:** Promise<WriteFileResult> - -**Since:** 1.0.0 - --------------------- - - -### appendFile(...) - -```typescript -appendFile(options: AppendFileOptions) => Promise -``` - -Append to a file on disk in the specified location on device - -| Param | Type | -| ------------- | --------------------------------------------------------------- | -| **`options`** | AppendFileOptions | - -**Since:** 1.0.0 - --------------------- - - -### deleteFile(...) - -```typescript -deleteFile(options: DeleteFileOptions) => Promise -``` - -Delete a file from disk - -| Param | Type | -| ------------- | --------------------------------------------------------------- | -| **`options`** | DeleteFileOptions | - -**Since:** 1.0.0 - --------------------- - - -### mkdir(...) - -```typescript -mkdir(options: MkdirOptions) => Promise -``` - -Create a directory. - -| Param | Type | -| ------------- | ----------------------------------------------------- | -| **`options`** | MkdirOptions | - -**Since:** 1.0.0 - --------------------- - - -### rmdir(...) - -```typescript -rmdir(options: RmdirOptions) => Promise -``` - -Remove a directory - -| Param | Type | -| ------------- | ----------------------------------------------------- | -| **`options`** | RmdirOptions | - -**Since:** 1.0.0 - --------------------- - - -### readdir(...) - -```typescript -readdir(options: ReaddirOptions) => Promise -``` - -Return a list of files from the directory (not recursive) - -| Param | Type | -| ------------- | --------------------------------------------------------- | -| **`options`** | ReaddirOptions | - -**Returns:** Promise<ReaddirResult> - -**Since:** 1.0.0 - --------------------- - - -### getUri(...) - -```typescript -getUri(options: GetUriOptions) => Promise -``` - -Return full File URI for a path and directory - -| Param | Type | -| ------------- | ------------------------------------------------------- | -| **`options`** | GetUriOptions | - -**Returns:** Promise<GetUriResult> - -**Since:** 1.0.0 - --------------------- - - -### stat(...) - -```typescript -stat(options: StatOptions) => Promise -``` - -Return data about a file - -| Param | Type | -| ------------- | --------------------------------------------------- | -| **`options`** | StatOptions | - -**Returns:** Promise<StatResult> - -**Since:** 1.0.0 - --------------------- - - -### rename(...) - -```typescript -rename(options: RenameOptions) => Promise -``` - -Rename a file or directory - -| Param | Type | -| ------------- | --------------------------------------------------- | -| **`options`** | CopyOptions | - -**Since:** 1.0.0 - --------------------- - - -### copy(...) - -```typescript -copy(options: CopyOptions) => Promise -``` - -Copy a file or directory - -| Param | Type | -| ------------- | --------------------------------------------------- | -| **`options`** | CopyOptions | - -**Returns:** Promise<CopyResult> - -**Since:** 1.0.0 - --------------------- - - -### checkPermissions() - -```typescript -checkPermissions() => Promise -``` - -Check read/write permissions. -Required on Android, only when using `Directory.Documents` or -`Directory.ExternalStorage`. - -**Returns:** Promise<PermissionStatus> - -**Since:** 1.0.0 - --------------------- - - -### requestPermissions() - -```typescript -requestPermissions() => Promise -``` - -Request read/write permissions. -Required on Android, only when using `Directory.Documents` or -`Directory.ExternalStorage`. - -**Returns:** Promise<PermissionStatus> - -**Since:** 1.0.0 - --------------------- - - -### downloadFile(...) - -```typescript -downloadFile(options: DownloadFileOptions) => Promise -``` - -Perform a http request to a server and download the file to the specified destination. - -| Param | Type | -| ------------- | ------------------------------------------------------------------- | -| **`options`** | DownloadFileOptions | - -**Returns:** Promise<DownloadFileResult> - -**Since:** 5.1.0 - --------------------- - - -### addListener('progress', ...) - -```typescript -addListener(eventName: 'progress', listenerFunc: ProgressListener) => Promise -``` - -Add a listener to file download progress events. - -| Param | Type | -| ------------------ | ------------------------------------------------------------- | -| **`eventName`** | 'progress' | -| **`listenerFunc`** | ProgressListener | - -**Returns:** Promise<PluginListenerHandle> - -**Since:** 5.1.0 - --------------------- - - -### removeAllListeners() - -```typescript -removeAllListeners() => Promise -``` - -Remove all listeners for this plugin. - -**Since:** 5.2.0 - --------------------- - - -### Interfaces - - -#### ReadFileResult - -| Prop | Type | Description | Since | -| ---------- | --------------------------- | -------------------------------------------------------------------------------------------------------------------------------------- | ----- | -| **`data`** | string \| Blob | The representation of the data contained in the file Note: Blob is only available on Web. On native, the data is returned as a string. | 1.0.0 | - - -#### ReadFileOptions - -| Prop | Type | Description | Since | -| --------------- | ----------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----- | -| **`path`** | string | The path of the file to read | 1.0.0 | -| **`directory`** | Directory | The `Directory` to read the file from | 1.0.0 | -| **`encoding`** | Encoding | The encoding to read the file in, if not provided, data is read as binary and returned as base64 encoded. Pass Encoding.UTF8 to read data as string | 1.0.0 | - - -#### WriteFileResult - -| Prop | Type | Description | Since | -| --------- | ------------------- | --------------------------------------- | ----- | -| **`uri`** | string | The uri where the file was written into | 1.0.0 | - - -#### WriteFileOptions - -| Prop | Type | Description | Default | Since | -| --------------- | ----------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------ | ----- | -| **`path`** | string | The path of the file to write | | 1.0.0 | -| **`data`** | string \| Blob | The data to write Note: Blob data is only supported on Web. | | 1.0.0 | -| **`directory`** | Directory | The `Directory` to store the file in | | 1.0.0 | -| **`encoding`** | Encoding | The encoding to write the file in. If not provided, data is written as base64 encoded. Pass Encoding.UTF8 to write data as string | | 1.0.0 | -| **`recursive`** | boolean | Whether to create any missing parent directories. | false | 1.0.0 | - - -#### AppendFileOptions - -| Prop | Type | Description | Since | -| --------------- | ----------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------- | ----- | -| **`path`** | string | The path of the file to append | 1.0.0 | -| **`data`** | string | The data to write | 1.0.0 | -| **`directory`** | Directory | The `Directory` to store the file in | 1.0.0 | -| **`encoding`** | Encoding | The encoding to write the file in. If not provided, data is written as base64 encoded. Pass Encoding.UTF8 to write data as string | 1.0.0 | - - -#### DeleteFileOptions - -| Prop | Type | Description | Since | -| --------------- | ----------------------------------------------- | ---------------------------------------------------------------- | ----- | -| **`path`** | string | The path of the file to delete | 1.0.0 | -| **`directory`** | Directory | The `Directory` to delete the file from | 1.0.0 | - - -#### MkdirOptions - -| Prop | Type | Description | Default | Since | -| --------------- | ----------------------------------------------- | --------------------------------------------------------------------- | ------------------ | ----- | -| **`path`** | string | The path of the new directory | | 1.0.0 | -| **`directory`** | Directory | The `Directory` to make the new directory in | | 1.0.0 | -| **`recursive`** | boolean | Whether to create any missing parent directories as well. | false | 1.0.0 | - - -#### RmdirOptions - -| Prop | Type | Description | Default | Since | -| --------------- | ----------------------------------------------- | --------------------------------------------------------------------- | ------------------ | ----- | -| **`path`** | string | The path of the directory to remove | | 1.0.0 | -| **`directory`** | Directory | The `Directory` to remove the directory from | | 1.0.0 | -| **`recursive`** | boolean | Whether to recursively remove the contents of the directory | false | 1.0.0 | - - -#### ReaddirResult - -| Prop | Type | Description | Since | -| ----------- | ----------------------- | -------------------------------------------------- | ----- | -| **`files`** | FileInfo[] | List of files and directories inside the directory | 1.0.0 | - - -#### FileInfo - -| Prop | Type | Description | Since | -| ----------- | ---------------------------------- | ------------------------------------------------------------------------------------ | ----- | -| **`name`** | string | Name of the file or directory. | | -| **`type`** | 'file' \| 'directory' | Type of the file. | 4.0.0 | -| **`size`** | number | Size of the file in bytes. | 4.0.0 | -| **`ctime`** | number | Time of creation in milliseconds. It's not available on Android 7 and older devices. | 4.0.0 | -| **`mtime`** | number | Time of last modification in milliseconds. | 4.0.0 | -| **`uri`** | string | The uri of the file. | 4.0.0 | - - -#### ReaddirOptions - -| Prop | Type | Description | Since | -| --------------- | ----------------------------------------------- | ----------------------------------------------------------- | ----- | -| **`path`** | string | The path of the directory to read | 1.0.0 | -| **`directory`** | Directory | The `Directory` to list files from | 1.0.0 | - - -#### GetUriResult - -| Prop | Type | Description | Since | -| --------- | ------------------- | ------------------- | ----- | -| **`uri`** | string | The uri of the file | 1.0.0 | - - -#### GetUriOptions - -| Prop | Type | Description | Since | -| --------------- | ----------------------------------------------- | -------------------------------------------------------------- | ----- | -| **`path`** | string | The path of the file to get the URI for | 1.0.0 | -| **`directory`** | Directory | The `Directory` to get the file under | 1.0.0 | - - -#### StatResult - -| Prop | Type | Description | Since | -| ----------- | ---------------------------------- | ------------------------------------------------------------------------------------ | ----- | -| **`type`** | 'file' \| 'directory' | Type of the file. | 1.0.0 | -| **`size`** | number | Size of the file in bytes. | 1.0.0 | -| **`ctime`** | number | Time of creation in milliseconds. It's not available on Android 7 and older devices. | 1.0.0 | -| **`mtime`** | number | Time of last modification in milliseconds. | 1.0.0 | -| **`uri`** | string | The uri of the file | 1.0.0 | - - -#### StatOptions - -| Prop | Type | Description | Since | -| --------------- | ----------------------------------------------- | -------------------------------------------------------------- | ----- | -| **`path`** | string | The path of the file to get data about | 1.0.0 | -| **`directory`** | Directory | The `Directory` to get the file under | 1.0.0 | - - -#### CopyOptions - -| Prop | Type | Description | Since | -| ----------------- | ----------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------ | ----- | -| **`from`** | string | The existing file or directory | 1.0.0 | -| **`to`** | string | The destination file or directory | 1.0.0 | -| **`directory`** | Directory | The `Directory` containing the existing file or directory | 1.0.0 | -| **`toDirectory`** | Directory | The `Directory` containing the destination file or directory. If not supplied will use the 'directory' parameter as the destination | 1.0.0 | - - -#### CopyResult - -| Prop | Type | Description | Since | -| --------- | ------------------- | -------------------------------------- | ----- | -| **`uri`** | string | The uri where the file was copied into | 4.0.0 | - - -#### PermissionStatus - -| Prop | Type | -| ------------------- | ----------------------------------------------------------- | -| **`publicStorage`** | PermissionState | - - -#### DownloadFileResult - -| Prop | Type | Description | Since | -| ---------- | ------------------- | -------------------------------------------------------------------- | ----- | -| **`path`** | string | The path the file was downloaded to. | 5.1.0 | -| **`blob`** | Blob | The blob data of the downloaded file. This is only available on web. | 5.1.0 | - - -#### DownloadFileOptions - -| Prop | Type | Description | Default | Since | -| --------------- | ----------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------ | ----- | -| **`path`** | string | The path the downloaded file should be moved to. | | 5.1.0 | -| **`directory`** | Directory | The directory to write the file to. If this option is used, filePath can be a relative path rather than absolute. The default is the `DATA` directory. | | 5.1.0 | -| **`progress`** | boolean | An optional listener function to receive downloaded progress events. If this option is used, progress event should be dispatched on every chunk received. Chunks are throttled to every 100ms on Android/iOS to avoid slowdowns. | | 5.1.0 | -| **`recursive`** | boolean | Whether to create any missing parent directories. | false | 5.1.2 | - - -#### PluginListenerHandle - -| Prop | Type | -| ------------ | ----------------------------------------- | -| **`remove`** | () => Promise<void> | - - -#### ProgressStatus - -| Prop | Type | Description | Since | -| ------------------- | ------------------- | ---------------------------------------------------- | ----- | -| **`url`** | string | The url of the file being downloaded. | 5.1.0 | -| **`bytes`** | number | The number of bytes downloaded so far. | 5.1.0 | -| **`contentLength`** | number | The total number of bytes to download for this file. | 5.1.0 | - - -### Type Aliases - - -#### RenameOptions - -CopyOptions - - -#### PermissionState - -'prompt' | 'prompt-with-rationale' | 'granted' | 'denied' - - -#### ProgressListener - -A listener function that receives progress events. - -(progress: ProgressStatus): void - - -### Enums - - -#### Directory - -| Members | Value | Description | Since | -| --------------------- | ------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----- | -| **`Documents`** | 'DOCUMENTS' | The Documents directory. On iOS it's the app's documents directory. Use this directory to store user-generated content. On Android it's the Public Documents folder, so it's accessible from other apps. It's not accesible on Android 10 unless the app enables legacy External Storage by adding `android:requestLegacyExternalStorage="true"` in the `application` tag in the `AndroidManifest.xml`. On Android 11 or newer the app can only access the files/folders the app created. | 1.0.0 | -| **`Data`** | 'DATA' | The Data directory. On iOS it will use the Documents directory. On Android it's the directory holding application files. Files will be deleted when the application is uninstalled. | 1.0.0 | -| **`Library`** | 'LIBRARY' | The Library directory. On iOS it will use the Library directory. On Android it's the directory holding application files. Files will be deleted when the application is uninstalled. | 1.1.0 | -| **`Cache`** | 'CACHE' | The Cache directory. Can be deleted in cases of low memory, so use this directory to write app-specific files. that your app can re-create easily. | 1.0.0 | -| **`External`** | 'EXTERNAL' | The external directory. On iOS it will use the Documents directory. On Android it's the directory on the primary shared/external storage device where the application can place persistent files it owns. These files are internal to the applications, and not typically visible to the user as media. Files will be deleted when the application is uninstalled. | 1.0.0 | -| **`ExternalStorage`** | 'EXTERNAL_STORAGE' | The external storage directory. On iOS it will use the Documents directory. On Android it's the primary shared/external storage directory. It's not accesible on Android 10 unless the app enables legacy External Storage by adding `android:requestLegacyExternalStorage="true"` in the `application` tag in the `AndroidManifest.xml`. It's not accesible on Android 11 or newer. | 1.0.0 | - - -#### Encoding - -| Members | Value | Description | Since | -| ----------- | -------------------- | ---------------------------------------------------------------------------------------------------------------------------------------- | ----- | -| **`UTF8`** | 'utf8' | Eight-bit UCS Transformation Format | 1.0.0 | -| **`ASCII`** | 'ascii' | Seven-bit ASCII, a.k.a. ISO646-US, a.k.a. the Basic Latin block of the Unicode character set This encoding is only supported on Android. | 1.0.0 | -| **`UTF16`** | 'utf16' | Sixteen-bit UCS Transformation Format, byte order identified by an optional byte-order mark This encoding is only supported on Android. | 1.0.0 | - - diff --git a/filesystem/android/.gitignore b/filesystem/android/.gitignore deleted file mode 100644 index 796b96d1c4..0000000000 --- a/filesystem/android/.gitignore +++ /dev/null @@ -1 +0,0 @@ -/build diff --git a/filesystem/android/build.gradle b/filesystem/android/build.gradle deleted file mode 100644 index 6d54581240..0000000000 --- a/filesystem/android/build.gradle +++ /dev/null @@ -1,79 +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.0' - androidxJunitVersion = project.hasProperty('androidxJunitVersion') ? rootProject.ext.androidxJunitVersion : '1.2.1' - androidxEspressoCoreVersion = project.hasProperty('androidxEspressoCoreVersion') ? rootProject.ext.androidxEspressoCoreVersion : '3.6.1' -} - -buildscript { - repositories { - google() - mavenCentral() - maven { - url "https://plugins.gradle.org/m2/" - } - } - dependencies { - classpath 'com.android.tools.build:gradle:8.7.2' - 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.filesystem" - compileSdk project.hasProperty('compileSdkVersion') ? rootProject.ext.compileSdkVersion : 35 - defaultConfig { - minSdkVersion project.hasProperty('minSdkVersion') ? rootProject.ext.minSdkVersion : 23 - targetSdkVersion project.hasProperty('targetSdkVersion') ? rootProject.ext.targetSdkVersion : 35 - versionCode 1 - versionName "1.0" - testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" - } - buildTypes { - release { - minifyEnabled false - proguardFiles getDefaultProguardFile('proguard-android.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.appcompat:appcompat:$androidxAppCompatVersion" - testImplementation "junit:junit:$junitVersion" - androidTestImplementation "androidx.test.ext:junit:$androidxJunitVersion" - androidTestImplementation "androidx.test.espresso:espresso-core:$androidxEspressoCoreVersion" -} diff --git a/filesystem/android/gradle.properties b/filesystem/android/gradle.properties deleted file mode 100644 index 2e87c52f83..0000000000 --- a/filesystem/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/filesystem/android/gradle/wrapper/gradle-wrapper.jar b/filesystem/android/gradle/wrapper/gradle-wrapper.jar deleted file mode 100644 index a4b76b9530..0000000000 Binary files a/filesystem/android/gradle/wrapper/gradle-wrapper.jar and /dev/null differ diff --git a/filesystem/android/gradle/wrapper/gradle-wrapper.properties b/filesystem/android/gradle/wrapper/gradle-wrapper.properties deleted file mode 100644 index c1d5e01859..0000000000 --- a/filesystem/android/gradle/wrapper/gradle-wrapper.properties +++ /dev/null @@ -1,7 +0,0 @@ -distributionBase=GRADLE_USER_HOME -distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-8.11.1-all.zip -networkTimeout=10000 -validateDistributionUrl=true -zipStoreBase=GRADLE_USER_HOME -zipStorePath=wrapper/dists diff --git a/filesystem/android/gradlew b/filesystem/android/gradlew deleted file mode 100755 index f5feea6d6b..0000000000 --- a/filesystem/android/gradlew +++ /dev/null @@ -1,252 +0,0 @@ -#!/bin/sh - -# -# Copyright © 2015-2021 the original authors. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# https://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# -# SPDX-License-Identifier: Apache-2.0 -# - -############################################################################## -# -# Gradle start up script for POSIX generated by Gradle. -# -# Important for running: -# -# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is -# noncompliant, but you have some other compliant shell such as ksh or -# bash, then to run this script, type that shell name before the whole -# command line, like: -# -# ksh Gradle -# -# Busybox and similar reduced shells will NOT work, because this script -# requires all of these POSIX shell features: -# * functions; -# * expansions «$var», «${var}», «${var:-default}», «${var+SET}», -# «${var#prefix}», «${var%suffix}», and «$( cmd )»; -# * compound commands having a testable exit status, especially «case»; -# * various built-in commands including «command», «set», and «ulimit». -# -# Important for patching: -# -# (2) This script targets any POSIX shell, so it avoids extensions provided -# by Bash, Ksh, etc; in particular arrays are avoided. -# -# The "traditional" practice of packing multiple parameters into a -# space-separated string is a well documented source of bugs and security -# problems, so this is (mostly) avoided, by progressively accumulating -# options in "$@", and eventually passing that to Java. -# -# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS, -# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly; -# see the in-line comments for details. -# -# There are tweaks for specific operating systems such as AIX, CygWin, -# Darwin, MinGW, and NonStop. -# -# (3) This script is generated from the Groovy template -# https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt -# within the Gradle project. -# -# You can find Gradle at https://github.com/gradle/gradle/. -# -############################################################################## - -# Attempt to set APP_HOME - -# Resolve links: $0 may be a link -app_path=$0 - -# Need this for daisy-chained symlinks. -while - APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path - [ -h "$app_path" ] -do - ls=$( ls -ld "$app_path" ) - link=${ls#*' -> '} - 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 -' "$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=$APP_HOME/gradle/wrapper/gradle-wrapper.jar - - -# 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, 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" \ - org.gradle.wrapper.GradleWrapperMain \ - "$@" - -# 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/filesystem/android/gradlew.bat b/filesystem/android/gradlew.bat deleted file mode 100644 index 9b42019c79..0000000000 --- a/filesystem/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=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar - - -@rem Execute Gradle -"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* - -: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/filesystem/android/proguard-rules.pro b/filesystem/android/proguard-rules.pro deleted file mode 100644 index f1b424510d..0000000000 --- a/filesystem/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/filesystem/android/settings.gradle b/filesystem/android/settings.gradle deleted file mode 100644 index 1e5b8431f7..0000000000 --- a/filesystem/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/filesystem/android/src/androidTest/java/com/getcapacitor/android/ExampleInstrumentedTest.java b/filesystem/android/src/androidTest/java/com/getcapacitor/android/ExampleInstrumentedTest.java deleted file mode 100644 index 58020e16cb..0000000000 --- a/filesystem/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/filesystem/android/src/main/AndroidManifest.xml b/filesystem/android/src/main/AndroidManifest.xml deleted file mode 100644 index a2f47b6057..0000000000 --- a/filesystem/android/src/main/AndroidManifest.xml +++ /dev/null @@ -1,2 +0,0 @@ - - diff --git a/filesystem/android/src/main/java/com/capacitorjs/plugins/filesystem/Filesystem.java b/filesystem/android/src/main/java/com/capacitorjs/plugins/filesystem/Filesystem.java deleted file mode 100644 index c3fb54b44c..0000000000 --- a/filesystem/android/src/main/java/com/capacitorjs/plugins/filesystem/Filesystem.java +++ /dev/null @@ -1,414 +0,0 @@ -package com.capacitorjs.plugins.filesystem; - -import android.content.Context; -import android.net.Uri; -import android.os.Environment; -import android.os.Handler; -import android.os.Looper; -import android.util.Base64; -import com.capacitorjs.plugins.filesystem.exceptions.CopyFailedException; -import com.capacitorjs.plugins.filesystem.exceptions.DirectoryExistsException; -import com.capacitorjs.plugins.filesystem.exceptions.DirectoryNotFoundException; -import com.getcapacitor.Bridge; -import com.getcapacitor.JSObject; -import com.getcapacitor.PluginCall; -import com.getcapacitor.plugin.util.CapacitorHttpUrlConnection; -import com.getcapacitor.plugin.util.HttpRequestHandler; -import java.io.BufferedWriter; -import java.io.ByteArrayOutputStream; -import java.io.File; -import java.io.FileInputStream; -import java.io.FileNotFoundException; -import java.io.FileOutputStream; -import java.io.IOException; -import java.io.InputStream; -import java.io.OutputStreamWriter; -import java.net.URISyntaxException; -import java.net.URL; -import java.nio.channels.FileChannel; -import java.nio.charset.Charset; -import java.nio.charset.StandardCharsets; -import java.util.Locale; -import java.util.concurrent.ExecutorService; -import java.util.concurrent.Executors; -import org.json.JSONException; - -public class Filesystem { - - private Context context; - - Filesystem(Context context) { - this.context = context; - } - - public String readFile(String path, String directory, Charset charset) throws IOException { - InputStream is = getInputStream(path, directory); - String dataStr; - if (charset != null) { - dataStr = readFileAsString(is, charset.name()); - } else { - dataStr = readFileAsBase64EncodedData(is); - } - return dataStr; - } - - public void saveFile(File file, String data, Charset charset, Boolean append) throws IOException { - // if charset is not null assume its a plain text file the user wants to save - if (charset != null) { - BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(file, append), charset)); - writer.write(data); - writer.close(); - } else { - //remove header from dataURL - if (data.contains(",")) { - data = data.split(",")[1]; - } - FileOutputStream fos = new FileOutputStream(file, append); - fos.write(Base64.decode(data, Base64.NO_WRAP)); - fos.close(); - } - } - - public boolean deleteFile(String file, String directory) throws FileNotFoundException { - File fileObject = getFileObject(file, directory); - if (!fileObject.exists()) { - throw new FileNotFoundException("File does not exist"); - } - return fileObject.delete(); - } - - public boolean mkdir(String path, String directory, Boolean recursive) throws DirectoryExistsException { - File fileObject = getFileObject(path, directory); - - if (fileObject.exists()) { - throw new DirectoryExistsException("Directory exists"); - } - - boolean created = false; - if (recursive) { - created = fileObject.mkdirs(); - } else { - created = fileObject.mkdir(); - } - return created; - } - - public File[] readdir(String path, String directory) throws DirectoryNotFoundException { - File[] files = null; - File fileObject = getFileObject(path, directory); - if (fileObject != null && fileObject.exists()) { - files = fileObject.listFiles(); - } else { - throw new DirectoryNotFoundException("Directory does not exist"); - } - return files; - } - - public File copy(String from, String directory, String to, String toDirectory, boolean doRename) - throws IOException, CopyFailedException { - if (toDirectory == null) { - toDirectory = directory; - } - - File fromObject = getFileObject(from, directory); - File toObject = getFileObject(to, toDirectory); - - if (fromObject == null) { - throw new CopyFailedException("from file is null"); - } - if (toObject == null) { - throw new CopyFailedException("to file is null"); - } - - if (toObject.equals(fromObject)) { - return toObject; - } - - if (!fromObject.exists()) { - throw new CopyFailedException("The source object does not exist"); - } - - if (toObject.getParentFile().isFile()) { - throw new CopyFailedException("The parent object of the destination is a file"); - } - - if (!toObject.getParentFile().exists()) { - throw new CopyFailedException("The parent object of the destination does not exist"); - } - - if (toObject.isDirectory()) { - throw new CopyFailedException("Cannot overwrite a directory"); - } - - toObject.delete(); - - if (doRename) { - boolean modified = fromObject.renameTo(toObject); - if (!modified) { - throw new CopyFailedException("Unable to rename, unknown reason"); - } - } else { - copyRecursively(fromObject, toObject); - } - - return toObject; - } - - public InputStream getInputStream(String path, String directory) throws IOException { - if (directory == null) { - Uri u = Uri.parse(path); - if (u.getScheme().equals("content")) { - return this.context.getContentResolver().openInputStream(u); - } else { - return new FileInputStream(new File(u.getPath())); - } - } - - File androidDirectory = this.getDirectory(directory); - - if (androidDirectory == null) { - throw new IOException("Directory not found"); - } - - return new FileInputStream(new File(androidDirectory, path)); - } - - public String readFileAsString(InputStream is, String encoding) throws IOException { - ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); - - byte[] buffer = new byte[1024]; - int length = 0; - - while ((length = is.read(buffer)) != -1) { - outputStream.write(buffer, 0, length); - } - - return outputStream.toString(encoding); - } - - public String readFileAsBase64EncodedData(InputStream is) throws IOException { - FileInputStream fileInputStreamReader = (FileInputStream) is; - ByteArrayOutputStream byteStream = new ByteArrayOutputStream(); - - byte[] buffer = new byte[1024]; - - int c; - while ((c = fileInputStreamReader.read(buffer)) != -1) { - byteStream.write(buffer, 0, c); - } - fileInputStreamReader.close(); - - return Base64.encodeToString(byteStream.toByteArray(), Base64.NO_WRAP); - } - - @SuppressWarnings("deprecation") - public File getDirectory(String directory) { - Context c = this.context; - switch (directory) { - case "DOCUMENTS": - return Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOCUMENTS); - case "DATA": - case "LIBRARY": - return c.getFilesDir(); - case "CACHE": - return c.getCacheDir(); - case "EXTERNAL": - return c.getExternalFilesDir(null); - case "EXTERNAL_STORAGE": - return Environment.getExternalStorageDirectory(); - } - return null; - } - - public File getFileObject(String path, String directory) { - if (directory == null) { - Uri u = Uri.parse(path); - if (u.getScheme() == null || u.getScheme().equals("file")) { - return new File(u.getPath()); - } - } - - File androidDirectory = this.getDirectory(directory); - - if (androidDirectory == null) { - return null; - } else { - if (!androidDirectory.exists()) { - androidDirectory.mkdir(); - } - } - - return new File(androidDirectory, path); - } - - public Charset getEncoding(String encoding) { - if (encoding == null) { - return null; - } - - switch (encoding) { - case "utf8": - return StandardCharsets.UTF_8; - case "utf16": - return StandardCharsets.UTF_16; - case "ascii": - return StandardCharsets.US_ASCII; - } - return null; - } - - /** - * Helper function to recursively delete a directory - * - * @param file The file or directory to recursively delete - * @throws IOException - */ - public void deleteRecursively(File file) throws IOException { - if (file.isFile()) { - file.delete(); - return; - } - - for (File f : file.listFiles()) { - deleteRecursively(f); - } - - file.delete(); - } - - /** - * Helper function to recursively copy a directory structure (or just a file) - * - * @param src The source location - * @param dst The destination location - * @throws IOException - */ - public void copyRecursively(File src, File dst) throws IOException { - if (src.isDirectory()) { - dst.mkdir(); - - for (String file : src.list()) { - copyRecursively(new File(src, file), new File(dst, file)); - } - - return; - } - - if (!dst.getParentFile().exists()) { - dst.getParentFile().mkdirs(); - } - - if (!dst.exists()) { - dst.createNewFile(); - } - - try (FileChannel source = new FileInputStream(src).getChannel(); FileChannel destination = new FileOutputStream(dst).getChannel()) { - destination.transferFrom(source, 0, source.size()); - } - } - - public void downloadFile( - PluginCall call, - Bridge bridge, - HttpRequestHandler.ProgressEmitter emitter, - FilesystemDownloadCallback callback - ) { - String urlString = call.getString("url", ""); - ExecutorService executor = Executors.newSingleThreadExecutor(); - Handler handler = new Handler(Looper.getMainLooper()); - - executor.execute( - () -> { - try { - JSObject result = doDownloadInBackground(urlString, call, bridge, emitter); - handler.post(() -> callback.onSuccess(result)); - } catch (Exception error) { - handler.post(() -> callback.onError(error)); - } finally { - executor.shutdown(); - } - } - ); - } - - private JSObject doDownloadInBackground(String urlString, PluginCall call, Bridge bridge, HttpRequestHandler.ProgressEmitter emitter) - throws IOException, URISyntaxException, JSONException { - JSObject headers = call.getObject("headers", new JSObject()); - JSObject params = call.getObject("params", new JSObject()); - Integer connectTimeout = call.getInt("connectTimeout"); - Integer readTimeout = call.getInt("readTimeout"); - Boolean disableRedirects = call.getBoolean("disableRedirects"); - Boolean shouldEncode = call.getBoolean("shouldEncodeUrlParams", true); - Boolean progress = call.getBoolean("progress", false); - - String method = call.getString("method", "GET").toUpperCase(Locale.ROOT); - String path = call.getString("path"); - String directory = call.getString("directory", Environment.DIRECTORY_DOWNLOADS); - - final URL url = new URL(urlString); - final File file = getFileObject(path, directory); - - HttpRequestHandler.HttpURLConnectionBuilder connectionBuilder = new HttpRequestHandler.HttpURLConnectionBuilder() - .setUrl(url) - .setMethod(method) - .setHeaders(headers) - .setUrlParams(params, shouldEncode) - .setConnectTimeout(connectTimeout) - .setReadTimeout(readTimeout) - .setDisableRedirects(disableRedirects) - .openConnection(); - - CapacitorHttpUrlConnection connection = connectionBuilder.build(); - - connection.setSSLSocketFactory(bridge); - - InputStream connectionInputStream = connection.getInputStream(); - FileOutputStream fileOutputStream = new FileOutputStream(file, false); - - String contentLength = connection.getHeaderField("content-length"); - int bytes = 0; - int maxBytes = 0; - - try { - maxBytes = contentLength != null ? Integer.parseInt(contentLength) : 0; - } catch (NumberFormatException ignored) {} - - byte[] buffer = new byte[1024]; - int len; - - // Throttle emitter to 100ms so it doesn't slow down app - long lastEmitTime = System.currentTimeMillis(); - long minEmitIntervalMillis = 100; - - while ((len = connectionInputStream.read(buffer)) > 0) { - fileOutputStream.write(buffer, 0, len); - - bytes += len; - - if (progress && null != emitter) { - long currentTime = System.currentTimeMillis(); - if (currentTime - lastEmitTime > minEmitIntervalMillis) { - emitter.emit(bytes, maxBytes); - lastEmitTime = currentTime; - } - } - } - - if (progress && null != emitter) { - emitter.emit(bytes, maxBytes); - } - - connectionInputStream.close(); - fileOutputStream.close(); - - JSObject ret = new JSObject(); - ret.put("path", file.getAbsolutePath()); - return ret; - } - - public interface FilesystemDownloadCallback { - void onSuccess(JSObject result); - - void onError(Exception error); - } -} diff --git a/filesystem/android/src/main/java/com/capacitorjs/plugins/filesystem/FilesystemPlugin.java b/filesystem/android/src/main/java/com/capacitorjs/plugins/filesystem/FilesystemPlugin.java deleted file mode 100644 index fb33906375..0000000000 --- a/filesystem/android/src/main/java/com/capacitorjs/plugins/filesystem/FilesystemPlugin.java +++ /dev/null @@ -1,551 +0,0 @@ -package com.capacitorjs.plugins.filesystem; - -import android.Manifest; -import android.media.MediaScannerConnection; -import android.net.Uri; -import android.os.Build; -import android.os.Environment; -import com.capacitorjs.plugins.filesystem.exceptions.CopyFailedException; -import com.capacitorjs.plugins.filesystem.exceptions.DirectoryExistsException; -import com.capacitorjs.plugins.filesystem.exceptions.DirectoryNotFoundException; -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.CapacitorPlugin; -import com.getcapacitor.annotation.Permission; -import com.getcapacitor.annotation.PermissionCallback; -import com.getcapacitor.plugin.util.HttpRequestHandler; -import java.io.File; -import java.io.FileNotFoundException; -import java.io.IOException; -import java.nio.charset.Charset; -import java.nio.file.Files; -import java.nio.file.attribute.BasicFileAttributes; -import org.json.JSONException; - -@CapacitorPlugin( - name = "Filesystem", - permissions = { - @Permission( - strings = { Manifest.permission.READ_EXTERNAL_STORAGE, Manifest.permission.WRITE_EXTERNAL_STORAGE }, - alias = "publicStorage" - ) - } -) -public class FilesystemPlugin extends Plugin { - - static final String PUBLIC_STORAGE = "publicStorage"; - private Filesystem implementation; - - @Override - public void load() { - implementation = new Filesystem(getContext()); - } - - private static final String PERMISSION_DENIED_ERROR = "Unable to do file operation, user denied permission request"; - - @PluginMethod - public void readFile(PluginCall call) { - String path = call.getString("path"); - String directory = getDirectoryParameter(call); - String encoding = call.getString("encoding"); - - Charset charset = implementation.getEncoding(encoding); - if (encoding != null && charset == null) { - call.reject("Unsupported encoding provided: " + encoding); - return; - } - - if (isPublicDirectory(directory) && !isStoragePermissionGranted()) { - requestAllPermissions(call, "permissionCallback"); - } else { - try { - String dataStr = implementation.readFile(path, directory, charset); - JSObject ret = new JSObject(); - ret.putOpt("data", dataStr); - call.resolve(ret); - } catch (FileNotFoundException ex) { - call.reject("File does not exist", ex); - } catch (IOException ex) { - call.reject("Unable to read file", ex); - } catch (JSONException ex) { - call.reject("Unable to return value for reading file", ex); - } - } - } - - @PluginMethod - public void writeFile(PluginCall call) { - String path = call.getString("path"); - String data = call.getString("data"); - Boolean recursive = call.getBoolean("recursive", false); - - if (path == null) { - Logger.error(getLogTag(), "No path or filename retrieved from call", null); - call.reject("NO_PATH"); - return; - } - - if (data == null) { - Logger.error(getLogTag(), "No data retrieved from call", null); - call.reject("NO_DATA"); - return; - } - - String directory = getDirectoryParameter(call); - if (directory != null) { - if (isPublicDirectory(directory) && !isStoragePermissionGranted()) { - requestAllPermissions(call, "permissionCallback"); - } else { - // create directory because it might not exist - File androidDir = implementation.getDirectory(directory); - if (androidDir != null) { - if (androidDir.exists() || androidDir.mkdirs()) { - // path might include directories as well - File fileObject = new File(androidDir, path); - if (fileObject.getParentFile().exists() || (recursive && fileObject.getParentFile().mkdirs())) { - saveFile(call, fileObject, data); - } else { - call.reject("Parent folder doesn't exist"); - } - } else { - Logger.error(getLogTag(), "Not able to create '" + directory + "'!", null); - call.reject("NOT_CREATED_DIR"); - } - } else { - Logger.error(getLogTag(), "Directory ID '" + directory + "' is not supported by plugin", null); - call.reject("INVALID_DIR"); - } - } - } else { - // check file:// or no scheme uris - Uri u = Uri.parse(path); - if (u.getScheme() == null || u.getScheme().equals("file")) { - File fileObject = new File(u.getPath()); - // do not know where the file is being store so checking the permission to be secure - // TODO to prevent permission checking we need a property from the call - if (!isStoragePermissionGranted()) { - requestAllPermissions(call, "permissionCallback"); - } else { - if ( - fileObject.getParentFile() == null || - fileObject.getParentFile().exists() || - (recursive && fileObject.getParentFile().mkdirs()) - ) { - saveFile(call, fileObject, data); - } else { - call.reject("Parent folder doesn't exist"); - } - } - } else { - call.reject(u.getScheme() + " scheme not supported"); - } - } - } - - private void saveFile(PluginCall call, File file, String data) { - String encoding = call.getString("encoding"); - boolean append = call.getBoolean("append", false); - - Charset charset = implementation.getEncoding(encoding); - if (encoding != null && charset == null) { - call.reject("Unsupported encoding provided: " + encoding); - return; - } - - try { - implementation.saveFile(file, data, charset, append); - // update mediaStore index only if file was written to external storage - if (isPublicDirectory(getDirectoryParameter(call))) { - MediaScannerConnection.scanFile(getContext(), new String[] { file.getAbsolutePath() }, null, null); - } - Logger.debug(getLogTag(), "File '" + file.getAbsolutePath() + "' saved!"); - JSObject result = new JSObject(); - result.put("uri", Uri.fromFile(file).toString()); - call.resolve(result); - } catch (IOException ex) { - Logger.error( - getLogTag(), - "Creating file '" + file.getPath() + "' with charset '" + charset + "' failed. Error: " + ex.getMessage(), - ex - ); - call.reject("FILE_NOTCREATED"); - } catch (IllegalArgumentException ex) { - call.reject("The supplied data is not valid base64 content."); - } - } - - @PluginMethod - public void appendFile(PluginCall call) { - try { - call.getData().putOpt("append", true); - } catch (JSONException ex) {} - - this.writeFile(call); - } - - @PluginMethod - public void deleteFile(PluginCall call) { - String file = call.getString("path"); - String directory = getDirectoryParameter(call); - if (isPublicDirectory(directory) && !isStoragePermissionGranted()) { - requestAllPermissions(call, "permissionCallback"); - } else { - try { - boolean deleted = implementation.deleteFile(file, directory); - if (!deleted) { - call.reject("Unable to delete file"); - } else { - call.resolve(); - } - } catch (FileNotFoundException ex) { - call.reject(ex.getMessage()); - } - } - } - - @PluginMethod - public void mkdir(PluginCall call) { - String path = call.getString("path"); - String directory = getDirectoryParameter(call); - boolean recursive = call.getBoolean("recursive", false).booleanValue(); - if (isPublicDirectory(directory) && !isStoragePermissionGranted()) { - requestAllPermissions(call, "permissionCallback"); - } else { - try { - boolean created = implementation.mkdir(path, directory, recursive); - if (!created) { - call.reject("Unable to create directory, unknown reason"); - } else { - call.resolve(); - } - } catch (DirectoryExistsException ex) { - call.reject(ex.getMessage()); - } - } - } - - @PluginMethod - public void rmdir(PluginCall call) { - String path = call.getString("path"); - String directory = getDirectoryParameter(call); - Boolean recursive = call.getBoolean("recursive", false); - - File fileObject = implementation.getFileObject(path, directory); - - if (isPublicDirectory(directory) && !isStoragePermissionGranted()) { - requestAllPermissions(call, "permissionCallback"); - } else { - if (!fileObject.exists()) { - call.reject("Directory does not exist"); - return; - } - - if (fileObject.isDirectory() && fileObject.listFiles().length != 0 && !recursive) { - call.reject("Directory is not empty"); - return; - } - - boolean deleted = false; - - try { - implementation.deleteRecursively(fileObject); - deleted = true; - } catch (IOException ignored) {} - - if (!deleted) { - call.reject("Unable to delete directory, unknown reason"); - } else { - call.resolve(); - } - } - } - - @PluginMethod - public void readdir(PluginCall call) { - String path = call.getString("path"); - String directory = getDirectoryParameter(call); - - if (isPublicDirectory(directory) && !isStoragePermissionGranted()) { - requestAllPermissions(call, "permissionCallback"); - } else { - try { - File[] files = implementation.readdir(path, directory); - JSArray filesArray = new JSArray(); - if (files != null) { - for (var i = 0; i < files.length; i++) { - File fileObject = files[i]; - JSObject data = new JSObject(); - data.put("name", fileObject.getName()); - data.put("type", fileObject.isDirectory() ? "directory" : "file"); - data.put("size", fileObject.length()); - data.put("mtime", fileObject.lastModified()); - data.put("uri", Uri.fromFile(fileObject).toString()); - - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { - try { - BasicFileAttributes attr = Files.readAttributes(fileObject.toPath(), BasicFileAttributes.class); - - // use whichever is the oldest between creationTime and lastAccessTime - if (attr.creationTime().toMillis() < attr.lastAccessTime().toMillis()) { - data.put("ctime", attr.creationTime().toMillis()); - } else { - data.put("ctime", attr.lastAccessTime().toMillis()); - } - } catch (Exception ex) {} - } else { - data.put("ctime", null); - } - filesArray.put(data); - } - - JSObject ret = new JSObject(); - ret.put("files", filesArray); - call.resolve(ret); - } else { - call.reject("Unable to read directory"); - } - } catch (DirectoryNotFoundException ex) { - call.reject(ex.getMessage()); - } - } - } - - @PluginMethod - public void getUri(PluginCall call) { - String path = call.getString("path"); - String directory = getDirectoryParameter(call); - - File fileObject = implementation.getFileObject(path, directory); - - if (isPublicDirectory(directory) && !isStoragePermissionGranted()) { - requestAllPermissions(call, "permissionCallback"); - } else { - JSObject data = new JSObject(); - data.put("uri", Uri.fromFile(fileObject).toString()); - call.resolve(data); - } - } - - @PluginMethod - public void stat(PluginCall call) { - String path = call.getString("path"); - String directory = getDirectoryParameter(call); - - File fileObject = implementation.getFileObject(path, directory); - - if (isPublicDirectory(directory) && !isStoragePermissionGranted()) { - requestAllPermissions(call, "permissionCallback"); - } else { - if (!fileObject.exists()) { - call.reject("File does not exist"); - return; - } - - JSObject data = new JSObject(); - data.put("type", fileObject.isDirectory() ? "directory" : "file"); - data.put("size", fileObject.length()); - data.put("mtime", fileObject.lastModified()); - data.put("uri", Uri.fromFile(fileObject).toString()); - - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { - try { - BasicFileAttributes attr = Files.readAttributes(fileObject.toPath(), BasicFileAttributes.class); - - // use whichever is the oldest between creationTime and lastAccessTime - if (attr.creationTime().toMillis() < attr.lastAccessTime().toMillis()) { - data.put("ctime", attr.creationTime().toMillis()); - } else { - data.put("ctime", attr.lastAccessTime().toMillis()); - } - } catch (Exception ex) {} - } else { - data.put("ctime", null); - } - - call.resolve(data); - } - } - - @PluginMethod - public void rename(PluginCall call) { - this._copy(call, true); - } - - @PluginMethod - public void copy(PluginCall call) { - this._copy(call, false); - } - - @PluginMethod - public void downloadFile(PluginCall call) { - try { - String directory = call.getString("directory", Environment.DIRECTORY_DOWNLOADS); - - if (isPublicDirectory(directory) && !isStoragePermissionGranted()) { - requestAllPermissions(call, "permissionCallback"); - return; - } - - HttpRequestHandler.ProgressEmitter emitter = (bytes, contentLength) -> { - JSObject ret = new JSObject(); - ret.put("url", call.getString("url")); - ret.put("bytes", bytes); - ret.put("contentLength", contentLength); - notifyListeners("progress", ret); - }; - - implementation.downloadFile( - call, - bridge, - emitter, - new Filesystem.FilesystemDownloadCallback() { - @Override - public void onSuccess(JSObject response) { - // update mediaStore index only if file was written to external storage - if (isPublicDirectory(directory)) { - MediaScannerConnection.scanFile(getContext(), new String[] { response.getString("path") }, null, null); - } - call.resolve(response); - } - - @Override - public void onError(Exception error) { - call.reject("Error downloading file: " + error.getLocalizedMessage(), error); - } - } - ); - } catch (Exception ex) { - call.reject("Error downloading file: " + ex.getLocalizedMessage(), ex); - } - } - - private void _copy(PluginCall call, Boolean doRename) { - String from = call.getString("from"); - String to = call.getString("to"); - String directory = call.getString("directory"); - String toDirectory = call.getString("toDirectory"); - - if (from == null || from.isEmpty() || to == null || to.isEmpty()) { - call.reject("Both to and from must be provided"); - return; - } - if (isPublicDirectory(directory) || isPublicDirectory(toDirectory)) { - if (!isStoragePermissionGranted()) { - requestAllPermissions(call, "permissionCallback"); - return; - } - } - try { - File file = implementation.copy(from, directory, to, toDirectory, doRename); - if (!doRename) { - JSObject result = new JSObject(); - result.put("uri", Uri.fromFile(file).toString()); - call.resolve(result); - } else { - call.resolve(); - } - } catch (CopyFailedException ex) { - call.reject(ex.getMessage()); - } catch (IOException ex) { - call.reject("Unable to perform action: " + ex.getLocalizedMessage()); - } - } - - @PluginMethod - public void checkPermissions(PluginCall call) { - if (isStoragePermissionGranted()) { - JSObject permissionsResultJSON = new JSObject(); - permissionsResultJSON.put(PUBLIC_STORAGE, "granted"); - call.resolve(permissionsResultJSON); - } else { - super.checkPermissions(call); - } - } - - @PluginMethod - public void requestPermissions(PluginCall call) { - if (isStoragePermissionGranted()) { - JSObject permissionsResultJSON = new JSObject(); - permissionsResultJSON.put(PUBLIC_STORAGE, "granted"); - call.resolve(permissionsResultJSON); - } else { - super.requestPermissions(call); - } - } - - @PermissionCallback - private void permissionCallback(PluginCall call) { - if (!isStoragePermissionGranted()) { - Logger.debug(getLogTag(), "User denied storage permission"); - call.reject(PERMISSION_DENIED_ERROR); - return; - } - - switch (call.getMethodName()) { - case "appendFile": - case "writeFile": - writeFile(call); - break; - case "deleteFile": - deleteFile(call); - break; - case "mkdir": - mkdir(call); - break; - case "rmdir": - rmdir(call); - break; - case "rename": - rename(call); - break; - case "copy": - copy(call); - break; - case "readFile": - readFile(call); - break; - case "readdir": - readdir(call); - break; - case "getUri": - getUri(call); - break; - case "stat": - stat(call); - break; - case "downloadFile": - downloadFile(call); - break; - } - } - - /** - * Checks the the given permission is granted or not - * @return Returns true if the app is running on Android 30 or newer or if the permission is already granted - * or false if it is denied. - */ - private boolean isStoragePermissionGranted() { - return Build.VERSION.SDK_INT >= Build.VERSION_CODES.R || getPermissionState(PUBLIC_STORAGE) == PermissionState.GRANTED; - } - - /** - * Reads the directory parameter from the plugin call - * @param call the plugin call - */ - private String getDirectoryParameter(PluginCall call) { - return call.getString("directory"); - } - - /** - * True if the given directory string is a public storage directory, which is accessible by the user or other apps. - * @param directory the directory string. - */ - private boolean isPublicDirectory(String directory) { - return "DOCUMENTS".equals(directory) || "EXTERNAL_STORAGE".equals(directory); - } -} diff --git a/filesystem/android/src/main/java/com/capacitorjs/plugins/filesystem/exceptions/CopyFailedException.java b/filesystem/android/src/main/java/com/capacitorjs/plugins/filesystem/exceptions/CopyFailedException.java deleted file mode 100644 index d722bd06fa..0000000000 --- a/filesystem/android/src/main/java/com/capacitorjs/plugins/filesystem/exceptions/CopyFailedException.java +++ /dev/null @@ -1,16 +0,0 @@ -package com.capacitorjs.plugins.filesystem.exceptions; - -public class CopyFailedException extends Exception { - - public CopyFailedException(String s) { - super(s); - } - - public CopyFailedException(Throwable t) { - super(t); - } - - public CopyFailedException(String s, Throwable t) { - super(s, t); - } -} diff --git a/filesystem/android/src/main/java/com/capacitorjs/plugins/filesystem/exceptions/DirectoryExistsException.java b/filesystem/android/src/main/java/com/capacitorjs/plugins/filesystem/exceptions/DirectoryExistsException.java deleted file mode 100644 index ff1722f037..0000000000 --- a/filesystem/android/src/main/java/com/capacitorjs/plugins/filesystem/exceptions/DirectoryExistsException.java +++ /dev/null @@ -1,16 +0,0 @@ -package com.capacitorjs.plugins.filesystem.exceptions; - -public class DirectoryExistsException extends Exception { - - public DirectoryExistsException(String s) { - super(s); - } - - public DirectoryExistsException(Throwable t) { - super(t); - } - - public DirectoryExistsException(String s, Throwable t) { - super(s, t); - } -} diff --git a/filesystem/android/src/main/java/com/capacitorjs/plugins/filesystem/exceptions/DirectoryNotFoundException.java b/filesystem/android/src/main/java/com/capacitorjs/plugins/filesystem/exceptions/DirectoryNotFoundException.java deleted file mode 100644 index 33a56e8b4f..0000000000 --- a/filesystem/android/src/main/java/com/capacitorjs/plugins/filesystem/exceptions/DirectoryNotFoundException.java +++ /dev/null @@ -1,16 +0,0 @@ -package com.capacitorjs.plugins.filesystem.exceptions; - -public class DirectoryNotFoundException extends Exception { - - public DirectoryNotFoundException(String s) { - super(s); - } - - public DirectoryNotFoundException(Throwable t) { - super(t); - } - - public DirectoryNotFoundException(String s, Throwable t) { - super(s, t); - } -} diff --git a/filesystem/android/src/test/java/com/getcapacitor/ExampleUnitTest.java b/filesystem/android/src/test/java/com/getcapacitor/ExampleUnitTest.java deleted file mode 100644 index a0fed0cfb7..0000000000 --- a/filesystem/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/filesystem/ios/.gitignore b/filesystem/ios/.gitignore deleted file mode 100644 index 0023a53406..0000000000 --- a/filesystem/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/filesystem/ios/Sources/FilesystemPlugin/Filesystem.swift b/filesystem/ios/Sources/FilesystemPlugin/Filesystem.swift deleted file mode 100644 index 51c7ba2061..0000000000 --- a/filesystem/ios/Sources/FilesystemPlugin/Filesystem.swift +++ /dev/null @@ -1,344 +0,0 @@ -import Foundation -import Capacitor - -@objc public class Filesystem: NSObject { - - public enum FilesystemError: LocalizedError { - case noParentFolder, noSave, failEncode, noAppend, notEmpty - - public var errorDescription: String? { - switch self { - case .noParentFolder: - return "Parent folder doesn't exist" - case .noSave: - return "Unable to save file" - case .failEncode: - return "Unable to encode data to utf-8" - case .noAppend: - return "Unable to append file" - case .notEmpty: - return "Folder is not empty" - } - } - } - - public typealias ProgressEmitter = (_ bytes: Int64, _ contentLength: Int64) -> Void - - public func readFile(at fileUrl: URL, with encoding: String?) throws -> String { - fileUrl.startAccessingSecurityScopedResource() - if encoding != nil { - let data = try String(contentsOf: fileUrl, encoding: .utf8) - fileUrl.stopAccessingSecurityScopedResource() - return data - } else { - let data = try Data(contentsOf: fileUrl) - fileUrl.stopAccessingSecurityScopedResource() - return data.base64EncodedString() - } - } - - public func writeFile(at fileUrl: URL, with data: String, recursive: Bool, with encoding: String?) throws -> String { - if !FileManager.default.fileExists(atPath: fileUrl.deletingLastPathComponent().path) { - if recursive { - try FileManager.default.createDirectory(at: fileUrl.deletingLastPathComponent(), withIntermediateDirectories: recursive, attributes: nil) - } else { - throw FilesystemError.noParentFolder - } - } - if encoding != nil { - try data.write(to: fileUrl, atomically: false, encoding: .utf8) - } else { - if let base64Data = Data.capacitor.data(base64EncodedOrDataUrl: data) { - try base64Data.write(to: fileUrl) - } else { - throw FilesystemError.noSave - } - } - return fileUrl.absoluteString - } - - @objc public func appendFile(at fileUrl: URL, with data: String, recursive: Bool, with encoding: String?) throws { - if FileManager.default.fileExists(atPath: fileUrl.path) { - let fileHandle = try FileHandle.init(forWritingTo: fileUrl) - var writeData: Data? - if encoding != nil { - guard let userData = data.data(using: .utf8) else { - throw FilesystemError.failEncode - } - writeData = userData - } else { - if let base64Data = Data.capacitor.data(base64EncodedOrDataUrl: data) { - writeData = base64Data - } else { - throw FilesystemError.noAppend - } - } - defer { - fileHandle.closeFile() - } - fileHandle.seekToEndOfFile() - fileHandle.write(writeData!) - } else { - _ = try writeFile(at: fileUrl, with: data, recursive: recursive, with: encoding) - } - } - - @objc func deleteFile(at fileUrl: URL) throws { - if FileManager.default.fileExists(atPath: fileUrl.path) { - try FileManager.default.removeItem(atPath: fileUrl.path) - } - } - - @objc public func mkdir(at fileUrl: URL, recursive: Bool) throws { - try FileManager.default.createDirectory(at: fileUrl, withIntermediateDirectories: recursive, attributes: nil) - } - - @objc public func rmdir(at fileUrl: URL, recursive: Bool) throws { - let directoryContents = try FileManager.default.contentsOfDirectory(at: fileUrl, includingPropertiesForKeys: nil, options: []) - if directoryContents.count != 0 && !recursive { - throw FilesystemError.notEmpty - } - try FileManager.default.removeItem(at: fileUrl) - } - - public func readdir(at fileUrl: URL) throws -> [URL] { - return try FileManager.default.contentsOfDirectory(at: fileUrl, includingPropertiesForKeys: nil, options: []) - } - - func stat(at fileUrl: URL) throws -> [FileAttributeKey: Any] { - return try FileManager.default.attributesOfItem(atPath: fileUrl.path) - } - - func getType(from attr: [FileAttributeKey: Any]) -> String { - let fileType = attr[.type] as? String ?? "" - if fileType == "NSFileTypeDirectory" { - return "directory" - } else { - return "file" - } - } - - @objc public func rename(at srcURL: URL, to dstURL: URL) throws { - try _copy(at: srcURL, to: dstURL, doRename: true) - } - - @objc public func copy(at srcURL: URL, to dstURL: URL) throws { - try _copy(at: srcURL, to: dstURL, doRename: false) - } - - /** - * Copy or rename a file or directory. - */ - private func _copy(at srcURL: URL, to dstURL: URL, doRename: Bool) throws { - if srcURL == dstURL { - return - } - var isDir: ObjCBool = false - if FileManager.default.fileExists(atPath: dstURL.path, isDirectory: &isDir) { - if !isDir.boolValue { - try? FileManager.default.removeItem(at: dstURL) - } - } - - if doRename { - try FileManager.default.moveItem(at: srcURL, to: dstURL) - } else { - try FileManager.default.copyItem(at: srcURL, to: dstURL) - } - - } - - /** - * Get the SearchPathDirectory corresponding to the JS string - */ - public func getDirectory(directory: String?) -> FileManager.SearchPathDirectory? { - if let directory = directory { - switch directory { - case "CACHE": - return .cachesDirectory - case "LIBRARY": - return .libraryDirectory - default: - return .documentDirectory - } - } - return nil - } - - /** - * Get the URL for this file, supporting file:// paths and - * files with directory mappings. - */ - @objc public func getFileUrl(at path: String, in directory: String?) -> URL? { - if let directory = getDirectory(directory: directory) { - guard let dir = FileManager.default.urls(for: directory, in: .userDomainMask).first else { - return nil - } - if !path.isEmpty { - return dir.appendingPathComponent(path) - } - return dir - } else { - return URL(string: path) - } - } - - // swiftlint:disable function_body_length - @objc public func downloadFile(call: CAPPluginCall, emitter: @escaping ProgressEmitter, config: InstanceConfiguration?) throws { - let directory = call.getString("directory", "DOCUMENTS") - guard let path = call.getString("path") else { - call.reject("Invalid file path") - return - } - guard var urlString = call.getString("url") else { throw URLError(.badURL) } - - func handleDownload(downloadLocation: URL?, response: URLResponse?, error: Error?) { - if let error = error { - CAPLog.print("Error on download file", String(describing: downloadLocation), String(describing: response), String(describing: error)) - call.reject(error.localizedDescription, "DOWNLOAD", error, nil) - return - } - - if let httpResponse = response as? HTTPURLResponse { - if !(200...299).contains(httpResponse.statusCode) { - CAPLog.print("Error downloading file:", urlString, httpResponse) - call.reject("Error downloading file: \(urlString)", "DOWNLOAD") - return - } - HttpRequestHandler.setCookiesFromResponse(httpResponse, config) - } - - guard let location = downloadLocation else { - call.reject("Unable to get file after downloading") - return - } - - let fileManager = FileManager.default - - if let foundDir = getDirectory(directory: directory) { - let dir = fileManager.urls(for: foundDir, in: .userDomainMask).first - - do { - let dest = dir!.appendingPathComponent(path) - CAPLog.print("Attempting to write to file destination: \(dest.absoluteString)") - - if !FileManager.default.fileExists(atPath: dest.deletingLastPathComponent().absoluteString) { - try FileManager.default.createDirectory(at: dest.deletingLastPathComponent(), withIntermediateDirectories: true, attributes: nil) - } - - if FileManager.default.fileExists(atPath: dest.relativePath) { - do { - CAPLog.print("File already exists. Attempting to remove file before writing.") - try fileManager.removeItem(at: dest) - } catch let error { - call.reject("Unable to remove existing file: \(error.localizedDescription)") - return - } - } - - try fileManager.moveItem(at: location, to: dest) - CAPLog.print("Downloaded file successfully! \(dest.absoluteString)") - call.resolve(["path": dest.absoluteString]) - } catch let error { - call.reject("Unable to download file: \(error.localizedDescription)", "DOWNLOAD", error) - return - } - } else { - call.reject("Unable to download file. Couldn't find directory \(directory)") - } - } - - let method = call.getString("method", "GET") - - let headers = (call.getObject("headers") ?? [:]) as [String: Any] - let params = (call.getObject("params") ?? [:]) as [String: Any] - let responseType = call.getString("responseType", "text") - let connectTimeout = call.getDouble("connectTimeout") - let readTimeout = call.getDouble("readTimeout") - - if urlString == urlString.removingPercentEncoding { - guard let encodedUrlString = urlString.addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed) else { throw URLError(.badURL) } - urlString = encodedUrlString - } - - let progress = call.getBool("progress", false) - - let request = try HttpRequestHandler.CapacitorHttpRequestBuilder() - .setUrl(urlString) - .setMethod(method) - .setUrlParams(params) - .openConnection() - .build() - - request.setRequestHeaders(headers) - - // Timeouts in iOS are in seconds. So read the value in millis and divide by 1000 - let timeout = (connectTimeout ?? readTimeout ?? 600000.0) / 1000.0 - request.setTimeout(timeout) - - if let data = call.options["data"] as? JSValue { - do { - try request.setRequestBody(data) - } catch { - // Explicitly reject if the http request body was not set successfully, - // so as to not send a known malformed request, and to provide the developer with additional context. - call.reject(error.localizedDescription, (error as NSError).domain, error, nil) - return - } - } - - var session: URLSession! - var task: URLSessionDownloadTask! - let urlRequest = request.getUrlRequest() - - if progress { - class ProgressDelegate: NSObject, URLSessionDataDelegate, URLSessionDownloadDelegate { - private var handler: (URL?, URLResponse?, Error?) -> Void - private var downloadLocation: URL? - private var response: URLResponse? - private var emitter: (Int64, Int64) -> Void - private var lastEmitTimestamp: TimeInterval = 0.0 - - init(downloadHandler: @escaping (URL?, URLResponse?, Error?) -> Void, progressEmitter: @escaping (Int64, Int64) -> Void) { - handler = downloadHandler - emitter = progressEmitter - } - - func urlSession(_ session: URLSession, downloadTask: URLSessionDownloadTask, didWriteData bytesWritten: Int64, totalBytesWritten: Int64, totalBytesExpectedToWrite: Int64) { - let currentTimestamp = Date().timeIntervalSince1970 - let timeElapsed = currentTimestamp - lastEmitTimestamp - - if totalBytesExpectedToWrite > 0 { - if timeElapsed >= 0.1 { - emitter(totalBytesWritten, totalBytesExpectedToWrite) - lastEmitTimestamp = currentTimestamp - } - } else { - emitter(totalBytesWritten, 0) - lastEmitTimestamp = currentTimestamp - } - } - - func urlSession(_ session: URLSession, downloadTask: URLSessionDownloadTask, didFinishDownloadingTo location: URL) { - downloadLocation = location - handler(downloadLocation, downloadTask.response, downloadTask.error) - } - - func urlSession(_ session: URLSession, task: URLSessionTask, didCompleteWithError error: Error?) { - if error != nil { - handler(downloadLocation, task.response, error) - } - } - } - - let progressDelegate = ProgressDelegate(downloadHandler: handleDownload, progressEmitter: emitter) - session = URLSession(configuration: .default, delegate: progressDelegate, delegateQueue: nil) - task = session.downloadTask(with: urlRequest) - } else { - task = URLSession.shared.downloadTask(with: urlRequest, completionHandler: handleDownload) - } - - task.resume() - } - // swiftlint:enable function_body_length -} diff --git a/filesystem/ios/Sources/FilesystemPlugin/FilesystemPlugin.swift b/filesystem/ios/Sources/FilesystemPlugin/FilesystemPlugin.swift deleted file mode 100644 index 2ffe4f42bf..0000000000 --- a/filesystem/ios/Sources/FilesystemPlugin/FilesystemPlugin.swift +++ /dev/null @@ -1,390 +0,0 @@ -import Foundation -import Capacitor - -/** - * Please read the Capacitor iOS Plugin Development Guide - * here: https://capacitorjs.com/docs/plugins/ios - */ -@objc(FilesystemPlugin) -public class FilesystemPlugin: CAPPlugin, CAPBridgedPlugin { - public let identifier = "FilesystemPlugin" - public let jsName = "Filesystem" - public let pluginMethods: [CAPPluginMethod] = [ - CAPPluginMethod(name: "readFile", returnType: CAPPluginReturnPromise), - CAPPluginMethod(name: "writeFile", returnType: CAPPluginReturnPromise), - CAPPluginMethod(name: "appendFile", returnType: CAPPluginReturnPromise), - CAPPluginMethod(name: "deleteFile", returnType: CAPPluginReturnPromise), - CAPPluginMethod(name: "mkdir", returnType: CAPPluginReturnPromise), - CAPPluginMethod(name: "rmdir", returnType: CAPPluginReturnPromise), - CAPPluginMethod(name: "readdir", returnType: CAPPluginReturnPromise), - CAPPluginMethod(name: "getUri", returnType: CAPPluginReturnPromise), - CAPPluginMethod(name: "stat", returnType: CAPPluginReturnPromise), - CAPPluginMethod(name: "rename", returnType: CAPPluginReturnPromise), - CAPPluginMethod(name: "copy", returnType: CAPPluginReturnPromise), - CAPPluginMethod(name: "checkPermissions", returnType: CAPPluginReturnPromise), - CAPPluginMethod(name: "requestPermissions", returnType: CAPPluginReturnPromise), - CAPPluginMethod(name: "downloadFile", returnType: CAPPluginReturnPromise) - ] - private let implementation = Filesystem() - - /** - * Read a file from the filesystem. - */ - @objc func readFile(_ call: CAPPluginCall) { - let encoding = call.getString("encoding") - - guard let file = call.getString("path") else { - handleError(call, "path must be provided and must be a string.") - return - } - let directory = call.getString("directory") - - guard let fileUrl = implementation.getFileUrl(at: file, in: directory) else { - handleError(call, "Invalid path") - return - } - do { - let data = try implementation.readFile(at: fileUrl, with: encoding) - call.resolve([ - "data": data - ]) - } catch let error as NSError { - handleError(call, error.localizedDescription, error) - } - } - - /** - * Write a file to the filesystem. - */ - @objc func writeFile(_ call: CAPPluginCall) { - let encoding = call.getString("encoding") - let recursive = call.getBool("recursive") ?? false - - guard let file = call.getString("path") else { - handleError(call, "path must be provided and must be a string.") - return - } - - guard let data = call.getString("data") else { - handleError(call, "Data must be provided and must be a string.") - return - } - - let directory = call.getString("directory") - - guard let fileUrl = implementation.getFileUrl(at: file, in: directory) else { - handleError(call, "Invalid path") - return - } - - do { - let path = try implementation.writeFile(at: fileUrl, with: data, recursive: recursive, with: encoding) - call.resolve([ - "uri": path - ]) - } catch let error as NSError { - handleError(call, error.localizedDescription, error) - } - } - - /** - * Append to a file. - */ - @objc func appendFile(_ call: CAPPluginCall) { - let encoding = call.getString("encoding") - - guard let file = call.getString("path") else { - handleError(call, "path must be provided and must be a string.") - return - } - - guard let data = call.getString("data") else { - handleError(call, "Data must be provided and must be a string.") - return - } - - let directory = call.getString("directory") - guard let fileUrl = implementation.getFileUrl(at: file, in: directory) else { - handleError(call, "Invalid path") - return - } - - do { - try implementation.appendFile(at: fileUrl, with: data, recursive: false, with: encoding) - call.resolve() - } catch let error as NSError { - handleError(call, error.localizedDescription, error) - } - } - - /** - * Delete a file. - */ - @objc func deleteFile(_ call: CAPPluginCall) { - guard let file = call.getString("path") else { - handleError(call, "path must be provided and must be a string.") - return - } - - let directory = call.getString("directory") - guard let fileUrl = implementation.getFileUrl(at: file, in: directory) else { - handleError(call, "Invalid path") - return - } - - do { - try implementation.deleteFile(at: fileUrl) - call.resolve() - } catch let error as NSError { - handleError(call, error.localizedDescription, error) - } - } - - /** - * Make a new directory, optionally creating parent folders first. - */ - @objc func mkdir(_ call: CAPPluginCall) { - guard let path = call.getString("path") else { - handleError(call, "path must be provided and must be a string.") - return - } - - let recursive = call.getBool("recursive") ?? false - let directory = call.getString("directory") - guard let fileUrl = implementation.getFileUrl(at: path, in: directory) else { - handleError(call, "Invalid path") - return - } - - do { - try implementation.mkdir(at: fileUrl, recursive: recursive) - call.resolve() - } catch let error as NSError { - handleError(call, error.localizedDescription, error) - } - } - - /** - * Remove a directory. - */ - @objc func rmdir(_ call: CAPPluginCall) { - guard let path = call.getString("path") else { - handleError(call, "path must be provided and must be a string.") - return - } - - let directory = call.getString("directory") - guard let fileUrl = implementation.getFileUrl(at: path, in: directory) else { - handleError(call, "Invalid path") - return - } - - let recursive = call.getBool("recursive") ?? false - - do { - try implementation.rmdir(at: fileUrl, recursive: recursive) - call.resolve() - } catch let error as NSError { - handleError(call, error.localizedDescription, error) - } - } - - /** - * Read the contents of a directory. - */ - @objc func readdir(_ call: CAPPluginCall) { - guard let path = call.getString("path") else { - handleError(call, "path must be provided and must be a string.") - return - } - - let directory = call.getString("directory") - guard let fileUrl = implementation.getFileUrl(at: path, in: directory) else { - handleError(call, "Invalid path") - return - } - - do { - let directoryContents = try implementation.readdir(at: fileUrl) - let directoryContent = try directoryContents.map {(url: URL) -> [String: Any] in - let attr = try implementation.stat(at: url) - var ctime: UInt64 = 0 - var mtime: UInt64 = 0 - - if let ctimeSeconds = (attr[.creationDate] as? Date)?.timeIntervalSince1970 { - ctime = UInt64((ctimeSeconds * 1000).rounded()) - } - - if let mtimeSeconds = (attr[.modificationDate] as? Date)?.timeIntervalSince1970 { - mtime = UInt64((mtimeSeconds * 1000).rounded()) - } - return [ - "name": url.lastPathComponent, - "type": implementation.getType(from: attr), - "size": attr[.size] as? UInt64 ?? 0, - "ctime": ctime, - "mtime": mtime, - "uri": url.absoluteString - ] - } - call.resolve([ - "files": directoryContent - ]) - } catch { - handleError(call, error.localizedDescription, error) - } - } - - @objc func stat(_ call: CAPPluginCall) { - guard let path = call.getString("path") else { - handleError(call, "path must be provided and must be a string.") - return - } - - let directory = call.getString("directory") - guard let fileUrl = implementation.getFileUrl(at: path, in: directory) else { - handleError(call, "Invalid path") - return - } - - do { - let attr = try implementation.stat(at: fileUrl) - - var ctime: UInt64 = 0 - var mtime: UInt64 = 0 - - if let ctimeSeconds = (attr[.creationDate] as? Date)?.timeIntervalSince1970 { - ctime = UInt64((ctimeSeconds * 1000).rounded()) - } - - if let mtimeSeconds = (attr[.modificationDate] as? Date)?.timeIntervalSince1970 { - mtime = UInt64((mtimeSeconds * 1000).rounded()) - } - - call.resolve([ - "type": implementation.getType(from: attr), - "size": attr[.size] as? UInt64 ?? 0, - "ctime": ctime, - "mtime": mtime, - "uri": fileUrl.absoluteString - ]) - } catch { - handleError(call, error.localizedDescription, error) - } - } - - @objc func getUri(_ call: CAPPluginCall) { - guard let path = call.getString("path") else { - handleError(call, "path must be provided and must be a string.") - return - } - - let directory = call.getString("directory") - guard let fileUrl = implementation.getFileUrl(at: path, in: directory) else { - handleError(call, "Invalid path") - return - } - - call.resolve([ - "uri": fileUrl.absoluteString - ]) - - } - - /** - * Rename a file or directory. - */ - @objc func rename(_ call: CAPPluginCall) { - guard let from = call.getString("from"), let to = call.getString("to") else { - handleError(call, "Both to and from must be provided") - return - } - - let directory = call.getString("directory") - let toDirectory = call.getString("toDirectory") ?? directory - - guard let fromUrl = implementation.getFileUrl(at: from, in: directory) else { - handleError(call, "Invalid from path") - return - } - - guard let toUrl = implementation.getFileUrl(at: to, in: toDirectory) else { - handleError(call, "Invalid to path") - return - } - do { - try implementation.rename(at: fromUrl, to: toUrl) - call.resolve() - } catch let error as NSError { - handleError(call, error.localizedDescription, error) - } - } - - /** - * Copy a file or directory. - */ - @objc func copy(_ call: CAPPluginCall) { - guard let from = call.getString("from"), let to = call.getString("to") else { - handleError(call, "Both to and from must be provided") - return - } - - let directory = call.getString("directory") - let toDirectory = call.getString("toDirectory") ?? directory - - guard let fromUrl = implementation.getFileUrl(at: from, in: directory) else { - handleError(call, "Invalid from path") - return - } - - guard let toUrl = implementation.getFileUrl(at: to, in: toDirectory) else { - handleError(call, "Invalid to path") - return - } - do { - try implementation.copy(at: fromUrl, to: toUrl) - call.resolve([ - "uri": toUrl.absoluteString - ]) - } catch let error as NSError { - handleError(call, error.localizedDescription, error) - } - } - - @objc override public func checkPermissions(_ call: CAPPluginCall) { - call.resolve([ - "publicStorage": "granted" - ]) - } - - @objc override public func requestPermissions(_ call: CAPPluginCall) { - call.resolve([ - "publicStorage": "granted" - ]) - } - - @objc func downloadFile(_ call: CAPPluginCall) { - guard let url = call.getString("url") else { return call.reject("Must provide a URL") } - let progressEmitter: Filesystem.ProgressEmitter = { bytes, contentLength in - self.notifyListeners("progress", data: [ - "url": url, - "bytes": bytes, - "contentLength": contentLength - ]) - } - - do { - try implementation.downloadFile(call: call, emitter: progressEmitter, config: bridge?.config) - } catch let error { - call.reject(error.localizedDescription) - } - } - - /** - * Helper for handling errors - */ - private func handleError(_ call: CAPPluginCall, _ message: String, _ error: Error? = nil) { - call.reject(message, nil, error) - } - -} diff --git a/filesystem/ios/Tests/FilesystemPluginTests/FilesystemPluginTests.swift b/filesystem/ios/Tests/FilesystemPluginTests/FilesystemPluginTests.swift deleted file mode 100644 index a625af9580..0000000000 --- a/filesystem/ios/Tests/FilesystemPluginTests/FilesystemPluginTests.swift +++ /dev/null @@ -1,12 +0,0 @@ -import XCTest -@testable import FilesystemPlugin - -final class FilesystemPluginTests: 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/filesystem/package.json b/filesystem/package.json deleted file mode 100644 index e8cbd4646e..0000000000 --- a/filesystem/package.json +++ /dev/null @@ -1,84 +0,0 @@ -{ - "name": "@capacitor/filesystem", - "version": "7.0.1", - "description": "The Filesystem API provides a NodeJS-like API for working with files on the device.", - "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", - "CapacitorFilesystem.podspec" - ], - "author": "Ionic ", - "license": "MIT", - "repository": { - "type": "git", - "url": "https://github.com/ionic-team/capacitor-plugins" - }, - "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 CapacitorFilesystem -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}\"", - "swiftlint": "node-swiftlint", - "docgen": "docgen --api FilesystemPlugin --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", - "publish:cocoapod": "pod trunk push ./CapacitorFilesystem.podspec --allow-warnings" - }, - "devDependencies": { - "@capacitor/android": "^7.0.0", - "@capacitor/core": "^7.0.0", - "@capacitor/docgen": "0.2.2", - "@capacitor/ios": "^7.0.0", - "@ionic/eslint-config": "^0.4.0", - "@ionic/prettier-config": "~1.0.1", - "@ionic/swiftlint-config": "^1.1.2", - "eslint": "^8.57.0", - "prettier": "~2.3.0", - "prettier-plugin-java": "~1.0.2", - "rimraf": "^6.0.1", - "rollup": "^4.26.0", - "swiftlint": "^1.0.1", - "typescript": "~4.1.5" - }, - "peerDependencies": { - "@capacitor/core": ">=7.0.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/filesystem/rollup.config.mjs b/filesystem/rollup.config.mjs deleted file mode 100644 index 0fd78567cf..0000000000 --- a/filesystem/rollup.config.mjs +++ /dev/null @@ -1,22 +0,0 @@ -export default { - input: 'dist/esm/index.js', - output: [ - { - file: 'dist/plugin.js', - format: 'iife', - name: 'capacitorFilesystem', - 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/filesystem/src/definitions.ts b/filesystem/src/definitions.ts deleted file mode 100644 index ca0c114ed2..0000000000 --- a/filesystem/src/definitions.ts +++ /dev/null @@ -1,739 +0,0 @@ -import type { - HttpOptions, - PermissionState, - PluginListenerHandle, -} from '@capacitor/core'; - -export interface PermissionStatus { - publicStorage: PermissionState; -} - -export enum Directory { - /** - * The Documents directory. - * On iOS it's the app's documents directory. - * Use this directory to store user-generated content. - * On Android it's the Public Documents folder, so it's accessible from other apps. - * It's not accesible on Android 10 unless the app enables legacy External Storage - * by adding `android:requestLegacyExternalStorage="true"` in the `application` tag - * in the `AndroidManifest.xml`. - * On Android 11 or newer the app can only access the files/folders the app created. - * - * @since 1.0.0 - */ - Documents = 'DOCUMENTS', - - /** - * The Data directory. - * On iOS it will use the Documents directory. - * On Android it's the directory holding application files. - * Files will be deleted when the application is uninstalled. - * - * @since 1.0.0 - */ - Data = 'DATA', - - /** - * The Library directory. - * On iOS it will use the Library directory. - * On Android it's the directory holding application files. - * Files will be deleted when the application is uninstalled. - * - * @since 1.1.0 - */ - Library = 'LIBRARY', - - /** - * The Cache directory. - * Can be deleted in cases of low memory, so use this directory to write app-specific files. - * that your app can re-create easily. - * - * @since 1.0.0 - */ - Cache = 'CACHE', - - /** - * The external directory. - * On iOS it will use the Documents directory. - * On Android it's the directory on the primary shared/external - * storage device where the application can place persistent files it owns. - * These files are internal to the applications, and not typically visible - * to the user as media. - * Files will be deleted when the application is uninstalled. - * - * @since 1.0.0 - */ - External = 'EXTERNAL', - - /** - * The external storage directory. - * On iOS it will use the Documents directory. - * On Android it's the primary shared/external storage directory. - * It's not accesible on Android 10 unless the app enables legacy External Storage - * by adding `android:requestLegacyExternalStorage="true"` in the `application` tag - * in the `AndroidManifest.xml`. - * It's not accesible on Android 11 or newer. - * - * @since 1.0.0 - */ - ExternalStorage = 'EXTERNAL_STORAGE', -} - -export enum Encoding { - /** - * Eight-bit UCS Transformation Format - * - * @since 1.0.0 - */ - UTF8 = 'utf8', - - /** - * Seven-bit ASCII, a.k.a. ISO646-US, a.k.a. the Basic Latin block of the - * Unicode character set - * This encoding is only supported on Android. - * - * @since 1.0.0 - */ - ASCII = 'ascii', - - /** - * Sixteen-bit UCS Transformation Format, byte order identified by an - * optional byte-order mark - * This encoding is only supported on Android. - * - * @since 1.0.0 - */ - UTF16 = 'utf16', -} - -export interface WriteFileOptions { - /** - * The path of the file to write - * - * @since 1.0.0 - */ - path: string; - - /** - * The data to write - * - * Note: Blob data is only supported on Web. - * - * @since 1.0.0 - */ - data: string | Blob; - - /** - * The `Directory` to store the file in - * - * @since 1.0.0 - */ - directory?: Directory; - - /** - * The encoding to write the file in. If not provided, data - * is written as base64 encoded. - * - * Pass Encoding.UTF8 to write data as string - * - * @since 1.0.0 - */ - encoding?: Encoding; - - /** - * Whether to create any missing parent directories. - * - * @default false - * @since 1.0.0 - */ - recursive?: boolean; -} - -export interface AppendFileOptions { - /** - * The path of the file to append - * - * @since 1.0.0 - */ - path: string; - - /** - * The data to write - * - * @since 1.0.0 - */ - data: string; - - /** - * The `Directory` to store the file in - * - * @since 1.0.0 - */ - directory?: Directory; - - /** - * The encoding to write the file in. If not provided, data - * is written as base64 encoded. - * - * Pass Encoding.UTF8 to write data as string - * - * @since 1.0.0 - */ - encoding?: Encoding; -} - -export interface ReadFileOptions { - /** - * The path of the file to read - * - * @since 1.0.0 - */ - path: string; - - /** - * The `Directory` to read the file from - * - * @since 1.0.0 - */ - directory?: Directory; - - /** - * The encoding to read the file in, if not provided, data - * is read as binary and returned as base64 encoded. - * - * Pass Encoding.UTF8 to read data as string - * - * @since 1.0.0 - */ - encoding?: Encoding; -} - -export interface DeleteFileOptions { - /** - * The path of the file to delete - * - * @since 1.0.0 - */ - path: string; - - /** - * The `Directory` to delete the file from - * - * @since 1.0.0 - */ - directory?: Directory; -} - -export interface MkdirOptions { - /** - * The path of the new directory - * - * @since 1.0.0 - */ - path: string; - - /** - * The `Directory` to make the new directory in - * - * @since 1.0.0 - */ - directory?: Directory; - - /** - * Whether to create any missing parent directories as well. - * - * @default false - * @since 1.0.0 - */ - recursive?: boolean; -} - -export interface RmdirOptions { - /** - * The path of the directory to remove - * - * @since 1.0.0 - */ - path: string; - - /** - * The `Directory` to remove the directory from - * - * @since 1.0.0 - */ - directory?: Directory; - - /** - * Whether to recursively remove the contents of the directory - * - * @default false - * @since 1.0.0 - */ - recursive?: boolean; -} - -export interface ReaddirOptions { - /** - * The path of the directory to read - * - * @since 1.0.0 - */ - path: string; - - /** - * The `Directory` to list files from - * - * @since 1.0.0 - */ - directory?: Directory; -} - -export interface GetUriOptions { - /** - * The path of the file to get the URI for - * - * @since 1.0.0 - */ - path: string; - - /** - * The `Directory` to get the file under - * - * @since 1.0.0 - */ - directory: Directory; -} - -export interface StatOptions { - /** - * The path of the file to get data about - * - * @since 1.0.0 - */ - path: string; - - /** - * The `Directory` to get the file under - * - * @since 1.0.0 - */ - directory?: Directory; -} - -export interface CopyOptions { - /** - * The existing file or directory - * - * @since 1.0.0 - */ - from: string; - - /** - * The destination file or directory - * - * @since 1.0.0 - */ - to: string; - - /** - * The `Directory` containing the existing file or directory - * - * @since 1.0.0 - */ - directory?: Directory; - - /** - * The `Directory` containing the destination file or directory. If not supplied will use the 'directory' - * parameter as the destination - * - * @since 1.0.0 - */ - toDirectory?: Directory; -} - -export type RenameOptions = CopyOptions; - -export interface ReadFileResult { - /** - * The representation of the data contained in the file - * - * Note: Blob is only available on Web. On native, the data is returned as a string. - * - * @since 1.0.0 - */ - data: string | Blob; -} - -export interface WriteFileResult { - /** - * The uri where the file was written into - * - * @since 1.0.0 - */ - uri: string; -} - -export interface ReaddirResult { - /** - * List of files and directories inside the directory - * - * @since 1.0.0 - */ - files: FileInfo[]; -} - -export interface FileInfo { - /** - * Name of the file or directory. - */ - name: string; - /** - * Type of the file. - * - * @since 4.0.0 - */ - type: 'directory' | 'file'; - - /** - * Size of the file in bytes. - * - * @since 4.0.0 - */ - size: number; - - /** - * Time of creation in milliseconds. - * - * It's not available on Android 7 and older devices. - * - * @since 4.0.0 - */ - ctime?: number; - - /** - * Time of last modification in milliseconds. - * - * @since 4.0.0 - */ - mtime: number; - - /** - * The uri of the file. - * - * @since 4.0.0 - */ - uri: string; -} - -export interface GetUriResult { - /** - * The uri of the file - * - * @since 1.0.0 - */ - uri: string; -} - -export interface StatResult { - /** - * Type of the file. - * - * @since 1.0.0 - */ - type: 'directory' | 'file'; - - /** - * Size of the file in bytes. - * - * @since 1.0.0 - */ - size: number; - - /** - * Time of creation in milliseconds. - * - * It's not available on Android 7 and older devices. - * - * @since 1.0.0 - */ - ctime?: number; - - /** - * Time of last modification in milliseconds. - * - * @since 1.0.0 - */ - mtime: number; - - /** - * The uri of the file - * - * @since 1.0.0 - */ - uri: string; -} - -export interface CopyResult { - /** - * The uri where the file was copied into - * - * @since 4.0.0 - */ - uri: string; -} - -export interface DownloadFileOptions extends HttpOptions { - /** - * The path the downloaded file should be moved to. - * - * @deprecated Use @capacitor/file-transfer instead. - * @since 5.1.0 - */ - path: string; - /** - * The directory to write the file to. - * If this option is used, filePath can be a relative path rather than absolute. - * The default is the `DATA` directory. - * - * @deprecated Use @capacitor/file-transfer instead. - * @since 5.1.0 - */ - directory?: Directory; - /** - * An optional listener function to receive downloaded progress events. - * If this option is used, progress event should be dispatched on every chunk received. - * Chunks are throttled to every 100ms on Android/iOS to avoid slowdowns. - * - * @deprecated Use @capacitor/file-transfer instead. - * @since 5.1.0 - */ - progress?: boolean; - /** - * Whether to create any missing parent directories. - * - * @default false - * @deprecated Use @capacitor/file-transfer instead. - * @since 5.1.2 - */ - recursive?: boolean; -} - -export interface DownloadFileResult { - /** - * The path the file was downloaded to. - * - * @deprecated Use @capacitor/file-transfer instead. - * @since 5.1.0 - */ - path?: string; - /** - * The blob data of the downloaded file. - * This is only available on web. - * - * @deprecated Use @capacitor/file-transfer instead. - * @since 5.1.0 - */ - blob?: Blob; -} -export interface ProgressStatus { - /** - * The url of the file being downloaded. - * - * @deprecated Use @capacitor/file-transfer instead. - * @since 5.1.0 - */ - url: string; - /** - * The number of bytes downloaded so far. - * - * @deprecated Use @capacitor/file-transfer instead. - * @since 5.1.0 - */ - bytes: number; - /** - * The total number of bytes to download for this file. - * - * @deprecated Use @capacitor/file-transfer instead. - * @since 5.1.0 - */ - contentLength: number; -} - -/** - * A listener function that receives progress events. - * - * @deprecated Use @capacitor/file-transfer instead. - * @since 5.1.0 - */ -export type ProgressListener = (progress: ProgressStatus) => void; - -export interface FilesystemPlugin { - /** - * Read a file from disk - * - * @since 1.0.0 - */ - readFile(options: ReadFileOptions): Promise; - - /** - * Write a file to disk in the specified location on device - * - * @since 1.0.0 - */ - writeFile(options: WriteFileOptions): Promise; - - /** - * Append to a file on disk in the specified location on device - * - * @since 1.0.0 - */ - appendFile(options: AppendFileOptions): Promise; - - /** - * Delete a file from disk - * - * @since 1.0.0 - */ - deleteFile(options: DeleteFileOptions): Promise; - - /** - * Create a directory. - * - * @since 1.0.0 - */ - mkdir(options: MkdirOptions): Promise; - - /** - * Remove a directory - * - * @since 1.0.0 - */ - rmdir(options: RmdirOptions): Promise; - - /** - * Return a list of files from the directory (not recursive) - * - * @since 1.0.0 - */ - readdir(options: ReaddirOptions): Promise; - - /** - * Return full File URI for a path and directory - * - * @since 1.0.0 - */ - getUri(options: GetUriOptions): Promise; - - /** - * Return data about a file - * - * @since 1.0.0 - */ - stat(options: StatOptions): Promise; - - /** - * Rename a file or directory - * - * @since 1.0.0 - */ - rename(options: RenameOptions): Promise; - - /** - * Copy a file or directory - * - * @since 1.0.0 - */ - copy(options: CopyOptions): Promise; - - /** - * Check read/write permissions. - * Required on Android, only when using `Directory.Documents` or - * `Directory.ExternalStorage`. - * - * @since 1.0.0 - */ - checkPermissions(): Promise; - - /** - * Request read/write permissions. - * Required on Android, only when using `Directory.Documents` or - * `Directory.ExternalStorage`. - * - * @since 1.0.0 - */ - requestPermissions(): Promise; - - /** - * Perform a http request to a server and download the file to the specified destination. - * - * @deprecated Use @capacitor/file-transfer plugin instead. - * @since 5.1.0 - */ - downloadFile(options: DownloadFileOptions): Promise; - - /** - * Add a listener to file download progress events. - * - * @deprecated Use @capacitor/file-transfer plugin instead. - * @since 5.1.0 - */ - addListener( - eventName: 'progress', - listenerFunc: ProgressListener, - ): Promise; - /** - * Remove all listeners for this plugin. - * - * @deprecated Use @capacitor/file-transfer plugin instead. - * @since 5.2.0 - */ - removeAllListeners(): Promise; -} - -/** - * @deprecated Use `ReadFileOptions`. - * @since 1.0.0 - */ -export type FileReadOptions = ReadFileOptions; - -/** - * @deprecated Use `ReadFileResult`. - * @since 1.0.0 - */ -export type FileReadResult = ReadFileResult; - -/** - * @deprecated Use `WriteFileOptions`. - * @since 1.0.0 - */ -export type FileWriteOptions = WriteFileOptions; - -/** - * @deprecated Use `WriteFileResult`. - * @since 1.0.0 - */ -export type FileWriteResult = WriteFileResult; - -/** - * @deprecated Use `AppendFileOptions`. - * @since 1.0.0 - */ -export type FileAppendOptions = AppendFileOptions; - -/** - * @deprecated Use `DeleteFileOptions`. - * @since 1.0.0 - */ -export type FileDeleteOptions = DeleteFileOptions; - -/** - * @deprecated Use `Directory`. - * @since 1.0.0 - */ -export const FilesystemDirectory = Directory; - -/** - * @deprecated Use `Encoding`. - * @since 1.0.0 - */ -export const FilesystemEncoding = Encoding; diff --git a/filesystem/src/index.ts b/filesystem/src/index.ts deleted file mode 100644 index e229f1cb19..0000000000 --- a/filesystem/src/index.ts +++ /dev/null @@ -1,10 +0,0 @@ -import { registerPlugin } from '@capacitor/core'; - -import type { FilesystemPlugin } from './definitions'; - -const Filesystem = registerPlugin('Filesystem', { - web: () => import('./web').then(m => new m.FilesystemWeb()), -}); - -export * from './definitions'; -export { Filesystem }; diff --git a/filesystem/src/web.ts b/filesystem/src/web.ts deleted file mode 100644 index 4712fb5071..0000000000 --- a/filesystem/src/web.ts +++ /dev/null @@ -1,738 +0,0 @@ -import { WebPlugin, buildRequestInit } from '@capacitor/core'; - -import type { - AppendFileOptions, - CopyOptions, - CopyResult, - DeleteFileOptions, - FilesystemPlugin, - GetUriOptions, - GetUriResult, - MkdirOptions, - PermissionStatus, - ReadFileOptions, - ReadFileResult, - ReaddirOptions, - ReaddirResult, - RenameOptions, - RmdirOptions, - StatOptions, - StatResult, - WriteFileOptions, - WriteFileResult, - Directory, - DownloadFileOptions, - DownloadFileResult, - ProgressStatus, -} from './definitions'; -import { Encoding } from './definitions'; - -function resolve(path: string): string { - const posix = path.split('/').filter(item => item !== '.'); - const newPosix: string[] = []; - - posix.forEach(item => { - if ( - item === '..' && - newPosix.length > 0 && - newPosix[newPosix.length - 1] !== '..' - ) { - newPosix.pop(); - } else { - newPosix.push(item); - } - }); - - return newPosix.join('/'); -} -function isPathParent(parent: string, children: string): boolean { - parent = resolve(parent); - children = resolve(children); - const pathsA = parent.split('/'); - const pathsB = children.split('/'); - - return ( - parent !== children && - pathsA.every((value, index) => value === pathsB[index]) - ); -} - -export class FilesystemWeb extends WebPlugin implements FilesystemPlugin { - DB_VERSION = 1; - DB_NAME = 'Disc'; - - private _writeCmds: string[] = ['add', 'put', 'delete']; - private _db?: IDBDatabase; - static _debug = true; - async initDb(): Promise { - if (this._db !== undefined) { - return this._db; - } - if (!('indexedDB' in window)) { - throw this.unavailable("This browser doesn't support IndexedDB"); - } - - return new Promise((resolve, reject) => { - const request = indexedDB.open(this.DB_NAME, this.DB_VERSION); - request.onupgradeneeded = FilesystemWeb.doUpgrade; - request.onsuccess = () => { - this._db = request.result; - resolve(request.result); - }; - request.onerror = () => reject(request.error); - request.onblocked = () => { - console.warn('db blocked'); - }; - }); - } - - static doUpgrade(event: IDBVersionChangeEvent): void { - const eventTarget = event.target as IDBOpenDBRequest; - const db = eventTarget.result; - switch (event.oldVersion) { - case 0: - case 1: - default: { - if (db.objectStoreNames.contains('FileStorage')) { - db.deleteObjectStore('FileStorage'); - } - const store = db.createObjectStore('FileStorage', { keyPath: 'path' }); - store.createIndex('by_folder', 'folder'); - } - } - } - - async dbRequest(cmd: string, args: any[]): Promise { - const readFlag = - this._writeCmds.indexOf(cmd) !== -1 ? 'readwrite' : 'readonly'; - return this.initDb().then((conn: IDBDatabase) => { - return new Promise((resolve, reject) => { - const tx: IDBTransaction = conn.transaction(['FileStorage'], readFlag); - const store: any = tx.objectStore('FileStorage'); - const req = store[cmd](...args); - req.onsuccess = () => resolve(req.result); - req.onerror = () => reject(req.error); - }); - }); - } - - async dbIndexRequest( - indexName: string, - cmd: string, - args: [any], - ): Promise { - const readFlag = - this._writeCmds.indexOf(cmd) !== -1 ? 'readwrite' : 'readonly'; - return this.initDb().then((conn: IDBDatabase) => { - return new Promise((resolve, reject) => { - const tx: IDBTransaction = conn.transaction(['FileStorage'], readFlag); - const store: IDBObjectStore = tx.objectStore('FileStorage'); - const index: any = store.index(indexName); - const req = index[cmd](...args) as any; - req.onsuccess = () => resolve(req.result); - req.onerror = () => reject(req.error); - }); - }); - } - - private getPath( - directory: Directory | undefined, - uriPath: string | undefined, - ): string { - const cleanedUriPath = - uriPath !== undefined ? uriPath.replace(/^[/]+|[/]+$/g, '') : ''; - let fsPath = ''; - if (directory !== undefined) fsPath += '/' + directory; - if (uriPath !== '') fsPath += '/' + cleanedUriPath; - return fsPath; - } - - async clear(): Promise { - const conn: IDBDatabase = await this.initDb(); - const tx: IDBTransaction = conn.transaction(['FileStorage'], 'readwrite'); - const store: IDBObjectStore = tx.objectStore('FileStorage'); - store.clear(); - } - - /** - * Read a file from disk - * @param options options for the file read - * @return a promise that resolves with the read file data result - */ - async readFile(options: ReadFileOptions): Promise { - const path: string = this.getPath(options.directory, options.path); - // const encoding = options.encoding; - - const entry = (await this.dbRequest('get', [path])) as EntryObj; - if (entry === undefined) throw Error('File does not exist.'); - return { data: entry.content ? entry.content : '' }; - } - - /** - * Write a file to disk in the specified location on device - * @param options options for the file write - * @return a promise that resolves with the file write result - */ - async writeFile(options: WriteFileOptions): Promise { - const path: string = this.getPath(options.directory, options.path); - let data = options.data; - const encoding = options.encoding; - const doRecursive = options.recursive; - - const occupiedEntry = (await this.dbRequest('get', [path])) as EntryObj; - if (occupiedEntry && occupiedEntry.type === 'directory') - throw Error('The supplied path is a directory.'); - - const parentPath = path.substr(0, path.lastIndexOf('/')); - - const parentEntry = (await this.dbRequest('get', [parentPath])) as EntryObj; - if (parentEntry === undefined) { - const subDirIndex = parentPath.indexOf('/', 1); - if (subDirIndex !== -1) { - const parentArgPath = parentPath.substr(subDirIndex); - await this.mkdir({ - path: parentArgPath, - directory: options.directory, - recursive: doRecursive, - }); - } - } - - if (!encoding && !(data instanceof Blob)) { - data = data.indexOf(',') >= 0 ? data.split(',')[1] : data; - if (!this.isBase64String(data)) - throw Error('The supplied data is not valid base64 content.'); - } - - const now = Date.now(); - const pathObj: EntryObj = { - path: path, - folder: parentPath, - type: 'file', - size: data instanceof Blob ? data.size : data.length, - ctime: now, - mtime: now, - content: data, - }; - await this.dbRequest('put', [pathObj]); - return { - uri: pathObj.path, - }; - } - - /** - * Append to a file on disk in the specified location on device - * @param options options for the file append - * @return a promise that resolves with the file write result - */ - async appendFile(options: AppendFileOptions): Promise { - const path: string = this.getPath(options.directory, options.path); - let data = options.data; - const encoding = options.encoding; - const parentPath = path.substr(0, path.lastIndexOf('/')); - - const now = Date.now(); - let ctime = now; - - const occupiedEntry = (await this.dbRequest('get', [path])) as EntryObj; - if (occupiedEntry && occupiedEntry.type === 'directory') - throw Error('The supplied path is a directory.'); - - const parentEntry = (await this.dbRequest('get', [parentPath])) as EntryObj; - if (parentEntry === undefined) { - const subDirIndex = parentPath.indexOf('/', 1); - if (subDirIndex !== -1) { - const parentArgPath = parentPath.substr(subDirIndex); - await this.mkdir({ - path: parentArgPath, - directory: options.directory, - recursive: true, - }); - } - } - - if (!encoding && !this.isBase64String(data)) - throw Error('The supplied data is not valid base64 content.'); - - if (occupiedEntry !== undefined) { - if (occupiedEntry.content instanceof Blob) { - throw Error( - 'The occupied entry contains a Blob object which cannot be appended to.', - ); - } - - if (occupiedEntry.content !== undefined && !encoding) { - data = btoa(atob(occupiedEntry.content) + atob(data)); - } else { - data = occupiedEntry.content + data; - } - ctime = occupiedEntry.ctime; - } - const pathObj: EntryObj = { - path: path, - folder: parentPath, - type: 'file', - size: data.length, - ctime: ctime, - mtime: now, - content: data, - }; - await this.dbRequest('put', [pathObj]); - } - - /** - * Delete a file from disk - * @param options options for the file delete - * @return a promise that resolves with the deleted file data result - */ - async deleteFile(options: DeleteFileOptions): Promise { - const path: string = this.getPath(options.directory, options.path); - - const entry = (await this.dbRequest('get', [path])) as EntryObj; - if (entry === undefined) throw Error('File does not exist.'); - const entries = await this.dbIndexRequest('by_folder', 'getAllKeys', [ - IDBKeyRange.only(path), - ]); - if (entries.length !== 0) throw Error('Folder is not empty.'); - - await this.dbRequest('delete', [path]); - } - - /** - * Create a directory. - * @param options options for the mkdir - * @return a promise that resolves with the mkdir result - */ - async mkdir(options: MkdirOptions): Promise { - const path: string = this.getPath(options.directory, options.path); - const doRecursive = options.recursive; - const parentPath = path.substr(0, path.lastIndexOf('/')); - - const depth = (path.match(/\//g) || []).length; - const parentEntry = (await this.dbRequest('get', [parentPath])) as EntryObj; - const occupiedEntry = (await this.dbRequest('get', [path])) as EntryObj; - if (depth === 1) throw Error('Cannot create Root directory'); - if (occupiedEntry !== undefined) - throw Error('Current directory does already exist.'); - if (!doRecursive && depth !== 2 && parentEntry === undefined) - throw Error('Parent directory must exist'); - - if (doRecursive && depth !== 2 && parentEntry === undefined) { - const parentArgPath = parentPath.substr(parentPath.indexOf('/', 1)); - await this.mkdir({ - path: parentArgPath, - directory: options.directory, - recursive: doRecursive, - }); - } - const now = Date.now(); - const pathObj: EntryObj = { - path: path, - folder: parentPath, - type: 'directory', - size: 0, - ctime: now, - mtime: now, - }; - await this.dbRequest('put', [pathObj]); - } - - /** - * Remove a directory - * @param options the options for the directory remove - */ - async rmdir(options: RmdirOptions): Promise { - const { path, directory, recursive } = options; - const fullPath: string = this.getPath(directory, path); - - const entry = (await this.dbRequest('get', [fullPath])) as EntryObj; - - if (entry === undefined) throw Error('Folder does not exist.'); - - if (entry.type !== 'directory') - throw Error('Requested path is not a directory'); - - const readDirResult = await this.readdir({ path, directory }); - - if (readDirResult.files.length !== 0 && !recursive) - throw Error('Folder is not empty'); - - for (const entry of readDirResult.files) { - const entryPath = `${path}/${entry.name}`; - const entryObj = await this.stat({ path: entryPath, directory }); - if (entryObj.type === 'file') { - await this.deleteFile({ path: entryPath, directory }); - } else { - await this.rmdir({ path: entryPath, directory, recursive }); - } - } - - await this.dbRequest('delete', [fullPath]); - } - - /** - * Return a list of files from the directory (not recursive) - * @param options the options for the readdir operation - * @return a promise that resolves with the readdir directory listing result - */ - async readdir(options: ReaddirOptions): Promise { - const path: string = this.getPath(options.directory, options.path); - - const entry = (await this.dbRequest('get', [path])) as EntryObj; - if (options.path !== '' && entry === undefined) - throw Error('Folder does not exist.'); - - const entries: string[] = await this.dbIndexRequest( - 'by_folder', - 'getAllKeys', - [IDBKeyRange.only(path)], - ); - const files = await Promise.all( - entries.map(async e => { - let subEntry = (await this.dbRequest('get', [e])) as EntryObj; - if (subEntry === undefined) { - subEntry = (await this.dbRequest('get', [e + '/'])) as EntryObj; - } - return { - name: e.substring(path.length + 1), - type: subEntry.type, - size: subEntry.size, - ctime: subEntry.ctime, - mtime: subEntry.mtime, - uri: subEntry.path, - }; - }), - ); - return { files: files }; - } - - /** - * Return full File URI for a path and directory - * @param options the options for the stat operation - * @return a promise that resolves with the file stat result - */ - async getUri(options: GetUriOptions): Promise { - const path: string = this.getPath(options.directory, options.path); - - let entry = (await this.dbRequest('get', [path])) as EntryObj; - if (entry === undefined) { - entry = (await this.dbRequest('get', [path + '/'])) as EntryObj; - } - return { - uri: entry?.path || path, - }; - } - - /** - * Return data about a file - * @param options the options for the stat operation - * @return a promise that resolves with the file stat result - */ - async stat(options: StatOptions): Promise { - const path: string = this.getPath(options.directory, options.path); - - let entry = (await this.dbRequest('get', [path])) as EntryObj; - if (entry === undefined) { - entry = (await this.dbRequest('get', [path + '/'])) as EntryObj; - } - if (entry === undefined) throw Error('Entry does not exist.'); - - return { - type: entry.type, - size: entry.size, - ctime: entry.ctime, - mtime: entry.mtime, - uri: entry.path, - }; - } - - /** - * Rename a file or directory - * @param options the options for the rename operation - * @return a promise that resolves with the rename result - */ - async rename(options: RenameOptions): Promise { - await this._copy(options, true); - return; - } - - /** - * Copy a file or directory - * @param options the options for the copy operation - * @return a promise that resolves with the copy result - */ - async copy(options: CopyOptions): Promise { - return this._copy(options, false); - } - - async requestPermissions(): Promise { - return { publicStorage: 'granted' }; - } - - async checkPermissions(): Promise { - return { publicStorage: 'granted' }; - } - - /** - * Function that can perform a copy or a rename - * @param options the options for the rename operation - * @param doRename whether to perform a rename or copy operation - * @return a promise that resolves with the result - */ - private async _copy( - options: CopyOptions, - doRename = false, - ): Promise { - let { toDirectory } = options; - const { to, from, directory: fromDirectory } = options; - - if (!to || !from) { - throw Error('Both to and from must be provided'); - } - - // If no "to" directory is provided, use the "from" directory - if (!toDirectory) { - toDirectory = fromDirectory; - } - - const fromPath = this.getPath(fromDirectory, from); - const toPath = this.getPath(toDirectory, to); - - // Test that the "to" and "from" locations are different - if (fromPath === toPath) { - return { - uri: toPath, - }; - } - - if (isPathParent(fromPath, toPath)) { - throw Error('To path cannot contain the from path'); - } - - // Check the state of the "to" location - let toObj; - try { - toObj = await this.stat({ - path: to, - directory: toDirectory, - }); - } catch (e) { - // To location does not exist, ensure the directory containing "to" location exists and is a directory - const toPathComponents = to.split('/'); - toPathComponents.pop(); - const toPath = toPathComponents.join('/'); - - // Check the containing directory of the "to" location exists - if (toPathComponents.length > 0) { - const toParentDirectory = await this.stat({ - path: toPath, - directory: toDirectory, - }); - - if (toParentDirectory.type !== 'directory') { - throw new Error('Parent directory of the to path is a file'); - } - } - } - - // Cannot overwrite a directory - if (toObj && toObj.type === 'directory') { - throw new Error('Cannot overwrite a directory with a file'); - } - - // Ensure the "from" object exists - const fromObj = await this.stat({ - path: from, - directory: fromDirectory, - }); - - // Set the mtime/ctime of the supplied path - const updateTime = async (path: string, ctime: number, mtime: number) => { - const fullPath: string = this.getPath(toDirectory, path); - const entry = (await this.dbRequest('get', [fullPath])) as EntryObj; - entry.ctime = ctime; - entry.mtime = mtime; - await this.dbRequest('put', [entry]); - }; - - const ctime = fromObj.ctime ? fromObj.ctime : Date.now(); - - switch (fromObj.type) { - // The "from" object is a file - case 'file': { - // Read the file - const file = await this.readFile({ - path: from, - directory: fromDirectory, - }); - - // Optionally remove the file - if (doRename) { - await this.deleteFile({ - path: from, - directory: fromDirectory, - }); - } - - let encoding; - if (!(file.data instanceof Blob) && !this.isBase64String(file.data)) { - encoding = Encoding.UTF8; - } - - // Write the file to the new location - const writeResult = await this.writeFile({ - path: to, - directory: toDirectory, - data: file.data, - encoding: encoding, - }); - - // Copy the mtime/ctime of a renamed file - if (doRename) { - await updateTime(to, ctime, fromObj.mtime); - } - - // Resolve promise - return writeResult; - } - case 'directory': { - if (toObj) { - throw Error('Cannot move a directory over an existing object'); - } - - try { - // Create the to directory - await this.mkdir({ - path: to, - directory: toDirectory, - recursive: false, - }); - - // Copy the mtime/ctime of a renamed directory - if (doRename) { - await updateTime(to, ctime, fromObj.mtime); - } - } catch (e) { - // ignore - } - - // Iterate over the contents of the from location - const contents = ( - await this.readdir({ - path: from, - directory: fromDirectory, - }) - ).files; - - for (const filename of contents) { - // Move item from the from directory to the to directory - await this._copy( - { - from: `${from}/${filename.name}`, - to: `${to}/${filename.name}`, - directory: fromDirectory, - toDirectory, - }, - doRename, - ); - } - - // Optionally remove the original from directory - if (doRename) { - await this.rmdir({ - path: from, - directory: fromDirectory, - }); - } - } - } - return { - uri: toPath, - }; - } - - /** - * Function that performs a http request to a server and downloads the file to the specified destination - * - * @param options the options for the download operation - * @returns a promise that resolves with the download file result - */ - public downloadFile = async ( - options: DownloadFileOptions, - ): Promise => { - const requestInit = buildRequestInit(options, options.webFetchExtra); - const response = await fetch(options.url, requestInit); - let blob: Blob; - - if (!options.progress) blob = await response.blob(); - else if (!response?.body) blob = new Blob(); - else { - const reader = response.body.getReader(); - - let bytes = 0; - const chunks: (Uint8Array | undefined)[] = []; - - const contentType: string | null = response.headers.get('content-type'); - const contentLength: number = parseInt( - response.headers.get('content-length') || '0', - 10, - ); - - while (true) { - const { done, value } = await reader.read(); - - if (done) break; - - chunks.push(value); - bytes += value?.length || 0; - - const status: ProgressStatus = { - url: options.url, - bytes, - contentLength, - }; - - this.notifyListeners('progress', status); - } - - const allChunks = new Uint8Array(bytes); - let position = 0; - for (const chunk of chunks) { - if (typeof chunk === 'undefined') continue; - - allChunks.set(chunk, position); - position += chunk.length; - } - - blob = new Blob([allChunks.buffer], { type: contentType || undefined }); - } - - const result = await this.writeFile({ - path: options.path, - directory: options.directory ?? undefined, - recursive: options.recursive ?? false, - data: blob, - }); - - return { path: result.uri, blob }; - }; - - private isBase64String(str: string): boolean { - try { - return btoa(atob(str)) == str; - } catch (err) { - return false; - } - } -} - -interface EntryObj { - path: string; - folder: string; - type: 'directory' | 'file'; - size: number; - ctime: number; - mtime: number; - uri?: string; - content?: string | Blob; -} diff --git a/filesystem/tsconfig.json b/filesystem/tsconfig.json deleted file mode 100644 index f2e88e6a07..0000000000 --- a/filesystem/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"] -} diff --git a/geolocation/.eslintignore b/geolocation/.eslintignore deleted file mode 100644 index 9d0b71a3c7..0000000000 --- a/geolocation/.eslintignore +++ /dev/null @@ -1,2 +0,0 @@ -build -dist diff --git a/geolocation/.gitignore b/geolocation/.gitignore deleted file mode 100644 index 6817637958..0000000000 --- a/geolocation/.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/geolocation/.prettierignore b/geolocation/.prettierignore deleted file mode 100644 index 9d0b71a3c7..0000000000 --- a/geolocation/.prettierignore +++ /dev/null @@ -1,2 +0,0 @@ -build -dist diff --git a/geolocation/CHANGELOG.md b/geolocation/CHANGELOG.md deleted file mode 100644 index 2cc4650afe..0000000000 --- a/geolocation/CHANGELOG.md +++ /dev/null @@ -1,310 +0,0 @@ -# ⓘ Plugin migrated - -**From version 7.1.0 onwards, this plugin is now hosted in a separate repository. Refer to the [updated CHANGELOG](https://github.com/ionic-team/capacitor-geolocation/blob/main/CHANGELOG.md).** - -This file remains here as a record of the plugin's history for versions older than 7.1.0. - -# Change Log - -All notable changes to this project will be documented in this file. -See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. - -# [7.0.0](https://github.com/ionic-team/capacitor-plugins/compare/@capacitor/geolocation@7.0.0-rc.0...@capacitor/geolocation@7.0.0) (2025-01-20) - -**Note:** Version bump only for package @capacitor/geolocation - -# [7.0.0-rc.0](https://github.com/ionic-team/capacitor-plugins/compare/@capacitor/geolocation@7.0.0-alpha.2...@capacitor/geolocation@7.0.0-rc.0) (2025-01-13) - -**Note:** Version bump only for package @capacitor/geolocation - -# [7.0.0-alpha.2](https://github.com/ionic-team/capacitor-plugins/compare/@capacitor/geolocation@7.0.0-alpha.1...@capacitor/geolocation@7.0.0-alpha.2) (2024-12-19) - -**Note:** Version bump only for package @capacitor/geolocation - -# [7.0.0-alpha.1](https://github.com/ionic-team/capacitor-plugins/compare/@capacitor/geolocation@6.0.1...@capacitor/geolocation@7.0.0-alpha.1) (2024-12-16) - -### Features - -- **geolocation:** add `minimumUpdateInterval` parameter for `startWatch` ([#2272](https://github.com/ionic-team/capacitor-plugins/issues/2272)) ([c6ddc53](https://github.com/ionic-team/capacitor-plugins/commit/c6ddc53efb7eb2b3fc04fc9f2dc9660c9db1a464)) - -## [6.0.1](https://github.com/ionic-team/capacitor-plugins/compare/@capacitor/geolocation@6.0.0...@capacitor/geolocation@6.0.1) (2024-08-08) - -**Note:** Version bump only for package @capacitor/geolocation - -# [6.0.0](https://github.com/ionic-team/capacitor-plugins/compare/@capacitor/geolocation@6.0.0-rc.1...@capacitor/geolocation@6.0.0) (2024-04-15) - -**Note:** Version bump only for package @capacitor/geolocation - -# [6.0.0-rc.1](https://github.com/ionic-team/capacitor-plugins/compare/@capacitor/geolocation@6.0.0-rc.0...@capacitor/geolocation@6.0.0-rc.1) (2024-03-25) - -**Note:** Version bump only for package @capacitor/geolocation - -# [6.0.0-rc.0](https://github.com/ionic-team/capacitor-plugins/compare/@capacitor/geolocation@6.0.0-beta.1...@capacitor/geolocation@6.0.0-rc.0) (2024-02-07) - -**Note:** Version bump only for package @capacitor/geolocation - -# [6.0.0-beta.1](https://github.com/ionic-team/capacitor-plugins/compare/@capacitor/geolocation@6.0.0-beta.0...@capacitor/geolocation@6.0.0-beta.1) (2023-12-14) - -**Note:** Version bump only for package @capacitor/geolocation - -# [6.0.0-beta.0](https://github.com/ionic-team/capacitor-plugins/compare/@capacitor/geolocation@6.0.0-alpha.2...@capacitor/geolocation@6.0.0-beta.0) (2023-12-13) - -**Note:** Version bump only for package @capacitor/geolocation - -# [6.0.0-alpha.2](https://github.com/ionic-team/capacitor-plugins/compare/@capacitor/geolocation@6.0.0-alpha.1...@capacitor/geolocation@6.0.0-alpha.2) (2023-11-15) - -**Note:** Version bump only for package @capacitor/geolocation - -# [6.0.0-alpha.1](https://github.com/ionic-team/capacitor-plugins/compare/@capacitor/geolocation@5.0.6...@capacitor/geolocation@6.0.0-alpha.1) (2023-11-08) - -**Note:** Version bump only for package @capacitor/geolocation - -## [5.0.6](https://github.com/ionic-team/capacitor-plugins/compare/@capacitor/geolocation@5.0.5...@capacitor/geolocation@5.0.6) (2023-07-12) - -**Note:** Version bump only for package @capacitor/geolocation - -## [5.0.5](https://github.com/ionic-team/capacitor-plugins/compare/@capacitor/geolocation@5.0.4...@capacitor/geolocation@5.0.5) (2023-06-29) - -**Note:** Version bump only for package @capacitor/geolocation - -## [5.0.4](https://github.com/ionic-team/capacitor-plugins/compare/@capacitor/geolocation@5.0.3...@capacitor/geolocation@5.0.4) (2023-06-08) - -**Note:** Version bump only for package @capacitor/geolocation - -## [5.0.3](https://github.com/ionic-team/capacitor-plugins/compare/@capacitor/geolocation@5.0.2...@capacitor/geolocation@5.0.3) (2023-06-08) - -**Note:** Version bump only for package @capacitor/geolocation - -## [5.0.2](https://github.com/ionic-team/capacitor-plugins/compare/@capacitor/geolocation@5.0.1...@capacitor/geolocation@5.0.2) (2023-05-09) - -**Note:** Version bump only for package @capacitor/geolocation - -## [5.0.1](https://github.com/ionic-team/capacitor-plugins/compare/@capacitor/geolocation@5.0.0...@capacitor/geolocation@5.0.1) (2023-05-05) - -### Bug Fixes - -- **android:** add appCompat libraries for maven releases ([#1577](https://github.com/ionic-team/capacitor-plugins/issues/1577)) ([8a2e0ea](https://github.com/ionic-team/capacitor-plugins/commit/8a2e0ea96538a46bde299a864dba760c6e2eba68)) -- 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/geolocation@5.0.0-beta.1...@capacitor/geolocation@5.0.0) (2023-05-03) - -**Note:** Version bump only for package @capacitor/geolocation - -# [5.0.0-beta.1](https://github.com/ionic-team/capacitor-plugins/compare/@capacitor/geolocation@5.0.0-beta.0...@capacitor/geolocation@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/geolocation@5.0.0-alpha.1...@capacitor/geolocation@5.0.0-beta.0) (2023-03-31) - -**Note:** Version bump only for package @capacitor/geolocation - -# [5.0.0-alpha.1](https://github.com/ionic-team/capacitor-plugins/compare/@capacitor/geolocation@4.1.0...@capacitor/geolocation@5.0.0-alpha.1) (2023-03-16) - -### Bug Fixes - -- **geolocation:** use LocationRequest builder instead of deprecated create ([#1483](https://github.com/ionic-team/capacitor-plugins/issues/1483)) ([7cfa12c](https://github.com/ionic-team/capacitor-plugins/commit/7cfa12c86807bd7434dbf907eb878f6796109fe9)) - -### Features - -- **android:** Removing enableJetifier ([d66f9cb](https://github.com/ionic-team/capacitor-plugins/commit/d66f9cbd9da7e3b1d8c64ca6a5b45156867d4a04)) - -# [4.1.0](https://github.com/ionic-team/capacitor-plugins/compare/@capacitor/geolocation@1.3.1...@capacitor/geolocation@4.1.0) (2022-11-16) - -## 4.0.1 (2022-07-28) - -# 4.0.0 (2022-07-27) - -# 4.0.0-beta.2 (2022-07-08) - -# 4.0.0-beta.0 (2022-06-27) - -### Bug Fixes - -- **geolocation:** reject checkPermissions / requestPermissions if location services are disabled ([#1053](https://github.com/ionic-team/capacitor-plugins/issues/1053)) ([774ec6e](https://github.com/ionic-team/capacitor-plugins/commit/774ec6e941193b1b06d07d31e6672340de532385)) -- **geolocation:** stop location requests on pause ([#1018](https://github.com/ionic-team/capacitor-plugins/issues/1018)) ([eb24f25](https://github.com/ionic-team/capacitor-plugins/commit/eb24f2521d05dd25a2087a1de2b3e0644568cda0)) - -### 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)) - -## [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/geolocation - -# [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/geolocation - -# [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/geolocation - -# 4.0.0-beta.0 (2022-06-27) - -### Bug Fixes - -- **geolocation:** reject checkPermissions / requestPermissions if location services are disabled ([#1053](https://github.com/ionic-team/capacitor-plugins/issues/1053)) ([774ec6e](https://github.com/ionic-team/capacitor-plugins/commit/774ec6e941193b1b06d07d31e6672340de532385)) -- **geolocation:** stop location requests on pause ([#1018](https://github.com/ionic-team/capacitor-plugins/issues/1018)) ([eb24f25](https://github.com/ionic-team/capacitor-plugins/commit/eb24f2521d05dd25a2087a1de2b3e0644568cda0)) -- 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)) -- 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)) -- **geolocation:** Make getCurrentPosition return only once ([#470](https://github.com/ionic-team/capacitor-plugins/issues/470)) ([c5f1ceb](https://github.com/ionic-team/capacitor-plugins/commit/c5f1ceb790910b92e3f64d0b7fa8c85d48ea9841)) -- **geolocation:** Replace deprecated call.save with new keepAlive API ([#375](https://github.com/ionic-team/capacitor-plugins/issues/375)) ([e4e7cf4](https://github.com/ionic-team/capacitor-plugins/commit/e4e7cf4afd4a70bf48359c625fa7a548211876d5)) -- **geolocation:** return cached location if newer than maximumAge ([#639](https://github.com/ionic-team/capacitor-plugins/issues/639)) ([7b08eea](https://github.com/ionic-team/capacitor-plugins/commit/7b08eea9729bbf2b2b6b881cc81389cf108b3a2c)) -- **geolocation:** Use the new APIs for handling/saving calls ([#374](https://github.com/ionic-team/capacitor-plugins/issues/374)) ([ebd5b52](https://github.com/ionic-team/capacitor-plugins/commit/ebd5b527cb7f8b6c0016e82d03a0e84287913d3e)) -- 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)) -- **geolocation:** Add new alias for coarse location ([#684](https://github.com/ionic-team/capacitor-plugins/issues/684)) ([7563040](https://github.com/ionic-team/capacitor-plugins/commit/7563040983ad397e28616246e7ed5ffce69727c2)) -- **geolocation:** Error if Google Play Services are not available ([#709](https://github.com/ionic-team/capacitor-plugins/issues/709)) ([fc79c43](https://github.com/ionic-team/capacitor-plugins/commit/fc79c4319c54cbcd5dbbb7221dfdd03d0515805b)) -- **geolocation:** Throw error if location is disabled ([#589](https://github.com/ionic-team/capacitor-plugins/issues/589)) ([14724c5](https://github.com/ionic-team/capacitor-plugins/commit/14724c5ec5b23bf94f6f3511bbe204482768d10f)) -- add commonjs output format ([#179](https://github.com/ionic-team/capacitor-plugins/issues/179)) ([8e9e098](https://github.com/ionic-team/capacitor-plugins/commit/8e9e09862064b3f6771d7facbc4008e995d9b463)) -- Geolocation plugin ([#13](https://github.com/ionic-team/capacitor-plugins/issues/13)) ([911ae71](https://github.com/ionic-team/capacitor-plugins/commit/911ae71e6aef4cfa9fb3ab5b0c13f3c06ef6b15c)) - -## [1.3.1](https://github.com/ionic-team/capacitor-plugins/compare/@capacitor/geolocation@1.3.0...@capacitor/geolocation@1.3.1) (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.3.0](https://github.com/ionic-team/capacitor-plugins/compare/@capacitor/geolocation@1.2.0...@capacitor/geolocation@1.3.0) (2021-12-08) - -### Features - -- **geolocation:** Error if Google Play Services are not available ([#709](https://github.com/ionic-team/capacitor-plugins/issues/709)) ([fc79c43](https://github.com/ionic-team/capacitor-plugins/commit/fc79c4319c54cbcd5dbbb7221dfdd03d0515805b)) - -# [1.2.0](https://github.com/ionic-team/capacitor-plugins/compare/@capacitor/geolocation@1.1.3...@capacitor/geolocation@1.2.0) (2021-11-17) - -### Features - -- **geolocation:** Add new alias for coarse location ([#684](https://github.com/ionic-team/capacitor-plugins/issues/684)) ([7563040](https://github.com/ionic-team/capacitor-plugins/commit/7563040983ad397e28616246e7ed5ffce69727c2)) - -## [1.1.3](https://github.com/ionic-team/capacitor-plugins/compare/@capacitor/geolocation@1.1.2...@capacitor/geolocation@1.1.3) (2021-11-03) - -**Note:** Version bump only for package @capacitor/geolocation - -## [1.1.2](https://github.com/ionic-team/capacitor-plugins/compare/@capacitor/geolocation@1.1.1...@capacitor/geolocation@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/geolocation@1.1.0...@capacitor/geolocation@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)) -- **geolocation:** return cached location if newer than maximumAge ([#639](https://github.com/ionic-team/capacitor-plugins/issues/639)) ([7b08eea](https://github.com/ionic-team/capacitor-plugins/commit/7b08eea9729bbf2b2b6b881cc81389cf108b3a2c)) - -# [1.1.0](https://github.com/ionic-team/capacitor-plugins/compare/@capacitor/geolocation@1.0.2...@capacitor/geolocation@1.1.0) (2021-09-01) - -### Features - -- **geolocation:** Throw error if location is disabled ([#589](https://github.com/ionic-team/capacitor-plugins/issues/589)) ([14724c5](https://github.com/ionic-team/capacitor-plugins/commit/14724c5ec5b23bf94f6f3511bbe204482768d10f)) - -## [1.0.2](https://github.com/ionic-team/capacitor-plugins/compare/@capacitor/geolocation@1.0.1...@capacitor/geolocation@1.0.2) (2021-06-23) - -### Bug Fixes - -- **geolocation:** Make getCurrentPosition return only once ([#470](https://github.com/ionic-team/capacitor-plugins/issues/470)) ([c5f1ceb](https://github.com/ionic-team/capacitor-plugins/commit/c5f1ceb790910b92e3f64d0b7fa8c85d48ea9841)) - -## [1.0.1](https://github.com/ionic-team/capacitor-plugins/compare/@capacitor/geolocation@1.0.0...@capacitor/geolocation@1.0.1) (2021-06-09) - -**Note:** Version bump only for package @capacitor/geolocation - -# [1.0.0](https://github.com/ionic-team/capacitor-plugins/compare/@capacitor/geolocation@0.4.7...@capacitor/geolocation@1.0.0) (2021-05-19) - -**Note:** Version bump only for package @capacitor/geolocation - -## [0.4.7](https://github.com/ionic-team/capacitor-plugins/compare/@capacitor/geolocation@0.4.6...@capacitor/geolocation@0.4.7) (2021-05-11) - -**Note:** Version bump only for package @capacitor/geolocation - -## [0.4.6](https://github.com/ionic-team/capacitor-plugins/compare/@capacitor/geolocation@0.4.5...@capacitor/geolocation@0.4.6) (2021-05-10) - -**Note:** Version bump only for package @capacitor/geolocation - -## [0.4.5](https://github.com/ionic-team/capacitor-plugins/compare/@capacitor/geolocation@0.4.4...@capacitor/geolocation@0.4.5) (2021-05-07) - -### Bug Fixes - -- **geolocation:** Replace deprecated call.save with new keepAlive API ([#375](https://github.com/ionic-team/capacitor-plugins/issues/375)) ([e4e7cf4](https://github.com/ionic-team/capacitor-plugins/commit/e4e7cf4afd4a70bf48359c625fa7a548211876d5)) -- **geolocation:** Use the new APIs for handling/saving calls ([#374](https://github.com/ionic-team/capacitor-plugins/issues/374)) ([ebd5b52](https://github.com/ionic-team/capacitor-plugins/commit/ebd5b527cb7f8b6c0016e82d03a0e84287913d3e)) - -## [0.4.4](https://github.com/ionic-team/capacitor-plugins/compare/@capacitor/geolocation@0.4.3...@capacitor/geolocation@0.4.4) (2021-04-29) - -**Note:** Version bump only for package @capacitor/geolocation - -## [0.4.3](https://github.com/ionic-team/capacitor-plugins/compare/@capacitor/geolocation@0.4.2...@capacitor/geolocation@0.4.3) (2021-03-10) - -**Note:** Version bump only for package @capacitor/geolocation - -## [0.4.2](https://github.com/ionic-team/capacitor-plugins/compare/@capacitor/geolocation@0.4.1...@capacitor/geolocation@0.4.2) (2021-03-02) - -**Note:** Version bump only for package @capacitor/geolocation - -## [0.4.1](https://github.com/ionic-team/capacitor-plugins/compare/@capacitor/geolocation@0.4.0...@capacitor/geolocation@0.4.1) (2021-02-27) - -**Note:** Version bump only for package @capacitor/geolocation - -# [0.4.0](https://github.com/ionic-team/capacitor-plugins/compare/@capacitor/geolocation@0.3.2...@capacitor/geolocation@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/geolocation@0.3.1...@capacitor/geolocation@0.3.2) (2021-02-05) - -**Note:** Version bump only for package @capacitor/geolocation - -## [0.3.1](https://github.com/ionic-team/capacitor-plugins/compare/@capacitor/geolocation@0.3.0...@capacitor/geolocation@0.3.1) (2021-01-26) - -**Note:** Version bump only for package @capacitor/geolocation - -# [0.3.0](https://github.com/ionic-team/capacitor-plugins/compare/@capacitor/geolocation@0.2.0...@capacitor/geolocation@0.3.0) (2021-01-14) - -**Note:** Version bump only for package @capacitor/geolocation - -# [0.2.0](https://github.com/ionic-team/capacitor-plugins/compare/@capacitor/geolocation@0.1.3...@capacitor/geolocation@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.3](https://github.com/ionic-team/capacitor-plugins/compare/@capacitor/geolocation@0.1.2...@capacitor/geolocation@0.1.3) (2021-01-13) - -**Note:** Version bump only for package @capacitor/geolocation - -## [0.1.2](https://github.com/ionic-team/capacitor-plugins/compare/@capacitor/geolocation@0.1.1...@capacitor/geolocation@0.1.2) (2021-01-08) - -**Note:** Version bump only for package @capacitor/geolocation - -## [0.1.1](https://github.com/ionic-team/capacitor-plugins/compare/@capacitor/geolocation@0.1.0...@capacitor/geolocation@0.1.1) (2020-12-27) - -**Note:** Version bump only for package @capacitor/geolocation - -# 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 - -- Geolocation plugin ([#13](https://github.com/ionic-team/capacitor-plugins/issues/13)) ([911ae71](https://github.com/ionic-team/capacitor-plugins/commit/911ae71e6aef4cfa9fb3ab5b0c13f3c06ef6b15c)) diff --git a/geolocation/CapacitorGeolocation.podspec b/geolocation/CapacitorGeolocation.podspec deleted file mode 100644 index 17ecc7b25c..0000000000 --- a/geolocation/CapacitorGeolocation.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 = 'CapacitorGeolocation' - 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}', 'geolocation/ios/Sources/**/*.{swift,h,m,c,cc,mm,cpp}' - s.ios.deployment_target = '14.0' - s.dependency 'Capacitor' - s.swift_version = '5.1' -end diff --git a/geolocation/LICENSE b/geolocation/LICENSE deleted file mode 100644 index 6652495cb2..0000000000 --- a/geolocation/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. diff --git a/geolocation/Package.swift b/geolocation/Package.swift deleted file mode 100644 index cc80cbfc92..0000000000 --- a/geolocation/Package.swift +++ /dev/null @@ -1,28 +0,0 @@ -// swift-tools-version: 5.9 -import PackageDescription - -let package = Package( - name: "CapacitorGeolocation", - platforms: [.iOS(.v14)], - products: [ - .library( - name: "CapacitorGeolocation", - targets: ["GeolocationPlugin"]) - ], - dependencies: [ - .package(url: "https://github.com/ionic-team/capacitor-swift-pm.git", from: "7.0.0") - ], - targets: [ - .target( - name: "GeolocationPlugin", - dependencies: [ - .product(name: "Capacitor", package: "capacitor-swift-pm"), - .product(name: "Cordova", package: "capacitor-swift-pm") - ], - path: "ios/Sources/GeolocationPlugin"), - .testTarget( - name: "GeolocationPluginTests", - dependencies: ["GeolocationPlugin"], - path: "ios/Tests/GeolocationPluginTests") - ] -) diff --git a/geolocation/README.md b/geolocation/README.md deleted file mode 100644 index af449ddadf..0000000000 --- a/geolocation/README.md +++ /dev/null @@ -1,232 +0,0 @@ -# ⓘ Plugin migrated - -**From version 7.1.0 onwards, this plugin is now hosted in a separate repository. Refer to [capacitor-geolocation repository](https://github.com/ionic-team/capacitor-geolocation).** - -This file remains here to serve as documentation for version 7.0.0. - -# @capacitor/geolocation - -The Geolocation API provides simple methods for getting and tracking the current position of the device using GPS, along with altitude, heading, and speed information if available. - -## Install - -```bash -npm install @capacitor/geolocation -npx cap sync -``` - -## iOS - -Apple requires privacy descriptions to be specified in `Info.plist` for location information: - -- `NSLocationWhenInUseUsageDescription` (`Privacy - Location When In Use 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 - -This API requires the following permissions be added to your `AndroidManifest.xml`: - -```xml - - - - -``` - -The first two permissions ask for location data, both fine and coarse, and the last line is optional but necessary if your app _requires_ GPS to function. You may leave it out, though keep in mind that this may mean your app is installed on devices lacking GPS hardware. - -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. - -### Variables - -This plugin will use the following project variables (defined in your app's `variables.gradle` file): - -- `playServicesLocationVersion` version of `com.google.android.gms:play-services-location` (default: `21.3.0`) - -## Example - -```typescript -import { Geolocation } from '@capacitor/geolocation'; - -const printCurrentPosition = async () => { - const coordinates = await Geolocation.getCurrentPosition(); - - console.log('Current position:', coordinates); -}; -``` - -## API - - - -* [`getCurrentPosition(...)`](#getcurrentposition) -* [`watchPosition(...)`](#watchposition) -* [`clearWatch(...)`](#clearwatch) -* [`checkPermissions()`](#checkpermissions) -* [`requestPermissions(...)`](#requestpermissions) -* [Interfaces](#interfaces) -* [Type Aliases](#type-aliases) - - - - - - -### getCurrentPosition(...) - -```typescript -getCurrentPosition(options?: PositionOptions | undefined) => Promise -``` - -Get the current GPS location of the device - -| Param | Type | -| ------------- | ----------------------------------------------------------- | -| **`options`** | PositionOptions | - -**Returns:** Promise<Position> - -**Since:** 1.0.0 - --------------------- - - -### watchPosition(...) - -```typescript -watchPosition(options: PositionOptions, callback: WatchPositionCallback) => Promise -``` - -Set up a watch for location changes. Note that watching for location changes -can consume a large amount of energy. Be smart about listening only when you need to. - -| Param | Type | -| -------------- | ----------------------------------------------------------------------- | -| **`options`** | PositionOptions | -| **`callback`** | WatchPositionCallback | - -**Returns:** Promise<string> - -**Since:** 1.0.0 - --------------------- - - -### clearWatch(...) - -```typescript -clearWatch(options: ClearWatchOptions) => Promise -``` - -Clear a given watch - -| Param | Type | -| ------------- | --------------------------------------------------------------- | -| **`options`** | ClearWatchOptions | - -**Since:** 1.0.0 - --------------------- - - -### checkPermissions() - -```typescript -checkPermissions() => Promise -``` - -Check location permissions. Will throw if system location services are disabled. - -**Returns:** Promise<PermissionStatus> - -**Since:** 1.0.0 - --------------------- - - -### requestPermissions(...) - -```typescript -requestPermissions(permissions?: GeolocationPluginPermissions | undefined) => Promise -``` - -Request location permissions. Will throw if system location services are disabled. - -| Param | Type | -| ----------------- | ------------------------------------------------------------------------------------- | -| **`permissions`** | GeolocationPluginPermissions | - -**Returns:** Promise<PermissionStatus> - -**Since:** 1.0.0 - --------------------- - - -### Interfaces - - -#### Position - -| Prop | Type | Description | Since | -| --------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------- | ----- | -| **`timestamp`** | number | Creation timestamp for coords | 1.0.0 | -| **`coords`** | { latitude: number; longitude: number; accuracy: number; altitudeAccuracy: number \| null; altitude: number \| null; speed: number \| null; heading: number \| null; } | The GPS coordinates along with the accuracy of the data | 1.0.0 | - - -#### PositionOptions - -| Prop | Type | Description | Default | Since | -| --------------------------- | -------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------ | ----- | -| **`enableHighAccuracy`** | boolean | High accuracy mode (such as GPS, if available) On Android 12+ devices it will be ignored if users didn't grant ACCESS_FINE_LOCATION permissions (can be checked with location alias). | false | 1.0.0 | -| **`timeout`** | number | The maximum wait time in milliseconds for location updates. In Android, since version 4.0.0 of the plugin, timeout gets ignored for getCurrentPosition. | 10000 | 1.0.0 | -| **`maximumAge`** | number | The maximum age in milliseconds of a possible cached position that is acceptable to return | 0 | 1.0.0 | -| **`minimumUpdateInterval`** | number | The minumum update interval for location updates. If location updates are available faster than this interval then an update will only occur if the minimum update interval has expired since the last location update. This parameter is only available for Android. It has no effect on iOS or Web platforms. | 5000 | 6.1.0 | - - -#### ClearWatchOptions - -| Prop | Type | -| -------- | ------------------------------------------------- | -| **`id`** | CallbackID | - - -#### PermissionStatus - -| Prop | Type | Description | Since | -| -------------------- | ----------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | ----- | -| **`location`** | PermissionState | Permission state for location alias. On Android it requests/checks both ACCESS_COARSE_LOCATION and ACCESS_FINE_LOCATION permissions. On iOS and web it requests/checks location permission. | 1.0.0 | -| **`coarseLocation`** | PermissionState | Permission state for coarseLocation alias. On Android it requests/checks ACCESS_COARSE_LOCATION. On Android 12+, users can choose between Approximate location (ACCESS_COARSE_LOCATION) or Precise location (ACCESS_FINE_LOCATION), so this alias can be used if the app doesn't need high accuracy. On iOS and web it will have the same value as location alias. | 1.2.0 | - - -#### GeolocationPluginPermissions - -| Prop | Type | -| ----------------- | ---------------------------------------- | -| **`permissions`** | GeolocationPermissionType[] | - - -### Type Aliases - - -#### WatchPositionCallback - -(position: Position | null, err?: any): void - - -#### CallbackID - -string - - -#### PermissionState - -'prompt' | 'prompt-with-rationale' | 'granted' | 'denied' - - -#### GeolocationPermissionType - -'location' | 'coarseLocation' - - diff --git a/geolocation/android/.gitignore b/geolocation/android/.gitignore deleted file mode 100644 index 42afabfd2a..0000000000 --- a/geolocation/android/.gitignore +++ /dev/null @@ -1 +0,0 @@ -/build \ No newline at end of file diff --git a/geolocation/android/build.gradle b/geolocation/android/build.gradle deleted file mode 100644 index f07f0102ac..0000000000 --- a/geolocation/android/build.gradle +++ /dev/null @@ -1,81 +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.0' - androidxJunitVersion = project.hasProperty('androidxJunitVersion') ? rootProject.ext.androidxJunitVersion : '1.2.1' - androidxEspressoCoreVersion = project.hasProperty('androidxEspressoCoreVersion') ? rootProject.ext.androidxEspressoCoreVersion : '3.6.1' - playServicesLocationVersion = project.hasProperty('playServicesLocationVersion') ? rootProject.ext.playServicesLocationVersion : '21.3.0' -} - -buildscript { - repositories { - google() - mavenCentral() - maven { - url "https://plugins.gradle.org/m2/" - } - } - dependencies { - classpath 'com.android.tools.build:gradle:8.7.2' - 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.geolocation" - compileSdk project.hasProperty('compileSdkVersion') ? rootProject.ext.compileSdkVersion : 35 - defaultConfig { - minSdkVersion project.hasProperty('minSdkVersion') ? rootProject.ext.minSdkVersion : 23 - targetSdkVersion project.hasProperty('targetSdkVersion') ? rootProject.ext.targetSdkVersion : 35 - versionCode 1 - versionName "1.0" - testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" - } - buildTypes { - release { - minifyEnabled false - proguardFiles getDefaultProguardFile('proguard-android.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.appcompat:appcompat:$androidxAppCompatVersion" - implementation "com.google.android.gms:play-services-location:$playServicesLocationVersion" - testImplementation "junit:junit:$junitVersion" - androidTestImplementation "androidx.test.ext:junit:$androidxJunitVersion" - androidTestImplementation "androidx.test.espresso:espresso-core:$androidxEspressoCoreVersion" -} diff --git a/geolocation/android/gradle.properties b/geolocation/android/gradle.properties deleted file mode 100644 index 2e87c52f83..0000000000 --- a/geolocation/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/geolocation/android/gradle/wrapper/gradle-wrapper.jar b/geolocation/android/gradle/wrapper/gradle-wrapper.jar deleted file mode 100644 index a4b76b9530..0000000000 Binary files a/geolocation/android/gradle/wrapper/gradle-wrapper.jar and /dev/null differ diff --git a/geolocation/android/gradle/wrapper/gradle-wrapper.properties b/geolocation/android/gradle/wrapper/gradle-wrapper.properties deleted file mode 100644 index c1d5e01859..0000000000 --- a/geolocation/android/gradle/wrapper/gradle-wrapper.properties +++ /dev/null @@ -1,7 +0,0 @@ -distributionBase=GRADLE_USER_HOME -distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-8.11.1-all.zip -networkTimeout=10000 -validateDistributionUrl=true -zipStoreBase=GRADLE_USER_HOME -zipStorePath=wrapper/dists diff --git a/geolocation/android/gradlew b/geolocation/android/gradlew deleted file mode 100755 index f5feea6d6b..0000000000 --- a/geolocation/android/gradlew +++ /dev/null @@ -1,252 +0,0 @@ -#!/bin/sh - -# -# Copyright © 2015-2021 the original authors. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# https://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# -# SPDX-License-Identifier: Apache-2.0 -# - -############################################################################## -# -# Gradle start up script for POSIX generated by Gradle. -# -# Important for running: -# -# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is -# noncompliant, but you have some other compliant shell such as ksh or -# bash, then to run this script, type that shell name before the whole -# command line, like: -# -# ksh Gradle -# -# Busybox and similar reduced shells will NOT work, because this script -# requires all of these POSIX shell features: -# * functions; -# * expansions «$var», «${var}», «${var:-default}», «${var+SET}», -# «${var#prefix}», «${var%suffix}», and «$( cmd )»; -# * compound commands having a testable exit status, especially «case»; -# * various built-in commands including «command», «set», and «ulimit». -# -# Important for patching: -# -# (2) This script targets any POSIX shell, so it avoids extensions provided -# by Bash, Ksh, etc; in particular arrays are avoided. -# -# The "traditional" practice of packing multiple parameters into a -# space-separated string is a well documented source of bugs and security -# problems, so this is (mostly) avoided, by progressively accumulating -# options in "$@", and eventually passing that to Java. -# -# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS, -# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly; -# see the in-line comments for details. -# -# There are tweaks for specific operating systems such as AIX, CygWin, -# Darwin, MinGW, and NonStop. -# -# (3) This script is generated from the Groovy template -# https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt -# within the Gradle project. -# -# You can find Gradle at https://github.com/gradle/gradle/. -# -############################################################################## - -# Attempt to set APP_HOME - -# Resolve links: $0 may be a link -app_path=$0 - -# Need this for daisy-chained symlinks. -while - APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path - [ -h "$app_path" ] -do - ls=$( ls -ld "$app_path" ) - link=${ls#*' -> '} - 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 -' "$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=$APP_HOME/gradle/wrapper/gradle-wrapper.jar - - -# 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, 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" \ - org.gradle.wrapper.GradleWrapperMain \ - "$@" - -# 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/geolocation/android/gradlew.bat b/geolocation/android/gradlew.bat deleted file mode 100644 index 9b42019c79..0000000000 --- a/geolocation/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=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar - - -@rem Execute Gradle -"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* - -: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/geolocation/android/proguard-rules.pro b/geolocation/android/proguard-rules.pro deleted file mode 100644 index f1b424510d..0000000000 --- a/geolocation/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/geolocation/android/settings.gradle b/geolocation/android/settings.gradle deleted file mode 100644 index 1e5b8431f7..0000000000 --- a/geolocation/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/geolocation/android/src/androidTest/java/com/getcapacitor/android/ExampleInstrumentedTest.java b/geolocation/android/src/androidTest/java/com/getcapacitor/android/ExampleInstrumentedTest.java deleted file mode 100644 index 58020e16cb..0000000000 --- a/geolocation/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/geolocation/android/src/main/AndroidManifest.xml b/geolocation/android/src/main/AndroidManifest.xml deleted file mode 100644 index a2f47b6057..0000000000 --- a/geolocation/android/src/main/AndroidManifest.xml +++ /dev/null @@ -1,2 +0,0 @@ - - diff --git a/geolocation/android/src/main/java/com/capacitorjs/plugins/geolocation/Geolocation.java b/geolocation/android/src/main/java/com/capacitorjs/plugins/geolocation/Geolocation.java deleted file mode 100644 index e5263a0a25..0000000000 --- a/geolocation/android/src/main/java/com/capacitorjs/plugins/geolocation/Geolocation.java +++ /dev/null @@ -1,147 +0,0 @@ -package com.capacitorjs.plugins.geolocation; - -import android.content.Context; -import android.location.Location; -import android.location.LocationManager; -import android.os.SystemClock; -import androidx.core.location.LocationManagerCompat; -import com.google.android.gms.common.ConnectionResult; -import com.google.android.gms.common.GoogleApiAvailability; -import com.google.android.gms.location.FusedLocationProviderClient; -import com.google.android.gms.location.LocationCallback; -import com.google.android.gms.location.LocationRequest; -import com.google.android.gms.location.LocationResult; -import com.google.android.gms.location.LocationServices; -import com.google.android.gms.location.Priority; - -public class Geolocation { - - private FusedLocationProviderClient fusedLocationClient; - private LocationCallback locationCallback; - - private Context context; - - public Geolocation(Context context) { - this.context = context; - } - - public Boolean isLocationServicesEnabled() { - LocationManager lm = (LocationManager) context.getSystemService(Context.LOCATION_SERVICE); - return LocationManagerCompat.isLocationEnabled(lm); - } - - @SuppressWarnings("MissingPermission") - public void sendLocation(boolean enableHighAccuracy, final LocationResultCallback resultCallback) { - int resultCode = GoogleApiAvailability.getInstance().isGooglePlayServicesAvailable(context); - if (resultCode == ConnectionResult.SUCCESS) { - LocationManager lm = (LocationManager) context.getSystemService(Context.LOCATION_SERVICE); - - if (this.isLocationServicesEnabled()) { - boolean networkEnabled = false; - - try { - networkEnabled = lm.isProviderEnabled(LocationManager.NETWORK_PROVIDER); - } catch (Exception ex) {} - - int lowPriority = networkEnabled ? Priority.PRIORITY_BALANCED_POWER_ACCURACY : Priority.PRIORITY_LOW_POWER; - int priority = enableHighAccuracy ? Priority.PRIORITY_HIGH_ACCURACY : lowPriority; - - LocationServices - .getFusedLocationProviderClient(context) - .getCurrentLocation(priority, null) - .addOnFailureListener(e -> resultCallback.error(e.getMessage())) - .addOnSuccessListener( - location -> { - if (location == null) { - resultCallback.error("location unavailable"); - } else { - resultCallback.success(location); - } - } - ); - } else { - resultCallback.error("location disabled"); - } - } else { - resultCallback.error("Google Play Services not available"); - } - } - - @SuppressWarnings("MissingPermission") - public void requestLocationUpdates( - boolean enableHighAccuracy, - int timeout, - int minUpdateInterval, - final LocationResultCallback resultCallback - ) { - int resultCode = GoogleApiAvailability.getInstance().isGooglePlayServicesAvailable(context); - if (resultCode == ConnectionResult.SUCCESS) { - clearLocationUpdates(); - fusedLocationClient = LocationServices.getFusedLocationProviderClient(context); - - LocationManager lm = (LocationManager) context.getSystemService(Context.LOCATION_SERVICE); - if (this.isLocationServicesEnabled()) { - boolean networkEnabled = false; - - try { - networkEnabled = lm.isProviderEnabled(LocationManager.NETWORK_PROVIDER); - } catch (Exception ex) {} - - int lowPriority = networkEnabled ? Priority.PRIORITY_BALANCED_POWER_ACCURACY : Priority.PRIORITY_LOW_POWER; - int priority = enableHighAccuracy ? Priority.PRIORITY_HIGH_ACCURACY : lowPriority; - - LocationRequest locationRequest = new LocationRequest.Builder(10000) - .setMaxUpdateDelayMillis(timeout) - .setMinUpdateIntervalMillis(minUpdateInterval) - .setPriority(priority) - .build(); - - locationCallback = - new LocationCallback() { - @Override - public void onLocationResult(LocationResult locationResult) { - Location lastLocation = locationResult.getLastLocation(); - if (lastLocation == null) { - resultCallback.error("location unavailable"); - } else { - resultCallback.success(lastLocation); - } - } - }; - - fusedLocationClient.requestLocationUpdates(locationRequest, locationCallback, null); - } else { - resultCallback.error("location disabled"); - } - } else { - resultCallback.error("Google Play Services not available"); - } - } - - public void clearLocationUpdates() { - if (locationCallback != null) { - fusedLocationClient.removeLocationUpdates(locationCallback); - locationCallback = null; - } - } - - @SuppressWarnings("MissingPermission") - public Location getLastLocation(int maximumAge) { - Location lastLoc = null; - LocationManager lm = (LocationManager) context.getSystemService(Context.LOCATION_SERVICE); - for (String provider : lm.getAllProviders()) { - Location tmpLoc = lm.getLastKnownLocation(provider); - if (tmpLoc != null) { - long locationAge = SystemClock.elapsedRealtimeNanos() - tmpLoc.getElapsedRealtimeNanos(); - long maximumAgeNanoSec = maximumAge * 1000000L; - if ( - locationAge <= maximumAgeNanoSec && - (lastLoc == null || lastLoc.getElapsedRealtimeNanos() > tmpLoc.getElapsedRealtimeNanos()) - ) { - lastLoc = tmpLoc; - } - } - } - return lastLoc; - } -} diff --git a/geolocation/android/src/main/java/com/capacitorjs/plugins/geolocation/GeolocationPlugin.java b/geolocation/android/src/main/java/com/capacitorjs/plugins/geolocation/GeolocationPlugin.java deleted file mode 100644 index d198c675b5..0000000000 --- a/geolocation/android/src/main/java/com/capacitorjs/plugins/geolocation/GeolocationPlugin.java +++ /dev/null @@ -1,255 +0,0 @@ -package com.capacitorjs.plugins.geolocation; - -import android.Manifest; -import android.location.Location; -import android.os.Build; -import com.getcapacitor.JSObject; -import com.getcapacitor.PermissionState; -import com.getcapacitor.Plugin; -import com.getcapacitor.PluginCall; -import com.getcapacitor.PluginMethod; -import com.getcapacitor.annotation.CapacitorPlugin; -import com.getcapacitor.annotation.Permission; -import com.getcapacitor.annotation.PermissionCallback; -import java.util.HashMap; -import java.util.Map; - -@CapacitorPlugin( - name = "Geolocation", - permissions = { - @Permission( - strings = { Manifest.permission.ACCESS_COARSE_LOCATION, Manifest.permission.ACCESS_FINE_LOCATION }, - alias = GeolocationPlugin.LOCATION - ), - @Permission(strings = { Manifest.permission.ACCESS_COARSE_LOCATION }, alias = GeolocationPlugin.COARSE_LOCATION) - } -) -public class GeolocationPlugin extends Plugin { - - static final String LOCATION = "location"; - static final String COARSE_LOCATION = "coarseLocation"; - private Geolocation implementation; - private Map watchingCalls = new HashMap<>(); - - @Override - public void load() { - implementation = new Geolocation(getContext()); - } - - @Override - protected void handleOnPause() { - super.handleOnPause(); - // Clear all location updates on pause to avoid possible background location calls - implementation.clearLocationUpdates(); - } - - @Override - protected void handleOnResume() { - super.handleOnResume(); - for (PluginCall call : watchingCalls.values()) { - startWatch(call); - } - } - - @Override - @PluginMethod - public void checkPermissions(PluginCall call) { - if (implementation.isLocationServicesEnabled()) { - super.checkPermissions(call); - } else { - call.reject("Location services are not enabled"); - } - } - - @Override - @PluginMethod - public void requestPermissions(PluginCall call) { - if (implementation.isLocationServicesEnabled()) { - super.requestPermissions(call); - } else { - call.reject("Location services are not enabled"); - } - } - - /** - * Gets a snapshot of the current device position if permission is granted. The call continues - * in the {@link #completeCurrentPosition(PluginCall)} method if a permission request is required. - * - * @param call Plugin call - */ - @PluginMethod - public void getCurrentPosition(final PluginCall call) { - String alias = getAlias(call); - if (getPermissionState(alias) != PermissionState.GRANTED) { - requestPermissionForAlias(alias, call, "completeCurrentPosition"); - } else { - getPosition(call); - } - } - - /** - * Completes the getCurrentPosition plugin call after a permission request - * @see #getCurrentPosition(PluginCall) - * @param call the plugin call - */ - @PermissionCallback - private void completeCurrentPosition(PluginCall call) { - if (getPermissionState(GeolocationPlugin.COARSE_LOCATION) == PermissionState.GRANTED) { - implementation.sendLocation( - isHighAccuracy(call), - new LocationResultCallback() { - @Override - public void success(Location location) { - call.resolve(getJSObjectForLocation(location)); - } - - @Override - public void error(String message) { - call.reject(message); - } - } - ); - } else { - call.reject("Location permission was denied"); - } - } - - /** - * Begins watching for live location changes if permission is granted. The call continues - * in the {@link #completeWatchPosition(PluginCall)} method if a permission request is required. - * - * @param call the plugin call - */ - @PluginMethod(returnType = PluginMethod.RETURN_CALLBACK) - public void watchPosition(PluginCall call) { - call.setKeepAlive(true); - String alias = getAlias(call); - if (getPermissionState(alias) != PermissionState.GRANTED) { - requestPermissionForAlias(alias, call, "completeWatchPosition"); - } else { - startWatch(call); - } - } - - /** - * Completes the watchPosition plugin call after a permission request - * @see #watchPosition(PluginCall) - * @param call the plugin call - */ - @PermissionCallback - private void completeWatchPosition(PluginCall call) { - if (getPermissionState(GeolocationPlugin.COARSE_LOCATION) == PermissionState.GRANTED) { - startWatch(call); - } else { - call.reject("Location permission was denied"); - } - } - - @SuppressWarnings("MissingPermission") - private void getPosition(PluginCall call) { - int maximumAge = call.getInt("maximumAge", 0); - Location location = implementation.getLastLocation(maximumAge); - if (location != null) { - call.resolve(getJSObjectForLocation(location)); - } else { - implementation.sendLocation( - isHighAccuracy(call), - new LocationResultCallback() { - @Override - public void success(Location location) { - call.resolve(getJSObjectForLocation(location)); - } - - @Override - public void error(String message) { - call.reject(message); - } - } - ); - } - } - - @SuppressWarnings("MissingPermission") - private void startWatch(final PluginCall call) { - int timeout = call.getInt("timeout", 10000); - int minUpdateInterval = call.getInt("minimumUpdateInterval", 5000); - - implementation.requestLocationUpdates( - isHighAccuracy(call), - timeout, - minUpdateInterval, - new LocationResultCallback() { - @Override - public void success(Location location) { - call.resolve(getJSObjectForLocation(location)); - } - - @Override - public void error(String message) { - call.reject(message); - } - } - ); - - watchingCalls.put(call.getCallbackId(), call); - } - - /** - * Removes an active geolocation watch. - * - * @param call Plugin call - */ - @SuppressWarnings("MissingPermission") - @PluginMethod - public void clearWatch(PluginCall call) { - String callbackId = call.getString("id"); - - if (callbackId != null) { - PluginCall removed = watchingCalls.remove(callbackId); - if (removed != null) { - removed.release(bridge); - } - - if (watchingCalls.size() == 0) { - implementation.clearLocationUpdates(); - } - - call.resolve(); - } else { - call.reject("Watch call id must be provided"); - } - } - - private JSObject getJSObjectForLocation(Location location) { - JSObject ret = new JSObject(); - JSObject coords = new JSObject(); - ret.put("coords", coords); - ret.put("timestamp", location.getTime()); - coords.put("latitude", location.getLatitude()); - coords.put("longitude", location.getLongitude()); - coords.put("accuracy", location.getAccuracy()); - coords.put("altitude", location.getAltitude()); - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { - coords.put("altitudeAccuracy", location.getVerticalAccuracyMeters()); - } - coords.put("speed", location.getSpeed()); - coords.put("heading", location.getBearing()); - return ret; - } - - private String getAlias(PluginCall call) { - String alias = GeolocationPlugin.LOCATION; - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { - boolean enableHighAccuracy = call.getBoolean("enableHighAccuracy", false); - if (!enableHighAccuracy) { - alias = GeolocationPlugin.COARSE_LOCATION; - } - } - return alias; - } - - private boolean isHighAccuracy(PluginCall call) { - boolean enableHighAccuracy = call.getBoolean("enableHighAccuracy", false); - return enableHighAccuracy && getPermissionState(GeolocationPlugin.LOCATION) == PermissionState.GRANTED; - } -} diff --git a/geolocation/android/src/main/java/com/capacitorjs/plugins/geolocation/LocationResultCallback.java b/geolocation/android/src/main/java/com/capacitorjs/plugins/geolocation/LocationResultCallback.java deleted file mode 100644 index f3da0fc3ae..0000000000 --- a/geolocation/android/src/main/java/com/capacitorjs/plugins/geolocation/LocationResultCallback.java +++ /dev/null @@ -1,8 +0,0 @@ -package com.capacitorjs.plugins.geolocation; - -import android.location.Location; - -public interface LocationResultCallback { - void success(Location location); - void error(String message); -} diff --git a/geolocation/android/src/test/java/com/getcapacitor/ExampleUnitTest.java b/geolocation/android/src/test/java/com/getcapacitor/ExampleUnitTest.java deleted file mode 100644 index a0fed0cfb7..0000000000 --- a/geolocation/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/geolocation/ios/.gitignore b/geolocation/ios/.gitignore deleted file mode 100644 index 0023a53406..0000000000 --- a/geolocation/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/geolocation/ios/Sources/GeolocationPlugin/GeolocationPlugin.swift b/geolocation/ios/Sources/GeolocationPlugin/GeolocationPlugin.swift deleted file mode 100644 index d43d7add1a..0000000000 --- a/geolocation/ios/Sources/GeolocationPlugin/GeolocationPlugin.swift +++ /dev/null @@ -1,224 +0,0 @@ -import Foundation -import CoreLocation -import UIKit -import Capacitor - -@objc(GeolocationPlugin) -public class GeolocationPlugin: CAPPlugin, CLLocationManagerDelegate, CAPBridgedPlugin { - public let identifier = "GeolocationPlugin" - public let jsName = "Geolocation" - public let pluginMethods: [CAPPluginMethod] = [ - CAPPluginMethod(name: "getCurrentPosition", returnType: CAPPluginReturnPromise), - CAPPluginMethod(name: "watchPosition", returnType: CAPPluginReturnCallback), - CAPPluginMethod(name: "clearWatch", returnType: CAPPluginReturnPromise), - CAPPluginMethod(name: "checkPermissions", returnType: CAPPluginReturnPromise), - CAPPluginMethod(name: "requestPermissions", returnType: CAPPluginReturnPromise) - ] - - enum CallType { - case permissions - case singleUpdate - case watch - } - - var locationManager = CLLocationManager() - private var isUpdatingLocation: Bool = false - private var callQueue: [String: CallType] = [:] - - @objc func getCurrentPosition(_ call: CAPPluginCall) { - bridge?.saveCall(call) - callQueue[call.callbackId] = .singleUpdate - - DispatchQueue.main.async { - self.locationManager.delegate = self - - if call.getBool("enableHighAccuracy", false) == true { - self.locationManager.desiredAccuracy = kCLLocationAccuracyBest - } else { - self.locationManager.desiredAccuracy = kCLLocationAccuracyThreeKilometers - } - - if self.locationManager.authorizationStatus == .notDetermined { - self.locationManager.requestWhenInUseAuthorization() - } else { - self.locationManager.requestLocation() - } - } - } - - @objc func watchPosition(_ call: CAPPluginCall) { - call.keepAlive = true - callQueue[call.callbackId] = .watch - - DispatchQueue.main.async { - self.locationManager.delegate = self - - if call.getBool("enableHighAccuracy", false) == true { - self.locationManager.desiredAccuracy = kCLLocationAccuracyBestForNavigation - } else { - self.locationManager.desiredAccuracy = kCLLocationAccuracyThreeKilometers - } - - if self.locationManager.authorizationStatus == .notDetermined { - self.locationManager.requestWhenInUseAuthorization() - } else { - self.locationManager.startUpdatingLocation() - self.isUpdatingLocation = true - } - } - } - - @objc func clearWatch(_ call: CAPPluginCall) { - guard let callbackId = call.getString("id") else { - call.reject("Watch call id must be provided") - return - } - - if let savedCall = bridge?.savedCall(withID: callbackId) { - bridge?.releaseCall(savedCall) - - self.stopUpdating() - } - - callQueue.removeValue(forKey: callbackId) - - call.resolve() - } - - public func locationManager(_ manager: CLLocationManager, didFailWithError error: Error) { - let removalQueue = callQueue.filter { $0.value == .permissions || $0.value == .singleUpdate } - callQueue = callQueue.filter { $0.value == .watch } - - for (id, _) in removalQueue { - if let call = bridge?.savedCall(withID: id) { - call.reject(error.localizedDescription) - bridge?.releaseCall(call) - } - } - - for (id, _) in callQueue { - if let call = bridge?.savedCall(withID: id) { - call.reject(error.localizedDescription) - } - } - } - - public func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) { - let removalQueue = callQueue.filter { $0.value == .singleUpdate } - callQueue = callQueue.filter { $0.value != .singleUpdate } - - for (id, _) in removalQueue { - if let call = bridge?.savedCall(withID: id) { - reportLocation(call, locations) - bridge?.releaseCall(call) - } - } - - for (id, callType) in callQueue { - if let call = bridge?.savedCall(withID: id), callType == .watch { - reportLocation(call, locations) - } - } - } - - public func locationManagerDidChangeAuthorization(_ manager: CLLocationManager) { - let removalQueue = callQueue.filter { $0.value == .permissions } - callQueue = callQueue.filter { $0.value != .permissions } - - for (id, _) in removalQueue { - if let call = bridge?.savedCall(withID: id) { - DispatchQueue(label: "permissionsQueue").async { - self.checkPermissions(call) - } - bridge?.releaseCall(call) - } - } - - if !(callQueue.filter({ $0.value == .singleUpdate }).isEmpty) { - self.locationManager.requestLocation() - } - - if !(callQueue.filter({ $0.value == .watch }).isEmpty) && !self.isUpdatingLocation { - self.locationManager.startUpdatingLocation() - self.isUpdatingLocation = true - } - } - - public func stopUpdating() { - self.locationManager.stopUpdatingLocation() - self.isUpdatingLocation = false - } - - private func reportLocation(_ call: CAPPluginCall, _ locations: [CLLocation]) { - if let location = locations.first { - let result = makePosition(location) - call.resolve(result) - } else { - // TODO: Handle case where location is nil - call.resolve() - } - } - - @objc override public func checkPermissions(_ call: CAPPluginCall) { - var status: String = "" - - if CLLocationManager.locationServicesEnabled() { - switch self.locationManager.authorizationStatus { - case .notDetermined: - status = "prompt" - case .restricted, .denied: - status = "denied" - case .authorizedAlways, .authorizedWhenInUse: - status = "granted" - @unknown default: - status = "prompt" - } - } else { - call.reject("Location services are not enabled") - return - } - - let result = [ - "location": status, - "coarseLocation": status - ] - - call.resolve(result) - } - - @objc override public func requestPermissions(_ call: CAPPluginCall) { - if CLLocationManager.locationServicesEnabled() { - // If state is not yet determined, request perms. - // Otherwise, report back the state right away - if self.locationManager.authorizationStatus == .notDetermined { - bridge?.saveCall(call) - callQueue[call.callbackId] = .permissions - - DispatchQueue.main.async { - self.locationManager.delegate = self - self.locationManager.requestWhenInUseAuthorization() - } - } else { - checkPermissions(call) - } - } else { - call.reject("Location services are not enabled") - } - } - - func makePosition(_ location: CLLocation) -> JSObject { - var ret = JSObject() - var coords = JSObject() - coords["latitude"] = location.coordinate.latitude - coords["longitude"] = location.coordinate.longitude - coords["accuracy"] = location.horizontalAccuracy - coords["altitude"] = location.altitude - coords["altitudeAccuracy"] = location.verticalAccuracy - coords["speed"] = location.speed - coords["heading"] = location.course - ret["timestamp"] = Int((location.timestamp.timeIntervalSince1970 * 1000)) - ret["coords"] = coords - return ret - } - -} diff --git a/geolocation/ios/Tests/GeolocationPluginTests/GeolocationPluginTests.swift b/geolocation/ios/Tests/GeolocationPluginTests/GeolocationPluginTests.swift deleted file mode 100644 index e13ac42bcb..0000000000 --- a/geolocation/ios/Tests/GeolocationPluginTests/GeolocationPluginTests.swift +++ /dev/null @@ -1,12 +0,0 @@ -import XCTest -@testable import GeolocationPlugin - -final class GeolocationPluginTests: 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/geolocation/package.json b/geolocation/package.json deleted file mode 100644 index 6c7f274195..0000000000 --- a/geolocation/package.json +++ /dev/null @@ -1,84 +0,0 @@ -{ - "name": "@capacitor/geolocation", - "version": "7.0.0", - "description": "The Geolocation API provides simple methods for getting and tracking the current position of the device using GPS, along with altitude, heading, and speed information if available.", - "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", - "CapacitorGeolocation.podspec" - ], - "author": "Ionic ", - "license": "MIT", - "repository": { - "type": "git", - "url": "https://github.com/ionic-team/capacitor-plugins" - }, - "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 CapacitorGeolocation -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}\"", - "swiftlint": "node-swiftlint", - "docgen": "docgen --api GeolocationPlugin --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", - "publish:cocoapod": "pod trunk push ./CapacitorGeolocation.podspec --allow-warnings" - }, - "devDependencies": { - "@capacitor/android": "^7.0.0", - "@capacitor/core": "^7.0.0", - "@capacitor/docgen": "0.2.2", - "@capacitor/ios": "^7.0.0", - "@ionic/eslint-config": "^0.4.0", - "@ionic/prettier-config": "~1.0.1", - "@ionic/swiftlint-config": "^1.1.2", - "eslint": "^8.57.0", - "prettier": "~2.3.0", - "prettier-plugin-java": "~1.0.2", - "rimraf": "^6.0.1", - "rollup": "^4.26.0", - "swiftlint": "^1.0.1", - "typescript": "~4.1.5" - }, - "peerDependencies": { - "@capacitor/core": ">=7.0.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/geolocation/rollup.config.mjs b/geolocation/rollup.config.mjs deleted file mode 100644 index 46fc903b64..0000000000 --- a/geolocation/rollup.config.mjs +++ /dev/null @@ -1,22 +0,0 @@ -export default { - input: 'dist/esm/index.js', - output: [ - { - file: 'dist/plugin.js', - format: 'iife', - name: 'capacitorGeolocation', - 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/geolocation/src/definitions.ts b/geolocation/src/definitions.ts deleted file mode 100644 index 9e6239e1d4..0000000000 --- a/geolocation/src/definitions.ts +++ /dev/null @@ -1,219 +0,0 @@ -import type { PermissionState } from '@capacitor/core'; - -export type CallbackID = string; - -export interface PermissionStatus { - /** - * Permission state for location alias. - * - * On Android it requests/checks both ACCESS_COARSE_LOCATION and - * ACCESS_FINE_LOCATION permissions. - * - * On iOS and web it requests/checks location permission. - * - * @since 1.0.0 - */ - location: PermissionState; - - /** - * Permission state for coarseLocation alias. - * - * On Android it requests/checks ACCESS_COARSE_LOCATION. - * - * On Android 12+, users can choose between Approximate location (ACCESS_COARSE_LOCATION) or - * Precise location (ACCESS_FINE_LOCATION), so this alias can be used if the app doesn't - * need high accuracy. - * - * On iOS and web it will have the same value as location alias. - * - * @since 1.2.0 - */ - coarseLocation: PermissionState; -} - -export type GeolocationPermissionType = 'location' | 'coarseLocation'; - -export interface GeolocationPluginPermissions { - permissions: GeolocationPermissionType[]; -} - -export interface GeolocationPlugin { - /** - * Get the current GPS location of the device - * - * @since 1.0.0 - */ - getCurrentPosition(options?: PositionOptions): Promise; - - /** - * Set up a watch for location changes. Note that watching for location changes - * can consume a large amount of energy. Be smart about listening only when you need to. - * - * @since 1.0.0 - */ - watchPosition( - options: PositionOptions, - callback: WatchPositionCallback, - ): Promise; - - /** - * Clear a given watch - * - * @since 1.0.0 - */ - clearWatch(options: ClearWatchOptions): Promise; - - /** - * Check location permissions. Will throw if system location services are disabled. - * - * @since 1.0.0 - */ - checkPermissions(): Promise; - - /** - * Request location permissions. Will throw if system location services are disabled. - * - * @since 1.0.0 - */ - requestPermissions( - permissions?: GeolocationPluginPermissions, - ): Promise; -} - -export interface ClearWatchOptions { - id: CallbackID; -} - -export interface Position { - /** - * Creation timestamp for coords - * - * @since 1.0.0 - */ - timestamp: number; - - /** - * The GPS coordinates along with the accuracy of the data - * - * @since 1.0.0 - */ - coords: { - /** - * Latitude in decimal degrees - * - * @since 1.0.0 - */ - latitude: number; - - /** - * longitude in decimal degrees - * - * @since 1.0.0 - */ - longitude: number; - - /** - * Accuracy level of the latitude and longitude coordinates in meters - * - * @since 1.0.0 - */ - accuracy: number; - - /** - * Accuracy level of the altitude coordinate in meters, if available. - * - * Available on all iOS versions and on Android 8.0+. - * - * @since 1.0.0 - */ - altitudeAccuracy: number | null | undefined; - - /** - * The altitude the user is at (if available) - * - * @since 1.0.0 - */ - altitude: number | null; - - /** - * The speed the user is traveling (if available) - * - * @since 1.0.0 - */ - speed: number | null; - - /** - * The heading the user is facing (if available) - * - * @since 1.0.0 - */ - heading: number | null; - }; -} - -export interface PositionOptions { - /** - * High accuracy mode (such as GPS, if available) - * - * On Android 12+ devices it will be ignored if users didn't grant - * ACCESS_FINE_LOCATION permissions (can be checked with location alias). - * - * @default false - * @since 1.0.0 - */ - enableHighAccuracy?: boolean; - - /** - * The maximum wait time in milliseconds for location updates. - * - * In Android, since version 4.0.0 of the plugin, timeout gets ignored for getCurrentPosition. - * - * @default 10000 - * @since 1.0.0 - */ - timeout?: number; - - /** - * The maximum age in milliseconds of a possible cached position that is acceptable to return - * - * @default 0 - * @since 1.0.0 - */ - maximumAge?: number; - - /** - * The minumum update interval for location updates. - * - * If location updates are available faster than this interval then an update - * will only occur if the minimum update interval has expired since the last location update. - * - * This parameter is only available for Android. It has no effect on iOS or Web platforms. - * - * @default 5000 - * @since 6.1.0 - */ - minimumUpdateInterval?: number; -} - -export type WatchPositionCallback = ( - position: Position | null, - err?: any, -) => void; - -/** - * @deprecated Use `PositionOptions`. - * @since 1.0.0 - */ -export type GeolocationOptions = PositionOptions; - -/** - * @deprecated Use `WatchPositionCallback`. - * @since 1.0.0 - */ -export type GeolocationWatchCallback = WatchPositionCallback; - -/** - * @deprecated Use `Position`. - * @since 1.0.0 - */ -export type GeolocationPosition = Position; diff --git a/geolocation/src/index.ts b/geolocation/src/index.ts deleted file mode 100644 index bc95b5b799..0000000000 --- a/geolocation/src/index.ts +++ /dev/null @@ -1,10 +0,0 @@ -import { registerPlugin } from '@capacitor/core'; - -import type { GeolocationPlugin } from './definitions'; - -const Geolocation = registerPlugin('Geolocation', { - web: () => import('./web').then(m => new m.GeolocationWeb()), -}); - -export * from './definitions'; -export { Geolocation }; diff --git a/geolocation/src/util.ts b/geolocation/src/util.ts deleted file mode 100644 index c668dcbc6c..0000000000 --- a/geolocation/src/util.ts +++ /dev/null @@ -1,12 +0,0 @@ -export const extend = (target: any, ...objs: any[]): any => { - objs.forEach(o => { - if (o && typeof o === 'object') { - for (const k in o) { - if (Object.prototype.hasOwnProperty.call(o, k)) { - target[k] = o[k]; - } - } - } - }); - return target; -}; diff --git a/geolocation/src/web.ts b/geolocation/src/web.ts deleted file mode 100644 index 5288ff9c04..0000000000 --- a/geolocation/src/web.ts +++ /dev/null @@ -1,77 +0,0 @@ -import { WebPlugin } from '@capacitor/core'; - -import type { - CallbackID, - GeolocationPlugin, - PermissionStatus, - Position, - PositionOptions, - WatchPositionCallback, -} from './definitions'; - -export class GeolocationWeb extends WebPlugin implements GeolocationPlugin { - async getCurrentPosition(options?: PositionOptions): Promise { - return new Promise((resolve, reject) => { - navigator.geolocation.getCurrentPosition( - pos => { - resolve(pos); - }, - err => { - reject(err); - }, - { - enableHighAccuracy: false, - timeout: 10000, - maximumAge: 0, - ...options, - }, - ); - }); - } - - async watchPosition( - options: PositionOptions, - callback: WatchPositionCallback, - ): Promise { - const id = navigator.geolocation.watchPosition( - pos => { - callback(pos); - }, - err => { - callback(null, err); - }, - { - enableHighAccuracy: false, - timeout: 10000, - maximumAge: 0, - minimumUpdateInterval: 5000, - ...options, - }, - ); - - return `${id}`; - } - - async clearWatch(options: { id: string }): Promise { - window.navigator.geolocation.clearWatch(parseInt(options.id, 10)); - } - - async checkPermissions(): Promise { - if (typeof navigator === 'undefined' || !navigator.permissions) { - throw this.unavailable('Permissions API not available in this browser'); - } - - const permission = await window.navigator.permissions.query({ - name: 'geolocation', - }); - return { location: permission.state, coarseLocation: permission.state }; - } - - async requestPermissions(): Promise { - throw this.unimplemented('Not implemented on web.'); - } -} - -const Geolocation = new GeolocationWeb(); - -export { Geolocation }; diff --git a/geolocation/tsconfig.json b/geolocation/tsconfig.json deleted file mode 100644 index f2e88e6a07..0000000000 --- a/geolocation/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"] -} diff --git a/haptics/.eslintignore b/haptics/.eslintignore deleted file mode 100644 index 9d0b71a3c7..0000000000 --- a/haptics/.eslintignore +++ /dev/null @@ -1,2 +0,0 @@ -build -dist diff --git a/haptics/.gitignore b/haptics/.gitignore deleted file mode 100644 index df9f0c2029..0000000000 --- a/haptics/.gitignore +++ /dev/null @@ -1,70 +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/haptics/.prettierignore b/haptics/.prettierignore deleted file mode 100644 index 9d0b71a3c7..0000000000 --- a/haptics/.prettierignore +++ /dev/null @@ -1,2 +0,0 @@ -build -dist diff --git a/haptics/CHANGELOG.md b/haptics/CHANGELOG.md deleted file mode 100644 index fe69561c91..0000000000 --- a/haptics/CHANGELOG.md +++ /dev/null @@ -1,295 +0,0 @@ -# Change Log - -All notable changes to this project will be documented in this file. -See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. - -## [7.0.1](https://github.com/ionic-team/capacitor-plugins/compare/@capacitor/haptics@7.0.0...@capacitor/haptics@7.0.1) (2025-04-02) - -**Note:** Version bump only for package @capacitor/haptics - -# [7.0.0](https://github.com/ionic-team/capacitor-plugins/compare/@capacitor/haptics@7.0.0-rc.0...@capacitor/haptics@7.0.0) (2025-01-20) - -**Note:** Version bump only for package @capacitor/haptics - -# [7.0.0-rc.0](https://github.com/ionic-team/capacitor-plugins/compare/@capacitor/haptics@7.0.0-alpha.2...@capacitor/haptics@7.0.0-rc.0) (2025-01-13) - -**Note:** Version bump only for package @capacitor/haptics - -# [7.0.0-alpha.2](https://github.com/ionic-team/capacitor-plugins/compare/@capacitor/haptics@7.0.0-alpha.1...@capacitor/haptics@7.0.0-alpha.2) (2024-12-19) - -**Note:** Version bump only for package @capacitor/haptics - -# [7.0.0-alpha.1](https://github.com/ionic-team/capacitor-plugins/compare/@capacitor/haptics@6.0.1...@capacitor/haptics@7.0.0-alpha.1) (2024-12-16) - -**Note:** Version bump only for package @capacitor/haptics - -## [6.0.1](https://github.com/ionic-team/capacitor-plugins/compare/@capacitor/haptics@6.0.0...@capacitor/haptics@6.0.1) (2024-08-08) - -**Note:** Version bump only for package @capacitor/haptics - -# [6.0.0](https://github.com/ionic-team/capacitor-plugins/compare/@capacitor/haptics@6.0.0-rc.1...@capacitor/haptics@6.0.0) (2024-04-15) - -**Note:** Version bump only for package @capacitor/haptics - -# [6.0.0-rc.1](https://github.com/ionic-team/capacitor-plugins/compare/@capacitor/haptics@6.0.0-rc.0...@capacitor/haptics@6.0.0-rc.1) (2024-03-25) - -**Note:** Version bump only for package @capacitor/haptics - -# [6.0.0-rc.0](https://github.com/ionic-team/capacitor-plugins/compare/@capacitor/haptics@6.0.0-beta.1...@capacitor/haptics@6.0.0-rc.0) (2024-02-07) - -**Note:** Version bump only for package @capacitor/haptics - -# [6.0.0-beta.1](https://github.com/ionic-team/capacitor-plugins/compare/@capacitor/haptics@6.0.0-beta.0...@capacitor/haptics@6.0.0-beta.1) (2023-12-14) - -**Note:** Version bump only for package @capacitor/haptics - -# [6.0.0-beta.0](https://github.com/ionic-team/capacitor-plugins/compare/@capacitor/haptics@6.0.0-alpha.2...@capacitor/haptics@6.0.0-beta.0) (2023-12-13) - -**Note:** Version bump only for package @capacitor/haptics - -# [6.0.0-alpha.2](https://github.com/ionic-team/capacitor-plugins/compare/@capacitor/haptics@6.0.0-alpha.1...@capacitor/haptics@6.0.0-alpha.2) (2023-11-15) - -**Note:** Version bump only for package @capacitor/haptics - -# [6.0.0-alpha.1](https://github.com/ionic-team/capacitor-plugins/compare/@capacitor/haptics@5.0.6...@capacitor/haptics@6.0.0-alpha.1) (2023-11-08) - -### Features - -- **app,haptics,status-bar,keyboard:** Supporting Swift Package Manager ([#1886](https://github.com/ionic-team/capacitor-plugins/issues/1886)) ([918ea30](https://github.com/ionic-team/capacitor-plugins/commit/918ea30a95f80d740f39e9ab472ed90f9d4a6aba)) - -## [5.0.6](https://github.com/ionic-team/capacitor-plugins/compare/@capacitor/haptics@5.0.5...@capacitor/haptics@5.0.6) (2023-07-12) - -**Note:** Version bump only for package @capacitor/haptics - -## [5.0.5](https://github.com/ionic-team/capacitor-plugins/compare/@capacitor/haptics@5.0.4...@capacitor/haptics@5.0.5) (2023-06-29) - -**Note:** Version bump only for package @capacitor/haptics - -## [5.0.4](https://github.com/ionic-team/capacitor-plugins/compare/@capacitor/haptics@5.0.3...@capacitor/haptics@5.0.4) (2023-06-08) - -**Note:** Version bump only for package @capacitor/haptics - -## [5.0.3](https://github.com/ionic-team/capacitor-plugins/compare/@capacitor/haptics@5.0.2...@capacitor/haptics@5.0.3) (2023-06-08) - -**Note:** Version bump only for package @capacitor/haptics - -## [5.0.2](https://github.com/ionic-team/capacitor-plugins/compare/@capacitor/haptics@5.0.1...@capacitor/haptics@5.0.2) (2023-05-09) - -**Note:** Version bump only for package @capacitor/haptics - -## [5.0.1](https://github.com/ionic-team/capacitor-plugins/compare/@capacitor/haptics@5.0.0...@capacitor/haptics@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/haptics@5.0.0-beta.1...@capacitor/haptics@5.0.0) (2023-05-03) - -**Note:** Version bump only for package @capacitor/haptics - -# [5.0.0-beta.1](https://github.com/ionic-team/capacitor-plugins/compare/@capacitor/haptics@5.0.0-beta.0...@capacitor/haptics@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/haptics@5.0.0-alpha.1...@capacitor/haptics@5.0.0-beta.0) (2023-03-31) - -**Note:** Version bump only for package @capacitor/haptics - -# [5.0.0-alpha.1](https://github.com/ionic-team/capacitor-plugins/compare/@capacitor/haptics@4.1.0...@capacitor/haptics@5.0.0-alpha.1) (2023-03-16) - -### Features - -- **android:** Removing enableJetifier ([d66f9cb](https://github.com/ionic-team/capacitor-plugins/commit/d66f9cbd9da7e3b1d8c64ca6a5b45156867d4a04)) - -# [4.1.0](https://github.com/ionic-team/capacitor-plugins/compare/@capacitor/haptics@1.1.4...@capacitor/haptics@4.1.0) (2022-11-16) - -## 4.0.1 (2022-07-28) - -# 4.0.0 (2022-07-27) - -# 4.0.0-beta.2 (2022-07-08) - -# 4.0.0-beta.0 (2022-06-27) - -### 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)) - -## [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/haptics - -# [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/haptics - -# [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/haptics - -# 4.0.0-beta.0 (2022-06-27) - -### 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)) -- better ignore rules for npm distribution ([#32](https://github.com/ionic-team/capacitor-plugins/issues/32)) ([b8d55b9](https://github.com/ionic-team/capacitor-plugins/commit/b8d55b9233e4ad7b8a1cd41110b4e580fc2a059f)) -- correct addListeners links ([#655](https://github.com/ionic-team/capacitor-plugins/issues/655)) ([f9871e7](https://github.com/ionic-team/capacitor-plugins/commit/f9871e7bd53478addb21155e148829f550c0e457)) -- 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)) -- 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)) -- use correct package in manifest files ([#22](https://github.com/ionic-team/capacitor-plugins/issues/22)) ([ab62987](https://github.com/ionic-team/capacitor-plugins/commit/ab629877e1951f944594f1b23e1bffefcbc783dd)) - -### 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)) -- Haptics plugin ([#5](https://github.com/ionic-team/capacitor-plugins/issues/5)) ([95322d3](https://github.com/ionic-team/capacitor-plugins/commit/95322d385c855cff50582a7d3daff83fe4fe9e90)) -- 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)) -- **haptics:** Implement duration for vibration ([#618](https://github.com/ionic-team/capacitor-plugins/issues/618)) ([78e6b68](https://github.com/ionic-team/capacitor-plugins/commit/78e6b6886fa50318b1bfbe229e7318e521e3c245)) - -## [1.1.4](https://github.com/ionic-team/capacitor-plugins/compare/@capacitor/haptics@1.1.3...@capacitor/haptics@1.1.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.1.3](https://github.com/ionic-team/capacitor-plugins/compare/@capacitor/haptics@1.1.2...@capacitor/haptics@1.1.3) (2021-11-03) - -**Note:** Version bump only for package @capacitor/haptics - -## [1.1.2](https://github.com/ionic-team/capacitor-plugins/compare/@capacitor/haptics@1.1.1...@capacitor/haptics@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/haptics@1.1.0...@capacitor/haptics@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/haptics@1.0.3...@capacitor/haptics@1.1.0) (2021-09-27) - -### Features - -- **haptics:** Implement duration for vibration ([#618](https://github.com/ionic-team/capacitor-plugins/issues/618)) ([78e6b68](https://github.com/ionic-team/capacitor-plugins/commit/78e6b6886fa50318b1bfbe229e7318e521e3c245)) - -## [1.0.3](https://github.com/ionic-team/capacitor-plugins/compare/@capacitor/haptics@1.0.2...@capacitor/haptics@1.0.3) (2021-09-01) - -**Note:** Version bump only for package @capacitor/haptics - -## [1.0.2](https://github.com/ionic-team/capacitor-plugins/compare/@capacitor/haptics@1.0.1...@capacitor/haptics@1.0.2) (2021-06-23) - -**Note:** Version bump only for package @capacitor/haptics - -## [1.0.1](https://github.com/ionic-team/capacitor-plugins/compare/@capacitor/haptics@1.0.0...@capacitor/haptics@1.0.1) (2021-06-09) - -**Note:** Version bump only for package @capacitor/haptics - -# [1.0.0](https://github.com/ionic-team/capacitor-plugins/compare/@capacitor/haptics@0.3.10...@capacitor/haptics@1.0.0) (2021-05-19) - -**Note:** Version bump only for package @capacitor/haptics - -## [0.3.10](https://github.com/ionic-team/capacitor-plugins/compare/@capacitor/haptics@0.3.9...@capacitor/haptics@0.3.10) (2021-05-11) - -**Note:** Version bump only for package @capacitor/haptics - -## [0.3.9](https://github.com/ionic-team/capacitor-plugins/compare/@capacitor/haptics@0.3.8...@capacitor/haptics@0.3.9) (2021-05-10) - -**Note:** Version bump only for package @capacitor/haptics - -## [0.3.8](https://github.com/ionic-team/capacitor-plugins/compare/@capacitor/haptics@0.3.7...@capacitor/haptics@0.3.8) (2021-05-07) - -**Note:** Version bump only for package @capacitor/haptics - -## [0.3.7](https://github.com/ionic-team/capacitor-plugins/compare/@capacitor/haptics@0.3.6...@capacitor/haptics@0.3.7) (2021-04-29) - -**Note:** Version bump only for package @capacitor/haptics - -## [0.3.6](https://github.com/ionic-team/capacitor-plugins/compare/@capacitor/haptics@0.3.5...@capacitor/haptics@0.3.6) (2021-03-10) - -**Note:** Version bump only for package @capacitor/haptics - -## [0.3.5](https://github.com/ionic-team/capacitor-plugins/compare/@capacitor/haptics@0.3.4...@capacitor/haptics@0.3.5) (2021-03-02) - -**Note:** Version bump only for package @capacitor/haptics - -## [0.3.4](https://github.com/ionic-team/capacitor-plugins/compare/@capacitor/haptics@0.3.3...@capacitor/haptics@0.3.4) (2021-02-27) - -**Note:** Version bump only for package @capacitor/haptics - -## [0.3.3](https://github.com/ionic-team/capacitor-plugins/compare/@capacitor/haptics@0.3.2...@capacitor/haptics@0.3.3) (2021-02-10) - -**Note:** Version bump only for package @capacitor/haptics - -## [0.3.2](https://github.com/ionic-team/capacitor-plugins/compare/@capacitor/haptics@0.3.1...@capacitor/haptics@0.3.2) (2021-02-05) - -**Note:** Version bump only for package @capacitor/haptics - -## [0.3.1](https://github.com/ionic-team/capacitor-plugins/compare/@capacitor/haptics@0.3.0...@capacitor/haptics@0.3.1) (2021-01-26) - -**Note:** Version bump only for package @capacitor/haptics - -# [0.3.0](https://github.com/ionic-team/capacitor-plugins/compare/@capacitor/haptics@0.2.0...@capacitor/haptics@0.3.0) (2021-01-14) - -**Note:** Version bump only for package @capacitor/haptics - -# [0.2.0](https://github.com/ionic-team/capacitor-plugins/compare/@capacitor/haptics@0.1.4...@capacitor/haptics@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/haptics@0.1.3...@capacitor/haptics@0.1.4) (2021-01-13) - -**Note:** Version bump only for package @capacitor/haptics - -## [0.1.3](https://github.com/ionic-team/capacitor-plugins/compare/@capacitor/haptics@0.1.2...@capacitor/haptics@0.1.3) (2021-01-08) - -**Note:** Version bump only for package @capacitor/haptics - -## [0.1.2](https://github.com/ionic-team/capacitor-plugins/compare/@capacitor/haptics@0.1.1...@capacitor/haptics@0.1.2) (2020-12-27) - -**Note:** Version bump only for package @capacitor/haptics - -## [0.1.1](https://github.com/ionic-team/capacitor-plugins/compare/@capacitor/haptics@0.1.0...@capacitor/haptics@0.1.1) (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)) - -# [0.1.0](https://github.com/ionic-team/capacitor-plugins/compare/@capacitor/haptics@0.0.3...@capacitor/haptics@0.1.0) (2020-12-02) - -**Note:** Version bump only for package @capacitor/haptics - -## [0.0.3](https://github.com/ionic-team/capacitor-plugins/compare/@capacitor/haptics@0.0.2...@capacitor/haptics@0.0.3) (2020-10-30) - -**Note:** Version bump only for package @capacitor/haptics - -## [0.0.2](https://github.com/ionic-team/capacitor-plugins/compare/@capacitor/haptics@0.0.1...@capacitor/haptics@0.0.2) (2020-10-12) - -### Bug Fixes - -- better ignore rules for npm distribution ([#32](https://github.com/ionic-team/capacitor-plugins/issues/32)) ([b8d55b9](https://github.com/ionic-team/capacitor-plugins/commit/b8d55b9233e4ad7b8a1cd41110b4e580fc2a059f)) - -## 0.0.1 (2020-09-01) - -### Bug Fixes - -- use correct package in manifest files ([#22](https://github.com/ionic-team/capacitor-plugins/issues/22)) ([ab62987](https://github.com/ionic-team/capacitor-plugins/commit/ab629877e1951f944594f1b23e1bffefcbc783dd)) - -### Features - -- Haptics plugin ([#5](https://github.com/ionic-team/capacitor-plugins/issues/5)) ([95322d3](https://github.com/ionic-team/capacitor-plugins/commit/95322d385c855cff50582a7d3daff83fe4fe9e90)) diff --git a/haptics/CapacitorHaptics.podspec b/haptics/CapacitorHaptics.podspec deleted file mode 100644 index 53d8548d2b..0000000000 --- a/haptics/CapacitorHaptics.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 = 'CapacitorHaptics' - 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}', 'haptics/ios/Sources/**/*.{swift,h,m,c,cc,mm,cpp}' - s.ios.deployment_target = '14.0' - s.dependency 'Capacitor' - s.swift_version = '5.1' -end diff --git a/haptics/LICENSE b/haptics/LICENSE deleted file mode 100644 index 6652495cb2..0000000000 --- a/haptics/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. diff --git a/haptics/Package.swift b/haptics/Package.swift deleted file mode 100644 index 91b7b783b4..0000000000 --- a/haptics/Package.swift +++ /dev/null @@ -1,28 +0,0 @@ -// swift-tools-version: 5.9 -import PackageDescription - -let package = Package( - name: "CapacitorHaptics", - platforms: [.iOS(.v14)], - products: [ - .library( - name: "CapacitorHaptics", - targets: ["HapticsPlugin"]) - ], - dependencies: [ - .package(url: "https://github.com/ionic-team/capacitor-swift-pm.git", from: "7.0.0") - ], - targets: [ - .target( - name: "HapticsPlugin", - dependencies: [ - .product(name: "Capacitor", package: "capacitor-swift-pm"), - .product(name: "Cordova", package: "capacitor-swift-pm") - ], - path: "ios/Sources/HapticsPlugin"), - .testTarget( - name: "HapticsPluginTests", - dependencies: ["HapticsPlugin"], - path: "ios/Tests/HapticsPluginTests") - ] -) diff --git a/haptics/README.md b/haptics/README.md deleted file mode 100644 index fa9ffb729d..0000000000 --- a/haptics/README.md +++ /dev/null @@ -1,199 +0,0 @@ -# @capacitor/haptics - -The Haptics API provides physical feedback to the user through touch or vibration. - -On devices that don't have Taptic Engine or Vibrator, the API calls will resolve without performing any action. - -## Install - -```bash -npm install @capacitor/haptics -npx cap sync -``` - -## Example - -```typescript -import { Haptics, ImpactStyle } from '@capacitor/haptics'; - -const hapticsImpactMedium = async () => { - await Haptics.impact({ style: ImpactStyle.Medium }); -}; - -const hapticsImpactLight = async () => { - await Haptics.impact({ style: ImpactStyle.Light }); -}; - -const hapticsVibrate = async () => { - await Haptics.vibrate(); -}; - -const hapticsSelectionStart = async () => { - await Haptics.selectionStart(); -}; - -const hapticsSelectionChanged = async () => { - await Haptics.selectionChanged(); -}; - -const hapticsSelectionEnd = async () => { - await Haptics.selectionEnd(); -}; -``` - -## API - - - -* [`impact(...)`](#impact) -* [`notification(...)`](#notification) -* [`vibrate(...)`](#vibrate) -* [`selectionStart()`](#selectionstart) -* [`selectionChanged()`](#selectionchanged) -* [`selectionEnd()`](#selectionend) -* [Interfaces](#interfaces) -* [Enums](#enums) - - - - - - -### impact(...) - -```typescript -impact(options?: ImpactOptions | undefined) => Promise -``` - -Trigger a haptics "impact" feedback - -| Param | Type | -| ------------- | ------------------------------------------------------- | -| **`options`** | ImpactOptions | - -**Since:** 1.0.0 - --------------------- - - -### notification(...) - -```typescript -notification(options?: NotificationOptions | undefined) => Promise -``` - -Trigger a haptics "notification" feedback - -| Param | Type | -| ------------- | ------------------------------------------------------------------- | -| **`options`** | NotificationOptions | - -**Since:** 1.0.0 - --------------------- - - -### vibrate(...) - -```typescript -vibrate(options?: VibrateOptions | undefined) => Promise -``` - -Vibrate the device - -| Param | Type | -| ------------- | --------------------------------------------------------- | -| **`options`** | VibrateOptions | - -**Since:** 1.0.0 - --------------------- - - -### selectionStart() - -```typescript -selectionStart() => Promise -``` - -Trigger a selection started haptic hint - -**Since:** 1.0.0 - --------------------- - - -### selectionChanged() - -```typescript -selectionChanged() => Promise -``` - -Trigger a selection changed haptic hint. If a selection was -started already, this will cause the device to provide haptic -feedback - -**Since:** 1.0.0 - --------------------- - - -### selectionEnd() - -```typescript -selectionEnd() => Promise -``` - -If selectionStart() was called, selectionEnd() ends the selection. -For example, call this when a user has lifted their finger from a control - -**Since:** 1.0.0 - --------------------- - - -### Interfaces - - -#### ImpactOptions - -| Prop | Type | Description | Default | Since | -| ----------- | --------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------ | ----- | -| **`style`** | ImpactStyle | Impact Feedback Style The mass of the objects in the collision simulated by a [UIImpactFeedbackGenerator](https://developer.apple.com/documentation/uikit/uiimpactfeedbackstyle) object. | ImpactStyle.Heavy | 1.0.0 | - - -#### NotificationOptions - -| Prop | Type | Description | Default | Since | -| ---------- | ------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------- | ----- | -| **`type`** | NotificationType | Notification Feedback Type The type of notification feedback generated by a [UINotificationFeedbackGenerator](https://developer.apple.com/documentation/uikit/uinotificationfeedbacktype) object. | NotificationType.SUCCESS | 1.0.0 | - - -#### VibrateOptions - -| Prop | Type | Description | Default | Since | -| -------------- | ------------------- | ------------------------------------------ | ---------------- | ----- | -| **`duration`** | number | Duration of the vibration in milliseconds. | 300 | 1.0.0 | - - -### Enums - - -#### ImpactStyle - -| Members | Value | Description | Since | -| ------------ | --------------------- | ------------------------------------------------------------ | ----- | -| **`Heavy`** | 'HEAVY' | A collision between large, heavy user interface elements | 1.0.0 | -| **`Medium`** | 'MEDIUM' | A collision between moderately sized user interface elements | 1.0.0 | -| **`Light`** | 'LIGHT' | A collision between small, light user interface elements | 1.0.0 | - - -#### NotificationType - -| Members | Value | Description | Since | -| ------------- | ---------------------- | ------------------------------------------------------------------------------ | ----- | -| **`Success`** | 'SUCCESS' | A notification feedback type indicating that a task has completed successfully | 1.0.0 | -| **`Warning`** | 'WARNING' | A notification feedback type indicating that a task has produced a warning | 1.0.0 | -| **`Error`** | 'ERROR' | A notification feedback type indicating that a task has failed | 1.0.0 | - - diff --git a/haptics/android/.gitignore b/haptics/android/.gitignore deleted file mode 100644 index 796b96d1c4..0000000000 --- a/haptics/android/.gitignore +++ /dev/null @@ -1 +0,0 @@ -/build diff --git a/haptics/android/build.gradle b/haptics/android/build.gradle deleted file mode 100644 index 9525b75eca..0000000000 --- a/haptics/android/build.gradle +++ /dev/null @@ -1,79 +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.0' - androidxJunitVersion = project.hasProperty('androidxJunitVersion') ? rootProject.ext.androidxJunitVersion : '1.2.1' - androidxEspressoCoreVersion = project.hasProperty('androidxEspressoCoreVersion') ? rootProject.ext.androidxEspressoCoreVersion : '3.6.1' -} - -buildscript { - repositories { - google() - mavenCentral() - maven { - url "https://plugins.gradle.org/m2/" - } - } - dependencies { - classpath 'com.android.tools.build:gradle:8.7.2' - 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.haptics" - compileSdk project.hasProperty('compileSdkVersion') ? rootProject.ext.compileSdkVersion : 35 - defaultConfig { - minSdkVersion project.hasProperty('minSdkVersion') ? rootProject.ext.minSdkVersion : 23 - targetSdkVersion project.hasProperty('targetSdkVersion') ? rootProject.ext.targetSdkVersion : 35 - versionCode 1 - versionName "1.0" - testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" - } - buildTypes { - release { - minifyEnabled false - proguardFiles getDefaultProguardFile('proguard-android.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.appcompat:appcompat:$androidxAppCompatVersion" - testImplementation "junit:junit:$junitVersion" - androidTestImplementation "androidx.test.ext:junit:$androidxJunitVersion" - androidTestImplementation "androidx.test.espresso:espresso-core:$androidxEspressoCoreVersion" -} diff --git a/haptics/android/gradle.properties b/haptics/android/gradle.properties deleted file mode 100644 index 2e87c52f83..0000000000 --- a/haptics/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/haptics/android/gradle/wrapper/gradle-wrapper.jar b/haptics/android/gradle/wrapper/gradle-wrapper.jar deleted file mode 100644 index a4b76b9530..0000000000 Binary files a/haptics/android/gradle/wrapper/gradle-wrapper.jar and /dev/null differ diff --git a/haptics/android/gradle/wrapper/gradle-wrapper.properties b/haptics/android/gradle/wrapper/gradle-wrapper.properties deleted file mode 100644 index c1d5e01859..0000000000 --- a/haptics/android/gradle/wrapper/gradle-wrapper.properties +++ /dev/null @@ -1,7 +0,0 @@ -distributionBase=GRADLE_USER_HOME -distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-8.11.1-all.zip -networkTimeout=10000 -validateDistributionUrl=true -zipStoreBase=GRADLE_USER_HOME -zipStorePath=wrapper/dists diff --git a/haptics/android/gradlew b/haptics/android/gradlew deleted file mode 100755 index f5feea6d6b..0000000000 --- a/haptics/android/gradlew +++ /dev/null @@ -1,252 +0,0 @@ -#!/bin/sh - -# -# Copyright © 2015-2021 the original authors. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# https://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# -# SPDX-License-Identifier: Apache-2.0 -# - -############################################################################## -# -# Gradle start up script for POSIX generated by Gradle. -# -# Important for running: -# -# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is -# noncompliant, but you have some other compliant shell such as ksh or -# bash, then to run this script, type that shell name before the whole -# command line, like: -# -# ksh Gradle -# -# Busybox and similar reduced shells will NOT work, because this script -# requires all of these POSIX shell features: -# * functions; -# * expansions «$var», «${var}», «${var:-default}», «${var+SET}», -# «${var#prefix}», «${var%suffix}», and «$( cmd )»; -# * compound commands having a testable exit status, especially «case»; -# * various built-in commands including «command», «set», and «ulimit». -# -# Important for patching: -# -# (2) This script targets any POSIX shell, so it avoids extensions provided -# by Bash, Ksh, etc; in particular arrays are avoided. -# -# The "traditional" practice of packing multiple parameters into a -# space-separated string is a well documented source of bugs and security -# problems, so this is (mostly) avoided, by progressively accumulating -# options in "$@", and eventually passing that to Java. -# -# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS, -# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly; -# see the in-line comments for details. -# -# There are tweaks for specific operating systems such as AIX, CygWin, -# Darwin, MinGW, and NonStop. -# -# (3) This script is generated from the Groovy template -# https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt -# within the Gradle project. -# -# You can find Gradle at https://github.com/gradle/gradle/. -# -############################################################################## - -# Attempt to set APP_HOME - -# Resolve links: $0 may be a link -app_path=$0 - -# Need this for daisy-chained symlinks. -while - APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path - [ -h "$app_path" ] -do - ls=$( ls -ld "$app_path" ) - link=${ls#*' -> '} - 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 -' "$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=$APP_HOME/gradle/wrapper/gradle-wrapper.jar - - -# 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, 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" \ - org.gradle.wrapper.GradleWrapperMain \ - "$@" - -# 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/haptics/android/gradlew.bat b/haptics/android/gradlew.bat deleted file mode 100644 index 9b42019c79..0000000000 --- a/haptics/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=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar - - -@rem Execute Gradle -"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* - -: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/haptics/android/proguard-rules.pro b/haptics/android/proguard-rules.pro deleted file mode 100644 index f1b424510d..0000000000 --- a/haptics/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/haptics/android/settings.gradle b/haptics/android/settings.gradle deleted file mode 100644 index 1e5b8431f7..0000000000 --- a/haptics/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/haptics/android/src/androidTest/java/com/getcapacitor/android/ExampleInstrumentedTest.java b/haptics/android/src/androidTest/java/com/getcapacitor/android/ExampleInstrumentedTest.java deleted file mode 100644 index 58020e16cb..0000000000 --- a/haptics/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/haptics/android/src/main/AndroidManifest.xml b/haptics/android/src/main/AndroidManifest.xml deleted file mode 100644 index 0c0e8176d5..0000000000 --- a/haptics/android/src/main/AndroidManifest.xml +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/haptics/android/src/main/java/com/capacitorjs/plugins/haptics/Haptics.java b/haptics/android/src/main/java/com/capacitorjs/plugins/haptics/Haptics.java deleted file mode 100644 index 392aa41901..0000000000 --- a/haptics/android/src/main/java/com/capacitorjs/plugins/haptics/Haptics.java +++ /dev/null @@ -1,69 +0,0 @@ -package com.capacitorjs.plugins.haptics; - -import android.content.Context; -import android.os.Build; -import android.os.VibrationEffect; -import android.os.Vibrator; -import android.os.VibratorManager; -import com.capacitorjs.plugins.haptics.arguments.HapticsSelectionType; -import com.capacitorjs.plugins.haptics.arguments.HapticsVibrationType; - -public class Haptics { - - private boolean selectionStarted = false; - private final Vibrator vibrator; - - Haptics(Context context) { - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { - VibratorManager vibratorManager = (VibratorManager) context.getSystemService(Context.VIBRATOR_MANAGER_SERVICE); - this.vibrator = vibratorManager.getDefaultVibrator(); - } else { - this.vibrator = getDeprecatedVibrator(context); - } - } - - @SuppressWarnings("deprecation") - private Vibrator getDeprecatedVibrator(Context context) { - return (Vibrator) context.getSystemService(Context.VIBRATOR_SERVICE); - } - - public void vibrate(int duration) { - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { - vibrator.vibrate(VibrationEffect.createOneShot(duration, VibrationEffect.DEFAULT_AMPLITUDE)); - } else { - vibratePre26(duration); - } - } - - @SuppressWarnings({ "deprecation" }) - private void vibratePre26(int duration) { - vibrator.vibrate(duration); - } - - @SuppressWarnings({ "deprecation" }) - private void vibratePre26(long[] pattern) { - vibrator.vibrate(pattern, -1); - } - - public void selectionStart() { - this.selectionStarted = true; - } - - public void selectionChanged() { - if (this.selectionStarted) { - performHaptics(new HapticsSelectionType()); - } - } - - public void selectionEnd() { - this.selectionStarted = false; - } - - public void performHaptics(HapticsVibrationType type) { - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { - vibrator.vibrate(VibrationEffect.createWaveform(type.getTimings(), type.getAmplitudes(), -1)); - } else { - vibratePre26(type.getOldSDKPattern()); - } - } -} diff --git a/haptics/android/src/main/java/com/capacitorjs/plugins/haptics/HapticsPlugin.java b/haptics/android/src/main/java/com/capacitorjs/plugins/haptics/HapticsPlugin.java deleted file mode 100644 index 3aa3590c0b..0000000000 --- a/haptics/android/src/main/java/com/capacitorjs/plugins/haptics/HapticsPlugin.java +++ /dev/null @@ -1,56 +0,0 @@ -package com.capacitorjs.plugins.haptics; - -import com.capacitorjs.plugins.haptics.arguments.HapticsImpactType; -import com.capacitorjs.plugins.haptics.arguments.HapticsNotificationType; -import com.getcapacitor.Plugin; -import com.getcapacitor.PluginCall; -import com.getcapacitor.PluginMethod; -import com.getcapacitor.annotation.CapacitorPlugin; - -@CapacitorPlugin(name = "Haptics") -public class HapticsPlugin extends Plugin { - - private Haptics implementation; - - @Override - public void load() { - implementation = new Haptics(getContext()); - } - - @PluginMethod - public void vibrate(PluginCall call) { - int duration = call.getInt("duration", 300); - implementation.vibrate(duration); - call.resolve(); - } - - @PluginMethod - public void impact(PluginCall call) { - implementation.performHaptics(HapticsImpactType.fromString(call.getString("style"))); - call.resolve(); - } - - @PluginMethod - public void notification(PluginCall call) { - implementation.performHaptics(HapticsNotificationType.fromString(call.getString("type"))); - call.resolve(); - } - - @PluginMethod - public void selectionStart(PluginCall call) { - implementation.selectionStart(); - call.resolve(); - } - - @PluginMethod - public void selectionChanged(PluginCall call) { - implementation.selectionChanged(); - call.resolve(); - } - - @PluginMethod - public void selectionEnd(PluginCall call) { - implementation.selectionEnd(); - call.resolve(); - } -} diff --git a/haptics/android/src/main/java/com/capacitorjs/plugins/haptics/arguments/HapticsImpactType.java b/haptics/android/src/main/java/com/capacitorjs/plugins/haptics/arguments/HapticsImpactType.java deleted file mode 100644 index 9dcf4fc0a6..0000000000 --- a/haptics/android/src/main/java/com/capacitorjs/plugins/haptics/arguments/HapticsImpactType.java +++ /dev/null @@ -1,43 +0,0 @@ -package com.capacitorjs.plugins.haptics.arguments; - -public enum HapticsImpactType implements HapticsVibrationType { - LIGHT("LIGHT", new long[] { 0, 50 }, new int[] { 0, 110 }, new long[] { 0, 20 }), - MEDIUM("MEDIUM", new long[] { 0, 43 }, new int[] { 0, 180 }, new long[] { 0, 43 }), - HEAVY("HEAVY", new long[] { 0, 60 }, new int[] { 0, 255 }, new long[] { 0, 61 }); - - private final String type; - private final long[] timings; - private final int[] amplitudes; - private final long[] oldSDKPattern; - - HapticsImpactType(String type, long[] timings, int[] amplitudes, long[] oldSDKPattern) { - this.type = type; - this.timings = timings; - this.amplitudes = amplitudes; - this.oldSDKPattern = oldSDKPattern; - } - - public static HapticsImpactType fromString(String style) { - for (HapticsImpactType nt : values()) { - if (nt.type.equals(style)) { - return nt; - } - } - return HEAVY; - } - - @Override - public long[] getTimings() { - return timings; - } - - @Override - public int[] getAmplitudes() { - return amplitudes; - } - - @Override - public long[] getOldSDKPattern() { - return oldSDKPattern; - } -} diff --git a/haptics/android/src/main/java/com/capacitorjs/plugins/haptics/arguments/HapticsNotificationType.java b/haptics/android/src/main/java/com/capacitorjs/plugins/haptics/arguments/HapticsNotificationType.java deleted file mode 100644 index 9a3e116115..0000000000 --- a/haptics/android/src/main/java/com/capacitorjs/plugins/haptics/arguments/HapticsNotificationType.java +++ /dev/null @@ -1,48 +0,0 @@ -package com.capacitorjs.plugins.haptics.arguments; - -public enum HapticsNotificationType implements HapticsVibrationType { - SUCCESS("SUCCESS", new long[] { 0, 35, 65, 21 }, new int[] { 0, 250, 0, 180 }, new long[] { 0, 35, 65, 21 }), - WARNING( - "WARNING", - new long[] { 0, 30, 40, 30, 50, 60 }, - new int[] { 255, 255, 255, 255, 255, 255 }, - new long[] { 0, 30, 40, 30, 50, 60 } - ), - ERROR("ERROR", new long[] { 0, 27, 45, 50 }, new int[] { 0, 120, 0, 250 }, new long[] { 0, 27, 45, 50 }); - - private final String type; - private final long[] timings; - private final int[] amplitudes; - private final long[] oldSDKPattern; - - HapticsNotificationType(String type, long[] timings, int[] amplitudes, long[] oldSDKPattern) { - this.type = type; - this.timings = timings; - this.amplitudes = amplitudes; - this.oldSDKPattern = oldSDKPattern; - } - - public static HapticsNotificationType fromString(String type) { - for (HapticsNotificationType nt : values()) { - if (nt.type.equals(type)) { - return nt; - } - } - return SUCCESS; - } - - @Override - public long[] getTimings() { - return timings; - } - - @Override - public int[] getAmplitudes() { - return amplitudes; - } - - @Override - public long[] getOldSDKPattern() { - return oldSDKPattern; - } -} diff --git a/haptics/android/src/main/java/com/capacitorjs/plugins/haptics/arguments/HapticsSelectionType.java b/haptics/android/src/main/java/com/capacitorjs/plugins/haptics/arguments/HapticsSelectionType.java deleted file mode 100644 index cec51bbae8..0000000000 --- a/haptics/android/src/main/java/com/capacitorjs/plugins/haptics/arguments/HapticsSelectionType.java +++ /dev/null @@ -1,23 +0,0 @@ -package com.capacitorjs.plugins.haptics.arguments; - -public class HapticsSelectionType implements HapticsVibrationType { - - private static final long[] timings = { 0, 100 }; - private static final int[] amplitudes = { 0, 100 }; - private static final long[] oldSDKPattern = { 0, 70 }; - - @Override - public long[] getTimings() { - return timings; - } - - @Override - public int[] getAmplitudes() { - return amplitudes; - } - - @Override - public long[] getOldSDKPattern() { - return oldSDKPattern; - } -} diff --git a/haptics/android/src/main/java/com/capacitorjs/plugins/haptics/arguments/HapticsVibrationType.java b/haptics/android/src/main/java/com/capacitorjs/plugins/haptics/arguments/HapticsVibrationType.java deleted file mode 100644 index c8399c5a51..0000000000 --- a/haptics/android/src/main/java/com/capacitorjs/plugins/haptics/arguments/HapticsVibrationType.java +++ /dev/null @@ -1,9 +0,0 @@ -package com.capacitorjs.plugins.haptics.arguments; - -public interface HapticsVibrationType { - long[] getTimings(); - - int[] getAmplitudes(); - - long[] getOldSDKPattern(); -} diff --git a/haptics/android/src/test/java/com/getcapacitor/ExampleUnitTest.java b/haptics/android/src/test/java/com/getcapacitor/ExampleUnitTest.java deleted file mode 100644 index a0fed0cfb7..0000000000 --- a/haptics/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/haptics/ios/Sources/HapticsPlugin/Haptics.swift b/haptics/ios/Sources/HapticsPlugin/Haptics.swift deleted file mode 100644 index 97ad97d8a2..0000000000 --- a/haptics/ios/Sources/HapticsPlugin/Haptics.swift +++ /dev/null @@ -1,69 +0,0 @@ -import AudioToolbox -import UIKit -import CoreHaptics - -@objc public class Haptics: NSObject { - - var selectionFeedbackGenerator: UISelectionFeedbackGenerator? - - @objc public func impact(_ impactStyle: UIImpactFeedbackGenerator.FeedbackStyle) { - let generator = UIImpactFeedbackGenerator(style: impactStyle) - generator.impactOccurred() - } - - @objc public func notification(_ notificationType: UINotificationFeedbackGenerator.FeedbackType) { - let generator = UINotificationFeedbackGenerator() - generator.notificationOccurred(notificationType) - } - - @objc public func selectionStart() { - self.selectionFeedbackGenerator = UISelectionFeedbackGenerator() - self.selectionFeedbackGenerator?.prepare() - } - - @objc public func selectionChanged() { - if let generator = self.selectionFeedbackGenerator { - generator.selectionChanged() - generator.prepare() - } - } - - @objc public func selectionEnd() { - self.selectionFeedbackGenerator = nil - } - - @objc public func vibrate() { - AudioServicesPlayAlertSound(kSystemSoundID_Vibrate) - } - - @objc public func vibrate(_ duration: Double) { - if CHHapticEngine.capabilitiesForHardware().supportsHaptics { - do { - let engine = try CHHapticEngine() - try engine.start() - engine.resetHandler = { [] in - do { - try engine.start() - } catch { - self.vibrate() - } - } - let intensity = CHHapticEventParameter(parameterID: .hapticIntensity, value: 1.0) - let sharpness = CHHapticEventParameter(parameterID: .hapticSharpness, value: 1.0) - - let continuousEvent = CHHapticEvent(eventType: .hapticContinuous, - parameters: [intensity, sharpness], - relativeTime: 0.0, - duration: duration) - let pattern = try CHHapticPattern(events: [continuousEvent], parameters: []) - let player = try engine.makePlayer(with: pattern) - - try player.start(atTime: 0) - } catch { - vibrate() - } - } else { - vibrate() - } - } -} diff --git a/haptics/ios/Sources/HapticsPlugin/HapticsPlugin.swift b/haptics/ios/Sources/HapticsPlugin/HapticsPlugin.swift deleted file mode 100644 index 49f2229a6e..0000000000 --- a/haptics/ios/Sources/HapticsPlugin/HapticsPlugin.swift +++ /dev/null @@ -1,75 +0,0 @@ -import Capacitor - -@objc(HapticsPlugin) -public class HapticsPlugin: CAPPlugin, CAPBridgedPlugin { - public let identifier = "HapticsPlugin" - public let jsName = "Haptics" - public let pluginMethods: [CAPPluginMethod] = [ - CAPPluginMethod(name: "impact", returnType: CAPPluginReturnPromise), - CAPPluginMethod(name: "notification", returnType: CAPPluginReturnPromise), - CAPPluginMethod(name: "selectionStart", returnType: CAPPluginReturnPromise), - CAPPluginMethod(name: "selectionChanged", returnType: CAPPluginReturnPromise), - CAPPluginMethod(name: "selectionEnd", returnType: CAPPluginReturnPromise), - CAPPluginMethod(name: "vibrate", returnType: CAPPluginReturnPromise) - ] - private let implementation = Haptics() - - @objc public func impact(_ call: CAPPluginCall) { - var impactStyle = UIImpactFeedbackGenerator.FeedbackStyle.heavy - if let style = call.options["style"] as? String { - if style == "MEDIUM" { - impactStyle = UIImpactFeedbackGenerator.FeedbackStyle.medium - } else if style == "LIGHT" { - impactStyle = UIImpactFeedbackGenerator.FeedbackStyle.light - } - } - DispatchQueue.main.async { - self.implementation.impact(impactStyle) - } - call.resolve() - } - - @objc public func notification(_ call: CAPPluginCall) { - var notificationType = UINotificationFeedbackGenerator.FeedbackType.success - if let type = call.options["type"] as? String { - if type == "WARNING" { - notificationType = UINotificationFeedbackGenerator.FeedbackType.warning - } else if type == "ERROR" { - notificationType = UINotificationFeedbackGenerator.FeedbackType.error - } - } - DispatchQueue.main.async { - self.implementation.notification(notificationType) - } - call.resolve() - } - - @objc public func selectionStart(_ call: CAPPluginCall) { - DispatchQueue.main.async { - self.implementation.selectionStart() - } - call.resolve() - } - - @objc public func selectionChanged(_ call: CAPPluginCall) { - DispatchQueue.main.async { - self.implementation.selectionChanged() - } - call.resolve() - } - - @objc public func selectionEnd(_ call: CAPPluginCall) { - DispatchQueue.main.async { - self.implementation.selectionEnd() - } - call.resolve() - } - - @objc public func vibrate(_ call: CAPPluginCall) { - let duration = call.getDouble("duration", 300)/1000 - DispatchQueue.main.async { - self.implementation.vibrate(duration) - } - call.resolve() - } -} diff --git a/haptics/ios/Tests/HapticsPluginTests/HapticsPluginTests.swift b/haptics/ios/Tests/HapticsPluginTests/HapticsPluginTests.swift deleted file mode 100644 index 7325c29f92..0000000000 --- a/haptics/ios/Tests/HapticsPluginTests/HapticsPluginTests.swift +++ /dev/null @@ -1,12 +0,0 @@ -import XCTest -@testable import HapticsPlugin - -final class HapticsPluginTests: 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/haptics/package.json b/haptics/package.json deleted file mode 100644 index 798576e53e..0000000000 --- a/haptics/package.json +++ /dev/null @@ -1,84 +0,0 @@ -{ - "name": "@capacitor/haptics", - "version": "7.0.1", - "description": "The Haptics API provides physical feedback to the user through touch or vibration.", - "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", - "CapacitorHaptics.podspec" - ], - "author": "Ionic ", - "license": "MIT", - "repository": { - "type": "git", - "url": "https://github.com/ionic-team/capacitor-plugins" - }, - "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 CapacitorHaptics -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}\"", - "swiftlint": "node-swiftlint", - "docgen": "docgen --api HapticsPlugin --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", - "publish:cocoapod": "pod trunk push ./CapacitorHaptics.podspec --allow-warnings" - }, - "devDependencies": { - "@capacitor/android": "^7.0.0", - "@capacitor/core": "^7.0.0", - "@capacitor/docgen": "0.2.2", - "@capacitor/ios": "^7.0.0", - "@ionic/eslint-config": "^0.4.0", - "@ionic/prettier-config": "~1.0.1", - "@ionic/swiftlint-config": "^1.1.2", - "eslint": "^8.57.0", - "prettier": "~2.3.0", - "prettier-plugin-java": "~1.0.2", - "rimraf": "^6.0.1", - "rollup": "^4.26.0", - "swiftlint": "^1.0.1", - "typescript": "~4.1.5" - }, - "peerDependencies": { - "@capacitor/core": ">=7.0.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/haptics/rollup.config.mjs b/haptics/rollup.config.mjs deleted file mode 100644 index 242f6ae132..0000000000 --- a/haptics/rollup.config.mjs +++ /dev/null @@ -1,22 +0,0 @@ -export default { - input: 'dist/esm/index.js', - output: [ - { - file: 'dist/plugin.js', - format: 'iife', - name: 'capacitorHaptics', - 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/haptics/src/definitions.ts b/haptics/src/definitions.ts deleted file mode 100644 index aa9f61b8f0..0000000000 --- a/haptics/src/definitions.ts +++ /dev/null @@ -1,126 +0,0 @@ -export interface HapticsPlugin { - /** - * Trigger a haptics "impact" feedback - * - * @since 1.0.0 - */ - impact(options?: ImpactOptions): Promise; - - /** - * Trigger a haptics "notification" feedback - * - * @since 1.0.0 - */ - notification(options?: NotificationOptions): Promise; - - /** - * Vibrate the device - * - * @since 1.0.0 - */ - vibrate(options?: VibrateOptions): Promise; - - /** - * Trigger a selection started haptic hint - * - * @since 1.0.0 - */ - selectionStart(): Promise; - - /** - * Trigger a selection changed haptic hint. If a selection was - * started already, this will cause the device to provide haptic - * feedback - * - * @since 1.0.0 - */ - selectionChanged(): Promise; - - /** - * If selectionStart() was called, selectionEnd() ends the selection. - * For example, call this when a user has lifted their finger from a control - * - * @since 1.0.0 - */ - selectionEnd(): Promise; -} - -export interface ImpactOptions { - /** - * Impact Feedback Style - * - * The mass of the objects in the collision simulated by a [UIImpactFeedbackGenerator](https://developer.apple.com/documentation/uikit/uiimpactfeedbackstyle) object. - * - * @default ImpactStyle.Heavy - * @since 1.0.0 - */ - style: ImpactStyle; -} - -export enum ImpactStyle { - /** - * A collision between large, heavy user interface elements - * - * @since 1.0.0 - */ - Heavy = 'HEAVY', - - /** - * A collision between moderately sized user interface elements - * - * @since 1.0.0 - */ - Medium = 'MEDIUM', - - /** - * A collision between small, light user interface elements - * - * @since 1.0.0 - */ - Light = 'LIGHT', -} - -export interface NotificationOptions { - /** - * Notification Feedback Type - * - * The type of notification feedback generated by a [UINotificationFeedbackGenerator](https://developer.apple.com/documentation/uikit/uinotificationfeedbacktype) object. - * - * @default NotificationType.SUCCESS - * @since 1.0.0 - */ - type: NotificationType; -} - -export enum NotificationType { - /** - * A notification feedback type indicating that a task has completed successfully - * - * @since 1.0.0 - */ - Success = 'SUCCESS', - - /** - * A notification feedback type indicating that a task has produced a warning - * - * @since 1.0.0 - */ - Warning = 'WARNING', - - /** - * A notification feedback type indicating that a task has failed - * - * @since 1.0.0 - */ - Error = 'ERROR', -} - -export interface VibrateOptions { - /** - * Duration of the vibration in milliseconds. - * - * @default 300 - * @since 1.0.0 - */ - duration: number; -} diff --git a/haptics/src/index.ts b/haptics/src/index.ts deleted file mode 100644 index 7fa70d042e..0000000000 --- a/haptics/src/index.ts +++ /dev/null @@ -1,10 +0,0 @@ -import { registerPlugin } from '@capacitor/core'; - -import type { HapticsPlugin } from './definitions'; - -const Haptics = registerPlugin('Haptics', { - web: () => import('./web').then(m => new m.HapticsWeb()), -}); - -export * from './definitions'; -export { Haptics }; diff --git a/haptics/src/web.ts b/haptics/src/web.ts deleted file mode 100644 index 55ed83037e..0000000000 --- a/haptics/src/web.ts +++ /dev/null @@ -1,70 +0,0 @@ -import { WebPlugin } from '@capacitor/core'; - -import { ImpactStyle, NotificationType } from './definitions'; -import type { - HapticsPlugin, - ImpactOptions, - NotificationOptions, - VibrateOptions, -} from './definitions'; - -export class HapticsWeb extends WebPlugin implements HapticsPlugin { - selectionStarted = false; - - async impact(options?: ImpactOptions): Promise { - const pattern = this.patternForImpact(options?.style); - this.vibrateWithPattern(pattern); - } - - async notification(options?: NotificationOptions): Promise { - const pattern = this.patternForNotification(options?.type); - this.vibrateWithPattern(pattern); - } - - async vibrate(options?: VibrateOptions): Promise { - const duration = options?.duration || 300; - this.vibrateWithPattern([duration]); - } - - async selectionStart(): Promise { - this.selectionStarted = true; - } - - async selectionChanged(): Promise { - if (this.selectionStarted) { - this.vibrateWithPattern([70]); - } - } - - async selectionEnd(): Promise { - this.selectionStarted = false; - } - - private patternForImpact(style: ImpactStyle = ImpactStyle.Heavy): number[] { - if (style === ImpactStyle.Medium) { - return [43]; - } else if (style === ImpactStyle.Light) { - return [20]; - } - return [61]; - } - - private patternForNotification( - type: NotificationType = NotificationType.Success, - ): number[] { - if (type === NotificationType.Warning) { - return [30, 40, 30, 50, 60]; - } else if (type === NotificationType.Error) { - return [27, 45, 50]; - } - return [35, 65, 21]; - } - - private vibrateWithPattern(pattern: number[]) { - if (navigator.vibrate) { - navigator.vibrate(pattern); - } else { - throw this.unavailable('Browser does not support the vibrate API'); - } - } -} diff --git a/haptics/tsconfig.json b/haptics/tsconfig.json deleted file mode 100644 index f2e88e6a07..0000000000 --- a/haptics/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"] -} diff --git a/keyboard/.eslintignore b/keyboard/.eslintignore deleted file mode 100644 index 9d0b71a3c7..0000000000 --- a/keyboard/.eslintignore +++ /dev/null @@ -1,2 +0,0 @@ -build -dist diff --git a/keyboard/.gitignore b/keyboard/.gitignore deleted file mode 100644 index 6817637958..0000000000 --- a/keyboard/.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/keyboard/.prettierignore b/keyboard/.prettierignore deleted file mode 100644 index 9d0b71a3c7..0000000000 --- a/keyboard/.prettierignore +++ /dev/null @@ -1,2 +0,0 @@ -build -dist diff --git a/keyboard/CHANGELOG.md b/keyboard/CHANGELOG.md deleted file mode 100644 index 17569a2dbe..0000000000 --- a/keyboard/CHANGELOG.md +++ /dev/null @@ -1,356 +0,0 @@ -# Change Log - -All notable changes to this project will be documented in this file. -See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. - -## [7.0.1](https://github.com/ionic-team/capacitor-plugins/compare/@capacitor/keyboard@7.0.0...@capacitor/keyboard@7.0.1) (2025-04-02) - -**Note:** Version bump only for package @capacitor/keyboard - -# [7.0.0](https://github.com/ionic-team/capacitor-plugins/compare/@capacitor/keyboard@7.0.0-rc.0...@capacitor/keyboard@7.0.0) (2025-01-20) - -**Note:** Version bump only for package @capacitor/keyboard - -# [7.0.0-rc.0](https://github.com/ionic-team/capacitor-plugins/compare/@capacitor/keyboard@7.0.0-alpha.2...@capacitor/keyboard@7.0.0-rc.0) (2025-01-13) - -**Note:** Version bump only for package @capacitor/keyboard - -# [7.0.0-alpha.2](https://github.com/ionic-team/capacitor-plugins/compare/@capacitor/keyboard@7.0.0-alpha.1...@capacitor/keyboard@7.0.0-alpha.2) (2024-12-19) - -**Note:** Version bump only for package @capacitor/keyboard - -# [7.0.0-alpha.1](https://github.com/ionic-team/capacitor-plugins/compare/@capacitor/keyboard@6.0.2...@capacitor/keyboard@7.0.0-alpha.1) (2024-12-16) - -**Note:** Version bump only for package @capacitor/keyboard - -## [6.0.2](https://github.com/ionic-team/capacitor-plugins/compare/@capacitor/keyboard@6.0.1...@capacitor/keyboard@6.0.2) (2024-08-08) - -**Note:** Version bump only for package @capacitor/keyboard - -## [6.0.1](https://github.com/ionic-team/capacitor-plugins/compare/@capacitor/keyboard@6.0.0...@capacitor/keyboard@6.0.1) (2024-06-13) - -### Bug Fixes - -- **ios:** apply filter to fetch correct scene object ([#2102](https://github.com/ionic-team/capacitor-plugins/issues/2102)) ([acd334d](https://github.com/ionic-team/capacitor-plugins/commit/acd334dae7fc7545446cafc8fbb469200f7831f8)) - -# [6.0.0](https://github.com/ionic-team/capacitor-plugins/compare/@capacitor/keyboard@6.0.0-rc.1...@capacitor/keyboard@6.0.0) (2024-04-15) - -**Note:** Version bump only for package @capacitor/keyboard - -# [6.0.0-rc.1](https://github.com/ionic-team/capacitor-plugins/compare/@capacitor/keyboard@6.0.0-rc.0...@capacitor/keyboard@6.0.0-rc.1) (2024-03-25) - -**Note:** Version bump only for package @capacitor/keyboard - -# [6.0.0-rc.0](https://github.com/ionic-team/capacitor-plugins/compare/@capacitor/keyboard@6.0.0-beta.1...@capacitor/keyboard@6.0.0-rc.0) (2024-02-07) - -**Note:** Version bump only for package @capacitor/keyboard - -# [6.0.0-beta.1](https://github.com/ionic-team/capacitor-plugins/compare/@capacitor/keyboard@6.0.0-beta.0...@capacitor/keyboard@6.0.0-beta.1) (2023-12-14) - -**Note:** Version bump only for package @capacitor/keyboard - -# [6.0.0-beta.0](https://github.com/ionic-team/capacitor-plugins/compare/@capacitor/keyboard@6.0.0-alpha.2...@capacitor/keyboard@6.0.0-beta.0) (2023-12-13) - -### Bug Fixes - -- **keyboard:** Change keyboard style during setStyle ([#1920](https://github.com/ionic-team/capacitor-plugins/issues/1920)) ([f5ef4dc](https://github.com/ionic-team/capacitor-plugins/commit/f5ef4dc53279573d76c683dc2ac783f8d261a15b)) - -# [6.0.0-alpha.2](https://github.com/ionic-team/capacitor-plugins/compare/@capacitor/keyboard@6.0.0-alpha.1...@capacitor/keyboard@6.0.0-alpha.2) (2023-11-15) - -### Bug Fixes - -- **keyboard:** distribute the new SPM files ([#1895](https://github.com/ionic-team/capacitor-plugins/issues/1895)) ([c5f7fbb](https://github.com/ionic-team/capacitor-plugins/commit/c5f7fbb99d5c73bfcfaa267155a4f641d4510177)) - -# [6.0.0-alpha.1](https://github.com/ionic-team/capacitor-plugins/compare/@capacitor/keyboard@5.0.6...@capacitor/keyboard@6.0.0-alpha.1) (2023-11-08) - -### Features - -- **app,haptics,status-bar,keyboard:** Supporting Swift Package Manager ([#1886](https://github.com/ionic-team/capacitor-plugins/issues/1886)) ([918ea30](https://github.com/ionic-team/capacitor-plugins/commit/918ea30a95f80d740f39e9ab472ed90f9d4a6aba)) - -## [5.0.7](https://github.com/ionic-team/capacitor-plugins/compare/@capacitor/keyboard@5.0.6...@capacitor/keyboard@5.0.7) (2023-12-15) - -### Bug Fixes - -- **keyboard:** Change keyboard style during setStyle ([#1935](https://github.com/ionic-team/capacitor-plugins/issues/1935)) ([3b520b8](https://github.com/ionic-team/capacitor-plugins/commit/3b520b845563f08897f55eb44fe212ae384a7675)) - -## [5.0.6](https://github.com/ionic-team/capacitor-plugins/compare/@capacitor/keyboard@5.0.5...@capacitor/keyboard@5.0.6) (2023-07-12) - -**Note:** Version bump only for package @capacitor/keyboard - -## [5.0.5](https://github.com/ionic-team/capacitor-plugins/compare/@capacitor/keyboard@5.0.4...@capacitor/keyboard@5.0.5) (2023-06-29) - -**Note:** Version bump only for package @capacitor/keyboard - -## [5.0.4](https://github.com/ionic-team/capacitor-plugins/compare/@capacitor/keyboard@5.0.3...@capacitor/keyboard@5.0.4) (2023-06-08) - -**Note:** Version bump only for package @capacitor/keyboard - -## [5.0.3](https://github.com/ionic-team/capacitor-plugins/compare/@capacitor/keyboard@5.0.2...@capacitor/keyboard@5.0.3) (2023-06-08) - -### Bug Fixes - -- **keyboard:** fixing webview resizing when keyboard visible ([#1637](https://github.com/ionic-team/capacitor-plugins/issues/1637)) ([32871f7](https://github.com/ionic-team/capacitor-plugins/commit/32871f700c734c6d590e7e93eed4755971d9e5bf)) - -## [5.0.2](https://github.com/ionic-team/capacitor-plugins/compare/@capacitor/keyboard@5.0.1...@capacitor/keyboard@5.0.2) (2023-05-09) - -**Note:** Version bump only for package @capacitor/keyboard - -## [5.0.1](https://github.com/ionic-team/capacitor-plugins/compare/@capacitor/keyboard@5.0.0...@capacitor/keyboard@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/keyboard@5.0.0-beta.1...@capacitor/keyboard@5.0.0) (2023-05-03) - -**Note:** Version bump only for package @capacitor/keyboard - -# [5.0.0-beta.1](https://github.com/ionic-team/capacitor-plugins/compare/@capacitor/keyboard@5.0.0-beta.0...@capacitor/keyboard@5.0.0-beta.1) (2023-04-21) - -### Bug Fixes - -- **keyboard:** calculate height correctly when using setDecorDitsSystemWindows ([#1523](https://github.com/ionic-team/capacitor-plugins/issues/1523)) ([14a747c](https://github.com/ionic-team/capacitor-plugins/commit/14a747c0c20369fbdfeb22e1d6c49a3f0a27f6df)) - -### 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/keyboard@5.0.0-alpha.1...@capacitor/keyboard@5.0.0-beta.0) (2023-03-31) - -**Note:** Version bump only for package @capacitor/keyboard - -# [5.0.0-alpha.1](https://github.com/ionic-team/capacitor-plugins/compare/@capacitor/keyboard@4.1.1...@capacitor/keyboard@5.0.0-alpha.1) (2023-03-16) - -### Features - -- **android:** Removing enableJetifier ([d66f9cb](https://github.com/ionic-team/capacitor-plugins/commit/d66f9cbd9da7e3b1d8c64ca6a5b45156867d4a04)) - -## [4.1.1](https://github.com/ionic-team/capacitor-plugins/compare/@capacitor/keyboard@4.1.0...@capacitor/keyboard@4.1.1) (2023-01-17) - -### Bug Fixes - -- **keyboard:** calculate overlapping keyboard height when using Stage Manager ([#1327](https://github.com/ionic-team/capacitor-plugins/issues/1327)) ([bc0b787](https://github.com/ionic-team/capacitor-plugins/commit/bc0b7876af30e190158114bc7afc68f80981fedf)) - -# [4.1.0](https://github.com/ionic-team/capacitor-plugins/compare/@capacitor/keyboard@1.2.3...@capacitor/keyboard@4.1.0) (2022-11-16) - -## 4.0.1 (2022-07-28) - -# 4.0.0 (2022-07-27) - -### Features - -- **keyboard:** add getResizeMode function ([#1082](https://github.com/ionic-team/capacitor-plugins/issues/1082)) ([594d854](https://github.com/ionic-team/capacitor-plugins/commit/594d8545fa1560c2d518d0eff30e2702f1b90a45)) - -# 4.0.0-beta.2 (2022-07-08) - -# 4.0.0-beta.0 (2022-06-27) - -### Bug Fixes - -- Make removeAllListeners return a promise ([#895](https://github.com/ionic-team/capacitor-plugins/issues/895)) ([e5c49d6](https://github.com/ionic-team/capacitor-plugins/commit/e5c49d64445dca70286334e6a0441d8021197b13)) - -### Features - -- 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)) -- **keyboard:** Use KeyboardStyle for style config option ([#969](https://github.com/ionic-team/capacitor-plugins/issues/969)) ([42a01b4](https://github.com/ionic-team/capacitor-plugins/commit/42a01b46d5a9e39bfb3ac05d5595aecaeb790557)) -- 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)) -- 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)) - -## [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/keyboard - -# [4.0.0](https://github.com/ionic-team/capacitor-plugins/compare/4.0.0-beta.2...4.0.0) (2022-07-27) - -### Features - -- **keyboard:** add getResizeMode function ([#1082](https://github.com/ionic-team/capacitor-plugins/issues/1082)) ([594d854](https://github.com/ionic-team/capacitor-plugins/commit/594d8545fa1560c2d518d0eff30e2702f1b90a45)) - -# [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/keyboard - -# 4.0.0-beta.0 (2022-06-27) - -### Bug Fixes - -- **ios:** Check if UIApplicationDelegate responds to window selector ([#1032](https://github.com/ionic-team/capacitor-plugins/issues/1032)) ([5be1466](https://github.com/ionic-team/capacitor-plugins/commit/5be14662181305e48bc097ade6504c579880bda8)) -- 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)) -- Make removeAllListeners return a promise ([#895](https://github.com/ionic-team/capacitor-plugins/issues/895)) ([e5c49d6](https://github.com/ionic-team/capacitor-plugins/commit/e5c49d64445dca70286334e6a0441d8021197b13)) -- **keyboard:** Prevent null pointer if native listener not set ([#789](https://github.com/ionic-team/capacitor-plugins/issues/789)) ([8b6f119](https://github.com/ionic-team/capacitor-plugins/commit/8b6f119ad5143d380aa9ebd7c73b701ad5a1f10a)) -- correct addListeners links ([#655](https://github.com/ionic-team/capacitor-plugins/issues/655)) ([f9871e7](https://github.com/ionic-team/capacitor-plugins/commit/f9871e7bd53478addb21155e148829f550c0e457)) -- 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)) -- **keyboard:** remove deprecated warnings ([#122](https://github.com/ionic-team/capacitor-plugins/issues/122)) ([a6e207c](https://github.com/ionic-team/capacitor-plugins/commit/a6e207c47dfac14d6cbcaa2a942b7b04cae3dae8)) -- **keyboard:** Use new Config name for scroll ([#335](https://github.com/ionic-team/capacitor-plugins/issues/335)) ([bec3d22](https://github.com/ionic-team/capacitor-plugins/commit/bec3d22e67dfb22db5b5bb774f53fb01a34f116f)) -- 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 32 ([#970](https://github.com/ionic-team/capacitor-plugins/issues/970)) ([fa70d96](https://github.com/ionic-team/capacitor-plugins/commit/fa70d96f141af751aae53ceb5642c46b204f5958)) -- **keyboard:** Use KeyboardStyle for style config option ([#969](https://github.com/ionic-team/capacitor-plugins/issues/969)) ([42a01b4](https://github.com/ionic-team/capacitor-plugins/commit/42a01b46d5a9e39bfb3ac05d5595aecaeb790557)) -- add commonjs output format ([#179](https://github.com/ionic-team/capacitor-plugins/issues/179)) ([8e9e098](https://github.com/ionic-team/capacitor-plugins/commit/8e9e09862064b3f6771d7facbc4008e995d9b463)) -- Keyboard plugin ([#59](https://github.com/ionic-team/capacitor-plugins/issues/59)) ([3a65a9a](https://github.com/ionic-team/capacitor-plugins/commit/3a65a9a9757a34c3cd803da7479b9403d8689511)) -- 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)) -- 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)) -- **keyboard:** Add default style option for setStyle ([#334](https://github.com/ionic-team/capacitor-plugins/issues/334)) ([9dbb809](https://github.com/ionic-team/capacitor-plugins/commit/9dbb809ce3219d517e62b235406d2b798c95425c)) -- **keyboard:** add types for config files ([#115](https://github.com/ionic-team/capacitor-plugins/issues/115)) ([09bba16](https://github.com/ionic-team/capacitor-plugins/commit/09bba168242c26b3816ab5ff5b15f22531935fec)) -- **keyboard:** Make resize work in apps that use scenes ([#729](https://github.com/ionic-team/capacitor-plugins/issues/729)) ([6dde082](https://github.com/ionic-team/capacitor-plugins/commit/6dde082d1683daa923e6f42678785bc679f63b02)) -- **Keyboard:** Add resizeOnFullScreen plugin configuration ([#627](https://github.com/ionic-team/capacitor-plugins/issues/627)) ([8e87836](https://github.com/ionic-team/capacitor-plugins/commit/8e8783622d6e77c38c4aa741a622455302f30486)) - -## [1.2.3](https://github.com/ionic-team/capacitor-plugins/compare/@capacitor/keyboard@1.2.2...@capacitor/keyboard@1.2.3) (2022-06-17) - -### Bug Fixes - -- **ios:** Check if UIApplicationDelegate responds to window selector ([#1032](https://github.com/ionic-team/capacitor-plugins/issues/1032)) ([5be1466](https://github.com/ionic-team/capacitor-plugins/commit/5be14662181305e48bc097ade6504c579880bda8)) - -## [1.2.2](https://github.com/ionic-team/capacitor-plugins/compare/@capacitor/keyboard@1.2.1...@capacitor/keyboard@1.2.2) (2022-02-10) - -### Bug Fixes - -- **keyboard:** Prevent null pointer if native listener not set ([#789](https://github.com/ionic-team/capacitor-plugins/issues/789)) ([8b6f119](https://github.com/ionic-team/capacitor-plugins/commit/8b6f119ad5143d380aa9ebd7c73b701ad5a1f10a)) - -## [1.2.1](https://github.com/ionic-team/capacitor-plugins/compare/@capacitor/keyboard@1.2.0...@capacitor/keyboard@1.2.1) (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.0](https://github.com/ionic-team/capacitor-plugins/compare/@capacitor/keyboard@1.1.3...@capacitor/keyboard@1.2.0) (2021-12-08) - -### Features - -- **keyboard:** Make resize work in apps that use scenes ([#729](https://github.com/ionic-team/capacitor-plugins/issues/729)) ([6dde082](https://github.com/ionic-team/capacitor-plugins/commit/6dde082d1683daa923e6f42678785bc679f63b02)) - -## [1.1.3](https://github.com/ionic-team/capacitor-plugins/compare/@capacitor/keyboard@1.1.2...@capacitor/keyboard@1.1.3) (2021-11-03) - -**Note:** Version bump only for package @capacitor/keyboard - -## [1.1.2](https://github.com/ionic-team/capacitor-plugins/compare/@capacitor/keyboard@1.1.1...@capacitor/keyboard@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/keyboard@1.1.0...@capacitor/keyboard@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/keyboard@1.0.3...@capacitor/keyboard@1.1.0) (2021-09-27) - -### Features - -- **Keyboard:** Add resizeOnFullScreen plugin configuration ([#627](https://github.com/ionic-team/capacitor-plugins/issues/627)) ([8e87836](https://github.com/ionic-team/capacitor-plugins/commit/8e8783622d6e77c38c4aa741a622455302f30486)) - -## [1.0.3](https://github.com/ionic-team/capacitor-plugins/compare/@capacitor/keyboard@1.0.2...@capacitor/keyboard@1.0.3) (2021-09-01) - -**Note:** Version bump only for package @capacitor/keyboard - -## [1.0.2](https://github.com/ionic-team/capacitor-plugins/compare/@capacitor/keyboard@1.0.1...@capacitor/keyboard@1.0.2) (2021-06-23) - -**Note:** Version bump only for package @capacitor/keyboard - -## [1.0.1](https://github.com/ionic-team/capacitor-plugins/compare/@capacitor/keyboard@1.0.0...@capacitor/keyboard@1.0.1) (2021-06-09) - -**Note:** Version bump only for package @capacitor/keyboard - -# [1.0.0](https://github.com/ionic-team/capacitor-plugins/compare/@capacitor/keyboard@0.6.3...@capacitor/keyboard@1.0.0) (2021-05-19) - -**Note:** Version bump only for package @capacitor/keyboard - -## [0.6.3](https://github.com/ionic-team/capacitor-plugins/compare/@capacitor/keyboard@0.6.2...@capacitor/keyboard@0.6.3) (2021-05-11) - -**Note:** Version bump only for package @capacitor/keyboard - -## [0.6.2](https://github.com/ionic-team/capacitor-plugins/compare/@capacitor/keyboard@0.6.1...@capacitor/keyboard@0.6.2) (2021-05-10) - -**Note:** Version bump only for package @capacitor/keyboard - -## [0.6.1](https://github.com/ionic-team/capacitor-plugins/compare/@capacitor/keyboard@0.6.0...@capacitor/keyboard@0.6.1) (2021-05-07) - -**Note:** Version bump only for package @capacitor/keyboard - -# [0.6.0](https://github.com/ionic-team/capacitor-plugins/compare/@capacitor/keyboard@0.5.6...@capacitor/keyboard@0.6.0) (2021-04-29) - -### Bug Fixes - -- **keyboard:** Use new Config name for scroll ([#335](https://github.com/ionic-team/capacitor-plugins/issues/335)) ([bec3d22](https://github.com/ionic-team/capacitor-plugins/commit/bec3d22e67dfb22db5b5bb774f53fb01a34f116f)) - -### Features - -- **keyboard:** Add default style option for setStyle ([#334](https://github.com/ionic-team/capacitor-plugins/issues/334)) ([9dbb809](https://github.com/ionic-team/capacitor-plugins/commit/9dbb809ce3219d517e62b235406d2b798c95425c)) - -## [0.5.6](https://github.com/ionic-team/capacitor-plugins/compare/@capacitor/keyboard@0.5.5...@capacitor/keyboard@0.5.6) (2021-03-10) - -**Note:** Version bump only for package @capacitor/keyboard - -## [0.5.5](https://github.com/ionic-team/capacitor-plugins/compare/@capacitor/keyboard@0.5.4...@capacitor/keyboard@0.5.5) (2021-03-02) - -**Note:** Version bump only for package @capacitor/keyboard - -## [0.5.4](https://github.com/ionic-team/capacitor-plugins/compare/@capacitor/keyboard@0.5.3...@capacitor/keyboard@0.5.4) (2021-02-27) - -**Note:** Version bump only for package @capacitor/keyboard - -## [0.5.3](https://github.com/ionic-team/capacitor-plugins/compare/@capacitor/keyboard@0.5.2...@capacitor/keyboard@0.5.3) (2021-02-10) - -**Note:** Version bump only for package @capacitor/keyboard - -## [0.5.2](https://github.com/ionic-team/capacitor-plugins/compare/@capacitor/keyboard@0.5.1...@capacitor/keyboard@0.5.2) (2021-02-05) - -**Note:** Version bump only for package @capacitor/keyboard - -## [0.5.1](https://github.com/ionic-team/capacitor-plugins/compare/@capacitor/keyboard@0.5.0...@capacitor/keyboard@0.5.1) (2021-01-26) - -**Note:** Version bump only for package @capacitor/keyboard - -# [0.5.0](https://github.com/ionic-team/capacitor-plugins/compare/@capacitor/keyboard@0.4.0...@capacitor/keyboard@0.5.0) (2021-01-14) - -**Note:** Version bump only for package @capacitor/keyboard - -# [0.4.0](https://github.com/ionic-team/capacitor-plugins/compare/@capacitor/keyboard@0.3.3...@capacitor/keyboard@0.4.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.3.3](https://github.com/ionic-team/capacitor-plugins/compare/@capacitor/keyboard@0.3.2...@capacitor/keyboard@0.3.3) (2021-01-13) - -**Note:** Version bump only for package @capacitor/keyboard - -## [0.3.2](https://github.com/ionic-team/capacitor-plugins/compare/@capacitor/keyboard@0.3.1...@capacitor/keyboard@0.3.2) (2021-01-08) - -**Note:** Version bump only for package @capacitor/keyboard - -## [0.3.1](https://github.com/ionic-team/capacitor-plugins/compare/@capacitor/keyboard@0.3.0...@capacitor/keyboard@0.3.1) (2020-12-27) - -**Note:** Version bump only for package @capacitor/keyboard - -# [0.3.0](https://github.com/ionic-team/capacitor-plugins/compare/@capacitor/keyboard@0.2.0...@capacitor/keyboard@0.3.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)) -- **keyboard:** remove deprecated warnings ([#122](https://github.com/ionic-team/capacitor-plugins/issues/122)) ([a6e207c](https://github.com/ionic-team/capacitor-plugins/commit/a6e207c47dfac14d6cbcaa2a942b7b04cae3dae8)) - -### Features - -- **keyboard:** add types for config files ([#115](https://github.com/ionic-team/capacitor-plugins/issues/115)) ([09bba16](https://github.com/ionic-team/capacitor-plugins/commit/09bba168242c26b3816ab5ff5b15f22531935fec)) - -# [0.2.0](https://github.com/ionic-team/capacitor-plugins/compare/@capacitor/keyboard@0.1.0...@capacitor/keyboard@0.2.0) (2020-12-02) - -**Note:** Version bump only for package @capacitor/keyboard - -# 0.1.0 (2020-10-30) - -### Features - -- Keyboard plugin ([#59](https://github.com/ionic-team/capacitor-plugins/issues/59)) ([3a65a9a](https://github.com/ionic-team/capacitor-plugins/commit/3a65a9a9757a34c3cd803da7479b9403d8689511)) diff --git a/keyboard/CapacitorKeyboard.podspec b/keyboard/CapacitorKeyboard.podspec deleted file mode 100644 index e942b47e33..0000000000 --- a/keyboard/CapacitorKeyboard.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 = 'CapacitorKeyboard' - 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}', 'keyboard/ios/Sources/**/*.{swift,h,m,c,cc,mm,cpp}' - s.ios.deployment_target = '14.0' - s.dependency 'Capacitor' - s.swift_version = '5.1' -end diff --git a/keyboard/LICENSE b/keyboard/LICENSE deleted file mode 100644 index 6652495cb2..0000000000 --- a/keyboard/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. diff --git a/keyboard/Package.swift b/keyboard/Package.swift deleted file mode 100644 index 167f78ac8a..0000000000 --- a/keyboard/Package.swift +++ /dev/null @@ -1,29 +0,0 @@ -// swift-tools-version: 5.9 - -import PackageDescription - -let package = Package( - name: "CapacitorKeyboard", - platforms: [.iOS(.v14)], - products: [ - .library( - name: "CapacitorKeyboard", - targets: ["KeyboardPlugin"]) - ], - dependencies: [ - .package(url: "https://github.com/ionic-team/capacitor-swift-pm.git", from: "7.0.0") - ], - targets: [ - .target( - name: "KeyboardPlugin", - dependencies: [ - .product(name: "Capacitor", package: "capacitor-swift-pm"), - .product(name: "Cordova", package: "capacitor-swift-pm")], - path: "ios/Sources/KeyboardPlugin", - publicHeadersPath: "include"), - .testTarget( - name: "KeyboardPluginTests", - dependencies: ["KeyboardPlugin"], - path: "ios/Tests/KeyboardPluginTests") - ] -) diff --git a/keyboard/README.md b/keyboard/README.md deleted file mode 100644 index d359bdde70..0000000000 --- a/keyboard/README.md +++ /dev/null @@ -1,391 +0,0 @@ -# @capacitor/keyboard - -The Keyboard API provides keyboard display and visibility control, along with event tracking when the keyboard shows and hides. - -## Install - -```bash -npm install @capacitor/keyboard -npx cap sync -``` - -## Example - -```typescript -import { Keyboard } from '@capacitor/keyboard'; - -Keyboard.addListener('keyboardWillShow', info => { - console.log('keyboard will show with height:', info.keyboardHeight); -}); - -Keyboard.addListener('keyboardDidShow', info => { - console.log('keyboard did show with height:', info.keyboardHeight); -}); - -Keyboard.addListener('keyboardWillHide', () => { - console.log('keyboard will hide'); -}); - -Keyboard.addListener('keyboardDidHide', () => { - console.log('keyboard did hide'); -}); -``` - -## Configuration - -On iOS, the keyboard can be configured with the following options: - -| Prop | Type | Description | Default | Since | -| ------------------------ | --------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------- | ----- | -| **`resize`** | KeyboardResize | Configure the way the app is resized when the Keyboard appears. Only available on iOS. | native | 1.0.0 | -| **`style`** | KeyboardStyle | Override the keyboard style if your app doesn't support dark/light theme changes. If not set, the keyboard style will depend on the device appearance. Only available on iOS. | | 1.0.0 | -| **`resizeOnFullScreen`** | boolean | There is an Android bug that prevents the keyboard from resizing the WebView when the app is in full screen (i.e. if StatusBar plugin is used to overlay the status bar). This setting, if set to true, add a workaround that resizes the WebView even when the app is in full screen. Only available for Android | | 1.1.0 | - -### Examples - -In `capacitor.config.json`: - -```json -{ - "plugins": { - "Keyboard": { - "resize": "body", - "style": "DARK", - "resizeOnFullScreen": true - } - } -} -``` - -In `capacitor.config.ts`: - -```ts -/// - -import { CapacitorConfig } from '@capacitor/cli'; -import { KeyboardResize, KeyboardStyle } from '@capacitor/keyboard'; - -const config: CapacitorConfig = { - plugins: { - Keyboard: { - resize: KeyboardResize.Body, - style: KeyboardStyle.Dark, - resizeOnFullScreen: true, - }, - }, -}; - -export default config; -``` - -## Compatibility with `cordova-plugin-ionic-keyboard` - -To maintain compatibility with -[`cordova-plugin-ionic-keyboard`](https://github.com/ionic-team/cordova-plugin-ionic-keyboard), -the following events also work with `window.addEventListener`: - -- `keyboardWillShow` -- `keyboardDidShow` -- `keyboardWillHide` -- `keyboardDidHide` - -## API - - - -* [`show()`](#show) -* [`hide()`](#hide) -* [`setAccessoryBarVisible(...)`](#setaccessorybarvisible) -* [`setScroll(...)`](#setscroll) -* [`setStyle(...)`](#setstyle) -* [`setResizeMode(...)`](#setresizemode) -* [`getResizeMode()`](#getresizemode) -* [`addListener('keyboardWillShow', ...)`](#addlistenerkeyboardwillshow-) -* [`addListener('keyboardDidShow', ...)`](#addlistenerkeyboarddidshow-) -* [`addListener('keyboardWillHide', ...)`](#addlistenerkeyboardwillhide-) -* [`addListener('keyboardDidHide', ...)`](#addlistenerkeyboarddidhide-) -* [`removeAllListeners()`](#removealllisteners) -* [Interfaces](#interfaces) -* [Enums](#enums) - - - - - - -### show() - -```typescript -show() => Promise -``` - -Show the keyboard. This method is alpha and may have issues. - -This method is only supported on Android. - -**Since:** 1.0.0 - --------------------- - - -### hide() - -```typescript -hide() => Promise -``` - -Hide the keyboard. - -**Since:** 1.0.0 - --------------------- - - -### setAccessoryBarVisible(...) - -```typescript -setAccessoryBarVisible(options: { isVisible: boolean; }) => Promise -``` - -Set whether the accessory bar should be visible on the keyboard. We recommend disabling -the accessory bar for short forms (login, signup, etc.) to provide a cleaner UI. - -This method is only supported on iPhone devices. - -| Param | Type | -| ------------- | ------------------------------------ | -| **`options`** | { isVisible: boolean; } | - -**Since:** 1.0.0 - --------------------- - - -### setScroll(...) - -```typescript -setScroll(options: { isDisabled: boolean; }) => Promise -``` - -Programmatically enable or disable the WebView scroll. - -This method is only supported on iOS. - -| Param | Type | -| ------------- | ------------------------------------- | -| **`options`** | { isDisabled: boolean; } | - -**Since:** 1.0.0 - --------------------- - - -### setStyle(...) - -```typescript -setStyle(options: KeyboardStyleOptions) => Promise -``` - -Programmatically set the keyboard style. - -This method is only supported on iOS. - -| Param | Type | -| ------------- | --------------------------------------------------------------------- | -| **`options`** | KeyboardStyleOptions | - -**Since:** 1.0.0 - --------------------- - - -### setResizeMode(...) - -```typescript -setResizeMode(options: KeyboardResizeOptions) => Promise -``` - -Programmatically set the resize mode. - -This method is only supported on iOS. - -| Param | Type | -| ------------- | ----------------------------------------------------------------------- | -| **`options`** | KeyboardResizeOptions | - -**Since:** 1.0.0 - --------------------- - - -### getResizeMode() - -```typescript -getResizeMode() => Promise -``` - -Get the currently set resize mode. - -This method is only supported on iOS. - -**Returns:** Promise<KeyboardResizeOptions> - -**Since:** 4.0.0 - --------------------- - - -### addListener('keyboardWillShow', ...) - -```typescript -addListener(eventName: 'keyboardWillShow', listenerFunc: (info: KeyboardInfo) => void) => Promise -``` - -Listen for when the keyboard is about to be shown. - -On Android keyboardWillShow and keyboardDidShow fire almost at the same time. - -| Param | Type | -| ------------------ | ------------------------------------------------------------------------ | -| **`eventName`** | 'keyboardWillShow' | -| **`listenerFunc`** | (info: KeyboardInfo) => void | - -**Returns:** Promise<PluginListenerHandle> - -**Since:** 1.0.0 - --------------------- - - -### addListener('keyboardDidShow', ...) - -```typescript -addListener(eventName: 'keyboardDidShow', listenerFunc: (info: KeyboardInfo) => void) => Promise -``` - -Listen for when the keyboard is shown. - -On Android keyboardWillShow and keyboardDidShow fire almost at the same time. - -| Param | Type | -| ------------------ | ------------------------------------------------------------------------ | -| **`eventName`** | 'keyboardDidShow' | -| **`listenerFunc`** | (info: KeyboardInfo) => void | - -**Returns:** Promise<PluginListenerHandle> - -**Since:** 1.0.0 - --------------------- - - -### addListener('keyboardWillHide', ...) - -```typescript -addListener(eventName: 'keyboardWillHide', listenerFunc: () => void) => Promise -``` - -Listen for when the keyboard is about to be hidden. - -On Android keyboardWillHide and keyboardDidHide fire almost at the same time. - -| Param | Type | -| ------------------ | ------------------------------- | -| **`eventName`** | 'keyboardWillHide' | -| **`listenerFunc`** | () => void | - -**Returns:** Promise<PluginListenerHandle> - -**Since:** 1.0.0 - --------------------- - - -### addListener('keyboardDidHide', ...) - -```typescript -addListener(eventName: 'keyboardDidHide', listenerFunc: () => void) => Promise -``` - -Listen for when the keyboard is hidden. - -On Android keyboardWillHide and keyboardDidHide fire almost at the same time. - -| Param | Type | -| ------------------ | ------------------------------ | -| **`eventName`** | 'keyboardDidHide' | -| **`listenerFunc`** | () => void | - -**Returns:** Promise<PluginListenerHandle> - -**Since:** 1.0.0 - --------------------- - - -### removeAllListeners() - -```typescript -removeAllListeners() => Promise -``` - -Remove all native listeners for this plugin. - -**Since:** 1.0.0 - --------------------- - - -### Interfaces - - -#### KeyboardStyleOptions - -| Prop | Type | Description | Default | Since | -| ----------- | ------------------------------------------------------- | ---------------------- | ---------------------------------- | ----- | -| **`style`** | KeyboardStyle | Style of the keyboard. | KeyboardStyle.Default | 1.0.0 | - - -#### KeyboardResizeOptions - -| Prop | Type | Description | Since | -| ---------- | --------------------------------------------------------- | ------------------------------------------------------- | ----- | -| **`mode`** | KeyboardResize | Mode used to resize elements when the keyboard appears. | 1.0.0 | - - -#### PluginListenerHandle - -| Prop | Type | -| ------------ | ----------------------------------------- | -| **`remove`** | () => Promise<void> | - - -#### KeyboardInfo - -| Prop | Type | Description | Since | -| -------------------- | ------------------- | ----------------------- | ----- | -| **`keyboardHeight`** | number | Height of the heyboard. | 1.0.0 | - - -### Enums - - -#### KeyboardStyle - -| Members | Value | Description | Since | -| ------------- | ---------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----- | -| **`Dark`** | 'DARK' | Dark keyboard. | 1.0.0 | -| **`Light`** | 'LIGHT' | Light keyboard. | 1.0.0 | -| **`Default`** | 'DEFAULT' | On iOS 13 and newer the keyboard style is based on the device appearance. If the device is using Dark mode, the keyboard will be dark. If the device is using Light mode, the keyboard will be light. On iOS 12 the keyboard will be light. | 1.0.0 | - - -#### KeyboardResize - -| Members | Value | Description | Since | -| ------------ | --------------------- | -------------------------------------------------------------------------------------------------------------------- | ----- | -| **`Body`** | 'body' | Only the `body` HTML element will be resized. Relative units are not affected, because the viewport does not change. | 1.0.0 | -| **`Ionic`** | 'ionic' | Only the `ion-app` HTML element will be resized. Use it only for Ionic Framework apps. | 1.0.0 | -| **`Native`** | 'native' | The whole native Web View will be resized when the keyboard shows/hides. This affects the `vh` relative unit. | 1.0.0 | -| **`None`** | 'none' | Neither the app nor the Web View are resized. | 1.0.0 | - - diff --git a/keyboard/android/.gitignore b/keyboard/android/.gitignore deleted file mode 100644 index 796b96d1c4..0000000000 --- a/keyboard/android/.gitignore +++ /dev/null @@ -1 +0,0 @@ -/build diff --git a/keyboard/android/build.gradle b/keyboard/android/build.gradle deleted file mode 100644 index 889d1cb96e..0000000000 --- a/keyboard/android/build.gradle +++ /dev/null @@ -1,79 +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.0' - androidxJunitVersion = project.hasProperty('androidxJunitVersion') ? rootProject.ext.androidxJunitVersion : '1.2.1' - androidxEspressoCoreVersion = project.hasProperty('androidxEspressoCoreVersion') ? rootProject.ext.androidxEspressoCoreVersion : '3.6.1' -} - -buildscript { - repositories { - google() - mavenCentral() - maven { - url "https://plugins.gradle.org/m2/" - } - } - dependencies { - classpath 'com.android.tools.build:gradle:8.7.2' - 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.keyboard" - compileSdk project.hasProperty('compileSdkVersion') ? rootProject.ext.compileSdkVersion : 35 - defaultConfig { - minSdkVersion project.hasProperty('minSdkVersion') ? rootProject.ext.minSdkVersion : 23 - targetSdkVersion project.hasProperty('targetSdkVersion') ? rootProject.ext.targetSdkVersion : 35 - versionCode 1 - versionName "1.0" - testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" - } - buildTypes { - release { - minifyEnabled false - proguardFiles getDefaultProguardFile('proguard-android.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.appcompat:appcompat:$androidxAppCompatVersion" - testImplementation "junit:junit:$junitVersion" - androidTestImplementation "androidx.test.ext:junit:$androidxJunitVersion" - androidTestImplementation "androidx.test.espresso:espresso-core:$androidxEspressoCoreVersion" -} diff --git a/keyboard/android/gradle.properties b/keyboard/android/gradle.properties deleted file mode 100644 index 2e87c52f83..0000000000 --- a/keyboard/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/keyboard/android/gradle/wrapper/gradle-wrapper.jar b/keyboard/android/gradle/wrapper/gradle-wrapper.jar deleted file mode 100644 index a4b76b9530..0000000000 Binary files a/keyboard/android/gradle/wrapper/gradle-wrapper.jar and /dev/null differ diff --git a/keyboard/android/gradle/wrapper/gradle-wrapper.properties b/keyboard/android/gradle/wrapper/gradle-wrapper.properties deleted file mode 100644 index c1d5e01859..0000000000 --- a/keyboard/android/gradle/wrapper/gradle-wrapper.properties +++ /dev/null @@ -1,7 +0,0 @@ -distributionBase=GRADLE_USER_HOME -distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-8.11.1-all.zip -networkTimeout=10000 -validateDistributionUrl=true -zipStoreBase=GRADLE_USER_HOME -zipStorePath=wrapper/dists diff --git a/keyboard/android/gradlew b/keyboard/android/gradlew deleted file mode 100755 index f5feea6d6b..0000000000 --- a/keyboard/android/gradlew +++ /dev/null @@ -1,252 +0,0 @@ -#!/bin/sh - -# -# Copyright © 2015-2021 the original authors. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# https://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# -# SPDX-License-Identifier: Apache-2.0 -# - -############################################################################## -# -# Gradle start up script for POSIX generated by Gradle. -# -# Important for running: -# -# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is -# noncompliant, but you have some other compliant shell such as ksh or -# bash, then to run this script, type that shell name before the whole -# command line, like: -# -# ksh Gradle -# -# Busybox and similar reduced shells will NOT work, because this script -# requires all of these POSIX shell features: -# * functions; -# * expansions «$var», «${var}», «${var:-default}», «${var+SET}», -# «${var#prefix}», «${var%suffix}», and «$( cmd )»; -# * compound commands having a testable exit status, especially «case»; -# * various built-in commands including «command», «set», and «ulimit». -# -# Important for patching: -# -# (2) This script targets any POSIX shell, so it avoids extensions provided -# by Bash, Ksh, etc; in particular arrays are avoided. -# -# The "traditional" practice of packing multiple parameters into a -# space-separated string is a well documented source of bugs and security -# problems, so this is (mostly) avoided, by progressively accumulating -# options in "$@", and eventually passing that to Java. -# -# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS, -# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly; -# see the in-line comments for details. -# -# There are tweaks for specific operating systems such as AIX, CygWin, -# Darwin, MinGW, and NonStop. -# -# (3) This script is generated from the Groovy template -# https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt -# within the Gradle project. -# -# You can find Gradle at https://github.com/gradle/gradle/. -# -############################################################################## - -# Attempt to set APP_HOME - -# Resolve links: $0 may be a link -app_path=$0 - -# Need this for daisy-chained symlinks. -while - APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path - [ -h "$app_path" ] -do - ls=$( ls -ld "$app_path" ) - link=${ls#*' -> '} - 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 -' "$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=$APP_HOME/gradle/wrapper/gradle-wrapper.jar - - -# 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, 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" \ - org.gradle.wrapper.GradleWrapperMain \ - "$@" - -# 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/keyboard/android/gradlew.bat b/keyboard/android/gradlew.bat deleted file mode 100644 index 9b42019c79..0000000000 --- a/keyboard/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=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar - - -@rem Execute Gradle -"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* - -: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/keyboard/android/proguard-rules.pro b/keyboard/android/proguard-rules.pro deleted file mode 100644 index f1b424510d..0000000000 --- a/keyboard/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/keyboard/android/settings.gradle b/keyboard/android/settings.gradle deleted file mode 100644 index 1e5b8431f7..0000000000 --- a/keyboard/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/keyboard/android/src/androidTest/java/com/getcapacitor/android/ExampleInstrumentedTest.java b/keyboard/android/src/androidTest/java/com/getcapacitor/android/ExampleInstrumentedTest.java deleted file mode 100644 index 58020e16cb..0000000000 --- a/keyboard/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/keyboard/android/src/main/AndroidManifest.xml b/keyboard/android/src/main/AndroidManifest.xml deleted file mode 100644 index a2f47b6057..0000000000 --- a/keyboard/android/src/main/AndroidManifest.xml +++ /dev/null @@ -1,2 +0,0 @@ - - diff --git a/keyboard/android/src/main/java/com/capacitorjs/plugins/keyboard/Keyboard.java b/keyboard/android/src/main/java/com/capacitorjs/plugins/keyboard/Keyboard.java deleted file mode 100644 index a132d26199..0000000000 --- a/keyboard/android/src/main/java/com/capacitorjs/plugins/keyboard/Keyboard.java +++ /dev/null @@ -1,184 +0,0 @@ -package com.capacitorjs.plugins.keyboard; - -import android.content.Context; -import android.graphics.Rect; -import android.os.Build; -import android.util.DisplayMetrics; -import android.util.TypedValue; -import android.view.View; -import android.view.Window; -import android.view.inputmethod.InputMethodManager; -import android.widget.FrameLayout; -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; -import androidx.appcompat.app.AppCompatActivity; -import androidx.core.view.ViewCompat; -import androidx.core.view.WindowInsetsAnimationCompat; -import androidx.core.view.WindowInsetsCompat; -import com.getcapacitor.Bridge; -import java.util.List; - -public class Keyboard { - - interface KeyboardEventListener { - void onKeyboardEvent(String event, int size); - } - - private Bridge bridge; - private AppCompatActivity activity; - private View rootView; - private int usableHeightPrevious; - private FrameLayout.LayoutParams frameLayoutParams; - private View mChildOfContent; - - public void setKeyboardEventListener(@Nullable KeyboardEventListener keyboardEventListener) { - this.keyboardEventListener = keyboardEventListener; - } - - @Nullable - private KeyboardEventListener keyboardEventListener; - - static final String EVENT_KB_WILL_SHOW = "keyboardWillShow"; - static final String EVENT_KB_DID_SHOW = "keyboardDidShow"; - static final String EVENT_KB_WILL_HIDE = "keyboardWillHide"; - static final String EVENT_KB_DID_HIDE = "keyboardDidHide"; - - // From android 15 on, we need access to the bridge to get the config to resize the keyboard properly. - public Keyboard(Bridge bridge, boolean resizeOnFullScreen) { - this(bridge.getActivity(), resizeOnFullScreen); - this.bridge = bridge; - } - - // We may want to deprecate this constructor in the future, but we are keeping it now to keep backward compatibility with cap 7 - public Keyboard(AppCompatActivity activity, boolean resizeOnFullScreen) { - this.activity = activity; - - //http://stackoverflow.com/a/4737265/1091751 detect if keyboard is showing - FrameLayout content = activity.getWindow().getDecorView().findViewById(android.R.id.content); - rootView = content.getRootView(); - - ViewCompat.setWindowInsetsAnimationCallback( - rootView, - new WindowInsetsAnimationCompat.Callback(WindowInsetsAnimationCompat.Callback.DISPATCH_MODE_STOP) { - @NonNull - @Override - public WindowInsetsCompat onProgress( - @NonNull WindowInsetsCompat insets, - @NonNull List runningAnimations - ) { - return insets; - } - - @NonNull - @Override - public WindowInsetsAnimationCompat.BoundsCompat onStart( - @NonNull WindowInsetsAnimationCompat animation, - @NonNull WindowInsetsAnimationCompat.BoundsCompat bounds - ) { - boolean showingKeyboard = ViewCompat.getRootWindowInsets(rootView).isVisible(WindowInsetsCompat.Type.ime()); - WindowInsetsCompat insets = ViewCompat.getRootWindowInsets(rootView); - int imeHeight = insets.getInsets(WindowInsetsCompat.Type.ime()).bottom; - DisplayMetrics dm = activity.getResources().getDisplayMetrics(); - final float density = dm.density; - - if (resizeOnFullScreen) { - possiblyResizeChildOfContent(showingKeyboard); - } - - if (showingKeyboard) { - keyboardEventListener.onKeyboardEvent(EVENT_KB_WILL_SHOW, Math.round(imeHeight / density)); - } else { - keyboardEventListener.onKeyboardEvent(EVENT_KB_WILL_HIDE, 0); - } - return super.onStart(animation, bounds); - } - - @Override - public void onEnd(@NonNull WindowInsetsAnimationCompat animation) { - super.onEnd(animation); - boolean showingKeyboard = ViewCompat.getRootWindowInsets(rootView).isVisible(WindowInsetsCompat.Type.ime()); - WindowInsetsCompat insets = ViewCompat.getRootWindowInsets(rootView); - int imeHeight = insets.getInsets(WindowInsetsCompat.Type.ime()).bottom; - DisplayMetrics dm = activity.getResources().getDisplayMetrics(); - final float density = dm.density; - - if (showingKeyboard) { - keyboardEventListener.onKeyboardEvent(EVENT_KB_DID_SHOW, Math.round(imeHeight / density)); - } else { - keyboardEventListener.onKeyboardEvent(EVENT_KB_DID_HIDE, 0); - } - } - } - ); - - mChildOfContent = content.getChildAt(0); - frameLayoutParams = (FrameLayout.LayoutParams) mChildOfContent.getLayoutParams(); - } - - public void show() { - ((InputMethodManager) activity.getSystemService(Context.INPUT_METHOD_SERVICE)).showSoftInput(activity.getCurrentFocus(), 0); - } - - public boolean hide() { - InputMethodManager inputManager = (InputMethodManager) activity.getSystemService(Context.INPUT_METHOD_SERVICE); - View v = activity.getCurrentFocus(); - if (v == null) { - return false; - } else { - inputManager.hideSoftInputFromWindow(v.getWindowToken(), InputMethodManager.HIDE_NOT_ALWAYS); - return true; - } - } - - private void possiblyResizeChildOfContent(boolean keyboardShown) { - int usableHeightNow = keyboardShown ? computeUsableHeight() : -1; - if (usableHeightPrevious != usableHeightNow) { - frameLayoutParams.height = usableHeightNow; - mChildOfContent.requestLayout(); - usableHeightPrevious = usableHeightNow; - } - } - - private int computeUsableHeight() { - Rect r = new Rect(); - mChildOfContent.getWindowVisibleDisplayFrame(r); - if (shouldApplyEdgeToEdgeAdjustments()) { - WindowInsetsCompat insets = ViewCompat.getRootWindowInsets(rootView); - if (insets != null) { - int systemBars = insets.getInsets(WindowInsetsCompat.Type.systemBars()).bottom; - if (systemBars > 0) { - return r.bottom + systemBars; - } - } - } - - return isOverlays() ? r.bottom : r.height(); - } - - private boolean shouldApplyEdgeToEdgeAdjustments() { - var adjustMarginsForEdgeToEdge = this.bridge == null ? "auto" : this.bridge.getConfig().adjustMarginsForEdgeToEdge(); - if (adjustMarginsForEdgeToEdge.equals("force")) { // Force edge-to-edge adjustments regardless of app settings - return true; - } else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.VANILLA_ICE_CREAM && adjustMarginsForEdgeToEdge.equals("auto")) { // Auto means that we need to check the app's edge-to-edge preference - TypedValue value = new TypedValue(); - boolean optOutAttributeExists = activity - .getTheme() - .resolveAttribute(android.R.attr.windowOptOutEdgeToEdgeEnforcement, value, true); - - if (!optOutAttributeExists) { // Default is to apply edge to edge - return true; - } else { - return value.data == 0; - } - } - return false; - } - - @SuppressWarnings("deprecation") - private boolean isOverlays() { - final Window window = activity.getWindow(); - return ( - (window.getDecorView().getSystemUiVisibility() & View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN) == View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN - ); - } -} diff --git a/keyboard/android/src/main/java/com/capacitorjs/plugins/keyboard/KeyboardPlugin.java b/keyboard/android/src/main/java/com/capacitorjs/plugins/keyboard/KeyboardPlugin.java deleted file mode 100644 index 722f94a35c..0000000000 --- a/keyboard/android/src/main/java/com/capacitorjs/plugins/keyboard/KeyboardPlugin.java +++ /dev/null @@ -1,103 +0,0 @@ -package com.capacitorjs.plugins.keyboard; - -import android.os.Handler; -import android.os.Looper; -import com.getcapacitor.JSObject; -import com.getcapacitor.Plugin; -import com.getcapacitor.PluginCall; -import com.getcapacitor.PluginMethod; -import com.getcapacitor.annotation.CapacitorPlugin; - -@CapacitorPlugin(name = "Keyboard") -public class KeyboardPlugin extends Plugin { - - private Keyboard implementation; - - @Override - public void load() { - execute( - () -> { - boolean resizeOnFullScreen = getConfig().getBoolean("resizeOnFullScreen", false); - implementation = new Keyboard(getBridge(), resizeOnFullScreen); - - implementation.setKeyboardEventListener(this::onKeyboardEvent); - } - ); - } - - @PluginMethod - public void show(final PluginCall call) { - execute( - () -> - new Handler(Looper.getMainLooper()) - .postDelayed( - () -> { - implementation.show(); - call.resolve(); - }, - 350 - ) - ); - } - - @PluginMethod - public void hide(final PluginCall call) { - execute( - () -> { - if (!implementation.hide()) { - call.reject("Can't close keyboard, not currently focused"); - } else { - call.resolve(); - } - } - ); - } - - @PluginMethod - public void setAccessoryBarVisible(PluginCall call) { - call.unimplemented(); - } - - @PluginMethod - public void setStyle(PluginCall call) { - call.unimplemented(); - } - - @PluginMethod - public void setResizeMode(PluginCall call) { - call.unimplemented(); - } - - @PluginMethod - public void getResizeMode(PluginCall call) { - call.unimplemented(); - } - - @PluginMethod - public void setScroll(PluginCall call) { - call.unimplemented(); - } - - void onKeyboardEvent(String event, int size) { - JSObject kbData = new JSObject(); - switch (event) { - case Keyboard.EVENT_KB_WILL_SHOW: - case Keyboard.EVENT_KB_DID_SHOW: - String data = "{ 'keyboardHeight': " + size + " }"; - bridge.triggerWindowJSEvent(event, data); - kbData.put("keyboardHeight", size); - notifyListeners(event, kbData); - break; - case Keyboard.EVENT_KB_WILL_HIDE: - case Keyboard.EVENT_KB_DID_HIDE: - bridge.triggerWindowJSEvent(event); - notifyListeners(event, kbData); - break; - } - } - - @Override - protected void handleOnDestroy() { - implementation.setKeyboardEventListener(null); - } -} diff --git a/keyboard/android/src/test/java/com/getcapacitor/ExampleUnitTest.java b/keyboard/android/src/test/java/com/getcapacitor/ExampleUnitTest.java deleted file mode 100644 index a0fed0cfb7..0000000000 --- a/keyboard/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/keyboard/ios/.gitignore b/keyboard/ios/.gitignore deleted file mode 100644 index 0023a53406..0000000000 --- a/keyboard/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/keyboard/ios/Sources/KeyboardPlugin/Keyboard.m b/keyboard/ios/Sources/KeyboardPlugin/Keyboard.m deleted file mode 100644 index 29c070840c..0000000000 --- a/keyboard/ios/Sources/KeyboardPlugin/Keyboard.m +++ /dev/null @@ -1,406 +0,0 @@ -/* - Licensed to the Apache Software Foundation (ASF) under one - or more contributor license agreements. See the NOTICE file - distributed with this work for additional information - regarding copyright ownership. The ASF licenses this file - to you under the Apache License, Version 2.0 (the - "License"); you may not use this file except in compliance - with the License. You may obtain a copy of the License at - http://www.apache.org/licenses/LICENSE-2.0 - Unless required by applicable law or agreed to in writing, - software distributed under the License is distributed on an - "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - KIND, either express or implied. See the License for the - specific language governing permissions and limitations - under the License. - */ - -#import "Keyboard.h" -#import -#import -#import -#import -#import -#import - -typedef enum : NSUInteger { - ResizeNone, - ResizeNative, - ResizeBody, - ResizeIonic, -} ResizePolicy; - - -@interface KeyboardPlugin () - -@property (readwrite, assign, nonatomic) BOOL disableScroll; -@property (readwrite, assign, nonatomic) BOOL hideFormAccessoryBar; -@property (readwrite, assign, nonatomic) BOOL keyboardIsVisible; -@property (nonatomic, readwrite) ResizePolicy keyboardResizes; -@property (readwrite, assign, nonatomic) NSString* keyboardStyle; -@property (nonatomic, readwrite) int paddingBottom; - -@end - -#pragma clang diagnostic push -#pragma clang diagnostic ignored "-Wprotocol" -// suppressing warnings of the type: "Class 'KeyboardPlugin' does not conform to protocol 'CAPBridgedPlugin'" -// protocol conformance for this class is implemented by a macro and clang isn't detecting that -@implementation KeyboardPlugin - -NSTimer *hideTimer; -NSString* UIClassString; -NSString* WKClassString; -NSString* UITraitsClassString; -double stageManagerOffset; - -- (void)load -{ - self.disableScroll = !self.bridge.config.scrollingEnabled; - - UIClassString = [@[@"UI", @"Web", @"Browser", @"View"] componentsJoinedByString:@""]; - WKClassString = [@[@"WK", @"Content", @"View"] componentsJoinedByString:@""]; - UITraitsClassString = [@[@"UI", @"Text", @"Input", @"Traits"] componentsJoinedByString:@""]; - - PluginConfig * config = [self getConfig]; - NSString * style = [config getString:@"style": nil]; - [self changeKeyboardStyle:style.uppercaseString]; - - self.keyboardResizes = ResizeNative; - NSString * resizeMode = [config getString:@"resize": nil]; - - if ([resizeMode isEqualToString:@"none"]) { - self.keyboardResizes = ResizeNone; - NSLog(@"KeyboardPlugin: no resize"); - } else if ([resizeMode isEqualToString:@"ionic"]) { - self.keyboardResizes = ResizeIonic; - NSLog(@"KeyboardPlugin: resize mode - ionic"); - } else if ([resizeMode isEqualToString:@"body"]) { - self.keyboardResizes = ResizeBody; - NSLog(@"KeyboardPlugin: resize mode - body"); - } - - if (self.keyboardResizes == ResizeNative) { - NSLog(@"KeyboardPlugin: resize mode - native"); - } - - self.hideFormAccessoryBar = YES; - - NSNotificationCenter *nc = [NSNotificationCenter defaultCenter]; - - [nc addObserver:self selector:@selector(onKeyboardDidHide:) name:UIKeyboardDidHideNotification object:nil]; - [nc addObserver:self selector:@selector(onKeyboardDidShow:) name:UIKeyboardDidShowNotification object:nil]; - [nc addObserver:self selector:@selector(onKeyboardWillHide:) name:UIKeyboardWillHideNotification object:nil]; - [nc addObserver:self selector:@selector(onKeyboardWillShow:) name:UIKeyboardWillShowNotification object:nil]; - - [nc removeObserver:self.webView name:UIKeyboardWillHideNotification object:nil]; - [nc removeObserver:self.webView name:UIKeyboardWillShowNotification object:nil]; - [nc removeObserver:self.webView name:UIKeyboardWillChangeFrameNotification object:nil]; - [nc removeObserver:self.webView name:UIKeyboardDidChangeFrameNotification object:nil]; -} - - -#pragma mark Keyboard events - -- (void)resetScrollView -{ - UIScrollView *scrollView = [self.webView scrollView]; - [scrollView setContentInset:UIEdgeInsetsZero]; -} - -- (void)onKeyboardWillHide:(NSNotification *)notification -{ - [self setKeyboardHeight:0 delay:0.01]; - [self resetScrollView]; - hideTimer = [NSTimer scheduledTimerWithTimeInterval:0 repeats:NO block:^(NSTimer * _Nonnull timer) { - [self.bridge triggerWindowJSEventWithEventName:@"keyboardWillHide"]; - [self notifyListeners:@"keyboardWillHide" data:nil]; - }]; - [[NSRunLoop currentRunLoop] addTimer:hideTimer forMode:NSRunLoopCommonModes]; -} - -- (void)onKeyboardWillShow:(NSNotification *)notification -{ - if (hideTimer != nil) { - [hideTimer invalidate]; - } - CGRect rect = [[notification.userInfo valueForKey:UIKeyboardFrameEndUserInfoKey] CGRectValue]; - - double height = rect.size.height; - - if ([[UIDevice currentDevice] userInterfaceIdiom] == UIUserInterfaceIdiomPad) { - if (stageManagerOffset > 0) { - height = stageManagerOffset; - } else { - CGRect webViewAbsolute = [self.webView convertRect:self.webView.frame toCoordinateSpace:self.webView.window.screen.coordinateSpace]; - height = (webViewAbsolute.size.height + webViewAbsolute.origin.y) - (UIScreen.mainScreen.bounds.size.height - rect.size.height); - if (height < 0) { - height = 0; - } - - stageManagerOffset = height; - } - } - - double duration = [[notification.userInfo valueForKey:UIKeyboardAnimationDurationUserInfoKey] doubleValue]+0.2; - [self setKeyboardHeight:height delay:duration]; - [self resetScrollView]; - - NSString * data = [NSString stringWithFormat:@"{ 'keyboardHeight': %d }", (int)height]; - [self.bridge triggerWindowJSEventWithEventName:@"keyboardWillShow" data:data]; - NSDictionary * kbData = @{@"keyboardHeight": [NSNumber numberWithDouble:height]}; - [self notifyListeners:@"keyboardWillShow" data:kbData]; -} - -- (void)onKeyboardDidShow:(NSNotification *)notification -{ - CGRect rect = [[notification.userInfo valueForKey:UIKeyboardFrameEndUserInfoKey] CGRectValue]; - double height = rect.size.height; - - [self resetScrollView]; - - NSString * data = [NSString stringWithFormat:@"{ 'keyboardHeight': %d }", (int)height]; - [self.bridge triggerWindowJSEventWithEventName:@"keyboardDidShow" data:data]; - NSDictionary * kbData = @{@"keyboardHeight": [NSNumber numberWithDouble:height]}; - [self notifyListeners:@"keyboardDidShow" data:kbData]; -} - -- (void)onKeyboardDidHide:(NSNotification *)notification -{ - [self.bridge triggerWindowJSEventWithEventName:@"keyboardDidHide"]; - [self notifyListeners:@"keyboardDidHide" data:nil]; - [self resetScrollView]; - - stageManagerOffset = 0; -} - -- (void)setKeyboardHeight:(int)height delay:(NSTimeInterval)delay -{ - if (self.paddingBottom == height) { - return; - } - - self.paddingBottom = height; - - __weak KeyboardPlugin* weakSelf = self; - SEL action = @selector(_updateFrame); - [NSObject cancelPreviousPerformRequestsWithTarget:weakSelf selector:action object:nil]; - if (delay == 0) { - [self _updateFrame]; - } else { - [weakSelf performSelector:action withObject:nil afterDelay:delay inModes:@[NSRunLoopCommonModes]]; - } -} - -- (void)resizeElement:(NSString *)element withPaddingBottom:(int)paddingBottom withScreenHeight:(int)screenHeight -{ - int height = -1; - if (paddingBottom > 0) { - height = screenHeight - paddingBottom; - } - - [self.bridge evalWithJs: [NSString stringWithFormat:@"(function() { var el = %@; var height = %d; if (el) { el.style.height = height > -1 ? height + 'px' : null; } })()", element, height]]; -} - -- (void)_updateFrame -{ - CGRect f, wf = CGRectZero; - UIWindow * window = nil; - - if ([[[UIApplication sharedApplication] delegate] respondsToSelector:@selector(window)]) { - window = [[[UIApplication sharedApplication] delegate] window]; - } - - if (!window) { - if (@available(iOS 13.0, *)) { - NSPredicate *predicate = [NSPredicate predicateWithFormat:@"self isKindOfClass: %@", UIWindowScene.class]; - UIScene *scene = [UIApplication.sharedApplication.connectedScenes.allObjects filteredArrayUsingPredicate:predicate].firstObject; - window = [[(UIWindowScene*)scene windows] filteredArrayUsingPredicate:[NSPredicate predicateWithFormat:@"isKeyWindow == YES"]].firstObject; - } - } - if (window) { - f = [window bounds]; - } - if (self.webView != nil) { - wf = self.webView.frame; - } - switch (self.keyboardResizes) { - case ResizeBody: - { - [self resizeElement:@"document.body" withPaddingBottom:_paddingBottom withScreenHeight:(int)f.size.height]; - break; - } - case ResizeIonic: - { - [self resizeElement:@"document.querySelector('ion-app')" withPaddingBottom:_paddingBottom withScreenHeight:(int)f.size.height]; - break; - } - case ResizeNative: - { - [self.webView setFrame:CGRectMake(wf.origin.x, wf.origin.y, f.size.width - wf.origin.x, f.size.height - wf.origin.y - self.paddingBottom)]; - break; - } - default: - break; - } - [self resetScrollView]; -} - - -#pragma mark HideFormAccessoryBar - -static IMP UIOriginalImp; -static IMP WKOriginalImp; - -- (void)setHideFormAccessoryBar:(BOOL)hideFormAccessoryBar -{ - if (hideFormAccessoryBar == _hideFormAccessoryBar) { - return; - } - Method UIMethod = class_getInstanceMethod(NSClassFromString(UIClassString), @selector(inputAccessoryView)); - Method WKMethod = class_getInstanceMethod(NSClassFromString(WKClassString), @selector(inputAccessoryView)); - if (hideFormAccessoryBar) { - UIOriginalImp = method_getImplementation(UIMethod); - WKOriginalImp = method_getImplementation(WKMethod); - IMP newImp = imp_implementationWithBlock(^(id _s) { - return nil; - }); - method_setImplementation(UIMethod, newImp); - method_setImplementation(WKMethod, newImp); - } else { - method_setImplementation(UIMethod, UIOriginalImp); - method_setImplementation(WKMethod, WKOriginalImp); - } - _hideFormAccessoryBar = hideFormAccessoryBar; -} - -#pragma mark scroll - -- (void)setDisableScroll:(BOOL)disableScroll { - if (disableScroll == _disableScroll) { - return; - } - dispatch_async(dispatch_get_main_queue(), ^{ - if (disableScroll) { - self.webView.scrollView.scrollEnabled = NO; - self.webView.scrollView.delegate = self; - } - else { - self.webView.scrollView.scrollEnabled = YES; - self.webView.scrollView.delegate = nil; - } - }); - _disableScroll = disableScroll; -} - -- (void)scrollViewDidScroll:(UIScrollView *)scrollView { - [scrollView setContentOffset: CGPointZero]; -} - -#pragma mark Plugin interface - -- (void)setAccessoryBarVisible:(CAPPluginCall *)call -{ - BOOL value = [call getBool:@"isVisible" defaultValue:FALSE]; - - NSLog(@"Accessory bar visible change %d", value); - self.hideFormAccessoryBar = !value; - [call resolve]; -} - -- (void)hide:(CAPPluginCall *)call -{ - dispatch_async(dispatch_get_main_queue(), ^{ - [self.webView endEditing:YES]; - }); - [call resolve]; -} - -- (void)show:(CAPPluginCall *)call -{ - [call unimplemented]; -} - -- (void)setStyle:(CAPPluginCall *)call -{ - self.keyboardStyle = [call getString:@"style" defaultValue:@"LIGHT"]; - [self changeKeyboardStyle:self.keyboardStyle]; - [call resolve]; -} - -- (void)setResizeMode:(CAPPluginCall *)call -{ - NSString * mode = [call getString:@"mode" defaultValue:@"none"]; - if ([mode isEqualToString:@"ionic"]) { - self.keyboardResizes = ResizeIonic; - } else if ([mode isEqualToString:@"body"]) { - self.keyboardResizes = ResizeBody; - } else if ([mode isEqualToString:@"native"]) { - self.keyboardResizes = ResizeNative; - } else { - self.keyboardResizes = ResizeNone; - } - [call resolve]; -} - -- (void)getResizeMode:(CAPPluginCall *)call -{ - NSString *mode; - - if (self.keyboardResizes == ResizeIonic) { - mode = @"ionic"; - } else if(self.keyboardResizes == ResizeBody) { - mode = @"body"; - } else if (self.keyboardResizes == ResizeNative) { - mode = @"native"; - } else { - mode = @"none"; - } - - NSDictionary *response = [NSDictionary dictionaryWithObject:mode forKey:@"mode"]; - [call resolve: response]; -} - -- (void)setScroll:(CAPPluginCall *)call { - self.disableScroll = [call getBool:@"isDisabled" defaultValue:FALSE]; - [call resolve]; -} - -- (void)changeKeyboardStyle:(NSString*)style -{ - IMP newImp = nil; - if ([style isEqualToString:@"DARK"]) { - newImp = imp_implementationWithBlock(^(id _s) { - return UIKeyboardAppearanceDark; - }); - } else if ([style isEqualToString:@"LIGHT"]) { - newImp = imp_implementationWithBlock(^(id _s) { - return UIKeyboardAppearanceLight; - }); - } else { - newImp = imp_implementationWithBlock(^(id _s) { - return UIKeyboardAppearanceDefault; - }); - } - for (NSString* classString in @[WKClassString, UITraitsClassString]) { - Class c = NSClassFromString(classString); - Method m = class_getInstanceMethod(c, @selector(keyboardAppearance)); - if (m != NULL) { - method_setImplementation(m, newImp); - } else { - class_addMethod(c, @selector(keyboardAppearance), newImp, "l@:"); - } - } - _keyboardStyle = style; -} - -#pragma mark dealloc - -- (void)dealloc -{ - [[NSNotificationCenter defaultCenter] removeObserver:self]; -} - -@end -#pragma clang diagnostic pop - diff --git a/keyboard/ios/Sources/KeyboardPlugin/KeyboardPlugin.m b/keyboard/ios/Sources/KeyboardPlugin/KeyboardPlugin.m deleted file mode 100644 index 8680896680..0000000000 --- a/keyboard/ios/Sources/KeyboardPlugin/KeyboardPlugin.m +++ /dev/null @@ -1,14 +0,0 @@ -#import -#import - -// Define the plugin using the CAP_PLUGIN Macro, and -// each method the plugin supports using the CAP_PLUGIN_METHOD macro. -CAP_PLUGIN(KeyboardPlugin, "Keyboard", - CAP_PLUGIN_METHOD(show, CAPPluginReturnPromise); - CAP_PLUGIN_METHOD(hide, CAPPluginReturnPromise); - CAP_PLUGIN_METHOD(setAccessoryBarVisible, CAPPluginReturnPromise); - CAP_PLUGIN_METHOD(setStyle, CAPPluginReturnPromise); - CAP_PLUGIN_METHOD(setResizeMode, CAPPluginReturnPromise); - CAP_PLUGIN_METHOD(getResizeMode, CAPPluginReturnPromise); - CAP_PLUGIN_METHOD(setScroll, CAPPluginReturnPromise); -) diff --git a/keyboard/ios/Sources/KeyboardPlugin/include/Keyboard.h b/keyboard/ios/Sources/KeyboardPlugin/include/Keyboard.h deleted file mode 100644 index d73b15acdd..0000000000 --- a/keyboard/ios/Sources/KeyboardPlugin/include/Keyboard.h +++ /dev/null @@ -1,11 +0,0 @@ -#import -#import -#import - - -@class CAPPluginCall; - -@interface KeyboardPlugin : CAPPlugin - -@end - diff --git a/keyboard/ios/Sources/KeyboardPlugin/include/KeyboardPlugin.h b/keyboard/ios/Sources/KeyboardPlugin/include/KeyboardPlugin.h deleted file mode 100644 index f2bd9e0bbf..0000000000 --- a/keyboard/ios/Sources/KeyboardPlugin/include/KeyboardPlugin.h +++ /dev/null @@ -1,10 +0,0 @@ -#import - -//! Project version number for Plugin. -FOUNDATION_EXPORT double PluginVersionNumber; - -//! Project version string for Plugin. -FOUNDATION_EXPORT const unsigned char PluginVersionString[]; - -// In this header, you should import all the public headers of your framework using statements like #import - diff --git a/keyboard/ios/Tests/KeyboardPluginTests/KeyboardPluginTests.swift b/keyboard/ios/Tests/KeyboardPluginTests/KeyboardPluginTests.swift deleted file mode 100644 index ec2bcde16e..0000000000 --- a/keyboard/ios/Tests/KeyboardPluginTests/KeyboardPluginTests.swift +++ /dev/null @@ -1,12 +0,0 @@ -import XCTest -@testable import KeyboardPlugin - -final class KeyboardPluginTests: 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/keyboard/package.json b/keyboard/package.json deleted file mode 100644 index 44270ecfb5..0000000000 --- a/keyboard/package.json +++ /dev/null @@ -1,85 +0,0 @@ -{ - "name": "@capacitor/keyboard", - "version": "7.0.1", - "description": "The Keyboard API provides keyboard display and visibility control, along with event tracking when the keyboard shows and hides.", - "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", - "CapacitorKeyboard.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 CapacitorKeyboard -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}\"", - "swiftlint": "node-swiftlint", - "docgen": "docgen --api KeyboardPlugin --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", - "publish:cocoapod": "pod trunk push ./CapacitorKeyboard.podspec --allow-warnings" - }, - "devDependencies": { - "@capacitor/android": "^7.0.0", - "@capacitor/cli": "^6.0.0", - "@capacitor/core": "^7.0.0", - "@capacitor/docgen": "0.2.2", - "@capacitor/ios": "^7.0.0", - "@ionic/eslint-config": "^0.4.0", - "@ionic/prettier-config": "~1.0.1", - "@ionic/swiftlint-config": "^1.1.2", - "eslint": "^8.57.0", - "prettier": "~2.3.0", - "prettier-plugin-java": "~1.0.2", - "rimraf": "^6.0.1", - "rollup": "^4.26.0", - "swiftlint": "^1.0.1", - "typescript": "~4.1.5" - }, - "peerDependencies": { - "@capacitor/core": ">=7.0.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/keyboard/rollup.config.mjs b/keyboard/rollup.config.mjs deleted file mode 100644 index d9d210ce85..0000000000 --- a/keyboard/rollup.config.mjs +++ /dev/null @@ -1,22 +0,0 @@ -export default { - input: 'dist/esm/index.js', - output: [ - { - file: 'dist/plugin.js', - format: 'iife', - name: 'capacitorKeyboard', - 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/keyboard/src/definitions.ts b/keyboard/src/definitions.ts deleted file mode 100644 index c712c598b2..0000000000 --- a/keyboard/src/definitions.ts +++ /dev/null @@ -1,252 +0,0 @@ -/// - -import type { PluginListenerHandle } from '@capacitor/core'; - -declare module '@capacitor/cli' { - export interface PluginsConfig { - /** - * On iOS, the keyboard can be configured with the following options: - */ - Keyboard?: { - /** - * Configure the way the app is resized when the Keyboard appears. - * - * Only available on iOS. - * - * @since 1.0.0 - * @default native - * @example "body" - */ - resize?: KeyboardResize; - - /** - * Override the keyboard style if your app doesn't support dark/light theme changes. - * If not set, the keyboard style will depend on the device appearance. - * - * Only available on iOS. - * - * @since 1.0.0 - * @example "DARK" - */ - style?: KeyboardStyle; - - /** - * There is an Android bug that prevents the keyboard from resizing the WebView - * when the app is in full screen (i.e. if StatusBar plugin is used to overlay the status bar). - * This setting, if set to true, add a workaround that resizes the WebView even when the app is in full screen. - * - * Only available for Android - * - * @since 1.1.0 - * @example true - */ - resizeOnFullScreen?: boolean; - }; - } -} - -export interface KeyboardInfo { - /** - * Height of the heyboard. - * - * @since 1.0.0 - */ - keyboardHeight: number; -} - -export interface KeyboardStyleOptions { - /** - * Style of the keyboard. - * - * @since 1.0.0 - * @default KeyboardStyle.Default - */ - style: KeyboardStyle; -} - -export enum KeyboardStyle { - /** - * Dark keyboard. - * - * @since 1.0.0 - */ - Dark = 'DARK', - - /** - * Light keyboard. - * - * @since 1.0.0 - */ - Light = 'LIGHT', - - /** - * On iOS 13 and newer the keyboard style is based on the device appearance. - * If the device is using Dark mode, the keyboard will be dark. - * If the device is using Light mode, the keyboard will be light. - * On iOS 12 the keyboard will be light. - * - * @since 1.0.0 - */ - Default = 'DEFAULT', -} - -export interface KeyboardResizeOptions { - /** - * Mode used to resize elements when the keyboard appears. - * - * @since 1.0.0 - */ - mode: KeyboardResize; -} - -export enum KeyboardResize { - /** - * Only the `body` HTML element will be resized. - * Relative units are not affected, because the viewport does not change. - * - * @since 1.0.0 - */ - Body = 'body', - - /** - * Only the `ion-app` HTML element will be resized. - * Use it only for Ionic Framework apps. - * - * @since 1.0.0 - */ - Ionic = 'ionic', - - /** - * The whole native Web View will be resized when the keyboard shows/hides. - * This affects the `vh` relative unit. - * - * @since 1.0.0 - */ - Native = 'native', - - /** - * Neither the app nor the Web View are resized. - * - * @since 1.0.0 - */ - None = 'none', -} - -export interface KeyboardPlugin { - /** - * Show the keyboard. This method is alpha and may have issues. - * - * This method is only supported on Android. - * - * @since 1.0.0 - */ - show(): Promise; - - /** - * Hide the keyboard. - * - * @since 1.0.0 - */ - hide(): Promise; - - /** - * Set whether the accessory bar should be visible on the keyboard. We recommend disabling - * the accessory bar for short forms (login, signup, etc.) to provide a cleaner UI. - * - * This method is only supported on iPhone devices. - * - * @since 1.0.0 - */ - setAccessoryBarVisible(options: { isVisible: boolean }): Promise; - - /** - * Programmatically enable or disable the WebView scroll. - * - * This method is only supported on iOS. - * - * @since 1.0.0 - */ - setScroll(options: { isDisabled: boolean }): Promise; - - /** - * Programmatically set the keyboard style. - * - * This method is only supported on iOS. - * - * @since 1.0.0 - */ - setStyle(options: KeyboardStyleOptions): Promise; - - /** - * Programmatically set the resize mode. - * - * This method is only supported on iOS. - * - * @since 1.0.0 - */ - setResizeMode(options: KeyboardResizeOptions): Promise; - - /** - * Get the currently set resize mode. - * - * This method is only supported on iOS. - * - * @since 4.0.0 - */ - getResizeMode(): Promise; - - /** - * Listen for when the keyboard is about to be shown. - * - * On Android keyboardWillShow and keyboardDidShow fire almost at the same time. - * - * @since 1.0.0 - */ - addListener( - eventName: 'keyboardWillShow', - listenerFunc: (info: KeyboardInfo) => void, - ): Promise; - - /** - * Listen for when the keyboard is shown. - * - * On Android keyboardWillShow and keyboardDidShow fire almost at the same time. - * - * @since 1.0.0 - */ - addListener( - eventName: 'keyboardDidShow', - listenerFunc: (info: KeyboardInfo) => void, - ): Promise; - - /** - * Listen for when the keyboard is about to be hidden. - * - * On Android keyboardWillHide and keyboardDidHide fire almost at the same time. - * - * @since 1.0.0 - */ - addListener( - eventName: 'keyboardWillHide', - listenerFunc: () => void, - ): Promise; - - /** - * Listen for when the keyboard is hidden. - * - * On Android keyboardWillHide and keyboardDidHide fire almost at the same time. - * - * @since 1.0.0 - */ - addListener( - eventName: 'keyboardDidHide', - listenerFunc: () => void, - ): Promise; - - /** - * Remove all native listeners for this plugin. - * - * @since 1.0.0 - */ - removeAllListeners(): Promise; -} diff --git a/keyboard/src/index.ts b/keyboard/src/index.ts deleted file mode 100644 index a20e05371f..0000000000 --- a/keyboard/src/index.ts +++ /dev/null @@ -1,8 +0,0 @@ -import { registerPlugin } from '@capacitor/core'; - -import type { KeyboardPlugin } from './definitions'; - -const Keyboard = registerPlugin('Keyboard'); - -export * from './definitions'; -export { Keyboard }; diff --git a/keyboard/tsconfig.json b/keyboard/tsconfig.json deleted file mode 100644 index f2e88e6a07..0000000000 --- a/keyboard/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"] -} diff --git a/local-notifications/CHANGELOG.md b/local-notifications/CHANGELOG.md index cff995493f..23b053517c 100644 --- a/local-notifications/CHANGELOG.md +++ b/local-notifications/CHANGELOG.md @@ -3,6 +3,18 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +# [8.0.0-alpha.1](https://github.com/ionic-team/capacitor-plugins/compare/@capacitor/local-notifications@7.0.3...@capacitor/local-notifications@8.0.0-alpha.1) (2025-09-08) + +**Note:** Version bump only for package @capacitor/local-notifications + +## [7.0.3](https://github.com/ionic-team/capacitor-plugins/compare/@capacitor/local-notifications@7.0.2...@capacitor/local-notifications@7.0.3) (2025-09-05) + +**Note:** Version bump only for package @capacitor/local-notifications + +## [7.0.2](https://github.com/ionic-team/capacitor-plugins/compare/@capacitor/local-notifications@7.0.1...@capacitor/local-notifications@7.0.2) (2025-08-05) + +**Note:** Version bump only for package @capacitor/local-notifications + ## [7.0.1](https://github.com/ionic-team/capacitor-plugins/compare/@capacitor/local-notifications@7.0.0...@capacitor/local-notifications@7.0.1) (2025-04-02) **Note:** Version bump only for package @capacitor/local-notifications diff --git a/local-notifications/CapacitorLocalNotifications.podspec b/local-notifications/CapacitorLocalNotifications.podspec index ca4168ca8f..5be5af31ac 100644 --- a/local-notifications/CapacitorLocalNotifications.podspec +++ b/local-notifications/CapacitorLocalNotifications.podspec @@ -11,7 +11,7 @@ Pod::Spec.new do |s| 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}', 'local-notifications/ios/Sources/**/*.{swift,h,m,c,cc,mm,cpp}' - s.ios.deployment_target = '14.0' + s.ios.deployment_target = '15.0' s.dependency 'Capacitor' s.swift_version = '5.1' end diff --git a/local-notifications/Package.swift b/local-notifications/Package.swift index 7de14b6a21..dcc7b65d9e 100644 --- a/local-notifications/Package.swift +++ b/local-notifications/Package.swift @@ -3,7 +3,7 @@ import PackageDescription let package = Package( name: "CapacitorLocalNotifications", - platforms: [.iOS(.v14)], + platforms: [.iOS(.v15)], products: [ .library( name: "CapacitorLocalNotifications", diff --git a/local-notifications/README.md b/local-notifications/README.md index 7af4e5076d..6e62cf9acb 100644 --- a/local-notifications/README.md +++ b/local-notifications/README.md @@ -38,11 +38,11 @@ For more information about the behavior changes of your app related to the priva On Android, the Local Notifications can be configured with the following options: -| Prop | Type | Description | Since | -| --------------- | ------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----- | -| **`smallIcon`** | string | Set the default status bar icon for notifications. Icons should be placed in your app's `res/drawable` folder. The value for this option should be the drawable resource ID, which is the filename without an extension. Only available for Android. | 1.0.0 | -| **`iconColor`** | string | Set the default color of status bar icons for notifications. Only available for Android. | 1.0.0 | -| **`sound`** | string | Set the default notification sound for notifications. On Android 26+ it sets the default channel sound and can't be changed unless the app is uninstalled. If the audio file is not found, it will result in the default system sound being played on Android 21-25 and no sound on Android 26+. Only available for Android. | 1.0.0 | +| Prop | Type | Description | Since | +| --------------- | ------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | ----- | +| **`smallIcon`** | string | Set the default status bar icon for notifications. Icons should be placed in your app's `res/drawable` folder. The value for this option should be the drawable resource ID, which is the filename without an extension. Only available for Android. | 1.0.0 | +| **`iconColor`** | string | Set the default color of status bar icons for notifications. Only available for Android. | 1.0.0 | +| **`sound`** | string | Set the default notification sound for notifications. On Android 8+ it sets the default channel sound and can't be changed unless the app is uninstalled. If the audio file is not found, it will result in the default system sound being played on Android 7.x and no sound on Android 8+. Only available for Android. | 1.0.0 | ### Examples @@ -453,30 +453,30 @@ The object that describes a local notification. #### LocalNotificationSchema -| Prop | Type | Description | Since | -| ---------------------- | --------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----- | -| **`title`** | string | The title of the notification. | 1.0.0 | -| **`body`** | string | The body of the notification, shown below the title. | 1.0.0 | -| **`largeBody`** | string | Sets a multiline text block for display in a big text notification style. | 1.0.0 | -| **`summaryText`** | string | Used to set the summary text detail in inbox and big text notification styles. Only available for Android. | 1.0.0 | -| **`id`** | number | The notification identifier. On Android it's a 32-bit int. So the value should be between -2147483648 and 2147483647 inclusive. | 1.0.0 | -| **`schedule`** | Schedule | Schedule this notification for a later time. | 1.0.0 | -| **`sound`** | string | Name of the audio file to play when this notification is displayed. Include the file extension with the filename. On iOS, the file should be in the app bundle. On Android, the file should be in res/raw folder. Recommended format is `.wav` because is supported by both iOS and Android. Only available for iOS and Android < 26. For Android 26+ use channelId of a channel configured with the desired sound. If the sound file is not found, (i.e. empty string or wrong name) the default system notification sound will be used. If not provided, it will produce the default sound on Android and no sound on iOS. | 1.0.0 | -| **`smallIcon`** | string | Set a custom status bar icon. If set, this overrides the `smallIcon` option from Capacitor configuration. Icons should be placed in your app's `res/drawable` folder. The value for this option should be the drawable resource ID, which is the filename without an extension. Only available for Android. | 1.0.0 | -| **`largeIcon`** | string | Set a large icon for notifications. Icons should be placed in your app's `res/drawable` folder. The value for this option should be the drawable resource ID, which is the filename without an extension. Only available for Android. | 1.0.0 | -| **`iconColor`** | string | Set the color of the notification icon. Only available for Android. | 1.0.0 | -| **`attachments`** | Attachment[] | Set attachments for this notification. | 1.0.0 | -| **`actionTypeId`** | string | Associate an action type with this notification. | 1.0.0 | -| **`extra`** | any | Set extra data to store within this notification. | 1.0.0 | -| **`threadIdentifier`** | string | Used to group multiple notifications. Sets `threadIdentifier` on the [`UNMutableNotificationContent`](https://developer.apple.com/documentation/usernotifications/unmutablenotificationcontent). Only available for iOS. | 1.0.0 | -| **`summaryArgument`** | string | The string this notification adds to the category's summary format string. Sets `summaryArgument` on the [`UNMutableNotificationContent`](https://developer.apple.com/documentation/usernotifications/unmutablenotificationcontent). Only available for iOS. | 1.0.0 | -| **`group`** | string | Used to group multiple notifications. Calls `setGroup()` on [`NotificationCompat.Builder`](https://developer.android.com/reference/androidx/core/app/NotificationCompat.Builder) with the provided value. Only available for Android. | 1.0.0 | -| **`groupSummary`** | boolean | If true, this notification becomes the summary for a group of notifications. Calls `setGroupSummary()` on [`NotificationCompat.Builder`](https://developer.android.com/reference/androidx/core/app/NotificationCompat.Builder) with the provided value. Only available for Android when using `group`. | 1.0.0 | -| **`channelId`** | string | Specifies the channel the notification should be delivered on. If channel with the given name does not exist then the notification will not fire. If not provided, it will use the default channel. Calls `setChannelId()` on [`NotificationCompat.Builder`](https://developer.android.com/reference/androidx/core/app/NotificationCompat.Builder) with the provided value. Only available for Android 26+. | 1.0.0 | -| **`ongoing`** | boolean | If true, the notification can't be swiped away. Calls `setOngoing()` on [`NotificationCompat.Builder`](https://developer.android.com/reference/androidx/core/app/NotificationCompat.Builder) with the provided value. Only available for Android. | 1.0.0 | -| **`autoCancel`** | boolean | If true, the notification is canceled when the user clicks on it. Calls `setAutoCancel()` on [`NotificationCompat.Builder`](https://developer.android.com/reference/androidx/core/app/NotificationCompat.Builder) with the provided value. Only available for Android. | 1.0.0 | -| **`inboxList`** | string[] | Sets a list of strings for display in an inbox style notification. Up to 5 strings are allowed. Only available for Android. | 1.0.0 | -| **`silent`** | boolean | If true, notification will not appear while app is in the foreground. Only available for iOS. | 5.0.0 | +| Prop | Type | Description | Since | +| ---------------------- | --------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----- | +| **`title`** | string | The title of the notification. | 1.0.0 | +| **`body`** | string | The body of the notification, shown below the title. | 1.0.0 | +| **`largeBody`** | string | Sets a multiline text block for display in a big text notification style. | 1.0.0 | +| **`summaryText`** | string | Used to set the summary text detail in inbox and big text notification styles. Only available for Android. | 1.0.0 | +| **`id`** | number | The notification identifier. On Android it's a 32-bit int. So the value should be between -2147483648 and 2147483647 inclusive. | 1.0.0 | +| **`schedule`** | Schedule | Schedule this notification for a later time. | 1.0.0 | +| **`sound`** | string | Name of the audio file to play when this notification is displayed. Include the file extension with the filename. On iOS, the file should be in the app bundle. On Android, the file should be in res/raw folder. Recommended format is `.wav` because is supported by both iOS and Android. Only available for iOS and Android 7.x. For Android 8+ use channelId of a channel configured with the desired sound. If the sound file is not found, (i.e. empty string or wrong name) the default system notification sound will be used. If not provided, it will produce the default sound on Android and no sound on iOS. | 1.0.0 | +| **`smallIcon`** | string | Set a custom status bar icon. If set, this overrides the `smallIcon` option from Capacitor configuration. Icons should be placed in your app's `res/drawable` folder. The value for this option should be the drawable resource ID, which is the filename without an extension. Only available for Android. | 1.0.0 | +| **`largeIcon`** | string | Set a large icon for notifications. Icons should be placed in your app's `res/drawable` folder. The value for this option should be the drawable resource ID, which is the filename without an extension. Only available for Android. | 1.0.0 | +| **`iconColor`** | string | Set the color of the notification icon. Only available for Android. | 1.0.0 | +| **`attachments`** | Attachment[] | Set attachments for this notification. | 1.0.0 | +| **`actionTypeId`** | string | Associate an action type with this notification. | 1.0.0 | +| **`extra`** | any | Set extra data to store within this notification. | 1.0.0 | +| **`threadIdentifier`** | string | Used to group multiple notifications. Sets `threadIdentifier` on the [`UNMutableNotificationContent`](https://developer.apple.com/documentation/usernotifications/unmutablenotificationcontent). Only available for iOS. | 1.0.0 | +| **`summaryArgument`** | string | The string this notification adds to the category's summary format string. Sets `summaryArgument` on the [`UNMutableNotificationContent`](https://developer.apple.com/documentation/usernotifications/unmutablenotificationcontent). Only available for iOS. | 1.0.0 | +| **`group`** | string | Used to group multiple notifications. Calls `setGroup()` on [`NotificationCompat.Builder`](https://developer.android.com/reference/androidx/core/app/NotificationCompat.Builder) with the provided value. Only available for Android. | 1.0.0 | +| **`groupSummary`** | boolean | If true, this notification becomes the summary for a group of notifications. Calls `setGroupSummary()` on [`NotificationCompat.Builder`](https://developer.android.com/reference/androidx/core/app/NotificationCompat.Builder) with the provided value. Only available for Android when using `group`. | 1.0.0 | +| **`channelId`** | string | Specifies the channel the notification should be delivered on. If channel with the given name does not exist then the notification will not fire. If not provided, it will use the default channel. Calls `setChannelId()` on [`NotificationCompat.Builder`](https://developer.android.com/reference/androidx/core/app/NotificationCompat.Builder) with the provided value. Only available for Android 8+. | 1.0.0 | +| **`ongoing`** | boolean | If true, the notification can't be swiped away. Calls `setOngoing()` on [`NotificationCompat.Builder`](https://developer.android.com/reference/androidx/core/app/NotificationCompat.Builder) with the provided value. Only available for Android. | 1.0.0 | +| **`autoCancel`** | boolean | If true, the notification is canceled when the user clicks on it. Calls `setAutoCancel()` on [`NotificationCompat.Builder`](https://developer.android.com/reference/androidx/core/app/NotificationCompat.Builder) with the provided value. Only available for Android. | 1.0.0 | +| **`inboxList`** | string[] | Sets a list of strings for display in an inbox style notification. Up to 5 strings are allowed. Only available for Android. | 1.0.0 | +| **`silent`** | boolean | If true, notification will not appear while app is in the foreground. Only available for iOS. | 5.0.0 | #### Schedule @@ -485,14 +485,14 @@ Represents a schedule for a notification. Use either `at`, `on`, or `every` to schedule notifications. -| Prop | Type | Description | Since | -| -------------------- | ------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----- | -| **`at`** | Date | Schedule a notification at a specific date and time. | 1.0.0 | -| **`repeats`** | boolean | Repeat delivery of this notification at the date and time specified by `at`. Only available for iOS and Android. | 1.0.0 | -| **`allowWhileIdle`** | boolean | Allow this notification to fire while in [Doze](https://developer.android.com/training/monitoring-device-state/doze-standby) Only available for Android 23+. Note that these notifications can only fire [once per 9 minutes, per app](https://developer.android.com/training/monitoring-device-state/doze-standby#assessing_your_app). | 1.0.0 | -| **`on`** | ScheduleOn | Schedule a notification on particular interval(s). This is similar to scheduling [cron](https://en.wikipedia.org/wiki/Cron) jobs. Only available for iOS and Android. | 1.0.0 | -| **`every`** | ScheduleEvery | Schedule a notification on a particular interval. | 1.0.0 | -| **`count`** | number | Limit the number times a notification is delivered by the interval specified by `every`. | 1.0.0 | +| Prop | Type | Description | Since | +| -------------------- | ------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----- | +| **`at`** | Date | Schedule a notification at a specific date and time. | 1.0.0 | +| **`repeats`** | boolean | Repeat delivery of this notification at the date and time specified by `at`. Only available for iOS and Android. | 1.0.0 | +| **`allowWhileIdle`** | boolean | Allow this notification to fire while in [Doze](https://developer.android.com/training/monitoring-device-state/doze-standby) Note that these notifications can only fire [once per 9 minutes, per app](https://developer.android.com/training/monitoring-device-state/doze-standby#assessing_your_app). | 1.0.0 | +| **`on`** | ScheduleOn | Schedule a notification on particular interval(s). This is similar to scheduling [cron](https://en.wikipedia.org/wiki/Cron) jobs. Only available for iOS and Android. | 1.0.0 | +| **`every`** | ScheduleEvery | Schedule a notification on a particular interval. | 1.0.0 | +| **`count`** | number | Limit the number times a notification is delivered by the interval specified by `every`. | 1.0.0 | #### Date diff --git a/local-notifications/android/build.gradle b/local-notifications/android/build.gradle index cc5dfc5018..ac8243d523 100644 --- a/local-notifications/android/build.gradle +++ b/local-notifications/android/build.gradle @@ -1,9 +1,9 @@ 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.0' - androidxJunitVersion = project.hasProperty('androidxJunitVersion') ? rootProject.ext.androidxJunitVersion : '1.2.1' - androidxEspressoCoreVersion = project.hasProperty('androidxEspressoCoreVersion') ? rootProject.ext.androidxEspressoCoreVersion : '3.6.1' + androidxAppCompatVersion = project.hasProperty('androidxAppCompatVersion') ? rootProject.ext.androidxAppCompatVersion : '1.7.1' + androidxJunitVersion = project.hasProperty('androidxJunitVersion') ? rootProject.ext.androidxJunitVersion : '1.3.0' + androidxEspressoCoreVersion = project.hasProperty('androidxEspressoCoreVersion') ? rootProject.ext.androidxEspressoCoreVersion : '3.7.0' } buildscript { @@ -11,11 +11,11 @@ buildscript { google() mavenCentral() maven { - url "https://plugins.gradle.org/m2/" + url = "https://plugins.gradle.org/m2/" } } dependencies { - classpath 'com.android.tools.build:gradle:8.7.2' + 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' } @@ -30,11 +30,11 @@ if (System.getenv("CAP_PLUGIN_PUBLISH") == "true") { } android { - namespace "com.capacitorjs.plugins.localnotifications" - compileSdk project.hasProperty('compileSdkVersion') ? rootProject.ext.compileSdkVersion : 35 + namespace = "com.capacitorjs.plugins.localnotifications" + compileSdk = project.hasProperty('compileSdkVersion') ? rootProject.ext.compileSdkVersion : 36 defaultConfig { - minSdkVersion project.hasProperty('minSdkVersion') ? rootProject.ext.minSdkVersion : 23 - targetSdkVersion project.hasProperty('targetSdkVersion') ? rootProject.ext.targetSdkVersion : 35 + 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" @@ -46,7 +46,7 @@ android { } } lintOptions { - abortOnError false + abortOnError = false } compileOptions { sourceCompatibility JavaVersion.VERSION_21 diff --git a/local-notifications/android/gradle/wrapper/gradle-wrapper.jar b/local-notifications/android/gradle/wrapper/gradle-wrapper.jar index a4b76b9530..1b33c55baa 100644 Binary files a/local-notifications/android/gradle/wrapper/gradle-wrapper.jar and b/local-notifications/android/gradle/wrapper/gradle-wrapper.jar differ diff --git a/local-notifications/android/gradle/wrapper/gradle-wrapper.properties b/local-notifications/android/gradle/wrapper/gradle-wrapper.properties index c1d5e01859..7705927e94 100644 --- a/local-notifications/android/gradle/wrapper/gradle-wrapper.properties +++ b/local-notifications/android/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-8.11.1-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.14.3-all.zip networkTimeout=10000 validateDistributionUrl=true zipStoreBase=GRADLE_USER_HOME diff --git a/local-notifications/android/gradlew b/local-notifications/android/gradlew index f5feea6d6b..23d15a9367 100755 --- a/local-notifications/android/gradlew +++ b/local-notifications/android/gradlew @@ -86,8 +86,7 @@ done # 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 -' "$PWD" ) || exit +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 @@ -115,7 +114,7 @@ case "$( uname )" in #( NONSTOP* ) nonstop=true ;; esac -CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar +CLASSPATH="\\\"\\\"" # Determine the Java command to use to start the JVM. @@ -206,7 +205,7 @@ fi DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' # Collect all arguments for the java command: -# * DEFAULT_JVM_OPTS, JAVA_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, +# * 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. @@ -214,7 +213,7 @@ DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' set -- \ "-Dorg.gradle.appname=$APP_BASE_NAME" \ -classpath "$CLASSPATH" \ - org.gradle.wrapper.GradleWrapperMain \ + -jar "$APP_HOME/gradle/wrapper/gradle-wrapper.jar" \ "$@" # Stop when "xargs" is not available. diff --git a/local-notifications/android/gradlew.bat b/local-notifications/android/gradlew.bat index 9b42019c79..5eed7ee845 100644 --- a/local-notifications/android/gradlew.bat +++ b/local-notifications/android/gradlew.bat @@ -70,11 +70,11 @@ goto fail :execute @rem Setup the command line -set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar +set CLASSPATH= @rem Execute Gradle -"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* +"%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 diff --git a/local-notifications/android/src/main/java/com/capacitorjs/plugins/localnotifications/LocalNotificationRestoreReceiver.java b/local-notifications/android/src/main/java/com/capacitorjs/plugins/localnotifications/LocalNotificationRestoreReceiver.java index 134fd0657f..8e0390f082 100644 --- a/local-notifications/android/src/main/java/com/capacitorjs/plugins/localnotifications/LocalNotificationRestoreReceiver.java +++ b/local-notifications/android/src/main/java/com/capacitorjs/plugins/localnotifications/LocalNotificationRestoreReceiver.java @@ -1,11 +1,8 @@ package com.capacitorjs.plugins.localnotifications; -import static android.os.Build.VERSION.SDK_INT; - import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; -import android.os.Build; import android.os.UserManager; import com.getcapacitor.CapConfig; import java.util.ArrayList; @@ -16,10 +13,8 @@ public class LocalNotificationRestoreReceiver extends BroadcastReceiver { @Override public void onReceive(Context context, Intent intent) { - if (SDK_INT >= Build.VERSION_CODES.N) { - UserManager um = context.getSystemService(UserManager.class); - if (um == null || !um.isUserUnlocked()) return; - } + UserManager um = context.getSystemService(UserManager.class); + if (um == null || !um.isUserUnlocked()) return; NotificationStorage storage = new NotificationStorage(context); List ids = storage.getSavedNotificationIds(); diff --git a/local-notifications/package.json b/local-notifications/package.json index bfd813da56..5b5cb76259 100644 --- a/local-notifications/package.json +++ b/local-notifications/package.json @@ -1,6 +1,6 @@ { "name": "@capacitor/local-notifications", - "version": "7.0.1", + "version": "8.0.0-alpha.1", "description": "The Local Notifications API provides a way to schedule device notifications locally (i.e. without a server sending push notifications).", "main": "dist/plugin.cjs.js", "module": "dist/esm/index.js", @@ -47,11 +47,11 @@ "publish:cocoapod": "pod trunk push ./CapacitorLocalNotifications.podspec --allow-warnings" }, "devDependencies": { - "@capacitor/android": "^7.0.0", - "@capacitor/cli": "^6.0.0", - "@capacitor/core": "^7.0.0", + "@capacitor/android": "next", + "@capacitor/cli": "next", + "@capacitor/core": "next", "@capacitor/docgen": "0.2.2", - "@capacitor/ios": "^7.0.0", + "@capacitor/ios": "next", "@ionic/eslint-config": "^0.4.0", "@ionic/prettier-config": "~1.0.1", "@ionic/swiftlint-config": "^1.1.2", @@ -64,7 +64,7 @@ "typescript": "~4.1.5" }, "peerDependencies": { - "@capacitor/core": ">=7.0.0" + "@capacitor/core": "next" }, "prettier": "@ionic/prettier-config", "swiftlint": "@ionic/swiftlint-config", diff --git a/local-notifications/src/definitions.ts b/local-notifications/src/definitions.ts index 2949f1068f..d4c60e3ff3 100644 --- a/local-notifications/src/definitions.ts +++ b/local-notifications/src/definitions.ts @@ -35,11 +35,11 @@ declare module '@capacitor/cli' { /** * Set the default notification sound for notifications. * - * On Android 26+ it sets the default channel sound and can't be + * On Android 8+ it sets the default channel sound and can't be * changed unless the app is uninstalled. * * If the audio file is not found, it will result in the default system - * sound being played on Android 21-25 and no sound on Android 26+. + * sound being played on Android 7.x and no sound on Android 8+. * * Only available for Android. * @@ -591,8 +591,8 @@ export interface LocalNotificationSchema { * * Recommended format is `.wav` because is supported by both iOS and Android. * - * Only available for iOS and Android < 26. - * For Android 26+ use channelId of a channel configured with the desired sound. + * Only available for iOS and Android 7.x. + * For Android 8+ use channelId of a channel configured with the desired sound. * * If the sound file is not found, (i.e. empty string or wrong name) * the default system notification sound will be used. @@ -722,7 +722,7 @@ export interface LocalNotificationSchema { * [`NotificationCompat.Builder`](https://developer.android.com/reference/androidx/core/app/NotificationCompat.Builder) * with the provided value. * - * Only available for Android 26+. + * Only available for Android 8+. * * @since 1.0.0 */ @@ -803,8 +803,6 @@ export interface Schedule { /** * Allow this notification to fire while in [Doze](https://developer.android.com/training/monitoring-device-state/doze-standby) * - * Only available for Android 23+. - * * Note that these notifications can only fire [once per 9 minutes, per app](https://developer.android.com/training/monitoring-device-state/doze-standby#assessing_your_app). * * @since 1.0.0 diff --git a/motion/CHANGELOG.md b/motion/CHANGELOG.md index 0e9d239bbe..070780db80 100644 --- a/motion/CHANGELOG.md +++ b/motion/CHANGELOG.md @@ -3,6 +3,14 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +# [8.0.0-alpha.1](https://github.com/ionic-team/capacitor-plugins/compare/@capacitor/motion@7.0.1...@capacitor/motion@8.0.0-alpha.1) (2025-09-08) + +**Note:** Version bump only for package @capacitor/motion + +## [7.0.1](https://github.com/ionic-team/capacitor-plugins/compare/@capacitor/motion@7.0.0...@capacitor/motion@7.0.1) (2025-08-05) + +**Note:** Version bump only for package @capacitor/motion + # [7.0.0](https://github.com/ionic-team/capacitor-plugins/compare/@capacitor/motion@7.0.0-rc.0...@capacitor/motion@7.0.0) (2025-01-20) **Note:** Version bump only for package @capacitor/motion diff --git a/motion/package.json b/motion/package.json index a1037542e5..8bd9704c0f 100644 --- a/motion/package.json +++ b/motion/package.json @@ -1,6 +1,6 @@ { "name": "@capacitor/motion", - "version": "7.0.0", + "version": "8.0.0-alpha.1", "description": "The Motion API tracks accelerometer and device orientation (compass heading, etc.)", "main": "dist/plugin.cjs.js", "module": "dist/esm/index.js", @@ -39,10 +39,10 @@ "prepublishOnly": "npm run build" }, "devDependencies": { - "@capacitor/android": "^7.0.0", - "@capacitor/core": "^7.0.0", + "@capacitor/android": "next", + "@capacitor/core": "next", "@capacitor/docgen": "0.2.2", - "@capacitor/ios": "^7.0.0", + "@capacitor/ios": "next", "@ionic/eslint-config": "^0.4.0", "@ionic/prettier-config": "~1.0.1", "eslint": "^8.57.0", @@ -53,7 +53,7 @@ "typescript": "~4.1.5" }, "peerDependencies": { - "@capacitor/core": ">=7.0.0" + "@capacitor/core": "next" }, "prettier": "@ionic/prettier-config", "eslintConfig": { diff --git a/network/CHANGELOG.md b/network/CHANGELOG.md index 770358aab5..294b5445a5 100644 --- a/network/CHANGELOG.md +++ b/network/CHANGELOG.md @@ -3,6 +3,14 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +# [8.0.0-alpha.1](https://github.com/ionic-team/capacitor-plugins/compare/@capacitor/network@7.0.2...@capacitor/network@8.0.0-alpha.1) (2025-09-08) + +**Note:** Version bump only for package @capacitor/network + +## [7.0.2](https://github.com/ionic-team/capacitor-plugins/compare/@capacitor/network@7.0.1...@capacitor/network@7.0.2) (2025-08-05) + +**Note:** Version bump only for package @capacitor/network + ## [7.0.1](https://github.com/ionic-team/capacitor-plugins/compare/@capacitor/network@7.0.0...@capacitor/network@7.0.1) (2025-04-02) **Note:** Version bump only for package @capacitor/network diff --git a/network/CapacitorNetwork.podspec b/network/CapacitorNetwork.podspec index ab886e8128..cbfdecbd37 100644 --- a/network/CapacitorNetwork.podspec +++ b/network/CapacitorNetwork.podspec @@ -11,7 +11,7 @@ Pod::Spec.new do |s| 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}', 'network/ios/Sources/**/*.{swift,h,m,c,cc,mm,cpp}' - s.ios.deployment_target = '14.0' + s.ios.deployment_target = '15.0' s.dependency 'Capacitor' s.swift_version = '5.1' end diff --git a/network/Package.swift b/network/Package.swift index f802991a5a..2e06ae0f3f 100644 --- a/network/Package.swift +++ b/network/Package.swift @@ -3,7 +3,7 @@ import PackageDescription let package = Package( name: "CapacitorNetwork", - platforms: [.iOS(.v14)], + platforms: [.iOS(.v15)], products: [ .library( name: "CapacitorNetwork", diff --git a/network/android/build.gradle b/network/android/build.gradle index 8a6205632f..7b9b2426ec 100644 --- a/network/android/build.gradle +++ b/network/android/build.gradle @@ -1,9 +1,9 @@ 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.0' - androidxJunitVersion = project.hasProperty('androidxJunitVersion') ? rootProject.ext.androidxJunitVersion : '1.2.1' - androidxEspressoCoreVersion = project.hasProperty('androidxEspressoCoreVersion') ? rootProject.ext.androidxEspressoCoreVersion : '3.6.1' + androidxAppCompatVersion = project.hasProperty('androidxAppCompatVersion') ? rootProject.ext.androidxAppCompatVersion : '1.7.1' + androidxJunitVersion = project.hasProperty('androidxJunitVersion') ? rootProject.ext.androidxJunitVersion : '1.3.0' + androidxEspressoCoreVersion = project.hasProperty('androidxEspressoCoreVersion') ? rootProject.ext.androidxEspressoCoreVersion : '3.7.0' } buildscript { @@ -11,11 +11,11 @@ buildscript { google() mavenCentral() maven { - url "https://plugins.gradle.org/m2/" + url = "https://plugins.gradle.org/m2/" } } dependencies { - classpath 'com.android.tools.build:gradle:8.7.2' + 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' } @@ -30,11 +30,11 @@ if (System.getenv("CAP_PLUGIN_PUBLISH") == "true") { } android { - namespace "com.capacitorjs.plugins.network" - compileSdk project.hasProperty('compileSdkVersion') ? rootProject.ext.compileSdkVersion : 35 + namespace = "com.capacitorjs.plugins.network" + compileSdk = project.hasProperty('compileSdkVersion') ? rootProject.ext.compileSdkVersion : 36 defaultConfig { - minSdkVersion project.hasProperty('minSdkVersion') ? rootProject.ext.minSdkVersion : 23 - targetSdkVersion project.hasProperty('targetSdkVersion') ? rootProject.ext.targetSdkVersion : 35 + 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" @@ -46,7 +46,7 @@ android { } } lintOptions { - abortOnError false + abortOnError = false } compileOptions { sourceCompatibility JavaVersion.VERSION_21 diff --git a/network/android/gradle/wrapper/gradle-wrapper.jar b/network/android/gradle/wrapper/gradle-wrapper.jar index a4b76b9530..1b33c55baa 100644 Binary files a/network/android/gradle/wrapper/gradle-wrapper.jar and b/network/android/gradle/wrapper/gradle-wrapper.jar differ diff --git a/network/android/gradle/wrapper/gradle-wrapper.properties b/network/android/gradle/wrapper/gradle-wrapper.properties index c1d5e01859..7705927e94 100644 --- a/network/android/gradle/wrapper/gradle-wrapper.properties +++ b/network/android/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-8.11.1-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.14.3-all.zip networkTimeout=10000 validateDistributionUrl=true zipStoreBase=GRADLE_USER_HOME diff --git a/network/android/gradlew b/network/android/gradlew index f5feea6d6b..23d15a9367 100755 --- a/network/android/gradlew +++ b/network/android/gradlew @@ -86,8 +86,7 @@ done # 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 -' "$PWD" ) || exit +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 @@ -115,7 +114,7 @@ case "$( uname )" in #( NONSTOP* ) nonstop=true ;; esac -CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar +CLASSPATH="\\\"\\\"" # Determine the Java command to use to start the JVM. @@ -206,7 +205,7 @@ fi DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' # Collect all arguments for the java command: -# * DEFAULT_JVM_OPTS, JAVA_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, +# * 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. @@ -214,7 +213,7 @@ DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' set -- \ "-Dorg.gradle.appname=$APP_BASE_NAME" \ -classpath "$CLASSPATH" \ - org.gradle.wrapper.GradleWrapperMain \ + -jar "$APP_HOME/gradle/wrapper/gradle-wrapper.jar" \ "$@" # Stop when "xargs" is not available. diff --git a/network/android/gradlew.bat b/network/android/gradlew.bat index 9b42019c79..5eed7ee845 100644 --- a/network/android/gradlew.bat +++ b/network/android/gradlew.bat @@ -70,11 +70,11 @@ goto fail :execute @rem Setup the command line -set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar +set CLASSPATH= @rem Execute Gradle -"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* +"%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 diff --git a/network/android/src/main/java/com/capacitorjs/plugins/network/Network.java b/network/android/src/main/java/com/capacitorjs/plugins/network/Network.java index decfd702fd..8f8842feec 100644 --- a/network/android/src/main/java/com/capacitorjs/plugins/network/Network.java +++ b/network/android/src/main/java/com/capacitorjs/plugins/network/Network.java @@ -1,18 +1,12 @@ package com.capacitorjs.plugins.network; -import android.annotation.TargetApi; import android.content.BroadcastReceiver; import android.content.Context; -import android.content.Intent; -import android.content.IntentFilter; import android.net.ConnectivityManager; import android.net.ConnectivityManager.NetworkCallback; import android.net.NetworkCapabilities; -import android.os.Build; import androidx.annotation.NonNull; import androidx.annotation.Nullable; -import androidx.annotation.RequiresApi; -import androidx.appcompat.app.AppCompatActivity; public class Network { @@ -50,21 +44,10 @@ public void onCapabilitiesChanged(@NonNull android.net.Network network, @NonNull * Create network monitoring object. * @param context */ - @SuppressWarnings("deprecation") public Network(@NonNull Context context) { this.context = context; this.connectivityManager = (ConnectivityManager) this.context.getSystemService(Context.CONNECTIVITY_SERVICE); - if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.M) { - receiver = - new BroadcastReceiver() { - @Override - public void onReceive(Context context, Intent intent) { - statusChangeListener.onNetworkStatusChanged(false); - } - }; - } else { - this.connectivityCallback = new ConnectivityCallback(); - } + this.connectivityCallback = new ConnectivityCallback(); } /** @@ -90,26 +73,21 @@ public NetworkStatusChangeListener getStatusChangeListener() { */ public NetworkStatus getNetworkStatus() { NetworkStatus networkStatus = new NetworkStatus(); - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { - if (this.connectivityManager != null) { - android.net.Network activeNetwork = this.connectivityManager.getActiveNetwork(); - NetworkCapabilities capabilities = - this.connectivityManager.getNetworkCapabilities(this.connectivityManager.getActiveNetwork()); - if (activeNetwork != null && capabilities != null) { - networkStatus.connected = - capabilities.hasCapability(NetworkCapabilities.NET_CAPABILITY_VALIDATED) && - capabilities.hasCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET); - if (capabilities.hasTransport(NetworkCapabilities.TRANSPORT_WIFI)) { - networkStatus.connectionType = NetworkStatus.ConnectionType.WIFI; - } else if (capabilities.hasTransport(NetworkCapabilities.TRANSPORT_CELLULAR)) { - networkStatus.connectionType = NetworkStatus.ConnectionType.CELLULAR; - } else { - networkStatus.connectionType = NetworkStatus.ConnectionType.UNKNOWN; - } + if (this.connectivityManager != null) { + android.net.Network activeNetwork = this.connectivityManager.getActiveNetwork(); + NetworkCapabilities capabilities = this.connectivityManager.getNetworkCapabilities(this.connectivityManager.getActiveNetwork()); + if (activeNetwork != null && capabilities != null) { + networkStatus.connected = + capabilities.hasCapability(NetworkCapabilities.NET_CAPABILITY_VALIDATED) && + capabilities.hasCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET); + if (capabilities.hasTransport(NetworkCapabilities.TRANSPORT_WIFI)) { + networkStatus.connectionType = NetworkStatus.ConnectionType.WIFI; + } else if (capabilities.hasTransport(NetworkCapabilities.TRANSPORT_CELLULAR)) { + networkStatus.connectionType = NetworkStatus.ConnectionType.CELLULAR; + } else { + networkStatus.connectionType = NetworkStatus.ConnectionType.UNKNOWN; } } - } else { - networkStatus = getAndParseNetworkInfo(); } return networkStatus; } @@ -133,27 +111,14 @@ private NetworkStatus getAndParseNetworkInfo() { /** * Register a network callback. */ - @RequiresApi(api = Build.VERSION_CODES.N) public void startMonitoring() { connectivityManager.registerDefaultNetworkCallback(connectivityCallback); } - @TargetApi(Build.VERSION_CODES.M) - public void startMonitoring(AppCompatActivity activity) { - IntentFilter filter = new IntentFilter("android.net.conn.CONNECTIVITY_CHANGE"); - activity.registerReceiver(receiver, filter); - } - /** * Unregister the network callback. */ - @RequiresApi(api = Build.VERSION_CODES.N) public void stopMonitoring() { connectivityManager.unregisterNetworkCallback(connectivityCallback); } - - @TargetApi(Build.VERSION_CODES.M) - public void stopMonitoring(@NonNull AppCompatActivity activity) { - activity.unregisterReceiver(receiver); - } } diff --git a/network/android/src/main/java/com/capacitorjs/plugins/network/NetworkPlugin.java b/network/android/src/main/java/com/capacitorjs/plugins/network/NetworkPlugin.java index 623b39f759..542ca03ae1 100644 --- a/network/android/src/main/java/com/capacitorjs/plugins/network/NetworkPlugin.java +++ b/network/android/src/main/java/com/capacitorjs/plugins/network/NetworkPlugin.java @@ -56,11 +56,7 @@ public void getStatus(PluginCall call) { */ @Override protected void handleOnResume() { - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { - implementation.startMonitoring(); - } else { - implementation.startMonitoring(getActivity()); - } + implementation.startMonitoring(); NetworkStatus afterPauseNetworkStatus = implementation.getNetworkStatus(); if ( prePauseNetworkStatus != null && @@ -82,11 +78,7 @@ protected void handleOnResume() { @Override protected void handleOnPause() { this.prePauseNetworkStatus = implementation.getNetworkStatus(); - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { - implementation.stopMonitoring(); - } else { - implementation.stopMonitoring(getActivity()); - } + implementation.stopMonitoring(); } private void updateNetworkStatus() { diff --git a/network/package.json b/network/package.json index 63314f7566..4f160e7965 100644 --- a/network/package.json +++ b/network/package.json @@ -1,6 +1,6 @@ { "name": "@capacitor/network", - "version": "7.0.1", + "version": "8.0.0-alpha.1", "description": "The Network API provides network and connectivity information.", "main": "dist/plugin.cjs.js", "module": "dist/esm/index.js", @@ -47,10 +47,10 @@ "publish:cocoapod": "pod trunk push ./CapacitorNetwork.podspec --allow-warnings" }, "devDependencies": { - "@capacitor/android": "^7.0.0", - "@capacitor/core": "^7.0.0", + "@capacitor/android": "next", + "@capacitor/core": "next", "@capacitor/docgen": "0.2.2", - "@capacitor/ios": "^7.0.0", + "@capacitor/ios": "next", "@ionic/eslint-config": "^0.4.0", "@ionic/prettier-config": "~1.0.1", "@ionic/swiftlint-config": "^1.1.2", @@ -63,7 +63,7 @@ "typescript": "~4.1.5" }, "peerDependencies": { - "@capacitor/core": ">=7.0.0" + "@capacitor/core": "next" }, "prettier": "@ionic/prettier-config", "swiftlint": "@ionic/swiftlint-config", diff --git a/nx.json b/nx.json index ee4e620131..e472b1ce67 100644 --- a/nx.json +++ b/nx.json @@ -20,8 +20,6 @@ "{projectRoot}/clipboard/dist", "{projectRoot}/device/dist", "{projectRoot}/dialog/dist", - "{projectRoot}/haptics/dist", - "{projectRoot}/keyboard/dist", "{projectRoot}/local-notifications/dist", "{projectRoot}/motion/dist", "{projectRoot}/network/dist", diff --git a/package.json b/package.json index 96d8fd9dca..1affa352fc 100644 --- a/package.json +++ b/package.json @@ -51,8 +51,6 @@ "clipboard", "device", "dialog", - "haptics", - "keyboard", "local-notifications", "motion", "network", diff --git a/preferences/CHANGELOG.md b/preferences/CHANGELOG.md index 116e709720..294a5cb258 100644 --- a/preferences/CHANGELOG.md +++ b/preferences/CHANGELOG.md @@ -3,6 +3,14 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +# [8.0.0-alpha.1](https://github.com/ionic-team/capacitor-plugins/compare/@capacitor/preferences@7.0.2...@capacitor/preferences@8.0.0-alpha.1) (2025-09-08) + +**Note:** Version bump only for package @capacitor/preferences + +## [7.0.2](https://github.com/ionic-team/capacitor-plugins/compare/@capacitor/preferences@7.0.1...@capacitor/preferences@7.0.2) (2025-08-05) + +**Note:** Version bump only for package @capacitor/preferences + ## [7.0.1](https://github.com/ionic-team/capacitor-plugins/compare/@capacitor/preferences@7.0.0...@capacitor/preferences@7.0.1) (2025-04-02) **Note:** Version bump only for package @capacitor/preferences diff --git a/preferences/CapacitorPreferences.podspec b/preferences/CapacitorPreferences.podspec index 322b44305f..9354b28950 100644 --- a/preferences/CapacitorPreferences.podspec +++ b/preferences/CapacitorPreferences.podspec @@ -11,7 +11,7 @@ Pod::Spec.new do |s| 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}', 'preferences/ios/Sources/**/*.{swift,h,m,c,cc,mm,cpp}' - s.ios.deployment_target = '14.0' + s.ios.deployment_target = '15.0' s.dependency 'Capacitor' s.swift_version = '5.1' end diff --git a/preferences/Package.swift b/preferences/Package.swift index 5520dbb07a..53e3d59d30 100644 --- a/preferences/Package.swift +++ b/preferences/Package.swift @@ -3,7 +3,7 @@ import PackageDescription let package = Package( name: "CapacitorPreferences", - platforms: [.iOS(.v14)], + platforms: [.iOS(.v15)], products: [ .library( name: "CapacitorPreferences", diff --git a/preferences/android/build.gradle b/preferences/android/build.gradle index fcef135e1c..97f0421f03 100644 --- a/preferences/android/build.gradle +++ b/preferences/android/build.gradle @@ -1,9 +1,9 @@ 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.0' - androidxJunitVersion = project.hasProperty('androidxJunitVersion') ? rootProject.ext.androidxJunitVersion : '1.2.1' - androidxEspressoCoreVersion = project.hasProperty('androidxEspressoCoreVersion') ? rootProject.ext.androidxEspressoCoreVersion : '3.6.1' + androidxAppCompatVersion = project.hasProperty('androidxAppCompatVersion') ? rootProject.ext.androidxAppCompatVersion : '1.7.1' + androidxJunitVersion = project.hasProperty('androidxJunitVersion') ? rootProject.ext.androidxJunitVersion : '1.3.0' + androidxEspressoCoreVersion = project.hasProperty('androidxEspressoCoreVersion') ? rootProject.ext.androidxEspressoCoreVersion : '3.7.0' } buildscript { @@ -11,11 +11,11 @@ buildscript { google() mavenCentral() maven { - url "https://plugins.gradle.org/m2/" + url = "https://plugins.gradle.org/m2/" } } dependencies { - classpath 'com.android.tools.build:gradle:8.7.2' + 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' } @@ -30,11 +30,11 @@ if (System.getenv("CAP_PLUGIN_PUBLISH") == "true") { } android { - namespace "com.capacitorjs.plugins.preferences" - compileSdk project.hasProperty('compileSdkVersion') ? rootProject.ext.compileSdkVersion : 35 + namespace = "com.capacitorjs.plugins.preferences" + compileSdk = project.hasProperty('compileSdkVersion') ? rootProject.ext.compileSdkVersion : 36 defaultConfig { - minSdkVersion project.hasProperty('minSdkVersion') ? rootProject.ext.minSdkVersion : 23 - targetSdkVersion project.hasProperty('targetSdkVersion') ? rootProject.ext.targetSdkVersion : 35 + 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" @@ -46,7 +46,7 @@ android { } } lintOptions { - abortOnError false + abortOnError = false } compileOptions { sourceCompatibility JavaVersion.VERSION_21 diff --git a/preferences/android/gradle/wrapper/gradle-wrapper.jar b/preferences/android/gradle/wrapper/gradle-wrapper.jar index a4b76b9530..1b33c55baa 100644 Binary files a/preferences/android/gradle/wrapper/gradle-wrapper.jar and b/preferences/android/gradle/wrapper/gradle-wrapper.jar differ diff --git a/preferences/android/gradle/wrapper/gradle-wrapper.properties b/preferences/android/gradle/wrapper/gradle-wrapper.properties index c1d5e01859..7705927e94 100644 --- a/preferences/android/gradle/wrapper/gradle-wrapper.properties +++ b/preferences/android/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-8.11.1-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.14.3-all.zip networkTimeout=10000 validateDistributionUrl=true zipStoreBase=GRADLE_USER_HOME diff --git a/preferences/android/gradlew b/preferences/android/gradlew index f5feea6d6b..23d15a9367 100755 --- a/preferences/android/gradlew +++ b/preferences/android/gradlew @@ -86,8 +86,7 @@ done # 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 -' "$PWD" ) || exit +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 @@ -115,7 +114,7 @@ case "$( uname )" in #( NONSTOP* ) nonstop=true ;; esac -CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar +CLASSPATH="\\\"\\\"" # Determine the Java command to use to start the JVM. @@ -206,7 +205,7 @@ fi DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' # Collect all arguments for the java command: -# * DEFAULT_JVM_OPTS, JAVA_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, +# * 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. @@ -214,7 +213,7 @@ DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' set -- \ "-Dorg.gradle.appname=$APP_BASE_NAME" \ -classpath "$CLASSPATH" \ - org.gradle.wrapper.GradleWrapperMain \ + -jar "$APP_HOME/gradle/wrapper/gradle-wrapper.jar" \ "$@" # Stop when "xargs" is not available. diff --git a/preferences/android/gradlew.bat b/preferences/android/gradlew.bat index 9b42019c79..5eed7ee845 100644 --- a/preferences/android/gradlew.bat +++ b/preferences/android/gradlew.bat @@ -70,11 +70,11 @@ goto fail :execute @rem Setup the command line -set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar +set CLASSPATH= @rem Execute Gradle -"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* +"%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 diff --git a/preferences/package.json b/preferences/package.json index 6098d6223c..6457a5e871 100644 --- a/preferences/package.json +++ b/preferences/package.json @@ -1,6 +1,6 @@ { "name": "@capacitor/preferences", - "version": "7.0.1", + "version": "8.0.0-alpha.1", "description": "The Preferences API provides a simple key/value persistent store for lightweight data.", "main": "dist/plugin.cjs.js", "module": "dist/esm/index.js", @@ -47,10 +47,10 @@ "publish:cocoapod": "pod trunk push ./CapacitorPreferences.podspec --allow-warnings" }, "devDependencies": { - "@capacitor/android": "^7.0.0", - "@capacitor/core": "^7.0.0", + "@capacitor/android": "next", + "@capacitor/core": "next", "@capacitor/docgen": "0.2.2", - "@capacitor/ios": "^7.0.0", + "@capacitor/ios": "next", "@ionic/eslint-config": "^0.4.0", "@ionic/prettier-config": "~1.0.1", "@ionic/swiftlint-config": "^1.1.2", @@ -63,7 +63,7 @@ "typescript": "~4.1.5" }, "peerDependencies": { - "@capacitor/core": ">=7.0.0" + "@capacitor/core": "next" }, "prettier": "@ionic/prettier-config", "swiftlint": "@ionic/swiftlint-config", diff --git a/push-notifications/CHANGELOG.md b/push-notifications/CHANGELOG.md index 200ecc1307..263474f8c0 100644 --- a/push-notifications/CHANGELOG.md +++ b/push-notifications/CHANGELOG.md @@ -3,6 +3,18 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +# [8.0.0-alpha.1](https://github.com/ionic-team/capacitor-plugins/compare/@capacitor/push-notifications@7.0.3...@capacitor/push-notifications@8.0.0-alpha.1) (2025-09-08) + +**Note:** Version bump only for package @capacitor/push-notifications + +## [7.0.3](https://github.com/ionic-team/capacitor-plugins/compare/@capacitor/push-notifications@7.0.2...@capacitor/push-notifications@7.0.3) (2025-09-05) + +**Note:** Version bump only for package @capacitor/push-notifications + +## [7.0.2](https://github.com/ionic-team/capacitor-plugins/compare/@capacitor/push-notifications@7.0.1...@capacitor/push-notifications@7.0.2) (2025-08-05) + +**Note:** Version bump only for package @capacitor/push-notifications + ## [7.0.1](https://github.com/ionic-team/capacitor-plugins/compare/@capacitor/push-notifications@7.0.0...@capacitor/push-notifications@7.0.1) (2025-04-02) **Note:** Version bump only for package @capacitor/push-notifications diff --git a/push-notifications/CapacitorPushNotifications.podspec b/push-notifications/CapacitorPushNotifications.podspec index 5e2af6e572..a2cb03cc02 100644 --- a/push-notifications/CapacitorPushNotifications.podspec +++ b/push-notifications/CapacitorPushNotifications.podspec @@ -11,7 +11,7 @@ Pod::Spec.new do |s| 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}', 'push-notifications/ios/Sources/**/*.{swift,h,m,c,cc,mm,cpp}' - s.ios.deployment_target = '14.0' + s.ios.deployment_target = '15.0' s.dependency 'Capacitor' s.swift_version = '5.1' end diff --git a/push-notifications/Package.swift b/push-notifications/Package.swift index ff692341fe..cd20be434e 100644 --- a/push-notifications/Package.swift +++ b/push-notifications/Package.swift @@ -3,7 +3,7 @@ import PackageDescription let package = Package( name: "CapacitorPushNotifications", - platforms: [.iOS(.v14)], + platforms: [.iOS(.v15)], products: [ .library( name: "CapacitorPushNotifications", diff --git a/push-notifications/README.md b/push-notifications/README.md index 85fca33b14..d5a8d71bb7 100644 --- a/push-notifications/README.md +++ b/push-notifications/README.md @@ -41,7 +41,7 @@ For more information about the behavior changes of your app related to the priva This plugin will use the following project variables (defined in your app's `variables.gradle` file): -- `firebaseMessagingVersion` version of `com.google.firebase:firebase-messaging` (default: `24.1.0`) +- `firebaseMessagingVersion` version of `com.google.firebase:firebase-messaging` (default: `25.0.1`) --- diff --git a/push-notifications/android/build.gradle b/push-notifications/android/build.gradle index b012769897..93ecab5151 100644 --- a/push-notifications/android/build.gradle +++ b/push-notifications/android/build.gradle @@ -1,10 +1,10 @@ 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.0' - androidxJunitVersion = project.hasProperty('androidxJunitVersion') ? rootProject.ext.androidxJunitVersion : '1.2.1' - androidxEspressoCoreVersion = project.hasProperty('androidxEspressoCoreVersion') ? rootProject.ext.androidxEspressoCoreVersion : '3.6.1' - firebaseMessagingVersion = project.hasProperty('firebaseMessagingVersion') ? rootProject.ext.firebaseMessagingVersion : '24.1.0' + androidxAppCompatVersion = project.hasProperty('androidxAppCompatVersion') ? rootProject.ext.androidxAppCompatVersion : '1.7.1' + androidxJunitVersion = project.hasProperty('androidxJunitVersion') ? rootProject.ext.androidxJunitVersion : '1.3.0' + androidxEspressoCoreVersion = project.hasProperty('androidxEspressoCoreVersion') ? rootProject.ext.androidxEspressoCoreVersion : '3.7.0' + firebaseMessagingVersion = project.hasProperty('firebaseMessagingVersion') ? rootProject.ext.firebaseMessagingVersion : '25.0.1' } buildscript { @@ -12,11 +12,11 @@ buildscript { google() mavenCentral() maven { - url "https://plugins.gradle.org/m2/" + url = "https://plugins.gradle.org/m2/" } } dependencies { - classpath 'com.android.tools.build:gradle:8.7.2' + 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' } @@ -31,11 +31,11 @@ if (System.getenv("CAP_PLUGIN_PUBLISH") == "true") { } android { - namespace "com.capacitorjs.plugins.pushnotifications" - compileSdk project.hasProperty('compileSdkVersion') ? rootProject.ext.compileSdkVersion : 35 + namespace = "com.capacitorjs.plugins.pushnotifications" + compileSdk = project.hasProperty('compileSdkVersion') ? rootProject.ext.compileSdkVersion : 36 defaultConfig { - minSdkVersion project.hasProperty('minSdkVersion') ? rootProject.ext.minSdkVersion : 23 - targetSdkVersion project.hasProperty('targetSdkVersion') ? rootProject.ext.targetSdkVersion : 35 + 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" @@ -47,7 +47,7 @@ android { } } lintOptions { - abortOnError false + abortOnError = false } compileOptions { sourceCompatibility JavaVersion.VERSION_21 diff --git a/push-notifications/android/gradle/wrapper/gradle-wrapper.jar b/push-notifications/android/gradle/wrapper/gradle-wrapper.jar index a4b76b9530..1b33c55baa 100644 Binary files a/push-notifications/android/gradle/wrapper/gradle-wrapper.jar and b/push-notifications/android/gradle/wrapper/gradle-wrapper.jar differ diff --git a/push-notifications/android/gradle/wrapper/gradle-wrapper.properties b/push-notifications/android/gradle/wrapper/gradle-wrapper.properties index c1d5e01859..7705927e94 100644 --- a/push-notifications/android/gradle/wrapper/gradle-wrapper.properties +++ b/push-notifications/android/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-8.11.1-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.14.3-all.zip networkTimeout=10000 validateDistributionUrl=true zipStoreBase=GRADLE_USER_HOME diff --git a/push-notifications/android/gradlew b/push-notifications/android/gradlew index f5feea6d6b..23d15a9367 100755 --- a/push-notifications/android/gradlew +++ b/push-notifications/android/gradlew @@ -86,8 +86,7 @@ done # 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 -' "$PWD" ) || exit +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 @@ -115,7 +114,7 @@ case "$( uname )" in #( NONSTOP* ) nonstop=true ;; esac -CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar +CLASSPATH="\\\"\\\"" # Determine the Java command to use to start the JVM. @@ -206,7 +205,7 @@ fi DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' # Collect all arguments for the java command: -# * DEFAULT_JVM_OPTS, JAVA_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, +# * 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. @@ -214,7 +213,7 @@ DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' set -- \ "-Dorg.gradle.appname=$APP_BASE_NAME" \ -classpath "$CLASSPATH" \ - org.gradle.wrapper.GradleWrapperMain \ + -jar "$APP_HOME/gradle/wrapper/gradle-wrapper.jar" \ "$@" # Stop when "xargs" is not available. diff --git a/push-notifications/android/gradlew.bat b/push-notifications/android/gradlew.bat index 9b42019c79..5eed7ee845 100644 --- a/push-notifications/android/gradlew.bat +++ b/push-notifications/android/gradlew.bat @@ -70,11 +70,11 @@ goto fail :execute @rem Setup the command line -set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar +set CLASSPATH= @rem Execute Gradle -"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* +"%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 diff --git a/push-notifications/package.json b/push-notifications/package.json index 09000e3c81..6345c98fae 100644 --- a/push-notifications/package.json +++ b/push-notifications/package.json @@ -1,6 +1,6 @@ { "name": "@capacitor/push-notifications", - "version": "7.0.1", + "version": "8.0.0-alpha.1", "description": "The Push Notifications API provides access to native push notifications.", "main": "dist/plugin.cjs.js", "module": "dist/esm/index.js", @@ -47,11 +47,11 @@ "publish:cocoapod": "pod trunk push ./CapacitorPushNotifications.podspec --allow-warnings" }, "devDependencies": { - "@capacitor/android": "^7.0.0", - "@capacitor/cli": "^6.0.0", - "@capacitor/core": "^7.0.0", + "@capacitor/android": "next", + "@capacitor/cli": "next", + "@capacitor/core": "next", "@capacitor/docgen": "0.2.2", - "@capacitor/ios": "^7.0.0", + "@capacitor/ios": "next", "@ionic/eslint-config": "^0.4.0", "@ionic/prettier-config": "~1.0.1", "@ionic/swiftlint-config": "^1.1.2", @@ -64,7 +64,7 @@ "typescript": "~4.1.5" }, "peerDependencies": { - "@capacitor/core": ">=7.0.0" + "@capacitor/core": "next" }, "prettier": "@ionic/prettier-config", "swiftlint": "@ionic/swiftlint-config", diff --git a/screen-orientation/CHANGELOG.md b/screen-orientation/CHANGELOG.md index e46d2aa8fd..e46e7cd9b2 100644 --- a/screen-orientation/CHANGELOG.md +++ b/screen-orientation/CHANGELOG.md @@ -3,6 +3,14 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +# [8.0.0-alpha.1](https://github.com/ionic-team/capacitor-plugins/compare/@capacitor/screen-orientation@7.0.2...@capacitor/screen-orientation@8.0.0-alpha.1) (2025-09-08) + +**Note:** Version bump only for package @capacitor/screen-orientation + +## [7.0.2](https://github.com/ionic-team/capacitor-plugins/compare/@capacitor/screen-orientation@7.0.1...@capacitor/screen-orientation@7.0.2) (2025-08-05) + +**Note:** Version bump only for package @capacitor/screen-orientation + ## [7.0.1](https://github.com/ionic-team/capacitor-plugins/compare/@capacitor/screen-orientation@7.0.0...@capacitor/screen-orientation@7.0.1) (2025-04-02) **Note:** Version bump only for package @capacitor/screen-orientation diff --git a/screen-orientation/CapacitorScreenOrientation.podspec b/screen-orientation/CapacitorScreenOrientation.podspec index e550e225b2..948b5e40a1 100644 --- a/screen-orientation/CapacitorScreenOrientation.podspec +++ b/screen-orientation/CapacitorScreenOrientation.podspec @@ -11,7 +11,7 @@ Pod::Spec.new do |s| 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}', 'screen-orientation/ios/Sources/**/*.{swift,h,m,c,cc,mm,cpp}' - s.ios.deployment_target = '14.0' + s.ios.deployment_target = '15.0' s.dependency 'Capacitor' s.swift_version = '5.1' end diff --git a/screen-orientation/Package.swift b/screen-orientation/Package.swift index d3e335cf7d..f459a4c068 100644 --- a/screen-orientation/Package.swift +++ b/screen-orientation/Package.swift @@ -3,7 +3,7 @@ import PackageDescription let package = Package( name: "CapacitorScreenOrientation", - platforms: [.iOS(.v14)], + platforms: [.iOS(.v15)], products: [ .library( name: "CapacitorScreenOrientation", diff --git a/screen-orientation/README.md b/screen-orientation/README.md index d909a75a6b..5ffcdef744 100644 --- a/screen-orientation/README.md +++ b/screen-orientation/README.md @@ -69,6 +69,12 @@ lock(options: OrientationLockOptions) => Promise Locks the screen orientation. +Starting in Android targetSdk 36, this method has no effect for large screens (e.g. tablets) on Android 16 an higher. +You may opt-out of this behavior in your app by adding `<property android:name="android.window.PROPERTY_COMPAT_ALLOW_RESTRICTED_RESIZABILITY" android:value="true" />` to your `AndroidManifest.xml` inside `<application>` or `<activity>`. +Keep in mind though that this opt-out is temporary will no longer work for Android 17. Android discourages setting specific orientations for large screens. +Regular Android phones are unaffected by this change. +For more information check the Android docs at https://developer.android.com/about/versions/16/behavior-changes-16#adaptive-layouts + | Param | Type | | ------------- | ------------------------------------------------------------------------- | | **`options`** | OrientationLockOptions | diff --git a/screen-orientation/android/build.gradle b/screen-orientation/android/build.gradle index 5da5c72481..b50f5126f5 100644 --- a/screen-orientation/android/build.gradle +++ b/screen-orientation/android/build.gradle @@ -1,9 +1,9 @@ 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.0' - androidxJunitVersion = project.hasProperty('androidxJunitVersion') ? rootProject.ext.androidxJunitVersion : '1.2.1' - androidxEspressoCoreVersion = project.hasProperty('androidxEspressoCoreVersion') ? rootProject.ext.androidxEspressoCoreVersion : '3.6.1' + androidxAppCompatVersion = project.hasProperty('androidxAppCompatVersion') ? rootProject.ext.androidxAppCompatVersion : '1.7.1' + androidxJunitVersion = project.hasProperty('androidxJunitVersion') ? rootProject.ext.androidxJunitVersion : '1.3.0' + androidxEspressoCoreVersion = project.hasProperty('androidxEspressoCoreVersion') ? rootProject.ext.androidxEspressoCoreVersion : '3.7.0' } buildscript { @@ -11,11 +11,11 @@ buildscript { google() mavenCentral() maven { - url "https://plugins.gradle.org/m2/" + url = "https://plugins.gradle.org/m2/" } } dependencies { - classpath 'com.android.tools.build:gradle:8.7.2' + 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' } @@ -30,11 +30,11 @@ if (System.getenv("CAP_PLUGIN_PUBLISH") == "true") { } android { - namespace "com.capacitorjs.plugins.screenorientation" - compileSdk project.hasProperty('compileSdkVersion') ? rootProject.ext.compileSdkVersion : 35 + namespace = "com.capacitorjs.plugins.screenorientation" + compileSdk = project.hasProperty('compileSdkVersion') ? rootProject.ext.compileSdkVersion : 36 defaultConfig { - minSdkVersion project.hasProperty('minSdkVersion') ? rootProject.ext.minSdkVersion : 23 - targetSdkVersion project.hasProperty('targetSdkVersion') ? rootProject.ext.targetSdkVersion : 35 + 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" @@ -46,7 +46,7 @@ android { } } lintOptions { - abortOnError false + abortOnError = false } compileOptions { sourceCompatibility JavaVersion.VERSION_21 diff --git a/screen-orientation/android/gradle/wrapper/gradle-wrapper.jar b/screen-orientation/android/gradle/wrapper/gradle-wrapper.jar index a4b76b9530..1b33c55baa 100644 Binary files a/screen-orientation/android/gradle/wrapper/gradle-wrapper.jar and b/screen-orientation/android/gradle/wrapper/gradle-wrapper.jar differ diff --git a/screen-orientation/android/gradle/wrapper/gradle-wrapper.properties b/screen-orientation/android/gradle/wrapper/gradle-wrapper.properties index c1d5e01859..7705927e94 100644 --- a/screen-orientation/android/gradle/wrapper/gradle-wrapper.properties +++ b/screen-orientation/android/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-8.11.1-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.14.3-all.zip networkTimeout=10000 validateDistributionUrl=true zipStoreBase=GRADLE_USER_HOME diff --git a/screen-orientation/android/gradlew b/screen-orientation/android/gradlew index f5feea6d6b..23d15a9367 100755 --- a/screen-orientation/android/gradlew +++ b/screen-orientation/android/gradlew @@ -86,8 +86,7 @@ done # 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 -' "$PWD" ) || exit +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 @@ -115,7 +114,7 @@ case "$( uname )" in #( NONSTOP* ) nonstop=true ;; esac -CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar +CLASSPATH="\\\"\\\"" # Determine the Java command to use to start the JVM. @@ -206,7 +205,7 @@ fi DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' # Collect all arguments for the java command: -# * DEFAULT_JVM_OPTS, JAVA_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, +# * 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. @@ -214,7 +213,7 @@ DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' set -- \ "-Dorg.gradle.appname=$APP_BASE_NAME" \ -classpath "$CLASSPATH" \ - org.gradle.wrapper.GradleWrapperMain \ + -jar "$APP_HOME/gradle/wrapper/gradle-wrapper.jar" \ "$@" # Stop when "xargs" is not available. diff --git a/screen-orientation/android/gradlew.bat b/screen-orientation/android/gradlew.bat index 9b42019c79..5eed7ee845 100644 --- a/screen-orientation/android/gradlew.bat +++ b/screen-orientation/android/gradlew.bat @@ -70,11 +70,11 @@ goto fail :execute @rem Setup the command line -set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar +set CLASSPATH= @rem Execute Gradle -"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* +"%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 diff --git a/screen-orientation/package.json b/screen-orientation/package.json index b8655e63be..8dfa44e63b 100644 --- a/screen-orientation/package.json +++ b/screen-orientation/package.json @@ -1,6 +1,6 @@ { "name": "@capacitor/screen-orientation", - "version": "7.0.1", + "version": "8.0.0-alpha.1", "description": "The Screen Orientation API provides methods to lock and unlock the screen orientation.", "main": "dist/plugin.cjs.js", "module": "dist/esm/index.js", @@ -47,10 +47,10 @@ "publish:cocoapod": "pod trunk push ./CapacitorScreenOrientation.podspec --allow-warnings" }, "devDependencies": { - "@capacitor/android": "^7.0.0", - "@capacitor/core": "^7.0.0", + "@capacitor/android": "next", + "@capacitor/core": "next", "@capacitor/docgen": "0.2.2", - "@capacitor/ios": "^7.0.0", + "@capacitor/ios": "next", "@ionic/eslint-config": "^0.4.0", "@ionic/prettier-config": "~1.0.1", "@ionic/swiftlint-config": "^1.1.2", @@ -63,7 +63,7 @@ "typescript": "~4.1.5" }, "peerDependencies": { - "@capacitor/core": ">=7.0.0" + "@capacitor/core": "next" }, "prettier": "@ionic/prettier-config", "swiftlint": "@ionic/swiftlint-config", diff --git a/screen-orientation/src/definitions.ts b/screen-orientation/src/definitions.ts index da041e0406..ce43908a9c 100644 --- a/screen-orientation/src/definitions.ts +++ b/screen-orientation/src/definitions.ts @@ -32,6 +32,12 @@ export interface ScreenOrientationPlugin { /** * Locks the screen orientation. * + * Starting in Android targetSdk 36, this method has no effect for large screens (e.g. tablets) on Android 16 an higher. + * You may opt-out of this behavior in your app by adding `` to your `AndroidManifest.xml` inside `` or ``. + * Keep in mind though that this opt-out is temporary will no longer work for Android 17. Android discourages setting specific orientations for large screens. + * Regular Android phones are unaffected by this change. + * For more information check the Android docs at https://developer.android.com/about/versions/16/behavior-changes-16#adaptive-layouts + * * @since 4.0.0 */ lock(options: OrientationLockOptions): Promise; diff --git a/screen-reader/CHANGELOG.md b/screen-reader/CHANGELOG.md index b553c2d983..2788c129b4 100644 --- a/screen-reader/CHANGELOG.md +++ b/screen-reader/CHANGELOG.md @@ -3,6 +3,14 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +# [8.0.0-alpha.1](https://github.com/ionic-team/capacitor-plugins/compare/@capacitor/screen-reader@7.0.2...@capacitor/screen-reader@8.0.0-alpha.1) (2025-09-08) + +**Note:** Version bump only for package @capacitor/screen-reader + +## [7.0.2](https://github.com/ionic-team/capacitor-plugins/compare/@capacitor/screen-reader@7.0.1...@capacitor/screen-reader@7.0.2) (2025-08-05) + +**Note:** Version bump only for package @capacitor/screen-reader + ## [7.0.1](https://github.com/ionic-team/capacitor-plugins/compare/@capacitor/screen-reader@7.0.0...@capacitor/screen-reader@7.0.1) (2025-04-02) **Note:** Version bump only for package @capacitor/screen-reader diff --git a/screen-reader/CapacitorScreenReader.podspec b/screen-reader/CapacitorScreenReader.podspec index 60881c8d72..bfb675ad8d 100644 --- a/screen-reader/CapacitorScreenReader.podspec +++ b/screen-reader/CapacitorScreenReader.podspec @@ -11,7 +11,7 @@ Pod::Spec.new do |s| 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}', 'screen-reader/ios/Sources/**/*.{swift,h,m,c,cc,mm,cpp}' - s.ios.deployment_target = '14.0' + s.ios.deployment_target = '15.0' s.dependency 'Capacitor' s.swift_version = '5.1' end diff --git a/screen-reader/Package.swift b/screen-reader/Package.swift index 4b5e884cfd..c526936fd4 100644 --- a/screen-reader/Package.swift +++ b/screen-reader/Package.swift @@ -3,7 +3,7 @@ import PackageDescription let package = Package( name: "CapacitorScreenReader", - platforms: [.iOS(.v14)], + platforms: [.iOS(.v15)], products: [ .library( name: "CapacitorScreenReader", diff --git a/screen-reader/android/build.gradle b/screen-reader/android/build.gradle index 96affc51b0..f44a8a54e5 100644 --- a/screen-reader/android/build.gradle +++ b/screen-reader/android/build.gradle @@ -1,9 +1,9 @@ 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.0' - androidxJunitVersion = project.hasProperty('androidxJunitVersion') ? rootProject.ext.androidxJunitVersion : '1.2.1' - androidxEspressoCoreVersion = project.hasProperty('androidxEspressoCoreVersion') ? rootProject.ext.androidxEspressoCoreVersion : '3.6.1' + androidxAppCompatVersion = project.hasProperty('androidxAppCompatVersion') ? rootProject.ext.androidxAppCompatVersion : '1.7.1' + androidxJunitVersion = project.hasProperty('androidxJunitVersion') ? rootProject.ext.androidxJunitVersion : '1.3.0' + androidxEspressoCoreVersion = project.hasProperty('androidxEspressoCoreVersion') ? rootProject.ext.androidxEspressoCoreVersion : '3.7.0' } buildscript { @@ -11,11 +11,11 @@ buildscript { google() mavenCentral() maven { - url "https://plugins.gradle.org/m2/" + url = "https://plugins.gradle.org/m2/" } } dependencies { - classpath 'com.android.tools.build:gradle:8.7.2' + 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' } @@ -30,11 +30,11 @@ if (System.getenv("CAP_PLUGIN_PUBLISH") == "true") { } android { - namespace "com.capacitorjs.plugins.screenreader" - compileSdk project.hasProperty('compileSdkVersion') ? rootProject.ext.compileSdkVersion : 35 + namespace = "com.capacitorjs.plugins.screenreader" + compileSdk = project.hasProperty('compileSdkVersion') ? rootProject.ext.compileSdkVersion : 36 defaultConfig { - minSdkVersion project.hasProperty('minSdkVersion') ? rootProject.ext.minSdkVersion : 23 - targetSdkVersion project.hasProperty('targetSdkVersion') ? rootProject.ext.targetSdkVersion : 35 + 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" @@ -46,7 +46,7 @@ android { } } lintOptions { - abortOnError false + abortOnError = false } compileOptions { sourceCompatibility JavaVersion.VERSION_21 diff --git a/screen-reader/android/gradle/wrapper/gradle-wrapper.jar b/screen-reader/android/gradle/wrapper/gradle-wrapper.jar index a4b76b9530..1b33c55baa 100644 Binary files a/screen-reader/android/gradle/wrapper/gradle-wrapper.jar and b/screen-reader/android/gradle/wrapper/gradle-wrapper.jar differ diff --git a/screen-reader/android/gradle/wrapper/gradle-wrapper.properties b/screen-reader/android/gradle/wrapper/gradle-wrapper.properties index c1d5e01859..7705927e94 100644 --- a/screen-reader/android/gradle/wrapper/gradle-wrapper.properties +++ b/screen-reader/android/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-8.11.1-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.14.3-all.zip networkTimeout=10000 validateDistributionUrl=true zipStoreBase=GRADLE_USER_HOME diff --git a/screen-reader/android/gradlew b/screen-reader/android/gradlew index f5feea6d6b..23d15a9367 100755 --- a/screen-reader/android/gradlew +++ b/screen-reader/android/gradlew @@ -86,8 +86,7 @@ done # 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 -' "$PWD" ) || exit +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 @@ -115,7 +114,7 @@ case "$( uname )" in #( NONSTOP* ) nonstop=true ;; esac -CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar +CLASSPATH="\\\"\\\"" # Determine the Java command to use to start the JVM. @@ -206,7 +205,7 @@ fi DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' # Collect all arguments for the java command: -# * DEFAULT_JVM_OPTS, JAVA_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, +# * 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. @@ -214,7 +213,7 @@ DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' set -- \ "-Dorg.gradle.appname=$APP_BASE_NAME" \ -classpath "$CLASSPATH" \ - org.gradle.wrapper.GradleWrapperMain \ + -jar "$APP_HOME/gradle/wrapper/gradle-wrapper.jar" \ "$@" # Stop when "xargs" is not available. diff --git a/screen-reader/android/gradlew.bat b/screen-reader/android/gradlew.bat index 9b42019c79..5eed7ee845 100644 --- a/screen-reader/android/gradlew.bat +++ b/screen-reader/android/gradlew.bat @@ -70,11 +70,11 @@ goto fail :execute @rem Setup the command line -set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar +set CLASSPATH= @rem Execute Gradle -"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* +"%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 diff --git a/screen-reader/package.json b/screen-reader/package.json index 1a4afb8db8..df4975ba71 100644 --- a/screen-reader/package.json +++ b/screen-reader/package.json @@ -1,6 +1,6 @@ { "name": "@capacitor/screen-reader", - "version": "7.0.1", + "version": "8.0.0-alpha.1", "description": "The Screen Reader API provides access to TalkBack/VoiceOver/etc. and provides simple text-to-speech capabilities for visual accessibility.", "main": "dist/plugin.cjs.js", "module": "dist/esm/index.js", @@ -47,10 +47,10 @@ "publish:cocoapod": "pod trunk push ./CapacitorScreenReader.podspec --allow-warnings" }, "devDependencies": { - "@capacitor/android": "^7.0.0", - "@capacitor/core": "^7.0.0", + "@capacitor/android": "next", + "@capacitor/core": "next", "@capacitor/docgen": "0.2.2", - "@capacitor/ios": "^7.0.0", + "@capacitor/ios": "next", "@ionic/eslint-config": "^0.4.0", "@ionic/prettier-config": "~1.0.1", "@ionic/swiftlint-config": "^1.1.2", @@ -63,7 +63,7 @@ "typescript": "~4.1.5" }, "peerDependencies": { - "@capacitor/core": ">=7.0.0" + "@capacitor/core": "next" }, "prettier": "@ionic/prettier-config", "swiftlint": "@ionic/swiftlint-config", diff --git a/share/CHANGELOG.md b/share/CHANGELOG.md index a0df24a72d..051f738278 100644 --- a/share/CHANGELOG.md +++ b/share/CHANGELOG.md @@ -3,6 +3,14 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +# [8.0.0-alpha.1](https://github.com/ionic-team/capacitor-plugins/compare/@capacitor/share@7.0.2...@capacitor/share@8.0.0-alpha.1) (2025-09-08) + +**Note:** Version bump only for package @capacitor/share + +## [7.0.2](https://github.com/ionic-team/capacitor-plugins/compare/@capacitor/share@7.0.1...@capacitor/share@7.0.2) (2025-08-05) + +**Note:** Version bump only for package @capacitor/share + ## [7.0.1](https://github.com/ionic-team/capacitor-plugins/compare/@capacitor/share@7.0.0...@capacitor/share@7.0.1) (2025-04-02) **Note:** Version bump only for package @capacitor/share diff --git a/share/CapacitorShare.podspec b/share/CapacitorShare.podspec index 1d8efeb0b0..3165a1e441 100644 --- a/share/CapacitorShare.podspec +++ b/share/CapacitorShare.podspec @@ -11,7 +11,7 @@ Pod::Spec.new do |s| 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}', 'share/ios/Sources/**/*.{swift,h,m,c,cc,mm,cpp}' - s.ios.deployment_target = '14.0' + s.ios.deployment_target = '15.0' s.dependency 'Capacitor' s.swift_version = '5.1' end diff --git a/share/Package.swift b/share/Package.swift index 9c04ca9736..6586f6b2d4 100644 --- a/share/Package.swift +++ b/share/Package.swift @@ -3,7 +3,7 @@ import PackageDescription let package = Package( name: "CapacitorShare", - platforms: [.iOS(.v14)], + platforms: [.iOS(.v15)], products: [ .library( name: "CapacitorShare", diff --git a/share/android/build.gradle b/share/android/build.gradle index e62e3875a4..16a3959286 100644 --- a/share/android/build.gradle +++ b/share/android/build.gradle @@ -1,10 +1,10 @@ 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.0' - androidxCoreVersion = project.hasProperty('androidxCoreVersion') ? rootProject.ext.androidxCoreVersion : '1.15.0' - androidxJunitVersion = project.hasProperty('androidxJunitVersion') ? rootProject.ext.androidxJunitVersion : '1.2.1' - androidxEspressoCoreVersion = project.hasProperty('androidxEspressoCoreVersion') ? rootProject.ext.androidxEspressoCoreVersion : '3.6.1' + androidxAppCompatVersion = project.hasProperty('androidxAppCompatVersion') ? rootProject.ext.androidxAppCompatVersion : '1.7.1' + androidxCoreVersion = project.hasProperty('androidxCoreVersion') ? rootProject.ext.androidxCoreVersion : '1.17.0' + androidxJunitVersion = project.hasProperty('androidxJunitVersion') ? rootProject.ext.androidxJunitVersion : '1.3.0' + androidxEspressoCoreVersion = project.hasProperty('androidxEspressoCoreVersion') ? rootProject.ext.androidxEspressoCoreVersion : '3.7.0' } buildscript { @@ -12,11 +12,11 @@ buildscript { google() mavenCentral() maven { - url "https://plugins.gradle.org/m2/" + url = "https://plugins.gradle.org/m2/" } } dependencies { - classpath 'com.android.tools.build:gradle:8.7.2' + 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' } @@ -31,11 +31,11 @@ if (System.getenv("CAP_PLUGIN_PUBLISH") == "true") { } android { - namespace "com.capacitorjs.plugins.share" - compileSdk project.hasProperty('compileSdkVersion') ? rootProject.ext.compileSdkVersion : 35 + namespace = "com.capacitorjs.plugins.share" + compileSdk = project.hasProperty('compileSdkVersion') ? rootProject.ext.compileSdkVersion : 36 defaultConfig { - minSdkVersion project.hasProperty('minSdkVersion') ? rootProject.ext.minSdkVersion : 23 - targetSdkVersion project.hasProperty('targetSdkVersion') ? rootProject.ext.targetSdkVersion : 35 + 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" @@ -47,7 +47,7 @@ android { } } lintOptions { - abortOnError false + abortOnError = false } compileOptions { sourceCompatibility JavaVersion.VERSION_21 diff --git a/share/android/gradle/wrapper/gradle-wrapper.jar b/share/android/gradle/wrapper/gradle-wrapper.jar index a4b76b9530..1b33c55baa 100644 Binary files a/share/android/gradle/wrapper/gradle-wrapper.jar and b/share/android/gradle/wrapper/gradle-wrapper.jar differ diff --git a/share/android/gradle/wrapper/gradle-wrapper.properties b/share/android/gradle/wrapper/gradle-wrapper.properties index c1d5e01859..7705927e94 100644 --- a/share/android/gradle/wrapper/gradle-wrapper.properties +++ b/share/android/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-8.11.1-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.14.3-all.zip networkTimeout=10000 validateDistributionUrl=true zipStoreBase=GRADLE_USER_HOME diff --git a/share/android/gradlew b/share/android/gradlew index f5feea6d6b..23d15a9367 100755 --- a/share/android/gradlew +++ b/share/android/gradlew @@ -86,8 +86,7 @@ done # 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 -' "$PWD" ) || exit +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 @@ -115,7 +114,7 @@ case "$( uname )" in #( NONSTOP* ) nonstop=true ;; esac -CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar +CLASSPATH="\\\"\\\"" # Determine the Java command to use to start the JVM. @@ -206,7 +205,7 @@ fi DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' # Collect all arguments for the java command: -# * DEFAULT_JVM_OPTS, JAVA_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, +# * 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. @@ -214,7 +213,7 @@ DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' set -- \ "-Dorg.gradle.appname=$APP_BASE_NAME" \ -classpath "$CLASSPATH" \ - org.gradle.wrapper.GradleWrapperMain \ + -jar "$APP_HOME/gradle/wrapper/gradle-wrapper.jar" \ "$@" # Stop when "xargs" is not available. diff --git a/share/android/gradlew.bat b/share/android/gradlew.bat index 9b42019c79..5eed7ee845 100644 --- a/share/android/gradlew.bat +++ b/share/android/gradlew.bat @@ -70,11 +70,11 @@ goto fail :execute @rem Setup the command line -set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar +set CLASSPATH= @rem Execute Gradle -"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* +"%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 diff --git a/share/package.json b/share/package.json index d4dbfa4db4..5d69d94f4f 100644 --- a/share/package.json +++ b/share/package.json @@ -1,6 +1,6 @@ { "name": "@capacitor/share", - "version": "7.0.1", + "version": "8.0.0-alpha.1", "description": "The Share API provides methods for sharing content in any sharing-enabled apps the user may have installed.", "main": "dist/plugin.cjs.js", "module": "dist/esm/index.js", @@ -47,10 +47,10 @@ "publish:cocoapod": "pod trunk push ./CapacitorShare.podspec --allow-warnings" }, "devDependencies": { - "@capacitor/android": "^7.0.0", - "@capacitor/core": "^7.0.0", + "@capacitor/android": "next", + "@capacitor/core": "next", "@capacitor/docgen": "0.2.2", - "@capacitor/ios": "^7.0.0", + "@capacitor/ios": "next", "@ionic/eslint-config": "^0.4.0", "@ionic/prettier-config": "~1.0.1", "@ionic/swiftlint-config": "^1.1.2", @@ -63,7 +63,7 @@ "typescript": "~4.1.5" }, "peerDependencies": { - "@capacitor/core": ">=7.0.0" + "@capacitor/core": "next" }, "prettier": "@ionic/prettier-config", "swiftlint": "@ionic/swiftlint-config", diff --git a/splash-screen/CHANGELOG.md b/splash-screen/CHANGELOG.md index abe70cde57..2ee00ac83b 100644 --- a/splash-screen/CHANGELOG.md +++ b/splash-screen/CHANGELOG.md @@ -3,6 +3,20 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +# [8.0.0-alpha.1](https://github.com/ionic-team/capacitor-plugins/compare/@capacitor/splash-screen@7.0.3...@capacitor/splash-screen@8.0.0-alpha.1) (2025-09-08) + +**Note:** Version bump only for package @capacitor/splash-screen + +## [7.0.3](https://github.com/ionic-team/capacitor-plugins/compare/@capacitor/splash-screen@7.0.2...@capacitor/splash-screen@7.0.3) (2025-09-05) + +**Note:** Version bump only for package @capacitor/splash-screen + +## [7.0.2](https://github.com/ionic-team/capacitor-plugins/compare/@capacitor/splash-screen@7.0.1...@capacitor/splash-screen@7.0.2) (2025-08-05) + +### Bug Fixes + +- **splash-screen:** call clearOnExitAnimationListener on animation end ([#2379](https://github.com/ionic-team/capacitor-plugins/issues/2379)) ([dcfda05](https://github.com/ionic-team/capacitor-plugins/commit/dcfda0542b5efb420ad3fe9af7b6ad54e37fb15a)) + ## [7.0.1](https://github.com/ionic-team/capacitor-plugins/compare/@capacitor/splash-screen@7.0.0...@capacitor/splash-screen@7.0.1) (2025-04-02) **Note:** Version bump only for package @capacitor/splash-screen diff --git a/splash-screen/CapacitorSplashScreen.podspec b/splash-screen/CapacitorSplashScreen.podspec index 2982e193b4..efbd421f9a 100644 --- a/splash-screen/CapacitorSplashScreen.podspec +++ b/splash-screen/CapacitorSplashScreen.podspec @@ -11,7 +11,7 @@ Pod::Spec.new do |s| 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}', 'splash-screen/ios/Sources/**/*.{swift,h,m,c,cc,mm,cpp}' - s.ios.deployment_target = '14.0' + s.ios.deployment_target = '15.0' s.dependency 'Capacitor' s.swift_version = '5.1' end diff --git a/splash-screen/Package.swift b/splash-screen/Package.swift index e35e8e55d8..e442a37e99 100644 --- a/splash-screen/Package.swift +++ b/splash-screen/Package.swift @@ -3,7 +3,7 @@ import PackageDescription let package = Package( name: "CapacitorSplashScreen", - platforms: [.iOS(.v14)], + platforms: [.iOS(.v15)], products: [ .library( name: "CapacitorSplashScreen", diff --git a/splash-screen/README.md b/splash-screen/README.md index ebad66dca2..dbe9df2289 100644 --- a/splash-screen/README.md +++ b/splash-screen/README.md @@ -185,7 +185,7 @@ To use splash screen images named something other than `splash.png`, set `androi This plugin will use the following project variables (defined in your app's `variables.gradle` file): -- `coreSplashScreenVersion` version of `androidx.core:core-splashscreen` (default: `1.0.1`) +- `coreSplashScreenVersion` version of `androidx.core:core-splashscreen` (default: `1.2.0`) ## Example Guides diff --git a/splash-screen/android/build.gradle b/splash-screen/android/build.gradle index d1cc5e393d..7649c8d376 100644 --- a/splash-screen/android/build.gradle +++ b/splash-screen/android/build.gradle @@ -1,10 +1,10 @@ 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.0' - androidxJunitVersion = project.hasProperty('androidxJunitVersion') ? rootProject.ext.androidxJunitVersion : '1.2.1' - androidxEspressoCoreVersion = project.hasProperty('androidxEspressoCoreVersion') ? rootProject.ext.androidxEspressoCoreVersion : '3.6.1' - coreSplashScreenVersion = project.hasProperty('coreSplashScreenVersion') ? rootProject.ext.coreSplashScreenVersion : '1.0.1' + androidxAppCompatVersion = project.hasProperty('androidxAppCompatVersion') ? rootProject.ext.androidxAppCompatVersion : '1.7.1' + androidxJunitVersion = project.hasProperty('androidxJunitVersion') ? rootProject.ext.androidxJunitVersion : '1.3.0' + androidxEspressoCoreVersion = project.hasProperty('androidxEspressoCoreVersion') ? rootProject.ext.androidxEspressoCoreVersion : '3.7.0' + coreSplashScreenVersion = project.hasProperty('coreSplashScreenVersion') ? rootProject.ext.coreSplashScreenVersion : '1.2.0' } buildscript { @@ -12,11 +12,11 @@ buildscript { google() mavenCentral() maven { - url "https://plugins.gradle.org/m2/" + url = "https://plugins.gradle.org/m2/" } } dependencies { - classpath 'com.android.tools.build:gradle:8.7.2' + 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' } @@ -31,11 +31,11 @@ if (System.getenv("CAP_PLUGIN_PUBLISH") == "true") { } android { - namespace "com.capacitorjs.plugins.splashscreen" - compileSdk project.hasProperty('compileSdkVersion') ? rootProject.ext.compileSdkVersion : 35 + namespace = "com.capacitorjs.plugins.splashscreen" + compileSdk = project.hasProperty('compileSdkVersion') ? rootProject.ext.compileSdkVersion : 36 defaultConfig { - minSdkVersion project.hasProperty('minSdkVersion') ? rootProject.ext.minSdkVersion : 23 - targetSdkVersion project.hasProperty('targetSdkVersion') ? rootProject.ext.targetSdkVersion : 35 + 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" @@ -47,7 +47,7 @@ android { } } lintOptions { - abortOnError false + abortOnError = false } compileOptions { sourceCompatibility JavaVersion.VERSION_21 diff --git a/splash-screen/android/gradle/wrapper/gradle-wrapper.jar b/splash-screen/android/gradle/wrapper/gradle-wrapper.jar index a4b76b9530..1b33c55baa 100644 Binary files a/splash-screen/android/gradle/wrapper/gradle-wrapper.jar and b/splash-screen/android/gradle/wrapper/gradle-wrapper.jar differ diff --git a/splash-screen/android/gradle/wrapper/gradle-wrapper.properties b/splash-screen/android/gradle/wrapper/gradle-wrapper.properties index c1d5e01859..7705927e94 100644 --- a/splash-screen/android/gradle/wrapper/gradle-wrapper.properties +++ b/splash-screen/android/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-8.11.1-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.14.3-all.zip networkTimeout=10000 validateDistributionUrl=true zipStoreBase=GRADLE_USER_HOME diff --git a/splash-screen/android/gradlew b/splash-screen/android/gradlew index f5feea6d6b..23d15a9367 100755 --- a/splash-screen/android/gradlew +++ b/splash-screen/android/gradlew @@ -86,8 +86,7 @@ done # 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 -' "$PWD" ) || exit +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 @@ -115,7 +114,7 @@ case "$( uname )" in #( NONSTOP* ) nonstop=true ;; esac -CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar +CLASSPATH="\\\"\\\"" # Determine the Java command to use to start the JVM. @@ -206,7 +205,7 @@ fi DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' # Collect all arguments for the java command: -# * DEFAULT_JVM_OPTS, JAVA_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, +# * 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. @@ -214,7 +213,7 @@ DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' set -- \ "-Dorg.gradle.appname=$APP_BASE_NAME" \ -classpath "$CLASSPATH" \ - org.gradle.wrapper.GradleWrapperMain \ + -jar "$APP_HOME/gradle/wrapper/gradle-wrapper.jar" \ "$@" # Stop when "xargs" is not available. diff --git a/splash-screen/android/gradlew.bat b/splash-screen/android/gradlew.bat index 9b42019c79..5eed7ee845 100644 --- a/splash-screen/android/gradlew.bat +++ b/splash-screen/android/gradlew.bat @@ -70,11 +70,11 @@ goto fail :execute @rem Setup the command line -set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar +set CLASSPATH= @rem Execute Gradle -"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* +"%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 diff --git a/splash-screen/package.json b/splash-screen/package.json index fba4d56548..d870d561f5 100644 --- a/splash-screen/package.json +++ b/splash-screen/package.json @@ -1,6 +1,6 @@ { "name": "@capacitor/splash-screen", - "version": "7.0.1", + "version": "8.0.0-alpha.1", "description": "The Splash Screen API provides methods for showing or hiding a Splash image.", "main": "dist/plugin.cjs.js", "module": "dist/esm/index.js", @@ -47,11 +47,11 @@ "publish:cocoapod": "pod trunk push ./CapacitorSplashScreen.podspec --allow-warnings" }, "devDependencies": { - "@capacitor/android": "^7.0.0", - "@capacitor/cli": "^6.0.0", - "@capacitor/core": "^7.0.0", + "@capacitor/android": "next", + "@capacitor/cli": "next", + "@capacitor/core": "next", "@capacitor/docgen": "0.2.2", - "@capacitor/ios": "^7.0.0", + "@capacitor/ios": "next", "@ionic/eslint-config": "^0.4.0", "@ionic/prettier-config": "~1.0.1", "@ionic/swiftlint-config": "^1.1.2", @@ -64,7 +64,7 @@ "typescript": "~4.1.5" }, "peerDependencies": { - "@capacitor/core": ">=7.0.0" + "@capacitor/core": "next" }, "prettier": "@ionic/prettier-config", "swiftlint": "@ionic/swiftlint-config", diff --git a/status-bar/CHANGELOG.md b/status-bar/CHANGELOG.md index e34cd97051..416f84d0cc 100644 --- a/status-bar/CHANGELOG.md +++ b/status-bar/CHANGELOG.md @@ -3,6 +3,20 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +# [8.0.0-alpha.1](https://github.com/ionic-team/capacitor-plugins/compare/@capacitor/status-bar@7.0.3...@capacitor/status-bar@8.0.0-alpha.1) (2025-09-08) + +**Note:** Version bump only for package @capacitor/status-bar + +## [7.0.3](https://github.com/ionic-team/capacitor-plugins/compare/@capacitor/status-bar@7.0.2...@capacitor/status-bar@7.0.3) (2025-09-05) + +### Bug Fixes + +- **statusbar:** using window size instead of screen size for resize bounds ([#2394](https://github.com/ionic-team/capacitor-plugins/issues/2394)) ([fb13a38](https://github.com/ionic-team/capacitor-plugins/commit/fb13a385a9e2c414420b398c2227483b2f4d1740)) + +## [7.0.2](https://github.com/ionic-team/capacitor-plugins/compare/@capacitor/status-bar@7.0.1...@capacitor/status-bar@7.0.2) (2025-08-05) + +**Note:** Version bump only for package @capacitor/status-bar + ## [7.0.1](https://github.com/ionic-team/capacitor-plugins/compare/@capacitor/status-bar@7.0.0...@capacitor/status-bar@7.0.1) (2025-04-02) **Note:** Version bump only for package @capacitor/status-bar diff --git a/status-bar/CapacitorStatusBar.podspec b/status-bar/CapacitorStatusBar.podspec index 7db148e5f8..f24e3965ca 100644 --- a/status-bar/CapacitorStatusBar.podspec +++ b/status-bar/CapacitorStatusBar.podspec @@ -11,7 +11,7 @@ Pod::Spec.new do |s| 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}', 'status-bar/ios/Sources/**/*.{swift,h,m,c,cc,mm,cpp}' - s.ios.deployment_target = '14.0' + s.ios.deployment_target = '15.0' s.dependency 'Capacitor' s.swift_version = '5.1' end diff --git a/status-bar/Package.swift b/status-bar/Package.swift index abbdabfd25..5eba38fe61 100644 --- a/status-bar/Package.swift +++ b/status-bar/Package.swift @@ -4,7 +4,7 @@ import PackageDescription let package = Package( name: "CapacitorStatusBar", - platforms: [.iOS(.v14)], + platforms: [.iOS(.v15)], products: [ .library( name: "CapacitorStatusBar", diff --git a/status-bar/android/build.gradle b/status-bar/android/build.gradle index 9badef6c40..8324fe467e 100644 --- a/status-bar/android/build.gradle +++ b/status-bar/android/build.gradle @@ -1,10 +1,10 @@ ext { capacitorVersion = System.getenv('CAPACITOR_VERSION') junitVersion = project.hasProperty('junitVersion') ? rootProject.ext.junitVersion : '4.13.2' - androidxCoreVersion = project.hasProperty('androidxCoreVersion') ? rootProject.ext.androidxCoreVersion : '1.15.0' - androidxAppCompatVersion = project.hasProperty('androidxAppCompatVersion') ? rootProject.ext.androidxAppCompatVersion : '1.7.0' - androidxJunitVersion = project.hasProperty('androidxJunitVersion') ? rootProject.ext.androidxJunitVersion : '1.2.1' - androidxEspressoCoreVersion = project.hasProperty('androidxEspressoCoreVersion') ? rootProject.ext.androidxEspressoCoreVersion : '3.6.1' + androidxCoreVersion = project.hasProperty('androidxCoreVersion') ? rootProject.ext.androidxCoreVersion : '1.17.0' + androidxAppCompatVersion = project.hasProperty('androidxAppCompatVersion') ? rootProject.ext.androidxAppCompatVersion : '1.7.1' + androidxJunitVersion = project.hasProperty('androidxJunitVersion') ? rootProject.ext.androidxJunitVersion : '1.3.0' + androidxEspressoCoreVersion = project.hasProperty('androidxEspressoCoreVersion') ? rootProject.ext.androidxEspressoCoreVersion : '3.7.0' } buildscript { @@ -12,11 +12,11 @@ buildscript { google() mavenCentral() maven { - url "https://plugins.gradle.org/m2/" + url = "https://plugins.gradle.org/m2/" } } dependencies { - classpath 'com.android.tools.build:gradle:8.7.2' + 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' } @@ -31,11 +31,11 @@ if (System.getenv("CAP_PLUGIN_PUBLISH") == "true") { } android { - namespace "com.capacitorjs.plugins.statusbar" - compileSdk project.hasProperty('compileSdkVersion') ? rootProject.ext.compileSdkVersion : 35 + namespace = "com.capacitorjs.plugins.statusbar" + compileSdk = project.hasProperty('compileSdkVersion') ? rootProject.ext.compileSdkVersion : 36 defaultConfig { - minSdkVersion project.hasProperty('minSdkVersion') ? rootProject.ext.minSdkVersion : 23 - targetSdkVersion project.hasProperty('targetSdkVersion') ? rootProject.ext.targetSdkVersion : 35 + 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" @@ -47,7 +47,7 @@ android { } } lintOptions { - abortOnError false + abortOnError = false } compileOptions { sourceCompatibility JavaVersion.VERSION_21 diff --git a/status-bar/android/gradle/wrapper/gradle-wrapper.jar b/status-bar/android/gradle/wrapper/gradle-wrapper.jar index a4b76b9530..1b33c55baa 100644 Binary files a/status-bar/android/gradle/wrapper/gradle-wrapper.jar and b/status-bar/android/gradle/wrapper/gradle-wrapper.jar differ diff --git a/status-bar/android/gradle/wrapper/gradle-wrapper.properties b/status-bar/android/gradle/wrapper/gradle-wrapper.properties index c1d5e01859..7705927e94 100644 --- a/status-bar/android/gradle/wrapper/gradle-wrapper.properties +++ b/status-bar/android/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-8.11.1-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.14.3-all.zip networkTimeout=10000 validateDistributionUrl=true zipStoreBase=GRADLE_USER_HOME diff --git a/status-bar/android/gradlew b/status-bar/android/gradlew index f5feea6d6b..23d15a9367 100755 --- a/status-bar/android/gradlew +++ b/status-bar/android/gradlew @@ -86,8 +86,7 @@ done # 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 -' "$PWD" ) || exit +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 @@ -115,7 +114,7 @@ case "$( uname )" in #( NONSTOP* ) nonstop=true ;; esac -CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar +CLASSPATH="\\\"\\\"" # Determine the Java command to use to start the JVM. @@ -206,7 +205,7 @@ fi DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' # Collect all arguments for the java command: -# * DEFAULT_JVM_OPTS, JAVA_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, +# * 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. @@ -214,7 +213,7 @@ DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' set -- \ "-Dorg.gradle.appname=$APP_BASE_NAME" \ -classpath "$CLASSPATH" \ - org.gradle.wrapper.GradleWrapperMain \ + -jar "$APP_HOME/gradle/wrapper/gradle-wrapper.jar" \ "$@" # Stop when "xargs" is not available. diff --git a/status-bar/android/gradlew.bat b/status-bar/android/gradlew.bat index 9b42019c79..5eed7ee845 100644 --- a/status-bar/android/gradlew.bat +++ b/status-bar/android/gradlew.bat @@ -70,11 +70,11 @@ goto fail :execute @rem Setup the command line -set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar +set CLASSPATH= @rem Execute Gradle -"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* +"%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 diff --git a/status-bar/ios/Sources/StatusBarPlugin/StatusBar.swift b/status-bar/ios/Sources/StatusBarPlugin/StatusBar.swift index 0f203f3ad6..2f08348620 100644 --- a/status-bar/ios/Sources/StatusBarPlugin/StatusBar.swift +++ b/status-bar/ios/Sources/StatusBarPlugin/StatusBar.swift @@ -124,9 +124,11 @@ public class StatusBar { } private func resizeWebView() { + let bounds: CGRect? = bridge.viewController?.view.window?.windowScene?.keyWindow?.bounds + guard let webView = bridge.webView, - let bounds = bridge.viewController?.view.window?.windowScene?.screen.bounds + let bounds = bounds else { return } bridge.viewController?.view.frame = bounds webView.frame = bounds diff --git a/status-bar/package.json b/status-bar/package.json index fc3492bb0b..afaaea5486 100644 --- a/status-bar/package.json +++ b/status-bar/package.json @@ -1,6 +1,6 @@ { "name": "@capacitor/status-bar", - "version": "7.0.1", + "version": "8.0.0-alpha.1", "description": "The StatusBar API Provides methods for configuring the style of the Status Bar, along with showing or hiding it.", "main": "dist/plugin.cjs.js", "module": "dist/esm/index.js", @@ -47,10 +47,11 @@ "publish:cocoapod": "pod trunk push ./CapacitorStatusBar.podspec --allow-warnings" }, "devDependencies": { - "@capacitor/android": "^7.0.0", - "@capacitor/core": "^7.0.0", + "@capacitor/android": "next", + "@capacitor/cli": "next", + "@capacitor/core": "next", "@capacitor/docgen": "0.2.2", - "@capacitor/ios": "^7.0.0", + "@capacitor/ios": "next", "@ionic/eslint-config": "^0.4.0", "@ionic/prettier-config": "~1.0.1", "@ionic/swiftlint-config": "^1.1.2", @@ -63,7 +64,7 @@ "typescript": "~4.1.5" }, "peerDependencies": { - "@capacitor/core": ">=7.0.0" + "@capacitor/core": "next" }, "prettier": "@ionic/prettier-config", "swiftlint": "@ionic/swiftlint-config", diff --git a/text-zoom/CHANGELOG.md b/text-zoom/CHANGELOG.md index 4066d75c31..5f118df6a1 100644 --- a/text-zoom/CHANGELOG.md +++ b/text-zoom/CHANGELOG.md @@ -3,6 +3,14 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +# [8.0.0-alpha.1](https://github.com/ionic-team/capacitor-plugins/compare/@capacitor/text-zoom@7.0.2...@capacitor/text-zoom@8.0.0-alpha.1) (2025-09-08) + +**Note:** Version bump only for package @capacitor/text-zoom + +## [7.0.2](https://github.com/ionic-team/capacitor-plugins/compare/@capacitor/text-zoom@7.0.1...@capacitor/text-zoom@7.0.2) (2025-08-05) + +**Note:** Version bump only for package @capacitor/text-zoom + ## [7.0.1](https://github.com/ionic-team/capacitor-plugins/compare/@capacitor/text-zoom@7.0.0...@capacitor/text-zoom@7.0.1) (2025-04-02) **Note:** Version bump only for package @capacitor/text-zoom diff --git a/text-zoom/CapacitorTextZoom.podspec b/text-zoom/CapacitorTextZoom.podspec index 11f37c2688..d6b756671c 100644 --- a/text-zoom/CapacitorTextZoom.podspec +++ b/text-zoom/CapacitorTextZoom.podspec @@ -11,7 +11,7 @@ Pod::Spec.new do |s| 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}', 'text-zoom/ios/Sources/**/*.{swift,h,m,c,cc,mm,cpp}' - s.ios.deployment_target = '14.0' + s.ios.deployment_target = '15.0' s.dependency 'Capacitor' s.swift_version = '5.1' end diff --git a/text-zoom/Package.swift b/text-zoom/Package.swift index ff486b7384..06cd76072e 100644 --- a/text-zoom/Package.swift +++ b/text-zoom/Package.swift @@ -3,7 +3,7 @@ import PackageDescription let package = Package( name: "CapacitorTextZoom", - platforms: [.iOS(.v14)], + platforms: [.iOS(.v15)], products: [ .library( name: "CapacitorTextZoom", diff --git a/text-zoom/android/build.gradle b/text-zoom/android/build.gradle index 98b05bc9c7..800a683c48 100644 --- a/text-zoom/android/build.gradle +++ b/text-zoom/android/build.gradle @@ -1,9 +1,9 @@ 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.0' - androidxJunitVersion = project.hasProperty('androidxJunitVersion') ? rootProject.ext.androidxJunitVersion : '1.2.1' - androidxEspressoCoreVersion = project.hasProperty('androidxEspressoCoreVersion') ? rootProject.ext.androidxEspressoCoreVersion : '3.6.1' + androidxAppCompatVersion = project.hasProperty('androidxAppCompatVersion') ? rootProject.ext.androidxAppCompatVersion : '1.7.1' + androidxJunitVersion = project.hasProperty('androidxJunitVersion') ? rootProject.ext.androidxJunitVersion : '1.3.0' + androidxEspressoCoreVersion = project.hasProperty('androidxEspressoCoreVersion') ? rootProject.ext.androidxEspressoCoreVersion : '3.7.0' } buildscript { @@ -11,11 +11,11 @@ buildscript { google() mavenCentral() maven { - url "https://plugins.gradle.org/m2/" + url = "https://plugins.gradle.org/m2/" } } dependencies { - classpath 'com.android.tools.build:gradle:8.7.2' + 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' } @@ -30,11 +30,11 @@ if (System.getenv("CAP_PLUGIN_PUBLISH") == "true") { } android { - namespace "com.capacitorjs.plugins.textzoom" - compileSdk project.hasProperty('compileSdkVersion') ? rootProject.ext.compileSdkVersion : 35 + namespace = "com.capacitorjs.plugins.textzoom" + compileSdk = project.hasProperty('compileSdkVersion') ? rootProject.ext.compileSdkVersion : 36 defaultConfig { - minSdkVersion project.hasProperty('minSdkVersion') ? rootProject.ext.minSdkVersion : 23 - targetSdkVersion project.hasProperty('targetSdkVersion') ? rootProject.ext.targetSdkVersion : 35 + 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" @@ -46,7 +46,7 @@ android { } } lintOptions { - abortOnError false + abortOnError = false } compileOptions { sourceCompatibility JavaVersion.VERSION_21 diff --git a/text-zoom/android/gradle/wrapper/gradle-wrapper.jar b/text-zoom/android/gradle/wrapper/gradle-wrapper.jar index a4b76b9530..1b33c55baa 100644 Binary files a/text-zoom/android/gradle/wrapper/gradle-wrapper.jar and b/text-zoom/android/gradle/wrapper/gradle-wrapper.jar differ diff --git a/text-zoom/android/gradle/wrapper/gradle-wrapper.properties b/text-zoom/android/gradle/wrapper/gradle-wrapper.properties index c1d5e01859..7705927e94 100644 --- a/text-zoom/android/gradle/wrapper/gradle-wrapper.properties +++ b/text-zoom/android/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-8.11.1-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.14.3-all.zip networkTimeout=10000 validateDistributionUrl=true zipStoreBase=GRADLE_USER_HOME diff --git a/text-zoom/android/gradlew b/text-zoom/android/gradlew index f5feea6d6b..23d15a9367 100755 --- a/text-zoom/android/gradlew +++ b/text-zoom/android/gradlew @@ -86,8 +86,7 @@ done # 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 -' "$PWD" ) || exit +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 @@ -115,7 +114,7 @@ case "$( uname )" in #( NONSTOP* ) nonstop=true ;; esac -CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar +CLASSPATH="\\\"\\\"" # Determine the Java command to use to start the JVM. @@ -206,7 +205,7 @@ fi DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' # Collect all arguments for the java command: -# * DEFAULT_JVM_OPTS, JAVA_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, +# * 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. @@ -214,7 +213,7 @@ DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' set -- \ "-Dorg.gradle.appname=$APP_BASE_NAME" \ -classpath "$CLASSPATH" \ - org.gradle.wrapper.GradleWrapperMain \ + -jar "$APP_HOME/gradle/wrapper/gradle-wrapper.jar" \ "$@" # Stop when "xargs" is not available. diff --git a/text-zoom/android/gradlew.bat b/text-zoom/android/gradlew.bat index 9b42019c79..5eed7ee845 100644 --- a/text-zoom/android/gradlew.bat +++ b/text-zoom/android/gradlew.bat @@ -70,11 +70,11 @@ goto fail :execute @rem Setup the command line -set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar +set CLASSPATH= @rem Execute Gradle -"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* +"%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 diff --git a/text-zoom/package.json b/text-zoom/package.json index 111e549b41..adc0f31e0c 100644 --- a/text-zoom/package.json +++ b/text-zoom/package.json @@ -1,6 +1,6 @@ { "name": "@capacitor/text-zoom", - "version": "7.0.1", + "version": "8.0.0-alpha.1", "description": "The Text Zoom API provides the ability to change Web View text size for visual accessibility.", "main": "dist/plugin.cjs.js", "module": "dist/esm/index.js", @@ -47,10 +47,10 @@ "publish:cocoapod": "pod trunk push ./CapacitorTextZoom.podspec --allow-warnings" }, "devDependencies": { - "@capacitor/android": "^7.0.0", - "@capacitor/core": "^7.0.0", + "@capacitor/android": "next", + "@capacitor/core": "next", "@capacitor/docgen": "0.2.2", - "@capacitor/ios": "^7.0.0", + "@capacitor/ios": "next", "@ionic/eslint-config": "^0.4.0", "@ionic/prettier-config": "~1.0.1", "@ionic/swiftlint-config": "^1.1.2", @@ -63,7 +63,7 @@ "typescript": "~4.1.5" }, "peerDependencies": { - "@capacitor/core": ">=7.0.0" + "@capacitor/core": "next" }, "prettier": "@ionic/prettier-config", "swiftlint": "@ionic/swiftlint-config", diff --git a/toast/CHANGELOG.md b/toast/CHANGELOG.md index 2f496d277d..feef1f89d5 100644 --- a/toast/CHANGELOG.md +++ b/toast/CHANGELOG.md @@ -3,6 +3,14 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +# [8.0.0-alpha.1](https://github.com/ionic-team/capacitor-plugins/compare/@capacitor/toast@7.0.2...@capacitor/toast@8.0.0-alpha.1) (2025-09-08) + +**Note:** Version bump only for package @capacitor/toast + +## [7.0.2](https://github.com/ionic-team/capacitor-plugins/compare/@capacitor/toast@7.0.1...@capacitor/toast@7.0.2) (2025-08-05) + +**Note:** Version bump only for package @capacitor/toast + ## [7.0.1](https://github.com/ionic-team/capacitor-plugins/compare/@capacitor/toast@7.0.0...@capacitor/toast@7.0.1) (2025-04-02) **Note:** Version bump only for package @capacitor/toast diff --git a/toast/CapacitorToast.podspec b/toast/CapacitorToast.podspec index 70d5be2020..bb30d6627a 100644 --- a/toast/CapacitorToast.podspec +++ b/toast/CapacitorToast.podspec @@ -11,7 +11,7 @@ Pod::Spec.new do |s| 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}', 'toast/ios/Sources/**/*.{swift,h,m,c,cc,mm,cpp}' - s.ios.deployment_target = '14.0' + s.ios.deployment_target = '15.0' s.dependency 'Capacitor' s.swift_version = '5.1' end diff --git a/toast/Package.swift b/toast/Package.swift index 94177a6e6e..a05a9ae324 100644 --- a/toast/Package.swift +++ b/toast/Package.swift @@ -3,7 +3,7 @@ import PackageDescription let package = Package( name: "CapacitorToast", - platforms: [.iOS(.v14)], + platforms: [.iOS(.v15)], products: [ .library( name: "CapacitorToast", diff --git a/toast/android/build.gradle b/toast/android/build.gradle index d4822e6305..0c105e804f 100644 --- a/toast/android/build.gradle +++ b/toast/android/build.gradle @@ -1,9 +1,9 @@ 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.0' - androidxJunitVersion = project.hasProperty('androidxJunitVersion') ? rootProject.ext.androidxJunitVersion : '1.2.1' - androidxEspressoCoreVersion = project.hasProperty('androidxEspressoCoreVersion') ? rootProject.ext.androidxEspressoCoreVersion : '3.6.1' + androidxAppCompatVersion = project.hasProperty('androidxAppCompatVersion') ? rootProject.ext.androidxAppCompatVersion : '1.7.1' + androidxJunitVersion = project.hasProperty('androidxJunitVersion') ? rootProject.ext.androidxJunitVersion : '1.3.0' + androidxEspressoCoreVersion = project.hasProperty('androidxEspressoCoreVersion') ? rootProject.ext.androidxEspressoCoreVersion : '3.7.0' } buildscript { @@ -11,11 +11,11 @@ buildscript { google() mavenCentral() maven { - url "https://plugins.gradle.org/m2/" + url = "https://plugins.gradle.org/m2/" } } dependencies { - classpath 'com.android.tools.build:gradle:8.7.2' + 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' } @@ -30,11 +30,11 @@ if (System.getenv("CAP_PLUGIN_PUBLISH") == "true") { } android { - namespace "com.capacitorjs.plugins.toast" - compileSdk project.hasProperty('compileSdkVersion') ? rootProject.ext.compileSdkVersion : 35 + namespace = "com.capacitorjs.plugins.toast" + compileSdk = project.hasProperty('compileSdkVersion') ? rootProject.ext.compileSdkVersion : 36 defaultConfig { - minSdkVersion project.hasProperty('minSdkVersion') ? rootProject.ext.minSdkVersion : 23 - targetSdkVersion project.hasProperty('targetSdkVersion') ? rootProject.ext.targetSdkVersion : 35 + 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" @@ -46,7 +46,7 @@ android { } } lintOptions { - abortOnError false + abortOnError = false } compileOptions { sourceCompatibility JavaVersion.VERSION_21 diff --git a/toast/android/gradle/wrapper/gradle-wrapper.jar b/toast/android/gradle/wrapper/gradle-wrapper.jar index a4b76b9530..1b33c55baa 100644 Binary files a/toast/android/gradle/wrapper/gradle-wrapper.jar and b/toast/android/gradle/wrapper/gradle-wrapper.jar differ diff --git a/toast/android/gradle/wrapper/gradle-wrapper.properties b/toast/android/gradle/wrapper/gradle-wrapper.properties index c1d5e01859..7705927e94 100644 --- a/toast/android/gradle/wrapper/gradle-wrapper.properties +++ b/toast/android/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-8.11.1-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.14.3-all.zip networkTimeout=10000 validateDistributionUrl=true zipStoreBase=GRADLE_USER_HOME diff --git a/toast/android/gradlew b/toast/android/gradlew index f5feea6d6b..23d15a9367 100755 --- a/toast/android/gradlew +++ b/toast/android/gradlew @@ -86,8 +86,7 @@ done # 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 -' "$PWD" ) || exit +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 @@ -115,7 +114,7 @@ case "$( uname )" in #( NONSTOP* ) nonstop=true ;; esac -CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar +CLASSPATH="\\\"\\\"" # Determine the Java command to use to start the JVM. @@ -206,7 +205,7 @@ fi DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' # Collect all arguments for the java command: -# * DEFAULT_JVM_OPTS, JAVA_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, +# * 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. @@ -214,7 +213,7 @@ DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' set -- \ "-Dorg.gradle.appname=$APP_BASE_NAME" \ -classpath "$CLASSPATH" \ - org.gradle.wrapper.GradleWrapperMain \ + -jar "$APP_HOME/gradle/wrapper/gradle-wrapper.jar" \ "$@" # Stop when "xargs" is not available. diff --git a/toast/android/gradlew.bat b/toast/android/gradlew.bat index 9b42019c79..5eed7ee845 100644 --- a/toast/android/gradlew.bat +++ b/toast/android/gradlew.bat @@ -70,11 +70,11 @@ goto fail :execute @rem Setup the command line -set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar +set CLASSPATH= @rem Execute Gradle -"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* +"%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 diff --git a/toast/package.json b/toast/package.json index ca1795ffab..3be114a940 100644 --- a/toast/package.json +++ b/toast/package.json @@ -1,6 +1,6 @@ { "name": "@capacitor/toast", - "version": "7.0.1", + "version": "8.0.0-alpha.1", "description": "The Toast API provides a notification pop up for displaying important information to a user. Just like real toast!", "main": "dist/plugin.cjs.js", "module": "dist/esm/index.js", @@ -47,10 +47,10 @@ "publish:cocoapod": "pod trunk push ./CapacitorToast.podspec --allow-warnings" }, "devDependencies": { - "@capacitor/android": "^7.0.0", - "@capacitor/core": "^7.0.0", + "@capacitor/android": "next", + "@capacitor/core": "next", "@capacitor/docgen": "0.2.2", - "@capacitor/ios": "^7.0.0", + "@capacitor/ios": "next", "@ionic/eslint-config": "^0.4.0", "@ionic/prettier-config": "~1.0.1", "@ionic/swiftlint-config": "^1.1.2", @@ -63,7 +63,7 @@ "typescript": "~4.1.5" }, "peerDependencies": { - "@capacitor/core": ">=7.0.0" + "@capacitor/core": "next" }, "prettier": "@ionic/prettier-config", "swiftlint": "@ionic/swiftlint-config",