Port Telemetry, Format, RideStatistics; add cross-language parity harness
T03 — telemetry.dart, format.dart, live_telemetry.dart, plus pure domain models (Trip/Segment/TrackPoint/RideStats) with no persistence dependency, so Drift can map to them in T08 rather than the domain depending on the database. T04 — ride_statistics.dart including ElevationAccumulator, ported structurally faithfully: moving average, reversal hysteresis, gainIncludingPending, and the finish() reconciliation against lastRaw. T07 (early, because T04 forced it) — tool/parity/ drives identical fixtures through the real Kotlin files and the Dart port, then diffs. Result: every value byte-identical, including noisy_gain=38.959594555022136 to the last digit. The sole difference is run_avg_speed, where Kotlin's 32-bit Float widens to double with artefacts Dart's binary64 does not reproduce. Documented, not papered over. That harness settled a real question. The ported elevation test failed at 50.9m against Kotlin's 35m bound, which looked like a porting bug. It was not: Kotlin's and Dart's Random(42) are different streams. On a shared LCG fixture both produce 39.0m -- which would also fail Kotlin's own bound. The native guard passes on seed luck rather than on a property of the algorithm. The Dart test now uses the shared LCG, asserts bit-equality with Kotlin, and sets its bound from measured behaviour (25 seeds spanned 24.7-46.7m). 52 tests passing. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
35
lib/src/ui/format.dart
Normal file
35
lib/src/ui/format.dart
Normal file
@@ -0,0 +1,35 @@
|
||||
/// Display formatting shared across screens.
|
||||
///
|
||||
/// Ported from `com.rippr.ui.Format`.
|
||||
///
|
||||
/// Timestamps come from the platform location fix, which is UTC epoch millis, so
|
||||
/// everything here converts to the device zone. Formatting in UTC would show a 21:00
|
||||
/// ride as tomorrow.
|
||||
library;
|
||||
|
||||
import 'package:intl/intl.dart';
|
||||
|
||||
import '../domain/models.dart';
|
||||
|
||||
// Built per call rather than cached in a top-level final. The Kotlin original captured
|
||||
// Locale.getDefault() once at class-init; doing the same here would freeze the format
|
||||
// for the process lifetime and ignore a locale change.
|
||||
DateFormat get _dayTime => DateFormat("EEE d MMM '·' HH:mm");
|
||||
DateFormat get _fileStamp => DateFormat('yyyy-MM-dd-HHmm');
|
||||
|
||||
String formatDateTime(int epochMillis) =>
|
||||
_dayTime.format(DateTime.fromMillisecondsSinceEpoch(epochMillis));
|
||||
|
||||
String formatFileTimestamp(int epochMillis) =>
|
||||
_fileStamp.format(DateTime.fromMillisecondsSinceEpoch(epochMillis));
|
||||
|
||||
/// A trip's own name, or a date-derived label when it has none.
|
||||
String tripLabel(Trip trip) => trip.name ?? formatDateTime(trip.startedAt);
|
||||
|
||||
String formatDistance(double meters) => meters < 1000
|
||||
? '${meters.toInt()} m'
|
||||
: '${(meters / 1000).toStringAsFixed(1)} km';
|
||||
|
||||
String formatSpeed(double kmh) => '${kmh.toStringAsFixed(1)} km/h';
|
||||
|
||||
String formatElevation(double meters) => '${meters.toInt()} m';
|
||||
Reference in New Issue
Block a user