diff --git a/composeApp/src/commonMain/kotlin/com/example/casera/network/ApiConfig.kt b/composeApp/src/commonMain/kotlin/com/example/casera/network/ApiConfig.kt index 45cffa3..a19e50e 100644 --- a/composeApp/src/commonMain/kotlin/com/example/casera/network/ApiConfig.kt +++ b/composeApp/src/commonMain/kotlin/com/example/casera/network/ApiConfig.kt @@ -9,7 +9,7 @@ package com.example.casera.network */ object ApiConfig { // ⚠️ CHANGE THIS TO TOGGLE ENVIRONMENT ⚠️ - val CURRENT_ENV = Environment.LOCAL + val CURRENT_ENV = Environment.DEV enum class Environment { LOCAL, diff --git a/iosApp/Casera/MyCrib.swift b/iosApp/Casera/MyCrib.swift index a780992..77a4e12 100644 --- a/iosApp/Casera/MyCrib.swift +++ b/iosApp/Casera/MyCrib.swift @@ -8,6 +8,53 @@ import WidgetKit import SwiftUI +// MARK: - Date Formatting Helper +/// 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 { + let calendar = Calendar.current + var date: Date? + + // Try parsing as yyyy-MM-dd first + let dateOnlyFormatter = DateFormatter() + dateOnlyFormatter.dateFormat = "yyyy-MM-dd" + date = dateOnlyFormatter.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) + + // Try without fractional seconds + if date == nil { + isoFormatter.formatOptions = [.withInternetDateTime] + date = isoFormatter.date(from: dateString) + } + } + + guard let parsedDate = date else { + return dateString + } + + let today = calendar.startOfDay(for: Date()) + let dueDay = calendar.startOfDay(for: parsedDate) + + if calendar.isDateInToday(parsedDate) { + return "Today" + } + + let components = calendar.dateComponents([.day], from: today, to: dueDay) + let days = components.day ?? 0 + + if days > 0 { + return days == 1 ? "in 1 day" : "in \(days) days" + } else { + let overdueDays = abs(days) + return overdueDays == 1 ? "1 day ago" : "\(overdueDays) days ago" + } +} + /// CacheManager reads tasks from the App Group shared container /// Data is written by the main app via WidgetDataManager class CacheManager { @@ -192,7 +239,7 @@ struct SmallWidgetView: View { HStack(spacing: 4) { Image(systemName: "calendar") .font(.system(size: 9)) - Text(formatDate(dueDate)) + Text(formatWidgetDate(dueDate)) .font(.system(size: 10, weight: .medium)) } .foregroundStyle(.orange) @@ -225,27 +272,6 @@ struct SmallWidgetView: View { } .padding(16) } - - private func formatDate(_ dateString: String) -> String { - let formatter = DateFormatter() - formatter.dateFormat = "yyyy-MM-dd" - - if let date = formatter.date(from: dateString) { - let now = Date() - let calendar = Calendar.current - - if calendar.isDateInToday(date) { - return "Today" - } else if calendar.isDateInTomorrow(date) { - return "Tomorrow" - } else { - formatter.dateFormat = "MMM d" - return formatter.string(from: date) - } - } - - return dateString - } } // MARK: - Medium Widget View @@ -338,15 +364,15 @@ struct TaskRowView: View { HStack(spacing: 3) { Image(systemName: "calendar") .font(.system(size: 8)) - Text(formatDate(dueDate)) + Text(formatWidgetDate(dueDate)) .font(.system(size: 9, weight: .medium)) } .foregroundStyle(.secondary) } } - + Spacer() - + if let priority = task.priority { Text(priority.prefix(1).uppercased()) .font(.system(size: 9, weight: .bold)) @@ -360,7 +386,7 @@ struct TaskRowView: View { } .padding(.vertical, 4) } - + private var priorityColor: Color { switch task.priority?.lowercased() { case "urgent": return .red @@ -369,27 +395,6 @@ struct TaskRowView: View { default: return .gray } } - - private func formatDate(_ dateString: String) -> String { - let formatter = DateFormatter() - formatter.dateFormat = "yyyy-MM-dd" - - if let date = formatter.date(from: dateString) { - let now = Date() - let calendar = Calendar.current - - if calendar.isDateInToday(date) { - return "Today" - } else if calendar.isDateInTomorrow(date) { - return "Tomorrow" - } else { - formatter.dateFormat = "MMM d" - return formatter.string(from: date) - } - } - - return dateString - } } // MARK: - Large Widget View @@ -483,7 +488,7 @@ struct LargeTaskRowView: View { HStack(spacing: 2) { Image(systemName: "calendar") .font(.system(size: 7)) - Text(formatDate(dueDate)) + Text(formatWidgetDate(dueDate)) .font(.system(size: 9, weight: isOverdue ? .semibold : .regular)) } .foregroundStyle(isOverdue ? .red : .secondary) @@ -518,28 +523,6 @@ struct LargeTaskRowView: View { default: return .gray } } - - private func formatDate(_ dateString: String) -> String { - let formatter = DateFormatter() - formatter.dateFormat = "yyyy-MM-dd" - - if let date = formatter.date(from: dateString) { - let calendar = Calendar.current - - if calendar.isDateInToday(date) { - return "Today" - } else if calendar.isDateInTomorrow(date) { - return "Tomorrow" - } else if calendar.isDateInYesterday(date) { - return "Yesterday" - } else { - formatter.dateFormat = "MMM d" - return formatter.string(from: date) - } - } - - return dateString - } } struct Casera: Widget { diff --git a/iosApp/iosApp/Helpers/WidgetDataManager.swift b/iosApp/iosApp/Helpers/WidgetDataManager.swift index 3d493c7..f30dde3 100644 --- a/iosApp/iosApp/Helpers/WidgetDataManager.swift +++ b/iosApp/iosApp/Helpers/WidgetDataManager.swift @@ -150,15 +150,32 @@ final class WidgetDataManager { private func isTaskOverdue(dueDate: String?, status: String?) -> Bool { guard let dueDateStr = dueDate else { return false } - let formatter = DateFormatter() - formatter.dateFormat = "yyyy-MM-dd" + var date: Date? - guard let date = formatter.date(from: dueDateStr) else { return false } + // Try parsing as yyyy-MM-dd first + let dateOnlyFormatter = DateFormatter() + dateOnlyFormatter.dateFormat = "yyyy-MM-dd" + date = dateOnlyFormatter.date(from: dueDateStr) + + // Try parsing as ISO8601 (RFC3339) if that fails + if date == nil { + let isoFormatter = ISO8601DateFormatter() + isoFormatter.formatOptions = [.withInternetDateTime, .withFractionalSeconds] + date = isoFormatter.date(from: dueDateStr) + + // Try without fractional seconds + if date == nil { + isoFormatter.formatOptions = [.withInternetDateTime] + date = isoFormatter.date(from: dueDateStr) + } + } + + guard let parsedDate = date 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()) + return !isCompleted && parsedDate < Calendar.current.startOfDay(for: Date()) } }