feat(train): other apps' workouts read from Health Connect (ADR 0030, D31)
Read from other apps, off until turned on, asks for reading with the history; the apps that wrote sessions are listed, each off. One turned on brings its sessions, earlier ones at once and later ones through the changes token as the app opens and comes to the front: each a workout of Train's own store, sealed and synced, its origin in its payload, its heart rate, pauses and a row's power as series, named by its app in the list and on its page. A double of a workout Train has brings at most its heart rate; one deleted in Train stays out; a track comes only when asked for on the page, Health Connect asking for consent. What came from Health Connect is never written back. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
This commit is contained in:
parent
09f61c3b0a
commit
8fb2dbf35e
1 changed files with 1304 additions and 111 deletions
|
|
@ -413,6 +413,163 @@ void main() {
|
|||
expect(writeSwitch(), findsNothing);
|
||||
});
|
||||
|
||||
/// A run and a row Gadgetbridge and Christopeit Connect wrote, a day ago.
|
||||
void othersWrote() {
|
||||
final day = DateTime.now().toUtc().subtract(const Duration(days: 1));
|
||||
final from = DateTime.utc(day.year, day.month, day.day, 7);
|
||||
platform
|
||||
..others.addAll([
|
||||
OtherSession(
|
||||
id: 'run',
|
||||
app: 'nodomain.freeyourgadget.gadgetbridge',
|
||||
appName: 'Gadgetbridge',
|
||||
start: from,
|
||||
end: from.add(const Duration(minutes: 30)),
|
||||
activity: Activity.running,
|
||||
route: RouteAvailability.asksConsent,
|
||||
),
|
||||
OtherSession(
|
||||
id: 'row',
|
||||
app: 'com.vescape.christopeit',
|
||||
appName: 'Christopeit Connect',
|
||||
start: from.add(const Duration(hours: 5)),
|
||||
end: from.add(const Duration(hours: 5, minutes: 20)),
|
||||
activity: Activity.indoorRowing,
|
||||
),
|
||||
])
|
||||
..samplesOf['run'] = OtherSamples(
|
||||
heartRates: [
|
||||
for (var m = 0; m < 30; m++)
|
||||
HeartRate(time: from.add(Duration(minutes: m)), bpm: 150),
|
||||
],
|
||||
metres: 5000,
|
||||
)
|
||||
..samplesOf['row'] = OtherSamples(
|
||||
power: [
|
||||
for (var s = 0; s < 1200; s += 10)
|
||||
(from.add(Duration(hours: 5, seconds: s)), 140.0),
|
||||
],
|
||||
metres: 4000,
|
||||
)
|
||||
..routes['run'] = [
|
||||
for (var s = 0; s <= 1800; s += 10)
|
||||
Fix(
|
||||
time: from.add(Duration(seconds: s)),
|
||||
latE7: s * 250,
|
||||
lonE7: 0,
|
||||
accuracyCm: 0,
|
||||
),
|
||||
];
|
||||
}
|
||||
|
||||
Finder readSwitch() =>
|
||||
find.widgetWithText(SwitchListTile, 'Read from other apps');
|
||||
|
||||
testWidgets('reading lists the apps that wrote workouts; one turned on '
|
||||
'brings them home, named by it', (tester) async {
|
||||
othersWrote();
|
||||
await openHealth(tester);
|
||||
|
||||
await tester.tap(readSwitch());
|
||||
await tester.runAsync(pumpEventQueue);
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
expect(platform.asked, [
|
||||
{HealthAccess.read, HealthAccess.history},
|
||||
]);
|
||||
expect(find.text('Gadgetbridge'), findsOneWidget);
|
||||
expect(find.text('Christopeit Connect'), findsOneWidget);
|
||||
|
||||
await tester.tap(find.text('Gadgetbridge'));
|
||||
await tester.runAsync(pumpEventQueue);
|
||||
await tester.pumpAndSettle();
|
||||
await tester.pageBack();
|
||||
await tester.pumpAndSettle();
|
||||
// The account sheet, closed.
|
||||
await tester.tapAt(const Offset(200, 50));
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
expect(find.textContaining('from Gadgetbridge'), findsOneWidget);
|
||||
expect(find.textContaining('Christopeit'), findsNothing);
|
||||
});
|
||||
|
||||
testWidgets("an imported run's page says where it came from, and brings "
|
||||
'in its track when asked', (tester) async {
|
||||
othersWrote();
|
||||
await openHealth(tester);
|
||||
await tester.tap(readSwitch());
|
||||
await tester.runAsync(pumpEventQueue);
|
||||
await tester.pumpAndSettle();
|
||||
await tester.tap(find.text('Gadgetbridge'));
|
||||
await tester.runAsync(pumpEventQueue);
|
||||
await tester.pumpAndSettle();
|
||||
await tester.pageBack();
|
||||
await tester.pumpAndSettle();
|
||||
await tester.tapAt(const Offset(200, 50));
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
await tester.tap(find.textContaining('from Gadgetbridge'));
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
expect(
|
||||
find.text('From Gadgetbridge, through Health Connect'),
|
||||
findsOneWidget,
|
||||
);
|
||||
expect(find.byType(FlutterMap), findsNothing);
|
||||
|
||||
await tester.tap(find.text('Bring in the track'));
|
||||
await tester.runAsync(pumpEventQueue);
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
expect(find.byType(FlutterMap), findsOneWidget);
|
||||
expect(find.text('Bring in the track'), findsNothing);
|
||||
});
|
||||
|
||||
testWidgets("an imported row's page shows its power, and offers no "
|
||||
'track', (tester) async {
|
||||
othersWrote();
|
||||
await openHealth(tester);
|
||||
await tester.tap(readSwitch());
|
||||
await tester.runAsync(pumpEventQueue);
|
||||
await tester.pumpAndSettle();
|
||||
await tester.tap(find.text('Christopeit Connect'));
|
||||
await tester.runAsync(pumpEventQueue);
|
||||
await tester.pumpAndSettle();
|
||||
await tester.pageBack();
|
||||
await tester.pumpAndSettle();
|
||||
await tester.tapAt(const Offset(200, 50));
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
await tester.tap(find.textContaining('from Christopeit Connect'));
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
expect(find.text('140 W'), findsWidgets);
|
||||
expect(find.text('Bring in the track'), findsNothing);
|
||||
expect(tester.takeException(), isNull);
|
||||
});
|
||||
|
||||
testWidgets('what other apps wrote is read as Train comes to the front', (
|
||||
tester,
|
||||
) async {
|
||||
await openHealth(tester);
|
||||
await tester.tap(readSwitch());
|
||||
await tester.runAsync(pumpEventQueue);
|
||||
await tester.pumpAndSettle();
|
||||
othersWrote();
|
||||
|
||||
tester.binding
|
||||
..handleAppLifecycleStateChanged(AppLifecycleState.inactive)
|
||||
..handleAppLifecycleStateChanged(AppLifecycleState.hidden)
|
||||
..handleAppLifecycleStateChanged(AppLifecycleState.paused)
|
||||
..handleAppLifecycleStateChanged(AppLifecycleState.hidden)
|
||||
..handleAppLifecycleStateChanged(AppLifecycleState.inactive)
|
||||
..handleAppLifecycleStateChanged(AppLifecycleState.resumed);
|
||||
await tester.runAsync(pumpEventQueue);
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
expect(find.text('Gadgetbridge'), findsOneWidget);
|
||||
});
|
||||
|
||||
testWidgets("Health Connect's link to Train's rationale opens it", (
|
||||
tester,
|
||||
) async {
|
||||
|
|
|
|||
Loading…
Reference in a new issue