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 android.view.View.GONE; 20 import static android.view.View.VISIBLE; 21 22 import android.content.Context; 23 import android.graphics.Color; 24 import android.graphics.drawable.GradientDrawable; 25 import android.graphics.drawable.LayerDrawable; 26 import android.graphics.drawable.StateListDrawable; 27 import android.graphics.drawable.VectorDrawable; 28 import android.view.View; 29 import android.widget.ImageView; 30 import android.widget.TextView; 31 32 import androidx.annotation.NonNull; 33 import androidx.lifecycle.LifecycleOwner; 34 import androidx.lifecycle.LiveData; 35 import androidx.recyclerview.widget.RecyclerView; 36 37 import com.android.providers.media.R; 38 import com.android.providers.media.photopicker.data.model.Item; 39 import com.android.providers.media.photopicker.util.AccentColorResources; 40 import com.android.providers.media.photopicker.viewmodel.PickerViewModel; 41 42 /** 43 * {@link RecyclerView.ViewHolder} of a {@link View} representing a (media) {@link Item} (a photo or 44 * a video). 45 */ 46 class MediaItemGridViewHolder extends RecyclerView.ViewHolder { 47 private final LifecycleOwner mLifecycleOwner; 48 private final ImageLoader mImageLoader; 49 private final ImageView mIconThumb; 50 private final ImageView mIconGif; 51 private final ImageView mIconMotionPhoto; 52 private final View mVideoBadgeContainer; 53 private final TextView mVideoDuration; 54 private final View mOverlayGradient; 55 private final boolean mCanSelectMultiple; 56 private final boolean mShowOrderedSelectionLabel; 57 private final TextView mSelectedOrderText; 58 private LiveData<Integer> mSelectionOrder; 59 private final ImageView mCheckIcon; 60 61 private final View.OnHoverListener mOnMediaItemHoverListener; 62 private final PhotosTabAdapter.OnMediaItemClickListener mOnMediaItemClickListener; 63 private final PickerViewModel mPickerViewModel; 64 MediaItemGridViewHolder( @onNull LifecycleOwner lifecycleOwner, @NonNull View itemView, @NonNull ImageLoader imageLoader, @NonNull PhotosTabAdapter.OnMediaItemClickListener onMediaItemClickListener, View.OnHoverListener onMediaItemHoverListener, PickerViewModel pickerViewModel, boolean canSelectMultiple, boolean isOrderedSelection)65 MediaItemGridViewHolder( 66 @NonNull LifecycleOwner lifecycleOwner, 67 @NonNull View itemView, 68 @NonNull ImageLoader imageLoader, 69 @NonNull PhotosTabAdapter.OnMediaItemClickListener onMediaItemClickListener, 70 View.OnHoverListener onMediaItemHoverListener, 71 PickerViewModel pickerViewModel, 72 boolean canSelectMultiple, 73 boolean isOrderedSelection) { 74 super(itemView); 75 mLifecycleOwner = lifecycleOwner; 76 mIconThumb = itemView.findViewById(R.id.icon_thumbnail); 77 mIconGif = itemView.findViewById(R.id.icon_gif); 78 mIconMotionPhoto = itemView.findViewById(R.id.icon_motion_photo); 79 mVideoBadgeContainer = itemView.findViewById(R.id.video_container); 80 mVideoDuration = mVideoBadgeContainer.findViewById(R.id.video_duration); 81 mOverlayGradient = itemView.findViewById(R.id.overlay_gradient); 82 mImageLoader = imageLoader; 83 mOnMediaItemClickListener = onMediaItemClickListener; 84 mCanSelectMultiple = canSelectMultiple; 85 mShowOrderedSelectionLabel = isOrderedSelection; 86 mOnMediaItemHoverListener = onMediaItemHoverListener; 87 mPickerViewModel = pickerViewModel; 88 mSelectedOrderText = itemView.findViewById(R.id.selected_order); 89 mCheckIcon = itemView.findViewById(R.id.icon_check); 90 mCheckIcon.setVisibility( 91 (mCanSelectMultiple && !mShowOrderedSelectionLabel) ? VISIBLE : GONE); 92 mSelectedOrderText.setVisibility( 93 (mCanSelectMultiple && mShowOrderedSelectionLabel) ? VISIBLE : GONE); 94 95 if (mPickerViewModel.getPickerAccentColorParameters().isCustomPickerColorSet()) { 96 setCustomSelectedMediaIconColors( 97 mPickerViewModel.getPickerAccentColorParameters().getPickerAccentColor(), 98 mPickerViewModel.getPickerAccentColorParameters().getThemeBasedColor( 99 AccentColorResources.SURFACE_CONTAINER_COLOR_LIGHT, 100 AccentColorResources.SURFACE_CONTAINER_COLOR_DARK 101 )); 102 } 103 } 104 bind(@onNull Item item, boolean isSelected)105 public void bind(@NonNull Item item, boolean isSelected) { 106 int position = getAbsoluteAdapterPosition(); 107 itemView.setOnClickListener(v -> mOnMediaItemClickListener.onItemClick(v, position, this)); 108 itemView.setOnLongClickListener(v -> 109 mOnMediaItemClickListener.onItemLongClick(v, position)); 110 itemView.setOnHoverListener(mOnMediaItemHoverListener); 111 112 mImageLoader.loadPhotoThumbnail(item, mIconThumb); 113 114 mIconGif.setVisibility(item.isGifOrAnimatedWebp() ? VISIBLE : GONE); 115 mIconMotionPhoto.setVisibility(item.isMotionPhoto() ? VISIBLE : GONE); 116 117 if (item.isVideo()) { 118 mVideoBadgeContainer.setVisibility(VISIBLE); 119 mVideoDuration.setText(item.getDurationText()); 120 } else { 121 mVideoBadgeContainer.setVisibility(GONE); 122 } 123 124 if (showShowOverlayGradient(item)) { 125 mOverlayGradient.setVisibility(VISIBLE); 126 } else { 127 mOverlayGradient.setVisibility(GONE); 128 } 129 130 final Context context = getContext(); 131 itemView.setContentDescription(item.getContentDescription(context)); 132 133 if (mCanSelectMultiple) { 134 itemView.setSelected(isSelected); 135 mSelectedOrderText.setText(""); 136 // There is an issue b/223695510 about not selected in Accessibility mode. It only 137 // says selected state, but it doesn't say not selected state. Add the not selected 138 // only to avoid that it says selected twice. 139 itemView.setStateDescription( 140 isSelected ? null : context.getString(R.string.not_selected)); 141 } 142 } 143 144 /** Sets the LiveData selection order for the current grid item view. */ setSelectionOrder(LiveData<Integer> selectionOrder)145 public void setSelectionOrder(LiveData<Integer> selectionOrder) { 146 if (selectionOrder == null) { 147 mSelectedOrderText.setText(""); 148 if (mSelectionOrder != null) { 149 mSelectionOrder.removeObservers(mLifecycleOwner); 150 } 151 } else { 152 mSelectedOrderText.setText(selectionOrder.getValue().toString()); 153 selectionOrder.observe( 154 mLifecycleOwner, 155 val -> { 156 mSelectedOrderText.setText(val.toString()); 157 }); 158 } 159 mSelectionOrder = selectionOrder; 160 } 161 setCustomSelectedMediaIconColors( int checkIconColor, int uncheckedIconColor)162 private void setCustomSelectedMediaIconColors( 163 int checkIconColor, int uncheckedIconColor) { 164 // Selected Media icon colors for unordered selection 165 StateListDrawable drawableCheckIcon = (StateListDrawable) mCheckIcon.getDrawable(); 166 // Set color of the selected media icon 167 LayerDrawable checkIcon = (LayerDrawable) drawableCheckIcon.getStateDrawable(0); 168 VectorDrawable selectedMediaBaseCircle = (VectorDrawable) checkIcon.findDrawableByLayerId( 169 R.id.selected_radio_button_selected_mark); 170 selectedMediaBaseCircle.setTint(checkIconColor); 171 // Set color of the unselected media icon 172 VectorDrawable uncheckedIcon = (VectorDrawable) drawableCheckIcon.getStateDrawable(1); 173 uncheckedIcon.setTint(uncheckedIconColor); 174 mCheckIcon.setImageDrawable(drawableCheckIcon); 175 176 // Selected Media icon for ordered selection 177 StateListDrawable drawableOrderedSelection = 178 (StateListDrawable) mSelectedOrderText.getBackground(); 179 // Set color of selected media icon 180 LayerDrawable orderedIcon = (LayerDrawable) drawableOrderedSelection.getStateDrawable(0); 181 GradientDrawable orderedIconBaseCircle = 182 (GradientDrawable) orderedIcon.findDrawableByLayerId( 183 R.id.ordered_selection_selected_icon); 184 orderedIconBaseCircle.setColor(checkIconColor); 185 // Set color of the unselected media icon 186 VectorDrawable orderedSelectionSelectedItem = 187 (VectorDrawable) drawableOrderedSelection.getStateDrawable(1); 188 orderedSelectionSelectedItem.setTint(uncheckedIconColor); 189 mSelectedOrderText.setBackground(drawableOrderedSelection); 190 mSelectedOrderText.setTextColor(Color.parseColor( 191 mPickerViewModel.getPickerAccentColorParameters().isAccentColorBright() 192 ? AccentColorResources.DARK_TEXT_COLOR 193 : AccentColorResources.LIGHT_TEXT_COLOR)); 194 } 195 196 @NonNull getContext()197 private Context getContext() { 198 return itemView.getContext(); 199 } 200 201 /** 202 * Get the {@link ImageView} for the thumbnail image representing this MediaItem. 203 * @return the image view for the thumbnail. 204 */ getThumbnailImageView()205 public ImageView getThumbnailImageView() { 206 return mIconThumb; 207 } 208 showShowOverlayGradient(@onNull Item item)209 private boolean showShowOverlayGradient(@NonNull Item item) { 210 return mCanSelectMultiple 211 || item.isGifOrAnimatedWebp() 212 || item.isVideo() 213 || item.isMotionPhoto(); 214 } 215 216 /** Release any non-reusable resources, as the view is being recycled. */ release()217 public void release() { 218 if (mSelectionOrder != null) { 219 mSelectionOrder.removeObservers(mLifecycleOwner); 220 mSelectionOrder = null; 221 } 222 } 223 } 224