feat(train): equipment by place and a plate calculator (ADR 0034)

The places a person trains at (home, a gym), each a sealed, synced
`training_place` row with its equipment: bars and handles with their own
weight, plates by weight and count, dumbbells and kettlebells, the rest
by count, the person's own by name. A new place starts from what a home
or a gym usually has. The place last chosen is remembered on the device,
never its location.

The plate calculator loads each weight both sides alike, the heaviest
load not above it with the fewest plates, heavier inside, and over a
series of sets picks the loads making the fewest plate moves, listing
what comes off and goes on. Equipment is in Settings; strength plans
are to build on it.

The migration only creates the table, its space index and its cascade
to the sync engine's spaces.

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-02 11:59:24 +01:00
commit 064b911aa2
Signed by: raisondetredev
GPG key ID: 07F6FCF405488B64

View file

@ -0,0 +1,35 @@
import 'dart:async';
import 'package:halkyon_app/halkyon_app.dart';
import 'package:halkyon_train_flutter/src/feature/equipment/data/place_gateway.dart';
/// [PlaceGateway] in memory, whose rows a test changes as an instance could.
class MemoryPlaceGateway() implements PlaceGateway {
final rows = <String, Envelope>{};
final _remote = StreamController<void>.broadcast();
/// As a sync bringing others' changes.
void bringRemoteChanges() => _remote.add(null);
@override
Future<List<PlaceRow>> places() async => [
for (final MapEntry(:key, :value) in rows.entries)
(id: key, envelope: value),
];
@override
Future<void> insert(PlaceRow place) async => rows[place.id] = place.envelope;
@override
Future<void> update(String id, Envelope envelope) async {
if (rows.containsKey(id)) {
rows[id] = envelope;
}
}
@override
Future<void> delete(String id) async => rows.remove(id);
@override
Stream<void> get remoteChanges => _remote.stream;
}