Files
honeyDueKMP/iosApp/iosApp/Helpers/WidgetDataManager.swift
Trey t c6eef720ed Rebrand from MyCrib to Casera
- Rename Kotlin package from com.example.mycrib to com.example.casera
- Update Android app name, namespace, and application ID
- Update iOS bundle identifiers and project settings
- Rename iOS directories (MyCribTests -> CaseraTests, etc.)
- Update deep link schemes from mycrib:// to casera://
- Update app group identifiers
- Update subscription product IDs
- Update all UI strings and branding

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

Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-28 21:10:38 -06:00

165 lines
5.7 KiB
Swift

import Foundation
import WidgetKit
import ComposeApp
/// Manages shared data between the main app and the widget extension
/// Uses App Group container to share files
final class WidgetDataManager {
static let shared = WidgetDataManager()
private let appGroupIdentifier = "group.com.tt.casera.CaseraDev"
private let tasksFileName = "widget_tasks.json"
private init() {}
/// Task model for widget display - simplified version of TaskDetail
struct WidgetTask: Codable {
let id: Int
let title: String
let description: String?
let priority: String?
let status: String?
let dueDate: String?
let category: String?
let residenceName: String?
let isOverdue: Bool
enum CodingKeys: String, CodingKey {
case id, title, description, priority, status, category
case dueDate = "due_date"
case residenceName = "residence_name"
case isOverdue = "is_overdue"
}
}
/// Get the shared App Group container URL
private var sharedContainerURL: URL? {
FileManager.default.containerURL(forSecurityApplicationGroupIdentifier: appGroupIdentifier)
}
/// Get the URL for the tasks file
private var tasksFileURL: URL? {
sharedContainerURL?.appendingPathComponent(tasksFileName)
}
/// Save tasks to the shared container for widget access
/// Call this after loading tasks in the main app
func saveTasks(from response: TaskColumnsResponse) {
guard let fileURL = tasksFileURL else {
print("WidgetDataManager: Unable to access shared container")
return
}
// Extract tasks from all columns and convert to WidgetTask
var allTasks: [WidgetTask] = []
for column in response.columns {
for task in column.tasks {
let widgetTask = WidgetTask(
id: Int(task.id),
title: task.title,
description: task.description_,
priority: task.priority?.name ?? "",
status: task.status?.name,
dueDate: task.dueDate,
category: task.category?.name ?? "",
residenceName: "", // No longer available in API, residence lookup needed
isOverdue: isTaskOverdue(dueDate: task.dueDate, status: task.status?.name)
)
allTasks.append(widgetTask)
}
}
do {
let encoder = JSONEncoder()
encoder.outputFormatting = .prettyPrinted
let data = try encoder.encode(allTasks)
try data.write(to: fileURL, options: .atomic)
print("WidgetDataManager: Saved \(allTasks.count) tasks to widget cache")
// Reload widget timeline
WidgetCenter.shared.reloadAllTimelines()
} catch {
print("WidgetDataManager: Error saving tasks - \(error)")
}
}
/// Load tasks from the shared container
/// Used by the widget to read cached data
func loadTasks() -> [WidgetTask] {
guard let fileURL = tasksFileURL else {
print("WidgetDataManager: Unable to access shared container")
return []
}
guard FileManager.default.fileExists(atPath: fileURL.path) else {
print("WidgetDataManager: No cached tasks file found")
return []
}
do {
let data = try Data(contentsOf: fileURL)
let tasks = try JSONDecoder().decode([WidgetTask].self, from: data)
print("WidgetDataManager: Loaded \(tasks.count) tasks from widget cache")
return tasks
} catch {
print("WidgetDataManager: Error loading tasks - \(error)")
return []
}
}
/// Get upcoming/pending tasks for widget display
func getUpcomingTasks() -> [WidgetTask] {
let allTasks = loadTasks()
// Filter for pending/in-progress tasks (non-archived, non-completed)
let upcoming = allTasks.filter { task in
let status = task.status?.lowercased() ?? ""
return status == "pending" || status == "in_progress" || status == "in progress"
}
// Sort by due date (earliest first), with overdue at top
return upcoming.sorted { task1, task2 in
// Overdue tasks first
if task1.isOverdue != task2.isOverdue {
return task1.isOverdue
}
// Then by due date
guard let date1 = task1.dueDate, let date2 = task2.dueDate else {
return task1.dueDate != nil
}
return date1 < date2
}
}
/// Clear cached task data (call on logout)
func clearCache() {
guard let fileURL = tasksFileURL else { return }
do {
try FileManager.default.removeItem(at: fileURL)
print("WidgetDataManager: Cleared widget cache")
WidgetCenter.shared.reloadAllTimelines()
} catch {
print("WidgetDataManager: Error clearing cache - \(error)")
}
}
/// Check if a task is overdue based on due date and status
private func isTaskOverdue(dueDate: String?, status: String?) -> Bool {
guard let dueDateStr = dueDate else { return false }
let formatter = DateFormatter()
formatter.dateFormat = "yyyy-MM-dd"
guard let date = formatter.date(from: dueDateStr) else { return false }
// Task is overdue if due date is in the past and status is not completed
let statusLower = status?.lowercased() ?? ""
let isCompleted = statusLower == "completed" || statusLower == "done"
return !isCompleted && date < Calendar.current.startOfDay(for: Date())
}
}