Files
honeyDueKMP/composeApp/src/commonMain/kotlin/com/example/casera/viewmodel/AuthViewModel.kt
Trey t 5a1a87fe8d Add Sign in with Apple for iOS
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>
2025-11-29 01:17:38 -06:00

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()
}
}
}