/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*- * This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ package org.mozilla.gecko; import org.mozilla.gecko.util.EventCallback; import org.mozilla.gecko.util.NativeJSObject; import android.app.Activity; import android.graphics.Color; import android.graphics.drawable.Drawable; import android.graphics.drawable.InsetDrawable; import android.support.annotation.StringRes; import android.support.design.widget.Snackbar; import android.support.v4.content.ContextCompat; import android.text.TextUtils; import android.util.Log; import android.util.TypedValue; import android.view.View; import android.widget.TextView; import java.lang.ref.WeakReference; /** * Helper class for creating and dismissing snackbars. Use this class to guarantee a consistent style and behavior * across the app. */ public class SnackbarBuilder { /** * Combined interface for handling all callbacks from a snackbar because anonymous classes can only extend one * interface or class. */ public static abstract class SnackbarCallback extends Snackbar.Callback implements View.OnClickListener {} public static final String LOGTAG = "GeckoSnackbarBuilder"; /** * SnackbarCallback implementation for delegating snackbar events to an EventCallback. */ private static class SnackbarEventCallback extends SnackbarCallback { private EventCallback callback; public SnackbarEventCallback(EventCallback callback) { this.callback = callback; } @Override public synchronized void onClick(View view) { if (callback == null) { return; } callback.sendSuccess(null); callback = null; // Releasing reference. We only want to execute the callback once. } @Override public synchronized void onDismissed(Snackbar snackbar, int event) { if (callback == null || event == Snackbar.Callback.DISMISS_EVENT_ACTION) { return; } callback.sendError(null); callback = null; // Releasing reference. We only want to execute the callback once. } } private static final Object currentSnackbarLock = new Object(); private static WeakReference currentSnackbar = new WeakReference<>(null); // Guarded by 'currentSnackbarLock' private final Activity activity; private String message; private int duration; private String action; private SnackbarCallback callback; private Drawable icon; private Integer backgroundColor; private Integer actionColor; /** * @param activity Activity to show the snackbar in. */ private SnackbarBuilder(final Activity activity) { this.activity = activity; } public static SnackbarBuilder builder(final Activity activity) { return new SnackbarBuilder(activity); } /** * @param message The text to show. Can be formatted text. */ public SnackbarBuilder message(final String message) { this.message = message; return this; } /** * @param id The id of the string resource to show. Can be formatted text. */ public SnackbarBuilder message(@StringRes final int id) { message = activity.getResources().getString(id); return this; } /** * @param duration How long to display the message. */ public SnackbarBuilder duration(final int duration) { this.duration = duration; return this; } /** * @param action Action text to display. */ public SnackbarBuilder action(final String action) { this.action = action; return this; } /** * @param id The id of the string resource for the action text to display. */ public SnackbarBuilder action(@StringRes final int id) { action = activity.getResources().getString(id); return this; } /** * @param callback Callback to be invoked when the action is clicked or the snackbar is dismissed. */ public SnackbarBuilder callback(final SnackbarCallback callback) { this.callback = callback; return this; } /** * @param callback Callback to be invoked when the action is clicked or the snackbar is dismissed. */ public SnackbarBuilder callback(final EventCallback callback) { this.callback = new SnackbarEventCallback(callback); return this; } /** * @param icon Icon to be displayed with the snackbar text. */ public SnackbarBuilder icon(final Drawable icon) { this.icon = icon; return this; } /** * @param backgroundColor Snackbar background color. */ public SnackbarBuilder backgroundColor(final Integer backgroundColor) { this.backgroundColor = backgroundColor; return this; } /** * @param actionColor Action text color. */ public SnackbarBuilder actionColor(final Integer actionColor) { this.actionColor = actionColor; return this; } /** * @param object Populate the builder with data from a Gecko Snackbar:Show event. */ public SnackbarBuilder fromEvent(final NativeJSObject object) { message = object.getString("message"); duration = object.getInt("duration"); if (object.has("backgroundColor")) { final String providedColor = object.getString("backgroundColor"); try { backgroundColor = Color.parseColor(providedColor); } catch (IllegalArgumentException e) { Log.w(LOGTAG, "Failed to parse color string: " + providedColor); } } NativeJSObject actionObject = object.optObject("action", null); if (actionObject != null) { action = actionObject.optString("label", null); } return this; } public void buildAndShow() { final View parentView = findBestParentView(activity); final Snackbar snackbar = Snackbar.make(parentView, message, duration); if (callback != null && !TextUtils.isEmpty(action)) { snackbar.setAction(action, callback); if (actionColor == null) { snackbar.setActionTextColor(ContextCompat.getColor(activity, R.color.fennec_ui_orange)); } else { snackbar.setActionTextColor(actionColor); } snackbar.setCallback(callback); } if (icon != null) { int leftPadding = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 10, activity.getResources().getDisplayMetrics()); final InsetDrawable paddedIcon = new InsetDrawable(icon, 0, 0, leftPadding, 0); paddedIcon.setBounds(0, 0, leftPadding + icon.getIntrinsicWidth(), icon.getIntrinsicHeight()); TextView textView = (TextView) snackbar.getView().findViewById(android.support.design.R.id.snackbar_text); textView.setCompoundDrawables(paddedIcon, null, null, null); } if (backgroundColor != null) { snackbar.getView().setBackgroundColor(backgroundColor); } snackbar.show(); synchronized (currentSnackbarLock) { currentSnackbar = new WeakReference<>(snackbar); } } /** * Dismiss the currently visible snackbar. */ public static void dismissCurrentSnackbar() { synchronized (currentSnackbarLock) { final Snackbar snackbar = currentSnackbar.get(); if (snackbar != null && snackbar.isShown()) { snackbar.dismiss(); } } } /** * Find the best parent view to hold the Snackbar's view. The Snackbar implementation of the support * library will use this view to walk up the view tree to find an actual suitable parent (if needed). */ private static View findBestParentView(Activity activity) { if (activity instanceof GeckoApp) { final View view = activity.findViewById(R.id.root_layout); if (view != null) { return view; } } return activity.findViewById(android.R.id.content); } }