Skip to content

Releases: thecloudseeker/activity_files

v0.7.6

Choose a tag to compare

@thecloudseeker thecloudseeker released this 21 Aug 11:56
d1efece
  • GPX trackpoints missing <time> no longer drag the segment's lap bounds to the epoch fallback.
  • TCX no longer drops a lap's first trackpoint when StartTime postdates it.
  • validateLapBoundaries() no longer skips corruption checks on activities with no GPS points.
  • GeoJSON multi-Point activity_type is no longer locked to whichever feature parses first.
  • GeoJSON no longer strands real data in additionalTracks when the first track feature is empty.
  • IntegrityMode.silent no longer discards every diagnostic, only integrity ones.
  • Raising maxPayloadBytes above 64MB no longer silently empties ActivityLoadResult.payload.
  • merge() no longer skips normalize: true for a single-activity list.
  • EncoderOptions is no longer a silent no-op for CSV/GeoJSON export; CSV lat/lon/elevation now default to fixed precision instead of raw toString() output.
  • CSV no longer lets a custom channel named after a fixed column (elevation, latitude, etc.) corrupt the real field on round-trip.
  • CSV now recognizes header variants like lat/lng/ele/hr, not just exact spellings.
  • CSV now reports a duplicate header instead of letting the rightmost column win.
  • splitBySport() no longer drops points in a gap between two laps of the same sport.
  • Validation diagnostics no longer point callers at the nonexistent trimToPoints(); they suggest RawEditor.crop() instead.

v0.7.5

Choose a tag to compare

@thecloudseeker thecloudseeker released this 14 Aug 18:12
f572ebc

Fixed

  • FIT parsing no longer mistakes vendor-specific messages for GPS records, producing garbage coordinates/altitude/heart rate on real files; messages it still can't decode are now reported via diagnostic instead of silently skipped.
  • GPX trackpoints missing <time> are no longer dropped; they keep an epoch timestamp instead, now reported via a diagnostic like an invalid timestamp already was.
  • TCX laps with more than one <Track> (pause/resume) no longer read only the first one.
  • GeoJSON/CSV timestamps without a UTC offset are now treated as UTC instead of local time, matching GPX/TCX.
  • FIT fields 78/120 (altitude, grade, balance, ebike assist) no longer map to the wrong field numbers.
  • load()/convert()/convertAndExport() no longer crash on a Stream<List<int>> source with isolates on (the default).
  • ActivityConverter.convert() no longer drops repair diagnostics.
  • trimInvalid() no longer drops laps on activities with no surviving GPS points (indoor/trainer).
  • crop()/deleteRange()/insertPause()/removePause() now carry events/lengths/sets forward, like shiftTime() already did.
  • sortAndDedup()/updatePoint() no longer pick a random winner among duplicate timestamps; sorting is stable now.
  • splitBySport() no longer bleeds points between sports on brick workouts (run/bike/run); splitting is per-lap now.
  • flattened() no longer gives every lap the primary track's sport; each lap keeps its own now.
  • GeoJSON no longer drops channel data (HR, power, etc.): export writes it as coordinateProperties.channels, and MultiLineString parsing now collects it per line too.
  • GeoJSON no longer smears one feature property across every point; it's kept as metadata now.
  • GPX export no longer drops anything outside the usual 9 channels; they're written as extra gpxtpx: tags now.
  • TCX export now reports lossy.channels_dropped instead of dropping a channel with no warning.
  • GeoJSON multi-track FeatureCollections no longer keep only the first track; the rest are kept in additionalTracks now.
  • GeoJSON parsing now also reads coordTimes (togeojson/Mapbox), not just our own coordinateProperties.times.
  • Duplicate, out-of-order, or pre-1990 timestamps no longer silently drop points, misplace lap boundaries, wrap FIT timestamps to a garbage future date, or reorder records; adjustments are reported via diagnostics now.
  • FIT's outlier filter no longer drops an entire legitimate track, only a stray bad point, when multiple tracks get merged into one FIT file; it only drops small (≤10-point) clusters now.
  • FIT laps with missing start_time/total_elapsed_time or a corrupted/reordered timestamp no longer get dropped or produce a negative duration; a start is inferred, or the duration is clamped to zero, with a diagnostic either way.
  • convert()/export() no longer only normalizes/orders the primary track: multi-track sources are flattened first for single-track formats now, and GPX normalizes additionalTracks directly instead of leaving them untouched.
  • One bad feature in a GeoJSON FeatureCollection no longer wipes the whole thing; only that feature is skipped now.
  • TCX multi-sport export no longer drops the point sitting on a sport boundary.
  • GeoJSON total_calories/total_steps/device_manufacturer are no longer dropped on parse.

