diff --git a/android/src/main/java/com/swmansion/rnscreens/legacy/bottomsheet/SheetUtils.kt b/android/src/main/java/com/swmansion/rnscreens/legacy/bottomsheet/SheetUtils.kt index d15849c329..fe771b8821 100644 --- a/android/src/main/java/com/swmansion/rnscreens/legacy/bottomsheet/SheetUtils.kt +++ b/android/src/main/java/com/swmansion/rnscreens/legacy/bottomsheet/SheetUtils.kt @@ -1,5 +1,6 @@ package com.swmansion.rnscreens.legacy.bottomsheet +import android.util.Log import android.view.View import com.google.android.material.bottomsheet.BottomSheetBehavior import com.google.android.material.bottomsheet.BottomSheetBehavior.STATE_COLLAPSED @@ -9,6 +10,8 @@ import com.google.android.material.bottomsheet.BottomSheetBehavior.STATE_HIDDEN import com.swmansion.rnscreens.legacy.Screen import com.swmansion.rnscreens.legacy.ext.asScreenStackFragment +private const val TAG = "SheetUtils" + object SheetUtils { /** * Verifies whether BottomSheetBehavior.State is one of stable states. As unstable states @@ -75,7 +78,8 @@ object SheetUtils { * @param state state of the bottom sheet * @param detentCount length of array with detents fractions * - * @throws IllegalArgumentException for invalid state / detentCount combinations + * A stable state that the current detents array cannot express is clamped to the top-most + * detent rather than throwing, see [unmappedDetentIndex]. */ fun detentIndexFromSheetState( @BottomSheetBehavior.State state: Int, @@ -86,7 +90,7 @@ object SheetUtils { when (state) { STATE_HIDDEN -> -1 STATE_EXPANDED -> 0 - else -> throw IllegalArgumentException("[RNScreens] Invalid state $state for detentCount $detentCount") + else -> unmappedDetentIndex(state, detentCount) } 2 -> @@ -94,7 +98,7 @@ object SheetUtils { STATE_HIDDEN -> -1 STATE_COLLAPSED -> 0 STATE_EXPANDED -> 1 - else -> throw IllegalArgumentException("[RNScreens] Invalid state $state for detentCount $detentCount") + else -> unmappedDetentIndex(state, detentCount) } 3 -> @@ -103,12 +107,34 @@ object SheetUtils { STATE_COLLAPSED -> 0 STATE_HALF_EXPANDED -> 1 STATE_EXPANDED -> 2 - else -> throw IllegalArgumentException("[RNScreens] Invalid state $state for detentCount $detentCount") + else -> unmappedDetentIndex(state, detentCount) } - else -> throw IllegalArgumentException("[RNScreens] Invalid state $state for detentCount $detentCount") + else -> unmappedDetentIndex(state, detentCount) } + /** + * Maps a stable [BottomSheetBehavior.State] that the current detents array cannot express. + * + * This is reachable through ordinary prop updates: when `sheetAllowedDetents` shrinks while the + * sheet rests in a detent that no longer exists, Material keeps reporting the old state until + * the sheet is re-settled. Going from `[0.5, 1]` to `[1]` while collapsed, for example, delivers + * STATE_COLLAPSED with a detentCount of 1. + * + * Throwing from a state change the user cannot avoid takes down the whole app, so clamp to the + * top-most valid detent instead. The rewritten form sheet already treats an unmapped state as a + * non-fatal case (`FORM_SHEET_UNKNOWN_DETENT_INDEX`); -1 cannot be reused here because this + * mapping already spends it on STATE_HIDDEN, and reporting the sheet as dismissed would be + * worse than reporting the nearest detent. + */ + private fun unmappedDetentIndex( + state: Int, + detentCount: Int, + ): Int { + Log.w(TAG, "[RNScreens] State $state has no detent in an array of $detentCount, clamping to the top detent") + return (detentCount - 1).coerceAtLeast(0) + } + fun isStateLessEqualThan( state: Int, otherState: Int,