Browse Source

Improve error management on login screens

feature/bma/flipper
Benoit Marty 2 years ago
parent
commit
b1ad191740
  1. 1
      features/login/build.gradle.kts
  2. 9
      features/login/src/main/java/io/element/android/x/features/login/LoginScreen.kt
  3. 3
      features/login/src/main/java/io/element/android/x/features/login/LoginViewModel.kt
  4. 14
      features/login/src/main/java/io/element/android/x/features/login/changeserver/ChangeServerScreen.kt
  5. 6
      features/login/src/main/java/io/element/android/x/features/login/changeserver/ChangeServerViewModel.kt
  6. 33
      features/login/src/main/java/io/element/android/x/features/login/error/ErrorFormatter.kt
  7. 31
      libraries/core/src/main/java/io/element/android/x/core/uri/UrlUtils.kt

1
features/login/build.gradle.kts

@ -10,6 +10,7 @@ dependencies {
implementation(project(":libraries:core")) implementation(project(":libraries:core"))
implementation(project(":libraries:matrix")) implementation(project(":libraries:matrix"))
implementation(project(":libraries:designsystem")) implementation(project(":libraries:designsystem"))
implementation(project(":libraries:elementresources"))
implementation(libs.mavericks.compose) implementation(libs.mavericks.compose)
implementation(libs.timber) implementation(libs.timber)
testImplementation("junit:junit:4.13.2") testImplementation("junit:junit:4.13.2")

9
features/login/src/main/java/io/element/android/x/features/login/LoginScreen.kt

@ -29,6 +29,7 @@ import com.airbnb.mvrx.Success
import com.airbnb.mvrx.compose.collectAsState import com.airbnb.mvrx.compose.collectAsState
import com.airbnb.mvrx.compose.mavericksViewModel import com.airbnb.mvrx.compose.mavericksViewModel
import io.element.android.x.designsystem.ElementXTheme import io.element.android.x.designsystem.ElementXTheme
import io.element.android.x.features.login.error.loginError
import timber.log.Timber import timber.log.Timber
@Composable @Composable
@ -164,14 +165,12 @@ fun LoginContent(
imeAction = ImeAction.Done, imeAction = ImeAction.Done,
), ),
keyboardActions = KeyboardActions( keyboardActions = KeyboardActions(
onDone = { onDone = { onSubmitClicked() }
if (state.submitEnabled) onSubmitClicked()
}
), ),
) )
if (isError) { if (state.isLoggedIn is Fail) {
Text( Text(
text = (state.isLoggedIn as? Fail)?.toString().orEmpty(), text = loginError(state.formState, state.isLoggedIn.error),
color = MaterialTheme.colorScheme.error, color = MaterialTheme.colorScheme.error,
style = MaterialTheme.typography.bodySmall, style = MaterialTheme.typography.bodySmall,
modifier = Modifier.padding(start = 16.dp) modifier = Modifier.padding(start = 16.dp)

3
features/login/src/main/java/io/element/android/x/features/login/LoginViewModel.kt

@ -3,6 +3,7 @@ package io.element.android.x.features.login
import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.snapshotFlow import androidx.compose.runtime.snapshotFlow
import com.airbnb.mvrx.MavericksViewModel import com.airbnb.mvrx.MavericksViewModel
import com.airbnb.mvrx.Uninitialized
import io.element.android.x.matrix.MatrixInstance import io.element.android.x.matrix.MatrixInstance
import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.flow.onEach
@ -47,9 +48,11 @@ class LoginViewModel(initialState: LoginViewState) :
fun onSetPassword(password: String) { fun onSetPassword(password: String) {
formState.value = formState.value.copy(password = password) formState.value = formState.value.copy(password = password)
setState { copy(isLoggedIn = Uninitialized) }
} }
fun onSetName(name: String) { fun onSetName(name: String) {
formState.value = formState.value.copy(login = name) formState.value = formState.value.copy(login = name)
setState { copy(isLoggedIn = Uninitialized) }
} }
} }

14
features/login/src/main/java/io/element/android/x/features/login/changeserver/ChangeServerScreen.kt

@ -6,6 +6,7 @@ import androidx.compose.foundation.background
import androidx.compose.foundation.layout.* import androidx.compose.foundation.layout.*
import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.foundation.text.KeyboardActions
import androidx.compose.foundation.text.KeyboardOptions import androidx.compose.foundation.text.KeyboardOptions
import androidx.compose.foundation.verticalScroll import androidx.compose.foundation.verticalScroll
import androidx.compose.material3.* import androidx.compose.material3.*
@ -25,6 +26,7 @@ import com.airbnb.mvrx.Loading
import com.airbnb.mvrx.Success import com.airbnb.mvrx.Success
import com.airbnb.mvrx.compose.collectAsState import com.airbnb.mvrx.compose.collectAsState
import com.airbnb.mvrx.compose.mavericksViewModel import com.airbnb.mvrx.compose.mavericksViewModel
import io.element.android.x.features.login.error.changeServerError
@Composable @Composable
fun ChangeServerScreen( fun ChangeServerScreen(
@ -107,12 +109,18 @@ fun ChangeServerContent(
isError = isError, isError = isError,
keyboardOptions = KeyboardOptions( keyboardOptions = KeyboardOptions(
keyboardType = KeyboardType.Password, keyboardType = KeyboardType.Password,
imeAction = ImeAction.Send, imeAction = ImeAction.Done,
), ),
keyboardActions = KeyboardActions(
onDone = { onChangeServerSubmit() }
)
) )
if (isError) { if (state.changeServerAction is Fail) {
Text( Text(
text = (state.changeServerAction as? Fail)?.toString().orEmpty(), text = changeServerError(
state.homeserver,
state.changeServerAction.error
),
color = MaterialTheme.colorScheme.error, color = MaterialTheme.colorScheme.error,
style = MaterialTheme.typography.bodySmall, style = MaterialTheme.typography.bodySmall,
modifier = Modifier.padding(start = 16.dp) modifier = Modifier.padding(start = 16.dp)

6
features/login/src/main/java/io/element/android/x/features/login/changeserver/ChangeServerViewModel.kt

@ -1,6 +1,7 @@
package io.element.android.x.features.login.changeserver package io.element.android.x.features.login.changeserver
import com.airbnb.mvrx.MavericksViewModel import com.airbnb.mvrx.MavericksViewModel
import com.airbnb.mvrx.Uninitialized
import io.element.android.x.matrix.MatrixInstance import io.element.android.x.matrix.MatrixInstance
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
@ -19,7 +20,10 @@ class ChangeServerViewModel(initialState: ChangeServerViewState) :
fun setServer(server: String) { fun setServer(server: String) {
setState { setState {
copy(homeserver = server) copy(
homeserver = server,
changeServerAction = Uninitialized,
)
} }
} }

33
features/login/src/main/java/io/element/android/x/features/login/error/ErrorFormatter.kt

@ -0,0 +1,33 @@
package io.element.android.x.features.login.error
import androidx.compose.runtime.Composable
import androidx.compose.ui.res.stringResource
import io.element.android.x.core.uri.isValidUrl
import io.element.android.x.features.login.LoginFormState
import io.element.android.x.element.resources.R as ElementR
@Composable
fun loginError(
data: LoginFormState,
throwable: Throwable?
): String {
return when {
data.login.isEmpty() -> "Please enter a login"
data.password.isEmpty() -> "Please enter a password"
throwable != null -> stringResource(id = ElementR.string.auth_invalid_login_param)
else -> "No error provided"
}
}
@Composable
fun changeServerError(
data: String,
throwable: Throwable?
): String {
return when {
data.isEmpty() -> "Please enter a server URL"
!data.isValidUrl() -> stringResource(id = ElementR.string.login_error_invalid_home_server)
throwable != null -> "That server doesn’t seem right. Please check the address."
else -> "No error provided"
}
}

31
libraries/core/src/main/java/io/element/android/x/core/uri/UrlUtils.kt

@ -0,0 +1,31 @@
package io.element.android.x.core.uri
import java.net.URL
fun String.isValidUrl(): Boolean {
return try {
URL(this)
true
} catch (t: Throwable) {
false
}
}
/**
* Ensure string starts with "http". If it is not the case, "https://" is added, only if the String is not empty
*/
fun String.ensureProtocol(): String {
return when {
isEmpty() -> this
!startsWith("http") -> "https://$this"
else -> this
}
}
fun String.ensureTrailingSlash(): String {
return when {
isEmpty() -> this
!endsWith("/") -> "$this/"
else -> this
}
}
Loading…
Cancel
Save