blob: f2b115715dfa24ce69e581674308dabf16b9ec88 [file] [log] [blame]
Chuck Liao1e0501f2020-02-17 18:20:54 +08001/*
2 * Copyright (C) 2020 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16package com.android.wallpaper.picker;
17
Santiago Etchebehere39c31342021-05-03 15:35:06 -070018import static com.android.wallpaper.picker.WallpaperPickerDelegate.PREVIEW_LIVE_WALLPAPER_REQUEST_CODE;
19import static com.android.wallpaper.picker.WallpaperPickerDelegate.PREVIEW_WALLPAPER_REQUEST_CODE;
20
Chuck Liao1e0501f2020-02-17 18:20:54 +080021import android.app.Activity;
Chuck Liaof646e052020-09-24 12:24:59 +080022import android.app.AlertDialog;
Chuck Liao1e0501f2020-02-17 18:20:54 +080023import android.content.Intent;
24import android.graphics.Point;
25import android.graphics.PorterDuff;
26import android.graphics.Rect;
27import android.net.Uri;
28import android.os.Bundle;
29import android.provider.Settings;
30import android.util.DisplayMetrics;
31import android.util.Log;
32import android.view.LayoutInflater;
33import android.view.View;
34import android.view.ViewGroup;
35import android.widget.ImageView;
36import android.widget.ProgressBar;
37import android.widget.TextView;
38
39import androidx.annotation.NonNull;
40import androidx.annotation.Nullable;
Chuck Liao1e0501f2020-02-17 18:20:54 +080041import androidx.cardview.widget.CardView;
42import androidx.fragment.app.Fragment;
43import androidx.recyclerview.widget.GridLayoutManager;
44import androidx.recyclerview.widget.RecyclerView;
45
46import com.android.wallpaper.R;
47import com.android.wallpaper.asset.Asset;
48import com.android.wallpaper.model.Category;
Chuck Liaoddf2b522021-04-15 00:36:25 +080049import com.android.wallpaper.model.CategoryProvider;
Santiago Etchebehere39c31342021-05-03 15:35:06 -070050import com.android.wallpaper.model.LiveWallpaperInfo;
51import com.android.wallpaper.model.WallpaperInfo;
Chuck Liao1e0501f2020-02-17 18:20:54 +080052import com.android.wallpaper.module.InjectorProvider;
53import com.android.wallpaper.module.UserEventLogger;
Chuck Liaof6b4b192020-08-07 02:31:32 +080054import com.android.wallpaper.util.DeepLinkUtils;
Chuck Liao1e0501f2020-02-17 18:20:54 +080055import com.android.wallpaper.util.DisplayMetricsRetriever;
Tianguang Zhangd3e4f632021-04-14 00:30:15 +020056import com.android.wallpaper.util.ResourceUtils;
Santiago Etchebehere53c63432020-05-07 18:55:35 -070057import com.android.wallpaper.util.SizeCalculator;
Wesley.CW Wangdc68fde2020-06-15 19:12:33 +080058import com.android.wallpaper.widget.WallpaperPickerRecyclerViewAccessibilityDelegate;
59import com.android.wallpaper.widget.WallpaperPickerRecyclerViewAccessibilityDelegate.BottomSheetHost;
Chuck Liao1e0501f2020-02-17 18:20:54 +080060
61import com.bumptech.glide.Glide;
62
63import java.util.ArrayList;
64import java.util.List;
65
66/**
67 * Displays the UI which contains the categories of the wallpaper.
68 */
Chuck Liao58aca1c2021-03-17 01:20:55 +080069public class CategorySelectorFragment extends AppbarFragment {
Chuck Liao1e0501f2020-02-17 18:20:54 +080070
71 // The number of ViewHolders that don't pertain to category tiles.
72 // Currently 2: one for the metadata section and one for the "Select wallpaper" header.
73 private static final int NUM_NON_CATEGORY_VIEW_HOLDERS = 0;
74 private static final int SETTINGS_APP_INFO_REQUEST_CODE = 1;
75 private static final String TAG = "CategorySelectorFragment";
76
77 /**
78 * Interface to be implemented by an Fragment hosting a {@link CategorySelectorFragment}
79 */
80 public interface CategorySelectorFragmentHost {
81
82 /**
83 * Requests to show the Android custom photo picker for the sake of picking a photo
84 * to set as the device's wallpaper.
85 */
86 void requestCustomPhotoPicker(MyPhotosStarter.PermissionChangedListener listener);
87
88 /**
89 * Shows the wallpaper page of the specific category.
90 *
Chuck Liaoa63f1bf2020-06-11 01:35:42 +080091 * @param category the wallpaper's {@link Category}
Chuck Liao1e0501f2020-02-17 18:20:54 +080092 */
Chuck Liaoa63f1bf2020-06-11 01:35:42 +080093 void show(Category category);
Chuck Liao0088bca2020-05-28 16:03:23 +080094
Chuck Liao58aca1c2021-03-17 01:20:55 +080095
Chuck Liao0088bca2020-05-28 16:03:23 +080096 /**
Chuck Liao58aca1c2021-03-17 01:20:55 +080097 * Indicates if the host has toolbar to show the title. If it does, we should set the title
98 * there.
99 */
100 boolean isHostToolbarShown();
101
102 /**
103 * Sets the title in the host's toolbar.
Chuck Liao0088bca2020-05-28 16:03:23 +0800104 */
105 void setToolbarTitle(CharSequence title);
Chuck Liaof6b4b192020-08-07 02:31:32 +0800106
107 /**
108 * Fetches the wallpaper categories.
109 */
110 void fetchCategories();
Chuck Liao81145b22020-09-03 09:52:25 +0800111
112 /**
Chuck Liao3abf15b2020-12-17 22:33:02 +0800113 * Cleans up the listeners which will be notified when there's a package event.
114 */
115 void cleanUp();
Chuck Liao1e0501f2020-02-17 18:20:54 +0800116 }
117
Chuck Liaoddf2b522021-04-15 00:36:25 +0800118 private final CategoryProvider mCategoryProvider;
119
Chuck Liao1e0501f2020-02-17 18:20:54 +0800120 private RecyclerView mImageGrid;
121 private CategoryAdapter mAdapter;
122 private ArrayList<Category> mCategories = new ArrayList<>();
123 private Point mTileSizePx;
124 private boolean mAwaitingCategories;
Chuck Liaoc4462d02021-05-20 01:06:23 +0800125 private boolean mIsFeaturedCollectionAvailable;
Chuck Liao1e0501f2020-02-17 18:20:54 +0800126
127 public CategorySelectorFragment() {
128 mAdapter = new CategoryAdapter(mCategories);
Chuck Liaoddf2b522021-04-15 00:36:25 +0800129 mCategoryProvider = InjectorProvider.getInjector().getCategoryProvider(getContext());
Chuck Liao1e0501f2020-02-17 18:20:54 +0800130 }
131
132 @Nullable
133 @Override
134 public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container,
135 @Nullable Bundle savedInstanceState) {
136 View view = inflater.inflate(R.layout.fragment_category_selector, container,
137 /* attachToRoot= */ false);
Chuck Liao1e0501f2020-02-17 18:20:54 +0800138 mImageGrid = view.findViewById(R.id.category_grid);
Chuck Liao1e6cb682021-04-22 00:51:11 +0800139 mImageGrid.addItemDecoration(new GridPaddingDecoration(getResources().getDimensionPixelSize(
140 R.dimen.grid_item_category_padding_horizontal)));
Chuck Liao1e0501f2020-02-17 18:20:54 +0800141
Santiago Etchebehere53c63432020-05-07 18:55:35 -0700142 mTileSizePx = SizeCalculator.getCategoryTileSize(getActivity());
Chuck Liao1e0501f2020-02-17 18:20:54 +0800143
144 mImageGrid.setAdapter(mAdapter);
145
Chuck Liaoddf2b522021-04-15 00:36:25 +0800146 GridLayoutManager gridLayoutManager = new GridLayoutManager(getActivity(),
147 getNumColumns() * CategorySpanSizeLookup.DEFAULT_CATEGORY_SPAN_SIZE);
148 gridLayoutManager.setSpanSizeLookup(new CategorySpanSizeLookup(mAdapter));
Chuck Liao1e0501f2020-02-17 18:20:54 +0800149 mImageGrid.setLayoutManager(gridLayoutManager);
Wesley.CW Wang27ff4262020-06-12 19:19:57 +0800150 mImageGrid.setAccessibilityDelegateCompat(
Wesley.CW Wangdc68fde2020-06-15 19:12:33 +0800151 new WallpaperPickerRecyclerViewAccessibilityDelegate(
152 mImageGrid, (BottomSheetHost) getParentFragment(), getNumColumns()));
Chuck Liao58aca1c2021-03-17 01:20:55 +0800153
154 if (getCategorySelectorFragmentHost().isHostToolbarShown()) {
155 view.findViewById(R.id.header_bar).setVisibility(View.GONE);
156 getCategorySelectorFragmentHost().setToolbarTitle(getText(R.string.wallpaper_title));
157 } else {
158 setUpToolbar(view);
159 setTitle(getText(R.string.wallpaper_title));
160 }
Chuck Liao1e0501f2020-02-17 18:20:54 +0800161
Chuck Liaof6b4b192020-08-07 02:31:32 +0800162 if (!DeepLinkUtils.isDeepLink(getActivity().getIntent())) {
163 getCategorySelectorFragmentHost().fetchCategories();
164 }
165
Chihhang Chuange6c73222021-04-14 22:36:41 +0800166 // For nav bar edge-to-edge effect.
Kunhung Li1b0cb472021-04-26 20:52:04 +0800167 view.setOnApplyWindowInsetsListener((v, windowInsets) -> {
168 // For status bar height.
Chihhang Chuange6c73222021-04-14 22:36:41 +0800169 v.setPadding(
170 v.getPaddingLeft(),
Kunhung Li1b0cb472021-04-26 20:52:04 +0800171 windowInsets.getSystemWindowInsetTop(),
Chihhang Chuange6c73222021-04-14 22:36:41 +0800172 v.getPaddingRight(),
Kunhung Li1b0cb472021-04-26 20:52:04 +0800173 v.getPaddingBottom());
174
175 View gridView = v.findViewById(R.id.category_grid);
176 gridView.setPadding(
177 gridView.getPaddingLeft(),
178 gridView.getPaddingTop(),
179 gridView.getPaddingRight(),
Chihhang Chuange6c73222021-04-14 22:36:41 +0800180 windowInsets.getSystemWindowInsetBottom());
181 return windowInsets.consumeSystemWindowInsets();
182 });
Chuck Liao1e0501f2020-02-17 18:20:54 +0800183 return view;
184 }
185
Chuck Liao3abf15b2020-12-17 22:33:02 +0800186 @Override
187 public void onDestroyView() {
188 getCategorySelectorFragmentHost().cleanUp();
189 super.onDestroyView();
190 }
191
Chuck Liao1e0501f2020-02-17 18:20:54 +0800192 /**
193 * Inserts the given category into the categories list in priority order.
194 */
195 void addCategory(Category category, boolean loading) {
196 // If not previously waiting for categories, enter the waiting state by showing the loading
197 // indicator.
198 if (loading && !mAwaitingCategories) {
199 mAdapter.notifyItemChanged(getNumColumns());
200 mAdapter.notifyItemInserted(getNumColumns());
201 mAwaitingCategories = true;
202 }
203 // Not add existing category to category list
204 if (mCategories.indexOf(category) >= 0) {
205 updateCategory(category);
206 return;
207 }
208
209 int priority = category.getPriority();
210
211 int index = 0;
212 while (index < mCategories.size() && priority >= mCategories.get(index).getPriority()) {
213 index++;
214 }
215
216 mCategories.add(index, category);
217 if (mAdapter != null) {
218 // Offset the index because of the static metadata element at beginning of RecyclerView.
219 mAdapter.notifyItemInserted(index + NUM_NON_CATEGORY_VIEW_HOLDERS);
220 }
221 }
222
223 void removeCategory(Category category) {
224 int index = mCategories.indexOf(category);
225 if (index >= 0) {
226 mCategories.remove(index);
227 mAdapter.notifyItemRemoved(index + NUM_NON_CATEGORY_VIEW_HOLDERS);
228 }
229 }
230
231 void updateCategory(Category category) {
232 int index = mCategories.indexOf(category);
233 if (index >= 0) {
234 mCategories.remove(index);
235 mCategories.add(index, category);
236 mAdapter.notifyItemChanged(index + NUM_NON_CATEGORY_VIEW_HOLDERS);
237 }
238 }
239
240 void clearCategories() {
241 mCategories.clear();
242 mAdapter.notifyDataSetChanged();
243 }
244
245 /**
Chuck Liao5db91d92021-12-27 19:42:36 +0800246 * Notifies that no further categories are expected so it may hide the loading indicator.
Chuck Liao1e0501f2020-02-17 18:20:54 +0800247 */
248 void doneFetchingCategories() {
249 if (mAwaitingCategories) {
250 mAdapter.notifyItemRemoved(mAdapter.getItemCount() - 1);
251 mAwaitingCategories = false;
252 }
Chuck Liaoc4462d02021-05-20 01:06:23 +0800253
254 mIsFeaturedCollectionAvailable = mCategoryProvider.isFeaturedCollectionAvailable();
Chuck Liao1e0501f2020-02-17 18:20:54 +0800255 }
256
257 void notifyDataSetChanged() {
258 mAdapter.notifyDataSetChanged();
259 }
260
261 private int getNumColumns() {
262 Activity activity = getActivity();
Chuck Liaoe2fe0302020-06-29 21:15:35 +0800263 return activity == null ? 1 : SizeCalculator.getNumCategoryColumns(activity);
Chuck Liao1e0501f2020-02-17 18:20:54 +0800264 }
265
266
267 private CategorySelectorFragmentHost getCategorySelectorFragmentHost() {
Chuck Liao8cbe49f2021-03-15 20:28:04 +0800268 Fragment parentFragment = getParentFragment();
269 if (parentFragment != null) {
270 return (CategorySelectorFragmentHost) parentFragment;
271 } else {
272 return (CategorySelectorFragmentHost) getActivity();
273 }
Chuck Liao1e0501f2020-02-17 18:20:54 +0800274 }
275
276 /**
277 * ViewHolder subclass for a category tile in the RecyclerView.
278 */
279 private class CategoryHolder extends RecyclerView.ViewHolder implements View.OnClickListener {
280 private Category mCategory;
281 private ImageView mImageView;
282 private ImageView mOverlayIconView;
283 private TextView mTitleView;
284
285 CategoryHolder(View itemView) {
286 super(itemView);
287 itemView.setOnClickListener(this);
288
289 mImageView = itemView.findViewById(R.id.image);
290 mOverlayIconView = itemView.findViewById(R.id.overlay_icon);
291 mTitleView = itemView.findViewById(R.id.category_title);
292
293 CardView categoryView = itemView.findViewById(R.id.category);
294 categoryView.getLayoutParams().height = mTileSizePx.y;
Ching-Sung Li1ac024f2021-06-08 14:43:28 +0800295 categoryView.setRadius(getResources().getDimension(R.dimen.grid_item_all_radius_small));
Chuck Liao1e0501f2020-02-17 18:20:54 +0800296 }
297
298 @Override
299 public void onClick(View view) {
Santiago Etchebehere39c31342021-05-03 15:35:06 -0700300 Activity activity = getActivity();
Chuck Liao1e0501f2020-02-17 18:20:54 +0800301 final UserEventLogger eventLogger =
Santiago Etchebehere39c31342021-05-03 15:35:06 -0700302 InjectorProvider.getInjector().getUserEventLogger(activity);
Chuck Liao1e0501f2020-02-17 18:20:54 +0800303 eventLogger.logCategorySelected(mCategory.getCollectionId());
304
305 if (mCategory.supportsCustomPhotos()) {
306 getCategorySelectorFragmentHost().requestCustomPhotoPicker(
307 new MyPhotosStarter.PermissionChangedListener() {
308 @Override
309 public void onPermissionsGranted() {
310 drawThumbnailAndOverlayIcon();
311 }
312
313 @Override
314 public void onPermissionsDenied(boolean dontAskAgain) {
315 // No-op
316 }
317 });
318 return;
319 }
320
Santiago Etchebehere39c31342021-05-03 15:35:06 -0700321 if (mCategory.isSingleWallpaperCategory()) {
322 WallpaperInfo wallpaper = mCategory.getSingleWallpaper();
323 // Log click on individual wallpaper
324 eventLogger.logIndividualWallpaperSelected(mCategory.getCollectionId());
325
326 InjectorProvider.getInjector().getWallpaperPersister(activity)
327 .setWallpaperInfoInPreview(wallpaper);
328 wallpaper.showPreview(activity,
329 new PreviewActivity.PreviewActivityIntentFactory(),
330 wallpaper instanceof LiveWallpaperInfo ? PREVIEW_LIVE_WALLPAPER_REQUEST_CODE
331 : PREVIEW_WALLPAPER_REQUEST_CODE);
332 return;
333 }
334
Chuck Liaoa63f1bf2020-06-11 01:35:42 +0800335 getCategorySelectorFragmentHost().show(mCategory);
Chuck Liao1e0501f2020-02-17 18:20:54 +0800336 }
337
338 /**
339 * Binds the given category to this CategoryHolder.
340 */
341 private void bindCategory(Category category) {
342 mCategory = category;
343 mTitleView.setText(category.getTitle());
344 drawThumbnailAndOverlayIcon();
345 }
346
347 /**
348 * Draws the CategoryHolder's thumbnail and overlay icon.
349 */
350 private void drawThumbnailAndOverlayIcon() {
351 mOverlayIconView.setImageDrawable(mCategory.getOverlayIcon(
352 getActivity().getApplicationContext()));
353
354 // Size the overlay icon according to the category.
355 int overlayIconDimenDp = mCategory.getOverlayIconSizeDp();
356 DisplayMetrics metrics = DisplayMetricsRetriever.getInstance().getDisplayMetrics(
357 getResources(), getActivity().getWindowManager().getDefaultDisplay());
358 int overlayIconDimenPx = (int) (overlayIconDimenDp * metrics.density);
359 mOverlayIconView.getLayoutParams().width = overlayIconDimenPx;
360 mOverlayIconView.getLayoutParams().height = overlayIconDimenPx;
361
362 Asset thumbnail = mCategory.getThumbnail(getActivity().getApplicationContext());
363 if (thumbnail != null) {
364 thumbnail.loadDrawable(getActivity(), mImageView,
Tianguang Zhangd3e4f632021-04-14 00:30:15 +0200365 ResourceUtils.getColorAttr(
366 getActivity(),
367 android.R.attr.colorSecondary
368 ));
Chuck Liao1e0501f2020-02-17 18:20:54 +0800369 } else {
370 // TODO(orenb): Replace this workaround for b/62584914 with a proper way of
371 // unloading the ImageView such that no incorrect image is improperly loaded upon
372 // rapid scroll.
373 Object nullObj = null;
374 Glide.with(getActivity())
375 .asDrawable()
376 .load(nullObj)
377 .into(mImageView);
378
379 }
380 }
381 }
382
Chuck Liaoddf2b522021-04-15 00:36:25 +0800383 private class FeaturedCategoryHolder extends CategoryHolder {
384
385 FeaturedCategoryHolder(View itemView) {
386 super(itemView);
387 CardView categoryView = itemView.findViewById(R.id.category);
388 categoryView.getLayoutParams().height =
389 SizeCalculator.getFeaturedCategoryTileSize(getActivity()).y;
Ching-Sung Li1ac024f2021-06-08 14:43:28 +0800390 categoryView.setRadius(getResources().getDimension(R.dimen.grid_item_all_radius));
Chuck Liaoddf2b522021-04-15 00:36:25 +0800391 }
392 }
393
394 private class MyPhotosCategoryHolder extends CategoryHolder {
395
396 MyPhotosCategoryHolder(View itemView) {
397 super(itemView);
398 // Reuse the height of featured category since My Photos category & featured category
399 // have the same height in current UI design.
400 CardView categoryView = itemView.findViewById(R.id.category);
Tianguang Zhangbae57642021-04-19 19:46:33 +0200401 int height = SizeCalculator.getFeaturedCategoryTileSize(getActivity()).y;
402 categoryView.getLayoutParams().height = height;
403 // Use the height as the card corner radius for the "My photos" category
404 // for a stadium border.
405 categoryView.setRadius(height);
Chuck Liaoddf2b522021-04-15 00:36:25 +0800406 }
407 }
408
Chuck Liao1e0501f2020-02-17 18:20:54 +0800409 /**
410 * ViewHolder subclass for the loading indicator ("spinner") shown when categories are being
411 * fetched.
412 */
413 private class LoadingIndicatorHolder extends RecyclerView.ViewHolder {
414 private LoadingIndicatorHolder(View view) {
415 super(view);
416 ProgressBar progressBar = view.findViewById(R.id.loading_indicator);
417 progressBar.getIndeterminateDrawable().setColorFilter(
Tianguang Zhangd3e4f632021-04-14 00:30:15 +0200418 ResourceUtils.getColorAttr(
419 getActivity(),
420 android.R.attr.colorAccent
421 ), PorterDuff.Mode.SRC_IN);
Chuck Liao1e0501f2020-02-17 18:20:54 +0800422 }
423 }
424
425 /**
426 * RecyclerView Adapter subclass for the category tiles in the RecyclerView.
427 */
428 private class CategoryAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder>
429 implements MyPhotosStarter.PermissionChangedListener {
Chuck Liaoddf2b522021-04-15 00:36:25 +0800430 private static final int ITEM_VIEW_TYPE_MY_PHOTOS = 1;
431 private static final int ITEM_VIEW_TYPE_FEATURED_CATEGORY = 2;
Chuck Liao1e0501f2020-02-17 18:20:54 +0800432 private static final int ITEM_VIEW_TYPE_CATEGORY = 3;
433 private static final int ITEM_VIEW_TYPE_LOADING_INDICATOR = 4;
434 private List<Category> mCategories;
435
436 private CategoryAdapter(List<Category> categories) {
437 mCategories = categories;
438 }
439
440 @Override
441 public int getItemViewType(int position) {
442 if (mAwaitingCategories && position == getItemCount() - 1) {
443 return ITEM_VIEW_TYPE_LOADING_INDICATOR;
444 }
445
Chuck Liaoddf2b522021-04-15 00:36:25 +0800446 if (position == 0) {
447 return ITEM_VIEW_TYPE_MY_PHOTOS;
448 }
449
Chuck Liaoc4462d02021-05-20 01:06:23 +0800450 if (mIsFeaturedCollectionAvailable && (position == 1 || position == 2)) {
Chuck Liaoddf2b522021-04-15 00:36:25 +0800451 return ITEM_VIEW_TYPE_FEATURED_CATEGORY;
452 }
453
Chuck Liao1e0501f2020-02-17 18:20:54 +0800454 return ITEM_VIEW_TYPE_CATEGORY;
455 }
456
457 @Override
458 public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
459 LayoutInflater layoutInflater = LayoutInflater.from(getActivity());
460 View view;
461
462 switch (viewType) {
463 case ITEM_VIEW_TYPE_LOADING_INDICATOR:
464 view = layoutInflater.inflate(R.layout.grid_item_loading_indicator,
465 parent, /* attachToRoot= */ false);
466 return new LoadingIndicatorHolder(view);
Chuck Liaoddf2b522021-04-15 00:36:25 +0800467 case ITEM_VIEW_TYPE_MY_PHOTOS:
468 view = layoutInflater.inflate(R.layout.grid_item_category,
469 parent, /* attachToRoot= */ false);
470 return new MyPhotosCategoryHolder(view);
471 case ITEM_VIEW_TYPE_FEATURED_CATEGORY:
472 view = layoutInflater.inflate(R.layout.grid_item_category,
473 parent, /* attachToRoot= */ false);
474 return new FeaturedCategoryHolder(view);
Chuck Liao1e0501f2020-02-17 18:20:54 +0800475 case ITEM_VIEW_TYPE_CATEGORY:
476 view = layoutInflater.inflate(R.layout.grid_item_category,
477 parent, /* attachToRoot= */ false);
478 return new CategoryHolder(view);
479 default:
480 Log.e(TAG, "Unsupported viewType " + viewType + " in CategoryAdapter");
481 return null;
482 }
483 }
484
485 @Override
486 public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
487 int viewType = getItemViewType(position);
488
489 switch (viewType) {
Chuck Liaoddf2b522021-04-15 00:36:25 +0800490 case ITEM_VIEW_TYPE_MY_PHOTOS:
491 case ITEM_VIEW_TYPE_FEATURED_CATEGORY:
Chuck Liao1e0501f2020-02-17 18:20:54 +0800492 case ITEM_VIEW_TYPE_CATEGORY:
493 // Offset position to get category index to account for the non-category view
494 // holders.
495 Category category = mCategories.get(position - NUM_NON_CATEGORY_VIEW_HOLDERS);
496 ((CategoryHolder) holder).bindCategory(category);
497 break;
498 case ITEM_VIEW_TYPE_LOADING_INDICATOR:
499 // No op.
500 break;
501 default:
502 Log.e(TAG, "Unsupported viewType " + viewType + " in CategoryAdapter");
503 }
504 }
505
506 @Override
507 public int getItemCount() {
508 // Add to size of categories to account for the metadata related views.
509 // Add 1 more for the loading indicator if not yet done loading.
510 int size = mCategories.size() + NUM_NON_CATEGORY_VIEW_HOLDERS;
511 if (mAwaitingCategories) {
512 size += 1;
513 }
514
515 return size;
516 }
517
518 @Override
519 public void onPermissionsGranted() {
520 notifyDataSetChanged();
521 }
522
523 @Override
524 public void onPermissionsDenied(boolean dontAskAgain) {
525 if (!dontAskAgain) {
526 return;
527 }
528
529 String permissionNeededMessage =
530 getString(R.string.permission_needed_explanation_go_to_settings);
531 AlertDialog dialog = new AlertDialog.Builder(getActivity(), R.style.LightDialogTheme)
532 .setMessage(permissionNeededMessage)
533 .setPositiveButton(android.R.string.ok, null /* onClickListener */)
534 .setNegativeButton(
535 R.string.settings_button_label,
536 (dialogInterface, i) -> {
537 Intent appInfoIntent =
538 new Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS);
539 Uri uri = Uri.fromParts("package",
540 getActivity().getPackageName(), /* fragment= */ null);
541 appInfoIntent.setData(uri);
542 startActivityForResult(
543 appInfoIntent, SETTINGS_APP_INFO_REQUEST_CODE);
544 })
545 .create();
546 dialog.show();
547 }
548 }
549
550 private class GridPaddingDecoration extends RecyclerView.ItemDecoration {
551
Chuck Liao1e6cb682021-04-22 00:51:11 +0800552 private final int mPadding;
Chuck Liao1e0501f2020-02-17 18:20:54 +0800553
554 GridPaddingDecoration(int padding) {
555 mPadding = padding;
556 }
557
558 @Override
559 public void getItemOffsets(Rect outRect, View view, RecyclerView parent,
560 RecyclerView.State state) {
561 int position = parent.getChildAdapterPosition(view) - NUM_NON_CATEGORY_VIEW_HOLDERS;
562 if (position >= 0) {
563 outRect.left = mPadding;
564 outRect.right = mPadding;
565 }
Chuck Liao1e6cb682021-04-22 00:51:11 +0800566
567 RecyclerView.ViewHolder viewHolder = parent.getChildViewHolder(view);
568 if (viewHolder instanceof MyPhotosCategoryHolder
569 || viewHolder instanceof FeaturedCategoryHolder) {
570 outRect.bottom = getResources().getDimensionPixelSize(
571 R.dimen.grid_item_featured_category_padding_bottom);
572 } else {
573 outRect.bottom = getResources().getDimensionPixelSize(
574 R.dimen.grid_item_category_padding_bottom);
575 }
Chuck Liao1e0501f2020-02-17 18:20:54 +0800576 }
577 }
578
579 /**
580 * SpanSizeLookup subclass which provides that the item in the first position spans the number
581 * of columns in the RecyclerView and all other items only take up a single span.
582 */
583 private class CategorySpanSizeLookup extends GridLayoutManager.SpanSizeLookup {
Chuck Liaoddf2b522021-04-15 00:36:25 +0800584 private static final int DEFAULT_CATEGORY_SPAN_SIZE = 2;
585
Chuck Liao1e0501f2020-02-17 18:20:54 +0800586 CategoryAdapter mAdapter;
587
588 private CategorySpanSizeLookup(CategoryAdapter adapter) {
589 mAdapter = adapter;
590 }
591
592 @Override
593 public int getSpanSize(int position) {
Chuck Liaoddf2b522021-04-15 00:36:25 +0800594 if (position < NUM_NON_CATEGORY_VIEW_HOLDERS || mAdapter.getItemViewType(position)
595 == CategoryAdapter.ITEM_VIEW_TYPE_LOADING_INDICATOR || mAdapter.getItemViewType(
596 position) == CategoryAdapter.ITEM_VIEW_TYPE_MY_PHOTOS) {
597 return getNumColumns() * DEFAULT_CATEGORY_SPAN_SIZE;
Chuck Liao1e0501f2020-02-17 18:20:54 +0800598 }
599
Chuck Liaoddf2b522021-04-15 00:36:25 +0800600 if (mAdapter.getItemViewType(position)
601 == CategoryAdapter.ITEM_VIEW_TYPE_FEATURED_CATEGORY) {
602 return getNumColumns() * DEFAULT_CATEGORY_SPAN_SIZE / 2;
603 }
604
605 return DEFAULT_CATEGORY_SPAN_SIZE;
Chuck Liao1e0501f2020-02-17 18:20:54 +0800606 }
607 }
Chuck Liao1e0501f2020-02-17 18:20:54 +0800608}