add ability to edit fuel log records
This commit is contained in:
parent
28c676fc76
commit
42188066f5
|
@ -289,7 +289,7 @@
|
||||||
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
|
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
|
||||||
CODE_SIGN_ENTITLEMENTS = "Gas Man/Gas_Man.entitlements";
|
CODE_SIGN_ENTITLEMENTS = "Gas Man/Gas_Man.entitlements";
|
||||||
CODE_SIGN_STYLE = Automatic;
|
CODE_SIGN_STYLE = Automatic;
|
||||||
CURRENT_PROJECT_VERSION = 202503191007;
|
CURRENT_PROJECT_VERSION = 202503191221;
|
||||||
DEVELOPMENT_ASSET_PATHS = "\"Gas Man/Preview Content\"";
|
DEVELOPMENT_ASSET_PATHS = "\"Gas Man/Preview Content\"";
|
||||||
DEVELOPMENT_TEAM = Z734T5CD6B;
|
DEVELOPMENT_TEAM = Z734T5CD6B;
|
||||||
ENABLE_HARDENED_RUNTIME = YES;
|
ENABLE_HARDENED_RUNTIME = YES;
|
||||||
|
@ -332,7 +332,7 @@
|
||||||
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
|
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
|
||||||
CODE_SIGN_ENTITLEMENTS = "Gas Man/Gas_Man.entitlements";
|
CODE_SIGN_ENTITLEMENTS = "Gas Man/Gas_Man.entitlements";
|
||||||
CODE_SIGN_STYLE = Automatic;
|
CODE_SIGN_STYLE = Automatic;
|
||||||
CURRENT_PROJECT_VERSION = 202503191007;
|
CURRENT_PROJECT_VERSION = 202503191221;
|
||||||
DEVELOPMENT_ASSET_PATHS = "\"Gas Man/Preview Content\"";
|
DEVELOPMENT_ASSET_PATHS = "\"Gas Man/Preview Content\"";
|
||||||
DEVELOPMENT_TEAM = Z734T5CD6B;
|
DEVELOPMENT_TEAM = Z734T5CD6B;
|
||||||
ENABLE_HARDENED_RUNTIME = YES;
|
ENABLE_HARDENED_RUNTIME = YES;
|
||||||
|
|
|
@ -8,34 +8,7 @@
|
||||||
import SwiftUI
|
import SwiftUI
|
||||||
import CoreData
|
import CoreData
|
||||||
|
|
||||||
// Helper function to check if a string is properly formatted (e.g., "18.526")
|
|
||||||
func isProperlyFormatted(_ input: String) -> Bool {
|
|
||||||
let pattern = #"^\d+\.\d{3}$"#
|
|
||||||
return input.range(of: pattern, options: .regularExpression) != nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// helper function to format input.
|
|
||||||
func formatInput(_ input: String) -> String {
|
|
||||||
// Extract only numeric characters.
|
|
||||||
let digitsOnly = input.filter { $0.isNumber }
|
|
||||||
// If no digits, return empty.
|
|
||||||
guard !digitsOnly.isEmpty else { return "" }
|
|
||||||
// Convert to an integer to remove any leading zeros.
|
|
||||||
let numberValue = Int(digitsOnly) ?? 0
|
|
||||||
// Convert back to a string.
|
|
||||||
let digits = String(numberValue)
|
|
||||||
|
|
||||||
if digits.count > 3 {
|
|
||||||
// Insert decimal point so that the last 3 digits are decimals.
|
|
||||||
let integerPart = digits.dropLast(3)
|
|
||||||
let decimalPart = digits.suffix(3)
|
|
||||||
return "\(integerPart).\(decimalPart)"
|
|
||||||
} else {
|
|
||||||
// If fewer than 4 digits, pad with zeros on the left to 3 digits.
|
|
||||||
let padded = String(repeating: "0", count: 3 - digits.count) + digits
|
|
||||||
return "0." + padded
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
struct AddFuelLogView: View {
|
struct AddFuelLogView: View {
|
||||||
@Environment(\.managedObjectContext) private var viewContext
|
@Environment(\.managedObjectContext) private var viewContext
|
||||||
|
|
|
@ -0,0 +1,199 @@
|
||||||
|
//
|
||||||
|
// EditFuelLogView.swift
|
||||||
|
// Gas Man
|
||||||
|
//
|
||||||
|
// Created by Kameron Kenny on 3/19/25.
|
||||||
|
//
|
||||||
|
|
||||||
|
|
||||||
|
import SwiftUI
|
||||||
|
import CoreData
|
||||||
|
|
||||||
|
struct EditFuelLogView: View {
|
||||||
|
@Environment(\.managedObjectContext) private var viewContext
|
||||||
|
@Environment(\.dismiss) var dismiss
|
||||||
|
|
||||||
|
@ObservedObject var fuelLog: FuelLog
|
||||||
|
|
||||||
|
// 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
|
||||||
|
@State private var pricePerGalon = ""
|
||||||
|
// Full Tank toggle
|
||||||
|
@State private var fullTank: Bool = true
|
||||||
|
|
||||||
|
// Allowed octane options
|
||||||
|
let octaneOptions = [87, 89, 91, 92, 93, 95]
|
||||||
|
|
||||||
|
// For calculation update control
|
||||||
|
@State private var isUpdatingCalculation = false
|
||||||
|
|
||||||
|
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
|
||||||
|
if !newValue.contains(".") {
|
||||||
|
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(".") {
|
||||||
|
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() }
|
||||||
|
}
|
||||||
|
|
||||||
|
Toggle("Full Tank", isOn: $fullTank)
|
||||||
|
.toggleStyle(SwitchToggleStyle(tint: .blue))
|
||||||
|
|
||||||
|
Button("Get Current Location") {
|
||||||
|
// Optionally trigger location update
|
||||||
|
}
|
||||||
|
|
||||||
|
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())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.navigationTitle("Edit Fuel Log")
|
||||||
|
.toolbar {
|
||||||
|
ToolbarItem(placement: .navigationBarTrailing) {
|
||||||
|
Button("Save") { saveFuelLog() }
|
||||||
|
}
|
||||||
|
ToolbarItem(placement: .navigationBarLeading) {
|
||||||
|
Button("Cancel") { dismiss() }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.onAppear {
|
||||||
|
loadFuelLogData()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private func loadFuelLogData() {
|
||||||
|
date = fuelLog.date ?? Date()
|
||||||
|
odometer = String(format: "%.0f", fuelLog.odometer)
|
||||||
|
fuelVolume = String(format: "%.3f", fuelLog.fuelVolume)
|
||||||
|
cost = String(format: "%.2f", fuelLog.cost)
|
||||||
|
pricePerGalon = String(format: "%.3f", fuelLog.pricePerGalon)
|
||||||
|
fullTank = fuelLog.fullTank
|
||||||
|
locationCoordinates = fuelLog.locationCoordinates ?? ""
|
||||||
|
locationName = fuelLog.locationName ?? ""
|
||||||
|
selectedOctane = Int(fuelLog.octane)
|
||||||
|
}
|
||||||
|
|
||||||
|
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 }
|
||||||
|
// Optional: Validate odometer vs. previous logs if needed.
|
||||||
|
|
||||||
|
fuelLog.date = date
|
||||||
|
fuelLog.odometer = newOdometer
|
||||||
|
fuelLog.fuelVolume = Double(fuelVolume) ?? 0
|
||||||
|
fuelLog.cost = Double(cost) ?? 0
|
||||||
|
fuelLog.locationCoordinates = locationCoordinates
|
||||||
|
fuelLog.locationName = locationName
|
||||||
|
fuelLog.octane = Int16(selectedOctane)
|
||||||
|
fuelLog.pricePerGalon = Double(pricePerGalon) ?? 0
|
||||||
|
fuelLog.fullTank = fullTank
|
||||||
|
|
||||||
|
do {
|
||||||
|
try viewContext.save()
|
||||||
|
dismiss()
|
||||||
|
} catch {
|
||||||
|
let nsError = error as NSError
|
||||||
|
fatalError("Unresolved error \(nsError), \(nsError.userInfo)")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -15,7 +15,8 @@ private let dateFormatter: DateFormatter = {
|
||||||
}()
|
}()
|
||||||
|
|
||||||
struct FuelLogDetailView: View {
|
struct FuelLogDetailView: View {
|
||||||
var fuelLog: FuelLog
|
@ObservedObject var fuelLog: FuelLog
|
||||||
|
@State private var showingEditFuelLog = false
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
Form {
|
Form {
|
||||||
|
@ -57,7 +58,6 @@ struct FuelLogDetailView: View {
|
||||||
Text("$\(fuelLog.pricePerGalon, specifier: "%.3f")")
|
Text("$\(fuelLog.pricePerGalon, specifier: "%.3f")")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// New Tank Status Section
|
|
||||||
Section(header: Text("Tank Status")) {
|
Section(header: Text("Tank Status")) {
|
||||||
HStack {
|
HStack {
|
||||||
Text("Tank:")
|
Text("Tank:")
|
||||||
|
@ -94,5 +94,16 @@ struct FuelLogDetailView: View {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.navigationTitle("Fuel Log Detail")
|
.navigationTitle("Fuel Log Detail")
|
||||||
|
.toolbar {
|
||||||
|
ToolbarItem(placement: .navigationBarTrailing) {
|
||||||
|
Button("Edit") {
|
||||||
|
showingEditFuelLog = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.sheet(isPresented: $showingEditFuelLog) {
|
||||||
|
EditFuelLogView(fuelLog: fuelLog)
|
||||||
|
.environment(\.managedObjectContext, fuelLog.managedObjectContext!)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,37 @@
|
||||||
|
//
|
||||||
|
// HelperFormat.swift
|
||||||
|
// Gas Man
|
||||||
|
//
|
||||||
|
// Created by Kameron Kenny on 3/19/25.
|
||||||
|
//
|
||||||
|
|
||||||
|
import SwiftUI
|
||||||
|
|
||||||
|
// Helper function to check if a string is properly formatted (e.g., "18.526")
|
||||||
|
func isProperlyFormatted(_ input: String) -> Bool {
|
||||||
|
let pattern = #"^\d+\.\d{3}$"#
|
||||||
|
return input.range(of: pattern, options: .regularExpression) != nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// helper function to format input.
|
||||||
|
func formatInput(_ input: String) -> String {
|
||||||
|
// Extract only numeric characters.
|
||||||
|
let digitsOnly = input.filter { $0.isNumber }
|
||||||
|
// If no digits, return empty.
|
||||||
|
guard !digitsOnly.isEmpty else { return "" }
|
||||||
|
// Convert to an integer to remove any leading zeros.
|
||||||
|
let numberValue = Int(digitsOnly) ?? 0
|
||||||
|
// Convert back to a string.
|
||||||
|
let digits = String(numberValue)
|
||||||
|
|
||||||
|
if digits.count > 3 {
|
||||||
|
// Insert decimal point so that the last 3 digits are decimals.
|
||||||
|
let integerPart = digits.dropLast(3)
|
||||||
|
let decimalPart = digits.suffix(3)
|
||||||
|
return "\(integerPart).\(decimalPart)"
|
||||||
|
} else {
|
||||||
|
// If fewer than 4 digits, pad with zeros on the left to 3 digits.
|
||||||
|
let padded = String(repeating: "0", count: 3 - digits.count) + digits
|
||||||
|
return "0." + padded
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue