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;
18 
19 import android.app.Activity;
20 import android.animation.Animator;
21 import android.animation.AnimatorSet;
22 import android.animation.ObjectAnimator;
23 import android.animation.PropertyValuesHolder;
24 import android.graphics.Paint;
25 import android.graphics.Paint.FontMetricsInt;
26 import android.support.v17.leanback.widget.Presenter;
27 import android.text.TextUtils;
28 import android.view.LayoutInflater;
29 import android.view.View;
30 import android.view.ViewGroup;
31 import android.view.ViewTreeObserver;
32 import android.widget.LinearLayout;
33 import android.widget.TextView;
34 
35 import com.android.tv.R;
36 import com.android.tv.ui.ViewUtils;
37 import com.android.tv.util.Utils;
38 
39 /**
40  * An {@link Presenter} for rendering a detailed description of an DVR item.
41  * Typically this Presenter will be used in a {@link DetailsOverviewRowPresenter}.
42  * Most codes of this class is originated from
43  * {@link android.support.v17.leanback.widget.AbstractDetailsDescriptionPresenter}.
44  * The latter class are re-used to provide a customized version of
45  * {@link android.support.v17.leanback.widget.DetailsOverviewRow}.
46  */
47 public class DetailsContentPresenter extends Presenter {
48     /**
49      * The ViewHolder for the {@link DetailsContentPresenter}.
50      */
51     public static class ViewHolder extends Presenter.ViewHolder {
52         final TextView mTitle;
53         final TextView mSubtitle;
54         final LinearLayout mDescriptionContainer;
55         final TextView mBody;
56         final TextView mReadMoreView;
57         final int mTitleMargin;
58         final int mUnderTitleBaselineMargin;
59         final int mUnderSubtitleBaselineMargin;
60         final int mTitleLineSpacing;
61         final int mBodyLineSpacing;
62         final int mBodyMaxLines;
63         final int mBodyMinLines;
64         final FontMetricsInt mTitleFontMetricsInt;
65         final FontMetricsInt mSubtitleFontMetricsInt;
66         final FontMetricsInt mBodyFontMetricsInt;
67         final int mTitleMaxLines;
68 
69         private Activity mActivity;
70         private boolean mFullTextMode;
71         private int mFullTextAnimationDuration;
72         private boolean mIsListeningToPreDraw;
73 
74         private ViewTreeObserver.OnPreDrawListener mPreDrawListener =
75                 new ViewTreeObserver.OnPreDrawListener() {
76                     @Override
77                     public boolean onPreDraw() {
78                         if (mSubtitle.getVisibility() == View.VISIBLE
79                                 && mSubtitle.getTop() > view.getHeight()
80                                 && mTitle.getLineCount() > 1) {
81                             mTitle.setMaxLines(mTitle.getLineCount() - 1);
82                             return false;
83                         }
84                         final int bodyLines = mBody.getLineCount();
85                         final int maxLines = mFullTextMode ? bodyLines :
86                                 (mTitle.getLineCount() > 1 ? mBodyMinLines : mBodyMaxLines);
87                         if (bodyLines > maxLines) {
88                             mReadMoreView.setVisibility(View.VISIBLE);
89                             mDescriptionContainer.setFocusable(true);
90                             mDescriptionContainer.setOnClickListener(new View.OnClickListener() {
91                                 @Override
92                                 public void onClick(View view) {
93                                     mFullTextMode = true;
94                                     mReadMoreView.setVisibility(View.GONE);
95                                     mDescriptionContainer.setFocusable(false);
96                                     mDescriptionContainer.setOnClickListener(null);
97                                     mBody.setMaxLines(bodyLines);
98                                     // Minus 1 from line difference to eliminate the space
99                                     // originally occupied by "READ MORE"
100                                     showFullText((bodyLines - maxLines - 1) * mBodyLineSpacing);
101                                 }
102                             });
103                         }
104                         if (mBody.getMaxLines() != maxLines) {
105                             mBody.setMaxLines(maxLines);
106                             return false;
107                         } else {
108                             removePreDrawListener();
109                             return true;
110                         }
111                     }
112                 };
113 
ViewHolder(final View view)114         public ViewHolder(final View view) {
115             super(view);
116             mTitle = (TextView) view.findViewById(R.id.dvr_details_description_title);
117             mSubtitle = (TextView) view.findViewById(R.id.dvr_details_description_subtitle);
118             mBody = (TextView) view.findViewById(R.id.dvr_details_description_body);
119             mDescriptionContainer =
120                     (LinearLayout) view.findViewById(R.id.dvr_details_description_container);
121             mReadMoreView = (TextView) view.findViewById(R.id.dvr_details_description_read_more);
122 
123             FontMetricsInt titleFontMetricsInt = getFontMetricsInt(mTitle);
124             final int titleAscent = view.getResources().getDimensionPixelSize(
125                     R.dimen.lb_details_description_title_baseline);
126             // Ascent is negative
127             mTitleMargin = titleAscent + titleFontMetricsInt.ascent;
128 
129             mUnderTitleBaselineMargin = view.getResources().getDimensionPixelSize(
130                     R.dimen.lb_details_description_under_title_baseline_margin);
131             mUnderSubtitleBaselineMargin = view.getResources().getDimensionPixelSize(
132                     R.dimen.lb_details_description_under_subtitle_baseline_margin);
133 
134             mTitleLineSpacing = view.getResources().getDimensionPixelSize(
135                     R.dimen.lb_details_description_title_line_spacing);
136             mBodyLineSpacing = view.getResources().getDimensionPixelSize(
137                     R.dimen.lb_details_description_body_line_spacing);
138 
139             mBodyMaxLines = view.getResources().getInteger(
140                     R.integer.lb_details_description_body_max_lines);
141             mBodyMinLines = view.getResources().getInteger(
142                     R.integer.lb_details_description_body_min_lines);
143             mTitleMaxLines = mTitle.getMaxLines();
144 
145             mTitleFontMetricsInt = getFontMetricsInt(mTitle);
146             mSubtitleFontMetricsInt = getFontMetricsInt(mSubtitle);
147             mBodyFontMetricsInt = getFontMetricsInt(mBody);
148         }
149 
addPreDrawListener()150         void addPreDrawListener() {
151             if (!mIsListeningToPreDraw) {
152                 mIsListeningToPreDraw = true;
153                 view.getViewTreeObserver().addOnPreDrawListener(mPreDrawListener);
154             }
155         }
156 
removePreDrawListener()157         void removePreDrawListener() {
158             if (mIsListeningToPreDraw) {
159                 view.getViewTreeObserver().removeOnPreDrawListener(mPreDrawListener);
160                 mIsListeningToPreDraw = false;
161             }
162         }
163 
getTitle()164         public TextView getTitle() {
165             return mTitle;
166         }
167 
getSubtitle()168         public TextView getSubtitle() {
169             return mSubtitle;
170         }
171 
getBody()172         public TextView getBody() {
173             return mBody;
174         }
175 
getFontMetricsInt(TextView textView)176         private FontMetricsInt getFontMetricsInt(TextView textView) {
177             Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG);
178             paint.setTextSize(textView.getTextSize());
179             paint.setTypeface(textView.getTypeface());
180             return paint.getFontMetricsInt();
181         }
182 
showFullText(int heightDiff)183         private void showFullText(int heightDiff) {
184             final ViewGroup detailsFrame = (ViewGroup) mActivity.findViewById(R.id.details_frame);
185             int nowHeight = ViewUtils.getLayoutHeight(detailsFrame);
186             Animator expandAnimator = ViewUtils.createHeightAnimator(
187                     detailsFrame, nowHeight, nowHeight + heightDiff);
188             expandAnimator.setDuration(mFullTextAnimationDuration);
189             Animator shiftAnimator = ObjectAnimator.ofPropertyValuesHolder(detailsFrame,
190                     PropertyValuesHolder.ofFloat(View.TRANSLATION_Y,
191                             0f, -(heightDiff / 2)));
192             shiftAnimator.setDuration(mFullTextAnimationDuration);
193             AnimatorSet fullTextAnimator = new AnimatorSet();
194             fullTextAnimator.playTogether(expandAnimator, shiftAnimator);
195             fullTextAnimator.start();
196         }
197     }
198 
199     private final Activity mActivity;
200     private final int mFullTextAnimationDuration;
201 
DetailsContentPresenter(Activity activity)202     public DetailsContentPresenter(Activity activity) {
203         super();
204         mActivity = activity;
205         mFullTextAnimationDuration = mActivity.getResources()
206                 .getInteger(R.integer.dvr_details_full_text_animation_duration);
207     }
208 
209     @Override
onCreateViewHolder(ViewGroup parent)210     public final ViewHolder onCreateViewHolder(ViewGroup parent) {
211         View v = LayoutInflater.from(parent.getContext())
212                 .inflate(R.layout.dvr_details_description, parent, false);
213         return new ViewHolder(v);
214     }
215 
216     @Override
onBindViewHolder(Presenter.ViewHolder viewHolder, Object item)217     public final void onBindViewHolder(Presenter.ViewHolder viewHolder, Object item) {
218         final ViewHolder vh = (ViewHolder) viewHolder;
219         final DetailsContent detailsContent = (DetailsContent) item;
220 
221         vh.mActivity = mActivity;
222         vh.mFullTextAnimationDuration = mFullTextAnimationDuration;
223 
224         boolean hasTitle = true;
225         if (TextUtils.isEmpty(detailsContent.getTitle())) {
226             vh.mTitle.setVisibility(View.GONE);
227             hasTitle = false;
228         } else {
229             vh.mTitle.setText(detailsContent.getTitle());
230             vh.mTitle.setVisibility(View.VISIBLE);
231             vh.mTitle.setLineSpacing(vh.mTitleLineSpacing - vh.mTitle.getLineHeight()
232                     + vh.mTitle.getLineSpacingExtra(), vh.mTitle.getLineSpacingMultiplier());
233             vh.mTitle.setMaxLines(vh.mTitleMaxLines);
234         }
235         setTopMargin(vh.mTitle, vh.mTitleMargin);
236 
237         boolean hasSubtitle = true;
238         if (detailsContent.getStartTimeUtcMillis() != DetailsContent.INVALID_TIME
239                 && detailsContent.getEndTimeUtcMillis() != DetailsContent.INVALID_TIME) {
240             vh.mSubtitle.setText(Utils.getDurationString(viewHolder.view.getContext(),
241                     detailsContent.getStartTimeUtcMillis(),
242                     detailsContent.getEndTimeUtcMillis(), false));
243             vh.mSubtitle.setVisibility(View.VISIBLE);
244             if (hasTitle) {
245                 setTopMargin(vh.mSubtitle, vh.mUnderTitleBaselineMargin
246                         + vh.mSubtitleFontMetricsInt.ascent - vh.mTitleFontMetricsInt.descent);
247             } else {
248                 setTopMargin(vh.mSubtitle, 0);
249             }
250         } else {
251             vh.mSubtitle.setVisibility(View.GONE);
252             hasSubtitle = false;
253         }
254 
255         if (TextUtils.isEmpty(detailsContent.getDescription())) {
256             vh.mBody.setVisibility(View.GONE);
257         } else {
258             vh.mBody.setText(detailsContent.getDescription());
259             vh.mBody.setVisibility(View.VISIBLE);
260             vh.mBody.setLineSpacing(vh.mBodyLineSpacing - vh.mBody.getLineHeight()
261                     + vh.mBody.getLineSpacingExtra(), vh.mBody.getLineSpacingMultiplier());
262             if (hasSubtitle) {
263                 setTopMargin(vh.mDescriptionContainer, vh.mUnderSubtitleBaselineMargin
264                         + vh.mBodyFontMetricsInt.ascent - vh.mSubtitleFontMetricsInt.descent
265                         - vh.mBody.getPaddingTop());
266             } else if (hasTitle) {
267                 setTopMargin(vh.mDescriptionContainer, vh.mUnderTitleBaselineMargin
268                         + vh.mBodyFontMetricsInt.ascent - vh.mTitleFontMetricsInt.descent
269                         - vh.mBody.getPaddingTop());
270             } else {
271                 setTopMargin(vh.mDescriptionContainer, 0);
272             }
273         }
274     }
275 
276     @Override
onUnbindViewHolder(Presenter.ViewHolder viewHolder)277     public void onUnbindViewHolder(Presenter.ViewHolder viewHolder) { }
278 
279     @Override
onViewAttachedToWindow(Presenter.ViewHolder holder)280     public void onViewAttachedToWindow(Presenter.ViewHolder holder) {
281         // In case predraw listener was removed in detach, make sure
282         // we have the proper layout.
283         ViewHolder vh = (ViewHolder) holder;
284         vh.addPreDrawListener();
285         super.onViewAttachedToWindow(holder);
286     }
287 
288     @Override
onViewDetachedFromWindow(Presenter.ViewHolder holder)289     public void onViewDetachedFromWindow(Presenter.ViewHolder holder) {
290         ViewHolder vh = (ViewHolder) holder;
291         vh.removePreDrawListener();
292         super.onViewDetachedFromWindow(holder);
293     }
294 
setTopMargin(View view, int topMargin)295     private void setTopMargin(View view, int topMargin) {
296         ViewGroup.MarginLayoutParams lp = (ViewGroup.MarginLayoutParams) view.getLayoutParams();
297         lp.topMargin = topMargin;
298         view.setLayoutParams(lp);
299     }
300 }