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 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01S7FuNBSKiyVecARSoE4B9P
This commit is contained in:
Claude
2026-09-28 01:58:23 +00:00
parent ad302796c9
commit 9fce45d91f
13 changed files with 42 additions and 21 deletions
@@ -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
@@ -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<String>())
private val adoptedConcordRotations = ConcurrentSet<String>()
/**
* 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<String, Long>()
private val lastConcordRecoveryCheck = ConcurrentMap<String, Long>()
/**
* Stranded recovery (CORD-05/06 receive path). A Refounding carries only
@@ -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,
)
@@ -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<String> = getLanguagesSpokenByUser(),
var languagePreferences: Map<String, String> = mapOf(),
var translateTo: String = Locale.getDefault().language,
var translateTo: String = getDefaultLanguageCode(),
)
@Serializable
@@ -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"
@@ -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<String, KeyPair>()
private val cache = ConcurrentMap<String, KeyPair>()
@Volatile private var loaded: GeohashIdentitySecrets? = null
@@ -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<Event>()
@@ -93,7 +93,7 @@ class OtsState(
settings.addPendingAttestation(
id = id,
stamp =
Base64.getEncoder().encodeToString(
Base64.encode(
OtsEvent.stamp(
id,
otsResolver(),
@@ -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(),
@@ -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)
@@ -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<String> {
val languageList = ConfigurationCompat.getLocales(Resources.getSystem().configuration)
@@ -31,3 +32,5 @@ actual fun getLanguagesSpokenByUser(): Set<String> {
}
return codedList
}
actual fun getDefaultLanguageCode(): String = Locale.getDefault().language
@@ -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<String>
/** The ISO 639 code of the device's current language, the default target for translations. */
expect fun getDefaultLanguageCode(): String
@@ -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<String> =
.mapNotNull { (it as? String)?.substringBefore('-')?.lowercase() }
.filter { it.isNotEmpty() }
.toSet()
actual fun getDefaultLanguageCode(): String = NSLocale.currentLocale.languageCode
@@ -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<String> = setOf(Locale.getDefault().language)
actual fun getDefaultLanguageCode(): String = Locale.getDefault().language