• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2021 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  */
16 package com.android.providers.media.photopicker.ui;
17 
18 import static com.android.providers.media.photopicker.util.LayoutModeUtils.MODE_ALBUM_PHOTOS_TAB;
19 import static com.android.providers.media.photopicker.util.LayoutModeUtils.MODE_PHOTOS_TAB;
20 
21 import android.content.Context;
22 import android.os.Bundle;
23 import android.text.TextUtils;
24 import android.view.View;
25 
26 import androidx.annotation.NonNull;
27 import androidx.annotation.Nullable;
28 import androidx.fragment.app.Fragment;
29 import androidx.fragment.app.FragmentManager;
30 import androidx.fragment.app.FragmentTransaction;
31 import androidx.lifecycle.LiveData;
32 import androidx.lifecycle.MutableLiveData;
33 
34 import com.android.providers.media.R;
35 import com.android.providers.media.photopicker.data.model.Category;
36 import com.android.providers.media.photopicker.data.model.Item;
37 import com.android.providers.media.photopicker.util.LayoutModeUtils;
38 import com.android.providers.media.util.StringUtils;
39 
40 import com.google.android.material.snackbar.Snackbar;
41 
42 import java.text.NumberFormat;
43 import java.util.List;
44 import java.util.Locale;
45 
46 /**
47  * Photos tab fragment for showing the photos
48  */
49 public class PhotosTabFragment extends TabFragment {
50     private static final int MINIMUM_SPAN_COUNT = 3;
51     private static final int GRID_COLUMN_COUNT = 3;
52     private static final String FRAGMENT_TAG = "PhotosTabFragment";
53 
54     private Category mCategory = Category.DEFAULT;
55 
56     @Override
onCreate(Bundle savedInstanceState)57     public void onCreate(Bundle savedInstanceState) {
58         super.onCreate(savedInstanceState);
59         // After the configuration is changed, if the fragment is now shown, onViewCreated will not
60         // be triggered. We need to restore the savedInstanceState in onCreate.
61         // E.g. Click the albums -> preview one item -> rotate the device
62         if (savedInstanceState != null) {
63             mCategory = Category.fromBundle(savedInstanceState);
64         }
65     }
66 
67     @Override
onViewCreated(@onNull View view, @Nullable Bundle savedInstanceState)68     public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
69         super.onViewCreated(view, savedInstanceState);
70         final Context context = getContext();
71 
72         // We only add the RECENT header on the PhotosTabFragment with CATEGORY_DEFAULT. In this
73         // case, we call this method {loadItems} with null category. When the category is not
74         // empty, we don't show the RECENT header.
75         final boolean showRecentSection = mCategory.isDefault();
76 
77         // We only show the Banners on the PhotosTabFragment with CATEGORY_DEFAULT (Main grid).
78         final boolean shouldShowBanners = mCategory.isDefault();
79         final LiveData<Boolean> doNotShowBanner = new MutableLiveData<>(false);
80         final LiveData<Boolean> showChooseAppBanner = shouldShowBanners
81                 ? mPickerViewModel.shouldShowChooseAppBannerLiveData() : doNotShowBanner;
82         final LiveData<Boolean> showCloudMediaAvailableBanner = shouldShowBanners
83                 ? mPickerViewModel.shouldShowCloudMediaAvailableBannerLiveData() : doNotShowBanner;
84         final LiveData<Boolean> showAccountUpdatedBanner = shouldShowBanners
85                 ? mPickerViewModel.shouldShowAccountUpdatedBannerLiveData() : doNotShowBanner;
86         final LiveData<Boolean> showChooseAccountBanner = shouldShowBanners
87                 ? mPickerViewModel.shouldShowChooseAccountBannerLiveData() : doNotShowBanner;
88 
89         final PhotosTabAdapter adapter = new PhotosTabAdapter(showRecentSection, mSelection,
90                 mImageLoader, this::onItemClick, this::onItemLongClick, /* lifecycleOwner */ this,
91                 mPickerViewModel.getCloudMediaProviderAppTitleLiveData(),
92                 mPickerViewModel.getCloudMediaAccountNameLiveData(), showChooseAppBanner,
93                 showCloudMediaAvailableBanner, showAccountUpdatedBanner, showChooseAccountBanner,
94                 mOnChooseAppBannerEventListener, mOnCloudMediaAvailableBannerEventListener,
95                 mOnAccountUpdatedBannerEventListener, mOnChooseAccountBannerEventListener);
96 
97         if (mCategory.isDefault()) {
98             setEmptyMessage(R.string.picker_photos_empty_message);
99             // Set the pane title for A11y
100             view.setAccessibilityPaneTitle(getString(R.string.picker_photos));
101             mPickerViewModel.getItems()
102                     .observe(this, itemList -> onChangeMediaItems(itemList, adapter));
103         } else {
104             setEmptyMessage(R.string.picker_album_media_empty_message);
105             // Set the pane title for A11y
106             view.setAccessibilityPaneTitle(mCategory.getDisplayName(context));
107             mPickerViewModel.getCategoryItems(mCategory)
108                     .observe(this, itemList -> onChangeMediaItems(itemList, adapter));
109         }
110 
111         final PhotosTabItemDecoration itemDecoration = new PhotosTabItemDecoration(context);
112 
113         final int spacing = getResources().getDimensionPixelSize(R.dimen.picker_photo_item_spacing);
114         final int photoSize = getResources().getDimensionPixelSize(R.dimen.picker_photo_size);
115         mRecyclerView.setColumnWidth(photoSize + spacing);
116         mRecyclerView.setMinimumSpanCount(MINIMUM_SPAN_COUNT);
117 
118         setLayoutManager(adapter, GRID_COLUMN_COUNT);
119         mRecyclerView.setAdapter(adapter);
120         mRecyclerView.addItemDecoration(itemDecoration);
121     }
122 
123     /**
124      * Called when owning activity is saving state to be used to restore state during creation.
125      *
126      * @param state Bundle to save state
127      */
onSaveInstanceState(Bundle state)128     public void onSaveInstanceState(Bundle state) {
129         super.onSaveInstanceState(state);
130         mCategory.toBundle(state);
131     }
132 
133     @Override
onResume()134     public void onResume() {
135         super.onResume();
136 
137         final String title;
138         final LayoutModeUtils.Mode layoutMode;
139         final boolean shouldHideProfileButton;
140         if (mCategory.isDefault()) {
141             title = "";
142             layoutMode = MODE_PHOTOS_TAB;
143             shouldHideProfileButton = false;
144         } else {
145             title = mCategory.getDisplayName(getContext());
146             layoutMode = MODE_ALBUM_PHOTOS_TAB;
147             shouldHideProfileButton = true;
148         }
149 
150         getPickerActivity().updateCommonLayouts(layoutMode, title);
151         hideProfileButton(shouldHideProfileButton);
152     }
153 
onChangeMediaItems(@onNull List<Item> itemList, @NonNull PhotosTabAdapter adapter)154     private void onChangeMediaItems(@NonNull List<Item> itemList,
155             @NonNull PhotosTabAdapter adapter) {
156         adapter.setMediaItems(itemList);
157         // Handle emptyView's visibility
158         updateVisibilityForEmptyView(/* shouldShowEmptyView */ itemList.size() == 0);
159     }
160 
onItemClick(@onNull View view)161     private void onItemClick(@NonNull View view) {
162         if (mSelection.canSelectMultiple()) {
163             final boolean isSelectedBefore = view.isSelected();
164 
165             if (isSelectedBefore) {
166                 mSelection.removeSelectedItem((Item) view.getTag());
167             } else {
168                 if (!mSelection.isSelectionAllowed()) {
169                     final int maxCount = mSelection.getMaxSelectionLimit();
170                     final CharSequence quantityText =
171                         StringUtils.getICUFormatString(
172                             getResources(), maxCount, R.string.select_up_to);
173                     final String itemCountString = NumberFormat.getInstance(Locale.getDefault())
174                         .format(maxCount);
175                     final CharSequence message = TextUtils.expandTemplate(quantityText,
176                         itemCountString);
177                     Snackbar.make(view, message, Snackbar.LENGTH_SHORT).show();
178                     return;
179                 } else {
180                     final Item item = (Item) view.getTag();
181                     mSelection.addSelectedItem(item);
182                 }
183             }
184             view.setSelected(!isSelectedBefore);
185             // There is an issue b/223695510 about not selected in Accessibility mode. It only says
186             // selected state, but it doesn't say not selected state. Add the not selected only to
187             // avoid that it says selected twice.
188             view.setStateDescription(isSelectedBefore ? getString(R.string.not_selected) : null);
189         } else {
190             final Item item = (Item) view.getTag();
191             mSelection.setSelectedItem(item);
192             getPickerActivity().setResultAndFinishSelf();
193         }
194     }
195 
onItemLongClick(@onNull View view)196     private boolean onItemLongClick(@NonNull View view) {
197         final Item item = (Item) view.getTag();
198         if (!mSelection.canSelectMultiple()) {
199             // In single select mode, if the item is previewed, we set it as selected item. This is
200             // will assist in "Add" button click to return all selected items.
201             // For multi select, long click only previews the item, and until user selects the item,
202             // it doesn't get added to selected items. Also, there is no "Add" button in the preview
203             // layout that can return selected items.
204             mSelection.setSelectedItem(item);
205         }
206         mSelection.prepareItemForPreviewOnLongPress(item);
207         // Transition to PreviewFragment.
208         PreviewFragment.show(getActivity().getSupportFragmentManager(),
209                 PreviewFragment.getArgsForPreviewOnLongPress());
210         return true;
211     }
212 
213     /**
214      * Create the fragment with the category and add it into the FragmentManager
215      *
216      * @param fm the fragment manager
217      * @param category the category
218      */
show(FragmentManager fm, Category category)219     public static void show(FragmentManager fm, Category category) {
220         final FragmentTransaction ft = fm.beginTransaction();
221         final PhotosTabFragment fragment = new PhotosTabFragment();
222         fragment.mCategory = category;
223         ft.replace(R.id.fragment_container, fragment, FRAGMENT_TAG);
224         if (!fragment.mCategory.isDefault()) {
225             ft.addToBackStack(FRAGMENT_TAG);
226         }
227         ft.commitAllowingStateLoss();
228     }
229 
230     /**
231      * Get the fragment in the FragmentManager
232      *
233      * @param fm The fragment manager
234      */
get(FragmentManager fm)235     public static Fragment get(FragmentManager fm) {
236         return fm.findFragmentByTag(FRAGMENT_TAG);
237     }
238 }
239