blob: d807f5001690532d7e2d1c04351fcb8053db1776 [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;
zonghuayang2b441632021-12-20 12:42:33 +080024import android.content.res.TypedArray;
25import android.graphics.Color;
Chuck Liao1e0501f2020-02-17 18:20:54 +080026import android.graphics.Point;
27import android.graphics.PorterDuff;
28import android.graphics.Rect;
29import android.net.Uri;
30import android.os.Bundle;
31import android.provider.Settings;
32import android.util.DisplayMetrics;
33import android.util.Log;
34import android.view.LayoutInflater;
35import android.view.View;
36import android.view.ViewGroup;
37import android.widget.ImageView;
38import android.widget.ProgressBar;
39import android.widget.TextView;
40
41import androidx.annotation.NonNull;
42import androidx.annotation.Nullable;
Chuck Liao1e0501f2020-02-17 18:20:54 +080043import androidx.cardview.widget.CardView;
44import androidx.fragment.app.Fragment;
45import androidx.recyclerview.widget.GridLayoutManager;
46import androidx.recyclerview.widget.RecyclerView;
47
48import com.android.wallpaper.R;
49import com.android.wallpaper.asset.Asset;
Bob Yang12ead102023-02-15 16:01:57 +080050import com.android.wallpaper.effects.EffectsController;
Chuck Liao1e0501f2020-02-17 18:20:54 +080051import 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
122 private RecyclerView mImageGrid;
123 private CategoryAdapter mAdapter;
George Linfeed6b12022-12-28 04:04:24 +0000124 private CategoryProvider mCategoryProvider;
Chuck Liao1e0501f2020-02-17 18:20:54 +0800125 private ArrayList<Category> mCategories = new ArrayList<>();
126 private Point mTileSizePx;
127 private boolean mAwaitingCategories;
Chuck Liaoc4462d02021-05-20 01:06:23 +0800128 private boolean mIsFeaturedCollectionAvailable;
Chuck Liao1e0501f2020-02-17 18:20:54 +0800129
George Linfeed6b12022-12-28 04:04:24 +0000130 @Override
131 public void onCreate(@Nullable Bundle savedInstanceState) {
132 super.onCreate(savedInstanceState);
Chuck Liao1e0501f2020-02-17 18:20:54 +0800133 mAdapter = new CategoryAdapter(mCategories);
George Linfeed6b12022-12-28 04:04:24 +0000134 mCategoryProvider = InjectorProvider.getInjector().getCategoryProvider(requireContext());
zonghuayang2b441632021-12-20 12:42:33 +0800135 }
136
Chuck Liao1e0501f2020-02-17 18:20:54 +0800137 @Nullable
138 @Override
139 public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container,
140 @Nullable Bundle savedInstanceState) {
141 View view = inflater.inflate(R.layout.fragment_category_selector, container,
142 /* attachToRoot= */ false);
Chuck Liao1e0501f2020-02-17 18:20:54 +0800143 mImageGrid = view.findViewById(R.id.category_grid);
Chuck Liao1e6cb682021-04-22 00:51:11 +0800144 mImageGrid.addItemDecoration(new GridPaddingDecoration(getResources().getDimensionPixelSize(
145 R.dimen.grid_item_category_padding_horizontal)));
Chuck Liao1e0501f2020-02-17 18:20:54 +0800146
Santiago Etchebehere53c63432020-05-07 18:55:35 -0700147 mTileSizePx = SizeCalculator.getCategoryTileSize(getActivity());
Chuck Liao1e0501f2020-02-17 18:20:54 +0800148
149 mImageGrid.setAdapter(mAdapter);
150
Chuck Liaoddf2b522021-04-15 00:36:25 +0800151 GridLayoutManager gridLayoutManager = new GridLayoutManager(getActivity(),
152 getNumColumns() * CategorySpanSizeLookup.DEFAULT_CATEGORY_SPAN_SIZE);
153 gridLayoutManager.setSpanSizeLookup(new CategorySpanSizeLookup(mAdapter));
Chuck Liao1e0501f2020-02-17 18:20:54 +0800154 mImageGrid.setLayoutManager(gridLayoutManager);
Wesley.CW Wang27ff4262020-06-12 19:19:57 +0800155 mImageGrid.setAccessibilityDelegateCompat(
Wesley.CW Wangdc68fde2020-06-15 19:12:33 +0800156 new WallpaperPickerRecyclerViewAccessibilityDelegate(
157 mImageGrid, (BottomSheetHost) getParentFragment(), getNumColumns()));
Chuck Liao58aca1c2021-03-17 01:20:55 +0800158
159 if (getCategorySelectorFragmentHost().isHostToolbarShown()) {
160 view.findViewById(R.id.header_bar).setVisibility(View.GONE);
161 getCategorySelectorFragmentHost().setToolbarTitle(getText(R.string.wallpaper_title));
162 } else {
163 setUpToolbar(view);
164 setTitle(getText(R.string.wallpaper_title));
165 }
Chuck Liao1e0501f2020-02-17 18:20:54 +0800166
Chuck Liaof6b4b192020-08-07 02:31:32 +0800167 if (!DeepLinkUtils.isDeepLink(getActivity().getIntent())) {
168 getCategorySelectorFragmentHost().fetchCategories();
169 }
170
Chihhang Chuange6c73222021-04-14 22:36:41 +0800171 // For nav bar edge-to-edge effect.
John Panc829d732022-01-11 21:25:02 +0800172 mImageGrid.setOnApplyWindowInsetsListener((v, windowInsets) -> {
Chihhang Chuange6c73222021-04-14 22:36:41 +0800173 v.setPadding(
174 v.getPaddingLeft(),
John Panc829d732022-01-11 21:25:02 +0800175 v.getPaddingTop(),
Chihhang Chuange6c73222021-04-14 22:36:41 +0800176 v.getPaddingRight(),
177 windowInsets.getSystemWindowInsetBottom());
178 return windowInsets.consumeSystemWindowInsets();
179 });
Chuck Liao1e0501f2020-02-17 18:20:54 +0800180 return view;
181 }
182
Chuck Liao3abf15b2020-12-17 22:33:02 +0800183 @Override
184 public void onDestroyView() {
185 getCategorySelectorFragmentHost().cleanUp();
186 super.onDestroyView();
187 }
188
Chuck Liao1e0501f2020-02-17 18:20:54 +0800189 /**
190 * Inserts the given category into the categories list in priority order.
191 */
192 void addCategory(Category category, boolean loading) {
193 // If not previously waiting for categories, enter the waiting state by showing the loading
194 // indicator.
195 if (loading && !mAwaitingCategories) {
196 mAdapter.notifyItemChanged(getNumColumns());
197 mAdapter.notifyItemInserted(getNumColumns());
198 mAwaitingCategories = true;
199 }
200 // Not add existing category to category list
201 if (mCategories.indexOf(category) >= 0) {
202 updateCategory(category);
203 return;
204 }
205
206 int priority = category.getPriority();
207
208 int index = 0;
209 while (index < mCategories.size() && priority >= mCategories.get(index).getPriority()) {
210 index++;
211 }
212
213 mCategories.add(index, category);
214 if (mAdapter != null) {
215 // Offset the index because of the static metadata element at beginning of RecyclerView.
216 mAdapter.notifyItemInserted(index + NUM_NON_CATEGORY_VIEW_HOLDERS);
217 }
218 }
219
220 void removeCategory(Category category) {
221 int index = mCategories.indexOf(category);
222 if (index >= 0) {
223 mCategories.remove(index);
224 mAdapter.notifyItemRemoved(index + NUM_NON_CATEGORY_VIEW_HOLDERS);
225 }
226 }
227
228 void updateCategory(Category category) {
229 int index = mCategories.indexOf(category);
230 if (index >= 0) {
231 mCategories.remove(index);
232 mCategories.add(index, category);
233 mAdapter.notifyItemChanged(index + NUM_NON_CATEGORY_VIEW_HOLDERS);
234 }
235 }
236
237 void clearCategories() {
238 mCategories.clear();
239 mAdapter.notifyDataSetChanged();
240 }
241
242 /**
Chuck Liao5db91d92021-12-27 19:42:36 +0800243 * Notifies that no further categories are expected so it may hide the loading indicator.
Chuck Liao1e0501f2020-02-17 18:20:54 +0800244 */
245 void doneFetchingCategories() {
246 if (mAwaitingCategories) {
247 mAdapter.notifyItemRemoved(mAdapter.getItemCount() - 1);
248 mAwaitingCategories = false;
249 }
Chuck Liaoc4462d02021-05-20 01:06:23 +0800250
251 mIsFeaturedCollectionAvailable = mCategoryProvider.isFeaturedCollectionAvailable();
Chuck Liao1e0501f2020-02-17 18:20:54 +0800252 }
253
254 void notifyDataSetChanged() {
255 mAdapter.notifyDataSetChanged();
256 }
257
258 private int getNumColumns() {
259 Activity activity = getActivity();
Chuck Liaoe2fe0302020-06-29 21:15:35 +0800260 return activity == null ? 1 : SizeCalculator.getNumCategoryColumns(activity);
Chuck Liao1e0501f2020-02-17 18:20:54 +0800261 }
262
263
264 private CategorySelectorFragmentHost getCategorySelectorFragmentHost() {
Chuck Liao8cbe49f2021-03-15 20:28:04 +0800265 Fragment parentFragment = getParentFragment();
266 if (parentFragment != null) {
267 return (CategorySelectorFragmentHost) parentFragment;
268 } else {
269 return (CategorySelectorFragmentHost) getActivity();
270 }
Chuck Liao1e0501f2020-02-17 18:20:54 +0800271 }
272
273 /**
274 * ViewHolder subclass for a category tile in the RecyclerView.
275 */
276 private class CategoryHolder extends RecyclerView.ViewHolder implements View.OnClickListener {
277 private Category mCategory;
278 private ImageView mImageView;
279 private ImageView mOverlayIconView;
280 private TextView mTitleView;
281
282 CategoryHolder(View itemView) {
283 super(itemView);
284 itemView.setOnClickListener(this);
285
286 mImageView = itemView.findViewById(R.id.image);
287 mOverlayIconView = itemView.findViewById(R.id.overlay_icon);
288 mTitleView = itemView.findViewById(R.id.category_title);
289
290 CardView categoryView = itemView.findViewById(R.id.category);
291 categoryView.getLayoutParams().height = mTileSizePx.y;
Ching-Sung Li1ac024f2021-06-08 14:43:28 +0800292 categoryView.setRadius(getResources().getDimension(R.dimen.grid_item_all_radius_small));
Chuck Liao1e0501f2020-02-17 18:20:54 +0800293 }
294
295 @Override
296 public void onClick(View view) {
Santiago Etchebehere39c31342021-05-03 15:35:06 -0700297 Activity activity = getActivity();
Chuck Liao1e0501f2020-02-17 18:20:54 +0800298 final UserEventLogger eventLogger =
Santiago Etchebehere39c31342021-05-03 15:35:06 -0700299 InjectorProvider.getInjector().getUserEventLogger(activity);
Chuck Liao1e0501f2020-02-17 18:20:54 +0800300 eventLogger.logCategorySelected(mCategory.getCollectionId());
301
302 if (mCategory.supportsCustomPhotos()) {
Bob Yang12ead102023-02-15 16:01:57 +0800303 EffectsController effectsController =
304 InjectorProvider.getInjector().getEffectsController(getContext());
Michael Bestas69d84502023-06-15 20:46:33 +0300305 if (effectsController != null && !effectsController.isEffectTriggered()) {
Bob Yang12ead102023-02-15 16:01:57 +0800306 effectsController.triggerEffect(getContext());
307 }
Chuck Liao1e0501f2020-02-17 18:20:54 +0800308 getCategorySelectorFragmentHost().requestCustomPhotoPicker(
309 new MyPhotosStarter.PermissionChangedListener() {
310 @Override
311 public void onPermissionsGranted() {
312 drawThumbnailAndOverlayIcon();
313 }
314
315 @Override
316 public void onPermissionsDenied(boolean dontAskAgain) {
zonghuayang2b441632021-12-20 12:42:33 +0800317 if (dontAskAgain) {
318 showPermissionSnackbar();
319 }
Chuck Liao1e0501f2020-02-17 18:20:54 +0800320 }
321 });
322 return;
323 }
324
Santiago Etchebehere39c31342021-05-03 15:35:06 -0700325 if (mCategory.isSingleWallpaperCategory()) {
326 WallpaperInfo wallpaper = mCategory.getSingleWallpaper();
327 // Log click on individual wallpaper
328 eventLogger.logIndividualWallpaperSelected(mCategory.getCollectionId());
329
330 InjectorProvider.getInjector().getWallpaperPersister(activity)
331 .setWallpaperInfoInPreview(wallpaper);
332 wallpaper.showPreview(activity,
333 new PreviewActivity.PreviewActivityIntentFactory(),
334 wallpaper instanceof LiveWallpaperInfo ? PREVIEW_LIVE_WALLPAPER_REQUEST_CODE
335 : PREVIEW_WALLPAPER_REQUEST_CODE);
336 return;
337 }
338
Chuck Liaoa63f1bf2020-06-11 01:35:42 +0800339 getCategorySelectorFragmentHost().show(mCategory);
Chuck Liao1e0501f2020-02-17 18:20:54 +0800340 }
341
342 /**
343 * Binds the given category to this CategoryHolder.
344 */
345 private void bindCategory(Category category) {
346 mCategory = category;
347 mTitleView.setText(category.getTitle());
348 drawThumbnailAndOverlayIcon();
349 }
350
351 /**
352 * Draws the CategoryHolder's thumbnail and overlay icon.
353 */
354 private void drawThumbnailAndOverlayIcon() {
355 mOverlayIconView.setImageDrawable(mCategory.getOverlayIcon(
356 getActivity().getApplicationContext()));
Chuck Liao1e0501f2020-02-17 18:20:54 +0800357 Asset thumbnail = mCategory.getThumbnail(getActivity().getApplicationContext());
358 if (thumbnail != null) {
zonghuayang21aa2762021-12-08 18:06:21 +0800359 // Size the overlay icon according to the category.
360 int overlayIconDimenDp = mCategory.getOverlayIconSizeDp();
361 DisplayMetrics metrics = DisplayMetricsRetriever.getInstance().getDisplayMetrics(
362 getResources(), getActivity().getWindowManager().getDefaultDisplay());
363 int overlayIconDimenPx = (int) (overlayIconDimenDp * metrics.density);
364 mOverlayIconView.getLayoutParams().width = overlayIconDimenPx;
365 mOverlayIconView.getLayoutParams().height = overlayIconDimenPx;
366 thumbnail.loadDrawable(getActivity(), mImageView,
367 ResourceUtils.getColorAttr(
368 getActivity(),
369 android.R.attr.colorSecondary
370 ));
Chuck Liao1e0501f2020-02-17 18:20:54 +0800371 } else {
372 // TODO(orenb): Replace this workaround for b/62584914 with a proper way of
373 // unloading the ImageView such that no incorrect image is improperly loaded upon
374 // rapid scroll.
zonghuayang21aa2762021-12-08 18:06:21 +0800375 mImageView.setBackgroundColor(
376 getResources().getColor(R.color.myphoto_background_color));
Chuck Liao1e0501f2020-02-17 18:20:54 +0800377 Object nullObj = null;
378 Glide.with(getActivity())
379 .asDrawable()
380 .load(nullObj)
381 .into(mImageView);
382
383 }
384 }
385 }
386
zonghuayang2b441632021-12-20 12:42:33 +0800387 private void showPermissionSnackbar() {
388 Snackbar snackbar = Snackbar.make(getView(), R.string.settings_snackbar_description,
389 Snackbar.LENGTH_LONG);
390 Snackbar.SnackbarLayout layout = (Snackbar.SnackbarLayout) snackbar.getView();
391 TextView textView = (TextView) layout.findViewById(R.id.snackbar_text);
392 layout.setBackgroundResource(R.drawable.snackbar_background);
393 TypedArray typedArray = getContext().obtainStyledAttributes(
394 new int[]{android.R.attr.textColorPrimary,
395 com.android.internal.R.attr.colorAccentPrimaryVariant});
396 textView.setTextColor(typedArray.getColor(0, Color.TRANSPARENT));
397 snackbar.setActionTextColor(typedArray.getColor(1, Color.TRANSPARENT));
398 typedArray.recycle();
399
400 snackbar.setAction(getContext().getString(R.string.settings_snackbar_enable),
401 new View.OnClickListener() {
402 @Override
403 public void onClick(View view) {
404 startSettings(SETTINGS_APP_INFO_REQUEST_CODE);
405 }
406 });
407 snackbar.show();
408 }
409
410 private void startSettings(int resultCode) {
411 Activity activity = getActivity();
412 if (activity == null) {
413 return;
414 }
415 Intent appInfoIntent = new Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS);
416 Uri uri = Uri.fromParts("package", activity.getPackageName(), /* fragment= */ null);
417 appInfoIntent.setData(uri);
418 startActivityForResult(appInfoIntent, resultCode);
419 }
420
Chuck Liaoddf2b522021-04-15 00:36:25 +0800421 private class FeaturedCategoryHolder extends CategoryHolder {
422
423 FeaturedCategoryHolder(View itemView) {
424 super(itemView);
425 CardView categoryView = itemView.findViewById(R.id.category);
426 categoryView.getLayoutParams().height =
427 SizeCalculator.getFeaturedCategoryTileSize(getActivity()).y;
Ching-Sung Li1ac024f2021-06-08 14:43:28 +0800428 categoryView.setRadius(getResources().getDimension(R.dimen.grid_item_all_radius));
Chuck Liaoddf2b522021-04-15 00:36:25 +0800429 }
430 }
431
432 private class MyPhotosCategoryHolder extends CategoryHolder {
433
434 MyPhotosCategoryHolder(View itemView) {
435 super(itemView);
436 // Reuse the height of featured category since My Photos category & featured category
437 // have the same height in current UI design.
438 CardView categoryView = itemView.findViewById(R.id.category);
Tianguang Zhangbae57642021-04-19 19:46:33 +0200439 int height = SizeCalculator.getFeaturedCategoryTileSize(getActivity()).y;
440 categoryView.getLayoutParams().height = height;
441 // Use the height as the card corner radius for the "My photos" category
442 // for a stadium border.
443 categoryView.setRadius(height);
Chuck Liaoddf2b522021-04-15 00:36:25 +0800444 }
445 }
446
Chuck Liao1e0501f2020-02-17 18:20:54 +0800447 /**
448 * ViewHolder subclass for the loading indicator ("spinner") shown when categories are being
449 * fetched.
450 */
451 private class LoadingIndicatorHolder extends RecyclerView.ViewHolder {
452 private LoadingIndicatorHolder(View view) {
453 super(view);
454 ProgressBar progressBar = view.findViewById(R.id.loading_indicator);
455 progressBar.getIndeterminateDrawable().setColorFilter(
Tianguang Zhangd3e4f632021-04-14 00:30:15 +0200456 ResourceUtils.getColorAttr(
457 getActivity(),
458 android.R.attr.colorAccent
459 ), PorterDuff.Mode.SRC_IN);
Chuck Liao1e0501f2020-02-17 18:20:54 +0800460 }
461 }
462
463 /**
464 * RecyclerView Adapter subclass for the category tiles in the RecyclerView.
465 */
466 private class CategoryAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder>
467 implements MyPhotosStarter.PermissionChangedListener {
Chuck Liaoddf2b522021-04-15 00:36:25 +0800468 private static final int ITEM_VIEW_TYPE_MY_PHOTOS = 1;
469 private static final int ITEM_VIEW_TYPE_FEATURED_CATEGORY = 2;
Chuck Liao1e0501f2020-02-17 18:20:54 +0800470 private static final int ITEM_VIEW_TYPE_CATEGORY = 3;
471 private static final int ITEM_VIEW_TYPE_LOADING_INDICATOR = 4;
472 private List<Category> mCategories;
473
474 private CategoryAdapter(List<Category> categories) {
475 mCategories = categories;
476 }
477
478 @Override
479 public int getItemViewType(int position) {
480 if (mAwaitingCategories && position == getItemCount() - 1) {
481 return ITEM_VIEW_TYPE_LOADING_INDICATOR;
482 }
483
Chuck Liaoddf2b522021-04-15 00:36:25 +0800484 if (position == 0) {
485 return ITEM_VIEW_TYPE_MY_PHOTOS;
486 }
487
Chuck Liaoc4462d02021-05-20 01:06:23 +0800488 if (mIsFeaturedCollectionAvailable && (position == 1 || position == 2)) {
Chuck Liaoddf2b522021-04-15 00:36:25 +0800489 return ITEM_VIEW_TYPE_FEATURED_CATEGORY;
490 }
491
Chuck Liao1e0501f2020-02-17 18:20:54 +0800492 return ITEM_VIEW_TYPE_CATEGORY;
493 }
494
495 @Override
496 public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
497 LayoutInflater layoutInflater = LayoutInflater.from(getActivity());
498 View view;
499
500 switch (viewType) {
501 case ITEM_VIEW_TYPE_LOADING_INDICATOR:
502 view = layoutInflater.inflate(R.layout.grid_item_loading_indicator,
503 parent, /* attachToRoot= */ false);
504 return new LoadingIndicatorHolder(view);
Chuck Liaoddf2b522021-04-15 00:36:25 +0800505 case ITEM_VIEW_TYPE_MY_PHOTOS:
506 view = layoutInflater.inflate(R.layout.grid_item_category,
507 parent, /* attachToRoot= */ false);
508 return new MyPhotosCategoryHolder(view);
509 case ITEM_VIEW_TYPE_FEATURED_CATEGORY:
510 view = layoutInflater.inflate(R.layout.grid_item_category,
511 parent, /* attachToRoot= */ false);
512 return new FeaturedCategoryHolder(view);
Chuck Liao1e0501f2020-02-17 18:20:54 +0800513 case ITEM_VIEW_TYPE_CATEGORY:
514 view = layoutInflater.inflate(R.layout.grid_item_category,
515 parent, /* attachToRoot= */ false);
516 return new CategoryHolder(view);
517 default:
518 Log.e(TAG, "Unsupported viewType " + viewType + " in CategoryAdapter");
519 return null;
520 }
521 }
522
523 @Override
524 public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
525 int viewType = getItemViewType(position);
526
527 switch (viewType) {
Chuck Liaoddf2b522021-04-15 00:36:25 +0800528 case ITEM_VIEW_TYPE_MY_PHOTOS:
529 case ITEM_VIEW_TYPE_FEATURED_CATEGORY:
Chuck Liao1e0501f2020-02-17 18:20:54 +0800530 case ITEM_VIEW_TYPE_CATEGORY:
531 // Offset position to get category index to account for the non-category view
532 // holders.
533 Category category = mCategories.get(position - NUM_NON_CATEGORY_VIEW_HOLDERS);
534 ((CategoryHolder) holder).bindCategory(category);
535 break;
536 case ITEM_VIEW_TYPE_LOADING_INDICATOR:
537 // No op.
538 break;
539 default:
540 Log.e(TAG, "Unsupported viewType " + viewType + " in CategoryAdapter");
541 }
542 }
543
544 @Override
545 public int getItemCount() {
546 // Add to size of categories to account for the metadata related views.
547 // Add 1 more for the loading indicator if not yet done loading.
548 int size = mCategories.size() + NUM_NON_CATEGORY_VIEW_HOLDERS;
549 if (mAwaitingCategories) {
550 size += 1;
551 }
552
553 return size;
554 }
555
556 @Override
557 public void onPermissionsGranted() {
558 notifyDataSetChanged();
559 }
560
561 @Override
562 public void onPermissionsDenied(boolean dontAskAgain) {
563 if (!dontAskAgain) {
564 return;
565 }
566
567 String permissionNeededMessage =
568 getString(R.string.permission_needed_explanation_go_to_settings);
569 AlertDialog dialog = new AlertDialog.Builder(getActivity(), R.style.LightDialogTheme)
570 .setMessage(permissionNeededMessage)
571 .setPositiveButton(android.R.string.ok, null /* onClickListener */)
572 .setNegativeButton(
573 R.string.settings_button_label,
574 (dialogInterface, i) -> {
zonghuayang2b441632021-12-20 12:42:33 +0800575 startSettings(SETTINGS_APP_INFO_REQUEST_CODE);
Chuck Liao1e0501f2020-02-17 18:20:54 +0800576 })
577 .create();
578 dialog.show();
579 }
580 }
581
582 private class GridPaddingDecoration extends RecyclerView.ItemDecoration {
583
Chuck Liao1e6cb682021-04-22 00:51:11 +0800584 private final int mPadding;
Chuck Liao1e0501f2020-02-17 18:20:54 +0800585
586 GridPaddingDecoration(int padding) {
587 mPadding = padding;
588 }
589
590 @Override
591 public void getItemOffsets(Rect outRect, View view, RecyclerView parent,
592 RecyclerView.State state) {
593 int position = parent.getChildAdapterPosition(view) - NUM_NON_CATEGORY_VIEW_HOLDERS;
594 if (position >= 0) {
595 outRect.left = mPadding;
596 outRect.right = mPadding;
597 }
Chuck Liao1e6cb682021-04-22 00:51:11 +0800598
599 RecyclerView.ViewHolder viewHolder = parent.getChildViewHolder(view);
600 if (viewHolder instanceof MyPhotosCategoryHolder
601 || viewHolder instanceof FeaturedCategoryHolder) {
602 outRect.bottom = getResources().getDimensionPixelSize(
603 R.dimen.grid_item_featured_category_padding_bottom);
604 } else {
605 outRect.bottom = getResources().getDimensionPixelSize(
606 R.dimen.grid_item_category_padding_bottom);
607 }
Chuck Liao1e0501f2020-02-17 18:20:54 +0800608 }
609 }
zonghuayang2b441632021-12-20 12:42:33 +0800610 @Override
611 public void onActivityResult(int requestCode, int resultCode, Intent data) {
612 if (requestCode == SETTINGS_APP_INFO_REQUEST_CODE) {
613 notifyDataSetChanged();
614 }
615 }
Chuck Liao1e0501f2020-02-17 18:20:54 +0800616 /**
617 * SpanSizeLookup subclass which provides that the item in the first position spans the number
618 * of columns in the RecyclerView and all other items only take up a single span.
619 */
620 private class CategorySpanSizeLookup extends GridLayoutManager.SpanSizeLookup {
Chuck Liaoddf2b522021-04-15 00:36:25 +0800621 private static final int DEFAULT_CATEGORY_SPAN_SIZE = 2;
622
Chuck Liao1e0501f2020-02-17 18:20:54 +0800623 CategoryAdapter mAdapter;
624
625 private CategorySpanSizeLookup(CategoryAdapter adapter) {
626 mAdapter = adapter;
627 }
628
629 @Override
630 public int getSpanSize(int position) {
Chuck Liaoddf2b522021-04-15 00:36:25 +0800631 if (position < NUM_NON_CATEGORY_VIEW_HOLDERS || mAdapter.getItemViewType(position)
632 == CategoryAdapter.ITEM_VIEW_TYPE_LOADING_INDICATOR || mAdapter.getItemViewType(
633 position) == CategoryAdapter.ITEM_VIEW_TYPE_MY_PHOTOS) {
634 return getNumColumns() * DEFAULT_CATEGORY_SPAN_SIZE;
Chuck Liao1e0501f2020-02-17 18:20:54 +0800635 }
636
Chuck Liaoddf2b522021-04-15 00:36:25 +0800637 if (mAdapter.getItemViewType(position)
638 == CategoryAdapter.ITEM_VIEW_TYPE_FEATURED_CATEGORY) {
639 return getNumColumns() * DEFAULT_CATEGORY_SPAN_SIZE / 2;
640 }
641
642 return DEFAULT_CATEGORY_SPAN_SIZE;
Chuck Liao1e0501f2020-02-17 18:20:54 +0800643 }
644 }
Chuck Liao1e0501f2020-02-17 18:20:54 +0800645}