From b8a653a407a1f483d9d3b59e583f6d60a021ecf8 Mon Sep 17 00:00:00 2001
From: ialokim <ialokim@mailbox.org>
Date: Fri, 14 Jan 2022 18:13:51 +0100
Subject: [PATCH] address review comments

---
 .../compose/material/OutlinedTextFieldExt.kt  |  3 +-
 .../briar/desktop/login/ErrorScreen.kt        | 13 ++---
 ...rrorViewHolder.kt => ErrorSubViewModel.kt} |  6 +--
 .../briar/desktop/login/FormScaffold.kt       | 39 ++++++++++++++
 .../briar/desktop/login/LoadingView.kt        | 23 +++++++++
 .../briar/desktop/login/LoginScreen.kt        | 21 ++++----
 ...oginViewHolder.kt => LoginSubViewModel.kt} | 14 ++---
 .../briar/desktop/login/RegistrationScreen.kt | 15 ++----
 ...wHolder.kt => RegistrationSubViewModel.kt} | 14 ++---
 .../briar/desktop/login/StartupScreen.kt      | 51 ++-----------------
 .../briar/desktop/login/StartupViewModel.kt   | 31 +++++------
 .../briar/desktop/login/StrengthMeter.kt      |  3 +-
 .../briar/desktop/settings/SettingsScreen.kt  |  1 -
 .../briar/desktop/ui/Constants.kt             |  1 +
 .../briar/desktop/utils/PreviewUtils.kt       | 14 +++++
 .../resources/strings/BriarDesktop.properties |  2 +-
 16 files changed, 140 insertions(+), 111 deletions(-)
 rename src/main/kotlin/org/briarproject/briar/desktop/login/{ErrorViewHolder.kt => ErrorSubViewModel.kt} (59%)
 create mode 100644 src/main/kotlin/org/briarproject/briar/desktop/login/FormScaffold.kt
 create mode 100644 src/main/kotlin/org/briarproject/briar/desktop/login/LoadingView.kt
 rename src/main/kotlin/org/briarproject/briar/desktop/login/{LoginViewHolder.kt => LoginSubViewModel.kt} (87%)
 rename src/main/kotlin/org/briarproject/briar/desktop/login/{RegistrationViewHolder.kt => RegistrationSubViewModel.kt} (87%)

diff --git a/src/main/kotlin/androidx/compose/material/OutlinedTextFieldExt.kt b/src/main/kotlin/androidx/compose/material/OutlinedTextFieldExt.kt
index 9f4c7373ee..e2b6fce9c9 100644
--- a/src/main/kotlin/androidx/compose/material/OutlinedTextFieldExt.kt
+++ b/src/main/kotlin/androidx/compose/material/OutlinedTextFieldExt.kt
@@ -108,7 +108,8 @@ fun OutlinedTextField(
             colors = colors
         )
         val message = if (showError && errorMessage != null) errorMessage else helperMessage ?: ""