v0.7.4

Choose a tag to compare

@thecloudseeker thecloudseeker released this 09 Aug 09:39
93ab0c0

Fixed

  • exportAsync(useIsolate: true) no longer drops most of RawActivity crossing the isolate boundary.
  • normalize: true no longer skips sentinel repairs on already-sorted input.
  • splitBySport() no longer duplicates a point sitting exactly on a shared lap boundary.
  • TCX export no longer duplicates a trackpoint across overlapping lap boundaries.
  • trimInvalid() no longer drops GPX extensions/attributes when clearing a sentinel elevation.
  • GeoJSON export no longer fabricates avg_heart_rate: 0 for laps with no heart rate data.
  • GeoJSON metadata no longer goes stale after an edit.
  • convert()'s isolate branch no longer exports the pre-autoFix activity.
  • GPX/TCX timestamps without a UTC offset are now treated as UTC instead of local time.

v0.7.3

Choose a tag to compare

@thecloudseeker thecloudseeker released this 08 Aug 11:56
aa3d52b

Fixed

  • GeoJSON LineString export no longer fabricates total_calories/total_steps as 0.
  • GeoJSON/CSV parsers now warn (geojson.point.*, csv.row.*) instead of silently dropping malformed points/rows, with correct per-feature diagnostics and CSV row numbers.

v0.7.2

Choose a tag to compare

@thecloudseeker thecloudseeker released this 02 Aug 12:59
5928826

Fixed

  • ActivityFiles.channelSamplesFrom() emitted second-resolution timestamps instead of milliseconds, which didn't match what builderFromStreams()/convertAndExport() expect — round-tripping through it silently produced dates near 1970 instead of the correct time. If you built on this in 0.7.0/0.7.1, re-check any stored output.
  • convertAndExport(location: ..., autoFix: ...) silently ignored autoFix when building from raw streams instead of a source file.

v0.7.1

Choose a tag to compare

@thecloudseeker thecloudseeker released this 29 Jul 19:57
b57ed97

Fixed

  • No changes to the package itself
  • GitHub workflow fix (fixes automated publishing to pub.dev)

v0.7.0

Choose a tag to compare

@thecloudseeker thecloudseeker released this 27 Jul 20:46
caf4637

Breaking

  • FIT parsing is now remapped to the official FIT profile. Since 0.6.0, session/lap stats and the sport value were parsed incorrectly
    • Migration: re-parse any FIT summaries stored with ≤ 0.6.x.
  • FIT encoding fixed accordingly: lap distance and sport values are now written correctly. FIT files exported with ≤ 0.6.x carry these defects; re-export them from source.
  • Multi-track GPX is now preserved instead of merged: the first <trk> becomes the primary RawActivity; the rest are stored in RawActivity.additionalTracks. Single-track encoders (TCX/FIT/CSV/GeoJSON) merge them automatically via the new RawActivity.flattened(), so conversions keep every point. But activity.points after loading a multi-track GPX now contains only the first track.
  • Default normalization now repairs sentinel values: RawEditor.trimInvalid() (run by convert/export when normalize: true, the default) removes Null Island points (lat/lon ≈ 0) and clears elevations ≤ −499 m while keeping the point. Repairs are reported via RawEditor.repairDiagnostics and in conversion/export diagnostics (repaired.sentinel_coords_removed, repaired.sentinel_elevation_cleared). Pass normalize: false to opt out.
  • ValidationResult.isValid / LapValidationResult.isValid now derive from the structured diagnostics list as well as the legacy errors list.

Added

