peachsim 0.1.663 → 0.1.665
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/cli/app-fonts.ts +6 -3
- package/cli/commands/control.ts +27 -19
- package/cli/commands/inspect/core.ts +58 -32
- package/cli/commands/inspect/settling.ts +27 -21
- package/dist-lib/agent-daemon-client.cjs +1 -1
- package/dist-lib/agent-events.cjs +1 -1
- package/dist-lib/agent-identity.cjs +1 -1
- package/dist-lib/agent-sessions.cjs +1 -1
- package/dist-lib/attached-projects.cjs +1 -1
- package/dist-lib/auth/shared-session.cjs +1 -1
- package/dist-lib/backend-origin.cjs +1 -1
- package/dist-lib/beta.cjs +1 -1
- package/dist-lib/beta.mjs +1 -1
- package/dist-lib/bridge-constants.cjs +1 -1
- package/dist-lib/bridge-contract-input.cjs +2 -6
- package/dist-lib/bridge-contract-input.mjs +2 -6
- package/dist-lib/bridge-contract.cjs +6 -1
- package/dist-lib/bridge-contract.mjs +5 -1
- package/dist-lib/capture-contract.cjs +1 -1
- package/dist-lib/capture-contract.mjs +1 -1
- package/dist-lib/cli-constants.cjs +1 -1
- package/dist-lib/cloud-contract.cjs +1 -1
- package/dist-lib/cloud-contract.mjs +1 -1
- package/dist-lib/cloud-test-args.cjs +1 -1
- package/dist-lib/cloud-test-args.mjs +1 -1
- package/dist-lib/cloud.cjs +1 -1
- package/dist-lib/cloud.mjs +1 -1
- package/dist-lib/config.cjs +1 -1
- package/dist-lib/detox/index.cjs +1 -1
- package/dist-lib/dev-bundle-resolution.cjs +1 -1
- package/dist-lib/home-paths.cjs +1 -1
- package/dist-lib/host/bridge-host.cjs +2191 -335
- package/dist-lib/host/fetch-proxy-handler.cjs +1 -1
- package/dist-lib/host/fetch-proxy-overrides.cjs +1 -1
- package/dist-lib/host/fetch-proxy-overrides.mjs +1 -1
- package/dist-lib/host/replacement-module-handler.cjs +7 -7
- package/dist-lib/host/websocket-proxy.cjs +1 -1
- package/dist-lib/index.cjs +23 -30
- package/dist-lib/jump-to-source-babel.cjs +1 -1
- package/dist-lib/jump-to-source-native.cjs +1 -1
- package/dist-lib/kotlin-host.cjs +1 -1
- package/dist-lib/menu.cjs +1 -1
- package/dist-lib/menu.mjs +1 -1
- package/dist-lib/metro-fingerprint-registry.cjs +1 -1
- package/dist-lib/metro-fingerprint-registry.mjs +1 -1
- package/dist-lib/metro-production-bundle.cjs +1 -1
- package/dist-lib/metro-production-bundle.mjs +1 -1
- package/dist-lib/metro.cjs +7 -7
- package/dist-lib/native-app-identity.cjs +1 -1
- package/dist-lib/native-app-identity.mjs +1 -1
- package/dist-lib/plist.cjs +1 -1
- package/dist-lib/plist.mjs +1 -1
- package/dist-lib/profiles.cjs +1 -1
- package/dist-lib/public-brand.cjs +1 -1
- package/dist-lib/react-native-host-modules.cjs +1 -1
- package/dist-lib/react-native-host-modules.mjs +1 -1
- package/dist-lib/render-mode.cjs +1 -1
- package/dist-lib/scripts/dev-server-scanner.cjs +1 -1
- package/dist-lib/sdk.cjs +56 -39
- package/dist-lib/sdk.mjs +54 -39
- package/dist-lib/skills.cjs +2803 -974
- package/dist-lib/swift-compile.cjs +1 -1
- package/dist-lib/swift-compile.mjs +1 -1
- package/dist-lib/swift-package.cjs +1 -1
- package/dist-lib/swift-package.mjs +1 -1
- package/dist-lib/swift-submit.cjs +1 -1
- package/dist-lib/swift-submit.mjs +1 -1
- package/dist-lib/swift.cjs +1 -1
- package/dist-lib/vite-swift.cjs +1 -1
- package/dist-lib/vite.cjs +7 -7
- package/kotlin/ComposeSources/PeachComposeHost.kt +6 -3
- package/kotlin/fixtures/kotlin-app-variants/Overlays.kt +56 -0
- package/kotlin/fixtures/kotlin-app-variants/Scroll.kt +63 -0
- package/kotlin/fixtures/kotlin-app-variants/Sheet.kt +43 -0
- package/kotlin/fixtures/kotlin-app-variants/Tabs.kt +47 -0
- package/kotlin/fixtures/kotlin-app-variants/Widgets.kt +67 -0
- package/package.json +2 -2
- package/skills/peach-setup/SKILL.md +1 -1
- package/src/bridge-contract-input.ts +1 -5
- package/src/bridge-contract.ts +16 -4
- package/src/expo-swift-host.ts +248 -0
- package/src/host/bridge-host.ts +19 -0
- package/src/vite-plugin.ts +3 -3
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
/*! rnx v0.1.
|
|
1
|
+
/*! rnx v0.1.665 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
|
|
2
2
|
let __sootsim_import_meta_url = ''; try { __sootsim_import_meta_url = require('url').pathToFileURL(__filename).href; } catch {}
|
|
3
3
|
"use strict";
|
|
4
4
|
var __create = Object.create;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
/*! rnx v0.1.
|
|
1
|
+
/*! rnx v0.1.665 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
|
|
2
2
|
let __sootsim_import_meta_url = ''; try { __sootsim_import_meta_url = require('url').pathToFileURL(__filename).href; } catch {}
|
|
3
3
|
"use strict";
|
|
4
4
|
var __defProp = Object.defineProperty;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
/*! rnx v0.1.
|
|
1
|
+
/*! rnx v0.1.665 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
|
|
2
2
|
let __sootsim_import_meta_url = ''; try { __sootsim_import_meta_url = require('url').pathToFileURL(__filename).href; } catch {}
|
|
3
3
|
"use strict";
|
|
4
4
|
var __defProp = Object.defineProperty;
|
package/dist-lib/swift.cjs
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
/*! rnx v0.1.
|
|
1
|
+
/*! rnx v0.1.665 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
|
|
2
2
|
let __sootsim_import_meta_url = ''; try { __sootsim_import_meta_url = require('url').pathToFileURL(__filename).href; } catch {}
|
|
3
3
|
"use strict";
|
|
4
4
|
var __defProp = Object.defineProperty;
|
package/dist-lib/vite-swift.cjs
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
/*! rnx v0.1.
|
|
1
|
+
/*! rnx v0.1.665 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
|
|
2
2
|
let __sootsim_import_meta_url = ''; try { __sootsim_import_meta_url = require('url').pathToFileURL(__filename).href; } catch {}
|
|
3
3
|
"use strict";
|
|
4
4
|
var __create = Object.create;
|
package/dist-lib/vite.cjs
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
/*! rnx v0.1.
|
|
1
|
+
/*! rnx v0.1.665 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
|
|
2
2
|
let __sootsim_import_meta_url = ''; try { __sootsim_import_meta_url = require('url').pathToFileURL(__filename).href; } catch {}
|
|
3
3
|
"use strict";
|
|
4
4
|
var __create = Object.create;
|
|
@@ -317,12 +317,12 @@ function transformsFor(filePath) {
|
|
|
317
317
|
function isLocalSpecifier(specifier) {
|
|
318
318
|
return specifier.startsWith("./") || specifier.startsWith("../");
|
|
319
319
|
}
|
|
320
|
-
function
|
|
321
|
-
return specifier === "
|
|
320
|
+
function isPeachKitSpecifier(specifier) {
|
|
321
|
+
return specifier === "peach-kit" || specifier.startsWith("peach-kit/");
|
|
322
322
|
}
|
|
323
323
|
function unsupportedSpecifier(specifier) {
|
|
324
324
|
return new Error(
|
|
325
|
-
`replacement modules may only import react, react-native,
|
|
325
|
+
`replacement modules may only import react, react-native, peach-kit, the packages rnx stubs for this app, and other project files by relative path; '${specifier}' is none of those. vendor the code into a project file, or add the package to peach.config so the simulator bridges it.`
|
|
326
326
|
);
|
|
327
327
|
}
|
|
328
328
|
function bridgeDeclaration(name, specifier) {
|
|
@@ -333,8 +333,8 @@ function bridgeDeclaration(name, specifier) {
|
|
|
333
333
|
if (specifier === "react-native") {
|
|
334
334
|
return `const ${name} = ${REACT_NATIVE_BRIDGE}; if (!${name}) throw new Error(${notReady("React Native")});`;
|
|
335
335
|
}
|
|
336
|
-
if (
|
|
337
|
-
return `const ${name} = (globalThis.
|
|
336
|
+
if (isPeachKitSpecifier(specifier)) {
|
|
337
|
+
return `const ${name} = (globalThis.__peach_kit_modules && globalThis.__peach_kit_modules[${JSON.stringify(specifier)}]) || (${PACKAGE_BRIDGES} || {})[${JSON.stringify(specifier)}]; if (!${name}) throw new Error(${JSON.stringify(`[peach] replacement package bridge ${specifier} is not ready`)});`;
|
|
338
338
|
}
|
|
339
339
|
if (JSX_RUNTIME_SPECIFIERS.has(specifier)) {
|
|
340
340
|
return `const ${name} = (() => { const react = ${REACT_BRIDGE}; if (!react) throw new Error(${notReady("React")}); const jsx = (type, props, key) => react.createElement(type, key === undefined ? props : { ...props, key }); return { Fragment: react.Fragment, jsx, jsxs: jsx, jsxDEV: jsx }; })();`;
|
|
@@ -462,7 +462,7 @@ function compileReplacementModule(input) {
|
|
|
462
462
|
pointAtLocalModule(specifierToken, specifier);
|
|
463
463
|
continue;
|
|
464
464
|
}
|
|
465
|
-
if (specifier !== "react" && specifier !== "react-native" && !JSX_RUNTIME_SPECIFIERS.has(specifier) && !
|
|
465
|
+
if (specifier !== "react" && specifier !== "react-native" && !JSX_RUNTIME_SPECIFIERS.has(specifier) && !isPeachKitSpecifier(specifier) && !input.runtimePackages.has(specifier)) {
|
|
466
466
|
throw unsupportedSpecifier(specifier);
|
|
467
467
|
}
|
|
468
468
|
const bridge = bindings.length > 0 ? bridgeFor(specifier) : null;
|
|
@@ -133,7 +133,10 @@ internal class PeachComposeMount(
|
|
|
133
133
|
var props by mutableStateOf("")
|
|
134
134
|
// the configuration's night mode, which isSystemInDarkTheme reads
|
|
135
135
|
var night by mutableStateOf(night)
|
|
136
|
-
|
|
136
|
+
// the bounds the host last measured or laid the view out in. like a
|
|
137
|
+
// ComposeView before its parent measures it, the content has none and
|
|
138
|
+
// lays out nothing until the first arrive
|
|
139
|
+
var constraints: Constraints? = null
|
|
137
140
|
set(value) { if (field != value) { field = value; needsMeasure = true } }
|
|
138
141
|
private val events = mutableListOf<JSONObject>()
|
|
139
142
|
// state reported once a frame, read when the frame draws: like the decor,
|
|
@@ -177,7 +180,7 @@ internal class PeachComposeMount(
|
|
|
177
180
|
}
|
|
178
181
|
|
|
179
182
|
fun measure(): IntSize {
|
|
180
|
-
val measured = scene.measureContent(constraints)
|
|
183
|
+
val measured = scene.measureContent(checkNotNull(constraints))
|
|
181
184
|
scene.size = measured
|
|
182
185
|
platform.size = measured
|
|
183
186
|
measuredSize = measured
|
|
@@ -188,7 +191,7 @@ internal class PeachComposeMount(
|
|
|
188
191
|
fun frame(nanos: Long): String {
|
|
189
192
|
Snapshot.sendApplyNotifications()
|
|
190
193
|
if (clockRunning()) recomposer.performFrame(nanos)
|
|
191
|
-
val measured = if (needsMeasure) measure() else measuredSize
|
|
194
|
+
val measured = if (needsMeasure && constraints != null) measure() else measuredSize
|
|
192
195
|
// a zero-size composition still runs its effects and reports its size;
|
|
193
196
|
// its backing surface uses one pixel until layout gives it real bounds.
|
|
194
197
|
val backing = IntSize(measured.width.coerceAtLeast(1), measured.height.coerceAtLeast(1))
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
package dev.contrast.kotlinapp
|
|
2
|
+
|
|
3
|
+
import androidx.compose.foundation.background
|
|
4
|
+
import androidx.compose.foundation.layout.Arrangement
|
|
5
|
+
import androidx.compose.foundation.layout.Box
|
|
6
|
+
import androidx.compose.foundation.layout.Column
|
|
7
|
+
import androidx.compose.foundation.layout.fillMaxSize
|
|
8
|
+
import androidx.compose.foundation.layout.padding
|
|
9
|
+
import androidx.compose.foundation.layout.safeDrawingPadding
|
|
10
|
+
import androidx.compose.material3.AlertDialog
|
|
11
|
+
import androidx.compose.material3.Button
|
|
12
|
+
import androidx.compose.material3.DropdownMenu
|
|
13
|
+
import androidx.compose.material3.DropdownMenuItem
|
|
14
|
+
import androidx.compose.material3.Text
|
|
15
|
+
import androidx.compose.material3.TextButton
|
|
16
|
+
import androidx.compose.runtime.Composable
|
|
17
|
+
import androidx.compose.runtime.getValue
|
|
18
|
+
import androidx.compose.runtime.mutableStateOf
|
|
19
|
+
import androidx.compose.runtime.remember
|
|
20
|
+
import androidx.compose.runtime.setValue
|
|
21
|
+
import androidx.compose.ui.Modifier
|
|
22
|
+
import androidx.compose.ui.graphics.Color
|
|
23
|
+
import androidx.compose.ui.unit.dp
|
|
24
|
+
|
|
25
|
+
// an alert dialog and a dropdown menu, each opened by a button and closed by
|
|
26
|
+
// one of its own items
|
|
27
|
+
@Composable
|
|
28
|
+
fun Overlays() {
|
|
29
|
+
var dialog by remember { mutableStateOf(false) }
|
|
30
|
+
var menu by remember { mutableStateOf(false) }
|
|
31
|
+
var result by remember { mutableStateOf("none") }
|
|
32
|
+
Column(
|
|
33
|
+
Modifier.fillMaxSize().background(Color(0xFFE8EEF8)).safeDrawingPadding().padding(16.dp),
|
|
34
|
+
verticalArrangement = Arrangement.spacedBy(12.dp),
|
|
35
|
+
) {
|
|
36
|
+
Text("Result: $result")
|
|
37
|
+
Button(onClick = { dialog = true }) { Text("Open dialog") }
|
|
38
|
+
Box {
|
|
39
|
+
Button(onClick = { menu = true }) { Text("Open menu") }
|
|
40
|
+
DropdownMenu(expanded = menu, onDismissRequest = { menu = false }) {
|
|
41
|
+
listOf("First", "Second", "Third").forEach { item ->
|
|
42
|
+
DropdownMenuItem(text = { Text(item) }, onClick = { result = item; menu = false })
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
if (dialog) {
|
|
48
|
+
AlertDialog(
|
|
49
|
+
onDismissRequest = { dialog = false },
|
|
50
|
+
title = { Text("Discard draft?") },
|
|
51
|
+
text = { Text("The draft will be deleted.") },
|
|
52
|
+
confirmButton = { TextButton(onClick = { result = "confirmed"; dialog = false }) { Text("Discard") } },
|
|
53
|
+
dismissButton = { TextButton(onClick = { dialog = false }) { Text("Keep") } },
|
|
54
|
+
)
|
|
55
|
+
}
|
|
56
|
+
}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
package dev.contrast.kotlinapp
|
|
2
|
+
|
|
3
|
+
import androidx.compose.foundation.background
|
|
4
|
+
import androidx.compose.foundation.layout.Column
|
|
5
|
+
import androidx.compose.foundation.layout.fillMaxSize
|
|
6
|
+
import androidx.compose.foundation.layout.fillMaxWidth
|
|
7
|
+
import androidx.compose.foundation.layout.height
|
|
8
|
+
import androidx.compose.foundation.layout.padding
|
|
9
|
+
import androidx.compose.foundation.layout.safeDrawingPadding
|
|
10
|
+
import androidx.compose.foundation.lazy.LazyColumn
|
|
11
|
+
import androidx.compose.foundation.lazy.rememberLazyListState
|
|
12
|
+
import androidx.compose.material3.HorizontalDivider
|
|
13
|
+
import androidx.compose.material3.Text
|
|
14
|
+
import androidx.compose.runtime.Composable
|
|
15
|
+
import androidx.compose.runtime.getValue
|
|
16
|
+
import androidx.compose.runtime.mutableStateOf
|
|
17
|
+
import androidx.compose.runtime.remember
|
|
18
|
+
import androidx.compose.runtime.setValue
|
|
19
|
+
import androidx.compose.ui.Modifier
|
|
20
|
+
import androidx.compose.ui.geometry.Offset
|
|
21
|
+
import androidx.compose.ui.input.nestedscroll.NestedScrollConnection
|
|
22
|
+
import androidx.compose.ui.input.nestedscroll.NestedScrollSource
|
|
23
|
+
import androidx.compose.ui.input.nestedscroll.nestedScroll
|
|
24
|
+
import androidx.compose.ui.unit.Velocity
|
|
25
|
+
import kotlin.math.roundToInt
|
|
26
|
+
import androidx.compose.ui.graphics.Color
|
|
27
|
+
import androidx.compose.ui.unit.dp
|
|
28
|
+
|
|
29
|
+
// a lazy list a drag scrolls and flings: the header reports the drag and the
|
|
30
|
+
// fling velocity the list received, then where it settled
|
|
31
|
+
@Composable
|
|
32
|
+
fun Scroll() {
|
|
33
|
+
val state = rememberLazyListState()
|
|
34
|
+
var moved by remember { mutableStateOf(false) }
|
|
35
|
+
if (state.isScrollInProgress) moved = true
|
|
36
|
+
val settled = moved && !state.isScrollInProgress
|
|
37
|
+
var dragged by remember { mutableStateOf(0f) }
|
|
38
|
+
var velocity by remember { mutableStateOf(0f) }
|
|
39
|
+
val connection = remember {
|
|
40
|
+
object : NestedScrollConnection {
|
|
41
|
+
override fun onPreScroll(available: Offset, source: NestedScrollSource): Offset {
|
|
42
|
+
if (source == NestedScrollSource.UserInput) dragged += available.y
|
|
43
|
+
return Offset.Zero
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
override suspend fun onPreFling(available: Velocity): Velocity {
|
|
47
|
+
velocity = available.y
|
|
48
|
+
return Velocity.Zero
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
Column(Modifier.fillMaxSize().background(Color(0xFFE8EEF8)).safeDrawingPadding()) {
|
|
53
|
+
Text("Scroll: ${if (settled) "settled" else if (moved) "moving" else "idle"}", Modifier.padding(16.dp))
|
|
54
|
+
Text("First: ${state.firstVisibleItemIndex} ${state.firstVisibleItemScrollOffset}", Modifier.padding(horizontal = 16.dp))
|
|
55
|
+
Text("Drag: ${dragged.roundToInt()} velocity ${velocity.roundToInt()}", Modifier.padding(horizontal = 16.dp))
|
|
56
|
+
LazyColumn(Modifier.fillMaxWidth().weight(1f).nestedScroll(connection), state = state) {
|
|
57
|
+
items(200) { index ->
|
|
58
|
+
Text("Row $index", Modifier.fillMaxWidth().height(56.dp).padding(16.dp))
|
|
59
|
+
HorizontalDivider()
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
package dev.contrast.kotlinapp
|
|
2
|
+
|
|
3
|
+
import androidx.compose.foundation.background
|
|
4
|
+
import androidx.compose.foundation.layout.Arrangement
|
|
5
|
+
import androidx.compose.foundation.layout.Column
|
|
6
|
+
import androidx.compose.foundation.layout.fillMaxSize
|
|
7
|
+
import androidx.compose.foundation.layout.padding
|
|
8
|
+
import androidx.compose.foundation.layout.safeDrawingPadding
|
|
9
|
+
import androidx.compose.material3.Button
|
|
10
|
+
import androidx.compose.material3.ExperimentalMaterial3Api
|
|
11
|
+
import androidx.compose.material3.ModalBottomSheet
|
|
12
|
+
import androidx.compose.material3.Text
|
|
13
|
+
import androidx.compose.runtime.Composable
|
|
14
|
+
import androidx.compose.runtime.getValue
|
|
15
|
+
import androidx.compose.runtime.mutableStateOf
|
|
16
|
+
import androidx.compose.runtime.remember
|
|
17
|
+
import androidx.compose.runtime.setValue
|
|
18
|
+
import androidx.compose.ui.Modifier
|
|
19
|
+
import androidx.compose.ui.graphics.Color
|
|
20
|
+
import androidx.compose.ui.unit.dp
|
|
21
|
+
|
|
22
|
+
// a modal bottom sheet opened by a button and closed by one inside it
|
|
23
|
+
@OptIn(ExperimentalMaterial3Api::class)
|
|
24
|
+
@Composable
|
|
25
|
+
fun Sheet() {
|
|
26
|
+
var open by remember { mutableStateOf(false) }
|
|
27
|
+
var closed by remember { mutableStateOf(0) }
|
|
28
|
+
Column(
|
|
29
|
+
Modifier.fillMaxSize().background(Color(0xFFE8EEF8)).safeDrawingPadding().padding(16.dp),
|
|
30
|
+
verticalArrangement = Arrangement.spacedBy(12.dp),
|
|
31
|
+
) {
|
|
32
|
+
Text("Closed: $closed")
|
|
33
|
+
Button(onClick = { open = true }) { Text("Open sheet") }
|
|
34
|
+
}
|
|
35
|
+
if (open) {
|
|
36
|
+
ModalBottomSheet(onDismissRequest = { open = false; closed += 1 }) {
|
|
37
|
+
Column(Modifier.padding(24.dp), verticalArrangement = Arrangement.spacedBy(12.dp)) {
|
|
38
|
+
Text("Sheet body")
|
|
39
|
+
Button(onClick = { open = false; closed += 1 }) { Text("Close sheet") }
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
package dev.contrast.kotlinapp
|
|
2
|
+
|
|
3
|
+
import androidx.compose.foundation.background
|
|
4
|
+
import androidx.compose.foundation.layout.Box
|
|
5
|
+
import androidx.compose.foundation.layout.padding
|
|
6
|
+
import androidx.compose.foundation.layout.size
|
|
7
|
+
import androidx.compose.foundation.shape.CircleShape
|
|
8
|
+
import androidx.compose.material3.ExperimentalMaterial3Api
|
|
9
|
+
import androidx.compose.material3.NavigationBar
|
|
10
|
+
import androidx.compose.material3.NavigationBarItem
|
|
11
|
+
import androidx.compose.material3.Scaffold
|
|
12
|
+
import androidx.compose.material3.Text
|
|
13
|
+
import androidx.compose.material3.TopAppBar
|
|
14
|
+
import androidx.compose.runtime.Composable
|
|
15
|
+
import androidx.compose.runtime.getValue
|
|
16
|
+
import androidx.compose.runtime.mutableStateOf
|
|
17
|
+
import androidx.compose.runtime.remember
|
|
18
|
+
import androidx.compose.runtime.setValue
|
|
19
|
+
import androidx.compose.ui.Modifier
|
|
20
|
+
import androidx.compose.ui.graphics.Color
|
|
21
|
+
import androidx.compose.ui.unit.dp
|
|
22
|
+
|
|
23
|
+
// a scaffold with a top app bar and a navigation bar whose tabs swap the body
|
|
24
|
+
@OptIn(ExperimentalMaterial3Api::class)
|
|
25
|
+
@Composable
|
|
26
|
+
fun Tabs() {
|
|
27
|
+
val tabs = listOf("Home", "Search", "Profile")
|
|
28
|
+
var selected by remember { mutableStateOf(0) }
|
|
29
|
+
Scaffold(
|
|
30
|
+
containerColor = Color(0xFFE8EEF8),
|
|
31
|
+
topBar = { TopAppBar(title = { Text("Inbox") }) },
|
|
32
|
+
bottomBar = {
|
|
33
|
+
NavigationBar {
|
|
34
|
+
tabs.forEachIndexed { index, tab ->
|
|
35
|
+
NavigationBarItem(
|
|
36
|
+
selected = selected == index,
|
|
37
|
+
onClick = { selected = index },
|
|
38
|
+
icon = { Box(Modifier.size(24.dp).background(Color(0xFF6750A4), CircleShape)) },
|
|
39
|
+
label = { Text(tab) },
|
|
40
|
+
)
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
},
|
|
44
|
+
) { padding ->
|
|
45
|
+
Text("Tab: ${tabs[selected]}", Modifier.padding(padding).padding(16.dp))
|
|
46
|
+
}
|
|
47
|
+
}
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
package dev.contrast.kotlinapp
|
|
2
|
+
|
|
3
|
+
import androidx.compose.foundation.background
|
|
4
|
+
import androidx.compose.foundation.isSystemInDarkTheme
|
|
5
|
+
import androidx.compose.foundation.layout.Arrangement
|
|
6
|
+
import androidx.compose.foundation.layout.Column
|
|
7
|
+
import androidx.compose.foundation.layout.Row
|
|
8
|
+
import androidx.compose.foundation.layout.fillMaxSize
|
|
9
|
+
import androidx.compose.foundation.layout.fillMaxWidth
|
|
10
|
+
import androidx.compose.foundation.layout.padding
|
|
11
|
+
import androidx.compose.foundation.layout.safeDrawingPadding
|
|
12
|
+
import androidx.compose.material3.Button
|
|
13
|
+
import androidx.compose.material3.Card
|
|
14
|
+
import androidx.compose.material3.Checkbox
|
|
15
|
+
import androidx.compose.material3.FilterChip
|
|
16
|
+
import androidx.compose.material3.HorizontalDivider
|
|
17
|
+
import androidx.compose.material3.LinearProgressIndicator
|
|
18
|
+
import androidx.compose.material3.MaterialTheme
|
|
19
|
+
import androidx.compose.material3.OutlinedButton
|
|
20
|
+
import androidx.compose.material3.RadioButton
|
|
21
|
+
import androidx.compose.material3.Slider
|
|
22
|
+
import androidx.compose.material3.Switch
|
|
23
|
+
import androidx.compose.material3.Text
|
|
24
|
+
import androidx.compose.material3.darkColorScheme
|
|
25
|
+
import androidx.compose.material3.lightColorScheme
|
|
26
|
+
import androidx.compose.runtime.Composable
|
|
27
|
+
import androidx.compose.runtime.getValue
|
|
28
|
+
import androidx.compose.runtime.mutableStateOf
|
|
29
|
+
import androidx.compose.runtime.remember
|
|
30
|
+
import androidx.compose.runtime.setValue
|
|
31
|
+
import androidx.compose.ui.Alignment
|
|
32
|
+
import androidx.compose.ui.Modifier
|
|
33
|
+
import androidx.compose.ui.graphics.Color
|
|
34
|
+
import androidx.compose.ui.unit.dp
|
|
35
|
+
|
|
36
|
+
// material controls in both states: the toggle flips every one at once. the
|
|
37
|
+
// baseline scheme follows the device's dark theme
|
|
38
|
+
@Composable
|
|
39
|
+
fun Widgets() {
|
|
40
|
+
val dark = isSystemInDarkTheme()
|
|
41
|
+
MaterialTheme(colorScheme = if (dark) darkColorScheme() else lightColorScheme()) { Controls(dark) }
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
@Composable
|
|
45
|
+
fun Controls(dark: Boolean) {
|
|
46
|
+
var on by remember { mutableStateOf(false) }
|
|
47
|
+
Column(
|
|
48
|
+
Modifier.fillMaxSize().background(Color(0xFFE8EEF8)).safeDrawingPadding().padding(16.dp),
|
|
49
|
+
verticalArrangement = Arrangement.spacedBy(8.dp),
|
|
50
|
+
) {
|
|
51
|
+
Text("Selection: $on${if (dark) " dark" else ""}")
|
|
52
|
+
Row(verticalAlignment = Alignment.CenterVertically, horizontalArrangement = Arrangement.spacedBy(8.dp)) {
|
|
53
|
+
Switch(checked = on, onCheckedChange = { on = it })
|
|
54
|
+
Checkbox(checked = on, onCheckedChange = { on = it })
|
|
55
|
+
RadioButton(selected = on, onClick = { on = !on })
|
|
56
|
+
FilterChip(selected = on, onClick = { on = !on }, label = { Text("Chip") })
|
|
57
|
+
}
|
|
58
|
+
Slider(value = if (on) 0.8f else 0.3f, onValueChange = {})
|
|
59
|
+
LinearProgressIndicator(progress = { if (on) 0.9f else 0.4f }, Modifier.fillMaxWidth())
|
|
60
|
+
Card(Modifier.fillMaxWidth()) { Text("Card body", Modifier.padding(16.dp)) }
|
|
61
|
+
HorizontalDivider()
|
|
62
|
+
Row(horizontalArrangement = Arrangement.spacedBy(8.dp)) {
|
|
63
|
+
Button(onClick = { on = !on }) { Text("Toggle") }
|
|
64
|
+
OutlinedButton(onClick = {}, enabled = on) { Text("Outlined") }
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "peachsim",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.665",
|
|
4
4
|
"description": "Vite and Metro plugins, testing drivers, and SDK exports for peach.",
|
|
5
5
|
"author": "Tamagui LLC",
|
|
6
6
|
"license": "SEE LICENSE IN LICENSE",
|
|
@@ -333,7 +333,7 @@
|
|
|
333
333
|
"@peach/skills": "workspace:*",
|
|
334
334
|
"@peach/compat": "workspace:*",
|
|
335
335
|
"@types/ws": "^8.5.13",
|
|
336
|
-
"
|
|
336
|
+
"peach-kit": "workspace:*",
|
|
337
337
|
"peach-cloud-box": "workspace:*",
|
|
338
338
|
"peach-engine": "workspace:*",
|
|
339
339
|
"typescript": "7.0.2",
|
|
@@ -135,7 +135,7 @@ export default defineConfig({
|
|
|
135
135
|
|
|
136
136
|
a `nativeViews` file default-exports a component that receives the native
|
|
137
137
|
view's props and renders what it shows on device, using `react-native` and
|
|
138
|
-
`
|
|
138
|
+
`peach-kit` primitives. a `nativeModules` file exports the module's
|
|
139
139
|
methods, served to `NativeModules` and to Expo's `requireNativeModule`. a
|
|
140
140
|
file may import other project files by relative path, so a large view splits
|
|
141
141
|
into components and helpers. reopen the app to load the change.
|
|
@@ -415,12 +415,8 @@ export function parseSemanticWaitOptions(value: unknown): SemanticWaitOptions {
|
|
|
415
415
|
const type = stringValue(condition.type, 'waitFor.condition.type')
|
|
416
416
|
const timeoutMs = semanticWaitTimeout(input, 'waitFor')
|
|
417
417
|
if (type === 'ready') {
|
|
418
|
-
const stableMs = optionalNumber(condition, 'stableMs', 'waitFor.condition')
|
|
419
|
-
if (stableMs !== undefined && stableMs < 0) {
|
|
420
|
-
validationFailure('waitFor.condition.stableMs', 'must be at least 0')
|
|
421
|
-
}
|
|
422
418
|
return {
|
|
423
|
-
condition: { type
|
|
419
|
+
condition: { type },
|
|
424
420
|
...(timeoutMs === undefined ? {} : { timeoutMs }),
|
|
425
421
|
}
|
|
426
422
|
}
|
package/src/bridge-contract.ts
CHANGED
|
@@ -332,10 +332,7 @@ export interface PerformResult {
|
|
|
332
332
|
// something to look at — those are 1x1 and painted under the whole app, so they
|
|
333
333
|
// are permanently occluded and `present` can never match one.
|
|
334
334
|
export type SemanticWaitCondition =
|
|
335
|
-
|
|
336
|
-
// (SEMANTIC_READY_NODE_STABLE_MS by default). a caller that already waited
|
|
337
|
-
// for the app to settle passes 0 to read the settled tree at once.
|
|
338
|
-
| { type: 'ready'; stableMs?: number }
|
|
335
|
+
| { type: 'ready' }
|
|
339
336
|
| { type: 'present'; selector: SimSemanticResolveSelector }
|
|
340
337
|
| { type: 'mounted'; selector: SimSemanticResolveSelector }
|
|
341
338
|
| { type: 'absent'; selector: SimSemanticResolveSelector }
|
|
@@ -392,6 +389,21 @@ export function readyProbeHasTargetContent(
|
|
|
392
389
|
return probe.liveFrameActive || probe.targets > 0
|
|
393
390
|
}
|
|
394
391
|
|
|
392
|
+
// one probe shows a ready app: it has said so with content behind it, or it
|
|
393
|
+
// paints targets without saying so, and it is neither loading nor errored.
|
|
394
|
+
// `wait ready` also requires the node count to hold still; a caller that has
|
|
395
|
+
// already let the app settle judges a single probe with this alone.
|
|
396
|
+
export function readyProbeIsReady(probe: SemanticReadyProbe): boolean {
|
|
397
|
+
return (
|
|
398
|
+
(probe.flag === true
|
|
399
|
+
? readyProbeHasContent(probe)
|
|
400
|
+
: readyProbeHasTargetContent(probe)) &&
|
|
401
|
+
probe.externalReady !== false &&
|
|
402
|
+
!probe.externalError &&
|
|
403
|
+
!probe.loadingText
|
|
404
|
+
)
|
|
405
|
+
}
|
|
406
|
+
|
|
395
407
|
export interface SemanticWaitResult {
|
|
396
408
|
matched: boolean
|
|
397
409
|
elapsedMs: number
|