add photo feature for vehicle.

This commit is contained in:
Kameron Kenny 2025-03-19 21:59:15 -04:00
parent b628154257
commit e4818a2c2f
13 changed files with 168 additions and 26 deletions

View File

@ -199,7 +199,7 @@
};
};
};
buildConfigurationList = 3D34444B2D889F7D00AA3172 /* Build configuration list for PBXProject "Gas Man" */;
buildConfigurationList = 3D34444B2D889F7D00AA3172 /* Build configuration list for PBXProject "Fuel Man" */;
developmentRegion = en;
hasScannedForEncodings = 0;
knownRegions = (
@ -575,7 +575,7 @@
/* End XCBuildConfiguration section */
/* Begin XCConfigurationList section */
3D34444B2D889F7D00AA3172 /* Build configuration list for PBXProject "Gas Man" */ = {
3D34444B2D889F7D00AA3172 /* Build configuration list for PBXProject "Fuel Man" */ = {
isa = XCConfigurationList;
buildConfigurations = (
3D34447D2D889F8000AA3172 /* Debug */,

View File

@ -18,7 +18,7 @@
BlueprintIdentifier = "3D34444F2D889F7D00AA3172"
BuildableName = "Gas Man.app"
BlueprintName = "Gas Man"
ReferencedContainer = "container:Gas Man.xcodeproj">
ReferencedContainer = "container:Fuel Man.xcodeproj">
</BuildableReference>
</BuildActionEntry>
</BuildActionEntries>
@ -38,7 +38,7 @@
BlueprintIdentifier = "3D3444662D889F7F00AA3172"
BuildableName = "Gas ManTests.xctest"
BlueprintName = "Gas ManTests"
ReferencedContainer = "container:Gas Man.xcodeproj">
ReferencedContainer = "container:Fuel Man.xcodeproj">
</BuildableReference>
</TestableReference>
<TestableReference
@ -49,7 +49,7 @@
BlueprintIdentifier = "3D3444702D889F8000AA3172"
BuildableName = "Gas ManUITests.xctest"
BlueprintName = "Gas ManUITests"
ReferencedContainer = "container:Gas Man.xcodeproj">
ReferencedContainer = "container:Fuel Man.xcodeproj">
</BuildableReference>
</TestableReference>
</Testables>
@ -71,7 +71,7 @@
BlueprintIdentifier = "3D34444F2D889F7D00AA3172"
BuildableName = "Gas Man.app"
BlueprintName = "Gas Man"
ReferencedContainer = "container:Gas Man.xcodeproj">
ReferencedContainer = "container:Fuel Man.xcodeproj">
</BuildableReference>
</BuildableProductRunnable>
</LaunchAction>
@ -88,7 +88,7 @@
BlueprintIdentifier = "3D34444F2D889F7D00AA3172"
BuildableName = "Gas Man.app"
BlueprintName = "Gas Man"
ReferencedContainer = "container:Gas Man.xcodeproj">
ReferencedContainer = "container:Fuel Man.xcodeproj">
</BuildableReference>
</BuildableProductRunnable>
</ProfileAction>

View File

@ -93,7 +93,7 @@ struct FuelLogListView: View {
List {
// Vehicle Picker Section
Section {
Picker("Vehicle", selection: $selectedVehicleID) {
Picker("Vehicle:", selection: $selectedVehicleID) {
ForEach(vehicles, id: \.id) { vehicle in
Text("\(vehicle.year ?? "") \(vehicle.make ?? "") \(vehicle.model ?? "")")
.tag(vehicle.id)
@ -102,6 +102,29 @@ struct FuelLogListView: View {
.pickerStyle(MenuPickerStyle())
}
// Vehicle Photo Section
Section {
if let selectedVehicle = vehicles.first(where: { $0.id == selectedVehicleID }) {
if let photoData = selectedVehicle.photo, let uiImage = UIImage(data: photoData) {
Image(uiImage: uiImage)
.resizable()
.aspectRatio(contentMode: .fit)
.frame(maxWidth: .infinity, maxHeight: 200)
.clipShape(Circle())
// .cornerRadius(8)
} else {
// Fallback image if no photo is available
Image(systemName: "car.fill")
.resizable()
.aspectRatio(contentMode: .fit)
.frame(maxWidth: .infinity, maxHeight: 200)
.foregroundColor(.gray)
.padding()
}
}
}
.listRowBackground(Color.black)
// Average MPG Section
Section {
HStack {

View File

@ -19,9 +19,13 @@
<true/>
<key>com.apple.security.assets.pictures.read-only</key>
<true/>
<key>com.apple.security.device.camera</key>
<true/>
<key>com.apple.security.files.user-selected.read-write</key>
<true/>
<key>com.apple.security.personal-information.location</key>
<true/>
<key>com.apple.security.personal-information.photos-library</key>
<true/>
</dict>
</plist>

View File

@ -34,6 +34,7 @@
<attribute name="notes" optional="YES" attributeType="String"/>
<attribute name="odometerAtPurchase" optional="YES" attributeType="Double" defaultValueString="0.0" usesScalarValueType="YES"/>
<attribute name="odometerAtSale" optional="YES" attributeType="Double" defaultValueString="0.0" usesScalarValueType="YES"/>
<attribute name="photo" optional="YES" attributeType="Binary"/>
<attribute name="purchaseDate" optional="YES" attributeType="Date" usesScalarValueType="NO"/>
<attribute name="purchasePrice" optional="YES" attributeType="Double" defaultValueString="0.0" usesScalarValueType="YES"/>
<attribute name="soldDate" optional="YES" attributeType="Date" usesScalarValueType="NO"/>

41
Gas Man/ImagePicker.swift Normal file
View File

@ -0,0 +1,41 @@
//
// ImagePicker.swift
// Gas Man
//
// Created by Kameron Kenny on 3/19/25.
//
import SwiftUI
import UIKit
struct ImagePicker: UIViewControllerRepresentable {
class Coordinator: NSObject, UINavigationControllerDelegate, UIImagePickerControllerDelegate {
let parent: ImagePicker
init(_ parent: ImagePicker) {
self.parent = parent
}
func imagePickerController(_ picker: UIImagePickerController,
didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey : Any]) {
if let uiImage = info[.originalImage] as? UIImage {
parent.image = uiImage
}
parent.presentationMode.wrappedValue.dismiss()
}
}
@Environment(\.presentationMode) var presentationMode
var sourceType: UIImagePickerController.SourceType = .photoLibrary
@Binding var image: UIImage?
func makeCoordinator() -> Coordinator {
Coordinator(self)
}
func makeUIViewController(context: Context) -> UIImagePickerController {
let picker = UIImagePickerController()
picker.sourceType = sourceType
picker.delegate = context.coordinator
return picker
}
func updateUIViewController(_ uiViewController: UIImagePickerController, context: Context) { }
}

View File

@ -25,6 +25,7 @@ extension Vehicle: Identifiable {
id ?? UUID()
}
@NSManaged public var photo: Data?
@NSManaged public var year: String?
@NSManaged public var make: String?
@NSManaged public var model: String?

View File

@ -9,6 +9,14 @@ import SwiftUI
struct VehicleDetailView: View {
@ObservedObject var vehicle: Vehicle
@State private var showingEditVehicle = false
// Image state for the vehicle photo.
@State private var vehicleImage: Image? = nil
@State private var inputImage: UIImage? = nil
@State private var showImagePicker = false
@State private var imagePickerSource: UIImagePickerController.SourceType = .photoLibrary
@State private var showImageSourceOptions = false
private let dateFormatter: DateFormatter = {
let formatter = DateFormatter()
formatter.dateStyle = .short
@ -17,6 +25,45 @@ struct VehicleDetailView: View {
var body: some View {
Form {
// Photo Section at the very top
Section {
ZStack {
if let vehicleImage = vehicleImage {
vehicleImage
.resizable()
.scaledToFill()
} else {
Rectangle()
.fill(Color.gray.opacity(0.2))
Image(systemName: "camera.fill")
.font(.largeTitle)
.foregroundColor(.gray)
}
}
.frame(height: 200)
.clipped()
.onTapGesture {
showImageSourceOptions = true
}
.confirmationDialog("Select Photo Source", isPresented: $showImageSourceOptions) {
if UIImagePickerController.isSourceTypeAvailable(.camera) {
Button("Camera") {
imagePickerSource = .camera
showImagePicker = true
}
}
Button("Photo Library") {
imagePickerSource = .photoLibrary
showImagePicker = true
}
Button("Cancel", role: .cancel) { }
}
.sheet(isPresented: $showImagePicker, onDismiss: loadImage) {
ImagePicker(sourceType: imagePickerSource, image: $inputImage)
}
}
// Rest of the details...
Section(header: Text("Basic Information")) {
HStack {
Text("Vehicle Type:")
@ -31,17 +78,17 @@ struct VehicleDetailView: View {
HStack {
Text("Make:")
Spacer()
Text(vehicle.make ?? "")
Text(vehicle.make ?? "N/A")
}
HStack {
Text("Model:")
Spacer()
Text(vehicle.model ?? "")
Text(vehicle.model ?? "N/A")
}
HStack {
Text("Color:")
Spacer()
Text(vehicle.color ?? "")
Text(vehicle.color ?? "N/A")
}
}
Section(header: Text("Purchase Information")) {
@ -69,7 +116,6 @@ struct VehicleDetailView: View {
}
}
}
Section(header: Text("Engine & Transmission")) {
HStack {
Text("Engine Name:")
@ -91,7 +137,6 @@ struct VehicleDetailView: View {
Text(vehicle.transmission ?? "Automatic")
}
}
Section(header: Text("Tires")) {
HStack {
Text("Brand:")
@ -123,10 +168,8 @@ struct VehicleDetailView: View {
} else {
Text("-")
}
}
}
Section(header: Text("Wheels")) {
HStack {
Text("Brand:")
@ -154,7 +197,6 @@ struct VehicleDetailView: View {
}
}
}
if vehicle.soldDate != nil {
Section(header: Text("Sale Information")) {
HStack {
@ -173,7 +215,6 @@ struct VehicleDetailView: View {
}
}
}
Section(header: Text("Notes")) {
Text(vehicle.notes ?? "")
}
@ -190,6 +231,21 @@ struct VehicleDetailView: View {
EditVehicleView(vehicle: vehicle)
.environment(\.managedObjectContext, vehicle.managedObjectContext!)
}
.onAppear {
if let data = vehicle.photo, let uiImage = UIImage(data: data) {
vehicleImage = Image(uiImage: uiImage)
}
}
}
// This function is called when the image picker is dismissed.
func loadImage() {
guard let inputImage = inputImage else { return }
vehicleImage = Image(uiImage: inputImage)
// Save the image to the vehicle.
if let imageData = inputImage.jpegData(compressionQuality: 0.8) {
vehicle.photo = imageData
try? vehicle.managedObjectContext?.save()
}
}
}

View File

@ -9,15 +9,31 @@
import SwiftUI
struct VehicleRowView: View {
var vehicle: Vehicle
let vehicle: Vehicle
var body: some View {
VStack(alignment: .leading) {
Text("\(vehicle.year ?? "") \(vehicle.make ?? "") \(vehicle.model ?? "")")
.font(.headline)
Text(vehicle.color ?? "")
.font(.subheadline)
.foregroundColor(.secondary)
HStack {
if let imageData = vehicle.photo, let uiImage = UIImage(data: imageData) {
Image(uiImage: uiImage)
.resizable()
.scaledToFill()
.frame(width: 50, height: 50)
.clipShape(Circle())
} else {
Circle()
.fill(Color.gray.opacity(0.3))
.frame(width: 50, height: 50)
.overlay(
Image(systemName: "car.fill")
.foregroundColor(.gray)
)
}
VStack(alignment: .leading) {
Text("\(vehicle.year ?? "") \(vehicle.make ?? "") \(vehicle.model ?? "")")
.font(.headline)
// Add other details if needed.
}
}
}
}