Improves stability of composables

This commit is contained in:
Vitor Pamplona
2024-08-26 16:45:07 -04:00
parent 7c974bc8ef
commit c88dded01b
9 changed files with 41 additions and 26 deletions
@@ -51,6 +51,7 @@ import com.vitorpamplona.quartz.events.MetadataEvent
import com.vitorpamplona.quartz.events.MuteListEvent import com.vitorpamplona.quartz.events.MuteListEvent
import com.vitorpamplona.quartz.events.PrivateOutboxRelayListEvent import com.vitorpamplona.quartz.events.PrivateOutboxRelayListEvent
import com.vitorpamplona.quartz.events.SearchRelayListEvent import com.vitorpamplona.quartz.events.SearchRelayListEvent
import kotlinx.collections.immutable.toImmutableList
import kotlinx.coroutines.CancellationException import kotlinx.coroutines.CancellationException
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.MutableStateFlow
@@ -555,8 +556,8 @@ object LocalPreferences {
dontTranslateFrom = dontTranslateFrom, dontTranslateFrom = dontTranslateFrom,
languagePreferences = languagePreferences, languagePreferences = languagePreferences,
translateTo = translateTo, translateTo = translateTo,
zapAmountChoices = MutableStateFlow(zapAmountChoices), zapAmountChoices = MutableStateFlow(zapAmountChoices.toImmutableList()),
reactionChoices = MutableStateFlow(reactionChoices), reactionChoices = MutableStateFlow(reactionChoices.toImmutableList()),
defaultZapType = MutableStateFlow(defaultZapType), defaultZapType = MutableStateFlow(defaultZapType),
defaultFileServer = defaultFileServer, defaultFileServer = defaultFileServer,
defaultHomeFollowList = MutableStateFlow(defaultHomeFollowList), defaultHomeFollowList = MutableStateFlow(defaultHomeFollowList),
@@ -45,6 +45,9 @@ import com.vitorpamplona.quartz.events.SearchRelayListEvent
import com.vitorpamplona.quartz.signers.ExternalSignerLauncher import com.vitorpamplona.quartz.signers.ExternalSignerLauncher
import com.vitorpamplona.quartz.signers.NostrSignerExternal import com.vitorpamplona.quartz.signers.NostrSignerExternal
import com.vitorpamplona.quartz.signers.NostrSignerInternal import com.vitorpamplona.quartz.signers.NostrSignerInternal
import kotlinx.collections.immutable.ImmutableList
import kotlinx.collections.immutable.persistentListOf
import kotlinx.collections.immutable.toImmutableList
import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.map
@@ -61,7 +64,7 @@ val DefaultChannels =
) )
val DefaultReactions = val DefaultReactions =
listOf( persistentListOf(
"\uD83D\uDE80", "\uD83D\uDE80",
"\uD83E\uDEC2", "\uD83E\uDEC2",
"\uD83D\uDC40", "\uD83D\uDC40",
@@ -92,7 +95,7 @@ val DefaultSearchRelayList =
RelayUrlFormatter.normalize("wss://relay.noswhere.com"), RelayUrlFormatter.normalize("wss://relay.noswhere.com"),
) )
val DefaultZapAmounts = listOf(100L, 500L, 1000L) val DefaultZapAmounts = persistentListOf(100L, 500L, 1000L)
fun getLanguagesSpokenByUser(): Set<String> { fun getLanguagesSpokenByUser(): Set<String> {
val languageList = ConfigurationCompat.getLocales(Resources.getSystem().getConfiguration()) val languageList = ConfigurationCompat.getLocales(Resources.getSystem().getConfiguration())
@@ -118,8 +121,8 @@ class AccountSettings(
var dontTranslateFrom: Set<String> = getLanguagesSpokenByUser(), var dontTranslateFrom: Set<String> = getLanguagesSpokenByUser(),
var languagePreferences: Map<String, String> = mapOf(), var languagePreferences: Map<String, String> = mapOf(),
var translateTo: String = Locale.getDefault().language, var translateTo: String = Locale.getDefault().language,
var zapAmountChoices: MutableStateFlow<List<Long>> = MutableStateFlow(DefaultZapAmounts), var zapAmountChoices: MutableStateFlow<ImmutableList<Long>> = MutableStateFlow(DefaultZapAmounts),
var reactionChoices: MutableStateFlow<List<String>> = MutableStateFlow(DefaultReactions), var reactionChoices: MutableStateFlow<ImmutableList<String>> = MutableStateFlow(DefaultReactions),
val defaultZapType: MutableStateFlow<LnZapEvent.ZapType> = MutableStateFlow(LnZapEvent.ZapType.PUBLIC), val defaultZapType: MutableStateFlow<LnZapEvent.ZapType> = MutableStateFlow(LnZapEvent.ZapType.PUBLIC),
var defaultFileServer: Nip96MediaServers.ServerName = Nip96MediaServers.DEFAULT[0], var defaultFileServer: Nip96MediaServers.ServerName = Nip96MediaServers.DEFAULT[0],
val defaultHomeFollowList: MutableStateFlow<String> = MutableStateFlow(KIND3_FOLLOWS), val defaultHomeFollowList: MutableStateFlow<String> = MutableStateFlow(KIND3_FOLLOWS),
@@ -177,7 +180,7 @@ class AccountSettings(
fun changeZapAmounts(newAmounts: List<Long>) { fun changeZapAmounts(newAmounts: List<Long>) {
if (zapAmountChoices.value != newAmounts) { if (zapAmountChoices.value != newAmounts) {
zapAmountChoices.tryEmit(newAmounts) zapAmountChoices.tryEmit(newAmounts.toImmutableList())
saveAccountSettings() saveAccountSettings()
} }
} }
@@ -191,7 +194,7 @@ class AccountSettings(
fun changeReactionTypes(newTypes: List<String>) { fun changeReactionTypes(newTypes: List<String>) {
if (reactionChoices.value != newTypes) { if (reactionChoices.value != newTypes) {
reactionChoices.tryEmit(newTypes) reactionChoices.tryEmit(newTypes.toImmutableList())
saveAccountSettings() saveAccountSettings()
} }
} }
@@ -21,6 +21,7 @@
package com.vitorpamplona.amethyst.model package com.vitorpamplona.amethyst.model
import android.util.Log import android.util.Log
import androidx.compose.runtime.Stable
import com.fasterxml.jackson.annotation.JsonSubTypes import com.fasterxml.jackson.annotation.JsonSubTypes
import com.fasterxml.jackson.annotation.JsonTypeInfo import com.fasterxml.jackson.annotation.JsonTypeInfo
import com.fasterxml.jackson.databind.DeserializationFeature import com.fasterxml.jackson.databind.DeserializationFeature
@@ -40,11 +41,13 @@ import kotlinx.collections.immutable.toImmutableMap
JsonSubTypes.Type(value = Bundle::class, name = "Bundle"), JsonSubTypes.Type(value = Bundle::class, name = "Bundle"),
JsonSubTypes.Type(value = VisionPrescription::class, name = "VisionPrescription"), JsonSubTypes.Type(value = VisionPrescription::class, name = "VisionPrescription"),
) )
@Stable
open class Resource( open class Resource(
var resourceType: String? = null, var resourceType: String? = null,
var id: String = "", var id: String = "",
) )
@Stable
class Practitioner( class Practitioner(
resourceType: String? = null, resourceType: String? = null,
id: String = "", id: String = "",
@@ -53,6 +56,7 @@ class Practitioner(
var gender: String? = null, var gender: String? = null,
) : Resource(resourceType, id) ) : Resource(resourceType, id)
@Stable
class Patient( class Patient(
resourceType: String? = null, resourceType: String? = null,
id: String = "", id: String = "",
@@ -61,6 +65,7 @@ class Patient(
var gender: String? = null, var gender: String? = null,
) : Resource(resourceType, id) ) : Resource(resourceType, id)
@Stable
class HumanName( class HumanName(
var use: String? = null, var use: String? = null,
var family: String? = null, var family: String? = null,
@@ -69,6 +74,7 @@ class HumanName(
fun assembleName(): String = given.joinToString(" ") + " " + family fun assembleName(): String = given.joinToString(" ") + " " + family
} }
@Stable
class Bundle( class Bundle(
resourceType: String? = null, resourceType: String? = null,
id: String = "", id: String = "",
@@ -77,6 +83,7 @@ class Bundle(
var entry: List<Resource> = arrayListOf(), var entry: List<Resource> = arrayListOf(),
) : Resource(resourceType, id) ) : Resource(resourceType, id)
@Stable
class VisionPrescription( class VisionPrescription(
resourceType: String? = null, resourceType: String? = null,
id: String = "", id: String = "",
@@ -89,6 +96,7 @@ class VisionPrescription(
var lensSpecification: List<LensSpecification> = arrayListOf(), var lensSpecification: List<LensSpecification> = arrayListOf(),
) : Resource(resourceType, id) ) : Resource(resourceType, id)
@Stable
class LensSpecification( class LensSpecification(
var eye: String? = null, var eye: String? = null,
var sphere: Double? = null, var sphere: Double? = null,
@@ -105,6 +113,7 @@ class LensSpecification(
var note: String? = null, var note: String? = null,
) )
@Stable
class Prism( class Prism(
var amount: Double? = null, var amount: Double? = null,
var base: String? = null, var base: String? = null,
@@ -32,6 +32,8 @@ import com.vitorpamplona.amethyst.ui.stringRes
import com.vitorpamplona.ammolite.service.HttpClientManager import com.vitorpamplona.ammolite.service.HttpClientManager
import com.vitorpamplona.quartz.encoders.toHexKey import com.vitorpamplona.quartz.encoders.toHexKey
import com.vitorpamplona.quartz.events.Event import com.vitorpamplona.quartz.events.Event
import kotlinx.collections.immutable.ImmutableList
import kotlinx.collections.immutable.toImmutableList
import kotlinx.serialization.ExperimentalSerializationApi import kotlinx.serialization.ExperimentalSerializationApi
import kotlinx.serialization.Serializable import kotlinx.serialization.Serializable
import kotlinx.serialization.cbor.ByteString import kotlinx.serialization.cbor.ByteString
@@ -61,11 +63,11 @@ class Proof(
) )
object CachedCashuProcessor { object CachedCashuProcessor {
val cashuCache = LruCache<String, GenericLoadable<List<CashuToken>>>(20) val cashuCache = LruCache<String, GenericLoadable<ImmutableList<CashuToken>>>(20)
fun cached(token: String): GenericLoadable<List<CashuToken>> = cashuCache[token] ?: GenericLoadable.Loading() fun cached(token: String): GenericLoadable<ImmutableList<CashuToken>> = cashuCache[token] ?: GenericLoadable.Loading()
fun parse(token: String): GenericLoadable<List<CashuToken>> { fun parse(token: String): GenericLoadable<ImmutableList<CashuToken>> {
if (cashuCache[token] !is GenericLoadable.Loaded) { if (cashuCache[token] !is GenericLoadable.Loaded) {
checkNotInMainThread() checkNotInMainThread()
val newCachuData = CashuProcessor().parse(token) val newCachuData = CashuProcessor().parse(token)
@@ -91,7 +93,7 @@ class CashuProcessor {
val proofs: List<Proof>, val proofs: List<Proof>,
) )
fun parse(cashuToken: String): GenericLoadable<List<CashuToken>> { fun parse(cashuToken: String): GenericLoadable<ImmutableList<CashuToken>> {
checkNotInMainThread() checkNotInMainThread()
if (cashuToken.startsWith("cashuA")) { if (cashuToken.startsWith("cashuA")) {
@@ -105,7 +107,7 @@ class CashuProcessor {
return GenericLoadable.Error("Could not parse this cashu token") return GenericLoadable.Error("Could not parse this cashu token")
} }
fun parseCashuA(cashuToken: String): GenericLoadable<List<CashuToken>> { fun parseCashuA(cashuToken: String): GenericLoadable<ImmutableList<CashuToken>> {
checkNotInMainThread() checkNotInMainThread()
try { try {
@@ -129,10 +131,10 @@ class CashuProcessor {
CashuToken(cashuToken, mint, totalAmount, proofs) CashuToken(cashuToken, mint, totalAmount, proofs)
} }
return GenericLoadable.Loaded(converted) return GenericLoadable.Loaded(converted.toImmutableList())
} catch (e: Exception) { } catch (e: Exception) {
if (e is CancellationException) throw e if (e is CancellationException) throw e
return GenericLoadable.Error<List<CashuToken>>("Could not parse this cashu token") return GenericLoadable.Error("Could not parse this cashu token")
} }
} }
@@ -172,7 +174,7 @@ class CashuProcessor {
) )
@OptIn(ExperimentalSerializationApi::class) @OptIn(ExperimentalSerializationApi::class)
fun parseCashuB(cashuToken: String): GenericLoadable<List<CashuToken>> { fun parseCashuB(cashuToken: String): GenericLoadable<ImmutableList<CashuToken>> {
checkNotInMainThread() checkNotInMainThread()
try { try {
@@ -205,7 +207,7 @@ class CashuProcessor {
CashuToken(cashuToken, mint, totalAmount, proofs) CashuToken(cashuToken, mint, totalAmount, proofs)
} }
return GenericLoadable.Loaded(converted) return GenericLoadable.Loaded(converted.toImmutableList())
} catch (e: Exception) { } catch (e: Exception) {
e.printStackTrace() e.printStackTrace()
if (e is CancellationException) throw e if (e is CancellationException) throw e
@@ -40,7 +40,6 @@ import androidx.compose.ui.graphics.BlendMode
import androidx.compose.ui.graphics.Brush import androidx.compose.ui.graphics.Brush
import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.SolidColor import androidx.compose.ui.graphics.SolidColor
import androidx.compose.ui.graphics.drawscope.DrawStyle
import androidx.compose.ui.graphics.drawscope.Fill import androidx.compose.ui.graphics.drawscope.Fill
import androidx.compose.ui.graphics.graphicsLayer import androidx.compose.ui.graphics.graphicsLayer
import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.Dp
@@ -67,7 +66,6 @@ private const val DEFAULT_GRAPHICS_LAYER_ALPHA: Float = 0.99F
@Composable @Composable
fun AudioWaveformReadOnly( fun AudioWaveformReadOnly(
modifier: Modifier = Modifier, modifier: Modifier = Modifier,
style: DrawStyle = Fill,
waveformBrush: Brush = SolidColor(Color.White), waveformBrush: Brush = SolidColor(Color.White),
progressBrush: Brush = SolidColor(Color.Blue), progressBrush: Brush = SolidColor(Color.Blue),
waveformAlignment: WaveformAlignment = WaveformAlignment.Center, waveformAlignment: WaveformAlignment = WaveformAlignment.Center,
@@ -131,7 +129,7 @@ fun AudioWaveformReadOnly(
height = amplitude, height = amplitude,
), ),
cornerRadius = CornerRadius(spikeRadiusState.toPx(), spikeRadiusState.toPx()), cornerRadius = CornerRadius(spikeRadiusState.toPx(), spikeRadiusState.toPx()),
style = style, style = Fill,
) )
drawRect( drawRect(
brush = progressBrush, brush = progressBrush,
@@ -75,6 +75,7 @@ import com.vitorpamplona.amethyst.ui.theme.Size18Modifier
import com.vitorpamplona.amethyst.ui.theme.Size20Modifier import com.vitorpamplona.amethyst.ui.theme.Size20Modifier
import com.vitorpamplona.amethyst.ui.theme.SmallishBorder import com.vitorpamplona.amethyst.ui.theme.SmallishBorder
import com.vitorpamplona.amethyst.ui.theme.StdHorzSpacer import com.vitorpamplona.amethyst.ui.theme.StdHorzSpacer
import kotlinx.collections.immutable.ImmutableList
import kotlinx.coroutines.CancellationException import kotlinx.coroutines.CancellationException
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
@@ -98,8 +99,8 @@ fun CashuPreview(
CrossfadeIfEnabled(targetState = cashuData, label = "CashuPreview", accountViewModel = accountViewModel) { CrossfadeIfEnabled(targetState = cashuData, label = "CashuPreview", accountViewModel = accountViewModel) {
when (it) { when (it) {
is GenericLoadable.Loaded<List<CashuToken>> -> CashuPreview(it.loaded, accountViewModel) is GenericLoadable.Loaded<ImmutableList<CashuToken>> -> CashuPreview(it.loaded, accountViewModel)
is GenericLoadable.Error<List<CashuToken>> -> is GenericLoadable.Error<ImmutableList<CashuToken>> ->
Text( Text(
text = "$cashutoken ", text = "$cashutoken ",
style = LocalTextStyle.current.copy(textDirection = TextDirection.Content), style = LocalTextStyle.current.copy(textDirection = TextDirection.Content),
@@ -111,7 +112,7 @@ fun CashuPreview(
@Composable @Composable
fun CashuPreview( fun CashuPreview(
tokens: List<CashuToken>, tokens: ImmutableList<CashuToken>,
accountViewModel: AccountViewModel, accountViewModel: AccountViewModel,
) { ) {
tokens.forEach { tokens.forEach {
@@ -1329,7 +1329,7 @@ fun ReactionChoicePopup(
onDismissRequest = { onDismiss() }, onDismissRequest = { onDismiss() },
) { ) {
ReactionChoicePopupContent( ReactionChoicePopupContent(
reactions.toImmutableList(), reactions,
toRemove = toRemove, toRemove = toRemove,
onClick = { reactionType -> onClick = { reactionType ->
accountViewModel.reactToOrDelete( accountViewModel.reactToOrDelete(
@@ -1487,7 +1487,7 @@ fun ZapAmountChoicePopup(
@Composable @Composable
fun ZapAmountChoicePopup( fun ZapAmountChoicePopup(
baseNote: Note, baseNote: Note,
zapAmountChoices: List<Long>, zapAmountChoices: ImmutableList<Long>,
accountViewModel: AccountViewModel, accountViewModel: AccountViewModel,
popupYOffset: Dp, popupYOffset: Dp,
onDismiss: () -> Unit, onDismiss: () -> Unit,
@@ -461,7 +461,7 @@ fun ZapDVMButton(
if (wantsToZap != null) { if (wantsToZap != null) {
ZapAmountChoicePopup( ZapAmountChoicePopup(
baseNote = baseNote, baseNote = baseNote,
zapAmountChoices = listOf(amount / 1000), zapAmountChoices = persistentListOf(amount / 1000),
popupYOffset = iconSize, popupYOffset = iconSize,
accountViewModel = accountViewModel, accountViewModel = accountViewModel,
onDismiss = { onDismiss = {
@@ -121,6 +121,7 @@ class TorrentEvent(
} }
} }
@Immutable
class TorrentFile( class TorrentFile(
val fileName: String, val fileName: String,
val bytes: Long?, val bytes: Long?,