fix(train): a recording followed whichever starts first, the app listening or the service

On a fresh install (GrapheneOS) a walk recorded no GPS on the screen,
which waited for GPS throughout though the file was whole. The app
listens to the recorder's lines as soon as it asked for the start, and
Android starts the service a moment later: listening first, it got
nothing of the file, and the service then wrote the header straight to
the file, past what hands each line on. The reader takes the first line
it gets as the header, found none, and dropped every line after.

The service now writes its header through write() as every line, and
the app takes the header from the file's first line where lines came
without one (RecordingReader.recover, on the next second), reading the
lines kept since after it, so either order and either recorder version
is followed. The fake recorder can now begin late, as the service does.

On foot, auto-pause by steps paused after 5 s and went on only with a
step where the phone has no step detector or one switched off
(GrapheneOS' Sensors): GPS speed decides there, and until the first
step and once the detector has been silent 30 s. A minute without any
fix, the recording screen asks whether location is on and precise.
ADR 0030, D65.

Whats-New: A recording follows its GPS again on a phone where it waited for GPS throughout.
Whats-New: Auto-pause on foot goes by GPS speed where the phone counts no steps.
Whats-New: After a minute without GPS, the recording screen asks whether location is on and precise.
Whats-New-De: Eine Aufzeichnung folgt ihrem GPS wieder auf Handys, auf denen sie die ganze Zeit auf GPS wartete.
Whats-New-De: Die automatische Pause zu Fuß richtet sich nach der GPS-Geschwindigkeit, wo das Handy keine Schritte zählt.
Whats-New-De: Nach einer Minute ohne GPS fragt die Aufzeichnung, ob der Standort an und genau ist.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
This commit is contained in:
Tizian 「ティツィアーン」 2026-11-04 20:38:07 +01:00
commit d305725bb5
Signed by: raisondetredev
GPG key ID: 07F6FCF405488B64
2 changed files with 252 additions and 21 deletions

View file

@ -36,6 +36,24 @@ void main() {
expect(live.pace, isNull);
});
test('a minute without any fix, from Start or since the last, says GPS is '
'silent (D65); a row never', () {
Recording run(List<Fix> fixes) =>
Recording(activity: Activity.walking, startedAt: start, fixes: fixes);
expect(liveOf(run([]), at(59)).gpsSilent, isFalse);
expect(liveOf(run([]), at(60)).gpsSilent, isTrue);
expect(liveOf(run([north(30, 0)]), at(89)).gpsSilent, isFalse);
expect(liveOf(run([north(30, 0)]), at(90)).gpsSilent, isTrue);
expect(
liveOf(
Recording(activity: Activity.indoorRowing, startedAt: start),
at(600),
).gpsSilent,
isFalse,
);
});
test('moving, it shows the distance and the pace of the last 30 seconds', () {
final live = liveOf(
Recording(

View file

@ -202,6 +202,38 @@ void main() {
expect(platform.started, hasLength(1));
});
test('a service that began after the app listened, its header written to '
'the file alone, is followed all the same (D65)', () async {
platform.beginsLate = true;
var now = start;
await withClock(Clock(() => now), () async {
final bloc = await open();
bloc.add(const RecordingEventStartRequested(Activity.walking));
await bloc.stream.firstWhere((s) => s.phase == RecordingPhase.recording);
await pumpEventQueue();
platform
..begin()
..now = start.add(const Duration(seconds: 1))
..write('p @ 0 0 500 -')
..now = start.add(const Duration(seconds: 30))
..write('p @ ${(100 * 89.932).round()} 0 500 -');
now = start.add(const Duration(seconds: 30));
final live = (await tick(bloc)).live!;
expect(live.distanceMetres, 100);
expect(live.movingTime, const Duration(seconds: 30));
expect(platform.notices.last.text, '100 m');
// What comes after is taken in once, as it comes.
platform
..now = start.add(const Duration(seconds: 60))
..write('p @ ${(200 * 89.932).round()} 0 500 -');
now = start.add(const Duration(seconds: 60));
expect((await tick(bloc)).live!.distanceMetres, 200);
});
});
test('it follows the service: distance, an automatic pause, a pause by '
'hand, and shows each change in the notification', () async {
var now = start;