-
-
Notifications
You must be signed in to change notification settings - Fork 2
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
# *Fix/issue 38 password keyboard behaviour* ## ♻️ Current situation & Problem #38 ## ⚙️ Release Notes - Added Password Toogle Icon to Login Screen - Added Login Form Validator - Added Login Error Cases - Switched to OutlinedTextFields ## ✅ Testing - Added CredentialRegisterManagerAuthTest - Added LoginViewModelTest - Added LoginFormValidatorTest ## 📝 Code of Conduct & Contributing Guidelines By submitting creating this pull request, you agree to follow our [Code of Conduct](https://github.com/StanfordSpezi/.github/blob/main/CODE_OF_CONDUCT.md) and [Contributing Guidelines](https://github.com/StanfordSpezi/.github/blob/main/CONTRIBUTING.md): - [x] I agree to follow the [Code of Conduct](https://github.com/StanfordSpezi/.github/blob/main/CODE_OF_CONDUCT.md) and [Contributing Guidelines](https://github.com/StanfordSpezi/.github/blob/main/CONTRIBUTING.md). --------- Signed-off-by: Basler182 <[email protected]>
- Loading branch information
Showing
20 changed files
with
925 additions
and
251 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
<vector xmlns:android="http://schemas.android.com/apk/res/android" | ||
android:width="24dp" | ||
android:height="24dp" | ||
android:tint="?attr/colorControlNormal" | ||
android:viewportWidth="960" | ||
android:viewportHeight="960"> | ||
<path | ||
android:fillColor="@android:color/black" | ||
android:pathData="M480,640Q555,640 607.5,587.5Q660,535 660,460Q660,385 607.5,332.5Q555,280 480,280Q405,280 352.5,332.5Q300,385 300,460Q300,535 352.5,587.5Q405,640 480,640ZM480,568Q435,568 403.5,536.5Q372,505 372,460Q372,415 403.5,383.5Q435,352 480,352Q525,352 556.5,383.5Q588,415 588,460Q588,505 556.5,536.5Q525,568 480,568ZM480,760Q334,760 214,678.5Q94,597 40,460Q94,323 214,241.5Q334,160 480,160Q626,160 746,241.5Q866,323 920,460Q866,597 746,678.5Q626,760 480,760ZM480,460Q480,460 480,460Q480,460 480,460Q480,460 480,460Q480,460 480,460Q480,460 480,460Q480,460 480,460Q480,460 480,460Q480,460 480,460ZM480,680Q593,680 687.5,620.5Q782,561 832,460Q782,359 687.5,299.5Q593,240 480,240Q367,240 272.5,299.5Q178,359 128,460Q178,561 272.5,620.5Q367,680 480,680Z" /> | ||
</vector> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
<vector xmlns:android="http://schemas.android.com/apk/res/android" | ||
android:width="24dp" | ||
android:height="24dp" | ||
android:tint="?attr/colorControlNormal" | ||
android:viewportWidth="960" | ||
android:viewportHeight="960"> | ||
<path | ||
android:fillColor="@android:color/black" | ||
android:pathData="M644,532L586,474Q595,427 559,386Q523,345 466,354L408,296Q425,288 442.5,284Q460,280 480,280Q555,280 607.5,332.5Q660,385 660,460Q660,480 656,497.5Q652,515 644,532ZM772,658L714,602Q752,573 781.5,538.5Q811,504 832,460Q782,359 688.5,299.5Q595,240 480,240Q451,240 423,244Q395,248 368,256L306,194Q347,177 390,168.5Q433,160 480,160Q631,160 749,243.5Q867,327 920,460Q897,519 859.5,569.5Q822,620 772,658ZM792,904L624,738Q589,749 553.5,754.5Q518,760 480,760Q329,760 211,676.5Q93,593 40,460Q61,407 93,361.5Q125,316 166,280L56,168L112,112L848,848L792,904ZM222,336Q193,362 169,393Q145,424 128,460Q178,561 271.5,620.5Q365,680 480,680Q500,680 519,677.5Q538,675 558,672L522,634Q511,637 501,638.5Q491,640 480,640Q405,640 352.5,587.5Q300,535 300,460Q300,449 301.5,439Q303,429 306,418L222,336ZM541,429L541,429Q541,429 541,429Q541,429 541,429Q541,429 541,429Q541,429 541,429Q541,429 541,429Q541,429 541,429ZM390,504Q390,504 390,504Q390,504 390,504L390,504Q390,504 390,504Q390,504 390,504Q390,504 390,504Q390,504 390,504Z" /> | ||
</vector> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
12 changes: 12 additions & 0 deletions
12
...les/account/src/main/kotlin/edu/stanford/spezi/module/account/login/LoginFormValidator.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,12 @@ | ||
package edu.stanford.spezi.module.account.login | ||
|
||
import edu.stanford.spezi.module.account.register.FormValidator | ||
import javax.inject.Inject | ||
|
||
internal class LoginFormValidator @Inject constructor() : FormValidator() { | ||
|
||
fun isFormValid(uiState: UiState): Boolean { | ||
return isValidEmail(uiState.email.value).isValid && | ||
isValidPassword(uiState.password.value).isValid | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -2,39 +2,54 @@ | |
|
||
package edu.stanford.spezi.module.account.login | ||
|
||
import androidx.compose.foundation.gestures.detectTapGestures | ||
import androidx.compose.foundation.layout.Arrangement | ||
import androidx.compose.foundation.layout.Column | ||
import androidx.compose.foundation.layout.Row | ||
import androidx.compose.foundation.layout.Spacer | ||
import androidx.compose.foundation.layout.fillMaxSize | ||
import androidx.compose.foundation.layout.fillMaxWidth | ||
import androidx.compose.foundation.layout.height | ||
import androidx.compose.foundation.layout.imePadding | ||
import androidx.compose.foundation.layout.padding | ||
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.icons.Icons | ||
import androidx.compose.material.icons.outlined.Email | ||
import androidx.compose.material.icons.outlined.Lock | ||
import androidx.compose.material3.Button | ||
import androidx.compose.material3.OutlinedTextField | ||
import androidx.compose.material3.Icon | ||
import androidx.compose.material3.IconButton | ||
import androidx.compose.material3.Text | ||
import androidx.compose.material3.TextButton | ||
import androidx.compose.runtime.Composable | ||
import androidx.compose.runtime.collectAsState | ||
import androidx.compose.runtime.getValue | ||
import androidx.compose.ui.Alignment | ||
import androidx.compose.ui.Modifier | ||
import androidx.compose.ui.input.pointer.pointerInput | ||
import androidx.compose.ui.platform.LocalSoftwareKeyboardController | ||
import androidx.compose.ui.res.painterResource | ||
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.tooling.preview.Preview | ||
import androidx.compose.ui.tooling.preview.PreviewParameter | ||
import androidx.compose.ui.tooling.preview.PreviewParameterProvider | ||
import androidx.hilt.navigation.compose.hiltViewModel | ||
import edu.stanford.spezi.core.design.component.validated.outlinedtextfield.ValidatedOutlinedTextField | ||
import edu.stanford.spezi.core.design.theme.Spacings | ||
import edu.stanford.spezi.core.design.theme.SpeziTheme | ||
import edu.stanford.spezi.core.design.theme.TextStyles.bodyLarge | ||
import edu.stanford.spezi.core.design.theme.TextStyles.titleLarge | ||
import edu.stanford.spezi.core.utils.extensions.testIdentifier | ||
import edu.stanford.spezi.module.account.login.components.SignInWithGoogleButton | ||
import edu.stanford.spezi.module.account.login.components.TextDivider | ||
import edu.stanford.spezi.module.account.register.FieldState | ||
import edu.stanford.spezi.module.account.register.IconLeadingContent | ||
import edu.stanford.spezi.core.design.R as DesignR | ||
|
||
@Composable | ||
fun LoginScreen( | ||
|
@@ -54,11 +69,23 @@ internal fun LoginScreen( | |
uiState: UiState, | ||
onAction: (Action) -> Unit, | ||
) { | ||
val keyboardController = LocalSoftwareKeyboardController.current | ||
|
||
Column( | ||
modifier = Modifier | ||
.testIdentifier(LoginScreenTestIdentifier.ROOT) | ||
.fillMaxSize() | ||
.padding(Spacings.medium), | ||
.imePadding() | ||
.verticalScroll(rememberScrollState()) | ||
.padding(Spacings.medium) | ||
.pointerInput(Unit) { | ||
detectTapGestures( | ||
onTap = { | ||
println("Hide Keyboard") | ||
keyboardController?.hide() | ||
} | ||
) | ||
}, | ||
horizontalAlignment = Alignment.CenterHorizontally, | ||
verticalArrangement = Arrangement.Center | ||
) { | ||
|
@@ -74,33 +101,57 @@ You may login to your existing account or create a new one if you don't have one | |
style = bodyLarge, | ||
) | ||
Spacer(modifier = Modifier.height(Spacings.large)) | ||
OutlinedTextField( | ||
modifier = Modifier.fillMaxWidth(), | ||
value = uiState.email, | ||
onValueChange = { email -> | ||
onAction(Action.TextFieldUpdate(email, TextFieldType.EMAIL)) | ||
}, | ||
label = { Text("E-Mail Address") }, | ||
singleLine = true, | ||
keyboardOptions = KeyboardOptions.Default.copy(imeAction = ImeAction.Next) | ||
) | ||
IconLeadingContent( | ||
icon = Icons.Outlined.Email, | ||
content = { | ||
ValidatedOutlinedTextField( | ||
modifier = Modifier.fillMaxWidth(), | ||
value = uiState.email.value, | ||
errorText = uiState.email.error, | ||
onValueChange = { email -> | ||
onAction(Action.TextFieldUpdate(email, TextFieldType.EMAIL)) | ||
}, | ||
labelText = "E-Mail Address", | ||
singleLine = true, | ||
keyboardOptions = KeyboardOptions.Default.copy(imeAction = ImeAction.Next) | ||
) | ||
}) | ||
Spacer(modifier = Modifier.height(Spacings.small)) | ||
OutlinedTextField( | ||
modifier = Modifier.fillMaxWidth(), | ||
value = uiState.password, | ||
onValueChange = { | ||
onAction(Action.TextFieldUpdate(it, TextFieldType.PASSWORD)) | ||
}, | ||
label = { Text("Password") }, | ||
singleLine = true, | ||
visualTransformation = if (uiState.passwordVisibility) { | ||
VisualTransformation.None | ||
} else { | ||
PasswordVisualTransformation() | ||
}, | ||
keyboardOptions = KeyboardOptions.Default.copy(imeAction = ImeAction.Done), | ||
keyboardActions = KeyboardActions(onDone = { onAction(Action.TogglePasswordVisibility) }) | ||
) | ||
IconLeadingContent( | ||
icon = Icons.Outlined.Lock, | ||
content = { | ||
ValidatedOutlinedTextField( | ||
modifier = Modifier.fillMaxWidth(), | ||
value = uiState.password.value, | ||
errorText = uiState.password.error, | ||
onValueChange = { | ||
onAction(Action.TextFieldUpdate(it, TextFieldType.PASSWORD)) | ||
}, | ||
labelText = "Password", | ||
visualTransformation = if (uiState.passwordVisibility) { | ||
VisualTransformation.None | ||
} else { | ||
PasswordVisualTransformation() | ||
}, | ||
keyboardOptions = KeyboardOptions.Default.copy(imeAction = ImeAction.Done), | ||
keyboardActions = KeyboardActions(onDone = { | ||
onAction(Action.PasswordSignInOrSignUp) | ||
}), | ||
trailingIcon = { | ||
IconButton(onClick = { onAction(Action.TogglePasswordVisibility) }) { | ||
val iconId = if (uiState.passwordVisibility) { | ||
DesignR.drawable.ic_visibility | ||
} else { | ||
DesignR.drawable.ic_visibility_off | ||
} | ||
Icon( | ||
painter = painterResource(id = iconId), | ||
contentDescription = if (uiState.passwordVisibility) "Hide password" else "Show password" | ||
) | ||
} | ||
} | ||
) | ||
}) | ||
TextButton( | ||
onClick = { | ||
onAction(Action.ForgotPassword) | ||
|
@@ -111,14 +162,10 @@ You may login to your existing account or create a new one if you don't have one | |
Spacer(modifier = Modifier.height(Spacings.medium)) | ||
Button( | ||
onClick = { | ||
if (uiState.isAlreadyRegistered) { | ||
onAction(Action.PasswordCredentialSignIn) | ||
} else { | ||
onAction(Action.NavigateToRegister) | ||
} | ||
onAction(Action.PasswordSignInOrSignUp) | ||
}, | ||
modifier = Modifier.fillMaxWidth(), | ||
enabled = uiState.email.isNotEmpty() && uiState.password.isNotEmpty() | ||
enabled = uiState.isPasswordSignInEnabled | ||
) { | ||
Text( | ||
text = if (uiState.isAlreadyRegistered) "Login" else "Register" | ||
|
@@ -145,11 +192,7 @@ You may login to your existing account or create a new one if you don't have one | |
Spacer(modifier = Modifier.height(Spacings.medium)) | ||
SignInWithGoogleButton( | ||
onButtonClick = { | ||
if (uiState.isAlreadyRegistered) { | ||
onAction(Action.GoogleSignIn) | ||
} else { | ||
onAction(Action.GoogleSignUp) | ||
} | ||
onAction(Action.GoogleSignInOrSignUp) | ||
}, | ||
isAlreadyRegistered = uiState.isAlreadyRegistered, | ||
) | ||
|
@@ -169,12 +212,12 @@ private fun LoginScreenPreview( | |
private class LoginScreenPreviewProvider : PreviewParameterProvider<UiState> { | ||
override val values: Sequence<UiState> = sequenceOf( | ||
UiState( | ||
email = "", | ||
password = "", | ||
email = FieldState(""), | ||
password = FieldState(""), | ||
passwordVisibility = false, | ||
), UiState( | ||
email = "[email protected]", | ||
password = "password", | ||
email = FieldState("[email protected]"), | ||
password = FieldState("password"), | ||
passwordVisibility = true, | ||
isAlreadyRegistered = true | ||
) | ||
|
Oops, something went wrong.