Home | History | Annotate | Download | only in ui
      1 /*
      2  * Copyright (C) 2015 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 package com.android.messaging.ui;
     17 
     18 import android.content.Context;
     19 import android.support.annotation.NonNull;
     20 import android.support.annotation.Nullable;
     21 import android.text.TextUtils;
     22 import android.view.LayoutInflater;
     23 import android.view.View;
     24 import android.view.View.OnClickListener;
     25 import android.view.ViewGroup.MarginLayoutParams;
     26 import android.widget.FrameLayout;
     27 import android.widget.TextView;
     28 
     29 import com.android.messaging.Factory;
     30 import com.android.messaging.R;
     31 import com.android.messaging.util.Assert;
     32 
     33 import java.util.ArrayList;
     34 import java.util.List;
     35 
     36 public class SnackBar {
     37     public static final int LONG_DURATION_IN_MS = 5000;
     38     public static final int SHORT_DURATION_IN_MS = 1000;
     39     public static final int MAX_DURATION_IN_MS = 10000;
     40 
     41     public interface SnackBarListener {
     42         void onActionClick();
     43     }
     44 
     45     /**
     46      * Defines an action to be performed when the user clicks on the action button on the snack bar
     47      */
     48     public static class Action {
     49         private final Runnable mActionRunnable;
     50         private final String mActionLabel;
     51 
     52         public final static int SNACK_BAR_UNDO = 0;
     53         public final static int SNACK_BAR_RETRY = 1;
     54 
     55         private Action(@Nullable Runnable actionRunnable, @Nullable String actionLabel) {
     56             mActionRunnable = actionRunnable;
     57             mActionLabel = actionLabel;
     58         }
     59 
     60         Runnable getActionRunnable() {
     61             return mActionRunnable;
     62         }
     63 
     64         String getActionLabel() {
     65             return mActionLabel;
     66         }
     67 
     68         public static Action createUndoAction(final Runnable undoRunnable) {
     69             return createCustomAction(undoRunnable, Factory.get().getApplicationContext()
     70                     .getString(R.string.snack_bar_undo));
     71         }
     72 
     73         public static Action createRetryAction(final Runnable retryRunnable) {
     74             return createCustomAction(retryRunnable, Factory.get().getApplicationContext()
     75                     .getString(R.string.snack_bar_retry));
     76         }
     77 
     78 
     79         public static Action createCustomAction(final Runnable runnable, final String actionLabel) {
     80             return new Action(runnable, actionLabel);
     81         }
     82     }
     83 
     84     /**
     85      * Defines the placement of the snack bar (e.g. anchored view, anchor gravity).
     86      */
     87     public static class Placement {
     88         private final View mAnchorView;
     89         private final boolean mAnchorAbove;
     90 
     91         private Placement(@NonNull final View anchorView, final boolean anchorAbove) {
     92             Assert.notNull(anchorView);
     93             mAnchorView = anchorView;
     94             mAnchorAbove = anchorAbove;
     95         }
     96 
     97         public View getAnchorView() {
     98             return mAnchorView;
     99         }
    100 
    101         public boolean getAnchorAbove() {
    102             return mAnchorAbove;
    103         }
    104 
    105         /**
    106          * Anchor the snack bar above the given {@code anchorView}.
    107          */
    108         public static Placement above(final View anchorView) {
    109             return new Placement(anchorView, true);
    110         }
    111 
    112         /**
    113          * Anchor the snack bar below the given {@code anchorView}.
    114          */
    115         public static Placement below(final View anchorView) {
    116             return new Placement(anchorView, false);
    117         }
    118     }
    119 
    120     public static class Builder {
    121         private static final List<SnackBarInteraction> NO_INTERACTIONS =
    122             new ArrayList<SnackBarInteraction>();
    123 
    124         private final Context mContext;
    125         private final SnackBarManager mSnackBarManager;
    126 
    127         private String mSnackBarMessage;
    128         private int mDuration = LONG_DURATION_IN_MS;
    129         private List<SnackBarInteraction> mInteractions = NO_INTERACTIONS;
    130         private Action mAction;
    131         private Placement mPlacement;
    132         // The parent view is only used to get a window token and doesn't affect the layout
    133         private View mParentView;
    134 
    135         public Builder(final SnackBarManager snackBarManager, final View parentView) {
    136             Assert.notNull(snackBarManager);
    137             Assert.notNull(parentView);
    138             mSnackBarManager = snackBarManager;
    139             mContext = parentView.getContext();
    140             mParentView = parentView;
    141         }
    142 
    143         public Builder setText(final String snackBarMessage) {
    144             Assert.isTrue(!TextUtils.isEmpty(snackBarMessage));
    145             mSnackBarMessage = snackBarMessage;
    146             return this;
    147         }
    148 
    149         public Builder setAction(final Action action) {
    150             mAction = action;
    151             return this;
    152         }
    153 
    154         /**
    155          * Sets the duration to show this toast for in milliseconds.
    156          */
    157         public Builder setDuration(final int duration) {
    158             Assert.isTrue(0 < duration && duration < MAX_DURATION_IN_MS);
    159             mDuration = duration;
    160             return this;
    161         }
    162 
    163         /**
    164          * Sets the components that this toast's animation will interact with. These components may
    165          * be animated to make room for the toast.
    166          */
    167         public Builder withInteractions(final List<SnackBarInteraction> interactions) {
    168             mInteractions = interactions;
    169             return this;
    170         }
    171 
    172         /**
    173          * Place the snack bar with the given placement requirement.
    174          */
    175         public Builder withPlacement(final Placement placement) {
    176             Assert.isNull(mPlacement);
    177             mPlacement = placement;
    178             return this;
    179         }
    180 
    181         public SnackBar build() {
    182             return new SnackBar(this);
    183         }
    184 
    185         public void show() {
    186             mSnackBarManager.show(build());
    187         }
    188     }
    189 
    190     private final View mRootView;
    191     private final Context mContext;
    192     private final View mSnackBarView;
    193     private final String mText;
    194     private final int mDuration;
    195     private final List<SnackBarInteraction> mInteractions;
    196     private final Action mAction;
    197     private final Placement mPlacement;
    198     private final TextView mActionTextView;
    199     private final TextView mMessageView;
    200     private final FrameLayout mMessageWrapper;
    201     private final View mParentView;
    202 
    203     private SnackBarListener mListener;
    204 
    205     private SnackBar(final Builder builder) {
    206         mContext = builder.mContext;
    207         mRootView = LayoutInflater.from(mContext).inflate(R.layout.snack_bar,
    208             null /* WindowManager will show this in show() below */);
    209         mSnackBarView = mRootView.findViewById(R.id.snack_bar);
    210         mText = builder.mSnackBarMessage;
    211         mDuration = builder.mDuration;
    212         mAction = builder.mAction;
    213         mPlacement = builder.mPlacement;
    214         mParentView = builder.mParentView;
    215         if (builder.mInteractions == null) {
    216             mInteractions = new ArrayList<SnackBarInteraction>();
    217         } else {
    218             mInteractions = builder.mInteractions;
    219         }
    220 
    221         mActionTextView = (TextView) mRootView.findViewById(R.id.snack_bar_action);
    222         mMessageView = (TextView) mRootView.findViewById(R.id.snack_bar_message);
    223         mMessageWrapper = (FrameLayout) mRootView.findViewById(R.id.snack_bar_message_wrapper);
    224 
    225         setUpButton();
    226         setUpTextLines();
    227     }
    228 
    229     public Context getContext() {
    230         return mContext;
    231     }
    232 
    233     public View getRootView() {
    234         return mRootView;
    235     }
    236 
    237     public View getParentView() {
    238         return mParentView;
    239     }
    240 
    241     public View getSnackBarView() {
    242         return mSnackBarView;
    243     }
    244 
    245     public String getMessageText() {
    246         return mText;
    247     }
    248 
    249     public String getActionLabel() {
    250         if (mAction == null) {
    251             return null;
    252         }
    253         return mAction.getActionLabel();
    254     }
    255 
    256     public int getDuration() {
    257         return mDuration;
    258     }
    259 
    260     public Placement getPlacement() {
    261         return mPlacement;
    262     }
    263 
    264     public List<SnackBarInteraction> getInteractions() {
    265         return mInteractions;
    266     }
    267 
    268     public void setEnabled(final boolean enabled) {
    269         mActionTextView.setClickable(enabled);
    270     }
    271 
    272     public void setListener(final SnackBarListener snackBarListener) {
    273         mListener = snackBarListener;
    274     }
    275 
    276     private void setUpButton() {
    277         if (mAction == null || mAction.getActionRunnable() == null) {
    278             mActionTextView.setVisibility(View.GONE);
    279             // In the XML layout we add left/right padding to the button to add space between
    280             // the message text and the button and on the right side we add padding to put space
    281             // between the button and the edge of the snack bar. This is so the button can use the
    282             // padding area as part of it's click target. Since we have no button, we need to put
    283             // some margin on the right side. While the left margin is already set on the wrapper,
    284             // we're setting it again to not have to make a special case for RTL.
    285             final MarginLayoutParams lp = (MarginLayoutParams) mMessageWrapper.getLayoutParams();
    286             final int leftRightMargin = mContext.getResources().getDimensionPixelSize(
    287                     R.dimen.snack_bar_left_right_margin);
    288             lp.leftMargin = leftRightMargin;
    289             lp.rightMargin = leftRightMargin;
    290             mMessageWrapper.setLayoutParams(lp);
    291         } else {
    292             mActionTextView.setVisibility(View.VISIBLE);
    293             mActionTextView.setText(mAction.getActionLabel());
    294             mActionTextView.setOnClickListener(new OnClickListener() {
    295                 @Override
    296                 public void onClick(final View v) {
    297                     mAction.getActionRunnable().run();
    298                     if (mListener != null) {
    299                         mListener.onActionClick();
    300                     }
    301                 }
    302             });
    303         }
    304     }
    305 
    306     private void setUpTextLines() {
    307         if (mText == null) {
    308             mMessageView.setVisibility(View.GONE);
    309         } else {
    310             mMessageView.setVisibility(View.VISIBLE);
    311             mMessageView.setText(mText);
    312         }
    313     }
    314 }
    315