Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat!: iOS custom detents & Android form sheets #2045

Open
wants to merge 257 commits into
base: main
Choose a base branch
from

Conversation

kkafar
Copy link
Member

@kkafar kkafar commented Feb 21, 2024

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. 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: Fix ScrollView interactions with Android's CoordinatorLayout facebook/react-native#44099, no other workaround found. There is one approach suggested by grahammendick, 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

It is now implemented as extension method on `View` class
@kkafar kkafar requested a review from tboba May 9, 2024 07:52
@kkafar kkafar self-assigned this May 9, 2024
Copy link
Member

@tboba tboba left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Great changes! Allow me to review this PR in parts 😄 (Part 1/3)

Comment on lines +261 to +287
// useFocusEffect(
// React.useCallback(() => {
// const handle = setInterval(() => {
// console.log('Hellow from setInterval');
// setBgColor(value => {
// return value === 'firebrick' ? 'green' : 'firebrick';
// });
// }, 750);
//
// return () => {
// clearInterval(handle);
// };
// }, []),
// );

// React.useEffect(() => {
// const handle = setInterval(() => {
// console.log('Hellow from setInterval');
// setBgColor(value => {
// return value === 'firebrick' ? 'green' : 'firebrick';
// });
// }, 750);
//
// return () => {
// clearInterval(handle);
// };
// }, []);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this commenting in the test intended? Do we want to leave this logic uncommented? 😄

Comment on lines +128 to +130
onSheetDetentChanged: (e: NativeSyntheticEvent<{ index: number }>) => {
console.log(`onSheetDetentChanged in App with index ${e.nativeEvent.index}`)
},
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why is this event defined like this? Don't we want to use listeners object in this case and define this event there?

Comment on lines -101 to +105
enableFreeze(true);
// enableFreeze(true);
enableFreeze(false);

export default function App() {
return <Test42 />;
return <Test1649 />;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This shouldn't be Test1649 and enableFreeze should be still true

Comment on lines +118 to +119

implementation("com.google.android.material:material:1.9.0")
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Shouldn't this be defined inside the build.gradle of screens? Or moved to the separate PR? 😄

<style name="AppTheme" parent="Theme.AppCompat.DayNight.NoActionBar">
<style name="AppTheme" parent="Theme.Material3.DayNight.NoActionBar">
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same as above

override fun runGuarded() {
// If the status bar is translucent hook into the window insets calculations
// and consume all the top insets so no padding will be added under the status bar.
val decorView = activity.window.decorView
// val decorView = activity.window.decorView
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

to be removed

if (translucent) {
Log.w("ScreenWindowTraits", "setting onApplyWindowInsetsListener.Callback on $decorView")
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

To be removed :D

ViewCompat.setOnApplyWindowInsetsListener(decorView) { v, insets ->
Log.w("ScreenWindowTraits", "onApplyWindowInsetsListener.Callback on $v")
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

to be removed

Comment on lines 161 to 186
// ViewCompat.requestApplyInsets(decorView)
} else {
Log.w("ScreenWindowTraits", "Removing listener from $decorView")
ViewCompat.setOnApplyWindowInsetsListener(decorView, null)
// ViewCompat.setOnApplyWindowInsetsListener(decorView) { view, insets ->
// val isImeVisible = insets.isVisible(WindowInsetsCompat.Type.ime())
// val prevInsets = insets.getInsets(WindowInsetsCompat.Type.navigationBars())
// if (isImeVisible) {
// WindowInsetsCompat
// .Builder(insets)
// .setInsets(
// WindowInsetsCompat.Type.navigationBars(),
// Insets.of(
// prevInsets.left,
// prevInsets.top,
// prevInsets.right,
// 0
// )
// ).build()
// } else {
// insets
// }
// }
// ViewCompat.setOnApplyWindowInsetsListener(decorView, decorView.getTag(androidx.core.R.id.tag_on_apply_window_listener) as? OnApplyWindowInsetsListener)
hasAttachedWindowInsetsCallback = false
// ViewCompat.requestApplyInsets(decorView)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's remove comments

private var jsPointerDispatcher: JSPointerDispatcher? = null

init {
// Can we safely use ReactFeatureFlags?
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's for now remove this comment and assume we can 😄

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

^Nevermind, lemme check source code of RN to find out if we can 🤔

Copy link
Member

@tboba tboba left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was lying, that my review will take 3 parts 😄

Part 2/2

Comment on lines +93 to +97
override fun handleException(throwable: Throwable?) {
// TODO: I need ThemedReactContext here.
// TODO: Determine where it is initially created & verify its lifecycle
// reactContext?.reactApplicationContext?.handleException(RuntimeException(throwable))
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we still keep this?

package com.swmansion.rnscreens.bottomsheet

import android.content.Context
import android.util.Log
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Optimize imports 😄

// to run, as this will lead to inconsistencies in ScreenStack state. Instead we intercept
// dismiss intention and run our logic.
override fun cancel() {
Log.d(TAG, "cancel")
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Remove Log call

// dismiss intention and run our logic.
override fun cancel() {
Log.d(TAG, "cancel")
fragmentRef.get()!!.dismissFromContainer()
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

requireNotNull()? 🥺

import android.graphics.Color
import android.os.Build
import android.os.Bundle
import android.util.Log
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Optimize imports

Comment on lines +9 to +17
// return (
// <ScreenContentWrapper style={{ display: "flex", flexDirection: "column", justifyContent: "space-between" }}>
// <View collapsable={false} />
// <ScreenFooter
// collapsable={false}>
// {children}
// </ScreenFooter>
// </ScreenContentWrapper>
// );
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Remove?

Comment on lines +105 to +112
// stackPresentation === 'formSheet' && Platform.OS === 'ios' ? styles.absoluteFillNoBottom : styles.container,
// styles.container,
stackPresentation === 'formSheet'
? Platform.OS === 'ios'
? styles.absoluteFillNoBottom
: null
: styles.container,
// stackPresentation === 'formSheet' && Platform.OS === 'ios' ? styles.absoluteFillNoBottom : styles.container,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Remove comments

@@ -319,31 +349,36 @@ const RouteView = ({
});
}}
onWillAppear={() => {
console.log(`onWillAppear/transitionStart route: ${route.key}`);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Remove console.logs

@@ -256,20 +262,30 @@ export interface ScreenProps extends ViewProps {
* - "landscape_right" – landscape-right orientation is permitted
*/
screenOrientation?: ScreenOrientationTypes;
screenStyle?: StyleProp<ViewStyle>;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What does this prop do?

/**
* Integer value describing elevation of the sheet, impacting shadow on the top edge of the sheet.
*
* Not dynamic.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What does not dynamic mean?

@tboba
Copy link
Member

tboba commented May 13, 2024

Also, should we create an example of how does new sheets behave with new detent types on iOS/Android? Example of using ScreenFooter would be also helpful!

@tboba tboba requested a review from WoLewicki May 14, 2024 09:18
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

2 participants