1 /*
2  * Copyright (C) 2016 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.tv.dvr.ui.list;
18 
19 import android.animation.ValueAnimator;
20 import android.content.Context;
21 import android.graphics.drawable.Drawable;
22 import androidx.leanback.widget.RowPresenter;
23 import android.view.LayoutInflater;
24 import android.view.View;
25 import android.view.View.OnClickListener;
26 import android.view.View.OnFocusChangeListener;
27 import android.view.ViewGroup;
28 import android.view.animation.DecelerateInterpolator;
29 import android.widget.TextView;
30 import com.android.tv.R;
31 import com.android.tv.TvSingletons;
32 import com.android.tv.dvr.data.SeriesRecording;
33 import com.android.tv.dvr.ui.DvrUiHelper;
34 import com.android.tv.dvr.ui.list.SchedulesHeaderRow.SeriesRecordingHeaderRow;
35 
36 /** A base class for RowPresenter for {@link SchedulesHeaderRow} */
37 abstract class SchedulesHeaderRowPresenter extends RowPresenter {
38     private Context mContext;
39 
SchedulesHeaderRowPresenter(Context context)40     public SchedulesHeaderRowPresenter(Context context) {
41         setHeaderPresenter(null);
42         setSelectEffectEnabled(false);
43         mContext = context;
44     }
45 
46     /** Returns the context. */
getContext()47     Context getContext() {
48         return mContext;
49     }
50 
51     /** A ViewHolder for {@link SchedulesHeaderRow}. */
52     public static class SchedulesHeaderRowViewHolder extends RowPresenter.ViewHolder {
53         private TextView mTitle;
54         private TextView mDescription;
55 
SchedulesHeaderRowViewHolder(Context context, ViewGroup parent)56         public SchedulesHeaderRowViewHolder(Context context, ViewGroup parent) {
57             super(
58                     LayoutInflater.from(context)
59                             .inflate(R.layout.dvr_schedules_header, parent, false));
60             mTitle = (TextView) view.findViewById(R.id.header_title);
61             mDescription = (TextView) view.findViewById(R.id.header_description);
62         }
63     }
64 
65     @Override
onBindRowViewHolder(RowPresenter.ViewHolder viewHolder, Object item)66     protected void onBindRowViewHolder(RowPresenter.ViewHolder viewHolder, Object item) {
67         super.onBindRowViewHolder(viewHolder, item);
68         SchedulesHeaderRowViewHolder headerViewHolder = (SchedulesHeaderRowViewHolder) viewHolder;
69         SchedulesHeaderRow header = (SchedulesHeaderRow) item;
70         headerViewHolder.mTitle.setText(header.getTitle());
71         headerViewHolder.mDescription.setText(header.getDescription());
72     }
73 
74     /** A presenter for {@link SchedulesHeaderRow.DateHeaderRow}. */
75     public static class DateHeaderRowPresenter extends SchedulesHeaderRowPresenter {
DateHeaderRowPresenter(Context context)76         public DateHeaderRowPresenter(Context context) {
77             super(context);
78         }
79 
80         @Override
createRowViewHolder(ViewGroup parent)81         protected ViewHolder createRowViewHolder(ViewGroup parent) {
82             return new DateHeaderRowViewHolder(getContext(), parent);
83         }
84 
85         /** A ViewHolder for {@link SchedulesHeaderRow.DateHeaderRow}. */
86         public static class DateHeaderRowViewHolder extends SchedulesHeaderRowViewHolder {
DateHeaderRowViewHolder(Context context, ViewGroup parent)87             public DateHeaderRowViewHolder(Context context, ViewGroup parent) {
88                 super(context, parent);
89             }
90         }
91     }
92 
93     /** A presenter for {@link SeriesRecordingHeaderRow}. */
94     public static class SeriesRecordingHeaderRowPresenter extends SchedulesHeaderRowPresenter {
95         private final boolean mLtr;
96         private final Drawable mSettingsDrawable;
97         private final Drawable mCancelDrawable;
98         private final Drawable mResumeDrawable;
99 
100         private final String mSettingsInfo;
101         private final String mCancelAllInfo;
102         private final String mResumeInfo;
103 
SeriesRecordingHeaderRowPresenter(Context context)104         public SeriesRecordingHeaderRowPresenter(Context context) {
105             super(context);
106             mLtr =
107                     context.getResources().getConfiguration().getLayoutDirection()
108                             == View.LAYOUT_DIRECTION_LTR;
109             mSettingsDrawable = context.getDrawable(R.drawable.ic_settings);
110             mCancelDrawable = context.getDrawable(R.drawable.ic_dvr_cancel_large);
111             mResumeDrawable = context.getDrawable(R.drawable.ic_record_start);
112             mSettingsInfo = context.getString(R.string.dvr_series_schedules_settings);
113             mCancelAllInfo = context.getString(R.string.dvr_series_schedules_stop);
114             mResumeInfo = context.getString(R.string.dvr_series_schedules_start);
115         }
116 
117         @Override
createRowViewHolder(ViewGroup parent)118         protected ViewHolder createRowViewHolder(ViewGroup parent) {
119             return new SeriesHeaderRowViewHolder(getContext(), parent);
120         }
121 
122         @Override
onBindRowViewHolder(RowPresenter.ViewHolder viewHolder, Object item)123         protected void onBindRowViewHolder(RowPresenter.ViewHolder viewHolder, Object item) {
124             super.onBindRowViewHolder(viewHolder, item);
125             SeriesHeaderRowViewHolder headerViewHolder = (SeriesHeaderRowViewHolder) viewHolder;
126             SeriesRecordingHeaderRow header = (SeriesRecordingHeaderRow) item;
127             headerViewHolder.mSeriesSettingsButton.setVisibility(
128                     header.getSeriesRecording().isStopped() ? View.INVISIBLE : View.VISIBLE);
129             headerViewHolder.mSeriesSettingsButton.setText(mSettingsInfo);
130             setTextDrawable(headerViewHolder.mSeriesSettingsButton, mSettingsDrawable);
131             if (header.getSeriesRecording().isStopped()) {
132                 headerViewHolder.mToggleStartStopButton.setText(mResumeInfo);
133                 setTextDrawable(headerViewHolder.mToggleStartStopButton, mResumeDrawable);
134             } else {
135                 headerViewHolder.mToggleStartStopButton.setText(mCancelAllInfo);
136                 setTextDrawable(headerViewHolder.mToggleStartStopButton, mCancelDrawable);
137             }
138             headerViewHolder.mSeriesSettingsButton.setOnClickListener(
139                     new OnClickListener() {
140                         @Override
141                         public void onClick(View view) {
142                             DvrUiHelper.startSeriesSettingsActivity(
143                                     getContext(),
144                                     header.getSeriesRecording().getId(),
145                                     header.getPrograms(),
146                                     false,
147                                     false,
148                                     false,
149                                     null);
150                         }
151                     });
152             headerViewHolder.mToggleStartStopButton.setOnClickListener(
153                     new OnClickListener() {
154                         @Override
155                         public void onClick(View view) {
156                             if (header.getSeriesRecording().isStopped()) {
157                                 // Reset priority to the highest.
158                                 SeriesRecording seriesRecording =
159                                         SeriesRecording.buildFrom(header.getSeriesRecording())
160                                                 .setPriority(
161                                                         TvSingletons.getSingletons(getContext())
162                                                                 .getDvrScheduleManager()
163                                                                 .suggestNewSeriesPriority())
164                                                 .build();
165                                 TvSingletons.getSingletons(getContext())
166                                         .getDvrManager()
167                                         .updateSeriesRecording(seriesRecording);
168                                 DvrUiHelper.startSeriesSettingsActivity(
169                                         getContext(),
170                                         header.getSeriesRecording().getId(),
171                                         header.getPrograms(),
172                                         false,
173                                         false,
174                                         false,
175                                         null);
176                             } else {
177                                 DvrUiHelper.showCancelAllSeriesRecordingDialog(
178                                         (DvrSchedulesActivity) view.getContext(),
179                                         header.getSeriesRecording());
180                             }
181                         }
182                     });
183         }
184 
setTextDrawable(TextView textView, Drawable drawableStart)185         private void setTextDrawable(TextView textView, Drawable drawableStart) {
186             textView.setCompoundDrawablesRelativeWithIntrinsicBounds(
187                     drawableStart, null, null, null);
188         }
189 
190         /** A ViewHolder for {@link SeriesRecordingHeaderRow}. */
191         public static class SeriesHeaderRowViewHolder extends SchedulesHeaderRowViewHolder {
192             private final TextView mSeriesSettingsButton;
193             private final TextView mToggleStartStopButton;
194             private final boolean mLtr;
195 
196             private final View mSelector;
197 
198             private View mLastFocusedView;
199 
SeriesHeaderRowViewHolder(Context context, ViewGroup parent)200             public SeriesHeaderRowViewHolder(Context context, ViewGroup parent) {
201                 super(context, parent);
202                 mLtr =
203                         context.getResources().getConfiguration().getLayoutDirection()
204                                 == View.LAYOUT_DIRECTION_LTR;
205                 view.findViewById(R.id.button_container).setVisibility(View.VISIBLE);
206                 mSeriesSettingsButton = (TextView) view.findViewById(R.id.series_settings);
207                 mToggleStartStopButton =
208                         (TextView) view.findViewById(R.id.series_toggle_start_stop);
209                 mSelector = view.findViewById(R.id.selector);
210                 OnFocusChangeListener onFocusChangeListener =
211                         new View.OnFocusChangeListener() {
212                             @Override
213                             public void onFocusChange(View view, boolean focused) {
214                                 view.post(() -> updateSelector(view));
215                             }
216                         };
217                 mSeriesSettingsButton.setOnFocusChangeListener(onFocusChangeListener);
218                 mToggleStartStopButton.setOnFocusChangeListener(onFocusChangeListener);
219             }
220 
updateSelector(View focusedView)221             private void updateSelector(View focusedView) {
222                 int animationDuration =
223                         mSelector
224                                 .getContext()
225                                 .getResources()
226                                 .getInteger(android.R.integer.config_shortAnimTime);
227                 DecelerateInterpolator interpolator = new DecelerateInterpolator();
228 
229                 if (focusedView.hasFocus()) {
230                     ViewGroup.LayoutParams lp = mSelector.getLayoutParams();
231                     final int targetWidth = focusedView.getWidth();
232                     float targetTranslationX;
233                     if (mLtr) {
234                         targetTranslationX = focusedView.getLeft() - mSelector.getLeft();
235                     } else {
236                         targetTranslationX = focusedView.getRight() - mSelector.getRight();
237                     }
238 
239                     // if the selector is invisible, set the width and translation X directly -
240                     // don't animate.
241                     if (mSelector.getAlpha() == 0) {
242                         mSelector.setTranslationX(targetTranslationX);
243                         lp.width = targetWidth;
244                         mSelector.requestLayout();
245                     }
246 
247                     // animate the selector in and to the proper width and translation X.
248                     final float deltaWidth = lp.width - targetWidth;
249                     mSelector.animate().cancel();
250                     mSelector
251                             .animate()
252                             .translationX(targetTranslationX)
253                             .alpha(1f)
254                             .setUpdateListener(
255                                     new ValueAnimator.AnimatorUpdateListener() {
256                                         @Override
257                                         public void onAnimationUpdate(ValueAnimator animation) {
258                                             // Set width to the proper width for this animation
259                                             // step.
260                                             lp.width =
261                                                     targetWidth
262                                                             + Math.round(
263                                                                     deltaWidth
264                                                                             * (1f
265                                                                                     - animation
266                                                                                             .getAnimatedFraction()));
267                                             mSelector.requestLayout();
268                                         }
269                                     })
270                             .setDuration(animationDuration)
271                             .setInterpolator(interpolator)
272                             .start();
273                     mLastFocusedView = focusedView;
274                 } else if (mLastFocusedView == focusedView) {
275                     mSelector.animate().setUpdateListener(null).cancel();
276                     mSelector
277                             .animate()
278                             .alpha(0f)
279                             .setDuration(animationDuration)
280                             .setInterpolator(interpolator)
281                             .start();
282                     mLastFocusedView = null;
283                 }
284             }
285         }
286     }
287 }
288