chargers in overview

This commit is contained in:
Stefan Lange-Hegermann
2025-10-23 15:27:22 +02:00
parent cd8a043c5c
commit 51d85cc352
7 changed files with 255 additions and 3 deletions

View File

@@ -7,11 +7,14 @@ struct SystemOverviewView: View {
let system: ElectricalSystem
let loads: [SavedLoad]
let batteries: [SavedBattery]
let chargers: [SavedCharger]
let onSelectLoads: () -> Void
let onSelectBatteries: () -> Void
let onSelectChargers: () -> Void
let onCreateLoad: () -> Void
let onBrowseLibrary: () -> Void
let onCreateBattery: () -> Void
let onCreateCharger: () -> Void
var body: some View {
ScrollView {
@@ -19,6 +22,7 @@ struct SystemOverviewView: View {
systemCard
loadsCard
batteriesCard
chargersCard
}
.padding(.horizontal, 16)
.padding(.vertical, 20)
@@ -70,6 +74,12 @@ struct SystemOverviewView: View {
value: "\(batteries.count)",
tint: .green
)
summaryMetric(
icon: "bolt.fill",
label: chargerCountLabel,
value: "\(chargers.count)",
tint: .orange
)
}
VStack(alignment: .leading, spacing: 12) {
@@ -85,6 +95,12 @@ struct SystemOverviewView: View {
value: "\(batteries.count)",
tint: .green
)
summaryMetric(
icon: "bolt.fill",
label: chargerCountLabel,
value: "\(chargers.count)",
tint: .orange
)
}
}
@@ -339,6 +355,94 @@ struct SystemOverviewView: View {
.buttonStyle(.plain)
}
private var chargersCard: some View {
Button(action: onSelectChargers) {
VStack(alignment: .leading, spacing: 16) {
Text(chargerSummaryTitle)
.font(.headline.weight(.semibold))
if chargers.isEmpty {
VStack(alignment: .leading, spacing: 12) {
VStack(alignment: .leading, spacing: 4) {
Text(chargerEmptyTitle)
.font(.subheadline.weight(.semibold))
Text(chargerEmptySubtitle)
.font(.footnote)
.foregroundStyle(.secondary)
.fixedSize(horizontal: false, vertical: true)
}
Button(action: onCreateCharger) {
Label(chargerEmptyCreateAction, systemImage: "plus")
.frame(maxWidth: .infinity)
}
.buttonStyle(.borderedProminent)
.controlSize(.large)
}
} else {
ViewThatFits(in: .horizontal) {
HStack(spacing: 16) {
summaryMetric(
icon: "bolt.fill",
label: chargerCountLabel,
value: "\(chargers.count)",
tint: .orange
)
summaryMetric(
icon: "powerplug",
label: chargerOutputLabel,
value: formattedChargerOutput(representativeChargerOutput),
tint: .indigo
)
summaryMetric(
icon: "gauge.medium",
label: chargerCurrentLabel,
value: formattedCurrent(totalChargerCurrent),
tint: .blue
)
summaryMetric(
icon: "bolt.circle",
label: chargerPowerLabel,
value: formattedPower(totalChargerPower),
tint: .pink
)
}
VStack(alignment: .leading, spacing: 12) {
summaryMetric(
icon: "bolt.fill",
label: chargerCountLabel,
value: "\(chargers.count)",
tint: .orange
)
summaryMetric(
icon: "powerplug",
label: chargerOutputLabel,
value: formattedChargerOutput(representativeChargerOutput),
tint: .indigo
)
summaryMetric(
icon: "gauge.medium",
label: chargerCurrentLabel,
value: formattedCurrent(totalChargerCurrent),
tint: .blue
)
}
}
}
}
.padding(.horizontal, 16)
.padding(.vertical, 18)
.frame(maxWidth: .infinity, alignment: .leading)
.background(
RoundedRectangle(cornerRadius: 20, style: .continuous)
.fill(Color(.systemBackground))
)
}
.buttonStyle(.plain)
}
private var loadStatus: LoadConfigurationStatus? {
guard !loads.isEmpty else { return nil }
let incomplete = loads.filter { load in
@@ -374,6 +478,25 @@ struct SystemOverviewView: View {
}
}
private var totalChargerCurrent: Double {
chargers.reduce(0) { result, charger in
result + max(0, charger.maxCurrentAmps)
}
}
private var totalChargerPower: Double {
chargers.reduce(0) { result, charger in
result + max(0, charger.effectivePowerWatts)
}
}
private var representativeChargerOutput: Double? {
let outputs = chargers.map { $0.outputVoltage }.filter { $0 > 0 }
guard !outputs.isEmpty else { return nil }
let total = outputs.reduce(0, +)
return total / Double(outputs.count)
}
private var batteryWarning: BatteryWarning? {
guard batteries.count > 1 else { return nil }
@@ -488,6 +611,11 @@ struct SystemOverviewView: View {
return "\(numberString) W"
}
private func formattedChargerOutput(_ value: Double?) -> String {
guard let value, value > 0 else { return "" }
return formattedValue(value, unit: "V")
}
private func formattedValue(_ value: Double, unit: String) -> String {
let numberString = Self.numberFormatter.string(from: NSNumber(value: value)) ?? String(format: "%.1f", value)
return "\(numberString) \(unit)"
@@ -659,6 +787,78 @@ struct SystemOverviewView: View {
)
}
private var chargerSummaryTitle: String {
NSLocalizedString(
"overview.chargers.header.title",
bundle: .main,
value: "Charger Overview",
comment: "Title for the chargers summary section"
)
}
private var chargerCountLabel: String {
NSLocalizedString(
"chargers.summary.metric.count",
bundle: .main,
value: "Chargers",
comment: "Label for number of chargers metric"
)
}
private var chargerOutputLabel: String {
NSLocalizedString(
"chargers.summary.metric.output",
bundle: .main,
value: "Output Voltage",
comment: "Label for representative output voltage metric"
)
}
private var chargerCurrentLabel: String {
NSLocalizedString(
"chargers.summary.metric.current",
bundle: .main,
value: "Charge Rate",
comment: "Label for total charger current metric"
)
}
private var chargerPowerLabel: String {
NSLocalizedString(
"chargers.summary.metric.power",
bundle: .main,
value: "Charge Power",
comment: "Label for total charger power metric"
)
}
private var chargerEmptyTitle: String {
NSLocalizedString(
"overview.chargers.empty.title",
bundle: .main,
value: "No chargers configured yet",
comment: "Title shown when no chargers are configured"
)
}
private var chargerEmptySubtitle: String {
NSLocalizedString(
"overview.chargers.empty.subtitle",
bundle: .main,
value: "Add shore power, DC-DC, or solar chargers to understand your charging capacity.",
comment: "Subtitle shown when no chargers are configured"
)
}
private var chargerEmptyCreateAction: String {
NSLocalizedString(
"overview.chargers.empty.create",
bundle: .main,
value: "Add Charger",
comment: "Button title to create a charger from the overview"
)
}
private var systemOverviewTitle: String {
NSLocalizedString(
"overview.system.header.title",
@@ -802,15 +1002,41 @@ struct SystemOverviewView: View {
)
]
let chargers: [SavedCharger] = [
SavedCharger(
name: "Shore Power",
inputVoltage: 230,
outputVoltage: 14.4,
maxCurrentAmps: 40,
maxPowerWatts: 580,
iconName: "powerplug",
colorName: "orange",
system: system
),
SavedCharger(
name: "DC-DC Charger",
inputVoltage: 12.8,
outputVoltage: 14.2,
maxCurrentAmps: 30,
maxPowerWatts: 0,
iconName: "bolt.circle",
colorName: "blue",
system: system
)
]
SystemOverviewView(
system: system,
loads: loads,
batteries: batteries,
chargers: chargers,
onSelectLoads: {},
onSelectBatteries: {},
onSelectChargers: {},
onCreateLoad: {},
onBrowseLibrary: {},
onCreateBattery: {}
onCreateBattery: {},
onCreateCharger: {}
)
.padding()
}
@@ -827,11 +1053,14 @@ struct SystemOverviewView: View {
system: system,
loads: [],
batteries: [],
chargers: [],
onSelectLoads: {},
onSelectBatteries: {},
onSelectChargers: {},
onCreateLoad: {},
onBrowseLibrary: {},
onCreateBattery: {}
onCreateBattery: {},
onCreateCharger: {}
)
.padding()
}