From 9fce45d91fc0b538665ce5ee7b5d023b0eb30e68 Mon Sep 17 00:00:00 2001 From: Claude Date: Mon, 28 Sep 2026 01:58:23 +0000 Subject: [PATCH] refactor(account): replace JVM-only APIs in Account's group with KMP ones So the group can land in commons/commonMain rather than jvmAndroid: - java.math.BigDecimal -> quartz.utils.BigDecimal (Account, AccountZapActions); on JVM/Android it is a typealias for the same class. - java.util.UUID.randomUUID() -> kotlin.uuid.Uuid.random() (AccountSettings, FollowSetsState, StarterPacksState); same random v4 string form. - java.util.Base64 -> kotlin.io.encoding.Base64 (OtsState); both are the RFC 4648 alphabet with padding. - ConcurrentHashMap -> quartz ConcurrentMap and Collections.synchronizedSet -> commons ConcurrentSet (GeohashChatIdentityState, AccountConcordActions); only get/set/remove and an atomic add are used. - Locale.getDefault().language -> commons getDefaultLanguageCode(), a new expect beside getLanguagesSpokenByUser (Android/JVM: the same call; iOS: NSLocale.currentLocale.languageCode). CashuWalletState's maps are iterated (keys/values/entries), which ConcurrentMap does not offer; they change with the rest of the Cashu promotion. No behaviour change. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01S7FuNBSKiyVecARSoE4B9P --- .../java/com/vitorpamplona/amethyst/model/Account.kt | 2 +- .../amethyst/model/AccountConcordActions.kt | 7 ++++--- .../com/vitorpamplona/amethyst/model/AccountSettings.kt | 8 +++++--- .../amethyst/model/AccountSyncedSettingsInternal.kt | 4 ++-- .../vitorpamplona/amethyst/model/AccountZapActions.kt | 2 +- .../amethyst/model/GeohashChatIdentityState.kt | 4 ++-- .../amethyst/model/nip03Timestamp/OtsState.kt | 6 +++--- .../model/nip51Lists/followSets/FollowSetsState.kt | 9 ++++++--- .../model/nip51Lists/followSets/StarterPacksState.kt | 9 ++++++--- .../amethyst/commons/util/SpokenLanguages.android.kt | 3 +++ .../amethyst/commons/util/SpokenLanguages.kt | 3 +++ .../amethyst/commons/util/SpokenLanguages.ios.kt | 4 ++++ .../amethyst/commons/util/SpokenLanguages.jvm.kt | 2 ++ 13 files changed, 42 insertions(+), 21 deletions(-) diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/model/Account.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/model/Account.kt index 594619ea7b..1c35b0e0e6 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/model/Account.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/model/Account.kt @@ -341,6 +341,7 @@ import com.vitorpamplona.quartz.nipA0VoiceMessages.VoiceReplyEvent import com.vitorpamplona.quartz.nipA3PaymentTargets.PaymentTarget import com.vitorpamplona.quartz.nipB0WebBookmarks.WebBookmarkEvent import com.vitorpamplona.quartz.nipC7Chats.ChatEvent +import com.vitorpamplona.quartz.utils.BigDecimal import com.vitorpamplona.quartz.utils.DualCase import com.vitorpamplona.quartz.utils.Log import com.vitorpamplona.quartz.utils.RandomInstance @@ -362,7 +363,6 @@ import kotlinx.coroutines.flow.stateIn import kotlinx.coroutines.launch import kotlinx.coroutines.sync.Mutex import kotlinx.coroutines.sync.withLock -import java.math.BigDecimal import kotlin.coroutines.cancellation.CancellationException import com.vitorpamplona.quartz.experimental.nip95.header.thumbhash as nip95thumbhash import com.vitorpamplona.quartz.experimental.profileGallery.thumbhash as galleryThumbhash diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/model/AccountConcordActions.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/model/AccountConcordActions.kt index 198509f959..710d87d3cd 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/model/AccountConcordActions.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/model/AccountConcordActions.kt @@ -30,6 +30,7 @@ import com.vitorpamplona.amethyst.commons.model.cache.filter import com.vitorpamplona.amethyst.commons.model.concord.ConcordChannel import com.vitorpamplona.amethyst.commons.model.concord.ConcordCommunitySession import com.vitorpamplona.amethyst.commons.model.concordChannelLastReadRoute +import com.vitorpamplona.amethyst.commons.util.ConcurrentSet import com.vitorpamplona.amethyst.commons.viewmodels.ReplyMode import com.vitorpamplona.quartz.concord.cord02Community.ConcordCommunityList.withControlRoot import com.vitorpamplona.quartz.concord.cord02Community.ConcordCommunityListEntry @@ -70,10 +71,10 @@ import com.vitorpamplona.quartz.nipC7Chats.ChatEvent import com.vitorpamplona.quartz.utils.Log import com.vitorpamplona.quartz.utils.RandomInstance import com.vitorpamplona.quartz.utils.TimeUtils +import com.vitorpamplona.quartz.utils.concurrent.ConcurrentMap import kotlinx.coroutines.async import kotlinx.coroutines.awaitAll import kotlinx.coroutines.coroutineScope -import java.util.concurrent.ConcurrentHashMap /** Name of the default Concord community Admin role minted by "Make admin". */ private const val CONCORD_ADMIN_ROLE = "Admin" @@ -1222,7 +1223,7 @@ class AccountConcordActions( // Rotations we've already adopted ("communityId:epoch"), so a base-rekey wrap still buffered // in the pre-rebuild window (the session rebuild off `liveCommunities` is async) is not // adopted — and re-published — twice on successive revision ticks. - private val adoptedConcordRotations = java.util.Collections.synchronizedSet(HashSet()) + private val adoptedConcordRotations = ConcurrentSet() /** * Persist a rotated access root/epoch for [entry], keeping the prior root as a @@ -1341,7 +1342,7 @@ class AccountConcordActions( // Last time we re-resolved each community's invite_ref, so the recovery sweep rides the // Concord revision tick (which fires on every structural change) without turning it into a // relay-fetch loop. - private val lastConcordRecoveryCheck = ConcurrentHashMap() + private val lastConcordRecoveryCheck = ConcurrentMap() /** * Stranded recovery (CORD-05/06 receive path). A Refounding carries only diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/model/AccountSettings.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/model/AccountSettings.kt index fbf80b4a67..211eb1de7f 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/model/AccountSettings.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/model/AccountSettings.kt @@ -18,6 +18,8 @@ * 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. */ +@file:OptIn(ExperimentalUuidApi::class) + package com.vitorpamplona.amethyst.model import androidx.compose.runtime.Stable @@ -92,6 +94,8 @@ import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.update import kotlinx.serialization.Serializable +import kotlin.uuid.ExperimentalUuidApi +import kotlin.uuid.Uuid val DefaultSignerPermissions = listOf( @@ -596,9 +600,7 @@ class AccountSettings( val entry = NwcWalletEntryNorm( id = - java.util.UUID - .randomUUID() - .toString(), + Uuid.random().toString(), name = "Wallet", uri = newServer, ) diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/model/AccountSyncedSettingsInternal.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/model/AccountSyncedSettingsInternal.kt index a8eb3b9f44..d6ae1dcacd 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/model/AccountSyncedSettingsInternal.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/model/AccountSyncedSettingsInternal.kt @@ -23,10 +23,10 @@ package com.vitorpamplona.amethyst.model import com.vitorpamplona.amethyst.commons.model.navigation.BottomBarEntry import com.vitorpamplona.amethyst.commons.model.navigation.DefaultBottomBarEntries import com.vitorpamplona.amethyst.commons.service.pow.PoWCategory +import com.vitorpamplona.amethyst.commons.util.getDefaultLanguageCode import com.vitorpamplona.amethyst.commons.util.getLanguagesSpokenByUser import com.vitorpamplona.quartz.nip57Zaps.ZapReceiptEvent import kotlinx.serialization.Serializable -import java.util.Locale val DefaultReactions = listOf( @@ -204,7 +204,7 @@ class AccountZapPreferencesInternal( class AccountLanguagePreferencesInternal( var dontTranslateFrom: Set = getLanguagesSpokenByUser(), var languagePreferences: Map = mapOf(), - var translateTo: String = Locale.getDefault().language, + var translateTo: String = getDefaultLanguageCode(), ) @Serializable diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/model/AccountZapActions.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/model/AccountZapActions.kt index 869e47ebf6..2d889cfb4a 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/model/AccountZapActions.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/model/AccountZapActions.kt @@ -50,9 +50,9 @@ import com.vitorpamplona.quartz.nip57Zaps.ZapReceiptEvent import com.vitorpamplona.quartz.nip57Zaps.ZapRequestEvent import com.vitorpamplona.quartz.nipB1Bolt12Zaps.builder.Bolt12ZapBuilder import com.vitorpamplona.quartz.nipB1Bolt12Zaps.verify.Bolt12ZapValidation +import com.vitorpamplona.quartz.utils.BigDecimal import com.vitorpamplona.quartz.utils.Log import kotlinx.coroutines.launch -import java.math.BigDecimal import kotlin.coroutines.cancellation.CancellationException private const val ONCHAIN_BACKEND_NOT_CONFIGURED = "Bitcoin chain backend is not configured" diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/model/GeohashChatIdentityState.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/model/GeohashChatIdentityState.kt index 3308383afe..9c933af7d2 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/model/GeohashChatIdentityState.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/model/GeohashChatIdentityState.kt @@ -28,11 +28,11 @@ import com.vitorpamplona.quartz.nip01Core.crypto.KeyPair import com.vitorpamplona.quartz.nip01Core.signers.NostrSigner import com.vitorpamplona.quartz.nip01Core.signers.NostrSignerInternal import com.vitorpamplona.quartz.utils.RandomInstance +import com.vitorpamplona.quartz.utils.concurrent.ConcurrentMap import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.launch import kotlinx.coroutines.sync.Mutex import kotlinx.coroutines.sync.withLock -import java.util.concurrent.ConcurrentHashMap /** * The account's anonymous, per-geohash chat identities. @@ -66,7 +66,7 @@ class GeohashChatIdentityState( * `synchronized`, because the store reads it protects are suspending. */ private val mutex = Mutex() - private val cache = ConcurrentHashMap() + private val cache = ConcurrentMap() @Volatile private var loaded: GeohashIdentitySecrets? = null diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/model/nip03Timestamp/OtsState.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/model/nip03Timestamp/OtsState.kt index d606671d44..cba59c03fe 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/model/nip03Timestamp/OtsState.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/model/nip03Timestamp/OtsState.kt @@ -32,7 +32,7 @@ import com.vitorpamplona.quartz.utils.TimeUtils import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.flow.update import kotlinx.coroutines.launch -import java.util.Base64 +import kotlin.io.encoding.Base64 class OtsState( val signer: NostrSigner, @@ -58,7 +58,7 @@ class OtsState( Log.d("Pending Attestations") { "Updating ${settings.pendingAttestations.value.size} pending attestations" } return settings.pendingAttestations.value.toList().mapNotNull { (key, value) -> - val otsState = OtsEvent.upgrade(Base64.getDecoder().decode(value), key, otsResolver()) + val otsState = OtsEvent.upgrade(Base64.decode(value), key, otsResolver()) if (otsState != null) { val hint = cache.getNoteIfExists(key)?.toEventHint() @@ -93,7 +93,7 @@ class OtsState( settings.addPendingAttestation( id = id, stamp = - Base64.getEncoder().encodeToString( + Base64.encode( OtsEvent.stamp( id, otsResolver(), diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/model/nip51Lists/followSets/FollowSetsState.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/model/nip51Lists/followSets/FollowSetsState.kt index 4b002aec38..1c0b3cd6fe 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/model/nip51Lists/followSets/FollowSetsState.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/model/nip51Lists/followSets/FollowSetsState.kt @@ -18,6 +18,8 @@ * 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. */ +@file:OptIn(ExperimentalUuidApi::class) + package com.vitorpamplona.amethyst.model.nip51Lists.followSets import com.vitorpamplona.amethyst.commons.model.AddressableNote @@ -55,7 +57,8 @@ import kotlinx.coroutines.flow.onStart import kotlinx.coroutines.flow.stateIn import kotlinx.coroutines.flow.transformLatest import kotlinx.coroutines.flow.update -import java.util.UUID +import kotlin.uuid.ExperimentalUuidApi +import kotlin.uuid.Uuid /** * Maintains several stateflows for each step in processing PeopleLists @@ -205,7 +208,7 @@ class FollowSetsState( isPrivate: Boolean = false, account: Account, ): String { - val dTag = UUID.randomUUID().toString() + val dTag = Uuid.random().toString() val newListTemplate = FollowSetEvent.build( dTag = dTag, @@ -253,7 +256,7 @@ class FollowSetsState( ) { val newList = FollowSetEvent.createListWithDescription( - dTag = UUID.randomUUID().toString(), + dTag = Uuid.random().toString(), title = customCloneName ?: currentPeopleList.title, description = customCloneDescription ?: currentPeopleList.description, publicMembers = currentPeopleList.publicMembers.toUserTags(), diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/model/nip51Lists/followSets/StarterPacksState.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/model/nip51Lists/followSets/StarterPacksState.kt index 881b97a43d..78471c7515 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/model/nip51Lists/followSets/StarterPacksState.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/model/nip51Lists/followSets/StarterPacksState.kt @@ -18,6 +18,8 @@ * 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. */ +@file:OptIn(ExperimentalUuidApi::class) + package com.vitorpamplona.amethyst.model.nip51Lists.followSets import com.vitorpamplona.amethyst.commons.model.AddressableNote @@ -58,7 +60,8 @@ import kotlinx.coroutines.flow.onStart import kotlinx.coroutines.flow.stateIn import kotlinx.coroutines.flow.transformLatest import kotlinx.coroutines.flow.update -import java.util.UUID +import kotlin.uuid.ExperimentalUuidApi +import kotlin.uuid.Uuid /** * Maintains several stateflows for each step in processing PeopleLists @@ -195,7 +198,7 @@ class StarterPacksState( isPrivate: Boolean = false, account: Account, ): String { - val dTag = UUID.randomUUID().toString() + val dTag = Uuid.random().toString() val newListTemplate = StarterPackEvent.build( @@ -245,7 +248,7 @@ class StarterPacksState( val template = listEvent.update { // new list - dTag(UUID.randomUUID().toString()) + dTag(Uuid.random().toString()) // updates names if (customCloneName != null) title(customCloneName) diff --git a/commons/src/androidMain/kotlin/com/vitorpamplona/amethyst/commons/util/SpokenLanguages.android.kt b/commons/src/androidMain/kotlin/com/vitorpamplona/amethyst/commons/util/SpokenLanguages.android.kt index 3badf78de7..418dabd421 100644 --- a/commons/src/androidMain/kotlin/com/vitorpamplona/amethyst/commons/util/SpokenLanguages.android.kt +++ b/commons/src/androidMain/kotlin/com/vitorpamplona/amethyst/commons/util/SpokenLanguages.android.kt @@ -22,6 +22,7 @@ package com.vitorpamplona.amethyst.commons.util import android.content.res.Resources import androidx.core.os.ConfigurationCompat +import java.util.Locale actual fun getLanguagesSpokenByUser(): Set { val languageList = ConfigurationCompat.getLocales(Resources.getSystem().configuration) @@ -31,3 +32,5 @@ actual fun getLanguagesSpokenByUser(): Set { } return codedList } + +actual fun getDefaultLanguageCode(): String = Locale.getDefault().language diff --git a/commons/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/util/SpokenLanguages.kt b/commons/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/util/SpokenLanguages.kt index 1964311e02..011b6f1750 100644 --- a/commons/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/util/SpokenLanguages.kt +++ b/commons/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/util/SpokenLanguages.kt @@ -25,3 +25,6 @@ package com.vitorpamplona.amethyst.commons.util * device, in no particular order. New accounts default to not translating from these. */ expect fun getLanguagesSpokenByUser(): Set + +/** The ISO 639 code of the device's current language, the default target for translations. */ +expect fun getDefaultLanguageCode(): String diff --git a/commons/src/iosMain/kotlin/com/vitorpamplona/amethyst/commons/util/SpokenLanguages.ios.kt b/commons/src/iosMain/kotlin/com/vitorpamplona/amethyst/commons/util/SpokenLanguages.ios.kt index 160d0eb3ba..602a6a2dd3 100644 --- a/commons/src/iosMain/kotlin/com/vitorpamplona/amethyst/commons/util/SpokenLanguages.ios.kt +++ b/commons/src/iosMain/kotlin/com/vitorpamplona/amethyst/commons/util/SpokenLanguages.ios.kt @@ -21,6 +21,8 @@ package com.vitorpamplona.amethyst.commons.util import platform.Foundation.NSLocale +import platform.Foundation.currentLocale +import platform.Foundation.languageCode import platform.Foundation.preferredLanguages // preferredLanguages holds BCP 47 tags ("pt-BR", "zh-Hans-CN"); the language code is the first subtag. @@ -29,3 +31,5 @@ actual fun getLanguagesSpokenByUser(): Set = .mapNotNull { (it as? String)?.substringBefore('-')?.lowercase() } .filter { it.isNotEmpty() } .toSet() + +actual fun getDefaultLanguageCode(): String = NSLocale.currentLocale.languageCode diff --git a/commons/src/jvmMain/kotlin/com/vitorpamplona/amethyst/commons/util/SpokenLanguages.jvm.kt b/commons/src/jvmMain/kotlin/com/vitorpamplona/amethyst/commons/util/SpokenLanguages.jvm.kt index adb19bbf1f..660709702d 100644 --- a/commons/src/jvmMain/kotlin/com/vitorpamplona/amethyst/commons/util/SpokenLanguages.jvm.kt +++ b/commons/src/jvmMain/kotlin/com/vitorpamplona/amethyst/commons/util/SpokenLanguages.jvm.kt @@ -24,3 +24,5 @@ import java.util.Locale // The JVM exposes one default locale, not the OS's ordered preference list. actual fun getLanguagesSpokenByUser(): Set = setOf(Locale.getDefault().language) + +actual fun getDefaultLanguageCode(): String = Locale.getDefault().language