diff --git a/.gitignore b/.gitignore
index 2e9df623..3d5b3ecf 100644
--- a/.gitignore
+++ b/.gitignore
@@ -12,3 +12,4 @@
xcuserdata/
project.xcworkspace/
tags
+.idea
\ No newline at end of file
diff --git a/.npmignore b/.npmignore
new file mode 100644
index 00000000..ea3e0d61
--- /dev/null
+++ b/.npmignore
@@ -0,0 +1,2 @@
+.idea
+demo
\ No newline at end of file
diff --git a/README.md b/README.md
index 440e3ab5..9612d299 100644
--- a/README.md
+++ b/README.md
@@ -8,9 +8,14 @@ Cordova Plugin For Multiple Image Selection - implemented for iOS and Android 4.
The plugin conforms to the Cordova plugin specification, it can be installed
using the Cordova / Phonegap command line interface.
- phonegap plugin add https://github.com/wymsee/cordova-imagePicker.git
+ # without desc
+ phonegap plugin add https://github.com/Telerik-Verified-Plugins/ImagePicker.git
+ cordova plugin add https://github.com/Telerik-Verified-Plugins/ImagePicker.git
+
+ # with desc
+ phonegap plugin add https://github.com/Telerik-Verified-Plugins/ImagePicker.git --variable PHOTO_LIBRARY_USAGE_DESCRIPTION="your usage message"
- cordova plugin add https://github.com/wymsee/cordova-imagePicker.git
+ cordova plugin add https://github.com/Telerik-Verified-Plugins/ImagePicker.git --variable PHOTO_LIBRARY_USAGE_DESCRIPTION="your usage message"
## Using the plugin
@@ -20,54 +25,92 @@ The plugin creates the object `window.imagePicker` with the method `getPictures(
Example - Get Full Size Images (all default options):
```javascript
window.imagePicker.getPictures(
- function(results) {
- for (var i = 0; i < results.length; i++) {
- console.log('Image URI: ' + results[i]);
- }
- }, function (error) {
- console.log('Error: ' + error);
- }
+ function(results) {
+ for (var i = 0; i < results.length; i++) {
+ console.log('Image URI: ' + results[i]);
+ }
+ }, function (error) {
+ console.log('Error: ' + error);
+ }
);
```
Example - Get at most 10 images scaled to width of 800:
```javascript
window.imagePicker.getPictures(
- function(results) {
- for (var i = 0; i < results.length; i++) {
- console.log('Image URI: ' + results[i]);
- }
- }, function (error) {
- console.log('Error: ' + error);
- }, {
- maximumImagesCount: 10,
- width: 800
- }
+ function(results) {
+ for (var i = 0; i < results.length; i++) {
+ console.log('Image URI: ' + results[i]);
+ }
+ }, function (error) {
+ console.log('Error: ' + error);
+ }, {
+ maximumImagesCount: 10,
+ width: 800
+ }
);
```
### Options
options = {
- // max images to be selected, defaults to 15. If this is set to 1, upon
- // selection of a single image, the plugin will return it.
- maximumImagesCount: int,
-
- // max width and height to allow the images to be. Will keep aspect
- // ratio no matter what. So if both are 800, the returned image
- // will be at most 800 pixels wide and 800 pixels tall. If the width is
- // 800 and height 0 the image will be 800 pixels wide if the source
- // is at least that wide.
- width: int,
- height: int,
-
- // quality of resized image, defaults to 100
- quality: int (0-100)
+ // Android only. Max images to be selected, defaults to 15. If this is set to 1, upon
+ // selection of a single image, the plugin will return it.
+ maximumImagesCount: int,
+
+ // max width and height to allow the images to be. Will keep aspect
+ // ratio no matter what. So if both are 800, the returned image
+ // will be at most 800 pixels wide and 800 pixels tall. If the width is
+ // 800 and height 0 the image will be 800 pixels wide if the source
+ // is at least that wide.
+ width: int,
+ height: int,
+
+ // quality of resized image, defaults to 100
+ quality: int (0-100),
+
+ // output type, defaults to FILE_URIs.
+ // available options are
+ // window.imagePicker.OutputType.FILE_URI (0) or
+ // window.imagePicker.OutputType.BASE64_STRING (1)
+ outputType: int
};
### Note for Android Use
-The plugin returns images that are stored in a temporary directory. These images will often not be deleted automatically though. The files should be moved or deleted after you get their filepaths in javascript.
+When outputType is FILE_URI the plugin returns images that are stored in a temporary directory. These images will often not be deleted automatically though. The files should be moved or deleted after you get their filepaths in javascript. If Base64 Strings are being returned, there is nothing to clean up.
+
+## Android 6 (M) Permissions
+On Android 6 you need to request permission to read external storage at runtime when targeting API level 23+.
+Even if the `uses-permission` tags for the Calendar are present in `AndroidManifest.xml`.
+
+Note that the `hasReadPermission` function will return true when:
+
+- You're running this on iOS, or
+- You're targeting an API level lower than 23, or
+- You're using Android < 6, or
+- You've already granted permission.
+
+```js
+ function hasReadPermission() {
+ window.imagePicker.hasReadPermission(
+ function(result) {
+ // if this is 'false' you probably want to call 'requestReadPermission' now
+ alert(result);
+ }
+ )
+ }
+
+ function requestReadPermission() {
+ // no callbacks required as this opens a popup which returns async
+ window.imagePicker.requestReadPermission();
+ }
+```
+
+Note that backward compatibility was added by checking for read permission automatically when `getPictures` is called.
+If permission is needed the plugin will now show the permission request popup.
+The user will then need to allow access and invoke the same method again after doing so.
+
## Libraries used
@@ -109,4 +152,4 @@ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
-THE SOFTWARE.
\ No newline at end of file
+THE SOFTWARE.
diff --git a/demo/index.html b/demo/index.html
new file mode 100644
index 00000000..39d1f2c6
--- /dev/null
+++ b/demo/index.html
@@ -0,0 +1,53 @@
+
+
+
+
+
+
+
+ ImagePicker demo
+
+
+
+
ImagePicker demo
+
+
+
Connecting to Device
+
+
Device is Ready
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/package.json b/package.json
new file mode 100644
index 00000000..9dec585a
--- /dev/null
+++ b/package.json
@@ -0,0 +1,29 @@
+{
+ "version": "2.3.6",
+ "name": "cordova-plugin-telerik-imagepicker",
+ "cordova_name": "ImagePicker",
+ "description": "This plugin allows selection of multiple images from the camera roll / gallery in a phonegap app",
+ "license": "MIT",
+ "issue": "",
+ "keywords": [],
+ "platforms": [
+ "android",
+ "ios"
+ ],
+ "cordova": {
+ "id": "cordova-plugin-telerik-imagepicker",
+ "platforms": [
+ "ios",
+ "android",
+ "wp8"
+ ]
+ },
+ "repository": {
+ "type": "git",
+ "url": "git+https://github.com/Telerik-Verified-Plugins/ImagePicker.git"
+ },
+ "engines": {
+ "name": "cordova",
+ "version": ">=3.5.0"
+ }
+}
diff --git a/plugin.xml b/plugin.xml
index 4a7924e6..e9d0e62c 100644
--- a/plugin.xml
+++ b/plugin.xml
@@ -1,110 +1,176 @@
-
- ImagePicker
-
-
- This plugin allows selection of multiple images from the camera roll / gallery in a phonegap app
-
-
- MIT
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+ id="cordova-plugin-telerik-imagepicker"
+ version="2.3.5">
+
+ ImagePicker
+
+
+ This plugin allows selection of multiple images from the camera roll / gallery in a phonegap app
+
+
+ MIT
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ $PHOTO_LIBRARY_USAGE_DESCRIPTION
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/android/Library/res/layout/actionbar_discard_button.xml b/src/android/Library/res/layout/actionbar_discard_button.xml
index bbe6321e..3d77b03f 100644
--- a/src/android/Library/res/layout/actionbar_discard_button.xml
+++ b/src/android/Library/res/layout/actionbar_discard_button.xml
@@ -12,22 +12,27 @@
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.
--->
+ -->
+
-
+
+
diff --git a/src/android/Library/res/layout/actionbar_done_button.xml b/src/android/Library/res/layout/actionbar_done_button.xml
index 907b9a13..33387c2c 100644
--- a/src/android/Library/res/layout/actionbar_done_button.xml
+++ b/src/android/Library/res/layout/actionbar_done_button.xml
@@ -15,20 +15,24 @@
-->
+ android:layout_height="fill_parent"
+ android:layout_weight="1"
+ style="?android:actionButtonStyle">
-
+ android:paddingLeft="10dp"
+ android:paddingRight="10dp"
+ android:paddingStart="10dp"
+ android:paddingEnd="10dp"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_gravity="center"
+ style="?android:actionBarTabTextStyle" />
diff --git a/src/android/Library/res/values-de/multiimagechooser_strings_de.xml b/src/android/Library/res/values-de/multiimagechooser_strings_de.xml
index 32123f13..ea2825f9 100644
--- a/src/android/Library/res/values-de/multiimagechooser_strings_de.xml
+++ b/src/android/Library/res/values-de/multiimagechooser_strings_de.xml
@@ -1,7 +1,9 @@
MultiImageChooser
- Free version - Images left: %d
- There was an error opening the images database. Please report the problem.
- Requesting thumbnails, please be patient
+ Freie Version - Nur noch %d Bilder können ausgewählt werden
+ Beim Öffnen der Bilder-Datenbank ist ein Fehler augetreten. Bitte melden Sie dieses Problem.
+ Vorschaubilder werden geladen, bitte haben Sie etwas Geduld
+ Bilder werden verarbeitet
+ Dies kann einen Moment dauern
diff --git a/src/android/Library/res/values-es/multiimagechooser_strings_es.xml b/src/android/Library/res/values-es/multiimagechooser_strings_es.xml
index fe05862e..5a131cf9 100644
--- a/src/android/Library/res/values-es/multiimagechooser_strings_es.xml
+++ b/src/android/Library/res/values-es/multiimagechooser_strings_es.xml
@@ -4,4 +4,8 @@
Solicitando miniaturas. Por favor, espere…Versión gratuita - Imágenes restantes: %dError al abrir la base de datos de imágenes.
+ Procesando imágenes
+ Esto puede tomar un momento
+ Cancelar
+ OK
\ No newline at end of file
diff --git a/src/android/Library/res/values-hu/multiimagechooser_strings_hu.xml b/src/android/Library/res/values-hu/multiimagechooser_strings_hu.xml
index 5bb897ce..1c60b5a0 100644
--- a/src/android/Library/res/values-hu/multiimagechooser_strings_hu.xml
+++ b/src/android/Library/res/values-hu/multiimagechooser_strings_hu.xml
@@ -1,7 +1,6 @@
MultiImageChooser
-
Ingyenes verzió - hátralévő képek: %dKépadatbázis megnyitási hiba történt. Kérjük, jelentse a problémát.Miniatűrök lekérése, kérjük legyen türelemmel
diff --git a/src/android/Library/res/values-it/multiimagechooser_strings_it.xml b/src/android/Library/res/values-it/multiimagechooser_strings_it.xml
new file mode 100644
index 00000000..a5df8241
--- /dev/null
+++ b/src/android/Library/res/values-it/multiimagechooser_strings_it.xml
@@ -0,0 +1,11 @@
+
+
+ MultiImageChooser
+ Versione gratuita - Immagini rimaste: %d
+ Si è verificato un errore durante l\'apertura del database delle immagini. Si prega di segnalare il problema.
+ Richiesta miniature. Attendere...
+ Elaborazione immagini
+ Potrebbe volerci un po\' di tempo.
+ Annulla
+ Fatto
+
diff --git a/src/android/Library/res/values-pl/multiimagechooser_strings_pl.xml b/src/android/Library/res/values-pl/multiimagechooser_strings_pl.xml
new file mode 100644
index 00000000..8ec55fde
--- /dev/null
+++ b/src/android/Library/res/values-pl/multiimagechooser_strings_pl.xml
@@ -0,0 +1,13 @@
+
+
+ MultiImageChooser
+ Darmowa wersja - Zdjęć zostało: %d
+ Wystąpił problem przy próbie otwarcia bazy zdjęć. Proszę, zgłoś problem.
+ Pobieram miniaturki, proszę o cierpliwość
+ Przetwarzam zdjęcia
+ To może chwilę zająć
+ Anuluj
+ OK
+ Maksymalnie %d zdjęć
+ Możesz wybrać maksymalnie %d zdjęć.
+
diff --git a/src/android/Library/res/values-pt/multiimagechooser_strings_pt.xml b/src/android/Library/res/values-pt/multiimagechooser_strings_pt.xml
new file mode 100644
index 00000000..c2adf7ba
--- /dev/null
+++ b/src/android/Library/res/values-pt/multiimagechooser_strings_pt.xml
@@ -0,0 +1,13 @@
+
+
+ MultiImageChooser
+ Free version - Images left: %d
+ Ocorreu um erro ao carregar as imagens.
+ Obtendo as miniaturas, aguarde
+ Processando imagens
+ Isso pode levar um tempo, aguarde
+ Cancelar
+ OK
+ Limite de %d fotos
+ Você pode selecionar até %d fotos
+
\ No newline at end of file
diff --git a/src/android/Library/res/values-sv/multiimagechooser_strings_sv.xml b/src/android/Library/res/values-sv/multiimagechooser_strings_sv.xml
new file mode 100644
index 00000000..2f73c0fc
--- /dev/null
+++ b/src/android/Library/res/values-sv/multiimagechooser_strings_sv.xml
@@ -0,0 +1,9 @@
+
+
+ MultiImageChooser
+ Gratisversion - Bilder kvar: %d
+ Det gick inte att öppna bilddatabasen. Var vänlig rapportera detta problem.
+ Hämtar tumnaglar, var god vänta
+ Behandlar bilder
+ Det här kan ta en stund
+
diff --git a/src/android/Library/res/values/multiimagechooser_strings_en.xml b/src/android/Library/res/values/multiimagechooser_strings_en.xml
index 2566b2f9..9b1ffef9 100644
--- a/src/android/Library/res/values/multiimagechooser_strings_en.xml
+++ b/src/android/Library/res/values/multiimagechooser_strings_en.xml
@@ -4,6 +4,10 @@
Free version - Images left: %dThere was an error opening the images database. Please report the problem.Requesting thumbnails, please be patient
- Cancel
- OK
-
+ Processing images
+ This may take a moment
+ Cancel
+ OK
+ Maximum %d Photos
+ You can only select %d photos at a time.
+
diff --git a/src/android/Library/src/ImageFetcher.java b/src/android/Library/src/ImageFetcher.java
index 88255bd8..855114ee 100644
--- a/src/android/Library/src/ImageFetcher.java
+++ b/src/android/Library/src/ImageFetcher.java
@@ -196,7 +196,6 @@ protected Bitmap doInBackground(Integer... params) {
} else {
if (rotate != 0) {
Matrix matrix = new Matrix();
- matrix.setRotate(rotate);
thumb = Bitmap.createBitmap(thumb, 0, 0, thumb.getWidth(), thumb.getHeight(), matrix, true);
}
return thumb;
@@ -290,7 +289,7 @@ public BitmapFetcherTask getBitmapDownloaderTask() {
private final HashMap sHardBitmapCache = new LinkedHashMap(
HARD_CACHE_CAPACITY / 2, 0.75f, true) {
@Override
- protected boolean removeEldestEntry(LinkedHashMap.Entry eldest) {
+ protected boolean removeEldestEntry(HashMap.Entry eldest) {
if (size() > HARD_CACHE_CAPACITY) {
// Entries push-out of hard reference cache are transferred to
// soft reference cache
diff --git a/src/android/Library/src/MultiImageChooserActivity.java b/src/android/Library/src/MultiImageChooserActivity.java
index 25ca388e..ba379e3b 100644
--- a/src/android/Library/src/MultiImageChooserActivity.java
+++ b/src/android/Library/src/MultiImageChooserActivity.java
@@ -31,6 +31,7 @@
package com.synconset;
import java.net.URI;
+import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
@@ -43,8 +44,6 @@
import java.util.Set;
import com.synconset.FakeR;
-import android.app.Activity;
-import android.app.ActionBar;
import android.app.AlertDialog;
import android.app.LoaderManager;
import android.app.ProgressDialog;
@@ -53,6 +52,7 @@
import android.content.DialogInterface;
import android.content.Intent;
import android.content.Loader;
+import android.content.pm.ActivityInfo;
import android.database.Cursor;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
@@ -62,7 +62,9 @@
import android.os.AsyncTask;
import android.os.Bundle;
import android.provider.MediaStore;
-import android.util.Log;
+import android.support.v7.app.ActionBar;
+import android.support.v7.app.AppCompatActivity;
+import android.util.Base64;
import android.util.SparseBooleanArray;
import android.view.Display;
import android.view.LayoutInflater;
@@ -75,10 +77,11 @@
import android.widget.BaseAdapter;
import android.widget.GridView;
import android.widget.ImageView;
-import android.widget.TextView;
-public class MultiImageChooserActivity extends Activity implements OnItemClickListener,
+public class MultiImageChooserActivity extends AppCompatActivity implements
+ OnItemClickListener,
LoaderManager.LoaderCallbacks {
+
private static final String TAG = "ImagePicker";
public static final int NOLIMIT = -1;
@@ -86,6 +89,7 @@ public class MultiImageChooserActivity extends Activity implements OnItemClickLi
public static final String WIDTH_KEY = "WIDTH";
public static final String HEIGHT_KEY = "HEIGHT";
public static final String QUALITY_KEY = "QUALITY";
+ public static final String OUTPUT_TYPE_KEY = "OUTPUT_TYPE";
private ImageAdapter ia;
@@ -102,20 +106,21 @@ public class MultiImageChooserActivity extends Activity implements OnItemClickLi
private int maxImages;
private int maxImageCount;
-
+
private int desiredWidth;
private int desiredHeight;
private int quality;
-
- private GridView gridView;
+ private OutputType outputType;
private final ImageFetcher fetcher = new ImageFetcher();
private int selectedColor = 0xff32b2e1;
private boolean shouldRequestThumb = true;
-
+
private FakeR fakeR;
-
+ private View abDoneView;
+ private View abDiscardView;
+
private ProgressDialog progress;
@Override
@@ -130,13 +135,14 @@ public void onCreate(Bundle savedInstanceState) {
desiredHeight = getIntent().getIntExtra(HEIGHT_KEY, 0);
quality = getIntent().getIntExtra(QUALITY_KEY, 0);
maxImageCount = maxImages;
+ outputType = OutputType.fromValue(getIntent().getIntExtra(OUTPUT_TYPE_KEY, 0));
Display display = getWindowManager().getDefaultDisplay();
int width = display.getWidth();
-
+
colWidth = width / 4;
- gridView = (GridView) findViewById(fakeR.getId("id", "gridview"));
+ GridView gridView = (GridView) findViewById(fakeR.getId("id", "gridview"));
gridView.setOnItemClickListener(this);
gridView.setOnScrollListener(new OnScrollListener() {
private int lastFirstItem = 0;
@@ -164,7 +170,7 @@ public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCoun
}
});
- ia = new ImageAdapter(this);
+ ia = new ImageAdapter();
gridView.setAdapter(ia);
LoaderManager.enableDebugLogging(false);
@@ -173,10 +179,10 @@ public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCoun
setupHeader();
updateAcceptButton();
progress = new ProgressDialog(this);
- progress.setTitle("Processing Images");
- progress.setMessage("This may take a few moments");
+ progress.setTitle(getString(fakeR.getId("string", "multi_image_picker_processing_images_title")));
+ progress.setMessage(getString(fakeR.getId("string", "multi_image_picker_processing_images_message")));
}
-
+
@Override
public void onItemClick(AdapterView> arg0, View view, int position, long id) {
String name = getImageName(position);
@@ -185,42 +191,51 @@ public void onItemClick(AdapterView> arg0, View view, int position, long id) {
if (name == null) {
return;
}
+
boolean isChecked = !isChecked(position);
+
if (maxImages == 0 && isChecked) {
isChecked = false;
- AlertDialog.Builder builder = new AlertDialog.Builder(this);
- builder.setTitle("Maximum " + maxImageCount + " Photos");
- builder.setMessage("You can only select " + maxImageCount + " photos at a time.");
- builder.setPositiveButton("OK", new DialogInterface.OnClickListener() {
- public void onClick(DialogInterface dialog, int which) {
- dialog.cancel();
- }
- });
- AlertDialog alert = builder.create();
- alert.show();
+ new AlertDialog.Builder(this)
+ .setTitle(String.format(getString(fakeR.getId("string", "max_count_photos_title")), maxImageCount))
+ .setMessage(String.format(getString(fakeR.getId("string", "max_count_photos_message")), maxImageCount))
+ .setPositiveButton("OK", new DialogInterface.OnClickListener() {
+ public void onClick(DialogInterface dialog, int which) {
+ dialog.cancel();
+ }
+ })
+ .create()
+ .show();
+
} else if (isChecked) {
- fileNames.put(name, new Integer(rotation));
+ fileNames.put(name, rotation);
+
if (maxImageCount == 1) {
- this.selectClicked(null);
+ selectClicked();
+
} else {
maxImages--;
- ImageView imageView = (ImageView)view;
- if (android.os.Build.VERSION.SDK_INT>=16) {
+ ImageView imageView = (ImageView) view;
+
+ if (android.os.Build.VERSION.SDK_INT >= 16) {
imageView.setImageAlpha(128);
} else {
imageView.setAlpha(128);
}
+
view.setBackgroundColor(selectedColor);
}
} else {
fileNames.remove(name);
maxImages++;
- ImageView imageView = (ImageView)view;
- if (android.os.Build.VERSION.SDK_INT>=16) {
+ ImageView imageView = (ImageView) view;
+
+ if (android.os.Build.VERSION.SDK_INT >= 16) {
imageView.setImageAlpha(255);
} else {
imageView.setAlpha(255);
}
+
view.setBackgroundColor(Color.TRANSPARENT);
}
@@ -230,26 +245,27 @@ public void onClick(DialogInterface dialog, int which) {
@Override
public Loader onCreateLoader(int cursorID, Bundle arg1) {
- CursorLoader cl = null;
-
ArrayList img = new ArrayList();
switch (cursorID) {
+ case CURSORLOADER_THUMBS:
+ img.add(MediaStore.Images.Media._ID);
+ img.add(MediaStore.Images.Media.ORIENTATION);
+ break;
- case CURSORLOADER_THUMBS:
- img.add(MediaStore.Images.Media._ID);
- img.add(MediaStore.Images.Media.ORIENTATION);
- break;
- case CURSORLOADER_REAL:
- img.add(MediaStore.Images.Thumbnails.DATA);
- img.add(MediaStore.Images.Media.ORIENTATION);
- break;
- default:
- break;
+ case CURSORLOADER_REAL:
+ img.add(MediaStore.Images.Thumbnails.DATA);
+ img.add(MediaStore.Images.Media.ORIENTATION);
+ break;
}
- cl = new CursorLoader(MultiImageChooserActivity.this, MediaStore.Images.Media.EXTERNAL_CONTENT_URI,
- img.toArray(new String[img.size()]), null, null, "DATE_MODIFIED DESC");
- return cl;
+ return new CursorLoader(
+ this,
+ MediaStore.Images.Media.EXTERNAL_CONTENT_URI,
+ img.toArray(new String[img.size()]),
+ null,
+ null,
+ "DATE_MODIFIED DESC"
+ );
}
@Override
@@ -266,53 +282,56 @@ public void onLoadFinished(Loader loader, Cursor cursor) {
image_column_orientation = imagecursor.getColumnIndex(MediaStore.Images.Media.ORIENTATION);
ia.notifyDataSetChanged();
break;
+
case CURSORLOADER_REAL:
actualimagecursor = cursor;
- String[] columns = actualimagecursor.getColumnNames();
actual_image_column_index = actualimagecursor.getColumnIndexOrThrow(MediaStore.Images.Media.DATA);
orientation_column_index = actualimagecursor.getColumnIndexOrThrow(MediaStore.Images.Media.ORIENTATION);
break;
- default:
- break;
}
}
@Override
public void onLoaderReset(Loader loader) {
- if (loader.getId() == CURSORLOADER_THUMBS) {
- imagecursor = null;
- } else if (loader.getId() == CURSORLOADER_REAL) {
- actualimagecursor = null;
+ switch (loader.getId()) {
+ case CURSORLOADER_THUMBS:
+ imagecursor = null;
+ break;
+
+ case CURSORLOADER_REAL:
+ actualimagecursor = null;
+ break;
}
}
-
- public void cancelClicked(View ignored) {
+
+ public void cancelClicked() {
setResult(RESULT_CANCELED);
finish();
}
- public void selectClicked(View ignored) {
- ((TextView) getActionBar().getCustomView().findViewById(fakeR.getId("id", "actionbar_done_textview"))).setEnabled(false);
- getActionBar().getCustomView().findViewById(fakeR.getId("id", "actionbar_done")).setEnabled(false);
+ public void selectClicked() {
+ abDiscardView.setEnabled(false);
+ abDoneView.setEnabled(false);
progress.show();
- Intent data = new Intent();
+
if (fileNames.isEmpty()) {
- this.setResult(RESULT_CANCELED);
+ setResult(RESULT_CANCELED);
progress.dismiss();
finish();
} else {
+ setRequestedOrientation(getResources().getConfiguration().orientation); //prevent orientation changes during processing
new ResizeImagesTask().execute(fileNames.entrySet());
}
}
-
-
+
+
/*********************
* Helper Methods
********************/
private void updateAcceptButton() {
- ((TextView) getActionBar().getCustomView().findViewById(fakeR.getId("id", "actionbar_done_textview")))
- .setEnabled(fileNames.size() != 0);
- getActionBar().getCustomView().findViewById(fakeR.getId("id", "actionbar_done")).setEnabled(fileNames.size() != 0);
+ if (abDoneView != null) {
+ abDoneView.setEnabled(fileNames.size() != 0);
+ }
}
private void setupHeader() {
@@ -334,29 +353,43 @@ private void setupHeader() {
* See the License for the specific language governing permissions and
* limitations under the License.
*/
- LayoutInflater inflater = (LayoutInflater) getActionBar().getThemedContext().getSystemService(
- LAYOUT_INFLATER_SERVICE);
- final View customActionBarView = inflater.inflate(fakeR.getId("layout", "actionbar_custom_view_done_discard"), null);
- customActionBarView.findViewById(fakeR.getId("id", "actionbar_done")).setOnClickListener(new View.OnClickListener() {
+ LayoutInflater inflater = (LayoutInflater) getSystemService(LAYOUT_INFLATER_SERVICE);
+ View customActionBarView = inflater.inflate(
+ fakeR.getId("layout", "actionbar_custom_view_done_discard"),
+ null
+ );
+
+ abDoneView = customActionBarView.findViewById(fakeR.getId("id", "actionbar_done"));
+ abDoneView.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
// "Done"
- selectClicked(null);
+ selectClicked();
}
});
- customActionBarView.findViewById(fakeR.getId("id", "actionbar_discard")).setOnClickListener(new View.OnClickListener() {
+
+ abDiscardView = customActionBarView.findViewById(fakeR.getId("id", "actionbar_discard"));
+ abDiscardView.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
- finish();
+ cancelClicked();
}
});
// Show the custom action bar view and hide the normal Home icon and title.
- final ActionBar actionBar = getActionBar();
- actionBar.setDisplayOptions(ActionBar.DISPLAY_SHOW_CUSTOM, ActionBar.DISPLAY_SHOW_CUSTOM
- | ActionBar.DISPLAY_SHOW_HOME | ActionBar.DISPLAY_SHOW_TITLE);
- actionBar.setCustomView(customActionBarView, new ActionBar.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,
- ViewGroup.LayoutParams.MATCH_PARENT));
+ ActionBar actionBar = getSupportActionBar();
+ if (actionBar != null) {
+ actionBar.setDisplayOptions(
+ ActionBar.DISPLAY_SHOW_CUSTOM,
+ ActionBar.DISPLAY_SHOW_CUSTOM
+ | ActionBar.DISPLAY_SHOW_HOME
+ | ActionBar.DISPLAY_SHOW_TITLE
+ );
+ actionBar.setCustomView(customActionBarView, new ActionBar.LayoutParams(
+ ViewGroup.LayoutParams.MATCH_PARENT,
+ ViewGroup.LayoutParams.MATCH_PARENT
+ ));
+ }
}
private String getImageName(int position) {
@@ -366,11 +399,12 @@ private String getImageName(int position) {
try {
name = actualimagecursor.getString(actual_image_column_index);
} catch (Exception e) {
- return null;
+ // Do something?
}
+
return name;
}
-
+
private int getImageRotation(int position) {
actualimagecursor.moveToPosition(position);
int rotation = 0;
@@ -378,17 +412,17 @@ private int getImageRotation(int position) {
try {
rotation = actualimagecursor.getInt(orientation_column_index);
} catch (Exception e) {
- return rotation;
+ // Do something?
}
+
return rotation;
}
-
+
public boolean isChecked(int position) {
- boolean ret = checkStatus.get(position);
- return ret;
+ return checkStatus.get(position);
}
-
+
/*********************
* Nested Classes
********************/
@@ -397,24 +431,14 @@ public SquareImageView(Context context) {
super(context);
}
- @Override
+ @Override
public void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, widthMeasureSpec);
}
}
-
-
+
+
private class ImageAdapter extends BaseAdapter {
- private final Bitmap mPlaceHolderBitmap;
-
- public ImageAdapter(Context c) {
- Bitmap tmpHolderBitmap = BitmapFactory.decodeResource(getResources(), fakeR.getId("drawable", "loading_icon"));
- mPlaceHolderBitmap = Bitmap.createScaledBitmap(tmpHolderBitmap, colWidth, colWidth, false);
- if (tmpHolderBitmap != mPlaceHolderBitmap) {
- tmpHolderBitmap.recycle();
- tmpHolderBitmap = null;
- }
- }
public int getCount() {
if (imagecursor != null) {
@@ -433,19 +457,17 @@ public long getItemId(int position) {
}
// create a new ImageView for each item referenced by the Adapter
- public View getView(int pos, View convertView, ViewGroup parent) {
+ public View getView(int position, View convertView, ViewGroup parent) {
if (convertView == null) {
ImageView temp = new SquareImageView(MultiImageChooserActivity.this);
temp.setScaleType(ImageView.ScaleType.CENTER_CROP);
- convertView = (View)temp;
+ convertView = temp;
}
- ImageView imageView = (ImageView)convertView;
+ ImageView imageView = (ImageView) convertView;
imageView.setImageBitmap(null);
- final int position = pos;
-
if (!imagecursor.moveToPosition(position)) {
return imageView;
}
@@ -456,30 +478,33 @@ public View getView(int pos, View convertView, ViewGroup parent) {
final int id = imagecursor.getInt(image_column_index);
final int rotate = imagecursor.getInt(image_column_orientation);
- if (isChecked(pos)) {
- if (android.os.Build.VERSION.SDK_INT>=16) {
+
+ if (isChecked(position)) {
+ if (android.os.Build.VERSION.SDK_INT >= 16) {
imageView.setImageAlpha(128);
} else {
- imageView.setAlpha(128);
+ imageView.setAlpha(128);
}
+
imageView.setBackgroundColor(selectedColor);
+
} else {
- if (android.os.Build.VERSION.SDK_INT>=16) {
+ if (android.os.Build.VERSION.SDK_INT >= 16) {
imageView.setImageAlpha(255);
} else {
- imageView.setAlpha(255);
+ imageView.setAlpha(255);
}
imageView.setBackgroundColor(Color.TRANSPARENT);
}
+
if (shouldRequestThumb) {
- fetcher.fetch(Integer.valueOf(id), imageView, colWidth, rotate);
+ fetcher.fetch(id, imageView, colWidth, rotate);
}
return imageView;
}
}
-
-
+
private class ResizeImagesTask extends AsyncTask>, Void, ArrayList> {
private Exception asyncTaskError = null;
@@ -490,10 +515,10 @@ protected ArrayList doInBackground(Set>... fileSe
try {
Iterator> i = fileNames.iterator();
Bitmap bmp;
- while(i.hasNext()) {
+ while (i.hasNext()) {
Entry imageInfo = i.next();
File file = new File(imageInfo.getKey());
- int rotate = imageInfo.getValue().intValue();
+ int rotate = imageInfo.getValue();
BitmapFactory.Options options = new BitmapFactory.Options();
options.inSampleSize = 1;
options.inJustDecodeBounds = true;
@@ -501,12 +526,14 @@ protected ArrayList doInBackground(Set>... fileSe
int width = options.outWidth;
int height = options.outHeight;
float scale = calculateScale(width, height);
+
if (scale < 1) {
int finalWidth = (int)(width * scale);
int finalHeight = (int)(height * scale);
int inSampleSize = calculateInSampleSize(options, finalWidth, finalHeight);
options = new BitmapFactory.Options();
options.inSampleSize = inSampleSize;
+
try {
bmp = this.tryToGetBitmap(file, options, rotate, true);
} catch (OutOfMemoryError e) {
@@ -523,11 +550,13 @@ protected ArrayList doInBackground(Set>... fileSe
} catch(OutOfMemoryError e) {
options = new BitmapFactory.Options();
options.inSampleSize = 2;
+
try {
bmp = this.tryToGetBitmap(file, options, rotate, false);
} catch(OutOfMemoryError e2) {
options = new BitmapFactory.Options();
options.inSampleSize = 4;
+
try {
bmp = this.tryToGetBitmap(file, options, rotate, false);
} catch (OutOfMemoryError e3) {
@@ -537,11 +566,16 @@ protected ArrayList doInBackground(Set>... fileSe
}
}
- file = this.storeImage(bmp, file.getName());
- al.add(Uri.fromFile(file).toString());
+ if (outputType == OutputType.FILE_URI) {
+ file = storeImage(bmp, file.getName());
+ al.add(Uri.fromFile(file).toString());
+
+ } else if (outputType == OutputType.BASE64_STRING) {
+ al.add(getBase64OfImage(bmp));
+ }
}
return al;
- } catch(IOException e) {
+ } catch (IOException e) {
try {
asyncTaskError = e;
for (int i = 0; i < al.size(); i++) {
@@ -549,14 +583,13 @@ protected ArrayList doInBackground(Set>... fileSe
File file = new File(uri);
file.delete();
}
- } catch(Exception exception) {
- // the finally does what we want to do
- } finally {
- return new ArrayList();
+ } catch (Exception ignore) {
}
+
+ return new ArrayList();
}
}
-
+
@Override
protected void onPostExecute(ArrayList al) {
Intent data = new Intent();
@@ -566,14 +599,19 @@ protected void onPostExecute(ArrayList al) {
res.putString("ERRORMESSAGE", asyncTaskError.getMessage());
data.putExtras(res);
setResult(RESULT_CANCELED, data);
+
} else if (al.size() > 0) {
Bundle res = new Bundle();
res.putStringArrayList("MULTIPLEFILENAMES", al);
+
if (imagecursor != null) {
res.putInt("TOTALFILES", imagecursor.getCount());
}
- data.putExtras(res);
+
+ int sync = ResultIPC.get().setLargeData(res);
+ data.putExtra("bigdata:synccode", sync);
setResult(RESULT_OK, data);
+
} else {
setResult(RESULT_CANCELED, data);
}
@@ -582,32 +620,39 @@ protected void onPostExecute(ArrayList al) {
finish();
}
- private Bitmap tryToGetBitmap(File file, BitmapFactory.Options options, int rotate, boolean shouldScale) throws IOException, OutOfMemoryError {
+ private Bitmap tryToGetBitmap(File file,
+ BitmapFactory.Options options,
+ int rotate,
+ boolean shouldScale) throws IOException, OutOfMemoryError {
Bitmap bmp;
if (options == null) {
bmp = BitmapFactory.decodeFile(file.getAbsolutePath());
} else {
bmp = BitmapFactory.decodeFile(file.getAbsolutePath(), options);
}
+
if (bmp == null) {
throw new IOException("The image file could not be opened.");
}
+
if (options != null && shouldScale) {
float scale = calculateScale(options.outWidth, options.outHeight);
bmp = this.getResizedBitmap(bmp, scale);
}
+
if (rotate != 0) {
Matrix matrix = new Matrix();
matrix.setRotate(rotate);
bmp = Bitmap.createBitmap(bmp, 0, 0, bmp.getWidth(), bmp.getHeight(), matrix, true);
}
+
return bmp;
}
-
+
/*
* The following functions are originally from
* https://github.com/raananw/PhoneGap-Image-Resizer
- *
+ *
* They have been modified by Andrew Stephan for Sync OnSet
*
* The software is open source, MIT Licensed.
@@ -619,16 +664,18 @@ private File storeImage(Bitmap bmp, String fileName) throws IOException {
String ext = fileName.substring(index);
File file = File.createTempFile("tmp_" + name, ext);
OutputStream outStream = new FileOutputStream(file);
+
if (ext.compareToIgnoreCase(".png") == 0) {
bmp.compress(Bitmap.CompressFormat.PNG, quality, outStream);
} else {
bmp.compress(Bitmap.CompressFormat.JPEG, quality, outStream);
}
+
outStream.flush();
outStream.close();
return file;
}
-
+
private Bitmap getResizedBitmap(Bitmap bm, float factor) {
int width = bm.getWidth();
int height = bm.getHeight();
@@ -637,28 +684,34 @@ private Bitmap getResizedBitmap(Bitmap bm, float factor) {
// resize the bit map
matrix.postScale(factor, factor);
// recreate the new Bitmap
- Bitmap resizedBitmap = Bitmap.createBitmap(bm, 0, 0, width, height, matrix, false);
- return resizedBitmap;
+ return Bitmap.createBitmap(bm, 0, 0, width, height, matrix, false);
+ }
+
+ private String getBase64OfImage(Bitmap bm) {
+ ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
+ bm.compress(Bitmap.CompressFormat.JPEG, quality, byteArrayOutputStream);
+ byte[] byteArray = byteArrayOutputStream.toByteArray();
+ return Base64.encodeToString(byteArray, Base64.NO_WRAP);
}
}
-
+
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;
}
@@ -666,7 +719,7 @@ private int calculateNextSampleSize(int sampleSize) {
double logBaseTwo = (int)(Math.log(sampleSize) / Math.log(2));
return (int)Math.pow(logBaseTwo + 1, 2);
}
-
+
private float calculateScale(int width, int height) {
float widthScale = 1.0f;
float heightScale = 1.0f;
@@ -674,15 +727,19 @@ private float calculateScale(int width, int height) {
if (desiredWidth > 0 || desiredHeight > 0) {
if (desiredHeight == 0 && desiredWidth < width) {
scale = (float)desiredWidth/width;
+
} else if (desiredWidth == 0 && desiredHeight < height) {
scale = (float)desiredHeight/height;
+
} else {
if (desiredWidth > 0 && desiredWidth < width) {
widthScale = (float)desiredWidth/width;
}
+
if (desiredHeight > 0 && desiredHeight < height) {
heightScale = (float)desiredHeight/height;
}
+
if (widthScale < heightScale) {
scale = widthScale;
} else {
@@ -690,7 +747,27 @@ private float calculateScale(int width, int height) {
}
}
}
-
+
return scale;
}
+
+ enum OutputType {
+
+ FILE_URI(0), BASE64_STRING(1);
+
+ int value;
+
+ OutputType(int value) {
+ this.value = value;
+ }
+
+ public static OutputType fromValue(int value) {
+ for (OutputType type : OutputType.values()) {
+ if (type.value == value) {
+ return type;
+ }
+ }
+ throw new IllegalArgumentException("Invalid enum value specified");
+ }
+ }
}
diff --git a/src/android/androidtarget.gradle b/src/android/androidtarget.gradle
new file mode 100644
index 00000000..e69de29b
diff --git a/src/android/com/synconset/ImagePicker/ImagePicker.java b/src/android/com/synconset/ImagePicker/ImagePicker.java
index 2f4939af..b534736a 100644
--- a/src/android/com/synconset/ImagePicker/ImagePicker.java
+++ b/src/android/com/synconset/ImagePicker/ImagePicker.java
@@ -6,67 +6,168 @@
import org.apache.cordova.CallbackContext;
import org.apache.cordova.CordovaPlugin;
+import org.apache.cordova.PluginResult;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import java.util.ArrayList;
+import android.Manifest;
+import android.annotation.SuppressLint;
import android.app.Activity;
import android.content.Intent;
-import android.util.Log;
+import android.content.pm.PackageManager;
+import android.os.Build;
+import android.os.Bundle;
+import android.support.v4.app.ActivityCompat;
+import android.support.v4.content.ContextCompat;
public class ImagePicker extends CordovaPlugin {
- public static String TAG = "ImagePicker";
-
- private CallbackContext callbackContext;
- private JSONObject params;
-
- public boolean execute(String action, final JSONArray args, final CallbackContext callbackContext) throws JSONException {
- this.callbackContext = callbackContext;
- this.params = args.getJSONObject(0);
- if (action.equals("getPictures")) {
- Intent intent = new Intent(cordova.getActivity(), MultiImageChooserActivity.class);
- int max = 20;
- int desiredWidth = 0;
- int desiredHeight = 0;
- int quality = 100;
- if (this.params.has("maximumImagesCount")) {
- max = this.params.getInt("maximumImagesCount");
- }
- if (this.params.has("width")) {
- desiredWidth = this.params.getInt("width");
- }
- if (this.params.has("height")) {
- desiredWidth = this.params.getInt("height");
- }
- if (this.params.has("quality")) {
- quality = this.params.getInt("quality");
- }
- intent.putExtra("MAX_IMAGES", max);
- intent.putExtra("WIDTH", desiredWidth);
- intent.putExtra("HEIGHT", desiredHeight);
- intent.putExtra("QUALITY", quality);
- if (this.cordova != null) {
- this.cordova.startActivityForResult((CordovaPlugin) this, intent, 0);
- }
- }
- return true;
- }
-
- public void onActivityResult(int requestCode, int resultCode, Intent data) {
- if (resultCode == Activity.RESULT_OK && data != null) {
- ArrayList fileNames = data.getStringArrayListExtra("MULTIPLEFILENAMES");
- JSONArray res = new JSONArray(fileNames);
- this.callbackContext.success(res);
- } else if (resultCode == Activity.RESULT_CANCELED && data != null) {
- String error = data.getStringExtra("ERRORMESSAGE");
- this.callbackContext.error(error);
- } else if (resultCode == Activity.RESULT_CANCELED) {
- JSONArray res = new JSONArray();
- this.callbackContext.success(res);
- } else {
- this.callbackContext.error("No images selected");
- }
- }
-}
\ No newline at end of file
+
+ private static final String ACTION_GET_PICTURES = "getPictures";
+ private static final String ACTION_HAS_READ_PERMISSION = "hasReadPermission";
+ private static final String ACTION_REQUEST_READ_PERMISSION = "requestReadPermission";
+
+ private static final int PERMISSION_REQUEST_CODE = 100;
+
+ private CallbackContext callbackContext;
+
+ public boolean execute(String action, final JSONArray args, final CallbackContext callbackContext) throws JSONException {
+ this.callbackContext = callbackContext;
+
+ if (ACTION_HAS_READ_PERMISSION.equals(action)) {
+ callbackContext.sendPluginResult(new PluginResult(PluginResult.Status.OK, hasReadPermission()));
+ return true;
+
+ } else if (ACTION_REQUEST_READ_PERMISSION.equals(action)) {
+ requestReadPermission();
+ return true;
+
+ } else if (ACTION_GET_PICTURES.equals(action)) {
+ final JSONObject params = args.getJSONObject(0);
+ final Intent imagePickerIntent = new Intent(cordova.getActivity(), MultiImageChooserActivity.class);
+ int max = 20;
+ int desiredWidth = 0;
+ int desiredHeight = 0;
+ int quality = 100;
+ int outputType = 0;
+ if (params.has("maximumImagesCount")) {
+ max = params.getInt("maximumImagesCount");
+ }
+ if (params.has("width")) {
+ desiredWidth = params.getInt("width");
+ }
+ if (params.has("height")) {
+ desiredHeight = params.getInt("height");
+ }
+ if (params.has("quality")) {
+ quality = params.getInt("quality");
+ }
+ if (params.has("outputType")) {
+ outputType = params.getInt("outputType");
+ }
+
+ imagePickerIntent.putExtra("MAX_IMAGES", max);
+ imagePickerIntent.putExtra("WIDTH", desiredWidth);
+ imagePickerIntent.putExtra("HEIGHT", desiredHeight);
+ imagePickerIntent.putExtra("QUALITY", quality);
+ imagePickerIntent.putExtra("OUTPUT_TYPE", outputType);
+
+ // some day, when everybody uses a cordova version supporting 'hasPermission', enable this:
+ /*
+ if (cordova != null) {
+ if (cordova.hasPermission(Manifest.permission.READ_EXTERNAL_STORAGE)) {
+ cordova.startActivityForResult(this, imagePickerIntent, 0);
+ } else {
+ cordova.requestPermission(
+ this,
+ PERMISSION_REQUEST_CODE,
+ Manifest.permission.READ_EXTERNAL_STORAGE
+ );
+ }
+ }
+ */
+ // .. until then use:
+ if (hasReadPermission()) {
+ cordova.startActivityForResult(this, imagePickerIntent, 0);
+ } else {
+ requestReadPermission();
+ // The downside is the user needs to re-invoke this picker method.
+ // The best thing to do for the dev is check 'hasReadPermission' manually and
+ // run 'requestReadPermission' or 'getPictures' based on the outcome.
+ }
+ return true;
+ }
+ return false;
+ }
+
+ @SuppressLint("InlinedApi")
+ private boolean hasReadPermission() {
+ return Build.VERSION.SDK_INT < 23 ||
+ PackageManager.PERMISSION_GRANTED == ContextCompat.checkSelfPermission(this.cordova.getActivity(), Manifest.permission.READ_EXTERNAL_STORAGE);
+ }
+
+ @SuppressLint("InlinedApi")
+ private void requestReadPermission() {
+ if (!hasReadPermission()) {
+ ActivityCompat.requestPermissions(
+ this.cordova.getActivity(),
+ new String[] {Manifest.permission.READ_EXTERNAL_STORAGE},
+ PERMISSION_REQUEST_CODE);
+ }
+ // This method executes async and we seem to have no known way to receive the result
+ // (that's why these methods were later added to Cordova), so simply returning ok now.
+ callbackContext.success();
+ }
+
+ public void onActivityResult(int requestCode, int resultCode, Intent data) {
+ if (resultCode == Activity.RESULT_OK && data != null) {
+ int sync = data.getIntExtra("bigdata:synccode", -1);
+ final Bundle bigData = ResultIPC.get().getLargeData(sync);
+
+ ArrayList fileNames = bigData.getStringArrayList("MULTIPLEFILENAMES");
+
+ JSONArray res = new JSONArray(fileNames);
+ callbackContext.success(res);
+
+ } else if (resultCode == Activity.RESULT_CANCELED && data != null) {
+ String error = data.getStringExtra("ERRORMESSAGE");
+ callbackContext.error(error);
+
+ } else if (resultCode == Activity.RESULT_CANCELED) {
+ JSONArray res = new JSONArray();
+ callbackContext.success(res);
+
+ } else {
+ callbackContext.error("No images selected");
+ }
+ }
+
+ /**
+ * Choosing a picture launches another Activity, so we need to implement the
+ * save/restore APIs to handle the case where the CordovaActivity is killed by the OS
+ * before we get the launched Activity's result.
+ *
+ * @see http://cordova.apache.org/docs/en/dev/guide/platforms/android/plugin.html#launching-other-activities
+ */
+ public void onRestoreStateForActivityResult(Bundle state, CallbackContext callbackContext) {
+ this.callbackContext = callbackContext;
+ }
+
+/*
+ @Override
+ public void onRequestPermissionResult(int requestCode,
+ String[] permissions,
+ int[] grantResults) throws JSONException {
+
+ // For now we just have one permission, so things can be kept simple...
+ if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
+ cordova.startActivityForResult(this, imagePickerIntent, 0);
+ } else {
+ // Tell the JS layer that something went wrong...
+ callbackContext.error("Permission denied");
+ }
+ }
+*/
+}
diff --git a/src/android/com/synconset/ImagePicker/ResultIPC.java b/src/android/com/synconset/ImagePicker/ResultIPC.java
new file mode 100644
index 00000000..714dd2e6
--- /dev/null
+++ b/src/android/com/synconset/ImagePicker/ResultIPC.java
@@ -0,0 +1,27 @@
+package com.synconset;
+
+import android.os.Bundle;
+
+public class ResultIPC {
+
+ private static ResultIPC instance;
+
+ public synchronized static ResultIPC get() {
+ if (instance == null) {
+ instance = new ResultIPC ();
+ }
+ return instance;
+ }
+
+ private int sync = 0;
+
+ private Bundle largeData;
+ public int setLargeData(Bundle largeData) {
+ this.largeData = largeData;
+ return ++sync;
+ }
+
+ public Bundle getLargeData(int request) {
+ return (request == sync) ? largeData : null;
+ }
+}
\ No newline at end of file
diff --git a/src/android/ignorelinterrors.gradle b/src/android/ignorelinterrors.gradle
new file mode 100644
index 00000000..c1d8e4bc
--- /dev/null
+++ b/src/android/ignorelinterrors.gradle
@@ -0,0 +1,5 @@
+android {
+ lintOptions {
+ checkReleaseBuilds false
+ }
+}
\ No newline at end of file
diff --git a/src/browser/ImagePicker.js b/src/browser/ImagePicker.js
new file mode 100644
index 00000000..ad17a94a
--- /dev/null
+++ b/src/browser/ImagePicker.js
@@ -0,0 +1,213 @@
+const cacheDirectory = (require('./isChrome')()) ? 'filesystem:' + window.location.origin + '/temporary/' : 'file:///temporary/';
+const nonScalableTypes = ['video', 'gif'];
+
+//Edge needs its own stuff
+if (!HTMLCanvasElement.prototype.toBlob) {
+ Object.defineProperty(HTMLCanvasElement.prototype, 'toBlob', {
+ value: function (callback, type, quality) {
+ var canvas = this;
+ setTimeout(function() {
+ var binStr = atob( canvas.toDataURL(type, quality).split(',')[1] ),
+ len = binStr.length,
+ arr = new Uint8Array(len);
+
+ for (var i = 0; i < len; i++ ) {
+ arr[i] = binStr.charCodeAt(i);
+ }
+
+ callback( new Blob( [arr], {type: type || 'image/png'} ) );
+ });
+ }
+ });
+}
+
+async function getPictures(successCallback, errorCallback, data) {
+ //Set default params just in case
+ var params = {
+ maximumImagesCount: 20,
+ width: 0,
+ height: 0,
+ quality: 100,
+ outputType: 0,
+ allow_video: false
+ }
+
+ //Read params from cordova
+ if (data[0].maximumImagesCount) params.maximumImagesCount = data[0].maximumImagesCount;
+ if (data[0].width) params.width = data[0].width;
+ if (data[0].height) params.height = data[0].height;
+ if (data[0].quality) params.quality = data[0].quality;
+ if (data[0].outputType) params.outputType = data[0].outputType;
+ if (data[0].allow_video) params.allow_video = data[0].allow_video;
+
+ openImagePicker(params.maximumImagesCount, params.width, params.height, params.quality, params.outputType, params.allow_video).then((images) => {
+ successCallback(images);
+ }).catch((error) => {
+ errorCallback(error);
+ });
+}
+
+//hasReadPermission is not relevant in browsers, let's just return a success so everyone is happy
+async function hasReadPermission(successCallback, errorCallback, data) {
+ successCallback();
+}
+
+//requestReadPermission is not relevant in browsers, let's just return a success so everyone is happy
+async function requestReadPermission(successCallback, errorCallback, data) {
+ successCallback();
+}
+
+function openImagePicker(maximumImagesCount, desiredWidth, desiredHeight, quality, outputType, allowVideo) {
+ return new Promise((resolve, reject) => {
+ let fileChooser = document.createElement('input');
+
+ fileChooser.type = 'file';
+ fileChooser.accept = 'image/png, image/jpeg, image/gif';
+ if (allowVideo) fileChooser.accept += ', video/mp4, video/webm, video/ogg';
+
+ if (maximumImagesCount > 1) {
+ fileChooser.setAttribute('multiple', '');
+ }
+
+ fileChooser.onchange = (event) => {
+ let resizeImagePromises = [];
+ let fileNames = []; //We need to store filenames as chrome deletes event.target.files to quickly
+
+ if (event.target.files.length <= maximumImagesCount) {
+ for (let file of event.target.files) {
+ fileNames.push('tmp_' + getDateTimeString() + '_' + file.name);
+ resizeImagePromises.push(resizeImage(file, desiredWidth, desiredHeight, quality, outputType));
+ }
+
+ Promise.all(resizeImagePromises).then((images) => {
+ if (outputType == 0) {
+ //If we need FILE_URI, we need to store the files in the temporary dir of the browser
+ //So we need to write the files there Before
+ let saveBlobPromises = [];
+
+ for (let i = 0; i < images.length; i++) {
+ saveBlobPromises.push(saveBlobToTemporaryFileSystem(images[i], fileNames[i]));
+ }
+
+ Promise.all(saveBlobPromises).then((fileURIs) => {
+ resolve(fileURIs);
+ }).catch((error) => {
+ reject('ERROR_WHILE_CREATING_FILES ' + error);
+ });
+ } else {
+ //resizeImages returns the images in base64, so no need to do anything
+ resolve(images);
+ }
+ }).catch((error) => {
+ reject('ERROR_WHILE_RESIZING ' + error);
+ });
+ } else {
+ reject('TO_MANY_IMAGES');
+ }
+ };
+
+ //Now that the event is hooked we can click it
+ fileChooser.click();
+ });
+}
+
+//Promise resolving either a blob or a base64 string depending on outputType
+function resizeImage(file, desiredWidth, desiredHeight, quality, outputType) {
+ return new Promise((resolve, reject) => {
+ if (outputType == 1 || ( (desiredWidth != 0 || desiredHeight != 0) && !nonScalableTypes.some(type => file.type.includes(type)) ) ) {
+ //BASE64_STRING required or
+ //Scaling required
+ //Base64 string needs to be jpeg formated so even if no scaling is required we need to do the conversion
+ let reader = new FileReader();
+ reader.readAsDataURL(file);
+
+ reader.onload = (e) => {
+ let img = new Image();
+ img.src = e.target.result;
+ img.onload = (pic) => {
+ let canvas = document.createElement('canvas');
+
+ if ((img.height > desiredHeight || img.width > desiredWidth) && (desiredWidth != 0 || desiredHeight != 0)) {
+ if ((img.height / desiredHeight) > (img.width / desiredWidth)) {
+ canvas.width = img.width / (img.height / desiredHeight)
+ canvas.height = desiredHeight;
+ } else {
+ canvas.width = desiredWidth
+ canvas.height = img.height / (img.width / desiredWidth) ;
+ }
+ } else {
+ canvas.width = img.width;
+ canvas.height = img.height;
+ }
+
+ let ctx = canvas.getContext('2d');
+ ctx.drawImage(img, 0, 0, canvas.width, canvas.height);
+
+ if (outputType == 0) {
+ //FILE_URI required, resolve with a blob
+ ctx.canvas.toBlob((blob) => {
+ resolve(blob);
+ }, 'image/jpeg', (quality / 100));
+
+ } else {
+ //BASE64_STRING required, resolve with a string
+ //To be consistent with Android (and iOS) we remove the base64 header from the string
+ resolve(ctx.canvas.toDataURL('image/jpeg', (quality / 100)).replace('data:image/jpeg;base64,', ''));
+ }
+ };
+ };
+
+ reader.onerror = (error) => {
+ reject(error);
+ }
+ } else {
+ //No scaling required and FILE_URI required?
+ //FILE_URI required, resolve with a blob (a file is a blob)
+ resolve(file);
+ }
+ });
+}
+
+function saveBlobToTemporaryFileSystem(blob, fileName) {
+ return new Promise((resolve, reject) => {
+ //Just make sure file extensions matches file type if possible
+ var ext = fileName.split('.').pop();
+
+ if (blob.type && ext) {
+ if (!blob.type.includes(ext.toLowerCase())) fileName = fileName.replace(ext, blob.type.split('/').pop());
+ }
+
+ window.requestFileSystem(window.TEMPORARY, blob.size, (fs) => {
+ fs.root.getFile(fileName, {create: true, exclusive: false}, (fileEntry) => {
+ fileEntry.createWriter((fileWriter) => {
+ fileWriter.onwriteend = (e) => {
+ resolve(cacheDirectory + fileName);
+ };
+
+ fileWriter.onerror = (error) => {
+ reject(error);
+ };
+
+ if (blob) {
+ fileWriter.write(blob);
+ } else {
+ reject('ERROR_NO_DATA_TO_WRITE');
+ }
+ });
+ });
+ });
+ });
+}
+
+function getDateTimeString() {
+ let d = new Date();
+ return d.getDate().toString() + d.getMonth().toString() + d.getFullYear().toString() + '_' + d.getHours().toString() + d.getMinutes().toString() + d.getSeconds().toString();
+}
+
+module.exports = {
+ getPictures: getPictures,
+ hasReadPermission: hasReadPermission,
+ requestReadPermission: requestReadPermission
+};
+
+require( "cordova/exec/proxy" ).add( "ImagePicker", module.exports );
diff --git a/src/ios/ELCImagePicker/ELCAlbumPickerController.h b/src/ios/ELCImagePicker/ELCAlbumPickerController.h
deleted file mode 100644
index 151c0f77..00000000
--- a/src/ios/ELCImagePicker/ELCAlbumPickerController.h
+++ /dev/null
@@ -1,24 +0,0 @@
-//
-// AlbumPickerController.h
-//
-// Created by ELC on 2/15/11.
-// Copyright 2011 ELC Technologies. All rights reserved.
-//
-
-#import
-#import
-#import "ELCAssetSelectionDelegate.h"
-#import "ELCAssetPickerFilterDelegate.h"
-
-@interface ELCAlbumPickerController : UITableViewController
-
-@property (nonatomic, weak) id parent;
-@property (nonatomic, strong) NSMutableArray *assetGroups;
-@property (nonatomic, assign) BOOL singleSelection;
-@property (nonatomic, assign) BOOL immediateReturn;
-
-// optional, can be used to filter the assets displayed
-@property (nonatomic, weak) id assetPickerFilterDelegate;
-
-@end
-
diff --git a/src/ios/ELCImagePicker/ELCAlbumPickerController.m b/src/ios/ELCImagePicker/ELCAlbumPickerController.m
deleted file mode 100644
index 8317d97d..00000000
--- a/src/ios/ELCImagePicker/ELCAlbumPickerController.m
+++ /dev/null
@@ -1,164 +0,0 @@
-//
-// AlbumPickerController.m
-//
-// Created by ELC on 2/15/11.
-// Copyright 2011 ELC Technologies. All rights reserved.
-//
-
-#import "ELCAlbumPickerController.h"
-#import "ELCImagePickerController.h"
-#import "ELCAssetTablePicker.h"
-
-@interface ELCAlbumPickerController ()
-
-@property (nonatomic, strong) ALAssetsLibrary *library;
-
-@end
-
-@implementation ELCAlbumPickerController
-
-//Using auto synthesizers
-
-#pragma mark -
-#pragma mark View lifecycle
-
-- (void)viewDidLoad
-{
- [super viewDidLoad];
-
- [self.navigationItem setTitle:@"Loading..."];
-
- UIBarButtonItem *cancelButton = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemCancel target:self.parent action:@selector(cancelImagePicker)];
- [self.navigationItem setRightBarButtonItem:cancelButton];
-
- NSMutableArray *tempArray = [[NSMutableArray alloc] init];
- self.assetGroups = tempArray;
-
- ALAssetsLibrary *assetLibrary = [[ALAssetsLibrary alloc] init];
- self.library = assetLibrary;
-
- // Load Albums into assetGroups
- dispatch_async(dispatch_get_main_queue(), ^
- {
- @autoreleasepool {
-
- // Group enumerator Block
- void (^assetGroupEnumerator)(ALAssetsGroup *, BOOL *) = ^(ALAssetsGroup *group, BOOL *stop)
- {
- if (group == nil) {
- return;
- }
-
- // added fix for camera albums order
- NSString *sGroupPropertyName = (NSString *)[group valueForProperty:ALAssetsGroupPropertyName];
- NSUInteger nType = [[group valueForProperty:ALAssetsGroupPropertyType] intValue];
-
- if ([[sGroupPropertyName lowercaseString] isEqualToString:@"camera roll"] && nType == ALAssetsGroupSavedPhotos) {
- [self.assetGroups insertObject:group atIndex:0];
- }
- else {
- [self.assetGroups addObject:group];
- }
-
- // Reload albums
- [self performSelectorOnMainThread:@selector(reloadTableView) withObject:nil waitUntilDone:YES];
- };
-
- // Group Enumerator Failure Block
- void (^assetGroupEnumberatorFailure)(NSError *) = ^(NSError *error) {
-
- UIAlertView * alert = [[UIAlertView alloc] initWithTitle:@"Error" message:[NSString stringWithFormat:@"Album Error: %@ - %@", [error localizedDescription], [error localizedRecoverySuggestion]] delegate:nil cancelButtonTitle:@"Ok" otherButtonTitles:nil];
- [alert show];
-
- NSLog(@"A problem occured %@", [error description]);
- };
-
- // Enumerate Albums
- [self.library enumerateGroupsWithTypes:ALAssetsGroupAll
- usingBlock:assetGroupEnumerator
- failureBlock:assetGroupEnumberatorFailure];
-
- }
- });
-}
-
-- (void)reloadTableView
-{
- [self.tableView reloadData];
- [self.navigationItem setTitle:@"Select an Album"];
-}
-
-- (BOOL)shouldSelectAsset:(ELCAsset *)asset previousCount:(NSUInteger)previousCount
-{
- return [self.parent shouldSelectAsset:asset previousCount:previousCount];
-}
-
-- (void)selectedAssets:(NSArray*)assets
-{
- [_parent selectedAssets:assets];
-}
-
-#pragma mark -
-#pragma mark Table view data source
-
-- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
-{
- // Return the number of sections.
- return 1;
-}
-
-
-- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
-{
- // Return the number of rows in the section.
- return [self.assetGroups count];
-}
-
-
-// Customize the appearance of table view cells.
-- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
-{
- static NSString *CellIdentifier = @"Cell";
-
- UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
- if (cell == nil) {
- cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier];
- }
-
- // Get count
- ALAssetsGroup *g = (ALAssetsGroup*)[self.assetGroups objectAtIndex:indexPath.row];
- [g setAssetsFilter:[ALAssetsFilter allPhotos]];
- NSInteger gCount = [g numberOfAssets];
-
- cell.textLabel.text = [NSString stringWithFormat:@"%@ (%ld)",[g valueForProperty:ALAssetsGroupPropertyName], (long)gCount];
- [cell.imageView setImage:[UIImage imageWithCGImage:[(ALAssetsGroup*)[self.assetGroups objectAtIndex:indexPath.row] posterImage]]];
- [cell setAccessoryType:UITableViewCellAccessoryDisclosureIndicator];
-
- return cell;
-}
-
-#pragma mark -
-#pragma mark Table view delegate
-
-- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
-{
- ELCAssetTablePicker *picker = [[ELCAssetTablePicker alloc] initWithNibName: nil bundle: nil];
- picker.parent = self;
-
- picker.assetGroup = [self.assetGroups objectAtIndex:indexPath.row];
- [picker.assetGroup setAssetsFilter:[ALAssetsFilter allPhotos]];
-
- picker.assetPickerFilterDelegate = self.assetPickerFilterDelegate;
- picker.immediateReturn = self.immediateReturn;
- picker.singleSelection = self.singleSelection;
-
- [self.navigationController pushViewController:picker animated:YES];
-}
-
-- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
-{
- return 57;
-}
-
-@end
-
diff --git a/src/ios/ELCImagePicker/ELCAsset.h b/src/ios/ELCImagePicker/ELCAsset.h
deleted file mode 100644
index ebef3685..00000000
--- a/src/ios/ELCImagePicker/ELCAsset.h
+++ /dev/null
@@ -1,29 +0,0 @@
-//
-// Asset.h
-//
-// Created by ELC on 2/15/11.
-// Copyright 2011 ELC Technologies. All rights reserved.
-//
-
-#import
-#import
-
-@class ELCAsset;
-
-@protocol ELCAssetDelegate
-
-@optional
-- (void)assetSelected:(ELCAsset *)asset;
-- (BOOL)shouldSelectAsset:(ELCAsset *)asset;
-@end
-
-
-@interface ELCAsset : NSObject
-
-@property (nonatomic, strong) ALAsset *asset;
-@property (nonatomic, weak) id parent;
-@property (nonatomic, assign) BOOL selected;
-
-- (id)initWithAsset:(ALAsset *)asset;
-
-@end
\ No newline at end of file
diff --git a/src/ios/ELCImagePicker/ELCAsset.m b/src/ios/ELCImagePicker/ELCAsset.m
deleted file mode 100644
index 12413af5..00000000
--- a/src/ios/ELCImagePicker/ELCAsset.m
+++ /dev/null
@@ -1,49 +0,0 @@
-//
-// Asset.m
-//
-// Created by ELC on 2/15/11.
-// Copyright 2011 ELC Technologies. All rights reserved.
-//
-
-#import "ELCAsset.h"
-#import "ELCAssetTablePicker.h"
-
-@implementation ELCAsset
-
-//Using auto synthesizers
-
-- (id)initWithAsset:(ALAsset*)asset
-{
- self = [super init];
- if (self) {
- self.asset = asset;
- _selected = NO;
- }
- return self;
-}
-
-- (void)toggleSelection
-{
- self.selected = !self.selected;
-}
-
-- (void)setSelected:(BOOL)selected
-{
- if (selected) {
- if ([_parent respondsToSelector:@selector(shouldSelectAsset:)]) {
- if (![_parent shouldSelectAsset:self]) {
- return;
- }
- }
- }
- _selected = selected;
- if (selected) {
- if (_parent != nil && [_parent respondsToSelector:@selector(assetSelected:)]) {
- [_parent assetSelected:self];
- }
- }
-}
-
-
-@end
-
diff --git a/src/ios/ELCImagePicker/ELCAssetCell.h b/src/ios/ELCImagePicker/ELCAssetCell.h
deleted file mode 100644
index b9071562..00000000
--- a/src/ios/ELCImagePicker/ELCAssetCell.h
+++ /dev/null
@@ -1,15 +0,0 @@
-//
-// AssetCell.h
-//
-// Created by ELC on 2/15/11.
-// Copyright 2011 ELC Technologies. All rights reserved.
-//
-
-#import
-
-
-@interface ELCAssetCell : UITableViewCell
-
-- (void)setAssets:(NSArray *)assets;
-
-@end
diff --git a/src/ios/ELCImagePicker/ELCAssetCell.m b/src/ios/ELCImagePicker/ELCAssetCell.m
deleted file mode 100644
index 2a6706aa..00000000
--- a/src/ios/ELCImagePicker/ELCAssetCell.m
+++ /dev/null
@@ -1,117 +0,0 @@
-//
-// AssetCell.m
-//
-// Created by ELC on 2/15/11.
-// Copyright 2011 ELC Technologies. All rights reserved.
-//
-
-#import "ELCAssetCell.h"
-#import "ELCAsset.h"
-
-@interface ELCAssetCell ()
-
-@property (nonatomic, strong) NSArray *rowAssets;
-@property (nonatomic, strong) NSMutableArray *imageViewArray;
-@property (nonatomic, strong) NSMutableArray *overlayViewArray;
-
-@end
-
-@implementation ELCAssetCell
-
-//Using auto synthesizers
-
-- (id)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString *)reuseIdentifier
-{
- self = [super initWithStyle:UITableViewCellStyleDefault reuseIdentifier:reuseIdentifier];
- if (self) {
- UITapGestureRecognizer *tapRecognizer = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(cellTapped:)];
- [self addGestureRecognizer:tapRecognizer];
-
- NSMutableArray *mutableArray = [[NSMutableArray alloc] initWithCapacity:4];
- self.imageViewArray = mutableArray;
-
- NSMutableArray *overlayArray = [[NSMutableArray alloc] initWithCapacity:4];
- self.overlayViewArray = overlayArray;
- }
- return self;
-}
-
-- (void)setAssets:(NSArray *)assets
-{
- self.rowAssets = assets;
- for (UIImageView *view in _imageViewArray) {
- [view removeFromSuperview];
- }
- for (UIImageView *view in _overlayViewArray) {
- [view removeFromSuperview];
- }
- //set up a pointer here so we don't keep calling [UIImage imageNamed:] if creating overlays
- UIImage *overlayImage = nil;
- for (int i = 0; i < [_rowAssets count]; ++i) {
-
- ELCAsset *asset = [_rowAssets objectAtIndex:i];
-
- if (i < [_imageViewArray count]) {
- UIImageView *imageView = [_imageViewArray objectAtIndex:i];
- imageView.image = [UIImage imageWithCGImage:asset.asset.thumbnail];
- } else {
- UIImageView *imageView = [[UIImageView alloc] initWithImage:[UIImage imageWithCGImage:asset.asset.thumbnail]];
- [_imageViewArray addObject:imageView];
- }
-
- if (i < [_overlayViewArray count]) {
- UIImageView *overlayView = [_overlayViewArray objectAtIndex:i];
- overlayView.hidden = asset.selected ? NO : YES;
- } else {
- if (overlayImage == nil) {
- overlayImage = [UIImage imageNamed:@"Overlay.png"];
- }
- UIImageView *overlayView = [[UIImageView alloc] initWithImage:overlayImage];
- [_overlayViewArray addObject:overlayView];
- overlayView.hidden = asset.selected ? NO : YES;
- }
- }
-}
-
-- (void)cellTapped:(UITapGestureRecognizer *)tapRecognizer
-{
- CGPoint point = [tapRecognizer locationInView:self];
- CGFloat totalWidth = self.rowAssets.count * 75 + (self.rowAssets.count - 1) * 4;
- CGFloat startX = (self.bounds.size.width - totalWidth) / 2;
-
- CGRect frame = CGRectMake(startX, 2, 75, 75);
-
- for (int i = 0; i < [_rowAssets count]; ++i) {
- if (CGRectContainsPoint(frame, point)) {
- ELCAsset *asset = [_rowAssets objectAtIndex:i];
- asset.selected = !asset.selected;
- UIImageView *overlayView = [_overlayViewArray objectAtIndex:i];
- overlayView.hidden = !asset.selected;
- break;
- }
- frame.origin.x = frame.origin.x + frame.size.width + 4;
- }
-}
-
-- (void)layoutSubviews
-{
- CGFloat totalWidth = self.rowAssets.count * 75 + (self.rowAssets.count - 1) * 4;
- CGFloat startX = (self.bounds.size.width - totalWidth) / 2;
-
- CGRect frame = CGRectMake(startX, 2, 75, 75);
-
- for (int i = 0; i < [_rowAssets count]; ++i) {
- UIImageView *imageView = [_imageViewArray objectAtIndex:i];
- [imageView setFrame:frame];
- [self addSubview:imageView];
-
- UIImageView *overlayView = [_overlayViewArray objectAtIndex:i];
- [overlayView setFrame:frame];
- [self addSubview:overlayView];
-
- frame.origin.x = frame.origin.x + frame.size.width + 4;
- }
-}
-
-
-@end
diff --git a/src/ios/ELCImagePicker/ELCAssetPickerFilterDelegate.h b/src/ios/ELCImagePicker/ELCAssetPickerFilterDelegate.h
deleted file mode 100644
index 478c256f..00000000
--- a/src/ios/ELCImagePicker/ELCAssetPickerFilterDelegate.h
+++ /dev/null
@@ -1,12 +0,0 @@
-//
-// ELCAssetPickerFilterDelegate.h
-
-@class ELCAsset;
-@class ELCAssetTablePicker;
-
-@protocol ELCAssetPickerFilterDelegate
-
-// respond YES/NO to filter out (not show the asset)
--(BOOL)assetTablePicker:(ELCAssetTablePicker *)picker isAssetFilteredOut:(ELCAsset *)elcAsset;
-
-@end
\ No newline at end of file
diff --git a/src/ios/ELCImagePicker/ELCAssetSelectionDelegate.h b/src/ios/ELCImagePicker/ELCAssetSelectionDelegate.h
deleted file mode 100644
index 47d85d77..00000000
--- a/src/ios/ELCImagePicker/ELCAssetSelectionDelegate.h
+++ /dev/null
@@ -1,18 +0,0 @@
-//
-// ELCAssetSelectionDelegate.h
-// ELCImagePickerDemo
-//
-// Created by JN on 9/6/12.
-// Copyright (c) 2012 ELC Technologies. All rights reserved.
-//
-
-#import
-
-@class ELCAsset;
-
-@protocol ELCAssetSelectionDelegate
-
-- (void)selectedAssets:(NSArray *)assets;
-- (BOOL)shouldSelectAsset:(ELCAsset *)asset previousCount:(NSUInteger)previousCount;
-
-@end
diff --git a/src/ios/ELCImagePicker/ELCAssetTablePicker.h b/src/ios/ELCImagePicker/ELCAssetTablePicker.h
deleted file mode 100644
index 8148ef64..00000000
--- a/src/ios/ELCImagePicker/ELCAssetTablePicker.h
+++ /dev/null
@@ -1,31 +0,0 @@
-//
-// ELCAssetTablePicker.h
-//
-// Created by ELC on 2/15/11.
-// Copyright 2011 ELC Technologies. All rights reserved.
-//
-
-#import
-#import
-#import "ELCAsset.h"
-#import "ELCAssetSelectionDelegate.h"
-#import "ELCAssetPickerFilterDelegate.h"
-
-@interface ELCAssetTablePicker : UITableViewController
-
-@property (nonatomic, weak) id parent;
-@property (nonatomic, strong) ALAssetsGroup *assetGroup;
-@property (nonatomic, strong) NSMutableArray *elcAssets;
-@property (nonatomic, strong) IBOutlet UILabel *selectedAssetsLabel;
-@property (nonatomic, assign) BOOL singleSelection;
-@property (nonatomic, assign) BOOL immediateReturn;
-
-// optional, can be used to filter the assets displayed
-@property(nonatomic, weak) id assetPickerFilterDelegate;
-
-- (int)totalSelectedAssets;
-- (void)preparePhotos;
-
-- (void)doneAction:(id)sender;
-
-@end
\ No newline at end of file
diff --git a/src/ios/ELCImagePicker/ELCAssetTablePicker.m b/src/ios/ELCImagePicker/ELCAssetTablePicker.m
deleted file mode 100644
index f7a5a5ab..00000000
--- a/src/ios/ELCImagePicker/ELCAssetTablePicker.m
+++ /dev/null
@@ -1,217 +0,0 @@
-//
-// ELCAssetTablePicker.m
-//
-// Created by ELC on 2/15/11.
-// Copyright 2011 ELC Technologies. All rights reserved.
-//
-
-#import "ELCAssetTablePicker.h"
-#import "ELCAssetCell.h"
-#import "ELCAsset.h"
-#import "ELCAlbumPickerController.h"
-
-@interface ELCAssetTablePicker ()
-
-@property (nonatomic, assign) int columns;
-
-@end
-
-@implementation ELCAssetTablePicker
-
-//Using auto synthesizers
-
-- (id)init
-{
- self = [super init];
- if (self) {
- //Sets a reasonable default bigger then 0 for columns
- //So that we don't have a divide by 0 scenario
- self.columns = 4;
- }
- return self;
-}
-
-- (void)viewDidLoad
-{
- [self.tableView setSeparatorStyle:UITableViewCellSeparatorStyleNone];
- [self.tableView setAllowsSelection:NO];
-
- NSMutableArray *tempArray = [[NSMutableArray alloc] init];
- self.elcAssets = tempArray;
-
- if (self.immediateReturn) {
-
- } else {
- UIBarButtonItem *doneButtonItem = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemDone target:self action:@selector(doneAction:)];
- [self.navigationItem setRightBarButtonItem:doneButtonItem];
- [self.navigationItem setTitle:@"Loading..."];
- }
-
- [self performSelectorInBackground:@selector(preparePhotos) withObject:nil];
-}
-
-- (void)viewWillAppear:(BOOL)animated
-{
- [super viewWillAppear:animated];
- self.columns = self.view.bounds.size.width / 80;
-}
-
-- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation
-{
- return YES;
-}
-
-- (void)didRotateFromInterfaceOrientation:(UIInterfaceOrientation)fromInterfaceOrientation
-{
- [super didRotateFromInterfaceOrientation:fromInterfaceOrientation];
- self.columns = self.view.bounds.size.width / 80;
- [self.tableView reloadData];
-}
-
-- (void)preparePhotos
-{
- @autoreleasepool {
-
- [self.assetGroup enumerateAssetsUsingBlock:^(ALAsset *result, NSUInteger index, BOOL *stop) {
-
- if (result == nil) {
- return;
- }
-
- ELCAsset *elcAsset = [[ELCAsset alloc] initWithAsset:result];
- [elcAsset setParent:self];
-
- BOOL isAssetFiltered = NO;
- if (self.assetPickerFilterDelegate &&
- [self.assetPickerFilterDelegate respondsToSelector:@selector(assetTablePicker:isAssetFilteredOut:)])
- {
- isAssetFiltered = [self.assetPickerFilterDelegate assetTablePicker:self isAssetFilteredOut:(ELCAsset*)elcAsset];
- }
-
- if (!isAssetFiltered) {
- [self.elcAssets addObject:elcAsset];
- }
-
- }];
-
- dispatch_sync(dispatch_get_main_queue(), ^{
- [self.tableView reloadData];
- // scroll to bottom
- long section = [self numberOfSectionsInTableView:self.tableView] - 1;
- long row = [self tableView:self.tableView numberOfRowsInSection:section] - 1;
- if (section >= 0 && row >= 0) {
- NSIndexPath *ip = [NSIndexPath indexPathForRow:row
- inSection:section];
- [self.tableView scrollToRowAtIndexPath:ip
- atScrollPosition:UITableViewScrollPositionBottom
- animated:NO];
- }
-
- [self.navigationItem setTitle:self.singleSelection ? @"Pick Photo" : @"Pick Photos"];
- });
- }
-}
-
-- (void)doneAction:(id)sender
-{
- NSMutableArray *selectedAssetsImages = [[NSMutableArray alloc] init];
-
- for (ELCAsset *elcAsset in self.elcAssets) {
- if ([elcAsset selected]) {
- [selectedAssetsImages addObject:[elcAsset asset]];
- }
- }
- [self.parent selectedAssets:selectedAssetsImages];
-}
-
-
-- (BOOL)shouldSelectAsset:(ELCAsset *)asset
-{
- NSUInteger selectionCount = 0;
- for (ELCAsset *elcAsset in self.elcAssets) {
- if (elcAsset.selected) selectionCount++;
- }
- BOOL shouldSelect = YES;
- if ([self.parent respondsToSelector:@selector(shouldSelectAsset:previousCount:)]) {
- shouldSelect = [self.parent shouldSelectAsset:asset previousCount:selectionCount];
- }
- return shouldSelect;
-}
-
-- (void)assetSelected:(ELCAsset *)asset
-{
- if (self.singleSelection) {
-
- for (ELCAsset *elcAsset in self.elcAssets) {
- if (asset != elcAsset) {
- elcAsset.selected = NO;
- }
- }
- }
- if (self.immediateReturn) {
- NSArray *singleAssetArray = @[asset.asset];
- [(NSObject *)self.parent performSelector:@selector(selectedAssets:) withObject:singleAssetArray afterDelay:0];
- }
-}
-
-#pragma mark UITableViewDataSource Delegate Methods
-
-- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
-{
- // Return the number of sections.
- return 1;
-}
-
-
-- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
-{
- if (self.columns <= 0) { //Sometimes called before we know how many columns we have
- self.columns = 4;
- }
- NSInteger numRows = ceil([self.elcAssets count] / (float)self.columns);
- return numRows;
-}
-
-- (NSArray *)assetsForIndexPath:(NSIndexPath *)path
-{
- long index = path.row * self.columns;
- long length = MIN(self.columns, [self.elcAssets count] - index);
- return [self.elcAssets subarrayWithRange:NSMakeRange(index, length)];
-}
-
-// Customize the appearance of table view cells.
-- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
-{
- static NSString *CellIdentifier = @"Cell";
-
- ELCAssetCell *cell = (ELCAssetCell*)[tableView dequeueReusableCellWithIdentifier:CellIdentifier];
-
- if (cell == nil) {
- cell = [[ELCAssetCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier];
- }
-
- [cell setAssets:[self assetsForIndexPath:indexPath]];
-
- return cell;
-}
-
-- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
-{
- return 79;
-}
-
-- (int)totalSelectedAssets
-{
- int count = 0;
-
- for (ELCAsset *asset in self.elcAssets) {
- if (asset.selected) {
- count++;
- }
- }
-
- return count;
-}
-
-
-@end
diff --git a/src/ios/ELCImagePicker/ELCImagePickerController.h b/src/ios/ELCImagePicker/ELCImagePickerController.h
deleted file mode 100644
index 0d1b54e5..00000000
--- a/src/ios/ELCImagePicker/ELCImagePickerController.h
+++ /dev/null
@@ -1,48 +0,0 @@
-//
-// ELCImagePickerController.h
-// ELCImagePickerDemo
-//
-// Created by ELC on 9/9/10.
-// Copyright 2010 ELC Technologies. All rights reserved.
-//
-
-#import
-#import "ELCAssetSelectionDelegate.h"
-
-@class ELCImagePickerController;
-@class ELCAlbumPickerController;
-
-@protocol ELCImagePickerControllerDelegate
-
-/**
- * Called with the picker the images were selected from, as well as an array of dictionary's
- * containing keys for ALAssetPropertyLocation, ALAssetPropertyType,
- * UIImagePickerControllerOriginalImage, and UIImagePickerControllerReferenceURL.
- * @param picker
- * @param info An NSArray containing dictionary's with the key UIImagePickerControllerOriginalImage, which is a rotated, and sized for the screen 'default representation' of the image selected. If you want to get the original image, use the UIImagePickerControllerReferenceURL key.
- */
-- (void)elcImagePickerController:(ELCImagePickerController *)picker didFinishPickingMediaWithInfo:(NSArray *)info;
-
-/**
- * Called when image selection was cancelled, by tapping the 'Cancel' BarButtonItem.
- */
-- (void)elcImagePickerControllerDidCancel:(ELCImagePickerController *)picker;
-
-@end
-
-@interface ELCImagePickerController : UINavigationController
-
-@property (nonatomic, weak) id imagePickerDelegate;
-@property (nonatomic, assign) NSInteger maximumImagesCount;
-
-/**
- * YES if the picker should return the original image,
- * or NO for an image suitable for displaying full screen on the device.
- */
-@property (nonatomic, assign) BOOL returnsOriginalImage;
-
-- (id)initImagePicker;
-- (void)cancelImagePicker;
-
-@end
-
diff --git a/src/ios/ELCImagePicker/ELCImagePickerController.m b/src/ios/ELCImagePicker/ELCImagePickerController.m
deleted file mode 100644
index 7b2a931a..00000000
--- a/src/ios/ELCImagePicker/ELCImagePickerController.m
+++ /dev/null
@@ -1,95 +0,0 @@
-//
-// ELCImagePickerController.m
-// ELCImagePickerDemo
-//
-// Created by ELC on 9/9/10.
-// Copyright 2010 ELC Technologies. All rights reserved.
-//
-
-#import "ELCImagePickerController.h"
-#import "ELCAsset.h"
-#import "ELCAssetCell.h"
-#import "ELCAssetTablePicker.h"
-#import "ELCAlbumPickerController.h"
-#import
-
-@implementation ELCImagePickerController
-
-//Using auto synthesizers
-
-- (id)initImagePicker
-{
- ELCAlbumPickerController *albumPicker = [[ELCAlbumPickerController alloc] initWithStyle:UITableViewStylePlain];
-
- self = [super initWithRootViewController:albumPicker];
- if (self) {
- self.maximumImagesCount = 4;
- [albumPicker setParent:self];
- }
- return self;
-}
-
-- (id)initWithRootViewController:(UIViewController *)rootViewController
-{
- self = [super initWithRootViewController:rootViewController];
- if (self) {
- self.maximumImagesCount = 4;
- }
- return self;
-}
-
-- (void)cancelImagePicker
-{
- if ([_imagePickerDelegate respondsToSelector:@selector(elcImagePickerControllerDidCancel:)]) {
- [_imagePickerDelegate performSelector:@selector(elcImagePickerControllerDidCancel:) withObject:self];
- }
-}
-
-- (BOOL)shouldSelectAsset:(ELCAsset *)asset previousCount:(NSUInteger)previousCount
-{
- BOOL shouldSelect = previousCount < self.maximumImagesCount;
- if (!shouldSelect) {
- NSString *title = [NSString stringWithFormat:NSLocalizedString(@"Maximum %d photos.", nil), self.maximumImagesCount];
- NSString *message = [NSString stringWithFormat:NSLocalizedString(@"You can only select %d photos at a time.", nil), self.maximumImagesCount];
- [[[UIAlertView alloc] initWithTitle:title
- message:message
- delegate:nil
- cancelButtonTitle:nil
- otherButtonTitles:NSLocalizedString(@"Okay", nil), nil] show];
- }
- return shouldSelect;
-}
-
-- (void)selectedAssets:(NSArray *)assets
-{
- NSMutableArray *returnArray = [[NSMutableArray alloc] init];
-
- for(ALAsset *asset in assets) {
- id obj = [asset valueForProperty:ALAssetPropertyType];
- if (!obj) {
- continue;
- }
- NSMutableDictionary *workingDictionary = [[NSMutableDictionary alloc] init];
- [workingDictionary setObject:asset forKey:@"ALAsset"];
- [workingDictionary setObject:[[asset valueForProperty:ALAssetPropertyURLs] valueForKey:[[[asset valueForProperty:ALAssetPropertyURLs] allKeys] objectAtIndex:0]] forKey:UIImagePickerControllerReferenceURL];
-
- [returnArray addObject:workingDictionary];
-
- }
- if (_imagePickerDelegate != nil && [_imagePickerDelegate respondsToSelector:@selector(elcImagePickerController:didFinishPickingMediaWithInfo:)]) {
- [_imagePickerDelegate performSelector:@selector(elcImagePickerController:didFinishPickingMediaWithInfo:) withObject:self withObject:returnArray];
- } else {
- [self popToRootViewControllerAnimated:NO];
- }
-}
-
-- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation
-{
- if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad) {
- return YES;
- } else {
- return toInterfaceOrientation != UIInterfaceOrientationPortraitUpsideDown;
- }
-}
-
-@end
diff --git a/src/ios/ELCImagePicker/Resources/ELCAlbumPickerController.xib b/src/ios/ELCImagePicker/Resources/ELCAlbumPickerController.xib
deleted file mode 100644
index 4170520f..00000000
--- a/src/ios/ELCImagePicker/Resources/ELCAlbumPickerController.xib
+++ /dev/null
@@ -1,374 +0,0 @@
-
-
-
- 1024
- 10F569
- 804
- 1038.29
- 461.00
-
-
-
-
-
- YES
-
- IBFilesOwner
- IBCocoaTouchFramework
-
-
- IBFirstResponder
- IBCocoaTouchFramework
-
-
-
- 274
- {320, 416}
-
-
- 3
- MQA
-
- NO
- YES
- NO
-
-
- NO
-
- IBCocoaTouchFramework
- NO
- 1
- 0
- YES
- 44
- 22
- 22
-
-
-
-
- YES
-
-
- view
-
-
-
- 5
-
-
-
- dataSource
-
-
-
- 6
-
-
-
- delegate
-
-
-
- 7
-
-
-
-
- YES
-
- 0
-
-
-
-
-
- -1
-
-
- File's Owner
-
-
- -2
-
-
-
-
- 4
-
-
-
-
-
-
- YES
-
- YES
- -1.CustomClassName
- -2.CustomClassName
- 4.IBEditorWindowLastContentRect
- 4.IBPluginDependency
-
-
- YES
- AlbumPickerController
- UIResponder
- {{329, 504}, {320, 480}}
- com.apple.InterfaceBuilder.IBCocoaTouchPlugin
-
-
-
- YES
-
-
- YES
-
-
-
-
- YES
-
-
- YES
-
-
-
- 7
-
-
-
- YES
-
- AlbumPickerController
- UITableViewController
-
- IBProjectSource
- Classes/AlbumPickerController.h
-
-
-
-
- YES
-
- NSObject
-
- IBFrameworkSource
- Foundation.framework/Headers/NSError.h
-
-
-
- NSObject
-
- IBFrameworkSource
- Foundation.framework/Headers/NSFileManager.h
-
-
-
- NSObject
-
- IBFrameworkSource
- Foundation.framework/Headers/NSKeyValueCoding.h
-
-
-
- NSObject
-
- IBFrameworkSource
- Foundation.framework/Headers/NSKeyValueObserving.h
-
-
-
- NSObject
-
- IBFrameworkSource
- Foundation.framework/Headers/NSKeyedArchiver.h
-
-
-
- NSObject
-
- IBFrameworkSource
- Foundation.framework/Headers/NSObject.h
-
-
-
- NSObject
-
- IBFrameworkSource
- Foundation.framework/Headers/NSRunLoop.h
-
-
-
- NSObject
-
- IBFrameworkSource
- Foundation.framework/Headers/NSThread.h
-
-
-
- NSObject
-
- IBFrameworkSource
- Foundation.framework/Headers/NSURL.h
-
-
-
- NSObject
-
- IBFrameworkSource
- Foundation.framework/Headers/NSURLConnection.h
-
-
-
- NSObject
-
- IBFrameworkSource
- UIKit.framework/Headers/UIAccessibility.h
-
-
-
- NSObject
-
- IBFrameworkSource
- UIKit.framework/Headers/UINibLoading.h
-
-
-
- NSObject
-
- IBFrameworkSource
- UIKit.framework/Headers/UIResponder.h
-
-
-
- UIResponder
- NSObject
-
-
-
- UIScrollView
- UIView
-
- IBFrameworkSource
- UIKit.framework/Headers/UIScrollView.h
-
-
-
- UISearchBar
- UIView
-
- IBFrameworkSource
- UIKit.framework/Headers/UISearchBar.h
-
-
-
- UISearchDisplayController
- NSObject
-
- IBFrameworkSource
- UIKit.framework/Headers/UISearchDisplayController.h
-
-
-
- UITableView
- UIScrollView
-
- IBFrameworkSource
- UIKit.framework/Headers/UITableView.h
-
-
-
- UITableViewController
- UIViewController
-
- IBFrameworkSource
- UIKit.framework/Headers/UITableViewController.h
-
-
-
- UIView
-
- IBFrameworkSource
- UIKit.framework/Headers/UITextField.h
-
-
-
- UIView
- UIResponder
-
- IBFrameworkSource
- UIKit.framework/Headers/UIView.h
-
-
-
- UIViewController
-
- IBFrameworkSource
- UIKit.framework/Headers/UINavigationController.h
-
-
-
- UIViewController
-
- IBFrameworkSource
- UIKit.framework/Headers/UIPopoverController.h
-
-
-
- UIViewController
-
- IBFrameworkSource
- UIKit.framework/Headers/UISplitViewController.h
-
-
-
- UIViewController
-
- IBFrameworkSource
- UIKit.framework/Headers/UITabBarController.h
-
-
-
- UIViewController
- UIResponder
-
- IBFrameworkSource
- UIKit.framework/Headers/UIViewController.h
-
-
-
-
- 0
- IBCocoaTouchFramework
-
- com.apple.InterfaceBuilder.CocoaTouchPlugin.iPhoneOS
-
-
-
- com.apple.InterfaceBuilder.CocoaTouchPlugin.InterfaceBuilder3
-
-
- YES
- ../ELCImagePickerDemo.xcodeproj
- 3
- 123
-
-
diff --git a/src/ios/ELCImagePicker/Resources/ELCAssetPicker.xib b/src/ios/ELCImagePicker/Resources/ELCAssetPicker.xib
deleted file mode 100644
index 5d5e2b57..00000000
--- a/src/ios/ELCImagePicker/Resources/ELCAssetPicker.xib
+++ /dev/null
@@ -1,435 +0,0 @@
-
-
-
- 1024
- 10F569
- 804
- 1038.29
- 461.00
-
- com.apple.InterfaceBuilder.IBCocoaTouchPlugin
- 123
-
-
- YES
-
-
-
- YES
- com.apple.InterfaceBuilder.IBCocoaTouchPlugin
-
-
- YES
-
- YES
-
-
- YES
-
-
-
- YES
-
- IBFilesOwner
- IBCocoaTouchFramework
-
-
- IBFirstResponder
- IBCocoaTouchFramework
-
-
-
- 274
-
- YES
-
-
- 268
- {320, 416}
-
-
- 1
- MSAxIDEAA
-
- YES
- YES
- IBCocoaTouchFramework
-
-
- {320, 416}
-
-
- 3
- MQA
-
- 2
-
-
-
-
- NO
-
- IBCocoaTouchFramework
-
-
-
-
- YES
-
-
- view
-
-
-
- 3
-
-
-
- scrollview
-
-
-
- 7
-
-
-
-
- YES
-
- 0
-
-
-
-
-
- 1
-
-
- YES
-
-
-
-
-
- -1
-
-
- File's Owner
-
-
- -2
-
-
-
-
- 6
-
-
- YES
-
-
-
-
-
-
- YES
-
- YES
- -1.CustomClassName
- -2.CustomClassName
- 1.IBEditorWindowLastContentRect
- 1.IBPluginDependency
- 6.IBPluginDependency
- 6.IBViewBoundsToFrameTransform
-
-
- YES
- AssetPicker
- UIResponder
- {{575, 376}, {320, 480}}
- com.apple.InterfaceBuilder.IBCocoaTouchPlugin
- com.apple.InterfaceBuilder.IBCocoaTouchPlugin
-
- P4AAAL+AAAAAAAAAw88AAA
-
-
-
-
- YES
-
-
- YES
-
-
-
-
- YES
-
-
- YES
-
-
-
- 15
-
-
-
- YES
-
- AssetPicker
- UIViewController
-
- dismiss:
- id
-
-
- dismiss:
-
- dismiss:
- id
-
-
-
- YES
-
- YES
- parent
- scrollview
- selectedAssetsLabel
-
-
- YES
- id
- UIScrollView
- UILabel
-
-
-
- YES
-
- YES
- parent
- scrollview
- selectedAssetsLabel
-
-
- YES
-
- parent
- id
-
-
- scrollview
- UIScrollView
-
-
- selectedAssetsLabel
- UILabel
-
-
-
-
- IBProjectSource
- Classes/ELCImagePickerController.h
-
-
-
-
- YES
-
- NSObject
-
- IBFrameworkSource
- Foundation.framework/Headers/NSError.h
-
-
-
- NSObject
-
- IBFrameworkSource
- Foundation.framework/Headers/NSFileManager.h
-
-
-
- NSObject
-
- IBFrameworkSource
- Foundation.framework/Headers/NSKeyValueCoding.h
-
-
-
- NSObject
-
- IBFrameworkSource
- Foundation.framework/Headers/NSKeyValueObserving.h
-
-
-
- NSObject
-
- IBFrameworkSource
- Foundation.framework/Headers/NSKeyedArchiver.h
-
-
-
- NSObject
-
- IBFrameworkSource
- Foundation.framework/Headers/NSObject.h
-
-
-
- NSObject
-
- IBFrameworkSource
- Foundation.framework/Headers/NSRunLoop.h
-
-
-
- NSObject
-
- IBFrameworkSource
- Foundation.framework/Headers/NSThread.h
-
-
-
- NSObject
-
- IBFrameworkSource
- Foundation.framework/Headers/NSURL.h
-
-
-
- NSObject
-
- IBFrameworkSource
- Foundation.framework/Headers/NSURLConnection.h
-
-
-
- NSObject
-
- IBFrameworkSource
- UIKit.framework/Headers/UIAccessibility.h
-
-
-
- NSObject
-
- IBFrameworkSource
- UIKit.framework/Headers/UINibLoading.h
-
-
-
- NSObject
-
- IBFrameworkSource
- UIKit.framework/Headers/UIResponder.h
-
-
-
- UILabel
- UIView
-
- IBFrameworkSource
- UIKit.framework/Headers/UILabel.h
-
-
-
- UIResponder
- NSObject
-
-
-
- UIScrollView
- UIView
-
- IBFrameworkSource
- UIKit.framework/Headers/UIScrollView.h
-
-
-
- UISearchBar
- UIView
-
- IBFrameworkSource
- UIKit.framework/Headers/UISearchBar.h
-
-
-
- UISearchDisplayController
- NSObject
-
- IBFrameworkSource
- UIKit.framework/Headers/UISearchDisplayController.h
-
-
-
- UIView
-
- IBFrameworkSource
- UIKit.framework/Headers/UITextField.h
-
-
-
- UIView
- UIResponder
-
- IBFrameworkSource
- UIKit.framework/Headers/UIView.h
-
-
-
- UIViewController
-
- IBFrameworkSource
- UIKit.framework/Headers/UINavigationController.h
-
-
-
- UIViewController
-
- IBFrameworkSource
- UIKit.framework/Headers/UIPopoverController.h
-
-
-
- UIViewController
-
- IBFrameworkSource
- UIKit.framework/Headers/UISplitViewController.h
-
-
-
- UIViewController
-
- IBFrameworkSource
- UIKit.framework/Headers/UITabBarController.h
-
-
-
- UIViewController
- UIResponder
-
- IBFrameworkSource
- UIKit.framework/Headers/UIViewController.h
-
-
-
-
- 0
- IBCocoaTouchFramework
-
- com.apple.InterfaceBuilder.CocoaTouchPlugin.iPhoneOS
-
-
-
- com.apple.InterfaceBuilder.CocoaTouchPlugin.InterfaceBuilder3
-
-
- YES
- ../ELCImagePickerDemo.xcodeproj
- 3
- 123
-
-
diff --git a/src/ios/ELCImagePicker/Resources/ELCAssetTablePicker.xib b/src/ios/ELCImagePicker/Resources/ELCAssetTablePicker.xib
deleted file mode 100644
index e59456c7..00000000
--- a/src/ios/ELCImagePicker/Resources/ELCAssetTablePicker.xib
+++ /dev/null
@@ -1,422 +0,0 @@
-
-
-
- 1024
- 10F569
- 804
- 1038.29
- 461.00
-
- com.apple.InterfaceBuilder.IBCocoaTouchPlugin
- 123
-
-
- YES
-
-
-
- YES
- com.apple.InterfaceBuilder.IBCocoaTouchPlugin
-
-
- YES
-
- YES
-
-
- YES
-
-
-
- YES
-
- IBFilesOwner
- IBCocoaTouchFramework
-
-
- IBFirstResponder
- IBCocoaTouchFramework
-
-
-
- 274
- {320, 436}
-
-
- 3
- MQA
-
- YES
-
- NO
-
- IBCocoaTouchFramework
- YES
- 1
- 0
- YES
- 44
- 22
- 22
-
-
-
-
- YES
-
-
- view
-
-
-
- 3
-
-
-
- dataSource
-
-
-
- 4
-
-
-
- delegate
-
-
-
- 5
-
-
-
-
- YES
-
- 0
-
-
-
-
-
- -1
-
-
- File's Owner
-
-
- -2
-
-
-
-
- 2
-
-
-
-
-
-
- YES
-
- YES
- -1.CustomClassName
- -2.CustomClassName
- 2.IBEditorWindowLastContentRect
- 2.IBPluginDependency
-
-
- YES
- AssetTablePicker
- UIResponder
- {{0, 526}, {320, 480}}
- com.apple.InterfaceBuilder.IBCocoaTouchPlugin
-
-
-
- YES
-
-
- YES
-
-
-
-
- YES
-
-
- YES
-
-
-
- 5
-
-
-
- YES
-
- AssetTablePicker
- UITableViewController
-
- dismiss:
- id
-
-
- dismiss:
-
- dismiss:
- id
-
-
-
- YES
-
- YES
- parent
- selectedAssetsLabel
-
-
- YES
- id
- UILabel
-
-
-
- YES
-
- YES
- parent
- selectedAssetsLabel
-
-
- YES
-
- parent
- id
-
-
- selectedAssetsLabel
- UILabel
-
-
-
-
- IBProjectSource
- Classes/ELCImagePickerController.h
-
-
-
-
- YES
-
- NSObject
-
- IBFrameworkSource
- Foundation.framework/Headers/NSError.h
-
-
-
- NSObject
-
- IBFrameworkSource
- Foundation.framework/Headers/NSFileManager.h
-
-
-
- NSObject
-
- IBFrameworkSource
- Foundation.framework/Headers/NSKeyValueCoding.h
-
-
-
- NSObject
-
- IBFrameworkSource
- Foundation.framework/Headers/NSKeyValueObserving.h
-
-
-
- NSObject
-
- IBFrameworkSource
- Foundation.framework/Headers/NSKeyedArchiver.h
-
-
-
- NSObject
-
- IBFrameworkSource
- Foundation.framework/Headers/NSObject.h
-
-
-
- NSObject
-
- IBFrameworkSource
- Foundation.framework/Headers/NSRunLoop.h
-
-
-
- NSObject
-
- IBFrameworkSource
- Foundation.framework/Headers/NSThread.h
-
-
-
- NSObject
-
- IBFrameworkSource
- Foundation.framework/Headers/NSURL.h
-
-
-
- NSObject
-
- IBFrameworkSource
- Foundation.framework/Headers/NSURLConnection.h
-
-
-
- NSObject
-
- IBFrameworkSource
- UIKit.framework/Headers/UIAccessibility.h
-
-
-
- NSObject
-
- IBFrameworkSource
- UIKit.framework/Headers/UINibLoading.h
-
-
-
- NSObject
-
- IBFrameworkSource
- UIKit.framework/Headers/UIResponder.h
-
-
-
- UILabel
- UIView
-
- IBFrameworkSource
- UIKit.framework/Headers/UILabel.h
-
-
-
- UIResponder
- NSObject
-
-
-
- UIScrollView
- UIView
-
- IBFrameworkSource
- UIKit.framework/Headers/UIScrollView.h
-
-
-
- UISearchBar
- UIView
-
- IBFrameworkSource
- UIKit.framework/Headers/UISearchBar.h
-
-
-
- UISearchDisplayController
- NSObject
-
- IBFrameworkSource
- UIKit.framework/Headers/UISearchDisplayController.h
-
-
-
- UITableView
- UIScrollView
-
- IBFrameworkSource
- UIKit.framework/Headers/UITableView.h
-
-
-
- UITableViewController
- UIViewController
-
- IBFrameworkSource
- UIKit.framework/Headers/UITableViewController.h
-
-
-
- UIView
-
- IBFrameworkSource
- UIKit.framework/Headers/UITextField.h
-
-
-
- UIView
- UIResponder
-
- IBFrameworkSource
- UIKit.framework/Headers/UIView.h
-
-
-
- UIViewController
-
- IBFrameworkSource
- UIKit.framework/Headers/UINavigationController.h
-
-
-
- UIViewController
-
- IBFrameworkSource
- UIKit.framework/Headers/UIPopoverController.h
-
-
-
- UIViewController
-
- IBFrameworkSource
- UIKit.framework/Headers/UISplitViewController.h
-
-
-
- UIViewController
-
- IBFrameworkSource
- UIKit.framework/Headers/UITabBarController.h
-
-
-
- UIViewController
- UIResponder
-
- IBFrameworkSource
- UIKit.framework/Headers/UIViewController.h
-
-
-
-
- 0
- IBCocoaTouchFramework
-
- com.apple.InterfaceBuilder.CocoaTouchPlugin.iPhoneOS
-
-
-
- com.apple.InterfaceBuilder.CocoaTouchPlugin.InterfaceBuilder3
-
-
- YES
- ../ELCImagePickerDemo.xcodeproj
- 3
- 123
-
-
diff --git a/src/ios/ELCImagePicker/Resources/Overlay.png b/src/ios/ELCImagePicker/Resources/Overlay.png
deleted file mode 100644
index a86f47f3..00000000
Binary files a/src/ios/ELCImagePicker/Resources/Overlay.png and /dev/null differ
diff --git a/src/ios/ELCImagePicker/Resources/Overlay@2x.png b/src/ios/ELCImagePicker/Resources/Overlay@2x.png
deleted file mode 100644
index a6fd78c5..00000000
Binary files a/src/ios/ELCImagePicker/Resources/Overlay@2x.png and /dev/null differ
diff --git a/src/ios/GMImagePicker/Base.lproj/GMImagePicker.strings b/src/ios/GMImagePicker/Base.lproj/GMImagePicker.strings
new file mode 100755
index 00000000..cd03baa6
Binary files /dev/null and b/src/ios/GMImagePicker/Base.lproj/GMImagePicker.strings differ
diff --git a/src/ios/GMImagePicker/FeHourGlass.h b/src/ios/GMImagePicker/FeHourGlass.h
new file mode 100644
index 00000000..848dea24
--- /dev/null
+++ b/src/ios/GMImagePicker/FeHourGlass.h
@@ -0,0 +1,30 @@
+//
+// FeCandyLoader.h
+// FeSpinner
+//
+// Created by Nghia Tran on 8/14/14.
+// Copyright (c) 2014 fe. All rights reserved.
+//
+
+#import
+
+@interface FeHourGlass : UIView
+
+// is running
+@property (assign, readonly, nonatomic) BOOL isShowing;
+
+-(instancetype) initWithView:(UIView *) view;
+
+-(void) show;
+
+-(void) showWhileExecutingBlock:(dispatch_block_t) block;
+
+-(void) showWhileExecutingBlock:(dispatch_block_t)block completion:(dispatch_block_t) completion;
+
+-(void) showWhileExecutingSelector:(SEL) selector onTarget:(id) target withObject:(id) object;
+
+-(void) showWhileExecutingSelector:(SEL)selector onTarget:(id)target withObject:(id)object completion:(dispatch_block_t) completion;
+
+-(void) dismiss;
+
+@end
diff --git a/src/ios/GMImagePicker/FeHourGlass.m b/src/ios/GMImagePicker/FeHourGlass.m
new file mode 100644
index 00000000..cb358aa3
--- /dev/null
+++ b/src/ios/GMImagePicker/FeHourGlass.m
@@ -0,0 +1,301 @@
+//
+// FeCandyLoader.m
+// FeSpinner
+//
+// Created by Nghia Tran on 8/14/14.
+// Copyright (c) 2014 fe. All rights reserved.
+//
+
+#import "FeHourGlass.h"
+#import
+
+#define kFe_HourGlass_Length 30.0f
+#define kFe_HourGlass_Duration 3.5f
+
+@interface FeHourGlass ()
+{
+ CGFloat width;
+ CGFloat height;
+
+ // Target, method, object and block
+ id targetForExecuting;
+ SEL methodForExecuting;
+ id objectForExecuting;
+ dispatch_block_t completionBlock;
+}
+// Top
+@property (strong, nonatomic) CAShapeLayer *topLayer;
+
+// Bottom
+@property (strong, nonatomic) CAShapeLayer *bottomLayer;
+
+// Dash line
+@property (strong, nonatomic) CAShapeLayer *lineLayer;
+
+// container Layer
+@property (strong, nonatomic) CALayer *containerLayer;
+
+// Container view
+@property (weak, nonatomic) UIView *containerView;
+
+// Animaiton
+@property (strong, nonatomic) CAKeyframeAnimation *topAnimation;
+
+@property (strong, nonatomic) CAKeyframeAnimation *bottomAnimation;
+
+@property (strong, nonatomic) CAKeyframeAnimation *lineAnimation;
+
+@property(strong, nonatomic) CAKeyframeAnimation *containerAnimation;
+
+///////////
+// Init
+-(void) initCommon;
+-(void) initContainer;
+-(void) initTop;
+-(void) initBottom;
+-(void) initLine;
+-(void) initAnimation;
+@end
+
+@implementation FeHourGlass
+
+-(instancetype) initWithView:(UIView *)view
+{
+ self = [super init];
+ if (self)
+ {
+ _containerView = view;
+
+ [self initCommon];
+
+ [self initContainer];
+
+ [self initTop];
+
+ [self initBottom];
+
+ [self initLine];
+
+ [self initAnimation];
+ }
+ return self;
+}
+-(void) initCommon
+{
+ _isShowing = NO;
+
+ self.frame = CGRectMake(0, 0, _containerView.bounds.size.width, _containerView.bounds.size.height);
+ self.backgroundColor = [UIColor clearColor];
+
+ width = sqrtf(kFe_HourGlass_Length * kFe_HourGlass_Length + kFe_HourGlass_Length * kFe_HourGlass_Length);
+ height = sqrtf((kFe_HourGlass_Length * kFe_HourGlass_Length) - ((width / 2.0f) * (width / 2.0f)));
+}
+-(void) initContainer
+{
+ _containerLayer = [CALayer layer];
+ _containerLayer.backgroundColor = [UIColor clearColor].CGColor;
+ _containerLayer.frame = CGRectMake(0, 0, width, height * 2);
+ _containerLayer.anchorPoint = CGPointMake(0.5f, 0.5f);
+ _containerLayer.position = CGPointMake(self.bounds.size.width / 2, self.bounds.size.height / 2);
+
+ [self.layer addSublayer:_containerLayer];
+}
+-(void) initTop
+{
+ // BezierPath
+ UIBezierPath *path = [UIBezierPath bezierPath];
+ [path moveToPoint:CGPointMake(0, 0)];
+ [path addLineToPoint:CGPointMake(width, 0)];
+ [path addLineToPoint:CGPointMake(width / 2.0f, height)];
+ [path addLineToPoint:CGPointMake(0, 0)];
+
+ [path closePath];
+
+ // Top Layer
+ _topLayer = [CAShapeLayer layer];
+ _topLayer.frame = CGRectMake(0, 0, width, height);
+ _topLayer.path = path.CGPath;
+ _topLayer.fillColor = [UIColor whiteColor].CGColor;
+ _topLayer.strokeColor = [UIColor whiteColor].CGColor;
+ _topLayer.lineWidth = 0.0f;
+ _topLayer.anchorPoint = CGPointMake(0.5f, 1);
+ _topLayer.position = CGPointMake(width / 2.0f, height);
+
+ [_containerLayer addSublayer:_topLayer];
+}
+-(void) initBottom
+{
+ // BezierPath
+ UIBezierPath *path = [UIBezierPath bezierPath];
+ [path moveToPoint:CGPointMake(width / 2, 0)];
+ [path addLineToPoint:CGPointMake(width, height)];
+ [path addLineToPoint:CGPointMake(0, height )];
+ [path addLineToPoint:CGPointMake(width / 2, 0)];
+
+ [path closePath];
+
+ // Top Layer
+ _bottomLayer = [CAShapeLayer layer];
+ _bottomLayer.frame = CGRectMake(0, height, width, height);
+ _bottomLayer.path = path.CGPath;
+ _bottomLayer.fillColor = [UIColor whiteColor].CGColor;
+ _bottomLayer.strokeColor = [UIColor whiteColor].CGColor;
+ _bottomLayer.lineWidth = 0.0f;
+ _bottomLayer.anchorPoint = CGPointMake(0.5f, 1.0f);
+ _bottomLayer.position = CGPointMake(width / 2.0f, height * 2.0f);
+ _bottomLayer.transform = CATransform3DMakeScale(0, 0, 0);
+
+ [_containerLayer addSublayer:_bottomLayer];
+}
+-(void) initLine
+{
+ // BezierPath
+ UIBezierPath *path = [UIBezierPath bezierPath];
+ [path moveToPoint:CGPointMake(width / 2, 0)];
+ [path addLineToPoint:CGPointMake(width / 2, height)];
+
+ // Line Layer
+ _lineLayer = [CAShapeLayer layer];
+ _lineLayer.frame = CGRectMake(0, height, width, height);
+ _lineLayer.strokeColor = [UIColor whiteColor].CGColor;
+ _lineLayer.lineWidth = 1.0;
+ _lineLayer.lineJoin = kCALineJoinMiter;
+ _lineLayer.lineDashPattern = [NSArray arrayWithObjects:[NSNumber numberWithInt:1],[NSNumber numberWithInt:1], nil];
+ _lineLayer.lineDashPhase = 3.0f;
+ _lineLayer.path = path.CGPath;
+ _lineLayer.strokeEnd = 0.0f;
+
+ [_containerLayer addSublayer:_lineLayer];
+}
+-(void) initAnimation
+{
+ if (YES) // Top Animation
+ {
+ _topAnimation = [CAKeyframeAnimation animationWithKeyPath:@"transform.scale"];
+ _topAnimation.duration = kFe_HourGlass_Duration;
+ _topAnimation.repeatCount = HUGE_VAL;
+ _topAnimation.keyTimes = @[@0.0f, @0.9f, @1.0f];
+ _topAnimation.values = @[@1.0f, @0.0f, @0.0f];
+ }
+ if (YES) // Bottom Animation
+ {
+ _bottomAnimation = [CAKeyframeAnimation animationWithKeyPath:@"transform.scale"];
+ _bottomAnimation.duration = kFe_HourGlass_Duration;
+ _bottomAnimation.repeatCount = HUGE_VAL;
+ _bottomAnimation.keyTimes = @[@0.1f, @0.9f, @1.0f];
+ _bottomAnimation.values = @[@0.0f, @1.0f, @1.0f];
+ }
+ if (YES) // Bottom Animation
+ {
+ _lineAnimation = [CAKeyframeAnimation animationWithKeyPath:@"strokeEnd"];
+ _lineAnimation.duration = kFe_HourGlass_Duration;
+ _lineAnimation.repeatCount = HUGE_VAL;
+ _lineAnimation.keyTimes = @[@0.0f, @0.1f, @0.9f, @1.0f];
+ _lineAnimation.values = @[@0.0f, @1.0f, @1.0f, @1.0f];
+ }
+ if (YES) // Container Animation
+ {
+ _containerAnimation = [CAKeyframeAnimation animationWithKeyPath:@"transform.rotation.z"];
+ _containerAnimation.timingFunction = [CAMediaTimingFunction functionWithControlPoints:0.2f :1 :0.8f :0.0f];
+ _containerAnimation.duration = kFe_HourGlass_Duration;
+ _containerAnimation.repeatCount = HUGE_VAL;
+ _containerAnimation.keyTimes = @[@0.8f, @1.0f];
+ _containerAnimation.values = @[[NSNumber numberWithFloat:0.0f], [NSNumber numberWithFloat:M_PI]];
+ //_containerAnimation.calculationMode = kCAAnimationCubic;
+ }
+}
+#pragma mark - Action
+-(void) show
+{
+ if (_isShowing)
+ return;
+
+ _isShowing = YES;
+
+ [_topLayer addAnimation:_topAnimation forKey:@"TopAnimatin"];
+ [_bottomLayer addAnimation:_bottomAnimation forKey:@"BottomAnimation"];
+ [_lineLayer addAnimation:_lineAnimation forKey:@"LineAnimation"];
+ [_containerLayer addAnimation:_containerAnimation forKey:@"ContainerAnimation"];
+}
+-(void) dismiss
+{
+ if (!_isShowing)
+ return;
+
+ _isShowing = NO;
+
+}
+-(void) showWhileExecutingBlock:(dispatch_block_t)block
+{
+ [self showWhileExecutingBlock:block completion:nil];
+}
+-(void) showWhileExecutingSelector:(SEL)selector onTarget:(id)target withObject:(id)object
+{
+ [self showWhileExecutingSelector:selector onTarget:target withObject:object completion:nil];
+
+}
+-(void) showWhileExecutingBlock:(dispatch_block_t)block completion:(dispatch_block_t)completion
+{
+ // Check block != nil
+ if (block != nil)
+ {
+ [self show];
+ dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^
+ {
+ block();
+
+ // Update UI
+ dispatch_async(dispatch_get_main_queue(), ^{
+ completion();
+ [self dismiss];
+ });
+ });
+ }
+}
+-(void) showWhileExecutingSelector:(SEL)selector onTarget:(id)target withObject:(id)object completion:(dispatch_block_t)completion
+{
+ // Check Selector is responded
+ if ([target respondsToSelector:selector])
+ {
+ methodForExecuting = selector;
+ targetForExecuting = target;
+ objectForExecuting = object;
+ completionBlock = completion;
+
+ [self show];
+ [NSThread detachNewThreadSelector:@selector(executingMethod) toTarget:self withObject:nil];
+ }
+}
+#pragma mark Helper method
+-(void) executingMethod
+{
+ @autoreleasepool {
+#pragma clang diagnostic push
+#pragma clang diagnostic ignored "-Warc-performSelector-leaks"
+ // Start executing the requested task
+ [targetForExecuting performSelector:methodForExecuting withObject:objectForExecuting];
+#pragma clang diagnostic pop
+ // Task completed, update view in main thread (note: view operations should
+ // be done only in the main thread)
+ dispatch_async(dispatch_get_main_queue(), ^{
+ completionBlock();
+ [self performSelectorOnMainThread:@selector(cleanUp) withObject:nil waitUntilDone:NO];
+ });
+
+ }
+}
+-(void) cleanUp
+{
+ NSLog(@"Clean up");
+ if (objectForExecuting)
+ objectForExecuting = nil;
+ if (methodForExecuting)
+ methodForExecuting = nil;
+ if (targetForExecuting)
+ targetForExecuting = nil;
+ if (completionBlock)
+ completionBlock = nil;
+ [self dismiss];
+}
+
+@end
diff --git a/src/ios/GMImagePicker/GMAlbumsViewCell.h b/src/ios/GMImagePicker/GMAlbumsViewCell.h
new file mode 100755
index 00000000..4a024ce9
--- /dev/null
+++ b/src/ios/GMImagePicker/GMAlbumsViewCell.h
@@ -0,0 +1,33 @@
+//
+// GMAlbumsViewCell.h
+// GMPhotoPicker
+//
+// Created by Guillermo Muntaner Perelló on 22/09/14.
+// Copyright (c) 2014 Guillermo Muntaner Perelló. All rights reserved.
+//
+
+
+#import
+
+
+@interface GMAlbumsViewCell : UITableViewCell
+
+@property (strong) PHFetchResult *assetsFetchResults;
+@property (strong) PHAssetCollection *assetCollection;
+
+//The labels
+@property (nonatomic, strong) UILabel *titleLabel;
+@property (nonatomic, strong) UILabel *infoLabel;
+//The imageView
+@property (nonatomic, strong) UIImageView *imageView1;
+@property (nonatomic, strong) UIImageView *imageView2;
+@property (nonatomic, strong) UIImageView *imageView3;
+//Video additional information
+@property (nonatomic, strong) UIImageView *videoIcon;
+@property (nonatomic, strong) UIImageView *slowMoIcon;
+@property (nonatomic, strong) UIView* gradientView;
+@property (nonatomic, strong) CAGradientLayer *gradient;
+//Selection overlay
+
+- (void)setVideoLayout:(BOOL)isVideo;
+@end
diff --git a/src/ios/GMImagePicker/GMAlbumsViewCell.m b/src/ios/GMImagePicker/GMAlbumsViewCell.m
new file mode 100755
index 00000000..9eef1e93
--- /dev/null
+++ b/src/ios/GMImagePicker/GMAlbumsViewCell.m
@@ -0,0 +1,190 @@
+//
+// GMAlbumsViewCell.m
+// GMPhotoPicker
+//
+// Created by Guillermo Muntaner Perelló on 22/09/14.
+// Copyright (c) 2014 Guillermo Muntaner Perelló. All rights reserved.
+//
+
+#import "GMAlbumsViewCell.h"
+#import "GMAlbumsViewController.h"
+#import "GMImagePickerController.h"
+#import
+
+@implementation GMAlbumsViewCell
+
+- (void)awakeFromNib
+{
+ [super awakeFromNib];
+ self.contentView.autoresizingMask = UIViewAutoresizingFlexibleHeight | UIViewAutoresizingFlexibleWidth;
+ self.contentView.translatesAutoresizingMaskIntoConstraints = YES;
+}
+
+- (id)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString *)reuseIdentifier
+{
+
+ if (self = [super initWithStyle:style reuseIdentifier:reuseIdentifier])
+ {
+ //self.opaque = YES;
+ //self.isAccessibilityElement = YES;
+ //self.textLabel.backgroundColor = self.backgroundColor;
+ //self.detailTextLabel.backgroundColor = self.backgroundColor;
+
+ self.titleLabel.backgroundColor = self.backgroundColor;
+ self.infoLabel.backgroundColor = self.backgroundColor;
+
+ self.accessoryType = UITableViewCellAccessoryDisclosureIndicator;
+
+ //Border width of 1 pixel:
+ float borderWidth = 1.0/[UIScreen mainScreen].scale;
+
+ //ImageView
+ _imageView3 = [UIImageView new];
+ _imageView3.contentMode = UIViewContentModeScaleAspectFill;
+ _imageView3.frame = CGRectMake(kAlbumLeftToImageSpace+4, 8, kAlbumThumbnailSize3.width, kAlbumThumbnailSize3.height );
+ [_imageView3.layer setBorderColor: [[UIColor whiteColor] CGColor]];
+ [_imageView3.layer setBorderWidth: borderWidth];
+ _imageView3.clipsToBounds = YES;
+ _imageView3.translatesAutoresizingMaskIntoConstraints = YES;
+ _imageView3.autoresizingMask = UIViewAutoresizingFlexibleRightMargin;
+ [self.contentView addSubview:_imageView3];
+
+ //ImageView
+ _imageView2 = [UIImageView new];
+ _imageView2.contentMode = UIViewContentModeScaleAspectFill;
+ _imageView2.frame = CGRectMake(kAlbumLeftToImageSpace+2, 8+2, kAlbumThumbnailSize2.width, kAlbumThumbnailSize2.height );
+ [_imageView2.layer setBorderColor: [[UIColor whiteColor] CGColor]];
+ [_imageView2.layer setBorderWidth: borderWidth];
+ _imageView2.clipsToBounds = YES;
+ _imageView2.translatesAutoresizingMaskIntoConstraints = YES;
+ _imageView2.autoresizingMask = UIViewAutoresizingFlexibleRightMargin;
+ [self.contentView addSubview:_imageView2];
+
+ //ImageView
+ _imageView1 = [UIImageView new];
+ _imageView1.contentMode = UIViewContentModeScaleAspectFill;
+ _imageView1.frame = CGRectMake(kAlbumLeftToImageSpace, 8+4, kAlbumThumbnailSize1.width, kAlbumThumbnailSize1.height );
+ [_imageView1.layer setBorderColor: [[UIColor whiteColor] CGColor]];
+ [_imageView1.layer setBorderWidth: borderWidth];
+ _imageView1.clipsToBounds = YES;
+ _imageView1.translatesAutoresizingMaskIntoConstraints = YES;
+ _imageView1.autoresizingMask = UIViewAutoresizingFlexibleRightMargin;
+ [self.contentView addSubview:_imageView1];
+
+
+ // The video gradient, label & icon
+ UIColor *topGradient = [UIColor colorWithRed:0.00 green:0.00 blue:0.00 alpha:0.0];
+ UIColor *midGradient = [UIColor colorWithRed:0.00 green:0.00 blue:0.00 alpha:0.33];
+ UIColor *botGradient = [UIColor colorWithRed:0.00 green:0.00 blue:0.00 alpha:0.75];
+ _gradientView = [[UIView alloc] initWithFrame: CGRectMake(0.0f, kAlbumThumbnailSize1.height-kAlbumGradientHeight, kAlbumThumbnailSize1.width, kAlbumGradientHeight)];
+ _gradient = [CAGradientLayer layer];
+ _gradient.frame = _gradientView.bounds;
+ _gradient.colors = [NSArray arrayWithObjects:(id)[topGradient CGColor], (id)[midGradient CGColor], (id)[botGradient CGColor], nil];
+ _gradient.locations = @[ @0.0f, @0.5f, @1.0f ];
+ [_gradientView.layer insertSublayer:_gradient atIndex:0];
+ _gradientView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleTopMargin;
+ _gradientView.translatesAutoresizingMaskIntoConstraints = YES;
+ [self.imageView1 addSubview:_gradientView];
+ _gradientView.hidden = YES;
+
+ //VideoIcon
+ _videoIcon = [UIImageView new];
+ _videoIcon.contentMode = UIViewContentModeScaleAspectFill;
+ _videoIcon.frame = CGRectMake(3,kAlbumThumbnailSize1.height - 4 - 8, 15, 8 );
+ _videoIcon.image = [UIImage imageNamed:@"GMVideoIcon"];
+ _videoIcon.clipsToBounds = YES;
+ _videoIcon.translatesAutoresizingMaskIntoConstraints = YES;
+ _videoIcon.autoresizingMask = UIViewAutoresizingFlexibleRightMargin;
+ [self.imageView1 addSubview:_videoIcon];
+ _videoIcon.hidden = NO;
+
+
+ //TextLabel
+// self.textLabel.font = [UIFont fontWithName:@"Helvetica" size:17.0];
+// self.textLabel.numberOfLines = 1;
+// self.textLabel.translatesAutoresizingMaskIntoConstraints = NO;
+
+ self.titleLabel = [UILabel new];
+ self.titleLabel.font = [UIFont fontWithName:@"HelveticaNeue" size:17.0];
+ self.titleLabel.numberOfLines = 1;
+ self.titleLabel.translatesAutoresizingMaskIntoConstraints = NO;
+ self.titleLabel.adjustsFontSizeToFitWidth = YES;
+ [self.contentView addSubview:self.titleLabel];
+
+// self.detailTextLabel.font = [UIFont fontWithName:@"Helvetica" size:14.0];
+// self.detailTextLabel.numberOfLines = 1;
+// self.detailTextLabel.translatesAutoresizingMaskIntoConstraints = NO;
+
+ self.infoLabel = [UILabel new];
+ self.infoLabel.font = [UIFont fontWithName:@"HelveticaNeue" size:14.0];
+ self.infoLabel.numberOfLines = 1;
+ self.infoLabel.translatesAutoresizingMaskIntoConstraints = NO;
+ self.infoLabel.adjustsFontSizeToFitWidth = YES;
+ [self.contentView addSubview:self.infoLabel];
+
+ //Set next text labels contraints :
+ [self.contentView addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"H:[imageView1]-(offset)-[titleLabel]-|"
+ options:0
+ metrics:@{@"offset": @(kAlbumImageToTextSpace)}
+ views:@{@"titleLabel": self.titleLabel,
+ @"imageView1": self.imageView1}]];
+
+ [self.contentView addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"H:[imageView1]-(offset)-[infoLabel]-|"
+ options:0
+ metrics:@{@"offset": @(kAlbumImageToTextSpace)}
+ views:@{@"infoLabel": self.infoLabel,
+ @"imageView1": self.imageView1}]];
+
+
+ [self.contentView addConstraints:@[[NSLayoutConstraint constraintWithItem:self.titleLabel
+ attribute:NSLayoutAttributeBottom
+ relatedBy:NSLayoutRelationEqual
+ toItem:self.titleLabel.superview
+ attribute:NSLayoutAttributeCenterY
+ multiplier:1.f constant:0.f]]];
+
+ [self.contentView addConstraints:@[[NSLayoutConstraint constraintWithItem:self.infoLabel
+ attribute:NSLayoutAttributeTop
+ relatedBy:NSLayoutRelationEqual
+ toItem:self.titleLabel.superview
+ attribute:NSLayoutAttributeCenterY
+ multiplier:1.f constant:+4.f]]];
+ }
+
+
+
+ return self;
+}
+
+- (void)layoutSubviews
+{
+ [super layoutSubviews];
+
+ //TODO Reduce text font size if the name label does not fit screen.
+
+}
+
+- (void)setVideoLayout:(BOOL)isVideo
+{
+ //TODO : Add additional icons for slowmo, burst, etc...
+ if (isVideo)
+ {
+ _videoIcon.hidden = NO;
+ _gradientView.hidden = NO;
+ }
+ else
+ {
+ _videoIcon.hidden = YES;
+ _gradientView.hidden = YES;
+ }
+}
+
+
+- (void)setSelected:(BOOL)selected animated:(BOOL)animated
+{
+ [super setSelected:selected animated:animated];
+
+ // Configure the view for the selected state
+}
+
+@end
diff --git a/src/ios/GMImagePicker/GMAlbumsViewController.h b/src/ios/GMImagePicker/GMAlbumsViewController.h
new file mode 100755
index 00000000..7bbed553
--- /dev/null
+++ b/src/ios/GMImagePicker/GMAlbumsViewController.h
@@ -0,0 +1,32 @@
+//
+// GMAlbumsViewController.h
+// GMPhotoPicker
+//
+// Created by Guillermo Muntaner Perelló on 19/09/14.
+// Copyright (c) 2014 Guillermo Muntaner Perelló. All rights reserved.
+//
+
+#import
+
+// Measuring IOS8 Photos APP at @2x (iPhone5s):
+// The rows are 180px/90pts
+// Left image border is 21px/10.5pts
+// Separation between image and text is 42px/21pts (double the previouse one)
+// The bigger image measures 139px/69.5pts including 1px/0.5pts white border.
+// The second image measures 131px/65.6pts including 1px/0.5pts white border. Only 3px/1.5pts visible
+// The third image measures 123px/61.5pts including 1px/0.5pts white border. Only 3px/1.5pts visible
+
+static int kAlbumRowHeight = 90;
+static int kAlbumLeftToImageSpace = 10;
+static int kAlbumImageToTextSpace = 21;
+static float const kAlbumGradientHeight = 20.0f;
+static CGSize const kAlbumThumbnailSize1 = {70.0f , 70.0f};
+static CGSize const kAlbumThumbnailSize2 = {66.0f , 66.0f};
+static CGSize const kAlbumThumbnailSize3 = {62.0f , 62.0f};
+
+
+@interface GMAlbumsViewController : UITableViewController
+
+- (id)init:(bool)allow_v;
+
+@end
\ No newline at end of file
diff --git a/src/ios/GMImagePicker/GMAlbumsViewController.m b/src/ios/GMImagePicker/GMAlbumsViewController.m
new file mode 100755
index 00000000..bc01f1d9
--- /dev/null
+++ b/src/ios/GMImagePicker/GMAlbumsViewController.m
@@ -0,0 +1,445 @@
+//
+// GMAlbumsViewController.m
+// GMPhotoPicker
+//
+// Created by Guillermo Muntaner Perelló on 19/09/14.
+// Copyright (c) 2014 Guillermo Muntaner Perelló. All rights reserved.
+//
+
+#import "GMImagePickerController.h"
+#import "GMAlbumsViewController.h"
+#import "GMGridViewCell.h"
+#import "GMGridViewController.h"
+#import "GMAlbumsViewCell.h"
+
+#import
+#import
+#import
+#import
+#import
+#import
+
+
+@interface GMAlbumsViewController()
+
+@property (strong) NSArray *collectionsFetchResults;
+@property (strong) NSArray *collectionsLocalizedTitles;
+@property (strong) NSArray *collectionsFetchResultsAssets;
+@property (strong) NSArray *collectionsFetchResultsTitles;
+@property (nonatomic, weak) GMImagePickerController *picker;
+@property (strong) PHCachingImageManager *imageManager;
+@property (nonatomic, strong) NSMutableDictionary * dic_asset_fetches;
+
+@end
+
+
+@implementation GMAlbumsViewController{
+ bool allow_video;
+}
+
+@synthesize dic_asset_fetches;
+
+- (id)init:(bool)allow_v
+{
+ if (self = [super initWithStyle:UITableViewStylePlain])
+ {
+ self.preferredContentSize = kPopoverContentSize;
+ }
+
+ dic_asset_fetches = [[NSMutableDictionary alloc] init];
+
+ allow_video = allow_v;
+
+ return self;
+}
+
+static NSString * const AllPhotosReuseIdentifier = @"AllPhotosCell";
+static NSString * const CollectionCellReuseIdentifier = @"CollectionCell";
+
+- (void)viewDidLoad
+{
+ [super viewDidLoad];
+
+ //Navigation bar customization_
+ if(self.picker.customNavigationBarPrompt)
+ {
+ self.navigationItem.prompt = self.picker.customNavigationBarPrompt;
+ }
+
+ self.imageManager = [[PHCachingImageManager alloc] init];
+
+ //Table view aspect
+ self.tableView.rowHeight = kAlbumRowHeight;
+ self.tableView.separatorStyle = UITableViewCellSeparatorStyleNone;
+
+ //Navigation bar items
+ //if (self.picker.showsCancelButton)
+ {
+ self.navigationItem.leftBarButtonItem =
+ [[UIBarButtonItem alloc] initWithTitle:NSLocalizedStringFromTable(@"picker.navigation.cancel-button", @"GMImagePicker",@"Cancel")
+ style:UIBarButtonItemStylePlain
+ target:self.picker
+ action:@selector(dismiss:)];
+ }
+
+ self.navigationItem.rightBarButtonItem =
+ [[UIBarButtonItem alloc] initWithTitle:NSLocalizedStringFromTable(@"picker.navigation.done-button", @"GMImagePicker",@"Done")
+ style:UIBarButtonItemStyleDone
+ target:self.picker
+ action:@selector(finishPickingAssets:)];
+
+ self.navigationItem.rightBarButtonItem.enabled = (self.picker.selectedAssets.count > 0);
+
+ //Bottom toolbar
+ self.toolbarItems = self.picker.toolbarItems;
+
+ //Title
+ if (!self.picker.title)
+ self.title = NSLocalizedStringFromTable(@"picker.navigation.title", @"GMImagePicker",@"Navigation bar default title");
+ else
+ self.title = self.picker.title;
+
+
+ // TO-DO Customizable predicates:
+ // Predicate has to filter properties of the type of object returned by the PHFetchResult:
+ // PHCollectionList, PHAssetCollection and PHAsset require different predicates
+ // with limited posibilities (cannot filter a collection by mediaType for example)
+
+ //NSPredicate *predicatePHCollectionList = [NSPredicate predicateWithFormat:@"(mediaType == %d)", PHAssetMediaTypeImage];
+ //NSPredicate *predicatePHAssetCollection = [NSPredicate predicateWithFormat:@"(mediaType == %d)", PHAssetMediaTypeImage];
+ //NSPredicate *predicatePHAsset = [NSPredicate predicateWithFormat:@"(mediaType == %d)", PHAssetMediaTypeImage];
+
+ PHFetchOptions * options = [[PHFetchOptions alloc] init];
+ //NSPredicate *predicatePHAssetCollection = [NSPredicate predicateWithFormat:@"(mediaType == %d)", PHAssetMediaTypeImage];
+
+ //options.predicate = predicatePHAssetCollection;
+ options.sortDescriptors = @[
+ //[NSSortDescriptor sortDescriptorWithKey:@"localizedTitle" ascending:YES],
+ [ NSSortDescriptor sortDescriptorWithKey:@"title" ascending:YES selector:@selector(localizedCaseInsensitiveCompare:)],
+ ];
+
+ //Fetch PHAssetCollections:
+ PHFetchResult *topLevelUserCollections = [PHAssetCollection fetchAssetCollectionsWithType:PHAssetCollectionTypeAlbum subtype:PHAssetCollectionSubtypeAny options:options];
+ //PHFetchResult *topLevelUserCollections = [PHCollectionList fetchTopLevelUserCollectionsWithOptions:nil];
+ //PHFetchResult *smartAlbums = [PHAssetCollection fetchAssetCollectionsWithType:PHAssetCollectionTypeSmartAlbum subtype:PHAssetCollectionSubtypeAlbumRegular options:nil];
+ PHFetchResult *smartAlbums = [PHAssetCollection fetchAssetCollectionsWithType:PHAssetCollectionTypeSmartAlbum subtype:PHAssetCollectionSubtypeAny options:nil];
+ self.collectionsFetchResults = @[topLevelUserCollections, smartAlbums];
+ self.collectionsLocalizedTitles = @[NSLocalizedStringFromTable(@"picker.table.user-albums-header", @"GMImagePicker",@"Albums"), NSLocalizedStringFromTable(@"picker.table.smart-albums-header", @"GMImagePicker",@"Smart Albums")];
+
+ [self updateFetchResults];
+
+ //Register for changes
+ [[PHPhotoLibrary sharedPhotoLibrary] registerChangeObserver:self];
+}
+
+- (void)dealloc
+{
+ [[PHPhotoLibrary sharedPhotoLibrary] unregisterChangeObserver:self];
+}
+
+-(void)updateFetchResults
+{
+ //What I do here is fetch both the albums list and the assets of each album.
+ //This way I have acces to the number of items in each album, I can load the 3
+ //thumbnails directly and I can pass the fetched result to the gridViewController.
+
+ NSPredicate * predicatePHAsset = allow_video? nil : [NSPredicate predicateWithFormat:@"mediaType == %d", PHAssetMediaTypeImage];
+
+ PHFetchOptions *options = [[PHFetchOptions alloc] init];
+ options.sortDescriptors = @[[NSSortDescriptor sortDescriptorWithKey:@"creationDate" ascending:NO]];
+ options.predicate = predicatePHAsset;
+
+ self.collectionsFetchResultsAssets=nil;
+ self.collectionsFetchResultsTitles=nil;
+
+ //Fetch PHAssetCollections:
+ PHFetchResult *topLevelUserCollections = [self.collectionsFetchResults objectAtIndex:0];
+ PHFetchResult *smartAlbums = [self.collectionsFetchResults objectAtIndex:1];
+
+ //All album: Sorted by descending creation date.
+ NSMutableArray *allFetchResultArray = [[NSMutableArray alloc] init];
+ NSMutableArray *allFetchResultLabel = [[NSMutableArray alloc] init];
+ {
+
+ PHFetchResult *assetsFetchResult = [PHAsset fetchAssetsWithOptions:options];
+ [allFetchResultArray addObject:assetsFetchResult];
+ [allFetchResultLabel addObject:NSLocalizedStringFromTable(@"picker.table.all-photos-label", @"GMImagePicker",@"All photos")];
+ }
+
+ //User albums:
+ NSMutableArray *userFetchResultArray = [[NSMutableArray alloc] init];
+ NSMutableArray *userFetchResultLabel = [[NSMutableArray alloc] init];
+ for(PHCollection *collection in topLevelUserCollections)
+ {
+ if ([collection isKindOfClass:[PHAssetCollection class]])
+ {
+ //PHFetchOptions *options = [[PHFetchOptions alloc] init];
+ //options.predicate = predicatePHAsset;
+ PHAssetCollection *assetCollection = (PHAssetCollection *)collection;
+
+ //Albums collections are allways PHAssetCollectionType=1 & PHAssetCollectionSubtype=2
+
+ PHFetchResult *assetsFetchResult = [PHAsset fetchAssetsInAssetCollection:assetCollection options:options];
+ [userFetchResultArray addObject:assetsFetchResult];
+ [userFetchResultLabel addObject:collection.localizedTitle ?: @""];
+ }
+ }
+
+
+ //Smart albums: Sorted by descending creation date.
+ NSMutableArray *smartFetchResultArray = [[NSMutableArray alloc] init];
+ NSMutableArray *smartFetchResultLabel = [[NSMutableArray alloc] init];
+ for(PHCollection *collection in smartAlbums)
+ {
+ if ([collection isKindOfClass:[PHAssetCollection class]])
+ {
+ PHAssetCollection *assetCollection = (PHAssetCollection *)collection;
+
+ //Smart collections are PHAssetCollectionType=2;
+ if(self.picker.customSmartCollections && [self.picker.customSmartCollections containsObject:@(assetCollection.assetCollectionSubtype)])
+ {
+ //PHFetchOptions *options = [[PHFetchOptions alloc] init];
+ //options.sortDescriptors = @[[NSSortDescriptor sortDescriptorWithKey:@"creationDate" ascending:NO]];
+ //options.predicate = predicatePHAsset;
+ PHFetchResult *assetsFetchResult = [PHAsset fetchAssetsInAssetCollection:assetCollection options:options];
+ if(assetsFetchResult.count>0)
+ {
+ [smartFetchResultArray addObject:assetsFetchResult];
+ [smartFetchResultLabel addObject:collection.localizedTitle];
+ }
+ }
+ }
+ }
+
+ self.collectionsFetchResultsAssets= @[allFetchResultArray,userFetchResultArray,smartFetchResultArray];
+ self.collectionsFetchResultsTitles= @[allFetchResultLabel,userFetchResultLabel,smartFetchResultLabel];
+}
+#pragma mark - Accessors
+
+- (GMImagePickerController *)picker
+{
+ return (GMImagePickerController *)self.navigationController.parentViewController;
+}
+
+
+#pragma mark - Rotation
+
+- (BOOL)shouldAutorotate
+{
+ return YES;
+}
+
+- (NSUInteger)supportedInterfaceOrientations
+{
+ return UIInterfaceOrientationMaskAllButUpsideDown;
+}
+
+#pragma mark - UITableViewDataSource
+
+- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
+{
+ return self.collectionsFetchResultsAssets.count;
+}
+
+- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
+{
+ PHFetchResult *fetchResult = self.collectionsFetchResultsAssets[section];
+ return fetchResult.count;
+}
+
+- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
+{
+ static NSString *CellIdentifier = @"Cell";
+
+ GMAlbumsViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
+ if (cell == nil) {
+ cell = [[GMAlbumsViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:CellIdentifier];
+ cell.accessoryType = UITableViewCellAccessoryDisclosureIndicator;
+ }
+
+ // Increment the cell's tag
+ NSInteger currentTag = cell.tag + 1;
+ cell.tag = currentTag;
+
+ //Set the label
+ cell.titleLabel.text = (self.collectionsFetchResultsTitles[indexPath.section])[indexPath.row];
+
+ //Retrieve the pre-fetched assets for this album:
+ PHFetchResult *assetsFetchResult = (self.collectionsFetchResultsAssets[indexPath.section])[indexPath.row];
+
+ //Display the number of assets
+ if(self.picker.displayAlbumsNumberOfAssets)
+ {
+ cell.infoLabel.text = [self tableCellSubtitle:assetsFetchResult];
+ }
+
+ //Set the 3 images (if exists):
+ if([assetsFetchResult count]>0)
+ {
+ CGFloat scale = [UIScreen mainScreen].scale;
+
+ //Compute the thumbnail pixel size:
+ CGSize tableCellThumbnailSize1 = CGSizeMake(kAlbumThumbnailSize1.width*scale, kAlbumThumbnailSize1.height*scale);
+ PHAsset *asset = assetsFetchResult[0];
+ [cell setVideoLayout:(asset.mediaType==PHAssetMediaTypeVideo)];
+ [self.imageManager requestImageForAsset:asset
+ targetSize:tableCellThumbnailSize1
+ contentMode:PHImageContentModeAspectFill
+ options:nil
+ resultHandler:^(UIImage *result, NSDictionary *info)
+ {
+ if (cell.tag == currentTag)
+ {
+ cell.imageView1.image = result;
+ }
+ }];
+
+ //Second & third images:
+ // TO DO: Only preload the 3pixels height visible frame!
+ if([assetsFetchResult count]>1)
+ {
+ //Compute the thumbnail pixel size:
+ CGSize tableCellThumbnailSize2 = CGSizeMake(kAlbumThumbnailSize2.width*scale, kAlbumThumbnailSize2.height*scale);
+ PHAsset *asset = assetsFetchResult[1];
+ [self.imageManager requestImageForAsset:asset
+ targetSize:tableCellThumbnailSize2
+ contentMode:PHImageContentModeAspectFill
+ options:nil
+ resultHandler:^(UIImage *result, NSDictionary *info)
+ {
+ if (cell.tag == currentTag)
+ {
+ cell.imageView2.image = result;
+ }
+ }];
+ }
+ else
+ {
+ cell.imageView2.image = nil;
+ }
+ if([assetsFetchResult count]>2)
+ {
+ CGSize tableCellThumbnailSize3 = CGSizeMake(kAlbumThumbnailSize3.width*scale, kAlbumThumbnailSize3.height*scale);
+ PHAsset *asset = assetsFetchResult[2];
+ [self.imageManager requestImageForAsset:asset
+ targetSize:tableCellThumbnailSize3
+ contentMode:PHImageContentModeAspectFill
+ options:nil
+ resultHandler:^(UIImage *result, NSDictionary *info)
+ {
+ if (cell.tag == currentTag)
+ {
+ cell.imageView3.image = result;
+ }
+ }];
+ }
+ else
+ {
+ cell.imageView3.image = nil;
+ }
+ }
+ else
+ {
+ [cell setVideoLayout:NO];
+ cell.imageView3.image = [UIImage imageNamed:@"EmptyFolder"];
+ cell.imageView2.image = [UIImage imageNamed:@"EmptyFolder"];
+ cell.imageView1.image = [UIImage imageNamed:@"EmptyFolder"];
+ }
+
+ return cell;
+}
+
+#pragma mark - UITableViewDelegate
+
+- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
+{
+ GMAlbumsViewCell *cell = [tableView cellForRowAtIndexPath:indexPath];
+
+ //Init the GMGridViewController
+ GMGridViewController *gridViewController = [[GMGridViewController alloc] initWithPicker:[self picker]];
+ //Set the title
+ gridViewController.title = cell.titleLabel.text;
+ //Use the prefetched assets!
+ gridViewController.assetsFetchResults = [[_collectionsFetchResultsAssets objectAtIndex:indexPath.section] objectAtIndex:indexPath.row];
+ gridViewController.dic_asset_fetches = dic_asset_fetches;
+
+ //Push GMGridViewController
+ [self.navigationController pushViewController:gridViewController animated:YES];
+}
+
+#pragma mark Header
+
+-(void)tableView:(UITableView *)tableView willDisplayHeaderView:(UIView *)view forSection:(NSInteger)section
+{
+ UITableViewHeaderFooterView *header = (UITableViewHeaderFooterView *)view;
+
+ //Here you can customize header views!
+ header.textLabel.font = [UIFont systemFontOfSize:14.0f]; //Set font to "normal" style (default is bold) and to 14 pts.
+ //header.textLabel.font = [UIFont boldSystemFontOfSize:14.0f];
+ //header.textLabel.textColor = [UIColor orangeColor];
+}
+
+- (NSString *)tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section
+{
+ //Tip: Returning nil hides the section header!
+
+ NSString *title = nil;
+ if (section > 0)
+ {
+ //Only show title for non-empty sections:
+ PHFetchResult *fetchResult = self.collectionsFetchResultsAssets[section];
+ if( fetchResult.count >0)
+ {
+ title = self.collectionsLocalizedTitles[section - 1];
+ }
+ }
+ return title;
+}
+
+
+#pragma mark - PHPhotoLibraryChangeObserver
+
+- (void)photoLibraryDidChange:(PHChange *)changeInstance
+{
+ // Call might come on any background queue. Re-dispatch to the main queue to handle it.
+ dispatch_async(dispatch_get_main_queue(), ^{
+
+ NSMutableArray *updatedCollectionsFetchResults = nil;
+
+ for (PHFetchResult *collectionsFetchResult in self.collectionsFetchResults) {
+ PHFetchResultChangeDetails *changeDetails = [changeInstance changeDetailsForFetchResult:collectionsFetchResult];
+ if (changeDetails) {
+ if (!updatedCollectionsFetchResults) {
+ updatedCollectionsFetchResults = [self.collectionsFetchResults mutableCopy];
+ }
+ [updatedCollectionsFetchResults replaceObjectAtIndex:[self.collectionsFetchResults indexOfObject:collectionsFetchResult] withObject:[changeDetails fetchResultAfterChanges]];
+ }
+ }
+
+ //This only affects to changes in albums level (add/remove/edit album)
+ if (updatedCollectionsFetchResults)
+ {
+ self.collectionsFetchResults = updatedCollectionsFetchResults;
+ }
+
+ //However, we want to update if photos are added, so the counts of items & thumbnails are updated too.
+ //Maybe some checks could be done here , but for now is OKey.
+ [self updateFetchResults];
+ [self.tableView reloadData];
+
+ });
+}
+
+
+
+#pragma mark - Cell Subtitle
+
+- (NSString *)tableCellSubtitle:(PHFetchResult*)assetsFetchResult
+{
+ //Just return the number of assets. Album app does this:
+ return [NSString stringWithFormat:@"%ld", (long)[assetsFetchResult count]];
+}
+
+
+
+@end
diff --git a/src/ios/GMImagePicker/GMEmptyFolder@1x.png b/src/ios/GMImagePicker/GMEmptyFolder@1x.png
new file mode 100755
index 00000000..d2789c72
Binary files /dev/null and b/src/ios/GMImagePicker/GMEmptyFolder@1x.png differ
diff --git a/src/ios/GMImagePicker/GMEmptyFolder@2x.png b/src/ios/GMImagePicker/GMEmptyFolder@2x.png
new file mode 100755
index 00000000..58848ae1
Binary files /dev/null and b/src/ios/GMImagePicker/GMEmptyFolder@2x.png differ
diff --git a/src/ios/GMImagePicker/GMFetchItem.h b/src/ios/GMImagePicker/GMFetchItem.h
new file mode 100644
index 00000000..e8ead2d8
--- /dev/null
+++ b/src/ios/GMImagePicker/GMFetchItem.h
@@ -0,0 +1,26 @@
+//
+// GMFetchItem.h
+// GMPhotoPicker
+//
+// Created by micheladrion on 4/26/15.
+// Copyright (c) 2015 Guillermo Muntaner Perelló. All rights reserved.
+//
+
+#import
+
+#import
+
+
+@interface GMFetchItem : NSObject
+
+@property (nonatomic, assign) bool be_progressed;
+@property (nonatomic, assign) bool be_finished;
+@property (nonatomic, assign) double percent;
+@property (nonatomic, strong) NSString * image_fullsize;
+@property (nonatomic, strong) NSString * image_thumb;
+
+
+@property (nonatomic, assign) bool be_saving_img_thumb;
+@property (nonatomic, assign) bool be_saving_img;
+
+@end
diff --git a/src/ios/GMImagePicker/GMFetchItem.m b/src/ios/GMImagePicker/GMFetchItem.m
new file mode 100644
index 00000000..30442be0
--- /dev/null
+++ b/src/ios/GMImagePicker/GMFetchItem.m
@@ -0,0 +1,32 @@
+//
+// GMFetchItem.m
+// GMPhotoPicker
+//
+// Created by micheladrion on 4/26/15.
+// Copyright (c) 2015 Guillermo Muntaner Perelló. All rights reserved.
+//
+
+#import "GMFetchItem.h"
+
+@implementation GMFetchItem
+
+@synthesize be_progressed, be_finished, percent, image_fullsize, image_thumb, be_saving_img, be_saving_img_thumb;
+
+- (id)init{
+
+ self = [super init];
+
+ be_progressed = false;
+ be_finished = false;
+ percent = 0;
+
+ image_thumb = nil;
+ image_fullsize = nil;
+
+ be_saving_img = false;
+ be_saving_img_thumb;
+
+ return self;
+}
+
+@end
diff --git a/src/ios/GMImagePicker/GMGridViewCell.h b/src/ios/GMImagePicker/GMGridViewCell.h
new file mode 100755
index 00000000..757edb75
--- /dev/null
+++ b/src/ios/GMImagePicker/GMGridViewCell.h
@@ -0,0 +1,43 @@
+//
+// GMGridViewCell.h
+// GMPhotoPicker
+//
+// Created by Guillermo Muntaner Perelló on 19/09/14.
+// Copyright (c) 2014 Guillermo Muntaner Perelló. All rights reserved.
+//
+
+//#import "MRCircularProgressView.h"
+#import
+#import "FeHourGlass.h"
+
+
+@interface GMGridViewCell : UICollectionViewCell
+
+@property (nonatomic, strong) PHAsset *asset;
+//The imageView
+@property (nonatomic, strong) UIImageView *imageView;
+//Video additional information
+@property (nonatomic, strong) UIImageView *videoIcon;
+@property (nonatomic, strong) UILabel *videoDuration;
+@property (nonatomic, strong) UIView* gradientView;
+@property (nonatomic, strong) CAGradientLayer *gradient;
+//Selection overlay
+@property (nonatomic, strong) UIView* coverView;
+@property (nonatomic, strong) UIButton *selectedButton;
+
+@property (nonatomic, assign, getter = isEnabled) BOOL enabled;
+
+- (void)bind:(PHAsset *)asset;
+
+//@property (nonatomic, strong) MRCircularProgressView *circularProgressView;
+
+@property (strong, nonatomic) FeHourGlass *hourGlass;
+-(void)show_progress;
+-(void)set_progress:(float)value animated:(BOOL)animated;
+-(void)hide_progress;
+
+@property (nonatomic, strong) UILabel *fetch;
+-(void)show_fetching;
+-(void)hide_fetching;
+
+@end
diff --git a/src/ios/GMImagePicker/GMGridViewCell.m b/src/ios/GMImagePicker/GMGridViewCell.m
new file mode 100755
index 00000000..fdb140df
--- /dev/null
+++ b/src/ios/GMImagePicker/GMGridViewCell.m
@@ -0,0 +1,223 @@
+//
+// GMGridViewCell.m
+// GMPhotoPicker
+//
+// Created by Guillermo Muntaner Perelló on 19/09/14.
+// Copyright (c) 2014 Guillermo Muntaner Perelló. All rights reserved.
+//
+
+#import "GMGridViewCell.h"
+
+@interface GMGridViewCell ()
+
+
+
+@end
+
+
+@implementation GMGridViewCell
+//@synthesize circularProgressView;
+
+static UIFont *titleFont;
+static CGFloat titleHeight;
+static UIImage *videoIcon;
+static UIColor *titleColor;
+static UIImage *checkedIcon;
+static UIColor *selectedColor;
+static UIColor *disabledColor;
+
+
++ (void)initialize
+{
+ titleFont = [UIFont systemFontOfSize:12];
+ titleHeight = 20.0f;
+ videoIcon = [UIImage imageNamed:@"GMImagePickerVideo"];
+ titleColor = [UIColor whiteColor];
+ checkedIcon = [UIImage imageNamed:@"CTAssetsPickerChecked"];
+ selectedColor = [UIColor colorWithWhite:1 alpha:0.3];
+ disabledColor = [UIColor colorWithWhite:1 alpha:0.9];
+}
+
+- (void)awakeFromNib
+{
+ [super awakeFromNib];
+
+ self.contentView.autoresizingMask = UIViewAutoresizingFlexibleHeight | UIViewAutoresizingFlexibleWidth;
+ self.contentView.translatesAutoresizingMaskIntoConstraints = YES;
+}
+
+- (id)initWithFrame:(CGRect)frame
+{
+ if (self = [super initWithFrame:frame])
+ {
+ self.opaque = NO;
+ self.enabled = YES;
+
+ CGFloat cellSize = self.contentView.bounds.size.width;
+
+ // The image view
+ _imageView = [UIImageView new];
+ _imageView.frame = CGRectMake(0, 0, cellSize, cellSize);
+ _imageView.contentMode = UIViewContentModeScaleAspectFill;
+ /*if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad)
+ {
+ _imageView.contentMode = UIViewContentModeScaleAspectFit;
+ }
+ else
+ {
+ _imageView.contentMode = UIViewContentModeScaleAspectFill;
+ }*/
+ _imageView.clipsToBounds = YES;
+ //_imageView.translatesAutoresizingMaskIntoConstraints = NO;
+ //_imageView.autoresizingMask = UIViewAutoresizingFlexibleHeight | UIViewAutoresizingFlexibleWidth;
+ [self addSubview:_imageView];
+
+
+ // The video gradient, label & icon
+ float x_offset = 4.0f;
+ UIColor *topGradient = [UIColor colorWithRed:0.00 green:0.00 blue:0.00 alpha:0.0];
+ UIColor *botGradient = [UIColor colorWithRed:0.00 green:0.00 blue:0.00 alpha:0.8];
+ _gradientView = [[UIView alloc] initWithFrame: CGRectMake(0.0f, self.bounds.size.height-titleHeight, self.bounds.size.width, titleHeight)];
+ _gradient = [CAGradientLayer layer];
+ _gradient.frame = _gradientView.bounds;
+ _gradient.colors = [NSArray arrayWithObjects:(id)[topGradient CGColor], (id)[botGradient CGColor], nil];
+ [_gradientView.layer insertSublayer:_gradient atIndex:0];
+ _gradientView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleTopMargin;
+ _gradientView.translatesAutoresizingMaskIntoConstraints = NO;
+ [self addSubview:_gradientView];
+ _gradientView.hidden = YES;
+
+ _videoIcon = [UIImageView new];
+ _videoIcon.frame = CGRectMake(x_offset, self.bounds.size.height-titleHeight, self.bounds.size.width-2*x_offset, titleHeight);
+ _videoIcon.contentMode = UIViewContentModeLeft;
+ _videoIcon.image = [UIImage imageNamed:@"GMVideoIcon"];
+ _videoIcon.translatesAutoresizingMaskIntoConstraints = NO;
+ _videoIcon.autoresizingMask = UIViewAutoresizingFlexibleTopMargin | UIViewAutoresizingFlexibleWidth;
+ [self addSubview:_videoIcon];
+ _videoIcon.hidden = YES;
+
+ _videoDuration = [UILabel new];
+ _videoDuration.font = titleFont;
+ _videoDuration.textColor = titleColor;
+ _videoDuration.textAlignment = NSTextAlignmentRight;
+ _videoDuration.frame = CGRectMake(x_offset, self.bounds.size.height-titleHeight, self.bounds.size.width-2*x_offset, titleHeight);
+ _videoDuration.contentMode = UIViewContentModeRight;
+ _videoDuration.translatesAutoresizingMaskIntoConstraints = NO;
+ _videoDuration.autoresizingMask = UIViewAutoresizingFlexibleTopMargin | UIViewAutoresizingFlexibleWidth;
+ [self addSubview:_videoDuration];
+ _videoDuration.hidden = YES;
+
+ // Selection overlay & icon
+ _coverView = [[UIView alloc] initWithFrame:self.bounds];
+ _coverView.translatesAutoresizingMaskIntoConstraints = NO;
+ _coverView.autoresizingMask = UIViewAutoresizingFlexibleHeight | UIViewAutoresizingFlexibleWidth;
+ _coverView.backgroundColor = [UIColor colorWithRed:0.24 green:0.47 blue:0.85 alpha:0.6];
+ [self addSubview:_coverView];
+ _coverView.hidden = YES;
+
+ _selectedButton = [UIButton buttonWithType:UIButtonTypeCustom];
+ _selectedButton.frame = CGRectMake(2*self.bounds.size.width/3, 0*self.bounds.size.width/3, self.bounds.size.width/3, self.bounds.size.width/3);
+ _selectedButton.contentMode = UIViewContentModeTopRight;
+ _selectedButton.adjustsImageWhenHighlighted = NO;
+ [_selectedButton setImage:nil forState:UIControlStateNormal];
+ _selectedButton.translatesAutoresizingMaskIntoConstraints = NO;
+ _selectedButton.autoresizingMask = UIViewAutoresizingFlexibleHeight | UIViewAutoresizingFlexibleWidth;
+ [_selectedButton setImage:[UIImage imageNamed:@"GMSelected"] forState:UIControlStateSelected];
+ _selectedButton.hidden = NO;
+ _selectedButton.userInteractionEnabled = NO;
+ [self addSubview:_selectedButton];
+
+ // circle progress
+
+// self.circularProgressView = [[MRCircularProgressView alloc] initWithFrame:CGRectMake(self.bounds.size.width/3, self.bounds.size.height/3, self.bounds.size.width/3, self.bounds.size.height/3)];
+// [self.circularProgressView.stopButton addTarget:self action:@selector(onCircularProgressViewTouchUpInside:) forControlEvents:UIControlEventTouchUpInside];
+// [self.circularProgressView setHidden:true];
+// [self addSubview:self.circularProgressView];
+
+
+ _fetch = [UILabel new];
+ _fetch.font = titleFont;
+ _fetch.textColor = titleColor;
+ _fetch.textAlignment = NSTextAlignmentCenter;
+ _fetch.frame = CGRectMake(0, 0, self.bounds.size.width, self.bounds.size.height);
+ _fetch.text = @""; // removed 'fetching'
+ [self addSubview:_fetch];
+
+ }
+
+ return self;
+}
+
+-(void)show_progress{
+// [self.circularProgressView setHidden:false];
+ self.hourGlass = [[FeHourGlass alloc] initWithView:self];
+ [self addSubview:self.hourGlass];
+ [self.hourGlass show];
+}
+
+-(void)hide_progress{
+// [self.circularProgressView setHidden:true];
+// [self.hourGlass dismiss];
+ [self.hourGlass removeFromSuperview];
+
+}
+
+-(void)set_progress:(float)value animated:(BOOL)animated{
+// [self.circularProgressView setProgress:value animated:animated];
+}
+
+-(void)show_fetching{
+ _fetch.hidden = false;
+}
+
+-(void)hide_fetching{
+ _fetch.hidden = true;
+}
+
+- (void)onCircularProgressViewTouchUpInside:(id)sender {
+// self.circularProgressView.progress = 0;
+}
+
+//Required to resize the CAGradientLayer because it does not support auto resizing.
+- (void)layoutSubviews {
+ [super layoutSubviews];
+ _gradient.frame = _gradientView.bounds;
+}
+
+- (void)bind:(PHAsset *)asset
+{
+ self.asset = asset;
+
+ if (self.asset.mediaType == PHAssetMediaTypeVideo)
+ {
+ _videoIcon.hidden = NO;
+ _videoDuration.hidden = NO;
+ _gradientView.hidden = NO;
+ _videoDuration.text = [self getDurationWithFormat:self.asset.duration];
+ }
+ else
+ {
+ _videoIcon.hidden = YES;
+ _videoDuration.hidden = YES;
+ _gradientView.hidden = YES;
+ }
+}
+
+// Override setSelected
+- (void)setSelected:(BOOL)selected
+{
+ [super setSelected:selected];
+ _coverView.hidden = !selected;
+ _selectedButton.selected = selected;
+}
+
+-(NSString*)getDurationWithFormat:(NSTimeInterval)duration
+{
+ NSInteger ti = (NSInteger)duration;
+ NSInteger seconds = ti % 60;
+ NSInteger minutes = (ti / 60) % 60;
+ //NSInteger hours = (ti / 3600);
+ return [NSString stringWithFormat:@"%02ld:%02ld", (long)minutes, (long)seconds];
+}
+
+@end
diff --git a/src/ios/GMImagePicker/GMGridViewController.h b/src/ios/GMImagePicker/GMGridViewController.h
new file mode 100755
index 00000000..e9930a15
--- /dev/null
+++ b/src/ios/GMImagePicker/GMGridViewController.h
@@ -0,0 +1,24 @@
+//
+// GMGridViewController.h
+// GMPhotoPicker
+//
+// Created by Guillermo Muntaner Perelló on 19/09/14.
+// Copyright (c) 2014 Guillermo Muntaner Perelló. All rights reserved.
+//
+
+
+#import "GMImagePickerController.h"
+#import "UIImage+fixOrientation.h"
+
+#import
+
+
+
+@interface GMGridViewController : UICollectionViewController
+
+@property (strong) PHFetchResult *assetsFetchResults;
+@property (nonatomic, weak) NSMutableDictionary * dic_asset_fetches;
+
+-(id)initWithPicker:(GMImagePickerController *)picker;
+
+@end
\ No newline at end of file
diff --git a/src/ios/GMImagePicker/GMGridViewController.m b/src/ios/GMImagePicker/GMGridViewController.m
new file mode 100755
index 00000000..6636eb72
--- /dev/null
+++ b/src/ios/GMImagePicker/GMGridViewController.m
@@ -0,0 +1,770 @@
+//
+// GMGridViewController.m
+// GMPhotoPicker
+//
+// Created by Guillermo Muntaner Perelló on 19/09/14.
+// Copyright (c) 2014 Guillermo Muntaner Perelló. All rights reserved.
+//
+
+#import "GMGridViewController.h"
+#import "GMImagePickerController.h"
+#import "GMAlbumsViewController.h"
+#import "GMGridViewCell.h"
+#import "GMPHAsset.h"
+
+//#import "PSYBlockTimer.h"
+#import "GMFetchItem.h"
+
+
+
+#define CDV_PHOTO_PREFIX @"cdv_photo_"
+#define CDV_THUMB_PREFIX @"cdv_thumb_"
+
+
+//Helper methods
+@implementation NSIndexSet (Convenience)
+- (NSArray *)aapl_indexPathsFromIndexesWithSection:(NSUInteger)section {
+ NSMutableArray *indexPaths = [NSMutableArray arrayWithCapacity:self.count];
+ [self enumerateIndexesUsingBlock:^(NSUInteger idx, BOOL *stop) {
+ [indexPaths addObject:[NSIndexPath indexPathForItem:idx inSection:section]];
+ }];
+ return indexPaths;
+}
+@end
+@implementation UICollectionView (Convenience)
+- (NSArray *)aapl_indexPathsForElementsInRect:(CGRect)rect {
+ NSArray *allLayoutAttributes = [self.collectionViewLayout layoutAttributesForElementsInRect:rect];
+ if (allLayoutAttributes.count == 0) { return nil; }
+ NSMutableArray *indexPaths = [NSMutableArray arrayWithCapacity:allLayoutAttributes.count];
+ for (UICollectionViewLayoutAttributes *layoutAttributes in allLayoutAttributes) {
+ NSIndexPath *indexPath = layoutAttributes.indexPath;
+ [indexPaths addObject:indexPath];
+ }
+ return indexPaths;
+}
+@end
+
+
+
+@interface GMImagePickerController (){
+}
+
+- (void)finishPickingAssets:(id)sender;
+- (NSString *)toolbarTitle;
+- (UIView *)noAssetsView;
+
+@end
+
+
+@interface GMGridViewController ()
+
+@property (nonatomic, weak) GMImagePickerController *picker;
+@property (strong) PHCachingImageManager *imageManager;
+@property CGRect previousPreheatRect;
+
+@end
+
+static CGSize AssetGridThumbnailSize;
+NSString * const GMGridViewCellIdentifier = @"GMGridViewCellIdentifier";
+
+@implementation GMGridViewController
+{
+ CGFloat screenWidth;
+ CGFloat screenHeight;
+ UICollectionViewFlowLayout *portraitLayout;
+ UICollectionViewFlowLayout *landscapeLayout;
+
+ NSFileManager* fileMgr;
+ NSString* docsPath;
+ int docCount;
+ int doc_thumbCount;
+}
+
+@synthesize dic_asset_fetches;
+
+-(id)initWithPicker:(GMImagePickerController *)picker
+{
+ //Custom init. The picker contains custom information to create the FlowLayout
+ self.picker = picker;
+
+ //Ipad popover is not affected by rotation!
+ if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad)
+ {
+ screenWidth = CGRectGetWidth(picker.view.bounds);
+ screenHeight = CGRectGetHeight(picker.view.bounds);
+ }
+ else
+ {
+ if(UIInterfaceOrientationIsLandscape([UIApplication sharedApplication].statusBarOrientation))
+ {
+ screenHeight = CGRectGetWidth(picker.view.bounds);
+ screenWidth = CGRectGetHeight(picker.view.bounds);
+ }
+ else
+ {
+ screenWidth = CGRectGetWidth(picker.view.bounds);
+ screenHeight = CGRectGetHeight(picker.view.bounds);
+ }
+ }
+
+
+ UICollectionViewFlowLayout *layout = [self collectionViewFlowLayoutForOrientation:[UIApplication sharedApplication].statusBarOrientation];
+ if (self = [super initWithCollectionViewLayout:layout])
+ {
+ //Compute the thumbnail pixel size:
+ CGFloat scale = [UIScreen mainScreen].scale;
+ //NSLog(@"This is @%fx scale device", scale);
+ AssetGridThumbnailSize = CGSizeMake(layout.itemSize.width * scale, layout.itemSize.height * scale);
+
+ self.collectionView.allowsMultipleSelection = YES;
+
+ [self.collectionView registerClass:GMGridViewCell.class
+ forCellWithReuseIdentifier:GMGridViewCellIdentifier];
+
+ self.preferredContentSize = kPopoverContentSize;
+ }
+
+ ///
+ fileMgr = [[NSFileManager alloc] init];
+ //dic_asset_fetches = [[NSMutableDictionary alloc] init];
+ docsPath = [NSTemporaryDirectory()stringByStandardizingPath];
+ docCount = 0;
+ doc_thumbCount = 0;
+
+ return self;
+}
+
+
+- (void)viewDidLoad
+{
+ [super viewDidLoad];
+ [self setupViews];
+
+ //Navigation bar customization_
+ if(self.picker.customNavigationBarPrompt)
+ {
+ self.navigationItem.prompt = self.picker.customNavigationBarPrompt;
+ }
+
+ self.imageManager = [[PHCachingImageManager alloc] init];
+ [self resetCachedAssets];
+ [[PHPhotoLibrary sharedPhotoLibrary] registerChangeObserver:self];
+
+}
+
+- (void)viewWillAppear:(BOOL)animated
+{
+ [super viewWillAppear:animated];
+
+ [self setupButtons];
+ [self setupToolbar];
+}
+
+- (void)viewDidAppear:(BOOL)animated
+{
+ [super viewDidAppear:animated];
+ [self updateCachedAssets];
+}
+
+- (void)dealloc
+{
+ [self resetCachedAssets];
+ [[PHPhotoLibrary sharedPhotoLibrary] unregisterChangeObserver:self];
+}
+
+
+
+#pragma mark - Rotation
+
+- (void)willAnimateRotationToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation duration:(NSTimeInterval)duration
+{
+ if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad)
+ {
+ return;
+ }
+
+ UICollectionViewFlowLayout *layout = [self collectionViewFlowLayoutForOrientation:toInterfaceOrientation];
+
+ //Update the AssetGridThumbnailSize:
+ CGFloat scale = [UIScreen mainScreen].scale;
+ AssetGridThumbnailSize = CGSizeMake(layout.itemSize.width * scale, layout.itemSize.height * scale);
+
+ [self resetCachedAssets];
+ //This is optional. Reload visible thumbnails:
+ for (GMGridViewCell *cell in [self.collectionView visibleCells])
+ {
+ NSInteger currentTag = cell.tag;
+ [self.imageManager requestImageForAsset:cell.asset
+ targetSize:AssetGridThumbnailSize
+ contentMode:PHImageContentModeAspectFill
+ options:nil
+ resultHandler:^(UIImage *result, NSDictionary *info)
+ {
+ // Only update the thumbnail if the cell tag hasn't changed. Otherwise, the cell has been re-used.
+ if (cell.tag == currentTag) {
+ [cell.imageView setImage:result];
+ }
+ }];
+ }
+
+ [self.collectionView setCollectionViewLayout:layout animated:YES];
+}
+
+
+
+
+
+
+#pragma mark - Setup
+
+- (void)setupViews
+{
+ self.collectionView.backgroundColor = [UIColor whiteColor];
+}
+
+- (void)setupButtons
+{
+ self.navigationItem.rightBarButtonItem =
+ [[UIBarButtonItem alloc] initWithTitle:NSLocalizedStringFromTable(@"picker.navigation.done-button", @"GMImagePicker",@"Done")
+ style:UIBarButtonItemStyleDone
+ target:self.picker
+ action:@selector(finishPickingAssets:)];
+
+ self.navigationItem.rightBarButtonItem.enabled = (self.picker.selectedAssets.count > 0);
+}
+
+- (void)setupToolbar
+{
+ self.toolbarItems = self.picker.toolbarItems;
+}
+
+#pragma mark - Collection View Layout
+
+
+- (UICollectionViewFlowLayout *)collectionViewFlowLayoutForOrientation:(UIInterfaceOrientation)orientation
+{
+ if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad)
+ {
+ if(!portraitLayout)
+ {
+ portraitLayout = [[UICollectionViewFlowLayout alloc] init];
+ portraitLayout.minimumInteritemSpacing = self.picker.minimumInteritemSpacing;
+ int cellTotalUsableWidth = screenWidth - (self.picker.colsInPortrait-1)*self.picker.minimumInteritemSpacing;
+ portraitLayout.itemSize = CGSizeMake(cellTotalUsableWidth/self.picker.colsInPortrait, cellTotalUsableWidth/self.picker.colsInPortrait);
+ double cellTotalUsedWidth = (double)portraitLayout.itemSize.width*self.picker.colsInPortrait;
+ double spaceTotalWidth = (double)screenWidth-cellTotalUsedWidth;
+ double spaceWidth = spaceTotalWidth/(double)(self.picker.colsInPortrait-1);
+ portraitLayout.minimumLineSpacing = spaceWidth;
+ }
+ return portraitLayout;
+ }
+ else
+ {
+ if(UIInterfaceOrientationIsLandscape(orientation))
+ {
+ if(!landscapeLayout)
+ {
+ landscapeLayout = [[UICollectionViewFlowLayout alloc] init];
+ landscapeLayout.minimumInteritemSpacing = self.picker.minimumInteritemSpacing;
+ int cellTotalUsableWidth = screenHeight - (self.picker.colsInLandscape-1)*self.picker.minimumInteritemSpacing;
+ landscapeLayout.itemSize = CGSizeMake(cellTotalUsableWidth/self.picker.colsInLandscape, cellTotalUsableWidth/self.picker.colsInLandscape);
+ double cellTotalUsedWidth = (double)landscapeLayout.itemSize.width*self.picker.colsInLandscape;
+ double spaceTotalWidth = (double)screenHeight-cellTotalUsedWidth;
+ double spaceWidth = spaceTotalWidth/(double)(self.picker.colsInLandscape-1);
+ landscapeLayout.minimumLineSpacing = spaceWidth;
+ }
+ return landscapeLayout;
+ }
+ else
+ {
+ if(!portraitLayout)
+ {
+ portraitLayout = [[UICollectionViewFlowLayout alloc] init];
+ portraitLayout.minimumInteritemSpacing = self.picker.minimumInteritemSpacing;
+ int cellTotalUsableWidth = screenWidth - (self.picker.colsInPortrait-1)*self.picker.minimumInteritemSpacing;
+ portraitLayout.itemSize = CGSizeMake(cellTotalUsableWidth/self.picker.colsInPortrait, cellTotalUsableWidth/self.picker.colsInPortrait);
+ double cellTotalUsedWidth = (double)portraitLayout.itemSize.width*self.picker.colsInPortrait;
+ double spaceTotalWidth = (double)screenWidth-cellTotalUsedWidth;
+ double spaceWidth = spaceTotalWidth/(double)(self.picker.colsInPortrait-1);
+ portraitLayout.minimumLineSpacing = spaceWidth;
+ }
+ return portraitLayout;
+ }
+ }
+}
+
+
+#pragma mark - Collection View Data Source
+
+- (NSInteger)numberOfSectionsInCollectionView:(UICollectionView *)collectionView
+{
+ return 1;
+}
+
+- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath
+{
+ GMGridViewCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:GMGridViewCellIdentifier
+ forIndexPath:indexPath];
+
+ // Increment the cell's tag
+ NSInteger currentTag = cell.tag + 1;
+ cell.tag = currentTag;
+
+ PHAsset *asset = self.assetsFetchResults[indexPath.item];
+ [cell bind:asset];
+
+ //GMFetchItem * fetch_item = [dic_asset_fetches objectForKey:[NSNumber numberWithLong:indexPath.item] ];
+ GMFetchItem * fetch_item = [dic_asset_fetches objectForKey:asset ];
+ if ( fetch_item == nil ) {
+ fetch_item = [[GMFetchItem alloc] init];
+ //[ dic_asset_fetches setObject:fetch_item forKey:[NSNumber numberWithLong:indexPath.item] ];
+ [ dic_asset_fetches setObject:fetch_item forKey:asset ];
+ }
+
+ //Optional protocol to determine if some kind of assets can't be selected (pej long videos, etc...)
+ if ([self.picker.delegate respondsToSelector:@selector(assetsPickerController:shouldEnableAsset:)])
+ {
+ cell.enabled = [self.picker.delegate assetsPickerController:self.picker shouldEnableAsset:asset];
+ }
+ else
+ {
+ cell.enabled = YES;
+ }
+
+ // Setting `selected` property blocks further deselection. Have to call selectItemAtIndexPath too. ( ref: http://stackoverflow.com/a/17812116/1648333 )
+ if ([self.picker.selectedAssets containsObject:asset])
+ {
+ cell.selected = YES;
+ [collectionView selectItemAtIndexPath:indexPath animated:NO scrollPosition:UICollectionViewScrollPositionNone];
+ }
+ else
+ {
+ cell.selected = NO;
+ }
+
+ [cell set_progress:fetch_item.percent animated:false];
+
+ if ( fetch_item.be_finished ) {
+ [ cell hide_progress ];
+ }
+ else if ( fetch_item.be_progressed ) {
+ [ cell show_progress ];
+ [ cell set_progress:fetch_item.percent animated:false];
+ }else{
+ [ cell hide_progress ];
+ }
+
+ if ( fetch_item.be_saving_img ) {
+ [ cell show_fetching ];
+ }else{
+ [ cell hide_fetching ];
+ }
+
+ //NSLog( @" cell : %ld ", (long)indexPath.item );
+
+ /*if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad)
+ {
+ NSLog(@"Image manager: Requesting FIT image for iPad");
+ [self.imageManager requestImageForAsset:asset
+ targetSize:AssetGridThumbnailSize
+ contentMode:PHImageContentModeAspectFit
+ options:nil
+ resultHandler:^(UIImage *result, NSDictionary *info) {
+
+ // Only update the thumbnail if the cell tag hasn't changed. Otherwise, the cell has been re-used.
+ if (cell.tag == currentTag) {
+ [cell.imageView setImage:result];
+ }
+ }];
+ }
+ else*/
+ {
+ //NSLog(@"Image manager: Requesting FILL image for iPhone");
+ [self.imageManager requestImageForAsset:asset
+ targetSize:AssetGridThumbnailSize
+ contentMode:PHImageContentModeAspectFill
+ options:nil
+ resultHandler:^(UIImage *result, NSDictionary *info) {
+
+ // Only update the thumbnail if the cell tag hasn't changed. Otherwise, the cell has been re-used.
+ if (cell.tag == currentTag) {
+ [cell.imageView setImage:result];
+ }
+
+ if ( fetch_item.be_saving_img_thumb==false && fetch_item.image_thumb == nil && result!= nil ) {
+
+ fetch_item.be_saving_img_thumb = true;
+
+ NSString * filePath;
+ do {
+ filePath = [NSString stringWithFormat:@"%@/%@%03d.%@", docsPath, CDV_THUMB_PREFIX, doc_thumbCount++, @"jpg"];
+ } while ([fileMgr fileExistsAtPath:filePath]);
+
+ dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
+
+ fetch_item.be_saving_img_thumb = false;
+
+ // TODO pass in quality
+ if ( ![ UIImageJPEGRepresentation(result, 1.0f ) writeToFile:filePath atomically:YES ] ) {
+ return;
+ }
+
+ fetch_item.image_thumb = filePath;
+
+ });
+ }
+
+ /*GMGridViewCell *cell = (GMGridViewCell *)[collectionView cellForItemAtIndexPath:indexPath];
+
+ if ( cell ) {
+ [cell.imageView setImage:result];
+ }
+ NSLog( @"%d", indexPath.item );*/
+
+ }];
+ }
+
+
+
+ return cell;
+}
+
+
+#pragma mark - Collection View Delegate
+
+- (BOOL)collectionView:(UICollectionView *)collectionView shouldSelectItemAtIndexPath:(NSIndexPath *)indexPath
+{
+ if(self.picker.selectedAssets.count >= self.picker.maximumImagesCount) {
+ return NO;
+ }
+
+ PHAsset *asset = self.assetsFetchResults[indexPath.item];
+ //GMFetchItem * fetch_item = [dic_asset_fetches objectForKey:[ NSNumber numberWithLong:indexPath.item ]];
+ GMFetchItem * fetch_item = [dic_asset_fetches objectForKey:asset];
+
+ GMGridViewCell *cell = (GMGridViewCell *)[collectionView cellForItemAtIndexPath:indexPath];
+
+ if ( cell == nil || fetch_item==nil || fetch_item.be_progressed ) {
+ return NO;
+ }
+
+ if ( fetch_item.be_saving_img == false && fetch_item.image_fullsize == nil ) {
+
+ fetch_item.be_progressed = true;
+ [ cell show_progress ];
+
+ PHImageRequestOptions *ph_options = [[PHImageRequestOptions alloc] init];
+
+ [ ph_options setNetworkAccessAllowed:YES];
+
+ // @BVL Set Deliverymode, in order to return highest quality
+ [ ph_options setDeliveryMode: PHImageRequestOptionsDeliveryModeHighQualityFormat ]; // Best Quality
+
+ [ ph_options setProgressHandler:^(double progress, NSError *error, BOOL *stop, NSDictionary *info) {
+
+ fetch_item.percent = progress;
+
+ GMGridViewCell *cell = (GMGridViewCell *)[collectionView cellForItemAtIndexPath:indexPath];
+
+ if ( cell ) {
+ [ cell set_progress:progress animated:false];
+ }
+
+ }];
+
+
+
+ [ self.imageManager requestImageForAsset:asset targetSize:PHImageManagerMaximumSize contentMode:PHImageContentModeDefault options:ph_options resultHandler:^(UIImage *result, NSDictionary *info) {
+
+ //dispatch_async(dispatch_get_main_queue(), ^{
+
+ GMGridViewCell *cell = (GMGridViewCell *)[collectionView cellForItemAtIndexPath:indexPath];
+
+ if ( cell ) {
+ [cell hide_progress];
+ [cell show_fetching];
+ }
+
+ fetch_item.be_progressed = false;
+ fetch_item.be_finished = true;
+
+ //asset.image_fullsize = result;
+
+ NSString * filePath;
+ do {
+ filePath = [NSString stringWithFormat:@"%@/%@%03d.%@", docsPath, CDV_PHOTO_PREFIX, docCount++, @"jpg"];
+ } while ([fileMgr fileExistsAtPath:filePath]);
+
+ fetch_item.be_saving_img = true;
+
+ dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
+
+
+ // @BVL: Added orientation-fix to correctly display the returned result
+
+// if ( ![ UIImageJPEGRepresentation(result, 1.0f ) writeToFile:filePath atomically:YES ] ) {
+// return;
+// }
+
+ NSLog(@"original orientation: %ld",(UIImageOrientation)result.imageOrientation);
+
+ UIImage *imageToDisplay = result.fixOrientation; // UIImage+fixOrientation extension
+
+ NSLog(@"corrected orientation: %ld",(UIImageOrientation)imageToDisplay.imageOrientation);
+
+ // setting compression to a low value (high compression) impact performance, but not actual img quality
+ if ( ![ UIImageJPEGRepresentation(imageToDisplay, 0.2f ) writeToFile:filePath atomically:YES ] ) {
+ return;
+ }
+
+ fetch_item.image_fullsize = filePath;
+ fetch_item.be_saving_img = false;
+
+ dispatch_async(dispatch_get_main_queue(), ^{
+
+ GMGridViewCell *cell = (GMGridViewCell *)[collectionView cellForItemAtIndexPath:indexPath];
+
+ if ( cell ) {
+ [cell hide_fetching];
+ }
+
+ //Your main thread code goes in here
+ [ collectionView selectItemAtIndexPath:indexPath animated:NO scrollPosition:UICollectionViewScrollPositionNone ];
+ [ self collectionView:collectionView didSelectItemAtIndexPath:indexPath ];
+ });
+
+ });
+ //});
+
+ }];
+
+
+ return NO;
+ }
+
+ if (!cell.isEnabled)
+ return NO;
+ else if ([self.picker.delegate respondsToSelector:@selector(assetsPickerController:shouldSelectAsset:)])
+ return [self.picker.delegate assetsPickerController:self.picker shouldSelectAsset:asset];
+ else
+ return YES;
+}
+
+- (void)collectionView:(UICollectionView *)collectionView didSelectItemAtIndexPath:(NSIndexPath *)indexPath
+{
+ PHAsset *asset = self.assetsFetchResults[indexPath.item];
+ //GMFetchItem * fetch_item = [dic_asset_fetches objectForKey:[ NSNumber numberWithLong:indexPath.item ]];
+ GMFetchItem * fetch_item = [dic_asset_fetches objectForKey:asset];
+
+ [self.picker selectAsset:asset];
+ [self.picker selectFetchItem:fetch_item];
+
+ if ([self.picker.delegate respondsToSelector:@selector(assetsPickerController:didSelectAsset:)])
+ [self.picker.delegate assetsPickerController:self.picker didSelectAsset:asset];
+}
+
+- (BOOL)collectionView:(UICollectionView *)collectionView shouldDeselectItemAtIndexPath:(NSIndexPath *)indexPath
+{
+ PHAsset *asset = self.assetsFetchResults[indexPath.item];
+
+ if ([self.picker.delegate respondsToSelector:@selector(assetsPickerController:shouldDeselectAsset:)])
+ return [self.picker.delegate assetsPickerController:self.picker shouldDeselectAsset:asset];
+ else
+ return YES;
+}
+
+- (void)collectionView:(UICollectionView *)collectionView didDeselectItemAtIndexPath:(NSIndexPath *)indexPath
+{
+ PHAsset *asset = self.assetsFetchResults[indexPath.item];
+ //GMFetchItem * fetch_item = [dic_asset_fetches objectForKey:[ NSNumber numberWithLong:indexPath.item ]];
+ GMFetchItem * fetch_item = [dic_asset_fetches objectForKey:asset];
+
+ [self.picker deselectAsset:asset];
+ [self.picker deselectFetchItem:fetch_item];
+
+ if ([self.picker.delegate respondsToSelector:@selector(assetsPickerController:didDeselectAsset:)])
+ [self.picker.delegate assetsPickerController:self.picker didDeselectAsset:asset];
+}
+
+- (BOOL)collectionView:(UICollectionView *)collectionView shouldHighlightItemAtIndexPath:(NSIndexPath *)indexPath
+{
+ PHAsset *asset = self.assetsFetchResults[indexPath.item];
+
+ if ([self.picker.delegate respondsToSelector:@selector(assetsPickerController:shouldHighlightAsset:)])
+ return [self.picker.delegate assetsPickerController:self.picker shouldHighlightAsset:asset];
+ else
+ return YES;
+}
+
+- (void)collectionView:(UICollectionView *)collectionView didHighlightItemAtIndexPath:(NSIndexPath *)indexPath
+{
+ PHAsset *asset = self.assetsFetchResults[indexPath.item];
+
+ if ([self.picker.delegate respondsToSelector:@selector(assetsPickerController:didHighlightAsset:)])
+ [self.picker.delegate assetsPickerController:self.picker didHighlightAsset:asset];
+}
+
+- (void)collectionView:(UICollectionView *)collectionView didUnhighlightItemAtIndexPath:(NSIndexPath *)indexPath
+{
+ PHAsset *asset = self.assetsFetchResults[indexPath.item];
+
+ if ([self.picker.delegate respondsToSelector:@selector(assetsPickerController:didUnhighlightAsset:)])
+ [self.picker.delegate assetsPickerController:self.picker didUnhighlightAsset:asset];
+}
+
+
+
+#pragma mark - UICollectionViewDataSource
+
+- (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section
+{
+ NSInteger count = self.assetsFetchResults.count;
+ return count;
+}
+
+
+#pragma mark - PHPhotoLibraryChangeObserver
+
+- (void)photoLibraryDidChange:(PHChange *)changeInstance
+{
+ // Call might come on any background queue. Re-dispatch to the main queue to handle it.
+ dispatch_async(dispatch_get_main_queue(), ^{
+
+ // check if there are changes to the assets (insertions, deletions, updates)
+ PHFetchResultChangeDetails *collectionChanges = [changeInstance changeDetailsForFetchResult:self.assetsFetchResults];
+ if (collectionChanges) {
+
+ // get the new fetch result
+ self.assetsFetchResults = [collectionChanges fetchResultAfterChanges];
+
+ //NSLog( @"reset all" );
+
+ UICollectionView *collectionView = self.collectionView;
+
+ if (![collectionChanges hasIncrementalChanges] || [collectionChanges hasMoves]) {
+ // we need to reload all if the incremental diffs are not available
+ [collectionView reloadData];
+
+ } else {
+ // if we have incremental diffs, tell the collection view to animate insertions and deletions
+ [collectionView performBatchUpdates:^{
+ NSIndexSet *removedIndexes = [collectionChanges removedIndexes];
+ if ([removedIndexes count]) {
+ [collectionView deleteItemsAtIndexPaths:[removedIndexes aapl_indexPathsFromIndexesWithSection:0]];
+ }
+ NSIndexSet *insertedIndexes = [collectionChanges insertedIndexes];
+ if ([insertedIndexes count]) {
+ [collectionView insertItemsAtIndexPaths:[insertedIndexes aapl_indexPathsFromIndexesWithSection:0]];
+ }
+ NSIndexSet *changedIndexes = [collectionChanges changedIndexes];
+ if ([changedIndexes count]) {
+ [collectionView reloadItemsAtIndexPaths:[changedIndexes aapl_indexPathsFromIndexesWithSection:0]];
+ }
+ } completion:NULL];
+ }
+
+ [self resetCachedAssets];
+ }
+ });
+}
+
+#pragma mark - UIScrollViewDelegate
+
+- (void)scrollViewDidScroll:(UIScrollView *)scrollView
+{
+ [self updateCachedAssets];
+}
+
+
+#pragma mark - Asset Caching
+
+- (void)resetCachedAssets
+{
+ [self.imageManager stopCachingImagesForAllAssets];
+ self.previousPreheatRect = CGRectZero;
+}
+
+- (void)updateCachedAssets
+{
+ BOOL isViewVisible = [self isViewLoaded] && [[self view] window] != nil;
+ if (!isViewVisible) { return; }
+
+ // The preheat window is twice the height of the visible rect
+ CGRect preheatRect = self.collectionView.bounds;
+ preheatRect = CGRectInset(preheatRect, 0.0f, -0.5f * CGRectGetHeight(preheatRect));
+
+ // If scrolled by a "reasonable" amount...
+ CGFloat delta = ABS(CGRectGetMidY(preheatRect) - CGRectGetMidY(self.previousPreheatRect));
+ if (delta > CGRectGetHeight(self.collectionView.bounds) / 3.0f) {
+
+ // Compute the assets to start caching and to stop caching.
+ NSMutableArray *addedIndexPaths = [NSMutableArray array];
+ NSMutableArray *removedIndexPaths = [NSMutableArray array];
+
+ [self computeDifferenceBetweenRect:self.previousPreheatRect andRect:preheatRect removedHandler:^(CGRect removedRect) {
+ NSArray *indexPaths = [self.collectionView aapl_indexPathsForElementsInRect:removedRect];
+ [removedIndexPaths addObjectsFromArray:indexPaths];
+ } addedHandler:^(CGRect addedRect) {
+ NSArray *indexPaths = [self.collectionView aapl_indexPathsForElementsInRect:addedRect];
+ [addedIndexPaths addObjectsFromArray:indexPaths];
+ }];
+
+ NSArray *assetsToStartCaching = [self assetsAtIndexPaths:addedIndexPaths];
+ NSArray *assetsToStopCaching = [self assetsAtIndexPaths:removedIndexPaths];
+
+ [self.imageManager startCachingImagesForAssets:assetsToStartCaching
+ targetSize:AssetGridThumbnailSize
+ contentMode:PHImageContentModeAspectFill
+ options:nil];
+ [self.imageManager stopCachingImagesForAssets:assetsToStopCaching
+ targetSize:AssetGridThumbnailSize
+ contentMode:PHImageContentModeAspectFill
+ options:nil];
+
+ self.previousPreheatRect = preheatRect;
+ }
+}
+
+- (void)computeDifferenceBetweenRect:(CGRect)oldRect andRect:(CGRect)newRect removedHandler:(void (^)(CGRect removedRect))removedHandler addedHandler:(void (^)(CGRect addedRect))addedHandler
+{
+ if (CGRectIntersectsRect(newRect, oldRect)) {
+ CGFloat oldMaxY = CGRectGetMaxY(oldRect);
+ CGFloat oldMinY = CGRectGetMinY(oldRect);
+ CGFloat newMaxY = CGRectGetMaxY(newRect);
+ CGFloat newMinY = CGRectGetMinY(newRect);
+ if (newMaxY > oldMaxY) {
+ CGRect rectToAdd = CGRectMake(newRect.origin.x, oldMaxY, newRect.size.width, (newMaxY - oldMaxY));
+ addedHandler(rectToAdd);
+ }
+ if (oldMinY > newMinY) {
+ CGRect rectToAdd = CGRectMake(newRect.origin.x, newMinY, newRect.size.width, (oldMinY - newMinY));
+ addedHandler(rectToAdd);
+ }
+ if (newMaxY < oldMaxY) {
+ CGRect rectToRemove = CGRectMake(newRect.origin.x, newMaxY, newRect.size.width, (oldMaxY - newMaxY));
+ removedHandler(rectToRemove);
+ }
+ if (oldMinY < newMinY) {
+ CGRect rectToRemove = CGRectMake(newRect.origin.x, oldMinY, newRect.size.width, (newMinY - oldMinY));
+ removedHandler(rectToRemove);
+ }
+ } else {
+ addedHandler(newRect);
+ removedHandler(oldRect);
+ }
+}
+
+- (NSArray *)assetsAtIndexPaths:(NSArray *)indexPaths
+{
+ if (indexPaths.count == 0) { return nil; }
+
+ NSMutableArray *assets = [NSMutableArray arrayWithCapacity:indexPaths.count];
+ for (NSIndexPath *indexPath in indexPaths) {
+ PHAsset *asset = self.assetsFetchResults[indexPath.item];
+ [assets addObject:asset];
+ }
+ return assets;
+}
+
+
+@end
diff --git a/src/ios/GMImagePicker/GMImagePickerController.h b/src/ios/GMImagePicker/GMImagePickerController.h
new file mode 100755
index 00000000..8bb4214b
--- /dev/null
+++ b/src/ios/GMImagePicker/GMImagePickerController.h
@@ -0,0 +1,238 @@
+//
+// GMImagePickerController.h
+// GMPhotoPicker
+//
+// Created by Guillermo Muntaner Perelló on 19/09/14.
+// Copyright (c) 2014 Guillermo Muntaner Perelló. All rights reserved.
+//
+
+
+#import
+
+#import "GMFetchItem.h"
+
+//This is the default image picker size!
+//static CGSize const kPopoverContentSize = {320, 480};
+//However, the iPad is 1024x768 so it can allow popups up to 768!
+static CGSize const kPopoverContentSize = {480, 720};
+
+
+@protocol GMImagePickerControllerDelegate;
+
+
+/**
+ * A controller that allows picking multiple photos and videos from user's photo library.
+ */
+@interface GMImagePickerController : UIViewController
+
+- (id)init:(bool)allow_v;
+
+/**
+ * The assets picker’s delegate object.
+ */
+@property (nonatomic, weak) id delegate;
+
+/**
+ * It contains the selected `PHAsset` objects. The order of the objects is the selection order.
+ *
+ * You can add assets before presenting the picker to show the user some preselected assets.
+ */
+@property (nonatomic, strong) NSMutableArray *selectedAssets;
+@property (nonatomic, strong) NSMutableArray *selectedFetches;
+
+
+/** UI Customizations **/
+
+/**
+ * Determines whether or not the number of assets is shown in the Album list.
+ * The number of assets is visible by default.
+ */
+@property (nonatomic, strong) NSArray* customSmartCollections;
+
+/**
+ * If set, it displays a promt in the navigation bar
+ */
+@property (nonatomic) NSString* customNavigationBarPrompt;
+
+/**
+ * Determines whether or not a toolbar with info about user selection is shown.
+ * The InfoToolbar is visible by default.
+ */
+@property (nonatomic) BOOL displaySelectionInfoToolbar;
+
+/**
+ * Determines whether or not the number of assets is shown in the Album list.
+ * The number of assets is visible by default.
+ */
+@property (nonatomic, assign) BOOL displayAlbumsNumberOfAssets;
+
+
+@property (nonatomic, assign) BOOL allow_video;
+@property (nonatomic, assign) NSInteger maximumImagesCount;
+/**
+ * Grid customizations:
+ *
+ * - colsInPortrait: Number of columns in portrait (3 by default)
+ * - colsInLandscape: Number of columns in landscape (5 by default)
+ * - minimumInteritemSpacing: Horizontal and vertical minimum space between grid cells (2.0 by default)
+ */
+@property (nonatomic) NSInteger colsInPortrait;
+@property (nonatomic) NSInteger colsInLandscape;
+@property (nonatomic) double minimumInteritemSpacing;
+
+
+@property (nonatomic, strong) UINavigationController *navigationController;
+
+/**
+ * Managing Asset Selection
+ */
+- (void)selectAsset:(PHAsset *)asset;
+- (void)deselectAsset:(PHAsset *)asset;
+
+- (void)selectFetchItem:(GMFetchItem *)asset;
+- (void)deselectFetchItem:(GMFetchItem *)asset;
+
+
+/**
+ * User finish Actions
+ */
+- (void)dismiss:(id)sender;
+- (void)finishPickingAssets:(id)sender;
+
+@end
+
+
+
+@protocol GMImagePickerControllerDelegate
+
+/**
+ * @name Closing the Picker
+ */
+
+/**
+ * Tells the delegate that the user finish picking photos or videos.
+ * @param picker The controller object managing the assets picker interface.
+ * @param assets An array containing picked PHAssets objects.
+ */
+
+- (void)assetsPickerController:(GMImagePickerController *)picker didFinishPickingAssets:(NSArray *)assets;
+
+
+@optional
+
+/**
+ * Tells the delegate that the user cancelled the pick operation.
+ * @param picker The controller object managing the assets picker interface.
+ */
+- (void)assetsPickerControllerDidCancel:(GMImagePickerController *)picker;
+
+
+/**
+ * @name Enabling Assets
+ */
+
+/**
+ * Ask the delegate if the specified asset should be shown.
+ *
+ * @param picker The controller object managing the assets picker interface.
+ * @param asset The asset to be shown.
+ *
+ * @return `YES` if the asset should be shown or `NO` if it should not.
+ */
+
+- (BOOL)assetsPickerController:(GMImagePickerController *)picker shouldShowAsset:(PHAsset *)asset;
+
+/**
+ * Ask the delegate if the specified asset should be enabled for selection.
+ *
+ * @param picker The controller object managing the assets picker interface.
+ * @param asset The asset to be enabled.
+ *
+ * @return `YES` if the asset should be enabled or `NO` if it should not.
+ */
+- (BOOL)assetsPickerController:(GMImagePickerController *)picker shouldEnableAsset:(PHAsset *)asset;
+
+
+/**
+ * @name Managing the Selected Assets
+ */
+
+/**
+ * Asks the delegate if the specified asset should be selected.
+ *
+ * @param picker The controller object managing the assets picker interface.
+ * @param asset The asset to be selected.
+ *
+ * @return `YES` if the asset should be selected or `NO` if it should not.
+ *
+ */
+- (BOOL)assetsPickerController:(GMImagePickerController *)picker shouldSelectAsset:(PHAsset *)asset;
+
+/**
+ * Tells the delegate that the asset was selected.
+ *
+ * @param picker The controller object managing the assets picker interface.
+ * @param indexPath The asset that was selected.
+ *
+ */
+- (void)assetsPickerController:(GMImagePickerController *)picker didSelectAsset:(PHAsset *)asset;
+
+/**
+ * Asks the delegate if the specified asset should be deselected.
+ *
+ * @param picker The controller object managing the assets picker interface.
+ * @param asset The asset to be deselected.
+ *
+ * @return `YES` if the asset should be deselected or `NO` if it should not.
+ *
+ */
+- (BOOL)assetsPickerController:(GMImagePickerController *)picker shouldDeselectAsset:(PHAsset *)asset;
+
+/**
+ * Tells the delegate that the item at the specified path was deselected.
+ *
+ * @param picker The controller object managing the assets picker interface.
+ * @param indexPath The asset that was deselected.
+ *
+ */
+- (void)assetsPickerController:(GMImagePickerController *)picker didDeselectAsset:(PHAsset *)asset;
+
+
+
+/**
+ * @name Managing Asset Highlighting
+ */
+
+/**
+ * Asks the delegate if the specified asset should be highlighted.
+ *
+ * @param picker The controller object managing the assets picker interface.
+ * @param asset The asset to be highlighted.
+ *
+ * @return `YES` if the asset should be highlighted or `NO` if it should not.
+ */
+- (BOOL)assetsPickerController:(GMImagePickerController *)picker shouldHighlightAsset:(PHAsset *)asset;
+
+/**
+ * Tells the delegate that asset was highlighted.
+ *
+ * @param picker The controller object managing the assets picker interface.
+ * @param indexPath The asset that was highlighted.
+ *
+ */
+- (void)assetsPickerController:(GMImagePickerController *)picker didHighlightAsset:(PHAsset *)asset;
+
+
+/**
+ * Tells the delegate that the highlight was removed from the asset.
+ *
+ * @param picker The controller object managing the assets picker interface.
+ * @param indexPath The asset that had its highlight removed.
+ *
+ */
+- (void)assetsPickerController:(GMImagePickerController *)picker didUnhighlightAsset:(PHAsset *)asset;
+
+
+
+
+@end
\ No newline at end of file
diff --git a/src/ios/GMImagePicker/GMImagePickerController.m b/src/ios/GMImagePicker/GMImagePickerController.m
new file mode 100755
index 00000000..08b5e486
--- /dev/null
+++ b/src/ios/GMImagePicker/GMImagePickerController.m
@@ -0,0 +1,236 @@
+//
+// GMImagePickerController.m
+// GMPhotoPicker
+//
+// Created by Guillermo Muntaner Perelló on 19/09/14.
+// Copyright (c) 2014 Guillermo Muntaner Perelló. All rights reserved.
+//
+
+#import "GMImagePickerController.h"
+#import "GMAlbumsViewController.h"
+
+
+@interface GMImagePickerController ()
+
+@end
+
+@implementation GMImagePickerController
+
+- (id)init:(bool)allow_v
+{
+ if (self = [super init])
+ {
+ _allow_video = allow_v;
+
+ _selectedAssets = [[NSMutableArray alloc] init];
+ _selectedFetches = [[NSMutableArray alloc] init];
+
+ //Default values:
+ _displaySelectionInfoToolbar = YES;
+ _displayAlbumsNumberOfAssets = YES;
+
+ //Grid configuration:
+ _colsInPortrait = 3;
+ _colsInLandscape = 5;
+ _minimumInteritemSpacing = 2.0;
+
+ //Sample of how to select the collections you want to display:
+ _customSmartCollections = @[@(PHAssetCollectionSubtypeSmartAlbumFavorites),
+ @(PHAssetCollectionSubtypeSmartAlbumRecentlyAdded),
+ @(PHAssetCollectionSubtypeSmartAlbumVideos),
+ @(PHAssetCollectionSubtypeSmartAlbumSlomoVideos),
+ @(PHAssetCollectionSubtypeSmartAlbumTimelapses),
+ @(PHAssetCollectionSubtypeSmartAlbumBursts),
+ @(PHAssetCollectionSubtypeSmartAlbumPanoramas)];
+ //If you don't want to show smart collections, just put _customSmartCollections to nil;
+ //_customSmartCollections=nil;
+
+ self.preferredContentSize = kPopoverContentSize;
+
+ [self setupNavigationController];
+ }
+ return self;
+}
+
+- (void)dealloc
+{
+
+}
+
+
+- (void)viewDidLoad
+{
+ [super viewDidLoad];
+ // Do any additional setup after loading the view.
+}
+
+- (void)didReceiveMemoryWarning
+{
+ [super didReceiveMemoryWarning];
+ // Dispose of any resources that can be recreated.
+}
+
+#pragma mark - Setup Navigation Controller
+
+- (void)setupNavigationController
+{
+ GMAlbumsViewController *albumsViewController = [[GMAlbumsViewController alloc] init:_allow_video];
+ _navigationController = [[UINavigationController alloc] initWithRootViewController:albumsViewController];
+ _navigationController.delegate = self;
+
+ [_navigationController willMoveToParentViewController:self];
+ [_navigationController.view setFrame:self.view.frame];
+ [self.view addSubview:_navigationController.view];
+ [self addChildViewController:_navigationController];
+ [_navigationController didMoveToParentViewController:self];
+}
+
+#pragma mark - Select / Deselect Asset
+
+- (void)selectAsset:(PHAsset *)asset
+{
+ [self.selectedAssets insertObject:asset atIndex:self.selectedAssets.count];
+ [self updateDoneButton];
+
+ if(self.displaySelectionInfoToolbar)
+ [self updateToolbar];
+}
+
+- (void)deselectAsset:(PHAsset *)asset
+{
+ [self.selectedAssets removeObjectAtIndex:[self.selectedAssets indexOfObject:asset]];
+ if(self.selectedAssets.count == 0)
+ [self updateDoneButton];
+
+ if(self.displaySelectionInfoToolbar)
+ [self updateToolbar];
+}
+
+- (void)selectFetchItem:(GMFetchItem *)fetch_item{
+ [self.selectedFetches insertObject:fetch_item atIndex:self.selectedFetches.count];
+}
+
+- (void)deselectFetchItem:(GMFetchItem *)fetch_item{
+ [self.selectedFetches removeObjectAtIndex:[self.selectedFetches indexOfObject:fetch_item]];
+}
+
+- (void)updateDoneButton
+{
+ UINavigationController *nav = (UINavigationController *)self.childViewControllers[0];
+ for (UIViewController *viewController in nav.viewControllers)
+ viewController.navigationItem.rightBarButtonItem.enabled = (self.selectedAssets.count > 0);
+}
+
+- (void)updateToolbar
+{
+ UINavigationController *nav = (UINavigationController *)self.childViewControllers[0];
+ for (UIViewController *viewController in nav.viewControllers)
+ {
+ [[viewController.toolbarItems objectAtIndex:1] setTitle:[self toolbarTitle]];
+ [viewController.navigationController setToolbarHidden:(self.selectedAssets.count == 0) animated:YES];
+ }
+}
+
+#pragma mark - User finish Actions
+
+- (void)dismiss:(id)sender
+{
+ if ([self.delegate respondsToSelector:@selector(assetsPickerControllerDidCancel:)])
+ [self.delegate assetsPickerControllerDidCancel:self];
+
+ [self.presentingViewController dismissViewControllerAnimated:YES completion:nil];
+}
+
+
+- (void)finishPickingAssets:(id)sender
+{
+ if ([self.delegate respondsToSelector:@selector(assetsPickerController:didFinishPickingAssets:)])
+ //[self.delegate assetsPickerController:self didFinishPickingAssets:self.selectedAssets];
+ [self.delegate assetsPickerController:self didFinishPickingAssets:self.selectedFetches];
+
+ //[self.presentingViewController dismissViewControllerAnimated:YES completion:nil];
+}
+
+
+#pragma mark - Toolbar Title
+
+- (NSPredicate *)predicateOfAssetType:(PHAssetMediaType)type
+{
+ return [NSPredicate predicateWithBlock:^BOOL(PHAsset *asset, NSDictionary *bindings) {
+ return (asset.mediaType==type);
+ }];
+}
+
+- (NSString *)toolbarTitle
+{
+ if (self.selectedAssets.count == 0)
+ return nil;
+
+ NSPredicate *photoPredicate = [self predicateOfAssetType:PHAssetMediaTypeImage];
+ NSPredicate *videoPredicate = [self predicateOfAssetType:PHAssetMediaTypeVideo];
+
+ NSInteger nImages = [self.selectedAssets filteredArrayUsingPredicate:photoPredicate].count;
+ NSInteger nVideos = [self.selectedAssets filteredArrayUsingPredicate:videoPredicate].count;
+
+ if (nImages>0 && nVideos>0)
+ {
+ return [NSString stringWithFormat:NSLocalizedStringFromTable(@"picker.selection.multiple-items", @"GMImagePicker", @"%@ Items Selected" ), @(nImages+nVideos)];
+ }
+ else if (nImages>1)
+ {
+ return [NSString stringWithFormat:NSLocalizedStringFromTable(@"picker.selection.multiple-photos", @"GMImagePicker", @"%@ Photos Selected"), @(nImages)];
+ }
+ else if (nImages==1)
+ {
+ return NSLocalizedStringFromTable(@"picker.selection.single-photo", @"GMImagePicker", @"1 Photo Selected" );
+ }
+ else if (nVideos>1)
+ {
+ return [NSString stringWithFormat:NSLocalizedStringFromTable(@"picker.selection.multiple-videos", @"GMImagePicker", @"%@ Videos Selected"), @(nVideos)];
+ }
+ else if (nVideos==1)
+ {
+ return NSLocalizedStringFromTable(@"picker.selection.single-video", @"GMImagePicker", @"1 Video Selected");
+ }
+ else
+ {
+ return nil;
+ }
+}
+
+
+#pragma mark - Toolbar Items
+
+- (UIBarButtonItem *)titleButtonItem
+{
+ UIBarButtonItem *title =
+ [[UIBarButtonItem alloc] initWithTitle:self.toolbarTitle
+ style:UIBarButtonItemStylePlain
+ target:nil
+ action:nil];
+
+ NSDictionary *attributes = @{NSForegroundColorAttributeName : [UIColor blackColor]};
+
+ [title setTitleTextAttributes:attributes forState:UIControlStateNormal];
+ [title setTitleTextAttributes:attributes forState:UIControlStateDisabled];
+ [title setEnabled:NO];
+
+ return title;
+}
+
+- (UIBarButtonItem *)spaceButtonItem
+{
+ return [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemFlexibleSpace target:nil action:nil];
+}
+
+- (NSArray *)toolbarItems
+{
+ UIBarButtonItem *title = [self titleButtonItem];
+ UIBarButtonItem *space = [self spaceButtonItem];
+
+ return @[space, title, space];
+}
+
+
+
+@end
diff --git a/src/ios/GMImagePicker/GMPHAsset.h b/src/ios/GMImagePicker/GMPHAsset.h
new file mode 100644
index 00000000..359b833c
--- /dev/null
+++ b/src/ios/GMImagePicker/GMPHAsset.h
@@ -0,0 +1,36 @@
+//
+// GMPHAsset.h
+// GMPhotoPicker
+//
+// Created by micheladrion on 4/24/15.
+// Copyright (c) 2015 Guillermo Muntaner Perelló. All rights reserved.
+//
+
+#define ADD_DYNAMIC_PROPERTY(PROPERTY_TYPE,PROPERTY_NAME,SETTER_NAME) \
+@dynamic PROPERTY_NAME ; \
+static char kProperty##PROPERTY_NAME; \
+- ( PROPERTY_TYPE ) PROPERTY_NAME \
+{ \
+return ( PROPERTY_TYPE ) objc_getAssociatedObject(self, &(kProperty##PROPERTY_NAME ) ); \
+} \
+\
+- (void) SETTER_NAME :( PROPERTY_TYPE ) PROPERTY_NAME \
+{ \
+objc_setAssociatedObject(self, &kProperty##PROPERTY_NAME , PROPERTY_NAME , OBJC_ASSOCIATION_RETAIN); \
+} \
+
+#import
+
+#import
+
+@interface PHAsset (GMPHAsset)
+
+
+@property (nonatomic, assign) id cell;
+@property (nonatomic, assign) NSNumber *be_progressed;
+@property (nonatomic, assign) NSNumber *be_finished;
+@property (nonatomic, assign) NSNumber *percent;
+@property (nonatomic, strong) UIImage * image_fullsize;
+@property (nonatomic, strong) UIImage * image_thumb;
+
+@end
diff --git a/src/ios/GMImagePicker/GMPHAsset.m b/src/ios/GMImagePicker/GMPHAsset.m
new file mode 100644
index 00000000..23393e50
--- /dev/null
+++ b/src/ios/GMImagePicker/GMPHAsset.m
@@ -0,0 +1,21 @@
+//
+// GMPHAsset.m
+// GMPhotoPicker
+//
+// Created by micheladrion on 4/24/15.
+// Copyright (c) 2015 Guillermo Muntaner Perelló. All rights reserved.
+//
+
+
+#import "GMPHAsset.h"
+
+@implementation PHAsset (GMPHAsset)
+
+ADD_DYNAMIC_PROPERTY(NSNumber *,cell,setCell);
+ADD_DYNAMIC_PROPERTY(NSNumber *,be_progressed,setBe_progressed);
+ADD_DYNAMIC_PROPERTY(NSNumber *,be_finished,setBe_finished);
+ADD_DYNAMIC_PROPERTY(NSNumber *,percent,setPercent);
+ADD_DYNAMIC_PROPERTY(UIImage *,image_fullsize,setImage_fullsize);
+ADD_DYNAMIC_PROPERTY(UIImage *,image_thumb,setImage_thumb);
+
+@end
\ No newline at end of file
diff --git a/src/ios/GMImagePicker/GMSelected.png b/src/ios/GMImagePicker/GMSelected.png
new file mode 100755
index 00000000..b251b6cd
Binary files /dev/null and b/src/ios/GMImagePicker/GMSelected.png differ
diff --git a/src/ios/GMImagePicker/GMSelected@2x.png b/src/ios/GMImagePicker/GMSelected@2x.png
new file mode 100755
index 00000000..5a8f6f8c
Binary files /dev/null and b/src/ios/GMImagePicker/GMSelected@2x.png differ
diff --git a/src/ios/GMImagePicker/GMVideoIcon.png b/src/ios/GMImagePicker/GMVideoIcon.png
new file mode 100755
index 00000000..865e68d2
Binary files /dev/null and b/src/ios/GMImagePicker/GMVideoIcon.png differ
diff --git a/src/ios/GMImagePicker/GMVideoIcon@2x.png b/src/ios/GMImagePicker/GMVideoIcon@2x.png
new file mode 100755
index 00000000..4ed325c5
Binary files /dev/null and b/src/ios/GMImagePicker/GMVideoIcon@2x.png differ
diff --git a/src/ios/GMImagePicker/PSYBlockTimer.h b/src/ios/GMImagePicker/PSYBlockTimer.h
new file mode 100644
index 00000000..81c4f498
--- /dev/null
+++ b/src/ios/GMImagePicker/PSYBlockTimer.h
@@ -0,0 +1,32 @@
+/*
+ Copyright (c) 2009 Remy Demarest
+
+ 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.
+ */
+
+#import
+
+
+@interface NSTimer (PSYBlockTimer)
++ (NSTimer *)scheduledTimerWithTimeInterval:(NSTimeInterval)seconds repeats:(BOOL)repeats usingBlock:(void (^)(NSTimer *timer))fireBlock;
++ (NSTimer *)timerWithTimeInterval:(NSTimeInterval)seconds repeats:(BOOL)repeats usingBlock:(void (^)(NSTimer *timer))fireBlock;
+@end
diff --git a/src/ios/GMImagePicker/PSYBlockTimer.m b/src/ios/GMImagePicker/PSYBlockTimer.m
new file mode 100644
index 00000000..65f178ab
--- /dev/null
+++ b/src/ios/GMImagePicker/PSYBlockTimer.m
@@ -0,0 +1,57 @@
+/*
+ Copyright (c) 2009 Remy Demarest
+
+ 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.
+ */
+
+#import "PSYBlockTimer.h"
+
+typedef void (^PSYTimerBlock)(NSTimer *);
+
+@interface NSTimer (PSYBlockTimer_private)
++ (void)PSYBlockTimer_executeBlockWithTimer:(NSTimer *)timer;
+@end
+
+
+@implementation NSTimer (PSYBlockTimer)
++ (NSTimer *)scheduledTimerWithTimeInterval:(NSTimeInterval)seconds repeats:(BOOL)repeats usingBlock:(void (^)(NSTimer *timer))fireBlock
+{
+ return [self scheduledTimerWithTimeInterval:seconds target:self selector:@selector(PSYBlockTimer_executeBlockWithTimer:) userInfo:[fireBlock copy] repeats:repeats];
+}
+
++ (NSTimer *)timerWithTimeInterval:(NSTimeInterval)seconds repeats:(BOOL)repeats usingBlock:(void (^)(NSTimer *timer))fireBlock
+{
+ return [self timerWithTimeInterval:seconds target:self selector:@selector(PSYBlockTimer_executeBlockWithTimer:) userInfo:[fireBlock copy] repeats:repeats];
+}
+@end
+
+
+@implementation NSTimer (PSYBlockTimer_private)
++ (void)PSYBlockTimer_executeBlockWithTimer:(NSTimer *)timer
+{
+ if([timer isValid])
+ {
+ PSYTimerBlock block = [timer userInfo];
+ block(timer);
+ }
+}
+@end
diff --git a/src/ios/GMImagePicker/UIImage+fixOrientation.h b/src/ios/GMImagePicker/UIImage+fixOrientation.h
new file mode 100644
index 00000000..34d4036d
--- /dev/null
+++ b/src/ios/GMImagePicker/UIImage+fixOrientation.h
@@ -0,0 +1,5 @@
+@interface UIImage(fixOrientation)
+
+- (UIImage *)fixOrientation;
+
+@end
\ No newline at end of file
diff --git a/src/ios/GMImagePicker/UIImage+fixOrientation.m b/src/ios/GMImagePicker/UIImage+fixOrientation.m
new file mode 100644
index 00000000..cb5d66ea
--- /dev/null
+++ b/src/ios/GMImagePicker/UIImage+fixOrientation.m
@@ -0,0 +1,15 @@
+#import "UIImage+fixOrientation.h"
+
+@implementation UIImage (fixOrientation)
+
+- (UIImage *)fixOrientation {
+ if (self.imageOrientation == UIImageOrientationUp) return self;
+
+ UIGraphicsBeginImageContextWithOptions(self.size, NO, self.scale);
+ [self drawInRect:(CGRect){0, 0, self.size}];
+ UIImage *normalizedImage = UIGraphicsGetImageFromCurrentImageContext();
+ UIGraphicsEndImageContext();
+ return normalizedImage;
+}
+
+@end
\ No newline at end of file
diff --git a/src/ios/GMImagePicker/ca.lproj/GMImagePicker.strings b/src/ios/GMImagePicker/ca.lproj/GMImagePicker.strings
new file mode 100755
index 00000000..b33a4deb
Binary files /dev/null and b/src/ios/GMImagePicker/ca.lproj/GMImagePicker.strings differ
diff --git a/src/ios/GMImagePicker/de.lproj/GMImagePicker.strings b/src/ios/GMImagePicker/de.lproj/GMImagePicker.strings
new file mode 100755
index 00000000..0e384dce
Binary files /dev/null and b/src/ios/GMImagePicker/de.lproj/GMImagePicker.strings differ
diff --git a/src/ios/GMImagePicker/en.lproj/GMImagePicker.strings b/src/ios/GMImagePicker/en.lproj/GMImagePicker.strings
new file mode 100755
index 00000000..cd03baa6
Binary files /dev/null and b/src/ios/GMImagePicker/en.lproj/GMImagePicker.strings differ
diff --git a/src/ios/GMImagePicker/es.lproj/GMImagePicker.strings b/src/ios/GMImagePicker/es.lproj/GMImagePicker.strings
new file mode 100755
index 00000000..147c276c
Binary files /dev/null and b/src/ios/GMImagePicker/es.lproj/GMImagePicker.strings differ
diff --git a/src/ios/GMImagePicker/fr.lproj/GMImagePicker.strings b/src/ios/GMImagePicker/fr.lproj/GMImagePicker.strings
new file mode 100755
index 00000000..0be5d582
Binary files /dev/null and b/src/ios/GMImagePicker/fr.lproj/GMImagePicker.strings differ
diff --git a/src/ios/GMImagePicker/it.lproj/GMImagePicker.strings b/src/ios/GMImagePicker/it.lproj/GMImagePicker.strings
new file mode 100755
index 00000000..f6946b32
Binary files /dev/null and b/src/ios/GMImagePicker/it.lproj/GMImagePicker.strings differ
diff --git a/src/ios/GMImagePicker/ja.lproj/GMImagePicker.strings b/src/ios/GMImagePicker/ja.lproj/GMImagePicker.strings
new file mode 100755
index 00000000..db4938c0
Binary files /dev/null and b/src/ios/GMImagePicker/ja.lproj/GMImagePicker.strings differ
diff --git a/src/ios/GMImagePicker/pl.lproj/GMImagePicker.strings b/src/ios/GMImagePicker/pl.lproj/GMImagePicker.strings
new file mode 100644
index 00000000..ef626112
--- /dev/null
+++ b/src/ios/GMImagePicker/pl.lproj/GMImagePicker.strings
@@ -0,0 +1,33 @@
+/* Cancel */
+"picker.navigation.cancel-button" = "Anuluj";
+
+/* Done */
+"picker.navigation.done-button" = "Gotowe";
+
+/* Navigation bar default title */
+"picker.navigation.title" = "GMImagePicker";
+
+/* %@ Items Selected */
+"picker.selection.multiple-items" = "%@ elementów wybranych";
+
+/* %@ Photos Selected */
+"picker.selection.multiple-photos" = "%@ zdjęć wybranych";
+
+/* %@ Videos Selected */
+"picker.selection.multiple-videos" = "%@ filmów wybranych";
+
+/* 1 Photo Selected */
+"picker.selection.single-photo" = "1 zdjecie wybrane";
+
+/* 1 Video Selected */
+"picker.selection.single-video" = "1 film wybrany";
+
+/* All photos */
+"picker.table.all-photos-label" = "Rolka z aparatu";
+
+/* Smart Albums */
+"picker.table.smart-albums-header" = "INTELIGENTNE ALBUMY";
+
+/* Albums */
+"picker.table.user-albums-header" = "ALBUMY";
+
diff --git a/src/ios/GMImagePicker/pt.lproj/GMImagePicker.strings b/src/ios/GMImagePicker/pt.lproj/GMImagePicker.strings
new file mode 100755
index 00000000..2f3caedc
Binary files /dev/null and b/src/ios/GMImagePicker/pt.lproj/GMImagePicker.strings differ
diff --git a/src/ios/GMImagePicker/zh-Hans.lproj/GMImagePicker.strings b/src/ios/GMImagePicker/zh-Hans.lproj/GMImagePicker.strings
new file mode 100755
index 00000000..e4754c09
Binary files /dev/null and b/src/ios/GMImagePicker/zh-Hans.lproj/GMImagePicker.strings differ
diff --git a/src/ios/SOSPicker.h b/src/ios/SOSPicker.h
index 71081eca..6c9894ab 100644
--- a/src/ios/SOSPicker.h
+++ b/src/ios/SOSPicker.h
@@ -7,18 +7,21 @@
//
#import
-#import "ELCAlbumPickerController.h"
-#import "ELCImagePickerController.h"
-@interface SOSPicker : CDVPlugin
+
+@interface SOSPicker : CDVPlugin < UINavigationControllerDelegate, UIScrollViewDelegate>
@property (copy) NSString* callbackId;
- (void) getPictures:(CDVInvokedUrlCommand *)command;
+- (void) hasReadPermission:(CDVInvokedUrlCommand *)command;
+- (void) requestReadPermission:(CDVInvokedUrlCommand *)command;
+
- (UIImage*)imageByScalingNotCroppingForSize:(UIImage*)anImage toSize:(CGSize)frameSize;
@property (nonatomic, assign) NSInteger width;
@property (nonatomic, assign) NSInteger height;
@property (nonatomic, assign) NSInteger quality;
+@property (nonatomic, assign) NSInteger outputType;
@end
diff --git a/src/ios/SOSPicker.m b/src/ios/SOSPicker.m
index da08db24..0e2e00e7 100644
--- a/src/ios/SOSPicker.m
+++ b/src/ios/SOSPicker.m
@@ -7,116 +7,107 @@
//
#import "SOSPicker.h"
-#import "ELCAlbumPickerController.h"
-#import "ELCImagePickerController.h"
-#import "ELCAssetTablePicker.h"
+
+
+#import "GMImagePickerController.h"
+#import "GMFetchItem.h"
#define CDV_PHOTO_PREFIX @"cdv_photo_"
+typedef enum : NSUInteger {
+ FILE_URI = 0,
+ BASE64_STRING = 1
+} SOSPickerOutputType;
+
+@interface SOSPicker ()
+@end
+
@implementation SOSPicker
@synthesize callbackId;
-- (void) getPictures:(CDVInvokedUrlCommand *)command {
- NSDictionary *options = [command.arguments objectAtIndex: 0];
-
- NSInteger maximumImagesCount = [[options objectForKey:@"maximumImagesCount"] integerValue];
- self.width = [[options objectForKey:@"width"] integerValue];
- self.height = [[options objectForKey:@"height"] integerValue];
- self.quality = [[options objectForKey:@"quality"] integerValue];
-
- // Create the an album controller and image picker
- ELCAlbumPickerController *albumController = [[ELCAlbumPickerController alloc] init];
-
- if (maximumImagesCount == 1) {
- albumController.immediateReturn = true;
- albumController.singleSelection = true;
- } else {
- albumController.immediateReturn = false;
- albumController.singleSelection = false;
- }
-
- ELCImagePickerController *imagePicker = [[ELCImagePickerController alloc] initWithRootViewController:albumController];
- imagePicker.maximumImagesCount = maximumImagesCount;
- imagePicker.returnsOriginalImage = 1;
- imagePicker.imagePickerDelegate = self;
-
- albumController.parent = imagePicker;
- self.callbackId = command.callbackId;
- // Present modally
- [self.viewController presentViewController:imagePicker
- animated:YES
- completion:nil];
+- (void) hasReadPermission:(CDVInvokedUrlCommand *)command {
+ CDVPluginResult* pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsBool:[PHPhotoLibrary authorizationStatus] == PHAuthorizationStatusAuthorized];
+ [self.commandDelegate sendPluginResult:pluginResult callbackId:command.callbackId];
}
+- (void) requestReadPermission:(CDVInvokedUrlCommand *)command {
+ // [PHPhotoLibrary requestAuthorization:]
+ // this method works only when it is a first time, see
+ // https://developer.apple.com/library/ios/documentation/Photos/Reference/PHPhotoLibrary_Class/
-- (void)elcImagePickerController:(ELCImagePickerController *)picker didFinishPickingMediaWithInfo:(NSArray *)info {
- CDVPluginResult* result = nil;
- NSMutableArray *resultStrings = [[NSMutableArray alloc] init];
- NSData* data = nil;
- NSString* docsPath = [NSTemporaryDirectory()stringByStandardizingPath];
- NSError* err = nil;
- NSFileManager* fileMgr = [[NSFileManager alloc] init];
- NSString* filePath;
- ALAsset* asset = nil;
- UIImageOrientation orientation = UIImageOrientationUp;;
- CGSize targetSize = CGSizeMake(self.width, self.height);
- for (NSDictionary *dict in info) {
- asset = [dict objectForKey:@"ALAsset"];
- // From ELCImagePickerController.m
-
- int i = 1;
- do {
- filePath = [NSString stringWithFormat:@"%@/%@%03d.%@", docsPath, CDV_PHOTO_PREFIX, i++, @"jpg"];
- } while ([fileMgr fileExistsAtPath:filePath]);
+ PHAuthorizationStatus status = [PHPhotoLibrary authorizationStatus];
+ if (status == PHAuthorizationStatusAuthorized) {
+ NSLog(@"Access has been granted.");
- @autoreleasepool {
- ALAssetRepresentation *assetRep = [asset defaultRepresentation];
- CGImageRef imgRef = NULL;
-
- //defaultRepresentation returns image as it appears in photo picker, rotated and sized,
- //so use UIImageOrientationUp when creating our image below.
- if (picker.returnsOriginalImage) {
- imgRef = [assetRep fullResolutionImage];
- orientation = [assetRep orientation];
- } else {
- imgRef = [assetRep fullScreenImage];
- }
-
- UIImage* image = [UIImage imageWithCGImage:imgRef scale:1.0f orientation:orientation];
- if (self.width == 0 && self.height == 0) {
- data = UIImageJPEGRepresentation(image, self.quality/100.0f);
- } else {
- UIImage* scaledImage = [self imageByScalingNotCroppingForSize:image toSize:targetSize];
- data = UIImageJPEGRepresentation(scaledImage, self.quality/100.0f);
- }
-
- if (![data writeToFile:filePath options:NSAtomicWrite error:&err]) {
- result = [CDVPluginResult resultWithStatus:CDVCommandStatus_IO_EXCEPTION messageAsString:[err localizedDescription]];
- break;
- } else {
- [resultStrings addObject:[[NSURL fileURLWithPath:filePath] absoluteString]];
- }
- }
+ CDVPluginResult* pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK];
+ [self.commandDelegate sendPluginResult:pluginResult callbackId:command.callbackId];
+ } else if (status == PHAuthorizationStatusDenied) {
+ NSString* message = @"Access has been denied. Change your setting > this app > Photo enable";
+ NSLog(@"%@", message);
+
+ CDVPluginResult* pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR messageAsString:message];
+ [self.commandDelegate sendPluginResult:pluginResult callbackId:command.callbackId];
+ } else if (status == PHAuthorizationStatusNotDetermined) {
+ // Access has not been determined. requestAuthorization: is available
+ [PHPhotoLibrary requestAuthorization:^(PHAuthorizationStatus status) {}];
+
+ CDVPluginResult* pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK];
+ [self.commandDelegate sendPluginResult:pluginResult callbackId:command.callbackId];
+ } else if (status == PHAuthorizationStatusRestricted) {
+ NSString* message = @"Access has been restricted. Change your setting > Privacy > Photo enable";
+ NSLog(@"%@", message);
+
+ CDVPluginResult* pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR messageAsString:message];
+ [self.commandDelegate sendPluginResult:pluginResult callbackId:command.callbackId];
+ }
+}
+
+- (void) getPictures:(CDVInvokedUrlCommand *)command {
- }
-
- if (nil == result) {
- result = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsArray:resultStrings];
- }
+ NSDictionary *options = [command.arguments objectAtIndex: 0];
+
+ self.outputType = [[options objectForKey:@"outputType"] integerValue];
+ BOOL allow_video = [[options objectForKey:@"allow_video" ] boolValue ];
+ NSInteger maximumImagesCount = [[options objectForKey:@"maximumImagesCount"] integerValue];
+ NSString * title = [options objectForKey:@"title"];
+ NSString * message = [options objectForKey:@"message"];
+ BOOL disable_popover = [[options objectForKey:@"disable_popover" ] boolValue];
+ if (message == (id)[NSNull null]) {
+ message = nil;
+ }
+ self.width = [[options objectForKey:@"width"] integerValue];
+ self.height = [[options objectForKey:@"height"] integerValue];
+ self.quality = [[options objectForKey:@"quality"] integerValue];
- [self.viewController dismissViewControllerAnimated:YES completion:nil];
- [self.commandDelegate sendPluginResult:result callbackId:self.callbackId];
+ self.callbackId = command.callbackId;
+ [self launchGMImagePicker:allow_video title:title message:message disable_popover:disable_popover maximumImagesCount:maximumImagesCount];
}
-- (void)elcImagePickerControllerDidCancel:(ELCImagePickerController *)picker {
- [self.viewController dismissViewControllerAnimated:YES completion:nil];
- CDVPluginResult* pluginResult = nil;
- NSArray* emptyArray = [NSArray array];
- pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsArray:emptyArray];
- [self.commandDelegate sendPluginResult:pluginResult callbackId:self.callbackId];
+- (void)launchGMImagePicker:(bool)allow_video title:(NSString *)title message:(NSString *)message disable_popover:(BOOL)disable_popover maximumImagesCount:(NSInteger)maximumImagesCount
+{
+ GMImagePickerController *picker = [[GMImagePickerController alloc] init:allow_video];
+ picker.delegate = self;
+ picker.maximumImagesCount = maximumImagesCount;
+ picker.title = title;
+ picker.customNavigationBarPrompt = message;
+ picker.colsInPortrait = 4;
+ picker.colsInLandscape = 6;
+ picker.minimumInteritemSpacing = 2.0;
+
+ if(!disable_popover) {
+ picker.modalPresentationStyle = UIModalPresentationPopover;
+
+ UIPopoverPresentationController *popPC = picker.popoverPresentationController;
+ popPC.permittedArrowDirections = UIPopoverArrowDirectionAny;
+ popPC.sourceView = picker.view;
+ //popPC.sourceRect = nil;
+ }
+
+ [self.viewController showViewController:picker sender:nil];
}
+
- (UIImage*)imageByScalingNotCroppingForSize:(UIImage*)anImage toSize:(CGSize)frameSize
{
UIImage* sourceImage = anImage;
@@ -143,7 +134,7 @@ - (UIImage*)imageByScalingNotCroppingForSize:(UIImage*)anImage toSize:(CGSize)fr
} else {
scaleFactor = widthFactor; // scale to fit width
}
- scaledSize = CGSizeMake(width * scaleFactor, height * scaleFactor);
+ scaledSize = CGSizeMake(floor(width * scaleFactor), floor(height * scaleFactor));
}
UIGraphicsBeginImageContext(scaledSize); // this will resize
@@ -160,4 +151,114 @@ - (UIImage*)imageByScalingNotCroppingForSize:(UIImage*)anImage toSize:(CGSize)fr
return newImage;
}
+
+#pragma mark - UIImagePickerControllerDelegate
+
+
+- (void)imagePickerController:(UIImagePickerController *)picker didFinishPickingMediaWithInfo:(NSDictionary *)info
+{
+ [picker.presentingViewController dismissViewControllerAnimated:YES completion:nil];
+ NSLog(@"UIImagePickerController: User finished picking assets");
+}
+
+- (void)imagePickerControllerDidCancel:(UIImagePickerController *)picker
+{
+ CDVPluginResult* pluginResult = nil;
+ NSArray* emptyArray = [NSArray array];
+ pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsArray:emptyArray];
+ [self.commandDelegate sendPluginResult:pluginResult callbackId:self.callbackId];
+ [self.viewController dismissViewControllerAnimated:YES completion:nil];
+ NSLog(@"UIImagePickerController: User pressed cancel button");
+}
+
+#pragma mark - GMImagePickerControllerDelegate
+
+- (void)assetsPickerController:(GMImagePickerController *)picker didFinishPickingAssets:(NSArray *)fetchArray
+{
+ [picker.presentingViewController dismissViewControllerAnimated:YES completion:nil];
+
+ NSLog(@"GMImagePicker: User finished picking assets. Number of selected items is: %lu", (unsigned long)fetchArray.count);
+
+ NSMutableArray * result_all = [[NSMutableArray alloc] init];
+ CGSize targetSize = CGSizeMake(self.width, self.height);
+ NSFileManager* fileMgr = [[NSFileManager alloc] init];
+ NSString* docsPath = [NSTemporaryDirectory()stringByStandardizingPath];
+
+ NSError* err = nil;
+ int i = 1;
+ NSString* filePath;
+ CDVPluginResult* result = nil;
+
+ for (GMFetchItem *item in fetchArray) {
+
+ if ( !item.image_fullsize ) {
+ continue;
+ }
+
+ do {
+ filePath = [NSString stringWithFormat:@"%@/%@%03d.%@", docsPath, CDV_PHOTO_PREFIX, i++, @"jpg"];
+ } while ([fileMgr fileExistsAtPath:filePath]);
+
+ NSData* data = nil;
+ if (self.width == 0 && self.height == 0) {
+ // no scaling required
+ if (self.outputType == BASE64_STRING){
+ UIImage* image = [UIImage imageNamed:item.image_fullsize];
+ [result_all addObject:[UIImageJPEGRepresentation(image, self.quality/100.0f) base64EncodedStringWithOptions:0]];
+ } else {
+ if (self.quality == 100) {
+ // no scaling, no downsampling, this is the fastest option
+ [result_all addObject:item.image_fullsize];
+ } else {
+ // resample first
+ UIImage* image = [UIImage imageNamed:item.image_fullsize];
+ data = UIImageJPEGRepresentation(image, self.quality/100.0f);
+ if (![data writeToFile:filePath options:NSAtomicWrite error:&err]) {
+ result = [CDVPluginResult resultWithStatus:CDVCommandStatus_IO_EXCEPTION messageAsString:[err localizedDescription]];
+ break;
+ } else {
+ [result_all addObject:[[NSURL fileURLWithPath:filePath] absoluteString]];
+ }
+ }
+ }
+ } else {
+ // scale
+ UIImage* image = [UIImage imageNamed:item.image_fullsize];
+ UIImage* scaledImage = [self imageByScalingNotCroppingForSize:image toSize:targetSize];
+ data = UIImageJPEGRepresentation(scaledImage, self.quality/100.0f);
+
+ if (![data writeToFile:filePath options:NSAtomicWrite error:&err]) {
+ result = [CDVPluginResult resultWithStatus:CDVCommandStatus_IO_EXCEPTION messageAsString:[err localizedDescription]];
+ break;
+ } else {
+ if(self.outputType == BASE64_STRING){
+ [result_all addObject:[data base64EncodedStringWithOptions:0]];
+ } else {
+ [result_all addObject:[[NSURL fileURLWithPath:filePath] absoluteString]];
+ }
+ }
+ }
+ }
+
+ if (result == nil) {
+ result = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsArray:result_all];
+ }
+
+ [self.viewController dismissViewControllerAnimated:YES completion:nil];
+ [self.commandDelegate sendPluginResult:result callbackId:self.callbackId];
+
+}
+
+//Optional implementation:
+-(void)assetsPickerControllerDidCancel:(GMImagePickerController *)picker
+{
+ CDVPluginResult* pluginResult = nil;
+ NSArray* emptyArray = [NSArray array];
+ pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsArray:emptyArray];
+ [self.commandDelegate sendPluginResult:pluginResult callbackId:self.callbackId];
+ [picker.presentingViewController dismissViewControllerAnimated:YES completion:nil];
+ NSLog(@"GMImagePicker: User pressed cancel button");
+}
+
+
@end
diff --git a/www/browser/isChrome.js b/www/browser/isChrome.js
new file mode 100644
index 00000000..853277d7
--- /dev/null
+++ b/www/browser/isChrome.js
@@ -0,0 +1,26 @@
+/*
+ *
+ * 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.
+ *
+ */
+
+module.exports = function () {
+ // window.webkitRequestFileSystem and window.webkitResolveLocalFileSystemURL are available only in Chrome and
+ // possibly a good flag to indicate that we're running in Chrome
+ return window.webkitRequestFileSystem && window.webkitResolveLocalFileSystemURL;
+};
diff --git a/www/imagepicker.js b/www/imagepicker.js
index 843dbda6..4dd3650c 100644
--- a/www/imagepicker.js
+++ b/www/imagepicker.js
@@ -1,7 +1,7 @@
/*global cordova,window,console*/
/**
* An Image Picker plugin for Cordova
- *
+ *
* Developed by Wymsee for Sync OnSet
*/
@@ -9,28 +9,60 @@ var ImagePicker = function() {
};
+ImagePicker.prototype.OutputType = {
+ FILE_URI: 0,
+ BASE64_STRING: 1
+};
+
+ImagePicker.prototype.validateOutputType = function(options){
+ var outputType = options.outputType;
+ if(outputType){
+ if(outputType !== this.OutputType.FILE_URI && outputType !== this.OutputType.BASE64_STRING){
+ console.log('Invalid output type option entered. Defaulting to FILE_URI. Please use window.imagePicker.OutputType.FILE_URI or window.imagePicker.OutputType.BASE64_STRING');
+ options.outputType = this.OutputType.FILE_URI;
+ }
+ }
+};
+
+ImagePicker.prototype.hasReadPermission = function(callback) {
+ return cordova.exec(callback, null, "ImagePicker", "hasReadPermission", []);
+};
+
+ImagePicker.prototype.requestReadPermission = function(callback, failureCallback) {
+ return cordova.exec(callback, failureCallback, "ImagePicker", "requestReadPermission", []);
+};
+
/*
* success - success callback
* fail - error callback
* options
-* .maximumImagesCount - max images to be selected, defaults to 15. If this is set to 1,
+* .maximumImagesCount - max images to be selected, defaults to 15. If this is set to 1,
* upon selection of a single image, the plugin will return it.
* .width - width to resize image to (if one of height/width is 0, will resize to fit the
* other while keeping aspect ratio, if both height and width are 0, the full size
* image will be returned)
* .height - height to resize image to
* .quality - quality of resized image, defaults to 100
+* .outputType - type of output returned. defaults to file URIs.
+* Please see ImagePicker.OutputType for available values.
*/
ImagePicker.prototype.getPictures = function(success, fail, options) {
if (!options) {
options = {};
}
-
+
+ this.validateOutputType(options);
+
var params = {
maximumImagesCount: options.maximumImagesCount ? options.maximumImagesCount : 15,
width: options.width ? options.width : 0,
height: options.height ? options.height : 0,
- quality: options.quality ? options.quality : 100
+ quality: options.quality ? options.quality : 100,
+ allow_video: options.allow_video ? options.allow_video : false,
+ title: options.title ? options.title : 'Select an Album', // the default is the message of the old plugin impl
+ message: options.message ? options.message : null, // the old plugin impl didn't have it, so passing null by default
+ outputType: options.outputType ? options.outputType : this.OutputType.FILE_URI,
+ disable_popover: options.disable_popover ? options.disable_popover : false // Disable the iOS popover as seen on iPad
};
return cordova.exec(success, fail, "ImagePicker", "getPictures", [params]);