blob: 2b6c71dcd489414e5caa32af2d194190069f2971 [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;
50import com.android.wallpaper.model.Category;
Chuck Liaoddf2b522021-04-15 00:36:25 +080051import com.android.wallpaper.model.CategoryProvider;
Santiago Etchebehere39c31342021-05-03 15:35:06 -070052import com.android.wallpaper.model.LiveWallpaperInfo;
53import com.android.wallpaper.model.WallpaperInfo;
Chuck Liao1e0501f2020-02-17 18:20:54 +080054import com.android.wallpaper.module.InjectorProvider;
55import com.android.wallpaper.module.UserEventLogger;
Chuck Liaof6b4b192020-08-07 02:31:32 +080056import com.android.wallpaper.util.DeepLinkUtils;
Chuck Liao1e0501f2020-02-17 18:20:54 +080057import com.android.wallpaper.util.DisplayMetricsRetriever;
Tianguang Zhangd3e4f632021-04-14 00:30:15 +020058import com.android.wallpaper.util.ResourceUtils;
Santiago Etchebehere53c63432020-05-07 18:55:35 -070059import com.android.wallpaper.util.SizeCalculator;
Wesley.CW Wangdc68fde2020-06-15 19:12:33 +080060import com.android.wallpaper.widget.WallpaperPickerRecyclerViewAccessibilityDelegate;
61import com.android.wallpaper.widget.WallpaperPickerRecyclerViewAccessibilityDelegate.BottomSheetHost;
Chuck Liao1e0501f2020-02-17 18:20:54 +080062
63import com.bumptech.glide.Glide;
zonghuayang2b441632021-12-20 12:42:33 +080064import com.google.android.material.snackbar.Snackbar;
Chuck Liao1e0501f2020-02-17 18:20:54 +080065
66import java.util.ArrayList;
67import java.util.List;
68
69/**
70 * Displays the UI which contains the categories of the wallpaper.
71 */
Chuck Liao58aca1c2021-03-17 01:20:55 +080072public class CategorySelectorFragment extends AppbarFragment {
Chuck Liao1e0501f2020-02-17 18:20:54 +080073
74 // The number of ViewHolders that don't pertain to category tiles.
75 // Currently 2: one for the metadata section and one for the "Select wallpaper" header.
76 private static final int NUM_NON_CATEGORY_VIEW_HOLDERS = 0;
77 private static final int SETTINGS_APP_INFO_REQUEST_CODE = 1;
78 private static final String TAG = "CategorySelectorFragment";
79
80 /**
81 * Interface to be implemented by an Fragment hosting a {@link CategorySelectorFragment}
82 */
83 public interface CategorySelectorFragmentHost {
84
85 /**
86 * Requests to show the Android custom photo picker for the sake of picking a photo
87 * to set as the device's wallpaper.
88 */
89 void requestCustomPhotoPicker(MyPhotosStarter.PermissionChangedListener listener);
90
91 /**
92 * Shows the wallpaper page of the specific category.
93 *
Chuck Liaoa63f1bf2020-06-11 01:35:42 +080094 * @param category the wallpaper's {@link Category}
Chuck Liao1e0501f2020-02-17 18:20:54 +080095 */
Chuck Liaoa63f1bf2020-06-11 01:35:42 +080096 void show(Category category);
Chuck Liao0088bca2020-05-28 16:03:23 +080097
Chuck Liao58aca1c2021-03-17 01:20:55 +080098
Chuck Liao0088bca2020-05-28 16:03:23 +080099 /**
Chuck Liao58aca1c2021-03-17 01:20:55 +0800100 * Indicates if the host has toolbar to show the title. If it does, we should set the title
101 * there.
102 */
103 boolean isHostToolbarShown();
104
105 /**
106 * Sets the title in the host's toolbar.
Chuck Liao0088bca2020-05-28 16:03:23 +0800107 */
108 void setToolbarTitle(CharSequence title);
Chuck Liaof6b4b192020-08-07 02:31:32 +0800109
110 /**
111 * Fetches the wallpaper categories.
112 */
113 void fetchCategories();
Chuck Liao81145b22020-09-03 09:52:25 +0800114
115 /**
Chuck Liao3abf15b2020-12-17 22:33:02 +0800116 * Cleans up the listeners which will be notified when there's a package event.
117 */
118 void cleanUp();
Chuck Liao1e0501f2020-02-17 18:20:54 +0800119 }
120
121 private RecyclerView mImageGrid;
122 private CategoryAdapter mAdapter;
George Linfeed6b12022-12-28 04:04:24 +0000123 private CategoryProvider mCategoryProvider;
Chuck Liao1e0501f2020-02-17 18:20:54 +0800124 private ArrayList<Category> mCategories = new ArrayList<>();
125 private Point mTileSizePx;
126 private boolean mAwaitingCategories;
Chuck Liaoc4462d02021-05-20 01:06:23 +0800127 private boolean mIsFeaturedCollectionAvailable;
Chuck Liao1e0501f2020-02-17 18:20:54 +0800128
George Linfeed6b12022-12-28 04:04:24 +0000129 @Override
130 public void onCreate(@Nullable Bundle savedInstanceState) {
131 super.onCreate(savedInstanceState);
Chuck Liao1e0501f2020-02-17 18:20:54 +0800132 mAdapter = new CategoryAdapter(mCategories);
George Linfeed6b12022-12-28 04:04:24 +0000133 mCategoryProvider = InjectorProvider.getInjector().getCategoryProvider(requireContext());
zonghuayang2b441632021-12-20 12:42:33 +0800134 }
135
Chuck Liao1e0501f2020-02-17 18:20:54 +0800136 @Nullable
137 @Override
138 public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container,
139 @Nullable Bundle savedInstanceState) {
140 View view = inflater.inflate(R.layout.fragment_category_selector, container,
141 /* attachToRoot= */ false);
Chuck Liao1e0501f2020-02-17 18:20:54 +0800142 mImageGrid = view.findViewById(R.id.category_grid);
Chuck Liao1e6cb682021-04-22 00:51:11 +0800143 mImageGrid.addItemDecoration(new GridPaddingDecoration(getResources().getDimensionPixelSize(
144 R.dimen.grid_item_category_padding_horizontal)));
Chuck Liao1e0501f2020-02-17 18:20:54 +0800145
Santiago Etchebehere53c63432020-05-07 18:55:35 -0700146 mTileSizePx = SizeCalculator.getCategoryTileSize(getActivity());
Chuck Liao1e0501f2020-02-17 18:20:54 +0800147
148 mImageGrid.setAdapter(mAdapter);
149
Chuck Liaoddf2b522021-04-15 00:36:25 +0800150 GridLayoutManager gridLayoutManager = new GridLayoutManager(getActivity(),
151 getNumColumns() * CategorySpanSizeLookup.DEFAULT_CATEGORY_SPAN_SIZE);
152 gridLayoutManager.setSpanSizeLookup(new CategorySpanSizeLookup(mAdapter));
Chuck Liao1e0501f2020-02-17 18:20:54 +0800153 mImageGrid.setLayoutManager(gridLayoutManager);
Wesley.CW Wang27ff4262020-06-12 19:19:57 +0800154 mImageGrid.setAccessibilityDelegateCompat(
Wesley.CW Wangdc68fde2020-06-15 19:12:33 +0800155 new WallpaperPickerRecyclerViewAccessibilityDelegate(
156 mImageGrid, (BottomSheetHost) getParentFragment(), getNumColumns()));
Chuck Liao58aca1c2021-03-17 01:20:55 +0800157
158 if (getCategorySelectorFragmentHost().isHostToolbarShown()) {
159 view.findViewById(R.id.header_bar).setVisibility(View.GONE);
160 getCategorySelectorFragmentHost().setToolbarTitle(getText(R.string.wallpaper_title));
161 } else {
162 setUpToolbar(view);
163 setTitle(getText(R.string.wallpaper_title));
164 }
Chuck Liao1e0501f2020-02-17 18:20:54 +0800165
Chuck Liaof6b4b192020-08-07 02:31:32 +0800166 if (!DeepLinkUtils.isDeepLink(getActivity().getIntent())) {
167 getCategorySelectorFragmentHost().fetchCategories();
168 }
169
Chihhang Chuange6c73222021-04-14 22:36:41 +0800170 // For nav bar edge-to-edge effect.
John Panc829d732022-01-11 21:25:02 +0800171 mImageGrid.setOnApplyWindowInsetsListener((v, windowInsets) -> {
Chihhang Chuange6c73222021-04-14 22:36:41 +0800172 v.setPadding(
173 v.getPaddingLeft(),
John Panc829d732022-01-11 21:25:02 +0800174 v.getPaddingTop(),
Chihhang Chuange6c73222021-04-14 22:36:41 +0800175 v.getPaddingRight(),
176 windowInsets.getSystemWindowInsetBottom());
177 return windowInsets.consumeSystemWindowInsets();
178 });
Chuck Liao1e0501f2020-02-17 18:20:54 +0800179 return view;
180 }
181
Chuck Liao3abf15b2020-12-17 22:33:02 +0800182 @Override
183 public void onDestroyView() {
184 getCategorySelectorFragmentHost().cleanUp();
185 super.onDestroyView();
186 }
187
Chuck Liao1e0501f2020-02-17 18:20:54 +0800188 /**
189 * Inserts the given category into the categories list in priority order.
190 */
191 void addCategory(Category category, boolean loading) {
192 // If not previously waiting for categories, enter the waiting state by showing the loading
193 // indicator.
194 if (loading && !mAwaitingCategories) {
195 mAdapter.notifyItemChanged(getNumColumns());
196 mAdapter.notifyItemInserted(getNumColumns());
197 mAwaitingCategories = true;
198 }
199 // Not add existing category to category list
200 if (mCategories.indexOf(category) >= 0) {
201 updateCategory(category);
202 return;
203 }
204
205 int priority = category.getPriority();
206
207 int index = 0;
208 while (index < mCategories.size() && priority >= mCategories.get(index).getPriority()) {
209 index++;
210 }
211
212 mCategories.add(index, category);
213 if (mAdapter != null) {
214 // Offset the index because of the static metadata element at beginning of RecyclerView.
215 mAdapter.notifyItemInserted(index + NUM_NON_CATEGORY_VIEW_HOLDERS);
216 }
217 }
218
219 void removeCategory(Category category) {
220 int index = mCategories.indexOf(category);
221 if (index >= 0) {
222 mCategories.remove(index);
223 mAdapter.notifyItemRemoved(index + NUM_NON_CATEGORY_VIEW_HOLDERS);
224 }
225 }
226
227 void updateCategory(Category category) {
228 int index = mCategories.indexOf(category);
229 if (index >= 0) {
230 mCategories.remove(index);
231 mCategories.add(index, category);
232 mAdapter.notifyItemChanged(index + NUM_NON_CATEGORY_VIEW_HOLDERS);
233 }
234 }
235
236 void clearCategories() {
237 mCategories.clear();
238 mAdapter.notifyDataSetChanged();
239 }
240
241 /**
Chuck Liao5db91d92021-12-27 19:42:36 +0800242 * Notifies that no further categories are expected so it may hide the loading indicator.
Chuck Liao1e0501f2020-02-17 18:20:54 +0800243 */
244 void doneFetchingCategories() {
245 if (mAwaitingCategories) {
246 mAdapter.notifyItemRemoved(mAdapter.getItemCount() - 1);
247 mAwaitingCategories = false;
248 }
Chuck Liaoc4462d02021-05-20 01:06:23 +0800249
250 mIsFeaturedCollectionAvailable = mCategoryProvider.isFeaturedCollectionAvailable();
Chuck Liao1e0501f2020-02-17 18:20:54 +0800251 }
252
253 void notifyDataSetChanged() {
254 mAdapter.notifyDataSetChanged();
255 }
256
257 private int getNumColumns() {
258 Activity activity = getActivity();
Chuck Liaoe2fe0302020-06-29 21:15:35 +0800259 return activity == null ? 1 : SizeCalculator.getNumCategoryColumns(activity);
Chuck Liao1e0501f2020-02-17 18:20:54 +0800260 }
261
262
263 private CategorySelectorFragmentHost getCategorySelectorFragmentHost() {
Chuck Liao8cbe49f2021-03-15 20:28:04 +0800264 Fragment parentFragment = getParentFragment();
265 if (parentFragment != null) {
266 return (CategorySelectorFragmentHost) parentFragment;
267 } else {
268 return (CategorySelectorFragmentHost) getActivity();
269 }
Chuck Liao1e0501f2020-02-17 18:20:54 +0800270 }
271
272 /**
273 * ViewHolder subclass for a category tile in the RecyclerView.
274 */
275 private class CategoryHolder extends RecyclerView.ViewHolder implements View.OnClickListener {
276 private Category mCategory;
277 private ImageView mImageView;
278 private ImageView mOverlayIconView;
279 private TextView mTitleView;
280
281 CategoryHolder(View itemView) {
282 super(itemView);
283 itemView.setOnClickListener(this);
284
285 mImageView = itemView.findViewById(R.id.image);
286 mOverlayIconView = itemView.findViewById(R.id.overlay_icon);
287 mTitleView = itemView.findViewById(R.id.category_title);
288
289 CardView categoryView = itemView.findViewById(R.id.category);
290 categoryView.getLayoutParams().height = mTileSizePx.y;
Ching-Sung Li1ac024f2021-06-08 14:43:28 +0800291 categoryView.setRadius(getResources().getDimension(R.dimen.grid_item_all_radius_small));
Chuck Liao1e0501f2020-02-17 18:20:54 +0800292 }
293
294 @Override
295 public void onClick(View view) {
Santiago Etchebehere39c31342021-05-03 15:35:06 -0700296 Activity activity = getActivity();
Chuck Liao1e0501f2020-02-17 18:20:54 +0800297 final UserEventLogger eventLogger =
Santiago Etchebehere39c31342021-05-03 15:35:06 -0700298 InjectorProvider.getInjector().getUserEventLogger(activity);
Chuck Liao1e0501f2020-02-17 18:20:54 +0800299 eventLogger.logCategorySelected(mCategory.getCollectionId());
300
301 if (mCategory.supportsCustomPhotos()) {
302 getCategorySelectorFragmentHost().requestCustomPhotoPicker(
303 new MyPhotosStarter.PermissionChangedListener() {
304 @Override
305 public void onPermissionsGranted() {
306 drawThumbnailAndOverlayIcon();
307 }
308
309 @Override
310 public void onPermissionsDenied(boolean dontAskAgain) {
zonghuayang2b441632021-12-20 12:42:33 +0800311 if (dontAskAgain) {
312 showPermissionSnackbar();
313 }
Chuck Liao1e0501f2020-02-17 18:20:54 +0800314 }
315 });
316 return;
317 }
318
Santiago Etchebehere39c31342021-05-03 15:35:06 -0700319 if (mCategory.isSingleWallpaperCategory()) {
320 WallpaperInfo wallpaper = mCategory.getSingleWallpaper();
321 // Log click on individual wallpaper
322 eventLogger.logIndividualWallpaperSelected(mCategory.getCollectionId());
323
324 InjectorProvider.getInjector().getWallpaperPersister(activity)
325 .setWallpaperInfoInPreview(wallpaper);
326 wallpaper.showPreview(activity,
327 new PreviewActivity.PreviewActivityIntentFactory(),
328 wallpaper instanceof LiveWallpaperInfo ? PREVIEW_LIVE_WALLPAPER_REQUEST_CODE
329 : PREVIEW_WALLPAPER_REQUEST_CODE);
330 return;
331 }
332
Chuck Liaoa63f1bf2020-06-11 01:35:42 +0800333 getCategorySelectorFragmentHost().show(mCategory);
Chuck Liao1e0501f2020-02-17 18:20:54 +0800334 }
335
336 /**
337 * Binds the given category to this CategoryHolder.
338 */
339 private void bindCategory(Category category) {
340 mCategory = category;
341 mTitleView.setText(category.getTitle());
342 drawThumbnailAndOverlayIcon();
343 }
344
345 /**
346 * Draws the CategoryHolder's thumbnail and overlay icon.
347 */
348 private void drawThumbnailAndOverlayIcon() {
349 mOverlayIconView.setImageDrawable(mCategory.getOverlayIcon(
350 getActivity().getApplicationContext()));
Chuck Liao1e0501f2020-02-17 18:20:54 +0800351 Asset thumbnail = mCategory.getThumbnail(getActivity().getApplicationContext());
352 if (thumbnail != null) {
zonghuayang21aa2762021-12-08 18:06:21 +0800353 // Size the overlay icon according to the category.
354 int overlayIconDimenDp = mCategory.getOverlayIconSizeDp();
355 DisplayMetrics metrics = DisplayMetricsRetriever.getInstance().getDisplayMetrics(
356 getResources(), getActivity().getWindowManager().getDefaultDisplay());
357 int overlayIconDimenPx = (int) (overlayIconDimenDp * metrics.density);
358 mOverlayIconView.getLayoutParams().width = overlayIconDimenPx;
359 mOverlayIconView.getLayoutParams().height = overlayIconDimenPx;
360 thumbnail.loadDrawable(getActivity(), mImageView,
361 ResourceUtils.getColorAttr(
362 getActivity(),
363 android.R.attr.colorSecondary
364 ));
Chuck Liao1e0501f2020-02-17 18:20:54 +0800365 } else {
366 // TODO(orenb): Replace this workaround for b/62584914 with a proper way of
367 // unloading the ImageView such that no incorrect image is improperly loaded upon
368 // rapid scroll.
zonghuayang21aa2762021-12-08 18:06:21 +0800369 mImageView.setBackgroundColor(
370 getResources().getColor(R.color.myphoto_background_color));
Chuck Liao1e0501f2020-02-17 18:20:54 +0800371 Object nullObj = null;
372 Glide.with(getActivity())
373 .asDrawable()
374 .load(nullObj)
375 .into(mImageView);
376
377 }
378 }
379 }
380
zonghuayang2b441632021-12-20 12:42:33 +0800381 private void showPermissionSnackbar() {
382 Snackbar snackbar = Snackbar.make(getView(), R.string.settings_snackbar_description,
383 Snackbar.LENGTH_LONG);
384 Snackbar.SnackbarLayout layout = (Snackbar.SnackbarLayout) snackbar.getView();
385 TextView textView = (TextView) layout.findViewById(R.id.snackbar_text);
386 layout.setBackgroundResource(R.drawable.snackbar_background);
387 TypedArray typedArray = getContext().obtainStyledAttributes(
388 new int[]{android.R.attr.textColorPrimary,
389 com.android.internal.R.attr.colorAccentPrimaryVariant});
390 textView.setTextColor(typedArray.getColor(0, Color.TRANSPARENT));
391 snackbar.setActionTextColor(typedArray.getColor(1, Color.TRANSPARENT));
392 typedArray.recycle();
393
394 snackbar.setAction(getContext().getString(R.string.settings_snackbar_enable),
395 new View.OnClickListener() {
396 @Override
397 public void onClick(View view) {
398 startSettings(SETTINGS_APP_INFO_REQUEST_CODE);
399 }
400 });
401 snackbar.show();
402 }
403
404 private void startSettings(int resultCode) {
405 Activity activity = getActivity();
406 if (activity == null) {
407 return;
408 }
409 Intent appInfoIntent = new Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS);
410 Uri uri = Uri.fromParts("package", activity.getPackageName(), /* fragment= */ null);
411 appInfoIntent.setData(uri);
412 startActivityForResult(appInfoIntent, resultCode);
413 }
414
Chuck Liaoddf2b522021-04-15 00:36:25 +0800415 private class FeaturedCategoryHolder extends CategoryHolder {
416
417 FeaturedCategoryHolder(View itemView) {
418 super(itemView);
419 CardView categoryView = itemView.findViewById(R.id.category);
420 categoryView.getLayoutParams().height =
421 SizeCalculator.getFeaturedCategoryTileSize(getActivity()).y;
Ching-Sung Li1ac024f2021-06-08 14:43:28 +0800422 categoryView.setRadius(getResources().getDimension(R.dimen.grid_item_all_radius));
Chuck Liaoddf2b522021-04-15 00:36:25 +0800423 }
424 }
425
426 private class MyPhotosCategoryHolder extends CategoryHolder {
427
428 MyPhotosCategoryHolder(View itemView) {
429 super(itemView);
430 // Reuse the height of featured category since My Photos category & featured category
431 // have the same height in current UI design.
432 CardView categoryView = itemView.findViewById(R.id.category);
Tianguang Zhangbae57642021-04-19 19:46:33 +0200433 int height = SizeCalculator.getFeaturedCategoryTileSize(getActivity()).y;
434 categoryView.getLayoutParams().height = height;
435 // Use the height as the card corner radius for the "My photos" category
436 // for a stadium border.
437 categoryView.setRadius(height);
Chuck Liaoddf2b522021-04-15 00:36:25 +0800438 }
439 }
440
Chuck Liao1e0501f2020-02-17 18:20:54 +0800441 /**
442 * ViewHolder subclass for the loading indicator ("spinner") shown when categories are being
443 * fetched.
444 */
445 private class LoadingIndicatorHolder extends RecyclerView.ViewHolder {
446 private LoadingIndicatorHolder(View view) {
447 super(view);
448 ProgressBar progressBar = view.findViewById(R.id.loading_indicator);
449 progressBar.getIndeterminateDrawable().setColorFilter(
Tianguang Zhangd3e4f632021-04-14 00:30:15 +0200450 ResourceUtils.getColorAttr(
451 getActivity(),
452 android.R.attr.colorAccent
453 ), PorterDuff.Mode.SRC_IN);
Chuck Liao1e0501f2020-02-17 18:20:54 +0800454 }
455 }
456
457 /**
458 * RecyclerView Adapter subclass for the category tiles in the RecyclerView.
459 */
460 private class CategoryAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder>
461 implements MyPhotosStarter.PermissionChangedListener {
Chuck Liaoddf2b522021-04-15 00:36:25 +0800462 private static final int ITEM_VIEW_TYPE_MY_PHOTOS = 1;
463 private static final int ITEM_VIEW_TYPE_FEATURED_CATEGORY = 2;
Chuck Liao1e0501f2020-02-17 18:20:54 +0800464 private static final int ITEM_VIEW_TYPE_CATEGORY = 3;
465 private static final int ITEM_VIEW_TYPE_LOADING_INDICATOR = 4;
466 private List<Category> mCategories;
467
468 private CategoryAdapter(List<Category> categories) {
469 mCategories = categories;
470 }
471
472 @Override
473 public int getItemViewType(int position) {
474 if (mAwaitingCategories && position == getItemCount() - 1) {
475 return ITEM_VIEW_TYPE_LOADING_INDICATOR;
476 }
477
Chuck Liaoddf2b522021-04-15 00:36:25 +0800478 if (position == 0) {
479 return ITEM_VIEW_TYPE_MY_PHOTOS;
480 }
481
Chuck Liaoc4462d02021-05-20 01:06:23 +0800482 if (mIsFeaturedCollectionAvailable && (position == 1 || position == 2)) {
Chuck Liaoddf2b522021-04-15 00:36:25 +0800483 return ITEM_VIEW_TYPE_FEATURED_CATEGORY;
484 }
485
Chuck Liao1e0501f2020-02-17 18:20:54 +0800486 return ITEM_VIEW_TYPE_CATEGORY;
487 }
488
489 @Override
490 public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
491 LayoutInflater layoutInflater = LayoutInflater.from(getActivity());
492 View view;
493
494 switch (viewType) {
495 case ITEM_VIEW_TYPE_LOADING_INDICATOR:
496 view = layoutInflater.inflate(R.layout.grid_item_loading_indicator,
497 parent, /* attachToRoot= */ false);
498 return new LoadingIndicatorHolder(view);
Chuck Liaoddf2b522021-04-15 00:36:25 +0800499 case ITEM_VIEW_TYPE_MY_PHOTOS:
500 view = layoutInflater.inflate(R.layout.grid_item_category,
501 parent, /* attachToRoot= */ false);
502 return new MyPhotosCategoryHolder(view);
503 case ITEM_VIEW_TYPE_FEATURED_CATEGORY:
504 view = layoutInflater.inflate(R.layout.grid_item_category,
505 parent, /* attachToRoot= */ false);
506 return new FeaturedCategoryHolder(view);
Chuck Liao1e0501f2020-02-17 18:20:54 +0800507 case ITEM_VIEW_TYPE_CATEGORY:
508 view = layoutInflater.inflate(R.layout.grid_item_category,
509 parent, /* attachToRoot= */ false);
510 return new CategoryHolder(view);
511 default:
512 Log.e(TAG, "Unsupported viewType " + viewType + " in CategoryAdapter");
513 return null;
514 }
515 }
516
517 @Override
518 public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
519 int viewType = getItemViewType(position);
520
521 switch (viewType) {
Chuck Liaoddf2b522021-04-15 00:36:25 +0800522 case ITEM_VIEW_TYPE_MY_PHOTOS:
523 case ITEM_VIEW_TYPE_FEATURED_CATEGORY:
Chuck Liao1e0501f2020-02-17 18:20:54 +0800524 case ITEM_VIEW_TYPE_CATEGORY:
525 // Offset position to get category index to account for the non-category view
526 // holders.
527 Category category = mCategories.get(position - NUM_NON_CATEGORY_VIEW_HOLDERS);
528 ((CategoryHolder) holder).bindCategory(category);
529 break;
530 case ITEM_VIEW_TYPE_LOADING_INDICATOR:
531 // No op.
532 break;
533 default:
534 Log.e(TAG, "Unsupported viewType " + viewType + " in CategoryAdapter");
535 }
536 }
537
538 @Override
539 public int getItemCount() {
540 // Add to size of categories to account for the metadata related views.
541 // Add 1 more for the loading indicator if not yet done loading.
542 int size = mCategories.size() + NUM_NON_CATEGORY_VIEW_HOLDERS;
543 if (mAwaitingCategories) {
544 size += 1;
545 }
546
547 return size;
548 }
549
550 @Override
551 public void onPermissionsGranted() {
552 notifyDataSetChanged();
553 }
554
555 @Override
556 public void onPermissionsDenied(boolean dontAskAgain) {
557 if (!dontAskAgain) {
558 return;
559 }
560
561 String permissionNeededMessage =
562 getString(R.string.permission_needed_explanation_go_to_settings);
563 AlertDialog dialog = new AlertDialog.Builder(getActivity(), R.style.LightDialogTheme)
564 .setMessage(permissionNeededMessage)
565 .setPositiveButton(android.R.string.ok, null /* onClickListener */)
566 .setNegativeButton(
567 R.string.settings_button_label,
568 (dialogInterface, i) -> {
zonghuayang2b441632021-12-20 12:42:33 +0800569 startSettings(SETTINGS_APP_INFO_REQUEST_CODE);
Chuck Liao1e0501f2020-02-17 18:20:54 +0800570 })
571 .create();
572 dialog.show();
573 }
574 }
575
576 private class GridPaddingDecoration extends RecyclerView.ItemDecoration {
577
Chuck Liao1e6cb682021-04-22 00:51:11 +0800578 private final int mPadding;
Chuck Liao1e0501f2020-02-17 18:20:54 +0800579
580 GridPaddingDecoration(int padding) {
581 mPadding = padding;
582 }
583
584 @Override
585 public void getItemOffsets(Rect outRect, View view, RecyclerView parent,
586 RecyclerView.State state) {
587 int position = parent.getChildAdapterPosition(view) - NUM_NON_CATEGORY_VIEW_HOLDERS;
588 if (position >= 0) {
589 outRect.left = mPadding;
590 outRect.right = mPadding;
591 }
Chuck Liao1e6cb682021-04-22 00:51:11 +0800592
593 RecyclerView.ViewHolder viewHolder = parent.getChildViewHolder(view);
594 if (viewHolder instanceof MyPhotosCategoryHolder
595 || viewHolder instanceof FeaturedCategoryHolder) {
596 outRect.bottom = getResources().getDimensionPixelSize(
597 R.dimen.grid_item_featured_category_padding_bottom);
598 } else {
599 outRect.bottom = getResources().getDimensionPixelSize(
600 R.dimen.grid_item_category_padding_bottom);
601 }
Chuck Liao1e0501f2020-02-17 18:20:54 +0800602 }
603 }
zonghuayang2b441632021-12-20 12:42:33 +0800604 @Override
605 public void onActivityResult(int requestCode, int resultCode, Intent data) {
606 if (requestCode == SETTINGS_APP_INFO_REQUEST_CODE) {
607 notifyDataSetChanged();
608 }
609 }
Chuck Liao1e0501f2020-02-17 18:20:54 +0800610 /**
611 * SpanSizeLookup subclass which provides that the item in the first position spans the number
612 * of columns in the RecyclerView and all other items only take up a single span.
613 */
614 private class CategorySpanSizeLookup extends GridLayoutManager.SpanSizeLookup {
Chuck Liaoddf2b522021-04-15 00:36:25 +0800615 private static final int DEFAULT_CATEGORY_SPAN_SIZE = 2;
616
Chuck Liao1e0501f2020-02-17 18:20:54 +0800617 CategoryAdapter mAdapter;
618
619 private CategorySpanSizeLookup(CategoryAdapter adapter) {
620 mAdapter = adapter;
621 }
622
623 @Override
624 public int getSpanSize(int position) {
Chuck Liaoddf2b522021-04-15 00:36:25 +0800625 if (position < NUM_NON_CATEGORY_VIEW_HOLDERS || mAdapter.getItemViewType(position)
626 == CategoryAdapter.ITEM_VIEW_TYPE_LOADING_INDICATOR || mAdapter.getItemViewType(
627 position) == CategoryAdapter.ITEM_VIEW_TYPE_MY_PHOTOS) {
628 return getNumColumns() * DEFAULT_CATEGORY_SPAN_SIZE;
Chuck Liao1e0501f2020-02-17 18:20:54 +0800629 }
630
Chuck Liaoddf2b522021-04-15 00:36:25 +0800631 if (mAdapter.getItemViewType(position)
632 == CategoryAdapter.ITEM_VIEW_TYPE_FEATURED_CATEGORY) {
633 return getNumColumns() * DEFAULT_CATEGORY_SPAN_SIZE / 2;
634 }
635
636 return DEFAULT_CATEGORY_SPAN_SIZE;
Chuck Liao1e0501f2020-02-17 18:20:54 +0800637 }
638 }
Chuck Liao1e0501f2020-02-17 18:20:54 +0800639}