Correctly logs off from the current account when creating new accounts from inside an existing account.

This commit is contained in:
Vitor Pamplona
2025-07-31 17:45:21 -04:00
parent 14e860928b
commit fe8f519dbf
4 changed files with 58 additions and 47 deletions

View File

@@ -270,7 +270,7 @@ object LocalPreferences {
* deleted * deleted
*/ */
@SuppressLint("ApplySharedPref") @SuppressLint("ApplySharedPref")
suspend fun updatePrefsForLogout(accountInfo: AccountInfo) { suspend fun deleteAccount(accountInfo: AccountInfo) {
Log.d("LocalPreferences", "Saving to encrypted storage updatePrefsForLogout ${accountInfo.npub}") Log.d("LocalPreferences", "Saving to encrypted storage updatePrefsForLogout ${accountInfo.npub}")
withContext(Dispatchers.IO) { withContext(Dispatchers.IO) {
encryptedPreferences(accountInfo.npub).edit(commit = true) { clear() } encryptedPreferences(accountInfo.npub).edit(commit = true) { clear() }
@@ -285,7 +285,7 @@ object LocalPreferences {
} }
} }
suspend fun updatePrefsForLogin(accountSettings: AccountSettings) { suspend fun setDefaultAccount(accountSettings: AccountSettings) {
setCurrentAccount(accountSettings) setCurrentAccount(accountSettings)
saveToEncryptedStorage(accountSettings) saveToEncryptedStorage(accountSettings)
} }

View File

@@ -76,7 +76,7 @@ class MainActivity : AppCompatActivity() {
val accountStateViewModel: AccountStateViewModel = viewModel() val accountStateViewModel: AccountStateViewModel = viewModel()
LaunchedEffect(key1 = Unit) { LaunchedEffect(key1 = Unit) {
accountStateViewModel.tryLoginExistingAccountAsync() accountStateViewModel.loginWithDefaultAccountIfLoggedOff()
} }
AccountScreen(accountStateViewModel, sharedPreferencesViewModel) AccountScreen(accountStateViewModel, sharedPreferencesViewModel)

View File

@@ -297,8 +297,8 @@ private fun NavigateIfIntentRequested(
if (actionableNextPage != null) { if (actionableNextPage != null) {
actionableNextPage?.let { nextRoute -> actionableNextPage?.let { nextRoute ->
val npub = runCatching { URI(intentNextPage.removePrefix("nostr:")).findParameterValue("account") }.getOrNull() val npub = runCatching { URI(intentNextPage.removePrefix("nostr:")).findParameterValue("account") }.getOrNull()
if (npub != null && accountStateViewModel.currentAccount() != npub) { if (npub != null && accountStateViewModel.currentAccountNPub() != npub) {
accountStateViewModel.switchUserSync(npub, nextRoute) accountStateViewModel.checkAndSwitchUserSync(npub, nextRoute)
} else { } else {
val currentRoute = getRouteWithArguments(nav.controller) val currentRoute = getRouteWithArguments(nav.controller)
if (!isSameRoute(currentRoute, nextRoute)) { if (!isSameRoute(currentRoute, nextRoute)) {
@@ -353,8 +353,8 @@ private fun NavigateIfIntentRequested(
if (newPage != null) { if (newPage != null) {
scope.launch { scope.launch {
val npub = runCatching { URI(uri.removePrefix("nostr:")).findParameterValue("account") }.getOrNull() val npub = runCatching { URI(uri.removePrefix("nostr:")).findParameterValue("account") }.getOrNull()
if (npub != null && accountStateViewModel.currentAccount() != npub) { if (npub != null && accountStateViewModel.currentAccountNPub() != npub) {
accountStateViewModel.switchUserSync(npub, newPage) accountStateViewModel.checkAndSwitchUserSync(npub, newPage)
} else { } else {
val currentRoute = getRouteWithArguments(nav.controller) val currentRoute = getRouteWithArguments(nav.controller)
if (!isSameRoute(currentRoute, newPage)) { if (!isSameRoute(currentRoute, newPage)) {

View File

@@ -33,6 +33,7 @@ import com.vitorpamplona.amethyst.model.DefaultDMRelayList
import com.vitorpamplona.amethyst.model.DefaultNIP65List import com.vitorpamplona.amethyst.model.DefaultNIP65List
import com.vitorpamplona.amethyst.model.DefaultNIP65RelaySet import com.vitorpamplona.amethyst.model.DefaultNIP65RelaySet
import com.vitorpamplona.amethyst.model.DefaultSearchRelayList import com.vitorpamplona.amethyst.model.DefaultSearchRelayList
import com.vitorpamplona.amethyst.model.preferences.AccountPreferenceStores.Companion.torSettings
import com.vitorpamplona.amethyst.service.Nip05NostrAddressVerifier import com.vitorpamplona.amethyst.service.Nip05NostrAddressVerifier
import com.vitorpamplona.amethyst.ui.navigation.routes.Route import com.vitorpamplona.amethyst.ui.navigation.routes.Route
import com.vitorpamplona.amethyst.ui.tor.TorSettings import com.vitorpamplona.amethyst.ui.tor.TorSettings
@@ -51,6 +52,7 @@ import com.vitorpamplona.quartz.nip19Bech32.bech32.bechToBytes
import com.vitorpamplona.quartz.nip19Bech32.entities.NAddress import com.vitorpamplona.quartz.nip19Bech32.entities.NAddress
import com.vitorpamplona.quartz.nip19Bech32.entities.NEmbed import com.vitorpamplona.quartz.nip19Bech32.entities.NEmbed
import com.vitorpamplona.quartz.nip19Bech32.entities.NEvent import com.vitorpamplona.quartz.nip19Bech32.entities.NEvent
import com.vitorpamplona.quartz.nip19Bech32.entities.NNote
import com.vitorpamplona.quartz.nip19Bech32.entities.NProfile import com.vitorpamplona.quartz.nip19Bech32.entities.NProfile
import com.vitorpamplona.quartz.nip19Bech32.entities.NPub import com.vitorpamplona.quartz.nip19Bech32.entities.NPub
import com.vitorpamplona.quartz.nip19Bech32.entities.NRelay import com.vitorpamplona.quartz.nip19Bech32.entities.NRelay
@@ -85,16 +87,16 @@ class AccountStateViewModel : ViewModel() {
private var collectorJob: Job? = null private var collectorJob: Job? = null
fun tryLoginExistingAccountAsync() { fun loginWithDefaultAccountIfLoggedOff() {
// pulls account from storage. // pulls account from storage.
if (_accountContent.value !is AccountState.LoggedIn) { if (_accountContent.value !is AccountState.LoggedIn) {
viewModelScope.launch { viewModelScope.launch {
tryLoginExistingAccount() loginWithDefaultAccount()
} }
} }
} }
private suspend fun tryLoginExistingAccount(route: Route? = null) { private suspend fun loginWithDefaultAccount(route: Route? = null) {
val accountSettings = val accountSettings =
withContext(Dispatchers.IO) { withContext(Dispatchers.IO) {
LocalPreferences.loadCurrentAccountFromEncryptedStorage() LocalPreferences.loadCurrentAccountFromEncryptedStorage()
@@ -107,9 +109,7 @@ class AccountStateViewModel : ViewModel() {
} }
} }
private suspend fun requestLoginUI() { private suspend fun requestLoginUI() = _accountContent.update { AccountState.LoggedOff }
_accountContent.update { AccountState.LoggedOff }
}
suspend fun loginAndStartUI( suspend fun loginAndStartUI(
key: String, key: String,
@@ -124,7 +124,7 @@ class AccountStateViewModel : ViewModel() {
is NSec -> null is NSec -> null
is NPub -> parsed.hex.hexToByteArray() is NPub -> parsed.hex.hexToByteArray()
is NProfile -> parsed.hex.hexToByteArray() is NProfile -> parsed.hex.hexToByteArray()
is com.vitorpamplona.quartz.nip19Bech32.entities.NNote -> null is NNote -> null
is NEvent -> null is NEvent -> null
is NEmbed -> null is NEmbed -> null
is NRelay -> null is NRelay -> null
@@ -175,7 +175,7 @@ class AccountStateViewModel : ViewModel() {
) )
} }
LocalPreferences.updatePrefsForLogin(account) LocalPreferences.setDefaultAccount(account)
startUI(account) startUI(account)
} }
@@ -277,6 +277,9 @@ class AccountStateViewModel : ViewModel() {
onError: (String) -> Unit, onError: (String) -> Unit,
) { ) {
try { try {
if (_accountContent.value is AccountState.LoggedIn) {
prepareLogoutOrSwitch()
}
loginAndStartUI(key, torSettings, transientAccount, loginWithExternalSigner, packageName) loginAndStartUI(key, torSettings, transientAccount, loginWithExternalSigner, packageName)
} catch (e: Exception) { } catch (e: Exception) {
if (e is CancellationException) throw e if (e is CancellationException) throw e
@@ -290,31 +293,15 @@ class AccountStateViewModel : ViewModel() {
name: String? = null, name: String? = null,
) { ) {
viewModelScope.launch(Dispatchers.IO) { viewModelScope.launch(Dispatchers.IO) {
if (_accountContent.value is AccountState.LoggedIn) {
prepareLogoutOrSwitch()
}
_accountContent.update { AccountState.Loading } _accountContent.update { AccountState.Loading }
val keyPair = KeyPair() val accountSettings = createNewAccount(torSettings, name)
val tempSigner = NostrSignerSync(keyPair)
val accountSettings = LocalPreferences.setDefaultAccount(accountSettings)
AccountSettings(
keyPair = keyPair,
transientAccount = false,
backupUserMetadata = tempSigner.sign(MetadataEvent.newUser(name)),
backupContactList =
ContactListEvent.createFromScratch(
followUsers = listOf(ContactTag(keyPair.pubKey.toHexKey(), null, null)),
relayUse = emptyMap(),
signer = tempSigner,
),
backupNIP65RelayList = AdvertisedRelayListEvent.create(DefaultNIP65List, tempSigner),
backupDMRelayList = ChatMessageRelayListEvent.create(DefaultDMRelayList, tempSigner),
backupSearchRelayList = SearchRelayListEvent.create(DefaultSearchRelayList.toList(), tempSigner),
backupChannelList = ChannelListEvent.create(emptyList(), DefaultChannels, tempSigner),
torSettings = TorSettingsFlow.build(torSettings),
)
// saves to local preferences
LocalPreferences.updatePrefsForLogin(accountSettings)
startUI(accountSettings) startUI(accountSettings)
@@ -333,13 +320,38 @@ class AccountStateViewModel : ViewModel() {
} }
} }
fun createNewAccount(
torSettings: TorSettings,
name: String? = null,
): AccountSettings {
val keyPair = KeyPair()
val tempSigner = NostrSignerSync(keyPair)
return AccountSettings(
keyPair = keyPair,
transientAccount = false,
backupUserMetadata = tempSigner.sign(MetadataEvent.newUser(name)),
backupContactList =
ContactListEvent.createFromScratch(
followUsers = listOf(ContactTag(keyPair.pubKey.toHexKey(), null, null)),
relayUse = emptyMap(),
signer = tempSigner,
),
backupNIP65RelayList = AdvertisedRelayListEvent.create(DefaultNIP65List, tempSigner),
backupDMRelayList = ChatMessageRelayListEvent.create(DefaultDMRelayList, tempSigner),
backupSearchRelayList = SearchRelayListEvent.create(DefaultSearchRelayList.toList(), tempSigner),
backupChannelList = ChannelListEvent.create(emptyList(), DefaultChannels, tempSigner),
torSettings = TorSettingsFlow.build(torSettings),
)
}
fun switchUser(accountInfo: AccountInfo) { fun switchUser(accountInfo: AccountInfo) {
viewModelScope.launch(Dispatchers.IO) { viewModelScope.launch(Dispatchers.IO) {
switchUserSync(accountInfo) switchUserSync(accountInfo)
} }
} }
suspend fun switchUserSync( suspend fun checkAndSwitchUserSync(
npub: String, npub: String,
route: Route, route: Route,
): Boolean { ): Boolean {
@@ -353,34 +365,33 @@ class AccountStateViewModel : ViewModel() {
return false return false
} }
suspend fun switchUserSync( private suspend fun switchUserSync(
accountInfo: AccountInfo, accountInfo: AccountInfo,
route: Route? = null, route: Route? = null,
) { ) {
prepareLogoutOrSwitch() prepareLogoutOrSwitch()
LocalPreferences.switchToAccount(accountInfo) LocalPreferences.switchToAccount(accountInfo)
tryLoginExistingAccount(route) loginWithDefaultAccount(route)
} }
fun currentAccount() = fun currentAccountNPub() =
when (val state = _accountContent.value) { when (val state = _accountContent.value) {
is AccountState.LoggedIn -> is AccountState.LoggedIn ->
state.accountSettings.keyPair.pubKey state.accountSettings.keyPair.pubKey
.toNpub() .toNpub()
else -> null else -> null
} }
fun logOff(accountInfo: AccountInfo) { fun logOff(accountInfo: AccountInfo) {
viewModelScope.launch(Dispatchers.IO) { viewModelScope.launch(Dispatchers.IO) {
if (accountInfo.npub == currentAccount()) { if (accountInfo.npub == currentAccountNPub()) {
// log off and relogin with the 0 account // log off and relogin with the 0 account
prepareLogoutOrSwitch() prepareLogoutOrSwitch()
LocalPreferences.updatePrefsForLogout(accountInfo) LocalPreferences.deleteAccount(accountInfo)
tryLoginExistingAccount() loginWithDefaultAccount()
} else { } else {
// delete without login off // delete without switching logins
LocalPreferences.updatePrefsForLogout(accountInfo) LocalPreferences.deleteAccount(accountInfo)
} }
} }
} }