better overview design in dark mode

This commit is contained in:
Stefan Lange-Hegermann
2025-11-07 21:11:59 +01:00
parent b11d627fdb
commit 8da6987f32
7 changed files with 92 additions and 32 deletions

View File

@@ -107,6 +107,10 @@
"bom.quantity.count.badge" = "%d×"; "bom.quantity.count.badge" = "%d×";
"bom.quantity.length.badge" = "%1$.1f %2$@"; "bom.quantity.length.badge" = "%1$.1f %2$@";
"bom.quantity.length.badge.with.spec" = "%1$.1f %2$@ · %3$@"; "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.label" = "Privacy";
"cable.pro.privacy.url" = "https://voltplan.app/privacy"; "cable.pro.privacy.url" = "https://voltplan.app/privacy";
"cable.pro.terms.label" = "Terms"; "cable.pro.terms.label" = "Terms";

View File

@@ -310,7 +310,7 @@ struct SystemOverviewView: View {
.frame(maxWidth: .infinity, alignment: .leading) .frame(maxWidth: .infinity, alignment: .leading)
.background( .background(
RoundedRectangle(cornerRadius: 20, style: .continuous) RoundedRectangle(cornerRadius: 20, style: .continuous)
.fill(Color(.systemBackground)) .fill(Color(.tertiarySystemBackground))
) )
} else { } else {
Button { Button {
@@ -386,7 +386,7 @@ struct SystemOverviewView: View {
.frame(maxWidth: .infinity, alignment: .leading) .frame(maxWidth: .infinity, alignment: .leading)
.background( .background(
RoundedRectangle(cornerRadius: 20, style: .continuous) RoundedRectangle(cornerRadius: 20, style: .continuous)
.fill(Color(.systemBackground)) .fill(Color(.tertiarySystemBackground))
) )
} }
.buttonStyle(.plain) .buttonStyle(.plain)
@@ -477,7 +477,7 @@ struct SystemOverviewView: View {
.frame(maxWidth: .infinity, alignment: .leading) .frame(maxWidth: .infinity, alignment: .leading)
.background( .background(
RoundedRectangle(cornerRadius: 20, style: .continuous) RoundedRectangle(cornerRadius: 20, style: .continuous)
.fill(Color(.systemBackground)) .fill(Color(.tertiarySystemBackground))
) )
} }
.buttonStyle(.plain) .buttonStyle(.plain)
@@ -560,7 +560,7 @@ struct SystemOverviewView: View {
.frame(maxWidth: .infinity, alignment: .leading) .frame(maxWidth: .infinity, alignment: .leading)
.background( .background(
RoundedRectangle(cornerRadius: 20, style: .continuous) RoundedRectangle(cornerRadius: 20, style: .continuous)
.fill(Color(.systemBackground)) .fill(Color(.tertiarySystemBackground))
) )
} }
.buttonStyle(.plain) .buttonStyle(.plain)
@@ -743,21 +743,47 @@ struct SystemOverviewView: View {
} }
private var completedBOMItemCount: Int { private var completedBOMItemCount: Int {
settledLoads.reduce(0) { result, load in let loadCount = settledLoads.reduce(0) { result, load in
let uniqueItems = Set(load.bomCompletedItemIDs) let uniqueItems = Set(load.bomCompletedItemIDs)
let cappedCount = min(uniqueItems.count, Self.bomItemsPerLoad) let cappedCount = min(uniqueItems.count, Self.bomItemsPerLoad)
return result + cappedCount 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 { 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] { private var settledLoads: [SavedLoad] {
loads.filter { $0.crossSection > 0 && $0.length > 0 } 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 { private func progressBar(progress: CGFloat?, tint: Color) -> some View {
RoundedRectangle(cornerRadius: 3, style: .continuous) RoundedRectangle(cornerRadius: 3, style: .continuous)
.fill(Color(.tertiarySystemFill)) .fill(Color(.tertiarySystemFill))
@@ -1350,6 +1376,8 @@ struct SystemOverviewView: View {
private static let fallbackGoalHours: Double = 4 private static let fallbackGoalHours: Double = 4
private static let bomItemsPerLoad = 5 private static let bomItemsPerLoad = 5
private static let bomItemsPerBattery = 1
private static let bomItemsPerCharger = 1
private enum BatteryWarning { private enum BatteryWarning {
case voltage(count: Int) case voltage(count: Int)

View File

@@ -165,26 +165,46 @@ struct SystemBillOfMaterialsView: View {
if let scale = quantityScale { if let scale = quantityScale {
guard let unit = quantifiedDetailContext else { return nil } guard let unit = quantifiedDetailContext else { return nil }
let value = Double(quantity) / scale let value = Double(quantity) / scale
if let spec = quantifiedDetailSecondaryContext { let format = NSLocalizedString(
let format = NSLocalizedString( "bom.quantity.cable.badge",
"bom.quantity.length.badge.with.spec", comment: "Metric text for total cable length including cross section"
comment: "Badge text for total cable length including cross section" )
) return String(format: format, locale: Locale.current, value, unit, quantifiedDetailSecondaryContext ?? "")
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)
}
} else if quantity > 1 { } 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( let format = NSLocalizedString(
"bom.quantity.count.badge", "bom.quantity.count.badge",
comment: "Badge text for quantity counts" comment: "Metric text for counted items"
) )
return String(format: format, quantity) 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 return nil
} }
@@ -441,15 +461,6 @@ struct SystemBillOfMaterialsView: View {
.foregroundColor(.accentColor) .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) { if shouldShowDetail(for: item) {
Text(item.detail) Text(item.detail)
.font(.subheadline) .font(.subheadline)
@@ -647,6 +658,7 @@ struct SystemBillOfMaterialsView: View {
let lengthQuantity = Int((max(lengthValue, 0) * 100).rounded()) let lengthQuantity = Int((max(lengthValue, 0) * 100).rounded())
let redCableMergeKey = "cable.red::\(crossSectionLabel)::\(unitSystem.lengthUnit)" let redCableMergeKey = "cable.red::\(crossSectionLabel)::\(unitSystem.lengthUnit)"
let blackCableMergeKey = "cable.black::\(crossSectionLabel)::\(unitSystem.lengthUnit)" let blackCableMergeKey = "cable.black::\(crossSectionLabel)::\(unitSystem.lengthUnit)"
let fuseMergeKey = "fuse::\(fuseRating)"
let items: [Item] = [ let items: [Item] = [
Item( Item(
@@ -712,9 +724,9 @@ struct SystemBillOfMaterialsView: View {
components: [component], components: [component],
category: .fuses, category: .fuses,
quantity: 1, quantity: 1,
mergeKey: nil, mergeKey: fuseMergeKey,
quantifiedDetailContext: nil, quantifiedDetailContext: nil,
quantifiedDetailSecondaryContext: nil, quantifiedDetailSecondaryContext: String(fuseRating),
quantityScale: nil quantityScale: nil
), ),
Item( Item(
@@ -730,7 +742,7 @@ struct SystemBillOfMaterialsView: View {
category: .accessories, category: .accessories,
quantity: terminalCount, quantity: terminalCount,
mergeKey: "terminals::\(crossSectionLabel.lowercased())", mergeKey: "terminals::\(crossSectionLabel.lowercased())",
quantifiedDetailContext: nil, quantifiedDetailContext: crossSectionLabel,
quantifiedDetailSecondaryContext: nil, quantifiedDetailSecondaryContext: nil,
quantityScale: nil quantityScale: nil
) )

View File

@@ -167,6 +167,10 @@
"bom.quantity.count.badge" = "%d×"; "bom.quantity.count.badge" = "%d×";
"bom.quantity.length.badge" = "%1$.1f %2$@"; "bom.quantity.length.badge" = "%1$.1f %2$@";
"bom.quantity.length.badge.with.spec" = "%1$.1f %2$@ · %3$@"; "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.label" = "Datenschutz";
"cable.pro.privacy.url" = "https://voltplan.app/de/datenschutz"; "cable.pro.privacy.url" = "https://voltplan.app/de/datenschutz";
"cable.pro.terms.label" = "Nutzungsbedingungen"; "cable.pro.terms.label" = "Nutzungsbedingungen";

View File

@@ -37,6 +37,10 @@
"bom.quantity.count.badge" = "%d×"; "bom.quantity.count.badge" = "%d×";
"bom.quantity.length.badge" = "%1$.1f %2$@"; "bom.quantity.length.badge" = "%1$.1f %2$@";
"bom.quantity.length.badge.with.spec" = "%1$.1f %2$@ · %3$@"; "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"; "component.fallback.name" = "Componente";
"default.load.library" = "Carga de la biblioteca"; "default.load.library" = "Carga de la biblioteca";
"default.load.name" = "Mi carga"; "default.load.name" = "Mi carga";

View File

@@ -37,6 +37,10 @@
"bom.quantity.count.badge" = "%d×"; "bom.quantity.count.badge" = "%d×";
"bom.quantity.length.badge" = "%1$.1f %2$@"; "bom.quantity.length.badge" = "%1$.1f %2$@";
"bom.quantity.length.badge.with.spec" = "%1$.1f %2$@ · %3$@"; "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"; "component.fallback.name" = "Composant";
"default.load.library" = "Charge de la bibliothèque"; "default.load.library" = "Charge de la bibliothèque";
"default.load.name" = "Ma charge"; "default.load.name" = "Ma charge";

View File

@@ -37,6 +37,10 @@
"bom.quantity.count.badge" = "%d×"; "bom.quantity.count.badge" = "%d×";
"bom.quantity.length.badge" = "%1$.1f %2$@"; "bom.quantity.length.badge" = "%1$.1f %2$@";
"bom.quantity.length.badge.with.spec" = "%1$.1f %2$@ · %3$@"; "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"; "component.fallback.name" = "Component";
"default.load.library" = "Bibliotheeklast"; "default.load.library" = "Bibliotheeklast";
"default.load.name" = "Mijn last"; "default.load.name" = "Mijn last";