Add weather feature with WeatherKit integration for mood entries

Fetch and display weather data (temp, condition, hi/lo, humidity) when
users log a mood. Weather is stored as JSON on MoodEntryModel and shown
as a card in EntryDetailView. Premium-gated with location permission
prompt. Includes BGTask retry for failed fetches and full analytics.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Trey t
2026-03-11 00:16:26 -05:00
parent a1340b4deb
commit 31fb2a7fe2
15 changed files with 557 additions and 3 deletions

View File

@@ -0,0 +1,71 @@
//
// LocationManager.swift
// Reflect
//
// CoreLocation wrapper with async/await for one-shot location requests.
//
import CoreLocation
import os.log
@MainActor
final class LocationManager: NSObject {
static let shared = LocationManager()
private static let logger = Logger(
subsystem: Bundle.main.bundleIdentifier ?? "com.88oakapps.reflect",
category: "LocationManager"
)
private let manager = CLLocationManager()
private var locationContinuation: CheckedContinuation<CLLocation, Error>?
var authorizationStatus: CLAuthorizationStatus {
manager.authorizationStatus
}
private override init() {
super.init()
manager.delegate = self
manager.desiredAccuracy = kCLLocationAccuracyKilometer
}
func requestAuthorization() {
manager.requestWhenInUseAuthorization()
}
var currentLocation: CLLocation {
get async throws {
// Return last known location if recent enough (within 10 minutes)
if let last = manager.location,
abs(last.timestamp.timeIntervalSinceNow) < 600 {
return last
}
return try await withCheckedThrowingContinuation { continuation in
self.locationContinuation = continuation
self.manager.requestLocation()
}
}
}
}
// MARK: - CLLocationManagerDelegate
extension LocationManager: CLLocationManagerDelegate {
nonisolated func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
Task { @MainActor in
guard let location = locations.first else { return }
locationContinuation?.resume(returning: location)
locationContinuation = nil
}
}
nonisolated func locationManager(_ manager: CLLocationManager, didFailWithError error: Error) {
Task { @MainActor in
Self.logger.error("Location request failed: \(error.localizedDescription)")
locationContinuation?.resume(throwing: error)
locationContinuation = nil
}
}
}