diff --git a/README.md b/README.md index 1f1e03a..9852d68 100644 --- a/README.md +++ b/README.md @@ -36,8 +36,7 @@ the project and functionality that must be provided by the hosting application. The [org.librarysimplified.r2.api](org.librarysimplified.r2.api) module defines a _controller API_ that accepts commands and publishes events in response to those commands. A _controller_ encapsulates -a Readium [Publication](https://readium.org/webpub-manifest/) and an internal server -used to expose resources from that `Publication`. A single _controller_ instance has +a Readium [Publication](https://readium.org/webpub-manifest/). A single _controller_ instance has a lifetime matching that of the `Publication`; when the user wants to open a book, a new _controller_ instance is created for that book, and then destroyed when the user closes the book. @@ -70,9 +69,7 @@ restoring this state each time a `WebView` is (re)connected. The [org.librarysimplified.r2.views](org.librarysimplified.r2.views) module defines a set of Android [Fragments](https://developer.android.com/guide/fragments) that implement a simple user interface for displaying a book and allowing the user to manage bookmarks and choose items from the book's table of contents. The fragments are -conceptually stateless views that simply respond to events published by the current -_controller_ instance. The fragments communicate by a shared, public [ViewModel](https://developer.android.com/topic/libraries/architecture/viewmodel), -with the _controller_ instance being stored in this `ViewModel`. +stateless views that simply respond to events published by the current _controller_ instance. ### Usage @@ -85,11 +82,10 @@ The user's application has the following responsibilities: database. Failing to handle events will merely result in bookmarks not being saved. * The application _must_ respond to requests from the fragments to instantiate other - fragments, and to pop the backstack as necessary. For example, when the user clicks - the _table of contents_ button in the `SR2ReaderFragment`, the `ViewModel` will - publish an event indicating that the user's application should now instantiate and - attach a `SR2TOCFragment` in order to show the table of contents. Failing to handle - these events will merely result in a UI that does nothing when the user selects + fragments. For example, when the user clicks the _table of contents_ button in the + `SR2ReaderFragment`, an event will be published indicating that the user's application + should now instantiate and attach a `SR2TOCFragment` in order to show the table of contents. + Failing to handle these events will merely result in a UI that does nothing when the user selects various menu items. An extremely minimal [demo application](org.librarysimplified.r2.demo) is included that diff --git a/gradle.properties b/gradle.properties index 0e7615c..29ece90 100644 --- a/gradle.properties +++ b/gradle.properties @@ -11,7 +11,7 @@ POM_SCM_CONNECTION=scm:git:git://github.com/ThePalaceProject/android-r2 POM_SCM_DEV_CONNECTION=scm:git:ssh://git@github.com/ThePalaceProject/android-r2 POM_SCM_URL=http://github.com/ThePalaceProject/android-r2 POM_URL=http://github.com/ThePalaceProject/android-r2 -VERSION_NAME=2.6.0-SNAPSHOT +VERSION_NAME=3.0.0-SNAPSHOT VERSION_PREVIOUS=2.5.0 android.useAndroidX=true diff --git a/org.librarysimplified.r2.api/build.gradle.kts b/org.librarysimplified.r2.api/build.gradle.kts index e431621..e1e44eb 100644 --- a/org.librarysimplified.r2.api/build.gradle.kts +++ b/org.librarysimplified.r2.api/build.gradle.kts @@ -1,8 +1,8 @@ dependencies { - implementation(libs.google.guava) implementation(libs.joda.time) implementation(libs.kotlin.stdlib) implementation(libs.r2.shared) implementation(libs.r2.streamer) implementation(libs.rxjava2) + implementation(libs.slf4j) } diff --git a/org.librarysimplified.r2.api/src/main/java/org/librarysimplified/r2/api/SR2ControllerConfiguration.kt b/org.librarysimplified.r2.api/src/main/java/org/librarysimplified/r2/api/SR2ControllerConfiguration.kt index ae3cec9..f0b4bb0 100644 --- a/org.librarysimplified.r2.api/src/main/java/org/librarysimplified/r2/api/SR2ControllerConfiguration.kt +++ b/org.librarysimplified.r2.api/src/main/java/org/librarysimplified/r2/api/SR2ControllerConfiguration.kt @@ -1,9 +1,8 @@ package org.librarysimplified.r2.api import android.content.Context -import com.google.common.util.concurrent.ListeningExecutorService -import org.readium.r2.shared.publication.ContentProtection -import org.readium.r2.shared.publication.asset.PublicationAsset +import org.readium.r2.shared.publication.protection.ContentProtection +import org.readium.r2.shared.util.asset.Asset /** * Configuration values for an R2 controller. @@ -15,7 +14,7 @@ data class SR2ControllerConfiguration( * A publication asset containing a book. */ - val bookFile: PublicationAsset, + val bookFile: Asset, /** * An identifier used to uniquely identify a publication. Unfortunately, identifier are optional @@ -42,12 +41,6 @@ data class SR2ControllerConfiguration( val contentProtections: List, - /** - * An executor service used to execute I/O code on one or more background threads. - */ - - val ioExecutor: ListeningExecutorService, - /** * A function that executes `f` on the Android UI thread. */ diff --git a/org.librarysimplified.r2.api/src/main/java/org/librarysimplified/r2/api/SR2ControllerProviderType.kt b/org.librarysimplified.r2.api/src/main/java/org/librarysimplified/r2/api/SR2ControllerProviderType.kt index 80a38e1..55e2f69 100644 --- a/org.librarysimplified.r2.api/src/main/java/org/librarysimplified/r2/api/SR2ControllerProviderType.kt +++ b/org.librarysimplified.r2.api/src/main/java/org/librarysimplified/r2/api/SR2ControllerProviderType.kt @@ -1,7 +1,6 @@ package org.librarysimplified.r2.api -import com.google.common.util.concurrent.ListenableFuture -import java.util.concurrent.Callable +import android.app.Application /** * A provider of R2 controllers. @@ -9,20 +8,6 @@ import java.util.concurrent.Callable interface SR2ControllerProviderType { - /** - * Create a new R2 controller on a thread provided by the given I/O executor. - */ - - fun create( - configuration: SR2ControllerConfiguration, - ): ListenableFuture { - return configuration.ioExecutor.submit( - Callable { - this.createHere(configuration) - }, - ) - } - /** * Create a new controller on the current thread. * @@ -30,7 +15,8 @@ interface SR2ControllerProviderType { * should _not_ be called on the Android UI thread. */ - fun createHere( + fun create( + context: Application, configuration: SR2ControllerConfiguration, ): SR2ControllerType } diff --git a/org.librarysimplified.r2.api/src/main/java/org/librarysimplified/r2/api/SR2Event.kt b/org.librarysimplified.r2.api/src/main/java/org/librarysimplified/r2/api/SR2Event.kt index 71d0ea5..3f099ae 100644 --- a/org.librarysimplified.r2.api/src/main/java/org/librarysimplified/r2/api/SR2Event.kt +++ b/org.librarysimplified.r2.api/src/main/java/org/librarysimplified/r2/api/SR2Event.kt @@ -1,6 +1,7 @@ package org.librarysimplified.r2.api import org.readium.r2.shared.Search +import org.readium.r2.shared.publication.Href import org.readium.r2.shared.publication.services.search.SearchIterator /** @@ -51,7 +52,7 @@ sealed class SR2Event { */ data class SR2ReadingPositionChanged( - val chapterHref: String, + val chapterHref: Href, val chapterProgress: Double, val chapterTitle: String?, val currentPage: Int?, diff --git a/org.librarysimplified.r2.api/src/main/java/org/librarysimplified/r2/api/SR2Executors.kt b/org.librarysimplified.r2.api/src/main/java/org/librarysimplified/r2/api/SR2Executors.kt new file mode 100644 index 0000000..9ffeac4 --- /dev/null +++ b/org.librarysimplified.r2.api/src/main/java/org/librarysimplified/r2/api/SR2Executors.kt @@ -0,0 +1,21 @@ +package org.librarysimplified.r2.api + +import org.slf4j.LoggerFactory +import java.util.concurrent.Executor +import java.util.concurrent.Executors + +object SR2Executors { + + private val logger = + LoggerFactory.getLogger(SR2Executors::class.java) + + val ioExecutor: Executor = + Executors.newSingleThreadExecutor { r -> + val thread = Thread(r) + thread.name = "org.librarysimplified.r2.io" + thread.setUncaughtExceptionHandler { t, e -> + logger.error("Uncaught exception: ", e) + } + thread + } +} diff --git a/org.librarysimplified.r2.api/src/main/java/org/librarysimplified/r2/api/SR2Locator.kt b/org.librarysimplified.r2.api/src/main/java/org/librarysimplified/r2/api/SR2Locator.kt index 7fb9fd0..f293d92 100644 --- a/org.librarysimplified.r2.api/src/main/java/org/librarysimplified/r2/api/SR2Locator.kt +++ b/org.librarysimplified.r2.api/src/main/java/org/librarysimplified/r2/api/SR2Locator.kt @@ -1,15 +1,17 @@ package org.librarysimplified.r2.api +import org.readium.r2.shared.publication.Href + /** * A location within a book. */ sealed class SR2Locator : Comparable { - abstract val chapterHref: String + abstract val chapterHref: Href data class SR2LocatorPercent( - override val chapterHref: String, + override val chapterHref: Href, val chapterProgress: Double, ) : SR2Locator() { @@ -20,13 +22,13 @@ sealed class SR2Locator : Comparable { } companion object { - fun start(href: String): SR2LocatorPercent { + fun start(href: Href): SR2LocatorPercent { return SR2LocatorPercent(chapterProgress = 0.0, chapterHref = href) } } override fun compareTo(other: SR2Locator): Int { - val indexCmp = this.chapterHref.compareTo(other.chapterHref) + val indexCmp = this.chapterHref.toString().compareTo(other.chapterHref.toString()) return if (indexCmp == 0) { when (other) { is SR2LocatorPercent -> @@ -41,10 +43,10 @@ sealed class SR2Locator : Comparable { } data class SR2LocatorChapterEnd( - override val chapterHref: String, + override val chapterHref: Href, ) : SR2Locator() { override fun compareTo(other: SR2Locator): Int { - val indexCmp = this.chapterHref.compareTo(other.chapterHref) + val indexCmp = this.chapterHref.toString().compareTo(other.chapterHref.toString()) return if (indexCmp == 0) { when (other) { is SR2LocatorPercent -> diff --git a/org.librarysimplified.r2.api/src/main/java/org/librarysimplified/r2/api/SR2TOCEntry.kt b/org.librarysimplified.r2.api/src/main/java/org/librarysimplified/r2/api/SR2TOCEntry.kt index b590288..c3e0214 100644 --- a/org.librarysimplified.r2.api/src/main/java/org/librarysimplified/r2/api/SR2TOCEntry.kt +++ b/org.librarysimplified.r2.api/src/main/java/org/librarysimplified/r2/api/SR2TOCEntry.kt @@ -1,12 +1,14 @@ package org.librarysimplified.r2.api +import org.readium.r2.shared.publication.Href + /** * A flattened entry from the table of contents. */ data class SR2TOCEntry( val title: String, - val href: String, + val href: Href, val depth: Int, ) { init { diff --git a/org.librarysimplified.r2.demo/build.gradle.kts b/org.librarysimplified.r2.demo/build.gradle.kts index 974a9f7..648a70f 100644 --- a/org.librarysimplified.r2.demo/build.gradle.kts +++ b/org.librarysimplified.r2.demo/build.gradle.kts @@ -57,8 +57,6 @@ dependencies { implementation(libs.androidx.vectordrawable.animated) implementation(libs.androidx.viewpager) implementation(libs.androidx.viewpager2) - implementation(libs.google.failureaccess) - implementation(libs.google.guava) implementation(libs.google.material) implementation(libs.joda.time) implementation(libs.jsoup) diff --git a/org.librarysimplified.r2.demo/src/main/java/org/librarysimplified/r2/demo/DemoActivity.kt b/org.librarysimplified.r2.demo/src/main/java/org/librarysimplified/r2/demo/DemoActivity.kt index 6a5576e..b6a78ac 100644 --- a/org.librarysimplified.r2.demo/src/main/java/org/librarysimplified/r2/demo/DemoActivity.kt +++ b/org.librarysimplified.r2.demo/src/main/java/org/librarysimplified/r2/demo/DemoActivity.kt @@ -3,21 +3,16 @@ package org.librarysimplified.r2.demo import android.app.Activity import android.content.Intent import android.net.Uri -import android.os.Build import android.os.Bundle -import android.view.View -import android.widget.Button -import android.widget.CheckBox import android.widget.Toast import androidx.annotation.UiThread import androidx.appcompat.app.AlertDialog import androidx.appcompat.app.AppCompatActivity import androidx.appcompat.widget.Toolbar import androidx.fragment.app.Fragment -import androidx.lifecycle.ViewModelProvider -import io.reactivex.disposables.Disposable +import io.reactivex.disposables.CompositeDisposable +import kotlinx.coroutines.runBlocking import org.librarysimplified.r2.api.SR2Command -import org.librarysimplified.r2.api.SR2ControllerType import org.librarysimplified.r2.api.SR2Event import org.librarysimplified.r2.api.SR2Event.SR2BookmarkEvent.SR2BookmarkCreate import org.librarysimplified.r2.api.SR2Event.SR2BookmarkEvent.SR2BookmarkCreated @@ -36,25 +31,26 @@ import org.librarysimplified.r2.api.SR2Event.SR2ExternalLinkSelected import org.librarysimplified.r2.api.SR2Event.SR2OnCenterTapped import org.librarysimplified.r2.api.SR2Event.SR2ReadingPositionChanged import org.librarysimplified.r2.api.SR2Event.SR2ThemeChanged -import org.librarysimplified.r2.api.SR2PageNumberingMode -import org.librarysimplified.r2.api.SR2ScrollingMode import org.librarysimplified.r2.ui_thread.SR2UIThread import org.librarysimplified.r2.vanilla.SR2Controllers import org.librarysimplified.r2.views.SR2ControllerReference +import org.librarysimplified.r2.views.SR2Fragment import org.librarysimplified.r2.views.SR2ReaderFragment -import org.librarysimplified.r2.views.SR2ReaderFragmentFactory -import org.librarysimplified.r2.views.SR2ReaderParameters +import org.librarysimplified.r2.views.SR2ReaderModel +import org.librarysimplified.r2.views.SR2ReaderViewCommand +import org.librarysimplified.r2.views.SR2ReaderViewCommand.SR2ReaderViewNavigationReaderClose +import org.librarysimplified.r2.views.SR2ReaderViewCommand.SR2ReaderViewNavigationSearchClose +import org.librarysimplified.r2.views.SR2ReaderViewCommand.SR2ReaderViewNavigationSearchOpen +import org.librarysimplified.r2.views.SR2ReaderViewCommand.SR2ReaderViewNavigationTOCClose +import org.librarysimplified.r2.views.SR2ReaderViewCommand.SR2ReaderViewNavigationTOCOpen import org.librarysimplified.r2.views.SR2ReaderViewEvent import org.librarysimplified.r2.views.SR2ReaderViewEvent.SR2ReaderViewBookEvent.SR2BookLoadingFailed import org.librarysimplified.r2.views.SR2ReaderViewEvent.SR2ReaderViewControllerEvent.SR2ControllerBecameAvailable -import org.librarysimplified.r2.views.SR2ReaderViewEvent.SR2ReaderViewNavigationEvent.SR2ReaderViewNavigationClose -import org.librarysimplified.r2.views.SR2ReaderViewEvent.SR2ReaderViewNavigationEvent.SR2ReaderViewNavigationOpenSearch -import org.librarysimplified.r2.views.SR2ReaderViewEvent.SR2ReaderViewNavigationEvent.SR2ReaderViewNavigationOpenTOC -import org.librarysimplified.r2.views.SR2ReaderViewModel -import org.librarysimplified.r2.views.SR2ReaderViewModelFactory +import org.librarysimplified.r2.views.SR2SearchFragment import org.librarysimplified.r2.views.SR2TOCFragment -import org.librarysimplified.r2.views.search.SR2SearchFragment -import org.readium.r2.shared.publication.asset.FileAsset +import org.readium.r2.shared.util.Try +import org.readium.r2.shared.util.asset.AssetRetriever +import org.readium.r2.shared.util.http.DefaultHttpClient import org.slf4j.LoggerFactory import java.io.File import java.io.FileNotFoundException @@ -66,42 +62,26 @@ import java.security.MessageDigest class DemoActivity : AppCompatActivity(R.layout.demo_activity_host) { + private val logger = + LoggerFactory.getLogger(DemoActivity::class.java) + companion object { const val PICK_DOCUMENT = 1001 } - private val logger = - LoggerFactory.getLogger(DemoActivity::class.java) - - private lateinit var readerFragmentFactory: SR2ReaderFragmentFactory - private lateinit var readerParameters: SR2ReaderParameters - private var controller: SR2ControllerType? = null - private var controllerSubscription: Disposable? = null - private var epubFile: File? = null - private var epubId: String? = null - private var viewSubscription: Disposable? = null - private lateinit var scrollMode: CheckBox - private lateinit var perChapterPageNumbering: CheckBox - private lateinit var readerFragment: Fragment - private lateinit var searchFragment: Fragment - private lateinit var tocFragment: Fragment + private lateinit var subscriptions: CompositeDisposable + private var fragmentNow: Fragment? = null override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) val toolbar = this.findViewById(R.id.mainToolbar) as Toolbar this.setSupportActionBar(toolbar) - - if (savedInstanceState == null) { - this.setContentView(R.layout.demo_fragment_host) - - val browseButton = this.findViewById