From 2a6eeb6686f5c1922da2bce5964c2bf7172951ea Mon Sep 17 00:00:00 2001 From: ivar Date: Mon, 23 Mar 2026 23:47:44 +0100 Subject: docs: add sunrise/sunset widget feature design spec --- .../Assets.xcassets/smallbg.imageset/Contents.json | 21 ++++ .../Assets.xcassets/smallbg.imageset/Group 1.svg | 17 +++ Solsnu.Widget/Views/LargeWidgetView.swift | 51 --------- .../2026-03-23-sunrise-sunset-widget-design.md | 123 +++++++++++++++++++++ 4 files changed, 161 insertions(+), 51 deletions(-) create mode 100644 Solsnu.Widget/Assets.xcassets/smallbg.imageset/Contents.json create mode 100644 Solsnu.Widget/Assets.xcassets/smallbg.imageset/Group 1.svg delete mode 100644 Solsnu.Widget/Views/LargeWidgetView.swift create mode 100644 docs/superpowers/specs/2026-03-23-sunrise-sunset-widget-design.md diff --git a/Solsnu.Widget/Assets.xcassets/smallbg.imageset/Contents.json b/Solsnu.Widget/Assets.xcassets/smallbg.imageset/Contents.json new file mode 100644 index 0000000..0792e35 --- /dev/null +++ b/Solsnu.Widget/Assets.xcassets/smallbg.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "filename" : "Group 1.svg", + "idiom" : "universal", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Solsnu.Widget/Assets.xcassets/smallbg.imageset/Group 1.svg b/Solsnu.Widget/Assets.xcassets/smallbg.imageset/Group 1.svg new file mode 100644 index 0000000..571280a --- /dev/null +++ b/Solsnu.Widget/Assets.xcassets/smallbg.imageset/Group 1.svg @@ -0,0 +1,17 @@ + + + + + + + + + + + + + + + + + diff --git a/Solsnu.Widget/Views/LargeWidgetView.swift b/Solsnu.Widget/Views/LargeWidgetView.swift deleted file mode 100644 index beba39c..0000000 --- a/Solsnu.Widget/Views/LargeWidgetView.swift +++ /dev/null @@ -1,51 +0,0 @@ -import SwiftUI -import WidgetKit - -struct LargeWidgetView: View { - let entry: SolvervEntry - - var body: some View { - VStack(spacing: 12) { - // Top: Emoji - Text(entry.emoji) - .font(.system(size: 80)) - .frame(height: 100) - - // Bottom: Info - VStack(alignment: .leading, spacing: 10) { - // Event name and countdown - VStack(alignment: .leading, spacing: 4) { - Text("Season Event") - .font(.headline) - - Text("\(entry.def.daysUntilNext()) days") - .font(.system(.title2, design: .default).weight(.bold)) - } - - // Progress bar - ProgressView(value: Double(entry.def.progressRatio())) - - Divider() - - // Event info - VStack(alignment: .leading, spacing: 6) { - Text("Upcoming") - .font(.caption) - .foregroundColor(.secondary) - - Text("More events coming soon") - .font(.caption) - } - - Spacer() - } - .padding(.horizontal) - .padding(.vertical, 8) - } - } -} - -#Preview { - let entry = SolvervEntry(def: SolvervDef(utcString: "2026-12-21 20:50:00")) - LargeWidgetView(entry: entry) -} diff --git a/docs/superpowers/specs/2026-03-23-sunrise-sunset-widget-design.md b/docs/superpowers/specs/2026-03-23-sunrise-sunset-widget-design.md new file mode 100644 index 0000000..27f7dbd --- /dev/null +++ b/docs/superpowers/specs/2026-03-23-sunrise-sunset-widget-design.md @@ -0,0 +1,123 @@ +# Sunrise/Sunset Times Widget Feature + +**Date:** 2026-03-23 +**Status:** Design Approved + +## Overview + +Add sunrise and sunset time display to both small and medium widgets. Times are calculated locally using the existing NOAA solar position algorithm, formatted as HH:mm, and updated daily at midnight based on user location. + +## Requirements + +- Display sunrise/sunset times in HH:mm format on widgets +- Calculate times locally using existing `SunTimes` utility +- Use user's current location for calculations +- Update once daily at midnight (aligned with current refresh policy) +- Show nothing if location permission is denied or unavailable +- Apply to both small and medium widget families + +## Architecture + +### Data Model + +Extend `SolvervDef` to include sunrise/sunset times: +``` +SolvervDef +├── date: Date +├── bg: String +├── sunriseTime: Date? (optional, time only) +└── sunsetTime: Date? (optional, time only) +``` + +Extend `SolvervEntry` to propagate the times: +``` +SolvervEntry +├── date: Date +├── def: SolvervDef (contains sunrise/sunset) +``` + +### Timeline Provider Logic + +Update `Provider.getTimeline()`: + +1. Get user's cached location (latitude, longitude) +2. If location unavailable → create entry with nil sunrise/sunset +3. Initialize `SunTimes(latitude, longitude, date: currentDate)` +4. Extract sunrise and sunset times +5. Create entry with times +6. Refresh at next midnight + +### Data Calculation Flow + +``` +Location Permission Check + ↓ (if granted) +Get Cached Coordinates + ↓ +SunTimes Calculator + ├─ Input: latitude, longitude, date + ├─ Output: sunrise Date, sunset Date + └─ Fallback: nil (polar regions, edge cases) + ↓ +Format as HH:mm + ↓ +SolvervDef/Entry + ↓ +Widget Display +``` + +### Error Handling + +| Scenario | Behavior | +|----------|----------| +| Location permission denied | No sunrise/sunset displayed | +| Location unavailable/stale | No sunrise/sunset displayed | +| SunTimes returns nil (polar) | No sunrise/sunset displayed | +| Date parsing fails | Widget shows current time's calculation | + +## Implementation Details + +### Changes to SolvervDef + +Add properties and computed properties for formatted times: +- `sunriseTime: Date?` — raw sunrise time +- `sunsetTime: Date?` — raw sunset time +- `sunriseFormatted: String` — "HH:mm" or empty +- `sunsetFormatted: String` — "HH:mm" or empty + +### Changes to Provider + +Modify `getTimeline()` to: +1. Fetch location from cache (use existing location service) +2. If available, calculate sun times via `SunTimes` +3. Pass times to `SolvervDef` constructor +4. Return timeline refreshing at next midnight + +### Widget View Updates + +**SmallWidgetView:** Add sunrise/sunset display (layout TBD) +**MediumWidgetView:** Add sunrise/sunset display (layout TBD) + +Display format: sunrise time and sunset time as HH:mm when available. + +## Timeline & Refresh + +- **Frequency:** Once per day at midnight +- **Rationale:** Sun times change gradually; daily updates are sufficient +- **Alignment:** Matches existing solstice countdown refresh policy + +## Testing + +- Test with various locations (tropics, temperate, near poles) +- Test without location permission +- Test at date boundaries (midnight refresh) +- Verify formatted times display correctly +- Verify nil times result in no display + +## Future Considerations + +- Visualization of sunrise/sunset (charts, gradients) +- Daylight duration calculation and trend +- Multiple locations support +- Custom location override + -- cgit v1.3