blob: ce98501f4a3b9ee9e583515fde282ed1ea22c073 [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;
zonghuayang2b441632021-12-20 12:42:33 +080023import android.content.Context;
Chuck Liao1e0501f2020-02-17 18:20:54 +080024import android.content.Intent;
zonghuayang2b441632021-12-20 12:42:33 +080025import android.content.res.TypedArray;
26import android.graphics.Color;
Chuck Liao1e0501f2020-02-17 18:20:54 +080027import android.graphics.Point;
28import android.graphics.PorterDuff;
29import android.graphics.Rect;
30import android.net.Uri;
31import android.os.Bundle;
32import android.provider.Settings;
33import android.util.DisplayMetrics;
34import android.util.Log;
35import android.view.LayoutInflater;
36import android.view.View;
37import android.view.ViewGroup;
38import android.widget.ImageView;
39import android.widget.ProgressBar;
40import android.widget.TextView;
41
42import androidx.annotation.NonNull;
43import androidx.annotation.Nullable;
Chuck Liao1e0501f2020-02-17 18:20:54 +080044import androidx.cardview.widget.CardView;
45import androidx.fragment.app.Fragment;
46import androidx.recyclerview.widget.GridLayoutManager;
47import androidx.recyclerview.widget.RecyclerView;
48
49import com.android.wallpaper.R;
50import com.android.wallpaper.asset.Asset;
51import com.android.wallpaper.model.Category;
Chuck Liaoddf2b522021-04-15 00:36:25 +080052import com.android.wallpaper.model.CategoryProvider;
Santiago Etchebehere39c31342021-05-03 15:35:06 -070053import com.android.wallpaper.model.LiveWallpaperInfo;
54import com.android.wallpaper.model.WallpaperInfo;
Chuck Liao1e0501f2020-02-17 18:20:54 +080055import com.android.wallpaper.module.InjectorProvider;
56import com.android.wallpaper.module.UserEventLogger;
Chuck Liaof6b4b192020-08-07 02:31:32 +080057import com.android.wallpaper.util.DeepLinkUtils;
Chuck Liao1e0501f2020-02-17 18:20:54 +080058import com.android.wallpaper.util.DisplayMetricsRetriever;
Tianguang Zhangd3e4f632021-04-14 00:30:15 +020059import com.android.wallpaper.util.ResourceUtils;
Santiago Etchebehere53c63432020-05-07 18:55:35 -070060import com.android.wallpaper.util.SizeCalculator;
Wesley.CW Wangdc68fde2020-06-15 19:12:33 +080061import com.android.wallpaper.widget.WallpaperPickerRecyclerViewAccessibilityDelegate;
62import com.android.wallpaper.widget.WallpaperPickerRecyclerViewAccessibilityDelegate.BottomSheetHost;
Chuck Liao1e0501f2020-02-17 18:20:54 +080063
64import com.bumptech.glide.Glide;
zonghuayang2b441632021-12-20 12:42:33 +080065import com.google.android.material.snackbar.Snackbar;
Chuck Liao1e0501f2020-02-17 18:20:54 +080066
67import java.util.ArrayList;
68import java.util.List;
69
70/**
71 * Displays the UI which contains the categories of the wallpaper.
72 */
Chuck Liao58aca1c2021-03-17 01:20:55 +080073public class CategorySelectorFragment extends AppbarFragment {
Chuck Liao1e0501f2020-02-17 18:20:54 +080074
75 // The number of ViewHolders that don't pertain to category tiles.
76 // Currently 2: one for the metadata section and one for the "Select wallpaper" header.
77 private static final int NUM_NON_CATEGORY_VIEW_HOLDERS = 0;
78 private static final int SETTINGS_APP_INFO_REQUEST_CODE = 1;
79 private static final String TAG = "CategorySelectorFragment";
80
81 /**
82 * Interface to be implemented by an Fragment hosting a {@link CategorySelectorFragment}
83 */
84 public interface CategorySelectorFragmentHost {
85
86 /**
87 * Requests to show the Android custom photo picker for the sake of picking a photo
88 * to set as the device's wallpaper.
89 */
90 void requestCustomPhotoPicker(MyPhotosStarter.PermissionChangedListener listener);
91
92 /**
93 * Shows the wallpaper page of the specific category.
94 *
Chuck Liaoa63f1bf2020-06-11 01:35:42 +080095 * @param category the wallpaper's {@link Category}
Chuck Liao1e0501f2020-02-17 18:20:54 +080096 */
Chuck Liaoa63f1bf2020-06-11 01:35:42 +080097 void show(Category category);
Chuck Liao0088bca2020-05-28 16:03:23 +080098
Chuck Liao58aca1c2021-03-17 01:20:55 +080099
Chuck Liao0088bca2020-05-28 16:03:23 +0800100 /**
Chuck Liao58aca1c2021-03-17 01:20:55 +0800101 * Indicates if the host has toolbar to show the title. If it does, we should set the title
102 * there.
103 */
104 boolean isHostToolbarShown();
105
106 /**
107 * Sets the title in the host's toolbar.
Chuck Liao0088bca2020-05-28 16:03:23 +0800108 */
109 void setToolbarTitle(CharSequence title);
Chuck Liaof6b4b192020-08-07 02:31:32 +0800110
111 /**
112 * Fetches the wallpaper categories.
113 */
114 void fetchCategories();
Chuck Liao81145b22020-09-03 09:52:25 +0800115
116 /**
Chuck Liao3abf15b2020-12-17 22:33:02 +0800117 * Cleans up the listeners which will be notified when there's a package event.
118 */
119 void cleanUp();
Chuck Liao1e0501f2020-02-17 18:20:54 +0800120 }
121
Chuck Liaoddf2b522021-04-15 00:36:25 +0800122 private final CategoryProvider mCategoryProvider;
123
Chuck Liao1e0501f2020-02-17 18:20:54 +0800124 private RecyclerView mImageGrid;
125 private CategoryAdapter mAdapter;
126 private ArrayList<Category> mCategories = new ArrayList<>();
127 private Point mTileSizePx;
128 private boolean mAwaitingCategories;
Chuck Liaoc4462d02021-05-20 01:06:23 +0800129 private boolean mIsFeaturedCollectionAvailable;
Chuck Liao1e0501f2020-02-17 18:20:54 +0800130
131 public CategorySelectorFragment() {
132 mAdapter = new CategoryAdapter(mCategories);
Chuck Liaoddf2b522021-04-15 00:36:25 +0800133 mCategoryProvider = InjectorProvider.getInjector().getCategoryProvider(getContext());
Chuck Liao1e0501f2020-02-17 18:20:54 +0800134 }
135
zonghuayang2b441632021-12-20 12:42:33 +0800136 public CategorySelectorFragment(Context context) {
137 mAdapter = new CategoryAdapter(mCategories);
138 mCategoryProvider = InjectorProvider.getInjector().getCategoryProvider(context);
139 }
140
Chuck Liao1e0501f2020-02-17 18:20:54 +0800141 @Nullable
142 @Override
143 public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container,
144 @Nullable Bundle savedInstanceState) {
145 View view = inflater.inflate(R.layout.fragment_category_selector, container,
146 /* attachToRoot= */ false);
Chuck Liao1e0501f2020-02-17 18:20:54 +0800147 mImageGrid = view.findViewById(R.id.category_grid);
Chuck Liao1e6cb682021-04-22 00:51:11 +0800148 mImageGrid.addItemDecoration(new GridPaddingDecoration(getResources().getDimensionPixelSize(
149 R.dimen.grid_item_category_padding_horizontal)));
Chuck Liao1e0501f2020-02-17 18:20:54 +0800150
Santiago Etchebehere53c63432020-05-07 18:55:35 -0700151 mTileSizePx = SizeCalculator.getCategoryTileSize(getActivity());
Chuck Liao1e0501f2020-02-17 18:20:54 +0800152
153 mImageGrid.setAdapter(mAdapter);
154
Chuck Liaoddf2b522021-04-15 00:36:25 +0800155 GridLayoutManager gridLayoutManager = new GridLayoutManager(getActivity(),
156 getNumColumns() * CategorySpanSizeLookup.DEFAULT_CATEGORY_SPAN_SIZE);
157 gridLayoutManager.setSpanSizeLookup(new CategorySpanSizeLookup(mAdapter));
Chuck Liao1e0501f2020-02-17 18:20:54 +0800158 mImageGrid.setLayoutManager(gridLayoutManager);
Wesley.CW Wang27ff4262020-06-12 19:19:57 +0800159 mImageGrid.setAccessibilityDelegateCompat(
Wesley.CW Wangdc68fde2020-06-15 19:12:33 +0800160 new WallpaperPickerRecyclerViewAccessibilityDelegate(
161 mImageGrid, (BottomSheetHost) getParentFragment(), getNumColumns()));
Chuck Liao58aca1c2021-03-17 01:20:55 +0800162
163 if (getCategorySelectorFragmentHost().isHostToolbarShown()) {
164 view.findViewById(R.id.header_bar).setVisibility(View.GONE);
165 getCategorySelectorFragmentHost().setToolbarTitle(getText(R.string.wallpaper_title));
166 } else {
167 setUpToolbar(view);
168 setTitle(getText(R.string.wallpaper_title));
169 }
Chuck Liao1e0501f2020-02-17 18:20:54 +0800170
Chuck Liaof6b4b192020-08-07 02:31:32 +0800171 if (!DeepLinkUtils.isDeepLink(getActivity().getIntent())) {
172 getCategorySelectorFragmentHost().fetchCategories();
173 }
174
Chihhang Chuange6c73222021-04-14 22:36:41 +0800175 // For nav bar edge-to-edge effect.
John Panc829d732022-01-11 21:25:02 +0800176 mImageGrid.setOnApplyWindowInsetsListener((v, windowInsets) -> {
Chihhang Chuange6c73222021-04-14 22:36:41 +0800177 v.setPadding(
178 v.getPaddingLeft(),
John Panc829d732022-01-11 21:25:02 +0800179 v.getPaddingTop(),
Chihhang Chuange6c73222021-04-14 22:36:41 +0800180 v.getPaddingRight(),
181 windowInsets.getSystemWindowInsetBottom());
182 return windowInsets.consumeSystemWindowInsets();
183 });
Chuck Liao1e0501f2020-02-17 18:20:54 +0800184 return view;
185 }
186
Chuck Liao3abf15b2020-12-17 22:33:02 +0800187 @Override
188 public void onDestroyView() {
189 getCategorySelectorFragmentHost().cleanUp();
190 super.onDestroyView();
191 }
192
Chuck Liao1e0501f2020-02-17 18:20:54 +0800193 /**
194 * Inserts the given category into the categories list in priority order.
195 */
196 void addCategory(Category category, boolean loading) {
197 // If not previously waiting for categories, enter the waiting state by showing the loading
198 // indicator.
199 if (loading && !mAwaitingCategories) {
200 mAdapter.notifyItemChanged(getNumColumns());
201 mAdapter.notifyItemInserted(getNumColumns());
202 mAwaitingCategories = true;
203 }
204 // Not add existing category to category list
205 if (mCategories.indexOf(category) >= 0) {
206 updateCategory(category);
207 return;
208 }
209
210 int priority = category.getPriority();
211
212 int index = 0;
213 while (index < mCategories.size() && priority >= mCategories.get(index).getPriority()) {
214 index++;
215 }
216
217 mCategories.add(index, category);
218 if (mAdapter != null) {
219 // Offset the index because of the static metadata element at beginning of RecyclerView.
220 mAdapter.notifyItemInserted(index + NUM_NON_CATEGORY_VIEW_HOLDERS);
221 }
222 }
223
224 void removeCategory(Category category) {
225 int index = mCategories.indexOf(category);
226 if (index >= 0) {
227 mCategories.remove(index);
228 mAdapter.notifyItemRemoved(index + NUM_NON_CATEGORY_VIEW_HOLDERS);
229 }
230 }
231
232 void updateCategory(Category category) {
233 int index = mCategories.indexOf(category);
234 if (index >= 0) {
235 mCategories.remove(index);
236 mCategories.add(index, category);
237 mAdapter.notifyItemChanged(index + NUM_NON_CATEGORY_VIEW_HOLDERS);
238 }
239 }
240
241 void clearCategories() {
242 mCategories.clear();
243 mAdapter.notifyDataSetChanged();
244 }
245
246 /**
Chuck Liao5db91d92021-12-27 19:42:36 +0800247 * Notifies that no further categories are expected so it may hide the loading indicator.
Chuck Liao1e0501f2020-02-17 18:20:54 +0800248 */
249 void doneFetchingCategories() {
250 if (mAwaitingCategories) {
251 mAdapter.notifyItemRemoved(mAdapter.getItemCount() - 1);
252 mAwaitingCategories = false;
253 }
Chuck Liaoc4462d02021-05-20 01:06:23 +0800254
255 mIsFeaturedCollectionAvailable = mCategoryProvider.isFeaturedCollectionAvailable();
Chuck Liao1e0501f2020-02-17 18:20:54 +0800256 }
257
258 void notifyDataSetChanged() {
259 mAdapter.notifyDataSetChanged();
260 }
261
262 private int getNumColumns() {
263 Activity activity = getActivity();
Chuck Liaoe2fe0302020-06-29 21:15:35 +0800264 return activity == null ? 1 : SizeCalculator.getNumCategoryColumns(activity);
Chuck Liao1e0501f2020-02-17 18:20:54 +0800265 }
266
267
268 private CategorySelectorFragmentHost getCategorySelectorFragmentHost() {
Chuck Liao8cbe49f2021-03-15 20:28:04 +0800269 Fragment parentFragment = getParentFragment();
270 if (parentFragment != null) {
271 return (CategorySelectorFragmentHost) parentFragment;
272 } else {
273 return (CategorySelectorFragmentHost) getActivity();
274 }
Chuck Liao1e0501f2020-02-17 18:20:54 +0800275 }
276
277 /**
278 * ViewHolder subclass for a category tile in the RecyclerView.
279 */
280 private class CategoryHolder extends RecyclerView.ViewHolder implements View.OnClickListener {
281 private Category mCategory;
282 private ImageView mImageView;
283 private ImageView mOverlayIconView;
284 private TextView mTitleView;
285
286 CategoryHolder(View itemView) {
287 super(itemView);
288 itemView.setOnClickListener(this);
289
290 mImageView = itemView.findViewById(R.id.image);
291 mOverlayIconView = itemView.findViewById(R.id.overlay_icon);
292 mTitleView = itemView.findViewById(R.id.category_title);
293
294 CardView categoryView = itemView.findViewById(R.id.category);
295 categoryView.getLayoutParams().height = mTileSizePx.y;
Ching-Sung Li1ac024f2021-06-08 14:43:28 +0800296 categoryView.setRadius(getResources().getDimension(R.dimen.grid_item_all_radius_small));
Chuck Liao1e0501f2020-02-17 18:20:54 +0800297 }
298
299 @Override
300 public void onClick(View view) {
Santiago Etchebehere39c31342021-05-03 15:35:06 -0700301 Activity activity = getActivity();
Chuck Liao1e0501f2020-02-17 18:20:54 +0800302 final UserEventLogger eventLogger =
Santiago Etchebehere39c31342021-05-03 15:35:06 -0700303 InjectorProvider.getInjector().getUserEventLogger(activity);
Chuck Liao1e0501f2020-02-17 18:20:54 +0800304 eventLogger.logCategorySelected(mCategory.getCollectionId());
305
306 if (mCategory.supportsCustomPhotos()) {
307 getCategorySelectorFragmentHost().requestCustomPhotoPicker(
308 new MyPhotosStarter.PermissionChangedListener() {
309 @Override
310 public void onPermissionsGranted() {
311 drawThumbnailAndOverlayIcon();
312 }
313
314 @Override
315 public void onPermissionsDenied(boolean dontAskAgain) {
zonghuayang2b441632021-12-20 12:42:33 +0800316 if (dontAskAgain) {
317 showPermissionSnackbar();
318 }
Chuck Liao1e0501f2020-02-17 18:20:54 +0800319 }
320 });
321 return;
322 }
323
Santiago Etchebehere39c31342021-05-03 15:35:06 -0700324 if (mCategory.isSingleWallpaperCategory()) {
325 WallpaperInfo wallpaper = mCategory.getSingleWallpaper();
326 // Log click on individual wallpaper
327 eventLogger.logIndividualWallpaperSelected(mCategory.getCollectionId());
328
329 InjectorProvider.getInjector().getWallpaperPersister(activity)
330 .setWallpaperInfoInPreview(wallpaper);
331 wallpaper.showPreview(activity,
332 new PreviewActivity.PreviewActivityIntentFactory(),
333 wallpaper instanceof LiveWallpaperInfo ? PREVIEW_LIVE_WALLPAPER_REQUEST_CODE
334 : PREVIEW_WALLPAPER_REQUEST_CODE);
335 return;
336 }
337
Chuck Liaoa63f1bf2020-06-11 01:35:42 +0800338 getCategorySelectorFragmentHost().show(mCategory);
Chuck Liao1e0501f2020-02-17 18:20:54 +0800339 }
340
341 /**
342 * Binds the given category to this CategoryHolder.
343 */
344 private void bindCategory(Category category) {
345 mCategory = category;
346 mTitleView.setText(category.getTitle());
347 drawThumbnailAndOverlayIcon();
348 }
349
350 /**
351 * Draws the CategoryHolder's thumbnail and overlay icon.
352 */
353 private void drawThumbnailAndOverlayIcon() {
354 mOverlayIconView.setImageDrawable(mCategory.getOverlayIcon(
355 getActivity().getApplicationContext()));
356
357 // Size the overlay icon according to the category.
358 int overlayIconDimenDp = mCategory.getOverlayIconSizeDp();
359 DisplayMetrics metrics = DisplayMetricsRetriever.getInstance().getDisplayMetrics(
360 getResources(), getActivity().getWindowManager().getDefaultDisplay());
361 int overlayIconDimenPx = (int) (overlayIconDimenDp * metrics.density);
362 mOverlayIconView.getLayoutParams().width = overlayIconDimenPx;
363 mOverlayIconView.getLayoutParams().height = overlayIconDimenPx;
364
365 Asset thumbnail = mCategory.getThumbnail(getActivity().getApplicationContext());
366 if (thumbnail != null) {
zonghuayangdf9c9632021-12-09 17:54:53 +0800367 thumbnail.loadDrawable(getActivity(), mImageView, Color.TRANSPARENT);
Chuck Liao1e0501f2020-02-17 18:20:54 +0800368 } else {
369 // TODO(orenb): Replace this workaround for b/62584914 with a proper way of
370 // unloading the ImageView such that no incorrect image is improperly loaded upon
371 // rapid scroll.
372 Object nullObj = null;
373 Glide.with(getActivity())
374 .asDrawable()
375 .load(nullObj)
376 .into(mImageView);
377
378 }
379 }
380 }
381
zonghuayang2b441632021-12-20 12:42:33 +0800382 private void showPermissionSnackbar() {
383 Snackbar snackbar = Snackbar.make(getView(), R.string.settings_snackbar_description,
384 Snackbar.LENGTH_LONG);
385 Snackbar.SnackbarLayout layout = (Snackbar.SnackbarLayout) snackbar.getView();
386 TextView textView = (TextView) layout.findViewById(R.id.snackbar_text);
387 layout.setBackgroundResource(R.drawable.snackbar_background);
388 TypedArray typedArray = getContext().obtainStyledAttributes(
389 new int[]{android.R.attr.textColorPrimary,
390 com.android.internal.R.attr.colorAccentPrimaryVariant});
391 textView.setTextColor(typedArray.getColor(0, Color.TRANSPARENT));
392 snackbar.setActionTextColor(typedArray.getColor(1, Color.TRANSPARENT));
393 typedArray.recycle();
394
395 snackbar.setAction(getContext().getString(R.string.settings_snackbar_enable),
396 new View.OnClickListener() {
397 @Override
398 public void onClick(View view) {
399 startSettings(SETTINGS_APP_INFO_REQUEST_CODE);
400 }
401 });
402 snackbar.show();
403 }
404
405 private void startSettings(int resultCode) {
406 Activity activity = getActivity();
407 if (activity == null) {
408 return;
409 }
410 Intent appInfoIntent = new Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS);
411 Uri uri = Uri.fromParts("package", activity.getPackageName(), /* fragment= */ null);
412 appInfoIntent.setData(uri);
413 startActivityForResult(appInfoIntent, resultCode);
414 }
415
Chuck Liaoddf2b522021-04-15 00:36:25 +0800416 private class FeaturedCategoryHolder extends CategoryHolder {
417
418 FeaturedCategoryHolder(View itemView) {
419 super(itemView);
420 CardView categoryView = itemView.findViewById(R.id.category);
421 categoryView.getLayoutParams().height =
422 SizeCalculator.getFeaturedCategoryTileSize(getActivity()).y;
Ching-Sung Li1ac024f2021-06-08 14:43:28 +0800423 categoryView.setRadius(getResources().getDimension(R.dimen.grid_item_all_radius));
Chuck Liaoddf2b522021-04-15 00:36:25 +0800424 }
425 }
426
427 private class MyPhotosCategoryHolder extends CategoryHolder {
428
429 MyPhotosCategoryHolder(View itemView) {
430 super(itemView);
431 // Reuse the height of featured category since My Photos category & featured category
432 // have the same height in current UI design.
433 CardView categoryView = itemView.findViewById(R.id.category);
Tianguang Zhangbae57642021-04-19 19:46:33 +0200434 int height = SizeCalculator.getFeaturedCategoryTileSize(getActivity()).y;
435 categoryView.getLayoutParams().height = height;
436 // Use the height as the card corner radius for the "My photos" category
437 // for a stadium border.
438 categoryView.setRadius(height);
Chuck Liaoddf2b522021-04-15 00:36:25 +0800439 }
440 }
441
Chuck Liao1e0501f2020-02-17 18:20:54 +0800442 /**
443 * ViewHolder subclass for the loading indicator ("spinner") shown when categories are being
444 * fetched.
445 */
446 private class LoadingIndicatorHolder extends RecyclerView.ViewHolder {
447 private LoadingIndicatorHolder(View view) {
448 super(view);
449 ProgressBar progressBar = view.findViewById(R.id.loading_indicator);
450 progressBar.getIndeterminateDrawable().setColorFilter(
Tianguang Zhangd3e4f632021-04-14 00:30:15 +0200451 ResourceUtils.getColorAttr(
452 getActivity(),
453 android.R.attr.colorAccent
454 ), PorterDuff.Mode.SRC_IN);
Chuck Liao1e0501f2020-02-17 18:20:54 +0800455 }
456 }
457
458 /**
459 * RecyclerView Adapter subclass for the category tiles in the RecyclerView.
460 */
461 private class CategoryAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder>
462 implements MyPhotosStarter.PermissionChangedListener {
Chuck Liaoddf2b522021-04-15 00:36:25 +0800463 private static final int ITEM_VIEW_TYPE_MY_PHOTOS = 1;
464 private static final int ITEM_VIEW_TYPE_FEATURED_CATEGORY = 2;
Chuck Liao1e0501f2020-02-17 18:20:54 +0800465 private static final int ITEM_VIEW_TYPE_CATEGORY = 3;
466 private static final int ITEM_VIEW_TYPE_LOADING_INDICATOR = 4;
467 private List<Category> mCategories;
468
469 private CategoryAdapter(List<Category> categories) {
470 mCategories = categories;
471 }
472
473 @Override
474 public int getItemViewType(int position) {
475 if (mAwaitingCategories && position == getItemCount() - 1) {
476 return ITEM_VIEW_TYPE_LOADING_INDICATOR;
477 }
478
Chuck Liaoddf2b522021-04-15 00:36:25 +0800479 if (position == 0) {
480 return ITEM_VIEW_TYPE_MY_PHOTOS;
481 }
482
Chuck Liaoc4462d02021-05-20 01:06:23 +0800483 if (mIsFeaturedCollectionAvailable && (position == 1 || position == 2)) {
Chuck Liaoddf2b522021-04-15 00:36:25 +0800484 return ITEM_VIEW_TYPE_FEATURED_CATEGORY;
485 }
486
Chuck Liao1e0501f2020-02-17 18:20:54 +0800487 return ITEM_VIEW_TYPE_CATEGORY;
488 }
489
490 @Override
491 public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
492 LayoutInflater layoutInflater = LayoutInflater.from(getActivity());
493 View view;
494
495 switch (viewType) {
496 case ITEM_VIEW_TYPE_LOADING_INDICATOR:
497 view = layoutInflater.inflate(R.layout.grid_item_loading_indicator,
498 parent, /* attachToRoot= */ false);
499 return new LoadingIndicatorHolder(view);
Chuck Liaoddf2b522021-04-15 00:36:25 +0800500 case ITEM_VIEW_TYPE_MY_PHOTOS:
501 view = layoutInflater.inflate(R.layout.grid_item_category,
502 parent, /* attachToRoot= */ false);
503 return new MyPhotosCategoryHolder(view);
504 case ITEM_VIEW_TYPE_FEATURED_CATEGORY:
505 view = layoutInflater.inflate(R.layout.grid_item_category,
506 parent, /* attachToRoot= */ false);
507 return new FeaturedCategoryHolder(view);
Chuck Liao1e0501f2020-02-17 18:20:54 +0800508 case ITEM_VIEW_TYPE_CATEGORY:
509 view = layoutInflater.inflate(R.layout.grid_item_category,
510 parent, /* attachToRoot= */ false);
511 return new CategoryHolder(view);
512 default:
513 Log.e(TAG, "Unsupported viewType " + viewType + " in CategoryAdapter");
514 return null;
515 }
516 }
517
518 @Override
519 public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
520 int viewType = getItemViewType(position);
521
522 switch (viewType) {
Chuck Liaoddf2b522021-04-15 00:36:25 +0800523 case ITEM_VIEW_TYPE_MY_PHOTOS:
524 case ITEM_VIEW_TYPE_FEATURED_CATEGORY:
Chuck Liao1e0501f2020-02-17 18:20:54 +0800525 case ITEM_VIEW_TYPE_CATEGORY:
526 // Offset position to get category index to account for the non-category view
527 // holders.
528 Category category = mCategories.get(position - NUM_NON_CATEGORY_VIEW_HOLDERS);
529 ((CategoryHolder) holder).bindCategory(category);
530 break;
531 case ITEM_VIEW_TYPE_LOADING_INDICATOR:
532 // No op.
533 break;
534 default:
535 Log.e(TAG, "Unsupported viewType " + viewType + " in CategoryAdapter");
536 }
537 }
538
539 @Override
540 public int getItemCount() {
541 // Add to size of categories to account for the metadata related views.
542 // Add 1 more for the loading indicator if not yet done loading.
543 int size = mCategories.size() + NUM_NON_CATEGORY_VIEW_HOLDERS;
544 if (mAwaitingCategories) {
545 size += 1;
546 }
547
548 return size;
549 }
550
551 @Override
552 public void onPermissionsGranted() {
553 notifyDataSetChanged();
554 }
555
556 @Override
557 public void onPermissionsDenied(boolean dontAskAgain) {
558 if (!dontAskAgain) {
559 return;
560 }
561
562 String permissionNeededMessage =
563 getString(R.string.permission_needed_explanation_go_to_settings);
564 AlertDialog dialog = new AlertDialog.Builder(getActivity(), R.style.LightDialogTheme)
565 .setMessage(permissionNeededMessage)
566 .setPositiveButton(android.R.string.ok, null /* onClickListener */)
567 .setNegativeButton(
568 R.string.settings_button_label,
569 (dialogInterface, i) -> {
zonghuayang2b441632021-12-20 12:42:33 +0800570 startSettings(SETTINGS_APP_INFO_REQUEST_CODE);
Chuck Liao1e0501f2020-02-17 18:20:54 +0800571 })
572 .create();
573 dialog.show();
574 }
575 }
576
577 private class GridPaddingDecoration extends RecyclerView.ItemDecoration {
578
Chuck Liao1e6cb682021-04-22 00:51:11 +0800579 private final int mPadding;
Chuck Liao1e0501f2020-02-17 18:20:54 +0800580
581 GridPaddingDecoration(int padding) {
582 mPadding = padding;
583 }
584
585 @Override
586 public void getItemOffsets(Rect outRect, View view, RecyclerView parent,
587 RecyclerView.State state) {
588 int position = parent.getChildAdapterPosition(view) - NUM_NON_CATEGORY_VIEW_HOLDERS;
589 if (position >= 0) {
590 outRect.left = mPadding;
591 outRect.right = mPadding;
592 }
Chuck Liao1e6cb682021-04-22 00:51:11 +0800593
594 RecyclerView.ViewHolder viewHolder = parent.getChildViewHolder(view);
595 if (viewHolder instanceof MyPhotosCategoryHolder
596 || viewHolder instanceof FeaturedCategoryHolder) {
597 outRect.bottom = getResources().getDimensionPixelSize(
598 R.dimen.grid_item_featured_category_padding_bottom);
599 } else {
600 outRect.bottom = getResources().getDimensionPixelSize(
601 R.dimen.grid_item_category_padding_bottom);
602 }
Chuck Liao1e0501f2020-02-17 18:20:54 +0800603 }
604 }
zonghuayang2b441632021-12-20 12:42:33 +0800605 @Override
606 public void onActivityResult(int requestCode, int resultCode, Intent data) {
607 if (requestCode == SETTINGS_APP_INFO_REQUEST_CODE) {
608 notifyDataSetChanged();
609 }
610 }
Chuck Liao1e0501f2020-02-17 18:20:54 +0800611 /**
612 * SpanSizeLookup subclass which provides that the item in the first position spans the number
613 * of columns in the RecyclerView and all other items only take up a single span.
614 */
615 private class CategorySpanSizeLookup extends GridLayoutManager.SpanSizeLookup {
Chuck Liaoddf2b522021-04-15 00:36:25 +0800616 private static final int DEFAULT_CATEGORY_SPAN_SIZE = 2;
617
Chuck Liao1e0501f2020-02-17 18:20:54 +0800618 CategoryAdapter mAdapter;
619
620 private CategorySpanSizeLookup(CategoryAdapter adapter) {
621 mAdapter = adapter;
622 }
623
624 @Override
625 public int getSpanSize(int position) {
Chuck Liaoddf2b522021-04-15 00:36:25 +0800626 if (position < NUM_NON_CATEGORY_VIEW_HOLDERS || mAdapter.getItemViewType(position)
627 == CategoryAdapter.ITEM_VIEW_TYPE_LOADING_INDICATOR || mAdapter.getItemViewType(
628 position) == CategoryAdapter.ITEM_VIEW_TYPE_MY_PHOTOS) {
629 return getNumColumns() * DEFAULT_CATEGORY_SPAN_SIZE;
Chuck Liao1e0501f2020-02-17 18:20:54 +0800630 }
631
Chuck Liaoddf2b522021-04-15 00:36:25 +0800632 if (mAdapter.getItemViewType(position)
633 == CategoryAdapter.ITEM_VIEW_TYPE_FEATURED_CATEGORY) {
634 return getNumColumns() * DEFAULT_CATEGORY_SPAN_SIZE / 2;
635 }
636
637 return DEFAULT_CATEGORY_SPAN_SIZE;
Chuck Liao1e0501f2020-02-17 18:20:54 +0800638 }
639 }
Chuck Liao1e0501f2020-02-17 18:20:54 +0800640}