Skip to content

Commit

Permalink
Merge pull request #270 from mikaelacaron/fix-vehicle-event-connection
Browse files Browse the repository at this point in the history
New Firestore Schema with Subcollections
  • Loading branch information
mikaelacaron authored Nov 24, 2023
2 parents 81507f3 + c463788 commit 1ebd13e
Show file tree
Hide file tree
Showing 21 changed files with 357 additions and 145 deletions.
4 changes: 4 additions & 0 deletions Basic-Car-Maintenance.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
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 */; };
Expand Down Expand Up @@ -121,6 +122,7 @@
8AEE816E2ACF37F800FC0C2A /* Action.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Action.swift; sourceTree = "<group>"; };
8AEE81712ACF384D00FC0C2A /* MainTabView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MainTabView.swift; sourceTree = "<group>"; };
8AEE81732ACF394E00FC0C2A /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
E55B630C2B079E5A006BDDDF /* EditVehicleView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EditVehicleView.swift; sourceTree = "<group>"; };
E58499652ACDDA8B00634660 /* ContributorsListView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContributorsListView.swift; sourceTree = "<group>"; };
E58499672ACDDA9A00634660 /* ContributorsProfileView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContributorsProfileView.swift; sourceTree = "<group>"; };
E58499692ACDDAFF00634660 /* Contributor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Contributor.swift; sourceTree = "<group>"; };
Expand Down Expand Up @@ -435,6 +437,7 @@
E58499652ACDDA8B00634660 /* ContributorsListView.swift */,
E58499672ACDDA9A00634660 /* ContributorsProfileView.swift */,
FF09FC902AB6FF44006BE61A /* AuthenticationView.swift */,
E55B630C2B079E5A006BDDDF /* EditVehicleView.swift */,
);
path = Views;
sourceTree = "<group>";
Expand Down Expand Up @@ -725,6 +728,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 */,
Expand Down
4 changes: 4 additions & 0 deletions Basic-Car-Maintenance/Documentation.docc/FirestoreDetails.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
# Firestore Collections

All about the Firebase Firestore data structure

![Firestore Diagram with sub-collections](FirestoreDiagram.png)

### _alerts_

The alerts collection contains system level alerts that will be visible to all users. The alerts
Expand Down
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import Foundation
@Observable
class DashboardViewModel {

let authenticationViewModel: AuthenticationViewModel
let userUID: String?

var events = [MaintenanceEvent]()
var showAddErrorAlert = false
Expand Down Expand Up @@ -40,41 +40,44 @@ class DashboardViewModel {
}
}

