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

@@ -10,6 +10,28 @@ import SwiftUI
import AppIntents
// MARK: - Date Formatting Helper
/// Cached formatters to avoid repeated allocation in widget rendering
private enum WidgetDateFormatters {
static let dateOnly: DateFormatter = {
let f = DateFormatter()
f.dateFormat = "yyyy-MM-dd"
return f
}()
static let iso8601WithFractional: ISO8601DateFormatter = {
let f = ISO8601DateFormatter()
f.formatOptions = [.withInternetDateTime, .withFractionalSeconds]
return f
}()
static let iso8601: ISO8601DateFormatter = {
let f = ISO8601DateFormatter()
f.formatOptions = [.withInternetDateTime]
return f
}()
}
/// Parses date strings in either yyyy-MM-dd or ISO8601 (RFC3339) format
/// and returns a user-friendly string like "Today" or "in X days"
private func formatWidgetDate(_ dateString: String) -> String {
@@ -17,20 +39,15 @@ private func formatWidgetDate(_ dateString: String) -> String {
var date: Date?
// Try parsing as yyyy-MM-dd first
let dateOnlyFormatter = DateFormatter()
dateOnlyFormatter.dateFormat = "yyyy-MM-dd"
date = dateOnlyFormatter.date(from: dateString)
date = WidgetDateFormatters.dateOnly.date(from: dateString)
// Try parsing as ISO8601 (RFC3339) if that fails
if date == nil {
let isoFormatter = ISO8601DateFormatter()
isoFormatter.formatOptions = [.withInternetDateTime, .withFractionalSeconds]
date = isoFormatter.date(from: dateString)
date = WidgetDateFormatters.iso8601WithFractional.date(from: dateString)
// Try without fractional seconds
if date == nil {
isoFormatter.formatOptions = [.withInternetDateTime]
date = isoFormatter.date(from: dateString)
date = WidgetDateFormatters.iso8601.date(from: dateString)
}
}
@@ -179,9 +196,11 @@ struct Provider: AppIntentTimelineProvider {
let tasks = CacheManager.getUpcomingTasks()
let isInteractive = WidgetActionManager.shared.shouldShowInteractiveWidget()
// Update every 30 minutes (more frequent for interactive widgets)
// Use a longer refresh interval during overnight hours (11pm-6am)
let currentDate = Date()
let nextUpdate = Calendar.current.date(byAdding: .minute, value: 30, to: currentDate)!
let hour = Calendar.current.component(.hour, from: currentDate)
let refreshMinutes = (hour >= 23 || hour < 6) ? 120 : 30
let nextUpdate = Calendar.current.date(byAdding: .minute, value: refreshMinutes, to: currentDate)!
let entry = SimpleEntry(
date: currentDate,
configuration: configuration,