chargers in overview
This commit is contained in:
@@ -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()
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user