diff --git a/features/login/build.gradle.kts b/features/login/build.gradle.kts index b6af8cb779..bbc5219123 100644 --- a/features/login/build.gradle.kts +++ b/features/login/build.gradle.kts @@ -10,6 +10,7 @@ dependencies { implementation(project(":libraries:core")) implementation(project(":libraries:matrix")) implementation(project(":libraries:designsystem")) + implementation(project(":libraries:elementresources")) implementation(libs.mavericks.compose) implementation(libs.timber) testImplementation("junit:junit:4.13.2") diff --git a/features/login/src/main/java/io/element/android/x/features/login/LoginScreen.kt b/features/login/src/main/java/io/element/android/x/features/login/LoginScreen.kt index 281a1869f5..02aba50ae4 100644 --- a/features/login/src/main/java/io/element/android/x/features/login/LoginScreen.kt +++ b/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.mavericksViewModel import io.element.android.x.designsystem.ElementXTheme +import io.element.android.x.features.login.error.loginError import timber.log.Timber @Composable @@ -164,14 +165,12 @@ fun LoginContent( imeAction = ImeAction.Done, ), keyboardActions = KeyboardActions( - onDone = { - if (state.submitEnabled) onSubmitClicked() - } + onDone = { onSubmitClicked() } ), ) - if (isError) { + if (state.isLoggedIn is Fail) { Text( - text = (state.isLoggedIn as? Fail)?.toString().orEmpty(), + text = loginError(state.formState, state.isLoggedIn.error), color = MaterialTheme.colorScheme.error, style = MaterialTheme.typography.bodySmall, modifier = Modifier.padding(start = 16.dp) diff --git a/features/login/src/main/java/io/element/android/x/features/login/LoginViewModel.kt b/features/login/src/main/java/io/element/android/x/features/login/LoginViewModel.kt index ae3164440e..5593313149 100644 --- a/features/login/src/main/java/io/element/android/x/features/login/LoginViewModel.kt +++ b/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.snapshotFlow import com.airbnb.mvrx.MavericksViewModel +import com.airbnb.mvrx.Uninitialized import io.element.android.x.matrix.MatrixInstance import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.onEach @@ -47,9 +48,11 @@ class LoginViewModel(initialState: LoginViewState) : fun onSetPassword(password: String) { formState.value = formState.value.copy(password = password) + setState { copy(isLoggedIn = Uninitialized) } } fun onSetName(name: String) { formState.value = formState.value.copy(login = name) + setState { copy(isLoggedIn = Uninitialized) } } } \ No newline at end of file diff --git a/features/login/src/main/java/io/element/android/x/features/login/changeserver/ChangeServerScreen.kt b/features/login/src/main/java/io/element/android/x/features/login/changeserver/ChangeServerScreen.kt index 4e2336ad50..4bb6b12a95 100644 --- a/features/login/src/main/java/io/element/android/x/features/login/changeserver/ChangeServerScreen.kt +++ b/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.rememberScrollState import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.foundation.text.KeyboardActions import androidx.compose.foundation.text.KeyboardOptions import androidx.compose.foundation.verticalScroll import androidx.compose.material3.* @@ -25,6 +26,7 @@ import com.airbnb.mvrx.Loading import com.airbnb.mvrx.Success import com.airbnb.mvrx.compose.collectAsState import com.airbnb.mvrx.compose.mavericksViewModel +import io.element.android.x.features.login.error.changeServerError @Composable fun ChangeServerScreen( @@ -107,12 +109,18 @@ fun ChangeServerContent( isError = isError, keyboardOptions = KeyboardOptions( keyboardType = KeyboardType.Password, - imeAction = ImeAction.Send, + imeAction = ImeAction.Done, ), + keyboardActions = KeyboardActions( + onDone = { onChangeServerSubmit() } + ) ) - if (isError) { + if (state.changeServerAction is Fail) { Text( - text = (state.changeServerAction as? Fail)?.toString().orEmpty(), + text = changeServerError( + state.homeserver, + state.changeServerAction.error + ), color = MaterialTheme.colorScheme.error, style = MaterialTheme.typography.bodySmall, modifier = Modifier.padding(start = 16.dp) diff --git a/features/login/src/main/java/io/element/android/x/features/login/changeserver/ChangeServerViewModel.kt b/features/login/src/main/java/io/element/android/x/features/login/changeserver/ChangeServerViewModel.kt index a2638da85b..58679beb91 100644 --- a/features/login/src/main/java/io/element/android/x/features/login/changeserver/ChangeServerViewModel.kt +++ b/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 import com.airbnb.mvrx.MavericksViewModel +import com.airbnb.mvrx.Uninitialized import io.element.android.x.matrix.MatrixInstance import kotlinx.coroutines.launch @@ -19,7 +20,10 @@ class ChangeServerViewModel(initialState: ChangeServerViewState) : fun setServer(server: String) { setState { - copy(homeserver = server) + copy( + homeserver = server, + changeServerAction = Uninitialized, + ) } } diff --git a/features/login/src/main/java/io/element/android/x/features/login/error/ErrorFormatter.kt b/features/login/src/main/java/io/element/android/x/features/login/error/ErrorFormatter.kt new file mode 100644 index 0000000000..ab862e085c --- /dev/null +++ b/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" + } +} diff --git a/libraries/core/src/main/java/io/element/android/x/core/uri/UrlUtils.kt b/libraries/core/src/main/java/io/element/android/x/core/uri/UrlUtils.kt new file mode 100644 index 0000000000..7718cd604b --- /dev/null +++ b/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 + } +}