diff --git a/amethyst/src/androidTest/java/com/vitorpamplona/amethyst/service/playback/composable/PlaybackErrorOverlayFitTest.kt b/amethyst/src/androidTest/java/com/vitorpamplona/amethyst/service/playback/composable/PlaybackErrorOverlayFitTest.kt index 5ea16ce0b6..6c2e86b123 100644 --- a/amethyst/src/androidTest/java/com/vitorpamplona/amethyst/service/playback/composable/PlaybackErrorOverlayFitTest.kt +++ b/amethyst/src/androidTest/java/com/vitorpamplona/amethyst/service/playback/composable/PlaybackErrorOverlayFitTest.kt @@ -31,7 +31,7 @@ import androidx.compose.ui.platform.LocalDensity import androidx.compose.ui.test.assertHeightIsAtLeast import androidx.compose.ui.test.assertIsDisplayed import androidx.compose.ui.test.getUnclippedBoundsInRoot -import androidx.compose.ui.test.junit4.createComposeRule +import androidx.compose.ui.test.junit4.v2.createComposeRule import androidx.compose.ui.test.onNodeWithText import androidx.compose.ui.unit.Density import androidx.compose.ui.unit.Dp @@ -62,7 +62,8 @@ import org.junit.runner.RunWith */ @RunWith(AndroidJUnit4::class) class PlaybackErrorOverlayFitTest { - @get:Rule val rule = createComposeRule() + @get:Rule + val rule = createComposeRule() private val targetContext = InstrumentationRegistry.getInstrumentation().targetContext diff --git a/amethyst/src/androidTest/java/com/vitorpamplona/amethyst/ui/components/AudioPlayerBoxOverflowTest.kt b/amethyst/src/androidTest/java/com/vitorpamplona/amethyst/ui/components/AudioPlayerBoxOverflowTest.kt index 564715b2f9..c9f738a88b 100644 --- a/amethyst/src/androidTest/java/com/vitorpamplona/amethyst/ui/components/AudioPlayerBoxOverflowTest.kt +++ b/amethyst/src/androidTest/java/com/vitorpamplona/amethyst/ui/components/AudioPlayerBoxOverflowTest.kt @@ -27,7 +27,7 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.layout.ContentScale import androidx.compose.ui.layout.onGloballyPositioned import androidx.compose.ui.layout.positionInRoot -import androidx.compose.ui.test.junit4.createComposeRule +import androidx.compose.ui.test.junit4.v2.createComposeRule import androidx.compose.ui.unit.dp import androidx.test.ext.junit.runners.AndroidJUnit4 import com.vitorpamplona.amethyst.service.playback.composable.audioSquare @@ -50,7 +50,8 @@ import org.junit.runner.RunWith */ @RunWith(AndroidJUnit4::class) class AudioPlayerBoxOverflowTest { - @get:Rule val rule = createComposeRule() + @get:Rule + val rule = createComposeRule() private class Bounds { var top = 0f diff --git a/amethyst/src/androidTest/java/com/vitorpamplona/amethyst/ui/insets/ComposeImeInsetWedgeTest.kt b/amethyst/src/androidTest/java/com/vitorpamplona/amethyst/ui/insets/ComposeImeInsetWedgeTest.kt index 54a1cf4624..b6ef4068fd 100644 --- a/amethyst/src/androidTest/java/com/vitorpamplona/amethyst/ui/insets/ComposeImeInsetWedgeTest.kt +++ b/amethyst/src/androidTest/java/com/vitorpamplona/amethyst/ui/insets/ComposeImeInsetWedgeTest.kt @@ -31,7 +31,7 @@ import androidx.compose.runtime.mutableIntStateOf import androidx.compose.runtime.setValue import androidx.compose.ui.platform.LocalDensity import androidx.compose.ui.platform.LocalView -import androidx.compose.ui.test.junit4.createComposeRule +import androidx.compose.ui.test.junit4.v2.createComposeRule import androidx.core.graphics.Insets import androidx.core.view.OnApplyWindowInsetsListener import androidx.core.view.WindowInsetsAnimationCompat @@ -70,7 +70,8 @@ import org.junit.Test * fallback would silently start reading a dead value too. */ class ComposeImeInsetWedgeTest { - @get:Rule val rule = createComposeRule() + @get:Rule + val rule = createComposeRule() private val keyboardHeight = 957 diff --git a/amethyst/src/androidTest/java/com/vitorpamplona/amethyst/ui/note/DeferredAnimationTest.kt b/amethyst/src/androidTest/java/com/vitorpamplona/amethyst/ui/note/DeferredAnimationTest.kt index 12b6892a0f..a2cbb273e7 100644 --- a/amethyst/src/androidTest/java/com/vitorpamplona/amethyst/ui/note/DeferredAnimationTest.kt +++ b/amethyst/src/androidTest/java/com/vitorpamplona/amethyst/ui/note/DeferredAnimationTest.kt @@ -27,7 +27,7 @@ import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.platform.testTag import androidx.compose.ui.test.assertIsDisplayed -import androidx.compose.ui.test.junit4.createComposeRule +import androidx.compose.ui.test.junit4.v2.createComposeRule import androidx.compose.ui.test.onNodeWithTag import androidx.test.ext.junit.runners.AndroidJUnit4 import com.vitorpamplona.amethyst.ui.actions.DeferredCrossfade diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/service/notifications/EventNotificationConsumer.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/service/notifications/EventNotificationConsumer.kt index a7ac9a4d4c..f28d53b3a8 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/service/notifications/EventNotificationConsumer.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/service/notifications/EventNotificationConsumer.kt @@ -201,6 +201,9 @@ class EventNotificationConsumer( .onFailure { Log.d(TAG) { "Skipping non-decodable npub $npub: ${it.message}" } } .getOrNull() + // GitReplyEvent (kind 1622) is deprecated in favour of NIP-22 comments, but + // events already on relays still arrive and still have to be routed. + @Suppress("DEPRECATION") private suspend fun dispatchForAccount( event: Event, account: Account, diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/service/notifications/NotificationDispatcher.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/service/notifications/NotificationDispatcher.kt index 6c66b6dc6b..41c4619596 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/service/notifications/NotificationDispatcher.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/service/notifications/NotificationDispatcher.kt @@ -112,6 +112,9 @@ class NotificationDispatcher( // recipient account. // `internal` (was `private`) so the notification-kinds contract test // can pin the push-side kind set against the in-app feed's kind set. + // GitReplyEvent (kind 1622) is deprecated in favour of NIP-22 comments, but + // events already on relays still arrive and still have to be routed. + @Suppress("DEPRECATION") internal val NOTIFICATION_KINDS: Set = setOf( // Direct-arrival diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/service/notifications/renderers/CodeNotification.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/service/notifications/renderers/CodeNotification.kt index 1c8b8196b6..086f230ac7 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/service/notifications/renderers/CodeNotification.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/service/notifications/renderers/CodeNotification.kt @@ -81,6 +81,9 @@ object CodeNotification { event: GitPullRequestUpdateEvent, ) = post(context, account, event.id, event.createdAt, event.pubKey, R.string.app_notification_code_channel_message_pr_update, event.content) + // GitReplyEvent (kind 1622) is deprecated in favour of NIP-22 comments, but + // events already on relays still arrive and still have to be rendered. + @Suppress("DEPRECATION") suspend fun notify( context: Context, account: Account, diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/marmotGroup/send/MarmotFileUploader.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/marmotGroup/send/MarmotFileUploader.kt index ad768a3be6..81ba7c19cc 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/marmotGroup/send/MarmotFileUploader.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/marmotGroup/send/MarmotFileUploader.kt @@ -119,7 +119,6 @@ class MarmotFileUploader( // imprecisely. val canonicalMediaType = MarmotMediaType.canonicalize(mimeType) ?: GENERIC_MEDIA_TYPE val cipher = EncryptedMediaV2Cipher(exporterSecret, canonicalMediaType, filename) - val v2Cipher = cipher item.orchestrator.uploadEncrypted( uri = media.uri, @@ -142,21 +141,19 @@ class MarmotFileUploader( // compression and metadata stripping — because that is what the // key was derived from. val reference = - v2Cipher?.let { - EncryptedMediaReferenceV2( - locators = - listOf( - MediaLocatorV2(EncryptedMediaPolicyV2.INITIAL_LOCATOR_KIND, serverResult.url), - ), - ciphertextSha256 = it.ciphertextSha256, - plaintextSha256 = it.plaintextSha256, - nonce = it.nonce, - mediaType = it.mediaType, - filename = filename, - dim = serverResult.fileHeader.dim?.toString(), - thumbhash = serverResult.fileHeader.thumbHash?.thumbhash, - ) - } + EncryptedMediaReferenceV2( + locators = + listOf( + MediaLocatorV2(EncryptedMediaPolicyV2.INITIAL_LOCATOR_KIND, serverResult.url), + ), + ciphertextSha256 = cipher.ciphertextSha256, + plaintextSha256 = cipher.plaintextSha256, + nonce = cipher.nonce, + mediaType = cipher.mediaType, + filename = filename, + dim = serverResult.fileHeader.dim?.toString(), + thumbhash = serverResult.fileHeader.thumbHash?.thumbhash, + ) results.add( Mip04UploadResult( url = serverResult.url, diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/notifications/dal/NotificationFeedFilter.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/notifications/dal/NotificationFeedFilter.kt index 750043a37c..cff84854d6 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/notifications/dal/NotificationFeedFilter.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/notifications/dal/NotificationFeedFilter.kt @@ -139,6 +139,9 @@ class NotificationFeedFilter( AttestationRequestEvent.KIND, ) + // GitReplyEvent (kind 1622) is deprecated in favour of NIP-22 comments, but + // events already on relays still arrive and still have to be routed. + @Suppress("DEPRECATION") val NOTIFICATION_KINDS = // Kinds that RENDER as a row on the Notifications tab. This is a // display gate over whatever is already in LocalCache — it plays no @@ -268,6 +271,9 @@ class NotificationFeedFilter( // Shared with EventNotificationConsumer so push notifications and the // in-app feed apply the same per-kind "is this event for me" rule. + // GitReplyEvent (kind 1622) is deprecated in favour of NIP-22 comments, but + // events already on relays still arrive and still have to be routed. + @Suppress("DEPRECATION") fun tagsAnEventByUser( note: Note, authorHex: HexKey, diff --git a/amethyst/src/test/java/com/vitorpamplona/amethyst/model/LocalCacheSearchParityTest.kt b/amethyst/src/test/java/com/vitorpamplona/amethyst/model/LocalCacheSearchParityTest.kt index c0bfc3a5c5..865f6c1457 100644 --- a/amethyst/src/test/java/com/vitorpamplona/amethyst/model/LocalCacheSearchParityTest.kt +++ b/amethyst/src/test/java/com/vitorpamplona/amethyst/model/LocalCacheSearchParityTest.kt @@ -47,6 +47,9 @@ import java.io.File */ class LocalCacheSearchParityTest { companion object { + /** Hoisted out of [loadCorpus]: building a Json format is expensive enough that the compiler warns on it. */ + private val json = Json { ignoreUnknownKeys = true } + private lateinit var corpus: List @BeforeClass @@ -57,7 +60,7 @@ class LocalCacheSearchParityTest { .firstOrNull { it.isFile } ?: error("tools/search-parity/fixture.json is missing; run tools/search-parity/fetch_fixtures.py") - val root = Json { ignoreUnknownKeys = true }.parseToJsonElement(file.readText()).jsonObject + val root = json.parseToJsonElement(file.readText()).jsonObject corpus = root["cases"]!! .jsonArray diff --git a/amethyst/src/test/java/com/vitorpamplona/amethyst/service/relayClient/authCommand/model/RelayAuthPromptBusTest.kt b/amethyst/src/test/java/com/vitorpamplona/amethyst/service/relayClient/authCommand/model/RelayAuthPromptBusTest.kt index f022d7192f..12586ddf8a 100644 --- a/amethyst/src/test/java/com/vitorpamplona/amethyst/service/relayClient/authCommand/model/RelayAuthPromptBusTest.kt +++ b/amethyst/src/test/java/com/vitorpamplona/amethyst/service/relayClient/authCommand/model/RelayAuthPromptBusTest.kt @@ -153,6 +153,7 @@ class RelayAuthPromptBusTest { * answer already sitting in the deferred, so the relay it belongs to goes unauthenticated for that * long despite the user having answered. Marking it shown is what makes the answer land now. */ + @OptIn(ExperimentalCoroutinesApi::class) @Test fun anAnswerFannedOutToAQueuedPromptLandsWithoutWaitingOutTheQueueWindow() = runTest { diff --git a/amethyst/src/test/java/com/vitorpamplona/amethyst/service/relayClient/authCommand/model/RelayAuthSessionGrantsTest.kt b/amethyst/src/test/java/com/vitorpamplona/amethyst/service/relayClient/authCommand/model/RelayAuthSessionGrantsTest.kt index 369f853b52..bbb2ea53a8 100644 --- a/amethyst/src/test/java/com/vitorpamplona/amethyst/service/relayClient/authCommand/model/RelayAuthSessionGrantsTest.kt +++ b/amethyst/src/test/java/com/vitorpamplona/amethyst/service/relayClient/authCommand/model/RelayAuthSessionGrantsTest.kt @@ -32,6 +32,7 @@ import com.vitorpamplona.amethyst.commons.relayauth.RelayAuthPermissionStore import com.vitorpamplona.amethyst.commons.relayauth.RelayAuthPolicy import com.vitorpamplona.amethyst.commons.relayauth.RelayAuthVerdict import kotlinx.coroutines.CompletableDeferred +import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.launch import kotlinx.coroutines.test.runCurrent import kotlinx.coroutines.test.runTest @@ -185,6 +186,7 @@ class RelayAuthSessionGrantsTest { } } + @OptIn(ExperimentalCoroutinesApi::class) @Test fun promotingAGrantToAlwaysNeverOpensAGapThatRePrompts() = runTest { @@ -205,6 +207,7 @@ class RelayAuthSessionGrantsTest { assertEquals(RelayAuthVerdict.ALLOW, ledger.decide(askable(relay))) } + @OptIn(ExperimentalCoroutinesApi::class) @Test fun neverAllowStopsAuthenticatingBeforeItsWriteLands() = runTest { diff --git a/amethyst/src/test/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/notifications/dal/Nip34NotificationCoverageTest.kt b/amethyst/src/test/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/notifications/dal/Nip34NotificationCoverageTest.kt index 3a86f8de1b..05b567cabe 100644 --- a/amethyst/src/test/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/notifications/dal/Nip34NotificationCoverageTest.kt +++ b/amethyst/src/test/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/notifications/dal/Nip34NotificationCoverageTest.kt @@ -70,6 +70,7 @@ class Nip34NotificationCoverageTest { * A NIP-22 [com.vitorpamplona.quartz.nip22Comments.CommentEvent] handles * modern comments through its own separate wiring. */ + @Suppress("DEPRECATION") private val nip34ParticipantKinds = setOf( GitPatchEvent.KIND, @@ -131,6 +132,7 @@ class Nip34NotificationCoverageTest { * uppercase `E`. Asserting the wrong half passes the kind list while matching * nothing on the wire. */ + @Suppress("DEPRECATION") @Test fun `status kinds are pulled by the lowercase-e engagement subscription`() { val eAnchoredActivityKinds = diff --git a/benchmark/src/androidTest/java/com/vitorpamplona/quartz/benchmark/HexBenchmark.kt b/benchmark/src/androidTest/java/com/vitorpamplona/quartz/benchmark/HexBenchmark.kt index 3b11aa84df..ee091ac91f 100644 --- a/benchmark/src/androidTest/java/com/vitorpamplona/quartz/benchmark/HexBenchmark.kt +++ b/benchmark/src/androidTest/java/com/vitorpamplona/quartz/benchmark/HexBenchmark.kt @@ -136,7 +136,7 @@ class HexBenchmark { /** The pre-existing two-pass way to safely decode an id, for comparison with [hexDecode64OrNull]. */ @Test fun hexIsHex64ThenDecode() { - r.measureRepeated { if (Hex.isHex64(hex)) Hex.decode(hex) else null } + r.measureRepeated { if (Hex.isHex64(hex)) Hex.decode(hex) } } @Test diff --git a/commons/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/viewmodels/LiveStreamTopZappersViewModel.kt b/commons/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/viewmodels/LiveStreamTopZappersViewModel.kt index 5fe963a9cd..de16160bc4 100644 --- a/commons/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/viewmodels/LiveStreamTopZappersViewModel.kt +++ b/commons/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/viewmodels/LiveStreamTopZappersViewModel.kt @@ -33,6 +33,7 @@ import com.vitorpamplona.quartz.nip01Core.core.HexKey import com.vitorpamplona.quartz.nip57Zaps.LnZapEvent import com.vitorpamplona.quartz.nip57Zaps.LnZapRequestEvent import com.vitorpamplona.quartz.nipB1Bolt12Zaps.zap.Bolt12ZapEvent +import com.vitorpamplona.quartz.utils.toLongValue import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.IO import kotlinx.coroutines.Job @@ -162,7 +163,7 @@ class LiveStreamTopZappersViewModel( when (val ev = note.event) { is LnZapEvent -> { val request = ev.zapRequest ?: return null - val sats = ev.amount()?.toLong() ?: return null + val sats = ev.amount()?.toLongValue() ?: return null ZapContribution(note.idHex, request.pubKey, request.isAnonTagged(), sats) } is Bolt12ZapEvent -> { @@ -178,7 +179,7 @@ class LiveStreamTopZappersViewModel( ): ZapContribution? { val receiptEv = receiptNote?.event as? LnZapEvent ?: return null val request = zapRequestNote.event as? LnZapRequestEvent ?: return null - val sats = receiptEv.amount()?.toLong() ?: return null + val sats = receiptEv.amount()?.toLongValue() ?: return null return ZapContribution(receiptNote.idHex, request.pubKey, request.isAnonTagged(), sats) } } diff --git a/commons/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/viewmodels/RoomZapsState.kt b/commons/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/viewmodels/RoomZapsState.kt index 8568f793f4..ce0267fb9e 100644 --- a/commons/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/viewmodels/RoomZapsState.kt +++ b/commons/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/viewmodels/RoomZapsState.kt @@ -23,6 +23,7 @@ package com.vitorpamplona.amethyst.commons.viewmodels import androidx.compose.runtime.Immutable import com.vitorpamplona.quartz.nip57Zaps.LnZapEvent import com.vitorpamplona.quartz.nipB1Bolt12Zaps.zap.Bolt12ZapEvent +import com.vitorpamplona.quartz.utils.toLongValue /** * One in-flight kind-9735 zap to render as a floating overlay on the @@ -59,7 +60,7 @@ data class RoomZap( eventId = event.id, sourcePubkey = event.zapRequest?.pubKey ?: event.pubKey, targetPubkey = event.zappedAuthor().firstOrNull(), - amountSats = event.amount?.toLong(), + amountSats = event.amount?.toLongValue(), createdAtSec = event.createdAt, ) diff --git a/desktopApp/src/jvmMain/kotlin/com/vitorpamplona/amethyst/desktop/ui/live/LivesSection.kt b/desktopApp/src/jvmMain/kotlin/com/vitorpamplona/amethyst/desktop/ui/live/LivesSection.kt index 2490177cfe..53013a900f 100644 --- a/desktopApp/src/jvmMain/kotlin/com/vitorpamplona/amethyst/desktop/ui/live/LivesSection.kt +++ b/desktopApp/src/jvmMain/kotlin/com/vitorpamplona/amethyst/desktop/ui/live/LivesSection.kt @@ -27,7 +27,6 @@ import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.ExperimentalLayoutApi import androidx.compose.foundation.layout.FlowRow -import androidx.compose.foundation.layout.FlowRowOverflow import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.aspectRatio @@ -146,7 +145,6 @@ fun LivesSection( horizontalArrangement = Arrangement.spacedBy(12.dp), verticalArrangement = Arrangement.spacedBy(12.dp), maxLines = 2, - overflow = FlowRowOverflow.Clip, modifier = Modifier.fillMaxWidth(), ) { ranked.forEach { channel -> diff --git a/quartz/src/appleMain/kotlin/com/vitorpamplona/quartz/utils/BigDecimalOps.apple.kt b/quartz/src/appleMain/kotlin/com/vitorpamplona/quartz/utils/BigDecimalOps.apple.kt new file mode 100644 index 0000000000..4bf7fc3821 --- /dev/null +++ b/quartz/src/appleMain/kotlin/com/vitorpamplona/quartz/utils/BigDecimalOps.apple.kt @@ -0,0 +1,23 @@ +/* + * Copyright (c) 2025 Vitor Pamplona + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to use, + * copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the + * Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN + * AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +package com.vitorpamplona.quartz.utils + +actual fun BigDecimal.toLongValue(): Long = toLong() diff --git a/quartz/src/commonMain/kotlin/com/vitorpamplona/quartz/marmot/MarmotInboundProcessor.kt b/quartz/src/commonMain/kotlin/com/vitorpamplona/quartz/marmot/MarmotInboundProcessor.kt index 698be085ec..b1cb1f8f79 100644 --- a/quartz/src/commonMain/kotlin/com/vitorpamplona/quartz/marmot/MarmotInboundProcessor.kt +++ b/quartz/src/commonMain/kotlin/com/vitorpamplona/quartz/marmot/MarmotInboundProcessor.kt @@ -760,7 +760,7 @@ class MarmotInboundProcessor( val author = payloadAuthor(candidate.content.decodeToString()) val sender = candidate.senderAccount val valid = author != null && sender != null && author == sender - if (valid && sender != null) { + if (valid) { convergence.recordWitness(groupId, candidate.stateId, sender) } return GroupEventResult.AppMessageOnCandidateBranch( diff --git a/quartz/src/commonMain/kotlin/com/vitorpamplona/quartz/marmot/protocolCore/MarmotConvergenceEngine.kt b/quartz/src/commonMain/kotlin/com/vitorpamplona/quartz/marmot/protocolCore/MarmotConvergenceEngine.kt index f1ed9c8d75..91b0760946 100644 --- a/quartz/src/commonMain/kotlin/com/vitorpamplona/quartz/marmot/protocolCore/MarmotConvergenceEngine.kt +++ b/quartz/src/commonMain/kotlin/com/vitorpamplona/quartz/marmot/protocolCore/MarmotConvergenceEngine.kt @@ -561,7 +561,7 @@ class MarmotConvergenceEngine( return mutex.withLock { val ctx = contexts[groupId] ?: return@withLock null - if (rewound && selectedTipId != null) { + if (rewound) { adoptBranch(ctx, graph, selectedTipId, inputs.baseId) } // Keep the states of branches that LOST but stay eligible: losing @@ -576,7 +576,7 @@ class MarmotConvergenceEngine( .forEach { tipId -> var cursor: String? = tipId while (cursor != null && cursor !in canonical) { - graph.statesById[cursor]?.let { ctx.candidateStates[cursor!!] = it } + graph.statesById[cursor]?.let { ctx.candidateStates[cursor] = it } cursor = graph.parentOf[cursor] } } diff --git a/quartz/src/commonMain/kotlin/com/vitorpamplona/quartz/utils/BigDecimalOps.kt b/quartz/src/commonMain/kotlin/com/vitorpamplona/quartz/utils/BigDecimalOps.kt index 571f812db1..be65f1dac7 100644 --- a/quartz/src/commonMain/kotlin/com/vitorpamplona/quartz/utils/BigDecimalOps.kt +++ b/quartz/src/commonMain/kotlin/com/vitorpamplona/quartz/utils/BigDecimalOps.kt @@ -23,3 +23,15 @@ package com.vitorpamplona.quartz.utils operator fun BigDecimal.plus(other: BigDecimal): BigDecimal = add(other) operator fun BigDecimal.minus(other: BigDecimal): BigDecimal = subtract(other) + +/** + * Truncate to a Long, the way Number.toLong() does on every platform. + * + * It has to be an expect *function* rather than a member of `expect class + * BigDecimal`: every actual is already a Number and so already has toLong(), + * but java.math.BigDecimal leaves toByte()/toShort() abstract, which makes + * `expect class BigDecimal : Number` impossible to actualize with the JVM + * typealias. Without this, `amount.toLong()` in shared code resolves only in + * the platform compilations and breaks `compileCommonMainKotlinMetadata`. + */ +expect fun BigDecimal.toLongValue(): Long diff --git a/quartz/src/jvmAndroid/kotlin/com/vitorpamplona/quartz/utils/BigDecimalOps.jvmAndroid.kt b/quartz/src/jvmAndroid/kotlin/com/vitorpamplona/quartz/utils/BigDecimalOps.jvmAndroid.kt new file mode 100644 index 0000000000..4bf7fc3821 --- /dev/null +++ b/quartz/src/jvmAndroid/kotlin/com/vitorpamplona/quartz/utils/BigDecimalOps.jvmAndroid.kt @@ -0,0 +1,23 @@ +/* + * Copyright (c) 2025 Vitor Pamplona + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to use, + * copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the + * Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN + * AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +package com.vitorpamplona.quartz.utils + +actual fun BigDecimal.toLongValue(): Long = toLong() diff --git a/quartz/src/linuxMain/kotlin/com/vitorpamplona/quartz/utils/BigDecimalOps.linux.kt b/quartz/src/linuxMain/kotlin/com/vitorpamplona/quartz/utils/BigDecimalOps.linux.kt new file mode 100644 index 0000000000..4bf7fc3821 --- /dev/null +++ b/quartz/src/linuxMain/kotlin/com/vitorpamplona/quartz/utils/BigDecimalOps.linux.kt @@ -0,0 +1,23 @@ +/* + * Copyright (c) 2025 Vitor Pamplona + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to use, + * copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the + * Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN + * AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +package com.vitorpamplona.quartz.utils + +actual fun BigDecimal.toLongValue(): Long = toLong()