Add task completion animations and fix 7-day task count

Animation Testing:
- Add AnimationTesting module with 14 animation types for task completion
- Include 4 celebration animations: Implode, Firework, Starburst, Ripple
- Card shrinks, shows checkmark with effect, then moves to next column
- Extended timing (2.2s) for celebration animations

Task Count Fix:
- Fix "7 Days" and "30 Days" counts on residence cards and dashboard
- Previously used API column membership (30-day "due soon" column)
- Now calculates actual days until due from task's effectiveDueDate
- Correctly counts tasks due within 7 days vs 8-30 days

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
Trey t
2025-12-26 21:21:48 -06:00
parent 556b187508
commit 3274924937
8 changed files with 1553 additions and 6 deletions
+35 -3
View File
@@ -280,6 +280,21 @@ final class WidgetDataManager {
)
}
/// Parse a date string (handles both "yyyy-MM-dd" and ISO datetime formats)
/// Extracts date part if it includes time (e.g., "2025-12-26T00:00:00Z" -> "2025-12-26")
private static let dateFormatter: DateFormatter = {
let formatter = DateFormatter()
formatter.dateFormat = "yyyy-MM-dd"
return formatter
}()
static func parseDate(_ dateString: String?) -> Date? {
guard let dateString = dateString, !dateString.isEmpty else { return nil }
// Extract date part if it includes time
let datePart = dateString.components(separatedBy: "T").first ?? dateString
return dateFormatter.date(from: datePart)
}
/// Get the shared App Group container URL
private var sharedContainerURL: URL? {
FileManager.default.containerURL(forSecurityApplicationGroupIdentifier: appGroupIdentifier)
@@ -300,6 +315,9 @@ final class WidgetDataManager {
// Columns to exclude from widget (these are "done" states)
let excludedColumns = [Self.completedColumn, Self.cancelledColumn]
// Date calculation setup
let today = Calendar.current.startOfDay(for: Date())
// Extract tasks from active columns only and convert to WidgetTask
var allTasks: [WidgetTask] = []
@@ -309,12 +327,26 @@ final class WidgetDataManager {
continue
}
// Determine flags based on column name (using shared constants)
// isOverdue is based on column (API correctly calculates this)
let isOverdue = column.name == Self.overdueColumn
let isDueWithin7Days = column.name == Self.dueWithin7DaysColumn
let isDue8To30Days = column.name == Self.due8To30DaysColumn
for task in column.tasks {
// Calculate isDueWithin7Days and isDue8To30Days from actual due date
var isDueWithin7Days = false
var isDue8To30Days = false
if let dueDate = Self.parseDate(task.effectiveDueDate) {
let dueDay = Calendar.current.startOfDay(for: dueDate)
let daysUntilDue = Calendar.current.dateComponents([.day], from: today, to: dueDay).day ?? 0
// Only count future tasks (not overdue)
if daysUntilDue >= 0 && daysUntilDue <= 7 {
isDueWithin7Days = true
} else if daysUntilDue > 7 && daysUntilDue <= 30 {
isDue8To30Days = true
}
}
let widgetTask = WidgetTask(
id: Int(task.id),
title: task.title,