Files
mantra-kmp/composeApp/build.gradle.kts
Kgothatso Ngako 39eac61838 Merge branch 'mantra' into claude/long-running-chat-sync-8983dc
mantra had moved on ~30 commits, several of them in exactly this area — and it
turns out both branches independently found the same bug and drew the same
conclusion about the same filter.

**The overlap.** f38a5f1 fixed the three kind:1059 filters that named the wrong
pubkey, including the two `authors=[userPublicKey]` requests in NostrDao that
could never match a wrap signed by a throwaway key. This branch deleted those
same two blocks, inverting the same `if` to the `== null` case, for the same
reason. The code merged to the same shape; only the comments conflicted, and
they are combined.

**Nip17Filters wins, and the live subscription now defers to it.** ad3304a
extracted the inbox filter to one definition precisely because it had been wrong
in three call sites, with the no-`since` reasoning this branch arrived at
separately. Keeping a fourth copy inside LiveSubscriptionManager would recreate
the problem that commit exists to solve, so:

  - queueCatchUpSynchronization now calls Nip17Filters.inbox() instead of
    building an identical SynchronizationFilter with its own limit constant,
  - Nip17Filters gains liveInbox(), the same shape as a quartz Filter for a REQ
    rather than a SynchronizationFilter for the queue, and giftWrapFilter()
    defers to it.

Two types for one filter is not duplication worth removing — the queue stores
one and hashes it for computeId, a live subscription puts the other on the wire
— but they belong side by side, because drift here means one of them quietly
stops matching mail.

**ChatMessageListViewModel keeps this branch's resolution.** mantra had it
refresh our own inbox on open (Nip17Filters.inbox on our DM relays, purpose
"chat"); this branch removed that call entirely. Both were right when written,
and the merge is where the second becomes true: LiveSubscriptionManager holds
exactly that filter open on exactly those relays for the whole account and
reconciles it on every foreground, so opening a chat has nothing left to ask
for. The redundancy is now recorded in the comment where the branch used to be,
so it reads as superseded rather than dropped. Discovery — the kind-10050 lookup
for a participant we cannot yet address — is untouched, and the purpose is no
longer a conditional now that only one case reaches it.

The commonTest coroutines-test dependency arrived on both sides; the comment
gives both reasons.

Verified: 154 tests pass, both branches' suites included — Nip17FiltersTest and
the marmot direct-message suites alongside this branch's 46.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-05 23:58:40 +02:00

234 lines
8.5 KiB
Kotlin

