From f309d2be7bd0f7ada3316437fbc6e824c46e1ac7 Mon Sep 17 00:00:00 2001 From: Corey Date: Fri, 5 Jul 2024 11:24:59 -0700 Subject: [PATCH 01/25] Update .spi.yml --- .spi.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.spi.yml b/.spi.yml index 790a3c27..7685f07e 100644 --- a/.spi.yml +++ b/.spi.yml @@ -2,4 +2,5 @@ version: 1 builder: configs: - documentation_targets: [CareKitEssentials] - swift_version: 5.10 + swift_version: 6.0 + platform: ios From 3f55dca33ee3d52b04e59e1a6bc707f7422d68b6 Mon Sep 17 00:00:00 2001 From: Corey Baker Date: Fri, 5 Jul 2024 12:13:35 -0700 Subject: [PATCH 02/25] build for macOS and visionOS --- .github/workflows/ci.yml | 2 +- CareKitEssentials.xcodeproj/project.pbxproj | 36 +++++++++++-------- .../xcshareddata/swiftpm/Package.resolved | 12 +++---- Package.resolved | 12 +++---- Package.swift | 2 +- .../Cards/Shared/CustomLabelView.swift | 32 +++++++++++++---- .../Cards/Shared/InformationHeaderView.swift | 11 ++++-- .../Cards/iOS/SliderLog/Slider.swift | 20 ++++++----- .../Extensions/CGFloat.swift | 12 ++++++- .../CareKitEssentials/Extensions/Image.swift | 2 ++ .../Extensions/NSImage.swift | 23 ++++++++++++ .../Extensions/OCKAnyEvent.swift | 9 +++++ .../Extensions/View+Default.swift | 2 +- 13 files changed, 126 insertions(+), 49 deletions(-) create mode 100644 Sources/CareKitEssentials/Extensions/NSImage.swift diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 22643315..4762a8ac 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -20,7 +20,7 @@ jobs: runs-on: macos-14 strategy: matrix: - destination: ['platform=iOS\ Simulator,name=iPhone\ 14\ Pro\ Max', 'platform=watchOS\ Simulator,name=Apple\ Watch\ Series\ 5\ \(40mm\)'] + destination: ['platform=iOS\ Simulator,name=iPhone\ 14\ Pro\ Max', 'platform=watchOS\ Simulator,name=Apple\ Watch\ Series\ 5\ \(40mm\)', 'platform=macOS CODE_SIGN_IDENTITY=""', 'platform=visionOS\ Simulator,OS=1.2,name=Apple\ Vision\ Pro CODE_SIGN_IDENTITY=""'] action: ['test'] steps: - uses: actions/checkout@v4 diff --git a/CareKitEssentials.xcodeproj/project.pbxproj b/CareKitEssentials.xcodeproj/project.pbxproj index e744e3f9..fdee8330 100644 --- a/CareKitEssentials.xcodeproj/project.pbxproj +++ b/CareKitEssentials.xcodeproj/project.pbxproj @@ -16,6 +16,7 @@ 70BBCB532A12BDAD00759A9C /* Slider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 70BBCB522A12BDAC00759A9C /* Slider.swift */; }; 70BBCB552A12BDBD00759A9C /* SliderLogButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 70BBCB542A12BDBD00759A9C /* SliderLogButton.swift */; }; 70BBCB572A14164E00759A9C /* SliderLogTaskViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 70BBCB562A14164E00759A9C /* SliderLogTaskViewModel.swift */; }; + 70FDC6162C387C9F00A32137 /* NSImage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 70FDC6152C387C9F00A32137 /* NSImage.swift */; }; 911BDB262A11C437004F8442 /* View+Default.swift in Sources */ = {isa = PBXBuildFile; fileRef = 911BDB252A11C437004F8442 /* View+Default.swift */; }; 911BDB282A11C491004F8442 /* CGFloat.swift in Sources */ = {isa = PBXBuildFile; fileRef = 911BDB272A11C491004F8442 /* CGFloat.swift */; }; 911BDB2C2A12F0D3004F8442 /* CareKitEssentialsError.swift in Sources */ = {isa = PBXBuildFile; fileRef = 911BDB2B2A12F0D3004F8442 /* CareKitEssentialsError.swift */; }; @@ -73,6 +74,7 @@ 70BBCB522A12BDAC00759A9C /* Slider.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Slider.swift; sourceTree = ""; }; 70BBCB542A12BDBD00759A9C /* SliderLogButton.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SliderLogButton.swift; sourceTree = ""; }; 70BBCB562A14164E00759A9C /* SliderLogTaskViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SliderLogTaskViewModel.swift; sourceTree = ""; }; + 70FDC6152C387C9F00A32137 /* NSImage.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NSImage.swift; sourceTree = ""; }; 911BDB252A11C437004F8442 /* View+Default.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "View+Default.swift"; sourceTree = ""; }; 911BDB272A11C491004F8442 /* CGFloat.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CGFloat.swift; sourceTree = ""; }; 911BDB2B2A12F0D3004F8442 /* CareKitEssentialsError.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CareKitEssentialsError.swift; sourceTree = ""; }; @@ -203,6 +205,7 @@ OBJ_32 /* CustomLinearCareTaskProgress.swift */, OBJ_27 /* Image.swift */, 700DA9062C2A66BF00435E2C /* Logger.swift */, + 70FDC6152C387C9F00A32137 /* NSImage.swift */, OBJ_29 /* OCKAnyEvent.swift */, OBJ_28 /* OCKAnyEvent+CustomStringConvertable.swift */, OBJ_30 /* OCKAnyOutcome.swift */, @@ -432,6 +435,7 @@ OBJ_765 /* UIImage.swift in Sources */, 911BDB262A11C437004F8442 /* View+Default.swift in Sources */, 911BDB322A130A2B004F8442 /* Utility.swift in Sources */, + 70FDC6162C387C9F00A32137 /* NSImage.swift in Sources */, 70BBCB492A12B9B300759A9C /* OCKScheduleEvent.swift in Sources */, 911BDB2C2A12F0D3004F8442 /* CareKitEssentialsError.swift in Sources */, OBJ_766 /* OSValue.swift in Sources */, @@ -504,7 +508,7 @@ GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; IPHONEOS_DEPLOYMENT_TARGET = 14.0; - MACOSX_DEPLOYMENT_TARGET = 12.0; + MACOSX_DEPLOYMENT_TARGET = 13.0; ONLY_ACTIVE_ARCH = YES; OTHER_SWIFT_FLAGS = "$(inherited) -DXcode"; PRODUCT_NAME = "$(TARGET_NAME)"; @@ -515,6 +519,7 @@ SWIFT_OPTIMIZATION_LEVEL = "-Onone"; USE_HEADERMAP = NO; WATCHOS_DEPLOYMENT_TARGET = 7.0; + XROS_DEPLOYMENT_TARGET = 1.0; }; name = Debug; }; @@ -561,7 +566,7 @@ GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; IPHONEOS_DEPLOYMENT_TARGET = 14.0; - MACOSX_DEPLOYMENT_TARGET = 12.0; + MACOSX_DEPLOYMENT_TARGET = 13.0; OTHER_SWIFT_FLAGS = "$(inherited) -DXcode"; PRODUCT_NAME = "$(TARGET_NAME)"; SDKROOT = macosx; @@ -572,6 +577,7 @@ SWIFT_OPTIMIZATION_LEVEL = "-O"; USE_HEADERMAP = NO; WATCHOS_DEPLOYMENT_TARGET = 7.0; + XROS_DEPLOYMENT_TARGET = 1.0; }; name = Release; }; @@ -593,7 +599,7 @@ "$(inherited)", "$(TOOLCHAIN_DIR)/usr/lib/swift/macosx", ); - MACOSX_DEPLOYMENT_TARGET = 12.0; + MACOSX_DEPLOYMENT_TARGET = 13.0; OTHER_CFLAGS = "$(inherited)"; OTHER_LDFLAGS = "$(inherited)"; OTHER_SWIFT_FLAGS = "$(inherited)"; @@ -601,12 +607,12 @@ PRODUCT_MODULE_NAME = "$(TARGET_NAME:c99extidentifier)"; PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; SKIP_INSTALL = YES; - SUPPORTED_PLATFORMS = "iphoneos iphonesimulator macosx watchos watchsimulator"; + SUPPORTED_PLATFORMS = "iphoneos iphonesimulator macosx watchos watchsimulator xros xrsimulator"; SUPPORTS_MACCATALYST = YES; SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = NO; SWIFT_ACTIVE_COMPILATION_CONDITIONS = "$(inherited)"; SWIFT_VERSION = 5.0; - TARGETED_DEVICE_FAMILY = "1,4"; + TARGETED_DEVICE_FAMILY = "1,4,7"; TARGET_NAME = CareKitEssentials; TVOS_DEPLOYMENT_TARGET = 12.0; WATCHOS_DEPLOYMENT_TARGET = 7.0; @@ -631,7 +637,7 @@ "$(inherited)", "$(TOOLCHAIN_DIR)/usr/lib/swift/macosx", ); - MACOSX_DEPLOYMENT_TARGET = 12.0; + MACOSX_DEPLOYMENT_TARGET = 13.0; OTHER_CFLAGS = "$(inherited)"; OTHER_LDFLAGS = "$(inherited)"; OTHER_SWIFT_FLAGS = "$(inherited)"; @@ -639,12 +645,12 @@ PRODUCT_MODULE_NAME = "$(TARGET_NAME:c99extidentifier)"; PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; SKIP_INSTALL = YES; - SUPPORTED_PLATFORMS = "iphoneos iphonesimulator macosx watchos watchsimulator"; + SUPPORTED_PLATFORMS = "iphoneos iphonesimulator macosx watchos watchsimulator xros xrsimulator"; SUPPORTS_MACCATALYST = YES; SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = NO; SWIFT_ACTIVE_COMPILATION_CONDITIONS = "$(inherited)"; SWIFT_VERSION = 5.0; - TARGETED_DEVICE_FAMILY = "1,4"; + TARGETED_DEVICE_FAMILY = "1,4,7"; TARGET_NAME = CareKitEssentials; TVOS_DEPLOYMENT_TARGET = 12.0; WATCHOS_DEPLOYMENT_TARGET = 7.0; @@ -671,16 +677,16 @@ "@loader_path/../Frameworks", "@loader_path/Frameworks", ); - MACOSX_DEPLOYMENT_TARGET = 12.0; + MACOSX_DEPLOYMENT_TARGET = 13.0; OTHER_CFLAGS = "$(inherited)"; OTHER_LDFLAGS = "$(inherited)"; OTHER_SWIFT_FLAGS = "$(inherited)"; - SUPPORTED_PLATFORMS = "iphoneos iphonesimulator macosx watchos watchsimulator"; + SUPPORTED_PLATFORMS = "iphoneos iphonesimulator macosx watchos watchsimulator xros xrsimulator"; SUPPORTS_MACCATALYST = YES; SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = NO; SWIFT_ACTIVE_COMPILATION_CONDITIONS = "$(inherited)"; SWIFT_VERSION = 5.0; - TARGETED_DEVICE_FAMILY = "1,4"; + TARGETED_DEVICE_FAMILY = "1,4,7"; TARGET_NAME = CareKitEssentialsTests; TVOS_DEPLOYMENT_TARGET = 14.0; WATCHOS_DEPLOYMENT_TARGET = 7.0; @@ -707,16 +713,16 @@ "@loader_path/../Frameworks", "@loader_path/Frameworks", ); - MACOSX_DEPLOYMENT_TARGET = 12.0; + MACOSX_DEPLOYMENT_TARGET = 13.0; OTHER_CFLAGS = "$(inherited)"; OTHER_LDFLAGS = "$(inherited)"; OTHER_SWIFT_FLAGS = "$(inherited)"; - SUPPORTED_PLATFORMS = "iphoneos iphonesimulator macosx watchos watchsimulator"; + SUPPORTED_PLATFORMS = "iphoneos iphonesimulator macosx watchos watchsimulator xros xrsimulator"; SUPPORTS_MACCATALYST = YES; SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = NO; SWIFT_ACTIVE_COMPILATION_CONDITIONS = "$(inherited)"; SWIFT_VERSION = 5.0; - TARGETED_DEVICE_FAMILY = "1,4"; + TARGETED_DEVICE_FAMILY = "1,4,7"; TARGET_NAME = CareKitEssentialsTests; TVOS_DEPLOYMENT_TARGET = 14.0; WATCHOS_DEPLOYMENT_TARGET = 7.0; @@ -761,7 +767,7 @@ repositoryURL = "https://github.com/cbaker6/CareKit.git"; requirement = { kind = upToNextMajorVersion; - minimumVersion = "3.0.0-beta.8"; + minimumVersion = "3.0.0-beta.9"; }; }; /* End XCRemoteSwiftPackageReference section */ diff --git a/CareKitEssentials.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/CareKitEssentials.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved index 4cc8437a..684d32a9 100644 --- a/CareKitEssentials.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/CareKitEssentials.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -6,8 +6,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/cbaker6/CareKit.git", "state" : { - "revision" : "95135c84d3de05e8397bd3a493729876bffc060e", - "version" : "3.0.0-beta.8" + "revision" : "c6a222725b0dbc9fa734e4a023ffe25dbb198869", + "version" : "3.0.0-beta.9" } }, { @@ -15,8 +15,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/apple/FHIRModels.git", "state" : { - "revision" : "e115442fb3c5d44ffb1dc9b4e039b77fd143ad96", - "version" : "0.4.0" + "revision" : "861afd5816a98d38f86220eab2f812d76cad84a0", + "version" : "0.5.0" } }, { @@ -24,8 +24,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/apple/swift-async-algorithms", "state" : { - "revision" : "9cfed92b026c524674ed869a4ff2dcfdeedf8a2a", - "version" : "0.1.0" + "revision" : "da4e36f86544cdf733a40d59b3a2267e3a7bbf36", + "version" : "1.0.0" } }, { diff --git a/Package.resolved b/Package.resolved index 8d0aeaa5..9357c844 100644 --- a/Package.resolved +++ b/Package.resolved @@ -5,8 +5,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/cbaker6/CareKit.git", "state" : { - "revision" : "95135c84d3de05e8397bd3a493729876bffc060e", - "version" : "3.0.0-beta.8" + "revision" : "c6a222725b0dbc9fa734e4a023ffe25dbb198869", + "version" : "3.0.0-beta.9" } }, { @@ -14,8 +14,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/apple/FHIRModels.git", "state" : { - "revision" : "e115442fb3c5d44ffb1dc9b4e039b77fd143ad96", - "version" : "0.4.0" + "revision" : "861afd5816a98d38f86220eab2f812d76cad84a0", + "version" : "0.5.0" } }, { @@ -23,8 +23,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/apple/swift-async-algorithms", "state" : { - "revision" : "9cfed92b026c524674ed869a4ff2dcfdeedf8a2a", - "version" : "0.1.0" + "revision" : "da4e36f86544cdf733a40d59b3a2267e3a7bbf36", + "version" : "1.0.0" } }, { diff --git a/Package.swift b/Package.swift index 0565e28c..a5761401 100644 --- a/Package.swift +++ b/Package.swift @@ -12,7 +12,7 @@ let package = Package( ], dependencies: [ .package(url: "https://github.com/cbaker6/CareKit.git", - .upToNextMajor(from: "3.0.0-beta.8")) + .upToNextMajor(from: "3.0.0-beta.9")) ], targets: [ .target( diff --git a/Sources/CareKitEssentials/Cards/Shared/CustomLabelView.swift b/Sources/CareKitEssentials/Cards/Shared/CustomLabelView.swift index 44bdb61e..05b7c92c 100644 --- a/Sources/CareKitEssentials/Cards/Shared/CustomLabelView.swift +++ b/Sources/CareKitEssentials/Cards/Shared/CustomLabelView.swift @@ -40,13 +40,13 @@ public struct CustomLabelView: View { } HStack(spacing: style.dimension.directionalInsets2.trailing) { - if let asset = viewModel.event.asset { - Image(uiImage: asset) - .resizable() - .renderingMode(.template) - .frame(width: 25, height: 30) - .foregroundColor(Color.accentColor) - } + + eventImage(viewModel.event)? + .resizable() + .renderingMode(.template) + .frame(width: 25, height: 30) + .foregroundColor(Color.accentColor) + VStack(alignment: .leading, spacing: style.dimension.directionalInsets2.bottom) { if let detail = viewModel.event.instructions { @@ -68,6 +68,24 @@ public struct CustomLabelView: View { .padding(.vertical) } + func eventImage(_ event: OCKAnyEvent) -> Image? { + #if canImport(UIKit) + guard let asset = viewModel.event.asset else { + return nil + } + return Image(uiImage: asset) + #elseif canImport(AppKit) + guard let asset = viewModel.event.asset else { + return nil + } + return Image(nsImage: asset) + #else + guard let asset = viewModel.event.task.asset else { + return nil + } + return Image(name: asset) + #endif + } } public extension CustomLabelView { diff --git a/Sources/CareKitEssentials/Cards/Shared/InformationHeaderView.swift b/Sources/CareKitEssentials/Cards/Shared/InformationHeaderView.swift index edbc4e5b..f6019477 100644 --- a/Sources/CareKitEssentials/Cards/Shared/InformationHeaderView.swift +++ b/Sources/CareKitEssentials/Cards/Shared/InformationHeaderView.swift @@ -43,7 +43,7 @@ public struct InformationHeaderView: View { HStack(spacing: style.dimension.directionalInsets2.trailing) { image? .font(.largeTitle) - .foregroundColor(Color(UIColor.lightGray)) + .foregroundColor(Color.gray) VStack(alignment: .leading, spacing: style.dimension.directionalInsets1.top / 4.0) { title .font(.headline) @@ -97,8 +97,15 @@ struct InformationHeaderView_Previews: PreviewProvider { if let event = try? Utility.createNauseaEvent() { InformationHeaderView(title: Text(event.title), information: Text(event.detail ?? ""), - image: Image(uiImage: event.asset ?? UIImage()), + image: imageFromEvent(event), event: event) } } + + static func imageFromEvent(_ event: OCKAnyEvent) -> Image? { + guard let asset = event.task.asset else { + return nil + } + return Image(asset) + } } diff --git a/Sources/CareKitEssentials/Cards/iOS/SliderLog/Slider.swift b/Sources/CareKitEssentials/Cards/iOS/SliderLog/Slider.swift index 3ab7bd29..2f73a06c 100644 --- a/Sources/CareKitEssentials/Cards/iOS/SliderLog/Slider.swift +++ b/Sources/CareKitEssentials/Cards/iOS/SliderLog/Slider.swift @@ -145,7 +145,7 @@ struct Slider: View { })) addTicks(sliderWidth: sliderWidth) .if(!viewModel.isActive) { - $0.accentColor(Color(style.color.customGray)) + $0.accentColor(Color.gray) } } .frame(width: sliderWidth, height: sliderHeight) @@ -161,7 +161,7 @@ struct Slider: View { LinearGradient(gradient: Gradient(colors: gradientColors ?? []), startPoint: .leading, endPoint: .trailing) : - LinearGradient(gradient: Gradient(colors: [Color(style.color.customGray)]), + LinearGradient(gradient: Gradient(colors: [Color.gray]), startPoint: .leading, endPoint: .trailing)) .mask(SwiftUI.Slider(value: $viewModel.valueAsDouble, in: range.0...range.1)) @@ -169,7 +169,7 @@ struct Slider: View { SwiftUI.Slider(value: $viewModel.valueAsDouble, in: range.0...range.1) .if(gradientColors == nil) { - $0.accentColor(viewModel.isActive ? .accentColor : Color(style.color.customGray)) + $0.accentColor(viewModel.isActive ? .accentColor : Color.gray) } .if(gradientColors != nil) { $0.accentColor(.clear) } } @@ -181,7 +181,7 @@ struct Slider: View { let barRightSize = CGSize(width: CGFloat(offsetX), height: height) let barLeft = Rectangle() .if(gradientColors == nil) { - $0.foregroundColor(viewModel.isActive ? .accentColor : Color(style.color.customGray)) + $0.foregroundColor(viewModel.isActive ? .accentColor : Color.gray) } .if(gradientColors != nil) { $0 @@ -190,11 +190,11 @@ struct Slider: View { LinearGradient(gradient: Gradient(colors: gradientColors ?? []), startPoint: .leading, endPoint: .trailing) : - LinearGradient(gradient: Gradient(colors: [Color(style.color.customGray)]), + LinearGradient(gradient: Gradient(colors: [Color.gray]), startPoint: .leading, endPoint: .trailing)) } - let barRight = Color(style.color.white) + let barRight = Color.white return ZStack { barLeft @@ -206,8 +206,10 @@ struct Slider: View { size: barRightSize, radius: cornerRadius!)) RoundedRectangle(cornerRadius: cornerRadius!) - .stroke(Color(style.color.customGray), - lineWidth: borderWidth) + .stroke( + Color.gray, + lineWidth: borderWidth + ) } } @@ -260,7 +262,7 @@ private struct SliderTickMark: View { private let sliderHeight: CGFloat private let width: CGFloat private var color: Color { - value > sliderValue ? Color(style.color.customGray) : value == sliderValue ? .clear : Color(style.color.white) + value > sliderValue ? Color.gray : value == sliderValue ? .clear : Color.white } public init(sliderValue: Binding, diff --git a/Sources/CareKitEssentials/Extensions/CGFloat.swift b/Sources/CareKitEssentials/Extensions/CGFloat.swift index 08a6d5ab..7784e950 100644 --- a/Sources/CareKitEssentials/Extensions/CGFloat.swift +++ b/Sources/CareKitEssentials/Extensions/CGFloat.swift @@ -13,6 +13,16 @@ extension CGFloat { /// Scaled value for the current size category. func scaled() -> CGFloat { - UIFontMetrics.default.scaledValue(for: self) + + #if canImport(UIKit) + + return UIFontMetrics.default.scaledValue(for: self) + + #else + + return 1 + + #endif + } } diff --git a/Sources/CareKitEssentials/Extensions/Image.swift b/Sources/CareKitEssentials/Extensions/Image.swift index f2ea0fab..39514bcc 100644 --- a/Sources/CareKitEssentials/Extensions/Image.swift +++ b/Sources/CareKitEssentials/Extensions/Image.swift @@ -14,6 +14,7 @@ public extension Image { // We can't be sure if the image they provide is in the assets folder, in the bundle, or in a directory. guard let name = name else { return nil } // We can check all 3 possibilities and then choose whichever is non-nil. + #if canImport(UIKit) guard UIImage(systemName: name) != nil else { guard UIImage(named: name) != nil else { guard let otherUrlUIImage = UIImage(contentsOfFile: name) else { @@ -23,6 +24,7 @@ public extension Image { } return Image(name) } + #endif return Image(systemName: name) } } diff --git a/Sources/CareKitEssentials/Extensions/NSImage.swift b/Sources/CareKitEssentials/Extensions/NSImage.swift new file mode 100644 index 00000000..59e2bd46 --- /dev/null +++ b/Sources/CareKitEssentials/Extensions/NSImage.swift @@ -0,0 +1,23 @@ +// +// NSImage.swift +// +// +// Created by Corey Baker on 7/5/24. +// + +#if canImport(AppKit) +import Foundation +import AppKit + +public extension NSImage { + static func asset(_ name: String?) -> NSImage? { + // We can't be sure if the image they provide is in the assets folder, in the bundle, or in a directory. + guard let name = name else { return nil } + // We can check all 3 possibilities and then choose whichever is non-nil. + let symbol = NSImage(systemSymbolName: name, accessibilityDescription: nil) + let appAssetsImage = NSImage(named: name) + let otherUrlImage = NSImage(contentsOfFile: name) + return otherUrlImage ?? appAssetsImage ?? symbol + } +} +#endif diff --git a/Sources/CareKitEssentials/Extensions/OCKAnyEvent.swift b/Sources/CareKitEssentials/Extensions/OCKAnyEvent.swift index ecdfb130..2f47fdba 100644 --- a/Sources/CareKitEssentials/Extensions/OCKAnyEvent.swift +++ b/Sources/CareKitEssentials/Extensions/OCKAnyEvent.swift @@ -44,6 +44,15 @@ public extension OCKAnyEvent { } return UIImage.asset(asset) } + + #elseif canImport(AppKit) + /// The first event task asset. + var asset: NSImage? { + guard let asset = self.task.asset else { + return nil + } + return NSImage.asset(asset) + } #endif /// The first event outcome. diff --git a/Sources/CareKitEssentials/Extensions/View+Default.swift b/Sources/CareKitEssentials/Extensions/View+Default.swift index db6109f9..30fed6d8 100644 --- a/Sources/CareKitEssentials/Extensions/View+Default.swift +++ b/Sources/CareKitEssentials/Extensions/View+Default.swift @@ -10,7 +10,7 @@ import Foundation import SwiftUI extension View { - #if !os(watchOS) + #if !os(watchOS) && canImport(UIKit) func formattedHostingController() -> UIHostingController { let viewController = UIHostingController(rootView: self) viewController.view.backgroundColor = .clear From 5132a32e074f593cad0eaf162a2b6dd0ac5c3168 Mon Sep 17 00:00:00 2001 From: Corey Baker Date: Fri, 5 Jul 2024 12:33:12 -0700 Subject: [PATCH 03/25] improve --- .../Cards/Shared/CustomLabelView.swift | 21 +------------------ .../Cards/Shared/InformationHeaderView.swift | 17 ++++++--------- .../Extensions/OCKAnyEvent.swift | 19 +++++++++++++++++ 3 files changed, 26 insertions(+), 31 deletions(-) diff --git a/Sources/CareKitEssentials/Cards/Shared/CustomLabelView.swift b/Sources/CareKitEssentials/Cards/Shared/CustomLabelView.swift index 05b7c92c..404fabeb 100644 --- a/Sources/CareKitEssentials/Cards/Shared/CustomLabelView.swift +++ b/Sources/CareKitEssentials/Cards/Shared/CustomLabelView.swift @@ -41,7 +41,7 @@ public struct CustomLabelView: View { HStack(spacing: style.dimension.directionalInsets2.trailing) { - eventImage(viewModel.event)? + viewModel.event.image()? .resizable() .renderingMode(.template) .frame(width: 25, height: 30) @@ -67,25 +67,6 @@ public struct CustomLabelView: View { } .padding(.vertical) } - - func eventImage(_ event: OCKAnyEvent) -> Image? { - #if canImport(UIKit) - guard let asset = viewModel.event.asset else { - return nil - } - return Image(uiImage: asset) - #elseif canImport(AppKit) - guard let asset = viewModel.event.asset else { - return nil - } - return Image(nsImage: asset) - #else - guard let asset = viewModel.event.task.asset else { - return nil - } - return Image(name: asset) - #endif - } } public extension CustomLabelView { diff --git a/Sources/CareKitEssentials/Cards/Shared/InformationHeaderView.swift b/Sources/CareKitEssentials/Cards/Shared/InformationHeaderView.swift index f6019477..abf282f2 100644 --- a/Sources/CareKitEssentials/Cards/Shared/InformationHeaderView.swift +++ b/Sources/CareKitEssentials/Cards/Shared/InformationHeaderView.swift @@ -95,17 +95,12 @@ public struct InformationHeaderView: View { struct InformationHeaderView_Previews: PreviewProvider { static var previews: some View { if let event = try? Utility.createNauseaEvent() { - InformationHeaderView(title: Text(event.title), - information: Text(event.detail ?? ""), - image: imageFromEvent(event), - event: event) + InformationHeaderView( + title: Text(event.title), + information: Text(event.detail ?? ""), + image: event.image(), + event: event + ) } } - - static func imageFromEvent(_ event: OCKAnyEvent) -> Image? { - guard let asset = event.task.asset else { - return nil - } - return Image(asset) - } } diff --git a/Sources/CareKitEssentials/Extensions/OCKAnyEvent.swift b/Sources/CareKitEssentials/Extensions/OCKAnyEvent.swift index 2f47fdba..410eff5a 100644 --- a/Sources/CareKitEssentials/Extensions/OCKAnyEvent.swift +++ b/Sources/CareKitEssentials/Extensions/OCKAnyEvent.swift @@ -150,4 +150,23 @@ public extension OCKAnyEvent { newOutcome.values = prependedValues return OCKAnyEvent(task: task, outcome: newOutcome, scheduleEvent: scheduleEvent) } + + func image() -> Image? { + #if canImport(UIKit) + guard let asset = self.asset else { + return nil + } + return Image(uiImage: asset) + #elseif canImport(AppKit) + guard let asset = self.asset else { + return nil + } + return Image(nsImage: asset) + #else + guard let asset = self.task.asset else { + return nil + } + return Image(name: asset) + #endif + } } From fcbf0220351a7fc3338f6447e6fd2a17fe3a76ed Mon Sep 17 00:00:00 2001 From: Corey Baker Date: Fri, 5 Jul 2024 12:40:39 -0700 Subject: [PATCH 04/25] improve image check --- Sources/CareKitEssentials/Extensions/Image.swift | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/Sources/CareKitEssentials/Extensions/Image.swift b/Sources/CareKitEssentials/Extensions/Image.swift index 39514bcc..223f7448 100644 --- a/Sources/CareKitEssentials/Extensions/Image.swift +++ b/Sources/CareKitEssentials/Extensions/Image.swift @@ -24,6 +24,16 @@ public extension Image { } return Image(name) } + #elseif canImport(AppKit) + guard NSImage(systemSymbolName: name, accessibilityDescription: nil) != nil else { + guard NSImage(named: name) != nil else { + guard let otherUrlUIImage = NSImage(contentsOfFile: name) else { + return nil + } + return Image(nsImage: otherUrlUIImage) + } + return Image(name) + } #endif return Image(systemName: name) } From 473c6dd87e12a866b0a92dfa40236a4133640d62 Mon Sep 17 00:00:00 2001 From: Corey Baker Date: Fri, 5 Jul 2024 16:03:57 -0700 Subject: [PATCH 05/25] fix slider behavior --- .../Cards/Shared/CardViewModel.swift | 12 +- .../Cards/Shared/CareKitEssentialView.swift | 31 ++-- .../Cards/Shared/CustomLabelView.swift | 4 +- .../Cards/iOS/SliderLog/Slider.swift | 42 +++-- .../Cards/iOS/SliderLog/SliderLogButton.swift | 9 +- .../iOS/SliderLog/SliderLogTaskView.swift | 166 ++++++++++-------- .../SliderLog/SliderLogTaskViewModel.swift | 50 ++++-- .../DigitalCrown/DigitalCrownViewModel.swift | 2 +- 8 files changed, 176 insertions(+), 140 deletions(-) diff --git a/Sources/CareKitEssentials/Cards/Shared/CardViewModel.swift b/Sources/CareKitEssentials/Cards/Shared/CardViewModel.swift index 600600d9..11b9b6e1 100644 --- a/Sources/CareKitEssentials/Cards/Shared/CardViewModel.swift +++ b/Sources/CareKitEssentials/Cards/Shared/CardViewModel.swift @@ -64,7 +64,7 @@ open class CardViewModel: ObservableObject { public private(set) var detailsInformation: String? var initialValue: OCKOutcomeValue - var action: ((OCKOutcomeValue?) async -> Void)? + var action: ((OCKOutcomeValue?) async -> OCKAnyOutcome)? /// Create an instance with specified content for an event. The view will update when changes /// occur in the store. @@ -79,7 +79,7 @@ open class CardViewModel: ObservableObject { initialValue: OCKOutcomeValue = OCKOutcomeValue(0.0), detailsTitle: String? = nil, detailsInformation: String? = nil, - action: ((OCKOutcomeValue?) async -> Void)? = nil + action: ((OCKOutcomeValue?) async -> OCKAnyOutcome)? = nil ) { self.value = event.outcomeFirstValue ?? initialValue self.initialValue = initialValue @@ -91,13 +91,13 @@ open class CardViewModel: ObservableObject { // MARK: Intents - /// Update the the viewModel with the current event. + /// Update the the viewModel with the current outcome. /// - Parameters: /// - event: The current event. @MainActor - public func updateEvent(_ event: OCKAnyEvent) { - self.event = event - value = event.outcomeFirstValue ?? initialValue + public func updateOutcome(_ outcome: OCKAnyOutcome) { + let outcomeWithSortedValues = outcome.sortedOutcomeValuesByRecency() + value = outcomeWithSortedValues.values.first ?? initialValue initialValue = value } diff --git a/Sources/CareKitEssentials/Cards/Shared/CareKitEssentialView.swift b/Sources/CareKitEssentials/Cards/Shared/CareKitEssentialView.swift index 8bec1d52..03f9c781 100644 --- a/Sources/CareKitEssentials/Cards/Shared/CareKitEssentialView.swift +++ b/Sources/CareKitEssentials/Cards/Shared/CareKitEssentialView.swift @@ -25,7 +25,7 @@ public protocol CareKitEssentialView: View { func updateEvent( _ event: OCKAnyEvent, with values: [OCKOutcomeValue]? - ) async throws + ) async throws -> OCKAnyOutcome /// Save a new `OCKAnyOutcome`. /// - Parameters: @@ -48,23 +48,22 @@ public protocol CareKitEssentialView: View { public extension CareKitEssentialView { - func deleteEventOutcome(_ event: OCKAnyEvent) async throws { + func deleteEventOutcome(_ event: OCKAnyEvent) async throws -> OCKAnyOutcome { guard let outcome = event.outcome else { throw CareKitEssentialsError.errorString("The event does not contain an outcome: \(event)") } - _ = try await careStore.deleteAnyOutcome(outcome) + return try await careStore.deleteAnyOutcome(outcome) } func updateEvent( _ event: OCKAnyEvent, with values: [OCKOutcomeValue]? - ) async throws { + ) async throws -> OCKAnyOutcome { guard let values = values else { // Attempts to delete outcome if it already exists. - try await deleteEventOutcome(event) - return + return try await deleteEventOutcome(event) } - _ = try await self.appendOutcomeValues( + return try await self.appendOutcomeValues( values, event: event ) @@ -85,7 +84,7 @@ public extension CareKitEssentialView { func appendOutcomeValues( _ values: [OCKOutcomeValue], event: OCKAnyEvent - ) async throws { + ) async throws -> OCKAnyOutcome { // Update the outcome with the new value guard var outcome = event.outcome else { @@ -93,12 +92,10 @@ public extension CareKitEssentialView { values, event: event ) - _ = try await careStore.addAnyOutcome(outcome) - return + return try await careStore.addAnyOutcome(outcome) } outcome.values.append(contentsOf: values) - _ = try await careStore.updateAnyOutcome(outcome) - return + return try await careStore.updateAnyOutcome(outcome) } /// Set/Replace the `OCKOutcomeValue`'s of an event. @@ -109,13 +106,12 @@ public extension CareKitEssentialView { func saveOutcomeValues( _ values: [OCKOutcomeValue], event: OCKAnyEvent - ) async throws { + ) async throws -> OCKAnyOutcome { // Check if outcome values need to be updated. guard !values.isEmpty else { // If the event has already been completed - try await deleteEventOutcome(event) - return + return try await deleteEventOutcome(event) } // If the event has already been completed @@ -125,12 +121,11 @@ public extension CareKitEssentialView { values, event: event ) - _ = try await careStore.addAnyOutcome(outcome) - return + return try await careStore.addAnyOutcome(outcome) } // Update the outcome with the new values. currentOutcome.values = values - _ = try await careStore.updateAnyOutcome(currentOutcome) + return try await careStore.updateAnyOutcome(currentOutcome) } /// Create an outcome for an event. diff --git a/Sources/CareKitEssentials/Cards/Shared/CustomLabelView.swift b/Sources/CareKitEssentials/Cards/Shared/CustomLabelView.swift index 404fabeb..d57c70a6 100644 --- a/Sources/CareKitEssentials/Cards/Shared/CustomLabelView.swift +++ b/Sources/CareKitEssentials/Cards/Shared/CustomLabelView.swift @@ -101,7 +101,7 @@ public extension CustomLabelView { initialValue: OCKOutcomeValue = OCKOutcomeValue(0.0), detailsTitle: String? = nil, detailsInformation: String? = nil, - action: ((OCKOutcomeValue?) async -> Void)? = nil, + action: ((OCKOutcomeValue?) async -> OCKAnyOutcome)? = nil, @ViewBuilder header: () -> Header) { self.init( viewModel: .init( @@ -160,7 +160,7 @@ public extension CustomLabelView where Header == InformationHeaderView { initialValue: OCKOutcomeValue = OCKOutcomeValue(0.0), detailsTitle: String? = nil, detailsInformation: String? = nil, - action: ((OCKOutcomeValue?) async -> Void)? = nil) { + action: ((OCKOutcomeValue?) async -> OCKAnyOutcome)? = nil) { self.init(viewModel: .init(event: event.result, initialValue: initialValue, detailsTitle: detailsTitle, diff --git a/Sources/CareKitEssentials/Cards/iOS/SliderLog/Slider.swift b/Sources/CareKitEssentials/Cards/iOS/SliderLog/Slider.swift index 2f73a06c..7bf253ad 100644 --- a/Sources/CareKitEssentials/Cards/iOS/SliderLog/Slider.swift +++ b/Sources/CareKitEssentials/Cards/iOS/SliderLog/Slider.swift @@ -131,24 +131,28 @@ struct Slider: View { let drag = DragGesture(minimumDistance: 0) return usesSystemSlider ? - ViewBuilder.buildEither(first: - systemSliderView() - .gesture(drag.onChanged({ drag in - onDragChange(drag, sliderWidth: sliderWidth) - })) - .frame(width: sliderWidth, height: imageWidth)) : - ViewBuilder.buildEither(second: - ZStack { - fillerBarView(width: sliderWidth, height: sliderHeight!) - .gesture(drag.onChanged({ drag in - onDragChange(drag, sliderWidth: sliderWidth) - })) - addTicks(sliderWidth: sliderWidth) - .if(!viewModel.isActive) { - $0.accentColor(Color.gray) - } - } - .frame(width: sliderWidth, height: sliderHeight) + ViewBuilder.buildEither( + first: systemSliderView() + .gesture( + drag.onChanged({ drag in + onDragChange(drag, sliderWidth: sliderWidth) + }) + ) + .frame(width: sliderWidth, height: imageWidth)) : + ViewBuilder.buildEither( + second: ZStack { + fillerBarView(width: sliderWidth, height: sliderHeight!) + .gesture( + drag.onChanged({ drag in + onDragChange(drag, sliderWidth: sliderWidth) + }) + ) + addTicks(sliderWidth: sliderWidth) + .if(!viewModel.isActive) { + $0.accentColor(Color.gray) + } + } + .frame(width: sliderWidth, height: sliderHeight) ) } @@ -167,7 +171,7 @@ struct Slider: View { .mask(SwiftUI.Slider(value: $viewModel.valueAsDouble, in: range.0...range.1)) } - SwiftUI.Slider(value: $viewModel.valueAsDouble, in: range.0...range.1) + SwiftUI.Slider(value: $viewModel.valueAsDouble, in: range.0...range.1, step: viewModel.step) .if(gradientColors == nil) { $0.accentColor(viewModel.isActive ? .accentColor : Color.gray) } diff --git a/Sources/CareKitEssentials/Cards/iOS/SliderLog/SliderLogButton.swift b/Sources/CareKitEssentials/Cards/iOS/SliderLog/SliderLogButton.swift index e2805629..50faf8d8 100644 --- a/Sources/CareKitEssentials/Cards/iOS/SliderLog/SliderLogButton.swift +++ b/Sources/CareKitEssentials/Cards/iOS/SliderLog/SliderLogButton.swift @@ -53,6 +53,7 @@ struct SliderLogButton: CareKitEssentialView { .foregroundColor(.accentColor) } .disabled(!viewModel.isActive) + .foregroundColor(viewModel.isActive ? .accentColor : Color.gray) .padding(.bottom) Button(action: {}) { @@ -72,15 +73,15 @@ struct SliderLogButton: CareKitEssentialView { guard let action = viewModel.action else { do { - try await updateEvent(viewModel.event, with: [newOutcomeValue]) + let outcome = try await updateEvent(viewModel.event, with: [newOutcomeValue]) + viewModel.updateOutcome(outcome) } catch { Logger.essentialView.error("Cannot update store with outcome value: \(error)") } - viewModel.isActive = false return } - await action(newOutcomeValue) - viewModel.isActive = false + let outcome = await action(newOutcomeValue) + viewModel.updateOutcome(outcome) } } } diff --git a/Sources/CareKitEssentials/Cards/iOS/SliderLog/SliderLogTaskView.swift b/Sources/CareKitEssentials/Cards/iOS/SliderLog/SliderLogTaskView.swift index bc3fce9d..88677eee 100644 --- a/Sources/CareKitEssentials/Cards/iOS/SliderLog/SliderLogTaskView.swift +++ b/Sources/CareKitEssentials/Cards/iOS/SliderLog/SliderLogTaskView.swift @@ -134,7 +134,7 @@ public extension SliderLogTaskView { detailsInformation: String? = nil, range: ClosedRange, step: Double = 1, - action: ((OCKOutcomeValue?) async -> Void)? = nil, + action: ((OCKOutcomeValue?) async -> OCKAnyOutcome)? = nil, @ViewBuilder header: () -> Header, @ViewBuilder slider: () -> Slider) { self.init(isHeaderPadded: false, @@ -236,33 +236,42 @@ public extension SliderLogTaskView where Header == InformationHeaderView, Slider /// indexes in the array correspond to the maximum side of the scale. Setting this value to nil results in no /// gradient being drawn. Defaults to nil. An example usage would set an array of red and green to visually /// indicate a scale from bad to good. - init(title: Text, - detail: Text? = nil, - instructions: Text? = nil, - viewModel: SliderLogTaskViewModel, - minimumImage: Image? = nil, - maximumImage: Image? = nil, - minimumDescription: String? = nil, - maximumDescription: String? = nil, - style: SliderStyle = .ticked, - gradientColors: [Color]? = nil) { - self.init(isHeaderPadded: true, - isSliderPadded: true, - instructions: instructions, - viewModel: viewModel, - header: { - InformationHeaderView(title: title, - information: detail, - event: viewModel.event) }, - slider: { - _SliderLogTaskViewSlider(viewModel: viewModel, - minimumImage: minimumImage, - maximumImage: maximumImage, - minimumDescription: minimumDescription, - maximumDescription: maximumDescription, - style: style, - gradientColors: gradientColors) - }) + init( + title: Text, + detail: Text? = nil, + instructions: Text? = nil, + viewModel: SliderLogTaskViewModel, + minimumImage: Image? = nil, + maximumImage: Image? = nil, + minimumDescription: String? = nil, + maximumDescription: String? = nil, + style: SliderStyle = .ticked, + gradientColors: [Color]? = nil + ) { + self.init( + isHeaderPadded: true, + isSliderPadded: true, + instructions: instructions, + viewModel: viewModel, + header: { + InformationHeaderView( + title: title, + information: detail, + event: viewModel.event + ) + }, + slider: { + _SliderLogTaskViewSlider( + viewModel: viewModel, + minimumImage: minimumImage, + maximumImage: maximumImage, + minimumDescription: minimumDescription, + maximumDescription: maximumDescription, + style: style, + gradientColors: gradientColors + ) + } + ) } /// Create a view using data from an event. @@ -290,39 +299,45 @@ public extension SliderLogTaskView where Header == InformationHeaderView, Slider /// indexes in the array correspond to the maximum side of the scale. Setting this value to nil results in no /// gradient being drawn. Defaults to nil. An example usage would set an array of red and green to visually /// indicate a scale from bad to good. - init(title: Text, - detail: Text? = nil, - instructions: Text? = nil, - event: CareStoreFetchedResult, - detailsTitle: String? = nil, - detailsInformation: String? = nil, - initialValue: Double? = 0, - range: ClosedRange, - step: Double = 1, - action: ((OCKOutcomeValue?) async -> Void)? = nil, - minimumImage: Image? = nil, - maximumImage: Image? = nil, - minimumDescription: String? = nil, - maximumDescription: String? = nil, - style: SliderStyle = .ticked, - gradientColors: [Color]? = nil) { - let viewModel = SliderLogTaskViewModel(event: event.result, - detailsTitle: detailsTitle, - detailsInformation: detailsInformation, - initialValue: initialValue, - range: range, - step: step, - action: action) - self.init(title: title, - detail: detail, - instructions: instructions, - viewModel: viewModel, - minimumImage: minimumImage, - maximumImage: maximumImage, - minimumDescription: minimumDescription, - maximumDescription: maximumDescription, - style: style, - gradientColors: gradientColors) + init( + title: Text, + detail: Text? = nil, + instructions: Text? = nil, + event: CareStoreFetchedResult, + detailsTitle: String? = nil, + detailsInformation: String? = nil, + initialValue: Double? = 0, + range: ClosedRange, + step: Double = 1, + action: ((OCKOutcomeValue?) async -> OCKAnyOutcome)? = nil, + minimumImage: Image? = nil, + maximumImage: Image? = nil, + minimumDescription: String? = nil, + maximumDescription: String? = nil, + style: SliderStyle = .ticked, + gradientColors: [Color]? = nil + ) { + let viewModel = SliderLogTaskViewModel( + event: event.result, + detailsTitle: detailsTitle, + detailsInformation: detailsInformation, + initialValue: initialValue, + range: range, + step: step, + action: action + ) + self.init( + title: title, + detail: detail, + instructions: instructions, + viewModel: viewModel, + minimumImage: minimumImage, + maximumImage: maximumImage, + minimumDescription: minimumDescription, + maximumDescription: maximumDescription, + style: style, + gradientColors: gradientColors + ) } } @@ -371,21 +386,30 @@ public struct _SliderLogTaskViewSlider: View { // swiftlint:disable:this type_na struct SliderLogTaskView_Previews: PreviewProvider { static var previews: some View { if let event = try? Utility.createNauseaEvent() { + let store = Utility.createPreviewStore() + let viewModel = SliderLogTaskViewModel( + event: event, + range: 0...10 + ) VStack { - SliderLogTaskView(title: Text(event.title), - detail: Text(event.detail ?? ""), - viewModel: .init(event: event, range: 0...10), - gradientColors: [.green, .yellow, .red]) + SliderLogTaskView( + title: Text(event.title), + detail: Text(event.detail ?? ""), + viewModel: viewModel, + gradientColors: [.green, .yellow, .red] + ) .padding() - SliderLogTaskView(title: Text(event.title), - detail: Text(event.detail ?? ""), - viewModel: .init(event: event, range: 0...10), - style: .system, - gradientColors: [.green, .yellow, .red]) + SliderLogTaskView( + title: Text(event.title), + detail: Text(event.detail ?? ""), + viewModel: viewModel, + style: .system, + gradientColors: [.green, .yellow, .red] + ) .padding() } - .environment(\.careStore, Utility.createPreviewStore()) + .environment(\.careStore, store) } } } diff --git a/Sources/CareKitEssentials/Cards/iOS/SliderLog/SliderLogTaskViewModel.swift b/Sources/CareKitEssentials/Cards/iOS/SliderLog/SliderLogTaskViewModel.swift index 90606d6b..18635e57 100644 --- a/Sources/CareKitEssentials/Cards/iOS/SliderLog/SliderLogTaskViewModel.swift +++ b/Sources/CareKitEssentials/Cards/iOS/SliderLog/SliderLogTaskViewModel.swift @@ -40,34 +40,46 @@ open class SliderLogTaskViewModel: CardViewModel { - parameter step: Value of the increment that the slider takes. Default value is 1. - parameter action: The action to perform when the button is tapped. Defaults to saving the outcome directly. */ - public init(event: OCKAnyEvent, - detailsTitle: String? = nil, - detailsInformation: String? = nil, - initialValue: Double? = 0, - range: ClosedRange, - step: Double = 1, - action: ((OCKOutcomeValue?) async -> Void)? = nil) { + public init( + event: OCKAnyEvent, + detailsTitle: String? = nil, + detailsInformation: String? = nil, + initialValue: Double? = 0, + range: ClosedRange, + step: Double = 1, + action: ((OCKOutcomeValue?) async -> OCKAnyOutcome)? = nil + ) { if let values = event.outcomeValues { self.previousValues = values.compactMap { $0.doubleValue } } self.range = range self.step = step - var currentInitialValue = range.lowerBound + round((range.upperBound - range.lowerBound) / (step * 2)) * step + var currentInitialDoubleValue = range.lowerBound + + round((range.upperBound - range.lowerBound) / (step * 2)) * step if let initialValue = initialValue { - currentInitialValue = initialValue + currentInitialDoubleValue = initialValue } - super.init(event: event, - initialValue: OCKOutcomeValue(currentInitialValue), - detailsTitle: detailsTitle, - detailsInformation: detailsInformation, - action: action + var currentInitialOutcome = OCKOutcomeValue(currentInitialDoubleValue) + if let initialOutcome = event.outcomeFirstValue, + let initialOutcomeDouble = initialOutcome.doubleValue { + if initialOutcomeDouble != currentInitialDoubleValue { + isActive = false + currentInitialOutcome = initialOutcome + } + } + super.init( + event: event, + initialValue: currentInitialOutcome, + detailsTitle: detailsTitle, + detailsInformation: detailsInformation, + action: action ) } - public override func updateEvent(_ event: OCKAnyEvent) { - super.updateEvent(event) - if let values = event.outcomeValues { - self.previousValues = values.compactMap { $0.doubleValue } - } + public override func updateOutcome(_ outcome: OCKAnyOutcome) { + super.updateOutcome(outcome) + let values = outcome.sortedOutcomeValuesByRecency().values + self.previousValues = values.compactMap { $0.doubleValue } + self.isActive = false } } diff --git a/Sources/CareKitEssentials/Cards/watchOS/DigitalCrown/DigitalCrownViewModel.swift b/Sources/CareKitEssentials/Cards/watchOS/DigitalCrown/DigitalCrownViewModel.swift index db8526ef..b462ba1d 100644 --- a/Sources/CareKitEssentials/Cards/watchOS/DigitalCrown/DigitalCrownViewModel.swift +++ b/Sources/CareKitEssentials/Cards/watchOS/DigitalCrown/DigitalCrownViewModel.swift @@ -70,7 +70,7 @@ open class DigitalCrownViewModel: CardViewModel { incrementValue: Double = 1, emojis: [String] = [], colorRatio: Double = 0.2, - action: ((OCKOutcomeValue?) async -> Void)? = nil) { + action: ((OCKOutcomeValue?) async -> OCKAnyOutcome)? = nil) { self.startValue = startValue self.incrementValue = incrementValue self.colorRatio = colorRatio From be0043fe613d4175bd4515f4a8a532e37e0cba68 Mon Sep 17 00:00:00 2001 From: Corey Baker Date: Fri, 5 Jul 2024 16:15:11 -0700 Subject: [PATCH 06/25] fix DigitalCrownView --- .../DigitalCrown/DigitalCrownView.swift | 51 +++++++++++-------- .../DigitalCrown/DigitalCrownViewFooter.swift | 9 +++- 2 files changed, 36 insertions(+), 24 deletions(-) diff --git a/Sources/CareKitEssentials/Cards/watchOS/DigitalCrown/DigitalCrownView.swift b/Sources/CareKitEssentials/Cards/watchOS/DigitalCrown/DigitalCrownView.swift index 5263244e..0f8f59e8 100644 --- a/Sources/CareKitEssentials/Cards/watchOS/DigitalCrown/DigitalCrownView.swift +++ b/Sources/CareKitEssentials/Cards/watchOS/DigitalCrown/DigitalCrownView.swift @@ -124,28 +124,35 @@ public extension DigitalCrownView where Footer == DigitalCrownViewFooter { /// - colorRatio: The ratio effect on the color gradient. /// - action: The action to perform when the log button is tapped. /// - header: Short and descriptive content that identifies the event. - init(event: CareStoreFetchedResult, - detailsTitle: String? = nil, - detailsInformation: String? = nil, - initialValue: Double? = nil, - startValue: Double = 0, - endValue: Double? = nil, - incrementValue: Double = 1, - emojis: [String] = [], - colorRatio: Double = 0.2, - action: ((OCKOutcomeValue?) async -> Void)? = nil, - @ViewBuilder header: () -> Header) { - self.init(viewModel: .init(event: event.result, - detailsTitle: detailsTitle, - detailsInformation: detailsInformation, - initialValue: initialValue, - startValue: startValue, - endValue: endValue, - incrementValue: incrementValue, - emojis: emojis, - colorRatio: colorRatio, - action: action), - header: header) + init( + event: CareStoreFetchedResult, + detailsTitle: String? = nil, + detailsInformation: String? = nil, + initialValue: Double? = nil, + startValue: Double = 0, + endValue: Double? = nil, + incrementValue: Double = 1, + emojis: [String] = [], + colorRatio: Double = 0.2, + action: ((OCKOutcomeValue?) async -> OCKAnyOutcome)? = nil, + @ViewBuilder header: () -> Header + ) { + let viewModel = DigitalCrownViewModel( + event: event.result, + detailsTitle: detailsTitle, + detailsInformation: detailsInformation, + initialValue: initialValue, + startValue: startValue, + endValue: endValue, + incrementValue: incrementValue, + emojis: emojis, + colorRatio: colorRatio, + action: action + ) + self.init( + viewModel: viewModel, + header: header + ) } } diff --git a/Sources/CareKitEssentials/Cards/watchOS/DigitalCrown/DigitalCrownViewFooter.swift b/Sources/CareKitEssentials/Cards/watchOS/DigitalCrown/DigitalCrownViewFooter.swift index 8ca20c87..bd0cf8b1 100644 --- a/Sources/CareKitEssentials/Cards/watchOS/DigitalCrown/DigitalCrownViewFooter.swift +++ b/Sources/CareKitEssentials/Cards/watchOS/DigitalCrown/DigitalCrownViewFooter.swift @@ -80,13 +80,18 @@ public struct DigitalCrownViewFooter: CareKitEssentialView { guard let action = viewModel.action else { do { - try await updateEvent(viewModel.event, with: [newOutcomeValue]) + let outcome = try await updateEvent( + viewModel.event, + with: [newOutcomeValue] + ) + viewModel.updateOutcome(outcome) } catch { Logger.essentialView.error("Cannot update store with outcome value: \(error)") } return } - await action(newOutcomeValue) + let outcome = await action(newOutcomeValue) + viewModel.updateOutcome(outcome) } } } From e30ea86b7dd0f6e59c93d16d45ba19cb85a333d9 Mon Sep 17 00:00:00 2001 From: Corey Baker Date: Fri, 5 Jul 2024 20:51:21 -0700 Subject: [PATCH 07/25] Update to latest CareKit --- CareKitEssentials.xcodeproj/project.pbxproj | 2 +- .../project.xcworkspace/xcshareddata/swiftpm/Package.resolved | 4 ++-- Package.resolved | 4 ++-- Package.swift | 2 +- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/CareKitEssentials.xcodeproj/project.pbxproj b/CareKitEssentials.xcodeproj/project.pbxproj index fdee8330..36b090c1 100644 --- a/CareKitEssentials.xcodeproj/project.pbxproj +++ b/CareKitEssentials.xcodeproj/project.pbxproj @@ -767,7 +767,7 @@ repositoryURL = "https://github.com/cbaker6/CareKit.git"; requirement = { kind = upToNextMajorVersion; - minimumVersion = "3.0.0-beta.9"; + minimumVersion = "3.0.0-beta.10"; }; }; /* End XCRemoteSwiftPackageReference section */ diff --git a/CareKitEssentials.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/CareKitEssentials.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved index 684d32a9..9c732d9b 100644 --- a/CareKitEssentials.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/CareKitEssentials.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -6,8 +6,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/cbaker6/CareKit.git", "state" : { - "revision" : "c6a222725b0dbc9fa734e4a023ffe25dbb198869", - "version" : "3.0.0-beta.9" + "revision" : "075d600052f168d49b542444577cf20033eb9666", + "version" : "3.0.0-beta.10" } }, { diff --git a/Package.resolved b/Package.resolved index 9357c844..5c155753 100644 --- a/Package.resolved +++ b/Package.resolved @@ -5,8 +5,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/cbaker6/CareKit.git", "state" : { - "revision" : "c6a222725b0dbc9fa734e4a023ffe25dbb198869", - "version" : "3.0.0-beta.9" + "revision" : "075d600052f168d49b542444577cf20033eb9666", + "version" : "3.0.0-beta.10" } }, { diff --git a/Package.swift b/Package.swift index a5761401..550e5498 100644 --- a/Package.swift +++ b/Package.swift @@ -12,7 +12,7 @@ let package = Package( ], dependencies: [ .package(url: "https://github.com/cbaker6/CareKit.git", - .upToNextMajor(from: "3.0.0-beta.9")) + .upToNextMajor(from: "3.0.0-beta.10")) ], targets: [ .target( From 382b8e1b74002b822f7f9d0279753bfa89dd5b59 Mon Sep 17 00:00:00 2001 From: Corey Baker Date: Fri, 5 Jul 2024 21:07:44 -0700 Subject: [PATCH 08/25] allow action to throw --- .../CareKitEssentials/Cards/Shared/CardViewModel.swift | 4 ++-- .../Cards/iOS/SliderLog/SliderLogButton.swift | 8 ++++++-- .../watchOS/DigitalCrown/DigitalCrownViewFooter.swift | 8 ++++++-- .../watchOS/DigitalCrown/DigitalCrownViewModel.swift | 2 +- 4 files changed, 15 insertions(+), 7 deletions(-) diff --git a/Sources/CareKitEssentials/Cards/Shared/CardViewModel.swift b/Sources/CareKitEssentials/Cards/Shared/CardViewModel.swift index 11b9b6e1..0f2a965e 100644 --- a/Sources/CareKitEssentials/Cards/Shared/CardViewModel.swift +++ b/Sources/CareKitEssentials/Cards/Shared/CardViewModel.swift @@ -64,7 +64,7 @@ open class CardViewModel: ObservableObject { public private(set) var detailsInformation: String? var initialValue: OCKOutcomeValue - var action: ((OCKOutcomeValue?) async -> OCKAnyOutcome)? + var action: ((OCKOutcomeValue?) async throws -> OCKAnyOutcome)? /// Create an instance with specified content for an event. The view will update when changes /// occur in the store. @@ -79,7 +79,7 @@ open class CardViewModel: ObservableObject { initialValue: OCKOutcomeValue = OCKOutcomeValue(0.0), detailsTitle: String? = nil, detailsInformation: String? = nil, - action: ((OCKOutcomeValue?) async -> OCKAnyOutcome)? = nil + action: ((OCKOutcomeValue?) async throws -> OCKAnyOutcome)? = nil ) { self.value = event.outcomeFirstValue ?? initialValue self.initialValue = initialValue diff --git a/Sources/CareKitEssentials/Cards/iOS/SliderLog/SliderLogButton.swift b/Sources/CareKitEssentials/Cards/iOS/SliderLog/SliderLogButton.swift index 50faf8d8..bd955f5d 100644 --- a/Sources/CareKitEssentials/Cards/iOS/SliderLog/SliderLogButton.swift +++ b/Sources/CareKitEssentials/Cards/iOS/SliderLog/SliderLogButton.swift @@ -80,8 +80,12 @@ struct SliderLogButton: CareKitEssentialView { } return } - let outcome = await action(newOutcomeValue) - viewModel.updateOutcome(outcome) + do { + let outcome = try await action(newOutcomeValue) + viewModel.updateOutcome(outcome) + } catch { + Logger.essentialView.error("Cannot update store with outcome value: \(error)") + } } } } diff --git a/Sources/CareKitEssentials/Cards/watchOS/DigitalCrown/DigitalCrownViewFooter.swift b/Sources/CareKitEssentials/Cards/watchOS/DigitalCrown/DigitalCrownViewFooter.swift index bd0cf8b1..6215d4cb 100644 --- a/Sources/CareKitEssentials/Cards/watchOS/DigitalCrown/DigitalCrownViewFooter.swift +++ b/Sources/CareKitEssentials/Cards/watchOS/DigitalCrown/DigitalCrownViewFooter.swift @@ -90,8 +90,12 @@ public struct DigitalCrownViewFooter: CareKitEssentialView { } return } - let outcome = await action(newOutcomeValue) - viewModel.updateOutcome(outcome) + do { + let outcome = try await action(newOutcomeValue) + viewModel.updateOutcome(outcome) + } catch { + Logger.essentialView.error("Cannot update store with outcome value: \(error)") + } } } } diff --git a/Sources/CareKitEssentials/Cards/watchOS/DigitalCrown/DigitalCrownViewModel.swift b/Sources/CareKitEssentials/Cards/watchOS/DigitalCrown/DigitalCrownViewModel.swift index b462ba1d..95d22a0a 100644 --- a/Sources/CareKitEssentials/Cards/watchOS/DigitalCrown/DigitalCrownViewModel.swift +++ b/Sources/CareKitEssentials/Cards/watchOS/DigitalCrown/DigitalCrownViewModel.swift @@ -70,7 +70,7 @@ open class DigitalCrownViewModel: CardViewModel { incrementValue: Double = 1, emojis: [String] = [], colorRatio: Double = 0.2, - action: ((OCKOutcomeValue?) async -> OCKAnyOutcome)? = nil) { + action: ((OCKOutcomeValue?) async throws -> OCKAnyOutcome)? = nil) { self.startValue = startValue self.incrementValue = incrementValue self.colorRatio = colorRatio From 770d36381c390a80e760c42b8ea135c5c9cf2efc Mon Sep 17 00:00:00 2001 From: Corey Baker Date: Fri, 5 Jul 2024 21:13:11 -0700 Subject: [PATCH 09/25] nit --- .../Cards/iOS/SliderLog/SliderLogTaskViewModel.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Sources/CareKitEssentials/Cards/iOS/SliderLog/SliderLogTaskViewModel.swift b/Sources/CareKitEssentials/Cards/iOS/SliderLog/SliderLogTaskViewModel.swift index 18635e57..30f3b903 100644 --- a/Sources/CareKitEssentials/Cards/iOS/SliderLog/SliderLogTaskViewModel.swift +++ b/Sources/CareKitEssentials/Cards/iOS/SliderLog/SliderLogTaskViewModel.swift @@ -47,7 +47,7 @@ open class SliderLogTaskViewModel: CardViewModel { initialValue: Double? = 0, range: ClosedRange, step: Double = 1, - action: ((OCKOutcomeValue?) async -> OCKAnyOutcome)? = nil + action: ((OCKOutcomeValue?) async throws -> OCKAnyOutcome)? = nil ) { if let values = event.outcomeValues { self.previousValues = values.compactMap { $0.doubleValue } From cca01f3c6b175eb8b46e342ae35eb297c78d4f83 Mon Sep 17 00:00:00 2001 From: Corey Baker Date: Fri, 5 Jul 2024 21:18:31 -0700 Subject: [PATCH 10/25] more nits --- Sources/CareKitEssentials/Cards/Shared/CustomLabelView.swift | 4 ++-- .../Cards/iOS/SliderLog/SliderLogTaskView.swift | 4 ++-- .../Cards/watchOS/DigitalCrown/DigitalCrownView.swift | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/Sources/CareKitEssentials/Cards/Shared/CustomLabelView.swift b/Sources/CareKitEssentials/Cards/Shared/CustomLabelView.swift index d57c70a6..eab4d8c0 100644 --- a/Sources/CareKitEssentials/Cards/Shared/CustomLabelView.swift +++ b/Sources/CareKitEssentials/Cards/Shared/CustomLabelView.swift @@ -101,7 +101,7 @@ public extension CustomLabelView { initialValue: OCKOutcomeValue = OCKOutcomeValue(0.0), detailsTitle: String? = nil, detailsInformation: String? = nil, - action: ((OCKOutcomeValue?) async -> OCKAnyOutcome)? = nil, + action: ((OCKOutcomeValue?) async throws -> OCKAnyOutcome)? = nil, @ViewBuilder header: () -> Header) { self.init( viewModel: .init( @@ -160,7 +160,7 @@ public extension CustomLabelView where Header == InformationHeaderView { initialValue: OCKOutcomeValue = OCKOutcomeValue(0.0), detailsTitle: String? = nil, detailsInformation: String? = nil, - action: ((OCKOutcomeValue?) async -> OCKAnyOutcome)? = nil) { + action: ((OCKOutcomeValue?) async throws -> OCKAnyOutcome)? = nil) { self.init(viewModel: .init(event: event.result, initialValue: initialValue, detailsTitle: detailsTitle, diff --git a/Sources/CareKitEssentials/Cards/iOS/SliderLog/SliderLogTaskView.swift b/Sources/CareKitEssentials/Cards/iOS/SliderLog/SliderLogTaskView.swift index 88677eee..34be277a 100644 --- a/Sources/CareKitEssentials/Cards/iOS/SliderLog/SliderLogTaskView.swift +++ b/Sources/CareKitEssentials/Cards/iOS/SliderLog/SliderLogTaskView.swift @@ -134,7 +134,7 @@ public extension SliderLogTaskView { detailsInformation: String? = nil, range: ClosedRange, step: Double = 1, - action: ((OCKOutcomeValue?) async -> OCKAnyOutcome)? = nil, + action: ((OCKOutcomeValue?) async throws -> OCKAnyOutcome)? = nil, @ViewBuilder header: () -> Header, @ViewBuilder slider: () -> Slider) { self.init(isHeaderPadded: false, @@ -309,7 +309,7 @@ public extension SliderLogTaskView where Header == InformationHeaderView, Slider initialValue: Double? = 0, range: ClosedRange, step: Double = 1, - action: ((OCKOutcomeValue?) async -> OCKAnyOutcome)? = nil, + action: ((OCKOutcomeValue?) async throws -> OCKAnyOutcome)? = nil, minimumImage: Image? = nil, maximumImage: Image? = nil, minimumDescription: String? = nil, diff --git a/Sources/CareKitEssentials/Cards/watchOS/DigitalCrown/DigitalCrownView.swift b/Sources/CareKitEssentials/Cards/watchOS/DigitalCrown/DigitalCrownView.swift index 0f8f59e8..58c9a9c3 100644 --- a/Sources/CareKitEssentials/Cards/watchOS/DigitalCrown/DigitalCrownView.swift +++ b/Sources/CareKitEssentials/Cards/watchOS/DigitalCrown/DigitalCrownView.swift @@ -134,7 +134,7 @@ public extension DigitalCrownView where Footer == DigitalCrownViewFooter { incrementValue: Double = 1, emojis: [String] = [], colorRatio: Double = 0.2, - action: ((OCKOutcomeValue?) async -> OCKAnyOutcome)? = nil, + action: ((OCKOutcomeValue?) async throws -> OCKAnyOutcome)? = nil, @ViewBuilder header: () -> Header ) { let viewModel = DigitalCrownViewModel( From 5bfe2a6a4ba1959b7c589ad9fd1ae76e01e7ecff Mon Sep 17 00:00:00 2001 From: Corey Baker Date: Fri, 5 Jul 2024 21:37:50 -0700 Subject: [PATCH 11/25] set initialValue before value in viewModel --- Sources/CareKitEssentials/Cards/Shared/CardViewModel.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Sources/CareKitEssentials/Cards/Shared/CardViewModel.swift b/Sources/CareKitEssentials/Cards/Shared/CardViewModel.swift index 0f2a965e..caebaa2e 100644 --- a/Sources/CareKitEssentials/Cards/Shared/CardViewModel.swift +++ b/Sources/CareKitEssentials/Cards/Shared/CardViewModel.swift @@ -81,8 +81,8 @@ open class CardViewModel: ObservableObject { detailsInformation: String? = nil, action: ((OCKOutcomeValue?) async throws -> OCKAnyOutcome)? = nil ) { - self.value = event.outcomeFirstValue ?? initialValue self.initialValue = initialValue + self.value = event.outcomeFirstValue ?? initialValue self.detailsTitle = detailsTitle self.detailsInformation = detailsInformation self.event = event From 1a2e7d6ca340238542c2a2671d41906e743c2679 Mon Sep 17 00:00:00 2001 From: Corey Baker Date: Fri, 5 Jul 2024 21:44:34 -0700 Subject: [PATCH 12/25] set initial value as double --- Sources/CareKitEssentials/Cards/Shared/CardViewModel.swift | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Sources/CareKitEssentials/Cards/Shared/CardViewModel.swift b/Sources/CareKitEssentials/Cards/Shared/CardViewModel.swift index caebaa2e..5c0520b2 100644 --- a/Sources/CareKitEssentials/Cards/Shared/CardViewModel.swift +++ b/Sources/CareKitEssentials/Cards/Shared/CardViewModel.swift @@ -82,6 +82,9 @@ open class CardViewModel: ObservableObject { action: ((OCKOutcomeValue?) async throws -> OCKAnyOutcome)? = nil ) { self.initialValue = initialValue + if let initialValueAsDouble = initialValue.doubleValue { + self.valueAsDouble = initialValueAsDouble + } self.value = event.outcomeFirstValue ?? initialValue self.detailsTitle = detailsTitle self.detailsInformation = detailsInformation From 39e653926faef54e56654b017ff43f8643a981cf Mon Sep 17 00:00:00 2001 From: Corey Baker Date: Fri, 5 Jul 2024 23:10:25 -0700 Subject: [PATCH 13/25] Add additional DigitalCrownView init --- .../DigitalCrown/DigitalCrownView.swift | 175 +++++++++++++----- .../DigitalCrown/DigitalCrownViewFooter.swift | 14 +- .../DigitalCrown/DigitalCrownViewHeader.swift | 30 +-- 3 files changed, 161 insertions(+), 58 deletions(-) diff --git a/Sources/CareKitEssentials/Cards/watchOS/DigitalCrown/DigitalCrownView.swift b/Sources/CareKitEssentials/Cards/watchOS/DigitalCrown/DigitalCrownView.swift index 58c9a9c3..6600bcce 100644 --- a/Sources/CareKitEssentials/Cards/watchOS/DigitalCrown/DigitalCrownView.swift +++ b/Sources/CareKitEssentials/Cards/watchOS/DigitalCrown/DigitalCrownView.swift @@ -42,10 +42,12 @@ public struct DigitalCrownView: View { } } - init(isHeaderPadded: Bool, - isFooterPadded: Bool, - @ViewBuilder header: () -> Header, - @ViewBuilder footer: () -> Footer) { + init( + isHeaderPadded: Bool, + isFooterPadded: Bool, + @ViewBuilder header: () -> Header, + @ViewBuilder footer: () -> Footer + ) { self.isHeaderPadded = isHeaderPadded self.isFooterPadded = isFooterPadded self.header = header() @@ -61,13 +63,17 @@ public extension DigitalCrownView { /// - Parameter instructions: Instructions text to display under the header. /// - Parameter header: Header to inject at the top of the card. Specified content will be stacked vertically. /// - Parameter footer: View to inject under the instructions. Specified content will be stacked vertically. - init(instructions: Text? = nil, - @ViewBuilder header: () -> Header, - @ViewBuilder footer: () -> Footer) { - self.init(isHeaderPadded: false, - isFooterPadded: false, - header: header, - footer: footer) + init( + instructions: Text? = nil, + @ViewBuilder header: () -> Header, + @ViewBuilder footer: () -> Footer + ) { + self.init( + isHeaderPadded: false, + isFooterPadded: false, + header: header, + footer: footer + ) } } @@ -78,18 +84,24 @@ public extension DigitalCrownView where Header == DigitalCrownViewHeader { /// - Parameter detail: Detail text to display in the header. /// - Parameter event: The event to display details for when the info button is tapped. /// - Parameter footer: View to inject under the instructions. Specified content will be stacked vertically. - init(title: Text, - detail: Text? = nil, - event: OCKAnyEvent? = nil, - @ViewBuilder footer: () -> Footer) { + init( + title: Text, + detail: Text? = nil, + event: OCKAnyEvent? = nil, + @ViewBuilder footer: () -> Footer + ) { self.init( isHeaderPadded: true, isFooterPadded: false, - header: { DigitalCrownViewHeader(title: title, - detail: detail, - event: event) + header: { + DigitalCrownViewHeader( + title: title, + detail: detail, + event: event + ) }, - footer: footer) + footer: footer + ) } } @@ -98,14 +110,18 @@ public extension DigitalCrownView where Footer == DigitalCrownViewFooter { /// Create an instance. /// - Parameter viewModel: The view model used to populate the view contents. /// - Parameter header: Header to inject at the top of the card. Specified content will be stacked vertically. - init(viewModel: DigitalCrownViewModel, - @ViewBuilder header: () -> Header) { - self.init(isHeaderPadded: false, - isFooterPadded: true, - header: header, - footer: { - DigitalCrownViewFooter(viewModel: viewModel) - }) + init( + viewModel: DigitalCrownViewModel, + @ViewBuilder header: () -> Header + ) { + self.init( + isHeaderPadded: false, + isFooterPadded: true, + header: header, + footer: { + DigitalCrownViewFooter(viewModel: viewModel) + } + ) } /// Create a view using data from an event. @@ -158,28 +174,103 @@ public extension DigitalCrownView where Footer == DigitalCrownViewFooter { } public extension DigitalCrownView where Header == DigitalCrownViewHeader, Footer == DigitalCrownViewFooter { - init(title: Text, - detail: Text? = nil, - viewModel: DigitalCrownViewModel) { - self.init(isHeaderPadded: true, - isFooterPadded: true, - header: { DigitalCrownViewHeader(title: title, - detail: detail, - event: viewModel.event) }, - footer: { DigitalCrownViewFooter(viewModel: viewModel) }) + init( + title: Text, + detail: Text? = nil, + viewModel: DigitalCrownViewModel + ) { + self.init( + isHeaderPadded: true, + isFooterPadded: true, + header: { + DigitalCrownViewHeader( + title: title, + detail: detail, + event: viewModel.event + ) + }, + footer: { + DigitalCrownViewFooter(viewModel: viewModel) + } + ) + } + + /// Create a view using data from an event. + /// + /// This view displays custom label card with title, detail, and/or image. + /// + /// - Parameters: + /// - event: The data that appears in the view. + /// - detailsTitle: An optional title for the event. + /// - detailsInformation: An optional detailed information string for the event. + /// - initialValue: The initial value shown for the digital crown. + /// - startValue: The minimum possible value. + /// - endValue: The maximum possible value. + /// - incrementValue: The step amount. + /// - emojis: An array of emoji's to show on the screen. + /// - colorRatio: The ratio effect on the color gradient. + /// - action: The action to perform when the log button is tapped. + init( + event: CareStoreFetchedResult, + detailsTitle: String? = nil, + detailsInformation: String? = nil, + initialValue: Double? = nil, + startValue: Double = 0, + endValue: Double? = nil, + incrementValue: Double = 1, + emojis: [String] = [], + colorRatio: Double = 0.2, + action: ((OCKOutcomeValue?) async throws -> OCKAnyOutcome)? = nil + ) { + let event = event.result + let viewModel = DigitalCrownViewModel( + event: event, + detailsTitle: detailsTitle, + detailsInformation: detailsInformation, + initialValue: initialValue, + startValue: startValue, + endValue: endValue, + incrementValue: incrementValue, + emojis: emojis, + colorRatio: colorRatio, + action: action + ) + let title = detailsTitle != nil ? + Text(detailsTitle!) : Text(event.title) + let detail = detailsInformation != nil ? + Text(detailsInformation!) : Text(event.detail ?? "") + self.init( + isHeaderPadded: true, + isFooterPadded: true, + header: { + DigitalCrownViewHeader( + title: title, + detail: detail, + event: event + ) + }, + footer: { + DigitalCrownViewFooter( + viewModel: viewModel + ) + } + ) } } struct DigitalCrownView_Previews: PreviewProvider { static let emojis = ["😄", "🙂", "😐", "😕", "😟", "☚ī¸", "😞", "😓", "đŸ˜Ĩ", "😰", "đŸ¤¯"] - static let task = Utility.createNauseaTask() static var previews: some View { if let event = try? Utility.createNauseaEvent() { - DigitalCrownView(title: Text(task.title!), - detail: Text(task.instructions!), - viewModel: .init(event: event, - emojis: emojis)) - .environment(\.careStore, Utility.createPreviewStore()) + DigitalCrownView( + title: Text(event.task.title ?? ""), + detail: Text(event.task.instructions ?? ""), + viewModel: .init( + event: event, + emojis: emojis + ) + ) + .environment(\.careStore, Utility.createPreviewStore()) } } } diff --git a/Sources/CareKitEssentials/Cards/watchOS/DigitalCrown/DigitalCrownViewFooter.swift b/Sources/CareKitEssentials/Cards/watchOS/DigitalCrown/DigitalCrownViewFooter.swift index 6215d4cb..266d93e0 100644 --- a/Sources/CareKitEssentials/Cards/watchOS/DigitalCrown/DigitalCrownViewFooter.swift +++ b/Sources/CareKitEssentials/Cards/watchOS/DigitalCrown/DigitalCrownViewFooter.swift @@ -48,12 +48,16 @@ public struct DigitalCrownViewFooter: CareKitEssentialView { } Text("\(String(format: "%g", round(viewModel.valueAsDouble)))") .focusable(true) - .digitalCrownRotation($viewModel.valueAsDouble, - from: viewModel.startValue, - through: viewModel.endValue, - by: viewModel.incrementValue) + .digitalCrownRotation( + $viewModel.valueAsDouble, + from: viewModel.startValue, + through: viewModel.endValue, + by: viewModel.incrementValue + ) .font(.largeTitle) - .foregroundColor(viewModel.getStoplightColor(for: viewModel.valueAsDouble)) + .foregroundColor( + viewModel.getStoplightColor(for: viewModel.valueAsDouble) + ) } Button(action: { updateValue() diff --git a/Sources/CareKitEssentials/Cards/watchOS/DigitalCrown/DigitalCrownViewHeader.swift b/Sources/CareKitEssentials/Cards/watchOS/DigitalCrown/DigitalCrownViewHeader.swift index 49b75459..68f916ef 100644 --- a/Sources/CareKitEssentials/Cards/watchOS/DigitalCrown/DigitalCrownViewHeader.swift +++ b/Sources/CareKitEssentials/Cards/watchOS/DigitalCrown/DigitalCrownViewHeader.swift @@ -22,27 +22,35 @@ public struct DigitalCrownViewHeader: View { let event: OCKAnyEvent? public var body: some View { - VStack(alignment: .leading, - spacing: style.dimension.directionalInsets1.top) { + VStack( + alignment: .leading, + spacing: style.dimension.directionalInsets1.top + ) { if let event = event { - InformationHeaderView(title: title, - information: detail, - event: event) + InformationHeaderView( + title: title, + information: detail, + event: event + ) } else { - HeaderView(title: title, detail: detail) + HeaderView( + title: title, + detail: detail + ) } } } } struct DigitalCrownViewHeader_Previews: PreviewProvider { - static let task = Utility.createNauseaTask() static var previews: some View { if let event = try? Utility.createNauseaEvent() { - DigitalCrownViewHeader(title: Text(task.title!), - detail: Text(task.instructions!), - event: event) - .environment(\.careStore, Utility.createPreviewStore()) + DigitalCrownViewHeader( + title: Text(event.task.title ?? ""), + detail: Text(event.task.instructions ?? ""), + event: event + ) + .environment(\.careStore, Utility.createPreviewStore()) } } } From f922a0f7591120d3ebafc9b38b0edbef96c3907b Mon Sep 17 00:00:00 2001 From: Corey Date: Sat, 6 Jul 2024 06:39:59 -0700 Subject: [PATCH 14/25] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 2a7afa5e..0812bdbd 100644 --- a/README.md +++ b/README.md @@ -7,7 +7,7 @@ ![Codecov](https://codecov.io/gh/netreconlab/CareKitEssentials/branches/main/graph/badge.svg) [![License](https://img.shields.io/badge/license-Apache%202.0-blue.svg)](https://github.com/netreconlab/ParseCareKit/#license) -Provides essential cards, views, models, protocols, and extentions to expedite building [CareKit](https://github.com/carekit-apple/CareKit) based applications. +Provides essential cards, views, models, protocols, and extensions to expedite building [CareKit](https://github.com/carekit-apple/CareKit) based applications. ## Entensions A number of public extensions are available to make using CareKit easier. All of the extensions can be found in the [Extensions](https://github.com/netreconlab/CareKitEssentials/tree/main/Sources/CareKitEssentials/Extensions) folder. From 6dd76935b0d82752a1f714043cbaa7091783708c Mon Sep 17 00:00:00 2001 From: Corey Date: Sat, 6 Jul 2024 06:44:46 -0700 Subject: [PATCH 15/25] add badges --- README.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 0812bdbd..810ac74e 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,7 @@ # CareKitEssentials - +"" +[![Documentation](https://img.shields.io/badge/read_-docs-2196f3.svg)](https://swiftpackageindex.com/netreconlab/CareKitEssentials/documentation/) +[![Tuturiol](https://img.shields.io/badge/read_-tuturials-2196f3.svg)](https://netreconlab.github.io/CareKitEssentials/release/tutorials/carekitessentials/) [![](https://img.shields.io/endpoint?url=https%3A%2F%2Fswiftpackageindex.com%2Fapi%2Fpackages%2Fnetreconlab%2FCareKitEssentials%2Fbadge%3Ftype%3Dswift-versions)](https://swiftpackageindex.com/netreconlab/CareKitEssentials) [![](https://img.shields.io/endpoint?url=https%3A%2F%2Fswiftpackageindex.com%2Fapi%2Fpackages%2Fnetreconlab%2FCareKitEssentials%2Fbadge%3Ftype%3Dplatforms)](https://swiftpackageindex.com/netreconlab/CareKitEssentials) ![Xcode 13.2+](https://img.shields.io/badge/xcode-13.2%2B-blue.svg) From 605db2bc08b2dff2a8cf4422f505a2ca25e647b0 Mon Sep 17 00:00:00 2001 From: Corey Date: Sat, 6 Jul 2024 06:45:01 -0700 Subject: [PATCH 16/25] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 810ac74e..b2aaab71 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,5 @@ # CareKitEssentials -"" + [![Documentation](https://img.shields.io/badge/read_-docs-2196f3.svg)](https://swiftpackageindex.com/netreconlab/CareKitEssentials/documentation/) [![Tuturiol](https://img.shields.io/badge/read_-tuturials-2196f3.svg)](https://netreconlab.github.io/CareKitEssentials/release/tutorials/carekitessentials/) [![](https://img.shields.io/endpoint?url=https%3A%2F%2Fswiftpackageindex.com%2Fapi%2Fpackages%2Fnetreconlab%2FCareKitEssentials%2Fbadge%3Ftype%3Dswift-versions)](https://swiftpackageindex.com/netreconlab/CareKitEssentials) From 1777d8968d95d11a18bdf341fd3cd6b831627ddd Mon Sep 17 00:00:00 2001 From: Corey Date: Sat, 6 Jul 2024 06:47:35 -0700 Subject: [PATCH 17/25] Create release.yml --- .github/workflows/release.yml | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) create mode 100644 .github/workflows/release.yml diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 00000000..41f25760 --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,17 @@ +name: release +on: + release: + types: [published] +env: + CI_XCODE: '/Applications/Xcode_15.4.app/Contents/Developer' + +jobs: + docs: + runs-on: macos-14 + steps: + - uses: actions/checkout@v4 + - name: Build and Deploy Docs + run: set -o pipefail && env NSUnbufferedIO=YES Scripts/update-gh-pages-documentation-site + env: + CURRENT_BRANCH_NAME: release + DEVELOPER_DIR: ${{ env.CI_XCODE }} From 202e025ac197982e4b4fd7fe1291088b53c9dbdb Mon Sep 17 00:00:00 2001 From: Corey Date: Sat, 6 Jul 2024 06:56:45 -0700 Subject: [PATCH 18/25] Create generate-documentation --- Scripts/generate-documentation | 140 +++++++++++++++++++++++++++++++++ 1 file changed, 140 insertions(+) create mode 100644 Scripts/generate-documentation diff --git a/Scripts/generate-documentation b/Scripts/generate-documentation new file mode 100644 index 00000000..7182217b --- /dev/null +++ b/Scripts/generate-documentation @@ -0,0 +1,140 @@ +#!/bin/bash +# +# Copyright (c) 2022, Apple Inc. All rights reserved. +# +# Redistribution and use in source and binary forms, with or without modification, +# are permitted provided that the following conditions are met: +# +# 1. Redistributions of source code must retain the above copyright notice, this +# list of conditions and the following disclaimer. +# +# 2. Redistributions in binary form must reproduce the above copyright notice, +# this list of conditions and the following disclaimer in the documentation and/or +# other materials provided with the distribution. +# +# 3. Neither the name of the copyright holder(s) nor the names of any contributors +# may be used to endorse or promote products derived from this software without +# specific prior written permission. No license is granted to the trademarks of +# the copyright holders even if such marks are included in this software. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE +# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +# A `realpath` alternative using the default C implementation. +filepath() { + [[ $1 = /* ]] && echo "$1" || echo "$PWD/${1#./}" +} + +# First get the absolute path to this file so we can get the absolute file path to the Swift-DocC root source dir. +PROJECT_ROOT="$(dirname $(dirname $(filepath $0)))" +DOCS_DIR="$PROJECT_ROOT/.build/swift-docc" +SGFS_DIR="$DOCS_DIR/symbol-graph-files" +TEMP_WORKSPACE_DIR="$DOCS_DIR/temporary-workspace-holding-directory" + +DOCC_CMD=convert +OUTPUT_PATH="$DOCS_DIR/CareKitEssentials.doccarchive" +HOSTING_BASE_PATH="" +PUBLISH="NO" + +# Process command line arguments +OUTPUT_PATH_PROCESSED=0 +HOSTING_BASE_PATH_PROCESSED=0 +while test $# -gt 0; do + case "$1" in + --help) + echo "Usage: $(basename $0) [] [] [--preview] [--publish] [--help]" + echo + echo "Builds CareKitEssentials and generates or previews the Swift-DocC documentation." + echo + echo " --preview: Starts a preview server after generating documentation." + echo " --publish: Configures the documentation build for publishing on GitHub pages." + echo + exit 0 + ;; + --preview) + DOCC_CMD=preview + shift + ;; + --publish) + PUBLISH="YES" + shift + ;; + *) + if [ ${OUTPUT_PATH_PROCESSED} -eq 0 ]; then + OUTPUT_PATH="$1" + OUTPUT_PATH_PROCESSED=1 + elif [ ${HOSTING_BASE_PATH_PROCESSED} -eq 0 ]; then + HOSTING_BASE_PATH="$1" + HOSTING_BASE_PATH_PROCESSED=1 + else + echo "Unrecognised argument \"$1\"" + exit 1 + fi + ;; + esac + shift +done + +if [ "$PUBLISH" = "YES" ]; then + if [ ${HOSTING_BASE_PATH_PROCESSED} -eq 0 ]; then + echo "A hosting base path must be provided if the '--publish' flag is passed." + echo "See '--help' for details." + exit 1 + fi +fi + +# Create the output directory for the symbol graphs if needed. +mkdir -p "$DOCS_DIR" +mkdir -p "$SGFS_DIR" +rm -f $SGFS_DIR/*.* + +cd "$PROJECT_ROOT" + +# Temporarily move the Xcode workspace aside so that xcodebuild uses the Swift package directly +mkdir "$TEMP_WORKSPACE_DIR" +mv CareKitEssentials.xcodeproj "$TEMP_WORKSPACE_DIR/CareKitEssentials.xcodeproj" + +xcodebuild clean build -scheme CareKitEssentials \ + -destination generic/platform=iOS \ + OTHER_SWIFT_FLAGS="-emit-symbol-graph -emit-symbol-graph-dir '$SGFS_DIR'" | xcpretty + +mv "$TEMP_WORKSPACE_DIR/CareKitEssentials.xcodeproj" ./CareKitEssentials.xcodeproj +rm -r "$TEMP_WORKSPACE_DIR" + +# Pretty print DocC JSON output so that it can be consistently diffed between commits +export DOCC_JSON_PRETTYPRINT="YES" + +# By default pass the --index flag so we produce a full DocC archive. +EXTRA_DOCC_FLAGS="--index" + +# If building for publishing, don't pass the --index flag but pass additional flags for +# static hosting configuration. +if [ "$PUBLISH" = "YES" ]; then + EXTRA_DOCC_FLAGS="--transform-for-static-hosting --hosting-base-path CareKitEssentials/$HOSTING_BASE_PATH" +fi + +# Handle the case where a DocC catalog does not exist in the CareKitEssentials repo +if [ -d Sources/CareKitEssentials/Documentation.docc ]; then + # The DocC catalog exists, so pass it to the docc invocation. + DOCC_CMD="$DOCC_CMD Sources/CareKitEssentials/Documentation.docc" +fi + +xcrun docc $DOCC_CMD \ + --additional-symbol-graph-dir "$SGFS_DIR" \ + --output-path "$OUTPUT_PATH" $EXTRA_DOCC_FLAGS \ + --fallback-display-name CareKitEssentials \ + --fallback-bundle-identifier edu.usc.netreconlab.CareKitEssentials \ + --fallback-bundle-version 1.0.0 + +if [[ "$DOCC_CMD" == "convert"* ]]; then + echo + echo "Generated DocC archive at: $OUTPUT_PATH" +fi From 0f83e0e93eb9200da64c2235e428778e181094a3 Mon Sep 17 00:00:00 2001 From: Corey Date: Sat, 6 Jul 2024 06:57:54 -0700 Subject: [PATCH 19/25] Create update-gh-pages-documentation-site --- Scripts/update-gh-pages-documentation-site | 80 ++++++++++++++++++++++ 1 file changed, 80 insertions(+) create mode 100644 Scripts/update-gh-pages-documentation-site diff --git a/Scripts/update-gh-pages-documentation-site b/Scripts/update-gh-pages-documentation-site new file mode 100644 index 00000000..3dc17a77 --- /dev/null +++ b/Scripts/update-gh-pages-documentation-site @@ -0,0 +1,80 @@ +#!/bin/bash +# +# Copyright (c) 2022, Apple Inc. All rights reserved. +# +# Redistribution and use in source and binary forms, with or without modification, +# are permitted provided that the following conditions are met: +# +# 1. Redistributions of source code must retain the above copyright notice, this +# list of conditions and the following disclaimer. +# +# 2. Redistributions in binary form must reproduce the above copyright notice, +# this list of conditions and the following disclaimer in the documentation and/or +# other materials provided with the distribution. +# +# 3. Neither the name of the copyright holder(s) nor the names of any contributors +# may be used to endorse or promote products derived from this software without +# specific prior written permission. No license is granted to the trademarks of +# the copyright holders even if such marks are included in this software. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE +# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +set -eu + +# A `realpath` alternative using the default C implementation. +filepath() { + [[ $1 = /* ]] && echo "$1" || echo "$PWD/${1#./}" +} + +PROJECT_ROOT="$(dirname $(dirname $(filepath $0)))" + +# Set current directory to the repository root +cd "$PROJECT_ROOT" + +# Use git worktree to checkout the gh-pages branch of this repository in a gh-pages sub-directory +git fetch +git worktree add --checkout gh-pages origin/gh-pages + +# Get the name of the current branch to use as the subdirectory for the deployment +if [ -z ${CURRENT_BRANCH_NAME+x} ]; then + CURRENT_BRANCH_NAME=`git rev-parse --abbrev-ref HEAD` +fi + +# Replace any forward slashes in the current branch name with dashes +DEPLOYMENT_SUBDIRECTORY=${CURRENT_BRANCH_NAME//\//-} + +# Create a subdirectory for the current branch name if it doesn't exist +mkdir -p "./gh-pages/$DEPLOYMENT_SUBDIRECTORY" + +# Generate documentation output it +# to the /docs subdirectory in the gh-pages worktree directory. +./Scripts/generate-documentation "$PROJECT_ROOT/gh-pages/$DEPLOYMENT_SUBDIRECTORY" "$DEPLOYMENT_SUBDIRECTORY" --publish + +# Save the current commit we've just built documentation from in a variable +CURRENT_COMMIT_HASH=`git rev-parse --short HEAD` + +# Commit and push our changes to the gh-pages branch +cd gh-pages +git add "$DEPLOYMENT_SUBDIRECTORY" + +if [ -n "$(git status --porcelain)" ]; then + echo "Documentation changes found. Commiting the changes to the 'gh-pages' branch and pushing to origin." + git commit -m "Update documentation to $CURRENT_COMMIT_HASH on '$CURRENT_BRANCH_NAME'" + git push origin HEAD:gh-pages +else + # No changes found, nothing to commit. + echo "No documentation changes found." +fi + +# Delete the git worktree we created +cd .. +git worktree remove gh-pages From ae0ea73d3e841e0036091032fe34df880cdd4a95 Mon Sep 17 00:00:00 2001 From: Corey Baker Date: Sat, 6 Jul 2024 07:03:03 -0700 Subject: [PATCH 20/25] nits --- .../DigitalCrown/DigitalCrownViewModel.swift | 44 ++++++++++--------- 1 file changed, 24 insertions(+), 20 deletions(-) diff --git a/Sources/CareKitEssentials/Cards/watchOS/DigitalCrown/DigitalCrownViewModel.swift b/Sources/CareKitEssentials/Cards/watchOS/DigitalCrown/DigitalCrownViewModel.swift index 95d22a0a..947ead3e 100644 --- a/Sources/CareKitEssentials/Cards/watchOS/DigitalCrown/DigitalCrownViewModel.swift +++ b/Sources/CareKitEssentials/Cards/watchOS/DigitalCrown/DigitalCrownViewModel.swift @@ -61,16 +61,18 @@ open class DigitalCrownViewModel: CardViewModel { /// - emojis: An array of emoji's to show on the screen. /// - colorRatio: The ratio effect on the color gradient. /// - action: The action to perform when the log button is tapped. - public init(event: OCKAnyEvent, - detailsTitle: String? = nil, - detailsInformation: String? = nil, - initialValue: Double? = nil, - startValue: Double = 0, - endValue: Double? = nil, - incrementValue: Double = 1, - emojis: [String] = [], - colorRatio: Double = 0.2, - action: ((OCKOutcomeValue?) async throws -> OCKAnyOutcome)? = nil) { + public init( + event: OCKAnyEvent, + detailsTitle: String? = nil, + detailsInformation: String? = nil, + initialValue: Double? = nil, + startValue: Double = 0, + endValue: Double? = nil, + incrementValue: Double = 1, + emojis: [String] = [], + colorRatio: Double = 0.2, + action: ((OCKOutcomeValue?) async throws -> OCKAnyOutcome)? = nil + ) { self.startValue = startValue self.incrementValue = incrementValue self.colorRatio = colorRatio @@ -83,18 +85,20 @@ open class DigitalCrownViewModel: CardViewModel { self.endValue = 0 } if let initialValue = initialValue { - super.init(event: event, - initialValue: OCKOutcomeValue(initialValue), - detailsTitle: detailsTitle, - detailsInformation: detailsInformation, - action: action + super.init( + event: event, + initialValue: OCKOutcomeValue(initialValue), + detailsTitle: detailsTitle, + detailsInformation: detailsInformation, + action: action ) } else { - super.init(event: event, - initialValue: OCKOutcomeValue(startValue), - detailsTitle: detailsTitle, - detailsInformation: detailsInformation, - action: action + super.init( + event: event, + initialValue: OCKOutcomeValue(startValue), + detailsTitle: detailsTitle, + detailsInformation: detailsInformation, + action: action ) } } From a5d54a9140deb7a20ff2aa8a0631260cbcdd2f9c Mon Sep 17 00:00:00 2001 From: Corey Baker Date: Sat, 6 Jul 2024 07:09:53 -0700 Subject: [PATCH 21/25] add additional OS's --- Sources/CareKitEssentials/Models/OSValue.swift | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/Sources/CareKitEssentials/Models/OSValue.swift b/Sources/CareKitEssentials/Models/OSValue.swift index 715c44da..5b9dd973 100644 --- a/Sources/CareKitEssentials/Models/OSValue.swift +++ b/Sources/CareKitEssentials/Models/OSValue.swift @@ -10,7 +10,7 @@ import Foundation // swiftlint:disable:next type_name enum OS: String { - case iOS, watchOS, macOS + case iOS, watchOS, macOS, visionOS } @propertyWrapper @@ -26,6 +26,8 @@ struct OSValue { return values[.watchOS] ?? defaultValue #elseif os(macOS) return values[.macOS] ?? defaultValue + #elseif os(visionOS) + return values[.visionOS] ?? defaultValue #else return defaultValue #endif From a83b1228714f3208a5ca60951a5c3338f0838de0 Mon Sep 17 00:00:00 2001 From: Corey Baker Date: Sat, 6 Jul 2024 14:04:21 -0700 Subject: [PATCH 22/25] fix digital crown initial value --- .../SliderLog/SliderLogTaskViewModel.swift | 8 ++++---- .../DigitalCrown/DigitalCrownView.swift | 19 +++++++++++++------ .../DigitalCrown/DigitalCrownViewFooter.swift | 13 +++++++++---- .../DigitalCrown/DigitalCrownViewModel.swift | 10 +++++++++- 4 files changed, 35 insertions(+), 15 deletions(-) diff --git a/Sources/CareKitEssentials/Cards/iOS/SliderLog/SliderLogTaskViewModel.swift b/Sources/CareKitEssentials/Cards/iOS/SliderLog/SliderLogTaskViewModel.swift index 30f3b903..7cc534d0 100644 --- a/Sources/CareKitEssentials/Cards/iOS/SliderLog/SliderLogTaskViewModel.swift +++ b/Sources/CareKitEssentials/Cards/iOS/SliderLog/SliderLogTaskViewModel.swift @@ -44,7 +44,7 @@ open class SliderLogTaskViewModel: CardViewModel { event: OCKAnyEvent, detailsTitle: String? = nil, detailsInformation: String? = nil, - initialValue: Double? = 0, + initialValue: Double? = nil, range: ClosedRange, step: Double = 1, action: ((OCKOutcomeValue?) async throws -> OCKAnyOutcome)? = nil @@ -60,11 +60,11 @@ open class SliderLogTaskViewModel: CardViewModel { currentInitialDoubleValue = initialValue } var currentInitialOutcome = OCKOutcomeValue(currentInitialDoubleValue) - if let initialOutcome = event.outcomeFirstValue, - let initialOutcomeDouble = initialOutcome.doubleValue { + if let latestOutcomeValue = event.outcomeFirstValue, + let initialOutcomeDouble = latestOutcomeValue.doubleValue { if initialOutcomeDouble != currentInitialDoubleValue { isActive = false - currentInitialOutcome = initialOutcome + currentInitialOutcome = latestOutcomeValue } } super.init( diff --git a/Sources/CareKitEssentials/Cards/watchOS/DigitalCrown/DigitalCrownView.swift b/Sources/CareKitEssentials/Cards/watchOS/DigitalCrown/DigitalCrownView.swift index 6600bcce..96ba0cb6 100644 --- a/Sources/CareKitEssentials/Cards/watchOS/DigitalCrown/DigitalCrownView.swift +++ b/Sources/CareKitEssentials/Cards/watchOS/DigitalCrown/DigitalCrownView.swift @@ -27,7 +27,10 @@ public struct DigitalCrownView: View { public var body: some View { CardView { - VStack(alignment: .leading, spacing: style.dimension.directionalInsets1.top) { + VStack( + alignment: .leading, + spacing: style.dimension.directionalInsets1.top + ) { if !(header is EmptyView) { VStack { header @@ -36,8 +39,12 @@ public struct DigitalCrownView: View { .if(isHeaderPadded) { $0.padding([.horizontal, .top]) } } - VStack { footer } - .if(isHeaderPadded) { $0.padding([.horizontal, .bottom]) } + VStack { + footer + } + .if(isHeaderPadded) { + $0.padding([.horizontal, .bottom]) + } } } } @@ -93,7 +100,7 @@ public extension DigitalCrownView where Header == DigitalCrownViewHeader { self.init( isHeaderPadded: true, isFooterPadded: false, - header: { + header: { DigitalCrownViewHeader( title: title, detail: detail, @@ -235,9 +242,9 @@ public extension DigitalCrownView where Header == DigitalCrownViewHeader, Footer colorRatio: colorRatio, action: action ) - let title = detailsTitle != nil ? + let title = detailsTitle != nil ? Text(detailsTitle!) : Text(event.title) - let detail = detailsInformation != nil ? + let detail = detailsInformation != nil ? Text(detailsInformation!) : Text(event.detail ?? "") self.init( isHeaderPadded: true, diff --git a/Sources/CareKitEssentials/Cards/watchOS/DigitalCrown/DigitalCrownViewFooter.swift b/Sources/CareKitEssentials/Cards/watchOS/DigitalCrown/DigitalCrownViewFooter.swift index 266d93e0..a684e06e 100644 --- a/Sources/CareKitEssentials/Cards/watchOS/DigitalCrown/DigitalCrownViewFooter.swift +++ b/Sources/CareKitEssentials/Cards/watchOS/DigitalCrown/DigitalCrownViewFooter.swift @@ -19,10 +19,15 @@ public struct DigitalCrownViewFooter: CareKitEssentialView { @Environment(\.sizeCategory) private var sizeCategory @StateObject var viewModel: DigitalCrownViewModel - @OSValue(values: [.watchOS: 8], - defaultValue: 14) private var padding - @OSValue(values: [.watchOS: .system(size: 13)], - defaultValue: .caption) private var font + @OSValue( + values: [.watchOS: 8], + defaultValue: 14 + ) private var padding + + @OSValue( + values: [.watchOS: .system(size: 13)], + defaultValue: .caption + ) private var font private var content: some View { Group { diff --git a/Sources/CareKitEssentials/Cards/watchOS/DigitalCrown/DigitalCrownViewModel.swift b/Sources/CareKitEssentials/Cards/watchOS/DigitalCrown/DigitalCrownViewModel.swift index 947ead3e..3608b56f 100644 --- a/Sources/CareKitEssentials/Cards/watchOS/DigitalCrown/DigitalCrownViewModel.swift +++ b/Sources/CareKitEssentials/Cards/watchOS/DigitalCrown/DigitalCrownViewModel.swift @@ -85,9 +85,17 @@ open class DigitalCrownViewModel: CardViewModel { self.endValue = 0 } if let initialValue = initialValue { + + var currentInitialOutcome = OCKOutcomeValue(initialValue) + if let latestOutcomeValue = event.outcomeFirstValue, + let initialOutcomeDouble = latestOutcomeValue.doubleValue { + if initialOutcomeDouble != initialValue { + currentInitialOutcome = latestOutcomeValue + } + } super.init( event: event, - initialValue: OCKOutcomeValue(initialValue), + initialValue: currentInitialOutcome, detailsTitle: detailsTitle, detailsInformation: detailsInformation, action: action From 1424d1b568f90420eb811711fc4cdb4d6a3116e5 Mon Sep 17 00:00:00 2001 From: Corey Baker Date: Sat, 6 Jul 2024 14:09:00 -0700 Subject: [PATCH 23/25] make scripts executable --- Scripts/generate-documentation | 0 Scripts/update-gh-pages-documentation-site | 0 2 files changed, 0 insertions(+), 0 deletions(-) mode change 100644 => 100755 Scripts/generate-documentation mode change 100644 => 100755 Scripts/update-gh-pages-documentation-site diff --git a/Scripts/generate-documentation b/Scripts/generate-documentation old mode 100644 new mode 100755 diff --git a/Scripts/update-gh-pages-documentation-site b/Scripts/update-gh-pages-documentation-site old mode 100644 new mode 100755 From 8a74903036560a925211ef27d8616dbe8c6bd1ee Mon Sep 17 00:00:00 2001 From: Corey Baker Date: Sat, 6 Jul 2024 14:34:56 -0700 Subject: [PATCH 24/25] more fixes to digital crown --- .../Cards/watchOS/DigitalCrown/DigitalCrownViewModel.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Sources/CareKitEssentials/Cards/watchOS/DigitalCrown/DigitalCrownViewModel.swift b/Sources/CareKitEssentials/Cards/watchOS/DigitalCrown/DigitalCrownViewModel.swift index 3608b56f..94ed8db5 100644 --- a/Sources/CareKitEssentials/Cards/watchOS/DigitalCrown/DigitalCrownViewModel.swift +++ b/Sources/CareKitEssentials/Cards/watchOS/DigitalCrown/DigitalCrownViewModel.swift @@ -89,7 +89,7 @@ open class DigitalCrownViewModel: CardViewModel { var currentInitialOutcome = OCKOutcomeValue(initialValue) if let latestOutcomeValue = event.outcomeFirstValue, let initialOutcomeDouble = latestOutcomeValue.doubleValue { - if initialOutcomeDouble != initialValue { + if initialOutcomeDouble == initialValue { currentInitialOutcome = latestOutcomeValue } } From 01f82f2797afbb53b15f92f92815532eb971b8f2 Mon Sep 17 00:00:00 2001 From: Corey Baker Date: Sat, 6 Jul 2024 14:46:54 -0700 Subject: [PATCH 25/25] compare integers for disabling --- .../Cards/watchOS/DigitalCrown/DigitalCrownView.swift | 2 +- .../Cards/watchOS/DigitalCrown/DigitalCrownViewModel.swift | 6 +++++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/Sources/CareKitEssentials/Cards/watchOS/DigitalCrown/DigitalCrownView.swift b/Sources/CareKitEssentials/Cards/watchOS/DigitalCrown/DigitalCrownView.swift index 96ba0cb6..a70ffe82 100644 --- a/Sources/CareKitEssentials/Cards/watchOS/DigitalCrown/DigitalCrownView.swift +++ b/Sources/CareKitEssentials/Cards/watchOS/DigitalCrown/DigitalCrownView.swift @@ -28,7 +28,7 @@ public struct DigitalCrownView: View { public var body: some View { CardView { VStack( - alignment: .leading, + alignment: .leading, spacing: style.dimension.directionalInsets1.top ) { if !(header is EmptyView) { diff --git a/Sources/CareKitEssentials/Cards/watchOS/DigitalCrown/DigitalCrownViewModel.swift b/Sources/CareKitEssentials/Cards/watchOS/DigitalCrown/DigitalCrownViewModel.swift index 94ed8db5..f294b678 100644 --- a/Sources/CareKitEssentials/Cards/watchOS/DigitalCrown/DigitalCrownViewModel.swift +++ b/Sources/CareKitEssentials/Cards/watchOS/DigitalCrown/DigitalCrownViewModel.swift @@ -34,7 +34,11 @@ open class DigitalCrownViewModel: CardViewModel { } open var isButtonDisabled: Bool { - value == event.outcomeFirstValue + guard let currentDouble = value.doubleValue, + let originalDouble = event.outcomeValueDouble else { + return false + } + return Int(currentDouble) == Int(originalDouble) } open var valueForButton: String {