peachsim 0.1.657 → 0.1.659
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 +21 -2
- package/cli/app-config.ts +25 -9
- package/cli/bridge-flow-runner.ts +10 -23
- package/cli/commands/compat.ts +234 -10
- package/cli/commands/do-chain.ts +5 -1
- package/cli/commands/inspect/actions.ts +33 -76
- package/cli/commands/inspect/resolve-target.ts +67 -2
- package/cli/commands/inspect.ts +16 -1
- package/cli/drivers/playwright-provisioning.ts +95 -0
- package/cli/ws-bridge.ts +7 -7
- 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 +9 -1
- package/dist-lib/bridge-contract-input.mjs +9 -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 +3 -2
- package/dist-lib/detox/index.cjs +2 -2
- package/dist-lib/dev-bundle-resolution.cjs +1 -1
- package/dist-lib/home-paths.cjs +1 -1
- package/dist-lib/host/bridge-host.cjs +78 -35
- 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 +51 -8
- package/dist-lib/host/websocket-proxy.cjs +1 -1
- package/dist-lib/index.cjs +317 -70
- 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 +14 -4
- 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 +300 -29
- package/dist-lib/native-app-identity.cjs +58 -25
- package/dist-lib/native-app-identity.mjs +57 -25
- package/dist-lib/plist.cjs +64 -0
- package/dist-lib/plist.mjs +29 -0
- 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 +2 -2
- package/dist-lib/sdk.cjs +36 -49
- package/dist-lib/sdk.mjs +36 -49
- package/dist-lib/skills.cjs +257 -138
- 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 +51 -8
- package/kotlin/AppSources/android/app/Activity.kt +44 -0
- package/kotlin/AppSources/android/app/UiModeManager.kt +9 -0
- package/kotlin/AppSources/android/content/Context.kt +9 -2
- package/kotlin/AppSources/android/content/res/Configuration.kt +16 -0
- package/kotlin/AppSources/android/content/res/Resources.kt +7 -0
- package/kotlin/AppSources/android/graphics/Color.kt +13 -0
- package/kotlin/AppSources/android/os/Bundle.kt +9 -3
- package/kotlin/AppSources/android/view/View.kt +2 -0
- package/kotlin/AppSources/android/view/Window.kt +99 -8
- package/kotlin/AppSources/android/view/WindowInsetsController.kt +12 -0
- package/kotlin/AppSources/android/view/WindowManager.kt +16 -0
- package/kotlin/AppSources/androidx/activity/BackEventCompat.kt +17 -0
- package/kotlin/AppSources/androidx/activity/Cancellable.kt +5 -0
- package/kotlin/AppSources/androidx/activity/ComponentActivity.kt +22 -3
- package/kotlin/AppSources/androidx/activity/EdgeToEdge.kt +71 -0
- package/kotlin/AppSources/androidx/activity/OnBackPressedCallback.kt +28 -0
- package/kotlin/AppSources/androidx/activity/OnBackPressedDispatcher.kt +144 -0
- package/kotlin/AppSources/androidx/activity/OnBackPressedDispatcherOwner.kt +7 -0
- package/kotlin/AppSources/androidx/activity/compose/BackHandler.kt +55 -0
- package/kotlin/AppSources/androidx/core/view/WindowCompat.kt +32 -0
- package/kotlin/AppSources/dev/peach/app/PeachApp.kt +67 -12
- package/kotlin/AppSources/dev/peach/app/PeachWindowHost.kt +2 -1
- package/kotlin/AppSources/java/lang/Runnable.kt +6 -0
- package/kotlin/ComposeSources/PeachComposeHost.kt +11 -1
- package/package.json +8 -1
- package/skills/peach-setup/SKILL.md +27 -0
- package/src/android-theme.ts +167 -0
- package/src/android-xml.ts +70 -0
- package/src/bridge-contract-input.ts +10 -1
- package/src/bridge-contract.ts +7 -1
- package/src/config.ts +8 -0
- package/src/generated/android-framework-themes.json +6303 -0
- package/src/host/replacement-module-handler.ts +33 -0
- package/src/index.ts +1 -0
- package/src/kotlin-app.ts +86 -61
- package/src/kotlin-host.ts +13 -3
- package/src/metro-plugin.ts +46 -3
- package/src/native-app-identity.ts +75 -26
- package/src/sim-client.ts +2 -4
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
package androidx.activity
|
|
2
|
+
|
|
3
|
+
// androidx.activity 1.9.3's OnBackPressedCallback
|
|
4
|
+
abstract class OnBackPressedCallback(enabled: Boolean) {
|
|
5
|
+
var isEnabled: Boolean = enabled
|
|
6
|
+
set(value) {
|
|
7
|
+
field = value
|
|
8
|
+
enabledChangedCallback?.invoke()
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
private val cancellables = mutableListOf<Cancellable>()
|
|
12
|
+
internal var enabledChangedCallback: (() -> Unit)? = null
|
|
13
|
+
|
|
14
|
+
fun remove() = cancellables.toList().forEach { it.cancel() }
|
|
15
|
+
|
|
16
|
+
open fun handleOnBackStarted(backEvent: BackEventCompat) {}
|
|
17
|
+
open fun handleOnBackProgressed(backEvent: BackEventCompat) {}
|
|
18
|
+
abstract fun handleOnBackPressed()
|
|
19
|
+
open fun handleOnBackCancelled() {}
|
|
20
|
+
|
|
21
|
+
internal fun addCancellable(cancellable: Cancellable) {
|
|
22
|
+
cancellables.add(cancellable)
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
internal fun removeCancellable(cancellable: Cancellable) {
|
|
26
|
+
cancellables.remove(cancellable)
|
|
27
|
+
}
|
|
28
|
+
}
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
package androidx.activity
|
|
2
|
+
|
|
3
|
+
import java.lang.Runnable
|
|
4
|
+
import androidx.lifecycle.Lifecycle
|
|
5
|
+
import androidx.lifecycle.LifecycleEventObserver
|
|
6
|
+
import androidx.lifecycle.LifecycleOwner
|
|
7
|
+
|
|
8
|
+
// androidx.activity 1.9.3's OnBackPressedDispatcher. the device's back
|
|
9
|
+
// dispatch stands in for its OnBackInvokedDispatcher registration: a window
|
|
10
|
+
// with predictive back gives this dispatcher the back only while it has an
|
|
11
|
+
// enabled callback (hasEnabledCallbacks), as the registered callback does.
|
|
12
|
+
class OnBackPressedDispatcher(
|
|
13
|
+
private val fallbackOnBackPressed: Runnable?,
|
|
14
|
+
private val onHasEnabledCallbacksChanged: ((Boolean) -> Unit)?,
|
|
15
|
+
) {
|
|
16
|
+
private val onBackPressedCallbacks = ArrayDeque<OnBackPressedCallback>()
|
|
17
|
+
private var inProgressCallback: OnBackPressedCallback? = null
|
|
18
|
+
private var hasEnabledCallbacks = false
|
|
19
|
+
|
|
20
|
+
constructor(fallbackOnBackPressed: Runnable? = null) : this(fallbackOnBackPressed, null)
|
|
21
|
+
|
|
22
|
+
private fun updateEnabledCallbacks() {
|
|
23
|
+
val hadEnabledCallbacks = hasEnabledCallbacks
|
|
24
|
+
val hasEnabledCallbacks = onBackPressedCallbacks.any { it.isEnabled }
|
|
25
|
+
this.hasEnabledCallbacks = hasEnabledCallbacks
|
|
26
|
+
if (hasEnabledCallbacks != hadEnabledCallbacks)
|
|
27
|
+
onHasEnabledCallbacksChanged?.invoke(hasEnabledCallbacks)
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
fun addCallback(onBackPressedCallback: OnBackPressedCallback) {
|
|
31
|
+
addCancellableCallback(onBackPressedCallback)
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
internal fun addCancellableCallback(onBackPressedCallback: OnBackPressedCallback): Cancellable {
|
|
35
|
+
onBackPressedCallbacks.add(onBackPressedCallback)
|
|
36
|
+
val cancellable = OnBackPressedCancellable(onBackPressedCallback)
|
|
37
|
+
onBackPressedCallback.addCancellable(cancellable)
|
|
38
|
+
updateEnabledCallbacks()
|
|
39
|
+
onBackPressedCallback.enabledChangedCallback = ::updateEnabledCallbacks
|
|
40
|
+
return cancellable
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
fun addCallback(owner: LifecycleOwner, onBackPressedCallback: OnBackPressedCallback) {
|
|
44
|
+
val lifecycle = owner.lifecycle
|
|
45
|
+
if (lifecycle.currentState === Lifecycle.State.DESTROYED) return
|
|
46
|
+
onBackPressedCallback.addCancellable(LifecycleOnBackPressedCancellable(lifecycle, onBackPressedCallback))
|
|
47
|
+
updateEnabledCallbacks()
|
|
48
|
+
onBackPressedCallback.enabledChangedCallback = ::updateEnabledCallbacks
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
fun hasEnabledCallbacks(): Boolean = hasEnabledCallbacks
|
|
52
|
+
|
|
53
|
+
fun dispatchOnBackStarted(backEvent: BackEventCompat) {
|
|
54
|
+
val callback = onBackPressedCallbacks.lastOrNull { it.isEnabled }
|
|
55
|
+
if (inProgressCallback != null) dispatchOnBackCancelled()
|
|
56
|
+
inProgressCallback = callback
|
|
57
|
+
callback?.handleOnBackStarted(backEvent)
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
fun dispatchOnBackProgressed(backEvent: BackEventCompat) {
|
|
61
|
+
val callback = inProgressCallback ?: onBackPressedCallbacks.lastOrNull { it.isEnabled }
|
|
62
|
+
callback?.handleOnBackProgressed(backEvent)
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
fun onBackPressed() {
|
|
66
|
+
val callback = inProgressCallback ?: onBackPressedCallbacks.lastOrNull { it.isEnabled }
|
|
67
|
+
inProgressCallback = null
|
|
68
|
+
if (callback != null) {
|
|
69
|
+
callback.handleOnBackPressed()
|
|
70
|
+
return
|
|
71
|
+
}
|
|
72
|
+
fallbackOnBackPressed?.run()
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
fun dispatchOnBackCancelled() {
|
|
76
|
+
val callback = inProgressCallback ?: onBackPressedCallbacks.lastOrNull { it.isEnabled }
|
|
77
|
+
inProgressCallback = null
|
|
78
|
+
callback?.handleOnBackCancelled()
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
private inner class OnBackPressedCancellable(
|
|
82
|
+
private val onBackPressedCallback: OnBackPressedCallback,
|
|
83
|
+
) : Cancellable {
|
|
84
|
+
override fun cancel() {
|
|
85
|
+
onBackPressedCallbacks.remove(onBackPressedCallback)
|
|
86
|
+
if (inProgressCallback == onBackPressedCallback) {
|
|
87
|
+
onBackPressedCallback.handleOnBackCancelled()
|
|
88
|
+
inProgressCallback = null
|
|
89
|
+
}
|
|
90
|
+
onBackPressedCallback.removeCancellable(this)
|
|
91
|
+
onBackPressedCallback.enabledChangedCallback?.invoke()
|
|
92
|
+
onBackPressedCallback.enabledChangedCallback = null
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
// a lifecycle-bound callback receives back only between ON_START and ON_STOP
|
|
97
|
+
private inner class LifecycleOnBackPressedCancellable(
|
|
98
|
+
private val lifecycle: Lifecycle,
|
|
99
|
+
private val onBackPressedCallback: OnBackPressedCallback,
|
|
100
|
+
) : LifecycleEventObserver, Cancellable {
|
|
101
|
+
private var currentCancellable: Cancellable? = null
|
|
102
|
+
|
|
103
|
+
init {
|
|
104
|
+
lifecycle.addObserver(this)
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
override fun onStateChanged(source: LifecycleOwner, event: Lifecycle.Event) {
|
|
108
|
+
if (event === Lifecycle.Event.ON_START) {
|
|
109
|
+
currentCancellable = addCancellableCallback(onBackPressedCallback)
|
|
110
|
+
} else if (event === Lifecycle.Event.ON_STOP) {
|
|
111
|
+
currentCancellable?.cancel()
|
|
112
|
+
} else if (event === Lifecycle.Event.ON_DESTROY) {
|
|
113
|
+
cancel()
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
override fun cancel() {
|
|
118
|
+
lifecycle.removeObserver(this)
|
|
119
|
+
onBackPressedCallback.removeCancellable(this)
|
|
120
|
+
currentCancellable?.cancel()
|
|
121
|
+
currentCancellable = null
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
// androidx.activity's Kotlin registration: a callback from a lambda, bound to
|
|
127
|
+
// owner's lifecycle when one is given
|
|
128
|
+
fun OnBackPressedDispatcher.addCallback(
|
|
129
|
+
owner: LifecycleOwner? = null,
|
|
130
|
+
enabled: Boolean = true,
|
|
131
|
+
onBackPressed: OnBackPressedCallback.() -> Unit
|
|
132
|
+
): OnBackPressedCallback {
|
|
133
|
+
val callback = object : OnBackPressedCallback(enabled) {
|
|
134
|
+
override fun handleOnBackPressed() {
|
|
135
|
+
onBackPressed()
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
if (owner != null) {
|
|
139
|
+
addCallback(owner, callback)
|
|
140
|
+
} else {
|
|
141
|
+
addCallback(callback)
|
|
142
|
+
}
|
|
143
|
+
return callback
|
|
144
|
+
}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
package androidx.activity.compose
|
|
2
|
+
|
|
3
|
+
import androidx.activity.OnBackPressedCallback
|
|
4
|
+
import androidx.activity.OnBackPressedDispatcherOwner
|
|
5
|
+
import androidx.compose.runtime.Composable
|
|
6
|
+
import androidx.compose.runtime.DisposableEffect
|
|
7
|
+
import androidx.compose.runtime.ProvidedValue
|
|
8
|
+
import androidx.compose.runtime.SideEffect
|
|
9
|
+
import androidx.compose.runtime.compositionLocalOf
|
|
10
|
+
import androidx.compose.runtime.getValue
|
|
11
|
+
import androidx.compose.runtime.remember
|
|
12
|
+
import androidx.compose.runtime.rememberUpdatedState
|
|
13
|
+
import androidx.lifecycle.compose.LocalLifecycleOwner
|
|
14
|
+
|
|
15
|
+
// activity-compose 1.9.3's LocalOnBackPressedDispatcherOwner. on the device
|
|
16
|
+
// an unprovided value resolves from the view tree or the context, both of
|
|
17
|
+
// which reach the activity that set the content; the window host provides that
|
|
18
|
+
// activity here.
|
|
19
|
+
object LocalOnBackPressedDispatcherOwner {
|
|
20
|
+
private val LocalOnBackPressedDispatcherOwner =
|
|
21
|
+
compositionLocalOf<OnBackPressedDispatcherOwner?> { null }
|
|
22
|
+
|
|
23
|
+
val current: OnBackPressedDispatcherOwner?
|
|
24
|
+
@Composable
|
|
25
|
+
get() = LocalOnBackPressedDispatcherOwner.current
|
|
26
|
+
|
|
27
|
+
infix fun provides(dispatcherOwner: OnBackPressedDispatcherOwner): ProvidedValue<OnBackPressedDispatcherOwner?> =
|
|
28
|
+
LocalOnBackPressedDispatcherOwner.provides(dispatcherOwner)
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
// activity-compose 1.9.3's BackHandler
|
|
32
|
+
@Composable
|
|
33
|
+
fun BackHandler(enabled: Boolean = true, onBack: () -> Unit) {
|
|
34
|
+
val currentOnBack by rememberUpdatedState(onBack)
|
|
35
|
+
val backCallback = remember {
|
|
36
|
+
object : OnBackPressedCallback(enabled) {
|
|
37
|
+
override fun handleOnBackPressed() {
|
|
38
|
+
currentOnBack()
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
SideEffect {
|
|
43
|
+
backCallback.isEnabled = enabled
|
|
44
|
+
}
|
|
45
|
+
val backDispatcher = checkNotNull(LocalOnBackPressedDispatcherOwner.current) {
|
|
46
|
+
"No OnBackPressedDispatcherOwner was provided via LocalOnBackPressedDispatcherOwner"
|
|
47
|
+
}.onBackPressedDispatcher
|
|
48
|
+
val lifecycleOwner = LocalLifecycleOwner.current
|
|
49
|
+
DisposableEffect(lifecycleOwner, backDispatcher) {
|
|
50
|
+
backDispatcher.addCallback(lifecycleOwner, backCallback)
|
|
51
|
+
onDispose {
|
|
52
|
+
backCallback.remove()
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
package androidx.core.view
|
|
2
|
+
|
|
3
|
+
import android.view.View
|
|
4
|
+
import android.view.Window
|
|
5
|
+
import android.view.WindowInsetsController
|
|
6
|
+
|
|
7
|
+
// androidx.core's window helpers as they run from API 30, on the platform
|
|
8
|
+
// window and its insets controller
|
|
9
|
+
object WindowCompat {
|
|
10
|
+
fun setDecorFitsSystemWindows(window: Window, decorFitsSystemWindows: Boolean) {
|
|
11
|
+
window.setDecorFitsSystemWindows(decorFitsSystemWindows)
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
fun getInsetsController(window: Window, view: View): WindowInsetsControllerCompat =
|
|
15
|
+
WindowInsetsControllerCompat(window, view)
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
class WindowInsetsControllerCompat(window: Window, @Suppress("UNUSED_PARAMETER") view: View) {
|
|
19
|
+
private val controller = window.insetsController
|
|
20
|
+
|
|
21
|
+
private fun appearance(flag: Int) = controller.systemBarsAppearance and flag != 0
|
|
22
|
+
private fun setAppearance(flag: Int, light: Boolean) =
|
|
23
|
+
controller.setSystemBarsAppearance(if (light) flag else 0, flag)
|
|
24
|
+
|
|
25
|
+
var isAppearanceLightStatusBars: Boolean
|
|
26
|
+
get() = appearance(WindowInsetsController.APPEARANCE_LIGHT_STATUS_BARS)
|
|
27
|
+
set(light) = setAppearance(WindowInsetsController.APPEARANCE_LIGHT_STATUS_BARS, light)
|
|
28
|
+
|
|
29
|
+
var isAppearanceLightNavigationBars: Boolean
|
|
30
|
+
get() = appearance(WindowInsetsController.APPEARANCE_LIGHT_NAVIGATION_BARS)
|
|
31
|
+
set(light) = setAppearance(WindowInsetsController.APPEARANCE_LIGHT_NAVIGATION_BARS, light)
|
|
32
|
+
}
|
|
@@ -2,13 +2,22 @@
|
|
|
2
2
|
|
|
3
3
|
package dev.peach.app
|
|
4
4
|
|
|
5
|
+
import android.os.Bundle
|
|
6
|
+
|
|
5
7
|
import android.app.Activity
|
|
6
8
|
import android.app.Application
|
|
7
9
|
import android.content.ComponentName
|
|
8
10
|
import android.content.Intent
|
|
11
|
+
import android.content.res.Configuration
|
|
12
|
+
import android.content.res.Resources
|
|
13
|
+
import android.content.res.processResources
|
|
9
14
|
import android.view.ViewGroup
|
|
10
15
|
import android.view.Window
|
|
16
|
+
import android.view.WindowTheme
|
|
11
17
|
import androidx.activity.ComponentActivity
|
|
18
|
+
import androidx.activity.compose.LocalOnBackPressedDispatcherOwner
|
|
19
|
+
import androidx.compose.runtime.Composable
|
|
20
|
+
import androidx.compose.runtime.CompositionLocalProvider
|
|
12
21
|
import androidx.compose.ui.platform.ComposeView
|
|
13
22
|
import androidx.compose.ui.platform.PlatformArchitectureComponentsOwner
|
|
14
23
|
import androidx.compose.ui.platform.PlatformInsets
|
|
@@ -26,15 +35,14 @@ import preparePeachDevice
|
|
|
26
35
|
import robotoFamily
|
|
27
36
|
import org.json.JSONObject
|
|
28
37
|
|
|
29
|
-
// what the manifest
|
|
30
|
-
// intent names (an alias stays the alias), the activity class it runs,
|
|
31
|
-
//
|
|
38
|
+
// what the manifest, Gradle files and theme resolved: the launcher component
|
|
39
|
+
// the intent names (an alias stays the alias), the activity class it runs, what
|
|
40
|
+
// its window read from the theme, and constructors for the manifest's
|
|
32
41
|
// Application and that class (wasm has no reflection).
|
|
33
42
|
internal class PeachAppSpec(
|
|
34
43
|
val launcher: ComponentName,
|
|
35
44
|
val component: ComponentName,
|
|
36
|
-
val
|
|
37
|
-
val optOutEdgeToEdge: Boolean,
|
|
45
|
+
val window: WindowTheme,
|
|
38
46
|
val createApplication: () -> Application,
|
|
39
47
|
val createActivity: () -> Activity,
|
|
40
48
|
)
|
|
@@ -71,12 +79,16 @@ internal fun peachStartApp(
|
|
|
71
79
|
spec: PeachAppSpec,
|
|
72
80
|
density: Float, fontScale: Float, locale: String, backend: String, stencilBits: Int,
|
|
73
81
|
regular: String, medium: String, bold: String,
|
|
74
|
-
statusBar: Int, navigationBar: Int,
|
|
82
|
+
statusBar: Int, navigationBar: Int, night: Boolean,
|
|
75
83
|
invalidate: (Boolean) -> Unit,
|
|
76
84
|
): String {
|
|
77
85
|
try {
|
|
78
86
|
require(backend == "graphite" || backend == "raster")
|
|
79
87
|
preparePeachDevice(locale)
|
|
88
|
+
processResources = Resources(Configuration().apply {
|
|
89
|
+
uiMode = Configuration.UI_MODE_TYPE_NORMAL or
|
|
90
|
+
if (night) Configuration.UI_MODE_NIGHT_YES else Configuration.UI_MODE_NIGHT_NO
|
|
91
|
+
})
|
|
80
92
|
val fonts = robotoFamily(regular, medium, bold)
|
|
81
93
|
val application = spec.createApplication()
|
|
82
94
|
application.onCreate()
|
|
@@ -96,11 +108,10 @@ internal fun peachStartApp(
|
|
|
96
108
|
|
|
97
109
|
override fun windowChanged() {
|
|
98
110
|
if (mountId != 0)
|
|
99
|
-
mount.
|
|
111
|
+
mount.emitAtFrame("window") { window.describe().toString() }
|
|
100
112
|
}
|
|
101
113
|
}
|
|
102
|
-
|
|
103
|
-
window = Window(activity, enforced, host)
|
|
114
|
+
window = Window(activity, spec.window, host)
|
|
104
115
|
activity.attach(
|
|
105
116
|
application,
|
|
106
117
|
spec.component,
|
|
@@ -111,21 +122,28 @@ internal fun peachStartApp(
|
|
|
111
122
|
activity.performStart()
|
|
112
123
|
activity.performPostCreate(null)
|
|
113
124
|
activity.performResume()
|
|
125
|
+
running = activity
|
|
114
126
|
mount = PeachComposeMount(
|
|
115
127
|
density, fontScale, locale, backend == "graphite", stencilBits, fonts, invalidate,
|
|
116
128
|
owners = componentActivity?.let(::ActivityOwners),
|
|
117
129
|
insets = SystemBarInsets(window, statusBar, navigationBar),
|
|
118
130
|
clockRunning = { componentActivity == null || componentActivity.lifecycle.currentState.isAtLeast(Lifecycle.State.STARTED) },
|
|
119
131
|
)
|
|
120
|
-
mount.setContent {
|
|
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
|
+
}
|
|
121
139
|
mountId = addMount(mount)
|
|
140
|
+
runningMount = mount
|
|
122
141
|
window.addToWindowManager()
|
|
123
142
|
val view = checkNotNull(held) {
|
|
124
143
|
"${spec.component.flattenToShortString()} set no ComposeView as its content; Peach composes only ComposeView content"
|
|
125
144
|
}
|
|
126
145
|
val params = view.layoutParams
|
|
127
|
-
return
|
|
128
|
-
.put("decorFitsSystemWindows", window.fitsSystemWindows)
|
|
146
|
+
return window.describe().put("ok", true).put("id", mountId)
|
|
129
147
|
.put("wrapWidth", params?.width == ViewGroup.LayoutParams.WRAP_CONTENT)
|
|
130
148
|
.put("wrapHeight", params?.height == ViewGroup.LayoutParams.WRAP_CONTENT)
|
|
131
149
|
.toString()
|
|
@@ -134,3 +152,40 @@ internal fun peachStartApp(
|
|
|
134
152
|
.put("error", "${error::class.simpleName}: ${error.message}").toString()
|
|
135
153
|
}
|
|
136
154
|
}
|
|
155
|
+
|
|
156
|
+
// the activity the app runs, once started, and the scene its window composes
|
|
157
|
+
private var running: Activity? = null
|
|
158
|
+
private var runningMount: PeachComposeMount? = null
|
|
159
|
+
|
|
160
|
+
// the device's back: an activity with predictive back gets it through its
|
|
161
|
+
// window's back dispatch, where ComponentActivity's dispatcher takes it only
|
|
162
|
+
// while a callback is enabled and the activity's own default handles the
|
|
163
|
+
// rest; without predictive back the key reaches onBackPressed. false when the
|
|
164
|
+
// task moved to the back, which the device then shows as home.
|
|
165
|
+
internal fun peachAppBack(predictive: Boolean): Boolean {
|
|
166
|
+
val activity = checkNotNull(running) { "the app is not running" }
|
|
167
|
+
activity.movedTaskToBack = false
|
|
168
|
+
val dispatcher = (activity as? ComponentActivity)?.onBackPressedDispatcher
|
|
169
|
+
if (!predictive) activity.onBackPressed()
|
|
170
|
+
else if (dispatcher?.hasEnabledCallbacks() == true) dispatcher.onBackPressed()
|
|
171
|
+
else activity.movedTaskToBack = true
|
|
172
|
+
return !activity.movedTaskToBack
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
// the task left the foreground (onPause, onStop, onSaveInstanceState) or came back to it
|
|
176
|
+
// (onRestart, onStart, onResume, onPostResume), as ActivityThread drives it
|
|
177
|
+
internal fun peachAppTask(foreground: Boolean) {
|
|
178
|
+
val activity = checkNotNull(running) { "the app is not running" }
|
|
179
|
+
if (foreground) {
|
|
180
|
+
activity.performRestart()
|
|
181
|
+
activity.performResume()
|
|
182
|
+
// the frame clock runs again once the activity is started
|
|
183
|
+
checkNotNull(runningMount).invalidate(false)
|
|
184
|
+
} else {
|
|
185
|
+
activity.performPause()
|
|
186
|
+
activity.performStop()
|
|
187
|
+
// from API 28 an activity that stops saves its state after onStop;
|
|
188
|
+
// ActivityThread keeps it for a relaunch, which no path here takes yet
|
|
189
|
+
activity.performSaveInstanceState(Bundle())
|
|
190
|
+
}
|
|
191
|
+
}
|
|
@@ -8,6 +8,7 @@ import androidx.compose.ui.platform.ComposeView
|
|
|
8
8
|
internal interface PeachWindowHost {
|
|
9
9
|
fun compose(view: ComposeView): AutoCloseable
|
|
10
10
|
|
|
11
|
-
// the
|
|
11
|
+
// the inset policy or status bar the window asks the device for changed
|
|
12
|
+
// after it was added
|
|
12
13
|
fun windowChanged()
|
|
13
14
|
}
|
|
@@ -131,6 +131,9 @@ internal class PeachComposeMount(
|
|
|
131
131
|
var constraints = Constraints()
|
|
132
132
|
set(value) { if (field != value) { field = value; needsMeasure = true } }
|
|
133
133
|
private val events = mutableListOf<JSONObject>()
|
|
134
|
+
// state reported once a frame, read when the frame draws: like the decor,
|
|
135
|
+
// the device sees the state a frame ends with, not each step to it
|
|
136
|
+
private val frameState = linkedMapOf<String, () -> Any?>()
|
|
134
137
|
private val platform = PeachComposePlatform(Density(density, fontScale), LocaleList(locale), IntSize.Zero, owners, insets)
|
|
135
138
|
private val recomposer = FrameRecomposer(Dispatchers.Main) { invalidate(false) }
|
|
136
139
|
private var needsMeasure = true
|
|
@@ -159,6 +162,11 @@ internal class PeachComposeMount(
|
|
|
159
162
|
invalidate(false)
|
|
160
163
|
}
|
|
161
164
|
|
|
165
|
+
fun emitAtFrame(name: String, args: () -> Any?) {
|
|
166
|
+
frameState[name] = args
|
|
167
|
+
invalidate(false)
|
|
168
|
+
}
|
|
169
|
+
|
|
162
170
|
fun measure(): IntSize {
|
|
163
171
|
val measured = scene.measureContent(constraints)
|
|
164
172
|
scene.size = measured
|
|
@@ -198,7 +206,9 @@ internal class PeachComposeMount(
|
|
|
198
206
|
scene.draw(canvas.asComposeCanvas())
|
|
199
207
|
val reply = JSONObject().put("width", measured.width).put("height", measured.height)
|
|
200
208
|
.put("pending", recomposer.hasPendingWork() || scene.hasInvalidations())
|
|
201
|
-
|
|
209
|
+
for ((name, args) in frameState) events.add(JSONObject().put("name", name).put("args", args()))
|
|
210
|
+
frameState.clear()
|
|
211
|
+
reply.put("events", JSONArray(events))
|
|
202
212
|
events.clear()
|
|
203
213
|
if (gpu) checkNotNull(surface).flushAndSubmit()
|
|
204
214
|
else {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "peachsim",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.659",
|
|
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",
|
|
@@ -84,6 +84,13 @@
|
|
|
84
84
|
"import": "./dist-lib/metro-production-bundle.mjs",
|
|
85
85
|
"default": "./dist-lib/metro-production-bundle.cjs"
|
|
86
86
|
},
|
|
87
|
+
"./plist": {
|
|
88
|
+
"types": "./src/plist.ts",
|
|
89
|
+
"source": "./src/plist.ts",
|
|
90
|
+
"bun": "./src/plist.ts",
|
|
91
|
+
"import": "./dist-lib/plist.mjs",
|
|
92
|
+
"default": "./dist-lib/plist.cjs"
|
|
93
|
+
},
|
|
87
94
|
"./native-app-identity": {
|
|
88
95
|
"types": "./src/native-app-identity.ts",
|
|
89
96
|
"source": "./src/native-app-identity.ts",
|
|
@@ -113,6 +113,33 @@ peach open https://my-app.local # load a hosted bundle
|
|
|
113
113
|
- the compatibility scan's partial, unsupported, unknown, and version-mismatched
|
|
114
114
|
packages have been summarized without treating its aggregate score as a pass/fail gate
|
|
115
115
|
|
|
116
|
+
## the app's own native code
|
|
117
|
+
|
|
118
|
+
an app's own native modules and views (a local Expo module, a
|
|
119
|
+
`requireNativeComponent` view written in Swift or Kotlin) have no Peach
|
|
120
|
+
implementation until the app supplies one. when the scan or the running app
|
|
121
|
+
shows one missing (an empty view where content belongs, a "native module not
|
|
122
|
+
found" error), add it to `peach.config.ts` at the app root:
|
|
123
|
+
|
|
124
|
+
```ts
|
|
125
|
+
import { defineConfig } from 'peachsim/config'
|
|
126
|
+
|
|
127
|
+
export default defineConfig({
|
|
128
|
+
nativeModules: { MyAppModule: { file: './peach/my-app-module.ts' } },
|
|
129
|
+
nativeViews: {
|
|
130
|
+
RNBalanceLabel: { file: './peach/BalanceLabel.tsx' }, // requireNativeComponent name
|
|
131
|
+
MyModule_StatusView: { file: './peach/StatusView.tsx' }, // requireNativeView('MyModule', 'StatusView')
|
|
132
|
+
},
|
|
133
|
+
})
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
a `nativeViews` file default-exports a component that receives the native
|
|
137
|
+
view's props and renders what it shows on device, using `react-native` and
|
|
138
|
+
`contrast-kit` primitives. a `nativeModules` file exports the module's
|
|
139
|
+
methods, served to `NativeModules` and to Expo's `requireNativeModule`. a
|
|
140
|
+
file may import other project files by relative path, so a large view splits
|
|
141
|
+
into components and helpers. reopen the app to load the change.
|
|
142
|
+
|
|
116
143
|
## compatibility feedback requires approval
|
|
117
144
|
|
|
118
145
|
run `peach compat --json` from the app project root during setup. if the scan
|