diff --git a/ReactAndroid/src/main/java/com/facebook/react/CoreModulesPackage.java b/ReactAndroid/src/main/java/com/facebook/react/CoreModulesPackage.java index 6dd813b6f62..a17763207b5 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/CoreModulesPackage.java +++ b/ReactAndroid/src/main/java/com/facebook/react/CoreModulesPackage.java @@ -16,6 +16,7 @@ import androidx.annotation.Nullable; import com.facebook.react.bridge.NativeModule; import com.facebook.react.bridge.ReactApplicationContext; import com.facebook.react.bridge.ReactMarker; +import com.facebook.react.devsupport.LogBoxModule; import com.facebook.react.module.annotations.ReactModule; import com.facebook.react.module.annotations.ReactModuleList; import com.facebook.react.module.model.ReactModuleInfo; @@ -49,6 +50,7 @@ import java.util.Map; DeviceInfoModule.class, DevSettingsModule.class, ExceptionsManagerModule.class, + LogBoxModule.class, HeadlessJsTaskSupportModule.class, SourceCodeModule.class, TimingModule.class, @@ -94,6 +96,7 @@ import java.util.Map; DeviceInfoModule.class, DevSettingsModule.class, ExceptionsManagerModule.class, + LogBoxModule.class, HeadlessJsTaskSupportModule.class, SourceCodeModule.class, TimingModule.class, @@ -142,6 +145,8 @@ import java.util.Map; return new DevSettingsModule(reactContext, mReactInstanceManager.getDevSupportManager()); case ExceptionsManagerModule.NAME: return new ExceptionsManagerModule(mReactInstanceManager.getDevSupportManager()); + case LogBoxModule.NAME: + return new LogBoxModule(reactContext, mReactInstanceManager.getDevSupportManager()); case HeadlessJsTaskSupportModule.NAME: return new HeadlessJsTaskSupportModule(reactContext); case SourceCodeModule.NAME: diff --git a/ReactAndroid/src/main/java/com/facebook/react/ReactInstanceManager.java b/ReactAndroid/src/main/java/com/facebook/react/ReactInstanceManager.java index 8d56f3418f5..2d79c4d3fb2 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/ReactInstanceManager.java +++ b/ReactAndroid/src/main/java/com/facebook/react/ReactInstanceManager.java @@ -304,6 +304,27 @@ public class ReactInstanceManager { public JavaScriptExecutorFactory getJavaScriptExecutorFactory() { return ReactInstanceManager.this.getJSExecutorFactory(); } + + @Override + public @Nullable View createRootView(String appKey) { + Activity currentActivity = getCurrentActivity(); + if (currentActivity != null) { + ReactRootView rootView = new ReactRootView(currentActivity); + + rootView.startReactApplication(ReactInstanceManager.this, appKey, null); + + return rootView; + } + + return null; + } + + @Override + public void destroyRootView(View rootView) { + if (rootView instanceof ReactRootView) { + ((ReactRootView) rootView).unmountReactApplication(); + } + } }; } diff --git a/ReactAndroid/src/main/java/com/facebook/react/devsupport/DevSupportManagerImpl.java b/ReactAndroid/src/main/java/com/facebook/react/devsupport/DevSupportManagerImpl.java index 2e3f651fb16..5dd361f1d83 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/devsupport/DevSupportManagerImpl.java +++ b/ReactAndroid/src/main/java/com/facebook/react/devsupport/DevSupportManagerImpl.java @@ -19,6 +19,7 @@ import android.content.pm.PackageInfo; import android.content.pm.PackageManager; import android.hardware.SensorManager; import android.util.Pair; +import android.view.View; import android.widget.EditText; import android.widget.Toast; import androidx.annotation.Nullable; @@ -377,6 +378,14 @@ public class DevSupportManagerImpl } } + public @Nullable View createRootView(String appKey) { + return mReactInstanceManagerHelper.createRootView(appKey); + } + + public void destroyRootView(View rootView) { + mReactInstanceManagerHelper.destroyRootView(rootView); + } + private void hideDevOptionsDialog() { if (mDevOptionsDialog != null) { mDevOptionsDialog.dismiss(); diff --git a/ReactAndroid/src/main/java/com/facebook/react/devsupport/DisabledDevSupportManager.java b/ReactAndroid/src/main/java/com/facebook/react/devsupport/DisabledDevSupportManager.java index 4f510fe1889..267a455e6eb 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/devsupport/DisabledDevSupportManager.java +++ b/ReactAndroid/src/main/java/com/facebook/react/devsupport/DisabledDevSupportManager.java @@ -7,6 +7,7 @@ package com.facebook.react.devsupport; +import android.view.View; import androidx.annotation.Nullable; import com.facebook.react.bridge.DefaultNativeModuleCallExceptionHandler; import com.facebook.react.bridge.ReactContext; @@ -40,6 +41,14 @@ public class DisabledDevSupportManager implements DevSupportManager { @Override public void showNewJSError(String message, ReadableArray details, int errorCookie) {} + @Override + public @Nullable View createRootView(String appKey) { + return null; + } + + @Override + public void destroyRootView(View rootView) {} + @Override public void updateJSError(String message, ReadableArray details, int errorCookie) {} diff --git a/ReactAndroid/src/main/java/com/facebook/react/devsupport/LogBoxDialog.java b/ReactAndroid/src/main/java/com/facebook/react/devsupport/LogBoxDialog.java new file mode 100644 index 00000000000..c6a5b80e8dd --- /dev/null +++ b/ReactAndroid/src/main/java/com/facebook/react/devsupport/LogBoxDialog.java @@ -0,0 +1,24 @@ +/* + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +package com.facebook.react.devsupport; + +import android.app.Activity; +import android.app.Dialog; +import android.view.View; +import android.view.Window; +import com.facebook.react.R; + +/** Dialog for displaying JS errors in LogBox. */ +public class LogBoxDialog extends Dialog { + public LogBoxDialog(Activity context, View reactRootView) { + super(context, R.style.Theme_Catalyst_LogBox); + + requestWindowFeature(Window.FEATURE_NO_TITLE); + setContentView(reactRootView); + } +} diff --git a/ReactAndroid/src/main/java/com/facebook/react/devsupport/LogBoxModule.java b/ReactAndroid/src/main/java/com/facebook/react/devsupport/LogBoxModule.java new file mode 100644 index 00000000000..05e70b069d3 --- /dev/null +++ b/ReactAndroid/src/main/java/com/facebook/react/devsupport/LogBoxModule.java @@ -0,0 +1,110 @@ +/* + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +package com.facebook.react.devsupport; + +import android.app.Activity; +import android.view.View; +import android.view.ViewGroup; +import androidx.annotation.Nullable; +import com.facebook.common.logging.FLog; +import com.facebook.react.bridge.ReactApplicationContext; +import com.facebook.react.bridge.ReactContextBaseJavaModule; +import com.facebook.react.bridge.ReactMethod; +import com.facebook.react.bridge.UiThreadUtil; +import com.facebook.react.common.ReactConstants; +import com.facebook.react.devsupport.interfaces.DevSupportManager; +import com.facebook.react.module.annotations.ReactModule; + +@ReactModule(name = LogBoxModule.NAME) +public class LogBoxModule extends ReactContextBaseJavaModule { + + public static final String NAME = "LogBox"; + + private final DevSupportManager mDevSupportManager; + private @Nullable View mReactRootView; + private @Nullable LogBoxDialog mLogBoxDialog; + + public LogBoxModule(ReactApplicationContext reactContext, DevSupportManager devSupportManager) { + super(reactContext); + + mDevSupportManager = devSupportManager; + UiThreadUtil.runOnUiThread( + new Runnable() { + @Override + public void run() { + if (mReactRootView == null) { + mReactRootView = mDevSupportManager.createRootView("LogBox"); + if (mReactRootView == null) { + FLog.e( + ReactConstants.TAG, + "Unable to launch logbox because react was unable to create the root view"); + } + } + } + }); + } + + @Override + public String getName() { + return NAME; + } + + @ReactMethod + public void show() { + UiThreadUtil.runOnUiThread( + new Runnable() { + @Override + public void run() { + if (mLogBoxDialog == null) { + Activity context = getCurrentActivity(); + if (context == null || context.isFinishing()) { + FLog.e( + ReactConstants.TAG, + "Unable to launch logbox because react activity " + + "is not available, here is the error that logbox would've displayed: "); + return; + } + mLogBoxDialog = new LogBoxDialog(context, mReactRootView); + mLogBoxDialog.setCancelable(false); + mLogBoxDialog.show(); + } + } + }); + } + + @ReactMethod + public void hide() { + UiThreadUtil.runOnUiThread( + new Runnable() { + @Override + public void run() { + if (mLogBoxDialog != null) { + if (mReactRootView.getParent() != null) { + ((ViewGroup) mReactRootView.getParent()).removeView(mReactRootView); + } + mLogBoxDialog.dismiss(); + mLogBoxDialog = null; + } + } + }); + } + + @Override + public void onCatalystInstanceDestroy() { + UiThreadUtil.runOnUiThread( + new Runnable() { + @Override + public void run() { + if (mReactRootView != null) { + mDevSupportManager.destroyRootView(mReactRootView); + mReactRootView = null; + } + } + }); + } +} diff --git a/ReactAndroid/src/main/java/com/facebook/react/devsupport/ReactInstanceManagerDevHelper.java b/ReactAndroid/src/main/java/com/facebook/react/devsupport/ReactInstanceManagerDevHelper.java index 60d45b1ba5b..02483586b34 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/devsupport/ReactInstanceManagerDevHelper.java +++ b/ReactAndroid/src/main/java/com/facebook/react/devsupport/ReactInstanceManagerDevHelper.java @@ -8,6 +8,7 @@ package com.facebook.react.devsupport; import android.app.Activity; +import android.view.View; import androidx.annotation.Nullable; import com.facebook.react.bridge.JavaJSExecutor; import com.facebook.react.bridge.JavaScriptExecutorFactory; @@ -32,4 +33,9 @@ public interface ReactInstanceManagerDevHelper { Activity getCurrentActivity(); JavaScriptExecutorFactory getJavaScriptExecutorFactory(); + + @Nullable + View createRootView(String appKey); + + void destroyRootView(View rootView); } diff --git a/ReactAndroid/src/main/java/com/facebook/react/devsupport/interfaces/DevSupportManager.java b/ReactAndroid/src/main/java/com/facebook/react/devsupport/interfaces/DevSupportManager.java index ac04bc6a6dd..43a477e3fad 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/devsupport/interfaces/DevSupportManager.java +++ b/ReactAndroid/src/main/java/com/facebook/react/devsupport/interfaces/DevSupportManager.java @@ -7,6 +7,7 @@ package com.facebook.react.devsupport.interfaces; +import android.view.View; import androidx.annotation.Nullable; import com.facebook.react.bridge.NativeModuleCallExceptionHandler; import com.facebook.react.bridge.ReactContext; @@ -25,6 +26,11 @@ public interface DevSupportManager extends NativeModuleCallExceptionHandler { void addCustomDevOption(String optionName, DevOptionHandler optionHandler); + @Nullable + View createRootView(String appKey); + + void destroyRootView(View rootView); + void showNewJSError(String message, ReadableArray details, int errorCookie); void updateJSError(final String message, final ReadableArray details, final int errorCookie); diff --git a/ReactAndroid/src/main/res/devsupport/values/colors.xml b/ReactAndroid/src/main/res/devsupport/values/colors.xml index d15ee8caf7b..8ac1a132df0 100644 --- a/ReactAndroid/src/main/res/devsupport/values/colors.xml +++ b/ReactAndroid/src/main/res/devsupport/values/colors.xml @@ -1,4 +1,5 @@ #eecc0000 + #ffffffff diff --git a/ReactAndroid/src/main/res/devsupport/values/styles.xml b/ReactAndroid/src/main/res/devsupport/values/styles.xml index e9f462c5359..cb88e438c91 100644 --- a/ReactAndroid/src/main/res/devsupport/values/styles.xml +++ b/ReactAndroid/src/main/res/devsupport/values/styles.xml @@ -9,10 +9,22 @@ @android:anim/fade_out @android:color/white + +