Releases: thecloudseeker/activity_files
Releases · thecloudseeker/activity_files
Release list
v0.7.6
- 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
StartTimepostdates it. validateLapBoundaries()no longer skips corruption checks on activities with no GPS points.- GeoJSON multi-Point
activity_typeis no longer locked to whichever feature parses first. - GeoJSON no longer strands real data in
additionalTrackswhen the first track feature is empty. IntegrityMode.silentno longer discards every diagnostic, only integrity ones.- Raising
maxPayloadBytesabove 64MB no longer silently emptiesActivityLoadResult.payload. merge()no longer skipsnormalize: truefor a single-activity list.EncoderOptionsis no longer a silent no-op for CSV/GeoJSON export; CSV lat/lon/elevation now default to fixed precision instead of rawtoString()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 suggestRawEditor.crop()instead.
v0.7.5
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 aStream<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 carryevents/lengths/setsforward, likeshiftTime()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_droppedinstead of dropping a channel with no warning. - GeoJSON multi-track FeatureCollections no longer keep only the first track; the rest are kept in
additionalTracksnow. - GeoJSON parsing now also reads
coordTimes(togeojson/Mapbox), not just our owncoordinateProperties.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_timeor 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 normalizesadditionalTracksdirectly 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_manufacturerare no longer dropped on parse.
v0.7.4
Fixed
exportAsync(useIsolate: true)no longer drops most ofRawActivitycrossing the isolate boundary.normalize: trueno 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: 0for 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
Fixed
- GeoJSON LineString export no longer fabricates
total_calories/total_stepsas0. - 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
Fixed
ActivityFiles.channelSamplesFrom()emitted second-resolution timestamps instead of milliseconds, which didn't match whatbuilderFromStreams()/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 ignoredautoFixwhen building from raw streams instead of a source file.
v0.7.1
Fixed
- No changes to the package itself
- GitHub workflow fix (fixes automated publishing to pub.dev)
v0.7.0
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 primaryRawActivity; the rest are stored inRawActivity.additionalTracks. Single-track encoders (TCX/FIT/CSV/GeoJSON) merge them automatically via the newRawActivity.flattened(), so conversions keep every point. Butactivity.pointsafter loading a multi-track GPX now contains only the first track. - Default normalization now repairs sentinel values:
RawEditor.trimInvalid()(run byconvert/exportwhennormalize: true, the default) removes Null Island points (lat/lon ≈ 0) and clears elevations ≤ −499 m while keeping the point. Repairs are reported viaRawEditor.repairDiagnosticsand in conversion/export diagnostics (repaired.sentinel_coords_removed,repaired.sentinel_elevation_cleared). Passnormalize: falseto opt out. ValidationResult.isValid/LapValidationResult.isValidnow derive from the structureddiagnosticslist as well as the legacyerrorslist.
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:TPXWatts/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, theSwimStrokeenum, plusActivitySummary.subSportand.totalCycles. - Strength-training sets: FIT set messages parse into
RawActivity.setsasWorkoutSet(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;shiftTimenow also shifts sets. - Batch import:
ActivityFiles.loadBatch(sources)with per-file error capture,onProgress, andstopOnError. - Structured validation diagnostics:
ValidationDiagnostic(stablecode,suggestedFix,priority) on every validation result;ParseDiagnosticgainssuggestedFix/priority; newvalidateDeviceMetadata()andvalidateChannels()validators;DiagnosticCategorydocuments the code prefixes. - Smaller additions:
ActivityFiles.channelSamplesFrom()(channels → export-ready stream samples),Lap.copyWithoutSport(),ValidationResult.fromDiagnostics()/LapValidationResult.fromDiagnostics(), and theParseFidelityModeenum (declared for 0.8.0; this release always usespragmaticNormalize).
Changed
- GPX diagnostics
gpx.wpt.invalid_timestamp/gpx.rtept.invalid_timestampnow 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_nameis now captured asActivityDeviceMetadata.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/wtempwere rounded to whole numbers although the schema types them as decimal (18.5 °C came back as 19).
v0.6.0
Breaking
ActivityFileFormatnow includescsvandgeojson; exhaustiveswitchstatements must handle the new enum values.- Facade conversion/export helpers now validate by default (
runValidation: true). SetrunValidation: falseto 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:
FitCorruptionHandlingandActivityAutoFixOptions. - 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:
autoLapByDistanceoption 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
ActivityFileFormatpath (detectFormat,load,convert,export, parser/encoder routing, CLI options). - Shared lap-boundary validation logic is reused between
validateRawActivity()andRawEditor.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_generatedis 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
timestampinstead of runtime-dependentDateTime.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.strictFitIntegritynow throws the same detailed troubleshootingFormatExceptionacross direct load and stream pipeline paths.
v0.5.1
Changed
- Upgraded dependencies
- Minor Fixes
v0.5.0
Migration
- Upgrading? This version mitigates the 0.4.0 breaking changes: the 64MB payload limit is now configurable per-call via
maxPayloadBytesand file paths work withallowFilePaths: trueor by passingFileobjects.
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 andChannelSnapshotaccessors. - 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.sportfield enables per-lap sport tracking, andActivityFiles.merge()/splitBySport()combine or divide multi-sport activities. - FIT session + lap stats are now parsed into
ActivitySummaryand additionalLapfields; 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.
Lapclass includes optionalsportfield; 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.mdfor details. - Payload limit now configurable per-call via optional
maxPayloadBytesparameter (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.