USB-Meter / USB Meter / Views / ChargedDevices / ChargeSessionCompletionSheetView.swift
1 contributor
157 lines | 5.705kb
//
//  ChargeSessionCompletionSheetView.swift
//  USB Meter
//

import SwiftUI

struct ChargeSessionCompletionSheetView: View {
    private enum FinalCheckpoint: String, CaseIterable, Identifiable {
        case full
        case skip
        case custom

        var id: String { rawValue }

        var label: String {
            switch self {
            case .full:   return "Full"
            case .skip:   return "Skip"
            case .custom: return "Other %"
            }
        }
    }

    @EnvironmentObject private var appData: AppData
    @Environment(\.dismiss) private var dismiss

    let sessionID: UUID
    let title: String
    let confirmTitle: String
    let explanation: String

    @State private var batteryPercent = ""
    @State private var finalCheckpoint: FinalCheckpoint = .skip

    var body: some View {
        NavigationView {
            Form {
                Section(
                    header: ContextInfoHeader(
                        title: "Final Checkpoint",
                        message: explanation
                    )
                ) {
                    Picker("Final Battery", selection: $finalCheckpoint) {
                        ForEach(FinalCheckpoint.allCases) { mode in
                            Text(mode.label).tag(mode)
                        }
                    }
                    .pickerStyle(.segmented)

                    if finalCheckpoint == .custom {
                        TextField("Battery %", text: $batteryPercent)
                            .keyboardType(.decimalPad)
                    }
                }

                Section {
                    if let refusalReason {
                        Label(refusalReason, systemImage: "exclamationmark.triangle.fill")
                            .font(.footnote)
                            .foregroundColor(.red)

                        Button(role: .destructive) {
                            _ = appData.deleteChargeSession(sessionID: sessionID)
                            dismiss()
                        } label: {
                            Label("Discard Session", systemImage: "trash")
                        }
                    } else if let customCheckpointWarning {
                        Label(customCheckpointWarning, systemImage: "exclamationmark.triangle.fill")
                            .font(.footnote)
                            .foregroundColor(.orange)
                    } else if let sessionWarning {
                        Text(sessionWarning)
                            .font(.footnote)
                            .foregroundColor(.orange)
                    } else if resolvedFinalBatteryPercent == 100 {
                        Text("A final checkpoint at 100% lets the app learn the stop current for this exact charging type when the session data is reliable.")
                            .font(.footnote)
                            .foregroundColor(.secondary)
                    }
                }
            }
            .navigationTitle(title)
            .navigationBarTitleDisplayMode(.inline)
            .toolbar {
                ToolbarItem(placement: .cancellationAction) {
                    Button("Cancel") {
                        dismiss()
                    }
                }
                ToolbarItem(placement: .confirmationAction) {
                    Button(confirmTitle) {
                        guard canSave else { return }
                        if appData.stopChargeSession(sessionID: sessionID, finalBatteryPercent: resolvedFinalBatteryPercent) {
                            dismiss()
                        }
                    }
                    .disabled(!canSave)
                    .opacity(canSave ? 1 : 0.45)
                }
            }
        }
        .navigationViewStyle(StackNavigationViewStyle())
    }

    private var session: ChargeSessionSummary? {
        appData.chargedDevices
            .flatMap(\.sessions)
            .first(where: { $0.id == sessionID })
    }

    private var canSave: Bool {
        session?.hasSavableChargeData == true
    }

    private var refusalReason: String? {
        canSave ? nil : "This session has no charging data to save. Discard it instead."
    }

    private var customCheckpointWarning: String? {
        guard finalCheckpoint == .custom,
              batteryPercent.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty == false,
              parsedBatteryPercent == nil else {
            return nil
        }
        return "Final battery percentage must be between 0 and 100. Save will close the session without a final checkpoint."
    }

    private var parsedBatteryPercent: Double? {
        let normalized = batteryPercent
            .trimmingCharacters(in: .whitespacesAndNewlines)
            .replacingOccurrences(of: ",", with: ".")
        guard let value = Double(normalized), value >= 0, value <= 100 else { return nil }
        return value
    }

    private var resolvedFinalBatteryPercent: Double? {
        switch finalCheckpoint {
        case .full:   return 100
        case .skip:   return nil
        case .custom: return parsedBatteryPercent
        }
    }

    private var sessionWarning: String? {
        guard let session,
              session.chargingTransportMode == .wireless,
              let chargerID = session.chargerID,
              let charger = appData.chargedDeviceSummary(id: chargerID),
              charger.chargerIdleCurrentAmps == nil else {
            return nil
        }
        return "This charger has no idle-current measurement, so the final checkpoint will stop the session but will not learn a wireless stop threshold yet."
    }
}