Files
ggbond268--mactools/Plugins/BatteryChargeLimit/Sources/BatteryChargeLimitSettingsView.swift
T
wehub-resource-sync 1d1286fadb
Build / Build and test (push) Waiting to run
chore: import upstream snapshot with attribution
2026-07-13 13:18:38 +08:00

185 lines
8.0 KiB
Swift
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import SwiftUI
import MacToolsPluginKit
// MARK: - BatteryChargeLimitSettingsView
//
// Custom configuration view shown in the plugin's settings page. Surfaces:
// 1. A larger limit slider with explanation of the "no auto-resume" semantics
// 2. SMC capability diagnostic (which inhibit path the helper found)
// 3. A note about how this differs from macOS Optimized Battery Charging
struct BatteryChargeLimitSettingsView: View {
@ObservedObject var store: BatteryChargeLimitStore
var capabilities: BatterySMCCapabilities
var snapshot: BatterySnapshot
let localization: PluginLocalization
@State private var sliderValue: Double
init(
store: BatteryChargeLimitStore,
capabilities: BatterySMCCapabilities,
snapshot: BatterySnapshot,
localization: PluginLocalization = PluginLocalization(bundle: .main)
) {
self.store = store
self.capabilities = capabilities
self.snapshot = snapshot
self.localization = localization
_sliderValue = State(initialValue: Double(store.limitPercent))
}
var body: some View {
VStack(alignment: .leading, spacing: PluginSettingsTheme.Spacing.section) {
limitSection
behaviorSection
compatibilitySection
}
.onChange(of: store.limitPercent) { _, newValue in
sliderValue = Double(newValue)
}
}
// MARK: - Sections
private var limitSection: some View {
VStack(alignment: .leading, spacing: PluginSettingsTheme.Spacing.sectionHeaderContent) {
sectionHeader(title: localization.string("settings.limit.title", defaultValue: "充电上限"), icon: "battery.75")
VStack(alignment: .leading, spacing: PluginSettingsTheme.Spacing.controlCluster) {
HStack(alignment: .firstTextBaseline) {
Text(localization.string("settings.limit.target", defaultValue: "目标电量"))
.font(PluginSettingsTheme.Typography.rowTitle)
Spacer()
Text("\(Int(sliderValue))%")
.font(PluginSettingsTheme.Typography.monospacedValue)
.foregroundStyle(.secondary)
.frame(minWidth: 36, alignment: .trailing)
}
Slider(
value: $sliderValue,
in: Double(BatteryChargeLimits.minimumPercent)...Double(BatteryChargeLimits.maximumPercent),
step: Double(BatteryChargeLimits.percentStep),
onEditingChanged: { editing in
if !editing {
store.setLimitPercent(Int(sliderValue))
}
}
)
.controlSize(.small)
Text(localization.string("settings.limit.description", defaultValue: "达到此电量后自动停止充电。"))
.font(PluginSettingsTheme.Typography.rowDescription)
.foregroundStyle(.secondary)
}
.pluginSettingsListRowPadding(interactive: true)
.pluginSettingsCardBackground(.host)
}
}
private var behaviorSection: some View {
VStack(alignment: .leading, spacing: PluginSettingsTheme.Spacing.sectionHeaderContent) {
sectionHeader(
title: localization.string("settings.behavior.title", defaultValue: "充电行为"),
icon: "bolt.badge.checkmark"
)
VStack(alignment: .leading, spacing: PluginSettingsTheme.Spacing.rowTitleDescription) {
Text(localization.string("settings.behavior.noAutoResume.title", defaultValue: "不自动恢复充电"))
.font(PluginSettingsTheme.Typography.emphasizedRowTitle)
Text(
localization.string(
"settings.behavior.noAutoResume.description",
defaultValue: "电量低于上限时不会自动充电,需要在菜单栏点击「开始充电」才会继续。这与系统自带的「优化电池充电」不同——系统会持续微充电以贴近上限。"
)
)
.font(PluginSettingsTheme.Typography.rowDescription)
.foregroundStyle(.secondary)
.fixedSize(horizontal: false, vertical: true)
}
.frame(maxWidth: .infinity, alignment: .leading)
.pluginSettingsListRowPadding()
.pluginSettingsCardBackground(.host)
}
}
private var compatibilitySection: some View {
VStack(alignment: .leading, spacing: PluginSettingsTheme.Spacing.sectionHeaderContent) {
sectionHeader(title: localization.string("settings.compatibility.title", defaultValue: "硬件兼容"), icon: "cpu")
VStack(alignment: .leading, spacing: 0) {
compatibilityRow(
title: localization.string("settings.compatibility.controlMethod", defaultValue: "充电控制方式"),
detail: capabilityDescription
)
if capabilities.canForceDischarge {
PluginSettingsListDivider()
compatibilityRow(
title: localization.string("settings.compatibility.forceDischarge", defaultValue: "强制放电"),
detail: localization.string("settings.compatibility.forceDischarge.supported", defaultValue: "支持(CH0I")
)
}
if capabilities.isBCLMOnly {
PluginSettingsListDivider()
VStack(alignment: .leading, spacing: PluginSettingsTheme.Spacing.rowTitleDescription) {
Label(
localization.string("settings.compatibility.intelLimit.title", defaultValue: "Intel Mac 限制"),
systemImage: "exclamationmark.triangle"
)
.font(PluginSettingsTheme.Typography.emphasizedRowTitle)
.foregroundStyle(.orange)
Text(
localization.string(
"settings.compatibility.intelLimit.description",
defaultValue: "当前 Mac 仅支持 BCLM,电量低于上限时仍可能被系统自动充至上限。「不自动恢复」语义在 Intel Mac 上无法保证。"
)
)
.font(PluginSettingsTheme.Typography.rowDescription)
.foregroundStyle(.secondary)
.fixedSize(horizontal: false, vertical: true)
}
.frame(maxWidth: .infinity, alignment: .leading)
.pluginSettingsListRowPadding()
}
}
.pluginSettingsCardBackground(.host)
}
}
// MARK: - Helpers
private func sectionHeader(title: String, icon: String) -> some View {
Label(title, systemImage: icon)
.font(PluginSettingsTheme.Typography.sectionTitle)
.foregroundStyle(.secondary)
}
private func compatibilityRow(title: String, detail: String) -> some View {
HStack(alignment: .firstTextBaseline) {
Text(title)
.font(PluginSettingsTheme.Typography.emphasizedRowTitle)
Spacer()
Text(detail)
.font(PluginSettingsTheme.Typography.rowDescription)
.foregroundStyle(.secondary)
}
.pluginSettingsListRowPadding()
}
private var capabilityDescription: String {
if capabilities.hasCHTE {
return "CHTE (macOS 26+)"
}
if capabilities.hasCH0BC {
return "CH0B + CH0C (Apple Silicon)"
}
if capabilities.hasBCLM {
return "BCLM (Intel)"
}
return localization.string("settings.compatibility.noSMCKey", defaultValue: "未检测到可用的 SMC 充电控制键")
}
}