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>
This commit is contained in:
@@ -162,3 +162,27 @@ data class ResetPasswordResponse(
|
||||
data class MessageResponse(
|
||||
val message: String
|
||||
)
|
||||
|
||||
// Apple Sign In Models
|
||||
|
||||
/**
|
||||
* Apple Sign In request matching Go API
|
||||
*/
|
||||
@Serializable
|
||||
data class AppleSignInRequest(
|
||||
@SerialName("id_token") val idToken: String,
|
||||
@SerialName("user_id") val userId: String,
|
||||
val email: String? = null,
|
||||
@SerialName("first_name") val firstName: String? = null,
|
||||
@SerialName("last_name") val lastName: String? = null
|
||||
)
|
||||
|
||||
/**
|
||||
* Apple Sign In response matching Go API
|
||||
*/
|
||||
@Serializable
|
||||
data class AppleSignInResponse(
|
||||
val token: String,
|
||||
val user: User,
|
||||
@SerialName("is_new_user") val isNewUser: Boolean
|
||||
)
|
||||
|
||||
@@ -921,6 +921,20 @@ object APILayer {
|
||||
return authApi.resetPassword(request)
|
||||
}
|
||||
|
||||
suspend fun appleSignIn(request: AppleSignInRequest): ApiResult<AppleSignInResponse> {
|
||||
val result = authApi.appleSignIn(request)
|
||||
|
||||
// Update cache on success
|
||||
if (result is ApiResult.Success) {
|
||||
TokenStorage.saveToken(result.data.token)
|
||||
DataCache.updateCurrentUser(result.data.user)
|
||||
// Prefetch all data after successful Apple sign in
|
||||
prefetchManager.prefetchAllData()
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
suspend fun updateProfile(token: String, request: UpdateProfileRequest): ApiResult<User> {
|
||||
val result = authApi.updateProfile(token, request)
|
||||
|
||||
|
||||
@@ -200,4 +200,27 @@ class AuthApi(private val client: HttpClient = ApiClient.httpClient) {
|
||||
ApiResult.Error(e.message ?: "Unknown error occurred")
|
||||
}
|
||||
}
|
||||
|
||||
// Apple Sign In
|
||||
suspend fun appleSignIn(request: AppleSignInRequest): ApiResult<AppleSignInResponse> {
|
||||
return try {
|
||||
val response = client.post("$baseUrl/auth/apple-sign-in/") {
|
||||
contentType(ContentType.Application.Json)
|
||||
setBody(request)
|
||||
}
|
||||
|
||||
if (response.status.isSuccess()) {
|
||||
ApiResult.Success(response.body())
|
||||
} else {
|
||||
val errorBody = try {
|
||||
response.body<Map<String, String>>()
|
||||
} catch (e: Exception) {
|
||||
mapOf("error" to "Apple Sign In failed")
|
||||
}
|
||||
ApiResult.Error(errorBody["error"] ?: "Apple Sign In failed", response.status.value)
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
ApiResult.Error(e.message ?: "Unknown error occurred")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,6 +2,8 @@ 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
|
||||
@@ -48,6 +50,9 @@ class AuthViewModel : ViewModel() {
|
||||
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
|
||||
@@ -207,6 +212,36 @@ class AuthViewModel : ViewModel() {
|
||||
_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()
|
||||
|
||||
Reference in New Issue
Block a user