mirror of
https://github.com/FBukovina/Atoll.git
synced 2026-10-27 15:14:33 +00:00
Working with fallbacks
This commit is contained in:
parent
5f324ecde8
commit
2612cf9d7f
6 changed files with 63 additions and 44 deletions
|
|
@ -8496,9 +8496,6 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
"Full" : {
|
||||
"comment" : "Full battery level label"
|
||||
},
|
||||
"Fully charged" : {
|
||||
"comment" : "Charging fallback label when already charged"
|
||||
},
|
||||
|
|
|
|||
|
|
@ -13,6 +13,8 @@ struct LockScreenWeatherWidget: View {
|
|||
private var stackSpacing: CGFloat { isInline ? 14 : 22 }
|
||||
private var gaugeLineWidth: CGFloat { 6 }
|
||||
private var gaugeDiameter: CGFloat { 64 }
|
||||
private var topPadding: CGFloat { isInline ? 6 : 22 }
|
||||
private var bottomPadding: CGFloat { isInline ? 6 : 10 }
|
||||
|
||||
private var gaugeBackgroundColor: Color {
|
||||
snapshot.usesGaugeTint ? Color.white.opacity(0.22) : Color.white.opacity(0.32)
|
||||
|
|
@ -45,7 +47,8 @@ struct LockScreenWeatherWidget: View {
|
|||
.frame(maxWidth: .infinity, alignment: .leading)
|
||||
.foregroundStyle(Color.white.opacity(0.65))
|
||||
.padding(.horizontal, 10)
|
||||
.padding(.vertical, 6)
|
||||
.padding(.top, topPadding)
|
||||
.padding(.bottom, bottomPadding)
|
||||
.background(Color.clear)
|
||||
.shadow(color: .black.opacity(0.35), radius: 8, x: 0, y: 3)
|
||||
.accessibilityElement(children: .ignore)
|
||||
|
|
@ -143,7 +146,7 @@ struct LockScreenWeatherWidget: View {
|
|||
Image(systemName: info.iconName)
|
||||
.font(.system(size: 20, weight: .semibold))
|
||||
.symbolRenderingMode(.hierarchical)
|
||||
Text(bluetoothStatusLabel(for: info.batteryLevel))
|
||||
Text(bluetoothPercentageText(for: info.batteryLevel))
|
||||
.font(inlinePrimaryFont)
|
||||
.lineLimit(1)
|
||||
.minimumScaleFactor(0.85)
|
||||
|
|
@ -159,7 +162,7 @@ struct LockScreenWeatherWidget: View {
|
|||
.font(.system(size: 22, weight: .semibold))
|
||||
.foregroundStyle(Color.white)
|
||||
}
|
||||
Text(bluetoothStatusLabel(for: info.batteryLevel))
|
||||
Text(bluetoothPercentageText(for: info.batteryLevel))
|
||||
.font(inlineSecondaryFont)
|
||||
.foregroundStyle(secondaryLabelColor)
|
||||
}
|
||||
|
|
@ -285,18 +288,8 @@ struct LockScreenWeatherWidget: View {
|
|||
return NSLocalizedString("Charging", comment: "Charging fallback label when no estimate is available")
|
||||
}
|
||||
|
||||
private func bluetoothStatusLabel(for level: Int) -> String {
|
||||
let value = clampedBatteryLevel(level)
|
||||
switch value {
|
||||
case ..<20:
|
||||
return NSLocalizedString("Low", comment: "Low battery level label")
|
||||
case 20..<50:
|
||||
return NSLocalizedString("Medium", comment: "Medium battery level label")
|
||||
case 50..<80:
|
||||
return NSLocalizedString("High", comment: "High battery level label")
|
||||
default:
|
||||
return NSLocalizedString("Full", comment: "Full battery level label")
|
||||
}
|
||||
private func bluetoothPercentageText(for level: Int) -> String {
|
||||
"\(clampedBatteryLevel(level))%"
|
||||
}
|
||||
|
||||
private func minimumTemperatureLabel(for info: LockScreenWeatherSnapshot.TemperatureInfo) -> String {
|
||||
|
|
@ -362,25 +355,27 @@ struct LockScreenWeatherWidget: View {
|
|||
|
||||
private func batteryTint(for level: Int) -> Color {
|
||||
guard snapshot.usesGaugeTint else { return monochromeGaugeTint }
|
||||
switch level {
|
||||
let clamped = clampedBatteryLevel(level)
|
||||
switch clamped {
|
||||
case ..<20:
|
||||
return Color(red: 0.91, green: 0.29, blue: 0.25)
|
||||
return Color(.systemRed)
|
||||
case 20..<50:
|
||||
return Color(red: 0.99, green: 0.74, blue: 0.30)
|
||||
return Color(.systemOrange)
|
||||
default:
|
||||
return Color(red: 0.20, green: 0.79, blue: 0.39)
|
||||
return Color(.systemGreen)
|
||||
}
|
||||
}
|
||||
|
||||
private func bluetoothTint(for level: Int) -> Color {
|
||||
guard snapshot.usesGaugeTint else { return monochromeGaugeTint }
|
||||
switch level {
|
||||
let clamped = clampedBatteryLevel(level)
|
||||
switch clamped {
|
||||
case ..<20:
|
||||
return Color(red: 0.91, green: 0.29, blue: 0.25)
|
||||
return Color(.systemRed)
|
||||
case 20..<50:
|
||||
return Color(red: 0.97, green: 0.58, blue: 0.29)
|
||||
return Color(.systemOrange)
|
||||
default:
|
||||
return Color(red: 0.29, green: 0.63, blue: 1.00)
|
||||
return Color(.systemGreen)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -4,13 +4,12 @@
|
|||
//
|
||||
// Created by Richard Kunkli on 07/08/2024.
|
||||
//
|
||||
|
||||
import AVFoundation
|
||||
import Defaults
|
||||
import EventKit
|
||||
import KeyboardShortcuts
|
||||
import LaunchAtLogin
|
||||
import LottieUI
|
||||
import AVFoundation
|
||||
import Sparkle
|
||||
import SwiftUI
|
||||
import SwiftUIIntrospect
|
||||
|
|
@ -347,6 +346,7 @@ struct GeneralSettings: View {
|
|||
}
|
||||
.pickerStyle(.segmented)
|
||||
Defaults.Toggle("Show location label", key: .lockScreenWeatherShowsLocation)
|
||||
.disabled(lockScreenWeatherWidgetStyle == .circular)
|
||||
Defaults.Toggle("Show charging status", key: .lockScreenWeatherShowsCharging)
|
||||
if lockScreenWeatherShowsCharging {
|
||||
Defaults.Toggle("Show charging percentage", key: .lockScreenWeatherShowsChargingPercentage)
|
||||
|
|
@ -662,9 +662,6 @@ struct HUD: View {
|
|||
|
||||
Section {
|
||||
Toggle("Enable Built-in System HUD", isOn: $enableSystemHUD)
|
||||
.onChange(of: enableSystemHUD) { _ in
|
||||
// SystemHUDManager will automatically handle start/stop via its @Default observer
|
||||
}
|
||||
|
||||
if enableSystemHUD {
|
||||
Toggle("Volume HUD", isOn: $enableVolumeHUD)
|
||||
|
|
@ -1943,34 +1940,34 @@ struct TimerSettings: View {
|
|||
let nextIndex = timerPresets.count + 1
|
||||
let defaultColor = Defaults[.accentColor]
|
||||
let newPreset = TimerPreset(name: "Preset \(nextIndex)", duration: 5 * 60, color: defaultColor)
|
||||
withAnimation(.smooth) {
|
||||
_ = withAnimation(.smooth) {
|
||||
timerPresets.append(newPreset)
|
||||
}
|
||||
}
|
||||
|
||||
private func movePresetUp(_ index: Int) {
|
||||
guard index > timerPresets.startIndex else { return }
|
||||
withAnimation(.smooth) {
|
||||
_ = withAnimation(.smooth) {
|
||||
timerPresets.swapAt(index, index - 1)
|
||||
}
|
||||
}
|
||||
|
||||
private func movePresetDown(_ index: Int) {
|
||||
guard index < timerPresets.index(before: timerPresets.endIndex) else { return }
|
||||
withAnimation(.smooth) {
|
||||
_ = withAnimation(.smooth) {
|
||||
timerPresets.swapAt(index, index + 1)
|
||||
}
|
||||
}
|
||||
|
||||
private func removePreset(_ index: Int) {
|
||||
guard timerPresets.indices.contains(index) else { return }
|
||||
withAnimation(.smooth) {
|
||||
_ = withAnimation(.smooth) {
|
||||
timerPresets.remove(at: index)
|
||||
}
|
||||
}
|
||||
|
||||
private func resetPresets() {
|
||||
withAnimation(.smooth) {
|
||||
_ = withAnimation(.smooth) {
|
||||
timerPresets = TimerPreset.defaultPresets
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -17,6 +17,11 @@ final class LockScreenWeatherManager: ObservableObject {
|
|||
|
||||
private init() {
|
||||
observeAccessoryChanges()
|
||||
Task { @MainActor [weak self] in
|
||||
guard let self else { return }
|
||||
self.locationProvider.prepareAuthorization()
|
||||
_ = await self.refresh(force: true)
|
||||
}
|
||||
}
|
||||
|
||||
func prepareLocationAccess() {
|
||||
|
|
@ -73,7 +78,7 @@ final class LockScreenWeatherManager: ObservableObject {
|
|||
|
||||
do {
|
||||
let location = await locationProvider.currentLocation()
|
||||
let payload = try await provider.fetchSnapshot(location: location)
|
||||
let payload = try await fetchWeatherPayload(location: location)
|
||||
latestWeatherPayload = payload
|
||||
if Defaults[.lockScreenWeatherShowsBluetooth] {
|
||||
BluetoothAudioManager.shared.refreshConnectedDeviceBatteries()
|
||||
|
|
@ -108,6 +113,25 @@ final class LockScreenWeatherManager: ObservableObject {
|
|||
}
|
||||
}
|
||||
|
||||
private func fetchWeatherPayload(location: CLLocation?) async throws -> LockScreenWeatherSnapshot {
|
||||
let primarySource = Defaults[.lockScreenWeatherProviderSource]
|
||||
|
||||
do {
|
||||
return try await provider.fetchSnapshot(location: location, source: primarySource)
|
||||
} catch {
|
||||
if primarySource == .openMeteo {
|
||||
NSLog("LockScreenWeatherManager: Open Meteo fetch failed - %@. Falling back to wttr.in", error.localizedDescription)
|
||||
do {
|
||||
return try await provider.fetchSnapshot(location: location, source: .wttr)
|
||||
} catch {
|
||||
NSLog("LockScreenWeatherManager: wttr.in fallback also failed - %@", error.localizedDescription)
|
||||
throw error
|
||||
}
|
||||
}
|
||||
throw error
|
||||
}
|
||||
}
|
||||
|
||||
private func observeAccessoryChanges() {
|
||||
let bluetoothManager = BluetoothAudioManager.shared
|
||||
|
||||
|
|
@ -198,11 +222,11 @@ final class LockScreenWeatherManager: ObservableObject {
|
|||
}
|
||||
|
||||
private func makeSnapshot(from payload: LockScreenWeatherSnapshot) -> LockScreenWeatherSnapshot {
|
||||
let locationName = payload.locationName
|
||||
let shouldShowLocation = Defaults[.lockScreenWeatherShowsLocation] && !(locationName?.isEmpty ?? true)
|
||||
let locationName = payload.locationName
|
||||
let chargingInfo = Defaults[.lockScreenWeatherShowsCharging] ? makeChargingInfo() : nil
|
||||
let bluetoothInfo = Defaults[.lockScreenWeatherShowsBluetooth] ? makeBluetoothInfo() : nil
|
||||
let widgetStyle = Defaults[.lockScreenWeatherWidgetStyle]
|
||||
let shouldShowLocation = widgetStyle == .inline && Defaults[.lockScreenWeatherShowsLocation] && !(locationName?.isEmpty ?? true)
|
||||
let showsChargingPercentage = Defaults[.lockScreenWeatherShowsChargingPercentage]
|
||||
let providerSource = Defaults[.lockScreenWeatherProviderSource]
|
||||
let airQualityInfo = (Defaults[.lockScreenWeatherShowsAQI] && providerSource.supportsAirQuality) ? payload.airQuality : nil
|
||||
|
|
@ -406,6 +430,10 @@ private actor LockScreenWeatherProvider {
|
|||
|
||||
func fetchSnapshot(location: CLLocation?) async throws -> LockScreenWeatherSnapshot {
|
||||
let source = Defaults[.lockScreenWeatherProviderSource]
|
||||
return try await fetchSnapshot(location: location, source: source)
|
||||
}
|
||||
|
||||
func fetchSnapshot(location: CLLocation?, source: LockScreenWeatherProviderSource) async throws -> LockScreenWeatherSnapshot {
|
||||
switch source {
|
||||
case .wttr:
|
||||
return try await fetchWttrSnapshot(location: location)
|
||||
|
|
|
|||
|
|
@ -80,7 +80,9 @@ final class LockScreenWeatherPanelManager {
|
|||
let originX = screenFrame.midX - (size.width / 2)
|
||||
let verticalOffset = screenFrame.height * 0.15
|
||||
let maxY = screenFrame.maxY - size.height - 48
|
||||
let originY = min(maxY, screenFrame.midY + verticalOffset)
|
||||
return NSRect(x: originX, y: originY, width: size.width, height: size.height)
|
||||
let baseY = min(maxY, screenFrame.midY + verticalOffset)
|
||||
let loweredY = baseY - 36
|
||||
let clampedY = max(screenFrame.minY + 80, loweredY)
|
||||
return NSRect(x: originX, y: clampedY, width: size.width, height: size.height)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -364,10 +364,10 @@ extension Defaults.Keys {
|
|||
static let lockScreenWeatherShowsCharging = Key<Bool>("lockScreenWeatherShowsCharging", default: true)
|
||||
static let lockScreenWeatherShowsChargingPercentage = Key<Bool>("lockScreenWeatherShowsChargingPercentage", default: true)
|
||||
static let lockScreenWeatherShowsBluetooth = Key<Bool>("lockScreenWeatherShowsBluetooth", default: true)
|
||||
static let lockScreenWeatherWidgetStyle = Key<LockScreenWeatherWidgetStyle>("lockScreenWeatherWidgetStyle", default: .inline)
|
||||
static let lockScreenWeatherWidgetStyle = Key<LockScreenWeatherWidgetStyle>("lockScreenWeatherWidgetStyle", default: .circular)
|
||||
static let lockScreenWeatherShowsAQI = Key<Bool>("lockScreenWeatherShowsAQI", default: false)
|
||||
static let lockScreenWeatherUsesGaugeTint = Key<Bool>("lockScreenWeatherUsesGaugeTint", default: true)
|
||||
static let lockScreenWeatherProviderSource = Key<LockScreenWeatherProviderSource>("lockScreenWeatherProviderSource", default: .wttr)
|
||||
static let lockScreenWeatherUsesGaugeTint = Key<Bool>("lockScreenWeatherUsesGaugeTint", default: false)
|
||||
static let lockScreenWeatherProviderSource = Key<LockScreenWeatherProviderSource>("lockScreenWeatherProviderSource", default: .openMeteo)
|
||||
static let lockScreenGlassStyle = Key<LockScreenGlassStyle>("lockScreenGlassStyle", default: .liquid)
|
||||
static let lockScreenShowAppIcon = Key<Bool>("lockScreenShowAppIcon", default: false)
|
||||
static let lockScreenPanelShowsBorder = Key<Bool>("lockScreenPanelShowsBorder", default: true)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue