diff --git a/.gitignore b/.gitignore
index 80e7971..2e9df62 100644
--- a/.gitignore
+++ b/.gitignore
@@ -11,4 +11,4 @@
# Xcode 4
xcuserdata/
project.xcworkspace/
-
+tags
diff --git a/plugin.xml b/plugin.xml
index 475b5d2..e19ae37 100644
--- a/plugin.xml
+++ b/plugin.xml
@@ -56,5 +56,52 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/Android/Library/res/anim/image_pop_in.xml b/src/Android/Library/res/anim/image_pop_in.xml
new file mode 100644
index 0000000..3b1f7e9
--- /dev/null
+++ b/src/Android/Library/res/anim/image_pop_in.xml
@@ -0,0 +1,11 @@
+
+
diff --git a/src/Android/Library/res/drawable-hdpi/icon.png b/src/Android/Library/res/drawable-hdpi/icon.png
new file mode 100644
index 0000000..8074c4c
Binary files /dev/null and b/src/Android/Library/res/drawable-hdpi/icon.png differ
diff --git a/src/Android/Library/res/drawable-hdpi/image_bg.9.png b/src/Android/Library/res/drawable-hdpi/image_bg.9.png
new file mode 100644
index 0000000..9835be6
Binary files /dev/null and b/src/Android/Library/res/drawable-hdpi/image_bg.9.png differ
diff --git a/src/Android/Library/res/drawable-hdpi/loading_icon.png b/src/Android/Library/res/drawable-hdpi/loading_icon.png
new file mode 100644
index 0000000..3490f83
Binary files /dev/null and b/src/Android/Library/res/drawable-hdpi/loading_icon.png differ
diff --git a/src/Android/Library/res/drawable-ldpi/icon.png b/src/Android/Library/res/drawable-ldpi/icon.png
new file mode 100644
index 0000000..1095584
Binary files /dev/null and b/src/Android/Library/res/drawable-ldpi/icon.png differ
diff --git a/src/Android/Library/res/drawable-mdpi/ic_action_discard_dark.png b/src/Android/Library/res/drawable-mdpi/ic_action_discard_dark.png
new file mode 100644
index 0000000..b65f4c6
Binary files /dev/null and b/src/Android/Library/res/drawable-mdpi/ic_action_discard_dark.png differ
diff --git a/src/Android/Library/res/drawable-mdpi/ic_action_discard_light.png b/src/Android/Library/res/drawable-mdpi/ic_action_discard_light.png
new file mode 100644
index 0000000..d74c9d5
Binary files /dev/null and b/src/Android/Library/res/drawable-mdpi/ic_action_discard_light.png differ
diff --git a/src/Android/Library/res/drawable-mdpi/ic_action_done_dark.png b/src/Android/Library/res/drawable-mdpi/ic_action_done_dark.png
new file mode 100644
index 0000000..b8fb4f8
Binary files /dev/null and b/src/Android/Library/res/drawable-mdpi/ic_action_done_dark.png differ
diff --git a/src/Android/Library/res/drawable-mdpi/ic_action_done_light.png b/src/Android/Library/res/drawable-mdpi/ic_action_done_light.png
new file mode 100644
index 0000000..e5d82fd
Binary files /dev/null and b/src/Android/Library/res/drawable-mdpi/ic_action_done_light.png differ
diff --git a/src/Android/Library/res/drawable-mdpi/ic_launcher.png b/src/Android/Library/res/drawable-mdpi/ic_launcher.png
new file mode 100644
index 0000000..9dc7f21
Binary files /dev/null and b/src/Android/Library/res/drawable-mdpi/ic_launcher.png differ
diff --git a/src/Android/Library/res/drawable-mdpi/icon.png b/src/Android/Library/res/drawable-mdpi/icon.png
new file mode 100644
index 0000000..a07c69f
Binary files /dev/null and b/src/Android/Library/res/drawable-mdpi/icon.png differ
diff --git a/src/Android/Library/res/drawable-xhdpi/ic_action_discard_dark.png b/src/Android/Library/res/drawable-xhdpi/ic_action_discard_dark.png
new file mode 100644
index 0000000..4bfac66
Binary files /dev/null and b/src/Android/Library/res/drawable-xhdpi/ic_action_discard_dark.png differ
diff --git a/src/Android/Library/res/drawable-xhdpi/ic_action_discard_light.png b/src/Android/Library/res/drawable-xhdpi/ic_action_discard_light.png
new file mode 100644
index 0000000..24256f1
Binary files /dev/null and b/src/Android/Library/res/drawable-xhdpi/ic_action_discard_light.png differ
diff --git a/src/Android/Library/res/drawable-xhdpi/ic_action_done_dark.png b/src/Android/Library/res/drawable-xhdpi/ic_action_done_dark.png
new file mode 100644
index 0000000..f70a494
Binary files /dev/null and b/src/Android/Library/res/drawable-xhdpi/ic_action_done_dark.png differ
diff --git a/src/Android/Library/res/drawable-xhdpi/ic_action_done_light.png b/src/Android/Library/res/drawable-xhdpi/ic_action_done_light.png
new file mode 100644
index 0000000..71094a0
Binary files /dev/null and b/src/Android/Library/res/drawable-xhdpi/ic_action_done_light.png differ
diff --git a/src/Android/Library/res/drawable-xhdpi/ic_launcher.png b/src/Android/Library/res/drawable-xhdpi/ic_launcher.png
new file mode 100644
index 0000000..75578f4
Binary files /dev/null and b/src/Android/Library/res/drawable-xhdpi/ic_launcher.png differ
diff --git a/src/Android/Library/res/drawable/grid_background.xml b/src/Android/Library/res/drawable/grid_background.xml
new file mode 100644
index 0000000..d57bb38
--- /dev/null
+++ b/src/Android/Library/res/drawable/grid_background.xml
@@ -0,0 +1,12 @@
+
+
+
+
\ No newline at end of file
diff --git a/src/Android/Library/res/layout/actionbar_custom_view_done_discard.xml b/src/Android/Library/res/layout/actionbar_custom_view_done_discard.xml
new file mode 100644
index 0000000..0b61fb9
--- /dev/null
+++ b/src/Android/Library/res/layout/actionbar_custom_view_done_discard.xml
@@ -0,0 +1,27 @@
+
+
+
+
+
+
+
diff --git a/src/Android/Library/res/layout/actionbar_discard_button.xml b/src/Android/Library/res/layout/actionbar_discard_button.xml
new file mode 100644
index 0000000..3e72461
--- /dev/null
+++ b/src/Android/Library/res/layout/actionbar_discard_button.xml
@@ -0,0 +1,33 @@
+
+
+
+
diff --git a/src/Android/Library/res/layout/actionbar_done_button.xml b/src/Android/Library/res/layout/actionbar_done_button.xml
new file mode 100644
index 0000000..a6071c6
--- /dev/null
+++ b/src/Android/Library/res/layout/actionbar_done_button.xml
@@ -0,0 +1,34 @@
+
+
+
+
+
+
diff --git a/src/Android/Library/res/layout/multiselectorgrid.xml b/src/Android/Library/res/layout/multiselectorgrid.xml
new file mode 100644
index 0000000..6e22b5f
--- /dev/null
+++ b/src/Android/Library/res/layout/multiselectorgrid.xml
@@ -0,0 +1,33 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/src/Android/Library/res/values-de/multiimagechooser_strings_de.xml b/src/Android/Library/res/values-de/multiimagechooser_strings_de.xml
new file mode 100644
index 0000000..32123f1
--- /dev/null
+++ b/src/Android/Library/res/values-de/multiimagechooser_strings_de.xml
@@ -0,0 +1,7 @@
+
+
+ MultiImageChooser
+ Free version - Images left: %d
+ There was an error opening the images database. Please report the problem.
+ Requesting thumbnails, please be patient
+
diff --git a/src/Android/Library/res/values-es/multiimagechooser_strings_es.xml b/src/Android/Library/res/values-es/multiimagechooser_strings_es.xml
new file mode 100644
index 0000000..fe05862
--- /dev/null
+++ b/src/Android/Library/res/values-es/multiimagechooser_strings_es.xml
@@ -0,0 +1,7 @@
+
+
+ MultiImageChooser
+ Solicitando miniaturas. Por favor, espere…
+ Versión gratuita - Imágenes restantes: %d
+ Error al abrir la base de datos de imágenes.
+
\ No newline at end of file
diff --git a/src/Android/Library/res/values-fr/multiimagechooser_strings_fr.xml b/src/Android/Library/res/values-fr/multiimagechooser_strings_fr.xml
new file mode 100644
index 0000000..964f190
--- /dev/null
+++ b/src/Android/Library/res/values-fr/multiimagechooser_strings_fr.xml
@@ -0,0 +1,6 @@
+
+ "MultiImageChooser"
+ "La version gratuite - gauche Images:%d"
+ "Il y avait une erreur d'ouvrir la base de données images. S'il vous plaît signaler le problème."
+ "Demande de vignettes, s'il vous plaît soyez patient"
+
\ 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
new file mode 100644
index 0000000..5bb897c
--- /dev/null
+++ b/src/Android/Library/res/values-hu/multiimagechooser_strings_hu.xml
@@ -0,0 +1,8 @@
+
+
+ MultiImageChooser
+
+ Ingyenes verzió - hátralévő képek: %d
+ Ké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
+
\ No newline at end of file
diff --git a/src/Android/Library/res/values-ja/multiimagechooser_strings_ja.xml b/src/Android/Library/res/values-ja/multiimagechooser_strings_ja.xml
new file mode 100644
index 0000000..92f084a
--- /dev/null
+++ b/src/Android/Library/res/values-ja/multiimagechooser_strings_ja.xml
@@ -0,0 +1,7 @@
+
+
+ MultiImageChooser
+ 無料版 - 残りの画像: %d
+ 画像データベースを開く際にエラーがありました。問題を報告してください。
+ サムネイルをリクエスト中です。お待ちください。
+
diff --git a/src/Android/Library/res/values-ko/multiimagechooser_strings_ko.xml b/src/Android/Library/res/values-ko/multiimagechooser_strings_ko.xml
new file mode 100644
index 0000000..4454379
--- /dev/null
+++ b/src/Android/Library/res/values-ko/multiimagechooser_strings_ko.xml
@@ -0,0 +1,7 @@
+
+
+ MultiImageChooser
+ 무료 버전 - 남은 이미지: %d
+ 이미지 데이터베이스를 여는 데 오류가 발생했습니다. 문제를 보고하세요.
+ 썸네일 요청 중. 기다려주세요
+
diff --git a/src/Android/Library/res/values/multiimagechooser_strings_en.xml b/src/Android/Library/res/values/multiimagechooser_strings_en.xml
new file mode 100644
index 0000000..2566b2f
--- /dev/null
+++ b/src/Android/Library/res/values/multiimagechooser_strings_en.xml
@@ -0,0 +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
+ Cancel
+ OK
+
diff --git a/src/Android/Library/res/values/themes.xml b/src/Android/Library/res/values/themes.xml
new file mode 100644
index 0000000..2ec73cf
--- /dev/null
+++ b/src/Android/Library/res/values/themes.xml
@@ -0,0 +1,5 @@
+
+
+
+
\ No newline at end of file
diff --git a/src/Android/Library/src/ImageFetcher.java b/src/Android/Library/src/ImageFetcher.java
new file mode 100644
index 0000000..f384862
--- /dev/null
+++ b/src/Android/Library/src/ImageFetcher.java
@@ -0,0 +1,368 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * 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.
+ */
+
+package com.synconset;
+
+import java.lang.ref.SoftReference;
+import java.lang.ref.WeakReference;
+import java.util.HashMap;
+import java.util.LinkedHashMap;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.RejectedExecutionException;
+
+import android.content.Context;
+import android.graphics.Bitmap;
+import android.graphics.Color;
+import android.graphics.drawable.ColorDrawable;
+import android.graphics.drawable.Drawable;
+import android.os.AsyncTask;
+import android.os.Build;
+import android.os.Handler;
+import android.provider.MediaStore;
+import android.util.Log;
+import android.view.View;
+import android.view.animation.Animation;
+import android.view.animation.AnimationUtils;
+import android.widget.ImageView;
+
+/**
+ * This helper class download images from the Internet and binds those with the
+ * provided ImageView.
+ *
+ *
+ * It requires the INTERNET permission, which should be added to your
+ * application's manifest file.
+ *
+ *
+ * A local cache of downloaded images is maintained internally to improve
+ * performance.
+ */
+public class ImageFetcher {
+
+ private int colWidth;
+ private long origId;
+ private ExecutorService executor;
+
+ public ImageFetcher() {
+ executor = Executors.newCachedThreadPool();
+ }
+
+ public void fetch(Integer id, ImageView imageView, int colWidth) {
+ resetPurgeTimer();
+ this.colWidth = colWidth;
+ this.origId = id;
+ Bitmap bitmap = getBitmapFromCache(id);
+
+ if (bitmap == null) {
+ forceDownload(id, imageView);
+ } else {
+ cancelPotentialDownload(id, imageView);
+ imageView.setImageBitmap(bitmap);
+ }
+ }
+
+ /**
+ * Same as download but the image is always downloaded and the cache is not
+ * used. Kept private at the moment as its interest is not clear.
+ */
+ private void forceDownload(Integer position, ImageView imageView) {
+ if (position == null) {
+ imageView.setImageDrawable(null);
+ return;
+ }
+
+ if (cancelPotentialDownload(position, imageView)) {
+ BitmapFetcherTask task = new BitmapFetcherTask(imageView.getContext(), imageView);
+ DownloadedDrawable downloadedDrawable = new DownloadedDrawable(imageView.getContext(), task, origId);
+ imageView.setImageDrawable(downloadedDrawable);
+ imageView.setMinimumHeight(colWidth);
+
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {
+ task.executeOnExecutor(executor, position);
+ } else {
+ try {
+ task.execute(position);
+ } catch (RejectedExecutionException e) {
+ // Oh :(
+ }
+ }
+
+ }
+ }
+
+ /**
+ * Returns true if the current download has been canceled or if there was no
+ * download in progress on this image view. Returns false if the download in
+ * progress deals with the same url. The download is not stopped in that
+ * case.
+ */
+ private static boolean cancelPotentialDownload(Integer position, ImageView imageView) {
+ BitmapFetcherTask bitmapDownloaderTask = getBitmapDownloaderTask(imageView);
+ long origId = getOrigId(imageView);
+
+ if (bitmapDownloaderTask != null) {
+ Integer bitmapPosition = bitmapDownloaderTask.position;
+ if ((bitmapPosition == null) || (!bitmapPosition.equals(position))) {
+ Log.d("DAVID", "Canceling...");
+ MediaStore.Images.Thumbnails.cancelThumbnailRequest(imageView.getContext().getContentResolver(),
+ origId, 12345);
+ bitmapDownloaderTask.cancel(true);
+ } else {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ /**
+ * @param imageView
+ * Any imageView
+ * @return Retrieve the currently active download task (if any) associated
+ * with this imageView. null if there is no such task.
+ */
+ private static BitmapFetcherTask getBitmapDownloaderTask(ImageView imageView) {
+ if (imageView != null) {
+ Drawable drawable = imageView.getDrawable();
+ if (drawable instanceof DownloadedDrawable) {
+ DownloadedDrawable downloadedDrawable = (DownloadedDrawable) drawable;
+ return downloadedDrawable.getBitmapDownloaderTask();
+ }
+ }
+ return null;
+ }
+
+ private static long getOrigId(ImageView imageView) {
+ if (imageView != null) {
+ Drawable drawable = imageView.getDrawable();
+ if (drawable instanceof DownloadedDrawable) {
+ DownloadedDrawable downloadedDrawable = (DownloadedDrawable) drawable;
+ return downloadedDrawable.getOrigId();
+ }
+ }
+ return -1;
+ }
+
+ /**
+ * The actual AsyncTask that will asynchronously download the image.
+ */
+ class BitmapFetcherTask extends AsyncTask {
+ private Integer position;
+ private final WeakReference imageViewReference;
+ private final Context mContext;
+
+ public BitmapFetcherTask(Context context, ImageView imageView) {
+ imageViewReference = new WeakReference(imageView);
+ mContext = context;
+ }
+
+ /**
+ * Actual download method.
+ */
+ @Override
+ protected Bitmap doInBackground(Integer... params) {
+ position = params[0];
+ if (isCancelled()) {
+ return null;
+ }
+ Bitmap thumb = MediaStore.Images.Thumbnails.getThumbnail(mContext.getContentResolver(), position, 12345,
+ MediaStore.Images.Thumbnails.MICRO_KIND, null);
+ if (isCancelled()) {
+ return null;
+ }
+ if (thumb == null) {
+ return null;
+ } else {
+ if (isCancelled()) {
+ return null;
+ } else {
+ return thumb;
+ }
+ }
+
+ }
+
+ private void setInvisible() {
+ Log.d("COLLAGE", "Setting something invisible...");
+ if (imageViewReference != null) {
+ final ImageView imageView = imageViewReference.get();
+ BitmapFetcherTask bitmapDownloaderTask = getBitmapDownloaderTask(imageView);
+ if (this == bitmapDownloaderTask) {
+ imageView.setVisibility(View.GONE);
+ imageView.setClickable(false);
+ imageView.setEnabled(false);
+ }
+ }
+ }
+
+ /**
+ * Once the image is downloaded, associates it to the imageView
+ */
+ @Override
+ protected void onPostExecute(Bitmap bitmap) {
+ if (isCancelled()) {
+ bitmap = null;
+ }
+ addBitmapToCache(position, bitmap);
+ if (imageViewReference != null) {
+ ImageView imageView = imageViewReference.get();
+ BitmapFetcherTask bitmapDownloaderTask = getBitmapDownloaderTask(imageView);
+ if (this == bitmapDownloaderTask) {
+ imageView.setImageBitmap(bitmap);
+ Animation anim = AnimationUtils.loadAnimation(imageView.getContext(), android.R.anim.fade_in);
+ imageView.setAnimation(anim);
+ anim.start();
+ }
+ } else {
+ setInvisible();
+ }
+ }
+ }
+
+ /**
+ * A fake Drawable that will be attached to the imageView while the download
+ * is in progress.
+ *
+ *
+ * Contains a reference to the actual download task, so that a download task
+ * can be stopped if a new binding is required, and makes sure that only the
+ * last started download process can bind its result, independently of the
+ * download finish order.
+ *
+ */
+ static class DownloadedDrawable extends ColorDrawable {
+ private final WeakReference bitmapDownloaderTaskReference;
+ private long origId;
+
+ public DownloadedDrawable(Context mContext, BitmapFetcherTask bitmapDownloaderTask, long origId) {
+ super(Color.TRANSPARENT);
+ bitmapDownloaderTaskReference = new WeakReference(bitmapDownloaderTask);
+ this.origId = origId;
+ }
+
+ public long getOrigId() {
+ return origId;
+ }
+
+ public BitmapFetcherTask getBitmapDownloaderTask() {
+ return bitmapDownloaderTaskReference.get();
+ }
+ }
+
+ /*
+ * Cache-related fields and methods.
+ *
+ * We use a hard and a soft cache. A soft reference cache is too aggressively cleared by the
+ * Garbage Collector.
+ */
+
+ private static final int HARD_CACHE_CAPACITY = 100;
+ private static final int DELAY_BEFORE_PURGE = 10 * 1000; // in milliseconds
+
+ // Hard cache, with a fixed maximum capacity and a life duration
+ private final HashMap sHardBitmapCache = new LinkedHashMap(
+ HARD_CACHE_CAPACITY / 2, 0.75f, true) {
+ @Override
+ protected boolean removeEldestEntry(LinkedHashMap.Entry eldest) {
+ if (size() > HARD_CACHE_CAPACITY) {
+ // Entries push-out of hard reference cache are transferred to
+ // soft reference cache
+ sSoftBitmapCache.put(eldest.getKey(), new SoftReference(eldest.getValue()));
+ return true;
+ } else
+ return false;
+ }
+ };
+
+ // Soft cache for bitmaps kicked out of hard cache
+ private final static ConcurrentHashMap> sSoftBitmapCache = new ConcurrentHashMap>(
+ HARD_CACHE_CAPACITY / 2);
+
+ private final Handler purgeHandler = new Handler();
+
+ private final Runnable purger = new Runnable() {
+ public void run() {
+ clearCache();
+ }
+ };
+
+ /**
+ * Adds this bitmap to the cache.
+ *
+ * @param bitmap
+ * The newly downloaded bitmap.
+ */
+ private void addBitmapToCache(Integer position, Bitmap bitmap) {
+ if (bitmap != null) {
+ synchronized (sHardBitmapCache) {
+ sHardBitmapCache.put(position, bitmap);
+ }
+ }
+ }
+
+ /**
+ * @param position
+ * The URL of the image that will be retrieved from the cache.
+ * @return The cached bitmap or null if it was not found.
+ */
+ private Bitmap getBitmapFromCache(Integer position) {
+ // First try the hard reference cache
+ synchronized (sHardBitmapCache) {
+ final Bitmap bitmap = sHardBitmapCache.get(position);
+ if (bitmap != null) {
+ Log.d("CACHE ****** ", "Hard hit!");
+ // Bitmap found in hard cache
+ // Move element to first position, so that it is removed last
+ return bitmap;
+ }
+ }
+
+ // Then try the soft reference cache
+ SoftReference bitmapReference = sSoftBitmapCache.get(position);
+ if (bitmapReference != null) {
+ final Bitmap bitmap = bitmapReference.get();
+ if (bitmap != null) {
+ // Bitmap found in soft cache
+ Log.d("CACHE ****** ", "Soft hit!");
+ return bitmap;
+ } else {
+ // Soft reference has been Garbage Collected
+ sSoftBitmapCache.remove(position);
+ }
+ }
+
+ return null;
+ }
+
+ /**
+ * Clears the image cache used internally to improve performance. Note that
+ * for memory efficiency reasons, the cache will automatically be cleared
+ * after a certain inactivity delay.
+ */
+ public void clearCache() {
+ // sHardBitmapCache.clear();
+ // sSoftBitmapCache.clear();
+ }
+
+ /**
+ * Allow a new delay before the automatic cache clear is done.
+ */
+ private void resetPurgeTimer() {
+ // purgeHandler.removeCallbacks(purger);
+ // purgeHandler.postDelayed(purger, DELAY_BEFORE_PURGE);
+ }
+}
diff --git a/src/Android/Library/src/MultiImageChooserActivity.java b/src/Android/Library/src/MultiImageChooserActivity.java
new file mode 100644
index 0000000..a7d0b3b
--- /dev/null
+++ b/src/Android/Library/src/MultiImageChooserActivity.java
@@ -0,0 +1,395 @@
+package com.synconset;
+
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.Set;
+
+import com.wymsee.apps.synconset.R;
+import android.app.Activity;
+import android.app.ActionBar;
+import android.app.LoaderManager;
+import android.content.Context;
+import android.content.CursorLoader;
+import android.content.Intent;
+import android.content.Loader;
+import android.database.Cursor;
+import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
+import android.graphics.Color;
+import android.os.Bundle;
+import android.provider.MediaStore;
+import android.util.Log;
+import android.util.SparseBooleanArray;
+import android.view.Display;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.AbsListView;
+import android.widget.AbsListView.OnScrollListener;
+import android.widget.AdapterView;
+import android.widget.AdapterView.OnItemClickListener;
+import android.widget.BaseAdapter;
+import android.widget.GridView;
+import android.widget.ImageView;
+import android.widget.TextView;
+
+public class MultiImageChooserActivity extends Activity implements OnItemClickListener,
+ LoaderManager.LoaderCallbacks {
+ private static final String TAG = "Collage";
+ public static final String COL_WIDTH_KEY = "COL_WIDTH";
+ public static final String FLURRY_EVENT_ADD_MULTIPLE_IMAGES = "Add multiple images";
+
+ // El tamaño por defecto es 100 porque los thumbnails MICRO_KIND son de
+ // 96x96
+ private static final int DEFAULT_COLUMN_WIDTH = 120;
+
+ public static final int NOLIMIT = -1;
+ public static final String MAX_IMAGES_KEY = "MAX_IMAGES";
+
+ private ImageAdapter ia;
+
+ private Cursor imagecursor, actualimagecursor;
+ private int image_column_index, actual_image_column_index;
+ private int colWidth;
+
+ private static final int CURSORLOADER_THUMBS = 0;
+ private static final int CURSORLOADER_REAL = 1;
+
+ private Set fileNames = new HashSet();
+
+ private SparseBooleanArray checkStatus = new SparseBooleanArray();
+
+ private TextView freeLabel = null;
+ private int maxImages;
+ private boolean unlimitedImages = false;
+
+ private GridView gridView;
+
+ private final ImageFetcher fetcher = new ImageFetcher();
+
+ private int selectedColor = Color.GREEN;
+ private boolean shouldRequestThumb = true;
+
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.multiselectorgrid);
+ fileNames.clear();
+
+ maxImages = getIntent().getIntExtra(MAX_IMAGES_KEY, NOLIMIT);
+
+ unlimitedImages = maxImages == NOLIMIT;
+ if (!unlimitedImages) {
+ freeLabel = (TextView) findViewById(R.id.label_images_left);
+ freeLabel.setVisibility(View.VISIBLE);
+ updateLabel();
+ }
+
+ colWidth = getIntent().getIntExtra(COL_WIDTH_KEY, DEFAULT_COLUMN_WIDTH);
+
+ Display display = getWindowManager().getDefaultDisplay();
+ @SuppressWarnings("deprecation")
+ int width = display.getWidth();
+ int testColWidth = width / 3;
+
+ if (testColWidth > colWidth) {
+ colWidth = width / 4;
+ }
+
+ // int bgColor = getIntent().getIntExtra("BG_COLOR", Color.BLACK);
+
+ gridView = (GridView) findViewById(R.id.gridview);
+ gridView.setColumnWidth(colWidth);
+ gridView.setOnItemClickListener(this);
+ gridView.setOnScrollListener(new OnScrollListener() {
+
+ private int lastFirstItem = 0;
+ private long timestamp = System.currentTimeMillis();
+
+ @Override
+ public void onScrollStateChanged(AbsListView view, int scrollState) {
+ if (scrollState == SCROLL_STATE_IDLE) {
+ Log.d(TAG, "IDLE - Reload!");
+ shouldRequestThumb = true;
+ ia.notifyDataSetChanged();
+ }
+ }
+
+ @Override
+ public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) {
+ float dt = System.currentTimeMillis() - timestamp;
+ if (firstVisibleItem != lastFirstItem) {
+ double speed = 1 / dt * 1000;
+ lastFirstItem = firstVisibleItem;
+ timestamp = System.currentTimeMillis();
+ Log.d(TAG, "Speed: " + speed + " elements/second");
+
+ // Limitarlo si vamos a más de una página por segundo...
+ shouldRequestThumb = speed < visibleItemCount;
+ }
+ }
+ });
+ selectedColor = 0xff32b2e1;
+ // selectedColor = Color.RED;
+
+ // gridView.setBackgroundColor(bgColor);
+ // gridView.setBackgroundResource(R.drawable.grid_background);
+
+ ia = new ImageAdapter(this);
+ gridView.setAdapter(ia);
+
+ LoaderManager.enableDebugLogging(false);
+ getLoaderManager().initLoader(CURSORLOADER_THUMBS, null, this);
+ getLoaderManager().initLoader(CURSORLOADER_REAL, null, this);
+ setupHeader();
+ updateAcceptButton();
+ }
+
+ private void setupHeader() {
+ // From Roman Nurik's code
+ // https://plus.google.com/113735310430199015092/posts/R49wVvcDoEW
+ // Inflate a "Done/Discard" custom action bar view.
+ LayoutInflater inflater = (LayoutInflater) getActionBar().getThemedContext().getSystemService(
+ LAYOUT_INFLATER_SERVICE);
+ final View customActionBarView = inflater.inflate(R.layout.actionbar_custom_view_done_discard, null);
+ customActionBarView.findViewById(R.id.actionbar_done).setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ // "Done"
+ selectClicked(null);
+ }
+ });
+ customActionBarView.findViewById(R.id.actionbar_discard).setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ finish();
+ }
+ });
+
+ // 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));
+ }
+
+ private void updateLabel() {
+ if (freeLabel != null) {
+ String text = String.format(getString(R.string.free_version_label), maxImages);
+ freeLabel.setText(text);
+ if (maxImages == 0) {
+ freeLabel.setTextColor(Color.RED);
+ } else {
+ freeLabel.setTextColor(Color.WHITE);
+ }
+ }
+ }
+
+ public class ImageAdapter extends BaseAdapter {
+ private final Bitmap mPlaceHolderBitmap;
+
+ public ImageAdapter(Context c) {
+ Bitmap tmpHolderBitmap = BitmapFactory.decodeResource(getResources(), R.drawable.loading_icon);
+ mPlaceHolderBitmap = Bitmap.createScaledBitmap(tmpHolderBitmap, colWidth, colWidth, false);
+ if (tmpHolderBitmap != mPlaceHolderBitmap) {
+ tmpHolderBitmap.recycle();
+ tmpHolderBitmap = null;
+ }
+ }
+
+ public int getCount() {
+ if (imagecursor != null) {
+ return imagecursor.getCount();
+ } else {
+ return 0;
+ }
+ }
+
+ public Object getItem(int position) {
+ return position;
+ }
+
+ public long getItemId(int position) {
+ return position;
+ }
+
+ // create a new ImageView for each item referenced by the Adapter
+ public View getView(int pos, View convertView, ViewGroup parent) {
+
+ if (convertView == null) {
+ convertView = new ImageView(MultiImageChooserActivity.this);
+ }
+
+ ImageView imageView = (ImageView) convertView;
+ imageView.setImageBitmap(null);
+
+ final int position = pos;
+
+ if (!imagecursor.moveToPosition(position)) {
+ return imageView;
+ }
+
+ if (image_column_index == -1) {
+ return imageView;
+ }
+
+ final int id = imagecursor.getInt(image_column_index);
+ if (isChecked(pos)) {
+ imageView.setBackgroundColor(selectedColor);
+ } else {
+ imageView.setBackgroundColor(Color.TRANSPARENT);
+ }
+ if (shouldRequestThumb) {
+ fetcher.fetch(Integer.valueOf(id), imageView, colWidth);
+ }
+
+ return imageView;
+ }
+ }
+
+ private String getImageName(int position) {
+ actualimagecursor.moveToPosition(position);
+ String name = null;
+
+ try {
+ name = actualimagecursor.getString(actual_image_column_index);
+ } catch (Exception e) {
+ return null;
+ }
+ return name;
+ }
+
+ private void setChecked(int position, boolean b) {
+ checkStatus.put(position, b);
+ }
+
+ public boolean isChecked(int position) {
+ boolean ret = checkStatus.get(position);
+ return ret;
+ }
+
+ public void cancelClicked(View ignored) {
+ setResult(RESULT_CANCELED);
+ finish();
+ }
+
+ public void selectClicked(View ignored) {
+ Intent data = new Intent();
+ if (fileNames.isEmpty()) {
+ this.setResult(RESULT_CANCELED);
+ } else {
+
+ ArrayList al = new ArrayList();
+ al.addAll(fileNames);
+ Bundle res = new Bundle();
+ res.putStringArrayList("MULTIPLEFILENAMES", al);
+ if (imagecursor != null) {
+ res.putInt("TOTALFILES", imagecursor.getCount());
+ }
+
+ data.putExtras(res);
+ this.setResult(RESULT_OK, data);
+ }
+ Log.d(TAG, "Returning " + fileNames.size() + " items");
+ finish();
+ }
+
+ @Override
+ public void onItemClick(AdapterView> arg0, View view, int position, long id) {
+ String name = getImageName(position);
+
+ if (name == null) {
+ return;
+ }
+ boolean isChecked = !isChecked(position);
+ // PhotoMix.Log("DAVID", "Posicion " + position + " isChecked: " +
+ // isChecked);
+ if (!unlimitedImages && maxImages == 0 && isChecked) {
+ // PhotoMix.Log("DAVID", "Aquí no debería entrar...");
+ isChecked = false;
+ }
+
+ if (isChecked) {
+ // Solo se resta un slot si hemos introducido un
+ // filename de verdad...
+ if (fileNames.add(name)) {
+ maxImages--;
+ view.setBackgroundColor(selectedColor);
+ }
+ } else {
+ if (fileNames.remove(name)) {
+ // Solo incrementa los slots libres si hemos
+ // "liberado" uno...
+ maxImages++;
+ view.setBackgroundColor(Color.TRANSPARENT);
+ }
+ }
+
+ setChecked(position, isChecked);
+ updateAcceptButton();
+ updateLabel();
+
+ }
+
+ private void updateAcceptButton() {
+ ((TextView) getActionBar().getCustomView().findViewById(R.id.actionbar_done_textview))
+ .setEnabled(fileNames.size() != 0);
+ getActionBar().getCustomView().findViewById(R.id.actionbar_done).setEnabled(fileNames.size() != 0);
+
+ }
+
+ @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);
+ break;
+ case CURSORLOADER_REAL:
+ img.add(MediaStore.Images.Thumbnails.DATA);
+ break;
+ default:
+ break;
+ }
+
+ cl = new CursorLoader(MultiImageChooserActivity.this, MediaStore.Images.Media.EXTERNAL_CONTENT_URI,
+ img.toArray(new String[img.size()]), null, null, null);
+ return cl;
+ }
+
+ @Override
+ public void onLoadFinished(Loader loader, Cursor cursor) {
+ if (cursor == null) {
+ // NULL cursor. This usually means there's no image database yet....
+ return;
+ }
+
+ switch (loader.getId()) {
+ case CURSORLOADER_THUMBS:
+ imagecursor = cursor;
+ image_column_index = imagecursor.getColumnIndex(MediaStore.Images.Media._ID);
+ ia.notifyDataSetChanged();
+ break;
+ case CURSORLOADER_REAL:
+ actualimagecursor = cursor;
+ actual_image_column_index = actualimagecursor.getColumnIndexOrThrow(MediaStore.Images.Media.DATA);
+ break;
+ default:
+ break;
+ }
+ }
+
+ @Override
+ public void onLoaderReset(Loader loader) {
+ if (loader.getId() == CURSORLOADER_THUMBS) {
+ imagecursor = null;
+ } else if (loader.getId() == CURSORLOADER_REAL) {
+ actualimagecursor = null;
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Android/com/synconset/ImagePicker/ImagePicker.java b/src/Android/com/synconset/ImagePicker/ImagePicker.java
new file mode 100644
index 0000000..46ac0bf
--- /dev/null
+++ b/src/Android/com/synconset/ImagePicker/ImagePicker.java
@@ -0,0 +1,44 @@
+/**
+ * An Internal Storage Plugin for Cordova/PhoneGap.
+ */
+package com.synconset;
+
+import org.apache.cordova.CallbackContext;
+import org.apache.cordova.CordovaPlugin;
+
+import org.json.JSONArray;
+import org.json.JSONException;
+import org.json.JSONObject;
+
+import java.util.ArrayList;
+import java.io.File;
+import java.io.IOException;
+
+import android.app.Activity;
+import android.content.Intent;
+import android.util.Log;
+
+public class ImagePicker extends CordovaPlugin {
+
+ public boolean execute(String action, final JSONArray args, final CallbackContext callbackContext) throws JSONException {
+ if (action.equals("getPictures")) {
+ Intent intent = new Intent(cordova.getActivity(), MultiImageChooserActivity.class);
+ intent.putExtra("MAX_IMAGES", 20);
+
+ if (this.cordova != null) {
+ this.cordova.startActivityForResult((CordovaPlugin) this, intent, 0);
+ }
+ }
+ return true;
+ }
+
+ public void onActivityResult(int requestCode, int resultCode, Intent data) {
+ Log.d("PLUGIN", "result code = " + resultCode);
+ if (resultCode == 1 && data != null) {
+ ArrayList fileNames = data.getStringArrayListExtra("MULTIPLEFILNAMES");
+ for (int i = 0; i < fileNames.size(); i++) {
+ Log.d("PLUGIN", fileNames.get(i));
+ }
+ }
+ }
+}