FuelMan/Gas Man/AddFuelLogView.swift

242 lines
9.5 KiB
Swift

//
// 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 = ""
// 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<Vehicle>
// 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)
}
HStack {
Text("Gallons:")
TextField("", text: $fuelVolume)
.keyboardType(.decimalPad)
.onChange(of: fuelVolume) { _ in updateCalculatedValues() }
}
HStack {
Text("Price/Gal:")
TextField("", text: $pricePerGalon)
.keyboardType(.decimalPad)
.onChange(of: pricePerGalon) { _ in updateCalculatedValues() }
}
HStack {
Text("Cost:")
TextField("", text: $cost)
.keyboardType(.decimalPad)
.onChange(of: cost) { _ in updateCalculatedValues() }
}
Button("Get Current Location") {
locationManager.requestLocation()
}
if !locationCoordinates.isEmpty {
Text("Coordinates: \(locationCoordinates)")
}
TextField("Location Coordinates", text: $locationCoordinates)
TextField("Location Name", text: $locationName)
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> = 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> = FuelLog.fetchRequest()
fetchRequest.sortDescriptors = [NSSortDescriptor(key: "date", ascending: false)]
fetchRequest.fetchLimit = 1
if let lastFuelLog = try? viewContext.fetch(fetchRequest).first {
// Compare against the latest fuel log's odometer.
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 vehicle relationship if a vehicle was selected:
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)")
}
}
}