peachsim 0.1.661 → 0.1.662
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/commands/inspect/wait-ready.ts +10 -1
- 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 +1 -1
- package/dist-lib/bridge-contract-input.mjs +1 -1
- package/dist-lib/bridge-contract.cjs +1 -1
- package/dist-lib/bridge-contract.mjs +1 -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 +1 -1
- 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 +1 -1
- 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 +191 -60
- 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 +1 -1
- 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 +1 -1
- package/dist-lib/sdk.mjs +1 -1
- package/dist-lib/skills.cjs +1 -1
- 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 +1 -1
- package/kotlin/AppSources/androidx/activity/ActivityViewModelLazy.kt +21 -0
- package/kotlin/AppSources/androidx/activity/ComponentActivity.kt +5 -3
- package/kotlin/AppSources/androidx/lifecycle/AndroidViewModel.kt +10 -0
- package/kotlin/AppSources/dev/peach/app/PeachApp.kt +8 -0
- package/kotlin/tools/abi-classes.main.kts +41 -0
- package/package.json +1 -1
- package/src/kotlin-host.ts +91 -10
- package/src/kotlin-view-models.ts +133 -0
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
/*! rnx v0.1.
|
|
1
|
+
/*! rnx v0.1.662 | (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/plist.cjs
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
/*! rnx v0.1.
|
|
1
|
+
/*! rnx v0.1.662 | (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/plist.mjs
CHANGED
package/dist-lib/profiles.cjs
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
/*! rnx v0.1.
|
|
1
|
+
/*! rnx v0.1.662 | (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.662 | (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.662 | (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/render-mode.cjs
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
/*! rnx v0.1.
|
|
1
|
+
/*! rnx v0.1.662 | (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.662 | (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/sdk.cjs
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
/*! rnx v0.1.
|
|
1
|
+
/*! rnx v0.1.662 | (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/sdk.mjs
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
/*! rnx v0.1.
|
|
1
|
+
/*! rnx v0.1.662 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
|
|
2
2
|
var __defProp = Object.defineProperty;
|
|
3
3
|
var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
|
|
4
4
|
var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
|
package/dist-lib/skills.cjs
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
/*! rnx v0.1.
|
|
1
|
+
/*! rnx v0.1.662 | (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.662 | (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.662 | (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.662 | (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.662 | (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.662 | (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.662 | (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;
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
package androidx.activity
|
|
2
|
+
|
|
3
|
+
import androidx.lifecycle.ViewModel
|
|
4
|
+
import androidx.lifecycle.ViewModelLazy
|
|
5
|
+
import androidx.lifecycle.ViewModelProvider
|
|
6
|
+
import androidx.lifecycle.viewmodel.CreationExtras
|
|
7
|
+
|
|
8
|
+
// activity-ktx's viewModels(): the activity's store, its default factory and
|
|
9
|
+
// creation extras unless the caller passes its own
|
|
10
|
+
inline fun <reified VM : ViewModel> ComponentActivity.viewModels(
|
|
11
|
+
noinline extrasProducer: (() -> CreationExtras)? = null,
|
|
12
|
+
noinline factoryProducer: (() -> ViewModelProvider.Factory)? = null,
|
|
13
|
+
): Lazy<VM> {
|
|
14
|
+
val factoryPromise = factoryProducer ?: { defaultViewModelProviderFactory }
|
|
15
|
+
return ViewModelLazy(
|
|
16
|
+
VM::class,
|
|
17
|
+
{ viewModelStore },
|
|
18
|
+
factoryPromise,
|
|
19
|
+
{ extrasProducer?.invoke() ?: this.defaultViewModelCreationExtras },
|
|
20
|
+
)
|
|
21
|
+
}
|
|
@@ -8,7 +8,6 @@ import androidx.lifecycle.LifecycleEventObserver
|
|
|
8
8
|
import androidx.lifecycle.LifecycleOwner
|
|
9
9
|
import androidx.lifecycle.LifecycleRegistry
|
|
10
10
|
import androidx.lifecycle.SAVED_STATE_REGISTRY_OWNER_KEY
|
|
11
|
-
import androidx.lifecycle.SavedStateViewModelFactory
|
|
12
11
|
import androidx.lifecycle.VIEW_MODEL_STORE_OWNER_KEY
|
|
13
12
|
import androidx.lifecycle.ViewModelStore
|
|
14
13
|
import androidx.lifecycle.ViewModelStoreOwner
|
|
@@ -23,7 +22,8 @@ import androidx.savedstate.SavedStateRegistryOwner
|
|
|
23
22
|
// lifecycle, viewmodel and savedstate libraries: the registry attaches in the
|
|
24
23
|
// constructor and restores in onCreate before super, the lifecycle moves up on
|
|
25
24
|
// the post callbacks and down on the pre callbacks, as ReportFragment does from
|
|
26
|
-
// API 29, and destroy clears the view model store
|
|
25
|
+
// API 29, and destroy clears the view model store unless the activity is
|
|
26
|
+
// changing configurations.
|
|
27
27
|
open class ComponentActivity : Activity(), LifecycleOwner, ViewModelStoreOwner,
|
|
28
28
|
HasDefaultViewModelProviderFactory, SavedStateRegistryOwner, OnBackPressedDispatcherOwner {
|
|
29
29
|
private val lifecycleRegistry = LifecycleRegistry(this)
|
|
@@ -48,7 +48,9 @@ open class ComponentActivity : Activity(), LifecycleOwner, ViewModelStoreOwner,
|
|
|
48
48
|
}
|
|
49
49
|
}
|
|
50
50
|
override val savedStateRegistry: SavedStateRegistry get() = savedStateRegistryController.savedStateRegistry
|
|
51
|
-
|
|
51
|
+
// SavedStateViewModelFactory's constructor choice, made at compile time
|
|
52
|
+
// for the app's view models since Kotlin/Wasm has no reflection
|
|
53
|
+
override val defaultViewModelProviderFactory get() = dev.peach.app.viewModelFactory()
|
|
52
54
|
override val defaultViewModelCreationExtras: CreationExtras
|
|
53
55
|
get() = MutableCreationExtras().also {
|
|
54
56
|
it[SAVED_STATE_REGISTRY_OWNER_KEY] = this
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
package androidx.lifecycle
|
|
2
|
+
|
|
3
|
+
import android.app.Application
|
|
4
|
+
|
|
5
|
+
// androidx.lifecycle's AndroidViewModel: a view model that holds its
|
|
6
|
+
// application, which the default factory passes to its constructor
|
|
7
|
+
open class AndroidViewModel(private val application: Application) : ViewModel() {
|
|
8
|
+
@Suppress("UNCHECKED_CAST")
|
|
9
|
+
open fun <T : Application> getApplication(): T = application as T
|
|
10
|
+
}
|
|
@@ -15,6 +15,7 @@ import android.view.ViewGroup
|
|
|
15
15
|
import android.view.Window
|
|
16
16
|
import android.view.WindowTheme
|
|
17
17
|
import androidx.activity.ComponentActivity
|
|
18
|
+
import androidx.lifecycle.ViewModelProvider
|
|
18
19
|
import androidx.activity.compose.LocalOnBackPressedDispatcherOwner
|
|
19
20
|
import androidx.compose.runtime.Composable
|
|
20
21
|
import androidx.compose.runtime.CompositionLocalProvider
|
|
@@ -51,8 +52,15 @@ internal class PeachAppSpec(
|
|
|
51
52
|
// the activity's configChanges includes uiMode: it takes a night mode
|
|
52
53
|
// change in onConfigurationChanged rather than relaunching
|
|
53
54
|
val handlesUiMode: Boolean,
|
|
55
|
+
// the app's view models, each built with the constructor the device's
|
|
56
|
+
// default factory finds for it
|
|
57
|
+
val viewModels: ViewModelProvider.Factory,
|
|
54
58
|
)
|
|
55
59
|
|
|
60
|
+
internal fun viewModelFactory(): ViewModelProvider.Factory = runningApp().spec.viewModels
|
|
61
|
+
|
|
62
|
+
internal fun peachApplication(): Application = runningApp().application
|
|
63
|
+
|
|
56
64
|
// the insets the device delivers to the window's content, in pixels: all of
|
|
57
65
|
// them on an edge-to-edge window, none once the decor fits system windows.
|
|
58
66
|
// insets lists the status bars, navigation bars, display cutout, system
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
// prints an app klib's classes as the compiler resolved them, one JSON object
|
|
2
|
+
// per line: qualified name, kind, modality, whether it is inner or generic,
|
|
3
|
+
// its supertypes and its public constructors' parameter types, each marked
|
|
4
|
+
// when every parameter has a default
|
|
5
|
+
@file:OptIn(org.jetbrains.kotlin.library.abi.ExperimentalLibraryAbiReader::class)
|
|
6
|
+
|
|
7
|
+
import org.jetbrains.kotlin.library.abi.*
|
|
8
|
+
|
|
9
|
+
fun name(qualified: AbiQualifiedName): String {
|
|
10
|
+
val pkg = qualified.packageName.value.replace('/', '.')
|
|
11
|
+
val relative = qualified.relativeName.value
|
|
12
|
+
return if (pkg.isEmpty()) relative else "$pkg.$relative"
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
fun type(type: AbiType): String =
|
|
16
|
+
((type as? AbiType.Simple)?.classifierReference as? AbiClassifierReference.ClassReference)
|
|
17
|
+
?.let { name(it.className) } ?: "?"
|
|
18
|
+
|
|
19
|
+
fun quote(value: String) = "\"" + value.replace("\\", "\\\\").replace("\"", "\\\"") + "\""
|
|
20
|
+
|
|
21
|
+
fun walk(declaration: AbiDeclaration) {
|
|
22
|
+
if (declaration !is AbiClass) return
|
|
23
|
+
val constructors = declaration.declarations.filterIsInstance<AbiFunction>()
|
|
24
|
+
.filter { it.isConstructor }
|
|
25
|
+
.map { constructor ->
|
|
26
|
+
val parameters = constructor.valueParameters.joinToString(",", "[", "]") { quote(type(it.type)) }
|
|
27
|
+
"{\"parameters\":$parameters,\"defaulted\":${constructor.valueParameters.all { it.hasDefaultArg }}}"
|
|
28
|
+
}
|
|
29
|
+
println(
|
|
30
|
+
"{\"name\":${quote(name(declaration.qualifiedName))}" +
|
|
31
|
+
",\"kind\":${quote(declaration.kind.name)}" +
|
|
32
|
+
",\"modality\":${quote(declaration.modality.name)}" +
|
|
33
|
+
",\"inner\":${declaration.isInner}" +
|
|
34
|
+
",\"generic\":${declaration.typeParameters.isNotEmpty()}" +
|
|
35
|
+
",\"supers\":${declaration.superTypes.joinToString(",", "[", "]") { quote(type(it)) }}" +
|
|
36
|
+
",\"constructors\":${constructors.joinToString(",", "[", "]")}}",
|
|
37
|
+
)
|
|
38
|
+
declaration.declarations.forEach(::walk)
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
LibraryAbiReader.readAbiInfo(java.io.File(args[0])).topLevelDeclarations.declarations.forEach(::walk)
|
package/package.json
CHANGED
package/src/kotlin-host.ts
CHANGED
|
@@ -12,6 +12,11 @@ import path from 'node:path'
|
|
|
12
12
|
import { promisify } from 'node:util'
|
|
13
13
|
import { rnxHomeDir } from './home-paths'
|
|
14
14
|
import { isRecord } from './is-record'
|
|
15
|
+
import {
|
|
16
|
+
readAbiClasses,
|
|
17
|
+
viewModelClasses,
|
|
18
|
+
type ViewModelClass,
|
|
19
|
+
} from './kotlin-view-models'
|
|
15
20
|
import type { KotlinAppProject } from './kotlin-app'
|
|
16
21
|
import type { ServerResponse } from 'node:http'
|
|
17
22
|
|
|
@@ -262,7 +267,7 @@ function entrySource(ids: string[]): string {
|
|
|
262
267
|
].join('\n')
|
|
263
268
|
}
|
|
264
269
|
|
|
265
|
-
const BUILD_FORMAT =
|
|
270
|
+
const BUILD_FORMAT = 8
|
|
266
271
|
const builds = new Map<string, Promise<KotlinArtifact>>()
|
|
267
272
|
|
|
268
273
|
function buildsDir(): string {
|
|
@@ -320,8 +325,30 @@ function viewsSource(viewIds: string[]): string {
|
|
|
320
325
|
].join('\n')
|
|
321
326
|
}
|
|
322
327
|
|
|
328
|
+
// the call SavedStateViewModelFactory makes for each constructor it finds, or
|
|
329
|
+
// its error for one that takes the application and handle in another order
|
|
330
|
+
function viewModelInitializer({ name, signature }: ViewModelClass): string {
|
|
331
|
+
const call = (args: string) =>
|
|
332
|
+
` addInitializer(${name}::class) { ${name}(${args}) }`
|
|
333
|
+
switch (signature) {
|
|
334
|
+
case 'none':
|
|
335
|
+
return call('')
|
|
336
|
+
case 'handle':
|
|
337
|
+
return call('createSavedStateHandle()')
|
|
338
|
+
case 'application':
|
|
339
|
+
return call('dev.peach.app.peachApplication()')
|
|
340
|
+
case 'application-handle':
|
|
341
|
+
return call('dev.peach.app.peachApplication(), createSavedStateHandle()')
|
|
342
|
+
case 'misordered':
|
|
343
|
+
return ` addInitializer(${name}::class) { throw UnsupportedOperationException(${JSON.stringify(
|
|
344
|
+
`Class ${name.split('.').pop()} must have parameters in the proper order: [class android.app.Application, class androidx.lifecycle.SavedStateHandle]`,
|
|
345
|
+
)}) }`
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
|
|
323
349
|
// a pure Kotlin app: its module's sources, the Android platform actuals and
|
|
324
350
|
// the compose host, with an entry that cold-starts the resolved launcher
|
|
351
|
+
|
|
325
352
|
export async function kotlinAppArtifact(
|
|
326
353
|
app: KotlinAppProject,
|
|
327
354
|
moduleRoot: string,
|
|
@@ -329,12 +356,12 @@ export async function kotlinAppArtifact(
|
|
|
329
356
|
): Promise<KotlinArtifact> {
|
|
330
357
|
const toolchain = readToolchain(moduleRoot)
|
|
331
358
|
const kotlinString = (value: string) => JSON.stringify(value).replaceAll('$', '\\$')
|
|
332
|
-
const
|
|
333
|
-
|
|
334
|
-
'PeachViews.kt': viewsSource([]),
|
|
335
|
-
'PeachAppEntry.kt': [
|
|
359
|
+
const appEntry = (viewModels: ViewModelClass[]) =>
|
|
360
|
+
[
|
|
336
361
|
'@file:OptIn(kotlin.js.ExperimentalJsExport::class)',
|
|
337
362
|
'',
|
|
363
|
+
'import androidx.lifecycle.createSavedStateHandle',
|
|
364
|
+
'',
|
|
338
365
|
'@JsExport',
|
|
339
366
|
'fun peachAppMount(density: Float, fontScale: Float, locale: String, backend: String, stencilBits: Int,',
|
|
340
367
|
' regular: String, medium: String, bold: String, insets: String, night: Boolean,',
|
|
@@ -351,6 +378,9 @@ export async function kotlinAppArtifact(
|
|
|
351
378
|
` { ${app.applicationClass ?? 'android.app.Application'}() },`,
|
|
352
379
|
` { ${app.launcher.targetClass}() },`,
|
|
353
380
|
` ${app.handlesUiMode},`,
|
|
381
|
+
' androidx.lifecycle.viewmodel.viewModelFactory {',
|
|
382
|
+
...viewModels.map(viewModelInitializer),
|
|
383
|
+
' },',
|
|
354
384
|
' ),',
|
|
355
385
|
' density, fontScale, locale, backend, stencilBits, regular, medium, bold, insets, night, invalidate,',
|
|
356
386
|
')',
|
|
@@ -364,7 +394,11 @@ export async function kotlinAppArtifact(
|
|
|
364
394
|
'@JsExport',
|
|
365
395
|
'fun peachAppNight(night: Boolean) = dev.peach.app.peachAppNight(night)',
|
|
366
396
|
'',
|
|
367
|
-
].join('\n')
|
|
397
|
+
].join('\n')
|
|
398
|
+
const generated: Record<string, string> = {
|
|
399
|
+
'PeachEntry.kt': entrySource([]),
|
|
400
|
+
'PeachViews.kt': viewsSource([]),
|
|
401
|
+
'PeachAppEntry.kt': appEntry([]),
|
|
368
402
|
}
|
|
369
403
|
const shims = [
|
|
370
404
|
...kotlinSources(path.join(moduleRoot, 'Sources')),
|
|
@@ -380,6 +414,11 @@ export async function kotlinAppArtifact(
|
|
|
380
414
|
generated,
|
|
381
415
|
shims,
|
|
382
416
|
usesCompose: true,
|
|
417
|
+
viewModels: {
|
|
418
|
+
file: 'PeachAppEntry.kt',
|
|
419
|
+
render: appEntry,
|
|
420
|
+
script: path.join(moduleRoot, 'tools/abi-classes.main.kts'),
|
|
421
|
+
},
|
|
383
422
|
},
|
|
384
423
|
origin,
|
|
385
424
|
)
|
|
@@ -394,10 +433,26 @@ async function compileKotlin(
|
|
|
394
433
|
generated: Record<string, string>
|
|
395
434
|
shims: string[]
|
|
396
435
|
usesCompose: boolean
|
|
436
|
+
// a generated file that names the app's view models: rendered empty for a
|
|
437
|
+
// first compile, then from that klib's ABI
|
|
438
|
+
viewModels?: {
|
|
439
|
+
file: string
|
|
440
|
+
render: (classes: ViewModelClass[]) => string
|
|
441
|
+
script: string
|
|
442
|
+
}
|
|
397
443
|
},
|
|
398
444
|
origin: string,
|
|
399
445
|
): Promise<KotlinArtifact> {
|
|
400
|
-
const {
|
|
446
|
+
const {
|
|
447
|
+
root,
|
|
448
|
+
toolchain,
|
|
449
|
+
language,
|
|
450
|
+
sources,
|
|
451
|
+
generated,
|
|
452
|
+
shims,
|
|
453
|
+
usesCompose,
|
|
454
|
+
viewModels,
|
|
455
|
+
} = input
|
|
401
456
|
const digest = createHash('sha256')
|
|
402
457
|
// BUILD_FORMAT moves when this host changes what it does to a build
|
|
403
458
|
digest.update(JSON.stringify({ format: BUILD_FORMAT, toolchain, language }))
|
|
@@ -444,8 +499,8 @@ async function compileKotlin(
|
|
|
444
499
|
'-libraries',
|
|
445
500
|
libraries.join(path.delimiter),
|
|
446
501
|
]
|
|
447
|
-
|
|
448
|
-
|
|
502
|
+
const compileKlib = (outputDir: string) =>
|
|
503
|
+
run(
|
|
449
504
|
kotlinc,
|
|
450
505
|
[
|
|
451
506
|
...common,
|
|
@@ -461,7 +516,7 @@ async function compileKotlin(
|
|
|
461
516
|
'-nowarn',
|
|
462
517
|
'-nopack',
|
|
463
518
|
'-ir-output-dir',
|
|
464
|
-
|
|
519
|
+
outputDir,
|
|
465
520
|
'-ir-output-name',
|
|
466
521
|
'app',
|
|
467
522
|
...shims,
|
|
@@ -470,6 +525,32 @@ async function compileKotlin(
|
|
|
470
525
|
],
|
|
471
526
|
{ maxBuffer: 16 * 1024 * 1024 },
|
|
472
527
|
)
|
|
528
|
+
try {
|
|
529
|
+
// the compiler resolves the app's classes and constructors: a first
|
|
530
|
+
// klib's ABI names the view models the generated factory builds. an app
|
|
531
|
+
// whose sources never name ViewModel declares none and skips it
|
|
532
|
+
if (
|
|
533
|
+
viewModels &&
|
|
534
|
+
sources.some((file) => fs.readFileSync(file, 'utf8').includes('ViewModel'))
|
|
535
|
+
) {
|
|
536
|
+
await compileKlib(path.join(work, 'abi'))
|
|
537
|
+
const { stdout } = await run(
|
|
538
|
+
path.join(path.dirname(kotlinc), 'kotlinc'),
|
|
539
|
+
[
|
|
540
|
+
'-cp',
|
|
541
|
+
path.resolve(path.dirname(kotlinc), '../lib/kotlin-compiler.jar'),
|
|
542
|
+
'-script',
|
|
543
|
+
viewModels.script,
|
|
544
|
+
path.join(work, 'abi'),
|
|
545
|
+
],
|
|
546
|
+
{ maxBuffer: 64 * 1024 * 1024 },
|
|
547
|
+
)
|
|
548
|
+
fs.writeFileSync(
|
|
549
|
+
path.join(generatedDir, viewModels.file),
|
|
550
|
+
viewModels.render(viewModelClasses(readAbiClasses(stdout))),
|
|
551
|
+
)
|
|
552
|
+
}
|
|
553
|
+
await compileKlib(path.join(work, 'klib'))
|
|
473
554
|
await run(
|
|
474
555
|
kotlinc,
|
|
475
556
|
[
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
// the ViewModel classes an app declares, read from its compiled klib's ABI
|
|
2
|
+
// (`kotlin/tools/abi-classes.main.kts`), each with the constructor androidx's
|
|
3
|
+
// SavedStateViewModelFactory finds by reflection: an AndroidViewModel takes a
|
|
4
|
+
// public (Application, SavedStateHandle) constructor, else AndroidViewModel
|
|
5
|
+
// Factory's public (Application) one; any other view model a public
|
|
6
|
+
// (SavedStateHandle) one, else NewInstanceFactory's no-argument one, which
|
|
7
|
+
// kotlin adds when every parameter has a default. Kotlin/Wasm has no
|
|
8
|
+
// reflection, so the host generates these calls. a class the factory cannot
|
|
9
|
+
// build is left out and fails when created, as it does on the device; the
|
|
10
|
+
// ABI lists public declarations only, so an internal class is left out too.
|
|
11
|
+
import { isRecord } from './is-record'
|
|
12
|
+
|
|
13
|
+
export type ViewModelSignature =
|
|
14
|
+
| 'none'
|
|
15
|
+
| 'handle'
|
|
16
|
+
| 'application'
|
|
17
|
+
| 'application-handle'
|
|
18
|
+
| 'misordered'
|
|
19
|
+
|
|
20
|
+
export interface ViewModelClass {
|
|
21
|
+
name: string
|
|
22
|
+
signature: ViewModelSignature
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
interface AbiClass {
|
|
26
|
+
name: string
|
|
27
|
+
kind: string
|
|
28
|
+
modality: string
|
|
29
|
+
inner: boolean
|
|
30
|
+
generic: boolean
|
|
31
|
+
supers: string[]
|
|
32
|
+
constructors: Array<{ parameters: string[]; defaulted: boolean }>
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
const VIEW_MODEL = 'androidx.lifecycle.ViewModel'
|
|
36
|
+
const ANDROID_VIEW_MODEL = 'androidx.lifecycle.AndroidViewModel'
|
|
37
|
+
const APPLICATION = 'android.app.Application'
|
|
38
|
+
const HANDLE = 'androidx.lifecycle.SavedStateHandle'
|
|
39
|
+
|
|
40
|
+
function strings(value: unknown): string[] {
|
|
41
|
+
if (!Array.isArray(value) || !value.every((item) => typeof item === 'string'))
|
|
42
|
+
throw new Error('abi-classes printed an invalid list')
|
|
43
|
+
return value
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export function readAbiClasses(output: string): AbiClass[] {
|
|
47
|
+
return output
|
|
48
|
+
.split('\n')
|
|
49
|
+
.filter((line) => line.startsWith('{'))
|
|
50
|
+
.map((line) => {
|
|
51
|
+
const row: unknown = JSON.parse(line)
|
|
52
|
+
if (
|
|
53
|
+
!isRecord(row) ||
|
|
54
|
+
typeof row.name !== 'string' ||
|
|
55
|
+
typeof row.kind !== 'string' ||
|
|
56
|
+
typeof row.modality !== 'string' ||
|
|
57
|
+
typeof row.inner !== 'boolean' ||
|
|
58
|
+
typeof row.generic !== 'boolean' ||
|
|
59
|
+
!Array.isArray(row.constructors)
|
|
60
|
+
)
|
|
61
|
+
throw new Error(`abi-classes printed an invalid class: ${line}`)
|
|
62
|
+
return {
|
|
63
|
+
name: row.name,
|
|
64
|
+
kind: row.kind,
|
|
65
|
+
modality: row.modality,
|
|
66
|
+
inner: row.inner,
|
|
67
|
+
generic: row.generic,
|
|
68
|
+
supers: strings(row.supers),
|
|
69
|
+
constructors: row.constructors.map((constructor: unknown) => {
|
|
70
|
+
if (!isRecord(constructor) || typeof constructor.defaulted !== 'boolean')
|
|
71
|
+
throw new Error(`abi-classes printed an invalid constructor: ${line}`)
|
|
72
|
+
return {
|
|
73
|
+
parameters: strings(constructor.parameters),
|
|
74
|
+
defaulted: constructor.defaulted,
|
|
75
|
+
}
|
|
76
|
+
}),
|
|
77
|
+
}
|
|
78
|
+
})
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
// SavedStateViewModelFactory's findMatchingConstructor: an exact match, or an
|
|
82
|
+
// error when a constructor takes the same types in another order
|
|
83
|
+
function matching(row: AbiClass, signature: string[]): 'match' | 'misordered' | null {
|
|
84
|
+
for (const { parameters } of row.constructors) {
|
|
85
|
+
if (parameters.join() === signature.join()) return 'match'
|
|
86
|
+
if (
|
|
87
|
+
parameters.length === signature.length &&
|
|
88
|
+
signature.every((type) => parameters.includes(type))
|
|
89
|
+
)
|
|
90
|
+
return 'misordered'
|
|
91
|
+
}
|
|
92
|
+
return null
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
function signatureOf(row: AbiClass, android: boolean): ViewModelSignature | null {
|
|
96
|
+
const handled = matching(row, android ? [APPLICATION, HANDLE] : [HANDLE])
|
|
97
|
+
if (handled === 'misordered') return 'misordered'
|
|
98
|
+
if (handled) return android ? 'application-handle' : 'handle'
|
|
99
|
+
if (android)
|
|
100
|
+
return row.constructors.some(({ parameters }) => parameters.join() === APPLICATION)
|
|
101
|
+
? 'application'
|
|
102
|
+
: null
|
|
103
|
+
return row.constructors.some(
|
|
104
|
+
({ parameters, defaulted }) => parameters.length === 0 || defaulted,
|
|
105
|
+
)
|
|
106
|
+
? 'none'
|
|
107
|
+
: null
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
export function viewModelClasses(rows: AbiClass[]): ViewModelClass[] {
|
|
111
|
+
const byName = new Map(rows.map((row) => [row.name, row]))
|
|
112
|
+
// the framework base a class reaches through the app's own classes
|
|
113
|
+
const base = (row: AbiClass, seen: Set<string>): string | null => {
|
|
114
|
+
for (const name of row.supers) {
|
|
115
|
+
if (name === VIEW_MODEL || name === ANDROID_VIEW_MODEL) return name
|
|
116
|
+
const parent = byName.get(name)
|
|
117
|
+
if (parent && !seen.has(name)) {
|
|
118
|
+
seen.add(name)
|
|
119
|
+
const found = base(parent, seen)
|
|
120
|
+
if (found) return found
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
return null
|
|
124
|
+
}
|
|
125
|
+
return rows.flatMap((row) => {
|
|
126
|
+
if (row.kind !== 'CLASS' || row.inner || row.generic) return []
|
|
127
|
+
if (row.modality === 'ABSTRACT' || row.modality === 'SEALED') return []
|
|
128
|
+
const root = base(row, new Set())
|
|
129
|
+
if (!root) return []
|
|
130
|
+
const signature = signatureOf(row, root === ANDROID_VIEW_MODEL)
|
|
131
|
+
return signature ? [{ name: row.name, signature }] : []
|
|
132
|
+
})
|
|
133
|
+
}
|