Use multiple preference files for different accounts

pull/263/head
maxmoney21m 2023-03-12 02:18:43 +08:00
rodzic b7f8241a08
commit 3a2403b344
10 zmienionych plików z 278 dodań i 116 usunięć

Wyświetl plik

@ -0,0 +1,17 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="deploymentTargetDropDown">
<targetSelectedWithDropDown>
<Target>
<type value="QUICK_BOOT_TARGET" />
<deviceKey>
<Key>
<type value="VIRTUAL_DEVICE_PATH" />
<value value="C:\Users\aiong\.android\avd\Pixel_6_API_33.avd" />
</Key>
</deviceKey>
</Target>
</targetSelectedWithDropDown>
<timeTargetWasSelectedWithDropDown value="2023-03-11T16:38:31.686457400Z" />
</component>
</project>

Wyświetl plik

@ -13,6 +13,7 @@
<application
android:allowBackup="false"
android:name=".Amethyst"
android:dataExtractionRules="@xml/data_extraction_rules"
android:fullBackupContent="@xml/backup_rules"
android:icon="@drawable/amethyst"

Wyświetl plik

@ -0,0 +1,15 @@
package com.vitorpamplona.amethyst
import android.app.Application
class Amethyst : Application() {
override fun onCreate() {
super.onCreate()
instance = this
}
companion object {
lateinit var instance: Amethyst
private set
}
}

Wyświetl plik

