From 0e21d7b3a8cd2a3d598d40f8801f176280c4966b Mon Sep 17 00:00:00 2001 From: Corey Date: Sat, 8 Apr 2023 21:41:19 -0400 Subject: [PATCH 01/52] Working without displaying HealthKit tasks --- OCKSample.xcodeproj/project.pbxproj | 18 +-- .../xcshareddata/swiftpm/Package.resolved | 34 ++++-- OCKSample/AppDelegate.swift | 41 ++++--- .../Environment/StoreCoordinatorKey.swift | 29 +++++ OCKSample/Environment/StoreManagerKey.swift | 30 ----- .../AppDelegate+UIApplicationDelegate.swift | 2 +- OCKSample/Main/Care/CareView.swift | 2 +- OCKSample/Main/Care/CareViewController.swift | 107 ++++++++++++------ OCKSample/Main/Contact/ContactView.swift | 2 +- OCKSample/Main/Login/LoginViewModel.swift | 11 +- OCKSample/Main/Profile/ProfileView.swift | 15 ++- OCKSample/Main/Profile/ProfileViewModel.swift | 68 ++++------- OCKSample/Utility.swift | 4 +- OCKWatchSample Extension/AppDelegate.swift | 7 +- .../Main/Care/CareView.swift | 21 ++-- .../Main/Care/CareViewModel.swift | 6 +- .../OCKWatchSampleApp.swift | 5 +- 17 files changed, 216 insertions(+), 186 deletions(-) create mode 100644 OCKSample/Environment/StoreCoordinatorKey.swift delete mode 100644 OCKSample/Environment/StoreManagerKey.swift diff --git a/OCKSample.xcodeproj/project.pbxproj b/OCKSample.xcodeproj/project.pbxproj index bfa68b6..1549763 100644 --- a/OCKSample.xcodeproj/project.pbxproj +++ b/OCKSample.xcodeproj/project.pbxproj @@ -19,7 +19,7 @@ 70221F2F28D7CDE400971195 /* AppDelegate+ParseRemoteDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 70221F2D28D7CD9200971195 /* AppDelegate+ParseRemoteDelegate.swift */; }; 70221F3428D8ABBE00971195 /* AppDelegate+UIApplicationDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 70221F3328D8ABBE00971195 /* AppDelegate+UIApplicationDelegate.swift */; }; 70221F3828D8C0CB00971195 /* AppDelegateKey.swift in Sources */ = {isa = PBXBuildFile; fileRef = 70F0EFE928C2EE6C0005B5A2 /* AppDelegateKey.swift */; }; - 70221F3928D8C10800971195 /* StoreManagerKey.swift in Sources */ = {isa = PBXBuildFile; fileRef = 918FDEC4271B4EA70045A0EF /* StoreManagerKey.swift */; }; + 70221F3928D8C10800971195 /* StoreCoordinatorKey.swift in Sources */ = {isa = PBXBuildFile; fileRef = 918FDEC4271B4EA70045A0EF /* StoreCoordinatorKey.swift */; }; 70308886258273D400FFABB6 /* LoginViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 70308885258273D400FFABB6 /* LoginViewModel.swift */; }; 703616FF29CA194900B50BC5 /* CareKitUtilities in Frameworks */ = {isa = PBXBuildFile; productRef = 703616FE29CA194900B50BC5 /* CareKitUtilities */; }; 7036170129CA196800B50BC5 /* CareKitUtilities in Frameworks */ = {isa = PBXBuildFile; productRef = 7036170029CA196800B50BC5 /* CareKitUtilities */; }; @@ -79,7 +79,7 @@ 918FDEB8271B49060045A0EF /* Logger.swift in Sources */ = {isa = PBXBuildFile; fileRef = 918FDEB6271B41FF0045A0EF /* Logger.swift */; }; 918FDEB9271B493A0045A0EF /* Installation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 918FDEB4271B40590045A0EF /* Installation.swift */; }; 918FDEC3271B4E950045A0EF /* TintColorKey.swift in Sources */ = {isa = PBXBuildFile; fileRef = 918FDEC2271B4E950045A0EF /* TintColorKey.swift */; }; - 918FDEC5271B4EA70045A0EF /* StoreManagerKey.swift in Sources */ = {isa = PBXBuildFile; fileRef = 918FDEC4271B4EA70045A0EF /* StoreManagerKey.swift */; }; + 918FDEC5271B4EA70045A0EF /* StoreCoordinatorKey.swift in Sources */ = {isa = PBXBuildFile; fileRef = 918FDEC4271B4EA70045A0EF /* StoreCoordinatorKey.swift */; }; 91AD922224A45A3900925D4D /* ParseCareKit.plist in Resources */ = {isa = PBXBuildFile; fileRef = 91AD922124A45A3900925D4D /* ParseCareKit.plist */; }; 91AD922424A461D200925D4D /* README.md in Resources */ = {isa = PBXBuildFile; fileRef = 91AD922324A461D200925D4D /* README.md */; }; 91AD922D24A4C42D00925D4D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 91AD922C24A4C42D00925D4D /* Assets.xcassets */; }; @@ -186,7 +186,7 @@ 918FDEB4271B40590045A0EF /* Installation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Installation.swift; sourceTree = ""; }; 918FDEB6271B41FF0045A0EF /* Logger.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Logger.swift; sourceTree = ""; }; 918FDEC2271B4E950045A0EF /* TintColorKey.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TintColorKey.swift; sourceTree = ""; }; - 918FDEC4271B4EA70045A0EF /* StoreManagerKey.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StoreManagerKey.swift; sourceTree = ""; }; + 918FDEC4271B4EA70045A0EF /* StoreCoordinatorKey.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StoreCoordinatorKey.swift; sourceTree = ""; }; 91AD922124A45A3900925D4D /* ParseCareKit.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = ParseCareKit.plist; sourceTree = ""; }; 91AD922324A461D200925D4D /* README.md */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = net.daringfireball.markdown; path = README.md; sourceTree = ""; }; 91AD922724A4C42B00925D4D /* OCKWatchSample.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = OCKWatchSample.app; sourceTree = BUILT_PRODUCTS_DIR; }; @@ -435,7 +435,7 @@ 70F0EFE928C2EE6C0005B5A2 /* AppDelegateKey.swift */, 70F921A727CA9A3A00368CEC /* CustomStylerKey.swift */, 70F03AA727860E7700E5AFB4 /* FontColorKey.swift */, - 918FDEC4271B4EA70045A0EF /* StoreManagerKey.swift */, + 918FDEC4271B4EA70045A0EF /* StoreCoordinatorKey.swift */, 918FDEC2271B4E950045A0EF /* TintColorKey.swift */, 70CF66E328E1E74C00FAE977 /* TintColorFlipKey.swift */, ); @@ -708,7 +708,7 @@ buildActionMask = 2147483647; files = ( 70F921B327CAC16E00368CEC /* LocalSyncSessionDelegate.swift in Sources */, - 70221F3928D8C10800971195 /* StoreManagerKey.swift in Sources */, + 70221F3928D8C10800971195 /* StoreCoordinatorKey.swift in Sources */, 70A98D63278A268B009B58F2 /* ColorStyler.swift in Sources */, 918FDEB8271B49060045A0EF /* Logger.swift in Sources */, 91AD923B24A4C42D00925D4D /* CareView.swift in Sources */, @@ -768,7 +768,7 @@ 918FDEB7271B41FF0045A0EF /* Logger.swift in Sources */, 70221F2A28D7BE0600971195 /* AppDelegate+ParseRemoteDelegate.swift in Sources */, 7036E4D3256EBE35006E9A3C /* MainView.swift in Sources */, - 918FDEC5271B4EA70045A0EF /* StoreManagerKey.swift in Sources */, + 918FDEC5271B4EA70045A0EF /* StoreCoordinatorKey.swift in Sources */, 91693822271B897200A634ED /* Utility.swift in Sources */, 707CC718254DA91900116728 /* OCKLocalization.swift in Sources */, E7C37849228F887800E982D8 /* TipView.swift in Sources */, @@ -1194,7 +1194,7 @@ repositoryURL = "https://github.com/cbaker6/CareKit.git"; requirement = { kind = upToNextMajorVersion; - minimumVersion = 2.1.8; + minimumVersion = "3.0.0-alpha.4"; }; }; 703616FD29CA194900B50BC5 /* XCRemoteSwiftPackageReference "CareKitUtilities" */ = { @@ -1202,7 +1202,7 @@ repositoryURL = "https://github.com/netreconlab/CareKitUtilities.git"; requirement = { kind = upToNextMajorVersion; - minimumVersion = 0.0.1; + minimumVersion = "1.0.0-alpha.1"; }; }; 918FDEAD271B3F8F0045A0EF /* XCRemoteSwiftPackageReference "ParseCareKit" */ = { @@ -1210,7 +1210,7 @@ repositoryURL = "https://github.com/netreconlab/ParseCareKit.git"; requirement = { kind = upToNextMajorVersion; - minimumVersion = 0.13.1; + minimumVersion = "1.0.0-alpha.1"; }; }; /* End XCRemoteSwiftPackageReference section */ diff --git a/OCKSample.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/OCKSample.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved index f6e1dc0..2c0cef5 100644 --- a/OCKSample.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/OCKSample.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -5,8 +5,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/cbaker6/CareKit.git", "state" : { - "revision" : "954185b307222431e50f656d2870019e9ec97c28", - "version" : "2.1.8" + "revision" : "863a5e8b3d224c23bff78929e01ae0f5027dcccf", + "version" : "3.0.0-alpha.4" } }, { @@ -14,8 +14,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/netreconlab/CareKitUtilities.git", "state" : { - "revision" : "ca65485fd95f40ae1bc27f61ee062b2688ad2554", - "version" : "0.0.1" + "revision" : "50eab8bc14396a1214a6f0d0ecc5a1e21e92630a", + "version" : "1.0.0-alpha.1" } }, { @@ -32,8 +32,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/netreconlab/Parse-Swift.git", "state" : { - "revision" : "8c7d8dce114571052d07516e8f4aef90891960aa", - "version" : "5.3.3" + "revision" : "088e42071dcebae970e5d94e3d7d0f34cf300f02", + "version" : "5.4.1" } }, { @@ -41,8 +41,26 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/netreconlab/ParseCareKit.git", "state" : { - "revision" : "57aaabd1dfe0d038ffc40e95239b2445ed832d74", - "version" : "0.13.1" + "revision" : "f84c6642d5ec7ecd84da116a042e6f48a2bd0605", + "version" : "1.0.0-alpha.1" + } + }, + { + "identity" : "swift-async-algorithms", + "kind" : "remoteSourceControl", + "location" : "https://github.com/apple/swift-async-algorithms", + "state" : { + "revision" : "aed5422380244498344a036b8d94e27f370d9a22", + "version" : "0.0.4" + } + }, + { + "identity" : "swift-collections", + "kind" : "remoteSourceControl", + "location" : "https://github.com/apple/swift-collections.git", + "state" : { + "revision" : "937e904258d22af6e447a0b72c0bc67583ef64a2", + "version" : "1.0.4" } } ], diff --git a/OCKSample/AppDelegate.swift b/OCKSample/AppDelegate.swift index 7e06c56..a075b05 100644 --- a/OCKSample/AppDelegate.swift +++ b/OCKSample/AppDelegate.swift @@ -47,19 +47,24 @@ class AppDelegate: UIResponder, ObservableObject { } // MARK: Public read private write properties - // swiftlint:disable:next line_length - @Published private(set) var storeManager: OCKSynchronizedStoreManager = .init(wrapping: OCKStore(name: Constants.noCareStoreName, - type: .inMemory)) { + @Published private(set) var storeCoordinator: OCKStoreCoordinator = .init() { willSet { - StoreManagerKey.defaultValue = newValue + StoreCoordinatorKey.defaultValue = newValue + DispatchQueue.main.async { + self.objectWillChange.send() + } + } + } + @Published private(set) var store: OCKStore! = .init(name: Constants.noCareStoreName, type: .inMemory) { + willSet { + // StoreCoordinatorKey.defaultValue = newValue DispatchQueue.main.async { self.objectWillChange.send() } } } - private(set) var parseRemote: ParseRemote! - private(set) var store: OCKStore? private(set) var healthKitStore: OCKHealthKitPassthroughStore! + private(set) var parseRemote: ParseRemote! // MARK: Private read/write properties private var sessionDelegate: SessionDelegate! @@ -68,19 +73,15 @@ class AppDelegate: UIResponder, ObservableObject { // MARK: Helpers func resetAppToInitialState() { do { - try healthKitStore.reset() - } catch { - Logger.appDelegate.error("Error deleting HealthKit Store: \(error)") - } - do { - try store?.delete() // Delete data in local OCKStore database + try storeCoordinator.reset() } catch { - Logger.appDelegate.error("Error deleting OCKStore: \(error)") + Logger.appDelegate.error("Error deleting Coordinator Store: \(error)") } - storeManager = .init(wrapping: OCKStore(name: Constants.noCareStoreName, type: .inMemory)) + + storeCoordinator = .init() healthKitStore = nil parseRemote = nil - store = nil + store = .init(name: Constants.noCareStoreName, type: .inMemory) sessionDelegate.store = store } @@ -112,15 +113,11 @@ class AppDelegate: UIResponder, ObservableObject { WCSession.default.delegate = sessionDelegate WCSession.default.activate() - guard let currentStore = store else { - Logger.appDelegate.error("Should have OCKStore") - return - } - healthKitStore = OCKHealthKitPassthroughStore(store: currentStore) + healthKitStore = OCKHealthKitPassthroughStore(store: store) let coordinator = OCKStoreCoordinator() - coordinator.attach(store: currentStore) + coordinator.attach(store: store) coordinator.attach(eventStore: healthKitStore) - storeManager = OCKSynchronizedStoreManager(wrapping: coordinator) + storeCoordinator = coordinator } catch { Logger.appDelegate.error("Error setting up remote: \(error)") throw error diff --git a/OCKSample/Environment/StoreCoordinatorKey.swift b/OCKSample/Environment/StoreCoordinatorKey.swift new file mode 100644 index 0000000..9cbf20f --- /dev/null +++ b/OCKSample/Environment/StoreCoordinatorKey.swift @@ -0,0 +1,29 @@ +// +// StoreCoordinatorKey.swift +// OCKSample +// +// Created by Corey Baker on 10/16/21. +// Copyright © 2021 Network Reconnaissance Lab. All rights reserved. +// + +import Foundation +import SwiftUI +import CareKit +import CareKitStore + +struct StoreCoordinatorKey: EnvironmentKey { + static var defaultValue = OCKStoreCoordinator() +} + +extension EnvironmentValues { + + var storeCoordinator: OCKStoreCoordinator { + get { + self[StoreCoordinatorKey.self] + } + + set { + self[StoreCoordinatorKey.self] = newValue + } + } +} diff --git a/OCKSample/Environment/StoreManagerKey.swift b/OCKSample/Environment/StoreManagerKey.swift deleted file mode 100644 index d7dbc78..0000000 --- a/OCKSample/Environment/StoreManagerKey.swift +++ /dev/null @@ -1,30 +0,0 @@ -// -// StoreManagerKey.swift -// OCKSample -// -// Created by Corey Baker on 10/16/21. -// Copyright © 2021 Network Reconnaissance Lab. All rights reserved. -// - -import Foundation -import SwiftUI -import CareKit -import CareKitStore - -struct StoreManagerKey: EnvironmentKey { - static var defaultValue = OCKSynchronizedStoreManager(wrapping: OCKStore(name: Constants.noCareStoreName, - type: .inMemory)) -} - -extension EnvironmentValues { - - var storeManager: OCKSynchronizedStoreManager { - get { - self[StoreManagerKey.self] - } - - set { - self[StoreManagerKey.self] = newValue - } - } -} diff --git a/OCKSample/Extensions/AppDelegate+UIApplicationDelegate.swift b/OCKSample/Extensions/AppDelegate+UIApplicationDelegate.swift index 4ff89c1..a839032 100644 --- a/OCKSample/Extensions/AppDelegate+UIApplicationDelegate.swift +++ b/OCKSample/Extensions/AppDelegate+UIApplicationDelegate.swift @@ -46,7 +46,7 @@ extension AppDelegate: UIApplicationDelegate { // When syncing directly with watchOS, we do not care about login and need to setup remotes do { try await setupRemotes() - try await store?.populateSampleData() + try await store.populateSampleData() try await healthKitStore.populateSampleData() DispatchQueue.main.asyncAfter(deadline: .now() + 1) { NotificationCenter.default.post(.init(name: Notification.Name(rawValue: Constants.requestSync))) diff --git a/OCKSample/Main/Care/CareView.swift b/OCKSample/Main/Care/CareView.swift index 6abfa53..80f4827 100644 --- a/OCKSample/Main/Care/CareView.swift +++ b/OCKSample/Main/Care/CareView.swift @@ -34,7 +34,7 @@ struct CareView: UIViewControllerRepresentable { } func createViewController() -> UIViewController { - CareViewController(storeManager: appDelegate.storeManager) + CareViewController(store: appDelegate.storeCoordinator) } } diff --git a/OCKSample/Main/Care/CareViewController.swift b/OCKSample/Main/Care/CareViewController.swift index c319f53..efcd4d7 100644 --- a/OCKSample/Main/Care/CareViewController.swift +++ b/OCKSample/Main/Care/CareViewController.swift @@ -102,7 +102,7 @@ class CareViewController: OCKDailyPageViewController { return } isSyncing = true - AppDelegateKey.defaultValue?.store?.synchronize { error in + AppDelegateKey.defaultValue?.store.synchronize { error in let errorString = error?.localizedDescription ?? "Successful sync with remote!" Logger.feed.info("\(errorString)") DispatchQueue.main.async { @@ -150,9 +150,10 @@ class CareViewController: OCKDailyPageViewController { } Task { - let tasks = await self.fetchTasks(on: date) + let (tasks, _) = await self.fetchTasks(on: date) tasks.compactMap { - let cards = self.taskViewController(for: $0, on: date) + let cards = self.taskViewController(for: $0, + on: date) cards?.forEach { if let carekitView = $0.view as? OCKView { carekitView.customStyle = CustomStylerKey.defaultValue @@ -172,37 +173,43 @@ class CareViewController: OCKDailyPageViewController { private func taskViewController(for task: OCKAnyTask, on date: Date) -> [UIViewController]? { + + var query = OCKEventQuery(for: Date()) + query.taskIDs = [task.id] + switch task.id { - case TaskID.steps: - let view = NumericProgressTaskView( - task: task, - eventQuery: OCKEventQuery(for: date), - storeManager: self.storeManager) + /* case TaskID.steps: + let title = task.title + let firstOutcome = event.outcome?.sortedOutcomeValuesByRecency().values.first + let computedProgress = event.computeProgress() + let progress = firstOutcome?.doubleValue ?? 0.0 + let goal: Double = progress / computedProgress.fractionCompleted + let isCompleted = event.computeProgress().isCompleted + let view = NumericProgressTaskView(title: Text(title ?? ""), + progress: Text("\(progress)"), + goal: Text("\(goal)"), + isComplete: isCompleted) .padding([.vertical], 20) .careKitStyle(CustomStylerKey.defaultValue) - return [view.formattedHostingController()] + return [view.formattedHostingController()] */ case TaskID.stretch: - return [OCKInstructionsTaskViewController(task: task, - eventQuery: .init(for: date), - storeManager: self.storeManager)] + return [OCKInstructionsTaskViewController(query: query, + store: self.store)] case TaskID.kegels: /* Since the kegel task is only scheduled every other day, there will be cases where it is not contained in the tasks array returned from the query. */ - return [OCKSimpleTaskViewController(task: task, - eventQuery: .init(for: date), - storeManager: self.storeManager)] + return [OCKSimpleTaskViewController(query: query, + store: self.store)] // Create a card for the doxylamine task if there are events for it on this day. case TaskID.doxylamine: - return [OCKChecklistTaskViewController( - task: task, - eventQuery: .init(for: date), - storeManager: self.storeManager)] + return [OCKChecklistTaskViewController(query: query, + store: self.store)] case TaskID.nausea: var cards = [UIViewController]() @@ -216,26 +223,28 @@ class CareViewController: OCKDailyPageViewController { legendTitle: "Nausea", gradientStartColor: nauseaGradientStart, gradientEndColor: nauseaGradientEnd, - markerSize: 10, - eventAggregator: OCKEventAggregator.countOutcomeValues) + markerSize: 10) { event in + event.computeProgress(by: .summingOutcomeValues) + } let doxylamineDataSeries = OCKDataSeriesConfiguration( taskID: task.id, legendTitle: "Doxylamine", gradientStartColor: .systemGray2, gradientEndColor: .systemGray, - markerSize: 10, - eventAggregator: OCKEventAggregator.countOutcomeValues) + markerSize: 10) { event in + event.computeProgress(by: .summingOutcomeValues) + } let insightsCard = OCKCartesianChartViewController( plotType: .bar, selectedDate: date, configurations: [nauseaDataSeries, doxylamineDataSeries], - storeManager: self.storeManager) + store: self.store) - insightsCard.chartView.headerView.titleLabel.text = "Nausea & Doxylamine Intake" - insightsCard.chartView.headerView.detailLabel.text = "This Week" - insightsCard.chartView.headerView.accessibilityLabel = "Nausea & Doxylamine Intake, This Week" + insightsCard.typedView.headerView.titleLabel.text = "Nausea & Doxylamine Intake" + insightsCard.typedView.headerView.detailLabel.text = "This Week" + insightsCard.typedView.headerView.accessibilityLabel = "Nausea & Doxylamine Intake, This Week" cards.append(insightsCard) /* @@ -243,9 +252,8 @@ class CareViewController: OCKDailyPageViewController { The event query passed into the initializer specifies that only today's log entries should be displayed by this log task view controller. */ - let nauseaCard = OCKButtonLogTaskViewController(task: task, - eventQuery: .init(for: date), - storeManager: self.storeManager) + let nauseaCard = OCKButtonLogTaskViewController(query: query, + store: self.store) cards.append(nauseaCard) return cards @@ -254,18 +262,49 @@ class CareViewController: OCKDailyPageViewController { } } - private func fetchTasks(on date: Date) async -> [OCKAnyTask] { + private func fetchEvents(on date: Date) async throws -> CareStoreQueryResults> { + let (tasks, taskQuery) = await self.fetchTasks(on: date) + return try streamCareStoreEvents(for: tasks, from: taskQuery) + } + + private func fetchTasks(on date: Date) async -> ([OCKAnyTask], OCKTaskQuery) { var query = OCKTaskQuery(for: date) query.excludesTasksWithNoEvents = true do { - let tasks = try await storeManager.store.fetchAnyTasks(query: query) + let tasks = try await store.fetchAnyTasks(query: query) let orderedTasks = TaskID.ordered.compactMap { orderedTaskID in tasks.first(where: { $0.id == orderedTaskID }) } - return orderedTasks + return (orderedTasks, query) } catch { Logger.feed.error("\(error, privacy: .public)") - return [] + return ([], query) + } + } + + private func streamCareStoreEvents(for tasks: [OCKAnyTask], + // swiftlint:disable:next line_length + from query: OCKTaskQuery) throws -> CareStoreQueryResults> { + guard tasks.count > 0 else { + throw AppError.errorString("No current tasks") + } + guard let dateInterval = query.dateInterval else { + throw AppError.errorString("Task query should have a set date") + } + // var events = [CareStoreFetchRequest]() + var eventQuery = OCKEventQuery(dateInterval: dateInterval) + eventQuery.taskIDs = tasks.map { $0.id } + guard let store = AppDelegateKey.defaultValue?.store else { + throw AppError.couldntBeUnwrapped } + return store.events(matching: eventQuery) + /*if let healthKitStore = AppDelegateKey.defaultValue?.healthKitStore { + let storeEvents = healthKitStore.events(matching: eventQuery) + // other.append(storeEvents) + } */ + /*tasks.forEach { + eventQuery.taskIDs = [$0.id] + events.append(CareStoreFetchRequest(query: eventQuery)) + }*/ } } diff --git a/OCKSample/Main/Contact/ContactView.swift b/OCKSample/Main/Contact/ContactView.swift index aa24f21..64304e9 100644 --- a/OCKSample/Main/Contact/ContactView.swift +++ b/OCKSample/Main/Contact/ContactView.swift @@ -30,7 +30,7 @@ struct ContactView: UIViewControllerRepresentable { } func createViewController() -> UIViewController { - OCKContactsListViewController(storeManager: appDelegate.storeManager) + OCKContactsListViewController(store: appDelegate.storeCoordinator) } } diff --git a/OCKSample/Main/Login/LoginViewModel.swift b/OCKSample/Main/Login/LoginViewModel.swift index 4ea412f..f5944c7 100644 --- a/OCKSample/Main/Login/LoginViewModel.swift +++ b/OCKSample/Main/Login/LoginViewModel.swift @@ -121,26 +121,21 @@ class LoginViewModel: ObservableObject { throw AppError.couldntBeUnwrapped } try await appDelegate.setupRemotes(uuid: remoteUUID) - let storeManager = appDelegate.storeManager var newPatient = OCKPatient(remoteUUID: remoteUUID, id: remoteUUID.uuidString, givenName: firstName, familyName: lastName) newPatient.userType = type - let savedPatient = try await storeManager.store.addAnyPatient(newPatient) - guard let patient = savedPatient as? OCKPatient else { - throw AppError.couldntCast - } - - try await appDelegate.store?.populateSampleData() + let savedPatient = try await appDelegate.store.addPatient(newPatient) + try await appDelegate.store.populateSampleData() try await appDelegate.healthKitStore.populateSampleData() appDelegate.parseRemote.automaticallySynchronizes = true // Post notification to sync NotificationCenter.default.post(.init(name: Notification.Name(rawValue: Constants.requestSync))) Logger.login.info("Successfully added a new Patient") - return patient + return savedPatient } // MARK: User intentional behavior diff --git a/OCKSample/Main/Profile/ProfileView.swift b/OCKSample/Main/Profile/ProfileView.swift index d760da7..515c8fa 100644 --- a/OCKSample/Main/Profile/ProfileView.swift +++ b/OCKSample/Main/Profile/ProfileView.swift @@ -74,7 +74,8 @@ struct ProfileView: View { }) .background(Color(.red)) .cornerRadius(15) - }.onReceive(viewModel.$patient) { patient in + } + .onReceive(viewModel.$patient) { patient in if let currentFirstName = patient?.name.givenName { firstName = currentFirstName } @@ -84,17 +85,19 @@ struct ProfileView: View { if let currentBirthday = patient?.birthday { birthday = currentBirthday } - }.onReceive(appDelegate.$storeManager) { newStoreManager in - viewModel.updateStoreManager(newStoreManager) - }.onReceive(appDelegate.$isFirstTimeLogin) { _ in - viewModel.updateStoreManager() + } + .onReceive(appDelegate.$store) { newStore in + viewModel.updateStore(newStore) + } + .onReceive(appDelegate.$isFirstTimeLogin) { _ in + viewModel.updateStore() } } } struct ProfileView_Previews: PreviewProvider { static var previews: some View { - ProfileView(viewModel: .init(storeManager: Utility.createPreviewStoreManager()), + ProfileView(viewModel: .init(store: Utility.createPreviewStore()), loginViewModel: .init()) .accentColor(Color(TintColorKey.defaultValue)) } diff --git a/OCKSample/Main/Profile/ProfileViewModel.swift b/OCKSample/Main/Profile/ProfileViewModel.swift index 6134067..21ab5c7 100644 --- a/OCKSample/Main/Profile/ProfileViewModel.swift +++ b/OCKSample/Main/Profile/ProfileViewModel.swift @@ -18,7 +18,7 @@ import Combine class ProfileViewModel: ObservableObject { // MARK: Public read, private write properties @Published private(set) var patient: OCKPatient? - private(set) var storeManager: OCKSynchronizedStoreManager { + private(set) var store: OCKStore { didSet { reloadViewModel() } @@ -27,13 +27,23 @@ class ProfileViewModel: ObservableObject { // MARK: Private read/write properties private var cancellables: Set = [] - init(storeManager: OCKSynchronizedStoreManager? = nil) { - self.storeManager = storeManager ?? StoreManagerKey.defaultValue + init(store: OCKStore? = nil) { + self.store = store ?? AppDelegateKey.defaultValue?.store + ?? .init(name: Constants.noCareStoreName, type: .inMemory) } // MARK: Helpers (private) - private func clearSubscriptions() { - cancellables = [] + + func updateStore(_ store: OCKStore? = nil) { + guard let store = store else { + guard let appDelegateStore = AppDelegateKey.defaultValue?.store else { + Logger.profile.error("Missing AppDelegate store") + return + } + self.store = appDelegateStore + return + } + self.store = store } private func reloadViewModel() { @@ -42,25 +52,12 @@ class ProfileViewModel: ObservableObject { } } - func updateStoreManager(_ storeManager: OCKSynchronizedStoreManager? = nil) { - guard let storeManager = storeManager else { - guard let appDelegateStoreManager = AppDelegateKey.defaultValue?.storeManager else { - Logger.profile.error("Missing AppDelegate storeManager") - return - } - self.storeManager = appDelegateStoreManager - return - } - self.storeManager = storeManager - } - @MainActor private func findAndObserveCurrentProfile() async { guard let uuid = try? await Utility.getRemoteClockUUID() else { Logger.profile.error("Could not get remote uuid for this user") return } - clearSubscriptions() // Build query to search for OCKPatient // swiftlint:disable:next line_length @@ -68,29 +65,15 @@ class ProfileViewModel: ObservableObject { queryForCurrentPatient.ids = [uuid.uuidString] // Search for the current logged in user do { - let foundPatient = try await storeManager.store.fetchAnyPatients(query: queryForCurrentPatient) - guard let currentPatient = foundPatient.first as? OCKPatient else { - // swiftlint:disable:next line_length - Logger.profile.error("Could not find patient with id \"\(uuid)\". It's possible they have never been saved") - return - } - self.observePatient(currentPatient) + var stream = store.patients(matching: queryForCurrentPatient) + let patients = try await stream.next() + self.patient = patients?.first } catch { // swiftlint:disable:next line_length Logger.profile.error("Could not find patient with id \"\(uuid)\". It's possible they have never been saved. Query error: \(error)") } } - @MainActor - private func observePatient(_ patient: OCKPatient) { - storeManager.publisher(forPatient: patient, - categories: [.add, .update, .delete]) - .sink { [weak self] in - self?.patient = $0 as? OCKPatient - } - .store(in: &cancellables) - } - // MARK: User intentional behavior @MainActor func saveProfile(_ first: String, last: String, birth: Date) async throws { @@ -115,12 +98,9 @@ class ProfileViewModel: ObservableObject { } if patientHasBeenUpdated { - let updated = try await storeManager.store.updateAnyPatient(patientToUpdate) + let updated = try await store.updatePatient(patientToUpdate) Logger.profile.info("Successfully updated patient") - guard let updatedPatient = updated as? OCKPatient else { - return - } - self.patient = updatedPatient + self.patient = updated } } else { @@ -133,13 +113,9 @@ class ProfileViewModel: ObservableObject { newPatient.birthday = birth // This is new patient that has never been saved before - let addedPatient = try await storeManager.store.addAnyPatient(newPatient) + let addedPatient = try await store.addPatient(newPatient) Logger.profile.info("Succesffully saved new patient") - guard let addedOCKPatient = addedPatient as? OCKPatient else { - Logger.profile.error("Could not cast to OCKPatient") - return - } - self.patient = addedOCKPatient + self.patient = addedPatient } } } diff --git a/OCKSample/Utility.swift b/OCKSample/Utility.swift index 10708ae..a45bc62 100644 --- a/OCKSample/Utility.swift +++ b/OCKSample/Utility.swift @@ -106,7 +106,7 @@ class Utility { } } - class func createPreviewStoreManager() -> OCKSynchronizedStoreManager { + class func createPreviewStore() -> OCKStore { let store = OCKStore(name: Constants.noCareStoreName, type: .inMemory) let patientId = "preview" Task { @@ -121,7 +121,7 @@ class Utility { try? await store.populateSampleData() } } - return .init(wrapping: store) + return store } #if os(iOS) diff --git a/OCKWatchSample Extension/AppDelegate.swift b/OCKWatchSample Extension/AppDelegate.swift index 0f870ac..ab64450 100644 --- a/OCKWatchSample Extension/AppDelegate.swift +++ b/OCKWatchSample Extension/AppDelegate.swift @@ -15,17 +15,16 @@ import WatchConnectivity import os.log class AppDelegate: NSObject, WKApplicationDelegate, ObservableObject { + // MARK: Public read private write properties - @Published private(set) var storeManager: OCKSynchronizedStoreManager! { + @Published private(set) var store: OCKStore! { willSet { - StoreManagerKey.defaultValue = newValue DispatchQueue.main.async { NotificationCenter.default.post(.init(name: Notification.Name(rawValue: Constants.storeInitialized))) self.objectWillChange.send() } } } - private(set) var store: OCKStore! private(set) var parseRemote: ParseRemote! // MARK: Private read/write properties @@ -91,14 +90,12 @@ class AppDelegate: NSObject, WKApplicationDelegate, ObservableObject { remote: parseRemote) parseRemote?.parseRemoteDelegate = self sessionDelegate.store = store - storeManager = OCKSynchronizedStoreManager(wrapping: store) } else { store = OCKStore(name: Constants.watchOSLocalCareStoreName, remote: phoneRemote) phoneRemote.delegate = self sessionDelegate = LocalSessionDelegate(remote: phoneRemote, store: store) WCSession.default.delegate = sessionDelegate - storeManager = OCKSynchronizedStoreManager(wrapping: store) } WCSession.default.activate() } catch { diff --git a/OCKWatchSample Extension/Main/Care/CareView.swift b/OCKWatchSample Extension/Main/Care/CareView.swift index eefe870..549dbbe 100644 --- a/OCKWatchSample Extension/Main/Care/CareView.swift +++ b/OCKWatchSample Extension/Main/Care/CareView.swift @@ -8,23 +8,28 @@ import CareKit import CareKitStore +import CareKitUI import SwiftUI import os.log struct CareView: View { @EnvironmentObject private var appDelegate: AppDelegate + @CareStoreFetchRequest(query: OCKEventQuery(for: Date())) private var events @StateObject var viewModel = CareViewModel() var body: some View { ScrollView { - SimpleTaskView(taskID: TaskID.kegels, - eventQuery: .init(for: Date()), - storeManager: appDelegate.storeManager) - InstructionsTaskView(taskID: TaskID.stretch, - eventQuery: .init(for: Date()), - storeManager: appDelegate.storeManager) - }.onReceive(appDelegate.$storeManager) { newStoreManager in - viewModel.synchronizeStore(storeManager: newStoreManager) + ForEach(events) { event in + if event.result.task.id == TaskID.kegels { + SimpleTaskView(event: event) + } else if event.result.task.id == TaskID.stretch { + InstructionsTaskView(event: event) + } else { + SimpleTaskView(event: event) + } + } + }.onReceive(appDelegate.$store) { newStore in + viewModel.synchronizeStore(newStore) } } } diff --git a/OCKWatchSample Extension/Main/Care/CareViewModel.swift b/OCKWatchSample Extension/Main/Care/CareViewModel.swift index c8d8445..8fcbdd9 100644 --- a/OCKWatchSample Extension/Main/Care/CareViewModel.swift +++ b/OCKWatchSample Extension/Main/Care/CareViewModel.swift @@ -15,9 +15,9 @@ import os.log class CareViewModel: ObservableObject { - func synchronizeStore(storeManager: OCKSynchronizedStoreManager?) { - guard let store = storeManager?.store as? OCKStore else { - Logger.feed.info("Could not cast to OCKStore") + func synchronizeStore(_ store: OCKStore?) { + guard let store = store else { + Logger.feed.info("OCKStore is nil") return } store.synchronize { error in diff --git a/OCKWatchSample Extension/OCKWatchSampleApp.swift b/OCKWatchSample Extension/OCKWatchSampleApp.swift index c882104..32ae0a6 100644 --- a/OCKWatchSample Extension/OCKWatchSampleApp.swift +++ b/OCKWatchSample Extension/OCKWatchSampleApp.swift @@ -11,7 +11,7 @@ import SwiftUI @main struct OCKWatchSampleApp: App { - @WKApplicationDelegateAdaptor private var delegate: AppDelegate + @WKApplicationDelegateAdaptor private var appDelegate: AppDelegate @Environment(\.scenePhase) private var scenePhase @Environment(\.tintColor) private var tintColor @Environment(\.customStyler) private var style @@ -19,7 +19,8 @@ struct OCKWatchSampleApp: App { @SceneBuilder var body: some Scene { WindowGroup { MainView() - .environment(\.appDelegate, delegate) + .environment(\.appDelegate, appDelegate) + .environment(\.careStore, appDelegate.store) .accentColor(Color(tintColor)) .careKitStyle(style) } From 9aa44540f67f9f602dab3d642e9b1dae79835084 Mon Sep 17 00:00:00 2001 From: Corey Date: Sat, 8 Apr 2023 21:54:46 -0400 Subject: [PATCH 02/52] nits --- .../OCKWatchSample (Notification).xcscheme | 96 +++++++++++++++++++ .../xcschemes/OCKWatchSample.xcscheme | 93 ++++++++++++++++++ OCKSample/AppDelegate.swift | 1 - .../OCKWatchSampleApp.swift | 2 +- 4 files changed, 190 insertions(+), 2 deletions(-) create mode 100644 OCKSample.xcodeproj/xcshareddata/xcschemes/OCKWatchSample (Notification).xcscheme create mode 100644 OCKSample.xcodeproj/xcshareddata/xcschemes/OCKWatchSample.xcscheme diff --git a/OCKSample.xcodeproj/xcshareddata/xcschemes/OCKWatchSample (Notification).xcscheme b/OCKSample.xcodeproj/xcshareddata/xcschemes/OCKWatchSample (Notification).xcscheme new file mode 100644 index 0000000..62e77f5 --- /dev/null +++ b/OCKSample.xcodeproj/xcshareddata/xcschemes/OCKWatchSample (Notification).xcscheme @@ -0,0 +1,96 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/OCKSample.xcodeproj/xcshareddata/xcschemes/OCKWatchSample.xcscheme b/OCKSample.xcodeproj/xcshareddata/xcschemes/OCKWatchSample.xcscheme new file mode 100644 index 0000000..299222c --- /dev/null +++ b/OCKSample.xcodeproj/xcshareddata/xcschemes/OCKWatchSample.xcscheme @@ -0,0 +1,93 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/OCKSample/AppDelegate.swift b/OCKSample/AppDelegate.swift index a075b05..613b757 100644 --- a/OCKSample/AppDelegate.swift +++ b/OCKSample/AppDelegate.swift @@ -57,7 +57,6 @@ class AppDelegate: UIResponder, ObservableObject { } @Published private(set) var store: OCKStore! = .init(name: Constants.noCareStoreName, type: .inMemory) { willSet { - // StoreCoordinatorKey.defaultValue = newValue DispatchQueue.main.async { self.objectWillChange.send() } diff --git a/OCKWatchSample Extension/OCKWatchSampleApp.swift b/OCKWatchSample Extension/OCKWatchSampleApp.swift index 32ae0a6..e6ee8c8 100644 --- a/OCKWatchSample Extension/OCKWatchSampleApp.swift +++ b/OCKWatchSample Extension/OCKWatchSampleApp.swift @@ -20,7 +20,7 @@ struct OCKWatchSampleApp: App { WindowGroup { MainView() .environment(\.appDelegate, appDelegate) - .environment(\.careStore, appDelegate.store) + // .environment(\.careStore, appDelegate.store) .accentColor(Color(tintColor)) .careKitStyle(style) } From f6491b04e064529e487c0173663bf442f9e2e728 Mon Sep 17 00:00:00 2001 From: Corey Date: Sat, 8 Apr 2023 22:17:56 -0400 Subject: [PATCH 03/52] add stream coordinator --- OCKSample/Main/Care/CareViewController.swift | 29 +++++++------------- 1 file changed, 10 insertions(+), 19 deletions(-) diff --git a/OCKSample/Main/Care/CareViewController.swift b/OCKSample/Main/Care/CareViewController.swift index efcd4d7..45a7085 100644 --- a/OCKSample/Main/Care/CareViewController.swift +++ b/OCKSample/Main/Care/CareViewController.swift @@ -262,11 +262,6 @@ class CareViewController: OCKDailyPageViewController { } } - private func fetchEvents(on date: Date) async throws -> CareStoreQueryResults> { - let (tasks, taskQuery) = await self.fetchTasks(on: date) - return try streamCareStoreEvents(for: tasks, from: taskQuery) - } - private func fetchTasks(on date: Date) async -> ([OCKAnyTask], OCKTaskQuery) { var query = OCKTaskQuery(for: date) query.excludesTasksWithNoEvents = true @@ -281,30 +276,26 @@ class CareViewController: OCKDailyPageViewController { } } - private func streamCareStoreEvents(for tasks: [OCKAnyTask], - // swiftlint:disable:next line_length - from query: OCKTaskQuery) throws -> CareStoreQueryResults> { + private func streamEvents(on date: Date) async throws -> CareStoreQueryResults { + let (tasks, taskQuery) = await self.fetchTasks(on: date) + return try streamCoordinatorEvents(for: tasks, from: taskQuery) + } + + private func streamCoordinatorEvents(for tasks: [OCKAnyTask], + // swiftlint:disable:next line_length + from query: OCKTaskQuery) throws -> CareStoreQueryResults { guard tasks.count > 0 else { throw AppError.errorString("No current tasks") } guard let dateInterval = query.dateInterval else { throw AppError.errorString("Task query should have a set date") } - // var events = [CareStoreFetchRequest]() var eventQuery = OCKEventQuery(dateInterval: dateInterval) eventQuery.taskIDs = tasks.map { $0.id } - guard let store = AppDelegateKey.defaultValue?.store else { + guard let store = store as? OCKStoreCoordinator else { throw AppError.couldntBeUnwrapped } - return store.events(matching: eventQuery) - /*if let healthKitStore = AppDelegateKey.defaultValue?.healthKitStore { - let storeEvents = healthKitStore.events(matching: eventQuery) - // other.append(storeEvents) - } */ - /*tasks.forEach { - eventQuery.taskIDs = [$0.id] - events.append(CareStoreFetchRequest(query: eventQuery)) - }*/ + return store.anyEvents(matching: eventQuery) } } From f24d23148492044b9d681b5cbd1cd506959d42cb Mon Sep 17 00:00:00 2001 From: Corey Date: Sun, 9 Apr 2023 01:18:47 -0400 Subject: [PATCH 04/52] simplify views and viewModels --- OCKSample/Main/Care/CareViewController.swift | 11 ++- OCKSample/Main/MainTabView.swift | 11 +++ OCKSample/Main/Profile/ProfileView.swift | 41 +++------ OCKSample/Main/Profile/ProfileViewModel.swift | 92 ++++++------------- .../Main/Care/CareView.swift | 10 +- .../Main/Care/CareViewModel.swift | 14 +-- OCKWatchSample Extension/Main/MainView.swift | 16 ++++ 7 files changed, 83 insertions(+), 112 deletions(-) diff --git a/OCKSample/Main/Care/CareViewController.swift b/OCKSample/Main/Care/CareViewController.swift index 45a7085..6703ae1 100644 --- a/OCKSample/Main/Care/CareViewController.swift +++ b/OCKSample/Main/Care/CareViewController.swift @@ -178,7 +178,12 @@ class CareViewController: OCKDailyPageViewController { query.taskIDs = [task.id] switch task.id { - /* case TaskID.steps: + case TaskID.steps: + /* + guard let event = CareStoreFetchRequest(query: query).wrappedValue.first else { + return nil + } + let view = NumericProgressTaskView(event: event) let title = task.title let firstOutcome = event.outcome?.sortedOutcomeValuesByRecency().values.first let computedProgress = event.computeProgress() @@ -192,7 +197,8 @@ class CareViewController: OCKDailyPageViewController { .padding([.vertical], 20) .careKitStyle(CustomStylerKey.defaultValue) - return [view.formattedHostingController()] */ + return [view.formattedHostingController()]*/ + return nil case TaskID.stretch: return [OCKInstructionsTaskViewController(query: query, store: self.store)] @@ -282,7 +288,6 @@ class CareViewController: OCKDailyPageViewController { } private func streamCoordinatorEvents(for tasks: [OCKAnyTask], - // swiftlint:disable:next line_length from query: OCKTaskQuery) throws -> CareStoreQueryResults { guard tasks.count > 0 else { throw AppError.errorString("No current tasks") diff --git a/OCKSample/Main/MainTabView.swift b/OCKSample/Main/MainTabView.swift index d33625b..e4d1bcd 100644 --- a/OCKSample/Main/MainTabView.swift +++ b/OCKSample/Main/MainTabView.swift @@ -9,10 +9,14 @@ // This was built using tutorial: https://www.hackingwithswift.com/books/ios-swiftui/creating-tabs-with-tabview-and-tabitem import SwiftUI +import CareKitStore struct MainTabView: View { + @EnvironmentObject private var appDelegate: AppDelegate @ObservedObject var loginViewModel: LoginViewModel @State private var selectedTab = 0 + @State private var store = OCKStore(name: Constants.noCareStoreName, + type: .inMemory) var body: some View { TabView(selection: $selectedTab) { @@ -51,6 +55,13 @@ struct MainTabView: View { } } .tag(2) + .environment(\.careStore, store) + .onReceive(appDelegate.$store) { newStore in + guard let newStore = newStore else { + return + } + store = newStore + } } .navigationBarHidden(true) } diff --git a/OCKSample/Main/Profile/ProfileView.swift b/OCKSample/Main/Profile/ProfileView.swift index 515c8fa..5e927d9 100644 --- a/OCKSample/Main/Profile/ProfileView.swift +++ b/OCKSample/Main/Profile/ProfileView.swift @@ -13,27 +13,28 @@ import CareKit import os.log struct ProfileView: View { - @EnvironmentObject private var appDelegate: AppDelegate + @CareStoreFetchRequest(query: OCKPatientQuery(for: Date())) private var patients @StateObject var viewModel = ProfileViewModel() @ObservedObject var loginViewModel: LoginViewModel - @State var firstName = "" - @State var lastName = "" - @State var birthday = Date() var body: some View { VStack { VStack(alignment: .leading) { - TextField("First Name", text: $firstName) + TextField("First Name", + text: $viewModel.firstName) .padding() .cornerRadius(20.0) .shadow(radius: 10.0, x: 20, y: 10) - TextField("Last Name", text: $lastName) + TextField("Last Name", + text: $viewModel.lastName) .padding() .cornerRadius(20.0) .shadow(radius: 10.0, x: 20, y: 10) - DatePicker("Birthday", selection: $birthday, displayedComponents: [DatePickerComponents.date]) + DatePicker("Birthday", + selection: $viewModel.birthday, + displayedComponents: [DatePickerComponents.date]) .padding() .cornerRadius(20.0) .shadow(radius: 10.0, x: 20, y: 10) @@ -42,9 +43,7 @@ struct ProfileView: View { Button(action: { Task { do { - try await viewModel.saveProfile(firstName, - last: lastName, - birth: birthday) + try await viewModel.saveProfile() } catch { Logger.profile.error("Error saving profile: \(error)") } @@ -75,30 +74,20 @@ struct ProfileView: View { .background(Color(.red)) .cornerRadius(15) } - .onReceive(viewModel.$patient) { patient in - if let currentFirstName = patient?.name.givenName { - firstName = currentFirstName + .onReceive(patients.publisher) { patient in + guard let patient = patient.result as? OCKPatient else { + return } - if let currentLastName = patient?.name.familyName { - lastName = currentLastName - } - if let currentBirthday = patient?.birthday { - birthday = currentBirthday - } - } - .onReceive(appDelegate.$store) { newStore in - viewModel.updateStore(newStore) - } - .onReceive(appDelegate.$isFirstTimeLogin) { _ in - viewModel.updateStore() + viewModel.patient = patient } } } struct ProfileView_Previews: PreviewProvider { static var previews: some View { - ProfileView(viewModel: .init(store: Utility.createPreviewStore()), + ProfileView(viewModel: .init(), loginViewModel: .init()) .accentColor(Color(TintColorKey.defaultValue)) + .environment(\.careStore, Utility.createPreviewStore()) } } diff --git a/OCKSample/Main/Profile/ProfileViewModel.swift b/OCKSample/Main/Profile/ProfileViewModel.swift index 21ab5c7..00e0696 100644 --- a/OCKSample/Main/Profile/ProfileViewModel.swift +++ b/OCKSample/Main/Profile/ProfileViewModel.swift @@ -16,89 +16,49 @@ import os.log import Combine class ProfileViewModel: ObservableObject { - // MARK: Public read, private write properties - @Published private(set) var patient: OCKPatient? - private(set) var store: OCKStore { - didSet { - reloadViewModel() - } - } - - // MARK: Private read/write properties - private var cancellables: Set = [] - - init(store: OCKStore? = nil) { - self.store = store ?? AppDelegateKey.defaultValue?.store - ?? .init(name: Constants.noCareStoreName, type: .inMemory) - } - - // MARK: Helpers (private) - - func updateStore(_ store: OCKStore? = nil) { - guard let store = store else { - guard let appDelegateStore = AppDelegateKey.defaultValue?.store else { - Logger.profile.error("Missing AppDelegate store") - return + // MARK: Public read/write properties + var firstName = "" + var lastName = "" + var birthday = Date() + var patient: OCKPatient? { + willSet { + if let currentFirstName = newValue?.name.givenName { + firstName = currentFirstName + } + if let currentLastName = newValue?.name.familyName { + lastName = currentLastName + } + if let currentBirthday = newValue?.birthday { + birthday = currentBirthday } - self.store = appDelegateStore - return - } - self.store = store - } - - private func reloadViewModel() { - Task { - _ = await findAndObserveCurrentProfile() - } - } - - @MainActor - private func findAndObserveCurrentProfile() async { - guard let uuid = try? await Utility.getRemoteClockUUID() else { - Logger.profile.error("Could not get remote uuid for this user") - return - } - - // Build query to search for OCKPatient - // swiftlint:disable:next line_length - var queryForCurrentPatient = OCKPatientQuery(for: Date()) // This makes the query for the current version of Patient - queryForCurrentPatient.ids = [uuid.uuidString] // Search for the current logged in user - - do { - var stream = store.patients(matching: queryForCurrentPatient) - let patients = try await stream.next() - self.patient = patients?.first - } catch { - // swiftlint:disable:next line_length - Logger.profile.error("Could not find patient with id \"\(uuid)\". It's possible they have never been saved. Query error: \(error)") } } // MARK: User intentional behavior @MainActor - func saveProfile(_ first: String, last: String, birth: Date) async throws { + func saveProfile() async throws { if var patientToUpdate = patient { // If there is a currentPatient that was fetched, check to see if any of the fields changed var patientHasBeenUpdated = false - if patient?.name.givenName != first { + if patient?.name.givenName != firstName { patientHasBeenUpdated = true - patientToUpdate.name.givenName = first + patientToUpdate.name.givenName = firstName } - if patient?.name.familyName != last { + if patient?.name.familyName != lastName { patientHasBeenUpdated = true - patientToUpdate.name.familyName = last + patientToUpdate.name.familyName = lastName } - if patient?.birthday != birth { + if patient?.birthday != birthday { patientHasBeenUpdated = true - patientToUpdate.birthday = birth + patientToUpdate.birthday = birthday } if patientHasBeenUpdated { - let updated = try await store.updatePatient(patientToUpdate) + let updated = try await AppDelegateKey.defaultValue?.store.updatePatient(patientToUpdate) Logger.profile.info("Successfully updated patient") self.patient = updated } @@ -109,11 +69,13 @@ class ProfileViewModel: ObservableObject { return } - var newPatient = OCKPatient(id: remoteUUID, givenName: first, familyName: last) - newPatient.birthday = birth + var newPatient = OCKPatient(id: remoteUUID, + givenName: firstName, + familyName: lastName) + newPatient.birthday = birthday // This is new patient that has never been saved before - let addedPatient = try await store.addPatient(newPatient) + let addedPatient = try await AppDelegateKey.defaultValue?.store.addPatient(newPatient) Logger.profile.info("Succesffully saved new patient") self.patient = addedPatient } diff --git a/OCKWatchSample Extension/Main/Care/CareView.swift b/OCKWatchSample Extension/Main/Care/CareView.swift index 549dbbe..076c033 100644 --- a/OCKWatchSample Extension/Main/Care/CareView.swift +++ b/OCKWatchSample Extension/Main/Care/CareView.swift @@ -13,7 +13,7 @@ import SwiftUI import os.log struct CareView: View { - @EnvironmentObject private var appDelegate: AppDelegate + @CareStoreFetchRequest(query: OCKEventQuery(for: Date())) private var events @StateObject var viewModel = CareViewModel() @@ -24,12 +24,12 @@ struct CareView: View { SimpleTaskView(event: event) } else if event.result.task.id == TaskID.stretch { InstructionsTaskView(event: event) - } else { - SimpleTaskView(event: event) } } - }.onReceive(appDelegate.$store) { newStore in - viewModel.synchronizeStore(newStore) + }.onAppear { + var query = events.query + query.taskIDs = [TaskID.kegels, TaskID.stretch] + events.query = query } } } diff --git a/OCKWatchSample Extension/Main/Care/CareViewModel.swift b/OCKWatchSample Extension/Main/Care/CareViewModel.swift index 8fcbdd9..14358a5 100644 --- a/OCKWatchSample Extension/Main/Care/CareViewModel.swift +++ b/OCKWatchSample Extension/Main/Care/CareViewModel.swift @@ -13,16 +13,4 @@ import CareKitStore import WatchConnectivity import os.log -class CareViewModel: ObservableObject { - - func synchronizeStore(_ store: OCKStore?) { - guard let store = store else { - Logger.feed.info("OCKStore is nil") - return - } - store.synchronize { error in - let errorString = error?.localizedDescription ?? "Successful sync with remote!" - Logger.feed.info("\(errorString)") - } - } -} +class CareViewModel: ObservableObject {} diff --git a/OCKWatchSample Extension/Main/MainView.swift b/OCKWatchSample Extension/Main/MainView.swift index f598ab7..7032623 100644 --- a/OCKWatchSample Extension/Main/MainView.swift +++ b/OCKWatchSample Extension/Main/MainView.swift @@ -7,10 +7,15 @@ // import SwiftUI +import CareKitStore +import os.log struct MainView: View { + @EnvironmentObject private var appDelegate: AppDelegate @StateObject var loginViewModel = LoginViewModel() @State var path = [MainViewPath]() + @State private var store = OCKStore(name: Constants.noCareStoreName, + type: .inMemory) var body: some View { NavigationStack(path: $path) { @@ -19,6 +24,17 @@ struct MainView: View { switch destination { case .tabs: CareView() + .environment(\.careStore, store) + .onReceive(appDelegate.$store) { newStore in + guard let newStore = newStore else { + return + } + store = newStore + store.synchronize { error in + let errorString = error?.localizedDescription ?? "Successful sync with remote!" + Logger.feed.info("\(errorString)") + } + } } } .navigationBarHidden(true) From 54b6f57b3785d23c7209c2ea90a5a584cd01acf7 Mon Sep 17 00:00:00 2001 From: Corey Date: Sun, 9 Apr 2023 01:28:48 -0400 Subject: [PATCH 05/52] simplify some more --- OCKSample/AppDelegate.swift | 22 +++++-------------- OCKSample/Constants.swift | 1 - OCKWatchSample Extension/AppDelegate.swift | 8 ++++--- OCKWatchSample Extension/Main/MainView.swift | 4 ---- .../OCKWatchSampleApp.swift | 1 - 5 files changed, 10 insertions(+), 26 deletions(-) diff --git a/OCKSample/AppDelegate.swift b/OCKSample/AppDelegate.swift index 613b757..4bab893 100644 --- a/OCKSample/AppDelegate.swift +++ b/OCKSample/AppDelegate.swift @@ -38,30 +38,16 @@ import WatchConnectivity class AppDelegate: UIResponder, ObservableObject { // MARK: Public read/write properties - @Published var isFirstTimeLogin = false { - willSet { - DispatchQueue.main.async { - self.objectWillChange.send() - } - } - } + @Published var isFirstTimeLogin = false // MARK: Public read private write properties @Published private(set) var storeCoordinator: OCKStoreCoordinator = .init() { willSet { StoreCoordinatorKey.defaultValue = newValue - DispatchQueue.main.async { - self.objectWillChange.send() - } - } - } - @Published private(set) var store: OCKStore! = .init(name: Constants.noCareStoreName, type: .inMemory) { - willSet { - DispatchQueue.main.async { - self.objectWillChange.send() - } + self.objectWillChange.send() } } + @Published private(set) var store: OCKStore! = .init(name: Constants.noCareStoreName, type: .inMemory) private(set) var healthKitStore: OCKHealthKitPassthroughStore! private(set) var parseRemote: ParseRemote! @@ -70,6 +56,7 @@ class AppDelegate: UIResponder, ObservableObject { private lazy var watchRemote = OCKWatchConnectivityPeer() // MARK: Helpers + @MainActor func resetAppToInitialState() { do { try storeCoordinator.reset() @@ -84,6 +71,7 @@ class AppDelegate: UIResponder, ObservableObject { sessionDelegate.store = store } + @MainActor func setupRemotes(uuid: UUID? = nil) async throws { do { if isSyncingWithCloud { diff --git a/OCKSample/Constants.swift b/OCKSample/Constants.swift index b605439..56a5453 100644 --- a/OCKSample/Constants.swift +++ b/OCKSample/Constants.swift @@ -76,7 +76,6 @@ enum Constants { static let finishedAskingForPermission = "finishedAskingForPermission" static let shouldRefreshView = "shouldRefreshView" static let userLoggedIn = "userLoggedIn" - static let storeInitialized = "storeInitialized" static let userTypeKey = "userType" } diff --git a/OCKWatchSample Extension/AppDelegate.swift b/OCKWatchSample Extension/AppDelegate.swift index ab64450..b62839d 100644 --- a/OCKWatchSample Extension/AppDelegate.swift +++ b/OCKWatchSample Extension/AppDelegate.swift @@ -19,10 +19,11 @@ class AppDelegate: NSObject, WKApplicationDelegate, ObservableObject { // MARK: Public read private write properties @Published private(set) var store: OCKStore! { willSet { - DispatchQueue.main.async { - NotificationCenter.default.post(.init(name: Notification.Name(rawValue: Constants.storeInitialized))) - self.objectWillChange.send() + newValue.synchronize { error in + let errorString = error?.localizedDescription ?? "Successful sync with remote!" + Logger.appDelegate.info("\(errorString)") } + self.objectWillChange.send() } } private(set) var parseRemote: ParseRemote! @@ -71,6 +72,7 @@ class AppDelegate: NSObject, WKApplicationDelegate, ObservableObject { } } + @MainActor func setupRemotes(uuid: UUID? = nil) async throws { do { if isSyncingWithCloud { diff --git a/OCKWatchSample Extension/Main/MainView.swift b/OCKWatchSample Extension/Main/MainView.swift index 7032623..7103fcf 100644 --- a/OCKWatchSample Extension/Main/MainView.swift +++ b/OCKWatchSample Extension/Main/MainView.swift @@ -30,10 +30,6 @@ struct MainView: View { return } store = newStore - store.synchronize { error in - let errorString = error?.localizedDescription ?? "Successful sync with remote!" - Logger.feed.info("\(errorString)") - } } } } diff --git a/OCKWatchSample Extension/OCKWatchSampleApp.swift b/OCKWatchSample Extension/OCKWatchSampleApp.swift index e6ee8c8..c0f3a60 100644 --- a/OCKWatchSample Extension/OCKWatchSampleApp.swift +++ b/OCKWatchSample Extension/OCKWatchSampleApp.swift @@ -20,7 +20,6 @@ struct OCKWatchSampleApp: App { WindowGroup { MainView() .environment(\.appDelegate, appDelegate) - // .environment(\.careStore, appDelegate.store) .accentColor(Color(tintColor)) .careKitStyle(style) } From 97a585a54b16c11196ce247fee946d0f06573a7e Mon Sep 17 00:00:00 2001 From: Corey Date: Sun, 9 Apr 2023 01:37:10 -0400 Subject: [PATCH 06/52] more simplifications --- .../Extensions/AppDelegate+ParseRemoteDelegate.swift | 5 ++--- OCKSample/Main/Login/LoginViewModel.swift | 9 +++------ 2 files changed, 5 insertions(+), 9 deletions(-) diff --git a/OCKSample/Extensions/AppDelegate+ParseRemoteDelegate.swift b/OCKSample/Extensions/AppDelegate+ParseRemoteDelegate.swift index 86388db..0a2b958 100644 --- a/OCKSample/Extensions/AppDelegate+ParseRemoteDelegate.swift +++ b/OCKSample/Extensions/AppDelegate+ParseRemoteDelegate.swift @@ -17,11 +17,10 @@ extension AppDelegate: ParseRemoteDelegate { NotificationCenter.default.post(.init(name: Notification.Name(rawValue: Constants.requestSync))) } + @MainActor func successfullyPushedDataToCloud() { if isFirstTimeLogin { - DispatchQueue.main.async { - self.isFirstTimeLogin.toggle() - } + self.isFirstTimeLogin.toggle() } #if !targetEnvironment(simulator) // watchOS 9 needs to be sent messages for updates on real devices diff --git a/OCKSample/Main/Login/LoginViewModel.swift b/OCKSample/Main/Login/LoginViewModel.swift index f5944c7..d307dbf 100644 --- a/OCKSample/Main/Login/LoginViewModel.swift +++ b/OCKSample/Main/Login/LoginViewModel.swift @@ -39,20 +39,17 @@ class LoginViewModel: ObservableObject { } // MARK: Helpers (private) + @MainActor private func checkStatus() async { let isLoggedOut = self.isLoggedOut do { _ = try await User.current() if isLoggedOut { - DispatchQueue.main.async { - self.isLoggedOut = false - } + self.isLoggedOut = false } } catch { if !isLoggedOut { - DispatchQueue.main.async { - self.isLoggedOut = true - } + self.isLoggedOut = true } } } From 088fb5121d907a49d968391b7f4ef319f7213d74 Mon Sep 17 00:00:00 2001 From: Corey Date: Sun, 9 Apr 2023 09:33:13 -0400 Subject: [PATCH 07/52] simplify profile --- OCKSample/Main/Care/CareViewController.swift | 29 ++------- OCKSample/Main/Profile/ProfileView.swift | 9 +-- OCKSample/Main/Profile/ProfileViewModel.swift | 64 +++++++++---------- 3 files changed, 36 insertions(+), 66 deletions(-) diff --git a/OCKSample/Main/Care/CareViewController.swift b/OCKSample/Main/Care/CareViewController.swift index 6703ae1..4fd931b 100644 --- a/OCKSample/Main/Care/CareViewController.swift +++ b/OCKSample/Main/Care/CareViewController.swift @@ -150,7 +150,7 @@ class CareViewController: OCKDailyPageViewController { } Task { - let (tasks, _) = await self.fetchTasks(on: date) + let tasks = await self.fetchTasks(on: date) tasks.compactMap { let cards = self.taskViewController(for: $0, on: date) @@ -268,40 +268,19 @@ class CareViewController: OCKDailyPageViewController { } } - private func fetchTasks(on date: Date) async -> ([OCKAnyTask], OCKTaskQuery) { + private func fetchTasks(on date: Date) async -> [OCKAnyTask] { var query = OCKTaskQuery(for: date) query.excludesTasksWithNoEvents = true do { let tasks = try await store.fetchAnyTasks(query: query) let orderedTasks = TaskID.ordered.compactMap { orderedTaskID in tasks.first(where: { $0.id == orderedTaskID }) } - return (orderedTasks, query) + return orderedTasks } catch { Logger.feed.error("\(error, privacy: .public)") - return ([], query) + return [] } } - - private func streamEvents(on date: Date) async throws -> CareStoreQueryResults { - let (tasks, taskQuery) = await self.fetchTasks(on: date) - return try streamCoordinatorEvents(for: tasks, from: taskQuery) - } - - private func streamCoordinatorEvents(for tasks: [OCKAnyTask], - from query: OCKTaskQuery) throws -> CareStoreQueryResults { - guard tasks.count > 0 else { - throw AppError.errorString("No current tasks") - } - guard let dateInterval = query.dateInterval else { - throw AppError.errorString("Task query should have a set date") - } - var eventQuery = OCKEventQuery(dateInterval: dateInterval) - eventQuery.taskIDs = tasks.map { $0.id } - guard let store = store as? OCKStoreCoordinator else { - throw AppError.couldntBeUnwrapped - } - return store.anyEvents(matching: eventQuery) - } } private extension View { diff --git a/OCKSample/Main/Profile/ProfileView.swift b/OCKSample/Main/Profile/ProfileView.swift index 5e927d9..0c556ef 100644 --- a/OCKSample/Main/Profile/ProfileView.swift +++ b/OCKSample/Main/Profile/ProfileView.swift @@ -13,9 +13,9 @@ import CareKit import os.log struct ProfileView: View { - @CareStoreFetchRequest(query: OCKPatientQuery(for: Date())) private var patients @StateObject var viewModel = ProfileViewModel() @ObservedObject var loginViewModel: LoginViewModel + @CareStoreFetchRequest(query: OCKPatientQuery(for: Date())) private var patients var body: some View { VStack { @@ -74,11 +74,8 @@ struct ProfileView: View { .background(Color(.red)) .cornerRadius(15) } - .onReceive(patients.publisher) { patient in - guard let patient = patient.result as? OCKPatient else { - return - } - viewModel.patient = patient + .onReceive(patients.publisher) { publishedPatient in + viewModel.updatePatient(publishedPatient.result) } } } diff --git a/OCKSample/Main/Profile/ProfileViewModel.swift b/OCKSample/Main/Profile/ProfileViewModel.swift index 00e0696..0b2b47a 100644 --- a/OCKSample/Main/Profile/ProfileViewModel.swift +++ b/OCKSample/Main/Profile/ProfileViewModel.swift @@ -16,7 +16,9 @@ import os.log import Combine class ProfileViewModel: ObservableObject { + // MARK: Public read/write properties + var firstName = "" var lastName = "" var birthday = Date() @@ -35,49 +37,41 @@ class ProfileViewModel: ObservableObject { } // MARK: User intentional behavior - @MainActor - func saveProfile() async throws { - if var patientToUpdate = patient { - // If there is a currentPatient that was fetched, check to see if any of the fields changed - var patientHasBeenUpdated = false + func saveProfile() async throws { - if patient?.name.givenName != firstName { - patientHasBeenUpdated = true - patientToUpdate.name.givenName = firstName - } + guard var patientToUpdate = patient else { + throw AppError.errorString("The profile is missing the Patient") + } - if patient?.name.familyName != lastName { - patientHasBeenUpdated = true - patientToUpdate.name.familyName = lastName - } + // If there is a currentPatient that was fetched, check to see if any of the fields changed + var patientHasBeenUpdated = false - if patient?.birthday != birthday { - patientHasBeenUpdated = true - patientToUpdate.birthday = birthday - } + if patient?.name.givenName != firstName { + patientHasBeenUpdated = true + patientToUpdate.name.givenName = firstName + } - if patientHasBeenUpdated { - let updated = try await AppDelegateKey.defaultValue?.store.updatePatient(patientToUpdate) - Logger.profile.info("Successfully updated patient") - self.patient = updated - } + if patient?.name.familyName != lastName { + patientHasBeenUpdated = true + patientToUpdate.name.familyName = lastName + } - } else { - guard let remoteUUID = try? await Utility.getRemoteClockUUID().uuidString else { - Logger.profile.error("The user currently is not logged in") - return - } + if patient?.birthday != birthday { + patientHasBeenUpdated = true + patientToUpdate.birthday = birthday + } - var newPatient = OCKPatient(id: remoteUUID, - givenName: firstName, - familyName: lastName) - newPatient.birthday = birthday + if patientHasBeenUpdated { + _ = try await AppDelegateKey.defaultValue?.store.updatePatient(patientToUpdate) + Logger.profile.info("Successfully updated patient") + } + } - // This is new patient that has never been saved before - let addedPatient = try await AppDelegateKey.defaultValue?.store.addPatient(newPatient) - Logger.profile.info("Succesffully saved new patient") - self.patient = addedPatient + func updatePatient(_ patient: OCKAnyPatient) { + guard let patient = patient as? OCKPatient else { + return } + self.patient = patient } } From 48abdc66baa6c08e1899cd5bf7e5f39cbe532a4a Mon Sep 17 00:00:00 2001 From: Corey Date: Sun, 9 Apr 2023 10:09:08 -0400 Subject: [PATCH 08/52] More clean up --- OCKSample.xcodeproj/project.pbxproj | 4 --- OCKSample/AppDelegate.swift | 26 ++++++++++++------- OCKSample/Main/Profile/ProfileViewModel.swift | 16 +++++++----- OCKWatchSample Extension/AppDelegate.swift | 20 +++++++++----- .../Main/Care/CareView.swift | 7 +---- .../Main/Care/CareViewModel.swift | 16 ------------ .../Main/Login/LoginView.swift | 1 - 7 files changed, 41 insertions(+), 49 deletions(-) delete mode 100644 OCKWatchSample Extension/Main/Care/CareViewModel.swift diff --git a/OCKSample.xcodeproj/project.pbxproj b/OCKSample.xcodeproj/project.pbxproj index 1549763..f465f8d 100644 --- a/OCKSample.xcodeproj/project.pbxproj +++ b/OCKSample.xcodeproj/project.pbxproj @@ -48,7 +48,6 @@ 70CF66E428E1E74C00FAE977 /* TintColorFlipKey.swift in Sources */ = {isa = PBXBuildFile; fileRef = 70CF66E328E1E74C00FAE977 /* TintColorFlipKey.swift */; }; 70CF66E528E1E74C00FAE977 /* TintColorFlipKey.swift in Sources */ = {isa = PBXBuildFile; fileRef = 70CF66E328E1E74C00FAE977 /* TintColorFlipKey.swift */; }; 70DFD80B2567074500B9DB12 /* LoginView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 70BD2B1E254B44DB0030B424 /* LoginView.swift */; }; - 70F03A912786073300E5AFB4 /* CareViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 70F03A902786073300E5AFB4 /* CareViewModel.swift */; }; 70F03A952786093B00E5AFB4 /* OCKHealthKitPassthroughStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = 70F03A942786093B00E5AFB4 /* OCKHealthKitPassthroughStore.swift */; }; 70F03A972786098F00E5AFB4 /* OCKStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = 70F03A962786098F00E5AFB4 /* OCKStore.swift */; }; 70F03A9C27860A2000E5AFB4 /* OCKStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = 70F03A962786098F00E5AFB4 /* OCKStore.swift */; }; @@ -168,7 +167,6 @@ 7083A855279CA40A00B3832E /* PCKUtility.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PCKUtility.swift; sourceTree = ""; }; 70BD2B1E254B44DB0030B424 /* LoginView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoginView.swift; sourceTree = ""; }; 70CF66E328E1E74C00FAE977 /* TintColorFlipKey.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TintColorFlipKey.swift; sourceTree = ""; }; - 70F03A902786073300E5AFB4 /* CareViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CareViewModel.swift; sourceTree = ""; }; 70F03A942786093B00E5AFB4 /* OCKHealthKitPassthroughStore.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OCKHealthKitPassthroughStore.swift; sourceTree = ""; }; 70F03A962786098F00E5AFB4 /* OCKStore.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OCKStore.swift; sourceTree = ""; }; 70F03AA227860AFF00E5AFB4 /* OCKPatient+Parse.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "OCKPatient+Parse.swift"; sourceTree = ""; }; @@ -361,7 +359,6 @@ isa = PBXGroup; children = ( 91AD923A24A4C42D00925D4D /* CareView.swift */, - 70F03A902786073300E5AFB4 /* CareViewModel.swift */, ); path = Care; sourceTree = ""; @@ -726,7 +723,6 @@ 9103AA5227B8C913002C921E /* FontColorKey.swift in Sources */, 707CC719254DA91900116728 /* OCKLocalization.swift in Sources */, 7007759B252229C900EC0EDA /* User.swift in Sources */, - 70F03A912786073300E5AFB4 /* CareViewModel.swift in Sources */, 70A98D68278A2DF1009B58F2 /* TintColorKey.swift in Sources */, 918FDEB9271B493A0045A0EF /* Installation.swift in Sources */, 91AD923F24A4C42D00925D4D /* NotificationController.swift in Sources */, diff --git a/OCKSample/AppDelegate.swift b/OCKSample/AppDelegate.swift index 4bab893..72bd62c 100644 --- a/OCKSample/AppDelegate.swift +++ b/OCKSample/AppDelegate.swift @@ -38,9 +38,11 @@ import WatchConnectivity class AppDelegate: UIResponder, ObservableObject { // MARK: Public read/write properties + @Published var isFirstTimeLogin = false // MARK: Public read private write properties + @Published private(set) var storeCoordinator: OCKStoreCoordinator = .init() { willSet { StoreCoordinatorKey.defaultValue = newValue @@ -52,10 +54,12 @@ class AppDelegate: UIResponder, ObservableObject { private(set) var parseRemote: ParseRemote! // MARK: Private read/write properties + private var sessionDelegate: SessionDelegate! private lazy var watchRemote = OCKWatchConnectivityPeer() // MARK: Helpers + @MainActor func resetAppToInitialState() { do { @@ -67,8 +71,10 @@ class AppDelegate: UIResponder, ObservableObject { storeCoordinator = .init() healthKitStore = nil parseRemote = nil - store = .init(name: Constants.noCareStoreName, type: .inMemory) + let store = OCKStore(name: Constants.noCareStoreName, + type: .inMemory) sessionDelegate.store = store + self.store = store } @MainActor @@ -83,17 +89,19 @@ class AppDelegate: UIResponder, ObservableObject { auto: false, subscribeToServerUpdates: true, defaultACL: try? ParseACL.defaultACL()) - store = OCKStore(name: Constants.iOSParseCareStoreName, - type: .onDisk(), - remote: parseRemote) + let store = OCKStore(name: Constants.iOSParseCareStoreName, + type: .onDisk(), + remote: parseRemote) parseRemote?.parseRemoteDelegate = self sessionDelegate = RemoteSessionDelegate(store: store) + self.store = store } else { - store = OCKStore(name: Constants.iOSLocalCareStoreName, + let store = OCKStore(name: Constants.iOSLocalCareStoreName, type: .onDisk(), remote: watchRemote) watchRemote.delegate = self sessionDelegate = LocalSessionDelegate(remote: watchRemote, store: store) + self.store = store } // Setup communication with watch @@ -101,10 +109,10 @@ class AppDelegate: UIResponder, ObservableObject { WCSession.default.activate() healthKitStore = OCKHealthKitPassthroughStore(store: store) - let coordinator = OCKStoreCoordinator() - coordinator.attach(store: store) - coordinator.attach(eventStore: healthKitStore) - storeCoordinator = coordinator + let storeCoordinator = OCKStoreCoordinator() + storeCoordinator.attach(store: store) + storeCoordinator.attach(eventStore: healthKitStore) + self.storeCoordinator = storeCoordinator } catch { Logger.appDelegate.error("Error setting up remote: \(error)") throw error diff --git a/OCKSample/Main/Profile/ProfileViewModel.swift b/OCKSample/Main/Profile/ProfileViewModel.swift index 0b2b47a..3caacb5 100644 --- a/OCKSample/Main/Profile/ProfileViewModel.swift +++ b/OCKSample/Main/Profile/ProfileViewModel.swift @@ -36,6 +36,15 @@ class ProfileViewModel: ObservableObject { } } + // MARK: Helpers (public) + + func updatePatient(_ patient: OCKAnyPatient) { + guard let patient = patient as? OCKPatient else { + return + } + self.patient = patient + } + // MARK: User intentional behavior func saveProfile() async throws { @@ -67,11 +76,4 @@ class ProfileViewModel: ObservableObject { Logger.profile.info("Successfully updated patient") } } - - func updatePatient(_ patient: OCKAnyPatient) { - guard let patient = patient as? OCKPatient else { - return - } - self.patient = patient - } } diff --git a/OCKWatchSample Extension/AppDelegate.swift b/OCKWatchSample Extension/AppDelegate.swift index b62839d..c1beee1 100644 --- a/OCKWatchSample Extension/AppDelegate.swift +++ b/OCKWatchSample Extension/AppDelegate.swift @@ -17,6 +17,7 @@ import os.log class AppDelegate: NSObject, WKApplicationDelegate, ObservableObject { // MARK: Public read private write properties + @Published private(set) var store: OCKStore! { willSet { newValue.synchronize { error in @@ -29,6 +30,7 @@ class AppDelegate: NSObject, WKApplicationDelegate, ObservableObject { private(set) var parseRemote: ParseRemote! // MARK: Private read/write properties + private var sessionDelegate: SessionDelegate! private lazy var phoneRemote = OCKWatchConnectivityPeer() @@ -87,17 +89,23 @@ class AppDelegate: NSObject, WKApplicationDelegate, ObservableObject { } parseRemote = try await ParseRemote(uuid: uuid, auto: false, - subscribeToServerUpdates: true) - store = OCKStore(name: Constants.watchOSParseCareStoreName, - remote: parseRemote) + subscribeToServerUpdates: true, + defaultACL: try? ParseACL.defaultACL()) + let store = OCKStore(name: Constants.watchOSParseCareStoreName, + type: .onDisk(), + remote: parseRemote) parseRemote?.parseRemoteDelegate = self sessionDelegate.store = store + self.store = store } else { - store = OCKStore(name: Constants.watchOSLocalCareStoreName, - remote: phoneRemote) + let store = OCKStore(name: Constants.watchOSLocalCareStoreName, + type: .onDisk(), + remote: phoneRemote) phoneRemote.delegate = self - sessionDelegate = LocalSessionDelegate(remote: phoneRemote, store: store) + sessionDelegate = LocalSessionDelegate(remote: phoneRemote, + store: store) WCSession.default.delegate = sessionDelegate + self.store = store } WCSession.default.activate() } catch { diff --git a/OCKWatchSample Extension/Main/Care/CareView.swift b/OCKWatchSample Extension/Main/Care/CareView.swift index 076c033..5590461 100644 --- a/OCKWatchSample Extension/Main/Care/CareView.swift +++ b/OCKWatchSample Extension/Main/Care/CareView.swift @@ -13,9 +13,7 @@ import SwiftUI import os.log struct CareView: View { - @CareStoreFetchRequest(query: OCKEventQuery(for: Date())) private var events - @StateObject var viewModel = CareViewModel() var body: some View { ScrollView { @@ -26,10 +24,6 @@ struct CareView: View { InstructionsTaskView(event: event) } } - }.onAppear { - var query = events.query - query.taskIDs = [TaskID.kegels, TaskID.stretch] - events.query = query } } } @@ -38,5 +32,6 @@ struct ContentView_Previews: PreviewProvider { static var previews: some View { CareView() .accentColor(Color(TintColorKey.defaultValue)) + .environment(\.careStore, Utility.createPreviewStore()) } } diff --git a/OCKWatchSample Extension/Main/Care/CareViewModel.swift b/OCKWatchSample Extension/Main/Care/CareViewModel.swift deleted file mode 100644 index 14358a5..0000000 --- a/OCKWatchSample Extension/Main/Care/CareViewModel.swift +++ /dev/null @@ -1,16 +0,0 @@ -// -// CareViewModel.swift -// OCKWatchSample Extension -// -// Created by Corey Baker on 1/5/22. -// Copyright © 2022 Network Reconnaissance Lab. All rights reserved. -// - -import Foundation -import Combine -import CareKit -import CareKitStore -import WatchConnectivity -import os.log - -class CareViewModel: ObservableObject {} diff --git a/OCKWatchSample Extension/Main/Login/LoginView.swift b/OCKWatchSample Extension/Main/Login/LoginView.swift index 131c57f..75d1a9b 100644 --- a/OCKWatchSample Extension/Main/Login/LoginView.swift +++ b/OCKWatchSample Extension/Main/Login/LoginView.swift @@ -9,7 +9,6 @@ import SwiftUI struct LoginView: View { - @EnvironmentObject private var appDelegate: AppDelegate @ObservedObject var viewModel: LoginViewModel var body: some View { From 396d66718e92bca45f40c51f5ac28ea48fe4d034 Mon Sep 17 00:00:00 2001 From: Corey Date: Mon, 10 Apr 2023 13:08:49 -0400 Subject: [PATCH 09/52] Everything working but watch login transition --- OCKSample.xcodeproj/project.pbxproj | 4 +- .../xcshareddata/swiftpm/Package.resolved | 8 +-- OCKSample/AppDelegate.swift | 13 ++++- OCKSample/Constants.swift | 1 + .../AppDelegate+ParseRemoteDelegate.swift | 5 +- .../AppDelegate+UIApplicationDelegate.swift | 1 + OCKSample/Utility.swift | 55 ++++++++++++++++++- OCKWatchSample Extension/AppDelegate.swift | 18 ++++++ 8 files changed, 92 insertions(+), 13 deletions(-) diff --git a/OCKSample.xcodeproj/project.pbxproj b/OCKSample.xcodeproj/project.pbxproj index f465f8d..c186686 100644 --- a/OCKSample.xcodeproj/project.pbxproj +++ b/OCKSample.xcodeproj/project.pbxproj @@ -1190,7 +1190,7 @@ repositoryURL = "https://github.com/cbaker6/CareKit.git"; requirement = { kind = upToNextMajorVersion; - minimumVersion = "3.0.0-alpha.4"; + minimumVersion = "3.0.0-alpha.6"; }; }; 703616FD29CA194900B50BC5 /* XCRemoteSwiftPackageReference "CareKitUtilities" */ = { @@ -1206,7 +1206,7 @@ repositoryURL = "https://github.com/netreconlab/ParseCareKit.git"; requirement = { kind = upToNextMajorVersion; - minimumVersion = "1.0.0-alpha.1"; + minimumVersion = "1.0.0-alpha.7"; }; }; /* End XCRemoteSwiftPackageReference section */ diff --git a/OCKSample.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/OCKSample.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved index 2c0cef5..c078512 100644 --- a/OCKSample.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/OCKSample.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -5,8 +5,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/cbaker6/CareKit.git", "state" : { - "revision" : "863a5e8b3d224c23bff78929e01ae0f5027dcccf", - "version" : "3.0.0-alpha.4" + "revision" : "032abb22324bcd2b80d506aa708f5e035cf1c0a5", + "version" : "3.0.0-alpha.6" } }, { @@ -41,8 +41,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/netreconlab/ParseCareKit.git", "state" : { - "revision" : "f84c6642d5ec7ecd84da116a042e6f48a2bd0605", - "version" : "1.0.0-alpha.1" + "revision" : "1ca887f246b3b81bed4031230836020abbff2130", + "version" : "1.0.0-alpha.7" } }, { diff --git a/OCKSample/AppDelegate.swift b/OCKSample/AppDelegate.swift index 72bd62c..97f73fb 100644 --- a/OCKSample/AppDelegate.swift +++ b/OCKSample/AppDelegate.swift @@ -65,12 +65,19 @@ class AppDelegate: UIResponder, ObservableObject { do { try storeCoordinator.reset() } catch { - Logger.appDelegate.error("Error deleting Coordinator Store: \(error)") + Logger.appDelegate.error("Could not delete Coordinator Store: \(error)") + } + + do { + try self.store?.delete() + } catch { + Logger.utility.error("Could not delete local OCKStore because of error: \(error)") } storeCoordinator = .init() healthKitStore = nil parseRemote = nil + let store = OCKStore(name: Constants.noCareStoreName, type: .inMemory) sessionDelegate.store = store @@ -82,7 +89,7 @@ class AppDelegate: UIResponder, ObservableObject { do { if isSyncingWithCloud { guard let uuid = uuid else { - Logger.appDelegate.error("Error in setupRemotes, uuid is nil") + Logger.appDelegate.error("Could not setupRemotes, uuid is nil") return } parseRemote = try await ParseRemote(uuid: uuid, @@ -114,7 +121,7 @@ class AppDelegate: UIResponder, ObservableObject { storeCoordinator.attach(eventStore: healthKitStore) self.storeCoordinator = storeCoordinator } catch { - Logger.appDelegate.error("Error setting up remote: \(error)") + Logger.appDelegate.error("Could not setup remote: \(error)") throw error } } diff --git a/OCKSample/Constants.swift b/OCKSample/Constants.swift index 56a5453..92e03e3 100644 --- a/OCKSample/Constants.swift +++ b/OCKSample/Constants.swift @@ -77,6 +77,7 @@ enum Constants { static let shouldRefreshView = "shouldRefreshView" static let userLoggedIn = "userLoggedIn" static let userTypeKey = "userType" + static let appName = "ParseCareKitSample" } enum MainViewPath { diff --git a/OCKSample/Extensions/AppDelegate+ParseRemoteDelegate.swift b/OCKSample/Extensions/AppDelegate+ParseRemoteDelegate.swift index 0a2b958..9e5edd8 100644 --- a/OCKSample/Extensions/AppDelegate+ParseRemoteDelegate.swift +++ b/OCKSample/Extensions/AppDelegate+ParseRemoteDelegate.swift @@ -20,7 +20,10 @@ extension AppDelegate: ParseRemoteDelegate { @MainActor func successfullyPushedDataToCloud() { if isFirstTimeLogin { - self.isFirstTimeLogin.toggle() + // BAKER: @MainActor not working (shows purple warning), leave async. + DispatchQueue.main.async { + self.isFirstTimeLogin.toggle() + } } #if !targetEnvironment(simulator) // watchOS 9 needs to be sent messages for updates on real devices diff --git a/OCKSample/Extensions/AppDelegate+UIApplicationDelegate.swift b/OCKSample/Extensions/AppDelegate+UIApplicationDelegate.swift index a839032..d7ff2a4 100644 --- a/OCKSample/Extensions/AppDelegate+UIApplicationDelegate.swift +++ b/OCKSample/Extensions/AppDelegate+UIApplicationDelegate.swift @@ -23,6 +23,7 @@ extension AppDelegate: UIApplicationDelegate { Logger.appDelegate.info("Could not configure Parse Swift: \(error)") return } + await Utility.clearDeviceOnFirstRun() if isSyncingWithCloud { do { _ = try await User.current() diff --git a/OCKSample/Utility.swift b/OCKSample/Utility.swift index a45bc62..0eb45e7 100644 --- a/OCKSample/Utility.swift +++ b/OCKSample/Utility.swift @@ -48,11 +48,11 @@ class Utility { do { try await setDefaultACL() } catch { - Logger.login.error("Could not set defaultACL: \(error)") + Logger.utility.error("Could not set defaultACL: \(error)") } guard let appDelegate = AppDelegateKey.defaultValue else { - Logger.login.error("Could not setup remotes, AppDelegate is nil") + Logger.utility.error("Could not setup remotes, AppDelegate is nil") return } try await appDelegate.setupRemotes(uuid: remoteUUID) @@ -124,6 +124,55 @@ class Utility { return store } + class func clearDeviceOnFirstRun(storeName: String? = nil) async { + // Clear items out of the Keychain on app first run. + if UserDefaults.standard.object(forKey: Constants.appName) == nil { + + if let storeName = storeName { + let store = OCKStore(name: storeName, type: .onDisk()) + do { + try store.delete() + } catch { + Logger.utility.error(""" + Could not delete OCKStore with name \"\(storeName)\" because of error: \(error) + """) + } + } else { + let localStore: OCKStore! + let parseStore: OCKStore! + + #if os(watchOS) + localStore = OCKStore(name: Constants.watchOSLocalCareStoreName, + type: .onDisk()) + parseStore = OCKStore(name: Constants.watchOSParseCareStoreName, + type: .onDisk()) + #else + localStore = OCKStore(name: Constants.iOSLocalCareStoreName, + type: .onDisk()) + parseStore = OCKStore(name: Constants.iOSParseCareStoreName, + type: .onDisk()) + #endif + + do { + try localStore.delete() + } catch { + Logger.utility.error("Could not delete local OCKStore because of error: \(error)") + } + do { + try parseStore.delete() + } catch { + Logger.utility.error("Could not delete parse OCKStore because of error: \(error)") + } + } + + // This is no longer the first run + UserDefaults.standard.setValue(String(Constants.appName), + forKey: Constants.appName) + UserDefaults.standard.synchronize() + try? await User.logout() + } + } + #if os(iOS) class func requestHealthKitPermissions() { AppDelegateKey.defaultValue?.healthKitStore.requestHealthKitPermissionsForAllTasksInStore { error in @@ -134,7 +183,7 @@ class Utility { } return } - Logger.login.error("Error requesting HealthKit permissions: \(error)") + Logger.utility.error("Error requesting HealthKit permissions: \(error)") } } #endif diff --git a/OCKWatchSample Extension/AppDelegate.swift b/OCKWatchSample Extension/AppDelegate.swift index c1beee1..4329919 100644 --- a/OCKWatchSample Extension/AppDelegate.swift +++ b/OCKWatchSample Extension/AppDelegate.swift @@ -41,6 +41,7 @@ class AppDelegate: NSObject, WKApplicationDelegate, ObservableObject { try await PCKUtility.setupServer(fileName: Constants.parseConfigFileName) { _, completionHandler in completionHandler(.performDefaultHandling, nil) } + await Utility.clearDeviceOnFirstRun() do { _ = try await User.current() do { @@ -114,6 +115,23 @@ class AppDelegate: NSObject, WKApplicationDelegate, ObservableObject { } } + @MainActor + func resetAppToInitialState() { + + do { + try self.store?.delete() + } catch { + Logger.appDelegate.error("Error deleting OCKStore: \(error)") + } + + parseRemote = nil + + let store = OCKStore(name: Constants.noCareStoreName, + type: .inMemory) + sessionDelegate.store = store + self.store = store + } + func applicationDidBecomeActive() { // swiftlint:disable:next line_length // Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface. From 914b8acadb7eda060dab072eef9398c24b6d27d1 Mon Sep 17 00:00:00 2001 From: Corey Baker Date: Mon, 10 Apr 2023 14:29:11 -0400 Subject: [PATCH 10/52] Remove notification center from watchOS app --- OCKWatchSample Extension/AppDelegate.swift | 6 ------ .../Main/Login/LoginViewModel.swift | 20 +------------------ OCKWatchSample Extension/Main/MainView.swift | 15 ++++++++------ 3 files changed, 10 insertions(+), 31 deletions(-) diff --git a/OCKWatchSample Extension/AppDelegate.swift b/OCKWatchSample Extension/AppDelegate.swift index 4329919..dc1c798 100644 --- a/OCKWatchSample Extension/AppDelegate.swift +++ b/OCKWatchSample Extension/AppDelegate.swift @@ -48,13 +48,7 @@ class AppDelegate: NSObject, WKApplicationDelegate, ObservableObject { let uuid = try await Utility.getRemoteClockUUID() try await self.setupRemotes(uuid: uuid) parseRemote.automaticallySynchronizes = true - // swiftlint:disable:next line_length - NotificationCenter.default.post(.init(name: Notification.Name(rawValue: Constants.userLoggedIn))) Logger.appDelegate.info("User is already signed in...") - store.synchronize { error in - let errorString = error?.localizedDescription ?? "Successful sync with remote!" - Logger.appDelegate.info("\(errorString)") - } } catch { Logger.appDelegate.error("User is logged in, but missing remoteId: \(error)") try await setupRemotes(uuid: nil) diff --git a/OCKWatchSample Extension/Main/Login/LoginViewModel.swift b/OCKWatchSample Extension/Main/Login/LoginViewModel.swift index 9bef7ef..1fc7410 100644 --- a/OCKWatchSample Extension/Main/Login/LoginViewModel.swift +++ b/OCKWatchSample Extension/Main/Login/LoginViewModel.swift @@ -16,24 +16,15 @@ class LoginViewModel: ObservableObject { @Published private(set) var isLoggedOut = true init() { - NotificationCenter.default.addObserver(self, - selector: #selector(userLoggedIn(_:)), - name: Notification.Name(rawValue: Constants.userLoggedIn), - object: nil) Task { await self.checkStatus() } } // MARK: Helpers (private) - @objc private func userLoggedIn(_ notification: Notification) { - Task { - await self.checkStatus() - } - } @MainActor - private func checkStatus() async { + func checkStatus() async { let isLoggedOut = self.isLoggedOut do { _ = try await User.current() @@ -59,15 +50,6 @@ class LoginViewModel: ObservableObject { let user = try await User.become(sessionToken: sessionToken) Logger.login.info("Parse login successful \(user, privacy: .private)") try await Utility.setupRemoteAfterLogin() - guard let watchDelegate = AppDelegateKey.defaultValue else { - Logger.login.error("ApplicationDelegate should not be nil") - return - } - watchDelegate.store.synchronize { error in - NotificationCenter.default.post(.init(name: Notification.Name(rawValue: Constants.userLoggedIn))) - let errorString = error?.localizedDescription ?? "Successful sync with remote!" - Logger.watch.info("\(errorString)") - } // Setup installation to receive push notifications Task { diff --git a/OCKWatchSample Extension/Main/MainView.swift b/OCKWatchSample Extension/Main/MainView.swift index 7103fcf..7af0bcc 100644 --- a/OCKWatchSample Extension/Main/MainView.swift +++ b/OCKWatchSample Extension/Main/MainView.swift @@ -25,12 +25,6 @@ struct MainView: View { case .tabs: CareView() .environment(\.careStore, store) - .onReceive(appDelegate.$store) { newStore in - guard let newStore = newStore else { - return - } - store = newStore - } } } .navigationBarHidden(true) @@ -53,6 +47,15 @@ struct MainView: View { } path = [.tabs] }) + .onReceive(appDelegate.$store) { newStore in + Task { + await loginViewModel.checkStatus() + } + guard let newStore = newStore else { + return + } + store = newStore + } } } From 56e61ac7f7cd74d8d443b7b40d42ae6870834e3b Mon Sep 17 00:00:00 2001 From: Corey Baker Date: Mon, 10 Apr 2023 15:55:13 -0400 Subject: [PATCH 11/52] remove extra notification --- OCKSample.xcodeproj/project.pbxproj | 3 ++- OCKSample.xcodeproj/xcshareddata/xcschemes/OCKSample.xcscheme | 2 +- .../xcschemes/OCKWatchSample (Notification).xcscheme | 2 +- .../xcshareddata/xcschemes/OCKWatchSample.xcscheme | 2 +- OCKSample/Utility.swift | 2 -- 5 files changed, 5 insertions(+), 6 deletions(-) diff --git a/OCKSample.xcodeproj/project.pbxproj b/OCKSample.xcodeproj/project.pbxproj index c186686..b6366b8 100644 --- a/OCKSample.xcodeproj/project.pbxproj +++ b/OCKSample.xcodeproj/project.pbxproj @@ -590,8 +590,9 @@ E72B2BFE226939E3009A9438 /* Project object */ = { isa = PBXProject; attributes = { + BuildIndependentTargetsInParallel = YES; LastSwiftUpdateCheck = 1200; - LastUpgradeCheck = 1400; + LastUpgradeCheck = 1430; ORGANIZATIONNAME = "Network Reconnaissance Lab"; TargetAttributes = { 5173CB8623C3A846007655A0 = { diff --git a/OCKSample.xcodeproj/xcshareddata/xcschemes/OCKSample.xcscheme b/OCKSample.xcodeproj/xcshareddata/xcschemes/OCKSample.xcscheme index 41cc038..6710cc1 100644 --- a/OCKSample.xcodeproj/xcshareddata/xcschemes/OCKSample.xcscheme +++ b/OCKSample.xcodeproj/xcshareddata/xcschemes/OCKSample.xcscheme @@ -1,6 +1,6 @@ Date: Mon, 10 Apr 2023 17:21:51 -0400 Subject: [PATCH 12/52] Update to tintColor fixes --- OCKSample.xcodeproj/project.pbxproj | 2 +- .../project.xcworkspace/xcshareddata/swiftpm/Package.resolved | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/OCKSample.xcodeproj/project.pbxproj b/OCKSample.xcodeproj/project.pbxproj index b6366b8..05e8c04 100644 --- a/OCKSample.xcodeproj/project.pbxproj +++ b/OCKSample.xcodeproj/project.pbxproj @@ -1191,7 +1191,7 @@ repositoryURL = "https://github.com/cbaker6/CareKit.git"; requirement = { kind = upToNextMajorVersion; - minimumVersion = "3.0.0-alpha.6"; + minimumVersion = "3.0.0-alpha.11"; }; }; 703616FD29CA194900B50BC5 /* XCRemoteSwiftPackageReference "CareKitUtilities" */ = { diff --git a/OCKSample.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/OCKSample.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved index c078512..d043c6d 100644 --- a/OCKSample.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/OCKSample.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -5,8 +5,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/cbaker6/CareKit.git", "state" : { - "revision" : "032abb22324bcd2b80d506aa708f5e035cf1c0a5", - "version" : "3.0.0-alpha.6" + "revision" : "38b9ef83b930f66daa329e9e1fd699d43340ea06", + "version" : "3.0.0-alpha.11" } }, { From f0bb01fda7f41b9b32ec01c026ab231299e7ac5e Mon Sep 17 00:00:00 2001 From: Corey Baker Date: Mon, 10 Apr 2023 17:49:12 -0400 Subject: [PATCH 13/52] Latest --- OCKSample.xcodeproj/project.pbxproj | 2 +- .../project.xcworkspace/xcshareddata/swiftpm/Package.resolved | 4 ++-- OCKSample/Main/MainView.swift | 1 + 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/OCKSample.xcodeproj/project.pbxproj b/OCKSample.xcodeproj/project.pbxproj index 05e8c04..daa8e87 100644 --- a/OCKSample.xcodeproj/project.pbxproj +++ b/OCKSample.xcodeproj/project.pbxproj @@ -1191,7 +1191,7 @@ repositoryURL = "https://github.com/cbaker6/CareKit.git"; requirement = { kind = upToNextMajorVersion; - minimumVersion = "3.0.0-alpha.11"; + minimumVersion = "3.0.0-alpha.13"; }; }; 703616FD29CA194900B50BC5 /* XCRemoteSwiftPackageReference "CareKitUtilities" */ = { diff --git a/OCKSample.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/OCKSample.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved index d043c6d..b8c391a 100644 --- a/OCKSample.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/OCKSample.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -5,8 +5,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/cbaker6/CareKit.git", "state" : { - "revision" : "38b9ef83b930f66daa329e9e1fd699d43340ea06", - "version" : "3.0.0-alpha.11" + "revision" : "3a431293294e84aa2c468c1465397e20209296e8", + "version" : "3.0.0-alpha.13" } }, { diff --git a/OCKSample/Main/MainView.swift b/OCKSample/Main/MainView.swift index a956c3f..c836f23 100644 --- a/OCKSample/Main/MainView.swift +++ b/OCKSample/Main/MainView.swift @@ -24,6 +24,7 @@ struct MainView: View { MainTabView(loginViewModel: loginViewModel) } else { CareView() + .navigationBarHidden(true) } } } From 3879a3a62895cd76df74a449cb80a35d3a3993ec Mon Sep 17 00:00:00 2001 From: Corey Baker Date: Mon, 10 Apr 2023 17:58:08 -0400 Subject: [PATCH 14/52] fix init --- .../AppDelegate+UIApplicationDelegate.swift | 18 +++++++++--------- OCKSample/Utility.swift | 4 +++- 2 files changed, 12 insertions(+), 10 deletions(-) diff --git a/OCKSample/Extensions/AppDelegate+UIApplicationDelegate.swift b/OCKSample/Extensions/AppDelegate+UIApplicationDelegate.swift index d7ff2a4..15188e1 100644 --- a/OCKSample/Extensions/AppDelegate+UIApplicationDelegate.swift +++ b/OCKSample/Extensions/AppDelegate+UIApplicationDelegate.swift @@ -14,17 +14,17 @@ extension AppDelegate: UIApplicationDelegate { func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { Task { - do { - // Parse-Server setup - try await PCKUtility.setupServer(fileName: Constants.parseConfigFileName) { _, completionHandler in - completionHandler(.performDefaultHandling, nil) - } - } catch { - Logger.appDelegate.info("Could not configure Parse Swift: \(error)") - return - } await Utility.clearDeviceOnFirstRun() if isSyncingWithCloud { + do { + // Parse-Server setup + try await PCKUtility.setupServer(fileName: Constants.parseConfigFileName) { _, completionHandler in + completionHandler(.performDefaultHandling, nil) + } + } catch { + Logger.appDelegate.info("Could not configure Parse Swift: \(error)") + return + } do { _ = try await User.current() Logger.appDelegate.info("User is already signed in...") diff --git a/OCKSample/Utility.swift b/OCKSample/Utility.swift index 55e1063..a0abace 100644 --- a/OCKSample/Utility.swift +++ b/OCKSample/Utility.swift @@ -167,7 +167,9 @@ class Utility { UserDefaults.standard.setValue(String(Constants.appName), forKey: Constants.appName) UserDefaults.standard.synchronize() - try? await User.logout() + if isSyncingWithCloud { + try? await User.logout() + } } } From 51349a3d20208638c9657c491a7150e874dc661d Mon Sep 17 00:00:00 2001 From: Corey Date: Mon, 10 Apr 2023 19:10:26 -0400 Subject: [PATCH 15/52] fix watchOS login transition --- OCKWatchSample Extension/AppDelegate.swift | 8 ++++++-- OCKWatchSample Extension/Main/MainView.swift | 10 ++++++++++ 2 files changed, 16 insertions(+), 2 deletions(-) diff --git a/OCKWatchSample Extension/AppDelegate.swift b/OCKWatchSample Extension/AppDelegate.swift index dc1c798..50d9fe4 100644 --- a/OCKWatchSample Extension/AppDelegate.swift +++ b/OCKWatchSample Extension/AppDelegate.swift @@ -47,12 +47,16 @@ class AppDelegate: NSObject, WKApplicationDelegate, ObservableObject { do { let uuid = try await Utility.getRemoteClockUUID() try await self.setupRemotes(uuid: uuid) - parseRemote.automaticallySynchronizes = true Logger.appDelegate.info("User is already signed in...") } catch { Logger.appDelegate.error("User is logged in, but missing remoteId: \(error)") try await setupRemotes(uuid: nil) } + if isSyncingWithCloud { + parseRemote.automaticallySynchronizes = true + } else { + phoneRemote.automaticallySynchronizes = true + } } catch { Logger.appDelegate.info("User is not logged in...") try await setupRemotes(uuid: nil) @@ -94,7 +98,7 @@ class AppDelegate: NSObject, WKApplicationDelegate, ObservableObject { self.store = store } else { let store = OCKStore(name: Constants.watchOSLocalCareStoreName, - type: .onDisk(), + type: .inMemory, remote: phoneRemote) phoneRemote.delegate = self sessionDelegate = LocalSessionDelegate(remote: phoneRemote, diff --git a/OCKWatchSample Extension/Main/MainView.swift b/OCKWatchSample Extension/Main/MainView.swift index 7af0bcc..77d8980 100644 --- a/OCKWatchSample Extension/Main/MainView.swift +++ b/OCKWatchSample Extension/Main/MainView.swift @@ -25,6 +25,7 @@ struct MainView: View { case .tabs: CareView() .environment(\.careStore, store) + .navigationBarHidden(true) } } .navigationBarHidden(true) @@ -41,6 +42,10 @@ struct MainView: View { } } .onReceive(loginViewModel.$isLoggedOut, perform: { isLoggedOut in + guard isSyncingWithCloud else { + path = [.tabs] + return + } guard !isLoggedOut else { path = [] return @@ -55,6 +60,11 @@ struct MainView: View { return } store = newStore + guard isSyncingWithCloud else { + path = [.tabs] + return + } + path = [.tabs] } } } From 0998a8a340dae2098aeed0bf5044b35396315a33 Mon Sep 17 00:00:00 2001 From: Corey Date: Mon, 10 Apr 2023 19:18:08 -0400 Subject: [PATCH 16/52] move sync properties --- OCKSample/Constants.swift | 12 ------------ OCKSample/OCKSampleApp.swift | 13 +++++++++++++ README.md | 2 +- 3 files changed, 14 insertions(+), 13 deletions(-) diff --git a/OCKSample/Constants.swift b/OCKSample/Constants.swift index 92e03e3..5a28cb2 100644 --- a/OCKSample/Constants.swift +++ b/OCKSample/Constants.swift @@ -11,18 +11,6 @@ import CareKit import CareKitStore import ParseSwift -/** - Set to **true** to sync with ParseServer, *false** to sync with iOS/watchOS. - */ -let isSyncingWithCloud = true -/** - Set to **true** to use WCSession to notify watchOS about updates, **false** to not notify. - A change in watchOS 9 removes the ability to use Websockets on real Apple Watches, - preventing auto updates from the Parse Server. See the link for - details: https://developer.apple.com/forums/thread/715024 - */ -let isSendingPushUpdatesToWatch = true - enum AppError: Error { case couldntCast case couldntBeUnwrapped diff --git a/OCKSample/OCKSampleApp.swift b/OCKSample/OCKSampleApp.swift index 9dc9397..45e6a53 100644 --- a/OCKSample/OCKSampleApp.swift +++ b/OCKSample/OCKSampleApp.swift @@ -10,6 +10,19 @@ import Foundation import SwiftUI import CareKit +/** + Set to **true** to sync with ParseServer, *false** to sync with iOS/watchOS. + */ +let isSyncingWithCloud = true + +/** + Set to **true** to use WCSession to notify watchOS about updates, **false** to not notify. + A change in watchOS 9 removes the ability to use Websockets on real Apple Watches, + preventing auto updates from the Parse Server. See the link for + details: https://developer.apple.com/forums/thread/715024 + */ +let isSendingPushUpdatesToWatch = true + @main struct OCKSampleApp: App { @UIApplicationDelegateAdaptor(AppDelegate.self) var appDelegate diff --git a/README.md b/README.md index a6d4b50..5512ac0 100644 --- a/README.md +++ b/README.md @@ -5,7 +5,7 @@ An example application of [CareKit](https://github.com/carekit-apple/CareKit)'s -**Similar to the [What's New in CareKit](https://developer.apple.com/videos/play/wwdc2020/10151/) WWDC20 video, this app syncs between the AppleWatch (setting the flag `isSyncingWithCloud` in `Constants.swift` to `isSyncingWithCloud = false`. Different from the video, setting `isSyncingWithCloud = true` (default behavior) in the aforementioned files syncs iOS and watchOS to a Parse Server.** +**Similar to the [What's New in CareKit](https://developer.apple.com/videos/play/wwdc2020/10151/) WWDC20 video, this app syncs between the AppleWatch (setting the flag `isSyncingWithCloud` in `OCKSampleApp.swift` to `isSyncingWithCloud = false`. Different from the video, setting `isSyncingWithCloud = true` (default behavior) in the aforementioned files syncs iOS and watchOS to a Parse Server.** ParseCareKit synchronizes the following entities to Parse tables/classes using [Parse-Swift](https://github.com/netreconlab/Parse-Swift): From 1d858a5f35c4984c6aa31b1301c3c8d46c40fee9 Mon Sep 17 00:00:00 2001 From: Corey Date: Mon, 10 Apr 2023 19:19:46 -0400 Subject: [PATCH 17/52] Doc nits --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 5512ac0..c907c38 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,5 @@ # CareKitSample+ParseCareKit -![Swift](https://img.shields.io/badge/swift-5.7-brightgreen.svg) ![Xcode 14.0+](https://img.shields.io/badge/xcode-14.0%2B-blue.svg) ![iOS 16.0+](https://img.shields.io/badge/iOS-16.0%2B-blue.svg) ![watchOS 9.0+](https://img.shields.io/badge/watchOS-9.0%2B-blue.svg) ![CareKit 2.1+](https://img.shields.io/badge/CareKit-2.1%2B-red.svg) ![ci](https://github.com/netreconlab/CareKitSample-ParseCareKit/workflows/ci/badge.svg?branch=main) +![Swift](https://img.shields.io/badge/swift-5.7-brightgreen.svg) ![Xcode 14.0+](https://img.shields.io/badge/xcode-14.0%2B-blue.svg) ![iOS 16.0+](https://img.shields.io/badge/iOS-16.0%2B-blue.svg) ![watchOS 9.0+](https://img.shields.io/badge/watchOS-9.0%2B-blue.svg) ![CareKit 3.0+](https://img.shields.io/badge/CareKit-3.0%2B-red.svg) ![ci](https://github.com/netreconlab/CareKitSample-ParseCareKit/workflows/ci/badge.svg?branch=main) An example application of [CareKit](https://github.com/carekit-apple/CareKit)'s OCKSample synchronizing CareKit data to the Cloud via [ParseCareKit](https://github.com/netreconlab/ParseCareKit). From d2f61d0086825e5a933dad140a9d825cab7de956 Mon Sep 17 00:00:00 2001 From: Corey Baker Date: Tue, 11 Apr 2023 13:10:31 -0400 Subject: [PATCH 18/52] move back global properties --- OCKSample/Constants.swift | 13 +++++++++++++ OCKSample/OCKSampleApp.swift | 13 ------------- 2 files changed, 13 insertions(+), 13 deletions(-) diff --git a/OCKSample/Constants.swift b/OCKSample/Constants.swift index 5a28cb2..d3bf2b6 100644 --- a/OCKSample/Constants.swift +++ b/OCKSample/Constants.swift @@ -11,6 +11,19 @@ import CareKit import CareKitStore import ParseSwift +/** + Set to **true** to sync with ParseServer, *false** to sync with iOS/watchOS. + */ +let isSyncingWithCloud = true + +/** + Set to **true** to use WCSession to notify watchOS about updates, **false** to not notify. + A change in watchOS 9 removes the ability to use Websockets on real Apple Watches, + preventing auto updates from the Parse Server. See the link for + details: https://developer.apple.com/forums/thread/715024 + */ +let isSendingPushUpdatesToWatch = true + enum AppError: Error { case couldntCast case couldntBeUnwrapped diff --git a/OCKSample/OCKSampleApp.swift b/OCKSample/OCKSampleApp.swift index 45e6a53..9dc9397 100644 --- a/OCKSample/OCKSampleApp.swift +++ b/OCKSample/OCKSampleApp.swift @@ -10,19 +10,6 @@ import Foundation import SwiftUI import CareKit -/** - Set to **true** to sync with ParseServer, *false** to sync with iOS/watchOS. - */ -let isSyncingWithCloud = true - -/** - Set to **true** to use WCSession to notify watchOS about updates, **false** to not notify. - A change in watchOS 9 removes the ability to use Websockets on real Apple Watches, - preventing auto updates from the Parse Server. See the link for - details: https://developer.apple.com/forums/thread/715024 - */ -let isSendingPushUpdatesToWatch = true - @main struct OCKSampleApp: App { @UIApplicationDelegateAdaptor(AppDelegate.self) var appDelegate From 1ed8dc02f6cb18a0fc4cee7f4930069f0224e27b Mon Sep 17 00:00:00 2001 From: Corey Baker Date: Tue, 11 Apr 2023 13:13:44 -0400 Subject: [PATCH 19/52] doc nits --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index c907c38..dd09cde 100644 --- a/README.md +++ b/README.md @@ -5,7 +5,7 @@ An example application of [CareKit](https://github.com/carekit-apple/CareKit)'s -**Similar to the [What's New in CareKit](https://developer.apple.com/videos/play/wwdc2020/10151/) WWDC20 video, this app syncs between the AppleWatch (setting the flag `isSyncingWithCloud` in `OCKSampleApp.swift` to `isSyncingWithCloud = false`. Different from the video, setting `isSyncingWithCloud = true` (default behavior) in the aforementioned files syncs iOS and watchOS to a Parse Server.** +**Similar to the [What's New in CareKit](https://developer.apple.com/videos/play/wwdc2020/10151/) WWDC20 video, this app syncs between data between iOS and an Apple Watch (setting the flag `isSyncingWithCloud` in `Constants.swift` to `isSyncingWithCloud = false`. Different from the video, setting `isSyncingWithCloud = true` (default behavior) in the aforementioned file syncs iOS and watchOS to a Parse Server.** ParseCareKit synchronizes the following entities to Parse tables/classes using [Parse-Swift](https://github.com/netreconlab/Parse-Swift): From d62c35dad7152cc67c1aee7554a7e0200089cff1 Mon Sep 17 00:00:00 2001 From: Corey Baker Date: Tue, 11 Apr 2023 14:48:39 -0400 Subject: [PATCH 20/52] Don't replace store if it has the same name --- OCKSample/AppDelegate.swift | 4 ++-- OCKSample/Main/MainTabView.swift | 3 ++- OCKWatchSample Extension/Main/MainView.swift | 3 ++- 3 files changed, 6 insertions(+), 4 deletions(-) diff --git a/OCKSample/AppDelegate.swift b/OCKSample/AppDelegate.swift index 97f73fb..4d5002f 100644 --- a/OCKSample/AppDelegate.swift +++ b/OCKSample/AppDelegate.swift @@ -104,8 +104,8 @@ class AppDelegate: UIResponder, ObservableObject { self.store = store } else { let store = OCKStore(name: Constants.iOSLocalCareStoreName, - type: .onDisk(), - remote: watchRemote) + type: .onDisk(), + remote: watchRemote) watchRemote.delegate = self sessionDelegate = LocalSessionDelegate(remote: watchRemote, store: store) self.store = store diff --git a/OCKSample/Main/MainTabView.swift b/OCKSample/Main/MainTabView.swift index e4d1bcd..1cca29f 100644 --- a/OCKSample/Main/MainTabView.swift +++ b/OCKSample/Main/MainTabView.swift @@ -57,7 +57,8 @@ struct MainTabView: View { .tag(2) .environment(\.careStore, store) .onReceive(appDelegate.$store) { newStore in - guard let newStore = newStore else { + guard let newStore = newStore, + store.name != newStore.name else { return } store = newStore diff --git a/OCKWatchSample Extension/Main/MainView.swift b/OCKWatchSample Extension/Main/MainView.swift index 77d8980..f00e0c2 100644 --- a/OCKWatchSample Extension/Main/MainView.swift +++ b/OCKWatchSample Extension/Main/MainView.swift @@ -56,7 +56,8 @@ struct MainView: View { Task { await loginViewModel.checkStatus() } - guard let newStore = newStore else { + guard let newStore = newStore, + store.name != newStore.name else { return } store = newStore From 4dafb1fb206f1800dcc67fa676e8d47cac34937a Mon Sep 17 00:00:00 2001 From: Corey Baker Date: Wed, 12 Apr 2023 16:51:40 -0400 Subject: [PATCH 21/52] UIKit views don't use async/await --- OCKSample.xcodeproj/project.pbxproj | 4 +- .../xcshareddata/swiftpm/Package.resolved | 8 +-- .../AppDelegate+ParseRemoteDelegate.swift | 4 ++ OCKSample/Main/Care/CareView.swift | 9 ++- OCKSample/Main/Care/CareViewController.swift | 55 +++++++++++-------- OCKSample/Main/Contact/ContactView.swift | 5 +- OCKSample/Main/Profile/ProfileView.swift | 3 +- .../AppDelegate+ParseRemoteDelegate.swift | 7 +++ 8 files changed, 60 insertions(+), 35 deletions(-) diff --git a/OCKSample.xcodeproj/project.pbxproj b/OCKSample.xcodeproj/project.pbxproj index daa8e87..e875325 100644 --- a/OCKSample.xcodeproj/project.pbxproj +++ b/OCKSample.xcodeproj/project.pbxproj @@ -1191,7 +1191,7 @@ repositoryURL = "https://github.com/cbaker6/CareKit.git"; requirement = { kind = upToNextMajorVersion; - minimumVersion = "3.0.0-alpha.13"; + minimumVersion = "3.0.0-alpha.17"; }; }; 703616FD29CA194900B50BC5 /* XCRemoteSwiftPackageReference "CareKitUtilities" */ = { @@ -1207,7 +1207,7 @@ repositoryURL = "https://github.com/netreconlab/ParseCareKit.git"; requirement = { kind = upToNextMajorVersion; - minimumVersion = "1.0.0-alpha.7"; + minimumVersion = "1.0.0-alpha.10"; }; }; /* End XCRemoteSwiftPackageReference section */ diff --git a/OCKSample.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/OCKSample.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved index b8c391a..3380372 100644 --- a/OCKSample.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/OCKSample.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -5,8 +5,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/cbaker6/CareKit.git", "state" : { - "revision" : "3a431293294e84aa2c468c1465397e20209296e8", - "version" : "3.0.0-alpha.13" + "revision" : "708d0ec751700d9dbb1316887ff7915b6260d6d7", + "version" : "3.0.0-alpha.17" } }, { @@ -41,8 +41,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/netreconlab/ParseCareKit.git", "state" : { - "revision" : "1ca887f246b3b81bed4031230836020abbff2130", - "version" : "1.0.0-alpha.7" + "revision" : "a2b8190581f6ad8abacda312e9d16fa5d0998762", + "version" : "1.0.0-alpha.10" } }, { diff --git a/OCKSample/Extensions/AppDelegate+ParseRemoteDelegate.swift b/OCKSample/Extensions/AppDelegate+ParseRemoteDelegate.swift index 9e5edd8..e2299ee 100644 --- a/OCKSample/Extensions/AppDelegate+ParseRemoteDelegate.swift +++ b/OCKSample/Extensions/AppDelegate+ParseRemoteDelegate.swift @@ -34,6 +34,10 @@ extension AppDelegate: ParseRemoteDelegate { #endif } + func needStore() -> OCKAnyStoreProtocol { + return storeCoordinator + } + func remote(_ remote: OCKRemoteSynchronizable, didUpdateProgress progress: Double) { let progressPercentage = Int(progress * 100.0) NotificationCenter.default.post(.init(name: Notification.Name(rawValue: Constants.progressUpdate), diff --git a/OCKSample/Main/Care/CareView.swift b/OCKSample/Main/Care/CareView.swift index 80f4827..9ab9b55 100644 --- a/OCKSample/Main/Care/CareView.swift +++ b/OCKSample/Main/Care/CareView.swift @@ -26,8 +26,13 @@ struct CareView: UIViewControllerRepresentable { func updateUIViewController(_ uiViewController: UIViewControllerType, context: Context) { - guard let navigationController = uiViewController as? UINavigationController else { - Logger.feed.error("View should have been a UINavigationController") + guard let navigationController = uiViewController as? UINavigationController, + let careViewController = navigationController.viewControllers.first as? CareViewController else { + Logger.feed.error("CareView should have been a UINavigationController") + return + } + guard careViewController.store !== appDelegate.storeCoordinator else { + // No need to update view return } navigationController.setViewControllers([createViewController()], animated: false) diff --git a/OCKSample/Main/Care/CareViewController.swift b/OCKSample/Main/Care/CareViewController.swift index 4fd931b..e0d5a0b 100644 --- a/OCKSample/Main/Care/CareViewController.swift +++ b/OCKSample/Main/Care/CareViewController.swift @@ -149,23 +149,27 @@ class CareViewController: OCKDailyPageViewController { } } - Task { - let tasks = await self.fetchTasks(on: date) - tasks.compactMap { - let cards = self.taskViewController(for: $0, - on: date) - cards?.forEach { - if let carekitView = $0.view as? OCKView { - carekitView.customStyle = CustomStylerKey.defaultValue + fetchTasks(on: date) { result in + switch result { + case .success(let tasks): + tasks.compactMap { + let cards = self.taskViewController(for: $0, + on: date) + cards?.forEach { + if let carekitView = $0.view as? OCKView { + carekitView.customStyle = CustomStylerKey.defaultValue + } + $0.view.isUserInteractionEnabled = isCurrentDay + $0.view.alpha = !isCurrentDay ? 0.4 : 1.0 + } + return cards + }.forEach { (cards: [UIViewController]) in + cards.forEach { + listViewController.appendViewController($0, animated: false) } - $0.view.isUserInteractionEnabled = isCurrentDay - $0.view.alpha = !isCurrentDay ? 0.4 : 1.0 - } - return cards - }.forEach { (cards: [UIViewController]) in - cards.forEach { - listViewController.appendViewController($0, animated: false) } + case .failure(let error): + Logger.feed.error("Could not fetch tasks: \(error)") } self.isLoading = false } @@ -268,17 +272,20 @@ class CareViewController: OCKDailyPageViewController { } } - private func fetchTasks(on date: Date) async -> [OCKAnyTask] { + private func fetchTasks(on date: Date, + completion: @escaping (Result<[OCKAnyTask], Error>) -> Void) { var query = OCKTaskQuery(for: date) query.excludesTasksWithNoEvents = true - do { - let tasks = try await store.fetchAnyTasks(query: query) - let orderedTasks = TaskID.ordered.compactMap { orderedTaskID in - tasks.first(where: { $0.id == orderedTaskID }) } - return orderedTasks - } catch { - Logger.feed.error("\(error, privacy: .public)") - return [] + store.fetchAnyTasks(query: query, callbackQueue: .main) { result in + switch result { + case .success(let tasks): + let orderedTasks = TaskID.ordered.compactMap { orderedTaskID in + tasks.first(where: { $0.id == orderedTaskID }) + } + completion(.success(orderedTasks)) + case .failure(let error): + completion(.failure(error)) + } } } } diff --git a/OCKSample/Main/Contact/ContactView.swift b/OCKSample/Main/Contact/ContactView.swift index 64304e9..42ecd69 100644 --- a/OCKSample/Main/Contact/ContactView.swift +++ b/OCKSample/Main/Contact/ContactView.swift @@ -23,14 +23,15 @@ struct ContactView: UIViewControllerRepresentable { func updateUIViewController(_ uiViewController: UIViewControllerType, context: Context) { guard let navigationController = uiViewController as? UINavigationController else { - Logger.feed.error("View should have been a UINavigationController") + Logger.feed.error("ContactView should have been a UINavigationController") return } navigationController.setViewControllers([createViewController()], animated: false) } func createViewController() -> UIViewController { - OCKContactsListViewController(store: appDelegate.storeCoordinator) + OCKContactsListViewController(store: appDelegate.storeCoordinator, + contactViewSynchronizer: OCKDetailedContactViewSynchronizer()) } } diff --git a/OCKSample/Main/Profile/ProfileView.swift b/OCKSample/Main/Profile/ProfileView.swift index 0c556ef..b40051c 100644 --- a/OCKSample/Main/Profile/ProfileView.swift +++ b/OCKSample/Main/Profile/ProfileView.swift @@ -13,9 +13,10 @@ import CareKit import os.log struct ProfileView: View { + private static var query = OCKPatientQuery(for: Date()) + @CareStoreFetchRequest(query: query) private var patients @StateObject var viewModel = ProfileViewModel() @ObservedObject var loginViewModel: LoginViewModel - @CareStoreFetchRequest(query: OCKPatientQuery(for: Date())) private var patients var body: some View { VStack { diff --git a/OCKWatchSample Extension/Extensions/AppDelegate+ParseRemoteDelegate.swift b/OCKWatchSample Extension/Extensions/AppDelegate+ParseRemoteDelegate.swift index c3fb78d..8cb1be7 100644 --- a/OCKWatchSample Extension/Extensions/AppDelegate+ParseRemoteDelegate.swift +++ b/OCKWatchSample Extension/Extensions/AppDelegate+ParseRemoteDelegate.swift @@ -24,6 +24,13 @@ extension AppDelegate: ParseRemoteDelegate { Logger.appDelegate.info("Finished pushing data") } + func needStore() -> OCKAnyStoreProtocol { + guard let store = store else { + return OCKStore(name: Constants.noCareStoreName, type: .inMemory) + } + return store + } + func remote(_ remote: OCKRemoteSynchronizable, didUpdateProgress progress: Double) {} func chooseConflictResolution(conflicts: [OCKEntity], completion: @escaping OCKResultClosure) { From 6336ee085a778e07cc2ee45a65da86f49276e40a Mon Sep 17 00:00:00 2001 From: Corey Date: Wed, 12 Apr 2023 23:12:26 -0400 Subject: [PATCH 22/52] Constrain query on watch --- OCKWatchSample Extension/Main/Care/CareView.swift | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/OCKWatchSample Extension/Main/Care/CareView.swift b/OCKWatchSample Extension/Main/Care/CareView.swift index 5590461..10bf8a0 100644 --- a/OCKWatchSample Extension/Main/Care/CareView.swift +++ b/OCKWatchSample Extension/Main/Care/CareView.swift @@ -13,7 +13,12 @@ import SwiftUI import os.log struct CareView: View { - @CareStoreFetchRequest(query: OCKEventQuery(for: Date())) private var events + private static var query: OCKEventQuery { + var query = OCKEventQuery(for: Date()) + query.taskIDs = [TaskID.stretch, TaskID.kegels] + return query + } + @CareStoreFetchRequest(query: query) private var events var body: some View { ScrollView { From 82b790208e40d458b1ac63868bfa3ccfc5beb009 Mon Sep 17 00:00:00 2001 From: Corey Baker Date: Thu, 13 Apr 2023 10:26:25 -0400 Subject: [PATCH 23/52] Add back SwiftUI step card in UIKit --- OCKSample/Main/Care/CareView.swift | 12 +++++- OCKSample/Main/Care/CareViewController.swift | 40 +++++++++++++------- OCKSample/Main/MainTabView.swift | 20 +++++----- 3 files changed, 46 insertions(+), 26 deletions(-) diff --git a/OCKSample/Main/Care/CareView.swift b/OCKSample/Main/Care/CareView.swift index 9ab9b55..d19157c 100644 --- a/OCKSample/Main/Care/CareView.swift +++ b/OCKSample/Main/Care/CareView.swift @@ -15,6 +15,12 @@ import CareKitStore import os.log struct CareView: UIViewControllerRepresentable { + private static var query: OCKEventQuery { + var query = OCKEventQuery(for: Date()) + query.taskIDs = [TaskID.steps] + return query + } + @CareStoreFetchRequest(query: query) private var events @EnvironmentObject private var appDelegate: AppDelegate func makeUIViewController(context: Context) -> some UIViewController { @@ -32,14 +38,16 @@ struct CareView: UIViewControllerRepresentable { return } guard careViewController.store !== appDelegate.storeCoordinator else { - // No need to update view + // No need to replace view + careViewController.events = events return } navigationController.setViewControllers([createViewController()], animated: false) } func createViewController() -> UIViewController { - CareViewController(store: appDelegate.storeCoordinator) + CareViewController(store: appDelegate.storeCoordinator, + events: events) } } diff --git a/OCKSample/Main/Care/CareViewController.swift b/OCKSample/Main/Care/CareViewController.swift index e0d5a0b..5b28400 100644 --- a/OCKSample/Main/Care/CareViewController.swift +++ b/OCKSample/Main/Care/CareViewController.swift @@ -41,6 +41,25 @@ class CareViewController: OCKDailyPageViewController { private var isSyncing = false private var isLoading = false + var events: CareStoreFetchedResults? { + didSet { + self.reloadView() + } + } + + /// Create an instance of the view controller. Will hook up the calendar to the tasks collection, + /// and query and display the tasks. + /// + /// - Parameter store: The store from which to query the tasks. + /// - Parameter computeProgress: Used to compute the combined progress for a series of CareKit events. + init(store: OCKAnyStoreProtocol, + events: CareStoreFetchedResults? = nil, + computeProgress: @escaping (OCKAnyEvent) -> CareTaskProgress = { event in + event.computeProgress(by: .checkingOutcomeExists) + }) { + super.init(store: store, computeProgress: computeProgress) + self.events = events + } override func viewDidLoad() { super.viewDidLoad() @@ -175,6 +194,10 @@ class CareViewController: OCKDailyPageViewController { } } + private func getStoreFetchRequestEvent(for taskId: String) -> CareStoreFetchedResult? { + events?.filter({ $0.result.task.id == taskId }).last + } + private func taskViewController(for task: OCKAnyTask, on date: Date) -> [UIViewController]? { @@ -183,26 +206,15 @@ class CareViewController: OCKDailyPageViewController { switch task.id { case TaskID.steps: - /* - guard let event = CareStoreFetchRequest(query: query).wrappedValue.first else { + + guard let event = getStoreFetchRequestEvent(for: task.id) else { return nil } let view = NumericProgressTaskView(event: event) - let title = task.title - let firstOutcome = event.outcome?.sortedOutcomeValuesByRecency().values.first - let computedProgress = event.computeProgress() - let progress = firstOutcome?.doubleValue ?? 0.0 - let goal: Double = progress / computedProgress.fractionCompleted - let isCompleted = event.computeProgress().isCompleted - let view = NumericProgressTaskView(title: Text(title ?? ""), - progress: Text("\(progress)"), - goal: Text("\(goal)"), - isComplete: isCompleted) .padding([.vertical], 20) .careKitStyle(CustomStylerKey.defaultValue) - return [view.formattedHostingController()]*/ - return nil + return [view.formattedHostingController()] case TaskID.stretch: return [OCKInstructionsTaskViewController(query: query, store: self.store)] diff --git a/OCKSample/Main/MainTabView.swift b/OCKSample/Main/MainTabView.swift index 1cca29f..20033f3 100644 --- a/OCKSample/Main/MainTabView.swift +++ b/OCKSample/Main/MainTabView.swift @@ -15,8 +15,7 @@ struct MainTabView: View { @EnvironmentObject private var appDelegate: AppDelegate @ObservedObject var loginViewModel: LoginViewModel @State private var selectedTab = 0 - @State private var store = OCKStore(name: Constants.noCareStoreName, - type: .inMemory) + @State private var store = OCKStoreCoordinator() var body: some View { TabView(selection: $selectedTab) { @@ -55,16 +54,15 @@ struct MainTabView: View { } } .tag(2) - .environment(\.careStore, store) - .onReceive(appDelegate.$store) { newStore in - guard let newStore = newStore, - store.name != newStore.name else { - return - } - store = newStore - } } .navigationBarHidden(true) + .environment(\.careStore, store) + .onReceive(appDelegate.$storeCoordinator) { newStore in + guard store !== newStore else { + return + } + store = newStore + } } } @@ -72,5 +70,7 @@ struct MainTabView_Previews: PreviewProvider { static var previews: some View { MainTabView(loginViewModel: .init()) .accentColor(Color(TintColorKey.defaultValue)) + .environment(\.careStore, Utility.createPreviewStore()) + } } From 5063b4a96cc6acb7d87abbbbaf8f8e187eedb6a0 Mon Sep 17 00:00:00 2001 From: Corey Baker Date: Thu, 13 Apr 2023 10:34:48 -0400 Subject: [PATCH 24/52] Remove padding on SwiftUI view --- OCKSample/Main/Care/CareViewController.swift | 1 - 1 file changed, 1 deletion(-) diff --git a/OCKSample/Main/Care/CareViewController.swift b/OCKSample/Main/Care/CareViewController.swift index 5b28400..473e422 100644 --- a/OCKSample/Main/Care/CareViewController.swift +++ b/OCKSample/Main/Care/CareViewController.swift @@ -211,7 +211,6 @@ class CareViewController: OCKDailyPageViewController { return nil } let view = NumericProgressTaskView(event: event) - .padding([.vertical], 20) .careKitStyle(CustomStylerKey.defaultValue) return [view.formattedHostingController()] From ad68a2115f20c55d65451c16e8ba85d35d3bc1e0 Mon Sep 17 00:00:00 2001 From: Corey Baker Date: Thu, 13 Apr 2023 11:44:45 -0400 Subject: [PATCH 25/52] disable steps card since it's causing crash --- OCKSample.xcodeproj/project.pbxproj | 2 +- .../xcshareddata/swiftpm/Package.resolved | 8 ++++---- OCKSample/Main/Care/CareView.swift | 2 +- OCKSample/Main/Care/CareViewController.swift | 4 ++-- 4 files changed, 8 insertions(+), 8 deletions(-) diff --git a/OCKSample.xcodeproj/project.pbxproj b/OCKSample.xcodeproj/project.pbxproj index e875325..98230ef 100644 --- a/OCKSample.xcodeproj/project.pbxproj +++ b/OCKSample.xcodeproj/project.pbxproj @@ -1191,7 +1191,7 @@ repositoryURL = "https://github.com/cbaker6/CareKit.git"; requirement = { kind = upToNextMajorVersion; - minimumVersion = "3.0.0-alpha.17"; + minimumVersion = "3.0.0-alpha.20"; }; }; 703616FD29CA194900B50BC5 /* XCRemoteSwiftPackageReference "CareKitUtilities" */ = { diff --git a/OCKSample.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/OCKSample.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved index 3380372..9e08fae 100644 --- a/OCKSample.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/OCKSample.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -5,8 +5,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/cbaker6/CareKit.git", "state" : { - "revision" : "708d0ec751700d9dbb1316887ff7915b6260d6d7", - "version" : "3.0.0-alpha.17" + "revision" : "7839f08370d1e3aaa5947d4ab2c3910b8cb7fc4b", + "version" : "3.0.0-alpha.20" } }, { @@ -50,8 +50,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/apple/swift-async-algorithms", "state" : { - "revision" : "aed5422380244498344a036b8d94e27f370d9a22", - "version" : "0.0.4" + "revision" : "9cfed92b026c524674ed869a4ff2dcfdeedf8a2a", + "version" : "0.1.0" } }, { diff --git a/OCKSample/Main/Care/CareView.swift b/OCKSample/Main/Care/CareView.swift index d19157c..acdc1a9 100644 --- a/OCKSample/Main/Care/CareView.swift +++ b/OCKSample/Main/Care/CareView.swift @@ -39,7 +39,7 @@ struct CareView: UIViewControllerRepresentable { } guard careViewController.store !== appDelegate.storeCoordinator else { // No need to replace view - careViewController.events = events + // careViewController.events = events return } navigationController.setViewControllers([createViewController()], animated: false) diff --git a/OCKSample/Main/Care/CareViewController.swift b/OCKSample/Main/Care/CareViewController.swift index 473e422..250cd37 100644 --- a/OCKSample/Main/Care/CareViewController.swift +++ b/OCKSample/Main/Care/CareViewController.swift @@ -58,7 +58,7 @@ class CareViewController: OCKDailyPageViewController { event.computeProgress(by: .checkingOutcomeExists) }) { super.init(store: store, computeProgress: computeProgress) - self.events = events + // self.events = events } override func viewDidLoad() { @@ -206,7 +206,6 @@ class CareViewController: OCKDailyPageViewController { switch task.id { case TaskID.steps: - guard let event = getStoreFetchRequestEvent(for: task.id) else { return nil } @@ -214,6 +213,7 @@ class CareViewController: OCKDailyPageViewController { .careKitStyle(CustomStylerKey.defaultValue) return [view.formattedHostingController()] + case TaskID.stretch: return [OCKInstructionsTaskViewController(query: query, store: self.store)] From e1440ad414c3fa86a42cb8a349423ca7ba10d4b0 Mon Sep 17 00:00:00 2001 From: Corey Date: Thu, 13 Apr 2023 20:13:29 -0400 Subject: [PATCH 26/52] Fix syncing with watch directly --- OCKSample/Main/Care/CareView.swift | 3 ++- OCKSample/Main/MainTabView.swift | 9 --------- OCKSample/Main/MainView.swift | 10 ++++++++++ OCKWatchSample Extension/Main/MainView.swift | 2 +- 4 files changed, 13 insertions(+), 11 deletions(-) diff --git a/OCKSample/Main/Care/CareView.swift b/OCKSample/Main/Care/CareView.swift index acdc1a9..c9ab958 100644 --- a/OCKSample/Main/Care/CareView.swift +++ b/OCKSample/Main/Care/CareView.swift @@ -37,7 +37,8 @@ struct CareView: UIViewControllerRepresentable { Logger.feed.error("CareView should have been a UINavigationController") return } - guard careViewController.store !== appDelegate.storeCoordinator else { + guard careViewController.store !== appDelegate.storeCoordinator || + appDelegate.isFirstTimeLogin else { // No need to replace view // careViewController.events = events return diff --git a/OCKSample/Main/MainTabView.swift b/OCKSample/Main/MainTabView.swift index 20033f3..afbef83 100644 --- a/OCKSample/Main/MainTabView.swift +++ b/OCKSample/Main/MainTabView.swift @@ -12,10 +12,8 @@ import SwiftUI import CareKitStore struct MainTabView: View { - @EnvironmentObject private var appDelegate: AppDelegate @ObservedObject var loginViewModel: LoginViewModel @State private var selectedTab = 0 - @State private var store = OCKStoreCoordinator() var body: some View { TabView(selection: $selectedTab) { @@ -56,13 +54,6 @@ struct MainTabView: View { .tag(2) } .navigationBarHidden(true) - .environment(\.careStore, store) - .onReceive(appDelegate.$storeCoordinator) { newStore in - guard store !== newStore else { - return - } - store = newStore - } } } diff --git a/OCKSample/Main/MainView.swift b/OCKSample/Main/MainView.swift index c836f23..f5ef6fe 100644 --- a/OCKSample/Main/MainView.swift +++ b/OCKSample/Main/MainView.swift @@ -11,8 +11,10 @@ import CareKitStore import CareKitUI struct MainView: View { + @EnvironmentObject private var appDelegate: AppDelegate @StateObject var loginViewModel = LoginViewModel() @State var path = [MainViewPath]() + @State private var storeCoordinator = OCKStoreCoordinator() var body: some View { NavigationStack(path: $path) { @@ -41,6 +43,7 @@ struct MainView: View { path = [.tabs] } } + .environment(\.careStore, storeCoordinator) .onReceive(loginViewModel.$isLoggedOut, perform: { isLoggedOut in guard !isLoggedOut else { path = [] @@ -48,12 +51,19 @@ struct MainView: View { } path = [.tabs] }) + .onReceive(appDelegate.$storeCoordinator) { newStoreCoordinator in + guard storeCoordinator !== newStoreCoordinator else { + return + } + storeCoordinator = newStoreCoordinator + } } } struct MainView_Previews: PreviewProvider { static var previews: some View { MainView() + .environment(\.careStore, Utility.createPreviewStore()) .accentColor(Color(TintColorKey.defaultValue)) } } diff --git a/OCKWatchSample Extension/Main/MainView.swift b/OCKWatchSample Extension/Main/MainView.swift index f00e0c2..9a2942f 100644 --- a/OCKWatchSample Extension/Main/MainView.swift +++ b/OCKWatchSample Extension/Main/MainView.swift @@ -24,7 +24,6 @@ struct MainView: View { switch destination { case .tabs: CareView() - .environment(\.careStore, store) .navigationBarHidden(true) } } @@ -41,6 +40,7 @@ struct MainView: View { path = [.tabs] } } + .environment(\.careStore, store) .onReceive(loginViewModel.$isLoggedOut, perform: { isLoggedOut in guard isSyncingWithCloud else { path = [.tabs] From da294225268b7d39ef0ae6e3984bb61f8a97b3a5 Mon Sep 17 00:00:00 2001 From: Corey Baker Date: Fri, 14 Apr 2023 09:29:29 -0400 Subject: [PATCH 27/52] enable use of isSendingPushUpdatesToWatch --- .../Extensions/AppDelegate+ParseRemoteDelegate.swift | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/OCKSample/Extensions/AppDelegate+ParseRemoteDelegate.swift b/OCKSample/Extensions/AppDelegate+ParseRemoteDelegate.swift index e2299ee..354875a 100644 --- a/OCKSample/Extensions/AppDelegate+ParseRemoteDelegate.swift +++ b/OCKSample/Extensions/AppDelegate+ParseRemoteDelegate.swift @@ -27,10 +27,12 @@ extension AppDelegate: ParseRemoteDelegate { } #if !targetEnvironment(simulator) // watchOS 9 needs to be sent messages for updates on real devices - let message = Utility.prepareSyncMessageForWatch() - WCSession.default.sendMessage(message, - replyHandler: nil, - errorHandler: nil) + if isSendingPushUpdatesToWatch { + let message = Utility.prepareSyncMessageForWatch() + WCSession.default.sendMessage(message, + replyHandler: nil, + errorHandler: nil) + } #endif } From 4a55bc55edc19691e8f4299352f0aa449f78cdd8 Mon Sep 17 00:00:00 2001 From: Corey Baker Date: Fri, 14 Apr 2023 09:57:55 -0400 Subject: [PATCH 28/52] move enums to models folder --- OCKSample.xcodeproj/project.pbxproj | 42 ++++++++++- OCKSample/Constants.swift | 71 ------------------- OCKSample/Models/AppError.swift | 51 +++++++++++++ OCKSample/Models/InstallationChannel.swift | 13 ++++ OCKSample/Models/MainViewPath.swift | 13 ++++ .../Models/{ => Parse}/Installation.swift | 0 OCKSample/Models/{ => Parse}/User.swift | 0 OCKSample/Models/TaskID.swift | 21 ++++++ OCKSample/Models/UserType.swift | 20 ++++++ 9 files changed, 158 insertions(+), 73 deletions(-) create mode 100644 OCKSample/Models/AppError.swift create mode 100644 OCKSample/Models/InstallationChannel.swift create mode 100644 OCKSample/Models/MainViewPath.swift rename OCKSample/Models/{ => Parse}/Installation.swift (100%) rename OCKSample/Models/{ => Parse}/User.swift (100%) create mode 100644 OCKSample/Models/TaskID.swift create mode 100644 OCKSample/Models/UserType.swift diff --git a/OCKSample.xcodeproj/project.pbxproj b/OCKSample.xcodeproj/project.pbxproj index 98230ef..f82c3cc 100644 --- a/OCKSample.xcodeproj/project.pbxproj +++ b/OCKSample.xcodeproj/project.pbxproj @@ -41,6 +41,16 @@ 7083A856279CA40A00B3832E /* PCKUtility.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7083A855279CA40A00B3832E /* PCKUtility.swift */; }; 7083A857279CA40F00B3832E /* PCKUtility.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7083A855279CA40A00B3832E /* PCKUtility.swift */; }; 708542F9276687F90029E888 /* HealthKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 51F6343923D2877B00FE576E /* HealthKit.framework */; }; + 7099D1FF29E98D900037CD8E /* AppError.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7099D1FE29E98D900037CD8E /* AppError.swift */; }; + 7099D20029E98D900037CD8E /* AppError.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7099D1FE29E98D900037CD8E /* AppError.swift */; }; + 7099D20229E98DDF0037CD8E /* MainViewPath.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7099D20129E98DDF0037CD8E /* MainViewPath.swift */; }; + 7099D20329E98DDF0037CD8E /* MainViewPath.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7099D20129E98DDF0037CD8E /* MainViewPath.swift */; }; + 7099D20529E98DFF0037CD8E /* TaskID.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7099D20429E98DFF0037CD8E /* TaskID.swift */; }; + 7099D20629E98DFF0037CD8E /* TaskID.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7099D20429E98DFF0037CD8E /* TaskID.swift */; }; + 7099D20829E98E200037CD8E /* UserType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7099D20729E98E200037CD8E /* UserType.swift */; }; + 7099D20929E98E200037CD8E /* UserType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7099D20729E98E200037CD8E /* UserType.swift */; }; + 7099D20B29E98E400037CD8E /* InstallationChannel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7099D20A29E98E400037CD8E /* InstallationChannel.swift */; }; + 7099D20C29E98E400037CD8E /* InstallationChannel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7099D20A29E98E400037CD8E /* InstallationChannel.swift */; }; 70A98D62278A2683009B58F2 /* Styler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9169381A271B64E100A634ED /* Styler.swift */; }; 70A98D63278A268B009B58F2 /* ColorStyler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9169381C271B650700A634ED /* ColorStyler.swift */; }; 70A98D68278A2DF1009B58F2 /* TintColorKey.swift in Sources */ = {isa = PBXBuildFile; fileRef = 918FDEC2271B4E950045A0EF /* TintColorKey.swift */; }; @@ -165,6 +175,11 @@ 707CC712254DA91900116728 /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.stringsdict; name = en; path = en.lproj/Localizable.stringsdict; sourceTree = ""; }; 707CC713254DA91900116728 /* OCKLocalization.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OCKLocalization.swift; sourceTree = ""; }; 7083A855279CA40A00B3832E /* PCKUtility.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PCKUtility.swift; sourceTree = ""; }; + 7099D1FE29E98D900037CD8E /* AppError.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppError.swift; sourceTree = ""; }; + 7099D20129E98DDF0037CD8E /* MainViewPath.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MainViewPath.swift; sourceTree = ""; }; + 7099D20429E98DFF0037CD8E /* TaskID.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TaskID.swift; sourceTree = ""; }; + 7099D20729E98E200037CD8E /* UserType.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserType.swift; sourceTree = ""; }; + 7099D20A29E98E400037CD8E /* InstallationChannel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InstallationChannel.swift; sourceTree = ""; }; 70BD2B1E254B44DB0030B424 /* LoginView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoginView.swift; sourceTree = ""; }; 70CF66E328E1E74C00FAE977 /* TintColorFlipKey.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TintColorFlipKey.swift; sourceTree = ""; }; 70F03A942786093B00E5AFB4 /* OCKHealthKitPassthroughStore.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OCKHealthKitPassthroughStore.swift; sourceTree = ""; }; @@ -355,6 +370,15 @@ path = Localization; sourceTree = ""; }; + 7099D1FD29E98D780037CD8E /* Parse */ = { + isa = PBXGroup; + children = ( + 918FDEB4271B40590045A0EF /* Installation.swift */, + 70077596252228E900EC0EDA /* User.swift */, + ); + path = Parse; + sourceTree = ""; + }; 70A98D64278A2722009B58F2 /* Care */ = { isa = PBXGroup; children = ( @@ -406,8 +430,12 @@ 918FDEB3271B402F0045A0EF /* Models */ = { isa = PBXGroup; children = ( - 918FDEB4271B40590045A0EF /* Installation.swift */, - 70077596252228E900EC0EDA /* User.swift */, + 7099D1FE29E98D900037CD8E /* AppError.swift */, + 7099D20A29E98E400037CD8E /* InstallationChannel.swift */, + 7099D20129E98DDF0037CD8E /* MainViewPath.swift */, + 7099D20429E98DFF0037CD8E /* TaskID.swift */, + 7099D20729E98E200037CD8E /* UserType.swift */, + 7099D1FD29E98D780037CD8E /* Parse */, ); path = Models; sourceTree = ""; @@ -707,10 +735,12 @@ files = ( 70F921B327CAC16E00368CEC /* LocalSyncSessionDelegate.swift in Sources */, 70221F3928D8C10800971195 /* StoreCoordinatorKey.swift in Sources */, + 7099D20029E98D900037CD8E /* AppError.swift in Sources */, 70A98D63278A268B009B58F2 /* ColorStyler.swift in Sources */, 918FDEB8271B49060045A0EF /* Logger.swift in Sources */, 91AD923B24A4C42D00925D4D /* CareView.swift in Sources */, 70F921A927CA9A4000368CEC /* CustomStylerKey.swift in Sources */, + 7099D20629E98DFF0037CD8E /* TaskID.swift in Sources */, 7075151E28DE1A8300A57A0C /* MainView.swift in Sources */, 70A98D62278A2683009B58F2 /* Styler.swift in Sources */, 91693818271B5E1600A634ED /* Constants.swift in Sources */, @@ -722,9 +752,11 @@ 70F03AA627860B2500E5AFB4 /* OCKPatient+Parse.swift in Sources */, 70221F3828D8C0CB00971195 /* AppDelegateKey.swift in Sources */, 9103AA5227B8C913002C921E /* FontColorKey.swift in Sources */, + 7099D20329E98DDF0037CD8E /* MainViewPath.swift in Sources */, 707CC719254DA91900116728 /* OCKLocalization.swift in Sources */, 7007759B252229C900EC0EDA /* User.swift in Sources */, 70A98D68278A2DF1009B58F2 /* TintColorKey.swift in Sources */, + 7099D20929E98E200037CD8E /* UserType.swift in Sources */, 918FDEB9271B493A0045A0EF /* Installation.swift in Sources */, 91AD923F24A4C42D00925D4D /* NotificationController.swift in Sources */, 70CF66E528E1E74C00FAE977 /* TintColorFlipKey.swift in Sources */, @@ -732,6 +764,7 @@ 70C0D474279BA492003DA141 /* Utility.swift in Sources */, 91AD923D24A4C42D00925D4D /* AppDelegate.swift in Sources */, 91AD924124A4C42D00925D4D /* NotificationView.swift in Sources */, + 7099D20C29E98E400037CD8E /* InstallationChannel.swift in Sources */, 70308886258273D400FFABB6 /* LoginViewModel.swift in Sources */, 70F921B127CAC16E00368CEC /* SessionDelegate.swift in Sources */, ); @@ -743,9 +776,11 @@ files = ( E7440E4F229477F7007AD30A /* CareViewController.swift in Sources */, 918FDEB5271B40590045A0EF /* Installation.swift in Sources */, + 7099D20829E98E200037CD8E /* UserType.swift in Sources */, 70F921AC27CABE3000368CEC /* SessionDelegate.swift in Sources */, 70F0EFEA28C2EE6C0005B5A2 /* AppDelegateKey.swift in Sources */, 7036E64025717F85006E9A3C /* Constants.swift in Sources */, + 7099D20529E98DFF0037CD8E /* TaskID.swift in Sources */, 7036E4CE256E9A0C006E9A3C /* ContactView.swift in Sources */, 70F03A972786098F00E5AFB4 /* OCKStore.swift in Sources */, 70DFD80B2567074500B9DB12 /* LoginView.swift in Sources */, @@ -761,8 +796,10 @@ 918FDEC3271B4E950045A0EF /* TintColorKey.swift in Sources */, 70CF66E428E1E74C00FAE977 /* TintColorFlipKey.swift in Sources */, 70F03AA327860AFF00E5AFB4 /* OCKPatient+Parse.swift in Sources */, + 7099D1FF29E98D900037CD8E /* AppError.swift in Sources */, 70F0EFE828C2EC050005B5A2 /* OCKSampleApp.swift in Sources */, 918FDEB7271B41FF0045A0EF /* Logger.swift in Sources */, + 7099D20229E98DDF0037CD8E /* MainViewPath.swift in Sources */, 70221F2A28D7BE0600971195 /* AppDelegate+ParseRemoteDelegate.swift in Sources */, 7036E4D3256EBE35006E9A3C /* MainView.swift in Sources */, 918FDEC5271B4EA70045A0EF /* StoreCoordinatorKey.swift in Sources */, @@ -776,6 +813,7 @@ 70F03A952786093B00E5AFB4 /* OCKHealthKitPassthroughStore.swift in Sources */, 7036E4BF256DA089006E9A3C /* LoginViewModel.swift in Sources */, 7036E517256F2413006E9A3C /* ProfileViewModel.swift in Sources */, + 7099D20B29E98E400037CD8E /* InstallationChannel.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; diff --git a/OCKSample/Constants.swift b/OCKSample/Constants.swift index d3bf2b6..8b53aa1 100644 --- a/OCKSample/Constants.swift +++ b/OCKSample/Constants.swift @@ -24,46 +24,6 @@ let isSyncingWithCloud = true */ let isSendingPushUpdatesToWatch = true -enum AppError: Error { - case couldntCast - case couldntBeUnwrapped - case valueNotFoundInUserInfo - case remoteClockIDNotAvailable - case emptyTaskEvents - case invalidIndexPath(_ indexPath: IndexPath) - case noOutcomeValueForEvent(_ event: OCKAnyEvent, index: Int) - case cannotMakeOutcomeFor(_ event: OCKAnyEvent) - case parseError(_ error: ParseError) - case error(_ error: Error) - case errorString(_ string: String) -} - -extension AppError: LocalizedError { - public var errorDescription: String? { - switch self { - case .couldntCast: - return NSLocalizedString("OCKSampleError: Could not cast to required type.", - comment: "Casting error") - case .couldntBeUnwrapped: - return NSLocalizedString("OCKSampleError: Could not unwrap a required type.", - comment: "Unwrapping error") - case .valueNotFoundInUserInfo: - return NSLocalizedString("OCKSampleError: Could not find the required value in userInfo.", - comment: "Value not found error") - case .remoteClockIDNotAvailable: - return NSLocalizedString("OCKSampleError: Could not get remote clock ID.", - comment: "Value not available error") - case .emptyTaskEvents: return "Task events is empty" - case let .noOutcomeValueForEvent(event, index): return "Event has no outcome value at index \(index): \(event)" - case .invalidIndexPath(let indexPath): return "Invalid index path \(indexPath)" - case .cannotMakeOutcomeFor(let event): return "Cannot make outcome for event: \(event)" - case .parseError(let error): return "\(error)" - case .error(let error): return "\(error)" - case .errorString(let string): return string - } - } -} - enum Constants { static let parseConfigFileName = "ParseCareKit" static let iOSParseCareStoreName = "iOSParseStore" @@ -80,34 +40,3 @@ enum Constants { static let userTypeKey = "userType" static let appName = "ParseCareKitSample" } - -enum MainViewPath { - case tabs -} - -enum TaskID { - static let doxylamine = "doxylamine" - static let nausea = "nausea" - static let stretch = "stretch" - static let kegels = "kegels" - static let steps = "steps" - - static var ordered: [String] { - [Self.steps, Self.doxylamine, Self.kegels, Self.stretch, Self.nausea] - } -} - -enum UserType: String, Codable { - case patient = "Patient" - case none = "None" - - // Return all types as an array, make sure to maintain order above - func allTypesAsArray() -> [String] { - return [UserType.patient.rawValue, - UserType.none.rawValue] - } -} - -enum InstallationChannel: String { - case global -} diff --git a/OCKSample/Models/AppError.swift b/OCKSample/Models/AppError.swift new file mode 100644 index 0000000..977e5c3 --- /dev/null +++ b/OCKSample/Models/AppError.swift @@ -0,0 +1,51 @@ +// +// AppError.swift +// OCKSample +// +// Created by Corey Baker on 4/14/23. +// Copyright © 2023 Network Reconnaissance Lab. All rights reserved. +// + +import CareKitStore +import Foundation +import ParseSwift + +enum AppError: Error { + case couldntCast + case couldntBeUnwrapped + case valueNotFoundInUserInfo + case remoteClockIDNotAvailable + case emptyTaskEvents + case invalidIndexPath(_ indexPath: IndexPath) + case noOutcomeValueForEvent(_ event: OCKAnyEvent, index: Int) + case cannotMakeOutcomeFor(_ event: OCKAnyEvent) + case parseError(_ error: ParseError) + case error(_ error: Error) + case errorString(_ string: String) +} + +extension AppError: LocalizedError { + public var errorDescription: String? { + switch self { + case .couldntCast: + return NSLocalizedString("OCKSampleError: Could not cast to required type.", + comment: "Casting error") + case .couldntBeUnwrapped: + return NSLocalizedString("OCKSampleError: Could not unwrap a required type.", + comment: "Unwrapping error") + case .valueNotFoundInUserInfo: + return NSLocalizedString("OCKSampleError: Could not find the required value in userInfo.", + comment: "Value not found error") + case .remoteClockIDNotAvailable: + return NSLocalizedString("OCKSampleError: Could not get remote clock ID.", + comment: "Value not available error") + case .emptyTaskEvents: return "Task events is empty" + case let .noOutcomeValueForEvent(event, index): return "Event has no outcome value at index \(index): \(event)" + case .invalidIndexPath(let indexPath): return "Invalid index path \(indexPath)" + case .cannotMakeOutcomeFor(let event): return "Cannot make outcome for event: \(event)" + case .parseError(let error): return "\(error)" + case .error(let error): return "\(error)" + case .errorString(let string): return string + } + } +} diff --git a/OCKSample/Models/InstallationChannel.swift b/OCKSample/Models/InstallationChannel.swift new file mode 100644 index 0000000..24ea1fd --- /dev/null +++ b/OCKSample/Models/InstallationChannel.swift @@ -0,0 +1,13 @@ +// +// InstallationChannel.swift +// OCKSample +// +// Created by Corey Baker on 4/14/23. +// Copyright © 2023 Network Reconnaissance Lab. All rights reserved. +// + +import Foundation + +enum InstallationChannel: String { + case global +} diff --git a/OCKSample/Models/MainViewPath.swift b/OCKSample/Models/MainViewPath.swift new file mode 100644 index 0000000..c312134 --- /dev/null +++ b/OCKSample/Models/MainViewPath.swift @@ -0,0 +1,13 @@ +// +// MainViewPath.swift +// OCKSample +// +// Created by Corey Baker on 4/14/23. +// Copyright © 2023 Network Reconnaissance Lab. All rights reserved. +// + +import Foundation + +enum MainViewPath { + case tabs +} diff --git a/OCKSample/Models/Installation.swift b/OCKSample/Models/Parse/Installation.swift similarity index 100% rename from OCKSample/Models/Installation.swift rename to OCKSample/Models/Parse/Installation.swift diff --git a/OCKSample/Models/User.swift b/OCKSample/Models/Parse/User.swift similarity index 100% rename from OCKSample/Models/User.swift rename to OCKSample/Models/Parse/User.swift diff --git a/OCKSample/Models/TaskID.swift b/OCKSample/Models/TaskID.swift new file mode 100644 index 0000000..192ff2a --- /dev/null +++ b/OCKSample/Models/TaskID.swift @@ -0,0 +1,21 @@ +// +// TaskID.swift +// OCKSample +// +// Created by Corey Baker on 4/14/23. +// Copyright © 2023 Network Reconnaissance Lab. All rights reserved. +// + +import Foundation + +enum TaskID { + static let doxylamine = "doxylamine" + static let nausea = "nausea" + static let stretch = "stretch" + static let kegels = "kegels" + static let steps = "steps" + + static var ordered: [String] { + [Self.steps, Self.doxylamine, Self.kegels, Self.stretch, Self.nausea] + } +} diff --git a/OCKSample/Models/UserType.swift b/OCKSample/Models/UserType.swift new file mode 100644 index 0000000..8e8b8ab --- /dev/null +++ b/OCKSample/Models/UserType.swift @@ -0,0 +1,20 @@ +// +// UserType.swift +// OCKSample +// +// Created by Corey Baker on 4/14/23. +// Copyright © 2023 Network Reconnaissance Lab. All rights reserved. +// + +import Foundation + +enum UserType: String, Codable { + case patient = "Patient" + case none = "None" + + // Return all types as an array, make sure to maintain order above + func allTypesAsArray() -> [String] { + return [UserType.patient.rawValue, + UserType.none.rawValue] + } +} From 38fe43aea91463a24a14b3cd3fc5036d99d9e13d Mon Sep 17 00:00:00 2001 From: Corey Baker Date: Fri, 14 Apr 2023 11:16:34 -0400 Subject: [PATCH 29/52] fix crash when deleting app --- OCKSample/Extensions/AppDelegate+UIApplicationDelegate.swift | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/OCKSample/Extensions/AppDelegate+UIApplicationDelegate.swift b/OCKSample/Extensions/AppDelegate+UIApplicationDelegate.swift index 15188e1..b29f0e7 100644 --- a/OCKSample/Extensions/AppDelegate+UIApplicationDelegate.swift +++ b/OCKSample/Extensions/AppDelegate+UIApplicationDelegate.swift @@ -14,7 +14,6 @@ extension AppDelegate: UIApplicationDelegate { func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { Task { - await Utility.clearDeviceOnFirstRun() if isSyncingWithCloud { do { // Parse-Server setup @@ -25,6 +24,7 @@ extension AppDelegate: UIApplicationDelegate { Logger.appDelegate.info("Could not configure Parse Swift: \(error)") return } + await Utility.clearDeviceOnFirstRun() do { _ = try await User.current() Logger.appDelegate.info("User is already signed in...") @@ -44,6 +44,7 @@ extension AppDelegate: UIApplicationDelegate { Logger.appDelegate.error("User is not loggied in: \(error)") } } else { + await Utility.clearDeviceOnFirstRun() // When syncing directly with watchOS, we do not care about login and need to setup remotes do { try await setupRemotes() From 776938deea0afbf7cc8543bc5188ae50aabb7a24 Mon Sep 17 00:00:00 2001 From: Corey Date: Sat, 15 Apr 2023 10:56:18 -0400 Subject: [PATCH 30/52] remover extra properties --- OCKSample/OCKSampleApp.swift | 2 +- OCKWatchSample Extension/OCKWatchSampleApp.swift | 2 -- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/OCKSample/OCKSampleApp.swift b/OCKSample/OCKSampleApp.swift index 9dc9397..85e5da9 100644 --- a/OCKSample/OCKSampleApp.swift +++ b/OCKSample/OCKSampleApp.swift @@ -21,7 +21,7 @@ struct OCKSampleApp: App { MainView() .environment(\.appDelegate, appDelegate) .accentColor(Color(tintColor)) - .careKitStyle(Styler()) + .careKitStyle(style) } } } diff --git a/OCKWatchSample Extension/OCKWatchSampleApp.swift b/OCKWatchSample Extension/OCKWatchSampleApp.swift index c0f3a60..a829d80 100644 --- a/OCKWatchSample Extension/OCKWatchSampleApp.swift +++ b/OCKWatchSample Extension/OCKWatchSampleApp.swift @@ -12,10 +12,8 @@ import SwiftUI @main struct OCKWatchSampleApp: App { @WKApplicationDelegateAdaptor private var appDelegate: AppDelegate - @Environment(\.scenePhase) private var scenePhase @Environment(\.tintColor) private var tintColor @Environment(\.customStyler) private var style - @State var isActive = false @SceneBuilder var body: some Scene { WindowGroup { MainView() From 5453a5a18d69a60eeb0351026e8986734cef6105 Mon Sep 17 00:00:00 2001 From: Corey Date: Sat, 15 Apr 2023 11:23:51 -0400 Subject: [PATCH 31/52] Simplify views --- OCKSample/Main/Care/CareView.swift | 1 + OCKSample/Main/MainTabView.swift | 1 - OCKSample/Main/MainView.swift | 28 ++++++++++---------- OCKSample/Main/Profile/ProfileView.swift | 5 ++-- OCKWatchSample Extension/Main/MainView.swift | 27 +++++++++---------- 5 files changed, 30 insertions(+), 32 deletions(-) diff --git a/OCKSample/Main/Care/CareView.swift b/OCKSample/Main/Care/CareView.swift index c9ab958..841f5d5 100644 --- a/OCKSample/Main/Care/CareView.swift +++ b/OCKSample/Main/Care/CareView.swift @@ -56,5 +56,6 @@ struct CareView_Previews: PreviewProvider { static var previews: some View { CareView() .accentColor(Color(TintColorKey.defaultValue)) + .environment(\.careStore, Utility.createPreviewStore()) } } diff --git a/OCKSample/Main/MainTabView.swift b/OCKSample/Main/MainTabView.swift index afbef83..693265a 100644 --- a/OCKSample/Main/MainTabView.swift +++ b/OCKSample/Main/MainTabView.swift @@ -53,7 +53,6 @@ struct MainTabView: View { } .tag(2) } - .navigationBarHidden(true) } } diff --git a/OCKSample/Main/MainView.swift b/OCKSample/Main/MainView.swift index f5ef6fe..caae433 100644 --- a/OCKSample/Main/MainView.swift +++ b/OCKSample/Main/MainView.swift @@ -12,8 +12,8 @@ import CareKitUI struct MainView: View { @EnvironmentObject private var appDelegate: AppDelegate - @StateObject var loginViewModel = LoginViewModel() - @State var path = [MainViewPath]() + @StateObject private var loginViewModel = LoginViewModel() + @State private var path = [MainViewPath]() @State private var storeCoordinator = OCKStoreCoordinator() var body: some View { @@ -24,26 +24,26 @@ struct MainView: View { case .tabs: if isSyncingWithCloud { MainTabView(loginViewModel: loginViewModel) + .navigationBarHidden(true) } else { CareView() .navigationBarHidden(true) } } } - .navigationBarHidden(true) - .onAppear { - guard isSyncingWithCloud else { - path = [.tabs] - return - } - guard !loginViewModel.isLoggedOut else { - path = [] - return - } - path = [.tabs] - } } .environment(\.careStore, storeCoordinator) + .onAppear { + guard isSyncingWithCloud else { + path = [.tabs] + return + } + guard !loginViewModel.isLoggedOut else { + path = [] + return + } + path = [.tabs] + } .onReceive(loginViewModel.$isLoggedOut, perform: { isLoggedOut in guard !isLoggedOut else { path = [] diff --git a/OCKSample/Main/Profile/ProfileView.swift b/OCKSample/Main/Profile/ProfileView.swift index b40051c..ab2fa7a 100644 --- a/OCKSample/Main/Profile/ProfileView.swift +++ b/OCKSample/Main/Profile/ProfileView.swift @@ -15,7 +15,7 @@ import os.log struct ProfileView: View { private static var query = OCKPatientQuery(for: Date()) @CareStoreFetchRequest(query: query) private var patients - @StateObject var viewModel = ProfileViewModel() + @StateObject private var viewModel = ProfileViewModel() @ObservedObject var loginViewModel: LoginViewModel var body: some View { @@ -83,8 +83,7 @@ struct ProfileView: View { struct ProfileView_Previews: PreviewProvider { static var previews: some View { - ProfileView(viewModel: .init(), - loginViewModel: .init()) + ProfileView(loginViewModel: .init()) .accentColor(Color(TintColorKey.defaultValue)) .environment(\.careStore, Utility.createPreviewStore()) } diff --git a/OCKWatchSample Extension/Main/MainView.swift b/OCKWatchSample Extension/Main/MainView.swift index 9a2942f..021cef8 100644 --- a/OCKWatchSample Extension/Main/MainView.swift +++ b/OCKWatchSample Extension/Main/MainView.swift @@ -12,8 +12,8 @@ import os.log struct MainView: View { @EnvironmentObject private var appDelegate: AppDelegate - @StateObject var loginViewModel = LoginViewModel() - @State var path = [MainViewPath]() + @StateObject private var loginViewModel = LoginViewModel() + @State private var path = [MainViewPath]() @State private var store = OCKStore(name: Constants.noCareStoreName, type: .inMemory) @@ -24,23 +24,22 @@ struct MainView: View { switch destination { case .tabs: CareView() - .navigationBarHidden(true) } } .navigationBarHidden(true) - .onAppear { - guard isSyncingWithCloud else { - path = [.tabs] - return - } - guard !loginViewModel.isLoggedOut else { - path = [] - return - } - path = [.tabs] - } } .environment(\.careStore, store) + .onAppear { + guard isSyncingWithCloud else { + path = [.tabs] + return + } + guard !loginViewModel.isLoggedOut else { + path = [] + return + } + path = [.tabs] + } .onReceive(loginViewModel.$isLoggedOut, perform: { isLoggedOut in guard isSyncingWithCloud else { path = [.tabs] From 00332ad1c67ee721bf0571668f4b66e8736a5884 Mon Sep 17 00:00:00 2001 From: Corey Date: Sat, 15 Apr 2023 12:10:25 -0400 Subject: [PATCH 32/52] Use careStore environment property in more views --- OCKSample/Main/Care/CareView.swift | 14 ++++++++------ OCKSample/Main/Care/CareViewController.swift | 6 ++---- OCKSample/Main/Contact/ContactView.swift | 9 +++++---- OCKSample/Main/Login/LoginView.swift | 2 +- OCKSample/Main/Login/LoginViewModel.swift | 7 +++---- OCKSample/Main/MainTabView.swift | 2 +- OCKSample/Main/MainView.swift | 3 ++- OCKSample/Main/Profile/ProfileView.swift | 2 +- OCKSample/Main/Profile/ProfileViewModel.swift | 3 --- OCKSample/Main/Stylers/ColorStyler.swift | 1 - OCKSample/Main/Stylers/Styler.swift | 1 - 11 files changed, 23 insertions(+), 27 deletions(-) diff --git a/OCKSample/Main/Care/CareView.swift b/OCKSample/Main/Care/CareView.swift index 841f5d5..13133a8 100644 --- a/OCKSample/Main/Care/CareView.swift +++ b/OCKSample/Main/Care/CareView.swift @@ -8,11 +8,11 @@ // swiftlint:disable:next line_length // This file embeds a UIKit View Controller inside of a SwiftUI view. I used this tutorial to figure this out https://developer.apple.com/tutorials/swiftui/interfacing-with-uikit -import SwiftUI -import UIKit import CareKit import CareKitStore import os.log +import SwiftUI +import UIKit struct CareView: UIViewControllerRepresentable { private static var query: OCKEventQuery { @@ -20,8 +20,9 @@ struct CareView: UIViewControllerRepresentable { query.taskIDs = [TaskID.steps] return query } + @Environment(\.appDelegate) private var appDelegate + @Environment(\.careStore) private var careStore @CareStoreFetchRequest(query: query) private var events - @EnvironmentObject private var appDelegate: AppDelegate func makeUIViewController(context: Context) -> some UIViewController { let viewController = createViewController() @@ -37,8 +38,8 @@ struct CareView: UIViewControllerRepresentable { Logger.feed.error("CareView should have been a UINavigationController") return } - guard careViewController.store !== appDelegate.storeCoordinator || - appDelegate.isFirstTimeLogin else { + guard careViewController.store !== careStore || + appDelegate?.isFirstTimeLogin == true else { // No need to replace view // careViewController.events = events return @@ -47,7 +48,7 @@ struct CareView: UIViewControllerRepresentable { } func createViewController() -> UIViewController { - CareViewController(store: appDelegate.storeCoordinator, + CareViewController(store: careStore, events: events) } } @@ -56,6 +57,7 @@ struct CareView_Previews: PreviewProvider { static var previews: some View { CareView() .accentColor(Color(TintColorKey.defaultValue)) + .environment(\.appDelegate, AppDelegate()) .environment(\.careStore, Utility.createPreviewStore()) } } diff --git a/OCKSample/Main/Care/CareViewController.swift b/OCKSample/Main/Care/CareViewController.swift index 250cd37..171812c 100644 --- a/OCKSample/Main/Care/CareViewController.swift +++ b/OCKSample/Main/Care/CareViewController.swift @@ -28,14 +28,12 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -import Foundation -import UIKit -import SwiftUI -import Combine import CareKit import CareKitStore import CareKitUI import os.log +import SwiftUI +import UIKit class CareViewController: OCKDailyPageViewController { diff --git a/OCKSample/Main/Contact/ContactView.swift b/OCKSample/Main/Contact/ContactView.swift index 42ecd69..203c531 100644 --- a/OCKSample/Main/Contact/ContactView.swift +++ b/OCKSample/Main/Contact/ContactView.swift @@ -6,14 +6,14 @@ // Copyright © 2020 Network Reconnaissance Lab. All rights reserved. // -import SwiftUI -import UIKit import CareKit import CareKitStore import os.log +import SwiftUI +import UIKit struct ContactView: UIViewControllerRepresentable { - @EnvironmentObject private var appDelegate: AppDelegate + @Environment(\.careStore) var careStore func makeUIViewController(context: Context) -> some UIViewController { let viewController = createViewController() @@ -30,7 +30,7 @@ struct ContactView: UIViewControllerRepresentable { } func createViewController() -> UIViewController { - OCKContactsListViewController(store: appDelegate.storeCoordinator, + OCKContactsListViewController(store: careStore, contactViewSynchronizer: OCKDetailedContactViewSynchronizer()) } } @@ -40,5 +40,6 @@ struct ContactView_Previews: PreviewProvider { static var previews: some View { ContactView() .accentColor(Color(TintColorKey.defaultValue)) + .environment(\.careStore, Utility.createPreviewStore()) } } diff --git a/OCKSample/Main/Login/LoginView.swift b/OCKSample/Main/Login/LoginView.swift index c182c5b..a902955 100644 --- a/OCKSample/Main/Login/LoginView.swift +++ b/OCKSample/Main/Login/LoginView.swift @@ -11,8 +11,8 @@ https://www.iosapptemplates.com/blog/swiftui/login-screen-swiftui */ -import SwiftUI import ParseSwift +import SwiftUI import UIKit /* diff --git a/OCKSample/Main/Login/LoginViewModel.swift b/OCKSample/Main/Login/LoginViewModel.swift index d307dbf..a88c703 100644 --- a/OCKSample/Main/Login/LoginViewModel.swift +++ b/OCKSample/Main/Login/LoginViewModel.swift @@ -6,13 +6,12 @@ // Copyright © 2020 Network Reconnaissance Lab. All rights reserved. // -import Foundation -import ParseCareKit -import ParseSwift import CareKit import CareKitStore -import WatchConnectivity +import ParseCareKit +import ParseSwift import os.log +import WatchConnectivity class LoginViewModel: ObservableObject { diff --git a/OCKSample/Main/MainTabView.swift b/OCKSample/Main/MainTabView.swift index 693265a..ce8ebfb 100644 --- a/OCKSample/Main/MainTabView.swift +++ b/OCKSample/Main/MainTabView.swift @@ -8,8 +8,8 @@ // swiftlint:disable:next line_length // This was built using tutorial: https://www.hackingwithswift.com/books/ios-swiftui/creating-tabs-with-tabview-and-tabitem -import SwiftUI import CareKitStore +import SwiftUI struct MainTabView: View { @ObservedObject var loginViewModel: LoginViewModel diff --git a/OCKSample/Main/MainView.swift b/OCKSample/Main/MainView.swift index caae433..bd50222 100644 --- a/OCKSample/Main/MainView.swift +++ b/OCKSample/Main/MainView.swift @@ -5,10 +5,10 @@ // Created by Corey Baker on 11/25/20. // Copyright © 2020 Network Reconnaissance Lab. All rights reserved. -import SwiftUI import CareKit import CareKitStore import CareKitUI +import SwiftUI struct MainView: View { @EnvironmentObject private var appDelegate: AppDelegate @@ -63,6 +63,7 @@ struct MainView: View { struct MainView_Previews: PreviewProvider { static var previews: some View { MainView() + .environment(\.appDelegate, AppDelegate()) .environment(\.careStore, Utility.createPreviewStore()) .accentColor(Color(TintColorKey.defaultValue)) } diff --git a/OCKSample/Main/Profile/ProfileView.swift b/OCKSample/Main/Profile/ProfileView.swift index ab2fa7a..928ddbd 100644 --- a/OCKSample/Main/Profile/ProfileView.swift +++ b/OCKSample/Main/Profile/ProfileView.swift @@ -6,11 +6,11 @@ // Copyright © 2020 Network Reconnaissance Lab. All rights reserved. // -import SwiftUI import CareKitUI import CareKitStore import CareKit import os.log +import SwiftUI struct ProfileView: View { private static var query = OCKPatientQuery(for: Date()) diff --git a/OCKSample/Main/Profile/ProfileViewModel.swift b/OCKSample/Main/Profile/ProfileViewModel.swift index 3caacb5..d1c7b92 100644 --- a/OCKSample/Main/Profile/ProfileViewModel.swift +++ b/OCKSample/Main/Profile/ProfileViewModel.swift @@ -6,14 +6,11 @@ // Copyright © 2020 Network Reconnaissance Lab. All rights reserved. // -import Foundation import CareKit import CareKitStore import CareKitUtilities import SwiftUI -import ParseCareKit import os.log -import Combine class ProfileViewModel: ObservableObject { diff --git a/OCKSample/Main/Stylers/ColorStyler.swift b/OCKSample/Main/Stylers/ColorStyler.swift index e539fcd..e89a510 100644 --- a/OCKSample/Main/Stylers/ColorStyler.swift +++ b/OCKSample/Main/Stylers/ColorStyler.swift @@ -6,7 +6,6 @@ // Copyright © 2021 Network Reconnaissance Lab. All rights reserved. // -import Foundation import CareKitUI import UIKit diff --git a/OCKSample/Main/Stylers/Styler.swift b/OCKSample/Main/Stylers/Styler.swift index 049f58d..cff4412 100644 --- a/OCKSample/Main/Stylers/Styler.swift +++ b/OCKSample/Main/Stylers/Styler.swift @@ -6,7 +6,6 @@ // Copyright © 2021 Network Reconnaissance Lab. All rights reserved. // -import Foundation import CareKitUI struct Styler: OCKStyler { From d62803b568842e7734211e5bb84478d7d0f55de1 Mon Sep 17 00:00:00 2001 From: Corey Date: Sat, 15 Apr 2023 12:26:39 -0400 Subject: [PATCH 33/52] Add bday to preview store --- OCKSample/Main/MainTabView.swift | 1 - OCKSample/Utility.swift | 5 ++++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/OCKSample/Main/MainTabView.swift b/OCKSample/Main/MainTabView.swift index ce8ebfb..02085f9 100644 --- a/OCKSample/Main/MainTabView.swift +++ b/OCKSample/Main/MainTabView.swift @@ -61,6 +61,5 @@ struct MainTabView_Previews: PreviewProvider { MainTabView(loginViewModel: .init()) .accentColor(Color(TintColorKey.defaultValue)) .environment(\.careStore, Utility.createPreviewStore()) - } } diff --git a/OCKSample/Utility.swift b/OCKSample/Utility.swift index a0abace..13fc639 100644 --- a/OCKSample/Utility.swift +++ b/OCKSample/Utility.swift @@ -112,9 +112,12 @@ class Utility { // If patient exists, assume store is already populated _ = try await store.fetchPatient(withID: patientId) } catch { - let patient = OCKPatient(id: patientId, + var patient = OCKPatient(id: patientId, givenName: "Preview", familyName: "Patient") + patient.birthday = Calendar.current.date(byAdding: .year, + value: -20, + to: Date()) _ = try? await store.addPatient(patient) try? await store.populateSampleData() } From 4f3538b5b2813ebe440b2a90279e2519c2c3543a Mon Sep 17 00:00:00 2001 From: Corey Date: Sat, 15 Apr 2023 13:42:03 -0400 Subject: [PATCH 34/52] Improve watch sync --- .../AppDelegate+UIApplicationDelegate.swift | 6 +-- OCKSample/Main/MainView.swift | 22 ++++---- OCKWatchSample Extension/AppDelegate.swift | 51 +++++++++++-------- OCKWatchSample Extension/Main/MainView.swift | 23 +++++---- 4 files changed, 56 insertions(+), 46 deletions(-) diff --git a/OCKSample/Extensions/AppDelegate+UIApplicationDelegate.swift b/OCKSample/Extensions/AppDelegate+UIApplicationDelegate.swift index b29f0e7..e41f37f 100644 --- a/OCKSample/Extensions/AppDelegate+UIApplicationDelegate.swift +++ b/OCKSample/Extensions/AppDelegate+UIApplicationDelegate.swift @@ -56,9 +56,9 @@ extension AppDelegate: UIApplicationDelegate { } } catch { Logger.appDelegate.error(""" - Error in SceneDelage, could not populate - data stores: \(error) - """) + Could not populate + data stores: \(error) + """) } } } diff --git a/OCKSample/Main/MainView.swift b/OCKSample/Main/MainView.swift index bd50222..9841c12 100644 --- a/OCKSample/Main/MainView.swift +++ b/OCKSample/Main/MainView.swift @@ -31,19 +31,19 @@ struct MainView: View { } } } + .onAppear { + guard isSyncingWithCloud else { + path = [.tabs] + return + } + guard !loginViewModel.isLoggedOut else { + path = [] + return + } + path = [.tabs] + } } .environment(\.careStore, storeCoordinator) - .onAppear { - guard isSyncingWithCloud else { - path = [.tabs] - return - } - guard !loginViewModel.isLoggedOut else { - path = [] - return - } - path = [.tabs] - } .onReceive(loginViewModel.$isLoggedOut, perform: { isLoggedOut in guard !isLoggedOut else { path = [] diff --git a/OCKWatchSample Extension/AppDelegate.swift b/OCKWatchSample Extension/AppDelegate.swift index 50d9fe4..b01ecd3 100644 --- a/OCKWatchSample Extension/AppDelegate.swift +++ b/OCKWatchSample Extension/AppDelegate.swift @@ -36,33 +36,42 @@ class AppDelegate: NSObject, WKApplicationDelegate, ObservableObject { func applicationDidFinishLaunching() { Task { - do { - // Parse-server setup - try await PCKUtility.setupServer(fileName: Constants.parseConfigFileName) { _, completionHandler in - completionHandler(.performDefaultHandling, nil) - } - await Utility.clearDeviceOnFirstRun() + if isSyncingWithCloud { do { - _ = try await User.current() + // Parse-server setup + try await PCKUtility.setupServer(fileName: Constants.parseConfigFileName) { _, completionHandler in + completionHandler(.performDefaultHandling, nil) + } + await Utility.clearDeviceOnFirstRun() do { - let uuid = try await Utility.getRemoteClockUUID() - try await self.setupRemotes(uuid: uuid) - Logger.appDelegate.info("User is already signed in...") + _ = try await User.current() + do { + let uuid = try await Utility.getRemoteClockUUID() + try await self.setupRemotes(uuid: uuid) + Logger.appDelegate.info("User is already signed in...") + } catch { + Logger.appDelegate.error("User is logged in, but missing remoteId: \(error)") + try await setupRemotes(uuid: nil) + } + parseRemote.automaticallySynchronizes = true } catch { - Logger.appDelegate.error("User is logged in, but missing remoteId: \(error)") + Logger.appDelegate.info("User is not logged in...") try await setupRemotes(uuid: nil) } - if isSyncingWithCloud { - parseRemote.automaticallySynchronizes = true - } else { - phoneRemote.automaticallySynchronizes = true - } } catch { - Logger.appDelegate.info("User is not logged in...") - try await setupRemotes(uuid: nil) + Logger.appDelegate.info("Could not configure Parse Swift: \(error)") + } + } else { + await Utility.clearDeviceOnFirstRun() + do { + try await self.setupRemotes() + phoneRemote.automaticallySynchronizes = true + } catch { + Logger.appDelegate.error(""" + Could not populate + data stores: \(error) + """) } - } catch { - Logger.appDelegate.info("Could not configure Parse Swift: \(error)") } } } @@ -98,7 +107,7 @@ class AppDelegate: NSObject, WKApplicationDelegate, ObservableObject { self.store = store } else { let store = OCKStore(name: Constants.watchOSLocalCareStoreName, - type: .inMemory, + type: .onDisk(), remote: phoneRemote) phoneRemote.delegate = self sessionDelegate = LocalSessionDelegate(remote: phoneRemote, diff --git a/OCKWatchSample Extension/Main/MainView.swift b/OCKWatchSample Extension/Main/MainView.swift index 021cef8..34d2fed 100644 --- a/OCKWatchSample Extension/Main/MainView.swift +++ b/OCKWatchSample Extension/Main/MainView.swift @@ -24,22 +24,23 @@ struct MainView: View { switch destination { case .tabs: CareView() + .navigationBarHidden(true) } } .navigationBarHidden(true) + .onAppear { + guard isSyncingWithCloud else { + path = [.tabs] + return + } + guard !loginViewModel.isLoggedOut else { + path = [] + return + } + path = [.tabs] + } } .environment(\.careStore, store) - .onAppear { - guard isSyncingWithCloud else { - path = [.tabs] - return - } - guard !loginViewModel.isLoggedOut else { - path = [] - return - } - path = [.tabs] - } .onReceive(loginViewModel.$isLoggedOut, perform: { isLoggedOut in guard isSyncingWithCloud else { path = [.tabs] From 2c65f3c1cc2db1e3ed0ecb9f0c86d7dcd054d10b Mon Sep 17 00:00:00 2001 From: Corey Baker Date: Mon, 17 Apr 2023 22:01:36 -0400 Subject: [PATCH 35/52] Fix entity conflicts --- OCKSample.xcodeproj/project.pbxproj | 2 +- .../xcshareddata/swiftpm/Package.resolved | 4 +-- .../AppDelegate+ParseRemoteDelegate.swift | 30 +++++-------------- 3 files changed, 11 insertions(+), 25 deletions(-) diff --git a/OCKSample.xcodeproj/project.pbxproj b/OCKSample.xcodeproj/project.pbxproj index f82c3cc..585c3d9 100644 --- a/OCKSample.xcodeproj/project.pbxproj +++ b/OCKSample.xcodeproj/project.pbxproj @@ -1245,7 +1245,7 @@ repositoryURL = "https://github.com/netreconlab/ParseCareKit.git"; requirement = { kind = upToNextMajorVersion; - minimumVersion = "1.0.0-alpha.10"; + minimumVersion = "1.0.0-alpha.19"; }; }; /* End XCRemoteSwiftPackageReference section */ diff --git a/OCKSample.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/OCKSample.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved index 9e08fae..3e57651 100644 --- a/OCKSample.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/OCKSample.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -41,8 +41,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/netreconlab/ParseCareKit.git", "state" : { - "revision" : "a2b8190581f6ad8abacda312e9d16fa5d0998762", - "version" : "1.0.0-alpha.10" + "revision" : "8225fbf343908bdd5f2dec679594528911ac2544", + "version" : "1.0.0-alpha.19" } }, { diff --git a/OCKSample/Extensions/AppDelegate+ParseRemoteDelegate.swift b/OCKSample/Extensions/AppDelegate+ParseRemoteDelegate.swift index 354875a..50b0295 100644 --- a/OCKSample/Extensions/AppDelegate+ParseRemoteDelegate.swift +++ b/OCKSample/Extensions/AppDelegate+ParseRemoteDelegate.swift @@ -48,28 +48,14 @@ extension AppDelegate: ParseRemoteDelegate { func chooseConflictResolution(conflicts: [OCKEntity], completion: @escaping OCKResultClosure) { // https://github.com/carekit-apple/CareKit/issues/567 - // Workaround to handle deleted and re-added outcomes. - // Always prefer updates over deletes. - let outcomes = conflicts.compactMap { conflict -> OCKOutcome? in - if case let .outcome(outcome) = conflict { - return outcome - } else { - return nil - } - } - - if outcomes.count == 2, - outcomes.contains(where: { $0.deletedDate != nil}), - let added = outcomes.first(where: { $0.deletedDate == nil}) { - - completion(.success(.outcome(added))) - return - } - - if let first = conflicts.first { - completion(.success(first)) - } else { - completion(.failure(.remoteSynchronizationFailed(reason: "Error, none selected for conflict"))) + // Last write wins + do { + let lastWrite = try conflicts + .max(by: { try $0.parseEntity().value.createdDate! > $1.parseEntity().value.createdDate! })! + + completion(.success(lastWrite)) + } catch { + completion(.failure(.invalidValue(reason: error.localizedDescription))) } } } From eb934f22e39bb026afc877fd531d8740bcafba12 Mon Sep 17 00:00:00 2001 From: Corey Date: Mon, 17 Apr 2023 22:08:34 -0400 Subject: [PATCH 36/52] update conflicts on watch --- .../AppDelegate+ParseRemoteDelegate.swift | 30 +++++-------------- 1 file changed, 8 insertions(+), 22 deletions(-) diff --git a/OCKWatchSample Extension/Extensions/AppDelegate+ParseRemoteDelegate.swift b/OCKWatchSample Extension/Extensions/AppDelegate+ParseRemoteDelegate.swift index 8cb1be7..910ee29 100644 --- a/OCKWatchSample Extension/Extensions/AppDelegate+ParseRemoteDelegate.swift +++ b/OCKWatchSample Extension/Extensions/AppDelegate+ParseRemoteDelegate.swift @@ -36,28 +36,14 @@ extension AppDelegate: ParseRemoteDelegate { func chooseConflictResolution(conflicts: [OCKEntity], completion: @escaping OCKResultClosure) { // https://github.com/carekit-apple/CareKit/issues/567 - // Workaround to handle deleted and re-added outcomes. - // Always prefer updates over deletes. - let outcomes = conflicts.compactMap { conflict -> OCKOutcome? in - if case let .outcome(outcome) = conflict { - return outcome - } else { - return nil - } - } - - if outcomes.count == 2, - outcomes.contains(where: { $0.deletedDate != nil}), - let added = outcomes.first(where: { $0.deletedDate == nil}) { - - completion(.success(.outcome(added))) - return - } - - if let first = conflicts.first { - completion(.success(first)) - } else { - completion(.failure(.remoteSynchronizationFailed(reason: "Error, non selected for conflict"))) + // Last write wins + do { + let lastWrite = try conflicts + .max(by: { try $0.parseEntity().value.createdDate! > $1.parseEntity().value.createdDate! })! + + completion(.success(lastWrite)) + } catch { + completion(.failure(.invalidValue(reason: error.localizedDescription))) } } } From 27d0fa7d2b84e705ab5605c118b1a200dd7106ec Mon Sep 17 00:00:00 2001 From: Corey Date: Mon, 17 Apr 2023 23:12:32 -0400 Subject: [PATCH 37/52] latest --- OCKSample.xcodeproj/project.pbxproj | 2 +- .../project.xcworkspace/xcshareddata/swiftpm/Package.resolved | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/OCKSample.xcodeproj/project.pbxproj b/OCKSample.xcodeproj/project.pbxproj index 585c3d9..8fdd2a3 100644 --- a/OCKSample.xcodeproj/project.pbxproj +++ b/OCKSample.xcodeproj/project.pbxproj @@ -1245,7 +1245,7 @@ repositoryURL = "https://github.com/netreconlab/ParseCareKit.git"; requirement = { kind = upToNextMajorVersion; - minimumVersion = "1.0.0-alpha.19"; + minimumVersion = "1.0.0-alpha.20"; }; }; /* End XCRemoteSwiftPackageReference section */ diff --git a/OCKSample.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/OCKSample.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved index 3e57651..47190f8 100644 --- a/OCKSample.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/OCKSample.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -41,8 +41,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/netreconlab/ParseCareKit.git", "state" : { - "revision" : "8225fbf343908bdd5f2dec679594528911ac2544", - "version" : "1.0.0-alpha.19" + "revision" : "dfd0377e898b8f732fe8c56db6e752ae86b26b91", + "version" : "1.0.0-alpha.20" } }, { From edb2c6b3abf3eddfd52bfcd23c808c2df658634a Mon Sep 17 00:00:00 2001 From: Corey Baker Date: Tue, 18 Apr 2023 09:57:33 -0400 Subject: [PATCH 38/52] Update to latest ParseCareKit --- OCKSample.xcodeproj/project.pbxproj | 2 +- .../project.xcworkspace/xcshareddata/swiftpm/Package.resolved | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/OCKSample.xcodeproj/project.pbxproj b/OCKSample.xcodeproj/project.pbxproj index 8fdd2a3..34da859 100644 --- a/OCKSample.xcodeproj/project.pbxproj +++ b/OCKSample.xcodeproj/project.pbxproj @@ -1245,7 +1245,7 @@ repositoryURL = "https://github.com/netreconlab/ParseCareKit.git"; requirement = { kind = upToNextMajorVersion; - minimumVersion = "1.0.0-alpha.20"; + minimumVersion = "1.0.0-alpha.22"; }; }; /* End XCRemoteSwiftPackageReference section */ diff --git a/OCKSample.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/OCKSample.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved index 47190f8..9f11d78 100644 --- a/OCKSample.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/OCKSample.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -41,8 +41,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/netreconlab/ParseCareKit.git", "state" : { - "revision" : "dfd0377e898b8f732fe8c56db6e752ae86b26b91", - "version" : "1.0.0-alpha.20" + "revision" : "e4902e17c05540a10a0b2dcba337f4eb418027ff", + "version" : "1.0.0-alpha.22" } }, { From 7bfeff6394b1190329c24a3906ff670170cae718 Mon Sep 17 00:00:00 2001 From: Corey Baker Date: Tue, 18 Apr 2023 10:38:21 -0400 Subject: [PATCH 39/52] updates --- OCKSample.xcodeproj/project.pbxproj | 2 +- .../project.xcworkspace/xcshareddata/swiftpm/Package.resolved | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/OCKSample.xcodeproj/project.pbxproj b/OCKSample.xcodeproj/project.pbxproj index 34da859..925145a 100644 --- a/OCKSample.xcodeproj/project.pbxproj +++ b/OCKSample.xcodeproj/project.pbxproj @@ -1245,7 +1245,7 @@ repositoryURL = "https://github.com/netreconlab/ParseCareKit.git"; requirement = { kind = upToNextMajorVersion; - minimumVersion = "1.0.0-alpha.22"; + minimumVersion = "1.0.0-alpha.23"; }; }; /* End XCRemoteSwiftPackageReference section */ diff --git a/OCKSample.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/OCKSample.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved index 9f11d78..b0d21ec 100644 --- a/OCKSample.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/OCKSample.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -41,8 +41,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/netreconlab/ParseCareKit.git", "state" : { - "revision" : "e4902e17c05540a10a0b2dcba337f4eb418027ff", - "version" : "1.0.0-alpha.22" + "revision" : "d5e9686cd7edfa707b875b7af4eb9ec537f09a7d", + "version" : "1.0.0-alpha.23" } }, { From 2d2d2e55561b87ab28f8b95be22e9ffdf747dd71 Mon Sep 17 00:00:00 2001 From: Corey Baker Date: Sat, 22 Apr 2023 08:56:13 -0400 Subject: [PATCH 40/52] updates --- OCKSample.xcodeproj/project.pbxproj | 4 ++-- .../xcshareddata/swiftpm/Package.resolved | 12 ++++++------ 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/OCKSample.xcodeproj/project.pbxproj b/OCKSample.xcodeproj/project.pbxproj index 925145a..dc183c2 100644 --- a/OCKSample.xcodeproj/project.pbxproj +++ b/OCKSample.xcodeproj/project.pbxproj @@ -1237,7 +1237,7 @@ repositoryURL = "https://github.com/netreconlab/CareKitUtilities.git"; requirement = { kind = upToNextMajorVersion; - minimumVersion = "1.0.0-alpha.1"; + minimumVersion = "1.0.0-alpha.2"; }; }; 918FDEAD271B3F8F0045A0EF /* XCRemoteSwiftPackageReference "ParseCareKit" */ = { @@ -1245,7 +1245,7 @@ repositoryURL = "https://github.com/netreconlab/ParseCareKit.git"; requirement = { kind = upToNextMajorVersion; - minimumVersion = "1.0.0-alpha.23"; + minimumVersion = "1.0.0-alpha.41"; }; }; /* End XCRemoteSwiftPackageReference section */ diff --git a/OCKSample.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/OCKSample.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved index b0d21ec..49ecda7 100644 --- a/OCKSample.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/OCKSample.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -5,8 +5,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/cbaker6/CareKit.git", "state" : { - "revision" : "7839f08370d1e3aaa5947d4ab2c3910b8cb7fc4b", - "version" : "3.0.0-alpha.20" + "revision" : "225341b1785513785b7d4a9d4538c2dd48bc0554", + "version" : "3.0.0-alpha.21" } }, { @@ -14,8 +14,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/netreconlab/CareKitUtilities.git", "state" : { - "revision" : "50eab8bc14396a1214a6f0d0ecc5a1e21e92630a", - "version" : "1.0.0-alpha.1" + "revision" : "7c7be19c8f652589fe97692df92cd7ffb634c41c", + "version" : "1.0.0-alpha.2" } }, { @@ -41,8 +41,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/netreconlab/ParseCareKit.git", "state" : { - "revision" : "d5e9686cd7edfa707b875b7af4eb9ec537f09a7d", - "version" : "1.0.0-alpha.23" + "revision" : "d46bf778b9502f408b10e0dae1ccb75d4d153310", + "version" : "1.0.0-alpha.41" } }, { From 6941e91c6c61ad624ff6062ebe98d131a6380360 Mon Sep 17 00:00:00 2001 From: Corey Date: Sat, 22 Apr 2023 16:01:09 -0400 Subject: [PATCH 41/52] Use transactions by default --- OCKSample.xcodeproj/project.pbxproj | 4 ++-- .../xcshareddata/swiftpm/Package.resolved | 8 ++++---- OCKSample/Supporting Files/ParseCareKit.plist | 2 +- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/OCKSample.xcodeproj/project.pbxproj b/OCKSample.xcodeproj/project.pbxproj index dc183c2..f5db7e5 100644 --- a/OCKSample.xcodeproj/project.pbxproj +++ b/OCKSample.xcodeproj/project.pbxproj @@ -1229,7 +1229,7 @@ repositoryURL = "https://github.com/cbaker6/CareKit.git"; requirement = { kind = upToNextMajorVersion; - minimumVersion = "3.0.0-alpha.20"; + minimumVersion = "3.0.0-alpha.22"; }; }; 703616FD29CA194900B50BC5 /* XCRemoteSwiftPackageReference "CareKitUtilities" */ = { @@ -1245,7 +1245,7 @@ repositoryURL = "https://github.com/netreconlab/ParseCareKit.git"; requirement = { kind = upToNextMajorVersion; - minimumVersion = "1.0.0-alpha.41"; + minimumVersion = "1.0.0-alpha.51"; }; }; /* End XCRemoteSwiftPackageReference section */ diff --git a/OCKSample.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/OCKSample.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved index 49ecda7..db0195c 100644 --- a/OCKSample.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/OCKSample.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -5,8 +5,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/cbaker6/CareKit.git", "state" : { - "revision" : "225341b1785513785b7d4a9d4538c2dd48bc0554", - "version" : "3.0.0-alpha.21" + "revision" : "698176a292e3077f7a30496c42f9e8392328930e", + "version" : "3.0.0-alpha.22" } }, { @@ -41,8 +41,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/netreconlab/ParseCareKit.git", "state" : { - "revision" : "d46bf778b9502f408b10e0dae1ccb75d4d153310", - "version" : "1.0.0-alpha.41" + "revision" : "9463925d261b51887080d8729846ca65c8c4cc1d", + "version" : "1.0.0-alpha.51" } }, { diff --git a/OCKSample/Supporting Files/ParseCareKit.plist b/OCKSample/Supporting Files/ParseCareKit.plist index cfd4d06..08979cc 100644 --- a/OCKSample/Supporting Files/ParseCareKit.plist +++ b/OCKSample/Supporting Files/ParseCareKit.plist @@ -9,7 +9,7 @@ LiveQueryServer UseTransactions - + DeleteKeychainIfNeeded From 1efc8b1e0c67c02b5a5a03888e46a2c97c6b6bcd Mon Sep 17 00:00:00 2001 From: Corey Date: Sun, 23 Apr 2023 00:07:16 -0400 Subject: [PATCH 42/52] Working ACL's with roles --- OCKSample.xcodeproj/project.pbxproj | 2 +- .../project.xcworkspace/xcshareddata/swiftpm/Package.resolved | 4 ++-- OCKSample/AppDelegate.swift | 3 +-- OCKWatchSample Extension/AppDelegate.swift | 3 +-- 4 files changed, 5 insertions(+), 7 deletions(-) diff --git a/OCKSample.xcodeproj/project.pbxproj b/OCKSample.xcodeproj/project.pbxproj index f5db7e5..444dbb6 100644 --- a/OCKSample.xcodeproj/project.pbxproj +++ b/OCKSample.xcodeproj/project.pbxproj @@ -1245,7 +1245,7 @@ repositoryURL = "https://github.com/netreconlab/ParseCareKit.git"; requirement = { kind = upToNextMajorVersion; - minimumVersion = "1.0.0-alpha.51"; + minimumVersion = "1.0.0-alpha.55"; }; }; /* End XCRemoteSwiftPackageReference section */ diff --git a/OCKSample.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/OCKSample.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved index db0195c..b6953a7 100644 --- a/OCKSample.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/OCKSample.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -41,8 +41,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/netreconlab/ParseCareKit.git", "state" : { - "revision" : "9463925d261b51887080d8729846ca65c8c4cc1d", - "version" : "1.0.0-alpha.51" + "revision" : "8edcd6a6fe50062566539464d980c26571cdd32b", + "version" : "1.0.0-alpha.55" } }, { diff --git a/OCKSample/AppDelegate.swift b/OCKSample/AppDelegate.swift index 4d5002f..28d2cc7 100644 --- a/OCKSample/AppDelegate.swift +++ b/OCKSample/AppDelegate.swift @@ -94,8 +94,7 @@ class AppDelegate: UIResponder, ObservableObject { } parseRemote = try await ParseRemote(uuid: uuid, auto: false, - subscribeToServerUpdates: true, - defaultACL: try? ParseACL.defaultACL()) + subscribeToServerUpdates: true) let store = OCKStore(name: Constants.iOSParseCareStoreName, type: .onDisk(), remote: parseRemote) diff --git a/OCKWatchSample Extension/AppDelegate.swift b/OCKWatchSample Extension/AppDelegate.swift index b01ecd3..b5aa90c 100644 --- a/OCKWatchSample Extension/AppDelegate.swift +++ b/OCKWatchSample Extension/AppDelegate.swift @@ -97,8 +97,7 @@ class AppDelegate: NSObject, WKApplicationDelegate, ObservableObject { } parseRemote = try await ParseRemote(uuid: uuid, auto: false, - subscribeToServerUpdates: true, - defaultACL: try? ParseACL.defaultACL()) + subscribeToServerUpdates: true) let store = OCKStore(name: Constants.watchOSParseCareStoreName, type: .onDisk(), remote: parseRemote) From cf174de5024c157317fa292e34f21901020e42eb Mon Sep 17 00:00:00 2001 From: Corey Date: Sun, 23 Apr 2023 13:45:11 -0400 Subject: [PATCH 43/52] Working --- OCKSample.xcodeproj/project.pbxproj | 2 +- .../xcshareddata/swiftpm/Package.resolved | 4 ++-- OCKSample/AppDelegate.swift | 5 +++-- OCKSample/Constants.swift | 2 +- OCKSample/Extensions/AppDelegate+ParseRemoteDelegate.swift | 2 +- .../Extensions/AppDelegate+UIApplicationDelegate.swift | 2 +- OCKSample/Main/MainView.swift | 4 ++-- OCKSample/Utility.swift | 4 ++-- OCKWatchSample Extension/AppDelegate.swift | 7 ++++--- .../Extensions/AppDelegate+ParseRemoteDelegate.swift | 2 +- OCKWatchSample Extension/Main/MainView.swift | 6 +++--- README.md | 2 +- 12 files changed, 22 insertions(+), 20 deletions(-) diff --git a/OCKSample.xcodeproj/project.pbxproj b/OCKSample.xcodeproj/project.pbxproj index 444dbb6..5f74224 100644 --- a/OCKSample.xcodeproj/project.pbxproj +++ b/OCKSample.xcodeproj/project.pbxproj @@ -1245,7 +1245,7 @@ repositoryURL = "https://github.com/netreconlab/ParseCareKit.git"; requirement = { kind = upToNextMajorVersion; - minimumVersion = "1.0.0-alpha.55"; + minimumVersion = "1.0.0-alpha.57"; }; }; /* End XCRemoteSwiftPackageReference section */ diff --git a/OCKSample.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/OCKSample.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved index b6953a7..372ad95 100644 --- a/OCKSample.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/OCKSample.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -41,8 +41,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/netreconlab/ParseCareKit.git", "state" : { - "revision" : "8edcd6a6fe50062566539464d980c26571cdd32b", - "version" : "1.0.0-alpha.55" + "revision" : "8586905ba61c8fa6e6df3537b3b7cbf2cecf6651", + "version" : "1.0.0-alpha.57" } }, { diff --git a/OCKSample/AppDelegate.swift b/OCKSample/AppDelegate.swift index 28d2cc7..c12ff52 100644 --- a/OCKSample/AppDelegate.swift +++ b/OCKSample/AppDelegate.swift @@ -87,14 +87,15 @@ class AppDelegate: UIResponder, ObservableObject { @MainActor func setupRemotes(uuid: UUID? = nil) async throws { do { - if isSyncingWithCloud { + if isSyncingWithRemote { guard let uuid = uuid else { Logger.appDelegate.error("Could not setupRemotes, uuid is nil") return } parseRemote = try await ParseRemote(uuid: uuid, auto: false, - subscribeToServerUpdates: true) + subscribeToServerUpdates: true, + defaultACL: PCKUtility.getDefaultACL()) let store = OCKStore(name: Constants.iOSParseCareStoreName, type: .onDisk(), remote: parseRemote) diff --git a/OCKSample/Constants.swift b/OCKSample/Constants.swift index 8b53aa1..9318c57 100644 --- a/OCKSample/Constants.swift +++ b/OCKSample/Constants.swift @@ -14,7 +14,7 @@ import ParseSwift /** Set to **true** to sync with ParseServer, *false** to sync with iOS/watchOS. */ -let isSyncingWithCloud = true +let isSyncingWithRemote = true /** Set to **true** to use WCSession to notify watchOS about updates, **false** to not notify. diff --git a/OCKSample/Extensions/AppDelegate+ParseRemoteDelegate.swift b/OCKSample/Extensions/AppDelegate+ParseRemoteDelegate.swift index 50b0295..156de83 100644 --- a/OCKSample/Extensions/AppDelegate+ParseRemoteDelegate.swift +++ b/OCKSample/Extensions/AppDelegate+ParseRemoteDelegate.swift @@ -18,7 +18,7 @@ extension AppDelegate: ParseRemoteDelegate { } @MainActor - func successfullyPushedDataToCloud() { + func successfullyPushedToRemote() { if isFirstTimeLogin { // BAKER: @MainActor not working (shows purple warning), leave async. DispatchQueue.main.async { diff --git a/OCKSample/Extensions/AppDelegate+UIApplicationDelegate.swift b/OCKSample/Extensions/AppDelegate+UIApplicationDelegate.swift index e41f37f..ca08a16 100644 --- a/OCKSample/Extensions/AppDelegate+UIApplicationDelegate.swift +++ b/OCKSample/Extensions/AppDelegate+UIApplicationDelegate.swift @@ -14,7 +14,7 @@ extension AppDelegate: UIApplicationDelegate { func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { Task { - if isSyncingWithCloud { + if isSyncingWithRemote { do { // Parse-Server setup try await PCKUtility.setupServer(fileName: Constants.parseConfigFileName) { _, completionHandler in diff --git a/OCKSample/Main/MainView.swift b/OCKSample/Main/MainView.swift index 9841c12..9cde6e6 100644 --- a/OCKSample/Main/MainView.swift +++ b/OCKSample/Main/MainView.swift @@ -22,7 +22,7 @@ struct MainView: View { .navigationDestination(for: MainViewPath.self) { destination in switch destination { case .tabs: - if isSyncingWithCloud { + if isSyncingWithRemote { MainTabView(loginViewModel: loginViewModel) .navigationBarHidden(true) } else { @@ -32,7 +32,7 @@ struct MainView: View { } } .onAppear { - guard isSyncingWithCloud else { + guard isSyncingWithRemote else { path = [.tabs] return } diff --git a/OCKSample/Utility.swift b/OCKSample/Utility.swift index 13fc639..01faeae 100644 --- a/OCKSample/Utility.swift +++ b/OCKSample/Utility.swift @@ -16,7 +16,7 @@ class Utility { // For classes, we can use "class" or "static" to declare type methods/properties. class func prepareSyncMessageForWatch() -> [String: Any] { var returnMessage = [String: Any]() - returnMessage[Constants.requestSync] = "new messages in Cloud" + returnMessage[Constants.requestSync] = "new messages on Remote" return returnMessage } @@ -170,7 +170,7 @@ class Utility { UserDefaults.standard.setValue(String(Constants.appName), forKey: Constants.appName) UserDefaults.standard.synchronize() - if isSyncingWithCloud { + if isSyncingWithRemote { try? await User.logout() } } diff --git a/OCKWatchSample Extension/AppDelegate.swift b/OCKWatchSample Extension/AppDelegate.swift index b5aa90c..b995cfa 100644 --- a/OCKWatchSample Extension/AppDelegate.swift +++ b/OCKWatchSample Extension/AppDelegate.swift @@ -36,7 +36,7 @@ class AppDelegate: NSObject, WKApplicationDelegate, ObservableObject { func applicationDidFinishLaunching() { Task { - if isSyncingWithCloud { + if isSyncingWithRemote { do { // Parse-server setup try await PCKUtility.setupServer(fileName: Constants.parseConfigFileName) { _, completionHandler in @@ -85,7 +85,7 @@ class AppDelegate: NSObject, WKApplicationDelegate, ObservableObject { @MainActor func setupRemotes(uuid: UUID? = nil) async throws { do { - if isSyncingWithCloud { + if isSyncingWithRemote { if sessionDelegate == nil { sessionDelegate = RemoteSessionDelegate(store: store) WCSession.default.delegate = sessionDelegate @@ -97,7 +97,8 @@ class AppDelegate: NSObject, WKApplicationDelegate, ObservableObject { } parseRemote = try await ParseRemote(uuid: uuid, auto: false, - subscribeToServerUpdates: true) + subscribeToServerUpdates: true, + defaultACL: PCKUtility.getDefaultACL()) let store = OCKStore(name: Constants.watchOSParseCareStoreName, type: .onDisk(), remote: parseRemote) diff --git a/OCKWatchSample Extension/Extensions/AppDelegate+ParseRemoteDelegate.swift b/OCKWatchSample Extension/Extensions/AppDelegate+ParseRemoteDelegate.swift index 910ee29..cdc58e6 100644 --- a/OCKWatchSample Extension/Extensions/AppDelegate+ParseRemoteDelegate.swift +++ b/OCKWatchSample Extension/Extensions/AppDelegate+ParseRemoteDelegate.swift @@ -20,7 +20,7 @@ extension AppDelegate: ParseRemoteDelegate { } } - func successfullyPushedDataToCloud() { + func successfullyPushedToRemote() { Logger.appDelegate.info("Finished pushing data") } diff --git a/OCKWatchSample Extension/Main/MainView.swift b/OCKWatchSample Extension/Main/MainView.swift index 34d2fed..740930f 100644 --- a/OCKWatchSample Extension/Main/MainView.swift +++ b/OCKWatchSample Extension/Main/MainView.swift @@ -29,7 +29,7 @@ struct MainView: View { } .navigationBarHidden(true) .onAppear { - guard isSyncingWithCloud else { + guard isSyncingWithRemote else { path = [.tabs] return } @@ -42,7 +42,7 @@ struct MainView: View { } .environment(\.careStore, store) .onReceive(loginViewModel.$isLoggedOut, perform: { isLoggedOut in - guard isSyncingWithCloud else { + guard isSyncingWithRemote else { path = [.tabs] return } @@ -61,7 +61,7 @@ struct MainView: View { return } store = newStore - guard isSyncingWithCloud else { + guard isSyncingWithRemote else { path = [.tabs] return } diff --git a/README.md b/README.md index dd09cde..53d09ee 100644 --- a/README.md +++ b/README.md @@ -5,7 +5,7 @@ An example application of [CareKit](https://github.com/carekit-apple/CareKit)'s -**Similar to the [What's New in CareKit](https://developer.apple.com/videos/play/wwdc2020/10151/) WWDC20 video, this app syncs between data between iOS and an Apple Watch (setting the flag `isSyncingWithCloud` in `Constants.swift` to `isSyncingWithCloud = false`. Different from the video, setting `isSyncingWithCloud = true` (default behavior) in the aforementioned file syncs iOS and watchOS to a Parse Server.** +**Similar to the [What's New in CareKit](https://developer.apple.com/videos/play/wwdc2020/10151/) WWDC20 video, this app syncs between data between iOS and an Apple Watch (setting the flag `isSyncingWithRemote` in `Constants.swift` to `isSyncingWithRemote = false`. Different from the video, setting `isSyncingWithRemote = true` (default behavior) in the aforementioned file syncs iOS and watchOS to a Parse Server.** ParseCareKit synchronizes the following entities to Parse tables/classes using [Parse-Swift](https://github.com/netreconlab/Parse-Swift): From 1b1840bc362dc9ed2275674f9f594a7c57ea986e Mon Sep 17 00:00:00 2001 From: Corey Date: Sun, 23 Apr 2023 14:21:38 -0400 Subject: [PATCH 44/52] latest --- OCKSample.xcodeproj/project.pbxproj | 2 +- .../project.xcworkspace/xcshareddata/swiftpm/Package.resolved | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/OCKSample.xcodeproj/project.pbxproj b/OCKSample.xcodeproj/project.pbxproj index 5f74224..92c8189 100644 --- a/OCKSample.xcodeproj/project.pbxproj +++ b/OCKSample.xcodeproj/project.pbxproj @@ -1245,7 +1245,7 @@ repositoryURL = "https://github.com/netreconlab/ParseCareKit.git"; requirement = { kind = upToNextMajorVersion; - minimumVersion = "1.0.0-alpha.57"; + minimumVersion = "1.0.0-alpha.59"; }; }; /* End XCRemoteSwiftPackageReference section */ diff --git a/OCKSample.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/OCKSample.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved index 372ad95..968ada2 100644 --- a/OCKSample.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/OCKSample.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -41,8 +41,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/netreconlab/ParseCareKit.git", "state" : { - "revision" : "8586905ba61c8fa6e6df3537b3b7cbf2cecf6651", - "version" : "1.0.0-alpha.57" + "revision" : "2f4ee3654f045249b6eb6c546f492d60f7a1fc0d", + "version" : "1.0.0-alpha.59" } }, { From 57858887f7862d4570f09171f5d7c948e0fffd85 Mon Sep 17 00:00:00 2001 From: Corey Baker Date: Mon, 24 Apr 2023 10:27:06 -0400 Subject: [PATCH 45/52] Use latest ParseCareKit --- OCKSample.xcodeproj/project.pbxproj | 2 +- .../project.xcworkspace/xcshareddata/swiftpm/Package.resolved | 4 ++-- OCKSample/Extensions/AppDelegate+ParseRemoteDelegate.swift | 2 +- .../Extensions/AppDelegate+ParseRemoteDelegate.swift | 2 +- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/OCKSample.xcodeproj/project.pbxproj b/OCKSample.xcodeproj/project.pbxproj index 92c8189..f797ed3 100644 --- a/OCKSample.xcodeproj/project.pbxproj +++ b/OCKSample.xcodeproj/project.pbxproj @@ -1245,7 +1245,7 @@ repositoryURL = "https://github.com/netreconlab/ParseCareKit.git"; requirement = { kind = upToNextMajorVersion; - minimumVersion = "1.0.0-alpha.59"; + minimumVersion = "1.0.0-alpha.60"; }; }; /* End XCRemoteSwiftPackageReference section */ diff --git a/OCKSample.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/OCKSample.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved index 968ada2..d4a18fd 100644 --- a/OCKSample.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/OCKSample.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -41,8 +41,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/netreconlab/ParseCareKit.git", "state" : { - "revision" : "2f4ee3654f045249b6eb6c546f492d60f7a1fc0d", - "version" : "1.0.0-alpha.59" + "revision" : "fc34d952aab7fbd2933d1612e6baf8c2cc59f225", + "version" : "1.0.0-alpha.60" } }, { diff --git a/OCKSample/Extensions/AppDelegate+ParseRemoteDelegate.swift b/OCKSample/Extensions/AppDelegate+ParseRemoteDelegate.swift index 156de83..5f8c72e 100644 --- a/OCKSample/Extensions/AppDelegate+ParseRemoteDelegate.swift +++ b/OCKSample/Extensions/AppDelegate+ParseRemoteDelegate.swift @@ -36,7 +36,7 @@ extension AppDelegate: ParseRemoteDelegate { #endif } - func needStore() -> OCKAnyStoreProtocol { + func provideStore() -> OCKAnyStoreProtocol { return storeCoordinator } diff --git a/OCKWatchSample Extension/Extensions/AppDelegate+ParseRemoteDelegate.swift b/OCKWatchSample Extension/Extensions/AppDelegate+ParseRemoteDelegate.swift index cdc58e6..1c2a379 100644 --- a/OCKWatchSample Extension/Extensions/AppDelegate+ParseRemoteDelegate.swift +++ b/OCKWatchSample Extension/Extensions/AppDelegate+ParseRemoteDelegate.swift @@ -24,7 +24,7 @@ extension AppDelegate: ParseRemoteDelegate { Logger.appDelegate.info("Finished pushing data") } - func needStore() -> OCKAnyStoreProtocol { + func provideStore() -> OCKAnyStoreProtocol { guard let store = store else { return OCKStore(name: Constants.noCareStoreName, type: .inMemory) } From 1452d7154f7c07c95e12459916d61b0669e25424 Mon Sep 17 00:00:00 2001 From: Corey Baker Date: Mon, 24 Apr 2023 10:52:20 -0400 Subject: [PATCH 46/52] delete ParseCareKit cache on logout --- OCKSample.xcodeproj/project.pbxproj | 2 +- .../project.xcworkspace/xcshareddata/swiftpm/Package.resolved | 4 ++-- OCKSample/AppDelegate.swift | 1 + 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/OCKSample.xcodeproj/project.pbxproj b/OCKSample.xcodeproj/project.pbxproj index f797ed3..357617c 100644 --- a/OCKSample.xcodeproj/project.pbxproj +++ b/OCKSample.xcodeproj/project.pbxproj @@ -1245,7 +1245,7 @@ repositoryURL = "https://github.com/netreconlab/ParseCareKit.git"; requirement = { kind = upToNextMajorVersion; - minimumVersion = "1.0.0-alpha.60"; + minimumVersion = "1.0.0-alpha.61"; }; }; /* End XCRemoteSwiftPackageReference section */ diff --git a/OCKSample.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/OCKSample.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved index d4a18fd..a28119a 100644 --- a/OCKSample.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/OCKSample.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -41,8 +41,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/netreconlab/ParseCareKit.git", "state" : { - "revision" : "fc34d952aab7fbd2933d1612e6baf8c2cc59f225", - "version" : "1.0.0-alpha.60" + "revision" : "fe4a72f6e6402134609a02270a8c66884b26b63e", + "version" : "1.0.0-alpha.61" } }, { diff --git a/OCKSample/AppDelegate.swift b/OCKSample/AppDelegate.swift index c12ff52..cd9c465 100644 --- a/OCKSample/AppDelegate.swift +++ b/OCKSample/AppDelegate.swift @@ -82,6 +82,7 @@ class AppDelegate: UIResponder, ObservableObject { type: .inMemory) sessionDelegate.store = store self.store = store + PCKUtility.removeCache() } @MainActor From 7ae5254a016aec2b516f414ff5a45de306cb36d7 Mon Sep 17 00:00:00 2001 From: Corey Date: Mon, 24 Apr 2023 21:47:54 -0400 Subject: [PATCH 47/52] latest --- OCKSample.xcodeproj/project.pbxproj | 2 +- .../project.xcworkspace/xcshareddata/swiftpm/Package.resolved | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/OCKSample.xcodeproj/project.pbxproj b/OCKSample.xcodeproj/project.pbxproj index 357617c..2ceeb4c 100644 --- a/OCKSample.xcodeproj/project.pbxproj +++ b/OCKSample.xcodeproj/project.pbxproj @@ -1245,7 +1245,7 @@ repositoryURL = "https://github.com/netreconlab/ParseCareKit.git"; requirement = { kind = upToNextMajorVersion; - minimumVersion = "1.0.0-alpha.61"; + minimumVersion = "1.0.0-alpha.69"; }; }; /* End XCRemoteSwiftPackageReference section */ diff --git a/OCKSample.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/OCKSample.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved index a28119a..a57c21c 100644 --- a/OCKSample.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/OCKSample.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -41,8 +41,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/netreconlab/ParseCareKit.git", "state" : { - "revision" : "fe4a72f6e6402134609a02270a8c66884b26b63e", - "version" : "1.0.0-alpha.61" + "revision" : "b35a7c165afaeb559852ce8e24265a4da88725ea", + "version" : "1.0.0-alpha.69" } }, { From 953b0831f0fdd1a70361c292e57edc8c13401217 Mon Sep 17 00:00:00 2001 From: Corey Date: Mon, 24 Apr 2023 22:13:09 -0400 Subject: [PATCH 48/52] use latest ParseCareKit --- OCKSample.xcodeproj/project.pbxproj | 2 +- .../project.xcworkspace/xcshareddata/swiftpm/Package.resolved | 4 ++-- OCKSample/AppDelegate.swift | 2 +- OCKWatchSample Extension/AppDelegate.swift | 2 +- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/OCKSample.xcodeproj/project.pbxproj b/OCKSample.xcodeproj/project.pbxproj index 2ceeb4c..fc8d82d 100644 --- a/OCKSample.xcodeproj/project.pbxproj +++ b/OCKSample.xcodeproj/project.pbxproj @@ -1245,7 +1245,7 @@ repositoryURL = "https://github.com/netreconlab/ParseCareKit.git"; requirement = { kind = upToNextMajorVersion; - minimumVersion = "1.0.0-alpha.69"; + minimumVersion = "1.0.0-alpha.70"; }; }; /* End XCRemoteSwiftPackageReference section */ diff --git a/OCKSample.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/OCKSample.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved index a57c21c..295953d 100644 --- a/OCKSample.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/OCKSample.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -41,8 +41,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/netreconlab/ParseCareKit.git", "state" : { - "revision" : "b35a7c165afaeb559852ce8e24265a4da88725ea", - "version" : "1.0.0-alpha.69" + "revision" : "5a7ed1a6dc6eaa676d844ed2e5961b5224d0abf5", + "version" : "1.0.0-alpha.70" } }, { diff --git a/OCKSample/AppDelegate.swift b/OCKSample/AppDelegate.swift index cd9c465..3553d75 100644 --- a/OCKSample/AppDelegate.swift +++ b/OCKSample/AppDelegate.swift @@ -95,7 +95,7 @@ class AppDelegate: UIResponder, ObservableObject { } parseRemote = try await ParseRemote(uuid: uuid, auto: false, - subscribeToServerUpdates: true, + subscribeToRemoteUpdates: true, defaultACL: PCKUtility.getDefaultACL()) let store = OCKStore(name: Constants.iOSParseCareStoreName, type: .onDisk(), diff --git a/OCKWatchSample Extension/AppDelegate.swift b/OCKWatchSample Extension/AppDelegate.swift index b995cfa..fed1238 100644 --- a/OCKWatchSample Extension/AppDelegate.swift +++ b/OCKWatchSample Extension/AppDelegate.swift @@ -97,7 +97,7 @@ class AppDelegate: NSObject, WKApplicationDelegate, ObservableObject { } parseRemote = try await ParseRemote(uuid: uuid, auto: false, - subscribeToServerUpdates: true, + subscribeToRemoteUpdates: true, defaultACL: PCKUtility.getDefaultACL()) let store = OCKStore(name: Constants.watchOSParseCareStoreName, type: .onDisk(), From a883f4b62b8e694dd8d86678fb4427f94e761952 Mon Sep 17 00:00:00 2001 From: Corey Baker Date: Tue, 25 Apr 2023 13:33:53 -0400 Subject: [PATCH 49/52] working --- OCKSample.xcodeproj/project.pbxproj | 2 +- .../project.xcworkspace/xcshareddata/swiftpm/Package.resolved | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/OCKSample.xcodeproj/project.pbxproj b/OCKSample.xcodeproj/project.pbxproj index fc8d82d..4967b26 100644 --- a/OCKSample.xcodeproj/project.pbxproj +++ b/OCKSample.xcodeproj/project.pbxproj @@ -1245,7 +1245,7 @@ repositoryURL = "https://github.com/netreconlab/ParseCareKit.git"; requirement = { kind = upToNextMajorVersion; - minimumVersion = "1.0.0-alpha.70"; + minimumVersion = "1.0.0-alpha.76"; }; }; /* End XCRemoteSwiftPackageReference section */ diff --git a/OCKSample.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/OCKSample.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved index 295953d..092bab1 100644 --- a/OCKSample.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/OCKSample.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -41,8 +41,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/netreconlab/ParseCareKit.git", "state" : { - "revision" : "5a7ed1a6dc6eaa676d844ed2e5961b5224d0abf5", - "version" : "1.0.0-alpha.70" + "revision" : "0b06329e2cf28cf034a5230d729f83679cf7bf04", + "version" : "1.0.0-alpha.76" } }, { From 7f8a64762708e32092a780e129e266377ffcd1f8 Mon Sep 17 00:00:00 2001 From: Corey Date: Sun, 30 Apr 2023 15:20:48 -0400 Subject: [PATCH 50/52] Use latest ParseCareKit --- OCKSample.xcodeproj/project.pbxproj | 2 +- .../project.xcworkspace/xcshareddata/swiftpm/Package.resolved | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/OCKSample.xcodeproj/project.pbxproj b/OCKSample.xcodeproj/project.pbxproj index 4967b26..f2b6447 100644 --- a/OCKSample.xcodeproj/project.pbxproj +++ b/OCKSample.xcodeproj/project.pbxproj @@ -1245,7 +1245,7 @@ repositoryURL = "https://github.com/netreconlab/ParseCareKit.git"; requirement = { kind = upToNextMajorVersion; - minimumVersion = "1.0.0-alpha.76"; + minimumVersion = "1.0.0-alpha.78"; }; }; /* End XCRemoteSwiftPackageReference section */ diff --git a/OCKSample.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/OCKSample.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved index 092bab1..579e4c0 100644 --- a/OCKSample.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/OCKSample.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -41,8 +41,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/netreconlab/ParseCareKit.git", "state" : { - "revision" : "0b06329e2cf28cf034a5230d729f83679cf7bf04", - "version" : "1.0.0-alpha.76" + "revision" : "2d851205d48b158761c6fac9c0537052bb53e0f9", + "version" : "1.0.0-alpha.78" } }, { From f5965788f28eee84a4eab382ab785e3badd6a1c6 Mon Sep 17 00:00:00 2001 From: Corey Baker Date: Mon, 1 May 2023 12:38:56 -0400 Subject: [PATCH 51/52] update --- OCKSample.xcodeproj/project.pbxproj | 2 +- .../xcshareddata/swiftpm/Package.resolved | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/OCKSample.xcodeproj/project.pbxproj b/OCKSample.xcodeproj/project.pbxproj index f2b6447..c60f9c5 100644 --- a/OCKSample.xcodeproj/project.pbxproj +++ b/OCKSample.xcodeproj/project.pbxproj @@ -1245,7 +1245,7 @@ repositoryURL = "https://github.com/netreconlab/ParseCareKit.git"; requirement = { kind = upToNextMajorVersion; - minimumVersion = "1.0.0-alpha.78"; + minimumVersion = "1.0.0-alpha.79"; }; }; /* End XCRemoteSwiftPackageReference section */ diff --git a/OCKSample.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/OCKSample.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved index 579e4c0..bcfc0bc 100644 --- a/OCKSample.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/OCKSample.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -32,8 +32,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/netreconlab/Parse-Swift.git", "state" : { - "revision" : "088e42071dcebae970e5d94e3d7d0f34cf300f02", - "version" : "5.4.1" + "revision" : "a746db0bf4e9a2b3b33bc0a7f9dbcaf79feb0c8b", + "version" : "5.4.2" } }, { @@ -41,8 +41,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/netreconlab/ParseCareKit.git", "state" : { - "revision" : "2d851205d48b158761c6fac9c0537052bb53e0f9", - "version" : "1.0.0-alpha.78" + "revision" : "7d163287a9b203212096389e0974812ef328df4a", + "version" : "1.0.0-alpha.79" } }, { From b362e420952d2c50038dd7871711b532b18e83ac Mon Sep 17 00:00:00 2001 From: Corey Baker Date: Mon, 1 May 2023 14:56:50 -0400 Subject: [PATCH 52/52] Update ParseCareKit --- OCKSample.xcodeproj/project.pbxproj | 2 +- .../project.xcworkspace/xcshareddata/swiftpm/Package.resolved | 4 ++-- OCKSample/Extensions/AppDelegate+UIApplicationDelegate.swift | 3 ++- OCKWatchSample Extension/AppDelegate.swift | 3 ++- 4 files changed, 7 insertions(+), 5 deletions(-) diff --git a/OCKSample.xcodeproj/project.pbxproj b/OCKSample.xcodeproj/project.pbxproj index c60f9c5..ed951de 100644 --- a/OCKSample.xcodeproj/project.pbxproj +++ b/OCKSample.xcodeproj/project.pbxproj @@ -1245,7 +1245,7 @@ repositoryURL = "https://github.com/netreconlab/ParseCareKit.git"; requirement = { kind = upToNextMajorVersion; - minimumVersion = "1.0.0-alpha.79"; + minimumVersion = "1.0.0-alpha.80"; }; }; /* End XCRemoteSwiftPackageReference section */ diff --git a/OCKSample.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/OCKSample.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved index bcfc0bc..8b7ca72 100644 --- a/OCKSample.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/OCKSample.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -41,8 +41,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/netreconlab/ParseCareKit.git", "state" : { - "revision" : "7d163287a9b203212096389e0974812ef328df4a", - "version" : "1.0.0-alpha.79" + "revision" : "566f68ef230e4285eb9e19a3d1045947d5a74eef", + "version" : "1.0.0-alpha.80" } }, { diff --git a/OCKSample/Extensions/AppDelegate+UIApplicationDelegate.swift b/OCKSample/Extensions/AppDelegate+UIApplicationDelegate.swift index ca08a16..52e8aeb 100644 --- a/OCKSample/Extensions/AppDelegate+UIApplicationDelegate.swift +++ b/OCKSample/Extensions/AppDelegate+UIApplicationDelegate.swift @@ -17,7 +17,8 @@ extension AppDelegate: UIApplicationDelegate { if isSyncingWithRemote { do { // Parse-Server setup - try await PCKUtility.setupServer(fileName: Constants.parseConfigFileName) { _, completionHandler in + // swiftlint:disable:next line_length + try await PCKUtility.configureParse(fileName: Constants.parseConfigFileName) { _, completionHandler in completionHandler(.performDefaultHandling, nil) } } catch { diff --git a/OCKWatchSample Extension/AppDelegate.swift b/OCKWatchSample Extension/AppDelegate.swift index fed1238..066bb3b 100644 --- a/OCKWatchSample Extension/AppDelegate.swift +++ b/OCKWatchSample Extension/AppDelegate.swift @@ -39,7 +39,8 @@ class AppDelegate: NSObject, WKApplicationDelegate, ObservableObject { if isSyncingWithRemote { do { // Parse-server setup - try await PCKUtility.setupServer(fileName: Constants.parseConfigFileName) { _, completionHandler in + // swiftlint:disable:next line_length + try await PCKUtility.configureParse(fileName: Constants.parseConfigFileName) { _, completionHandler in completionHandler(.performDefaultHandling, nil) } await Utility.clearDeviceOnFirstRun()