Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
62 changes: 39 additions & 23 deletions apps/bare-expo/e2e/expo-video/fullscreen-test.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -4,29 +4,45 @@ jsEngine: graaljs
---
- openLink: bareexpo://components/video/fullscreen

- tapOn: "Enter Fullscreen"
- tapOn: 'Enter Fullscreen'
- runFlow:
when:
platform: Android
commands:
# Dismiss the android system fullscreen instruction popup
- tapOn:
text: "Got it"
optional: true
- assertVisible:
id: "dev.expo.payments:id/exo_content_frame"
- runFlow:
when:
platform: iOS
commands:
- assertVisible: "Video"
when:
platform: Android
commands:
# Dismiss the android system fullscreen instruction popup
- tapOn:
text: 'Got it'
optional: true
- assertVisible:
id: 'dev.expo.payments:id/exo_content_frame'

# Once the fullscreen player is shown it covers the in-app button, so this confirms
# we actually entered fullscreen on both platforms. (On iOS the native player title
# is not asserted because its controls auto-hide and would flake on slow simulators.)
- assertNotVisible: 'Enter Fullscreen'

- assertNotVisible: "Enter Fullscreen"
- tapOn:
point: '${maestro.platform == "android" ? "94%,97%" : "11%,10%"}'
delay: 400
repeat: 2
# Exit fullscreen. Android has a dedicated exit button in the bottom-right of the
# custom fullscreen activity.
- runFlow:
when:
platform: Android
commands:
- tapOn:
point: 94%,97%
delay: 400
repeat: 2
# iOS uses the native AVPlayerViewController chrome, whose controls auto-hide. Tap the
# center to reveal them, then tap the close button in the top-left exactly once: a
# second tap would land on the screen's back button and navigate away from the test.
- runFlow:
when:
platform: iOS
commands:
- tapOn:
point: 50%,50%
- tapOn:
point: 11%,10%

- assertVisible: "Enter Fullscreen"
- assertVisible: "0 = onFullscreenEnter"
- assertVisible: "1 = onFullscreenExit"
- assertVisible: 'Enter Fullscreen'
- assertVisible: '0 = onFullscreenEnter'
- assertVisible: '1 = onFullscreenExit'
12 changes: 11 additions & 1 deletion apps/bare-expo/e2e/expo-video/playback-test.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -20,12 +20,22 @@ jsEngine: graaljs

- tapOn: Play

- assertVisible: 'isPlaying = true'
# Playback takes a moment to start after tapping Play; wait instead of
# asserting instantly to avoid a startup race.
- extendedWaitUntil:
visible: 'isPlaying = true'
timeout: 10000
- assertVisible: 'isAtStart = false'

