diff --git a/Basic-Car-Maintenance.xcodeproj/project.pbxproj b/Basic-Car-Maintenance.xcodeproj/project.pbxproj index e176f9ec..887c376b 100644 --- a/Basic-Car-Maintenance.xcodeproj/project.pbxproj +++ b/Basic-Car-Maintenance.xcodeproj/project.pbxproj @@ -23,9 +23,11 @@ 8A3D748C2AD9C41D0000FEEB /* AlertItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8A3D748B2AD9C41D0000FEEB /* AlertItem.swift */; }; 8AEE816F2ACF37F800FC0C2A /* Action.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8AEE816E2ACF37F800FC0C2A /* Action.swift */; }; 8AEE81722ACF384D00FC0C2A /* MainTabView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8AEE81712ACF384D00FC0C2A /* MainTabView.swift */; }; + E55B630D2B079E5A006BDDDF /* EditVehicleView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E55B630C2B079E5A006BDDDF /* EditVehicleView.swift */; }; E58499662ACDDA8B00634660 /* ContributorsListView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E58499652ACDDA8B00634660 /* ContributorsListView.swift */; }; E58499682ACDDA9A00634660 /* ContributorsProfileView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E58499672ACDDA9A00634660 /* ContributorsProfileView.swift */; }; E584996A2ACDDAFF00634660 /* Contributor.swift in Sources */ = {isa = PBXBuildFile; fileRef = E58499692ACDDAFF00634660 /* Contributor.swift */; }; + E5EDD11D2B0933700059C52E /* EditVehicleEvent.swift in Sources */ = {isa = PBXBuildFile; fileRef = E5EDD11C2B0933700059C52E /* EditVehicleEvent.swift */; }; FF09FC912AB6FF44006BE61A /* AuthenticationView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF09FC902AB6FF44006BE61A /* AuthenticationView.swift */; }; FF153AFF2B07C3E000D0BA30 /* FirebaseCrashlytics in Frameworks */ = {isa = PBXBuildFile; productRef = FF153AFE2B07C3E000D0BA30 /* FirebaseCrashlytics */; }; FF218EF62B00865F0025A533 /* AnalyticsService.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF218EF52B00865F0025A533 /* AnalyticsService.swift */; }; @@ -121,9 +123,11 @@ 8AEE816E2ACF37F800FC0C2A /* Action.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Action.swift; sourceTree = ""; }; 8AEE81712ACF384D00FC0C2A /* MainTabView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MainTabView.swift; sourceTree = ""; }; 8AEE81732ACF394E00FC0C2A /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + E55B630C2B079E5A006BDDDF /* EditVehicleView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EditVehicleView.swift; sourceTree = ""; }; E58499652ACDDA8B00634660 /* ContributorsListView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContributorsListView.swift; sourceTree = ""; }; E58499672ACDDA9A00634660 /* ContributorsProfileView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContributorsProfileView.swift; sourceTree = ""; }; E58499692ACDDAFF00634660 /* Contributor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Contributor.swift; sourceTree = ""; }; + E5EDD11C2B0933700059C52E /* EditVehicleEvent.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EditVehicleEvent.swift; sourceTree = ""; }; FF0813562AD0A83000910EFA /* UITests.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = UITests.xcconfig; sourceTree = ""; }; FF0813572AD0A92700910EFA /* Widget.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Widget.xcconfig; sourceTree = ""; }; FF098EFA2AB3424E003EC0FE /* Basic-Car-Maintenance.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = "Basic-Car-Maintenance.xcconfig"; sourceTree = SOURCE_ROOT; }; @@ -422,6 +426,7 @@ 8014A4CE2AD75928005B51F6 /* AppIcon.swift */, 57CDD9A32ADC320F002EFED0 /* OdometerReading.swift */, 8A3D748B2AD9C41D0000FEEB /* AlertItem.swift */, + E5EDD11C2B0933700059C52E /* EditVehicleEvent.swift */, ); path = Models; sourceTree = ""; @@ -435,6 +440,7 @@ E58499652ACDDA8B00634660 /* ContributorsListView.swift */, E58499672ACDDA9A00634660 /* ContributorsProfileView.swift */, FF09FC902AB6FF44006BE61A /* AuthenticationView.swift */, + E55B630C2B079E5A006BDDDF /* EditVehicleView.swift */, ); path = Views; sourceTree = ""; @@ -725,6 +731,7 @@ 8A3D74862AD6D9A10000FEEB /* AlertView.swift in Sources */, 57CDD9A02ADC31A8002EFED0 /* AddOdometerReadingView.swift in Sources */, 57CDD99E2ADC3173002EFED0 /* OdometerViewModel.swift in Sources */, + E55B630D2B079E5A006BDDDF /* EditVehicleView.swift in Sources */, FF755B3E2A908E7A00F49A13 /* SettingsView.swift in Sources */, FF3DDF522AA4D28F009D91C4 /* DashboardViewModel.swift in Sources */, FFE0AF562AD66C3500AB46F8 /* OdometerView.swift in Sources */, @@ -732,6 +739,7 @@ 0CA7ED092AE82BF100609019 /* ContributionTip.swift in Sources */, FFBFE0972A98F7CB000A9BEB /* AddVehicleView.swift in Sources */, 8A3D748C2AD9C41D0000FEEB /* AlertItem.swift in Sources */, + E5EDD11D2B0933700059C52E /* EditVehicleEvent.swift in Sources */, 8A3D748A2AD9C3E00000FEEB /* MainTabViewModel.swift in Sources */, E584996A2ACDDAFF00634660 /* Contributor.swift in Sources */, 8014A4D32AD77C92005B51F6 /* ChooseAppIconViewModel.swift in Sources */, diff --git a/Basic-Car-Maintenance/Shared/Localizable.xcstrings b/Basic-Car-Maintenance/Shared/Localizable.xcstrings index 0a687355..8f34bd64 100644 --- a/Basic-Car-Maintenance/Shared/Localizable.xcstrings +++ b/Basic-Car-Maintenance/Shared/Localizable.xcstrings @@ -1518,7 +1518,7 @@ } }, "Edit" : { - "comment" : "Button label to edit this maintenance", + "comment" : "Button label to edit this vehicle\nButton label to edit this maintenance", "localizations" : { "be" : { "stringUnit" : { @@ -4280,6 +4280,7 @@ } }, "Update" : { + "comment" : "Label to update vehicle information.", "localizations" : { "be" : { "stringUnit" : { @@ -4407,6 +4408,9 @@ } } } + }, + "Update Vehicle Info" : { + }, "Vehicle" : { "comment" : "Maintenance event vehicle picker header", diff --git a/Basic-Car-Maintenance/Shared/Models/EditVehicleEvent.swift b/Basic-Car-Maintenance/Shared/Models/EditVehicleEvent.swift new file mode 100644 index 00000000..272050bd --- /dev/null +++ b/Basic-Car-Maintenance/Shared/Models/EditVehicleEvent.swift @@ -0,0 +1,22 @@ +// +// EditVehicleEvent.swift +// Basic-Car-Maintenance +// +// Created by Traton Gossink on 11/18/23. +// + +import FirebaseFirestoreSwift +import Foundation + +struct EditVehicleEvent: Codable, Identifiable, Hashable { + @DocumentID var id: String? + var userID: String? + let name: String + let make: String + let model: String + let year: String + let color: String + let VIN: String + let licenseplatenumber: String + var vehicle: Vehicle? +} diff --git a/Basic-Car-Maintenance/Shared/Settings/ViewModels/SettingsViewModel.swift b/Basic-Car-Maintenance/Shared/Settings/ViewModels/SettingsViewModel.swift index 46ed2ae5..f5714912 100644 --- a/Basic-Car-Maintenance/Shared/Settings/ViewModels/SettingsViewModel.swift +++ b/Basic-Car-Maintenance/Shared/Settings/ViewModels/SettingsViewModel.swift @@ -17,6 +17,8 @@ final class SettingsViewModel { var contributors: [Contributor]? var vehicles = [Vehicle]() + var errorMessage: String = "" + var showErrorAlert = false var sortedContributors: [Contributor] { guard let contributors = contributors, !contributors.isEmpty else { @@ -88,6 +90,30 @@ final class SettingsViewModel { } } + ///Updates users vehicle that is being edited. Fetches selected vehicle from Firestore and saves to that selected vehicle. + func updateEvent(_ editVehicleEvent: EditVehicleEvent) async { + + if let uid = authenticationViewModel.user?.uid { + guard let id = editVehicleEvent.id else { return } + var eventToUpdate = editVehicleEvent + eventToUpdate.userID = uid + do { + try Firestore + .firestore() + .collection(FirestoreCollection.vehicles) + .document(id) + .setData(from: eventToUpdate) + } catch { + showErrorAlert.toggle() + errorMessage = error.localizedDescription + } + } + + AnalyticsService.shared.logEvent(.maintenanceUpdate) + + await self.getVehicles() + } + /// Fetches the user's vehicles from Firestore based on their unique user ID. func getVehicles() async { if let uid = authenticationViewModel.user?.uid { @@ -110,7 +136,7 @@ final class SettingsViewModel { } } } - + /// Deletes a vehicle from both Firestore and the local ``SettingsViewModel/vehicles`` array. /// /// - Parameter vehicle: The vehicle to be deleted. diff --git a/Basic-Car-Maintenance/Shared/Settings/Views/EditVehicleView.swift b/Basic-Car-Maintenance/Shared/Settings/Views/EditVehicleView.swift new file mode 100644 index 00000000..3ff3a285 --- /dev/null +++ b/Basic-Car-Maintenance/Shared/Settings/Views/EditVehicleView.swift @@ -0,0 +1,124 @@ +// +// EditVehicleView.swift +// Basic-Car-Maintenance +// +// Created by Traton Gossink on 11/6/23. +// + +import SwiftUI + +struct EditVehicleView: View, Observable { + @Binding var selectedEvent: EditVehicleEvent? + var viewModel: SettingsViewModel + @State private var selectedVehicle: Vehicle? + @State private var name = "" + @State private var make = "" + @State private var model = "" + @State private var year = "" + @State private var color = "" + @State private var VIN = "" + @State private var licensePlateNumber = "" + @Environment(\.dismiss) var dismiss + + var body: some View { + NavigationStack { + Form { + Section { + TextField("Name", text: $name) + } header: { + Text("Name") + } + Section { + TextField("Make", text: $make) + } header: { + Text("Make") + } + + Section { + TextField("Model", text: $model) + } header: { + Text("Model") + } + + Section { + TextField("Year", text: $year) + } header: { + Text("Year") + } + + Section { + TextField("Color", text: $color) + } header: { + Text("Color") + } + Section { + TextField("VIN", text: $VIN) + } header: { + Text("VIN") + } + Section { + TextField("License Plate Number", text: $licensePlateNumber) + } header: { + Text("License Plate Number") + } + } + .analyticsView("\(Self.self)") + .onAppear { + guard let selectedEvent = selectedEvent else { return } + setEditVehicleEventValues(event: selectedEvent) + } + .navigationTitle(Text("Update Vehicle Info")) + .toolbar { + ToolbarItem(placement: .topBarLeading) { + Button { + dismiss() + } label: { + Text("Cancel") + } + } + + ToolbarItem(placement: .topBarTrailing) { + Button { + if let selectedVehicle, let selectedEvent { + var event = EditVehicleEvent(name: name, + make: make, + model: model, + year: year, + color: color, + VIN: VIN, + licenseplatenumber: licensePlateNumber, + vehicle: selectedVehicle) + event.id = selectedEvent.id + Task { + await viewModel.updateEvent(event) + dismiss() + } + } + } label: { + Text("Update") + } + .disabled(name.isEmpty) + } + } + } + } + + func setEditVehicleEventValues(event: EditVehicleEvent) { + self.name = event.name + self.make = event.make + self.model = event.model + self.year = event.year + self.color = event.color + self.VIN = event.VIN + self.licensePlateNumber = event.licenseplatenumber + self.selectedVehicle = event.vehicle + } +} + +#Preview { + EditVehicleView(selectedEvent: + .constant(EditVehicleEvent(name: "", make: "", model: "", year: "", color: "", VIN: "", licenseplatenumber: "")), + viewModel: + SettingsViewModel(authenticationViewModel: AuthenticationViewModel()) + ) +} diff --git a/Basic-Car-Maintenance/Shared/Settings/Views/SettingsView.swift b/Basic-Car-Maintenance/Shared/Settings/Views/SettingsView.swift index 5f933bdc..b20d1aba 100644 --- a/Basic-Car-Maintenance/Shared/Settings/Views/SettingsView.swift +++ b/Basic-Car-Maintenance/Shared/Settings/Views/SettingsView.swift @@ -23,6 +23,11 @@ struct SettingsView: View { @State private var errorDetails: Error? @State private var copiedAppVersion: Bool = false + @State private var selectedVehicleEvent: EditVehicleEvent? + @State private var isEditingVehicle = false + @State var editViewModel: EditVehicleView? + @State private var vehicleToEdit: Vehicle? + private let appVersion = "Version \(Bundle.main.versionNumber) (\(Bundle.main.buildNumber))" init(authenticationViewModel: AuthenticationViewModel) { @@ -93,7 +98,7 @@ struct SettingsView: View { Text(vehicle.make) Text(vehicle.model) - + if let year = vehicle.year, !year.isEmpty { Text(year) } @@ -124,9 +129,21 @@ struct SettingsView: View { } label: { Text("Delete", comment: "Label to delete a vehicle") } + Button { + isEditingVehicle = true + } label: { + VStack { + Text("Edit", comment: "Button label to edit this vehicle") + Image(systemName: SFSymbol.pencil) + } + } + } + .sheet(isPresented: $isEditingVehicle) { + EditVehicleView( + selectedEvent: $selectedVehicleEvent, viewModel: viewModel) } } - + Button { // TODO: Show Paywall // Show paywall if adding more than 1 vehicle, or show the `isShowingAddVehicle` view