Phase 6, steps 1 and 2. The app had no notion of window width at all -- two
`BoxWithConstraints` in 30,000 lines of UI, both inside a view model -- so every
layout decision in it was made once, for a phone, and then rendered unchanged
into a 1800dp desktop window.
**The dependency question the plan asked to settle first.** `material3-adaptive`
publishes multiplatform under `org.jetbrains.compose.material3.adaptive`, with
android, desktop and ios variants; the ios ones carry `ios_arm64` and
`ios_simulator_arm64` attributes despite the `uikit*` artifact names, so the
targets this project declares on a mac resolve. Version **1.2.0**, not the newer
1.3.0-beta02, because that is the version the pinned material3 itself resolves:
`material3-adaptive-navigation-suite:1.10.0-alpha05` names `adaptive:1.2.0` in
its pom, and 1.3.0 would pull window-core 1.5.0 in beside the 1.4.0 the pinned
material3 compiled against. Nothing is lost by staying: 1.2.0 already computes
the large and extra-large breakpoints through `supportLargeAndXLargeWidth`, and
carries `ListDetailPaneScaffold` for the pane work. So steps 3-4 can use the
library scaffolds rather than a hand-rolled equivalent.
**`Breakpoint`** is the five-value enum -- compact / medium / expanded / large /
extra-large at 0 / 600 / 840 / 1200 / 1600dp -- with `ofWidth` as a pure function
so the thresholds are assertable without a Compose runtime. `TorchTheme`
classifies once and provides `LocalBreakpoint`, so no two screens can disagree
about the window they are both in.
It reads `currentWindowDpSize()` rather than `currentWindowAdaptiveInfo()`.
The latter also computes a `Posture` from the platform's fold state, which on
android reaches for `WindowInfoTracker` and an activity; this call sits in
`TorchTheme`, which wraps all 51 `@Preview` bodies in the tree, and a preview
context is not an activity. The pane scaffolds ask for posture themselves, at
the one place a fold changes the answer.
**Spacing now adapts, and exactly one value moves.** M3 publishes a margin per
breakpoint -- 16dp compact, 24dp everywhere wider -- and publishes nothing else
that varies with window width. The scale itself is absolute: `space200` is 16dp
on a phone and 16dp on a desktop, and what adapts is which token a job reaches
for, not the token. So `screenMargin` goes 16 -> 24 at medium and holds there,
and `containerPadding`, `itemGap` and the rest do not move -- a card does not
become a different component because the window grew. Widening all of them is
the "everything breathes on a big screen" instinct, and it reads as a zoomed
phone rather than as a layout. A test asserts the non-movement, because that is
the edit a later reviewer would wave through.
Mechanically this made the eight semantic names constructor parameters instead
of `get()`s over the scale, so a breakpoint can reassign one without moving the
stop underneath it. Kotlin resolves a default expression against the parameters
before it, so each still reads its stop by name and still follows it when the
scale is overridden -- phase 2's `Spacing(space200 = 24.dp)` assertion holds
unchanged. The two instances are singletons because `LocalSpacing` is a
`staticCompositionLocalOf` and invalidates on identity, not equality.
**A test found a real defect while being written.** `ofWidth` was
`entries.last { width >= it.minWidth }`, which throws `NoSuchElementException`
below 0dp. A desktop window reports a zero size for the frame before its first
layout pass, and this is called from the theme on every composition, so the
crash would have arrived on a resize rather than on anything a user did. Now
total.
`:composeApp:compileDebugKotlinAndroid` and `:composeApp:compileKotlinJvm` both
green; 28 theme tests pass.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
103 lines
7.1 KiB
TOML
103 lines
7.1 KiB
TOML
[versions]
|
|
agp = "9.4.0"
|
|
android-compileSdk = "37"
|
|
android-minSdk = "26"
|
|
android-targetSdk = "37"
|
|
androidx-activity = "1.13.0"
|
|
androidx-appcompat = "1.8.0"
|
|
androidx-core = "1.19.0"
|
|
androidx-espresso = "3.7.0"
|
|
androidx-lifecycle = "2.11.0"
|
|
androidx-testExt = "1.3.0"
|
|
coilCompose = "3.5.0"
|
|
composeHotReload = "1.2.0"
|
|
composeMultiplatform = "1.11.1"
|
|
datastorePreferences = "1.2.1"
|
|
junit = "4.13.2"
|
|
kermit = "2.1.0"
|
|
kotlin = "2.4.10"
|
|
kotlinx-coroutines = "1.11.0"
|
|
kotlinx-datetime = "0.8.0"
|
|
kotlinxSerialization = "1.11.0"
|
|
ksp = "2.3.6"
|
|
ktor = "3.5.2"
|
|
lightningKmpCore = "1.11.5"
|
|
material3 = "1.10.0-alpha05"
|
|
# The version material3 1.10.0-alpha05 itself resolves: its
|
|
# material3-adaptive-navigation-suite pom asks for adaptive 1.2.0. Picking the newer
|
|
# 1.3.0-beta02 would drag window-core 1.5.0 in beside the 1.4.0 the pinned material3
|
|
# compiled against, for a large/extra-large breakpoint pair that 1.2.0 already computes
|
|
# through `supportLargeAndXLargeWidth`.
|
|
material3Adaptive = "1.2.0"
|
|
materialIconsCore = "1.7.3"
|
|
materialIconsExtended = "1.7.3"
|
|
navigationCompose = "2.9.2"
|
|
okio = "3.18.1"
|
|
qrose = "1.1.2"
|
|
quartz = "1.14.0"
|
|
room3 = "3.0.1"
|
|
sqldelight = "2.3.2"
|
|
sqlite = "2.7.0"
|
|
|
|
[libraries]
|
|
androidx-core-ktx = { module = "androidx.core:core-ktx", version.ref = "androidx-core" }
|
|
androidx-testExt-junit = { module = "androidx.test.ext:junit", version.ref = "androidx-testExt" }
|
|
androidx-espresso-core = { module = "androidx.test.espresso:espresso-core", version.ref = "androidx-espresso" }
|
|
androidx-appcompat = { module = "androidx.appcompat:appcompat", version.ref = "androidx-appcompat" }
|
|
androidx-activity-compose = { module = "androidx.activity:activity-compose", version.ref = "androidx-activity" }
|
|
androidx-datastore = { module = "androidx.datastore:datastore", version.ref = "datastorePreferences" }
|
|
androidx-datastore-preferences = { module = "androidx.datastore:datastore-preferences", version.ref = "datastorePreferences" }
|
|
androidx-sqlite-bundled = { module = "androidx.sqlite:sqlite-bundled", version.ref = "sqlite" }
|
|
androidx-sqlite-bundled-jvm = { module = "androidx.sqlite:sqlite-bundled-jvm", version.ref = "sqlite" }
|
|
androidx-room3-runtime = { module = "androidx.room3:room3-runtime", version.ref = "room3" }
|
|
androidx-room3-compiler = { module = "androidx.room3:room3-compiler", version.ref = "room3" }
|
|
androidx-room3-sqlite-wrapper = { module = "androidx.room3:room3-sqlite-wrapper", version.ref = "room3" }
|
|
androidx-lifecycle-viewmodelCompose = { module = "org.jetbrains.androidx.lifecycle:lifecycle-viewmodel-compose", version.ref = "androidx-lifecycle" }
|
|
androidx-lifecycle-runtimeCompose = { module = "org.jetbrains.androidx.lifecycle:lifecycle-runtime-compose", version.ref = "androidx-lifecycle" }
|
|
coil-compose = { module = "io.coil-kt.coil3:coil-compose", version.ref = "coilCompose" }
|
|
coil-network-ktor3 = { module = "io.coil-kt.coil3:coil-network-ktor3", version.ref = "coilCompose" }
|
|
compose-components-resources = { module = "org.jetbrains.compose.components:components-resources", version.ref = "composeMultiplatform" }
|
|
compose-runtime = { module = "org.jetbrains.compose.runtime:runtime", version.ref = "composeMultiplatform" }
|
|
compose-foundation = { module = "org.jetbrains.compose.foundation:foundation", version.ref = "composeMultiplatform" }
|
|
compose-material3 = { module = "org.jetbrains.compose.material3:material3", version.ref = "material3" }
|
|
compose-material3-adaptive = { module = "org.jetbrains.compose.material3.adaptive:adaptive", version.ref = "material3Adaptive" }
|
|
compose-material-icons-core = { module = "org.jetbrains.compose.material:material-icons-core", version.ref = "materialIconsCore" }
|
|
compose-material-icons-extended = { module = "org.jetbrains.compose.material:material-icons-extended", version.ref = "materialIconsExtended" }
|
|
compose-ui = { module = "org.jetbrains.compose.ui:ui", version.ref = "composeMultiplatform" }
|
|
compose-uiTooling = { module = "org.jetbrains.compose.ui:ui-tooling", version.ref = "composeMultiplatform" }
|
|
compose-uiToolingPreview = { module = "org.jetbrains.compose.ui:ui-tooling-preview", version.ref = "composeMultiplatform" }
|
|
junit = { module = "junit:junit", version.ref = "junit" }
|
|
kermit = { module = "co.touchlab:kermit", version.ref = "kermit" }
|
|
kotlin-test = { module = "org.jetbrains.kotlin:kotlin-test", version.ref = "kotlin" }
|
|
kotlin-testJunit = { module = "org.jetbrains.kotlin:kotlin-test-junit", version.ref = "kotlin" }
|
|
kotlinx-coroutinesSwing = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-swing", version.ref = "kotlinx-coroutines" }
|
|
kotlinx-coroutinesTest = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-test", version.ref = "kotlinx-coroutines" }
|
|
kotlinx-datetime = { module = "org.jetbrains.kotlinx:kotlinx-datetime", version.ref = "kotlinx-datetime" }
|
|
kotlinx-serialization-json = { module = "org.jetbrains.kotlinx:kotlinx-serialization-json", version.ref = "kotlinxSerialization" }
|
|
kotlinx-serialization-cbor = { module = "org.jetbrains.kotlinx:kotlinx-serialization-cbor", version.ref = "kotlinxSerialization" }
|
|
ktor-client-core = { module = "io.ktor:ktor-client-core", version.ref = "ktor" }
|
|
ktor-client-cio = { module = "io.ktor:ktor-client-cio", version.ref = "ktor" }
|
|
ktor-client-websockets = { module = "io.ktor:ktor-client-websockets", version.ref = "ktor" }
|
|
ktor-serialization-kotlinx-json = { module = "io.ktor:ktor-serialization-kotlinx-json", version.ref = "ktor" }
|
|
lightning-kmp-core = { module = "fr.acinq.lightning:lightning-kmp-core", version.ref = "lightningKmpCore" }
|
|
navigation-compose = { module = "org.jetbrains.androidx.navigation:navigation-compose", version.ref = "navigationCompose" }
|
|
okio = { module = "com.squareup.okio:okio", version.ref = "okio" }
|
|
qrose = { module = "io.github.alexzhirkevich:qrose", version.ref = "qrose" }
|
|
sqldelight-android-driver = { module = "app.cash.sqldelight:android-driver", version.ref = "sqldelight" }
|
|
sqldelight-coroutines-extensions = { module = "app.cash.sqldelight:coroutines-extensions", version.ref = "sqldelight" }
|
|
sqldelight-native-driver = { module = "app.cash.sqldelight:native-driver", version.ref = "sqldelight" }
|
|
sqldelight-runtime = { module = "app.cash.sqldelight:runtime", version.ref = "sqldelight" }
|
|
sqldelight-sqlite-driver = { module = "app.cash.sqldelight:sqlite-driver", version.ref = "sqldelight" }
|
|
vitorpamplona-quartz = { module = "com.vitorpamplona.quartz:quartz", version.ref = "quartz" }
|
|
|
|
[plugins]
|
|
androidApplication = { id = "com.android.application", version.ref = "agp" }
|
|
androidx-room3 = { id = "androidx.room3", version.ref = "room3" }
|
|
androidLibrary = { id = "com.android.library", version.ref = "agp" }
|
|
composeHotReload = { id = "org.jetbrains.compose.hot-reload", version.ref = "composeHotReload" }
|
|
composeMultiplatform = { id = "org.jetbrains.compose", version.ref = "composeMultiplatform" }
|
|
composeCompiler = { id = "org.jetbrains.kotlin.plugin.compose", version.ref = "kotlin" }
|
|
kotlinMultiplatform = { id = "org.jetbrains.kotlin.multiplatform", version.ref = "kotlin" }
|
|
kotlinPluginSerialization = { id = "org.jetbrains.kotlin.plugin.serialization", version.ref = "kotlin" }
|
|
ksp = { id = "com.google.devtools.ksp", version.ref = "ksp" }
|
|
sqldelight = { id = "app.cash.sqldelight", version.ref = "sqldelight" } |