USB-Meter / USB Meter / Views / Powerbanks / PowerbankDetailView.swift
1 contributor
162 lines | 6.604kb
//
//  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)
        }
    }
}