init(authenticationViewModel: AuthenticationViewModel) {
self.authenticationViewModel = authenticationViewModel
init(userUID: String?) {
self.userUID = userUID
}

/// Adding a `MaintenanceEvent` in Firestore at:
/// `vehicles/{vehicleDocumentID}/maintenance_events/{maintenceEventDocumentID}`
/// - Parameter maintenanceEvent: The `MaintenanceEvent` to save
func addEvent(_ maintenanceEvent: MaintenanceEvent) {
if let uid = authenticationViewModel.user?.uid {
if let uid = userUID {
var eventToAdd = maintenanceEvent
eventToAdd.userID = uid

do {
try Firestore
.firestore()
.collection(FirestoreCollection.maintenanceEvents)
.collection(FirestorePath.maintenanceEvents(vehicleID: eventToAdd.vehicleID).path)
.addDocument(from: eventToAdd)

events.append(maintenanceEvent)
AnalyticsService.shared.logEvent(.maintenanceCreate)

errorMessage = ""
isShowingAddMaintenanceEvent = false
} catch {
showAddErrorAlert.toggle()
errorMessage = error.localizedDescription
}

AnalyticsService.shared.logEvent(.maintenanceCreate)
}
}

func getMaintenanceEvents() async {
isLoading = true

if let uid = authenticationViewModel.user?.uid {
if let userUID = userUID {
let db = Firestore.firestore()
let docRef = db.collection(FirestoreCollection.maintenanceEvents)
.whereField(FirestoreField.userID, isEqualTo: uid)

let docRef = db.collectionGroup(FirestoreCollection.maintenanceEvents)
.whereField(FirestoreField.userID, isEqualTo: userUID)

let querySnapshot = try? await docRef.getDocuments()

Expand All @@ -86,6 +89,7 @@ class DashboardViewModel {
events.append(event)
}
}

self.isLoading = false
self.events = events
}
Expand All @@ -94,14 +98,15 @@ class DashboardViewModel {

func updateEvent(_ maintenanceEvent: MaintenanceEvent) async {

if let uid = authenticationViewModel.user?.uid {
if let uid = userUID {
guard let id = maintenanceEvent.id else { return }
var eventToUpdate = maintenanceEvent
eventToUpdate.userID = uid

do {
try Firestore
.firestore()
.collection(FirestoreCollection.maintenanceEvents)
.collection(FirestorePath.maintenanceEvents(vehicleID: eventToUpdate.vehicleID).path)
.document(id)
.setData(from: eventToUpdate)
} catch {
Expand All @@ -123,7 +128,7 @@ class DashboardViewModel {
do {
try await Firestore
.firestore()
.collection(FirestoreCollection.maintenanceEvents)
.collection(FirestorePath.maintenanceEvents(vehicleID: event.vehicleID).path)
.document(documentId)
.delete()
errorMessage = ""
Expand All @@ -141,7 +146,7 @@ class DashboardViewModel {

/// Fetches the user's vehicles from Firestore based on their unique user ID.
func getVehicles() async {
if let uid = authenticationViewModel.user?.uid {
if let uid = userUID {
let db = Firestore.firestore()
let docRef = db.collection("vehicles").whereField("userID", isEqualTo: uid)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -70,18 +70,20 @@ struct AddMaintenanceView: View {
.analyticsView("\(Self.self)")
.navigationTitle(Text("Add Maintenance",
comment: "Nagivation title for Add Maintenance view"))
.onAppear {
if !vehicles.isEmpty {
selectedVehicleID = vehicles[0].id
}
}
.toolbar {
ToolbarItem {
Button {

if let selectedVehicleID {
if let vehicle = vehicles.filter({ $0.id == selectedVehicleID }).first {
let event = MaintenanceEvent(title: title,
date: date,
notes: notes,
vehicle: vehicle)
addTapped(event)
}
let event = MaintenanceEvent(vehicleID: selectedVehicleID,
title: title,
date: date,
notes: notes)
addTapped(event)
dismiss()
}
} label: {
Expand Down
13 changes: 8 additions & 5 deletions Basic-Car-Maintenance/Shared/Dashboard/Views/DashboardView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,8 @@ struct DashboardView: View {
@State private var isShowingEditView = false
@State private var selectedMaintenanceEvent: MaintenanceEvent?

init(authenticationViewModel: AuthenticationViewModel) {
viewModel = DashboardViewModel(authenticationViewModel: authenticationViewModel)
init(userUID: String?) {
viewModel = DashboardViewModel(userUID: userUID)
}

private var eventDateFormat: DateFormatter = {
Expand All @@ -33,8 +33,11 @@ struct DashboardView: View {
Text(event.title)
.font(.title3)

Text("\(event.vehicle.name) on \(event.date, formatter: self.eventDateFormat)",
comment: "Maintenance list item for a vehicle on a date")
let vehicleName = viewModel.vehicles.first { $0.id == event.vehicleID }?.name
if let vehicleName {
Text("\(vehicleName) on \(event.date, formatter: self.eventDateFormat)",
comment: "Maintenance list item for a vehicle on a date")
}

if !event.notes.isEmpty {
Text(event.notes)
Expand Down Expand Up @@ -196,6 +199,6 @@ struct DashboardView: View {
}

#Preview {
DashboardView(authenticationViewModel: AuthenticationViewModel())
DashboardView(userUID: "")
.environment(ActionService.shared)
}
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@ struct EditMaintenanceEventView: View {
var viewModel: DashboardViewModel
@State private var title = ""
@State private var date = Date()
@State private var selectedVehicle: Vehicle?
@State private var notes = ""
@Environment(\.dismiss) var dismiss

Expand All @@ -26,18 +25,13 @@ struct EditMaintenanceEventView: View {
}

Section {
Picker(selection: $selectedVehicle) {
ForEach(viewModel.vehicles) { vehicle in
Text(vehicle.name).tag(vehicle as Vehicle)
}
} label: {
Text("Select a vehicle",
comment: "Picker for selecting a vehicle")
if let vehicleName = viewModel.vehicles
.filter({ $0.id == selectedEvent?.vehicleID }).first?.name {
Text(vehicleName)
.opacity(0.3)
}
.pickerStyle(.menu)
} header: {
Text("Vehicle",
comment: "Maintenance event vehicle picker header")
Text("Vehicle")
}

DatePicker(selection: $date, displayedComponents: .date) {
Expand Down Expand Up @@ -67,11 +61,11 @@ struct EditMaintenanceEventView: View {

ToolbarItem(placement: .topBarTrailing) {
Button {
if let selectedVehicle, let selectedEvent {
var event = MaintenanceEvent(title: title,
if let selectedEvent {
var event = MaintenanceEvent(vehicleID: selectedEvent.vehicleID,
title: title,
date: date,
notes: notes,
vehicle: selectedVehicle)
notes: notes)
event.id = selectedEvent.id
Task {
await viewModel.updateEvent(event)
Expand All @@ -91,15 +85,18 @@ struct EditMaintenanceEventView: View {
self.title = event.title
self.date = event.date
self.notes = event.notes
self.selectedVehicle = event.vehicle
}
}

#Preview {
EditMaintenanceEventView(selectedEvent:
.constant(MaintenanceEvent(title: "", date: Date(), notes: "",
vehicle: Vehicle(name: "", make: "", model: ""))),
.constant(MaintenanceEvent(id: "",
userID: "",
vehicleID: "",
title: "",
date: Date(),
notes: "")),
viewModel:
DashboardViewModel(authenticationViewModel: AuthenticationViewModel())
DashboardViewModel(userUID: "")
)
}
3 changes: 3 additions & 0 deletions Basic-Car-Maintenance/Shared/Localizable.xcstrings
Original file line number Diff line number Diff line change
Expand Up @@ -4407,6 +4407,9 @@
}
}
}
},
"Update Vehicle Info" : {

},
"Vehicle" : {
"comment" : "Maintenance event vehicle picker header",
Expand Down
4 changes: 2 additions & 2 deletions Basic-Car-Maintenance/Shared/MainView/Views/MainTabView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -29,13 +29,13 @@ struct MainTabView: View {

var body: some View {
TabView(selection: $selectedTab) {
DashboardView(authenticationViewModel: authenticationViewModel)
DashboardView(userUID: authenticationViewModel.user?.uid)
.tag(TabSelection.dashboard)
.tabItem {
Label("Dashboard", systemImage: SFSymbol.dashboard)
}

OdometerView(authenticationViewModel: authenticationViewModel)
OdometerView(userUID: authenticationViewModel.user?.uid)
.tag(TabSelection.odometer)
.tabItem {
Label("Odometer", systemImage: SFSymbol.gauge)
Expand Down
3 changes: 2 additions & 1 deletion Basic-Car-Maintenance/Shared/Models/MaintenanceEvent.swift
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,9 @@ import Foundation
struct MaintenanceEvent: Codable, Identifiable, Hashable {
@DocumentID var id: String?
var userID: String?

let vehicleID: String
let title: String
let date: Date
let notes: String
var vehicle: Vehicle
}
2 changes: 1 addition & 1 deletion Basic-Car-Maintenance/Shared/Models/OdometerReading.swift
Original file line number Diff line number Diff line change
Expand Up @@ -14,5 +14,5 @@ struct OdometerReading: Codable, Identifiable, Hashable {
let date: Date
let distance: Int
let isMetric: Bool
let vehicle: Vehicle
let vehicleID: String
}
Original file line number Diff line number Diff line change
Expand Up @@ -12,26 +12,26 @@ import Foundation
@Observable
class OdometerViewModel {

let authenticationViewModel: AuthenticationViewModel
let userUID: String?

var readings = [OdometerReading]()
var showAddErrorAlert = false
var isShowingAddOdometerReading = false
var errorMessage: String = ""
var vehicles = [Vehicle]()

init(authenticationViewModel: AuthenticationViewModel) {
self.authenticationViewModel = authenticationViewModel
init(userUID: String?) {
self.userUID = userUID
}

func addReading(_ odometerReading: OdometerReading) throws {
if let uid = authenticationViewModel.user?.uid {
if let uid = userUID {
var readingToAdd = odometerReading
readingToAdd.userID = uid

_ = try Firestore
try Firestore
.firestore()
.collection("odometer_readings")
.collection(FirestorePath.odometerReadings(vehicleID: readingToAdd.vehicleID).path)
.addDocument(from: readingToAdd)

AnalyticsService.shared.logEvent(.odometerCreate)
Expand All @@ -45,7 +45,7 @@ class OdometerViewModel {

try? await Firestore
.firestore()
.collection(FirestoreCollection.odometerReadings)
.collection(FirestorePath.odometerReadings(vehicleID: reading.vehicleID).path)
.document(documentId)
.delete()

Expand All @@ -57,10 +57,10 @@ class OdometerViewModel {
}

func getOdometerReadings() async {
if let uid = authenticationViewModel.user?.uid {
if let userUID = userUID {
let db = Firestore.firestore()
let docRef = db.collection(FirestoreCollection.odometerReadings)
.whereField(FirestoreField.userID, isEqualTo: uid)
let docRef = db.collectionGroup(FirestoreCollection.odometerReadings)
.whereField(FirestoreField.userID, isEqualTo: userUID)

let querySnapshot = try? await docRef.getDocuments()

Expand All @@ -78,7 +78,7 @@ class OdometerViewModel {
}

func getVehicles() async {
if let uid = authenticationViewModel.user?.uid {
if let uid = userUID {
let db = Firestore.firestore()
let docRef = db.collection(FirestoreCollection.vehicles)
.whereField(FirestoreField.userID, isEqualTo: uid)
Expand Down
Loading

0 comments on commit 1ebd13e

Please sign in to comment.