Replace reader's Presenter with ViewModel (#8698)
includes: * Use coroutines in more places * Use domain Manga data class and effectively changing the state system * Replace deprecated onBackPress method Co-authored-by: arkon <arkon@users.noreply.github.com> (cherry picked from commit f7a92cf6ac58cae26b09b02578318e12cd888f4c) # Conflicts: # .github/renovate.json # app/src/main/java/eu/kanade/domain/manga/model/Manga.kt # app/src/main/java/eu/kanade/tachiyomi/ui/reader/ReaderActivity.kt # app/src/main/java/eu/kanade/tachiyomi/ui/reader/ReaderViewModel.kt
This commit is contained in:
parent
3d8f3b34b7
commit
de6a5bf67b
@ -236,9 +236,6 @@ dependencies {
|
||||
// Preferences
|
||||
implementation(libs.preferencektx)
|
||||
|
||||
// Model View Presenter
|
||||
implementation(libs.bundles.nucleus)
|
||||
|
||||
// Dependency injection
|
||||
implementation(libs.injekt.core)
|
||||
|
||||
|
@ -1,20 +1,18 @@
|
||||
package eu.kanade.domain.manga.model
|
||||
|
||||
import eu.kanade.data.listOfStringsAdapter
|
||||
import eu.kanade.data.listOfStringsAndAdapter
|
||||
import eu.kanade.domain.base.BasePreferences
|
||||
import eu.kanade.tachiyomi.data.cache.CoverCache
|
||||
import eu.kanade.tachiyomi.data.database.models.MangaImpl
|
||||
import eu.kanade.tachiyomi.data.library.CustomMangaManager
|
||||
import eu.kanade.tachiyomi.source.LocalSource
|
||||
import eu.kanade.tachiyomi.source.model.SManga
|
||||
import eu.kanade.tachiyomi.source.model.UpdateStrategy
|
||||
import eu.kanade.tachiyomi.ui.reader.setting.OrientationType
|
||||
import eu.kanade.tachiyomi.ui.reader.setting.ReadingModeType
|
||||
import eu.kanade.tachiyomi.widget.ExtendedNavigationView
|
||||
import uy.kohesive.injekt.Injekt
|
||||
import uy.kohesive.injekt.api.get
|
||||
import uy.kohesive.injekt.injectLazy
|
||||
import java.io.Serializable
|
||||
import eu.kanade.tachiyomi.data.database.models.Manga as DbManga
|
||||
|
||||
data class Manga(
|
||||
val id: Long,
|
||||
@ -83,6 +81,12 @@ data class Manga(
|
||||
val bookmarkedFilterRaw: Long
|
||||
get() = chapterFlags and CHAPTER_BOOKMARKED_MASK
|
||||
|
||||
val readingModeType: Long
|
||||
get() = viewerFlags and ReadingModeType.MASK.toLong()
|
||||
|
||||
val orientationType: Long
|
||||
get() = viewerFlags and OrientationType.MASK.toLong()
|
||||
|
||||
val unreadFilter: TriStateFilter
|
||||
get() = when (unreadFilterRaw) {
|
||||
CHAPTER_SHOW_UNREAD -> TriStateFilter.ENABLED_IS
|
||||
@ -240,33 +244,6 @@ fun TriStateFilter.toTriStateGroupState(): ExtendedNavigationView.Item.TriStateG
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: Remove when all deps are migrated
|
||||
fun Manga.toDbManga(): DbManga = MangaImpl().also {
|
||||
it.id = id
|
||||
it.source = source
|
||||
it.favorite = favorite
|
||||
it.last_update = lastUpdate
|
||||
it.date_added = dateAdded
|
||||
it.viewer_flags = viewerFlags.toInt()
|
||||
it.chapter_flags = chapterFlags.toInt()
|
||||
it.cover_last_modified = coverLastModified
|
||||
it.url = url
|
||||
// SY -->
|
||||
it.title = ogTitle
|
||||
it.artist = ogArtist
|
||||
it.author = ogAuthor
|
||||
it.description = ogDescription
|
||||
it.genre = ogGenre?.let(listOfStringsAdapter::encode)
|
||||
it.status = ogStatus.toInt()
|
||||
// SY <--
|
||||
it.thumbnail_url = thumbnailUrl
|
||||
it.update_strategy = updateStrategy
|
||||
it.initialized = initialized
|
||||
// SY -->
|
||||
it.filtered_scanlators = filteredScanlators?.let(listOfStringsAndAdapter::encode)
|
||||
// SY <--
|
||||
}
|
||||
|
||||
fun Manga.toMangaUpdate(): MangaUpdate {
|
||||
return MangaUpdate(
|
||||
id = id,
|
||||
|
@ -34,7 +34,9 @@ import android.widget.FrameLayout
|
||||
import android.widget.RelativeLayout
|
||||
import android.widget.TextView
|
||||
import android.widget.Toast
|
||||
import androidx.activity.viewModels
|
||||
import androidx.annotation.ColorInt
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import androidx.core.graphics.ColorUtils
|
||||
import androidx.core.transition.doOnEnd
|
||||
import androidx.core.view.WindowCompat
|
||||
@ -54,9 +56,9 @@ import com.google.android.material.transition.platform.MaterialContainerTransfor
|
||||
import com.google.android.material.transition.platform.MaterialContainerTransformSharedElementCallback
|
||||
import dev.chrisbanes.insetter.applyInsetter
|
||||
import eu.kanade.domain.base.BasePreferences
|
||||
import eu.kanade.domain.manga.model.Manga
|
||||
import eu.kanade.tachiyomi.BuildConfig
|
||||
import eu.kanade.tachiyomi.R
|
||||
import eu.kanade.tachiyomi.data.database.models.Manga
|
||||
import eu.kanade.tachiyomi.data.notification.NotificationReceiver
|
||||
import eu.kanade.tachiyomi.data.notification.Notifications
|
||||
import eu.kanade.tachiyomi.databinding.ReaderActivityBinding
|
||||
@ -67,9 +69,9 @@ import eu.kanade.tachiyomi.ui.base.delegate.SecureActivityDelegateImpl
|
||||
import eu.kanade.tachiyomi.ui.base.delegate.ThemingDelegate
|
||||
import eu.kanade.tachiyomi.ui.base.delegate.ThemingDelegateImpl
|
||||
import eu.kanade.tachiyomi.ui.main.MainActivity
|
||||
import eu.kanade.tachiyomi.ui.reader.ReaderPresenter.SetAsCoverResult.AddToLibraryFirst
|
||||
import eu.kanade.tachiyomi.ui.reader.ReaderPresenter.SetAsCoverResult.Error
|
||||
import eu.kanade.tachiyomi.ui.reader.ReaderPresenter.SetAsCoverResult.Success
|
||||
import eu.kanade.tachiyomi.ui.reader.ReaderViewModel.SetAsCoverResult.AddToLibraryFirst
|
||||
import eu.kanade.tachiyomi.ui.reader.ReaderViewModel.SetAsCoverResult.Error
|
||||
import eu.kanade.tachiyomi.ui.reader.ReaderViewModel.SetAsCoverResult.Success
|
||||
import eu.kanade.tachiyomi.ui.reader.chapter.ReaderChapterDialog
|
||||
import eu.kanade.tachiyomi.ui.reader.loader.HttpPageLoader
|
||||
import eu.kanade.tachiyomi.ui.reader.model.ReaderChapter
|
||||
@ -89,6 +91,8 @@ import eu.kanade.tachiyomi.ui.reader.viewer.pager.VerticalPagerViewer
|
||||
import eu.kanade.tachiyomi.ui.reader.viewer.webtoon.WebtoonViewer
|
||||
import eu.kanade.tachiyomi.ui.webview.WebViewActivity
|
||||
import eu.kanade.tachiyomi.util.Constants
|
||||
import eu.kanade.tachiyomi.util.lang.launchNonCancellable
|
||||
import eu.kanade.tachiyomi.util.lang.withUIContext
|
||||
import eu.kanade.tachiyomi.util.preference.toggle
|
||||
import eu.kanade.tachiyomi.util.system.applySystemAnimatorScale
|
||||
import eu.kanade.tachiyomi.util.system.createReaderThemeContext
|
||||
@ -115,16 +119,18 @@ import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.callbackFlow
|
||||
import kotlinx.coroutines.flow.combine
|
||||
import kotlinx.coroutines.flow.conflate
|
||||
import kotlinx.coroutines.flow.distinctUntilChanged
|
||||
import kotlinx.coroutines.flow.drop
|
||||
import kotlinx.coroutines.flow.filterNotNull
|
||||
import kotlinx.coroutines.flow.launchIn
|
||||
import kotlinx.coroutines.flow.map
|
||||
import kotlinx.coroutines.flow.mapLatest
|
||||
import kotlinx.coroutines.flow.merge
|
||||
import kotlinx.coroutines.flow.onEach
|
||||
import kotlinx.coroutines.flow.onStart
|
||||
import kotlinx.coroutines.flow.sample
|
||||
import kotlinx.coroutines.launch
|
||||
import logcat.LogPriority
|
||||
import nucleus.factory.RequiresPresenter
|
||||
import nucleus.view.NucleusAppCompatActivity
|
||||
import uy.kohesive.injekt.injectLazy
|
||||
import kotlin.math.abs
|
||||
import kotlin.math.max
|
||||
@ -134,9 +140,8 @@ import kotlin.time.Duration.Companion.seconds
|
||||
* Activity containing the reader of Tachiyomi. This activity is mostly a container of the
|
||||
* viewers, to which calls from the presenter or UI events are delegated.
|
||||
*/
|
||||
@RequiresPresenter(ReaderPresenter::class)
|
||||
class ReaderActivity :
|
||||
NucleusAppCompatActivity<ReaderPresenter>(),
|
||||
AppCompatActivity(),
|
||||
SecureActivityDelegate by SecureActivityDelegateImpl(),
|
||||
ThemingDelegate by ThemingDelegateImpl() {
|
||||
|
||||
@ -169,6 +174,8 @@ class ReaderActivity :
|
||||
|
||||
lateinit var binding: ReaderActivityBinding
|
||||
|
||||
val viewModel by viewModels<ReaderViewModel>()
|
||||
|
||||
val hasCutout by lazy { hasDisplayCutout() }
|
||||
|
||||
/**
|
||||
@ -245,7 +252,7 @@ class ReaderActivity :
|
||||
binding = ReaderActivityBinding.inflate(layoutInflater)
|
||||
setContentView(binding.root)
|
||||
|
||||
if (presenter.needsInit()) {
|
||||
if (viewModel.needsInit()) {
|
||||
val manga = intent.extras!!.getLong("manga", -1)
|
||||
val chapter = intent.extras!!.getLong("chapter", -1)
|
||||
// SY -->
|
||||
@ -256,7 +263,16 @@ class ReaderActivity :
|
||||
return
|
||||
}
|
||||
NotificationReceiver.dismissNotification(this, manga.hashCode(), Notifications.ID_NEW_CHAPTERS)
|
||||
presenter.init(manga, chapter /* SY --> */, page/* SY <-- */)
|
||||
|
||||
lifecycleScope.launchNonCancellable {
|
||||
val initResult = viewModel.init(manga, chapter/* SY --> */, page/* SY <-- */)
|
||||
if (!initResult.getOrDefault(false)) {
|
||||
val exception = initResult.exceptionOrNull() ?: IllegalStateException("Unknown err")
|
||||
withUIContext {
|
||||
setInitialChapterError(exception)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (savedInstanceState != null) {
|
||||
@ -279,6 +295,48 @@ class ReaderActivity :
|
||||
.drop(1)
|
||||
.onEach { if (!it) finish() }
|
||||
.launchIn(lifecycleScope)
|
||||
|
||||
viewModel.state
|
||||
.map { it.isLoadingAdjacentChapter }
|
||||
.distinctUntilChanged()
|
||||
.onEach(::setProgressDialog)
|
||||
.launchIn(lifecycleScope)
|
||||
|
||||
viewModel.state
|
||||
.map { it.manga }
|
||||
.distinctUntilChanged()
|
||||
.filterNotNull()
|
||||
.onEach(::setManga)
|
||||
.launchIn(lifecycleScope)
|
||||
|
||||
viewModel.state
|
||||
.map { it.viewerChapters }
|
||||
.distinctUntilChanged()
|
||||
.filterNotNull()
|
||||
.onEach(::setChapters)
|
||||
.launchIn(lifecycleScope)
|
||||
|
||||
viewModel.eventFlow
|
||||
.onEach { event ->
|
||||
when (event) {
|
||||
ReaderViewModel.Event.ReloadViewerChapters -> {
|
||||
viewModel.state.value.viewerChapters?.let(::setChapters)
|
||||
}
|
||||
is ReaderViewModel.Event.SetOrientation -> {
|
||||
setOrientation(event.orientation)
|
||||
}
|
||||
is ReaderViewModel.Event.SavedImage -> {
|
||||
onSaveImageResult(event.result)
|
||||
}
|
||||
is ReaderViewModel.Event.ShareImage -> {
|
||||
onShareImageResult(event.uri, event.page /* SY --> */, event.secondPage /* SY <-- */)
|
||||
}
|
||||
is ReaderViewModel.Event.SetCoverResult -> {
|
||||
onSetAsCoverResult(event.result)
|
||||
}
|
||||
}
|
||||
}
|
||||
.launchIn(lifecycleScope)
|
||||
}
|
||||
|
||||
// SY -->
|
||||
@ -329,13 +387,13 @@ class ReaderActivity :
|
||||
}
|
||||
// SY <--
|
||||
if (!isChangingConfigurations) {
|
||||
presenter.onSaveInstanceStateNonConfigurationChange()
|
||||
viewModel.onSaveInstanceStateNonConfigurationChange()
|
||||
}
|
||||
super.onSaveInstanceState(outState)
|
||||
}
|
||||
|
||||
override fun onPause() {
|
||||
presenter.saveCurrentChapterReadingProgress()
|
||||
viewModel.saveCurrentChapterReadingProgress()
|
||||
super.onPause()
|
||||
}
|
||||
|
||||
@ -345,7 +403,7 @@ class ReaderActivity :
|
||||
*/
|
||||
override fun onResume() {
|
||||
super.onResume()
|
||||
presenter.setReadStartTime()
|
||||
viewModel.setReadStartTime()
|
||||
setMenuVisibility(menuVisible, animate = false)
|
||||
}
|
||||
|
||||
@ -366,7 +424,7 @@ class ReaderActivity :
|
||||
override fun onCreateOptionsMenu(menu: Menu): Boolean {
|
||||
menuInflater.inflate(R.menu.reader, menu)
|
||||
|
||||
/*val isChapterBookmarked = presenter?.getCurrentChapter()?.chapter?.bookmark ?: false
|
||||
/*val isChapterBookmarked = viewModel.getCurrentChapter()?.chapter?.bookmark ?: false
|
||||
menu.findItem(R.id.action_bookmark).isVisible = !isChapterBookmarked
|
||||
menu.findItem(R.id.action_remove_bookmark).isVisible = isChapterBookmarked*/
|
||||
|
||||
@ -383,11 +441,11 @@ class ReaderActivity :
|
||||
openChapterInWebview()
|
||||
}
|
||||
R.id.action_bookmark -> {
|
||||
presenter.bookmarkCurrentChapter(true)
|
||||
viewModel.bookmarkCurrentChapter(true)
|
||||
invalidateOptionsMenu()
|
||||
}
|
||||
R.id.action_remove_bookmark -> {
|
||||
presenter.bookmarkCurrentChapter(false)
|
||||
viewModel.bookmarkCurrentChapter(false)
|
||||
invalidateOptionsMenu()
|
||||
}
|
||||
}
|
||||
@ -398,17 +456,17 @@ class ReaderActivity :
|
||||
* Called when the user clicks the back key or the button on the toolbar. The call is
|
||||
* delegated to the presenter.
|
||||
*/
|
||||
override fun onBackPressed() {
|
||||
presenter.onBackPressed()
|
||||
super.onBackPressed()
|
||||
override fun finish() {
|
||||
viewModel.onActivityFinish()
|
||||
super.finish()
|
||||
}
|
||||
|
||||
override fun onKeyUp(keyCode: Int, event: KeyEvent?): Boolean {
|
||||
if (keyCode == KeyEvent.KEYCODE_N) {
|
||||
presenter.loadNextChapter()
|
||||
loadNextChapter()
|
||||
return true
|
||||
} else if (keyCode == KeyEvent.KEYCODE_P) {
|
||||
presenter.loadPreviousChapter()
|
||||
loadPreviousChapter()
|
||||
return true
|
||||
}
|
||||
return super.onKeyUp(keyCode, event)
|
||||
@ -475,7 +533,7 @@ class ReaderActivity :
|
||||
setSupportActionBar(binding.toolbar)
|
||||
supportActionBar?.setDisplayHomeAsUpEnabled(true)
|
||||
binding.toolbar.setNavigationOnClickListener {
|
||||
onBackPressed()
|
||||
onBackPressedDispatcher.onBackPressed()
|
||||
}
|
||||
|
||||
binding.header.applyInsetter {
|
||||
@ -490,7 +548,7 @@ class ReaderActivity :
|
||||
}
|
||||
|
||||
binding.toolbar.setOnClickListener {
|
||||
presenter.manga?.id?.let { id ->
|
||||
viewModel.manga?.id?.let { id ->
|
||||
startActivity(
|
||||
Intent(this, MainActivity::class.java).apply {
|
||||
action = MainActivity.SHORTCUT_MANGA
|
||||
@ -612,11 +670,11 @@ class ReaderActivity :
|
||||
setOnClickListener {
|
||||
popupMenu(
|
||||
items = ReadingModeType.values().map { it.flagValue to it.stringRes },
|
||||
selectedItemId = presenter.getMangaReadingMode(resolveDefault = false),
|
||||
selectedItemId = viewModel.getMangaReadingMode(resolveDefault = false),
|
||||
) {
|
||||
val newReadingMode = ReadingModeType.fromPreference(itemId)
|
||||
|
||||
presenter.setMangaReadingMode(newReadingMode.flagValue)
|
||||
viewModel.setMangaReadingMode(newReadingMode.flagValue)
|
||||
|
||||
menuToggleToast?.cancel()
|
||||
if (!readerPreferences.showReadingMode().get()) {
|
||||
@ -634,7 +692,7 @@ class ReaderActivity :
|
||||
|
||||
setOnClickListener {
|
||||
// SY -->
|
||||
val mangaViewer = presenter.getMangaReadingMode()
|
||||
val mangaViewer = viewModel.getMangaReadingMode()
|
||||
// SY <--
|
||||
val isPagerType = ReadingModeType.isPagerType(mangaViewer)
|
||||
val enabled = if (isPagerType) {
|
||||
@ -674,12 +732,12 @@ class ReaderActivity :
|
||||
setOnClickListener {
|
||||
popupMenu(
|
||||
items = OrientationType.values().map { it.flagValue to it.stringRes },
|
||||
selectedItemId = presenter.manga?.orientationType
|
||||
selectedItemId = viewModel.manga?.orientationType?.toInt()
|
||||
?: readerPreferences.defaultOrientationType().get(),
|
||||
) {
|
||||
val newOrientation = OrientationType.fromPreference(itemId)
|
||||
|
||||
presenter.setMangaOrientationType(newOrientation.flagValue)
|
||||
viewModel.setMangaOrientationType(newOrientation.flagValue)
|
||||
|
||||
menuToggleToast?.cancel()
|
||||
menuToggleToast = toast(newOrientation.stringRes)
|
||||
@ -804,9 +862,9 @@ class ReaderActivity :
|
||||
binding.ehRetryAll.setOnClickListener {
|
||||
var retried = 0
|
||||
|
||||
presenter.viewerChaptersRelay.value
|
||||
.currChapter
|
||||
.pages
|
||||
viewModel.state.value.viewerChapters
|
||||
?.currChapter
|
||||
?.pages
|
||||
?.forEachIndexed { _, page ->
|
||||
var shouldQueuePage = false
|
||||
if (page.status == Page.State.ERROR) {
|
||||
@ -823,7 +881,7 @@ class ReaderActivity :
|
||||
}
|
||||
|
||||
// If we are using EHentai/ExHentai, get a new image URL
|
||||
presenter.manga?.let { m ->
|
||||
viewModel.manga?.let { m ->
|
||||
val src = sourceManager.get(m.source)
|
||||
if (src?.isEhBasedSource() == true) {
|
||||
page.imageUrl = null
|
||||
@ -865,7 +923,7 @@ class ReaderActivity :
|
||||
} else if (curPage.status == Page.State.READY) {
|
||||
toast(R.string.eh_boost_page_downloaded)
|
||||
} else {
|
||||
val loader = (presenter.viewerChaptersRelay.value.currChapter.pageLoader as? HttpPageLoader)
|
||||
val loader = (viewModel.state.value.viewerChapters?.currChapter?.pageLoader as? HttpPageLoader)
|
||||
if (loader != null) {
|
||||
loader.boostPage(curPage)
|
||||
toast(R.string.eh_boost_boosted)
|
||||
@ -886,7 +944,7 @@ class ReaderActivity :
|
||||
|
||||
private fun exhCurrentpage(): ReaderPage? {
|
||||
val currentPage = (((viewer as? PagerViewer)?.currentPage ?: (viewer as? WebtoonViewer)?.currentPage) as? ReaderPage)?.index
|
||||
return currentPage?.let { presenter.viewerChaptersRelay.value.currChapter.pages?.getOrNull(it) }
|
||||
return currentPage?.let { viewModel.state.value.viewerChapters?.currChapter?.pages?.getOrNull(it) }
|
||||
}
|
||||
|
||||
fun updateBottomButtons() {
|
||||
@ -924,7 +982,7 @@ class ReaderActivity :
|
||||
} else {
|
||||
pViewer.config.doublePages = doublePages
|
||||
}
|
||||
val currentChapter = presenter.getCurrentChapter()
|
||||
val currentChapter = viewModel.getCurrentChapter()
|
||||
if (doublePages) {
|
||||
// If we're moving from singe to double, we want the current page to be the first page
|
||||
pViewer.config.shiftDoublePage = (
|
||||
@ -932,7 +990,7 @@ class ReaderActivity :
|
||||
(currentChapter?.pages?.take(binding.pageSlider.value.floor())?.count { it.fullPage || it.isolatedPage } ?: 0)
|
||||
) % 2 != 0
|
||||
}
|
||||
presenter.viewerChaptersRelay.value?.let {
|
||||
viewModel.state.value.viewerChapters?.let {
|
||||
pViewer.setChaptersDoubleShift(it)
|
||||
}
|
||||
}
|
||||
@ -945,7 +1003,7 @@ class ReaderActivity :
|
||||
private fun shiftDoublePages() {
|
||||
(viewer as? PagerViewer)?.config?.let { config ->
|
||||
config.shiftDoublePage = !config.shiftDoublePage
|
||||
presenter.viewerChaptersRelay.value?.let {
|
||||
viewModel.state.value.viewerChapters?.let {
|
||||
(viewer as? PagerViewer)?.updateShifting()
|
||||
(viewer as? PagerViewer)?.setChaptersDoubleShift(it)
|
||||
invalidateOptionsMenu()
|
||||
@ -960,7 +1018,7 @@ class ReaderActivity :
|
||||
}
|
||||
|
||||
private fun updateCropBordersShortcut() {
|
||||
val mangaViewer = presenter.getMangaReadingMode()
|
||||
val mangaViewer = viewModel.getMangaReadingMode()
|
||||
val isPagerType = ReadingModeType.isPagerType(mangaViewer)
|
||||
val enabled = if (isPagerType) {
|
||||
readerPreferences.cropBorders().get()
|
||||
@ -1070,19 +1128,19 @@ class ReaderActivity :
|
||||
fun setManga(manga: Manga) {
|
||||
val prevViewer = viewer
|
||||
|
||||
val viewerMode = ReadingModeType.fromPreference(presenter.getMangaReadingMode(resolveDefault = false))
|
||||
val viewerMode = ReadingModeType.fromPreference(viewModel.getMangaReadingMode(resolveDefault = false))
|
||||
binding.actionReadingMode.setImageResource(viewerMode.iconRes)
|
||||
|
||||
val newViewer = ReadingModeType.toViewer(presenter.getMangaReadingMode(), this)
|
||||
val newViewer = ReadingModeType.toViewer(viewModel.getMangaReadingMode(), this)
|
||||
|
||||
updateCropBordersShortcut()
|
||||
if (window.sharedElementEnterTransition is MaterialContainerTransform) {
|
||||
// Wait until transition is complete to avoid crash on API 26
|
||||
window.sharedElementEnterTransition.doOnEnd {
|
||||
setOrientation(presenter.getMangaOrientationType())
|
||||
setOrientation(viewModel.getMangaOrientationType())
|
||||
}
|
||||
} else {
|
||||
setOrientation(presenter.getMangaOrientationType())
|
||||
setOrientation(viewModel.getMangaOrientationType())
|
||||
}
|
||||
|
||||
// Destroy previous viewer if there was one
|
||||
@ -1103,12 +1161,12 @@ class ReaderActivity :
|
||||
}
|
||||
|
||||
val defaultReaderType = manga.defaultReaderType(manga.mangaType(sourceName = sourceManager.get(manga.source)?.name))
|
||||
if (readerPreferences.useAutoWebtoon().get() && manga.readingModeType == ReadingModeType.DEFAULT.flagValue && defaultReaderType != null && defaultReaderType == ReadingModeType.WEBTOON.prefValue) {
|
||||
if (readerPreferences.useAutoWebtoon().get() && manga.readingModeType.toInt() == ReadingModeType.DEFAULT.flagValue && defaultReaderType != null && defaultReaderType == ReadingModeType.WEBTOON.prefValue) {
|
||||
readingModeToast?.cancel()
|
||||
readingModeToast = toast(resources.getString(R.string.eh_auto_webtoon_snack))
|
||||
} else if (readerPreferences.showReadingMode().get()) {
|
||||
// SY <--
|
||||
showReadingModeToast(presenter.getMangaReadingMode())
|
||||
showReadingModeToast(viewModel.getMangaReadingMode())
|
||||
}
|
||||
|
||||
// SY -->
|
||||
@ -1171,9 +1229,9 @@ class ReaderActivity :
|
||||
}
|
||||
|
||||
private fun openChapterInWebview() {
|
||||
val manga = presenter.manga ?: return
|
||||
val source = presenter.getSource() ?: return
|
||||
val url = presenter.getChapterUrl() ?: return
|
||||
val manga = viewModel.manga ?: return
|
||||
val source = viewModel.getSource() ?: return
|
||||
val url = viewModel.getChapterUrl() ?: return
|
||||
|
||||
val intent = WebViewActivity.newIntent(this, url, source.id, manga.title)
|
||||
startActivity(intent)
|
||||
@ -1194,7 +1252,7 @@ class ReaderActivity :
|
||||
* method to the current viewer, but also set the subtitle on the toolbar, and
|
||||
* hides or disables the reader prev/next buttons if there's a prev or next chapter
|
||||
*/
|
||||
fun setChapters(viewerChapters: ViewerChapters) {
|
||||
private fun setChapters(viewerChapters: ViewerChapters) {
|
||||
binding.readerContainer.removeView(loadingIndicator)
|
||||
// SY -->
|
||||
if (indexChapterToShift != null && indexPageToShift != null) {
|
||||
@ -1280,7 +1338,7 @@ class ReaderActivity :
|
||||
*/
|
||||
fun moveToPageIndex(index: Int) {
|
||||
val viewer = viewer ?: return
|
||||
val currentChapter = presenter.getCurrentChapter() ?: return
|
||||
val currentChapter = viewModel.getCurrentChapter() ?: return
|
||||
val page = currentChapter.pages?.getOrNull(index) ?: return
|
||||
viewer.moveToPage(page)
|
||||
}
|
||||
@ -1290,7 +1348,10 @@ class ReaderActivity :
|
||||
* should be automatically shown.
|
||||
*/
|
||||
private fun loadNextChapter() {
|
||||
presenter.loadNextChapter()
|
||||
lifecycleScope.launch {
|
||||
viewModel.loadNextChapter()
|
||||
moveToPageIndex(0)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@ -1298,7 +1359,10 @@ class ReaderActivity :
|
||||
* should be automatically shown.
|
||||
*/
|
||||
private fun loadPreviousChapter() {
|
||||
presenter.loadPreviousChapter()
|
||||
lifecycleScope.launch {
|
||||
viewModel.loadPreviousChapter()
|
||||
moveToPageIndex(0)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@ -1307,7 +1371,7 @@ class ReaderActivity :
|
||||
*/
|
||||
@SuppressLint("SetTextI18n")
|
||||
fun onPageSelected(page: ReaderPage, hasExtraPage: Boolean = false) {
|
||||
val newChapter = presenter.onPageSelected(page, hasExtraPage)
|
||||
val newChapter = viewModel.onPageSelected(page, hasExtraPage)
|
||||
val pages = page.chapter.pages ?: return
|
||||
|
||||
val currentPage = if (hasExtraPage) {
|
||||
@ -1372,7 +1436,7 @@ class ReaderActivity :
|
||||
* the viewer is reaching the beginning or end of a chapter or the transition page is active.
|
||||
*/
|
||||
fun requestPreloadChapter(chapter: ReaderChapter) {
|
||||
presenter.preloadChapter(chapter)
|
||||
lifecycleScope.launch { viewModel.preloadChapter(chapter) }
|
||||
}
|
||||
|
||||
/**
|
||||
@ -1406,12 +1470,12 @@ class ReaderActivity :
|
||||
* will call [onShareImageResult] with the path the image was saved on when it's ready.
|
||||
*/
|
||||
fun shareImage(page: ReaderPage) {
|
||||
presenter.shareImage(page)
|
||||
viewModel.shareImage(page)
|
||||
}
|
||||
|
||||
// SY -->
|
||||
fun shareImages(firstPage: ReaderPage, secondPage: ReaderPage, isLTR: Boolean, @ColorInt bg: Int) {
|
||||
presenter.shareImages(firstPage, secondPage, isLTR, bg)
|
||||
viewModel.shareImages(firstPage, secondPage, isLTR, bg)
|
||||
}
|
||||
// SY <--
|
||||
|
||||
@ -1420,7 +1484,7 @@ class ReaderActivity :
|
||||
* sharing tool.
|
||||
*/
|
||||
fun onShareImageResult(uri: Uri, page: ReaderPage /* SY --> */, secondPage: ReaderPage? = null /* SY <-- */) {
|
||||
val manga = presenter.manga ?: return
|
||||
val manga = viewModel.manga ?: return
|
||||
val chapter = page.chapter.chapter
|
||||
|
||||
// SY -->
|
||||
@ -1443,12 +1507,12 @@ class ReaderActivity :
|
||||
* storage to the presenter.
|
||||
*/
|
||||
fun saveImage(page: ReaderPage) {
|
||||
presenter.saveImage(page)
|
||||
viewModel.saveImage(page)
|
||||
}
|
||||
|
||||
// SY -->
|
||||
fun saveImages(firstPage: ReaderPage, secondPage: ReaderPage, isLTR: Boolean, @ColorInt bg: Int) {
|
||||
presenter.saveImages(firstPage, secondPage, isLTR, bg)
|
||||
viewModel.saveImages(firstPage, secondPage, isLTR, bg)
|
||||
}
|
||||
// SY <--
|
||||
|
||||
@ -1456,12 +1520,12 @@ class ReaderActivity :
|
||||
* Called from the presenter when a page is saved or fails. It shows a message or logs the
|
||||
* event depending on the [result].
|
||||
*/
|
||||
fun onSaveImageResult(result: ReaderPresenter.SaveImageResult) {
|
||||
private fun onSaveImageResult(result: ReaderViewModel.SaveImageResult) {
|
||||
when (result) {
|
||||
is ReaderPresenter.SaveImageResult.Success -> {
|
||||
is ReaderViewModel.SaveImageResult.Success -> {
|
||||
toast(R.string.picture_saved)
|
||||
}
|
||||
is ReaderPresenter.SaveImageResult.Error -> {
|
||||
is ReaderViewModel.SaveImageResult.Error -> {
|
||||
logcat(LogPriority.ERROR, result.error)
|
||||
}
|
||||
}
|
||||
@ -1472,14 +1536,14 @@ class ReaderActivity :
|
||||
* cover to the presenter.
|
||||
*/
|
||||
fun setAsCover(page: ReaderPage) {
|
||||
presenter.setAsCover(this, page)
|
||||
viewModel.setAsCover(this, page)
|
||||
}
|
||||
|
||||
/**
|
||||
* Called from the presenter when a page is set as cover or fails. It shows a different message
|
||||
* depending on the [result].
|
||||
*/
|
||||
fun onSetAsCoverResult(result: ReaderPresenter.SetAsCoverResult) {
|
||||
private fun onSetAsCoverResult(result: ReaderViewModel.SetAsCoverResult) {
|
||||
toast(
|
||||
when (result) {
|
||||
Success -> R.string.cover_updated
|
||||
@ -1492,12 +1556,12 @@ class ReaderActivity :
|
||||
/**
|
||||
* Forces the user preferred [orientation] on the activity.
|
||||
*/
|
||||
fun setOrientation(orientation: Int) {
|
||||
private fun setOrientation(orientation: Int) {
|
||||
val newOrientation = OrientationType.fromPreference(orientation)
|
||||
if (newOrientation.flag != requestedOrientation) {
|
||||
requestedOrientation = newOrientation.flag
|
||||
}
|
||||
updateOrientationShortcut(presenter.getMangaOrientationType(resolveDefault = false))
|
||||
updateOrientationShortcut(viewModel.getMangaOrientationType(resolveDefault = false))
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -3,13 +3,16 @@ package eu.kanade.tachiyomi.ui.reader
|
||||
import android.app.Application
|
||||
import android.content.Context
|
||||
import android.net.Uri
|
||||
import android.os.Bundle
|
||||
import androidx.annotation.ColorInt
|
||||
import com.jakewharton.rxrelay.BehaviorRelay
|
||||
import androidx.lifecycle.SavedStateHandle
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import eu.kanade.core.util.asFlow
|
||||
import eu.kanade.domain.base.BasePreferences
|
||||
import eu.kanade.domain.chapter.interactor.GetChapterByMangaId
|
||||
import eu.kanade.domain.chapter.interactor.GetMergedChapterByMangaId
|
||||
import eu.kanade.domain.chapter.interactor.UpdateChapter
|
||||
import eu.kanade.domain.chapter.model.Chapter
|
||||
import eu.kanade.domain.chapter.model.ChapterUpdate
|
||||
import eu.kanade.domain.chapter.model.toDbChapter
|
||||
import eu.kanade.domain.download.service.DownloadPreferences
|
||||
@ -21,8 +24,8 @@ import eu.kanade.domain.manga.interactor.GetManga
|
||||
import eu.kanade.domain.manga.interactor.GetMergedManga
|
||||
import eu.kanade.domain.manga.interactor.GetMergedReferencesById
|
||||
import eu.kanade.domain.manga.interactor.SetMangaViewerFlags
|
||||
import eu.kanade.domain.manga.model.Manga
|
||||
import eu.kanade.domain.manga.model.isLocal
|
||||
import eu.kanade.domain.manga.model.toDbManga
|
||||
import eu.kanade.domain.track.interactor.GetTracks
|
||||
import eu.kanade.domain.track.interactor.InsertTrack
|
||||
import eu.kanade.domain.track.model.toDbTrack
|
||||
@ -30,9 +33,7 @@ import eu.kanade.domain.track.service.DelayedTrackingUpdateJob
|
||||
import eu.kanade.domain.track.service.TrackPreferences
|
||||
import eu.kanade.domain.track.store.DelayedTrackingStore
|
||||
import eu.kanade.domain.ui.UiPreferences
|
||||
import eu.kanade.tachiyomi.data.database.models.Manga
|
||||
import eu.kanade.tachiyomi.data.database.models.toDomainChapter
|
||||
import eu.kanade.tachiyomi.data.database.models.toDomainManga
|
||||
import eu.kanade.tachiyomi.data.download.DownloadManager
|
||||
import eu.kanade.tachiyomi.data.download.DownloadProvider
|
||||
import eu.kanade.tachiyomi.data.download.model.Download
|
||||
@ -63,6 +64,7 @@ import eu.kanade.tachiyomi.util.lang.byteSize
|
||||
import eu.kanade.tachiyomi.util.lang.launchIO
|
||||
import eu.kanade.tachiyomi.util.lang.launchNonCancellable
|
||||
import eu.kanade.tachiyomi.util.lang.takeBytes
|
||||
import eu.kanade.tachiyomi.util.lang.withIOContext
|
||||
import eu.kanade.tachiyomi.util.lang.withUIContext
|
||||
import eu.kanade.tachiyomi.util.storage.DiskUtil
|
||||
import eu.kanade.tachiyomi.util.storage.cacheImageDir
|
||||
@ -77,36 +79,38 @@ import exh.source.getMainSource
|
||||
import exh.source.isEhBasedManga
|
||||
import exh.util.defaultReaderType
|
||||
import exh.util.mangaType
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.MainScope
|
||||
import kotlinx.coroutines.async
|
||||
import kotlinx.coroutines.awaitAll
|
||||
import kotlinx.coroutines.cancel
|
||||
import kotlinx.coroutines.channels.Channel
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.flow.collectLatest
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.asStateFlow
|
||||
import kotlinx.coroutines.flow.catch
|
||||
import kotlinx.coroutines.flow.distinctUntilChanged
|
||||
import kotlinx.coroutines.flow.first
|
||||
import kotlinx.coroutines.flow.firstOrNull
|
||||
import kotlinx.coroutines.flow.launchIn
|
||||
import kotlinx.coroutines.flow.map
|
||||
import kotlinx.coroutines.flow.onEach
|
||||
import kotlinx.coroutines.flow.receiveAsFlow
|
||||
import kotlinx.coroutines.flow.update
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import logcat.LogPriority
|
||||
import nucleus.presenter.RxPresenter
|
||||
import rx.Observable
|
||||
import rx.Subscription
|
||||
import rx.android.schedulers.AndroidSchedulers
|
||||
import rx.schedulers.Schedulers
|
||||
import tachiyomi.decoder.ImageDecoder
|
||||
import uy.kohesive.injekt.Injekt
|
||||
import uy.kohesive.injekt.api.get
|
||||
import java.text.DecimalFormat
|
||||
import java.text.DecimalFormatSymbols
|
||||
import java.util.Date
|
||||
import eu.kanade.domain.chapter.model.Chapter as DomainChapter
|
||||
import eu.kanade.domain.manga.model.Manga as DomainManga
|
||||
|
||||
/**
|
||||
* Presenter used by the activity to perform background operations.
|
||||
*/
|
||||
class ReaderPresenter(
|
||||
class ReaderViewModel(
|
||||
private val savedState: SavedStateHandle = SavedStateHandle(),
|
||||
private val sourceManager: SourceManager = Injekt.get(),
|
||||
private val downloadManager: DownloadManager = Injekt.get(),
|
||||
private val downloadProvider: DownloadProvider = Injekt.get(),
|
||||
@ -131,27 +135,28 @@ class ReaderPresenter(
|
||||
private val getMergedReferencesById: GetMergedReferencesById = Injekt.get(),
|
||||
private val getMergedChapterByMangaId: GetMergedChapterByMangaId = Injekt.get(),
|
||||
// SY <--
|
||||
) : RxPresenter<ReaderActivity>() {
|
||||
) : ViewModel() {
|
||||
|
||||
private val coroutineScope: CoroutineScope = MainScope()
|
||||
private val mutableState = MutableStateFlow(State())
|
||||
val state = mutableState.asStateFlow()
|
||||
|
||||
private val eventChannel = Channel<Event>()
|
||||
val eventFlow = eventChannel.receiveAsFlow()
|
||||
|
||||
/**
|
||||
* The manga loaded in the reader. It can be null when instantiated for a short time.
|
||||
*/
|
||||
var manga: Manga? = null
|
||||
private set
|
||||
|
||||
// SY -->
|
||||
var meta: RaisedSearchMetadata? = null
|
||||
private set
|
||||
var mergedManga: Map<Long, DomainManga>? = null
|
||||
private set
|
||||
// SY <--
|
||||
val manga: Manga?
|
||||
get() = state.value.manga
|
||||
|
||||
/**
|
||||
* The chapter id of the currently loaded chapter. Used to restore from process kill.
|
||||
*/
|
||||
private var chapterId = -1L
|
||||
private var chapterId = savedState.get<Long>("chapter_id") ?: -1L
|
||||
set(value) {
|
||||
savedState["chapter_id"] = value
|
||||
field = value
|
||||
}
|
||||
|
||||
/**
|
||||
* The chapter loader for the loaded manga. It'll be null until [manga] is set.
|
||||
@ -168,17 +173,6 @@ class ReaderPresenter(
|
||||
*/
|
||||
private var activeChapterSubscription: Subscription? = null
|
||||
|
||||
/**
|
||||
* Relay for currently active viewer chapters.
|
||||
*/
|
||||
/* [EXH] private */
|
||||
val viewerChaptersRelay = BehaviorRelay.create<ViewerChapters>()
|
||||
|
||||
/**
|
||||
* Used when loading prev/next chapter needed to lock the UI (with a dialog).
|
||||
*/
|
||||
private val isLoadingAdjacentChapterEvent = Channel<Boolean>()
|
||||
|
||||
private var chapterToDownload: Download? = null
|
||||
|
||||
/**
|
||||
@ -186,7 +180,7 @@ class ReaderPresenter(
|
||||
* time in a background thread to avoid blocking the UI.
|
||||
*/
|
||||
private val chapterList by lazy {
|
||||
val manga = manga!!.toDomainManga()!!
|
||||
val manga = manga!!
|
||||
val chapters = runBlocking {
|
||||
/* SY --> */ if (manga.source == MERGED_SOURCE_ID) {
|
||||
getMergedChapterByMangaId.await(manga.id)
|
||||
@ -204,12 +198,12 @@ class ReaderPresenter(
|
||||
when {
|
||||
readerPreferences.skipRead().get() && it.read -> true
|
||||
readerPreferences.skipFiltered().get() -> {
|
||||
(manga.unreadFilterRaw == DomainManga.CHAPTER_SHOW_READ && !it.read) ||
|
||||
(manga.unreadFilterRaw == DomainManga.CHAPTER_SHOW_UNREAD && it.read) ||
|
||||
(manga.downloadedFilterRaw == DomainManga.CHAPTER_SHOW_DOWNLOADED && !downloadManager.isChapterDownloaded(it.name, it.scanlator, /* SY --> */ manga.ogTitle /* SY <-- */, manga.source)) ||
|
||||
(manga.downloadedFilterRaw == DomainManga.CHAPTER_SHOW_NOT_DOWNLOADED && downloadManager.isChapterDownloaded(it.name, it.scanlator, /* SY --> */ manga.ogTitle /* SY <-- */, manga.source)) ||
|
||||
(manga.bookmarkedFilterRaw == DomainManga.CHAPTER_SHOW_BOOKMARKED && !it.bookmark) ||
|
||||
(manga.bookmarkedFilterRaw == DomainManga.CHAPTER_SHOW_NOT_BOOKMARKED && it.bookmark) ||
|
||||
(manga.unreadFilterRaw == Manga.CHAPTER_SHOW_READ && !it.read) ||
|
||||
(manga.unreadFilterRaw == Manga.CHAPTER_SHOW_UNREAD && it.read) ||
|
||||
(manga.downloadedFilterRaw == Manga.CHAPTER_SHOW_DOWNLOADED && !downloadManager.isChapterDownloaded(it.name, it.scanlator, /* SY --> */ manga.ogTitle /* SY <-- */, manga.source)) ||
|
||||
(manga.downloadedFilterRaw == Manga.CHAPTER_SHOW_NOT_DOWNLOADED && downloadManager.isChapterDownloaded(it.name, it.scanlator, /* SY --> */ manga.ogTitle /* SY <-- */, manga.source)) ||
|
||||
(manga.bookmarkedFilterRaw == Manga.CHAPTER_SHOW_BOOKMARKED && !it.bookmark) ||
|
||||
(manga.bookmarkedFilterRaw == Manga.CHAPTER_SHOW_NOT_BOOKMARKED && it.bookmark) ||
|
||||
// SY -->
|
||||
(manga.filteredScanlators != null && MdUtil.getScanlators(it.scanlator).none { group -> manga.filteredScanlators.contains(group) })
|
||||
// SY <--
|
||||
@ -234,32 +228,15 @@ class ReaderPresenter(
|
||||
}
|
||||
|
||||
private var hasTrackers: Boolean = false
|
||||
private val checkTrackers: (DomainManga) -> Unit = { manga ->
|
||||
private val checkTrackers: (Manga) -> Unit = { manga ->
|
||||
val tracks = runBlocking { getTracks.await(manga.id) }
|
||||
hasTrackers = tracks.isNotEmpty()
|
||||
}
|
||||
|
||||
private val incognitoMode = preferences.incognitoMode().get()
|
||||
|
||||
/**
|
||||
* Called when the presenter is created. It retrieves the saved active chapter if the process
|
||||
* was restored.
|
||||
*/
|
||||
override fun onCreate(savedState: Bundle?) {
|
||||
super.onCreate(savedState)
|
||||
if (savedState != null) {
|
||||
chapterId = savedState.getLong(::chapterId.name, -1)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when the presenter is destroyed. It saves the current progress and cleans up
|
||||
* references on the currently active chapters.
|
||||
*/
|
||||
override fun onDestroy() {
|
||||
super.onDestroy()
|
||||
coroutineScope.cancel()
|
||||
val currentChapters = viewerChaptersRelay.value
|
||||
override fun onCleared() {
|
||||
val currentChapters = state.value.viewerChapters
|
||||
if (currentChapters != null) {
|
||||
currentChapters.unref()
|
||||
saveReadingProgress(currentChapters.currChapter)
|
||||
@ -269,24 +246,24 @@ class ReaderPresenter(
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when the presenter instance is being saved. It saves the currently active chapter
|
||||
* id and the last page read.
|
||||
*/
|
||||
override fun onSave(state: Bundle) {
|
||||
super.onSave(state)
|
||||
val currentChapter = getCurrentChapter()
|
||||
if (currentChapter != null) {
|
||||
currentChapter.requestedPage = currentChapter.chapter.last_page_read
|
||||
state.putLong(::chapterId.name, currentChapter.chapter.id!!)
|
||||
}
|
||||
init {
|
||||
// To save state
|
||||
state.map { it.viewerChapters?.currChapter }
|
||||
.distinctUntilChanged()
|
||||
.onEach { currentChapter ->
|
||||
if (currentChapter != null) {
|
||||
currentChapter.requestedPage = currentChapter.chapter.last_page_read
|
||||
chapterId = currentChapter.chapter.id!!
|
||||
}
|
||||
}
|
||||
.launchIn(viewModelScope)
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when the user pressed the back button and is going to leave the reader. Used to
|
||||
* trigger deletion of the downloaded chapters.
|
||||
*/
|
||||
fun onBackPressed() {
|
||||
fun onActivityFinish() {
|
||||
deletePendingChapters()
|
||||
}
|
||||
|
||||
@ -296,7 +273,7 @@ class ReaderPresenter(
|
||||
*/
|
||||
fun onSaveInstanceStateNonConfigurationChange() {
|
||||
val currentChapter = getCurrentChapter() ?: return
|
||||
coroutineScope.launchNonCancellable {
|
||||
viewModelScope.launchNonCancellable {
|
||||
saveChapterProgress(currentChapter)
|
||||
}
|
||||
}
|
||||
@ -312,73 +289,46 @@ class ReaderPresenter(
|
||||
* Initializes this presenter with the given [mangaId] and [initialChapterId]. This method will
|
||||
* fetch the manga from the database and initialize the initial chapter.
|
||||
*/
|
||||
fun init(mangaId: Long, initialChapterId: Long /* SY --> */, page: Int?/* SY <-- */) {
|
||||
if (!needsInit()) return
|
||||
|
||||
coroutineScope.launchIO {
|
||||
suspend fun init(mangaId: Long, initialChapterId: Long /* SY --> */, page: Int?/* SY <-- */): Result<Boolean> {
|
||||
if (!needsInit()) return Result.success(true)
|
||||
return withIOContext {
|
||||
try {
|
||||
// SY -->
|
||||
val manga = getManga.await(mangaId) ?: return@launchIO
|
||||
val source = sourceManager.get(manga.source)?.getMainSource<MetadataSource<*, *>>()
|
||||
val metadata = if (source != null) {
|
||||
getFlatMetadataById.await(mangaId)?.raise(source.metaClass)
|
||||
val manga = getManga.await(mangaId)
|
||||
if (manga != null) {
|
||||
// SY -->
|
||||
val source = sourceManager.getOrStub(manga.source)
|
||||
val metadataSource = source.getMainSource<MetadataSource<*, *>>()
|
||||
val metadata = if (metadataSource != null) {
|
||||
getFlatMetadataById.await(mangaId)?.raise(metadataSource.metaClass)
|
||||
} else {
|
||||
null
|
||||
}
|
||||
val mergedReferences = if (source is MergedSource) runBlocking { getMergedReferencesById.await(manga.id) } else emptyList()
|
||||
val mergedManga = if (source is MergedSource) runBlocking { getMergedManga.await() /* <-- TODO */ }.associateBy { it.id } else emptyMap()
|
||||
// SY <--
|
||||
mutableState.update { it.copy(manga = manga /* SY --> */, meta = metadata, mergedManga = mergedManga/* SY <-- */) }
|
||||
if (chapterId == -1L) chapterId = initialChapterId
|
||||
|
||||
checkTrackers(manga)
|
||||
|
||||
val context = Injekt.get<Application>()
|
||||
// val source = sourceManager.getOrStub(manga.source)
|
||||
loader = ChapterLoader(context, downloadManager, downloadProvider, manga, source, /* SY --> */sourceManager, mergedReferences, mergedManga/* SY <-- */)
|
||||
|
||||
getLoadObservable(loader!!, chapterList.first { chapterId == it.chapter.id } /* SY --> */, page/* SY <-- */)
|
||||
.asFlow()
|
||||
.first()
|
||||
Result.success(true)
|
||||
} else {
|
||||
null
|
||||
// Unlikely but okay
|
||||
Result.success(false)
|
||||
}
|
||||
withUIContext {
|
||||
init(manga.toDbManga(), initialChapterId, metadata, page)
|
||||
}
|
||||
// SY <--
|
||||
} catch (e: Throwable) {
|
||||
view?.setInitialChapterError(e)
|
||||
Result.failure(e)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes this presenter with the given [manga] and [initialChapterId]. This method will
|
||||
* set the chapter loader, view subscriptions and trigger an initial load.
|
||||
*/
|
||||
private fun init(manga: Manga, initialChapterId: Long /* SY --> */, metadata: RaisedSearchMetadata?, page: Int?/* SY <-- */) {
|
||||
if (!needsInit()) return
|
||||
|
||||
this.manga = manga
|
||||
// SY -->
|
||||
this.meta = metadata
|
||||
// SY <--
|
||||
if (chapterId == -1L) chapterId = initialChapterId
|
||||
|
||||
checkTrackers(manga.toDomainManga()!!)
|
||||
|
||||
val context = Injekt.get<Application>()
|
||||
val source = sourceManager.getOrStub(manga.source)
|
||||
val mergedReferences = if (source is MergedSource) runBlocking { getMergedReferencesById.await(manga.id!!) } else emptyList()
|
||||
mergedManga = if (source is MergedSource) runBlocking { getMergedManga.await() }.associateBy { it.id } else emptyMap()
|
||||
loader = ChapterLoader(context, downloadManager, downloadProvider, manga.toDomainManga()!!, source, sourceManager, mergedReferences, mergedManga ?: emptyMap())
|
||||
|
||||
Observable.just(manga).subscribeLatestCache(ReaderActivity::setManga)
|
||||
viewerChaptersRelay.subscribeLatestCache(ReaderActivity::setChapters)
|
||||
coroutineScope.launch {
|
||||
isLoadingAdjacentChapterEvent.receiveAsFlow().collectLatest {
|
||||
view?.setProgressDialog(it)
|
||||
}
|
||||
}
|
||||
|
||||
// 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 /* SY --> */, page/* SY <-- */) }
|
||||
.subscribeOn(Schedulers.io())
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.subscribeFirst(
|
||||
{ _, _ ->
|
||||
// Ignore onNext event
|
||||
},
|
||||
ReaderActivity::setInitialChapterError,
|
||||
)
|
||||
}
|
||||
|
||||
// SY -->
|
||||
fun getChapters(context: Context): List<ReaderChapterItem> {
|
||||
val currentChapter = getCurrentChapter()
|
||||
@ -391,7 +341,7 @@ class ReaderPresenter(
|
||||
return chapterList.map {
|
||||
ReaderChapterItem(
|
||||
it.chapter.toDomainChapter()!!,
|
||||
manga!!.toDomainManga()!!,
|
||||
manga!!,
|
||||
it.chapter.id == currentChapter?.chapter?.id,
|
||||
context,
|
||||
UiPreferences.dateFormat(uiPreferences.dateFormat().get()),
|
||||
@ -429,14 +379,14 @@ class ReaderPresenter(
|
||||
)
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.doOnNext { newChapters ->
|
||||
val oldChapters = viewerChaptersRelay.value
|
||||
mutableState.update {
|
||||
// Add new references first to avoid unnecessary recycling
|
||||
newChapters.ref()
|
||||
it.viewerChapters?.unref()
|
||||
|
||||
// Add new references first to avoid unnecessary recycling
|
||||
newChapters.ref()
|
||||
oldChapters?.unref()
|
||||
|
||||
chapterToDownload = cancelQueuedDownloads(newChapters.currChapter)
|
||||
viewerChaptersRelay.call(newChapters)
|
||||
chapterToDownload = cancelQueuedDownloads(newChapters.currChapter)
|
||||
it.copy(viewerChapters = newChapters)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -444,20 +394,20 @@ class ReaderPresenter(
|
||||
* Called when the user changed to the given [chapter] when changing pages from the viewer.
|
||||
* It's used only to set this chapter as active.
|
||||
*/
|
||||
private fun loadNewChapter(chapter: ReaderChapter) {
|
||||
private suspend fun loadNewChapter(chapter: ReaderChapter) {
|
||||
val loader = loader ?: return
|
||||
|
||||
logcat { "Loading ${chapter.chapter.url}" }
|
||||
|
||||
activeChapterSubscription?.unsubscribe()
|
||||
activeChapterSubscription = getLoadObservable(loader, chapter)
|
||||
.toCompletable()
|
||||
.onErrorComplete()
|
||||
.subscribe()
|
||||
.also(::add)
|
||||
withIOContext {
|
||||
getLoadObservable(loader, chapter)
|
||||
.asFlow()
|
||||
.catch { logcat(LogPriority.ERROR, it) }
|
||||
.first()
|
||||
}
|
||||
}
|
||||
|
||||
fun loadNewChapterFromDialog(chapter: DomainChapter) {
|
||||
suspend fun loadNewChapterFromDialog(chapter: Chapter) {
|
||||
val newChapter = chapterList.firstOrNull { it.chapter.id == chapter.id } ?: return
|
||||
loadAdjacent(newChapter)
|
||||
}
|
||||
@ -467,37 +417,32 @@ class ReaderPresenter(
|
||||
* sets the [isLoadingAdjacentChapterRelay] that the view uses to prevent any further
|
||||
* interaction until the chapter is loaded.
|
||||
*/
|
||||
private fun loadAdjacent(chapter: ReaderChapter) {
|
||||
private suspend fun loadAdjacent(chapter: ReaderChapter) {
|
||||
val loader = loader ?: return
|
||||
|
||||
logcat { "Loading adjacent ${chapter.chapter.url}" }
|
||||
|
||||
activeChapterSubscription?.unsubscribe()
|
||||
activeChapterSubscription = getLoadObservable(loader, chapter)
|
||||
.doOnSubscribe { coroutineScope.launch { isLoadingAdjacentChapterEvent.send(true) } }
|
||||
.doOnUnsubscribe { coroutineScope.launch { isLoadingAdjacentChapterEvent.send(false) } }
|
||||
.subscribeFirst(
|
||||
{ view, _ ->
|
||||
view.moveToPageIndex(0)
|
||||
},
|
||||
{ _, _ ->
|
||||
// Ignore onError event, viewers handle that state
|
||||
},
|
||||
)
|
||||
mutableState.update { it.copy(isLoadingAdjacentChapter = true) }
|
||||
withIOContext {
|
||||
getLoadObservable(loader, chapter)
|
||||
.asFlow()
|
||||
.first()
|
||||
}
|
||||
mutableState.update { it.copy(isLoadingAdjacentChapter = false) }
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when the viewers decide it's a good time to preload a [chapter] and improve the UX so
|
||||
* that the user doesn't have to wait too long to continue reading.
|
||||
*/
|
||||
private fun preload(chapter: ReaderChapter) {
|
||||
private suspend fun preload(chapter: ReaderChapter) {
|
||||
if (chapter.pageLoader is HttpPageLoader) {
|
||||
val manga = manga ?: return
|
||||
val dbChapter = chapter.chapter
|
||||
val isDownloaded = downloadManager.isChapterDownloaded(
|
||||
dbChapter.name,
|
||||
dbChapter.scanlator,
|
||||
/* SY --> */ manga.originalTitle /* SY <-- */,
|
||||
/* SY --> */ manga.ogTitle /* SY <-- */,
|
||||
manga.source,
|
||||
skipCache = true,
|
||||
)
|
||||
@ -513,13 +458,14 @@ class ReaderPresenter(
|
||||
logcat { "Preloading ${chapter.chapter.url}" }
|
||||
|
||||
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)
|
||||
withIOContext {
|
||||
loader.loadChapter(chapter)
|
||||
.doOnCompleted { eventChannel.trySend(Event.ReloadViewerChapters) }
|
||||
.onErrorComplete()
|
||||
.toObservable<Unit>()
|
||||
.asFlow()
|
||||
.firstOrNull()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@ -528,7 +474,7 @@ class ReaderPresenter(
|
||||
* [page]'s chapter is different from the currently active.
|
||||
*/
|
||||
fun onPageSelected(page: ReaderPage, hasExtraPage: Boolean) {
|
||||
val currentChapters = viewerChaptersRelay.value ?: return
|
||||
val currentChapters = state.value.viewerChapters ?: return
|
||||
|
||||
val selectedChapter = page.chapter
|
||||
|
||||
@ -547,7 +493,7 @@ class ReaderPresenter(
|
||||
selectedChapter.chapter.read = true
|
||||
// SY -->
|
||||
if (manga?.isEhBasedManga() == true) {
|
||||
coroutineScope.launchNonCancellable {
|
||||
viewModelScope.launchNonCancellable {
|
||||
chapterList
|
||||
.filter { it.chapter.source_order > selectedChapter.chapter.source_order }
|
||||
.onEach {
|
||||
@ -565,7 +511,7 @@ class ReaderPresenter(
|
||||
logcat { "Setting ${selectedChapter.chapter.url} as active" }
|
||||
saveReadingProgress(currentChapters.currChapter)
|
||||
setReadStartTime()
|
||||
loadNewChapter(selectedChapter)
|
||||
viewModelScope.launch { loadNewChapter(selectedChapter) }
|
||||
}
|
||||
val pages = page.chapter.pages ?: return
|
||||
val inDownloadRange = page.number.toDouble() / pages.size > 0.25
|
||||
@ -581,23 +527,23 @@ class ReaderPresenter(
|
||||
|
||||
// Only download ahead if current + next chapter is already downloaded too to avoid jank
|
||||
if (getCurrentChapter()?.pageLoader !is DownloadPageLoader) return
|
||||
val nextChapter = viewerChaptersRelay.value?.nextChapter?.chapter ?: return
|
||||
val nextChapter = state.value.viewerChapters?.nextChapter?.chapter ?: return
|
||||
|
||||
coroutineScope.launchIO {
|
||||
viewModelScope.launchIO {
|
||||
val isNextChapterDownloaded = downloadManager.isChapterDownloaded(
|
||||
nextChapter.name,
|
||||
nextChapter.scanlator,
|
||||
// SY -->
|
||||
manga.originalTitle,
|
||||
manga.ogTitle,
|
||||
// SY <--
|
||||
manga.source,
|
||||
)
|
||||
if (!isNextChapterDownloaded) return@launchIO
|
||||
|
||||
val chaptersToDownload = getNextChapters.await(manga.id!!, nextChapter.id!!)
|
||||
val chaptersToDownload = getNextChapters.await(manga.id, nextChapter.id!!)
|
||||
.take(amount)
|
||||
downloadManager.downloadChapters(
|
||||
manga.toDomainManga()!!,
|
||||
manga,
|
||||
chaptersToDownload,
|
||||
)
|
||||
}
|
||||
@ -641,7 +587,7 @@ class ReaderPresenter(
|
||||
* Called when reader chapter is changed in reader or when activity is paused.
|
||||
*/
|
||||
private fun saveReadingProgress(readerChapter: ReaderChapter) {
|
||||
coroutineScope.launchNonCancellable {
|
||||
viewModelScope.launchNonCancellable {
|
||||
saveChapterProgress(readerChapter)
|
||||
saveChapterHistory(readerChapter)
|
||||
}
|
||||
@ -689,23 +635,23 @@ class ReaderPresenter(
|
||||
/**
|
||||
* Called from the activity to preload the given [chapter].
|
||||
*/
|
||||
fun preloadChapter(chapter: ReaderChapter) {
|
||||
suspend fun preloadChapter(chapter: ReaderChapter) {
|
||||
preload(chapter)
|
||||
}
|
||||
|
||||
/**
|
||||
* Called from the activity to load and set the next chapter as active.
|
||||
*/
|
||||
fun loadNextChapter() {
|
||||
val nextChapter = viewerChaptersRelay.value?.nextChapter ?: return
|
||||
suspend fun loadNextChapter() {
|
||||
val nextChapter = state.value.viewerChapters?.nextChapter ?: return
|
||||
loadAdjacent(nextChapter)
|
||||
}
|
||||
|
||||
/**
|
||||
* Called from the activity to load and set the previous chapter as active.
|
||||
*/
|
||||
fun loadPreviousChapter() {
|
||||
val prevChapter = viewerChaptersRelay.value?.prevChapter ?: return
|
||||
suspend fun loadPreviousChapter() {
|
||||
val prevChapter = state.value.viewerChapters?.prevChapter ?: return
|
||||
loadAdjacent(prevChapter)
|
||||
}
|
||||
|
||||
@ -713,7 +659,7 @@ class ReaderPresenter(
|
||||
* Returns the currently active chapter.
|
||||
*/
|
||||
fun getCurrentChapter(): ReaderChapter? {
|
||||
return viewerChaptersRelay.value?.currChapter
|
||||
return state.value.viewerChapters?.currChapter
|
||||
}
|
||||
|
||||
fun getSource() = manga?.source?.let { sourceManager.getOrStub(it) } as? HttpSource
|
||||
@ -731,7 +677,7 @@ class ReaderPresenter(
|
||||
fun bookmarkCurrentChapter(bookmarked: Boolean) {
|
||||
val chapter = getCurrentChapter()?.chapter ?: return
|
||||
chapter.bookmark = bookmarked // Otherwise the bookmark icon doesn't update
|
||||
coroutineScope.launchNonCancellable {
|
||||
viewModelScope.launchNonCancellable {
|
||||
updateChapter.await(
|
||||
ChapterUpdate(
|
||||
id = chapter.id!!.toLong(),
|
||||
@ -745,7 +691,7 @@ class ReaderPresenter(
|
||||
fun toggleBookmark(chapterId: Long, bookmarked: Boolean) {
|
||||
val chapter = chapterList.find { it.chapter.id == chapterId }?.chapter ?: return
|
||||
chapter.bookmark = bookmarked
|
||||
coroutineScope.launchNonCancellable {
|
||||
viewModelScope.launchNonCancellable {
|
||||
updateChapter.await(
|
||||
ChapterUpdate(
|
||||
id = chapter.id!!.toLong(),
|
||||
@ -762,7 +708,7 @@ class ReaderPresenter(
|
||||
fun getMangaReadingMode(resolveDefault: Boolean = true): Int {
|
||||
val default = readerPreferences.defaultReadingMode().get()
|
||||
val manga = manga ?: return default
|
||||
val readingMode = ReadingModeType.fromPreference(manga.readingModeType)
|
||||
val readingMode = ReadingModeType.fromPreference(manga.readingModeType.toInt())
|
||||
// SY -->
|
||||
return when {
|
||||
resolveDefault && readingMode == ReadingModeType.DEFAULT && readerPreferences.useAutoWebtoon().get() -> {
|
||||
@ -770,7 +716,7 @@ class ReaderPresenter(
|
||||
?: default
|
||||
}
|
||||
resolveDefault && readingMode == ReadingModeType.DEFAULT -> default
|
||||
else -> manga.readingModeType
|
||||
else -> manga.readingModeType.toInt()
|
||||
}
|
||||
// SY <--
|
||||
}
|
||||
@ -780,22 +726,21 @@ class ReaderPresenter(
|
||||
*/
|
||||
fun setMangaReadingMode(readingModeType: Int) {
|
||||
val manga = manga ?: return
|
||||
manga.readingModeType = readingModeType
|
||||
|
||||
coroutineScope.launchIO {
|
||||
setMangaViewerFlags.awaitSetMangaReadingMode(manga.id!!.toLong(), readingModeType.toLong())
|
||||
delay(250)
|
||||
val currChapters = viewerChaptersRelay.value
|
||||
viewModelScope.launchIO {
|
||||
setMangaViewerFlags.awaitSetMangaReadingMode(manga.id, readingModeType.toLong())
|
||||
val currChapters = state.value.viewerChapters
|
||||
if (currChapters != null) {
|
||||
// Save current page
|
||||
val currChapter = currChapters.currChapter
|
||||
currChapter.requestedPage = currChapter.chapter.last_page_read
|
||||
|
||||
withUIContext {
|
||||
// Emit manga and chapters to the new viewer
|
||||
view?.setManga(manga)
|
||||
view?.setChapters(currChapters)
|
||||
mutableState.update {
|
||||
it.copy(
|
||||
manga = getManga.await(manga.id),
|
||||
viewerChapters = currChapters,
|
||||
)
|
||||
}
|
||||
eventChannel.send(Event.ReloadViewerChapters)
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -805,10 +750,10 @@ class ReaderPresenter(
|
||||
*/
|
||||
fun getMangaOrientationType(resolveDefault: Boolean = true): Int {
|
||||
val default = readerPreferences.defaultOrientationType().get()
|
||||
val orientation = OrientationType.fromPreference(manga?.orientationType)
|
||||
val orientation = OrientationType.fromPreference(manga?.orientationType?.toInt())
|
||||
return when {
|
||||
resolveDefault && orientation == OrientationType.DEFAULT -> default
|
||||
else -> manga?.orientationType ?: default
|
||||
else -> manga?.orientationType?.toInt() ?: default
|
||||
}
|
||||
}
|
||||
|
||||
@ -817,14 +762,22 @@ class ReaderPresenter(
|
||||
*/
|
||||
fun setMangaOrientationType(rotationType: Int) {
|
||||
val manga = manga ?: return
|
||||
manga.orientationType = rotationType
|
||||
|
||||
coroutineScope.launchIO {
|
||||
setMangaViewerFlags.awaitSetOrientationType(manga.id!!.toLong(), rotationType.toLong())
|
||||
delay(250)
|
||||
val currChapters = viewerChaptersRelay.value
|
||||
viewModelScope.launchIO {
|
||||
setMangaViewerFlags.awaitSetOrientationType(manga.id, rotationType.toLong())
|
||||
val currChapters = state.value.viewerChapters
|
||||
if (currChapters != null) {
|
||||
withUIContext { view?.setOrientation(getMangaOrientationType()) }
|
||||
// Save current page
|
||||
val currChapter = currChapters.currChapter
|
||||
currChapter.requestedPage = currChapter.chapter.last_page_read
|
||||
|
||||
mutableState.update {
|
||||
it.copy(
|
||||
manga = getManga.await(manga.id),
|
||||
viewerChapters = currChapters,
|
||||
)
|
||||
}
|
||||
eventChannel.send(Event.SetOrientation(getMangaOrientationType()))
|
||||
eventChannel.send(Event.ReloadViewerChapters)
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -861,8 +814,8 @@ class ReaderPresenter(
|
||||
val relativePath = if (readerPreferences.folderPerManga().get()) DiskUtil.buildValidFilename(manga.title) else ""
|
||||
|
||||
// Copy file in background.
|
||||
try {
|
||||
coroutineScope.launchNonCancellable {
|
||||
viewModelScope.launchNonCancellable {
|
||||
try {
|
||||
val uri = imageSaver.save(
|
||||
image = Image.Page(
|
||||
inputStream = page.stream!!,
|
||||
@ -872,12 +825,12 @@ class ReaderPresenter(
|
||||
)
|
||||
withUIContext {
|
||||
notifier.onComplete(uri)
|
||||
view?.onSaveImageResult(SaveImageResult.Success(uri))
|
||||
eventChannel.send(Event.SavedImage(SaveImageResult.Success(uri)))
|
||||
}
|
||||
} catch (e: Throwable) {
|
||||
notifier.onError(e.message)
|
||||
eventChannel.send(Event.SavedImage(SaveImageResult.Error(e)))
|
||||
}
|
||||
} catch (e: Throwable) {
|
||||
notifier.onError(e.message)
|
||||
view?.onSaveImageResult(SaveImageResult.Error(e))
|
||||
}
|
||||
}
|
||||
|
||||
@ -895,8 +848,8 @@ class ReaderPresenter(
|
||||
val relativePath = if (readerPreferences.folderPerManga().get()) DiskUtil.buildValidFilename(manga.title) else ""
|
||||
|
||||
// Copy file in background.
|
||||
try {
|
||||
coroutineScope.launchIO {
|
||||
viewModelScope.launchNonCancellable {
|
||||
try {
|
||||
val uri = saveImages(
|
||||
page1 = firstPage,
|
||||
page2 = secondPage,
|
||||
@ -905,14 +858,11 @@ class ReaderPresenter(
|
||||
location = Location.Pictures.create(relativePath),
|
||||
manga = manga,
|
||||
)
|
||||
withUIContext {
|
||||
notifier.onComplete(uri)
|
||||
view!!.onSaveImageResult(SaveImageResult.Success(uri))
|
||||
}
|
||||
eventChannel.send(Event.SavedImage(SaveImageResult.Success(uri)))
|
||||
} catch (e: Throwable) {
|
||||
notifier.onError(e.message)
|
||||
eventChannel.send(Event.SavedImage(SaveImageResult.Error(e)))
|
||||
}
|
||||
} catch (e: Throwable) {
|
||||
notifier.onError(e.message)
|
||||
view!!.onSaveImageResult(SaveImageResult.Error(e))
|
||||
}
|
||||
}
|
||||
|
||||
@ -966,7 +916,7 @@ class ReaderPresenter(
|
||||
val filename = generateFilename(manga, page)
|
||||
|
||||
try {
|
||||
coroutineScope.launchNonCancellable {
|
||||
viewModelScope.launchNonCancellable {
|
||||
destDir.deleteRecursively()
|
||||
val uri = imageSaver.save(
|
||||
image = Image.Page(
|
||||
@ -975,9 +925,7 @@ class ReaderPresenter(
|
||||
location = Location.Cache,
|
||||
),
|
||||
)
|
||||
withUIContext {
|
||||
view?.onShareImageResult(uri, page)
|
||||
}
|
||||
eventChannel.send(Event.ShareImage(uri, page))
|
||||
}
|
||||
} catch (e: Throwable) {
|
||||
logcat(LogPriority.ERROR, e)
|
||||
@ -994,7 +942,7 @@ class ReaderPresenter(
|
||||
val destDir = context.cacheImageDir
|
||||
|
||||
try {
|
||||
coroutineScope.launchIO {
|
||||
viewModelScope.launchNonCancellable {
|
||||
destDir.deleteRecursively()
|
||||
val uri = saveImages(
|
||||
page1 = firstPage,
|
||||
@ -1004,9 +952,7 @@ class ReaderPresenter(
|
||||
location = Location.Cache,
|
||||
manga = manga,
|
||||
)
|
||||
withUIContext {
|
||||
view!!.onShareImageResult(uri, firstPage, secondPage)
|
||||
}
|
||||
eventChannel.send(Event.ShareImage(uri, firstPage, secondPage))
|
||||
}
|
||||
} catch (e: Throwable) {
|
||||
logcat(LogPriority.ERROR, e)
|
||||
@ -1019,24 +965,21 @@ class ReaderPresenter(
|
||||
*/
|
||||
fun setAsCover(context: Context, page: ReaderPage) {
|
||||
if (page.status != Page.State.READY) return
|
||||
val manga = manga?.toDomainManga() ?: return
|
||||
val manga = manga ?: return
|
||||
val stream = page.stream ?: return
|
||||
|
||||
coroutineScope.launchNonCancellable {
|
||||
try {
|
||||
viewModelScope.launchNonCancellable {
|
||||
val result = try {
|
||||
manga.editCover(context, stream())
|
||||
withUIContext {
|
||||
view?.onSetAsCoverResult(
|
||||
if (manga.isLocal() || manga.favorite) {
|
||||
SetAsCoverResult.Success
|
||||
} else {
|
||||
SetAsCoverResult.AddToLibraryFirst
|
||||
},
|
||||
)
|
||||
if (manga.isLocal() || manga.favorite) {
|
||||
SetAsCoverResult.Success
|
||||
} else {
|
||||
SetAsCoverResult.AddToLibraryFirst
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
withUIContext { view?.onSetAsCoverResult(SetAsCoverResult.Error) }
|
||||
SetAsCoverResult.Error
|
||||
}
|
||||
eventChannel.send(Event.SetCoverResult(result))
|
||||
}
|
||||
}
|
||||
|
||||
@ -1068,8 +1011,8 @@ class ReaderPresenter(
|
||||
val trackManager = Injekt.get<TrackManager>()
|
||||
val context = Injekt.get<Application>()
|
||||
|
||||
coroutineScope.launchNonCancellable {
|
||||
getTracks.await(manga.id!!)
|
||||
viewModelScope.launchNonCancellable {
|
||||
getTracks.await(manga.id)
|
||||
.mapNotNull { track ->
|
||||
val service = trackManager.getService(track.syncId)
|
||||
if (service != null && service.isLogged && chapterRead > track.lastChapterRead /* SY --> */ && ((service.id == TrackManager.MDLIST && track.status != FollowStatus.UNFOLLOWED.int.toLong()) || service.id != TrackManager.MDLIST)/* SY <-- */) {
|
||||
@ -1106,16 +1049,17 @@ class ReaderPresenter(
|
||||
*/
|
||||
private fun enqueueDeleteReadChapters(chapter: ReaderChapter) {
|
||||
if (!chapter.chapter.read) return
|
||||
val mergedManga = state.value.mergedManga
|
||||
// SY -->
|
||||
val manga = if (mergedManga.isNullOrEmpty()) {
|
||||
manga
|
||||
} else {
|
||||
mergedManga.orEmpty()[chapter.chapter.manga_id]?.toDbManga()
|
||||
mergedManga[chapter.chapter.manga_id]
|
||||
} ?: return
|
||||
// SY <--
|
||||
|
||||
coroutineScope.launchNonCancellable {
|
||||
downloadManager.enqueueChaptersToDelete(listOf(chapter.chapter.toDomainChapter()!!), manga.toDomainManga()!!)
|
||||
viewModelScope.launchNonCancellable {
|
||||
downloadManager.enqueueChaptersToDelete(listOf(chapter.chapter.toDomainChapter()!!), manga)
|
||||
}
|
||||
}
|
||||
|
||||
@ -1124,35 +1068,30 @@ class ReaderPresenter(
|
||||
* are ignored.
|
||||
*/
|
||||
private fun deletePendingChapters() {
|
||||
coroutineScope.launchNonCancellable {
|
||||
viewModelScope.launchNonCancellable {
|
||||
downloadManager.deletePendingChapters()
|
||||
}
|
||||
}
|
||||
|
||||
// We're trying to avoid using Rx, so we "undeprecate" this
|
||||
@Suppress("DEPRECATION")
|
||||
override fun getView(): ReaderActivity? {
|
||||
return super.getView()
|
||||
data class State(
|
||||
val manga: Manga? = null,
|
||||
val viewerChapters: ViewerChapters? = null,
|
||||
val isLoadingAdjacentChapter: Boolean = false,
|
||||
// SY -->
|
||||
val meta: RaisedSearchMetadata? = null,
|
||||
val mergedManga: Map<Long, Manga>? = null,
|
||||
// SY <--
|
||||
)
|
||||
|
||||
sealed class Event {
|
||||
object ReloadViewerChapters : Event()
|
||||
data class SetOrientation(val orientation: Int) : Event()
|
||||
data class SetCoverResult(val result: SetAsCoverResult) : Event()
|
||||
|
||||
data class SavedImage(val result: SaveImageResult) : Event()
|
||||
data class ShareImage(val uri: Uri, val page: ReaderPage, val secondPage: ReaderPage? = null) : Event()
|
||||
}
|
||||
|
||||
/**
|
||||
* Subscribes an observable with [deliverFirst] and adds it to the presenter's lifecycle
|
||||
* subscription list.
|
||||
*
|
||||
* @param onNext function to execute when the observable emits an item.
|
||||
* @param onError function to execute when the observable throws an error.
|
||||
*/
|
||||
private fun <T> Observable<T>.subscribeFirst(onNext: (ReaderActivity, T) -> Unit, onError: ((ReaderActivity, Throwable) -> Unit) = { _, _ -> }) = compose(deliverFirst<T>()).subscribe(split(onNext, onError)).apply { add(this) }
|
||||
|
||||
/**
|
||||
* Subscribes an observable with [deliverLatestCache] and adds it to the presenter's lifecycle
|
||||
* subscription list.
|
||||
*
|
||||
* @param onNext function to execute when the observable emits an item.
|
||||
* @param onError function to execute when the observable throws an error.
|
||||
*/
|
||||
private fun <T> Observable<T>.subscribeLatestCache(onNext: (ReaderActivity, T) -> Unit, onError: ((ReaderActivity, Throwable) -> Unit) = { _, _ -> }) = compose(deliverLatestCache<T>()).subscribe(split(onNext, onError)).apply { add(this) }
|
||||
|
||||
companion object {
|
||||
// Safe theoretical max filename size is 255 bytes and 1 char = 2-4 bytes (UTF-8)
|
||||
private const val MAX_FILE_NAME_BYTES = 250
|
@ -7,10 +7,9 @@ import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||
import eu.davidea.flexibleadapter.FlexibleAdapter
|
||||
import eu.kanade.domain.chapter.model.Chapter
|
||||
import eu.kanade.tachiyomi.R
|
||||
import eu.kanade.tachiyomi.data.database.models.toDomainManga
|
||||
import eu.kanade.tachiyomi.databinding.ReaderChaptersDialogBinding
|
||||
import eu.kanade.tachiyomi.ui.reader.ReaderActivity
|
||||
import eu.kanade.tachiyomi.ui.reader.ReaderPresenter
|
||||
import eu.kanade.tachiyomi.ui.reader.ReaderViewModel
|
||||
import eu.kanade.tachiyomi.util.chapter.getChapterSort
|
||||
import eu.kanade.tachiyomi.util.system.dpToPx
|
||||
import kotlinx.coroutines.launch
|
||||
@ -18,7 +17,7 @@ import kotlinx.coroutines.launch
|
||||
class ReaderChapterDialog(private val activity: ReaderActivity) : ReaderChapterAdapter.OnBookmarkClickListener {
|
||||
private val binding = ReaderChaptersDialogBinding.inflate(activity.layoutInflater)
|
||||
|
||||
var presenter: ReaderPresenter = activity.presenter
|
||||
var viewModel: ReaderViewModel = activity.viewModel
|
||||
var adapter: FlexibleAdapter<ReaderChapterItem>? = null
|
||||
var dialog: AlertDialog
|
||||
|
||||
@ -35,9 +34,11 @@ class ReaderChapterDialog(private val activity: ReaderActivity) : ReaderChapterA
|
||||
|
||||
adapter?.mItemClickListener = FlexibleAdapter.OnItemClickListener { _, position ->
|
||||
val item = adapter?.getItem(position)
|
||||
if (item != null && item.chapter.id != presenter.getCurrentChapter()?.chapter?.id) {
|
||||
if (item != null && item.chapter.id != viewModel.getCurrentChapter()?.chapter?.id) {
|
||||
dialog.dismiss()
|
||||
presenter.loadNewChapterFromDialog(item.chapter)
|
||||
activity.lifecycleScope.launch {
|
||||
viewModel.loadNewChapterFromDialog(item.chapter)
|
||||
}
|
||||
}
|
||||
true
|
||||
}
|
||||
@ -51,8 +52,8 @@ class ReaderChapterDialog(private val activity: ReaderActivity) : ReaderChapterA
|
||||
}
|
||||
|
||||
private fun refreshList(scroll: Boolean = true) {
|
||||
val chapterSort = getChapterSort(presenter.manga!!.toDomainManga()!!)
|
||||
val chapters = presenter.getChapters(activity)
|
||||
val chapterSort = getChapterSort(viewModel.manga!!)
|
||||
val chapters = viewModel.getChapters(activity)
|
||||
.sortedWith { a, b ->
|
||||
chapterSort(a.chapter, b.chapter)
|
||||
}
|
||||
@ -73,7 +74,7 @@ class ReaderChapterDialog(private val activity: ReaderActivity) : ReaderChapterA
|
||||
}
|
||||
|
||||
override fun bookmarkChapter(chapter: Chapter) {
|
||||
presenter.toggleBookmark(chapter.id, !chapter.bookmark)
|
||||
viewModel.toggleBookmark(chapter.id, !chapter.bookmark)
|
||||
refreshList(scroll = false)
|
||||
}
|
||||
}
|
||||
|
@ -44,22 +44,22 @@ class ReaderReadingModeSettings @JvmOverloads constructor(context: Context, attr
|
||||
private fun initGeneralPreferences() {
|
||||
binding.viewer.onItemSelectedListener = { position ->
|
||||
val readingModeType = ReadingModeType.fromSpinner(position)
|
||||
(context as ReaderActivity).presenter.setMangaReadingMode(readingModeType.flagValue)
|
||||
(context as ReaderActivity).viewModel.setMangaReadingMode(readingModeType.flagValue)
|
||||
|
||||
val mangaViewer = (context as ReaderActivity).presenter.getMangaReadingMode()
|
||||
val mangaViewer = (context as ReaderActivity).viewModel.getMangaReadingMode()
|
||||
if (mangaViewer == ReadingModeType.WEBTOON.flagValue || mangaViewer == ReadingModeType.CONTINUOUS_VERTICAL.flagValue) {
|
||||
initWebtoonPreferences()
|
||||
} else {
|
||||
initPagerPreferences()
|
||||
}
|
||||
}
|
||||
binding.viewer.setSelection((context as ReaderActivity).presenter.manga?.readingModeType?.let { ReadingModeType.fromPreference(it).prefValue } ?: ReadingModeType.DEFAULT.prefValue)
|
||||
binding.viewer.setSelection((context as ReaderActivity).viewModel.manga?.readingModeType?.let { ReadingModeType.fromPreference(it.toInt()).prefValue } ?: ReadingModeType.DEFAULT.prefValue)
|
||||
|
||||
binding.rotationMode.onItemSelectedListener = { position ->
|
||||
val rotationType = OrientationType.fromSpinner(position)
|
||||
(context as ReaderActivity).presenter.setMangaOrientationType(rotationType.flagValue)
|
||||
(context as ReaderActivity).viewModel.setMangaOrientationType(rotationType.flagValue)
|
||||
}
|
||||
binding.rotationMode.setSelection((context as ReaderActivity).presenter.manga?.orientationType?.let { OrientationType.fromPreference(it).prefValue } ?: OrientationType.DEFAULT.prefValue)
|
||||
binding.rotationMode.setSelection((context as ReaderActivity).viewModel.manga?.orientationType?.let { OrientationType.fromPreference(it.toInt()).prefValue } ?: OrientationType.DEFAULT.prefValue)
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -11,8 +11,8 @@ import androidx.core.text.bold
|
||||
import androidx.core.text.buildSpannedString
|
||||
import androidx.core.text.inSpans
|
||||
import androidx.core.view.isVisible
|
||||
import eu.kanade.domain.manga.model.Manga
|
||||
import eu.kanade.tachiyomi.R
|
||||
import eu.kanade.tachiyomi.data.database.models.Manga
|
||||
import eu.kanade.tachiyomi.data.download.DownloadManager
|
||||
import eu.kanade.tachiyomi.databinding.ReaderTransitionViewBinding
|
||||
import eu.kanade.tachiyomi.ui.reader.loader.DownloadPageLoader
|
||||
@ -55,7 +55,7 @@ class ReaderTransitionView @JvmOverloads constructor(context: Context, attrs: At
|
||||
val isPrevDownloaded = downloadManager.isChapterDownloaded(
|
||||
prevChapter.name,
|
||||
prevChapter.scanlator,
|
||||
/* SY --> */ manga.originalTitle /* SY <-- */,
|
||||
/* SY --> */ manga.ogTitle /* SY <-- */,
|
||||
manga.source,
|
||||
skipCache = true,
|
||||
)
|
||||
@ -93,7 +93,7 @@ class ReaderTransitionView @JvmOverloads constructor(context: Context, attrs: At
|
||||
val isNextDownloaded = downloadManager.isChapterDownloaded(
|
||||
nextChapter.name,
|
||||
nextChapter.scanlator,
|
||||
/* SY --> */ manga.originalTitle /* SY <-- */,
|
||||
/* SY --> */ manga.ogTitle /* SY <-- */,
|
||||
manga.source,
|
||||
skipCache = true,
|
||||
)
|
||||
|
@ -62,7 +62,7 @@ class PagerTransitionHolder(
|
||||
addView(transitionView)
|
||||
addView(pagesContainer)
|
||||
|
||||
transitionView.bind(transition, viewer.downloadManager, viewer.activity.presenter.manga)
|
||||
transitionView.bind(transition, viewer.downloadManager, viewer.activity.viewModel.manga)
|
||||
|
||||
transition.to?.let { observeStatus(it) }
|
||||
}
|
||||
|
@ -64,7 +64,7 @@ class WebtoonTransitionHolder(
|
||||
* Binds the given [transition] with this view holder, subscribing to its state.
|
||||
*/
|
||||
fun bind(transition: ChapterTransition) {
|
||||
transitionView.bind(transition, viewer.downloadManager, viewer.activity.presenter.manga)
|
||||
transitionView.bind(transition, viewer.downloadManager, viewer.activity.viewModel.manga)
|
||||
|
||||
transition.to?.let { observeStatus(it, transition) }
|
||||
}
|
||||
|
@ -1,14 +1,13 @@
|
||||
package exh.util
|
||||
|
||||
import android.content.Context
|
||||
import eu.kanade.domain.manga.model.Manga
|
||||
import eu.kanade.tachiyomi.R
|
||||
import eu.kanade.tachiyomi.data.database.models.Manga
|
||||
import eu.kanade.tachiyomi.source.SourceManager
|
||||
import eu.kanade.tachiyomi.ui.reader.setting.ReadingModeType
|
||||
import uy.kohesive.injekt.Injekt
|
||||
import uy.kohesive.injekt.api.get
|
||||
import java.util.Locale
|
||||
import eu.kanade.domain.manga.model.Manga as DomainManga
|
||||
|
||||
fun Manga.mangaType(context: Context): String {
|
||||
return context.getString(
|
||||
@ -26,30 +25,6 @@ fun Manga.mangaType(context: Context): String {
|
||||
* The type of comic the manga is (ie. manga, manhwa, manhua)
|
||||
*/
|
||||
fun Manga.mangaType(sourceName: String? = Injekt.get<SourceManager>().get(source)?.name): MangaType {
|
||||
val currentTags = getGenres().orEmpty()
|
||||
return when {
|
||||
currentTags.any { tag -> isMangaTag(tag) } -> {
|
||||
MangaType.TYPE_MANGA
|
||||
}
|
||||
currentTags.any { tag -> isWebtoonTag(tag) } || sourceName?.let { isWebtoonSource(it) } == true -> {
|
||||
MangaType.TYPE_WEBTOON
|
||||
}
|
||||
currentTags.any { tag -> isComicTag(tag) } || sourceName?.let { isComicSource(it) } == true -> {
|
||||
MangaType.TYPE_COMIC
|
||||
}
|
||||
currentTags.any { tag -> isManhuaTag(tag) } || sourceName?.let { isManhuaSource(it) } == true -> {
|
||||
MangaType.TYPE_MANHUA
|
||||
}
|
||||
currentTags.any { tag -> isManhwaTag(tag) } || sourceName?.let { isManhwaSource(it) } == true -> {
|
||||
MangaType.TYPE_MANHWA
|
||||
}
|
||||
else -> {
|
||||
MangaType.TYPE_MANGA
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun DomainManga.mangaType(sourceName: String? = Injekt.get<SourceManager>().get(source)?.name): MangaType {
|
||||
val currentTags = genre.orEmpty()
|
||||
return when {
|
||||
currentTags.any { tag -> isMangaTag(tag) } -> {
|
||||
|
@ -1,7 +1,6 @@
|
||||
[versions]
|
||||
aboutlib_version = "10.5.2"
|
||||
okhttp_version = "5.0.0-alpha.10"
|
||||
nucleus_version = "3.0.0"
|
||||
coil_version = "2.2.2"
|
||||
shizuku_version = "12.2.0"
|
||||
sqlite = "2.3.0-rc01"
|
||||
@ -41,9 +40,6 @@ sqlite-android = "com.github.requery:sqlite-android:3.39.2"
|
||||
|
||||
preferencektx = "androidx.preference:preference-ktx:1.2.0"
|
||||
|
||||
nucleus-core = { module = "info.android15.nucleus:nucleus", version.ref = "nucleus_version" }
|
||||
nucleus-supportv7 = { module = "info.android15.nucleus:nucleus-support-v7", version.ref = "nucleus_version" }
|
||||
|
||||
injekt-core = "com.github.inorichi.injekt:injekt-core:65b0440"
|
||||
|
||||
coil-core = { module = "io.coil-kt:coil", version.ref = "coil_version" }
|
||||
@ -97,7 +93,6 @@ reactivex = ["rxandroid", "rxjava", "rxrelay"]
|
||||
okhttp = ["okhttp-core", "okhttp-logging", "okhttp-dnsoverhttps"]
|
||||
js-engine = ["quickjs-android"]
|
||||
sqlite = ["sqlite-framework", "sqlite-ktx", "sqlite-android"]
|
||||
nucleus = ["nucleus-core", "nucleus-supportv7"]
|
||||
coil = ["coil-core", "coil-gif", "coil-compose"]
|
||||
shizuku = ["shizuku-api", "shizuku-provider"]
|
||||
voyager = ["voyager-navigator", "voyager-tab-navigator", "voyager-transitions"]
|
||||
|
Loading…
x
Reference in New Issue
Block a user