Harden iOS app with audit fixes, UI consistency, and sheet race condition fixes

Applies verified fixes from deep audit (concurrency, performance, security,
accessibility), standardizes CRUD form buttons to Add/Save pattern, removes
.drawingGroup() that broke search bar TextFields, and converts vulnerable
.sheet(isPresented:) + if-let patterns to safe presentation to prevent
blank white modals.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Trey t
2026-03-06 09:59:56 -06:00
parent 61ab95d108
commit 9c574c4343
76 changed files with 824 additions and 971 deletions

View File

@@ -1,59 +1,75 @@
import SwiftUI
struct MainTabView: View {
enum Tab: Hashable {
case residences
case tasks
case contractors
case documents
}
@EnvironmentObject private var themeManager: ThemeManager
@State private var selectedTab = 0
@State private var selectedTab: Tab = .residences
@State private var residencesPath = NavigationPath()
@State private var tasksPath = NavigationPath()
@State private var contractorsPath = NavigationPath()
@State private var documentsPath = NavigationPath()
@ObservedObject private var authManager = AuthenticationManager.shared
@ObservedObject private var pushManager = PushNotificationManager.shared
var refreshID: UUID
var body: some View {
TabView(selection: $selectedTab) {
NavigationStack {
NavigationStack(path: $residencesPath) {
ResidencesListView()
}
.id(refreshID)
.tabItem {
Label("Residences", image: "tab_view_house")
}
.tag(0)
.tag(Tab.residences)
.accessibilityIdentifier(AccessibilityIdentifiers.Navigation.residencesTab)
NavigationStack {
NavigationStack(path: $tasksPath) {
AllTasksView()
}
.id(refreshID)
.tabItem {
Label("Tasks", systemImage: "checklist")
}
.tag(1)
.tag(Tab.tasks)
.accessibilityIdentifier(AccessibilityIdentifiers.Navigation.tasksTab)
NavigationStack {
NavigationStack(path: $contractorsPath) {
ContractorsListView()
}
.id(refreshID)
.tabItem {
Label("Contractors", systemImage: "wrench.and.screwdriver.fill")
}
.tag(2)
.tag(Tab.contractors)
.accessibilityIdentifier(AccessibilityIdentifiers.Navigation.contractorsTab)
NavigationStack {
NavigationStack(path: $documentsPath) {
DocumentsWarrantiesView(residenceId: nil)
}
.id(refreshID)
.tabItem {
Label("Docs", systemImage: "doc.text.fill")
}
.tag(3)
.tag(Tab.documents)
.accessibilityIdentifier(AccessibilityIdentifiers.Navigation.documentsTab)
}
.tabViewStyle(.sidebarAdaptable)
.tint(Color.appPrimary)
.onChange(of: authManager.isAuthenticated) { _, _ in
selectedTab = 0
selectedTab = .residences
}
.onAppear {
// FIX_SKIPPED(F-10): UITabBar.appearance() is the standard SwiftUI pattern
// for customizing tab bar appearance. The global side effect persists but
// there is no safe alternative without UIKit hosting.
// Configure tab bar appearance
let appearance = UITabBarAppearance()
appearance.configureWithOpaqueBackground()
@@ -61,18 +77,18 @@ struct MainTabView: View {
// Use theme-aware colors
appearance.backgroundColor = UIColor(Color.appBackgroundSecondary)
// Selected item
// Selected item uses Dynamic Type caption2 style (A-2)
appearance.stackedLayoutAppearance.selected.iconColor = UIColor(Color.appPrimary)
appearance.stackedLayoutAppearance.selected.titleTextAttributes = [
.foregroundColor: UIColor(Color.appPrimary),
.font: UIFont.systemFont(ofSize: 10, weight: .semibold)
.font: UIFont.preferredFont(forTextStyle: .caption2)
]
// Normal item
// Normal item uses Dynamic Type caption2 style (A-2)
appearance.stackedLayoutAppearance.normal.iconColor = UIColor(Color.appTextSecondary)
appearance.stackedLayoutAppearance.normal.titleTextAttributes = [
.foregroundColor: UIColor(Color.appTextSecondary),
.font: UIFont.systemFont(ofSize: 10, weight: .medium)
.font: UIFont.preferredFont(forTextStyle: .caption2)
]
UITabBar.appearance().standardAppearance = appearance
@@ -80,27 +96,27 @@ struct MainTabView: View {
// Handle pending navigation from push notification
if pushManager.pendingNavigationTaskId != nil {
selectedTab = 1
selectedTab = .tasks
} else if pushManager.pendingNavigationDocumentId != nil {
selectedTab = 3
selectedTab = .documents
} else if pushManager.pendingNavigationResidenceId != nil {
selectedTab = 0
selectedTab = .residences
}
}
.onReceive(NotificationCenter.default.publisher(for: .navigateToTask)) { _ in
selectedTab = 1
selectedTab = .tasks
}
.onReceive(NotificationCenter.default.publisher(for: .navigateToEditTask)) { _ in
selectedTab = 1
selectedTab = .tasks
}
.onReceive(NotificationCenter.default.publisher(for: .navigateToResidence)) { _ in
selectedTab = 0
selectedTab = .residences
}
.onReceive(NotificationCenter.default.publisher(for: .navigateToDocument)) { _ in
selectedTab = 3
selectedTab = .documents
}
.onReceive(NotificationCenter.default.publisher(for: .navigateToHome)) { _ in
selectedTab = 0
selectedTab = .residences
}
}
}