Faster logout processing without closing the account switcher dialog.

This commit is contained in:
Vitor Pamplona
2024-10-08 13:59:51 -04:00
parent f51f929079
commit 67ea0dcf7f
3 changed files with 49 additions and 25 deletions
@@ -57,6 +57,7 @@ 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
import kotlinx.coroutines.flow.filter
import kotlinx.coroutines.sync.Mutex import kotlinx.coroutines.sync.Mutex
import kotlinx.coroutines.sync.withLock import kotlinx.coroutines.sync.withLock
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
@@ -74,7 +75,7 @@ data class AccountInfo(
val npub: String, val npub: String,
val hasPrivKey: Boolean, val hasPrivKey: Boolean,
val loggedInWithExternalSigner: Boolean, val loggedInWithExternalSigner: Boolean,
val isTransient: Boolean = false, val isTransient: Boolean,
) )
private object PrefKeys { private object PrefKeys {
@@ -126,7 +127,7 @@ object LocalPreferences {
private const val COMMA = "," private const val COMMA = ","
private var currentAccount: String? = null private var currentAccount: String? = null
private var savedAccounts: List<AccountInfo>? = null private var savedAccounts: MutableStateFlow<List<AccountInfo>?> = MutableStateFlow(null)
private var cachedAccounts: MutableMap<String, AccountSettings?> = mutableMapOf() private var cachedAccounts: MutableMap<String, AccountSettings?> = mutableMapOf()
suspend fun currentAccount(): String? { suspend fun currentAccount(): String? {
@@ -156,7 +157,7 @@ object LocalPreferences {
} }
private suspend fun savedAccounts(): List<AccountInfo> { private suspend fun savedAccounts(): List<AccountInfo> {
if (savedAccounts == null) { if (savedAccounts.value == null) {
withContext(Dispatchers.IO) { withContext(Dispatchers.IO) {
with(encryptedPreferences()) { with(encryptedPreferences()) {
val newSystemOfAccounts = val newSystemOfAccounts =
@@ -165,7 +166,7 @@ object LocalPreferences {
} }
if (!newSystemOfAccounts.isNullOrEmpty()) { if (!newSystemOfAccounts.isNullOrEmpty()) {
savedAccounts = newSystemOfAccounts savedAccounts.emit(newSystemOfAccounts)
} else { } else {
val oldAccounts = getString(PrefKeys.SAVED_ACCOUNTS, null)?.split(COMMA) ?: listOf() val oldAccounts = getString(PrefKeys.SAVED_ACCOUNTS, null)?.split(COMMA) ?: listOf()
@@ -174,27 +175,28 @@ object LocalPreferences {
AccountInfo( AccountInfo(
npub, npub,
encryptedPreferences(npub).getBoolean(PrefKeys.LOGIN_WITH_EXTERNAL_SIGNER, false), encryptedPreferences(npub).getBoolean(PrefKeys.LOGIN_WITH_EXTERNAL_SIGNER, false),
(encryptedPreferences(npub).getString(PrefKeys.NOSTR_PRIVKEY, "") ?: "") (encryptedPreferences(npub).getString(PrefKeys.NOSTR_PRIVKEY, "") ?: "").isNotBlank(),
.isNotBlank(), false,
) )
} }
savedAccounts = migrated savedAccounts.emit(migrated)
edit().apply { putString(PrefKeys.ALL_ACCOUNT_INFO, Event.mapper.writeValueAsString(savedAccounts)) }.apply() edit().apply { putString(PrefKeys.ALL_ACCOUNT_INFO, Event.mapper.writeValueAsString(savedAccounts)) }.apply()
} }
} }
} }
} }
return savedAccounts!! // it's always not null when it gets here.
return savedAccounts.value!!
} }
fun cachedAccounts() = savedAccounts fun accountsFlow() = savedAccounts
private suspend fun updateSavedAccounts(accounts: List<AccountInfo>) = private suspend fun updateSavedAccounts(accounts: List<AccountInfo>) =
withContext(Dispatchers.IO) { withContext(Dispatchers.IO) {
if (savedAccounts != accounts) { if (savedAccounts != accounts) {
savedAccounts = accounts savedAccounts.emit(accounts)
encryptedPreferences() encryptedPreferences()
.edit() .edit()
@@ -269,7 +271,7 @@ object LocalPreferences {
*/ */
@SuppressLint("ApplySharedPref") @SuppressLint("ApplySharedPref")
suspend fun updatePrefsForLogout(accountInfo: AccountInfo) { suspend fun updatePrefsForLogout(accountInfo: AccountInfo) {
Log.d("LocalPreferences", "Saving to encrypted storage updatePrefsForLogout") Log.d("LocalPreferences", "Saving to encrypted storage updatePrefsForLogout ${accountInfo.npub}")
withContext(Dispatchers.IO) { withContext(Dispatchers.IO) {
encryptedPreferences(accountInfo.npub).edit().clear().commit() encryptedPreferences(accountInfo.npub).edit().clear().commit()
removeAccount(accountInfo) removeAccount(accountInfo)
@@ -47,7 +47,6 @@ import androidx.compose.runtime.derivedStateOf
import androidx.compose.runtime.getValue import androidx.compose.runtime.getValue
import androidx.compose.runtime.livedata.observeAsState import androidx.compose.runtime.livedata.observeAsState
import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.produceState
import androidx.compose.runtime.remember import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment
@@ -55,6 +54,7 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import com.vitorpamplona.amethyst.AccountInfo import com.vitorpamplona.amethyst.AccountInfo
import com.vitorpamplona.amethyst.LocalPreferences import com.vitorpamplona.amethyst.LocalPreferences
import com.vitorpamplona.amethyst.R import com.vitorpamplona.amethyst.R
@@ -73,6 +73,7 @@ import com.vitorpamplona.amethyst.ui.theme.Size10dp
import com.vitorpamplona.amethyst.ui.theme.Size55dp import com.vitorpamplona.amethyst.ui.theme.Size55dp
import com.vitorpamplona.quartz.encoders.decodePublicKeyAsHexOrNull import com.vitorpamplona.quartz.encoders.decodePublicKeyAsHexOrNull
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.forEach
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
@OptIn(ExperimentalMaterial3Api::class) @OptIn(ExperimentalMaterial3Api::class)
@@ -81,14 +82,6 @@ fun AccountSwitchBottomSheet(
accountViewModel: AccountViewModel, accountViewModel: AccountViewModel,
accountStateViewModel: AccountStateViewModel, accountStateViewModel: AccountStateViewModel,
) { ) {
@Suppress("ProduceStateDoesNotAssignValue")
val accounts by
produceState(initialValue = LocalPreferences.cachedAccounts()) {
if (value == null) {
value = LocalPreferences.allSavedAccounts()
}
}
var popupExpanded by remember { mutableStateOf(false) } var popupExpanded by remember { mutableStateOf(false) }
val scrollState = rememberScrollState() val scrollState = rememberScrollState()
@@ -103,7 +96,7 @@ fun AccountSwitchBottomSheet(
) { ) {
Text(stringRes(R.string.account_switch_select_account), fontWeight = FontWeight.Bold) Text(stringRes(R.string.account_switch_select_account), fontWeight = FontWeight.Bold)
} }
accounts?.forEach { acc -> DisplayAccount(acc, accountViewModel, accountStateViewModel) } DisplayAllAccounts(accountViewModel, accountStateViewModel)
Row( Row(
modifier = modifier =
Modifier Modifier
@@ -123,13 +116,22 @@ fun AccountSwitchBottomSheet(
} }
} }
@Composable
private fun DisplayAllAccounts(
accountViewModel: AccountViewModel,
accountStateViewModel: AccountStateViewModel,
) {
val accounts by LocalPreferences.accountsFlow().collectAsStateWithLifecycle()
accounts?.forEach { acc -> DisplayAccount(acc, accountViewModel, accountStateViewModel) }
}
@Composable @Composable
fun DisplayAccount( fun DisplayAccount(
acc: AccountInfo, acc: AccountInfo,
accountViewModel: AccountViewModel, accountViewModel: AccountViewModel,
accountStateViewModel: AccountStateViewModel, accountStateViewModel: AccountStateViewModel,
) { ) {
var baseUser by remember { var baseUser by remember(acc) {
mutableStateOf<User?>( mutableStateOf<User?>(
decodePublicKeyAsHexOrNull(acc.npub)?.let { decodePublicKeyAsHexOrNull(acc.npub)?.let {
LocalCache.getUserIfExists(it) LocalCache.getUserIfExists(it)
@@ -27,6 +27,7 @@ import androidx.lifecycle.viewModelScope
import com.vitorpamplona.amethyst.AccountInfo import com.vitorpamplona.amethyst.AccountInfo
import com.vitorpamplona.amethyst.Amethyst import com.vitorpamplona.amethyst.Amethyst
import com.vitorpamplona.amethyst.LocalPreferences import com.vitorpamplona.amethyst.LocalPreferences
import com.vitorpamplona.amethyst.LocalPreferences.currentAccount
import com.vitorpamplona.amethyst.model.AccountSettings import com.vitorpamplona.amethyst.model.AccountSettings
import com.vitorpamplona.amethyst.model.DefaultChannels import com.vitorpamplona.amethyst.model.DefaultChannels
import com.vitorpamplona.amethyst.model.DefaultDMRelayList import com.vitorpamplona.amethyst.model.DefaultDMRelayList
@@ -321,11 +322,30 @@ class AccountStateViewModel : ViewModel() {
} }
} }
fun currentAccount() =
when (val state = _accountContent.value) {
is AccountState.LoggedIn ->
state.accountSettings.keyPair.pubKey
.toNpub()
is AccountState.LoggedInViewOnly ->
state.accountSettings.keyPair.pubKey
.toNpub()
else -> null
}
fun logOff(accountInfo: AccountInfo) { fun logOff(accountInfo: AccountInfo) {
viewModelScope.launch(Dispatchers.IO) { viewModelScope.launch(Dispatchers.IO) {
prepareLogoutOrSwitch() if (accountInfo.npub == currentAccount()) {
LocalPreferences.updatePrefsForLogout(accountInfo) // log off and relogin with the 0 account
tryLoginExistingAccount() prepareLogoutOrSwitch()
LocalPreferences.updatePrefsForLogout(accountInfo)
tryLoginExistingAccount()
} else {
// delete without login off
LocalPreferences.updatePrefsForLogout(accountInfo)
}
} }
} }
} }