Files
Dylan 0bc42b2e5a Add the Rippr Flutter port: source, history bundle, and installable APK
The port runs on Android and iOS and is feature-complete; the native Android app
is superseded but kept, since it is still the only version that has recorded real
rides.

rippr-flutter-1.0-debug.apk is package com.rippr.port, deliberately different
from the native com.rippr so both install side by side. Recording the same ride
on both at once is the strongest available check that the port is faithful.

Added INSTALL.md covering both platforms. Android is a one-line adb install; iOS
has no APK equivalent and must be built and signed through Xcode with a free
Apple ID, which gives a 7-day profile.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-15 22:37:45 -05:00

103 lines
3.7 KiB
Dart

// ignore_for_file: avoid_print -- this is a command-line harness; stdout is the output.
// Dart side of the cross-language parity harness (T07).
// Must stay fixture-for-fixture identical to tool/parity/main.kt.
import 'dart:math' as math;
import 'package:rippr/src/domain/models.dart';
import 'package:rippr/src/export/ride_export.dart';
import 'package:rippr/src/geo/geo.dart';
import 'package:rippr/src/stats/ride_statistics.dart';
/// Same LCG as the Kotlin oracle. Dart ints are 64-bit two's complement on the VM and
/// multiplication wraps, matching Kotlin's Long.
class Lcg {
Lcg(this._s);
int _s;
double nextDouble() {
_s = _s * 6364136223846793005 + 1442695040888963407;
final bits = (_s >>> 11) & ((1 << 53) - 1);
return bits / (1 << 53);
}
}
/// FNV-1a 32-bit. Dart's String.hashCode differs from Java's, so it cannot be used to
/// compare text across languages -- this can.
int fnv1a(String s) {
var h = 2166136261;
for (final c in s.codeUnits) {
h ^= c;
h = (h * 16777619) & 0xFFFFFFFF;
}
return h;
}
TrackPoint p(int seg, int ts, double lat, double lon, double sp, double alt, int id) =>
TrackPoint(id: id, tripId: 1, segmentId: seg, timestamp: ts,
latitude: lat, longitude: lon, speedKmh: sp, altitudeM: alt);
void main() {
void out(String k, Object v) => print('$k=$v');
out('haversine_calgary_edmonton', haversineMeters(51.0447, -114.0719, 53.5461, -113.4938));
out('haversine_short_hop', haversineMeters(51.0447, -114.0719, 51.04480, -114.0719));
out('perp_beyond_end', perpendicularDistanceMeters(
const LatLon(51.003, -114.0), const LatLon(51.000, -114.0), const LatLon(51.002, -114.0)));
final ride = List.generate(21600,
(i) => LatLon(51.0 + i * 0.00001, -114.0 + math.sin(i / 100.0) * 0.001));
final simplified = simplify(ride, 5.0);
out('simplify_count', simplified.length);
out('simplify_last_lat', simplified.last.lat);
out('path_length_ride', pathLengthMeters(ride));
final rng = Lcg(42);
final noisy = List.generate(600,
(i) => p(1, i * 1000, 51.0, -114.0, 0.0, 1000.0 + (rng.nextDouble() * 16.0 - 8.0), i + 1));
out('noisy_gain', computeSummary(noisy).elevationGainM);
out('noisy_loss', computeSummary(noisy).elevationLossM);
final climb = List.generate(101,
(i) => p(1, i * 1000, 51.0 + i * 0.0001, -114.0, 50.0, 1000.0 + i, i + 1));
out('climb_gain', computeSummary(climb).elevationGainM);
final run = List.generate(100,
(i) => p(1, i * 1000, 51.0 + i * 0.0001, -114.0, 40.0, 1000.0, i + 1));
final s = computeSummary(run);
out('run_distance', s.distanceM);
out('run_moving_millis', s.movingMillis);
out('run_avg_speed', s.avgMovingSpeedKmh);
final prof = elevationProfile(run, maxSamples: 200);
out('profile_size', prof.length);
out('profile_last_distance', prof.last.distanceM);
final hist = speedHistogram(run, bucketKmh: 10);
out('hist_buckets', hist.length);
out('hist_first_millis', hist.first.millis);
// --- export byte output -------------------------------------------------
const etrip = Trip(
id: 1,
startedAt: 1700000000000,
endedAt: 1700000600000,
state: TripState.completed,
distanceM: 12345.6,
maxSpeedKmh: 118.4);
const esegs = [
Segment(id: 1, tripId: 1, startedAt: 0, endedAt: 1),
Segment(id: 2, tripId: 1, startedAt: 2, endedAt: 3),
];
final epts = <TrackPoint>[
for (final sg in esegs)
for (var i = 0; i < 5; i++)
p(sg.id, 1700000000000 + i * 1000, 51.0447 + i * 0.0001, -114.0719, 72.0,
1045.0, i + 1),
];
final g = gpx(etrip, esegs, epts, "Sam & Dave's <ride>");
final j = geoJson(etrip, esegs, epts, "Sam & Dave's <ride>");
out('gpx_len', g.length);
out('gpx_fnv', fnv1a(g));
out('geojson_len', j.length);
out('geojson_fnv', fnv1a(j));
}