Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
50 changes: 41 additions & 9 deletions OCKSample.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,12 @@
701230EA280DE687003F5ECE /* OCKAnyEvent+Answer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 701230E9280DE687003F5ECE /* OCKAnyEvent+Answer.swift */; };
701230EB280DE687003F5ECE /* OCKAnyEvent+Answer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 701230E9280DE687003F5ECE /* OCKAnyEvent+Answer.swift */; };
701230F12810894D003F5ECE /* CustomContactViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 701230F02810894D003F5ECE /* CustomContactViewController.swift */; };
701230FD28122A22003F5ECE /* MyContactView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 701230FC28122A22003F5ECE /* MyContactView.swift */; };
7012310028122A4E003F5ECE /* MyContactViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 701230FF28122A4E003F5ECE /* MyContactViewModel.swift */; };
70123105281255BD003F5ECE /* ImagePicker.swift in Sources */ = {isa = PBXBuildFile; fileRef = 70123104281255BD003F5ECE /* ImagePicker.swift */; };
70123107281262FF003F5ECE /* OCKBiologicalSex+Hashable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 70123106281262FF003F5ECE /* OCKBiologicalSex+Hashable.swift */; };
701231092812DC3B003F5ECE /* MyContactViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 701231082812DC3B003F5ECE /* MyContactViewController.swift */; };
7012310B2812E24F003F5ECE /* ContactViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7012310A2812E24F003F5ECE /* ContactViewModel.swift */; };
70202EC12807333900CF73FB /* CareKit in Frameworks */ = {isa = PBXBuildFile; productRef = 70202EC02807333900CF73FB /* CareKit */; };
70202EC32807333900CF73FB /* CareKitFHIR in Frameworks */ = {isa = PBXBuildFile; productRef = 70202EC22807333900CF73FB /* CareKitFHIR */; };
70202EC52807333A00CF73FB /* CareKitUI in Frameworks */ = {isa = PBXBuildFile; productRef = 70202EC42807333A00CF73FB /* CareKitUI */; };
Expand Down Expand Up @@ -201,6 +207,12 @@
701230E7280DE595003F5ECE /* SurveyViewSynchronizer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SurveyViewSynchronizer.swift; sourceTree = "<group>"; };
701230E9280DE687003F5ECE /* OCKAnyEvent+Answer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "OCKAnyEvent+Answer.swift"; sourceTree = "<group>"; };
701230F02810894D003F5ECE /* CustomContactViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CustomContactViewController.swift; sourceTree = "<group>"; };
701230FC28122A22003F5ECE /* MyContactView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MyContactView.swift; sourceTree = "<group>"; };
701230FF28122A4E003F5ECE /* MyContactViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MyContactViewModel.swift; sourceTree = "<group>"; };
70123104281255BD003F5ECE /* ImagePicker.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImagePicker.swift; sourceTree = "<group>"; };
70123106281262FF003F5ECE /* OCKBiologicalSex+Hashable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "OCKBiologicalSex+Hashable.swift"; sourceTree = "<group>"; };
701231082812DC3B003F5ECE /* MyContactViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MyContactViewController.swift; sourceTree = "<group>"; };
7012310A2812E24F003F5ECE /* ContactViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContactViewModel.swift; sourceTree = "<group>"; };
70202EC9280746E900CF73FB /* ResearchKit.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = ResearchKit.xcodeproj; path = ResearchKit/ResearchKit.xcodeproj; sourceTree = "<group>"; };
70202ED52807529900CF73FB /* Surveys.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Surveys.swift; sourceTree = "<group>"; };
70308885258273D400FFABB6 /* LoginViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoginViewModel.swift; sourceTree = "<group>"; };
Expand Down Expand Up @@ -343,6 +355,16 @@
path = Cards;
sourceTree = "<group>";
};
701230FE28122A31003F5ECE /* MyContact */ = {
isa = PBXGroup;
children = (
701230FC28122A22003F5ECE /* MyContactView.swift */,
701231082812DC3B003F5ECE /* MyContactViewController.swift */,
701230FF28122A4E003F5ECE /* MyContactViewModel.swift */,
);
path = MyContact;
sourceTree = "<group>";
};
70202ECA280746E900CF73FB /* Products */ = {
isa = PBXGroup;
children = (
Expand All @@ -355,33 +377,35 @@
703088772582727500FFABB6 /* MainTabs */ = {
isa = PBXGroup;
children = (
91693819271B64CB00A634ED /* Stylers */,
70308878258272CD00FFABB6 /* Care */,
7036E4D2256EBE35006E9A3C /* MainView.swift */,
70308878258272CD00FFABB6 /* Care */,
7030887D258272F400FFABB6 /* Contact */,
7030887C258272E700FFABB6 /* Profile */,
91693819271B64CB00A634ED /* Stylers */,
);
path = MainTabs;
sourceTree = "<group>";
};
70308878258272CD00FFABB6 /* Care */ = {
isa = PBXGroup;
children = (
7036E4C3256E0A48006E9A3C /* CareView.swift */,
E7440E4E229477F7007AD30A /* CareViewController.swift */,
70F03ABA2789071400E5AFB4 /* CareViewModel.swift */,
70123068280830A1003F5ECE /* Consent.swift */,
701230E6280DE574003F5ECE /* Cards */,
701230DC280DDF3A003F5ECE /* Surveys */,
70123068280830A1003F5ECE /* Consent.swift */,
7036E4C3256E0A48006E9A3C /* CareView.swift */,
70F03ABA2789071400E5AFB4 /* CareViewModel.swift */,
);
path = Care;
sourceTree = "<group>";
};
7030887C258272E700FFABB6 /* Profile */ = {
isa = PBXGroup;
children = (
70123104281255BD003F5ECE /* ImagePicker.swift */,
7036E4C8256E1A6F006E9A3C /* ProfileView.swift */,
7036E516256F2413006E9A3C /* ProfileViewModel.swift */,
701230FE28122A31003F5ECE /* MyContact */,
);
path = Profile;
sourceTree = "<group>";
Expand All @@ -390,6 +414,7 @@
isa = PBXGroup;
children = (
7036E4CD256E9A0C006E9A3C /* ContactView.swift */,
7012310A2812E24F003F5ECE /* ContactViewModel.swift */,
701230F02810894D003F5ECE /* CustomContactViewController.swift */,
);
path = Contact;
Expand Down Expand Up @@ -484,15 +509,16 @@
children = (
918FDEBB271B4E4A0045A0EF /* Calendar+Dates.swift */,
918FDEB6271B41FF0045A0EF /* Logger.swift */,
70F03A922786087800E5AFB4 /* OCKAnyEvent+CustomStringConvertable.swift */,
701230E9280DE687003F5ECE /* OCKAnyEvent+Answer.swift */,
70F03A922786087800E5AFB4 /* OCKAnyEvent+CustomStringConvertable.swift */,
70F03A942786093B00E5AFB4 /* OCKHealthKitPassthroughStore+Default.swift */,
70F03A962786098F00E5AFB4 /* OCKStore+Default.swift */,
70F03A9827860A0800E5AFB4 /* OCKSynchronizedStoreManager+Publishers.swift */,
70F03A9E27860A8800E5AFB4 /* OCKOutcomeValue+Identifiable.swift */,
70F03AA027860AB700E5AFB4 /* OCKOutcome+default.swift */,
70F03A9E27860A8800E5AFB4 /* OCKOutcomeValue+Identifiable.swift */,
70F03AA227860AFF00E5AFB4 /* OCKPatient+Parse.swift */,
70F03A962786098F00E5AFB4 /* OCKStore+Default.swift */,
70F03A9827860A0800E5AFB4 /* OCKSynchronizedStoreManager+Publishers.swift */,
7083A855279CA40A00B3832E /* PCKUtility+extention.swift */,
70123106281262FF003F5ECE /* OCKBiologicalSex+Hashable.swift */,
);
path = Extensions;
sourceTree = "<group>";
Expand Down Expand Up @@ -884,6 +910,7 @@
E7440E4F229477F7007AD30A /* CareViewController.swift in Sources */,
918FDEB5271B40590045A0EF /* Installation.swift in Sources */,
70F03AB62788C80500E5AFB4 /* UserStatus.swift in Sources */,
701230FD28122A22003F5ECE /* MyContactView.swift in Sources */,
70F921AC27CABE3000368CEC /* SessionDelegate.swift in Sources */,
7036E64025717F85006E9A3C /* Constants.swift in Sources */,
918FDEBC271B4E4A0045A0EF /* Calendar+Dates.swift in Sources */,
Expand All @@ -897,19 +924,24 @@
701230EA280DE687003F5ECE /* OCKAnyEvent+Answer.swift in Sources */,
701230F12810894D003F5ECE /* CustomContactViewController.swift in Sources */,
70202ED62807529900CF73FB /* Surveys.swift in Sources */,
70123105281255BD003F5ECE /* ImagePicker.swift in Sources */,
701231092812DC3B003F5ECE /* MyContactViewController.swift in Sources */,
9169381D271B650700A634ED /* ColorStyle.swift in Sources */,
E72B2C0A226939E3009A9438 /* AppDelegate.swift in Sources */,
7036E4C4256E0A48006E9A3C /* CareView.swift in Sources */,
70F921B027CABED600368CEC /* LocalSyncSessionDelegate.swift in Sources */,
70123069280830A1003F5ECE /* Consent.swift in Sources */,
70F03A932786087800E5AFB4 /* OCKAnyEvent+CustomStringConvertable.swift in Sources */,
7012310B2812E24F003F5ECE /* ContactViewModel.swift in Sources */,
918FDEC3271B4E950045A0EF /* TintColorKey.swift in Sources */,
7012310028122A4E003F5ECE /* MyContactViewModel.swift in Sources */,
70F03AA327860AFF00E5AFB4 /* OCKPatient+Parse.swift in Sources */,
918FDEB7271B41FF0045A0EF /* Logger.swift in Sources */,
70F03A9F27860A8800E5AFB4 /* OCKOutcomeValue+Identifiable.swift in Sources */,
7036E4D3256EBE35006E9A3C /* MainView.swift in Sources */,
918FDEC5271B4EA70045A0EF /* StoreManagerKey.swift in Sources */,
91693822271B897200A634ED /* Utility.swift in Sources */,
70123107281262FF003F5ECE /* OCKBiologicalSex+Hashable.swift in Sources */,
707CC718254DA91900116728 /* OCKLocalization.swift in Sources */,
E7C37849228F887800E982D8 /* TipView.swift in Sources */,
70077597252228E900EC0EDA /* User.swift in Sources */,
Expand Down
1 change: 1 addition & 0 deletions OCKSample/Extensions/Logger.swift
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ extension Logger {
static let localSessionDelegate = Logger(subsystem: subsystem, category: "LocalSessionDelegate")
static let utility = Logger(subsystem: subsystem, category: "Utility")
static let contact = Logger(subsystem: subsystem, category: "Contact")
static let myContact = Logger(subsystem: subsystem, category: "MyContact")
static let login = Logger(subsystem: subsystem, category: "Login")
static let feed = Logger(subsystem: subsystem, category: "Feed")
static let watch = Logger(subsystem: subsystem, category: "Watch")
Expand Down
13 changes: 13 additions & 0 deletions OCKSample/Extensions/OCKBiologicalSex+Hashable.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
//
// OCKBiologicalSex+Hashable.swift
// OCKSample
//
// Created by Corey Baker on 4/22/22.
// Copyright © 2022 Network Reconnaissance Lab. All rights reserved.
//

import CareKitStore

// Needed to use OCKBiologicalSex in a Picker.
// Simple conformance to hashable protocol.
extension OCKBiologicalSex: Hashable { }
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ extension OCKHealthKitPassthroughStore {
}
}

func populateSampleData() async throws {
func populateSampleData(_ patientUUID: UUID? = nil) async throws {

let schedule = OCKSchedule.dailyAtTime(
hour: 8, minutes: 0, start: Date(), end: nil, text: nil,
Expand Down
36 changes: 22 additions & 14 deletions OCKSample/Extensions/OCKStore+Default.swift
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,15 @@ import ParseCareKit

extension OCKStore {

func addCarePlansIfNotPresent(_ carePlans: [OCKCarePlan]) async throws {
/**
Adds an `OCKCarePlan`*asynchronously* to `OCKStore` if it has not been added already.

- parameter carePlans: The array of `OCKCarePlan`'s to be added to the `OCKStore`.
- parameter patientUUID: The uuid of the `OCKPatient` to tie to the `OCKCarePlan`. Defaults to nil.
- throws: An error if there was a problem adding the missing `OCKCarePlan`'s.
- note: `OCKCarePlan`'s that have an existing `id` will not be added and will not cause errors to be thrown.
*/
func addCarePlansIfNotPresent(_ carePlans: [OCKCarePlan], patientUUID: UUID? = nil) async throws {
let carePlanIdsToAdd = carePlans.compactMap { $0.id }

// Prepare query to see if Care Plan are already added
Expand All @@ -29,7 +37,9 @@ extension OCKStore {
// Check results to see if there's a missing Care Plan
carePlans.forEach { potentialCarePlan in
if foundCarePlans.first(where: { $0.id == potentialCarePlan.id }) == nil {
carePlanNotInStore.append(potentialCarePlan)
var mutableCarePlan = potentialCarePlan
mutableCarePlan.patientUUID = patientUUID
carePlanNotInStore.append(mutableCarePlan)
}
}

Expand Down Expand Up @@ -72,7 +82,7 @@ extension OCKStore {
}
}

func addContactsIfNotPresent(_ contacts: [OCKContact]) async throws {
func addContactsIfNotPresent(_ contacts: [OCKContact], carePlanUUID: UUID? = nil) async throws {
let contactIdsToAdd = contacts.compactMap { $0.id }

// Prepare query to see if contacts are already added
Expand All @@ -85,7 +95,9 @@ extension OCKStore {
// Check results to see if there's a missing task
contacts.forEach { potential in
if foundContacts.first(where: { $0.id == potential.id }) == nil {
contactsNotInStore.append(potential)
var mutableContact = potential
mutableContact.carePlanUUID = carePlanUUID
contactsNotInStore.append(mutableContact)
}
}

Expand All @@ -100,17 +112,13 @@ extension OCKStore {
}
}

func populateCarePlans() async throws {
// Get patient from local database if available
guard let uuid = ProfileViewModel.getRemoteClockUUIDAfterLoginFromLocalStorage() else {
throw AppError.remoteClockIDNotAvailable
}
let patient = try await fetchPatient(withID: uuid.uuidString)
func populateCarePlans(patientUUID: UUID? = nil) async throws {

let checkInCarePlan = OCKCarePlan(id: CarePlanID.checkIn.rawValue,
title: "Check in Care Plan",
patientUUID: patient.uuid)
try await addCarePlansIfNotPresent([checkInCarePlan])
patientUUID: nil)

try await addCarePlansIfNotPresent([checkInCarePlan], patientUUID: patientUUID)
}

@MainActor
Expand All @@ -134,8 +142,8 @@ extension OCKStore {
}

// Adds tasks and contacts into the store
func populateSampleData() async throws {
try await populateCarePlans()
func populateSampleData(_ patientUUID: UUID? = nil) async throws {
try await populateCarePlans(patientUUID: patientUUID)
let carePlanUUIDs = try await Self.getCarePlanUUIDs()

let thisMorning = Calendar.current.startOfDay(for: Date())
Expand Down
36 changes: 36 additions & 0 deletions OCKSample/Extensions/OCKSynchronizedStoreManager+Publishers.swift
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,42 @@ extension OCKSynchronizedStoreManager {
.prepend(presentValuePublisher))
}

// MARK: Contacts

func contactsPublisher(categories: [OCKStoreNotificationCategory]) -> AnyPublisher<OCKAnyContact, Never> {
return AnyPublisher(notificationPublisher
.compactMap { $0 as? OCKContactNotification }
.filter { categories.contains($0.category) }
.map { $0.contact })
}

func publisher(forContactID id: String,
categories: [OCKStoreNotificationCategory]) -> AnyPublisher<OCKAnyContact, Never> {
return notificationPublisher
.compactMap { $0 as? OCKContactNotification }
.filter { $0.contact.id == id && categories.contains($0.category) }
.map { $0.contact }
.eraseToAnyPublisher()
}

func publisher(forContact contact: OCKAnyContact,
categories: [OCKStoreNotificationCategory],
fetchImmediately: Bool = true) -> AnyPublisher<OCKAnyContact, Never> {
let presentValuePublisher = Future<OCKAnyContact, Never>({ completion in
self.store.fetchAnyContact(withID: contact.id) { result in
completion(.success((try? result.get()) ?? contact))
}
})

let changePublisher = notificationPublisher
.compactMap { $0 as? OCKContactNotification }
.filter { $0.contact.id == contact.id && categories.contains($0.category) }
.map { $0.contact }

// swiftlint:disable:next line_length
return fetchImmediately ? AnyPublisher(changePublisher.prepend(presentValuePublisher)) : AnyPublisher(changePublisher)
}

// MARK: Tasks

func publisherForTasks(categories: [OCKStoreNotificationCategory]) -> AnyPublisher<OCKTaskNotification, Never> {
Expand Down
1 change: 1 addition & 0 deletions OCKSample/MainTabs/Care/CareView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ struct CareView: UIViewControllerRepresentable {

@ObservedObject var viewModel = CareViewModel()

@MainActor
func makeUIViewController(context: Context) -> some UIViewController {

let view = createCareView()
Expand Down
43 changes: 37 additions & 6 deletions OCKSample/MainTabs/Contact/ContactView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -12,19 +12,50 @@ import CareKit
import CareKitStore
import os.log

/*
You should notice this looks like CareView but with references
to CustomContactViewController instead.
*/

struct ContactView: UIViewControllerRepresentable {

@ObservedObject var viewModel = ContactViewModel()

@MainActor
func makeUIViewController(context: Context) -> some UIViewController {
guard let manager = StoreManagerKey.defaultValue else {
Logger.contact.error("Couldn't unwrap storeManager")
return UINavigationController()
}
let contactViewController = CustomContactViewController(storeManager: manager)
return UINavigationController(rootViewController: contactViewController)
let viewController = createViewContoller()
let navigationController = UINavigationController(rootViewController: viewController)
navigationController.navigationBar.backgroundColor = UIColor { $0.userInterfaceStyle == .light ? #colorLiteral(red: 1, green: 1, blue: 1, alpha: 1): #colorLiteral(red: 0, green: 0, blue: 0, alpha: 1) }

return navigationController
}

@MainActor
func updateUIViewController(_ uiViewController: UIViewControllerType, context: Context) {
// swiftlint:disable:next force_cast
let appDelegate = UIApplication.shared.delegate as! AppDelegate

if appDelegate.isFirstLogin && appDelegate.isFirstAppOpen {
guard let navigationController = uiViewController as? UINavigationController,
// swiftlint:disable:next line_length
let currentViewController = navigationController.viewControllers.first as? CustomContactViewController,
appDelegate.storeManager !== currentViewController.storeManager else {
return
}
// Replace current view controller
let viewController = createViewContoller()
navigationController.viewControllers = [viewController]
}
}

// MARK: Helpers
func createViewContoller() -> UIViewController {
guard let manager = StoreManagerKey.defaultValue else {
Logger.contact.error("Couldn't unwrap storeManager")
return CustomContactViewController(storeManager: .init(wrapping: OCKStore(name: "none_contact",
type: .inMemory)))
}
return CustomContactViewController(storeManager: manager)
}
}

Expand Down
29 changes: 29 additions & 0 deletions OCKSample/MainTabs/Contact/ContactViewModel.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
//
// ContactViewModel.swift
// OCKSample
//
// Created by Corey Baker on 4/22/22.
// Copyright © 2022 Network Reconnaissance Lab. All rights reserved.
//

import Foundation

/*
You should notice this looks like CareViewModel.
*/

class ContactViewModel: ObservableObject {
@Published var update = false

init() {
NotificationCenter.default.addObserver(self, selector: #selector(reloadViewModel),
name: Notification.Name(rawValue: Constants.storeInitialized),
object: nil)
}

// MARK: Helpers

@objc private func reloadViewModel() {
update = !update
}
}
Loading