Skip to content

Commit

Permalink
Modal bugfix for statusBarTranslucent prop and Android 15 (#46359)
Browse files Browse the repository at this point in the history
Summary:
Pull Request resolved: #46359

Remove unneeded code around size calculation and old arch support
- updateState was getting called unnecessarily in multiple places --> only call from onSizeChanged()
    - this is a reliable source for getting the content size area of the dialog used for Modal
     - remove code checking duplicated update
- Old architecture cleanup
    - Remove Java implementation of ShadowNode
      - we already have logic to set the node size via UIManagerModule::updateNodeSize(). This  code is now group together in updateState() for both new and old architecture

This fixes issues with resulting from wrong size calculation:
- having gaps at bottom when we set `statusBarTranslucent` to `true`
- Modal cut off at bottom on Android 15 (drawn under bottom nav bar)

Changelog:
[Android][Fixed] - Modal statusBarTranslucent bug, Modal at bottom being cut off in Android 15 (without forced edge-to-edge)
[Android][Deprecation] - Deprecating ModalHostShadowNode

Reviewed By: mdvacca

Differential Revision: D62286026
  • Loading branch information
alanleedev authored and facebook-github-bot committed Sep 7, 2024
1 parent 521358c commit 59caa3d
Show file tree
Hide file tree
Showing 5 changed files with 19 additions and 153 deletions.
5 changes: 0 additions & 5 deletions packages/react-native/ReactAndroid/api/ReactAndroid.api
Original file line number Diff line number Diff line change
Expand Up @@ -6776,13 +6776,10 @@ public final class com/facebook/react/views/modal/ReactModalHostManager : com/fa
public static final field REACT_CLASS Ljava/lang/String;
public fun <init> ()V
public synthetic fun addEventEmitters (Lcom/facebook/react/uimanager/ThemedReactContext;Landroid/view/View;)V
public fun createShadowNodeInstance ()Lcom/facebook/react/uimanager/LayoutShadowNode;
public synthetic fun createShadowNodeInstance ()Lcom/facebook/react/uimanager/ReactShadowNode;
public synthetic fun createViewInstance (Lcom/facebook/react/uimanager/ThemedReactContext;)Landroid/view/View;
public fun getDelegate ()Lcom/facebook/react/uimanager/ViewManagerDelegate;
public fun getExportedCustomDirectEventTypeConstants ()Ljava/util/Map;
public fun getName ()Ljava/lang/String;
public fun getShadowNodeClass ()Ljava/lang/Class;
public synthetic fun onAfterUpdateTransaction (Landroid/view/View;)V
public synthetic fun onDropViewInstance (Landroid/view/View;)V
public fun onDropViewInstance (Lcom/facebook/react/views/modal/ReactModalHostView;)V
Expand Down Expand Up @@ -6850,11 +6847,9 @@ public final class com/facebook/react/views/modal/ReactModalHostView : android/v
public final fun setStatusBarTranslucent (Z)V
public final fun setTransparent (Z)V
public final fun showOrUpdate ()V
public final fun updateState (II)V
}