Parsers and encoders now round-trip everything a format can hold; anything the target format can't represent is reported via a lossy.* diagnostic (lossy.sets_dropped, lossy.events_dropped, lossy.lengths_dropped, lossy.sessions_dropped, lossy.summary_dropped, lossy.laps_dropped, lossy.multi_track_flattened) instead of being dropped silently. Per format:

  • FIT import: multi-session files (e.g. triathlons), timer events, per-length swim data, and unknown record/session/lap fields (extraFitFields/extraFitArrays) are all preserved; developer fields decode using their declared name, type, scale, and offset.
  • FIT export: lossless FIT→FIT round-trips: session/lap stats, sets, events, swim lengths, device metadata, and custom channels (including unmapped ones as named developer fields) all write back.
  • GPX: waypoints and routes are preserved as structured data (gpxWaypoints, gpxRoutes) instead of being folded into the track; multi-segment tracks and unknown extensions round-trip.
  • TCX: power (ns3:TPX Watts/Speed/RunCadence) now parses as channels instead of being dropped; notes, author, and lap intensity/trigger fields round-trip.
  • GeoJSON: elevation, per-point timestamps, and feature properties all round-trip; polygons parse as tracks.
  • CSV: unknown numeric columns round-trip as custom channels.

New data model and API:

  • Swim metrics: ActivitySummary.poolLengthMeters/.numActiveLengths/.swimStroke/.avgStrokeCount, Lap.numActiveLengths/.swimStroke, the SwimStroke enum, plus ActivitySummary.subSport and .totalCycles.
  • Strength-training sets: FIT set messages parse into RawActivity.sets as WorkoutSet (type, repetitions, weight, exercise category; raw id kept for round-trips).
  • Point-level editing on RawEditor: insertPoint, deletePointAt, updatePoint, deleteRange, insertPause, removePause, all with lap/set boundary clipping; shiftTime now also shifts sets.
  • Batch import: ActivityFiles.loadBatch(sources) with per-file error capture, onProgress, and stopOnError.
  • Structured validation diagnostics: ValidationDiagnostic (stable code, suggestedFix, priority) on every validation result; ParseDiagnostic gains suggestedFix/priority; new validateDeviceMetadata() and validateChannels() validators; DiagnosticCategory documents the code prefixes.
  • Smaller additions: ActivityFiles.channelSamplesFrom() (channels → export-ready stream samples), Lap.copyWithoutSport(), ValidationResult.fromDiagnostics()/LapValidationResult.fromDiagnostics(), and the ParseFidelityMode enum (declared for 0.8.0; this release always uses pragmaticNormalize).

Changed

  • GPX diagnostics gpx.wpt.invalid_timestamp/gpx.rtept.invalid_timestamp now say the point is kept with an epoch fallback time (the previous "ignored" wording contradicted the behaviour; codes unchanged).

Fixed

  • FIT device metadata was read from the wrong fields: device_info was shifted by one (the serial number as the manufacturer, the hardware revision as the software version), and metadata from paired sensors overwrote the recording device's (a connected speed sensor could replace the head unit's name). product_name is now captured as ActivityDeviceMetadata.model.
  • Modern Garmin files no longer collapse to a handful of points: message definitions with more than 96 fields (routine on current devices) were rejected, and multi-byte array fields misaligned every following field in the message. Together, these could reduce a multi-hour ride to a few points and zero laps.
  • Real-device laps were silently dropped: the fallback record heuristic (0.6.0) misclassified normal lap messages as GPS records.
  • Valid legacy FIT files were flagged as corrupt: the trailer CRC must be computed over header + data, not data alone, and a header CRC of 0x0000 means "not computed" and is no longer compared. 12-byte-header files (including the official FIT SDK sample) and Edge 810-era files now validate cleanly, while genuinely corrupt files are still detected.
  • GPX temperatures are now written as decimals: atemp/wtemp were rounded to whole numbers although the schema types them as decimal (18.5 °C came back as 19).

v0.6.0

Choose a tag to compare

@thecloudseeker thecloudseeker released this 06 Jun 09:42

Breaking

  • ActivityFileFormat now includes csv and geojson; exhaustive switch statements must handle the new enum values.
  • Facade conversion/export helpers now validate by default (runValidation: true). Set runValidation: false to keep previous behavior.

Added

  • FIT regression suite for problematic real-world files.
  • Additional integration coverage for CSV/GeoJSON conversion and format detection.
  • New pipeline option models: FitCorruptionHandling and ActivityAutoFixOptions.
  • FIT Developer-Fields Support: decodes developer fields and exposes them as channels. (lib/src/parse/fit_parser.dart)
  • Extended FIT Message Coverage: handlers for messages 23, 34, 49. (lib/src/parse/fit_parser.dart)
  • Typed FIT Views: asFitView() typed accessors for session/lap/record data. (lib/src/fit/typed_views.dart)
  • Auto-Lap Heuristics: autoLapByDistance option generates laps by distance with sport defaults. (lib/src/api/pipeline_options.dart, lib/src/api/activity_files_facade.dart)