-        val color = if (showError) MaterialTheme.colors.error else LocalTextStyle.current.color.copy(alpha = ContentAlpha.medium)
+        val color =
+            if (showError) MaterialTheme.colors.error else LocalTextStyle.current.color.copy(alpha = ContentAlpha.medium)
         Text(
             text = message,
             style = TextStyle(
diff --git a/src/main/kotlin/org/briarproject/briar/desktop/login/ErrorScreen.kt b/src/main/kotlin/org/briarproject/briar/desktop/login/ErrorScreen.kt
index 3744c8608d..00bfb43fa6 100644
--- a/src/main/kotlin/org/briarproject/briar/desktop/login/ErrorScreen.kt
+++ b/src/main/kotlin/org/briarproject/briar/desktop/login/ErrorScreen.kt
@@ -33,14 +33,15 @@ import org.briarproject.bramble.api.lifecycle.LifecycleManager.StartResult.DB_ER
 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.ui.Constants.STARTUP_FIELDS_WIDTH
 import org.briarproject.briar.desktop.utils.InternationalizationUtils.i18n
 import org.briarproject.briar.desktop.utils.PreviewUtils.preview
 
 fun main() = preview {
-    var error: ErrorViewHolder.Error by remember { mutableStateOf(RegistrationViewHolder.RegistrationError) }
+    var error: ErrorSubViewModel.Error by remember { mutableStateOf(RegistrationSubViewModel.RegistrationError) }
 
     Row(horizontalArrangement = spacedBy(8.dp)) {
-        Button(onClick = { error = RegistrationViewHolder.RegistrationError }) {
+        Button(onClick = { error = RegistrationSubViewModel.RegistrationError }) {
             Text("Registration")
         }
         for (e in StartResult.values().filterNot { it in listOf(SUCCESS, ALREADY_RUNNING) }) {
@@ -54,12 +55,12 @@ fun main() = preview {
 }
 
 @Composable
-fun ErrorScreen(viewHolder: ErrorViewHolder) =
+fun ErrorScreen(viewHolder: ErrorSubViewModel) =
     ErrorScreen(viewHolder.error, viewHolder.onBackButton)
 
 @Composable
 fun ErrorScreen(
-    error: ErrorViewHolder.Error,
+    error: ErrorSubViewModel.Error,
     onBackButton: () -> Unit,
 ) = Surface {
     IconButton(onClick = onBackButton) {
@@ -81,7 +82,7 @@ fun ErrorScreen(
         Text(i18n("sorry"), style = MaterialTheme.typography.h5)
 
         val text = when (error) {
-            is RegistrationViewHolder.RegistrationError -> i18n("startup.failed.registration")
+            is RegistrationSubViewModel.RegistrationError -> i18n("startup.failed.registration")
             is StartupViewModel.StartingError -> {
                 when (error.error) {
                     CLOCK_ERROR -> i18n("startup.failed.clock_error")
@@ -96,7 +97,7 @@ fun ErrorScreen(
         Text(
             text = text,
             style = MaterialTheme.typography.body1,
-            modifier = Modifier.widthIn(max = 400.dp)
+            modifier = Modifier.widthIn(max = STARTUP_FIELDS_WIDTH)
         )
     }
 }
diff --git a/src/main/kotlin/org/briarproject/briar/desktop/login/ErrorViewHolder.kt b/src/main/kotlin/org/briarproject/briar/desktop/login/ErrorSubViewModel.kt
similarity index 59%
rename from src/main/kotlin/org/briarproject/briar/desktop/login/ErrorViewHolder.kt
rename to src/main/kotlin/org/briarproject/briar/desktop/login/ErrorSubViewModel.kt
index 8d540501ab..498efb6e38 100644
--- a/src/main/kotlin/org/briarproject/briar/desktop/login/ErrorViewHolder.kt
+++ b/src/main/kotlin/org/briarproject/briar/desktop/login/ErrorSubViewModel.kt
@@ -1,11 +1,9 @@
 package org.briarproject.briar.desktop.login
 
-import org.briarproject.bramble.api.lifecycle.LifecycleManager
-
-class ErrorViewHolder(
+class ErrorSubViewModel(
     private val viewModel: StartupViewModel,
     val error: Error,
     val onBackButton: () -> Unit,
-) : StartupViewModel.ViewHolder {
+) : StartupViewModel.SubViewModel {
     sealed interface Error
 }
diff --git a/src/main/kotlin/org/briarproject/briar/desktop/login/FormScaffold.kt b/src/main/kotlin/org/briarproject/briar/desktop/login/FormScaffold.kt
new file mode 100644
index 0000000000..95e8e4e664
--- /dev/null
+++ b/src/main/kotlin/org/briarproject/briar/desktop/login/FormScaffold.kt
@@ -0,0 +1,39 @@
+package org.briarproject.briar.desktop.login
+
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.Spacer
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.requiredWidth
+import androidx.compose.foundation.layout.requiredWidthIn
+import androidx.compose.material.Button
+import androidx.compose.material.MaterialTheme
+import androidx.compose.material.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.graphics.Color
+import org.briarproject.briar.desktop.ui.Constants.STARTUP_FIELDS_WIDTH
+import java.util.Locale
+
+@Composable
+fun FormScaffold(
+    explanationText: String?,
+    buttonText: String,
+    buttonClick: () -> Unit,
+    buttonEnabled: Boolean,
+    content: @Composable () -> Unit,
+) = Column(
+    modifier = Modifier.requiredWidthIn(max = STARTUP_FIELDS_WIDTH),
+    horizontalAlignment = Alignment.CenterHorizontally
+) {
+    if (explanationText != null) {
+        Spacer(Modifier.weight(0.5f))
+        Text(explanationText, style = MaterialTheme.typography.body2, modifier = Modifier.requiredWidth(STARTUP_FIELDS_WIDTH))
+        Spacer(Modifier.weight(0.5f))
+    } else Spacer(Modifier.weight(1.0f))
+    content()
+    Spacer(Modifier.weight(1.0f))
+    Button(onClick = buttonClick, enabled = buttonEnabled, modifier = Modifier.fillMaxWidth()) {
+        Text(buttonText.uppercase(Locale.getDefault()), color = Color.Black)
+    }
+}
diff --git a/src/main/kotlin/org/briarproject/briar/desktop/login/LoadingView.kt b/src/main/kotlin/org/briarproject/briar/desktop/login/LoadingView.kt
new file mode 100644
index 0000000000..a8cf0dc22f
--- /dev/null
+++ b/src/main/kotlin/org/briarproject/briar/desktop/login/LoadingView.kt
@@ -0,0 +1,23 @@
+package org.briarproject.briar.desktop.login
+
+import androidx.compose.foundation.layout.Arrangement
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.fillMaxHeight
+import androidx.compose.foundation.layout.padding
+import androidx.compose.material.CircularProgressIndicator
+import androidx.compose.material.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.unit.dp
+
+@Composable
+fun LoadingView(text: String) =
+    Column(
+        horizontalAlignment = Alignment.CenterHorizontally,
+        verticalArrangement = Arrangement.Center,
+        modifier = Modifier.fillMaxHeight()
+    ) {
+        CircularProgressIndicator(Modifier.padding(16.dp))
+        Text(text)
+    }
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 008ffbe444..5a44006402 100644
--- a/src/main/kotlin/org/briarproject/briar/desktop/login/LoginScreen.kt
+++ b/src/main/kotlin/org/briarproject/briar/desktop/login/LoginScreen.kt
@@ -15,23 +15,21 @@ import androidx.compose.runtime.remember
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.focus.FocusRequester
 import androidx.compose.ui.focus.focusRequester
-import androidx.compose.ui.graphics.Color
-import androidx.compose.ui.text.TextStyle
 import androidx.compose.ui.text.input.ImeAction
 import androidx.compose.ui.text.input.KeyboardType
 import androidx.compose.ui.text.input.PasswordVisualTransformation
 import androidx.compose.ui.unit.dp
-import org.briarproject.briar.desktop.login.LoginViewHolder.State.COMPACTING
-import org.briarproject.briar.desktop.login.LoginViewHolder.State.MIGRATING
-import org.briarproject.briar.desktop.login.LoginViewHolder.State.SIGNED_OUT
-import org.briarproject.briar.desktop.login.LoginViewHolder.State.STARTED
-import org.briarproject.briar.desktop.login.LoginViewHolder.State.STARTING
+import org.briarproject.briar.desktop.login.LoginSubViewModel.State.COMPACTING
+import org.briarproject.briar.desktop.login.LoginSubViewModel.State.MIGRATING
+import org.briarproject.briar.desktop.login.LoginSubViewModel.State.SIGNED_OUT
+import org.briarproject.briar.desktop.login.LoginSubViewModel.State.STARTED
+import org.briarproject.briar.desktop.login.LoginSubViewModel.State.STARTING
 import org.briarproject.briar.desktop.utils.InternationalizationUtils.i18n
 
 @OptIn(ExperimentalMaterialApi::class)
 @Composable
 fun LoginScreen(
-    viewHolder: LoginViewHolder,
+    viewHolder: LoginSubViewModel,
 ) = StartupScreenScaffold(i18n("startup.title.login")) {
     when (viewHolder.state.value) {
         SIGNED_OUT ->
@@ -56,8 +54,10 @@ fun LoginScreen(
     }
 
     if (viewHolder.decryptionFailedError.value) {
-        // todo: is this actually needed on Desktop?
-        // todo: use ErrorScreen to display this instead?
+        // todo: this should never be triggered,
+        //  since we don't use any keyStrengthener for now
+        //  when adding this, we could think about showing
+        //  a proper error screen instead
         AlertDialog(
             onDismissRequest = viewHolder::closeDecryptionFailedDialog,
             title = { Text(i18n("startup.error.decryption.title")) },
@@ -90,7 +90,6 @@ fun LoginForm(
         singleLine = true,
         isError = passwordInvalidError,
         errorMessage = i18n("startup.error.password_wrong"),
-        textStyle = TextStyle(color = Color.White),
         visualTransformation = PasswordVisualTransformation(),
         keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Password, imeAction = ImeAction.Next),
         modifier = Modifier.fillMaxWidth().focusRequester(initialFocusRequester),
diff --git a/src/main/kotlin/org/briarproject/briar/desktop/login/LoginViewHolder.kt b/src/main/kotlin/org/briarproject/briar/desktop/login/LoginSubViewModel.kt
similarity index 87%
rename from src/main/kotlin/org/briarproject/briar/desktop/login/LoginViewHolder.kt
rename to src/main/kotlin/org/briarproject/briar/desktop/login/LoginSubViewModel.kt
index 8ad23db1f6..74b96432d7 100644
--- a/src/main/kotlin/org/briarproject/briar/desktop/login/LoginViewHolder.kt
+++ b/src/main/kotlin/org/briarproject/briar/desktop/login/LoginSubViewModel.kt
@@ -7,20 +7,20 @@ import org.briarproject.bramble.api.crypto.DecryptionException
 import org.briarproject.bramble.api.crypto.DecryptionResult.INVALID_PASSWORD
 import org.briarproject.bramble.api.crypto.DecryptionResult.KEY_STRENGTHENER_ERROR
 import org.briarproject.bramble.api.lifecycle.LifecycleManager.LifecycleState
-import org.briarproject.briar.desktop.login.LoginViewHolder.State.COMPACTING
-import org.briarproject.briar.desktop.login.LoginViewHolder.State.MIGRATING
-import org.briarproject.briar.desktop.login.LoginViewHolder.State.SIGNED_OUT
-import org.briarproject.briar.desktop.login.LoginViewHolder.State.STARTED
-import org.briarproject.briar.desktop.login.LoginViewHolder.State.STARTING
+import org.briarproject.briar.desktop.login.LoginSubViewModel.State.COMPACTING
+import org.briarproject.briar.desktop.login.LoginSubViewModel.State.MIGRATING
+import org.briarproject.briar.desktop.login.LoginSubViewModel.State.SIGNED_OUT
+import org.briarproject.briar.desktop.login.LoginSubViewModel.State.STARTED
+import org.briarproject.briar.desktop.login.LoginSubViewModel.State.STARTING
 import org.briarproject.briar.desktop.threading.BriarExecutors
 import org.briarproject.briar.desktop.viewmodel.asState
 
-class LoginViewHolder(
+class LoginSubViewModel(
     private val viewModel: StartupViewModel,
     private val accountManager: AccountManager,
     private val briarExecutors: BriarExecutors,
     initialLifecycleState: LifecycleState,
-) : StartupViewModel.ViewHolder {
+) : StartupViewModel.SubViewModel {
 
     enum class State {
         SIGNED_OUT, STARTING, MIGRATING, COMPACTING, STARTED
diff --git a/src/main/kotlin/org/briarproject/briar/desktop/login/RegistrationScreen.kt b/src/main/kotlin/org/briarproject/briar/desktop/login/RegistrationScreen.kt
index b981d77398..2e534ec07a 100644
--- a/src/main/kotlin/org/briarproject/briar/desktop/login/RegistrationScreen.kt
+++ b/src/main/kotlin/org/briarproject/briar/desktop/login/RegistrationScreen.kt
@@ -16,22 +16,20 @@ import androidx.compose.ui.Modifier
 import androidx.compose.ui.focus.FocusDirection
 import androidx.compose.ui.focus.FocusRequester
 import androidx.compose.ui.focus.focusRequester
-import androidx.compose.ui.graphics.Color
 import androidx.compose.ui.platform.LocalFocusManager
-import androidx.compose.ui.text.TextStyle
 import androidx.compose.ui.text.input.ImeAction
 import androidx.compose.ui.text.input.KeyboardType
 import androidx.compose.ui.text.input.PasswordVisualTransformation
 import androidx.compose.ui.unit.dp
-import org.briarproject.briar.desktop.login.RegistrationViewHolder.State.CREATED
-import org.briarproject.briar.desktop.login.RegistrationViewHolder.State.CREATING
-import org.briarproject.briar.desktop.login.RegistrationViewHolder.State.INSERT_NICKNAME
-import org.briarproject.briar.desktop.login.RegistrationViewHolder.State.INSERT_PASSWORD
+import org.briarproject.briar.desktop.login.RegistrationSubViewModel.State.CREATED
+import org.briarproject.briar.desktop.login.RegistrationSubViewModel.State.CREATING
+import org.briarproject.briar.desktop.login.RegistrationSubViewModel.State.INSERT_NICKNAME
+import org.briarproject.briar.desktop.login.RegistrationSubViewModel.State.INSERT_PASSWORD
 import org.briarproject.briar.desktop.utils.InternationalizationUtils.i18n
 
 @Composable
 fun RegistrationScreen(
-    viewHolder: RegistrationViewHolder,
+    viewHolder: RegistrationSubViewModel,
 ) = StartupScreenScaffold(
     title = i18n("startup.title.registration"),
     showBackButton = viewHolder.showBackButton.value,
@@ -91,7 +89,6 @@ fun NicknameForm(
         singleLine = true,
         isError = nicknameTooLongError,
         errorMessage = i18n("startup.error.name_too_long"),
-        textStyle = TextStyle(color = Color.White),
         keyboardOptions = KeyboardOptions(imeAction = ImeAction.Next),
         modifier = Modifier.fillMaxWidth().focusRequester(initialFocusRequester),
         onEnter = onEnter
@@ -131,7 +128,6 @@ fun PasswordForm(
         isError = passwordTooWeakError,
         showErrorWhen = AFTER_FOCUS_LOST_ONCE,
         errorMessage = i18n("startup.error.password_too_weak"),
-        textStyle = TextStyle(color = Color.White),
         visualTransformation = PasswordVisualTransformation(),
         keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Password, imeAction = ImeAction.Next),
         modifier = Modifier.fillMaxWidth().focusRequester(initialFocusRequester),
@@ -145,7 +141,6 @@ fun PasswordForm(
         isError = passwordsDontMatchError,
         showErrorWhen = AFTER_FIRST_FOCUSSED,
         errorMessage = i18n("startup.error.passwords_not_match"),
-        textStyle = TextStyle(color = Color.White),
         visualTransformation = PasswordVisualTransformation(),
         keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Password, imeAction = ImeAction.Done),
         modifier = Modifier.fillMaxWidth(),
diff --git a/src/main/kotlin/org/briarproject/briar/desktop/login/RegistrationViewHolder.kt b/src/main/kotlin/org/briarproject/briar/desktop/login/RegistrationSubViewModel.kt
similarity index 87%
rename from src/main/kotlin/org/briarproject/briar/desktop/login/RegistrationViewHolder.kt
rename to src/main/kotlin/org/briarproject/briar/desktop/login/RegistrationSubViewModel.kt
index ca2a309376..990c2ca67f 100644
--- a/src/main/kotlin/org/briarproject/briar/desktop/login/RegistrationViewHolder.kt
+++ b/src/main/kotlin/org/briarproject/briar/desktop/login/RegistrationSubViewModel.kt
@@ -7,24 +7,24 @@ 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.identity.AuthorConstants.MAX_AUTHOR_NAME_LENGTH
-import org.briarproject.briar.desktop.login.RegistrationViewHolder.State.CREATING
-import org.briarproject.briar.desktop.login.RegistrationViewHolder.State.INSERT_NICKNAME
-import org.briarproject.briar.desktop.login.RegistrationViewHolder.State.INSERT_PASSWORD
+import org.briarproject.briar.desktop.login.RegistrationSubViewModel.State.CREATING
+import org.briarproject.briar.desktop.login.RegistrationSubViewModel.State.INSERT_NICKNAME
+import org.briarproject.briar.desktop.login.RegistrationSubViewModel.State.INSERT_PASSWORD
 import org.briarproject.briar.desktop.threading.BriarExecutors
 import org.briarproject.briar.desktop.viewmodel.asState
 
-class RegistrationViewHolder(
+class RegistrationSubViewModel(
     private val viewModel: StartupViewModel,
     private val accountManager: AccountManager,
     private val briarExecutors: BriarExecutors,
     private val passwordStrengthEstimator: PasswordStrengthEstimator,
-) : StartupViewModel.ViewHolder {
+) : StartupViewModel.SubViewModel {
 
     companion object {
         private val LOG = KotlinLogging.logger {}
     }
 
-    object RegistrationError : ErrorViewHolder.Error
+    object RegistrationError : ErrorSubViewModel.Error
 
     enum class State {
         INSERT_NICKNAME, INSERT_PASSWORD, CREATING, CREATED
@@ -59,7 +59,7 @@ class RegistrationViewHolder(
     val buttonEnabled = derivedStateOf {
         when (_state.value) {
             INSERT_NICKNAME ->
-                nickname.value.isNotEmpty() && !nicknameTooLongError.value
+                nickname.value.isNotBlank() && !nicknameTooLongError.value
             INSERT_PASSWORD ->
                 password.value.isNotEmpty() && passwordConfirmation.value.isNotEmpty() &&
                     !passwordTooWeakError.value && !passwordMatchError.value
diff --git a/src/main/kotlin/org/briarproject/briar/desktop/login/StartupScreen.kt b/src/main/kotlin/org/briarproject/briar/desktop/login/StartupScreen.kt
index 8dead60266..24a9d8a05f 100644
--- a/src/main/kotlin/org/briarproject/briar/desktop/login/StartupScreen.kt
+++ b/src/main/kotlin/org/briarproject/briar/desktop/login/StartupScreen.kt
@@ -3,16 +3,9 @@ package org.briarproject.briar.desktop.login
 import androidx.compose.foundation.layout.Arrangement
 import androidx.compose.foundation.layout.Column
 import androidx.compose.foundation.layout.Row
-import androidx.compose.foundation.layout.Spacer
-import androidx.compose.foundation.layout.fillMaxHeight
 import androidx.compose.foundation.layout.fillMaxSize
-import androidx.compose.foundation.layout.fillMaxWidth
 import androidx.compose.foundation.layout.padding
-import androidx.compose.foundation.layout.requiredWidth
-import androidx.compose.foundation.layout.requiredWidthIn
 import androidx.compose.foundation.layout.width
-import androidx.compose.material.Button
-import androidx.compose.material.CircularProgressIndicator
 import androidx.compose.material.Icon
 import androidx.compose.material.IconButton
 import androidx.compose.material.MaterialTheme
@@ -24,21 +17,19 @@ import androidx.compose.runtime.Composable
 import androidx.compose.ui.Alignment.Companion.CenterHorizontally
 import androidx.compose.ui.Alignment.Companion.CenterVertically
 import androidx.compose.ui.Modifier
-import androidx.compose.ui.graphics.Color
 import androidx.compose.ui.unit.dp
 import org.briarproject.briar.desktop.ui.BriarLogo
 import org.briarproject.briar.desktop.utils.InternationalizationUtils.i18n
 import org.briarproject.briar.desktop.viewmodel.viewModel
-import java.util.Locale
 
 @Composable
 fun StartupScreen(
     viewModel: StartupViewModel = viewModel(),
 ) {
-    when (val holder = viewModel.mode.value) {
-        is LoginViewHolder -> LoginScreen(holder)
-        is RegistrationViewHolder -> RegistrationScreen(holder)
-        is ErrorViewHolder -> ErrorScreen(holder)
+    when (val holder = viewModel.currentSubViewModel.value) {
+        is LoginSubViewModel -> LoginScreen(holder)
+        is RegistrationSubViewModel -> RegistrationScreen(holder)
+        is ErrorSubViewModel -> ErrorScreen(holder)
     }
 }
 
@@ -73,37 +64,3 @@ fun HeaderLine(title: String) =
         BriarLogo(Modifier.width(100.dp))
         Text(title, style = MaterialTheme.typography.h4)
     }
-
-@Composable
-fun LoadingView(text: String) =
-    Column(
-        horizontalAlignment = CenterHorizontally,
-        verticalArrangement = Arrangement.Center,
-        modifier = Modifier.fillMaxHeight()
-    ) {
-        CircularProgressIndicator(Modifier.padding(16.dp))
-        Text(text)
-    }
-
-@Composable
-fun FormScaffold(
-    explanationText: String?,
-    buttonText: String,
-    buttonClick: () -> Unit,
-    buttonEnabled: Boolean,
-    content: @Composable () -> Unit,
-) = Column(
-    modifier = Modifier.requiredWidthIn(max = 400.dp),
-    horizontalAlignment = CenterHorizontally
-) {
-    if (explanationText != null) {
-        Spacer(Modifier.weight(0.5f))
-        Text(explanationText, style = MaterialTheme.typography.body2, modifier = Modifier.requiredWidth(400.dp))
-        Spacer(Modifier.weight(0.5f))
-    } else Spacer(Modifier.weight(1.0f))
-    content()
-    Spacer(Modifier.weight(1.0f))
-    Button(onClick = buttonClick, enabled = buttonEnabled, modifier = Modifier.fillMaxWidth()) {
-        Text(buttonText.uppercase(Locale.getDefault()), color = Color.Black)
-    }
-}
diff --git a/src/main/kotlin/org/briarproject/briar/desktop/login/StartupViewModel.kt b/src/main/kotlin/org/briarproject/briar/desktop/login/StartupViewModel.kt
index bf42613767..ef58597c77 100644
--- a/src/main/kotlin/org/briarproject/briar/desktop/login/StartupViewModel.kt
+++ b/src/main/kotlin/org/briarproject/briar/desktop/login/StartupViewModel.kt
@@ -32,45 +32,46 @@ constructor(
         private val LOG = KotlinLogging.logger {}
     }
 
-    sealed interface ViewHolder {
+    sealed interface SubViewModel {
         fun lifecycleStateChanged(s: LifecycleManager.LifecycleState) {}
     }
 
-    class StartingError(val error: LifecycleManager.StartResult):
-        ErrorViewHolder.Error
+    class StartingError(val error: LifecycleManager.StartResult) :
+        ErrorSubViewModel.Error
 
-    private val _mode = mutableStateOf(decideMode())
-    val mode = _mode.asState()
+    private val _currentSubViewModel = mutableStateOf(decideSubViewModel())
+    val currentSubViewModel = _currentSubViewModel.asState()
 
-    private fun decideMode() =
+    private fun decideSubViewModel() =
         if (accountManager.accountExists()) makeLogin()
         else makeRegistration()
 
-    private fun makeLogin() = LoginViewHolder(
+    private fun makeLogin() = LoginSubViewModel(
         this, accountManager, briarExecutors, lifecycleManager.lifecycleState
     )
 
     fun showLogin() {
-        _mode.value = makeLogin()
+        _currentSubViewModel.value = makeLogin()
     }
 
-    private fun makeRegistration() = RegistrationViewHolder(
+    private fun makeRegistration() = RegistrationSubViewModel(
         this, accountManager, briarExecutors, passwordStrengthEstimator
     )
 
     fun showRegistration() {
-        _mode.value = makeRegistration()
+        _currentSubViewModel.value = makeRegistration()
     }
 
-    private fun makeError(error: ErrorViewHolder.Error) = ErrorViewHolder(
-        this, error, onBackButton = { _mode.value = decideMode() }
+    private fun makeError(error: ErrorSubViewModel.Error) = ErrorSubViewModel(
+        this, error, onBackButton = { _currentSubViewModel.value = decideSubViewModel() }
     )
-    fun showError(error: ErrorViewHolder.Error) {
-        _mode.value = makeError(error)
+
+    fun showError(error: ErrorSubViewModel.Error) {
+        _currentSubViewModel.value = makeError(error)
     }
 
     override fun eventOccurred(e: Event) {
-        if (e is LifecycleEvent) _mode.value.lifecycleStateChanged(e.lifecycleState)
+        if (e is LifecycleEvent) _currentSubViewModel.value.lifecycleStateChanged(e.lifecycleState)
     }
 
     @IoExecutor
diff --git a/src/main/kotlin/org/briarproject/briar/desktop/login/StrengthMeter.kt b/src/main/kotlin/org/briarproject/briar/desktop/login/StrengthMeter.kt
index 0207c290cf..9a834aad46 100644
--- a/src/main/kotlin/org/briarproject/briar/desktop/login/StrengthMeter.kt
+++ b/src/main/kotlin/org/briarproject/briar/desktop/login/StrengthMeter.kt
@@ -12,6 +12,7 @@ import org.briarproject.bramble.api.crypto.PasswordStrengthEstimator.QUITE_STRON
 import org.briarproject.bramble.api.crypto.PasswordStrengthEstimator.QUITE_WEAK
 import org.briarproject.bramble.api.crypto.PasswordStrengthEstimator.STRONG
 import org.briarproject.bramble.api.crypto.PasswordStrengthEstimator.WEAK
+import org.briarproject.briar.desktop.utils.PreviewUtils
 import org.briarproject.briar.desktop.utils.PreviewUtils.preview
 
 val RED = Color(255, 0, 0)
@@ -21,7 +22,7 @@ val LIME = Color(180, 255, 0)
 val GREEN = Color(0, 255, 0)
 
 fun main() = preview(
-    "strength" to 0f
+    "strength" to PreviewUtils.FloatSlider(0f, 0f, 1f)
 ) {
     StrengthMeter(getFloatParameter("strength"))
 }
diff --git a/src/main/kotlin/org/briarproject/briar/desktop/settings/SettingsScreen.kt b/src/main/kotlin/org/briarproject/briar/desktop/settings/SettingsScreen.kt
index 500fcc851a..d47c682fdb 100644
--- a/src/main/kotlin/org/briarproject/briar/desktop/settings/SettingsScreen.kt
+++ b/src/main/kotlin/org/briarproject/briar/desktop/settings/SettingsScreen.kt
@@ -4,7 +4,6 @@ import androidx.compose.foundation.layout.Row
 import androidx.compose.foundation.layout.fillMaxSize
 import androidx.compose.runtime.Composable
 import androidx.compose.ui.Modifier
-import org.briarproject.briar.desktop.viewmodel.viewModel
 
 @Composable
 fun SettingsScreen(viewModel: SettingsViewModel) {
diff --git a/src/main/kotlin/org/briarproject/briar/desktop/ui/Constants.kt b/src/main/kotlin/org/briarproject/briar/desktop/ui/Constants.kt
index abd377d359..34edbf5e26 100644
--- a/src/main/kotlin/org/briarproject/briar/desktop/ui/Constants.kt
+++ b/src/main/kotlin/org/briarproject/briar/desktop/ui/Constants.kt
@@ -5,5 +5,6 @@ import androidx.compose.ui.unit.dp
 object Constants {
     val HEADER_SIZE = 56.dp
     val COLUMN_WIDTH = 275.dp
+    val STARTUP_FIELDS_WIDTH = 400.dp
     val PARAGRAPH_WIDTH = 540.dp
 }
diff --git a/src/main/kotlin/org/briarproject/briar/desktop/utils/PreviewUtils.kt b/src/main/kotlin/org/briarproject/briar/desktop/utils/PreviewUtils.kt
index 032a405e1a..5c0e6ef2d5 100644
--- a/src/main/kotlin/org/briarproject/briar/desktop/utils/PreviewUtils.kt
+++ b/src/main/kotlin/org/briarproject/briar/desktop/utils/PreviewUtils.kt
@@ -8,9 +8,11 @@ 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.width
 import androidx.compose.foundation.text.BasicTextField
 import androidx.compose.material.Icon
 import androidx.compose.material.MaterialTheme
+import androidx.compose.material.Slider
 import androidx.compose.material.Surface
 import androidx.compose.material.Text
 import androidx.compose.material.icons.Icons
@@ -119,6 +121,11 @@ object PreviewUtils {
         BasicTextField(value.value.toString(), { value.value = it.toFloat() })
     }
 
+    @Composable
+    private fun PreviewScope.addFloatSliderParameter(name: String, initial: FloatSlider) = addParameter(name, initial.initial) { value ->
+        Slider(value.value, { value.value = it }, valueRange = initial.min..initial.max, modifier = Modifier.width(400.dp))
+    }
+
     /**
      * Open an interactive preview of the composable specified by [content].
      * All [parameters] passed to this function will be changeable on the fly.
@@ -142,6 +149,7 @@ object PreviewUtils {
                             is Int -> scope.addIntParameter(name, initial)
                             is Long -> scope.addLongParameter(name, initial)
                             is Float -> scope.addFloatParameter(name, initial)
+                            is FloatSlider -> scope.addFloatSliderParameter(name, initial)
                             else -> throw IllegalArgumentException("Type ${initial::class.simpleName} is not supported for previewing.")
                         }
                     }
@@ -157,4 +165,10 @@ object PreviewUtils {
             }
         }
     }
+
+    data class FloatSlider(
+        val initial: Float,
+        val min: Float,
+        val max: Float,
+    )
 }
diff --git a/src/main/resources/strings/BriarDesktop.properties b/src/main/resources/strings/BriarDesktop.properties
index 4f6b716a71..6c35408dc9 100644
--- a/src/main/resources/strings/BriarDesktop.properties
+++ b/src/main/resources/strings/BriarDesktop.properties
@@ -141,7 +141,7 @@ 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.registration=Briar was unable to create your account.\n\nPlease upgrade to the latest version and try again.
 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.db_error=Briar was unable to open the database containing your account, your contacts and your messages.\n\nPlease check if Briar is already running on this device. Otherwise, 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.
-- 
GitLab