Skip to content

Commit

Permalink
refactor(Android): cleanup sheet behavior configuration (#2420)
Browse files Browse the repository at this point in the history
## Description

I plant to extract most of the form sheet API related code out of
`ScreenStackFragment`,
so that it is not polluted that much and more readable. Having
experienced the #2045, where
big changes were rolled out at once, now I want to do it in couple of
separate PRs.

This one simplifies sheet behaviour configuration logic in
ScreenStackFragment by implementing
series of helper extension functions on BottomSheetBehavior.


## Test code and steps to reproduce

Test1649

## Checklist

- [ ] Included code example that can be used to test this change
- [ ] Updated TS types
- [ ] Updated documentation: <!-- For adding new props to native-stack
-->
- [ ]
https://github.com/software-mansion/react-native-screens/blob/main/guides/GUIDE_FOR_LIBRARY_AUTHORS.md
- [ ]
https://github.com/software-mansion/react-native-screens/blob/main/native-stack/README.md
- [ ]
https://github.com/software-mansion/react-native-screens/blob/main/src/types.tsx
- [ ]
https://github.com/software-mansion/react-native-screens/blob/main/src/native-stack/types.tsx
- [ ] Ensured that CI passes
  • Loading branch information
kkafar authored Oct 21, 2024
1 parent b331ad8 commit 273194f
Show file tree
Hide file tree
Showing 3 changed files with 103 additions and 71 deletions.
124 changes: 53 additions & 71 deletions android/src/main/java/com/swmansion/rnscreens/ScreenStackFragment.kt
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,11 @@ import com.google.android.material.shape.MaterialShapeDrawable
import com.google.android.material.shape.ShapeAppearanceModel
import com.swmansion.rnscreens.bottomsheet.DimmingFragment
import com.swmansion.rnscreens.bottomsheet.SheetUtils
import com.swmansion.rnscreens.bottomsheet.isSheetFitToContents
import com.swmansion.rnscreens.bottomsheet.useSingleDetent
import com.swmansion.rnscreens.bottomsheet.useThreeDetents
import com.swmansion.rnscreens.bottomsheet.useTwoDetents
import com.swmansion.rnscreens.bottomsheet.usesFormSheetPresentation
import com.swmansion.rnscreens.ext.recycle
import com.swmansion.rnscreens.utils.DeviceUtils

Expand Down Expand Up @@ -157,14 +162,15 @@ class ScreenStackFragment :
screen.notifySheetDetentChange(
SheetUtils.detentIndexFromSheetState(
lastStableState,
screen.sheetDetents.count()
), true
screen.sheetDetents.count(),
),
true,
)
} else if (newState == BottomSheetBehavior.STATE_DRAGGING) {
screen.notifySheetDetentChange(
SheetUtils.detentIndexFromSheetState(
lastStableState,
screen.sheetDetents.count()
screen.sheetDetents.count(),
),
false,
)
Expand Down Expand Up @@ -223,7 +229,7 @@ class ScreenStackFragment :
LinearLayout.LayoutParams.MATCH_PARENT,
).apply {
behavior =
if (screen.stackPresentation == Screen.StackPresentation.FORM_SHEET) {
if (screen.usesFormSheetPresentation()) {
createAndConfigureBottomSheetBehaviour()
} else if (isToolbarTranslucent) {
null
Expand All @@ -232,7 +238,7 @@ class ScreenStackFragment :
}
}

if (screen.stackPresentation == Screen.StackPresentation.FORM_SHEET) {
if (screen.usesFormSheetPresentation()) {
screen.clipToOutline = true
// TODO(@kkafar): without this line there is no drawable / outline & nothing shows...? Determine what's going on here
attachShapeToScreen(screen)
Expand All @@ -241,7 +247,7 @@ class ScreenStackFragment :

coordinatorLayout.addView(screen.recycle())

if (screen.stackPresentation != Screen.StackPresentation.FORM_SHEET) {
if (!screen.usesFormSheetPresentation()) {
appBarLayout =
context?.let { AppBarLayout(it) }?.apply {
// By default AppBarLayout will have a background color set but since we cover the whole layout
Expand Down Expand Up @@ -348,52 +354,39 @@ class ScreenStackFragment :
return when (keyboardState) {
is KeyboardNotVisible -> {
when (screen.sheetDetents.count()) {
1 -> if (screen.sheetDetents.first() == Screen.SHEET_FIT_TO_CONTENTS) {
behavior.apply {
state = BottomSheetBehavior.STATE_EXPANDED
screen.contentWrapper.get()?.let {
maxHeight = it.height
}
skipCollapsed = true
isFitToContents = true
}
} else {
1 ->
behavior.apply {
state = BottomSheetBehavior.STATE_EXPANDED
skipCollapsed = true
isFitToContents = true
maxHeight = (screen.sheetDetents.first() * containerHeight).toInt()
val height =
if (screen.isSheetFitToContents()) {
screen.contentWrapper.get()?.height
} else {
(screen.sheetDetents.first() * containerHeight).toInt()
}
useSingleDetent(height = height)
}
}

2 ->
behavior.apply {
behavior.useTwoDetents(
state =
SheetUtils.sheetStateFromDetentIndex(
screen.sheetInitialDetentIndex,
screen.sheetDetents.count(),
)
skipCollapsed = false
isFitToContents = true
peekHeight = (screen.sheetDetents[0] * containerHeight).toInt()
maxHeight = (screen.sheetDetents[1] * containerHeight).toInt()
}
),
firstHeight = (screen.sheetDetents[0] * containerHeight).toInt(),
secondHeight = (screen.sheetDetents[1] * containerHeight).toInt(),
)

3 ->
behavior.apply {
behavior.useThreeDetents(
state =
SheetUtils.sheetStateFromDetentIndex(
screen.sheetInitialDetentIndex,
screen.sheetDetents.count(),
)
skipCollapsed = false
isFitToContents = false
peekHeight = (screen.sheetDetents[0] * containerHeight).toInt()
expandedOffset =
((1 - screen.sheetDetents[2]) * containerHeight).toInt()
halfExpandedRatio =
(screen.sheetDetents[1] / screen.sheetDetents[2]).toFloat()
}
),
firstHeight = (screen.sheetDetents[0] * containerHeight).toInt(),
halfExpandedRatio = (screen.sheetDetents[1] / screen.sheetDetents[2]).toFloat(),
expandedOffsetFromTop = ((1 - screen.sheetDetents[2]) * containerHeight).toInt(),
)

else -> throw IllegalStateException(
"[RNScreens] Invalid detent count ${screen.sheetDetents.count()}. Expected at most 3.",
Expand All @@ -411,27 +404,24 @@ class ScreenStackFragment :
when (screen.sheetDetents.count()) {
1 ->
behavior.apply {
state = BottomSheetBehavior.STATE_EXPANDED
skipCollapsed = true
isFitToContents = true
maxHeight = newMaxHeight
useSingleDetent(height = newMaxHeight)
addBottomSheetCallback(keyboardSheetCallback)
}

2 ->
behavior.apply {
state = BottomSheetBehavior.STATE_EXPANDED
skipCollapsed = false
isFitToContents = true
maxHeight = newMaxHeight
useTwoDetents(
state = BottomSheetBehavior.STATE_EXPANDED,
secondHeight = newMaxHeight,
)
addBottomSheetCallback(keyboardSheetCallback)
}

3 ->
behavior.apply {
state = BottomSheetBehavior.STATE_EXPANDED
skipCollapsed = false
isFitToContents = false
useThreeDetents(
state = BottomSheetBehavior.STATE_EXPANDED,
)
maxHeight = newMaxHeight
addBottomSheetCallback(keyboardSheetCallback)
}
Expand All @@ -450,30 +440,23 @@ class ScreenStackFragment :
behavior.removeBottomSheetCallback(keyboardSheetCallback)
when (screen.sheetDetents.count()) {
1 ->
behavior.apply {
skipCollapsed = true
isFitToContents = true
maxHeight = (screen.sheetDetents.first() * containerHeight).toInt()
}
behavior.useSingleDetent(
height = (screen.sheetDetents.first() * containerHeight).toInt(),
forceExpandedState = false,
)

2 ->
behavior.apply {
skipCollapsed = false
isFitToContents = true
peekHeight = (screen.sheetDetents[0] * containerHeight).toInt()
maxHeight = (screen.sheetDetents[1] * containerHeight).toInt()
}
behavior.useTwoDetents(
firstHeight = (screen.sheetDetents[0] * containerHeight).toInt(),
secondHeight = (screen.sheetDetents[1] * containerHeight).toInt(),
)

3 ->
behavior.apply {
skipCollapsed = false
isFitToContents = false
peekHeight = (screen.sheetDetents[0] * containerHeight).toInt()
expandedOffset =
((1 - screen.sheetDetents[2]) * containerHeight).toInt()
halfExpandedRatio =
(screen.sheetDetents[1] / screen.sheetDetents[2]).toFloat()
}
behavior.useThreeDetents(
firstHeight = (screen.sheetDetents[0] * containerHeight).toInt(),
halfExpandedRatio = (screen.sheetDetents[1] / screen.sheetDetents[2]).toFloat(),
expandedOffsetFromTop = ((1 - screen.sheetDetents[2]) * containerHeight).toInt(),
)

else -> throw IllegalStateException(
"[RNScreens] Invalid detent count ${screen.sheetDetents.count()}. Expected at most 3.",
Expand Down Expand Up @@ -595,8 +578,7 @@ class ScreenStackFragment :
// ) : CoordinatorLayout(context), ReactCompoundViewGroup, ReactHitSlopView {
) : CoordinatorLayout(context),
ReactPointerEventsView {
override fun onApplyWindowInsets(insets: WindowInsets?): WindowInsets =
super.onApplyWindowInsets(insets)
override fun onApplyWindowInsets(insets: WindowInsets?): WindowInsets = super.onApplyWindowInsets(insets)

private val animationListener: Animation.AnimationListener =
object : Animation.AnimationListener {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
package com.swmansion.rnscreens.bottomsheet

import android.view.View
import com.google.android.material.bottomsheet.BottomSheetBehavior

internal fun <T : View> BottomSheetBehavior<T>.useSingleDetent(height: Int? = null, forceExpandedState: Boolean = true): BottomSheetBehavior<T> {
this.skipCollapsed = true
this.isFitToContents = true
if (forceExpandedState) {
this.state = BottomSheetBehavior.STATE_EXPANDED
}
height?.let { maxHeight = height }
return this
}

internal fun <T : View> BottomSheetBehavior<T>.useTwoDetents(
@BottomSheetBehavior.StableState state: Int? = null,
firstHeight: Int? = null,
secondHeight: Int? = null
): BottomSheetBehavior<T> {
skipCollapsed = false
isFitToContents = true
state?.let { this.state = state }
firstHeight?.let { peekHeight = firstHeight }
secondHeight?.let { maxHeight = secondHeight }
return this
}

internal fun <T : View> BottomSheetBehavior<T>.useThreeDetents(
@BottomSheetBehavior.StableState state: Int? = null,
firstHeight: Int? = null,
halfExpandedRatio: Float? = null,
expandedOffsetFromTop: Int? = null
): BottomSheetBehavior<T> {
skipCollapsed = false
isFitToContents = false
state?.let { this.state = state }
firstHeight?.let { this.peekHeight = firstHeight }
halfExpandedRatio?.let { this.halfExpandedRatio = halfExpandedRatio }
expandedOffsetFromTop?.let { this.expandedOffset = expandedOffsetFromTop }
return this
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import com.google.android.material.bottomsheet.BottomSheetBehavior.STATE_COLLAPS
import com.google.android.material.bottomsheet.BottomSheetBehavior.STATE_EXPANDED
import com.google.android.material.bottomsheet.BottomSheetBehavior.STATE_HALF_EXPANDED
import com.google.android.material.bottomsheet.BottomSheetBehavior.STATE_HIDDEN
import com.swmansion.rnscreens.Screen

object SheetUtils {
/**
Expand Down Expand Up @@ -125,3 +126,10 @@ object SheetUtils {
return false
}
}

fun Screen.isSheetFitToContents(): Boolean =
stackPresentation === Screen.StackPresentation.FORM_SHEET &&
sheetDetents.count() == 1 &&
sheetDetents.first() == Screen.SHEET_FIT_TO_CONTENTS

fun Screen.usesFormSheetPresentation(): Boolean = stackPresentation === Screen.StackPresentation.FORM_SHEET

0 comments on commit 273194f

Please sign in to comment.