Neben ihr wären die Arme zwangsläufig gleich breit — die Silhouette zeichnet den Steg mittig um die Hardware. Ein Symbol links und ein Balken rechts hieße dann 132 Punkte Schwarz für ein Symbol. Darunter hängt die Fläche frei und wird so breit, wie ihr Inhalt es braucht. Lautstärke, Strom, Titelwechsel und Helligkeit. Gemeldet wird nur, was sich **geändert** hat: `PowerWatcher` schweigt beim ersten gelesenen Zustand, sonst begrüßte Onyx jeden Start mit „am Netz". Die Helligkeit kostete einen Spike. Von drei Wegen trägt nur `DisplayServices`, und nur für den eingebauten Bildschirm. `CoreDisplay` — der Weg, den jede Anleitung empfiehlt — gibt für alle Bildschirme 1.0 zurück, auch für den, der auf 62 % stand. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
128 lines
4.4 KiB
Swift
128 lines
4.4 KiB
Swift
import SwiftUI
|
|
import ActivityProvider
|
|
import MetricsProvider
|
|
import OnyxDesign
|
|
|
|
/// Was am Strom geschieht — Netzteil dran, ab, Limit erreicht, Akku fast leer.
|
|
///
|
|
/// **Nicht bei jedem Messwert, sondern bei jedem Wechsel.** Der Ladestand
|
|
/// ändert sich ständig; eine Notch, die das begleitet, wäre eine Leuchtreklame.
|
|
/// Angemeldet wird nur, wenn sich der *Zustand* ändert.
|
|
struct PowerActivity: NotchActivity {
|
|
|
|
/// Was gemeldet wird.
|
|
enum Kind: Equatable {
|
|
case charging
|
|
case plugged
|
|
case unplugged
|
|
case atLimit(Int)
|
|
case low
|
|
|
|
var text: String {
|
|
switch self {
|
|
case .charging: String(localized: "activity.power.charging")
|
|
case .plugged: String(localized: "activity.power.plugged")
|
|
case .unplugged: String(localized: "activity.power.unplugged")
|
|
case .atLimit: String(localized: "activity.power.atLimit")
|
|
case .low: String(localized: "activity.power.low")
|
|
}
|
|
}
|
|
|
|
/// „Fast leer" ist das eine, das man nicht verpassen darf.
|
|
var rank: Int { self == .low ? ActivityRank.alert : ActivityRank.notice }
|
|
|
|
/// Eine Warnung darf länger stehen als eine Beiläufigkeit.
|
|
var duration: TimeInterval { self == .low ? 4 : 2.2 }
|
|
}
|
|
|
|
let id = "power"
|
|
let kind: Kind
|
|
let charge: Double
|
|
let isCharging: Bool
|
|
let isPluggedIn: Bool
|
|
|
|
var rank: Int { kind.rank }
|
|
var duration: TimeInterval? { kind.duration }
|
|
var size: CGSize { CGSize(width: 232, height: 34) }
|
|
|
|
func makeContent() -> AnyView {
|
|
AnyView(
|
|
HStack(spacing: Onyx.Space.lg) {
|
|
BatteryGlyphView(charge: charge,
|
|
isCharging: isCharging,
|
|
isPluggedIn: isPluggedIn,
|
|
height: 13)
|
|
|
|
Text(kind.text)
|
|
.font(Onyx.Font.footnote)
|
|
.foregroundStyle(Onyx.Color.textPrimary)
|
|
.lineLimit(1)
|
|
|
|
Spacer(minLength: Onyx.Space.sm)
|
|
|
|
Text("\(Int((min(max(charge, 0), 1) * 100).rounded())) %")
|
|
.font(Onyx.Font.metricSmall)
|
|
.foregroundStyle(kind == .low ? Onyx.Color.critical
|
|
: Onyx.Color.textSecondary)
|
|
}
|
|
.padding(.horizontal, Onyx.Space.xl)
|
|
.frame(maxWidth: .infinity, maxHeight: .infinity)
|
|
)
|
|
}
|
|
}
|
|
|
|
/// Was gerade spielt — beim Wechsel des Titels.
|
|
///
|
|
/// Ohne Fortschritt und ohne Knöpfe: das steht im Panel. Hier geht es um die
|
|
/// eine Frage, die man beim Wechsel hat — was ist das jetzt?
|
|
struct MediaActivity: NotchActivity {
|
|
|
|
let id = "media"
|
|
let rank = ActivityRank.notice
|
|
let duration: TimeInterval? = 3
|
|
var size: CGSize { CGSize(width: 264, height: 40) }
|
|
|
|
let title: String
|
|
let artist: String
|
|
let artwork: Data?
|
|
|
|
func makeContent() -> AnyView {
|
|
AnyView(
|
|
HStack(spacing: Onyx.Space.lg) {
|
|
Group {
|
|
if let artwork, let image = NSImage(data: artwork) {
|
|
Image(nsImage: image).resizable()
|
|
} else {
|
|
// Kein Cover ist kein Fehler — Radiostreams haben keins.
|
|
Onyx.Color.elevated
|
|
.overlay {
|
|
Image(systemName: "music.note")
|
|
.font(Onyx.Font.micro)
|
|
.foregroundStyle(Onyx.Color.textTertiary)
|
|
}
|
|
}
|
|
}
|
|
.frame(width: 26, height: 26)
|
|
.clipShape(RoundedRectangle(cornerRadius: Onyx.Radius.chip, style: .continuous))
|
|
|
|
VStack(alignment: .leading, spacing: Onyx.Space.xxs) {
|
|
Text(title)
|
|
.font(Onyx.Font.micro)
|
|
.foregroundStyle(Onyx.Color.textPrimary)
|
|
.lineLimit(1)
|
|
if !artist.isEmpty {
|
|
Text(artist)
|
|
.font(Onyx.Font.micro)
|
|
.foregroundStyle(Onyx.Color.textTertiary)
|
|
.lineLimit(1)
|
|
}
|
|
}
|
|
|
|
Spacer(minLength: 0)
|
|
}
|
|
.padding(.horizontal, Onyx.Space.xl)
|
|
.frame(maxWidth: .infinity, maxHeight: .infinity)
|
|
)
|
|
}
|
|
}
|