1 contributor
//
// PowerbankDetailView.swift
// USB Meter
//
import SwiftUI
struct PowerbankDetailView: View {
@EnvironmentObject private var appData: AppData
@Environment(\.dismiss) private var dismiss
let powerbankID: UUID
@State private var isEditing = false
@State private var pendingDeletion = false
private var powerbank: PowerbankSummary? {
appData.powerbankSummaries.first { $0.id == powerbankID }
}
var body: some View {
Group {
if let powerbank {
content(for: powerbank)
} else {
Text("Powerbank not found.")
.foregroundColor(.secondary)
}
}
.navigationTitle(powerbank?.name ?? "Powerbank")
.navigationBarTitleDisplayMode(.inline)
.toolbar {
ToolbarItem(placement: .navigationBarTrailing) {
Menu {
Button("Edit") { isEditing = true }
Button("Delete", role: .destructive) { pendingDeletion = true }
} label: {
Image(systemName: "ellipsis.circle")
}
.disabled(powerbank == nil)
}
}
.sheet(isPresented: $isEditing) {
if let powerbank {
PowerbankEditorSheetView(powerbank: powerbank)
.environmentObject(appData)
}
}
.confirmationDialog(
"Delete \(powerbank?.name ?? "powerbank")?",
isPresented: $pendingDeletion,
titleVisibility: .visible
) {
Button("Delete", role: .destructive) {
if let powerbank {
_ = appData.deletePowerbank(id: powerbank.id)
dismiss()
}
}
Button("Cancel", role: .cancel) {}
} message: {
Text("This will permanently remove the powerbank and any sessions where it is the subject. Sessions where it was the source will keep their data, with the source link cleared.")
}
}
@ViewBuilder
private func content(for powerbank: PowerbankSummary) -> some View {
Form {
Section(header: Text("Identity")) {
row("Name", value: powerbank.name)
HStack {
Text("QR identifier")
Spacer()
Text(powerbank.qrIdentifier)
.font(.caption.monospaced())
.foregroundColor(.secondary)
.textSelection(.enabled)
}
}
Section(header: Text("Battery")) {
row("Reporting", value: powerbank.batteryLevelReporting.title)
if powerbank.batteryLevelReporting == .bars {
row("Bars resolution", value: "\(powerbank.batteryBarsCount)")
}
if let estimatedCapacityWh = powerbank.estimatedBatteryCapacityWh {
row("Capacity (charged)", value: "\(estimatedCapacityWh.format(decimalDigits: 2)) Wh")
}
if let apparentCapacityWh = powerbank.apparentCapacityWh {
row("Apparent capacity (delivered)", value: "\(apparentCapacityWh.format(decimalDigits: 2)) Wh")
}
if let efficiency = powerbank.sourceEfficiencyFactor {
row("Efficiency", value: "\((efficiency * 100).format(decimalDigits: 1))%")
}
}
Section(header: Text("Source statistics")) {
if powerbank.sessionsAsSource.isEmpty {
Text("No discharge sessions recorded yet.")
.font(.caption)
.foregroundColor(.secondary)
} else {
row("Sessions as source", value: "\(powerbank.sessionsAsSource.count)")
row("Total Wh delivered", value: "\(powerbank.totalDeliveredEnergyWh.format(decimalDigits: 2)) Wh")
if let maxPower = powerbank.sourceMaximumPowerWatts {
row("Max power", value: "\(maxPower.format(decimalDigits: 2)) W")
}
if powerbank.sourceVoltageMaxCurrents.isEmpty == false {
VStack(alignment: .leading, spacing: 4) {
Text("Voltage profile")
.font(.subheadline.weight(.semibold))
ForEach(powerbank.sourceVoltageMaxCurrents.keys.sorted(), id: \.self) { voltage in
let maxAmps = powerbank.sourceVoltageMaxCurrents[voltage] ?? 0
HStack {
Text(String(format: "%.1f V", voltage))
.font(.caption.monospaced())
Spacer()
Text("max \(maxAmps.format(decimalDigits: 2)) A")
.font(.caption)
.foregroundColor(.secondary)
}
}
}
} else if powerbank.sourceObservedVoltageSelections.isEmpty == false {
row(
"Observed voltages",
value: powerbank.sourceObservedVoltageSelections
.map { String(format: "%.1fV", $0) }
.joined(separator: ", ")
)
}
}
}
Section(header: Text("Charging history")) {
if powerbank.sessionsAsSubject.isEmpty {
Text("Not charged yet.")
.font(.caption)
.foregroundColor(.secondary)
} else {
row("Charge sessions", value: "\(powerbank.sessionsAsSubject.count)")
row("Total Wh received", value: "\(powerbank.totalReceivedEnergyWh.format(decimalDigits: 2)) Wh")
}
}
if let notes = powerbank.notes, !notes.isEmpty {
Section(header: Text("Notes")) {
Text(notes)
}
}
}
}
private func row(_ label: String, value: String) -> some View {
HStack {
Text(label)
Spacer()
Text(value)
.foregroundColor(.secondary)
}
}
}