Verified Commit b8a653a4 authored by Mikolai Gütschow's avatar Mikolai Gütschow
Browse files

address review comments

parent 6a01b301
Pipeline #9201 passed with stages
in 8 minutes and 24 seconds
......@@ -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(
......
......@@ -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)
)
}
}
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
}
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)
}
}
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)
}
......@@ -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),
......
......@@ -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
......
......@@ -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(),
......
......@@ -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
......
......@@ -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)
}
}
......@@ -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
......
......@@ -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"))
}
......
......@@ -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) {
......
......@@ -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
}
......@@ -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,
)
}
......@@ -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.
......
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment