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)); + } + } + } +}