From 767d9a356d29a8f34a8e54f534e9430cd0e39997 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marcin=20J=C4=99drzejak?= Date: Sun, 4 Aug 2024 02:08:19 +0200 Subject: [PATCH 1/7] feat: Connect WelcomeView and WelcomeViewAddVehicle to App Flow --- .../test.colorset/Contents.json | 38 +++ .../Shared/BasicCarMaintenanceApp.swift | 12 +- .../Shared/Localizable.xcstrings | 20 +- .../Views/WelcomeViewAddVehicle.swift | 228 ++++++++++++------ .../Shared/Settings/Views/SettingsView.swift | 36 +++ 5 files changed, 249 insertions(+), 85 deletions(-) create mode 100644 Basic-Car-Maintenance-Widget/Assets.xcassets/test.colorset/Contents.json diff --git a/Basic-Car-Maintenance-Widget/Assets.xcassets/test.colorset/Contents.json b/Basic-Car-Maintenance-Widget/Assets.xcassets/test.colorset/Contents.json new file mode 100644 index 00000000..be9d677b --- /dev/null +++ b/Basic-Car-Maintenance-Widget/Assets.xcassets/test.colorset/Contents.json @@ -0,0 +1,38 @@ +{ + "colors" : [ + { + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "1.000", + "blue" : "0x00", + "green" : "0x00", + "red" : "0x00" + } + }, + "idiom" : "universal" + }, + { + "appearances" : [ + { + "appearance" : "luminosity", + "value" : "dark" + } + ], + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "1.000", + "blue" : "0x00", + "green" : "0x00", + "red" : "0x00" + } + }, + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Basic-Car-Maintenance/Shared/BasicCarMaintenanceApp.swift b/Basic-Car-Maintenance/Shared/BasicCarMaintenanceApp.swift index 02b2527e..25e91d38 100644 --- a/Basic-Car-Maintenance/Shared/BasicCarMaintenanceApp.swift +++ b/Basic-Car-Maintenance/Shared/BasicCarMaintenanceApp.swift @@ -14,8 +14,8 @@ struct BasicCarMaintenanceApp: App { @State private var actionService = ActionService.shared @UIApplicationDelegateAdaptor(AppDelegate.self) var delegate - // Logic to load Onboarding screen when app was first launched -// @AppStorage("isFirstTime") private var isFirstTime: Bool = true + // Logic to load OnboardingScreen when app first launch + @AppStorage("isFirstTime") private var isFirstTime: Bool = true var body: some Scene { WindowGroup { @@ -25,10 +25,10 @@ struct BasicCarMaintenanceApp: App { .task { try? Tips.configure() } -// .sheet(isPresented: $isFirstTime) { -// WelcomeView() -// .interactiveDismissDisabled() -// } + .sheet(isPresented: $isFirstTime) { + WelcomeView() + .interactiveDismissDisabled() + } } } } diff --git a/Basic-Car-Maintenance/Shared/Localizable.xcstrings b/Basic-Car-Maintenance/Shared/Localizable.xcstrings index 8eee8845..8066d075 100644 --- a/Basic-Car-Maintenance/Shared/Localizable.xcstrings +++ b/Basic-Car-Maintenance/Shared/Localizable.xcstrings @@ -1119,6 +1119,9 @@ } } } + }, + "Congratulations! 🎉 🚙" : { + }, "Continue" : { @@ -2480,6 +2483,9 @@ } } }, + "From now on, next screen will be the main app screen." : { + "comment" : "Shows popup when completed Onboarding tutorial" + }, "GitHub Repo" : { "comment" : "Link to the Basic Car Maintenance GitHub repo.", "localizations" : { @@ -2741,6 +2747,9 @@ } } } + }, + "Locally Saved Vehicle" : { + }, "Logged in anonymously with ID: %@" : { "localizations" : { @@ -5005,6 +5014,12 @@ } } } + }, + "Vehicle %@" : { + + }, + "Vehicle %@ cannot be empty! 🚗" : { + }, "Vehicle Color" : { "localizations" : { @@ -5784,9 +5799,6 @@ } } } - }, - "Welcome 🥳" : { - }, "Welcome to" : { @@ -5863,4 +5875,4 @@ } }, "version" : "1.0" -} +} \ No newline at end of file diff --git a/Basic-Car-Maintenance/Shared/Onboarding/Views/WelcomeViewAddVehicle.swift b/Basic-Car-Maintenance/Shared/Onboarding/Views/WelcomeViewAddVehicle.swift index 81babd68..6115e4f8 100644 --- a/Basic-Car-Maintenance/Shared/Onboarding/Views/WelcomeViewAddVehicle.swift +++ b/Basic-Car-Maintenance/Shared/Onboarding/Views/WelcomeViewAddVehicle.swift @@ -9,99 +9,177 @@ import SwiftUI struct WelcomeViewAddVehicle: View { - // Logic to remember Onboarding screen to not load again when app is launched -// @AppStorage("isFirstTime") private var isFirstTime: Bool = true + // Logic to remember OnboardingScreen to not load again when app is launched + @AppStorage("isFirstTime") private var isFirstTime: Bool = true + @AppStorage("vehicleName") private var vehicleName: String = "" + @AppStorage("vehicleMake") private var vehicleMake: String = "" + @AppStorage("vehicleModel") private var vehicleModel: String = "" @Environment(\.dismiss) var dismiss - - @State private var vehicleName: String = "" - @State private var vehicleMake: String = "" - @State private var vehicleModel: String = "" + @State private var validationAlertName: Bool = false + @State private var validationAlertMake: Bool = false + @State private var validationAlertModel: Bool = false + @State private var onboardingViewCompletedAlert: Bool = false var body: some View { VStack(spacing: 15) { VStack { - Text("Add the details below") - HStack(spacing: 5) { - Text("about") - Text("your vehicle") - .foregroundStyle(Color("basicGreen")) - } + headerView + vehicleDetailsView + bottomText + Spacer(minLength: 10) + addVehicleButton } - .font(.largeTitle.bold()) - .multilineTextAlignment(.center) - .padding(.top, 65) - .padding(.bottom, 15) - - VStack { - Image(systemName: "car.side.lock.open") - .font(.system(size: 45)) + } + .frame(maxWidth: .infinity, maxHeight: .infinity) + .background(Color(UIColor.secondarySystemBackground).ignoresSafeArea()) + .navigationBarBackButtonHidden(true) + .toolbar { backButton } + } + + private var headerView: some View { + VStack { + Text("Add the details below") + HStack(spacing: 5) { + Text("about") + Text("your vehicle") .foregroundStyle(Color("basicGreen")) - - List { - HStack { - Text("Name") - Spacer() - .frame(width: 40) - TextField("Vehicle Name", text: $vehicleName) - } - - HStack { - Text("Make") - Spacer() - .frame(width: 45) - TextField("Vehicle Make", text: $vehicleMake) - } - HStack { - Text("Model") - Spacer() - .frame(width: 40) - TextField("Vehicle Model", text: $vehicleModel) - } - } - .frame(maxHeight: 200) } - .frame(maxWidth: .infinity, alignment: .leading) - .padding(.horizontal, 15) - - Text("You can edit more data about the vehicle in the 'Settings' tab.") - .foregroundStyle(.gray) - .padding(.horizontal, 25) + } + .font(.largeTitle.bold()) + .multilineTextAlignment(.center) + .padding(.top, 65) + .padding(.bottom, 15) + } + + private var vehicleDetailsView: some View { + VStack { + Image(systemName: "car.side.lock.open") + .font(.system(size: 45)) + .foregroundStyle(Color("basicGreen")) - Spacer(minLength: 10) + List { + vehicleDetailRow(title: "Name", text: $vehicleName, alert: $validationAlertName) + vehicleDetailRow(title: "Make", text: $vehicleMake, alert: $validationAlertMake) + vehicleDetailRow(title: "Model", text: $vehicleModel, alert: $validationAlertModel) + } + .frame(maxHeight: 200) + .scrollContentBackground(.hidden) + } + .padding(.horizontal, 15) + } + + private func vehicleDetailRow(title: String, text: Binding, alert: Binding) -> some View { + LabeledContent { + TextField("Vehicle \(title)", text: text) + .alert( + "Vehicle \(title) cannot be empty! 🚗", + isPresented: alert + ) { + Button("OK", role: .cancel) {} + } + .frame(width: 200) + } label: { + HStack { + Text(title) + Spacer() + } + } + .showClearButton(text) + } + + private var bottomText: some View { + Text("You can edit more data about the vehicle in the 'Settings' tab.") + .foregroundStyle(.gray) + .padding(.horizontal, 25) + } + + private var addVehicleButton: some View { + Button { + addVehicle() + } label: { + Text("Add Vehicle") + .fontWeight(.bold) + .foregroundStyle(.white) + .frame(maxWidth: .infinity) + .padding(.vertical, 14) + .background(Color("basicGreen").gradient, in: .rect(cornerRadius: 12)) + .contentShape(.rect) + } + .padding(15) + .padding(.horizontal, 15) + .alert( + Text("Congratulations! 🎉 🚙"), + isPresented: $onboardingViewCompletedAlert + ) { + Button("OK", role: .cancel, action: { + isFirstTime = false + }) + } message: { + Text( + "From now on, next screen will be the main app screen.", + comment: "Shows popup when completed Onboarding tutorial" + ) + } + } + + func addVehicle() { + if vehicleName.isEmpty { + validationAlertName = true + } else if vehicleMake.isEmpty { + validationAlertMake = true + } else if vehicleModel.isEmpty { + validationAlertModel = true + } else { + UserDefaults.standard.set(vehicleName, forKey: "vehicleName") + UserDefaults.standard.set(vehicleMake, forKey: "vehicleMake") + UserDefaults.standard.set(vehicleModel, forKey: "vehicleModel") +// isFirstTime = false + onboardingViewCompletedAlert = true + } + } + + private var backButton: some ToolbarContent { + ToolbarItem(placement: .topBarLeading) { Button { -// isFirstTime = false + dismiss() } label: { - Text("Welcome 🥳") - .fontWeight(.bold) - .foregroundStyle(.white) - .frame(maxWidth: .infinity) - .padding(.vertical, 14) - .background(Color("basicGreen").gradient, in: .rect(cornerRadius: 12)) - .contentShape(.rect) + HStack { + Image(systemName: "arrow.left.circle") + Text("Back") + } + .tint(Color("basicGreen")) } - .padding(15) - .padding(.horizontal, 15) - } - .frame(maxWidth: .infinity, maxHeight: .infinity) - .background { - Color(UIColor.secondarySystemBackground) - .ignoresSafeArea() } - .navigationBarBackButtonHidden(true) - .toolbar { - ToolbarItem(placement: .topBarLeading) { - Button { - dismiss() - } label: { + } +} + +struct TextFieldClearButton: ViewModifier { + @Binding var text: String + + func body(content: Content) -> some View { + content + .overlay { + if !text.isEmpty { HStack { - Image(systemName: "arrow.left.circle") - Text("Back") + Spacer() + Button { + text = "" + } label: { + Image(systemName: "multiply.circle.fill") + .imageScale(.medium) + } + .foregroundColor(.secondary) + .padding(.trailing, 4) } - .tint(Color("basicGreen")) } } - } + } +} + +extension View { + func showClearButton(_ text: Binding) -> some View { + self.modifier(TextFieldClearButton(text: text)) } } diff --git a/Basic-Car-Maintenance/Shared/Settings/Views/SettingsView.swift b/Basic-Car-Maintenance/Shared/Settings/Views/SettingsView.swift index c0ef0a21..72e19b72 100644 --- a/Basic-Car-Maintenance/Shared/Settings/Views/SettingsView.swift +++ b/Basic-Car-Maintenance/Shared/Settings/Views/SettingsView.swift @@ -10,6 +10,10 @@ import UniformTypeIdentifiers import TipKit struct SettingsView: View { + @AppStorage("vehicleName") private var savedVehicleName: String = "" + @AppStorage("vehicleMake") private var savedVehicleMake: String = "" + @AppStorage("vehicleModel") private var savedVehicleModel: String = "" + @Environment(ActionService.self) var actionService @Environment(\.scenePhase) var scenePhase @Environment(\.colorScheme) var colorScheme @@ -88,6 +92,23 @@ struct SettingsView: View { } Section { + if !savedVehicleName.isEmpty { + VStack(alignment: .leading) { + Text("Locally Saved Vehicle") + .font(.headline) + Text(savedVehicleName) + Text(savedVehicleMake) + Text(savedVehicleModel) + } + .swipeActions { + Button(role: .destructive) { + deleteLocallySavedVehicle() + } label: { + Text("Delete") + } + } + } + ForEach(viewModel.vehicles) { vehicle in VStack(alignment: .leading) { Text(vehicle.name) @@ -280,6 +301,21 @@ struct SettingsView: View { } } } + + func deleteLocallySavedVehicle() { + // Clear the AppStorage values + UserDefaults.standard.removeObject(forKey: "vehicleName") + UserDefaults.standard.removeObject(forKey: "vehicleMake") + UserDefaults.standard.removeObject(forKey: "vehicleModel") + + // Reset the @AppStorage properties + savedVehicleName = "" + savedVehicleMake = "" + savedVehicleModel = "" + + // Force UserDefaults to save the changes immediately + UserDefaults.standard.synchronize() + } } #Preview { From a9a411ac33dae8bb4fbacaef603e0fcc2a34bc2b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marcin=20J=C4=99drzejak?= Date: Sun, 4 Aug 2024 02:08:19 +0200 Subject: [PATCH 2/7] feat: Connect WelcomeView and WelcomeViewAddVehicle to App Flow --- .../test.colorset/Contents.json | 38 +++ .../Shared/BasicCarMaintenanceApp.swift | 12 +- .../Shared/Localizable.xcstrings | 18 +- .../Views/WelcomeViewAddVehicle.swift | 228 ++++++++++++------ .../Shared/Settings/Views/SettingsView.swift | 36 +++ 5 files changed, 248 insertions(+), 84 deletions(-) create mode 100644 Basic-Car-Maintenance-Widget/Assets.xcassets/test.colorset/Contents.json diff --git a/Basic-Car-Maintenance-Widget/Assets.xcassets/test.colorset/Contents.json b/Basic-Car-Maintenance-Widget/Assets.xcassets/test.colorset/Contents.json new file mode 100644 index 00000000..be9d677b --- /dev/null +++ b/Basic-Car-Maintenance-Widget/Assets.xcassets/test.colorset/Contents.json @@ -0,0 +1,38 @@ +{ + "colors" : [ + { + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "1.000", + "blue" : "0x00", + "green" : "0x00", + "red" : "0x00" + } + }, + "idiom" : "universal" + }, + { + "appearances" : [ + { + "appearance" : "luminosity", + "value" : "dark" + } + ], + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "1.000", + "blue" : "0x00", + "green" : "0x00", + "red" : "0x00" + } + }, + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Basic-Car-Maintenance/Shared/BasicCarMaintenanceApp.swift b/Basic-Car-Maintenance/Shared/BasicCarMaintenanceApp.swift index 77274d0e..aa6a0119 100644 --- a/Basic-Car-Maintenance/Shared/BasicCarMaintenanceApp.swift +++ b/Basic-Car-Maintenance/Shared/BasicCarMaintenanceApp.swift @@ -17,8 +17,8 @@ struct BasicCarMaintenanceApp: App { @State private var actionService = ActionService.shared @UIApplicationDelegateAdaptor(AppDelegate.self) var delegate - // Logic to load Onboarding screen when app was first launched -// @AppStorage("isFirstTime") private var isFirstTime: Bool = true + // Logic to load OnboardingScreen when app first launch + @AppStorage("isFirstTime") private var isFirstTime: Bool = true var body: some Scene { WindowGroup { @@ -28,10 +28,10 @@ struct BasicCarMaintenanceApp: App { .task { try? Tips.configure() } -// .sheet(isPresented: $isFirstTime) { -// WelcomeView() -// .interactiveDismissDisabled() -// } + .sheet(isPresented: $isFirstTime) { + WelcomeView() + .interactiveDismissDisabled() + } } } } diff --git a/Basic-Car-Maintenance/Shared/Localizable.xcstrings b/Basic-Car-Maintenance/Shared/Localizable.xcstrings index 071667ed..8066d075 100644 --- a/Basic-Car-Maintenance/Shared/Localizable.xcstrings +++ b/Basic-Car-Maintenance/Shared/Localizable.xcstrings @@ -1119,6 +1119,9 @@ } } } + }, + "Congratulations! 🎉 🚙" : { + }, "Continue" : { @@ -2480,6 +2483,9 @@ } } }, + "From now on, next screen will be the main app screen." : { + "comment" : "Shows popup when completed Onboarding tutorial" + }, "GitHub Repo" : { "comment" : "Link to the Basic Car Maintenance GitHub repo.", "localizations" : { @@ -2741,6 +2747,9 @@ } } } + }, + "Locally Saved Vehicle" : { + }, "Logged in anonymously with ID: %@" : { "localizations" : { @@ -5005,6 +5014,12 @@ } } } + }, + "Vehicle %@" : { + + }, + "Vehicle %@ cannot be empty! 🚗" : { + }, "Vehicle Color" : { "localizations" : { @@ -5784,9 +5799,6 @@ } } } - }, - "Welcome 🥳" : { - }, "Welcome to" : { diff --git a/Basic-Car-Maintenance/Shared/Onboarding/Views/WelcomeViewAddVehicle.swift b/Basic-Car-Maintenance/Shared/Onboarding/Views/WelcomeViewAddVehicle.swift index 8261ae96..28d5d817 100644 --- a/Basic-Car-Maintenance/Shared/Onboarding/Views/WelcomeViewAddVehicle.swift +++ b/Basic-Car-Maintenance/Shared/Onboarding/Views/WelcomeViewAddVehicle.swift @@ -10,99 +10,177 @@ import SwiftUI struct WelcomeViewAddVehicle: View { - // Logic to remember Onboarding screen to not load again when app is launched -// @AppStorage("isFirstTime") private var isFirstTime: Bool = true + // Logic to remember OnboardingScreen to not load again when app is launched + @AppStorage("isFirstTime") private var isFirstTime: Bool = true + @AppStorage("vehicleName") private var vehicleName: String = "" + @AppStorage("vehicleMake") private var vehicleMake: String = "" + @AppStorage("vehicleModel") private var vehicleModel: String = "" @Environment(\.dismiss) var dismiss - - @State private var vehicleName: String = "" - @State private var vehicleMake: String = "" - @State private var vehicleModel: String = "" + @State private var validationAlertName: Bool = false + @State private var validationAlertMake: Bool = false + @State private var validationAlertModel: Bool = false + @State private var onboardingViewCompletedAlert: Bool = false var body: some View { VStack(spacing: 15) { VStack { - Text("Add the details below") - HStack(spacing: 5) { - Text("about") - Text("your vehicle") - .foregroundStyle(Color("basicGreen")) - } + headerView + vehicleDetailsView + bottomText + Spacer(minLength: 10) + addVehicleButton } - .font(.largeTitle.bold()) - .multilineTextAlignment(.center) - .padding(.top, 65) - .padding(.bottom, 15) - - VStack { - Image(systemName: "car.side.lock.open") - .font(.system(size: 45)) + } + .frame(maxWidth: .infinity, maxHeight: .infinity) + .background(Color(UIColor.secondarySystemBackground).ignoresSafeArea()) + .navigationBarBackButtonHidden(true) + .toolbar { backButton } + } + + private var headerView: some View { + VStack { + Text("Add the details below") + HStack(spacing: 5) { + Text("about") + Text("your vehicle") .foregroundStyle(Color("basicGreen")) - - List { - HStack { - Text("Name") - Spacer() - .frame(width: 40) - TextField("Vehicle Name", text: $vehicleName) - } - - HStack { - Text("Make") - Spacer() - .frame(width: 45) - TextField("Vehicle Make", text: $vehicleMake) - } - HStack { - Text("Model") - Spacer() - .frame(width: 40) - TextField("Vehicle Model", text: $vehicleModel) - } - } - .frame(maxHeight: 200) } - .frame(maxWidth: .infinity, alignment: .leading) - .padding(.horizontal, 15) - - Text("You can edit more data about the vehicle in the 'Settings' tab.") - .foregroundStyle(.gray) - .padding(.horizontal, 25) + } + .font(.largeTitle.bold()) + .multilineTextAlignment(.center) + .padding(.top, 65) + .padding(.bottom, 15) + } + + private var vehicleDetailsView: some View { + VStack { + Image(systemName: "car.side.lock.open") + .font(.system(size: 45)) + .foregroundStyle(Color("basicGreen")) - Spacer(minLength: 10) + List { + vehicleDetailRow(title: "Name", text: $vehicleName, alert: $validationAlertName) + vehicleDetailRow(title: "Make", text: $vehicleMake, alert: $validationAlertMake) + vehicleDetailRow(title: "Model", text: $vehicleModel, alert: $validationAlertModel) + } + .frame(maxHeight: 200) + .scrollContentBackground(.hidden) + } + .padding(.horizontal, 15) + } + + private func vehicleDetailRow(title: String, text: Binding, alert: Binding) -> some View { + LabeledContent { + TextField("Vehicle \(title)", text: text) + .alert( + "Vehicle \(title) cannot be empty! 🚗", + isPresented: alert + ) { + Button("OK", role: .cancel) {} + } + .frame(width: 200) + } label: { + HStack { + Text(title) + Spacer() + } + } + .showClearButton(text) + } + + private var bottomText: some View { + Text("You can edit more data about the vehicle in the 'Settings' tab.") + .foregroundStyle(.gray) + .padding(.horizontal, 25) + } + + private var addVehicleButton: some View { + Button { + addVehicle() + } label: { + Text("Add Vehicle") + .fontWeight(.bold) + .foregroundStyle(.white) + .frame(maxWidth: .infinity) + .padding(.vertical, 14) + .background(Color("basicGreen").gradient, in: .rect(cornerRadius: 12)) + .contentShape(.rect) + } + .padding(15) + .padding(.horizontal, 15) + .alert( + Text("Congratulations! 🎉 🚙"), + isPresented: $onboardingViewCompletedAlert + ) { + Button("OK", role: .cancel, action: { + isFirstTime = false + }) + } message: { + Text( + "From now on, next screen will be the main app screen.", + comment: "Shows popup when completed Onboarding tutorial" + ) + } + } + + func addVehicle() { + if vehicleName.isEmpty { + validationAlertName = true + } else if vehicleMake.isEmpty { + validationAlertMake = true + } else if vehicleModel.isEmpty { + validationAlertModel = true + } else { + UserDefaults.standard.set(vehicleName, forKey: "vehicleName") + UserDefaults.standard.set(vehicleMake, forKey: "vehicleMake") + UserDefaults.standard.set(vehicleModel, forKey: "vehicleModel") +// isFirstTime = false + onboardingViewCompletedAlert = true + } + } + + private var backButton: some ToolbarContent { + ToolbarItem(placement: .topBarLeading) { Button { -// isFirstTime = false + dismiss() } label: { - Text("Welcome 🥳") - .fontWeight(.bold) - .foregroundStyle(.white) - .frame(maxWidth: .infinity) - .padding(.vertical, 14) - .background(Color("basicGreen").gradient, in: .rect(cornerRadius: 12)) - .contentShape(.rect) + HStack { + Image(systemName: "arrow.left.circle") + Text("Back") + } + .tint(Color("basicGreen")) } - .padding(15) - .padding(.horizontal, 15) - } - .frame(maxWidth: .infinity, maxHeight: .infinity) - .background { - Color(UIColor.secondarySystemBackground) - .ignoresSafeArea() } - .navigationBarBackButtonHidden(true) - .toolbar { - ToolbarItem(placement: .topBarLeading) { - Button { - dismiss() - } label: { + } +} + +struct TextFieldClearButton: ViewModifier { + @Binding var text: String + + func body(content: Content) -> some View { + content + .overlay { + if !text.isEmpty { HStack { - Image(systemName: "arrow.left.circle") - Text("Back") + Spacer() + Button { + text = "" + } label: { + Image(systemName: "multiply.circle.fill") + .imageScale(.medium) + } + .foregroundColor(.secondary) + .padding(.trailing, 4) } - .tint(Color("basicGreen")) } } - } + } +} + +extension View { + func showClearButton(_ text: Binding) -> some View { + self.modifier(TextFieldClearButton(text: text)) } } diff --git a/Basic-Car-Maintenance/Shared/Settings/Views/SettingsView.swift b/Basic-Car-Maintenance/Shared/Settings/Views/SettingsView.swift index 9f770a7d..2b1b1999 100644 --- a/Basic-Car-Maintenance/Shared/Settings/Views/SettingsView.swift +++ b/Basic-Car-Maintenance/Shared/Settings/Views/SettingsView.swift @@ -11,6 +11,10 @@ import UniformTypeIdentifiers import TipKit struct SettingsView: View { + @AppStorage("vehicleName") private var savedVehicleName: String = "" + @AppStorage("vehicleMake") private var savedVehicleMake: String = "" + @AppStorage("vehicleModel") private var savedVehicleModel: String = "" + @Environment(ActionService.self) var actionService @Environment(\.scenePhase) var scenePhase @Environment(\.colorScheme) var colorScheme @@ -89,6 +93,23 @@ struct SettingsView: View { } Section { + if !savedVehicleName.isEmpty { + VStack(alignment: .leading) { + Text("Locally Saved Vehicle") + .font(.headline) + Text(savedVehicleName) + Text(savedVehicleMake) + Text(savedVehicleModel) + } + .swipeActions { + Button(role: .destructive) { + deleteLocallySavedVehicle() + } label: { + Text("Delete") + } + } + } + ForEach(viewModel.vehicles) { vehicle in VStack(alignment: .leading) { Text(vehicle.name) @@ -278,6 +299,21 @@ struct SettingsView: View { } } } + + func deleteLocallySavedVehicle() { + // Clear the AppStorage values + UserDefaults.standard.removeObject(forKey: "vehicleName") + UserDefaults.standard.removeObject(forKey: "vehicleMake") + UserDefaults.standard.removeObject(forKey: "vehicleModel") + + // Reset the @AppStorage properties + savedVehicleName = "" + savedVehicleMake = "" + savedVehicleModel = "" + + // Force UserDefaults to save the changes immediately + UserDefaults.standard.synchronize() + } } #Preview { From 13ad7a859845f2ffd525feec81d9d005bf046194 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marcin=20J=C4=99drzejak?= Date: Mon, 7 Oct 2024 14:14:20 +0200 Subject: [PATCH 3/7] feat: (corrected) Connect WelcomeView and WelcomeViewAddVehicle to App Flow --- .../Shared/Localizable.xcstrings | 23 +-- .../Shared/Onboarding/Views/WelcomeView.swift | 4 +- .../Views/WelcomeViewAddVehicle.swift | 174 +++++++++++------- .../Shared/Settings/Views/SettingsView.swift | 35 ---- 4 files changed, 111 insertions(+), 125 deletions(-) diff --git a/Basic-Car-Maintenance/Shared/Localizable.xcstrings b/Basic-Car-Maintenance/Shared/Localizable.xcstrings index 8066d075..5bb0c61b 100644 --- a/Basic-Car-Maintenance/Shared/Localizable.xcstrings +++ b/Basic-Car-Maintenance/Shared/Localizable.xcstrings @@ -199,9 +199,6 @@ } } } - }, - "about" : { - }, "Add" : { "comment" : "Label for submit button on form to add an entry", @@ -416,7 +413,7 @@ } } }, - "Add the details below" : { + "Add the details below about " : { }, "Add Vehicle" : { @@ -814,9 +811,6 @@ } } } - }, - "Back" : { - }, "Basic" : { @@ -1119,9 +1113,6 @@ } } } - }, - "Congratulations! 🎉 🚙" : { - }, "Continue" : { @@ -1803,6 +1794,9 @@ } } } + }, + "Error" : { + }, "Failed To Add Vehicle" : { "localizations" : { @@ -2483,9 +2477,6 @@ } } }, - "From now on, next screen will be the main app screen." : { - "comment" : "Shows popup when completed Onboarding tutorial" - }, "GitHub Repo" : { "comment" : "Link to the Basic Car Maintenance GitHub repo.", "localizations" : { @@ -2747,9 +2738,6 @@ } } } - }, - "Locally Saved Vehicle" : { - }, "Logged in anonymously with ID: %@" : { "localizations" : { @@ -5017,9 +5005,6 @@ }, "Vehicle %@" : { - }, - "Vehicle %@ cannot be empty! 🚗" : { - }, "Vehicle Color" : { "localizations" : { diff --git a/Basic-Car-Maintenance/Shared/Onboarding/Views/WelcomeView.swift b/Basic-Car-Maintenance/Shared/Onboarding/Views/WelcomeView.swift index 85ceec7f..69530dc6 100644 --- a/Basic-Car-Maintenance/Shared/Onboarding/Views/WelcomeView.swift +++ b/Basic-Car-Maintenance/Shared/Onboarding/Views/WelcomeView.swift @@ -9,6 +9,7 @@ import SwiftUI struct WelcomeView: View { + @State private var authenticationViewModel = AuthenticationViewModel() var body: some View { NavigationView { @@ -53,7 +54,8 @@ struct WelcomeView: View { Spacer(minLength: 10) - NavigationLink(destination: WelcomeViewAddVehicle()) { + NavigationLink( + destination: WelcomeViewAddVehicle(authenticationViewModel: authenticationViewModel)) { Text("Continue") .fontWeight(.bold) .foregroundStyle(.white) diff --git a/Basic-Car-Maintenance/Shared/Onboarding/Views/WelcomeViewAddVehicle.swift b/Basic-Car-Maintenance/Shared/Onboarding/Views/WelcomeViewAddVehicle.swift index 28d5d817..648b492b 100644 --- a/Basic-Car-Maintenance/Shared/Onboarding/Views/WelcomeViewAddVehicle.swift +++ b/Basic-Car-Maintenance/Shared/Onboarding/Views/WelcomeViewAddVehicle.swift @@ -7,77 +7,127 @@ // import SwiftUI +import FirebaseFirestore struct WelcomeViewAddVehicle: View { - - // Logic to remember OnboardingScreen to not load again when app is launched - @AppStorage("isFirstTime") private var isFirstTime: Bool = true - @AppStorage("vehicleName") private var vehicleName: String = "" - @AppStorage("vehicleMake") private var vehicleMake: String = "" - @AppStorage("vehicleModel") private var vehicleModel: String = "" @Environment(\.dismiss) var dismiss - @State private var validationAlertName: Bool = false - @State private var validationAlertMake: Bool = false - @State private var validationAlertModel: Bool = false - @State private var onboardingViewCompletedAlert: Bool = false + @State private var viewModel: SettingsViewModel + // Existing properties + @AppStorage("isFirstTime") private var isFirstTime: Bool = true + @State private var vehicleName: String = "" + @State private var vehicleMake: String = "" + @State private var vehicleModel: String = "" + + // New alert-related properties + @State private var showAlert: Bool = false + @State private var alertType: AlertType? + + // Define an enum for different alert types + enum AlertType { + case emptyName + case emptyMake + case emptyModel + case vehicleAdded(name: String) + case error(message: String) + + var title: String { + switch self { + case .emptyName, .emptyMake, .emptyModel: + return "Validation Error" + case .vehicleAdded(let name): + return "\(name) added successfully! 🎉" + case .error: + return "Error" + } + } + + var message: String? { + switch self { + case .emptyName: + return "Vehicle Name cannot be empty! 🚗" + case .emptyMake: + return "Vehicle Make cannot be empty! 🚗" + case .emptyModel: + return "Vehicle Model cannot be empty! 🚗" + case .vehicleAdded: + return nil + case .error(let message): + return message + } + } + } + + init(authenticationViewModel: AuthenticationViewModel) { + _viewModel = State(initialValue: SettingsViewModel(authenticationViewModel: authenticationViewModel)) + } + var body: some View { - VStack(spacing: 15) { - VStack { + ScrollView { + VStack(spacing: 15) { headerView vehicleDetailsView bottomText Spacer(minLength: 10) addVehicleButton } + .padding(.horizontal) } .frame(maxWidth: .infinity, maxHeight: .infinity) .background(Color(UIColor.secondarySystemBackground).ignoresSafeArea()) - .navigationBarBackButtonHidden(true) - .toolbar { backButton } + .alert(isPresented: $showAlert) { + Alert( + title: Text(alertType?.title ?? ""), + message: alertType?.message.map { Text($0) }, + dismissButton: .default(Text("OK")) { + if case .vehicleAdded = alertType { + isFirstTime = false + dismiss() + } + } + ) + } } private var headerView: some View { VStack { - Text("Add the details below") - HStack(spacing: 5) { - Text("about") - Text("your vehicle") - .foregroundStyle(Color("basicGreen")) - } + Text("Add the details below about ") + + Text("your vehicle") + .foregroundStyle(Color("basicGreen")) } - .font(.largeTitle.bold()) + .font(.largeTitle) + .bold() .multilineTextAlignment(.center) .padding(.top, 65) .padding(.bottom, 15) } private var vehicleDetailsView: some View { - VStack { + VStack(spacing: 20) { Image(systemName: "car.side.lock.open") .font(.system(size: 45)) .foregroundStyle(Color("basicGreen")) - List { - vehicleDetailRow(title: "Name", text: $vehicleName, alert: $validationAlertName) - vehicleDetailRow(title: "Make", text: $vehicleMake, alert: $validationAlertMake) - vehicleDetailRow(title: "Model", text: $vehicleModel, alert: $validationAlertModel) + VStack(spacing: 0) { + vehicleDetailRow(title: "Name", text: $vehicleName) + .padding(.bottom, 10) + Divider() + vehicleDetailRow(title: "Make", text: $vehicleMake) + .padding(.vertical, 10) + Divider() + vehicleDetailRow(title: "Model", text: $vehicleModel) + .padding(.top, 10) } - .frame(maxHeight: 200) - .scrollContentBackground(.hidden) + .padding() + .background(Color(UIColor.systemBackground)) + .cornerRadius(12) } .padding(.horizontal, 15) } - private func vehicleDetailRow(title: String, text: Binding, alert: Binding) -> some View { + private func vehicleDetailRow(title: String, text: Binding) -> some View { LabeledContent { TextField("Vehicle \(title)", text: text) - .alert( - "Vehicle \(title) cannot be empty! 🚗", - isPresented: alert - ) { - Button("OK", role: .cancel) {} - } .frame(width: 200) } label: { HStack { @@ -108,48 +158,31 @@ struct WelcomeViewAddVehicle: View { } .padding(15) .padding(.horizontal, 15) - .alert( - Text("Congratulations! 🎉 🚙"), - isPresented: $onboardingViewCompletedAlert - ) { - Button("OK", role: .cancel, action: { - isFirstTime = false - }) - } message: { - Text( - "From now on, next screen will be the main app screen.", - comment: "Shows popup when completed Onboarding tutorial" - ) - } } - func addVehicle() { + private func addVehicle() { if vehicleName.isEmpty { - validationAlertName = true + alertType = .emptyName + showAlert = true } else if vehicleMake.isEmpty { - validationAlertMake = true + alertType = .emptyMake + showAlert = true } else if vehicleModel.isEmpty { - validationAlertModel = true + alertType = .emptyModel + showAlert = true } else { - UserDefaults.standard.set(vehicleName, forKey: "vehicleName") - UserDefaults.standard.set(vehicleMake, forKey: "vehicleMake") - UserDefaults.standard.set(vehicleModel, forKey: "vehicleModel") -// isFirstTime = false - onboardingViewCompletedAlert = true - } - } - - private var backButton: some ToolbarContent { - ToolbarItem(placement: .topBarLeading) { - Button { - dismiss() - } label: { - HStack { - Image(systemName: "arrow.left.circle") - Text("Back") + let newVehicle = Vehicle(name: vehicleName, make: vehicleMake, model: vehicleModel) + + Task { + do { + try await viewModel.addVehicle(newVehicle) + alertType = .vehicleAdded(name: vehicleName) + showAlert = true + } catch { + alertType = .error(message: "Failed to add vehicle. Please try again.") + showAlert = true } - .tint(Color("basicGreen")) } } } @@ -185,5 +218,6 @@ extension View { } #Preview { - WelcomeViewAddVehicle() + WelcomeViewAddVehicle(authenticationViewModel: AuthenticationViewModel()) + .environment(ActionService.shared) } diff --git a/Basic-Car-Maintenance/Shared/Settings/Views/SettingsView.swift b/Basic-Car-Maintenance/Shared/Settings/Views/SettingsView.swift index 2b1b1999..32c2b4f2 100644 --- a/Basic-Car-Maintenance/Shared/Settings/Views/SettingsView.swift +++ b/Basic-Car-Maintenance/Shared/Settings/Views/SettingsView.swift @@ -11,9 +11,6 @@ import UniformTypeIdentifiers import TipKit struct SettingsView: View { - @AppStorage("vehicleName") private var savedVehicleName: String = "" - @AppStorage("vehicleMake") private var savedVehicleMake: String = "" - @AppStorage("vehicleModel") private var savedVehicleModel: String = "" @Environment(ActionService.self) var actionService @Environment(\.scenePhase) var scenePhase @@ -93,23 +90,6 @@ struct SettingsView: View { } Section { - if !savedVehicleName.isEmpty { - VStack(alignment: .leading) { - Text("Locally Saved Vehicle") - .font(.headline) - Text(savedVehicleName) - Text(savedVehicleMake) - Text(savedVehicleModel) - } - .swipeActions { - Button(role: .destructive) { - deleteLocallySavedVehicle() - } label: { - Text("Delete") - } - } - } - ForEach(viewModel.vehicles) { vehicle in VStack(alignment: .leading) { Text(vehicle.name) @@ -299,21 +279,6 @@ struct SettingsView: View { } } } - - func deleteLocallySavedVehicle() { - // Clear the AppStorage values - UserDefaults.standard.removeObject(forKey: "vehicleName") - UserDefaults.standard.removeObject(forKey: "vehicleMake") - UserDefaults.standard.removeObject(forKey: "vehicleModel") - - // Reset the @AppStorage properties - savedVehicleName = "" - savedVehicleMake = "" - savedVehicleModel = "" - - // Force UserDefaults to save the changes immediately - UserDefaults.standard.synchronize() - } } #Preview { From 63fb901735ae18e6056f94e9998647d235db33c6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marcin=20J=C4=99drzejak?= Date: Wed, 9 Oct 2024 00:00:09 +0200 Subject: [PATCH 4/7] feat: (corrected v2) Connect WelcomeView and WelcomeViewAddVehicle to App Flow --- .../Shared/Onboarding/Views/WelcomeViewAddVehicle.swift | 1 + 1 file changed, 1 insertion(+) diff --git a/Basic-Car-Maintenance/Shared/Onboarding/Views/WelcomeViewAddVehicle.swift b/Basic-Car-Maintenance/Shared/Onboarding/Views/WelcomeViewAddVehicle.swift index 648b492b..8fd9c9db 100644 --- a/Basic-Car-Maintenance/Shared/Onboarding/Views/WelcomeViewAddVehicle.swift +++ b/Basic-Car-Maintenance/Shared/Onboarding/Views/WelcomeViewAddVehicle.swift @@ -177,6 +177,7 @@ struct WelcomeViewAddVehicle: View { Task { do { try await viewModel.addVehicle(newVehicle) + print("Vehicle \(newVehicle.name) added to firebase successfully!") alertType = .vehicleAdded(name: vehicleName) showAlert = true } catch { From 064ca37cd9fe2f7088cff4b4b3ab4b90d5a92749 Mon Sep 17 00:00:00 2001 From: Mikaela Caron Date: Thu, 17 Oct 2024 12:06:20 +0300 Subject: [PATCH 5/7] PR 301 Suggestions, moving around functionality --- .../Shared/BasicCarMaintenanceApp.swift | 7 - .../Shared/Localizable.xcstrings | 20 ++- .../Shared/MainView/Views/MainTabView.swift | 7 + .../Shared/Onboarding/Views/WelcomeView.swift | 6 +- .../Views/WelcomeViewAddVehicle.swift | 140 +++++++----------- .../Shared/Settings/Views/SettingsView.swift | 1 - .../Utilities/TextFieldClearButton.swift | 38 +++++ 7 files changed, 118 insertions(+), 101 deletions(-) create mode 100644 Basic-Car-Maintenance/Shared/Utilities/TextFieldClearButton.swift diff --git a/Basic-Car-Maintenance/Shared/BasicCarMaintenanceApp.swift b/Basic-Car-Maintenance/Shared/BasicCarMaintenanceApp.swift index aa6a0119..087fc684 100644 --- a/Basic-Car-Maintenance/Shared/BasicCarMaintenanceApp.swift +++ b/Basic-Car-Maintenance/Shared/BasicCarMaintenanceApp.swift @@ -17,9 +17,6 @@ struct BasicCarMaintenanceApp: App { @State private var actionService = ActionService.shared @UIApplicationDelegateAdaptor(AppDelegate.self) var delegate - // Logic to load OnboardingScreen when app first launch - @AppStorage("isFirstTime") private var isFirstTime: Bool = true - var body: some Scene { WindowGroup { MainTabView() @@ -28,10 +25,6 @@ struct BasicCarMaintenanceApp: App { .task { try? Tips.configure() } - .sheet(isPresented: $isFirstTime) { - WelcomeView() - .interactiveDismissDisabled() - } } } } diff --git a/Basic-Car-Maintenance/Shared/Localizable.xcstrings b/Basic-Car-Maintenance/Shared/Localizable.xcstrings index 3355502c..7c60bfde 100644 --- a/Basic-Car-Maintenance/Shared/Localizable.xcstrings +++ b/Basic-Car-Maintenance/Shared/Localizable.xcstrings @@ -1,6 +1,9 @@ { "sourceLanguage" : "en", "strings" : { + "" : { + + }, "%@" : { "comment" : "Maintenance list item description 'Date' formatted", "localizations" : { @@ -71,6 +74,9 @@ } } } + }, + "%@ added successfully! 🎉" : { + }, "%@ on %@" : { "comment" : "Maintenance list item for a vehicle on a date", @@ -4937,6 +4943,9 @@ }, "User-friendly interface for controlling car maintenance tasks." : { + }, + "Validation Error" : { + }, "Vehicle" : { "comment" : "Maintenance event vehicle picker header", @@ -5215,6 +5224,9 @@ } } } + }, + "Vehicle Make cannot be empty! 🚗" : { + }, "Vehicle Model" : { "localizations" : { @@ -5297,6 +5309,9 @@ } } } + }, + "Vehicle Model cannot be empty! 🚗" : { + }, "Vehicle Name" : { "localizations" : { @@ -5379,6 +5394,9 @@ } } } + }, + "Vehicle Name cannot be empty! 🚗" : { + }, "Vehicle VIN" : { "localizations" : { @@ -5860,4 +5878,4 @@ } }, "version" : "1.0" -} +} \ No newline at end of file diff --git a/Basic-Car-Maintenance/Shared/MainView/Views/MainTabView.swift b/Basic-Car-Maintenance/Shared/MainView/Views/MainTabView.swift index 2967873b..eeafd836 100644 --- a/Basic-Car-Maintenance/Shared/MainView/Views/MainTabView.swift +++ b/Basic-Car-Maintenance/Shared/MainView/Views/MainTabView.swift @@ -57,6 +57,9 @@ struct MainTabView: View { @State private var authenticationViewModel = AuthenticationViewModel() @State private var viewModel = MainTabViewModel() + // Logic to load `WelcomeView` when app is first launched + @AppStorage("isFirstTime") private var isFirstTime: Bool = true + init() { _selectedTabId = State(initialValue: selectedTab) } @@ -77,6 +80,10 @@ struct MainTabView: View { AlertView(alert: alert) .presentationDetents([.medium]) } + .sheet(isPresented: $isFirstTime) { + WelcomeView(authenticationViewModel: authenticationViewModel) + .interactiveDismissDisabled() + } .onChange(of: scenePhase) { _, newScenePhase in guard case .active = newScenePhase, diff --git a/Basic-Car-Maintenance/Shared/Onboarding/Views/WelcomeView.swift b/Basic-Car-Maintenance/Shared/Onboarding/Views/WelcomeView.swift index 69530dc6..48bcf2be 100644 --- a/Basic-Car-Maintenance/Shared/Onboarding/Views/WelcomeView.swift +++ b/Basic-Car-Maintenance/Shared/Onboarding/Views/WelcomeView.swift @@ -9,7 +9,7 @@ import SwiftUI struct WelcomeView: View { - @State private var authenticationViewModel = AuthenticationViewModel() + var authenticationViewModel: AuthenticationViewModel var body: some View { NavigationView { @@ -55,7 +55,7 @@ struct WelcomeView: View { Spacer(minLength: 10) NavigationLink( - destination: WelcomeViewAddVehicle(authenticationViewModel: authenticationViewModel)) { + destination: WelcomeViewAddVehicle()) { Text("Continue") .fontWeight(.bold) .foregroundStyle(.white) @@ -96,5 +96,5 @@ struct WelcomeView: View { } #Preview { - WelcomeView() + WelcomeView(authenticationViewModel: AuthenticationViewModel()) } diff --git a/Basic-Car-Maintenance/Shared/Onboarding/Views/WelcomeViewAddVehicle.swift b/Basic-Car-Maintenance/Shared/Onboarding/Views/WelcomeViewAddVehicle.swift index 8fd9c9db..6eb8ff67 100644 --- a/Basic-Car-Maintenance/Shared/Onboarding/Views/WelcomeViewAddVehicle.swift +++ b/Basic-Car-Maintenance/Shared/Onboarding/Views/WelcomeViewAddVehicle.swift @@ -7,68 +7,31 @@ // import SwiftUI -import FirebaseFirestore struct WelcomeViewAddVehicle: View { @Environment(\.dismiss) var dismiss - @State private var viewModel: SettingsViewModel - // Existing properties @AppStorage("isFirstTime") private var isFirstTime: Bool = true @State private var vehicleName: String = "" @State private var vehicleMake: String = "" @State private var vehicleModel: String = "" - - // New alert-related properties + @State private var showAlert: Bool = false @State private var alertType: AlertType? - // Define an enum for different alert types - enum AlertType { - case emptyName - case emptyMake - case emptyModel - case vehicleAdded(name: String) - case error(message: String) - - var title: String { - switch self { - case .emptyName, .emptyMake, .emptyModel: - return "Validation Error" - case .vehicleAdded(let name): - return "\(name) added successfully! 🎉" - case .error: - return "Error" - } - } - - var message: String? { - switch self { - case .emptyName: - return "Vehicle Name cannot be empty! 🚗" - case .emptyMake: - return "Vehicle Make cannot be empty! 🚗" - case .emptyModel: - return "Vehicle Model cannot be empty! 🚗" - case .vehicleAdded: - return nil - case .error(let message): - return message - } - } - } - - init(authenticationViewModel: AuthenticationViewModel) { - _viewModel = State(initialValue: SettingsViewModel(authenticationViewModel: authenticationViewModel)) - } - var body: some View { ScrollView { VStack(spacing: 15) { headerView + vehicleDetailsView - bottomText + + Text("You can edit more data about the vehicle in the 'Settings' tab.") + .foregroundStyle(.gray) + .padding(.horizontal, 25) + Spacer(minLength: 10) + addVehicleButton } .padding(.horizontal) @@ -78,7 +41,7 @@ struct WelcomeViewAddVehicle: View { .alert(isPresented: $showAlert) { Alert( title: Text(alertType?.title ?? ""), - message: alertType?.message.map { Text($0) }, + message: Text(alertType?.message ?? ""), dismissButton: .default(Text("OK")) { if case .vehicleAdded = alertType { isFirstTime = false @@ -106,7 +69,7 @@ struct WelcomeViewAddVehicle: View { VStack(spacing: 20) { Image(systemName: "car.side.lock.open") .font(.system(size: 45)) - .foregroundStyle(Color("basicGreen")) + .foregroundStyle(.basicGreen) VStack(spacing: 0) { vehicleDetailRow(title: "Name", text: $vehicleName) @@ -130,20 +93,11 @@ struct WelcomeViewAddVehicle: View { TextField("Vehicle \(title)", text: text) .frame(width: 200) } label: { - HStack { - Text(title) - Spacer() - } + Text(title) } .showClearButton(text) } - private var bottomText: some View { - Text("You can edit more data about the vehicle in the 'Settings' tab.") - .foregroundStyle(.gray) - .padding(.horizontal, 25) - } - private var addVehicleButton: some View { Button { addVehicle() @@ -153,7 +107,7 @@ struct WelcomeViewAddVehicle: View { .foregroundStyle(.white) .frame(maxWidth: .infinity) .padding(.vertical, 14) - .background(Color("basicGreen").gradient, in: .rect(cornerRadius: 12)) + .background(.basicGreen.gradient, in: .rect(cornerRadius: 12)) .contentShape(.rect) } .padding(15) @@ -176,8 +130,8 @@ struct WelcomeViewAddVehicle: View { Task { do { - try await viewModel.addVehicle(newVehicle) - print("Vehicle \(newVehicle.name) added to firebase successfully!") + // add new vehicle here + alertType = .vehicleAdded(name: vehicleName) showAlert = true } catch { @@ -189,36 +143,44 @@ struct WelcomeViewAddVehicle: View { } } -struct TextFieldClearButton: ViewModifier { - @Binding var text: String - - func body(content: Content) -> some View { - content - .overlay { - if !text.isEmpty { - HStack { - Spacer() - Button { - text = "" - } label: { - Image(systemName: "multiply.circle.fill") - .imageScale(.medium) - } - .foregroundColor(.secondary) - .padding(.trailing, 4) - } - } - } - } +#Preview { + WelcomeViewAddVehicle() + .environment(ActionService.shared) } -extension View { - func showClearButton(_ text: Binding) -> some View { - self.modifier(TextFieldClearButton(text: text)) +extension WelcomeViewAddVehicle { + /// Types of alerts to be shown on the `WelcomeViewAddVehicle` + enum AlertType { + case emptyName + case emptyMake + case emptyModel + case vehicleAdded(name: String) + case error(message: String) + + var title: LocalizedStringResource { + switch self { + case .emptyName, .emptyMake, .emptyModel: + return "Validation Error" + case .vehicleAdded(let name): + return "\(name) added successfully! 🎉" + case .error: + return "Error" + } + } + + var message: LocalizedStringResource? { + switch self { + case .emptyName: + return "Vehicle Name cannot be empty! 🚗" + case .emptyMake: + return "Vehicle Make cannot be empty! 🚗" + case .emptyModel: + return "Vehicle Model cannot be empty! 🚗" + case .vehicleAdded: + return nil + case .error(let message): + return LocalizedStringResource(stringLiteral: message) + } + } } } - -#Preview { - WelcomeViewAddVehicle(authenticationViewModel: AuthenticationViewModel()) - .environment(ActionService.shared) -} diff --git a/Basic-Car-Maintenance/Shared/Settings/Views/SettingsView.swift b/Basic-Car-Maintenance/Shared/Settings/Views/SettingsView.swift index 32c2b4f2..9f770a7d 100644 --- a/Basic-Car-Maintenance/Shared/Settings/Views/SettingsView.swift +++ b/Basic-Car-Maintenance/Shared/Settings/Views/SettingsView.swift @@ -11,7 +11,6 @@ import UniformTypeIdentifiers import TipKit struct SettingsView: View { - @Environment(ActionService.self) var actionService @Environment(\.scenePhase) var scenePhase @Environment(\.colorScheme) var colorScheme diff --git a/Basic-Car-Maintenance/Shared/Utilities/TextFieldClearButton.swift b/Basic-Car-Maintenance/Shared/Utilities/TextFieldClearButton.swift new file mode 100644 index 00000000..3da3fb87 --- /dev/null +++ b/Basic-Car-Maintenance/Shared/Utilities/TextFieldClearButton.swift @@ -0,0 +1,38 @@ +// +// TextFieldClearButton.swift +// Basic-Car-Maintenance +// +// https://github.com/mikaelacaron/Basic-Car-Maintenance +// See LICENSE for license information. +// + +import SwiftUI + +struct TextFieldClearButton: ViewModifier { + @Binding var text: String + + func body(content: Content) -> some View { + content + .overlay { + if !text.isEmpty { + HStack { + Spacer() + Button { + text = "" + } label: { + Image(systemName: "multiply.circle.fill") + .imageScale(.medium) + } + .foregroundColor(.secondary) + .padding(.trailing, 4) + } + } + } + } +} + +extension View { + func showClearButton(_ text: Binding) -> some View { + self.modifier(TextFieldClearButton(text: text)) + } +} From 7ca3f4e7f1a7ab4e2535aab67f20a4a269131b48 Mon Sep 17 00:00:00 2001 From: Mikaela Caron Date: Thu, 17 Oct 2024 12:07:23 +0300 Subject: [PATCH 6/7] remove test color --- .../test.colorset/Contents.json | 38 ------------------- 1 file changed, 38 deletions(-) delete mode 100644 Basic-Car-Maintenance-Widget/Assets.xcassets/test.colorset/Contents.json diff --git a/Basic-Car-Maintenance-Widget/Assets.xcassets/test.colorset/Contents.json b/Basic-Car-Maintenance-Widget/Assets.xcassets/test.colorset/Contents.json deleted file mode 100644 index be9d677b..00000000 --- a/Basic-Car-Maintenance-Widget/Assets.xcassets/test.colorset/Contents.json +++ /dev/null @@ -1,38 +0,0 @@ -{ - "colors" : [ - { - "color" : { - "color-space" : "srgb", - "components" : { - "alpha" : "1.000", - "blue" : "0x00", - "green" : "0x00", - "red" : "0x00" - } - }, - "idiom" : "universal" - }, - { - "appearances" : [ - { - "appearance" : "luminosity", - "value" : "dark" - } - ], - "color" : { - "color-space" : "srgb", - "components" : { - "alpha" : "1.000", - "blue" : "0x00", - "green" : "0x00", - "red" : "0x00" - } - }, - "idiom" : "universal" - } - ], - "info" : { - "author" : "xcode", - "version" : 1 - } -} From c732c4fbb760db2beaecf3b45aed46311c07e428 Mon Sep 17 00:00:00 2001 From: Mikaela Caron Date: Thu, 17 Oct 2024 12:15:27 +0300 Subject: [PATCH 7/7] fix colors, capitalization and comments --- .../Shared/Onboarding/Views/WelcomeView.swift | 17 +++++++++-------- .../Shared/Utilities/TextFieldClearButton.swift | 3 +++ 2 files changed, 12 insertions(+), 8 deletions(-) diff --git a/Basic-Car-Maintenance/Shared/Onboarding/Views/WelcomeView.swift b/Basic-Car-Maintenance/Shared/Onboarding/Views/WelcomeView.swift index 48bcf2be..26935f95 100644 --- a/Basic-Car-Maintenance/Shared/Onboarding/Views/WelcomeView.swift +++ b/Basic-Car-Maintenance/Shared/Onboarding/Views/WelcomeView.swift @@ -18,10 +18,10 @@ struct WelcomeView: View { HStack(spacing: 5) { Text("Welcome to") Text("Basic") - .foregroundStyle(Color("basicGreen")) + .foregroundStyle(.basicGreen) } Text("Car Maintenance") - .foregroundStyle(Color("basicGreen")) + .foregroundStyle(.basicGreen) } .font(.largeTitle.bold()) .multilineTextAlignment(.center) @@ -33,19 +33,19 @@ struct WelcomeView: View { pointView( symbol: "car", title: "Dashboard", - subTitle: "User-friendly interface for controlling car maintenance tasks." + subtitle: "User-friendly interface for controlling car maintenance tasks." ) pointView( symbol: "gauge.with.dots.needle.bottom.50percent.badge.plus", title: "Odometer", - subTitle: "Tracks & displays total mileage, aiding timely maintenance planning." + subtitle: "Tracks & displays total mileage, aiding timely maintenance planning." ) pointView( symbol: "lock.open", title: "Open Source", - subTitle: "Built collaboratively with contributors, enhancing the app functionality." + subtitle: "Built collaboratively with contributors, enhancing the app functionality." ) } .frame(maxWidth: .infinity, alignment: .leading) @@ -76,11 +76,12 @@ struct WelcomeView: View { } @ViewBuilder - func pointView(symbol: String, title: LocalizedStringKey, subTitle: LocalizedStringKey) -> some View { + // swiftlint:disable:next line_length + func pointView(symbol: String, title: LocalizedStringResource, subtitle: LocalizedStringResource) -> some View { HStack(spacing: 20) { Image(systemName: symbol) .font(.largeTitle) - .foregroundStyle(Color("basicGreen")) + .foregroundStyle(.basicGreen) .frame(width: 45) VStack(alignment: .leading, spacing: 6) { @@ -88,7 +89,7 @@ struct WelcomeView: View { .font(.title3) .fontWeight(.semibold) - Text(subTitle) + Text(subtitle) .foregroundStyle(.gray) } } diff --git a/Basic-Car-Maintenance/Shared/Utilities/TextFieldClearButton.swift b/Basic-Car-Maintenance/Shared/Utilities/TextFieldClearButton.swift index 3da3fb87..c02f342d 100644 --- a/Basic-Car-Maintenance/Shared/Utilities/TextFieldClearButton.swift +++ b/Basic-Car-Maintenance/Shared/Utilities/TextFieldClearButton.swift @@ -8,6 +8,7 @@ import SwiftUI +/// An overlay of a button that is used to clear a TextField. struct TextFieldClearButton: ViewModifier { @Binding var text: String @@ -32,6 +33,8 @@ struct TextFieldClearButton: ViewModifier { } extension View { + + /// Add a `TextFieldClearButton` to a view. func showClearButton(_ text: Binding) -> some View { self.modifier(TextFieldClearButton(text: text)) }