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