diff --git a/.gitignore b/.gitignore index c96a83f..ca6ae36 100644 --- a/.gitignore +++ b/.gitignore @@ -36,3 +36,7 @@ demo-angular/report/stats.json /src/platforms/android/nativescript_imagepicker.aar /src/*.tgz !demo-vue/app/app.js + +// Tests +**/mochawesome-report/ +test-results.xml diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index 4658607..0000000 --- a/.travis.yml +++ /dev/null @@ -1,202 +0,0 @@ -env: - global: - - ANDROID_PACKAGE_JS='imagepicker-debug-js.apk' - - ANDROID_PACKAGE_NG='imagepicker-debug-ng.apk' - - ANDROID_PACKAGE_VUE='imagepicker-debug-vue.apk' - - ANDROID_PACKAGE_FOLDER_JS=$TRAVIS_BUILD_DIR/demo/outputs - - ANDROID_PACKAGE_FOLDER_NG=$TRAVIS_BUILD_DIR/demo-angular/outputs - - ANDROID_PACKAGE_FOLDER_VUE=$TRAVIS_BUILD_DIR/demo-vue/outputs - - ANDROID_SAUCE_STORAGE="https://saucelabs.com/rest/v1/storage/$SAUCE_USER" - - IOS_PACKAGE_JS='imagepicker-demo-js.zip' - - IOS_PACKAGE_NG='imagepicker-demo-ng.zip' - - IOS_PACKAGE_VUE='imagepicker-demo-vue.zip' - - IOS_PACKAGE_FOLDER_JS=$TRAVIS_BUILD_DIR/demo/outputs - - IOS_PACKAGE_FOLDER_NG=$TRAVIS_BUILD_DIR/demo-angular/outputs - - IOS_PACKAGE_FOLDER_VUE=$TRAVIS_BUILD_DIR/demo-vue/outputs - - IOS_SAUCE_STORAGE="https://saucelabs.com/rest/v1/storage/$SAUCE_USER" - -git: - depth: 1 - -branches: - only: - - master - -matrix: - include: - - stage: "Lint" - language: node_js - os: linux - node_js: "10" - script: cd src && npm run ci.tslint && cd ../demo && npm run ci.tslint && cd ../demo-angular && npm run ci.tslint - - stage: "WebPack and Build" - os: osx - env: - - WebpackiOS="12.0" - - Type="VanillaJS" - osx_image: xcode10.2 - language: node_js - node_js: "10" - jdk: oraclejdk8 - before_script: pod repo update - script: - - cd src && npm run build - - cd ../demo && npm i && tns build ios --bundle --env.uglify --copy-to "./outputs/demo.app" - - cd $IOS_PACKAGE_FOLDER_JS && zip -r $IOS_PACKAGE_JS demo.app - - "curl -u $SAUCE_USER:$SAUCE_KEY -X POST -H 'Content-Type: application/octet-stream' $IOS_SAUCE_STORAGE/$IOS_PACKAGE_JS?overwrite=true --data-binary @$IOS_PACKAGE_FOLDER_JS/$IOS_PACKAGE_JS" - - os: osx - env: - - WebpackiOS="12.0" - - Type="Angular" - osx_image: xcode10.2 - language: node_js - node_js: "10" - jdk: oraclejdk8 - before_script: pod repo update - script: - - cd src && npm run build - - cd ../demo-angular && npm i && tns build ios --bundle --env.uglify --env.aot --copy-to "./outputs/demo-angular.app" - - cd $IOS_PACKAGE_FOLDER_NG && zip -r $IOS_PACKAGE_NG demo-angular.app - - "curl -u $SAUCE_USER:$SAUCE_KEY -X POST -H 'Content-Type: application/octet-stream' $IOS_SAUCE_STORAGE/$IOS_PACKAGE_NG?overwrite=true --data-binary @$IOS_PACKAGE_FOLDER_NG/$IOS_PACKAGE_NG" - - os: osx - env: - - WebpackiOS="12.0" - - Type="VueJS" - osx_image: xcode10.2 - language: node_js - node_js: "10" - jdk: oraclejdk8 - before_script: pod repo update - script: - - cd src && npm run build - - cd ../demo-vue && npm i && tns build ios --bundle --env.uglify --copy-to "./outputs/demovue.app" - - cd $IOS_PACKAGE_FOLDER_VUE && zip -r $IOS_PACKAGE_VUE demovue.app - - "curl -u $SAUCE_USER:$SAUCE_KEY -X POST -H 'Content-Type: application/octet-stream' $IOS_SAUCE_STORAGE/$IOS_PACKAGE_VUE?overwrite=true --data-binary @$IOS_PACKAGE_FOLDER_VUE/$IOS_PACKAGE_VUE" - - language: android - os: linux - dist: trusty - env: - - WebpackAndroid="28" - - Type="VanillaJS" - jdk: oraclejdk8 - before_install: nvm install 10 - script: - - cd src && npm run build - - cd ../demo && npm i && tns build android --bundle --env.uglify --env.snapshot --copy-to "./outputs/app-debug.apk" - - "curl -u $SAUCE_USER:$SAUCE_KEY -X POST -H 'Content-Type: application/octet-stream' $ANDROID_SAUCE_STORAGE/$ANDROID_PACKAGE_JS?overwrite=true --data-binary @$ANDROID_PACKAGE_FOLDER_JS/app-debug.apk" - - language: android - os: linux - dist: trusty - env: - - WebpackAndroid="28" - - Type="Angular" - jdk: oraclejdk8 - before_install: nvm install 10 - script: - - cd src && npm run build - - cd ../demo-angular && npm i && tns build android --bundle --env.uglify --env.snapshot --copy-to "./outputs/app-debug.apk" - - "curl -u $SAUCE_USER:$SAUCE_KEY -X POST -H 'Content-Type: application/octet-stream' $ANDROID_SAUCE_STORAGE/$ANDROID_PACKAGE_NG?overwrite=true --data-binary @$ANDROID_PACKAGE_FOLDER_NG/app-debug.apk" - - language: android - os: linux - dist: trusty - env: - - WebpackAndroid="28" - - Type="VueJS" - jdk: oraclejdk8 - before_install: nvm install 10 - script: - - cd src && npm run build - - cd ../demo-vue && npm i && tns build android --bundle --env.uglify --copy-to "./outputs/app-debug.apk" - - "curl -u $SAUCE_USER:$SAUCE_KEY -X POST -H 'Content-Type: application/octet-stream' $ANDROID_SAUCE_STORAGE/$ANDROID_PACKAGE_VUE?overwrite=true --data-binary @$ANDROID_PACKAGE_FOLDER_VUE/app-debug.apk" - - stage: "UI Tests" - env: - - Android="24" - - Type="VanillaJS" - language: node_js - os: linux - node_js: "10" - script: - - npm i -g appium - - cd tests && npm i - - travis_wait travis_retry npm run e2e -- --runType android24 --sauceLab --appPath $ANDROID_PACKAGE_JS - - os: linux - env: - - Android="24" - - Type="VueJS" - language: node_js - os: linux - node_js: "10" - script: - - npm i -g appium - - cd tests && npm i - - travis_wait travis_retry npm run e2e -- --runType android24 --sauceLab --appPath $ANDROID_PACKAGE_VUE - - os: linux - env: - - Android="24" - - Type="Angular" - language: node_js - os: linux - node_js: "10" - script: - - npm i -g appium - - cd tests && npm i - - travis_wait travis_retry npm run e2e -- --runType android24 --sauceLab --appPath $ANDROID_PACKAGE_NG - - os: linux - env: - - iOS="12.0" - - Type="VanillaJS" - language: node_js - node_js: "10" - script: - - npm i -g appium - - cd tests && npm i - - travis_wait travis_retry npm run e2e -- --runType sim12iPhoneX --sauceLab --appPath $IOS_PACKAGE_JS - - os: linux - env: - - iOS="12.0" - - Type="VueJS" - language: node_js - node_js: "10" - script: - - npm i -g appium - - cd tests && npm i - - travis_wait travis_retry npm run e2e -- --runType sim12iPhoneX --sauceLab --appPath $IOS_PACKAGE_VUE - - os: linux - env: - - iOS="12.0" - - Type="Angular" - language: node_js - node_js: "10" - script: - - npm i -g appium - - cd tests && npm i - - travis_wait travis_retry npm run e2e -- --runType sim12iPhoneX --sauceLab --appPath $IOS_PACKAGE_NG - -android: - components: - - tools - - platform-tools - - build-tools-28.0.3 - - android-28 - - android-23 - - extra-android-m2repository - -before_cache: - - rm -f $HOME/.gradle/caches/modules-2/modules-2.lock - -cache: - directories: - - .nvm - - $HOME/.gradle/caches/ - - $HOME/.gradle/wrapper/ - -before_install: - - sudo pip install --upgrade pip - - sudo pip install six - -install: - - echo no | npm install -g nativescript - - tns usage-reporting disable - - tns error-reporting disable - - diff --git a/README.md b/README.md index 475aca3..e1a4292 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,10 @@ +## NativeScript 7 or higher + +* Use `@nativescript/imagepicker`: `~1.0.0` +* [Source managed here](https://github.com/NativeScript/plugins) + +## If using 6 and below, see the following: + # NativeScript Image Picker ![apple](https://cdn3.iconfinder.com/data/icons/picons-social/57/16-apple-32.png) ![android](https://cdn4.iconfinder.com/data/icons/logos-3/228/android-32.png) @@ -22,8 +29,6 @@ Imagepicker plugin supporting both single and multiple selection. - [Request permissions, show the images list and process the selection](#request-permissions-show-the-images-list-and-process-the-selection) - [API](#api) - [Methods](#methods) - - [Properties](#properties) - - [Image properties](#image-properties) - [Contribute](#contribute) - [Get Help](#get-help) @@ -111,6 +116,18 @@ context > **NOTE**: To request permissions for Android 6+ (API 23+) we use [nativescript-permissions](https://www.npmjs.com/package/nativescript-permissions). +> **NOTE**: To be sure to have permissions add the following lines in AndroidManifest.xml +``` + + + + + ... + + +``` + + > **NOTE**: Using the plugin on iOS requres photo library permission. Your app might be rejected from the Apple App Store if you do not provide a description about why you need this permission. The default message "Requires access to photo library." might not be enough for the App Store reviewers. You can customize it by editing the `app/App_Resources/iOS/Info.plist` file in your app and adding the following key: ```xml @@ -133,7 +150,8 @@ context | prompt | iOS | undefined | Display prompt text when selecting assets. | | numberOfColumnsInPortrait | iOS | 4 | Set the number of columns in Portrait orientation. | | numberOfColumnsInLandscape | iOS | 7 | Set the number of columns in Landscape orientation. | -| mediaType | both | Any (iOS), Image (Android) | Choose whether to pick Image/Video/Any type of assets. | +| mediaType | both | Any | Choose whether to pick Image/Video/Any type of assets. | +| showAdvanced | Android | false | Show internal and removable storage options on Android (**WARNING**: [not supported officially](https://issuetracker.google.com/issues/72053350)). | The **hostView** parameter can be set to the view that hosts the image picker. Applicable in iOS only, intended to be used when open picker from a modal page. diff --git a/demo-angular/app/App_Resources/Android/src/main/AndroidManifest.xml b/demo-angular/app/App_Resources/Android/src/main/AndroidManifest.xml index 9db8321..976f83f 100644 --- a/demo-angular/app/App_Resources/Android/src/main/AndroidManifest.xml +++ b/demo-angular/app/App_Resources/Android/src/main/AndroidManifest.xml @@ -19,6 +19,7 @@ 23) { + (this.getContentResolver() as any).takePersistableUriPermission( + uri, + android.content.Intent.FLAG_GRANT_READ_URI_PERMISSION | android.content.Intent.FLAG_GRANT_WRITE_URI_PERMISSION, + ); + const externalMediaDirs = application.android.context.getExternalMediaDirs(); + if (externalMediaDirs.length > 1) { + let filePath = externalMediaDirs[1].getAbsolutePath(); + filePath = filePath.substring(0, filePath.indexOf("Android")) + id; + return filePath; + } + } } - - // TODO handle non-primary volumes } // DownloadsProvider else if (UriHelper.isDownloadsDocument(uri)) { @@ -147,7 +158,7 @@ export class ImagePicker { } get mediaType(): string { - const mediaType = this._options && 'mediaType' in this._options ? this._options.mediaType : ImagePickerMediaType.Image; + const mediaType = this._options && 'mediaType' in this._options ? this._options.mediaType : ImagePickerMediaType.Any; if (mediaType === ImagePickerMediaType.Image) { return "image/*"; } else if (mediaType === ImagePickerMediaType.Video) { @@ -157,6 +168,20 @@ export class ImagePicker { } } + get mimeTypes() { + let length = this.mediaType === "*/*" ? 2 : 1; + let mimeTypes = Array.create(java.lang.String, length); + + if (this.mediaType === "*/*") { + mimeTypes[0] = "image/*"; + mimeTypes[1] = "video/*"; + } + else { + mimeTypes[0] = this.mediaType; + } + return mimeTypes; + } + authorize(): Promise { if ((android).os.Build.VERSION.SDK_INT >= 23) { return permissions.requestPermission([(android).Manifest.permission.READ_EXTERNAL_STORAGE]); @@ -171,7 +196,7 @@ export class ImagePicker { // WARNING: If we want to support multiple pickers we will need to have a range of IDs here: let RESULT_CODE_PICKER_IMAGES = 9192; - let application = require("application"); + let application = require("tns-core-modules/application"); application.android.on(application.AndroidApplication.activityResultEvent, onResult); function onResult(args) { @@ -227,11 +252,18 @@ export class ImagePicker { let intent = new Intent(); intent.setType(this.mediaType); + // not in platform-declaration typings + intent.putExtra((android.content.Intent as any).EXTRA_MIME_TYPES, this.mimeTypes); + // TODO: Use (android).content.Intent.EXTRA_ALLOW_MULTIPLE if (this.mode === 'multiple') { intent.putExtra("android.intent.extra.ALLOW_MULTIPLE", true); } + if (this._options.showAdvanced) { + intent.putExtra("android.content.extra.SHOW_ADVANCED", true); + } + intent.putExtra(android.content.Intent.EXTRA_LOCAL_ONLY, true); intent.setAction("android.intent.action.OPEN_DOCUMENT"); let chooser = Intent.createChooser(intent, "Select Picture"); diff --git a/src/imagepicker.common.ts b/src/imagepicker.common.ts index 364787e..0d904f4 100644 --- a/src/imagepicker.common.ts +++ b/src/imagepicker.common.ts @@ -48,6 +48,12 @@ export interface Options { */ mediaType?: ImagePickerMediaType; + /** + * Show internal and removable storage options on Android. + * Not supported officially, see https://issuetracker.google.com/issues/72053350 | + */ + showAdvanced?: boolean; + android?: { /** * Provide a reason for permission request to access external storage on api levels above 23. diff --git a/src/index.d.ts b/src/index.d.ts index 38208e1..8a5b1a8 100644 --- a/src/index.d.ts +++ b/src/index.d.ts @@ -4,6 +4,9 @@ import { ImageAsset } from "tns-core-modules/image-asset"; import { View } from "tns-core-modules/ui/core/view/view"; export class ImagePicker { + + constructor(options?: Options); + /** * Call this before 'present' to request any additional permissions that may be necessary. * In case of failed authorization consider notifying the user for degraded functionality. @@ -67,12 +70,19 @@ interface Options { */ mediaType?: ImagePickerMediaType; + /** + * Show internal and removable storage options on Android. + * Not supported officially, see https://issuetracker.google.com/issues/72053350 | + */ + showAdvanced?: boolean; + android?: { /** * Provide a reason for permission request to access external storage on api levels above 23. */ read_external_storage?: string; }; + } /** diff --git a/src/package.json b/src/package.json index 59b4a4c..f97ecb9 100644 --- a/src/package.json +++ b/src/package.json @@ -1,6 +1,6 @@ { "name": "nativescript-imagepicker", - "version": "6.3.0", + "version": "7.1.0", "description": "A plugin for the NativeScript framework implementing multiple image picker", "repository": { "type": "git", @@ -46,9 +46,9 @@ "homepage": "https://github.com/NativeScript/nativescript-imagepicker", "readmeFilename": "README.md", "devDependencies": { - "tns-core-modules": "^5.0.0", - "tns-platform-declarations": "^5.0.0", - "typescript": "~3.4.5", + "tns-core-modules": "^6.0.0", + "tns-platform-declarations": "^6.0.0", + "typescript": "~3.5.3", "tslint": "~5.11.0" }, "dependencies": { diff --git a/src/platforms/android/AndroidManifest.xml b/src/platforms/android/AndroidManifest.xml index 5a0779c..4df3b7f 100644 --- a/src/platforms/android/AndroidManifest.xml +++ b/src/platforms/android/AndroidManifest.xml @@ -6,5 +6,5 @@ - + diff --git a/src/platforms/ios/Podfile b/src/platforms/ios/Podfile index 4bf1aa1..66bb1d8 100644 --- a/src/platforms/ios/Podfile +++ b/src/platforms/ios/Podfile @@ -1 +1 @@ -pod 'QBImagePickerController', '~> 3.4.0' \ No newline at end of file +pod "QBImagePickerController", :git => 'https://github.com/flypapertech/QBImagePicker.git', :commit => '47aa21d32f8e6db0df79b089ad64e43d498b5951' diff --git a/tests/e2e/config/appium.capabilities.json b/tests/e2e/config/appium.capabilities.json index 1333fc5..0347667 100644 --- a/tests/e2e/config/appium.capabilities.json +++ b/tests/e2e/config/appium.capabilities.json @@ -36,6 +36,13 @@ "appium-version": "1.7.1", "noReset": true }, + "android28": { + "platformName": "Android", + "platformVersion": "9.0", + "deviceName": "Android GoogleAPI Emulator", + "appiumVersion": "1.9.1", + "noReset": true + }, "sim11iPhone6": { "platformName": "iOS", "platformVersion": "11.0", diff --git a/tests/e2e/helper.ts b/tests/e2e/helper.ts new file mode 100644 index 0000000..6f7a49f --- /dev/null +++ b/tests/e2e/helper.ts @@ -0,0 +1,10 @@ +import { AppiumDriver } from "nativescript-dev-appium"; + +export async function findAndroidImages(driver: AppiumDriver) { + const sidedrawer = await driver.findElementByAccessibilityId("Show roots"); + await sidedrawer.click(); + const images = await driver.findElementByText("Images"); + await images.click(); + const dcimFolder = await driver.findElementByText("DCIM"); + await dcimFolder.click(); +} \ No newline at end of file diff --git a/tests/e2e/test.e2e.ts b/tests/e2e/test.e2e.ts index 5a56a2d..d6d718a 100644 --- a/tests/e2e/test.e2e.ts +++ b/tests/e2e/test.e2e.ts @@ -1,6 +1,7 @@ import { AppiumDriver, createDriver, SearchOptions } from "nativescript-dev-appium"; import { isSauceLab, runType } from "nativescript-dev-appium/lib/parser"; import { expect } from "chai"; +import { findAndroidImages } from "./helper"; const fs = require('fs'); const addContext = require('mochawesome/addContext'); const rimraf = require('rimraf'); @@ -57,26 +58,19 @@ describe("Imagepicker", async function () { const confirmButton = await driver.findElementByText(confirmButtonText); await confirmButton.click(); + // TODO remove when SauceLabs fix images location if (isAndroid) { - const imagesFolderXpath = await driver.elementHelper.getXPathByText(imagesFolderName, SearchOptions.contains); - await driver.driver.sleep(3000); - let imagesFolder = await driver.driver.elementByXPathIfExists(imagesFolderXpath, 10000); - - if (isSauceRun && imagesFolder) { - await imagesFolder.click(); - let dcimFolder = await driver.findElementByText("DCIM", SearchOptions.contains); - await dcimFolder.click(); - imagesFolder = await driver.findElementByClassName(driver.locators.image); - await imagesFolder.click(); - } - } else { + await findAndroidImages(driver); + } + + if (!isAndroid) { const cameraRollFolder = await driver.findElementByAccessibilityId(imagesFolderNameIos); await cameraRollFolder.click(); } const imageLocator = isAndroid ? "android.widget.ImageView" : "XCUIElementTypeCell"; - const image = await driver.findElementByClassName(imageLocator); - await image.tap(); + const images = await driver.findElementsByClassName(imageLocator); + await images[1].tap(); pickSingleButton = await driver.findElementByText(pickSingleButtonText, SearchOptions.contains); expect(pickSingleButton).to.exist; @@ -94,6 +88,11 @@ describe("Imagepicker", async function () { const pickMultipleButton = await driver.findElementByText(pickMultipleButtonText, SearchOptions.contains); await pickMultipleButton.click(); + // TODO remove when SauceLabs fix images location + if (isAndroid) { + await findAndroidImages(driver); + } + if (!isAndroid) { const cameraRollFolder = await driver.findElementByText(imagesFolderNameIos); await cameraRollFolder.click(); @@ -101,8 +100,8 @@ describe("Imagepicker", async function () { if (isAndroid) { const allImages = await driver.findElementsByClassName("android.widget.ImageView"); - await allImages[5].hold(); // second Image - await allImages[2].click(); // first image + await allImages[1].hold(); // second Image + await allImages[4].click(); // first image } else { const allImages = await driver.findElementsByClassName("XCUIElementTypeCell"); await allImages[0].click(); // first image diff --git a/tests/package.json b/tests/package.json index a983540..4370321 100644 --- a/tests/package.json +++ b/tests/package.json @@ -11,7 +11,7 @@ "karma-nativescript-launcher": "^0.4.0", "mocha": "^3.3.0", "mocha-junit-reporter": "^1.18.0", - "mocha-multi": "^1.0.1", + "mocha-multi": "1.1.0", "mochawesome": "^3.1.1", "nativescript-dev-appium": "~5.2.0", "typescript": "~3.5.3" @@ -19,4 +19,4 @@ "scripts": { "e2e": "tsc -p e2e && mocha --opts ./e2e/config/mocha.opts" } -} \ No newline at end of file +}