Linting fixes
This commit is contained in:
@@ -26,7 +26,8 @@ class ChapterLoadByNumber {
|
||||
// If there is only one chapter for this number, use it
|
||||
chaptersForNumber.size == 1 -> chaptersForNumber.first()
|
||||
// Prefer a chapter of the same scanlator as the selected
|
||||
else -> chaptersForNumber.find { it.scanlator == selectedChapter.scanlator }
|
||||
else ->
|
||||
chaptersForNumber.find { it.scanlator == selectedChapter.scanlator }
|
||||
?: chaptersForNumber.first()
|
||||
}
|
||||
chapters.add(preferredChapter)
|
||||
|
||||
@@ -47,8 +47,8 @@ class PageIndicatorTextView(
|
||||
|
||||
// A span object with text outlining properties
|
||||
val spanOutline = OutlineSpan(
|
||||
strokeColor = strokeColor,
|
||||
strokeWidth = 4f
|
||||
strokeColor = strokeColor,
|
||||
strokeWidth = 4f
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -127,10 +127,12 @@ class ReaderActivity : BaseRxActivity<ReaderActivityBinding, ReaderPresenter>()
|
||||
* Called when the activity is created. Initializes the presenter and configuration.
|
||||
*/
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
setTheme(when (preferences.readerTheme().get()) {
|
||||
0 -> R.style.Theme_Reader_Light
|
||||
else -> R.style.Theme_Reader
|
||||
})
|
||||
setTheme(
|
||||
when (preferences.readerTheme().get()) {
|
||||
0 -> R.style.Theme_Reader_Light
|
||||
else -> R.style.Theme_Reader
|
||||
}
|
||||
)
|
||||
super.onCreate(savedInstanceState)
|
||||
|
||||
binding = ReaderActivityBinding.inflate(layoutInflater)
|
||||
@@ -275,10 +277,11 @@ class ReaderActivity : BaseRxActivity<ReaderActivityBinding, ReaderPresenter>()
|
||||
ViewCompat.setOnApplyWindowInsetsListener(binding.readerMenu) { _, insets ->
|
||||
if (!window.isDefaultBar()) {
|
||||
binding.readerMenu.setPadding(
|
||||
insets.systemWindowInsetLeft,
|
||||
insets.systemWindowInsetTop,
|
||||
insets.systemWindowInsetRight,
|
||||
insets.systemWindowInsetBottom)
|
||||
insets.systemWindowInsetLeft,
|
||||
insets.systemWindowInsetTop,
|
||||
insets.systemWindowInsetRight,
|
||||
insets.systemWindowInsetBottom
|
||||
)
|
||||
}
|
||||
insets
|
||||
}
|
||||
@@ -293,18 +296,20 @@ class ReaderActivity : BaseRxActivity<ReaderActivityBinding, ReaderPresenter>()
|
||||
})
|
||||
binding.leftChapter.setOnClickListener {
|
||||
if (viewer != null) {
|
||||
if (viewer is R2LPagerViewer)
|
||||
if (viewer is R2LPagerViewer) {
|
||||
loadNextChapter()
|
||||
else
|
||||
} else {
|
||||
loadPreviousChapter()
|
||||
}
|
||||
}
|
||||
}
|
||||
binding.rightChapter.setOnClickListener {
|
||||
if (viewer != null) {
|
||||
if (viewer is R2LPagerViewer)
|
||||
if (viewer is R2LPagerViewer) {
|
||||
loadPreviousChapter()
|
||||
else
|
||||
} else {
|
||||
loadNextChapter()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -590,11 +595,13 @@ class ReaderActivity : BaseRxActivity<ReaderActivityBinding, ReaderPresenter>()
|
||||
* depending on the [result].
|
||||
*/
|
||||
fun onSetAsCoverResult(result: ReaderPresenter.SetAsCoverResult) {
|
||||
toast(when (result) {
|
||||
Success -> R.string.cover_updated
|
||||
AddToLibraryFirst -> R.string.notification_first_add_to_library
|
||||
Error -> R.string.notification_cover_update_failed
|
||||
})
|
||||
toast(
|
||||
when (result) {
|
||||
Success -> R.string.cover_updated
|
||||
AddToLibraryFirst -> R.string.notification_first_add_to_library
|
||||
Error -> R.string.notification_cover_update_failed
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -614,10 +621,10 @@ class ReaderActivity : BaseRxActivity<ReaderActivityBinding, ReaderPresenter>()
|
||||
val sharedRotation = preferences.rotation().asObservable().share()
|
||||
val initialRotation = sharedRotation.take(1)
|
||||
val rotationUpdates = sharedRotation.skip(1)
|
||||
.delay(250, TimeUnit.MILLISECONDS, AndroidSchedulers.mainThread())
|
||||
.delay(250, TimeUnit.MILLISECONDS, AndroidSchedulers.mainThread())
|
||||
|
||||
subscriptions += Observable.merge(initialRotation, rotationUpdates)
|
||||
.subscribe { setOrientation(it) }
|
||||
.subscribe { setOrientation(it) }
|
||||
|
||||
preferences.readerTheme().asFlow()
|
||||
.drop(1) // We only care about updates
|
||||
@@ -700,10 +707,11 @@ class ReaderActivity : BaseRxActivity<ReaderActivityBinding, ReaderPresenter>()
|
||||
* Sets the 32-bit color mode according to [enabled].
|
||||
*/
|
||||
private fun setTrueColor(enabled: Boolean) {
|
||||
if (enabled)
|
||||
if (enabled) {
|
||||
SubsamplingScaleImageView.setPreferredBitmapConfig(Bitmap.Config.ARGB_8888)
|
||||
else
|
||||
} else {
|
||||
SubsamplingScaleImageView.setPreferredBitmapConfig(Bitmap.Config.RGB_565)
|
||||
}
|
||||
}
|
||||
|
||||
@TargetApi(Build.VERSION_CODES.P)
|
||||
|
||||
@@ -214,8 +214,9 @@ class ReaderColorFilterSheet(private val activity: ReaderActivity) : BottomSheet
|
||||
brightness_overlay.gone()
|
||||
}
|
||||
|
||||
if (!isDisabled)
|
||||
if (!isDisabled) {
|
||||
txt_brightness_seekbar_value.text = value.toString()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -17,14 +17,16 @@ class ReaderColorFilterView(
|
||||
|
||||
fun setFilterColor(color: Int, filterMode: Int) {
|
||||
colorFilterPaint.color = color
|
||||
colorFilterPaint.xfermode = PorterDuffXfermode(when (filterMode) {
|
||||
1 -> PorterDuff.Mode.MULTIPLY
|
||||
2 -> PorterDuff.Mode.SCREEN
|
||||
3 -> PorterDuff.Mode.OVERLAY
|
||||
4 -> PorterDuff.Mode.LIGHTEN
|
||||
5 -> PorterDuff.Mode.DARKEN
|
||||
else -> PorterDuff.Mode.SRC_OVER
|
||||
})
|
||||
colorFilterPaint.xfermode = PorterDuffXfermode(
|
||||
when (filterMode) {
|
||||
1 -> PorterDuff.Mode.MULTIPLY
|
||||
2 -> PorterDuff.Mode.SCREEN
|
||||
3 -> PorterDuff.Mode.OVERLAY
|
||||
4 -> PorterDuff.Mode.LIGHTEN
|
||||
5 -> PorterDuff.Mode.DARKEN
|
||||
else -> PorterDuff.Mode.SRC_OVER
|
||||
}
|
||||
)
|
||||
invalidate()
|
||||
}
|
||||
|
||||
|
||||
@@ -44,13 +44,13 @@ class ReaderPageSheet(
|
||||
if (page.status != Page.READY) return
|
||||
|
||||
MaterialDialog(activity)
|
||||
.message(R.string.confirm_set_image_as_cover)
|
||||
.positiveButton(android.R.string.ok) {
|
||||
activity.setAsCover(page)
|
||||
dismiss()
|
||||
}
|
||||
.negativeButton(android.R.string.cancel)
|
||||
.show()
|
||||
.message(R.string.confirm_set_image_as_cover)
|
||||
.positiveButton(android.R.string.ok) {
|
||||
activity.setAsCover(page)
|
||||
dismiss()
|
||||
}
|
||||
.negativeButton(android.R.string.cancel)
|
||||
.show()
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -88,38 +88,40 @@ class ReaderPresenter(
|
||||
val dbChapters = db.getChapters(manga).executeAsBlocking()
|
||||
|
||||
val selectedChapter = dbChapters.find { it.id == chapterId }
|
||||
?: error("Requested chapter of id $chapterId not found in chapter list")
|
||||
?: error("Requested chapter of id $chapterId not found in chapter list")
|
||||
|
||||
val chaptersForReader =
|
||||
if (preferences.skipRead() || preferences.skipFiltered()) {
|
||||
val list = dbChapters
|
||||
.filter {
|
||||
if (preferences.skipRead() && it.read) {
|
||||
if (preferences.skipRead() || preferences.skipFiltered()) {
|
||||
val list = dbChapters
|
||||
.filter {
|
||||
if (preferences.skipRead() && it.read) {
|
||||
return@filter false
|
||||
} else if (preferences.skipFiltered()) {
|
||||
if (
|
||||
(manga.readFilter == Manga.SHOW_READ && !it.read) ||
|
||||
(manga.readFilter == Manga.SHOW_UNREAD && it.read) ||
|
||||
(
|
||||
manga.downloadedFilter == Manga.SHOW_DOWNLOADED &&
|
||||
!downloadManager.isChapterDownloaded(it, manga)
|
||||
) ||
|
||||
(manga.bookmarkedFilter == Manga.SHOW_BOOKMARKED && !it.bookmark)
|
||||
) {
|
||||
return@filter false
|
||||
} else if (preferences.skipFiltered()) {
|
||||
if (
|
||||
(manga.readFilter == Manga.SHOW_READ && !it.read) ||
|
||||
(manga.readFilter == Manga.SHOW_UNREAD && it.read) ||
|
||||
(manga.downloadedFilter == Manga.SHOW_DOWNLOADED &&
|
||||
!downloadManager.isChapterDownloaded(it, manga)) ||
|
||||
(manga.bookmarkedFilter == Manga.SHOW_BOOKMARKED && !it.bookmark)
|
||||
) {
|
||||
return@filter false
|
||||
}
|
||||
}
|
||||
|
||||
true
|
||||
}
|
||||
.toMutableList()
|
||||
|
||||
val find = list.find { it.id == chapterId }
|
||||
if (find == null) {
|
||||
list.add(selectedChapter)
|
||||
true
|
||||
}
|
||||
list
|
||||
} else {
|
||||
dbChapters
|
||||
.toMutableList()
|
||||
|
||||
val find = list.find { it.id == chapterId }
|
||||
if (find == null) {
|
||||
list.add(selectedChapter)
|
||||
}
|
||||
list
|
||||
} else {
|
||||
dbChapters
|
||||
}
|
||||
|
||||
when (manga.sorting) {
|
||||
Manga.SORTING_SOURCE -> ChapterLoadBySource().get(chaptersForReader)
|
||||
@@ -198,12 +200,15 @@ class ReaderPresenter(
|
||||
if (!needsInit()) return
|
||||
|
||||
db.getManga(mangaId).asRxObservable()
|
||||
.first()
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.doOnNext { init(it, initialChapterId) }
|
||||
.subscribeFirst({ _, _ ->
|
||||
.first()
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.doOnNext { init(it, initialChapterId) }
|
||||
.subscribeFirst(
|
||||
{ _, _ ->
|
||||
// Ignore onNext event
|
||||
}, ReaderActivity::setInitialChapterError)
|
||||
},
|
||||
ReaderActivity::setInitialChapterError
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -241,13 +246,16 @@ class ReaderPresenter(
|
||||
// Read chapterList from an io thread because it's retrieved lazily and would block main.
|
||||
activeChapterSubscription?.unsubscribe()
|
||||
activeChapterSubscription = Observable
|
||||
.fromCallable { chapterList.first { chapterId == it.chapter.id } }
|
||||
.flatMap { getLoadObservable(loader!!, it) }
|
||||
.subscribeOn(Schedulers.io())
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.subscribeFirst({ _, _ ->
|
||||
.fromCallable { chapterList.first { chapterId == it.chapter.id } }
|
||||
.flatMap { getLoadObservable(loader!!, it) }
|
||||
.subscribeOn(Schedulers.io())
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.subscribeFirst(
|
||||
{ _, _ ->
|
||||
// Ignore onNext event
|
||||
}, ReaderActivity::setInitialChapterError)
|
||||
},
|
||||
ReaderActivity::setInitialChapterError
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -262,23 +270,27 @@ class ReaderPresenter(
|
||||
chapter: ReaderChapter
|
||||
): Observable<ViewerChapters> {
|
||||
return loader.loadChapter(chapter)
|
||||
.andThen(Observable.fromCallable {
|
||||
.andThen(
|
||||
Observable.fromCallable {
|
||||
val chapterPos = chapterList.indexOf(chapter)
|
||||
|
||||
ViewerChapters(chapter,
|
||||
chapterList.getOrNull(chapterPos - 1),
|
||||
chapterList.getOrNull(chapterPos + 1))
|
||||
})
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.doOnNext { newChapters ->
|
||||
val oldChapters = viewerChaptersRelay.value
|
||||
|
||||
// Add new references first to avoid unnecessary recycling
|
||||
newChapters.ref()
|
||||
oldChapters?.unref()
|
||||
|
||||
viewerChaptersRelay.call(newChapters)
|
||||
ViewerChapters(
|
||||
chapter,
|
||||
chapterList.getOrNull(chapterPos - 1),
|
||||
chapterList.getOrNull(chapterPos + 1)
|
||||
)
|
||||
}
|
||||
)
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.doOnNext { newChapters ->
|
||||
val oldChapters = viewerChaptersRelay.value
|
||||
|
||||
// Add new references first to avoid unnecessary recycling
|
||||
newChapters.ref()
|
||||
oldChapters?.unref()
|
||||
|
||||
viewerChaptersRelay.call(newChapters)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -292,10 +304,10 @@ class ReaderPresenter(
|
||||
|
||||
activeChapterSubscription?.unsubscribe()
|
||||
activeChapterSubscription = getLoadObservable(loader, chapter)
|
||||
.toCompletable()
|
||||
.onErrorComplete()
|
||||
.subscribe()
|
||||
.also(::add)
|
||||
.toCompletable()
|
||||
.onErrorComplete()
|
||||
.subscribe()
|
||||
.also(::add)
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -310,13 +322,16 @@ class ReaderPresenter(
|
||||
|
||||
activeChapterSubscription?.unsubscribe()
|
||||
activeChapterSubscription = getLoadObservable(loader, chapter)
|
||||
.doOnSubscribe { isLoadingAdjacentChapterRelay.call(true) }
|
||||
.doOnUnsubscribe { isLoadingAdjacentChapterRelay.call(false) }
|
||||
.subscribeFirst({ view, _ ->
|
||||
.doOnSubscribe { isLoadingAdjacentChapterRelay.call(true) }
|
||||
.doOnUnsubscribe { isLoadingAdjacentChapterRelay.call(false) }
|
||||
.subscribeFirst(
|
||||
{ view, _ ->
|
||||
view.moveToPageIndex(0)
|
||||
}, { _, _ ->
|
||||
},
|
||||
{ _, _ ->
|
||||
// Ignore onError event, viewers handle that state
|
||||
})
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -333,12 +348,12 @@ class ReaderPresenter(
|
||||
val loader = loader ?: return
|
||||
|
||||
loader.loadChapter(chapter)
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
// Update current chapters whenever a chapter is preloaded
|
||||
.doOnCompleted { viewerChaptersRelay.value?.let(viewerChaptersRelay::call) }
|
||||
.onErrorComplete()
|
||||
.subscribe()
|
||||
.also(::add)
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
// Update current chapters whenever a chapter is preloaded
|
||||
.doOnCompleted { viewerChaptersRelay.value?.let(viewerChaptersRelay::call) }
|
||||
.onErrorComplete()
|
||||
.subscribe()
|
||||
.also(::add)
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -380,9 +395,9 @@ class ReaderPresenter(
|
||||
*/
|
||||
private fun saveChapterProgress(chapter: ReaderChapter) {
|
||||
db.updateChapterProgress(chapter.chapter).asRxCompletable()
|
||||
.onErrorComplete()
|
||||
.subscribeOn(Schedulers.io())
|
||||
.subscribe()
|
||||
.onErrorComplete()
|
||||
.subscribeOn(Schedulers.io())
|
||||
.subscribe()
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -391,9 +406,9 @@ class ReaderPresenter(
|
||||
private fun saveChapterHistory(chapter: ReaderChapter) {
|
||||
val history = History.create(chapter.chapter).apply { last_read = Date().time }
|
||||
db.updateHistoryLastRead(history).asRxCompletable()
|
||||
.onErrorComplete()
|
||||
.subscribeOn(Schedulers.io())
|
||||
.subscribe()
|
||||
.onErrorComplete()
|
||||
.subscribeOn(Schedulers.io())
|
||||
.subscribe()
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -456,18 +471,18 @@ class ReaderPresenter(
|
||||
db.updateMangaViewer(manga).executeAsBlocking()
|
||||
|
||||
Observable.timer(250, TimeUnit.MILLISECONDS, AndroidSchedulers.mainThread())
|
||||
.subscribeFirst({ view, _ ->
|
||||
val currChapters = viewerChaptersRelay.value
|
||||
if (currChapters != null) {
|
||||
// Save current page
|
||||
val currChapter = currChapters.currChapter
|
||||
currChapter.requestedPage = currChapter.chapter.last_page_read
|
||||
.subscribeFirst({ view, _ ->
|
||||
val currChapters = viewerChaptersRelay.value
|
||||
if (currChapters != null) {
|
||||
// Save current page
|
||||
val currChapter = currChapters.currChapter
|
||||
currChapter.requestedPage = currChapter.chapter.last_page_read
|
||||
|
||||
// Emit manga and chapters to the new viewer
|
||||
view.setManga(manga)
|
||||
view.setChapters(currChapters)
|
||||
}
|
||||
})
|
||||
// Emit manga and chapters to the new viewer
|
||||
view.setManga(manga)
|
||||
view.setChapters(currChapters)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -484,7 +499,7 @@ class ReaderPresenter(
|
||||
// Build destination file.
|
||||
val filenameSuffix = " - ${page.number}.${type.extension}"
|
||||
val filename = DiskUtil.buildValidFilename(
|
||||
"${manga.title} - ${chapter.name}".takeBytes(MAX_FILE_NAME_BYTES - filenameSuffix.byteSize())
|
||||
"${manga.title} - ${chapter.name}".takeBytes(MAX_FILE_NAME_BYTES - filenameSuffix.byteSize())
|
||||
) + filenameSuffix
|
||||
|
||||
val destFile = File(directory, filename)
|
||||
@@ -509,23 +524,25 @@ class ReaderPresenter(
|
||||
notifier.onClear()
|
||||
|
||||
// Pictures directory.
|
||||
val destDir = File(Environment.getExternalStorageDirectory().absolutePath +
|
||||
val destDir = File(
|
||||
Environment.getExternalStorageDirectory().absolutePath +
|
||||
File.separator + Environment.DIRECTORY_PICTURES +
|
||||
File.separator + "Tachiyomi")
|
||||
File.separator + "Tachiyomi"
|
||||
)
|
||||
|
||||
// Copy file in background.
|
||||
Observable.fromCallable { saveImage(page, destDir, manga) }
|
||||
.doOnNext { file ->
|
||||
DiskUtil.scanMedia(context, file)
|
||||
notifier.onComplete(file)
|
||||
}
|
||||
.doOnError { notifier.onError(it.message) }
|
||||
.subscribeOn(Schedulers.io())
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.subscribeFirst(
|
||||
{ view, file -> view.onSaveImageResult(SaveImageResult.Success(file)) },
|
||||
{ view, error -> view.onSaveImageResult(SaveImageResult.Error(error)) }
|
||||
)
|
||||
.doOnNext { file ->
|
||||
DiskUtil.scanMedia(context, file)
|
||||
notifier.onComplete(file)
|
||||
}
|
||||
.doOnError { notifier.onError(it.message) }
|
||||
.subscribeOn(Schedulers.io())
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.subscribeFirst(
|
||||
{ view, file -> view.onSaveImageResult(SaveImageResult.Success(file)) },
|
||||
{ view, error -> view.onSaveImageResult(SaveImageResult.Error(error)) }
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -543,13 +560,13 @@ class ReaderPresenter(
|
||||
val destDir = File(context.cacheDir, "shared_image")
|
||||
|
||||
Observable.fromCallable { destDir.deleteRecursively() } // Keep only the last shared file
|
||||
.map { saveImage(page, destDir, manga) }
|
||||
.subscribeOn(Schedulers.io())
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.subscribeFirst(
|
||||
{ view, file -> view.onShareImageResult(file) },
|
||||
{ _, _ -> /* Empty */ }
|
||||
)
|
||||
.map { saveImage(page, destDir, manga) }
|
||||
.subscribeOn(Schedulers.io())
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.subscribeFirst(
|
||||
{ view, file -> view.onShareImageResult(file) },
|
||||
{ _, _ -> /* Empty */ }
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -561,28 +578,28 @@ class ReaderPresenter(
|
||||
val stream = page.stream ?: return
|
||||
|
||||
Observable
|
||||
.fromCallable {
|
||||
if (manga.source == LocalSource.ID) {
|
||||
val context = Injekt.get<Application>()
|
||||
LocalSource.updateCover(context, manga, stream())
|
||||
R.string.cover_updated
|
||||
.fromCallable {
|
||||
if (manga.source == LocalSource.ID) {
|
||||
val context = Injekt.get<Application>()
|
||||
LocalSource.updateCover(context, manga, stream())
|
||||
R.string.cover_updated
|
||||
SetAsCoverResult.Success
|
||||
} else {
|
||||
val thumbUrl = manga.thumbnail_url ?: throw Exception("Image url not found")
|
||||
if (manga.favorite) {
|
||||
coverCache.copyToCache(thumbUrl, stream())
|
||||
SetAsCoverResult.Success
|
||||
} else {
|
||||
val thumbUrl = manga.thumbnail_url ?: throw Exception("Image url not found")
|
||||
if (manga.favorite) {
|
||||
coverCache.copyToCache(thumbUrl, stream())
|
||||
SetAsCoverResult.Success
|
||||
} else {
|
||||
SetAsCoverResult.AddToLibraryFirst
|
||||
}
|
||||
SetAsCoverResult.AddToLibraryFirst
|
||||
}
|
||||
}
|
||||
.subscribeOn(Schedulers.io())
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.subscribeFirst(
|
||||
{ view, result -> view.onSetAsCoverResult(result) },
|
||||
{ view, _ -> view.onSetAsCoverResult(SetAsCoverResult.Error) }
|
||||
)
|
||||
}
|
||||
.subscribeOn(Schedulers.io())
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.subscribeFirst(
|
||||
{ view, result -> view.onSetAsCoverResult(result) },
|
||||
{ view, _ -> view.onSetAsCoverResult(SetAsCoverResult.Error) }
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -613,8 +630,9 @@ class ReaderPresenter(
|
||||
val trackManager = Injekt.get<TrackManager>()
|
||||
|
||||
db.getTracks(manga).asRxSingle()
|
||||
.flatMapCompletable { trackList ->
|
||||
Completable.concat(trackList.map { track ->
|
||||
.flatMapCompletable { trackList ->
|
||||
Completable.concat(
|
||||
trackList.map { track ->
|
||||
val service = trackManager.getService(track.sync_id)
|
||||
if (service != null && service.isLogged && chapterRead > track.last_chapter_read) {
|
||||
track.last_chapter_read = chapterRead
|
||||
@@ -622,17 +640,18 @@ class ReaderPresenter(
|
||||
// We wan't these to execute even if the presenter is destroyed and leaks
|
||||
// for a while. The view can still be garbage collected.
|
||||
Observable.defer { service.update(track) }
|
||||
.map { db.insertTrack(track).executeAsBlocking() }
|
||||
.toCompletable()
|
||||
.onErrorComplete()
|
||||
.map { db.insertTrack(track).executeAsBlocking() }
|
||||
.toCompletable()
|
||||
.onErrorComplete()
|
||||
} else {
|
||||
Completable.complete()
|
||||
}
|
||||
})
|
||||
}
|
||||
.onErrorComplete()
|
||||
.subscribeOn(Schedulers.io())
|
||||
.subscribe()
|
||||
}
|
||||
)
|
||||
}
|
||||
.onErrorComplete()
|
||||
.subscribeOn(Schedulers.io())
|
||||
.subscribe()
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -648,19 +667,19 @@ class ReaderPresenter(
|
||||
if (removeAfterReadSlots == -1) return
|
||||
|
||||
Completable
|
||||
.fromCallable {
|
||||
// Position of the read chapter
|
||||
val position = chapterList.indexOf(chapter)
|
||||
.fromCallable {
|
||||
// Position of the read chapter
|
||||
val position = chapterList.indexOf(chapter)
|
||||
|
||||
// Retrieve chapter to delete according to preference
|
||||
val chapterToDelete = chapterList.getOrNull(position - removeAfterReadSlots)
|
||||
if (chapterToDelete != null) {
|
||||
downloadManager.enqueueDeleteChapters(listOf(chapterToDelete.chapter), manga)
|
||||
}
|
||||
// Retrieve chapter to delete according to preference
|
||||
val chapterToDelete = chapterList.getOrNull(position - removeAfterReadSlots)
|
||||
if (chapterToDelete != null) {
|
||||
downloadManager.enqueueDeleteChapters(listOf(chapterToDelete.chapter), manga)
|
||||
}
|
||||
.onErrorComplete()
|
||||
.subscribeOn(Schedulers.io())
|
||||
.subscribe()
|
||||
}
|
||||
.onErrorComplete()
|
||||
.subscribeOn(Schedulers.io())
|
||||
.subscribe()
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -669,9 +688,9 @@ class ReaderPresenter(
|
||||
*/
|
||||
private fun deletePendingChapters() {
|
||||
Completable.fromCallable { downloadManager.deletePendingChapters() }
|
||||
.onErrorComplete()
|
||||
.subscribeOn(Schedulers.io())
|
||||
.subscribe()
|
||||
.onErrorComplete()
|
||||
.subscribeOn(Schedulers.io())
|
||||
.subscribe()
|
||||
}
|
||||
|
||||
companion object {
|
||||
|
||||
@@ -37,12 +37,12 @@ class SaveImageNotifier(private val context: Context) {
|
||||
*/
|
||||
fun onComplete(file: File) {
|
||||
val bitmap = GlideApp.with(context)
|
||||
.asBitmap()
|
||||
.load(file)
|
||||
.diskCacheStrategy(DiskCacheStrategy.NONE)
|
||||
.skipMemoryCache(true)
|
||||
.submit(720, 1280)
|
||||
.get()
|
||||
.asBitmap()
|
||||
.load(file)
|
||||
.diskCacheStrategy(DiskCacheStrategy.NONE)
|
||||
.skipMemoryCache(true)
|
||||
.submit(720, 1280)
|
||||
.get()
|
||||
|
||||
if (bitmap != null) {
|
||||
showCompleteNotification(file, bitmap)
|
||||
@@ -66,13 +66,17 @@ class SaveImageNotifier(private val context: Context) {
|
||||
|
||||
setContentIntent(NotificationHandler.openImagePendingActivity(context, file))
|
||||
// Share action
|
||||
addAction(R.drawable.ic_share_24dp,
|
||||
context.getString(R.string.action_share),
|
||||
NotificationReceiver.shareImagePendingBroadcast(context, file.absolutePath, notificationId))
|
||||
addAction(
|
||||
R.drawable.ic_share_24dp,
|
||||
context.getString(R.string.action_share),
|
||||
NotificationReceiver.shareImagePendingBroadcast(context, file.absolutePath, notificationId)
|
||||
)
|
||||
// Delete action
|
||||
addAction(R.drawable.ic_delete_24dp,
|
||||
context.getString(R.string.action_delete),
|
||||
NotificationReceiver.deleteImagePendingBroadcast(context, file.absolutePath, notificationId))
|
||||
addAction(
|
||||
R.drawable.ic_delete_24dp,
|
||||
context.getString(R.string.action_delete),
|
||||
NotificationReceiver.deleteImagePendingBroadcast(context, file.absolutePath, notificationId)
|
||||
)
|
||||
|
||||
updateNotification()
|
||||
}
|
||||
|
||||
@@ -31,34 +31,34 @@ class ChapterLoader(
|
||||
}
|
||||
|
||||
return Observable.just(chapter)
|
||||
.doOnNext { chapter.state = ReaderChapter.State.Loading }
|
||||
.observeOn(Schedulers.io())
|
||||
.flatMap { readerChapter ->
|
||||
Timber.d("Loading pages for ${chapter.chapter.name}")
|
||||
.doOnNext { chapter.state = ReaderChapter.State.Loading }
|
||||
.observeOn(Schedulers.io())
|
||||
.flatMap { readerChapter ->
|
||||
Timber.d("Loading pages for ${chapter.chapter.name}")
|
||||
|
||||
val loader = getPageLoader(readerChapter)
|
||||
chapter.pageLoader = loader
|
||||
val loader = getPageLoader(readerChapter)
|
||||
chapter.pageLoader = loader
|
||||
|
||||
loader.getPages().take(1).doOnNext { pages ->
|
||||
pages.forEach { it.chapter = chapter }
|
||||
}
|
||||
loader.getPages().take(1).doOnNext { pages ->
|
||||
pages.forEach { it.chapter = chapter }
|
||||
}
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.doOnNext { pages ->
|
||||
if (pages.isEmpty()) {
|
||||
throw Exception("Page list is empty")
|
||||
}
|
||||
|
||||
chapter.state = ReaderChapter.State.Loaded(pages)
|
||||
|
||||
// If the chapter is partially read, set the starting page to the last the user read
|
||||
// otherwise use the requested page.
|
||||
if (!chapter.chapter.read) {
|
||||
chapter.requestedPage = chapter.chapter.last_page_read
|
||||
}
|
||||
}
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.doOnNext { pages ->
|
||||
if (pages.isEmpty()) {
|
||||
throw Exception("Page list is empty")
|
||||
}
|
||||
.toCompletable()
|
||||
.doOnError { chapter.state = ReaderChapter.State.Error(it) }
|
||||
|
||||
chapter.state = ReaderChapter.State.Loaded(pages)
|
||||
|
||||
// If the chapter is partially read, set the starting page to the last the user read
|
||||
// otherwise use the requested page.
|
||||
if (!chapter.chapter.read) {
|
||||
chapter.requestedPage = chapter.chapter.last_page_read
|
||||
}
|
||||
}
|
||||
.toCompletable()
|
||||
.doOnError { chapter.state = ReaderChapter.State.Error(it) }
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -19,16 +19,16 @@ class DirectoryPageLoader(val file: File) : PageLoader() {
|
||||
*/
|
||||
override fun getPages(): Observable<List<ReaderPage>> {
|
||||
return file.listFiles()
|
||||
.filter { !it.isDirectory && ImageUtil.isImage(it.name) { FileInputStream(it) } }
|
||||
.sortedWith(Comparator<File> { f1, f2 -> f1.name.compareToCaseInsensitiveNaturalOrder(f2.name) })
|
||||
.mapIndexed { i, file ->
|
||||
val streamFn = { FileInputStream(file) }
|
||||
ReaderPage(i).apply {
|
||||
stream = streamFn
|
||||
status = Page.READY
|
||||
}
|
||||
.filter { !it.isDirectory && ImageUtil.isImage(it.name) { FileInputStream(it) } }
|
||||
.sortedWith(Comparator<File> { f1, f2 -> f1.name.compareToCaseInsensitiveNaturalOrder(f2.name) })
|
||||
.mapIndexed { i, file ->
|
||||
val streamFn = { FileInputStream(file) }
|
||||
ReaderPage(i).apply {
|
||||
stream = streamFn
|
||||
status = Page.READY
|
||||
}
|
||||
.let { Observable.just(it) }
|
||||
}
|
||||
.let { Observable.just(it) }
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -31,15 +31,15 @@ class DownloadPageLoader(
|
||||
*/
|
||||
override fun getPages(): Observable<List<ReaderPage>> {
|
||||
return downloadManager.buildPageList(source, manga, chapter.chapter)
|
||||
.map { pages ->
|
||||
pages.map { page ->
|
||||
ReaderPage(page.index, page.url, page.imageUrl) {
|
||||
context.contentResolver.openInputStream(page.uri ?: Uri.EMPTY)!!
|
||||
}.apply {
|
||||
status = Page.READY
|
||||
}
|
||||
.map { pages ->
|
||||
pages.map { page ->
|
||||
ReaderPage(page.index, page.url, page.imageUrl) {
|
||||
context.contentResolver.openInputStream(page.uri ?: Uri.EMPTY)!!
|
||||
}.apply {
|
||||
status = Page.READY
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun getPage(page: ReaderPage): Observable<Int> {
|
||||
|
||||
@@ -30,24 +30,26 @@ class EpubPageLoader(file: File) : PageLoader() {
|
||||
*/
|
||||
override fun getPages(): Observable<List<ReaderPage>> {
|
||||
return epub.getImagesFromPages()
|
||||
.mapIndexed { i, path ->
|
||||
val streamFn = { epub.getInputStream(epub.getEntry(path)!!) }
|
||||
ReaderPage(i).apply {
|
||||
stream = streamFn
|
||||
status = Page.READY
|
||||
}
|
||||
.mapIndexed { i, path ->
|
||||
val streamFn = { epub.getInputStream(epub.getEntry(path)!!) }
|
||||
ReaderPage(i).apply {
|
||||
stream = streamFn
|
||||
status = Page.READY
|
||||
}
|
||||
.let { Observable.just(it) }
|
||||
}
|
||||
.let { Observable.just(it) }
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an observable that emits a ready state unless the loader was recycled.
|
||||
*/
|
||||
override fun getPage(page: ReaderPage): Observable<Int> {
|
||||
return Observable.just(if (isRecycled) {
|
||||
Page.ERROR
|
||||
} else {
|
||||
Page.READY
|
||||
})
|
||||
return Observable.just(
|
||||
if (isRecycled) {
|
||||
Page.ERROR
|
||||
} else {
|
||||
Page.READY
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -42,16 +42,19 @@ class HttpPageLoader(
|
||||
|
||||
init {
|
||||
subscriptions += Observable.defer { Observable.just(queue.take().page) }
|
||||
.filter { it.status == Page.QUEUE }
|
||||
.concatMap { source.fetchImageFromCacheThenNet(it) }
|
||||
.repeat()
|
||||
.subscribeOn(Schedulers.io())
|
||||
.subscribe({
|
||||
}, { error ->
|
||||
.filter { it.status == Page.QUEUE }
|
||||
.concatMap { source.fetchImageFromCacheThenNet(it) }
|
||||
.repeat()
|
||||
.subscribeOn(Schedulers.io())
|
||||
.subscribe(
|
||||
{
|
||||
},
|
||||
{ error ->
|
||||
if (error !is InterruptedException) {
|
||||
Timber.e(error)
|
||||
}
|
||||
})
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -66,14 +69,14 @@ class HttpPageLoader(
|
||||
val pages = chapter.pages
|
||||
if (pages != null) {
|
||||
Completable
|
||||
.fromAction {
|
||||
// Convert to pages without reader information
|
||||
val pagesToSave = pages.map { Page(it.index, it.url, it.imageUrl) }
|
||||
chapterCache.putPageListToCache(chapter.chapter, pagesToSave)
|
||||
}
|
||||
.onErrorComplete()
|
||||
.subscribeOn(Schedulers.io())
|
||||
.subscribe()
|
||||
.fromAction {
|
||||
// Convert to pages without reader information
|
||||
val pagesToSave = pages.map { Page(it.index, it.url, it.imageUrl) }
|
||||
chapterCache.putPageListToCache(chapter.chapter, pagesToSave)
|
||||
}
|
||||
.onErrorComplete()
|
||||
.subscribeOn(Schedulers.io())
|
||||
.subscribe()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -83,14 +86,14 @@ class HttpPageLoader(
|
||||
*/
|
||||
override fun getPages(): Observable<List<ReaderPage>> {
|
||||
return chapterCache
|
||||
.getPageListFromCache(chapter.chapter)
|
||||
.onErrorResumeNext { source.fetchPageList(chapter.chapter) }
|
||||
.map { pages ->
|
||||
pages.mapIndexed { index, page ->
|
||||
// Don't trust sources and use our own indexing
|
||||
ReaderPage(index, page.url, page.imageUrl)
|
||||
}
|
||||
.getPageListFromCache(chapter.chapter)
|
||||
.onErrorResumeNext { source.fetchPageList(chapter.chapter) }
|
||||
.map { pages ->
|
||||
pages.mapIndexed { index, page ->
|
||||
// Don't trust sources and use our own indexing
|
||||
ReaderPage(index, page.url, page.imageUrl)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -121,16 +124,16 @@ class HttpPageLoader(
|
||||
queuedPages += preloadNextPages(page, preloadSize)
|
||||
|
||||
statusSubject.startWith(page.status)
|
||||
.doOnUnsubscribe {
|
||||
queuedPages.forEach {
|
||||
if (it.page.status == Page.QUEUE) {
|
||||
queue.remove(it)
|
||||
}
|
||||
.doOnUnsubscribe {
|
||||
queuedPages.forEach {
|
||||
if (it.page.status == Page.QUEUE) {
|
||||
queue.remove(it)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
.subscribeOn(Schedulers.io())
|
||||
.unsubscribeOn(Schedulers.io())
|
||||
.subscribeOn(Schedulers.io())
|
||||
.unsubscribeOn(Schedulers.io())
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -143,12 +146,12 @@ class HttpPageLoader(
|
||||
if (pageIndex == pages.lastIndex) return emptyList()
|
||||
|
||||
return pages
|
||||
.subList(pageIndex + 1, min(pageIndex + 1 + amount, pages.size))
|
||||
.mapNotNull {
|
||||
if (it.status == Page.QUEUE) {
|
||||
PriorityPage(it, 0).apply { queue.offer(this) }
|
||||
} else null
|
||||
}
|
||||
.subList(pageIndex + 1, min(pageIndex + 1 + amount, pages.size))
|
||||
.mapNotNull {
|
||||
if (it.status == Page.QUEUE) {
|
||||
PriorityPage(it, 0).apply { queue.offer(this) }
|
||||
} else null
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -186,19 +189,20 @@ class HttpPageLoader(
|
||||
* @param page the page whose source image has to be downloaded.
|
||||
*/
|
||||
private fun HttpSource.fetchImageFromCacheThenNet(page: ReaderPage): Observable<ReaderPage> {
|
||||
return if (page.imageUrl.isNullOrEmpty())
|
||||
return if (page.imageUrl.isNullOrEmpty()) {
|
||||
getImageUrl(page).flatMap { getCachedImage(it) }
|
||||
else
|
||||
} else {
|
||||
getCachedImage(page)
|
||||
}
|
||||
}
|
||||
|
||||
private fun HttpSource.getImageUrl(page: ReaderPage): Observable<ReaderPage> {
|
||||
page.status = Page.LOAD_PAGE
|
||||
return fetchImageUrl(page)
|
||||
.doOnError { page.status = Page.ERROR }
|
||||
.onErrorReturn { null }
|
||||
.doOnNext { page.imageUrl = it }
|
||||
.map { page }
|
||||
.doOnError { page.status = Page.ERROR }
|
||||
.onErrorReturn { null }
|
||||
.doOnNext { page.imageUrl = it }
|
||||
.map { page }
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -211,19 +215,19 @@ class HttpPageLoader(
|
||||
val imageUrl = page.imageUrl ?: return Observable.just(page)
|
||||
|
||||
return Observable.just(page)
|
||||
.flatMap {
|
||||
if (!chapterCache.isImageInCache(imageUrl)) {
|
||||
cacheImage(page)
|
||||
} else {
|
||||
Observable.just(page)
|
||||
}
|
||||
.flatMap {
|
||||
if (!chapterCache.isImageInCache(imageUrl)) {
|
||||
cacheImage(page)
|
||||
} else {
|
||||
Observable.just(page)
|
||||
}
|
||||
.doOnNext {
|
||||
page.stream = { chapterCache.getImageFile(imageUrl).inputStream() }
|
||||
page.status = Page.READY
|
||||
}
|
||||
.doOnError { page.status = Page.ERROR }
|
||||
.onErrorReturn { page }
|
||||
}
|
||||
.doOnNext {
|
||||
page.stream = { chapterCache.getImageFile(imageUrl).inputStream() }
|
||||
page.status = Page.READY
|
||||
}
|
||||
.doOnError { page.status = Page.ERROR }
|
||||
.onErrorReturn { page }
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -234,7 +238,7 @@ class HttpPageLoader(
|
||||
private fun HttpSource.cacheImage(page: ReaderPage): Observable<ReaderPage> {
|
||||
page.status = Page.DOWNLOAD_IMAGE
|
||||
return fetchImage(page)
|
||||
.doOnNext { chapterCache.putImageToCache(page.imageUrl!!, it) }
|
||||
.map { page }
|
||||
.doOnNext { chapterCache.putImageToCache(page.imageUrl!!, it) }
|
||||
.map { page }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -43,28 +43,30 @@ class RarPageLoader(file: File) : PageLoader() {
|
||||
*/
|
||||
override fun getPages(): Observable<List<ReaderPage>> {
|
||||
return archive.fileHeaders
|
||||
.filter { !it.isDirectory && ImageUtil.isImage(it.fileNameString) { archive.getInputStream(it) } }
|
||||
.sortedWith(Comparator<FileHeader> { f1, f2 -> f1.fileNameString.compareToCaseInsensitiveNaturalOrder(f2.fileNameString) })
|
||||
.mapIndexed { i, header ->
|
||||
val streamFn = { getStream(header) }
|
||||
.filter { !it.isDirectory && ImageUtil.isImage(it.fileNameString) { archive.getInputStream(it) } }
|
||||
.sortedWith(Comparator<FileHeader> { f1, f2 -> f1.fileNameString.compareToCaseInsensitiveNaturalOrder(f2.fileNameString) })
|
||||
.mapIndexed { i, header ->
|
||||
val streamFn = { getStream(header) }
|
||||
|
||||
ReaderPage(i).apply {
|
||||
stream = streamFn
|
||||
status = Page.READY
|
||||
}
|
||||
ReaderPage(i).apply {
|
||||
stream = streamFn
|
||||
status = Page.READY
|
||||
}
|
||||
.let { Observable.just(it) }
|
||||
}
|
||||
.let { Observable.just(it) }
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an observable that emits a ready state unless the loader was recycled.
|
||||
*/
|
||||
override fun getPage(page: ReaderPage): Observable<Int> {
|
||||
return Observable.just(if (isRecycled) {
|
||||
Page.ERROR
|
||||
} else {
|
||||
Page.READY
|
||||
})
|
||||
return Observable.just(
|
||||
if (isRecycled) {
|
||||
Page.ERROR
|
||||
} else {
|
||||
Page.READY
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -33,26 +33,28 @@ class ZipPageLoader(file: File) : PageLoader() {
|
||||
*/
|
||||
override fun getPages(): Observable<List<ReaderPage>> {
|
||||
return zip.entries().toList()
|
||||
.filter { !it.isDirectory && ImageUtil.isImage(it.name) { zip.getInputStream(it) } }
|
||||
.sortedWith(Comparator<ZipEntry> { f1, f2 -> f1.name.compareToCaseInsensitiveNaturalOrder(f2.name) })
|
||||
.mapIndexed { i, entry ->
|
||||
val streamFn = { zip.getInputStream(entry) }
|
||||
ReaderPage(i).apply {
|
||||
stream = streamFn
|
||||
status = Page.READY
|
||||
}
|
||||
.filter { !it.isDirectory && ImageUtil.isImage(it.name) { zip.getInputStream(it) } }
|
||||
.sortedWith(Comparator<ZipEntry> { f1, f2 -> f1.name.compareToCaseInsensitiveNaturalOrder(f2.name) })
|
||||
.mapIndexed { i, entry ->
|
||||
val streamFn = { zip.getInputStream(entry) }
|
||||
ReaderPage(i).apply {
|
||||
stream = streamFn
|
||||
status = Page.READY
|
||||
}
|
||||
.let { Observable.just(it) }
|
||||
}
|
||||
.let { Observable.just(it) }
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an observable that emits a ready state unless the loader was recycled.
|
||||
*/
|
||||
override fun getPage(page: ReaderPage): Observable<Int> {
|
||||
return Observable.just(if (isRecycled) {
|
||||
Page.ERROR
|
||||
} else {
|
||||
Page.READY
|
||||
})
|
||||
return Observable.just(
|
||||
if (isRecycled) {
|
||||
Page.ERROR
|
||||
} else {
|
||||
Page.READY
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,11 +8,11 @@ import timber.log.Timber
|
||||
data class ReaderChapter(val chapter: Chapter) {
|
||||
|
||||
var state: State =
|
||||
State.Wait
|
||||
State.Wait
|
||||
set(value) {
|
||||
field = value
|
||||
stateRelay.call(value)
|
||||
}
|
||||
field = value
|
||||
stateRelay.call(value)
|
||||
}
|
||||
|
||||
private val stateRelay by lazy { BehaviorRelay.create(state) }
|
||||
|
||||
|
||||
@@ -61,9 +61,10 @@ class ReaderProgressBar @JvmOverloads constructor(
|
||||
* The rotation animation to use while the progress bar is visible.
|
||||
*/
|
||||
private val rotationAnimation by lazy {
|
||||
RotateAnimation(0f, 360f,
|
||||
Animation.RELATIVE_TO_SELF, 0.5f,
|
||||
Animation.RELATIVE_TO_SELF, 0.5f
|
||||
RotateAnimation(
|
||||
0f, 360f,
|
||||
Animation.RELATIVE_TO_SELF, 0.5f,
|
||||
Animation.RELATIVE_TO_SELF, 0.5f
|
||||
).apply {
|
||||
interpolator = LinearInterpolator()
|
||||
repeatCount = Animation.INFINITE
|
||||
|
||||
@@ -126,8 +126,8 @@ class PagerPageHolder(
|
||||
|
||||
val loader = page.chapter.pageLoader ?: return
|
||||
statusSubscription = loader.getPage(page)
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.subscribe { processStatus(it) }
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.subscribe { processStatus(it) }
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -137,11 +137,11 @@ class PagerPageHolder(
|
||||
progressSubscription?.unsubscribe()
|
||||
|
||||
progressSubscription = Observable.interval(100, TimeUnit.MILLISECONDS)
|
||||
.map { page.progress }
|
||||
.distinctUntilChanged()
|
||||
.onBackpressureLatest()
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.subscribe { value -> progressBar.setProgress(value) }
|
||||
.map { page.progress }
|
||||
.distinctUntilChanged()
|
||||
.onBackpressureLatest()
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.subscribe { value -> progressBar.setProgress(value) }
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -233,25 +233,25 @@ class PagerPageHolder(
|
||||
|
||||
var openStream: InputStream? = null
|
||||
readImageHeaderSubscription = Observable
|
||||
.fromCallable {
|
||||
val stream = streamFn().buffered(16)
|
||||
openStream = stream
|
||||
.fromCallable {
|
||||
val stream = streamFn().buffered(16)
|
||||
openStream = stream
|
||||
|
||||
ImageUtil.findImageType(stream) == ImageUtil.ImageType.GIF
|
||||
ImageUtil.findImageType(stream) == ImageUtil.ImageType.GIF
|
||||
}
|
||||
.subscribeOn(Schedulers.io())
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.doOnNext { isAnimated ->
|
||||
if (!isAnimated) {
|
||||
initSubsamplingImageView().setImage(ImageSource.inputStream(openStream!!))
|
||||
} else {
|
||||
initImageView().setImage(openStream!!)
|
||||
}
|
||||
.subscribeOn(Schedulers.io())
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.doOnNext { isAnimated ->
|
||||
if (!isAnimated) {
|
||||
initSubsamplingImageView().setImage(ImageSource.inputStream(openStream!!))
|
||||
} else {
|
||||
initImageView().setImage(openStream!!)
|
||||
}
|
||||
}
|
||||
// Keep the Rx stream alive to close the input stream only when unsubscribed
|
||||
.flatMap { Observable.never<Unit>() }
|
||||
.doOnUnsubscribe { openStream?.close() }
|
||||
.subscribe({}, {})
|
||||
}
|
||||
// Keep the Rx stream alive to close the input stream only when unsubscribed
|
||||
.flatMap { Observable.never<Unit>() }
|
||||
.doOnUnsubscribe { openStream?.close() }
|
||||
.subscribe({}, {})
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -283,7 +283,6 @@ class PagerPageHolder(
|
||||
@SuppressLint("PrivateResource")
|
||||
private fun createProgressBar(): ReaderProgressBar {
|
||||
return ReaderProgressBar(context, null).apply {
|
||||
|
||||
val size = 48.dpToPx
|
||||
layoutParams = LayoutParams(size, size).apply {
|
||||
gravity = Gravity.CENTER
|
||||
@@ -435,35 +434,35 @@ class PagerPageHolder(
|
||||
*/
|
||||
private fun ImageView.setImage(stream: InputStream) {
|
||||
GlideApp.with(this)
|
||||
.load(stream)
|
||||
.skipMemoryCache(true)
|
||||
.diskCacheStrategy(DiskCacheStrategy.NONE)
|
||||
.transition(DrawableTransitionOptions.with(NoTransition.getFactory()))
|
||||
.listener(object : RequestListener<Drawable> {
|
||||
override fun onLoadFailed(
|
||||
e: GlideException?,
|
||||
model: Any?,
|
||||
target: Target<Drawable>?,
|
||||
isFirstResource: Boolean
|
||||
): Boolean {
|
||||
onImageDecodeError()
|
||||
return false
|
||||
}
|
||||
.load(stream)
|
||||
.skipMemoryCache(true)
|
||||
.diskCacheStrategy(DiskCacheStrategy.NONE)
|
||||
.transition(DrawableTransitionOptions.with(NoTransition.getFactory()))
|
||||
.listener(object : RequestListener<Drawable> {
|
||||
override fun onLoadFailed(
|
||||
e: GlideException?,
|
||||
model: Any?,
|
||||
target: Target<Drawable>?,
|
||||
isFirstResource: Boolean
|
||||
): Boolean {
|
||||
onImageDecodeError()
|
||||
return false
|
||||
}
|
||||
|
||||
override fun onResourceReady(
|
||||
resource: Drawable?,
|
||||
model: Any?,
|
||||
target: Target<Drawable>?,
|
||||
dataSource: DataSource?,
|
||||
isFirstResource: Boolean
|
||||
): Boolean {
|
||||
if (resource is GifDrawable) {
|
||||
resource.setLoopCount(GifDrawable.LOOP_INTRINSIC)
|
||||
}
|
||||
onImageDecoded()
|
||||
return false
|
||||
override fun onResourceReady(
|
||||
resource: Drawable?,
|
||||
model: Any?,
|
||||
target: Target<Drawable>?,
|
||||
dataSource: DataSource?,
|
||||
isFirstResource: Boolean
|
||||
): Boolean {
|
||||
if (resource is GifDrawable) {
|
||||
resource.setLoopCount(GifDrawable.LOOP_INTRINSIC)
|
||||
}
|
||||
})
|
||||
.into(this)
|
||||
onImageDecoded()
|
||||
return false
|
||||
}
|
||||
})
|
||||
.into(this)
|
||||
}
|
||||
}
|
||||
|
||||
+9
-9
@@ -140,17 +140,17 @@ class PagerTransitionHolder(
|
||||
private fun observeStatus(chapter: ReaderChapter) {
|
||||
statusSubscription?.unsubscribe()
|
||||
statusSubscription = chapter.stateObserver
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.subscribe { state ->
|
||||
pagesContainer.removeAllViews()
|
||||
when (state) {
|
||||
is ReaderChapter.State.Wait -> {
|
||||
}
|
||||
is ReaderChapter.State.Loading -> setLoading()
|
||||
is ReaderChapter.State.Error -> setError(state.error)
|
||||
is ReaderChapter.State.Loaded -> setLoaded()
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.subscribe { state ->
|
||||
pagesContainer.removeAllViews()
|
||||
when (state) {
|
||||
is ReaderChapter.State.Wait -> {
|
||||
}
|
||||
is ReaderChapter.State.Loading -> setLoading()
|
||||
is ReaderChapter.State.Error -> setError(state.error)
|
||||
is ReaderChapter.State.Loaded -> setLoaded()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -57,12 +57,13 @@ class PagerViewerAdapter(private val viewer: PagerViewer) : ViewPagerAdapter() {
|
||||
|
||||
// Add next chapter transition and pages.
|
||||
nextTransition = ChapterTransition.Next(chapters.currChapter, chapters.nextChapter)
|
||||
.also {
|
||||
if (forceTransition ||
|
||||
chapters.nextChapter?.state !is ReaderChapter.State.Loaded) {
|
||||
newItems.add(it)
|
||||
}
|
||||
.also {
|
||||
if (forceTransition ||
|
||||
chapters.nextChapter?.state !is ReaderChapter.State.Loaded
|
||||
) {
|
||||
newItems.add(it)
|
||||
}
|
||||
}
|
||||
|
||||
if (chapters.nextChapter != null) {
|
||||
// Add at most two pages, because this chapter will be selected before the user can
|
||||
|
||||
+6
-5
@@ -37,17 +37,18 @@ class WebtoonLayoutManager(activity: ReaderActivity) : LinearLayoutManager(activ
|
||||
fun findLastEndVisibleItemPosition(): Int {
|
||||
ensureLayoutState()
|
||||
@ViewBoundsCheck.ViewBounds val preferredBoundsFlag =
|
||||
(ViewBoundsCheck.FLAG_CVE_LT_PVE or ViewBoundsCheck.FLAG_CVE_EQ_PVE)
|
||||
(ViewBoundsCheck.FLAG_CVE_LT_PVE or ViewBoundsCheck.FLAG_CVE_EQ_PVE)
|
||||
|
||||
val fromIndex = childCount - 1
|
||||
val toIndex = -1
|
||||
|
||||
val child = if (mOrientation == HORIZONTAL)
|
||||
val child = if (mOrientation == HORIZONTAL) {
|
||||
mHorizontalBoundCheck
|
||||
.findOneViewWithinBoundFlags(fromIndex, toIndex, preferredBoundsFlag, 0)
|
||||
else
|
||||
.findOneViewWithinBoundFlags(fromIndex, toIndex, preferredBoundsFlag, 0)
|
||||
} else {
|
||||
mVerticalBoundCheck
|
||||
.findOneViewWithinBoundFlags(fromIndex, toIndex, preferredBoundsFlag, 0)
|
||||
.findOneViewWithinBoundFlags(fromIndex, toIndex, preferredBoundsFlag, 0)
|
||||
}
|
||||
|
||||
return if (child == null) NO_POSITION else getPosition(child)
|
||||
}
|
||||
|
||||
+56
-56
@@ -162,8 +162,8 @@ class WebtoonPageHolder(
|
||||
val page = page ?: return
|
||||
val loader = page.chapter.pageLoader ?: return
|
||||
statusSubscription = loader.getPage(page)
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.subscribe { processStatus(it) }
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.subscribe { processStatus(it) }
|
||||
|
||||
addSubscription(statusSubscription)
|
||||
}
|
||||
@@ -177,11 +177,11 @@ class WebtoonPageHolder(
|
||||
val page = page ?: return
|
||||
|
||||
progressSubscription = Observable.interval(100, TimeUnit.MILLISECONDS)
|
||||
.map { page.progress }
|
||||
.distinctUntilChanged()
|
||||
.onBackpressureLatest()
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.subscribe { value -> progressBar.setProgress(value) }
|
||||
.map { page.progress }
|
||||
.distinctUntilChanged()
|
||||
.onBackpressureLatest()
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.subscribe { value -> progressBar.setProgress(value) }
|
||||
|
||||
addSubscription(progressSubscription)
|
||||
}
|
||||
@@ -279,29 +279,29 @@ class WebtoonPageHolder(
|
||||
|
||||
var openStream: InputStream? = null
|
||||
readImageHeaderSubscription = Observable
|
||||
.fromCallable {
|
||||
val stream = streamFn().buffered(16)
|
||||
openStream = stream
|
||||
.fromCallable {
|
||||
val stream = streamFn().buffered(16)
|
||||
openStream = stream
|
||||
|
||||
ImageUtil.findImageType(stream) == ImageUtil.ImageType.GIF
|
||||
ImageUtil.findImageType(stream) == ImageUtil.ImageType.GIF
|
||||
}
|
||||
.subscribeOn(Schedulers.io())
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.doOnNext { isAnimated ->
|
||||
if (!isAnimated) {
|
||||
val subsamplingView = initSubsamplingImageView()
|
||||
subsamplingView.visible()
|
||||
subsamplingView.setImage(ImageSource.inputStream(openStream!!))
|
||||
} else {
|
||||
val imageView = initImageView()
|
||||
imageView.visible()
|
||||
imageView.setImage(openStream!!)
|
||||
}
|
||||
.subscribeOn(Schedulers.io())
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.doOnNext { isAnimated ->
|
||||
if (!isAnimated) {
|
||||
val subsamplingView = initSubsamplingImageView()
|
||||
subsamplingView.visible()
|
||||
subsamplingView.setImage(ImageSource.inputStream(openStream!!))
|
||||
} else {
|
||||
val imageView = initImageView()
|
||||
imageView.visible()
|
||||
imageView.setImage(openStream!!)
|
||||
}
|
||||
}
|
||||
// Keep the Rx stream alive to close the input stream only when unsubscribed
|
||||
.flatMap { Observable.never<Unit>() }
|
||||
.doOnUnsubscribe { openStream?.close() }
|
||||
.subscribe({}, {})
|
||||
}
|
||||
// Keep the Rx stream alive to close the input stream only when unsubscribed
|
||||
.flatMap { Observable.never<Unit>() }
|
||||
.doOnUnsubscribe { openStream?.close() }
|
||||
.subscribe({}, {})
|
||||
|
||||
addSubscription(readImageHeaderSubscription)
|
||||
}
|
||||
@@ -489,35 +489,35 @@ class WebtoonPageHolder(
|
||||
*/
|
||||
private fun ImageView.setImage(stream: InputStream) {
|
||||
GlideApp.with(this)
|
||||
.load(stream)
|
||||
.skipMemoryCache(true)
|
||||
.diskCacheStrategy(DiskCacheStrategy.NONE)
|
||||
.transition(DrawableTransitionOptions.with(NoTransition.getFactory()))
|
||||
.listener(object : RequestListener<Drawable> {
|
||||
override fun onLoadFailed(
|
||||
e: GlideException?,
|
||||
model: Any?,
|
||||
target: Target<Drawable>?,
|
||||
isFirstResource: Boolean
|
||||
): Boolean {
|
||||
onImageDecodeError()
|
||||
return false
|
||||
}
|
||||
.load(stream)
|
||||
.skipMemoryCache(true)
|
||||
.diskCacheStrategy(DiskCacheStrategy.NONE)
|
||||
.transition(DrawableTransitionOptions.with(NoTransition.getFactory()))
|
||||
.listener(object : RequestListener<Drawable> {
|
||||
override fun onLoadFailed(
|
||||
e: GlideException?,
|
||||
model: Any?,
|
||||
target: Target<Drawable>?,
|
||||
isFirstResource: Boolean
|
||||
): Boolean {
|
||||
onImageDecodeError()
|
||||
return false
|
||||
}
|
||||
|
||||
override fun onResourceReady(
|
||||
resource: Drawable?,
|
||||
model: Any?,
|
||||
target: Target<Drawable>?,
|
||||
dataSource: DataSource?,
|
||||
isFirstResource: Boolean
|
||||
): Boolean {
|
||||
if (resource is GifDrawable) {
|
||||
resource.setLoopCount(GifDrawable.LOOP_INTRINSIC)
|
||||
}
|
||||
onImageDecoded()
|
||||
return false
|
||||
override fun onResourceReady(
|
||||
resource: Drawable?,
|
||||
model: Any?,
|
||||
target: Target<Drawable>?,
|
||||
dataSource: DataSource?,
|
||||
isFirstResource: Boolean
|
||||
): Boolean {
|
||||
if (resource is GifDrawable) {
|
||||
resource.setLoopCount(GifDrawable.LOOP_INTRINSIC)
|
||||
}
|
||||
})
|
||||
.into(this)
|
||||
onImageDecoded()
|
||||
return false
|
||||
}
|
||||
})
|
||||
.into(this)
|
||||
}
|
||||
}
|
||||
|
||||
+11
-10
@@ -59,7 +59,7 @@ open class WebtoonRecyclerView @JvmOverloads constructor(
|
||||
super.onScrolled(dx, dy)
|
||||
val layoutManager = layoutManager
|
||||
lastVisibleItemPosition =
|
||||
(layoutManager as LinearLayoutManager).findLastVisibleItemPosition()
|
||||
(layoutManager as LinearLayoutManager).findLastVisibleItemPosition()
|
||||
firstVisibleItemPosition = layoutManager.findFirstVisibleItemPosition()
|
||||
}
|
||||
|
||||
@@ -146,13 +146,13 @@ open class WebtoonRecyclerView @JvmOverloads constructor(
|
||||
}
|
||||
|
||||
animate()
|
||||
.apply {
|
||||
newX?.let { x(it) }
|
||||
newY?.let { y(it) }
|
||||
}
|
||||
.setInterpolator(DecelerateInterpolator())
|
||||
.setDuration(400)
|
||||
.start()
|
||||
.apply {
|
||||
newX?.let { x(it) }
|
||||
newY?.let { y(it) }
|
||||
}
|
||||
.setInterpolator(DecelerateInterpolator())
|
||||
.setDuration(400)
|
||||
.start()
|
||||
|
||||
return true
|
||||
}
|
||||
@@ -174,8 +174,9 @@ open class WebtoonRecyclerView @JvmOverloads constructor(
|
||||
fun onScale(scaleFactor: Float) {
|
||||
currentScale *= scaleFactor
|
||||
currentScale = currentScale.coerceIn(
|
||||
MIN_RATE,
|
||||
MAX_SCALE_RATE)
|
||||
MIN_RATE,
|
||||
MAX_SCALE_RATE
|
||||
)
|
||||
|
||||
setScaleRate(currentScale)
|
||||
|
||||
|
||||
+10
-10
@@ -143,18 +143,18 @@ class WebtoonTransitionHolder(
|
||||
unsubscribeStatus()
|
||||
|
||||
statusSubscription = chapter.stateObserver
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.subscribe { state ->
|
||||
pagesContainer.removeAllViews()
|
||||
when (state) {
|
||||
is ReaderChapter.State.Wait -> {
|
||||
}
|
||||
is ReaderChapter.State.Loading -> setLoading()
|
||||
is ReaderChapter.State.Error -> setError(state.error, transition)
|
||||
is ReaderChapter.State.Loaded -> setLoaded()
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.subscribe { state ->
|
||||
pagesContainer.removeAllViews()
|
||||
when (state) {
|
||||
is ReaderChapter.State.Wait -> {
|
||||
}
|
||||
pagesContainer.visibleIf { pagesContainer.childCount > 0 }
|
||||
is ReaderChapter.State.Loading -> setLoading()
|
||||
is ReaderChapter.State.Error -> setError(state.error, transition)
|
||||
is ReaderChapter.State.Loaded -> setLoaded()
|
||||
}
|
||||
pagesContainer.visibleIf { pagesContainer.childCount > 0 }
|
||||
}
|
||||
|
||||
addSubscription(statusSubscription)
|
||||
}
|
||||
|
||||
@@ -291,6 +291,7 @@ class WebtoonViewer(val activity: ReaderActivity, val isContinuous: Boolean = tr
|
||||
val position = layoutManager.findLastEndVisibleItemPosition()
|
||||
adapter.notifyItemRangeChanged(
|
||||
max(0, position - 2),
|
||||
min(position + 2, adapter.itemCount - 1))
|
||||
min(position + 2, adapter.itemCount - 1)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user