Kotlin Shared Layer: - Add AppleSignInRequest and AppleSignInResponse models - Add appleSignIn method to AuthApi and APILayer - Add appleSignInState and appleSignIn() to AuthViewModel iOS App: - Create AppleSignInManager for AuthenticationServices integration - Create AppleSignInViewModel to coordinate Apple auth flow - Update LoginView with "Sign in with Apple" button - Add Sign in with Apple entitlement - Add accessibility identifier for UI testing 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
252 lines
9.4 KiB
Kotlin
252 lines
9.4 KiB
Kotlin
package com.example.casera.viewmodel
|
|
|
|
import androidx.lifecycle.ViewModel
|
|
import androidx.lifecycle.viewModelScope
|
|
import com.example.casera.models.AppleSignInRequest
|
|
import com.example.casera.models.AppleSignInResponse
|
|
import com.example.casera.models.AuthResponse
|
|
import com.example.casera.models.ForgotPasswordRequest
|
|
import com.example.casera.models.ForgotPasswordResponse
|
|
import com.example.casera.models.LoginRequest
|
|
import com.example.casera.models.RegisterRequest
|
|
import com.example.casera.models.ResetPasswordRequest
|
|
import com.example.casera.models.ResetPasswordResponse
|
|
import com.example.casera.models.Residence
|
|
import com.example.casera.models.User
|
|
import com.example.casera.models.VerifyEmailRequest
|
|
import com.example.casera.models.VerifyEmailResponse
|
|
import com.example.casera.models.VerifyResetCodeRequest
|
|
import com.example.casera.models.VerifyResetCodeResponse
|
|
import com.example.casera.network.ApiResult
|
|
import com.example.casera.network.APILayer
|
|
import com.example.casera.storage.TokenStorage
|
|
import kotlinx.coroutines.flow.MutableStateFlow
|
|
import kotlinx.coroutines.flow.StateFlow
|
|
import kotlinx.coroutines.launch
|
|
|
|
class AuthViewModel : ViewModel() {
|
|
|
|
private val _loginState = MutableStateFlow<ApiResult<AuthResponse>>(ApiResult.Idle)
|
|
val loginState: StateFlow<ApiResult<AuthResponse>> = _loginState
|
|
|
|
private val _registerState = MutableStateFlow<ApiResult<AuthResponse>>(ApiResult.Idle)
|
|
val registerState: StateFlow<ApiResult<AuthResponse>> = _registerState
|
|
|
|
private val _verifyEmailState = MutableStateFlow<ApiResult<VerifyEmailResponse>>(ApiResult.Idle)
|
|
val verifyEmailState: StateFlow<ApiResult<VerifyEmailResponse>> = _verifyEmailState
|
|
|
|
private val _updateProfileState = MutableStateFlow<ApiResult<User>>(ApiResult.Idle)
|
|
val updateProfileState: StateFlow<ApiResult<User>> = _updateProfileState
|
|
|
|
private val _currentUserState = MutableStateFlow<ApiResult<User>>(ApiResult.Idle)
|
|
val currentUserState: StateFlow<ApiResult<User>> = _currentUserState
|
|
|
|
private val _forgotPasswordState = MutableStateFlow<ApiResult<ForgotPasswordResponse>>(ApiResult.Idle)
|
|
val forgotPasswordState: StateFlow<ApiResult<ForgotPasswordResponse>> = _forgotPasswordState
|
|
|
|
private val _verifyResetCodeState = MutableStateFlow<ApiResult<VerifyResetCodeResponse>>(ApiResult.Idle)
|
|
val verifyResetCodeState: StateFlow<ApiResult<VerifyResetCodeResponse>> = _verifyResetCodeState
|
|
|
|
private val _resetPasswordState = MutableStateFlow<ApiResult<ResetPasswordResponse>>(ApiResult.Idle)
|
|
val resetPasswordState: StateFlow<ApiResult<ResetPasswordResponse>> = _resetPasswordState
|
|
|
|
private val _appleSignInState = MutableStateFlow<ApiResult<AppleSignInResponse>>(ApiResult.Idle)
|
|
val appleSignInState: StateFlow<ApiResult<AppleSignInResponse>> = _appleSignInState
|
|
|
|
fun login(username: String, password: String) {
|
|
viewModelScope.launch {
|
|
_loginState.value = ApiResult.Loading
|
|
val result = APILayer.login(LoginRequest(username, password))
|
|
_loginState.value = when (result) {
|
|
is ApiResult.Success -> {
|
|
// Store token for future API calls
|
|
TokenStorage.saveToken(result.data.token)
|
|
ApiResult.Success(result.data)
|
|
}
|
|
is ApiResult.Error -> result
|
|
else -> ApiResult.Error("Unknown error")
|
|
}
|
|
}
|
|
}
|
|
|
|
fun register(username: String, email: String, password: String) {
|
|
viewModelScope.launch {
|
|
_registerState.value = ApiResult.Loading
|
|
val result = APILayer.register(
|
|
RegisterRequest(
|
|
username = username,
|
|
email = email,
|
|
password = password
|
|
)
|
|
)
|
|
_registerState.value = when (result) {
|
|
is ApiResult.Success -> {
|
|
// Store token for future API calls
|
|
TokenStorage.saveToken(result.data.token)
|
|
ApiResult.Success(result.data)
|
|
}
|
|
is ApiResult.Error -> result
|
|
else -> ApiResult.Error("Unknown error")
|
|
}
|
|
}
|
|
}
|
|
|
|
fun resetRegisterState() {
|
|
_registerState.value = ApiResult.Idle
|
|
}
|
|
|
|
fun verifyEmail(code: String) {
|
|
viewModelScope.launch {
|
|
_verifyEmailState.value = ApiResult.Loading
|
|
val token = TokenStorage.getToken() ?: run {
|
|
_verifyEmailState.value = ApiResult.Error("Not authenticated")
|
|
return@launch
|
|
}
|
|
val result = APILayer.verifyEmail(token, VerifyEmailRequest(code = code))
|
|
_verifyEmailState.value = when (result) {
|
|
is ApiResult.Success -> ApiResult.Success(result.data)
|
|
is ApiResult.Error -> result
|
|
else -> ApiResult.Error("Unknown error")
|
|
}
|
|
}
|
|
}
|
|
|
|
fun resetVerifyEmailState() {
|
|
_verifyEmailState.value = ApiResult.Idle
|
|
}
|
|
|
|
fun updateProfile(firstName: String?, lastName: String?, email: String?) {
|
|
viewModelScope.launch {
|
|
_updateProfileState.value = ApiResult.Loading
|
|
val token = TokenStorage.getToken() ?: run {
|
|
_updateProfileState.value = ApiResult.Error("Not authenticated")
|
|
return@launch
|
|
}
|
|
val result = APILayer.updateProfile(
|
|
token,
|
|
com.example.casera.models.UpdateProfileRequest(
|
|
firstName = firstName,
|
|
lastName = lastName,
|
|
email = email
|
|
)
|
|
)
|
|
_updateProfileState.value = when (result) {
|
|
is ApiResult.Success -> ApiResult.Success(result.data)
|
|
is ApiResult.Error -> result
|
|
else -> ApiResult.Error("Unknown error")
|
|
}
|
|
}
|
|
}
|
|
|
|
fun resetUpdateProfileState() {
|
|
_updateProfileState.value = ApiResult.Idle
|
|
}
|
|
|
|
fun getCurrentUser(forceRefresh: Boolean = false) {
|
|
viewModelScope.launch {
|
|
_currentUserState.value = ApiResult.Loading
|
|
val result = APILayer.getCurrentUser(forceRefresh = forceRefresh)
|
|
_currentUserState.value = when (result) {
|
|
is ApiResult.Success -> ApiResult.Success(result.data)
|
|
is ApiResult.Error -> result
|
|
else -> ApiResult.Error("Unknown error")
|
|
}
|
|
}
|
|
}
|
|
|
|
fun resetCurrentUserState() {
|
|
_currentUserState.value = ApiResult.Idle
|
|
}
|
|
|
|
fun forgotPassword(email: String) {
|
|
viewModelScope.launch {
|
|
_forgotPasswordState.value = ApiResult.Loading
|
|
val result = APILayer.forgotPassword(ForgotPasswordRequest(email = email))
|
|
_forgotPasswordState.value = when (result) {
|
|
is ApiResult.Success -> ApiResult.Success(result.data)
|
|
is ApiResult.Error -> result
|
|
else -> ApiResult.Error("Unknown error")
|
|
}
|
|
}
|
|
}
|
|
|
|
fun resetForgotPasswordState() {
|
|
_forgotPasswordState.value = ApiResult.Idle
|
|
}
|
|
|
|
fun verifyResetCode(email: String, code: String) {
|
|
viewModelScope.launch {
|
|
_verifyResetCodeState.value = ApiResult.Loading
|
|
val result = APILayer.verifyResetCode(VerifyResetCodeRequest(email = email, code = code))
|
|
_verifyResetCodeState.value = when (result) {
|
|
is ApiResult.Success -> ApiResult.Success(result.data)
|
|
is ApiResult.Error -> result
|
|
else -> ApiResult.Error("Unknown error")
|
|
}
|
|
}
|
|
}
|
|
|
|
fun resetVerifyResetCodeState() {
|
|
_verifyResetCodeState.value = ApiResult.Idle
|
|
}
|
|
|
|
fun resetPassword(resetToken: String, newPassword: String, confirmPassword: String) {
|
|
viewModelScope.launch {
|
|
_resetPasswordState.value = ApiResult.Loading
|
|
// Note: confirmPassword is for UI validation only, not sent to API
|
|
val result = APILayer.resetPassword(
|
|
ResetPasswordRequest(
|
|
resetToken = resetToken,
|
|
newPassword = newPassword
|
|
)
|
|
)
|
|
_resetPasswordState.value = when (result) {
|
|
is ApiResult.Success -> ApiResult.Success(result.data)
|
|
is ApiResult.Error -> result
|
|
else -> ApiResult.Error("Unknown error")
|
|
}
|
|
}
|
|
}
|
|
|
|
fun resetResetPasswordState() {
|
|
_resetPasswordState.value = ApiResult.Idle
|
|
}
|
|
|
|
fun appleSignIn(
|
|
idToken: String,
|
|
userId: String,
|
|
email: String?,
|
|
firstName: String?,
|
|
lastName: String?
|
|
) {
|
|
viewModelScope.launch {
|
|
_appleSignInState.value = ApiResult.Loading
|
|
val result = APILayer.appleSignIn(
|
|
AppleSignInRequest(
|
|
idToken = idToken,
|
|
userId = userId,
|
|
email = email,
|
|
firstName = firstName,
|
|
lastName = lastName
|
|
)
|
|
)
|
|
_appleSignInState.value = when (result) {
|
|
is ApiResult.Success -> ApiResult.Success(result.data)
|
|
is ApiResult.Error -> result
|
|
else -> ApiResult.Error("Unknown error")
|
|
}
|
|
}
|
|
}
|
|
|
|
fun resetAppleSignInState() {
|
|
_appleSignInState.value = ApiResult.Idle
|
|
}
|
|
|
|
fun logout() {
|
|
viewModelScope.launch {
|
|
APILayer.logout()
|
|
TokenStorage.clearToken()
|
|
}
|
|
}
|
|
}
|