mirror of
https://github.com/greenart7c3/Amber.git
synced 2026-10-06 03:18:23 +00:00
Refactor account persistence to use Kotlin Coroutines Flow instead of LiveData
- Replace `LiveData` with `MutableStateFlow` in the `Account` model and `AccountStateViewModel` to observe and save account changes. - Implement custom setters for `signPolicy` and `didBackup` in the `Account` class to trigger state updates automatically. - Simplify `AccountStateViewModel` by using a `Job` to collect and save account state changes on a background dispatcher. - Remove redundant manual calls to `LocalPreferences.saveToEncryptedStorage` across several UI components (`SignPolicySettingsScreen`, `ProfileSubscription`, `AccountBackupDialog`, etc.), as saving is now handled reactively. - Remove `runtime-livedata` dependency from `build.gradle` and `libs.versions.toml`. - Clean up unused imports and the obsolete `AccountLiveData` class.
This commit is contained in:
@@ -189,14 +189,9 @@ dependencies {
|
||||
implementation libs.ui
|
||||
implementation libs.ui.tooling.preview
|
||||
|
||||
implementation libs.runtime.livedata
|
||||
|
||||
// Navigation
|
||||
implementation libs.navigation.compose
|
||||
|
||||
// Observe Live data as State
|
||||
implementation libs.runtime.livedata
|
||||
|
||||
implementation libs.material3
|
||||
implementation libs.material.icons.extended
|
||||
|
||||
|
||||
@@ -6,10 +6,8 @@ import androidx.compose.runtime.Immutable
|
||||
import androidx.compose.runtime.Stable
|
||||
import androidx.compose.ui.platform.ClipEntry
|
||||
import androidx.compose.ui.platform.Clipboard
|
||||
import androidx.lifecycle.LiveData
|
||||
import com.greenart7c3.nostrsigner.Amber
|
||||
import com.greenart7c3.nostrsigner.DataStoreAccess
|
||||
import com.greenart7c3.nostrsigner.LocalPreferences
|
||||
import com.greenart7c3.nostrsigner.R
|
||||
import com.vitorpamplona.quartz.nip01Core.core.Event
|
||||
import com.vitorpamplona.quartz.nip01Core.core.HexKey
|
||||
@@ -26,6 +24,9 @@ import com.vitorpamplona.quartz.nip57Zaps.PrivateZapRequestBuilder
|
||||
import kotlinx.coroutines.CancellationException
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.asStateFlow
|
||||
import kotlinx.coroutines.flow.combine
|
||||
import kotlinx.coroutines.flow.drop
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
@Stable
|
||||
@@ -35,10 +36,35 @@ class Account(
|
||||
val npub: String,
|
||||
val name: MutableStateFlow<String>,
|
||||
val picture: MutableStateFlow<String>,
|
||||
var signPolicy: Int,
|
||||
var didBackup: Boolean,
|
||||
signPolicy: Int,
|
||||
didBackup: Boolean,
|
||||
) {
|
||||
val saveable: AccountLiveData = AccountLiveData(this)
|
||||
var signPolicy: Int = signPolicy
|
||||
set(value) {
|
||||
if (field != value) {
|
||||
field = value
|
||||
_saveable.value = AccountState(this)
|
||||
}
|
||||
}
|
||||
|
||||
var didBackup: Boolean = didBackup
|
||||
set(value) {
|
||||
if (field != value) {
|
||||
field = value
|
||||
_saveable.value = AccountState(this)
|
||||
}
|
||||
}
|
||||
|
||||
private val _saveable = MutableStateFlow(AccountState(this))
|
||||
val saveable = _saveable.asStateFlow()
|
||||
|
||||
init {
|
||||
Amber.instance.applicationIOScope.launch {
|
||||
combine(name, picture) { _, _ -> }.drop(1).collect {
|
||||
_saveable.value = AccountState(this@Account)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun <T : Event> sign(eventTemplate: EventTemplate<T>): T = signer.sign(eventTemplate)
|
||||
|
||||
@@ -87,7 +113,6 @@ class Account(
|
||||
fun copyToClipboard(clipboardManager: Clipboard) {
|
||||
Amber.instance.applicationIOScope.launch {
|
||||
didBackup = true
|
||||
LocalPreferences.saveToEncryptedStorage(Amber.instance, this@Account, null, null, null)
|
||||
val nsec = getNsec()
|
||||
Amber.instance.applicationIOScope.launch(Dispatchers.Main) {
|
||||
clipboardManager.setClipEntry(
|
||||
@@ -106,7 +131,5 @@ class Account(
|
||||
}
|
||||
}
|
||||
|
||||
class AccountLiveData(account: Account) : LiveData<AccountState>(AccountState(account))
|
||||
|
||||
@Immutable
|
||||
class AccountState(val account: Account)
|
||||
|
||||
@@ -88,7 +88,6 @@ class ProfileSubscription(
|
||||
|
||||
if (atLeastOne) {
|
||||
scope.launch {
|
||||
LocalPreferences.saveToEncryptedStorage(appContext, account, null, null, null)
|
||||
LocalPreferences.setLastMetadataUpdate(appContext, npub, TimeUtils.now())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,6 +17,7 @@ import com.vitorpamplona.quartz.nip19Bech32.toNpub
|
||||
import com.vitorpamplona.quartz.nip49PrivKeyEnc.Nip49
|
||||
import com.vitorpamplona.quartz.utils.Hex
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.Job
|
||||
import kotlinx.coroutines.channels.BufferOverflow
|
||||
import kotlinx.coroutines.flow.MutableSharedFlow
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
@@ -48,6 +49,7 @@ class AccountStateViewModel(npub: String?) : ViewModel() {
|
||||
private val _accountContent = MutableStateFlow<AccountState>(AccountState.LoggedOff)
|
||||
val accountContent = _accountContent.asStateFlow()
|
||||
val toasts = MutableSharedFlow<ToastMsg?>(0, 3, onBufferOverflow = BufferOverflow.DROP_OLDEST)
|
||||
private var observerJob: Job? = null
|
||||
|
||||
init {
|
||||
tryLoginExistingAccount(null, npub)
|
||||
@@ -103,15 +105,7 @@ class AccountStateViewModel(npub: String?) : ViewModel() {
|
||||
}
|
||||
|
||||
private fun prepareLogoutOrSwitch() {
|
||||
when (val state = accountContent.value) {
|
||||
is AccountState.LoggedIn -> {
|
||||
Amber.instance.applicationIOScope.launch(Dispatchers.Main) {
|
||||
state.account.saveable.removeObserver(saveListener)
|
||||
}
|
||||
}
|
||||
else -> {}
|
||||
}
|
||||
|
||||
observerJob?.cancel()
|
||||
_accountContent.update { AccountState.LoggedOff }
|
||||
}
|
||||
|
||||
@@ -236,15 +230,12 @@ class AccountStateViewModel(npub: String?) : ViewModel() {
|
||||
) {
|
||||
_accountContent.update { AccountState.LoggedIn(account, route) }
|
||||
|
||||
Amber.instance.applicationIOScope.launch(Dispatchers.Main) {
|
||||
account.saveable.observeForever(saveListener)
|
||||
}
|
||||
}
|
||||
|
||||
private val saveListener: (com.greenart7c3.nostrsigner.models.AccountState) -> Unit = {
|
||||
Amber.instance.applicationIOScope.launch(Dispatchers.Main) {
|
||||
Log.d(Amber.TAG, "Account saved")
|
||||
LocalPreferences.saveToEncryptedStorage(Amber.instance, it.account, null, null, null)
|
||||
observerJob?.cancel()
|
||||
observerJob = Amber.instance.applicationIOScope.launch(Dispatchers.IO) {
|
||||
account.saveable.collect {
|
||||
Log.d(Amber.TAG, "Account saved")
|
||||
LocalPreferences.saveToEncryptedStorage(Amber.instance, it.account, null, null, null)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -25,7 +25,6 @@ import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.navigation.NavController
|
||||
import com.greenart7c3.nostrsigner.LocalPreferences
|
||||
import com.greenart7c3.nostrsigner.R
|
||||
import com.greenart7c3.nostrsigner.models.Account
|
||||
import com.greenart7c3.nostrsigner.ui.components.AmberButton
|
||||
@@ -107,7 +106,6 @@ fun SignPolicySettingsScreen(
|
||||
onClick = {
|
||||
scope.launch(Dispatchers.IO) {
|
||||
account.signPolicy = selectedOption
|
||||
LocalPreferences.saveToEncryptedStorage(context, account, null, null, null)
|
||||
scope.launch(Dispatchers.Main) {
|
||||
navController.navigateUp()
|
||||
}
|
||||
|
||||
@@ -330,7 +330,6 @@ private fun NSecQrButton(
|
||||
) {
|
||||
Amber.instance.applicationIOScope.launch {
|
||||
account.didBackup = true
|
||||
LocalPreferences.saveToEncryptedStorage(context, account, null, null, null)
|
||||
showDialog = false
|
||||
}
|
||||
}
|
||||
@@ -345,7 +344,6 @@ private fun NSecQrButton(
|
||||
onLoading(true)
|
||||
val ncryptsec = account.nip49Encrypt(password)
|
||||
account.didBackup = true
|
||||
LocalPreferences.saveToEncryptedStorage(context, account, null, null, null)
|
||||
Amber.instance.applicationIOScope.launch(Dispatchers.Main) {
|
||||
navController.navigate(Route.QrCode.route.replace("{content}", ncryptsec))
|
||||
}
|
||||
@@ -369,7 +367,6 @@ private fun NSecQrButton(
|
||||
onLoading(true)
|
||||
val ncryptsec = account.nip49Encrypt(password)
|
||||
account.didBackup = true
|
||||
LocalPreferences.saveToEncryptedStorage(context, account, null, null, null)
|
||||
Amber.instance.applicationIOScope.launch(Dispatchers.Main) {
|
||||
navController.navigate(Route.QrCode.route.replace("{content}", ncryptsec))
|
||||
}
|
||||
@@ -533,9 +530,6 @@ private fun SeedWordsButton(
|
||||
Amber.instance.applicationIOScope.launch {
|
||||
account.didBackup = true
|
||||
showSeedWords = true
|
||||
Amber.instance.applicationIOScope.launch {
|
||||
LocalPreferences.saveToEncryptedStorage(context, account, null, null, null)
|
||||
}
|
||||
}
|
||||
},
|
||||
onError = { _, message ->
|
||||
@@ -588,7 +582,6 @@ private fun NSecCopyButton(
|
||||
onApproved = {
|
||||
scope.launch(Dispatchers.IO) {
|
||||
account.didBackup = true
|
||||
LocalPreferences.saveToEncryptedStorage(context, account, null, null, null)
|
||||
if (password.isNotBlank()) {
|
||||
encryptCopyNSec(password, Amber.instance, account, clipboardManager, onLoading)
|
||||
} else {
|
||||
@@ -645,7 +638,6 @@ private fun encryptCopyNSec(
|
||||
try {
|
||||
val key = account.nip49Encrypt(password)
|
||||
account.didBackup = true
|
||||
LocalPreferences.saveToEncryptedStorage(context, account, null, null, null)
|
||||
Amber.instance.applicationIOScope.launch(Dispatchers.Main) {
|
||||
clipboardManager.setClipEntry(
|
||||
ClipEntry(
|
||||
|
||||
@@ -122,7 +122,6 @@ fun ExportAllAccountsScreen(
|
||||
LocalPreferences.allSavedAccounts(context).forEach { accountInfo ->
|
||||
LocalPreferences.loadFromEncryptedStorage(context, accountInfo.npub)?.let { account ->
|
||||
account.didBackup = true
|
||||
LocalPreferences.saveToEncryptedStorage(context, account, null, null, null)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -58,7 +58,6 @@ room-compiler = { module = "androidx.room:room-compiler", version.ref = "roomKtx
|
||||
room-ktx = { module = "androidx.room:room-ktx", version.ref = "roomKtx" }
|
||||
room-runtime = { module = "androidx.room:room-runtime", version.ref = "roomKtx" }
|
||||
room-paging = { module = "androidx.room:room-paging", version.ref = "roomKtx" }
|
||||
runtime-livedata = { module = "androidx.compose.runtime:runtime-livedata", version.ref = "compose_ui" }
|
||||
security-crypto = { module = "androidx.security:security-crypto", version.ref = "securityCryptoKtx" }
|
||||
security-crypto-ktx = { module = "androidx.security:security-crypto-ktx", version.ref = "securityCryptoKtx" }
|
||||
ui = { module = "androidx.compose.ui:ui", version.ref = "compose_ui" }
|
||||
|
||||
Reference in New Issue
Block a user