Files
honeyDueKMP/iosApp/iosApp/Contractor/ContractorViewModel.swift
Trey t a61cada072 Implement unified network layer with APILayer and migrate iOS ViewModels
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>
2025-11-12 20:29:42 -06:00

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