chargers in overview
This commit is contained in:
@@ -108,6 +108,10 @@
|
||||
"battery.onboarding.title" = "Add your first battery";
|
||||
"battery.onboarding.subtitle" = "Track your bank's capacity and chemistry to keep runtime expectations in check.";
|
||||
"battery.bank.badge.voltage" = "Voltage";
|
||||
"overview.chargers.header.title" = "Charger Overview";
|
||||
"overview.chargers.empty.title" = "No chargers configured yet";
|
||||
"overview.chargers.empty.subtitle" = "Add shore power, DC-DC, or solar chargers to understand your charging capacity.";
|
||||
"overview.chargers.empty.create" = "Add Charger";
|
||||
"battery.bank.badge.capacity" = "Capacity";
|
||||
"battery.bank.badge.energy" = "Energy";
|
||||
"battery.bank.banner.voltage" = "Voltage mismatch detected";
|
||||
|
||||
@@ -282,11 +282,14 @@ struct LoadsView: View {
|
||||
system: system,
|
||||
loads: savedLoads,
|
||||
batteries: savedBatteries,
|
||||
chargers: savedChargers,
|
||||
onSelectLoads: { selectedComponentTab = .components },
|
||||
onSelectBatteries: { selectedComponentTab = .batteries },
|
||||
onSelectChargers: { selectedComponentTab = .chargers },
|
||||
onCreateLoad: { createNewLoad() },
|
||||
onBrowseLibrary: { showingComponentLibrary = true },
|
||||
onCreateBattery: { startBatteryConfiguration() }
|
||||
onCreateBattery: { startBatteryConfiguration() },
|
||||
onCreateCharger: { startChargerConfiguration() }
|
||||
)
|
||||
.accessibilityIdentifier("system-overview")
|
||||
}
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
|
||||
@@ -176,6 +176,10 @@
|
||||
"battery.onboarding.title" = "Füge deine erste Batterie hinzu";
|
||||
"battery.onboarding.subtitle" = "Behalte Kapazität und Chemie deiner Batterien im Blick, um die Laufzeit im Griff zu behalten.";
|
||||
"battery.bank.badge.voltage" = "Spannung";
|
||||
"overview.chargers.header.title" = "Ladegeräte";
|
||||
"overview.chargers.empty.title" = "Noch keine Ladegeräte konfiguriert";
|
||||
"overview.chargers.empty.subtitle" = "Füge Landstrom-, DC-DC- oder Solarladegeräte hinzu, um deine Ladeleistung zu verstehen.";
|
||||
"overview.chargers.empty.create" = "Ladegerät hinzufügen";
|
||||
"battery.bank.badge.capacity" = "Kapazität";
|
||||
"battery.bank.badge.energy" = "Energie";
|
||||
"battery.bank.banner.voltage" = "Spannungsabweichung erkannt";
|
||||
|
||||
@@ -175,6 +175,10 @@
|
||||
"battery.onboarding.title" = "Añade tu primera batería";
|
||||
"battery.onboarding.subtitle" = "Controla la capacidad y la química del banco para mantener tus tiempos de autonomía bajo control.";
|
||||
"battery.bank.badge.voltage" = "Voltaje";
|
||||
"overview.chargers.header.title" = "Resumen de cargadores";
|
||||
"overview.chargers.empty.title" = "Aún no hay cargadores configurados";
|
||||
"overview.chargers.empty.subtitle" = "Añade cargadores de toma de puerto, DC-DC o solares para conocer tu capacidad de carga.";
|
||||
"overview.chargers.empty.create" = "Agregar cargador";
|
||||
"battery.bank.badge.capacity" = "Capacidad";
|
||||
"battery.bank.badge.energy" = "Energía";
|
||||
"battery.bank.banner.voltage" = "Se detectó un desajuste de voltaje";
|
||||
|
||||
@@ -175,6 +175,10 @@
|
||||
"battery.onboarding.title" = "Ajoutez votre première batterie";
|
||||
"battery.onboarding.subtitle" = "Suivez la capacité et la chimie de votre banc pour mieux maîtriser l'autonomie.";
|
||||
"battery.bank.badge.voltage" = "Tension";
|
||||
"overview.chargers.header.title" = "Vue d’ensemble des chargeurs";
|
||||
"overview.chargers.empty.title" = "Aucun chargeur configuré pour l’instant";
|
||||
"overview.chargers.empty.subtitle" = "Ajoutez des chargeurs secteur, DC-DC ou solaires pour comprendre votre capacité de charge.";
|
||||
"overview.chargers.empty.create" = "Ajouter un chargeur";
|
||||
"battery.bank.badge.capacity" = "Capacité";
|
||||
"battery.bank.badge.energy" = "Énergie";
|
||||
"battery.bank.banner.voltage" = "Écart de tension détecté";
|
||||
|
||||
@@ -175,6 +175,10 @@
|
||||
"battery.onboarding.title" = "Voeg je eerste accu toe";
|
||||
"battery.onboarding.subtitle" = "Houd capaciteit en chemie van de accubank in de gaten om de gebruiksduur te beheersen.";
|
||||
"battery.bank.badge.voltage" = "Spanning";
|
||||
"overview.chargers.header.title" = "Overzicht van laders";
|
||||
"overview.chargers.empty.title" = "Nog geen laders geconfigureerd";
|
||||
"overview.chargers.empty.subtitle" = "Voeg walstroom-, DC-DC- of zonneladers toe om je laadvermogen te begrijpen.";
|
||||
"overview.chargers.empty.create" = "Lader toevoegen";
|
||||
"battery.bank.badge.capacity" = "Capaciteit";
|
||||
"battery.bank.badge.energy" = "Energie";
|
||||
"battery.bank.banner.voltage" = "Spanningsafwijking gedetecteerd";
|
||||
|
||||
Reference in New Issue
Block a user