Commit 3a35b652 authored by Shakti Sahu's avatar Shakti Sahu Committed by Commit Bot

Download Home : Added section titles

1- Modified the mutator logic to maintain the items in a date/section
   ordered hierarchical form
2- The mutator will add views for sections, section separators and
   date separators.

Several other smaller details such as :
1 - First section should have less spacing between the preceding date.
2 - The last date/section shouldn't have a separator.


Bug: 866307
Change-Id: I6eca116c26f403760160d016655fad3c05278c3a
Reviewed-on: https://chromium-review.googlesource.com/1151062
Commit-Queue: Shakti Sahu <shaktisahu@chromium.org>
Reviewed-by: default avatarTheresa <twellington@chromium.org>
Cr-Commit-Position: refs/heads/master@{#581045}
parent bc9d7d70
...@@ -7,11 +7,10 @@ ...@@ -7,11 +7,10 @@
<TextView xmlns:android="http://schemas.android.com/apk/res/android" <TextView xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:paddingTop="4dp" android:paddingTop="@dimen/download_manager_section_title_padding_top"
android:paddingBottom="4dp" android:paddingBottom="@dimen/download_manager_section_title_padding_bottom"
android:paddingStart="12dp" android:paddingStart="@dimen/list_item_default_margin"
android:maxLines="1" android:maxLines="1"
android:gravity="start|center_vertical" android:gravity="start|center_vertical"
android:textAppearance="@style/BlackTitle2" android:textAppearance="@style/BlackTitle1"
android:textAlignment="viewStart" android:textAlignment="viewStart" />
/> \ No newline at end of file
\ 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.
-->
<View
xmlns:android="http://schemas.android.com/apk/res/android"
style="@style/Divider"
android:minHeight="2dp" />
\ 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.
-->
<TextView
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:paddingTop="@dimen/download_manager_section_title_padding_top"
android:paddingBottom="@dimen/download_manager_section_title_padding_bottom"
android:paddingStart="@dimen/list_item_default_margin"
android:gravity="start|center_vertical"
android:textAppearance="@style/BlackHint2"
android:textAlignment="viewStart" />
\ 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.
-->
<FrameLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:background="@android:color/white"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<View
style="@style/Divider"
android:layout_marginStart="16dp" />
</FrameLayout>
\ No newline at end of file
...@@ -584,6 +584,10 @@ ...@@ -584,6 +584,10 @@
<dimen name="download_manager_prefetch_thumbnail_size">114dp</dimen> <dimen name="download_manager_prefetch_thumbnail_size">114dp</dimen>
<dimen name="download_manager_prefetch_horizontal_margin">16dp</dimen> <dimen name="download_manager_prefetch_horizontal_margin">16dp</dimen>
<dimen name="download_manager_prefetch_vertical_margin">12dp</dimen> <dimen name="download_manager_prefetch_vertical_margin">12dp</dimen>
<dimen name="download_manager_section_title_padding_top">16dp</dimen>
<dimen name="download_manager_section_title_padding_top_condensed">0dp</dimen>
<dimen name="download_manager_section_title_padding_bottom">0dp</dimen>
<dimen name="download_manager_section_title_padding_image">8dp</dimen>
<!-- Navigation history popup dimensions --> <!-- Navigation history popup dimensions -->
<dimen name="navigation_popup_width">312dp</dimen> <dimen name="navigation_popup_width">312dp</dimen>
......
...@@ -39,7 +39,7 @@ public class Filters { ...@@ -39,7 +39,7 @@ public class Filters {
* @param filter The {@link OfflineItem#filter} type to convert. * @param filter The {@link OfflineItem#filter} type to convert.
* @return The corresponding {@link FilterType}. * @return The corresponding {@link FilterType}.
*/ */
public static @FilterType int fromOfflineItem(@OfflineItemFilter int filter) { public static @FilterType Integer fromOfflineItem(@OfflineItemFilter int filter) {
switch (filter) { switch (filter) {
case OfflineItemFilter.FILTER_PAGE: case OfflineItemFilter.FILTER_PAGE:
return FilterType.SITES; return FilterType.SITES;
......
...@@ -8,13 +8,14 @@ import android.support.annotation.Nullable; ...@@ -8,13 +8,14 @@ import android.support.annotation.Nullable;
import android.support.v7.util.BatchingListUpdateCallback; import android.support.v7.util.BatchingListUpdateCallback;
import android.support.v7.util.ListUpdateCallback; import android.support.v7.util.ListUpdateCallback;
import org.chromium.chrome.browser.modelutil.ListObservableImpl; import org.chromium.chrome.browser.modelutil.SimpleListObservable;
/** /**
* Helper class to batch updates to ListObservable before notifying observers. * Helper class to batch updates to SimpleListObservable before notifying observers.
* @see BatchingListUpdateCallback * @see BatchingListUpdateCallback
* @param <T> The object type that this class manages in a list.
*/ */
public abstract class BatchListObservable extends ListObservableImpl<Void> { public abstract class BatchListObservable<T> extends SimpleListObservable<T> {
final BatchingListUpdateCallback mBatchingCallback; final BatchingListUpdateCallback mBatchingCallback;
/** Creates a new BatchListObservable instance. */ /** Creates a new BatchListObservable instance. */
......
...@@ -72,7 +72,7 @@ class DateOrderedListMediator { ...@@ -72,7 +72,7 @@ class DateOrderedListMediator {
boolean selected = mSelectionDelegate.isItemSelected(item); boolean selected = mSelectionDelegate.isItemSelected(item);
item.showSelectedAnimation = selected && !item.selected; item.showSelectedAnimation = selected && !item.selected;
item.selected = selected; item.selected = selected;
mModel.setItem(i, item); mModel.update(i, item);
} }
mModel.dispatchLastEvent(); mModel.dispatchLastEvent();
mModel.getProperties().setValue( mModel.getProperties().setValue(
......
...@@ -14,6 +14,10 @@ import java.util.Date; ...@@ -14,6 +14,10 @@ import java.util.Date;
/** An abstract class that represents a variety of possible list items to show in downloads home. */ /** An abstract class that represents a variety of possible list items to show in downloads home. */
public abstract class ListItem { public abstract class ListItem {
private static final long DATE_SEPARATOR_HASH_CODE_OFFSET = 10;
private static final long SECTION_SEPARATOR_HASH_CODE_OFFSET = 100;
private static final long SECTION_HEADER_HASH_CODE_OFFSET = 1000;
public final long stableId; public final long stableId;
/** Indicates that we are in multi-select mode and the item is currently selected. */ /** Indicates that we are in multi-select mode and the item is currently selected. */
...@@ -66,6 +70,65 @@ public abstract class ListItem { ...@@ -66,6 +70,65 @@ public abstract class ListItem {
} }
} }
/** A {@link ListItem} representing a section header. */
public static class SectionHeaderListItem extends DateListItem {
public final int filter;
public boolean isFirstSectionOfDay;
/**
* Creates a {@link SectionHeaderListItem} instance for a given {@code filter} and
* {@code timestamp}.
*/
public SectionHeaderListItem(int filter, long timestamp) {
super(generateStableId(timestamp, filter), new Date(timestamp));
this.filter = filter;
}
@VisibleForTesting
static long generateStableId(long timestamp, int filter) {
long hash = new Date(timestamp).hashCode();
return hash + filter + SECTION_HEADER_HASH_CODE_OFFSET;
}
}
/** A {@link ListItem} representing a divider that separates sections and dates. */
public static class SeparatorViewListItem extends DateListItem {
private final boolean mIsDateDivider;
/**
* Creates a separator to be shown at the end of a given date.
* @param timestamp The date corresponding to this group of downloads.
*/
public SeparatorViewListItem(long timestamp) {
super(generateStableId(timestamp), new Date(timestamp));
mIsDateDivider = true;
}
/**
* Creates a separator to be shown at the end of a section for a given section on a given
* date.
* @param timestamp The date corresponding to the section.
* @param filter The type of downloads contained in this section.
*/
public SeparatorViewListItem(long timestamp, int filter) {
super(generateStableId(timestamp, filter), new Date(timestamp));
mIsDateDivider = false;
}
/** Whether this view represents a date divider. */
public boolean isDateDivider() {
return mIsDateDivider;
}
private static long generateStableId(long timestamp) {
return ((long) (new Date(timestamp).hashCode())) + DATE_SEPARATOR_HASH_CODE_OFFSET;
}
private static long generateStableId(long timestamp, int filter) {
return generateStableId(timestamp) + filter + SECTION_SEPARATOR_HASH_CODE_OFFSET;
}
}
/** A {@link ListItem} that involves a {@link OfflineItem}. */ /** A {@link ListItem} that involves a {@link OfflineItem}. */
public static class OfflineItemListItem extends DateListItem { public static class OfflineItemListItem extends DateListItem {
public final OfflineItem item; public final OfflineItem item;
......
...@@ -4,20 +4,14 @@ ...@@ -4,20 +4,14 @@
package org.chromium.chrome.browser.download.home.list; package org.chromium.chrome.browser.download.home.list;
import org.chromium.chrome.browser.modelutil.ListObservable;
import org.chromium.chrome.browser.modelutil.PropertyModel; import org.chromium.chrome.browser.modelutil.PropertyModel;
import org.chromium.chrome.browser.modelutil.SimpleList;
import java.util.ArrayList;
import java.util.List;
/** /**
* This model represents the data required to build a list UI around a set of {@link ListItem}s. * This model represents the data required to build a list UI around a set of {@link ListItem}s.
* This includes (1) a {@link ListObservable} implementation and (2) exposing a * This includes (1) a {@link BatchListObservable} implementation and (2) exposing a
* {@link PropertyModel} for shared item properties and general list information. * {@link PropertyModel} for shared item properties and general list information.
*/ */
class ListItemModel extends BatchListObservable implements SimpleList<ListItem> { class ListItemModel extends BatchListObservable<ListItem> {
private final List<ListItem> mItems = new ArrayList<>();
private final PropertyModel mListProperties = new PropertyModel(ListProperties.ALL_KEYS); private final PropertyModel mListProperties = new PropertyModel(ListProperties.ALL_KEYS);
/** /**
...@@ -27,33 +21,4 @@ class ListItemModel extends BatchListObservable implements SimpleList<ListItem> ...@@ -27,33 +21,4 @@ class ListItemModel extends BatchListObservable implements SimpleList<ListItem>
public PropertyModel getProperties() { public PropertyModel getProperties() {
return mListProperties; return mListProperties;
} }
}
/** Adds {@code item} to this list at {@code index}. */
public void addItem(int index, ListItem item) {
mItems.add(index, item);
notifyItemInserted(index);
}
/** Removes the {@link ListItem} at {@code index}. */
public void removeItem(int index) {
mItems.remove(index);
notifyItemRemoved(index);
}
/** Sets the {@link ListItem} at {@code index} to {@code item}. */
public void setItem(int index, ListItem item) {
mItems.set(index, item);
notifyItemChanged(index);
}
// SimpleList implementation.
@Override
public ListItem get(int index) {
return mItems.get(index);
}
@Override
public int size() {
return mItems.size();
}
}
\ No newline at end of file
...@@ -5,7 +5,9 @@ ...@@ -5,7 +5,9 @@
package org.chromium.chrome.browser.download.home.list; package org.chromium.chrome.browser.download.home.list;
import android.support.annotation.IntDef; import android.support.annotation.IntDef;
import android.support.annotation.StringRes;
import org.chromium.chrome.R;
import org.chromium.chrome.browser.download.home.list.ListItem.DateListItem; import org.chromium.chrome.browser.download.home.list.ListItem.DateListItem;
import org.chromium.chrome.browser.download.home.list.ListItem.OfflineItemListItem; import org.chromium.chrome.browser.download.home.list.ListItem.OfflineItemListItem;
import org.chromium.chrome.browser.download.home.list.ListItem.ViewListItem; import org.chromium.chrome.browser.download.home.list.ListItem.ViewListItem;
...@@ -19,7 +21,8 @@ import java.lang.annotation.RetentionPolicy; ...@@ -19,7 +21,8 @@ import java.lang.annotation.RetentionPolicy;
public class ListUtils { public class ListUtils {
/** The potential types of list items that could be displayed. */ /** The potential types of list items that could be displayed. */
@IntDef({ViewType.DATE, ViewType.IN_PROGRESS, ViewType.GENERIC, ViewType.VIDEO, ViewType.IMAGE, @IntDef({ViewType.DATE, ViewType.IN_PROGRESS, ViewType.GENERIC, ViewType.VIDEO, ViewType.IMAGE,
ViewType.CUSTOM_VIEW, ViewType.PREFETCH}) ViewType.CUSTOM_VIEW, ViewType.PREFETCH, ViewType.SECTION_HEADER,
ViewType.SEPARATOR_DATE, ViewType.SEPARATOR_SECTION})
@Retention(RetentionPolicy.SOURCE) @Retention(RetentionPolicy.SOURCE)
public @interface ViewType { public @interface ViewType {
int DATE = 0; int DATE = 0;
...@@ -29,6 +32,9 @@ public class ListUtils { ...@@ -29,6 +32,9 @@ public class ListUtils {
int IMAGE = 4; int IMAGE = 4;
int CUSTOM_VIEW = 5; int CUSTOM_VIEW = 5;
int PREFETCH = 6; int PREFETCH = 6;
int SECTION_HEADER = 7;
int SEPARATOR_DATE = 8;
int SEPARATOR_SECTION = 9;
} }
private ListUtils() {} private ListUtils() {}
...@@ -42,6 +48,12 @@ public class ListUtils { ...@@ -42,6 +48,12 @@ public class ListUtils {
*/ */
public static @ViewType int getViewTypeForItem(ListItem item) { public static @ViewType int getViewTypeForItem(ListItem item) {
if (item instanceof ViewListItem) return ViewType.CUSTOM_VIEW; if (item instanceof ViewListItem) return ViewType.CUSTOM_VIEW;
if (item instanceof ListItem.SectionHeaderListItem) return ViewType.SECTION_HEADER;
if (item instanceof ListItem.SeparatorViewListItem) {
ListItem.SeparatorViewListItem separator = (ListItem.SeparatorViewListItem) item;
return separator.isDateDivider() ? ViewType.SEPARATOR_DATE : ViewType.SEPARATOR_SECTION;
}
if (item instanceof DateListItem) { if (item instanceof DateListItem) {
if (item instanceof OfflineItemListItem) { if (item instanceof OfflineItemListItem) {
OfflineItemListItem offlineItem = (OfflineItemListItem) item; OfflineItemListItem offlineItem = (OfflineItemListItem) item;
...@@ -76,6 +88,28 @@ public class ListUtils { ...@@ -76,6 +88,28 @@ public class ListUtils {
return ViewType.GENERIC; return ViewType.GENERIC;
} }
/**
* @return The id of the string to be displayed as the section header for the given filter.
*/
public static @StringRes int getTextForSection(int filter) {
switch (filter) {
case OfflineItemFilter.FILTER_PAGE:
return R.string.download_manager_ui_pages;
case OfflineItemFilter.FILTER_IMAGE:
return R.string.download_manager_ui_images;
case OfflineItemFilter.FILTER_VIDEO:
return R.string.download_manager_ui_video;
case OfflineItemFilter.FILTER_AUDIO:
return R.string.download_manager_ui_audio;
case OfflineItemFilter.FILTER_OTHER:
return R.string.download_manager_ui_other;
case OfflineItemFilter.FILTER_DOCUMENT:
return R.string.download_manager_ui_documents;
default:
return R.string.download_manager_ui_all_downloads;
}
}
/** /**
* Analyzes a {@link ListItem} and finds the best span size based on the current state. Span * Analyzes a {@link ListItem} and finds the best span size based on the current state. Span
* size determines how many columns this {@link ListItem}'s {@link View} will take up in the * size determines how many columns this {@link ListItem}'s {@link View} will take up in the
......
...@@ -43,6 +43,12 @@ public abstract class ListItemViewHolder extends ViewHolder { ...@@ -43,6 +43,12 @@ public abstract class ListItemViewHolder extends ViewHolder {
return new CustomViewHolder(parent); return new CustomViewHolder(parent);
case ListUtils.ViewType.PREFETCH: case ListUtils.ViewType.PREFETCH:
return PrefetchViewHolder.create(parent); return PrefetchViewHolder.create(parent);
case ListUtils.ViewType.SECTION_HEADER:
return SectionTitleViewHolder.create(parent);
case ListUtils.ViewType.SEPARATOR_DATE:
return SeparatorViewHolder.create(parent, true);
case ListUtils.ViewType.SEPARATOR_SECTION:
return SeparatorViewHolder.create(parent, false);
} }
assert false; assert false;
......
// 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.list.holder;
import android.content.res.Resources;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;
import org.chromium.chrome.R;
import org.chromium.chrome.browser.download.home.list.ListItem;
import org.chromium.chrome.browser.download.home.list.ListUtils;
import org.chromium.chrome.browser.modelutil.PropertyModel;
import org.chromium.components.offline_items_collection.OfflineItemFilter;
/**
* A {@link ViewHolder} specifically meant to display a section header.
*/
public class SectionTitleViewHolder extends ListItemViewHolder {
/** Create a new {@link SectionTitleViewHolder} instance. */
public static SectionTitleViewHolder create(ViewGroup parent) {
View view = LayoutInflater.from(parent.getContext())
.inflate(R.layout.download_manager_section_header, null);
return new SectionTitleViewHolder(view);
}
private SectionTitleViewHolder(View view) {
super(view);
}
// ListItemViewHolder implementation.
@Override
public void bind(PropertyModel properties, ListItem item) {
ListItem.SectionHeaderListItem sectionItem = (ListItem.SectionHeaderListItem) item;
((TextView) itemView).setText(ListUtils.getTextForSection(sectionItem.filter));
boolean isPhoto = sectionItem.filter == OfflineItemFilter.FILTER_IMAGE;
Resources resources = itemView.getContext().getResources();
int paddingTop = resources.getDimensionPixelSize(isPhoto
? R.dimen.download_manager_section_title_padding_image
: R.dimen.download_manager_section_title_padding_top);
int paddingBottom = resources.getDimensionPixelSize(isPhoto
? R.dimen.download_manager_section_title_padding_image
: R.dimen.download_manager_section_title_padding_bottom);
if (sectionItem.isFirstSectionOfDay) {
paddingTop = resources.getDimensionPixelSize(
R.dimen.download_manager_section_title_padding_top_condensed);
}
itemView.setPadding(
itemView.getPaddingLeft(), paddingTop, itemView.getPaddingRight(), paddingBottom);
}
}
// 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.list.holder;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import org.chromium.chrome.R;
import org.chromium.chrome.browser.download.home.list.ListItem;
import org.chromium.chrome.browser.modelutil.PropertyModel;
/**
* A {@link ViewHolder} specifically meant to display a separator.
*/
public class SeparatorViewHolder extends ListItemViewHolder {
/**
* Creates a new {@link SeparatorViewHolder} instance.
* @param isDateDivider Whether the divider is between dates or individual sections.
*/
public static SeparatorViewHolder create(ViewGroup parent, boolean isDateDivider) {
View dividerView =
LayoutInflater.from(parent.getContext())
.inflate(isDateDivider ? R.layout.download_manager_date_separator
: R.layout.download_manager_section_separator,
null);
return new SeparatorViewHolder(dividerView);
}
private SeparatorViewHolder(View view) {
super(view);
}
// ListItemViewHolder implementation.
@Override
public void bind(PropertyModel properties, ListItem item) {}
}
...@@ -495,6 +495,8 @@ chrome_java_sources = [ ...@@ -495,6 +495,8 @@ chrome_java_sources = [
"java/src/org/chromium/chrome/browser/download/home/list/holder/ListItemViewHolder.java", "java/src/org/chromium/chrome/browser/download/home/list/holder/ListItemViewHolder.java",
"java/src/org/chromium/chrome/browser/download/home/list/holder/MoreButtonViewHolder.java", "java/src/org/chromium/chrome/browser/download/home/list/holder/MoreButtonViewHolder.java",
"java/src/org/chromium/chrome/browser/download/home/list/holder/PrefetchViewHolder.java", "java/src/org/chromium/chrome/browser/download/home/list/holder/PrefetchViewHolder.java",
"java/src/org/chromium/chrome/browser/download/home/list/holder/SectionTitleViewHolder.java",
"java/src/org/chromium/chrome/browser/download/home/list/holder/SeparatorViewHolder.java",
"java/src/org/chromium/chrome/browser/download/home/list/holder/ThumbnailAwareViewHolder.java", "java/src/org/chromium/chrome/browser/download/home/list/holder/ThumbnailAwareViewHolder.java",
"java/src/org/chromium/chrome/browser/download/home/list/holder/VideoViewHolder.java", "java/src/org/chromium/chrome/browser/download/home/list/holder/VideoViewHolder.java",
"java/src/org/chromium/chrome/browser/download/home/list/ListProperties.java", "java/src/org/chromium/chrome/browser/download/home/list/ListProperties.java",
......
...@@ -18,17 +18,17 @@ import org.chromium.components.offline_items_collection.OfflineItemFilter; ...@@ -18,17 +18,17 @@ import org.chromium.components.offline_items_collection.OfflineItemFilter;
public class FiltersTest { public class FiltersTest {
@Test @Test
public void testFilterConversions() { public void testFilterConversions() {
Assert.assertEquals( Assert.assertEquals(Integer.valueOf(Filters.FilterType.SITES),
Filters.FilterType.SITES, Filters.fromOfflineItem(OfflineItemFilter.FILTER_PAGE)); Filters.fromOfflineItem(OfflineItemFilter.FILTER_PAGE));
Assert.assertEquals( Assert.assertEquals(Integer.valueOf(Filters.FilterType.VIDEOS),
Filters.FilterType.VIDEOS, Filters.fromOfflineItem(OfflineItemFilter.FILTER_VIDEO)); Filters.fromOfflineItem(OfflineItemFilter.FILTER_VIDEO));
Assert.assertEquals( Assert.assertEquals(Integer.valueOf(Filters.FilterType.MUSIC),
Filters.FilterType.MUSIC, Filters.fromOfflineItem(OfflineItemFilter.FILTER_AUDIO)); Filters.fromOfflineItem(OfflineItemFilter.FILTER_AUDIO));
Assert.assertEquals( Assert.assertEquals(Integer.valueOf(Filters.FilterType.IMAGES),
Filters.FilterType.IMAGES, Filters.fromOfflineItem(OfflineItemFilter.FILTER_IMAGE)); Filters.fromOfflineItem(OfflineItemFilter.FILTER_IMAGE));
Assert.assertEquals( Assert.assertEquals(Integer.valueOf(Filters.FilterType.OTHER),
Filters.FilterType.OTHER, Filters.fromOfflineItem(OfflineItemFilter.FILTER_OTHER)); Filters.fromOfflineItem(OfflineItemFilter.FILTER_OTHER));
Assert.assertEquals(Filters.FilterType.OTHER, Assert.assertEquals(Integer.valueOf(Filters.FilterType.OTHER),
Filters.fromOfflineItem(OfflineItemFilter.FILTER_DOCUMENT)); Filters.fromOfflineItem(OfflineItemFilter.FILTER_DOCUMENT));
} }
} }
\ No newline at end of file
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