• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 androidx.annotation.NonNull;
20 import androidx.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 {
onActionClick()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 
Action(@ullable Runnable actionRunnable, @Nullable String actionLabel)55         private Action(@Nullable Runnable actionRunnable, @Nullable String actionLabel) {
56             mActionRunnable = actionRunnable;
57             mActionLabel = actionLabel;
58         }
59 
getActionRunnable()60         Runnable getActionRunnable() {
61             return mActionRunnable;
62         }
63 
getActionLabel()64         String getActionLabel() {
65             return mActionLabel;
66         }
67 
createUndoAction(final Runnable undoRunnable)68         public static Action createUndoAction(final Runnable undoRunnable) {
69             return createCustomAction(undoRunnable, Factory.get().getApplicationContext()
70                     .getString(R.string.snack_bar_undo));
71         }
72 
createRetryAction(final Runnable retryRunnable)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 
createCustomAction(final Runnable runnable, final String actionLabel)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 
Placement(@onNull final View anchorView, final boolean anchorAbove)91         private Placement(@NonNull final View anchorView, final boolean anchorAbove) {
92             Assert.notNull(anchorView);
93             mAnchorView = anchorView;
94             mAnchorAbove = anchorAbove;
95         }
96 
getAnchorView()97         public View getAnchorView() {
98             return mAnchorView;
99         }
100 
getAnchorAbove()101         public boolean getAnchorAbove() {
102             return mAnchorAbove;
103         }
104 
105         /**
106          * Anchor the snack bar above the given {@code anchorView}.
107          */
above(final View anchorView)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          */
below(final View anchorView)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 
Builder(final SnackBarManager snackBarManager, final View parentView)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 
setText(final String snackBarMessage)143         public Builder setText(final String snackBarMessage) {
144             Assert.isTrue(!TextUtils.isEmpty(snackBarMessage));
145             mSnackBarMessage = snackBarMessage;
146             return this;
147         }
148 
setAction(final Action action)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          */
setDuration(final int duration)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          */
withInteractions(final List<SnackBarInteraction> interactions)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          */
withPlacement(final Placement placement)175         public Builder withPlacement(final Placement placement) {
176             Assert.isNull(mPlacement);
177             mPlacement = placement;
178             return this;
179         }
180 
build()181         public SnackBar build() {
182             return new SnackBar(this);
183         }
184 
show()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 
SnackBar(final Builder builder)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 
getContext()229     public Context getContext() {
230         return mContext;
231     }
232 
getRootView()233     public View getRootView() {
234         return mRootView;
235     }
236 
getParentView()237     public View getParentView() {
238         return mParentView;
239     }
240 
getSnackBarView()241     public View getSnackBarView() {
242         return mSnackBarView;
243     }
244 
getMessageText()245     public String getMessageText() {
246         return mText;
247     }
248 
getActionLabel()249     public String getActionLabel() {
250         if (mAction == null) {
251             return null;
252         }
253         return mAction.getActionLabel();
254     }
255 
getDuration()256     public int getDuration() {
257         return mDuration;
258     }
259 
getPlacement()260     public Placement getPlacement() {
261         return mPlacement;
262     }
263 
getInteractions()264     public List<SnackBarInteraction> getInteractions() {
265         return mInteractions;
266     }
267 
setEnabled(final boolean enabled)268     public void setEnabled(final boolean enabled) {
269         mActionTextView.setClickable(enabled);
270     }
271 
setListener(final SnackBarListener snackBarListener)272     public void setListener(final SnackBarListener snackBarListener) {
273         mListener = snackBarListener;
274     }
275 
setUpButton()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 
setUpTextLines()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