peachsim 0.1.659 → 0.1.660
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/cli-update.ts +53 -21
- package/cli/commands/compat.ts +6 -2
- package/cli/commands/control.ts +12 -3
- package/cli/commands/inspect.ts +16 -14
- package/cli/commands/upgrade.ts +4 -2
- package/cli/current-sim.ts +4 -0
- 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 +5 -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 +5 -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 +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 +8 -4
- 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/app/Activity.kt +48 -3
- package/kotlin/AppSources/android/app/Application.kt +4 -0
- package/kotlin/AppSources/android/content/res/Configuration.kt +4 -0
- package/kotlin/AppSources/android/content/res/Resources.kt +3 -1
- package/kotlin/AppSources/androidx/activity/ComponentActivity.kt +36 -3
- package/kotlin/AppSources/dev/peach/app/PeachApp.kt +207 -73
- package/kotlin/ComposeSources/PeachComposeHost.kt +15 -3
- package/kotlin/fork/README.md +1 -0
- package/kotlin/fork/compose-worker-globals.patch +26 -0
- package/kotlin/fork/fork.json +1 -1
- package/kotlin/toolchain.json +3 -3
- package/package.json +1 -1
- package/src/kotlin-app.ts +7 -0
- package/src/kotlin-host.ts +4 -0
|
@@ -24,13 +24,16 @@ import androidx.compose.ui.platform.PlatformInsets
|
|
|
24
24
|
import androidx.compose.ui.platform.PlatformWindowInsets
|
|
25
25
|
import androidx.compose.runtime.getValue
|
|
26
26
|
import androidx.compose.runtime.key
|
|
27
|
+
import androidx.compose.runtime.mutableIntStateOf
|
|
27
28
|
import androidx.compose.runtime.mutableStateOf
|
|
28
29
|
import androidx.compose.runtime.setValue
|
|
30
|
+
import androidx.compose.ui.text.font.FontFamily
|
|
29
31
|
import androidx.lifecycle.Lifecycle
|
|
30
32
|
import androidx.navigationevent.NavigationEventDispatcher
|
|
31
33
|
import androidx.navigationevent.NavigationEventDispatcherOwner
|
|
32
34
|
import PeachComposeMount
|
|
33
35
|
import addMount
|
|
36
|
+
import mounts
|
|
34
37
|
import preparePeachDevice
|
|
35
38
|
import robotoFamily
|
|
36
39
|
import org.json.JSONObject
|
|
@@ -45,6 +48,9 @@ internal class PeachAppSpec(
|
|
|
45
48
|
val window: WindowTheme,
|
|
46
49
|
val createApplication: () -> Application,
|
|
47
50
|
val createActivity: () -> Activity,
|
|
51
|
+
// the activity's configChanges includes uiMode: it takes a night mode
|
|
52
|
+
// change in onConfigurationChanged rather than relaunching
|
|
53
|
+
val handlesUiMode: Boolean,
|
|
48
54
|
)
|
|
49
55
|
|
|
50
56
|
// the system bars the device delivers to the window's content, in pixels: all
|
|
@@ -71,6 +77,41 @@ private class ActivityOwners(activity: ComponentActivity) : PlatformArchitecture
|
|
|
71
77
|
}
|
|
72
78
|
}
|
|
73
79
|
|
|
80
|
+
// the device and the application one started app process keeps, and what
|
|
81
|
+
// ActivityThread's record holds for its one activity: whether it is paused or
|
|
82
|
+
// stopped, and the state it saved when it stopped, until it resumes
|
|
83
|
+
private class AppProcess(
|
|
84
|
+
val spec: PeachAppSpec,
|
|
85
|
+
val application: Application,
|
|
86
|
+
val density: Float, val fontScale: Float, val locale: String, val gpu: Boolean, val stencilBits: Int,
|
|
87
|
+
val fonts: FontFamily,
|
|
88
|
+
val statusBar: Int, val navigationBar: Int,
|
|
89
|
+
val invalidate: (Boolean) -> Unit,
|
|
90
|
+
) {
|
|
91
|
+
lateinit var activity: Activity
|
|
92
|
+
lateinit var mount: PeachComposeMount
|
|
93
|
+
// the window the device presents, once its scene is in place
|
|
94
|
+
var window: Window? = null
|
|
95
|
+
var mountId = 0
|
|
96
|
+
// the held view's place among its window's views; a relaunched window's
|
|
97
|
+
// first view takes the place the last window's view held, as it takes
|
|
98
|
+
// that view's saved state on the device
|
|
99
|
+
var view = 0
|
|
100
|
+
var paused = false
|
|
101
|
+
var stopped = false
|
|
102
|
+
var state: Bundle? = null
|
|
103
|
+
// the night mode last reported to the activity: ActivityRecord compares
|
|
104
|
+
// the configuration with it before a relaunch or onConfigurationChanged
|
|
105
|
+
var reportedNight = false
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
private var process: AppProcess? = null
|
|
109
|
+
|
|
110
|
+
private fun nightConfiguration(night: Boolean) = Configuration().apply {
|
|
111
|
+
uiMode = Configuration.UI_MODE_TYPE_NORMAL or
|
|
112
|
+
if (night) Configuration.UI_MODE_NIGHT_YES else Configuration.UI_MODE_NIGHT_NO
|
|
113
|
+
}
|
|
114
|
+
|
|
74
115
|
// a cold start in ActivityThread's order: the Application and its onCreate,
|
|
75
116
|
// then the launcher activity attached to its window and launcher intent,
|
|
76
117
|
// onCreate, onStart, onPostCreate, onResume and onPostResume, and only then the
|
|
@@ -85,65 +126,17 @@ internal fun peachStartApp(
|
|
|
85
126
|
try {
|
|
86
127
|
require(backend == "graphite" || backend == "raster")
|
|
87
128
|
preparePeachDevice(locale)
|
|
88
|
-
processResources = Resources(
|
|
89
|
-
uiMode = Configuration.UI_MODE_TYPE_NORMAL or
|
|
90
|
-
if (night) Configuration.UI_MODE_NIGHT_YES else Configuration.UI_MODE_NIGHT_NO
|
|
91
|
-
})
|
|
92
|
-
val fonts = robotoFamily(regular, medium, bold)
|
|
129
|
+
processResources = Resources(nightConfiguration(night))
|
|
93
130
|
val application = spec.createApplication()
|
|
94
131
|
application.onCreate()
|
|
95
|
-
val
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
lateinit var mount: PeachComposeMount
|
|
99
|
-
var mountId = 0
|
|
100
|
-
// the window's one scene composes the ComposeView it holds; a new
|
|
101
|
-
// view starts a new composition, as on the device
|
|
102
|
-
var held by mutableStateOf<ComposeView?>(null)
|
|
103
|
-
val host = object : PeachWindowHost {
|
|
104
|
-
override fun compose(view: ComposeView): AutoCloseable {
|
|
105
|
-
held = view
|
|
106
|
-
return AutoCloseable { if (held === view) held = null }
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
override fun windowChanged() {
|
|
110
|
-
if (mountId != 0)
|
|
111
|
-
mount.emitAtFrame("window") { window.describe().toString() }
|
|
112
|
-
}
|
|
113
|
-
}
|
|
114
|
-
window = Window(activity, spec.window, host)
|
|
115
|
-
activity.attach(
|
|
116
|
-
application,
|
|
117
|
-
spec.component,
|
|
118
|
-
Intent(Intent.ACTION_MAIN, setOf(Intent.CATEGORY_LAUNCHER), spec.launcher),
|
|
119
|
-
window,
|
|
132
|
+
val app = AppProcess(
|
|
133
|
+
spec, application, density, fontScale, locale, backend == "graphite", stencilBits,
|
|
134
|
+
robotoFamily(regular, medium, bold), statusBar, navigationBar, invalidate,
|
|
120
135
|
)
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
activity.performPostCreate(null)
|
|
124
|
-
activity.performResume()
|
|
125
|
-
running = activity
|
|
126
|
-
mount = PeachComposeMount(
|
|
127
|
-
density, fontScale, locale, backend == "graphite", stencilBits, fonts, invalidate,
|
|
128
|
-
owners = componentActivity?.let(::ActivityOwners),
|
|
129
|
-
insets = SystemBarInsets(window, statusBar, navigationBar),
|
|
130
|
-
clockRunning = { componentActivity == null || componentActivity.lifecycle.currentState.isAtLeast(Lifecycle.State.STARTED) },
|
|
131
|
-
)
|
|
132
|
-
mount.setContent {
|
|
133
|
-
val content = @Composable { held?.let { view -> key(view) { view.Content() } }; Unit }
|
|
134
|
-
// the content view's tree resolves the activity as its back dispatcher owner
|
|
135
|
-
if (componentActivity != null)
|
|
136
|
-
CompositionLocalProvider(LocalOnBackPressedDispatcherOwner provides componentActivity, content = content)
|
|
137
|
-
else content()
|
|
138
|
-
}
|
|
139
|
-
mountId = addMount(mount)
|
|
140
|
-
runningMount = mount
|
|
141
|
-
window.addToWindowManager()
|
|
142
|
-
val view = checkNotNull(held) {
|
|
143
|
-
"${spec.component.flattenToShortString()} set no ComposeView as its content; Peach composes only ComposeView content"
|
|
144
|
-
}
|
|
136
|
+
process = app
|
|
137
|
+
val view = launchActivity(app, null)
|
|
145
138
|
val params = view.layoutParams
|
|
146
|
-
return window.describe().put("ok", true).put("id", mountId)
|
|
139
|
+
return app.activity.window.describe().put("ok", true).put("id", app.mountId)
|
|
147
140
|
.put("wrapWidth", params?.width == ViewGroup.LayoutParams.WRAP_CONTENT)
|
|
148
141
|
.put("wrapHeight", params?.height == ViewGroup.LayoutParams.WRAP_CONTENT)
|
|
149
142
|
.toString()
|
|
@@ -153,9 +146,89 @@ internal fun peachStartApp(
|
|
|
153
146
|
}
|
|
154
147
|
}
|
|
155
148
|
|
|
156
|
-
//
|
|
157
|
-
|
|
158
|
-
|
|
149
|
+
// ActivityThread's handleLaunchActivity: a new instance of the launcher
|
|
150
|
+
// activity created from the state its last instance saved, if any, with what
|
|
151
|
+
// that instance retained, then started, restored, resumed and its window
|
|
152
|
+
// added. the window's scene takes the process's mount id, so the device keeps
|
|
153
|
+
// presenting it.
|
|
154
|
+
private fun launchActivity(app: AppProcess, lastNonConfigurationInstance: Any?): ComposeView {
|
|
155
|
+
val spec = app.spec
|
|
156
|
+
val activity = spec.createActivity()
|
|
157
|
+
val componentActivity = activity as? ComponentActivity
|
|
158
|
+
val state = app.state
|
|
159
|
+
lateinit var window: Window
|
|
160
|
+
lateinit var mount: PeachComposeMount
|
|
161
|
+
// the window's one scene composes the ComposeView it holds; a new
|
|
162
|
+
// view starts a new composition, as on the device. the key is the view's
|
|
163
|
+
// place, not the view, so a relaunched window's view lands on the keys its
|
|
164
|
+
// saved state was written under
|
|
165
|
+
var held by mutableStateOf<ComposeView?>(null)
|
|
166
|
+
var place by mutableIntStateOf((app.view - 1).coerceAtLeast(0))
|
|
167
|
+
val host = object : PeachWindowHost {
|
|
168
|
+
override fun compose(view: ComposeView): AutoCloseable {
|
|
169
|
+
held = view
|
|
170
|
+
place += 1
|
|
171
|
+
app.view = place
|
|
172
|
+
return AutoCloseable { if (held === view) held = null }
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
// a change before the window is presented reaches the device with
|
|
176
|
+
// the window itself
|
|
177
|
+
override fun windowChanged() {
|
|
178
|
+
if (app.window === window)
|
|
179
|
+
mount.emitAtFrame("window") { window.describe().toString() }
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
window = Window(activity, spec.window, host)
|
|
183
|
+
activity.attach(
|
|
184
|
+
app.application,
|
|
185
|
+
spec.component,
|
|
186
|
+
Intent(Intent.ACTION_MAIN, setOf(Intent.CATEGORY_LAUNCHER), spec.launcher),
|
|
187
|
+
window,
|
|
188
|
+
lastNonConfigurationInstance,
|
|
189
|
+
)
|
|
190
|
+
activity.performCreate(state)
|
|
191
|
+
activity.performStart()
|
|
192
|
+
if (state != null) activity.performRestoreInstanceState(state)
|
|
193
|
+
activity.performPostCreate(state)
|
|
194
|
+
activity.performResume()
|
|
195
|
+
app.activity = activity
|
|
196
|
+
app.paused = false
|
|
197
|
+
app.stopped = false
|
|
198
|
+
app.state = null
|
|
199
|
+
app.reportedNight = isNight(activity.resources.configuration)
|
|
200
|
+
mount = PeachComposeMount(
|
|
201
|
+
app.density, app.fontScale, app.locale, app.gpu, app.stencilBits, app.fonts, app.invalidate,
|
|
202
|
+
night = isNight(activity.resources.configuration),
|
|
203
|
+
owners = componentActivity?.let(::ActivityOwners),
|
|
204
|
+
insets = SystemBarInsets(window, app.statusBar, app.navigationBar),
|
|
205
|
+
clockRunning = { componentActivity == null || componentActivity.lifecycle.currentState.isAtLeast(Lifecycle.State.STARTED) },
|
|
206
|
+
)
|
|
207
|
+
mount.setContent {
|
|
208
|
+
val content = @Composable { held?.let { view -> key(place) { view.Content() } }; Unit }
|
|
209
|
+
// the content view's tree resolves the activity as its back dispatcher owner
|
|
210
|
+
if (componentActivity != null)
|
|
211
|
+
CompositionLocalProvider(LocalOnBackPressedDispatcherOwner provides componentActivity, content = content)
|
|
212
|
+
else content()
|
|
213
|
+
}
|
|
214
|
+
if (app.mountId == 0) app.mountId = addMount(mount)
|
|
215
|
+
else {
|
|
216
|
+
// the relaunched window takes the device's bounds and reports its
|
|
217
|
+
// window, which the new instance may have set differently
|
|
218
|
+
mount.constraints = app.mount.constraints
|
|
219
|
+
mounts[app.mountId] = mount
|
|
220
|
+
mount.emitAtFrame("window") { window.describe().toString() }
|
|
221
|
+
app.invalidate(true)
|
|
222
|
+
}
|
|
223
|
+
app.mount = mount
|
|
224
|
+
app.window = window
|
|
225
|
+
window.addToWindowManager()
|
|
226
|
+
return checkNotNull(held) {
|
|
227
|
+
"${spec.component.flattenToShortString()} set no ComposeView as its content; Peach composes only ComposeView content"
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
private fun runningApp() = checkNotNull(process) { "the app is not running" }
|
|
159
232
|
|
|
160
233
|
// the device's back: an activity with predictive back gets it through its
|
|
161
234
|
// window's back dispatch, where ComponentActivity's dispatcher takes it only
|
|
@@ -163,7 +236,7 @@ private var runningMount: PeachComposeMount? = null
|
|
|
163
236
|
// rest; without predictive back the key reaches onBackPressed. false when the
|
|
164
237
|
// task moved to the back, which the device then shows as home.
|
|
165
238
|
internal fun peachAppBack(predictive: Boolean): Boolean {
|
|
166
|
-
val activity =
|
|
239
|
+
val activity = runningApp().activity
|
|
167
240
|
activity.movedTaskToBack = false
|
|
168
241
|
val dispatcher = (activity as? ComponentActivity)?.onBackPressedDispatcher
|
|
169
242
|
if (!predictive) activity.onBackPressed()
|
|
@@ -172,20 +245,81 @@ internal fun peachAppBack(predictive: Boolean): Boolean {
|
|
|
172
245
|
return !activity.movedTaskToBack
|
|
173
246
|
}
|
|
174
247
|
|
|
175
|
-
//
|
|
176
|
-
//
|
|
248
|
+
// ActivityThread's pause and stop; from API 28 an activity that stops saves
|
|
249
|
+
// its state after onStop, kept for a relaunch until it resumes
|
|
250
|
+
private fun pauseAndStop(app: AppProcess) {
|
|
251
|
+
if (!app.paused) {
|
|
252
|
+
app.activity.performPause()
|
|
253
|
+
app.paused = true
|
|
254
|
+
}
|
|
255
|
+
if (!app.stopped) {
|
|
256
|
+
app.activity.performStop()
|
|
257
|
+
app.stopped = true
|
|
258
|
+
app.state = Bundle().also(app.activity::performSaveInstanceState)
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
// the task left the foreground (onPause, onStop, onSaveInstanceState) or came
|
|
263
|
+
// back to it (onRestart, onStart, onResume, onPostResume). a configuration
|
|
264
|
+
// that changed while it was stopped reaches it first: a relaunch in place of
|
|
265
|
+
// the restart, or onConfigurationChanged.
|
|
177
266
|
internal fun peachAppTask(foreground: Boolean) {
|
|
178
|
-
val
|
|
179
|
-
if (foreground)
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
267
|
+
val app = runningApp()
|
|
268
|
+
if (!foreground) return pauseAndStop(app)
|
|
269
|
+
if (reportConfiguration(app)) return
|
|
270
|
+
app.activity.performRestart()
|
|
271
|
+
app.activity.performResume()
|
|
272
|
+
app.paused = false
|
|
273
|
+
app.stopped = false
|
|
274
|
+
app.state = null
|
|
275
|
+
// the frame clock runs again once the activity is started
|
|
276
|
+
app.mount.invalidate(false)
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
// gives a visible activity the process configuration when it differs from the
|
|
280
|
+
// one last reported to it: a relaunch, or onConfigurationChanged under
|
|
281
|
+
// configChanges uiMode. true when it relaunched.
|
|
282
|
+
private fun reportConfiguration(app: AppProcess): Boolean {
|
|
283
|
+
val configuration = processResources.configuration
|
|
284
|
+
if (isNight(configuration) == app.reportedNight) return false
|
|
285
|
+
if (!app.spec.handlesUiMode) {
|
|
286
|
+
relaunch(app)
|
|
287
|
+
return true
|
|
288
|
+
}
|
|
289
|
+
app.reportedNight = isNight(configuration)
|
|
290
|
+
app.activity.performConfigurationChanged(Configuration().apply { setTo(configuration) })
|
|
291
|
+
app.mount.night = app.reportedNight
|
|
292
|
+
return false
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
private fun isNight(configuration: Configuration) =
|
|
296
|
+
configuration.uiMode and Configuration.UI_MODE_NIGHT_MASK == Configuration.UI_MODE_NIGHT_YES
|
|
297
|
+
|
|
298
|
+
// the device's night mode changed. the process's configuration changes in
|
|
299
|
+
// place and the Application hears it first; a visible activity then takes it.
|
|
300
|
+
// a stopped activity takes it once visible again, if it still differs.
|
|
301
|
+
internal fun peachAppNight(night: Boolean) {
|
|
302
|
+
val app = runningApp()
|
|
303
|
+
val configuration = processResources.configuration
|
|
304
|
+
if (isNight(configuration) == night) return
|
|
305
|
+
configuration.setTo(nightConfiguration(night))
|
|
306
|
+
app.application.onConfigurationChanged(Configuration().apply { setTo(configuration) })
|
|
307
|
+
if (!app.stopped) reportConfiguration(app)
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
// ActivityThread's handleRelaunchActivityInner: pause and stop with the state
|
|
311
|
+
// saved, collect what the next instance keeps, destroy, then launch anew
|
|
312
|
+
private fun relaunch(app: AppProcess) {
|
|
313
|
+
val old = app.activity
|
|
314
|
+
val oldMount = app.mount
|
|
315
|
+
old.isChangingConfigurations = true
|
|
316
|
+
pauseAndStop(app)
|
|
317
|
+
val retained = old.retainNonConfigurationInstances()
|
|
318
|
+
old.performDestroy()
|
|
319
|
+
old.window.removeFromWindowManager()
|
|
320
|
+
try {
|
|
321
|
+
launchActivity(app, retained)
|
|
322
|
+
} finally {
|
|
323
|
+
oldMount.close()
|
|
190
324
|
}
|
|
191
325
|
}
|
|
@@ -7,6 +7,7 @@ import androidx.compose.runtime.mutableStateOf
|
|
|
7
7
|
import androidx.compose.runtime.remember
|
|
8
8
|
import androidx.compose.runtime.setValue
|
|
9
9
|
import androidx.compose.runtime.snapshots.Snapshot
|
|
10
|
+
import androidx.compose.ui.LocalSystemTheme
|
|
10
11
|
import androidx.compose.ui.geometry.Offset
|
|
11
12
|
import androidx.compose.ui.graphics.asComposeCanvas
|
|
12
13
|
import androidx.compose.ui.input.InputMode
|
|
@@ -44,6 +45,7 @@ import androidx.compose.ui.unit.LayoutDirection
|
|
|
44
45
|
import dev.onejs.one.OneNativeSourceViewDispatch
|
|
45
46
|
import kotlin.io.encoding.Base64
|
|
46
47
|
import kotlinx.coroutines.Dispatchers
|
|
48
|
+
import org.jetbrains.skiko.SystemTheme
|
|
47
49
|
import org.jetbrains.skia.BackendRenderTarget
|
|
48
50
|
import org.jetbrains.skia.Bitmap
|
|
49
51
|
import org.jetbrains.skia.Color
|
|
@@ -123,11 +125,14 @@ internal class PeachComposeMount(
|
|
|
123
125
|
private val stencilBits: Int,
|
|
124
126
|
private val fonts: FontFamily,
|
|
125
127
|
val invalidate: (Boolean) -> Unit,
|
|
128
|
+
night: Boolean,
|
|
126
129
|
owners: PlatformArchitectureComponentsOwner? = null,
|
|
127
130
|
insets: PlatformWindowInsets? = null,
|
|
128
131
|
private val clockRunning: () -> Boolean = { true },
|
|
129
132
|
) : AutoCloseable {
|
|
130
133
|
var props by mutableStateOf("")
|
|
134
|
+
// the configuration's night mode, which isSystemInDarkTheme reads
|
|
135
|
+
var night by mutableStateOf(night)
|
|
131
136
|
var constraints = Constraints()
|
|
132
137
|
set(value) { if (field != value) { field = value; needsMeasure = true } }
|
|
133
138
|
private val events = mutableListOf<JSONObject>()
|
|
@@ -150,7 +155,11 @@ internal class PeachComposeMount(
|
|
|
150
155
|
|
|
151
156
|
fun setContent(content: @Composable () -> Unit) {
|
|
152
157
|
try {
|
|
153
|
-
scene.setContent {
|
|
158
|
+
scene.setContent {
|
|
159
|
+
CompositionLocalProvider(LocalSystemTheme provides if (night) SystemTheme.DARK else SystemTheme.LIGHT) {
|
|
160
|
+
PeachAndroidFonts(fonts, content)
|
|
161
|
+
}
|
|
162
|
+
}
|
|
154
163
|
} catch (failure: Throwable) {
|
|
155
164
|
close()
|
|
156
165
|
throw failure
|
|
@@ -289,7 +298,7 @@ internal fun robotoFamily(regular: String, medium: String, bold: String) = FontF
|
|
|
289
298
|
|
|
290
299
|
@JsExport
|
|
291
300
|
fun peachViewMount(source: String, view: String, hash: String, props: String,
|
|
292
|
-
density: Float, fontScale: Float, locale: String, backend: String, stencilBits: Int,
|
|
301
|
+
density: Float, fontScale: Float, locale: String, night: Boolean, backend: String, stencilBits: Int,
|
|
293
302
|
regular: String, medium: String, bold: String, invalidate: (Boolean) -> Unit): String {
|
|
294
303
|
try {
|
|
295
304
|
val dispatch = peachViewDispatcher(source)
|
|
@@ -297,7 +306,7 @@ fun peachViewMount(source: String, view: String, hash: String, props: String,
|
|
|
297
306
|
.put("error", "composable source $source changed; rebuild the app").toString()
|
|
298
307
|
require(backend == "graphite" || backend == "raster")
|
|
299
308
|
preparePeachDevice(locale)
|
|
300
|
-
val mount = PeachComposeMount(density, fontScale, locale, backend == "graphite", stencilBits, robotoFamily(regular, medium, bold), invalidate)
|
|
309
|
+
val mount = PeachComposeMount(density, fontScale, locale, backend == "graphite", stencilBits, robotoFamily(regular, medium, bold), invalidate, night)
|
|
301
310
|
mount.props = props
|
|
302
311
|
mount.setContent { dispatch.Content(view, mount.props, mount::emit) }
|
|
303
312
|
return JSONObject().put("ok", true).put("id", addMount(mount)).toString()
|
|
@@ -307,6 +316,9 @@ fun peachViewMount(source: String, view: String, hash: String, props: String,
|
|
|
307
316
|
}
|
|
308
317
|
|
|
309
318
|
@JsExport fun peachViewUpdate(id: Int, props: String) { checkNotNull(mounts[id]).props = props }
|
|
319
|
+
// a host activity that handles uiMode itself, as React Native's does, keeps
|
|
320
|
+
// its views and hands them the new configuration
|
|
321
|
+
@JsExport fun peachViewNight(id: Int, night: Boolean) { checkNotNull(mounts[id]).night = night }
|
|
310
322
|
@JsExport fun peachViewConstraints(id: Int, minWidth: Int, maxWidth: Int, minHeight: Int, maxHeight: Int) {
|
|
311
323
|
checkNotNull(mounts[id]).also {
|
|
312
324
|
it.constraints = Constraints(minWidth, maxWidth, minHeight, maxHeight)
|
package/kotlin/fork/README.md
CHANGED
|
@@ -10,6 +10,7 @@ builds until upstream supplies equivalent worker and native host hooks.
|
|
|
10
10
|
| --- | --- | --- |
|
|
11
11
|
| Compose `ui` | `Actuals.wasm.kt` | clock reads `globalThis.performance.now()` |
|
|
12
12
|
| Compose `ui` | `PlatformClipboard.web.kt` | worker-safe secure-context and clipboard reads |
|
|
13
|
+
| Compose `ui` | `SystemTheme.kt` | `LocalSystemTheme` visible to the host, which provides the device's night mode |
|
|
13
14
|
| Compose `ui-text` | `PlatformLocale.web.kt` | explicit `setPlatformLocaleProvider`; its default keeps upstream navigator behavior |
|
|
14
15
|
| Skiko | `OsArch.web.kt` | explicit `setHostOsProvider`; its default lazily retains upstream OS detection |
|
|
15
16
|
| Skiko | `Actuals.web.kt` | nanosecond clock reads `globalThis.performance.now()` |
|
|
@@ -105,3 +105,29 @@ index 7aa3e26..47e20a8 100644
|
|
|
105
105
|
|
|
106
106
|
/**
|
|
107
107
|
* https://developer.mozilla.org/en-US/docs/Web/API/Clipboard_API
|
|
108
|
+
diff --git a/compose/ui/ui/src/skikoMain/kotlin/androidx/compose/ui/SystemTheme.kt b/compose/ui/ui/src/skikoMain/kotlin/androidx/compose/ui/SystemTheme.kt
|
|
109
|
+
index a14f182..9c4862d 100644
|
|
110
|
+
--- a/compose/ui/ui/src/skikoMain/kotlin/androidx/compose/ui/SystemTheme.kt
|
|
111
|
+
+++ b/compose/ui/ui/src/skikoMain/kotlin/androidx/compose/ui/SystemTheme.kt
|
|
112
|
+
@@ -12,6 +12,11 @@
|
|
113
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
114
|
+
* See the License for the specific language governing permissions and
|
|
115
|
+
* limitations under the License.
|
|
116
|
+
+ *
|
|
117
|
+
+ * modified for Peach: the system theme composition local is visible to a
|
|
118
|
+
+ * native host, which provides the device's night mode to the scenes it owns,
|
|
119
|
+
+ * as later upstream releases expose it. see the fork record for the patch and
|
|
120
|
+
+ * upstream revision.
|
|
121
|
+
*/
|
|
122
|
+
|
|
123
|
+
package androidx.compose.ui
|
|
124
|
+
@@ -27,7 +32,8 @@ enum class SystemTheme {
|
|
125
|
+
Dark, Light, Unknown
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
-internal val LocalSystemTheme = staticCompositionLocalOf { currentSystemTheme }
|
|
129
|
+
+@InternalComposeUiApi
|
|
130
|
+
+val LocalSystemTheme = staticCompositionLocalOf { currentSystemTheme }
|
|
131
|
+
|
|
132
|
+
@InternalComposeUiApi
|
|
133
|
+
@Composable
|
package/kotlin/fork/fork.json
CHANGED
|
@@ -18,7 +18,7 @@
|
|
|
18
18
|
"tag": "v1.13.0-alpha01",
|
|
19
19
|
"commit": "a9e47e8aa04c95cfe572ab4297c770f3cbfb234e",
|
|
20
20
|
"patch": "compose-worker-globals.patch",
|
|
21
|
-
"patchSha256": "
|
|
21
|
+
"patchSha256": "5c45b67b3028637e6eafa351db004f23ad50c6f00ac2b5e7738fd026ac93cef0"
|
|
22
22
|
},
|
|
23
23
|
{
|
|
24
24
|
"name": "skiko",
|
package/kotlin/toolchain.json
CHANGED
|
@@ -18,9 +18,9 @@
|
|
|
18
18
|
],
|
|
19
19
|
"composeLibraries": [
|
|
20
20
|
{
|
|
21
|
-
"name": "ui-wasm-js-1.13.0-alpha01-peach-
|
|
22
|
-
"url": "https://r2.rnxsim.com/peach-kotlin-fork/
|
|
23
|
-
"sha256": "
|
|
21
|
+
"name": "ui-wasm-js-1.13.0-alpha01-peach-8caddb83ba2f.klib",
|
|
22
|
+
"url": "https://r2.rnxsim.com/peach-kotlin-fork/8caddb83ba2f8dddd4c869eca936f0039d99c39c986f8af162e59eb27dbb92ed/ui-wasm-js-1.13.0-alpha01.klib",
|
|
23
|
+
"sha256": "8caddb83ba2f8dddd4c869eca936f0039d99c39c986f8af162e59eb27dbb92ed"
|
|
24
24
|
},
|
|
25
25
|
{
|
|
26
26
|
"name": "foundation-wasm-js-1.13.0-alpha01.klib",
|
package/package.json
CHANGED
package/src/kotlin-app.ts
CHANGED
|
@@ -32,6 +32,9 @@ export interface KotlinAppProject {
|
|
|
32
32
|
// back reaches the activity through its window's back dispatch rather than
|
|
33
33
|
// the back key's onBackPressed
|
|
34
34
|
predictiveBack: boolean
|
|
35
|
+
// the activity's configChanges includes uiMode: a night mode change reaches
|
|
36
|
+
// onConfigurationChanged instead of relaunching it
|
|
37
|
+
handlesUiMode: boolean
|
|
35
38
|
}
|
|
36
39
|
|
|
37
40
|
export interface ComposeAndroidPins {
|
|
@@ -402,6 +405,9 @@ export function readKotlinApp(
|
|
|
402
405
|
(themeOwner?.attributes[`${ANDROID_NS}enableOnBackInvokedCallback`] ??
|
|
403
406
|
application.attributes[`${ANDROID_NS}enableOnBackInvokedCallback`] ??
|
|
404
407
|
String(targetSdk > 35)) === 'true'
|
|
408
|
+
const handlesUiMode = (themeOwner?.attributes[`${ANDROID_NS}configChanges`] ?? '')
|
|
409
|
+
.split('|')
|
|
410
|
+
.includes('uiMode')
|
|
405
411
|
|
|
406
412
|
const sources: string[] = []
|
|
407
413
|
for (const set of ['java', 'kotlin']) {
|
|
@@ -432,5 +438,6 @@ export function readKotlinApp(
|
|
|
432
438
|
applicationClass: applicationName ? className(applicationName) : null,
|
|
433
439
|
launcher: { name: launcherName, targetClass },
|
|
434
440
|
predictiveBack,
|
|
441
|
+
handlesUiMode,
|
|
435
442
|
}
|
|
436
443
|
}
|
package/src/kotlin-host.ts
CHANGED
|
@@ -349,6 +349,7 @@ export async function kotlinAppArtifact(
|
|
|
349
349
|
' ),',
|
|
350
350
|
` { ${app.applicationClass ?? 'android.app.Application'}() },`,
|
|
351
351
|
` { ${app.launcher.targetClass}() },`,
|
|
352
|
+
` ${app.handlesUiMode},`,
|
|
352
353
|
' ),',
|
|
353
354
|
' density, fontScale, locale, backend, stencilBits, regular, medium, bold, statusBar, navigationBar, night, invalidate,',
|
|
354
355
|
')',
|
|
@@ -359,6 +360,9 @@ export async function kotlinAppArtifact(
|
|
|
359
360
|
'@JsExport',
|
|
360
361
|
'fun peachAppTask(foreground: Boolean) = dev.peach.app.peachAppTask(foreground)',
|
|
361
362
|
'',
|
|
363
|
+
'@JsExport',
|
|
364
|
+
'fun peachAppNight(night: Boolean) = dev.peach.app.peachAppNight(night)',
|
|
365
|
+
'',
|
|
362
366
|
].join('\n'),
|
|
363
367
|
}
|
|
364
368
|
const shims = [
|