peachsim 0.1.675 → 0.1.677
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/dist-lib/agent-daemon-client.cjs +1 -1
- package/dist-lib/agent-events.cjs +1 -1
- package/dist-lib/agent-identity.cjs +1 -1
- package/dist-lib/agent-sessions.cjs +1 -1
- package/dist-lib/attached-projects.cjs +1 -1
- package/dist-lib/auth/shared-session.cjs +1 -1
- package/dist-lib/backend-origin.cjs +1 -1
- package/dist-lib/beta.cjs +1 -1
- package/dist-lib/beta.mjs +1 -1
- package/dist-lib/bridge-constants.cjs +1 -1
- package/dist-lib/bridge-contract-input.cjs +1 -1
- package/dist-lib/bridge-contract-input.mjs +1 -1
- package/dist-lib/bridge-contract.cjs +1 -1
- package/dist-lib/bridge-contract.mjs +1 -1
- package/dist-lib/capture-contract.cjs +1 -1
- package/dist-lib/capture-contract.mjs +1 -1
- package/dist-lib/cli-constants.cjs +1 -1
- package/dist-lib/cloud-contract.cjs +1 -1
- package/dist-lib/cloud-contract.mjs +1 -1
- package/dist-lib/cloud-test-args.cjs +1 -1
- package/dist-lib/cloud-test-args.mjs +1 -1
- package/dist-lib/cloud.cjs +1 -1
- package/dist-lib/cloud.mjs +1 -1
- package/dist-lib/config.cjs +1 -1
- package/dist-lib/detox/index.cjs +1 -1
- package/dist-lib/dev-bundle-resolution.cjs +1 -1
- package/dist-lib/home-paths.cjs +1 -1
- package/dist-lib/host/bridge-host.cjs +1 -1
- package/dist-lib/host/fetch-proxy-handler.cjs +1 -1
- package/dist-lib/host/fetch-proxy-overrides.cjs +1 -1
- package/dist-lib/host/fetch-proxy-overrides.mjs +1 -1
- package/dist-lib/host/replacement-module-handler.cjs +1 -1
- package/dist-lib/host/websocket-proxy.cjs +1 -1
- package/dist-lib/index.cjs +1 -1
- package/dist-lib/jump-to-source-babel.cjs +1 -1
- package/dist-lib/jump-to-source-native.cjs +1 -1
- package/dist-lib/kotlin-host.cjs +33 -19
- package/dist-lib/menu.cjs +43 -1
- package/dist-lib/menu.mjs +40 -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/native-firebase-metadata.cjs +1 -1
- package/dist-lib/native-firebase-metadata.mjs +1 -1
- package/dist-lib/plist.cjs +1 -1
- package/dist-lib/plist.mjs +1 -1
- package/dist-lib/profiles.cjs +1 -1
- package/dist-lib/public-brand.cjs +1 -1
- package/dist-lib/react-native-host-modules.cjs +1 -1
- package/dist-lib/react-native-host-modules.mjs +1 -1
- package/dist-lib/render-mode.cjs +1 -1
- package/dist-lib/scripts/dev-server-scanner.cjs +1 -1
- package/dist-lib/sdk.cjs +1 -1
- package/dist-lib/sdk.mjs +1 -1
- package/dist-lib/skills.cjs +1 -1
- package/dist-lib/swift-compile.cjs +1 -1
- package/dist-lib/swift-compile.mjs +1 -1
- package/dist-lib/swift-package.cjs +1 -1
- package/dist-lib/swift-package.mjs +1 -1
- package/dist-lib/swift-submit.cjs +1 -1
- package/dist-lib/swift-submit.mjs +1 -1
- package/dist-lib/swift.cjs +1 -1
- package/dist-lib/vite-swift.cjs +1 -1
- package/dist-lib/vite.cjs +1 -1
- package/kotlin/AppSources/android/content/Context.kt +6 -0
- package/kotlin/AppSources/android/content/res/Resources.kt +16 -0
- package/kotlin/AppSources/androidx/compose/ui/platform/AndroidCompositionLocals.kt +10 -2
- package/kotlin/AppSources/androidx/compose/ui/res/ColorResources.kt +16 -0
- package/kotlin/AppSources/androidx/compose/ui/res/StringResources.kt +11 -26
- package/kotlin/fixtures/kotlin-app-variants/Colors.kt +41 -0
- package/package.json +1 -1
- package/src/android-theme.ts +32 -16
- package/src/kotlin-app.ts +69 -31
- package/src/kotlin-host.ts +44 -33
- package/src/menu.ts +48 -0
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
/*! rnx v0.1.
|
|
1
|
+
/*! rnx v0.1.677 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
|
|
2
2
|
let __sootsim_import_meta_url = ''; try { __sootsim_import_meta_url = require('url').pathToFileURL(__filename).href; } catch {}
|
|
3
3
|
"use strict";
|
|
4
4
|
var __create = Object.create;
|
package/dist-lib/sdk.cjs
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
/*! rnx v0.1.
|
|
1
|
+
/*! rnx v0.1.677 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
|
|
2
2
|
let __sootsim_import_meta_url = ''; try { __sootsim_import_meta_url = require('url').pathToFileURL(__filename).href; } catch {}
|
|
3
3
|
"use strict";
|
|
4
4
|
var __defProp = Object.defineProperty;
|
package/dist-lib/sdk.mjs
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
/*! rnx v0.1.
|
|
1
|
+
/*! rnx v0.1.677 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
|
|
2
2
|
var __defProp = Object.defineProperty;
|
|
3
3
|
var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
|
|
4
4
|
var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
|
package/dist-lib/skills.cjs
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
/*! rnx v0.1.
|
|
1
|
+
/*! rnx v0.1.677 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
|
|
2
2
|
let __sootsim_import_meta_url = ''; try { __sootsim_import_meta_url = require('url').pathToFileURL(__filename).href; } catch {}
|
|
3
3
|
"use strict";
|
|
4
4
|
var __create = Object.create;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
/*! rnx v0.1.
|
|
1
|
+
/*! rnx v0.1.677 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
|
|
2
2
|
let __sootsim_import_meta_url = ''; try { __sootsim_import_meta_url = require('url').pathToFileURL(__filename).href; } catch {}
|
|
3
3
|
"use strict";
|
|
4
4
|
var __create = Object.create;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
/*! rnx v0.1.
|
|
1
|
+
/*! rnx v0.1.677 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
|
|
2
2
|
let __sootsim_import_meta_url = ''; try { __sootsim_import_meta_url = require('url').pathToFileURL(__filename).href; } catch {}
|
|
3
3
|
"use strict";
|
|
4
4
|
var __defProp = Object.defineProperty;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
/*! rnx v0.1.
|
|
1
|
+
/*! rnx v0.1.677 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
|
|
2
2
|
let __sootsim_import_meta_url = ''; try { __sootsim_import_meta_url = require('url').pathToFileURL(__filename).href; } catch {}
|
|
3
3
|
"use strict";
|
|
4
4
|
var __defProp = Object.defineProperty;
|
package/dist-lib/swift.cjs
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
/*! rnx v0.1.
|
|
1
|
+
/*! rnx v0.1.677 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
|
|
2
2
|
let __sootsim_import_meta_url = ''; try { __sootsim_import_meta_url = require('url').pathToFileURL(__filename).href; } catch {}
|
|
3
3
|
"use strict";
|
|
4
4
|
var __defProp = Object.defineProperty;
|
package/dist-lib/vite-swift.cjs
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
/*! rnx v0.1.
|
|
1
|
+
/*! rnx v0.1.677 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
|
|
2
2
|
let __sootsim_import_meta_url = ''; try { __sootsim_import_meta_url = require('url').pathToFileURL(__filename).href; } catch {}
|
|
3
3
|
"use strict";
|
|
4
4
|
var __create = Object.create;
|
package/dist-lib/vite.cjs
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
/*! rnx v0.1.
|
|
1
|
+
/*! rnx v0.1.677 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
|
|
2
2
|
let __sootsim_import_meta_url = ''; try { __sootsim_import_meta_url = require('url').pathToFileURL(__filename).href; } catch {}
|
|
3
3
|
"use strict";
|
|
4
4
|
var __create = Object.create;
|
|
@@ -6,6 +6,10 @@ import android.content.res.processResources
|
|
|
6
6
|
abstract class Context {
|
|
7
7
|
abstract val resources: Resources
|
|
8
8
|
|
|
9
|
+
abstract val theme: Resources.Theme
|
|
10
|
+
|
|
11
|
+
fun getColor(id: Int): Int = resources.getColor(id, theme)
|
|
12
|
+
|
|
9
13
|
fun getString(resId: Int): String = resources.getString(resId)
|
|
10
14
|
|
|
11
15
|
fun getString(resId: Int, vararg formatArgs: Any?): String = resources.getString(resId, *formatArgs)
|
|
@@ -13,4 +17,6 @@ abstract class Context {
|
|
|
13
17
|
|
|
14
18
|
open class ContextWrapper : Context() {
|
|
15
19
|
override val resources: Resources get() = processResources
|
|
20
|
+
|
|
21
|
+
override val theme: Resources.Theme = Resources.Theme()
|
|
16
22
|
}
|
|
@@ -2,12 +2,17 @@ package android.content.res
|
|
|
2
2
|
|
|
3
3
|
import dev.peach.app.javaFormat
|
|
4
4
|
import dev.peach.app.peachAppArrays
|
|
5
|
+
import dev.peach.app.peachAppColors
|
|
5
6
|
import dev.peach.app.peachAppPlurals
|
|
6
7
|
import dev.peach.app.peachAppStrings
|
|
7
8
|
|
|
8
9
|
class Resources internal constructor(val configuration: Configuration) {
|
|
9
10
|
class NotFoundException(name: String) : RuntimeException(name)
|
|
10
11
|
|
|
12
|
+
// a context's theme; app color resources are plain values, so getColor
|
|
13
|
+
// reads none of it
|
|
14
|
+
class Theme internal constructor()
|
|
15
|
+
|
|
11
16
|
fun getString(id: Int): String =
|
|
12
17
|
resolve(peachAppStrings[id]) ?: throw NotFoundException("String resource ID #0x${id.toString(16)}")
|
|
13
18
|
|
|
@@ -31,6 +36,17 @@ class Resources internal constructor(val configuration: Configuration) {
|
|
|
31
36
|
resolve(peachAppArrays[id])?.toTypedArray()
|
|
32
37
|
?: throw NotFoundException("String array resource ID #0x${id.toString(16)}")
|
|
33
38
|
|
|
39
|
+
// the color the configuration's night mode selects, values-night over
|
|
40
|
+
// values
|
|
41
|
+
fun getColor(id: Int, theme: Theme?): Int {
|
|
42
|
+
val colors = peachAppColors[id] ?: throw NotFoundException("Resource ID #0x${id.toString(16)}")
|
|
43
|
+
val night = configuration.uiMode and Configuration.UI_MODE_NIGHT_MASK == Configuration.UI_MODE_NIGHT_YES
|
|
44
|
+
return colors[if (night) 1 else 0]
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
@Deprecated("Use getColor(int, Theme) instead.")
|
|
48
|
+
fun getColor(id: Int): Int = getColor(id, null)
|
|
49
|
+
|
|
34
50
|
// the value the configuration's locale selects: its language and region,
|
|
35
51
|
// then its language, then the default values, as the resource table
|
|
36
52
|
// matches a locale qualifier. a plurals or array resource resolves whole
|
|
@@ -2,18 +2,26 @@ package androidx.compose.ui.platform
|
|
|
2
2
|
|
|
3
3
|
import android.content.Context
|
|
4
4
|
import android.content.res.Configuration
|
|
5
|
+
import android.content.res.Resources
|
|
5
6
|
import androidx.compose.runtime.compositionLocalOf
|
|
7
|
+
import androidx.compose.runtime.compositionLocalWithComputedDefaultOf
|
|
6
8
|
import androidx.compose.runtime.neverEqualPolicy
|
|
7
9
|
import androidx.compose.runtime.staticCompositionLocalOf
|
|
8
10
|
|
|
9
|
-
// AndroidCompositionLocals.android.kt: the configuration
|
|
10
|
-
// ComposeView's content reads, provided where its composition starts
|
|
11
|
+
// AndroidCompositionLocals.android.kt: the configuration, context and
|
|
12
|
+
// resources a ComposeView's content reads, provided where its composition starts
|
|
11
13
|
val LocalConfiguration = compositionLocalOf<Configuration>(neverEqualPolicy()) {
|
|
12
14
|
noLocalProvidedFor("LocalConfiguration")
|
|
13
15
|
}
|
|
14
16
|
|
|
15
17
|
val LocalContext = staticCompositionLocalOf<Context> { noLocalProvidedFor("LocalContext") }
|
|
16
18
|
|
|
19
|
+
// the context's resources, recomputed when the configuration changes
|
|
20
|
+
val LocalResources = compositionLocalWithComputedDefaultOf<Resources> {
|
|
21
|
+
LocalConfiguration.currentValue
|
|
22
|
+
LocalContext.currentValue.resources
|
|
23
|
+
}
|
|
24
|
+
|
|
17
25
|
private fun noLocalProvidedFor(name: String): Nothing {
|
|
18
26
|
error("CompositionLocal $name not present")
|
|
19
27
|
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
package androidx.compose.ui.res
|
|
2
|
+
|
|
3
|
+
import androidx.compose.runtime.Composable
|
|
4
|
+
import androidx.compose.runtime.ReadOnlyComposable
|
|
5
|
+
import androidx.compose.ui.graphics.Color
|
|
6
|
+
import androidx.compose.ui.platform.LocalContext
|
|
7
|
+
import androidx.compose.ui.platform.LocalResources
|
|
8
|
+
|
|
9
|
+
// ColorResources.android.kt: ResourcesCompat.getColor on API 23 and up is
|
|
10
|
+
// Resources.getColor with the context's theme
|
|
11
|
+
@Composable
|
|
12
|
+
@ReadOnlyComposable
|
|
13
|
+
fun colorResource(id: Int): Color {
|
|
14
|
+
val context = LocalContext.current
|
|
15
|
+
return Color(LocalResources.current.getColor(id, context.theme))
|
|
16
|
+
}
|
|
@@ -1,53 +1,38 @@
|
|
|
1
1
|
package androidx.compose.ui.res
|
|
2
2
|
|
|
3
|
-
import android.content.res.Resources
|
|
4
3
|
import androidx.compose.runtime.Composable
|
|
5
4
|
import androidx.compose.runtime.ReadOnlyComposable
|
|
6
|
-
import androidx.compose.ui.platform.
|
|
7
|
-
import androidx.compose.ui.platform.LocalContext
|
|
5
|
+
import androidx.compose.ui.platform.LocalResources
|
|
8
6
|
|
|
9
|
-
// StringResources.android.kt
|
|
10
|
-
//
|
|
11
|
-
// when its configuration changes
|
|
7
|
+
// StringResources.android.kt: string, string array and plural resources read
|
|
8
|
+
// through the composition's resources
|
|
12
9
|
|
|
13
10
|
@Composable
|
|
14
11
|
@ReadOnlyComposable
|
|
15
12
|
fun stringResource(id: Int): String {
|
|
16
|
-
|
|
17
|
-
return resources.getString(id)
|
|
13
|
+
return LocalResources.current.getString(id)
|
|
18
14
|
}
|
|
19
15
|
|
|
20
16
|
@Composable
|
|
21
17
|
@ReadOnlyComposable
|
|
22
18
|
fun stringResource(id: Int, vararg formatArgs: Any): String {
|
|
23
|
-
|
|
24
|
-
return resources.getString(id, *formatArgs)
|
|
19
|
+
return LocalResources.current.getString(id, *formatArgs)
|
|
25
20
|
}
|
|
26
21
|
|
|
27
22
|
@Composable
|
|
28
23
|
@ReadOnlyComposable
|
|
29
|
-
fun
|
|
30
|
-
|
|
31
|
-
return resources.getQuantityString(id, count)
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
@Composable
|
|
35
|
-
@ReadOnlyComposable
|
|
36
|
-
fun pluralStringResource(id: Int, count: Int, vararg formatArgs: Any): String {
|
|
37
|
-
val resources = resources()
|
|
38
|
-
return resources.getQuantityString(id, count, *formatArgs)
|
|
24
|
+
fun stringArrayResource(id: Int): Array<String> {
|
|
25
|
+
return LocalResources.current.getStringArray(id)
|
|
39
26
|
}
|
|
40
27
|
|
|
41
28
|
@Composable
|
|
42
29
|
@ReadOnlyComposable
|
|
43
|
-
fun
|
|
44
|
-
|
|
45
|
-
return resources.getStringArray(id)
|
|
30
|
+
fun pluralStringResource(id: Int, count: Int): String {
|
|
31
|
+
return LocalResources.current.getQuantityString(id, count)
|
|
46
32
|
}
|
|
47
33
|
|
|
48
34
|
@Composable
|
|
49
35
|
@ReadOnlyComposable
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
return LocalContext.current.resources
|
|
36
|
+
fun pluralStringResource(id: Int, count: Int, vararg formatArgs: Any): String {
|
|
37
|
+
return LocalResources.current.getQuantityString(id, count, *formatArgs)
|
|
53
38
|
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
package dev.contrast.kotlinapp
|
|
2
|
+
|
|
3
|
+
import androidx.compose.foundation.background
|
|
4
|
+
import androidx.compose.foundation.layout.Arrangement
|
|
5
|
+
import androidx.compose.foundation.layout.Box
|
|
6
|
+
import androidx.compose.foundation.layout.Column
|
|
7
|
+
import androidx.compose.foundation.layout.Row
|
|
8
|
+
import androidx.compose.foundation.layout.fillMaxSize
|
|
9
|
+
import androidx.compose.foundation.layout.padding
|
|
10
|
+
import androidx.compose.foundation.layout.safeDrawingPadding
|
|
11
|
+
import androidx.compose.foundation.layout.size
|
|
12
|
+
import androidx.compose.material3.Text
|
|
13
|
+
import androidx.compose.runtime.Composable
|
|
14
|
+
import androidx.compose.ui.Modifier
|
|
15
|
+
import androidx.compose.ui.graphics.Color
|
|
16
|
+
import androidx.compose.ui.platform.LocalContext
|
|
17
|
+
import androidx.compose.ui.res.colorResource
|
|
18
|
+
import androidx.compose.ui.unit.dp
|
|
19
|
+
|
|
20
|
+
// color resources through colorResource and the context: each swatch and its
|
|
21
|
+
// ARGB, in the day values and after a night relaunch in values-night
|
|
22
|
+
@Composable
|
|
23
|
+
fun ColorsScreen() {
|
|
24
|
+
val context = LocalContext.current
|
|
25
|
+
val ids = listOf(R.color.brand, R.color.short_rgb, R.color.alpha, R.color.short_argb, R.color.alias, R.color.framework)
|
|
26
|
+
Column(
|
|
27
|
+
Modifier.fillMaxSize().background(Color(0xFFE8EEF8)).safeDrawingPadding().padding(16.dp),
|
|
28
|
+
verticalArrangement = Arrangement.spacedBy(8.dp),
|
|
29
|
+
) {
|
|
30
|
+
Text("Colors: " + context.getColor(R.color.brand).toUInt().toString(16))
|
|
31
|
+
Text(ids.joinToString(" ") { context.getColor(it).toUInt().toString(16) })
|
|
32
|
+
Row(horizontalArrangement = Arrangement.spacedBy(8.dp)) {
|
|
33
|
+
Box(Modifier.size(48.dp).background(colorResource(R.color.brand)))
|
|
34
|
+
Box(Modifier.size(48.dp).background(colorResource(R.color.short_rgb)))
|
|
35
|
+
Box(Modifier.size(48.dp).background(colorResource(R.color.alpha)))
|
|
36
|
+
Box(Modifier.size(48.dp).background(colorResource(R.color.short_argb)))
|
|
37
|
+
Box(Modifier.size(48.dp).background(colorResource(R.color.alias)))
|
|
38
|
+
Box(Modifier.size(48.dp).background(colorResource(R.color.framework)))
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
}
|
package/package.json
CHANGED
package/src/android-theme.ts
CHANGED
|
@@ -40,7 +40,7 @@ export function readResourceValues(
|
|
|
40
40
|
return values
|
|
41
41
|
}
|
|
42
42
|
|
|
43
|
-
type Namespace = 'android' | 'app'
|
|
43
|
+
export type Namespace = 'android' | 'app'
|
|
44
44
|
interface StyleRef {
|
|
45
45
|
namespace: Namespace
|
|
46
46
|
name: string
|
|
@@ -155,10 +155,7 @@ export function resolveThemeAttribute(
|
|
|
155
155
|
throw new Error(
|
|
156
156
|
`${theme}: ${attribute} uses the dynamic color ${ref[2]}, which peach kotlin does not model`,
|
|
157
157
|
)
|
|
158
|
-
|
|
159
|
-
if (next === undefined) throw new Error(`${theme}: ${value} is not defined`)
|
|
160
|
-
found = { value: next, namespace: colorNamespace }
|
|
161
|
-
continue
|
|
158
|
+
return resourceColor(colorNamespace, ref[2], sources)
|
|
162
159
|
}
|
|
163
160
|
throw new Error(`${theme}: ${attribute} has the unsupported value ${value}`)
|
|
164
161
|
}
|
|
@@ -166,16 +163,35 @@ export function resolveThemeAttribute(
|
|
|
166
163
|
return undefined
|
|
167
164
|
}
|
|
168
165
|
|
|
169
|
-
// a
|
|
170
|
-
//
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
166
|
+
// a color resource as context.getColor reads it, following references to
|
|
167
|
+
// other colors in either namespace; a reference written in a framework file
|
|
168
|
+
// stays in the framework
|
|
169
|
+
export function resourceColor(
|
|
170
|
+
namespace: Namespace,
|
|
171
|
+
name: string,
|
|
172
|
+
sources: ThemeSources,
|
|
173
|
+
): number {
|
|
174
|
+
const seen = new Set<string>()
|
|
175
|
+
let ref = { namespace, name }
|
|
176
|
+
for (;;) {
|
|
177
|
+
const key = `${ref.namespace}:${ref.name}`
|
|
178
|
+
if (seen.has(key)) throw new Error(`@${key} refers to itself`)
|
|
179
|
+
seen.add(key)
|
|
180
|
+
const value = sources[ref.namespace].colors[ref.name]
|
|
181
|
+
if (value === undefined)
|
|
182
|
+
throw new Error(`@${ref.namespace}:color/${ref.name} is not defined`)
|
|
183
|
+
if (value.startsWith('#')) return color(value)
|
|
184
|
+
const next = /^@(?:(android):)?color\/(.+)$/.exec(value)
|
|
185
|
+
if (!next?.[2])
|
|
186
|
+
throw new Error(
|
|
187
|
+
`@${ref.namespace}:color/${ref.name} has the unsupported value ${value}`,
|
|
188
|
+
)
|
|
189
|
+
const nextNamespace: Namespace = next[1] ? 'android' : ref.namespace
|
|
190
|
+
// system_ colors are the device's dynamic palette, set at runtime
|
|
191
|
+
if (nextNamespace === 'android' && next[2].startsWith('system_'))
|
|
192
|
+
throw new Error(
|
|
193
|
+
`@${ref.namespace}:color/${ref.name} uses the dynamic color ${next[2]}, which peach kotlin does not model`,
|
|
194
|
+
)
|
|
195
|
+
ref = { namespace: nextNamespace, name: next[2] }
|
|
178
196
|
}
|
|
179
|
-
if (value === undefined) throw new Error(`@android:color/${name} is not defined`)
|
|
180
|
-
return color(value)
|
|
181
197
|
}
|
package/src/kotlin-app.ts
CHANGED
|
@@ -7,10 +7,11 @@ import fs from 'node:fs'
|
|
|
7
7
|
import path from 'node:path'
|
|
8
8
|
import { readAndroidStrings, type AndroidStringResources } from './android-strings'
|
|
9
9
|
import {
|
|
10
|
-
frameworkColor,
|
|
11
10
|
readResourceValues,
|
|
12
11
|
resolveThemeAttribute,
|
|
12
|
+
resourceColor,
|
|
13
13
|
type ResourceValues,
|
|
14
|
+
type ThemeSources,
|
|
14
15
|
} from './android-theme'
|
|
15
16
|
import { parseXml, type XmlElement } from './android-xml'
|
|
16
17
|
import frameworkThemes from './generated/android-framework-themes.json' with { type: 'json' }
|
|
@@ -50,6 +51,14 @@ export interface KotlinAppProject {
|
|
|
50
51
|
softInputAdjust: 'pan' | 'resize' | 'nothing'
|
|
51
52
|
// the module's string resources, which its R class names
|
|
52
53
|
strings: AndroidStringResources
|
|
54
|
+
colors: KotlinAppColor[]
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// a color resource's ARGB in the day and the night configuration
|
|
58
|
+
export interface KotlinAppColor {
|
|
59
|
+
name: string
|
|
60
|
+
day: number
|
|
61
|
+
night: number
|
|
53
62
|
}
|
|
54
63
|
|
|
55
64
|
export interface AndroidPins {
|
|
@@ -195,43 +204,71 @@ function defaultTheme(targetSdk: number): string {
|
|
|
195
204
|
return '@android:style/Theme.DeviceDefault.Light.DarkActionBar'
|
|
196
205
|
}
|
|
197
206
|
|
|
198
|
-
// PhoneWindow.generateLayout's status bar reads from the activity's theme.
|
|
199
|
-
// enforcement is isEdgeToEdgeEnforced: target 35 and up unless the theme opts
|
|
200
|
-
// out, which only target 35 may. an enforced window's status bar is
|
|
201
|
-
// transparent and setStatusBarColor does nothing. an unset or @null value
|
|
202
|
-
// reads as the default PhoneWindow passes, as TypedArray returns it.
|
|
203
207
|
// a night configuration takes the styles and colors values-night redefines,
|
|
204
208
|
// in the app and the framework, over values.
|
|
205
|
-
function
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
targetSdk: number,
|
|
209
|
-
night: boolean,
|
|
210
|
-
): KotlinWindowTheme {
|
|
211
|
-
const read = (qualifier: string) => {
|
|
212
|
-
const values = path.join(moduleDir, 'src/main/res', qualifier)
|
|
213
|
-
return readResourceValues(
|
|
214
|
-
fs.existsSync(values)
|
|
215
|
-
? fs
|
|
216
|
-
.readdirSync(values)
|
|
217
|
-
.filter((entry) => entry.endsWith('.xml'))
|
|
218
|
-
.map((entry) => {
|
|
219
|
-
const file = path.join(values, entry)
|
|
220
|
-
return { file, text: fs.readFileSync(file, 'utf8') }
|
|
221
|
-
})
|
|
222
|
-
: [],
|
|
223
|
-
)
|
|
224
|
-
}
|
|
209
|
+
function themeSources(moduleDir: string, night: boolean): ThemeSources {
|
|
210
|
+
const read = (qualifier: string) =>
|
|
211
|
+
readResourceValues(valuesFiles(moduleDir, qualifier))
|
|
225
212
|
const over = (base: ResourceValues, qualified: ResourceValues): ResourceValues => ({
|
|
226
213
|
styles: { ...base.styles, ...qualified.styles },
|
|
227
214
|
colors: { ...base.colors, ...qualified.colors },
|
|
228
215
|
})
|
|
229
|
-
|
|
216
|
+
return night
|
|
230
217
|
? {
|
|
231
218
|
android: over(frameworkThemes, frameworkThemes.night),
|
|
232
219
|
app: over(read('values'), read('values-night')),
|
|
233
220
|
}
|
|
234
221
|
: { android: frameworkThemes, app: read('values') }
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
function valuesFiles(moduleDir: string, qualifier: string) {
|
|
225
|
+
const values = path.join(moduleDir, 'src/main/res', qualifier)
|
|
226
|
+
return fs.existsSync(values)
|
|
227
|
+
? fs
|
|
228
|
+
.readdirSync(values)
|
|
229
|
+
.filter((entry) => entry.endsWith('.xml'))
|
|
230
|
+
.map((entry) => {
|
|
231
|
+
const file = path.join(values, entry)
|
|
232
|
+
return { file, text: fs.readFileSync(file, 'utf8') }
|
|
233
|
+
})
|
|
234
|
+
: []
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
// the app's color resources as getColor resolves them in a day and a night
|
|
238
|
+
// configuration. only colors the default values define get an R field, as
|
|
239
|
+
// AGP's compile R lists; a color under a qualifier other than night is
|
|
240
|
+
// rejected by name
|
|
241
|
+
function readAppColors(moduleDir: string): KotlinAppColor[] {
|
|
242
|
+
const res = path.join(moduleDir, 'src/main/res')
|
|
243
|
+
for (const dir of fs.existsSync(res) ? fs.readdirSync(res).sort() : []) {
|
|
244
|
+
if (!dir.startsWith('values') || dir === 'values' || dir === 'values-night') continue
|
|
245
|
+
const colors = Object.keys(readResourceValues(valuesFiles(moduleDir, dir)).colors)
|
|
246
|
+
if (colors.length > 0)
|
|
247
|
+
throw new Error(
|
|
248
|
+
`${res}/${dir}: color ${colors[0]} is not resolved by peach kotlin yet (only values and values-night colors)`,
|
|
249
|
+
)
|
|
250
|
+
}
|
|
251
|
+
const day = themeSources(moduleDir, false)
|
|
252
|
+
const night = themeSources(moduleDir, true)
|
|
253
|
+
return Object.keys(day.app.colors)
|
|
254
|
+
.sort()
|
|
255
|
+
.map((name) => ({
|
|
256
|
+
name,
|
|
257
|
+
day: resourceColor('app', name, day),
|
|
258
|
+
night: resourceColor('app', name, night),
|
|
259
|
+
}))
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
// PhoneWindow.generateLayout's status bar reads from the activity's theme.
|
|
263
|
+
// enforcement is isEdgeToEdgeEnforced: target 35 and up unless the theme opts
|
|
264
|
+
// out, which only target 35 may. an enforced window's status bar is
|
|
265
|
+
// transparent and setStatusBarColor does nothing. an unset or @null value
|
|
266
|
+
// reads as the default PhoneWindow passes, as TypedArray returns it.
|
|
267
|
+
function readWindowTheme(
|
|
268
|
+
theme: string | undefined,
|
|
269
|
+
targetSdk: number,
|
|
270
|
+
sources: ThemeSources,
|
|
271
|
+
): KotlinWindowTheme {
|
|
235
272
|
const name = theme ?? defaultTheme(targetSdk)
|
|
236
273
|
const flag = (attribute: string, fallback = false) => {
|
|
237
274
|
const value = resolveThemeAttribute(name, attribute, sources)
|
|
@@ -259,11 +296,11 @@ function readWindowTheme(
|
|
|
259
296
|
const navigationColor = resolveThemeAttribute(name, 'navigationBarColor', sources)
|
|
260
297
|
if (typeof navigationColor === 'boolean')
|
|
261
298
|
throw new Error(`${name}: navigationBarColor is not a color`)
|
|
262
|
-
const navigationDefault =
|
|
299
|
+
const navigationDefault = resourceColor('android', 'navigation_bar_default', sources)
|
|
263
300
|
const navigationTheme = navigationColor ?? navigationDefault
|
|
264
301
|
const navigationBarColor =
|
|
265
302
|
navigationTheme === navigationDefault && !edgeToEdgeEnforced
|
|
266
|
-
?
|
|
303
|
+
? resourceColor('android', 'navigation_bar_compatible', sources)
|
|
267
304
|
: navigationTheme
|
|
268
305
|
// PhoneWindow enforces edge to edge apart from whether the theme draws bar
|
|
269
306
|
// backgrounds: either way the bar is transparent
|
|
@@ -496,8 +533,8 @@ export function readKotlinApp(
|
|
|
496
533
|
namespace: base,
|
|
497
534
|
targetSdk,
|
|
498
535
|
window: {
|
|
499
|
-
day: readWindowTheme(
|
|
500
|
-
night: readWindowTheme(
|
|
536
|
+
day: readWindowTheme(theme, targetSdk, themeSources(moduleDir, false)),
|
|
537
|
+
night: readWindowTheme(theme, targetSdk, themeSources(moduleDir, true)),
|
|
501
538
|
},
|
|
502
539
|
applicationClass: applicationName ? className(applicationName) : null,
|
|
503
540
|
launcher: { name: launcherName, targetClass },
|
|
@@ -505,5 +542,6 @@ export function readKotlinApp(
|
|
|
505
542
|
handlesUiMode,
|
|
506
543
|
softInputAdjust,
|
|
507
544
|
strings: readAndroidStrings(moduleDir),
|
|
545
|
+
colors: readAppColors(moduleDir),
|
|
508
546
|
}
|
|
509
547
|
}
|
package/src/kotlin-host.ts
CHANGED
|
@@ -436,8 +436,8 @@ export async function kotlinAppArtifact(
|
|
|
436
436
|
)
|
|
437
437
|
}
|
|
438
438
|
|
|
439
|
-
// the module's R class in its namespace and the string, plurals and
|
|
440
|
-
// tables Resources reads. an id is 0x7f, the type, then the entry in name
|
|
439
|
+
// the module's R class in its namespace and the string, plurals, array and
|
|
440
|
+
// color tables Resources reads. an id is 0x7f, the type, then the entry in name
|
|
441
441
|
// order. a device's ids also count the module's other resource types and the
|
|
442
442
|
// resources its libraries merge in, which Peach does not build, so the
|
|
443
443
|
// numbers themselves differ
|
|
@@ -459,48 +459,61 @@ function appResourceSources(app: KotlinAppProject): Record<string, string> {
|
|
|
459
459
|
const field = (name: string) => `\`${name.replace(/[.-]/g, '_')}\``
|
|
460
460
|
const id = (type: number, entry: number) =>
|
|
461
461
|
`0x7f${type.toString(16).padStart(2, '0')}${entry.toString(16).padStart(4, '0')}`
|
|
462
|
+
const localized = <Value>(
|
|
463
|
+
resources: Array<{ name: string; values: Record<string, Value> }>,
|
|
464
|
+
render: (value: Value) => string,
|
|
465
|
+
) =>
|
|
466
|
+
resources.map((resource) => ({
|
|
467
|
+
name: resource.name,
|
|
468
|
+
init: `mapOf(${Object.entries(resource.values)
|
|
469
|
+
.map(([locale, value]) => `${literal(locale)} to ${render(value)}`)
|
|
470
|
+
.join(', ')})`,
|
|
471
|
+
}))
|
|
472
|
+
const argb = (value: number) =>
|
|
473
|
+
`0x${value.toString(16).padStart(8, '0').toUpperCase()}.toInt()`
|
|
462
474
|
const kinds = [
|
|
463
475
|
{
|
|
464
476
|
type: 'string',
|
|
465
477
|
table: 'peachAppStrings',
|
|
466
|
-
value: 'String',
|
|
467
|
-
|
|
478
|
+
value: 'Map<String, String>',
|
|
479
|
+
entries: localized(app.strings.strings, literal),
|
|
468
480
|
},
|
|
469
481
|
{
|
|
470
482
|
type: 'plurals',
|
|
471
483
|
table: 'peachAppPlurals',
|
|
472
|
-
value: 'Map<String, String
|
|
473
|
-
|
|
484
|
+
value: 'Map<String, Map<String, String>>',
|
|
485
|
+
entries: localized(
|
|
486
|
+
app.strings.plurals,
|
|
487
|
+
(items) =>
|
|
488
|
+
`mapOf(${Object.entries(items)
|
|
489
|
+
.map(([quantity, text]) => `${literal(quantity)} to ${literal(text ?? '')}`)
|
|
490
|
+
.join(', ')})`,
|
|
491
|
+
),
|
|
474
492
|
},
|
|
475
493
|
{
|
|
476
494
|
type: 'array',
|
|
477
495
|
table: 'peachAppArrays',
|
|
478
|
-
value: 'List<String
|
|
479
|
-
|
|
496
|
+
value: 'Map<String, List<String>>',
|
|
497
|
+
entries: localized(
|
|
498
|
+
app.strings.arrays,
|
|
499
|
+
(items) => `listOf(${items.map(literal).join(', ')})`,
|
|
500
|
+
),
|
|
501
|
+
},
|
|
502
|
+
// the day and the night ARGB
|
|
503
|
+
{
|
|
504
|
+
type: 'color',
|
|
505
|
+
table: 'peachAppColors',
|
|
506
|
+
value: 'IntArray',
|
|
507
|
+
entries: app.colors.map((color) => ({
|
|
508
|
+
name: color.name,
|
|
509
|
+
init: `intArrayOf(${argb(color.day)}, ${argb(color.night)})`,
|
|
510
|
+
})),
|
|
480
511
|
},
|
|
481
512
|
].map((kind, index) => ({
|
|
482
513
|
...kind,
|
|
483
|
-
entries: kind.
|
|
484
|
-
|
|
485
|
-
id: id(index + 1,
|
|
486
|
-
values: Object.entries(resource.values).map(
|
|
487
|
-
([locale, value]: [
|
|
488
|
-
string,
|
|
489
|
-
string | string[] | Partial<Record<string, string>>,
|
|
490
|
-
]) =>
|
|
491
|
-
`${literal(locale)} to ${
|
|
492
|
-
typeof value === 'string'
|
|
493
|
-
? literal(value)
|
|
494
|
-
: Array.isArray(value)
|
|
495
|
-
? `listOf(${value.map(literal).join(', ')})`
|
|
496
|
-
: `mapOf(${Object.entries(value)
|
|
497
|
-
.map(
|
|
498
|
-
([quantity, text]) =>
|
|
499
|
-
`${literal(quantity)} to ${literal(text ?? '')}`,
|
|
500
|
-
)
|
|
501
|
-
.join(', ')})`
|
|
502
|
-
}`,
|
|
503
|
-
),
|
|
514
|
+
entries: kind.entries.map((entry, number) => ({
|
|
515
|
+
...entry,
|
|
516
|
+
id: id(index + 1, number),
|
|
504
517
|
})),
|
|
505
518
|
}))
|
|
506
519
|
return {
|
|
@@ -522,10 +535,8 @@ function appResourceSources(app: KotlinAppProject): Record<string, string> {
|
|
|
522
535
|
'package dev.peach.app',
|
|
523
536
|
'',
|
|
524
537
|
...kinds.flatMap((kind) => [
|
|
525
|
-
`internal val ${kind.table}: Map<Int,
|
|
526
|
-
...kind.entries.map(
|
|
527
|
-
(entry) => ` ${entry.id} to mapOf(${entry.values.join(', ')}),`,
|
|
528
|
-
),
|
|
538
|
+
`internal val ${kind.table}: Map<Int, ${kind.value}> = mapOf(`,
|
|
539
|
+
...kind.entries.map((entry) => ` ${entry.id} to ${entry.init},`),
|
|
529
540
|
')',
|
|
530
541
|
'',
|
|
531
542
|
]),
|