summaryrefslogtreecommitdiffstats
path: root/docs/superpowers/plans/2026-03-23-sunrise-sunset-widget-implementation.md
diff options
context:
space:
mode:
authorivar <i@oiee.no>2026-05-07 01:24:28 +0200
committerivar <i@oiee.no>2026-05-07 01:24:28 +0200
commit6eb17a18e901e2d7faa219d7e5a79083a5891dc9 (patch)
tree3d0796e1e567864dfdf7c675f7e8a5a40fb51a95 /docs/superpowers/plans/2026-03-23-sunrise-sunset-widget-implementation.md
parent4fb690150b77afced6453e6bdb14cc4cf00d5305 (diff)
downloadsolverv-master.tar.xz
solverv-master.zip
RefactorsHEADmaster
Diffstat (limited to 'docs/superpowers/plans/2026-03-23-sunrise-sunset-widget-implementation.md')
-rw-r--r--docs/superpowers/plans/2026-03-23-sunrise-sunset-widget-implementation.md539
1 files changed, 0 insertions, 539 deletions
diff --git a/docs/superpowers/plans/2026-03-23-sunrise-sunset-widget-implementation.md b/docs/superpowers/plans/2026-03-23-sunrise-sunset-widget-implementation.md
deleted file mode 100644
index 19c36fb..0000000
--- a/docs/superpowers/plans/2026-03-23-sunrise-sunset-widget-implementation.md
+++ /dev/null
@@ -1,539 +0,0 @@
-# Sunrise/Sunset Widget Implementation Plan
-
-> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
-
-**Goal:** Display sunrise and sunset times in HH:mm format on small and medium widgets, calculated locally using the user's location.
-
-**Architecture:** Widget Provider fetches cached user location from AppGroupManager, calculates sunrise/sunset times using the existing SunTimes utility, and passes formatted times to widget views. Gracefully handles unavailable locations by showing no times.
-
-**Tech Stack:**
-- SwiftUI WidgetKit (existing)
-- SunTimes (NOAA algorithm, already available)
-- AppGroupManager (existing shared storage)
-
----
-
-## File Structure
-
-**Files to Modify:**
-- `Solsnu.Widget/Solsnu_Widget.swift` — Add time properties to SolvervDef, update Provider logic
-- `Solsnu.Widget/Views/SmallWidgetView.swift` — Display sunrise/sunset if available
-- `Solsnu.Widget/Views/MediumWidgetView.swift` — Display sunrise/sunset if available
-
-**Files to Create:**
-- None (all infrastructure already exists)
-
----
-
-## Task 1: Add Properties to SolvervDef
-
-**Files:**
-- Modify: `Solsnu.Widget/Solsnu_Widget.swift:72-90`
-
-- [ ] **Step 1: Add sunrise/sunset Date properties to SolvervDef struct**
-
-Add these properties after `bg: String`:
-```swift
-let sunriseTime: Date?
-let sunsetTime: Date?
-```
-
-- [ ] **Step 2: Update existing constructors to accept nil times**
-
-Modify `init(date:Date)`:
-```swift
-init(date: Date, sunriseTime: Date? = nil, sunsetTime: Date? = nil) {
- self.date = date
- self.sunriseTime = sunriseTime
- self.sunsetTime = sunsetTime
- self.bg = "smallbg"
-}
-```
-
-Modify `init(utcString:String)`:
-```swift
-init(utcString: String, sunriseTime: Date? = nil, sunsetTime: Date? = nil) {
- let formatter = DateFormatter()
- formatter.dateFormat = "yyyy-MM-dd HH:mm:ss"
- formatter.timeZone = TimeZone(abbreviation: "UTC")
- let date = formatter.date(from: utcString)!
- self.date = date
- self.sunriseTime = sunriseTime
- self.sunsetTime = sunsetTime
- self.bg = "smallbg"
-}
-```
-
-- [ ] **Step 3: Add computed properties for formatted times**
-
-Add after `bg: String` property declarations:
-```swift
-var sunriseFormatted: String {
- guard let time = sunriseTime else { return "" }
- let formatter = DateFormatter()
- formatter.timeStyle = .short
- formatter.dateStyle = .none
- return formatter.string(from: time)
-}
-
-var sunsetFormatted: String {
- guard let time = sunsetTime else { return "" }
- let formatter = DateFormatter()
- formatter.timeStyle = .short
- formatter.dateStyle = .none
- return formatter.string(from: time)
-}
-```
-
-- [ ] **Step 4: Verify compilation**
-
-Build the widget target:
-```bash
-xcodebuild build -scheme Solsnu_Widget -destination "generic/platform=iOS"
-```
-
-Expected: Build succeeds with no errors
-
-- [ ] **Step 5: Commit**
-
-```bash
-git add Solsnu.Widget/Solsnu_Widget.swift
-git commit -m "feat: add sunrise/sunset properties to SolvervDef"
-```
-
----
-
-## Task 2: Update Provider to Fetch and Calculate Sun Times
-
-**Files:**
-- Modify: `Solsnu.Widget/Solsnu_Widget.swift:11-37` (Provider struct and getTimeline method)
-
-- [ ] **Step 1: Import AppGroupManager in widget**
-
-Add to imports at top of Solsnu_Widget.swift:
-```swift
-import WidgetKit
-import SwiftUI
-// Add this:
-import Combine
-```
-
-Note: Check if you need to add AppGroupManager to the widget target. It may require adding the file to the widget's Build Phases.
-
-- [ ] **Step 2: Rewrite Provider.getTimeline() to fetch location**
-
-Replace the entire `getTimeline` method with:
-```swift
-func getTimeline(in context: Context, completion: @escaping (Timeline<SolvervEntry>) -> ()) {
- var entries: [SolvervEntry] = []
- let currentDate = Date()
-
- // Fetch location from AppGroupManager
- let location = AppGroupManager.shared.getLocation()
-
- var sunriseTime: Date? = nil
- var sunsetTime: Date? = nil
-
- // If location exists and is less than 24 hours old, calculate sun times
- if let location = location {
- let locationFormatter = ISO8601DateFormatter()
- if let locationTimestamp = locationFormatter.date(from: location.timestamp) {
- let hoursSinceLocation = currentDate.timeIntervalSince(locationTimestamp) / 3600.0
-
- if hoursSinceLocation < 24.0 {
- // Calculate sun times using SunTimes utility
- let calculator = SunTimes(
- latitude: location.latitude,
- longitude: location.longitude,
- date: currentDate
- )
- sunriseTime = calculator.sunrise()
- sunsetTime = calculator.sunset()
- }
- }
- }
-
- // Create entry with calculated times
- let entry = SolvervEntry(
- def: SolvervDef(
- date: currentDate,
- sunriseTime: sunriseTime,
- sunsetTime: sunsetTime
- )
- )
- entries.append(entry)
-
- // Refresh at next midnight
- let calendar = Calendar.current
- var components = calendar.dateComponents([.year, .month, .day], from: currentDate)
- components.hour = 0
- components.minute = 0
- components.second = 0
- let todayMidnight = calendar.date(from: components)!
- let nextMidnight = calendar.date(byAdding: .day, value: 1, to: todayMidnight)!
-
- let timeline = Timeline(entries: entries, policy: .after(nextMidnight))
- completion(timeline)
-}
-```
-
-- [ ] **Step 3: Verify Provider needs SunTimes import**
-
-At the top of Solsnu_Widget.swift, verify you have:
-```swift
-import WidgetKit
-import SwiftUI
-```
-
-Note: SunTimes is in the main app target. You may need to either:
-- Move SunTimes to a shared framework, OR
-- Copy/duplicate SunTimes into the widget target, OR
-- Add SunTimes file to widget's Build Phases
-
-Check your project setup. If SunTimes isn't accessible to the widget, you'll need to add it to the widget's Build Phases.
-
-- [ ] **Step 4: Build and verify no errors**
-
-```bash
-xcodebuild build -scheme Solsnu_Widget -destination "generic/platform=iOS"
-```
-
-Expected: Build succeeds
-
-- [ ] **Step 5: Commit**
-
-```bash
-git add Solsnu.Widget/Solsnu_Widget.swift
-git commit -m "feat: add location fetching and sun time calculation to widget provider"
-```
-
----
-
-## Task 3: Update SmallWidgetView to Display Times
-
-**Files:**
-- Modify: `Solsnu.Widget/Views/SmallWidgetView.swift`
-
-- [ ] **Step 1: Understand current SmallWidgetView layout**
-
-Read the file to see how it currently displays the countdown
-
-- [ ] **Step 2: Add sunrise/sunset display**
-
-Update `body` to include times if available:
-
-```swift
-var body: some View {
- ZStack {
- Image(entry.def.bg)
- .resizable()
- .scaledToFill()
-
- VStack(spacing: 8) {
- // Days until next event (existing)
- Text("\(entry.def.daysUntilNext())")
- .font(.system(size: 26, weight: .bold, design: .serif))
- .foregroundStyle(Color(red: 0.152, green: 0.136, blue: 0.056))
- .italic()
-
- // Sunrise/Sunset times (new)
- if !entry.def.sunriseFormatted.isEmpty && !entry.def.sunsetFormatted.isEmpty {
- HStack(spacing: 4) {
- Text("↑ \(entry.def.sunriseFormatted)")
- Text("↓ \(entry.def.sunsetFormatted)")
- }
- .font(.system(size: 11, weight: .regular))
- .foregroundStyle(Color(red: 0.152, green: 0.136, blue: 0.056))
- }
- }
- .position(x: 50, y: 50)
- }
- .containerBackground(for: .widget, alignment: .center) { Color.clear }
-}
-```
-
-- [ ] **Step 3: Build and preview**
-
-Build the widget:
-```bash
-xcodebuild build -scheme Solsnu_Widget -destination "generic/platform=iOS"
-```
-
-Expected: Build succeeds
-
-Check the preview in Xcode to see the new layout with times (if available)
-
-- [ ] **Step 4: Commit**
-
-```bash
-git add Solsnu.Widget/Views/SmallWidgetView.swift
-git commit -m "feat: display sunrise/sunset times in small widget"
-```
-
----
-
-## Task 4: Update MediumWidgetView to Display Times
-
-**Files:**
-- Modify: `Solsnu.Widget/Views/MediumWidgetView.swift`
-
-- [ ] **Step 1: Read current MediumWidgetView**
-
-Understand its current structure
-
-- [ ] **Step 2: Add sunrise/sunset display**
-
-Add the times to the medium widget view. Example approach:
-
-```swift
-var body: some View {
- ZStack {
- Image(entry.def.bg)
- .resizable()
- .scaledToFill()
-
- VStack(alignment: .leading, spacing: 12) {
- // Existing countdown content
- Text("\(entry.def.daysUntilNext())")
- .font(.system(size: 32, weight: .bold, design: .serif))
- .foregroundStyle(Color(red: 0.152, green: 0.136, blue: 0.056))
-
- // Sunrise/Sunset times
- if !entry.def.sunriseFormatted.isEmpty && !entry.def.sunsetFormatted.isEmpty {
- HStack {
- Text("Sunrise: \(entry.def.sunriseFormatted)")
- Spacer()
- Text("Sunset: \(entry.def.sunsetFormatted)")
- }
- .font(.system(size: 12, weight: .regular))
- .foregroundStyle(Color(red: 0.152, green: 0.136, blue: 0.056))
- }
- }
- .padding()
- }
- .containerBackground(for: .widget, alignment: .topLeading) { Color.clear }
-}
-```
-
-(Adjust layout to match your design preferences)
-
-- [ ] **Step 3: Build and preview**
-
-```bash
-xcodebuild build -scheme Solsnu_Widget -destination "generic/platform=iOS"
-```
-
-Expected: Build succeeds
-
-- [ ] **Step 4: Commit**
-
-```bash
-git add Solsnu.Widget/Views/MediumWidgetView.swift
-git commit -m "feat: display sunrise/sunset times in medium widget"
-```
-
----
-
-## Task 5: Handle SunTimes Availability in Widget
-
-**Files:**
-- Modify: `Solsnu.Widget/Solsnu_Widget.swift` (if SunTimes is not available)
-
-**Note:** This task only applies if SunTimes cannot be imported directly into the widget. If you got build errors in Task 2 Step 3, you need to copy SunTimes into the widget target.
-
-- [ ] **Step 1: Check if SunTimes is available in widget**
-
-Try to build:
-```bash
-xcodebuild build -scheme Solsnu_Widget -destination "generic/platform=iOS" 2>&1 | grep -i suntimes
-```
-
-If no errors: Skip to Step 4
-If error: Proceed to Step 2
-
-- [ ] **Step 2: Copy SunTimes to widget target (if needed)**
-
-If build failed because SunTimes isn't accessible:
-
-Copy the file:
-```bash
-cp Solverv/Utilities/SunTimes.swift Solsnu.Widget/Utilities/SunTimes.swift
-```
-
-Create the Utilities folder in widget if needed:
-```bash
-mkdir -p Solsnu.Widget/Utilities
-```
-
-- [ ] **Step 3: Add SunTimes to widget target in Xcode**
-
-In Xcode:
-1. Open the project
-2. Select `Solsnu.Widget` target
-3. Go to Build Phases → Compile Sources
-4. Add `Solsnu.Widget/Utilities/SunTimes.swift`
-
-Or via command line:
-```bash
-# This is typically done in Xcode UI
-```
-
-Build again:
-```bash
-xcodebuild build -scheme Solsnu_Widget -destination "generic/platform=iOS"
-```
-
-Expected: Build succeeds
-
-- [ ] **Step 4: Commit (if changes made)**
-
-```bash
-git add Solsnu.Widget/Utilities/SunTimes.swift Solsnu.Widget/Solsnu_Widget.swift
-git commit -m "feat: add SunTimes calculator to widget target for sun time calculations"
-```
-
-If no changes needed, skip this commit.
-
----
-
-## Task 6: Integration Testing
-
-**Files:**
-- Test: Solsnu_Widget preview in Xcode
-
-- [ ] **Step 1: Test with valid location**
-
-In Solsnu_Widget.swift preview, modify the preview to include sample sun times:
-
-```swift
-#Preview(as: .systemSmall) {
- Solsnu_Widget()
-} timeline: {
- let formatter = DateFormatter()
- formatter.timeStyle = .short
- formatter.dateStyle = .none
-
- // Sample entry with sun times
- let springDate = Calendar.current.date(from: DateComponents(year: 2026, month: 3, day: 20))!
- let sunriseTime = Calendar.current.date(bySettingHour: 7, minute: 30, second: 0, of: springDate)!
- let sunsetTime = Calendar.current.date(bySettingHour: 19, minute: 45, second: 0, of: springDate)!
-
- SolvervEntry(
- def: SolvervDef(
- date: springDate,
- sunriseTime: sunriseTime,
- sunsetTime: sunsetTime
- )
- )
-}
-```
-
-Build the preview:
-```bash
-xcodebuild build -scheme Solsnu_Widget -destination "generic/platform=iOS"
-```
-
-- [ ] **Step 2: Verify widget displays times correctly**
-
-In Xcode, view the canvas/preview and confirm:
-- Sunrise time displays as HH:mm format
-- Sunset time displays as HH:mm format
-- Both times appear on the widget (layout may vary)
-
-- [ ] **Step 3: Test with nil times (no location)**
-
-Update preview to test with nil times:
-
-```swift
-SolvervEntry(
- def: SolvervDef(
- date: springDate,
- sunriseTime: nil,
- sunsetTime: nil
- )
-)
-```
-
-Build and verify:
-- Widget displays without crashing
-- No times are shown
-- Layout remains clean (no empty space for times)
-
-- [ ] **Step 4: Test different locations**
-
-Manually verify with sample calculations:
-- Oslo (59.9°N, 10.7°E): Should show reasonable spring times
-- Equator (0°, 0°): Should show ~6am and ~6pm
-- Near poles: May show nil (handled gracefully)
-
-- [ ] **Step 5: Commit testing changes (if any)**
-
-```bash
-git add Solsnu.Widget/Solsnu_Widget.swift
-git commit -m "test: add preview entries with sunrise/sunset times"
-```
-
----
-
-## Task 7: Final Integration & Verification
-
-**Files:**
-- Verify: All modified files
-
-- [ ] **Step 1: Run full build**
-
-```bash
-xcodebuild build -scheme Solverv -destination "generic/platform=iOS"
-xcodebuild build -scheme Solsnu_Widget -destination "generic/platform=iOS"
-```
-
-Expected: Both schemes build successfully with no warnings
-
-- [ ] **Step 2: Check widget refresh behavior**
-
-The widget should:
-- Refresh at midnight (existing policy maintained)
-- Show sun times if location is available and fresh (< 24 hours)
-- Show nothing for times if location is unavailable or stale
-
-This is verified automatically via the Provider logic.
-
-- [ ] **Step 3: Verify no regressions**
-
-Check that existing functionality still works:
-- Countdown still displays
-- Solstice events still calculate correctly
-- Widget background image still loads
-- Other widget configurations still work
-
-- [ ] **Step 4: Final commit and review**
-
-Review all changes:
-```bash
-git log --oneline -7
-```
-
-Should see commits for:
-1. Add sunrise/sunset properties to SolvervDef
-2. Add location fetching and sun time calculation to widget provider
-3. Display sunrise/sunset times in small widget
-4. Display sunrise/sunset times in medium widget
-5. (Optional) Add SunTimes to widget target
-6. (Optional) Add preview entries
-
-All changes complete ✅
-
----
-
-## Notes
-
-- **AppGroupManager Dependency:** The widget must be able to access AppGroupManager to read cached location. Ensure both the main app and widget targets have access to this file or share it via a framework.
-
-- **SunTimes Availability:** The SunTimes calculator must be available to the widget. Either ensure it's in a shared framework or copy it to the widget target.
-
-- **Timezone Handling:** SunTimes already handles timezone conversion internally. Times are in device local time.
-
-- **Stale Location:** If location is older than 24 hours, no sun times are displayed. This aligns with the widget's daily refresh policy.
-
-- **Privacy:** Widget cannot request location permission—must rely on main app providing location via AppGroupManager.
-