Widgets: bars countdown widget + unified slate/orange theme (KC-15)
- New EventBarsWidget ("Event Countdown (Bars)") using BarGrid + EventBarsView,
with a fine "X hr, Y min left" label; registered in the bundle.
- Re-themed every home-screen widget to the shared WidgetTheme (dark slate
background + brand orange): Day Progress, Tasks Done Today, Event Countdown
(dots), My Tasks. Lock-screen widgets keep .thinMaterial (system-tinted).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -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])
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
Reference in New Issue
Block a user