• 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 
17 package com.android.providers.media.photopicker.ui;
18 
19 import static com.android.providers.media.photopicker.ui.ItemsAction.ACTION_CLEAR_AND_UPDATE_LIST;
20 
21 import android.view.View;
22 import android.view.ViewGroup;
23 import android.widget.TextView;
24 
25 import androidx.annotation.NonNull;
26 import androidx.annotation.VisibleForTesting;
27 import androidx.lifecycle.LifecycleOwner;
28 import androidx.lifecycle.LiveData;
29 import androidx.recyclerview.widget.RecyclerView;
30 
31 import com.android.providers.media.R;
32 import com.android.providers.media.photopicker.data.Selection;
33 import com.android.providers.media.photopicker.data.model.Item;
34 import com.android.providers.media.photopicker.util.AccentColorResources;
35 import com.android.providers.media.photopicker.util.DateTimeUtils;
36 import com.android.providers.media.photopicker.viewmodel.PickerViewModel;
37 
38 import com.bumptech.glide.util.ViewPreloadSizeProvider;
39 
40 import java.util.ArrayList;
41 import java.util.Collections;
42 import java.util.List;
43 
44 /**
45  * Adapts from model to something RecyclerView understands.
46  */
47 public class PhotosTabAdapter extends TabAdapter {
48 
49     private static final int RECENT_MINIMUM_COUNT = 12;
50     private final LifecycleOwner mLifecycleOwner;
51     private final boolean mShowRecentSection;
52     private final OnMediaItemClickListener mOnMediaItemClickListener;
53     private final Selection mSelection;
54     private final ViewPreloadSizeProvider mPreloadSizeProvider;
55 
56     private final View.OnHoverListener mOnMediaItemHoverListener;
57 
PhotosTabAdapter(boolean showRecentSection, @NonNull Selection selection, @NonNull ImageLoader imageLoader, @NonNull OnMediaItemClickListener onMediaItemClickListener, @NonNull LifecycleOwner lifecycleOwner, @NonNull LiveData<String> cloudMediaProviderAppTitle, @NonNull LiveData<String> cloudMediaAccountName, @NonNull LiveData<Boolean> shouldShowChooseAppBanner, @NonNull LiveData<Boolean> shouldShowCloudMediaAvailableBanner, @NonNull LiveData<Boolean> shouldShowAccountUpdatedBanner, @NonNull LiveData<Boolean> shouldShowChooseAccountBanner, @NonNull OnBannerEventListener onChooseAppBannerEventListener, @NonNull OnBannerEventListener onCloudMediaAvailableBannerEventListener, @NonNull OnBannerEventListener onAccountUpdatedBannerEventListener, @NonNull OnBannerEventListener onChooseAccountBannerEventListener, @NonNull View.OnHoverListener onMediaItemHoverListener, @NonNull ViewPreloadSizeProvider preloadSizeProvider, @NonNull PickerViewModel pickerViewModel)58     PhotosTabAdapter(boolean showRecentSection,
59             @NonNull Selection selection,
60             @NonNull ImageLoader imageLoader,
61             @NonNull OnMediaItemClickListener onMediaItemClickListener,
62             @NonNull LifecycleOwner lifecycleOwner,
63             @NonNull LiveData<String> cloudMediaProviderAppTitle,
64             @NonNull LiveData<String> cloudMediaAccountName,
65             @NonNull LiveData<Boolean> shouldShowChooseAppBanner,
66             @NonNull LiveData<Boolean> shouldShowCloudMediaAvailableBanner,
67             @NonNull LiveData<Boolean> shouldShowAccountUpdatedBanner,
68             @NonNull LiveData<Boolean> shouldShowChooseAccountBanner,
69             @NonNull OnBannerEventListener onChooseAppBannerEventListener,
70             @NonNull OnBannerEventListener onCloudMediaAvailableBannerEventListener,
71             @NonNull OnBannerEventListener onAccountUpdatedBannerEventListener,
72             @NonNull OnBannerEventListener onChooseAccountBannerEventListener,
73             @NonNull View.OnHoverListener onMediaItemHoverListener,
74             @NonNull ViewPreloadSizeProvider preloadSizeProvider,
75             @NonNull PickerViewModel pickerViewModel) {
76         super(imageLoader, lifecycleOwner, cloudMediaProviderAppTitle, cloudMediaAccountName,
77                 shouldShowChooseAppBanner, shouldShowCloudMediaAvailableBanner,
78                 shouldShowAccountUpdatedBanner, shouldShowChooseAccountBanner,
79                 onChooseAppBannerEventListener, onCloudMediaAvailableBannerEventListener,
80                 onAccountUpdatedBannerEventListener, onChooseAccountBannerEventListener,
81                 pickerViewModel);
82         mLifecycleOwner = lifecycleOwner;
83         mShowRecentSection = showRecentSection;
84         mSelection = selection;
85         mOnMediaItemClickListener = onMediaItemClickListener;
86         mOnMediaItemHoverListener = onMediaItemHoverListener;
87         mPreloadSizeProvider = preloadSizeProvider;
88     }
89 
90     @NonNull
91     @Override
createSectionViewHolder(@onNull ViewGroup viewGroup)92     RecyclerView.ViewHolder createSectionViewHolder(@NonNull ViewGroup viewGroup) {
93         final View view = getView(viewGroup, R.layout.item_date_header);
94         return new DateHeaderViewHolder(view);
95     }
96 
97     @NonNull
98     @Override
createMediaItemViewHolder(@onNull ViewGroup viewGroup)99     RecyclerView.ViewHolder createMediaItemViewHolder(@NonNull ViewGroup viewGroup) {
100         final View view = getView(viewGroup, R.layout.item_photo_grid);
101         final MediaItemGridViewHolder viewHolder =
102                 new MediaItemGridViewHolder(
103                         mLifecycleOwner,
104                         view,
105                         mImageLoader,
106                         mOnMediaItemClickListener,
107                         mOnMediaItemHoverListener,
108                         mPickerViewModel,
109                         mSelection.canSelectMultiple(),
110                         mSelection.isSelectionOrdered());
111         mPreloadSizeProvider.setView(viewHolder.getThumbnailImageView());
112         return viewHolder;
113     }
114 
115     @Override
onBindSectionViewHolder(@onNull RecyclerView.ViewHolder viewHolder, int position)116     void onBindSectionViewHolder(@NonNull RecyclerView.ViewHolder viewHolder, int position) {
117         final DateHeader dateHeader = (DateHeader) getAdapterItem(position);
118         final DateHeaderViewHolder dateHeaderVH = (DateHeaderViewHolder) viewHolder;
119 
120         dateHeaderVH.bind(dateHeader);
121         TextView titleTextView = dateHeaderVH.title;
122         if (mPickerViewModel.getPickerAccentColorParameters().isCustomPickerColorSet()) {
123             titleTextView.setTextColor(
124                     mPickerViewModel.getPickerAccentColorParameters().getThemeBasedColor(
125                             AccentColorResources.ON_SURFACE_COLOR_LIGHT,
126                             AccentColorResources.ON_SURFACE_COLOR_DARK));
127         }
128     }
129 
130     @Override
onBindMediaItemViewHolder(@onNull RecyclerView.ViewHolder viewHolder, int position)131     void onBindMediaItemViewHolder(@NonNull RecyclerView.ViewHolder viewHolder, int position) {
132         final Item item = (Item) getAdapterItem(position);
133         final MediaItemGridViewHolder mediaItemVH = (MediaItemGridViewHolder) viewHolder;
134 
135         final boolean isSelected = mSelection.canSelectMultiple()
136                 && mSelection.isItemSelected(item);
137 
138         if (isSelected) {
139             mSelection.addCheckedItemIndex(item, position);
140         }
141 
142         mediaItemVH.bind(item, isSelected);
143         if (isSelected && mSelection.isSelectionOrdered()) {
144             mediaItemVH.setSelectionOrder(mSelection.getSelectedItemOrder(item));
145         }
146         // We also need to set Item as a tag so that OnClick/OnLongClickListeners can then
147         // retrieve it.
148         mediaItemVH.itemView.setTag(item);
149     }
150 
151     @Override
isItemTypeSection(int position)152     boolean isItemTypeSection(int position) {
153         return getAdapterItem(position) instanceof DateHeader;
154     }
155 
156     @Override
isItemTypeMediaItem(int position)157     public boolean isItemTypeMediaItem(int position) {
158         return getAdapterItem(position) instanceof Item;
159     }
160 
setMediaItems(@onNull List<Item> mediaItems)161     void setMediaItems(@NonNull List<Item> mediaItems) {
162         setMediaItems(mediaItems, ACTION_CLEAR_AND_UPDATE_LIST);
163     }
164 
setMediaItems(@onNull List<Item> mediaItems, @ItemsAction.Type int action)165     void setMediaItems(@NonNull List<Item> mediaItems, @ItemsAction.Type int action) {
166         final List<Object> mediaItemsWithDateHeaders;
167         if (!mediaItems.isEmpty()) {
168             // We'll have at least one section
169             mediaItemsWithDateHeaders = new ArrayList<>(mediaItems.size() + 1);
170 
171             // First: show "Recent" section header if needed.
172             if (mShowRecentSection) {
173                 mediaItemsWithDateHeaders.add(new DateHeader(DateHeader.RECENT));
174             }
175 
176             int recentItemsCount = 0;
177             long prevItemDate = -1;
178             for (Item mediaItem : mediaItems) {
179                 final long itemDate = mediaItem.getDateTaken();
180 
181                 if (mShowRecentSection && recentItemsCount < RECENT_MINIMUM_COUNT) {
182                     // The minimum count of items in "Recent" section is not reached yet.
183                     recentItemsCount++;
184                 } else if (!DateTimeUtils.isSameDate(prevItemDate, itemDate)) {
185                     // The dateTaken of these two images are not on the same day: add a new date
186                     // header
187                     mediaItemsWithDateHeaders.add(new DateHeader(itemDate));
188                 }
189 
190                 mediaItemsWithDateHeaders.add(mediaItem);
191 
192                 prevItemDate = itemDate;
193             }
194         } else {
195             mediaItemsWithDateHeaders = Collections.emptyList();
196         }
197         setAllItems(mediaItemsWithDateHeaders, action);
198     }
199 
200     @VisibleForTesting
201     static class DateHeader {
202         static final int RECENT = -1;
203         final long timestamp;
204 
DateHeader(long timestamp)205         DateHeader(long timestamp) {
206             this.timestamp = timestamp;
207         }
208     }
209 
210     private static class DateHeaderViewHolder extends RecyclerView.ViewHolder {
211         final TextView title;
212 
DateHeaderViewHolder(@onNull View itemView)213         DateHeaderViewHolder(@NonNull View itemView) {
214             super(itemView);
215             title = itemView.findViewById(R.id.date_header_title);
216         }
217 
bind(@onNull DateHeader dateHeader)218         void bind(@NonNull DateHeader dateHeader) {
219             if (dateHeader.timestamp == DateHeader.RECENT) {
220                 title.setText(R.string.recent);
221             } else {
222                 title.setText(DateTimeUtils.getDateHeaderString(dateHeader.timestamp));
223             }
224         }
225     }
226 
227     interface OnMediaItemClickListener {
onItemClick(@onNull View view, int position, MediaItemGridViewHolder viewHolder)228         void onItemClick(@NonNull View view, int position, MediaItemGridViewHolder viewHolder);
229 
onItemLongClick(@onNull View view, int position)230         boolean onItemLongClick(@NonNull View view, int position);
231     }
232 }
233