Change the Authentication Screen.

This commit is contained in:
Andres Gomez, Thomas (ITDV RL) 2024-06-26 16:39:08 +02:00
parent 529ea5f893
commit b907e934c9
18 changed files with 657 additions and 380 deletions

BIN
app/.DS_Store vendored Normal file

Binary file not shown.

View file

@ -9,7 +9,7 @@ plugins {
}
android {
compileSdk 33
compileSdk 34
namespace 'com.pixelized.biblib'
signingConfigs {

View file

@ -0,0 +1,20 @@
{
"version": 3,
"artifactType": {
"type": "APK",
"kind": "Directory"
},
"applicationId": "com.pixelized.biblib",
"variantName": "release",
"elements": [
{
"type": "SINGLE",
"filters": [],
"attributes": [],
"versionCode": 161,
"versionName": "0.1.6",
"outputFile": "app-release.apk"
}
],
"elementType": "File"
}

View file

@ -1,12 +1,17 @@
package com.pixelized.biblib.ui.composable
import android.annotation.SuppressLint
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.runtime.*
import androidx.compose.runtime.Composable
import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.runtime.MutableState
import androidx.compose.runtime.State
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.staticCompositionLocalOf
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Brush

View file

@ -1,32 +1,89 @@
package com.pixelized.biblib.ui.screen.authentication
import android.content.res.Configuration
import androidx.activity.compose.BackHandler
import androidx.annotation.DrawableRes
import androidx.annotation.StringRes
import androidx.compose.animation.AnimatedVisibility
import androidx.compose.animation.animateColorAsState
import androidx.compose.animation.core.animateDpAsState
import androidx.compose.animation.fadeIn
import androidx.compose.animation.fadeOut
import androidx.compose.animation.slideInVertically
import androidx.compose.animation.slideOutVertically
import androidx.compose.foundation.Image
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.navigationBarsPadding
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.systemBarsPadding
import androidx.compose.foundation.layout.statusBarsPadding
import androidx.compose.foundation.text.KeyboardActions
import androidx.compose.foundation.text.KeyboardOptions
import androidx.compose.material.Button
import androidx.compose.material.ButtonDefaults
import androidx.compose.material.CircularProgressIndicator
import androidx.compose.material.Icon
import androidx.compose.material.IconButton
import androidx.compose.material.MaterialTheme
import androidx.compose.material.SnackbarResult
import androidx.compose.runtime.*
import androidx.compose.material.Surface
import androidx.compose.material.Text
import androidx.compose.material.TextButton
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Close
import androidx.compose.runtime.Composable
import androidx.compose.runtime.Immutable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.Stable
import androidx.compose.runtime.State
import androidx.compose.runtime.derivedStateOf
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.BlurredEdgeTreatment
import androidx.compose.ui.draw.blur
import androidx.compose.ui.focus.FocusManager
import androidx.compose.ui.graphics.Brush
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.platform.LocalFocusManager
import androidx.compose.ui.platform.LocalUriHandler
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.input.ImeAction
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.tooling.preview.PreviewParameter
import androidx.compose.ui.tooling.preview.PreviewParameterProvider
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp
import androidx.hilt.navigation.compose.hiltViewModel
import com.pixelized.biblib.R
import com.pixelized.biblib.network.client.IBibLibClient
import com.pixelized.biblib.ui.LocalSnackHostState
import com.pixelized.biblib.ui.navigation.LocalScreenNavHostController
import com.pixelized.biblib.ui.navigation.navigateToHome
import com.pixelized.biblib.ui.screen.authentication.composable.CredentialRemember
import com.pixelized.biblib.ui.screen.authentication.composable.LoginField
import com.pixelized.biblib.ui.screen.authentication.composable.PasswordField
import com.pixelized.biblib.ui.screen.authentication.composable.googleStringResource
import com.pixelized.biblib.ui.screen.authentication.viewModel.AuthenticationFormViewModel
import com.pixelized.biblib.ui.screen.authentication.viewModel.AuthenticationViewModel
import com.pixelized.biblib.ui.theme.BibLibTheme
import com.pixelized.biblib.ui.theme.color.ShadowPalette
import com.pixelized.biblib.utils.extention.bibLib
import com.pixelized.biblib.utils.extention.modifier.unclickable
import kotlinx.coroutines.launch
@Stable
@ -65,60 +122,287 @@ fun AuthenticationScreen(
val snackBarHostState = LocalSnackHostState.current
val uriHandler = LocalUriHandler.current
val context = LocalContext.current
val focusManager = LocalFocusManager.current
val scope = rememberCoroutineScope()
AuthenticationScreenContent(
modifier = Modifier.systemBarsPadding(),
login = formViewModel.form.login,
password = formViewModel.form.password,
rememberPassword = formViewModel.form.remember,
onLoginChange = formViewModel::onLoginChange,
onPasswordChange = formViewModel::onPasswordChange,
onRememberPasswordChange = formViewModel::onRememberChange,
onGoogleSignIn = authenticationViewModel::loginWithGoogle,
onSignIn = {
authenticationViewModel.login(
login = formViewModel.form.login,
password = formViewModel.form.password,
)
},
onRegister = {
uriHandler.openUri(uri = IBibLibClient.REGISTER_URL)
},
)
val background = rememberSaveable {
val tmp = listOf(
R.drawable.biblib_background_1,
R.drawable.biblib_background_2,
R.drawable.biblib_background_3,
)
mutableStateOf(tmp.random())
}
val displayBibLibSignIn = remember { mutableStateOf(false) }
val blur = remember { derivedStateOf { if (displayBibLibSignIn.value) 8.dp else 0.dp } }
AuthenticationHandler(
onFailure = {
scope.launch {
val result = snackBarHostState.showSnackbar(
message = context.getString(it.message),
actionLabel = context.getString(it.action),
Surface(
modifier = Modifier.fillMaxSize(),
) {
AuthenticationScreenContent(
modifier = Modifier.fillMaxSize(),
background = background.value,
blur = blur,
onGoogleSignIn = authenticationViewModel::loginWithGoogle,
onBibLibSingIn = { displayBibLibSignIn.value = true },
)
AuthenticationSignDialog(
focusManager = focusManager,
visibility = displayBibLibSignIn,
login = formViewModel.form.login,
password = formViewModel.form.password,
rememberPassword = formViewModel.form.remember,
onLoginChange = formViewModel::onLoginChange,
onPasswordChange = formViewModel::onPasswordChange,
onRememberPasswordChange = formViewModel::onRememberChange,
onSignIn = {
authenticationViewModel.login(
login = formViewModel.form.login,
password = formViewModel.form.password,
)
if (result == SnackbarResult.ActionPerformed) {
when (it) {
is AuthenticationErrorUio.GoogleLogin -> {
authenticationViewModel.loginWithGoogle()
}
is AuthenticationErrorUio.Login -> {
authenticationViewModel.login(
login = formViewModel.form.login,
password = formViewModel.form.password,
)
}
},
onRegister = {
uriHandler.openUri(uri = IBibLibClient.REGISTER_URL)
},
onDismissRequest = {
displayBibLibSignIn.value = false
},
)
}
BackHandler(
enabled = displayBibLibSignIn.value
) {
displayBibLibSignIn.value = false
}
AuthenticationHandler(onFailure = {
scope.launch {
val result = snackBarHostState.showSnackbar(
message = context.getString(it.message),
actionLabel = context.getString(it.action),
)
if (result == SnackbarResult.ActionPerformed) {
when (it) {
is AuthenticationErrorUio.GoogleLogin -> {
authenticationViewModel.loginWithGoogle()
}
is AuthenticationErrorUio.Login -> {
authenticationViewModel.login(
login = formViewModel.form.login,
password = formViewModel.form.password,
)
}
}
}
},
onSuccess = {
navHostController.navigateToHome()
}
)
}, onSuccess = {
navHostController.navigateToHome()
})
AuthenticationProgress()
}
@Composable
fun AuthenticationHandler(
private fun AuthenticationScreenContent(
modifier: Modifier = Modifier,
blur: State<Dp>,
@DrawableRes background: Int,
onGoogleSignIn: () -> Unit,
onBibLibSingIn: () -> Unit,
) {
val colorScheme = MaterialTheme.colors
val animatedBlur = animateDpAsState(
targetValue = blur.value,
// label = "AnimatedContentBlur",
)
Column(
modifier = modifier.blur(
radius = animatedBlur.value,
edgeTreatment = BlurredEdgeTreatment.Unbounded,
),
) {
Box(
modifier = Modifier
.fillMaxWidth()
.weight(1f),
) {
Image(
modifier = Modifier
.matchParentSize()
.background(color = MaterialTheme.colors.surface.copy(alpha = 0.8f)),
contentScale = ContentScale.FillHeight,
painter = painterResource(id = background),
contentDescription = null,
)
Box(
modifier = Modifier
.matchParentSize()
.background(brush = remember {
Brush.verticalGradient(
listOf(
colorScheme.surface.copy(alpha = 0.0f),
colorScheme.surface.copy(alpha = 0.0f),
colorScheme.surface.copy(alpha = 0.0f),
colorScheme.surface.copy(alpha = 0.3f),
colorScheme.surface.copy(alpha = 1.0f),
)
)
}),
)
}
Box(
modifier = Modifier.navigationBarsPadding(),
) {
Column(
verticalArrangement = Arrangement.spacedBy(
space = 8.dp, alignment = Alignment.Bottom
),
) {
Button(
modifier = Modifier
.padding(top = 16.dp)
.padding(horizontal = 16.dp)
.fillMaxWidth()
.height(height = 48.dp),
colors = ButtonDefaults.outlinedButtonColors(),
onClick = onGoogleSignIn,
) {
Text(text = googleStringResource())
}
TextButton(
modifier = Modifier
.padding(bottom = 16.dp)
.padding(horizontal = 16.dp)
.fillMaxWidth()
.height(height = 48.dp),
colors = ButtonDefaults.textButtonColors(),
onClick = onBibLibSingIn,
) {
Text(text = "Sign in with BibLib account")
}
}
}
}
}
@Composable
private fun AuthenticationSignDialog(
focusManager: FocusManager = LocalFocusManager.current,
visibility: State<Boolean>,
login: String,
onLoginChange: (String) -> Unit,
password: String,
onPasswordChange: (String) -> Unit,
rememberPassword: Boolean,
onRememberPasswordChange: (Boolean) -> Unit,
onSignIn: () -> Unit,
onRegister: () -> Unit,
onDismissRequest: () -> Unit,
) {
val density = LocalDensity.current
val colorScheme = MaterialTheme.colors
val slideOffset = remember { with(density) { 16.dp.roundToPx() } }
Box(
modifier = Modifier
.unclickable(disable = visibility.value)
.fillMaxSize()
.background(
color = animateColorAsState(
targetValue = when (visibility.value) {
true -> colorScheme.surface.copy(alpha = 0.3f)
else -> Color.Transparent
},
// label = "AnimatedSignInScrim"
).value
),
) {
AnimatedVisibility(
modifier = Modifier.fillMaxSize(),
enter = fadeIn() + slideInVertically { slideOffset },
exit = fadeOut() + slideOutVertically { slideOffset },
visible = visibility.value,
) {
Box(
modifier = Modifier.fillMaxSize(),
) {
IconButton(
modifier = Modifier
.statusBarsPadding()
.align(alignment = Alignment.TopEnd),
onClick = onDismissRequest,
) {
Icon(
imageVector = Icons.Default.Close,
contentDescription = null,
)
}
Column(
modifier = Modifier.align(alignment = Alignment.Center),
verticalArrangement = Arrangement.spacedBy(
space = 24.dp,
alignment = Alignment.CenterVertically,
),
) {
Column(
modifier = Modifier.padding(horizontal = 16.dp),
) {
LoginField(
modifier = Modifier.fillMaxWidth(),
value = login,
onValueChange = onLoginChange,
keyboardOptions = KeyboardOptions(imeAction = ImeAction.Next),
)
PasswordField(
modifier = Modifier.fillMaxWidth(),
value = password,
onValueChange = onPasswordChange,
keyboardOptions = KeyboardOptions(imeAction = ImeAction.Done),
keyboardActions = KeyboardActions { focusManager.clearFocus() },
)
}
CredentialRemember(
modifier = Modifier.padding(start = 24.dp, end = 16.dp),
value = rememberPassword,
onValueChange = onRememberPasswordChange,
)
Row(
modifier = Modifier
.fillMaxWidth()
.padding(horizontal = 16.dp),
horizontalArrangement = Arrangement.spacedBy(
space = 16.dp, alignment = Alignment.End
),
) {
TextButton(
modifier = Modifier.height(height = 48.dp),
colors = ButtonDefaults.textButtonColors(
contentColor = MaterialTheme.colors.onSurface
),
onClick = onRegister,
) {
Text(text = stringResource(id = R.string.action_register))
}
Button(
modifier = Modifier.height(height = 48.dp),
colors = ButtonDefaults.buttonColors(),
onClick = onSignIn,
) {
Text(text = stringResource(id = R.string.action_login))
}
}
}
}
}
}
}
@Composable
private fun AuthenticationHandler(
viewModel: AuthenticationViewModel = hiltViewModel(),
onFailure: (AuthenticationErrorUio) -> Unit = { },
onSuccess: (AuthenticationUio.Done) -> Unit = { },
@ -160,4 +444,42 @@ fun AuthenticationProgress(
)
}
}
}
@Composable
@Preview(showBackground = true, uiMode = Configuration.UI_MODE_NIGHT_NO)
@Preview(showBackground = true, uiMode = Configuration.UI_MODE_NIGHT_YES)
private fun AuthenticationScreenContentPreview(
@PreviewParameter(AuthenticationPreviewProvider::class) preview: State<Boolean>,
) {
BibLibTheme {
Surface {
AuthenticationScreenContent(
modifier = Modifier.fillMaxSize(),
blur = remember { mutableStateOf(if (preview.value) 8.dp else 0.dp) },
background = R.drawable.biblib_background_3,
onGoogleSignIn = { },
onBibLibSingIn = { },
)
AuthenticationSignDialog(
visibility = preview,
login = "",
onLoginChange = { },
password = "",
onPasswordChange = { },
rememberPassword = true,
onRememberPasswordChange = { },
onSignIn = { },
onRegister = { },
onDismissRequest = { },
)
}
}
}
private class AuthenticationPreviewProvider : PreviewParameterProvider<State<Boolean>> {
override val values: Sequence<State<Boolean>> = sequenceOf(
mutableStateOf(false),
mutableStateOf(true),
)
}

View file

@ -1,330 +0,0 @@
package com.pixelized.biblib.ui.screen.authentication
import android.content.res.Configuration
import androidx.compose.foundation.ScrollState
import androidx.compose.foundation.clickable
import androidx.compose.foundation.interaction.MutableInteractionSource
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.text.KeyboardActions
import androidx.compose.foundation.text.KeyboardOptions
import androidx.compose.foundation.verticalScroll
import androidx.compose.material.*
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.sharp.Visibility
import androidx.compose.material.icons.sharp.VisibilityOff
import androidx.compose.runtime.*
import androidx.compose.ui.Alignment
import androidx.compose.ui.ExperimentalComposeUiApi
import androidx.compose.ui.Modifier
import androidx.compose.ui.autofill.AutofillType
import androidx.compose.ui.platform.LocalFocusManager
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.AnnotatedString
import androidx.compose.ui.text.buildAnnotatedString
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.input.ImeAction
import androidx.compose.ui.text.input.PasswordVisualTransformation
import androidx.compose.ui.text.input.VisualTransformation
import androidx.compose.ui.text.withStyle
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import com.pixelized.biblib.R
import com.pixelized.biblib.ui.composable.animation.AnimatedDelayer
import com.pixelized.biblib.ui.composable.animation.AnimatedOffset
import com.pixelized.biblib.ui.theme.BibLibTheme
import com.pixelized.biblib.ui.theme.color.GoogleColorPalette
import com.pixelized.biblib.utils.extention.autofill
@Composable
fun AuthenticationScreenContent(
modifier: Modifier = Modifier,
scrollState: ScrollState = rememberScrollState(),
login: String,
onLoginChange: (String) -> Unit,
password: String,
onPasswordChange: (String) -> Unit,
rememberPassword: Boolean,
onRememberPasswordChange: (Boolean) -> Unit,
onGoogleSignIn: () -> Unit,
onSignIn: () -> Unit,
onRegister: () -> Unit,
) {
val focusManager = LocalFocusManager.current
AnimatedDelayer(targetState = "AuthenticationScreenContent") {
Column(
modifier = modifier
.fillMaxSize()
.verticalScroll(scrollState)
) {
Spacer(modifier = Modifier.weight(1f))
AnimatedOffset(
modifier = Modifier
.padding(all = 16.dp)
.align(alignment = Alignment.CenterHorizontally),
) {
Text(
style = MaterialTheme.typography.h4,
color = MaterialTheme.colors.onBackground,
text = stringResource(id = R.string.authentication_title),
)
}
Spacer(modifier = Modifier.weight(1f))
AnimatedOffset(
modifier = Modifier.padding(horizontal = 16.dp),
) {
LoginField(
modifier = Modifier.fillMaxWidth(),
value = login,
onValueChange = onLoginChange,
keyboardOptions = KeyboardOptions(imeAction = ImeAction.Next),
)
}
Spacer(modifier = Modifier.height(8.dp))
AnimatedOffset(
modifier = Modifier.padding(horizontal = 16.dp),
) {
PasswordField(
modifier = Modifier.fillMaxWidth(),
value = password,
onValueChange = onPasswordChange,
keyboardOptions = KeyboardOptions(imeAction = ImeAction.Done),
keyboardActions = KeyboardActions { focusManager.clearFocus() },
)
}
Spacer(modifier = Modifier.height(16.dp))
AnimatedOffset(
modifier = Modifier.padding(horizontal = 16.dp),
) {
CredentialRemember(
value = rememberPassword,
onValueChange = onRememberPasswordChange,
)
}
Spacer(modifier = Modifier.height(16.dp))
AnimatedOffset(
modifier = Modifier
.padding(horizontal = 16.dp)
.align(Alignment.End),
) {
Row {
Button(
colors = ButtonDefaults.outlinedButtonColors(),
onClick = onRegister,
) {
Text(text = stringResource(id = R.string.action_register))
}
Spacer(modifier = Modifier.width(8.dp))
Button(
colors = ButtonDefaults.buttonColors(),
onClick = onSignIn,
) {
Text(text = stringResource(id = R.string.action_login))
}
}
}
Spacer(modifier = Modifier.weight(2f))
AnimatedOffset {
Button(
modifier = Modifier
.padding(all = 16.dp)
.fillMaxWidth(),
colors = ButtonDefaults.outlinedButtonColors(),
onClick = onGoogleSignIn,
) {
Text(text = googleStringResource())
}
}
}
}
}
//////////////////////////////////////
// region: Content Helper Composable
@OptIn(ExperimentalComposeUiApi::class)
@Composable
private fun LoginField(
modifier: Modifier = Modifier,
value: String,
onValueChange: (String) -> Unit,
keyboardOptions: KeyboardOptions = KeyboardOptions.Default,
keyboardActions: KeyboardActions = KeyboardActions(),
) {
TextField(
modifier = modifier.autofill(
autofillTypes = listOf(AutofillType.Username, AutofillType.EmailAddress),
onFill = onValueChange,
),
value = value,
onValueChange = onValueChange,
label = { Text(text = stringResource(id = R.string.authentication_login)) },
colors = TextFieldDefaults.outlinedTextFieldColors(),
maxLines = 1,
singleLine = true,
keyboardOptions = keyboardOptions,
keyboardActions = keyboardActions,
)
}
@OptIn(ExperimentalComposeUiApi::class)
@Composable
private fun PasswordField(
modifier: Modifier = Modifier,
value: String,
onValueChange: (String) -> Unit,
keyboardOptions: KeyboardOptions = KeyboardOptions.Default,
keyboardActions: KeyboardActions = KeyboardActions(),
) {
var passwordVisibility by remember { mutableStateOf(false) }
TextField(
modifier = modifier.autofill(
autofillTypes = listOf(AutofillType.Password),
onFill = onValueChange,
),
value = value,
onValueChange = onValueChange,
label = { Text(text = stringResource(id = R.string.authentication_password)) },
colors = TextFieldDefaults.outlinedTextFieldColors(),
maxLines = 1,
singleLine = true,
visualTransformation = if (passwordVisibility) VisualTransformation.None else PasswordVisualTransformation(),
trailingIcon = {
IconButton(onClick = { passwordVisibility = passwordVisibility.not() }) {
Icon(
imageVector = if (passwordVisibility) Icons.Sharp.VisibilityOff else Icons.Sharp.Visibility,
contentDescription = null,
)
}
},
keyboardOptions = keyboardOptions,
keyboardActions = keyboardActions,
)
}
@Composable
private fun CredentialRemember(
modifier: Modifier = Modifier,
value: Boolean,
onValueChange: (Boolean) -> Unit,
) {
Row(
modifier = modifier.clickable(
interactionSource = remember { MutableInteractionSource() },
indication = null,
onClick = { onValueChange(value.not()) }
)
) {
Checkbox(
modifier = Modifier.align(Alignment.CenterVertically),
checked = value,
onCheckedChange = null
)
Spacer(modifier = Modifier.width(8.dp))
Text(
modifier = Modifier.align(Alignment.CenterVertically),
style = MaterialTheme.typography.caption,
color = MaterialTheme.colors.onBackground,
text = stringResource(id = R.string.authentication_credential_remember)
)
}
}
@Composable
private fun googleStringResource(): AnnotatedString {
val default = LocalTextStyle.current.toSpanStyle()
val google = stringResource(id = R.string.action_google_sign_in)
return remember {
buildAnnotatedString {
withStyle(
style = default
) {
append(google)
append(" ")
}
withStyle(
style = default.copy(
color = GoogleColorPalette.blue,
fontWeight = FontWeight.ExtraBold
),
) {
append("G")
}
withStyle(
style = default.copy(
color = GoogleColorPalette.red,
fontWeight = FontWeight.ExtraBold
),
) {
append("o")
}
withStyle(
style = default.copy(
color = GoogleColorPalette.yellow,
fontWeight = FontWeight.ExtraBold
),
) {
append("o")
}
withStyle(
style = default.copy(
color = GoogleColorPalette.blue,
fontWeight = FontWeight.ExtraBold
),
) {
append("g")
}
withStyle(
style = default.copy(
color = GoogleColorPalette.green,
fontWeight = FontWeight.ExtraBold
),
) {
append("l")
}
withStyle(
style = default.copy(
color = GoogleColorPalette.red,
fontWeight = FontWeight.ExtraBold
),
) {
append("e")
}
}
}
}
@Composable
@Preview(showBackground = true, uiMode = Configuration.UI_MODE_NIGHT_NO)
@Preview(showBackground = true, uiMode = Configuration.UI_MODE_NIGHT_YES)
private fun AuthenticationScreenContentPreview() {
BibLibTheme {
AuthenticationScreenContent(
login = "",
onLoginChange = { },
password = "",
onPasswordChange = { },
rememberPassword = true,
onRememberPasswordChange = { },
onGoogleSignIn = { },
onSignIn = { },
onRegister = { },
)
}
}

View file

@ -0,0 +1,47 @@
package com.pixelized.biblib.ui.screen.authentication.composable
import androidx.compose.foundation.clickable
import androidx.compose.foundation.interaction.MutableInteractionSource
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.width
import androidx.compose.material.Checkbox
import androidx.compose.material.MaterialTheme
import androidx.compose.material.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp
import com.pixelized.biblib.R
@Composable
fun CredentialRemember(
modifier: Modifier = Modifier,
value: Boolean,
onValueChange: (Boolean) -> Unit,
) {
Row(
modifier = modifier.clickable(
interactionSource = remember { MutableInteractionSource() },
indication = null,
onClick = { onValueChange(value.not()) }
)
) {
Checkbox(
modifier = Modifier.align(Alignment.CenterVertically),
checked = value,
onCheckedChange = null
)
Spacer(modifier = Modifier.width(8.dp))
Text(
modifier = Modifier.align(Alignment.CenterVertically),
style = MaterialTheme.typography.caption,
color = MaterialTheme.colors.onBackground,
text = stringResource(id = R.string.authentication_credential_remember)
)
}
}

View file

@ -0,0 +1,39 @@
package com.pixelized.biblib.ui.screen.authentication.composable
import androidx.compose.foundation.text.KeyboardActions
import androidx.compose.foundation.text.KeyboardOptions
import androidx.compose.material.Text
import androidx.compose.material.TextField
import androidx.compose.material.TextFieldDefaults
import androidx.compose.runtime.Composable
import androidx.compose.ui.ExperimentalComposeUiApi
import androidx.compose.ui.Modifier
import androidx.compose.ui.autofill.AutofillType
import androidx.compose.ui.res.stringResource
import com.pixelized.biblib.R
import com.pixelized.biblib.utils.extention.autofill
@OptIn(ExperimentalComposeUiApi::class)
@Composable
fun LoginField(
modifier: Modifier = Modifier,
value: String,
onValueChange: (String) -> Unit,
keyboardOptions: KeyboardOptions = KeyboardOptions.Default,
keyboardActions: KeyboardActions = KeyboardActions(),
) {
TextField(
modifier = modifier.autofill(
autofillTypes = listOf(AutofillType.Username, AutofillType.EmailAddress),
onFill = onValueChange,
),
value = value,
onValueChange = onValueChange,
label = { Text(text = stringResource(id = R.string.authentication_login)) },
colors = TextFieldDefaults.outlinedTextFieldColors(),
maxLines = 1,
singleLine = true,
keyboardOptions = keyboardOptions,
keyboardActions = keyboardActions,
)
}

View file

@ -0,0 +1,60 @@
package com.pixelized.biblib.ui.screen.authentication.composable
import androidx.compose.foundation.text.KeyboardActions
import androidx.compose.foundation.text.KeyboardOptions
import androidx.compose.material.Icon
import androidx.compose.material.IconButton
import androidx.compose.material.Text
import androidx.compose.material.TextField
import androidx.compose.material.TextFieldDefaults
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.sharp.Visibility
import androidx.compose.material.icons.sharp.VisibilityOff
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.ExperimentalComposeUiApi
import androidx.compose.ui.Modifier
import androidx.compose.ui.autofill.AutofillType
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.input.PasswordVisualTransformation
import androidx.compose.ui.text.input.VisualTransformation
import com.pixelized.biblib.R
import com.pixelized.biblib.utils.extention.autofill
@OptIn(ExperimentalComposeUiApi::class)
@Composable
fun PasswordField(
modifier: Modifier = Modifier,
value: String,
onValueChange: (String) -> Unit,
keyboardOptions: KeyboardOptions = KeyboardOptions.Default,
keyboardActions: KeyboardActions = KeyboardActions(),
) {
var passwordVisibility by remember { mutableStateOf(false) }
TextField(
modifier = modifier.autofill(
autofillTypes = listOf(AutofillType.Password),
onFill = onValueChange,
),
value = value,
onValueChange = onValueChange,
label = { Text(text = stringResource(id = R.string.authentication_password)) },
colors = TextFieldDefaults.outlinedTextFieldColors(),
maxLines = 1,
singleLine = true,
visualTransformation = if (passwordVisibility) VisualTransformation.None else PasswordVisualTransformation(),
trailingIcon = {
IconButton(onClick = { passwordVisibility = passwordVisibility.not() }) {
Icon(
imageVector = if (passwordVisibility) Icons.Sharp.VisibilityOff else Icons.Sharp.Visibility,
contentDescription = null,
)
}
},
keyboardOptions = keyboardOptions,
keyboardActions = keyboardActions,
)
}

View file

@ -0,0 +1,76 @@
package com.pixelized.biblib.ui.screen.authentication.composable
import androidx.compose.material.LocalTextStyle
import androidx.compose.runtime.Composable
import androidx.compose.runtime.remember
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.AnnotatedString
import androidx.compose.ui.text.buildAnnotatedString
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.withStyle
import com.pixelized.biblib.R
import com.pixelized.biblib.ui.theme.color.GoogleColorPalette
@Composable
fun googleStringResource(): AnnotatedString {
val default = LocalTextStyle.current.toSpanStyle()
val google = stringResource(id = R.string.action_google_sign_in)
return remember {
buildAnnotatedString {
withStyle(
style = default
) {
append(google)
append(" ")
}
withStyle(
style = default.copy(
color = GoogleColorPalette.blue,
fontWeight = FontWeight.ExtraBold
),
) {
append("G")
}
withStyle(
style = default.copy(
color = GoogleColorPalette.red,
fontWeight = FontWeight.ExtraBold
),
) {
append("o")
}
withStyle(
style = default.copy(
color = GoogleColorPalette.yellow,
fontWeight = FontWeight.ExtraBold
),
) {
append("o")
}
withStyle(
style = default.copy(
color = GoogleColorPalette.blue,
fontWeight = FontWeight.ExtraBold
),
) {
append("g")
}
withStyle(
style = default.copy(
color = GoogleColorPalette.green,
fontWeight = FontWeight.ExtraBold
),
) {
append("l")
}
withStyle(
style = default.copy(
color = GoogleColorPalette.red,
fontWeight = FontWeight.ExtraBold
),
) {
append("e")
}
}
}
}

View file

@ -2,6 +2,7 @@ package com.pixelized.biblib.ui.screen.authentication.viewModel
import androidx.compose.runtime.mutableStateOf
import androidx.lifecycle.ViewModel
import com.pixelized.biblib.BuildConfig
import com.pixelized.biblib.repository.credential.ICredentialRepository
import com.pixelized.biblib.ui.screen.authentication.AuthenticationFormUIO
import dagger.hilt.android.lifecycle.HiltViewModel
@ -56,8 +57,16 @@ class AuthenticationFormViewModel @Inject constructor(
}
private val ICredentialRepository.rememberedLogin: String
get() = if (rememberCredential) login ?: "" else ""
get() = when {
rememberCredential -> login ?: ""
BuildConfig.DEBUG -> "ThomasTest"
else -> ""
}
private val ICredentialRepository.rememberedPassword: String
get() = if (rememberCredential) password ?: "" else ""
get() = when {
rememberCredential -> password ?: ""
BuildConfig.DEBUG -> "MotDePasseRandom$2"
else -> ""
}
}

View file

@ -0,0 +1,16 @@
package com.pixelized.biblib.utils
import androidx.compose.foundation.interaction.Interaction
import androidx.compose.foundation.interaction.MutableInteractionSource
import androidx.compose.runtime.Stable
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.flowOf
@Stable
class DisableInteractionSource : MutableInteractionSource {
override val interactions: Flow<Interaction> = flowOf()
override suspend fun emit(interaction: Interaction) = Unit
override fun tryEmit(interaction: Interaction): Boolean = false
}

View file

@ -0,0 +1,15 @@
package com.pixelized.biblib.utils.extention.modifier
import androidx.compose.foundation.clickable
import androidx.compose.runtime.Stable
import androidx.compose.ui.Modifier
import com.pixelized.biblib.utils.DisableInteractionSource
@Stable
fun Modifier.unclickable(
disable: Boolean
): Modifier = if (disable) this.clickable(
interactionSource = DisableInteractionSource(),
indication = null,
onClick = { },
) else this

Binary file not shown.

After

Width:  |  Height:  |  Size: 273 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 312 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 324 KiB

View file

@ -37,7 +37,6 @@
<!-- Screens & pages -->
<string name="authentication_title">Authentification à BibLib</string>
<string name="authentication_login">Alias du compte</string>
<string name="authentication_password">Mot de passe</string>
<string name="authentication_credential_remember">Mémoriser mes identifiants</string>

View file

@ -50,7 +50,6 @@
<!-- Screens & pages -->
<string name="authentication_title">Sign in to BibLib</string>
<string name="authentication_login">Login</string>
<string name="authentication_password">Password</string>
<string name="authentication_credential_remember">Remember my credential</string>