/** * Copyright (c) 2015-present, Facebook, Inc. * All rights reserved. * * This source code is licensed under the BSD-style license found in the * LICENSE file in the root directory of this source tree. An additional grant * of patent rights can be found in the PATENTS file in the same directory. */ package com.facebook.react.modules.dialog; import javax.annotation.Nullable; import java.util.Map; import android.app.Activity; import android.content.DialogInterface; import android.content.DialogInterface.OnClickListener; import android.content.DialogInterface.OnDismissListener; import android.os.Bundle; import android.support.v4.app.FragmentActivity; import com.facebook.common.logging.FLog; import com.facebook.react.bridge.Callback; import com.facebook.react.bridge.LifecycleEventListener; import com.facebook.react.bridge.ReactApplicationContext; import com.facebook.react.bridge.ReactContextBaseJavaModule; import com.facebook.react.bridge.ReactMethod; import com.facebook.react.bridge.ReadableArray; import com.facebook.react.bridge.ReadableMap; import com.facebook.react.common.MapBuilder; import com.facebook.react.module.annotations.ReactModule; @ReactModule(name = DialogModule.NAME) public class DialogModule extends ReactContextBaseJavaModule implements LifecycleEventListener { /* package */ static final String FRAGMENT_TAG = "com.facebook.catalyst.react.dialog.DialogModule"; /* package */ static final String NAME = "DialogManagerAndroid"; /* package */ static final String ACTION_BUTTON_CLICKED = "buttonClicked"; /* package */ static final String ACTION_DISMISSED = "dismissed"; /* package */ static final String KEY_TITLE = "title"; /* package */ static final String KEY_MESSAGE = "message"; /* package */ static final String KEY_BUTTON_POSITIVE = "buttonPositive"; /* package */ static final String KEY_BUTTON_NEGATIVE = "buttonNegative"; /* package */ static final String KEY_BUTTON_NEUTRAL = "buttonNeutral"; /* package */ static final String KEY_ITEMS = "items"; /* package */ static final String KEY_CANCELABLE = "cancelable"; /* package */ static final Map<String, Object> CONSTANTS = MapBuilder.<String, Object>of( ACTION_BUTTON_CLICKED, ACTION_BUTTON_CLICKED, ACTION_DISMISSED, ACTION_DISMISSED, KEY_BUTTON_POSITIVE, DialogInterface.BUTTON_POSITIVE, KEY_BUTTON_NEGATIVE, DialogInterface.BUTTON_NEGATIVE, KEY_BUTTON_NEUTRAL, DialogInterface.BUTTON_NEUTRAL); private boolean mIsInForeground; public DialogModule(ReactApplicationContext reactContext) { super(reactContext); } @Override public String getName() { return NAME; } /** * Helper to allow this module to work with both the standard FragmentManager * and the Support FragmentManager (for apps that need to use it for legacy reasons). * Since the two APIs don't share a common interface there's unfortunately some * code duplication. */ private class FragmentManagerHelper { // Exactly one of the two is null private final @Nullable android.app.FragmentManager mFragmentManager; private final @Nullable android.support.v4.app.FragmentManager mSupportFragmentManager; private @Nullable Object mFragmentToShow; private boolean isUsingSupportLibrary() { return mSupportFragmentManager != null; } public FragmentManagerHelper(android.support.v4.app.FragmentManager supportFragmentManager) { mFragmentManager = null; mSupportFragmentManager = supportFragmentManager; } public FragmentManagerHelper(android.app.FragmentManager fragmentManager) { mFragmentManager = fragmentManager; mSupportFragmentManager = null; } public void showPendingAlert() { if (mFragmentToShow == null) { return; } if (isUsingSupportLibrary()) { ((SupportAlertFragment) mFragmentToShow).show(mSupportFragmentManager, FRAGMENT_TAG); } else { ((AlertFragment) mFragmentToShow).show(mFragmentManager, FRAGMENT_TAG); } mFragmentToShow = null; } private void dismissExisting() { if (isUsingSupportLibrary()) { SupportAlertFragment oldFragment = (SupportAlertFragment) mSupportFragmentManager.findFragmentByTag(FRAGMENT_TAG); if (oldFragment != null) { oldFragment.dismiss(); } } else { AlertFragment oldFragment = (AlertFragment) mFragmentManager.findFragmentByTag(FRAGMENT_TAG); if (oldFragment != null) { oldFragment.dismiss(); } } } public void showNewAlert(boolean isInForeground, Bundle arguments, Callback actionCallback) { dismissExisting(); AlertFragmentListener actionListener = actionCallback != null ? new AlertFragmentListener(actionCallback) : null; if (isUsingSupportLibrary()) { SupportAlertFragment alertFragment = new SupportAlertFragment(actionListener, arguments); if (isInForeground) { if (arguments.containsKey(KEY_CANCELABLE)) { alertFragment.setCancelable(arguments.getBoolean(KEY_CANCELABLE)); } alertFragment.show(mSupportFragmentManager, FRAGMENT_TAG); } else { mFragmentToShow = alertFragment; } } else { AlertFragment alertFragment = new AlertFragment(actionListener, arguments); if (isInForeground) { if (arguments.containsKey(KEY_CANCELABLE)) { alertFragment.setCancelable(arguments.getBoolean(KEY_CANCELABLE)); } alertFragment.show(mFragmentManager, FRAGMENT_TAG); } else { mFragmentToShow = alertFragment; } } } } /* package */ class AlertFragmentListener implements OnClickListener, OnDismissListener { private final Callback mCallback; private boolean mCallbackConsumed = false; public AlertFragmentListener(Callback callback) { mCallback = callback; } @Override public void onClick(DialogInterface dialog, int which) { if (!mCallbackConsumed) { if (getReactApplicationContext().hasActiveCatalystInstance()) { mCallback.invoke(ACTION_BUTTON_CLICKED, which); mCallbackConsumed = true; } } } @Override public void onDismiss(DialogInterface dialog) { if (!mCallbackConsumed) { if (getReactApplicationContext().hasActiveCatalystInstance()) { mCallback.invoke(ACTION_DISMISSED); mCallbackConsumed = true; } } } } @Override public Map<String, Object> getConstants() { return CONSTANTS; } @Override public void initialize() { getReactApplicationContext().addLifecycleEventListener(this); } @Override public void onHostPause() { // Don't show the dialog if the host is paused. mIsInForeground = false; } @Override public void onHostDestroy() { } @Override public void onHostResume() { mIsInForeground = true; // Check if a dialog has been created while the host was paused, so that we can show it now. FragmentManagerHelper fragmentManagerHelper = getFragmentManagerHelper(); if (fragmentManagerHelper != null) { fragmentManagerHelper.showPendingAlert(); } else { FLog.w(DialogModule.class, "onHostResume called but no FragmentManager found"); } } @ReactMethod public void showAlert( ReadableMap options, Callback errorCallback, Callback actionCallback) { FragmentManagerHelper fragmentManagerHelper = getFragmentManagerHelper(); if (fragmentManagerHelper == null) { errorCallback.invoke("Tried to show an alert while not attached to an Activity"); return; } final Bundle args = new Bundle(); if (options.hasKey(KEY_TITLE)) { args.putString(AlertFragment.ARG_TITLE, options.getString(KEY_TITLE)); } if (options.hasKey(KEY_MESSAGE)) { args.putString(AlertFragment.ARG_MESSAGE, options.getString(KEY_MESSAGE)); } if (options.hasKey(KEY_BUTTON_POSITIVE)) { args.putString(AlertFragment.ARG_BUTTON_POSITIVE, options.getString(KEY_BUTTON_POSITIVE)); } if (options.hasKey(KEY_BUTTON_NEGATIVE)) { args.putString(AlertFragment.ARG_BUTTON_NEGATIVE, options.getString(KEY_BUTTON_NEGATIVE)); } if (options.hasKey(KEY_BUTTON_NEUTRAL)) { args.putString(AlertFragment.ARG_BUTTON_NEUTRAL, options.getString(KEY_BUTTON_NEUTRAL)); } if (options.hasKey(KEY_ITEMS)) { ReadableArray items = options.getArray(KEY_ITEMS); CharSequence[] itemsArray = new CharSequence[items.size()]; for (int i = 0; i < items.size(); i ++) { itemsArray[i] = items.getString(i); } args.putCharSequenceArray(AlertFragment.ARG_ITEMS, itemsArray); } if (options.hasKey(KEY_CANCELABLE)) { args.putBoolean(KEY_CANCELABLE, options.getBoolean(KEY_CANCELABLE)); } fragmentManagerHelper.showNewAlert(mIsInForeground, args, actionCallback); } /** * Creates a new helper to work with either the FragmentManager or the legacy support * FragmentManager transparently. Returns null if we're not attached to an Activity. * * DO NOT HOLD LONG-LIVED REFERENCES TO THE OBJECT RETURNED BY THIS METHOD, AS THIS WILL CAUSE * MEMORY LEAKS. */ private @Nullable FragmentManagerHelper getFragmentManagerHelper() { Activity activity = getCurrentActivity(); if (activity == null) { return null; } if (activity instanceof FragmentActivity) { return new FragmentManagerHelper(((FragmentActivity) activity).getSupportFragmentManager()); } else { return new FragmentManagerHelper(activity.getFragmentManager()); } } }