Skip to content

Commit 5532a84

Browse files
authored
Merge branch 'main' into @maciekstosio/Handle-bottom-sheet-with-a-keyboard
2 parents bcded13 + 54acad4 commit 5532a84

File tree

26 files changed

+453
-86
lines changed

26 files changed

+453
-86
lines changed
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
import { device, expect, element, by } from 'detox';
2+
import { describeIfiOS } from '../e2e-utils';
3+
4+
// PR related to iOS search bar
5+
describeIfiOS('Test2926', () => {
6+
beforeAll(async () => {
7+
await device.reloadReactNative();
8+
});
9+
10+
it('Test2926 should exist', async () => {
11+
await waitFor(element(by.id('root-screen-tests-Test2926')))
12+
.toBeVisible()
13+
.whileElement(by.id('root-screen-examples-scrollview'))
14+
.scroll(600, 'down', NaN, 0.85);
15+
16+
await expect(element(by.id('root-screen-tests-Test2926'))).toBeVisible();
17+
await element(by.id('root-screen-tests-Test2926')).tap();
18+
});
19+
20+
it('searchBar should be initially visible', async () => {
21+
await expect(element(by.type('UISearchBarTextField'))).toBeVisible();
22+
});
23+
24+
it('searchBar should hide after setting headerSearchBarOptions to undefined', async () => {
25+
await element(by.id('home-switch-search-enabled')).tap();
26+
await expect(element(by.type('UISearchBarTextField'))).not.toBeVisible();
27+
});
28+
29+
it('searchBar value should stay in text field after coming back from another screen', async () => {
30+
await element(by.id('home-switch-search-enabled')).tap();
31+
32+
await element(by.type('UISearchBarTextField')).replaceText('Item 2');
33+
await element(by.id('home-button-open-second')).tap();
34+
35+
await element(by.id('BackButton')).tap();
36+
37+
await expect(element(by.type('UISearchBarTextField'))).toBeVisible();
38+
await expect(element(by.type('UISearchBarTextField'))).toHaveText('Item 2');
39+
});
40+
});
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
import { device, expect, element, by } from 'detox';
2+
import { describeIfiOS } from '../e2e-utils';
3+
4+
// issue related to iOS
5+
describeIfiOS('Test726', () => {
6+
beforeAll(async () => {
7+
await device.reloadReactNative();
8+
});
9+
10+
it('Test726 should exist', async () => {
11+
await waitFor(element(by.id('root-screen-tests-Test726')))
12+
.toBeVisible()
13+
.whileElement(by.id('root-screen-examples-scrollview'))
14+
.scroll(600, 'down', NaN, 0.85);
15+
16+
await expect(element(by.id('root-screen-tests-Test726'))).toBeVisible();
17+
await element(by.id('root-screen-tests-Test726')).tap();
18+
});
19+
20+
it('swiping back should not cause the freeze', async () => {
21+
await expect(element(by.id('test-screen-button-push-me'))).toBeVisible();
22+
await element(by.id('test-screen-button-push-me')).tap();
23+
24+
await expect(element(by.id('test-screen-2-text'))).toBeVisible();
25+
await element(by.id('test-screen-2-view')).swipe('right', 'slow', 0.9, 0.0);
26+
27+
// Check that the app is still responsive by navigating to TestScreen2 again
28+
await expect(element(by.id('test-screen-button-push-me'))).toBeVisible();
29+
await element(by.id('test-screen-button-push-me')).tap();
30+
31+
await expect(element(by.id('test-screen-2-text'))).toBeVisible();
32+
});
33+
});

android/src/main/java/com/swmansion/rnscreens/InsetsObserverProxy.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ import com.facebook.react.bridge.ReactApplicationContext
1010
import java.lang.ref.WeakReference
1111

1212
object InsetsObserverProxy : OnApplyWindowInsetsListener, LifecycleEventListener {
13-
private val listeners: ArrayList<OnApplyWindowInsetsListener> = arrayListOf()
13+
private val listeners: HashSet<OnApplyWindowInsetsListener> = hashSetOf()
1414
private var eventSourceView: WeakReference<View> = WeakReference(null)
1515

1616
// Please note semantics of this property. This is not `isRegistered`, because somebody, could unregister

android/src/main/java/com/swmansion/rnscreens/Screen.kt

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ import com.swmansion.rnscreens.bottomsheet.useSingleDetent
3131
import com.swmansion.rnscreens.bottomsheet.usesFormSheetPresentation
3232
import com.swmansion.rnscreens.events.HeaderHeightChangeEvent
3333
import com.swmansion.rnscreens.events.SheetDetentChangedEvent
34+
import com.swmansion.rnscreens.ext.asScreenStackFragment
3435
import com.swmansion.rnscreens.ext.parentAsViewGroup
3536

3637
@SuppressLint("ViewConstructor") // Only we construct this view, it is never inflated.
@@ -541,6 +542,22 @@ class Screen(
541542
}
542543
}
543544

