/**
* 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());
}
}
}