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