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>
235 lines
7.8 KiB
Swift
235 lines
7.8 KiB
Swift
import Foundation
|
|
import ComposeApp
|
|
import Combine
|
|
|
|
@MainActor
|
|
class ContractorViewModel: ObservableObject {
|
|
// MARK: - Published Properties
|
|
@Published var contractors: [ContractorSummary] = []
|
|
@Published var selectedContractor: Contractor?
|
|
@Published var isLoading: Bool = false
|
|
@Published var errorMessage: String?
|
|
@Published var isCreating: Bool = false
|
|
@Published var isUpdating: Bool = false
|
|
@Published var isDeleting: Bool = false
|
|
@Published var successMessage: String?
|
|
|
|
// MARK: - Private Properties
|
|
private let sharedViewModel: ComposeApp.ContractorViewModel
|
|
private var cancellables = Set<AnyCancellable>()
|
|
|
|
// MARK: - Initialization
|
|
init() {
|
|
self.sharedViewModel = ComposeApp.ContractorViewModel()
|
|
}
|
|
|
|
// MARK: - Public Methods
|
|
func loadContractors(
|
|
specialty: String? = nil,
|
|
isFavorite: Bool? = nil,
|
|
isActive: Bool? = nil,
|
|
search: String? = nil,
|
|
forceRefresh: Bool = false
|
|
) {
|
|
isLoading = true
|
|
errorMessage = nil
|
|
|
|
sharedViewModel.loadContractors(
|
|
specialty: specialty,
|
|
isFavorite: isFavorite?.toKotlinBoolean(),
|
|
isActive: isActive?.toKotlinBoolean(),
|
|
search: search,
|
|
forceRefresh: forceRefresh
|
|
)
|
|
|
|
// Observe the state
|
|
Task {
|
|
for await state in sharedViewModel.contractorsState {
|
|
if state is ApiResultLoading {
|
|
await MainActor.run {
|
|
self.isLoading = true
|
|
}
|
|
} else if let success = state as? ApiResultSuccess<ContractorListResponse> {
|
|
await MainActor.run {
|
|
self.contractors = success.data?.results ?? []
|
|
self.isLoading = false
|
|
}
|
|
break
|
|
} else if let error = state as? ApiResultError {
|
|
await MainActor.run {
|
|
self.errorMessage = error.message
|
|
self.isLoading = false
|
|
}
|
|
break
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
func loadContractorDetail(id: Int32) {
|
|
isLoading = true
|
|
errorMessage = nil
|
|
|
|
sharedViewModel.loadContractorDetail(id: id)
|
|
|
|
// Observe the state
|
|
Task {
|
|
for await state in sharedViewModel.contractorDetailState {
|
|
if state is ApiResultLoading {
|
|
await MainActor.run {
|
|
self.isLoading = true
|
|
}
|
|
} else if let success = state as? ApiResultSuccess<Contractor> {
|
|
await MainActor.run {
|
|
self.selectedContractor = success.data
|
|
self.isLoading = false
|
|
}
|
|
break
|
|
} else if let error = state as? ApiResultError {
|
|
await MainActor.run {
|
|
self.errorMessage = error.message
|
|
self.isLoading = false
|
|
}
|
|
break
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
func createContractor(request: ContractorCreateRequest, completion: @escaping (Bool) -> Void) {
|
|
isCreating = true
|
|
errorMessage = nil
|
|
|
|
sharedViewModel.createContractor(request: request)
|
|
|
|
// Observe the state
|
|
Task {
|
|
for await state in sharedViewModel.createState {
|
|
if state is ApiResultLoading {
|
|
await MainActor.run {
|
|
self.isCreating = true
|
|
}
|
|
} else if state is ApiResultSuccess<Contractor> {
|
|
await MainActor.run {
|
|
self.successMessage = "Contractor added successfully"
|
|
self.isCreating = false
|
|
}
|
|
sharedViewModel.resetCreateState()
|
|
completion(true)
|
|
break
|
|
} else if let error = state as? ApiResultError {
|
|
await MainActor.run {
|
|
self.errorMessage = error.message
|
|
self.isCreating = false
|
|
}
|
|
sharedViewModel.resetCreateState()
|
|
completion(false)
|
|
break
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
func updateContractor(id: Int32, request: ContractorUpdateRequest, completion: @escaping (Bool) -> Void) {
|
|
isUpdating = true
|
|
errorMessage = nil
|
|
|
|
sharedViewModel.updateContractor(id: id, request: request)
|
|
|
|
// Observe the state
|
|
Task {
|
|
for await state in sharedViewModel.updateState {
|
|
if state is ApiResultLoading {
|
|
await MainActor.run {
|
|
self.isUpdating = true
|
|
}
|
|
} else if state is ApiResultSuccess<Contractor> {
|
|
await MainActor.run {
|
|
self.successMessage = "Contractor updated successfully"
|
|
self.isUpdating = false
|
|
}
|
|
sharedViewModel.resetUpdateState()
|
|
completion(true)
|
|
break
|
|
} else if let error = state as? ApiResultError {
|
|
await MainActor.run {
|
|
self.errorMessage = error.message
|
|
self.isUpdating = false
|
|
}
|
|
sharedViewModel.resetUpdateState()
|
|
completion(false)
|
|
break
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
func deleteContractor(id: Int32, completion: @escaping (Bool) -> Void) {
|
|
isDeleting = true
|
|
errorMessage = nil
|
|
|
|
sharedViewModel.deleteContractor(id: id)
|
|
|
|
// Observe the state
|
|
Task {
|
|
for await state in sharedViewModel.deleteState {
|
|
if state is ApiResultLoading {
|
|
await MainActor.run {
|
|
self.isDeleting = true
|
|
}
|
|
} else if state is ApiResultSuccess<KotlinUnit> {
|
|
await MainActor.run {
|
|
self.successMessage = "Contractor deleted successfully"
|
|
self.isDeleting = false
|
|
}
|
|
sharedViewModel.resetDeleteState()
|
|
completion(true)
|
|
break
|
|
} else if let error = state as? ApiResultError {
|
|
await MainActor.run {
|
|
self.errorMessage = error.message
|
|
self.isDeleting = false
|
|
}
|
|
sharedViewModel.resetDeleteState()
|
|
completion(false)
|
|
break
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
func toggleFavorite(id: Int32, completion: @escaping (Bool) -> Void) {
|
|
sharedViewModel.toggleFavorite(id: id)
|
|
|
|
// Observe the state
|
|
Task {
|
|
for await state in sharedViewModel.toggleFavoriteState {
|
|
if state is ApiResultSuccess<Contractor> {
|
|
sharedViewModel.resetToggleFavoriteState()
|
|
completion(true)
|
|
break
|
|
} else if let error = state as? ApiResultError {
|
|
await MainActor.run {
|
|
self.errorMessage = error.message
|
|
}
|
|
sharedViewModel.resetToggleFavoriteState()
|
|
completion(false)
|
|
break
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
func clearMessages() {
|
|
errorMessage = nil
|
|
successMessage = nil
|
|
}
|
|
}
|
|
|
|
// MARK: - Helper Extension
|
|
extension Bool {
|
|
func toKotlinBoolean() -> KotlinBoolean {
|
|
return KotlinBoolean(bool: self)
|
|
}
|
|
}
|