Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(4270)

Unified Diff: chrome/android/java/src/org/chromium/chrome/browser/photo_picker/PickerCategoryView.java

Issue 2758313002: Implement the new Photo picker, part two. (Closed)
Patch Set: Fix scrim color Created 3 years, 9 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View side-by-side diff with in-line comments
Download patch
Index: chrome/android/java/src/org/chromium/chrome/browser/photo_picker/PickerCategoryView.java
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/photo_picker/PickerCategoryView.java b/chrome/android/java/src/org/chromium/chrome/browser/photo_picker/PickerCategoryView.java
new file mode 100644
index 0000000000000000000000000000000000000000..879fd82c415faa395b70c926dff039ccaae69d11
--- /dev/null
+++ b/chrome/android/java/src/org/chromium/chrome/browser/photo_picker/PickerCategoryView.java
@@ -0,0 +1,360 @@
+// Copyright 2016 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package org.chromium.chrome.browser.photo_picker;
+
+import android.app.Activity;
+import android.content.Context;
+import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
+import android.graphics.Canvas;
+import android.graphics.Paint;
+import android.graphics.PorterDuff;
+import android.graphics.PorterDuffColorFilter;
+import android.graphics.Rect;
+import android.support.v4.content.ContextCompat;
+import android.support.v7.widget.DefaultItemAnimator;
+import android.support.v7.widget.GridLayoutManager;
+import android.support.v7.widget.RecyclerView;
+import android.support.v7.widget.Toolbar.OnMenuItemClickListener;
+import android.util.AttributeSet;
+import android.util.LruCache;
+import android.view.LayoutInflater;
+import android.view.MenuItem;
+import android.view.View;
+import android.widget.RelativeLayout;
+
+import org.chromium.chrome.R;
+import org.chromium.chrome.browser.widget.selection.SelectableListLayout;
+import org.chromium.chrome.browser.widget.selection.SelectionDelegate;
+import org.chromium.ui.PhotoPickerListener;
+
+import java.util.List;
+
+/**
+ * A class for keeping track of common data associated with showing photos in
+ * the photo picker, for example the RecyclerView and the bitmap caches.
+ */
+public class PickerCategoryView extends RelativeLayout
+ implements FileEnumWorkerTask.FilesEnumeratedCallback, OnMenuItemClickListener {
+ // The dialog that owns us.
+ private PhotoPickerDialog mDialog;
+
+ // The view containing the recycler view and the toolbar, etc.
+ private SelectableListLayout<PickerBitmap> mSelectableListLayout;
+
+ // The toolbar at the top of the dialog.
+ private PhotoPickerToolbar mToolbar;
+
+ // Our context.
+ private Context mContext;
+
+ // The list of images on disk, sorted by last-modified first.
+ private List<PickerBitmap> mPickerBitmaps;
+
+ // True if multi-selection is allowed in the picker.
+ private boolean mMultiSelection;
Theresa 2017/03/28 20:40:29 nit: mIsMultiSelectionAllowed? or mIsMultiSelectio
Finnur 2017/03/31 14:26:50 Done.
+
+ // The callback to notify the listener of decisions reached in the picker.
+ private PhotoPickerListener mListener;
+
+ // The recycler view showing the images.
+ private RecyclerView mRecyclerView;
+
+ // The picker adapter for the RecyclerView.
+ private PickerAdapter mPickerAdapter;
+
+ // The selection delegate keeping track of which images are selected.
+ private SelectionDelegate<PickerBitmap> mSelectionDelegate;
+
+ // A low-resolution cache for images. Helpful for cache misses from the high-resolution cache
+ // to avoid showing gray squares (show pixelated versions instead until image can be loaded off
+ // disk).
+ private LruCache<String, Bitmap> mLowResBitmaps;
+
+ // A high-resolution cache for images.
+ private LruCache<String, Bitmap> mHighResBitmaps;
+
+ // The number of columns to show. Note: mColumns and mPadding (see below) should both be even
+ // numbers or both odd, not a mix (the column padding will not be of uniform thickness if they
+ // are a mix).
Theresa 2017/03/28 20:40:29 nit: Should these be JavaDoc /** */ comments so th
Finnur 2017/03/31 14:26:50 Done. Unless you mean all of them?
+ private int mColumns;
+
+ // The padding between columns. See also comment for mColumns.
+ private int mPadding;
+
+ // The size of the bitmaps (equal length for width and height).
+ private int mImageSize;
+
+ // The control to show for when an image is selected.
+ private Bitmap mBitmapSelected;
+
+ // The control to show for when an image is not selected.
+ private Bitmap mBitmapUnselected;
+
+ // A worker task for asynchronously enumerating files off the main thread.
+ private FileEnumWorkerTask mWorkerTask;
+
+ public PickerCategoryView(Context context) {
+ super(context);
+ init(context);
+ }
+
+ public PickerCategoryView(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ init(context);
+ }
+
+ public PickerCategoryView(Context context, AttributeSet attrs, int defStyle) {
+ super(context, attrs, defStyle);
+ init(context);
+ }
+
+ /**
+ * A helper function for initializing the PickerCategoryView.
+ * @param context The context to use.
+ */
+ @SuppressWarnings("unchecked") // mSelectableListLayout
+ private void init(Context context) {
+ mContext = context;
+
+ mSelectionDelegate = new SelectionDelegate<PickerBitmap>();
+
+ View root = LayoutInflater.from(context).inflate(R.layout.photo_picker_dialog, this);
+ mSelectableListLayout =
+ (SelectableListLayout<PickerBitmap>) root.findViewById(R.id.selectable_list);
+
+ mPickerAdapter = new PickerAdapter(this);
+ mRecyclerView = mSelectableListLayout.initializeRecyclerView(mPickerAdapter);
+ mToolbar = (PhotoPickerToolbar) mSelectableListLayout.initializeToolbar(
+ R.layout.photo_picker_toolbar, mSelectionDelegate, R.string.menu_history, null,
+ R.id.file_picker_normal_menu_group, R.id.file_picker_selection_mode_menu_group,
+ R.color.default_primary_color, false, this);
+
+ View view = ((Activity) context).getWindow().getDecorView();
+ int width = view.getWidth() - view.getPaddingLeft() - view.getPaddingRight();
+
+ calculateGridMetrics(width);
+
+ RecyclerView.LayoutManager mLayoutManager = new GridLayoutManager(mContext, mColumns);
+ mRecyclerView.setHasFixedSize(true);
+ mRecyclerView.setLayoutManager(mLayoutManager);
+ mRecyclerView.setItemAnimator(new DefaultItemAnimator());
Theresa 2017/03/28 20:40:29 This shouldn't be necessary. By default, RecyclerV
Finnur 2017/03/31 14:26:50 Done.
+ mRecyclerView.addItemDecoration(new GridSpacingItemDecoration(mColumns, mPadding));
+
+ mBitmapSelected = BitmapFactory.decodeResource(
+ mContext.getResources(), R.drawable.ic_check_circle_black_24dp);
+ mBitmapUnselected = BitmapFactory.decodeResource(
+ mContext.getResources(), R.drawable.ic_radio_button_unchecked_black_24dp);
+
+ // Apply color to the bitmaps.
+ int prefAccentColor = ContextCompat.getColor(mContext, R.color.pref_accent_color);
+ mBitmapSelected = colorBitmap(mBitmapSelected, prefAccentColor);
+ int unselectedColor = ContextCompat.getColor(mContext, R.color.white_mode_tint);
+ mBitmapUnselected = colorBitmap(mBitmapUnselected, unselectedColor);
Theresa 2017/03/28 20:40:29 Typically we would set the src for an ImageView in
Finnur 2017/03/31 14:26:50 Neat. Done.
+
+ final int maxMemory = (int) (Runtime.getRuntime().maxMemory() / 1024);
+ final int cacheSizeLarge = maxMemory / 2; // 1/2 of the available memory.
+ final int cacheSizeSmall = maxMemory / 8; // 1/8th of the available memory.
+ mLowResBitmaps = new LruCache<String, Bitmap>(cacheSizeSmall) {
+ @Override
+ protected int sizeOf(String key, Bitmap bitmap) {
+ return bitmap.getByteCount() / 1024;
Theresa 2017/03/28 20:40:29 nit: define a constant for 1024
Finnur 2017/03/31 14:26:50 Done.
+ }
+ };
+ mHighResBitmaps = new LruCache<String, Bitmap>(cacheSizeLarge) {
+ @Override
+ protected int sizeOf(String key, Bitmap bitmap) {
+ return bitmap.getByteCount() / 1024;
+ }
+ };
+
+ // TODO(finnur): Remove this once the decoder service is in place.
+ prepareBitmaps();
+ }
+
+ /**
+ * Sets the starting state for the PickerCategoryView object.
+ * @param selectionDelegate The selection delegate to use.
+ * @param listener The listener who should be notified of actions.
+ * @param multiSelection Whether to allow the user to select more than one image.
+ */
+ public void setStartingState(
+ PhotoPickerDialog dialog, PhotoPickerListener listener, boolean multiSelection) {
+ if (!multiSelection) mSelectionDelegate.setSingleSelectionMode();
+
+ mDialog = dialog;
+ mMultiSelection = multiSelection;
+ mListener = listener;
+ }
+
+ // FileEnumWorkerTask.FilesEnumeratedCallback:
+
+ @Override
+ public void filesEnumeratedCallback(List<PickerBitmap> files) {
+ mPickerBitmaps = files;
+ if (files != null && files.size() > 0) {
+ mPickerAdapter.notifyDataSetChanged();
+ } else {
+ setVisibility(View.GONE);
Theresa 2017/03/28 20:40:29 Can this ever be empty, since there's the camera a
Finnur 2017/03/31 14:26:50 Removed. I believe this is from back when the Came
+ }
+ }
+
+ // OnMenuItemClickListener:
+
+ @Override
+ public boolean onMenuItemClick(MenuItem item) {
+ if (item.getItemId() == R.id.close_menu_id
+ || item.getItemId() == R.id.selection_mode_done_menu_id) {
+ tryNotifyPhotoSet();
+ mDialog.dismiss();
+ return true;
+ }
+ return false;
+ }
+
+ // Simple accessors:
+
+ public int getImageSize() {
+ return mImageSize;
+ }
+ public SelectionDelegate<PickerBitmap> getSelectionDelegate() {
+ return mSelectionDelegate;
+ }
+ public List<PickerBitmap> getPickerBitmaps() {
+ return mPickerBitmaps;
+ }
+ public LruCache<String, Bitmap> getLowResBitmaps() {
+ return mLowResBitmaps;
+ }
+ public LruCache<String, Bitmap> getHighResBitmaps() {
+ return mHighResBitmaps;
+ }
+ public boolean isMultiSelect() {
+ return mMultiSelection;
+ }
+
+ /**
+ * Notifies the caller that the user selected to launch the gallery.
+ */
+ public void showGallery() {
+ mListener.onPickerUserAction(PhotoPickerListener.Action.LAUNCH_GALLERY, null);
+ }
+
+ /**
+ * Notifies the caller that the user selected to launch the camera intent.
+ */
+ public void showCamera() {
+ mListener.onPickerUserAction(PhotoPickerListener.Action.LAUNCH_CAMERA, null);
+ }
+
+ /**
+ * Returns the selection bitmaps (control indicating whether the image is selected or not).
+ * @param selected See return value.
+ * @return If |selected| is true, the selection bitmap is returned. Otherwise the unselection
+ * bitmap is returned.
+ */
+ public Bitmap getSelectionBitmap(boolean selected) {
+ if (selected) {
+ return mBitmapSelected;
+ } else {
+ return mBitmapUnselected;
+ }
+ }
+
+ /**
+ * Applies a color filter to a bitmap.
+ * @param original The bitmap to color.
+ * @param color The color to apply.
+ * @return A colored bitmap.
+ */
+ private Bitmap colorBitmap(Bitmap original, int color) {
+ Bitmap mutable = original.copy(Bitmap.Config.ARGB_8888, true);
+ Canvas canvas = new Canvas(mutable);
+ Paint paint = new Paint();
+ paint.setColorFilter(new PorterDuffColorFilter(color, PorterDuff.Mode.SRC_IN));
+ canvas.drawBitmap(mutable, 0.f, 0.f, paint);
+ return mutable;
+ }
+
+ /**
+ * Calculates image size and how many columns can fit on-screen.
+ * @param width The total width of the boundary to show the images in.
+ */
+ private void calculateGridMetrics(int width) {
+ int minSize =
+ mContext.getResources().getDimensionPixelSize(R.dimen.file_picker_tile_min_size);
+ mPadding = mContext.getResources().getDimensionPixelSize(R.dimen.file_picker_tile_gap);
+ mColumns = Math.max(1, (width - mPadding) / (minSize + mPadding));
+ mImageSize = (width - mPadding * (mColumns + 1)) / (mColumns);
+
+ // Make sure columns and padding are either both even or both odd.
+ if (((mColumns % 2) == 0) != ((mPadding % 2) == 0)) {
+ mPadding++;
+ }
+ }
+
+ /**
+ * Prepares bitmaps for loading.
+ */
+ private void prepareBitmaps() {
+ if (mWorkerTask != null) {
+ mWorkerTask.cancel(true);
+ }
+
+ mWorkerTask = new FileEnumWorkerTask(this, new AttrAcceptFileFilter("image/*,video/*"));
+ mWorkerTask.execute();
+ }
+
+ /**
+ * Tries to notify any listeners that one or more photos have been selected.
+ */
+ private void tryNotifyPhotoSet() {
+ List<PickerBitmap> selectedFiles = mSelectionDelegate.getSelectedItems();
+ String[] photos = new String[selectedFiles.size()];
+ int i = 0;
+ for (PickerBitmap bitmap : selectedFiles) {
+ photos[i++] = bitmap.getFilePath();
+ }
+
+ mListener.onPickerUserAction(PhotoPickerListener.Action.PHOTOS_SELECTED, photos);
+ }
+
+ /**
+ * A class for implementing grid spacing between items.
+ */
+ private class GridSpacingItemDecoration extends RecyclerView.ItemDecoration {
+ // The number of spans to account for.
+ private int mSpanCount;
+
+ // The amount of spacing to use.
+ private int mSpacing;
+
+ public GridSpacingItemDecoration(int spanCount, int spacing) {
+ mSpanCount = spanCount;
+ mSpacing = spacing;
+ }
+
+ @Override
+ public void getItemOffsets(
+ Rect outRect, View view, RecyclerView parent, RecyclerView.State state) {
+ int left = 0, right = 0, top = 0, bottom = 0;
+ int position = parent.getChildAdapterPosition(view);
+
+ if (position >= 0) {
+ int column = position % mSpanCount;
+
+ left = mSpacing - column * mSpacing / mSpanCount;
+ right = (column + 1) * mSpacing / mSpanCount;
+
+ if (position < mSpanCount) {
+ top = mSpacing;
+ }
+ bottom = mSpacing;
+ }
+
+ outRect.set(left, top, right, bottom);
+ }
+ }
+}

Powered by Google App Engine
This is Rietveld 408576698