Add lifecycle for BasePageHolder
This commit is contained in:
@@ -134,7 +134,7 @@ dependencies {
|
|||||||
|
|
||||||
implementation 'io.coil-kt:coil-base:2.5.0'
|
implementation 'io.coil-kt:coil-base:2.5.0'
|
||||||
implementation 'io.coil-kt:coil-svg:2.5.0'
|
implementation 'io.coil-kt:coil-svg:2.5.0'
|
||||||
implementation 'com.github.KotatsuApp:subsampling-scale-image-view:cf089a264d'
|
implementation 'com.github.KotatsuApp:subsampling-scale-image-view:2bcc9791b1'
|
||||||
implementation 'com.github.solkin:disk-lru-cache:1.4'
|
implementation 'com.github.solkin:disk-lru-cache:1.4'
|
||||||
implementation 'io.noties.markwon:core:4.6.2'
|
implementation 'io.noties.markwon:core:4.6.2'
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,88 @@
|
|||||||
|
package org.koitharu.kotatsu.core.ui.list.lifecycle
|
||||||
|
|
||||||
|
import android.view.View
|
||||||
|
import androidx.annotation.CallSuper
|
||||||
|
import androidx.lifecycle.DefaultLifecycleObserver
|
||||||
|
import androidx.lifecycle.Lifecycle
|
||||||
|
import androidx.lifecycle.LifecycleOwner
|
||||||
|
import androidx.lifecycle.LifecycleRegistry
|
||||||
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
|
|
||||||
|
abstract class LifecycleAwareViewHolder(
|
||||||
|
itemView: View,
|
||||||
|
private val parentLifecycleOwner: LifecycleOwner,
|
||||||
|
) : RecyclerView.ViewHolder(itemView), LifecycleOwner {
|
||||||
|
|
||||||
|
@Suppress("LeakingThis")
|
||||||
|
final override val lifecycle = LifecycleRegistry(this)
|
||||||
|
private var isCurrent = false
|
||||||
|
|
||||||
|
init {
|
||||||
|
parentLifecycleOwner.lifecycle.addObserver(ParentLifecycleObserver())
|
||||||
|
if (parentLifecycleOwner.lifecycle.currentState.isAtLeast(Lifecycle.State.CREATED)) {
|
||||||
|
lifecycle.handleLifecycleEvent(Lifecycle.Event.ON_CREATE)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun setIsCurrent(value: Boolean) {
|
||||||
|
isCurrent = value
|
||||||
|
dispatchResumed()
|
||||||
|
}
|
||||||
|
|
||||||
|
@CallSuper
|
||||||
|
open fun onStart() = lifecycle.handleLifecycleEvent(Lifecycle.Event.ON_START)
|
||||||
|
|
||||||
|
@CallSuper
|
||||||
|
open fun onResume() = lifecycle.handleLifecycleEvent(Lifecycle.Event.ON_RESUME)
|
||||||
|
|
||||||
|
@CallSuper
|
||||||
|
open fun onPause() = lifecycle.handleLifecycleEvent(Lifecycle.Event.ON_PAUSE)
|
||||||
|
|
||||||
|
@CallSuper
|
||||||
|
open fun onStop() = lifecycle.handleLifecycleEvent(Lifecycle.Event.ON_STOP)
|
||||||
|
|
||||||
|
private fun dispatchResumed() {
|
||||||
|
val isParentResumed = parentLifecycleOwner.lifecycle.currentState.isAtLeast(Lifecycle.State.RESUMED)
|
||||||
|
if (isCurrent && isParentResumed) {
|
||||||
|
if (!isResumed()) {
|
||||||
|
onResume()
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (isResumed()) {
|
||||||
|
onPause()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected fun isResumed(): Boolean {
|
||||||
|
return lifecycle.currentState.isAtLeast(Lifecycle.State.RESUMED)
|
||||||
|
}
|
||||||
|
|
||||||
|
private inner class ParentLifecycleObserver : DefaultLifecycleObserver {
|
||||||
|
|
||||||
|
override fun onCreate(owner: LifecycleOwner) {
|
||||||
|
lifecycle.handleLifecycleEvent(Lifecycle.Event.ON_CREATE)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onStart(owner: LifecycleOwner) {
|
||||||
|
onStart()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onResume(owner: LifecycleOwner) {
|
||||||
|
dispatchResumed()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onPause(owner: LifecycleOwner) {
|
||||||
|
dispatchResumed()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onStop(owner: LifecycleOwner) {
|
||||||
|
onStop()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onDestroy(owner: LifecycleOwner) {
|
||||||
|
lifecycle.handleLifecycleEvent(Lifecycle.Event.ON_DESTROY)
|
||||||
|
owner.lifecycle.removeObserver(this)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
package org.koitharu.kotatsu.core.ui.list.lifecycle
|
||||||
|
|
||||||
|
import androidx.core.view.children
|
||||||
|
import androidx.viewpager2.widget.ViewPager2
|
||||||
|
import org.koitharu.kotatsu.core.util.ext.recyclerView
|
||||||
|
|
||||||
|
class PagerLifecycleDispatcher(
|
||||||
|
private val pager: ViewPager2,
|
||||||
|
) : ViewPager2.OnPageChangeCallback() {
|
||||||
|
|
||||||
|
override fun onPageSelected(position: Int) {
|
||||||
|
super.onPageSelected(position)
|
||||||
|
val rv = pager.recyclerView ?: return
|
||||||
|
for (child in rv.children) {
|
||||||
|
val wh = rv.getChildViewHolder(child) ?: continue
|
||||||
|
(wh as? LifecycleAwareViewHolder)?.setIsCurrent(wh.absoluteAdapterPosition == position)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -2,13 +2,15 @@ package org.koitharu.kotatsu.reader.ui.pager
|
|||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import androidx.annotation.CallSuper
|
import androidx.annotation.CallSuper
|
||||||
import androidx.recyclerview.widget.RecyclerView
|
import androidx.lifecycle.LifecycleOwner
|
||||||
import androidx.viewbinding.ViewBinding
|
import androidx.viewbinding.ViewBinding
|
||||||
import org.koitharu.kotatsu.core.exceptions.resolve.ExceptionResolver
|
import org.koitharu.kotatsu.core.exceptions.resolve.ExceptionResolver
|
||||||
import org.koitharu.kotatsu.core.os.NetworkState
|
import org.koitharu.kotatsu.core.os.NetworkState
|
||||||
|
import org.koitharu.kotatsu.core.ui.list.lifecycle.LifecycleAwareViewHolder
|
||||||
import org.koitharu.kotatsu.databinding.LayoutPageInfoBinding
|
import org.koitharu.kotatsu.databinding.LayoutPageInfoBinding
|
||||||
import org.koitharu.kotatsu.reader.domain.PageLoader
|
import org.koitharu.kotatsu.reader.domain.PageLoader
|
||||||
import org.koitharu.kotatsu.reader.ui.config.ReaderSettings
|
import org.koitharu.kotatsu.reader.ui.config.ReaderSettings
|
||||||
|
import org.koitharu.kotatsu.reader.ui.pager.PageHolderDelegate.State
|
||||||
|
|
||||||
abstract class BasePageHolder<B : ViewBinding>(
|
abstract class BasePageHolder<B : ViewBinding>(
|
||||||
protected val binding: B,
|
protected val binding: B,
|
||||||
@@ -16,7 +18,8 @@ abstract class BasePageHolder<B : ViewBinding>(
|
|||||||
protected val settings: ReaderSettings,
|
protected val settings: ReaderSettings,
|
||||||
networkState: NetworkState,
|
networkState: NetworkState,
|
||||||
exceptionResolver: ExceptionResolver,
|
exceptionResolver: ExceptionResolver,
|
||||||
) : RecyclerView.ViewHolder(binding.root), PageHolderDelegate.Callback {
|
lifecycleOwner: LifecycleOwner,
|
||||||
|
) : LifecycleAwareViewHolder(binding.root, lifecycleOwner), PageHolderDelegate.Callback {
|
||||||
|
|
||||||
@Suppress("LeakingThis")
|
@Suppress("LeakingThis")
|
||||||
protected val delegate = PageHolderDelegate(loader, settings, this, networkState, exceptionResolver)
|
protected val delegate = PageHolderDelegate(loader, settings, this, networkState, exceptionResolver)
|
||||||
@@ -43,6 +46,13 @@ abstract class BasePageHolder<B : ViewBinding>(
|
|||||||
|
|
||||||
protected abstract fun onBind(data: ReaderPage)
|
protected abstract fun onBind(data: ReaderPage)
|
||||||
|
|
||||||
|
override fun onResume() {
|
||||||
|
super.onResume()
|
||||||
|
if (delegate.state == State.ERROR && !delegate.isLoading()) {
|
||||||
|
boundData?.let { delegate.retry(it.toMangaPage(), isFromUser = false) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@CallSuper
|
@CallSuper
|
||||||
open fun onAttachedToWindow() {
|
open fun onAttachedToWindow() {
|
||||||
delegate.onAttachedToWindow()
|
delegate.onAttachedToWindow()
|
||||||
|
|||||||
@@ -35,7 +35,8 @@ class PageHolderDelegate(
|
|||||||
) : DefaultOnImageEventListener, Observer<ReaderSettings> {
|
) : DefaultOnImageEventListener, Observer<ReaderSettings> {
|
||||||
|
|
||||||
private val scope = loader.loaderScope + Dispatchers.Main.immediate
|
private val scope = loader.loaderScope + Dispatchers.Main.immediate
|
||||||
private var state = State.EMPTY
|
var state = State.EMPTY
|
||||||
|
private set
|
||||||
private var job: Job? = null
|
private var job: Job? = null
|
||||||
private var file: File? = null
|
private var file: File? = null
|
||||||
private var error: Throwable? = null
|
private var error: Throwable? = null
|
||||||
@@ -44,6 +45,8 @@ class PageHolderDelegate(
|
|||||||
callback.onConfigChanged()
|
callback.onConfigChanged()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun isLoading() = job?.isActive == true
|
||||||
|
|
||||||
fun onBind(page: MangaPage) {
|
fun onBind(page: MangaPage) {
|
||||||
val prevJob = job
|
val prevJob = job
|
||||||
job = scope.launch {
|
job = scope.launch {
|
||||||
@@ -52,12 +55,15 @@ class PageHolderDelegate(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun retry(page: MangaPage) {
|
fun retry(page: MangaPage, isFromUser: Boolean) {
|
||||||
val prevJob = job
|
val prevJob = job
|
||||||
job = scope.launch {
|
job = scope.launch {
|
||||||
prevJob?.cancelAndJoin()
|
prevJob?.cancelAndJoin()
|
||||||
val e = error
|
val e = error
|
||||||
if (e != null && ExceptionResolver.canResolve(e)) {
|
if (e != null && ExceptionResolver.canResolve(e)) {
|
||||||
|
if (!isFromUser) {
|
||||||
|
return@launch
|
||||||
|
}
|
||||||
exceptionResolver.resolve(e)
|
exceptionResolver.resolve(e)
|
||||||
}
|
}
|
||||||
doLoad(page, force = true)
|
doLoad(page, force = true)
|
||||||
@@ -85,7 +91,7 @@ class PageHolderDelegate(
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun reload() {
|
fun reload() {
|
||||||
if (state == State.SHOWN ) {
|
if (state == State.SHOWN) {
|
||||||
file?.let {
|
file?.let {
|
||||||
callback.onImageReady(it.toUri())
|
callback.onImageReady(it.toUri())
|
||||||
}
|
}
|
||||||
@@ -166,7 +172,7 @@ class PageHolderDelegate(
|
|||||||
callback.onError(e)
|
callback.onError(e)
|
||||||
if (e is IOException && !networkState.value) {
|
if (e is IOException && !networkState.value) {
|
||||||
networkState.awaitForConnection()
|
networkState.awaitForConnection()
|
||||||
retry(data)
|
retry(data, isFromUser = false)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -176,7 +182,7 @@ class PageHolderDelegate(
|
|||||||
.onEach { callback.onProgressChanged((100 * it).toInt()) }
|
.onEach { callback.onProgressChanged((100 * it).toInt()) }
|
||||||
.launchIn(scope)
|
.launchIn(scope)
|
||||||
|
|
||||||
private enum class State {
|
enum class State {
|
||||||
EMPTY, LOADING, LOADED, CONVERTING, CONVERTED, SHOWING, SHOWN, ERROR
|
EMPTY, LOADING, LOADED, CONVERTING, CONVERTED, SHOWING, SHOWN, ERROR
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -30,7 +30,7 @@ open class PageHolder(
|
|||||||
settings: ReaderSettings,
|
settings: ReaderSettings,
|
||||||
networkState: NetworkState,
|
networkState: NetworkState,
|
||||||
exceptionResolver: ExceptionResolver,
|
exceptionResolver: ExceptionResolver,
|
||||||
) : BasePageHolder<ItemPageBinding>(binding, loader, settings, networkState, exceptionResolver),
|
) : BasePageHolder<ItemPageBinding>(binding, loader, settings, networkState, exceptionResolver, owner),
|
||||||
View.OnClickListener,
|
View.OnClickListener,
|
||||||
ZoomControl.ZoomControlListener {
|
ZoomControl.ZoomControlListener {
|
||||||
|
|
||||||
@@ -129,7 +129,7 @@ open class PageHolder(
|
|||||||
|
|
||||||
final override fun onClick(v: View) {
|
final override fun onClick(v: View) {
|
||||||
when (v.id) {
|
when (v.id) {
|
||||||
R.id.button_retry -> delegate.retry(boundData?.toMangaPage() ?: return)
|
R.id.button_retry -> delegate.retry(boundData?.toMangaPage() ?: return, isFromUser = true)
|
||||||
R.id.button_error_details -> delegate.showErrorDetails(boundData?.url)
|
R.id.button_error_details -> delegate.showErrorDetails(boundData?.url)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ import kotlinx.coroutines.yield
|
|||||||
import org.koitharu.kotatsu.R
|
import org.koitharu.kotatsu.R
|
||||||
import org.koitharu.kotatsu.core.os.NetworkState
|
import org.koitharu.kotatsu.core.os.NetworkState
|
||||||
import org.koitharu.kotatsu.core.prefs.ReaderAnimation
|
import org.koitharu.kotatsu.core.prefs.ReaderAnimation
|
||||||
|
import org.koitharu.kotatsu.core.ui.list.lifecycle.PagerLifecycleDispatcher
|
||||||
import org.koitharu.kotatsu.core.util.ext.doOnPageChanged
|
import org.koitharu.kotatsu.core.util.ext.doOnPageChanged
|
||||||
import org.koitharu.kotatsu.core.util.ext.findCurrentViewHolder
|
import org.koitharu.kotatsu.core.util.ext.findCurrentViewHolder
|
||||||
import org.koitharu.kotatsu.core.util.ext.observe
|
import org.koitharu.kotatsu.core.util.ext.observe
|
||||||
@@ -61,6 +62,7 @@ class PagerReaderFragment : BaseReaderFragment<FragmentReaderStandardBinding>(),
|
|||||||
recyclerView?.defaultFocusHighlightEnabled = false
|
recyclerView?.defaultFocusHighlightEnabled = false
|
||||||
}
|
}
|
||||||
PagerEventSupplier(this).attach()
|
PagerEventSupplier(this).attach()
|
||||||
|
registerOnPageChangeCallback(PagerLifecycleDispatcher(this))
|
||||||
}
|
}
|
||||||
|
|
||||||
viewModel.pageAnimation.observe(viewLifecycleOwner) {
|
viewModel.pageAnimation.observe(viewLifecycleOwner) {
|
||||||
|
|||||||
@@ -25,7 +25,7 @@ class WebtoonHolder(
|
|||||||
settings: ReaderSettings,
|
settings: ReaderSettings,
|
||||||
networkState: NetworkState,
|
networkState: NetworkState,
|
||||||
exceptionResolver: ExceptionResolver,
|
exceptionResolver: ExceptionResolver,
|
||||||
) : BasePageHolder<ItemPageWebtoonBinding>(binding, loader, settings, networkState, exceptionResolver),
|
) : BasePageHolder<ItemPageWebtoonBinding>(binding, loader, settings, networkState, exceptionResolver, owner),
|
||||||
View.OnClickListener {
|
View.OnClickListener {
|
||||||
|
|
||||||
private var scrollToRestore = 0
|
private var scrollToRestore = 0
|
||||||
@@ -107,7 +107,7 @@ class WebtoonHolder(
|
|||||||
|
|
||||||
override fun onClick(v: View) {
|
override fun onClick(v: View) {
|
||||||
when (v.id) {
|
when (v.id) {
|
||||||
R.id.button_retry -> delegate.retry(boundData?.toMangaPage() ?: return)
|
R.id.button_retry -> delegate.retry(boundData?.toMangaPage() ?: return, isFromUser = true)
|
||||||
R.id.button_error_details -> delegate.showErrorDetails(boundData?.url)
|
R.id.button_error_details -> delegate.showErrorDetails(boundData?.url)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user