Changed

  • CI workflows were consolidated and hardened
  • CSV/GeoJSON are now first-class in the unified ActivityFileFormat path (detectFormat, load, convert, export, parser/encoder routing, CLI options).
  • Shared lap-boundary validation logic is reused between validateRawActivity() and RawEditor.validateLapBoundaries().
  • GPX parser: more robust TrackPoint parsing and TrackPointExtension handling

Fixed

  • Auto-Lap: regenerate placeholder laps and recompute distance before marking. (lib/src/api/activity_files_facade.dart)
  • Autofix diagnostic autofix.laps.auto_generated is emitted when laps are generated. (lib/src/api/activity_files_facade.dart)
  • FIT parser now performs best-effort extraction for problematic FIT variants by applying in-stream local-definition updates and bounded timestamp recovery (fit.record.recovered_timestamp) instead of returning empty output.
  • GeoJSON parser now uses a deterministic UTC-epoch fallback timestamp for points without timestamp instead of runtime-dependent DateTime.now().
  • TCX parser cache lifecycle is now scoped with weak-key caching to avoid retaining parsed XML documents.
  • ActivityFiles.splitBySport() now preserves lap metadata fields (e.g. avg/max metrics, calories, FIT event fields) while removing per-lap sport in split outputs.
  • strictFitIntegrity now throws the same detailed troubleshooting FormatException across direct load and stream pipeline paths.

v0.5.1

Choose a tag to compare

@thecloudseeker thecloudseeker released this 08 May 11:05

Changed

  • Upgraded dependencies
  • Minor Fixes

v0.5.0

Choose a tag to compare

@thecloudseeker thecloudseeker released this 12 Apr 10:52

Migration

  • Upgrading? This version mitigates the 0.4.0 breaking changes: the 64MB payload limit is now configurable per-call via maxPayloadBytes and file paths work with allowFilePaths: true or by passing File objects.

Added

  • CSV & GeoJSON Support: Full import/export for CSV and GeoJSON formats with roundtrip testing.
  • Public API Methods: Facade methods for CSV/GeoJSON: exportToCsv(), importFromCsv(), exportToGeojson(), exportToGeojsonPoints(), importFromGeojson().
  • Better Error Messages: All exceptions now include actionable recovery hints and troubleshooting steps.
  • Parser Performance: XML element caching and batch lookups reduce parsing time by 2-5x for large files (5000+ points).
  • Full Garmin TrackPointExtension v2 schema support: GPX parser and encoder now handle all v2 fields (wtemp, depth, speed, course, bearing) plus existing v1 fields (hr, cad, power, atemp) with corresponding channel types and ChannelSnapshot accessors.
  • Expanded FIT manufacturer database from 28 to 179 entries for comprehensive device identification.
  • Multi-sport activity support: TCX and FIT parsers handle triathlon files with multiple sport segments. New Lap.sport field enables per-lap sport tracking, and ActivityFiles.merge()/splitBySport() combine or divide multi-sport activities.
  • FIT session + lap stats are now parsed into ActivitySummary and additional Lap fields; extra record fields are surfaced as custom channels.
  • Added an artificial fixture generator (scripts/generate_artificial_fixtures.dart) that refreshes sample fixtures for tests and examples.
  • Lap boundary validation via RawEditor.validateLapBoundaries() for detecting timing mismatches.
  • Performance improvements in normalization and hot paths.
  • Parser optimization: through eliminating redundant XML traversals, removing case-insensitive string comparisons and caching child element lookups during trackpoint parsing. Large files (>5000 points) see the most benefit.

Changed

  • CSV parser normalizes line endings (\r\n\n).
  • Optimized sport string conversion with caching.
  • GPX/TCX parsing now uses batch element lookups instead of repeated traversals.
  • Lap class includes optional sport field; null values inherit from activity-level sport.
  • TCX parser sport mapping extended to include swimming.
  • Test suite reorganized into focused categories with shared fixtures. See doc/testing.md for details.
  • Payload limit now configurable per-call via optional maxPayloadBytes parameter (default: 64MB).

Fixed

  • FIT parser now fails gracefully (with diagnostics) instead of crashing when a data message references an unknown local definition (e.g. issue #2 user fixture). New diagnostics: fit.data.unknown_definition, fit.no_usable_data.