peachsim 0.1.648 → 0.1.650
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 -9
- package/cli/command-registry.ts +2 -1
- package/cli/commands/guest-host.ts +67 -0
- package/cli/commands/kotlin.ts +231 -0
- package/cli/commands/project-report.ts +1 -1
- package/cli/commands/report-issue.ts +55 -32
- package/cli/commands/setup.ts +10 -14
- package/cli/commands/support.ts +399 -0
- package/cli/commands/swift.ts +6 -52
- package/cli/commands/telemetry.ts +10 -13
- package/cli/main.ts +22 -10
- package/cli/outbound-endpoints.ts +23 -2
- package/cli/privacy.ts +23 -25
- package/cli/runtime-notes.ts +3 -2
- package/cli/telemetry.ts +109 -14
- 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 +3 -3
- 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 +102 -21
- 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/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 +28 -39
- package/dist-lib/swift-compile.cjs +6 -3
- package/dist-lib/swift-compile.mjs +6 -3
- 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 +6 -3
- package/dist-lib/vite.cjs +1 -1
- package/kotlin/AppSources/android/app/Activity.kt +85 -0
- package/kotlin/AppSources/android/app/Application.kt +7 -0
- package/kotlin/AppSources/android/content/Context.kt +5 -0
- package/kotlin/AppSources/android/content/Intent.kt +16 -0
- package/kotlin/AppSources/android/os/Bundle.kt +5 -0
- package/kotlin/AppSources/android/util/SuperNotCalledException.kt +3 -0
- package/kotlin/AppSources/android/view/View.kt +72 -0
- package/kotlin/AppSources/android/view/Window.kt +44 -0
- package/kotlin/AppSources/android/widget/FrameLayout.kt +6 -0
- package/kotlin/AppSources/androidx/activity/ComponentActivity.kt +57 -0
- package/kotlin/AppSources/androidx/activity/compose/ComponentActivity.kt +29 -0
- package/kotlin/AppSources/androidx/compose/ui/platform/ComposeView.kt +67 -0
- package/kotlin/AppSources/dev/peach/app/PeachApp.kt +136 -0
- package/kotlin/AppSources/dev/peach/app/PeachWindowHost.kt +13 -0
- package/kotlin/ComposeSources/PeachComposeHost.kt +316 -0
- package/kotlin/Sources/dev/onejs/one/OneNativeSourceViewDispatch.kt +13 -0
- package/kotlin/Sources/org/json/JSON.kt +6 -0
- package/kotlin/fixtures/counter/Counter.d.kt.ts +16 -0
- package/kotlin/fixtures/counter/Counter.kt +18 -0
- package/kotlin/fixtures/counter/CounterApp.native.tsx +34 -0
- package/kotlin/fixtures/counter/CounterApp.tsx +3 -0
- package/kotlin/fixtures/counter/CounterScreen.d.kt.ts +7 -0
- package/kotlin/fixtures/counter/CounterScreen.kt +18 -3
- package/kotlin/fixtures/counter/app/_layout.tsx +4 -0
- package/kotlin/fixtures/counter/app/index+spa.tsx +1 -0
- package/kotlin/fixtures/counter/app/routes.d.ts +18 -0
- package/kotlin/fixtures/counter/package.json +7 -0
- package/kotlin/fixtures/counter/tsconfig.json +19 -0
- package/kotlin/fixtures/counter/vite.config.ts +10 -0
- package/kotlin/fixtures/headless-counter/Headless.kt +5 -0
- package/kotlin/fixtures/kotlin-app/app/build.gradle.kts +30 -0
- package/kotlin/fixtures/kotlin-app/app/src/main/AndroidManifest.xml +13 -0
- package/kotlin/fixtures/kotlin-app/app/src/main/java/dev/contrast/kotlinapp/MainActivity.kt +60 -0
- package/kotlin/fixtures/kotlin-app/app/src/main/java/dev/contrast/kotlinapp/ProbeApplication.kt +18 -0
- package/kotlin/fixtures/kotlin-app/build.gradle.kts +5 -0
- package/kotlin/fixtures/kotlin-app/gradle.properties +4 -0
- package/kotlin/fixtures/kotlin-app/settings.gradle.kts +4 -0
- package/kotlin/fork/NOTICE +7 -5
- package/kotlin/fork/README.md +41 -49
- package/kotlin/fork/build.sh +3 -2
- package/kotlin/fork/compose-worker-globals.patch +22 -11
- package/kotlin/fork/fork.json +2 -2
- package/kotlin/fork/publish.ts +30 -15
- package/kotlin/fork/skiko-worker-clock.patch +38 -0
- package/kotlin/toolchain.json +9 -9
- package/package.json +1 -1
- package/skills/contrast/SKILL.md +2 -1
- package/skills/peach-setup/SKILL.md +2 -2
- package/src/{swift-guest.ts → guest-bundle.ts} +22 -7
- package/src/home-paths.ts +5 -3
- package/src/kotlin-app.ts +411 -0
- package/src/kotlin-host.ts +148 -25
- package/src/swift-compile.ts +7 -2
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
package android.app
|
|
2
|
+
|
|
3
|
+
import android.content.ComponentName
|
|
4
|
+
import android.content.ContextWrapper
|
|
5
|
+
import android.content.Intent
|
|
6
|
+
import android.os.Bundle
|
|
7
|
+
import android.util.SuperNotCalledException
|
|
8
|
+
import android.view.View
|
|
9
|
+
import android.view.ViewGroup
|
|
10
|
+
import android.view.Window
|
|
11
|
+
|
|
12
|
+
// the cold-start half of Activity: ActivityThread attaches it, then drives the
|
|
13
|
+
// perform calls in its own order. each lifecycle method must call through to
|
|
14
|
+
// super, as Activity.mCalled enforces, and the post callbacks run after it
|
|
15
|
+
// returns, where ActivityLifecycleCallbacks see them.
|
|
16
|
+
open class Activity : ContextWrapper() {
|
|
17
|
+
private var called = false
|
|
18
|
+
private var attachedIntent: Intent? = null
|
|
19
|
+
private var attachedApplication: Application? = null
|
|
20
|
+
private var attachedWindow: Window? = null
|
|
21
|
+
private var attachedComponent: ComponentName? = null
|
|
22
|
+
|
|
23
|
+
val intent: Intent get() = checkNotNull(attachedIntent)
|
|
24
|
+
val application: Application get() = checkNotNull(attachedApplication)
|
|
25
|
+
val window: Window get() = checkNotNull(attachedWindow)
|
|
26
|
+
|
|
27
|
+
// the activity class that runs; launched through an alias, the intent
|
|
28
|
+
// still names the alias
|
|
29
|
+
val componentName: ComponentName get() = checkNotNull(attachedComponent)
|
|
30
|
+
|
|
31
|
+
internal fun attach(application: Application, component: ComponentName, intent: Intent, window: Window) {
|
|
32
|
+
attachedApplication = application
|
|
33
|
+
attachedComponent = component
|
|
34
|
+
attachedIntent = intent
|
|
35
|
+
attachedWindow = window
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
open fun setContentView(view: View) {
|
|
39
|
+
setContentView(view, ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT))
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
open fun setContentView(view: View, params: ViewGroup.LayoutParams) {
|
|
43
|
+
window.setContentView(view, params)
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
protected open fun onCreate(savedInstanceState: Bundle?) { called = true }
|
|
47
|
+
protected open fun onStart() { called = true }
|
|
48
|
+
protected open fun onPostCreate(savedInstanceState: Bundle?) { called = true }
|
|
49
|
+
protected open fun onResume() { called = true }
|
|
50
|
+
protected open fun onPostResume() { called = true }
|
|
51
|
+
|
|
52
|
+
// ActivityLifecycleCallbacks' post events; ComponentActivity's lifecycle
|
|
53
|
+
// moves on these, as its ReportFragment does from API 29.
|
|
54
|
+
internal open fun dispatchActivityPostCreated() {}
|
|
55
|
+
internal open fun dispatchActivityPostStarted() {}
|
|
56
|
+
internal open fun dispatchActivityPostResumed() {}
|
|
57
|
+
|
|
58
|
+
private fun require(method: String, call: () -> Unit) {
|
|
59
|
+
called = false
|
|
60
|
+
call()
|
|
61
|
+
if (!called) throw SuperNotCalledException(
|
|
62
|
+
"Activity {${componentName.flattenToShortString()}} did not call through to super.$method()"
|
|
63
|
+
)
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
internal fun performCreate(icicle: Bundle?) {
|
|
67
|
+
require("onCreate") { onCreate(icicle) }
|
|
68
|
+
dispatchActivityPostCreated()
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
internal fun performStart() {
|
|
72
|
+
require("onStart") { onStart() }
|
|
73
|
+
dispatchActivityPostStarted()
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
internal fun performPostCreate(icicle: Bundle?) {
|
|
77
|
+
require("onPostCreate") { onPostCreate(icicle) }
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
internal fun performResume() {
|
|
81
|
+
require("onResume") { onResume() }
|
|
82
|
+
require("onPostResume") { onPostResume() }
|
|
83
|
+
dispatchActivityPostResumed()
|
|
84
|
+
}
|
|
85
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
package android.content
|
|
2
|
+
|
|
3
|
+
class ComponentName(val packageName: String, val className: String) {
|
|
4
|
+
fun flattenToShortString(): String =
|
|
5
|
+
if (className.startsWith("$packageName.")) "$packageName/${className.removePrefix(packageName)}"
|
|
6
|
+
else "$packageName/$className"
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
class Intent(val action: String?, val categories: Set<String>?, val component: ComponentName?) {
|
|
10
|
+
fun hasCategory(category: String): Boolean = categories?.contains(category) == true
|
|
11
|
+
|
|
12
|
+
companion object {
|
|
13
|
+
const val ACTION_MAIN = "android.intent.action.MAIN"
|
|
14
|
+
const val CATEGORY_LAUNCHER = "android.intent.category.LAUNCHER"
|
|
15
|
+
}
|
|
16
|
+
}
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
package android.view
|
|
2
|
+
|
|
3
|
+
import android.content.Context
|
|
4
|
+
|
|
5
|
+
open class View(val context: Context) {
|
|
6
|
+
var parent: ViewGroup? = null
|
|
7
|
+
internal set
|
|
8
|
+
var layoutParams: ViewGroup.LayoutParams? = null
|
|
9
|
+
internal var attachedWindow: Window? = null
|
|
10
|
+
|
|
11
|
+
val isAttachedToWindow: Boolean get() = attachedWindow != null
|
|
12
|
+
|
|
13
|
+
// ViewRootImpl dispatches attach through the tree once the window is added
|
|
14
|
+
internal open fun dispatchAttachedToWindow(window: Window) {
|
|
15
|
+
attachedWindow = window
|
|
16
|
+
onAttachedToWindow()
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
internal open fun dispatchDetachedFromWindow() {
|
|
20
|
+
onDetachedFromWindow()
|
|
21
|
+
attachedWindow = null
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
protected open fun onAttachedToWindow() {}
|
|
25
|
+
protected open fun onDetachedFromWindow() {}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
open class ViewGroup(context: Context) : View(context) {
|
|
29
|
+
open class LayoutParams(val width: Int, val height: Int) {
|
|
30
|
+
companion object {
|
|
31
|
+
const val MATCH_PARENT = -1
|
|
32
|
+
const val WRAP_CONTENT = -2
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
private val children = mutableListOf<View>()
|
|
37
|
+
|
|
38
|
+
val childCount: Int get() = children.size
|
|
39
|
+
fun getChildAt(index: Int): View? = children.getOrNull(index)
|
|
40
|
+
|
|
41
|
+
fun addView(child: View, params: LayoutParams) {
|
|
42
|
+
child.layoutParams = params
|
|
43
|
+
addView(child)
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
open fun addView(child: View) {
|
|
47
|
+
check(child.parent == null) {
|
|
48
|
+
"The specified child already has a parent. You must call removeView() on the child's parent first."
|
|
49
|
+
}
|
|
50
|
+
children += child
|
|
51
|
+
child.parent = this
|
|
52
|
+
attachedWindow?.let { child.dispatchAttachedToWindow(it) }
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
fun removeAllViews() {
|
|
56
|
+
for (child in children) {
|
|
57
|
+
if (child.isAttachedToWindow) child.dispatchDetachedFromWindow()
|
|
58
|
+
child.parent = null
|
|
59
|
+
}
|
|
60
|
+
children.clear()
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
override fun dispatchAttachedToWindow(window: Window) {
|
|
64
|
+
super.dispatchAttachedToWindow(window)
|
|
65
|
+
for (child in children.toList()) child.dispatchAttachedToWindow(window)
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
override fun dispatchDetachedFromWindow() {
|
|
69
|
+
for (child in children.toList()) child.dispatchDetachedFromWindow()
|
|
70
|
+
super.dispatchDetachedFromWindow()
|
|
71
|
+
}
|
|
72
|
+
}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
package android.view
|
|
2
|
+
|
|
3
|
+
import android.content.Context
|
|
4
|
+
import android.widget.FrameLayout
|
|
5
|
+
import androidx.compose.runtime.getValue
|
|
6
|
+
import androidx.compose.runtime.mutableStateOf
|
|
7
|
+
import androidx.compose.runtime.setValue
|
|
8
|
+
import dev.peach.app.PeachWindowHost
|
|
9
|
+
|
|
10
|
+
// PhoneWindow's content parent (android.R.id.content) inside its decor. the
|
|
11
|
+
// window is added, and its views attached, after the activity resumes.
|
|
12
|
+
class Window internal constructor(
|
|
13
|
+
context: Context,
|
|
14
|
+
private val edgeToEdgeEnforced: Boolean,
|
|
15
|
+
internal val host: PeachWindowHost,
|
|
16
|
+
) {
|
|
17
|
+
internal val contentParent = FrameLayout(context)
|
|
18
|
+
val decorView: View get() = contentParent
|
|
19
|
+
|
|
20
|
+
// PhoneWindow fits system windows unless told otherwise, and applies that
|
|
21
|
+
// only while edge to edge is not enforced for the app's target SDK
|
|
22
|
+
private var decorFitsSystemWindows by mutableStateOf(true)
|
|
23
|
+
internal val fitsSystemWindows: Boolean get() = decorFitsSystemWindows && !edgeToEdgeEnforced
|
|
24
|
+
|
|
25
|
+
fun setDecorFitsSystemWindows(decorFitsSystemWindows: Boolean) {
|
|
26
|
+
val before = fitsSystemWindows
|
|
27
|
+
this.decorFitsSystemWindows = decorFitsSystemWindows
|
|
28
|
+
if (fitsSystemWindows != before) host.windowChanged()
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
// Activity.setContentView(View) fills the content parent
|
|
32
|
+
internal fun setContentView(view: View, params: ViewGroup.LayoutParams) {
|
|
33
|
+
contentParent.removeAllViews()
|
|
34
|
+
contentParent.addView(view, params)
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
internal fun addToWindowManager() {
|
|
38
|
+
contentParent.dispatchAttachedToWindow(this)
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
internal fun removeFromWindowManager() {
|
|
42
|
+
if (contentParent.isAttachedToWindow) contentParent.dispatchDetachedFromWindow()
|
|
43
|
+
}
|
|
44
|
+
}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
package androidx.activity
|
|
2
|
+
|
|
3
|
+
import android.app.Activity
|
|
4
|
+
import android.os.Bundle
|
|
5
|
+
import androidx.lifecycle.HasDefaultViewModelProviderFactory
|
|
6
|
+
import androidx.lifecycle.Lifecycle
|
|
7
|
+
import androidx.lifecycle.LifecycleEventObserver
|
|
8
|
+
import androidx.lifecycle.LifecycleOwner
|
|
9
|
+
import androidx.lifecycle.LifecycleRegistry
|
|
10
|
+
import androidx.lifecycle.SAVED_STATE_REGISTRY_OWNER_KEY
|
|
11
|
+
import androidx.lifecycle.SavedStateViewModelFactory
|
|
12
|
+
import androidx.lifecycle.VIEW_MODEL_STORE_OWNER_KEY
|
|
13
|
+
import androidx.lifecycle.ViewModelStore
|
|
14
|
+
import androidx.lifecycle.ViewModelStoreOwner
|
|
15
|
+
import androidx.lifecycle.enableSavedStateHandles
|
|
16
|
+
import androidx.lifecycle.viewmodel.CreationExtras
|
|
17
|
+
import androidx.lifecycle.viewmodel.MutableCreationExtras
|
|
18
|
+
import androidx.savedstate.SavedStateRegistry
|
|
19
|
+
import androidx.savedstate.SavedStateRegistryController
|
|
20
|
+
import androidx.savedstate.SavedStateRegistryOwner
|
|
21
|
+
|
|
22
|
+
// androidx.activity's ComponentActivity owners, on the multiplatform
|
|
23
|
+
// lifecycle, viewmodel and savedstate libraries: the registry attaches in the
|
|
24
|
+
// constructor and restores in onCreate before super, the lifecycle moves on
|
|
25
|
+
// the post callbacks, and destroy clears the view model store.
|
|
26
|
+
open class ComponentActivity : Activity(), LifecycleOwner, ViewModelStoreOwner,
|
|
27
|
+
HasDefaultViewModelProviderFactory, SavedStateRegistryOwner {
|
|
28
|
+
private val lifecycleRegistry = LifecycleRegistry(this)
|
|
29
|
+
private val savedStateRegistryController = SavedStateRegistryController.create(this)
|
|
30
|
+
|
|
31
|
+
override val lifecycle: Lifecycle get() = lifecycleRegistry
|
|
32
|
+
override val viewModelStore: ViewModelStore = ViewModelStore()
|
|
33
|
+
override val savedStateRegistry: SavedStateRegistry get() = savedStateRegistryController.savedStateRegistry
|
|
34
|
+
override val defaultViewModelProviderFactory = SavedStateViewModelFactory()
|
|
35
|
+
override val defaultViewModelCreationExtras: CreationExtras
|
|
36
|
+
get() = MutableCreationExtras().also {
|
|
37
|
+
it[SAVED_STATE_REGISTRY_OWNER_KEY] = this
|
|
38
|
+
it[VIEW_MODEL_STORE_OWNER_KEY] = this
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
init {
|
|
42
|
+
lifecycleRegistry.addObserver(LifecycleEventObserver { _, event ->
|
|
43
|
+
if (event == Lifecycle.Event.ON_DESTROY) viewModelStore.clear()
|
|
44
|
+
})
|
|
45
|
+
savedStateRegistryController.performAttach()
|
|
46
|
+
enableSavedStateHandles()
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
override fun onCreate(savedInstanceState: Bundle?) {
|
|
50
|
+
savedStateRegistryController.performRestore(null)
|
|
51
|
+
super.onCreate(savedInstanceState)
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
override fun dispatchActivityPostCreated() = lifecycleRegistry.handleLifecycleEvent(Lifecycle.Event.ON_CREATE)
|
|
55
|
+
override fun dispatchActivityPostStarted() = lifecycleRegistry.handleLifecycleEvent(Lifecycle.Event.ON_START)
|
|
56
|
+
override fun dispatchActivityPostResumed() = lifecycleRegistry.handleLifecycleEvent(Lifecycle.Event.ON_RESUME)
|
|
57
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
package androidx.activity.compose
|
|
2
|
+
|
|
3
|
+
import android.view.ViewGroup
|
|
4
|
+
import androidx.activity.ComponentActivity
|
|
5
|
+
import androidx.compose.runtime.Composable
|
|
6
|
+
import androidx.compose.runtime.CompositionContext
|
|
7
|
+
import androidx.compose.ui.platform.ComposeView
|
|
8
|
+
|
|
9
|
+
private val DefaultActivityContentLayoutParams =
|
|
10
|
+
ViewGroup.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT)
|
|
11
|
+
|
|
12
|
+
// activity-compose's setContent: reuse the ComposeView already in the content
|
|
13
|
+
// parent, otherwise create one and set it as the content view. the activity
|
|
14
|
+
// itself supplies the view tree owners.
|
|
15
|
+
fun ComponentActivity.setContent(
|
|
16
|
+
parent: CompositionContext? = null,
|
|
17
|
+
content: @Composable () -> Unit,
|
|
18
|
+
) {
|
|
19
|
+
val existingComposeView =
|
|
20
|
+
(window.decorView as ViewGroup).getChildAt(0) as? ComposeView
|
|
21
|
+
if (existingComposeView != null) with(existingComposeView) {
|
|
22
|
+
setParentCompositionContext(parent)
|
|
23
|
+
setContent(content)
|
|
24
|
+
} else ComposeView(this).apply {
|
|
25
|
+
setParentCompositionContext(parent)
|
|
26
|
+
setContent(content)
|
|
27
|
+
setContentView(this, DefaultActivityContentLayoutParams)
|
|
28
|
+
}
|
|
29
|
+
}
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
package androidx.compose.ui.platform
|
|
2
|
+
|
|
3
|
+
import android.content.Context
|
|
4
|
+
import android.view.ViewGroup
|
|
5
|
+
import androidx.compose.runtime.Composable
|
|
6
|
+
import androidx.compose.runtime.CompositionContext
|
|
7
|
+
import androidx.compose.runtime.mutableStateOf
|
|
8
|
+
|
|
9
|
+
// AbstractComposeView and ComposeView as compose-ui's Android source has them:
|
|
10
|
+
// setContent stores a state-backed lambda, composition starts when the view
|
|
11
|
+
// attaches to a window, a later setContent replaces the lambda inside the same
|
|
12
|
+
// composition, and detaching disposes it (the default view composition
|
|
13
|
+
// strategy).
|
|
14
|
+
class ComposeView(context: Context) : ViewGroup(context) {
|
|
15
|
+
private val content = mutableStateOf<(@Composable () -> Unit)?>(null)
|
|
16
|
+
private var shouldCreateCompositionOnAttachedToWindow = false
|
|
17
|
+
private var parentContext: CompositionContext? = null
|
|
18
|
+
private var composition: AutoCloseable? = null
|
|
19
|
+
|
|
20
|
+
val hasComposition: Boolean get() = composition != null
|
|
21
|
+
|
|
22
|
+
@Composable
|
|
23
|
+
internal fun Content() {
|
|
24
|
+
content.value?.invoke()
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
fun setParentCompositionContext(parent: CompositionContext?) {
|
|
28
|
+
parentContext = parent
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
fun setContent(content: @Composable () -> Unit) {
|
|
32
|
+
shouldCreateCompositionOnAttachedToWindow = true
|
|
33
|
+
this.content.value = content
|
|
34
|
+
if (isAttachedToWindow) createComposition()
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
fun createComposition() {
|
|
38
|
+
check(parentContext != null || isAttachedToWindow) {
|
|
39
|
+
"createComposition requires a previous call to createComposition(ComposeViewContext)," +
|
|
40
|
+
" a parent reference, or the View to be attached to a window. Attach the View or " +
|
|
41
|
+
"call setParentCompositionReference."
|
|
42
|
+
}
|
|
43
|
+
ensureCompositionCreated()
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
fun disposeComposition() {
|
|
47
|
+
composition?.close()
|
|
48
|
+
composition = null
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
private fun ensureCompositionCreated() {
|
|
52
|
+
if (composition != null) return
|
|
53
|
+
check(parentContext == null) { "a ComposeView composing into a parent composition is not supported in Peach yet" }
|
|
54
|
+
val window = checkNotNull(attachedWindow) { "ComposeView is not attached to a window" }
|
|
55
|
+
composition = window.host.compose(this)
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
override fun onAttachedToWindow() {
|
|
59
|
+
super.onAttachedToWindow()
|
|
60
|
+
if (shouldCreateCompositionOnAttachedToWindow) ensureCompositionCreated()
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
override fun onDetachedFromWindow() {
|
|
64
|
+
super.onDetachedFromWindow()
|
|
65
|
+
disposeComposition()
|
|
66
|
+
}
|
|
67
|
+
}
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
@file:OptIn(androidx.compose.ui.InternalComposeUiApi::class)
|
|
2
|
+
|
|
3
|
+
package dev.peach.app
|
|
4
|
+
|
|
5
|
+
import android.app.Activity
|
|
6
|
+
import android.app.Application
|
|
7
|
+
import android.content.ComponentName
|
|
8
|
+
import android.content.Intent
|
|
9
|
+
import android.view.ViewGroup
|
|
10
|
+
import android.view.Window
|
|
11
|
+
import androidx.activity.ComponentActivity
|
|
12
|
+
import androidx.compose.ui.platform.ComposeView
|
|
13
|
+
import androidx.compose.ui.platform.PlatformArchitectureComponentsOwner
|
|
14
|
+
import androidx.compose.ui.platform.PlatformInsets
|
|
15
|
+
import androidx.compose.ui.platform.PlatformWindowInsets
|
|
16
|
+
import androidx.compose.runtime.getValue
|
|
17
|
+
import androidx.compose.runtime.key
|
|
18
|
+
import androidx.compose.runtime.mutableStateOf
|
|
19
|
+
import androidx.compose.runtime.setValue
|
|
20
|
+
import androidx.lifecycle.Lifecycle
|
|
21
|
+
import androidx.navigationevent.NavigationEventDispatcher
|
|
22
|
+
import androidx.navigationevent.NavigationEventDispatcherOwner
|
|
23
|
+
import PeachComposeMount
|
|
24
|
+
import addMount
|
|
25
|
+
import preparePeachDevice
|
|
26
|
+
import robotoFamily
|
|
27
|
+
import org.json.JSONObject
|
|
28
|
+
|
|
29
|
+
// what the manifest and Gradle files resolved: the launcher component the
|
|
30
|
+
// intent names (an alias stays the alias), the activity class it runs, the
|
|
31
|
+
// app's target SDK and theme opt-out, and constructors for the manifest's
|
|
32
|
+
// Application and that class (wasm has no reflection).
|
|
33
|
+
internal class PeachAppSpec(
|
|
34
|
+
val launcher: ComponentName,
|
|
35
|
+
val component: ComponentName,
|
|
36
|
+
val targetSdk: Int,
|
|
37
|
+
val optOutEdgeToEdge: Boolean,
|
|
38
|
+
val createApplication: () -> Application,
|
|
39
|
+
val createActivity: () -> Activity,
|
|
40
|
+
)
|
|
41
|
+
|
|
42
|
+
// the system bars the device delivers to the window's content, in pixels: all
|
|
43
|
+
// of them on an edge-to-edge window, none once the decor fits system windows.
|
|
44
|
+
private class SystemBarInsets(
|
|
45
|
+
private val window: Window,
|
|
46
|
+
private val status: Int,
|
|
47
|
+
private val navigation: Int,
|
|
48
|
+
) : PlatformWindowInsets {
|
|
49
|
+
// read when compose lays out, so a later change to the window applies
|
|
50
|
+
private fun delivered(pixels: Int) = if (window.fitsSystemWindows) 0 else pixels
|
|
51
|
+
override val statusBars = PlatformInsets(getTop = { delivered(status) })
|
|
52
|
+
override val navigationBars = PlatformInsets(getBottom = { delivered(navigation) })
|
|
53
|
+
override val systemBars =
|
|
54
|
+
PlatformInsets(getTop = { delivered(status) }, getBottom = { delivered(navigation) })
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
private class ActivityOwners(activity: ComponentActivity) : PlatformArchitectureComponentsOwner {
|
|
58
|
+
override val lifecycleOwner = activity
|
|
59
|
+
override val viewModelStoreOwner = activity
|
|
60
|
+
override val savedStateRegistryOwner = activity
|
|
61
|
+
override val navigationEventDispatcherOwner = object : NavigationEventDispatcherOwner {
|
|
62
|
+
override val navigationEventDispatcher = NavigationEventDispatcher()
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// a cold start in ActivityThread's order: the Application and its onCreate,
|
|
67
|
+
// then the launcher activity attached to its window and launcher intent,
|
|
68
|
+
// onCreate, onStart, onPostCreate, onResume and onPostResume, and only then the
|
|
69
|
+
// window added, which attaches the content view and composes it.
|
|
70
|
+
internal fun peachStartApp(
|
|
71
|
+
spec: PeachAppSpec,
|
|
72
|
+
density: Float, fontScale: Float, locale: String, backend: String, stencilBits: Int,
|
|
73
|
+
regular: String, medium: String, bold: String,
|
|
74
|
+
statusBar: Int, navigationBar: Int,
|
|
75
|
+
invalidate: (Boolean) -> Unit,
|
|
76
|
+
): String {
|
|
77
|
+
try {
|
|
78
|
+
require(backend == "graphite" || backend == "raster")
|
|
79
|
+
preparePeachDevice(locale)
|
|
80
|
+
val fonts = robotoFamily(regular, medium, bold)
|
|
81
|
+
val application = spec.createApplication()
|
|
82
|
+
application.onCreate()
|
|
83
|
+
val activity = spec.createActivity()
|
|
84
|
+
val componentActivity = activity as? ComponentActivity
|
|
85
|
+
lateinit var window: Window
|
|
86
|
+
lateinit var mount: PeachComposeMount
|
|
87
|
+
var mountId = 0
|
|
88
|
+
// the window's one scene composes the ComposeView it holds; a new
|
|
89
|
+
// view starts a new composition, as on the device
|
|
90
|
+
var held by mutableStateOf<ComposeView?>(null)
|
|
91
|
+
val host = object : PeachWindowHost {
|
|
92
|
+
override fun compose(view: ComposeView): AutoCloseable {
|
|
93
|
+
held = view
|
|
94
|
+
return AutoCloseable { if (held === view) held = null }
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
override fun windowChanged() {
|
|
98
|
+
if (mountId != 0)
|
|
99
|
+
mount.emit("window", JSONObject().put("decorFitsSystemWindows", window.fitsSystemWindows).toString())
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
val enforced = spec.targetSdk > 35 || (spec.targetSdk == 35 && !spec.optOutEdgeToEdge)
|
|
103
|
+
window = Window(activity, enforced, host)
|
|
104
|
+
activity.attach(
|
|
105
|
+
application,
|
|
106
|
+
spec.component,
|
|
107
|
+
Intent(Intent.ACTION_MAIN, setOf(Intent.CATEGORY_LAUNCHER), spec.launcher),
|
|
108
|
+
window,
|
|
109
|
+
)
|
|
110
|
+
activity.performCreate(null)
|
|
111
|
+
activity.performStart()
|
|
112
|
+
activity.performPostCreate(null)
|
|
113
|
+
activity.performResume()
|
|
114
|
+
mount = PeachComposeMount(
|
|
115
|
+
density, fontScale, locale, backend == "graphite", stencilBits, fonts, invalidate,
|
|
116
|
+
owners = componentActivity?.let(::ActivityOwners),
|
|
117
|
+
insets = SystemBarInsets(window, statusBar, navigationBar),
|
|
118
|
+
clockRunning = { componentActivity == null || componentActivity.lifecycle.currentState.isAtLeast(Lifecycle.State.STARTED) },
|
|
119
|
+
)
|
|
120
|
+
mount.setContent { held?.let { view -> key(view) { view.Content() } } }
|
|
121
|
+
mountId = addMount(mount)
|
|
122
|
+
window.addToWindowManager()
|
|
123
|
+
val view = checkNotNull(held) {
|
|
124
|
+
"${spec.component.flattenToShortString()} set no ComposeView as its content; Peach composes only ComposeView content"
|
|
125
|
+
}
|
|
126
|
+
val params = view.layoutParams
|
|
127
|
+
return JSONObject().put("ok", true).put("id", mountId)
|
|
128
|
+
.put("decorFitsSystemWindows", window.fitsSystemWindows)
|
|
129
|
+
.put("wrapWidth", params?.width == ViewGroup.LayoutParams.WRAP_CONTENT)
|
|
130
|
+
.put("wrapHeight", params?.height == ViewGroup.LayoutParams.WRAP_CONTENT)
|
|
131
|
+
.toString()
|
|
132
|
+
} catch (error: Throwable) {
|
|
133
|
+
return JSONObject().put("ok", false).put("code", "E_KOTLIN_APP")
|
|
134
|
+
.put("error", "${error::class.simpleName}: ${error.message}").toString()
|
|
135
|
+
}
|
|
136
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
package dev.peach.app
|
|
2
|
+
|
|
3
|
+
import androidx.compose.ui.platform.ComposeView
|
|
4
|
+
|
|
5
|
+
// the device side of a window: one Peach scene for the window's life, which
|
|
6
|
+
// composes whichever ComposeView the window holds once that view attaches.
|
|
7
|
+
// the composition ends when the view disposes it.
|
|
8
|
+
internal interface PeachWindowHost {
|
|
9
|
+
fun compose(view: ComposeView): AutoCloseable
|
|
10
|
+
|
|
11
|
+
// the window's inset policy changed after it was added
|
|
12
|
+
fun windowChanged()
|
|
13
|
+
}
|