Skip to content

Commit

Permalink
also toothbrushing in sessions per day
Browse files Browse the repository at this point in the history
  • Loading branch information
krugerk committed Dec 18, 2024
1 parent e3655e3 commit 63decd7
Show file tree
Hide file tree
Showing 5 changed files with 122 additions and 22 deletions.
1 change: 1 addition & 0 deletions BeeKit/HeathKit/HealthKitConfig.swift
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ public enum HealthKitConfig {

// Self care
ToothbrushingHealthKitMetric.make(),
ToothbrushingSessionsHealthKitMetric.make(),

// Sleep
TimeInBedHealthKitMetric(humanText: "Time in bed", databaseString: "timeInBed", category: .Sleep),
Expand Down
36 changes: 36 additions & 0 deletions BeeKit/HeathKit/Toothbrushing/ToothbrushingHealthKitMetric.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import Foundation
import HealthKit

/// tracks toothbrushing, in number of seconds per day (daystamp)
class ToothbrushingHealthKitMetric: CategoryHealthKitMetric {
private static let healthkitMetric = ["toothbrushing", "seconds-per-day"].joined(separator: "|")

private init(humanText: String,
databaseString: String,
category: HealthKitCategory) {
super.init(humanText: humanText,
databaseString: databaseString,
category: category,
hkSampleType: HKObjectType.categoryType(forIdentifier: .toothbrushingEvent)!)
}

override func units(healthStore : HKHealthStore) async throws -> HKUnit {
HKUnit.second()
}

static func make() -> ToothbrushingHealthKitMetric {
.init(humanText: "Teethbrushing (in seconds per day)",
databaseString: healthkitMetric,
category: HealthKitCategory.SelfCare)
}

override func recentDataPoints(days: Int, deadline: Int, healthStore: HKHealthStore) async throws -> [any BeeDataPoint] {
try await super.recentDataPoints(days: days, deadline: deadline, healthStore: healthStore)
.map {
NewDataPoint(requestid: $0.requestid,
daystamp: $0.daystamp,
value: $0.value,
comment: "Auto-entered via Apple Health (\(Self.healthkitMetric))")
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
// Part of BeeSwift. Copyright Beeminder

/// tracks toothbrushing, in number of sessions per day (daystamp)
class ToothbrushingSessionsHealthKitMetric: CategoryHealthKitMetric {
private static let healthkitMetric = ["toothbrushing", "sessions-per-day"].joined(separator: "|")

private init(humanText: String,
databaseString: String,
category: HealthKitCategory) {
super.init(humanText: humanText,
databaseString: databaseString,
category: category,
hkSampleType: HKObjectType.categoryType(forIdentifier: .toothbrushingEvent)!)
}

override func units(healthStore : HKHealthStore) async throws -> HKUnit {
.count()
}

static func make() -> ToothbrushingSessionsHealthKitMetric {
.init(humanText: "Teethbrushing (in sessions per day)",
databaseString: healthkitMetric,
category: HealthKitCategory.SelfCare)
}

override func recentDataPoints(days: Int, deadline: Int, healthStore: HKHealthStore) async throws -> [any BeeDataPoint] {
let todayDaystamp = Daystamp.now(deadline: deadline)
let startDaystamp = todayDaystamp - days

let predicate = HKQuery.predicateForSamples(withStart: startDaystamp.start(deadline: deadline),
end: todayDaystamp.end(deadline: deadline))

let samples = try await withCheckedThrowingContinuation({ (continuation: CheckedContinuation<[HKSample], Error>) in
let query = HKSampleQuery(sampleType: sampleType(),
predicate: predicate,
limit: HKObjectQueryNoLimit,
sortDescriptors: [NSSortDescriptor(key: HKSampleSortIdentifierStartDate, ascending: true)],
resultsHandler: { (query, samples, error) in
if let error {
continuation.resume(throwing: error)
} else if let samples {
continuation.resume(returning: samples)
} else {
continuation.resume(throwing: HealthKitError("HKSampleQuery did not return samples"))
}
})
healthStore.execute(query)
})
.compactMap { $0 as? HKCategorySample }

let calendar = Calendar.autoupdatingCurrent
let groupedByDay = Dictionary(grouping: samples, by: { sample in
calendar.startOfDay(for: sample.startDate)
})

let dailyCounts = groupedByDay
.map { ($0, $1.count) }
.sorted { $0.0 < $1.0 }

let datapoints = dailyCounts.map({ (date, numberOfEntries) in
let daystamp = Daystamp(fromDate: date, deadline: deadline)
let requestID = "apple-heath-" + daystamp.description

return NewDataPoint(requestid: requestID,
daystamp: daystamp,
value: NSNumber(value: numberOfEntries),
comment: "Auto-entered via Apple Health (\(Self.healthkitMetric))")
})

return datapoints
}
}
21 changes: 0 additions & 21 deletions BeeKit/HeathKit/ToothbrushingHealthKitMetric.swift

This file was deleted.

14 changes: 13 additions & 1 deletion BeeSwift.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@

/* Begin PBXBuildFile section */
9B1DCA5B2D10EA76006A64D9 /* ToothbrushingHealthKitMetric.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9B1DCA5A2D10EA76006A64D9 /* ToothbrushingHealthKitMetric.swift */; };
9B7D44662D12C304003B62B1 /* ToothbrushingSessionsHealthKitMetric.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9B7D44652D12C304003B62B1 /* ToothbrushingSessionsHealthKitMetric.swift */; };
9B8CA57D24B120CA009C86C2 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 9B8CA57C24B120CA009C86C2 /* LaunchScreen.storyboard */; };
A10D4E931B07948500A72D29 /* DatapointsTableView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A10D4E921B07948500A72D29 /* DatapointsTableView.swift */; };
A10DC2DF207BFCBA00FB7B3A /* RemoveHKMetricViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = A10DC2DE207BFCBA00FB7B3A /* RemoveHKMetricViewController.swift */; };
Expand Down Expand Up @@ -219,6 +220,7 @@

