From 0e5566a4183dc7b278f638caa7e8a4d6dc47e8e8 Mon Sep 17 00:00:00 2001 From: Aprameya Kannan Date: Tue, 27 Jan 2026 23:29:31 -0500 Subject: [PATCH 01/10] 392-odometer-liquid-glass --- .../Shared/Design/LiquidGlassModifiers.swift | 80 +++++++++++++++++++ .../Views/AddOdometerReadingView.swift | 76 ++++++++++++------ .../Views/EditOdometerReadingView.swift | 67 +++++++++++----- .../Odometer/Views/OdometerRowView.swift | 28 +++++-- .../Shared/Odometer/Views/OdometerView.swift | 54 ++++++++----- .../Shared/Utilities/Constants.swift | 3 + 6 files changed, 232 insertions(+), 76 deletions(-) create mode 100644 Basic-Car-Maintenance/Shared/Design/LiquidGlassModifiers.swift diff --git a/Basic-Car-Maintenance/Shared/Design/LiquidGlassModifiers.swift b/Basic-Car-Maintenance/Shared/Design/LiquidGlassModifiers.swift new file mode 100644 index 00000000..e3ce703c --- /dev/null +++ b/Basic-Car-Maintenance/Shared/Design/LiquidGlassModifiers.swift @@ -0,0 +1,80 @@ +import SwiftUI + +struct LiquidGlassCardModifier: ViewModifier { + @Environment(\.colorScheme) var colorScheme + @Environment(\.accessibilityReduceTransparency) var reduceTransparency + @Environment(\.colorSchemeContrast) var colorSchemeContrast + + func body(content: Content) -> some View { + content + .background( + reduceTransparency ? + Color(UIColor.secondarySystemGroupedBackground) : Color.clear + ) + .background(.ultraThinMaterial, in: RoundedRectangle(cornerRadius: 16)) + .shadow(color: .black.opacity(colorScheme == .dark ? 0.3 : 0.1), radius: 6, y: 3) + .overlay( + RoundedRectangle(cornerRadius: 16) + .stroke( + colorSchemeContrast == .increased ? + Color.primary.opacity(0.8) : .primary.opacity(0.15), + lineWidth: colorSchemeContrast == .increased ? 2.0 : 0.5 + ) + ) + } +} + +struct LiquidGlassSectionModifier: ViewModifier { + @Environment(\.colorScheme) var colorScheme + @Environment(\.accessibilityReduceTransparency) var reduceTransparency + @Environment(\.colorSchemeContrast) var colorSchemeContrast + + func body(content: Content) -> some View { + content + .background(.thinMaterial, in: RoundedRectangle(cornerRadius: 16)) + .shadow(color: .black.opacity(colorScheme == .dark ? 0.2 : 0.08), radius: 4, y: 2) + .overlay( + RoundedRectangle(cornerRadius: 16) + .stroke( + colorSchemeContrast == .increased ? + Color.primary.opacity(0.5) : .primary.opacity(0.12), + lineWidth: colorSchemeContrast == .increased ? 1.0 : 0.5 + ) + ) + } +} + +struct LiquidGlassChartModifier: ViewModifier { + @Environment(\.colorScheme) var colorScheme + @Environment(\.accessibilityReduceTransparency) var reduceTransparency + @Environment(\.colorSchemeContrast) var colorSchemeContrast + + func body(content: Content) -> some View { + content + .background(.ultraThinMaterial, in: RoundedRectangle(cornerRadius: 16)) + .shadow(color: .black.opacity(colorScheme == .dark ? 0.25 : 0.1), radius: 8, y: 4) + .overlay( + RoundedRectangle(cornerRadius: 16) + .stroke( + colorSchemeContrast == .increased ? + Color.primary.opacity(0.6) : .primary.opacity(0.2), + lineWidth: colorSchemeContrast == .increased ? 1.0 : 0.5 + ) + ) + .padding(.horizontal) + } +} + +extension View { + func liquidGlassCard() -> some View { + modifier(LiquidGlassCardModifier()) + } + + func liquidGlassSection() -> some View { + modifier(LiquidGlassSectionModifier()) + } + + func liquidGlassChart() -> some View { + modifier(LiquidGlassChartModifier()) + } +} diff --git a/Basic-Car-Maintenance/Shared/Odometer/Views/AddOdometerReadingView.swift b/Basic-Car-Maintenance/Shared/Odometer/Views/AddOdometerReadingView.swift index 2515c8aa..6312f288 100644 --- a/Basic-Car-Maintenance/Shared/Odometer/Views/AddOdometerReadingView.swift +++ b/Basic-Car-Maintenance/Shared/Odometer/Views/AddOdometerReadingView.swift @@ -34,43 +34,67 @@ struct AddOdometerReadingView: View { var body: some View { NavigationStack { - Form { - Section { - HStack { - TextField("Distance", value: $distance, format: .number) + ScrollView { + VStack(spacing: 20) { + VStack(spacing: 16) { + HStack { + Image(systemName: SFSymbol.speedometer) + .foregroundStyle(.secondary) + TextField("Distance", value: $distance, format: .number) + } Picker(selection: $isMetric) { - Text("Miles").tag(false) - Text("Kilometers").tag(true) + Text("Miles", comment: "Label for miles unit").tag(false) + Text("Kilometers", comment: "Label for kilometers unit").tag(true) } label: { Text("Preferred units", - comment: "Label for units selected when adding an odometer reading") + comment: "Label for unit system picker") } .pickerStyle(.segmented) } - } - - Section { - Picker(selection: $selectedVehicleID) { - ForEach(vehicles) { vehicle in - Text(vehicle.name) - .tag(vehicle.id) + .padding() + .liquidGlassSection() + + VStack(alignment: .leading, spacing: 12) { + Text("VehicleSectionHeader", + comment: "Label for Picker for selecting a vehicle") + .font(.caption) + .foregroundStyle(.secondary) + .padding(.leading, 8) + + HStack { + Image(systemName: SFSymbol.carFill) + .foregroundStyle(.secondary) + Picker(selection: $selectedVehicleID) { + ForEach(vehicles) { vehicle in + Text(vehicle.name) + .tag(vehicle.id as String?) + } + } label: { + Text("Select a vehicle", + comment: "Picker for selecting a vehicle") + } + .pickerStyle(.menu) } - } label: { - Text("Select a vehicle", - comment: "Picker for selecting a vehicle") + .padding() + .liquidGlassSection() } - .pickerStyle(.menu) - } header: { - Text("VehicleSectionHeader", - comment: "Label for Picker for selecting a vehicle") - } - - DatePicker(selection: $date, displayedComponents: .date) { - Text("Date", comment: "Date picker label") + + VStack { + DatePicker(selection: $date, displayedComponents: .date) { + Label { + Text("Date", comment: "Date picker label") + } icon: { + Image(systemName: SFSymbol.calendar) + } + } + .dynamicTypeSize(...DynamicTypeSize.accessibility2) + } + .padding() + .liquidGlassSection() } - .dynamicTypeSize(...DynamicTypeSize.accessibility2) } + .background(Color(UIColor.systemGroupedBackground).ignoresSafeArea()) .onAppear { if !vehicles.isEmpty { selectedVehicleID = vehicles[0].id diff --git a/Basic-Car-Maintenance/Shared/Odometer/Views/EditOdometerReadingView.swift b/Basic-Car-Maintenance/Shared/Odometer/Views/EditOdometerReadingView.swift index 69249e17..69579d9a 100644 --- a/Basic-Car-Maintenance/Shared/Odometer/Views/EditOdometerReadingView.swift +++ b/Basic-Car-Maintenance/Shared/Odometer/Views/EditOdometerReadingView.swift @@ -22,37 +22,62 @@ struct EditOdometerReadingView: View { var body: some View { NavigationStack { - Form { - Section { - HStack { - TextField("Distance", value: $distance, format: .number) + ScrollView { + VStack(spacing: 20) { + VStack(spacing: 16) { + HStack { + Image(systemName: SFSymbol.speedometer) + .foregroundStyle(.secondary) + TextField("Distance", value: $distance, format: .number) + } Picker(selection: $isMetric) { - Text("Miles").tag(false) - Text("Kilometers").tag(true) + Text("Miles", comment: "Label for miles unit").tag(false) + Text("Kilometers", comment: "Label for kilometers unit").tag(true) } label: { Text("Preferred units", - comment: "Label for units selected when adding an odometer reading") + comment: "Label for unit system picker") } .pickerStyle(.segmented) } - } - - Section { - if let vehicleName = vehicles - .filter({ $0.id == selectedReading.vehicleID }).first?.name { - Text(vehicleName) - .opacity(0.3) + .padding() + .liquidGlassSection() + + VStack(alignment: .leading, spacing: 12) { + Text("Vehicle", comment: "Label for vehicle selection") + .font(.caption) + .foregroundStyle(.secondary) + .padding(.leading, 8) + + HStack { + Image(systemName: SFSymbol.carFill) + .foregroundStyle(.secondary) + if let vehicleName = vehicles + .filter({ $0.id == selectedReading.vehicleID }).first?.name { + Text(vehicleName) + .foregroundStyle(.secondary) + } + } + .padding() + .frame(maxWidth: .infinity, alignment: .leading) + .liquidGlassSection() } - } header: { - Text("Vehicle") - } - - DatePicker(selection: $date, displayedComponents: .date) { - Text("Date", comment: "Date picker label") + + VStack { + DatePicker(selection: $date, displayedComponents: .date) { + Label { + Text("Date", comment: "Date picker label") + } icon: { + Image(systemName: SFSymbol.calendar) + } + } + .dynamicTypeSize(...DynamicTypeSize.accessibility2) + } + .padding() + .liquidGlassSection() } - .dynamicTypeSize(...DynamicTypeSize.accessibility2) } + .background(Color(UIColor.systemGroupedBackground).ignoresSafeArea()) .onAppear { setEditReadingValues(selectedReading) } diff --git a/Basic-Car-Maintenance/Shared/Odometer/Views/OdometerRowView.swift b/Basic-Car-Maintenance/Shared/Odometer/Views/OdometerRowView.swift index 046721a3..4b5a07cf 100644 --- a/Basic-Car-Maintenance/Shared/Odometer/Views/OdometerRowView.swift +++ b/Basic-Car-Maintenance/Shared/Odometer/Views/OdometerRowView.swift @@ -13,17 +13,29 @@ struct OdometerRowView: View { let vehicleName: String? var body: some View { - VStack(alignment: .leading, spacing: 8) { - Text("\(vehicleName ?? "No Name")") - .font(.title3) + VStack(alignment: .leading, spacing: 12) { + HStack { + Image(systemName: SFSymbol.carFill) + .foregroundStyle(.secondary) + Text(vehicleName ?? "No Name") + .font(.headline) + } - VStack(alignment: .leading, spacing: 4) { - Text("Mileage: \(reading.distance) \(reading.isMetric ? "km" : "mi")") - .foregroundStyle(.gray) + VStack(alignment: .leading, spacing: 8) { + Label { + Text("\(reading.distance) \(reading.isMetric ? "kilometers" : "miles")") + } icon: { + Image(systemName: SFSymbol.speedometer) + } - Text("Recorded On: \(reading.date.formatted(date: .abbreviated, time: .omitted))") - .foregroundStyle(.gray) + Label { + Text(reading.date.formatted(date: .abbreviated, time: .omitted)) + } icon: { + Image(systemName: SFSymbol.calendar) + } } + .font(.subheadline) + .foregroundStyle(.secondary) } } } diff --git a/Basic-Car-Maintenance/Shared/Odometer/Views/OdometerView.swift b/Basic-Car-Maintenance/Shared/Odometer/Views/OdometerView.swift index 7b54fd6a..a294537f 100644 --- a/Basic-Car-Maintenance/Shared/Odometer/Views/OdometerView.swift +++ b/Basic-Car-Maintenance/Shared/Odometer/Views/OdometerView.swift @@ -45,34 +45,35 @@ struct OdometerView: View { .padding(.horizontal) if !viewModel.readings.isEmpty { - GroupBox { - Chart { - ForEach(viewModel.vehicles) { vehicle in - let vehicleReadings = filteredReadings(for: vehicle) - - if !vehicleReadings.isEmpty { - ForEach(vehicleReadings) { reading in - LineMark( - x: .value("Date", reading.date, unit: .day), - y: .value("Odometer", reading.distance) - ) - } - .foregroundStyle(by: .value("Vehicle", vehicle.name)) - .symbol(by: .value("Vehicle", vehicle.name)) - .interpolationMethod(.monotone) + Chart { + ForEach(viewModel.vehicles) { vehicle in + let vehicleReadings = filteredReadings(for: vehicle) + + if !vehicleReadings.isEmpty { + ForEach(vehicleReadings) { reading in + LineMark( + x: .value("Date", reading.date, unit: .day), + y: .value("Odometer", reading.distance) + ) } + .foregroundStyle(by: .value("Vehicle", vehicle.name)) + .symbol(by: .value("Vehicle", vehicle.name)) + .interpolationMethod(.monotone) } } - .frame(height: 200) } - .padding(.horizontal) - .listRowSeparator(.hidden) + .frame(height: 200) + .liquidGlassChart() } List { ForEach(filteredReadings) { reading in let vehicleName = viewModel.vehicles.first { $0.id == reading.vehicleID }?.name OdometerRowView(reading: reading, vehicleName: vehicleName) + .listRowInsets(EdgeInsets(top: 8, leading: 16, bottom: 8, trailing: 16)) + .listRowBackground(Color.clear) + .listRowSeparator(.hidden) + .liquidGlassCard() .swipeActions(edge: .trailing, allowsFullSwipe: true) { Button(role: .destructive) { Task { @@ -94,13 +95,24 @@ struct OdometerView: View { } } } - .listStyle(.inset) } + .listStyle(.plain) + .scrollContentBackground(.hidden) + .background(Color(UIColor.systemGroupedBackground).ignoresSafeArea()) } .overlay { if viewModel.readings.isEmpty { - Text("Add your first odometer", - comment: "Placeholder text for empty odometer reading list") + ContentUnavailableView { + Label { + Text("Tap the + to begin", + comment: "Empty odometer list prompt") + } icon: { + Image(systemName: SFSymbol.speedometer) + } + } description: { + Text("Add your first odometer", + comment: "Placeholder description for empty odometer reading list") + } } } .navigationTitle(Text("Odometer")) diff --git a/Basic-Car-Maintenance/Shared/Utilities/Constants.swift b/Basic-Car-Maintenance/Shared/Utilities/Constants.swift index 2a9e17ab..954cca0f 100644 --- a/Basic-Car-Maintenance/Shared/Utilities/Constants.swift +++ b/Basic-Car-Maintenance/Shared/Utilities/Constants.swift @@ -65,11 +65,14 @@ enum SFSymbol { static let filter = "line.3.horizontal.decrease" static let plus = "plus" static let share = "square.and.arrow.up" + static let carFill = "car.fill" // Dashboard static let trash = "trash" static let pencil = "pencil" static let magnifyingGlass = "magnifyingglass" + static let speedometer = "speedometer" + static let calendar = "calendar" // Settings static let document = "doc.badge.plus" From e085e9edffcb5fe034f93203c4e86e5d0a3337d1 Mon Sep 17 00:00:00 2001 From: Mikaela Caron Date: Wed, 11 Feb 2026 19:54:00 -0700 Subject: [PATCH 02/10] Make button into an icon --- .../Views/AddOdometerReadingView.swift | 92 +++++++------------ 1 file changed, 34 insertions(+), 58 deletions(-) diff --git a/Basic-Car-Maintenance/Shared/Odometer/Views/AddOdometerReadingView.swift b/Basic-Car-Maintenance/Shared/Odometer/Views/AddOdometerReadingView.swift index 6312f288..3f507103 100644 --- a/Basic-Car-Maintenance/Shared/Odometer/Views/AddOdometerReadingView.swift +++ b/Basic-Car-Maintenance/Shared/Odometer/Views/AddOdometerReadingView.swift @@ -34,67 +34,43 @@ struct AddOdometerReadingView: View { var body: some View { NavigationStack { - ScrollView { - VStack(spacing: 20) { - VStack(spacing: 16) { - HStack { - Image(systemName: SFSymbol.speedometer) - .foregroundStyle(.secondary) - TextField("Distance", value: $distance, format: .number) - } + Form { + Section { + HStack { + TextField("Distance", value: $distance, format: .number) Picker(selection: $isMetric) { - Text("Miles", comment: "Label for miles unit").tag(false) - Text("Kilometers", comment: "Label for kilometers unit").tag(true) + Text("Miles").tag(false) + Text("Kilometers").tag(true) } label: { Text("Preferred units", - comment: "Label for unit system picker") + comment: "Label for units selected when adding an odometer reading") } .pickerStyle(.segmented) } - .padding() - .liquidGlassSection() - - VStack(alignment: .leading, spacing: 12) { - Text("VehicleSectionHeader", - comment: "Label for Picker for selecting a vehicle") - .font(.caption) - .foregroundStyle(.secondary) - .padding(.leading, 8) - - HStack { - Image(systemName: SFSymbol.carFill) - .foregroundStyle(.secondary) - Picker(selection: $selectedVehicleID) { - ForEach(vehicles) { vehicle in - Text(vehicle.name) - .tag(vehicle.id as String?) - } - } label: { - Text("Select a vehicle", - comment: "Picker for selecting a vehicle") - } - .pickerStyle(.menu) - } - .padding() - .liquidGlassSection() - } - - VStack { - DatePicker(selection: $date, displayedComponents: .date) { - Label { - Text("Date", comment: "Date picker label") - } icon: { - Image(systemName: SFSymbol.calendar) - } + } + + Section { + Picker(selection: $selectedVehicleID) { + ForEach(vehicles) { vehicle in + Text(vehicle.name) + .tag(vehicle.id) } - .dynamicTypeSize(...DynamicTypeSize.accessibility2) + } label: { + Text("Select a vehicle", + comment: "Picker for selecting a vehicle") } - .padding() - .liquidGlassSection() + .pickerStyle(.menu) + } header: { + Text("VehicleSectionHeader", + comment: "Label for Picker for selecting a vehicle") + } + + DatePicker(selection: $date, displayedComponents: .date) { + Text("Date", comment: "Date picker label") } + .dynamicTypeSize(...DynamicTypeSize.accessibility2) } - .background(Color(UIColor.systemGroupedBackground).ignoresSafeArea()) .onAppear { if !vehicles.isEmpty { selectedVehicleID = vehicles[0].id @@ -104,7 +80,7 @@ struct AddOdometerReadingView: View { comment: "Title for form when adding an odometer reading")) .toolbar { ToolbarItem { - Button { + Button(role: .confirm) { if let selectedVehicleID { let reading = OdometerReading(date: date, distance: distance, @@ -113,8 +89,8 @@ struct AddOdometerReadingView: View { addTapped(reading) } } label: { - Text("Add", - comment: "Label for submit button on form to add an entry") + Label("Add", systemImage: "checkmark") + .labelStyle(.iconOnly) } .disabled(distance < 0) } @@ -125,10 +101,10 @@ struct AddOdometerReadingView: View { } #Preview { + let sampleVehicle = [ + Vehicle(name: "Nate Forester", make: "Subaru", model: "Forester"), + Vehicle(name: "Dani Impreza", make: "Subaru", model: "Impreza") + ] + AddOdometerReadingView(vehicles: sampleVehicles) { _ in } } - -let sampleVehicle = [ - Vehicle(name: "Nate Forester", make: "Subaru", model: "Forester"), - Vehicle(name: "Dani Impreza", make: "Subaru", model: "Impreza") -] From 606977551b6da0b22386d8d443e5f3559bae1638 Mon Sep 17 00:00:00 2001 From: Mikaela Caron Date: Wed, 11 Feb 2026 20:03:59 -0700 Subject: [PATCH 03/10] Add back icons --- .../Views/AddOdometerReadingView.swift | 34 +++++++++++++------ .../Shared/Utilities/Constants.swift | 2 +- 2 files changed, 24 insertions(+), 12 deletions(-) diff --git a/Basic-Car-Maintenance/Shared/Odometer/Views/AddOdometerReadingView.swift b/Basic-Car-Maintenance/Shared/Odometer/Views/AddOdometerReadingView.swift index 3f507103..c1ca7bd7 100644 --- a/Basic-Car-Maintenance/Shared/Odometer/Views/AddOdometerReadingView.swift +++ b/Basic-Car-Maintenance/Shared/Odometer/Views/AddOdometerReadingView.swift @@ -37,6 +37,8 @@ struct AddOdometerReadingView: View { Form { Section { HStack { + Image(systemName: SFSymbol.gaugeWithNeedle) + .foregroundStyle(.secondary) TextField("Distance", value: $distance, format: .number) Picker(selection: $isMetric) { @@ -51,25 +53,35 @@ struct AddOdometerReadingView: View { } Section { - Picker(selection: $selectedVehicleID) { - ForEach(vehicles) { vehicle in - Text(vehicle.name) - .tag(vehicle.id) + HStack { + Image(systemName: SFSymbol.carFill) + .foregroundStyle(.secondary) + + Picker(selection: $selectedVehicleID) { + ForEach(vehicles) { vehicle in + Text(vehicle.name) + .tag(vehicle.id) + } + } label: { + Text("Select a vehicle", + comment: "Picker for selecting a vehicle") } - } label: { - Text("Select a vehicle", - comment: "Picker for selecting a vehicle") + .pickerStyle(.menu) } - .pickerStyle(.menu) } header: { Text("VehicleSectionHeader", comment: "Label for Picker for selecting a vehicle") } - DatePicker(selection: $date, displayedComponents: .date) { - Text("Date", comment: "Date picker label") + HStack { + Image(systemName: SFSymbol.calendar) + .foregroundStyle(.secondary) + + DatePicker(selection: $date, displayedComponents: .date) { + Text("Date", comment: "Date picker label") + } + .dynamicTypeSize(...DynamicTypeSize.accessibility2) } - .dynamicTypeSize(...DynamicTypeSize.accessibility2) } .onAppear { if !vehicles.isEmpty { diff --git a/Basic-Car-Maintenance/Shared/Utilities/Constants.swift b/Basic-Car-Maintenance/Shared/Utilities/Constants.swift index 954cca0f..73353b51 100644 --- a/Basic-Car-Maintenance/Shared/Utilities/Constants.swift +++ b/Basic-Car-Maintenance/Shared/Utilities/Constants.swift @@ -71,7 +71,7 @@ enum SFSymbol { static let trash = "trash" static let pencil = "pencil" static let magnifyingGlass = "magnifyingglass" - static let speedometer = "speedometer" + static let gaugeWithNeedle = "gauge.with.needle" static let calendar = "calendar" // Settings From 20efc2e14ebed5f65590d325175cce7d57a4a834 Mon Sep 17 00:00:00 2001 From: Mikaela Caron Date: Wed, 11 Feb 2026 20:07:02 -0700 Subject: [PATCH 04/10] Add back comments --- .../Shared/Odometer/Views/AddOdometerReadingView.swift | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/Basic-Car-Maintenance/Shared/Odometer/Views/AddOdometerReadingView.swift b/Basic-Car-Maintenance/Shared/Odometer/Views/AddOdometerReadingView.swift index c1ca7bd7..b68c940a 100644 --- a/Basic-Car-Maintenance/Shared/Odometer/Views/AddOdometerReadingView.swift +++ b/Basic-Car-Maintenance/Shared/Odometer/Views/AddOdometerReadingView.swift @@ -42,8 +42,10 @@ struct AddOdometerReadingView: View { TextField("Distance", value: $distance, format: .number) Picker(selection: $isMetric) { - Text("Miles").tag(false) - Text("Kilometers").tag(true) + Text("Miles", comment: "Label for miles unit") + .tag(false) + Text("Kilometers", comment: "Label for kilometers unit") + .tag(true) } label: { Text("Preferred units", comment: "Label for units selected when adding an odometer reading") From 816eeddb1a171877f9e98eb53f16ffeee3baace6 Mon Sep 17 00:00:00 2001 From: Mikaela Caron Date: Wed, 11 Feb 2026 20:19:58 -0700 Subject: [PATCH 05/10] Update EditOdometerReadingView --- .../Views/EditOdometerReadingView.swift | 90 +++++++++---------- 1 file changed, 41 insertions(+), 49 deletions(-) diff --git a/Basic-Car-Maintenance/Shared/Odometer/Views/EditOdometerReadingView.swift b/Basic-Car-Maintenance/Shared/Odometer/Views/EditOdometerReadingView.swift index 69579d9a..cdc5c3a9 100644 --- a/Basic-Car-Maintenance/Shared/Odometer/Views/EditOdometerReadingView.swift +++ b/Basic-Car-Maintenance/Shared/Odometer/Views/EditOdometerReadingView.swift @@ -22,62 +22,51 @@ struct EditOdometerReadingView: View { var body: some View { NavigationStack { - ScrollView { - VStack(spacing: 20) { - VStack(spacing: 16) { - HStack { - Image(systemName: SFSymbol.speedometer) - .foregroundStyle(.secondary) - TextField("Distance", value: $distance, format: .number) - } + Form { + Section { + HStack { + Image(systemName: SFSymbol.gaugeWithNeedle) + .foregroundStyle(.secondary) + TextField("Distance", value: $distance, format: .number) Picker(selection: $isMetric) { - Text("Miles", comment: "Label for miles unit").tag(false) - Text("Kilometers", comment: "Label for kilometers unit").tag(true) + Text("Miles", comment: "Label for miles unit") + .tag(false) + Text("Kilometers", comment: "Label for kilometers unit") + .tag(true) } label: { Text("Preferred units", - comment: "Label for unit system picker") + comment: "Label for units selected when adding an odometer reading") } .pickerStyle(.segmented) } - .padding() - .liquidGlassSection() - - VStack(alignment: .leading, spacing: 12) { - Text("Vehicle", comment: "Label for vehicle selection") - .font(.caption) + } + + Section { + HStack { + Image(systemName: SFSymbol.carFill) .foregroundStyle(.secondary) - .padding(.leading, 8) - HStack { - Image(systemName: SFSymbol.carFill) - .foregroundStyle(.secondary) - if let vehicleName = vehicles - .filter({ $0.id == selectedReading.vehicleID }).first?.name { - Text(vehicleName) - .foregroundStyle(.secondary) - } + if let vehicleName = vehicles + .filter({ $0.id == selectedReading.vehicleID }).first?.name { + Text(vehicleName) + .opacity(0.3) } - .padding() - .frame(maxWidth: .infinity, alignment: .leading) - .liquidGlassSection() } + } header: { + Text("Vehicle") + } + + HStack { + Image(systemName: SFSymbol.calendar) + .foregroundStyle(.secondary) - VStack { - DatePicker(selection: $date, displayedComponents: .date) { - Label { - Text("Date", comment: "Date picker label") - } icon: { - Image(systemName: SFSymbol.calendar) - } - } - .dynamicTypeSize(...DynamicTypeSize.accessibility2) + DatePicker(selection: $date, displayedComponents: .date) { + Text("Date", comment: "Date picker label") } - .padding() - .liquidGlassSection() + .dynamicTypeSize(...DynamicTypeSize.accessibility2) } } - .background(Color(UIColor.systemGroupedBackground).ignoresSafeArea()) .onAppear { setEditReadingValues(selectedReading) } @@ -85,15 +74,13 @@ struct EditOdometerReadingView: View { comment: "Title for form when editing an odometer reading")) .toolbar { ToolbarItem(placement: .topBarLeading) { - Button { + Button(role: .cancel) { dismiss() - } label: { - Text("Cancel") } } ToolbarItem { - Button { + Button(role: .confirm) { let reading = OdometerReading(id: selectedReading.id, date: date, distance: distance, @@ -101,8 +88,8 @@ struct EditOdometerReadingView: View { vehicleID: selectedReading.vehicleID) updateTapped(reading) } label: { - Text("Update", - comment: "Label for submit button on form to update an existing entry") + Label("Update", systemImage: "checkmark") + .labelStyle(.iconOnly) } .disabled(distance < 0) } @@ -119,10 +106,15 @@ struct EditOdometerReadingView: View { } #Preview { + let sampleVehicles = [ + Vehicle(id: UUID().uuidString, name: "Nate Forester", make: "Subaru", model: "Forester"), + Vehicle(id: UUID().uuidString, name: "Dani Impreza", make: "Subaru", model: "Impreza") + ] + EditOdometerReadingView( selectedReading: OdometerReading(date: Date(), distance: 0, isMetric: false, - vehicleID: ""), - vehicles: []) { _ in } + vehicleID: sampleVehicles[0].id!), + vehicles: sampleVehicles) { _ in } } From dbc7a10e631f8b297e52798e030a46d6717ebf41 Mon Sep 17 00:00:00 2001 From: Mikaela Caron Date: Wed, 11 Feb 2026 20:20:09 -0700 Subject: [PATCH 06/10] Fix previews --- .../Shared/Dashboard/Views/AddMaintenanceView.swift | 10 +++++----- .../Shared/Odometer/Views/AddOdometerReadingView.swift | 2 +- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/Basic-Car-Maintenance/Shared/Dashboard/Views/AddMaintenanceView.swift b/Basic-Car-Maintenance/Shared/Dashboard/Views/AddMaintenanceView.swift index 098b6f69..81a8ce6e 100644 --- a/Basic-Car-Maintenance/Shared/Dashboard/Views/AddMaintenanceView.swift +++ b/Basic-Car-Maintenance/Shared/Dashboard/Views/AddMaintenanceView.swift @@ -99,10 +99,10 @@ struct AddMaintenanceView: View { } #Preview { + let sampleVehicles = [ + Vehicle(name: "Lexus", make: "Lexus", model: "White"), + Vehicle(name: "Test", make: "Lexus", model: "White") + ] + AddMaintenanceView(vehicles: sampleVehicles) { _ in } } - -let sampleVehicles = [ - Vehicle(name: "Lexus", make: "Lexus", model: "White"), - Vehicle(name: "Test", make: "Lexus", model: "White") -] diff --git a/Basic-Car-Maintenance/Shared/Odometer/Views/AddOdometerReadingView.swift b/Basic-Car-Maintenance/Shared/Odometer/Views/AddOdometerReadingView.swift index b68c940a..b36ec03b 100644 --- a/Basic-Car-Maintenance/Shared/Odometer/Views/AddOdometerReadingView.swift +++ b/Basic-Car-Maintenance/Shared/Odometer/Views/AddOdometerReadingView.swift @@ -115,7 +115,7 @@ struct AddOdometerReadingView: View { } #Preview { - let sampleVehicle = [ + let sampleVehicles = [ Vehicle(name: "Nate Forester", make: "Subaru", model: "Forester"), Vehicle(name: "Dani Impreza", make: "Subaru", model: "Impreza") ] From 196932f41cd30ec392e977972a0cf754de92af6e Mon Sep 17 00:00:00 2001 From: Mikaela Caron Date: Wed, 11 Feb 2026 20:26:18 -0700 Subject: [PATCH 07/10] Update OdometerRowView --- .../Odometer/Views/OdometerRowView.swift | 18 ++++++++---------- 1 file changed, 8 insertions(+), 10 deletions(-) diff --git a/Basic-Car-Maintenance/Shared/Odometer/Views/OdometerRowView.swift b/Basic-Car-Maintenance/Shared/Odometer/Views/OdometerRowView.swift index 4b5a07cf..1f1ca645 100644 --- a/Basic-Car-Maintenance/Shared/Odometer/Views/OdometerRowView.swift +++ b/Basic-Car-Maintenance/Shared/Odometer/Views/OdometerRowView.swift @@ -13,19 +13,19 @@ struct OdometerRowView: View { let vehicleName: String? var body: some View { - VStack(alignment: .leading, spacing: 12) { + VStack(alignment: .leading, spacing: 8) { HStack { Image(systemName: SFSymbol.carFill) - .foregroundStyle(.secondary) + Text(vehicleName ?? "No Name") - .font(.headline) + .font(.title3) } - - VStack(alignment: .leading, spacing: 8) { + + VStack(alignment: .leading, spacing: 4) { Label { - Text("\(reading.distance) \(reading.isMetric ? "kilometers" : "miles")") + Text("\(reading.distance) \(reading.isMetric ? "km" : "mi")") } icon: { - Image(systemName: SFSymbol.speedometer) + Image(systemName: SFSymbol.gaugeWithNeedle) } Label { @@ -34,13 +34,11 @@ struct OdometerRowView: View { Image(systemName: SFSymbol.calendar) } } - .font(.subheadline) - .foregroundStyle(.secondary) } } } #Preview { OdometerRowView(reading: .init(date: .now, distance: 1000, isMetric: true, vehicleID: "1234"), - vehicleName: "Sample Vehicle Name") + vehicleName: "Sample Vehicle Name") } From bee811be04ab08ee578bd478b7cbfa226f141567 Mon Sep 17 00:00:00 2001 From: Mikaela Caron Date: Wed, 11 Feb 2026 20:34:45 -0700 Subject: [PATCH 08/10] Update OdometerView --- .../Shared/Odometer/Views/OdometerView.swift | 50 +++++++++---------- 1 file changed, 24 insertions(+), 26 deletions(-) diff --git a/Basic-Car-Maintenance/Shared/Odometer/Views/OdometerView.swift b/Basic-Car-Maintenance/Shared/Odometer/Views/OdometerView.swift index a294537f..b22d39ae 100644 --- a/Basic-Car-Maintenance/Shared/Odometer/Views/OdometerView.swift +++ b/Basic-Car-Maintenance/Shared/Odometer/Views/OdometerView.swift @@ -45,35 +45,35 @@ struct OdometerView: View { .padding(.horizontal) if !viewModel.readings.isEmpty { - Chart { - ForEach(viewModel.vehicles) { vehicle in - let vehicleReadings = filteredReadings(for: vehicle) - - if !vehicleReadings.isEmpty { - ForEach(vehicleReadings) { reading in - LineMark( - x: .value("Date", reading.date, unit: .day), - y: .value("Odometer", reading.distance) - ) + GroupBox { + Chart { + ForEach(viewModel.vehicles) { vehicle in + let vehicleReadings = filteredReadings(for: vehicle) + + if !vehicleReadings.isEmpty { + ForEach(vehicleReadings) { reading in + LineMark( + x: .value("Date", reading.date, unit: .day), + y: .value("Odometer", reading.distance) + ) + } + .foregroundStyle(by: .value("Vehicle", vehicle.name)) + .symbol(by: .value("Vehicle", vehicle.name)) + .interpolationMethod(.monotone) } - .foregroundStyle(by: .value("Vehicle", vehicle.name)) - .symbol(by: .value("Vehicle", vehicle.name)) - .interpolationMethod(.monotone) } } + .frame(height: 200) } - .frame(height: 200) - .liquidGlassChart() + .padding(.horizontal) + .listRowSeparator(.hidden) } List { ForEach(filteredReadings) { reading in let vehicleName = viewModel.vehicles.first { $0.id == reading.vehicleID }?.name OdometerRowView(reading: reading, vehicleName: vehicleName) - .listRowInsets(EdgeInsets(top: 8, leading: 16, bottom: 8, trailing: 16)) - .listRowBackground(Color.clear) - .listRowSeparator(.hidden) - .liquidGlassCard() + .foregroundStyle(.primary) .swipeActions(edge: .trailing, allowsFullSwipe: true) { Button(role: .destructive) { Task { @@ -95,22 +95,20 @@ struct OdometerView: View { } } } + .listStyle(.inset) } - .listStyle(.plain) - .scrollContentBackground(.hidden) - .background(Color(UIColor.systemGroupedBackground).ignoresSafeArea()) } .overlay { if viewModel.readings.isEmpty { ContentUnavailableView { Label { - Text("Tap the + to begin", - comment: "Empty odometer list prompt") + Text("Add your first odometer reading", + comment: "Placeholder text for empty odometer reading list") } icon: { - Image(systemName: SFSymbol.speedometer) + Image(systemName: SFSymbol.gaugeWithNeedle) } } description: { - Text("Add your first odometer", + Text("Tap the + tadd your firsst odometer reading", comment: "Placeholder description for empty odometer reading list") } } From 66f86da9990f8c3588f6b8d8232f08640bcb9433 Mon Sep 17 00:00:00 2001 From: Mikaela Caron Date: Wed, 11 Feb 2026 20:36:17 -0700 Subject: [PATCH 09/10] Update Localizable.xcstrings --- .../Shared/Localizable.xcstrings | 52 +++++++++---------- 1 file changed, 24 insertions(+), 28 deletions(-) diff --git a/Basic-Car-Maintenance/Shared/Localizable.xcstrings b/Basic-Car-Maintenance/Shared/Localizable.xcstrings index 91f0f2ce..7bf8901c 100644 --- a/Basic-Car-Maintenance/Shared/Localizable.xcstrings +++ b/Basic-Car-Maintenance/Shared/Localizable.xcstrings @@ -78,6 +78,18 @@ } } }, + "%lld %@" : { + "comment" : "A text label followed by a value, indicating the distance traveled by a vehicle. The value is formatted as a number followed by a unit of distance (either kilometers or miles).", + "isCommentAutoGenerated" : true, + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "new", + "value" : "%1$lld %2$@" + } + } + } + }, "^[%lld contributions](inflect: true)" : { "comment" : "the number of contributions by a contributor", "localizations" : { @@ -614,7 +626,8 @@ } }, "Add your first odometer" : { - "comment" : "Placeholder text for empty odometer reading list", + "comment" : "Placeholder description for empty odometer reading list", + "extractionState" : "stale", "localizations" : { "de" : { "stringUnit" : { @@ -666,6 +679,9 @@ } } }, + "Add your first odometer reading" : { + "comment" : "Placeholder text for empty odometer reading list" + }, "AddEvent" : { "comment" : "Label for adding maintenance event on Dashboard view", "localizations" : { @@ -2931,6 +2947,7 @@ } }, "Kilometers" : { + "comment" : "Label for kilometers unit", "localizations" : { "de" : { "stringUnit" : { @@ -3276,23 +3293,8 @@ } } }, - "Mileage: %lld %@" : { - "localizations" : { - "en" : { - "stringUnit" : { - "state" : "new", - "value" : "Mileage: %1$lld %2$@" - } - }, - "nl" : { - "stringUnit" : { - "state" : "translated", - "value" : "Verbruik: %1$lld %2$@" - } - } - } - }, "Miles" : { + "comment" : "Label for miles unit", "localizations" : { "de" : { "stringUnit" : { @@ -4285,16 +4287,6 @@ "Rate this app" : { "comment" : "Link to rate the app." }, - "Recorded On: %@" : { - "localizations" : { - "nl" : { - "stringUnit" : { - "state" : "translated", - "value" : "Opgenomen op: %@" - } - } - } - }, "Report a Bug" : { "comment" : "Link to report a bug", "localizations" : { @@ -4863,7 +4855,11 @@ } } }, + "Tap the + tadd your firsst odometer reading" : { + "comment" : "Placeholder description for empty odometer reading list" + }, "Tap the + to begin" : { + "comment" : "Empty odometer list prompt", "localizations" : { "fa" : { "stringUnit" : { @@ -6753,5 +6749,5 @@ } } }, - "version" : "1.0" + "version" : "1.1" } \ No newline at end of file From 9f3a337ecb678b0fe09190bea2941ab230c4a94d Mon Sep 17 00:00:00 2001 From: Mikaela Caron Date: Wed, 11 Feb 2026 20:39:45 -0700 Subject: [PATCH 10/10] Remove Glass modifiers --- .../Shared/Design/LiquidGlassModifiers.swift | 80 ------------------- 1 file changed, 80 deletions(-) delete mode 100644 Basic-Car-Maintenance/Shared/Design/LiquidGlassModifiers.swift diff --git a/Basic-Car-Maintenance/Shared/Design/LiquidGlassModifiers.swift b/Basic-Car-Maintenance/Shared/Design/LiquidGlassModifiers.swift deleted file mode 100644 index e3ce703c..00000000 --- a/Basic-Car-Maintenance/Shared/Design/LiquidGlassModifiers.swift +++ /dev/null @@ -1,80 +0,0 @@ -import SwiftUI - -struct LiquidGlassCardModifier: ViewModifier { - @Environment(\.colorScheme) var colorScheme - @Environment(\.accessibilityReduceTransparency) var reduceTransparency - @Environment(\.colorSchemeContrast) var colorSchemeContrast - - func body(content: Content) -> some View { - content - .background( - reduceTransparency ? - Color(UIColor.secondarySystemGroupedBackground) : Color.clear - ) - .background(.ultraThinMaterial, in: RoundedRectangle(cornerRadius: 16)) - .shadow(color: .black.opacity(colorScheme == .dark ? 0.3 : 0.1), radius: 6, y: 3) - .overlay( - RoundedRectangle(cornerRadius: 16) - .stroke( - colorSchemeContrast == .increased ? - Color.primary.opacity(0.8) : .primary.opacity(0.15), - lineWidth: colorSchemeContrast == .increased ? 2.0 : 0.5 - ) - ) - } -} - -struct LiquidGlassSectionModifier: ViewModifier { - @Environment(\.colorScheme) var colorScheme - @Environment(\.accessibilityReduceTransparency) var reduceTransparency - @Environment(\.colorSchemeContrast) var colorSchemeContrast - - func body(content: Content) -> some View { - content - .background(.thinMaterial, in: RoundedRectangle(cornerRadius: 16)) - .shadow(color: .black.opacity(colorScheme == .dark ? 0.2 : 0.08), radius: 4, y: 2) - .overlay( - RoundedRectangle(cornerRadius: 16) - .stroke( - colorSchemeContrast == .increased ? - Color.primary.opacity(0.5) : .primary.opacity(0.12), - lineWidth: colorSchemeContrast == .increased ? 1.0 : 0.5 - ) - ) - } -} - -struct LiquidGlassChartModifier: ViewModifier { - @Environment(\.colorScheme) var colorScheme - @Environment(\.accessibilityReduceTransparency) var reduceTransparency - @Environment(\.colorSchemeContrast) var colorSchemeContrast - - func body(content: Content) -> some View { - content - .background(.ultraThinMaterial, in: RoundedRectangle(cornerRadius: 16)) - .shadow(color: .black.opacity(colorScheme == .dark ? 0.25 : 0.1), radius: 8, y: 4) - .overlay( - RoundedRectangle(cornerRadius: 16) - .stroke( - colorSchemeContrast == .increased ? - Color.primary.opacity(0.6) : .primary.opacity(0.2), - lineWidth: colorSchemeContrast == .increased ? 1.0 : 0.5 - ) - ) - .padding(.horizontal) - } -} - -extension View { - func liquidGlassCard() -> some View { - modifier(LiquidGlassCardModifier()) - } - - func liquidGlassSection() -> some View { - modifier(LiquidGlassSectionModifier()) - } - - func liquidGlassChart() -> some View { - modifier(LiquidGlassChartModifier()) - } -}