diff --git a/CareKitEssentials.xcodeproj/project.pbxproj b/CareKitEssentials.xcodeproj/project.pbxproj index 30aa1c34..ecd7a1f6 100644 --- a/CareKitEssentials.xcodeproj/project.pbxproj +++ b/CareKitEssentials.xcodeproj/project.pbxproj @@ -7,6 +7,8 @@ objects = { /* Begin PBXBuildFile section */ + 700DA9052C2A609600435E2C /* CareKitEssentialView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 700DA9042C2A609600435E2C /* CareKitEssentialView.swift */; }; + 700DA9072C2A66BF00435E2C /* Logger.swift in Sources */ = {isa = PBXBuildFile; fileRef = 700DA9062C2A66BF00435E2C /* Logger.swift */; }; 70BBCB472A12B8F500759A9C /* CardEnabledEnvironmentKey.swift in Sources */ = {isa = PBXBuildFile; fileRef = 70BBCB462A12B8F500759A9C /* CardEnabledEnvironmentKey.swift */; }; 70BBCB492A12B9B300759A9C /* OCKScheduleEvent.swift in Sources */ = {isa = PBXBuildFile; fileRef = 70BBCB482A12B9B300759A9C /* OCKScheduleEvent.swift */; }; 70BBCB4E2A12BB0500759A9C /* SliderLogTaskView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 70BBCB4D2A12BB0500759A9C /* SliderLogTaskView.swift */; }; @@ -62,6 +64,8 @@ /* Begin PBXFileReference section */ 700DA8F92C29051900435E2C /* CareKitEssentials.xctestplan */ = {isa = PBXFileReference; lastKnownFileType = text; path = CareKitEssentials.xctestplan; sourceTree = ""; }; + 700DA9042C2A609600435E2C /* CareKitEssentialView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CareKitEssentialView.swift; sourceTree = ""; }; + 700DA9062C2A66BF00435E2C /* Logger.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Logger.swift; sourceTree = ""; }; 70BBCB462A12B8F500759A9C /* CardEnabledEnvironmentKey.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CardEnabledEnvironmentKey.swift; sourceTree = ""; }; 70BBCB482A12B9B300759A9C /* OCKScheduleEvent.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OCKScheduleEvent.swift; sourceTree = ""; }; 70BBCB4D2A12BB0500759A9C /* SliderLogTaskView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SliderLogTaskView.swift; sourceTree = ""; }; @@ -140,6 +144,7 @@ isa = PBXGroup; children = ( OBJ_23 /* CardViewModel.swift */, + 700DA9042C2A609600435E2C /* CareKitEssentialView.swift */, OBJ_11 /* CustomLabelView.swift */, OBJ_13 /* DetailsView.swift */, OBJ_22 /* InformationHeaderView.swift */, @@ -194,6 +199,7 @@ isa = PBXGroup; children = ( OBJ_26 /* Calendar+Dates.swift */, + 700DA9062C2A66BF00435E2C /* Logger.swift */, 911BDB272A11C491004F8442 /* CGFloat.swift */, OBJ_32 /* CustomLinearCareTaskProgress.swift */, OBJ_27 /* Image.swift */, @@ -341,7 +347,7 @@ attributes = { BuildIndependentTargetsInParallel = YES; LastSwiftMigration = 9999; - LastUpgradeCheck = 1430; + LastUpgradeCheck = 1540; }; buildConfigurationList = OBJ_2 /* Build configuration list for PBXProject "CareKitEssentials" */; compatibilityVersion = "Xcode 3.2"; @@ -349,6 +355,7 @@ hasScannedForEncodings = 0; knownRegions = ( en, + Base, ); mainGroup = OBJ_5; packageReferences = ( @@ -393,6 +400,7 @@ files = ( 70BBCB512A12BD5000759A9C /* SliderStyle.swift in Sources */, 70BBCB532A12BDAD00759A9C /* Slider.swift in Sources */, + 700DA9052C2A609600435E2C /* CareKitEssentialView.swift in Sources */, 911BDB282A11C491004F8442 /* CGFloat.swift in Sources */, OBJ_740 /* CustomLabelView.swift in Sources */, OBJ_742 /* DetailsView.swift in Sources */, @@ -409,6 +417,7 @@ OBJ_753 /* Image.swift in Sources */, OBJ_754 /* OCKAnyEvent+CustomStringConvertable.swift in Sources */, OBJ_755 /* OCKAnyEvent.swift in Sources */, + 700DA9072C2A66BF00435E2C /* Logger.swift in Sources */, OBJ_756 /* OCKAnyOutcome.swift in Sources */, 91A9E7E42A197A9300F3414D /* SimpleTaskView.swift in Sources */, 70BBCB472A12B8F500759A9C /* CardEnabledEnvironmentKey.swift in Sources */, @@ -452,6 +461,7 @@ OBJ_3 /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; CLANG_ENABLE_OBJC_ARC = YES; CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; CLANG_WARN_BOOL_CONVERSION = YES; @@ -479,6 +489,7 @@ ENABLE_NS_ASSERTIONS = YES; ENABLE_STRICT_OBJC_MSGSEND = YES; ENABLE_TESTABILITY = YES; + ENABLE_USER_SCRIPT_SANDBOXING = YES; GCC_NO_COMMON_BLOCKS = YES; GCC_OPTIMIZATION_LEVEL = 0; GCC_PREPROCESSOR_DEFINITIONS = ( @@ -510,6 +521,7 @@ OBJ_4 /* Release */ = { isa = XCBuildConfiguration; buildSettings = { + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; CLANG_ENABLE_OBJC_ARC = YES; CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; CLANG_WARN_BOOL_CONVERSION = YES; @@ -535,6 +547,7 @@ DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DYLIB_INSTALL_NAME_BASE = "@rpath"; ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_USER_SCRIPT_SANDBOXING = YES; GCC_NO_COMMON_BLOCKS = YES; GCC_OPTIMIZATION_LEVEL = s; GCC_PREPROCESSOR_DEFINITIONS = ( diff --git a/CareKitEssentials.xcodeproj/xcshareddata/xcschemes/CareKitEssentials.xcscheme b/CareKitEssentials.xcodeproj/xcshareddata/xcschemes/CareKitEssentials.xcscheme index b53af431..c4716b2a 100644 --- a/CareKitEssentials.xcodeproj/xcshareddata/xcschemes/CareKitEssentials.xcscheme +++ b/CareKitEssentials.xcodeproj/xcshareddata/xcschemes/CareKitEssentials.xcscheme @@ -30,7 +30,8 @@ shouldUseLaunchSchemeArgsEnv = "YES"> + reference = "container:CareKitEssentials.xctestplan" + default = "YES"> diff --git a/Sources/CareKitEssentials/Cards/Shared/CardViewModel.swift b/Sources/CareKitEssentials/Cards/Shared/CardViewModel.swift index e27aee8c..21761f08 100644 --- a/Sources/CareKitEssentials/Cards/Shared/CardViewModel.swift +++ b/Sources/CareKitEssentials/Cards/Shared/CardViewModel.swift @@ -21,8 +21,6 @@ open class CardViewModel: ObservableObject { /// The error encountered by the view model. @Published public var error: Error? - /// The store associated with the view model. - @Environment(\.careStore) public var store /// The latest `OCKOutcomeValue` for the event. @Published public var value = OCKOutcomeValue(0.0) { didSet { @@ -37,7 +35,6 @@ open class CardViewModel: ObservableObject { @Published public var valueAsDouble: Double = 0 { didSet { guard !isInitialValue else { - isInitialValue = true return } var updatedValue = value @@ -49,7 +46,9 @@ open class CardViewModel: ObservableObject { // MARK: Public read/write properties /// Specifies if this is the first time a value is being set. - public var isInitialValue = true + public var isInitialValue: Bool { + valueAsDouble == initialValue.doubleValue + } // MARK: Public read only properties @@ -64,8 +63,8 @@ open class CardViewModel: ObservableObject { /// A custom details information string to display for the task of the view model. public private(set) var detailsInformation: String? - // MARK: Private properties - var action: (OCKOutcomeValue?) async -> Void = { _ in } + var initialValue: OCKOutcomeValue + var action: ((OCKOutcomeValue?) async -> Void)? = nil /// Create an instance with specified content for an event. The view will update when changes /// occur in the store. @@ -75,97 +74,31 @@ open class CardViewModel: ObservableObject { /// - detailsTitle: An optional title for the event. /// - detailsInformation: An optional detailed information string for the event. /// - action: The action to take when event is completed. - public init(event: OCKAnyEvent, - initialValue: OCKOutcomeValue = OCKOutcomeValue(0.0), - detailsTitle: String? = nil, - detailsInformation: String? = nil, - action: ((OCKOutcomeValue?) async -> Void)? = nil) { + public init( + event: OCKAnyEvent, + initialValue: OCKOutcomeValue = OCKOutcomeValue(0.0), + detailsTitle: String? = nil, + detailsInformation: String? = nil, + action: ((OCKOutcomeValue?) async -> Void)? = nil + ) { self.value = event.outcomeFirstValue ?? initialValue + self.initialValue = initialValue self.detailsTitle = detailsTitle self.detailsInformation = detailsInformation self.event = event - guard let action = action else { - // Use the default action - self.action = { value in - do { - guard let value = value else { - // Attempts to delete outcome if it already exists. - _ = try await self.saveOutcomeValues([]) - return - } - _ = try await self.appendOutcomeValues([value]) - } catch { - self.error = error - } - } - return - } self.action = action } - // MARK: Intentions - - /// Append an `OCKOutcomeValue` to an event's `OCKOutcome`. - /// - Parameters: - /// - values: An array of `OCKOutcomeValue`'s to append. - /// - Throws: An error if the outcome values cannot be set. - /// - Note: Appends occur if an`OCKOutcome` currently exists for the event. - /// Otherwise a new `OCKOutcome` is created with the respective outcome values. - open func appendOutcomeValues(_ values: [OCKOutcomeValue]) async throws { - - // Update the outcome with the new value - guard var outcome = event.outcome else { - let outcome = try createOutcomeWithValues(values) - _ = try await store.addAnyOutcome(outcome) - return - } - outcome.values.append(contentsOf: values) - _ = try await store.updateAnyOutcome(outcome) - return - } - - /// Set/Replace the `OCKOutcomeValue`'s of an event. - /// - Parameters: - /// - values: An array of `OCKOutcomeValue`'s to save. - /// - Throws: An error if the outcome values cannot be set. - /// - Note: Setting `values` to an empty array will delete the current `OCKOutcome` if it currently exists. - open func saveOutcomeValues(_ values: [OCKOutcomeValue]) async throws { - - // Check if outcome values need to be updated. - guard !values.isEmpty else { - // If the event has already been completed - guard let oldOutcome = event.outcome else { - return - } - // Delete the outcome, and create a new one. - _ = try await store.deleteAnyOutcome(oldOutcome) - return - } - - // If the event has already been completed - guard var currentOutcome = event.outcome else { - // Create a new outcome with the new values. - let outcome = try createOutcomeWithValues(values) - _ = try await store.addAnyOutcome(outcome) - return - } - // Update the outcome with the new values. - currentOutcome.values = values - _ = try await store.updateAnyOutcome(currentOutcome) - } - - // MARK: Helpers + // MARK: Intents - /// Create an outcome for an event with the given outcome values. + /// Update the the viewModel with the current event. /// - Parameters: - /// - values: The outcome values to attach to the outcome. - open func createOutcomeWithValues(_ values: [OCKOutcomeValue]) throws -> OCKAnyOutcome { - guard let task = event.task as? OCKAnyVersionableTask else { - throw CareKitEssentialsError.errorString("Cannot make outcome for event: \(event)") - } - return OCKOutcome(taskUUID: task.uuid, - taskOccurrenceIndex: event.scheduleEvent.occurrence, - values: values) + /// - event: The current event. + @MainActor + public func updateEvent(_ event: OCKAnyEvent) { + self.event = event + value = event.outcomeFirstValue ?? initialValue + initialValue = value } } diff --git a/Sources/CareKitEssentials/Cards/Shared/CareKitEssentialView.swift b/Sources/CareKitEssentials/Cards/Shared/CareKitEssentialView.swift new file mode 100644 index 00000000..bbae9c93 --- /dev/null +++ b/Sources/CareKitEssentials/Cards/Shared/CareKitEssentialView.swift @@ -0,0 +1,189 @@ +// +// CareKitEssentialView.swift +// CareKitEssentials +// +// Created by Corey Baker on 6/24/24. +// + +import CareKitStore +import SwiftUI + +/// A type that represents part of your app's user interface and provides +/// modifiers that you use to configure views. +public protocol CareKitEssentialView: View { + + /// A repository for CareKit information. + var careStore: any OCKAnyStoreProtocol { get } + + /// Update an `OCKAnyEvent` with new `OCKOutcomeValue`'s. + /// - Parameters: + /// - event: The event to update. + /// - values: A new array `OCKOutcomeValue`'s. + /// - Throws: An error if the outcome values cannot be updated. + /// - Note: Appends occur if an`OCKOutcome` currently exists for the event. + /// Otherwise a new `OCKOutcome` is created with the respective outcome values. + func updateEvent( + _ event: OCKAnyEvent, + with values: [OCKOutcomeValue]? + ) async throws + + /// Update an `OCKAnyEvent` with new a `OCKOutcome`. + /// - Parameters: + /// - event: The event to update. + /// - outcome: A new `OCKOutcome`. + /// - Throws: An error if the outcome values cannot be updated. + func updateEvent( + _ event: OCKAnyEvent, + with outcome: OCKOutcome? + ) async throws + + /// Create an `OCKEventQuery` constrained to a set of `taskIDs` on a particular date. + /// - Parameters: + /// - taskIDs: The array of taskIDs to search. + /// - date: The date the event occurs on. + /// - Returns: An `OCKEventQuery` with the predefined constraints. + static func eventQuery( + with taskIDs: [String], + on date: Date + ) -> OCKEventQuery +} + +public extension CareKitEssentialView { + + func updateEvent( + _ event: OCKAnyEvent, + with values: [OCKOutcomeValue]? + ) async throws { + guard let values = values else { + // Attempts to delete outcome if it already exists. + _ = try await self.saveOutcomeValues( + [], + event: event, + store: careStore + ) + return + } + _ = try await self.appendOutcomeValues( + values, + event: event, + store: careStore + ) + } + + func updateEvent( + _ event: OCKAnyEvent, + with outcome: OCKOutcome? + ) async throws { + guard let outcome = outcome else { + guard let task = event.task as? OCKAnyVersionableTask else { + throw CareKitEssentialsError.errorString("Cannot make outcome for event: \(event)") + } + let outcome = OCKOutcome( + taskUUID: task.uuid, + taskOccurrenceIndex: event.scheduleEvent.occurrence, + values: [] + ) + // Attempts to set the latest outcome values to an empty array. + _ = try await careStore.deleteAnyOutcome(outcome) + return + } + _ = try await careStore.addAnyOutcome(outcome) + } + + /// Append an `OCKOutcomeValue` to an event's `OCKOutcome`. + /// - Parameters: + /// - values: An array of `OCKOutcomeValue`'s to append. + /// - Throws: An error if the outcome values cannot be set. + /// - Note: Appends occur if an`OCKOutcome` currently exists for the event. + /// Otherwise a new `OCKOutcome` is created with the respective outcome values. + func appendOutcomeValues( + _ values: [OCKOutcomeValue], + event: OCKAnyEvent, + store: OCKAnyStoreProtocol + ) async throws { + + // Update the outcome with the new value + guard var outcome = event.outcome else { + let outcome = try createOutcomeWithValues( + values, + event: event, + store: store + ) + _ = try await store.addAnyOutcome(outcome) + return + } + outcome.values.append(contentsOf: values) + _ = try await store.updateAnyOutcome(outcome) + return + } + + /// Set/Replace the `OCKOutcomeValue`'s of an event. + /// - Parameters: + /// - values: An array of `OCKOutcomeValue`'s to save. + /// - Throws: An error if the outcome values cannot be set. + /// - Note: Setting `values` to an empty array will delete the current `OCKOutcome` if it currently exists. + func saveOutcomeValues( + _ values: [OCKOutcomeValue], + event: OCKAnyEvent, + store: OCKAnyStoreProtocol + ) async throws { + + // Check if outcome values need to be updated. + guard !values.isEmpty else { + // If the event has already been completed + guard let oldOutcome = event.outcome else { + return + } + // Delete the outcome, and create a new one. + _ = try await store.deleteAnyOutcome(oldOutcome) + return + } + + // If the event has already been completed + guard var currentOutcome = event.outcome else { + // Create a new outcome with the new values. + let outcome = try createOutcomeWithValues( + values, + event: event, + store: store + ) + _ = try await store.addAnyOutcome(outcome) + return + } + // Update the outcome with the new values. + currentOutcome.values = values + _ = try await store.updateAnyOutcome(currentOutcome) + } + + static func eventQuery( + with taskIDs: [String], + on date: Date + ) -> OCKEventQuery { + var query = OCKEventQuery(for: date) + query.taskIDs = taskIDs + return query + } +} + +extension CareKitEssentialView { + + /// Create an outcome for an event with the given outcome values. + /// - Parameters: + /// - values: The outcome values to attach to the outcome. + func createOutcomeWithValues( + _ values: [OCKOutcomeValue], + event: OCKAnyEvent, + store: OCKAnyStoreProtocol + ) throws -> OCKAnyOutcome { + guard let task = event.task as? OCKAnyVersionableTask else { + throw CareKitEssentialsError.errorString("Cannot make outcome for event: \(event)") + } + let outcome = OCKOutcome( + taskUUID: task.uuid, + taskOccurrenceIndex: event.scheduleEvent.occurrence, + values: values + ) + return outcome + } + +} diff --git a/Sources/CareKitEssentials/Cards/Shared/CustomLabelView.swift b/Sources/CareKitEssentials/Cards/Shared/CustomLabelView.swift index 0092c361..44bdb61e 100644 --- a/Sources/CareKitEssentials/Cards/Shared/CustomLabelView.swift +++ b/Sources/CareKitEssentials/Cards/Shared/CustomLabelView.swift @@ -94,22 +94,27 @@ public extension CustomLabelView { /// - initialValue: The default outcome value for the view model. Defaults to 0.0. /// - detailsTitle: An optional title for the event. /// - detailsInformation: An optional detailed information string for the event. - /// - header: Short and descriptive content that identifies the event. /// - action: The action to take when event is completed. - init(event: CareStoreFetchedResult, - initialValue: OCKOutcomeValue = OCKOutcomeValue(0.0), - detailsTitle: String? = nil, - detailsInformation: String? = nil, - @ViewBuilder header: () -> Header, - action: ((OCKOutcomeValue?) async -> Void)? = nil) { - self.init(viewModel: .init(event: event.result, - initialValue: initialValue, - detailsTitle: detailsTitle, - detailsInformation: detailsInformation, - action: action), - header: header) - } + /// - header: Short and descriptive content that identifies the event. + init( + event: CareStoreFetchedResult, + initialValue: OCKOutcomeValue = OCKOutcomeValue(0.0), + detailsTitle: String? = nil, + detailsInformation: String? = nil, + action: ((OCKOutcomeValue?) async -> Void)? = nil, + @ViewBuilder header: () -> Header) { + self.init( + viewModel: .init( + event: event.result, + initialValue: initialValue, + detailsTitle: detailsTitle, + detailsInformation: detailsInformation, + action: action + ), + header: header + ) + } } public extension CustomLabelView where Header == InformationHeaderView { @@ -161,7 +166,8 @@ public extension CustomLabelView where Header == InformationHeaderView { initialValue: initialValue, detailsTitle: detailsTitle, detailsInformation: detailsInformation, - action: action)) + action: action) + ) } } diff --git a/Sources/CareKitEssentials/Cards/iOS/SliderLog/SliderLogButton.swift b/Sources/CareKitEssentials/Cards/iOS/SliderLog/SliderLogButton.swift index 475b70a7..e2805629 100644 --- a/Sources/CareKitEssentials/Cards/iOS/SliderLog/SliderLogButton.swift +++ b/Sources/CareKitEssentials/Cards/iOS/SliderLog/SliderLogButton.swift @@ -10,10 +10,12 @@ import CareKitUI import CareKitStore +import os.log import SwiftUI -struct SliderLogButton: View { +struct SliderLogButton: CareKitEssentialView { + @Environment(\.careStore) var careStore @Environment(\.careKitStyle) private var style @ObservedObject var viewModel: SliderLogTaskViewModel @@ -34,10 +36,7 @@ struct SliderLogButton: View { var body: some View { VStack { Button(action: { - Task { - await viewModel.action(OCKOutcomeValue(viewModel.valueAsDouble)) - } - viewModel.isActive = false + updateValue() }) { HStack { Spacer() @@ -65,6 +64,25 @@ struct SliderLogButton: View { } .buttonStyle(NoHighlightStyle()) } + + func updateValue() { + Task { + // Any additional info that needs to be added to the outcome + let newOutcomeValue = OCKOutcomeValue(viewModel.valueAsDouble) + + guard let action = viewModel.action else { + do { + try await updateEvent(viewModel.event, with: [newOutcomeValue]) + } catch { + Logger.essentialView.error("Cannot update store with outcome value: \(error)") + } + viewModel.isActive = false + return + } + await action(newOutcomeValue) + viewModel.isActive = false + } + } } struct NoHighlightStyle: ButtonStyle { diff --git a/Sources/CareKitEssentials/Cards/iOS/SliderLog/SliderLogTaskView.swift b/Sources/CareKitEssentials/Cards/iOS/SliderLog/SliderLogTaskView.swift index 81385664..bc3fce9d 100644 --- a/Sources/CareKitEssentials/Cards/iOS/SliderLog/SliderLogTaskView.swift +++ b/Sources/CareKitEssentials/Cards/iOS/SliderLog/SliderLogTaskView.swift @@ -46,7 +46,6 @@ import SwiftUI public struct SliderLogTaskView: View { // MARK: - Properties - @Environment(\.careKitStyle) private var style @Environment(\.isCardEnabled) private var isCardEnabled diff --git a/Sources/CareKitEssentials/Cards/iOS/SliderLog/SliderLogTaskViewModel.swift b/Sources/CareKitEssentials/Cards/iOS/SliderLog/SliderLogTaskViewModel.swift index 35279619..90606d6b 100644 --- a/Sources/CareKitEssentials/Cards/iOS/SliderLog/SliderLogTaskViewModel.swift +++ b/Sources/CareKitEssentials/Cards/iOS/SliderLog/SliderLogTaskViewModel.swift @@ -60,7 +60,14 @@ open class SliderLogTaskViewModel: CardViewModel { initialValue: OCKOutcomeValue(currentInitialValue), detailsTitle: detailsTitle, detailsInformation: detailsInformation, - action: action) + action: action + ) } + public override func updateEvent(_ event: OCKAnyEvent) { + super.updateEvent(event) + if let values = event.outcomeValues { + self.previousValues = values.compactMap { $0.doubleValue } + } + } } diff --git a/Sources/CareKitEssentials/Cards/watchOS/DigitalCrown/DigitalCrownViewFooter.swift b/Sources/CareKitEssentials/Cards/watchOS/DigitalCrown/DigitalCrownViewFooter.swift index 6fa5954a..8ca20c87 100644 --- a/Sources/CareKitEssentials/Cards/watchOS/DigitalCrown/DigitalCrownViewFooter.swift +++ b/Sources/CareKitEssentials/Cards/watchOS/DigitalCrown/DigitalCrownViewFooter.swift @@ -10,10 +10,12 @@ import CareKitStore import CareKitUI +import os.log import SwiftUI -public struct DigitalCrownViewFooter: View { +public struct DigitalCrownViewFooter: CareKitEssentialView { + @Environment(\.careStore) public var careStore @Environment(\.sizeCategory) private var sizeCategory @StateObject var viewModel: DigitalCrownViewModel @@ -54,9 +56,7 @@ public struct DigitalCrownViewFooter: View { .foregroundColor(viewModel.getStoplightColor(for: viewModel.valueAsDouble)) } Button(action: { - Task { - await viewModel.updateValue(viewModel.value) - } + updateValue() }) { RectangularCompletionView(isComplete: viewModel.isButtonDisabled) { HStack { @@ -71,6 +71,24 @@ public struct DigitalCrownViewFooter: View { .disabled(viewModel.isButtonDisabled) } } + + func updateValue() { + Task { + let originalOutcomeValue = viewModel.value + // Any additional info that needs to be added to the outcome + let newOutcomeValue = OCKOutcomeValue(originalOutcomeValue.value) + + guard let action = viewModel.action else { + do { + try await updateEvent(viewModel.event, with: [newOutcomeValue]) + } catch { + Logger.essentialView.error("Cannot update store with outcome value: \(error)") + } + return + } + await action(newOutcomeValue) + } + } } /// Turns off the highlighted (AKA pressed) state. diff --git a/Sources/CareKitEssentials/Cards/watchOS/DigitalCrown/DigitalCrownViewModel.swift b/Sources/CareKitEssentials/Cards/watchOS/DigitalCrown/DigitalCrownViewModel.swift index ab8d1f00..db8526ef 100644 --- a/Sources/CareKitEssentials/Cards/watchOS/DigitalCrown/DigitalCrownViewModel.swift +++ b/Sources/CareKitEssentials/Cards/watchOS/DigitalCrown/DigitalCrownViewModel.swift @@ -87,23 +87,16 @@ open class DigitalCrownViewModel: CardViewModel { initialValue: OCKOutcomeValue(initialValue), detailsTitle: detailsTitle, detailsInformation: detailsInformation, - action: action) + action: action + ) } else { super.init(event: event, initialValue: OCKOutcomeValue(startValue), detailsTitle: detailsTitle, detailsInformation: detailsInformation, - action: action) + action: action + ) } } - /** - Update new value with new information - */ - open func updateValue(_ value: OCKOutcomeValue?) async { - guard let value = value else { return } - // Any additional info that needs to be added to the outcome - let newOutcomeValue = OCKOutcomeValue(value.value) - await action(newOutcomeValue) - } } diff --git a/Sources/CareKitEssentials/Extensions/Logger.swift b/Sources/CareKitEssentials/Extensions/Logger.swift new file mode 100644 index 00000000..e2079fe2 --- /dev/null +++ b/Sources/CareKitEssentials/Extensions/Logger.swift @@ -0,0 +1,15 @@ +// +// Logger.swift +// CareKitEssentials +// +// Created by Corey Baker on 6/24/24. +// + +import Foundation +import os.log + +extension Logger { + private static var subsystem = Bundle.main.bundleIdentifier! + + static let essentialView = Logger(subsystem: subsystem, category: "CareKitEssentialView") +}