- tapOn: Pause
- tapOn: Seek to 30s
- assertVisible: 'currentTime = 30'
# The player reports currentTime = 30 before the paused surface repaints the
# seeked frame. Wait for the screen to settle so the screenshot captures the
# t=30s frame and not a stale/blank one. Safe only because the player is paused
# here (a playing video never settles and would burn the full timeout).
- waitForAnimationToEnd:
timeout: 5000
- runFlow:
file: ../_nested-flows/viewshot-comparison.yaml
env:
Expand Down
11 changes: 7 additions & 4 deletions apps/bare-expo/scripts/lib/e2e-common.ts
Original file line number Diff line number Diff line change
Expand Up @@ -63,10 +63,13 @@ jsEngine: graaljs
for (const testCase of testCases) {
contents.push(`\
- openLink: bareexpo://test-suite/run?tests=${testCase}
# make sure we're running the right test
- assertVisible:
id: "test_suite_selection_query_text"
text: "${testCase}"
# make sure we're running the right test. Wait rather than assert instantly: after
# clearState the app cold-starts, and the selection header may not have rendered yet.
- extendedWaitUntil:
visible:
id: "test_suite_selection_query_text"
text: "${testCase}"
timeout: 30000
- extendedWaitUntil:
visible:
id: "test_suite_text_results"
Expand Down
4 changes: 2 additions & 2 deletions apps/bare-expo/scripts/start-android-e2e-test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -120,7 +120,7 @@ async function testAsync(
},
});
console.timeEnd(TEST_DURATION_LABEL);
} catch {
} catch (error: unknown) {
stopLogCollectionController.abort();
console.timeEnd(TEST_DURATION_LABEL);
console.warn(`\n⚠️ Maestro flow failed, because:\n\n`);
Expand All @@ -140,7 +140,7 @@ async function testAsync(
}
}
console.log('\n\n');
throw new Error('e2e tests have failed.');
throw new Error('e2e tests have failed.', { cause: error });
} finally {
endGroup();
stopLogCollectionController.abort();
Expand Down
12 changes: 4 additions & 8 deletions apps/bare-expo/scripts/start-ios-e2e-test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -159,9 +159,8 @@ async function startSimulatorAsync(deviceId: string, timeout: number = 180_000)
);
const label = 'device startup duration';
console.time(label);
const bootProc = spawnAsync('xcrun', ['simctl', 'bootstatus', deviceId, '-b'], {
stdio: 'inherit',
});
// Capture (don't inherit) stdio so the verbose boot/data-migration progress doesn't flood the logs.
const bootProc = spawnAsync('xcrun', ['simctl', 'bootstatus', deviceId, '-b']);

let timeoutHandle: NodeJS.Timeout | null = null;
const timeoutPromise = new Promise((_, reject) => {
Expand Down Expand Up @@ -239,7 +238,7 @@ async function testAsync(
},
});
console.timeEnd(TEST_DURATION_LABEL);
} catch {
} catch (error: unknown) {
stopLogCollectionController.abort();
console.timeEnd(TEST_DURATION_LABEL);
console.warn(`\n⚠️ Maestro flow failed, because:\n\n`);
Expand All @@ -262,11 +261,8 @@ async function testAsync(
}

console.log('\n\n');
throw new Error('e2e tests have failed.');
throw new Error('e2e tests have failed.', { cause: error });
}
} catch (e: unknown) {
console.error('Uncaught Error', e);
throw e;
} finally {
endGroup();
stopLogCollectionController.abort();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ export default function BottomSheetScreen() {

// Configurable props
const [skipPartiallyExpanded, setSkipPartiallyExpanded] = React.useState(false);
const [initialFullyExpanded, setInitialFullyExpanded] = React.useState(false);
const [showDragHandle, setShowDragHandle] = React.useState(true);
const [useCustomDragHandle, setUseCustomDragHandle] = React.useState(false);
const [sheetGesturesEnabled, setSheetGesturesEnabled] = React.useState(true);
Expand Down Expand Up @@ -91,6 +92,15 @@ export default function BottomSheetScreen() {
</ComposeText>
<Switch value={skipPartiallyExpanded} onCheckedChange={setSkipPartiallyExpanded} />
</Row>
<Row
modifiers={[fillMaxWidth()]}
horizontalArrangement="spaceBetween"
verticalAlignment="center">
<ComposeText style={{ typography: 'bodyMedium' }}>
Initial fully expanded
</ComposeText>
<Switch value={initialFullyExpanded} onCheckedChange={setInitialFullyExpanded} />
</Row>
<Row
modifiers={[fillMaxWidth()]}
horizontalArrangement="spaceBetween"
Expand Down Expand Up @@ -201,6 +211,7 @@ export default function BottomSheetScreen() {
ref={sheetRef}
onDismissRequest={() => setShowSheet(false)}
skipPartiallyExpanded={skipPartiallyExpanded}
initialFullyExpanded={initialFullyExpanded}
showDragHandle={showDragHandle}
sheetGesturesEnabled={sheetGesturesEnabled}
properties={{
Expand Down
14 changes: 14 additions & 0 deletions apps/test-suite/screens/SelectScreen.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,20 @@ export default function SelectScreen({ navigation }) {
({ url }: { url: string }) => {
url = url || '';
// TODO: Use Expo Linking library once parseURL is implemented for web

// Run a specific set of tests, e.g. bareexpo://test-suite/run?tests=basic,blur
// React Navigation linking also maps this URL to the run screen, but on a cold
// start the two race and this handler used to win by falling through to the
// selection list below. Handle the query explicitly so the deep link always runs.
const testsQueryMatch = url.match(/[?&]tests=([^&]+)/);

if (testsQueryMatch) {
const tests = getSelectedTestNames(decodeURIComponent(testsQueryMatch[1]));
const query = createQueryString(tests);
navigation.navigate(routeNames.run, { tests: query });
return;
}

if (url.includes(`/${routeNames.select}/`)) {
const selectedTests = url.split('/').pop();
if (selectedTests) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,52 @@ export default function SkipPartiallyExpandedExample() {
}
```

### Initial fully expanded state

When `initialFullyExpanded` is `true`, the sheet opens directly in the fully expanded state on first composition while leaving the partial state reachable. Unlike `skipPartiallyExpanded`, the user can still drag down to the partial state. The `partialExpand()` method also continues to work.

```tsx InitialFullyExpandedExample.tsx
import { useRef, useState } from 'react';
import { Host, ModalBottomSheet, Button, Column, Text } from '@expo/ui/jetpack-compose';
import type { ModalBottomSheetRef } from '@expo/ui/jetpack-compose';
import { paddingAll } from '@expo/ui/jetpack-compose/modifiers';

export default function InitialFullyExpandedExample() {
const [visible, setVisible] = useState(false);
const sheetRef = useRef<ModalBottomSheetRef>(null);

const hideSheet = async () => {
await sheetRef.current?.hide();
setVisible(false);
};

return (
<Host matchContents>
<Button onClick={() => setVisible(true)}>
<Text>Open Sheet</Text>
</Button>
{visible && (
<ModalBottomSheet
ref={sheetRef}
onDismissRequest={() => setVisible(false)}
initialFullyExpanded>
<Column verticalArrangement={{ spacedBy: 12 }} modifiers={[paddingAll(24)]}>
<Text>This sheet opened fully expanded.</Text>
<Text>You can still drag it down to the partial state.</Text>
<Button onClick={() => sheetRef.current?.partialExpand()}>
<Text>Collapse to partial</Text>
</Button>
<Button onClick={hideSheet}>
<Text>Close</Text>
</Button>
</Column>
</ModalBottomSheet>
)}
</Host>
);
}
```

### Custom colors

Use `containerColor`, `contentColor`, and `scrimColor` to customize the sheet's appearance.
Expand Down
46 changes: 46 additions & 0 deletions docs/pages/versions/v56.0.0/sdk/ui/jetpack-compose/bottomsheet.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,52 @@ export default function SkipPartiallyExpandedExample() {
}
```

### Initial fully expanded state

When `initialFullyExpanded` is `true`, the sheet opens directly in the fully expanded state on first composition while leaving the partial state reachable. Unlike `skipPartiallyExpanded`, the user can still drag down to the partial state. The `partialExpand()` method also continues to work.

```tsx InitialFullyExpandedExample.tsx
import { useRef, useState } from 'react';
import { Host, ModalBottomSheet, Button, Column, Text } from '@expo/ui/jetpack-compose';
import type { ModalBottomSheetRef } from '@expo/ui/jetpack-compose';
import { paddingAll } from '@expo/ui/jetpack-compose/modifiers';

export default function InitialFullyExpandedExample() {
const [visible, setVisible] = useState(false);
const sheetRef = useRef<ModalBottomSheetRef>(null);

const hideSheet = async () => {
await sheetRef.current?.hide();
setVisible(false);
};

return (
<Host matchContents>
<Button onClick={() => setVisible(true)}>
<Text>Open Sheet</Text>
</Button>
{visible && (
<ModalBottomSheet
ref={sheetRef}
onDismissRequest={() => setVisible(false)}
initialFullyExpanded>
<Column verticalArrangement={{ spacedBy: 12 }} modifiers={[paddingAll(24)]}>
<Text>This sheet opened fully expanded.</Text>
<Text>You can still drag it down to the partial state.</Text>
<Button onClick={() => sheetRef.current?.partialExpand()}>
<Text>Collapse to partial</Text>
</Button>
<Button onClick={hideSheet}>
<Text>Close</Text>
</Button>
</Column>
</ModalBottomSheet>
)}
</Host>
);
}
```

### Custom colors

Use `containerColor`, `contentColor`, and `scrimColor` to customize the sheet's appearance.
Expand Down

Large diffs are not rendered by default.

Large diffs are not rendered by default.

Large diffs are not rendered by default.

Large diffs are not rendered by default.

3 changes: 3 additions & 0 deletions packages/expo-ui/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,9 @@

### 🐛 Bug fixes

- [universal] Fix user-supplied `modifiers` having no effect when the component derives a modifier of the same type from its props, e.g. `buttonStyle` on `Button` always losing to the `variant` prop. ([#46815](https://github.com/expo/expo/pull/46815) by [@nishan](https://github.com/intergalacticspacehighway))
- [android] Fix race between JS imperative `expand()` and Compose handler registration on `ModalBottomSheet`. Adds `initialFullyExpanded` prop to drive initial snap state natively. ([#46367](https://github.com/expo/expo/pull/46367) by [@duyanhv](https://github.com/duyanhv))
- [iOS][android] Fix `community/bottom-sheet` blocking touches behind the sheet after it's dismissed. ([#46805](https://github.com/expo/expo/pull/46805) by [@nishan](https://github.com/intergalacticspacehighway))
- [android] Fix React Native touchables (e.g. `Pressable`) on `community/pager-view` pages not responding, or triggering the wrong page's handler, after navigating between pages. ([#46778](https://github.com/expo/expo/pull/46778) by [@nishan](https://github.com/intergalacticspacehighway))
- [iOS] Fix `font`, `dynamicTypeSize`, and `resizable` modifiers not applying to the SwiftUI `Image`. SF Symbols scale with Dynamic Type when a `font` modifier sets a `textStyle`. ([#46714](https://github.com/expo/expo/pull/46714) by [@ramonclaudio](https://github.com/ramonclaudio))
- [android] Fix React Native `ScrollView` nested scrolling inside `BottomSheet`. ([#46544](https://github.com/expo/expo/pull/46544) by [@nishan](https://github.com/intergalacticspacehighway))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import androidx.compose.material3.ModalBottomSheetProperties
import androidx.compose.material3.contentColorFor
import androidx.compose.material3.rememberModalBottomSheetState
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.rememberCoroutineScope
import expo.modules.kotlin.records.Field
import expo.modules.kotlin.records.Record
Expand All @@ -30,6 +31,7 @@ data class ModalBottomSheetPropertiesRecord(
@OptimizedComposeProps
data class ModalBottomSheetViewProps(
val skipPartiallyExpanded: Boolean = false,
val initialFullyExpanded: Boolean = false,
val containerColor: Color? = null,
val contentColor: Color? = null,
val scrimColor: Color? = null,
Expand Down Expand Up @@ -112,4 +114,16 @@ fun FunctionalComposableScope.ModalBottomSheetContent(
) {
Children(UIComposableScope(), filter = { !isSlotView(it) })
}

LaunchedEffect(Unit) {
if (props.initialFullyExpanded && !props.skipPartiallyExpanded) {
try {
sheetState.expand()
} catch (_: CancellationException) {
// Dismissal can cancel the expand animation.
} catch (_: Exception) {
// Expanded anchor may be unreachable; never crash the view.
}
}
}
}

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion packages/expo-ui/build/universal/Button/index.ios.d.ts.map

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion packages/expo-ui/build/universal/Text/index.ios.d.ts.map

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading
Loading