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:
@@ -3,13 +3,10 @@ import ComposeApp
|
||||
|
||||
struct JoinResidenceView: View {
|
||||
@Environment(\.dismiss) private var dismiss
|
||||
@StateObject private var viewModel = ResidenceViewModel()
|
||||
let onJoined: () -> Void
|
||||
|
||||
@State private var shareCode: String = ""
|
||||
@State private var isJoining = false
|
||||
@State private var errorMessage: String?
|
||||
|
||||
private let residenceApi = ResidenceApi(client: ApiClient_iosKt.createHttpClient())
|
||||
|
||||
var body: some View {
|
||||
NavigationView {
|
||||
@@ -24,9 +21,9 @@ struct JoinResidenceView: View {
|
||||
shareCode = String(newValue.prefix(6))
|
||||
}
|
||||
shareCode = shareCode.uppercased()
|
||||
errorMessage = nil
|
||||
viewModel.clearError()
|
||||
}
|
||||
.disabled(isJoining)
|
||||
.disabled(viewModel.isLoading)
|
||||
} header: {
|
||||
Text("Enter Share Code")
|
||||
} footer: {
|
||||
@@ -34,7 +31,7 @@ struct JoinResidenceView: View {
|
||||
.foregroundColor(.secondary)
|
||||
}
|
||||
|
||||
if let error = errorMessage {
|
||||
if let error = viewModel.errorMessage {
|
||||
Section {
|
||||
Text(error)
|
||||
.foregroundColor(.red)
|
||||
@@ -45,7 +42,7 @@ struct JoinResidenceView: View {
|
||||
Button(action: joinResidence) {
|
||||
HStack {
|
||||
Spacer()
|
||||
if isJoining {
|
||||
if viewModel.isLoading {
|
||||
ProgressView()
|
||||
.progressViewStyle(CircularProgressViewStyle())
|
||||
} else {
|
||||
@@ -55,7 +52,7 @@ struct JoinResidenceView: View {
|
||||
Spacer()
|
||||
}
|
||||
}
|
||||
.disabled(shareCode.count != 6 || isJoining)
|
||||
.disabled(shareCode.count != 6 || viewModel.isLoading)
|
||||
}
|
||||
}
|
||||
.navigationTitle("Join Residence")
|
||||
@@ -65,7 +62,7 @@ struct JoinResidenceView: View {
|
||||
Button("Cancel") {
|
||||
dismiss()
|
||||
}
|
||||
.disabled(isJoining)
|
||||
.disabled(viewModel.isLoading)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -73,29 +70,30 @@ struct JoinResidenceView: View {
|
||||
|
||||
private func joinResidence() {
|
||||
guard shareCode.count == 6 else {
|
||||
errorMessage = "Share code must be 6 characters"
|
||||
viewModel.errorMessage = "Share code must be 6 characters"
|
||||
return
|
||||
}
|
||||
|
||||
guard let token = TokenStorage.shared.getToken() else {
|
||||
errorMessage = "Not authenticated"
|
||||
return
|
||||
}
|
||||
Task {
|
||||
// Call the shared ViewModel which uses APILayer
|
||||
await viewModel.sharedViewModel.joinWithCode(code: shareCode)
|
||||
|
||||
isJoining = true
|
||||
errorMessage = nil
|
||||
|
||||
residenceApi.joinWithCode(token: token, code: shareCode) { result, error in
|
||||
if result is ApiResultSuccess<JoinResidenceResponse> {
|
||||
self.isJoining = false
|
||||
self.onJoined()
|
||||
self.dismiss()
|
||||
} else if let errorResult = result as? ApiResultError {
|
||||
self.errorMessage = errorResult.message
|
||||
self.isJoining = false
|
||||
} else if let error = error {
|
||||
self.errorMessage = error.localizedDescription
|
||||
self.isJoining = false
|
||||
// Observe the result
|
||||
for await state in viewModel.sharedViewModel.joinResidenceState {
|
||||
if state is ApiResultSuccess<JoinResidenceResponse> {
|
||||
await MainActor.run {
|
||||
viewModel.sharedViewModel.resetJoinResidenceState()
|
||||
onJoined()
|
||||
dismiss()
|
||||
}
|
||||
break
|
||||
} else if let error = state as? ApiResultError {
|
||||
await MainActor.run {
|
||||
viewModel.errorMessage = error.message
|
||||
viewModel.sharedViewModel.resetJoinResidenceState()
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user