diff --git a/KisaniCal/ISSUES.md b/KisaniCal/ISSUES.md index 84abdd2..bb135ab 100644 --- a/KisaniCal/ISSUES.md +++ b/KisaniCal/ISSUES.md @@ -458,3 +458,27 @@ Files: `ExerciseModels.swift`, `WorkoutView.swift`. Stats are computed from the app's own per-day logs. Health (steps/kcal/resting HR + workout dates) is already read on the Today dashboard and merged into the streak; a Health overlay on this screen could be added later. + +--- + +## KC-15 — Bars countdown widget + unified slate/orange widget theme + +**Status:** In Progress (implemented & build-verified, pending device build) +**Reported by:** User +**Area:** Widgets + +### Description +Add a bar-meter event-countdown widget (per reference screenshot) and re-theme +every widget to one look: dark slate background + brand orange, matching the app. + +### Fix +- `WidgetTheme` (slate background `RGB(0.224,0.255,0.31)`, brand orange accent). +- `BarGrid` vertical-bar progress view; new `EventBarsView` + `EventBarsWidget` + ("Event Countdown (Bars)", medium) reusing the existing event config/provider. +- `EventEntry.fineTimeLeft` (days → "X hr, Y min left") for the bars label. +- Re-themed all home-screen widgets to slate bg + orange: Day Progress, Tasks Done + Today, Event Countdown (dots), My Tasks. Lock-screen accessory widgets stay on + `.thinMaterial` (system-tinted on the Lock Screen). + +Files: `WidgetViews.swift`, `EventCountdownWidget.swift`, `MyTasksWidget.swift`, +`KisaniCalWidgets.swift`. diff --git a/KisaniCalWidgets/EventCountdownWidget.swift b/KisaniCalWidgets/EventCountdownWidget.swift index 71ae09e..dc2e831 100644 --- a/KisaniCalWidgets/EventCountdownWidget.swift +++ b/KisaniCalWidgets/EventCountdownWidget.swift @@ -151,6 +151,18 @@ struct EventEntry: TimelineEntry { return max(0, d) } + /// Granular countdown for the bars widget: days → hr → min as it nears. + var fineTimeLeft: String { + if target <= date { return "Today" } + let secs = target.timeIntervalSince(date) + let days = Int(secs / 86400) + let hrs = Int(secs.truncatingRemainder(dividingBy: 86400) / 3600) + let mins = Int(secs.truncatingRemainder(dividingBy: 3600) / 60) + if days >= 1 { return hrs > 0 ? "\(days)d, \(hrs) hr left" : "\(days) day\(days == 1 ? "" : "s") left" } + if hrs >= 1 { return "\(hrs) hr, \(mins) min left" } + return "\(mins) min left" + } + /// Countdown from the current date. Tapping the label cycles the unit: /// days → weeks → time remaining. var timeLeftLabel: String { @@ -254,3 +266,18 @@ struct EventCountdownWidget: Widget { .supportedFamilies([.systemMedium, .systemSmall]) } } + +// MARK: - Event Countdown (Bars) + +struct EventBarsWidget: Widget { + let kind = "KisaniEventBars" + + var body: some WidgetConfiguration { + AppIntentConfiguration(kind: kind, intent: EventConfigIntent.self, provider: EventProvider()) { entry in + EventBarsView(entry: entry) + } + .configurationDisplayName("Event Countdown (Bars)") + .description("Count down to an event with a bar meter.") + .supportedFamilies([.systemMedium]) + } +} diff --git a/KisaniCalWidgets/KisaniCalWidgets.swift b/KisaniCalWidgets/KisaniCalWidgets.swift index a9cc3a8..b858023 100644 --- a/KisaniCalWidgets/KisaniCalWidgets.swift +++ b/KisaniCalWidgets/KisaniCalWidgets.swift @@ -40,7 +40,7 @@ struct DayProgressWidget: Widget { let kind = "KisaniDayProgress" var body: some WidgetConfiguration { StaticConfiguration(kind: kind, provider: KisaniProvider()) { _ in - ProgressDotView(period: .day, accent: Color(red: 0.48, green: 0.87, blue: 0.80)) + ProgressDotView(period: .day, accent: WidgetTheme.accent) } .configurationDisplayName("Day Progress") .description("How much of today has elapsed, as a dot grid.") @@ -106,6 +106,7 @@ struct LockScreenCircularWidget: Widget { struct KisaniWidgetBundle: WidgetBundle { var body: some Widget { EventCountdownWidget() + EventBarsWidget() MyTasksWidget() DayProgressWidget() TasksCompleteWidget() diff --git a/KisaniCalWidgets/MyTasksWidget.swift b/KisaniCalWidgets/MyTasksWidget.swift index 738072d..904c12e 100644 --- a/KisaniCalWidgets/MyTasksWidget.swift +++ b/KisaniCalWidgets/MyTasksWidget.swift @@ -96,7 +96,7 @@ struct MyTasksView: View { HStack(spacing: 6) { Text("Today") .font(.system(size: 18, weight: .bold)) - .foregroundColor(Color(red: 0.42, green: 0.5, blue: 0.96)) + .foregroundColor(WidgetTheme.accent) Text("\(entry.openCount)") .font(.system(size: 15, weight: .semibold)) .foregroundColor(Color(white: 0.5)) @@ -104,7 +104,7 @@ struct MyTasksView: View { Button(intent: AddTaskFromWidgetIntent()) { Image(systemName: "plus") .font(.system(size: 16, weight: .semibold)) - .foregroundColor(Color(red: 0.42, green: 0.5, blue: 0.96)) + .foregroundColor(WidgetTheme.accent) } .buttonStyle(.plain) } @@ -143,6 +143,6 @@ struct MyTasksView: View { Spacer(minLength: 0) } } - .containerBackground(.black, for: .widget) + .containerBackground(WidgetTheme.background, for: .widget) } } diff --git a/KisaniCalWidgets/WidgetViews.swift b/KisaniCalWidgets/WidgetViews.swift index 0cf9ea3..f2f5bb2 100644 --- a/KisaniCalWidgets/WidgetViews.swift +++ b/KisaniCalWidgets/WidgetViews.swift @@ -98,7 +98,7 @@ struct ProgressDotView: View { DotGrid(total: 100, filled: Int(progress * 100), color: accent) } .padding(.horizontal, isMedium ? 4 : 0) - .containerBackground(.black, for: .widget) + .containerBackground(WidgetTheme.background, for: .widget) } } @@ -106,7 +106,7 @@ struct ProgressDotView: View { struct TasksCompleteView: View { @Environment(\.widgetFamily) private var family - private let teal = Color(red: 0.48, green: 0.87, blue: 0.80) + private let teal = WidgetTheme.accent private var stats: (done: Int, total: Int) { todayTaskCompletion() } private var percent: Int { @@ -144,7 +144,7 @@ struct TasksCompleteView: View { } } .padding(.horizontal, isMedium ? 4 : 0) - .containerBackground(.black, for: .widget) + .containerBackground(WidgetTheme.background, for: .widget) } } @@ -211,7 +211,7 @@ struct EventCountdownView: View { let entry: EventEntry @Environment(\.widgetFamily) var family - private let teal = Color(red: 0.48, green: 0.87, blue: 0.80) + private let teal = WidgetTheme.accent private var percent: Int { Int((entry.progress * 100).rounded()) } @@ -223,7 +223,7 @@ struct EventCountdownView: View { medium } } - .containerBackground(.black, for: .widget) + .containerBackground(WidgetTheme.background, for: .widget) } private var filled: Int { Int(entry.progress * Double(entry.totalDays)) } @@ -274,6 +274,36 @@ struct EventCountdownView: View { } } +// MARK: - Event Countdown — Bars style (medium) + +struct EventBarsView: View { + let entry: EventEntry + + var body: some View { + VStack(alignment: .leading, spacing: 0) { + HStack(alignment: .firstTextBaseline, spacing: 10) { + Text(LocalizedStringKey(entry.eventName)) + .font(.system(size: 22, weight: .heavy)) + .foregroundColor(WidgetTheme.accent) + .lineLimit(1) + Button(intent: CycleCountdownUnitIntent()) { + Text(entry.fineTimeLeft) + .font(.system(size: 16, weight: .semibold)) + .foregroundColor(WidgetTheme.textDim) + .lineLimit(1) + } + .buttonStyle(.plain) + Spacer(minLength: 0) + } + Spacer(minLength: 8) + BarGrid(progress: entry.progress, color: WidgetTheme.accent) + .frame(maxHeight: .infinity) + } + .padding(.horizontal, 4).padding(.vertical, 2) + .containerBackground(WidgetTheme.background, for: .widget) + } +} + // MARK: - Lock Screen Widget (accessoryRectangular) struct LockScreenRectView: View {