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

Unified Diff: chrome/android/java/src/org/chromium/chrome/browser/widget/DateDividedAdapter.java

Issue 2154063003: [Android] Introduce DateDividedAdapter to Chrome (Closed) Base URL: https://chromium.googlesource.com/chromium/src.git@master
Patch Set: make findbugs happy Created 4 years, 5 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/widget/DateDividedAdapter.java
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/widget/DateDividedAdapter.java b/chrome/android/java/src/org/chromium/chrome/browser/widget/DateDividedAdapter.java
new file mode 100644
index 0000000000000000000000000000000000000000..3974d6bcfad1c2eadbe6224cd7e484af82bea10d
--- /dev/null
+++ b/chrome/android/java/src/org/chromium/chrome/browser/widget/DateDividedAdapter.java
@@ -0,0 +1,307 @@
+// 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.widget;
+
+import android.os.AsyncTask;
+import android.support.v7.widget.RecyclerView;
+import android.support.v7.widget.RecyclerView.Adapter;
+import android.support.v7.widget.RecyclerView.ViewHolder;
+import android.text.format.DateUtils;
+import android.util.Pair;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.TextView;
+
+import org.chromium.chrome.R;
+
+import java.util.ArrayList;
+import java.util.Calendar;
+import java.util.Comparator;
+import java.util.Date;
+import java.util.List;
+import java.util.SortedSet;
+import java.util.TreeSet;
+import java.util.concurrent.ExecutionException;
+
+/**
+ * An {@link Adapter} that works with a {@link RecyclerView}. It sorts the given {@link List} of
+ * {@link TimedItem}s according to their date, and divides them into sub lists and displays them in
+ * different sections.
+ * <p>
+ * Subclasses should not care about the how date headers are placed in the list. Instead, they
+ * should call {@link #loadItems(List)} with a list of {@link TimedItem}, and this adapter will
+ * insert the headers automatically.
+ */
+public abstract class DateDividedAdapter extends Adapter<RecyclerView.ViewHolder> {
+
+ /**
+ * Interface that the {@link Adapter} uses to interact with the items it manages.
+ */
+ public interface TimedItem {
+ /**
+ * @return The timestamp of this item.
+ */
+ long getStartTime();
+ }
+
+ private static class DateViewHolder extends RecyclerView.ViewHolder {
+ private TextView mTextView;
+
+ public DateViewHolder(View view) {
+ super(view);
+ if (view instanceof TextView) mTextView = (TextView) view;
+ }
+
+ public void setDate(Date date) {
+ // Calender.getInstance() may take long time to run, so Calendar object should be reused
+ // as much as possible.
+ Pair<Calendar, Calendar> pair = getCachedCalendars();
+ Calendar cal1 = pair.first, cal2 = pair.second;
+ cal1.setTimeInMillis(System.currentTimeMillis());
+ cal2.setTime(date);
+
+ StringBuilder builder = new StringBuilder();
+ if (compareCalendar(cal1, cal2) == 0) {
+ builder.append(mTextView.getContext().getString(R.string.today));
+ builder.append(" - ");
+ } else {
+ // Set cal1 to yesterday.
+ cal1.add(Calendar.DATE, -1);
+ if (compareCalendar(cal1, cal2) == 0) {
+ builder.append(mTextView.getContext().getString(R.string.yesterday));
+ builder.append(" - ");
+ }
+ }
+ builder.append(DateUtils.formatDateTime(mTextView.getContext(), date.getTime(),
+ DateUtils.FORMAT_SHOW_WEEKDAY | DateUtils.FORMAT_SHOW_DATE
+ | DateUtils.FORMAT_SHOW_YEAR));
+ mTextView.setText(builder);
+ }
+ }
+
+ /**
+ * A bucket of items with the same date.
+ */
+ private static class ItemGroup {
+ private Date mDate;
+ private List<TimedItem> mItems;
+
+ public ItemGroup(TimedItem item) {
+ mItems = new ArrayList<>();
+ mItems.add(item);
+ mDate = new Date(item.getStartTime());
+ }
+
+ public void addItem(TimedItem item) {
+ mItems.add(item);
+ }
+
+ /**
+ * @return Whether the given date happens in the same day as the items in this group.
+ */
+ public boolean isSameDay(Date otherDate) {
+ return compareDate(mDate, otherDate) == 0;
+ }
+
+ /**
+ * @return The size of this group.
+ */
+ public int size() {
+ // Plus 1 to account for the date header.
+ return mItems.size() + 1;
+ }
+
+ public TimedItem getItemAt(int index) {
+ // 0 is allocated to the date header.
+ if (index == 0) return null;
+ return mItems.get(index - 1);
+ }
+ }
+
+ // Cached async tasks to get the two Calendar objects, which are used when comparing dates.
+ private static final AsyncTask<Void, Void, Calendar> sCal1 = createCalendar();
+ private static final AsyncTask<Void, Void, Calendar> sCal2 = createCalendar();
+
+ public static final int TYPE_DATE = 0;
+ public static final int TYPE_NORMAL = 1;
+
+ private int mSize;
+ private SortedSet<ItemGroup> mItems = new TreeSet<>(new Comparator<ItemGroup>() {
+ @Override
+ public int compare(ItemGroup lhs, ItemGroup rhs) {
+ return compareDate(lhs.mDate, rhs.mDate);
+ }
+ });
+
+ /**
+ * Creates a {@link ViewHolder} in the given view parent.
+ * @see #onCreateViewHolder(ViewGroup, int)
+ */
+ protected abstract ViewHolder createViewHolder(ViewGroup parent);
+
+ /**
+ * Binds the {@link ViewHolder} with the given {@link TimedItem}.
+ * @see #onBindViewHolder(ViewHolder, int)
+ */
+ protected abstract void bindViewHolderForTimedItem(ViewHolder viewHolder, TimedItem item);
+
+ /**
+ * Gets the resource id of the view showing the date header.
+ * Contract for subclasses: this view should be a {@link TextView}.
+ */
+ protected abstract int getTimedItemViewResId();
+
+ /**
+ * Loads a list of {@link TimedItem}s to this adapter. Any previous data will be removed.
+ */
+ public void loadItems(List<? extends TimedItem> timedItems) {
+ mSize = 0;
+ mItems.clear();
+ for (TimedItem timedItem : timedItems) {
+ Date date = new Date(timedItem.getStartTime());
+ boolean found = false;
+ for (ItemGroup item : mItems) {
+ if (item.isSameDay(date)) {
+ found = true;
+ item.addItem(timedItem);
+ mSize++;
+ break;
+ }
+ }
+ if (!found) {
+ // Create a new ItemGroup with the date for the new item. This increases the
+ // size by two because we add new views for the date and the item itself.
+ mItems.add(new ItemGroup(timedItem));
+ mSize += 2;
+ }
+ }
+ notifyDataSetChanged();
+ }
+
+ /**
+ * Removes all items from this adapter.
+ */
+ public void clear() {
+ mSize = 0;
+ mItems.clear();
+ notifyDataSetChanged();
+ }
+
+ /**
+ * Gets the item at the given position. For date headers, {@link TimedItem} will be null; for
+ * normal items, {@link Date} will be null.
+ */
+ private Pair<Date, TimedItem> getItemAt(int position) {
+ Pair<ItemGroup, Integer> pair = getGroupAt(position);
+ ItemGroup group = pair.first;
+ return new Pair<>(group.mDate, group.getItemAt(pair.second));
+ }
+
+ @Override
+ public final int getItemViewType(int position) {
+ Pair<ItemGroup, Integer> pair = getGroupAt(position);
+ return pair.second == 0 ? TYPE_DATE : TYPE_NORMAL;
+ }
+
+ @Override
+ public final RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
+ if (viewType == TYPE_DATE) {
+ return new DateViewHolder(LayoutInflater.from(parent.getContext()).inflate(
+ getTimedItemViewResId(), parent, false));
+ } else if (viewType == TYPE_NORMAL) {
+ return createViewHolder(parent);
+ }
+ return null;
+ }
+
+ @Override
+ public final void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
+ Pair<Date, TimedItem> pair = getItemAt(position);
+ if (holder instanceof DateViewHolder) {
+ ((DateViewHolder) holder).setDate(pair.first);
+ } else {
+ bindViewHolderForTimedItem(holder, pair.second);
+ }
+ }
+
+ @Override
+ public final int getItemCount() {
+ return mSize;
+ }
+
+ /**
+ * Utility method to traverse all groups and find the {@link ItemGroup} for the given position.
+ */
+ private Pair<ItemGroup, Integer> getGroupAt(int position) {
+ // TODO(ianwen): Optimize the performance if the number of groups becomes too large.
+ int i = position;
+ for (ItemGroup group : mItems) {
+ if (i >= group.size()) {
+ i -= group.size();
+ } else {
+ return new Pair<>(group, i);
+ }
+ }
+ assert false;
+ return null;
+ }
+
+ /**
+ * Compares two {@link Date}s. Note if you already have two {@link Calendar} objects, use
+ * {@link #compareCalendar(Calendar, Calendar)} instead.
+ * @return 0 if date1 and date2 are in the same day; 1 if date1 is before date2; -1 otherwise.
+ */
+ private static int compareDate(Date date1, Date date2) {
+ Pair<Calendar, Calendar> pair = getCachedCalendars();
+ Calendar cal1 = pair.first, cal2 = pair.second;
+ cal1.setTime(date1);
+ cal2.setTime(date2);
+ return compareCalendar(cal1, cal2);
+ }
+
+ /**
+ * @return 0 if cal1 and cal2 are in the same day; 1 if cal1 happens before cal2; -1 otherwise.
+ */
+ private static int compareCalendar(Calendar cal1, Calendar cal2) {
+ boolean sameDay = cal1.get(Calendar.YEAR) == cal2.get(Calendar.YEAR)
+ && cal1.get(Calendar.DAY_OF_YEAR) == cal2.get(Calendar.DAY_OF_YEAR);
+ if (sameDay) {
+ return 0;
+ } else if (cal1.before(cal2)) {
+ return 1;
+ } else {
+ return -1;
+ }
+ }
+
+ /**
+ * Convenient getter for {@link #sCal1} and {@link #sCal2}.
+ */
+ private static Pair<Calendar, Calendar> getCachedCalendars() {
+ Calendar cal1, cal2;
+ try {
+ cal1 = sCal1.get();
+ cal2 = sCal2.get();
+ } catch (InterruptedException | ExecutionException e) {
+ // We've tried our best. If AsyncTask really does not work, we give up. :(
+ cal1 = Calendar.getInstance();
+ cal2 = Calendar.getInstance();
+ }
+ return new Pair<>(cal1, cal2);
+ }
+
+ /**
+ * Wraps {@link Calendar#getInstance()} in an {@link AsyncTask} to avoid Strict mode violation.
+ */
+ private static AsyncTask<Void, Void, Calendar> createCalendar() {
+ return new AsyncTask<Void, Void, Calendar>() {
+ @Override
+ protected Calendar doInBackground(Void... unused) {
+ return Calendar.getInstance();
+ }
+ }.execute();
+ }
+}

Powered by Google App Engine
This is Rietveld 408576698