peachsim 0.1.1406 → 0.1.1463
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/LICENSE +8 -4
- package/README.md +22 -2
- package/cli/app-fonts.ts +50 -44
- package/cli/bin.ts +5 -4
- package/cli/bridge-diagnostics.ts +5 -0
- package/cli/bridge-flow-runner.ts +21 -13
- package/cli/cloud-client.ts +4 -3
- package/cli/commands/control.ts +155 -123
- package/cli/commands/daemon-mac-app.ts +3 -3
- package/cli/commands/daemon.ts +2 -2
- package/cli/commands/do-chain.ts +9 -2
- package/cli/commands/electron.ts +4 -1
- package/cli/commands/inspect/core.ts +1 -1
- package/cli/commands/inspect/settle.ts +19 -7
- package/cli/commands/inspect/settling.ts +92 -56
- package/cli/commands/inspect/wait-idle.ts +21 -9
- package/cli/commands/inspect/wait-ready.ts +22 -8
- package/cli/commands/inspect.ts +101 -134
- package/cli/commands/setup.ts +26 -16
- package/cli/commands/upgrade.ts +53 -9
- package/cli/desktop-companion.ts +1 -1
- package/cli/drivers/playwright-sim-host.ts +22 -13
- package/cli/drivers/playwright.ts +3 -3
- package/cli/main.ts +14 -5
- package/cli/outbound-endpoints.ts +1 -9
- package/detox/index.ts +6 -1
- package/dist-lib/agent-daemon-client.cjs +1 -1
- package/dist-lib/agent-events.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 +1 -1
- package/dist-lib/bridge-contract-input.mjs +1 -1
- package/dist-lib/bridge-contract.cjs +3 -3
- package/dist-lib/bridge-contract.mjs +3 -3
- package/dist-lib/bundle-channel.cjs +322 -274
- package/dist-lib/capture-contract.cjs +1 -1
- package/dist-lib/capture-contract.mjs +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/config.mjs +1 -1
- package/dist-lib/detox/index.cjs +322 -45
- package/dist-lib/dev-bundle-resolution.cjs +1 -1
- package/dist-lib/expo-swift-bundle.cjs +69 -33
- package/dist-lib/expo-swift-metadata.cjs +1 -1
- package/dist-lib/expo-swift-metadata.mjs +1 -1
- package/dist-lib/home-paths.cjs +1 -1
- package/dist-lib/host/bridge-host.cjs +3650 -3528
- 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 +1 -1
- package/dist-lib/host/websocket-proxy.cjs +1 -1
- package/dist-lib/index.cjs +327 -123
- 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 +10 -5
- 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 +49 -47
- package/dist-lib/native-app-identity-project.cjs +1 -1
- package/dist-lib/native-app-identity-project.mjs +1 -1
- package/dist-lib/native-app-identity.cjs +1 -1
- package/dist-lib/native-app-identity.mjs +1 -1
- package/dist-lib/native-firebase-metadata.cjs +1 -1
- package/dist-lib/native-firebase-metadata.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/scripts/dev-server-scanner.cjs +23 -17
- package/dist-lib/sdk.cjs +99 -61
- package/dist-lib/sdk.mjs +97 -61
- package/dist-lib/skills.cjs +2959 -2780
- package/dist-lib/swift-compile.cjs +26 -12
- package/dist-lib/swift-compile.mjs +26 -12
- package/dist-lib/swift-package.cjs +1 -1
- package/dist-lib/swift-package.mjs +1 -1
- package/dist-lib/swift-submit.cjs +2 -2
- package/dist-lib/swift-submit.mjs +2 -2
- package/dist-lib/swift-toolchain.cjs +1 -1
- package/dist-lib/swift.cjs +1 -1
- package/dist-lib/vite-swift.cjs +66 -31
- package/dist-lib/vite.cjs +1 -1
- package/dist-lib/worklet-executable.cjs +1 -1
- package/dist-lib/worklet-executable.mjs +1 -1
- package/kotlin/AppSources/android/view/Window.kt +2 -0
- package/kotlin/AppSources/dev/peach/app/PeachApp.kt +1 -1
- package/kotlin/ComposeSources/PeachComposeHost.kt +35 -9
- package/kotlin/ExpoComposeSources/dev/peach/expo/PeachExpoCompose.kt +2 -2
- package/kotlin/ExpoSources/dev/peach/expo/PeachExpoRuntime.kt +9 -6
- package/kotlin/ExpoSources/dev/peach/expo/PeachExpoTypes.kt +4 -4
- package/kotlin/ExpoSources/expo/modules/kotlin/modules/Module.kt +2 -2
- package/kotlin/ExpoSources/expo/modules/kotlin/viewevent/ViewEvent.kt +22 -8
- package/kotlin/ExpoSources/expo/modules/kotlin/views/ExpoView.kt +0 -3
- package/kotlin/ExpoSources/expo/modules/kotlin/views/ViewDefinitionBuilder.kt +10 -9
- package/kotlin/ExpoSources/expo/modules/kotlin/views/ViewProps.kt +6 -5
- package/kotlin/fixtures/import-drawing/Drawing.d.kt.ts +7 -0
- package/kotlin/fixtures/import-drawing/Drawing.kt +101 -0
- package/kotlin/fixtures/import-drawing/ImportDrawing.native.tsx +18 -0
- package/kotlin/fixtures/import-drawing/README.md +34 -0
- package/kotlin/fixtures/import-drawing/app/_layout.tsx +4 -0
- package/kotlin/fixtures/import-drawing/app/index+spa.tsx +1 -0
- package/kotlin/fixtures/import-drawing/package.json +23 -0
- package/kotlin/fixtures/import-drawing/tsconfig.json +20 -0
- package/kotlin/fixtures/import-drawing/vite.config.ts +20 -0
- package/kotlin/fixtures/import-scroll/ImportNested.native.tsx +10 -0
- package/kotlin/fixtures/import-scroll/Nested.d.kt.ts +4 -0
- package/kotlin/fixtures/import-scroll/README.md +15 -0
- package/kotlin/fixtures/import-scroll/app/nested+spa.tsx +1 -0
- package/kotlin/fixtures/import-values/ImportValues.native.tsx +26 -0
- package/kotlin/fixtures/import-values/README.md +6 -1
- package/kotlin/fixtures/import-values/Values.d.kt.ts +4 -0
- package/kotlin/fixtures/import-values/Values.kt +18 -0
- package/kotlin/tools/expo-types.main.kts +17 -5
- package/package.json +2 -1
- package/scripts/dev-server-scanner.ts +33 -23
- package/src/assetCatalogContract.ts +7 -0
- package/src/bridge-contract.ts +21 -3
- package/src/connect.ts +18 -5
- package/src/desktop-host-delivery.ts +33 -3
- package/src/expo-swift-host.ts +6 -3
- package/src/host/bridge-host.ts +2 -0
- package/src/host/fetch-proxy-transport.ts +8 -0
- package/src/kotlin-host.ts +11 -5
- package/src/native-firebase-config.ts +95 -54
- package/src/process-output.ts +16 -0
- package/src/sdk.ts +6 -1
- package/src/sim-client.ts +6 -1
- package/src/swift-compile.ts +36 -15
- package/src/swift-distribution.lock.json +8 -8
- package/src/swift-standin-emit.ts +10 -1
- package/src/swift-standins.ts +21 -7
- package/src/swift-submit.ts +6 -1
- /package/kotlin/fixtures/{kotlin-app-variants → import-scroll}/Nested.kt +0 -0
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
package dev.peach.expo
|
|
4
4
|
|
|
5
5
|
import android.content.Context
|
|
6
|
+
import android.view.View
|
|
6
7
|
import expo.modules.kotlin.AppContext
|
|
7
8
|
import expo.modules.kotlin.exception.CodedException
|
|
8
9
|
import expo.modules.kotlin.exception.EnumNoSuchValueException
|
|
@@ -13,7 +14,6 @@ import expo.modules.kotlin.exception.exceptionDecorator
|
|
|
13
14
|
import expo.modules.kotlin.types.AnyType
|
|
14
15
|
import expo.modules.kotlin.types.TypeCastException
|
|
15
16
|
import expo.modules.kotlin.views.AnyViewProp
|
|
16
|
-
import expo.modules.kotlin.views.ExpoView
|
|
17
17
|
import kotlin.reflect.KClass
|
|
18
18
|
import kotlin.reflect.KType
|
|
19
19
|
import kotlin.reflect.typeOf
|
|
@@ -38,9 +38,9 @@ object PeachExpoTypes {
|
|
|
38
38
|
|
|
39
39
|
private class Frame(val type: KClass<*>?, val name: String, val values: Map<*, *>?)
|
|
40
40
|
|
|
41
|
-
class ViewType(val create: (Context, AppContext) ->
|
|
41
|
+
class ViewType(val create: (Context, AppContext) -> View, val props: List<AnyViewProp>)
|
|
42
42
|
|
|
43
|
-
private class ViewClass(val props: KClass<*>?, val create: (Context, AppContext) ->
|
|
43
|
+
private class ViewClass(val props: KClass<*>?, val create: (Context, AppContext) -> View)
|
|
44
44
|
|
|
45
45
|
private val views = HashMap<KClass<*>, ViewClass>()
|
|
46
46
|
private val composeProps = HashMap<KClass<*>, List<AnyViewProp>>()
|
|
@@ -49,7 +49,7 @@ object PeachExpoTypes {
|
|
|
49
49
|
// records under construction; a frame with no values builds defaults
|
|
50
50
|
private val frames = ArrayList<Frame>()
|
|
51
51
|
|
|
52
|
-
fun <V :
|
|
52
|
+
fun <V : View> view(type: KClass<V>, props: KClass<*>?, create: (Context, AppContext) -> V) {
|
|
53
53
|
views[type] = ViewClass(props, create)
|
|
54
54
|
}
|
|
55
55
|
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
package expo.modules.kotlin.modules
|
|
2
2
|
|
|
3
|
+
import android.view.View
|
|
3
4
|
import expo.modules.kotlin.AppContext
|
|
4
5
|
import expo.modules.kotlin.AppContextProvider
|
|
5
6
|
import expo.modules.kotlin.classcomponent.ClassComponentBuilder
|
|
@@ -7,7 +8,6 @@ import expo.modules.kotlin.classcomponent.ClassDefinitionData
|
|
|
7
8
|
import expo.modules.kotlin.objects.ObjectDefinitionBuilder
|
|
8
9
|
import expo.modules.kotlin.objects.ObjectDefinitionData
|
|
9
10
|
import expo.modules.kotlin.sharedobjects.SharedObject
|
|
10
|
-
import expo.modules.kotlin.views.ExpoView
|
|
11
11
|
import expo.modules.kotlin.views.ViewDefinitionBuilder
|
|
12
12
|
import expo.modules.kotlin.views.ViewManagerDefinition
|
|
13
13
|
import kotlin.reflect.KClass
|
|
@@ -57,7 +57,7 @@ class ModuleDefinitionBuilder(@PublishedApi internal val module: Module? = null)
|
|
|
57
57
|
|
|
58
58
|
// a compose view also takes its props class's MutableState props, which
|
|
59
59
|
// upstream unwraps by reflection and the host's generated table names here
|
|
60
|
-
inline fun <reified T :
|
|
60
|
+
inline fun <reified T : View> View(viewClass: KClass<T>, body: ViewDefinitionBuilder<T>.() -> Unit = {}) {
|
|
61
61
|
val view = dev.peach.expo.PeachExpoTypes.viewType(viewClass)
|
|
62
62
|
val builder = ViewDefinitionBuilder(viewClass, typeOf<T>())
|
|
63
63
|
for (prop in view.props) builder.props[prop.name] = prop
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
package expo.modules.kotlin.viewevent
|
|
2
2
|
|
|
3
3
|
import android.util.Log
|
|
4
|
-
import
|
|
4
|
+
import android.view.View
|
|
5
5
|
import kotlin.reflect.KProperty
|
|
6
6
|
|
|
7
7
|
typealias CoalescingKey<T> = (event: T) -> Short
|
|
@@ -14,14 +14,14 @@ fun interface ViewEventCallback<T> {
|
|
|
14
14
|
// reaches JavaScript as { payload }, as convertEventBody wraps it
|
|
15
15
|
open class ViewEvent<T>(
|
|
16
16
|
private val name: String,
|
|
17
|
-
private val view:
|
|
17
|
+
private val view: View,
|
|
18
18
|
private val coalescingKey: CoalescingKey<T>?,
|
|
19
19
|
) : ViewEventCallback<T> {
|
|
20
20
|
private var isValidated = false
|
|
21
21
|
|
|
22
22
|
override operator fun invoke(arg: T) {
|
|
23
23
|
if (!isValidated) {
|
|
24
|
-
if (name !in
|
|
24
|
+
if (name !in ViewEventRegistry.callbacks(view)) {
|
|
25
25
|
Log.w("ExpoModulesCore", "⚠️ Event $name wasn't exported from ${view::class}")
|
|
26
26
|
return
|
|
27
27
|
}
|
|
@@ -32,23 +32,37 @@ open class ViewEvent<T>(
|
|
|
32
32
|
is Map<*, *> -> arg
|
|
33
33
|
else -> mapOf("payload" to arg)
|
|
34
34
|
}
|
|
35
|
-
|
|
35
|
+
ViewEventRegistry.emit(view, name, body)
|
|
36
36
|
}
|
|
37
37
|
}
|
|
38
38
|
|
|
39
|
-
class ViewEventDelegate<T>(private val view:
|
|
39
|
+
class ViewEventDelegate<T>(private val view: View, private val coalescingKey: CoalescingKey<T>?) {
|
|
40
40
|
fun getValue(property: KProperty<*>): ViewEventCallback<T> = ViewEvent(property.name, view, coalescingKey)
|
|
41
41
|
|
|
42
|
-
operator fun getValue(thisRef:
|
|
42
|
+
operator fun getValue(thisRef: View, property: KProperty<*>): ViewEventCallback<T> = getValue(property)
|
|
43
43
|
}
|
|
44
44
|
|
|
45
45
|
@Suppress("NOTHING_TO_INLINE")
|
|
46
46
|
inline operator fun <T> ViewEventDelegate<T>.getValue(thisObj: Any?, property: KProperty<*>): ViewEventCallback<T> = getValue(property)
|
|
47
47
|
|
|
48
48
|
@Suppress("FunctionName")
|
|
49
|
-
inline fun <reified T>
|
|
49
|
+
inline fun <reified T> View.EventDispatcher(noinline coalescingKey: CoalescingKey<T>? = null): ViewEventDelegate<T> =
|
|
50
50
|
ViewEventDelegate(this, coalescingKey)
|
|
51
51
|
|
|
52
52
|
@Suppress("FunctionName")
|
|
53
|
-
fun
|
|
53
|
+
fun View.EventDispatcher(coalescingKey: CoalescingKey<Map<String, Any>>? = null): ViewEventDelegate<Map<String, Any>> =
|
|
54
54
|
ViewEventDelegate(this, coalescingKey)
|
|
55
|
+
|
|
56
|
+
// the runtime binds every Android view, including views outside the ExpoView hierarchy.
|
|
57
|
+
internal object ViewEventRegistry {
|
|
58
|
+
private class Binding(val callbacks: List<String>, val emit: (String, Any?) -> Unit)
|
|
59
|
+
private val bindings = HashMap<View, Binding>()
|
|
60
|
+
|
|
61
|
+
fun bind(view: View, callbacks: List<String>, emit: (String, Any?) -> Unit) {
|
|
62
|
+
bindings[view] = Binding(callbacks, emit)
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
fun unbind(view: View) { bindings.remove(view) }
|
|
66
|
+
fun callbacks(view: View): List<String> = bindings[view]?.callbacks ?: emptyList()
|
|
67
|
+
fun emit(view: View, name: String, body: Any?) { bindings[view]?.emit?.invoke(name, body) }
|
|
68
|
+
}
|
|
@@ -17,7 +17,4 @@ abstract class ExpoView(context: Context, val appContext: AppContext) : LinearLa
|
|
|
17
17
|
canvas.restoreToCount(checkpoint)
|
|
18
18
|
}
|
|
19
19
|
|
|
20
|
-
// the runtime's sink for this view's events, set when the view is created
|
|
21
|
-
internal var emitter: ((name: String, body: Any?) -> Unit)? = null
|
|
22
|
-
internal var callbacks: List<String> = emptyList()
|
|
23
20
|
}
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
package expo.modules.kotlin.views
|
|
4
4
|
|
|
5
5
|
import android.content.Context
|
|
6
|
+
import android.view.View
|
|
6
7
|
import expo.modules.kotlin.AppContext
|
|
7
8
|
import expo.modules.kotlin.functions.AsyncFunctionComponent
|
|
8
9
|
import expo.modules.kotlin.objects.ObjectDefinitionBuilder
|
|
@@ -13,26 +14,26 @@ import kotlin.reflect.KType
|
|
|
13
14
|
class ViewManagerDefinition(
|
|
14
15
|
val name: String,
|
|
15
16
|
internal val viewClass: KClass<*>,
|
|
16
|
-
internal val createView: (Context, AppContext) ->
|
|
17
|
+
internal val createView: (Context, AppContext) -> View,
|
|
17
18
|
internal val props: Map<String, AnyViewProp>,
|
|
18
19
|
internal val callbacks: List<String>,
|
|
19
20
|
internal val asyncFunctions: Map<String, AsyncFunctionComponent>,
|
|
20
|
-
internal val onViewDestroys: ((
|
|
21
|
-
internal val onViewDidUpdateProps: ((
|
|
21
|
+
internal val onViewDestroys: ((View) -> Unit)?,
|
|
22
|
+
internal val onViewDidUpdateProps: ((View) -> Unit)?,
|
|
22
23
|
)
|
|
23
24
|
|
|
24
25
|
// the view's async functions take the view as their first argument when they
|
|
25
26
|
// name its type, as upstream's do; their arities come from ObjectDefinitionBuilder
|
|
26
|
-
class ViewDefinitionBuilder<T :
|
|
27
|
+
class ViewDefinitionBuilder<T : View>(
|
|
27
28
|
@PublishedApi internal val viewClass: KClass<T>,
|
|
28
29
|
@PublishedApi internal val viewType: KType,
|
|
29
30
|
) : ObjectDefinitionBuilder() {
|
|
30
31
|
@PublishedApi internal var name: String = requireNotNull(viewClass.simpleName)
|
|
31
32
|
@PublishedApi internal val props = mutableMapOf<String, AnyViewProp>()
|
|
32
|
-
@PublishedApi internal var onViewDestroys: ((
|
|
33
|
-
@PublishedApi internal var onViewDidUpdateProps: ((
|
|
33
|
+
@PublishedApi internal var onViewDestroys: ((View) -> Unit)? = null
|
|
34
|
+
@PublishedApi internal var onViewDidUpdateProps: ((View) -> Unit)? = null
|
|
34
35
|
|
|
35
|
-
fun build(createView: (Context, AppContext) ->
|
|
36
|
+
fun build(createView: (Context, AppContext) -> View): ViewManagerDefinition {
|
|
36
37
|
val functions = asyncFunctions + asyncFunctionBuilders.mapValues { (_, builder) -> builder.build() }
|
|
37
38
|
for (function in functions.values) {
|
|
38
39
|
function.ownerType = viewType
|
|
@@ -56,13 +57,13 @@ class ViewDefinitionBuilder<T : ExpoView>(
|
|
|
56
57
|
inline fun <reified PropType> Prop(name: String, noinline body: (view: T, prop: PropType) -> Unit): ConcreteViewProp<T, PropType> =
|
|
57
58
|
ConcreteViewProp(name, toAnyType<PropType>(), body).also { props[name] = it }
|
|
58
59
|
|
|
59
|
-
inline fun <reified ViewType :
|
|
60
|
+
inline fun <reified ViewType : View, reified PropType> Prop(
|
|
60
61
|
name: String,
|
|
61
62
|
noinline body: (view: ViewType, prop: PropType) -> Unit,
|
|
62
63
|
): ConcreteViewProp<ViewType, PropType> =
|
|
63
64
|
ConcreteViewProp(name, toAnyType<PropType>(), body).also { props[name] = it }
|
|
64
65
|
|
|
65
|
-
inline fun <reified ViewType :
|
|
66
|
+
inline fun <reified ViewType : View, reified PropType> Prop(
|
|
66
67
|
name: String,
|
|
67
68
|
defaultValue: PropType,
|
|
68
69
|
noinline body: (view: ViewType, prop: PropType) -> Unit,
|
|
@@ -2,33 +2,34 @@
|
|
|
2
2
|
|
|
3
3
|
package expo.modules.kotlin.views
|
|
4
4
|
|
|
5
|
+
import android.view.View
|
|
5
6
|
import expo.modules.kotlin.exception.PropSetException
|
|
6
7
|
import expo.modules.kotlin.exception.exceptionDecorator
|
|
7
8
|
import expo.modules.kotlin.types.AnyType
|
|
8
9
|
|
|
9
10
|
abstract class AnyViewProp(val name: String, internal val type: AnyType) {
|
|
10
|
-
abstract fun set(prop: Any?, onView:
|
|
11
|
+
abstract fun set(prop: Any?, onView: View)
|
|
11
12
|
}
|
|
12
13
|
|
|
13
|
-
open class ConcreteViewProp<ViewType :
|
|
14
|
+
open class ConcreteViewProp<ViewType : View, PropType>(
|
|
14
15
|
name: String,
|
|
15
16
|
propType: AnyType,
|
|
16
17
|
protected val setter: (view: ViewType, prop: PropType) -> Unit,
|
|
17
18
|
) : AnyViewProp(name, propType) {
|
|
18
|
-
override fun set(prop: Any?, onView:
|
|
19
|
+
override fun set(prop: Any?, onView: View) {
|
|
19
20
|
exceptionDecorator({ PropSetException(name, onView::class, it) }) {
|
|
20
21
|
setter(onView as ViewType, type.convert(prop) as PropType)
|
|
21
22
|
}
|
|
22
23
|
}
|
|
23
24
|
}
|
|
24
25
|
|
|
25
|
-
class ConcreteViewPropWithDefault<ViewType :
|
|
26
|
+
class ConcreteViewPropWithDefault<ViewType : View, PropType>(
|
|
26
27
|
name: String,
|
|
27
28
|
propType: AnyType,
|
|
28
29
|
setter: (view: ViewType, prop: PropType) -> Unit,
|
|
29
30
|
private val defaultValue: PropType,
|
|
30
31
|
) : ConcreteViewProp<ViewType, PropType>(name, propType, setter) {
|
|
31
|
-
override fun set(prop: Any?, onView:
|
|
32
|
+
override fun set(prop: Any?, onView: View) {
|
|
32
33
|
if (prop == null) setter(onView as ViewType, defaultValue)
|
|
33
34
|
else super.set(prop, onView)
|
|
34
35
|
}
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
package dev.contrast.kotlindrawing
|
|
2
|
+
|
|
3
|
+
import androidx.compose.foundation.Canvas
|
|
4
|
+
import androidx.compose.foundation.background
|
|
5
|
+
import androidx.compose.foundation.gestures.detectDragGestures
|
|
6
|
+
import androidx.compose.foundation.gestures.detectTapGestures
|
|
7
|
+
import androidx.compose.foundation.layout.Arrangement
|
|
8
|
+
import androidx.compose.foundation.layout.Box
|
|
9
|
+
import androidx.compose.foundation.layout.Column
|
|
10
|
+
import androidx.compose.foundation.layout.fillMaxSize
|
|
11
|
+
import androidx.compose.foundation.layout.padding
|
|
12
|
+
import androidx.compose.foundation.layout.size
|
|
13
|
+
import androidx.compose.material3.Button
|
|
14
|
+
import androidx.compose.material3.MaterialTheme
|
|
15
|
+
import androidx.compose.material3.Surface
|
|
16
|
+
import androidx.compose.material3.Text
|
|
17
|
+
import androidx.compose.material3.lightColorScheme
|
|
18
|
+
import androidx.compose.runtime.Composable
|
|
19
|
+
import androidx.compose.runtime.getValue
|
|
20
|
+
import androidx.compose.runtime.mutableStateOf
|
|
21
|
+
import androidx.compose.runtime.remember
|
|
22
|
+
import androidx.compose.runtime.setValue
|
|
23
|
+
import androidx.compose.ui.Modifier
|
|
24
|
+
import androidx.compose.ui.geometry.Offset
|
|
25
|
+
import androidx.compose.ui.geometry.Size
|
|
26
|
+
import androidx.compose.ui.graphics.Color
|
|
27
|
+
import androidx.compose.ui.graphics.Path
|
|
28
|
+
import androidx.compose.ui.graphics.graphicsLayer
|
|
29
|
+
import androidx.compose.ui.input.pointer.pointerInput
|
|
30
|
+
import androidx.compose.ui.platform.LocalDensity
|
|
31
|
+
import androidx.compose.ui.platform.testTag
|
|
32
|
+
import androidx.compose.ui.semantics.contentDescription
|
|
33
|
+
import androidx.compose.ui.semantics.semantics
|
|
34
|
+
import androidx.compose.ui.unit.dp
|
|
35
|
+
|
|
36
|
+
@Composable
|
|
37
|
+
fun Drawing(title: String, onStep: ((Int) -> Unit)? = null) {
|
|
38
|
+
var count by remember { mutableStateOf(0) }
|
|
39
|
+
var taps by remember { mutableStateOf(0) }
|
|
40
|
+
var moved by remember { mutableStateOf(false) }
|
|
41
|
+
var transformed by remember { mutableStateOf(false) }
|
|
42
|
+
val density = LocalDensity.current.density
|
|
43
|
+
MaterialTheme(colorScheme = lightColorScheme()) {
|
|
44
|
+
Surface(Modifier.fillMaxSize()) {
|
|
45
|
+
Column(Modifier.fillMaxSize().padding(16.dp), verticalArrangement = Arrangement.spacedBy(8.dp)) {
|
|
46
|
+
Text(title, Modifier.testTag("drawing-title"))
|
|
47
|
+
Text("Count: $count", Modifier.testTag("drawing-count"))
|
|
48
|
+
Text("taps=$taps;moved=${if (moved) 1 else 0};transformed=${if (transformed) 1 else 0}", Modifier.testTag("drawing-state"))
|
|
49
|
+
Box(Modifier.size(240.dp, 144.dp).background(Color(0xFFE8EEF8)).testTag("drawing-stage").semantics { contentDescription = "Drawing stage" }) {
|
|
50
|
+
Canvas(
|
|
51
|
+
Modifier.size(240.dp, 144.dp).testTag("drawing-canvas").graphicsLayer {
|
|
52
|
+
rotationZ = if (transformed) 180f else 0f
|
|
53
|
+
scaleX = if (transformed) 0.75f else 1f
|
|
54
|
+
scaleY = if (transformed) 0.75f else 1f
|
|
55
|
+
},
|
|
56
|
+
) {
|
|
57
|
+
drawRect(Color(0xFFFFC107), Offset(16.dp.toPx(), 16.dp.toPx()), Size(64.dp.toPx(), 40.dp.toPx()))
|
|
58
|
+
drawCircle(if (taps % 2 == 1) Color(0xFFE91E63) else Color(0xFF673AB7), 20.dp.toPx(), Offset(if (moved) 152.dp.toPx() else 112.dp.toPx(), if (moved) 96.dp.toPx() else 56.dp.toPx()))
|
|
59
|
+
val path = Path().apply {
|
|
60
|
+
moveTo(184.dp.toPx(), 24.dp.toPx())
|
|
61
|
+
lineTo(220.dp.toPx(), 88.dp.toPx())
|
|
62
|
+
lineTo(164.dp.toPx(), 112.dp.toPx())
|
|
63
|
+
close()
|
|
64
|
+
}
|
|
65
|
+
drawPath(path, Color(0xFF009688))
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
Box(
|
|
69
|
+
Modifier.size(240.dp, 48.dp).background(Color(0xFFD1C4E9)).testTag("drawing-tap").semantics { contentDescription = "Tap drawing" }.pointerInput(Unit) {
|
|
70
|
+
detectTapGestures {
|
|
71
|
+
taps += 1
|
|
72
|
+
count += 1
|
|
73
|
+
onStep?.invoke(count)
|
|
74
|
+
}
|
|
75
|
+
},
|
|
76
|
+
) { Text("Tap drawing", Modifier.padding(8.dp)) }
|
|
77
|
+
Box(
|
|
78
|
+
Modifier.size(240.dp, 64.dp).background(Color(0xFFB2DFDB)).testTag("drawing-drag").semantics { contentDescription = "Drag drawing right" }.pointerInput(Unit) {
|
|
79
|
+
var horizontal = 0f
|
|
80
|
+
detectDragGestures(
|
|
81
|
+
onDragStart = { horizontal = 0f },
|
|
82
|
+
onDragEnd = {
|
|
83
|
+
if (horizontal > 24f * density) {
|
|
84
|
+
moved = true
|
|
85
|
+
count += 1
|
|
86
|
+
onStep?.invoke(count)
|
|
87
|
+
}
|
|
88
|
+
},
|
|
89
|
+
) { change, amount ->
|
|
90
|
+
change.consume()
|
|
91
|
+
horizontal += amount.x
|
|
92
|
+
}
|
|
93
|
+
},
|
|
94
|
+
) { Text("Drag drawing right", Modifier.padding(8.dp)) }
|
|
95
|
+
Button(onClick = { transformed = !transformed; count += 1; onStep?.invoke(count) }, modifier = Modifier.testTag("drawing-transform")) { Text("Transform drawing") }
|
|
96
|
+
Button(onClick = { count += 1000; onStep?.invoke(count) }, enabled = false, modifier = Modifier.testTag("drawing-disabled")) { Text("Disabled drawing") }
|
|
97
|
+
Button(onClick = { taps = 0; moved = false; transformed = false; count += 1; onStep?.invoke(count) }, modifier = Modifier.testTag("drawing-reset")) { Text("Reset drawing") }
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { useState } from 'react'
|
|
2
|
+
import { Text, View, type ViewStyle } from 'react-native'
|
|
3
|
+
import { Drawing } from './Drawing.kt'
|
|
4
|
+
|
|
5
|
+
export default function Index() {
|
|
6
|
+
const [step, setStep] = useState(0)
|
|
7
|
+
const drawingProps = {
|
|
8
|
+
title: `Drawing step ${step}`,
|
|
9
|
+
onStep: setStep,
|
|
10
|
+
style: { width: '100%', height: 680 } satisfies ViewStyle,
|
|
11
|
+
}
|
|
12
|
+
return (
|
|
13
|
+
<View style={{ flex: 1, backgroundColor: '#fff' }}>
|
|
14
|
+
<Drawing {...drawingProps} />
|
|
15
|
+
<Text testID="drawing-react-step">React step: {step}</Text>
|
|
16
|
+
</View>
|
|
17
|
+
)
|
|
18
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
# Imported Compose drawing and pointer input
|
|
2
|
+
|
|
3
|
+
RAN: unchanged Kotlin and React sources pass six real input states in One
|
|
4
|
+
Android, Peach Android, then an actual signed-in Contrast project import.
|
|
5
|
+
The sequence is initial, tap, rightward drag, transform, disabled and reset.
|
|
6
|
+
Compose and React callback counts agree at 0, 1, 2, 3, 3 and 4.
|
|
7
|
+
|
|
8
|
+
[Immutable three-host receipt](https://r2.sootsim.com/kotlin-evidence/import-drawing/5686486071b1f1f109acbf76b033362e401197f62eed6dee9badf1d1ca4e852f/proof.json) binds the source hashes, installed APK,
|
|
9
|
+
compiled Kotlin artifact, paired runtime, full-density lossless WebP captures,
|
|
10
|
+
trees, probes and eight rejected negative controls. Every uploaded object was
|
|
11
|
+
read back and checked by SHA-256.
|
|
12
|
+
|
|
13
|
+
Canvas rectangle, circle and path centroids and bounds match native after
|
|
14
|
+
normalization, within one pixel from each capture density. Every active
|
|
15
|
+
transition changes stage pixels. The disabled tap preserves transformed
|
|
16
|
+
pixels and both counters; reset restores initial pixels. `graphicsLayer`
|
|
17
|
+
rotation of 180 degrees and scale of 0.75 move and shrink the drawing.
|
|
18
|
+
`detectTapGestures` and `detectDragGestures` reach Compose through real input.
|
|
19
|
+
|
|
20
|
+
The live producer now plays the recorded picture at the requested display
|
|
21
|
+
density while keeping native layout and pointer coordinates unchanged.
|
|
22
|
+
Native stage crops are 630x378 at density 2.625; browser crops are 720x432 at
|
|
23
|
+
density 3, captured without resizing. The prior cubic sampling and path-bound
|
|
24
|
+
failure became exact sampling with passing native shape checks.
|
|
25
|
+
|
|
26
|
+
One canary `2.0.0-0.canary.1791785147152` was verified from all 17 family
|
|
27
|
+
tarballs and 11,723 installed files. Its main adoption, dev/prod compile
|
|
28
|
+
catalog qualification and native runtime 105 delivery remain separate under
|
|
29
|
+
the CI owner. Build Android through the normal remote Linux builder and use
|
|
30
|
+
that adopting One family before re-prebuild.
|
|
31
|
+
|
|
32
|
+
This receipt covers debug Android API 37 and settled drawing/input behavior.
|
|
33
|
+
It makes no claim about nested scrolling, 3D projection, fonts, full-frame
|
|
34
|
+
pixel fidelity, animation timing, release binaries or production deployment.
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { default } from '../ImportDrawing.native'
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "peach-kotlin-import-drawing-fixture",
|
|
3
|
+
"private": true,
|
|
4
|
+
"type": "module",
|
|
5
|
+
"targets": [
|
|
6
|
+
"native"
|
|
7
|
+
],
|
|
8
|
+
"dependencies": {
|
|
9
|
+
"@react-navigation/core": "catalog:",
|
|
10
|
+
"@react-navigation/native": "catalog:",
|
|
11
|
+
"one": "catalog:",
|
|
12
|
+
"react": "catalog:",
|
|
13
|
+
"react-native": "catalog:",
|
|
14
|
+
"react-native-reanimated": "catalog:",
|
|
15
|
+
"react-native-screens": "catalog:",
|
|
16
|
+
"react-native-worklets": "catalog:"
|
|
17
|
+
},
|
|
18
|
+
"devDependencies": {
|
|
19
|
+
"@react-native-community/template": "catalog:",
|
|
20
|
+
"peachsim": "workspace:*",
|
|
21
|
+
"vite": "catalog:"
|
|
22
|
+
}
|
|
23
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"paths": {
|
|
4
|
+
"~/*": ["./*"]
|
|
5
|
+
},
|
|
6
|
+
"strict": true,
|
|
7
|
+
"allowArbitraryExtensions": true,
|
|
8
|
+
"rootDir": ".",
|
|
9
|
+
"module": "Preserve",
|
|
10
|
+
// allows react-native style imports without path extensions, for compat with platform-specific files
|
|
11
|
+
"moduleResolution": "Bundler",
|
|
12
|
+
"preserveSymlinks": true,
|
|
13
|
+
"skipLibCheck": true,
|
|
14
|
+
"jsx": "react-jsx",
|
|
15
|
+
"noImplicitAny": false,
|
|
16
|
+
"types": ["node", "react", "vite/client"],
|
|
17
|
+
"lib": ["dom", "esnext"]
|
|
18
|
+
},
|
|
19
|
+
"exclude": ["node_modules", "**/test", "**/dist", "**/types", "**/__tests__"]
|
|
20
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { one } from 'one/vite'
|
|
2
|
+
import { peachPlugin } from 'peachsim/vite'
|
|
3
|
+
export default {
|
|
4
|
+
root: import.meta.dirname,
|
|
5
|
+
plugins: [
|
|
6
|
+
one({
|
|
7
|
+
web: { defaultRenderMode: 'spa' },
|
|
8
|
+
native: {
|
|
9
|
+
bundler: 'vite',
|
|
10
|
+
app: {
|
|
11
|
+
name: 'KotlinDrawing',
|
|
12
|
+
scheme: 'kotlindrawing',
|
|
13
|
+
android: { applicationId: 'dev.contrast.kotlindrawing' },
|
|
14
|
+
},
|
|
15
|
+
},
|
|
16
|
+
}),
|
|
17
|
+
peachPlugin(),
|
|
18
|
+
],
|
|
19
|
+
server: { host: '0.0.0.0' },
|
|
20
|
+
}
|
|
@@ -4,6 +4,21 @@ Run One in this fixture, then open its Android bundle in Peach on Pixel 8.
|
|
|
4
4
|
Build the native Android target on a remote builder. Import the same files in
|
|
5
5
|
Contrast with catalog versions materialized.
|
|
6
6
|
|
|
7
|
+
Open `/nested` for the shared pure/imported nested-scroll fixture. Its upward
|
|
8
|
+
drag collapses the 160 dp header before moving the vertical column, and a
|
|
9
|
+
horizontal drag moves the row inside that column. The text reports physical
|
|
10
|
+
header, column and row offsets.
|
|
11
|
+
|
|
12
|
+
RAN, 2026-10-13: One Android API 37, Peach and a signed-in Contrast import
|
|
13
|
+
match `420/0/0`, `0/84/0` and `0/84/504` after the same 24 delivered native
|
|
14
|
+
MotionEvent samples. The [nested functional receipt](https://r2.sootsim.com/kotlin-evidence/import-nested/e7c3e61f0841e5dd99eae4b1a80f692d5eeec8056fee3d0dd48014f05cc55478/proof.json)
|
|
15
|
+
binds the unchanged host, source, APK, compiled artifacts, input history and
|
|
16
|
+
captures. Five saved-data controls reject changed offsets, missing samples,
|
|
17
|
+
wrong engine/source identities and slow-animation settings. These bounded
|
|
18
|
+
drags establish settled state, without fling, presentation timing or pixel
|
|
19
|
+
fidelity. Contrast preview shell appearance and production delivery remain
|
|
20
|
+
outside this receipt.
|
|
21
|
+
|
|
7
22
|
Tap `scroll-step` to scroll all five containers through their official state
|
|
8
23
|
APIs. The plain containers move to 180 and 80 physical pixels. LazyColumn,
|
|
9
24
|
LazyRow and LazyVerticalGrid move to index/offset pairs 5/8, 4/4 and 8/6.
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { default } from '../ImportNested.native'
|
|
@@ -7,6 +7,31 @@ const input = JSON.stringify({
|
|
|
7
7
|
metadata: { source: 'One' },
|
|
8
8
|
})
|
|
9
9
|
|
|
10
|
+
async function processResults() {
|
|
11
|
+
const results = [
|
|
12
|
+
await Values.current(),
|
|
13
|
+
await Values.delayed('changed'),
|
|
14
|
+
await Values.current(),
|
|
15
|
+
]
|
|
16
|
+
try {
|
|
17
|
+
await Values.throwing()
|
|
18
|
+
throw new Error('sync call unexpectedly succeeded')
|
|
19
|
+
} catch (reason) {
|
|
20
|
+
if (!String(reason).includes('sync failure')) throw reason
|
|
21
|
+
results.push('sync rejected')
|
|
22
|
+
}
|
|
23
|
+
results.push(await Values.current())
|
|
24
|
+
try {
|
|
25
|
+
await Values.delayedThrow()
|
|
26
|
+
throw new Error('suspended call unexpectedly succeeded')
|
|
27
|
+
} catch (reason) {
|
|
28
|
+
if (!String(reason).includes('suspend failure')) throw reason
|
|
29
|
+
results.push('suspend rejected')
|
|
30
|
+
}
|
|
31
|
+
results.push(await Values.delayed('recovered'), await Values.current())
|
|
32
|
+
return results
|
|
33
|
+
}
|
|
34
|
+
|
|
10
35
|
export default function Index() {
|
|
11
36
|
const [result, setResult] = useState('running')
|
|
12
37
|
const [error, setError] = useState('')
|
|
@@ -21,6 +46,7 @@ export default function Index() {
|
|
|
21
46
|
Values.nullableList([]),
|
|
22
47
|
Values.nullableMap({ first: [1, null, 3], empty: [] }),
|
|
23
48
|
Values.nullableMap({}),
|
|
49
|
+
processResults(),
|
|
24
50
|
])
|
|
25
51
|
.then((values) => setResult(JSON.stringify(values)))
|
|
26
52
|
.catch((reason: unknown) => setError(String(reason)))
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
This fixture exercises a real `OneModule` through the same `.kt` import on
|
|
4
4
|
Android and in Peach. Its visible result contains nested map/list round trips,
|
|
5
5
|
nullable strings and collection elements, 14 JSON reads and 17 expected Android
|
|
6
|
-
JSON errors. The
|
|
6
|
+
JSON errors. The ten results include null elements, empty lists, nested nullable
|
|
7
7
|
map values and an empty map, so declaration errors cannot hide behind a scalar
|
|
8
8
|
call that happens to work.
|
|
9
9
|
|
|
@@ -21,3 +21,8 @@ For Contrast, import these project files with their catalog dependency versions
|
|
|
21
21
|
materialized. Its Android native bundle producer compiles `Values.kt`, publishes
|
|
22
22
|
the generated Wasm and registers the module contract before calling methods.
|
|
23
23
|
A successful bundle alone does not establish runtime acceptance.
|
|
24
|
+
|
|
25
|
+
The final result checks retained module state across coroutine completion, synchronous
|
|
26
|
+
and suspended rejection, and recovery. Its eight values are `initial`, `changed`,
|
|
27
|
+
`changed`, `sync rejected`, `changed`, `suspend rejected`, `recovered`, `recovered`.
|
|
28
|
+
The fixture asserts settled call behavior; it makes no timing or concurrency claim.
|
|
@@ -1,5 +1,9 @@
|
|
|
1
1
|
// generated by one typegen; edits will be replaced
|
|
2
2
|
export declare const Values: {
|
|
3
|
+
current(): Promise<string>
|
|
4
|
+
delayed(value: string): Promise<string>
|
|
5
|
+
throwing(): Promise<string>
|
|
6
|
+
delayedThrow(): Promise<string>
|
|
3
7
|
nested(values: Record<string, number[]>): Promise<Record<string, number[]>>
|
|
4
8
|
nullable(value: string | null): Promise<string | null>
|
|
5
9
|
nullableList(values: (string | null)[]): Promise<(string | null)[]>
|
|
@@ -1,11 +1,29 @@
|
|
|
1
1
|
package dev.contrast.kotlinimports
|
|
2
2
|
|
|
3
3
|
import dev.onejs.one.source.OneModule
|
|
4
|
+
import kotlinx.coroutines.delay
|
|
4
5
|
import org.json.JSONArray
|
|
5
6
|
import org.json.JSONException
|
|
6
7
|
import org.json.JSONObject
|
|
7
8
|
|
|
8
9
|
object Values : OneModule {
|
|
10
|
+
private var processState = "initial"
|
|
11
|
+
|
|
12
|
+
fun current(): String = processState
|
|
13
|
+
|
|
14
|
+
suspend fun delayed(value: String): String {
|
|
15
|
+
delay(30)
|
|
16
|
+
processState = value
|
|
17
|
+
return processState
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
fun throwing(): String = throw IllegalStateException("sync failure")
|
|
21
|
+
|
|
22
|
+
suspend fun delayedThrow(): String {
|
|
23
|
+
delay(30)
|
|
24
|
+
throw IllegalArgumentException("suspend failure")
|
|
25
|
+
}
|
|
26
|
+
|
|
9
27
|
fun nested(values: Map<String, List<Int>>): Map<String, List<Int>> = values
|
|
10
28
|
|
|
11
29
|
fun nullable(value: String?): String? = value
|