mirror of
https://github.com/vitorpamplona/amethyst.git
synced 2026-10-30 07:37:38 +00:00
Audit of the warning-cleanup commit found two regressions in it: - rememberModalSheetState went through rememberBottomSheetState, which is not equivalent to the deprecated rememberModalBottomSheetState: the old function pins isBottomSheetPartiallyExpandedDeterministicEnabled to false, the new one takes the global flag (true by default), changing where sheets that keep PartiallyExpanded place and settle that anchor. The flag is only reachable through Material3 internals, so the helper now calls the deprecated function once, with a documented suppression; every sheet still goes through the helper. - The Cashu cleanups dropped `?: event.id` fallbacks the compiler proved dead, but they were dead because dTag() returns "" for a missing tag, not because the tag is always there. d-tag-less kind:38000 recommendations and mint announcements all shared the key "". Use ifEmpty so they stay apart, skip "" in the d-tag -> URL map, and fall back to the event id for the recommendation card's title and the delete dialog. Also declares navigationevent-compose (Apache-2.0, already in the graph via navigation3-ui) directly in commonsUI skikoMain, since PlatformBackHandler imports it, and corrects the gradle.properties note on what kotlin.native.ignoreDisabledTargets silences. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01SazxgvMHyaPGET4ishFuTM
286 lines
12 KiB
Kotlin
286 lines
12 KiB
Kotlin
import org.jetbrains.kotlin.gradle.dsl.JvmTarget
|
|
import org.jetbrains.kotlin.gradle.plugin.mpp.DisableCacheInKotlinVersion
|
|
import org.jetbrains.kotlin.gradle.plugin.mpp.KotlinNativeCacheApi
|
|
import org.jetbrains.kotlin.gradle.plugin.mpp.KotlinNativeTarget
|
|
import org.jetbrains.kotlin.gradle.plugin.mpp.TestExecutable
|
|
|
|
// `:commonsUI` is the Compose half of the shared layer: every composable,
|
|
// icon, theme, Coil fetcher and Compose-resource (strings/fonts/files) that the
|
|
// GUI front ends (Android, Desktop, iOS) render. It sits on top of `:commons`
|
|
// (headless models, state, ViewModels, relay client) and is never a dependency
|
|
// of `:cli`, which keeps Compose UI + Skiko off the CLI classpath.
|
|
// Source files keep their `com.vitorpamplona.amethyst.commons.*` packages so
|
|
// the module boundary is purely a build-graph constraint — consumers did not
|
|
// have to change a single import when the split happened.
|
|
|
|
// Disables the Kotlin/Native compiler cache for an iOS test binary so the
|
|
// Compose ui-uikit klib recompiles fresh instead of linking the broken prebuilt
|
|
// cache (see the call site in the `kotlin {}` block). The version guard makes
|
|
// Kotlin re-surface this workaround once we move past 2.4.20, so it can be
|
|
// dropped when a newer Compose/Kotlin pairing fixes the cache. Wrapped in a
|
|
// helper because @OptIn only applies to declarations, not bare statements.
|
|
@OptIn(KotlinNativeCacheApi::class)
|
|
fun TestExecutable.disableUiKitPrebuiltCache() =
|
|
disableNativeCache(
|
|
DisableCacheInKotlinVersion.`2_4_20`,
|
|
"Compose ui-uikit prebuilt cache references UIViewLayoutRegion (iOS 17+); " +
|
|
"linking the iOS test binary fails under Xcode 16.4.",
|
|
)
|
|
|
|
plugins {
|
|
alias(libs.plugins.kotlinMultiplatform)
|
|
alias(libs.plugins.androidKotlinMultiplatformLibrary)
|
|
alias(libs.plugins.jetbrainsComposeCompiler)
|
|
alias(libs.plugins.composeMultiplatform)
|
|
}
|
|
|
|
kotlin {
|
|
compilerOptions {
|
|
freeCompilerArgs.add("-Xexpect-actual-classes")
|
|
}
|
|
|
|
jvm {
|
|
compilerOptions {
|
|
jvmTarget.set(JvmTarget.JVM_21)
|
|
}
|
|
}
|
|
|
|
android {
|
|
namespace = "com.vitorpamplona.amethyst.commons.ui"
|
|
compileSdk =
|
|
libs.versions.android.compileSdk
|
|
.get()
|
|
.toInt()
|
|
minSdk =
|
|
libs.versions.android.minSdk
|
|
.get()
|
|
.toInt()
|
|
|
|
compilerOptions {
|
|
jvmTarget.set(JvmTarget.JVM_21)
|
|
}
|
|
|
|
androidResources.enable = true
|
|
|
|
withHostTest {
|
|
isReturnDefaultValues = true
|
|
}
|
|
|
|
withDeviceTest {
|
|
instrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
|
|
}
|
|
}
|
|
|
|
// iOS targets — same compile-only spike as :commons.
|
|
iosArm64()
|
|
iosSimulatorArm64()
|
|
|
|
// Compose Multiplatform 1.11.x ships an `org.jetbrains.compose.ui:ui-uikit`
|
|
// prebuilt Kotlin/Native cache whose CMPLayoutRegion object hard-references
|
|
// the UIKit class `UIViewLayoutRegion` (introduced in iOS 17). Linking the
|
|
// iOS *test* executable against that cache under Xcode 16.4 fails with
|
|
// ld: Undefined symbols: _OBJC_CLASS_$_UIViewLayoutRegion
|
|
// because the cached object was built for a newer simulator SDK (18.5) than
|
|
// the test binary is being linked for (14.0). Disabling the native cache for
|
|
// the iOS test binaries makes ui-uikit recompile against the active SDK,
|
|
// where the symbol resolves. See disableUiKitPrebuiltCache() above and
|
|
// https://kotl.in/disable-native-cache
|
|
targets.withType<KotlinNativeTarget>().configureEach {
|
|
binaries.withType<TestExecutable>().configureEach {
|
|
disableUiKitPrebuiltCache()
|
|
}
|
|
}
|
|
|
|
sourceSets {
|
|
commonMain {
|
|
dependencies {
|
|
// The headless half. `api` because every composable here takes
|
|
// or returns commons types (Note, User, ViewModels, states).
|
|
api(project(":commons"))
|
|
api(project(":quartz"))
|
|
|
|
// Compose Multiplatform
|
|
implementation(libs.jetbrains.compose.ui)
|
|
implementation(libs.jetbrains.compose.foundation)
|
|
implementation(libs.jetbrains.compose.runtime)
|
|
implementation(libs.jetbrains.compose.material3)
|
|
implementation(libs.jetbrains.compose.ui.tooling.preview)
|
|
|
|
// Lifecycle (KMP since 2.8.0). lifecycle-runtime-compose ships
|
|
// iOS variants; lifecycle-viewmodel-compose (the viewModel()
|
|
// Composable helper) is Android-only and lives in jvmAndroid.
|
|
implementation(libs.androidx.lifecycle.viewmodel)
|
|
implementation(libs.androidx.lifecycle.runtime.compose)
|
|
|
|
// Image loading (Coil 3 - KMP). The okhttp network fetcher is
|
|
// JVM-only and lives in jvmAndroid; iOS will pull coil-ktor
|
|
// when that target wires its actual.
|
|
implementation(libs.coil.compose)
|
|
// NetworkHeaders on avatar requests (the local Blossom cache marker). Multiplatform,
|
|
// Apache-2.0; already on the JVM classpath through coil-network-okhttp.
|
|
implementation(libs.coil.network.core)
|
|
|
|
// LruCache (KMP-ready)
|
|
implementation(libs.androidx.collection)
|
|
|
|
// Pinch-to-zoom (net.engawapg zoomable, Compose Multiplatform: Android, desktop,
|
|
// iOS; Apache-2.0). Already in the app; the Blossom blob viewer uses it here.
|
|
implementation(libs.zoomable)
|
|
|
|
// Charts (Vico 3, Compose Multiplatform, Apache-2.0): the notification
|
|
// summary builds its chart model off the main thread. `api` because the
|
|
// model is part of that state holder's public surface.
|
|
api(libs.vico.charts.compose)
|
|
|
|
// Immutable collections
|
|
api(libs.kotlinx.collections.immutable)
|
|
|
|
// JSON trees for the LNURL-pay replies the payment stack reads.
|
|
implementation(libs.kotlinx.serialization.json)
|
|
|
|
// Compose Multiplatform Resources (strings, fonts, napplet shell files)
|
|
implementation(libs.jetbrains.compose.components.resources)
|
|
|
|
// KMP syntax highlighter (Apache-2.0) for the git code browser.
|
|
implementation(libs.highlights)
|
|
|
|
// Navigation 3 (JetBrains' multiplatform build, Apache-2.0: Android, desktop, iOS).
|
|
// The app owns its back stack (navs/NavBackStacks); NavDisplay renders it, and the
|
|
// ViewModel decorator scopes each screen's ViewModels to its entry.
|
|
implementation(libs.jetbrains.navigation3.ui)
|
|
implementation(libs.jetbrains.lifecycle.viewmodel.navigation3)
|
|
}
|
|
}
|
|
|
|
commonTest {
|
|
dependencies {
|
|
implementation(libs.kotlin.test)
|
|
implementation(libs.kotlinx.coroutines.test)
|
|
}
|
|
}
|
|
|
|
jvmTest {
|
|
dependencies {
|
|
// Compose Desktop on the test classpath so composables can be
|
|
// rendered headlessly to a Skia surface (ImageComposeScene) and
|
|
// asserted on. No new third-party dependency: this is the same
|
|
// artifact jvmMain already uses, and it rasterises in software,
|
|
// so it needs no display.
|
|
implementation(compose.desktop.currentOs)
|
|
// RouteForPointerTest stubs an Account (already the app's and androidHostTest's mock lib).
|
|
implementation(libs.mockk)
|
|
}
|
|
}
|
|
|
|
// Shared JVM code for both Android and Desktop
|
|
val jvmAndroid =
|
|
create("jvmAndroid") {
|
|
dependsOn(commonMain.get())
|
|
dependencies {
|
|
// Coil's OkHttp network fetcher (JVM-only). iOS will use
|
|
// coil-ktor when the iOS Compose UI ships.
|
|
implementation(libs.coil.okhttp)
|
|
|
|
// OkHttp for the Blossom read-auth Coil fetcher.
|
|
implementation(libs.okhttp)
|
|
// executeAsync for PdfFetcher; already on every module that ships OkHttp.
|
|
implementation(libs.okhttpCoroutines)
|
|
|
|
// Markdown rendering (richtext-commonmark). The single
|
|
// consumer (RenderMarkdown.kt) lives in jvmAndroid.
|
|
// iOS support pending Phase 3 markdown decision.
|
|
implementation(libs.markdown.commonmark)
|
|
implementation(libs.markdown.ui)
|
|
implementation(libs.markdown.ui.material3)
|
|
|
|
// viewModel() Compose helper. AndroidX publishes this
|
|
// artifact for android/jvmStubs/linuxx64Stubs but not iOS,
|
|
// so it stays in jvmAndroid until we either swap to the
|
|
// org.jetbrains.androidx.lifecycle variant or accept a
|
|
// platform-specific ViewModel access pattern on iOS.
|
|
implementation(libs.androidx.lifecycle.viewmodel.compose)
|
|
|
|
// ZXing core encodes the QR codes the shared QrCodeDrawer shows. Pure Java,
|
|
// Apache-2.0, and already linked by both apps.
|
|
implementation(libs.zxing)
|
|
}
|
|
}
|
|
|
|
jvmMain {
|
|
dependsOn(jvmAndroid)
|
|
dependencies {
|
|
// Desktop-specific Compose
|
|
implementation(compose.desktop.currentOs)
|
|
implementation(libs.jetbrains.compose.ui.tooling)
|
|
}
|
|
}
|
|
|
|
androidMain {
|
|
dependsOn(jvmAndroid)
|
|
dependencies {
|
|
// Android-specific Compose tooling
|
|
implementation(libs.androidx.ui.tooling.preview)
|
|
// WindowInsetsControllerCompat, for DisappearingScaffold's immersive status bar.
|
|
implementation(libs.androidx.core.ktx)
|
|
// BackHandler, behind the shared PlatformBackHandler. Apache-2.0, already in the app.
|
|
implementation(libs.androidx.activity.compose)
|
|
// Camera permission prompt for TakePicture. Apache-2.0, already in the app.
|
|
implementation(libs.accompanist.permissions)
|
|
implementation(libs.accompanist.adaptive)
|
|
// BiometricPrompt behind rememberDeviceAuthenticator. Apache-2.0, already in the app.
|
|
implementation(libs.androidx.biometric.ktx)
|
|
}
|
|
}
|
|
|
|
// iOS intermediate so iosArm64Main and iosSimulatorArm64Main share code.
|
|
val iosMain =
|
|
create("iosMain") {
|
|
dependsOn(commonMain.get())
|
|
}
|
|
getByName("iosArm64Main").dependsOn(iosMain)
|
|
getByName("iosSimulatorArm64Main").dependsOn(iosMain)
|
|
|
|
// Skiko-backed targets (desktop JVM + iOS) share pixel-format helpers
|
|
// (org.jetbrains.skia.* resolves on both through Compose). Android is
|
|
// deliberately NOT in this set — it renders through android.graphics.
|
|
val skikoMain =
|
|
create("skikoMain") {
|
|
dependsOn(commonMain.get())
|
|
dependencies {
|
|
// NavigationBackHandler for desktop (Esc) and iOS (swipe), behind
|
|
// PlatformBackHandler. Apache-2.0; Android uses AndroidX activity's BackHandler.
|
|
implementation(libs.jetbrains.navigationevent.compose)
|
|
}
|
|
}
|
|
getByName("jvmMain").dependsOn(skikoMain)
|
|
iosMain.dependsOn(skikoMain)
|
|
|
|
getByName("androidHostTest") {
|
|
dependencies {
|
|
implementation(libs.junit)
|
|
// SafeImeInsetsTest mocks the android.view.View its per-window cache is keyed on.
|
|
implementation(libs.mockk)
|
|
implementation(libs.kotlinx.coroutines.test)
|
|
}
|
|
}
|
|
|
|
getByName("androidDeviceTest") {
|
|
dependencies {
|
|
implementation(libs.androidx.junit)
|
|
implementation(libs.androidx.espresso.core)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
compose.resources {
|
|
publicResClass = true
|
|
// Kept on the pre-split package so `Res` imports in every consumer
|
|
// (amethyst, desktopApp, nappletHost) keep resolving unchanged.
|
|
packageOfResClass = "com.vitorpamplona.amethyst.commons.resources"
|
|
generateResClass = always
|
|
}
|
|
|
|
// iOS purity gate — shared task, see gradle/kmp-purity.gradle.kts.
|
|
apply(from = rootProject.file("gradle/kmp-purity.gradle.kts"))
|