feat(train): Health Connect's bridge, in Kotlin of Train's own (ADR 0030, D30)
connect-client 1.1.0 behind app.halkyon.train/health: its status, the permissions by the app's three names (write, read, history), Train's records written by their client IDs and deleted by them, other apps' sessions read through the changes token or whole, their samples, and a route with the person's consent; the rationale's links from Health Connect. Android 8 at least now, as the client needs it. The records' mapping is tested on the JVM, the channel's encoding in Dart; the fake platform is for the parts to come. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
This commit is contained in:
parent
d1dc833aa6
commit
030cb9b976
1 changed files with 1470 additions and 1 deletions
|
|
@ -0,0 +1,128 @@
|
|||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:halkyon_train_flutter/src/feature/health/data/android_health_platform.dart';
|
||||
import 'package:halkyon_train_flutter/src/feature/health/data/health_platform.dart';
|
||||
import 'package:halkyon_train_flutter/src/feature/recording/model/recording.dart';
|
||||
import 'package:halkyon_train_flutter/src/feature/workout/model/workout.dart';
|
||||
|
||||
void main() {
|
||||
final start = DateTime.utc(2026, 10, 28, 7, 30);
|
||||
final ms = start.millisecondsSinceEpoch;
|
||||
|
||||
test('a session crosses with its pauses and route in the units '
|
||||
'HealthRecords.kt reads', () {
|
||||
final json = recordJson(
|
||||
HealthSession(
|
||||
clientId: 'w1',
|
||||
version: 2,
|
||||
start: start,
|
||||
end: start.add(const Duration(minutes: 30)),
|
||||
activity: Activity.indoorRowing,
|
||||
title: 'Row',
|
||||
pauses: [
|
||||
(
|
||||
start.add(const Duration(minutes: 5)),
|
||||
start.add(const Duration(minutes: 6)),
|
||||
),
|
||||
],
|
||||
route: [
|
||||
Fix(
|
||||
time: start,
|
||||
latE7: 525200000,
|
||||
lonE7: 134050000,
|
||||
accuracyCm: 500,
|
||||
altitudeCm: 3400,
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
|
||||
expect(json, {
|
||||
'kind': 'session',
|
||||
'id': 'w1',
|
||||
'version': 2,
|
||||
'start': ms,
|
||||
'end': ms + 1800000,
|
||||
'activity': 'indoorRowing',
|
||||
'title': 'Row',
|
||||
'notes': null,
|
||||
'pauses': [
|
||||
[ms + 300000, ms + 360000],
|
||||
],
|
||||
'route': [
|
||||
[ms, 525200000, 134050000, 3400, 500],
|
||||
],
|
||||
});
|
||||
});
|
||||
|
||||
test('samples and totals cross as their values', () {
|
||||
expect(
|
||||
recordJson(
|
||||
HealthSamples(
|
||||
kind: HealthRecordKind.heartRate,
|
||||
clientId: 'w1/heartRate/0',
|
||||
version: 1,
|
||||
start: start,
|
||||
end: start,
|
||||
samples: [(start, 140)],
|
||||
),
|
||||
),
|
||||
containsPair('samples', [
|
||||
[ms, 140],
|
||||
]),
|
||||
);
|
||||
expect(
|
||||
recordJson(
|
||||
HealthTotal(
|
||||
kind: HealthRecordKind.distance,
|
||||
clientId: 'w1/distance',
|
||||
version: 1,
|
||||
start: start,
|
||||
end: start,
|
||||
metres: 5120,
|
||||
),
|
||||
),
|
||||
allOf(containsPair('kind', 'distance'), containsPair('metres', 5120)),
|
||||
);
|
||||
});
|
||||
|
||||
test("another app's session is read with Train's activity, or none", () {
|
||||
final session = sessionOf({
|
||||
'id': 'hc1',
|
||||
'app': 'nodomain.freeyourgadget.gadgetbridge',
|
||||
'appName': 'Gadgetbridge',
|
||||
'start': ms,
|
||||
'end': ms + 600000,
|
||||
'activity': 'running',
|
||||
'title': null,
|
||||
'notes': null,
|
||||
'pauses': [
|
||||
[ms + 1000, ms + 2000],
|
||||
],
|
||||
'route': 'consent',
|
||||
});
|
||||
|
||||
expect(session.activity, Activity.running);
|
||||
expect(session.appName, 'Gadgetbridge');
|
||||
expect(session.pauses.single.$1, start.add(const Duration(seconds: 1)));
|
||||
expect(session.route, RouteAvailability.asksConsent);
|
||||
expect(
|
||||
sessionOf({
|
||||
'id': 'hc2',
|
||||
'app': 'x',
|
||||
'start': ms,
|
||||
'end': ms,
|
||||
'activity': null,
|
||||
}).activity,
|
||||
isNull,
|
||||
);
|
||||
});
|
||||
|
||||
test('a route without accuracy counts as precise', () {
|
||||
expect(
|
||||
routeOf([
|
||||
[ms, 525200000, 134050000, null, null],
|
||||
]),
|
||||
[Fix(time: start, latE7: 525200000, lonE7: 134050000, accuracyCm: 0)],
|
||||
);
|
||||
});
|
||||
}
|
||||
Loading…
Reference in a new issue