public final class com/facebook/react/views/modal/ReactModalHostView$DialogRootViewGroup : com/facebook/react/views/view/ReactViewGroup, com/facebook/react/uimanager/RootView {
public fun addView (Landroid/view/View;ILandroid/view/ViewGroup$LayoutParams;)V
public fun handleException (Ljava/lang/Throwable;)V
public fun onChildEndedNativeGesture (Landroid/view/View;Landroid/view/MotionEvent;)V
public fun onChildStartedNativeGesture (Landroid/view/View;Landroid/view/MotionEvent;)V
Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@ package com.facebook.react.views.modal

import com.facebook.react.uimanager.LayoutShadowNode
import com.facebook.react.uimanager.ReactShadowNodeImpl
import com.facebook.react.views.modal.ModalHostHelper.getModalHostSize

/**
* We implement the Modal by using an Android Dialog. That will fill the entire window of the
Expand All @@ -19,6 +18,7 @@ import com.facebook.react.views.modal.ModalHostHelper.getModalHostSize
* to be the window size. This will then cause the children of the Modal to layout as if they can
* fill the window.
*/
@Deprecated("ModalHostShadowNode is no longer used")
internal class ModalHostShadowNode : LayoutShadowNode() {
/**
* We need to set the styleWidth and styleHeight of the one child (represented by the
Expand All @@ -27,8 +27,5 @@ internal class ModalHostShadowNode : LayoutShadowNode() {
*/
override fun addChildAt(child: ReactShadowNodeImpl, i: Int) {
super.addChildAt(child, i)
val modalSize = getModalHostSize(themedContext)
child.setStyleWidth(modalSize.x.toFloat())
child.setStyleHeight(modalSize.y.toFloat())
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@ package com.facebook.react.views.modal
import android.content.DialogInterface.OnShowListener
import com.facebook.react.bridge.ReadableArray
import com.facebook.react.module.annotations.ReactModule
import com.facebook.react.uimanager.LayoutShadowNode
import com.facebook.react.uimanager.ReactStylesDiffMap
import com.facebook.react.uimanager.StateWrapper
import com.facebook.react.uimanager.ThemedReactContext
Expand All @@ -20,7 +19,6 @@ import com.facebook.react.uimanager.ViewManagerDelegate
import com.facebook.react.uimanager.annotations.ReactProp
import com.facebook.react.viewmanagers.ModalHostViewManagerDelegate
import com.facebook.react.viewmanagers.ModalHostViewManagerInterface
import com.facebook.react.views.modal.ModalHostHelper.getModalHostSize
import com.facebook.react.views.modal.ReactModalHostView.OnRequestCloseListener

/** View manager for [ReactModalHostView] components. */
Expand All @@ -34,11 +32,6 @@ public class ReactModalHostManager :
protected override fun createViewInstance(reactContext: ThemedReactContext): ReactModalHostView =
ReactModalHostView(reactContext)

public override fun createShadowNodeInstance(): LayoutShadowNode = ModalHostShadowNode()

public override fun getShadowNodeClass(): Class<out LayoutShadowNode> =
ModalHostShadowNode::class.java

public override fun onDropViewInstance(view: ReactModalHostView) {
super.onDropViewInstance(view)
view.onDropInstance()
Expand Down Expand Up @@ -128,8 +121,6 @@ public class ReactModalHostManager :
stateWrapper: StateWrapper
): Any? {
view.stateWrapper = stateWrapper
val modalSize = getModalHostSize(view.context)
view.updateState(modalSize.x, modalSize.y)
return null
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,6 @@ import com.facebook.react.R
import com.facebook.react.bridge.GuardedRunnable
import com.facebook.react.bridge.LifecycleEventListener
import com.facebook.react.bridge.ReactContext
import com.facebook.react.bridge.ReadableMap
import com.facebook.react.bridge.UiThreadUtil
import com.facebook.react.bridge.WritableMap
import com.facebook.react.bridge.WritableNativeMap
Expand All @@ -45,9 +44,9 @@ import com.facebook.react.uimanager.ThemedReactContext
import com.facebook.react.uimanager.UIManagerModule
import com.facebook.react.uimanager.events.EventDispatcher
import com.facebook.react.views.common.ContextUtils
import com.facebook.react.views.view.setStatusBarTranslucency
import com.facebook.react.views.view.ReactViewGroup
import java.util.Objects
import kotlin.math.abs

/**
* ReactModalHostView is a view that sits in the view hierarchy representing a Modal view.
Expand Down Expand Up @@ -295,16 +294,7 @@ public class ReactModalHostView(context: ThemedReactContext) :
* changed. This has the pleasant side-effect of us not having to preface all Modals with "top:
* statusBarHeight", since that margin will be included in the FrameLayout.
*/
get() {
val frameLayout = FrameLayout(context)
frameLayout.addView(dialogRootViewGroup)
if (statusBarTranslucent) {
frameLayout.systemUiVisibility = SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
} else {
frameLayout.fitsSystemWindows = true
}
return frameLayout
}
get() = FrameLayout(context).apply { addView(dialogRootViewGroup) }

/**
* updateProperties will update the properties that do not require us to recreate the dialog
Expand All @@ -331,6 +321,8 @@ public class ReactModalHostView(context: ThemedReactContext) :
}
}

dialogWindow.setStatusBarTranslucency(statusBarTranslucent)

if (transparent) {
dialogWindow.clearFlags(WindowManager.LayoutParams.FLAG_DIM_BEHIND)
} else {
Expand Down Expand Up @@ -361,10 +353,6 @@ public class ReactModalHostView(context: ThemedReactContext) :
}
}

public fun updateState(width: Int, height: Int) {
dialogRootViewGroup.updateState(width, height)
}

// This listener is called when the user presses KeyEvent.KEYCODE_BACK
// An event is then passed to JS which can either close or not close the Modal by setting the
// visible property
Expand Down Expand Up @@ -392,7 +380,6 @@ public class ReactModalHostView(context: ThemedReactContext) :
internal var stateWrapper: StateWrapper? = null
internal var eventDispatcher: EventDispatcher? = null

private var hasAdjustedSize = false
private var viewWidth = 0
private var viewHeight = 0
private val jSTouchDispatcher: JSTouchDispatcher = JSTouchDispatcher(this)
Expand All @@ -411,75 +398,33 @@ public class ReactModalHostView(context: ThemedReactContext) :
super.onSizeChanged(w, h, oldw, oldh)
viewWidth = w
viewHeight = h
updateFirstChildView()
}

private fun updateFirstChildView() {
if (childCount > 0) {
hasAdjustedSize = false
val viewTag: Int = getChildAt(0).id
if (stateWrapper != null) {
// This will only be called under Fabric
updateState(viewWidth, viewHeight)
} else {
// TODO: T44725185 remove after full migration to Fabric
val reactContext: ReactContext = reactContext
reactContext.runOnNativeModulesQueueThread(
object : GuardedRunnable(reactContext) {
override fun runGuarded() {
this@DialogRootViewGroup.reactContext.reactApplicationContext
.getNativeModule(UIManagerModule::class.java)
?.updateNodeSize(viewTag, viewWidth, viewHeight)
}
})
}
} else {
hasAdjustedSize = true
}
updateState(viewWidth, viewHeight)
}

@UiThread
public fun updateState(width: Int, height: Int) {
val realWidth: Float = width.toFloat().pxToDp()
val realHeight: Float = height.toFloat().pxToDp()

// Check incoming state values. If they're already the correct value, return early to prevent
// infinite UpdateState/SetState loop.
val currentState: ReadableMap? = stateWrapper?.stateData
if (currentState != null) {
val delta = 0.9f
val stateScreenHeight =
if (currentState.hasKey("screenHeight")) {
currentState.getDouble("screenHeight").toFloat()
} else {
0f
}
val stateScreenWidth =
if (currentState.hasKey("screenWidth")) {
currentState.getDouble("screenWidth").toFloat()
} else {
0f
}

if (abs((stateScreenWidth - realWidth).toDouble()) < delta &&
abs((stateScreenHeight - realHeight).toDouble()) < delta) {
return
}
}

stateWrapper?.let { sw ->
// new architecture
val newStateData: WritableMap = WritableNativeMap()
newStateData.putDouble("screenWidth", realWidth.toDouble())
newStateData.putDouble("screenHeight", realHeight.toDouble())
sw.updateState(newStateData)
}
}

override fun addView(child: View, index: Int, params: LayoutParams) {
super.addView(child, index, params)
if (hasAdjustedSize) {
updateFirstChildView()
}
} ?: run {
// old architecture
// TODO: T44725185 remove after full migration to Fabric
reactContext.runOnNativeModulesQueueThread(
object : GuardedRunnable(reactContext) {
override fun runGuarded() {
reactContext.reactApplicationContext
.getNativeModule(UIManagerModule::class.java)
?.updateNodeSize(id, viewWidth, viewHeight)
}
})
}
}

override fun handleException(t: Throwable) {
Expand Down

0 comments on commit 59caa3d

Please sign in to comment.