Settling in on an auth screen design that I like...

This commit is contained in:
Z. Charles Dziura 2024-08-22 12:59:10 -04:00
parent 01dbcbcc4a
commit 4c752b4a0f
6 changed files with 138 additions and 232 deletions

View file

@ -31,7 +31,7 @@ android {
)
}
debug {
buildConfigField("String", "API_BASE_URL", "\"http://localhost:42069\"")
buildConfigField("String", "API_BASE_URL", "\"http://10.0.2.2:42069\"")
}
}
compileOptions {

View file

@ -8,16 +8,33 @@ import androidx.compose.foundation.layout.PaddingValues
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.padding
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.text.KeyboardOptions
import androidx.compose.foundation.verticalScroll
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.outlined.Key
import androidx.compose.material.icons.outlined.Mail
import androidx.compose.material.icons.outlined.Person
import androidx.compose.material3.Button
import androidx.compose.material3.Icon
import androidx.compose.material3.OutlinedButton
import androidx.compose.material3.OutlinedTextField
import androidx.compose.material3.Scaffold
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.State
import androidx.compose.runtime.collectAsState
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.text.input.ImeAction
import androidx.compose.ui.text.input.KeyboardCapitalization
import androidx.compose.ui.text.input.KeyboardType
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.dp
import software.patchwork.debtpirate.R
@ -28,48 +45,143 @@ fun AuthScreen(
) {
Scaffold(
modifier = Modifier.fillMaxSize()
) { innerPadding ->
) {
Column {
AuthScreenTopAppBar(
innerPadding,
modifier = Modifier
.fillMaxWidth()
.weight(1f)
)
val displayName = viewModel.displayName.collectAsState()
val emailAddress = viewModel.emailAddress.collectAsState()
SignUpScreen(
displayName,
onDisplayNameUpdate = viewModel::updateDisplayName,
emailAddress,
onEmailAddressUpdate = viewModel::updateEmailAddress,
Column(
modifier = Modifier
.weight(3f)
.padding(16.dp)
) {
val displayName = viewModel.displayName.collectAsState()
val emailAddress = viewModel.emailAddress.collectAsState()
RegistrationScreen(
displayName,
onDisplayNameUpdate = viewModel::updateDisplayName,
emailAddress,
onEmailAddressUpdate = viewModel::updateEmailAddress
)
Separator(modifier = Modifier.padding(PaddingValues(top = 24.dp, bottom = 24.dp)))
PassKeyButton()
}
}
}
}
@Composable
private fun AuthScreenTopAppBar(innerPadding: PaddingValues, modifier: Modifier = Modifier) {
private fun AuthScreenTopAppBar(modifier: Modifier = Modifier) {
Box(modifier = modifier.background(Color.Green)) {
Text(
text = "Hello from Login! I'm in a box!",
modifier = Modifier.align(Alignment.Center)
)
Row(
modifier = Modifier
.align(Alignment.TopEnd)
.padding(innerPadding)
.padding(16.dp)
)
{
OutlinedButton(onClick = { /*TODO*/ }) {
Text(stringResource(id = R.string.login_screen__sign_in))
}
}
@Composable
private fun Separator(modifier: Modifier = Modifier) {
Row(modifier = modifier.fillMaxWidth(), verticalAlignment = Alignment.CenterVertically) {
Box(
modifier = Modifier
.height(2.dp)
.weight(4.5f)
.background(Color.LightGray)
) {}
Text(
text = "OR",
style = TextStyle(
textAlign = TextAlign.Center,
color = Color.Gray
),
modifier = Modifier.weight(1f)
)
Box(
modifier = Modifier
.height(2.dp)
.weight(4.5f)
.background(Color.LightGray)
) {}
}
}
@Composable
private fun RegistrationScreen(
displayName: State<String>,
onDisplayNameUpdate: (String) -> Unit,
emailAddress: State<String>,
onEmailAddressUpdate: (String) -> Unit,
modifier: Modifier = Modifier
) {
Column(modifier.verticalScroll(rememberScrollState()))
{
OutlinedTextField(
value = displayName.value,
label = { Text(stringResource(id = R.string.auth_screen__display_name)) },
placeholder = { Text(stringResource(id = R.string.auth_screen__display_name)) },
leadingIcon = { Icon(Icons.Outlined.Person, "person") },
singleLine = true,
keyboardOptions = KeyboardOptions(
capitalization = KeyboardCapitalization.Words,
keyboardType = KeyboardType.Text,
imeAction = ImeAction.Next
),
onValueChange = onDisplayNameUpdate,
modifier = Modifier.fillMaxWidth()
)
OutlinedTextField(
value = emailAddress.value,
label = { Text(stringResource(id = R.string.auth_screen__email)) },
placeholder = { Text(stringResource(id = R.string.auth_screen__email)) },
leadingIcon = { Icon(Icons.Outlined.Mail, "email") },
singleLine = true,
keyboardOptions = KeyboardOptions(
capitalization = KeyboardCapitalization.Unspecified,
keyboardType = KeyboardType.Email,
imeAction = ImeAction.Next
),
onValueChange = onEmailAddressUpdate,
modifier = Modifier
.fillMaxWidth()
.padding(PaddingValues(top = 8.dp))
)
Button(
onClick = { /*TODO*/ },
modifier = Modifier
.padding(PaddingValues(top = 16.dp))
.fillMaxWidth()
) {
Text(stringResource(id = R.string.auth_screen__register))
}
}
}
@Composable
private fun PassKeyButton() {
OutlinedButton(
onClick = { /*TODO*/ },
modifier = Modifier
.fillMaxWidth()
) {
Box(modifier = Modifier.fillMaxWidth()) {
Icon(Icons.Outlined.Key, "passkey")
Text(
stringResource(id = R.string.auth_screen__sign_in),
textAlign = TextAlign.Center,
modifier = Modifier
.fillMaxWidth()
.align(Alignment.Center)
)
}
}
}

View file

@ -1,124 +0,0 @@
package software.patchwork.debtpirate.screens.auth
import android.util.Log
import androidx.compose.foundation.interaction.FocusInteraction
import androidx.compose.foundation.interaction.MutableInteractionSource
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.text.KeyboardOptions
import androidx.compose.foundation.verticalScroll
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.outlined.Lock
import androidx.compose.material.icons.outlined.Person
import androidx.compose.material3.Button
import androidx.compose.material3.Icon
import androidx.compose.material3.OutlinedTextField
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.remember
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.LinkAnnotation
import androidx.compose.ui.text.SpanStyle
import androidx.compose.ui.text.TextLinkStyles
import androidx.compose.ui.text.buildAnnotatedString
import androidx.compose.ui.text.input.ImeAction
import androidx.compose.ui.text.input.KeyboardCapitalization
import androidx.compose.ui.text.input.KeyboardType
import androidx.compose.ui.text.input.PasswordVisualTransformation
import androidx.compose.ui.text.withLink
import androidx.compose.ui.unit.dp
import kotlinx.coroutines.flow.StateFlow
import software.patchwork.debtpirate.R
@Composable
internal fun SignInScreen(
usernameState: StateFlow<String>,
passwordState: StateFlow<String>,
onUsernameUpdate: (String) -> Unit,
onPasswordUpdate: (String) -> Unit,
modifier: Modifier = Modifier,
) {
Column(
modifier.verticalScroll(rememberScrollState())
) {
val username = usernameState.collectAsState()
OutlinedTextField(
value = username.value,
label = { Text(stringResource(id = R.string.login_screen__username)) },
placeholder = { Text(stringResource(id = R.string.login_screen__username)) },
leadingIcon = { Icon(Icons.Outlined.Person, "person") },
singleLine = true,
keyboardOptions = KeyboardOptions(
capitalization = KeyboardCapitalization.Unspecified,
keyboardType = KeyboardType.Email,
imeAction = ImeAction.Next
),
onValueChange = onUsernameUpdate,
interactionSource = remember { MutableInteractionSource() }.also { source ->
LaunchedEffect(source) {
source.interactions.collect { interaction ->
when (interaction) {
is FocusInteraction -> Log.v(
"LoginScreen::LoginForm::usernameTextField",
"Clicked!"
)
}
}
}
},
modifier = Modifier
.fillMaxWidth()
)
val password = passwordState.collectAsState()
OutlinedTextField(
value = password.value,
label = { Text(stringResource(id = R.string.login_screen__password)) },
placeholder = { Text(stringResource(id = R.string.login_screen__password)) },
leadingIcon = { Icon(Icons.Outlined.Lock, "password") },
singleLine = true,
keyboardOptions = KeyboardOptions(
capitalization = KeyboardCapitalization.Unspecified,
keyboardType = KeyboardType.Password,
imeAction = ImeAction.Send
),
visualTransformation = PasswordVisualTransformation(),
onValueChange = onPasswordUpdate,
modifier = Modifier
.fillMaxWidth()
.padding(PaddingValues(top = 8.dp))
)
Text(
text = buildAnnotatedString {
withLink(
LinkAnnotation.Url(
"", TextLinkStyles(
style = SpanStyle(
color = Color.Gray
)
)
)
) {
append(stringResource(id = R.string.login_screen__forgot_password))
}
},
modifier = Modifier.padding(PaddingValues(top = 16.dp))
)
Button(
onClick = { /*TODO*/ },
modifier = Modifier
.padding(PaddingValues(top = 32.dp))
.fillMaxWidth()
) {
Text(stringResource(id = R.string.login_screen__sign_in))
}
}
}

View file

@ -1,78 +0,0 @@
package software.patchwork.debtpirate.screens.auth
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.text.KeyboardOptions
import androidx.compose.foundation.verticalScroll
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.outlined.Mail
import androidx.compose.material.icons.outlined.Person
import androidx.compose.material3.Button
import androidx.compose.material3.Icon
import androidx.compose.material3.OutlinedTextField
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.State
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.input.ImeAction
import androidx.compose.ui.text.input.KeyboardCapitalization
import androidx.compose.ui.text.input.KeyboardType
import androidx.compose.ui.unit.dp
import software.patchwork.debtpirate.R
@Composable
internal fun SignUpScreen(
displayName: State<String>,
onDisplayNameUpdate: (String) -> Unit,
emailAddress: State<String>,
onEmailAddressUpdate: (String) -> Unit,
modifier: Modifier = Modifier
) {
Column(modifier.verticalScroll(rememberScrollState()))
{
OutlinedTextField(
value = displayName.value,
label = { Text(stringResource(id = R.string.login_screen__display_name)) },
placeholder = { Text(stringResource(id = R.string.login_screen__display_name)) },
leadingIcon = { Icon(Icons.Outlined.Person, "person") },
singleLine = true,
keyboardOptions = KeyboardOptions(
capitalization = KeyboardCapitalization.Words,
keyboardType = KeyboardType.Text,
imeAction = ImeAction.Next
),
onValueChange = onDisplayNameUpdate,
modifier = Modifier.fillMaxWidth()
)
OutlinedTextField(
value = emailAddress.value,
label = { Text(stringResource(id = R.string.login_screen__email)) },
placeholder = { Text(stringResource(id = R.string.login_screen__email)) },
leadingIcon = { Icon(Icons.Outlined.Mail, "email") },
singleLine = true,
keyboardOptions = KeyboardOptions(
capitalization = KeyboardCapitalization.Unspecified,
keyboardType = KeyboardType.Email,
imeAction = ImeAction.Next
),
onValueChange = onEmailAddressUpdate,
modifier = Modifier
.fillMaxWidth()
.padding(PaddingValues(top = 8.dp))
)
Button(
onClick = { /*TODO*/ },
modifier = Modifier
.padding(PaddingValues(top = 32.dp))
.fillMaxWidth()
) {
Text(stringResource(id = R.string.login_screen__signup))
}
}
}

View file

@ -1,14 +1,10 @@
<resources>
<string name="app_name">Debt Pirate</string>
<!-- Login Screen -->
<string name="login_screen__sign_in">Sign In</string>
<string name="login_screen__signup">Sign Up</string>
<string name="login_screen__username">Username</string>
<string name="login_screen__email">Email Address</string>
<string name="login_screen__password">Password</string>
<string name="login_screen__confirm_password">Confirm Password</string>
<string name="login_screen__display_name">Display Name</string>
<string name="login_screen__forgot_password">Forgot Password?</string>
<!-- Auth Screen -->
<string name="auth_screen__register">Register</string>
<string name="auth_screen__sign_in">Sign in with a Passkey</string>
<string name="auth_screen__email">Email Address</string>
<string name="auth_screen__display_name">Display Name</string>
</resources>

View file

@ -30,7 +30,7 @@ androidx-espresso-core = { group = "androidx.test.espresso", name = "espresso-co
androidx-junit = { group = "androidx.test.ext", name = "junit", version.ref = "junitVersion" }
androidx-lifecycle-runtime-ktx = { group = "androidx.lifecycle", name = "lifecycle-runtime-ktx", version.ref = "lifecycleRuntimeKtx" }
androidx-lifecycle-viewmodel-compose = { group = "androidx.lifecycle", name = "lifecycle-viewmodel-compose", version.ref = "lifecycleViewmodelCompose" }
androidx-material-icons-extended = { group = "androidx.compose.material", name = "material-icons-extended-android", version.ref = "iconsExtended" }
androidx-material-icons-extended = { group = "androidx.compose.material", name = "material-icons-extended", version.ref = "iconsExtended" }
androidx-navigation-compose = { group = "androidx.navigation", name = "navigation-compose", version.ref = "navigation" }
androidx-navigation-testing = { group = "androidx.navigation", name = "navigation-testing", version.ref = "navigation" }
androidx-ui = { group = "androidx.compose.ui", name = "ui" }