545+
override fun onAttachedToWindow() {
546+
super.onAttachedToWindow()
547+
548+
// Insets handler for formSheet is added onResume but it is often too late if we use input
549+
// with autofocus - onResume is called after finishing animator animation.
550+
// onAttachedToWindow is called before onApplyWindowInsets so we use it to set the handler
551+
// earlier. More details: https://github.com/software-mansion/react-native-screens/pull/2911
552+
if (usesFormSheetPresentation()) {
553+
fragment?.asScreenStackFragment()?.sheetDelegate?.let {
554+
InsetsObserverProxy.addOnApplyWindowInsetsListener(
555+
it,
556+
)
557+
}
558+
}
559+
}
560+
544561
private fun dispatchSheetDetentChanged(
545562
detentIndex: Int,
546563
isStable: Boolean,

android/src/main/java/com/swmansion/rnscreens/ScreenStackFragment.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -75,7 +75,7 @@ class ScreenStackFragment :
7575

7676
private var dimmingDelegate: DimmingViewManager? = null
7777

78-
private var sheetDelegate: SheetDelegate? = null
78+
internal var sheetDelegate: SheetDelegate? = null
7979

8080
@SuppressLint("ValidFragment")
8181
constructor(screenView: Screen) : super(screenView)

android/src/main/java/com/swmansion/rnscreens/bottomsheet/SheetDelegate.kt

Lines changed: 1 addition & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -181,23 +181,13 @@ class SheetDelegate(
181181
}
182182

183183
is KeyboardVisible -> {
184-
val newMaxHeight =
185-
if (behavior.maxHeight - keyboardState.height > 1) {
186-
behavior.maxHeight - keyboardState.height
187-
} else {
188-
behavior.maxHeight
189-
}
190184
when (screen.sheetDetents.count()) {
191185
1 ->
192-
behavior.apply {
193-
useSingleDetent(height = newMaxHeight)
194-
}
195-
186+
behavior
196187
2 ->
197188
behavior.apply {
198189
useTwoDetents(
199190
state = BottomSheetBehavior.STATE_EXPANDED,
200-
secondHeight = newMaxHeight,
201191
)
202192
}
203193

@@ -206,7 +196,6 @@ class SheetDelegate(
206196
useThreeDetents(
207197
state = BottomSheetBehavior.STATE_EXPANDED,
208198
)
209-
maxHeight = newMaxHeight
210199
}
211200

212201
else -> throw IllegalStateException(
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
package com.swmansion.rnscreens.ext
2+
3+
import androidx.fragment.app.Fragment
4+
import com.swmansion.rnscreens.ScreenStackFragment
5+
6+
internal fun Fragment.asScreenStackFragment() = this as ScreenStackFragment

apps/Example.tsx

Lines changed: 20 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -5,11 +5,8 @@ import {
55
I18nManager,
66
Platform,
77
useColorScheme,
8-
View,
98
} from 'react-native';
109
import {
11-
DarkTheme,
12-
DefaultTheme,
1310
NavigationContainer,
1411
NavigationIndependentTree,
1512
useTheme,
@@ -38,6 +35,7 @@ import { GestureDetectorProvider } from 'react-native-screens/gesture-handler';
3835
import { GestureHandlerRootView } from 'react-native-gesture-handler';
3936

4037
import * as Tests from './src/tests';
38+
import { ScreensDarkTheme, ScreensLightTheme } from './src/shared/styling/adapter/react-navigation';
4139

4240
function isPlatformReady(name: keyof typeof SCREENS) {
4341
if (Platform.isTV) {
@@ -153,29 +151,29 @@ const examples = screens.filter(name => SCREENS[name].type === 'example');
153151
const playgrounds = screens.filter(name => SCREENS[name].type === 'playground');
154152
const tests = isTestSectionEnabled()
155153
? screens
156-
.filter(name => SCREENS[name].type === 'test')
157-
.sort((name1, name2) => {
158-
const testNumber1 = Number(name1.substring(4));
159-
const testNumber2 = Number(name2.substring(4));
154+
.filter(name => SCREENS[name].type === 'test')
155+
.sort((name1, name2) => {
156+
const testNumber1 = Number(name1.substring(4));
157+
const testNumber2 = Number(name2.substring(4));
160158

161-
if (Number.isNaN(testNumber1) && Number.isNaN(testNumber2)) {
162-
return 0;
163-
} else if (Number.isNaN(testNumber1)) {
164-
return 1;
165-
} else if (Number.isNaN(testNumber2)) {
166-
return -1;
167-
} else {
168-
return testNumber1 - testNumber2;
169-
}
170-
})
159+
if (Number.isNaN(testNumber1) && Number.isNaN(testNumber2)) {
160+
return 0;
161+
} else if (Number.isNaN(testNumber1)) {
162+
return 1;
163+
} else if (Number.isNaN(testNumber2)) {
164+
return -1;
165+
} else {
166+
return testNumber1 - testNumber2;
167+
}
168+
})
171169
: [];
172170

173171
type RootStackParamList = {
174172
Main: undefined;
175173
Tests: undefined;
176174
} & {
177-
[P in keyof typeof SCREENS]: undefined;
178-
};
175+
[P in keyof typeof SCREENS]: undefined;
176+
};
179177

180178
const Stack = createNativeStackNavigator<RootStackParamList>();
181179

@@ -258,15 +256,14 @@ const ExampleApp = (): React.JSX.Element => {
258256
<GestureHandlerRootView style={{ flex: 1 }}>
259257
<GestureDetectorProvider>
260258
<ThemeToggle.Provider value={{ toggleTheme }}>
261-
<NavigationContainer theme={isDark ? DarkTheme : DefaultTheme}>
259+
<NavigationContainer theme={isDark ? ScreensDarkTheme : ScreensLightTheme}>
262260
<Stack.Navigator
263261
screenOptions={{ statusBarStyle: isDark ? 'light' : 'dark' }}>
264262
<Stack.Screen
265263
name="Main"
266264
options={{
267-
title: `${
268-
Platform.isTV ? '📺' : '📱'
269-
} React Native Screens Examples`,
265+
title: `${Platform.isTV ? '📺' : '📱'
266+
} React Native Screens Examples`,
270267
}}
271268
component={MainScreen}
272269
/>

apps/src/shared/Alert.tsx

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,18 @@
11
import React from 'react';
22
import { Text, StyleSheet, Dimensions, View, Pressable } from 'react-native';
33
import { useNavigation } from '@react-navigation/native';
4+
import useThemeColorPallette from './styling/adapter/react-navigation/useColorPallette';
45

56
export const Alert = (): React.JSX.Element => {
67
const navigation = useNavigation();
8+
const { colors } = useThemeColorPallette();
9+
710
const backgrounds = [
8-
'darkviolet',
9-
'slateblue',
10-
'mediumseagreen',
11-
'orange',
12-
'indianred',
11+
colors.BlueLight80,
12+
colors.YellowDark80,
13+
colors.PurpleLight80,
14+
colors.BlueDark80,
15+
colors.YellowLight80,
1316
];
1417
const bgColor = backgrounds[Math.floor(Math.random() * backgrounds.length)];
1518

apps/src/shared/Dialog.tsx

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import {
88
SafeAreaView,
99
} from 'react-native';
1010
import { useNavigation } from '@react-navigation/native';
11+
import Colors from './styling/Colors';
1112

1213
export const Dialog = (): React.JSX.Element => {
1314
const navigation = useNavigation();
@@ -35,7 +36,7 @@ const styles = StyleSheet.create({
3536
},
3637
wrapper: {
3738
width: Dimensions.get('screen').width - 40,
38-
backgroundColor: 'white',
39+
backgroundColor: Colors.background,
3940
shadowColor: 'black',
4041
shadowOffset: {
4142
width: 0,
@@ -57,7 +58,7 @@ const styles = StyleSheet.create({
5758
textAlign: 'center',
5859
},
5960
button: {
60-
backgroundColor: 'dodgerblue',
61+
backgroundColor: Colors.BlueLight80,
6162
height: 40,
6263
borderRadius: 8,
6364
justifyContent: 'center',

0 commit comments

Comments
 (0)