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:
@@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user