@ -1,20 +1,22 @@
package com.vitorpamplona.amethyst
import android.content.Context
import androidx.security.crypto.EncryptedSharedPreferences
import androidx.security.crypto.MasterKey
object EncryptedStorage {
private const val PREFERENCES_NAME = "secret_keeper"
fun preferences(context: Context): EncryptedSharedPreferences {
fun preferences(npub: String? = null): EncryptedSharedPreferences {
val context = Amethyst.instance
val masterKey: MasterKey = MasterKey.Builder(context, MasterKey.DEFAULT_MASTER_KEY_ALIAS)
.setKeyScheme(MasterKey.KeyScheme.AES256_GCM)
.build()
val preferencesName = if (npub == null) PREFERENCES_NAME else "${PREFERENCES_NAME}_$npub"
return EncryptedSharedPreferences.create(
context,
PREFERENCES_NAME,
preferencesName,
masterKey,
EncryptedSharedPreferences.PrefKeyEncryptionScheme.AES256_SIV,
EncryptedSharedPreferences.PrefValueEncryptionScheme.AES256_GCM

Wyświetl plik

@ -1,6 +1,7 @@
package com.vitorpamplona.amethyst
import android.content.Context
import android.content.SharedPreferences
import com.google.gson.GsonBuilder
import com.google.gson.reflect.TypeToken
import com.vitorpamplona.amethyst.model.Account
@ -11,53 +12,77 @@ import com.vitorpamplona.amethyst.service.model.Event
import com.vitorpamplona.amethyst.service.model.Event.Companion.getRefinedEvent
import nostr.postr.Persona
import nostr.postr.toHex
import nostr.postr.toNpub
import java.util.Locale
const val DEBUG_PLAINTEXT_PREFERENCES = true
data class AccountInfo(val npub: String, val current: Boolean, val displayName: String?, val profilePicture: String?)
class LocalPreferences(context: Context) {
private object PrefKeys {
const val CURRENT_ACCOUNT = "currently_logged_in_account"
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"
const val DONT_TRANSLATE_FROM = "dontTranslateFrom"
const val LANGUAGE_PREFS = "languagePreferences"
const val TRANSLATE_TO = "translateTo"
const val ZAP_AMOUNTS = "zapAmounts"
const val LATEST_CONTACT_LIST = "latestContactList"
const val HIDE_DELETE_REQUEST_INFO = "hideDeleteRequestInfo"
val LAST_READ: (String) -> String = { route -> "last_read_route_$route" }
}
private fun prefKeysForAccount(npub: String) = object {
val NOSTR_PRIVKEY = "$npub/nostr_privkey"
val NOSTR_PUBKEY = "$npub/nostr_pubkey"
val DISPLAY_NAME = "$npub/display_name"
val PROFILE_PICTURE_URL = "$npub/profile_picture"
val FOLLOWING_CHANNELS = "$npub/following_channels"
val HIDDEN_USERS = "$npub/hidden_users"
val RELAYS = "$npub/relays"
val DONT_TRANSLATE_FROM = "$npub/dontTranslateFrom"
val LANGUAGE_PREFS = "$npub/languagePreferences"
val TRANSLATE_TO = "$npub/translateTo"
val ZAP_AMOUNTS = "$npub/zapAmounts"
val LATEST_CONTACT_LIST = "$npub/latestContactList"
val HIDE_DELETE_REQUEST_INFO = "$npub/hideDeleteRequestInfo"
// val LAST_READ: (String) -> String = { route -> "$npub/last_read_route_$route" }
private val gson = GsonBuilder().create()
object LocalPreferences {
private var currentAccount: String?
get() = encryptedPreferences().getString(PrefKeys.CURRENT_ACCOUNT, null)
set(npub) {
val prefs = encryptedPreferences()
prefs.edit().apply {
putString(PrefKeys.CURRENT_ACCOUNT, npub)
}.apply()
}
private val savedAccounts: Set<String>
get() = encryptedPreferences().getStringSet(PrefKeys.SAVED_ACCOUNTS, null) ?: setOf()
private fun addAccount(npub: String) {
val accounts = savedAccounts.toMutableSet()
accounts.add(npub)
val prefs = encryptedPreferences()
prefs.edit().apply {
putStringSet(PrefKeys.SAVED_ACCOUNTS, accounts)
}.apply()
}
private object PrefKeys {
const val CURRENT_ACCOUNT = "currentlyLoggedInAccount"
// val NOSTR_PRIVKEY = "nostr_privkey"
// val NOSTR_PUBKEY = "nostr_pubkey"
// val FOLLOWING_CHANNELS = "following_channels"
// val HIDDEN_USERS = "hidden_users"
// val RELAYS = "relays"
// val DONT_TRANSLATE_FROM = "dontTranslateFrom"
// val LANGUAGE_PREFS = "languagePreferences"
// val TRANSLATE_TO = "translateTo"
// val ZAP_AMOUNTS = "zapAmounts"
// val LATEST_CONTACT_LIST = "latestContactList"
// val HIDE_DELETE_REQUEST_INFO = "hideDeleteRequestInfo"
val LAST_READ: (String) -> String = { route -> "last_read_route_$route" }
private fun removeAccount(npub: String) {
val accounts = savedAccounts.toMutableSet()
accounts.remove(npub)
val prefs = encryptedPreferences()
prefs.edit().apply {
putStringSet(PrefKeys.SAVED_ACCOUNTS, accounts)
}.apply()
}
private val encryptedPreferences = EncryptedStorage.preferences(context)
private val gson = GsonBuilder().create()
private fun encryptedPreferences(npub: String? = null): SharedPreferences {
return if (DEBUG_PLAINTEXT_PREFERENCES) {
val preferenceFile = if (npub == null) "testing_only" else "testing_only_$npub"
Amethyst.instance.getSharedPreferences(preferenceFile, Context.MODE_PRIVATE)
} else {
return EncryptedStorage.preferences(npub)
}
}
fun clearEncryptedStorage() {
encryptedPreferences.edit().apply {
encryptedPreferences.all.keys.forEach {
fun clearEncryptedStorage(npub: String? = null) {
val encPrefs = encryptedPreferences(npub)
encPrefs.edit().apply {
encPrefs.all.keys.forEach {
remove(it)
}
// encryptedPreferences.all.keys.filter {
@ -69,76 +94,64 @@ class LocalPreferences(context: Context) {
}
fun findAllLocalAccounts(): List<AccountInfo> {
encryptedPreferences.apply {
val currentAccount = getString(PrefKeys.CURRENT_ACCOUNT, null)
return encryptedPreferences.all.keys.filter {
it.endsWith("nostr_pubkey")
}.map {
val npub = it.substringBefore("/")
val myPrefs = prefKeysForAccount(npub)
AccountInfo(
npub,
npub == currentAccount,
getString(myPrefs.DISPLAY_NAME, null),
getString(myPrefs.PROFILE_PICTURE_URL, null)
)
}
return savedAccounts.map { npub ->
val prefs = encryptedPreferences(npub)
AccountInfo(
npub = npub,
current = npub == currentAccount,
displayName = prefs.getString(PrefKeys.DISPLAY_NAME, null),
profilePicture = prefs.getString(PrefKeys.PROFILE_PICTURE_URL, null)
)
}
}
fun saveToEncryptedStorage(account: Account) {
val npub = account.loggedIn.pubKey.toNpub()
val myPrefs = prefKeysForAccount(npub)
encryptedPreferences.edit().apply {
putString(PrefKeys.CURRENT_ACCOUNT, npub)
account.loggedIn.privKey?.let { putString(myPrefs.NOSTR_PRIVKEY, it.toHex()) }
account.loggedIn.pubKey.let { putString(myPrefs.NOSTR_PUBKEY, it.toHex()) }
putStringSet(myPrefs.FOLLOWING_CHANNELS, account.followingChannels)
putStringSet(myPrefs.HIDDEN_USERS, account.hiddenUsers)
putString(myPrefs.RELAYS, gson.toJson(account.localRelays))
putStringSet(myPrefs.DONT_TRANSLATE_FROM, account.dontTranslateFrom)
putString(myPrefs.LANGUAGE_PREFS, gson.toJson(account.languagePreferences))
putString(myPrefs.TRANSLATE_TO, account.translateTo)
putString(myPrefs.ZAP_AMOUNTS, gson.toJson(account.zapAmountChoices))
putString(myPrefs.LATEST_CONTACT_LIST, Event.gson.toJson(account.backupContactList))
putBoolean(myPrefs.HIDE_DELETE_REQUEST_INFO, account.hideDeleteRequestInfo)
}.apply()
fun setCurrentAccount(account: Account) {
val npub = account.userProfile().pubkeyNpub()
currentAccount = npub
addAccount(npub)
}
fun saveCurrentAccountMetadata(account: Account) {
val myPrefs = prefKeysForAccount(account.loggedIn.pubKey.toNpub())
encryptedPreferences.edit().apply {
putString(myPrefs.DISPLAY_NAME, account.userProfile().toBestDisplayName())
putString(myPrefs.PROFILE_PICTURE_URL, account.userProfile().profilePicture())
fun saveToEncryptedStorage(account: Account) {
val prefs = encryptedPreferences(account.userProfile().pubkeyNpub())
prefs.edit().apply {
account.loggedIn.privKey?.let { putString(PrefKeys.NOSTR_PRIVKEY, it.toHex()) }
account.loggedIn.pubKey.let { putString(PrefKeys.NOSTR_PUBKEY, it.toHex()) }
putStringSet(PrefKeys.FOLLOWING_CHANNELS, account.followingChannels)
putStringSet(PrefKeys.HIDDEN_USERS, account.hiddenUsers)
putString(PrefKeys.RELAYS, gson.toJson(account.localRelays))
putStringSet(PrefKeys.DONT_TRANSLATE_FROM, account.dontTranslateFrom)
putString(PrefKeys.LANGUAGE_PREFS, gson.toJson(account.languagePreferences))
putString(PrefKeys.TRANSLATE_TO, account.translateTo)
putString(PrefKeys.ZAP_AMOUNTS, gson.toJson(account.zapAmountChoices))
putString(PrefKeys.LATEST_CONTACT_LIST, Event.gson.toJson(account.backupContactList))
putBoolean(PrefKeys.HIDE_DELETE_REQUEST_INFO, account.hideDeleteRequestInfo)
putString(PrefKeys.DISPLAY_NAME, account.userProfile().toBestDisplayName())
putString(PrefKeys.PROFILE_PICTURE_URL, account.userProfile().profilePicture())
}.apply()
}
fun loadFromEncryptedStorage(): Account? {
encryptedPreferences.apply {
val npub = getString(PrefKeys.CURRENT_ACCOUNT, null) ?: return null
val myPrefs = prefKeysForAccount(npub)
val pubKey = getString(myPrefs.NOSTR_PUBKEY, null) ?: return null
val privKey = getString(myPrefs.NOSTR_PRIVKEY, null)
val followingChannels = getStringSet(myPrefs.FOLLOWING_CHANNELS, null) ?: setOf()
val hiddenUsers = getStringSet(myPrefs.HIDDEN_USERS, emptySet()) ?: setOf()
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()
val hiddenUsers = getStringSet(PrefKeys.HIDDEN_USERS, emptySet()) ?: setOf()
val localRelays = gson.fromJson(
getString(myPrefs.RELAYS, "[]"),
getString(PrefKeys.RELAYS, "[]"),
object : TypeToken<Set<RelaySetupInfo>>() {}.type
) ?: setOf<RelaySetupInfo>()
val dontTranslateFrom = getStringSet(myPrefs.DONT_TRANSLATE_FROM, null) ?: setOf()
val translateTo = getString(myPrefs.TRANSLATE_TO, null) ?: Locale.getDefault().language
val dontTranslateFrom = getStringSet(PrefKeys.DONT_TRANSLATE_FROM, null) ?: setOf()
val translateTo = getString(PrefKeys.TRANSLATE_TO, null) ?: Locale.getDefault().language
val zapAmountChoices = gson.fromJson(
getString(myPrefs.ZAP_AMOUNTS, "[]"),
getString(PrefKeys.ZAP_AMOUNTS, "[]"),
object : TypeToken<List<Long>>() {}.type
) ?: listOf(500L, 1000L, 5000L)
val latestContactList = try {
getString(myPrefs.LATEST_CONTACT_LIST, null)?.let {
getString(PrefKeys.LATEST_CONTACT_LIST, null)?.let {
Event.gson.fromJson(it, Event::class.java).getRefinedEvent(true) as ContactListEvent
}
} catch (e: Throwable) {
@ -147,7 +160,7 @@ class LocalPreferences(context: Context) {
}
val languagePreferences = try {
getString(myPrefs.LANGUAGE_PREFS, null)?.let {
getString(PrefKeys.LANGUAGE_PREFS, null)?.let {
gson.fromJson(it, object : TypeToken<Map<String, String>>() {}.type) as Map<String, String>
} ?: mapOf()
} catch (e: Throwable) {
@ -155,7 +168,7 @@ class LocalPreferences(context: Context) {
mapOf()
}
val hideDeleteRequestInfo = getBoolean(myPrefs.HIDE_DELETE_REQUEST_INFO, false)
val hideDeleteRequestInfo = getBoolean(PrefKeys.HIDE_DELETE_REQUEST_INFO, false)
return Account(
Persona(privKey = privKey?.toByteArray(), pubKey = pubKey.toByteArray()),
@ -173,13 +186,13 @@ class LocalPreferences(context: Context) {
}
fun saveLastRead(route: String, timestampInSecs: Long) {
encryptedPreferences.edit().apply {
encryptedPreferences(currentAccount).edit().apply {
putLong(PrefKeys.LAST_READ(route), timestampInSecs)
}.apply()
}
fun loadLastRead(route: String): Long {
encryptedPreferences.run {
encryptedPreferences(currentAccount).run {
return getLong(PrefKeys.LAST_READ(route), 0)
}
}

Wyświetl plik

@ -21,7 +21,7 @@ object NotificationCache {
val scope = CoroutineScope(Job() + Dispatchers.IO)
scope.launch {
LocalPreferences(context).saveLastRead(route, timestampInSecs)
LocalPreferences.saveLastRead(route, timestampInSecs)
live.invalidateData()
}
}
@ -30,7 +30,7 @@ object NotificationCache {
fun load(route: String, context: Context): Long {
var lastTime = lastReadByRoute[route]
if (lastTime == null) {
lastTime = LocalPreferences(context).loadLastRead(route)
lastTime = LocalPreferences.loadLastRead(route)
lastReadByRoute[route] = lastTime
}
return lastTime

Wyświetl plik

@ -14,7 +14,6 @@ import coil.ImageLoader
import coil.decode.GifDecoder
import coil.decode.ImageDecoderDecoder
import coil.decode.SvgDecoder
import com.vitorpamplona.amethyst.LocalPreferences
import com.vitorpamplona.amethyst.ServiceManager
import com.vitorpamplona.amethyst.service.nip19.Nip19
import com.vitorpamplona.amethyst.service.relays.Client
@ -54,7 +53,7 @@ class MainActivity : FragmentActivity() {
// A surface container using the 'background' color from the theme
Surface(modifier = Modifier.fillMaxSize(), color = MaterialTheme.colors.background) {
val accountStateViewModel: AccountStateViewModel = viewModel {
AccountStateViewModel(LocalPreferences(applicationContext))
AccountStateViewModel()
}
AccountScreen(accountStateViewModel, startingPage)

Wyświetl plik

@ -1,54 +1,81 @@
package com.vitorpamplona.amethyst.ui.navigation
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxHeight
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.width
import androidx.compose.foundation.shape.CircleShape
import androidx.compose.foundation.text.KeyboardActions
import androidx.compose.foundation.text.KeyboardOptions
import androidx.compose.material.ExperimentalMaterialApi
import androidx.compose.material.Icon
import androidx.compose.material.IconButton
import androidx.compose.material.MaterialTheme
import androidx.compose.material.ModalBottomSheetState
import androidx.compose.material.OutlinedTextField
import androidx.compose.material.Surface
import androidx.compose.material.Text
import androidx.compose.material.TextButton
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Logout
import androidx.compose.material.icons.outlined.Visibility
import androidx.compose.material.icons.outlined.VisibilityOff
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
import androidx.compose.runtime.livedata.observeAsState
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.ExperimentalComposeUiApi
import androidx.compose.ui.Modifier
import androidx.compose.ui.autofill.AutofillNode
import androidx.compose.ui.autofill.AutofillType
import androidx.compose.ui.draw.clip
import androidx.compose.ui.focus.onFocusChanged
import androidx.compose.ui.graphics.painter.BitmapPainter
import androidx.compose.ui.layout.boundsInWindow
import androidx.compose.ui.layout.onGloballyPositioned
import androidx.compose.ui.platform.LocalAutofill
import androidx.compose.ui.platform.LocalAutofillTree
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.input.ImeAction
import androidx.compose.ui.text.input.KeyboardType
import androidx.compose.ui.text.input.PasswordVisualTransformation
import androidx.compose.ui.text.input.TextFieldValue
import androidx.compose.ui.text.input.VisualTransformation
import androidx.compose.ui.unit.dp
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.RoboHashCache
import com.vitorpamplona.amethyst.ui.components.AsyncImageProxy
import com.vitorpamplona.amethyst.ui.components.ResizeImage
import com.vitorpamplona.amethyst.ui.note.toShortenHex
import com.vitorpamplona.amethyst.ui.screen.AccountStateViewModel
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
import kotlinx.coroutines.launch
@OptIn(ExperimentalMaterialApi::class)
@OptIn(ExperimentalMaterialApi::class, ExperimentalComposeUiApi::class)
@Composable
fun AccountSwitchBottomSheet(
accountViewModel: AccountViewModel,
accountStateViewModel: AccountStateViewModel,
sheetState: ModalBottomSheetState
) {
val coroutineScope = rememberCoroutineScope()
val context = LocalContext.current
val localPrefs = LocalPreferences(context)
val accounts = localPrefs.findAllLocalAccounts()
val accounts = LocalPreferences.findAllLocalAccounts()
val accountState by accountViewModel.accountLiveData.observeAsState()
val account = accountState?.account ?: return
@ -56,9 +83,7 @@ fun AccountSwitchBottomSheet(
val accountUserState by account.userProfile().live().metadata.observeAsState()
val accountUser = accountUserState?.user ?: return
LaunchedEffect(key1 = accountUser) {
localPrefs.saveCurrentAccountMetadata(account)
}
var popupExpanded by remember { mutableStateOf(false) }
Column {
Row(
@ -114,9 +139,98 @@ fun AccountSwitchBottomSheet(
horizontalArrangement = Arrangement.Center,
verticalAlignment = Alignment.CenterVertically
) {
TextButton(onClick = { coroutineScope.launch { sheetState.hide() } }) {
TextButton(onClick = { popupExpanded = true }) {
Text("Add New Account")
}
}
}
if (popupExpanded) {
Dialog(
onDismissRequest = { popupExpanded = false },
properties = DialogProperties(usePlatformDefaultWidth = false)
) {
Surface(modifier = Modifier.fillMaxSize()) {
Column(
modifier = Modifier
.fillMaxHeight()
.background(MaterialTheme.colors.surface),
verticalArrangement = Arrangement.Center,
horizontalAlignment = Alignment.CenterHorizontally
) {
val key = remember { mutableStateOf(TextFieldValue("")) }
var errorMessage by remember { mutableStateOf("") }
var showPassword by remember {
mutableStateOf(false)
}
val autofillNode = AutofillNode(
autofillTypes = listOf(AutofillType.Password),
onFill = { key.value = TextFieldValue(it) }
)
val autofill = LocalAutofill.current
LocalAutofillTree.current += autofillNode
OutlinedTextField(
modifier = Modifier
.onGloballyPositioned { coordinates ->
autofillNode.boundingBox = coordinates.boundsInWindow()
}
.onFocusChanged { focusState ->
autofill?.run {
if (focusState.isFocused) {
requestAutofillForNode(autofillNode)
} else {
cancelAutofillForNode(autofillNode)
}
}
},
value = key.value,
onValueChange = { key.value = it },
keyboardOptions = KeyboardOptions(
autoCorrect = false,
keyboardType = KeyboardType.Password,
imeAction = ImeAction.Go
),
placeholder = {
Text(
text = stringResource(R.string.nsec_npub_hex_private_key),
color = MaterialTheme.colors.onSurface.copy(alpha = 0.32f)
)
},
trailingIcon = {
IconButton(onClick = { showPassword = !showPassword }) {
Icon(
imageVector = if (showPassword) Icons.Outlined.VisibilityOff else Icons.Outlined.Visibility,
contentDescription = if (showPassword) {
stringResource(R.string.show_password)
} else {
stringResource(
R.string.hide_password
)
}
)
}
},
visualTransformation = if (showPassword) VisualTransformation.None else PasswordVisualTransformation(),
keyboardActions = KeyboardActions(
onGo = {
try {
accountStateViewModel.login(key.value.text)
} catch (e: Exception) {
errorMessage = context.getString(R.string.invalid_key)
}
}
)
)
if (errorMessage.isNotBlank()) {
Text(
text = errorMessage,
color = MaterialTheme.colors.error,
style = MaterialTheme.typography.caption
)
}
}
}
}
}
}

Wyświetl plik

@ -17,7 +17,7 @@ import nostr.postr.Persona
import nostr.postr.bechToBytes
import java.util.regex.Pattern
class AccountStateViewModel(private val localPreferences: LocalPreferences) : ViewModel() {
class AccountStateViewModel() : ViewModel() {
private val _accountContent = MutableStateFlow<AccountState>(AccountState.LoggedOff)
val accountContent = _accountContent.asStateFlow()
@ -26,7 +26,7 @@ class AccountStateViewModel(private val localPreferences: LocalPreferences) : Vi
// Keeps it in the the UI thread to void blinking the login page.
// viewModelScope.launch(Dispatchers.IO) {
localPreferences.loadFromEncryptedStorage()?.let {
LocalPreferences.loadFromEncryptedStorage()?.let {
login(it)
}
// }
@ -47,18 +47,19 @@ class AccountStateViewModel(private val localPreferences: LocalPreferences) : Vi
Account(Persona(Hex.decode(key)))
}
localPreferences.saveToEncryptedStorage(account)
LocalPreferences.saveToEncryptedStorage(account)
login(account)
}
fun newKey() {
val account = Account(Persona())
localPreferences.saveToEncryptedStorage(account)
LocalPreferences.saveToEncryptedStorage(account)
login(account)
}
fun login(account: Account) {
LocalPreferences.setCurrentAccount(account)
if (account.loggedIn.privKey != null) {
_accountContent.update { AccountState.LoggedIn(account) }
} else {
@ -77,7 +78,7 @@ class AccountStateViewModel(private val localPreferences: LocalPreferences) : Vi
private val saveListener: (com.vitorpamplona.amethyst.model.AccountState) -> Unit = {
GlobalScope.launch(Dispatchers.IO) {
localPreferences.saveToEncryptedStorage(it.account)
LocalPreferences.saveToEncryptedStorage(it.account)
}
}
@ -100,6 +101,6 @@ class AccountStateViewModel(private val localPreferences: LocalPreferences) : Vi
_accountContent.update { AccountState.LoggedOff }
localPreferences.clearEncryptedStorage()
LocalPreferences.clearEncryptedStorage()
}
}

Wyświetl plik

@ -47,7 +47,7 @@ fun MainScreen(accountViewModel: AccountViewModel, accountStateViewModel: Accoun
ModalBottomSheetLayout(
sheetState = sheetState,
sheetContent = {
AccountSwitchBottomSheet(accountViewModel = accountViewModel, sheetState = sheetState)
AccountSwitchBottomSheet(accountViewModel = accountViewModel, accountStateViewModel = accountStateViewModel, sheetState = sheetState)
}
) {
Scaffold(