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