add photo feature for vehicle.
This commit is contained in:
parent
b628154257
commit
e4818a2c2f
|
@ -199,7 +199,7 @@
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
buildConfigurationList = 3D34444B2D889F7D00AA3172 /* Build configuration list for PBXProject "Gas Man" */;
|
buildConfigurationList = 3D34444B2D889F7D00AA3172 /* Build configuration list for PBXProject "Fuel Man" */;
|
||||||
developmentRegion = en;
|
developmentRegion = en;
|
||||||
hasScannedForEncodings = 0;
|
hasScannedForEncodings = 0;
|
||||||
knownRegions = (
|
knownRegions = (
|
||||||
|
@ -575,7 +575,7 @@
|
||||||
/* End XCBuildConfiguration section */
|
/* End XCBuildConfiguration section */
|
||||||
|
|
||||||
/* Begin XCConfigurationList section */
|
/* Begin XCConfigurationList section */
|
||||||
3D34444B2D889F7D00AA3172 /* Build configuration list for PBXProject "Gas Man" */ = {
|
3D34444B2D889F7D00AA3172 /* Build configuration list for PBXProject "Fuel Man" */ = {
|
||||||
isa = XCConfigurationList;
|
isa = XCConfigurationList;
|
||||||
buildConfigurations = (
|
buildConfigurations = (
|
||||||
3D34447D2D889F8000AA3172 /* Debug */,
|
3D34447D2D889F8000AA3172 /* Debug */,
|
|
@ -18,7 +18,7 @@
|
||||||
BlueprintIdentifier = "3D34444F2D889F7D00AA3172"
|
BlueprintIdentifier = "3D34444F2D889F7D00AA3172"
|
||||||
BuildableName = "Gas Man.app"
|
BuildableName = "Gas Man.app"
|
||||||
BlueprintName = "Gas Man"
|
BlueprintName = "Gas Man"
|
||||||
ReferencedContainer = "container:Gas Man.xcodeproj">
|
ReferencedContainer = "container:Fuel Man.xcodeproj">
|
||||||
</BuildableReference>
|
</BuildableReference>
|
||||||
</BuildActionEntry>
|
</BuildActionEntry>
|
||||||
</BuildActionEntries>
|
</BuildActionEntries>
|
||||||
|
@ -38,7 +38,7 @@
|
||||||
BlueprintIdentifier = "3D3444662D889F7F00AA3172"
|
BlueprintIdentifier = "3D3444662D889F7F00AA3172"
|
||||||
BuildableName = "Gas ManTests.xctest"
|
BuildableName = "Gas ManTests.xctest"
|
||||||
BlueprintName = "Gas ManTests"
|
BlueprintName = "Gas ManTests"
|
||||||
ReferencedContainer = "container:Gas Man.xcodeproj">
|
ReferencedContainer = "container:Fuel Man.xcodeproj">
|
||||||
</BuildableReference>
|
</BuildableReference>
|
||||||
</TestableReference>
|
</TestableReference>
|
||||||
<TestableReference
|
<TestableReference
|
||||||
|
@ -49,7 +49,7 @@
|
||||||
BlueprintIdentifier = "3D3444702D889F8000AA3172"
|
BlueprintIdentifier = "3D3444702D889F8000AA3172"
|
||||||
BuildableName = "Gas ManUITests.xctest"
|
BuildableName = "Gas ManUITests.xctest"
|
||||||
BlueprintName = "Gas ManUITests"
|
BlueprintName = "Gas ManUITests"
|
||||||
ReferencedContainer = "container:Gas Man.xcodeproj">
|
ReferencedContainer = "container:Fuel Man.xcodeproj">
|
||||||
</BuildableReference>
|
</BuildableReference>
|
||||||
</TestableReference>
|
</TestableReference>
|
||||||
</Testables>
|
</Testables>
|
||||||
|
@ -71,7 +71,7 @@
|
||||||
BlueprintIdentifier = "3D34444F2D889F7D00AA3172"
|
BlueprintIdentifier = "3D34444F2D889F7D00AA3172"
|
||||||
BuildableName = "Gas Man.app"
|
BuildableName = "Gas Man.app"
|
||||||
BlueprintName = "Gas Man"
|
BlueprintName = "Gas Man"
|
||||||
ReferencedContainer = "container:Gas Man.xcodeproj">
|
ReferencedContainer = "container:Fuel Man.xcodeproj">
|
||||||
</BuildableReference>
|
</BuildableReference>
|
||||||
</BuildableProductRunnable>
|
</BuildableProductRunnable>
|
||||||
</LaunchAction>
|
</LaunchAction>
|
||||||
|
@ -88,7 +88,7 @@
|
||||||
BlueprintIdentifier = "3D34444F2D889F7D00AA3172"
|
BlueprintIdentifier = "3D34444F2D889F7D00AA3172"
|
||||||
BuildableName = "Gas Man.app"
|
BuildableName = "Gas Man.app"
|
||||||
BlueprintName = "Gas Man"
|
BlueprintName = "Gas Man"
|
||||||
ReferencedContainer = "container:Gas Man.xcodeproj">
|
ReferencedContainer = "container:Fuel Man.xcodeproj">
|
||||||
</BuildableReference>
|
</BuildableReference>
|
||||||
</BuildableProductRunnable>
|
</BuildableProductRunnable>
|
||||||
</ProfileAction>
|
</ProfileAction>
|
|
@ -93,7 +93,7 @@ struct FuelLogListView: View {
|
||||||
List {
|
List {
|
||||||
// Vehicle Picker Section
|
// Vehicle Picker Section
|
||||||
Section {
|
Section {
|
||||||
Picker("Vehicle", selection: $selectedVehicleID) {
|
Picker("Vehicle:", selection: $selectedVehicleID) {
|
||||||
ForEach(vehicles, id: \.id) { vehicle in
|
ForEach(vehicles, id: \.id) { vehicle in
|
||||||
Text("\(vehicle.year ?? "") \(vehicle.make ?? "") \(vehicle.model ?? "")")
|
Text("\(vehicle.year ?? "") \(vehicle.make ?? "") \(vehicle.model ?? "")")
|
||||||
.tag(vehicle.id)
|
.tag(vehicle.id)
|
||||||
|
@ -102,6 +102,29 @@ struct FuelLogListView: View {
|
||||||
.pickerStyle(MenuPickerStyle())
|
.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
|
// Average MPG Section
|
||||||
Section {
|
Section {
|
||||||
HStack {
|
HStack {
|
||||||
|
|
|
@ -19,9 +19,13 @@
|
||||||
<true/>
|
<true/>
|
||||||
<key>com.apple.security.assets.pictures.read-only</key>
|
<key>com.apple.security.assets.pictures.read-only</key>
|
||||||
<true/>
|
<true/>
|
||||||
|
<key>com.apple.security.device.camera</key>
|
||||||
|
<true/>
|
||||||
<key>com.apple.security.files.user-selected.read-write</key>
|
<key>com.apple.security.files.user-selected.read-write</key>
|
||||||
<true/>
|
<true/>
|
||||||
<key>com.apple.security.personal-information.location</key>
|
<key>com.apple.security.personal-information.location</key>
|
||||||
<true/>
|
<true/>
|
||||||
|
<key>com.apple.security.personal-information.photos-library</key>
|
||||||
|
<true/>
|
||||||
</dict>
|
</dict>
|
||||||
</plist>
|
</plist>
|
||||||
|
|
|
@ -34,6 +34,7 @@
|
||||||
<attribute name="notes" optional="YES" attributeType="String"/>
|
<attribute name="notes" optional="YES" attributeType="String"/>
|
||||||
<attribute name="odometerAtPurchase" optional="YES" attributeType="Double" defaultValueString="0.0" usesScalarValueType="YES"/>
|
<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="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="purchaseDate" optional="YES" attributeType="Date" usesScalarValueType="NO"/>
|
||||||
<attribute name="purchasePrice" optional="YES" attributeType="Double" defaultValueString="0.0" usesScalarValueType="YES"/>
|
<attribute name="purchasePrice" optional="YES" attributeType="Double" defaultValueString="0.0" usesScalarValueType="YES"/>
|
||||||
<attribute name="soldDate" optional="YES" attributeType="Date" usesScalarValueType="NO"/>
|
<attribute name="soldDate" optional="YES" attributeType="Date" usesScalarValueType="NO"/>
|
||||||
|
|
|
@ -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) { }
|
||||||
|
}
|
|
@ -25,6 +25,7 @@ extension Vehicle: Identifiable {
|
||||||
id ?? UUID()
|
id ?? UUID()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@NSManaged public var photo: Data?
|
||||||
@NSManaged public var year: String?
|
@NSManaged public var year: String?
|
||||||
@NSManaged public var make: String?
|
@NSManaged public var make: String?
|
||||||
@NSManaged public var model: String?
|
@NSManaged public var model: String?
|
||||||
|
|
|
@ -9,6 +9,14 @@ import SwiftUI
|
||||||
struct VehicleDetailView: View {
|
struct VehicleDetailView: View {
|
||||||
@ObservedObject var vehicle: Vehicle
|
@ObservedObject var vehicle: Vehicle
|
||||||
@State private var showingEditVehicle = false
|
@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 = {
|
private let dateFormatter: DateFormatter = {
|
||||||
let formatter = DateFormatter()
|
let formatter = DateFormatter()
|
||||||
formatter.dateStyle = .short
|
formatter.dateStyle = .short
|
||||||
|
@ -17,6 +25,45 @@ struct VehicleDetailView: View {
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
Form {
|
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")) {
|
Section(header: Text("Basic Information")) {
|
||||||
HStack {
|
HStack {
|
||||||
Text("Vehicle Type:")
|
Text("Vehicle Type:")
|
||||||
|
@ -31,17 +78,17 @@ struct VehicleDetailView: View {
|
||||||
HStack {
|
HStack {
|
||||||
Text("Make:")
|
Text("Make:")
|
||||||
Spacer()
|
Spacer()
|
||||||
Text(vehicle.make ?? "")
|
Text(vehicle.make ?? "N/A")
|
||||||
}
|
}
|
||||||
HStack {
|
HStack {
|
||||||
Text("Model:")
|
Text("Model:")
|
||||||
Spacer()
|
Spacer()
|
||||||
Text(vehicle.model ?? "")
|
Text(vehicle.model ?? "N/A")
|
||||||
}
|
}
|
||||||
HStack {
|
HStack {
|
||||||
Text("Color:")
|
Text("Color:")
|
||||||
Spacer()
|
Spacer()
|
||||||
Text(vehicle.color ?? "")
|
Text(vehicle.color ?? "N/A")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Section(header: Text("Purchase Information")) {
|
Section(header: Text("Purchase Information")) {
|
||||||
|
@ -69,7 +116,6 @@ struct VehicleDetailView: View {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Section(header: Text("Engine & Transmission")) {
|
Section(header: Text("Engine & Transmission")) {
|
||||||
HStack {
|
HStack {
|
||||||
Text("Engine Name:")
|
Text("Engine Name:")
|
||||||
|
@ -91,7 +137,6 @@ struct VehicleDetailView: View {
|
||||||
Text(vehicle.transmission ?? "Automatic")
|
Text(vehicle.transmission ?? "Automatic")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Section(header: Text("Tires")) {
|
Section(header: Text("Tires")) {
|
||||||
HStack {
|
HStack {
|
||||||
Text("Brand:")
|
Text("Brand:")
|
||||||
|
@ -123,10 +168,8 @@ struct VehicleDetailView: View {
|
||||||
} else {
|
} else {
|
||||||
Text("-")
|
Text("-")
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Section(header: Text("Wheels")) {
|
Section(header: Text("Wheels")) {
|
||||||
HStack {
|
HStack {
|
||||||
Text("Brand:")
|
Text("Brand:")
|
||||||
|
@ -154,7 +197,6 @@ struct VehicleDetailView: View {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if vehicle.soldDate != nil {
|
if vehicle.soldDate != nil {
|
||||||
Section(header: Text("Sale Information")) {
|
Section(header: Text("Sale Information")) {
|
||||||
HStack {
|
HStack {
|
||||||
|
@ -173,7 +215,6 @@ struct VehicleDetailView: View {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Section(header: Text("Notes")) {
|
Section(header: Text("Notes")) {
|
||||||
Text(vehicle.notes ?? "")
|
Text(vehicle.notes ?? "")
|
||||||
}
|
}
|
||||||
|
@ -190,6 +231,21 @@ struct VehicleDetailView: View {
|
||||||
EditVehicleView(vehicle: vehicle)
|
EditVehicleView(vehicle: vehicle)
|
||||||
.environment(\.managedObjectContext, vehicle.managedObjectContext!)
|
.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()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -9,15 +9,31 @@
|
||||||
import SwiftUI
|
import SwiftUI
|
||||||
|
|
||||||
struct VehicleRowView: View {
|
struct VehicleRowView: View {
|
||||||
var vehicle: Vehicle
|
let vehicle: Vehicle
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
|
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) {
|
VStack(alignment: .leading) {
|
||||||
Text("\(vehicle.year ?? "") \(vehicle.make ?? "") \(vehicle.model ?? "")")
|
Text("\(vehicle.year ?? "") \(vehicle.make ?? "") \(vehicle.model ?? "")")
|
||||||
.font(.headline)
|
.font(.headline)
|
||||||
Text(vehicle.color ?? "")
|
// Add other details if needed.
|
||||||
.font(.subheadline)
|
}
|
||||||
.foregroundColor(.secondary)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue