mirror of
				https://github.com/mihonapp/mihon.git
				synced 2025-11-04 08:08:55 +01:00 
			
		
		
		
	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>
This commit is contained in:
		@@ -1,17 +1,16 @@
 | 
			
		||||
package eu.kanade.domain.manga.model
 | 
			
		||||
 | 
			
		||||
import eu.kanade.data.listOfStringsAdapter
 | 
			
		||||
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.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 java.io.Serializable
 | 
			
		||||
import eu.kanade.tachiyomi.data.database.models.Manga as DbManga
 | 
			
		||||
 | 
			
		||||
data class Manga(
 | 
			
		||||
    val id: Long,
 | 
			
		||||
@@ -49,6 +48,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
 | 
			
		||||
@@ -187,28 +192,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
 | 
			
		||||
    it.title = title
 | 
			
		||||
    it.artist = artist
 | 
			
		||||
    it.author = author
 | 
			
		||||
    it.description = description
 | 
			
		||||
    it.genre = genre?.let(listOfStringsAdapter::encode)
 | 
			
		||||
    it.status = status.toInt()
 | 
			
		||||
    it.thumbnail_url = thumbnailUrl
 | 
			
		||||
    it.update_strategy = updateStrategy
 | 
			
		||||
    it.initialized = initialized
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
fun Manga.toMangaUpdate(): MangaUpdate {
 | 
			
		||||
    return MangaUpdate(
 | 
			
		||||
        id = id,
 | 
			
		||||
 
 | 
			
		||||
@@ -29,6 +29,8 @@ import android.view.animation.Animation
 | 
			
		||||
import android.view.animation.AnimationUtils
 | 
			
		||||
import android.widget.FrameLayout
 | 
			
		||||
import android.widget.Toast
 | 
			
		||||
import androidx.activity.viewModels
 | 
			
		||||
import androidx.appcompat.app.AppCompatActivity
 | 
			
		||||
import androidx.core.graphics.ColorUtils
 | 
			
		||||
import androidx.core.transition.doOnEnd
 | 
			
		||||
import androidx.core.view.WindowCompat
 | 
			
		||||
@@ -45,9 +47,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
 | 
			
		||||
@@ -56,9 +58,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.model.ReaderChapter
 | 
			
		||||
import eu.kanade.tachiyomi.ui.reader.model.ReaderPage
 | 
			
		||||
import eu.kanade.tachiyomi.ui.reader.model.ViewerChapters
 | 
			
		||||
@@ -71,6 +73,8 @@ import eu.kanade.tachiyomi.ui.reader.viewer.ReaderProgressIndicator
 | 
			
		||||
import eu.kanade.tachiyomi.ui.reader.viewer.pager.R2LPagerViewer
 | 
			
		||||
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
 | 
			
		||||
@@ -85,14 +89,16 @@ import eu.kanade.tachiyomi.util.view.copy
 | 
			
		||||
import eu.kanade.tachiyomi.util.view.popupMenu
 | 
			
		||||
import eu.kanade.tachiyomi.util.view.setTooltip
 | 
			
		||||
import eu.kanade.tachiyomi.widget.listener.SimpleAnimationListener
 | 
			
		||||
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.merge
 | 
			
		||||
import kotlinx.coroutines.flow.onEach
 | 
			
		||||
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
 | 
			
		||||
@@ -101,9 +107,8 @@ import kotlin.math.max
 | 
			
		||||
 * 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() {
 | 
			
		||||
 | 
			
		||||
@@ -128,6 +133,8 @@ class ReaderActivity :
 | 
			
		||||
 | 
			
		||||
    lateinit var binding: ReaderActivityBinding
 | 
			
		||||
 | 
			
		||||
    val viewModel by viewModels<ReaderViewModel>()
 | 
			
		||||
 | 
			
		||||
    val hasCutout by lazy { hasDisplayCutout() }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
@@ -194,7 +201,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)
 | 
			
		||||
            if (manga == -1L || chapter == -1L) {
 | 
			
		||||
@@ -202,7 +209,16 @@ class ReaderActivity :
 | 
			
		||||
                return
 | 
			
		||||
            }
 | 
			
		||||
            NotificationReceiver.dismissNotification(this, manga.hashCode(), Notifications.ID_NEW_CHAPTERS)
 | 
			
		||||
            presenter.init(manga, chapter)
 | 
			
		||||
 | 
			
		||||
            lifecycleScope.launchNonCancellable {
 | 
			
		||||
                val initResult = viewModel.init(manga, chapter)
 | 
			
		||||
                if (!initResult.getOrDefault(false)) {
 | 
			
		||||
                    val exception = initResult.exceptionOrNull() ?: IllegalStateException("Unknown err")
 | 
			
		||||
                    withUIContext {
 | 
			
		||||
                        setInitialChapterError(exception)
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if (savedInstanceState != null) {
 | 
			
		||||
@@ -217,6 +233,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)
 | 
			
		||||
                    }
 | 
			
		||||
                    is ReaderViewModel.Event.SetCoverResult -> {
 | 
			
		||||
                        onSetAsCoverResult(event.result)
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
            .launchIn(lifecycleScope)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
@@ -240,13 +298,13 @@ class ReaderActivity :
 | 
			
		||||
    override fun onSaveInstanceState(outState: Bundle) {
 | 
			
		||||
        outState.putBoolean(::menuVisible.name, menuVisible)
 | 
			
		||||
        if (!isChangingConfigurations) {
 | 
			
		||||
            presenter.onSaveInstanceStateNonConfigurationChange()
 | 
			
		||||
            viewModel.onSaveInstanceStateNonConfigurationChange()
 | 
			
		||||
        }
 | 
			
		||||
        super.onSaveInstanceState(outState)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    override fun onPause() {
 | 
			
		||||
        presenter.saveCurrentChapterReadingProgress()
 | 
			
		||||
        viewModel.saveCurrentChapterReadingProgress()
 | 
			
		||||
        super.onPause()
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@@ -256,7 +314,7 @@ class ReaderActivity :
 | 
			
		||||
     */
 | 
			
		||||
    override fun onResume() {
 | 
			
		||||
        super.onResume()
 | 
			
		||||
        presenter.setReadStartTime()
 | 
			
		||||
        viewModel.setReadStartTime()
 | 
			
		||||
        setMenuVisibility(menuVisible, animate = false)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@@ -277,7 +335,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
 | 
			
		||||
 | 
			
		||||
@@ -294,11 +352,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()
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
@@ -309,17 +367,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)
 | 
			
		||||
@@ -356,7 +414,7 @@ class ReaderActivity :
 | 
			
		||||
        setSupportActionBar(binding.toolbar)
 | 
			
		||||
        supportActionBar?.setDisplayHomeAsUpEnabled(true)
 | 
			
		||||
        binding.toolbar.setNavigationOnClickListener {
 | 
			
		||||
            onBackPressed()
 | 
			
		||||
            onBackPressedDispatcher.onBackPressed()
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        binding.toolbar.applyInsetter {
 | 
			
		||||
@@ -371,7 +429,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
 | 
			
		||||
@@ -461,11 +519,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()) {
 | 
			
		||||
@@ -482,7 +540,7 @@ class ReaderActivity :
 | 
			
		||||
            setTooltip(R.string.pref_crop_borders)
 | 
			
		||||
 | 
			
		||||
            setOnClickListener {
 | 
			
		||||
                val isPagerType = ReadingModeType.isPagerType(presenter.getMangaReadingMode())
 | 
			
		||||
                val isPagerType = ReadingModeType.isPagerType(viewModel.getMangaReadingMode())
 | 
			
		||||
                val enabled = if (isPagerType) {
 | 
			
		||||
                    readerPreferences.cropBorders().toggle()
 | 
			
		||||
                } else {
 | 
			
		||||
@@ -514,12 +572,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)
 | 
			
		||||
@@ -550,7 +608,7 @@ class ReaderActivity :
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private fun updateCropBordersShortcut() {
 | 
			
		||||
        val isPagerType = ReadingModeType.isPagerType(presenter.getMangaReadingMode())
 | 
			
		||||
        val isPagerType = ReadingModeType.isPagerType(viewModel.getMangaReadingMode())
 | 
			
		||||
        val enabled = if (isPagerType) {
 | 
			
		||||
            readerPreferences.cropBorders().get()
 | 
			
		||||
        } else {
 | 
			
		||||
@@ -633,19 +691,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
 | 
			
		||||
@@ -658,10 +716,10 @@ class ReaderActivity :
 | 
			
		||||
        binding.viewerContainer.addView(newViewer.getView())
 | 
			
		||||
 | 
			
		||||
        if (readerPreferences.showReadingMode().get()) {
 | 
			
		||||
            showReadingModeToast(presenter.getMangaReadingMode())
 | 
			
		||||
            showReadingModeToast(viewModel.getMangaReadingMode())
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        binding.toolbar.title = manga.title
 | 
			
		||||
        supportActionBar?.title = manga.title
 | 
			
		||||
 | 
			
		||||
        binding.pageSlider.isRTL = newViewer is R2LPagerViewer
 | 
			
		||||
        if (newViewer is R2LPagerViewer) {
 | 
			
		||||
@@ -684,9 +742,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)
 | 
			
		||||
@@ -707,7 +765,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)
 | 
			
		||||
        viewer?.setChapters(viewerChapters)
 | 
			
		||||
        binding.toolbar.subtitle = viewerChapters.currChapter.chapter.name
 | 
			
		||||
@@ -765,7 +823,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)
 | 
			
		||||
    }
 | 
			
		||||
@@ -775,7 +833,10 @@ class ReaderActivity :
 | 
			
		||||
     * should be automatically shown.
 | 
			
		||||
     */
 | 
			
		||||
    private fun loadNextChapter() {
 | 
			
		||||
        presenter.loadNextChapter()
 | 
			
		||||
        lifecycleScope.launch {
 | 
			
		||||
            viewModel.loadNextChapter()
 | 
			
		||||
            moveToPageIndex(0)
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
@@ -783,7 +844,10 @@ class ReaderActivity :
 | 
			
		||||
     * should be automatically shown.
 | 
			
		||||
     */
 | 
			
		||||
    private fun loadPreviousChapter() {
 | 
			
		||||
        presenter.loadPreviousChapter()
 | 
			
		||||
        lifecycleScope.launch {
 | 
			
		||||
            viewModel.loadPreviousChapter()
 | 
			
		||||
            moveToPageIndex(0)
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
@@ -792,7 +856,7 @@ class ReaderActivity :
 | 
			
		||||
     */
 | 
			
		||||
    @SuppressLint("SetTextI18n")
 | 
			
		||||
    fun onPageSelected(page: ReaderPage) {
 | 
			
		||||
        presenter.onPageSelected(page)
 | 
			
		||||
        viewModel.onPageSelected(page)
 | 
			
		||||
        val pages = page.chapter.pages ?: return
 | 
			
		||||
 | 
			
		||||
        // Set bottom page number
 | 
			
		||||
@@ -826,7 +890,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) }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
@@ -860,15 +924,15 @@ 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)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Called from the presenter when a page is ready to be shared. It shows Android's default
 | 
			
		||||
     * sharing tool.
 | 
			
		||||
     */
 | 
			
		||||
    fun onShareImageResult(uri: Uri, page: ReaderPage) {
 | 
			
		||||
        val manga = presenter.manga ?: return
 | 
			
		||||
    private fun onShareImageResult(uri: Uri, page: ReaderPage) {
 | 
			
		||||
        val manga = viewModel.manga ?: return
 | 
			
		||||
        val chapter = page.chapter.chapter
 | 
			
		||||
 | 
			
		||||
        val intent = uri.toShareIntent(
 | 
			
		||||
@@ -883,19 +947,19 @@ class ReaderActivity :
 | 
			
		||||
     * storage to the presenter.
 | 
			
		||||
     */
 | 
			
		||||
    fun saveImage(page: ReaderPage) {
 | 
			
		||||
        presenter.saveImage(page)
 | 
			
		||||
        viewModel.saveImage(page)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 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)
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
@@ -906,14 +970,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
 | 
			
		||||
@@ -926,12 +990,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,8 +3,10 @@ package eu.kanade.tachiyomi.ui.reader
 | 
			
		||||
import android.app.Application
 | 
			
		||||
import android.content.Context
 | 
			
		||||
import android.net.Uri
 | 
			
		||||
import android.os.Bundle
 | 
			
		||||
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.UpdateChapter
 | 
			
		||||
@@ -16,17 +18,15 @@ import eu.kanade.domain.history.interactor.UpsertHistory
 | 
			
		||||
import eu.kanade.domain.history.model.HistoryUpdate
 | 
			
		||||
import eu.kanade.domain.manga.interactor.GetManga
 | 
			
		||||
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
 | 
			
		||||
import eu.kanade.domain.track.service.DelayedTrackingUpdateJob
 | 
			
		||||
import eu.kanade.domain.track.service.TrackPreferences
 | 
			
		||||
import eu.kanade.domain.track.store.DelayedTrackingStore
 | 
			
		||||
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
 | 
			
		||||
@@ -54,37 +54,41 @@ 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
 | 
			
		||||
import eu.kanade.tachiyomi.util.system.isOnline
 | 
			
		||||
import eu.kanade.tachiyomi.util.system.logcat
 | 
			
		||||
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 uy.kohesive.injekt.Injekt
 | 
			
		||||
import uy.kohesive.injekt.api.get
 | 
			
		||||
import java.util.Date
 | 
			
		||||
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(),
 | 
			
		||||
@@ -102,20 +106,28 @@ class ReaderPresenter(
 | 
			
		||||
    private val upsertHistory: UpsertHistory = Injekt.get(),
 | 
			
		||||
    private val updateChapter: UpdateChapter = Injekt.get(),
 | 
			
		||||
    private val setMangaViewerFlags: SetMangaViewerFlags = Injekt.get(),
 | 
			
		||||
) : 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
 | 
			
		||||
    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.
 | 
			
		||||
@@ -132,16 +144,6 @@ class ReaderPresenter(
 | 
			
		||||
     */
 | 
			
		||||
    private var activeChapterSubscription: Subscription? = null
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Relay for currently active viewer chapters.
 | 
			
		||||
     */
 | 
			
		||||
    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
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
@@ -149,7 +151,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 { getChapterByMangaId.await(manga.id) }
 | 
			
		||||
 | 
			
		||||
        val selectedChapter = chapters.find { it.id == chapterId }
 | 
			
		||||
@@ -161,12 +163,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, manga.title, manga.source)) ||
 | 
			
		||||
                                (manga.downloadedFilterRaw == DomainManga.CHAPTER_SHOW_NOT_DOWNLOADED && downloadManager.isChapterDownloaded(it.name, it.scanlator, manga.title, 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, manga.title, manga.source)) ||
 | 
			
		||||
                                (manga.downloadedFilterRaw == Manga.CHAPTER_SHOW_NOT_DOWNLOADED && downloadManager.isChapterDownloaded(it.name, it.scanlator, manga.title, manga.source)) ||
 | 
			
		||||
                                (manga.bookmarkedFilterRaw == Manga.CHAPTER_SHOW_BOOKMARKED && !it.bookmark) ||
 | 
			
		||||
                                (manga.bookmarkedFilterRaw == Manga.CHAPTER_SHOW_NOT_BOOKMARKED && it.bookmark)
 | 
			
		||||
                        }
 | 
			
		||||
                        else -> false
 | 
			
		||||
                    }
 | 
			
		||||
@@ -188,32 +190,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)
 | 
			
		||||
@@ -223,24 +208,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()
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@@ -250,7 +235,7 @@ class ReaderPresenter(
 | 
			
		||||
     */
 | 
			
		||||
    fun onSaveInstanceStateNonConfigurationChange() {
 | 
			
		||||
        val currentChapter = getCurrentChapter() ?: return
 | 
			
		||||
        coroutineScope.launchNonCancellable {
 | 
			
		||||
        viewModelScope.launchNonCancellable {
 | 
			
		||||
            saveChapterProgress(currentChapter)
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
@@ -266,60 +251,35 @@ 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) {
 | 
			
		||||
        if (!needsInit()) return
 | 
			
		||||
 | 
			
		||||
        coroutineScope.launchIO {
 | 
			
		||||
    suspend fun init(mangaId: Long, initialChapterId: Long): Result<Boolean> {
 | 
			
		||||
        if (!needsInit()) return Result.success(true)
 | 
			
		||||
        return withIOContext {
 | 
			
		||||
            try {
 | 
			
		||||
                val manga = getManga.await(mangaId)
 | 
			
		||||
                withUIContext {
 | 
			
		||||
                    manga?.let { init(it.toDbManga(), initialChapterId) }
 | 
			
		||||
                if (manga != null) {
 | 
			
		||||
                    mutableState.update { it.copy(manga = manga) }
 | 
			
		||||
                    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)
 | 
			
		||||
 | 
			
		||||
                    getLoadObservable(loader!!, chapterList.first { chapterId == it.chapter.id })
 | 
			
		||||
                        .asFlow()
 | 
			
		||||
                        .first()
 | 
			
		||||
                    Result.success(true)
 | 
			
		||||
                } else {
 | 
			
		||||
                    // Unlikely but okay
 | 
			
		||||
                    Result.success(false)
 | 
			
		||||
                }
 | 
			
		||||
            } 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) {
 | 
			
		||||
        if (!needsInit()) return
 | 
			
		||||
 | 
			
		||||
        this.manga = manga
 | 
			
		||||
        if (chapterId == -1L) chapterId = initialChapterId
 | 
			
		||||
 | 
			
		||||
        checkTrackers(manga.toDomainManga()!!)
 | 
			
		||||
 | 
			
		||||
        val context = Injekt.get<Application>()
 | 
			
		||||
        val source = sourceManager.getOrStub(manga.source)
 | 
			
		||||
        loader = ChapterLoader(context, downloadManager, downloadProvider, manga.toDomainManga()!!, source)
 | 
			
		||||
 | 
			
		||||
        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) }
 | 
			
		||||
            .subscribeOn(Schedulers.io())
 | 
			
		||||
            .observeOn(AndroidSchedulers.mainThread())
 | 
			
		||||
            .subscribeFirst(
 | 
			
		||||
                { _, _ ->
 | 
			
		||||
                    // Ignore onNext event
 | 
			
		||||
                },
 | 
			
		||||
                ReaderActivity::setInitialChapterError,
 | 
			
		||||
            )
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Returns an observable that loads the given [chapter] with this [loader]. This observable
 | 
			
		||||
     * handles main thread synchronization and updating the currently active chapters on
 | 
			
		||||
@@ -345,14 +305,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)
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@@ -360,17 +320,17 @@ 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()
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
@@ -378,30 +338,25 @@ 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
 | 
			
		||||
@@ -424,13 +379,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()
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
@@ -439,7 +395,7 @@ class ReaderPresenter(
 | 
			
		||||
     * [page]'s chapter is different from the currently active.
 | 
			
		||||
     */
 | 
			
		||||
    fun onPageSelected(page: ReaderPage) {
 | 
			
		||||
        val currentChapters = viewerChaptersRelay.value ?: return
 | 
			
		||||
        val currentChapters = state.value.viewerChapters ?: return
 | 
			
		||||
 | 
			
		||||
        val selectedChapter = page.chapter
 | 
			
		||||
 | 
			
		||||
@@ -461,7 +417,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
 | 
			
		||||
@@ -477,9 +433,9 @@ 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,
 | 
			
		||||
@@ -488,10 +444,10 @@ class ReaderPresenter(
 | 
			
		||||
            )
 | 
			
		||||
            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,
 | 
			
		||||
            )
 | 
			
		||||
        }
 | 
			
		||||
@@ -535,7 +491,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)
 | 
			
		||||
        }
 | 
			
		||||
@@ -583,23 +539,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)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@@ -607,7 +563,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
 | 
			
		||||
@@ -625,7 +581,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(),
 | 
			
		||||
@@ -640,10 +596,10 @@ class ReaderPresenter(
 | 
			
		||||
     */
 | 
			
		||||
    fun getMangaReadingMode(resolveDefault: Boolean = true): Int {
 | 
			
		||||
        val default = readerPreferences.defaultReadingMode().get()
 | 
			
		||||
        val readingMode = ReadingModeType.fromPreference(manga?.readingModeType)
 | 
			
		||||
        val readingMode = ReadingModeType.fromPreference(manga?.readingModeType?.toInt())
 | 
			
		||||
        return when {
 | 
			
		||||
            resolveDefault && readingMode == ReadingModeType.DEFAULT -> default
 | 
			
		||||
            else -> manga?.readingModeType ?: default
 | 
			
		||||
            else -> manga?.readingModeType?.toInt() ?: default
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@@ -652,22 +608,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)
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
@@ -677,10 +632,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
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@@ -689,14 +644,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)
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
@@ -733,8 +696,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!!,
 | 
			
		||||
@@ -744,12 +707,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))
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@@ -770,7 +733,7 @@ class ReaderPresenter(
 | 
			
		||||
        val filename = generateFilename(manga, page)
 | 
			
		||||
 | 
			
		||||
        try {
 | 
			
		||||
            coroutineScope.launchNonCancellable {
 | 
			
		||||
            viewModelScope.launchNonCancellable {
 | 
			
		||||
                destDir.deleteRecursively()
 | 
			
		||||
                val uri = imageSaver.save(
 | 
			
		||||
                    image = Image.Page(
 | 
			
		||||
@@ -779,9 +742,7 @@ class ReaderPresenter(
 | 
			
		||||
                        location = Location.Cache,
 | 
			
		||||
                    ),
 | 
			
		||||
                )
 | 
			
		||||
                withUIContext {
 | 
			
		||||
                    view?.onShareImageResult(uri, page)
 | 
			
		||||
                }
 | 
			
		||||
                eventChannel.send(Event.ShareImage(uri, page))
 | 
			
		||||
            }
 | 
			
		||||
        } catch (e: Throwable) {
 | 
			
		||||
            logcat(LogPriority.ERROR, e)
 | 
			
		||||
@@ -793,24 +754,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))
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@@ -842,8 +800,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) {
 | 
			
		||||
@@ -882,8 +840,8 @@ class ReaderPresenter(
 | 
			
		||||
        if (!chapter.chapter.read) return
 | 
			
		||||
        val manga = manga ?: return
 | 
			
		||||
 | 
			
		||||
        coroutineScope.launchNonCancellable {
 | 
			
		||||
            downloadManager.enqueueChaptersToDelete(listOf(chapter.chapter.toDomainChapter()!!), manga.toDomainManga()!!)
 | 
			
		||||
        viewModelScope.launchNonCancellable {
 | 
			
		||||
            downloadManager.enqueueChaptersToDelete(listOf(chapter.chapter.toDomainChapter()!!), manga)
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@@ -892,35 +850,26 @@ 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,
 | 
			
		||||
    )
 | 
			
		||||
 | 
			
		||||
    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) : 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
 | 
			
		||||
@@ -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
 | 
			
		||||
 
 | 
			
		||||
@@ -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) }
 | 
			
		||||
    }
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user