Account State is more than just a view model.

This commit is contained in:
Vitor Pamplona
2026-03-03 16:42:45 -05:00
parent 2e71401342
commit d8911ef69c
16 changed files with 144 additions and 111 deletions
@@ -55,6 +55,7 @@ import com.vitorpamplona.amethyst.service.relayClient.notifyCommand.model.Notify
import com.vitorpamplona.amethyst.service.relayClient.reqCommand.RelaySubscriptionsCoordinator import com.vitorpamplona.amethyst.service.relayClient.reqCommand.RelaySubscriptionsCoordinator
import com.vitorpamplona.amethyst.service.relayClient.speedLogger.RelaySpeedLogger import com.vitorpamplona.amethyst.service.relayClient.speedLogger.RelaySpeedLogger
import com.vitorpamplona.amethyst.service.uploads.nip95.Nip95CacheFactory import com.vitorpamplona.amethyst.service.uploads.nip95.Nip95CacheFactory
import com.vitorpamplona.amethyst.ui.screen.AccountSessionManager
import com.vitorpamplona.amethyst.ui.screen.UiSettingsState import com.vitorpamplona.amethyst.ui.screen.UiSettingsState
import com.vitorpamplona.amethyst.ui.tor.TorManager import com.vitorpamplona.amethyst.ui.tor.TorManager
import com.vitorpamplona.quartz.nip01Core.relay.client.INostrClient import com.vitorpamplona.quartz.nip01Core.relay.client.INostrClient
@@ -231,6 +232,15 @@ class AppModules(
client = client, client = client,
) )
val sessionManager =
AccountSessionManager(
accountsCache = accountsCache,
nip05Client = nip05Client,
client = client,
localPreferences = LocalPreferences,
scope = applicationIOScope,
)
// Organizes cache clearing // Organizes cache clearing
val trimmingService = MemoryTrimmingService(cache) val trimmingService = MemoryTrimmingService(cache)
@@ -274,6 +284,7 @@ class AppModules(
applicationIOScope.launch { applicationIOScope.launch {
// loads main account quickly. // loads main account quickly.
LocalPreferences.loadAccountConfigFromEncryptedStorage() LocalPreferences.loadAccountConfigFromEncryptedStorage()
sessionManager.loginWithDefaultAccountIfLoggedOff()
} }
// forces initialization of uiPrefs in the main thread to avoid blinking themes // forces initialization of uiPrefs in the main thread to avoid blinking themes
@@ -26,8 +26,6 @@ import androidx.activity.compose.setContent
import androidx.activity.enableEdgeToEdge import androidx.activity.enableEdgeToEdge
import androidx.annotation.RequiresApi import androidx.annotation.RequiresApi
import androidx.appcompat.app.AppCompatActivity import androidx.appcompat.app.AppCompatActivity
import androidx.compose.runtime.LaunchedEffect
import androidx.lifecycle.viewmodel.compose.viewModel
import com.vitorpamplona.amethyst.Amethyst import com.vitorpamplona.amethyst.Amethyst
import com.vitorpamplona.amethyst.debugState import com.vitorpamplona.amethyst.debugState
import com.vitorpamplona.amethyst.model.Account import com.vitorpamplona.amethyst.model.Account
@@ -38,7 +36,6 @@ import com.vitorpamplona.amethyst.service.playback.pip.BackgroundMedia
import com.vitorpamplona.amethyst.ui.navigation.routes.Route import com.vitorpamplona.amethyst.ui.navigation.routes.Route
import com.vitorpamplona.amethyst.ui.navigation.routes.routeFor import com.vitorpamplona.amethyst.ui.navigation.routes.routeFor
import com.vitorpamplona.amethyst.ui.screen.AccountScreen import com.vitorpamplona.amethyst.ui.screen.AccountScreen
import com.vitorpamplona.amethyst.ui.screen.AccountStateViewModel
import com.vitorpamplona.amethyst.ui.theme.AmethystTheme import com.vitorpamplona.amethyst.ui.theme.AmethystTheme
import com.vitorpamplona.quartz.nip01Core.core.AddressableEvent import com.vitorpamplona.quartz.nip01Core.core.AddressableEvent
import com.vitorpamplona.quartz.nip19Bech32.Nip19Parser import com.vitorpamplona.quartz.nip19Bech32.Nip19Parser
@@ -68,13 +65,7 @@ class MainActivity : AppCompatActivity() {
setContent { setContent {
StringResSetup() StringResSetup()
AmethystTheme { AmethystTheme {
val accountStateViewModel: AccountStateViewModel = viewModel() AccountScreen(Amethyst.instance.sessionManager)
LaunchedEffect(key1 = Unit) {
accountStateViewModel.loginWithDefaultAccountIfLoggedOff()
}
AccountScreen(accountStateViewModel)
} }
} }
} }
@@ -46,7 +46,7 @@ private tailrec fun Context.getActivityWindow(): Window? =
} }
@Composable @Composable
fun getActivity(): Activity? = LocalContext.current.getActivity() fun getActivity(): Activity = LocalContext.current.getActivity()
tailrec fun Context.getActivity(): ComponentActivity = tailrec fun Context.getActivity(): ComponentActivity =
when (this) { when (this) {
@@ -55,6 +55,13 @@ tailrec fun Context.getActivity(): ComponentActivity =
else -> throw IllegalStateException("Requires a ComponentActivity to run") else -> throw IllegalStateException("Requires a ComponentActivity to run")
} }
fun Context.getActivityOrNull(): ComponentActivity? =
when (this) {
is ComponentActivity -> this
is ContextWrapper -> baseContext.getActivity()
else -> null
}
@Composable @Composable
fun amethystApp(): Amethyst = LocalContext.current.asAmethystApp() fun amethystApp(): Amethyst = LocalContext.current.asAmethystApp()
@@ -57,7 +57,7 @@ import com.vitorpamplona.amethyst.ui.navigation.routes.isSameRoute
import com.vitorpamplona.amethyst.ui.note.PayViaIntentScreen import com.vitorpamplona.amethyst.ui.note.PayViaIntentScreen
import com.vitorpamplona.amethyst.ui.note.UpdateReactionTypeScreen import com.vitorpamplona.amethyst.ui.note.UpdateReactionTypeScreen
import com.vitorpamplona.amethyst.ui.note.nip22Comments.ReplyCommentPostScreen import com.vitorpamplona.amethyst.ui.note.nip22Comments.ReplyCommentPostScreen
import com.vitorpamplona.amethyst.ui.screen.AccountStateViewModel import com.vitorpamplona.amethyst.ui.screen.AccountSessionManager
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountSwitcherAndLeftDrawerLayout import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountSwitcherAndLeftDrawerLayout
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
import com.vitorpamplona.amethyst.ui.screen.loggedIn.bookmarkgroups.default.BookmarkListScreen import com.vitorpamplona.amethyst.ui.screen.loggedIn.bookmarkgroups.default.BookmarkListScreen
@@ -128,11 +128,11 @@ import java.net.URI
@Composable @Composable
fun AppNavigation( fun AppNavigation(
accountViewModel: AccountViewModel, accountViewModel: AccountViewModel,
accountStateViewModel: AccountStateViewModel, accountSessionManager: AccountSessionManager,
) { ) {
val nav = rememberNav() val nav = rememberNav()
AccountSwitcherAndLeftDrawerLayout(accountViewModel, accountStateViewModel, nav) { AccountSwitcherAndLeftDrawerLayout(accountViewModel, accountSessionManager, nav) {
NavHost( NavHost(
navController = nav.controller, navController = nav.controller,
startDestination = Route.Home, startDestination = Route.Home,
@@ -320,7 +320,7 @@ fun AppNavigation(
} }
} }
NavigateIfIntentRequested(nav, accountViewModel, accountStateViewModel) NavigateIfIntentRequested(nav, accountViewModel, accountSessionManager)
DisplayErrorMessages(accountViewModel.toastManager, accountViewModel, nav) DisplayErrorMessages(accountViewModel.toastManager, accountViewModel, nav)
DisplayNotifyMessages(accountViewModel, nav) DisplayNotifyMessages(accountViewModel, nav)
@@ -332,7 +332,7 @@ fun AppNavigation(
private fun NavigateIfIntentRequested( private fun NavigateIfIntentRequested(
nav: Nav, nav: Nav,
accountViewModel: AccountViewModel, accountViewModel: AccountViewModel,
accountStateViewModel: AccountStateViewModel, accountSessionManager: AccountSessionManager,
) { ) {
accountViewModel.firstRoute?.let { newRoute -> accountViewModel.firstRoute?.let { newRoute ->
accountViewModel.firstRoute = null accountViewModel.firstRoute = null
@@ -386,8 +386,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.currentAccountNPub() != npub) { if (npub != null && accountSessionManager.currentAccountNPub() != npub) {
accountStateViewModel.checkAndSwitchUserSync(npub, nextRoute) accountSessionManager.checkAndSwitchUserSync(npub, nextRoute)
} else { } else {
val currentRoute = getRouteWithArguments(nextRoute::class, nav.controller) val currentRoute = getRouteWithArguments(nextRoute::class, nav.controller)
if (!isSameRoute(currentRoute, nextRoute)) { if (!isSameRoute(currentRoute, nextRoute)) {
@@ -442,8 +442,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.currentAccountNPub() != npub) { if (npub != null && accountSessionManager.currentAccountNPub() != npub) {
accountStateViewModel.checkAndSwitchUserSync(npub, newPage) accountSessionManager.checkAndSwitchUserSync(npub, newPage)
} else { } else {
val currentRoute = getRouteWithArguments(newPage::class, nav.controller) val currentRoute = getRouteWithArguments(newPage::class, nav.controller)
if (!isSameRoute(currentRoute, newPage)) { if (!isSameRoute(currentRoute, newPage)) {
@@ -474,7 +474,7 @@ private fun NavigateIfIntentRequested(
} }
if (newAccount != null) { if (newAccount != null) {
AddAccountDialog(newAccount, accountStateViewModel) { newAccount = null } AddAccountDialog(newAccount, accountSessionManager) { newAccount = null }
} }
} }
} }
@@ -63,7 +63,7 @@ import com.vitorpamplona.amethyst.service.relayClient.reqCommand.user.observeUse
import com.vitorpamplona.amethyst.ui.components.CreateTextWithEmoji import com.vitorpamplona.amethyst.ui.components.CreateTextWithEmoji
import com.vitorpamplona.amethyst.ui.components.RobohashFallbackAsyncImage import com.vitorpamplona.amethyst.ui.components.RobohashFallbackAsyncImage
import com.vitorpamplona.amethyst.ui.note.toShortDisplay import com.vitorpamplona.amethyst.ui.note.toShortDisplay
import com.vitorpamplona.amethyst.ui.screen.AccountStateViewModel import com.vitorpamplona.amethyst.ui.screen.AccountSessionManager
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
import com.vitorpamplona.amethyst.ui.screen.loggedOff.AddAccountDialog import com.vitorpamplona.amethyst.ui.screen.loggedOff.AddAccountDialog
import com.vitorpamplona.amethyst.ui.stringRes import com.vitorpamplona.amethyst.ui.stringRes
@@ -78,7 +78,7 @@ import kotlinx.coroutines.launch
@Composable @Composable
fun AccountSwitchBottomSheet( fun AccountSwitchBottomSheet(
accountViewModel: AccountViewModel, accountViewModel: AccountViewModel,
accountStateViewModel: AccountStateViewModel, accountSessionManager: AccountSessionManager,
) { ) {
var popupExpanded by remember { mutableStateOf(false) } var popupExpanded by remember { mutableStateOf(false) }
val scrollState = rememberScrollState() val scrollState = rememberScrollState()
@@ -94,7 +94,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)
} }
DisplayAllAccounts(accountViewModel, accountStateViewModel) DisplayAllAccounts(accountViewModel, accountSessionManager)
Row( Row(
modifier = modifier =
Modifier Modifier
@@ -110,24 +110,24 @@ fun AccountSwitchBottomSheet(
} }
if (popupExpanded) { if (popupExpanded) {
AddAccountDialog(null, accountStateViewModel) { popupExpanded = false } AddAccountDialog(null, accountSessionManager) { popupExpanded = false }
} }
} }
@Composable @Composable
private fun DisplayAllAccounts( private fun DisplayAllAccounts(
accountViewModel: AccountViewModel, accountViewModel: AccountViewModel,
accountStateViewModel: AccountStateViewModel, accountSessionManager: AccountSessionManager,
) { ) {
val accounts by LocalPreferences.accountsFlow().collectAsStateWithLifecycle() val accounts by LocalPreferences.accountsFlow().collectAsStateWithLifecycle()
accounts?.forEach { acc -> DisplayAccount(acc, accountViewModel, accountStateViewModel) } accounts?.forEach { acc -> DisplayAccount(acc, accountViewModel, accountSessionManager) }
} }
@Composable @Composable
fun DisplayAccount( fun DisplayAccount(
acc: AccountInfo, acc: AccountInfo,
accountViewModel: AccountViewModel, accountViewModel: AccountViewModel,
accountStateViewModel: AccountStateViewModel, accountSessionManager: AccountSessionManager,
) { ) {
var baseUser by remember(acc) { var baseUser by remember(acc) {
mutableStateOf<User?>( mutableStateOf<User?>(
@@ -153,7 +153,7 @@ fun DisplayAccount(
modifier = modifier =
Modifier Modifier
.fillMaxWidth() .fillMaxWidth()
.clickable { accountStateViewModel.switchUser(acc) } .clickable { accountSessionManager.switchUser(acc) }
.padding(16.dp, 16.dp), .padding(16.dp, 16.dp),
verticalAlignment = Alignment.CenterVertically, verticalAlignment = Alignment.CenterVertically,
) { ) {
@@ -179,7 +179,7 @@ fun DisplayAccount(
} }
} }
LogoutButton(acc, accountStateViewModel) LogoutButton(acc, accountSessionManager)
} }
} }
} }
@@ -247,7 +247,7 @@ private fun AccountName(
@Composable @Composable
private fun LogoutButton( private fun LogoutButton(
acc: AccountInfo, acc: AccountInfo,
accountStateViewModel: AccountStateViewModel, accountSessionManager: AccountSessionManager,
) { ) {
var logoutDialog by remember { mutableStateOf(false) } var logoutDialog by remember { mutableStateOf(false) }
if (logoutDialog) { if (logoutDialog) {
@@ -259,7 +259,7 @@ private fun LogoutButton(
TextButton( TextButton(
onClick = { onClick = {
logoutDialog = false logoutDialog = false
accountStateViewModel.logOff(acc) accountSessionManager.logOff(acc)
}, },
) { ) {
Text(text = stringRes(R.string.log_out)) Text(text = stringRes(R.string.log_out))
@@ -41,14 +41,14 @@ import com.vitorpamplona.amethyst.ui.stringRes
import com.vitorpamplona.quartz.utils.Log import com.vitorpamplona.quartz.utils.Log
@Composable @Composable
fun AccountScreen(accountStateViewModel: AccountStateViewModel) { fun AccountScreen(accountSessionManager: AccountSessionManager) {
// Pauses relay services when the app pauses // Pauses relay services when the app pauses
ManageRelayServices() ManageRelayServices()
ManageWebOkHttp() ManageWebOkHttp()
val accountState by accountStateViewModel.accountContent.collectAsStateWithLifecycle() val accountState by accountSessionManager.accountContent.collectAsStateWithLifecycle()
Log.d("ActivityLifecycle", "AccountScreen $accountState $accountStateViewModel") Log.d("ActivityLifecycle", "AccountScreen $accountState $accountSessionManager")
Crossfade( Crossfade(
targetState = accountState, targetState = accountState,
@@ -56,8 +56,8 @@ fun AccountScreen(accountStateViewModel: AccountStateViewModel) {
) { state -> ) { state ->
when (state) { when (state) {
is AccountState.Loading -> LoadingSetup() is AccountState.Loading -> LoadingSetup()
is AccountState.LoggedOff -> LoggedOffSetup(accountStateViewModel) is AccountState.LoggedOff -> LoggedOffSetup(accountSessionManager)
is AccountState.LoggedIn -> LoggedInSetup(state, accountStateViewModel) is AccountState.LoggedIn -> LoggedInSetup(state, accountSessionManager)
} }
} }
} }
@@ -94,25 +94,25 @@ fun LoadingSetup() {
} }
@Composable @Composable
fun LoggedOffSetup(accountStateViewModel: AccountStateViewModel) { fun LoggedOffSetup(accountSessionManager: AccountSessionManager) {
Surface( Surface(
modifier = Modifier.fillMaxSize(), modifier = Modifier.fillMaxSize(),
color = MaterialTheme.colorScheme.background, color = MaterialTheme.colorScheme.background,
) { ) {
LoginOrSignupScreen(null, accountStateViewModel, isFirstLogin = true) LoginOrSignupScreen(null, accountSessionManager, isFirstLogin = true)
} }
} }
@Composable @Composable
fun LoggedInSetup( fun LoggedInSetup(
state: AccountState.LoggedIn, state: AccountState.LoggedIn,
accountStateViewModel: AccountStateViewModel, accountSessionManager: AccountSessionManager,
) { ) {
SetAccountCentricViewModelStore(state) { SetAccountCentricViewModelStore(state) {
LoggedInPage( LoggedInPage(
account = state.account, account = state.account,
route = state.route, route = state.route,
accountStateViewModel = accountStateViewModel, accountSessionManager = accountSessionManager,
) )
} }
} }
@@ -21,10 +21,7 @@
package com.vitorpamplona.amethyst.ui.screen package com.vitorpamplona.amethyst.ui.screen
import androidx.compose.runtime.Stable import androidx.compose.runtime.Stable
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import com.vitorpamplona.amethyst.AccountInfo import com.vitorpamplona.amethyst.AccountInfo
import com.vitorpamplona.amethyst.Amethyst
import com.vitorpamplona.amethyst.LocalPreferences import com.vitorpamplona.amethyst.LocalPreferences
import com.vitorpamplona.amethyst.model.AccountSettings import com.vitorpamplona.amethyst.model.AccountSettings
import com.vitorpamplona.amethyst.model.DefaultChannels import com.vitorpamplona.amethyst.model.DefaultChannels
@@ -33,14 +30,17 @@ import com.vitorpamplona.amethyst.model.DefaultIndexerRelayList
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.accountsCache.AccountCacheState
import com.vitorpamplona.amethyst.ui.navigation.routes.Route import com.vitorpamplona.amethyst.ui.navigation.routes.Route
import com.vitorpamplona.quartz.nip01Core.core.hexToByteArray import com.vitorpamplona.quartz.nip01Core.core.hexToByteArray
import com.vitorpamplona.quartz.nip01Core.core.toHexKey import com.vitorpamplona.quartz.nip01Core.core.toHexKey
import com.vitorpamplona.quartz.nip01Core.crypto.KeyPair import com.vitorpamplona.quartz.nip01Core.crypto.KeyPair
import com.vitorpamplona.quartz.nip01Core.metadata.MetadataEvent import com.vitorpamplona.quartz.nip01Core.metadata.MetadataEvent
import com.vitorpamplona.quartz.nip01Core.relay.client.INostrClient
import com.vitorpamplona.quartz.nip01Core.signers.NostrSignerSync import com.vitorpamplona.quartz.nip01Core.signers.NostrSignerSync
import com.vitorpamplona.quartz.nip02FollowList.ContactListEvent import com.vitorpamplona.quartz.nip02FollowList.ContactListEvent
import com.vitorpamplona.quartz.nip02FollowList.tags.ContactTag import com.vitorpamplona.quartz.nip02FollowList.tags.ContactTag
import com.vitorpamplona.quartz.nip05DnsIdentifiers.Nip05Client
import com.vitorpamplona.quartz.nip05DnsIdentifiers.Nip05Id import com.vitorpamplona.quartz.nip05DnsIdentifiers.Nip05Id
import com.vitorpamplona.quartz.nip06KeyDerivation.Nip06 import com.vitorpamplona.quartz.nip06KeyDerivation.Nip06
import com.vitorpamplona.quartz.nip17Dm.settings.ChatMessageRelayListEvent import com.vitorpamplona.quartz.nip17Dm.settings.ChatMessageRelayListEvent
@@ -63,9 +63,8 @@ import com.vitorpamplona.quartz.nip65RelayList.AdvertisedRelayListEvent
import com.vitorpamplona.quartz.utils.Hex import com.vitorpamplona.quartz.utils.Hex
import com.vitorpamplona.quartz.utils.Log import com.vitorpamplona.quartz.utils.Log
import kotlinx.coroutines.CancellationException import kotlinx.coroutines.CancellationException
import kotlinx.coroutines.DelicateCoroutinesApi import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.delay import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.flow.asStateFlow
@@ -77,21 +76,27 @@ import java.util.regex.Pattern
val EMAIL_PATTERN: Pattern = Pattern.compile(".+@.+\\.[a-z]+") val EMAIL_PATTERN: Pattern = Pattern.compile(".+@.+\\.[a-z]+")
@Stable @Stable
class AccountStateViewModel : ViewModel() { class AccountSessionManager(
val accountsCache: AccountCacheState,
val nip05Client: Nip05Client,
val client: INostrClient,
val localPreferences: LocalPreferences,
val scope: CoroutineScope,
) {
private val _accountContent = MutableStateFlow<AccountState>(AccountState.Loading) private val _accountContent = MutableStateFlow<AccountState>(AccountState.Loading)
val accountContent = _accountContent.asStateFlow() val accountContent = _accountContent.asStateFlow()
fun loginWithDefaultAccountIfLoggedOff() { fun loginWithDefaultAccountIfLoggedOff() {
// pulls account from storage. // pulls account from storage.
if (_accountContent.value !is AccountState.LoggedIn) { if (_accountContent.value !is AccountState.LoggedIn) {
viewModelScope.launch(Dispatchers.IO) { scope.launch(Dispatchers.IO) {
loginWithDefaultAccount() loginWithDefaultAccount()
} }
} }
} }
private suspend fun loginWithDefaultAccount(route: Route? = null) { private suspend fun loginWithDefaultAccount(route: Route? = null) {
val accountSettings = LocalPreferences.loadAccountConfigFromEncryptedStorage() val accountSettings = localPreferences.loadAccountConfigFromEncryptedStorage()
if (accountSettings != null) { if (accountSettings != null) {
startUI(accountSettings, route) startUI(accountSettings, route)
@@ -155,7 +160,7 @@ class AccountStateViewModel : ViewModel() {
) )
} }
LocalPreferences.setDefaultAccount(accountSettings) localPreferences.setDefaultAccount(accountSettings)
startUI(accountSettings) startUI(accountSettings)
} }
@@ -164,7 +169,7 @@ class AccountStateViewModel : ViewModel() {
accountSettings: AccountSettings, accountSettings: AccountSettings,
route: Route? = null, route: Route? = null,
) { ) {
val account = Amethyst.instance.accountsCache.loadAccount(accountSettings) val account = accountsCache.loadAccount(accountSettings)
_accountContent.update { _accountContent.update {
AccountState.LoggedIn(account, route) AccountState.LoggedIn(account, route)
} }
@@ -187,7 +192,7 @@ class AccountStateViewModel : ViewModel() {
packageName: String = "", packageName: String = "",
onError: (String?) -> Unit, onError: (String?) -> Unit,
) { ) {
viewModelScope.launch(Dispatchers.IO) { scope.launch(Dispatchers.IO) {
if (key.startsWith("ncryptsec")) { if (key.startsWith("ncryptsec")) {
val newKey = val newKey =
try { try {
@@ -214,7 +219,7 @@ class AccountStateViewModel : ViewModel() {
onError("Could not parse nip05 address: $nip05") onError("Could not parse nip05 address: $nip05")
} else { } else {
try { try {
val pubkeyInfo = Amethyst.instance.nip05Client.get(nip05) val pubkeyInfo = nip05Client.get(nip05)
if (pubkeyInfo == null) { if (pubkeyInfo == null) {
onError("User not found in the nip05 server: $nip05") onError("User not found in the nip05 server: $nip05")
} else { } else {
@@ -238,7 +243,7 @@ class AccountStateViewModel : ViewModel() {
packageName: String = "", packageName: String = "",
onError: (String) -> Unit, onError: (String) -> Unit,
) { ) {
viewModelScope.launch(Dispatchers.IO) { scope.launch(Dispatchers.IO) {
loginSync(key, transientAccount, loginWithExternalSigner, packageName, onError) loginSync(key, transientAccount, loginWithExternalSigner, packageName, onError)
} }
} }
@@ -263,7 +268,7 @@ class AccountStateViewModel : ViewModel() {
} }
fun newKey(name: String? = null) { fun newKey(name: String? = null) {
viewModelScope.launch(Dispatchers.IO) { scope.launch(Dispatchers.IO) {
if (_accountContent.value is AccountState.LoggedIn) { if (_accountContent.value is AccountState.LoggedIn) {
prepareLogoutOrSwitch() prepareLogoutOrSwitch()
} }
@@ -272,22 +277,21 @@ class AccountStateViewModel : ViewModel() {
val accountSettings = createNewAccount(name) val accountSettings = createNewAccount(name)
LocalPreferences.setDefaultAccount(accountSettings) localPreferences.setDefaultAccount(accountSettings)
startUI(accountSettings) startUI(accountSettings)
@OptIn(DelicateCoroutinesApi::class) scope.launch(Dispatchers.IO) {
GlobalScope.launch(Dispatchers.IO) {
delay(2000) // waits for the new user to connect to the new relays. delay(2000) // waits for the new user to connect to the new relays.
val toPost = accountSettings.backupNIP65RelayList?.writeRelaysNorm()?.toSet() ?: DefaultNIP65RelaySet val toPost = accountSettings.backupNIP65RelayList?.writeRelaysNorm()?.toSet() ?: DefaultNIP65RelaySet
accountSettings.backupUserMetadata?.let { Amethyst.instance.client.send(it, toPost) } accountSettings.backupUserMetadata?.let { client.send(it, toPost) }
accountSettings.backupContactList?.let { Amethyst.instance.client.send(it, toPost) } accountSettings.backupContactList?.let { client.send(it, toPost) }
accountSettings.backupNIP65RelayList?.let { Amethyst.instance.client.send(it, toPost) } accountSettings.backupNIP65RelayList?.let { client.send(it, toPost) }
accountSettings.backupDMRelayList?.let { Amethyst.instance.client.send(it, toPost) } accountSettings.backupDMRelayList?.let { client.send(it, toPost) }
accountSettings.backupSearchRelayList?.let { Amethyst.instance.client.send(it, toPost) } accountSettings.backupSearchRelayList?.let { client.send(it, toPost) }
accountSettings.backupIndexRelayList?.let { Amethyst.instance.client.send(it, toPost) } accountSettings.backupIndexRelayList?.let { client.send(it, toPost) }
} }
} }
} }
@@ -315,7 +319,7 @@ class AccountStateViewModel : ViewModel() {
} }
fun switchUser(accountInfo: AccountInfo) { fun switchUser(accountInfo: AccountInfo) {
viewModelScope.launch(Dispatchers.IO) { scope.launch(Dispatchers.IO) {
switchUserSync(accountInfo) switchUserSync(accountInfo)
} }
} }
@@ -324,8 +328,8 @@ class AccountStateViewModel : ViewModel() {
npub: String, npub: String,
route: Route, route: Route,
): Boolean { ): Boolean {
if (npub != LocalPreferences.currentAccount()) { if (npub != localPreferences.currentAccount()) {
val account = LocalPreferences.allSavedAccounts().firstOrNull { it.npub == npub } val account = localPreferences.allSavedAccounts().firstOrNull { it.npub == npub }
if (account != null) { if (account != null) {
switchUserSync(account, route) switchUserSync(account, route)
return true return true
@@ -339,7 +343,7 @@ class AccountStateViewModel : ViewModel() {
route: Route? = null, route: Route? = null,
) { ) {
prepareLogoutOrSwitch() prepareLogoutOrSwitch()
LocalPreferences.switchToAccount(accountInfo) localPreferences.switchToAccount(accountInfo)
loginWithDefaultAccount(route) loginWithDefaultAccount(route)
} }
@@ -357,17 +361,17 @@ class AccountStateViewModel : ViewModel() {
} }
fun logOff(accountInfo: AccountInfo) { fun logOff(accountInfo: AccountInfo) {
viewModelScope.launch(Dispatchers.IO) { scope.launch(Dispatchers.IO) {
if (accountInfo.npub == currentAccountNPub()) { if (accountInfo.npub == currentAccountNPub()) {
// log off and relogin with the 0 account // log off and relogin with the 0 account
prepareLogoutOrSwitch() prepareLogoutOrSwitch()
LocalPreferences.deleteAccount(accountInfo) localPreferences.deleteAccount(accountInfo)
Amethyst.instance.accountsCache.removeAccount(accountInfo.npub.bechToBytes().toHexKey()) accountsCache.removeAccount(accountInfo.npub.bechToBytes().toHexKey())
loginWithDefaultAccount() loginWithDefaultAccount()
} else { } else {
// delete without switching logins // delete without switching logins
LocalPreferences.deleteAccount(accountInfo) localPreferences.deleteAccount(accountInfo)
Amethyst.instance.accountsCache.removeAccount(accountInfo.npub.bechToBytes().toHexKey()) accountsCache.removeAccount(accountInfo.npub.bechToBytes().toHexKey())
} }
} }
} }
@@ -40,14 +40,14 @@ import androidx.compose.ui.platform.LocalConfiguration
import com.vitorpamplona.amethyst.ui.navigation.drawer.AccountSwitchBottomSheet import com.vitorpamplona.amethyst.ui.navigation.drawer.AccountSwitchBottomSheet
import com.vitorpamplona.amethyst.ui.navigation.drawer.DrawerContent import com.vitorpamplona.amethyst.ui.navigation.drawer.DrawerContent
import com.vitorpamplona.amethyst.ui.navigation.navs.INav import com.vitorpamplona.amethyst.ui.navigation.navs.INav
import com.vitorpamplona.amethyst.ui.screen.AccountStateViewModel import com.vitorpamplona.amethyst.ui.screen.AccountSessionManager
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
@OptIn(ExperimentalMaterial3Api::class) @OptIn(ExperimentalMaterial3Api::class)
@Composable @Composable
fun AccountSwitcherAndLeftDrawerLayout( fun AccountSwitcherAndLeftDrawerLayout(
accountViewModel: AccountViewModel, accountViewModel: AccountViewModel,
accountStateViewModel: AccountStateViewModel, accountSessionManager: AccountSessionManager,
nav: INav, nav: INav,
content: @Composable () -> Unit, content: @Composable () -> Unit,
) { ) {
@@ -106,7 +106,7 @@ fun AccountSwitcherAndLeftDrawerLayout(
) { ) {
AccountSwitchBottomSheet( AccountSwitchBottomSheet(
accountViewModel = accountViewModel, accountViewModel = accountViewModel,
accountStateViewModel = accountStateViewModel, accountSessionManager = accountSessionManager,
) )
} }
} }
@@ -46,7 +46,7 @@ import com.vitorpamplona.amethyst.service.relayClient.authCommand.compose.RelayA
import com.vitorpamplona.amethyst.service.relayClient.reqCommand.account.AccountFilterAssemblerSubscription import com.vitorpamplona.amethyst.service.relayClient.reqCommand.account.AccountFilterAssemblerSubscription
import com.vitorpamplona.amethyst.ui.navigation.AppNavigation import com.vitorpamplona.amethyst.ui.navigation.AppNavigation
import com.vitorpamplona.amethyst.ui.navigation.routes.Route import com.vitorpamplona.amethyst.ui.navigation.routes.Route
import com.vitorpamplona.amethyst.ui.screen.AccountStateViewModel import com.vitorpamplona.amethyst.ui.screen.AccountSessionManager
import com.vitorpamplona.amethyst.ui.screen.loggedIn.chats.rooms.datasource.ChatroomListFilterAssemblerSubscription import com.vitorpamplona.amethyst.ui.screen.loggedIn.chats.rooms.datasource.ChatroomListFilterAssemblerSubscription
import com.vitorpamplona.amethyst.ui.screen.loggedIn.discover.datasource.DiscoveryFilterAssemblerSubscription import com.vitorpamplona.amethyst.ui.screen.loggedIn.discover.datasource.DiscoveryFilterAssemblerSubscription
import com.vitorpamplona.amethyst.ui.screen.loggedIn.home.datasource.HomeFilterAssemblerSubscription import com.vitorpamplona.amethyst.ui.screen.loggedIn.home.datasource.HomeFilterAssemblerSubscription
@@ -59,7 +59,7 @@ import kotlinx.coroutines.launch
fun LoggedInPage( fun LoggedInPage(
account: Account, account: Account,
route: Route?, route: Route?,
accountStateViewModel: AccountStateViewModel, accountSessionManager: AccountSessionManager,
) { ) {
val accountViewModel: AccountViewModel = val accountViewModel: AccountViewModel =
viewModel( viewModel(
@@ -99,7 +99,7 @@ fun LoggedInPage(
AppNavigation( AppNavigation(
accountViewModel = accountViewModel, accountViewModel = accountViewModel,
accountStateViewModel = accountStateViewModel, accountSessionManager = accountSessionManager,
) )
} }
@@ -35,14 +35,14 @@ import androidx.compose.ui.window.Dialog
import androidx.compose.ui.window.DialogProperties import androidx.compose.ui.window.DialogProperties
import com.vitorpamplona.amethyst.R import com.vitorpamplona.amethyst.R
import com.vitorpamplona.amethyst.ui.note.ArrowBackIcon import com.vitorpamplona.amethyst.ui.note.ArrowBackIcon
import com.vitorpamplona.amethyst.ui.screen.AccountStateViewModel import com.vitorpamplona.amethyst.ui.screen.AccountSessionManager
import com.vitorpamplona.amethyst.ui.stringRes import com.vitorpamplona.amethyst.ui.stringRes
@OptIn(ExperimentalMaterial3Api::class) @OptIn(ExperimentalMaterial3Api::class)
@Composable @Composable
fun AddAccountDialog( fun AddAccountDialog(
newAccountKey: String?, newAccountKey: String?,
accountStateViewModel: AccountStateViewModel, accountSessionManager: AccountSessionManager,
onClose: () -> Unit, onClose: () -> Unit,
) { ) {
Dialog( Dialog(
@@ -51,7 +51,7 @@ fun AddAccountDialog(
) { ) {
Surface(modifier = Modifier.fillMaxSize()) { Surface(modifier = Modifier.fillMaxSize()) {
Box { Box {
LoginOrSignupScreen(newAccountKey, accountStateViewModel, isFirstLogin = false) LoginOrSignupScreen(newAccountKey, accountSessionManager, isFirstLogin = false)
TopAppBar( TopAppBar(
title = { title = {
Text(text = stringRes(R.string.account_switch_add_account_dialog_title)) Text(text = stringRes(R.string.account_switch_add_account_dialog_title))
@@ -26,14 +26,14 @@ import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue import androidx.compose.runtime.setValue
import com.vitorpamplona.amethyst.ui.screen.AccountStateViewModel import com.vitorpamplona.amethyst.ui.screen.AccountSessionManager
import com.vitorpamplona.amethyst.ui.screen.loggedOff.login.LoginPage import com.vitorpamplona.amethyst.ui.screen.loggedOff.login.LoginPage
import com.vitorpamplona.amethyst.ui.screen.loggedOff.signup.SignUpPage import com.vitorpamplona.amethyst.ui.screen.loggedOff.signup.SignUpPage
@Composable @Composable
fun LoginOrSignupScreen( fun LoginOrSignupScreen(
newAccountKey: String?, newAccountKey: String?,
accountStateViewModel: AccountStateViewModel, accountSessionManager: AccountSessionManager,
isFirstLogin: Boolean, isFirstLogin: Boolean,
) { ) {
var wantsNewUser by remember { var wantsNewUser by remember {
@@ -42,11 +42,11 @@ fun LoginOrSignupScreen(
Crossfade(wantsNewUser, label = "LoginOrSignupScreen") { Crossfade(wantsNewUser, label = "LoginOrSignupScreen") {
if (it) { if (it) {
SignUpPage(accountStateViewModel) { SignUpPage(accountSessionManager) {
wantsNewUser = false wantsNewUser = false
} }
} else { } else {
LoginPage(accountStateViewModel, isFirstLogin, newAccountKey) { LoginPage(accountSessionManager, isFirstLogin, newAccountKey) {
wantsNewUser = true wantsNewUser = true
} }
} }
@@ -78,7 +78,7 @@ import com.vitorpamplona.amethyst.Amethyst
import com.vitorpamplona.amethyst.R import com.vitorpamplona.amethyst.R
import com.vitorpamplona.amethyst.commons.hashtags.Amethyst import com.vitorpamplona.amethyst.commons.hashtags.Amethyst
import com.vitorpamplona.amethyst.commons.hashtags.CustomHashTagIcons import com.vitorpamplona.amethyst.commons.hashtags.CustomHashTagIcons
import com.vitorpamplona.amethyst.ui.screen.AccountStateViewModel import com.vitorpamplona.amethyst.ui.screen.AccountSessionManager
import com.vitorpamplona.amethyst.ui.screen.loggedOff.AcceptTerms import com.vitorpamplona.amethyst.ui.screen.loggedOff.AcceptTerms
import com.vitorpamplona.amethyst.ui.screen.loggedOff.TorSettingsSetup import com.vitorpamplona.amethyst.ui.screen.loggedOff.TorSettingsSetup
import com.vitorpamplona.amethyst.ui.stringRes import com.vitorpamplona.amethyst.ui.stringRes
@@ -87,6 +87,7 @@ import com.vitorpamplona.amethyst.ui.theme.Size20dp
import com.vitorpamplona.amethyst.ui.theme.Size40dp import com.vitorpamplona.amethyst.ui.theme.Size40dp
import com.vitorpamplona.amethyst.ui.theme.ThemeComparisonRow import com.vitorpamplona.amethyst.ui.theme.ThemeComparisonRow
import com.vitorpamplona.amethyst.ui.theme.placeholderText import com.vitorpamplona.amethyst.ui.theme.placeholderText
import com.vitorpamplona.amethyst.ui.tor.TorSettingsFlow
import com.vitorpamplona.quartz.nip55AndroidSigner.client.isExternalSignerInstalled import com.vitorpamplona.quartz.nip55AndroidSigner.client.isExternalSignerInstalled
import kotlinx.coroutines.delay import kotlinx.coroutines.delay
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
@@ -94,24 +95,26 @@ import kotlinx.coroutines.launch
@Preview(device = "spec:width=2160px,height=2340px,dpi=440") @Preview(device = "spec:width=2160px,height=2340px,dpi=440")
@Composable @Composable
fun LoginPagePreview() { fun LoginPagePreview() {
val accountViewModel: AccountStateViewModel = viewModel() val loginViewModel: LoginViewModel = viewModel()
loginViewModel.init(TorSettingsFlow())
ThemeComparisonRow( ThemeComparisonRow(
toPreview = { toPreview = {
LoginPage(accountViewModel, true) {} LoginPage(loginViewModel) {}
}, },
) )
} }
@Composable @Composable
fun LoginPage( fun LoginPage(
accountStateViewModel: AccountStateViewModel, accountSessionManager: AccountSessionManager,
isFirstLogin: Boolean, isFirstLogin: Boolean,
newAccountKey: String? = null, newAccountKey: String? = null,
onWantsToLogin: () -> Unit, onWantsToLogin: () -> Unit,
) { ) {
val loginViewModel: LoginViewModel = viewModel() val loginViewModel: LoginViewModel = viewModel()
loginViewModel.init(accountStateViewModel) loginViewModel.init(accountSessionManager)
loginViewModel.init(Amethyst.instance.torPrefs.value)
LaunchedEffect(loginViewModel, isFirstLogin, newAccountKey) { LaunchedEffect(loginViewModel, isFirstLogin, newAccountKey) {
loginViewModel.load(isFirstLogin, newAccountKey) loginViewModel.load(isFirstLogin, newAccountKey)
@@ -180,7 +183,7 @@ fun LoginPage(
Spacer(modifier = Modifier.height(10.dp)) Spacer(modifier = Modifier.height(10.dp))
TorSettingsSetup( TorSettingsSetup(
torSettingsFlow = Amethyst.instance.torPrefs.value, torSettingsFlow = loginViewModel.torSettings,
onError = { onError = {
scope.launch { scope.launch {
Toast Toast
@@ -28,11 +28,13 @@ import androidx.compose.runtime.setValue
import androidx.compose.ui.text.input.TextFieldValue import androidx.compose.ui.text.input.TextFieldValue
import androidx.lifecycle.ViewModel import androidx.lifecycle.ViewModel
import com.vitorpamplona.amethyst.R import com.vitorpamplona.amethyst.R
import com.vitorpamplona.amethyst.ui.screen.AccountStateViewModel import com.vitorpamplona.amethyst.ui.screen.AccountSessionManager
import com.vitorpamplona.amethyst.ui.tor.TorSettingsFlow
@Stable @Stable
class LoginViewModel : ViewModel() { class LoginViewModel : ViewModel() {
lateinit var accountStateViewModel: AccountStateViewModel lateinit var accountSessionManager: AccountSessionManager
lateinit var torSettings: TorSettingsFlow
val errorManager = LoginErrorManager() val errorManager = LoginErrorManager()
@@ -52,8 +54,12 @@ class LoginViewModel : ViewModel() {
var isFirstLogin by mutableStateOf(false) var isFirstLogin by mutableStateOf(false)
fun init(accountStateViewModel: AccountStateViewModel) { fun init(accountSessionManager: AccountSessionManager) {
this.accountStateViewModel = accountStateViewModel this.accountSessionManager = accountSessionManager
}
fun init(torSettings: TorSettingsFlow) {
this.torSettings = torSettings
} }
fun load( fun load(
@@ -132,7 +138,7 @@ class LoginViewModel : ViewModel() {
fun login() { fun login() {
if (checkCanLogin()) { if (checkCanLogin()) {
processingLogin = true processingLogin = true
accountStateViewModel.login( accountSessionManager.login(
key = key.text, key = key.text,
password = password.text, password = password.text,
transientAccount = isTemporary, transientAccount = isTemporary,
@@ -150,7 +156,7 @@ class LoginViewModel : ViewModel() {
fun loginWithExternalSigner(packageName: String) { fun loginWithExternalSigner(packageName: String) {
if (checkCanLogin()) { if (checkCanLogin()) {
processingLogin = true processingLogin = true
accountStateViewModel.login( accountSessionManager.login(
key = key.text, key = key.text,
transientAccount = isTemporary, transientAccount = isTemporary,
loginWithExternalSigner = true, loginWithExternalSigner = true,
@@ -33,7 +33,7 @@ import androidx.compose.runtime.setValue
import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalContext
import androidx.core.util.Consumer import androidx.core.util.Consumer
import com.vitorpamplona.amethyst.R import com.vitorpamplona.amethyst.R
import com.vitorpamplona.amethyst.ui.components.getActivity import com.vitorpamplona.amethyst.ui.components.getActivityOrNull
import com.vitorpamplona.amethyst.ui.stringRes import com.vitorpamplona.amethyst.ui.stringRes
import com.vitorpamplona.quartz.nip19Bech32.Nip19Parser import com.vitorpamplona.quartz.nip19Bech32.Nip19Parser
import kotlinx.coroutines.delay import kotlinx.coroutines.delay
@@ -42,7 +42,7 @@ import kotlinx.coroutines.launch
@Composable @Composable
fun OpenURIIfNotLoggedIn(onNewNIP19: suspend (String) -> Unit) { fun OpenURIIfNotLoggedIn(onNewNIP19: suspend (String) -> Unit) {
val context = LocalContext.current val context = LocalContext.current
val activity = context.getActivity() val activity = context.getActivityOrNull() ?: return
val scope = rememberCoroutineScope() val scope = rememberCoroutineScope()
var currentIntentNextPage by remember { var currentIntentNextPage by remember {
@@ -53,7 +53,7 @@ import com.vitorpamplona.amethyst.Amethyst
import com.vitorpamplona.amethyst.R import com.vitorpamplona.amethyst.R
import com.vitorpamplona.amethyst.commons.hashtags.Amethyst import com.vitorpamplona.amethyst.commons.hashtags.Amethyst
import com.vitorpamplona.amethyst.commons.hashtags.CustomHashTagIcons import com.vitorpamplona.amethyst.commons.hashtags.CustomHashTagIcons
import com.vitorpamplona.amethyst.ui.screen.AccountStateViewModel import com.vitorpamplona.amethyst.ui.screen.AccountSessionManager
import com.vitorpamplona.amethyst.ui.screen.loggedOff.AcceptTerms import com.vitorpamplona.amethyst.ui.screen.loggedOff.AcceptTerms
import com.vitorpamplona.amethyst.ui.screen.loggedOff.TorSettingsSetup import com.vitorpamplona.amethyst.ui.screen.loggedOff.TorSettingsSetup
import com.vitorpamplona.amethyst.ui.screen.loggedOff.login.LoginErrorManager import com.vitorpamplona.amethyst.ui.screen.loggedOff.login.LoginErrorManager
@@ -63,27 +63,30 @@ import com.vitorpamplona.amethyst.ui.theme.Size20dp
import com.vitorpamplona.amethyst.ui.theme.Size40dp import com.vitorpamplona.amethyst.ui.theme.Size40dp
import com.vitorpamplona.amethyst.ui.theme.ThemeComparisonRow import com.vitorpamplona.amethyst.ui.theme.ThemeComparisonRow
import com.vitorpamplona.amethyst.ui.theme.placeholderText import com.vitorpamplona.amethyst.ui.theme.placeholderText
import com.vitorpamplona.amethyst.ui.tor.TorSettingsFlow
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
@Preview(device = "spec:width=2160px,height=2340px,dpi=440") @Preview(device = "spec:width=2160px,height=2340px,dpi=440")
@Composable @Composable
fun SignUpPagePreview() { fun SignUpPagePreview() {
val accountViewModel: AccountStateViewModel = viewModel() val signUpViewModel: SignUpViewModel = viewModel()
signUpViewModel.init(TorSettingsFlow())
ThemeComparisonRow( ThemeComparisonRow(
toPreview = { toPreview = {
SignUpPage(accountViewModel) {} SignUpPage(signUpViewModel) {}
}, },
) )
} }
@Composable @Composable
fun SignUpPage( fun SignUpPage(
accountStateViewModel: AccountStateViewModel, accountSessionManager: AccountSessionManager,
onWantsToLogin: () -> Unit, onWantsToLogin: () -> Unit,
) { ) {
val signUpViewModel: SignUpViewModel = viewModel() val signUpViewModel: SignUpViewModel = viewModel()
signUpViewModel.init(accountStateViewModel) signUpViewModel.init(accountSessionManager)
signUpViewModel.init(Amethyst.instance.torPrefs.value)
SignUpPage(signUpViewModel, onWantsToLogin) SignUpPage(signUpViewModel, onWantsToLogin)
} }
@@ -171,7 +174,7 @@ fun SignUpPage(
Spacer(modifier = Modifier.height(10.dp)) Spacer(modifier = Modifier.height(10.dp))
TorSettingsSetup( TorSettingsSetup(
torSettingsFlow = Amethyst.instance.torPrefs.value, torSettingsFlow = signUpViewModel.torSettings,
onError = { onError = {
scope.launch { scope.launch {
Toast Toast
@@ -20,17 +20,21 @@
*/ */
package com.vitorpamplona.amethyst.ui.screen.loggedOff.signup package com.vitorpamplona.amethyst.ui.screen.loggedOff.signup
import androidx.compose.runtime.Stable
import androidx.compose.runtime.getValue import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.setValue import androidx.compose.runtime.setValue
import androidx.compose.ui.text.input.TextFieldValue import androidx.compose.ui.text.input.TextFieldValue
import androidx.lifecycle.ViewModel import androidx.lifecycle.ViewModel
import com.vitorpamplona.amethyst.R import com.vitorpamplona.amethyst.R
import com.vitorpamplona.amethyst.ui.screen.AccountStateViewModel import com.vitorpamplona.amethyst.ui.screen.AccountSessionManager
import com.vitorpamplona.amethyst.ui.screen.loggedOff.login.LoginErrorManager import com.vitorpamplona.amethyst.ui.screen.loggedOff.login.LoginErrorManager
import com.vitorpamplona.amethyst.ui.tor.TorSettingsFlow
@Stable
class SignUpViewModel : ViewModel() { class SignUpViewModel : ViewModel() {
lateinit var accountStateViewModel: AccountStateViewModel lateinit var accountSessionManager: AccountSessionManager
lateinit var torSettings: TorSettingsFlow
val errorManager = LoginErrorManager() val errorManager = LoginErrorManager()
@@ -39,8 +43,12 @@ class SignUpViewModel : ViewModel() {
var acceptedTerms by mutableStateOf(false) var acceptedTerms by mutableStateOf(false)
var termsAcceptanceIsRequiredError by mutableStateOf(false) var termsAcceptanceIsRequiredError by mutableStateOf(false)
fun init(accountStateViewModel: AccountStateViewModel) { fun init(accountSessionManager: AccountSessionManager) {
this.accountStateViewModel = accountStateViewModel this.accountSessionManager = accountSessionManager
}
fun init(torSettings: TorSettingsFlow) {
this.torSettings = torSettings
} }
fun updateDisplayName(value: TextFieldValue) { fun updateDisplayName(value: TextFieldValue) {
@@ -73,7 +81,7 @@ class SignUpViewModel : ViewModel() {
fun signup() { fun signup() {
if (checkCanSignup()) { if (checkCanSignup()) {
accountStateViewModel.newKey(displayName.text) accountSessionManager.newKey(displayName.text)
} }
} }
} }