1/*
2 * Copyright (C) 2014 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
5 * in compliance with the License. You may obtain a copy of the License at
6 *
7 * http://www.apache.org/licenses/LICENSE-2.0
8 *
9 * Unless required by applicable law or agreed to in writing, software distributed under the License
10 * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
11 * or implied. See the License for the specific language governing permissions and limitations under
12 * the License.
13 */
14package android.support.v17.leanback.app;
15
16import android.support.v17.leanback.R;
17import android.support.v17.leanback.transition.TransitionHelper;
18import android.support.v17.leanback.widget.BrowseFrameLayout;
19import android.support.v17.leanback.widget.OnItemViewClickedListener;
20import android.support.v17.leanback.widget.OnItemViewSelectedListener;
21import android.support.v17.leanback.widget.Presenter;
22import android.support.v17.leanback.widget.Row;
23import android.support.v17.leanback.widget.RowPresenter;
24import android.support.v17.leanback.widget.TitleView;
25import android.support.v17.leanback.widget.VerticalGridPresenter;
26import android.support.v17.leanback.widget.ObjectAdapter;
27import android.support.v17.leanback.widget.OnItemClickedListener;
28import android.support.v17.leanback.widget.OnItemSelectedListener;
29import android.support.v17.leanback.widget.SearchOrbView;
30import android.support.v4.view.ViewCompat;
31import android.app.Fragment;
32import android.content.Context;
33import android.graphics.drawable.Drawable;
34import android.os.Bundle;
35import android.util.Log;
36import android.view.LayoutInflater;
37import android.view.View;
38import android.view.ViewGroup;
39import android.view.ViewGroup.MarginLayoutParams;
40import android.widget.ImageView;
41import android.widget.TextView;
42
43/**
44 * A fragment for creating leanback vertical grids.
45 *
46 * <p>Renders a vertical grid of objects given a {@link VerticalGridPresenter} and
47 * an {@link ObjectAdapter}.
48 */
49public class VerticalGridFragment extends Fragment {
50    private static final String TAG = "VerticalGridFragment";
51    private static boolean DEBUG = false;
52
53    private BrowseFrameLayout mBrowseFrame;
54    private String mTitle;
55    private Drawable mBadgeDrawable;
56    private ObjectAdapter mAdapter;
57    private VerticalGridPresenter mGridPresenter;
58    private VerticalGridPresenter.ViewHolder mGridViewHolder;
59    private OnItemSelectedListener mOnItemSelectedListener;
60    private OnItemClickedListener mOnItemClickedListener;
61    private OnItemViewSelectedListener mOnItemViewSelectedListener;
62    private OnItemViewClickedListener mOnItemViewClickedListener;
63    private View.OnClickListener mExternalOnSearchClickedListener;
64    private int mSelectedPosition = -1;
65
66    private TitleView mTitleView;
67    private SearchOrbView.Colors mSearchAffordanceColors;
68    private boolean mSearchAffordanceColorSet;
69    private boolean mShowingTitle = true;
70
71    // transition related
72    private static TransitionHelper sTransitionHelper = TransitionHelper.getInstance();
73    private Object mTitleUpTransition;
74    private Object mTitleDownTransition;
75    private Object mSceneWithTitle;
76    private Object mSceneWithoutTitle;
77
78    /**
79     * Sets the badge drawable displayed in the title area.
80     */
81    public void setBadgeDrawable(Drawable drawable) {
82        if (drawable != mBadgeDrawable) {
83            mBadgeDrawable = drawable;
84            if (mTitleView != null) {
85                mTitleView.setBadgeDrawable(drawable);
86            }
87        }
88    }
89
90    /**
91     * Returns the badge drawable.
92     */
93    public Drawable getBadgeDrawable() {
94        return mBadgeDrawable;
95    }
96
97    /**
98     * Sets a title for the fragment.
99     */
100    public void setTitle(String title) {
101        mTitle = title;
102        if (mTitleView != null) {
103            mTitleView.setTitle(mTitle);
104        }
105    }
106
107    /**
108     * Returns the title for the fragment.
109     */
110    public String getTitle() {
111        return mTitle;
112    }
113
114    /**
115     * Sets the grid presenter.
116     */
117    public void setGridPresenter(VerticalGridPresenter gridPresenter) {
118        if (gridPresenter == null) {
119            throw new IllegalArgumentException("Grid presenter may not be null");
120        }
121        mGridPresenter = gridPresenter;
122        mGridPresenter.setOnItemViewSelectedListener(mRowSelectedListener);
123        if (mOnItemViewClickedListener != null) {
124            mGridPresenter.setOnItemViewClickedListener(mOnItemViewClickedListener);
125        }
126        if (mOnItemClickedListener != null) {
127            mGridPresenter.setOnItemClickedListener(mOnItemClickedListener);
128        }
129    }
130
131    /**
132     * Returns the grid presenter.
133     */
134    public VerticalGridPresenter getGridPresenter() {
135        return mGridPresenter;
136    }
137
138    /**
139     * Sets the object adapter for the fragment.
140     */
141    public void setAdapter(ObjectAdapter adapter) {
142        mAdapter = adapter;
143        updateAdapter();
144    }
145
146    /**
147     * Returns the object adapter.
148     */
149    public ObjectAdapter getAdapter() {
150        return mAdapter;
151    }
152
153    final private OnItemViewSelectedListener mRowSelectedListener =
154            new OnItemViewSelectedListener() {
155        @Override
156        public void onItemSelected(Presenter.ViewHolder itemViewHolder, Object item,
157                RowPresenter.ViewHolder rowViewHolder, Row row) {
158            int position = mGridViewHolder.getGridView().getSelectedPosition();
159            if (DEBUG) Log.v(TAG, "row selected position " + position);
160            onRowSelected(position);
161            if (mOnItemSelectedListener != null) {
162                mOnItemSelectedListener.onItemSelected(item, row);
163            }
164            if (mOnItemViewSelectedListener != null) {
165                mOnItemViewSelectedListener.onItemSelected(itemViewHolder, item,
166                        rowViewHolder, row);
167            }
168        }
169    };
170
171    /**
172     * Sets an item selection listener.
173     * @deprecated Use {@link #setOnItemViewSelectedListener(OnItemViewSelectedListener)}
174     */
175    public void setOnItemSelectedListener(OnItemSelectedListener listener) {
176        mOnItemSelectedListener = listener;
177    }
178
179    /**
180     * Sets an item selection listener.
181     */
182    public void setOnItemViewSelectedListener(OnItemViewSelectedListener listener) {
183        mOnItemViewSelectedListener = listener;
184    }
185
186    private void onRowSelected(int position) {
187        if (position != mSelectedPosition) {
188            if (!mGridViewHolder.getGridView().hasPreviousViewInSameRow(position)) {
189                // if has no sibling in front of it,  show title
190                if (!mShowingTitle) {
191                    sTransitionHelper.runTransition(mSceneWithTitle, mTitleDownTransition);
192                    mShowingTitle = true;
193                }
194            } else if (mShowingTitle) {
195                sTransitionHelper.runTransition(mSceneWithoutTitle, mTitleUpTransition);
196                mShowingTitle = false;
197            }
198            mSelectedPosition = position;
199        }
200    }
201
202    /**
203     * Sets an item clicked listener.
204     * @deprecated Use {@link #setOnItemViewClickedListener(OnItemViewClickedListener)}
205     */
206    public void setOnItemClickedListener(OnItemClickedListener listener) {
207        mOnItemClickedListener = listener;
208        if (mGridPresenter != null) {
209            mGridPresenter.setOnItemClickedListener(mOnItemClickedListener);
210        }
211    }
212
213    /**
214     * Returns the item clicked listener.
215     * @deprecated Use {@link #getOnItemViewClickedListener()}
216     */
217    public OnItemClickedListener getOnItemClickedListener() {
218        return mOnItemClickedListener;
219    }
220
221    /**
222     * Sets an item clicked listener.
223     */
224    public void setOnItemViewClickedListener(OnItemViewClickedListener listener) {
225        mOnItemViewClickedListener = listener;
226        if (mGridPresenter != null) {
227            mGridPresenter.setOnItemViewClickedListener(mOnItemViewClickedListener);
228        }
229    }
230
231    /**
232     * Returns the item clicked listener.
233     */
234    public OnItemViewClickedListener getOnItemViewClickedListener() {
235        return mOnItemViewClickedListener;
236    }
237
238    /**
239     * Sets a click listener for the search affordance.
240     *
241     * <p>The presence of a listener will change the visibility of the search
242     * affordance in the title area. When set to non-null, the title area will
243     * contain a call to search action.
244     *
245     * <p>The listener's onClick method will be invoked when the user clicks on
246     * the search action.
247     *
248     * @param listener The listener to invoke when the search affordance is
249     *        clicked, or null to hide the search affordance.
250     */
251    public void setOnSearchClickedListener(View.OnClickListener listener) {
252        mExternalOnSearchClickedListener = listener;
253        if (mTitleView != null) {
254            mTitleView.setOnSearchClickedListener(listener);
255        }
256    }
257
258    /**
259     * Sets the {@link SearchOrbView.Colors} used to draw the search affordance.
260     */
261    public void setSearchAffordanceColors(SearchOrbView.Colors colors) {
262        mSearchAffordanceColors = colors;
263        mSearchAffordanceColorSet = true;
264        if (mTitleView != null) {
265            mTitleView.setSearchAffordanceColors(mSearchAffordanceColors);
266        }
267    }
268
269    /**
270     * Returns the {@link SearchOrbView.Colors} used to draw the search affordance.
271     */
272    public SearchOrbView.Colors getSearchAffordanceColors() {
273        if (mSearchAffordanceColorSet) {
274            return mSearchAffordanceColors;
275        }
276        if (mTitleView == null) {
277            throw new IllegalStateException("Fragment views not yet created");
278        }
279        return mTitleView.getSearchAffordanceColors();
280    }
281
282    /**
283     * Sets the color used to draw the search affordance.
284     * A default brighter color will be set by the framework.
285     *
286     * @param color The color to use for the search affordance.
287     */
288    public void setSearchAffordanceColor(int color) {
289        setSearchAffordanceColors(new SearchOrbView.Colors(color));
290    }
291
292    /**
293     * Returns the color used to draw the search affordance.
294     */
295    public int getSearchAffordanceColor() {
296        return getSearchAffordanceColors().color;
297    }
298
299    private final BrowseFrameLayout.OnFocusSearchListener mOnFocusSearchListener =
300            new BrowseFrameLayout.OnFocusSearchListener() {
301        @Override
302        public View onFocusSearch(View focused, int direction) {
303            if (DEBUG) Log.v(TAG, "onFocusSearch focused " + focused + " + direction " + direction);
304
305            final View searchOrbView = mTitleView.getSearchAffordanceView();
306            final boolean isRtl = ViewCompat.getLayoutDirection(focused) ==
307                    View.LAYOUT_DIRECTION_RTL;
308            final int forward = isRtl ? View.FOCUS_LEFT : View.FOCUS_RIGHT;
309            if (focused == searchOrbView && (
310                    direction == View.FOCUS_DOWN || direction == forward)) {
311                return mGridViewHolder.view;
312
313            } else if (focused != searchOrbView && searchOrbView.getVisibility() == View.VISIBLE
314                    && direction == View.FOCUS_UP) {
315                return searchOrbView;
316
317            } else {
318                return null;
319            }
320        }
321    };
322
323    @Override
324    public View onCreateView(LayoutInflater inflater, ViewGroup container,
325            Bundle savedInstanceState) {
326        ViewGroup root = (ViewGroup) inflater.inflate(R.layout.lb_vertical_grid_fragment,
327                container, false);
328
329        mBrowseFrame = (BrowseFrameLayout) root.findViewById(R.id.browse_frame);
330        mBrowseFrame.setOnFocusSearchListener(mOnFocusSearchListener);
331
332        mTitleView = (TitleView) root.findViewById(R.id.browse_title_group);
333        mTitleView.setBadgeDrawable(mBadgeDrawable);
334        mTitleView.setTitle(mTitle);
335        if (mSearchAffordanceColorSet) {
336            mTitleView.setSearchAffordanceColors(mSearchAffordanceColors);
337        }
338        if (mExternalOnSearchClickedListener != null) {
339            mTitleView.setOnSearchClickedListener(mExternalOnSearchClickedListener);
340        }
341
342        mSceneWithTitle = sTransitionHelper.createScene(root, new Runnable() {
343            @Override
344            public void run() {
345                mTitleView.setVisibility(View.VISIBLE);
346            }
347        });
348        mSceneWithoutTitle = sTransitionHelper.createScene(root, new Runnable() {
349            @Override
350            public void run() {
351                mTitleView.setVisibility(View.INVISIBLE);
352            }
353        });
354        Context context = getActivity();
355        mTitleUpTransition = sTransitionHelper.loadTransition(context, R.transition.lb_title_out);
356        mTitleDownTransition = sTransitionHelper.loadTransition(context, R.transition.lb_title_in);
357
358        return root;
359    }
360
361    @Override
362    public void onViewCreated(View view, Bundle savedInstanceState) {
363        ViewGroup gridDock = (ViewGroup) view.findViewById(R.id.browse_grid_dock);
364        mGridViewHolder = mGridPresenter.onCreateViewHolder(gridDock);
365        gridDock.addView(mGridViewHolder.view);
366
367        updateAdapter();
368    }
369
370    @Override
371    public void onStart() {
372        super.onStart();
373        mGridViewHolder.getGridView().requestFocus();
374    }
375
376    @Override
377    public void onPause() {
378        mTitleView.enableAnimation(false);
379        super.onPause();
380    }
381
382    @Override
383    public void onResume() {
384        super.onResume();
385        mTitleView.enableAnimation(true);
386    }
387
388    @Override
389    public void onDestroyView() {
390        super.onDestroyView();
391        mGridViewHolder = null;
392    }
393
394    /**
395     * Sets the selected item position.
396     */
397    public void setSelectedPosition(int position) {
398        mSelectedPosition = position;
399        if(mGridViewHolder != null && mGridViewHolder.getGridView().getAdapter() != null) {
400            mGridViewHolder.getGridView().setSelectedPositionSmooth(position);
401        }
402    }
403
404    private void updateAdapter() {
405        if (mGridViewHolder != null) {
406            mGridPresenter.onBindViewHolder(mGridViewHolder, mAdapter);
407            if (mSelectedPosition != -1) {
408                mGridViewHolder.getGridView().setSelectedPosition(mSelectedPosition);
409            }
410        }
411    }
412}
413