Fix 12 iOS issues: race conditions, data flow, UX

Critical bugs:
- RootView: auth check deferred to .task{} modifier (after DataManager init)
- DataManagerObservable: map conversion failures now logged with key details
- ContractorViewModel: replace stuck boolean flag with time-based suppression
- DocumentViewModel: guard full success.data before image upload

Logic fixes:
- AllTasksView: 300ms delay before animation flag release
- ResidenceViewModel: trigger initializeLookups() if not ready
- TaskFormView: hasDueDate toggle prevents defaulting to today
- OnboardingState: guard isAuthenticated before completing onboarding

UX fixes:
- ResidencesListView: 10-second refresh timeout
- AllTasksView: add button disabled while sheet presented
- TaskViewModel: actionState auto-resets after 3s, explicit reset on consume
This commit is contained in:
Trey T
2026-03-26 18:01:49 -05:00
parent 4d363ca44e
commit 8f86fa2cd0
10 changed files with 119 additions and 21 deletions

View File

@@ -96,6 +96,13 @@ class ResidenceViewModel: ObservableObject {
/// Load my residences - checks cache first, then fetches if needed
func loadMyResidences(forceRefresh: Bool = false) {
// Ensure lookups are initialized (may not be during onboarding)
if !DataManagerObservable.shared.lookupsInitialized {
Task {
_ = try? await APILayer.shared.initializeLookups()
}
}
if UITestRuntime.shouldMockAuth {
if Self.uiTestMockResidences.isEmpty || forceRefresh {
if Self.uiTestMockResidences.isEmpty {

View File

@@ -91,13 +91,13 @@ struct ResidencesListView: View {
AddResidenceView(
isPresented: $showingAddResidence,
onResidenceCreated: {
viewModel.loadMyResidences(forceRefresh: true)
refreshWithTimeout()
}
)
}
.sheet(isPresented: $showingJoinResidence) {
JoinResidenceView(onJoined: {
viewModel.loadMyResidences(forceRefresh: true)
refreshWithTimeout()
})
}
.sheet(isPresented: $showingUpgradePrompt) {
@@ -142,6 +142,17 @@ struct ResidencesListView: View {
}
}
/// Refresh residences with a 10-second timeout to prevent indefinite loading
private func refreshWithTimeout() {
viewModel.loadMyResidences(forceRefresh: true)
// Safety timeout: if the API hangs, clear loading state after 10 seconds
DispatchQueue.main.asyncAfter(deadline: .now() + 10) {
if viewModel.isLoading {
viewModel.isLoading = false
}
}
}
private func navigateToResidenceFromPush(residenceId: Int) {
pushTargetResidenceId = Int32(residenceId)
PushNotificationManager.shared.pendingNavigationResidenceId = nil