Home | History | Annotate | Download | only in ui
      1 /*
      2  * Copyright (C) 2014 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.mail.ui;
     18 
     19 import android.animation.ObjectAnimator;
     20 import android.app.LoaderManager;
     21 import android.content.Context;
     22 import android.content.res.Resources;
     23 import android.os.Bundle;
     24 import android.support.annotation.LayoutRes;
     25 import android.util.AttributeSet;
     26 import android.view.LayoutInflater;
     27 import android.view.View;
     28 import android.view.ViewGroup;
     29 import android.view.animation.DecelerateInterpolator;
     30 import android.widget.AbsListView;
     31 import android.widget.ImageView;
     32 import android.widget.LinearLayout;
     33 import android.widget.TextView;
     34 
     35 import com.android.mail.R;
     36 import com.android.mail.browse.ConversationCursor;
     37 import com.android.mail.providers.Folder;
     38 import com.android.mail.utils.LogTag;
     39 
     40 /**
     41  * Base class to display tip teasers in the thread list.
     42  * Supports two-line text and start/end icons.
     43  */
     44 public abstract class ConversationTipView extends LinearLayout
     45         implements ConversationSpecialItemView, SwipeableItemView, View.OnClickListener {
     46     protected static final String LOG_TAG = LogTag.getLogTag();
     47 
     48     protected Context mContext;
     49     protected AnimatedAdapter mAdapter;
     50 
     51     private int mScrollSlop;
     52     private int mShrinkAnimationDuration;
     53     private int mAnimatedHeight = -1;
     54 
     55     protected View mSwipeableContent;
     56     private View mContent;
     57     private TextView mText;
     58 
     59     public ConversationTipView(Context context) {
     60         this(context, null);
     61     }
     62 
     63     public ConversationTipView(Context context, AttributeSet attrs) {
     64         super(context, attrs);
     65         mContext = context;
     66 
     67         final Resources resources = context.getResources();
     68         mScrollSlop = resources.getInteger(R.integer.swipeScrollSlop);
     69         mShrinkAnimationDuration = resources.getInteger(
     70                 R.integer.shrink_animation_duration);
     71 
     72         // Inflate the actual content and add it to this view
     73         mContent = LayoutInflater.from(mContext).inflate(getChildLayout(), this, false);
     74         addView(mContent);
     75         setupViews();
     76     }
     77 
     78     @Override
     79     public ViewGroup.LayoutParams getLayoutParams() {
     80         ViewGroup.LayoutParams params = super.getLayoutParams();
     81         if (params != null) {
     82             params.width = ViewGroup.LayoutParams.MATCH_PARENT;
     83             params.height = ViewGroup.LayoutParams.WRAP_CONTENT;
     84         }
     85         return params;
     86     }
     87 
     88     protected @LayoutRes int getChildLayout() {
     89         // Should override setupViews as well if this is overridden.
     90         return R.layout.conversation_tip_view;
     91     }
     92 
     93     protected void setupViews() {
     94         // If this is overridden, child classes cannot rely on setText/getStartIconAttr/etc.
     95         mSwipeableContent = mContent.findViewById(R.id.conversation_tip_swipeable_content);
     96         mText = (TextView) mContent.findViewById(R.id.conversation_tip_text);
     97         final ImageView startImage = (ImageView) mContent.findViewById(R.id.conversation_tip_icon1);
     98         final ImageView dismiss = (ImageView) mContent.findViewById(R.id.dismiss_icon);
     99 
    100         // Bind content (text content must be bound by calling setText(..))
    101         bindIcon(startImage, getStartIconAttr());
    102 
    103         // Bind listeners
    104         dismiss.setOnClickListener(this);
    105         mText.setOnClickListener(getTextAreaOnClickListener());
    106     }
    107 
    108     /**
    109      * Helper function to bind the additional attributes to the icon, or make the icon GONE.
    110      */
    111     private void bindIcon(ImageView image, ImageAttrSet attr) {
    112         if (attr != null) {
    113             image.setVisibility(VISIBLE);
    114             image.setContentDescription(attr.contentDescription);
    115             // Must override resId for the actual icon, so no need to check -1 here.
    116             image.setImageResource(attr.resId);
    117             if (attr.background != -1) {
    118                 image.setBackgroundResource(attr.background);
    119             }
    120         } else {
    121             image.setVisibility(GONE);
    122         }
    123     }
    124 
    125     @Override
    126     protected void onMeasure(final int widthMeasureSpec, final int heightMeasureSpec) {
    127         if (mAnimatedHeight == -1) {
    128             super.onMeasure(widthMeasureSpec, heightMeasureSpec);
    129         } else {
    130             setMeasuredDimension(View.MeasureSpec.getSize(widthMeasureSpec), mAnimatedHeight);
    131         }
    132     }
    133 
    134     protected ImageAttrSet getStartIconAttr() {
    135         return null;
    136     }
    137 
    138     protected void setText(CharSequence text) {
    139         mText.setText(text);
    140     }
    141 
    142     protected OnClickListener getTextAreaOnClickListener() {
    143         return null;
    144     }
    145 
    146     @Override
    147     public void onClick(View view) {
    148         // Default on click for the default dismiss button
    149         dismiss();
    150     }
    151 
    152     @Override
    153     public void onUpdate(Folder folder, ConversationCursor cursor) {
    154         // Do nothing by default
    155     }
    156 
    157     @Override
    158     public void onGetView() {
    159         // Do nothing by default
    160     }
    161 
    162     @Override
    163     public int getPosition() {
    164         // By default the tip teasers go on top of the list.
    165         return 0;
    166     }
    167 
    168     @Override
    169     public void setAdapter(AnimatedAdapter adapter) {
    170         mAdapter = adapter;
    171     }
    172 
    173     @Override
    174     public void bindFragment(LoaderManager loaderManager, Bundle savedInstanceState) {
    175         // Do nothing by default
    176     }
    177 
    178     @Override
    179     public void cleanup() {
    180         // Do nothing by default
    181     }
    182 
    183     @Override
    184     public void onConversationSelected() {
    185         // Do nothing by default
    186     }
    187 
    188     @Override
    189     public void onCabModeEntered() {
    190         // Do nothing by default
    191     }
    192 
    193     @Override
    194     public void onCabModeExited() {
    195         // Do nothing by default
    196     }
    197 
    198     @Override
    199     public boolean acceptsUserTaps() {
    200         return true;
    201     }
    202 
    203     @Override
    204     public void onConversationListVisibilityChanged(boolean visible) {
    205         // Do nothing by default
    206     }
    207 
    208     @Override
    209     public void saveInstanceState(Bundle outState) {
    210         // Do nothing by default
    211     }
    212 
    213     @Override
    214     public boolean commitLeaveBehindItem() {
    215         // Tip has no leave-behind by default
    216         return false;
    217     }
    218 
    219     @Override
    220     public SwipeableView getSwipeableView() {
    221         return SwipeableView.from(mSwipeableContent);
    222     }
    223 
    224     @Override
    225     public boolean canChildBeDismissed() {
    226         return true;
    227     }
    228 
    229     @Override
    230     public void dismiss() {
    231         startDestroyAnimation();
    232     }
    233 
    234     @Override
    235     public float getMinAllowScrollDistance() {
    236         return mScrollSlop;
    237     }
    238 
    239     private void startDestroyAnimation() {
    240         final int start = getHeight();
    241         final int end = 0;
    242         mAnimatedHeight = start;
    243         final ObjectAnimator heightAnimator =
    244                 ObjectAnimator.ofInt(this, "animatedHeight", start, end);
    245         heightAnimator.setInterpolator(new DecelerateInterpolator(2.0f));
    246         heightAnimator.setDuration(mShrinkAnimationDuration);
    247         heightAnimator.start();
    248 
    249         /*
    250          * Ideally, we would like to call mAdapter.notifyDataSetChanged() in a listener's
    251          * onAnimationEnd(), but we are in the middle of a touch event, and this will cause all the
    252          * views to get recycled, which will cause problems.
    253          *
    254          * Instead, we'll just leave the item in the list with a height of 0, and the next
    255          * notifyDatasetChanged() will remove it from the adapter.
    256          */
    257     }
    258 
    259     /**
    260      * This method is used by the animator.  It is explicitly kept in proguard.flags to prevent it
    261      * from being removed, inlined, or obfuscated.
    262      * Edit ./vendor/unbundled/packages/apps/UnifiedGmail/proguard.flags
    263      * In the future, we want to use @Keep
    264      */
    265     public void setAnimatedHeight(final int height) {
    266         mAnimatedHeight = height;
    267         requestLayout();
    268     }
    269 
    270     public static class ImageAttrSet {
    271         // -1 for these resIds to not override the default value.
    272         public int resId;
    273         public int background;
    274         public String contentDescription;
    275 
    276         public ImageAttrSet(int resId, int background, String contentDescription) {
    277             this.resId = resId;
    278             this.background = background;
    279             this.contentDescription = contentDescription;
    280         }
    281     }
    282 }
    283