From 8da6987f325613388558dfbc971124ca35d66198 Mon Sep 17 00:00:00 2001 From: Stefan Lange-Hegermann Date: Fri, 7 Nov 2025 21:11:59 +0100 Subject: [PATCH] better overview design in dark mode --- Cable/Base.lproj/Localizable.strings | 4 ++ Cable/Overview/SystemOverviewView.swift | 40 ++++++++++-- Cable/Systems/SystemBillOfMaterialsView.swift | 64 +++++++++++-------- Cable/de.lproj/Localizable.strings | 4 ++ Cable/es.lproj/Localizable.strings | 4 ++ Cable/fr.lproj/Localizable.strings | 4 ++ Cable/nl.lproj/Localizable.strings | 4 ++ 7 files changed, 92 insertions(+), 32 deletions(-) diff --git a/Cable/Base.lproj/Localizable.strings b/Cable/Base.lproj/Localizable.strings index 2dc729b..def6e00 100644 --- a/Cable/Base.lproj/Localizable.strings +++ b/Cable/Base.lproj/Localizable.strings @@ -107,6 +107,10 @@ "bom.quantity.count.badge" = "%d×"; "bom.quantity.length.badge" = "%1$.1f %2$@"; "bom.quantity.length.badge.with.spec" = "%1$.1f %2$@ · %3$@"; +"bom.quantity.fuse.badge" = "%1$d× · %2$d A"; +"bom.quantity.terminal.badge" = "%1$d× · %2$@"; +"bom.quantity.cable.badge" = "%1$.1f %2$@ · %3$@"; +"bom.quantity.single.badge" = "1× • %@"; "cable.pro.privacy.label" = "Privacy"; "cable.pro.privacy.url" = "https://voltplan.app/privacy"; "cable.pro.terms.label" = "Terms"; diff --git a/Cable/Overview/SystemOverviewView.swift b/Cable/Overview/SystemOverviewView.swift index aefd457..b54f079 100644 --- a/Cable/Overview/SystemOverviewView.swift +++ b/Cable/Overview/SystemOverviewView.swift @@ -310,7 +310,7 @@ struct SystemOverviewView: View { .frame(maxWidth: .infinity, alignment: .leading) .background( RoundedRectangle(cornerRadius: 20, style: .continuous) - .fill(Color(.systemBackground)) + .fill(Color(.tertiarySystemBackground)) ) } else { Button { @@ -386,7 +386,7 @@ struct SystemOverviewView: View { .frame(maxWidth: .infinity, alignment: .leading) .background( RoundedRectangle(cornerRadius: 20, style: .continuous) - .fill(Color(.systemBackground)) + .fill(Color(.tertiarySystemBackground)) ) } .buttonStyle(.plain) @@ -477,7 +477,7 @@ struct SystemOverviewView: View { .frame(maxWidth: .infinity, alignment: .leading) .background( RoundedRectangle(cornerRadius: 20, style: .continuous) - .fill(Color(.systemBackground)) + .fill(Color(.tertiarySystemBackground)) ) } .buttonStyle(.plain) @@ -560,7 +560,7 @@ struct SystemOverviewView: View { .frame(maxWidth: .infinity, alignment: .leading) .background( RoundedRectangle(cornerRadius: 20, style: .continuous) - .fill(Color(.systemBackground)) + .fill(Color(.tertiarySystemBackground)) ) } .buttonStyle(.plain) @@ -743,21 +743,47 @@ struct SystemOverviewView: View { } private var completedBOMItemCount: Int { - settledLoads.reduce(0) { result, load in + let loadCount = settledLoads.reduce(0) { result, load in let uniqueItems = Set(load.bomCompletedItemIDs) let cappedCount = min(uniqueItems.count, Self.bomItemsPerLoad) return result + cappedCount } + + let batteryCount = settledBatteries.reduce(0) { result, battery in + let uniqueItems = Set(battery.bomCompletedItemIDs) + let cappedCount = min(uniqueItems.count, Self.bomItemsPerBattery) + return result + cappedCount + } + + let chargerCount = settledChargers.reduce(0) { result, charger in + let uniqueItems = Set(charger.bomCompletedItemIDs) + let cappedCount = min(uniqueItems.count, Self.bomItemsPerCharger) + return result + cappedCount + } + + return loadCount + batteryCount + chargerCount } private var bomItemsCount: Int { - settledLoads.isEmpty ? 0 : settledLoads.count * Self.bomItemsPerLoad + let loadItems = settledLoads.count * Self.bomItemsPerLoad + let batteryItems = settledBatteries.count * Self.bomItemsPerBattery + let chargerItems = settledChargers.count * Self.bomItemsPerCharger + let total = loadItems + batteryItems + chargerItems + return total } private var settledLoads: [SavedLoad] { loads.filter { $0.crossSection > 0 && $0.length > 0 } } + private var settledBatteries: [SavedBattery] { + batteries.filter { $0.system == system } + } + + private var settledChargers: [SavedCharger] { + chargers.filter { $0.system == system } + } + private func progressBar(progress: CGFloat?, tint: Color) -> some View { RoundedRectangle(cornerRadius: 3, style: .continuous) .fill(Color(.tertiarySystemFill)) @@ -1350,6 +1376,8 @@ struct SystemOverviewView: View { private static let fallbackGoalHours: Double = 4 private static let bomItemsPerLoad = 5 + private static let bomItemsPerBattery = 1 + private static let bomItemsPerCharger = 1 private enum BatteryWarning { case voltage(count: Int) diff --git a/Cable/Systems/SystemBillOfMaterialsView.swift b/Cable/Systems/SystemBillOfMaterialsView.swift index fd65c34..ca1788a 100644 --- a/Cable/Systems/SystemBillOfMaterialsView.swift +++ b/Cable/Systems/SystemBillOfMaterialsView.swift @@ -165,26 +165,46 @@ struct SystemBillOfMaterialsView: View { if let scale = quantityScale { guard let unit = quantifiedDetailContext else { return nil } let value = Double(quantity) / scale - if let spec = quantifiedDetailSecondaryContext { - let format = NSLocalizedString( - "bom.quantity.length.badge.with.spec", - comment: "Badge text for total cable length including cross section" - ) - return String(format: format, locale: Locale.current, value, unit, spec) - } else { - let format = NSLocalizedString( - "bom.quantity.length.badge", - comment: "Badge text for total cable length" - ) - return String(format: format, locale: Locale.current, value, unit) - } + let format = NSLocalizedString( + "bom.quantity.cable.badge", + comment: "Metric text for total cable length including cross section" + ) + return String(format: format, locale: Locale.current, value, unit, quantifiedDetailSecondaryContext ?? "") } else if quantity > 1 { + if let fuseRating = quantifiedDetailSecondaryContext, let amps = Int(fuseRating) { + let format = NSLocalizedString( + "bom.quantity.fuse.badge", + comment: "Metric text for consolidated fuses" + ) + return String(format: format, quantity, amps) + } + if let gauge = quantifiedDetailContext { + let format = NSLocalizedString( + "bom.quantity.terminal.badge", + comment: "Metric text for consolidated terminals" + ) + return String(format: format, quantity, gauge) + } let format = NSLocalizedString( "bom.quantity.count.badge", - comment: "Badge text for quantity counts" + comment: "Metric text for counted items" ) return String(format: format, quantity) } + if let fuseRating = quantifiedDetailSecondaryContext, let amps = Int(fuseRating) { + let format = NSLocalizedString( + "bom.quantity.fuse.badge", + comment: "Metric text for consolidated fuses" + ) + return String(format: format, 1, amps) + } + if let gauge = quantifiedDetailContext, !gauge.isEmpty { + let format = NSLocalizedString( + "bom.quantity.single.badge", + comment: "Metric text when quantity is one but should be explicit" + ) + return String(format: format, gauge) + } return nil } @@ -441,15 +461,6 @@ struct SystemBillOfMaterialsView: View { .foregroundColor(.accentColor) } - if item.isPrimaryComponent { - Text(String(localized: "component.fallback.name", comment: "Tag label marking an item as the component itself")) - .font(.caption2.weight(.medium)) - .foregroundColor(.accentColor) - .padding(.horizontal, 6) - .padding(.vertical, 2) - .background(Color.accentColor.opacity(0.15), in: Capsule()) - } - if shouldShowDetail(for: item) { Text(item.detail) .font(.subheadline) @@ -647,6 +658,7 @@ struct SystemBillOfMaterialsView: View { let lengthQuantity = Int((max(lengthValue, 0) * 100).rounded()) let redCableMergeKey = "cable.red::\(crossSectionLabel)::\(unitSystem.lengthUnit)" let blackCableMergeKey = "cable.black::\(crossSectionLabel)::\(unitSystem.lengthUnit)" + let fuseMergeKey = "fuse::\(fuseRating)" let items: [Item] = [ Item( @@ -712,9 +724,9 @@ struct SystemBillOfMaterialsView: View { components: [component], category: .fuses, quantity: 1, - mergeKey: nil, + mergeKey: fuseMergeKey, quantifiedDetailContext: nil, - quantifiedDetailSecondaryContext: nil, + quantifiedDetailSecondaryContext: String(fuseRating), quantityScale: nil ), Item( @@ -730,7 +742,7 @@ struct SystemBillOfMaterialsView: View { category: .accessories, quantity: terminalCount, mergeKey: "terminals::\(crossSectionLabel.lowercased())", - quantifiedDetailContext: nil, + quantifiedDetailContext: crossSectionLabel, quantifiedDetailSecondaryContext: nil, quantityScale: nil ) diff --git a/Cable/de.lproj/Localizable.strings b/Cable/de.lproj/Localizable.strings index 40b1e3e..d64fd36 100644 --- a/Cable/de.lproj/Localizable.strings +++ b/Cable/de.lproj/Localizable.strings @@ -167,6 +167,10 @@ "bom.quantity.count.badge" = "%d×"; "bom.quantity.length.badge" = "%1$.1f %2$@"; "bom.quantity.length.badge.with.spec" = "%1$.1f %2$@ · %3$@"; +"bom.quantity.fuse.badge" = "%1$d× · %2$d A"; +"bom.quantity.terminal.badge" = "%1$d× · %2$@"; +"bom.quantity.cable.badge" = "%1$.1f %2$@ · %3$@"; +"bom.quantity.single.badge" = "1× • %@"; "cable.pro.privacy.label" = "Datenschutz"; "cable.pro.privacy.url" = "https://voltplan.app/de/datenschutz"; "cable.pro.terms.label" = "Nutzungsbedingungen"; diff --git a/Cable/es.lproj/Localizable.strings b/Cable/es.lproj/Localizable.strings index 8074d1d..248ffde 100644 --- a/Cable/es.lproj/Localizable.strings +++ b/Cable/es.lproj/Localizable.strings @@ -37,6 +37,10 @@ "bom.quantity.count.badge" = "%d×"; "bom.quantity.length.badge" = "%1$.1f %2$@"; "bom.quantity.length.badge.with.spec" = "%1$.1f %2$@ · %3$@"; +"bom.quantity.fuse.badge" = "%1$d× · %2$d A"; +"bom.quantity.terminal.badge" = "%1$d× · %2$@"; +"bom.quantity.cable.badge" = "%1$.1f %2$@ · %3$@"; +"bom.quantity.single.badge" = "1× • %@"; "component.fallback.name" = "Componente"; "default.load.library" = "Carga de la biblioteca"; "default.load.name" = "Mi carga"; diff --git a/Cable/fr.lproj/Localizable.strings b/Cable/fr.lproj/Localizable.strings index a20a6fa..4af7afe 100644 --- a/Cable/fr.lproj/Localizable.strings +++ b/Cable/fr.lproj/Localizable.strings @@ -37,6 +37,10 @@ "bom.quantity.count.badge" = "%d×"; "bom.quantity.length.badge" = "%1$.1f %2$@"; "bom.quantity.length.badge.with.spec" = "%1$.1f %2$@ · %3$@"; +"bom.quantity.fuse.badge" = "%1$d× · %2$d A"; +"bom.quantity.terminal.badge" = "%1$d× · %2$@"; +"bom.quantity.cable.badge" = "%1$.1f %2$@ · %3$@"; +"bom.quantity.single.badge" = "1× • %@"; "component.fallback.name" = "Composant"; "default.load.library" = "Charge de la bibliothèque"; "default.load.name" = "Ma charge"; diff --git a/Cable/nl.lproj/Localizable.strings b/Cable/nl.lproj/Localizable.strings index f2dce3f..3d18136 100644 --- a/Cable/nl.lproj/Localizable.strings +++ b/Cable/nl.lproj/Localizable.strings @@ -37,6 +37,10 @@ "bom.quantity.count.badge" = "%d×"; "bom.quantity.length.badge" = "%1$.1f %2$@"; "bom.quantity.length.badge.with.spec" = "%1$.1f %2$@ · %3$@"; +"bom.quantity.fuse.badge" = "%1$d× · %2$d A"; +"bom.quantity.terminal.badge" = "%1$d× · %2$@"; +"bom.quantity.cable.badge" = "%1$.1f %2$@ · %3$@"; +"bom.quantity.single.badge" = "1× • %@"; "component.fallback.name" = "Component"; "default.load.library" = "Bibliotheeklast"; "default.load.name" = "Mijn last";