1 // Copyright 2018 The Chromium Authors. All rights reserved. 2 // Use of this source code is governed by a BSD-style license that can be 3 // found in the LICENSE file. 4 5 package org.chromium.chrome.browser.download.home.list; 6 7 import androidx.annotation.IntDef; 8 9 import org.chromium.chrome.browser.download.home.DownloadManagerUiConfig; 10 import org.chromium.chrome.browser.download.home.filter.Filters.FilterType; 11 import org.chromium.chrome.browser.download.home.list.ListItem.OfflineItemListItem; 12 import org.chromium.chrome.browser.download.home.list.ListItem.ViewListItem; 13 import org.chromium.components.browser_ui.util.date.CalendarUtils; 14 import org.chromium.components.offline_items_collection.LegacyHelpers; 15 import org.chromium.components.offline_items_collection.OfflineItem; 16 import org.chromium.components.offline_items_collection.OfflineItemFilter; 17 import org.chromium.components.offline_items_collection.OfflineItemState; 18 19 import java.lang.annotation.Retention; 20 import java.lang.annotation.RetentionPolicy; 21 import java.util.ArrayList; 22 import java.util.Collection; 23 import java.util.Date; 24 import java.util.List; 25 26 /** Utility methods for representing {@link ListItem}s in a {@link RecyclerView} list. */ 27 public class ListUtils { 28 /** The potential types of list items that could be displayed. */ 29 @IntDef({ViewType.DATE, ViewType.IN_PROGRESS, ViewType.GENERIC, ViewType.VIDEO, ViewType.AUDIO, 30 ViewType.IMAGE, ViewType.IMAGE_FULL_WIDTH, ViewType.CUSTOM_VIEW, 31 ViewType.SECTION_HEADER, ViewType.IN_PROGRESS_VIDEO, ViewType.IN_PROGRESS_IMAGE, 32 ViewType.PREFETCH_ARTICLE, ViewType.GROUP_CARD_ITEM, ViewType.GROUP_CARD_HEADER, 33 ViewType.GROUP_CARD_FOOTER, ViewType.PAGINATION_HEADER, ViewType.GROUP_CARD_DIVIDER_TOP, 34 ViewType.GROUP_CARD_DIVIDER_MIDDLE, ViewType.GROUP_CARD_DIVIDER_BOTTOM}) 35 @Retention(RetentionPolicy.SOURCE) 36 public @interface ViewType { 37 int DATE = 0; 38 int IN_PROGRESS = 1; 39 int GENERIC = 2; 40 int VIDEO = 3; 41 int AUDIO = 4; 42 int IMAGE = 5; 43 int IMAGE_FULL_WIDTH = 6; 44 int CUSTOM_VIEW = 7; 45 int SECTION_HEADER = 8; 46 int IN_PROGRESS_VIDEO = 9; 47 int IN_PROGRESS_IMAGE = 10; 48 int PREFETCH_ARTICLE = 11; 49 int GROUP_CARD_ITEM = 12; 50 int GROUP_CARD_HEADER = 13; 51 int GROUP_CARD_FOOTER = 14; 52 int GROUP_CARD_DIVIDER_TOP = 15; 53 int GROUP_CARD_DIVIDER_MIDDLE = 16; 54 int GROUP_CARD_DIVIDER_BOTTOM = 17; 55 int PAGINATION_HEADER = 18; 56 } 57 58 /** 59 * A visual ordering of the {@link Filters#FilterType}s to determine what order the sections 60 * should appear in the UI. 61 * 62 * Note that this list should have an entry for each {@link Filters#FilterType} that can be 63 * shown visually and asserts will fire if it does not. 64 */ 65 private static final int[] FILTER_TYPE_ORDER_LIST = 66 new int[] {FilterType.NONE, FilterType.VIDEOS, FilterType.MUSIC, FilterType.IMAGES, 67 FilterType.SITES, FilterType.OTHER, FilterType.DOCUMENT, FilterType.PREFETCHED}; 68 69 /** Converts a given list of {@link ListItem}s to a list of {@link OfflineItem}s. */ toOfflineItems(Collection<ListItem> items)70 public static List<OfflineItem> toOfflineItems(Collection<ListItem> items) { 71 List<OfflineItem> offlineItems = new ArrayList<>(); 72 for (ListItem item : items) { 73 if (item instanceof ListItem.OfflineItemListItem) { 74 offlineItems.add(((ListItem.OfflineItemListItem) item).item); 75 } 76 } 77 return offlineItems; 78 } 79 80 /** 81 * Analyzes a {@link ListItem} and finds the most appropriate {@link ViewType} based on the 82 * current state. 83 * @param item The {@link ListItem} to determine the {@link ViewType} for. 84 * @param config The {@link DownloadManagerUiConfig}. 85 * @return The type of {@link ViewType} to use for a particular {@link ListItem}. 86 * @see ViewType 87 */ getViewTypeForItem(ListItem item, DownloadManagerUiConfig config)88 public static @ViewType int getViewTypeForItem(ListItem item, DownloadManagerUiConfig config) { 89 if (item instanceof ViewListItem) return ViewType.CUSTOM_VIEW; 90 if (item instanceof ListItem.SectionHeaderListItem) return ViewType.SECTION_HEADER; 91 if (item instanceof ListItem.PaginationListItem) return ViewType.PAGINATION_HEADER; 92 if (item instanceof ListItem.CardHeaderListItem) { 93 return ViewType.GROUP_CARD_HEADER; 94 } 95 if (item instanceof ListItem.CardFooterListItem) { 96 return ViewType.GROUP_CARD_FOOTER; 97 } 98 99 if (item instanceof ListItem.CardDividerListItem) { 100 switch (((ListItem.CardDividerListItem) item).position) { 101 case TOP: 102 return ViewType.GROUP_CARD_DIVIDER_TOP; 103 case MIDDLE: 104 return ViewType.GROUP_CARD_DIVIDER_MIDDLE; 105 case BOTTOM: 106 return ViewType.GROUP_CARD_DIVIDER_BOTTOM; 107 } 108 } 109 110 if (item instanceof OfflineItemListItem) { 111 OfflineItemListItem offlineItem = (OfflineItemListItem) item; 112 if (offlineItem.isGrouped) return ViewType.GROUP_CARD_ITEM; 113 if (offlineItem.item.schedule != null) return ViewType.GENERIC; 114 115 boolean inProgress = offlineItem.item.state == OfflineItemState.IN_PROGRESS 116 || offlineItem.item.state == OfflineItemState.PAUSED 117 || offlineItem.item.state == OfflineItemState.INTERRUPTED 118 || offlineItem.item.state == OfflineItemState.PENDING 119 || offlineItem.item.state == OfflineItemState.FAILED; 120 121 if (config.useGenericViewTypes) { 122 return inProgress ? ViewType.IN_PROGRESS : ViewType.GENERIC; 123 } 124 125 if (offlineItem.item.isSuggested) { 126 if (offlineItem.item.filter == OfflineItemFilter.PAGE) { 127 return ViewType.PREFETCH_ARTICLE; 128 } else if (offlineItem.item.filter == OfflineItemFilter.AUDIO) { 129 return ViewType.AUDIO; 130 } 131 } 132 133 switch (offlineItem.item.filter) { 134 case OfflineItemFilter.VIDEO: 135 return inProgress ? ViewType.IN_PROGRESS_VIDEO : ViewType.VIDEO; 136 case OfflineItemFilter.IMAGE: 137 return inProgress ? ViewType.IN_PROGRESS_IMAGE 138 : (offlineItem.spanFullWidth ? ViewType.IMAGE_FULL_WIDTH 139 : ViewType.IMAGE); 140 // case OfflineItemFilter.PAGE: 141 // case OfflineItemFilter.AUDIO: 142 // case OfflineItemFilter.OTHER: 143 // case OfflineItemFilter.DOCUMENT: 144 default: 145 return inProgress ? ViewType.IN_PROGRESS : ViewType.GENERIC; 146 } 147 } 148 149 assert false; 150 return ViewType.GENERIC; 151 } 152 153 /** @return Whether the given {@link ListItem} can be grouped inside a card. */ canGroup(ListItem listItem)154 public static boolean canGroup(ListItem listItem) { 155 if (!(listItem instanceof OfflineItemListItem)) return false; 156 return LegacyHelpers.isLegacyContentIndexedItem(((OfflineItemListItem) listItem).item.id); 157 } 158 159 /** 160 * Analyzes a {@link ListItem} and finds the best span size based on the current state. Span 161 * size determines how many columns this {@link ListItem}'s {@link View} will take up in the 162 * overall list. 163 * @param item The {@link ListItem} to determine the span size for. 164 * @param config The {@link DownloadManagerUiConfig}. 165 * @param spanCount The maximum span amount of columns {@code item} can take up. 166 * @return The number of columns {@code item} should take. 167 * @see GridLayoutManager.SpanSizeLookup 168 */ getSpanSize(ListItem item, DownloadManagerUiConfig config, int spanCount)169 public static int getSpanSize(ListItem item, DownloadManagerUiConfig config, int spanCount) { 170 switch (getViewTypeForItem(item, config)) { 171 case ViewType.IMAGE: // Intentional fallthrough. 172 case ViewType.IN_PROGRESS_IMAGE: 173 return 1; 174 default: 175 return spanCount; 176 } 177 } 178 179 /** 180 * Helper method to determine which item type section to show first in the list. 181 * @return -1 if {@code a} should be shown before {@code b}. 182 * 0 if {@code a} == {@code b}. 183 * 1 if {@code a} should be shown after {@code b}. 184 */ compareFilterTypesTo(@ilterType int a, @FilterType int b)185 public static int compareFilterTypesTo(@FilterType int a, @FilterType int b) { 186 int aPriority = getVisualPriorityForFilter(a); 187 int bPriority = getVisualPriorityForFilter(b); 188 return (aPriority < bPriority) ? -1 : ((aPriority == bPriority) ? 0 : 1); 189 } 190 191 /** 192 * Helper method to compare list items based on date. Two items have equal if they both got 193 * created on the same day. 194 * @return -1 if {@code a} should be shown before {@code b}. 195 * 0 if {@code a} == {@code b}. 196 * 1 if {@code a} should be shown after {@code b}. 197 */ compareItemByDate(OfflineItem a, OfflineItem b)198 public static int compareItemByDate(OfflineItem a, OfflineItem b) { 199 Date aDay = CalendarUtils.getStartOfDay(a.creationTimeMs).getTime(); 200 Date bDay = CalendarUtils.getStartOfDay(b.creationTimeMs).getTime(); 201 return bDay.compareTo(aDay); 202 } 203 204 /** 205 * Helper method to compare list items based on timestamp. 206 * @return -1 if {@code a} should be shown before {@code b}. 207 * 0 if {@code a} == {@code b}. 208 * 1 if {@code a} should be shown after {@code b}. 209 */ compareItemByTimestamp(OfflineItem a, OfflineItem b)210 public static int compareItemByTimestamp(OfflineItem a, OfflineItem b) { 211 return Long.compare(b.creationTimeMs, a.creationTimeMs); 212 } 213 214 /** 215 * Helper method to compare list items based on ID. 216 * @return -1 if {@code a} should be shown before {@code b}. 217 * 0 if {@code a} == {@code b}. 218 * 1 if {@code a} should be shown after {@code b}. 219 */ compareItemByID(OfflineItem a, OfflineItem b)220 public static int compareItemByID(OfflineItem a, OfflineItem b) { 221 int comparison = a.id.namespace.compareTo(b.id.namespace); 222 if (comparison != 0) return comparison; 223 return a.id.id.compareTo(b.id.id); 224 } 225 /** 226 * Helper method to compare list items based on {@link OfflineItemSchedule}. 227 * @return -1 if {@code a} should be shown before {@code b}. 228 * 0 if {@code a} == {@code b}. 229 * 1 if {@code a} should be shown after {@code b}. 230 */ compareItemBySchedule(OfflineItem a, OfflineItem b)231 public static int compareItemBySchedule(OfflineItem a, OfflineItem b) { 232 if (a.schedule != null && b.schedule != null) { 233 return a.schedule.startTimeMs <= b.schedule.startTimeMs ? -1 : 1; 234 } 235 236 if (a.schedule == null && b.schedule == null) return 0; 237 return a.schedule != null ? -1 : 1; 238 } 239 getVisualPriorityForFilter(@ilterType int type)240 private static int getVisualPriorityForFilter(@FilterType int type) { 241 for (int i = 0; i < FILTER_TYPE_ORDER_LIST.length; i++) { 242 if (FILTER_TYPE_ORDER_LIST[i] == type) return i; 243 } 244 245 assert false 246 : "Unexpected Filters.FilterType (did you forget to update FILTER_TYPE_ORDER_LIST?)."; 247 return 0; 248 } 249 } 250