mirror of
https://github.com/facebook/react-native.git
synced 2025-11-01 09:14:26 +00:00
Convert com.facebook.react.devsupport.RedBoxDialogSurfaceDelegate to Kotlin (#49091)
Summary: Pull Request resolved: https://github.com/facebook/react-native/pull/49091 Just another Kotlin migration for the devsupport package. Changelog: [Internal] [Changed] - Reviewed By: Abbondanzo Differential Revision: D68954219 fbshipit-source-id: 4d82a8965207916acec4fa3779627b9e93bb8b10
This commit is contained in:
committed by
Facebook GitHub Bot
parent
95f9ca5b5b
commit
55b3839183
-188
@@ -1,188 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) Meta Platforms, Inc. and 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.graphics.Color;
|
||||
import android.graphics.drawable.ColorDrawable;
|
||||
import android.os.Bundle;
|
||||
import android.view.KeyEvent;
|
||||
import android.view.Window;
|
||||
import android.widget.FrameLayout;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.core.graphics.Insets;
|
||||
import androidx.core.view.ViewCompat;
|
||||
import androidx.core.view.WindowInsetsCompat;
|
||||
import com.facebook.common.logging.FLog;
|
||||
import com.facebook.react.R;
|
||||
import com.facebook.react.bridge.LifecycleEventListener;
|
||||
import com.facebook.react.bridge.ReactContext;
|
||||
import com.facebook.react.common.ReactConstants;
|
||||
import com.facebook.react.common.SurfaceDelegate;
|
||||
import com.facebook.react.devsupport.interfaces.DevSupportManager;
|
||||
import com.facebook.react.devsupport.interfaces.RedBoxHandler;
|
||||
import java.util.Objects;
|
||||
|
||||
/**
|
||||
* The implementation of SurfaceDelegate with {@link Activity}. This is the default SurfaceDelegate
|
||||
* for Mobile.
|
||||
*/
|
||||
class RedBoxDialogSurfaceDelegate implements SurfaceDelegate {
|
||||
|
||||
private final DoubleTapReloadRecognizer mDoubleTapReloadRecognizer;
|
||||
private final DevSupportManager mDevSupportManager;
|
||||
|
||||
private @Nullable Dialog mDialog;
|
||||
private @Nullable RedBoxContentView mRedBoxContentView;
|
||||
|
||||
public RedBoxDialogSurfaceDelegate(DevSupportManager devSupportManager) {
|
||||
mDevSupportManager = devSupportManager;
|
||||
mDoubleTapReloadRecognizer = new DoubleTapReloadRecognizer();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void createContentView(String appKey) {
|
||||
// The content view is created in android instead of using react app. Hence the appKey is not
|
||||
// used here.
|
||||
RedBoxHandler redBoxHandler = mDevSupportManager.getRedBoxHandler();
|
||||
Activity context = mDevSupportManager.getCurrentActivity();
|
||||
if (context == null || context.isFinishing()) {
|
||||
@Nullable String message = mDevSupportManager.getLastErrorTitle();
|
||||
FLog.e(
|
||||
ReactConstants.TAG,
|
||||
"Unable to launch redbox because react activity "
|
||||
+ "are not available, here is the error that redbox would've displayed: "
|
||||
+ (message != null ? message : "N/A"));
|
||||
return;
|
||||
}
|
||||
// Create a new RedBox when currentActivity get updated
|
||||
mRedBoxContentView = new RedBoxContentView(context, mDevSupportManager, redBoxHandler);
|
||||
mRedBoxContentView.init();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isContentViewReady() {
|
||||
return mRedBoxContentView != null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void destroyContentView() {
|
||||
mRedBoxContentView = null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void show() {
|
||||
@Nullable String message = mDevSupportManager.getLastErrorTitle();
|
||||
Activity context = mDevSupportManager.getCurrentActivity();
|
||||
if (context == null || context.isFinishing()) {
|
||||
final @Nullable ReactContext reactContext = mDevSupportManager.getCurrentReactContext();
|
||||
if (reactContext != null) {
|
||||
/**
|
||||
* If the activity isn't available, try again after the next onHostResume(). onHostResume()
|
||||
* is when the activity gets attached to the react native.
|
||||
*/
|
||||
runAfterHostResume(reactContext, this::show);
|
||||
return;
|
||||
}
|
||||
|
||||
FLog.e(
|
||||
ReactConstants.TAG,
|
||||
"Unable to launch redbox because react activity and react context "
|
||||
+ "is not available, here is the error that redbox would've displayed: "
|
||||
+ (message != null ? message : "N/A"));
|
||||
return;
|
||||
}
|
||||
|
||||
if (mRedBoxContentView == null || mRedBoxContentView.getContext() != context) {
|
||||
// Create a new RedBox when currentActivity get updated
|
||||
createContentView("RedBox");
|
||||
}
|
||||
|
||||
mRedBoxContentView.refreshContentView();
|
||||
if (mDialog == null) {
|
||||
mDialog =
|
||||
new Dialog(context, R.style.Theme_Catalyst_RedBox) {
|
||||
@Override
|
||||
public boolean onKeyUp(int keyCode, KeyEvent event) {
|
||||
if (keyCode == KeyEvent.KEYCODE_MENU) {
|
||||
mDevSupportManager.showDevOptionsDialog();
|
||||
return true;
|
||||
}
|
||||
if (mDoubleTapReloadRecognizer.didDoubleTapR(keyCode, getCurrentFocus())) {
|
||||
mDevSupportManager.handleReloadJS();
|
||||
}
|
||||
return super.onKeyUp(keyCode, event);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
Objects.requireNonNull(getWindow());
|
||||
// set background color so it will show below transparent system bars on forced
|
||||
// edge-to-edge
|
||||
getWindow().setBackgroundDrawable(new ColorDrawable(Color.BLACK));
|
||||
// register insets listener to update margins on the ReactRootView to avoid overlap w/
|
||||
// system bars
|
||||
int insetsType =
|
||||
WindowInsetsCompat.Type.systemBars() | WindowInsetsCompat.Type.displayCutout();
|
||||
|
||||
ViewCompat.setOnApplyWindowInsetsListener(
|
||||
mRedBoxContentView,
|
||||
(view, windowInsetsCompat) -> {
|
||||
Insets insets = windowInsetsCompat.getInsets(insetsType);
|
||||
|
||||
FrameLayout.LayoutParams lp = (FrameLayout.LayoutParams) view.getLayoutParams();
|
||||
lp.setMargins(insets.left, insets.top, insets.right, insets.bottom);
|
||||
|
||||
return WindowInsetsCompat.CONSUMED;
|
||||
});
|
||||
}
|
||||
};
|
||||
mDialog.requestWindowFeature(Window.FEATURE_NO_TITLE);
|
||||
mDialog.setContentView(mRedBoxContentView);
|
||||
}
|
||||
mDialog.show();
|
||||
}
|
||||
|
||||
private static void runAfterHostResume(ReactContext reactContext, Runnable runnable) {
|
||||
reactContext.addLifecycleEventListener(
|
||||
new LifecycleEventListener() {
|
||||
@Override
|
||||
public void onHostResume() {
|
||||
runnable.run();
|
||||
reactContext.removeLifecycleEventListener(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onHostPause() {}
|
||||
|
||||
@Override
|
||||
public void onHostDestroy() {}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void hide() {
|
||||
// dismiss redbox if exists
|
||||
if (mDialog != null) {
|
||||
try {
|
||||
mDialog.dismiss();
|
||||
} catch (IllegalArgumentException e) {
|
||||
FLog.e(
|
||||
ReactConstants.TAG, "RedBoxDialogSurfaceDelegate: error while dismissing dialog: ", e);
|
||||
}
|
||||
destroyContentView();
|
||||
mDialog = null;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isShowing() {
|
||||
return mDialog != null && mDialog.isShowing();
|
||||
}
|
||||
}
|
||||
+159
@@ -0,0 +1,159 @@
|
||||
/*
|
||||
* Copyright (c) Meta Platforms, Inc. and 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.graphics.Color
|
||||
import android.graphics.drawable.ColorDrawable
|
||||
import android.os.Bundle
|
||||
import android.view.KeyEvent
|
||||
import android.view.View
|
||||
import android.view.Window
|
||||
import android.widget.FrameLayout
|
||||
import androidx.core.graphics.Insets
|
||||
import androidx.core.view.ViewCompat
|
||||
import androidx.core.view.WindowInsetsCompat
|
||||
import com.facebook.common.logging.FLog
|
||||
import com.facebook.react.R
|
||||
import com.facebook.react.bridge.LifecycleEventListener
|
||||
import com.facebook.react.bridge.ReactContext
|
||||
import com.facebook.react.common.ReactConstants
|
||||
import com.facebook.react.common.SurfaceDelegate
|
||||
import com.facebook.react.devsupport.interfaces.DevSupportManager
|
||||
|
||||
/**
|
||||
* The implementation of SurfaceDelegate with [Activity]. This is the default [SurfaceDelegate] for
|
||||
* Mobile.
|
||||
*/
|
||||
internal class RedBoxDialogSurfaceDelegate(private val devSupportManager: DevSupportManager) :
|
||||
SurfaceDelegate {
|
||||
private val doubleTapReloadRecognizer = DoubleTapReloadRecognizer()
|
||||
|
||||
private var dialog: Dialog? = null
|
||||
private var redBoxContentView: RedBoxContentView? = null
|
||||
|
||||
override fun createContentView(appKey: String) {
|
||||
// The content view is created in android instead of using react app. Hence the appKey is not
|
||||
// used here.
|
||||
val redBoxHandler = devSupportManager.redBoxHandler
|
||||
val context = devSupportManager.currentActivity
|
||||
if (context == null || context.isFinishing) {
|
||||
val message = devSupportManager.lastErrorTitle
|
||||
FLog.e(
|
||||
ReactConstants.TAG,
|
||||
"Unable to launch redbox because react activity is not available, here is the error that redbox would've displayed: ${(message ?: "N/A")}")
|
||||
return
|
||||
}
|
||||
|
||||
// Create a new RedBox when currentActivity get updated
|
||||
redBoxContentView =
|
||||
RedBoxContentView(context, devSupportManager, redBoxHandler).also { it.init() }
|
||||
}
|
||||
|
||||
override fun isContentViewReady(): Boolean = redBoxContentView != null
|
||||
|
||||
override fun destroyContentView() {
|
||||
redBoxContentView = null
|
||||
}
|
||||
|
||||
override fun show() {
|
||||
val message: String? = devSupportManager.lastErrorTitle
|
||||
val context: Activity? = devSupportManager.currentActivity
|
||||
if (context == null || context.isFinishing) {
|
||||
devSupportManager.currentReactContext?.let { reactContext ->
|
||||
/**
|
||||
* If the activity isn't available, try again after the next onHostResume(). onHostResume()
|
||||
* is when the activity gets attached to the react native.
|
||||
*/
|
||||
runAfterHostResume(reactContext) { this.show() }
|
||||
return
|
||||
}
|
||||
FLog.e(
|
||||
ReactConstants.TAG,
|
||||
"Unable to launch redbox because react activity and react context is not available, here is the error that redbox would've displayed: ${message ?: "N/A"}")
|
||||
return
|
||||
}
|
||||
|
||||
if (redBoxContentView?.context !== context) {
|
||||
// Create a new RedBox when currentActivity get updated
|
||||
createContentView("RedBox")
|
||||
}
|
||||
|
||||
redBoxContentView?.refreshContentView()
|
||||
if (dialog == null) {
|
||||
dialog =
|
||||
object : Dialog(context, R.style.Theme_Catalyst_RedBox) {
|
||||
override fun onKeyUp(keyCode: Int, event: KeyEvent): Boolean {
|
||||
if (keyCode == KeyEvent.KEYCODE_MENU) {
|
||||
devSupportManager.showDevOptionsDialog()
|
||||
return true
|
||||
}
|
||||
if (doubleTapReloadRecognizer.didDoubleTapR(keyCode, currentFocus)) {
|
||||
devSupportManager.handleReloadJS()
|
||||
}
|
||||
return super.onKeyUp(keyCode, event)
|
||||
}
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
// set background color so it will show below transparent system bars on forced
|
||||
// edge-to-edge
|
||||
checkNotNull(window).setBackgroundDrawable(ColorDrawable(Color.BLACK))
|
||||
// register insets listener to update margins on the ReactRootView to avoid
|
||||
// overlap w/ system bars
|
||||
val insetsType: Int =
|
||||
WindowInsetsCompat.Type.systemBars() or
|
||||
WindowInsetsCompat.Type.displayCutout()
|
||||
|
||||
ViewCompat.setOnApplyWindowInsetsListener(checkNotNull(redBoxContentView)) {
|
||||
view: View,
|
||||
windowInsetsCompat: WindowInsetsCompat ->
|
||||
val insets: Insets = windowInsetsCompat.getInsets(insetsType)
|
||||
val lp: FrameLayout.LayoutParams = view.layoutParams as FrameLayout.LayoutParams
|
||||
lp.setMargins(insets.left, insets.top, insets.right, insets.bottom)
|
||||
WindowInsetsCompat.CONSUMED
|
||||
}
|
||||
}
|
||||
}
|
||||
.apply {
|
||||
requestWindowFeature(Window.FEATURE_NO_TITLE)
|
||||
setContentView(checkNotNull(redBoxContentView))
|
||||
}
|
||||
}
|
||||
dialog?.show()
|
||||
}
|
||||
|
||||
override fun hide() {
|
||||
try {
|
||||
// dismiss redbox if exists
|
||||
dialog?.dismiss()
|
||||
} catch (e: IllegalArgumentException) {
|
||||
FLog.e(ReactConstants.TAG, "RedBoxDialogSurfaceDelegate: error while dismissing dialog: ", e)
|
||||
}
|
||||
destroyContentView()
|
||||
dialog = null
|
||||
}
|
||||
|
||||
override fun isShowing(): Boolean = dialog?.isShowing == true
|
||||
|
||||
companion object {
|
||||
private fun runAfterHostResume(reactContext: ReactContext, runnable: Runnable) {
|
||||
reactContext.addLifecycleEventListener(
|
||||
object : LifecycleEventListener {
|
||||
override fun onHostResume() {
|
||||
runnable.run()
|
||||
reactContext.removeLifecycleEventListener(this)
|
||||
}
|
||||
|
||||
override fun onHostPause() = Unit
|
||||
|
||||
override fun onHostDestroy() = Unit
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user