Ladelimit ist eine Anzeige, Akku-Kachel sagt etwas, Menüleiste bekommt Kürzel
Der Regler war eine Pseudobedienung. Der SMC lehnt Schreibzugriffe auf CHLT ab — der Helfer hat es mit korrekter Größe versucht und „geschrieben (false), zurückgelesen 85" gemeldet. Das Sicherheitsnetz hat also die Wahrheit gesagt; die Oberfläche hat sie nur nicht gezeigt. Weiter zu raten hieße, im Lade-Subsystem Keys durchzuprobieren, und das ist die eine Operation in diesem Projekt, die Hardware dauerhaft beschädigen kann. Also: das Limit wird gelesen und angezeigt, mit einem Weg in die Systemeinstellungen, wo es tatsächlich geht. Der Schreibpfad ist aus dem privilegierten Helfer entfernt — ein Root-Dienst trägt nur, was benutzt wird. Die Akku-Kachel zeigte „40 %" und einen Verlauf über die letzten Sekunden. Der Ladestand ändert sich in Minuten, nicht in Sekunden; der Graph war eine waagerechte Linie. Jetzt: verbleibende Zeit bzw. Zeit bis voll, Leistung in Watt mit Richtung, Ladelimit, Zustand, Zyklen — und der Energiesparmodus. Der erklärt, warum die Maschine langsamer wirkt, und ohne diesen Hinweis sucht man den Grund woanders. In der Menüleiste standen fünf nackte Prozentzahlen nebeneinander. Man sah Zahlen und wusste nicht, welche wozu gehört. Zwei neue Darstellungen: Kürzel und Wert („CPU 34 %") sowie Symbol und Wert. Beim Akku stehen sie zuoberst in der Auswahl. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -54,14 +54,8 @@ private struct MetricWidgetView: View {
|
||||
// alles andere zeigt Zahl und Verlauf.
|
||||
if metric == .temperature {
|
||||
SensorList(snapshot: model.snapshot, detailed: true)
|
||||
} else if metric == .battery, let control = model.chargeControl {
|
||||
// Die Akku-Kachel bekommt den Ladelimit-Regler direkt: dort
|
||||
// schaut man ohnehin hin, wenn man ihn braucht.
|
||||
VStack(alignment: .leading, spacing: 8) {
|
||||
WideMetric(model: model, metric: metric)
|
||||
Divider().overlay(Onyx.Color.hairline)
|
||||
ChargeLimitControl(control: control, roomy: false)
|
||||
}
|
||||
} else if metric == .battery {
|
||||
BatteryTile(model: model)
|
||||
} else {
|
||||
WideMetric(model: model, metric: metric)
|
||||
}
|
||||
@@ -327,3 +321,97 @@ public enum MetricSummary {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// Die Akku-Kachel.
|
||||
///
|
||||
/// „40 %" und ein Verlauf über die letzten Sekunden ist keine Auskunft — der
|
||||
/// Ladestand ändert sich in Minuten, nicht in Sekunden, und ein Graph darüber
|
||||
/// ist eine waagerechte Linie. Was man wissen will: wie lange noch, zieht oder
|
||||
/// gibt der Akku gerade, läuft der Energiesparmodus, wo endet das Laden.
|
||||
private struct BatteryTile: View {
|
||||
let model: MetricsModel
|
||||
|
||||
var body: some View {
|
||||
let battery = model.snapshot.battery
|
||||
|
||||
VStack(alignment: .leading, spacing: 8) {
|
||||
HStack(alignment: .firstTextBaseline, spacing: 6) {
|
||||
Text(MetricFormat.percent(battery?.charge ?? 0))
|
||||
.font(Onyx.Font.metric).monospacedDigit()
|
||||
.foregroundStyle(tint(battery))
|
||||
if let battery, battery.isCharging {
|
||||
Image(systemName: "bolt.fill")
|
||||
.font(.system(size: 11))
|
||||
.foregroundStyle(Onyx.Color.positive)
|
||||
}
|
||||
Spacer(minLength: 0)
|
||||
}
|
||||
|
||||
if let battery {
|
||||
if let remaining = battery.timeRemaining {
|
||||
Text(battery.isCharging ? "battery.untilFull \(duration(remaining))"
|
||||
: "battery.remaining \(duration(remaining))",
|
||||
bundle: .module)
|
||||
.font(Onyx.Font.caption)
|
||||
.foregroundStyle(Onyx.Color.textSecondary)
|
||||
} else if battery.isPluggedIn {
|
||||
Text("battery.pluggedIn", bundle: .module)
|
||||
.font(Onyx.Font.caption)
|
||||
.foregroundStyle(Onyx.Color.textSecondary)
|
||||
}
|
||||
|
||||
Spacer(minLength: 0)
|
||||
|
||||
VStack(alignment: .leading, spacing: 3) {
|
||||
if let watts = battery.watts, abs(watts) > 0.1 {
|
||||
row(watts > 0 ? "battery.charging" : "battery.draining",
|
||||
value: String(format: "%.1f W", abs(watts)))
|
||||
}
|
||||
if let limit = model.chargeControl?.chargeLimit, limit < 100 {
|
||||
row("charge.limit", value: "\(limit) %")
|
||||
}
|
||||
if let health = battery.health {
|
||||
row("battery.health", value: MetricFormat.percent(health))
|
||||
}
|
||||
if let cycles = battery.cycleCount {
|
||||
row("battery.cycles", value: "\(cycles)")
|
||||
}
|
||||
}
|
||||
|
||||
// Der Energiesparmodus erklärt, warum die Maschine langsamer
|
||||
// wirkt. Ohne diesen Hinweis sucht man den Grund woanders.
|
||||
if battery.isLowPower {
|
||||
HStack(spacing: 4) {
|
||||
Image(systemName: "leaf.fill").font(.system(size: 9))
|
||||
Text("battery.lowPower", bundle: .module).font(Onyx.Font.metricSmall)
|
||||
}
|
||||
.foregroundStyle(Onyx.Color.warning)
|
||||
}
|
||||
}
|
||||
}
|
||||
.frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .topLeading)
|
||||
}
|
||||
|
||||
private func row(_ key: String.LocalizationValue, value: String) -> some View {
|
||||
HStack(spacing: 4) {
|
||||
Text(String(localized: key, bundle: .module))
|
||||
.foregroundStyle(Onyx.Color.textTertiary)
|
||||
Spacer(minLength: 4)
|
||||
Text(value).monospacedDigit().foregroundStyle(Onyx.Color.textSecondary)
|
||||
}
|
||||
.font(Onyx.Font.metricSmall)
|
||||
}
|
||||
|
||||
private func tint(_ battery: BatteryReading?) -> Color {
|
||||
guard let battery else { return Onyx.Color.textPrimary }
|
||||
if battery.isCharging { return Onyx.Color.positive }
|
||||
return battery.charge < 0.2 ? Onyx.Color.critical : Onyx.Color.textPrimary
|
||||
}
|
||||
|
||||
private func duration(_ seconds: TimeInterval) -> String {
|
||||
let total = Int(seconds)
|
||||
return total >= 3600 ? "\(total / 3600) h \(total % 3600 / 60) min"
|
||||
: "\(total / 60) min"
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user