Add auto-login after password reset on iOS and Android
- Add LOGGING_IN step to PasswordResetStep enum on both platforms - Auto-login with new credentials after successful password reset - Navigate directly to main app (or verification screen if unverified) - Show "Logging in..." state during auto-login process - Hide back button during auto-login to prevent interruption - Fall back to "Return to Login" if auto-login fails 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -289,9 +289,28 @@ fun App(
|
||||
}
|
||||
val passwordResetViewModel: PasswordResetViewModel = viewModel(parentEntry) { PasswordResetViewModel() }
|
||||
|
||||
// Set up auto-login callback
|
||||
LaunchedEffect(Unit) {
|
||||
passwordResetViewModel.onLoginSuccess = { verified ->
|
||||
isLoggedIn = true
|
||||
isVerified = verified
|
||||
onClearDeepLinkToken()
|
||||
// Navigate directly to main app or verification screen
|
||||
if (verified) {
|
||||
navController.navigate(MainRoute) {
|
||||
popUpTo<ForgotPasswordRoute> { inclusive = true }
|
||||
}
|
||||
} else {
|
||||
navController.navigate(VerifyEmailRoute) {
|
||||
popUpTo<ForgotPasswordRoute> { inclusive = true }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ResetPasswordScreen(
|
||||
onPasswordResetSuccess = {
|
||||
// Clear deep link token and navigate back to login after successful password reset
|
||||
// Fallback: manual return to login (only shown if auto-login fails)
|
||||
onClearDeepLinkToken()
|
||||
navController.navigate(LoginRoute) {
|
||||
popUpTo<ForgotPasswordRoute> { inclusive = true }
|
||||
|
||||
@@ -36,6 +36,7 @@ fun ResetPasswordScreen(
|
||||
var confirmPasswordVisible by remember { mutableStateOf(false) }
|
||||
|
||||
val resetPasswordState by viewModel.resetPasswordState.collectAsState()
|
||||
val loginState by viewModel.loginState.collectAsState()
|
||||
val currentStep by viewModel.currentStep.collectAsState()
|
||||
|
||||
// Handle errors for password reset
|
||||
@@ -50,6 +51,7 @@ fun ResetPasswordScreen(
|
||||
}
|
||||
|
||||
val isLoading = resetPasswordState is ApiResult.Loading
|
||||
val isLoggingIn = currentStep == com.example.casera.viewmodel.PasswordResetStep.LOGGING_IN
|
||||
val isSuccess = currentStep == com.example.casera.viewmodel.PasswordResetStep.SUCCESS
|
||||
|
||||
// Password validation
|
||||
@@ -63,9 +65,12 @@ fun ResetPasswordScreen(
|
||||
TopAppBar(
|
||||
title = { Text(stringResource(Res.string.auth_reset_title)) },
|
||||
navigationIcon = {
|
||||
onNavigateBack?.let { callback ->
|
||||
IconButton(onClick = callback) {
|
||||
Icon(Icons.Default.ArrowBack, contentDescription = stringResource(Res.string.common_back))
|
||||
// Hide back button while logging in
|
||||
if (!isLoggingIn) {
|
||||
onNavigateBack?.let { callback ->
|
||||
IconButton(onClick = callback) {
|
||||
Icon(Icons.Default.ArrowBack, contentDescription = stringResource(Res.string.common_back))
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -239,15 +244,21 @@ fun ResetPasswordScreen(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.height(56.dp),
|
||||
enabled = isFormValid && !isLoading,
|
||||
enabled = isFormValid && !isLoading && !isLoggingIn,
|
||||
shape = RoundedCornerShape(12.dp)
|
||||
) {
|
||||
if (isLoading) {
|
||||
if (isLoading || isLoggingIn) {
|
||||
CircularProgressIndicator(
|
||||
modifier = Modifier.size(24.dp),
|
||||
color = MaterialTheme.colorScheme.onPrimary,
|
||||
strokeWidth = 2.dp
|
||||
)
|
||||
Spacer(modifier = Modifier.width(8.dp))
|
||||
Text(
|
||||
if (isLoggingIn) "Logging in..." else "Resetting...",
|
||||
style = MaterialTheme.typography.titleMedium,
|
||||
fontWeight = FontWeight.SemiBold
|
||||
)
|
||||
} else {
|
||||
Icon(Icons.Default.LockReset, contentDescription = null)
|
||||
Spacer(modifier = Modifier.width(8.dp))
|
||||
|
||||
@@ -13,6 +13,7 @@ enum class PasswordResetStep {
|
||||
REQUEST_CODE,
|
||||
VERIFY_CODE,
|
||||
RESET_PASSWORD,
|
||||
LOGGING_IN,
|
||||
SUCCESS
|
||||
}
|
||||
|
||||
@@ -29,6 +30,12 @@ class PasswordResetViewModel(
|
||||
private val _resetPasswordState = MutableStateFlow<ApiResult<ResetPasswordResponse>>(ApiResult.Idle)
|
||||
val resetPasswordState: StateFlow<ApiResult<ResetPasswordResponse>> = _resetPasswordState
|
||||
|
||||
private val _loginState = MutableStateFlow<ApiResult<AuthResponse>>(ApiResult.Idle)
|
||||
val loginState: StateFlow<ApiResult<AuthResponse>> = _loginState
|
||||
|
||||
// Callback for successful login after password reset
|
||||
var onLoginSuccess: ((Boolean) -> Unit)? = null
|
||||
|
||||
private val _currentStep = MutableStateFlow(
|
||||
if (deepLinkToken != null) PasswordResetStep.RESET_PASSWORD else PasswordResetStep.REQUEST_CODE
|
||||
)
|
||||
@@ -80,6 +87,8 @@ class PasswordResetViewModel(
|
||||
}
|
||||
}
|
||||
|
||||
private var _newPassword: String = ""
|
||||
|
||||
fun resetPassword(newPassword: String, confirmPassword: String) {
|
||||
val token = _resetToken.value
|
||||
if (token == null) {
|
||||
@@ -87,6 +96,8 @@ class PasswordResetViewModel(
|
||||
return
|
||||
}
|
||||
|
||||
_newPassword = newPassword
|
||||
|
||||
viewModelScope.launch {
|
||||
_resetPasswordState.value = ApiResult.Loading
|
||||
// Note: confirmPassword is for UI validation only, not sent to API
|
||||
@@ -98,7 +109,9 @@ class PasswordResetViewModel(
|
||||
)
|
||||
_resetPasswordState.value = when (result) {
|
||||
is ApiResult.Success -> {
|
||||
_currentStep.value = PasswordResetStep.SUCCESS
|
||||
// Password reset successful - now auto-login
|
||||
_currentStep.value = PasswordResetStep.LOGGING_IN
|
||||
autoLogin()
|
||||
ApiResult.Success(result.data)
|
||||
}
|
||||
is ApiResult.Error -> result
|
||||
@@ -107,11 +120,46 @@ class PasswordResetViewModel(
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun autoLogin() {
|
||||
val username = _email.value
|
||||
|
||||
if (username.isEmpty()) {
|
||||
// If we don't have the email (e.g., deep link flow), fall back to manual login
|
||||
_currentStep.value = PasswordResetStep.SUCCESS
|
||||
return
|
||||
}
|
||||
|
||||
_loginState.value = ApiResult.Loading
|
||||
|
||||
val loginResult = APILayer.login(LoginRequest(username = username, password = _newPassword))
|
||||
|
||||
when (loginResult) {
|
||||
is ApiResult.Success -> {
|
||||
val isVerified = loginResult.data.user.verified
|
||||
// Initialize lookups
|
||||
APILayer.initializeLookups()
|
||||
_loginState.value = loginResult
|
||||
// Call the login success callback
|
||||
onLoginSuccess?.invoke(isVerified)
|
||||
}
|
||||
is ApiResult.Error -> {
|
||||
// Auto-login failed, fall back to manual login
|
||||
println("Auto-login failed: ${loginResult.message}")
|
||||
_loginState.value = ApiResult.Idle
|
||||
_currentStep.value = PasswordResetStep.SUCCESS
|
||||
}
|
||||
else -> {
|
||||
_currentStep.value = PasswordResetStep.SUCCESS
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun moveToNextStep() {
|
||||
_currentStep.value = when (_currentStep.value) {
|
||||
PasswordResetStep.REQUEST_CODE -> PasswordResetStep.VERIFY_CODE
|
||||
PasswordResetStep.VERIFY_CODE -> PasswordResetStep.RESET_PASSWORD
|
||||
PasswordResetStep.RESET_PASSWORD -> PasswordResetStep.SUCCESS
|
||||
PasswordResetStep.RESET_PASSWORD -> PasswordResetStep.LOGGING_IN
|
||||
PasswordResetStep.LOGGING_IN -> PasswordResetStep.SUCCESS
|
||||
PasswordResetStep.SUCCESS -> PasswordResetStep.SUCCESS
|
||||
}
|
||||
}
|
||||
@@ -121,6 +169,7 @@ class PasswordResetViewModel(
|
||||
PasswordResetStep.REQUEST_CODE -> PasswordResetStep.REQUEST_CODE
|
||||
PasswordResetStep.VERIFY_CODE -> PasswordResetStep.REQUEST_CODE
|
||||
PasswordResetStep.RESET_PASSWORD -> PasswordResetStep.VERIFY_CODE
|
||||
PasswordResetStep.LOGGING_IN -> PasswordResetStep.LOGGING_IN // Can't go back while logging in
|
||||
PasswordResetStep.SUCCESS -> PasswordResetStep.SUCCESS
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user