/* Begin PBXFileReference section */
9B1DCA5A2D10EA76006A64D9 /* ToothbrushingHealthKitMetric.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ToothbrushingHealthKitMetric.swift; sourceTree = "<group>"; };
9B7D44652D12C304003B62B1 /* ToothbrushingSessionsHealthKitMetric.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ToothbrushingSessionsHealthKitMetric.swift; sourceTree = "<group>"; };
9B8CA57C24B120CA009C86C2 /* LaunchScreen.storyboard */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; path = LaunchScreen.storyboard; sourceTree = "<group>"; };
A10D4E921B07948500A72D29 /* DatapointsTableView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DatapointsTableView.swift; sourceTree = "<group>"; };
A10DC2DE207BFCBA00FB7B3A /* RemoveHKMetricViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RemoveHKMetricViewController.swift; sourceTree = "<group>"; };
Expand Down Expand Up @@ -411,6 +413,15 @@
/* End PBXFrameworksBuildPhase section */

/* Begin PBXGroup section */
9B7D44642D12C2F3003B62B1 /* Toothbrushing */ = {
isa = PBXGroup;
children = (
9B7D44652D12C304003B62B1 /* ToothbrushingSessionsHealthKitMetric.swift */,
9B1DCA5A2D10EA76006A64D9 /* ToothbrushingHealthKitMetric.swift */,
);
path = Toothbrushing;
sourceTree = "<group>";
};
A106AD8B1AF1F62800C434E8 /* Managers */ = {
isa = PBXGroup;
children = (
Expand Down Expand Up @@ -624,7 +635,7 @@
E4E6426E290E27CB004F3EA9 /* HeathKit */ = {
isa = PBXGroup;
children = (
9B1DCA5A2D10EA76006A64D9 /* ToothbrushingHealthKitMetric.swift */,
9B7D44642D12C2F3003B62B1 /* Toothbrushing */,
A1E618FF1E86980900D8ED93 /* HealthKitConfig.swift */,
E4E642832910C442004F3EA9 /* CategoryHealthKitMetric.swift */,
E4E642872910D055004F3EA9 /* MindfulSessionHealthKitMetric.swift */,
Expand Down Expand Up @@ -1108,6 +1119,7 @@
E46071012B451FA400305DB4 /* BeeminderModel.xcdatamodeld in Sources */,
E458C80D2AD11C64000DCA5C /* Crypto.swift in Sources */,
E458C8012AD11BB3000DCA5C /* RequestManager.swift in Sources */,
9B7D44662D12C304003B62B1 /* ToothbrushingSessionsHealthKitMetric.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
Expand Down

0 comments on commit 63decd7

Please sign in to comment.