1package com.example.android.leanback;
2
3import static com.example.android.leanback.CardPresenter.CONTENT;
4import static com.example.android.leanback.CardPresenter.IMAGE;
5import static com.example.android.leanback.CardPresenter.TITLE;
6
7import android.content.Context;
8import android.content.Intent;
9import android.os.Bundle;
10import android.os.Handler;
11import android.text.TextUtils;
12import android.util.Log;
13
14import androidx.annotation.Nullable;
15import androidx.core.app.ActivityOptionsCompat;
16import androidx.core.content.res.ResourcesCompat;
17import androidx.leanback.widget.ArrayObjectAdapter;
18import androidx.leanback.widget.DiffCallback;
19import androidx.leanback.widget.HeaderItem;
20import androidx.leanback.widget.ImageCardView;
21import androidx.leanback.widget.ListRow;
22import androidx.leanback.widget.ListRowPresenter;
23import androidx.leanback.widget.ObjectAdapter;
24import androidx.leanback.widget.OnItemViewClickedListener;
25import androidx.leanback.widget.Presenter;
26import androidx.leanback.widget.Row;
27import androidx.leanback.widget.RowPresenter;
28
29import java.util.ArrayList;
30
31public class SearchFragment extends androidx.leanback.app.SearchFragment
32        implements androidx.leanback.app.SearchFragment.SearchResultProvider {
33    private static final String TAG = "leanback.SearchFragment";
34    private static final int NUM_ROWS = 3;
35    private static final int SEARCH_DELAY_MS = 1000;
36
37    private ArrayObjectAdapter mRowsAdapter;
38    private Handler mHandler = new Handler();
39    private String mQuery;
40
41    // Flag to represent if data set one is presented in the fragment
42    private boolean mIsDataSetOnePresented;
43
44    // Adapter for first row
45    private ArrayObjectAdapter mFirstRowAdapter;
46
47    // The diff callback which defines the standard to judge if two items are the same or if
48    // two items have the same content.
49    private DiffCallback<PhotoItem> mDiffCallback = new DiffCallback<PhotoItem>() {
50
51        // when two photo items have the same id, they are the same from adapter's
52        // perspective
53        @Override
54        public boolean areItemsTheSame(PhotoItem oldItem, PhotoItem newItem) {
55            return oldItem.getId() == newItem.getId();
56        }
57
58        // when two photo items is equal to each other (based on the equal method defined in
59        // PhotoItem), they have the same content.
60        @Override
61        public boolean areContentsTheSame(PhotoItem oldItem, PhotoItem newItem) {
62            return oldItem.equals(newItem);
63        }
64
65        @Nullable
66        @Override
67        public Object getChangePayload(PhotoItem oldItem, PhotoItem newItem) {
68            Bundle diff = new Bundle();
69            if (oldItem.getImageResourceId()
70                    != newItem.getImageResourceId()) {
71                diff.putLong(IMAGE, newItem.getImageResourceId());
72            }
73
74            if (oldItem.getTitle() != null && newItem.getTitle() != null
75                    && !oldItem.getTitle().equals(newItem.getTitle())) {
76                diff.putString(TITLE, newItem.getTitle());
77            }
78
79            if (oldItem.getContent() != null && newItem.getContent() != null
80                    && !oldItem.getContent().equals(newItem.getContent())) {
81                diff.putString(CONTENT, newItem.getContent());
82            }
83            return diff;
84        }
85    };
86
87    @Override
88    public void onCreate(Bundle savedInstanceState) {
89        super.onCreate(savedInstanceState);
90
91        mRowsAdapter = new ArrayObjectAdapter(new ListRowPresenter());
92
93        final Context context = getActivity();
94        setBadgeDrawable(ResourcesCompat.getDrawable(context.getResources(),
95                R.drawable.ic_title, context.getTheme()));
96        setTitle("Leanback Sample App");
97        setSearchResultProvider(this);
98        setOnItemViewClickedListener(new ItemViewClickedListener());
99    }
100
101    @Override
102    public ObjectAdapter getResultsAdapter() {
103        return mRowsAdapter;
104    }
105
106    @Override
107    public boolean onQueryTextChange(String newQuery) {
108        Log.i(TAG, String.format("Search Query Text Change %s", newQuery));
109        mRowsAdapter.clear();
110        loadQuery(newQuery);
111        return true;
112    }
113
114    @Override
115    public boolean onQueryTextSubmit(String query) {
116        Log.i(TAG, String.format("Search Query Text Submit %s", query));
117        mRowsAdapter.clear();
118        loadQuery(query);
119        return true;
120    }
121
122    private void loadQuery(String query) {
123        mQuery = query;
124        mHandler.removeCallbacks(mDelayedLoad);
125        if (!TextUtils.isEmpty(query) && !query.equals("nil")) {
126            mHandler.postDelayed(mDelayedLoad, SEARCH_DELAY_MS);
127        }
128    }
129
130    private void loadRows() {
131        HeaderItem header = new HeaderItem(0, mQuery + " results row " + 0);
132
133        // Every time when the query event is fired, we will update the fake search result in the
134        // first row based on the flag mIsDataSetOnePresented flag.
135        // Also the first row adapter will only be created once so the animation will be triggered
136        // when the items in the adapter changed.
137        if (!mIsDataSetOnePresented) {
138            if (mFirstRowAdapter == null) {
139                mFirstRowAdapter = createFirstListRowAdapter();
140            } else {
141                mFirstRowAdapter.setItems(createDataSetOneDebug(), mDiffCallback);
142            }
143            mIsDataSetOnePresented = true;
144        } else {
145            mFirstRowAdapter.setItems(createDataSetTwoDebug(), mDiffCallback);
146            mIsDataSetOnePresented = false;
147        }
148        mRowsAdapter.add(new ListRow(header, mFirstRowAdapter));
149        for (int i = 1; i < NUM_ROWS + 1; ++i) {
150            ArrayObjectAdapter listRowAdapter = new ArrayObjectAdapter(new CardPresenter());
151            listRowAdapter.add(new PhotoItem("Hello world", R.drawable.gallery_photo_1));
152            listRowAdapter.add(new PhotoItem("This is a test", R.drawable.gallery_photo_2));
153            header = new HeaderItem(i, mQuery + " results row " + i);
154            mRowsAdapter.add(new ListRow(header, listRowAdapter));
155        }
156    }
157
158    private Runnable mDelayedLoad = new Runnable() {
159        @Override
160        public void run() {
161            loadRows();
162        }
163    };
164
165    private final class ItemViewClickedListener implements OnItemViewClickedListener {
166        @Override
167        public void onItemClicked(Presenter.ViewHolder itemViewHolder, Object item,
168                RowPresenter.ViewHolder rowViewHolder, Row row) {
169            Intent intent = new Intent(getActivity(), DetailsActivity.class);
170            intent.putExtra(DetailsActivity.EXTRA_ITEM, (PhotoItem) item);
171
172            Bundle bundle = ActivityOptionsCompat.makeSceneTransitionAnimation(
173                    getActivity(),
174                    ((ImageCardView) itemViewHolder.view).getMainImageView(),
175                    DetailsActivity.SHARED_ELEMENT_NAME).toBundle();
176            getActivity().startActivity(intent, bundle);
177        }
178    }
179
180
181    private ArrayObjectAdapter createFirstListRowAdapter() {
182        ArrayObjectAdapter listRowAdapter = new ArrayObjectAdapter(new CardPresenter());
183        listRowAdapter.setItems(createDataSetOneDebug(), mDiffCallback);
184        mIsDataSetOnePresented = true;
185        return listRowAdapter;
186    }
187
188    /**
189     * Create a data set (data set one) for the last row of this browse fragment. It will be
190     * changed by another set of data when user click one of the photo items in the list.
191     * Different with other rows in the browsing fragment, the photo item in last row all have been
192     * allocated with a unique id. And the id will be used to jduge if two photo items are the same
193     * or not.
194     *
195     * @return List of photoItem
196     */
197    private ArrayList<PhotoItem> createDataSetOne() {
198        ArrayList<PhotoItem> photoItems = new ArrayList<>();
199        photoItems.add(new PhotoItem(
200                "Hello world",
201                R.drawable.gallery_photo_1,
202                1));
203        photoItems.add(new PhotoItem(
204                "This is a test",
205                "Only a test",
206                R.drawable.gallery_photo_2,
207                2));
208        photoItems.add(new PhotoItem(
209                "Android TV",
210                "by Google",
211                R.drawable.gallery_photo_3,
212                3));
213        photoItems.add(new PhotoItem(
214                "Leanback",
215                R.drawable.gallery_photo_4,
216                4));
217        photoItems.add(new PhotoItem(
218                "GuidedStep (Slide left/right)",
219                R.drawable.gallery_photo_5,
220                5));
221        photoItems.add(new PhotoItem(
222                "GuidedStep (Slide bottom up)",
223                "Open GuidedStepFragment",
224                R.drawable.gallery_photo_6,
225                6));
226        photoItems.add(new PhotoItem(
227                "Android TV",
228                "open RowsActivity",
229                R.drawable.gallery_photo_7,
230                7));
231        photoItems.add(new PhotoItem(
232                "Leanback",
233                "open BrowseActivity",
234                R.drawable.gallery_photo_8,
235                8));
236        photoItems.add(new PhotoItem(
237                "Hello world",
238                R.drawable.gallery_photo_1,
239                1));
240        photoItems.add(new PhotoItem(
241                "This is a test",
242                "Only a test",
243                R.drawable.gallery_photo_2,
244                2));
245        photoItems.add(new PhotoItem(
246                "Android TV",
247                "by Google",
248                R.drawable.gallery_photo_3,
249                3));
250        photoItems.add(new PhotoItem(
251                "Leanback",
252                R.drawable.gallery_photo_4,
253                4));
254        return photoItems;
255    }
256
257    /**
258     * Create a new data set (data set one) for the last row of this browse fragment. It will be
259     * changed by another set of data when user click one of the photo items in the list.
260     * Different with other rows in the browsing fragment, the photo item in last row all have been
261     * allocated with a unique id. And the id will be used to jduge if two photo items are the same
262     * or not.
263     *
264     * @return List of photoItem
265     */
266    private ArrayList<PhotoItem> createDataSetTwo() {
267        ArrayList<PhotoItem> photoItems = new ArrayList<>();
268        photoItems.add(new PhotoItem(
269                "This is a test",
270                "Only a test",
271                R.drawable.gallery_photo_2,
272                2));
273        photoItems.add(new PhotoItem(
274                "Hello world",
275                R.drawable.gallery_photo_1,
276                1));
277        photoItems.add(new PhotoItem(
278                "Leanback",
279                R.drawable.gallery_photo_4,
280                4));
281        photoItems.add(new PhotoItem(
282                "Android TV",
283                "by Google",
284                R.drawable.gallery_photo_3,
285                3));
286        photoItems.add(new PhotoItem(
287                "change title",
288                R.drawable.gallery_photo_5,
289                5));
290        photoItems.add(new PhotoItem(
291                "GuidedStep (Slide bottom up)",
292                "change comment",
293                R.drawable.gallery_photo_6,
294                6));
295        photoItems.add(new PhotoItem(
296                "Android TV",
297                R.drawable.gallery_photo_7,
298                7));
299        photoItems.add(new PhotoItem(
300                "Leanback",
301                "open BrowseActivity",
302                R.drawable.gallery_photo_7,
303                8));
304        photoItems.add(new PhotoItem(
305                "Hello world",
306                R.drawable.gallery_photo_1,
307                10));
308        photoItems.add(new PhotoItem(
309                "This is a test",
310                "Only a test",
311                R.drawable.gallery_photo_2,
312                20));
313        photoItems.add(new PhotoItem(
314                "Android TV",
315                "by Google",
316                R.drawable.gallery_photo_3,
317                30));
318        photoItems.add(new PhotoItem(
319                "Leanback",
320                R.drawable.gallery_photo_4,
321                40));
322        return photoItems;
323    }
324
325
326    private ArrayList<PhotoItem> createDataSetOneDebug() {
327        ArrayList<PhotoItem> photoItems = new ArrayList<>();
328        photoItems.add(new PhotoItem(
329                "Hello world",
330                R.drawable.gallery_photo_1,
331                1));
332        return photoItems;
333    }
334
335    /**
336     * Create a new data set (data set one) for the last row of this browse fragment. It will be
337     * changed by another set of data when user click one of the photo items in the list.
338     * Different with other rows in the browsing fragment, the photo item in last row all have been
339     * allocated with a unique id. And the id will be used to jduge if two photo items are the same
340     * or not.
341     *
342     * @return List of photoItem
343     */
344    private ArrayList<PhotoItem> createDataSetTwoDebug() {
345        ArrayList<PhotoItem> photoItems = new ArrayList<>();
346        photoItems.add(new PhotoItem(
347                "Hello world Hello world",
348                R.drawable.gallery_photo_1,
349                1));
350        return photoItems;
351    }
352}
353