// // 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 for fuel log @State private var date = Date() @State private var odometer = "" @State private var fuelVolume: String = "" @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 = "" // New Full Tank toggle; default is on (true) @State private var fullTank: Bool = true // 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 @State private var isUpdatingCalculation = false // Vehicle selection: @FetchRequest( sortDescriptors: [NSSortDescriptor(keyPath: \Vehicle.make, ascending: true)], animation: .default) private var vehicles: FetchedResults // Use the new UUID id for vehicle selection @State private var selectedVehicleID: UUID? = nil // Computed property for formatted previous odometer value private var previousOdometerString: String { previousOdometer.map { String(format: "%.0f", $0) } ?? "N/A" } var body: some View { NavigationView { Form { Section(header: Text("Fuel Log Details")) { DatePicker("Date", selection: $date, displayedComponents: [.date, .hourAndMinute]) HStack { Text("Odometer:") TextField("", text: $odometer) .keyboardType(.decimalPad) .multilineTextAlignment(.trailing) } HStack { Text("Gallons:") TextField("", text: $fuelVolume) .keyboardType(.decimalPad) .multilineTextAlignment(.trailing) .onChange(of: fuelVolume) { newValue in // Only reformat if the input is not already in the proper format. // if newValue.contains(".") { return } let pattern = #"^\d+\.\d{3}$"# if newValue.range(of: pattern, options: .regularExpression) != nil { return } let formatted = formatInput(newValue) if formatted != newValue { fuelVolume = formatted } updateCalculatedValues() } } HStack { Text("Price/Gal:") TextField("", text: $pricePerGalon) .keyboardType(.decimalPad) .multilineTextAlignment(.trailing) .onChange(of: pricePerGalon) { newValue in // if newValue.contains(".") { return } let pattern = #"^\d+\.\d{3}$"# if newValue.range(of: pattern, options: .regularExpression) != nil { return } let formatted = formatInput(newValue) if formatted != newValue { pricePerGalon = formatted } updateCalculatedValues() } } HStack { Text("Cost:") TextField("", text: $cost) .keyboardType(.decimalPad) .multilineTextAlignment(.trailing) .onChange(of: cost) { _ in updateCalculatedValues() } } // New Full Tank toggle Toggle("Full Tank", isOn: $fullTank) .toggleStyle(SwitchToggleStyle(tint: .blue)) Button("Get Current Location") { locationManager.requestLocation() } if !locationCoordinates.isEmpty { Text("Coordinates: \(locationCoordinates)") } TextField("Location Name", text: $locationName) .multilineTextAlignment(.trailing) Picker("Octane", selection: $selectedOctane) { ForEach(octaneOptions, id: \.self) { option in Text("\(option)").tag(option) } } .pickerStyle(MenuPickerStyle()) } Section(header: Text("Vehicle")) { Picker("Select Vehicle", selection: $selectedVehicleID) { ForEach(vehicles, id: \.id) { vehicle in Text("\(vehicle.year ?? "") \(vehicle.make ?? "") \(vehicle.model ?? "")") .tag(vehicle.id) } } .pickerStyle(MenuPickerStyle()) } } .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 { // Fetch the last FuelLog record to set defaults: 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) // Set default vehicle from previous record if available: if let lastVehicle = lastFuelLog.vehicle { selectedVehicleID = lastVehicle.id } } else { selectedOctane = 87 odometer = "" // If no previous fuel log, default to the first available vehicle. selectedVehicleID = vehicles.first?.id } } .alert(isPresented: $showOdometerAlert) { Alert(title: Text("Odometer Reading Error"), message: Text("Odometer reading must be greater than the previous record (\(previousOdometerString))."), dismissButton: .default(Text("OK"))) } } } 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() { guard !isUpdatingCalculation else { return } isUpdatingCalculation = true let fuel = Double(fuelVolume) let costVal = Double(cost) let price = Double(pricePerGalon) 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 } } 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 } } 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 } // Re-fetch the latest fuel log from Core Data let fetchRequest: NSFetchRequest = FuelLog.fetchRequest() fetchRequest.sortDescriptors = [NSSortDescriptor(key: "date", ascending: false)] fetchRequest.fetchLimit = 1 if let lastFuelLog = try? viewContext.fetch(fetchRequest).first { if newOdometer <= lastFuelLog.odometer { 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 // Set the fullTank property from the toggle: newLog.fullTank = fullTank if let vehicleID = selectedVehicleID, let selectedVehicle = vehicles.first(where: { $0.id == vehicleID }) { newLog.vehicle = selectedVehicle } do { try viewContext.save() print("Saved fuel log with vehicle: \(newLog.vehicle?.make ?? "none")") dismiss() } catch { let nsError = error as NSError fatalError("Unresolved error \(nsError), \(nsError.userInfo)") } } }