1 /*
2  * Copyright (C) 2023 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.google.android.car.kitchensink;
18 
19 import android.content.Context;
20 import android.graphics.drawable.Drawable;
21 import android.text.TextUtils;
22 import android.util.TypedValue;
23 import android.view.View;
24 
25 import androidx.annotation.DrawableRes;
26 import androidx.annotation.NonNull;
27 import androidx.recyclerview.widget.RecyclerView;
28 
29 import com.android.car.ui.recyclerview.CarUiListItemAdapter;
30 import com.android.car.ui.recyclerview.CarUiRecyclerView;
31 import com.android.car.ui.widget.CarUiTextView;
32 
33 import java.util.List;
34 
35 /** RecyclerView adapter that supports single-preference highlighting. */
36 public class HighlightableAdapter extends CarUiListItemAdapter {
37 
38     private static final int INVALID_BACKGROUND_RES = -1;
39     private static final String EMPTY_STRING = "";
40     @DrawableRes
41     private final int mDefaultBackgroundRes;
42     @DrawableRes
43     private final int mHighlightBackgroundRes;
44     private final int mItemHeight;
45     private final Drawable mPinIconDrawable;
46     private final int mPinIconPadding;
47     private String mHighlightTitle = EMPTY_STRING;
48     private final List<FragmentListItem> mListItems;
49     private final CarUiRecyclerView mRecyclerView;
50     private int mHighlightPosition;
51 
HighlightableAdapter(Context context, List<FragmentListItem> data, CarUiRecyclerView recyclerView)52     public HighlightableAdapter(Context context, List<FragmentListItem> data,
53             CarUiRecyclerView recyclerView) {
54         this(context, data, recyclerView, INVALID_BACKGROUND_RES,
55                 R.drawable.preference_highlight_default);
56     }
57 
HighlightableAdapter(Context context, List<FragmentListItem> data, CarUiRecyclerView recyclerView, @DrawableRes int defaultBackgroundRes, @DrawableRes int highlightBackgroundRes)58     public HighlightableAdapter(Context context, List<FragmentListItem> data,
59             CarUiRecyclerView recyclerView,
60             @DrawableRes int defaultBackgroundRes, @DrawableRes int highlightBackgroundRes) {
61         super(data);
62         mListItems = data;
63         mRecyclerView = recyclerView;
64         if (defaultBackgroundRes == INVALID_BACKGROUND_RES) {
65             TypedValue outValue = new TypedValue();
66             context.getTheme().resolveAttribute(android.R.attr.selectableItemBackground,
67                     outValue, /* resolveRefs= */ true);
68             mDefaultBackgroundRes = outValue.resourceId;
69         } else {
70             mDefaultBackgroundRes = defaultBackgroundRes;
71         }
72         mHighlightBackgroundRes = highlightBackgroundRes;
73 
74         mItemHeight = context.getResources().getDimensionPixelSize(
75                 R.dimen.top_level_preference_height);
76         mPinIconDrawable = context.getDrawable(R.drawable.ic_item_pin);
77         mPinIconPadding = context.getResources().getDimensionPixelSize(
78                 R.dimen.top_level_pin_icon_padding);
79     }
80 
81     @Override
onBindViewHolder(@onNull RecyclerView.ViewHolder holder, int position)82     public void onBindViewHolder(@NonNull RecyclerView.ViewHolder holder, int position) {
83         super.onBindViewHolder(holder, position);
84         holder.itemView.setSelected(mHighlightPosition == position);
85         holder.itemView.getLayoutParams().height = mItemHeight;
86 
87         //itemView is inflated by CarUiListItemAdapter
88         CarUiTextView titleView = holder.itemView.findViewById(R.id.car_ui_list_item_title);
89         if (titleView != null) {
90             titleView.setEllipsize(TextUtils.TruncateAt.END);
91             titleView.setSingleLine(true);
92             if (mListItems.get(position).isFavourite()) {
93                 titleView.setCompoundDrawablesWithIntrinsicBounds(null, null, mPinIconDrawable,
94                         null);
95                 titleView.setPaddingRelative(0, 0, mPinIconPadding, 0);
96             } else {
97                 titleView.setCompoundDrawablesWithIntrinsicBounds(0, 0, 0, 0);
98             }
99         }
100         updateBackground(holder, position);
101     }
102 
updateBackground(RecyclerView.ViewHolder holder, int position)103     private void updateBackground(RecyclerView.ViewHolder holder, int position) {
104         View v = holder.itemView;
105         if (mHighlightPosition == position) {
106             addHighlightBackground(v);
107         } else if (hasHighlightBackground(v)) {
108             removeHighlightBackground(v);
109         }
110     }
111 
112     /**
113      * Requests that a particular list item be highlighted. This will remove the highlight from
114      * the previously highlighted item.
115      */
requestHighlight(String fragmentTitle, int newPosition)116     public void requestHighlight(String fragmentTitle, int newPosition) {
117         if (mRecyclerView == null) {
118             return;
119         }
120 
121         if (mHighlightPosition < 0) {
122             // Item is not in the list - clearing the previous highlight without setting a new one.
123             mHighlightTitle = EMPTY_STRING;
124             mRecyclerView.getView().post(() -> notifyItemChanged(mHighlightPosition));
125         }
126 
127         if (newPosition >= 0) {
128             mHighlightTitle = fragmentTitle;
129             mRecyclerView.getView().post(() -> {
130                 notifyItemChanged(mHighlightPosition);
131                 mHighlightPosition = newPosition;
132                 mRecyclerView.scrollToPosition(newPosition);
133                 notifyItemChanged(newPosition);
134             });
135         }
136     }
137 
getPositionFromTitle(String fragmentTitle)138     private int getPositionFromTitle(String fragmentTitle) {
139         for (int i = 0; i < mListItems.size(); i++) {
140             String targetText = mListItems.get(i).getTitle().getPreferredText().toString();
141             if (targetText.equalsIgnoreCase(fragmentTitle)) {
142                 return i;
143             }
144         }
145         return RecyclerView.NO_POSITION;
146     }
147 
addHighlightBackground(View v)148     private void addHighlightBackground(View v) {
149         v.setTag(R.id.preference_highlighted, true);
150         v.setBackgroundResource(mHighlightBackgroundRes);
151     }
152 
removeHighlightBackground(View v)153     private void removeHighlightBackground(View v) {
154         v.setTag(R.id.preference_highlighted, false);
155         v.setBackgroundResource(mDefaultBackgroundRes);
156     }
157 
hasHighlightBackground(View v)158     private boolean hasHighlightBackground(View v) {
159         return Boolean.TRUE.equals(v.getTag(R.id.preference_highlighted));
160     }
161 
162     /**
163      * Will be called each time the search query is changed.
164      */
afterTextChanged()165     public void afterTextChanged() {
166         mHighlightPosition = getPositionFromTitle(mHighlightTitle);
167         notifyDataSetChanged();
168     }
169 
170     /**
171      * Scrolls to the highlighted item after search ends.
172      */
onSearchEnded()173     public void onSearchEnded() {
174         mRecyclerView.getView().post(() -> mRecyclerView.scrollToPosition(mHighlightPosition));
175     }
176 
afterFavClicked(int from, int to)177     public void afterFavClicked(int from, int to) {
178         mHighlightPosition = to;
179         notifyItemMoved(from, to);
180         notifyItemChanged(to);
181         mRecyclerView.scrollToPosition(to);
182     }
183 }
184