From 6371367f9907acfaa33c02e77f7ea3c0287fccaa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mickae=CC=88l=20Menu?= Date: Wed, 13 May 2020 12:14:49 +0200 Subject: [PATCH 01/11] Add basic PDF navigator --- r2-navigator/build.gradle | 7 +++ r2-navigator/src/main/AndroidManifest.xml | 3 + .../r2/navigator/NavigatorFragmentFactory.kt | 25 ++++++++ .../r2/navigator/extensions/Publication.kt | 26 +++++++++ .../r2/navigator/pdf/PdfNavigatorFragment.kt | 57 +++++++++++++++++++ .../readium/r2/navigator/pdf/R2PdfActivity.kt | 34 +++++++++++ .../src/main/res/layout/activity_r2_pdf.xml | 13 +++++ 7 files changed, 165 insertions(+) create mode 100644 r2-navigator/src/main/java/org/readium/r2/navigator/NavigatorFragmentFactory.kt create mode 100644 r2-navigator/src/main/java/org/readium/r2/navigator/extensions/Publication.kt create mode 100644 r2-navigator/src/main/java/org/readium/r2/navigator/pdf/PdfNavigatorFragment.kt create mode 100644 r2-navigator/src/main/java/org/readium/r2/navigator/pdf/R2PdfActivity.kt create mode 100644 r2-navigator/src/main/res/layout/activity_r2_pdf.xml diff --git a/r2-navigator/build.gradle b/r2-navigator/build.gradle index f7332dbb..af39ded3 100644 --- a/r2-navigator/build.gradle +++ b/r2-navigator/build.gradle @@ -62,12 +62,19 @@ dependencies { implementation 'org.zeroturnaround:zt-zip:1.13' implementation 'org.jsoup:jsoup:1.10.3' + implementation 'com.github.barteksc:android-pdf-viewer:2.8.2' testImplementation 'junit:junit:4.12' androidTestImplementation 'androidx.test:runner:1.2.0' androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0' testImplementation "org.jetbrains.kotlin:kotlin-test-junit:$kotlin_version" + // Lifecycle + implementation "androidx.lifecycle:lifecycle-livedata-ktx:2.2.0" + implementation "androidx.lifecycle:lifecycle-runtime-ktx:2.2.0" + implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:2.2.0" + implementation "androidx.lifecycle:lifecycle-viewmodel-savedstate:2.2.0" + // ChrisBane/PhotoView ( for the Zoom handling ) implementation 'com.github.chrisbanes:PhotoView:2.1.4' } diff --git a/r2-navigator/src/main/AndroidManifest.xml b/r2-navigator/src/main/AndroidManifest.xml index 4e9a901c..8104901d 100644 --- a/r2-navigator/src/main/AndroidManifest.xml +++ b/r2-navigator/src/main/AndroidManifest.xml @@ -29,6 +29,9 @@ + diff --git a/r2-navigator/src/main/java/org/readium/r2/navigator/NavigatorFragmentFactory.kt b/r2-navigator/src/main/java/org/readium/r2/navigator/NavigatorFragmentFactory.kt new file mode 100644 index 00000000..03ede444 --- /dev/null +++ b/r2-navigator/src/main/java/org/readium/r2/navigator/NavigatorFragmentFactory.kt @@ -0,0 +1,25 @@ +/* + * Module: r2-navigator-kotlin + * Developers: Mickaël Menu + * + * Copyright (c) 2020. Readium Foundation. All rights reserved. + * Use of this source code is governed by a BSD-style license which is detailed in the + * LICENSE file present in the project repository where this source code is maintained. + */ + +package org.readium.r2.navigator + +import androidx.fragment.app.Fragment +import androidx.fragment.app.FragmentFactory +import org.readium.r2.navigator.pdf.PdfNavigatorFragment +import org.readium.r2.shared.publication.Publication + +class NavigatorFragmentFactory(private val publication: Publication) : FragmentFactory() { + + override fun instantiate(classLoader: ClassLoader, className: String): Fragment = + when (className) { + PdfNavigatorFragment::class.java.name -> PdfNavigatorFragment(publication) + else -> super.instantiate(classLoader, className) + } + +} \ No newline at end of file diff --git a/r2-navigator/src/main/java/org/readium/r2/navigator/extensions/Publication.kt b/r2-navigator/src/main/java/org/readium/r2/navigator/extensions/Publication.kt new file mode 100644 index 00000000..ffaf268d --- /dev/null +++ b/r2-navigator/src/main/java/org/readium/r2/navigator/extensions/Publication.kt @@ -0,0 +1,26 @@ +/* + * Module: r2-navigator-kotlin + * Developers: Mickaël Menu + * + * Copyright (c) 2020. Readium Foundation. All rights reserved. + * Use of this source code is governed by a BSD-style license which is detailed in the + * LICENSE file present in the project repository where this source code is maintained. + */ + +package org.readium.r2.navigator.extensions + +import org.readium.r2.shared.extensions.tryOrNull +import org.readium.r2.shared.publication.Link +import org.readium.r2.shared.publication.Publication +import java.net.URL + +/** Computes an absolute URL to the given [link]. */ +internal fun Publication.urlTo(link: Link): URL? { + val baseUrl = this.baseUrl?.toString()?.removeSuffix("/") + val urlString = if (baseUrl != null && link.href.startsWith("/")) { + "$baseUrl${link.href}" + } else { + link.href + } + return tryOrNull { URL(urlString) } +} diff --git a/r2-navigator/src/main/java/org/readium/r2/navigator/pdf/PdfNavigatorFragment.kt b/r2-navigator/src/main/java/org/readium/r2/navigator/pdf/PdfNavigatorFragment.kt new file mode 100644 index 00000000..88b72194 --- /dev/null +++ b/r2-navigator/src/main/java/org/readium/r2/navigator/pdf/PdfNavigatorFragment.kt @@ -0,0 +1,57 @@ +/* + * Module: r2-navigator-kotlin + * Developers: Mickaël Menu + * + * Copyright (c) 2020. Readium Foundation. All rights reserved. + * Use of this source code is governed by a BSD-style license which is detailed in the + * LICENSE file present in the project repository where this source code is maintained. + */ + +package org.readium.r2.navigator.pdf + +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import androidx.fragment.app.Fragment +import androidx.lifecycle.lifecycleScope +import com.github.barteksc.pdfviewer.PDFView +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.launch +import kotlinx.coroutines.withContext +import org.readium.r2.navigator.extensions.urlTo +import org.readium.r2.shared.publication.Link +import org.readium.r2.shared.publication.Publication +import timber.log.Timber + +class PdfNavigatorFragment(private val publication: Publication) : Fragment() { + + private lateinit var pdfView: PDFView + + override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { + val context = requireContext() + pdfView = PDFView(context, null) + + lifecycleScope.launch { + loadLink(publication.readingOrder.firstOrNull()) + } + + return pdfView + } + + private suspend fun loadLink(link: Link?) { + link ?: return + val url = publication.urlTo(link) ?: return + + // Android forbids network requests on the main thread by default, so we + // have to do that in the IO dispatcher. + withContext(Dispatchers.IO) { + try { + pdfView.fromStream(url.openStream()).load() + } catch (e: Exception) { + Timber.e(e) + } + } + } + +} diff --git a/r2-navigator/src/main/java/org/readium/r2/navigator/pdf/R2PdfActivity.kt b/r2-navigator/src/main/java/org/readium/r2/navigator/pdf/R2PdfActivity.kt new file mode 100644 index 00000000..af889585 --- /dev/null +++ b/r2-navigator/src/main/java/org/readium/r2/navigator/pdf/R2PdfActivity.kt @@ -0,0 +1,34 @@ +/* + * Module: r2-navigator-kotlin + * Developers: Mickaël Menu + * + * Copyright (c) 2020. Readium Foundation. All rights reserved. + * Use of this source code is governed by a BSD-style license which is detailed in the + * LICENSE file present in the project repository where this source code is maintained. + */ + +package org.readium.r2.navigator.pdf + +import android.os.Bundle +import androidx.appcompat.app.AppCompatActivity +import org.readium.r2.navigator.NavigatorFragmentFactory +import org.readium.r2.navigator.R +import org.readium.r2.shared.extensions.getPublication +import org.readium.r2.shared.publication.Publication + + +class R2PdfActivity : AppCompatActivity() { + + private lateinit var publication: Publication + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + + publication = intent.getPublication(this) + + supportFragmentManager.fragmentFactory = NavigatorFragmentFactory(publication) + + setContentView(R.layout.activity_r2_pdf) + } + +} \ No newline at end of file diff --git a/r2-navigator/src/main/res/layout/activity_r2_pdf.xml b/r2-navigator/src/main/res/layout/activity_r2_pdf.xml new file mode 100644 index 00000000..9386f706 --- /dev/null +++ b/r2-navigator/src/main/res/layout/activity_r2_pdf.xml @@ -0,0 +1,13 @@ + + + + + + \ No newline at end of file From 8f06b225700c7882a84f21845c845be63696255d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mickae=CC=88l=20Menu?= Date: Wed, 13 May 2020 16:23:23 +0200 Subject: [PATCH 02/11] Remove unused Navigator APIs Deprecate Navigator.currentLocation in favor of currentLocator using LiveData Add the PdfNavigatorViewModel --- r2-navigator/build.gradle | 21 ++-- .../org/readium/r2/navigator/IR2Activity.kt | 63 ++---------- .../r2/navigator/NavigatorFragmentFactory.kt | 19 +++- .../audiobook/R2AudiobookActivity.kt | 45 +++++---- .../readium/r2/navigator/cbz/R2CbzActivity.kt | 42 ++++---- .../r2/navigator/epub/R2EpubActivity.kt | 41 ++++---- .../r2/navigator/extensions/Locator.kt | 29 ++++++ .../r2/navigator/extensions/Publication.kt | 11 +-- .../r2/navigator/pdf/PdfNavigatorFragment.kt | 75 ++++++++++---- .../r2/navigator/pdf/PdfNavigatorViewModel.kt | 99 +++++++++++++++++++ .../readium/r2/navigator/pdf/R2PdfActivity.kt | 1 - 11 files changed, 299 insertions(+), 147 deletions(-) create mode 100644 r2-navigator/src/main/java/org/readium/r2/navigator/extensions/Locator.kt create mode 100644 r2-navigator/src/main/java/org/readium/r2/navigator/pdf/PdfNavigatorViewModel.kt diff --git a/r2-navigator/build.gradle b/r2-navigator/build.gradle index af39ded3..93049d41 100644 --- a/r2-navigator/build.gradle +++ b/r2-navigator/build.gradle @@ -25,6 +25,9 @@ android { sourceCompatibility 1.8 targetCompatibility 1.8 } + kotlinOptions { + jvmTarget = "1.8" + } buildTypes { release { minifyEnabled false @@ -42,17 +45,10 @@ dependencies { } else { implementation "com.github.readium:r2-shared-kotlin:1.1.6" } - implementation "androidx.appcompat:appcompat:1.2.0-beta01" - implementation 'androidx.constraintlayout:constraintlayout:1.1.3' - implementation 'androidx.webkit:webkit:1.1.0' - implementation "androidx.legacy:legacy-support-v4:1.0.0" implementation "com.google.android.material:material:1.2.0-alpha03" - implementation "androidx.recyclerview:recyclerview:1.1.0" implementation 'joda-time:joda-time:2.9.9' - implementation "androidx.legacy:legacy-support-core-ui:1.0.0" implementation 'com.duolingo.open:rtl-viewpager:1.0.3' - implementation 'androidx.viewpager2:viewpager2:1.0.0' implementation 'com.jakewharton.timber:timber:4.7.1' @@ -69,11 +65,20 @@ dependencies { androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0' testImplementation "org.jetbrains.kotlin:kotlin-test-junit:$kotlin_version" - // Lifecycle + implementation "androidx.activity:activity-ktx:1.1.0" + implementation "androidx.appcompat:appcompat:1.2.0-beta01" + implementation 'androidx.constraintlayout:constraintlayout:1.1.3' + implementation "androidx.core:core-ktx:1.2.0" + implementation "androidx.fragment:fragment-ktx:1.2.4" + implementation "androidx.legacy:legacy-support-core-ui:1.0.0" + implementation "androidx.legacy:legacy-support-v4:1.0.0" implementation "androidx.lifecycle:lifecycle-livedata-ktx:2.2.0" implementation "androidx.lifecycle:lifecycle-runtime-ktx:2.2.0" implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:2.2.0" implementation "androidx.lifecycle:lifecycle-viewmodel-savedstate:2.2.0" + implementation "androidx.recyclerview:recyclerview:1.1.0" + implementation 'androidx.viewpager2:viewpager2:1.0.0' + implementation 'androidx.webkit:webkit:1.1.0' // ChrisBane/PhotoView ( for the Zoom handling ) implementation 'com.github.chrisbanes:PhotoView:2.1.4' diff --git a/r2-navigator/src/main/java/org/readium/r2/navigator/IR2Activity.kt b/r2-navigator/src/main/java/org/readium/r2/navigator/IR2Activity.kt index 24e64de9..4729d5e8 100644 --- a/r2-navigator/src/main/java/org/readium/r2/navigator/IR2Activity.kt +++ b/r2-navigator/src/main/java/org/readium/r2/navigator/IR2Activity.kt @@ -11,12 +11,12 @@ package org.readium.r2.navigator import android.content.SharedPreferences import android.view.View +import androidx.lifecycle.LiveData import org.readium.r2.navigator.pager.R2ViewPager import org.readium.r2.shared.publication.Link import org.readium.r2.shared.publication.Locator import org.readium.r2.shared.publication.Publication import org.readium.r2.shared.publication.ReadingProgression -import java.net.URL interface IR2Activity { @@ -51,59 +51,25 @@ interface IR2TTS { interface Navigator { - val currentLocation: Locator? - fun go(locator: Locator, animated: Boolean, completion: () -> Unit): Boolean - fun go(link: Link, animated: Boolean, completion: () -> Unit): Boolean - fun goForward(animated: Boolean, completion: () -> Unit): Boolean - fun goBackward(animated: Boolean, completion: () -> Unit): Boolean -} - -fun Navigator.go(locator: Locator, animated: Boolean = false, completion: () -> Unit = {}): Boolean = - go(locator = locator, animated = animated, completion = completion) - -fun Navigator.go(link: Link, animated: Boolean = false, completion: () -> Unit = {}): Boolean = - go(link = link, animated = animated, completion = completion) + val currentLocator: LiveData -fun Navigator.goForward(animated: Boolean = false, completion: () -> Unit = {}): Boolean = - goForward(animated = animated, completion = completion) - -fun Navigator.goBackward(animated: Boolean = false, completion: () -> Unit = {}): Boolean = - goBackward(animated = animated, completion = completion) + fun go(locator: Locator, animated: Boolean = false, completion: () -> Unit = {}): Boolean + fun go(link: Link, animated: Boolean = false, completion: () -> Unit = {}): Boolean + fun goForward(animated: Boolean = false, completion: () -> Unit = {}): Boolean + fun goBackward(animated: Boolean = false, completion: () -> Unit = {}): Boolean + @Deprecated("Use [currentLocator] instead", ReplaceWith("currentLocator.value")) + val currentLocation: Locator? get() = currentLocator.value +} interface NavigatorDelegate { + @Deprecated("Observe [currentLocator] instead") fun locationDidChange(navigator: Navigator? = null, locator: Locator) - - // present error message - fun presentError(navigator: Navigator? = null, error: NavigatorError) {} - - // present external url - fun presentExternalURL(navigator: Navigator? = null, url: URL) {} -} - - -//public fun NavigatorDelegate.navigator(navigator: Navigator, url: URL) { -// if (UIApplication.shared.canOpenURL(url)) { -// UIApplication.shared.openURL(url) -// } -//} - - -sealed class NavigatorError : Exception() { - object copyForbidden : NavigatorError() - - val errorDescription: String? - get() { - return when (this) { - is copyForbidden -> "NavigatorError.copyForbidden" - } - } } interface VisualNavigator : Navigator { - // val view: UIView val readingProgression: ReadingProgression fun goLeft(animated: Boolean, completion: () -> Unit): Boolean @@ -130,12 +96,3 @@ fun VisualNavigator.goRight(animated: Boolean = false, completion: () -> Unit = goBackward(animated = animated, completion = completion) } } - - -//public interface VisualNavigatorDelegate: NavigatorDelegate { -// fun navigator(navigator: VisualNavigator, point: CGPoint) -//} - -//public fun VisualNavigatorDelegate.navigator(navigator: VisualNavigator, point: CGPoint) {} - - diff --git a/r2-navigator/src/main/java/org/readium/r2/navigator/NavigatorFragmentFactory.kt b/r2-navigator/src/main/java/org/readium/r2/navigator/NavigatorFragmentFactory.kt index 03ede444..90664553 100644 --- a/r2-navigator/src/main/java/org/readium/r2/navigator/NavigatorFragmentFactory.kt +++ b/r2-navigator/src/main/java/org/readium/r2/navigator/NavigatorFragmentFactory.kt @@ -11,14 +11,29 @@ package org.readium.r2.navigator import androidx.fragment.app.Fragment import androidx.fragment.app.FragmentFactory +import androidx.lifecycle.ViewModel +import androidx.lifecycle.ViewModelProvider import org.readium.r2.navigator.pdf.PdfNavigatorFragment +import org.readium.r2.navigator.pdf.PdfNavigatorViewModel +import org.readium.r2.shared.publication.Locator import org.readium.r2.shared.publication.Publication -class NavigatorFragmentFactory(private val publication: Publication) : FragmentFactory() { +class NavigatorFragmentFactory( + private val publication: Publication, + private val initialLocator: Locator? = null +) : FragmentFactory() { override fun instantiate(classLoader: ClassLoader, className: String): Fragment = when (className) { - PdfNavigatorFragment::class.java.name -> PdfNavigatorFragment(publication) + PdfNavigatorFragment::class.java.name -> PdfNavigatorFragment(object : ViewModelProvider.Factory { + @Suppress("UNCHECKED_CAST") + override fun create(modelClass: Class): T { + if (!modelClass.isAssignableFrom(PdfNavigatorViewModel::class.java)) { + throw ClassCastException("Unknown ViewModel class: ${modelClass.name}") + } + return PdfNavigatorViewModel(publication, initialLocator) as T + } + }) else -> super.instantiate(classLoader, className) } diff --git a/r2-navigator/src/main/java/org/readium/r2/navigator/audiobook/R2AudiobookActivity.kt b/r2-navigator/src/main/java/org/readium/r2/navigator/audiobook/R2AudiobookActivity.kt index e0770c8a..a215b70a 100644 --- a/r2-navigator/src/main/java/org/readium/r2/navigator/audiobook/R2AudiobookActivity.kt +++ b/r2-navigator/src/main/java/org/readium/r2/navigator/audiobook/R2AudiobookActivity.kt @@ -10,11 +10,16 @@ import android.widget.SeekBar import androidx.appcompat.app.AppCompatActivity import androidx.core.content.ContextCompat import androidx.lifecycle.Lifecycle +import androidx.lifecycle.LiveData +import androidx.lifecycle.MutableLiveData import kotlinx.android.synthetic.main.activity_r2_audiobook.* import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers -import org.readium.r2.navigator.* -import org.readium.r2.navigator.BuildConfig.* +import org.readium.r2.navigator.BuildConfig.DEBUG +import org.readium.r2.navigator.IR2Activity +import org.readium.r2.navigator.NavigatorDelegate +import org.readium.r2.navigator.R +import org.readium.r2.navigator.VisualNavigator import org.readium.r2.shared.extensions.destroyPublication import org.readium.r2.shared.extensions.getPublication import org.readium.r2.shared.publication.* @@ -24,8 +29,11 @@ import kotlin.coroutines.CoroutineContext open class R2AudiobookActivity : AppCompatActivity(), CoroutineScope, IR2Activity, MediaPlayerCallback, VisualNavigator { - override val currentLocation: Locator? get() = - publication.readingOrder[currentResource].let { resource -> + override val currentLocator: LiveData get() = _currentLocator + private val _currentLocator = MutableLiveData(null) + + private fun notifyCurrentLocation() { + val locator = publication.readingOrder[currentResource].let { resource -> val progression = mediaPlayer ?.let { it.currentPosition / it.duration } ?: 0.0 @@ -44,6 +52,14 @@ open class R2AudiobookActivity : AppCompatActivity(), CoroutineScope, IR2Activit ) } + if (locator == currentLocator.value) { + return + } + + _currentLocator.postValue(locator) + navigatorDelegate?.locationDidChange(navigator = this, locator = locator) + } + override fun go(locator: Locator, animated: Boolean, completion: () -> Unit): Boolean { val resourceIndex = publication.readingOrder.indexOfFirstWithHref(locator.href) ?: return false @@ -135,9 +151,9 @@ open class R2AudiobookActivity : AppCompatActivity(), CoroutineScope, IR2Activit mediaPlayer?.goTo(currentResource) - currentLocation?.locations?.progression?.let { progression -> + currentLocator.value?.locations?.progression?.let { progression -> mediaPlayer?.seekTo(progression) - seekLocation = currentLocation?.locations + seekLocation = currentLocator.value?.locations isSeekNeeded = true } @@ -270,12 +286,7 @@ open class R2AudiobookActivity : AppCompatActivity(), CoroutineScope, IR2Activit seekBar!!.progress = startTime.toInt() - val resource = publication.readingOrder[currentResource] - val resourceHref = resource.href - val resourceType = resource.type ?: "" - - navigatorDelegate?.locationDidChange(locator = Locator(resourceHref, resourceType, publication.metadata.title, Locator.Locations(progression = seekBar!!.progress.toDouble()))) - + notifyCurrentLocation() } private var seekLocation: Locator.Locations? = null @@ -337,11 +348,7 @@ open class R2AudiobookActivity : AppCompatActivity(), CoroutineScope, IR2Activit TimeUnit.MILLISECONDS.toSeconds(startTime.toLong()) - TimeUnit.MINUTES.toSeconds(TimeUnit.MILLISECONDS.toMinutes(startTime.toLong()))) seekBar!!.progress = startTime.toInt() - val resource = publication.readingOrder[currentResource] - val resourceHref = resource.href - val resourceType = resource.type ?: "" - - navigatorDelegate?.locationDidChange(locator = Locator(resourceHref, resourceType, publication.metadata.title, Locator.Locations(progression = seekBar!!.progress.toDouble()))) + notifyCurrentLocation() Handler().postDelayed(this, 100) } @@ -375,9 +382,6 @@ open class R2AudiobookActivity : AppCompatActivity(), CoroutineScope, IR2Activit if (data != null) { val locator = data.getParcelableExtra("locator") as Locator - // Set the progression fetched - navigatorDelegate?.locationDidChange(locator = locator) - // href is the link to the page in the toc var href = locator.href @@ -403,6 +407,7 @@ open class R2AudiobookActivity : AppCompatActivity(), CoroutineScope, IR2Activit chapterView!!.text = publication.readingOrder[currentResource].title + notifyCurrentLocation() } } diff --git a/r2-navigator/src/main/java/org/readium/r2/navigator/cbz/R2CbzActivity.kt b/r2-navigator/src/main/java/org/readium/r2/navigator/cbz/R2CbzActivity.kt index 9831a4c7..7801860a 100644 --- a/r2-navigator/src/main/java/org/readium/r2/navigator/cbz/R2CbzActivity.kt +++ b/r2-navigator/src/main/java/org/readium/r2/navigator/cbz/R2CbzActivity.kt @@ -16,11 +16,16 @@ import android.content.SharedPreferences import android.os.Bundle import android.view.View import androidx.appcompat.app.AppCompatActivity +import androidx.lifecycle.LiveData +import androidx.lifecycle.MutableLiveData import androidx.viewpager.widget.ViewPager import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch -import org.readium.r2.navigator.* +import org.readium.r2.navigator.IR2Activity +import org.readium.r2.navigator.NavigatorDelegate +import org.readium.r2.navigator.R +import org.readium.r2.navigator.VisualNavigator import org.readium.r2.navigator.extensions.layoutDirectionIsRTL import org.readium.r2.navigator.pager.R2PagerAdapter import org.readium.r2.navigator.pager.R2ViewPager @@ -32,8 +37,19 @@ import kotlin.coroutines.CoroutineContext open class R2CbzActivity : AppCompatActivity(), CoroutineScope, IR2Activity, VisualNavigator { - override val currentLocation: Locator? - get() = publication.positions[resourcePager.currentItem] + override val currentLocator: LiveData get() = _currentLocator + private val _currentLocator = MutableLiveData(null) + + private fun notifyCurrentLocation() { + val locator = publication.positions[resourcePager.currentItem] + if (locator == currentLocator.value) { + return + } + + _currentLocator.postValue(locator) + navigatorDelegate?.locationDidChange(navigator = this, locator = locator) + } + override fun go(locator: Locator, animated: Boolean, completion: () -> Unit): Boolean { val resourceIndex = publication.readingOrder.indexOfFirstWithHref(locator.href) @@ -105,13 +121,10 @@ open class R2CbzActivity : AppCompatActivity(), CoroutineScope, IR2Activity, Vis resources = publication.readingOrder.map { it.href } - val navigator = this resourcePager.addOnPageChangeListener(object : ViewPager.SimpleOnPageChangeListener() { override fun onPageSelected(position: Int) { - val delegate = navigatorDelegate ?: return - val locator = currentLocation ?: return - delegate.locationDidChange(navigator = navigator, locator = locator) + notifyCurrentLocation() } }) @@ -144,10 +157,7 @@ open class R2CbzActivity : AppCompatActivity(), CoroutineScope, IR2Activity, Vis // OnPageChangeListener.onPageSelected is not called on the first page of the book, so we // trigger the locationDidChange event manually. - val navigator = this - currentLocation?.let { - navigatorDelegate?.locationDidChange(navigator = navigator, locator = it) - } + notifyCurrentLocation() } override fun nextResource(v: View?) { @@ -159,11 +169,8 @@ open class R2CbzActivity : AppCompatActivity(), CoroutineScope, IR2Activity, Vis // The view has LTR layout resourcePager.currentItem = resourcePager.currentItem + 1 } - val resource = publication.readingOrder[resourcePager.currentItem] - val resourceHref = resource.href - val resourceType = resource.type ?: "" - navigatorDelegate?.locationDidChange(locator = Locator(resourceHref, resourceType, publication.metadata.title)) + notifyCurrentLocation() } } @@ -176,11 +183,8 @@ open class R2CbzActivity : AppCompatActivity(), CoroutineScope, IR2Activity, Vis // The view has LTR layout resourcePager.currentItem = resourcePager.currentItem - 1 } - val resource = publication.readingOrder[resourcePager.currentItem] - val resourceHref = resource.href - val resourceType = resource.type ?: "" - navigatorDelegate?.locationDidChange(locator = Locator(resourceHref, resourceType, publication.metadata.title)) + notifyCurrentLocation() } } diff --git a/r2-navigator/src/main/java/org/readium/r2/navigator/epub/R2EpubActivity.kt b/r2-navigator/src/main/java/org/readium/r2/navigator/epub/R2EpubActivity.kt index 464b028f..23b60fe7 100644 --- a/r2-navigator/src/main/java/org/readium/r2/navigator/epub/R2EpubActivity.kt +++ b/r2-navigator/src/main/java/org/readium/r2/navigator/epub/R2EpubActivity.kt @@ -20,6 +20,8 @@ import android.util.DisplayMetrics import android.view.ActionMode import android.view.View import androidx.appcompat.app.AppCompatActivity +import androidx.lifecycle.LiveData +import androidx.lifecycle.MutableLiveData import androidx.viewpager.widget.ViewPager import kotlinx.coroutines.* import org.json.JSONException @@ -34,7 +36,10 @@ import org.readium.r2.shared.SCROLL_REF import org.readium.r2.shared.extensions.destroyPublication import org.readium.r2.shared.extensions.getPublication import org.readium.r2.shared.getAbsolute -import org.readium.r2.shared.publication.* +import org.readium.r2.shared.publication.Link +import org.readium.r2.shared.publication.Locator +import org.readium.r2.shared.publication.Publication +import org.readium.r2.shared.publication.ReadingProgression import org.readium.r2.shared.publication.epub.EpubLayout import org.readium.r2.shared.publication.presentation.presentation import java.net.URI @@ -404,21 +409,8 @@ open class R2EpubActivity : AppCompatActivity(), IR2Activity, IR2Selectable, IR2 private val currentFragment: R2EpubPageFragment? get() = r2PagerAdapter.mFragments.get(r2PagerAdapter.getItemId(resourcePager.currentItem)) as? R2EpubPageFragment - override val currentLocation: Locator? get() { - val resource = publication.readingOrder[resourcePager.currentItem] - val progression = currentFragment?.webView?.progression ?: 0.0 - val positions = publication.positionsByResource[resource.href] - ?: return null - val positionIndex = ceil(progression * (positions.size - 1)).toInt() - return positions[positionIndex] - .copyWithLocations(progression = progression) - } - - /** - * Last current location notified. - * Used to avoid sending twice the same location. - */ - private var notifiedCurrentLocation: Locator? = null + override val currentLocator: LiveData get() = _currentLocator + private val _currentLocator = MutableLiveData(null) /** * While scrolling we receive a lot of new current locations, so we use a coroutine job to @@ -432,13 +424,20 @@ open class R2EpubActivity : AppCompatActivity(), IR2Activity, IR2Selectable, IR2 debounceLocationNotificationJob = launch { delay(100L) - val delegate = navigatorDelegate ?: return@launch - val locator = currentLocation ?: return@launch - if (locator == notifiedCurrentLocation) { + val resource = publication.readingOrder[resourcePager.currentItem] + val progression = currentFragment?.webView?.progression ?: 0.0 + val positions = publication.positionsByResource[resource.href] + ?: return@launch + val positionIndex = ceil(progression * (positions.size - 1)).toInt() + val locator = positions[positionIndex] + .copyWithLocations(progression = progression) + + if (locator == currentLocator.value) { return@launch } - notifiedCurrentLocation = locator - delegate.locationDidChange(navigator = navigator, locator = locator) + + _currentLocator.postValue(locator) + navigatorDelegate?.locationDidChange(navigator = navigator, locator = locator) } } diff --git a/r2-navigator/src/main/java/org/readium/r2/navigator/extensions/Locator.kt b/r2-navigator/src/main/java/org/readium/r2/navigator/extensions/Locator.kt new file mode 100644 index 00000000..2bdc9749 --- /dev/null +++ b/r2-navigator/src/main/java/org/readium/r2/navigator/extensions/Locator.kt @@ -0,0 +1,29 @@ +/* + * Module: r2-navigator-kotlin + * Developers: Mickaël Menu + * + * Copyright (c) 2020. Readium Foundation. All rights reserved. + * Use of this source code is governed by a BSD-style license which is detailed in the + * LICENSE file present in the project repository where this source code is maintained. + */ + +package org.readium.r2.navigator.extensions + +import org.readium.r2.shared.publication.Locator +import java.util.* + +// FIXME: To add to r2-shared +internal val Locator.Locations.fragmentParameters: Map get() = + fragments + // Concatenates fragments together, after dropping any # + .map { it.removePrefix("#") } + .joinToString(separator = "&") + // Splits parameters + .split("&") + .map { it.split("=") } + // Only keep named parameters + .filter { it.size == 2 } + .associate { Pair(it[0].trim().toLowerCase(Locale.ROOT), it[1].trim()) } + +internal val Locator.Locations.page: Int? get() = + fragmentParameters["page"]?.toIntOrNull() diff --git a/r2-navigator/src/main/java/org/readium/r2/navigator/extensions/Publication.kt b/r2-navigator/src/main/java/org/readium/r2/navigator/extensions/Publication.kt index ffaf268d..df3ebdd9 100644 --- a/r2-navigator/src/main/java/org/readium/r2/navigator/extensions/Publication.kt +++ b/r2-navigator/src/main/java/org/readium/r2/navigator/extensions/Publication.kt @@ -10,17 +10,16 @@ package org.readium.r2.navigator.extensions import org.readium.r2.shared.extensions.tryOrNull -import org.readium.r2.shared.publication.Link import org.readium.r2.shared.publication.Publication import java.net.URL -/** Computes an absolute URL to the given [link]. */ -internal fun Publication.urlTo(link: Link): URL? { +/** Computes an absolute URL to the given HREF. */ +internal fun Publication.urlToHref(href: String): URL? { val baseUrl = this.baseUrl?.toString()?.removeSuffix("/") - val urlString = if (baseUrl != null && link.href.startsWith("/")) { - "$baseUrl${link.href}" + val urlString = if (baseUrl != null && href.startsWith("/")) { + "$baseUrl${href}" } else { - link.href + href } return tryOrNull { URL(urlString) } } diff --git a/r2-navigator/src/main/java/org/readium/r2/navigator/pdf/PdfNavigatorFragment.kt b/r2-navigator/src/main/java/org/readium/r2/navigator/pdf/PdfNavigatorFragment.kt index 88b72194..88513c70 100644 --- a/r2-navigator/src/main/java/org/readium/r2/navigator/pdf/PdfNavigatorFragment.kt +++ b/r2-navigator/src/main/java/org/readium/r2/navigator/pdf/PdfNavigatorFragment.kt @@ -14,44 +14,85 @@ import android.view.LayoutInflater import android.view.View import android.view.ViewGroup import androidx.fragment.app.Fragment +import androidx.fragment.app.viewModels +import androidx.lifecycle.LiveData +import androidx.lifecycle.Observer +import androidx.lifecycle.ViewModelProvider import androidx.lifecycle.lifecycleScope import com.github.barteksc.pdfviewer.PDFView import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch import kotlinx.coroutines.withContext -import org.readium.r2.navigator.extensions.urlTo +import org.readium.r2.navigator.Navigator +import org.readium.r2.navigator.pdf.PdfNavigatorViewModel.GoToLocationEvent import org.readium.r2.shared.publication.Link -import org.readium.r2.shared.publication.Publication +import org.readium.r2.shared.publication.Locator import timber.log.Timber -class PdfNavigatorFragment(private val publication: Publication) : Fragment() { +class PdfNavigatorFragment(viewModelFactory: ViewModelProvider.Factory) : Fragment(), Navigator { private lateinit var pdfView: PDFView + private val viewModel: PdfNavigatorViewModel by viewModels { viewModelFactory } + + private var currentHref: String? = null + override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { val context = requireContext() pdfView = PDFView(context, null) - lifecycleScope.launch { - loadLink(publication.readingOrder.firstOrNull()) - } + viewModel.goToLocation.observe(viewLifecycleOwner, Observer { event -> + event ?: return@Observer + + lifecycleScope.launch { + goTo(event) + viewModel.goToLocation.value = null + } + }) return pdfView } - private suspend fun loadLink(link: Link?) { - link ?: return - val url = publication.urlTo(link) ?: return - - // Android forbids network requests on the main thread by default, so we - // have to do that in the IO dispatcher. - withContext(Dispatchers.IO) { - try { - pdfView.fromStream(url.openStream()).load() - } catch (e: Exception) { - Timber.e(e) + private suspend fun goTo(event: GoToLocationEvent) { + if (currentHref == event.href) { + pdfView.jumpTo(event.page, event.animated) + + } else { + // Android forbids network requests on the main thread by default, so we have to do that + // in the IO dispatcher. + withContext(Dispatchers.IO) { + try { + pdfView + .fromStream(event.url.openStream()) + .defaultPage(event.page) + .onPageChange { page, pageCount -> + if (isAdded) { + viewModel.onPageChanged(event.href, page = page, pageCount = pageCount) + } + } + .load() + + currentHref = event.href + } catch (e: Exception) { + Timber.e(e) + } } } } + override val currentLocator: LiveData get() = + viewModel.currentLocator + + override fun go(locator: Locator, animated: Boolean, completion: () -> Unit): Boolean = + viewModel.goTo(locator, animated, completion) + + override fun go(link: Link, animated: Boolean, completion: () -> Unit): Boolean = + viewModel.goTo(link, animated, completion) + + override fun goForward(animated: Boolean, completion: () -> Unit): Boolean = + viewModel.goForward(animated, completion) + + override fun goBackward(animated: Boolean, completion: () -> Unit): Boolean = + viewModel.goBackward(animated, completion) + } diff --git a/r2-navigator/src/main/java/org/readium/r2/navigator/pdf/PdfNavigatorViewModel.kt b/r2-navigator/src/main/java/org/readium/r2/navigator/pdf/PdfNavigatorViewModel.kt new file mode 100644 index 00000000..bccd5353 --- /dev/null +++ b/r2-navigator/src/main/java/org/readium/r2/navigator/pdf/PdfNavigatorViewModel.kt @@ -0,0 +1,99 @@ +/* + * Module: r2-navigator-kotlin + * Developers: Mickaël Menu + * + * Copyright (c) 2020. Readium Foundation. All rights reserved. + * Use of this source code is governed by a BSD-style license which is detailed in the + * LICENSE file present in the project repository where this source code is maintained. + */ + +package org.readium.r2.navigator.pdf + +import androidx.lifecycle.LiveData +import androidx.lifecycle.MutableLiveData +import androidx.lifecycle.ViewModel +import org.readium.r2.navigator.extensions.page +import org.readium.r2.navigator.extensions.urlToHref +import org.readium.r2.shared.format.MediaType +import org.readium.r2.shared.publication.Link +import org.readium.r2.shared.publication.Locator +import org.readium.r2.shared.publication.Publication +import java.net.URL + +class PdfNavigatorViewModel( + private val publication: Publication, + initialLocator: Locator? = null +) : ViewModel() { + + data class GoToLocationEvent(val href: String, val url: URL, val page: Int, val animated: Boolean) + + val goToLocation = MutableLiveData(null) + + val currentLocator: LiveData get() = _currentLocator + private val _currentLocator = MutableLiveData(null) + + private var currentPageCount: Int? = null + + init { + if (initialLocator != null) { + goTo(initialLocator) + } else { + goTo(publication.readingOrder.first()) + } + } + + fun goTo(locator: Locator, animated: Boolean = false, completion: () -> Unit = {}): Boolean { + return goToHref(locator.href, locator.locations.page ?: 0, animated, completion) + } + + fun goTo(link: Link, animated: Boolean = false, completion: () -> Unit = {}): Boolean { + return goToHref(link.href, 0, animated, completion) + } + + fun goToHref(href: String, page: Int, animated: Boolean = false, completion: () -> Unit = {}): Boolean { + val url = publication.urlToHref(href) ?: return false + goToLocation.value = GoToLocationEvent( + href = href, + url = url, + page = page, + animated = animated + ) + // FIXME: call in onLocationChanged + completion() + return true + } + + fun goForward(animated: Boolean, completion: () -> Unit): Boolean { + val currentLocator = currentLocator.value ?: return false + val page = currentLocator.locations.page ?: 0 + val pageCount = currentPageCount ?: return false + if (page >= (pageCount - 1)) { + return false + } + return goToHref(currentLocator.href, page + 1, animated, completion) + } + + fun goBackward(animated: Boolean, completion: () -> Unit): Boolean { + val currentLocator = currentLocator.value ?: return false + val page = currentLocator.locations.page ?: 0 + if (page <= 0) { + return false + } + return goToHref(currentLocator.href, page - 1, animated, completion) + } + + /** Called by the PDF view when the visible page changed. */ + fun onPageChanged(href: String, page: Int, pageCount: Int) { + val link = publication.linkWithHref(href) + _currentLocator.value = Locator( + href = href, + type = link?.type ?: MediaType.PDF.toString(), + title = link?.title, + locations = Locator.Locations( + fragments = listOf("page=${page + 1}"), + progression = if (pageCount > 0) page / pageCount.toDouble() else 0.0 + ) + ) + } + +} \ No newline at end of file diff --git a/r2-navigator/src/main/java/org/readium/r2/navigator/pdf/R2PdfActivity.kt b/r2-navigator/src/main/java/org/readium/r2/navigator/pdf/R2PdfActivity.kt index af889585..75373bd7 100644 --- a/r2-navigator/src/main/java/org/readium/r2/navigator/pdf/R2PdfActivity.kt +++ b/r2-navigator/src/main/java/org/readium/r2/navigator/pdf/R2PdfActivity.kt @@ -16,7 +16,6 @@ import org.readium.r2.navigator.R import org.readium.r2.shared.extensions.getPublication import org.readium.r2.shared.publication.Publication - class R2PdfActivity : AppCompatActivity() { private lateinit var publication: Publication From d02ee6d0ffe9b7ba9b7f41c462e3e5e12dd39a3d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mickae=CC=88l=20Menu?= Date: Fri, 15 May 2020 13:50:57 +0200 Subject: [PATCH 03/11] Add Navigator.VisualListener Refactor PdfNavigatorFragment --- r2-navigator/build.gradle | 2 +- .../org/readium/r2/navigator/IR2Activity.kt | 8 ++ .../r2/navigator/NavigatorFragmentFactory.kt | 16 +-- .../r2/navigator/pdf/PdfNavigatorFragment.kt | 135 ++++++++++++------ .../r2/navigator/pdf/PdfNavigatorViewModel.kt | 99 ------------- 5 files changed, 106 insertions(+), 154 deletions(-) delete mode 100644 r2-navigator/src/main/java/org/readium/r2/navigator/pdf/PdfNavigatorViewModel.kt diff --git a/r2-navigator/build.gradle b/r2-navigator/build.gradle index 93049d41..d5a7aef6 100644 --- a/r2-navigator/build.gradle +++ b/r2-navigator/build.gradle @@ -58,7 +58,7 @@ dependencies { implementation 'org.zeroturnaround:zt-zip:1.13' implementation 'org.jsoup:jsoup:1.10.3' - implementation 'com.github.barteksc:android-pdf-viewer:2.8.2' + implementation 'com.github.barteksc:android-pdf-viewer:3.2.0-beta.1' testImplementation 'junit:junit:4.12' androidTestImplementation 'androidx.test:runner:1.2.0' diff --git a/r2-navigator/src/main/java/org/readium/r2/navigator/IR2Activity.kt b/r2-navigator/src/main/java/org/readium/r2/navigator/IR2Activity.kt index 4729d5e8..6957fb48 100644 --- a/r2-navigator/src/main/java/org/readium/r2/navigator/IR2Activity.kt +++ b/r2-navigator/src/main/java/org/readium/r2/navigator/IR2Activity.kt @@ -10,6 +10,7 @@ package org.readium.r2.navigator import android.content.SharedPreferences +import android.graphics.PointF import android.view.View import androidx.lifecycle.LiveData import org.readium.r2.navigator.pager.R2ViewPager @@ -61,6 +62,13 @@ interface Navigator { @Deprecated("Use [currentLocator] instead", ReplaceWith("currentLocator.value")) val currentLocation: Locator? get() = currentLocator.value + + interface Listener { + } + + interface VisualListener : Listener { + fun onTap(point: PointF): Boolean = false + } } interface NavigatorDelegate { diff --git a/r2-navigator/src/main/java/org/readium/r2/navigator/NavigatorFragmentFactory.kt b/r2-navigator/src/main/java/org/readium/r2/navigator/NavigatorFragmentFactory.kt index 90664553..f0354e37 100644 --- a/r2-navigator/src/main/java/org/readium/r2/navigator/NavigatorFragmentFactory.kt +++ b/r2-navigator/src/main/java/org/readium/r2/navigator/NavigatorFragmentFactory.kt @@ -11,29 +11,19 @@ package org.readium.r2.navigator import androidx.fragment.app.Fragment import androidx.fragment.app.FragmentFactory -import androidx.lifecycle.ViewModel -import androidx.lifecycle.ViewModelProvider import org.readium.r2.navigator.pdf.PdfNavigatorFragment -import org.readium.r2.navigator.pdf.PdfNavigatorViewModel import org.readium.r2.shared.publication.Locator import org.readium.r2.shared.publication.Publication class NavigatorFragmentFactory( private val publication: Publication, - private val initialLocator: Locator? = null + private val initialLocator: Locator? = null, + private val listener: Navigator.Listener? = null ) : FragmentFactory() { override fun instantiate(classLoader: ClassLoader, className: String): Fragment = when (className) { - PdfNavigatorFragment::class.java.name -> PdfNavigatorFragment(object : ViewModelProvider.Factory { - @Suppress("UNCHECKED_CAST") - override fun create(modelClass: Class): T { - if (!modelClass.isAssignableFrom(PdfNavigatorViewModel::class.java)) { - throw ClassCastException("Unknown ViewModel class: ${modelClass.name}") - } - return PdfNavigatorViewModel(publication, initialLocator) as T - } - }) + PdfNavigatorFragment::class.java.name -> PdfNavigatorFragment(publication, initialLocator, listener) else -> super.instantiate(classLoader, className) } diff --git a/r2-navigator/src/main/java/org/readium/r2/navigator/pdf/PdfNavigatorFragment.kt b/r2-navigator/src/main/java/org/readium/r2/navigator/pdf/PdfNavigatorFragment.kt index 88513c70..2c11b714 100644 --- a/r2-navigator/src/main/java/org/readium/r2/navigator/pdf/PdfNavigatorFragment.kt +++ b/r2-navigator/src/main/java/org/readium/r2/navigator/pdf/PdfNavigatorFragment.kt @@ -9,31 +9,37 @@ package org.readium.r2.navigator.pdf +import android.graphics.PointF import android.os.Bundle import android.view.LayoutInflater +import android.view.MotionEvent import android.view.View import android.view.ViewGroup import androidx.fragment.app.Fragment -import androidx.fragment.app.viewModels import androidx.lifecycle.LiveData -import androidx.lifecycle.Observer -import androidx.lifecycle.ViewModelProvider +import androidx.lifecycle.MutableLiveData import androidx.lifecycle.lifecycleScope import com.github.barteksc.pdfviewer.PDFView +import com.github.barteksc.pdfviewer.util.FitPolicy import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch import kotlinx.coroutines.withContext import org.readium.r2.navigator.Navigator -import org.readium.r2.navigator.pdf.PdfNavigatorViewModel.GoToLocationEvent +import org.readium.r2.navigator.extensions.page +import org.readium.r2.navigator.extensions.urlToHref +import org.readium.r2.shared.format.MediaType import org.readium.r2.shared.publication.Link import org.readium.r2.shared.publication.Locator +import org.readium.r2.shared.publication.Publication import timber.log.Timber -class PdfNavigatorFragment(viewModelFactory: ViewModelProvider.Factory) : Fragment(), Navigator { +class PdfNavigatorFragment( + private val publication: Publication, + private val initialLocator: Locator? = null, + private val listener: Navigator.Listener? = null +) : Fragment(), Navigator { - private lateinit var pdfView: PDFView - - private val viewModel: PdfNavigatorViewModel by viewModels { viewModelFactory } + lateinit var pdfView: PDFView private var currentHref: String? = null @@ -41,58 +47,105 @@ class PdfNavigatorFragment(viewModelFactory: ViewModelProvider.Factory) : Fragme val context = requireContext() pdfView = PDFView(context, null) - viewModel.goToLocation.observe(viewLifecycleOwner, Observer { event -> - event ?: return@Observer - - lifecycleScope.launch { - goTo(event) - viewModel.goToLocation.value = null - } - }) + if (initialLocator != null) { + go(initialLocator) + } else { + go(publication.readingOrder.first()) + } return pdfView } - private suspend fun goTo(event: GoToLocationEvent) { - if (currentHref == event.href) { - pdfView.jumpTo(event.page, event.animated) + private fun goToHref(href: String, page: Int, animated: Boolean = false, completion: () -> Unit = {}): Boolean { + val url = publication.urlToHref(href) ?: return false + + if (currentHref == href) { + pdfView.jumpTo(page, animated) + completion() } else { - // Android forbids network requests on the main thread by default, so we have to do that - // in the IO dispatcher. - withContext(Dispatchers.IO) { + val listener = this + lifecycleScope.launch { try { - pdfView - .fromStream(event.url.openStream()) - .defaultPage(event.page) - .onPageChange { page, pageCount -> - if (isAdded) { - viewModel.onPageChanged(event.href, page = page, pageCount = pageCount) - } - } - .load() - - currentHref = event.href + // Android forbids network requests on the main thread by default, so we have to + // do that in the IO dispatcher. + withContext(Dispatchers.IO) { + pdfView.fromStream(url.openStream()) + .defaultPage(page) + .spacing(10) + .pageFitPolicy(FitPolicy.WIDTH) + .onRender { _ -> completion() } + .onPageChange { page, pageCount -> onPageChanged(page, pageCount) } + .onTap { event -> onTap(event) } + .load() + } + + currentHref = href + } catch (e: Exception) { Timber.e(e) + completion() } } } + + return true } - override val currentLocator: LiveData get() = - viewModel.currentLocator + // Navigator + + override val currentLocator: LiveData get() = _currentLocator + private val _currentLocator = MutableLiveData(null) override fun go(locator: Locator, animated: Boolean, completion: () -> Unit): Boolean = - viewModel.goTo(locator, animated, completion) + goToHref(locator.href, locator.locations.page ?: 0, animated, completion) override fun go(link: Link, animated: Boolean, completion: () -> Unit): Boolean = - viewModel.goTo(link, animated, completion) + goToHref(link.href, 0, animated, completion) + + override fun goForward(animated: Boolean, completion: () -> Unit): Boolean { + val page = pdfView.currentPage + val pageCount = pdfView.pageCount + if (page >= (pageCount - 1)) return false + + pdfView.jumpTo(page + 1, animated) + completion() + return true + } + - override fun goForward(animated: Boolean, completion: () -> Unit): Boolean = - viewModel.goForward(animated, completion) + override fun goBackward(animated: Boolean, completion: () -> Unit): Boolean { + val page = pdfView.currentPage + if (page <= 0) return false - override fun goBackward(animated: Boolean, completion: () -> Unit): Boolean = - viewModel.goBackward(animated, completion) + pdfView.jumpTo(page - 1, animated) + completion() + return true + } + + // PdfView Listeners + private fun onPageChanged(page: Int, pageCount: Int) { + val href = currentHref ?: return + val link = publication.linkWithHref(href) + val progression = if (pageCount > 0) page / pageCount.toDouble() else 0.0 + // FIXME: proper position and totalProgression + _currentLocator.value = Locator( + href = href, + type = link?.type ?: MediaType.PDF.toString(), + title = link?.title, + locations = Locator.Locations( + fragments = listOf("page=${page + 1}"), + position = page + 1, + progression = progression, + totalProgression = progression + ) + ) + } + + private fun onTap(e: MotionEvent?): Boolean { + e ?: return false + val listener = (listener as? Navigator.VisualListener) ?: return false + return listener.onTap(PointF(e.x, e.y)) + } } diff --git a/r2-navigator/src/main/java/org/readium/r2/navigator/pdf/PdfNavigatorViewModel.kt b/r2-navigator/src/main/java/org/readium/r2/navigator/pdf/PdfNavigatorViewModel.kt deleted file mode 100644 index bccd5353..00000000 --- a/r2-navigator/src/main/java/org/readium/r2/navigator/pdf/PdfNavigatorViewModel.kt +++ /dev/null @@ -1,99 +0,0 @@ -/* - * Module: r2-navigator-kotlin - * Developers: Mickaël Menu - * - * Copyright (c) 2020. Readium Foundation. All rights reserved. - * Use of this source code is governed by a BSD-style license which is detailed in the - * LICENSE file present in the project repository where this source code is maintained. - */ - -package org.readium.r2.navigator.pdf - -import androidx.lifecycle.LiveData -import androidx.lifecycle.MutableLiveData -import androidx.lifecycle.ViewModel -import org.readium.r2.navigator.extensions.page -import org.readium.r2.navigator.extensions.urlToHref -import org.readium.r2.shared.format.MediaType -import org.readium.r2.shared.publication.Link -import org.readium.r2.shared.publication.Locator -import org.readium.r2.shared.publication.Publication -import java.net.URL - -class PdfNavigatorViewModel( - private val publication: Publication, - initialLocator: Locator? = null -) : ViewModel() { - - data class GoToLocationEvent(val href: String, val url: URL, val page: Int, val animated: Boolean) - - val goToLocation = MutableLiveData(null) - - val currentLocator: LiveData get() = _currentLocator - private val _currentLocator = MutableLiveData(null) - - private var currentPageCount: Int? = null - - init { - if (initialLocator != null) { - goTo(initialLocator) - } else { - goTo(publication.readingOrder.first()) - } - } - - fun goTo(locator: Locator, animated: Boolean = false, completion: () -> Unit = {}): Boolean { - return goToHref(locator.href, locator.locations.page ?: 0, animated, completion) - } - - fun goTo(link: Link, animated: Boolean = false, completion: () -> Unit = {}): Boolean { - return goToHref(link.href, 0, animated, completion) - } - - fun goToHref(href: String, page: Int, animated: Boolean = false, completion: () -> Unit = {}): Boolean { - val url = publication.urlToHref(href) ?: return false - goToLocation.value = GoToLocationEvent( - href = href, - url = url, - page = page, - animated = animated - ) - // FIXME: call in onLocationChanged - completion() - return true - } - - fun goForward(animated: Boolean, completion: () -> Unit): Boolean { - val currentLocator = currentLocator.value ?: return false - val page = currentLocator.locations.page ?: 0 - val pageCount = currentPageCount ?: return false - if (page >= (pageCount - 1)) { - return false - } - return goToHref(currentLocator.href, page + 1, animated, completion) - } - - fun goBackward(animated: Boolean, completion: () -> Unit): Boolean { - val currentLocator = currentLocator.value ?: return false - val page = currentLocator.locations.page ?: 0 - if (page <= 0) { - return false - } - return goToHref(currentLocator.href, page - 1, animated, completion) - } - - /** Called by the PDF view when the visible page changed. */ - fun onPageChanged(href: String, page: Int, pageCount: Int) { - val link = publication.linkWithHref(href) - _currentLocator.value = Locator( - href = href, - type = link?.type ?: MediaType.PDF.toString(), - title = link?.title, - locations = Locator.Locations( - fragments = listOf("page=${page + 1}"), - progression = if (pageCount > 0) page / pageCount.toDouble() else 0.0 - ) - ) - } - -} \ No newline at end of file From a540c8e18faa6842012a55232f421a5285f9f59d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mickae=CC=88l=20Menu?= Date: Fri, 15 May 2020 16:06:51 +0200 Subject: [PATCH 04/11] Restore PDF current location during configuration changes Add extension point to customize the PDFView in reading apps --- .../r2/navigator/pdf/PdfNavigatorFragment.kt | 34 +++++++++++++++---- 1 file changed, 27 insertions(+), 7 deletions(-) diff --git a/r2-navigator/src/main/java/org/readium/r2/navigator/pdf/PdfNavigatorFragment.kt b/r2-navigator/src/main/java/org/readium/r2/navigator/pdf/PdfNavigatorFragment.kt index 2c11b714..f127fbc7 100644 --- a/r2-navigator/src/main/java/org/readium/r2/navigator/pdf/PdfNavigatorFragment.kt +++ b/r2-navigator/src/main/java/org/readium/r2/navigator/pdf/PdfNavigatorFragment.kt @@ -39,6 +39,11 @@ class PdfNavigatorFragment( private val listener: Navigator.Listener? = null ) : Fragment(), Navigator { + interface Listener: Navigator.Listener { + /** Called when configuring [PDFView]. */ + fun onConfigurePdfView(configurator: PDFView.Configurator) {} + } + lateinit var pdfView: PDFView private var currentHref: String? = null @@ -47,8 +52,9 @@ class PdfNavigatorFragment( val context = requireContext() pdfView = PDFView(context, null) - if (initialLocator != null) { - go(initialLocator) + val locator: Locator? = savedInstanceState?.getParcelable(KEY_LOCATOR) ?: initialLocator + if (locator != null) { + go(locator) } else { go(publication.readingOrder.first()) } @@ -56,6 +62,11 @@ class PdfNavigatorFragment( return pdfView } + override fun onSaveInstanceState(outState: Bundle) { + super.onSaveInstanceState(outState) + outState.putParcelable(KEY_LOCATOR, currentLocator.value) + } + private fun goToHref(href: String, page: Int, animated: Boolean = false, completion: () -> Unit = {}): Boolean { val url = publication.urlToHref(href) ?: return false @@ -64,16 +75,19 @@ class PdfNavigatorFragment( completion() } else { - val listener = this lifecycleScope.launch { try { // Android forbids network requests on the main thread by default, so we have to // do that in the IO dispatcher. withContext(Dispatchers.IO) { pdfView.fromStream(url.openStream()) - .defaultPage(page) .spacing(10) .pageFitPolicy(FitPolicy.WIDTH) + // Customization of [PDFView] is done before setting the listeners, + // to avoid overriding them in reading apps, which would break the + // navigator. + .also { (listener as? Listener)?.onConfigurePdfView(it) } + .defaultPage(page) .onRender { _ -> completion() } .onPageChange { page, pageCount -> onPageChanged(page, pageCount) } .onTap { event -> onTap(event) } @@ -97,8 +111,10 @@ class PdfNavigatorFragment( override val currentLocator: LiveData get() = _currentLocator private val _currentLocator = MutableLiveData(null) - override fun go(locator: Locator, animated: Boolean, completion: () -> Unit): Boolean = - goToHref(locator.href, locator.locations.page ?: 0, animated, completion) + override fun go(locator: Locator, animated: Boolean, completion: () -> Unit): Boolean { + val page = ((locator.locations.page ?: 1) - 1).coerceAtLeast(0) + return goToHref (locator.href, page, animated, completion) + } override fun go(link: Link, animated: Boolean, completion: () -> Unit): Boolean = goToHref(link.href, 0, animated, completion) @@ -123,7 +139,7 @@ class PdfNavigatorFragment( return true } - // PdfView Listeners + // [PDFView] Listeners private fun onPageChanged(page: Int, pageCount: Int) { val href = currentHref ?: return val link = publication.linkWithHref(href) @@ -148,4 +164,8 @@ class PdfNavigatorFragment( return listener.onTap(PointF(e.x, e.y)) } + companion object { + private const val KEY_LOCATOR = "locator" + } + } From 1f76b80ce85cae0644b687944a151bf753c50158 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mickae=CC=88l=20Menu?= Date: Fri, 15 May 2020 18:51:39 +0200 Subject: [PATCH 05/11] Leak interface of PDFView to allow customization from reading apps --- r2-navigator/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/r2-navigator/build.gradle b/r2-navigator/build.gradle index d5a7aef6..df762dac 100644 --- a/r2-navigator/build.gradle +++ b/r2-navigator/build.gradle @@ -58,7 +58,7 @@ dependencies { implementation 'org.zeroturnaround:zt-zip:1.13' implementation 'org.jsoup:jsoup:1.10.3' - implementation 'com.github.barteksc:android-pdf-viewer:3.2.0-beta.1' + api 'com.github.barteksc:android-pdf-viewer:3.2.0-beta.1' testImplementation 'junit:junit:4.12' androidTestImplementation 'androidx.test:runner:1.2.0' From 13a1cca6d1975024eac63f8f361c45a1bdd9a358 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mickae=CC=88l=20Menu?= Date: Tue, 19 May 2020 12:01:47 +0200 Subject: [PATCH 06/11] Use the PDF positions list to generate locators --- r2-navigator/build.gradle | 2 +- .../r2/navigator/pdf/PdfNavigatorFragment.kt | 28 ++++++------------- 2 files changed, 9 insertions(+), 21 deletions(-) diff --git a/r2-navigator/build.gradle b/r2-navigator/build.gradle index df762dac..5a575ecd 100644 --- a/r2-navigator/build.gradle +++ b/r2-navigator/build.gradle @@ -58,7 +58,7 @@ dependencies { implementation 'org.zeroturnaround:zt-zip:1.13' implementation 'org.jsoup:jsoup:1.10.3' - api 'com.github.barteksc:android-pdf-viewer:3.2.0-beta.1' + api 'com.github.barteksc:android-pdf-viewer:2.8.2' testImplementation 'junit:junit:4.12' androidTestImplementation 'androidx.test:runner:1.2.0' diff --git a/r2-navigator/src/main/java/org/readium/r2/navigator/pdf/PdfNavigatorFragment.kt b/r2-navigator/src/main/java/org/readium/r2/navigator/pdf/PdfNavigatorFragment.kt index f127fbc7..4d37b628 100644 --- a/r2-navigator/src/main/java/org/readium/r2/navigator/pdf/PdfNavigatorFragment.kt +++ b/r2-navigator/src/main/java/org/readium/r2/navigator/pdf/PdfNavigatorFragment.kt @@ -20,14 +20,12 @@ import androidx.lifecycle.LiveData import androidx.lifecycle.MutableLiveData import androidx.lifecycle.lifecycleScope import com.github.barteksc.pdfviewer.PDFView -import com.github.barteksc.pdfviewer.util.FitPolicy import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch import kotlinx.coroutines.withContext import org.readium.r2.navigator.Navigator import org.readium.r2.navigator.extensions.page import org.readium.r2.navigator.extensions.urlToHref -import org.readium.r2.shared.format.MediaType import org.readium.r2.shared.publication.Link import org.readium.r2.shared.publication.Locator import org.readium.r2.shared.publication.Publication @@ -82,14 +80,16 @@ class PdfNavigatorFragment( withContext(Dispatchers.IO) { pdfView.fromStream(url.openStream()) .spacing(10) - .pageFitPolicy(FitPolicy.WIDTH) // Customization of [PDFView] is done before setting the listeners, // to avoid overriding them in reading apps, which would break the // navigator. .also { (listener as? Listener)?.onConfigurePdfView(it) } .defaultPage(page) - .onRender { _ -> completion() } - .onPageChange { page, pageCount -> onPageChanged(page, pageCount) } + .onRender { _, _, _ -> + pdfView.fitToWidth() + completion() + } + .onPageChange { page, _ -> onPageChanged(page) } .onTap { event -> onTap(event) } .load() } @@ -140,22 +140,10 @@ class PdfNavigatorFragment( } // [PDFView] Listeners - private fun onPageChanged(page: Int, pageCount: Int) { + + private fun onPageChanged(page: Int) { val href = currentHref ?: return - val link = publication.linkWithHref(href) - val progression = if (pageCount > 0) page / pageCount.toDouble() else 0.0 - // FIXME: proper position and totalProgression - _currentLocator.value = Locator( - href = href, - type = link?.type ?: MediaType.PDF.toString(), - title = link?.title, - locations = Locator.Locations( - fragments = listOf("page=${page + 1}"), - position = page + 1, - progression = progression, - totalProgression = progression - ) - ) + _currentLocator.value = publication.positionsByResource[href]?.getOrNull(page) } private fun onTap(e: MotionEvent?): Boolean { From 7701f2dc231360e869221ecb602056c09f39eb92 Mon Sep 17 00:00:00 2001 From: Quentin Gliosca <32197639+qnga@users.noreply.github.com> Date: Fri, 29 May 2020 08:23:46 +0200 Subject: [PATCH 07/11] Use new Publication model --- .../src/main/java/org/readium/r2/navigator/cbz/R2CbzActivity.kt | 1 + .../main/java/org/readium/r2/navigator/epub/R2EpubActivity.kt | 1 + 2 files changed, 2 insertions(+) diff --git a/r2-navigator/src/main/java/org/readium/r2/navigator/cbz/R2CbzActivity.kt b/r2-navigator/src/main/java/org/readium/r2/navigator/cbz/R2CbzActivity.kt index 9831a4c7..1cb5f90c 100644 --- a/r2-navigator/src/main/java/org/readium/r2/navigator/cbz/R2CbzActivity.kt +++ b/r2-navigator/src/main/java/org/readium/r2/navigator/cbz/R2CbzActivity.kt @@ -27,6 +27,7 @@ import org.readium.r2.navigator.pager.R2ViewPager import org.readium.r2.shared.extensions.destroyPublication import org.readium.r2.shared.extensions.getPublication import org.readium.r2.shared.publication.* +import org.readium.r2.shared.publication.services.positions import kotlin.coroutines.CoroutineContext diff --git a/r2-navigator/src/main/java/org/readium/r2/navigator/epub/R2EpubActivity.kt b/r2-navigator/src/main/java/org/readium/r2/navigator/epub/R2EpubActivity.kt index 464b028f..2e99774b 100644 --- a/r2-navigator/src/main/java/org/readium/r2/navigator/epub/R2EpubActivity.kt +++ b/r2-navigator/src/main/java/org/readium/r2/navigator/epub/R2EpubActivity.kt @@ -37,6 +37,7 @@ import org.readium.r2.shared.getAbsolute import org.readium.r2.shared.publication.* import org.readium.r2.shared.publication.epub.EpubLayout import org.readium.r2.shared.publication.presentation.presentation +import org.readium.r2.shared.publication.services.positionsByResource import java.net.URI import kotlin.coroutines.CoroutineContext import kotlin.math.ceil From a5df724df4d8f6cae40c9180f4d3c320ee0872bc Mon Sep 17 00:00:00 2001 From: Quentin Gliosca <32197639+qnga@users.noreply.github.com> Date: Fri, 12 Jun 2020 10:28:56 +0200 Subject: [PATCH 08/11] Quick fix to be able to use positions() --- r2-navigator/build.gradle | 2 +- .../main/java/org/readium/r2/navigator/cbz/R2CbzActivity.kt | 5 ++++- .../java/org/readium/r2/navigator/epub/R2EpubActivity.kt | 3 +++ 3 files changed, 8 insertions(+), 2 deletions(-) diff --git a/r2-navigator/build.gradle b/r2-navigator/build.gradle index 1af1d75c..353fed43 100644 --- a/r2-navigator/build.gradle +++ b/r2-navigator/build.gradle @@ -60,7 +60,7 @@ dependencies { implementation 'com.jakewharton.timber:timber:4.7.1' - implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-core:1.2.1' + implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-core:1.3.6' implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.1.1' implementation 'org.zeroturnaround:zt-zip:1.13' diff --git a/r2-navigator/src/main/java/org/readium/r2/navigator/cbz/R2CbzActivity.kt b/r2-navigator/src/main/java/org/readium/r2/navigator/cbz/R2CbzActivity.kt index 1cb5f90c..79ada412 100644 --- a/r2-navigator/src/main/java/org/readium/r2/navigator/cbz/R2CbzActivity.kt +++ b/r2-navigator/src/main/java/org/readium/r2/navigator/cbz/R2CbzActivity.kt @@ -20,6 +20,7 @@ import androidx.viewpager.widget.ViewPager import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch +import kotlinx.coroutines.runBlocking import org.readium.r2.navigator.* import org.readium.r2.navigator.extensions.layoutDirectionIsRTL import org.readium.r2.navigator.pager.R2PagerAdapter @@ -34,7 +35,7 @@ import kotlin.coroutines.CoroutineContext open class R2CbzActivity : AppCompatActivity(), CoroutineScope, IR2Activity, VisualNavigator { override val currentLocation: Locator? - get() = publication.positions[resourcePager.currentItem] + get() = positions[resourcePager.currentItem] override fun go(locator: Locator, animated: Boolean, completion: () -> Unit): Boolean { val resourceIndex = publication.readingOrder.indexOfFirstWithHref(locator.href) @@ -80,6 +81,7 @@ open class R2CbzActivity : AppCompatActivity(), CoroutineScope, IR2Activity, Vis override lateinit var publication: Publication override lateinit var publicationIdentifier: String override lateinit var publicationFileName: String + protected lateinit var positions: List override var bookId: Long = -1 var resources: List = emptyList() @@ -99,6 +101,7 @@ open class R2CbzActivity : AppCompatActivity(), CoroutineScope, IR2Activity, Vis publicationPath = intent.getStringExtra("publicationPath") ?: throw Exception("publicationPath required") publicationFileName = intent.getStringExtra("publicationFileName") ?: throw Exception("publicationFileName required") publication = intent.getPublication(this) + positions = runBlocking { publication.positions() } publicationIdentifier = publication.metadata.identifier!! title = publication.metadata.title diff --git a/r2-navigator/src/main/java/org/readium/r2/navigator/epub/R2EpubActivity.kt b/r2-navigator/src/main/java/org/readium/r2/navigator/epub/R2EpubActivity.kt index 34b70439..ba360ca2 100644 --- a/r2-navigator/src/main/java/org/readium/r2/navigator/epub/R2EpubActivity.kt +++ b/r2-navigator/src/main/java/org/readium/r2/navigator/epub/R2EpubActivity.kt @@ -38,6 +38,7 @@ import org.readium.r2.shared.getAbsolute import org.readium.r2.shared.publication.* import org.readium.r2.shared.publication.epub.EpubLayout import org.readium.r2.shared.publication.presentation.presentation +import org.readium.r2.shared.publication.services.positions import org.readium.r2.shared.publication.services.positionsByResource import java.net.URI import kotlin.coroutines.CoroutineContext @@ -217,6 +218,7 @@ open class R2EpubActivity : AppCompatActivity(), IR2Activity, IR2Selectable, IR2 override lateinit var publicationFileName: String override lateinit var publication: Publication override lateinit var publicationIdentifier: String + lateinit var positions: List override var bookId: Long = -1 override var allowToggleActionBar = true @@ -241,6 +243,7 @@ open class R2EpubActivity : AppCompatActivity(), IR2Activity, IR2Selectable, IR2 resourcesDouble = ArrayList() publication = intent.getPublication(this) + positions = runBlocking { publication.positions() } publicationPath = intent.getStringExtra("publicationPath") ?: throw Exception("publicationPath required") publicationFileName = intent.getStringExtra("publicationFileName") ?: throw Exception("publicationFileName required") publicationIdentifier = publication.metadata.identifier!! From 10fd31f2fb4049a596ff17db3bf466cb41a6d4d5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mickae=CC=88l=20Menu?= Date: Tue, 16 Jun 2020 12:42:43 +0200 Subject: [PATCH 09/11] Fix resolving CBZ HREFs --- .../java/org/readium/r2/navigator/pager/R2CbzPageFragment.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/r2-navigator/src/main/java/org/readium/r2/navigator/pager/R2CbzPageFragment.kt b/r2-navigator/src/main/java/org/readium/r2/navigator/pager/R2CbzPageFragment.kt index a0b984f6..f0571223 100755 --- a/r2-navigator/src/main/java/org/readium/r2/navigator/pager/R2CbzPageFragment.kt +++ b/r2-navigator/src/main/java/org/readium/r2/navigator/pager/R2CbzPageFragment.kt @@ -33,7 +33,7 @@ class R2CbzPageFragment : androidx.fragment.app.Fragment() { val v = inflater.inflate(R.layout.viewpager_fragment_cbz, container, false) val imageView = v.findViewById(R.id.imageView) - val blob = ZipUtil.unpackEntry(File(publication), resource) + val blob = ZipUtil.unpackEntry(File(publication), resource?.removePrefix("/")) blob?.let { val arrayInputStream = ByteArrayInputStream(it) val bitmap = BitmapFactory.decodeStream(arrayInputStream) From e0ef199ff8992c5d47e299762e102271b85f66bb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mickae=CC=88l=20Menu?= Date: Tue, 16 Jun 2020 16:53:47 +0200 Subject: [PATCH 10/11] Fix usage of positions in the PDF navigator --- .../r2/navigator/pdf/PdfNavigatorFragment.kt | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/r2-navigator/src/main/java/org/readium/r2/navigator/pdf/PdfNavigatorFragment.kt b/r2-navigator/src/main/java/org/readium/r2/navigator/pdf/PdfNavigatorFragment.kt index 4d37b628..432e979f 100644 --- a/r2-navigator/src/main/java/org/readium/r2/navigator/pdf/PdfNavigatorFragment.kt +++ b/r2-navigator/src/main/java/org/readium/r2/navigator/pdf/PdfNavigatorFragment.kt @@ -22,6 +22,7 @@ import androidx.lifecycle.lifecycleScope import com.github.barteksc.pdfviewer.PDFView import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch +import kotlinx.coroutines.runBlocking import kotlinx.coroutines.withContext import org.readium.r2.navigator.Navigator import org.readium.r2.navigator.extensions.page @@ -29,6 +30,8 @@ import org.readium.r2.navigator.extensions.urlToHref import org.readium.r2.shared.publication.Link import org.readium.r2.shared.publication.Locator import org.readium.r2.shared.publication.Publication +import org.readium.r2.shared.publication.indexOfFirstWithHref +import org.readium.r2.shared.publication.services.positionsByReadingOrder import timber.log.Timber class PdfNavigatorFragment( @@ -44,12 +47,22 @@ class PdfNavigatorFragment( lateinit var pdfView: PDFView + private lateinit var positionsByReadingOrder: List> + private var currentHref: String? = null + private val currentResourcePositions: List get() { + val href = currentHref ?: return emptyList() + val index = publication.readingOrder.indexOfFirstWithHref(href) ?: return emptyList() + return positionsByReadingOrder[index] + } + override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { val context = requireContext() pdfView = PDFView(context, null) + positionsByReadingOrder = runBlocking { publication.positionsByReadingOrder() } + val locator: Locator? = savedInstanceState?.getParcelable(KEY_LOCATOR) ?: initialLocator if (locator != null) { go(locator) @@ -142,8 +155,7 @@ class PdfNavigatorFragment( // [PDFView] Listeners private fun onPageChanged(page: Int) { - val href = currentHref ?: return - _currentLocator.value = publication.positionsByResource[href]?.getOrNull(page) + _currentLocator.value = currentResourcePositions.getOrNull(page) } private fun onTap(e: MotionEvent?): Boolean { From e3ce19b0d360772ad3b30fc11cc7fd208e96bd0e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mickae=CC=88l=20Menu?= Date: Tue, 16 Jun 2020 17:33:11 +0200 Subject: [PATCH 11/11] Fix restoring last location in PDF --- .../java/org/readium/r2/navigator/pdf/PdfNavigatorFragment.kt | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/r2-navigator/src/main/java/org/readium/r2/navigator/pdf/PdfNavigatorFragment.kt b/r2-navigator/src/main/java/org/readium/r2/navigator/pdf/PdfNavigatorFragment.kt index 432e979f..66596e3b 100644 --- a/r2-navigator/src/main/java/org/readium/r2/navigator/pdf/PdfNavigatorFragment.kt +++ b/r2-navigator/src/main/java/org/readium/r2/navigator/pdf/PdfNavigatorFragment.kt @@ -100,6 +100,10 @@ class PdfNavigatorFragment( .defaultPage(page) .onRender { _, _, _ -> pdfView.fitToWidth() + // Using `fitToWidth` often breaks the use of `defaultPage`, so we + // need to jump manually to the target page. + pdfView.jumpTo(page, false) + completion() } .onPageChange { page, _ -> onPageChanged(page) }