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 */
16package com.android.messaging.ui;
17
18import android.content.Context;
19import android.support.annotation.NonNull;
20import android.support.annotation.Nullable;
21import android.text.TextUtils;
22import android.view.LayoutInflater;
23import android.view.View;
24import android.view.View.OnClickListener;
25import android.view.ViewGroup.MarginLayoutParams;
26import android.widget.FrameLayout;
27import android.widget.TextView;
28
29import com.android.messaging.Factory;
30import com.android.messaging.R;
31import com.android.messaging.util.Assert;
32
33import java.util.ArrayList;
34import java.util.List;
35
36public 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