Major architectural improvements: - Created APILayer as single entry point for all network operations - Integrated cache-first reads with automatic cache updates on mutations - Migrated all shared Kotlin ViewModels to use APILayer instead of direct API calls - Migrated iOS ViewModels to wrap shared Kotlin ViewModels with StateFlow observation - Replaced LookupsManager with DataCache for centralized lookup data management - Added password reset methods to AuthViewModel - Added task completion and update methods to APILayer - Added residence user management methods to APILayer iOS specific changes: - Updated LoginViewModel, RegisterViewModel, ProfileViewModel to use shared AuthViewModel - Updated ContractorViewModel, DocumentViewModel to use shared ViewModels - Updated ResidenceViewModel to use shared ViewModel and APILayer - Updated TaskViewModel to wrap shared ViewModel with callback-based interface - Migrated PasswordResetViewModel and VerifyEmailViewModel to shared AuthViewModel - Migrated AllTasksView, CompleteTaskView, EditTaskView to use APILayer - Migrated ManageUsersView, ResidenceDetailView to use APILayer - Migrated JoinResidenceView to use async/await pattern with APILayer - Removed LookupsManager.swift in favor of DataCache - Fixed PushNotificationManager @MainActor issue - Converted all direct API calls to use async/await with proper error handling Benefits: - Reduced code duplication between iOS and Android - Consistent error handling across platforms - Automatic cache management for better performance - Centralized network layer for easier testing and maintenance - Net reduction of ~700 lines of code through shared logic 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
217 lines
8.2 KiB
Kotlin
217 lines
8.2 KiB
Kotlin
package com.mycrib.android.viewmodel
|
|
|
|
import androidx.lifecycle.ViewModel
|
|
import androidx.lifecycle.viewModelScope
|
|
import com.mycrib.shared.models.AuthResponse
|
|
import com.mycrib.shared.models.ForgotPasswordRequest
|
|
import com.mycrib.shared.models.ForgotPasswordResponse
|
|
import com.mycrib.shared.models.LoginRequest
|
|
import com.mycrib.shared.models.RegisterRequest
|
|
import com.mycrib.shared.models.ResetPasswordRequest
|
|
import com.mycrib.shared.models.ResetPasswordResponse
|
|
import com.mycrib.shared.models.Residence
|
|
import com.mycrib.shared.models.User
|
|
import com.mycrib.shared.models.VerifyEmailRequest
|
|
import com.mycrib.shared.models.VerifyEmailResponse
|
|
import com.mycrib.shared.models.VerifyResetCodeRequest
|
|
import com.mycrib.shared.models.VerifyResetCodeResponse
|
|
import com.mycrib.shared.network.ApiResult
|
|
import com.mycrib.network.APILayer
|
|
import com.mycrib.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
|
|
|
|
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.mycrib.shared.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
|
|
val result = APILayer.resetPassword(
|
|
ResetPasswordRequest(
|
|
resetToken = resetToken,
|
|
newPassword = newPassword,
|
|
confirmPassword = confirmPassword
|
|
)
|
|
)
|
|
_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 logout() {
|
|
viewModelScope.launch {
|
|
APILayer.logout()
|
|
TokenStorage.clearToken()
|
|
}
|
|
}
|
|
}
|