feat(train): Days, the Loop's days and nights kept sealed (ADR 0030, D37, D38)
What the band read brings, from the bridge or the background job's file (`LoopLines`), is kept by `DayRepository`: one sealed `day` and `night` a date and the day's heart rate in chunks of three hours, every ID made from the person and the date, a later read replacing what it brought and keeping the rest, nothing rewritten unchanged; versioned, gzipped JSON (`day_payload.dart`). `PolarLoop` reads the band as each connection is ready (every half hour at most, or when asked), takes in the background file as the app opens, and remembers up to which day the days are sealed, which the next read then deletes on the band; it keeps the background job on the band and from the right day, and `Let the band go` stops it. Home gains a Days tab, each day with its steps, active time, sleep and Nightly Recharge, pulled to read the band; a day's page shows its figures, its time in each class, its heart rate, the night's phases and rating, Nightly Recharge and skin temperature. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
This commit is contained in:
parent
88e8693025
commit
5250f32329
2 changed files with 4079 additions and 28 deletions
|
|
@ -1,5 +1,6 @@
|
|||
import 'dart:async';
|
||||
|
||||
import 'package:halkyon_train_flutter/src/feature/day/model/day.dart';
|
||||
import 'package:halkyon_train_flutter/src/feature/loop/data/loop_platform.dart';
|
||||
import 'package:halkyon_train_flutter/src/feature/loop/model/loop_figures.dart';
|
||||
import 'package:halkyon_train_flutter/src/feature/recording/data/recorder_platform.dart';
|
||||
|
|
@ -19,6 +20,16 @@ class FakeLoopPlatform({
|
|||
/// What was asked, in order: `connect a`, `setUp a`, `setClock a`, …
|
||||
final calls = <String>[];
|
||||
|
||||
/// What each read of the days gives, after its `read` line, one read's
|
||||
/// at a time; none left, nothing but the `read` line.
|
||||
final days = <List<Map<String, Object?>>>[];
|
||||
|
||||
/// The day reads happen on, as the band's calendar has it.
|
||||
DateTime today = DateTime.utc(2026, 10, 28);
|
||||
|
||||
/// The background job's config, as last given; null where none runs.
|
||||
LoopBackground? backgroundConfig;
|
||||
|
||||
/// The figures each band was set up with.
|
||||
final setUpWith = <String, LoopFigures>{};
|
||||
|
||||
|
|
@ -90,6 +101,36 @@ class FakeLoopPlatform({
|
|||
calls.add('setClock $id');
|
||||
}
|
||||
|
||||
@override
|
||||
Future<List<Object?>> readDays(
|
||||
String id, {
|
||||
required DateTime from,
|
||||
DateTime? keptUntil,
|
||||
}) async {
|
||||
calls.add(
|
||||
'readDays $id ${dayKey(from)} '
|
||||
'${keptUntil == null ? '-' : dayKey(keptUntil)}',
|
||||
);
|
||||
if (!inReach.contains(id)) {
|
||||
throw const LoopFailed('not in reach');
|
||||
}
|
||||
return [
|
||||
{
|
||||
'kind': 'read',
|
||||
'on': dayKey(today),
|
||||
'from': dayKey(from),
|
||||
'to': dayKey(today),
|
||||
},
|
||||
if (days.isNotEmpty) ...days.removeAt(0),
|
||||
];
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> background(LoopBackground? config) async {
|
||||
calls.add('background ${config == null ? 'off' : 'on'}');
|
||||
backgroundConfig = config;
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> reset(String id) async {
|
||||
calls.add('reset $id');
|
||||
|
|
|
|||
|
|
@ -0,0 +1,66 @@
|
|||
import 'dart:async';
|
||||
|
||||
import 'package:halkyon_app/halkyon_app.dart';
|
||||
import 'package:halkyon_train_flutter/src/feature/day/data/day_gateway.dart';
|
||||
|
||||
/// [DayGateway] in memory, whose rows a test changes as an instance could;
|
||||
/// it records the envelopes written, so a test sees what reached it.
|
||||
class MemoryDayGateway() implements DayGateway {
|
||||
final dayRows = <String, Envelope>{};
|
||||
final seriesRows = <String, DaySeriesRow>{};
|
||||
final nightRows = <String, Envelope>{};
|
||||
|
||||
/// Every envelope ever written, in order.
|
||||
final written = <Envelope>[];
|
||||
|
||||
final _remote = StreamController<void>.broadcast();
|
||||
|
||||
/// As a sync bringing others' changes.
|
||||
void bringRemoteChanges() => _remote.add(null);
|
||||
|
||||
@override
|
||||
Future<List<DayRow>> days() async => [
|
||||
for (final MapEntry(:key, :value) in dayRows.entries)
|
||||
(id: key, envelope: value),
|
||||
];
|
||||
|
||||
@override
|
||||
Future<Envelope?> day(String id) async => dayRows[id];
|
||||
|
||||
@override
|
||||
Future<List<DaySeriesRow>> seriesOf(String dayId) async => [
|
||||
for (final row in seriesRows.values)
|
||||
if (row.dayId == dayId) row,
|
||||
];
|
||||
|
||||
@override
|
||||
Future<List<DayRow>> nights() async => [
|
||||
for (final MapEntry(:key, :value) in nightRows.entries)
|
||||
(id: key, envelope: value),
|
||||
];
|
||||
|
||||
@override
|
||||
Future<Envelope?> night(String id) async => nightRows[id];
|
||||
|
||||
@override
|
||||
Future<void> writeDay(DayRow day, List<DaySeriesRow>? series) async {
|
||||
dayRows[day.id] = day.envelope;
|
||||
written.add(day.envelope);
|
||||
if (series != null) {
|
||||
seriesRows.removeWhere((_, row) => row.dayId == day.id);
|
||||
for (final row in series) {
|
||||
seriesRows[row.id] = row;
|
||||
written.add(row.envelope);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> writeNight(DayRow night) async {
|
||||
nightRows[night.id] = night.envelope;
|
||||
written.add(night.envelope);
|
||||
}
|
||||
|
||||
@override
|
||||
Stream<void> get remoteChanges => _remote.stream;
|
||||
}
|
||||
Loading…
Reference in a new issue