Skip to content

Commit ce1c73e

Browse files
authored
Fix EPUB currentLocator update when positions are missing (#119)
1 parent 9954600 commit ce1c73e

File tree

9 files changed

+322
-35
lines changed

9 files changed

+322
-35
lines changed

CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,10 @@ All notable changes to this project will be documented in this file. Take a look
88

99
### Added
1010

11+
#### Shared
12+
13+
* Add a new `Locator.Builder` object to progressively construct an immutable `Locator` object.
14+
1115
#### Navigator
1216

1317
* Improved Javascript support in the EPUB navigator:

readium/navigator/src/main/java/org/readium/r2/navigator/epub/EpubNavigatorFragment.kt

Lines changed: 18 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,7 @@ import org.readium.r2.shared.publication.presentation.presentation
5454
import org.readium.r2.shared.publication.services.isRestricted
5555
import org.readium.r2.shared.publication.services.positionsByReadingOrder
5656
import org.readium.r2.shared.util.launchWebBrowser
57+
import org.readium.r2.shared.util.mediatype.MediaType
5758
import kotlin.math.ceil
5859
import kotlin.reflect.KClass
5960

@@ -342,7 +343,7 @@ class EpubNavigatorFragment private constructor(
342343

343344
resourcePager.adapter = adapter
344345

345-
if (publication.metadata.presentation.layout == EpubLayout.REFLOWABLE) {
346+
if (publication.metadata.presentation.layout != EpubLayout.FIXED) {
346347
pendingLocator = locator
347348
setCurrent(resourcesSingle)
348349
} else {
@@ -732,19 +733,26 @@ class EpubNavigatorFragment private constructor(
732733
webView.updateCurrentItem()
733734

734735
val resource = publication.readingOrder[resourcePager.currentItem]
735-
val progression = webView.progression.coerceIn(0.0, 1.0)
736-
val positions = publication.positionsByResource[resource.href]?.takeIf { it.isNotEmpty() }
737-
?: return@launch
736+
val builder = Locator.Builder(
737+
href = resource.href,
738+
type = resource.type ?: MediaType.XHTML.toString(),
739+
)
738740

739-
val positionIndex = ceil(progression * (positions.size - 1)).toInt()
740-
if (!positions.indices.contains(positionIndex)) {
741-
return@launch
741+
val progression = webView.progression.coerceIn(0.0, 1.0)
742+
builder.locations.progression = progression
743+
744+
val positions = publication.positionsByResource[resource.href]
745+
?.takeIf { it.isNotEmpty() }
746+
if (positions != null) {
747+
val index = ceil(progression * (positions.size - 1)).toInt()
748+
positions.getOrNull(index)
749+
?.let { builder.merge(it) }
742750
}
743751

744-
val locator = positions[positionIndex]
745-
.copy(title = tableOfContentsTitleByHref[resource.href])
746-
.copyWithLocations(progression = progression)
752+
tableOfContentsTitleByHref[resource.href]
753+
?.let { builder.title = it }
747754

755+
val locator = builder.build()
748756
_currentLocator.value = locator
749757

750758
// Deprecated notifications

readium/shared/src/main/java/org/readium/r2/shared/publication/Locator.kt

Lines changed: 130 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ import org.readium.r2.shared.util.logging.WarningLogger
2121
import org.readium.r2.shared.util.logging.log
2222

2323
/**
24-
* Provides a precise location in a publication in a format that can be stored and shared.
24+
* Represents a precise location in a publication in a format that can be stored and shared.
2525
*
2626
* There are many different use cases for locators:
2727
* - getting back to the last position in a publication
@@ -30,7 +30,7 @@ import org.readium.r2.shared.util.logging.log
3030
* - search results
3131
* - human-readable (and shareable) reference in a publication
3232
*
33-
* https://github.com/readium/architecture/tree/master/locators
33+
* https://readium.org/architecture/models/locators/
3434
*/
3535
@Parcelize
3636
data class Locator(
@@ -41,6 +41,45 @@ data class Locator(
4141
val text: Text = Text()
4242
) : JSONable, Parcelable {
4343

44+
/**
45+
* Builder for a [Locator] object.
46+
*/
47+
data class Builder(
48+
var href: String,
49+
var type: String,
50+
var title: String? = null,
51+
var locations: Locations.Builder = Locations.Builder(),
52+
var text: Text.Builder = Text.Builder()
53+
) {
54+
constructor(locator: Locator): this(
55+
href = locator.href,
56+
type = locator.type,
57+
title = locator.title,
58+
locations = Locations.Builder(locator.locations),
59+
text = Text.Builder(locator.text),
60+
)
61+
62+
/**
63+
* Merges the given [locator], replacing any non null properties.
64+
*/
65+
fun merge(locator: Locator): Builder {
66+
href = locator.href
67+
type = locator.type
68+
locator.title?.let { title = it }
69+
locations.merge(locator.locations)
70+
text.merge(locator.text)
71+
return this
72+
}
73+
74+
fun build(): Locator = Locator(
75+
href = href,
76+
type = type,
77+
title = title,
78+
locations = locations.build(),
79+
text = text.build()
80+
)
81+
}
82+
4483
/**
4584
* One or more alternative expressions of the location.
4685
* https://github.com/readium/architecture/tree/master/models/locators#the-location-object
@@ -61,6 +100,51 @@ data class Locator(
61100
val otherLocations: @WriteWith<JSONParceler> Map<String, Any> = emptyMap()
62101
) : JSONable, Parcelable {
63102

103+
/**
104+
* Builder for a [Locations] object.
105+
*/
106+
data class Builder(
107+
var fragments: MutableList<String> = mutableListOf(),
108+
var progression: Double? = null,
109+
var position: Int? = null,
110+
var totalProgression: Double? = null,
111+
var otherLocations: MutableMap<String, Any> = mutableMapOf()
112+
) {
113+
constructor(locations: Locations) : this(
114+
fragments = locations.fragments.toMutableList(),
115+
progression = locations.progression,
116+
position = locations.position,
117+
totalProgression = locations.totalProgression,
118+
otherLocations = locations.otherLocations.toMutableMap()
119+
)
120+
121+
fun merge(locations: Locations): Builder {
122+
locations.progression?.let { progression = it }
123+
locations.position?.let { position = it }
124+
locations.totalProgression?.let { totalProgression = it }
125+
126+
if (locations.fragments.isNotEmpty()) {
127+
fragments = (locations.fragments + fragments)
128+
.distinct()
129+
.toMutableList()
130+
}
131+
132+
for ((k, v) in locations.otherLocations) {
133+
otherLocations[k] = v
134+
}
135+
136+
return this
137+
}
138+
139+
fun build(): Locations = Locations(
140+
fragments = fragments.toList(),
141+
progression = progression,
142+
position = position,
143+
totalProgression = totalProgression,
144+
otherLocations = otherLocations.toMap()
145+
)
146+
}
147+
64148
override fun toJSON() = JSONObject(otherLocations).apply {
65149
putIfNotEmpty("fragments", fragments)
66150
put("progression", progression)
@@ -123,6 +207,38 @@ data class Locator(
123207
val after: String? = null
124208
) : JSONable, Parcelable {
125209

210+
/**
211+
* Builder for a [Text] object.
212+
*/
213+
data class Builder(
214+
var before: String? = null,
215+
var highlight: String? = null,
216+
var after: String? = null
217+
) {
218+
constructor(text: Text) : this(
219+
before = text.before,
220+
highlight = text.highlight,
221+
after = text.after,
222+
)
223+
224+
fun merge(text: Text): Builder {
225+
// It doesn't make sense to merge partially a [Text] object, as the [before],
226+
// [highlight] and [after] properties are related to each other.
227+
if (text.before != null || text.highlight != null || text.after != null) {
228+
before = text.before
229+
highlight = text.highlight
230+
after = text.after
231+
}
232+
return this
233+
}
234+
235+
fun build(): Text = Text(
236+
before = before,
237+
highlight = highlight,
238+
after = after
239+
)
240+
}
241+
126242
override fun toJSON() = JSONObject().apply {
127243
put("before", before)
128244
put("highlight", highlight)
@@ -136,11 +252,21 @@ data class Locator(
136252
highlight = json?.optNullableString("highlight"),
137253
after = json?.optNullableString("after")
138254
)
139-
140255
}
141-
142256
}
143257

258+
/**
259+
* Makes a copy of the `Locator` after modifying its properties using a `Builder`.
260+
*
261+
* ```
262+
* locator.copy {
263+
* locations.progression = 0.5
264+
* }
265+
* ```
266+
*/
267+
fun copy(build: Builder.() -> Unit): Locator =
268+
Builder(this).apply(build).build()
269+
144270
/**
145271
* Shortcut to get a copy of the [Locator] with different [Locations] sub-properties.
146272
*/

0 commit comments

Comments
 (0)