Improve auth screen
This commit is contained in:
parent
b6e4d282b7
commit
0ca6ac9c9a
3 changed files with 83 additions and 44 deletions
|
|
@ -72,6 +72,7 @@ dependencies {
|
||||||
implementation "androidx.appcompat:appcompat:$androidx_appcompat_version"
|
implementation "androidx.appcompat:appcompat:$androidx_appcompat_version"
|
||||||
implementation "com.google.android.material:material:$androidx_material_version"
|
implementation "com.google.android.material:material:$androidx_material_version"
|
||||||
implementation "androidx.constraintlayout:constraintlayout:$androidx_constraintlayout_version"
|
implementation "androidx.constraintlayout:constraintlayout:$androidx_constraintlayout_version"
|
||||||
|
implementation "androidx.constraintlayout:constraintlayout-compose:1.0.0"
|
||||||
implementation "androidx.lifecycle:lifecycle-livedata-core-ktx:$androidx_livedata_version"
|
implementation "androidx.lifecycle:lifecycle-livedata-core-ktx:$androidx_livedata_version"
|
||||||
implementation "androidx.lifecycle:lifecycle-livedata-ktx:$androidx_livedata_version"
|
implementation "androidx.lifecycle:lifecycle-livedata-ktx:$androidx_livedata_version"
|
||||||
implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:$androidx_livedata_version"
|
implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:$androidx_livedata_version"
|
||||||
|
|
|
||||||
|
|
@ -1,72 +1,106 @@
|
||||||
package org.fnives.test.showcase.ui.compose.screen.auth
|
package org.fnives.test.showcase.ui.compose.screen.auth
|
||||||
|
|
||||||
import androidx.compose.foundation.layout.*
|
import androidx.compose.foundation.layout.*
|
||||||
|
import androidx.compose.foundation.text.KeyboardActions
|
||||||
|
import androidx.compose.foundation.text.KeyboardOptions
|
||||||
import androidx.compose.material.*
|
import androidx.compose.material.*
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.LaunchedEffect
|
import androidx.compose.runtime.LaunchedEffect
|
||||||
import androidx.compose.runtime.remember
|
import androidx.compose.runtime.remember
|
||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
|
import androidx.compose.ui.ExperimentalComposeUiApi
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.platform.LocalSoftwareKeyboardController
|
||||||
import androidx.compose.ui.res.stringResource
|
import androidx.compose.ui.res.stringResource
|
||||||
|
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.text.input.PasswordVisualTransformation
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import com.google.accompanist.insets.systemBarsPadding
|
import androidx.constraintlayout.compose.ConstraintLayout
|
||||||
|
import com.google.accompanist.insets.statusBarsPadding
|
||||||
import org.fnives.test.showcase.R
|
import org.fnives.test.showcase.R
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun AuthScreen(
|
fun AuthScreen(
|
||||||
authScreenState: AuthScreenState = rememberAuthScreenState()
|
authScreenState: AuthScreenState = rememberAuthScreenState()
|
||||||
) {
|
) {
|
||||||
Column(
|
ConstraintLayout(Modifier.fillMaxSize()) {
|
||||||
|
val (title, credentials, snackbar, loading, login) = createRefs()
|
||||||
|
Title(
|
||||||
Modifier
|
Modifier
|
||||||
.fillMaxSize()
|
.statusBarsPadding()
|
||||||
.systemBarsPadding()
|
.constrainAs(title) { top.linkTo(parent.top) })
|
||||||
) {
|
CredentialsFields(authScreenState, Modifier.constrainAs(credentials) {
|
||||||
Title()
|
top.linkTo(title.bottom)
|
||||||
Column(
|
bottom.linkTo(login.top)
|
||||||
Modifier
|
})
|
||||||
.fillMaxWidth()
|
Snackbar(authScreenState, Modifier.constrainAs(snackbar) {
|
||||||
.padding(16.dp)
|
bottom.linkTo(login.top)
|
||||||
.weight(1f),
|
})
|
||||||
verticalArrangement = Arrangement.Center,
|
if (authScreenState.loading) {
|
||||||
horizontalAlignment = Alignment.CenterHorizontally
|
CircularProgressIndicator(Modifier.constrainAs(loading) {
|
||||||
) {
|
bottom.linkTo(login.top)
|
||||||
TextField(
|
centerHorizontallyTo(parent)
|
||||||
value = authScreenState.username,
|
})
|
||||||
onValueChange = { authScreenState.onUsernameChanged(it) },
|
|
||||||
modifier = Modifier.fillMaxWidth()
|
|
||||||
)
|
|
||||||
TextField(
|
|
||||||
value = authScreenState.password,
|
|
||||||
onValueChange = { authScreenState.onPasswordChanged(it) },
|
|
||||||
visualTransformation = PasswordVisualTransformation(),
|
|
||||||
modifier = Modifier
|
|
||||||
.fillMaxWidth()
|
|
||||||
.padding(top = 16.dp)
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Snackbar(authScreenState)
|
|
||||||
LoginButton(
|
LoginButton(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.align(Alignment.CenterHorizontally)
|
.constrainAs(login) { bottom.linkTo(parent.bottom) }
|
||||||
.padding(16.dp),
|
.padding(16.dp),
|
||||||
onClick = { authScreenState.onLogin() }
|
onClick = { authScreenState.onLogin() }
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@OptIn(ExperimentalComposeUiApi::class)
|
||||||
@Composable
|
@Composable
|
||||||
private fun Snackbar(authScreenState: AuthScreenState) {
|
private fun CredentialsFields(authScreenState: AuthScreenState, modifier: Modifier = Modifier) {
|
||||||
|
val keyboardController = LocalSoftwareKeyboardController.current
|
||||||
|
Column(
|
||||||
|
modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.padding(16.dp),
|
||||||
|
verticalArrangement = Arrangement.Center,
|
||||||
|
horizontalAlignment = Alignment.CenterHorizontally
|
||||||
|
) {
|
||||||
|
OutlinedTextField(
|
||||||
|
value = authScreenState.username,
|
||||||
|
label = { Text(text = stringResource(id = R.string.username)) },
|
||||||
|
placeholder = { Text(text = stringResource(id = R.string.username)) },
|
||||||
|
keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Email, imeAction = ImeAction.Next),
|
||||||
|
onValueChange = { authScreenState.onUsernameChanged(it) },
|
||||||
|
modifier = Modifier.fillMaxWidth()
|
||||||
|
)
|
||||||
|
OutlinedTextField(
|
||||||
|
value = authScreenState.password,
|
||||||
|
label = { Text(text = stringResource(id = R.string.password)) },
|
||||||
|
placeholder = { Text(text = stringResource(id = R.string.password)) },
|
||||||
|
onValueChange = { authScreenState.onPasswordChanged(it) },
|
||||||
|
keyboardOptions = KeyboardOptions(autoCorrect = false, imeAction = ImeAction.Done, keyboardType = KeyboardType.Password),
|
||||||
|
keyboardActions = KeyboardActions(onDone = {
|
||||||
|
keyboardController?.hide()
|
||||||
|
authScreenState.onLogin()
|
||||||
|
}),
|
||||||
|
visualTransformation = PasswordVisualTransformation(),
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.padding(top = 16.dp)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
private fun Snackbar(authScreenState: AuthScreenState, modifier: Modifier = Modifier) {
|
||||||
val snackbarState = remember { SnackbarHostState() }
|
val snackbarState = remember { SnackbarHostState() }
|
||||||
val errorType = authScreenState.error?.consume()
|
val error = authScreenState.error
|
||||||
LaunchedEffect(errorType) {
|
LaunchedEffect(error) {
|
||||||
if (errorType != null) {
|
if (error != null) {
|
||||||
snackbarState.showSnackbar(errorType.name)
|
snackbarState.showSnackbar(error.name)
|
||||||
|
authScreenState.dismissError()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
SnackbarHost(hostState = snackbarState) {
|
SnackbarHost(hostState = snackbarState, modifier) {
|
||||||
val stringId = errorType?.stringResId()
|
val stringId = error?.stringResId()
|
||||||
if (stringId != null) {
|
if (stringId != null) {
|
||||||
Snackbar(modifier = Modifier.padding(horizontal = 16.dp)) {
|
Snackbar(modifier = Modifier.padding(horizontal = 16.dp)) {
|
||||||
Text(text = stringResource(stringId))
|
Text(text = stringResource(stringId))
|
||||||
|
|
@ -85,10 +119,10 @@ private fun LoginButton(modifier: Modifier = Modifier, onClick: () -> Unit) {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
private fun Title() {
|
private fun Title(modifier: Modifier = Modifier) {
|
||||||
Text(
|
Text(
|
||||||
stringResource(id = R.string.login_title),
|
stringResource(id = R.string.login_title),
|
||||||
modifier = Modifier.padding(16.dp),
|
modifier = modifier.padding(16.dp),
|
||||||
style = MaterialTheme.typography.h4
|
style = MaterialTheme.typography.h4
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -29,7 +29,7 @@ class AuthScreenState(
|
||||||
private set
|
private set
|
||||||
var loading by mutableStateOf(false)
|
var loading by mutableStateOf(false)
|
||||||
private set
|
private set
|
||||||
var error by mutableStateOf<Event<ErrorType>?>(null)
|
var error by mutableStateOf<ErrorType?>(null)
|
||||||
private set
|
private set
|
||||||
var navigateToHome by mutableStateOf<Event<Unit>?>(null)
|
var navigateToHome by mutableStateOf<Event<Unit>?>(null)
|
||||||
private set
|
private set
|
||||||
|
|
@ -53,7 +53,7 @@ class AuthScreenState(
|
||||||
password = password
|
password = password
|
||||||
)
|
)
|
||||||
when (val response = loginUseCase.invoke(credentials)) {
|
when (val response = loginUseCase.invoke(credentials)) {
|
||||||
is Answer.Error -> error = Event(ErrorType.GENERAL_NETWORK_ERROR)
|
is Answer.Error -> error = ErrorType.GENERAL_NETWORK_ERROR
|
||||||
is Answer.Success -> processLoginStatus(response.data)
|
is Answer.Success -> processLoginStatus(response.data)
|
||||||
}
|
}
|
||||||
loading = false
|
loading = false
|
||||||
|
|
@ -63,13 +63,17 @@ class AuthScreenState(
|
||||||
private fun processLoginStatus(loginStatus: LoginStatus) {
|
private fun processLoginStatus(loginStatus: LoginStatus) {
|
||||||
when (loginStatus) {
|
when (loginStatus) {
|
||||||
LoginStatus.SUCCESS -> navigateToHome = Event(Unit)
|
LoginStatus.SUCCESS -> navigateToHome = Event(Unit)
|
||||||
LoginStatus.INVALID_CREDENTIALS -> error = Event(ErrorType.INVALID_CREDENTIALS)
|
LoginStatus.INVALID_CREDENTIALS -> error = ErrorType.INVALID_CREDENTIALS
|
||||||
LoginStatus.INVALID_USERNAME -> error = Event(ErrorType.UNSUPPORTED_USERNAME)
|
LoginStatus.INVALID_USERNAME -> error = ErrorType.UNSUPPORTED_USERNAME
|
||||||
LoginStatus.INVALID_PASSWORD -> error = Event(ErrorType.UNSUPPORTED_PASSWORD)
|
LoginStatus.INVALID_PASSWORD -> error = ErrorType.UNSUPPORTED_PASSWORD
|
||||||
}
|
}
|
||||||
println("asdasdasd: ${error.hashCode()}")
|
println("asdasdasd: ${error.hashCode()}")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun dismissError() {
|
||||||
|
error = null
|
||||||
|
}
|
||||||
|
|
||||||
enum class ErrorType {
|
enum class ErrorType {
|
||||||
INVALID_CREDENTIALS,
|
INVALID_CREDENTIALS,
|
||||||
GENERAL_NETWORK_ERROR,
|
GENERAL_NETWORK_ERROR,
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue