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

@@ -100,7 +100,6 @@ struct TaskFormView: View {
if needsResidenceSelection, let residences = residences {
Section {
Picker(L10n.Tasks.property, selection: $selectedResidence) {
Text(L10n.Tasks.selectProperty).tag(nil as ResidenceResponse?)
ForEach(residences, id: \.id) { residence in
Text(residence.name).tag(residence as ResidenceResponse?)
}
@@ -111,10 +110,6 @@ struct TaskFormView: View {
}
} header: {
Text(L10n.Tasks.property)
} footer: {
Text(L10n.Tasks.required)
.font(.caption)
.foregroundColor(Color.appError)
}
.sectionBackground()
}
@@ -168,7 +163,7 @@ struct TaskFormView: View {
VStack(alignment: .leading, spacing: 8) {
TextField(L10n.Tasks.titleLabel, text: $title)
.focused($focusedField, equals: .title)
.onChange(of: title) { newValue in
.onChange(of: title) { _, newValue in
updateSuggestions(query: newValue)
}
@@ -190,7 +185,6 @@ struct TaskFormView: View {
TextField(L10n.Tasks.descriptionOptional, text: $description, axis: .vertical)
.lineLimit(3...6)
.focused($focusedField, equals: .description)
.keyboardDismissToolbar()
} header: {
Text(L10n.Tasks.taskDetails)
} footer: {
@@ -219,7 +213,7 @@ struct TaskFormView: View {
Text(frequency.displayName).tag(frequency as TaskFrequency?)
}
}
.onChange(of: selectedFrequency) { newFrequency in
.onChange(of: selectedFrequency) { _, newFrequency in
// Clear interval days if not Custom frequency
if newFrequency?.name.lowercased() != "custom" {
intervalDays = ""
@@ -231,7 +225,6 @@ struct TaskFormView: View {
TextField(L10n.Tasks.customInterval, text: $intervalDays)
.keyboardType(.numberPad)
.focused($focusedField, equals: .intervalDays)
.keyboardDismissToolbar()
}
DatePicker(L10n.Tasks.dueDate, selection: $dueDate, displayedComponents: .date)
@@ -266,7 +259,6 @@ struct TaskFormView: View {
.focused($focusedField, equals: .estimatedCost)
}
.sectionBackground()
.keyboardDismissToolbar()
if let errorMessage = viewModel.errorMessage {
Section {
@@ -290,11 +282,20 @@ struct TaskFormView: View {
}
ToolbarItem(placement: .navigationBarTrailing) {
Button(L10n.Common.save) {
Button(isEditMode ? L10n.Common.save : L10n.Common.add) {
submitForm()
}
.disabled(!canSave || viewModel.isLoading || isLoadingLookups)
}
ToolbarItemGroup(placement: .keyboard) {
Spacer()
Button("Done") {
focusedField = nil
}
.foregroundColor(Color.appPrimary)
.fontWeight(.medium)
}
}
.onAppear {
// Track screen view for new tasks
@@ -306,17 +307,17 @@ struct TaskFormView: View {
setDefaults()
}
}
.onChange(of: dataManager.lookupsInitialized) { initialized in
.onChange(of: dataManager.lookupsInitialized) { _, initialized in
if initialized {
setDefaults()
}
}
.onChange(of: viewModel.taskCreated) { created in
.onChange(of: viewModel.taskCreated) { _, created in
if created {
isPresented = false
}
}
.onChange(of: viewModel.errorMessage) { errorMessage in
.onChange(of: viewModel.errorMessage) { _, errorMessage in
if let errorMessage = errorMessage, !errorMessage.isEmpty {
errorAlert = ErrorAlertInfo(message: errorMessage)
}