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>
This commit is contained in:
Trey t
2025-11-12 20:29:42 -06:00
parent eeb8a96f20
commit a61cada072
38 changed files with 2458 additions and 2395 deletions

View File

@@ -282,15 +282,14 @@ struct CompleteTaskView: View {
}
private func handleComplete() {
isSubmitting = true
guard let token = TokenStorage.shared.getToken() else {
guard TokenStorage.shared.getToken() != nil else {
errorMessage = "Not authenticated"
showError = true
isSubmitting = false
return
}
isSubmitting = true
// Get current date in ISO format
let dateFormatter = ISO8601DateFormatter()
let currentDate = dateFormatter.string(from: Date())
@@ -310,48 +309,52 @@ struct CompleteTaskView: View {
rating: KotlinInt(int: Int32(rating))
)
let completionApi = TaskCompletionApi(client: ApiClient_iosKt.createHttpClient())
Task {
do {
let result: ApiResult<TaskCompletion>
// If there are images, upload with images
if !selectedImages.isEmpty {
// Compress images to meet size requirements
let imageDataArray = ImageCompression.compressImages(selectedImages)
let imageByteArrays = imageDataArray.map { KotlinByteArray(data: $0) }
let fileNames = (0..<imageDataArray.count).map { "image_\($0).jpg" }
// If there are images, upload with images
if !selectedImages.isEmpty {
// Compress images to meet size requirements
let imageDataArray = ImageCompression.compressImages(selectedImages)
let imageByteArrays = imageDataArray.map { KotlinByteArray(data: $0) }
let fileNames = (0..<imageDataArray.count).map { "image_\($0).jpg" }
completionApi.createCompletionWithImages(
token: token,
request: request,
images: imageByteArrays,
imageFileNames: fileNames
) { result, error in
handleCompletionResult(result: result, error: error)
}
} else {
// Upload without images
completionApi.createCompletion(token: token, request: request) { result, error in
handleCompletionResult(result: result, error: error)
result = try await APILayer.shared.createTaskCompletionWithImages(
request: request,
images: imageByteArrays,
imageFileNames: fileNames
)
} else {
// Upload without images
result = try await APILayer.shared.createTaskCompletion(request: request)
}
await MainActor.run {
if result is ApiResultSuccess<TaskCompletion> {
self.isSubmitting = false
self.dismiss()
self.onComplete()
} else if let errorResult = result as? ApiResultError {
self.errorMessage = errorResult.message
self.showError = true
self.isSubmitting = false
} else {
self.errorMessage = "Failed to complete task"
self.showError = true
self.isSubmitting = false
}
}
} catch {
await MainActor.run {
self.errorMessage = error.localizedDescription
self.showError = true
self.isSubmitting = false
}
}
}
}
private func handleCompletionResult(result: ApiResult<TaskCompletion>?, error: Error?) {
DispatchQueue.main.async {
if result is ApiResultSuccess<TaskCompletion> {
self.isSubmitting = false
self.dismiss()
self.onComplete()
} else if let errorResult = result as? ApiResultError {
self.errorMessage = errorResult.message
self.showError = true
self.isSubmitting = false
} else if let error = error {
self.errorMessage = error.localizedDescription
self.showError = true
self.isSubmitting = false
}
}
}
}
// Helper extension to convert Data to KotlinByteArray