diff --git a/src/main/kotlin/org/briarproject/briar/desktop/login/ErrorScreen.kt b/src/main/kotlin/org/briarproject/briar/desktop/login/ErrorScreen.kt new file mode 100644 index 0000000000000000000000000000000000000000..47dc19d0552bfb72dced3fe629f32f2e0e66bb74 --- /dev/null +++ b/src/main/kotlin/org/briarproject/briar/desktop/login/ErrorScreen.kt @@ -0,0 +1,81 @@ +package org.briarproject.briar.desktop.login + +import androidx.compose.foundation.layout.Arrangement.spacedBy +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.layout.widthIn +import androidx.compose.material.Button +import androidx.compose.material.Icon +import androidx.compose.material.MaterialTheme +import androidx.compose.material.Surface +import androidx.compose.material.Text +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.Error +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue +import androidx.compose.ui.Alignment.Companion.CenterHorizontally +import androidx.compose.ui.Modifier +import androidx.compose.ui.unit.dp +import org.briarproject.bramble.api.lifecycle.LifecycleManager.StartResult +import org.briarproject.bramble.api.lifecycle.LifecycleManager.StartResult.ALREADY_RUNNING +import org.briarproject.bramble.api.lifecycle.LifecycleManager.StartResult.CLOCK_ERROR +import org.briarproject.bramble.api.lifecycle.LifecycleManager.StartResult.DATA_TOO_NEW_ERROR +import org.briarproject.bramble.api.lifecycle.LifecycleManager.StartResult.DATA_TOO_OLD_ERROR +import org.briarproject.bramble.api.lifecycle.LifecycleManager.StartResult.DB_ERROR +import org.briarproject.bramble.api.lifecycle.LifecycleManager.StartResult.SERVICE_ERROR +import org.briarproject.bramble.api.lifecycle.LifecycleManager.StartResult.SUCCESS +import org.briarproject.briar.desktop.theme.Red500 +import org.briarproject.briar.desktop.utils.InternationalizationUtils.i18n +import org.briarproject.briar.desktop.utils.PreviewUtils.preview + +fun main() = preview { + var error by remember { mutableStateOf(SUCCESS) } + + Row(horizontalArrangement = spacedBy(8.dp)) { + for (e in StartResult.values().filterNot { it in listOf(SUCCESS, ALREADY_RUNNING) }) { + Button(onClick = { error = e }) { + Text(e.name.removeSuffix("_ERROR")) + } + } + } + + ErrorScreen(error) +} + +@Composable +fun ErrorScreen(error: StartResult) = Surface { + Column( + modifier = Modifier.fillMaxSize().padding(32.dp), + horizontalAlignment = CenterHorizontally, + verticalArrangement = spacedBy(32.dp) + ) { + Icon( + imageVector = Icons.Filled.Error, + contentDescription = i18n("error"), + modifier = Modifier.size(128.dp), + tint = Red500 + ) + + Text(i18n("sorry"), style = MaterialTheme.typography.h5) + + val text = when (error) { + CLOCK_ERROR -> i18n("startup.failed.clock_error") + DB_ERROR -> i18n("startup.failed.db_error") + DATA_TOO_OLD_ERROR -> i18n("startup.failed.data_too_old_error") + DATA_TOO_NEW_ERROR -> i18n("startup.failed.data_too_new_error") + SERVICE_ERROR -> i18n("startup.failed.service_error") + else -> "" + } + Text( + text = text, + style = MaterialTheme.typography.body1, + modifier = Modifier.widthIn(max = 400.dp) + ) + } +} diff --git a/src/main/kotlin/org/briarproject/briar/desktop/login/LoginScreen.kt b/src/main/kotlin/org/briarproject/briar/desktop/login/LoginScreen.kt index 3104a56e3a7aafb702d1ccf2ae6ef817e7b7d120..576026e5b15d7bf4da2dad084fcbdfaf5d8046c6 100644 --- a/src/main/kotlin/org/briarproject/briar/desktop/login/LoginScreen.kt +++ b/src/main/kotlin/org/briarproject/briar/desktop/login/LoginScreen.kt @@ -33,7 +33,7 @@ import org.briarproject.briar.desktop.viewmodel.viewModel @Composable fun LoginScreen( viewModel: LoginViewModel = viewModel(), -) = StartupScreen(i18n("startup.title.login")) { // todo: i18n +) = StartupScreen(i18n("startup.title.login")) { when (viewModel.state.value) { SIGNED_OUT -> FormScaffold( @@ -58,6 +58,7 @@ fun LoginScreen( if (viewModel.decryptionFailedError.value) { // todo: is this actually needed on Desktop? + // todo: use ErrorScreen to display this instead AlertDialog( onDismissRequest = viewModel::closeDecryptionFailedDialog, title = { Text(i18n("startup.error.decryption.title")) }, diff --git a/src/main/kotlin/org/briarproject/briar/desktop/login/LoginViewModel.kt b/src/main/kotlin/org/briarproject/briar/desktop/login/LoginViewModel.kt index a7887c4281d5da9f73eb1e39fe2df033fa80622a..418e0b5ba10a2e594d619989eda8d83ab0202a69 100644 --- a/src/main/kotlin/org/briarproject/briar/desktop/login/LoginViewModel.kt +++ b/src/main/kotlin/org/briarproject/briar/desktop/login/LoginViewModel.kt @@ -9,7 +9,6 @@ import org.briarproject.bramble.api.crypto.DecryptionResult.KEY_STRENGTHENER_ERR import org.briarproject.bramble.api.db.TransactionManager import org.briarproject.bramble.api.event.Event import org.briarproject.bramble.api.event.EventBus -import org.briarproject.bramble.api.lifecycle.IoExecutor import org.briarproject.bramble.api.lifecycle.LifecycleManager import org.briarproject.bramble.api.lifecycle.LifecycleManager.LifecycleState import org.briarproject.bramble.api.lifecycle.event.LifecycleEvent @@ -18,6 +17,7 @@ import org.briarproject.briar.desktop.login.LoginViewModel.State.MIGRATING import org.briarproject.briar.desktop.login.LoginViewModel.State.SIGNED_OUT import org.briarproject.briar.desktop.login.LoginViewModel.State.STARTED import org.briarproject.briar.desktop.login.LoginViewModel.State.STARTING +import org.briarproject.briar.desktop.login.StartupUtils.startBriarCore import org.briarproject.briar.desktop.threading.BriarExecutors import org.briarproject.briar.desktop.viewmodel.EventListenerDbViewModel import org.briarproject.briar.desktop.viewmodel.asState @@ -85,13 +85,6 @@ constructor( } } - @IoExecutor - private fun signedIn() { - val dbKey = accountManager.databaseKey ?: throw AssertionError() - lifecycleManager.startServices(dbKey) - lifecycleManager.waitForStartup() - } - fun deleteAccount() = briarExecutors.onIoThread { accountManager.deleteAccount() eventBus.broadcast(AccountDeletedEvent()) @@ -104,7 +97,7 @@ constructor( briarExecutors.onIoThread { try { accountManager.signIn(password.value) - signedIn() + startBriarCore(accountManager, lifecycleManager, eventBus) } catch (e: DecryptionException) { // failure, try again briarExecutors.onUiThread { diff --git a/src/main/kotlin/org/briarproject/briar/desktop/login/RegistrationViewModel.kt b/src/main/kotlin/org/briarproject/briar/desktop/login/RegistrationViewModel.kt index e83367336aeb4197f6cb2af176e3d8f130310991..a72d5a9e85072789659072f61dab1346cb59453d 100644 --- a/src/main/kotlin/org/briarproject/briar/desktop/login/RegistrationViewModel.kt +++ b/src/main/kotlin/org/briarproject/briar/desktop/login/RegistrationViewModel.kt @@ -6,13 +6,14 @@ import mu.KotlinLogging import org.briarproject.bramble.api.account.AccountManager import org.briarproject.bramble.api.crypto.PasswordStrengthEstimator import org.briarproject.bramble.api.crypto.PasswordStrengthEstimator.QUITE_WEAK +import org.briarproject.bramble.api.event.EventBus import org.briarproject.bramble.api.identity.AuthorConstants.MAX_AUTHOR_NAME_LENGTH -import org.briarproject.bramble.api.lifecycle.IoExecutor import org.briarproject.bramble.api.lifecycle.LifecycleManager import org.briarproject.briar.desktop.login.RegistrationViewModel.State.CREATED import org.briarproject.briar.desktop.login.RegistrationViewModel.State.CREATING import org.briarproject.briar.desktop.login.RegistrationViewModel.State.INSERT_NICKNAME import org.briarproject.briar.desktop.login.RegistrationViewModel.State.INSERT_PASSWORD +import org.briarproject.briar.desktop.login.StartupUtils.startBriarCore import org.briarproject.briar.desktop.threading.BriarExecutors import org.briarproject.briar.desktop.viewmodel.ViewModel import org.briarproject.briar.desktop.viewmodel.asState @@ -24,7 +25,8 @@ constructor( private val accountManager: AccountManager, private val briarExecutors: BriarExecutors, private val lifecycleManager: LifecycleManager, - private val passwordStrengthEstimator: PasswordStrengthEstimator + private val passwordStrengthEstimator: PasswordStrengthEstimator, + private val eventBus: EventBus, ) : ViewModel { companion object { @@ -87,13 +89,6 @@ constructor( _passwordConfirmation.value = passwordConfirmation } - @IoExecutor - private fun signedIn() { // todo: factor out? - val dbKey = accountManager.databaseKey ?: throw AssertionError() - lifecycleManager.startServices(dbKey) - lifecycleManager.waitForStartup() - } - fun goToPassword() { if (!buttonEnabled.value) return @@ -111,7 +106,7 @@ constructor( briarExecutors.onIoThread { if (accountManager.createAccount(_nickname.value, _password.value)) { LOG.info { "Created account" } - signedIn() + startBriarCore(accountManager, lifecycleManager, eventBus) _state.value = CREATED } else { LOG.warn { "Failed to create account" } diff --git a/src/main/kotlin/org/briarproject/briar/desktop/login/StartupFailedEvent.kt b/src/main/kotlin/org/briarproject/briar/desktop/login/StartupFailedEvent.kt new file mode 100644 index 0000000000000000000000000000000000000000..1976843ebced37832cfdc103cbb40cd376c4af87 --- /dev/null +++ b/src/main/kotlin/org/briarproject/briar/desktop/login/StartupFailedEvent.kt @@ -0,0 +1,6 @@ +package org.briarproject.briar.desktop.login + +import org.briarproject.bramble.api.event.Event +import org.briarproject.bramble.api.lifecycle.LifecycleManager + +class StartupFailedEvent(val result: LifecycleManager.StartResult) : Event() diff --git a/src/main/kotlin/org/briarproject/briar/desktop/login/StartupUtils.kt b/src/main/kotlin/org/briarproject/briar/desktop/login/StartupUtils.kt new file mode 100644 index 0000000000000000000000000000000000000000..cbb1dc1460db44c3121e095980dc07452a4f14a2 --- /dev/null +++ b/src/main/kotlin/org/briarproject/briar/desktop/login/StartupUtils.kt @@ -0,0 +1,28 @@ +package org.briarproject.briar.desktop.login + +import mu.KotlinLogging +import org.briarproject.bramble.api.account.AccountManager +import org.briarproject.bramble.api.event.EventBus +import org.briarproject.bramble.api.lifecycle.IoExecutor +import org.briarproject.bramble.api.lifecycle.LifecycleManager +import org.briarproject.bramble.api.lifecycle.LifecycleManager.StartResult.ALREADY_RUNNING +import org.briarproject.bramble.api.lifecycle.LifecycleManager.StartResult.SUCCESS + +object StartupUtils { + + private val LOG = KotlinLogging.logger {} + + @IoExecutor + fun startBriarCore(accountManager: AccountManager, lifecycleManager: LifecycleManager, eventBus: EventBus) { + val dbKey = accountManager.databaseKey ?: throw AssertionError() + val result = lifecycleManager.startServices(dbKey) + when (result) { + SUCCESS -> lifecycleManager.waitForStartup() + ALREADY_RUNNING -> LOG.info { "Already running" } + else -> { + LOG.warn { "Startup failed: $result" } + eventBus.broadcast(StartupFailedEvent(result)) + } + } + } +} diff --git a/src/main/kotlin/org/briarproject/briar/desktop/ui/BriarUi.kt b/src/main/kotlin/org/briarproject/briar/desktop/ui/BriarUi.kt index 445e1a526a86395ff7e3b29a59414c9c9061e4e7..2ec6a6949cf298ecc32b6f4ada4250bcfb9cf364 100644 --- a/src/main/kotlin/org/briarproject/briar/desktop/ui/BriarUi.kt +++ b/src/main/kotlin/org/briarproject/briar/desktop/ui/BriarUi.kt @@ -15,16 +15,20 @@ import org.briarproject.bramble.api.event.EventBus import org.briarproject.bramble.api.event.EventListener import org.briarproject.bramble.api.lifecycle.LifecycleManager import org.briarproject.bramble.api.lifecycle.LifecycleManager.LifecycleState.RUNNING +import org.briarproject.bramble.api.lifecycle.LifecycleManager.StartResult import org.briarproject.bramble.api.lifecycle.event.LifecycleEvent import org.briarproject.briar.desktop.DesktopFeatureFlags import org.briarproject.briar.desktop.login.AccountDeletedEvent +import org.briarproject.briar.desktop.login.ErrorScreen import org.briarproject.briar.desktop.login.LoginScreen import org.briarproject.briar.desktop.login.RegistrationScreen +import org.briarproject.briar.desktop.login.StartupFailedEvent import org.briarproject.briar.desktop.settings.SettingsViewModel import org.briarproject.briar.desktop.theme.BriarTheme import org.briarproject.briar.desktop.ui.Screen.LOGIN import org.briarproject.briar.desktop.ui.Screen.MAIN import org.briarproject.briar.desktop.ui.Screen.REGISTRATION +import org.briarproject.briar.desktop.ui.Screen.STARTUP_ERROR import org.briarproject.briar.desktop.utils.InternationalizationUtils.i18n import org.briarproject.briar.desktop.viewmodel.ViewModelProvider import org.briarproject.briar.desktop.viewmodel.viewModel @@ -33,10 +37,11 @@ import javax.annotation.concurrent.Immutable import javax.inject.Inject import javax.inject.Singleton -enum class Screen { - REGISTRATION, - LOGIN, - MAIN +sealed interface Screen { + object REGISTRATION : Screen + object LOGIN : Screen + object MAIN : Screen + class STARTUP_ERROR(val error: StartResult) : Screen } interface BriarUi { @@ -71,10 +76,13 @@ constructor( ) override fun eventOccurred(e: Event?) { - if (e is LifecycleEvent && e.lifecycleState == RUNNING) { - screenState = MAIN - } else if (e is AccountDeletedEvent) { - screenState = REGISTRATION + when { + e is LifecycleEvent && e.lifecycleState == RUNNING -> + screenState = MAIN + e is AccountDeletedEvent -> + screenState = REGISTRATION + e is StartupFailedEvent -> + screenState = STARTUP_ERROR(e.result) } } @@ -104,10 +112,11 @@ constructor( ) { val settingsViewModel: SettingsViewModel = viewModel() BriarTheme(isDarkTheme = settingsViewModel.isDarkMode.value) { - when (screenState) { - REGISTRATION -> RegistrationScreen() - LOGIN -> LoginScreen() - MAIN -> MainScreen(settingsViewModel) + when (val state = screenState) { + is REGISTRATION -> RegistrationScreen() + is LOGIN -> LoginScreen() + is STARTUP_ERROR -> ErrorScreen(state.error) + is MAIN -> MainScreen(settingsViewModel) } } } diff --git a/src/main/resources/strings/BriarDesktop.properties b/src/main/resources/strings/BriarDesktop.properties index c43b7a2d57678d0c6cf4029f66dea15c4751807a..39f99f3c2461a66dd78c12f2794e95b59fc3ce9c 100644 --- a/src/main/resources/strings/BriarDesktop.properties +++ b/src/main/resources/strings/BriarDesktop.properties @@ -116,6 +116,8 @@ decline=Decline back=Back next=Next open=Open +sorry=Sorry +error=Error unsupported_feature=Unfortunately, this feature is not yet supported by Briar Desktop. # Startup screen @@ -137,6 +139,11 @@ startup.error.decryption.text=Briar cannot check your password. Please try reboo startup.password_forgotten.button=I have forgotten my password startup.password_forgotten.title=Lost Password startup.password_forgotten.text=Your Briar account is stored encrypted on your device, not in the cloud, so we can't reset your password. Would you like to delete your account and start again?\n\nCaution: Your identities, contacts and messages will be permanently lost. +startup.failed.clock_error=Briar was unable to start because your device's clock is wrong.\n\nPlease set your device's clock to the right time and try again. +startup.failed.db_error=Briar was unable to open the database containing your account, your contacts and your messages.\n\nPlease upgrade to the latest version of the app and try again, or set up a new account by choosing 'I have forgotten my password' at the password prompt. +startup.failed.data_too_old_error=Your account was created with an old version of this app and cannot be opened with this version.\n\nYou must either reinstall the old version or set up a new account by choosing 'I have forgotten my password' at the password prompt. +startup.failed.data_too_new_error=Your account was created with a newer version of this app and cannot be opened with this version.\n\nPlease upgrade to the latest version and try again. +startup.failed.service_error=Briar was unable to start a required component.\n\nPlease upgrade to the latest version of the app and try again. startup.database.creating=Creating Account... startup.database.opening=Decrypting Database... startup.database.migrating=Upgrading Database...