Commit dab0f2ec authored by Shakti Sahu's avatar Shakti Sahu Committed by Commit Bot

Read Later : Add section header to reading list

This CL adds reading list read and unread section headers to the
reading list page in bookmarks.

Bug: 1139070
Change-Id: If2d3d02bb4b354f1e2d1b5269fa89875e46a6562
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2518426
Commit-Queue: Shakti Sahu <shaktisahu@chromium.org>
Reviewed-by: default avatarTheresa  <twellington@chromium.org>
Reviewed-by: default avatarXing Liu <xingliu@chromium.org>
Cr-Commit-Position: refs/heads/master@{#826598}
parent ecbd53c5
...@@ -805,6 +805,7 @@ chrome_java_resources = [ ...@@ -805,6 +805,7 @@ chrome_java_resources = [
"java/res/layout/bookmark_folder_select_activity.xml", "java/res/layout/bookmark_folder_select_activity.xml",
"java/res/layout/bookmark_item_row.xml", "java/res/layout/bookmark_item_row.xml",
"java/res/layout/bookmark_main.xml", "java/res/layout/bookmark_main.xml",
"java/res/layout/bookmark_section_header.xml",
"java/res/layout/bookmark_widget.xml", "java/res/layout/bookmark_widget.xml",
"java/res/layout/bookmark_widget_icons_only.xml", "java/res/layout/bookmark_widget_icons_only.xml",
"java/res/layout/bookmark_widget_item.xml", "java/res/layout/bookmark_widget_item.xml",
......
...@@ -164,6 +164,7 @@ chrome_java_sources = [ ...@@ -164,6 +164,7 @@ chrome_java_sources = [
"java/src/org/chromium/chrome/browser/bookmarks/BookmarkUIState.java", "java/src/org/chromium/chrome/browser/bookmarks/BookmarkUIState.java",
"java/src/org/chromium/chrome/browser/bookmarks/BookmarkUndoController.java", "java/src/org/chromium/chrome/browser/bookmarks/BookmarkUndoController.java",
"java/src/org/chromium/chrome/browser/bookmarks/BookmarkUtils.java", "java/src/org/chromium/chrome/browser/bookmarks/BookmarkUtils.java",
"java/src/org/chromium/chrome/browser/bookmarks/ReadingListSectionHeader.java",
"java/src/org/chromium/chrome/browser/bookmarks/bottomsheet/BookmarkBottomSheetContent.java", "java/src/org/chromium/chrome/browser/bookmarks/bottomsheet/BookmarkBottomSheetContent.java",
"java/src/org/chromium/chrome/browser/bookmarks/bottomsheet/BookmarkBottomSheetCoordinator.java", "java/src/org/chromium/chrome/browser/bookmarks/bottomsheet/BookmarkBottomSheetCoordinator.java",
"java/src/org/chromium/chrome/browser/bookmarkswidget/BookmarkWidgetProvider.java", "java/src/org/chromium/chrome/browser/bookmarkswidget/BookmarkWidgetProvider.java",
......
...@@ -15,6 +15,7 @@ chrome_junit_test_java_sources = [ ...@@ -15,6 +15,7 @@ chrome_junit_test_java_sources = [
"junit/src/org/chromium/chrome/browser/background_sync/BackgroundSyncGooglePlayServicesCheckerTest.java", "junit/src/org/chromium/chrome/browser/background_sync/BackgroundSyncGooglePlayServicesCheckerTest.java",
"junit/src/org/chromium/chrome/browser/background_sync/PeriodicBackgroundSyncChromeWakeUpTaskTest.java", "junit/src/org/chromium/chrome/browser/background_sync/PeriodicBackgroundSyncChromeWakeUpTaskTest.java",
"junit/src/org/chromium/chrome/browser/background_task_scheduler/NativeBackgroundTaskTest.java", "junit/src/org/chromium/chrome/browser/background_task_scheduler/NativeBackgroundTaskTest.java",
"junit/src/org/chromium/chrome/browser/bookmarks/ReadingListSectionHeaderTest.java",
"junit/src/org/chromium/chrome/browser/browserservices/ClearDataDialogResultRecorderTest.java", "junit/src/org/chromium/chrome/browser/browserservices/ClearDataDialogResultRecorderTest.java",
"junit/src/org/chromium/chrome/browser/browserservices/ClientAppBroadcastReceiverTest.java", "junit/src/org/chromium/chrome/browser/browserservices/ClientAppBroadcastReceiverTest.java",
"junit/src/org/chromium/chrome/browser/browserservices/ClientAppDataRegisterTest.java", "junit/src/org/chromium/chrome/browser/browserservices/ClientAppDataRegisterTest.java",
......
<?xml version="1.0" encoding="utf-8"?>
<!-- Copyright 2020 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. -->
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<TextView
android:id="@+id/title"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
<TextView
android:id="@+id/description"
android:layout_width="match_parent"
android:layout_height="wrap_content"
style="@style/TextAppearance.TextMedium.Secondary" />
</LinearLayout>
\ No newline at end of file
...@@ -185,7 +185,8 @@ public class BookmarkBridge { ...@@ -185,7 +185,8 @@ public class BookmarkBridge {
private long mDateAdded; private long mDateAdded;
private boolean mRead; private boolean mRead;
private BookmarkItem(BookmarkId id, String title, String url, boolean isFolder, @VisibleForTesting
public BookmarkItem(BookmarkId id, String title, String url, boolean isFolder,
BookmarkId parentId, boolean isEditable, boolean isManaged, long dateAdded, BookmarkId parentId, boolean isEditable, boolean isManaged, long dateAdded,
boolean read) { boolean read) {
mId = id; mId = id;
......
...@@ -8,7 +8,9 @@ import android.content.Context; ...@@ -8,7 +8,9 @@ import android.content.Context;
import android.text.TextUtils; import android.text.TextUtils;
import android.view.LayoutInflater; import android.view.LayoutInflater;
import android.view.MotionEvent; import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup; import android.view.ViewGroup;
import android.widget.TextView;
import androidx.annotation.LayoutRes; import androidx.annotation.LayoutRes;
import androidx.annotation.VisibleForTesting; import androidx.annotation.VisibleForTesting;
...@@ -136,6 +138,11 @@ class BookmarkItemsAdapter extends DragReorderableListAdapter<BookmarkListEntry> ...@@ -136,6 +138,11 @@ class BookmarkItemsAdapter extends DragReorderableListAdapter<BookmarkListEntry>
assert topLevelFoldersShowing(); assert topLevelFoldersShowing();
} }
} }
if (mCurrentFolder.getType() == BookmarkType.READING_LIST
&& mDelegate.getCurrentState() != BookmarkUIState.STATE_SEARCHING) {
ReadingListSectionHeader.maybeSortAndInsertSectionHeaders(mElements, mContext);
}
notifyDataSetChanged(); notifyDataSetChanged();
} }
...@@ -173,6 +180,8 @@ class BookmarkItemsAdapter extends DragReorderableListAdapter<BookmarkListEntry> ...@@ -173,6 +180,8 @@ class BookmarkItemsAdapter extends DragReorderableListAdapter<BookmarkListEntry>
return mPromoHeaderManager.createPersonalizedSigninAndSyncPromoHolder(parent); return mPromoHeaderManager.createPersonalizedSigninAndSyncPromoHolder(parent);
case ViewType.SYNC_PROMO: case ViewType.SYNC_PROMO:
return mPromoHeaderManager.createSyncPromoHolder(parent); return mPromoHeaderManager.createSyncPromoHolder(parent);
case ViewType.SECTION_HEADER:
return createSectionHeaderViewHolder(parent, viewType);
case ViewType.FOLDER: case ViewType.FOLDER:
return createViewHolderHelper(parent, R.layout.bookmark_folder_row); return createViewHolderHelper(parent, R.layout.bookmark_folder_row);
case ViewType.BOOKMARK: case ViewType.BOOKMARK:
...@@ -195,6 +204,8 @@ class BookmarkItemsAdapter extends DragReorderableListAdapter<BookmarkListEntry> ...@@ -195,6 +204,8 @@ class BookmarkItemsAdapter extends DragReorderableListAdapter<BookmarkListEntry>
} else if (holder.getItemViewType() == ViewType.PERSONALIZED_SYNC_PROMO) { } else if (holder.getItemViewType() == ViewType.PERSONALIZED_SYNC_PROMO) {
PersonalizedSigninPromoView view = (PersonalizedSigninPromoView) holder.itemView; PersonalizedSigninPromoView view = (PersonalizedSigninPromoView) holder.itemView;
mPromoHeaderManager.setupPersonalizedSyncPromo(view); mPromoHeaderManager.setupPersonalizedSyncPromo(view);
} else if (holder.getItemViewType() == ViewType.SECTION_HEADER) {
bindSectionHeaderViewHolder(holder.itemView, getItemByPosition(position));
} else if (BookmarkListEntry.isBookmarkEntry(holder.getItemViewType())) { } else if (BookmarkListEntry.isBookmarkEntry(holder.getItemViewType())) {
BookmarkRow row = ((BookmarkRow) holder.itemView); BookmarkRow row = ((BookmarkRow) holder.itemView);
BookmarkId id = getIdByPosition(position); BookmarkId id = getIdByPosition(position);
...@@ -217,6 +228,23 @@ class BookmarkItemsAdapter extends DragReorderableListAdapter<BookmarkListEntry> ...@@ -217,6 +228,23 @@ class BookmarkItemsAdapter extends DragReorderableListAdapter<BookmarkListEntry>
} }
} }
private ViewHolder createSectionHeaderViewHolder(ViewGroup parent, @ViewType int viewType) {
ViewGroup sectionHeader = (ViewGroup) LayoutInflater.from(parent.getContext())
.inflate(R.layout.bookmark_section_header, parent, false);
// ViewHolder is abstract and it cannot be instantiated directly.
return new ViewHolder(sectionHeader) {};
}
private void bindSectionHeaderViewHolder(View view, BookmarkListEntry listItem) {
TextView title = view.findViewById(R.id.title);
TextView description = view.findViewById(R.id.description);
title.setText(listItem.getHeaderTitle());
description.setText(listItem.getHeaderDescription());
description.setVisibility(
TextUtils.isEmpty(listItem.getHeaderDescription()) ? View.GONE : View.VISIBLE);
}
@Override @Override
public void onViewRecycled(ViewHolder holder) { public void onViewRecycled(ViewHolder holder) {
switch (holder.getItemViewType()) { switch (holder.getItemViewType()) {
......
...@@ -4,9 +4,14 @@ ...@@ -4,9 +4,14 @@
package org.chromium.chrome.browser.bookmarks; package org.chromium.chrome.browser.bookmarks;
import android.content.Context;
import androidx.annotation.IntDef; import androidx.annotation.IntDef;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
import androidx.annotation.StringRes;
import org.chromium.base.ContextUtils;
import org.chromium.chrome.R;
import org.chromium.chrome.browser.bookmarks.BookmarkBridge.BookmarkItem; import org.chromium.chrome.browser.bookmarks.BookmarkBridge.BookmarkItem;
import java.lang.annotation.Retention; import java.lang.annotation.Retention;
...@@ -23,7 +28,8 @@ final class BookmarkListEntry { ...@@ -23,7 +28,8 @@ final class BookmarkListEntry {
*/ */
@Retention(RetentionPolicy.SOURCE) @Retention(RetentionPolicy.SOURCE)
@IntDef({ViewType.INVALID, ViewType.PERSONALIZED_SIGNIN_PROMO, ViewType.PERSONALIZED_SYNC_PROMO, @IntDef({ViewType.INVALID, ViewType.PERSONALIZED_SIGNIN_PROMO, ViewType.PERSONALIZED_SYNC_PROMO,
ViewType.SYNC_PROMO, ViewType.FOLDER, ViewType.BOOKMARK, ViewType.DIVIDER}) ViewType.SYNC_PROMO, ViewType.FOLDER, ViewType.BOOKMARK, ViewType.DIVIDER,
ViewType.SECTION_HEADER})
@interface ViewType { @interface ViewType {
int INVALID = -1; int INVALID = -1;
int PERSONALIZED_SIGNIN_PROMO = 0; int PERSONALIZED_SIGNIN_PROMO = 0;
...@@ -32,17 +38,32 @@ final class BookmarkListEntry { ...@@ -32,17 +38,32 @@ final class BookmarkListEntry {
int FOLDER = 3; int FOLDER = 3;
int BOOKMARK = 4; int BOOKMARK = 4;
int DIVIDER = 5; int DIVIDER = 5;
int SECTION_HEADER = 6;
} }
private final @ViewType int mViewType; private final @ViewType int mViewType;
@Nullable @Nullable
private final BookmarkItem mBookmarkItem; private final BookmarkItem mBookmarkItem;
@Nullable
private String mHeaderTitle;
@Nullable
private String mHeaderDescription;
private BookmarkListEntry(int viewType, @Nullable BookmarkItem bookmarkItem) { private BookmarkListEntry(int viewType, @Nullable BookmarkItem bookmarkItem) {
this.mViewType = viewType; this.mViewType = viewType;
this.mBookmarkItem = bookmarkItem; this.mBookmarkItem = bookmarkItem;
} }
/** Constructor for section headers. */
private BookmarkListEntry(
int viewType, String headerTitle, @Nullable String headerDescription) {
assert viewType == ViewType.SECTION_HEADER;
this.mViewType = viewType;
this.mBookmarkItem = null;
this.mHeaderTitle = headerTitle;
this.mHeaderDescription = headerDescription;
}
/** /**
* Create an entry presenting a bookmark folder or bookmark. * Create an entry presenting a bookmark folder or bookmark.
* @param bookmarkItem The data object created from the bookmark backend. * @param bookmarkItem The data object created from the bookmark backend.
...@@ -78,6 +99,31 @@ final class BookmarkListEntry { ...@@ -78,6 +99,31 @@ final class BookmarkListEntry {
return viewType == ViewType.BOOKMARK || viewType == ViewType.FOLDER; return viewType == ViewType.BOOKMARK || viewType == ViewType.FOLDER;
} }
/**
* Create an entry representing the reading list read/unread section header.
* @param titleRes The string resource for title text.
* @param descriptionRes The string resource for description.
* @param context The context to use.
*/
static BookmarkListEntry createSectionHeader(@StringRes Integer titleRes,
@Nullable @StringRes Integer descriptionRes, Context context) {
return new BookmarkListEntry(ViewType.SECTION_HEADER, context.getString(titleRes),
descriptionRes == null ? null : context.getString(descriptionRes));
}
/**
* Create an entry representing the reading list read/unread section header.
* @param read True if it represents read section, false for unread section.
*/
static BookmarkListEntry createReadingListSectionHeader(boolean read) {
Context context = ContextUtils.getApplicationContext();
return read ? new BookmarkListEntry(
ViewType.SECTION_HEADER, context.getString(R.string.reading_list_read), null)
: new BookmarkListEntry(ViewType.SECTION_HEADER,
context.getString(R.string.reading_list_unread),
context.getString(R.string.reading_list_ready_for_offline));
}
/** /**
* Returns the view type used in the bookmark list UI. * Returns the view type used in the bookmark list UI.
*/ */
...@@ -93,4 +139,16 @@ final class BookmarkListEntry { ...@@ -93,4 +139,16 @@ final class BookmarkListEntry {
BookmarkItem getBookmarkItem() { BookmarkItem getBookmarkItem() {
return mBookmarkItem; return mBookmarkItem;
} }
/** @return The title text to be shown if it is a section header. */
@Nullable
String getHeaderTitle() {
return mHeaderTitle;
}
/** @return The description text to be shown if it is a section header. */
@Nullable
String getHeaderDescription() {
return mHeaderDescription;
}
} }
// Copyright 2020 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.bookmarks;
import android.content.Context;
import org.chromium.chrome.R;
import org.chromium.components.bookmarks.BookmarkType;
import java.util.Collections;
import java.util.List;
/**
* Helper class to manage all the logic and UI behind adding the reading list section headers in the
* bookmark content UI.
* TODO(crbug/1147787): Add integration tests.
*/
class ReadingListSectionHeader {
/**
* Sorts the reading list and adds section headers if the list is a reading list.
* Noop, if the list isn't a reading list. The layout rows are shown in the following order :
* 1 - Section header with title "Unread"
* 2 - Unread reading list articles.
* 3 - Section header with title "Read"
* 4 - Read reading list articles.
* @param listItems The list of bookmark items to be shown in the UI.
* @param context The associated activity context.
*/
public static void maybeSortAndInsertSectionHeaders(
List<BookmarkListEntry> listItems, Context context) {
if (listItems.isEmpty()) return;
sort(listItems);
// Add a section header at the top.
assert listItems.get(0).getBookmarkItem().getId().getType() == BookmarkType.READING_LIST;
boolean isRead = listItems.get(0).getBookmarkItem().isRead();
listItems.add(0, createReadingListSectionHeader(isRead, context));
if (isRead) return;
// Search for the first read element, and insert the read section header.
for (int i = 2; i < listItems.size(); i++) {
BookmarkListEntry listItem = listItems.get(i);
assert listItem.getBookmarkItem().getId().getType() == BookmarkType.READING_LIST;
if (listItem.getBookmarkItem().isRead()) {
listItems.add(i, createReadingListSectionHeader(true /*read*/, context));
return;
}
}
}
/**
* Sorts the given {@code listItems} to show unread items ahead of read items.
*/
private static void sort(List<BookmarkListEntry> listItems) {
// TODO(crbug.com/1147259): Sort items by creation time possibly.
Collections.sort(listItems, (lhs, rhs) -> {
// Unread items are shown first.
boolean lhsRead = lhs.getBookmarkItem().isRead();
boolean rhsRead = rhs.getBookmarkItem().isRead();
if (lhsRead == rhsRead) return 0;
return lhsRead ? 1 : -1;
});
}
private static BookmarkListEntry createReadingListSectionHeader(boolean read, Context context) {
return BookmarkListEntry.createSectionHeader(
read ? R.string.reading_list_read : R.string.reading_list_unread,
read ? R.string.reading_list_ready_for_offline : null, context);
}
}
// Copyright 2020 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.bookmarks;
import static org.junit.Assert.assertEquals;
import android.content.Context;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.robolectric.annotation.Config;
import org.chromium.base.ContextUtils;
import org.chromium.base.test.BaseRobolectricTestRunner;
import org.chromium.chrome.browser.bookmarks.BookmarkBridge.BookmarkItem;
import org.chromium.chrome.browser.bookmarks.BookmarkListEntry.ViewType;
import org.chromium.components.bookmarks.BookmarkId;
import org.chromium.components.bookmarks.BookmarkType;
import java.util.ArrayList;
import java.util.List;
/** Unit tests for {@link ReadingListSectionHeader}. */
@RunWith(BaseRobolectricTestRunner.class)
@Config(manifest = Config.NONE)
public class ReadingListSectionHeaderTest {
@Test
public void testAddReadingListSectionHeaders() {
Context context = ContextUtils.getApplicationContext();
String titleRead = context.getString(org.chromium.chrome.R.string.reading_list_read);
String titleUnread = context.getString(org.chromium.chrome.R.string.reading_list_unread);
List<BookmarkListEntry> listItems = new ArrayList<>();
listItems.add(createReadingListEntry(1, true));
listItems.add(createReadingListEntry(2, true));
listItems.add(createReadingListEntry(3, false));
ReadingListSectionHeader.maybeSortAndInsertSectionHeaders(listItems, context);
assertEquals("Incorrect number of items in the adapter", 5, listItems.size());
assertEquals("Expected unread section header", ViewType.SECTION_HEADER,
listItems.get(0).getViewType());
assertEquals("Expected unread title text", titleUnread, listItems.get(0).getHeaderTitle());
assertEquals("Expected read section header", ViewType.SECTION_HEADER,
listItems.get(2).getViewType());
assertEquals("Expected read title text", titleRead, listItems.get(2).getHeaderTitle());
assertEquals(
"Expected a different item", 3, listItems.get(1).getBookmarkItem().getId().getId());
assertEquals(
"Expected a different item", 1, listItems.get(3).getBookmarkItem().getId().getId());
assertEquals(
"Expected a different item", 2, listItems.get(4).getBookmarkItem().getId().getId());
}
private BookmarkListEntry createReadingListEntry(long id, boolean read) {
BookmarkId bookmarkId = new BookmarkId(id, BookmarkType.READING_LIST);
BookmarkItem bookmarkItem =
new BookmarkItem(bookmarkId, null, null, false, null, false, false, 0, read);
return BookmarkListEntry.createBookmarkEntry(bookmarkItem);
}
}
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