185 lines
8.0 KiB
Swift
185 lines
8.0 KiB
Swift
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 充电控制键")
|
||
}
|
||
}
|