import org.jetbrains.compose.desktop.application.dsl.TargetFormat
import org.jetbrains.kotlin.gradle.dsl.JvmTarget
plugins {
alias(libs.plugins.androidApplication)
// alias(libs.plugins.androidx.room)
alias(libs.plugins.androidx.room3)
alias(libs.plugins.kotlinMultiplatform)
alias(libs.plugins.composeMultiplatform)
alias(libs.plugins.composeCompiler)
alias(libs.plugins.composeHotReload)
alias(libs.plugins.kotlinPluginSerialization)
alias(libs.plugins.ksp)
alias(libs.plugins.sqldelight)
}
kotlin {
compilerOptions {
freeCompilerArgs.add("-Xexpect-actual-classes")
}
androidTarget {
compilerOptions {
jvmTarget.set(JvmTarget.JVM_21)
}
}
// Declared only on a mac, mirroring the gate lightning-kmp-app's :library applies to its own ios
// targets. Down the composite chain secp256k1-kmp declares a libsecp256k1 cinterop, which makes
// gradle switch off klib cross compilation for apple targets, so on a linux/windows host nothing
// in the build tree offers an ios variant of fr.acinq.phoenix:lightning-kmp-app. Declaring these
// targets anyway leaves every ios compilation unable to resolve it and fails the build outright.
// Building an ios binary needs a mac regardless.
if (org.gradle.internal.os.OperatingSystem.current().isMacOsX) {
listOf(
iosArm64(),
iosSimulatorArm64()
).forEach { iosTarget ->
iosTarget.binaries.framework {
baseName = "ComposeApp"
isStatic = true
}
}
}
// jvm()
sourceSets {
androidMain.dependencies {
// The android secp256k1 natives. Nothing pulls these in transitively: lightning-kmp-core
// publishes no android variant, so the android target resolves it to the jvm one, which
// asks for secp256k1-kmp-jni-jvm and gets desktop .so/.dylib/.dll files that cannot load
// on a device. The app has to name the android artifact itself.
//
// Resolved from source via the composite build, not from Maven Central -- bitcoin-kmp's
// settings.gradle.kts substitutes this coordinate for secp256k1-kmp's :jni:android. That
// matters: the stock artifact has no ChillDKG module, so ChillDKG would compile and then
// fail with UnsatisfiedLinkError. The version is never resolved; substitution matches on
// group:name.
implementation("fr.acinq.secp256k1:secp256k1-kmp-jni-android:0.24.0")
implementation(libs.androidx.activity.compose)
// implementation(libs.androidx.room.sqlite.wrapper)
implementation(libs.compose.uiToolingPreview)
implementation(libs.sqldelight.android.driver)
}
commonMain.dependencies {
implementation(libs.androidx.datastore)
implementation(libs.androidx.datastore.preferences)
implementation(libs.androidx.lifecycle.viewmodelCompose)
implementation(libs.androidx.lifecycle.runtimeCompose)
// implementation(libs.androidx.room.runtime)
implementation(libs.androidx.room3.runtime)
implementation(libs.androidx.sqlite.bundled)
implementation(libs.coil.compose)
implementation(libs.coil.network.ktor3)
implementation(libs.compose.runtime)
implementation(libs.compose.foundation)
implementation(libs.compose.material3)
implementation (libs.compose.material.icons.core)
implementation (libs.compose.material.icons.extended)
implementation(libs.compose.ui)
implementation(libs.compose.components.resources)
implementation(libs.compose.uiToolingPreview)
implementation(libs.kermit)
implementation(libs.kotlinx.datetime)
implementation(libs.kotlinx.serialization.json)
implementation(libs.kotlinx.serialization.cbor)
implementation(libs.ktor.client.core)
implementation(libs.ktor.client.cio)
implementation(libs.ktor.client.websockets)
implementation(libs.ktor.serialization.kotlinx.json)
// implementation(libs.lightning.kmp.core)
// resolved via the lightning-kmp-app composite build (see settings.gradle.kts)
implementation("fr.acinq.phoenix:lightning-kmp-app:1.0.0")
implementation(libs.navigation.compose)
implementation(libs.okio)
implementation(libs.qrose)
implementation(libs.sqldelight.runtime)
implementation(libs.sqldelight.coroutines.extensions)
implementation(libs.vitorpamplona.quartz)
}
commonTest.dependencies {
implementation(libs.kotlin.test)
// runTest: the DAO, model and relay layers are all suspending, and there is no
// runBlocking in a common source set — so anything worth asserting about them
// needs a coroutine, and a scheduler, to assert it in.
implementation(libs.kotlinx.coroutinesTest)
}
jvmMain.dependencies {
implementation(compose.desktop.currentOs)
implementation(libs.kotlinx.coroutinesSwing)
}
// Only exists when the ios targets above were declared; the default hierarchy template
// creates this source set from them.
if (org.gradle.internal.os.OperatingSystem.current().isMacOsX) {
iosMain.dependencies {
implementation(libs.sqldelight.native.driver)
}
}
}
}
android {
namespace = "press.mantra.android"
compileSdk = libs.versions.android.compileSdk.get().toInt()
buildFeatures {
buildConfig = true
}
defaultConfig {
applicationId = "press.mantra.android"
minSdk = libs.versions.android.minSdk.get().toInt()
targetSdk = libs.versions.android.targetSdk.get().toInt()
versionCode = 1
versionName = "0.1.0"
}
packaging {
resources {
excludes += "/META-INF/{AL2.0,LGPL2.1}"
excludes += "META-INF/versions/9/OSGI-INF/MANIFEST.MF"
}
}
buildTypes {
getByName("release") {
isMinifyEnabled = false
}
getByName("debug") {
applicationIdSuffix = ".debug"
versionNameSuffix = "-DEBUG"
}
}
compileOptions {
sourceCompatibility = JavaVersion.VERSION_21
targetCompatibility = JavaVersion.VERSION_21
}
testOptions {
unitTests {
// Quartz logs through android.util.Log on the error paths -- parsing a
// malformed event, for one. Unmocked, those methods throw, so a test
// covering such a path fails on the log line rather than on what it
// came to check. Default values let the code under test carry on.
isReturnDefaultValues = true
}
}
}
dependencies {
debugImplementation(libs.compose.uiTooling)
// ksp(libs.androidx.room3.compiler)
add("kspAndroid", libs.androidx.room3.compiler)
// These configurations only exist when the ios targets are declared, which the kotlin block
// above does only on a mac.
if (org.gradle.internal.os.OperatingSystem.current().isMacOsX) {
add("kspIosSimulatorArm64", libs.androidx.room3.compiler)
// add("kspIosX64", libs.androidx.room.compiler)
add("kspIosArm64", libs.androidx.room3.compiler)
}
// add("kspJvm", libs.androidx.room3.compiler)
// Add any other platform target you use in your project, for example kspDesktop
}
room3 {
schemaDirectory("$projectDir/schemas")
}
sqldelight {
databases {
create("ChannelsDatabase") {
packageName.set("fr.acinq.phoenix.db.sqldelight")
srcDirs.from("src/commonMain/sqldelight/channelsdb")
}
create("PaymentsDatabase") {
packageName.set("fr.acinq.phoenix.db.sqldelight")
srcDirs.from("src/commonMain/sqldelight/paymentsdb")
}
create("AppDatabase") {
packageName.set("fr.acinq.phoenix.db.sqldelight")
srcDirs.from("src/commonMain/sqldelight/appdb")
}
}
}
compose.desktop {
application {
mainClass = "press.mantra.desktop.MainKt"
nativeDistributions {
targetFormats(TargetFormat.Dmg, TargetFormat.Msi, TargetFormat.Deb)
packageName = "press.mantra.desktop"
packageVersion = "1.0.0"
}
}
}