peachsim 0.1.660 → 0.1.661
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/README.md +10 -15
- package/cli/drivers/linux-browser-libs.lock.json +1088 -0
- package/cli/drivers/linux-browser-libs.ts +182 -0
- package/cli/drivers/playwright-provisioning.ts +20 -98
- package/cli/drivers/playwright-sim-host.ts +9 -0
- package/cli/drivers/playwright.ts +2 -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 +18 -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 +45 -41
- 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 +4 -3
- 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 +1332 -177
- 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/android/view/Window.kt +15 -9
- package/kotlin/AppSources/dev/peach/app/PeachApp.kt +29 -16
- package/package.json +2 -1
- package/src/android-theme.ts +14 -0
- package/src/backend-origin.ts +25 -0
- package/src/host/bridge-host.ts +24 -37
- package/src/kotlin-app.ts +22 -4
- package/src/kotlin-host.ts +3 -2
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
/*! rnx v0.1.
|
|
1
|
+
/*! rnx v0.1.661 | (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.661 | (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.661 | (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.661 | (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.661 | (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.661 | (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;
|
|
@@ -13,6 +13,7 @@ import org.json.JSONObject
|
|
|
13
13
|
internal class WindowTheme(
|
|
14
14
|
val edgeToEdgeEnforced: Boolean,
|
|
15
15
|
val statusBarColor: Int,
|
|
16
|
+
val navigationBarColor: Int,
|
|
16
17
|
val statusBarContrastEnforced: Boolean,
|
|
17
18
|
val navigationBarContrastEnforced: Boolean,
|
|
18
19
|
val lightStatusBar: Boolean,
|
|
@@ -62,15 +63,17 @@ class Window internal constructor(
|
|
|
62
63
|
fun getStatusBarColor(): Int = statusBarColor
|
|
63
64
|
fun setStatusBarColor(color: Int) { statusBarColor = color }
|
|
64
65
|
|
|
65
|
-
// the theme's navigation bar color
|
|
66
|
-
//
|
|
67
|
-
|
|
68
|
-
private var navigationBar: Int? = null
|
|
66
|
+
// the theme's navigation bar color as PhoneWindow resolved it; an enforced
|
|
67
|
+
// edge to edge window reports and draws transparent
|
|
68
|
+
private var navigationBar = theme.navigationBarColor
|
|
69
69
|
|
|
70
70
|
var navigationBarColor: Int
|
|
71
|
-
get() = if (edgeToEdgeEnforced) Color.TRANSPARENT
|
|
72
|
-
|
|
73
|
-
|
|
71
|
+
get() = if (edgeToEdgeEnforced) Color.TRANSPARENT else navigationBar
|
|
72
|
+
set(color) {
|
|
73
|
+
if (color == navigationBar) return
|
|
74
|
+
navigationBar = color
|
|
75
|
+
host.windowChanged()
|
|
76
|
+
}
|
|
74
77
|
|
|
75
78
|
fun getNavigationBarColor(): Int = navigationBarColor
|
|
76
79
|
fun setNavigationBarColor(color: Int) { navigationBarColor = color }
|
|
@@ -113,11 +116,14 @@ class Window internal constructor(
|
|
|
113
116
|
return SCRIM_LIGHT
|
|
114
117
|
}
|
|
115
118
|
|
|
116
|
-
// what the decor draws for this window: its inset policy and its
|
|
117
|
-
//
|
|
119
|
+
// what the decor draws for this window: its inset policy and its bar
|
|
120
|
+
// colors as unsigned ARGB. gesture navigation draws no contrast scrim
|
|
121
|
+
// behind a transparent navigation bar, as the oracle's edge to edge
|
|
122
|
+
// captures show
|
|
118
123
|
internal fun describe(): JSONObject = JSONObject()
|
|
119
124
|
.put("decorFitsSystemWindows", fits)
|
|
120
125
|
.put("statusBarColor", drawnStatusBarColor().toLong() and 0xffffffffL)
|
|
126
|
+
.put("navigationBarColor", navigationBarColor.toLong() and 0xffffffffL)
|
|
121
127
|
|
|
122
128
|
// Activity.setContentView(View) fills the content parent
|
|
123
129
|
internal fun setContentView(view: View, params: ViewGroup.LayoutParams) {
|
|
@@ -53,19 +53,32 @@ internal class PeachAppSpec(
|
|
|
53
53
|
val handlesUiMode: Boolean,
|
|
54
54
|
)
|
|
55
55
|
|
|
56
|
-
// the
|
|
57
|
-
//
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
)
|
|
56
|
+
// the insets the device delivers to the window's content, in pixels: all of
|
|
57
|
+
// them on an edge-to-edge window, none once the decor fits system windows.
|
|
58
|
+
// insets lists the status bars, navigation bars, display cutout, system
|
|
59
|
+
// gestures, mandatory system gestures and tappable element, each left, top,
|
|
60
|
+
// right and bottom.
|
|
61
|
+
private class DeviceInsets(private val window: Window, insets: String) : PlatformWindowInsets {
|
|
62
|
+
private val pixels = insets.split(',').map { it.toInt() }.also { require(it.size == 24) }
|
|
63
|
+
|
|
63
64
|
// read when compose lays out, so a later change to the window applies
|
|
64
|
-
private fun
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
65
|
+
private fun type(index: Int): PlatformInsets {
|
|
66
|
+
fun side(offset: Int) = { if (window.fitsSystemWindows) 0 else pixels[index * 4 + offset] }
|
|
67
|
+
return PlatformInsets(side(0), side(1), side(2), side(3))
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
override val statusBars = type(0)
|
|
71
|
+
override val navigationBars = type(1)
|
|
72
|
+
override val displayCutout = type(2)
|
|
73
|
+
override val systemGestures = type(3)
|
|
74
|
+
override val mandatorySystemGestures = type(4)
|
|
75
|
+
override val tappableElement = type(5)
|
|
76
|
+
override val systemBars = PlatformInsets(
|
|
77
|
+
{ maxOf(statusBars.left, navigationBars.left) },
|
|
78
|
+
{ maxOf(statusBars.top, navigationBars.top) },
|
|
79
|
+
{ maxOf(statusBars.right, navigationBars.right) },
|
|
80
|
+
{ maxOf(statusBars.bottom, navigationBars.bottom) },
|
|
81
|
+
)
|
|
69
82
|
}
|
|
70
83
|
|
|
71
84
|
private class ActivityOwners(activity: ComponentActivity) : PlatformArchitectureComponentsOwner {
|
|
@@ -85,7 +98,7 @@ private class AppProcess(
|
|
|
85
98
|
val application: Application,
|
|
86
99
|
val density: Float, val fontScale: Float, val locale: String, val gpu: Boolean, val stencilBits: Int,
|
|
87
100
|
val fonts: FontFamily,
|
|
88
|
-
val
|
|
101
|
+
val insets: String,
|
|
89
102
|
val invalidate: (Boolean) -> Unit,
|
|
90
103
|
) {
|
|
91
104
|
lateinit var activity: Activity
|
|
@@ -120,7 +133,7 @@ internal fun peachStartApp(
|
|
|
120
133
|
spec: PeachAppSpec,
|
|
121
134
|
density: Float, fontScale: Float, locale: String, backend: String, stencilBits: Int,
|
|
122
135
|
regular: String, medium: String, bold: String,
|
|
123
|
-
|
|
136
|
+
insets: String, night: Boolean,
|
|
124
137
|
invalidate: (Boolean) -> Unit,
|
|
125
138
|
): String {
|
|
126
139
|
try {
|
|
@@ -131,7 +144,7 @@ internal fun peachStartApp(
|
|
|
131
144
|
application.onCreate()
|
|
132
145
|
val app = AppProcess(
|
|
133
146
|
spec, application, density, fontScale, locale, backend == "graphite", stencilBits,
|
|
134
|
-
robotoFamily(regular, medium, bold),
|
|
147
|
+
robotoFamily(regular, medium, bold), insets, invalidate,
|
|
135
148
|
)
|
|
136
149
|
process = app
|
|
137
150
|
val view = launchActivity(app, null)
|
|
@@ -201,7 +214,7 @@ private fun launchActivity(app: AppProcess, lastNonConfigurationInstance: Any?):
|
|
|
201
214
|
app.density, app.fontScale, app.locale, app.gpu, app.stencilBits, app.fonts, app.invalidate,
|
|
202
215
|
night = isNight(activity.resources.configuration),
|
|
203
216
|
owners = componentActivity?.let(::ActivityOwners),
|
|
204
|
-
insets =
|
|
217
|
+
insets = DeviceInsets(window, app.insets),
|
|
205
218
|
clockRunning = { componentActivity == null || componentActivity.lifecycle.currentState.isAtLeast(Lifecycle.State.STARTED) },
|
|
206
219
|
)
|
|
207
220
|
mount.setContent {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "peachsim",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.661",
|
|
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",
|
|
@@ -303,6 +303,7 @@
|
|
|
303
303
|
"build:cli-binary": "bun scripts/build-cli-binary.ts",
|
|
304
304
|
"smoke:cli-binary": "bun scripts/smoke-cli-binary.ts",
|
|
305
305
|
"publish:cli-binary": "bun scripts/publish-cli-binaries.ts",
|
|
306
|
+
"publish:linux-browser-libs": "bun scripts/linux-browser-libs.ts publish",
|
|
306
307
|
"pack:smoke": "bun scripts/pack-smoke.ts"
|
|
307
308
|
},
|
|
308
309
|
"dependencies": {
|
package/src/android-theme.ts
CHANGED
|
@@ -165,3 +165,17 @@ export function resolveThemeAttribute(
|
|
|
165
165
|
// unset: the reader's own default applies, as PhoneWindow's getBoolean/getColor do
|
|
166
166
|
return undefined
|
|
167
167
|
}
|
|
168
|
+
|
|
169
|
+
// a framework color resource as context.getColor reads it, following
|
|
170
|
+
// references to other framework colors
|
|
171
|
+
export function frameworkColor(name: string, sources: ThemeSources): number {
|
|
172
|
+
let value = sources.android.colors[name]
|
|
173
|
+
while (value !== undefined && !value.startsWith('#')) {
|
|
174
|
+
const ref = /^@(?:android:)?color\/(.+)$/.exec(value)
|
|
175
|
+
if (!ref?.[1])
|
|
176
|
+
throw new Error(`@android:color/${name} has the unsupported value ${value}`)
|
|
177
|
+
value = sources.android.colors[ref[1]]
|
|
178
|
+
}
|
|
179
|
+
if (value === undefined) throw new Error(`@android:color/${name} is not defined`)
|
|
180
|
+
return color(value)
|
|
181
|
+
}
|
package/src/backend-origin.ts
CHANGED
|
@@ -46,6 +46,31 @@ export function buildBrowserAssetProxyUrl(targetUrl: string): string {
|
|
|
46
46
|
return `${proxyPath}?url=${encodeURIComponent(targetUrl)}`
|
|
47
47
|
}
|
|
48
48
|
|
|
49
|
+
export function resolveBrowserAssetProxyUrl(
|
|
50
|
+
targetUrl: string,
|
|
51
|
+
currentOrigin: string,
|
|
52
|
+
): string {
|
|
53
|
+
const proxyUrl = isBrowserAssetProxyUrl(targetUrl)
|
|
54
|
+
? targetUrl
|
|
55
|
+
: buildBrowserAssetProxyUrl(targetUrl)
|
|
56
|
+
// loopback assets belong to the local bridge; external bytes to the backend.
|
|
57
|
+
if (proxyUrl.startsWith('/__bundle-proxy?')) return proxyUrl
|
|
58
|
+
return new URL(proxyUrl, resolveContrastBackendOrigin(currentOrigin)).toString()
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
export function isBrowserAssetProxyUrl(value: string): boolean {
|
|
62
|
+
try {
|
|
63
|
+
const url = new URL(value, DEFAULT_CONTRAST_BACKEND_ORIGIN)
|
|
64
|
+
return (
|
|
65
|
+
isContrastBackendOrigin(url.origin) &&
|
|
66
|
+
url.pathname === '/__fetch-proxy' &&
|
|
67
|
+
url.searchParams.has('url')
|
|
68
|
+
)
|
|
69
|
+
} catch {
|
|
70
|
+
return false
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
49
74
|
export function isContrastBackendHost(hostname: string): boolean {
|
|
50
75
|
const normalized = normalizeHostname(hostname)
|
|
51
76
|
return (
|
package/src/host/bridge-host.ts
CHANGED
|
@@ -592,24 +592,11 @@ export class SootSimBridgeHost {
|
|
|
592
592
|
// subscriptions get cleaned up automatically on close.
|
|
593
593
|
this.agentHost.registerSocket(ws)
|
|
594
594
|
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
connectedAt: Date.now(),
|
|
601
|
-
lastSeenAt: Date.now(),
|
|
602
|
-
lastActiveAt: 0,
|
|
603
|
-
recentActions: [],
|
|
604
|
-
}
|
|
605
|
-
this.sims.set(sim.id, sim)
|
|
606
|
-
this.writeConnectedRuntimeSnapshot()
|
|
607
|
-
if (this.shouldPromoteSim(sim)) {
|
|
608
|
-
this.primarySimId = sim.id
|
|
609
|
-
}
|
|
610
|
-
this.broadcastSimAssignments()
|
|
611
|
-
this.broadcastSimClientStates()
|
|
612
|
-
} else {
|
|
595
|
+
// a browser socket becomes a sim when its page registers. the shell's
|
|
596
|
+
// agent bridge and other page helpers share this endpoint without ever
|
|
597
|
+
// registering, so giving them a sim id at connect listed ghost sims
|
|
598
|
+
// that took an id and could be neither driven nor closed.
|
|
599
|
+
if (role === 'cli') {
|
|
613
600
|
const fallbackKey = `ws-${this.nextCliFallbackId++}`
|
|
614
601
|
this.cliIdentityKeyBySocket.set(ws, fallbackKey)
|
|
615
602
|
}
|
|
@@ -709,26 +696,30 @@ export class SootSimBridgeHost {
|
|
|
709
696
|
// Origin-based role before this socket has registered a page. This reuses the existing
|
|
710
697
|
// daemon transport and call envelope; it does not add a second routing
|
|
711
698
|
// surface or let a registered sim switch roles mid-connection.
|
|
712
|
-
if (role === 'sim' && msg.type === 'bridge:hello' &&
|
|
713
|
-
const provisionalId = sim.id
|
|
714
|
-
this.sims.delete(provisionalId)
|
|
715
|
-
if (this.primarySimId === provisionalId) {
|
|
716
|
-
this.primarySimId = this.getOpenSim()?.id ?? null
|
|
717
|
-
}
|
|
718
|
-
sim = null
|
|
699
|
+
if (role === 'sim' && msg.type === 'bridge:hello' && !sim) {
|
|
719
700
|
role = 'cli'
|
|
720
701
|
this.cliIdentityKeyBySocket.set(ws, `ws-${this.nextCliFallbackId++}`)
|
|
721
|
-
this.writeConnectedRuntimeSnapshot()
|
|
722
|
-
this.broadcastSimAssignments()
|
|
723
|
-
this.broadcastSimClientStates()
|
|
724
702
|
}
|
|
725
703
|
|
|
726
704
|
if (role === 'sim') {
|
|
727
705
|
if (sim) {
|
|
728
706
|
sim.lastSeenAt = Date.now()
|
|
729
707
|
}
|
|
730
|
-
if (msg.type === 'bridge:register'
|
|
708
|
+
if (msg.type === 'bridge:register') {
|
|
731
709
|
const registration = msg as BridgeSimRegistrationMessage
|
|
710
|
+
const firstRegistration = !sim
|
|
711
|
+
if (!sim) {
|
|
712
|
+
sim = {
|
|
713
|
+
id: this.allocateSimId(),
|
|
714
|
+
ws,
|
|
715
|
+
origin,
|
|
716
|
+
connectedAt: Date.now(),
|
|
717
|
+
lastSeenAt: Date.now(),
|
|
718
|
+
lastActiveAt: 0,
|
|
719
|
+
recentActions: [],
|
|
720
|
+
}
|
|
721
|
+
this.sims.set(sim.id, sim)
|
|
722
|
+
}
|
|
732
723
|
const restored = this.tryRestoreSimId(sim, registration.simId)
|
|
733
724
|
sim.url = registration.url
|
|
734
725
|
sim.title = registration.title
|
|
@@ -743,16 +734,12 @@ export class SootSimBridgeHost {
|
|
|
743
734
|
if (registration.meta && typeof registration.meta === 'object') {
|
|
744
735
|
sim.meta = registration.meta as Record<string, unknown>
|
|
745
736
|
}
|
|
746
|
-
//
|
|
747
|
-
//
|
|
748
|
-
//
|
|
749
|
-
// last-resort fallback (or a stale zombie still holding it)
|
|
750
|
-
// would never be superseded by this freshly-registered, actually
|
|
751
|
-
// driveable sim (QA F19-2). only adopt a *different* sim so a
|
|
752
|
-
// routine heartbeat re-register doesn't churn the assignment.
|
|
737
|
+
// elect primary once the sim has a page; only adopt a *different*
|
|
738
|
+
// sim so a routine heartbeat re-register doesn't churn the
|
|
739
|
+
// assignment.
|
|
753
740
|
const reElected = this.primarySimId !== sim.id && this.shouldPromoteSim(sim)
|
|
754
741
|
if (reElected) this.primarySimId = sim.id
|
|
755
|
-
if (restored || reElected) {
|
|
742
|
+
if (firstRegistration || restored || reElected) {
|
|
756
743
|
this.broadcastSimAssignments()
|
|
757
744
|
this.broadcastSimClientStates()
|
|
758
745
|
}
|
package/src/kotlin-app.ts
CHANGED
|
@@ -5,7 +5,11 @@
|
|
|
5
5
|
|
|
6
6
|
import fs from 'node:fs'
|
|
7
7
|
import path from 'node:path'
|
|
8
|
-
import {
|
|
8
|
+
import {
|
|
9
|
+
frameworkColor,
|
|
10
|
+
readResourceValues,
|
|
11
|
+
resolveThemeAttribute,
|
|
12
|
+
} from './android-theme'
|
|
9
13
|
import { parseXml, type XmlElement } from './android-xml'
|
|
10
14
|
import frameworkThemes from './generated/android-framework-themes.json' with { type: 'json' }
|
|
11
15
|
|
|
@@ -18,10 +22,11 @@ export interface KotlinAppProject {
|
|
|
18
22
|
namespace: string
|
|
19
23
|
targetSdk: number
|
|
20
24
|
// the launched activity's window as its theme and target SDK configure it;
|
|
21
|
-
// statusBarColor
|
|
25
|
+
// statusBarColor and navigationBarColor are ARGB
|
|
22
26
|
window: {
|
|
23
27
|
edgeToEdgeEnforced: boolean
|
|
24
28
|
statusBarColor: number
|
|
29
|
+
navigationBarColor: number
|
|
25
30
|
statusBarContrastEnforced: boolean
|
|
26
31
|
navigationBarContrastEnforced: boolean
|
|
27
32
|
lightStatusBar: boolean
|
|
@@ -221,9 +226,22 @@ function readWindowTheme(
|
|
|
221
226
|
lightStatusBar: flag('windowLightStatusBar'),
|
|
222
227
|
lightNavigationBar: flag('windowLightNavigationBar'),
|
|
223
228
|
}
|
|
229
|
+
// the theme's navigation bar color; left at the framework's transparent
|
|
230
|
+
// default, a window that is not edge to edge takes the compatible color
|
|
231
|
+
// unless the device's config_navBarDefaultTransparent is set, which it is
|
|
232
|
+
// not on the pixel 8 android 17 device the oracle measures
|
|
233
|
+
const navigationColor = resolveThemeAttribute(name, 'navigationBarColor', sources)
|
|
234
|
+
if (typeof navigationColor === 'boolean')
|
|
235
|
+
throw new Error(`${name}: navigationBarColor is not a color`)
|
|
236
|
+
const navigationDefault = frameworkColor('navigation_bar_default', sources)
|
|
237
|
+
const navigationTheme = navigationColor ?? navigationDefault
|
|
238
|
+
const navigationBarColor =
|
|
239
|
+
navigationTheme === navigationDefault && !edgeToEdgeEnforced
|
|
240
|
+
? frameworkColor('navigation_bar_compatible', sources)
|
|
241
|
+
: navigationTheme
|
|
224
242
|
// PhoneWindow enforces edge to edge apart from whether the theme draws bar
|
|
225
243
|
// backgrounds: either way the bar is transparent
|
|
226
|
-
if (edgeToEdgeEnforced) return { ...window, statusBarColor: 0 }
|
|
244
|
+
if (edgeToEdgeEnforced) return { ...window, statusBarColor: 0, navigationBarColor }
|
|
227
245
|
if (!flag('windowDrawsSystemBarBackgrounds'))
|
|
228
246
|
throw new Error(
|
|
229
247
|
`${name} does not draw system bar backgrounds; peach kotlin draws only theme status bar colors`,
|
|
@@ -231,7 +249,7 @@ function readWindowTheme(
|
|
|
231
249
|
const color = resolveThemeAttribute(name, 'statusBarColor', sources)
|
|
232
250
|
if (typeof color === 'boolean')
|
|
233
251
|
throw new Error(`${name}: statusBarColor is not a color`)
|
|
234
|
-
return { ...window, statusBarColor: color ?? 0xff000000 }
|
|
252
|
+
return { ...window, statusBarColor: color ?? 0xff000000, navigationBarColor }
|
|
235
253
|
}
|
|
236
254
|
|
|
237
255
|
function enabled(element: XmlElement): boolean {
|
package/src/kotlin-host.ts
CHANGED
|
@@ -337,13 +337,14 @@ export async function kotlinAppArtifact(
|
|
|
337
337
|
'',
|
|
338
338
|
'@JsExport',
|
|
339
339
|
'fun peachAppMount(density: Float, fontScale: Float, locale: String, backend: String, stencilBits: Int,',
|
|
340
|
-
' regular: String, medium: String, bold: String,
|
|
340
|
+
' regular: String, medium: String, bold: String, insets: String, night: Boolean,',
|
|
341
341
|
' invalidate: (Boolean) -> Unit): String = dev.peach.app.peachStartApp(',
|
|
342
342
|
' dev.peach.app.PeachAppSpec(',
|
|
343
343
|
` android.content.ComponentName(${kotlinString(app.packageName)}, ${kotlinString(app.launcher.name)}),`,
|
|
344
344
|
` android.content.ComponentName(${kotlinString(app.packageName)}, ${kotlinString(app.launcher.targetClass)}),`,
|
|
345
345
|
' android.view.WindowTheme(',
|
|
346
346
|
` ${app.window.edgeToEdgeEnforced}, 0x${app.window.statusBarColor.toString(16).padStart(8, '0')}.toInt(),`,
|
|
347
|
+
` 0x${app.window.navigationBarColor.toString(16).padStart(8, '0')}.toInt(),`,
|
|
347
348
|
` ${app.window.statusBarContrastEnforced}, ${app.window.navigationBarContrastEnforced},`,
|
|
348
349
|
` ${app.window.lightStatusBar}, ${app.window.lightNavigationBar},`,
|
|
349
350
|
' ),',
|
|
@@ -351,7 +352,7 @@ export async function kotlinAppArtifact(
|
|
|
351
352
|
` { ${app.launcher.targetClass}() },`,
|
|
352
353
|
` ${app.handlesUiMode},`,
|
|
353
354
|
' ),',
|
|
354
|
-
' density, fontScale, locale, backend, stencilBits, regular, medium, bold,
|
|
355
|
+
' density, fontScale, locale, backend, stencilBits, regular, medium, bold, insets, night, invalidate,',
|
|
355
356
|
')',
|
|
356
357
|
'',
|
|
357
358
|
'@JsExport',
|