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