diff --git a/.travis.yml b/.travis.yml
index d6e5ed1..8734fc7 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -1,7 +1,7 @@
env:
global:
- - ANDROID_PACKAGE='imagepickerdemo-debug.apk'
- - ANDROID_PACKAGE_FOLDER=$TRAVIS_BUILD_DIR/demo/platforms/android/app/build/outputs/apk
+ - ANDROID_PACKAGE='app-debug.apk'
+ - ANDROID_PACKAGE_FOLDER=$TRAVIS_BUILD_DIR/demo/platforms/android/app/build/outputs/apk/debug
- ANDROID_SAUCE_STORAGE="https://saucelabs.com/rest/v1/storage/$SAUCE_USER/$ANDROID_PACKAGE?overwrite=true"
- IOS_PACKAGE='imagepickerdemo.zip'
- IOS_PACKAGE_FOLDER=$TRAVIS_BUILD_DIR/demo/platforms/ios/build/emulator
@@ -78,7 +78,7 @@ android:
components:
- tools
- platform-tools
- - build-tools-26.0.1
+ - build-tools-26.0.2
- android-26
- android-23
- extra-android-m2repository
diff --git a/README.md b/README.md
index 9c33a1c..e54d06c 100644
--- a/README.md
+++ b/README.md
@@ -6,13 +6,14 @@
[](https://travis-ci.org/NativeScript/nativescript-imagepicker)
Imagepicker plugin supporting both single and multiple selection.
- Plugin supports **iOS8+** and uses [Photos Framework](https://developer.apple.com/library/prerelease/ios//documentation/Photos/Reference/Photos_Framework/index.html).
+ Plugin supports **iOS8+** and uses [QBImagePicker](https://github.com/questbeat/QBImagePicker) cocoa pod.
For **Android** it uses Intents to open the stock images or file pickers. For Android 6 (API 23) and above the permissions to read file storage should be explicitly required. See demo for implementation details.
- [Installation](#installation)
- [Configuration](#configuration)
+- [Migrating from 5.x.x to 6.x.x](#migrating-from-5xx-to-6xx)
- [Migrating from 4.x.x to 5.x.x](#migrating-from-4xx-to-5xx)
- [Migrating from 3.x.x to 4.x.x](#migrating-from-3xx-to-4xx)
- [Usage](#usage)
@@ -41,6 +42,9 @@ tns run
## Configuration
No additional configuration required!
+## Migrating from 5.x.x to 6.x.x
+With version **6.x.x** the dependency to the `nativescript-ui-listview` plugin is removed and for iOS the [QBImagePicker](https://github.com/questbeat/QBImagePicker) cocoa pod is used. Now the plugin supports some new features, fixes some bugs and looks super native for iOS. You can remove any dependencies to `nativescript-pro-ui`, `nativescript-ui-listview`, etc. in case you've added them in your app specifically for this plugin. Also the options **doneText**, **cancelText**, **albumsText**, **newestFirst** and the methods **cancel()** and **done()** are no longer applicable. The image picker now returns the basic [{N} ImageAsset class](https://github.com/NativeScript/NativeScript/tree/master/tns-core-modules/image-asset) (and not custom asset as before).
+
## Migrating from 4.x.x to 5.x.x
With version **5.x.x** major update to the plugin there is a related dependency which needs to be updated inside your project. The plugin uses internally the `nativescript-ui-listview` plugin (part of the NativeScript Pro UI components). Recently the monolithic [NativeScript Pro UI plugin was split in multiple plugins](https://www.nativescript.org/blog/professional-components-from-nativescript-ui-the-big-breakup), each of them representing a single component. Now, instead of the monolithic package, nativescript-imagepicker uses only the component it needs. To use version 5.x.x of the plugin, you need to update any dependencies to `nativescript-pro-ui` in your project with the single component alternatives as described in the [migration guide](http://docs.telerik.com/devtools/nativescript-ui/migration).
@@ -116,33 +120,18 @@ context
| Option | Platform | Default | Description |
| --- | --- | --- | --- |
| mode | both | multiple | The mode if the imagepicker. Possible values are `single` for single selection and `multiple` for multiple selection. |
-| doneText | iOS | Done | The text of the "Done" button on top right. |
-| cancelText | iOS | Cancel | The text of the "Cancel" button on top left. |
-| albumsText | iOS | Albums | The title of the "Albums" screen from where the selection of album and images can be done. |
-| newestFirst | iOS | false | Set to `true` to sort the images in an album by newest first. |
+| minimumNumberOfSelection | iOS | 0 | The minumum number of selected assets. |
+| maximumNumberOfSelection | iOS | 0 | The maximum number of selected assets. |
+| showsNumberOfSelectedAssets | iOS | True | Display the number of selected assets. |
+| 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 | iOS | Any | Choose whether to pick Image/Video/Any type of assets. |
+
+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.
* authorize() - request the required permissions.
* present() - show the albums to present the user the ability to select images. Returns an array of the selected images.
-* cancel() - cancel selection. iOS only.
-* done() - confirm the selection is ready. iOS only.
-
-
-### Properties
-| Property | Default | Description |
-| --- | --- | --- |
-| selection | null | An array of selected image assets. |
-| albums | null | Albums from where the images are picked. |
-
-
-### Image properties
-
-Once image is picked some options can be applied to it before it is used:
-
-| Option | Default | Description |
-| --- | --- | --- |
-| maxWidth | null | Image max width |
-| maxHeight | null | Image max height |
-| aspectRatio | fit | iOS only. Possible values are `fit` and `fill`. [Read more](https://developer.apple.com/documentation/photos/phimagecontentmode) |
## Contribute
We love PRs! Check out the [contributing guidelines](CONTRIBUTING.md). If you want to contribute, but you are not sure where to start - look for [issues labeled `help wanted`](https://github.com/NativeScript/nativescript-imagepicker/issues?q=is%3Aopen+is%3Aissue+label%3A%22help+wanted%22).
diff --git a/demo-angular/app/app.component.html b/demo-angular/app/app.component.html
index 7efff0d..82bbd9a 100644
--- a/demo-angular/app/app.component.html
+++ b/demo-angular/app/app.component.html
@@ -3,15 +3,19 @@
-
-
-
-
-
-
+
+
+
+
+
-
-
+
+
+
+
\ No newline at end of file
diff --git a/demo-angular/app/app.component.ts b/demo-angular/app/app.component.ts
index b35dd6f..bc2a1c6 100644
--- a/demo-angular/app/app.component.ts
+++ b/demo-angular/app/app.component.ts
@@ -1,6 +1,4 @@
-import { Component, ChangeDetectorRef } from "@angular/core";
-import { ListView } from "tns-core-modules/ui/list-view";
-import { isAndroid } from "tns-core-modules/platform";
+import { Component } from "@angular/core";
import * as imagepicker from "nativescript-imagepicker";
@Component({
@@ -8,44 +6,51 @@ import * as imagepicker from "nativescript-imagepicker";
templateUrl: "app.component.html",
})
export class AppComponent {
+ imageAssets = [];
+ imageSrc: any;
+ isSingleMode: boolean = true;
+ thumbSize: number = 80;
+ previewSize: number = 300;
- items = [];
+ public onSelectMultipleTap() {
+ this.isSingleMode = false;
- constructor(private _changeDetectionRef: ChangeDetectorRef) {
- }
-
- onSelectMultipleTap() {
let context = imagepicker.create({
mode: "multiple"
});
this.startSelection(context);
}
- onSelectSingleTap() {
+ public onSelectSingleTap() {
+ this.isSingleMode = true;
+
let context = imagepicker.create({
mode: "single"
});
this.startSelection(context);
}
- startSelection(context) {
- let _that = this;
+ private startSelection(context) {
+ let that = this;
context
.authorize()
.then(() => {
- _that.items = [];
+ that.imageAssets = [];
+ that.imageSrc = null;
return context.present();
})
.then((selection) => {
- console.log("Selection done:");
- selection.forEach(function (selected) {
- console.log("----------------");
- console.log("uri: " + selected.uri);
- console.log("fileUri: " + selected.fileUri);
+ console.log("Selection done: " + JSON.stringify(selection));
+ that.imageSrc = that.isSingleMode && selection.length > 0 ? selection[0] : null;
+
+ // set the images to be loaded from the assets with optimal sizes (optimize memory usage)
+ selection.forEach(function (element) {
+ element.options.width = that.isSingleMode ? that.previewSize : that.thumbSize;
+ element.options.height = that.isSingleMode ? that.previewSize : that.thumbSize;
});
- _that.items = selection;
- _that._changeDetectionRef.detectChanges();
+
+ that.imageAssets = selection;
}).catch(function (e) {
console.log(e);
});
diff --git a/demo-angular/app/main.ts b/demo-angular/app/main.ts
index 639bfd5..5cf76e1 100644
--- a/demo-angular/app/main.ts
+++ b/demo-angular/app/main.ts
@@ -1,4 +1,4 @@
import { platformNativeScriptDynamic } from "nativescript-angular/platform";
import { AppModule } from "./app.module";
-platformNativeScriptDynamic().bootstrapModule(AppModule);
+platformNativeScriptDynamic({ createFrameOnBootstrap: true }).bootstrapModule(AppModule);
\ No newline at end of file
diff --git a/demo-angular/package.json b/demo-angular/package.json
index 1eb9e13..548bd5e 100644
--- a/demo-angular/package.json
+++ b/demo-angular/package.json
@@ -2,10 +2,10 @@
"nativescript": {
"id": "org.nativescript.imagepickerdemoangular",
"tns-ios": {
- "version": "3.4.0"
+ "version": "4.0.1"
},
"tns-android": {
- "version": "3.4.0"
+ "version": "4.0.0"
}
},
"dependencies": {
@@ -23,7 +23,7 @@
"nativescript-unit-test-runner": "^0.3.4",
"reflect-metadata": "~0.1.8",
"rxjs": "^5.5.0",
- "tns-core-modules": "^3.4.0"
+ "tns-core-modules": "^4.0.0"
},
"devDependencies": {
"@angular/compiler-cli": "~5.0.0",
@@ -42,7 +42,7 @@
"lazy": "1.0.11",
"nativescript-css-loader": "~0.26.0",
"nativescript-dev-typescript": "^0.6.0",
- "nativescript-dev-webpack": "^0.9.0",
+ "nativescript-dev-webpack": "^0.10.1",
"nativescript-worker-loader": "~0.8.1",
"raw-loader": "~0.5.1",
"resolve-url-loader": "~2.1.0",
@@ -52,7 +52,8 @@
"webpack": "~3.8.1",
"webpack-bundle-analyzer": "^2.8.2",
"webpack-sources": "~1.0.1",
- "zone.js": "^0.8.4"
+ "zone.js": "^0.8.4",
+ "uglifyjs-webpack-plugin": "~1.1.6"
},
"scripts": {
"build.plugin": "cd ../src && npm run build",
diff --git a/demo/app/main-page.ts b/demo/app/main-page.ts
index 8199a20..2f755ff 100644
--- a/demo/app/main-page.ts
+++ b/demo/app/main-page.ts
@@ -1,50 +1,10 @@
import { EventData } from 'tns-core-modules/data/observable';
import { Page } from 'tns-core-modules/ui/page';
-import { isAndroid } from "tns-core-modules/platform";
-import * as imagepicker from "nativescript-imagepicker";
+import { MainViewModel } from './main-view-model';
-let list;
-let imageSrc;
-
-export function pageLoaded(args: EventData) {
+export function onNavigatingTo(args: EventData) {
+ let viewModel = new MainViewModel();
let page = args.object;
- list = page.getViewById("urls-list");
- imageSrc = page.getViewById("imageSrc");
-}
-
-export function onSelectMultipleTap(args) {
- let context = imagepicker.create({ mode: "multiple" });
- startSelection(context, false);
-}
-
-export function onSelectSingleTap(args) {
- let context = imagepicker.create({ mode: "single" });
- startSelection(context, true);
-}
-function startSelection(context, isSingle) {
- context
- .authorize()
- .then(function() {
- list.items = [];
- return context.present();
- })
- .then(function(selection) {
- console.log("Selection done:");
- selection.forEach(function(selected) {
- console.log("----------------");
- console.log("uri: " + selected.uri);
- if (isSingle) {
- selected.getImage({ maxWidth: 200, maxHeight: 200, aspectRatio: 'fill' })
- .then((imageSource) => {
- imageSrc.src = imageSource;
- });
- } else {
- imageSrc.visibility = 'hidden';
- }
- });
- list.items = selection;
- }).catch(function (e) {
- console.log(e);
- });
+ page.bindingContext = viewModel;
}
\ No newline at end of file
diff --git a/demo/app/main-page.xml b/demo/app/main-page.xml
index 41d50bb..b237af5 100644
--- a/demo/app/main-page.xml
+++ b/demo/app/main-page.xml
@@ -1,20 +1,18 @@
-
+
-
+
-
-
-
-
+
+
+
-
-
-
-
+
+
+
diff --git a/demo/app/main-view-model.ts b/demo/app/main-view-model.ts
new file mode 100644
index 0000000..1a76bc6
--- /dev/null
+++ b/demo/app/main-view-model.ts
@@ -0,0 +1,98 @@
+import { Observable } from 'tns-core-modules/data/observable';
+import * as imagepicker from "nativescript-imagepicker";
+import { ItemEventData } from "tns-core-modules/ui/list-view";
+import { Label } from "tns-core-modules/ui/label";
+
+export class MainViewModel extends Observable {
+ constructor() {
+ super();
+ }
+
+ private _imageSrc: any;
+ private _imageAssets: Array;
+ private _isSingleMode: boolean;
+
+ get thumbSize(): any {
+ return 80;
+ }
+
+ get previewSize(): any {
+ return 300;
+ }
+
+ get imageSrc(): any {
+ return this._imageSrc;
+ }
+
+ set imageSrc(value: any) {
+ if (this._imageSrc !== value) {
+ this._imageSrc = value;
+ this.notifyPropertyChange('imageSrc', value);
+ }
+ }
+
+ get imageAssets(): any {
+ return this._imageAssets;
+ }
+
+ set imageAssets(value: any) {
+ if (this._imageAssets !== value) {
+ this._imageAssets = value;
+ this.notifyPropertyChange('imageAssets', value);
+ }
+ }
+
+ get isSingleMode(): any {
+ return this._isSingleMode;
+ }
+
+ set isSingleMode(value: any) {
+ if (this._isSingleMode !== value) {
+ this._isSingleMode = value;
+ this.notifyPropertyChange('isSingleMode', value);
+ }
+ }
+
+ public onItemLoading(args: ItemEventData) {
+ let label = args.view.getViewById("imageLabel");
+ label.text = "image " + args.index;
+ }
+
+ public onSelectMultipleTap(args) {
+ this.isSingleMode = false;
+ let context = imagepicker.create({
+ mode: "multiple"
+ });
+ this.startSelection(context);
+ }
+
+ public onSelectSingleTap(args) {
+ this.isSingleMode = true;
+ let context = imagepicker.create({ mode: "single" });
+ this.startSelection(context);
+ }
+
+ private startSelection(context) {
+ context
+ .authorize()
+ .then(() => {
+ this.imageAssets = [];
+ this.imageSrc = null;
+ return context.present();
+ })
+ .then((selection) => {
+ console.log("Selection done: " + JSON.stringify(selection));
+ this.imageSrc = this.isSingleMode && selection.length > 0 ? selection[0] : null;
+
+ // set the images to be loaded from the assets with optimal sizes (optimize memory usage)
+ selection.forEach(element => {
+ element.options.width = this.isSingleMode ? this.previewSize : this.thumbSize;
+ element.options.height = this.isSingleMode ? this.previewSize : this.thumbSize;
+ });
+
+ this.imageAssets = selection;
+ }).catch(function (e) {
+ console.log(e);
+ });
+ }
+}
\ No newline at end of file
diff --git a/demo/e2e/test.e2e.ts b/demo/e2e/test.e2e.ts
index 4d4f592..7f46dca 100644
--- a/demo/e2e/test.e2e.ts
+++ b/demo/e2e/test.e2e.ts
@@ -9,7 +9,6 @@ describe("Imagepicker", async function () {
const imagesFolderName = "Images";
const imagesFolderNameIos = "Camera Roll";
const doneButtonText = "Done";
- let isOlderEmulator;
let driver: AppiumDriver;
before(async () => {
@@ -31,12 +30,7 @@ describe("Imagepicker", async function () {
//await driver.driver.resetApp();
const pickSingleButtonText = "Pick Single";
let confirmButtonText = isAndroid ? "Allow" : "OK";
- let uploadPicVerification;
- if (isAndroid) {
- uploadPicVerification = isSauceRun ? "sauce_logo.png" : "pic1.jpeg";
- } else {
- uploadPicVerification = "IMG_0001.JPG";
- }
+ let uploadPicVerification = "image 0";
const pickSingleButton = await driver.findElementByText(pickSingleButtonText, SearchOptions.contains);
await pickSingleButton.click();
@@ -49,7 +43,6 @@ describe("Imagepicker", async function () {
const imagesFolder = await driver.driver.elementByXPathIfExists(imagesFolderXpath, 10000);
if (isSauceRun && imagesFolder) {
- isOlderEmulator = true;
await imagesFolder.click();
const downloadFolder = await driver.findElementByClassName(driver.locators.image);
await downloadFolder.click();
@@ -62,27 +55,14 @@ describe("Imagepicker", async function () {
const pickedImage = await driver.findElementByClassName(driver.locators.image);
await pickedImage.click();
- if (!isAndroid) {
- const doneButton = await driver.findElementByText(doneButtonText);
- await doneButton.click();
- }
-
const result = await driver.findElementByText(uploadPicVerification, SearchOptions.contains);
expect(result).to.exist;
});
it("should pick multiple images", async function () {
let openImagesButtonText = isAndroid ? "Open" : doneButtonText;
- let uploadPicVerification;
- let uploadPicVerification2;
-
- if (isAndroid) {
- uploadPicVerification = isSauceRun ? "sauce_logo_red.png" : "pic2.jpeg";
- uploadPicVerification2 = isSauceRun ? "sauce_logo.png" : "pic3.jpeg";
- } else {
- uploadPicVerification = "IMG_0001.JPG";
- uploadPicVerification2 = "IMG_0002.JPG";
- }
+ let uploadPicVerification = "image 0";
+ let uploadPicVerification2 = "image 1";
const pickMultipleButtonText = "Pick Multiple";
const pickMultipleButton = await driver.findElementByText(pickMultipleButtonText, SearchOptions.contains);
@@ -107,11 +87,6 @@ describe("Imagepicker", async function () {
await openImagesButton.click();
const img = await driver.findElementByText(uploadPicVerification, SearchOptions.contains);
expect(img).to.exist;
-
- if(isOlderEmulator){
- uploadPicVerification2 = "saucelabs_sauce.png";
- }
-
const img1 = await driver.findElementByText(uploadPicVerification2, SearchOptions.contains);
expect(img1).to.exist;
});
diff --git a/demo/package.json b/demo/package.json
index 6c0c408..328d4fb 100644
--- a/demo/package.json
+++ b/demo/package.json
@@ -2,17 +2,17 @@
"nativescript": {
"id": "org.nativescript.imagepickerdemo",
"tns-android": {
- "version": "3.4.0"
+ "version": "4.0.0"
},
"tns-ios": {
- "version": "3.4.0"
+ "version": "4.0.1"
}
},
"dependencies": {
"nativescript-imagepicker": "../src",
"nativescript-theme-core": "^1.0.4",
"nativescript-unit-test-runner": "^0.3.4",
- "tns-core-modules": "^3.4.0"
+ "tns-core-modules": "^4.0.0"
},
"devDependencies": {
"@types/chai": "^4.0.2",
@@ -39,7 +39,7 @@
"nativescript-css-loader": "~0.26.0",
"nativescript-dev-appium": "3.1.0",
"nativescript-dev-typescript": "^0.6.0",
- "nativescript-dev-webpack": "^0.9.0",
+ "nativescript-dev-webpack": "^0.10.1",
"nativescript-worker-loader": "~0.8.1",
"raw-loader": "~0.5.1",
"resolve-url-loader": "~2.1.0",
@@ -48,7 +48,8 @@
"typescript": "~2.5.0",
"webpack": "~3.8.1",
"webpack-bundle-analyzer": "^2.8.2",
- "webpack-sources": "~1.0.1"
+ "webpack-sources": "~1.0.1",
+ "uglifyjs-webpack-plugin": "~1.1.6"
},
"scripts": {
"build.plugin": "cd ../src && npm run build",
diff --git a/src/albums.ios.d.ts b/src/albums.ios.d.ts
deleted file mode 100644
index 5846044..0000000
--- a/src/albums.ios.d.ts
+++ /dev/null
@@ -1,6 +0,0 @@
-import { Page } from "tns-core-modules/ui/page";
-export declare function onAlbumsItemTap(args: any): void;
-export declare function pageLoaded(args: any): void;
-export declare function navigatedFrom(args: any): void;
-export declare function done(args: any): void;
-export declare function albumsPageFactory(): Page;
diff --git a/src/albums.ios.ts b/src/albums.ios.ts
deleted file mode 100644
index e80f1be..0000000
--- a/src/albums.ios.ts
+++ /dev/null
@@ -1,106 +0,0 @@
-// Apple example: https://developer.apple.com/library/ios/samplecode/UsingPhotosFramework/Listings/SamplePhotosApp_AAPLRootListViewController_m.html
-import data_observable = require("tns-core-modules/data/observable");
-import data_observablearray = require("tns-core-modules/data/observable-array");
-
-import ui_frame = require("tns-core-modules/ui/frame");
-import { Page } from "tns-core-modules/ui/page";
-import { ActionBar, NavigationButton, ActionItems, ActionItem } from "tns-core-modules/ui/action-bar";
-import { ListView } from "tns-core-modules/ui/list-view";
-import { Label } from "tns-core-modules/ui/label";
-
-import { ImagePicker } from "./imagepicker.ios";
-
-let imagesModule;
-
-if (global.TNS_WEBPACK) {
- imagesModule = require("./images.ios");
-
- require("bundle-entry-points");
-} else {
- imagesModule = require("./images");
-}
-
-let page;
-let goingToAlbum: boolean = false;
-
-export function onAlbumsItemTap(args) {
- let list = args.object;
- let topmost = ui_frame.topmost();
- goingToAlbum = true;
- topmost.navigate({
- create: imagesModule.imagesPageFactory,
- context: list.items.getItem(args.index)
- });
-}
-
-export function pageLoaded(args) {
- page = args.object;
- let list = page.getViewById("albums-list");
-
- list.on(ListView.itemLoadingEvent, function(args) {
- if (args.ios) {
- args.ios.accessoryType = UITableViewCellAccessoryType.DisclosureIndicator;
- }
- });
-
- if (page.navigationContext) {
- page.bindingContext = page.navigationContext;
- }
-}
-
-export function navigatedFrom(args) {
- if (!goingToAlbum) {
- page.bindingContext.cancel();
- }
- goingToAlbum = false;
-}
-
-export function done(args) {
- let topmost = ui_frame.topmost();
- topmost.goBack();
- page.bindingContext.done();
-}
-
-export function albumsPageFactory(): Page {
- //
- let page = new Page();
- page.on(Page.loadedEvent, pageLoaded);
- page.on(Page.navigatedFromEvent, navigatedFrom);
-
- //
- let actionBar = new ActionBar();
- actionBar.bind({ targetProperty: "title", sourceProperty: "albumsText", twoWay: false });
- //
- //
- //
- let navigationButton = new NavigationButton();
- navigationButton.bind({ targetProperty: "text", sourceProperty: "cancelText", twoWay: false });
- actionBar.navigationButton = navigationButton;
- //
- //
- //
- //
- let actionItems = new ActionItems();
- let item = new ActionItem();
- item.bind({ targetProperty: "text", sourceProperty: "selection.length", twoWay: false,
- expression: "doneText + (mode === 'single' ? '' : ' (' + selection.length + ')')" });
- item.ios.position = "right";
- item.on("tap", done);
- actionBar.actionItems.addItem(item);
- page.actionBar = actionBar;
-
- //
- let listView = new ListView();
- listView.id = "albums-list";
- listView.bind({ targetProperty: "items", sourceProperty: "albums", twoWay: false });
- listView.on(ListView.itemTapEvent, onAlbumsItemTap);
- listView.itemTemplate = " \
- \
- \
- \
- ";
-
- page.content = listView;
-
- return page;
-}
diff --git a/src/albums.xml b/src/albums.xml
deleted file mode 100644
index ff6c8b4..0000000
--- a/src/albums.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/src/imagepicker.android.ts b/src/imagepicker.android.ts
index 4c57629..1c1ab94 100644
--- a/src/imagepicker.android.ts
+++ b/src/imagepicker.android.ts
@@ -1,74 +1,9 @@
-import * as observable from "tns-core-modules/data/observable";
-import * as imagesource from "tns-core-modules/image-source";
import * as application from "tns-core-modules/application";
-import * as platform from "tns-core-modules/platform";
import * as imageAssetModule from "tns-core-modules/image-asset";
import * as permissions from "nativescript-permissions";
-interface ArrayBufferStatic extends ArrayBufferConstructor {
- from(buffer: java.nio.ByteBuffer): ArrayBuffer;
-}
-
-export class SelectedAsset extends imageAssetModule.ImageAsset {
- private _uri: android.net.Uri;
- private _thumbAsset: imageAssetModule.ImageAsset;
- private _fileUri: string;
- private _data: ArrayBuffer;
-
- constructor(uri: android.net.Uri) {
- super(SelectedAsset._calculateFileUri(uri));
- this._uri = uri;
- }
-
- data(): Promise {
- return Promise.reject(new Error("Not implemented."));
- }
-
- getImage(options?: { maxWidth: number, maxHeight: number }): Promise {
- return new Promise((resolve, reject) => {
- try {
- resolve(this.decodeUri(this._uri, options));
- } catch (ex) {
- reject(ex);
- }
- });
- }
-
- getImageData(): Promise {
- return new Promise((resolve, reject) => {
- try {
- if (!this._data) {
- let bb = this.getByteBuffer(this._uri);
- this._data = (ArrayBuffer).from(bb);
- }
- resolve(this._data);
- } catch (ex) {
- reject(ex);
- }
- });
- }
-
- get thumbAsset(): imageAssetModule.ImageAsset {
- return this._thumbAsset;
- }
-
- protected setThumbAsset(value: imageAssetModule.ImageAsset): void {
- this._thumbAsset = value;
- this.notifyPropertyChange("thumbAsset", value);
- }
-
- get uri(): string {
- return this._uri.toString();
- }
-
- get fileUri(): string {
- if (!this._fileUri) {
- this._fileUri = SelectedAsset._calculateFileUri(this._uri);
- }
- return this._fileUri;
- }
-
- private static _calculateFileUri(uri: android.net.Uri) {
+class UriHelper {
+ public static _calculateFileUri(uri: android.net.Uri) {
let DocumentsContract = (android.provider).DocumentsContract;
let isKitKat = android.os.Build.VERSION.SDK_INT >= 19; // android.os.Build.VERSION_CODES.KITKAT
@@ -77,7 +12,7 @@ export class SelectedAsset extends imageAssetModule.ImageAsset {
let contentUri: android.net.Uri = null;
// ExternalStorageProvider
- if (SelectedAsset.isExternalStorageDocument(uri)) {
+ if (UriHelper.isExternalStorageDocument(uri)) {
docId = DocumentsContract.getDocumentId(uri);
id = docId.split(":")[1];
type = docId.split(":")[0];
@@ -89,15 +24,15 @@ export class SelectedAsset extends imageAssetModule.ImageAsset {
// TODO handle non-primary volumes
}
// DownloadsProvider
- else if (SelectedAsset.isDownloadsDocument(uri)) {
+ else if (UriHelper.isDownloadsDocument(uri)) {
id = DocumentsContract.getDocumentId(uri);
contentUri = android.content.ContentUris.withAppendedId(
android.net.Uri.parse("content://downloads/public_downloads"), long(id));
- return SelectedAsset.getDataColumn(contentUri, null, null);
+ return UriHelper.getDataColumn(contentUri, null, null);
}
// MediaProvider
- else if (SelectedAsset.isMediaDocument(uri)) {
+ else if (UriHelper.isMediaDocument(uri)) {
docId = DocumentsContract.getDocumentId(uri);
let split = docId.split(":");
type = split[0];
@@ -114,13 +49,13 @@ export class SelectedAsset extends imageAssetModule.ImageAsset {
let selection = "_id=?";
let selectionArgs = [id];
- return SelectedAsset.getDataColumn(contentUri, selection, selectionArgs);
+ return UriHelper.getDataColumn(contentUri, selection, selectionArgs);
}
}
else {
// MediaStore (and general)
if ("content" === uri.getScheme()) {
- return SelectedAsset.getDataColumn(uri, null, null);
+ return UriHelper.getDataColumn(uri, null, null);
}
// FILE
else if ("file" === uri.getScheme()) {
@@ -132,7 +67,6 @@ export class SelectedAsset extends imageAssetModule.ImageAsset {
}
private static getDataColumn(uri: android.net.Uri, selection, selectionArgs) {
-
let cursor = null;
let columns = [android.provider.MediaStore.MediaColumns.DATA];
let filePath;
@@ -171,111 +105,6 @@ export class SelectedAsset extends imageAssetModule.ImageAsset {
return "com.android.providers.media.documents" === uri.getAuthority();
}
- private decodeThumbAssetUri(): void {
- // Decode image size
- let REQUIRED_SIZE = {
- maxWidth: 100,
- maxHeight: 100
- };
-
- // Decode with scale
- this._thumbAsset = this.decodeUriForImageAsset(this._uri, REQUIRED_SIZE);
- this.notifyPropertyChange("thumbAsset", this._thumbAsset);
- }
-
- /**
- * Discovers the sample size that a BitmapFactory.Options object should have
- * to scale the retrieved image to the given max size.
- * @param uri The URI of the image that should be scaled.
- * @param options The options that should be used to produce the correct image scale.
- */
- private getSampleSize(uri: android.net.Uri, options?: { maxWidth: number, maxHeight: number }): number {
- let boundsOptions = new android.graphics.BitmapFactory.Options();
- boundsOptions.inJustDecodeBounds = true;
- android.graphics.BitmapFactory.decodeStream(this.openInputStream(uri), null, boundsOptions);
-
- // Find the correct scale value. It should be the power of 2.
- let outWidth = boundsOptions.outWidth;
- let outHeight = boundsOptions.outHeight;
- let scale = 1;
- if (options) {
- // TODO: Refactor to accomodate different scaling options
- // Right now, it just selects the smallest of the two sizes
- // and scales the image proportionally to that.
- let targetSize = !options.maxWidth && options.maxHeight ? options.maxHeight :
- (!options.maxHeight && options.maxWidth ? options.maxWidth :
- (options.maxWidth < options.maxHeight ? options.maxWidth : options.maxHeight));
- if (targetSize) {
- while (!(this.matchesSize(targetSize, outWidth) ||
- this.matchesSize(targetSize, outHeight))) {
- outWidth /= 2;
- outHeight /= 2;
- scale *= 2;
- }
- }
- }
- return scale;
- }
-
- private matchesSize(targetSize: number, actualSize: number): boolean {
- return targetSize && actualSize / 2 < targetSize;
- }
-
- /**
- * Decodes the given URI using the given options.
- * @param uri The URI that should be decoded into an ImageSource.
- * @param options The options that should be used to decode the image.
- */
- private decodeUri(uri: android.net.Uri, options?: { maxWidth: number, maxHeight: number }): imagesource.ImageSource {
- let downsampleOptions = new android.graphics.BitmapFactory.Options();
- downsampleOptions.inSampleSize = this.getSampleSize(uri, options);
- let bitmap = android.graphics.BitmapFactory.decodeStream(this.openInputStream(uri), null, downsampleOptions);
- let image = new imagesource.ImageSource();
- image.setNativeSource(bitmap);
- return image;
- }
-
- /**
- * Decodes the given URI using the given options.
- * @param uri The URI that should be decoded into an ImageAsset.
- * @param options The options that should be used to decode the image.
- */
- private decodeUriForImageAsset(uri: android.net.Uri, options?: { maxWidth: number, maxHeight: number }): imageAssetModule.ImageAsset {
- let downsampleOptions = new android.graphics.BitmapFactory.Options();
- downsampleOptions.inSampleSize = this.getSampleSize(uri, options);
- let bitmap = android.graphics.BitmapFactory.decodeStream(this.openInputStream(uri), null, downsampleOptions);
- return new imageAssetModule.ImageAsset(bitmap);
- }
-
- /**
- * Retrieves the raw data of the given file and exposes it as a byte buffer.
- */
- private getByteBuffer(uri: android.net.Uri): java.nio.ByteBuffer {
- let file: android.content.res.AssetFileDescriptor = null;
- try {
- file = SelectedAsset.getContentResolver().openAssetFileDescriptor(uri, "r");
-
- // Determine how many bytes to allocate in memory based on the file length
- let length: number = file.getLength();
- let buffer: java.nio.ByteBuffer = java.nio.ByteBuffer.allocateDirect(length);
- let bytes = buffer.array();
- let stream = file.createInputStream();
-
- // Buffer the data in 4KiB amounts
- let reader = new java.io.BufferedInputStream(stream, 4096);
- reader.read(bytes, 0, bytes.length);
- return buffer;
- } finally {
- if (file) {
- file.close();
- }
- }
- }
-
- private openInputStream(uri: android.net.Uri): java.io.InputStream {
- return SelectedAsset.getContentResolver().openInputStream(uri);
- }
-
private static getContentResolver(): android.content.ContentResolver {
return application.android.nativeApp.getContentResolver();
}
@@ -300,7 +129,7 @@ export class ImagePicker {
}
}
- present(): Promise {
+ present(): Promise {
return new Promise((resolve, reject) => {
// WARNING: If we want to support multiple pickers we will need to have a range of IDs here:
@@ -329,13 +158,15 @@ export class ImagePicker {
if (clipItem) {
let uri = clipItem.getUri();
if (uri) {
- results.push(new SelectedAsset(uri));
+ let selectedAsset = new imageAssetModule.ImageAsset(UriHelper._calculateFileUri(uri));
+ results.push(selectedAsset);
}
}
}
} else {
let uri = data.getData();
- results.push(new SelectedAsset(uri));
+ let selectedAsset = new imageAssetModule.ImageAsset(UriHelper._calculateFileUri(uri));
+ results.push(selectedAsset);
}
application.android.off(application.AndroidApplication.activityResultEvent, onResult);
diff --git a/src/imagepicker.ios.ts b/src/imagepicker.ios.ts
index debd108..d23ccd8 100644
--- a/src/imagepicker.ios.ts
+++ b/src/imagepicker.ios.ts
@@ -1,245 +1,56 @@
import * as data_observable from "tns-core-modules/data/observable";
-import * as data_observablearray from "tns-core-modules/data/observable-array";
import * as frame from "tns-core-modules/ui/frame";
import * as imageAssetModule from "tns-core-modules/image-asset";
-import * as image_source from "tns-core-modules/image-source";
-
-let albumsModule;
-if (global.TNS_WEBPACK) {
- albumsModule = require("./albums.ios");
-
- require("bundle-entry-points");
-} else {
- albumsModule = require("./albums");
-}
-
-const IMAGE_WIDTH = 80;
-const IMAGE_HEIGHT = 80;
-
-interface ImageOptions {
- maxWidth?: number;
- maxHeight?: number;
- aspectRatio?: "fill" | "fit";
-}
-
-export function create(options?): ImagePicker {
- return new ImagePickerPH(options);
-}
+import { Options, ImagePickerMediaType } from ".";
+import { View } from "tns-core-modules/ui/core/view/view";
+
+const defaultAssetCollectionSubtypes: NSArray = NSArray.arrayWithArray([
+ PHAssetCollectionSubtype.SmartAlbumRecentlyAdded,
+ PHAssetCollectionSubtype.SmartAlbumUserLibrary,
+ PHAssetCollectionSubtype.AlbumMyPhotoStream,
+ PHAssetCollectionSubtype.SmartAlbumFavorites,
+ PHAssetCollectionSubtype.SmartAlbumPanoramas,
+ PHAssetCollectionSubtype.SmartAlbumBursts,
+ PHAssetCollectionSubtype.AlbumCloudShared,
+ PHAssetCollectionSubtype.SmartAlbumSelfPortraits,
+ PHAssetCollectionSubtype.SmartAlbumScreenshots,
+ PHAssetCollectionSubtype.SmartAlbumLivePhotos
+]);
export class ImagePicker extends data_observable.Observable {
- private _selection: data_observablearray.ObservableArray;
- private _albums: data_observablearray.ObservableArray;
-
- private _resolve;
- private _reject;
-
- protected _options;
+ _imagePickerController: QBImagePickerController;
+ _imagePickerControllerDelegate: ImagePickerControllerDelegate;
+ _hostView: View;
- constructor(options) {
- super();
- this._selection = new data_observablearray.ObservableArray();
- this._albums = new data_observablearray.ObservableArray();
- this._options = options;
+ // lazy-load latest frame.topmost() if _hostName is not used
+ get hostView() {
+ return this._hostView || frame.topmost();
}
- authorize(): Promise {
- return Promise.reject(new Error("Not implemented"));
- }
-
- present(): Promise {
- if (this._resolve || this._reject) {
- return Promise.reject(new Error("Selection is already in progress..."));
- } else {
- return new Promise((resolve, reject) => {
- this._resolve = resolve;
- this._reject = reject;
- frame.topmost().navigate({
- create: albumsModule.albumsPageFactory,
- context: this
- });
- });
- }
- }
-
- get albums(): data_observablearray.ObservableArray {
- return this._albums;
- }
-
- get selection(): data_observablearray.ObservableArray {
- return this._selection;
- }
-
- get doneText(): string {
- return this._options && this._options.doneText ? this._options.doneText : "Done";
- }
-
- get cancelText(): string {
- return this._options && this._options.cancelText ? this._options.cancelText : "Cancel";
- }
-
- get albumsText(): string {
- return this._options && this._options.albumsText ? this._options.albumsText : "Albums";
- }
-
- get mode(): string {
- return this._options && this._options.mode && this._options.mode.toLowerCase() === 'single' ? 'single' : 'multiple';
- }
-
- get newestFirst(): boolean {
- return this._options && !!this._options.newestFirst;
- }
-
- cancel(): void {
- this.notifyCanceled();
- }
-
- done(): void {
- this.notifySelection([]);
- }
-
- protected notifyCanceled(): void {
- if (this._reject) {
- this._reject(new Error("Selection canceled."));
- }
- }
-
- protected notifySelection(results: SelectedAsset[]): void {
- if (this._resolve) {
- this._resolve(results);
- }
- }
-}
-
-export class Album extends data_observable.Observable {
-
- private _imagePicker: ImagePicker;
- private _assets: data_observablearray.ObservableArray;
- private _title: string;
- private _thumbAsset: imageAssetModule.ImageAsset;
-
- constructor(imagePicker: ImagePicker, title: string) {
+ constructor(options: Options = {}, hostView: View) {
super();
- this._imagePicker = imagePicker;
- this._title = title;
- this._assets = new data_observablearray.ObservableArray();
- }
-
- get imagePicker(): ImagePicker {
- return this._imagePicker;
- }
-
- get title(): string {
- return this._title;
- }
-
- get assets(): data_observablearray.ObservableArray {
- return this._assets;
- }
-
- get thumbAsset(): imageAssetModule.ImageAsset {
- return this._thumbAsset;
- }
-
- protected setThumbAsset(value: imageAssetModule.ImageAsset): void {
- this._thumbAsset = value;
- this.notifyPropertyChange("thumbAsset", value);
- }
-}
-
-export class SelectedAsset extends imageAssetModule.ImageAsset {
- get uri(): string {
- return null;
- }
-
- get fileUri(): string {
- return null;
- }
-
- getImage(options?: ImageOptions): Promise {
- return Promise.reject(new Error("getImage() is not implemented in SelectedAsset. Derived classes should implement it to be fully functional."));
- }
-
- getImageData(): Promise {
- return Promise.reject(new Error("getImageData() is not implemented in SelectedAsset. Derived classes should implement it to be fully functional."));
- }
-}
-export class Asset extends SelectedAsset {
- private _selected: boolean;
- private _album: Album;
+ this._hostView = hostView;
+ this._imagePickerControllerDelegate = new ImagePickerControllerDelegate();
- private _image: image_source.ImageSource;
+ let imagePickerController = QBImagePickerController.alloc().init();
+ imagePickerController.assetCollectionSubtypes = defaultAssetCollectionSubtypes;
+ imagePickerController.mediaType = options.mediaType ? options.mediaType.valueOf() : QBImagePickerMediaType.Any;
+ imagePickerController.delegate = this._imagePickerControllerDelegate;
+ imagePickerController.allowsMultipleSelection = options.mode === 'multiple';
+ imagePickerController.minimumNumberOfSelection = options.minimumNumberOfSelection || 0;
+ imagePickerController.maximumNumberOfSelection = options.maximumNumberOfSelection || 0;
+ imagePickerController.showsNumberOfSelectedAssets = options.showsNumberOfSelectedAssets || true;
+ imagePickerController.numberOfColumnsInPortrait = options.numberOfColumnsInPortrait || imagePickerController.numberOfColumnsInPortrait;
+ imagePickerController.numberOfColumnsInLandscape = options.numberOfColumnsInLandscape || imagePickerController.numberOfColumnsInLandscape;
+ imagePickerController.prompt = options.prompt || imagePickerController.prompt;
- constructor(album: Album, asset: PHAsset | UIImage) {
- super(asset);
- this._album = album;
- this._image = null;
- }
-
- get album(): Album {
- return this._album;
- }
-
- get selected(): boolean {
- return !!this._selected;
- }
-
- set selected(value: boolean) {
- if (!!value === this.selected) return;
- let index = this.album.imagePicker.selection.indexOf(this);
- if (value) {
- this._selected = true;
- if (this.album.imagePicker.mode === "single") {
- if (this.album.imagePicker.selection.length > 0) {
- this.album.imagePicker.selection.getItem(0).selected = false;
- }
- }
- if (index < 0) {
- this.album.imagePicker.selection.push(this);
- }
- } else {
- delete this._selected;
- if (index >= 0) {
- this.album.imagePicker.selection.splice(index, 1);
- }
- }
-
- this.notifyPropertyChange("selected", this.selected);
- }
-
- toggleSelection(args): void {
- this.selected = !this.selected;
- }
-
- data(): Promise {
- return Promise.reject(new Error("Not implemented."));
- }
-}
-
-// iOS8+ Photo framework based view model implementation...
-class ImagePickerPH extends ImagePicker {
-
- private _thumbRequestOptions: PHImageRequestOptions;
- private _thumbRequestSize: CGSize;
- private _initialized: boolean;
-
- constructor(options) {
- super(options);
-
- this._thumbRequestOptions = PHImageRequestOptions.alloc().init();
- this._thumbRequestOptions.resizeMode = PHImageRequestOptionsResizeMode.Exact;
- this._thumbRequestOptions.synchronous = false;
- this._thumbRequestOptions.deliveryMode = PHImageRequestOptionsDeliveryMode.Opportunistic;
- this._thumbRequestOptions.networkAccessAllowed = true; // needed for retrieving iCloud images
- this._thumbRequestOptions.normalizedCropRect = CGRectMake(0, 0, 1, 1);
-
- this._thumbRequestSize = CGSizeMake(80, 80);
- this._options = options;
-
- this._initialized = false;
+ this._imagePickerController = imagePickerController;
}
authorize(): Promise {
+ console.log("authorizing...");
+
return new Promise((resolve, reject) => {
let runloop = CFRunLoopGetCurrent();
PHPhotoLibrary.requestAuthorization(function (result) {
@@ -252,219 +63,49 @@ class ImagePickerPH extends ImagePicker {
});
}
- present(): Promise {
- this.initialize();
- return super.present();
- }
-
- addAlbumsForFetchResult(result: PHFetchResult): void {
- for (let i = 0; i < result.count; i++) {
- let item = result.objectAtIndex(i);
- if (item.isKindOfClass(PHAssetCollection)) {
- this.addAlbumForAssetCollection(item);
- } else {
- console.log("Ignored result: " + item);
- }
- }
- }
-
- addAlbumForAssetCollection(assetCollection: PHAssetCollection): void {
- let album = new AlbumPH(this, assetCollection.localizedTitle);
- let pfAssets = PHAsset.fetchAssetsInAssetCollectionOptions(assetCollection, null);
- album.addAssetsForFetchResult(pfAssets);
- if (album.assets.length > 0) {
- this.albums.push(album);
- }
- }
-
- createPHImageThumbAsset(target, asset: PHAsset): void {
- PHImageManager.defaultManager().requestImageForAssetTargetSizeContentModeOptionsResultHandler(asset, this._thumbRequestSize, PHImageContentMode.AspectFill,
- this._thumbRequestOptions, function (target, uiImage, info) {
- let imageAsset = new imageAssetModule.ImageAsset(uiImage);
- imageAsset.options = {
- width: this._options.maxWidth && this._options.maxWidth < IMAGE_WIDTH ? this._options.maxWidth : IMAGE_WIDTH,
- height: this._options.maxHeight && this._options.IMAGE_HEIGHT < 80 ? this._options.IMAGE_HEIGHT : IMAGE_HEIGHT,
- keepAspectRatio: true
- };
- target.setThumbAsset(imageAsset);
- }.bind(this, target));
- }
-
- /**
- * Creates a new ImageSource from the given image, using the given sizing options.
- * @param image The image asset that should be put into an ImageSource.
- * @param options The options that should be used to create the ImageSource.
- */
- createPHImage(image: PHAsset, options?: ImageOptions): Promise {
- return new Promise((resolve, reject) => {
- let size: CGSize = options ? CGSizeMake(options.maxWidth, options.maxHeight) : PHImageManagerMaximumSize;
- let resizeMode = PHImageRequestOptions.alloc().init();
- let aspectRatio = (options && options.aspectRatio && options.aspectRatio === 'fill') ? PHImageContentMode.AspectFill : PHImageContentMode.AspectFit;
-
- // TODO: Decide whether it is benefical to use PHImageRequestOptionsResizeModeFast
- // Accuracy vs Performance. It is probably best to expose these as iOS specific options.
- resizeMode.resizeMode = PHImageRequestOptionsResizeMode.Exact;
- resizeMode.synchronous = false;
-
- // TODO: provide the ability to change this setting.
- // Right now, it is needed to make sure that resolve is not called twice.
- resizeMode.deliveryMode = PHImageRequestOptionsDeliveryMode.HighQualityFormat;
- resizeMode.normalizedCropRect = CGRectMake(0, 0, 1, 1);
- PHImageManager.defaultManager().requestImageForAssetTargetSizeContentModeOptionsResultHandler(
- image,
- size,
- aspectRatio,
- resizeMode,
- (createdImage, data) => {
- if (createdImage) {
- let imageSource = new image_source.ImageSource();
- imageSource.setNativeSource(createdImage);
+ present() {
+ return new Promise((resolve, reject) => {
+ this._imagePickerControllerDelegate._resolve = resolve;
- // TODO: Determine whether runOnRunLoop is needed
- // for callback or not. (See the data() implementation in AssetPH below)
- resolve(imageSource);
- } else {
- reject(new Error("The image could not be created."));
- }
- }
- );
+ (this.hostView).viewController.presentViewControllerAnimatedCompletion(this._imagePickerController, true, null);
});
}
-
- done(): void {
- let result = [];
- for (let i = 0; i < this.selection.length; ++i) {
- result.push(this.selection.getItem(i));
- }
- this.notifySelection(result);
- }
-
- private initialize(): void {
- if (this._initialized) {
- return;
- }
-
- this._initialized = true;
-
- let smart = PHAssetCollection.fetchAssetCollectionsWithTypeSubtypeOptions(PHAssetCollectionType.SmartAlbum, PHAssetCollectionSubtype.AlbumRegular, null);
- this.addAlbumsForFetchResult(smart);
-
- let user = PHCollection.fetchTopLevelUserCollectionsWithOptions(null);
- this.addAlbumsForFetchResult(user);
- }
}
-class AlbumPH extends Album {
- private _setThumb: boolean;
- private _options: ImageOptions;
-
- constructor(imagePicker: ImagePicker, title: string, options?: ImageOptions) {
- super(imagePicker, title);
- this._setThumb = false;
- this._options = options;
- }
-
- addAssetsForFetchResult(result: PHFetchResult): void {
- for (let i = 0; i < result.count; i++) {
- let asset = result.objectAtIndex(i);
- if (asset.isKindOfClass(PHAsset)) {
- this.addAsset(asset);
- } else {
- console.log("Ignored asset: " + asset);
- }
- }
- }
-
- addAsset(asset: PHAsset): void {
- let imagePicker = this.imagePicker;
- let item = new AssetPH(this, asset, this._options);
-
- if (!this._setThumb && imagePicker) {
- this._setThumb = true;
- imagePicker.createPHImageThumbAsset(this, asset);
+export class ImagePickerControllerDelegate extends NSObject implements QBImagePickerControllerDelegate {
+ _resolve: any;
- }
- if (this.imagePicker.newestFirst) {
- this.assets.unshift(item);
- } else {
- this.assets.push(item);
- }
+ qb_imagePickerControllerDidCancel?(imagePickerController: QBImagePickerController): void {
+ imagePickerController.dismissViewControllerAnimatedCompletion(true, null);
}
-}
-
-class AssetPH extends Asset {
- private _phAsset: PHAsset;
- private static _uriRequestOptions: PHImageRequestOptions;
- constructor(album: AlbumPH, phAsset: PHAsset, options?: ImageOptions) {
- super(album, phAsset);
- this._phAsset = phAsset;
- this._initializeOptions(options);
- }
+ qb_imagePickerControllerDidFinishPickingAssets?(imagePickerController: QBImagePickerController, iosAssets: NSArray): void {
+ let assets = [];
- /**
- * Gets the underlying iOS PHAsset instance.
- */
- public get ios(): any {
- return this._phAsset;
- }
+ for (let i = 0; i < iosAssets.count; i++) {
+ let asset = new imageAssetModule.ImageAsset(iosAssets[i]);
- get uri(): string {
- return this._phAsset.localIdentifier.toString();
- }
+ // this fixes the image aspect ratio in tns-core-modules version < 4.0
+ if (!asset.options) {
+ asset.options = { keepAspectRatio: true };
+ }
- private _initializeOptions(options: ImageOptions): void {
- if (options) {
- this.options = {
- width: options.maxWidth && options.maxWidth < IMAGE_WIDTH ? options.maxWidth : IMAGE_WIDTH,
- height: options.maxHeight && options.maxHeight < IMAGE_HEIGHT ? options.maxHeight : IMAGE_HEIGHT,
- keepAspectRatio: true
- };
- } else {
- this.options = {
- width: IMAGE_WIDTH,
- height: IMAGE_HEIGHT,
- keepAspectRatio: true
- };
+ assets.push(asset);
}
- }
- getImage(options?: ImageOptions): Promise {
- return ((this.album).imagePicker).createPHImage(this._phAsset, options);
- }
+ this._resolve(assets);
- getImageData(): Promise {
- return this.data().then((data: NSData) => {
- return (interop).bufferFromData(data);
- });
+ imagePickerController.dismissViewControllerAnimatedCompletion(true, null);
}
- get fileUri(): string {
- if (!AssetPH._uriRequestOptions) {
- AssetPH._uriRequestOptions = PHImageRequestOptions.alloc().init();
- AssetPH._uriRequestOptions.synchronous = true;
- }
+ public static ObjCProtocols = [QBImagePickerControllerDelegate];
- let uri;
- PHImageManager.defaultManager().requestImageDataForAssetOptionsResultHandler(this._phAsset, AssetPH._uriRequestOptions, (data, uti, orientation, info) => {
- uri = info.objectForKey("PHImageFileURLKey");
- });
- if (uri) {
- return uri.toString();
- }
- return undefined;
+ static new(): ImagePickerControllerDelegate {
+ return super.new(); // calls new() on the NSObject
}
+}
- data(): Promise {
- return new Promise((resolve, reject) => {
- let runloop = CFRunLoopGetCurrent();
- PHImageManager.defaultManager().requestImageDataForAssetOptionsResultHandler(this._phAsset, null, (data, dataUTI, orientation, info) => {
- if (data) {
- resolve(data);
- } else {
- reject(new Error("Failed to get image data."));
- }
- });
- });
- }
+export function create(options?: Options, hostView?: View): ImagePicker {
+ return new ImagePicker(options, hostView);
}
+
diff --git a/src/images.ios.ts b/src/images.ios.ts
deleted file mode 100644
index 1fd5850..0000000
--- a/src/images.ios.ts
+++ /dev/null
@@ -1,85 +0,0 @@
-import application = require("tns-core-modules/application");
-import platform = require("tns-core-modules/platform");
-
-import ui_frame = require("tns-core-modules/ui/frame");
-import { Page } from "tns-core-modules/ui/page";
-import { ActionBar, NavigationButton, ActionItems, ActionItem } from "tns-core-modules/ui/action-bar";
-import { RadListView, ListViewGridLayout } from "nativescript-ui-listview";
-
-let page;
-let list;
-let album;
-
-export function pageLoaded(args) {
- page = args.object;
- page.bindingContext = page.navigationContext;
- list = page.getViewById("images-list");
-
- // Get the current Size, and then adjust the number of columns based on it...
- list.listViewLayout.spanCount = Math.floor(platform.screen.mainScreen.widthDIPs / 80);
-
- application.on("orientationChanged", function (e: application.OrientationChangedEventData) {
- let currentPageWidth = platform.screen.mainScreen.heightDIPs;
- list.listViewLayout.spanCount = Math.floor(currentPageWidth / 80);
- });
-}
-
-export function done(args) {
- let topmost = ui_frame.topmost();
- topmost.goBack();
- topmost.goBack();
-
- page.bindingContext.imagePicker.done();
-}
-
-export function imagesPageFactory(): Page {
- //
- let page = new Page();
- page.on(Page.loadedEvent, pageLoaded);
-
- //
- let actionBar = new ActionBar();
- actionBar.bind({ targetProperty: "title", sourceProperty: "title", twoWay: false });
- //
- //
- //
- let navigationButton = new NavigationButton();
- navigationButton.text = "Albums";
- actionBar.navigationButton = navigationButton;
- //
- //
- //
- let actionItems = new ActionItems();
- let item = new ActionItem();
- item.bind({
- targetProperty: "text", sourceProperty: "imagePicker.selection.length", twoWay: false,
- expression: "imagePicker.doneText + (imagePicker.mode === 'single' ? '' : ' (' + imagePicker.selection.length + ')')"
- });
- item.ios.position = "right";
- item.on("tap", done);
- actionBar.actionItems.addItem(item);
- page.actionBar = actionBar;
-
- //
- let listView = new RadListView();
- listView.id = "images-list";
- listView.bind({ targetProperty: "items", sourceProperty: "assets", twoWay: false });
- //
- //
- //
- let listViewGridLayout = new ListViewGridLayout();
- listViewGridLayout.scrollDirection = "Vertical";
- listViewGridLayout.spanCount = 4;
- listViewGridLayout.itemHeight = 80;
- listView.listViewLayout = listViewGridLayout;
- listView.itemTemplate =
- " \
- \
- ";
-
- page.content = listView;
-
- return page;
-}
diff --git a/src/images.xml b/src/images.xml
deleted file mode 100644
index 9402872..0000000
--- a/src/images.xml
+++ /dev/null
@@ -1,27 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/src/index.d.ts b/src/index.d.ts
index b355fc8..50cf59b 100644
--- a/src/index.d.ts
+++ b/src/index.d.ts
@@ -1,102 +1,71 @@
import { Observable } from "tns-core-modules/data/observable";
import { ImageSource } from "tns-core-modules/image-source";
import { ImageAsset } from "tns-core-modules/image-asset";
+import { View } from "tns-core-modules/ui/core/view/view";
-export interface ImageOptions {
- /**
- * The maximum width that the image is allowed to be.
- */
- maxWidth?: number;
-
+export class ImagePicker {
/**
- * The maximum height that the image is allowed to be.
+ * 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.
*/
- maxHeight?: number;
+ authorize(): Promise;
/**
- * iOS only. The image aspect ratio. Default value: fit.
+ * Present the image picker UI.
+ * The result will be an array of SelectedAsset instances provided when the promise is fulfilled.
*/
- aspectRatio?: "fill" | "fit";
+ present(): Promise;
}
-export class SelectedAsset extends ImageAsset {
- /**
- * A 100x100 pixels thumb of the selected image.
- * This property will be initialized on demand. The first access will return undefined or null.
- * It will trigger an async load and when the thumb is obtained, a property changed notification will occur.
- */
- thumb: ImageSource;
+export declare const enum ImagePickerMediaType {
+ Any = 0,
+ Image = 1,
+ Video = 2
+}
+/**
+ * Provide options for the image picker.
+ */
+interface Options {
/**
- * URI that identifies the image asset.
- * Chances are you do not have permissions to read this uri.
- * The image data should be obtained using the other instance members.
+ * Set the picker mode. Supported modes: "single" or "multiple" (default).
*/
- uri: string;
+ mode?: string;
/**
- * An URI that identifies the local asset file.
- * Chances are you do not have permissions to read this file.
- * The image data should be obtained using the other instance members.
- */
- fileUri: string;
+ * Set the minumum number of selected assets in iOS
+ */
+ minimumNumberOfSelection?: number;
/**
- * Asynchronously retrieves an ImageSource object that represents this selected image.
- * Scaled to the given size. (Aspect-ratio is preserved by default)
+ * Set the maximum number of selected assets in iOS
*/
- getImage(options?: ImageOptions): Promise;
+ maximumNumberOfSelection?: number;
/**
- * Asynchronously retrieves an ArrayBuffer that represents the raw byte data from this selected image.
+ * Display the number of selected assets in iOS
*/
- getImageData(): Promise;
+ showsNumberOfSelectedAssets?: boolean;
/**
- * For iOS Returns a promise with NSData representation of the asset.
- * On Android, Returns a promise with a java.io.InputStream.
- * Note that in future versions it should return ArrayBuffer.
+ * Display prompt text when selecting assets in iOS
*/
- data(): Promise;
-}
+ prompt?: string;
-export class ImagePicker {
/**
- * 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.
+ * Set the number of columns in Portrait in iOS
*/
- authorize(): Promise;
+ numberOfColumnsInPortrait?: number;
/**
- * Present the image picker UI.
- * The result will be an array of SelectedAsset instances provided when the promise is fulfilled.
+ * Set the number of columns in Landscape in iOS
*/
- present(): Promise;
-}
+ numberOfColumnsInLandscape?: number;
-/**
- * Provide options for the image picker.
- */
-interface Options {
/**
- * Set the picker mode. Supported modes: "single" or "multiple" (default).
+ * Set the media type (image/video/both) to pick in iOS
*/
- mode?: string;
-
- /**
- * Set the text for the done button in iOS
- */
- doneText?: string;
-
- /**
- * Set the text for the cancel button in iOS
- */
- cancelText?: string;
-
- /**
- * Set the text for the albums button in iOS
- */
- albumsText?: string;
+ mediaType?: ImagePickerMediaType;
android?: {
/**
@@ -104,11 +73,10 @@ interface Options {
*/
read_external_storage?: string;
};
-
- /**
- * Indicates images should be sorted newest-first (iOS only, default false).
- */
- newestFirst?: boolean;
}
-export function create(options?: Options): ImagePicker;
+/**
+ * @param {Options} [options] - options for the image picker.
+ * @param {View} [hostView] - [use in iOS] the view that hosts the image picker (e.g. to use when open from a modal page).
+ */
+export function create(options?: Options, hostView?: View): ImagePicker;
diff --git a/src/package.json b/src/package.json
index f8dbe77..75bd280 100644
--- a/src/package.json
+++ b/src/package.json
@@ -1,6 +1,6 @@
{
"name": "nativescript-imagepicker",
- "version": "5.0.2",
+ "version": "6.0.0",
"description": "A plugin for the NativeScript framework implementing multiple image picker",
"repository": {
"type": "git",
@@ -52,9 +52,7 @@
"tslint": "~5.4.3"
},
"dependencies": {
- "nativescript-ui-core": "^1.0.0",
- "nativescript-ui-listview": "^3.5.0",
"nativescript-permissions": "~1.2.3"
},
"bootstrapper": "nativescript-plugin-seed"
-}
\ No newline at end of file
+}
diff --git a/src/platforms/android/nativescript_imagepicker.aar b/src/platforms/android/nativescript_imagepicker.aar
new file mode 100644
index 0000000..6d54ca0
Binary files /dev/null and b/src/platforms/android/nativescript_imagepicker.aar differ
diff --git a/src/platforms/ios/Podfile b/src/platforms/ios/Podfile
new file mode 100644
index 0000000..e0d5c2c
--- /dev/null
+++ b/src/platforms/ios/Podfile
@@ -0,0 +1,3 @@
+platform :ios, '8.0'
+
+pod 'QBImagePickerController', '~> 3.4.0'
\ No newline at end of file
diff --git a/src/references.d.ts b/src/references.d.ts
index 1e5e961..0d40032 100644
--- a/src/references.d.ts
+++ b/src/references.d.ts
@@ -1,2 +1,3 @@
///
///
+///
\ No newline at end of file
diff --git a/src/typings/objc!QBImagePickerController.d.ts b/src/typings/objc!QBImagePickerController.d.ts
new file mode 100644
index 0000000..c51fc83
--- /dev/null
+++ b/src/typings/objc!QBImagePickerController.d.ts
@@ -0,0 +1,229 @@
+declare class QBAlbumCell extends UITableViewCell {
+ static alloc(): QBAlbumCell; // inherited from NSObject
+
+ static appearance(): QBAlbumCell; // inherited from UIAppearance
+
+ static appearanceForTraitCollection(trait: UITraitCollection): QBAlbumCell; // inherited from UIAppearance
+
+ static appearanceForTraitCollectionWhenContainedIn(trait: UITraitCollection, ContainerClass: typeof NSObject): QBAlbumCell; // inherited from UIAppearance
+
+ static appearanceForTraitCollectionWhenContainedInInstancesOfClasses(trait: UITraitCollection, containerTypes: NSArray): QBAlbumCell; // inherited from UIAppearance
+
+ static appearanceWhenContainedIn(ContainerClass: typeof NSObject): QBAlbumCell; // inherited from UIAppearance
+
+ static appearanceWhenContainedInInstancesOfClasses(containerTypes: NSArray): QBAlbumCell; // inherited from UIAppearance
+
+ static new(): QBAlbumCell; // inherited from NSObject
+
+ borderWidth: number;
+
+ countLabel: UILabel;
+
+ imageView1: UIImageView;
+
+ imageView2: UIImageView;
+
+ imageView3: UIImageView;
+
+ titleLabel: UILabel;
+}
+
+declare class QBAlbumsViewController extends UITableViewController {
+
+ static alloc(): QBAlbumsViewController; // inherited from NSObject
+
+ static new(): QBAlbumsViewController; // inherited from NSObject
+
+ imagePickerController: QBImagePickerController;
+}
+
+declare class QBAssetCell extends UICollectionViewCell {
+
+ static alloc(): QBAssetCell; // inherited from NSObject
+
+ static appearance(): QBAssetCell; // inherited from UIAppearance
+
+ static appearanceForTraitCollection(trait: UITraitCollection): QBAssetCell; // inherited from UIAppearance
+
+ static appearanceForTraitCollectionWhenContainedIn(trait: UITraitCollection, ContainerClass: typeof NSObject): QBAssetCell; // inherited from UIAppearance
+
+ static appearanceForTraitCollectionWhenContainedInInstancesOfClasses(trait: UITraitCollection, containerTypes: NSArray): QBAssetCell; // inherited from UIAppearance
+
+ static appearanceWhenContainedIn(ContainerClass: typeof NSObject): QBAssetCell; // inherited from UIAppearance
+
+ static appearanceWhenContainedInInstancesOfClasses(containerTypes: NSArray): QBAssetCell; // inherited from UIAppearance
+
+ static new(): QBAssetCell; // inherited from NSObject
+
+ imageView: UIImageView;
+
+ showsOverlayViewWhenSelected: boolean;
+
+ videoIndicatorView: QBVideoIndicatorView;
+}
+
+declare class QBAssetsViewController extends UICollectionViewController {
+
+ static alloc(): QBAssetsViewController; // inherited from NSObject
+
+ static new(): QBAssetsViewController; // inherited from NSObject
+
+ assetCollection: PHAssetCollection;
+
+ imagePickerController: QBImagePickerController;
+}
+
+declare class QBCheckmarkView extends UIView {
+
+ static alloc(): QBCheckmarkView; // inherited from NSObject
+
+ static appearance(): QBCheckmarkView; // inherited from UIAppearance
+
+ static appearanceForTraitCollection(trait: UITraitCollection): QBCheckmarkView; // inherited from UIAppearance
+
+ static appearanceForTraitCollectionWhenContainedIn(trait: UITraitCollection, ContainerClass: typeof NSObject): QBCheckmarkView; // inherited from UIAppearance
+
+ static appearanceForTraitCollectionWhenContainedInInstancesOfClasses(trait: UITraitCollection, containerTypes: NSArray): QBCheckmarkView; // inherited from UIAppearance
+
+ static appearanceWhenContainedIn(ContainerClass: typeof NSObject): QBCheckmarkView; // inherited from UIAppearance
+
+ static appearanceWhenContainedInInstancesOfClasses(containerTypes: NSArray): QBCheckmarkView; // inherited from UIAppearance
+
+ static new(): QBCheckmarkView; // inherited from NSObject
+
+ bodyColor: UIColor;
+
+ borderColor: UIColor;
+
+ borderWidth: number;
+
+ checkmarkColor: UIColor;
+
+ checkmarkLineWidth: number;
+}
+
+declare class QBImagePickerController extends UIViewController {
+
+ static alloc(): QBImagePickerController; // inherited from NSObject
+
+ static new(): QBImagePickerController; // inherited from NSObject
+
+ allowsMultipleSelection: boolean;
+
+ assetCollectionSubtypes: NSArray;
+
+ delegate: QBImagePickerControllerDelegate;
+
+ maximumNumberOfSelection: number;
+
+ mediaType: QBImagePickerMediaType;
+
+ minimumNumberOfSelection: number;
+
+ numberOfColumnsInLandscape: number;
+
+ numberOfColumnsInPortrait: number;
+
+ prompt: string;
+
+ readonly selectedAssets: NSMutableOrderedSet;
+
+ showsNumberOfSelectedAssets: boolean;
+}
+
+interface QBImagePickerControllerDelegate extends NSObjectProtocol {
+
+ qb_imagePickerControllerDidCancel?(imagePickerController: QBImagePickerController): void;
+
+ qb_imagePickerControllerDidDeselectAsset?(imagePickerController: QBImagePickerController, asset: PHAsset): void;
+
+ qb_imagePickerControllerDidFinishPickingAssets?(imagePickerController: QBImagePickerController, assets: NSArray): void;
+
+ qb_imagePickerControllerDidSelectAsset?(imagePickerController: QBImagePickerController, asset: PHAsset): void;
+
+ qb_imagePickerControllerShouldSelectAsset?(imagePickerController: QBImagePickerController, asset: PHAsset): boolean;
+}
+declare var QBImagePickerControllerDelegate: {
+
+ prototype: QBImagePickerControllerDelegate;
+};
+
+declare var QBImagePickerControllerVersionNumber: number;
+
+declare var QBImagePickerControllerVersionString: interop.Reference;
+
+declare const enum QBImagePickerMediaType {
+
+ Any = 0,
+
+ Image = 1,
+
+ Video = 2
+}
+
+declare class QBSlomoIconView extends UIView {
+
+ static alloc(): QBSlomoIconView; // inherited from NSObject
+
+ static appearance(): QBSlomoIconView; // inherited from UIAppearance
+
+ static appearanceForTraitCollection(trait: UITraitCollection): QBSlomoIconView; // inherited from UIAppearance
+
+ static appearanceForTraitCollectionWhenContainedIn(trait: UITraitCollection, ContainerClass: typeof NSObject): QBSlomoIconView; // inherited from UIAppearance
+
+ static appearanceForTraitCollectionWhenContainedInInstancesOfClasses(trait: UITraitCollection, containerTypes: NSArray): QBSlomoIconView; // inherited from UIAppearance
+
+ static appearanceWhenContainedIn(ContainerClass: typeof NSObject): QBSlomoIconView; // inherited from UIAppearance
+
+ static appearanceWhenContainedInInstancesOfClasses(containerTypes: NSArray): QBSlomoIconView; // inherited from UIAppearance
+
+ static new(): QBSlomoIconView; // inherited from NSObject
+
+ iconColor: UIColor;
+}
+
+declare class QBVideoIconView extends UIView {
+
+ static alloc(): QBVideoIconView; // inherited from NSObject
+
+ static appearance(): QBVideoIconView; // inherited from UIAppearance
+
+ static appearanceForTraitCollection(trait: UITraitCollection): QBVideoIconView; // inherited from UIAppearance
+
+ static appearanceForTraitCollectionWhenContainedIn(trait: UITraitCollection, ContainerClass: typeof NSObject): QBVideoIconView; // inherited from UIAppearance
+
+ static appearanceForTraitCollectionWhenContainedInInstancesOfClasses(trait: UITraitCollection, containerTypes: NSArray): QBVideoIconView; // inherited from UIAppearance
+
+ static appearanceWhenContainedIn(ContainerClass: typeof NSObject): QBVideoIconView; // inherited from UIAppearance
+
+ static appearanceWhenContainedInInstancesOfClasses(containerTypes: NSArray): QBVideoIconView; // inherited from UIAppearance
+
+ static new(): QBVideoIconView; // inherited from NSObject
+
+ iconColor: UIColor;
+}
+
+declare class QBVideoIndicatorView extends UIView {
+
+ static alloc(): QBVideoIndicatorView; // inherited from NSObject
+
+ static appearance(): QBVideoIndicatorView; // inherited from UIAppearance
+
+ static appearanceForTraitCollection(trait: UITraitCollection): QBVideoIndicatorView; // inherited from UIAppearance
+
+ static appearanceForTraitCollectionWhenContainedIn(trait: UITraitCollection, ContainerClass: typeof NSObject): QBVideoIndicatorView; // inherited from UIAppearance
+
+ static appearanceForTraitCollectionWhenContainedInInstancesOfClasses(trait: UITraitCollection, containerTypes: NSArray): QBVideoIndicatorView; // inherited from UIAppearance
+
+ static appearanceWhenContainedIn(ContainerClass: typeof NSObject): QBVideoIndicatorView; // inherited from UIAppearance
+
+ static appearanceWhenContainedInInstancesOfClasses(containerTypes: NSArray): QBVideoIndicatorView; // inherited from UIAppearance
+
+ static new(): QBVideoIndicatorView; // inherited from NSObject
+
+ slomoIcon: QBSlomoIconView;
+
+ timeLabel: UILabel;
+
+ videoIcon: QBVideoIconView;
+}