diff options
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.md | 539 |
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. - |
