/* * Copyright (C) 2015 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.messaging.ui; import android.content.Context; import androidx.annotation.NonNull; import androidx.annotation.Nullable; import android.text.TextUtils; import android.view.LayoutInflater; import android.view.View; import android.view.View.OnClickListener; import android.view.ViewGroup.MarginLayoutParams; import android.widget.FrameLayout; import android.widget.TextView; import com.android.messaging.Factory; import com.android.messaging.R; import com.android.messaging.util.Assert; import java.util.ArrayList; import java.util.List; public class SnackBar { public static final int LONG_DURATION_IN_MS = 5000; public static final int SHORT_DURATION_IN_MS = 1000; public static final int MAX_DURATION_IN_MS = 10000; public interface SnackBarListener { void onActionClick(); } /** * Defines an action to be performed when the user clicks on the action button on the snack bar */ public static class Action { private final Runnable mActionRunnable; private final String mActionLabel; public final static int SNACK_BAR_UNDO = 0; public final static int SNACK_BAR_RETRY = 1; private Action(@Nullable Runnable actionRunnable, @Nullable String actionLabel) { mActionRunnable = actionRunnable; mActionLabel = actionLabel; } Runnable getActionRunnable() { return mActionRunnable; } String getActionLabel() { return mActionLabel; } public static Action createUndoAction(final Runnable undoRunnable) { return createCustomAction(undoRunnable, Factory.get().getApplicationContext() .getString(R.string.snack_bar_undo)); } public static Action createRetryAction(final Runnable retryRunnable) { return createCustomAction(retryRunnable, Factory.get().getApplicationContext() .getString(R.string.snack_bar_retry)); } public static Action createCustomAction(final Runnable runnable, final String actionLabel) { return new Action(runnable, actionLabel); } } /** * Defines the placement of the snack bar (e.g. anchored view, anchor gravity). */ public static class Placement { private final View mAnchorView; private final boolean mAnchorAbove; private Placement(@NonNull final View anchorView, final boolean anchorAbove) { Assert.notNull(anchorView); mAnchorView = anchorView; mAnchorAbove = anchorAbove; } public View getAnchorView() { return mAnchorView; } public boolean getAnchorAbove() { return mAnchorAbove; } /** * Anchor the snack bar above the given {@code anchorView}. */ public static Placement above(final View anchorView) { return new Placement(anchorView, true); } /** * Anchor the snack bar below the given {@code anchorView}. */ public static Placement below(final View anchorView) { return new Placement(anchorView, false); } } public static class Builder { private static final List NO_INTERACTIONS = new ArrayList(); private final Context mContext; private final SnackBarManager mSnackBarManager; private String mSnackBarMessage; private int mDuration = LONG_DURATION_IN_MS; private List mInteractions = NO_INTERACTIONS; private Action mAction; private Placement mPlacement; // The parent view is only used to get a window token and doesn't affect the layout private View mParentView; public Builder(final SnackBarManager snackBarManager, final View parentView) { Assert.notNull(snackBarManager); Assert.notNull(parentView); mSnackBarManager = snackBarManager; mContext = parentView.getContext(); mParentView = parentView; } public Builder setText(final String snackBarMessage) { Assert.isTrue(!TextUtils.isEmpty(snackBarMessage)); mSnackBarMessage = snackBarMessage; return this; } public Builder setAction(final Action action) { mAction = action; return this; } /** * Sets the duration to show this toast for in milliseconds. */ public Builder setDuration(final int duration) { Assert.isTrue(0 < duration && duration < MAX_DURATION_IN_MS); mDuration = duration; return this; } /** * Sets the components that this toast's animation will interact with. These components may * be animated to make room for the toast. */ public Builder withInteractions(final List interactions) { mInteractions = interactions; return this; } /** * Place the snack bar with the given placement requirement. */ public Builder withPlacement(final Placement placement) { Assert.isNull(mPlacement); mPlacement = placement; return this; } public SnackBar build() { return new SnackBar(this); } public void show() { mSnackBarManager.show(build()); } } private final View mRootView; private final Context mContext; private final View mSnackBarView; private final String mText; private final int mDuration; private final List mInteractions; private final Action mAction; private final Placement mPlacement; private final TextView mActionTextView; private final TextView mMessageView; private final FrameLayout mMessageWrapper; private final View mParentView; private SnackBarListener mListener; private SnackBar(final Builder builder) { mContext = builder.mContext; mRootView = LayoutInflater.from(mContext).inflate(R.layout.snack_bar, null /* WindowManager will show this in show() below */); mSnackBarView = mRootView.findViewById(R.id.snack_bar); mText = builder.mSnackBarMessage; mDuration = builder.mDuration; mAction = builder.mAction; mPlacement = builder.mPlacement; mParentView = builder.mParentView; if (builder.mInteractions == null) { mInteractions = new ArrayList(); } else { mInteractions = builder.mInteractions; } mActionTextView = (TextView) mRootView.findViewById(R.id.snack_bar_action); mMessageView = (TextView) mRootView.findViewById(R.id.snack_bar_message); mMessageWrapper = (FrameLayout) mRootView.findViewById(R.id.snack_bar_message_wrapper); setUpButton(); setUpTextLines(); } public Context getContext() { return mContext; } public View getRootView() { return mRootView; } public View getParentView() { return mParentView; } public View getSnackBarView() { return mSnackBarView; } public String getMessageText() { return mText; } public String getActionLabel() { if (mAction == null) { return null; } return mAction.getActionLabel(); } public int getDuration() { return mDuration; } public Placement getPlacement() { return mPlacement; } public List getInteractions() { return mInteractions; } public void setEnabled(final boolean enabled) { mActionTextView.setClickable(enabled); } public void setListener(final SnackBarListener snackBarListener) { mListener = snackBarListener; } private void setUpButton() { if (mAction == null || mAction.getActionRunnable() == null) { mActionTextView.setVisibility(View.GONE); // In the XML layout we add left/right padding to the button to add space between // the message text and the button and on the right side we add padding to put space // between the button and the edge of the snack bar. This is so the button can use the // padding area as part of it's click target. Since we have no button, we need to put // some margin on the right side. While the left margin is already set on the wrapper, // we're setting it again to not have to make a special case for RTL. final MarginLayoutParams lp = (MarginLayoutParams) mMessageWrapper.getLayoutParams(); final int leftRightMargin = mContext.getResources().getDimensionPixelSize( R.dimen.snack_bar_left_right_margin); lp.leftMargin = leftRightMargin; lp.rightMargin = leftRightMargin; mMessageWrapper.setLayoutParams(lp); } else { mActionTextView.setVisibility(View.VISIBLE); mActionTextView.setText(mAction.getActionLabel()); mActionTextView.setOnClickListener(new OnClickListener() { @Override public void onClick(final View v) { mAction.getActionRunnable().run(); if (mListener != null) { mListener.onActionClick(); } } }); } } private void setUpTextLines() { if (mText == null) { mMessageView.setVisibility(View.GONE); } else { mMessageView.setVisibility(View.VISIBLE); mMessageView.setText(mText); } } }