import SwiftUI import OnyxDesign import OnyxHelperProtocol /// Wer das Ladelimit setzen kann. /// /// Ein Protokoll und kein direkter Zugriff: das Limit schreibt der privilegierte /// Helfer, und der gehört zur App, nicht zu diesem Package. Die Akku-Ansicht /// soll ihn bedienen können, ohne ihn zu kennen. @MainActor public protocol ChargeLimitControlling: AnyObject { /// Das eingestellte Limit in Prozent, `nil` wenn keines gilt. var chargeLimit: Int? { get } /// Ob überhaupt geschrieben werden kann — ohne Helfer geht nichts. var canControlCharge: Bool { get } func refreshChargeLimit() func setChargeLimit(_ percent: Int) } /// Der Ladelimit-Regler, wie er im Akku-Widget und im Akku-Popover steht. /// /// Ein Akku, der dauerhaft bei 100 % am Netz hängt, altert schneller. Deshalb /// gibt es die Einstellung — und deshalb ist ihr Bereich so schmal: 80 bis /// 100 % ist, was macOS selbst anbietet und wofür gemessen wurde, wie der /// zuständige SMC-Key aussieht. Darunter wäre geraten. public struct ChargeLimitControl: View { private let control: any ChargeLimitControlling /// Im Popover ist Platz für die Erklärung, in der Kachel nicht. private let roomy: Bool @State private var pending: Double? public init(control: any ChargeLimitControlling, roomy: Bool) { self.control = control self.roomy = roomy } private var current: Double { pending ?? Double(control.chargeLimit ?? ChargeSafety.noLimit) } private var isOff: Bool { current >= Double(ChargeSafety.noLimit) } public var body: some View { VStack(alignment: .leading, spacing: 5) { HStack(spacing: 6) { Text("charge.limit", bundle: .module) .font(Onyx.Font.caption) .foregroundStyle(Onyx.Color.textSecondary) Spacer(minLength: 4) Group { // Hundert Prozent ist kein Limit — und soll auch nicht so // heißen, sonst sucht man den Ausschalter. if isOff { Text("charge.limit.off", bundle: .module) } else { Text(verbatim: "\(Int(current)) %") } } .font(Onyx.Font.metricSmall).monospacedDigit() .foregroundStyle(isOff ? Onyx.Color.textTertiary : Onyx.Color.accent) } Slider(value: Binding(get: { current }, set: { pending = ($0 / 5).rounded() * 5 }), in: Double(ChargeSafety.minimumPercent)...Double(ChargeSafety.maximumPercent), onEditingChanged: { editing in // Erst beim Loslassen schreiben. Bei jedem Zwischenwert // in den Ladepfad zu schreiben wäre ein Dutzend // Schreibvorgänge für eine einzige Entscheidung. guard !editing, let pending else { return } control.setChargeLimit(Int(pending)) self.pending = nil }) .controlSize(.mini) .disabled(!control.canControlCharge) if !control.canControlCharge { Text("charge.limit.needsHelper", bundle: .module) .font(Onyx.Font.metricSmall) .foregroundStyle(Onyx.Color.textTertiary) .fixedSize(horizontal: false, vertical: true) } else if roomy { Text("charge.limit.hint", bundle: .module) .font(Onyx.Font.metricSmall) .foregroundStyle(Onyx.Color.textTertiary) .fixedSize(horizontal: false, vertical: true) } } .onAppear { control.refreshChargeLimit() } } }