# Solstice Countdown Widget & App Design **Date:** 2026-03-23 **Project:** Solverv (Solstice Countdown) **Status:** Design Approved --- ## Overview Build an iOS app that displays a countdown to the next solstice or equinox, with season-specific imagery and detailed information. The app includes: - **Widget Extension:** Compact countdown widget in small, medium, and large sizes - **Main App:** Info screen showing expanded details including sunrise/sunset times and upcoming events --- ## Requirements ### Functional Requirements 1. Track all four annual solstices/equinoxes (Spring, Summer, Autumn, Winter) 2. Display countdown to next event in **days only** 3. Show season-specific images that change based on upcoming event 4. Calculate and display sunrise/sunset times for user's current location 5. Show expanded event details in main app (exact times, season descriptions, upcoming event previews) 6. Support multiple widget sizes (small, medium, large) ### Non-Functional Requirements - All calculations happen locally (no external APIs) - Works offline after initial setup - Respects location privacy with explicit permission - Single location permission prompt (shared via AppGroup) - Data synced between app and widget via AppGroup container --- ## Architecture ### Data Models **SolsticeEvent** ``` - name: String (e.g., "Summer Solstice", "Spring Equinox") - date: Date (UTC) - season: Season (enum: spring, summer, autumn, winter) - seasonDescription: String (brief description of the season) ``` **SolsticeData** ``` - events: [SolsticeEvent] (hardcoded data, 2025-2030) - nextEvent() -> SolsticeEvent (returns next upcoming event) - allUpcoming(count: Int) -> [SolsticeEvent] (returns next N events) - daysUntil(_ event: SolsticeEvent) -> Int - progressToEvent(_ event: SolsticeEvent) -> (elapsed: Int, total: Int) ``` **SunTimes** ``` - latitude: Double - longitude: Double - date: Date - sunrise() -> Date - sunset() -> Date ``` ### Data Sharing **AppGroup Container:** Store user location and cached sunrise/sunset times using the container ID `group.com.ivarlovlie.solverv` **AppGroup Data Schema:** ```json { "userLocation": { "latitude": Double, "longitude": Double, "timestamp": Date (when location was last updated), "isDefaultLocation": Boolean (true if using Greenwich fallback) }, "sunTimes": { "date": Date (which day these times are for), "sunrise": Date (full DateTime in local timezone), "sunset": Date (full DateTime in local timezone), "timestamp": Date (when this was calculated) } } ``` - **Widget Timeline:** Updates at midnight (local time) using `timelineReloadPolicy: .after(nextMidnight)` - Both app and widget read from the same AppGroup container for consistency - Cache invalidation: Sunrise/sunset cache is recalculated if stored date differs from today or location changes --- ## Widget Specification ### Small Widget (169×169) - **Layout:** Vertical stack - Seasonal image (fills most of space) - Event name (small caption) - Countdown in days (large, bold text) - **Refresh:** `timelineReloadPolicy: .after(nextMidnight)` — refreshes at midnight local time to update countdown - **Purpose:** Quick glance at how many days remain ### Medium Widget (364×169) - **Layout:** Horizontal split - Left: Seasonal image (square) - Right: Vertical stack with event name, countdown (large), progress bar - **Refresh:** `timelineReloadPolicy: .after(nextMidnight)` — refreshes at midnight local time to update countdown - **Purpose:** Balance of visual and numeric information ### Large Widget (364×364) - **Layout:** Vertical stack - Top half: Seasonal image - Bottom half: Event name, countdown, progress bar, preview of next 3 upcoming events (mini list) - **Refresh:** `timelineReloadPolicy: .after(nextMidnight)` — refreshes at midnight local time to update countdown - **Purpose:** Comprehensive view with upcoming events preview ### Images - One image per season (spring, summer, autumn, winter) - Sourced from Assets.xcassets - Same image shown for all events in that season **Image Specifications:** - Aspect ratio: 1:1 (square) - Resolutions: 1x (1024×1024), 2x (2048×2048), 3x (3072×3072) - Variants: One version each for light mode and dark mode - Safe area: Ensure important visual content avoids outer 20-point margin - Format: PNG with alpha channel (for light/dark transparency support) --- ## Main App Info Screen ### Top Section - Seasonal image (landscape orientation friendly) - Next event name (large) - Countdown in days (very large, prominent) - Progress bar showing days elapsed since the previous solstice/equinox divided by total days in that cycle - Example: If Winter Solstice (Dec 21) is passed and Summer Solstice (Jun 21) is upcoming, on Jan 21 the progress is 31/183 days elapsed ### Middle Section - **Today's Sun Times** - Sunrise time - Sunset time - Calculated from device location - **Season Info** - Season name - Brief description (e.g., "Spring Equinox — Day and night are approximately equal length") ### Bottom Section - **Upcoming Events List** (scrollable) - Shows next 8-12 events - Each row displays: - Event name (e.g., "Spring Equinox") - Date/Time in local timezone, 12-hour format with AM/PM (e.g., "Mar 20, 2026 2:46 PM") - Days remaining as integer (e.g., "45 days") - Season color indicator: 12pt circle matching the season's primary color (spring: green, summer: yellow, autumn: orange, winter: blue) ### Navigation - Tab bar or simple navigation to this screen - Refresh button to manually update sunrise/sunset (in case location changed) --- ## Time Zone & Location Handling ### Location - Request permission on first app launch - Store latitude/longitude in AppGroup container - Fall back to Greenwich/UTC (0°, 0°) if permission denied - User can manually update location in app settings ### Time Zones - All solstice times stored in UTC (as they are now) - Convert to user's local timezone for display - Sunrise/sunset calculated for user's timezone and location ### Sunrise/Sunset Algorithm - Implement a simplified solar position algorithm based on the NOAA Sunrise/Sunset and Solar Equinoxes/Solstices algorithm - No external APIs or third-party dependencies - Algorithm inputs: latitude, longitude, date - Algorithm output: sunrise time, sunset time (in local timezone) - Cache results in AppGroup container to avoid recalculation on each app launch --- ## Error Handling & Edge Cases ### Location Permission - App requests permission on first launch - If denied, use default location (Greenwich) and notify user - User can grant permission later in system Settings ### Time Zone Edge Cases - Solstice at midnight: Display correctly in both UTC and local time - User crosses timezone: Times update automatically on app launch - Widget timezone: Uses device timezone (set by system) ### Data Integrity - Solstice dates are hardcoded and immutable - Sunrise/sunset cached but recalculated daily - Widget syncs with app via AppGroup on launch --- ## Testing Strategy ### Widget Testing - Preview all three widget sizes with mock solstice data - Verify countdown updates correctly across timezone boundaries - Test image display in different iOS versions (iOS 17 fallback) ### App Testing - Verify sunrise/sunset calculations against known values - Test location permission flows (allowed, denied, not yet asked) - Test data sync between app and widget - Verify time zone conversions for various user locations ### Integration Testing - Widget refreshes daily and displays current countdown - App and widget show consistent "next event" data - Location changes update sunrise/sunset in real time --- ## Implementation Notes ### Existing Code - `SolvervDef` already contains solstice dates (2025-2030) - Widget structure (`Solsnu_Widget.swift`) is scaffolded - Main app has basic SwiftUI structure ready for info screen ### New Components to Build - `SunTimes` calculation (sunrise/sunset) - `SolsticeEvent` model - Widget layout variants (small, medium, large) - Info screen UI - AppGroup data sharing ### Dependencies - None (all calculations are built-in or custom) - WidgetKit (already available) - SwiftUI (already used) --- ## Additional Clarifications ### Solstice Date Coverage - Hardcoded data spans 2025–2030 - After 2030, app continues functioning but upcoming events won't display beyond December 2030 - Plan for data expansion before 2030: extend to 2040+ in an app update ### Season Assignment - The displayed season for an event is always the season being celebrated - Example: Spring Equinox always displays spring imagery (green), even if shown during late winter on the calendar ### Offline Functionality - **First Launch:** Location permission is requested. If granted, app calculates and caches sunrise/sunset. If denied, app uses Greenwich (0°, 0°) and displays all features with default location times. - **Subsequent Use:** App and widget work entirely offline; all solstice dates and countdown calculations are local - **Sunrise/Sunset:** Cached and recalculated daily or when location changes. If unavailable (permission denied), widget displays countdown only --- ## Success Criteria ✅ Widget displays countdown in days ✅ Seasonal image changes based on next event ✅ All four solstices/equinoxes tracked ✅ Sunrise/sunset times calculated from location ✅ Info screen shows all requested details ✅ App and widget data stay in sync ✅ Works offline after initial setup ✅ All three widget sizes render correctly