mirror of
https://github.com/FBukovina/Atoll.git
synced 2026-10-27 15:14:33 +00:00
Merge pull request #192 from Ebullioscopic/feature/lock-screen
Add Sunrise widget and update the icons for night time
This commit is contained in:
commit
04426d8af7
5 changed files with 138 additions and 9 deletions
|
|
@ -18478,6 +18478,10 @@
|
||||||
},
|
},
|
||||||
"Style" : {
|
"Style" : {
|
||||||
|
|
||||||
|
},
|
||||||
|
"Sunrise at %@" : {
|
||||||
|
"comment" : "A view representing the sunrise time, displayed in a horizontal stack with an icon and the time itself.",
|
||||||
|
"isCommentAutoGenerated" : true
|
||||||
},
|
},
|
||||||
"Support Us" : {
|
"Support Us" : {
|
||||||
"extractionState" : "stale",
|
"extractionState" : "stale",
|
||||||
|
|
|
||||||
|
|
@ -12,6 +12,13 @@ struct LockScreenWeatherWidget: View {
|
||||||
private let inlinePrimaryFont = Font.system(size: 22, weight: .semibold, design: .rounded)
|
private let inlinePrimaryFont = Font.system(size: 22, weight: .semibold, design: .rounded)
|
||||||
private let inlineSecondaryFont = Font.system(size: 13, weight: .medium, design: .rounded)
|
private let inlineSecondaryFont = Font.system(size: 13, weight: .medium, design: .rounded)
|
||||||
private let secondaryLabelColor = Color.white.opacity(0.7)
|
private let secondaryLabelColor = Color.white.opacity(0.7)
|
||||||
|
private static let sunriseFormatter: DateFormatter = {
|
||||||
|
let formatter = DateFormatter()
|
||||||
|
formatter.dateStyle = .none
|
||||||
|
formatter.timeStyle = .short
|
||||||
|
formatter.locale = .current
|
||||||
|
return formatter
|
||||||
|
}()
|
||||||
|
|
||||||
private var isInline: Bool { snapshot.widgetStyle == .inline }
|
private var isInline: Bool { snapshot.widgetStyle == .inline }
|
||||||
private var stackAlignment: VerticalAlignment { isInline ? .firstTextBaseline : .top }
|
private var stackAlignment: VerticalAlignment { isInline ? .firstTextBaseline : .top }
|
||||||
|
|
@ -62,12 +69,21 @@ struct LockScreenWeatherWidget: View {
|
||||||
|
|
||||||
weatherSegment
|
weatherSegment
|
||||||
|
|
||||||
|
if snapshot.showsSunrise, let sunriseText = sunriseTimeText {
|
||||||
|
sunriseSegment(text: sunriseText)
|
||||||
|
}
|
||||||
|
|
||||||
if shouldShowLocation {
|
if shouldShowLocation {
|
||||||
locationSegment
|
locationSegment
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private var sunriseTimeText: String? {
|
||||||
|
guard let sunrise = snapshot.sunCycle?.sunrise else { return nil }
|
||||||
|
return Self.sunriseFormatter.string(from: sunrise)
|
||||||
|
}
|
||||||
|
|
||||||
@ViewBuilder
|
@ViewBuilder
|
||||||
private var weatherSegment: some View {
|
private var weatherSegment: some View {
|
||||||
switch snapshot.widgetStyle {
|
switch snapshot.widgetStyle {
|
||||||
|
|
@ -346,6 +362,20 @@ struct LockScreenWeatherWidget: View {
|
||||||
.layoutPriority(0.7)
|
.layoutPriority(0.7)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private func sunriseSegment(text: String) -> some View {
|
||||||
|
HStack(alignment: .firstTextBaseline, spacing: 4) {
|
||||||
|
Image(systemName: "sunrise.fill")
|
||||||
|
.font(.system(size: 20, weight: .semibold))
|
||||||
|
.symbolRenderingMode(.hierarchical)
|
||||||
|
Text(text)
|
||||||
|
.font(inlinePrimaryFont)
|
||||||
|
.lineLimit(1)
|
||||||
|
.minimumScaleFactor(0.85)
|
||||||
|
}
|
||||||
|
.layoutPriority(0.8)
|
||||||
|
.accessibilityLabel("Sunrise at \(text)")
|
||||||
|
}
|
||||||
|
|
||||||
private var shouldShowLocation: Bool {
|
private var shouldShowLocation: Bool {
|
||||||
snapshot.showsLocation && (snapshot.locationName?.isEmpty == false)
|
snapshot.showsLocation && (snapshot.locationName?.isEmpty == false)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -2974,6 +2974,7 @@ struct LockScreenSettings: View {
|
||||||
@Default(.lockScreenWeatherShowsCharging) private var lockScreenWeatherShowsCharging
|
@Default(.lockScreenWeatherShowsCharging) private var lockScreenWeatherShowsCharging
|
||||||
@Default(.lockScreenWeatherShowsBatteryGauge) private var lockScreenWeatherShowsBatteryGauge
|
@Default(.lockScreenWeatherShowsBatteryGauge) private var lockScreenWeatherShowsBatteryGauge
|
||||||
@Default(.lockScreenWeatherShowsAQI) private var lockScreenWeatherShowsAQI
|
@Default(.lockScreenWeatherShowsAQI) private var lockScreenWeatherShowsAQI
|
||||||
|
@Default(.lockScreenWeatherShowsSunrise) private var lockScreenWeatherShowsSunrise
|
||||||
@Default(.lockScreenWeatherAQIScale) private var lockScreenWeatherAQIScale
|
@Default(.lockScreenWeatherAQIScale) private var lockScreenWeatherAQIScale
|
||||||
|
|
||||||
private func highlightID(_ title: String) -> String {
|
private func highlightID(_ title: String) -> String {
|
||||||
|
|
@ -3089,6 +3090,10 @@ struct LockScreenSettings: View {
|
||||||
.settingsHighlight(id: highlightID("Show charging percentage"))
|
.settingsHighlight(id: highlightID("Show charging percentage"))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Defaults.Toggle("Show sunrise time", key: .lockScreenWeatherShowsSunrise)
|
||||||
|
.disabled(lockScreenWeatherWidgetStyle != .inline)
|
||||||
|
.settingsHighlight(id: highlightID("Show sunrise time"))
|
||||||
|
|
||||||
Defaults.Toggle("Show battery indicator", key: .lockScreenWeatherShowsBatteryGauge)
|
Defaults.Toggle("Show battery indicator", key: .lockScreenWeatherShowsBatteryGauge)
|
||||||
.settingsHighlight(id: highlightID("Show battery indicator"))
|
.settingsHighlight(id: highlightID("Show battery indicator"))
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -107,6 +107,9 @@ final class LockScreenWeatherManager: ObservableObject {
|
||||||
let showsBatteryGauge = Defaults[.lockScreenWeatherShowsBatteryGauge]
|
let showsBatteryGauge = Defaults[.lockScreenWeatherShowsBatteryGauge]
|
||||||
let widgetStyle = Defaults[.lockScreenWeatherWidgetStyle]
|
let widgetStyle = Defaults[.lockScreenWeatherWidgetStyle]
|
||||||
let batteryInfo = showsBatteryGauge ? makeBatteryGaugeInfo(isCharging: chargingInfo != nil, widgetStyle: widgetStyle) : nil
|
let batteryInfo = showsBatteryGauge ? makeBatteryGaugeInfo(isCharging: chargingInfo != nil, widgetStyle: widgetStyle) : nil
|
||||||
|
let showsSunriseSetting = Defaults[.lockScreenWeatherShowsSunrise]
|
||||||
|
let sunCycle = snapshot?.sunCycle
|
||||||
|
let shouldShowSunrise = showsSunriseSetting && widgetStyle == .inline && sunCycle?.sunrise != nil
|
||||||
let fallback = LockScreenWeatherSnapshot(
|
let fallback = LockScreenWeatherSnapshot(
|
||||||
temperatureText: snapshot?.temperatureText ?? "--",
|
temperatureText: snapshot?.temperatureText ?? "--",
|
||||||
symbolName: snapshot?.symbolName ?? "cloud.fill",
|
symbolName: snapshot?.symbolName ?? "cloud.fill",
|
||||||
|
|
@ -120,7 +123,9 @@ final class LockScreenWeatherManager: ObservableObject {
|
||||||
widgetStyle: widgetStyle,
|
widgetStyle: widgetStyle,
|
||||||
showsChargingPercentage: Defaults[.lockScreenWeatherShowsChargingPercentage],
|
showsChargingPercentage: Defaults[.lockScreenWeatherShowsChargingPercentage],
|
||||||
temperatureInfo: snapshot?.temperatureInfo,
|
temperatureInfo: snapshot?.temperatureInfo,
|
||||||
usesGaugeTint: Defaults[.lockScreenWeatherUsesGaugeTint]
|
usesGaugeTint: Defaults[.lockScreenWeatherUsesGaugeTint],
|
||||||
|
sunCycle: sunCycle,
|
||||||
|
showsSunrise: shouldShowSunrise
|
||||||
)
|
)
|
||||||
|
|
||||||
self.snapshot = fallback
|
self.snapshot = fallback
|
||||||
|
|
@ -192,6 +197,8 @@ final class LockScreenWeatherManager: ObservableObject {
|
||||||
.map { _ in () }.eraseToAnyPublisher(),
|
.map { _ in () }.eraseToAnyPublisher(),
|
||||||
Defaults.publisher(.lockScreenWeatherBatteryUsesLaptopSymbol, options: [])
|
Defaults.publisher(.lockScreenWeatherBatteryUsesLaptopSymbol, options: [])
|
||||||
.map { _ in () }.eraseToAnyPublisher(),
|
.map { _ in () }.eraseToAnyPublisher(),
|
||||||
|
Defaults.publisher(.lockScreenWeatherShowsSunrise, options: [])
|
||||||
|
.map { _ in () }.eraseToAnyPublisher(),
|
||||||
Defaults.publisher(.lockScreenWeatherWidgetStyle, options: [])
|
Defaults.publisher(.lockScreenWeatherWidgetStyle, options: [])
|
||||||
.map { _ in () }.eraseToAnyPublisher(),
|
.map { _ in () }.eraseToAnyPublisher(),
|
||||||
Defaults.publisher(.lockScreenWeatherTemperatureUnit, options: [])
|
Defaults.publisher(.lockScreenWeatherTemperatureUnit, options: [])
|
||||||
|
|
@ -294,6 +301,8 @@ final class LockScreenWeatherManager: ObservableObject {
|
||||||
let airQualityInfo = (Defaults[.lockScreenWeatherShowsAQI] && providerSource.supportsAirQuality) ? payload.airQuality : nil
|
let airQualityInfo = (Defaults[.lockScreenWeatherShowsAQI] && providerSource.supportsAirQuality) ? payload.airQuality : nil
|
||||||
let batteryInfo = Defaults[.lockScreenWeatherShowsBatteryGauge] ? makeBatteryGaugeInfo(isCharging: chargingInfo != nil, widgetStyle: widgetStyle) : nil
|
let batteryInfo = Defaults[.lockScreenWeatherShowsBatteryGauge] ? makeBatteryGaugeInfo(isCharging: chargingInfo != nil, widgetStyle: widgetStyle) : nil
|
||||||
let usesGaugeTint = Defaults[.lockScreenWeatherUsesGaugeTint]
|
let usesGaugeTint = Defaults[.lockScreenWeatherUsesGaugeTint]
|
||||||
|
let showsSunriseSetting = Defaults[.lockScreenWeatherShowsSunrise]
|
||||||
|
let shouldShowSunrise = showsSunriseSetting && widgetStyle == .inline && payload.sunCycle?.sunrise != nil
|
||||||
|
|
||||||
return LockScreenWeatherSnapshot(
|
return LockScreenWeatherSnapshot(
|
||||||
temperatureText: payload.temperatureText,
|
temperatureText: payload.temperatureText,
|
||||||
|
|
@ -308,7 +317,9 @@ final class LockScreenWeatherManager: ObservableObject {
|
||||||
widgetStyle: widgetStyle,
|
widgetStyle: widgetStyle,
|
||||||
showsChargingPercentage: showsChargingPercentage,
|
showsChargingPercentage: showsChargingPercentage,
|
||||||
temperatureInfo: payload.temperatureInfo,
|
temperatureInfo: payload.temperatureInfo,
|
||||||
usesGaugeTint: usesGaugeTint
|
usesGaugeTint: usesGaugeTint,
|
||||||
|
sunCycle: payload.sunCycle,
|
||||||
|
showsSunrise: shouldShowSunrise
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -384,6 +395,11 @@ final class LockScreenWeatherManager: ObservableObject {
|
||||||
}
|
}
|
||||||
|
|
||||||
struct LockScreenWeatherSnapshot: Equatable {
|
struct LockScreenWeatherSnapshot: Equatable {
|
||||||
|
struct SunCycleInfo: Equatable {
|
||||||
|
let sunrise: Date?
|
||||||
|
let sunset: Date?
|
||||||
|
}
|
||||||
|
|
||||||
struct TemperatureInfo: Equatable {
|
struct TemperatureInfo: Equatable {
|
||||||
let current: Double
|
let current: Double
|
||||||
let minimum: Double?
|
let minimum: Double?
|
||||||
|
|
@ -487,6 +503,8 @@ struct LockScreenWeatherSnapshot: Equatable {
|
||||||
let showsChargingPercentage: Bool
|
let showsChargingPercentage: Bool
|
||||||
let temperatureInfo: TemperatureInfo?
|
let temperatureInfo: TemperatureInfo?
|
||||||
let usesGaugeTint: Bool
|
let usesGaugeTint: Bool
|
||||||
|
let sunCycle: SunCycleInfo?
|
||||||
|
let showsSunrise: Bool
|
||||||
}
|
}
|
||||||
|
|
||||||
extension LockScreenWeatherSnapshot.AirQualityInfo.Category {
|
extension LockScreenWeatherSnapshot.AirQualityInfo.Category {
|
||||||
|
|
@ -615,7 +633,9 @@ private actor LockScreenWeatherProvider {
|
||||||
)
|
)
|
||||||
|
|
||||||
let code = Int(condition.weatherCode) ?? 113
|
let code = Int(condition.weatherCode) ?? 113
|
||||||
let symbol = WeatherSymbolMapper.symbol(for: code)
|
let isDaytime = condition.isDaytime ?? true
|
||||||
|
let baseSymbol = WeatherSymbolMapper.symbol(for: code)
|
||||||
|
let symbol = symbolAdjustedForDaylight(baseSymbol, isDaytime: isDaytime)
|
||||||
let description = condition.localizedDescription
|
let description = condition.localizedDescription
|
||||||
|
|
||||||
let nearest = payload.nearestArea.first
|
let nearest = payload.nearestArea.first
|
||||||
|
|
@ -646,7 +666,9 @@ private actor LockScreenWeatherProvider {
|
||||||
widgetStyle: .inline,
|
widgetStyle: .inline,
|
||||||
showsChargingPercentage: true,
|
showsChargingPercentage: true,
|
||||||
temperatureInfo: temperatureInfo,
|
temperatureInfo: temperatureInfo,
|
||||||
usesGaugeTint: true
|
usesGaugeTint: true,
|
||||||
|
sunCycle: nil,
|
||||||
|
showsSunrise: false
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -660,9 +682,9 @@ private actor LockScreenWeatherProvider {
|
||||||
var weatherQueryItems: [URLQueryItem] = [
|
var weatherQueryItems: [URLQueryItem] = [
|
||||||
URLQueryItem(name: "latitude", value: latitude),
|
URLQueryItem(name: "latitude", value: latitude),
|
||||||
URLQueryItem(name: "longitude", value: longitude),
|
URLQueryItem(name: "longitude", value: longitude),
|
||||||
URLQueryItem(name: "current", value: "temperature_2m,weather_code,pressure_msl"),
|
URLQueryItem(name: "current", value: "temperature_2m,weather_code,is_day,pressure_msl"),
|
||||||
URLQueryItem(name: "daily", value: "temperature_2m_max,temperature_2m_min"),
|
URLQueryItem(name: "daily", value: "temperature_2m_max,temperature_2m_min,sunrise,sunset"),
|
||||||
URLQueryItem(name: "forecast_days", value: "1"),
|
URLQueryItem(name: "forecast_days", value: "2"),
|
||||||
URLQueryItem(name: "timezone", value: "auto")
|
URLQueryItem(name: "timezone", value: "auto")
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
@ -692,9 +714,21 @@ private actor LockScreenWeatherProvider {
|
||||||
let temperatureText = "\(Int(round(temperatureValue)))°"
|
let temperatureText = "\(Int(round(temperatureValue)))°"
|
||||||
let code = current.weatherCode ?? 0
|
let code = current.weatherCode ?? 0
|
||||||
let mapping = OpenMeteoSymbolMapper.mapping(for: code)
|
let mapping = OpenMeteoSymbolMapper.mapping(for: code)
|
||||||
|
let isDaytime = (current.isDay ?? 1) == 1
|
||||||
|
let symbolName = symbolAdjustedForDaylight(mapping.symbol, isDaytime: isDaytime)
|
||||||
let unitSymbol = unit.symbol
|
let unitSymbol = unit.symbol
|
||||||
let minTempValue = weatherPayload.daily?.temperature2MMin?.first
|
let minTempValue = weatherPayload.daily?.temperature2MMin?.first
|
||||||
let maxTempValue = weatherPayload.daily?.temperature2MMax?.first
|
let maxTempValue = weatherPayload.daily?.temperature2MMax?.first
|
||||||
|
let timezoneIdentifier = weatherPayload.timezone
|
||||||
|
let utcOffsetSeconds = weatherPayload.utcOffsetSeconds
|
||||||
|
let sunriseDate = nextSunEvent(from: weatherPayload.daily?.sunrise, timezoneIdentifier: timezoneIdentifier, offsetSeconds: utcOffsetSeconds)
|
||||||
|
let sunsetDate = nextSunEvent(from: weatherPayload.daily?.sunset, timezoneIdentifier: timezoneIdentifier, offsetSeconds: utcOffsetSeconds)
|
||||||
|
let sunCycle: LockScreenWeatherSnapshot.SunCycleInfo?
|
||||||
|
if sunriseDate != nil || sunsetDate != nil {
|
||||||
|
sunCycle = LockScreenWeatherSnapshot.SunCycleInfo(sunrise: sunriseDate, sunset: sunsetDate)
|
||||||
|
} else {
|
||||||
|
sunCycle = nil
|
||||||
|
}
|
||||||
|
|
||||||
let temperatureInfo = LockScreenWeatherSnapshot.TemperatureInfo(
|
let temperatureInfo = LockScreenWeatherSnapshot.TemperatureInfo(
|
||||||
current: temperatureValue,
|
current: temperatureValue,
|
||||||
|
|
@ -712,7 +746,7 @@ private actor LockScreenWeatherProvider {
|
||||||
|
|
||||||
return LockScreenWeatherSnapshot(
|
return LockScreenWeatherSnapshot(
|
||||||
temperatureText: temperatureText,
|
temperatureText: temperatureText,
|
||||||
symbolName: mapping.symbol,
|
symbolName: symbolName,
|
||||||
description: mapping.description,
|
description: mapping.description,
|
||||||
locationName: nil,
|
locationName: nil,
|
||||||
charging: nil,
|
charging: nil,
|
||||||
|
|
@ -723,7 +757,9 @@ private actor LockScreenWeatherProvider {
|
||||||
widgetStyle: .inline,
|
widgetStyle: .inline,
|
||||||
showsChargingPercentage: true,
|
showsChargingPercentage: true,
|
||||||
temperatureInfo: temperatureInfo,
|
temperatureInfo: temperatureInfo,
|
||||||
usesGaugeTint: true
|
usesGaugeTint: true,
|
||||||
|
sunCycle: sunCycle,
|
||||||
|
showsSunrise: false
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -771,6 +807,30 @@ private actor LockScreenWeatherProvider {
|
||||||
scale: scale
|
scale: scale
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private func nextSunEvent(from values: [String]?, timezoneIdentifier: String?, offsetSeconds: Int?) -> Date? {
|
||||||
|
guard let values else { return nil }
|
||||||
|
let now = Date()
|
||||||
|
for value in values {
|
||||||
|
if let date = parseLocalSunTime(value, timezoneIdentifier: timezoneIdentifier, offsetSeconds: offsetSeconds), date >= now {
|
||||||
|
return date
|
||||||
|
}
|
||||||
|
}
|
||||||
|
guard let fallbackValue = values.last else { return nil }
|
||||||
|
return parseLocalSunTime(fallbackValue, timezoneIdentifier: timezoneIdentifier, offsetSeconds: offsetSeconds)
|
||||||
|
}
|
||||||
|
|
||||||
|
private func parseLocalSunTime(_ value: String, timezoneIdentifier: String?, offsetSeconds: Int?) -> Date? {
|
||||||
|
let formatter = DateFormatter()
|
||||||
|
formatter.locale = Locale(identifier: "en_US_POSIX")
|
||||||
|
formatter.dateFormat = "yyyy-MM-dd'T'HH:mm"
|
||||||
|
if let identifier = timezoneIdentifier, let timeZone = TimeZone(identifier: identifier) {
|
||||||
|
formatter.timeZone = timeZone
|
||||||
|
} else if let offset = offsetSeconds, let timeZone = TimeZone(secondsFromGMT: offset) {
|
||||||
|
formatter.timeZone = timeZone
|
||||||
|
}
|
||||||
|
return formatter.date(from: value)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
enum WeatherProviderError: Error {
|
enum WeatherProviderError: Error {
|
||||||
|
|
@ -799,6 +859,7 @@ private struct WTTRCurrentCondition: Decodable {
|
||||||
case pressure = "pressure"
|
case pressure = "pressure"
|
||||||
case pressureInches = "pressureInches"
|
case pressureInches = "pressureInches"
|
||||||
case airQuality = "air_quality"
|
case airQuality = "air_quality"
|
||||||
|
case isday = "isday"
|
||||||
}
|
}
|
||||||
|
|
||||||
let tempC: String
|
let tempC: String
|
||||||
|
|
@ -809,6 +870,7 @@ private struct WTTRCurrentCondition: Decodable {
|
||||||
let pressure: String?
|
let pressure: String?
|
||||||
let pressureInches: String?
|
let pressureInches: String?
|
||||||
let airQuality: WTTRAirQuality?
|
let airQuality: WTTRAirQuality?
|
||||||
|
let isday: String?
|
||||||
|
|
||||||
var localizedDescription: String {
|
var localizedDescription: String {
|
||||||
if let english = langEn?.first?.value, !english.isEmpty {
|
if let english = langEn?.first?.value, !english.isEmpty {
|
||||||
|
|
@ -819,6 +881,12 @@ private struct WTTRCurrentCondition: Decodable {
|
||||||
}
|
}
|
||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var isDaytime: Bool? {
|
||||||
|
guard let isday else { return nil }
|
||||||
|
let normalized = isday.trimmingCharacters(in: .whitespacesAndNewlines).lowercased()
|
||||||
|
return normalized == "1" || normalized == "yes"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private struct WTTRTextValue: Decodable {
|
private struct WTTRTextValue: Decodable {
|
||||||
|
|
@ -885,14 +953,19 @@ private struct OpenMeteoForecastResponse: Decodable {
|
||||||
let temperature2M: Double?
|
let temperature2M: Double?
|
||||||
let weatherCode: Int?
|
let weatherCode: Int?
|
||||||
let pressureMsl: Double?
|
let pressureMsl: Double?
|
||||||
|
let isDay: Int?
|
||||||
}
|
}
|
||||||
|
|
||||||
let current: Current?
|
let current: Current?
|
||||||
let daily: Daily?
|
let daily: Daily?
|
||||||
|
let timezone: String?
|
||||||
|
let utcOffsetSeconds: Int?
|
||||||
|
|
||||||
struct Daily: Decodable {
|
struct Daily: Decodable {
|
||||||
let temperature2MMax: [Double]?
|
let temperature2MMax: [Double]?
|
||||||
let temperature2MMin: [Double]?
|
let temperature2MMin: [Double]?
|
||||||
|
let sunrise: [String]?
|
||||||
|
let sunset: [String]?
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -968,6 +1041,22 @@ private enum WeatherSymbolMapper {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private func symbolAdjustedForDaylight(_ symbol: String, isDaytime: Bool) -> String {
|
||||||
|
guard !isDaytime else { return symbol }
|
||||||
|
switch symbol {
|
||||||
|
case "sun.max.fill":
|
||||||
|
return "moon.stars.fill"
|
||||||
|
case "cloud.sun.fill":
|
||||||
|
return "cloud.moon.fill"
|
||||||
|
case "cloud.sun.rain.fill":
|
||||||
|
return "cloud.moon.rain.fill"
|
||||||
|
case "cloud.sun.bolt.fill":
|
||||||
|
return "cloud.moon.bolt.fill"
|
||||||
|
default:
|
||||||
|
return symbol
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@MainActor
|
@MainActor
|
||||||
private final class LockScreenWeatherLocationProvider: NSObject, CLLocationManagerDelegate {
|
private final class LockScreenWeatherLocationProvider: NSObject, CLLocationManagerDelegate {
|
||||||
private let manager: CLLocationManager
|
private let manager: CLLocationManager
|
||||||
|
|
|
||||||
|
|
@ -466,6 +466,7 @@ extension Defaults.Keys {
|
||||||
static let lockScreenWeatherShowsBluetooth = Key<Bool>("lockScreenWeatherShowsBluetooth", default: true)
|
static let lockScreenWeatherShowsBluetooth = Key<Bool>("lockScreenWeatherShowsBluetooth", default: true)
|
||||||
static let lockScreenWeatherShowsBatteryGauge = Key<Bool>("lockScreenWeatherShowsBatteryGauge", default: true)
|
static let lockScreenWeatherShowsBatteryGauge = Key<Bool>("lockScreenWeatherShowsBatteryGauge", default: true)
|
||||||
static let lockScreenWeatherBatteryUsesLaptopSymbol = Key<Bool>("lockScreenWeatherBatteryUsesLaptopSymbol", default: true)
|
static let lockScreenWeatherBatteryUsesLaptopSymbol = Key<Bool>("lockScreenWeatherBatteryUsesLaptopSymbol", default: true)
|
||||||
|
static let lockScreenWeatherShowsSunrise = Key<Bool>("lockScreenWeatherShowsSunrise", default: true)
|
||||||
static let lockScreenWeatherWidgetStyle = Key<LockScreenWeatherWidgetStyle>("lockScreenWeatherWidgetStyle", default: .circular)
|
static let lockScreenWeatherWidgetStyle = Key<LockScreenWeatherWidgetStyle>("lockScreenWeatherWidgetStyle", default: .circular)
|
||||||
static let lockScreenWeatherTemperatureUnit = Key<LockScreenWeatherTemperatureUnit>("lockScreenWeatherTemperatureUnit", default: .celsius)
|
static let lockScreenWeatherTemperatureUnit = Key<LockScreenWeatherTemperatureUnit>("lockScreenWeatherTemperatureUnit", default: .celsius)
|
||||||
static let lockScreenWeatherShowsAQI = Key<Bool>("lockScreenWeatherShowsAQI", default: true)
|
static let lockScreenWeatherShowsAQI = Key<Bool>("lockScreenWeatherShowsAQI", default: true)
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue