Commit 86598099 authored by David Trainor's avatar David Trainor Committed by Commit Bot

Add a chip widget for the new Download Home UI.

This adds the complete MVC structure for a generic horizontal list of
chip views for filter selection.  This will be first used by the new
Download Home UI.

BUG=839489

Screenshot of widget placed in UI: https://drive.google.com/open?id=1Em7em1jg-GnEdyoGLwHUezfgryuJ-cja

Change-Id: I6a7f8a29280c5aeaf01c7be86c15f3fb64ba0288
Reviewed-on: https://chromium-review.googlesource.com/1042813Reviewed-by: default avatarTed Choc <tedchoc@chromium.org>
Commit-Queue: David Trainor <dtrainor@chromium.org>
Cr-Commit-Position: refs/heads/master@{#559224}
parent 0c4c06e3
<?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.
-->
<!-- TODO(crbug.com/839489): Update based on specs. -->
<shape xmlns:android="http://schemas.android.com/apk/res/android">
<solid android:color="@color/google_grey_300" />
<stroke android:width="2dp" android:color="@color/google_blue_300" />
<corners android:radius="3dp" />
<padding android:left="3dp" android:top="3dp" android:right="3dp" android:bottom="3dp" />
</shape>
\ No newline at end of file
<?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.
-->
<!-- TODO(crbug.com/839489): Update values based on specs. -->
<org.chromium.chrome.browser.download.home.filter.chips.ChipView
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:minHeight="32dp"
android:gravity="center"
android:maxLines="1"
android:paddingStart="13dp"
android:paddingEnd="13dp"
android:textAlignment="center"
android:clickable="true"
android:textAppearance="@style/BlueButtonText2"
android:background="@drawable/chip_bg" />
\ No newline at end of file
......@@ -563,4 +563,7 @@
<item type="dimen" name="dialog_fixed_width_minor">100%</item>
<item type="dimen" name="dialog_fixed_height_major">100%</item>
<item type="dimen" name="dialog_fixed_height_minor">100%</item>
<!-- Chip list dimensions -->
<dimen name="chip_list_padding">2.5dp</dimen>
</resources>
// 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.filter;
import org.chromium.base.ObserverList;
import org.chromium.chrome.R;
import org.chromium.chrome.browser.download.home.filter.Filters.FilterType;
import org.chromium.chrome.browser.download.home.filter.chips.Chip;
import org.chromium.chrome.browser.download.home.filter.chips.ChipsProvider;
import java.util.ArrayList;
import java.util.List;
/**
* A {@link ChipsProvider} implementation that wraps a subset of {@link Filters} to be used as a
* chip selector for filtering downloads.
* TODO(dtrainor): Hook in as an observer to the download list data source.
*/
public class FilterChipsProvider implements ChipsProvider {
private static final int INVALID_INDEX = -1;
private final ObserverList<Observer> mObservers = new ObserverList<ChipsProvider.Observer>();
private final List<Chip> mSortedChips = new ArrayList<>();
/** Builds a new FilterChipsBackend. */
public FilterChipsProvider() {
Chip noneChip = new Chip(Filters.NONE, R.string.download_manager_ui_all_downloads,
R.string.download_manager_ui_all_downloads, R.drawable.ic_play_arrow_white_24dp,
() -> onChipSelected(Filters.NONE));
Chip videosChip = new Chip(Filters.VIDEOS, R.string.download_manager_ui_video,
R.string.download_manager_ui_video, R.drawable.ic_play_arrow_white_24dp,
() -> onChipSelected(Filters.VIDEOS));
Chip musicChip = new Chip(Filters.MUSIC, R.string.download_manager_ui_audio,
R.string.download_manager_ui_audio, R.drawable.ic_play_arrow_white_24dp,
() -> onChipSelected(Filters.MUSIC));
Chip imagesChip = new Chip(Filters.IMAGES, R.string.download_manager_ui_images,
R.string.download_manager_ui_images, R.drawable.ic_play_arrow_white_24dp,
() -> onChipSelected(Filters.IMAGES));
Chip sitesChip = new Chip(Filters.SITES, R.string.download_manager_ui_pages,
R.string.download_manager_ui_pages, R.drawable.ic_play_arrow_white_24dp,
() -> onChipSelected(Filters.SITES));
Chip otherChip = new Chip(Filters.OTHER, R.string.download_manager_ui_other,
R.string.download_manager_ui_other, R.drawable.ic_play_arrow_white_24dp,
() -> onChipSelected(Filters.OTHER));
mSortedChips.add(noneChip);
mSortedChips.add(videosChip);
mSortedChips.add(musicChip);
mSortedChips.add(imagesChip);
mSortedChips.add(sitesChip);
mSortedChips.add(otherChip);
}
/**
* Sets whether or not a filter is enabled.
* @param type The type of filter to enable.
* @param enabled Whether or not that filter is enabled.
*/
public void setFilterEnabled(@FilterType int type, boolean enabled) {
int chipIndex = getChipIndex(type);
if (chipIndex == INVALID_INDEX) return;
Chip chip = mSortedChips.get(chipIndex);
if (enabled == chip.enabled) return;
chip.enabled = enabled;
for (Observer observer : mObservers) observer.onChipChanged(chipIndex, chip);
}
/**
* Sets the filter that is currently selected.
* @param type The type of filter to select.
*/
public void setFilterSelected(@FilterType int type) {
for (int i = 0; i < mSortedChips.size(); i++) {
Chip chip = mSortedChips.get(i);
boolean willSelect = chip.id == type;
// Early out if we're already selecting the appropriate Chip type.
if (chip.selected && willSelect) return;
if (chip.selected == willSelect) continue;
chip.selected = willSelect;
for (Observer observer : mObservers) observer.onChipChanged(i, chip);
}
}
// ChipsProvider implementation.
@Override
public void addObserver(Observer observer) {
mObservers.addObserver(observer);
}
@Override
public void removeObserver(Observer observer) {
mObservers.removeObserver(observer);
}
@Override
public List<Chip> getChips() {
return mSortedChips;
}
private void onChipSelected(int id) {
setFilterSelected(id);
// TODO(dtrainor): Notify external sources.
}
private int getChipIndex(@FilterType int type) {
for (int i = 0; i < mSortedChips.size(); i++) {
if (mSortedChips.get(i).id == type) return i;
}
return INVALID_INDEX;
}
}
\ No newline at end of file
// 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.filter;
import android.support.annotation.IntDef;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
/** A list of possible filter types on offlined items. */
public interface Filters {
@Retention(RetentionPolicy.SOURCE)
@IntDef({NONE, VIDEOS, MUSIC, IMAGES, SITES, OTHER})
public @interface FilterType {}
public static final int NONE = 0;
public static final int VIDEOS = 1;
public static final int MUSIC = 2;
public static final int IMAGES = 3;
public static final int SITES = 4;
public static final int OTHER = 5;
}
\ No newline at end of file
// 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.filter.chips;
import android.support.annotation.DrawableRes;
import android.support.annotation.StringRes;
/**
* A generic visual representation of a Chip. Most of the visuals are immutable, but the selection
* and enable states are not.
*/
public class Chip {
/** An id used to identify this chip. */
public final int id;
/** The resource id for the text to show in the chip. */
public final @StringRes int text;
/** The resource id for the accessibility text to use for the chip. */
public final @StringRes int contentDescription;
/** The resource id for the icon to use in the chip. */
public final @DrawableRes int icon;
/** The {@link Runnable} to trigger when this chip is selected by the UI. */
public final Runnable chipSelectedListener;
/** Whether or not this Chip is enabled. */
public boolean enabled;
/** Whether or not this Chip is selected. */
public boolean selected;
/** Builds a new {@link Chip} instance. These properties cannot be changed. */
public Chip(int id, @StringRes int text, @StringRes int contentDescription,
@DrawableRes int icon, Runnable chipSelectedListener) {
this.id = id;
this.text = text;
this.contentDescription = contentDescription;
this.icon = icon;
this.chipSelectedListener = chipSelectedListener;
}
}
\ No newline at end of file
// 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.filter.chips;
import android.content.Context;
import android.content.res.ColorStateList;
import android.graphics.drawable.Drawable;
import android.support.annotation.DrawableRes;
import android.support.v4.graphics.drawable.DrawableCompat;
import android.support.v7.widget.AppCompatTextView;
import android.util.AttributeSet;
import org.chromium.base.ApiCompatibilityUtils;
/**
* A {@link AppCompatTextView} that visually represents a {@link Chip} in a chip list. The way to
* get a properly set up {@link ChipView} with the proper properties would be to inflate
* R.layout.chip.
*/
public class ChipView extends AppCompatTextView {
public ChipView(Context context, AttributeSet attrs) {
super(context, attrs);
}
/**
* Used to effectively set the start compound drawable on the {@link TextView}. Currently there
* is no compat method to handle setting the {@link ColorStateList} for compound drawables.
* @param icon The drawable id that represents the icon to use in the chip.
*/
public void setChipIcon(@DrawableRes int icon) {
Drawable drawable =
DrawableCompat.wrap(ApiCompatibilityUtils.getDrawable(getResources(), icon));
ColorStateList textColor = getTextColors();
if (textColor != null) DrawableCompat.setTintList(drawable.mutate(), textColor);
ApiCompatibilityUtils.setCompoundDrawablesRelativeWithIntrinsicBounds(
this, drawable, null, null, null);
}
}
\ No newline at end of file
// 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.filter.chips;
import android.content.Context;
import android.graphics.Rect;
import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.RecyclerView;
import android.support.v7.widget.RecyclerView.ItemDecoration;
import android.support.v7.widget.RecyclerView.State;
import android.view.View;
import org.chromium.chrome.R;
import org.chromium.chrome.browser.modelutil.RecyclerViewAdapter;
import org.chromium.chrome.browser.modelutil.RecyclerViewAdapter.ViewBinder;
import org.chromium.chrome.browser.modelutil.RecyclerViewModelChangeProcessor;
/**
* The coordinator responsible for managing a list of chips. To get the {@link View} that
* represents this coordinator use {@link #getView()}.
*/
public class ChipsCoordinator implements ChipsProvider.Observer {
private final ChipsProvider mProvider;
private final ChipsModel mModel;
private final RecyclerViewModelChangeProcessor<ChipsModel, ChipsViewBinder.ChipsViewHolder>
mModelChangeProcessor;
private final RecyclerView mView;
/**
* Builds and initializes this coordinator, including all sub-components.
* @param context The {@link Context} to use to grab all of the resources.
* @param provider The source for the underlying Chip state.
*/
public ChipsCoordinator(Context context, ChipsProvider provider) {
assert context != null;
assert provider != null;
mProvider = provider;
// Build the underlying components.
mModel = new ChipsModel();
mView = createView(context);
ViewBinder<ChipsModel, ChipsViewBinder.ChipsViewHolder> viewBinder = new ChipsViewBinder();
RecyclerViewAdapter<ChipsModel, ChipsViewBinder.ChipsViewHolder> adapter =
new RecyclerViewAdapter<>(mModel, viewBinder);
mModelChangeProcessor =
new RecyclerViewModelChangeProcessor<ChipsModel, ChipsViewBinder.ChipsViewHolder>(
adapter);
mView.setAdapter(adapter);
adapter.setViewBinder(viewBinder);
mModel.addObserver(mModelChangeProcessor);
mProvider.addObserver(this);
mModel.setChips(mProvider.getChips());
}
/**
* Destroys the coordinator. This should be called when the coordinator is no longer in use.
* The coordinator should not be used after that point.
*/
public void destroy() {
mProvider.removeObserver(this);
}
/** @return The {@link View} that represents this coordinator. */
public View getView() {
return mView;
}
// ChipsProvider.Observer implementation.
@Override
public void onChipChanged(int position, Chip chip) {
mModel.updateChip(position, chip);
mModel.setChips(mProvider.getChips());
}
private static RecyclerView createView(Context context) {
RecyclerView view = new RecyclerView(context);
view.setLayoutManager(
new LinearLayoutManager(context, LinearLayoutManager.HORIZONTAL, false));
view.addItemDecoration(new SpaceItemDecoration(context));
view.getItemAnimator().setChangeDuration(0);
return view;
}
private static class SpaceItemDecoration extends ItemDecoration {
private final int mPaddingPx;
public SpaceItemDecoration(Context context) {
mPaddingPx = (int) context.getResources().getDimension(R.dimen.chip_list_padding);
}
@Override
public void getItemOffsets(Rect outRect, View view, RecyclerView parent, State state) {
int position = parent.getChildAdapterPosition(view);
boolean isFirst = position == 0;
boolean isLast = position == parent.getAdapter().getItemCount() - 1;
outRect.left = isFirst ? 2 * mPaddingPx : mPaddingPx;
outRect.right = isLast ? 2 * mPaddingPx : mPaddingPx;
}
}
}
\ No newline at end of file
// 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.filter.chips;
import org.chromium.chrome.browser.modelutil.ListObservable;
import java.util.ArrayList;
import java.util.List;
/**
* A model that holds a list of chips to visually represent in the UI.
* TODO(dtrainor): Move to SimpleListObservable once that is supported.
*/
class ChipsModel extends ListObservable {
private final List<Chip> mChips = new ArrayList<>();
// ListObservable implementation.
@Override
public int getItemCount() {
return mChips.size();
}
/**
* Populates the model with a list of {@link Chip}s. The list of chips is copied but the
* internal {@link Chip} objects are not.
* @param chips The new list of {@link Chip}s to store in the model.
*/
public void setChips(List<Chip> chips) {
if (chips.size() == mChips.size()) {
// Avoid complete list rebuilds for the case where the Chip count is the same.
mChips.clear();
mChips.addAll(chips);
notifyItemRangeChanged(0, mChips.size(), null);
} else {
if (!mChips.isEmpty()) {
int size = mChips.size();
mChips.clear();
notifyItemRangeRemoved(0, size);
}
if (!chips.isEmpty()) {
mChips.addAll(chips);
notifyItemRangeInserted(0, mChips.size());
}
}
}
/** Updates an individual item in the list of Chips. */
public void updateChip(int position, Chip chip) {
assert position >= 0 && position < mChips.size();
mChips.set(position, chip);
notifyItemRangeChanged(position, 1, null);
}
/**
* @param position The position of the {@link Chip} in the list of chips to return.
* @return The {@link Chip} stored at {@code position} in the list of chips.
*/
public Chip getItemAt(int position) {
assert position >= 0 && position < mChips.size();
return mChips.get(position);
}
}
\ No newline at end of file
// 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.filter.chips;
import java.util.List;
/** A source of Chips meant to be visually represented by a {@link ChipCoordinator}. */
public interface ChipsProvider {
/** Interface to be called a Chip's state changes. */
interface Observer {
/** Called whenever the list of Chips changes. */
void onChipChanged(int position, Chip chip);
}
/** Adds an {@link Observer} to be notified of Chip state changes. */
void addObserver(Observer observer);
/** Removes an {@link Observer} to be notified of Chip state changes. */
void removeObserver(Observer observer);
/** @return A list of {@link Chip} objects. */
List<Chip> getChips();
}
\ No newline at end of file
// 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.filter.chips;
import android.support.v7.widget.RecyclerView.ViewHolder;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import org.chromium.chrome.R;
import org.chromium.chrome.browser.modelutil.RecyclerViewAdapter.ViewBinder;
/** Responsible for binding the {@link ChipsModel} to {@link ViewHolder}s in the RecyclerView. */
class ChipsViewBinder implements ViewBinder<ChipsModel, ChipsViewBinder.ChipsViewHolder> {
/** The {@link ViewHolder} responsible for reflecting a {@link Chip} to a {@link ChipView}. */
public static class ChipsViewHolder extends ViewHolder {
/**
* Builds a ChipsViewHolder around a specific {@link ChipView}.
* @param itemView
*/
public ChipsViewHolder(View itemView) {
super(itemView);
assert itemView instanceof ChipView;
}
/**
* Pushes the properties of {@code chip} to {@code itemView}.
* @param chip The {@link Chip} to visually reflect in the stored {@link ChipView}.
*/
public void bind(Chip chip) {
ChipView view = (ChipView) itemView;
view.setEnabled(chip.enabled);
view.setSelected(chip.selected);
view.setOnClickListener(v -> chip.chipSelectedListener.run());
view.setChipIcon(chip.icon);
view.setText(chip.text);
view.setContentDescription(view.getContext().getText(chip.contentDescription));
}
}
// ViewBinder implementation.
@Override
public ChipsViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
return new ChipsViewHolder(
LayoutInflater.from(parent.getContext()).inflate(R.layout.chip, null));
}
@Override
public void onBindViewHolder(ChipsModel model, ChipsViewHolder holder, int position) {
holder.bind(model.getItemAt(position));
}
}
\ No newline at end of file
......@@ -416,6 +416,14 @@ chrome_java_sources = [
"java/src/org/chromium/chrome/browser/download/OMADownloadHandler.java",
"java/src/org/chromium/chrome/browser/download/SystemDownloadNotifier.java",
"java/src/org/chromium/chrome/browser/download/SystemDownloadNotifier2.java",
"java/src/org/chromium/chrome/browser/download/home/filter/Filters.java",
"java/src/org/chromium/chrome/browser/download/home/filter/FilterChipsProvider.java",
"java/src/org/chromium/chrome/browser/download/home/filter/chips/Chip.java",
"java/src/org/chromium/chrome/browser/download/home/filter/chips/ChipsCoordinator.java",
"java/src/org/chromium/chrome/browser/download/home/filter/chips/ChipsModel.java",
"java/src/org/chromium/chrome/browser/download/home/filter/chips/ChipsProvider.java",
"java/src/org/chromium/chrome/browser/download/home/filter/chips/ChipsViewBinder.java",
"java/src/org/chromium/chrome/browser/download/home/filter/chips/ChipView.java",
"java/src/org/chromium/chrome/browser/download/items/OfflineContentAggregatorFactory.java",
"java/src/org/chromium/chrome/browser/download/items/OfflineContentAggregatorNotificationBridgeUi.java",
"java/src/org/chromium/chrome/browser/download/items/OfflineContentAggregatorNotificationBridgeUiFactory.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