diff --git a/.gitignore b/.gitignore index 330d167..d547f2c 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,8 @@ # Xcode # # gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore +.DS_Store + ## User settings xcuserdata/ diff --git a/CareKit Sample.xcodeproj/project.pbxproj b/CareKit Sample.xcodeproj/project.pbxproj index f1e550e..97ceb89 100644 --- a/CareKit Sample.xcodeproj/project.pbxproj +++ b/CareKit Sample.xcodeproj/project.pbxproj @@ -7,7 +7,7 @@ objects = { /* Begin PBXBuildFile section */ - 0CAB8B817CADA9997FF1A039 /* Pods_CareKit_Sample.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 189D3D46F99949809F1E2362 /* Pods_CareKit_Sample.framework */; }; + 8E24AA2F2602EF57005FFF2D /* SwiftUICharts in Frameworks */ = {isa = PBXBuildFile; productRef = 8E24AA2E2602EF57005FFF2D /* SwiftUICharts */; }; 8E7FFF7E25DA2476002982B4 /* CKCareKitRemoteSyncWithFirestore.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8E7FFF7D25DA2476002982B4 /* CKCareKitRemoteSyncWithFirestore.swift */; }; 8E7FFF8225DA24AA002982B4 /* CKCareKitManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8E7FFF8125DA24AA002982B4 /* CKCareKitManager.swift */; }; 8E7FFF8625DA24D0002982B4 /* CKCareKitManager+Sample.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8E7FFF8525DA24D0002982B4 /* CKCareKitManager+Sample.swift */; }; @@ -18,24 +18,40 @@ 8E7FFF9925DA4638002982B4 /* TipView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8E7FFF9525DA4638002982B4 /* TipView.swift */; }; 8E7FFF9A25DA4638002982B4 /* SurveyItemViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8E7FFF9625DA4638002982B4 /* SurveyItemViewController.swift */; }; 8E7FFF9D25DA4656002982B4 /* CareTeamViewControllerRepresentable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8E7FFF9C25DA4656002982B4 /* CareTeamViewControllerRepresentable.swift */; }; - 8E7FFFA225DA49D6002982B4 /* GoogleService-Info.plist in Resources */ = {isa = PBXBuildFile; fileRef = 8E7FFFA125DA49D6002982B4 /* GoogleService-Info.plist */; }; 8E7FFFA625DA4D95002982B4 /* CKTaskViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8E7FFFA525DA4D95002982B4 /* CKTaskViewController.swift */; }; 8E7FFFAC25DA4E05002982B4 /* ORKESerialization.m in Sources */ = {isa = PBXBuildFile; fileRef = 8E7FFFAA25DA4E05002982B4 /* ORKESerialization.m */; }; 8E7FFFAF25DA56E2002982B4 /* CKUploadToGCPTaskViewControllerDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8E7FFFAE25DA56E2002982B4 /* CKUploadToGCPTaskViewControllerDelegate.swift */; }; 8EE530BD25D8E31100E92C82 /* CareKit_SampleApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8EE530BC25D8E31100E92C82 /* CareKit_SampleApp.swift */; }; - 8EE530BF25D8E31100E92C82 /* ContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8EE530BE25D8E31100E92C82 /* ContentView.swift */; }; 8EE530C125D8E31200E92C82 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 8EE530C025D8E31200E92C82 /* Assets.xcassets */; }; 8EE530C425D8E31200E92C82 /* Preview Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 8EE530C325D8E31200E92C82 /* Preview Assets.xcassets */; }; 8EE530CE25D8E41200E92C82 /* CareKitStore in Frameworks */ = {isa = PBXBuildFile; productRef = 8EE530CD25D8E41200E92C82 /* CareKitStore */; }; 8EE530D025D8E41200E92C82 /* CareKit in Frameworks */ = {isa = PBXBuildFile; productRef = 8EE530CF25D8E41200E92C82 /* CareKit */; }; 8EE530D225D8E41200E92C82 /* CareKitFHIR in Frameworks */ = {isa = PBXBuildFile; productRef = 8EE530D125D8E41200E92C82 /* CareKitFHIR */; }; 8EE530D425D8E41200E92C82 /* CareKitUI in Frameworks */ = {isa = PBXBuildFile; productRef = 8EE530D325D8E41200E92C82 /* CareKitUI */; }; + CE0B6EC051BEC21932F879CA /* Pods_CareKit_Sample.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 802A4A5BA15F6E096A993083 /* Pods_CareKit_Sample.framework */; }; + F65E663725E399430009CBB4 /* Launch Screen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = F65E663625E399430009CBB4 /* Launch Screen.storyboard */; }; + F65E663A25E39B910009CBB4 /* LaunchUIView.swift in Sources */ = {isa = PBXBuildFile; fileRef = F65E663925E39B910009CBB4 /* LaunchUIView.swift */; }; + F65E663D25E39C2F0009CBB4 /* OnboardingUIView.swift in Sources */ = {isa = PBXBuildFile; fileRef = F65E663C25E39C2F0009CBB4 /* OnboardingUIView.swift */; }; + F65E664425E39CD30009CBB4 /* OnboardingSurveyViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = F65E664325E39CD30009CBB4 /* OnboardingSurveyViewController.swift */; }; + F65E664825E39EC60009CBB4 /* Config.swift in Sources */ = {isa = PBXBuildFile; fileRef = F65E664725E39EC60009CBB4 /* Config.swift */; }; + F65E664B25E39F670009CBB4 /* Config.plist in Resources */ = {isa = PBXBuildFile; fileRef = F65E664A25E39F670009CBB4 /* Config.plist */; }; + F65E664E25E39FF20009CBB4 /* Constants.swift in Sources */ = {isa = PBXBuildFile; fileRef = F65E664D25E39FF20009CBB4 /* Constants.swift */; }; + F65E665325E3A1330009CBB4 /* Metrics.swift in Sources */ = {isa = PBXBuildFile; fileRef = F65E665225E3A1330009CBB4 /* Metrics.swift */; }; + F65E665725E3A3530009CBB4 /* PasswordlessLoginStep.swift in Sources */ = {isa = PBXBuildFile; fileRef = F65E665625E3A3530009CBB4 /* PasswordlessLoginStep.swift */; }; + F65E665E25E3A82A0009CBB4 /* OnboardingViewCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = F65E665D25E3A82A0009CBB4 /* OnboardingViewCoordinator.swift */; }; + F65E669D25E3B39A0009CBB4 /* PageViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = F65E669C25E3B39A0009CBB4 /* PageViewController.swift */; }; + F66F913E25F70FF900A78360 /* acne_classification.mlmodel in Sources */ = {isa = PBXBuildFile; fileRef = F66F913D25F70FF900A78360 /* acne_classification.mlmodel */; }; + F67796C42601A14200ABEF8D /* LineGraphChartView.swift in Sources */ = {isa = PBXBuildFile; fileRef = F67796C32601A14200ABEF8D /* LineGraphChartView.swift */; }; + F67796C72601A1C700ABEF8D /* LineGraphChartDataSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = F67796C62601A1C700ABEF8D /* LineGraphChartDataSource.swift */; }; + F67796CA2601A1F000ABEF8D /* LineGraphUIView.swift in Sources */ = {isa = PBXBuildFile; fileRef = F67796C92601A1F000ABEF8D /* LineGraphUIView.swift */; }; + F67EF41125EC82BB00E7713E /* GoogleService-Info.plist in Resources */ = {isa = PBXBuildFile; fileRef = F67EF41025EC82BB00E7713E /* GoogleService-Info.plist */; }; + F6836BFE25EDBA2800E329B6 /* AcneModelViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = F6836BFD25EDBA2800E329B6 /* AcneModelViewController.swift */; }; + F68F128F25EF35F900DF109B /* ContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = F68F128E25EF35F900DF109B /* ContentView.swift */; }; /* End PBXBuildFile section */ /* Begin PBXFileReference section */ - 189D3D46F99949809F1E2362 /* Pods_CareKit_Sample.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_CareKit_Sample.framework; sourceTree = BUILT_PRODUCTS_DIR; }; - 32EE0C31BB55DD1BC53FF0A4 /* Pods-CareKit Sample.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-CareKit Sample.debug.xcconfig"; path = "Target Support Files/Pods-CareKit Sample/Pods-CareKit Sample.debug.xcconfig"; sourceTree = ""; }; - 63021CBA05620C69C8373068 /* Pods-CareKit Sample.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-CareKit Sample.release.xcconfig"; path = "Target Support Files/Pods-CareKit Sample/Pods-CareKit Sample.release.xcconfig"; sourceTree = ""; }; + 78353591FD67768226A50BB0 /* Pods-CareKit Sample.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-CareKit Sample.debug.xcconfig"; path = "Target Support Files/Pods-CareKit Sample/Pods-CareKit Sample.debug.xcconfig"; sourceTree = ""; }; + 802A4A5BA15F6E096A993083 /* Pods_CareKit_Sample.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_CareKit_Sample.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 8E7FFF7D25DA2476002982B4 /* CKCareKitRemoteSyncWithFirestore.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CKCareKitRemoteSyncWithFirestore.swift; sourceTree = ""; }; 8E7FFF8125DA24AA002982B4 /* CKCareKitManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CKCareKitManager.swift; sourceTree = ""; }; 8E7FFF8525DA24D0002982B4 /* CKCareKitManager+Sample.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "CKCareKitManager+Sample.swift"; sourceTree = ""; }; @@ -46,7 +62,6 @@ 8E7FFF9525DA4638002982B4 /* TipView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TipView.swift; sourceTree = ""; }; 8E7FFF9625DA4638002982B4 /* SurveyItemViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SurveyItemViewController.swift; sourceTree = ""; }; 8E7FFF9C25DA4656002982B4 /* CareTeamViewControllerRepresentable.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CareTeamViewControllerRepresentable.swift; sourceTree = ""; }; - 8E7FFFA125DA49D6002982B4 /* GoogleService-Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = "GoogleService-Info.plist"; sourceTree = ""; }; 8E7FFFA525DA4D95002982B4 /* CKTaskViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CKTaskViewController.swift; sourceTree = ""; }; 8E7FFFA925DA4E03002982B4 /* CareKit Sample-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "CareKit Sample-Bridging-Header.h"; sourceTree = ""; }; 8E7FFFAA25DA4E05002982B4 /* ORKESerialization.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ORKESerialization.m; sourceTree = ""; }; @@ -54,10 +69,28 @@ 8E7FFFAE25DA56E2002982B4 /* CKUploadToGCPTaskViewControllerDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CKUploadToGCPTaskViewControllerDelegate.swift; sourceTree = ""; }; 8EE530B925D8E31100E92C82 /* CareKit Sample.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "CareKit Sample.app"; sourceTree = BUILT_PRODUCTS_DIR; }; 8EE530BC25D8E31100E92C82 /* CareKit_SampleApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CareKit_SampleApp.swift; sourceTree = ""; }; - 8EE530BE25D8E31100E92C82 /* ContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContentView.swift; sourceTree = ""; }; 8EE530C025D8E31200E92C82 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 8EE530C325D8E31200E92C82 /* Preview Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = "Preview Assets.xcassets"; sourceTree = ""; }; 8EE530C525D8E31200E92C82 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + C080DF17E15BF8B76B35F00B /* Pods-CareKit Sample.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-CareKit Sample.release.xcconfig"; path = "Target Support Files/Pods-CareKit Sample/Pods-CareKit Sample.release.xcconfig"; sourceTree = ""; }; + F65E663625E399430009CBB4 /* Launch Screen.storyboard */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; path = "Launch Screen.storyboard"; sourceTree = ""; }; + F65E663925E39B910009CBB4 /* LaunchUIView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LaunchUIView.swift; sourceTree = ""; }; + F65E663C25E39C2F0009CBB4 /* OnboardingUIView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OnboardingUIView.swift; sourceTree = ""; }; + F65E664325E39CD30009CBB4 /* OnboardingSurveyViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OnboardingSurveyViewController.swift; sourceTree = ""; }; + F65E664725E39EC60009CBB4 /* Config.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Config.swift; sourceTree = ""; }; + F65E664A25E39F670009CBB4 /* Config.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Config.plist; sourceTree = ""; }; + F65E664D25E39FF20009CBB4 /* Constants.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Constants.swift; sourceTree = ""; }; + F65E665225E3A1330009CBB4 /* Metrics.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Metrics.swift; sourceTree = ""; }; + F65E665625E3A3530009CBB4 /* PasswordlessLoginStep.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PasswordlessLoginStep.swift; sourceTree = ""; }; + F65E665D25E3A82A0009CBB4 /* OnboardingViewCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OnboardingViewCoordinator.swift; sourceTree = ""; }; + F65E669C25E3B39A0009CBB4 /* PageViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PageViewController.swift; sourceTree = ""; }; + F66F913D25F70FF900A78360 /* acne_classification.mlmodel */ = {isa = PBXFileReference; lastKnownFileType = file.mlmodel; path = acne_classification.mlmodel; sourceTree = ""; }; + F67796C32601A14200ABEF8D /* LineGraphChartView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LineGraphChartView.swift; sourceTree = ""; }; + F67796C62601A1C700ABEF8D /* LineGraphChartDataSource.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LineGraphChartDataSource.swift; sourceTree = ""; }; + F67796C92601A1F000ABEF8D /* LineGraphUIView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LineGraphUIView.swift; sourceTree = ""; }; + F67EF41025EC82BB00E7713E /* GoogleService-Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = "GoogleService-Info.plist"; path = "../../../../Downloads/GoogleService-Info.plist"; sourceTree = ""; }; + F6836BFD25EDBA2800E329B6 /* AcneModelViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AcneModelViewController.swift; sourceTree = ""; }; + F68F128E25EF35F900DF109B /* ContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContentView.swift; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -68,8 +101,9 @@ 8EE530D025D8E41200E92C82 /* CareKit in Frameworks */, 8EE530D225D8E41200E92C82 /* CareKitFHIR in Frameworks */, 8EE530D425D8E41200E92C82 /* CareKitUI in Frameworks */, + 8E24AA2F2602EF57005FFF2D /* SwiftUICharts in Frameworks */, 8EE530CE25D8E41200E92C82 /* CareKitStore in Frameworks */, - 0CAB8B817CADA9997FF1A039 /* Pods_CareKit_Sample.framework in Frameworks */, + CE0B6EC051BEC21932F879CA /* Pods_CareKit_Sample.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -89,6 +123,8 @@ children = ( 8E7FFF9425DA4638002982B4 /* Views */, 8E7FFF9225DA4635002982B4 /* ScheduleViewController.swift */, + 8E7FFF9625DA4638002982B4 /* SurveyItemViewController.swift */, + F6836BFD25EDBA2800E329B6 /* AcneModelViewController.swift */, 8E7FFF9325DA4636002982B4 /* ScheduleViewControllerRepresentable.swift */, ); path = Schedule; @@ -106,7 +142,6 @@ isa = PBXGroup; children = ( 8E7FFF9525DA4638002982B4 /* TipView.swift */, - 8E7FFF9625DA4638002982B4 /* SurveyItemViewController.swift */, ); path = Views; sourceTree = ""; @@ -124,9 +159,14 @@ 8E7FFFB525DCEAFE002982B4 /* Tabs */ = { isa = PBXGroup; children = ( + F67796B5260198C700ABEF8D /* Chart */, + F65E662025E3958D0009CBB4 /* Onboarding */, 8E7FFF9025DA4621002982B4 /* Schedule */, 8E7FFF9125DA4626002982B4 /* Care Team */, - 8EE530BE25D8E31100E92C82 /* ContentView.swift */, + F65E663925E39B910009CBB4 /* LaunchUIView.swift */, + F65E664725E39EC60009CBB4 /* Config.swift */, + F65E665225E3A1330009CBB4 /* Metrics.swift */, + F68F128E25EF35F900DF109B /* ContentView.swift */, ); path = Tabs; sourceTree = ""; @@ -137,7 +177,7 @@ 8EE530BB25D8E31100E92C82 /* CareKit Sample */, 8EE530BA25D8E31100E92C82 /* Products */, E11BB4F6FC59F3A7669B1072 /* Pods */, - EA83C4B652754BD4747D762E /* Frameworks */, + D0BC8C690D0221665748075B /* Frameworks */, ); sourceTree = ""; }; @@ -177,8 +217,12 @@ 8E7FFF7C25DA246C002982B4 /* CareKit */, 8EE530EC25DA154900E92C82 /* Firebase */, 8EE530C025D8E31200E92C82 /* Assets.xcassets */, - 8E7FFFA125DA49D6002982B4 /* GoogleService-Info.plist */, + F65E663625E399430009CBB4 /* Launch Screen.storyboard */, + F67EF41025EC82BB00E7713E /* GoogleService-Info.plist */, + F66F913D25F70FF900A78360 /* acne_classification.mlmodel */, 8EE530C525D8E31200E92C82 /* Info.plist */, + F65E664A25E39F670009CBB4 /* Config.plist */, + F65E664D25E39FF20009CBB4 /* Constants.swift */, ); path = "Supporting Files"; sourceTree = ""; @@ -194,21 +238,43 @@ path = Firebase; sourceTree = ""; }; + D0BC8C690D0221665748075B /* Frameworks */ = { + isa = PBXGroup; + children = ( + 802A4A5BA15F6E096A993083 /* Pods_CareKit_Sample.framework */, + ); + name = Frameworks; + sourceTree = ""; + }; E11BB4F6FC59F3A7669B1072 /* Pods */ = { isa = PBXGroup; children = ( - 32EE0C31BB55DD1BC53FF0A4 /* Pods-CareKit Sample.debug.xcconfig */, - 63021CBA05620C69C8373068 /* Pods-CareKit Sample.release.xcconfig */, + 78353591FD67768226A50BB0 /* Pods-CareKit Sample.debug.xcconfig */, + C080DF17E15BF8B76B35F00B /* Pods-CareKit Sample.release.xcconfig */, ); path = Pods; sourceTree = ""; }; - EA83C4B652754BD4747D762E /* Frameworks */ = { + F65E662025E3958D0009CBB4 /* Onboarding */ = { isa = PBXGroup; children = ( - 189D3D46F99949809F1E2362 /* Pods_CareKit_Sample.framework */, + F65E663C25E39C2F0009CBB4 /* OnboardingUIView.swift */, + F65E664325E39CD30009CBB4 /* OnboardingSurveyViewController.swift */, + F65E665625E3A3530009CBB4 /* PasswordlessLoginStep.swift */, + F65E665D25E3A82A0009CBB4 /* OnboardingViewCoordinator.swift */, + F65E669C25E3B39A0009CBB4 /* PageViewController.swift */, ); - name = Frameworks; + path = Onboarding; + sourceTree = ""; + }; + F67796B5260198C700ABEF8D /* Chart */ = { + isa = PBXGroup; + children = ( + F67796C32601A14200ABEF8D /* LineGraphChartView.swift */, + F67796C62601A1C700ABEF8D /* LineGraphChartDataSource.swift */, + F67796C92601A1F000ABEF8D /* LineGraphUIView.swift */, + ); + path = Chart; sourceTree = ""; }; /* End PBXGroup section */ @@ -218,11 +284,11 @@ isa = PBXNativeTarget; buildConfigurationList = 8EE530C825D8E31200E92C82 /* Build configuration list for PBXNativeTarget "CareKit Sample" */; buildPhases = ( - CD247DEABED9B59A0385662B /* [CP] Check Pods Manifest.lock */, + 5CEC8F828FCA09969F7E6D85 /* [CP] Check Pods Manifest.lock */, 8EE530B525D8E31100E92C82 /* Sources */, 8EE530B625D8E31100E92C82 /* Frameworks */, 8EE530B725D8E31100E92C82 /* Resources */, - 8D1F707A368F58776F259896 /* [CP] Embed Pods Frameworks */, + BD7F93125045B231AD9B7BD7 /* [CP] Embed Pods Frameworks */, ); buildRules = ( ); @@ -234,6 +300,7 @@ 8EE530CF25D8E41200E92C82 /* CareKit */, 8EE530D125D8E41200E92C82 /* CareKitFHIR */, 8EE530D325D8E41200E92C82 /* CareKitUI */, + 8E24AA2E2602EF57005FFF2D /* SwiftUICharts */, ); productName = "CareKit Sample"; productReference = 8EE530B925D8E31100E92C82 /* CareKit Sample.app */; @@ -265,6 +332,7 @@ mainGroup = 8EE530B025D8E31100E92C82; packageReferences = ( 8EE530CC25D8E41200E92C82 /* XCRemoteSwiftPackageReference "CareKit" */, + 8E24AA2D2602EF56005FFF2D /* XCRemoteSwiftPackageReference "ChartView" */, ); productRefGroup = 8EE530BA25D8E31100E92C82 /* Products */; projectDirPath = ""; @@ -281,7 +349,9 @@ buildActionMask = 2147483647; files = ( 8EE530C425D8E31200E92C82 /* Preview Assets.xcassets in Resources */, - 8E7FFFA225DA49D6002982B4 /* GoogleService-Info.plist in Resources */, + F65E663725E399430009CBB4 /* Launch Screen.storyboard in Resources */, + F67EF41125EC82BB00E7713E /* GoogleService-Info.plist in Resources */, + F65E664B25E39F670009CBB4 /* Config.plist in Resources */, 8EE530C125D8E31200E92C82 /* Assets.xcassets in Resources */, ); runOnlyForDeploymentPostprocessing = 0; @@ -289,43 +359,43 @@ /* End PBXResourcesBuildPhase section */ /* Begin PBXShellScriptBuildPhase section */ - 8D1F707A368F58776F259896 /* [CP] Embed Pods Frameworks */ = { + 5CEC8F828FCA09969F7E6D85 /* [CP] Check Pods Manifest.lock */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( ); inputFileListPaths = ( - "${PODS_ROOT}/Target Support Files/Pods-CareKit Sample/Pods-CareKit Sample-frameworks-${CONFIGURATION}-input-files.xcfilelist", ); - name = "[CP] Embed Pods Frameworks"; + inputPaths = ( + "${PODS_PODFILE_DIR_PATH}/Podfile.lock", + "${PODS_ROOT}/Manifest.lock", + ); + name = "[CP] Check Pods Manifest.lock"; outputFileListPaths = ( - "${PODS_ROOT}/Target Support Files/Pods-CareKit Sample/Pods-CareKit Sample-frameworks-${CONFIGURATION}-output-files.xcfilelist", + ); + outputPaths = ( + "$(DERIVED_FILE_DIR)/Pods-CareKit Sample-checkManifestLockResult.txt", ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; - shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-CareKit Sample/Pods-CareKit Sample-frameworks.sh\"\n"; + shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; showEnvVarsInLog = 0; }; - CD247DEABED9B59A0385662B /* [CP] Check Pods Manifest.lock */ = { + BD7F93125045B231AD9B7BD7 /* [CP] Embed Pods Frameworks */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( ); inputFileListPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-CareKit Sample/Pods-CareKit Sample-frameworks-${CONFIGURATION}-input-files.xcfilelist", ); - inputPaths = ( - "${PODS_PODFILE_DIR_PATH}/Podfile.lock", - "${PODS_ROOT}/Manifest.lock", - ); - name = "[CP] Check Pods Manifest.lock"; + name = "[CP] Embed Pods Frameworks"; outputFileListPaths = ( - ); - outputPaths = ( - "$(DERIVED_FILE_DIR)/Pods-CareKit Sample-checkManifestLockResult.txt", + "${PODS_ROOT}/Target Support Files/Pods-CareKit Sample/Pods-CareKit Sample-frameworks-${CONFIGURATION}-output-files.xcfilelist", ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; - shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; + shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-CareKit Sample/Pods-CareKit Sample-frameworks.sh\"\n"; showEnvVarsInLog = 0; }; /* End PBXShellScriptBuildPhase section */ @@ -336,18 +406,32 @@ buildActionMask = 2147483647; files = ( 8E7FFF9825DA4638002982B4 /* ScheduleViewControllerRepresentable.swift in Sources */, + F66F913E25F70FF900A78360 /* acne_classification.mlmodel in Sources */, + F65E663A25E39B910009CBB4 /* LaunchUIView.swift in Sources */, 8E7FFF8D25DA26D7002982B4 /* CKSendHelper.swift in Sources */, 8E7FFF8625DA24D0002982B4 /* CKCareKitManager+Sample.swift in Sources */, + F67796CA2601A1F000ABEF8D /* LineGraphUIView.swift in Sources */, + F65E665325E3A1330009CBB4 /* Metrics.swift in Sources */, + F65E665725E3A3530009CBB4 /* PasswordlessLoginStep.swift in Sources */, + F65E665E25E3A82A0009CBB4 /* OnboardingViewCoordinator.swift in Sources */, + F65E664825E39EC60009CBB4 /* Config.swift in Sources */, + F65E664425E39CD30009CBB4 /* OnboardingSurveyViewController.swift in Sources */, + F68F128F25EF35F900DF109B /* ContentView.swift in Sources */, 8E7FFF7E25DA2476002982B4 /* CKCareKitRemoteSyncWithFirestore.swift in Sources */, 8E7FFF9925DA4638002982B4 /* TipView.swift in Sources */, 8E7FFF9725DA4638002982B4 /* ScheduleViewController.swift in Sources */, 8E7FFFA625DA4D95002982B4 /* CKTaskViewController.swift in Sources */, 8E7FFFAF25DA56E2002982B4 /* CKUploadToGCPTaskViewControllerDelegate.swift in Sources */, 8E7FFF9A25DA4638002982B4 /* SurveyItemViewController.swift in Sources */, + F65E669D25E3B39A0009CBB4 /* PageViewController.swift in Sources */, + F65E663D25E39C2F0009CBB4 /* OnboardingUIView.swift in Sources */, 8E7FFF8925DA267E002982B4 /* CKStudyUser.swift in Sources */, + F67796C42601A14200ABEF8D /* LineGraphChartView.swift in Sources */, 8E7FFF8225DA24AA002982B4 /* CKCareKitManager.swift in Sources */, + F65E664E25E39FF20009CBB4 /* Constants.swift in Sources */, + F67796C72601A1C700ABEF8D /* LineGraphChartDataSource.swift in Sources */, 8E7FFF9D25DA4656002982B4 /* CareTeamViewControllerRepresentable.swift in Sources */, - 8EE530BF25D8E31100E92C82 /* ContentView.swift in Sources */, + F6836BFE25EDBA2800E329B6 /* AcneModelViewController.swift in Sources */, 8EE530BD25D8E31100E92C82 /* CareKit_SampleApp.swift in Sources */, 8E7FFFAC25DA4E05002982B4 /* ORKESerialization.m in Sources */, ); @@ -474,14 +558,14 @@ }; 8EE530C925D8E31200E92C82 /* Debug */ = { isa = XCBuildConfiguration; - baseConfigurationReference = 32EE0C31BB55DD1BC53FF0A4 /* Pods-CareKit Sample.debug.xcconfig */; + baseConfigurationReference = 78353591FD67768226A50BB0 /* Pods-CareKit Sample.debug.xcconfig */; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; CLANG_ENABLE_MODULES = YES; CODE_SIGN_STYLE = Automatic; DEVELOPMENT_ASSET_PATHS = "\"CareKit Sample/Supporting Files/Preview Content\""; - DEVELOPMENT_TEAM = CQRZ4E7K9U; + DEVELOPMENT_TEAM = 8QL2EG96Y4; ENABLE_PREVIEWS = YES; INFOPLIST_FILE = "CareKit Sample/Supporting Files/Info.plist"; IPHONEOS_DEPLOYMENT_TARGET = 14.0; @@ -489,7 +573,7 @@ "$(inherited)", "@executable_path/Frameworks", ); - PRODUCT_BUNDLE_IDENTIFIER = edu.stanford.cs342.carekit.sample; + PRODUCT_BUNDLE_IDENTIFIER = edu.stanford.cs342.carekit.skinai; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_OBJC_BRIDGING_HEADER = "CareKit Sample/Supporting Files/ResearchKit/CareKit Sample-Bridging-Header.h"; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; @@ -500,14 +584,14 @@ }; 8EE530CA25D8E31200E92C82 /* Release */ = { isa = XCBuildConfiguration; - baseConfigurationReference = 63021CBA05620C69C8373068 /* Pods-CareKit Sample.release.xcconfig */; + baseConfigurationReference = C080DF17E15BF8B76B35F00B /* Pods-CareKit Sample.release.xcconfig */; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; CLANG_ENABLE_MODULES = YES; CODE_SIGN_STYLE = Automatic; DEVELOPMENT_ASSET_PATHS = "\"CareKit Sample/Supporting Files/Preview Content\""; - DEVELOPMENT_TEAM = CQRZ4E7K9U; + DEVELOPMENT_TEAM = 8QL2EG96Y4; ENABLE_PREVIEWS = YES; INFOPLIST_FILE = "CareKit Sample/Supporting Files/Info.plist"; IPHONEOS_DEPLOYMENT_TARGET = 14.0; @@ -515,7 +599,7 @@ "$(inherited)", "@executable_path/Frameworks", ); - PRODUCT_BUNDLE_IDENTIFIER = edu.stanford.cs342.carekit.sample; + PRODUCT_BUNDLE_IDENTIFIER = edu.stanford.cs342.carekit.skinai; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_OBJC_BRIDGING_HEADER = "CareKit Sample/Supporting Files/ResearchKit/CareKit Sample-Bridging-Header.h"; SWIFT_VERSION = 5.0; @@ -547,6 +631,14 @@ /* End XCConfigurationList section */ /* Begin XCRemoteSwiftPackageReference section */ + 8E24AA2D2602EF56005FFF2D /* XCRemoteSwiftPackageReference "ChartView" */ = { + isa = XCRemoteSwiftPackageReference; + repositoryURL = "https://github.com/AppPear/ChartView"; + requirement = { + kind = upToNextMajorVersion; + minimumVersion = 1.5.4; + }; + }; 8EE530CC25D8E41200E92C82 /* XCRemoteSwiftPackageReference "CareKit" */ = { isa = XCRemoteSwiftPackageReference; repositoryURL = "https://github.com/carekit-apple/CareKit"; @@ -558,6 +650,11 @@ /* End XCRemoteSwiftPackageReference section */ /* Begin XCSwiftPackageProductDependency section */ + 8E24AA2E2602EF57005FFF2D /* SwiftUICharts */ = { + isa = XCSwiftPackageProductDependency; + package = 8E24AA2D2602EF56005FFF2D /* XCRemoteSwiftPackageReference "ChartView" */; + productName = SwiftUICharts; + }; 8EE530CD25D8E41200E92C82 /* CareKitStore */ = { isa = XCSwiftPackageProductDependency; package = 8EE530CC25D8E41200E92C82 /* XCRemoteSwiftPackageReference "CareKit" */; diff --git a/CareKit Sample.xcworkspace/xcshareddata/swiftpm/Package.resolved b/CareKit Sample.xcworkspace/xcshareddata/swiftpm/Package.resolved index 2669904..07c2cf5 100644 --- a/CareKit Sample.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/CareKit Sample.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -10,6 +10,15 @@ "version": null } }, + { + "package": "SwiftUICharts", + "repositoryURL": "https://github.com/AppPear/ChartView", + "state": { + "branch": null, + "revision": "4699847a9ac0c694666cea3acef133498952566e", + "version": "1.5.4" + } + }, { "package": "FHIRModels", "repositoryURL": "https://github.com/apple/FHIRModels.git", diff --git a/CareKit Sample/CKCareKitManager+Sample.swift b/CareKit Sample/CKCareKitManager+Sample.swift index e79b45c..7e273de 100644 --- a/CareKit Sample/CKCareKitManager+Sample.swift +++ b/CareKit Sample/CKCareKitManager+Sample.swift @@ -17,7 +17,7 @@ internal extension OCKStore { func populateSampleData() { seedLiveLectureTasks() seedResearchKitSample() - + seedSkinAI() createContacts() } @@ -49,36 +49,26 @@ internal extension OCKStore { fileprivate func seedLiveLectureTasks() { - // Daily medication task - let medsSchedule = OCKSchedule.dailyAtTime(hour: 8, minutes: 0, start: Date(), end: nil, text: nil) - var meds = OCKTask(id: "meds", title: "Take your accutane", carePlanUUID: nil, schedule: medsSchedule) - meds.instructions = "Take 1 tablet after breakfast." - // Rehab task // Week 1 - twice daily // Week 2 - once daily // Week 3 - end let startOfWeek1 = Calendar.current.startOfDay(for: Date()) let endOfWeek1 = Calendar.current.date(byAdding: .weekOfYear, value: 1, to: startOfWeek1)! - let startOfWeek2 = endOfWeek1 - let endOfWeek2 = Calendar.current.date(byAdding: .weekOfYear, value: 1, to: startOfWeek2)! - + let week1Morning = OCKSchedule.dailyAtTime(hour: 8, minutes: 0, start: startOfWeek1, end: endOfWeek1, text: "Morning") let week1Evening = OCKSchedule.dailyAtTime(hour: 20, minutes: 0, start: startOfWeek1, end: endOfWeek1, text: "Before Bed") - let week2Afternoon = OCKSchedule.dailyAtTime(hour: 12, minutes: 0, start: startOfWeek2, end: endOfWeek2, text: "Afternoon") - - let rehabSchedule = OCKSchedule(composing: [week1Morning, week1Evening, week2Afternoon]) + let rehabSchedule = OCKSchedule(composing: [week1Morning, week1Evening]) - var rehab = OCKTask(id: "rehab", title: "Rehab Exercises", carePlanUUID: nil, schedule: rehabSchedule) + var rehab = OCKTask(id: "rehab", title: "Skincare routine", carePlanUUID: nil, schedule: rehabSchedule) rehab.instructions = """ - Lie on your back and bend your affected knee 90 degrees with your foot. - You can modify this text with any instructions of your choice! + Don't forget to follow your morning and evening skincare routine steps! """ - addTasks([meds, rehab], callbackQueue: .main) { result in + addTasks([rehab], callbackQueue: .main) { result in switch result { case let .success(tasks): print("Added \(tasks.count) tasks") @@ -89,12 +79,20 @@ internal extension OCKStore { } fileprivate func seedResearchKitSample() { - let surveySchedule = OCKSchedule.dailyAtTime(hour: 12, minutes: 0, start: Date(), end: nil, text: nil) - var survey = OCKTask(id: "survey", title: "Take a Survey 📝", carePlanUUID: nil, schedule: surveySchedule) + let startOfWeek1 = Calendar.current.startOfDay(for: Date()) + let surveySchedule = OCKSchedule.weeklyAtTime(weekday: 7, hours: 12, minutes: 0, start: startOfWeek1, end: nil, targetValues: [], text: "") + var survey = OCKTask(id: "survey", title: "Take your skin survey", carePlanUUID: nil, schedule: surveySchedule) survey.impactsAdherence = true - survey.instructions = "You can schedule any ResearchKit survey in your app." - +// survey.instructions = "Please take your skin survey so we can know how you are doing." addTasks([survey], callbackQueue: .main, completion: nil) } + fileprivate func seedSkinAI() { + let startOfWeek1 = Calendar.current.startOfDay(for: Date()) + let skinAISchedule = OCKSchedule.weeklyAtTime(weekday: 7, hours: 12, minutes: 0, start: startOfWeek1, end: nil, targetValues: [], text: "") + var skinAI = OCKTask(id: "skinAI", title: "Take your skin diagnosis", carePlanUUID: nil, schedule: skinAISchedule) + skinAI.impactsAdherence = true +// skinAI.instructions = "Please take your skin diagnosis so we can monitor you skin progession." + addTasks([skinAI], callbackQueue: .main, completion: nil) + } } diff --git a/CareKit Sample/CKCareKitManager.swift b/CareKit Sample/CKCareKitManager.swift index bc6f8b4..acce094 100644 --- a/CareKit Sample/CKCareKitManager.swift +++ b/CareKit Sample/CKCareKitManager.swift @@ -25,7 +25,8 @@ class CKCareKitManager: NSObject { override init() { super.init() - coreDataStore = OCKStore(name: "CKSampleCareKitStore", securityApplicationGroupIdentifier: nil, type: coreDataStoreType, remote: CKCareKitRemoteSyncWithFirestore()) +// coreDataStore = OCKStore(name: "CKSampleCareKitStore", securityApplicationGroupIdentifier: nil, type: coreDataStoreType, remote: CKCareKitRemoteSyncWithFirestore()) + coreDataStore = OCKStore(name: "CKSampleCareKitStore", securityApplicationGroupIdentifier: nil, type: .inMemory) initStore(forceUpdate: coreDataStoreType == .inMemory) diff --git a/CareKit Sample/CareKit_SampleApp.swift b/CareKit Sample/CareKit_SampleApp.swift index a3844ea..8f67646 100644 --- a/CareKit Sample/CareKit_SampleApp.swift +++ b/CareKit Sample/CareKit_SampleApp.swift @@ -27,7 +27,7 @@ struct CareKit_SampleApp: App { var body: some Scene { WindowGroup { - ContentView() + LaunchUIView() } } } diff --git a/CareKit Sample/Supporting Files/Assets.xcassets/AppIcon.appiconset/100.png b/CareKit Sample/Supporting Files/Assets.xcassets/AppIcon.appiconset/100.png new file mode 100644 index 0000000..945a0e3 Binary files /dev/null and b/CareKit Sample/Supporting Files/Assets.xcassets/AppIcon.appiconset/100.png differ diff --git a/CareKit Sample/Supporting Files/Assets.xcassets/AppIcon.appiconset/1024.png b/CareKit Sample/Supporting Files/Assets.xcassets/AppIcon.appiconset/1024.png new file mode 100644 index 0000000..76bc86e Binary files /dev/null and b/CareKit Sample/Supporting Files/Assets.xcassets/AppIcon.appiconset/1024.png differ diff --git a/CareKit Sample/Supporting Files/Assets.xcassets/AppIcon.appiconset/114.png b/CareKit Sample/Supporting Files/Assets.xcassets/AppIcon.appiconset/114.png new file mode 100644 index 0000000..8549579 Binary files /dev/null and b/CareKit Sample/Supporting Files/Assets.xcassets/AppIcon.appiconset/114.png differ diff --git a/CareKit Sample/Supporting Files/Assets.xcassets/AppIcon.appiconset/120.png b/CareKit Sample/Supporting Files/Assets.xcassets/AppIcon.appiconset/120.png new file mode 100644 index 0000000..a94ba21 Binary files /dev/null and b/CareKit Sample/Supporting Files/Assets.xcassets/AppIcon.appiconset/120.png differ diff --git a/CareKit Sample/Supporting Files/Assets.xcassets/AppIcon.appiconset/128.png b/CareKit Sample/Supporting Files/Assets.xcassets/AppIcon.appiconset/128.png new file mode 100644 index 0000000..25873e8 Binary files /dev/null and b/CareKit Sample/Supporting Files/Assets.xcassets/AppIcon.appiconset/128.png differ diff --git a/CareKit Sample/Supporting Files/Assets.xcassets/AppIcon.appiconset/144.png b/CareKit Sample/Supporting Files/Assets.xcassets/AppIcon.appiconset/144.png new file mode 100644 index 0000000..b847879 Binary files /dev/null and b/CareKit Sample/Supporting Files/Assets.xcassets/AppIcon.appiconset/144.png differ diff --git a/CareKit Sample/Supporting Files/Assets.xcassets/AppIcon.appiconset/152.png b/CareKit Sample/Supporting Files/Assets.xcassets/AppIcon.appiconset/152.png new file mode 100644 index 0000000..0e2fd9b Binary files /dev/null and b/CareKit Sample/Supporting Files/Assets.xcassets/AppIcon.appiconset/152.png differ diff --git a/CareKit Sample/Supporting Files/Assets.xcassets/AppIcon.appiconset/16.png b/CareKit Sample/Supporting Files/Assets.xcassets/AppIcon.appiconset/16.png new file mode 100644 index 0000000..696a46a Binary files /dev/null and b/CareKit Sample/Supporting Files/Assets.xcassets/AppIcon.appiconset/16.png differ diff --git a/CareKit Sample/Supporting Files/Assets.xcassets/AppIcon.appiconset/167.png b/CareKit Sample/Supporting Files/Assets.xcassets/AppIcon.appiconset/167.png new file mode 100644 index 0000000..a9c0ae6 Binary files /dev/null and b/CareKit Sample/Supporting Files/Assets.xcassets/AppIcon.appiconset/167.png differ diff --git a/CareKit Sample/Supporting Files/Assets.xcassets/AppIcon.appiconset/172.png b/CareKit Sample/Supporting Files/Assets.xcassets/AppIcon.appiconset/172.png new file mode 100644 index 0000000..b0f48e3 Binary files /dev/null and b/CareKit Sample/Supporting Files/Assets.xcassets/AppIcon.appiconset/172.png differ diff --git a/CareKit Sample/Supporting Files/Assets.xcassets/AppIcon.appiconset/180.png b/CareKit Sample/Supporting Files/Assets.xcassets/AppIcon.appiconset/180.png new file mode 100644 index 0000000..2a2fb35 Binary files /dev/null and b/CareKit Sample/Supporting Files/Assets.xcassets/AppIcon.appiconset/180.png differ diff --git a/CareKit Sample/Supporting Files/Assets.xcassets/AppIcon.appiconset/196.png b/CareKit Sample/Supporting Files/Assets.xcassets/AppIcon.appiconset/196.png new file mode 100644 index 0000000..16fa10e Binary files /dev/null and b/CareKit Sample/Supporting Files/Assets.xcassets/AppIcon.appiconset/196.png differ diff --git a/CareKit Sample/Supporting Files/Assets.xcassets/AppIcon.appiconset/20.png b/CareKit Sample/Supporting Files/Assets.xcassets/AppIcon.appiconset/20.png new file mode 100644 index 0000000..19c9555 Binary files /dev/null and b/CareKit Sample/Supporting Files/Assets.xcassets/AppIcon.appiconset/20.png differ diff --git a/CareKit Sample/Supporting Files/Assets.xcassets/AppIcon.appiconset/216.png b/CareKit Sample/Supporting Files/Assets.xcassets/AppIcon.appiconset/216.png new file mode 100644 index 0000000..4ad4229 Binary files /dev/null and b/CareKit Sample/Supporting Files/Assets.xcassets/AppIcon.appiconset/216.png differ diff --git a/CareKit Sample/Supporting Files/Assets.xcassets/AppIcon.appiconset/256.png b/CareKit Sample/Supporting Files/Assets.xcassets/AppIcon.appiconset/256.png new file mode 100644 index 0000000..d2ad34c Binary files /dev/null and b/CareKit Sample/Supporting Files/Assets.xcassets/AppIcon.appiconset/256.png differ diff --git a/CareKit Sample/Supporting Files/Assets.xcassets/AppIcon.appiconset/29.png b/CareKit Sample/Supporting Files/Assets.xcassets/AppIcon.appiconset/29.png new file mode 100644 index 0000000..641ef85 Binary files /dev/null and b/CareKit Sample/Supporting Files/Assets.xcassets/AppIcon.appiconset/29.png differ diff --git a/CareKit Sample/Supporting Files/Assets.xcassets/AppIcon.appiconset/32.png b/CareKit Sample/Supporting Files/Assets.xcassets/AppIcon.appiconset/32.png new file mode 100644 index 0000000..5c5c3ef Binary files /dev/null and b/CareKit Sample/Supporting Files/Assets.xcassets/AppIcon.appiconset/32.png differ diff --git a/CareKit Sample/Supporting Files/Assets.xcassets/AppIcon.appiconset/40.png b/CareKit Sample/Supporting Files/Assets.xcassets/AppIcon.appiconset/40.png new file mode 100644 index 0000000..6ee1ff6 Binary files /dev/null and b/CareKit Sample/Supporting Files/Assets.xcassets/AppIcon.appiconset/40.png differ diff --git a/CareKit Sample/Supporting Files/Assets.xcassets/AppIcon.appiconset/48.png b/CareKit Sample/Supporting Files/Assets.xcassets/AppIcon.appiconset/48.png new file mode 100644 index 0000000..24a1830 Binary files /dev/null and b/CareKit Sample/Supporting Files/Assets.xcassets/AppIcon.appiconset/48.png differ diff --git a/CareKit Sample/Supporting Files/Assets.xcassets/AppIcon.appiconset/50.png b/CareKit Sample/Supporting Files/Assets.xcassets/AppIcon.appiconset/50.png new file mode 100644 index 0000000..615aa72 Binary files /dev/null and b/CareKit Sample/Supporting Files/Assets.xcassets/AppIcon.appiconset/50.png differ diff --git a/CareKit Sample/Supporting Files/Assets.xcassets/AppIcon.appiconset/512.png b/CareKit Sample/Supporting Files/Assets.xcassets/AppIcon.appiconset/512.png new file mode 100644 index 0000000..1def687 Binary files /dev/null and b/CareKit Sample/Supporting Files/Assets.xcassets/AppIcon.appiconset/512.png differ diff --git a/CareKit Sample/Supporting Files/Assets.xcassets/AppIcon.appiconset/55.png b/CareKit Sample/Supporting Files/Assets.xcassets/AppIcon.appiconset/55.png new file mode 100644 index 0000000..25ea99e Binary files /dev/null and b/CareKit Sample/Supporting Files/Assets.xcassets/AppIcon.appiconset/55.png differ diff --git a/CareKit Sample/Supporting Files/Assets.xcassets/AppIcon.appiconset/57.png b/CareKit Sample/Supporting Files/Assets.xcassets/AppIcon.appiconset/57.png new file mode 100644 index 0000000..b5cde6b Binary files /dev/null and b/CareKit Sample/Supporting Files/Assets.xcassets/AppIcon.appiconset/57.png differ diff --git a/CareKit Sample/Supporting Files/Assets.xcassets/AppIcon.appiconset/58.png b/CareKit Sample/Supporting Files/Assets.xcassets/AppIcon.appiconset/58.png new file mode 100644 index 0000000..1c35ad3 Binary files /dev/null and b/CareKit Sample/Supporting Files/Assets.xcassets/AppIcon.appiconset/58.png differ diff --git a/CareKit Sample/Supporting Files/Assets.xcassets/AppIcon.appiconset/60.png b/CareKit Sample/Supporting Files/Assets.xcassets/AppIcon.appiconset/60.png new file mode 100644 index 0000000..b7a162a Binary files /dev/null and b/CareKit Sample/Supporting Files/Assets.xcassets/AppIcon.appiconset/60.png differ diff --git a/CareKit Sample/Supporting Files/Assets.xcassets/AppIcon.appiconset/64.png b/CareKit Sample/Supporting Files/Assets.xcassets/AppIcon.appiconset/64.png new file mode 100644 index 0000000..321a4bd Binary files /dev/null and b/CareKit Sample/Supporting Files/Assets.xcassets/AppIcon.appiconset/64.png differ diff --git a/CareKit Sample/Supporting Files/Assets.xcassets/AppIcon.appiconset/72.png b/CareKit Sample/Supporting Files/Assets.xcassets/AppIcon.appiconset/72.png new file mode 100644 index 0000000..85f9a28 Binary files /dev/null and b/CareKit Sample/Supporting Files/Assets.xcassets/AppIcon.appiconset/72.png differ diff --git a/CareKit Sample/Supporting Files/Assets.xcassets/AppIcon.appiconset/76.png b/CareKit Sample/Supporting Files/Assets.xcassets/AppIcon.appiconset/76.png new file mode 100644 index 0000000..0e30123 Binary files /dev/null and b/CareKit Sample/Supporting Files/Assets.xcassets/AppIcon.appiconset/76.png differ diff --git a/CareKit Sample/Supporting Files/Assets.xcassets/AppIcon.appiconset/80.png b/CareKit Sample/Supporting Files/Assets.xcassets/AppIcon.appiconset/80.png new file mode 100644 index 0000000..81fcde3 Binary files /dev/null and b/CareKit Sample/Supporting Files/Assets.xcassets/AppIcon.appiconset/80.png differ diff --git a/CareKit Sample/Supporting Files/Assets.xcassets/AppIcon.appiconset/87.png b/CareKit Sample/Supporting Files/Assets.xcassets/AppIcon.appiconset/87.png new file mode 100644 index 0000000..b3d12aa Binary files /dev/null and b/CareKit Sample/Supporting Files/Assets.xcassets/AppIcon.appiconset/87.png differ diff --git a/CareKit Sample/Supporting Files/Assets.xcassets/AppIcon.appiconset/88.png b/CareKit Sample/Supporting Files/Assets.xcassets/AppIcon.appiconset/88.png new file mode 100644 index 0000000..d37328f Binary files /dev/null and b/CareKit Sample/Supporting Files/Assets.xcassets/AppIcon.appiconset/88.png differ diff --git a/CareKit Sample/Supporting Files/Assets.xcassets/AppIcon.appiconset/Contents.json b/CareKit Sample/Supporting Files/Assets.xcassets/AppIcon.appiconset/Contents.json index 9221b9b..d48fcc0 100644 --- a/CareKit Sample/Supporting Files/Assets.xcassets/AppIcon.appiconset/Contents.json +++ b/CareKit Sample/Supporting Files/Assets.xcassets/AppIcon.appiconset/Contents.json @@ -1,94 +1,298 @@ { "images" : [ { + "filename" : "40.png", "idiom" : "iphone", "scale" : "2x", "size" : "20x20" }, { + "filename" : "60.png", "idiom" : "iphone", "scale" : "3x", "size" : "20x20" }, { + "filename" : "29.png", + "idiom" : "iphone", + "scale" : "1x", + "size" : "29x29" + }, + { + "filename" : "58.png", "idiom" : "iphone", "scale" : "2x", "size" : "29x29" }, { + "filename" : "87.png", "idiom" : "iphone", "scale" : "3x", "size" : "29x29" }, { + "filename" : "80.png", "idiom" : "iphone", "scale" : "2x", "size" : "40x40" }, { + "filename" : "120.png", "idiom" : "iphone", "scale" : "3x", "size" : "40x40" }, { + "filename" : "57.png", + "idiom" : "iphone", + "scale" : "1x", + "size" : "57x57" + }, + { + "filename" : "114.png", + "idiom" : "iphone", + "scale" : "2x", + "size" : "57x57" + }, + { + "filename" : "120.png", "idiom" : "iphone", "scale" : "2x", "size" : "60x60" }, { + "filename" : "180.png", "idiom" : "iphone", "scale" : "3x", "size" : "60x60" }, { + "filename" : "20.png", "idiom" : "ipad", "scale" : "1x", "size" : "20x20" }, { + "filename" : "40.png", "idiom" : "ipad", "scale" : "2x", "size" : "20x20" }, { + "filename" : "29.png", "idiom" : "ipad", "scale" : "1x", "size" : "29x29" }, { + "filename" : "58.png", "idiom" : "ipad", "scale" : "2x", "size" : "29x29" }, { + "filename" : "40.png", "idiom" : "ipad", "scale" : "1x", "size" : "40x40" }, { + "filename" : "80.png", "idiom" : "ipad", "scale" : "2x", "size" : "40x40" }, { + "filename" : "50.png", + "idiom" : "ipad", + "scale" : "1x", + "size" : "50x50" + }, + { + "filename" : "100.png", + "idiom" : "ipad", + "scale" : "2x", + "size" : "50x50" + }, + { + "filename" : "72.png", + "idiom" : "ipad", + "scale" : "1x", + "size" : "72x72" + }, + { + "filename" : "144.png", + "idiom" : "ipad", + "scale" : "2x", + "size" : "72x72" + }, + { + "filename" : "76.png", "idiom" : "ipad", "scale" : "1x", "size" : "76x76" }, { + "filename" : "152.png", "idiom" : "ipad", "scale" : "2x", "size" : "76x76" }, { + "filename" : "167.png", "idiom" : "ipad", "scale" : "2x", "size" : "83.5x83.5" }, { + "filename" : "1024.png", "idiom" : "ios-marketing", "scale" : "1x", "size" : "1024x1024" + }, + { + "filename" : "48.png", + "idiom" : "watch", + "role" : "notificationCenter", + "scale" : "2x", + "size" : "24x24", + "subtype" : "38mm" + }, + { + "filename" : "55.png", + "idiom" : "watch", + "role" : "notificationCenter", + "scale" : "2x", + "size" : "27.5x27.5", + "subtype" : "42mm" + }, + { + "filename" : "58.png", + "idiom" : "watch", + "role" : "companionSettings", + "scale" : "2x", + "size" : "29x29" + }, + { + "filename" : "87.png", + "idiom" : "watch", + "role" : "companionSettings", + "scale" : "3x", + "size" : "29x29" + }, + { + "filename" : "80.png", + "idiom" : "watch", + "role" : "appLauncher", + "scale" : "2x", + "size" : "40x40", + "subtype" : "38mm" + }, + { + "filename" : "88.png", + "idiom" : "watch", + "role" : "appLauncher", + "scale" : "2x", + "size" : "44x44", + "subtype" : "40mm" + }, + { + "filename" : "100.png", + "idiom" : "watch", + "role" : "appLauncher", + "scale" : "2x", + "size" : "50x50", + "subtype" : "44mm" + }, + { + "filename" : "172.png", + "idiom" : "watch", + "role" : "quickLook", + "scale" : "2x", + "size" : "86x86", + "subtype" : "38mm" + }, + { + "filename" : "196.png", + "idiom" : "watch", + "role" : "quickLook", + "scale" : "2x", + "size" : "98x98", + "subtype" : "42mm" + }, + { + "filename" : "216.png", + "idiom" : "watch", + "role" : "quickLook", + "scale" : "2x", + "size" : "108x108", + "subtype" : "44mm" + }, + { + "filename" : "1024.png", + "idiom" : "watch-marketing", + "scale" : "1x", + "size" : "1024x1024" + }, + { + "filename" : "16.png", + "idiom" : "mac", + "scale" : "1x", + "size" : "16x16" + }, + { + "filename" : "32.png", + "idiom" : "mac", + "scale" : "2x", + "size" : "16x16" + }, + { + "filename" : "32.png", + "idiom" : "mac", + "scale" : "1x", + "size" : "32x32" + }, + { + "filename" : "64.png", + "idiom" : "mac", + "scale" : "2x", + "size" : "32x32" + }, + { + "filename" : "128.png", + "idiom" : "mac", + "scale" : "1x", + "size" : "128x128" + }, + { + "filename" : "256.png", + "idiom" : "mac", + "scale" : "2x", + "size" : "128x128" + }, + { + "filename" : "256.png", + "idiom" : "mac", + "scale" : "1x", + "size" : "256x256" + }, + { + "filename" : "512.png", + "idiom" : "mac", + "scale" : "2x", + "size" : "256x256" + }, + { + "filename" : "512.png", + "idiom" : "mac", + "scale" : "1x", + "size" : "512x512" + }, + { + "filename" : "1024.png", + "idiom" : "mac", + "scale" : "2x", + "size" : "512x512" } ], "info" : { diff --git a/CareKit Sample/Supporting Files/Assets.xcassets/appstore.imageset/Contents.json b/CareKit Sample/Supporting Files/Assets.xcassets/appstore.imageset/Contents.json new file mode 100644 index 0000000..cfbbc77 --- /dev/null +++ b/CareKit Sample/Supporting Files/Assets.xcassets/appstore.imageset/Contents.json @@ -0,0 +1,23 @@ +{ + "images" : [ + { + "filename" : "appstore.png", + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "appstore@2x.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "appstore@3x.png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/CareKit Sample/Supporting Files/Assets.xcassets/appstore.imageset/appstore.png b/CareKit Sample/Supporting Files/Assets.xcassets/appstore.imageset/appstore.png new file mode 100644 index 0000000..a82d923 Binary files /dev/null and b/CareKit Sample/Supporting Files/Assets.xcassets/appstore.imageset/appstore.png differ diff --git a/CareKit Sample/Supporting Files/Assets.xcassets/appstore.imageset/appstore@2x.png b/CareKit Sample/Supporting Files/Assets.xcassets/appstore.imageset/appstore@2x.png new file mode 100644 index 0000000..fd17c28 Binary files /dev/null and b/CareKit Sample/Supporting Files/Assets.xcassets/appstore.imageset/appstore@2x.png differ diff --git a/CareKit Sample/Supporting Files/Assets.xcassets/appstore.imageset/appstore@3x.png b/CareKit Sample/Supporting Files/Assets.xcassets/appstore.imageset/appstore@3x.png new file mode 100644 index 0000000..e69cdb2 Binary files /dev/null and b/CareKit Sample/Supporting Files/Assets.xcassets/appstore.imageset/appstore@3x.png differ diff --git a/CareKit Sample/Supporting Files/Assets.xcassets/oval.imageset/Contents.json b/CareKit Sample/Supporting Files/Assets.xcassets/oval.imageset/Contents.json new file mode 100644 index 0000000..23c2424 --- /dev/null +++ b/CareKit Sample/Supporting Files/Assets.xcassets/oval.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "filename" : "oval.png", + "idiom" : "universal", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/CareKit Sample/Supporting Files/Assets.xcassets/oval.imageset/oval.png b/CareKit Sample/Supporting Files/Assets.xcassets/oval.imageset/oval.png new file mode 100644 index 0000000..99b097d Binary files /dev/null and b/CareKit Sample/Supporting Files/Assets.xcassets/oval.imageset/oval.png differ diff --git a/CareKit Sample/Supporting Files/Config.plist b/CareKit Sample/Supporting Files/Config.plist new file mode 100644 index 0000000..11462e2 --- /dev/null +++ b/CareKit Sample/Supporting Files/Config.plist @@ -0,0 +1,47 @@ + + + + + Tint Color + #92bcc1 + Login Step Text + Please sign-up with a valid email adress. + Login Step Title + Sign up + Team Name + Personalized skin care assistant and tracker + Study Title + SkinAI + Completion Step Title + All done! + Primary Color + #92bcc1 + Onboarding + + + Title + Data Collection + Description + Upload a photo of your skin and tell us about your goals. Don't worry, your data never leaves your phone and remains private! + Logo + 1️⃣ + + + Title + Machine Learning + Description + Our algorithm will analyze your results and prepare a skincare routine made exclusively for you! + Logo + 2️⃣ + + + Title + Regular Check-ins + Description + Use our bi-weekly survey to let us know about your skin evolution, we'll update your routine to always meet your needs. + Logo + 3️⃣ + + + + diff --git a/CareKit Sample/Supporting Files/Constants.swift b/CareKit Sample/Supporting Files/Constants.swift new file mode 100644 index 0000000..c6aa1bf --- /dev/null +++ b/CareKit Sample/Supporting Files/Constants.swift @@ -0,0 +1,29 @@ +// +// Constants.swift +// Master-Sample +// +// Created by Santiago Gutierrez on 11/5/19. +// Copyright © 2019 Stanford University. All rights reserved. +// + +import Foundation + +class Constants { + + static let prefConfirmedLogin = "PREF_CONFIRMED_LOGIN" + static let prefFirstRunWasMarked = "PREF_FIRST_RUN" + static let prefUserEmail = "PREF_USER_EMAIL" + + static let prefCareKitCoreDataInitDate = "PREF_CORE_DATA_INIT_DATE" + static let prefHealthRecordsLastUploaded = "PREF_HEALTH_LAST_UPLOAD" + + static let notificationUserLogin = "NOTIFICATION_USER_LOGIN" + + static let dataBucketUserDetails = "userDetails" + static let dataBucketSurveys = "surveys" + static let dataBucketHealthKit = "healthKit" + static let dataBucketStorage = "storage" + + static let onboardingDidComplete = "didCompleteOnboarding" + +} diff --git a/CareKit Sample/Supporting Files/GoogleService-Info.plist b/CareKit Sample/Supporting Files/GoogleService-Info.plist deleted file mode 100644 index 00ad5e3..0000000 --- a/CareKit Sample/Supporting Files/GoogleService-Info.plist +++ /dev/null @@ -1,36 +0,0 @@ - - - - - CLIENT_ID - 267563013930-t8ijpatrv3qeb109g30tcorneoejka3u.apps.googleusercontent.com - REVERSED_CLIENT_ID - com.googleusercontent.apps.267563013930-t8ijpatrv3qeb109g30tcorneoejka3u - API_KEY - AIzaSyDZFphOPsibFOO40SdVJfRF-lVgH21riJk - GCM_SENDER_ID - 267563013930 - PLIST_VERSION - 1 - BUNDLE_ID - edu.stanford.cs342.carekit.sample - PROJECT_ID - cs342-master-sample - STORAGE_BUCKET - cs342-master-sample.appspot.com - IS_ADS_ENABLED - - IS_ANALYTICS_ENABLED - - IS_APPINVITE_ENABLED - - IS_GCM_ENABLED - - IS_SIGNIN_ENABLED - - GOOGLE_APP_ID - 1:267563013930:ios:463ed2831c79d697ccb053 - DATABASE_URL - https://cs342-master-sample.firebaseio.com - - \ No newline at end of file diff --git a/CareKit Sample/Supporting Files/Info.plist b/CareKit Sample/Supporting Files/Info.plist index efc211a..eaafa72 100644 --- a/CareKit Sample/Supporting Files/Info.plist +++ b/CareKit Sample/Supporting Files/Info.plist @@ -4,6 +4,8 @@ CFBundleDevelopmentRegion $(DEVELOPMENT_LANGUAGE) + CFBundleDisplayName + SkinAI CFBundleExecutable $(EXECUTABLE_NAME) CFBundleIdentifier @@ -20,6 +22,8 @@ 1 LSRequiresIPhoneOS + NSCameraUsageDescription + This app captures photos and video during certain tasks. UIApplicationSceneManifest UIApplicationSupportsMultipleScenes diff --git a/CareKit Sample/Supporting Files/Launch Screen.storyboard b/CareKit Sample/Supporting Files/Launch Screen.storyboard new file mode 100644 index 0000000..18c5ab2 --- /dev/null +++ b/CareKit Sample/Supporting Files/Launch Screen.storyboard @@ -0,0 +1,51 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/CareKit Sample/Supporting Files/acne_classification.mlmodel b/CareKit Sample/Supporting Files/acne_classification.mlmodel new file mode 100644 index 0000000..630c43f Binary files /dev/null and b/CareKit Sample/Supporting Files/acne_classification.mlmodel differ diff --git a/CareKit Sample/Supporting Files/oval.png b/CareKit Sample/Supporting Files/oval.png new file mode 100644 index 0000000..99b097d Binary files /dev/null and b/CareKit Sample/Supporting Files/oval.png differ diff --git a/CareKit Sample/Tabs/Chart/ChartDataSource.swift b/CareKit Sample/Tabs/Chart/ChartDataSource.swift new file mode 100644 index 0000000..b21375d --- /dev/null +++ b/CareKit Sample/Tabs/Chart/ChartDataSource.swift @@ -0,0 +1,85 @@ +/* +Copyright (c) 2015, James Cox. 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. +*/ + +import ResearchKit + +class LineGraphDataSource: NSObject, ORKValueRangeGraphChartViewDataSource { + + var plotPoints = + [ + [ + ORKValueRange(value: 10), + ORKValueRange(value: 20), + ORKValueRange(value: 25), + ORKValueRange(), + ORKValueRange(value: 30), + ORKValueRange(value: 40) + ], + [ + ORKValueRange(value: 2), + ORKValueRange(value: 4), + ORKValueRange(value: 8), + ORKValueRange(value: 16), + ORKValueRange(value: 32), + ORKValueRange(value: 64) + ] + ] + + func numberOfPlots(in graphChartView: ORKGraphChartView) -> Int { + return plotPoints.count + } + + func graphChartView(_ graphChartView: ORKGraphChartView, dataPointForPointIndex pointIndex: Int, plotIndex: Int) -> ORKValueRange { + return plotPoints[plotIndex][pointIndex] + } + + func graphChartView(_ graphChartView: ORKGraphChartView, numberOfDataPointsForPlotIndex plotIndex: Int) -> Int { + return plotPoints[plotIndex].count + } + + func maximumValue(for graphChartView: ORKGraphChartView) -> Double { + return 70 + } + + func minimumValue(for graphChartView: ORKGraphChartView) -> Double { + return 0 + } + + func graphChartView(_ graphChartView: ORKGraphChartView, titleForXAxisAtPointIndex pointIndex: Int) -> String? { + return "\(pointIndex + 1)" + } + + func graphChartView(_ graphChartView: ORKGraphChartView, drawsPointIndicatorsForPlotIndex plotIndex: Int) -> Bool { + if plotIndex == 1 { + return false + } + return true + } +} diff --git a/CareKit Sample/Tabs/Chart/ChartTableViewCell.swift b/CareKit Sample/Tabs/Chart/ChartTableViewCell.swift new file mode 100644 index 0000000..36efc71 --- /dev/null +++ b/CareKit Sample/Tabs/Chart/ChartTableViewCell.swift @@ -0,0 +1,46 @@ +/* +Copyright (c) 2015, James Cox. 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. +*/ + +import UIKit +import ResearchKit + +class PieChartTableViewCell: UITableViewCell { + @IBOutlet weak var pieChartView: ORKPieChartView! +} + +class GraphChartTableViewCell: UITableViewCell { + @IBOutlet weak var graphView: ORKGraphChartView! +} + +class LineGraphChartTableViewCell: GraphChartTableViewCell { } + +class DiscreteGraphChartTableViewCell: GraphChartTableViewCell { } + +class BarGraphChartTableViewCell: GraphChartTableViewCell { } diff --git a/CareKit Sample/Tabs/Chart/ChartViewController.swift b/CareKit Sample/Tabs/Chart/ChartViewController.swift new file mode 100644 index 0000000..c58b538 --- /dev/null +++ b/CareKit Sample/Tabs/Chart/ChartViewController.swift @@ -0,0 +1,85 @@ +/* +Copyright (c) 2015, James Cox. 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. +*/ + +import UIKit +import ResearchKit + +class ChartViewController: UITableViewController { + + let lineGraphChartDataSource = LineGraphDataSource() + let lineGraphChartIdentifier = "LineGraphChartCell" + var lineGraphChartTableViewCell: LineGraphChartTableViewCell! + var chartTableViewCells: [UITableViewCell]! + + override func viewDidLoad() { + super.viewDidLoad() + + if #available(iOS 13.0, *) { + self.tableView.backgroundColor = UIColor.systemBackground + } + + // ORKLineGraphChartView + lineGraphChartTableViewCell = (tableView.dequeueReusableCell(withIdentifier: lineGraphChartIdentifier) as! LineGraphChartTableViewCell) + let lineGraphChartView = lineGraphChartTableViewCell.graphView as! ORKLineGraphChartView + lineGraphChartView.dataSource = lineGraphChartDataSource + lineGraphChartView.tintColor = UIColor(red: 244 / 255, green: 190 / 255, blue: 74 / 255, alpha: 1) + // Optional custom configuration + lineGraphChartView.showsHorizontalReferenceLines = true + lineGraphChartView.showsVerticalReferenceLines = true + + chartTableViewCells = [lineGraphChartTableViewCell] + + if #available(iOS 13.0, *) { + lineGraphChartView.backgroundColor = UIColor.secondarySystemBackground + } + + tableView.tableFooterView = UIView(frame: CGRect.zero) + } + + override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { + return chartTableViewCells.count + } + + override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { + let cell = chartTableViewCells[(indexPath as NSIndexPath).row] + + if #available(iOS 13.0, *) { + cell.contentView.backgroundColor = UIColor.systemBackground + } + + return cell + } + + override func viewWillAppear(_ animated: Bool) { + super.viewWillAppear(animated) + lineGraphChartTableViewCell.graphView.animate(withDuration: 0.5) + } + +} diff --git a/CareKit Sample/Tabs/Chart/ChartViewControllerRepresentable.swift b/CareKit Sample/Tabs/Chart/ChartViewControllerRepresentable.swift new file mode 100644 index 0000000..ef3471e --- /dev/null +++ b/CareKit Sample/Tabs/Chart/ChartViewControllerRepresentable.swift @@ -0,0 +1,26 @@ +// +// ChartViewControllerRepresentable.swift +// CareKit Sample +// +// Created by Ines Chami on 3/16/21. +// + +import Foundation +import SwiftUI +import UIKit +import CareKit + +struct ChartViewControllerRepresentable: UIViewControllerRepresentable { + + typealias UIViewControllerType = UIViewController + + func updateUIViewController(_ taskViewController: UIViewController, context: Context) {} + func makeUIViewController(context: Context) -> UIViewController { + + let viewController = ChartViewController() + viewController.title = "Charts" + + return UINavigationController(rootViewController: viewController) + } + +} diff --git a/CareKit Sample/Tabs/Chart/LineGraphChartDataSource.swift b/CareKit Sample/Tabs/Chart/LineGraphChartDataSource.swift new file mode 100644 index 0000000..1dd3c32 --- /dev/null +++ b/CareKit Sample/Tabs/Chart/LineGraphChartDataSource.swift @@ -0,0 +1,127 @@ +// +// CoffeeChartDataSource.swift +// CardinalKit_Example +// +// Created for the CardinalKit Framework. +// Copyright © 2019 Stanford University. All rights reserved. +// +import Foundation +import ResearchKit + + +//class LineGraphChartDataSource: NSObject, ORKValueRangeGraphChartViewDataSource { +//// var plotPoints = [Int]() +// var plotPoints = [ +// [ +// ORKValueRange(value: 200), +// ORKValueRange(value: 450), +// ORKValueRange(value: 500), +// ORKValueRange(value: 250), +// ORKValueRange(value: 300), +// ORKValueRange(value: 600), +// ORKValueRange(value: 300), +// ], +// [ +// ORKValueRange(value: 100), +// ORKValueRange(value: 350), +// ORKValueRange(value: 400), +// ORKValueRange(value: 150), +// ORKValueRange(value: 200), +// ORKValueRange(value: 500), +// ORKValueRange(value: 400), +// ] +// ] +// +// override init() { +// if let arr = (UserDefaults.standard.array(forKey: "user_default_array") ?? []) as? [Int] { +//// self.plotPoints = arr +// } +// super.init() +// } +// +// func numberOfPlots(in graphChartView: ORKGraphChartView) -> Int { +// return 1 +// } +// +// func graphChartView(_ graphChartView: ORKGraphChartView, dataPointForPointIndex pointIndex: Int, plotIndex: Int) -> ORKValueRange { +// print(Double(plotPoints[pointIndex])) +// return ORKValueRange(value: Double(plotPoints[pointIndex])) +// } +// +// func graphChartView(_ graphChartView: ORKGraphChartView, numberOfDataPointsForPlotIndex plotIndex: Int) -> Int { +// graphChartView.noDataText = plotPoints.count == 0 ? "No Data" : "" +// return plotPoints.count +// } +// +// func maximumValue(for graphChartView: ORKGraphChartView) -> Double { +// return Double(plotPoints.max() ?? 0) +// } +// +// func minimumValue(for graphChartView: ORKGraphChartView) -> Double { +// return 0 +// } +// +// func graphChartView(_ graphChartView: ORKGraphChartView, titleForXAxisAtPointIndex pointIndex: Int) -> String? { +// return String(self.plotPoints[pointIndex]) +// } +// +// // MARK: - Convenience +//// +//// func pointsAvg() -> Double { +//// if plotPoints.count > 0 { +//// return plotPoints.reduce(0) {$0 + $1} / Double(plotPoints.count) +//// } +//// return 0 +//// } +// +//} + +class LineGraphChartDataSource: NSObject, ORKValueRangeGraphChartViewDataSource { + // MARK: Properties + + var plotPoints = + [ + [ + ORKValueRange(value: 10), + ORKValueRange(value: 20), + ORKValueRange(value: 25), + ORKValueRange(), + ORKValueRange(value: 30), + ORKValueRange(value: 40) + ], + [ + ORKValueRange(value: 2), + ORKValueRange(value: 4), + ORKValueRange(value: 8), + ORKValueRange(value: 16), + ORKValueRange(value: 32), + ORKValueRange(value: 64) + ] + ] + + // MARK: ORKGraphChartViewDataSource + + func numberOfPlots(in graphChartView: ORKGraphChartView) -> Int { + return plotPoints.count + } + + func graphChartView(_ graphChartView: ORKGraphChartView, dataPointForPointIndex pointIndex: Int, plotIndex: Int) -> ORKValueRange { + return plotPoints[plotIndex][pointIndex] + } + + func graphChartView(_ graphChartView: ORKGraphChartView, numberOfDataPointsForPlotIndex plotIndex: Int) -> Int { + return plotPoints[plotIndex].count + } + + func maximumValue(for graphChartView: ORKGraphChartView) -> Double { + return 70 + } + + func minimumValue(for graphChartView: ORKGraphChartView) -> Double { + return 0 + } + + func graphChartView(_ graphChartView: ORKGraphChartView, titleForXAxisAtPointIndex pointIndex: Int) -> String? { + return "\(pointIndex + 1)" + } +} diff --git a/CareKit Sample/Tabs/Chart/LineGraphChartView.swift b/CareKit Sample/Tabs/Chart/LineGraphChartView.swift new file mode 100644 index 0000000..c097abe --- /dev/null +++ b/CareKit Sample/Tabs/Chart/LineGraphChartView.swift @@ -0,0 +1,40 @@ +// +// CoffeePieChartView.swift +// CardinalKit_Example +// +// Created by Santiago Gutierrez on 2/17/21. +// Copyright © 2021 CocoaPods. All rights reserved. +// + +import SwiftUI +import ResearchKit + +struct LineGraphChartView : UIViewRepresentable { + + func makeUIView(context: Context) -> some UIView { +// let config = Config.shared +// let tintColor = config.readColor(query: "Tint Color") + +// let chartView = ORKPieChartView() + let chartView = ORKLineGraphChartView() + chartView.showsHorizontalReferenceLines = true + chartView.showsVerticalReferenceLines = true + +// chartView.tintColor = tintColor +// chartView.showsTitleAboveChart = true +// chartView.title = "Skin progression" +// chartView.text = "Here is you acne score plotted over time." +// chartView.noDataText = "Take your skin diagnosis and come back!" + + let dataSource = LineGraphChartDataSource() + chartView.dataSource = dataSource as ORKValueRangeGraphChartViewDataSource + chartView.animate(withDuration: 1.0) + + return chartView + } + + func updateUIView(_ uiView: UIViewType, context: Context) { + + } + +} diff --git a/CareKit Sample/Tabs/Chart/LineGraphUIView.swift b/CareKit Sample/Tabs/Chart/LineGraphUIView.swift new file mode 100644 index 0000000..8e2aaae --- /dev/null +++ b/CareKit Sample/Tabs/Chart/LineGraphUIView.swift @@ -0,0 +1,32 @@ +// +// CoffeeUIView.swift +// CardinalKit_Example +// +// Created for the CardinalKit Framework. +// Copyright © 2019 Stanford University. All rights reserved. +// + +import SwiftUI +import ResearchKit + +struct LineGraphUIView: View { + + var body: some View { + VStack(spacing: 10) { + +// LineGraphChartView() +// .frame(minWidth: 0, maxWidth: .infinity, minHeight: 0, maxHeight: .infinity) +// .scaledToFit() +// .padding(Metrics.PADDING_HORIZONTAL_MAIN*4) +// + Spacer() + + } + } +} + +struct LineGraphUIView_Previews: PreviewProvider { + static var previews: some View { + LineGraphUIView() + } +} diff --git a/CareKit Sample/Tabs/Config.swift b/CareKit Sample/Tabs/Config.swift new file mode 100644 index 0000000..b6cb629 --- /dev/null +++ b/CareKit Sample/Tabs/Config.swift @@ -0,0 +1,112 @@ +// +// CKPropertyReader.swift +// CardinalKit_Example +// +// Created by Varun Shenoy on 8/1/20. +// Copyright © 2020 CocoaPods. All rights reserved. +// + +import Foundation +import UIKit +import SwiftUI + +public class CKPropertyReader { + + var data: [String: AnyObject] = [:] + + init(file: String) { + + // read input plist file + var propertyListFormat = PropertyListSerialization.PropertyListFormat.xml + let plistPath: String? = Bundle.main.path(forResource: file, ofType: "plist")! + let plistXML = FileManager.default.contents(atPath: plistPath!)! + + // convert plist file into dictionary + do { + self.data = try PropertyListSerialization.propertyList(from: plistXML, options: .mutableContainersAndLeaves, format: &propertyListFormat) as! [String:AnyObject] + + } catch { + print("Error reading plist: \(error), format: \(propertyListFormat)") + } + } + + // read from stored value + func read(query: String) -> String { + return data[query] as! String + } + + func readBool(query: String) -> Bool { + return data[query] as! Bool + } + + func readAny(query: String) -> AnyObject { + return data[query]! + } + + // read from stored dictionary + func readDict(query: String) -> [String:String] { + return data[query] as! [String:String] + } + + subscript(query: String) -> [String: AnyObject] { + return data[query] as! [String: AnyObject] + } + + // read from stored dictionary + func readArray(query: String) -> [String] { + return data[query] as! [String] + } + + // read color from stored dictionary + func readColor(query: String) -> UIColor { + let hex = self.read(query: query) + var cString:String = hex.trimmingCharacters(in: .whitespacesAndNewlines).uppercased() + + if (cString.hasPrefix("#")) { + cString.remove(at: cString.startIndex) + } + + if ((cString.count) != 6) { + return UIColor.gray + } + + var rgbValue:UInt32 = 0 + Scanner(string: cString).scanHexInt32(&rgbValue) + + return UIColor( + red: CGFloat((rgbValue & 0xFF0000) >> 16) / 255.0, + green: CGFloat((rgbValue & 0x00FF00) >> 8) / 255.0, + blue: CGFloat(rgbValue & 0x0000FF) / 255.0, + alpha: CGFloat(1.0) + ) + } +} + +extension UIColor { + var rgba: (red: CGFloat, green: CGFloat, blue: CGFloat, alpha: CGFloat) { + var red: CGFloat = 0 + var green: CGFloat = 0 + var blue: CGFloat = 0 + var alpha: CGFloat = 0 + getRed(&red, green: &green, blue: &blue, alpha: &alpha) + + return (red, green, blue, alpha) + } +} + +extension Color { + init(uiColor: UIColor) { + self.init(red: Double(uiColor.rgba.red), + green: Double(uiColor.rgba.green), + blue: Double(uiColor.rgba.blue), + opacity: Double(uiColor.rgba.alpha)) + } +} + +import Foundation + +class Config : CKPropertyReader { + + static let shared = CKPropertyReader(file: "Config") + +} diff --git a/CareKit Sample/Tabs/ContentView.swift b/CareKit Sample/Tabs/ContentView.swift index 41f1aef..2cf2014 100644 --- a/CareKit Sample/Tabs/ContentView.swift +++ b/CareKit Sample/Tabs/ContentView.swift @@ -6,20 +6,46 @@ // import SwiftUI +import SwiftUICharts struct ContentView: View { + let color: Color + let config = Config.shared + + init() { + self.color = Color(config.readColor(query: "Primary Color")) + } + var body: some View { TabView { - ScheduleViewControllerRepresentable().tabItem { - Image("tab_schedule").renderingMode(.template) - Text("Schedule") - } - CareTeamViewControllerRepresentable().tabItem { - Image("tab_care").renderingMode(.template) - Text("Contact") - } + ScheduleViewControllerRepresentable() + .tabItem { + Image("tab_schedule").renderingMode(.template) + Text("Schedule") + } + + if let arr = (UserDefaults.standard.array(forKey: "user_default_array") ?? []) as? [Double] { + LineView(data: arr, + title: "Line chart", + legend: "Full screen") + .padding(10) + .tabItem { + Image("tab_tasks").renderingMode(.template) + Text("Chart") + } + } + + + + + CareTeamViewControllerRepresentable() + .tabItem { + Image("tab_profile").renderingMode(.template) + Text("Contact") + } } + .accentColor(self.color) } } diff --git a/CareKit Sample/Tabs/LaunchUIView.swift b/CareKit Sample/Tabs/LaunchUIView.swift new file mode 100644 index 0000000..bcd8755 --- /dev/null +++ b/CareKit Sample/Tabs/LaunchUIView.swift @@ -0,0 +1,53 @@ +// +// OnboardingUIView.swift +// CardinalKit_Example +// +// Created by Varun Shenoy on 8/14/20. +// Copyright © 2020 Stanford University. All rights reserved. +// + +import SwiftUI +import UIKit +import ResearchKit +import Firebase + +struct LaunchUIView: View { + + @State var didCompleteOnboarding = false + + init() { + + } + + var body: some View { + VStack(spacing: 10) { + if didCompleteOnboarding { + ContentView() + } else { + OnboardingUIView() { + //on complete + if let completed = UserDefaults.standard.object(forKey: Constants.onboardingDidComplete) as? Bool { + self.didCompleteOnboarding = completed + } + } + } + }.onAppear(perform: { + if let completed = UserDefaults.standard.object(forKey: Constants.onboardingDidComplete) as? Bool { + self.didCompleteOnboarding = completed + } + }).onReceive(NotificationCenter.default.publisher(for: NSNotification.Name(Constants.onboardingDidComplete))) { notification in + if let newValue = notification.object as? Bool { + self.didCompleteOnboarding = newValue + } else if let completed = UserDefaults.standard.object(forKey: Constants.onboardingDidComplete) as? Bool { + self.didCompleteOnboarding = completed + } + } + + } +} + +struct LaunchUIView_Previews: PreviewProvider { + static var previews: some View { + LaunchUIView() + } +} diff --git a/CareKit Sample/Tabs/Metrics.swift b/CareKit Sample/Tabs/Metrics.swift new file mode 100644 index 0000000..6eb035b --- /dev/null +++ b/CareKit Sample/Tabs/Metrics.swift @@ -0,0 +1,20 @@ +// +// Metrics.swift +// CardinalKit_Example +// +// Created by Santiago Gutierrez on 12/22/20. +// Copyright © 2020 CocoaPods. All rights reserved. +// + +import Foundation + +class Metrics { + + static let PADDING_HORIZONTAL_MAIN: CGFloat = 15 + static let PADDING_VERTICAL_MAIN: CGFloat = 15 + + static let PADDING_BUTTON_LABEL: CGFloat = 15 + + static let RADIUS_CORNER_BUTTON: CGFloat = 15 + +} diff --git a/CareKit Sample/Tabs/Onboarding/OnboardingSurveyViewController.swift b/CareKit Sample/Tabs/Onboarding/OnboardingSurveyViewController.swift new file mode 100644 index 0000000..cff3d0a --- /dev/null +++ b/CareKit Sample/Tabs/Onboarding/OnboardingSurveyViewController.swift @@ -0,0 +1,172 @@ +// +// OnboardingViewController.swift +// CardinalKit_Example +// +// Created by Santiago Gutierrez on 10/12/20. +// Copyright © 2020 CocoaPods. All rights reserved. +// + +import SwiftUI +import UIKit +import ResearchKit +import Firebase + +struct OnboardingSurveyViewController: UIViewControllerRepresentable { + + func makeCoordinator() -> OnboardingViewCoordinator { + OnboardingViewCoordinator() + } + + typealias UIViewControllerType = ORKTaskViewController + + func updateUIViewController(_ taskViewController: ORKTaskViewController, context: Context) {} + func makeUIViewController(context: Context) -> ORKTaskViewController { + +// let config = CKPropertyReader(file: "CKConfiguration") + + /* ************************************************************** + * STEP (1): get user consent + **************************************************************/ + // use the `ORKVisualConsentStep` from ResearchKit +// let consentDocument = ConsentDocument() +// let consentStep = ORKVisualConsentStep(identifier: "VisualConsentStep", document: consentDocument) + + /* ************************************************************** + * STEP (2): ask user to review and sign consent document + **************************************************************/ + // use the `ORKConsentReviewStep` from ResearchKit +// let signature = consentDocument.signatures?.first +// let reviewConsentStep = ORKConsentReviewStep(identifier: "ConsentReviewStep", signature: signature, in: consentDocument) +// reviewConsentStep.text = config.read(query: "Review Consent Step Text") +// reviewConsentStep.reasonForConsent = config.read(query: "Reason for Consent Text") +// +// /* ************************************************************** +// * STEP (3): get permission to collect HealthKit data +// **************************************************************/ +// // see `HealthDataStep` to configure! +// let healthDataStep = CKHealthDataStep(identifier: "Healthkit") +// +// /* ************************************************************** +// * STEP (3.5): get permission to collect HealthKit health records data +// **************************************************************/ +// let healthRecordsStep = CKHealthRecordsStep(identifier: "HealthRecords") +// +// /* ************************************************************** +// * STEP (4): ask user to enter their email address for login +// **************************************************************/ +// // the `LoginStep` collects and email address, and +// // the `LoginCustomWaitStep` waits for email verification. + + var loginSteps: [ORKStep] + let loginStep = PasswordlessLoginStep(identifier: PasswordlessLoginStep.identifier) +// let loginVerificationStep = LoginCustomWaitStep(identifier: LoginCustomWaitStep.identifier) + + loginSteps = [loginStep] + + /* ************************************************************** + * STEP (5): ask the user to create a security passcode + * that will be required to use this app! + **************************************************************/ + // use the `ORKPasscodeStep` from ResearchKit. +// let passcodeStep = ORKPasscodeStep(identifier: "Passcode") //NOTE: requires NSFaceIDUsageDescription in info.plist +// let type = config.read(query: "Passcode Type") +// if type == "6" { +// passcodeStep.passcodeType = .type6Digit +// } else { +// passcodeStep.passcodeType = .type4Digit +// } +// passcodeStep.text = config.read(query: "Passcode Text") +// + /* ************************************************************** + * STEP (6): inform the user that they are done with sign-up! + **************************************************************/ + // use the `ORKCompletionStep` from ResearchKit +// let completionStep = ORKCompletionStep(identifier: "CompletionStep") +// completionStep.title = config.read(query: "Completion Step Title") +// completionStep.text = config.read(query: "Completion Step Text") + + /* ************************************************************** + * finally, CREATE an array with the steps to show the user + **************************************************************/ + + // given intro steps that the user should review and consent to +// let introSteps: [ORKStep] = [consentStep, reviewConsentStep] + + // and steps regarding login / security + let emailVerificationSteps = loginSteps // + [passcodeStep] + + var onbardingSurveySteps = [ORKStep]() + let stringAnswerFormat = ORKTextAnswerFormat() + let numberAnswerFormat = ORKNumericAnswerFormat(style: .integer, unit: nil, minimum: 18 as NSNumber, maximum: 100 as NSNumber) + + // Question 1 is getting name, email, and age + let AboutYouFormItem = ORKFormItem(sectionTitle: "Personal information") + let firstNameFormItem = ORKFormItem(identifier: "RegistrationForm-FirstName", text: "First Name", answerFormat: stringAnswerFormat) + let lastNameFormItem = ORKFormItem(identifier: "RegistrationForm-LastName", text: "Last Name", answerFormat: stringAnswerFormat) + let ageFromItem = ORKFormItem(identifier: "RegistrationForm-Age", text: "Age", answerFormat: numberAnswerFormat) + + // registration form + let formStep = ORKFormStep(identifier: "RegistrationForm", title: "About you", text: "Before we get started, tell us a little bit about yourself") + formStep.formItems = [AboutYouFormItem, firstNameFormItem, lastNameFormItem, ageFromItem] + onbardingSurveySteps += [formStep] + + // Question 4 is asking about allergies + let booleanAnswer = ORKBooleanAnswerFormat(yesString: "Yes", noString: "No") + let booleanStep = ORKQuestionStep(identifier: "Allergies-Boolean", title: "Allergies", question: "Do you have any allergies?", answer: booleanAnswer) + booleanStep.isOptional = true + onbardingSurveySteps += [booleanStep] + + // if yes then we ask the user to enter them in a text box + let allergiesAnswerFormat = ORKTextAnswerFormat(maximumLength: 200) + allergiesAnswerFormat.multipleLines = true + let allergiesQuestionStep = ORKQuestionStep(identifier: "AllergiesQuestionStep", title: "Allergies", question: "Please describe your allergies.", answer: allergiesAnswerFormat) + onbardingSurveySteps += [allergiesQuestionStep] + + // Question 2 is asking about skin type (oily, combination, dry) + let skinTypes = [ + ORKTextChoice(text: "Oily", value: 0 as NSNumber), + ORKTextChoice(text: "Combination", value: 1 as NSNumber), + ORKTextChoice(text: "Dry", value: 2 as NSNumber) + ] + let skinTypeAnswerFormat: ORKTextChoiceAnswerFormat = ORKAnswerFormat.choiceAnswerFormat(with: .singleChoice, textChoices: skinTypes) + let skinTypeQuestionStep = ORKQuestionStep(identifier: "SkinTypeQuestionStep", title: "Skin Type", question: "How would you describe your skin?", answer: skinTypeAnswerFormat) + onbardingSurveySteps += [skinTypeQuestionStep] + + // Question 3 is aking about main concern (clogged pores, control breakouts, acne scars) + let skinIssues = [ + ORKTextChoice(text: "Control breakouts", value: 0 as NSNumber), + ORKTextChoice(text: "Clean clogged pores", value: 1 as NSNumber), + ORKTextChoice(text: "Reduce acne scars", value: 2 as NSNumber), + ORKTextChoice(text: "Fade dark spots", value: 3 as NSNumber), + ORKTextChoice(text: "Fight wrinkles", value: 4 as NSNumber), + ORKTextChoice(text: "Reduce fine lines", value: 5 as NSNumber), + ] + let skinConcernAnswerFormat: ORKTextChoiceAnswerFormat = ORKAnswerFormat.choiceAnswerFormat(with: .multipleChoice, textChoices: skinIssues) + let skinIssuesQuestionStep = ORKQuestionStep(identifier: "SkinIssuesQuestionStep", title: "Goals", question: "What would you like to improve about your skin?", answer: skinConcernAnswerFormat) + onbardingSurveySteps += [skinIssuesQuestionStep] + + // guide the user through ALL steps + let fullSteps = emailVerificationSteps + onbardingSurveySteps + + /* ************************************************************** + * and SHOW the user these steps! + **************************************************************/ + // create navigable rule for allergy question + let resultBooleanSelector = ORKResultSelector(resultIdentifier: booleanStep.identifier) + let predicate = ORKResultPredicate.predicateForBooleanQuestionResult(with: resultBooleanSelector, expectedAnswer: false) + let navigableRule = ORKPredicateStepNavigationRule(resultPredicatesAndDestinationStepIdentifiers: [(resultPredicate: predicate, destinationStepIdentifier: skinTypeQuestionStep.identifier)]) + + // create a task with each step + let orderedTask = ORKNavigableOrderedTask(identifier: "StudyOnboardingTask", steps: fullSteps) + orderedTask.setNavigationRule(navigableRule, forTriggerStepIdentifier: booleanStep.identifier) + + // wrap that task on a view controller + let taskViewController = ORKTaskViewController(task: orderedTask, taskRun: nil) + taskViewController.delegate = context.coordinator // enables `ORKTaskViewControllerDelegate` below + + taskViewController.view.tintColor = Config.shared.readColor(query: "Primary Color") + // & present the VC! + return taskViewController + } + +} diff --git a/CareKit Sample/Tabs/Onboarding/OnboardingUIView.swift b/CareKit Sample/Tabs/Onboarding/OnboardingUIView.swift new file mode 100644 index 0000000..8ad73c7 --- /dev/null +++ b/CareKit Sample/Tabs/Onboarding/OnboardingUIView.swift @@ -0,0 +1,114 @@ +// +// OnboardingUIView.swift +// CardinalKit_Example +// +// Created by Varun Shenoy on 8/14/20. +// Copyright © 2020 Stanford University. All rights reserved. +// +import SwiftUI +import UIKit +import ResearchKit +import Firebase + +struct OnboardingElement { + let logo: String + let title: String + let description: String +} + +struct OnboardingUIView: View { + @State private var foreground = Color.gray + var onboardingElements: [OnboardingElement] = [] + let color: Color + let config = Config.shared + + @State var showingDetail = false + var onComplete: (() -> Void)? = nil + init(onComplete: (() -> Void)? = nil) { + let onboardingData = config.readAny(query: "Onboarding") as! [[String:String]] + self.color = Color(config.readColor(query: "Primary Color")) + self.onComplete = onComplete + for data in onboardingData { + self.onboardingElements.append(OnboardingElement(logo: data["Logo"]!, title: data["Title"]!, description: data["Description"]!)) + } + } + var body: some View { + VStack(spacing: 10) { + Spacer() +// Image("Hygia-Logo") +// .resizable() +// .scaledToFit() +// .padding(.leading, Metrics.PADDING_HORIZONTAL_MAIN*4) +// .padding(.trailing, Metrics.PADDING_HORIZONTAL_MAIN*4) + Spacer(minLength: 2) + Text(config.read(query: "Study Title")) + .foregroundColor(self.color) + .multilineTextAlignment(.center) + .font(.system(size: 40, weight: .bold, design: .default)) + .padding(.leading, Metrics.PADDING_HORIZONTAL_MAIN) + .padding(.trailing, Metrics.PADDING_HORIZONTAL_MAIN) + Text(config.read(query: "Team Name")) + .foregroundColor(foreground) + .padding(.leading, Metrics.PADDING_HORIZONTAL_MAIN) + .padding(.trailing, Metrics.PADDING_HORIZONTAL_MAIN) + PageView(self.onboardingElements.map { InfoView(logo: $0.logo, title: $0.title, description: $0.description, color: self.color) }) + Spacer() + HStack { + Spacer() + Button(action: { + self.showingDetail.toggle() + }, label: { + Text("Get Started") + .padding(Metrics.PADDING_BUTTON_LABEL) + .frame(maxWidth: .infinity) + .frame(width: .infinity, height: 80, alignment: .center) + .foregroundColor(.white) + .background(self.color) + .cornerRadius(Metrics.RADIUS_CORNER_BUTTON) + .font(.system(size: 20, weight: .bold, design: .default)) + }) + .padding(.leading, Metrics.PADDING_HORIZONTAL_MAIN) + .padding(.trailing, Metrics.PADDING_HORIZONTAL_MAIN) + .sheet(isPresented: $showingDetail, onDismiss: { + self.onComplete?() + }, content: { + OnboardingSurveyViewController() + }) + Spacer() + } + Spacer() + } + } +} + +struct InfoView: View { + let logo: String + let title: String + let description: String + let color: Color + var body: some View { + VStack(spacing: 10) { + Circle() + .fill(color) + .frame(width: 100, height: 100, alignment: .center) + .padding(6) + .overlay( + Text(logo) + .foregroundColor(.white) + .font(.system(size: 42, weight: .light, design: .default)) + ) + Text(title).font(.title) + Text(description) + .font(.body) + .multilineTextAlignment(.center) + .padding(.leading, 40) + .padding(.trailing, 40) + } + } +} + +struct OnboardingUIView_Previews: PreviewProvider { + static var previews: some View { + OnboardingUIView() + } +} diff --git a/CareKit Sample/Tabs/Onboarding/OnboardingViewCoordinator.swift b/CareKit Sample/Tabs/Onboarding/OnboardingViewCoordinator.swift new file mode 100644 index 0000000..3d37fab --- /dev/null +++ b/CareKit Sample/Tabs/Onboarding/OnboardingViewCoordinator.swift @@ -0,0 +1,190 @@ +// +// OnboardingViewController+Coordinator.swift +// CardinalKit_Example +// +// Created by Santiago Gutierrez on 10/12/20. +// Copyright © 2020 CocoaPods. All rights reserved. +// + +import ResearchKit +import Firebase + +class OnboardingViewCoordinator: NSObject, ORKTaskViewControllerDelegate { + + public func taskViewController(_ taskViewController: ORKTaskViewController, didFinishWith reason: ORKTaskViewControllerFinishReason, error: Error?) { + switch reason { + case .completed: + // if we completed the onboarding task view controller, go to study. + // performSegue(withIdentifier: "unwindToStudy", sender: nil) + + // TODO: where to go next? + // trigger "Studies UI" + UserDefaults.standard.set(true, forKey: Constants.onboardingDidComplete) + +// if let signatureResult = taskViewController.result.stepResult(forStepIdentifier: "ConsentReviewStep")?.results?.first as? ORKConsentSignatureResult { +// +// let consentDocument = ConsentDocument() +// signatureResult.apply(to: consentDocument) +// +// consentDocument.makePDF { (data, error) -> Void in +// +// let config = CKPropertyReader(file: "CKConfiguration") +// +// var docURL = (FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)).last as NSURL? +// docURL = docURL?.appendingPathComponent("\(config.read(query: "Consent File Name")).pdf") as NSURL? +// +// +// do { +// let url = docURL! as URL +// try data?.write(to: url) +// +// UserDefaults.standard.set(url.path, forKey: "consentFormURL") +// print(url.path) +// +// } catch let error { +// +// print(error.localizedDescription) +// } +// } +// } + + + print("Login successful! task: \(taskViewController.task?.identifier ?? "(no ID)")") + + fallthrough + default: + // otherwise dismiss onboarding without proceeding. + taskViewController.dismiss(animated: true, completion: nil) + } + } + + func taskViewController(_ taskViewController: ORKTaskViewController, stepViewControllerWillAppear stepViewController: ORKStepViewController) { + + // MARK: - Advanced Concepts + // Sometimes we might want some custom logic + // to run when a step appears 🎩 + + if stepViewController.step?.identifier == PasswordlessLoginStep.identifier { + + /* ************************************************************** + * When the login step appears, asking for the patient's email + **************************************************************/ + if let _ = CKStudyUser.shared.currentUser?.email { + // if we already have an email, go forward and continue. + DispatchQueue.main.async { + stepViewController.goForward() + } + } + + } else if (stepViewController.step?.identifier == "RegistrationStep") { + + if let _ = CKStudyUser.shared.currentUser?.email { + // if we already have an email, go forward and continue. + DispatchQueue.main.async { + stepViewController.goForward() + } + } + + } else if (stepViewController.step?.identifier == "LoginStep") { + + if let _ = CKStudyUser.shared.currentUser?.email { + // good — we have an email! + } else { + let alert = UIAlertController(title: nil, message: "Creating account...", preferredStyle: .alert) + + let loadingIndicator = UIActivityIndicatorView(frame: CGRect(x: 10, y: 5, width: 50, height: 50)) + loadingIndicator.hidesWhenStopped = true + loadingIndicator.style = UIActivityIndicatorView.Style.medium + loadingIndicator.startAnimating(); + + alert.view.addSubview(loadingIndicator) + taskViewController.present(alert, animated: true, completion: nil) + + let stepResult = taskViewController.result.stepResult(forStepIdentifier: "RegistrationStep") + if let emailRes = stepResult?.results?.first as? ORKTextQuestionResult, let email = emailRes.textAnswer { + if let passwordRes = stepResult?.results?[1] as? ORKTextQuestionResult, let pass = passwordRes.textAnswer { + Auth.auth().createUser(withEmail: email, password: pass) { (res, error) in + DispatchQueue.main.async { + if error != nil { + alert.dismiss(animated: true, completion: nil) + if let errCode = AuthErrorCode(rawValue: error!._code) { + + switch errCode { + default: + let alert = UIAlertController(title: "Registration Error!", message: error?.localizedDescription, preferredStyle: .alert) + alert.addAction(UIAlertAction(title: "Ok", style: .cancel, handler: nil)) + + taskViewController.present(alert, animated: true) + } + } + + stepViewController.goBackward() + + } else { + alert.dismiss(animated: true, completion: nil) + print("Created user!") + } + } + } + + } + } + } + } +// else if stepViewController.step?.identifier == LoginCustomWaitStep.identifier { +// +// /* ************************************************************** +// * When the email verification step appears, send email in background! +// **************************************************************/ +// +// let stepResult = taskViewController.result.stepResult(forStepIdentifier: PasswordlessLoginStep.identifier) +// if let emailRes = stepResult?.results?.first as? ORKTextQuestionResult, let email = emailRes.textAnswer { +// +// // if we received a valid email +// CKStudyUser.shared.sendLoginLink(email: email) { (success) in +// // send a login link +// guard success else { +// // and react accordingly if we ran into an error. +// DispatchQueue.main.async { +// let config = CKPropertyReader(file: "CKConfiguration") +// +// Alerts.showInfo(title: config.read(query: "Failed Login Title"), message: config.read(query: "Failed Login Text")) +// stepViewController.goBackward() +// } +// return +// } +// +// CKStudyUser.shared.email = email +// } +// +// } +// +// } + + } + +// func taskViewController(_ taskViewController: ORKTaskViewController, viewControllerFor step: ORKStep) -> ORKStepViewController? { +// +// // MARK: - Advanced Concepts +// // Overriding the view controller of an ORKStep +// // lets us run our own code on top of what +// // ResearchKit already provides! +// +// switch step { +// case is CKHealthDataStep: +// // this step lets us run custom logic to ask for +// // HealthKit permissins when this step appears on screen. +// return CKHealthDataStepViewController(step: step) +// case is CKHealthRecordsStep: +// return CKHealthRecordsStepViewController(step: step) +// case is LoginCustomWaitStep: +// // run custom code to send an email for login! +// return LoginCustomWaitStepViewController(step: step) +// case is CKSignInWithAppleStep: +// // handle Sign in with Apple +// return CKSignInWithAppleStepViewController(step: step) +// default: +// return nil +// } +// } +} diff --git a/CareKit Sample/Tabs/Onboarding/PageViewController.swift b/CareKit Sample/Tabs/Onboarding/PageViewController.swift new file mode 100644 index 0000000..2e9f48b --- /dev/null +++ b/CareKit Sample/Tabs/Onboarding/PageViewController.swift @@ -0,0 +1,127 @@ +// +// PageViewController.swift +// CardinalKit_Example +// +// Created by Santiago Gutierrez on 10/12/20. +// Copyright © 2020 CocoaPods. All rights reserved. +// + +import UIKit +import SwiftUI + +struct PageControl: UIViewRepresentable { + var numberOfPages: Int + @Binding var currentPage: Int + func makeCoordinator() -> Coordinator { + Coordinator(self) + } + + func makeUIView(context: Context) -> UIPageControl { + let control = UIPageControl() + let config = Config.shared + control.numberOfPages = numberOfPages + control.pageIndicatorTintColor = UIColor.lightGray + control.currentPageIndicatorTintColor = config.readColor(query: "Primary Color") + control.addTarget( + context.coordinator, + action: #selector(Coordinator.updateCurrentPage(sender:)), + for: .valueChanged) + + return control + } + + func updateUIView(_ uiView: UIPageControl, context: Context) { + uiView.currentPage = currentPage + } + + class Coordinator: NSObject { + var control: PageControl + + init(_ control: PageControl) { + self.control = control + } + @objc + func updateCurrentPage(sender: UIPageControl) { + control.currentPage = sender.currentPage + } + } +} + +struct PageView: View { + var viewControllers: [UIHostingController] + @State var currentPage = 0 + init(_ views: [Page]) { + self.viewControllers = views.map { UIHostingController(rootView: $0) } + } + + var body: some View { + ZStack(alignment: .bottom) { + PageViewController(controllers: viewControllers, currentPage: $currentPage) + PageControl(numberOfPages: viewControllers.count, currentPage: $currentPage) + } + } +} + +struct PageViewController: UIViewControllerRepresentable { + var controllers: [UIViewController] + @Binding var currentPage: Int + + func makeCoordinator() -> Coordinator { + Coordinator(self) + } + + func makeUIViewController(context: Context) -> UIPageViewController { + let pageViewController = UIPageViewController( + transitionStyle: .scroll, + navigationOrientation: .horizontal) + pageViewController.dataSource = context.coordinator + pageViewController.delegate = context.coordinator + + return pageViewController + } + + func updateUIViewController(_ pageViewController: UIPageViewController, context: Context) { + pageViewController.setViewControllers( + [self.controllers[self.currentPage]], direction: .forward, animated: true) + } + + class Coordinator: NSObject, UIPageViewControllerDataSource, UIPageViewControllerDelegate { + var parent: PageViewController + + init(_ pageViewController: PageViewController) { + self.parent = pageViewController + } + + func pageViewController( + _ pageViewController: UIPageViewController, + viewControllerBefore viewController: UIViewController) -> UIViewController? { + guard let index = parent.controllers.firstIndex(of: viewController) else { + return nil + } + if index == 0 { + return parent.controllers.last + } + return parent.controllers[index - 1] + } + + func pageViewController( + _ pageViewController: UIPageViewController, + viewControllerAfter viewController: UIViewController) -> UIViewController? { + guard let index = parent.controllers.firstIndex(of: viewController) else { + return nil + } + if index + 1 == parent.controllers.count { + return parent.controllers.first + } + return parent.controllers[index + 1] + } + + func pageViewController(_ pageViewController: UIPageViewController, didFinishAnimating finished: Bool, previousViewControllers: [UIViewController], transitionCompleted completed: Bool) { + if completed, + let visibleViewController = pageViewController.viewControllers?.first, + let index = parent.controllers.firstIndex(of: visibleViewController) { + parent.currentPage = index + } + } + } +} diff --git a/CareKit Sample/Tabs/Onboarding/PasswordlessLoginStep.swift b/CareKit Sample/Tabs/Onboarding/PasswordlessLoginStep.swift new file mode 100644 index 0000000..ea2aeb5 --- /dev/null +++ b/CareKit Sample/Tabs/Onboarding/PasswordlessLoginStep.swift @@ -0,0 +1,48 @@ +// +// LoginStep.swift +// +// Created for the CardinalKit Framework. +// Copyright © 2019 Stanford University. All rights reserved. +// + +import ResearchKit + +class PasswordlessLoginStep: ORKFormStep { + + static let identifier = "Login" + + static let idStepIdentifier = "IdStep" + static let idConfirmStepIdentifier = "ConfirmIdStep" + + override init(identifier: String) { + super.init(identifier: identifier) + + let config = Config.shared + + title = NSLocalizedString(config.read(query: "Login Step Title"), comment: "") + text = NSLocalizedString(config.read(query: "Login Step Text"), comment: "") + + formItems = createFormItems() + isOptional = false + } + + required init(coder aDecoder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + /** + This function creates a form with the exact email question to ask. + + - Returns a `ORKFormItem` array with questions to show. + */ + fileprivate func createFormItems() -> [ORKFormItem] { + let idStepTitle = "Email:" + + let titleStep = ORKFormItem(sectionTitle: "✉️ 🌎") + + let idQuestionStep = ORKFormItem(identifier: PasswordlessLoginStep.idStepIdentifier, text: idStepTitle, answerFormat: ORKEmailAnswerFormat(), optional: false) + + return [titleStep, idQuestionStep] + } + +} diff --git a/CareKit Sample/Tabs/Schedule/AcneModelViewController.swift b/CareKit Sample/Tabs/Schedule/AcneModelViewController.swift new file mode 100644 index 0000000..bbfc1ce --- /dev/null +++ b/CareKit Sample/Tabs/Schedule/AcneModelViewController.swift @@ -0,0 +1,159 @@ +// +// SkinAIViewController.swift +// CareKit Sample +// +// Created by Ines Chami on 3/1/21. +// +import UIKit +import Vision +import CoreML +import ResearchKit +import Foundation +import CareKit +import CareKitUI +import CareKitStore + +class AcneModelViewController: OCKInstructionsTaskViewController, UIImagePickerControllerDelegate & UINavigationControllerDelegate, ORKTaskViewControllerDelegate { + var image: UIImage! + var acneLevel: String = "" + // 2. This method is called when the use taps the button! + override func taskView(_ taskView: UIView & OCKTaskDisplayable, didCompleteEvent isComplete: Bool, at indexPath: IndexPath, sender: Any?) { + // Do any additional setup after loading the view. + let vc = UIImagePickerController() + vc.sourceType = .camera + vc.allowsEditing = true + vc.delegate = self + present(vc, animated: true) + } + /* + // MARK: - Navigation + // In a storyboard-based application, you will often want to do a little preparation before navigation + override func prepare(for segue: UIStoryboardSegue, sender: Any?) { + // Get the new view controller using segue.destination. + // Pass the selected object to the new view controller. + } + */ + func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey : Any]) { + picker.dismiss(animated: true) + guard let image = info[.editedImage] as? UIImage else { + print("No image found") + return + } + // print out the image size as a test + print(image.size) + self.image = image + detectImageContent() + } + func detectImageContent() { + guard let model = try? VNCoreMLModel(for: acne_classification().model) else { + fatalError("Failed to load model") + } + // Create a vision request + let request = VNCoreMLRequest(model: model) {[weak self] request, error in + guard let results = request.results as? [VNClassificationObservation], + let topResult = results.first + else { + fatalError("Unexpected results") + } + // UserDefaults.standard.set(topResult.identifier, forKey: "acneLevel") + self?.acneLevel = topResult.identifier + var prediction_int = Int() + // Update the Main UI Thread with our result + DispatchQueue.main.async { [weak self] in + print("acne level: \(String(describing: self?.acneLevel))") + var user_default_array = [Int]() + if UserDefaults.standard.array(forKey: "user_default_array") == nil { + UserDefaults.standard.setValue(user_default_array, forKey: "user_default_array") + } + if String(describing: self!.acneLevel) == "mild" { + prediction_int = 1 + user_default_array.append(prediction_int) + var user_array = UserDefaults.standard.array(forKey: "user_default_array") + user_array?.append(prediction_int) + UserDefaults.standard.setValue(user_array, forKey: "user_default_array") + } + if String(describing: self!.acneLevel) == "moderate" { + prediction_int = 2 + user_default_array.append(prediction_int) + var user_array = UserDefaults.standard.array(forKey: "user_default_array") + user_array?.append(prediction_int) + UserDefaults.standard.setValue(user_array, forKey: "user_default_array") + } + if String(describing: self!.acneLevel) == "severe" { + prediction_int = 3 + user_default_array.append(prediction_int) + var user_array = UserDefaults.standard.array(forKey: "user_default_array") + user_array?.append(prediction_int) + UserDefaults.standard.setValue(user_array, forKey: "user_default_array") + } + if String(describing: self!.acneLevel) == "very severe" { + prediction_int = 4 + user_default_array.append(prediction_int) + var user_array = UserDefaults.standard.array(forKey: "user_default_array") + user_array?.append(prediction_int) + UserDefaults.standard.setValue(user_array, forKey: "user_default_array") + } + let alert = UIAlertController(title: "Results", message: String(describing: self!.acneLevel), preferredStyle: .alert) + alert.addAction(UIAlertAction(title: "OK", style: .default, handler: { action in + switch action.style{ + case .default: + print("default") + case .cancel: + print("cancel") + case .destructive: + print("destructive") + }})) + self?.present(alert, animated: true, completion: nil) +// DispatchQueue.main.asyncAfter(deadline: .now() + 1) { +// // Show result on screen here using identifier +// print("\(topResult.identifier) with \(Int(topResult.confidence * 100))% confidence") +// } + } + } + guard let ciImage = CIImage(image: self.image!) + else { fatalError("Cant create CIImage from UIImage") } + let handler = VNImageRequestHandler(ciImage: ciImage) + DispatchQueue.global().async { + do { + try handler.perform([request]) + } catch { + print(error) + } + } + } + func taskViewController(_ taskViewController: ORKTaskViewController, didFinishWith reason: ORKTaskViewControllerFinishReason, error: Error?) { + taskViewController.dismiss(animated: true, completion: nil) + guard reason == .completed else { + taskView.completionButton.isSelected = false + return + } + // 4a. Retrieve the result from the ResearchKit survey +// if let savedData = UserDefaults.standard.string(forKey: "acneLevel") { +// self.acneLevel = savedData +// } + // 4b. Save the result into CareKit's store + controller.appendOutcomeValue(value: self.acneLevel, at: IndexPath(item: 0, section: 0), completion: nil) + let gcpDelegate = CKUploadToGCPTaskViewControllerDelegate() + gcpDelegate.taskViewController(taskViewController, didFinishWith: reason, error: error) + } +} +class SkinAIViewSynchronizer: OCKInstructionsTaskViewSynchronizer { + // Customize the initial state of the view + override func makeView() -> OCKInstructionsTaskView { + let instructionsView = super.makeView() + instructionsView.completionButton.label.text = "Start" + return instructionsView + } + override func updateView(_ view: OCKInstructionsTaskView, context: OCKSynchronizationContext) { + super.updateView(view, context: context) + // Check if an answer exists or not and set the detail label accordingly + let element: [OCKAnyEvent]? = context.viewModel.first + let firstEvent = element?.first + view.headerView.titleLabel.text = "Skin Diagnosis" + if let acneLevel = firstEvent?.outcome?.values.first { + view.headerView.detailLabel.text = "Thank you for completing your skin diagnosis." + } else { + view.headerView.detailLabel.text = "Please take your skin diagnosis so we can monitor you skin progession." + } + } +} diff --git a/CareKit Sample/Tabs/Schedule/ModelViewController.swift b/CareKit Sample/Tabs/Schedule/ModelViewController.swift new file mode 100644 index 0000000..31ae377 --- /dev/null +++ b/CareKit Sample/Tabs/Schedule/ModelViewController.swift @@ -0,0 +1,15 @@ +// +// ModelViewController.swift +// CareKit Sample +// +// Created by Ines Chami on 3/2/21. +// + +import Foundation + +class ModelViewController: NSObject { + + func run() { + + } +} diff --git a/CareKit Sample/Tabs/Schedule/ScheduleViewController.swift b/CareKit Sample/Tabs/Schedule/ScheduleViewController.swift index 44b9ced..954271d 100644 --- a/CareKit Sample/Tabs/Schedule/ScheduleViewController.swift +++ b/CareKit Sample/Tabs/Schedule/ScheduleViewController.swift @@ -11,6 +11,8 @@ import CareKitStore import UIKit import SwiftUI + + class ScheduleViewController: OCKDailyPageViewController { override func viewDidLoad() { @@ -20,30 +22,45 @@ class ScheduleViewController: OCKDailyPageViewController { override func dailyPageViewController(_ dailyPageViewController: OCKDailyPageViewController, prepare listViewController: OCKListViewController, for date: Date) { - // Meds - let medsViewController = OCKGridTaskViewController(taskID: "meds", eventQuery: OCKEventQuery(for: date), storeManager: storeManager) - listViewController.appendViewController(medsViewController, animated: true) - - // Rehab - let rehabViewController = OCKGridTaskViewController(taskID: "rehab", eventQuery: OCKEventQuery(for: date), storeManager: storeManager) - listViewController.appendViewController(rehabViewController, animated: true) - - // Charts - let rehabConfig = OCKDataSeriesConfiguration(taskID: "rehab", legendTitle: "Rehab", gradientStartColor: .systemGray, gradientEndColor: .systemGray, markerSize: 6, eventAggregator: .countOutcomeValues) - let rehabCharts = OCKCartesianChartViewController(plotType: .bar, selectedDate: date, configurations: [rehabConfig], storeManager: storeManager) - listViewController.appendViewController(rehabCharts, animated: true) - - // Survey - let surveyCard = SurveyItemViewController( - viewSynchronizer: SurveyItemViewSynchronizer(), - taskID: "survey", - eventQuery: .init(for: date), - storeManager: self.storeManager) - listViewController.appendViewController(surveyCard, animated: true) - - super.dailyPageViewController(dailyPageViewController, prepare: listViewController, for: date) + let identifiers = ["skinAI", "survey", "rehab"] + var query = OCKTaskQuery(for: date) + query.ids = identifiers + query.excludesTasksWithNoEvents = true + + storeManager.store.fetchAnyTasks(query: query, callbackQueue: .main) { result in + switch result { + case .failure(let error): print("Error: \(error)") + case .success(let tasks): + + // Routine + if let task = tasks.first(where: { $0.id == "rehab" }) { + let rehabViewController = OCKGridTaskViewController(task: task, eventQuery: OCKEventQuery(for: date), storeManager: self.storeManager) + listViewController.appendViewController(rehabViewController, animated: true) + } + + // Skin diagnosis + if let task = tasks.first(where: { $0.id == "skinAI" }) { + let skinAIViewController = AcneModelViewController( + viewSynchronizer: SkinAIViewSynchronizer(), + task: task, + eventQuery: .init(for: date), + storeManager: self.storeManager) + listViewController.appendViewController(skinAIViewController, animated: true) + } + + // Survey + if let task = tasks.first(where: { $0.id == "survey" }) { + let surveyCard = SurveyItemViewController( + viewSynchronizer: SurveyItemViewSynchronizer(), + task: task, + eventQuery: .init(for: date), + storeManager: self.storeManager) + listViewController.appendViewController(surveyCard, animated: true) + } + } + } +// super.dailyPageViewController(dailyPageViewController, prepare: listViewController, for: date) } - } private extension View { @@ -53,3 +70,4 @@ private extension View { return viewController } } + diff --git a/CareKit Sample/Tabs/Schedule/SurveyItemViewController.swift b/CareKit Sample/Tabs/Schedule/SurveyItemViewController.swift new file mode 100644 index 0000000..eea95e6 --- /dev/null +++ b/CareKit Sample/Tabs/Schedule/SurveyItemViewController.swift @@ -0,0 +1,154 @@ +// +// SurveyItemViewController.swift +// CardinalKit_Example +// +// Created by Santiago Gutierrez on 12/23/20. +// Copyright © 2020 CocoaPods. All rights reserved. +// + +import Foundation +import CareKit +import ResearchKit +import CareKitUI +import CareKitStore + + +// 1. Subclass a task view controller to customize the control flow and present a ResearchKit survey! +class SurveyItemViewController: OCKInstructionsTaskViewController, ORKTaskViewControllerDelegate { + + // 2. This method is called when the use taps the button! + override func taskView(_ taskView: UIView & OCKTaskDisplayable, didCompleteEvent isComplete: Bool, at indexPath: IndexPath, sender: Any?) { + + // 2a. If the task was marked incomplete, fall back on the super class's default behavior or deleting the outcome. + if !isComplete { + super.taskView(taskView, didCompleteEvent: isComplete, at: indexPath, sender: sender) + return + } + + // 2b. If the user attempted to mark the task complete, display a ResearchKit survey. + var steps = [ORKStep]() + + // Question 1 is asking about how one's feeling today + let moodTypes = [ + ORKTextChoice(text: "Great", value: 0 as NSNumber), + ORKTextChoice(text: "Good", value: 1 as NSNumber), + ORKTextChoice(text: "OK", value: 2 as NSNumber), + ORKTextChoice(text: "Not so great", value: 3 as NSNumber) + ] + let moodTypeAnswerFormat: ORKTextChoiceAnswerFormat = ORKAnswerFormat.choiceAnswerFormat(with: .singleChoice, textChoices: moodTypes) + let moodTypeQuestionStep = ORKQuestionStep(identifier: "moodTypeQuestionStep", title: "Mood Type", question: "How are you feeling today?", answer: moodTypeAnswerFormat) + steps += [moodTypeQuestionStep] + + // Question 2 is asking about whether the user followed the routine + let booleanAnswer = ORKBooleanAnswerFormat(yesString: "Yes!", noString: "No") + let booleanStep = ORKQuestionStep(identifier: "Routine-Boolean", title: "Routine", question: "Did you follow the skincare routing that our AI sugegsted for you?", answer: booleanAnswer) + booleanStep.isOptional = true + steps += [booleanStep] + + // if not then we ll ask the customer to do so so we can best help them cure their acne + let textAnswerFormat = ORKTextAnswerFormat(maximumLength: 200) + textAnswerFormat.multipleLines = true +// let routineQuestionStep = ORKQuestionStep(identifier: "RoutineQuestionStep", title: "Routine", question: "Please explain why you did not follow your skincare routine, we can build a routine that better fits your needs.", answer: textAnswerFormat) + + +// let colorQuestionStepTitle = "What is your favorite color?" +// let colorTuples = [ +// (UIImage(named: "red")!, "Red"), +// (UIImage(named: "orange")!, "Orange"), +// (UIImage(named: "yellow")!, "Yellow"), +// (UIImage(named: "green")!, "Green"), +// (UIImage(named: "blue")!, "Blue"), +// (UIImage(named: "purple")!, "Purple") +// ] +// let imageChoices : [ORKImageChoice] = colorTuples.map { +// return ORKImageChoice(normalImage: $0.0, selectedImage: nil, text: $0.1, value: $0.1) +// } +// let colorAnswerFormat: ORKImageChoiceAnswerFormat = ORKAnswerFormat.choiceAnswerFormatWithImageChoices(imageChoices) +// let colorQuestionStep = ORKQuestionStep(identifier: "ImageChoiceQuestionStep", title: colorQuestionStepTitle, answer: colorAnswerFormat) +// steps += [colorQuestionStep] +// + // Question 3 is aking about whether they have seen any improvements + let skinCondition = [ + ORKTextChoice(text: "Breakouts", value: 0 as NSNumber), + ORKTextChoice(text: "Clogged pores", value: 1 as NSNumber), + ORKTextChoice(text: "Acne scars", value: 2 as NSNumber), + ORKTextChoice(text: "Dark spots", value: 3 as NSNumber), + ORKTextChoice(text: "Wrinkles", value: 4 as NSNumber), + ORKTextChoice(text: "Fine lines", value: 5 as NSNumber), + ] + let skinImprovementAnswerFormat: ORKTextChoiceAnswerFormat = ORKAnswerFormat.choiceAnswerFormat(with: .multipleChoice, textChoices: skinCondition) + let skinImprovementQuestionStep = ORKQuestionStep(identifier: "SkinImprovementQuestionStep", title: "Skin Evolution", question: "Did you notice improvements for any of the following skin conditions?", answer: skinImprovementAnswerFormat) + steps += [skinImprovementQuestionStep] + let skinWorseningQuestionStep = ORKQuestionStep(identifier: "SkinWorseningQuestionStep", title: "Skin Evolution", question: "Did any of the below symptoms worsen since you started your routine?", answer: skinImprovementAnswerFormat) + steps += [skinWorseningQuestionStep] + + // Summary step + let summaryStep = ORKCompletionStep(identifier: "SummaryStep") + summaryStep.title = "All done!" + summaryStep.text = "Our AI will review this information and update your routine if needed. See you soon for your next skin check-up!" + steps += [summaryStep] + + // create navigable rule for allergy question +// let resultBooleanSelector = ORKResultSelector(resultIdentifier: booleanStep.identifier) +// let predicate = ORKResultPredicate.predicateForBooleanQuestionResult(with: resultBooleanSelector, expectedAnswer: false) +// let navigableRule = ORKPredicateStepNavigationRule(resultPredicatesAndDestinationStepIdentifiers: [(resultPredicate: predicate, destinationStepIdentifier: routineQuestionStep.identifier)]) +// + // create task grouping all steps + let task = ORKNavigableOrderedTask(identifier: "SurveyTask-Assessment", steps: steps) + // task.setNavigationRule(navigableRule, forTriggerStepIdentifier: booleanStep.identifier) + + let surveyViewController = ORKTaskViewController(task: task, taskRun: nil) + surveyViewController.view.tintColor = Config.shared.readColor(query: "Primary Color") + surveyViewController.delegate = self + surveyViewController.outputDirectory = CKUploadToGCPTaskViewControllerDelegate().CKGetTaskOutputDirectory(surveyViewController) + + // 3a. Present the survey to the user + present(surveyViewController, animated: true, completion: nil) + } + + // 3b. This method will be called when the user completes the survey. + func taskViewController(_ taskViewController: ORKTaskViewController, didFinishWith reason: ORKTaskViewControllerFinishReason, error: Error?) { + taskViewController.dismiss(animated: true, completion: nil) + guard reason == .completed else { + taskView.completionButton.isSelected = false + return + } + + // 4a. Retrieve the result from the ResearchKit survey +// let survey = taskViewController.result.results!.first(where: { $0.identifier == "moodTypeQuestionStep" }) as! ORKStepResult +// let feedbackResult = survey.results!.first as! ORKScaleQuestionResult +// let answer = Int(truncating: feedbackResult.scaleAnswer!) + +// // 4b. Save the result into CareKit's store + controller.appendOutcomeValue(value: true, at: IndexPath(item: 0, section: 0), completion: nil) + +// // 5. Upload results to GCP, using the CKTaskViewControllerDelegate class. + let gcpDelegate = CKUploadToGCPTaskViewControllerDelegate() + gcpDelegate.taskViewController(taskViewController, didFinishWith: reason, error: error) + } +} + +class SurveyItemViewSynchronizer: OCKInstructionsTaskViewSynchronizer { + + // Customize the initial state of the view + override func makeView() -> OCKInstructionsTaskView { + let instructionsView = super.makeView() + instructionsView.completionButton.label.text = "Start" + return instructionsView + } + + override func updateView(_ view: OCKInstructionsTaskView, context: OCKSynchronizationContext) { + super.updateView(view, context: context) + + // Check if an answer exists or not and set the detail label accordingly + let element: [OCKAnyEvent]? = context.viewModel.first + let firstEvent = element?.first + view.headerView.titleLabel.text = "Skin Survey" + if (firstEvent?.outcome?.values.first) != nil { + view.headerView.detailLabel.text = "Thank you for completing your skin survey." + } else { + view.headerView.detailLabel.text = "Please take your skin survey so we can know how you are doing." + } + } +} + diff --git a/CareKit Sample/Tabs/Schedule/Views/SurveyItemViewController.swift b/CareKit Sample/Tabs/Schedule/Views/SurveyItemViewController.swift deleted file mode 100644 index 4b76795..0000000 --- a/CareKit Sample/Tabs/Schedule/Views/SurveyItemViewController.swift +++ /dev/null @@ -1,82 +0,0 @@ -// -// SurveyItemViewController.swift -// CardinalKit_Example -// -// Created by Santiago Gutierrez on 12/23/20. -// Copyright © 2020 CocoaPods. All rights reserved. -// - -import Foundation -import CareKit -import ResearchKit -import CareKitUI -import CareKitStore - -// 1. Subclass a task view controller to customize the control flow and present a ResearchKit survey! -class SurveyItemViewController: OCKInstructionsTaskViewController, ORKTaskViewControllerDelegate { - - // 2. This method is called when the use taps the button! - override func taskView(_ taskView: UIView & OCKTaskDisplayable, didCompleteEvent isComplete: Bool, at indexPath: IndexPath, sender: Any?) { - - // 2a. If the task was marked incomplete, fall back on the super class's default behavior or deleting the outcome. - if !isComplete { - super.taskView(taskView, didCompleteEvent: isComplete, at: indexPath, sender: sender) - return - } - - // 2b. If the user attempted to mark the task complete, display a ResearchKit survey. - let answerFormat = ORKAnswerFormat.scale(withMaximumValue: 5, minimumValue: 1, defaultValue: 5, step: 1, vertical: false, maximumValueDescription: "A LOT!", minimumValueDescription: "a little") - let feedbackStep = ORKQuestionStep(identifier: "feedback", title: "Feedback", question: "How are you liking CardinalKit?", answer: answerFormat) - let surveyTask = ORKOrderedTask(identifier: "feedback", steps: [feedbackStep]) - let surveyViewController = ORKTaskViewController(task: surveyTask, taskRun: nil) - surveyViewController.delegate = self - - // 3a. Present the survey to the user - present(surveyViewController, animated: true, completion: nil) - } - - // 3b. This method will be called when the user completes the survey. - func taskViewController(_ taskViewController: ORKTaskViewController, didFinishWith reason: ORKTaskViewControllerFinishReason, error: Error?) { - taskViewController.dismiss(animated: true, completion: nil) - guard reason == .completed else { - taskView.completionButton.isSelected = false - return - } - - // 4a. Retrieve the result from the ResearchKit survey - let survey = taskViewController.result.results!.first(where: { $0.identifier == "feedback" }) as! ORKStepResult - let feedbackResult = survey.results!.first as! ORKScaleQuestionResult - let answer = Int(truncating: feedbackResult.scaleAnswer!) - - // 4b. Save the result into CareKit's store - controller.appendOutcomeValue(value: answer, at: IndexPath(item: 0, section: 0), completion: nil) - - // 5. Upload results to GCP, using the CKTaskViewControllerDelegate class. - let gcpDelegate = CKUploadToGCPTaskViewControllerDelegate() - gcpDelegate.taskViewController(taskViewController, didFinishWith: reason, error: error) - } -} - -class SurveyItemViewSynchronizer: OCKInstructionsTaskViewSynchronizer { - - // Customize the initial state of the view - override func makeView() -> OCKInstructionsTaskView { - let instructionsView = super.makeView() - instructionsView.completionButton.label.text = "Start" - return instructionsView - } - - override func updateView(_ view: OCKInstructionsTaskView, context: OCKSynchronizationContext) { - super.updateView(view, context: context) - - // Check if an answer exists or not and set the detail label accordingly - let element: [OCKAnyEvent]? = context.viewModel.first - let firstEvent = element?.first - - if let answer = firstEvent?.outcome?.values.first?.integerValue { - view.headerView.detailLabel.text = "CardinalKit Rating: \(answer)" - } else { - view.headerView.detailLabel.text = "How are you liking CardinalKit?" - } - } -} diff --git a/Podfile b/Podfile index 590a0ed..a80b24d 100644 --- a/Podfile +++ b/Podfile @@ -7,7 +7,7 @@ target 'CareKit Sample' do # Pods for CareKit Sample pod 'ResearchKit', :git => 'https://github.com/ResearchKit/ResearchKit.git', :branch => 'master' - + pod 'Firebase/Firestore' pod 'Firebase/Auth' pod 'Firebase/Storage' diff --git a/Podfile.lock b/Podfile.lock index eda7a28..2e888dd 100644 --- a/Podfile.lock +++ b/Podfile.lock @@ -363,6 +363,6 @@ SPEC CHECKSUMS: PromisesObjC: 8c196f5a328c2cba3e74624585467a557dcb482f ResearchKit: 449f96824c4e71c70a9278953c3b279f0fe10805 -PODFILE CHECKSUM: 457c59eccb102315f07126e9744311517b7044cb +PODFILE CHECKSUM: 0d8da78ec41932430b9ad4fc8a631d5a4a673aad -COCOAPODS: 1.10.0 +COCOAPODS: 1.10.1 diff --git a/Pods/Manifest.lock b/Pods/Manifest.lock index eda7a28..2e888dd 100644 --- a/Pods/Manifest.lock +++ b/Pods/Manifest.lock @@ -363,6 +363,6 @@ SPEC CHECKSUMS: PromisesObjC: 8c196f5a328c2cba3e74624585467a557dcb482f ResearchKit: 449f96824c4e71c70a9278953c3b279f0fe10805 -PODFILE CHECKSUM: 457c59eccb102315f07126e9744311517b7044cb +PODFILE CHECKSUM: 0d8da78ec41932430b9ad4fc8a631d5a4a673aad -COCOAPODS: 1.10.0 +COCOAPODS: 1.10.1