Update dependency

This commit is contained in:
Christoph Hagen 2024-03-15 14:02:03 +01:00
parent 08825f84a1
commit 5dcaf0b3d7
16 changed files with 75 additions and 110 deletions

View File

@ -47,8 +47,8 @@
E2E5528E2BA21C5900BF5E9B /* FileManager+Directory.swift in Sources */ = {isa = PBXBuildFile; fileRef = E2E5528D2BA21C5900BF5E9B /* FileManager+Directory.swift */; }; E2E5528E2BA21C5900BF5E9B /* FileManager+Directory.swift in Sources */ = {isa = PBXBuildFile; fileRef = E2E5528D2BA21C5900BF5E9B /* FileManager+Directory.swift */; };
E2E552902BA236A000BF5E9B /* DatabaseList.swift in Sources */ = {isa = PBXBuildFile; fileRef = E2E5528F2BA236A000BF5E9B /* DatabaseList.swift */; }; E2E552902BA236A000BF5E9B /* DatabaseList.swift in Sources */ = {isa = PBXBuildFile; fileRef = E2E5528F2BA236A000BF5E9B /* DatabaseList.swift */; };
E2E552922BA236D000BF5E9B /* DatabaseFile.swift in Sources */ = {isa = PBXBuildFile; fileRef = E2E552912BA236D000BF5E9B /* DatabaseFile.swift */; }; E2E552922BA236D000BF5E9B /* DatabaseFile.swift in Sources */ = {isa = PBXBuildFile; fileRef = E2E552912BA236D000BF5E9B /* DatabaseFile.swift */; };
E2E552992BA3748500BF5E9B /* HKDatabase in Frameworks */ = {isa = PBXBuildFile; productRef = E2E552982BA3748500BF5E9B /* HKDatabase */; };
E2E5529B2BA3935600BF5E9B /* HKWorkout+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = E2E5529A2BA3935600BF5E9B /* HKWorkout+Extensions.swift */; }; E2E5529B2BA3935600BF5E9B /* HKWorkout+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = E2E5529A2BA3935600BF5E9B /* HKWorkout+Extensions.swift */; };
E2E5529E2BA47BA600BF5E9B /* HealthDB in Frameworks */ = {isa = PBXBuildFile; productRef = E2E5529D2BA47BA600BF5E9B /* HealthDB */; };
E2FDFF202B6BE34C0080A7B3 /* SwiftProtobuf in Frameworks */ = {isa = PBXBuildFile; productRef = E2FDFF1F2B6BE34C0080A7B3 /* SwiftProtobuf */; }; E2FDFF202B6BE34C0080A7B3 /* SwiftProtobuf in Frameworks */ = {isa = PBXBuildFile; productRef = E2FDFF1F2B6BE34C0080A7B3 /* SwiftProtobuf */; };
E2FDFF292B6D10D60080A7B3 /* String+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = E2FDFF282B6D10D60080A7B3 /* String+Extensions.swift */; }; E2FDFF292B6D10D60080A7B3 /* String+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = E2FDFF282B6D10D60080A7B3 /* String+Extensions.swift */; };
/* End PBXBuildFile section */ /* End PBXBuildFile section */
@ -102,9 +102,9 @@
files = ( files = (
885002A62B5D296700E7D4DB /* Collections in Frameworks */, 885002A62B5D296700E7D4DB /* Collections in Frameworks */,
E20881D32B76912000D41D95 /* HealthKitExtensions in Frameworks */, E20881D32B76912000D41D95 /* HealthKitExtensions in Frameworks */,
E2E552992BA3748500BF5E9B /* HKDatabase in Frameworks */,
885002772B5C2FC400E7D4DB /* SQLite in Frameworks */, 885002772B5C2FC400E7D4DB /* SQLite in Frameworks */,
885002AA2B5D296700E7D4DB /* OrderedCollections in Frameworks */, 885002AA2B5D296700E7D4DB /* OrderedCollections in Frameworks */,
E2E5529E2BA47BA600BF5E9B /* HealthDB in Frameworks */,
885002A82B5D296700E7D4DB /* DequeModule in Frameworks */, 885002A82B5D296700E7D4DB /* DequeModule in Frameworks */,
E2A38EA82B9C6EE800BAD02E /* SFSafeSymbols in Frameworks */, E2A38EA82B9C6EE800BAD02E /* SFSafeSymbols in Frameworks */,
E2FDFF202B6BE34C0080A7B3 /* SwiftProtobuf in Frameworks */, E2FDFF202B6BE34C0080A7B3 /* SwiftProtobuf in Frameworks */,
@ -119,6 +119,7 @@
children = ( children = (
885002592B5C273C00E7D4DB /* HealthImport */, 885002592B5C273C00E7D4DB /* HealthImport */,
885002582B5C273C00E7D4DB /* Products */, 885002582B5C273C00E7D4DB /* Products */,
E2E5529C2BA47BA600BF5E9B /* Frameworks */,
); );
sourceTree = "<group>"; sourceTree = "<group>";
}; };
@ -215,6 +216,13 @@
path = Model; path = Model;
sourceTree = "<group>"; sourceTree = "<group>";
}; };
E2E5529C2BA47BA600BF5E9B /* Frameworks */ = {
isa = PBXGroup;
children = (
);
name = Frameworks;
sourceTree = "<group>";
};
/* End PBXGroup section */ /* End PBXGroup section */
/* Begin PBXNativeTarget section */ /* Begin PBXNativeTarget section */
@ -239,7 +247,7 @@
E2FDFF1F2B6BE34C0080A7B3 /* SwiftProtobuf */, E2FDFF1F2B6BE34C0080A7B3 /* SwiftProtobuf */,
E20881D22B76912000D41D95 /* HealthKitExtensions */, E20881D22B76912000D41D95 /* HealthKitExtensions */,
E2A38EA72B9C6EE800BAD02E /* SFSafeSymbols */, E2A38EA72B9C6EE800BAD02E /* SFSafeSymbols */,
E2E552982BA3748500BF5E9B /* HKDatabase */, E2E5529D2BA47BA600BF5E9B /* HealthDB */,
); );
productName = HealthImport; productName = HealthImport;
productReference = 885002572B5C273C00E7D4DB /* HealthImport.app */; productReference = 885002572B5C273C00E7D4DB /* HealthImport.app */;
@ -576,8 +584,8 @@
isa = XCRemoteSwiftPackageReference; isa = XCRemoteSwiftPackageReference;
repositoryURL = "https://github.com/christophhagen/HealthKitExtensions"; repositoryURL = "https://github.com/christophhagen/HealthKitExtensions";
requirement = { requirement = {
kind = upToNextMajorVersion; branch = main;
minimumVersion = 0.3.3; kind = branch;
}; };
}; };
E2A38EA62B9C6EE800BAD02E /* XCRemoteSwiftPackageReference "SFSafeSymbols" */ = { E2A38EA62B9C6EE800BAD02E /* XCRemoteSwiftPackageReference "SFSafeSymbols" */ = {
@ -592,8 +600,8 @@
isa = XCRemoteSwiftPackageReference; isa = XCRemoteSwiftPackageReference;
repositoryURL = "https://github.com/christophhagen/HealthDB"; repositoryURL = "https://github.com/christophhagen/HealthDB";
requirement = { requirement = {
kind = upToNextMajorVersion; branch = main;
minimumVersion = 0.2.0; kind = branch;
}; };
}; };
E2FDFF1E2B6BE34C0080A7B3 /* XCRemoteSwiftPackageReference "swift-protobuf" */ = { E2FDFF1E2B6BE34C0080A7B3 /* XCRemoteSwiftPackageReference "swift-protobuf" */ = {
@ -637,10 +645,10 @@
package = E2A38EA62B9C6EE800BAD02E /* XCRemoteSwiftPackageReference "SFSafeSymbols" */; package = E2A38EA62B9C6EE800BAD02E /* XCRemoteSwiftPackageReference "SFSafeSymbols" */;
productName = SFSafeSymbols; productName = SFSafeSymbols;
}; };
E2E552982BA3748500BF5E9B /* HKDatabase */ = { E2E5529D2BA47BA600BF5E9B /* HealthDB */ = {
isa = XCSwiftPackageProductDependency; isa = XCSwiftPackageProductDependency;
package = E2E552972BA3748500BF5E9B /* XCRemoteSwiftPackageReference "HealthDB" */; package = E2E552972BA3748500BF5E9B /* XCRemoteSwiftPackageReference "HealthDB" */;
productName = HKDatabase; productName = HealthDB;
}; };
E2FDFF1F2B6BE34C0080A7B3 /* SwiftProtobuf */ = { E2FDFF1F2B6BE34C0080A7B3 /* SwiftProtobuf */ = {
isa = XCSwiftPackageProductDependency; isa = XCSwiftPackageProductDependency;

View File

@ -6,8 +6,8 @@
"kind" : "remoteSourceControl", "kind" : "remoteSourceControl",
"location" : "https://github.com/christophhagen/HealthDB", "location" : "https://github.com/christophhagen/HealthDB",
"state" : { "state" : {
"revision" : "b1f45d1abf47a13696fba9670db24fe6ca7fab53", "branch" : "main",
"version" : "0.2.1" "revision" : "90b616517861733c1f52ef6f0aaf42849b44e09f"
} }
}, },
{ {
@ -15,8 +15,8 @@
"kind" : "remoteSourceControl", "kind" : "remoteSourceControl",
"location" : "https://github.com/christophhagen/HealthKitExtensions", "location" : "https://github.com/christophhagen/HealthKitExtensions",
"state" : { "state" : {
"revision" : "18ee575892e6cc429c74c7bc3f156cc6791b220f", "branch" : "main",
"version" : "0.3.3" "revision" : "a7f6612e959a76f211d8526adfd9b5bf88442bb8"
} }
}, },
{ {

View File

@ -1,12 +1,12 @@
import SwiftUI import SwiftUI
import HealthKit import HealthKit
import HKDatabase import HealthDB
import CoreLocation import CoreLocation
struct ActivityDetailView: View { struct ActivityDetailView: View {
@EnvironmentObject @EnvironmentObject
var database: HealthDatabase var database: Database
let workout: Workout let workout: Workout
@ -91,6 +91,6 @@ struct ActivityDetailView: View {
#Preview { #Preview {
NavigationStack { NavigationStack {
ActivityDetailView(workout: .mock1, activity: .mock1) ActivityDetailView(workout: .mock1, activity: .mock1)
.environmentObject(HealthDatabase.mock) .environmentObject(Database.mock)
} }
} }

View File

@ -1,7 +1,7 @@
import SwiftUI import SwiftUI
import OrderedCollections import OrderedCollections
import HealthKit import HealthKit
import HKDatabase import HealthDB
struct ActivitySamplesView: View { struct ActivitySamplesView: View {

View File

@ -1,5 +1,5 @@
import SwiftUI import SwiftUI
import HKDatabase import HealthDB
import SFSafeSymbols import SFSafeSymbols
private enum TabSelection: Int { private enum TabSelection: Int {
@ -12,7 +12,7 @@ private enum TabSelection: Int {
struct HealthImportApp: App { struct HealthImportApp: App {
@State @State
var database = HealthDatabase() var database = Database()
@State @State
private var selection: TabSelection = .databases private var selection: TabSelection = .databases

View File

@ -1,15 +1,15 @@
import Foundation import Foundation
import HKDatabase import HealthDB
final class HealthDatabase: ObservableObject { final class Database: ObservableObject {
@Published @Published
var store: HKDatabaseStoreWrapper? = nil var store: HealthDatabase? = nil
@Published @Published
var file: DatabaseFile? = nil var file: DatabaseFile? = nil
init(store: HKDatabaseStoreWrapper? = nil) { init(store: HealthDatabase? = nil) {
self.store = store self.store = store
} }
@ -21,7 +21,7 @@ final class HealthDatabase: ObservableObject {
} }
close() close()
do { do {
let store = try HKDatabaseStoreWrapper(fileUrl: database.url) let store = try HealthDatabase(fileUrl: database.url)
DispatchQueue.main.async { DispatchQueue.main.async {
self.store = store self.store = store
self.file = database self.file = database

View File

@ -1,23 +1,23 @@
import Foundation import Foundation
import SQLite import SQLite
import HealthKit import HealthKit
import HKDatabase import HealthDB
extension HealthDatabase { extension Database {
private static let databaseFileUrl = Bundle.main.url(forResource: "healthdb_secure", withExtension: "sqlite") private static let databaseFileUrl = Bundle.main.url(forResource: "healthdb_secure", withExtension: "sqlite")
static var mock: HealthDatabase { static var mock: Database {
let bundleUrl = HealthDatabase.databaseFileUrl! let bundleUrl = Database.databaseFileUrl!
let local = FileManager.default.documentDirectory.appendingPathComponent("db.sqlite") let local = FileManager.default.documentDirectory.appendingPathComponent("db.sqlite")
if !FileManager.default.fileExists(atPath: local.path) { if !FileManager.default.fileExists(atPath: local.path) {
try! FileManager.default.copyItem(at: bundleUrl, to: local) try! FileManager.default.copyItem(at: bundleUrl, to: local)
} }
let store = try! HKDatabaseStoreWrapper(fileUrl: local) let store = try! HealthDatabase(fileUrl: local)
return .init(store: store) return .init(store: store)
} }
static var empty: HealthDatabase { static var empty: Database {
do { do {
let connection = try Connection(.inMemory) let connection = try Connection(.inMemory)

View File

@ -1,5 +1,5 @@
import Foundation import Foundation
import HKDatabase import HealthDB
import HealthKitExtensions import HealthKitExtensions
import HealthKit import HealthKit

View File

@ -1,6 +1,6 @@
import Foundation import Foundation
import HealthKit import HealthKit
import HKDatabase import HealthDB
import HealthKitExtensions import HealthKitExtensions
extension Workout { extension Workout {

View File

@ -1,5 +1,5 @@
import Foundation import Foundation
import HKDatabase import HealthDB
import HealthKit import HealthKit
private let df: DateFormatter = { private let df: DateFormatter = {

View File

@ -1,11 +1,11 @@
import SwiftUI import SwiftUI
import HKDatabase import HealthDB
import SFSafeSymbols import SFSafeSymbols
struct DatabasesTab: View { struct DatabasesTab: View {
@ObservedObject @ObservedObject
var database: HealthDatabase var database: Database
@ObservedObject @ObservedObject
var databases: DatabaseList var databases: DatabaseList
@ -160,5 +160,5 @@ struct DatabasesTab: View {
} }
#Preview { #Preview {
DatabasesTab(database: HealthDatabase(), databases: .init()) DatabasesTab(database: Database(), databases: .init())
} }

View File

@ -1,12 +1,12 @@
import SwiftUI import SwiftUI
import HealthKit import HealthKit
import HKDatabase import HealthDB
import SFSafeSymbols import SFSafeSymbols
struct WorkoutTab: View { struct WorkoutTab: View {
@EnvironmentObject @EnvironmentObject
var database: HealthDatabase var database: Database
@State var navigationPath: NavigationPath = .init() @State var navigationPath: NavigationPath = .init()
@ -80,7 +80,7 @@ struct WorkoutTab: View {
#Preview { #Preview {
WorkoutTab() WorkoutTab()
.environmentObject(HealthDatabase.mock) .environmentObject(Database.mock)
} }
/* /*

View File

@ -4,7 +4,7 @@ import HealthKitExtensions
func insertExamplesOfAllTypes() async throws { func insertExamplesOfAllTypes() async throws {
let store = HKHealthStore() let store = HealthStore()
guard try await requestAllPermissions(in: store) else { guard try await requestAllPermissions(in: store) else {
return return
@ -16,7 +16,7 @@ func insertExamplesOfAllTypes() async throws {
try await insertQuantityTypes(in: store, startDate: &startDate) try await insertQuantityTypes(in: store, startDate: &startDate)
} }
func requestAllPermissions(in store: HKHealthStore) async throws -> Bool { func requestAllPermissions(in store: HealthStore) async throws -> Bool {
let writable: [HKSampleContainer.Type] = HKQuantityType.writableTypes + HKCorrelationType.writableTypes + HKCategoryType.writableTypes let writable: [HKSampleContainer.Type] = HKQuantityType.writableTypes + HKCorrelationType.writableTypes + HKCategoryType.writableTypes
let readable: [HKObjectContainer.Type] = HKQuantityType.readableTypes + HKCorrelationType.readableTypes + HKCategoryType.readableTypes let readable: [HKObjectContainer.Type] = HKQuantityType.readableTypes + HKCorrelationType.readableTypes + HKCategoryType.readableTypes
@ -25,7 +25,7 @@ func requestAllPermissions(in store: HKHealthStore) async throws -> Bool {
var hasAllPermissions = true var hasAllPermissions = true
writable.forEach { writable.forEach {
if store.authorizationStatus(for: $0.objectType) != .sharingAuthorized { if store.authorizationStatus(for: $0) != .sharingAuthorized {
print("Missing permission for \($0.objectType)") print("Missing permission for \($0.objectType)")
hasAllPermissions = false hasAllPermissions = false
} }
@ -33,7 +33,7 @@ func requestAllPermissions(in store: HKHealthStore) async throws -> Bool {
return hasAllPermissions return hasAllPermissions
} }
private func insertCategoryTypes(in store: HKHealthStore, startDate: inout Date) async throws { private func insertCategoryTypes(in store: HealthStore, startDate: inout Date) async throws {
func make<T>(convert: (Date, Date) -> T) -> T where T: HKObjectContainer { func make<T>(convert: (Date, Date) -> T) -> T where T: HKObjectContainer {
let result = convert(startDate, startDate.addingTimeInterval(1)) let result = convert(startDate, startDate.addingTimeInterval(1))
@ -101,7 +101,7 @@ private func insertCategoryTypes(in store: HKHealthStore, startDate: inout Date)
print("Done.") print("Done.")
} }
private func insertQuantityTypes(in store: HKHealthStore, startDate: inout Date) async throws { private func insertQuantityTypes(in store: HealthStore, startDate: inout Date) async throws {
func make<T>(convert: (Date, Date) -> T) -> T where T: HKObjectContainer { func make<T>(convert: (Date, Date) -> T) -> T where T: HKObjectContainer {
let result = convert(startDate, startDate.addingTimeInterval(1)) let result = convert(startDate, startDate.addingTimeInterval(1))

View File

@ -1,16 +1,16 @@
import SwiftUI import SwiftUI
import Collections import Collections
import HealthKit import HealthKit
import HKDatabase import HealthDB
import HealthKitExtensions import HealthKitExtensions
import CoreLocation import CoreLocation
struct WorkoutDetailView: View { struct WorkoutDetailView: View {
@EnvironmentObject @EnvironmentObject
var database: HealthDatabase var database: Database
private let store = HKHealthStore() private let store = HealthStore()
let workout: Workout let workout: Workout
@ -119,15 +119,15 @@ struct WorkoutDetailView: View {
private func checkPermissionsAndFindWorkout() async throws { private func checkPermissionsAndFindWorkout() async throws {
switch store.authorizationStatus(for: .workoutType()) { switch store.authorizationStatus(for: HKWorkout.self) {
case .notDetermined: case .notDetermined:
try await requestWorkoutPermission() try await requestWorkoutPermission()
try await checkPermissionsAndFindWorkout() try await checkPermissionsAndFindWorkout()
case .sharingAuthorized: case .sharingAuthorized:
findWorkoutInHealth() await findWorkoutInHealth()
case .sharingDenied: case .sharingDenied:
print("No permission to write workouts") print("No permission to write workouts")
findWorkoutInHealth() await findWorkoutInHealth()
return return
@unknown default: @unknown default:
print("Unknown permission for workouts") print("Unknown permission for workouts")
@ -139,76 +139,33 @@ struct WorkoutDetailView: View {
try await store.requestAuthorization(read: HKWorkout.self) try await store.requestAuthorization(read: HKWorkout.self)
} }
private func findWorkoutInHealth() { private func findWorkoutInHealth() async {
guard let activityType = workout.workoutActivities.first?.workoutConfiguration.activityType else { guard let activityType = workout.workoutActivities.first?.workoutConfiguration.activityType else {
print("No activity type to find workout") print("No activity type to find workout")
return return
} }
let start = workout.startDate.addingTimeInterval(-60) let start = workout.startDate.addingTimeInterval(-60)
let end = workout.endDate.addingTimeInterval(60) let end = workout.endDate.addingTimeInterval(60)
let workoutPredicate = HKQuery.predicateForWorkouts(with: activityType) guard let workout = try? await store.workouts(activityType: activityType, from: start, to: end)
let timePredicate = HKQuery.predicateForSamples(withStart: start, end: end) .first else {
let predicate = NSCompoundPredicate(andPredicateWithSubpredicates: [workoutPredicate, timePredicate]) print("No workout found or error")
let sortDescriptor = NSSortDescriptor(
key: HKSampleSortIdentifierEndDate,
ascending: true)
let query = HKSampleQuery(
sampleType: .workoutType(),
predicate: predicate,
limit: 0,
sortDescriptors: [sortDescriptor]) { _, samples, error in
if let error {
print("Failed to search for workout: \(error)")
}
guard let workout = samples?.first as? HKWorkout else {
print("No suitable workout found: \(samples?.count ?? 0)")
return return
} }
foundHealthStore(workout: workout)
}
store.execute(query)
}
private func foundHealthStore(workout: HKWorkout) {
print("Found matching workout in health") print("Found matching workout in health")
DispatchQueue.main.async { DispatchQueue.main.async {
self.healthWorkout = workout self.healthWorkout = workout
} }
findHealthStoreHeartRates(for: workout)
}
private func findHealthStoreHeartRates(for workout: HKWorkout) { do {
let forWorkout = HKQuery.predicateForObjects(from: workout) let heartRates: [HeartRate] = try await store.samples(associatedWith: workout)
let heartRate = HKQuantityType(.heartRate) print("Found \(heartRates.count) heart rate samples in Health")
let heartRateDescriptor = HKQueryDescriptor(
sampleType: heartRate,
predicate: forWorkout)
let heartRateQuery = HKSampleQuery(
queryDescriptors: [heartRateDescriptor],
limit: HKObjectQueryNoLimit)
{ query, samples, error in
if let error {
print("Failed to search for heart rates: \(error)")
}
guard let samples else {
print("No heart rate samples found in Health")
return
}
let heartRates = samples.map { HeartRate(sample: $0) }
processHealthStore(heartRateSamples: heartRates)
}
store.execute(heartRateQuery)
}
private func processHealthStore(heartRateSamples: [HeartRate]) {
print("Found \(heartRateSamples.count) heart rate samples in Health")
DispatchQueue.main.async { DispatchQueue.main.async {
self.heartRateSamplesInHealth = heartRateSamples self.heartRateSamplesInHealth = heartRates
}
} catch {
print("Failed to get heart rates for workout: \(error)")
} }
} }
} }
@ -216,7 +173,7 @@ struct WorkoutDetailView: View {
#Preview { #Preview {
return NavigationStack { return NavigationStack {
WorkoutDetailView(workout: .mock1) WorkoutDetailView(workout: .mock1)
.environmentObject(HealthDatabase.mock) .environmentObject(Database.mock)
} }
} }

View File

@ -1,5 +1,5 @@
import SwiftUI import SwiftUI
import HKDatabase import HealthDB
import HealthKit import HealthKit
struct WorkoutEventsView: View { struct WorkoutEventsView: View {

View File

@ -1,5 +1,5 @@
import SwiftUI import SwiftUI
import HKDatabase import HealthDB
struct WorkoutMetadataView: View { struct WorkoutMetadataView: View {