Reducing the use of Slow disk access of Local Preferences.

This commit is contained in:
Vitor Pamplona
2023-04-05 08:44:48 -04:00
parent 7b539e63bd
commit 924b21cdfc
4 changed files with 143 additions and 129 deletions

View File

@@ -27,10 +27,7 @@ private const val DEBUG_PREFERENCES_NAME = "debug_prefs"
data class AccountInfo(
val npub: String,
val hasPrivKey: Boolean,
val current: Boolean,
val displayName: String?,
val profilePicture: String?
val hasPrivKey: Boolean = false
)
private object PrefKeys {
@@ -38,8 +35,6 @@ private object PrefKeys {
const val SAVED_ACCOUNTS = "all_saved_accounts"
const val NOSTR_PRIVKEY = "nostr_privkey"
const val NOSTR_PUBKEY = "nostr_pubkey"
const val DISPLAY_NAME = "display_name"
const val PROFILE_PICTURE_URL = "profile_picture"
const val FOLLOWING_CHANNELS = "following_channels"
const val HIDDEN_USERS = "hidden_users"
const val RELAYS = "relays"
@@ -59,53 +54,73 @@ private val gson = GsonBuilder().create()
object LocalPreferences {
private const val comma = ","
private var currentAccount: String?
get() = encryptedPreferences().getString(PrefKeys.CURRENT_ACCOUNT, null)
set(npub) {
val prefs = encryptedPreferences()
prefs.edit().apply {
private var _currentAccount: String? = null
private fun currentAccount(): String? {
if (_currentAccount == null) {
_currentAccount = encryptedPreferences().getString(PrefKeys.CURRENT_ACCOUNT, null)
}
return _currentAccount
}
private fun updateCurrentAccount(npub: String) {
if (_currentAccount != npub) {
_currentAccount = npub
encryptedPreferences().edit().apply {
putString(PrefKeys.CURRENT_ACCOUNT, npub)
}.apply()
}
}
private val savedAccounts: List<String>
get() = encryptedPreferences()
.getString(PrefKeys.SAVED_ACCOUNTS, null)?.split(comma) ?: listOf()
private var _savedAccounts: List<String>? = null
private fun savedAccounts(): List<String> {
if (_savedAccounts == null) {
_savedAccounts = encryptedPreferences()
.getString(PrefKeys.SAVED_ACCOUNTS, null)?.split(comma) ?: listOf()
}
return _savedAccounts!!
}
private fun updateSavedAccounts(accounts: List<String>) {
if (_savedAccounts != accounts) {
_savedAccounts = accounts
encryptedPreferences().edit().apply {
putString(PrefKeys.SAVED_ACCOUNTS, accounts.joinToString(comma).ifBlank { null })
}.apply()
}
}
private val prefsDirPath: String
get() = "${Amethyst.instance.filesDir.parent}/shared_prefs/"
private fun addAccount(npub: String) {
val accounts = savedAccounts.toMutableList()
val accounts = savedAccounts().toMutableList()
if (npub !in accounts) {
accounts.add(npub)
updateSavedAccounts(accounts)
}
val prefs = encryptedPreferences()
prefs.edit().apply {
putString(PrefKeys.SAVED_ACCOUNTS, accounts.joinToString(comma).ifBlank { null })
}.apply()
}
private fun setCurrentAccount(account: Account) {
val npub = account.userProfile().pubkeyNpub()
currentAccount = npub
updateCurrentAccount(npub)
addAccount(npub)
}
fun switchToAccount(npub: String) {
currentAccount = npub
updateCurrentAccount(npub)
}
/**
* Removes the account from the app level shared preferences
*/
private fun removeAccount(npub: String) {
val accounts = savedAccounts.toMutableList()
val accounts = savedAccounts().toMutableList()
if (accounts.remove(npub)) {
val prefs = encryptedPreferences()
prefs.edit().apply {
putString(PrefKeys.SAVED_ACCOUNTS, accounts.joinToString(comma).ifBlank { null })
}.apply()
updateSavedAccounts(accounts)
}
}
@@ -145,11 +160,11 @@ object LocalPreferences {
removeAccount(npub)
deleteUserPreferenceFile(npub)
if (savedAccounts.isEmpty()) {
if (savedAccounts().isEmpty()) {
val appPrefs = encryptedPreferences()
appPrefs.edit().clear().apply()
} else if (currentAccount == npub) {
currentAccount = savedAccounts.elementAt(0)
} else if (currentAccount() == npub) {
updateCurrentAccount(savedAccounts().elementAt(0))
}
}
@@ -159,17 +174,8 @@ object LocalPreferences {
}
fun allSavedAccounts(): List<AccountInfo> {
return savedAccounts.map { npub ->
val prefs = encryptedPreferences(npub)
val hasPrivKey = prefs.getString(PrefKeys.NOSTR_PRIVKEY, null) != null
AccountInfo(
npub = npub,
hasPrivKey = hasPrivKey,
current = npub == currentAccount,
displayName = prefs.getString(PrefKeys.DISPLAY_NAME, null),
profilePicture = prefs.getString(PrefKeys.PROFILE_PICTURE_URL, null)
)
return savedAccounts().map { npub ->
AccountInfo(npub = npub)
}
}
@@ -189,13 +195,11 @@ object LocalPreferences {
putString(PrefKeys.LATEST_CONTACT_LIST, Event.gson.toJson(account.backupContactList))
putBoolean(PrefKeys.HIDE_DELETE_REQUEST_DIALOG, account.hideDeleteRequestDialog)
putBoolean(PrefKeys.HIDE_BLOCK_ALERT_DIALOG, account.hideBlockAlertDialog)
putString(PrefKeys.DISPLAY_NAME, account.userProfile().toBestDisplayName())
putString(PrefKeys.PROFILE_PICTURE_URL, account.userProfile().profilePicture())
}.apply()
}
fun loadFromEncryptedStorage(): Account? {
encryptedPreferences(currentAccount).apply {
encryptedPreferences(currentAccount()).apply {
val pubKey = getString(PrefKeys.NOSTR_PUBKEY, null) ?: return null
val privKey = getString(PrefKeys.NOSTR_PRIVKEY, null)
val followingChannels = getStringSet(PrefKeys.FOLLOWING_CHANNELS, null) ?: setOf()
@@ -247,7 +251,7 @@ object LocalPreferences {
val hideDeleteRequestDialog = getBoolean(PrefKeys.HIDE_DELETE_REQUEST_DIALOG, false)
val hideBlockAlertDialog = getBoolean(PrefKeys.HIDE_BLOCK_ALERT_DIALOG, false)
return Account(
val a = Account(
Persona(privKey = privKey?.toByteArray(), pubKey = pubKey.toByteArray()),
followingChannels,
hiddenUsers,
@@ -261,23 +265,25 @@ object LocalPreferences {
hideBlockAlertDialog,
latestContactList
)
return a
}
}
fun saveLastRead(route: String, timestampInSecs: Long) {
encryptedPreferences(currentAccount).edit().apply {
encryptedPreferences(currentAccount()).edit().apply {
putLong(PrefKeys.LAST_READ(route), timestampInSecs)
}.apply()
}
fun loadLastRead(route: String): Long {
encryptedPreferences(currentAccount).run {
encryptedPreferences(currentAccount()).run {
return getLong(PrefKeys.LAST_READ(route), 0)
}
}
fun migrateSingleUserPrefs() {
if (currentAccount != null) return
if (currentAccount() != null) return
val pubkey = encryptedPreferences().getString(PrefKeys.NOSTR_PUBKEY, null) ?: return
val npub = Hex.decode(pubkey).toNpub()
@@ -314,6 +320,6 @@ object LocalPreferences {
encryptedPreferences().edit().clear().apply()
addAccount(npub)
currentAccount = npub
updateCurrentAccount(npub)
}
}

View File

@@ -10,7 +10,6 @@ import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.width
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.shape.CircleShape
@@ -24,10 +23,8 @@ import androidx.compose.material.TextButton
import androidx.compose.material.TopAppBar
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.ArrowBack
import androidx.compose.material.icons.filled.Key
import androidx.compose.material.icons.filled.Logout
import androidx.compose.material.icons.filled.RadioButtonChecked
import androidx.compose.material.icons.filled.Visibility
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.livedata.observeAsState
@@ -45,14 +42,15 @@ import androidx.compose.ui.window.Dialog
import androidx.compose.ui.window.DialogProperties
import com.vitorpamplona.amethyst.LocalPreferences
import com.vitorpamplona.amethyst.R
import com.vitorpamplona.amethyst.model.LocalCache
import com.vitorpamplona.amethyst.model.decodePublicKey
import com.vitorpamplona.amethyst.model.toHexKey
import com.vitorpamplona.amethyst.ui.components.ResizeImage
import com.vitorpamplona.amethyst.ui.components.RobohashAsyncImageProxy
import com.vitorpamplona.amethyst.ui.note.toShortenHex
import com.vitorpamplona.amethyst.ui.screen.AccountStateViewModel
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
import com.vitorpamplona.amethyst.ui.screen.loggedOff.LoginPage
import nostr.postr.bechToBytes
import nostr.postr.toHex
@Composable
fun AccountSwitchBottomSheet(
@@ -82,90 +80,101 @@ fun AccountSwitchBottomSheet(
accounts.forEach { acc ->
val current = accountUser.pubkeyNpub() == acc.npub
Row(
modifier = Modifier.fillMaxWidth(),
verticalAlignment = Alignment.CenterVertically
) {
val baseUser = try {
LocalCache.getOrCreateUser(decodePublicKey(acc.npub).toHexKey())
} catch (e: Exception) {
null
}
if (baseUser != null) {
val userState by baseUser.live().metadata.observeAsState()
val user = userState?.user ?: return
Row(
modifier = Modifier
.weight(1f)
.clickable {
accountStateViewModel.switchUser(acc.npub)
},
modifier = Modifier.fillMaxWidth(),
verticalAlignment = Alignment.CenterVertically
) {
Row(
modifier = Modifier
.padding(16.dp, 16.dp)
.weight(1f),
.weight(1f)
.clickable {
accountStateViewModel.switchUser(acc.npub)
},
verticalAlignment = Alignment.CenterVertically
) {
Box(
Row(
modifier = Modifier
.width(55.dp)
.padding(0.dp)
.padding(16.dp, 16.dp)
.weight(1f),
verticalAlignment = Alignment.CenterVertically
) {
RobohashAsyncImageProxy(
robot = acc.npub.bechToBytes("npub").toHex(),
model = ResizeImage(acc.profilePicture, 55.dp),
contentDescription = stringResource(R.string.profile_image),
modifier = Modifier
.width(55.dp)
.height(55.dp)
.clip(shape = CircleShape)
)
Box(
modifier = Modifier
.size(20.dp)
.align(Alignment.TopEnd)
.width(55.dp)
.padding(0.dp)
) {
if (acc.hasPrivKey) {
RobohashAsyncImageProxy(
robot = user.pubkeyHex,
model = ResizeImage(user.profilePicture(), 55.dp),
contentDescription = stringResource(R.string.profile_image),
modifier = Modifier
.width(55.dp)
.height(55.dp)
.clip(shape = CircleShape)
)/*
Box(
modifier = Modifier
.size(20.dp)
.align(Alignment.TopEnd)
) {
if (acc.hasPrivKey) {
Icon(
imageVector = Icons.Default.Key,
contentDescription = stringResource(R.string.account_switch_has_private_key),
modifier = Modifier.size(20.dp),
tint = MaterialTheme.colors.primary
)
} else {
Icon(
imageVector = Icons.Default.Visibility,
contentDescription = stringResource(R.string.account_switch_pubkey_only),
modifier = Modifier.size(20.dp),
tint = MaterialTheme.colors.primary
)
}
}*/
}
Spacer(modifier = Modifier.width(16.dp))
Column(modifier = Modifier.weight(1f)) {
val npubShortHex = acc.npub.toShortenHex()
user.bestDisplayName()?.let {
Text(it)
}
Text(npubShortHex)
}
Column(modifier = Modifier.width(32.dp)) {
if (current) {
Icon(
imageVector = Icons.Default.Key,
contentDescription = stringResource(R.string.account_switch_has_private_key),
modifier = Modifier.size(20.dp),
tint = MaterialTheme.colors.primary
)
} else {
Icon(
imageVector = Icons.Default.Visibility,
contentDescription = stringResource(R.string.account_switch_pubkey_only),
modifier = Modifier.size(20.dp),
tint = MaterialTheme.colors.primary
imageVector = Icons.Default.RadioButtonChecked,
contentDescription = stringResource(R.string.account_switch_active_account),
tint = MaterialTheme.colors.secondary
)
}
}
}
Spacer(modifier = Modifier.width(16.dp))
Column(modifier = Modifier.weight(1f)) {
val npubShortHex = acc.npub.toShortenHex()
if (acc.displayName != null && acc.displayName != npubShortHex) {
Text(acc.displayName)
}
Text(npubShortHex)
}
Column(modifier = Modifier.width(32.dp)) {
if (current) {
Icon(
imageVector = Icons.Default.RadioButtonChecked,
contentDescription = stringResource(R.string.account_switch_active_account),
tint = MaterialTheme.colors.secondary
)
}
}
}
}
IconButton(
onClick = { accountStateViewModel.logOff(acc.npub) }
) {
Icon(
imageVector = Icons.Default.Logout,
contentDescription = stringResource(R.string.log_out),
tint = MaterialTheme.colors.onSurface
)
IconButton(
onClick = { accountStateViewModel.logOff(acc.npub) }
) {
Icon(
imageVector = Icons.Default.Logout,
contentDescription = stringResource(R.string.log_out),
tint = MaterialTheme.colors.onSurface
)
}
}
}
}

View File

@@ -35,11 +35,11 @@ class AccountStateViewModel() : ViewModel() {
private fun tryLoginExistingAccount() {
LocalPreferences.loadFromEncryptedStorage()?.let {
login(it)
startUI(it)
}
}
fun login(key: String) {
fun startUI(key: String) {
val pattern = Pattern.compile(".+@.+\\.[a-z]+")
val parsed = Nip19.uriToRoute(key)
val pubKeyParsed = parsed?.hex?.toByteArray()
@@ -56,7 +56,8 @@ class AccountStateViewModel() : ViewModel() {
Account(Persona(Hex.decode(key)))
}
login(account)
LocalPreferences.updatePrefsForLogin(account)
startUI(account)
}
fun switchUser(npub: String) {
@@ -67,24 +68,22 @@ class AccountStateViewModel() : ViewModel() {
fun newKey() {
val account = Account(Persona())
login(account)
// saves to local preferences
LocalPreferences.updatePrefsForLogin(account)
startUI(account)
}
@OptIn(DelicateCoroutinesApi::class)
fun login(account: Account) {
LocalPreferences.updatePrefsForLogin(account)
fun startUI(account: Account) {
if (account.loggedIn.privKey != null) {
_accountContent.update { AccountState.LoggedIn(account) }
} else {
_accountContent.update { AccountState.LoggedInViewOnly(account) }
}
val scope = CoroutineScope(Job() + Dispatchers.IO)
scope.launch {
ServiceManager.start(account)
}
GlobalScope.launch(Dispatchers.Main) {
account.saveable.observeForever(saveListener)
}

View File

@@ -158,7 +158,7 @@ fun LoginPage(
keyboardActions = KeyboardActions(
onGo = {
try {
accountViewModel.login(key.value.text)
accountViewModel.startUI(key.value.text)
} catch (e: Exception) {
errorMessage = context.getString(R.string.invalid_key)
}
@@ -237,7 +237,7 @@ fun LoginPage(
if (acceptedTerms.value && key.value.text.isNotBlank()) {
try {
accountViewModel.login(key.value.text)
accountViewModel.startUI(key.value.text)
} catch (e: Exception) {
errorMessage = context.getString(R.string.invalid_key)
}