Skip to content

Commit

Permalink
feat: Remove viewModel access to environment store (#7)
Browse files Browse the repository at this point in the history
* feat: Remove viewModel access to environment store

* make store property read only

* create CareKitEssentialView

* remove action argument from viewModel

* add intent to update CardViewModel

* update slider outcome on button tap

* add eventQuery to essential views

* update event for watch

* turn eventQuery into a type method

* make more methods public

* revert

* add back action to CardViewModel

* add updateEvent method

* testing

* fix saving new values
  • Loading branch information
cbaker6 committed Jun 29, 2024
1 parent 5fd0c29 commit 1cf1120
Show file tree
Hide file tree
Showing 11 changed files with 319 additions and 127 deletions.
15 changes: 14 additions & 1 deletion CareKitEssentials.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -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 */; };
Expand Down Expand Up @@ -62,6 +64,8 @@

/* Begin PBXFileReference section */
700DA8F92C29051900435E2C /* CareKitEssentials.xctestplan */ = {isa = PBXFileReference; lastKnownFileType = text; path = CareKitEssentials.xctestplan; sourceTree = "<group>"; };
700DA9042C2A609600435E2C /* CareKitEssentialView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CareKitEssentialView.swift; sourceTree = "<group>"; };
700DA9062C2A66BF00435E2C /* Logger.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Logger.swift; sourceTree = "<group>"; };
70BBCB462A12B8F500759A9C /* CardEnabledEnvironmentKey.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CardEnabledEnvironmentKey.swift; sourceTree = "<group>"; };
70BBCB482A12B9B300759A9C /* OCKScheduleEvent.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OCKScheduleEvent.swift; sourceTree = "<group>"; };
70BBCB4D2A12BB0500759A9C /* SliderLogTaskView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SliderLogTaskView.swift; sourceTree = "<group>"; };
Expand Down Expand Up @@ -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 */,
Expand Down Expand Up @@ -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 */,
Expand Down Expand Up @@ -341,14 +347,15 @@
attributes = {
BuildIndependentTargetsInParallel = YES;
LastSwiftMigration = 9999;
LastUpgradeCheck = 1430;
LastUpgradeCheck = 1540;
};
buildConfigurationList = OBJ_2 /* Build configuration list for PBXProject "CareKitEssentials" */;
compatibilityVersion = "Xcode 3.2";
developmentRegion = en;
hasScannedForEncodings = 0;
knownRegions = (
en,
Base,
);
mainGroup = OBJ_5;
packageReferences = (
Expand Down Expand Up @@ -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 */,
Expand All @@ -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 */,
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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 = (
Expand Down Expand Up @@ -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;
Expand All @@ -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 = (
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,8 @@
shouldUseLaunchSchemeArgsEnv = "YES">
<TestPlans>
<TestPlanReference
reference = "container:CareKitEssentials.xctestplan">
reference = "container:CareKitEssentials.xctestplan"
default = "YES">
</TestPlanReference>
</TestPlans>
</TestAction>
Expand Down
109 changes: 21 additions & 88 deletions Sources/CareKitEssentials/Cards/Shared/CardViewModel.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -37,7 +35,6 @@ open class CardViewModel: ObservableObject {
@Published public var valueAsDouble: Double = 0 {
didSet {
guard !isInitialValue else {
isInitialValue = true
return
}
var updatedValue = value
Expand All @@ -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

Expand All @@ -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.
Expand All @@ -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
}

}
Loading

0 comments on commit 1cf1120

Please sign in to comment.