项目文件夹

文件
wehub-resource-sync 1d1286fadb
Build / Build and test (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 13:18:38 +08:00

185 行
8.0 KiB
Swift

此文件含有模棱两可的 Unicode 字符
此文件含有可能会与其他字符混淆的 Unicode 字符。 如果您是想特意这样的,可以安全地忽略该警告。 使用 Escape 按钮显示他们。
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 充电控制键")
}
}