diff --git a/flutter_readium_platform_interface/CHANGELOG.md b/flutter_readium_platform_interface/CHANGELOG.md index c2fc0d08..3ac847cc 100644 --- a/flutter_readium_platform_interface/CHANGELOG.md +++ b/flutter_readium_platform_interface/CHANGELOG.md @@ -5,6 +5,12 @@ Format follows [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). ## Unreleased +### Fixed + +- `ReadiumTimebasedState.fromJson()` now tolerates audiobook terminal locators + that omit `progression` / `totalProgression`, preserving the locator while + normalizing ended-state progress to `1.0` instead of crashing on Android. + ## [0.3.3] - 2026-08-04 ## [0.3.2] - 2026-08-03 diff --git a/flutter_readium_platform_interface/lib/src/shared/publication/locator.dart b/flutter_readium_platform_interface/lib/src/shared/publication/locator.dart index c214a45a..72214de0 100644 --- a/flutter_readium_platform_interface/lib/src/shared/publication/locator.dart +++ b/flutter_readium_platform_interface/lib/src/shared/publication/locator.dart @@ -21,13 +21,7 @@ import 'link.dart'; const int _emptyIntValue = -1; const double _emptyDoubleValue = -1; -extension IntCheck on int? { - int? check(int? defaultValue) => (this == _emptyIntValue) ? defaultValue : this; -} - -extension DoubleNullableCheck on double? { - double? check(double? defaultValue) => (this == _emptyDoubleValue) ? defaultValue : this; - +extension DoubleNullableRound on double? { /// Ensure that this double? is within [epsilon] of [defaultValue], otherwise return this double? (or defaultValue if this is null). double roundToIfCloseTo( double defaultValue, { @@ -183,9 +177,9 @@ class Locator extends AdditionalProperties with Equatable implements JSONable { }) => copyWith( locations: (locations ?? Locations()).copyWith( fragments: fragments ?? locations?.fragments, - progression: progression.check(locations?.progression), - position: position.check(locations?.position), - totalProgression: totalProgression.check(locations?.totalProgression), + progression: progression == _emptyDoubleValue ? locations?.progression : progression, + position: position == _emptyIntValue ? locations?.position : position, + totalProgression: totalProgression == _emptyDoubleValue ? locations?.totalProgression : totalProgression, additionalProperties: otherLocations ?? locations?.additionalProperties, ), ); @@ -326,9 +320,9 @@ class Locations extends AdditionalProperties with Equatable implements JSONable ..removeWhere((key, value) => value == null); return Locations( - progression: progression.check(this.progression), - position: position.check(this.position), - totalProgression: totalProgression.check(this.totalProgression), + progression: progression == _emptyDoubleValue ? this.progression : progression, + position: position == _emptyIntValue ? this.position : position, + totalProgression: totalProgression == _emptyDoubleValue ? this.totalProgression : totalProgression, fragments: fragments ?? this.fragments, cssSelector: cssSelector ?? this.cssSelector, domRange: domRange ?? this.domRange, diff --git a/flutter_readium_platform_interface/test/models_test.dart b/flutter_readium_platform_interface/test/models_test.dart index ac44010a..018f9a2c 100644 --- a/flutter_readium_platform_interface/test/models_test.dart +++ b/flutter_readium_platform_interface/test/models_test.dart @@ -255,6 +255,29 @@ void main() { }); }); + group('ReadiumTimebasedState', () { + test('fromJson normalizes an ended locator with partial location data', () { + final state = ReadiumTimebasedState.fromJson({ + 'state': 'ended', + 'currentLocator': { + 'href': 'last.mp3', + 'type': 'audio/mpeg', + 'locations': { + 'position': 3, + 'fragments': ['t=123.45'], + }, + }, + }); + + expect(state.state, TimebasedState.ended); + expect(state.currentLocator?.href, 'last.mp3'); + expect(state.currentLocator?.locations?.position, 3); + expect(state.currentLocator?.locations?.fragments, ['t=123.45']); + expect(state.currentLocator?.locations?.progression, 1.0); + expect(state.currentLocator?.locations?.totalProgression, 1.0); + }); + }); + // --------------------------------------------------------------------------- // ReadiumReaderStatus enum // ---------------------------------------------------------------------------