peachsim 0.1.676 → 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.
Files changed (78) hide show
  1. package/dist-lib/agent-daemon-client.cjs +1 -1
  2. package/dist-lib/agent-events.cjs +1 -1
  3. package/dist-lib/agent-identity.cjs +1 -1
  4. package/dist-lib/agent-sessions.cjs +1 -1
  5. package/dist-lib/attached-projects.cjs +1 -1
  6. package/dist-lib/auth/shared-session.cjs +1 -1
  7. package/dist-lib/backend-origin.cjs +1 -1
  8. package/dist-lib/beta.cjs +1 -1
  9. package/dist-lib/beta.mjs +1 -1
  10. package/dist-lib/bridge-constants.cjs +1 -1
  11. package/dist-lib/bridge-contract-input.cjs +1 -1
  12. package/dist-lib/bridge-contract-input.mjs +1 -1
  13. package/dist-lib/bridge-contract.cjs +1 -1
  14. package/dist-lib/bridge-contract.mjs +1 -1
  15. package/dist-lib/capture-contract.cjs +1 -1
  16. package/dist-lib/capture-contract.mjs +1 -1
  17. package/dist-lib/cli-constants.cjs +1 -1
  18. package/dist-lib/cloud-contract.cjs +1 -1
  19. package/dist-lib/cloud-contract.mjs +1 -1
  20. package/dist-lib/cloud-test-args.cjs +1 -1
  21. package/dist-lib/cloud-test-args.mjs +1 -1
  22. package/dist-lib/cloud.cjs +1 -1
  23. package/dist-lib/cloud.mjs +1 -1
  24. package/dist-lib/config.cjs +1 -1
  25. package/dist-lib/detox/index.cjs +1 -1
  26. package/dist-lib/dev-bundle-resolution.cjs +1 -1
  27. package/dist-lib/home-paths.cjs +1 -1
  28. package/dist-lib/host/bridge-host.cjs +1 -1
  29. package/dist-lib/host/fetch-proxy-handler.cjs +1 -1
  30. package/dist-lib/host/fetch-proxy-overrides.cjs +1 -1
  31. package/dist-lib/host/fetch-proxy-overrides.mjs +1 -1
  32. package/dist-lib/host/replacement-module-handler.cjs +1 -1
  33. package/dist-lib/host/websocket-proxy.cjs +1 -1
  34. package/dist-lib/index.cjs +1 -1
  35. package/dist-lib/jump-to-source-babel.cjs +1 -1
  36. package/dist-lib/jump-to-source-native.cjs +1 -1
  37. package/dist-lib/kotlin-host.cjs +33 -19
  38. package/dist-lib/menu.cjs +1 -1
  39. package/dist-lib/menu.mjs +1 -1
  40. package/dist-lib/metro-fingerprint-registry.cjs +1 -1
  41. package/dist-lib/metro-fingerprint-registry.mjs +1 -1
  42. package/dist-lib/metro-production-bundle.cjs +1 -1
  43. package/dist-lib/metro-production-bundle.mjs +1 -1
  44. package/dist-lib/metro.cjs +1 -1
  45. package/dist-lib/native-app-identity.cjs +1 -1
  46. package/dist-lib/native-app-identity.mjs +1 -1
  47. package/dist-lib/native-firebase-metadata.cjs +1 -1
  48. package/dist-lib/native-firebase-metadata.mjs +1 -1
  49. package/dist-lib/plist.cjs +1 -1
  50. package/dist-lib/plist.mjs +1 -1
  51. package/dist-lib/profiles.cjs +1 -1
  52. package/dist-lib/public-brand.cjs +1 -1
  53. package/dist-lib/react-native-host-modules.cjs +1 -1
  54. package/dist-lib/react-native-host-modules.mjs +1 -1
  55. package/dist-lib/render-mode.cjs +1 -1
  56. package/dist-lib/scripts/dev-server-scanner.cjs +1 -1
  57. package/dist-lib/sdk.cjs +1 -1
  58. package/dist-lib/sdk.mjs +1 -1
  59. package/dist-lib/skills.cjs +1 -1
  60. package/dist-lib/swift-compile.cjs +1 -1
  61. package/dist-lib/swift-compile.mjs +1 -1
  62. package/dist-lib/swift-package.cjs +1 -1
  63. package/dist-lib/swift-package.mjs +1 -1
  64. package/dist-lib/swift-submit.cjs +1 -1
  65. package/dist-lib/swift-submit.mjs +1 -1
  66. package/dist-lib/swift.cjs +1 -1
  67. package/dist-lib/vite-swift.cjs +1 -1
  68. package/dist-lib/vite.cjs +1 -1
  69. package/kotlin/AppSources/android/content/Context.kt +6 -0
  70. package/kotlin/AppSources/android/content/res/Resources.kt +16 -0
  71. package/kotlin/AppSources/androidx/compose/ui/platform/AndroidCompositionLocals.kt +10 -2
  72. package/kotlin/AppSources/androidx/compose/ui/res/ColorResources.kt +16 -0
  73. package/kotlin/AppSources/androidx/compose/ui/res/StringResources.kt +11 -26
  74. package/kotlin/fixtures/kotlin-app-variants/Colors.kt +41 -0
  75. package/package.json +1 -1
  76. package/src/android-theme.ts +32 -16
  77. package/src/kotlin-app.ts +69 -31
  78. package/src/kotlin-host.ts +44 -33
