diff --git a/code/frontend/MakeItSo/MakeItSo.xcodeproj/project.pbxproj b/code/frontend/MakeItSo/MakeItSo.xcodeproj/project.pbxproj index 1e32d24e5..1a42aa669 100644 --- a/code/frontend/MakeItSo/MakeItSo.xcodeproj/project.pbxproj +++ b/code/frontend/MakeItSo/MakeItSo.xcodeproj/project.pbxproj @@ -7,6 +7,8 @@ objects = { /* Begin PBXBuildFile section */ + 49281E992760EBCB0046A465 /* ReminderDateTimeView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 49281E982760EBCB0046A465 /* ReminderDateTimeView.swift */; }; + 49281E9A2760EBCB0046A465 /* ReminderDateTimeView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 49281E982760EBCB0046A465 /* ReminderDateTimeView.swift */; }; 881EF5BD272DC399004761E5 /* View+Focus.swift in Sources */ = {isa = PBXBuildFile; fileRef = 881EF5BC272DC399004761E5 /* View+Focus.swift */; }; 887B6FAD273ED4180028263D /* EmptyStateView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 887B6FA9273ED4180028263D /* EmptyStateView.swift */; }; 887B6FAE273ED4180028263D /* EmptyStateView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 887B6FA9273ED4180028263D /* EmptyStateView.swift */; }; @@ -33,11 +35,12 @@ /* End PBXBuildFile section */ /* Begin PBXFileReference section */ + 49281E982760EBCB0046A465 /* ReminderDateTimeView.swift */ = {isa = PBXFileReference; indentWidth = 2; lastKnownFileType = sourcecode.swift; path = ReminderDateTimeView.swift; sourceTree = ""; tabWidth = 2; }; 881EF5BC272DC399004761E5 /* View+Focus.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "View+Focus.swift"; sourceTree = ""; }; 887B6FA9273ED4180028263D /* EmptyStateView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = EmptyStateView.swift; sourceTree = ""; }; 88A1B7422756541400DB0494 /* ReminderListRowViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReminderListRowViewModel.swift; sourceTree = ""; }; - 88C30CD5274D1B4500E6694D /* ReminderDetailsViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReminderDetailsViewModel.swift; sourceTree = ""; }; - 88E7B7BD274CF30A00AF477D /* ReminderDetailsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReminderDetailsView.swift; sourceTree = ""; }; + 88C30CD5274D1B4500E6694D /* ReminderDetailsViewModel.swift */ = {isa = PBXFileReference; indentWidth = 2; lastKnownFileType = sourcecode.swift; path = ReminderDetailsViewModel.swift; sourceTree = ""; tabWidth = 2; }; + 88E7B7BD274CF30A00AF477D /* ReminderDetailsView.swift */ = {isa = PBXFileReference; indentWidth = 2; lastKnownFileType = sourcecode.swift; path = ReminderDetailsView.swift; sourceTree = ""; tabWidth = 2; }; 88FA998C274D63A400670474 /* View+ConfirmationDialog.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "View+ConfirmationDialog.swift"; sourceTree = ""; }; 88FA99912750090200670474 /* View+InteractiveDismissDisable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "View+InteractiveDismissDisable.swift"; sourceTree = ""; }; 88FEECCA27275ABC00ED368C /* MakeItSoApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MakeItSoApp.swift; sourceTree = ""; }; @@ -45,7 +48,7 @@ 88FEECD127275ABD00ED368C /* MakeItSo.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = MakeItSo.app; sourceTree = BUILT_PRODUCTS_DIR; }; 88FEECD727275ABD00ED368C /* MakeItSo.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = MakeItSo.app; sourceTree = BUILT_PRODUCTS_DIR; }; 88FEECD927275ABD00ED368C /* macOS.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = macOS.entitlements; sourceTree = ""; }; - 88FEECEF2727FEFF00ED368C /* Reminder.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Reminder.swift; sourceTree = ""; }; + 88FEECEF2727FEFF00ED368C /* Reminder.swift */ = {isa = PBXFileReference; indentWidth = 2; lastKnownFileType = sourcecode.swift; path = Reminder.swift; sourceTree = ""; tabWidth = 2; }; 88FEECF22728044100ED368C /* RemindersListView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RemindersListView.swift; sourceTree = ""; }; 88FEECF62728072D00ED368C /* RemindersListViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RemindersListViewModel.swift; sourceTree = ""; }; 88FEECF927280F3D00ED368C /* ReminderListRowView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReminderListRowView.swift; sourceTree = ""; }; @@ -165,6 +168,7 @@ 88FEECF22728044100ED368C /* RemindersListView.swift */, 88FEECF927280F3D00ED368C /* ReminderListRowView.swift */, 88E7B7BD274CF30A00AF477D /* ReminderDetailsView.swift */, + 49281E982760EBCB0046A465 /* ReminderDateTimeView.swift */, ); path = Views; sourceTree = ""; @@ -226,7 +230,7 @@ attributes = { BuildIndependentTargetsInParallel = 1; LastSwiftUpdateCheck = 1310; - LastUpgradeCheck = 1310; + LastUpgradeCheck = 1320; TargetAttributes = { 88FEECD027275ABD00ED368C = { CreatedOnToolsVersion = 13.1; @@ -291,6 +295,7 @@ 88C30CD6274D1B4500E6694D /* ReminderDetailsViewModel.swift in Sources */, 88FEECDA27275ABD00ED368C /* MakeItSoApp.swift in Sources */, 881EF5BD272DC399004761E5 /* View+Focus.swift in Sources */, + 49281E992760EBCB0046A465 /* ReminderDateTimeView.swift in Sources */, 88FEECF32728044100ED368C /* RemindersListView.swift in Sources */, 88FEECF72728072D00ED368C /* RemindersListViewModel.swift in Sources */, ); @@ -305,6 +310,7 @@ 88FEECFB27280F3D00ED368C /* ReminderListRowView.swift in Sources */, 88FEECDB27275ABD00ED368C /* MakeItSoApp.swift in Sources */, 88FEECF42728044100ED368C /* RemindersListView.swift in Sources */, + 49281E9A2760EBCB0046A465 /* ReminderDateTimeView.swift in Sources */, 88FEECF82728072D00ED368C /* RemindersListViewModel.swift in Sources */, 88A1B7442756541400DB0494 /* ReminderListRowViewModel.swift in Sources */, 887B6FAE273ED4180028263D /* EmptyStateView.swift in Sources */, @@ -433,7 +439,7 @@ ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 1; - DEVELOPMENT_TEAM = YGAZHQXHH4; + DEVELOPMENT_TEAM = NWK2EYP5Q6; ENABLE_PREVIEWS = YES; GENERATE_INFOPLIST_FILE = YES; INFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES; @@ -447,7 +453,7 @@ "@executable_path/Frameworks", ); MARKETING_VERSION = 1.0; - PRODUCT_BUNDLE_IDENTIFIER = dev.peterfriese.MakeItSo; + PRODUCT_BUNDLE_IDENTIFIER = dev.andrewcowley.MakeItSo; PRODUCT_NAME = MakeItSo; SDKROOT = iphoneos; SWIFT_EMIT_LOC_STRINGS = YES; @@ -463,7 +469,7 @@ ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 1; - DEVELOPMENT_TEAM = YGAZHQXHH4; + DEVELOPMENT_TEAM = NWK2EYP5Q6; ENABLE_PREVIEWS = YES; GENERATE_INFOPLIST_FILE = YES; INFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES; @@ -477,7 +483,7 @@ "@executable_path/Frameworks", ); MARKETING_VERSION = 1.0; - PRODUCT_BUNDLE_IDENTIFIER = dev.peterfriese.MakeItSo; + PRODUCT_BUNDLE_IDENTIFIER = dev.andrewcowley.MakeItSo; PRODUCT_NAME = MakeItSo; SDKROOT = iphoneos; SWIFT_EMIT_LOC_STRINGS = YES; @@ -493,10 +499,11 @@ ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; CODE_SIGN_ENTITLEMENTS = macOS/macOS.entitlements; + CODE_SIGN_IDENTITY = "-"; CODE_SIGN_STYLE = Automatic; COMBINE_HIDPI_IMAGES = YES; CURRENT_PROJECT_VERSION = 1; - DEVELOPMENT_TEAM = YGAZHQXHH4; + DEVELOPMENT_TEAM = NWK2EYP5Q6; ENABLE_HARDENED_RUNTIME = YES; ENABLE_PREVIEWS = YES; GENERATE_INFOPLIST_FILE = YES; @@ -507,7 +514,7 @@ ); MACOSX_DEPLOYMENT_TARGET = 12.0; MARKETING_VERSION = 1.0; - PRODUCT_BUNDLE_IDENTIFIER = dev.peterfriese.MakeItSo; + PRODUCT_BUNDLE_IDENTIFIER = dev.andrewcowley.MakeItSo; PRODUCT_NAME = MakeItSo; SDKROOT = macosx; SWIFT_EMIT_LOC_STRINGS = YES; @@ -521,10 +528,11 @@ ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; CODE_SIGN_ENTITLEMENTS = macOS/macOS.entitlements; + CODE_SIGN_IDENTITY = "-"; CODE_SIGN_STYLE = Automatic; COMBINE_HIDPI_IMAGES = YES; CURRENT_PROJECT_VERSION = 1; - DEVELOPMENT_TEAM = YGAZHQXHH4; + DEVELOPMENT_TEAM = NWK2EYP5Q6; ENABLE_HARDENED_RUNTIME = YES; ENABLE_PREVIEWS = YES; GENERATE_INFOPLIST_FILE = YES; @@ -535,7 +543,7 @@ ); MACOSX_DEPLOYMENT_TARGET = 12.0; MARKETING_VERSION = 1.0; - PRODUCT_BUNDLE_IDENTIFIER = dev.peterfriese.MakeItSo; + PRODUCT_BUNDLE_IDENTIFIER = dev.andrewcowley.MakeItSo; PRODUCT_NAME = MakeItSo; SDKROOT = macosx; SWIFT_EMIT_LOC_STRINGS = YES; diff --git a/code/frontend/MakeItSo/Shared/Features/ReminderList/Models/Reminder.swift b/code/frontend/MakeItSo/Shared/Features/ReminderList/Models/Reminder.swift index 42cce0f56..d5c9cddd4 100644 --- a/code/frontend/MakeItSo/Shared/Features/ReminderList/Models/Reminder.swift +++ b/code/frontend/MakeItSo/Shared/Features/ReminderList/Models/Reminder.swift @@ -16,7 +16,6 @@ // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. - import Foundation enum Priority: String { diff --git a/code/frontend/MakeItSo/Shared/Features/ReminderList/ViewModels/ReminderDetailsViewModel.swift b/code/frontend/MakeItSo/Shared/Features/ReminderList/ViewModels/ReminderDetailsViewModel.swift index 6ae3ccd49..4d77a7669 100644 --- a/code/frontend/MakeItSo/Shared/Features/ReminderList/ViewModels/ReminderDetailsViewModel.swift +++ b/code/frontend/MakeItSo/Shared/Features/ReminderList/ViewModels/ReminderDetailsViewModel.swift @@ -3,6 +3,7 @@ // MakeItSo // // Created by Peter Friese on 23.11.21. +// Contributing editor: Andrew Cowley on 8th Dec 2021 // Copyright © 2021 Google LLC. All rights reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); @@ -16,59 +17,8 @@ // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. - import Foundation -import Combine - -extension Date { - func formattedRelativeToday() -> String { - if Calendar.autoupdatingCurrent.isDateInToday(self) - || Calendar.autoupdatingCurrent.isDateInYesterday(self) - || Calendar.autoupdatingCurrent.isDateInTomorrow(self) { - - let formatStyle = Date.RelativeFormatStyle( - presentation: .named, - unitsStyle: .wide, - capitalizationContext: .beginningOfSentence) - - return self.formatted(formatStyle) - } - else { - return self.formatted(date: .complete, time: .omitted) - } - } - - func nearestHour() -> Date? { - var components = NSCalendar.current.dateComponents([.minute], from: self) - let minute = components.minute ?? 0 - components.minute = minute >= 30 ? 60 - minute : -minute - return Calendar.current.date(byAdding: components, to: self) - } - - func nextHour(basedOn date: Date? = nil) -> Date? { - let other = date ?? self - - var timeComponents = Calendar.current.dateComponents([.hour, .minute], from: other) - let minute = timeComponents.minute ?? 0 - timeComponents.minute = minute >= 0 ? 60 : 0 - - let dateComponents = Calendar.current.dateComponents([.year, .month, .day], from: self) - - let newDateComponents = DateComponents(calendar: Calendar.current, - year: dateComponents.year, - month: dateComponents.month, - day: dateComponents.day, - hour: timeComponents.hour, - minute: timeComponents.minute) - - return Calendar.current.date(from: newDateComponents) - } - - func startOfDay() -> Date { - Calendar.current.startOfDay(for: self) - } -} - +import SwiftUI //Needed for withOptionalAnimation class ReminderDetailsViewModel: ObservableObject { @Published var reminder: Reminder @@ -129,12 +79,13 @@ class ReminderDetailsViewModel: ObservableObject { set { if newValue == true { reminder.dueDate = Date() - isShowingDatePicker = true + setPickerState(.date) } - else { - hasDueTime = false + else + { reminder.dueDate = nil - isShowingDatePicker = false + reminder.hasDueTime = false + setPickerState(.none) } } } @@ -148,27 +99,89 @@ class ReminderDetailsViewModel: ObservableObject { guard let nearestHour = dueDate.nextHour(basedOn: Date()) else { return } dueDate = nearestHour reminder.hasDueTime = true - isShowingTimePicker = true + setPickerState(.time) } - else { + else + { dueDate = dueDate.startOfDay() reminder.hasDueTime = false - isShowingTimePicker = false + setPickerState(.none) } } } - @Published var isShowingDatePicker: Bool = false - @Published var isShowingTimePicker: Bool = false + // We should never display the date picker and the time picker together + // so define an enum to control single source of the truth for the + // view state of the pickers (instead of two separate booleans which could both + // mistakenly be set to true. Also allows each of the 6 transition animations to be controlled + // precisely in one place. + enum PickerState { + case none, date, time + } + @Published var pickerState: PickerState = .none - func toggleTimePicker() { - isShowingTimePicker.toggle() - isShowingDatePicker = false + func setPickerState(_ newValue: PickerState) { + // Dont animate if transitioning from state where date picker is shown + if pickerState == .date { + pickerState = newValue + } + else + { + // Animate transition if Accessibility setting allows. + withOptionalAnimation { + pickerState = newValue + } + } } - func toggleDatePicker() { - isShowingDatePicker.toggle() - isShowingTimePicker = false + // Toggle the display when a date value is pressed + func datePressed() { + pickerState = pickerState == .date ? .none : .date + } + // Toggle the display when a time value is pressed + func timePressed() { + pickerState = pickerState == .time ? .none : .time + } + + func withOptionalAnimation(_ animation: Animation? = .default, _ body: () throws -> Result) rethrows -> Result { + if UIAccessibility.isReduceMotionEnabled { + return try body() + } + else + { + return try withAnimation(animation, body) + } } +} +// The following Date extension functions are only currently used in this view model +// so for now, keep them here. If used elsewhere at a later date, they should be moved +// to their own extension file. +extension Date { + func formattedRelativeToday() -> String { + if !Calendar.autoupdatingCurrent.isDateInToday(self), + !Calendar.autoupdatingCurrent.isDateInYesterday(self), + !Calendar.autoupdatingCurrent.isDateInTomorrow(self) { + return self.formatted(date: .complete, time: .omitted) + } + else + { + let formatStyle = Date.RelativeFormatStyle( + presentation: .named, + unitsStyle: .wide, + capitalizationContext: .beginningOfSentence) + return self.formatted(formatStyle) + } + } + + func nextHour(basedOn date: Date? = nil) -> Date? { + var timeComponents = Calendar.current.dateComponents([.hour, .minute], from: date ?? self) + let minute = timeComponents.minute ?? 0 + timeComponents.minute = 60 - minute + return Calendar.current.date(byAdding: timeComponents, to: self) + } + + func startOfDay() -> Date { + Calendar.current.startOfDay(for: self) + } } diff --git a/code/frontend/MakeItSo/Shared/Features/ReminderList/ViewModels/RemindersListViewModel.swift b/code/frontend/MakeItSo/Shared/Features/ReminderList/ViewModels/RemindersListViewModel.swift index 09c2d6f77..85646628e 100644 --- a/code/frontend/MakeItSo/Shared/Features/ReminderList/ViewModels/RemindersListViewModel.swift +++ b/code/frontend/MakeItSo/Shared/Features/ReminderList/ViewModels/RemindersListViewModel.swift @@ -52,7 +52,7 @@ class RemindersListViewModel: ObservableObject { return previousIndex } - .delay(for: 0.01, scheduler: RunLoop.main) // <-- this helps reduce the visual jank +// .delay(for: 0.01, scheduler: RunLoop.main) // <-- this helps reduce the visual jank .sink { index in self.reminders.remove(at: index) } diff --git a/code/frontend/MakeItSo/Shared/Features/ReminderList/Views/ReminderDateTimeView.swift b/code/frontend/MakeItSo/Shared/Features/ReminderList/Views/ReminderDateTimeView.swift new file mode 100644 index 000000000..291788b36 --- /dev/null +++ b/code/frontend/MakeItSo/Shared/Features/ReminderList/Views/ReminderDateTimeView.swift @@ -0,0 +1,103 @@ +// +// ReminderDateTimeView.swift +// MakeItSo +// +// Created by Andrew Cowley on 08/12/2021. +// Copyright © 2021 Google LLC. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +import SwiftUI + +// Make the date time toggle section in the ReminderDetailsView +struct ReminderDateTimeView: View { + @EnvironmentObject var viewModel: ReminderDetailsViewModel + + var body: some View { + Toggle(isOn: $viewModel.hasDueDate) { + dateToggleLabel + } + .animation(.none, value: viewModel.pickerState ) + + if viewModel.pickerState == .date { + DatePicker("Date", selection: $viewModel.dueDate, displayedComponents: .date) + .datePickerStyle(.graphical) + } + + Toggle(isOn: $viewModel.hasDueTime) { + timeToggleLabel + } + if viewModel.pickerState == .time { + DatePicker("Time", selection: $viewModel.dueDate, displayedComponents: .hourAndMinute) + .datePickerStyle(.wheel) + } + } + + // Toggle label subviews for body + var dateToggleLabel: some View { + HStack { + Image(systemName: "calendar") + .frame(width: 26, height: 26) + .background(.red) + .foregroundColor(.white) + .cornerRadius(4) + VStack(alignment: .leading) { + Text("Date") + if let dueDate = viewModel.dueDate { + Button( action: viewModel.datePressed) { + Text(dueDate.formattedRelativeToday()) + .font(.caption2) + .foregroundColor(Color.accentColor) // Accent Color, which currently defaults to blue + } + } + } + } + } + var timeToggleLabel: some View { + HStack { + Image(systemName: "clock") + .frame(width: 26, height: 26) //, alignment: .center) + .background(.blue) + .foregroundColor(.white) + .cornerRadius(4) + VStack(alignment: .leading) { + Text("Time") + if viewModel.hasDueTime { + Button(action: viewModel.timePressed) { + Text(viewModel.dueDate, style: .time) + .font(.caption2) + .foregroundColor(Color.accentColor) // Accent Color, which will default to blue + } + } + } + } + } +} + +struct DateTimeSection_Previews: PreviewProvider { + static var previews: some View { + NavigationView { + Form { + Section { + Text("Header") + } + Section { + ReminderDateTimeView() + .environmentObject(ReminderDetailsViewModel(reminder: Reminder.samples[0])) + } + Section { + Text("Footer") + } + } + } + } +} diff --git a/code/frontend/MakeItSo/Shared/Features/ReminderList/Views/ReminderDetailsView.swift b/code/frontend/MakeItSo/Shared/Features/ReminderList/Views/ReminderDetailsView.swift index 934770fc2..1b9a32b68 100644 --- a/code/frontend/MakeItSo/Shared/Features/ReminderList/Views/ReminderDetailsView.swift +++ b/code/frontend/MakeItSo/Shared/Features/ReminderList/Views/ReminderDetailsView.swift @@ -20,13 +20,13 @@ import SwiftUI struct ReminderDetailsView: View { - @ObservedObject private var viewModel: ReminderDetailsViewModel + @StateObject private var viewModel: ReminderDetailsViewModel private var onCancel: (() -> Void)? private var onCommit: (Reminder) -> Void init(reminder: Reminder, onCancel: (() -> Void)? = nil, onCommit: @escaping (Reminder) -> Void) { - self.viewModel = ReminderDetailsViewModel(reminder: reminder) + self._viewModel = StateObject(wrappedValue: ReminderDetailsViewModel(reminder: reminder)) self.onCommit = onCommit } @@ -37,60 +37,9 @@ struct ReminderDetailsView: View { TextField("Notes", text: $viewModel.notes) TextField("URL", text: $viewModel.url) } + // Date and Time Toggles and Pickers refactored to separate view Section { - Toggle(isOn: $viewModel.hasDueDate) { - HStack { - Image(systemName: "calendar") - .frame(width: 26, height: 26, alignment: .center) - .background(.red) - .foregroundColor(.white) - .cornerRadius(4) - VStack(alignment: .leading) { - Text("Date") - if viewModel.hasDueDate { - Text(viewModel.dueDate.formattedRelativeToday()) - .font(.caption2) - .foregroundColor(.blue) - } } - } - } - .contentShape(Rectangle()) - .onTapGesture { - withAnimation { - viewModel.toggleDatePicker() - } - } - if viewModel.hasDueDate && viewModel.isShowingDatePicker { - DatePicker("Date", selection: $viewModel.dueDate, displayedComponents: .date) - .datePickerStyle(.graphical) - } - - Toggle(isOn: $viewModel.hasDueTime) { - HStack { - Image(systemName: "clock") - .frame(width: 26, height: 26, alignment: .center) - .background(.blue) - .foregroundColor(.white) - .cornerRadius(4) - VStack(alignment: .leading) { - Text("Time") - if (viewModel.hasDueTime) { - Text(viewModel.dueTime, style: .time) - .font(.caption2) - .foregroundColor(.blue) - } } - } - } - .contentShape(Rectangle()) - .onTapGesture { - withAnimation { - viewModel.toggleTimePicker() - } - } - if viewModel.hasDueTime && viewModel.isShowingTimePicker { - DatePicker("Date", selection: $viewModel.dueTime, displayedComponents: .hourAndMinute) - .datePickerStyle(.wheel) - } + ReminderDateTimeView() } Section { Toggle(isOn: $viewModel.reminder.flagged) { @@ -112,6 +61,7 @@ struct ReminderDetailsView: View { } } } + .environmentObject(viewModel) //pass view model to refactored subview DateTimeView .animation(.default, value: viewModel.reminder) .navigationTitle("Details") .navigationBarTitleDisplayMode(.inline) @@ -121,16 +71,17 @@ struct ReminderDetailsView: View { } } +// Commented out modal sheet presentation so that preview will run correctly. struct ReminderDetailsView_Previews: PreviewProvider { static var previews: some View { NavigationView { - Text("Dummy screen content") - .navigationTitle("Dummy screen") - .sheet(isPresented: .constant(true)) { +// Text("Dummy screen content") +// .navigationTitle("Dummy screen") +// .sheet(isPresented: .constant(true)) { ReminderDetailsView(reminder: Reminder.samples[0]) { updatedReminder in print(updatedReminder) } } - } +// } } }