Files
onyx/Packages/OnyxKit/Sources/WeatherProvider/WeatherModels.swift
Guido Schmit fc92d226b4 Phase 3d: Wetter-Widget (WeatherKit-Entitlement noch offen)
Eigene WeatherCondition statt WeatherKits Typ: die Widget-Ebene soll nicht an
ein Framework gebunden sein, das eine kostenpflichtige Mitgliedschaft und eine
Entitlement voraussetzt. Fällt WeatherKit weg, wird nur die Abbildung getauscht.

Der Zwischenspeicher ist Teil der Funktion, kein Feinschliff: WeatherKit rechnet
nach Abrufen ab, und ein Widget, das bei jedem Öffnen des Panels neu lädt,
verbrennt das Kontingent für Daten, die sich stündlich ändern. 15 Minuten
Gültigkeit, und der Schlüssel führt den Ort mit — sonst zeigt das Widget nach
einem Ortswechsel weiter das alte Wetter, überzeugend, weil Zahlen dastehen.

Ein abgelaufener Stand bleibt als "letzter bekannter" erhalten. Bei einem
Netzfehler ist ein alter Messwert mit sichtbarem Zeitstempel ehrlicher und
nützlicher als ein leeres Feld; er wird abgeblendet und mit Warnzeichen gezeigt.

Apples Namensnennung steht im Datenmodell, nicht in einer Notiz: sie ist
Bedingung der Nutzung von WeatherKit und wird sonst beim Bauen der Oberfläche
übersehen. In der 2x1-Kachel als klickbarer Link, in der 1x1 im Tooltip.

Temperaturen ohne Nachkommastellen — die täuschen eine Genauigkeit vor, die
die Vorhersage nicht hat.

NICHT aktiviert: com.apple.developer.weatherkit. Die Entitlement erzwingt ein
echtes Provisioning-Profil, und die App-ID lässt sich nicht registrieren
("cannot be registered to your development team because it is not available").
Bis das im Portal geklärt ist, bleibt sie auskommentiert — die App baut und
läuft, das Widget meldet ehrlich "Wetter nicht abrufbar".

106 Tests grün.
2026-08-10 20:33:32 +02:00

126 lines
4.1 KiB
Swift

import Foundation
/// Wetterlage, unabhängig von der Quelle.
///
/// Eine eigene Aufzählung statt WeatherKits Typ: die Widget-Ebene soll nicht
/// gegen ein Framework gebunden sein, das eine kostenpflichtige Mitgliedschaft
/// und eine Entitlement voraussetzt. Fällt WeatherKit einmal weg, wird nur die
/// Abbildung ausgetauscht.
public enum WeatherCondition: String, Equatable, Sendable, CaseIterable {
case clear
case mostlyClear
case partlyCloudy
case cloudy
case fog
case drizzle
case rain
case heavyRain
case sleet
case snow
case heavySnow
case hail
case thunderstorm
case windy
case hot
case frigid
case unknown
}
/// Der Ort, für den Wetter angezeigt wird.
public enum WeatherPlace: Equatable, Sendable, Codable {
/// Aktueller Standort über CoreLocation.
case current
/// Fester Ort, in den Widget-Einstellungen gewählt.
case fixed(name: String, latitude: Double, longitude: Double)
public var isCurrent: Bool { self == .current }
}
public struct HourlyPoint: Equatable, Sendable, Identifiable {
public let date: Date
public let temperature: Measurement<UnitTemperature>
public let condition: WeatherCondition
public let precipitationChance: Double
public var id: Date { date }
public init(date: Date, temperature: Measurement<UnitTemperature>,
condition: WeatherCondition, precipitationChance: Double) {
self.date = date
self.temperature = temperature
self.condition = condition
self.precipitationChance = precipitationChance
}
}
/// Was Apple bei Nutzung von WeatherKit sichtbar verlangt.
///
/// Das ist keine Kür: die Nennung samt Link auf die Rechtshinweise ist
/// Bedingung der Nutzung. Deshalb steht sie im Datenmodell und nicht in einer
/// Notiz, die man beim Bauen der Oberfläche übersieht.
public struct WeatherAttribution: Equatable, Sendable {
public let name: String
public let legalPageURL: URL
public let logoURL: URL?
public init(name: String, legalPageURL: URL, logoURL: URL?) {
self.name = name
self.legalPageURL = legalPageURL
self.logoURL = logoURL
}
}
public struct WeatherSnapshot: Equatable, Sendable {
public let placeName: String
public let temperature: Measurement<UnitTemperature>
public let apparentTemperature: Measurement<UnitTemperature>
public let condition: WeatherCondition
public let isDaylight: Bool
public let humidity: Double
public let windSpeed: Measurement<UnitSpeed>
public let high: Measurement<UnitTemperature>?
public let low: Measurement<UnitTemperature>?
public let hourly: [HourlyPoint]
public let asOf: Date
public let attribution: WeatherAttribution?
public init(placeName: String,
temperature: Measurement<UnitTemperature>,
apparentTemperature: Measurement<UnitTemperature>,
condition: WeatherCondition,
isDaylight: Bool,
humidity: Double,
windSpeed: Measurement<UnitSpeed>,
high: Measurement<UnitTemperature>?,
low: Measurement<UnitTemperature>?,
hourly: [HourlyPoint],
asOf: Date,
attribution: WeatherAttribution?) {
self.placeName = placeName
self.temperature = temperature
self.apparentTemperature = apparentTemperature
self.condition = condition
self.isDaylight = isDaylight
self.humidity = humidity
self.windSpeed = windSpeed
self.high = high
self.low = low
self.hourly = hourly
self.asOf = asOf
self.attribution = attribution
}
}
/// Wie beim Kalender: jeder Fehlerfall bekommt eine eigene Antwort in der
/// Oberfläche. Keine Daten" ist keine.
public enum WeatherState: Equatable, Sendable {
case ready(WeatherSnapshot)
/// Noch nie geladen.
case idle
case loading
/// Ortungsberechtigung fehlt betrifft nur `.current`.
case locationDenied
/// Kein Netz oder Dienst nicht erreichbar. Der letzte Stand bleibt sichtbar.
case unavailable(lastKnown: WeatherSnapshot?, reason: String)
}