diff --git a/authenticator/src/main/java/com/amplifyframework/ui/authenticator/locals/LocalAuthenticatorStep.kt b/authenticator/src/main/java/com/amplifyframework/ui/authenticator/locals/LocalAuthenticatorStep.kt new file mode 100644 index 00000000..22f8fb01 --- /dev/null +++ b/authenticator/src/main/java/com/amplifyframework/ui/authenticator/locals/LocalAuthenticatorStep.kt @@ -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.Loading } diff --git a/authenticator/src/main/java/com/amplifyframework/ui/authenticator/ui/Authenticator.kt b/authenticator/src/main/java/com/amplifyframework/ui/authenticator/ui/Authenticator.kt index 1abe05da..ef00f113 100644 --- a/authenticator/src/main/java/com/amplifyframework/ui/authenticator/ui/Authenticator.kt +++ b/authenticator/src/main/java/com/amplifyframework/ui/authenticator/ui/Authenticator.kt @@ -31,6 +31,7 @@ import androidx.compose.foundation.verticalScroll 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 @@ -56,6 +57,7 @@ import com.amplifyframework.ui.authenticator.SignUpState 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 @@ -140,31 +142,35 @@ fun Authenticator( 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) { + 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 + } + footerContent() } - footerContent() } } SnackbarHost(hostState = snackbarState, modifier = Modifier.align(Alignment.BottomCenter)) diff --git a/authenticator/src/main/java/com/amplifyframework/ui/authenticator/ui/AuthenticatorForm.kt b/authenticator/src/main/java/com/amplifyframework/ui/authenticator/ui/AuthenticatorForm.kt index ab7a8e6f..57b31c6a 100644 --- a/authenticator/src/main/java/com/amplifyframework/ui/authenticator/ui/AuthenticatorForm.kt +++ b/authenticator/src/main/java/com/amplifyframework/ui/authenticator/ui/AuthenticatorForm.kt @@ -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 diff --git a/authenticator/src/main/java/com/amplifyframework/ui/authenticator/ui/AuthenticatorLoading.kt b/authenticator/src/main/java/com/amplifyframework/ui/authenticator/ui/AuthenticatorLoading.kt index 3346312f..7a681348 100644 --- a/authenticator/src/main/java/com/amplifyframework/ui/authenticator/ui/AuthenticatorLoading.kt +++ b/authenticator/src/main/java/com/amplifyframework/ui/authenticator/ui/AuthenticatorLoading.kt @@ -40,6 +40,7 @@ import androidx.compose.ui.Modifier 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 /** @@ -58,10 +59,7 @@ fun AuthenticatorLoading(modifier: Modifier = Modifier) { } @Composable -private fun LoadingDot( - modifier: Modifier = Modifier, - color: Color -) { +private fun LoadingDot(modifier: Modifier = Modifier, color: Color) { Box( modifier = modifier .clip(shape = CircleShape) @@ -101,7 +99,7 @@ internal fun LoadingIndicator( modifier = Modifier .size(dotSize) .aspectRatio(1f) - .offset(y = offset.dp) + .offset { IntOffset(x = 0, y = offset.dp.roundToPx()) } ) } } diff --git a/authenticator/src/main/java/com/amplifyframework/ui/authenticator/ui/PasswordInputField.kt b/authenticator/src/main/java/com/amplifyframework/ui/authenticator/ui/PasswordInputField.kt index 8483a41a..ab858d65 100644 --- a/authenticator/src/main/java/com/amplifyframework/ui/authenticator/ui/PasswordInputField.kt +++ b/authenticator/src/main/java/com/amplifyframework/ui/authenticator/ui/PasswordInputField.kt @@ -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( @@ -55,7 +56,7 @@ internal fun PasswordInputField( ) OutlinedTextField( - modifier = modifier, + modifier = modifier.contentTypeForKey(fieldConfig.key), enabled = enabled, value = fieldState.content, onValueChange = { fieldState.content = it }, diff --git a/authenticator/src/main/java/com/amplifyframework/ui/authenticator/ui/PasswordResetConfirm.kt b/authenticator/src/main/java/com/amplifyframework/ui/authenticator/ui/PasswordResetConfirm.kt index e2406576..696f5d0d 100644 --- a/authenticator/src/main/java/com/amplifyframework/ui/authenticator/ui/PasswordResetConfirm.kt +++ b/authenticator/src/main/java/com/amplifyframework/ui/authenticator/ui/PasswordResetConfirm.kt @@ -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 @@ -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)) } } diff --git a/authenticator/src/main/java/com/amplifyframework/ui/authenticator/ui/PhoneInputField.kt b/authenticator/src/main/java/com/amplifyframework/ui/authenticator/ui/PhoneInputField.kt index f6859134..e356e797 100644 --- a/authenticator/src/main/java/com/amplifyframework/ui/authenticator/ui/PhoneInputField.kt +++ b/authenticator/src/main/java/com/amplifyframework/ui/authenticator/ui/PhoneInputField.kt @@ -64,6 +64,7 @@ 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.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 @@ -102,7 +103,7 @@ internal fun PhoneInputField( } OutlinedTextField( - modifier = modifier, + modifier = modifier.contentTypeForKey(fieldConfig.key), enabled = enabled, value = state.number, onValueChange = { state.number = it }, diff --git a/authenticator/src/main/java/com/amplifyframework/ui/authenticator/ui/SignIn.kt b/authenticator/src/main/java/com/amplifyframework/ui/authenticator/ui/SignIn.kt index 35f1ade0..9d91213b 100644 --- a/authenticator/src/main/java/com/amplifyframework/ui/authenticator/ui/SignIn.kt +++ b/authenticator/src/main/java/com/amplifyframework/ui/authenticator/ui/SignIn.kt @@ -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 @@ -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)) } } diff --git a/authenticator/src/main/java/com/amplifyframework/ui/authenticator/ui/SignUp.kt b/authenticator/src/main/java/com/amplifyframework/ui/authenticator/ui/SignUp.kt index d6aeb0a2..dfb0a41f 100644 --- a/authenticator/src/main/java/com/amplifyframework/ui/authenticator/ui/SignUp.kt +++ b/authenticator/src/main/java/com/amplifyframework/ui/authenticator/ui/SignUp.kt @@ -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 @@ -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)) } } diff --git a/authenticator/src/main/java/com/amplifyframework/ui/authenticator/ui/TestTags.kt b/authenticator/src/main/java/com/amplifyframework/ui/authenticator/ui/TestTags.kt index 208195ae..a63665a5 100644 --- a/authenticator/src/main/java/com/amplifyframework/ui/authenticator/ui/TestTags.kt +++ b/authenticator/src/main/java/com/amplifyframework/ui/authenticator/ui/TestTags.kt @@ -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() diff --git a/authenticator/src/main/java/com/amplifyframework/ui/authenticator/ui/TextInputField.kt b/authenticator/src/main/java/com/amplifyframework/ui/authenticator/ui/TextInputField.kt index 7797d102..02930868 100644 --- a/authenticator/src/main/java/com/amplifyframework/ui/authenticator/ui/TextInputField.kt +++ b/authenticator/src/main/java/com/amplifyframework/ui/authenticator/ui/TextInputField.kt @@ -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( @@ -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) }, diff --git a/authenticator/src/main/java/com/amplifyframework/ui/authenticator/util/Autofill.kt b/authenticator/src/main/java/com/amplifyframework/ui/authenticator/util/Autofill.kt new file mode 100644 index 00000000..76c6f8f5 --- /dev/null +++ b/authenticator/src/main/java/com/amplifyframework/ui/authenticator/util/Autofill.kt @@ -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 + } +} + +private fun AuthenticatorStep.isNewUsername() = this is AuthenticatorStep.SignUp +private fun AuthenticatorStep.isNewPassword() = + this is AuthenticatorStep.SignUp || this is AuthenticatorStep.PasswordResetConfirm diff --git a/authenticator/src/test/java/com/amplifyframework/ui/authenticator/MockAuthenticatorData.kt b/authenticator/src/test/java/com/amplifyframework/ui/authenticator/MockAuthenticatorData.kt index 981e2004..071b06be 100644 --- a/authenticator/src/test/java/com/amplifyframework/ui/authenticator/MockAuthenticatorData.kt +++ b/authenticator/src/test/java/com/amplifyframework/ui/authenticator/MockAuthenticatorData.kt @@ -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 @@ -138,3 +139,13 @@ internal fun mockUserAttributes(vararg attribute: Pair Unit + ) = setContent { + val step = providedStep ?: AuthenticatorStep.Loading + CompositionLocalProvider( + LocalAutofillManager provides autofillManager, + LocalAuthenticatorStep provides step + ) { + content() + } + } + override fun setContent(content: @Composable () -> Unit) { super.setContent { AmplifyTheme { diff --git a/authenticator/src/test/java/com/amplifyframework/ui/authenticator/testUtil/MockStates.kt b/authenticator/src/test/java/com/amplifyframework/ui/authenticator/testUtil/MockStates.kt index e980edd2..b37831f5 100644 --- a/authenticator/src/test/java/com/amplifyframework/ui/authenticator/testUtil/MockStates.kt +++ b/authenticator/src/test/java/com/amplifyframework/ui/authenticator/testUtil/MockStates.kt @@ -22,6 +22,8 @@ import com.amplifyframework.ui.authenticator.auth.PasswordCriteria import com.amplifyframework.ui.authenticator.auth.SignInMethod import com.amplifyframework.ui.authenticator.enums.AuthenticatorInitialStep import com.amplifyframework.ui.authenticator.forms.FormData +import com.amplifyframework.ui.authenticator.mockAuthCodeDeliveryDetails +import com.amplifyframework.ui.authenticator.states.PasswordResetConfirmStateImpl import com.amplifyframework.ui.authenticator.states.PasswordResetStateImpl import com.amplifyframework.ui.authenticator.states.SignInConfirmMfaStateImpl import com.amplifyframework.ui.authenticator.states.SignInConfirmTotpCodeStateImpl @@ -53,6 +55,18 @@ internal fun mockPasswordResetState() = PasswordResetStateImpl( onMoveTo = {} ) +internal fun mockPasswordResetConfirmState( + passwordCriteria: PasswordCriteria = PasswordCriteria(8, false, false, false, false), + deliveryDetails: AuthCodeDeliveryDetails = mockAuthCodeDeliveryDetails(), + onSubmit: (String, String) -> Unit = { _, _ -> }, + onMoveTo: (AuthenticatorInitialStep) -> Unit = {} +) = PasswordResetConfirmStateImpl( + passwordCriteria = passwordCriteria, + deliveryDetails = deliveryDetails, + onSubmit = onSubmit, + onMoveTo = onMoveTo +) + internal fun mockSignInConfirmTotpCodeState( onSubmit: (String) -> Unit = { }, onMoveTo: (AuthenticatorInitialStep) -> Unit = { } @@ -92,10 +106,7 @@ internal fun mockSignInContinueWithTotpSetupState( ) internal fun mockSignInConfirmMfaState( - deliveryDetails: AuthCodeDeliveryDetails = AuthCodeDeliveryDetails( - "123-123-1234", - AuthCodeDeliveryDetails.DeliveryMedium.SMS - ) + deliveryDetails: AuthCodeDeliveryDetails = mockAuthCodeDeliveryDetails() ) = SignInConfirmMfaStateImpl( deliveryDetails = deliveryDetails, onSubmit = { }, diff --git a/authenticator/src/test/java/com/amplifyframework/ui/authenticator/ui/PasswordResetConfirmTest.kt b/authenticator/src/test/java/com/amplifyframework/ui/authenticator/ui/PasswordResetConfirmTest.kt new file mode 100644 index 00000000..534bd6a7 --- /dev/null +++ b/authenticator/src/test/java/com/amplifyframework/ui/authenticator/ui/PasswordResetConfirmTest.kt @@ -0,0 +1,98 @@ +package com.amplifyframework.ui.authenticator.ui + +import androidx.compose.ui.autofill.AutofillManager +import androidx.compose.ui.autofill.ContentType +import com.amplifyframework.ui.authenticator.enums.AuthenticatorInitialStep +import com.amplifyframework.ui.authenticator.enums.AuthenticatorStep +import com.amplifyframework.ui.authenticator.forms.FieldError +import com.amplifyframework.ui.authenticator.forms.FieldKey +import com.amplifyframework.ui.authenticator.forms.setFieldError +import com.amplifyframework.ui.authenticator.testUtil.AuthenticatorUiTest +import com.amplifyframework.ui.authenticator.testUtil.mockPasswordResetConfirmState +import com.amplifyframework.ui.authenticator.ui.robots.passwordResetConfirm +import com.amplifyframework.ui.testing.ScreenshotTest +import io.mockk.mockk +import io.mockk.verify +import org.junit.Test + +class PasswordResetConfirmTest : AuthenticatorUiTest() { + @Test + fun `title is reset password`() { + setContent { + PasswordResetConfirm(state = mockPasswordResetConfirmState()) + } + passwordResetConfirm { + hasTitle("Reset Password") + } + } + + @Test + fun `has expected content types set`() { + setContent(providedStep = AuthenticatorStep.PasswordResetConfirm) { + PasswordResetConfirm(state = mockPasswordResetConfirmState()) + } + passwordResetConfirm { + hasPasswordContentType(ContentType.NewPassword) + hasConfirmPasswordContentType(ContentType.NewPassword) + } + } + + @Test + fun `cancels autofill values on back to sign in`() { + val autofillManager = mockk(relaxed = true) + setContent(autofillManager = autofillManager) { + PasswordResetConfirm(state = mockPasswordResetConfirmState()) + } + passwordResetConfirm { + setConfirmationCode("123456") + setPassword("newPassword") + setConfirmPassword("newPassword") + clickBackToSignIn() + } + verify { + autofillManager.cancel() + } + } + + @Test + fun `moves back to sign in`() { + val onMoveTo = mockk<(AuthenticatorInitialStep) -> Unit>(relaxed = true) + setContent { + PasswordResetConfirm(state = mockPasswordResetConfirmState(onMoveTo = onMoveTo)) + } + passwordResetConfirm { + clickBackToSignIn() + } + verify { + onMoveTo(AuthenticatorStep.SignIn) + } + } + + @Test + @ScreenshotTest + fun `default state`() { + setContent { + PasswordResetConfirm(state = mockPasswordResetConfirmState()) + } + } + + @Test + @ScreenshotTest + fun `incorrect confirmation code`() { + val state = mockPasswordResetConfirmState() + setContent { + PasswordResetConfirm(state = state) + } + state.form.setFieldError(FieldKey.ConfirmationCode, FieldError.ConfirmationCodeIncorrect) + } + + @Test + @ScreenshotTest + fun `passwords do not match`() { + val state = mockPasswordResetConfirmState() + setContent { + PasswordResetConfirm(state = state) + } + state.form.setFieldError(FieldKey.ConfirmPassword, FieldError.PasswordsDoNotMatch) + } +} diff --git a/authenticator/src/test/java/com/amplifyframework/ui/authenticator/ui/SignInTest.kt b/authenticator/src/test/java/com/amplifyframework/ui/authenticator/ui/SignInTest.kt index 612d0140..dfee0dae 100644 --- a/authenticator/src/test/java/com/amplifyframework/ui/authenticator/ui/SignInTest.kt +++ b/authenticator/src/test/java/com/amplifyframework/ui/authenticator/ui/SignInTest.kt @@ -15,6 +15,9 @@ package com.amplifyframework.ui.authenticator.ui +import androidx.compose.ui.autofill.AutofillManager +import androidx.compose.ui.autofill.ContentType +import com.amplifyframework.ui.authenticator.enums.AuthenticatorStep import com.amplifyframework.ui.authenticator.forms.FieldError import com.amplifyframework.ui.authenticator.forms.FieldKey import com.amplifyframework.ui.authenticator.forms.setFieldError @@ -22,6 +25,8 @@ import com.amplifyframework.ui.authenticator.testUtil.AuthenticatorUiTest import com.amplifyframework.ui.authenticator.testUtil.mockSignInState import com.amplifyframework.ui.authenticator.ui.robots.signIn import com.amplifyframework.ui.testing.ScreenshotTest +import io.mockk.mockk +import io.mockk.verify import org.junit.Test class SignInTest : AuthenticatorUiTest() { @@ -46,6 +51,49 @@ class SignInTest : AuthenticatorUiTest() { } } + @Test + fun `has expected content types set`() { + setContent(providedStep = AuthenticatorStep.SignIn) { + SignIn(state = mockSignInState()) + } + signIn { + hasUsernameContentType(ContentType.Username) + hasPasswordContentType(ContentType.Password) + } + } + + @Test + fun `cancels autofill values on create account`() { + val autofillManager = mockk(relaxed = true) + setContent(autofillManager = autofillManager) { + SignIn(state = mockSignInState()) + } + signIn { + setUsername("foo") + setPassword("bar") + clickCreateAccount() + } + verify { + autofillManager.cancel() + } + } + + @Test + fun `cancels autofill values on forgot password`() { + val autofillManager = mockk(relaxed = true) + setContent(autofillManager = autofillManager) { + SignIn(state = mockSignInState()) + } + signIn { + setUsername("foo") + setPassword("bar") + clickForgotPassword() + } + verify { + autofillManager.cancel() + } + } + @Test @ScreenshotTest fun `default state`() { diff --git a/authenticator/src/test/java/com/amplifyframework/ui/authenticator/ui/SignUpTest.kt b/authenticator/src/test/java/com/amplifyframework/ui/authenticator/ui/SignUpTest.kt index e9fe1156..a1256705 100644 --- a/authenticator/src/test/java/com/amplifyframework/ui/authenticator/ui/SignUpTest.kt +++ b/authenticator/src/test/java/com/amplifyframework/ui/authenticator/ui/SignUpTest.kt @@ -15,6 +15,9 @@ package com.amplifyframework.ui.authenticator.ui +import androidx.compose.ui.autofill.AutofillManager +import androidx.compose.ui.autofill.ContentType +import com.amplifyframework.ui.authenticator.enums.AuthenticatorStep import com.amplifyframework.ui.authenticator.forms.FieldError import com.amplifyframework.ui.authenticator.forms.FieldKey import com.amplifyframework.ui.authenticator.forms.PasswordError @@ -23,6 +26,8 @@ import com.amplifyframework.ui.authenticator.testUtil.AuthenticatorUiTest import com.amplifyframework.ui.authenticator.testUtil.mockSignUpState import com.amplifyframework.ui.authenticator.ui.robots.signUp import com.amplifyframework.ui.testing.ScreenshotTest +import io.mockk.mockk +import io.mockk.verify import org.junit.Test class SignUpTest : AuthenticatorUiTest() { @@ -47,6 +52,34 @@ class SignUpTest : AuthenticatorUiTest() { } } + @Test + fun `expected content types are set`() { + setContent(providedStep = AuthenticatorStep.SignUp) { + SignUp(state = mockSignUpState()) + } + signUp { + hasUsernameContentType(ContentType.NewUsername) + hasPasswordContentType(ContentType.NewPassword) + hasConfirmPasswordContentType(ContentType.NewPassword) + } + } + + @Test + fun `cancels autofill values on back to sign in`() { + val autofillManager = mockk(relaxed = true) + setContent(autofillManager = autofillManager) { + SignUp(state = mockSignUpState()) + } + signUp { + setUsername("foo") + setPassword("bar") + clickBackToSignIn() + } + verify { + autofillManager.cancel() + } + } + @Test @ScreenshotTest fun `default state`() { diff --git a/authenticator/src/test/java/com/amplifyframework/ui/authenticator/ui/robots/PasswordResetConfirmRobot.kt b/authenticator/src/test/java/com/amplifyframework/ui/authenticator/ui/robots/PasswordResetConfirmRobot.kt new file mode 100644 index 00000000..ebf539f8 --- /dev/null +++ b/authenticator/src/test/java/com/amplifyframework/ui/authenticator/ui/robots/PasswordResetConfirmRobot.kt @@ -0,0 +1,21 @@ +package com.amplifyframework.ui.authenticator.ui.robots + +import androidx.compose.ui.autofill.ContentType +import androidx.compose.ui.test.junit4.ComposeTestRule +import com.amplifyframework.ui.authenticator.forms.FieldKey +import com.amplifyframework.ui.authenticator.ui.TestTags +import com.amplifyframework.ui.testing.ComposeTest + +fun ComposeTest.passwordResetConfirm(func: PasswordResetConfirmRobot.() -> Unit) = + PasswordResetConfirmRobot(composeTestRule).func() + +class PasswordResetConfirmRobot(rule: ComposeTestRule) : ScreenLevelRobot(rule) { + fun hasSubmitButton(expected: String) = assertExists(TestTags.PasswordResetButton, expected) + fun hasPasswordContentType(contentType: ContentType) = hasContentType(FieldKey.Password, contentType) + fun hasConfirmPasswordContentType(contentType: ContentType) = hasContentType(FieldKey.ConfirmPassword, contentType) + + fun setConfirmationCode(value: String) = setFieldContent(FieldKey.ConfirmationCode, value) + fun setPassword(value: String) = setFieldContent(FieldKey.Password, value) + fun setConfirmPassword(value: String) = setFieldContent(FieldKey.ConfirmPassword, value) + fun clickBackToSignIn() = clickOnTag(TestTags.BackToSignInButton) +} diff --git a/authenticator/src/test/java/com/amplifyframework/ui/authenticator/ui/robots/ScreenLevelRobot.kt b/authenticator/src/test/java/com/amplifyframework/ui/authenticator/ui/robots/ScreenLevelRobot.kt index db48f964..e96fd81a 100644 --- a/authenticator/src/test/java/com/amplifyframework/ui/authenticator/ui/robots/ScreenLevelRobot.kt +++ b/authenticator/src/test/java/com/amplifyframework/ui/authenticator/ui/robots/ScreenLevelRobot.kt @@ -15,21 +15,30 @@ package com.amplifyframework.ui.authenticator.ui.robots +import androidx.compose.ui.autofill.ContentType +import androidx.compose.ui.semantics.SemanticsProperties +import androidx.compose.ui.test.SemanticsMatcher import androidx.compose.ui.test.hasAnyAncestor import androidx.compose.ui.test.hasTestTag import androidx.compose.ui.test.junit4.ComposeTestRule import androidx.compose.ui.test.performClick import com.amplifyframework.ui.authenticator.forms.FieldKey import com.amplifyframework.ui.authenticator.ui.TestTags +import com.amplifyframework.ui.authenticator.ui.testTag import com.amplifyframework.ui.testing.ComposeRobot abstract class ScreenLevelRobot(rule: ComposeTestRule) : ComposeRobot(rule) { // Check that the composable has the expected title fun hasTitle(expected: String) = assertExists(TestTags.AuthenticatorTitle, expected) - fun setFieldContent(key: FieldKey, content: String) = writeTo(key.toString(), content) + // Check that the expected content type is set + fun hasContentType(key: FieldKey, contentType: ContentType) = composeTestRule.onNode( + hasTestTag(key.testTag) and SemanticsMatcher.expectValue(SemanticsProperties.ContentType, contentType) + ).assertExists() + + fun setFieldContent(key: FieldKey, content: String) = writeTo(key.testTag, content) fun clickOnShowIcon(key: FieldKey) = composeTestRule.onNode( - hasTestTag(TestTags.ShowPasswordIcon) and hasAnyAncestor(hasTestTag(key.toString())) + hasTestTag(TestTags.ShowPasswordIcon) and hasAnyAncestor(hasTestTag(key.testTag)) ).performClick() } diff --git a/authenticator/src/test/java/com/amplifyframework/ui/authenticator/ui/robots/SignInRobot.kt b/authenticator/src/test/java/com/amplifyframework/ui/authenticator/ui/robots/SignInRobot.kt index 0555bad3..65248d2a 100644 --- a/authenticator/src/test/java/com/amplifyframework/ui/authenticator/ui/robots/SignInRobot.kt +++ b/authenticator/src/test/java/com/amplifyframework/ui/authenticator/ui/robots/SignInRobot.kt @@ -15,16 +15,23 @@ package com.amplifyframework.ui.authenticator.ui.robots +import androidx.compose.ui.autofill.ContentType import androidx.compose.ui.test.junit4.ComposeTestRule import com.amplifyframework.ui.authenticator.forms.FieldKey +import com.amplifyframework.ui.authenticator.ui.TestTags import com.amplifyframework.ui.testing.ComposeTest fun ComposeTest.signIn(func: SignInRobot.() -> Unit) = SignInRobot(composeTestRule).func() class SignInRobot(rule: ComposeTestRule) : ScreenLevelRobot(rule) { - fun hasSubmitButton(expected: String) = assertExists("SignInButton", expected) + fun hasSubmitButton(expected: String) = assertExists(TestTags.SignInButton, expected) + fun hasUsernameContentType(contentType: ContentType) = hasContentType(FieldKey.Username, contentType) + fun hasPasswordContentType(contentType: ContentType) = hasContentType(FieldKey.Password, contentType) fun setUsername(value: String) = setFieldContent(FieldKey.Username, value) fun setPassword(value: String) = setFieldContent(FieldKey.Password, value) fun clickShowPassword() = clickOnShowIcon(FieldKey.Password) + fun clickSubmitButton() = clickOnTag(TestTags.SignInButton) + fun clickForgotPassword() = clickOnTag(TestTags.ForgotPasswordButton) + fun clickCreateAccount() = clickOnTag(TestTags.CreateAccountButton) } diff --git a/authenticator/src/test/java/com/amplifyframework/ui/authenticator/ui/robots/SignUpRobot.kt b/authenticator/src/test/java/com/amplifyframework/ui/authenticator/ui/robots/SignUpRobot.kt index d7774645..46380c49 100644 --- a/authenticator/src/test/java/com/amplifyframework/ui/authenticator/ui/robots/SignUpRobot.kt +++ b/authenticator/src/test/java/com/amplifyframework/ui/authenticator/ui/robots/SignUpRobot.kt @@ -15,6 +15,7 @@ package com.amplifyframework.ui.authenticator.ui.robots +import androidx.compose.ui.autofill.ContentType import androidx.compose.ui.test.junit4.ComposeTestRule import com.amplifyframework.ui.authenticator.forms.FieldKey import com.amplifyframework.ui.authenticator.ui.TestTags @@ -24,10 +25,15 @@ fun ComposeTest.signUp(func: SignUpRobot.() -> Unit) = SignUpRobot(composeTestRu class SignUpRobot(rule: ComposeTestRule) : ScreenLevelRobot(rule) { fun hasSubmitButton(expected: String) = assertExists(TestTags.SignUpButton, expected) + fun hasUsernameContentType(contentType: ContentType) = hasContentType(FieldKey.Username, contentType) + fun hasPasswordContentType(contentType: ContentType) = hasContentType(FieldKey.Password, contentType) + fun hasConfirmPasswordContentType(contentType: ContentType) = hasContentType(FieldKey.ConfirmPassword, contentType) fun setUsername(value: String) = setFieldContent(FieldKey.Username, value) fun setPassword(value: String) = setFieldContent(FieldKey.Password, value) fun setConfirmPassword(value: String) = setFieldContent(FieldKey.ConfirmPassword, value) fun setEmail(value: String) = setFieldContent(FieldKey.Email, value) fun clickShowPassword(fieldKey: FieldKey) = clickOnShowIcon(fieldKey) + fun clickSubmitButton() = clickOnTag(TestTags.SignUpButton) + fun clickBackToSignIn() = clickOnTag(TestTags.BackToSignInButton) } diff --git a/authenticator/src/test/screenshots/PasswordResetConfirmTest_default-state.png b/authenticator/src/test/screenshots/PasswordResetConfirmTest_default-state.png new file mode 100644 index 00000000..028b1cd4 Binary files /dev/null and b/authenticator/src/test/screenshots/PasswordResetConfirmTest_default-state.png differ diff --git a/authenticator/src/test/screenshots/PasswordResetConfirmTest_incorrect-confirmation-code.png b/authenticator/src/test/screenshots/PasswordResetConfirmTest_incorrect-confirmation-code.png new file mode 100644 index 00000000..c0c09d02 Binary files /dev/null and b/authenticator/src/test/screenshots/PasswordResetConfirmTest_incorrect-confirmation-code.png differ diff --git a/authenticator/src/test/screenshots/PasswordResetConfirmTest_passwords-do-not-match.png b/authenticator/src/test/screenshots/PasswordResetConfirmTest_passwords-do-not-match.png new file mode 100644 index 00000000..9f30b3bf Binary files /dev/null and b/authenticator/src/test/screenshots/PasswordResetConfirmTest_passwords-do-not-match.png differ diff --git a/authenticator/src/test/screenshots/PasswordResetTest_default-state.png b/authenticator/src/test/screenshots/PasswordResetTest_default-state.png index 99f3aea3..9ea5c440 100644 Binary files a/authenticator/src/test/screenshots/PasswordResetTest_default-state.png and b/authenticator/src/test/screenshots/PasswordResetTest_default-state.png differ diff --git a/authenticator/src/test/screenshots/PasswordResetTest_username-not-found.png b/authenticator/src/test/screenshots/PasswordResetTest_username-not-found.png index 865a92b8..7ba427d5 100644 Binary files a/authenticator/src/test/screenshots/PasswordResetTest_username-not-found.png and b/authenticator/src/test/screenshots/PasswordResetTest_username-not-found.png differ diff --git a/authenticator/src/test/screenshots/SignInConfirmMfaTest_default-state.png b/authenticator/src/test/screenshots/SignInConfirmMfaTest_default-state.png index ef5a1f93..11f8cbe6 100644 Binary files a/authenticator/src/test/screenshots/SignInConfirmMfaTest_default-state.png and b/authenticator/src/test/screenshots/SignInConfirmMfaTest_default-state.png differ diff --git a/authenticator/src/test/screenshots/SignInConfirmMfaTest_incorrect-code.png b/authenticator/src/test/screenshots/SignInConfirmMfaTest_incorrect-code.png index 94165b5c..2a2e07ff 100644 Binary files a/authenticator/src/test/screenshots/SignInConfirmMfaTest_incorrect-code.png and b/authenticator/src/test/screenshots/SignInConfirmMfaTest_incorrect-code.png differ diff --git a/authenticator/src/test/screenshots/SignInConfirmTotpCodeTest_default-state.png b/authenticator/src/test/screenshots/SignInConfirmTotpCodeTest_default-state.png index c72ff482..0a72053f 100644 Binary files a/authenticator/src/test/screenshots/SignInConfirmTotpCodeTest_default-state.png and b/authenticator/src/test/screenshots/SignInConfirmTotpCodeTest_default-state.png differ diff --git a/authenticator/src/test/screenshots/SignInConfirmTotpCodeTest_invalid-code.png b/authenticator/src/test/screenshots/SignInConfirmTotpCodeTest_invalid-code.png index 5e903b17..6197ae7d 100644 Binary files a/authenticator/src/test/screenshots/SignInConfirmTotpCodeTest_invalid-code.png and b/authenticator/src/test/screenshots/SignInConfirmTotpCodeTest_invalid-code.png differ diff --git a/authenticator/src/test/screenshots/SignInContinueWithEmailSetupTest_default-state.png b/authenticator/src/test/screenshots/SignInContinueWithEmailSetupTest_default-state.png index f3f66e19..dda54741 100644 Binary files a/authenticator/src/test/screenshots/SignInContinueWithEmailSetupTest_default-state.png and b/authenticator/src/test/screenshots/SignInContinueWithEmailSetupTest_default-state.png differ diff --git a/authenticator/src/test/screenshots/SignInContinueWithMfaSelectionTest_default-state.png b/authenticator/src/test/screenshots/SignInContinueWithMfaSelectionTest_default-state.png index a5f5602d..494b3468 100644 Binary files a/authenticator/src/test/screenshots/SignInContinueWithMfaSelectionTest_default-state.png and b/authenticator/src/test/screenshots/SignInContinueWithMfaSelectionTest_default-state.png differ diff --git a/authenticator/src/test/screenshots/SignInContinueWithTotpSetupTest_default-state.png b/authenticator/src/test/screenshots/SignInContinueWithTotpSetupTest_default-state.png index 1081a32f..c0ce85bb 100644 Binary files a/authenticator/src/test/screenshots/SignInContinueWithTotpSetupTest_default-state.png and b/authenticator/src/test/screenshots/SignInContinueWithTotpSetupTest_default-state.png differ diff --git a/authenticator/src/test/screenshots/SignInTest_default-state.png b/authenticator/src/test/screenshots/SignInTest_default-state.png index 9f4ccc9a..541b0dd9 100644 Binary files a/authenticator/src/test/screenshots/SignInTest_default-state.png and b/authenticator/src/test/screenshots/SignInTest_default-state.png differ diff --git a/authenticator/src/test/screenshots/SignInTest_password-visible.png b/authenticator/src/test/screenshots/SignInTest_password-visible.png index fa61261d..d5f9a629 100644 Binary files a/authenticator/src/test/screenshots/SignInTest_password-visible.png and b/authenticator/src/test/screenshots/SignInTest_password-visible.png differ diff --git a/authenticator/src/test/screenshots/SignInTest_ready-to-submit.png b/authenticator/src/test/screenshots/SignInTest_ready-to-submit.png index 9df29da5..31e403ad 100644 Binary files a/authenticator/src/test/screenshots/SignInTest_ready-to-submit.png and b/authenticator/src/test/screenshots/SignInTest_ready-to-submit.png differ diff --git a/authenticator/src/test/screenshots/SignInTest_username-not-found.png b/authenticator/src/test/screenshots/SignInTest_username-not-found.png index b192482f..7329fc89 100644 Binary files a/authenticator/src/test/screenshots/SignInTest_username-not-found.png and b/authenticator/src/test/screenshots/SignInTest_username-not-found.png differ diff --git a/authenticator/src/test/screenshots/SignUpTest_default-state.png b/authenticator/src/test/screenshots/SignUpTest_default-state.png index c4419737..055c69de 100644 Binary files a/authenticator/src/test/screenshots/SignUpTest_default-state.png and b/authenticator/src/test/screenshots/SignUpTest_default-state.png differ diff --git a/authenticator/src/test/screenshots/SignUpTest_invalid-email.png b/authenticator/src/test/screenshots/SignUpTest_invalid-email.png index baf6ffbc..67e08d3b 100644 Binary files a/authenticator/src/test/screenshots/SignUpTest_invalid-email.png and b/authenticator/src/test/screenshots/SignUpTest_invalid-email.png differ diff --git a/authenticator/src/test/screenshots/SignUpTest_invalid-password.png b/authenticator/src/test/screenshots/SignUpTest_invalid-password.png index 8b6364b6..e41b35c8 100644 Binary files a/authenticator/src/test/screenshots/SignUpTest_invalid-password.png and b/authenticator/src/test/screenshots/SignUpTest_invalid-password.png differ diff --git a/authenticator/src/test/screenshots/SignUpTest_password-visible.png b/authenticator/src/test/screenshots/SignUpTest_password-visible.png index 35f465de..f6ae189f 100644 Binary files a/authenticator/src/test/screenshots/SignUpTest_password-visible.png and b/authenticator/src/test/screenshots/SignUpTest_password-visible.png differ diff --git a/authenticator/src/test/screenshots/SignUpTest_passwords-do-not-match.png b/authenticator/src/test/screenshots/SignUpTest_passwords-do-not-match.png index d2c08a28..52583d87 100644 Binary files a/authenticator/src/test/screenshots/SignUpTest_passwords-do-not-match.png and b/authenticator/src/test/screenshots/SignUpTest_passwords-do-not-match.png differ diff --git a/authenticator/src/test/screenshots/SignUpTest_ready-to-submit.png b/authenticator/src/test/screenshots/SignUpTest_ready-to-submit.png index ad1bc4ec..c9e91676 100644 Binary files a/authenticator/src/test/screenshots/SignUpTest_ready-to-submit.png and b/authenticator/src/test/screenshots/SignUpTest_ready-to-submit.png differ diff --git a/authenticator/src/test/screenshots/SignUpTest_username-exists.png b/authenticator/src/test/screenshots/SignUpTest_username-exists.png index 9f6fbcec..331f6f61 100644 Binary files a/authenticator/src/test/screenshots/SignUpTest_username-exists.png and b/authenticator/src/test/screenshots/SignUpTest_username-exists.png differ diff --git a/build-logic/plugins/src/main/kotlin/AndroidLibraryConventionPlugin.kt b/build-logic/plugins/src/main/kotlin/AndroidLibraryConventionPlugin.kt index 49eb1a21..dbece493 100644 --- a/build-logic/plugins/src/main/kotlin/AndroidLibraryConventionPlugin.kt +++ b/build-logic/plugins/src/main/kotlin/AndroidLibraryConventionPlugin.kt @@ -75,7 +75,7 @@ class AndroidLibraryConventionPlugin : Plugin { } extension.apply { - compileSdk = 34 + compileSdk = 35 buildFeatures { buildConfig = true diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 56ed2732..359feb70 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -9,7 +9,7 @@ androidx-activity = "1.6.1" androidx-navigation = "2.5.3" binary-compatibility = "0.14.0" cameraX = "1.4.2" -compose-bom = "2025.03.01" +compose-bom = "2025.06.01" coroutines = "1.7.3" desugar = "1.2.0" futures = "1.1.0" diff --git a/samples/authenticator/app/build.gradle b/samples/authenticator/app/build.gradle index 272f79ae..8f0a95a3 100644 --- a/samples/authenticator/app/build.gradle +++ b/samples/authenticator/app/build.gradle @@ -5,7 +5,7 @@ plugins { android { namespace 'com.amplifyframework.ui.sample.authenticator' - compileSdk 34 + compileSdk 35 defaultConfig { applicationId "com.amplifyframework.ui.sample.authenticator"