Commit 2df35f29 authored by Shakti Sahu's avatar Shakti Sahu Committed by Commit Bot

Download Home : Decoupled SelectionDelegate from list item view

Removed using SelectableItemView in download home. Instead properties for
selected and selectionModeActive will be set into the ListItem from the mediator.

Bug: 850600, 868205
Change-Id: I36d98b4f7cfde8d6c8a93adae091cd8024b14e3c
Reviewed-on: https://chromium-review.googlesource.com/1150810Reviewed-by: default avatarTheresa <twellington@chromium.org>
Commit-Queue: Shakti Sahu <shaktisahu@chromium.org>
Cr-Commit-Position: refs/heads/master@{#580249}
parent 7398fb38
......@@ -29,6 +29,17 @@
app:layout_gravity="center_vertical"
app:chrometint="@color/dark_mode_tint" />
<org.chromium.chrome.browser.download.home.view.SelectionView
android:id="@+id/selection"
android:layout_width="@dimen/download_manager_generic_thumbnail_size"
android:layout_height="@dimen/download_manager_generic_thumbnail_size"
android:layout_marginStart="16dp"
android:layout_marginEnd="16dp"
app:layout_column="0"
app:layout_row="0"
app:layout_rowSpan="2"
app:layout_gravity="center_vertical" />
<TextView
android:id="@+id/title"
style="@style/DownloadItemText"
......
......@@ -21,14 +21,12 @@
android:layout_gravity="center"
tools:ignore="ContentDescription" />
<org.chromium.chrome.browser.widget.TintedImageView
<org.chromium.chrome.browser.download.home.view.SelectionView
android:id="@+id/selection"
android:layout_width="20dp"
android:layout_height="20dp"
android:layout_gravity="start|top"
android:layout_marginStart="6dp"
android:layout_marginTop="6dp"
android:visibility="gone"
app:chrometint="@color/dark_mode_tint"/>
android:layout_marginTop="6dp"/>
</FrameLayout>
<?xml version="1.0" encoding="utf-8"?>
<!-- Copyright 2018 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.
-->
<merge
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools">
<org.chromium.chrome.browser.widget.TintedImageView
android:id="@+id/check"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:scaleType="center"
android:background="@drawable/list_item_icon_modern_bg"
android:visibility="gone"
app:chrometint="@color/white_mode_tint"
app:layout_gravity="center"
tools:ignore="ContentDescription" />
<org.chromium.chrome.browser.widget.TintedImageView
android:id="@+id/circle"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_gravity="center"
android:background="@null"
android:src="@drawable/download_circular_selector_transparent"
android:visibility="gone"
app:chrometint="@null"
tools:ignore="ContentDescription"/>
</merge>
......@@ -46,12 +46,39 @@ class DateOrderedListMediator {
private final OfflineItemSource mSource;
private final DateOrderedListMutator mListMutator;
private final ThumbnailProvider mThumbnailProvider;
private final MediatorSelectionObserver mSelectionObserver;
private final OffTheRecordOfflineItemFilter mOffTheRecordFilter;
private final DeleteUndoOfflineItemFilter mDeleteUndoFilter;
private final TypeOfflineItemFilter mTypeFilter;
private final SearchOfflineItemFilter mSearchFilter;
/**
* A selection observer that correctly updates the selection state for each item in the list.
*/
private class MediatorSelectionObserver
implements SelectionDelegate.SelectionObserver<ListItem> {
private final SelectionDelegate<ListItem> mSelectionDelegate;
public MediatorSelectionObserver(SelectionDelegate<ListItem> delegate) {
mSelectionDelegate = delegate;
mSelectionDelegate.addObserver(this);
}
@Override
public void onSelectionStateChange(List<ListItem> selectedItems) {
for (int i = 0; i < mModel.size(); i++) {
ListItem item = mModel.get(i);
boolean selected = mSelectionDelegate.isItemSelected(item);
item.showSelectedAnimation = selected && !item.selected;
item.selected = selected;
mModel.setItem(i, item);
}
mModel.dispatchLastEvent();
mModel.getProperties().setSelectionModeActive(mSelectionDelegate.isSelectionEnabled());
}
}
/**
* Creates an instance of a DateOrderedListMediator that will push {@code provider} into
* {@code model}.
......@@ -86,6 +113,7 @@ class DateOrderedListMediator {
mThumbnailProvider = new ThumbnailProviderImpl(
((ChromeApplication) ContextUtils.getApplicationContext()).getReferencePool());
mSelectionObserver = new MediatorSelectionObserver(selectionDelegate);
mModel.getProperties().setEnableItemAnimations(true);
mModel.getProperties().setOpenCallback(mProvider::openItem);
......@@ -95,7 +123,7 @@ class DateOrderedListMediator {
mModel.getProperties().setShareCallback(item -> {});
mModel.getProperties().setRemoveCallback(this::onDeleteItem);
mModel.getProperties().setVisualsProvider(this::getVisuals);
mModel.getProperties().setSelectionDelegate(selectionDelegate);
mModel.getProperties().setSelectionCallback(selectionDelegate::toggleSelectionForItem);
}
/** Tears down this mediator. */
......
......@@ -16,6 +16,12 @@ import java.util.Date;
public abstract class ListItem {
public final long stableId;
/** Indicates that we are in multi-select mode and the item is currently selected. */
public boolean selected;
/** Whether animation should be shown for the recent change in selection state for this item. */
public boolean showSelectedAnimation;
/** Creates a {@link ListItem} instance. */
ListItem(long stableId) {
this.stableId = stableId;
......
......@@ -6,7 +6,6 @@ package org.chromium.chrome.browser.download.home.list;
import org.chromium.base.Callback;
import org.chromium.chrome.browser.modelutil.PropertyObservable;
import org.chromium.chrome.browser.widget.selection.SelectionDelegate;
import org.chromium.components.offline_items_collection.OfflineItem;
import org.chromium.components.offline_items_collection.OfflineItemVisuals;
import org.chromium.components.offline_items_collection.VisualsCallback;
......@@ -41,6 +40,8 @@ public class ListPropertyModel extends PropertyObservable<ListPropertyModel.Prop
static final PropertyKey CALLBACK_CANCEL = new PropertyKey();
static final PropertyKey CALLBACK_SHARE = new PropertyKey();
static final PropertyKey CALLBACK_REMOVE = new PropertyKey();
static final PropertyKey CALLBACK_SELECTION = new PropertyKey();
static final PropertyKey SELECTION_MODE_ACTIVE = new PropertyKey();
// Utility properties.
static final PropertyKey PROVIDER_VISUALS = new PropertyKey();
......@@ -49,14 +50,15 @@ public class ListPropertyModel extends PropertyObservable<ListPropertyModel.Prop
}
private boolean mEnableItemAnimations;
private boolean mSelectionModeActive;
private Callback<OfflineItem> mOpenCallback;
private Callback<OfflineItem> mPauseCallback;
private Callback<OfflineItem> mResumeCallback;
private Callback<OfflineItem> mCancelCallback;
private Callback<OfflineItem> mShareCallback;
private Callback<OfflineItem> mRemoveCallback;
private Callback<ListItem> mSelectionCallback;
private VisualsProvider mVisualsProvider;
private SelectionDelegate<ListItem> mSelectionDelegate;
/** Sets whether or not item animations should be enabled. */
public void setEnableItemAnimations(boolean enableItemAnimations) {
......@@ -70,6 +72,18 @@ public class ListPropertyModel extends PropertyObservable<ListPropertyModel.Prop
return mEnableItemAnimations;
}
/** Sets whether or not selection mode is currently active. */
public void setSelectionModeActive(boolean selectionModeActive) {
if (mSelectionModeActive == selectionModeActive) return;
mSelectionModeActive = selectionModeActive;
notifyPropertyChanged(PropertyKey.SELECTION_MODE_ACTIVE);
}
/** @return Whether or not selection mode is currently active. */
public boolean getSelectionModeActive() {
return mSelectionModeActive;
}
/** Sets the callback for when a UI action should open a {@link OfflineItem}. */
public void setOpenCallback(Callback<OfflineItem> callback) {
if (mOpenCallback == callback) return;
......@@ -154,13 +168,15 @@ public class ListPropertyModel extends PropertyObservable<ListPropertyModel.Prop
return mVisualsProvider;
}
/** Sets the selection delegate to handle selection of list items. */
public void setSelectionDelegate(SelectionDelegate<ListItem> selectionDelegate) {
mSelectionDelegate = selectionDelegate;
/** Sets the callback for when an {@link ListItem} is selected or deselected on the UI. */
public void setSelectionCallback(Callback<ListItem> callback) {
if (mSelectionCallback == callback) return;
mSelectionCallback = callback;
notifyPropertyChanged(PropertyKey.CALLBACK_SELECTION);
}
/** @return The selection delegate to handle multiple item selections. */
public SelectionDelegate<ListItem> getSelectionDelegate() {
return mSelectionDelegate;
/** @return The callback to trigger when a UI action selects or deselects a {@link ListItem}. */
public Callback<ListItem> getSelectionCallback() {
return mSelectionCallback;
}
}
......@@ -33,7 +33,9 @@ class ListPropertyViewBinder
|| propertyKey == ListPropertyModel.PropertyKey.CALLBACK_CANCEL
|| propertyKey == ListPropertyModel.PropertyKey.CALLBACK_SHARE
|| propertyKey == ListPropertyModel.PropertyKey.CALLBACK_REMOVE
|| propertyKey == ListPropertyModel.PropertyKey.PROVIDER_VISUALS) {
|| propertyKey == ListPropertyModel.PropertyKey.PROVIDER_VISUALS
|| propertyKey == ListPropertyModel.PropertyKey.CALLBACK_SELECTION
|| propertyKey == ListPropertyModel.PropertyKey.SELECTION_MODE_ACTIVE) {
view.getAdapter().notifyItemChanged(0, view.getAdapter().getItemCount());
}
}
......
......@@ -4,11 +4,9 @@
package org.chromium.chrome.browser.download.home.list.holder;
import android.content.res.ColorStateList;
import android.content.res.Resources;
import android.graphics.Bitmap;
import android.support.annotation.DrawableRes;
import android.support.graphics.drawable.AnimatedVectorDrawableCompat;
import android.support.v4.graphics.drawable.RoundedBitmapDrawable;
import android.support.v4.graphics.drawable.RoundedBitmapDrawableFactory;
import android.support.v7.content.res.AppCompatResources;
......@@ -19,10 +17,10 @@ import android.widget.ImageView;
import android.widget.TextView;
import org.chromium.chrome.R;
import org.chromium.chrome.browser.download.DownloadUtils;
import org.chromium.chrome.browser.download.home.list.ListItem;
import org.chromium.chrome.browser.download.home.list.ListPropertyModel;
import org.chromium.chrome.browser.download.home.list.UiUtils;
import org.chromium.chrome.browser.download.home.view.SelectionView;
import org.chromium.chrome.browser.widget.TintedImageView;
import org.chromium.components.offline_items_collection.OfflineItemVisuals;
......@@ -31,9 +29,6 @@ import org.chromium.components.offline_items_collection.OfflineItemVisuals;
public class GenericViewHolder extends ThumbnailAwareViewHolder {
private static final int INVALID_ID = -1;
private final ColorStateList mCheckedIconForegroundColorList;
private final AnimatedVectorDrawableCompat mCheckDrawable;
private final TextView mTitle;
private final TextView mCaption;
private final TintedImageView mThumbnailView;
......@@ -61,11 +56,6 @@ public class GenericViewHolder extends ThumbnailAwareViewHolder {
mTitle = (TextView) itemView.findViewById(R.id.title);
mCaption = (TextView) itemView.findViewById(R.id.caption);
mThumbnailView = (TintedImageView) itemView.findViewById(R.id.thumbnail);
mCheckDrawable = AnimatedVectorDrawableCompat.create(
itemView.getContext(), R.drawable.ic_check_googblue_24dp_animated);
mCheckedIconForegroundColorList =
DownloadUtils.getIconForegroundColorList(itemView.getContext());
}
// ListItemViewHolder implementation.
......@@ -89,18 +79,9 @@ public class GenericViewHolder extends ThumbnailAwareViewHolder {
private void updateThumbnailView() {
Resources resources = itemView.getContext().getResources();
// TODO(shaktisahu): Pass the appropriate value of selection.
boolean selected = false;
if (selected) {
mThumbnailView.setBackgroundResource(R.drawable.list_item_icon_modern_bg);
mThumbnailView.getBackground().setLevel(
resources.getInteger(R.integer.list_item_level_selected));
mThumbnailView.setImageDrawable(mCheckDrawable);
mThumbnailView.setTint(mCheckedIconForegroundColorList);
mCheckDrawable.start();
} else if (mThumbnailBitmap != null) {
SelectionView selectionView = itemView.findViewById(R.id.selection);
mThumbnailView.setVisibility(selectionView.isSelected() ? View.GONE : View.VISIBLE);
if (mThumbnailBitmap != null) {
assert !mThumbnailBitmap.isRecycled();
mThumbnailView.setBackground(null);
......
......@@ -4,27 +4,18 @@
package org.chromium.chrome.browser.download.home.list.holder;
import android.content.res.ColorStateList;
import android.content.res.Resources;
import android.support.graphics.drawable.AnimatedVectorDrawableCompat;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageView;
import org.chromium.chrome.R;
import org.chromium.chrome.browser.download.DownloadUtils;
import org.chromium.chrome.browser.download.home.list.ListItem;
import org.chromium.chrome.browser.download.home.list.ListPropertyModel;
import org.chromium.chrome.browser.widget.TintedImageView;
import org.chromium.components.offline_items_collection.OfflineItemVisuals;
/** A {@link RecyclerView.ViewHolder} specifically meant to display an image {@code OfflineItem}. */
public class ImageViewHolder extends ThumbnailAwareViewHolder {
private final TintedImageView mSelectionImage;
private final ColorStateList mCheckedIconForegroundColorList;
private final AnimatedVectorDrawableCompat mCheckDrawable;
public static org.chromium.chrome.browser.download.home.list.holder.ImageViewHolder create(
ViewGroup parent) {
View view = LayoutInflater.from(parent.getContext())
......@@ -37,11 +28,6 @@ public class ImageViewHolder extends ThumbnailAwareViewHolder {
public ImageViewHolder(View view, int thumbnailSizePx) {
super(view, thumbnailSizePx, thumbnailSizePx);
mCheckDrawable = AnimatedVectorDrawableCompat.create(
itemView.getContext(), R.drawable.ic_check_googblue_24dp_animated);
mCheckedIconForegroundColorList =
DownloadUtils.getIconForegroundColorList(itemView.getContext());
mSelectionImage = (TintedImageView) itemView.findViewById(R.id.selection);
}
// ThumbnailAwareViewHolder implementation.
......@@ -51,39 +37,10 @@ public class ImageViewHolder extends ThumbnailAwareViewHolder {
ListItem.OfflineItemListItem offlineItem = (ListItem.OfflineItemListItem) item;
View imageView = itemView.findViewById(R.id.thumbnail);
imageView.setContentDescription(offlineItem.item.title);
updateImageView();
}
@Override
void onVisualsChanged(ImageView view, OfflineItemVisuals visuals) {
view.setImageBitmap(visuals == null ? null : visuals.icon);
updateImageView();
}
protected void updateImageView() {
Resources resources = itemView.getContext().getResources();
// TODO(shaktisahu): Pass the appropriate value of selection.
boolean selected = false;
boolean selectionModeActive = false;
if (selected) {
mSelectionImage.setVisibility(View.VISIBLE);
mSelectionImage.setBackgroundResource(R.drawable.list_item_icon_modern_bg);
mSelectionImage.getBackground().setLevel(
resources.getInteger(R.integer.list_item_level_selected));
mSelectionImage.setImageDrawable(mCheckDrawable);
mSelectionImage.setTint(mCheckedIconForegroundColorList);
mCheckDrawable.start();
} else if (selectionModeActive) {
mSelectionImage.setVisibility(View.VISIBLE);
mSelectionImage.setBackground(null);
mSelectionImage.setTint(null);
mSelectionImage.setImageResource(R.drawable.download_circular_selector_transparent);
} else {
mSelectionImage.setBackground(null);
mSelectionImage.setTint(null);
mSelectionImage.setVisibility(View.GONE);
}
}
}
......@@ -37,6 +37,7 @@ class MoreButtonViewHolder extends ListItemViewHolder implements ListMenuButton.
ListItem.OfflineItemListItem offlineItem = (ListItem.OfflineItemListItem) item;
mShareCallback = () -> properties.getShareCallback().onResult(offlineItem.item);
mDeleteCallback = () -> properties.getRemoveCallback().onResult(offlineItem.item);
if (mMore != null) mMore.setClickable(!properties.getSelectionModeActive());
}
// ListMenuButton.Delegate implementation.
......
......@@ -12,6 +12,7 @@ import android.widget.ImageView;
import org.chromium.chrome.R;
import org.chromium.chrome.browser.download.home.list.ListItem;
import org.chromium.chrome.browser.download.home.list.ListPropertyModel;
import org.chromium.chrome.browser.download.home.view.SelectionView;
import org.chromium.components.offline_items_collection.ContentId;
import org.chromium.components.offline_items_collection.OfflineItem;
import org.chromium.components.offline_items_collection.OfflineItemVisuals;
......@@ -22,6 +23,7 @@ import org.chromium.components.offline_items_collection.VisualsCallback;
*/
abstract class ThumbnailAwareViewHolder extends MoreButtonViewHolder implements VisualsCallback {
private final ImageView mThumbnail;
private final SelectionView mSelectionView;
/**
* The {@link ContentId} of the associated thumbnail/request if any.
......@@ -58,6 +60,7 @@ abstract class ThumbnailAwareViewHolder extends MoreButtonViewHolder implements
super(view);
mThumbnail = (ImageView) view.findViewById(R.id.thumbnail);
mSelectionView = itemView.findViewById(R.id.selection);
mWidthPx = thumbnailWidthPx;
mHeightPx = thumbnailHeightPx;
}
......@@ -73,9 +76,27 @@ abstract class ThumbnailAwareViewHolder extends MoreButtonViewHolder implements
OfflineItem offlineItem = ((ListItem.OfflineItemListItem) item).item;
// If we're rebinding the same item, ignore the bind.
if (offlineItem.id.equals(mId)) return;
// TODO(shaktisahu): Add callbacks for selection and open.
if (offlineItem.id.equals(mId) && !selectionStateHasChanged(properties, item)) {
return;
}
if (mSelectionView != null) {
mSelectionView.setSelectionState(
item.selected, properties.getSelectionModeActive(), item.showSelectedAnimation);
}
itemView.setOnLongClickListener(v -> {
properties.getSelectionCallback().onResult(item);
return true;
});
itemView.setOnClickListener(v -> {
if (mSelectionView != null && mSelectionView.isInSelectionMode()) {
properties.getSelectionCallback().onResult(item);
} else {
properties.getOpenCallback().onResult(offlineItem);
}
});
// Clear any associated bitmap from the thumbnail.
if (mId != null) onVisualsChanged(mThumbnail, null);
......@@ -92,6 +113,13 @@ abstract class ThumbnailAwareViewHolder extends MoreButtonViewHolder implements
if (!mIsRequesting) mCancellable = null;
}
private boolean selectionStateHasChanged(ListPropertyModel properties, ListItem item) {
if (mSelectionView == null) return false;
return mSelectionView.isSelected() != item.selected
|| mSelectionView.isInSelectionMode() != properties.getSelectionModeActive();
}
// VisualsCallback implementation.
@Override
public void onVisualsAvailable(ContentId id, OfflineItemVisuals visuals) {
......
// Copyright 2018 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.download.home.view;
import android.content.Context;
import android.support.graphics.drawable.AnimatedVectorDrawableCompat;
import android.util.AttributeSet;
import android.view.LayoutInflater;
import android.widget.FrameLayout;
import org.chromium.chrome.R;
import org.chromium.chrome.browser.widget.TintedImageView;
/**
* A helper UI widget that provides visual feedback when the selection state of the underlying view
* is changed. The widget represents three distinct states : selected, in selection mode and not
* selected. The caller can define the UI behavior at each of these states by subclassing this view.
*/
public class SelectionView extends FrameLayout {
private final TintedImageView mCheck;
private final TintedImageView mCircle;
private final AnimatedVectorDrawableCompat mCheckDrawable;
private boolean mIsSelected;
private boolean mInSelectionMode;
private boolean mShowSelectedAnimation;
/** Constructor for inflating from XML. */
public SelectionView(Context context, AttributeSet attrs) {
super(context, attrs);
LayoutInflater.from(context).inflate(R.layout.list_selection_handle_view, this, true);
mCheck = (TintedImageView) findViewById(R.id.check);
mCircle = (TintedImageView) findViewById(R.id.circle);
mCheckDrawable = AnimatedVectorDrawableCompat.create(
context, R.drawable.ic_check_googblue_24dp_animated);
}
@Override
public boolean isSelected() {
return mIsSelected;
}
/** @return Whether the selection mode is currently active. */
public boolean isInSelectionMode() {
return mInSelectionMode;
}
/**
* Called to inform the view about its current selection state.
* @param selected Whether the item is currently selected.
* @param inSelectionMode Whether we are currently in active selection mode.
* @param showSelectedAnimation Whether the item was recently selected from an unselected state
* and animation should be shown.
*/
public void setSelectionState(
boolean selected, boolean inSelectionMode, boolean showSelectedAnimation) {
mIsSelected = selected;
mInSelectionMode = inSelectionMode;
mShowSelectedAnimation = showSelectedAnimation;
updateView();
}
private void updateView() {
if (mIsSelected) {
mCheck.setVisibility(VISIBLE);
mCircle.setVisibility(GONE);
mCheck.setImageDrawable(mCheckDrawable);
mCheck.getBackground().setLevel(
getResources().getInteger(R.integer.list_item_level_selected));
if (mShowSelectedAnimation) mCheckDrawable.start();
} else if (mInSelectionMode) {
mCheck.setVisibility(GONE);
mCircle.setVisibility(VISIBLE);
} else {
mCheck.setVisibility(GONE);
mCircle.setVisibility(GONE);
}
}
}
......@@ -500,6 +500,7 @@ chrome_java_sources = [
"java/src/org/chromium/chrome/browser/download/home/toolbar/DownloadHomeToolbar.java",
"java/src/org/chromium/chrome/browser/download/home/snackbars/DeleteUndoCoordinator.java",
"java/src/org/chromium/chrome/browser/download/home/snackbars/UndoUiUtils.java",
"java/src/org/chromium/chrome/browser/download/home/view/SelectionView.java",
"java/src/org/chromium/chrome/browser/download/items/DownloadBlockedOfflineContentProvider.java",
"java/src/org/chromium/chrome/browser/download/items/OfflineContentAggregatorFactory.java",
"java/src/org/chromium/chrome/browser/download/items/OfflineContentAggregatorNotificationBridgeUi.java",
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment