mirror of
https://github.com/vitorpamplona/amethyst.git
synced 2025-09-25 15:30:48 +02:00
Reducing the use of Slow disk access of Local Preferences.
This commit is contained in:
@@ -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)
|
||||
}
|
||||
}
|
||||
|
@@ -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
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -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)
|
||||
}
|
||||
|
@@ -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)
|
||||
}
|
||||
|
Reference in New Issue
Block a user