Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package com.amplifyframework.ui.authenticator.locals

import androidx.compose.runtime.compositionLocalOf
import com.amplifyframework.ui.authenticator.enums.AuthenticatorStep

/**
* This composition local supplies the current AuthenticatorStep. This allows descendant composables to tailor
* their content to specific steps.
*/
internal val LocalAuthenticatorStep = compositionLocalOf<AuthenticatorStep> { AuthenticatorStep.Loading }
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
import androidx.compose.material3.SnackbarHost
import androidx.compose.material3.SnackbarHostState
import androidx.compose.runtime.Composable
import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment
Expand All @@ -56,6 +57,7 @@
import com.amplifyframework.ui.authenticator.SignedInState
import com.amplifyframework.ui.authenticator.VerifyUserConfirmState
import com.amplifyframework.ui.authenticator.VerifyUserState
import com.amplifyframework.ui.authenticator.locals.LocalAuthenticatorStep
import com.amplifyframework.ui.authenticator.rememberAuthenticatorState
import com.amplifyframework.ui.authenticator.util.AuthenticatorMessage

Expand Down Expand Up @@ -140,31 +142,35 @@
Column(
modifier = Modifier.verticalScroll(rememberScrollState())
) {
headerContent()
when (targetState) {
is LoadingState -> loadingContent()
is SignInState -> signInContent(targetState)
is SignInConfirmMfaState -> signInConfirmMfaContent(targetState)
is SignInConfirmCustomState -> signInConfirmCustomContent(targetState)
is SignInConfirmNewPasswordState -> signInConfirmNewPasswordContent(
targetState
)
is SignInConfirmTotpCodeState -> signInConfirmTotpCodeContent(targetState)
is SignInContinueWithTotpSetupState -> signInContinueWithTotpSetupContent(targetState)
is SignInContinueWithEmailSetupState -> signInContinueWithEmailSetupContent(targetState)
is SignInContinueWithMfaSetupSelectionState ->
signInContinueWithMfaSetupSelectionContent(targetState)
is SignInContinueWithMfaSelectionState -> signInContinueWithMfaSelectionContent(targetState)
is SignUpState -> signUpContent(targetState)
is PasswordResetState -> passwordResetContent(targetState)
is PasswordResetConfirmState -> passwordResetConfirmContent(targetState)
is ErrorState -> errorContent(targetState)
is SignUpConfirmState -> signUpConfirmContent(targetState)
is VerifyUserState -> verifyUserContent(targetState)
is VerifyUserConfirmState -> verifyUserConfirmContent(targetState)
else -> Unit
CompositionLocalProvider(LocalAuthenticatorStep provides targetState.step) {
headerContent()
when (targetState) {

Check warning on line 147 in authenticator/src/main/java/com/amplifyframework/ui/authenticator/ui/Authenticator.kt

View check run for this annotation

Codecov / codecov/patch

authenticator/src/main/java/com/amplifyframework/ui/authenticator/ui/Authenticator.kt#L145-L147

Added lines #L145 - L147 were not covered by tests
is LoadingState -> loadingContent()
is SignInState -> signInContent(targetState)
is SignInConfirmMfaState -> signInConfirmMfaContent(targetState)
is SignInConfirmCustomState -> signInConfirmCustomContent(targetState)
is SignInConfirmNewPasswordState -> signInConfirmNewPasswordContent(
targetState

Check warning on line 153 in authenticator/src/main/java/com/amplifyframework/ui/authenticator/ui/Authenticator.kt

View check run for this annotation

Codecov / codecov/patch

authenticator/src/main/java/com/amplifyframework/ui/authenticator/ui/Authenticator.kt#L153

Added line #L153 was not covered by tests
)

is SignInConfirmTotpCodeState -> signInConfirmTotpCodeContent(targetState)
is SignInContinueWithTotpSetupState -> signInContinueWithTotpSetupContent(targetState)
is SignInContinueWithEmailSetupState -> signInContinueWithEmailSetupContent(targetState)
is SignInContinueWithMfaSetupSelectionState ->
signInContinueWithMfaSetupSelectionContent(targetState)

Check warning on line 160 in authenticator/src/main/java/com/amplifyframework/ui/authenticator/ui/Authenticator.kt

View check run for this annotation

Codecov / codecov/patch

authenticator/src/main/java/com/amplifyframework/ui/authenticator/ui/Authenticator.kt#L160

Added line #L160 was not covered by tests

is SignInContinueWithMfaSelectionState -> signInContinueWithMfaSelectionContent(targetState)
is SignUpState -> signUpContent(targetState)
is PasswordResetState -> passwordResetContent(targetState)
is PasswordResetConfirmState -> passwordResetConfirmContent(targetState)
is ErrorState -> errorContent(targetState)
is SignUpConfirmState -> signUpConfirmContent(targetState)
is VerifyUserState -> verifyUserContent(targetState)
is VerifyUserConfirmState -> verifyUserConfirmContent(targetState)
else -> Unit

Check warning on line 170 in authenticator/src/main/java/com/amplifyframework/ui/authenticator/ui/Authenticator.kt

View check run for this annotation

Codecov / codecov/patch

authenticator/src/main/java/com/amplifyframework/ui/authenticator/ui/Authenticator.kt#L170

Added line #L170 was not covered by tests
}
footerContent()

Check warning on line 172 in authenticator/src/main/java/com/amplifyframework/ui/authenticator/ui/Authenticator.kt

View check run for this annotation

Codecov / codecov/patch

authenticator/src/main/java/com/amplifyframework/ui/authenticator/ui/Authenticator.kt#L172

Added line #L172 was not covered by tests
}
footerContent()
}
}
SnackbarHost(hostState = snackbarState, modifier = Modifier.align(Alignment.BottomCenter))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ internal fun AuthenticatorForm(
) {
state.fields.values.forEach { field ->
AuthenticatorField(
modifier = Modifier.fillMaxWidth().testTag(field.config.key.toString()),
modifier = Modifier.fillMaxWidth().testTag(field.config.key.testTag),
fieldConfig = field.config,
fieldState = field.state,
formState = state
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@
import androidx.compose.ui.draw.clip
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.IntOffset
import androidx.compose.ui.unit.dp

/**
Expand All @@ -58,10 +59,7 @@
}

@Composable
private fun LoadingDot(
modifier: Modifier = Modifier,
color: Color
) {
private fun LoadingDot(modifier: Modifier = Modifier, color: Color) {
Box(
modifier = modifier
.clip(shape = CircleShape)
Expand Down Expand Up @@ -101,7 +99,7 @@
modifier = Modifier
.size(dotSize)
.aspectRatio(1f)
.offset(y = offset.dp)
.offset { IntOffset(x = 0, y = offset.dp.roundToPx()) }

Check warning on line 102 in authenticator/src/main/java/com/amplifyframework/ui/authenticator/ui/AuthenticatorLoading.kt

View check run for this annotation

Codecov / codecov/patch

authenticator/src/main/java/com/amplifyframework/ui/authenticator/ui/AuthenticatorLoading.kt#L102

Added line #L102 was not covered by tests
)
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ import com.amplifyframework.ui.authenticator.R
import com.amplifyframework.ui.authenticator.forms.FieldConfig
import com.amplifyframework.ui.authenticator.forms.MutablePasswordFieldState
import com.amplifyframework.ui.authenticator.strings.StringResolver
import com.amplifyframework.ui.authenticator.util.contentTypeForKey

@Composable
internal fun PasswordInputField(
Expand All @@ -55,7 +56,7 @@ internal fun PasswordInputField(
)

OutlinedTextField(
modifier = modifier,
modifier = modifier.contentTypeForKey(fieldConfig.key),
enabled = enabled,
value = fieldState.content,
onValueChange = { fieldState.content = it },
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@ import androidx.compose.runtime.Composable
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalAutofillManager
import androidx.compose.ui.platform.testTag
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp
import com.amplifyframework.auth.AuthCodeDeliveryDetails
Expand Down Expand Up @@ -62,12 +64,18 @@ fun PasswordResetConfirm(
}

@Composable
fun PasswordResetConfirmFooter(
state: PasswordResetConfirmState,
modifier: Modifier = Modifier
) {
fun PasswordResetConfirmFooter(state: PasswordResetConfirmState, modifier: Modifier = Modifier) {
// If we navigate away from the screen via some reason other that submitting the form then cancel any changes
// in autofill manager so that user won't be prompted to update their saved password
val autofillManager = LocalAutofillManager.current
Column(modifier = modifier.fillMaxWidth(), horizontalAlignment = Alignment.CenterHorizontally) {
TextButton(onClick = { state.moveTo(AuthenticatorStep.SignIn) }) {
TextButton(
modifier = Modifier.testTag(TestTags.BackToSignInButton),
onClick = {
autofillManager?.cancel()
state.moveTo(AuthenticatorStep.SignIn)
}
) {
Text(stringResource(R.string.amplify_ui_authenticator_button_back_to_signin))
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,7 @@
import com.amplifyframework.ui.authenticator.forms.MutableFieldState
import com.amplifyframework.ui.authenticator.strings.StringResolver
import com.amplifyframework.ui.authenticator.util.Region
import com.amplifyframework.ui.authenticator.util.contentTypeForKey
import com.amplifyframework.ui.authenticator.util.regionList
import com.amplifyframework.ui.authenticator.util.regionMap
import java.util.Locale
Expand Down Expand Up @@ -102,7 +103,7 @@
}

OutlinedTextField(
modifier = modifier,
modifier = modifier.contentTypeForKey(fieldConfig.key),

Check warning on line 106 in authenticator/src/main/java/com/amplifyframework/ui/authenticator/ui/PhoneInputField.kt

View check run for this annotation

Codecov / codecov/patch

authenticator/src/main/java/com/amplifyframework/ui/authenticator/ui/PhoneInputField.kt#L106

Added line #L106 was not covered by tests
enabled = enabled,
value = state.number,
onValueChange = { state.number = it },
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import androidx.compose.material3.TextButton
import androidx.compose.runtime.Composable
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalAutofillManager
import androidx.compose.ui.platform.testTag
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp
Expand Down Expand Up @@ -61,20 +62,31 @@ fun SignIn(
}

@Composable
fun SignInFooter(
state: SignInState,
modifier: Modifier = Modifier,
hideSignUp: Boolean = false
) {
fun SignInFooter(state: SignInState, modifier: Modifier = Modifier, hideSignUp: Boolean = false) {
// If we navigate away from the screen via some reason other that submitting the form then cancel any changes
// in autofill manager so that user won't be prompted to update their saved password
val autofillManager = LocalAutofillManager.current
Row(
modifier = modifier.fillMaxWidth(),
horizontalArrangement = Arrangement.SpaceEvenly
) {
TextButton(onClick = { state.moveTo(AuthenticatorStep.PasswordReset) }) {
TextButton(
modifier = Modifier.testTag(TestTags.ForgotPasswordButton),
onClick = {
autofillManager?.cancel()
state.moveTo(AuthenticatorStep.PasswordReset)
}
) {
Text(stringResource(R.string.amplify_ui_authenticator_button_forgot_password))
}
if (!hideSignUp) {
TextButton(onClick = { state.moveTo(AuthenticatorStep.SignUp) }) {
TextButton(
modifier = Modifier.testTag(TestTags.CreateAccountButton),
onClick = {
autofillManager?.cancel()
state.moveTo(AuthenticatorStep.SignUp)
}
) {
Text(stringResource(R.string.amplify_ui_authenticator_button_signup))
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import androidx.compose.runtime.Composable
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalAutofillManager
import androidx.compose.ui.platform.testTag
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp
Expand Down Expand Up @@ -59,12 +60,18 @@ fun SignUp(
}

@Composable
fun SignUpFooter(
state: SignUpState,
modifier: Modifier = Modifier
) {
fun SignUpFooter(state: SignUpState, modifier: Modifier = Modifier) {
// If we navigate away from the screen via some reason other that submitting the form then cancel any changes
// in autofill manager so that user won't be prompted to update their saved password
val autofillManager = LocalAutofillManager.current
Box(modifier = modifier.fillMaxWidth(), contentAlignment = Alignment.Center) {
TextButton(onClick = { state.moveTo(AuthenticatorStep.SignIn) }) {
TextButton(
modifier = Modifier.testTag(TestTags.BackToSignInButton),
onClick = {
autofillManager?.cancel()
state.moveTo(AuthenticatorStep.SignIn)
}
) {
Text(stringResource(R.string.amplify_ui_authenticator_button_back_to_signin))
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,14 +15,22 @@

package com.amplifyframework.ui.authenticator.ui

import com.amplifyframework.ui.authenticator.forms.FieldKey

@Suppress("ConstPropertyName")
internal object TestTags {
const val SignInConfirmButton = "SignInConfirmButton"
const val BackToSignInButton = "BackToSignInButton"
const val CopyKeyButton = "CopyKeyButton"
const val SignInButton = "SignInButton"
const val SignUpButton = "SignUpButton"
const val ForgotPasswordButton = "ForgotPasswordButton"
const val CreateAccountButton = "CreateAccountButton"
const val PasswordResetButton = "PasswordResetButton"
const val AuthenticatorTitle = "AuthenticatorTitle"

const val ShowPasswordIcon = "ShowPasswordIcon"
}

internal val FieldKey.testTag: String
get() = this.toString()
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ import androidx.compose.ui.text.input.ImeAction
import com.amplifyframework.ui.authenticator.forms.FieldConfig
import com.amplifyframework.ui.authenticator.forms.MutableFieldState
import com.amplifyframework.ui.authenticator.strings.StringResolver
import com.amplifyframework.ui.authenticator.util.contentTypeForKey

@Composable
internal fun TextInputField(
Expand All @@ -40,8 +41,9 @@ internal fun TextInputField(

val label = StringResolver.label(fieldConfig)
val hint = StringResolver.hint(fieldConfig)

OutlinedTextField(
modifier = modifier,
modifier = modifier.contentTypeForKey(fieldConfig.key),
enabled = enabled,
value = fieldState.content,
onValueChange = { fieldState.content = it.take(fieldConfig.maxLength) },
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
package com.amplifyframework.ui.authenticator.util

import androidx.compose.runtime.Composable
import androidx.compose.runtime.ReadOnlyComposable
import androidx.compose.ui.Modifier
import androidx.compose.ui.autofill.ContentType
import androidx.compose.ui.semantics.contentType
import androidx.compose.ui.semantics.semantics
import com.amplifyframework.ui.authenticator.enums.AuthenticatorStep
import com.amplifyframework.ui.authenticator.forms.FieldKey
import com.amplifyframework.ui.authenticator.locals.LocalAuthenticatorStep

@Composable
@ReadOnlyComposable
internal fun Modifier.contentTypeForKey(key: FieldKey): Modifier {
val derivedContentType = key.deriveContentType()
return this.semantics { if (derivedContentType != null) contentType = derivedContentType }
}

@Composable
@ReadOnlyComposable
internal fun FieldKey.deriveContentType(): ContentType? {
val step = LocalAuthenticatorStep.current
return when (this) {
is FieldKey.Username -> if (step.isNewUsername()) ContentType.NewUsername else ContentType.Username
is FieldKey.Password -> if (step.isNewPassword()) ContentType.NewPassword else ContentType.Password
is FieldKey.ConfirmPassword -> ContentType.NewPassword
is FieldKey.ConfirmationCode -> ContentType.SmsOtpCode
is FieldKey.Email -> ContentType.EmailAddress
is FieldKey.PhoneNumber -> ContentType.PhoneNumber
else -> null

Check warning on line 31 in authenticator/src/main/java/com/amplifyframework/ui/authenticator/util/Autofill.kt

View check run for this annotation

Codecov / codecov/patch

authenticator/src/main/java/com/amplifyframework/ui/authenticator/util/Autofill.kt#L31

Added line #L31 was not covered by tests
}
}

private fun AuthenticatorStep.isNewUsername() = this is AuthenticatorStep.SignUp
private fun AuthenticatorStep.isNewPassword() =
this is AuthenticatorStep.SignUp || this is AuthenticatorStep.PasswordResetConfirm
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
package com.amplifyframework.ui.authenticator

import com.amplifyframework.auth.AuthCodeDeliveryDetails
import com.amplifyframework.auth.AuthCodeDeliveryDetails.DeliveryMedium
import com.amplifyframework.auth.AuthException
import com.amplifyframework.auth.AuthFactorType
import com.amplifyframework.auth.AuthSession
Expand Down Expand Up @@ -138,3 +139,13 @@ internal fun mockUserAttributes(vararg attribute: Pair<AuthUserAttributeKey, Str
attribute.map { AuthUserAttribute(it.first, it.second) }

internal fun mockUser(userId: String = "userId", username: String = "username") = AuthUser(userId, username)

internal fun mockAuthCodeDeliveryDetails(
destination: String = "123-123-1234",
deliveryMedium: DeliveryMedium = DeliveryMedium.SMS,
attributeName: String? = AuthUserAttributeKey.phoneNumber().keyString
) = AuthCodeDeliveryDetails(
destination,
deliveryMedium,
attributeName
)
Loading