| OLD | NEW |
| 1 // Copyright 2016 The Chromium Authors. All rights reserved. | 1 // Copyright 2016 The Chromium Authors. All rights reserved. |
| 2 // Use of this source code is governed by a BSD-style license that can be | 2 // Use of this source code is governed by a BSD-style license that can be |
| 3 // found in the LICENSE file. | 3 // found in the LICENSE file. |
| 4 | 4 |
| 5 package org.chromium.chrome.browser.widget; | 5 package org.chromium.chrome.browser.widget; |
| 6 | 6 |
| 7 import android.os.AsyncTask; | 7 import android.os.AsyncTask; |
| 8 import android.support.annotation.Nullable; |
| 8 import android.support.v7.widget.RecyclerView; | 9 import android.support.v7.widget.RecyclerView; |
| 9 import android.support.v7.widget.RecyclerView.Adapter; | 10 import android.support.v7.widget.RecyclerView.Adapter; |
| 10 import android.support.v7.widget.RecyclerView.ViewHolder; | 11 import android.support.v7.widget.RecyclerView.ViewHolder; |
| 11 import android.text.format.DateUtils; | 12 import android.text.format.DateUtils; |
| 12 import android.util.Pair; | 13 import android.util.Pair; |
| 13 import android.view.LayoutInflater; | 14 import android.view.LayoutInflater; |
| 14 import android.view.View; | 15 import android.view.View; |
| 15 import android.view.ViewGroup; | 16 import android.view.ViewGroup; |
| 16 import android.widget.TextView; | 17 import android.widget.TextView; |
| 17 | 18 |
| (...skipping 124 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 142 mTextView.setText(builder); | 143 mTextView.setText(builder); |
| 143 } | 144 } |
| 144 } | 145 } |
| 145 | 146 |
| 146 protected static class BasicViewHolder extends RecyclerView.ViewHolder { | 147 protected static class BasicViewHolder extends RecyclerView.ViewHolder { |
| 147 public BasicViewHolder(View itemView) { | 148 public BasicViewHolder(View itemView) { |
| 148 super(itemView); | 149 super(itemView); |
| 149 } | 150 } |
| 150 } | 151 } |
| 151 | 152 |
| 153 protected static class SubsectionHeaderViewHolder extends RecyclerView.ViewH
older { |
| 154 private View mView; |
| 155 |
| 156 public SubsectionHeaderViewHolder(View itemView) { |
| 157 super(itemView); |
| 158 mView = itemView; |
| 159 } |
| 160 |
| 161 public View getView() { |
| 162 return mView; |
| 163 } |
| 164 } |
| 165 |
| 152 /** | 166 /** |
| 153 * A bucket of items with the same date. | 167 * A bucket of items with the same date. |
| 154 */ | 168 */ |
| 155 protected static class ItemGroup { | 169 public static class ItemGroup { |
| 156 private final Date mDate; | 170 private final Date mDate; |
| 157 private final List<TimedItem> mItems = new ArrayList<>(); | 171 private final List<TimedItem> mItems = new ArrayList<>(); |
| 158 | 172 |
| 159 /** Index of the header, relative to the full list. Must be set only on
ce.*/ | 173 /** Index of the header, relative to the full list. Must be set only on
ce.*/ |
| 160 private int mIndex; | 174 private int mIndex; |
| 161 | 175 |
| 162 private boolean mIsSorted; | 176 private boolean mIsSorted; |
| 163 private boolean mIsListHeader; | 177 private boolean mIsListHeader; |
| 164 private boolean mIsListFooter; | 178 private boolean mIsListFooter; |
| 165 | 179 |
| (...skipping 54 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 220 if (index == 0 || mIsListHeader || mIsListFooter) return null; | 234 if (index == 0 || mIsListHeader || mIsListFooter) return null; |
| 221 | 235 |
| 222 sortIfNeeded(); | 236 sortIfNeeded(); |
| 223 return mItems.get(index - 1); | 237 return mItems.get(index - 1); |
| 224 } | 238 } |
| 225 | 239 |
| 226 /** | 240 /** |
| 227 * Rather than sorting the list each time a new item is added, the list
is sorted when | 241 * Rather than sorting the list each time a new item is added, the list
is sorted when |
| 228 * something requires a correct ordering of the items. | 242 * something requires a correct ordering of the items. |
| 229 */ | 243 */ |
| 230 private void sortIfNeeded() { | 244 protected void sortIfNeeded() { |
| 231 if (mIsSorted) return; | 245 if (mIsSorted) return; |
| 232 mIsSorted = true; | 246 mIsSorted = true; |
| 233 | 247 |
| 234 Collections.sort(mItems, new Comparator<TimedItem>() { | 248 Collections.sort(mItems, new Comparator<TimedItem>() { |
| 235 @Override | 249 @Override |
| 236 public int compare(TimedItem lhs, TimedItem rhs) { | 250 public int compare(TimedItem lhs, TimedItem rhs) { |
| 237 // More recent items are listed first. Ideally we'd use Lon
g.compare, but that | 251 return compareItem(lhs, rhs); |
| 238 // is an API level 19 call for some inexplicable reason. | |
| 239 long timeDelta = lhs.getTimestamp() - rhs.getTimestamp(); | |
| 240 if (timeDelta > 0) { | |
| 241 return -1; | |
| 242 } else if (timeDelta == 0) { | |
| 243 return 0; | |
| 244 } else { | |
| 245 return 1; | |
| 246 } | |
| 247 } | 252 } |
| 248 }); | 253 }); |
| 249 } | 254 } |
| 255 |
| 256 protected int compareItem(TimedItem lhs, TimedItem rhs) { |
| 257 // More recent items are listed first. Ideally we'd use Long.compar
e, but that |
| 258 // is an API level 19 call for some inexplicable reason. |
| 259 long timeDelta = lhs.getTimestamp() - rhs.getTimestamp(); |
| 260 if (timeDelta > 0) { |
| 261 return -1; |
| 262 } else if (timeDelta == 0) { |
| 263 return 0; |
| 264 } else { |
| 265 return 1; |
| 266 } |
| 267 } |
| 250 } | 268 } |
| 251 | 269 |
| 252 // Cached async tasks to get the two Calendar objects, which are used when c
omparing dates. | 270 // Cached async tasks to get the two Calendar objects, which are used when c
omparing dates. |
| 253 private static final AsyncTask<Void, Void, Calendar> sCal1 = createCalendar(
); | 271 private static final AsyncTask<Void, Void, Calendar> sCal1 = createCalendar(
); |
| 254 private static final AsyncTask<Void, Void, Calendar> sCal2 = createCalendar(
); | 272 private static final AsyncTask<Void, Void, Calendar> sCal2 = createCalendar(
); |
| 255 | 273 |
| 256 public static final int TYPE_FOOTER = -2; | 274 public static final int TYPE_FOOTER = -2; |
| 257 public static final int TYPE_HEADER = -1; | 275 public static final int TYPE_HEADER = -1; |
| 258 public static final int TYPE_DATE = 0; | 276 public static final int TYPE_DATE = 0; |
| 259 public static final int TYPE_NORMAL = 1; | 277 public static final int TYPE_NORMAL = 1; |
| 278 public static final int TYPE_SUBSECTION_HEADER = 2; |
| 260 | 279 |
| 261 private int mSize; | 280 private int mSize; |
| 262 private boolean mHasListHeader; | 281 private boolean mHasListHeader; |
| 263 private boolean mHasListFooter; | 282 private boolean mHasListFooter; |
| 264 | 283 |
| 265 private SortedSet<ItemGroup> mGroups = new TreeSet<>(new Comparator<ItemGrou
p>() { | 284 private SortedSet<ItemGroup> mGroups = new TreeSet<>(new Comparator<ItemGrou
p>() { |
| 266 @Override | 285 @Override |
| 267 public int compare(ItemGroup lhs, ItemGroup rhs) { | 286 public int compare(ItemGroup lhs, ItemGroup rhs) { |
| 268 if (lhs == rhs) return 0; | 287 if (lhs == rhs) return 0; |
| 269 | 288 |
| 270 // There should only be at most one list header and one list footer
in the SortedSet. | 289 // There should only be at most one list header and one list footer
in the SortedSet. |
| 271 if (lhs.mIsListHeader || rhs.mIsListFooter) return -1; | 290 if (lhs.mIsListHeader || rhs.mIsListFooter) return -1; |
| 272 if (lhs.mIsListFooter || rhs.mIsListHeader) return 1; | 291 if (lhs.mIsListFooter || rhs.mIsListHeader) return 1; |
| 273 | 292 |
| 274 return compareDate(lhs.mDate, rhs.mDate); | 293 return compareDate(lhs.mDate, rhs.mDate); |
| 275 } | 294 } |
| 276 }); | 295 }); |
| 277 | 296 |
| 278 /** | 297 /** |
| 279 * Creates a {@link ViewHolder} in the given view parent. | 298 * Creates a {@link ViewHolder} in the given view parent. |
| 280 * @see #onCreateViewHolder(ViewGroup, int) | 299 * @see #onCreateViewHolder(ViewGroup, int) |
| 281 */ | 300 */ |
| 282 protected abstract ViewHolder createViewHolder(ViewGroup parent); | 301 protected abstract ViewHolder createViewHolder(ViewGroup parent); |
| 283 | 302 |
| 284 /** | 303 /** |
| 285 * Creates a {@link BasicViewHolder} in the given view parent for the header
. | 304 * Creates a {@link BasicViewHolder} in the given view parent for the header
. |
| 286 * @see #onCreateViewHolder(ViewGroup, int) | 305 * @see #onCreateViewHolder(ViewGroup, int) |
| 287 */ | 306 */ |
| 307 @Nullable |
| 288 protected BasicViewHolder createHeader(ViewGroup parent) { | 308 protected BasicViewHolder createHeader(ViewGroup parent) { |
| 289 return null; | 309 return null; |
| 290 } | 310 } |
| 291 | 311 |
| 292 /** | 312 /** |
| 293 * Creates a {@link BasicViewHolder} in the given view parent for the footer
. | 313 * Creates a {@link BasicViewHolder} in the given view parent for the footer
. |
| 294 * See {@link #onCreateViewHolder(ViewGroup, int)}. | 314 * See {@link #onCreateViewHolder(ViewGroup, int)}. |
| 295 */ | 315 */ |
| 316 @Nullable |
| 296 protected BasicViewHolder createFooter(ViewGroup parent) { | 317 protected BasicViewHolder createFooter(ViewGroup parent) { |
| 297 return null; | 318 return null; |
| 298 } | 319 } |
| 299 | 320 |
| 300 /** | 321 /** |
| 301 * Creates a {@link DateViewHolder} in the given view parent. | 322 * Creates a {@link DateViewHolder} in the given view parent. |
| 302 * @see #onCreateViewHolder(ViewGroup, int) | 323 * @see #onCreateViewHolder(ViewGroup, int) |
| 303 */ | 324 */ |
| 304 protected DateViewHolder createDateViewHolder(ViewGroup parent) { | 325 protected DateViewHolder createDateViewHolder(ViewGroup parent) { |
| 305 return new DateViewHolder(LayoutInflater.from(parent.getContext()).infla
te( | 326 return new DateViewHolder(LayoutInflater.from(parent.getContext()).infla
te( |
| 306 getTimedItemViewResId(), parent, false)); | 327 getTimedItemViewResId(), parent, false)); |
| 307 } | 328 } |
| 308 | 329 |
| 309 /** | 330 /** |
| 331 * Creates a {@link ViewHolder} for a subsection in the given view parent. |
| 332 * @see #onCreateViewHolder(ViewGroup, int) |
| 333 */ |
| 334 @Nullable |
| 335 protected SubsectionHeaderViewHolder createSubsectionHeader(ViewGroup parent
) { |
| 336 return null; |
| 337 } |
| 338 |
| 339 /** |
| 340 * Helper function to determine whether an item is a subsection header. |
| 341 * @param timedItem The item. |
| 342 * @return Whether the item is a subsection header. |
| 343 */ |
| 344 protected boolean isSubsectionHeader(TimedItem timedItem) { |
| 345 return false; |
| 346 } |
| 347 |
| 348 /** |
| 310 * Binds the {@link ViewHolder} with the given {@link TimedItem}. | 349 * Binds the {@link ViewHolder} with the given {@link TimedItem}. |
| 311 * @see #onBindViewHolder(ViewHolder, int) | 350 * @see #onBindViewHolder(ViewHolder, int) |
| 312 */ | 351 */ |
| 313 protected abstract void bindViewHolderForTimedItem(ViewHolder viewHolder, Ti
medItem item); | 352 protected abstract void bindViewHolderForTimedItem(ViewHolder viewHolder, Ti
medItem item); |
| 314 | 353 |
| 315 /** | 354 /** |
| 355 * Binds the {@link SubsectionHeaderViewHolder} with the given {@link TimedI
tem}. |
| 356 * @see #onBindViewHolder(ViewHolder, int) |
| 357 */ |
| 358 protected void bindViewHolderForSubsectionHeader( |
| 359 SubsectionHeaderViewHolder holder, TimedItem timedItem) {} |
| 360 |
| 361 /** |
| 316 * Gets the resource id of the view showing the date header. | 362 * Gets the resource id of the view showing the date header. |
| 317 * Contract for subclasses: this view should be a {@link TextView}. | 363 * Contract for subclasses: this view should be a {@link TextView}. |
| 318 */ | 364 */ |
| 319 protected abstract int getTimedItemViewResId(); | 365 protected abstract int getTimedItemViewResId(); |
| 320 | 366 |
| 321 /** | 367 /** |
| 322 * Loads a list of {@link TimedItem}s to this adapter. Previous data will no
t be removed. Call | 368 * Loads a list of {@link TimedItem}s to this adapter. Previous data will no
t be removed. Call |
| 323 * {@link #clear(boolean)} to remove previous items. | 369 * {@link #clear(boolean)} to remove previous items. |
| 324 */ | 370 */ |
| 325 public void loadItems(List<? extends TimedItem> timedItems) { | 371 public void loadItems(List<? extends TimedItem> timedItems) { |
| 326 for (TimedItem timedItem : timedItems) { | 372 for (TimedItem timedItem : timedItems) { |
| 327 Date date = new Date(timedItem.getTimestamp()); | 373 Date date = new Date(timedItem.getTimestamp()); |
| 328 boolean found = false; | 374 boolean found = false; |
| 329 for (ItemGroup group : mGroups) { | 375 for (ItemGroup group : mGroups) { |
| 330 if (group.isSameDay(date)) { | 376 if (group.isSameDay(date)) { |
| 331 found = true; | 377 found = true; |
| 332 group.addItem(timedItem); | 378 group.addItem(timedItem); |
| 333 mSize++; | 379 mSize++; |
| 334 break; | 380 break; |
| 335 } | 381 } |
| 336 } | 382 } |
| 337 if (!found) { | 383 if (!found) { |
| 338 // Create a new ItemGroup with the date for the new item. This i
ncreases the | 384 // Create a new ItemGroup with the date for the new item. This i
ncreases the |
| 339 // size by two because we add new views for the date and the ite
m itself. | 385 // size by two because we add new views for the date and the ite
m itself. |
| 340 ItemGroup newGroup = new ItemGroup(timedItem.getTimestamp()); | 386 ItemGroup newGroup = createGroup(timedItem.getTimestamp()); |
| 341 newGroup.addItem(timedItem); | 387 newGroup.addItem(timedItem); |
| 342 mGroups.add(newGroup); | 388 mGroups.add(newGroup); |
| 343 mSize += 2; | 389 mSize += 2; |
| 344 } | 390 } |
| 345 } | 391 } |
| 346 | 392 |
| 347 setGroupPositions(); | 393 setGroupPositions(); |
| 348 notifyDataSetChanged(); | 394 notifyDataSetChanged(); |
| 349 } | 395 } |
| 350 | 396 |
| 351 /** | 397 /** |
| 398 * Creates and returns an item group for a given day. |
| 399 * @param timestamp A timestamp from which the date is determined. |
| 400 * @return The item group. |
| 401 */ |
| 402 protected ItemGroup createGroup(long timestamp) { |
| 403 return new ItemGroup(timestamp); |
| 404 } |
| 405 |
| 406 /** |
| 352 * Tells each group where they start in the list. | 407 * Tells each group where they start in the list. |
| 353 */ | 408 */ |
| 354 private void setGroupPositions() { | 409 private void setGroupPositions() { |
| 355 int startIndex = 0; | 410 int startIndex = 0; |
| 356 for (ItemGroup group : mGroups) { | 411 for (ItemGroup group : mGroups) { |
| 357 group.resetPosition(); | 412 group.resetPosition(); |
| 358 group.setPosition(startIndex); | 413 group.setPosition(startIndex); |
| 359 startIndex += group.size(); | 414 startIndex += group.size(); |
| 360 } | 415 } |
| 361 } | 416 } |
| (...skipping 88 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 450 */ | 505 */ |
| 451 public Pair<Date, TimedItem> getItemAt(int position) { | 506 public Pair<Date, TimedItem> getItemAt(int position) { |
| 452 Pair<ItemGroup, Integer> pair = getGroupAt(position); | 507 Pair<ItemGroup, Integer> pair = getGroupAt(position); |
| 453 ItemGroup group = pair.first; | 508 ItemGroup group = pair.first; |
| 454 return new Pair<>(group.mDate, group.getItemAt(pair.second)); | 509 return new Pair<>(group.mDate, group.getItemAt(pair.second)); |
| 455 } | 510 } |
| 456 | 511 |
| 457 @Override | 512 @Override |
| 458 public final int getItemViewType(int position) { | 513 public final int getItemViewType(int position) { |
| 459 Pair<ItemGroup, Integer> pair = getGroupAt(position); | 514 Pair<ItemGroup, Integer> pair = getGroupAt(position); |
| 515 ItemGroup group = pair.first; |
| 460 if (pair.second == TYPE_HEADER) { | 516 if (pair.second == TYPE_HEADER) { |
| 461 return TYPE_HEADER; | 517 return TYPE_HEADER; |
| 462 } else if (pair.second == TYPE_FOOTER) { | 518 } else if (pair.second == TYPE_FOOTER) { |
| 463 return TYPE_FOOTER; | 519 return TYPE_FOOTER; |
| 464 } else if (pair.second == 0) { | 520 } else if (pair.second == 0) { |
| 465 return TYPE_DATE; | 521 return TYPE_DATE; |
| 522 } else if (isSubsectionHeader(group.getItemAt(pair.second))) { |
| 523 return TYPE_SUBSECTION_HEADER; |
| 466 } else { | 524 } else { |
| 467 return TYPE_NORMAL; | 525 return TYPE_NORMAL; |
| 468 } | 526 } |
| 469 } | 527 } |
| 470 | 528 |
| 471 @Override | 529 @Override |
| 472 public final RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, in
t viewType) { | 530 public final RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, in
t viewType) { |
| 473 if (viewType == TYPE_DATE) { | 531 if (viewType == TYPE_DATE) { |
| 474 return createDateViewHolder(parent); | 532 return createDateViewHolder(parent); |
| 475 } else if (viewType == TYPE_NORMAL) { | 533 } else if (viewType == TYPE_NORMAL) { |
| 476 return createViewHolder(parent); | 534 return createViewHolder(parent); |
| 477 } else if (viewType == TYPE_HEADER) { | 535 } else if (viewType == TYPE_HEADER) { |
| 478 return createHeader(parent); | 536 return createHeader(parent); |
| 479 } else if (viewType == TYPE_FOOTER) { | 537 } else if (viewType == TYPE_FOOTER) { |
| 480 return createFooter(parent); | 538 return createFooter(parent); |
| 539 } else if (viewType == TYPE_SUBSECTION_HEADER) { |
| 540 return createSubsectionHeader(parent); |
| 481 } | 541 } |
| 482 assert false; | 542 assert false; |
| 483 return null; | 543 return null; |
| 484 } | 544 } |
| 485 | 545 |
| 486 @Override | 546 @Override |
| 487 public final void onBindViewHolder(RecyclerView.ViewHolder holder, int posit
ion) { | 547 public final void onBindViewHolder(RecyclerView.ViewHolder holder, int posit
ion) { |
| 488 Pair<Date, TimedItem> pair = getItemAt(position); | 548 Pair<Date, TimedItem> pair = getItemAt(position); |
| 489 if (holder instanceof DateViewHolder) { | 549 if (holder instanceof DateViewHolder) { |
| 490 ((DateViewHolder) holder).setDate(pair.first); | 550 ((DateViewHolder) holder).setDate(pair.first); |
| 551 } else if (holder instanceof SubsectionHeaderViewHolder) { |
| 552 bindViewHolderForSubsectionHeader((SubsectionHeaderViewHolder) holde
r, pair.second); |
| 491 } else if (!(holder instanceof BasicViewHolder)) { | 553 } else if (!(holder instanceof BasicViewHolder)) { |
| 492 bindViewHolderForTimedItem(holder, pair.second); | 554 bindViewHolderForTimedItem(holder, pair.second); |
| 493 } | 555 } |
| 494 } | 556 } |
| 495 | 557 |
| 496 @Override | 558 @Override |
| 497 public final int getItemCount() { | 559 public final int getItemCount() { |
| 498 return mSize; | 560 return mSize; |
| 499 } | 561 } |
| 500 | 562 |
| (...skipping 55 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 556 long dayOfYear = calendar.get(Calendar.DAY_OF_YEAR); | 618 long dayOfYear = calendar.get(Calendar.DAY_OF_YEAR); |
| 557 long year = calendar.get(Calendar.YEAR); | 619 long year = calendar.get(Calendar.YEAR); |
| 558 return (year << 16) + dayOfYear; | 620 return (year << 16) + dayOfYear; |
| 559 } | 621 } |
| 560 | 622 |
| 561 /** | 623 /** |
| 562 * Compares two {@link Date}s. Note if you already have two {@link Calendar}
objects, use | 624 * Compares two {@link Date}s. Note if you already have two {@link Calendar}
objects, use |
| 563 * {@link #compareCalendar(Calendar, Calendar)} instead. | 625 * {@link #compareCalendar(Calendar, Calendar)} instead. |
| 564 * @return 0 if date1 and date2 are in the same day; 1 if date1 is before da
te2; -1 otherwise. | 626 * @return 0 if date1 and date2 are in the same day; 1 if date1 is before da
te2; -1 otherwise. |
| 565 */ | 627 */ |
| 566 private static int compareDate(Date date1, Date date2) { | 628 protected static int compareDate(Date date1, Date date2) { |
| 567 Pair<Calendar, Calendar> pair = getCachedCalendars(); | 629 Pair<Calendar, Calendar> pair = getCachedCalendars(); |
| 568 Calendar cal1 = pair.first, cal2 = pair.second; | 630 Calendar cal1 = pair.first, cal2 = pair.second; |
| 569 cal1.setTime(date1); | 631 cal1.setTime(date1); |
| 570 cal2.setTime(date2); | 632 cal2.setTime(date2); |
| 571 return compareCalendar(cal1, cal2); | 633 return compareCalendar(cal1, cal2); |
| 572 } | 634 } |
| 573 | 635 |
| 574 /** | 636 /** |
| 575 * @return 0 if cal1 and cal2 are in the same day; 1 if cal1 happens before
cal2; -1 otherwise. | 637 * @return 0 if cal1 and cal2 are in the same day; 1 if cal1 happens before
cal2; -1 otherwise. |
| 576 */ | 638 */ |
| (...skipping 30 matching lines...) Expand all Loading... |
| 607 */ | 669 */ |
| 608 private static AsyncTask<Void, Void, Calendar> createCalendar() { | 670 private static AsyncTask<Void, Void, Calendar> createCalendar() { |
| 609 return new AsyncTask<Void, Void, Calendar>() { | 671 return new AsyncTask<Void, Void, Calendar>() { |
| 610 @Override | 672 @Override |
| 611 protected Calendar doInBackground(Void... unused) { | 673 protected Calendar doInBackground(Void... unused) { |
| 612 return Calendar.getInstance(); | 674 return Calendar.getInstance(); |
| 613 } | 675 } |
| 614 }.execute(); | 676 }.execute(); |
| 615 } | 677 } |
| 616 } | 678 } |
| OLD | NEW |