V3-10: trip splitting

TripRepository.splitTrip mirrors mergeTrips' transaction shape: splits at a segment boundary, moves the target segment and everything after it to a new trip, recomputes both trips' aggregates from scratch. startedAt/endedAt derive from each trip's actual remaining segments, not copied from the pre-split row. Trip detail gained a split action with a segment-boundary picker and a naming confirmation; a single-segment ride explains why it can't split instead of offering a dead control.
This commit is contained in:
2026-08-17 19:23:44 -05:00
parent a1e99684e0
commit 0e605ef174
7 changed files with 417 additions and 2 deletions

View File

@@ -337,6 +337,13 @@ class AppDatabase extends _$AppDatabase {
SegmentsCompanion(tripId: Value(newTripId)),
);
/// Used by split (V3-10): unlike [reparentSegments], moves exactly one segment rather
/// than every segment on a trip.
Future<void> reparentSegment(int segmentId, int newTripId) =>
(update(segments)..where((s) => s.id.equals(segmentId))).write(
SegmentsCompanion(tripId: Value(newTripId)),
);
Future<int> countSegmentsForTrip(int tripId) async => (await (select(
segments,
)..where((s) => s.tripId.equals(tripId))).get()).length;
@@ -499,6 +506,17 @@ class AppDatabase extends _$AppDatabase {
TrackPointsCompanion(tripId: Value(newTripId)),
);
/// Used by split (V3-10): moves only the points belonging to the given segments, not
/// every point on the trip -- the counterpart to [reparentSegment].
Future<void> reparentPointsForSegments(List<int> segmentIds, int newTripId) {
if (segmentIds.isEmpty) return Future.value();
return (update(
trackPoints,
)..where((p) => p.segmentId.isIn(segmentIds))).write(
TrackPointsCompanion(tripId: Value(newTripId)),
);
}
Future<List<domain.TrackPoint>> allPoints() async {
final rows = await (select(
trackPoints,