@@ -1,4 +1,4 @@
1
- /*! rnx v0.1.676 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
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.676 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
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.676 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
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.676 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
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.676 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
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.676 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
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.676 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
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/beta.cjs CHANGED
@@ -1,4 +1,4 @@
1
- /*! rnx v0.1.676 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
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/beta.mjs CHANGED
@@ -1,4 +1,4 @@
1
- /*! rnx v0.1.676 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
1
+ /*! rnx v0.1.677 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
2
2
 
3
3
  // src/beta.ts
4
4
  var IS_BETA = true;
@@ -1,4 +1,4 @@
1
- /*! rnx v0.1.676 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
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.676 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
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.676 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
1
+ /*! rnx v0.1.677 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
2
2
 
3
3
  // src/bridge-contract.ts
4
4
  var SIM_LONG_PRESS_MAX_MS = 5e3;
@@ -1,4 +1,4 @@
1
- /*! rnx v0.1.676 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
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.676 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
1
+ /*! rnx v0.1.677 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
2
2
 
3
3
  // src/bridge-contract.ts
4
4
  var REFUSED_PERFORM_STEP_TYPES = [
@@ -1,4 +1,4 @@
1
- /*! rnx v0.1.676 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
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.676 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
1
+ /*! rnx v0.1.677 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
2
2
 
3
3
  // src/capture-contract.ts
4
4
  var RNX_SCREEN_CAPTURE_VERSION = 1;
@@ -1,4 +1,4 @@
1
- /*! rnx v0.1.676 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
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.676 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
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.676 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
1
+ /*! rnx v0.1.677 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
2
2
 
3
3
  // src/cloud-contract.ts
4
4
  var RNX_CLOUD_MAX_ARTIFACT_BYTES = 20 * 1024 * 1024;
@@ -1,4 +1,4 @@
1
- /*! rnx v0.1.676 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
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.676 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
1
+ /*! rnx v0.1.677 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
2
2
 
3
3
  // src/cloud-test-args.ts
4
4
  var CLOUD_TEST_ARGS_MAX_COUNT = 32;
@@ -1,4 +1,4 @@
1
- /*! rnx v0.1.676 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
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.676 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
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);
@@ -1,4 +1,4 @@
1
- /*! rnx v0.1.676 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
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.676 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
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.676 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
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.676 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
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.676 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
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.676 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
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.676 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
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.676 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
1
+ /*! rnx v0.1.677 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
2
2
 
3
3
  // src/host/fetch-proxy-overrides.ts
4
4
  var FETCH_PROXY_BROWSER_USER_AGENT = "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36";
@@ -1,4 +1,4 @@
1
- /*! rnx v0.1.676 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
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.676 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
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.676 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
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.676 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
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.676 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
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.676 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
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;
@@ -470,35 +470,51 @@ function appResourceSources(app) {
470
470
  )}"`;
471
471
  const field = (name) => `\`${name.replace(/[.-]/g, "_")}\``;
472
472
  const id = (type, entry) => `0x7f${type.toString(16).padStart(2, "0")}${entry.toString(16).padStart(4, "0")}`;
473
+ const localized = (resources, render) => resources.map((resource) => ({
474
+ name: resource.name,
475
+ init: `mapOf(${Object.entries(resource.values).map(([locale, value]) => `${literal(locale)} to ${render(value)}`).join(", ")})`
476
+ }));
477
+ const argb = (value) => `0x${value.toString(16).padStart(8, "0").toUpperCase()}.toInt()`;
473
478
  const kinds = [
474
479
  {
475
480
  type: "string",
476
481
  table: "peachAppStrings",
477
- value: "String",
478
- resources: app.strings.strings
482
+ value: "Map<String, String>",
483
+ entries: localized(app.strings.strings, literal)
479
484
  },
480
485
  {
481
486
  type: "plurals",
482
487
  table: "peachAppPlurals",
483
- value: "Map<String, String>",
484
- resources: app.strings.plurals
488
+ value: "Map<String, Map<String, String>>",
489
+ entries: localized(
490
+ app.strings.plurals,
491
+ (items) => `mapOf(${Object.entries(items).map(([quantity, text]) => `${literal(quantity)} to ${literal(text ?? "")}`).join(", ")})`
492
+ )
485
493
  },
486
494
  {
487
495
  type: "array",
488
496
  table: "peachAppArrays",
489
- value: "List<String>",
490
- resources: app.strings.arrays
497
+ value: "Map<String, List<String>>",
498
+ entries: localized(
499
+ app.strings.arrays,
500
+ (items) => `listOf(${items.map(literal).join(", ")})`
501
+ )
502
+ },
503
+ // the day and the night ARGB
504
+ {
505
+ type: "color",
506
+ table: "peachAppColors",
507
+ value: "IntArray",
508
+ entries: app.colors.map((color) => ({
509
+ name: color.name,
510
+ init: `intArrayOf(${argb(color.day)}, ${argb(color.night)})`
511
+ }))
491
512
  }
492
513
  ].map((kind, index) => ({
493
514
  ...kind,
494
- entries: kind.resources.map((resource, entry) => ({
495
- name: resource.name,
496
- id: id(index + 1, entry),
497
- values: Object.entries(resource.values).map(
498
- ([locale, value]) => `${literal(locale)} to ${typeof value === "string" ? literal(value) : Array.isArray(value) ? `listOf(${value.map(literal).join(", ")})` : `mapOf(${Object.entries(value).map(
499
- ([quantity, text]) => `${literal(quantity)} to ${literal(text ?? "")}`
500
- ).join(", ")})`}`
501
- )
515
+ entries: kind.entries.map((entry, number) => ({
516
+ ...entry,
517
+ id: id(index + 1, number)
502
518
  }))
503
519
  }));
504
520
  return {
@@ -520,10 +536,8 @@ function appResourceSources(app) {
520
536
  "package dev.peach.app",
521
537
  "",
522
538
  ...kinds.flatMap((kind) => [
523
- `internal val ${kind.table}: Map<Int, Map<String, ${kind.value}>> = mapOf(`,
524
- ...kind.entries.map(
525
- (entry) => ` ${entry.id} to mapOf(${entry.values.join(", ")}),`
526
- ),
539
+ `internal val ${kind.table}: Map<Int, ${kind.value}> = mapOf(`,
540
+ ...kind.entries.map((entry) => ` ${entry.id} to ${entry.init},`),
527
541
  ")",
528
542
  ""
529
543
  ])
package/dist-lib/menu.cjs CHANGED
@@ -1,4 +1,4 @@
1
- /*! rnx v0.1.676 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
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/menu.mjs CHANGED
@@ -1,4 +1,4 @@
1
- /*! rnx v0.1.676 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
1
+ /*! rnx v0.1.677 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
2
2
 
3
3
  // src/public-brand.ts
4
4
  var name = "Peach";
@@ -1,4 +1,4 @@
1
- /*! rnx v0.1.676 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
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.676 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
1
+ /*! rnx v0.1.677 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
2
2
 
3
3
  // src/is-record.ts
4
4
  function isRecord(value) {
@@ -1,4 +1,4 @@
1
- /*! rnx v0.1.676 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
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.676 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
1
+ /*! rnx v0.1.677 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
2
2
 
3
3
  // src/metro-production-bundle.ts
4
4
  var RNXSIM_METRO_MODULE_PATHS_PREFIX = "globalThis.__sootsimModulePaths=";
@@ -1,4 +1,4 @@
1
- /*! rnx v0.1.676 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
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.676 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
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.676 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
1
+ /*! rnx v0.1.677 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
2
2
 
3
3
  // src/native-app-identity.ts
4
4
  var NATIVE_APP_IDENTITY_PREFIX = "globalThis.__sootsimNativeAppIdentity=";
@@ -1,4 +1,4 @@
1
- /*! rnx v0.1.676 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
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.676 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
1
+ /*! rnx v0.1.677 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
2
2
 
3
3
  // src/native-firebase-metadata.ts
4
4
  var PREFIX = "globalThis.__peachNativeFirebaseConfig=";
@@ -1,4 +1,4 @@
1
- /*! rnx v0.1.676 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
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.676 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
1
+ /*! rnx v0.1.677 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
2
2
 
3
3
  // src/plist.ts
4
4
  import plistModule from "@expo/plist";
@@ -1,4 +1,4 @@
1
- /*! rnx v0.1.676 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
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.676 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
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.676 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
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.676 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
1
+ /*! rnx v0.1.677 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
2
2
 
3
3
  // src/react-native-host-modules.ts
4
4
  var REACT_NATIVE_PASSTHROUGH_SPECIFIERS = [
@@ -1,4 +1,4 @@
1
- /*! rnx v0.1.676 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
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.676 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
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.676 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
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.676 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
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);
@@ -1,4 +1,4 @@
1
- /*! rnx v0.1.676 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
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.676 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
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.676 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
1
+ /*! rnx v0.1.677 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
2
2
 
3
3
  // src/swift-compile.ts
4
4
  import { execFile as execFile2 } from "node:child_process";
@@ -1,4 +1,4 @@
1
- /*! rnx v0.1.676 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
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.676 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
1
+ /*! rnx v0.1.677 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
2
2
 
3
3
  // src/swift-package.ts
4
4
  function isSwiftManifestFile(file) {
@@ -1,4 +1,4 @@
1
- /*! rnx v0.1.676 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
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.676 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
1
+ /*! rnx v0.1.677 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
2
2
 
3
3
  // src/swift-submit.ts
4
4
  var SWIFT_COMPILE_PATH = "/api/swift-compile";
@@ -1,4 +1,4 @@
1
- /*! rnx v0.1.676 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
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.676 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
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.676 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
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 and context a
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.LocalConfiguration
7
- import androidx.compose.ui.platform.LocalContext
5
+ import androidx.compose.ui.platform.LocalResources
8
6
 
9
- // StringResources.android.kt and Resources.android.kt: a string, plural or
10
- // string array resource read through the composition's context, recomposed
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
- val resources = resources()
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
- val resources = resources()
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 pluralStringResource(id: Int, count: Int): String {
30
- val resources = resources()
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 stringArrayResource(id: Int): Array<String> {
44
- val resources = resources()
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
- internal fun resources(): Resources {
51
- LocalConfiguration.current
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "peachsim",
3
- "version": "0.1.676",
3
+ "version": "0.1.677",
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",
@@ -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
- const next = sources[colorNamespace].colors[ref[2]]
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 framework color resource as context.getColor reads it, following
170
- // references to other framework colors
171
- export function frameworkColor(name: string, sources: ThemeSources): number {
172
- let value = sources.android.colors[name]
173
- while (value !== undefined && !value.startsWith('#')) {
174
- const ref = /^@(?:android:)?color\/(.+)$/.exec(value)
175
- if (!ref?.[1])
176
- throw new Error(`@android:color/${name} has the unsupported value ${value}`)
177
- value = sources.android.colors[ref[1]]
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 readWindowTheme(
206
- moduleDir: string,
207
- theme: string | undefined,
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
- const sources = night
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 = frameworkColor('navigation_bar_default', sources)
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
- ? frameworkColor('navigation_bar_compatible', sources)
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(moduleDir, theme, targetSdk, false),
500
- night: readWindowTheme(moduleDir, theme, targetSdk, true),
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
  }
@@ -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 array
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
- resources: app.strings.strings,
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
- resources: app.strings.plurals,
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
- resources: app.strings.arrays,
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.resources.map((resource, entry) => ({
484
- name: resource.name,
485
- id: id(index + 1, entry),
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, Map<String, ${kind.value}>> = mapOf(`,
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
  ]),