-
-
Notifications
You must be signed in to change notification settings - Fork 521
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat!: iOS custom detents & Android form sheets (#2045)
## Description This PR introduces series of features & changes: 1. possibility of specifying custom detents for form sheets on devices with iOS 16 or newer, 2. changes existing form sheet API of `Screen` component (namely types of values accepted), 3. Android form sheets (bottom sheets presented in current presentation context (in iOS terms) with dimming view with configurable interaction. The form sheet supports up to three detent levels with additional option of `fitToContents` 4. Android Footer component that works together with `formSheet` presentation style 5. 🚧 Android modal bottom sheet - similar to `formSheet`, however the sheet is mounted under separate window. 6. 🚧 iOS Footer component - similar to Android 7. Usage of Material 3 8. series of new props allowing for: a. controlling style of the `Screen` component (necessary workaround for issue with flickering on iOS, b. controlling whether the screen fragment of particular screen should be unmounted or not on Android when the screen is on JS stack but not visible (necessary to achieve "staying form sheet" when navigating back to a screen with presented form sheet), c. listening for `sheetDetentChange` events, in case of Android stable & dragging states are reported, in case of iOS only stable states d. todo: describe rest ## Changes ## Known issues 1. [x] ~After recent commits - iOS compilation on Fabric~ 2. [ ] Android: issue with nested scrollview - invalid behaviour when there is not enough content for scrollview to scroll (viewport is >= content size). Solvable by patching react-native: facebook/react-native#44099, no other workaround found. There is one approach [suggested by grahammendick](https://github.com/grahammendick/navigation/blob/916688d267bd3fc520e2e22328b6aa66124f52ed/NavigationReactNative/src/android/src/main/java/com/navigation/reactnative/CoordinatorLayoutView.java#L96-L148), however yet untested. 3. [ ] Android 'modal' presentation can crash randomly (unknown reason yet, can be deffered) ## Test code and steps to reproduce I've used & extended `Test1649` to present all capabilities of new API. ## 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
Showing
78 changed files
with
4,363 additions
and
517 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
67 changes: 67 additions & 0 deletions
67
android/src/main/java/com/swmansion/rnscreens/InsetsObserverProxy.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,67 @@ | ||
package com.swmansion.rnscreens | ||
|
||
import android.view.View | ||
import androidx.core.view.OnApplyWindowInsetsListener | ||
import androidx.core.view.ViewCompat | ||
import androidx.core.view.WindowInsetsCompat | ||
import java.lang.ref.WeakReference | ||
|
||
object InsetsObserverProxy : OnApplyWindowInsetsListener { | ||
private val listeners: ArrayList<OnApplyWindowInsetsListener> = arrayListOf() | ||
private var eventSourceView: WeakReference<View> = WeakReference(null) | ||
|
||
// Please note semantics of this property. This is not `isRegistered`, because somebody, could unregister | ||
// us, without our knowledge, e.g. reanimated or different 3rd party library. This holds only information | ||
// whether this observer has been initially registered. | ||
private var hasBeenRegistered: Boolean = false | ||
|
||
private var shouldForwardInsetsToView = true | ||
|
||
override fun onApplyWindowInsets( | ||
v: View, | ||
insets: WindowInsetsCompat, | ||
): WindowInsetsCompat { | ||
var rollingInsets = | ||
if (shouldForwardInsetsToView) { | ||
WindowInsetsCompat.toWindowInsetsCompat( | ||
v.onApplyWindowInsets(insets.toWindowInsets()), | ||
v, | ||
) | ||
} else { | ||
insets | ||
} | ||
|
||
listeners.forEach { | ||
rollingInsets = it.onApplyWindowInsets(v, insets) | ||
} | ||
return rollingInsets | ||
} | ||
|
||
fun addOnApplyWindowInsetsListener(listener: OnApplyWindowInsetsListener) { | ||
listeners.add(listener) | ||
} | ||
|
||
fun removeOnApplyWindowInsetsListener(listener: OnApplyWindowInsetsListener) { | ||
listeners.remove(listener) | ||
} | ||
|
||
fun registerOnView(view: View) { | ||
if (!hasBeenRegistered) { | ||
ViewCompat.setOnApplyWindowInsetsListener(view, this) | ||
eventSourceView = WeakReference(view) | ||
hasBeenRegistered = true | ||
} else if (getObservedView() != view) { | ||
throw IllegalStateException( | ||
"[RNScreens] Attempt to register InsetsObserverProxy on $view while it has been already registered on ${getObservedView()}", | ||
) | ||
} | ||
} | ||
|
||
fun unregister() { | ||
eventSourceView.get()?.takeIf { hasBeenRegistered }?.let { | ||
ViewCompat.setOnApplyWindowInsetsListener(it, null) | ||
} | ||
} | ||
|
||
private fun getObservedView(): View? = eventSourceView.get() | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
38 changes: 38 additions & 0 deletions
38
android/src/main/java/com/swmansion/rnscreens/ScreenContentWrapper.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,38 @@ | ||
package com.swmansion.rnscreens | ||
|
||
import android.annotation.SuppressLint | ||
import com.facebook.react.bridge.ReactContext | ||
import com.facebook.react.views.view.ReactViewGroup | ||
|
||
/** | ||
* When we wrap children of the Screen component inside this component in JS code, | ||
* we can later use it to get the enclosing frame size of our content as it is rendered by RN. | ||
* | ||
* This is useful when adapting form sheet height to its contents height. | ||
*/ | ||
@SuppressLint("ViewConstructor") | ||
class ScreenContentWrapper( | ||
reactContext: ReactContext, | ||
) : ReactViewGroup(reactContext) { | ||
internal var delegate: OnLayoutCallback? = null | ||
|
||
interface OnLayoutCallback { | ||
fun onLayoutCallback( | ||
changed: Boolean, | ||
left: Int, | ||
top: Int, | ||
right: Int, | ||
bottom: Int, | ||
) | ||
} | ||
|
||
override fun onLayout( | ||
changed: Boolean, | ||
left: Int, | ||
top: Int, | ||
right: Int, | ||
bottom: Int, | ||
) { | ||
delegate?.onLayoutCallback(changed, left, top, right, bottom) | ||
} | ||
} |
Oops, something went wrong.