// // AddFuelLogView.swift // Gas Man // // Created by Kameron Kenny on 3/17/25. // import SwiftUI import CoreData struct AddFuelLogView: View { @Environment(\.managedObjectContext) private var viewContext @Environment(\.dismiss) var dismiss // Form fields @State private var date = Date() @State private var odometer = "" @State private var fuelVolume = "" @State private var cost = "" @State private var locationCoordinates = "" @State private var locationName = "" @State private var selectedOctane: Int = 87 // Default or from previous record @State private var pricePerGalon = "" // Allowed octane options let octaneOptions = [87, 89, 91, 92, 93, 95] // Location manager for automatic location updates @StateObject private var locationManager = LocationManager() // For tracking the previous odometer reading @State private var previousOdometer: Double? = nil @State private var showOdometerAlert = false // Flag to avoid update loops in our calculation logic @State private var isUpdatingCalculation = false // Computed property for formatted previous odometer value private var previousOdometerString: String { previousOdometer.map { String(format: "%.0f", $0) } ?? "N/A" } // Computed property for the odometer alert var odometerAlert: Alert { let messageString = "Odometer reading must be greater than the previous record (\(previousOdometerString))." return Alert( title: Text("Odometer Reading Error"), message: Text(messageString), dismissButton: .default(Text("OK")) ) } var body: some View { NavigationView { Form { fuelLogDetailsSection } .navigationTitle("Add Fuel Log") .toolbar { ToolbarItem(placement: .navigationBarTrailing) { Button("Save") { saveFuelLog() } } ToolbarItem(placement: .navigationBarLeading) { Button("Cancel") { dismiss() } } } .onChange(of: locationManager.location) { newLocation in if let loc = newLocation { locationCoordinates = "\(loc.coordinate.latitude), \(loc.coordinate.longitude)" } } .onChange(of: locationManager.placemark) { newPlacemark in if let placemark = newPlacemark { locationName = placemark.name ?? "" } } .onAppear(perform: loadPreviousData) .alert(isPresented: $showOdometerAlert) { odometerAlert } } } // MARK: - Subviews private var fuelLogDetailsSection: some View { Section(header: Text("Fuel Log Details")) { DatePicker("Date", selection: $date, displayedComponents: [.date, .hourAndMinute]) // Odometer field HStack(spacing: 4) { Text("Odometer: ") .font(.caption) .foregroundColor(.secondary) TextField("", text: $odometer) .keyboardType(.decimalPad) .textFieldStyle(RoundedBorderTextFieldStyle()) } // Fuel Volume field HStack(spacing: 4) { Text("Gallons: ") .font(.caption) .foregroundColor(.secondary) TextField("", text: $fuelVolume) .keyboardType(.decimalPad) .textFieldStyle(RoundedBorderTextFieldStyle()) .onChange(of: fuelVolume) { _ in updateCalculatedValues() } } // Price per Gallon field HStack(spacing: 4) { Text("Price/Gal: $") .font(.caption) .foregroundColor(.secondary) TextField("", text: $pricePerGalon) .keyboardType(.decimalPad) .textFieldStyle(RoundedBorderTextFieldStyle()) .onChange(of: pricePerGalon) { _ in updateCalculatedValues() } } // Cost field HStack(spacing: 4) { Text("Cost: $") .font(.caption) .foregroundColor(.secondary) TextField("", text: $cost) .keyboardType(.decimalPad) .textFieldStyle(RoundedBorderTextFieldStyle()) .onChange(of: cost) { _ in updateCalculatedValues() } } Button("Get Current Location") { locationManager.requestLocation() } if !locationCoordinates.isEmpty { Text("Coordinates: \(locationCoordinates)") .font(.caption) .foregroundColor(.secondary) } TextField("Location Coordinates", text: $locationCoordinates) HStack(spacing: 4) { Text("Location Name:") .font(.caption) .foregroundColor(.secondary) TextField("", text: $locationName) } Picker("Octane", selection: $selectedOctane) { ForEach(octaneOptions, id: \.self) { option in Text("\(option)").tag(option) } } .pickerStyle(MenuPickerStyle()) } } // MARK: - Helper Methods private func loadPreviousData() { let fetchRequest: NSFetchRequest = FuelLog.fetchRequest() fetchRequest.sortDescriptors = [NSSortDescriptor(key: "date", ascending: false)] fetchRequest.fetchLimit = 1 if let lastFuelLog = try? viewContext.fetch(fetchRequest).first { selectedOctane = Int(lastFuelLog.octane) previousOdometer = lastFuelLog.odometer odometer = String(format: "%.0f", lastFuelLog.odometer) } else { selectedOctane = 87 odometer = "" } } private func roundToThree(_ value: Double) -> Double { return (value * 1000).rounded() / 1000 } private func roundToTwo(_ value: Double) -> Double { return (value * 100).rounded() / 100 } private func updateCalculatedValues() { // Prevent recursive updates. guard !isUpdatingCalculation else { return } isUpdatingCalculation = true let fuel = Double(fuelVolume) let costVal = Double(cost) let price = Double(pricePerGalon) // 1. If fuelVolume and pricePerGalon are provided, calculate cost. if let f = fuel, let p = price, f > 0 { let computedCost = roundToTwo(f * p) let computedCostStr = String(format: "%.2f", computedCost) if cost != computedCostStr { cost = computedCostStr } } // 2. If fuelVolume and cost are provided, and pricePerGalon is empty, calculate pricePerGalon. else if let f = fuel, let c = costVal, f > 0, pricePerGalon.trimmingCharacters(in: .whitespaces).isEmpty { let computedPrice = roundToThree(c / f) let computedPriceStr = String(format: "%.3f", computedPrice) if pricePerGalon != computedPriceStr { pricePerGalon = computedPriceStr } } // 3. If pricePerGalon and cost are provided, and fuelVolume is empty, calculate fuelVolume. else if let p = price, let c = costVal, p > 0, fuelVolume.trimmingCharacters(in: .whitespaces).isEmpty { let computedFuel = roundToThree(c / p) let computedFuelStr = String(format: "%.3f", computedFuel) if fuelVolume != computedFuelStr { fuelVolume = computedFuelStr } } isUpdatingCalculation = false } private func saveFuelLog() { guard let newOdometer = Double(odometer) else { return } // Validate that the new odometer reading is greater than the previous record if let previous = previousOdometer, newOdometer <= previous { showOdometerAlert = true return } let newLog = FuelLog(context: viewContext) newLog.id = UUID() newLog.date = date newLog.odometer = newOdometer newLog.fuelVolume = Double(fuelVolume) ?? 0 newLog.cost = Double(cost) ?? 0 newLog.locationCoordinates = locationCoordinates newLog.locationName = locationName newLog.octane = Int16(selectedOctane) newLog.pricePerGalon = Double(pricePerGalon) ?? 0 do { try viewContext.save() dismiss() } catch { let nsError = error as NSError fatalError("Unresolved error \(nsError), \(nsError.userInfo)") } } }