From fe5d37f45e1fd89ddd2c974acd4263c4d45d6585 Mon Sep 17 00:00:00 2001 From: Koitharu Date: Mon, 7 Apr 2025 08:05:27 +0300 Subject: [PATCH] Fix hiding page loading indicator (close #1357) --- .../core/util/GoneOnInvisibleListener.kt | 28 ------------ .../koitharu/kotatsu/core/util/ext/View.kt | 5 +-- .../kotatsu/reader/ui/pager/BasePageHolder.kt | 26 ++++++++--- .../reader/ui/pager/standard/PageHolder.kt | 12 +----- .../reader/ui/pager/vm/PageViewModel.kt | 43 +++++++------------ .../reader/ui/pager/webtoon/WebtoonHolder.kt | 26 ++--------- 6 files changed, 42 insertions(+), 98 deletions(-) delete mode 100644 app/src/main/kotlin/org/koitharu/kotatsu/core/util/GoneOnInvisibleListener.kt diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/core/util/GoneOnInvisibleListener.kt b/app/src/main/kotlin/org/koitharu/kotatsu/core/util/GoneOnInvisibleListener.kt deleted file mode 100644 index 25ab3717f..000000000 --- a/app/src/main/kotlin/org/koitharu/kotatsu/core/util/GoneOnInvisibleListener.kt +++ /dev/null @@ -1,28 +0,0 @@ -package org.koitharu.kotatsu.core.util - -import android.view.View -import android.view.ViewTreeObserver - -/** - * ProgressIndicator become INVISIBLE instead of GONE by hide() call. - * It`s final so we need this workaround - */ -class GoneOnInvisibleListener( - private val view: View, -) : ViewTreeObserver.OnGlobalLayoutListener { - - override fun onGlobalLayout() { - if (view.visibility == View.INVISIBLE) { - view.visibility = View.GONE - } - } - - fun attach() { - view.viewTreeObserver.addOnGlobalLayoutListener(this) - onGlobalLayout() - } - - fun detach() { - view.viewTreeObserver.removeOnGlobalLayoutListener(this) - } -} diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/core/util/ext/View.kt b/app/src/main/kotlin/org/koitharu/kotatsu/core/util/ext/View.kt index da05edc0f..c325cd8b4 100644 --- a/app/src/main/kotlin/org/koitharu/kotatsu/core/util/ext/View.kt +++ b/app/src/main/kotlin/org/koitharu/kotatsu/core/util/ext/View.kt @@ -10,7 +10,6 @@ import androidx.appcompat.widget.ActionMenuView import androidx.appcompat.widget.Toolbar import androidx.core.view.children import androidx.core.view.descendants -import androidx.core.view.isVisible import androidx.fragment.app.Fragment import androidx.fragment.app.FragmentManager import androidx.recyclerview.widget.RecyclerView @@ -155,9 +154,9 @@ fun TabLayout.setTabsEnabled(enabled: Boolean) { fun BaseProgressIndicator<*>.showOrHide(value: Boolean) { if (value) { - if (!isVisible) show() + show() } else { - if (isVisible) hide() + hide() } } diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/reader/ui/pager/BasePageHolder.kt b/app/src/main/kotlin/org/koitharu/kotatsu/reader/ui/pager/BasePageHolder.kt index 4ec5e3de9..63ef98914 100644 --- a/app/src/main/kotlin/org/koitharu/kotatsu/reader/ui/pager/BasePageHolder.kt +++ b/app/src/main/kotlin/org/koitharu/kotatsu/reader/ui/pager/BasePageHolder.kt @@ -1,6 +1,9 @@ package org.koitharu.kotatsu.reader.ui.pager +import android.content.ComponentCallbacks2 +import android.content.ComponentCallbacks2.TRIM_MEMORY_COMPLETE import android.content.Context +import android.content.res.Configuration import android.view.View import androidx.annotation.CallSuper import androidx.core.view.isGone @@ -21,7 +24,6 @@ import org.koitharu.kotatsu.core.util.ext.getDisplayMessage import org.koitharu.kotatsu.core.util.ext.isLowRamDevice import org.koitharu.kotatsu.core.util.ext.isSerializable import org.koitharu.kotatsu.core.util.ext.observe -import org.koitharu.kotatsu.core.util.ext.showOrHide import org.koitharu.kotatsu.databinding.LayoutPageInfoBinding import org.koitharu.kotatsu.parsers.util.ifZero import org.koitharu.kotatsu.reader.domain.PageLoader @@ -37,7 +39,7 @@ abstract class BasePageHolder( networkState: NetworkState, exceptionResolver: ExceptionResolver, lifecycleOwner: LifecycleOwner, -) : LifecycleAwareViewHolder(binding.root, lifecycleOwner), DefaultOnImageEventListener { +) : LifecycleAwareViewHolder(binding.root, lifecycleOwner), DefaultOnImageEventListener, ComponentCallbacks2 { protected val viewModel = PageViewModel( loader = loader, @@ -82,9 +84,12 @@ abstract class BasePageHolder( @CallSuper protected open fun onConfigChanged(settings: ReaderSettings) { settings.applyBackground(itemView) - if (viewModel.state.value is PageState.Shown) { + if (settings.applyBitmapConfig(ssiv)) { + reloadImage() + } else if (viewModel.state.value is PageState.Shown) { onReady() } + ssiv.applyDownSampling(isResumed()) } fun reloadImage() { @@ -103,7 +108,7 @@ abstract class BasePageHolder( override fun onCreate() { super.onCreate() - context.registerComponentCallbacks(viewModel) + context.registerComponentCallbacks(this) viewModel.state.observe(this, ::onStateChanged) viewModel.settingsProducer.observe(this, ::onConfigChanged) } @@ -122,7 +127,7 @@ abstract class BasePageHolder( } override fun onDestroy() { - context.unregisterComponentCallbacks(viewModel) + context.unregisterComponentCallbacks(this) super.onDestroy() } @@ -136,9 +141,18 @@ abstract class BasePageHolder( ssiv.recycle() } + override fun onTrimMemory(level: Int) { + // TODO + } + + override fun onConfigurationChanged(newConfig: Configuration) = Unit + + @Deprecated("Deprecated in Java") + final override fun onLowMemory() = onTrimMemory(TRIM_MEMORY_COMPLETE) + protected open fun onStateChanged(state: PageState) { bindingInfo.layoutError.isVisible = state is PageState.Error - bindingInfo.progressBar.showOrHide(!state.isFinalState()) + bindingInfo.progressBar.isGone = state.isFinalState() bindingInfo.textViewStatus.isGone = state.isFinalState() val progress = (state as? PageState.Loading)?.progress ?: -1 if (progress in 0..100) { diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/reader/ui/pager/standard/PageHolder.kt b/app/src/main/kotlin/org/koitharu/kotatsu/reader/ui/pager/standard/PageHolder.kt index 4c96cf3fa..3f3e2bccd 100644 --- a/app/src/main/kotlin/org/koitharu/kotatsu/reader/ui/pager/standard/PageHolder.kt +++ b/app/src/main/kotlin/org/koitharu/kotatsu/reader/ui/pager/standard/PageHolder.kt @@ -30,17 +30,12 @@ open class PageHolder( networkState = networkState, exceptionResolver = exceptionResolver, lifecycleOwner = owner, -), - ZoomControl.ZoomControlListener { +), ZoomControl.ZoomControlListener { override val ssiv = binding.ssiv override fun onConfigChanged(settings: ReaderSettings) { super.onConfigChanged(settings) - if (settings.applyBitmapConfig(binding.ssiv)) { - reloadImage() - } - binding.ssiv.applyDownSampling(isResumed()) binding.textViewNumber.isVisible = settings.isPagesNumbersEnabled } @@ -50,11 +45,6 @@ open class PageHolder( binding.textViewNumber.text = (data.index + 1).toString() } - override fun onRecycled() { - super.onRecycled() - binding.ssiv.recycle() - } - override fun onReady() { binding.ssiv.maxScale = 2f * maxOf( binding.ssiv.width / binding.ssiv.sWidth.toFloat(), diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/reader/ui/pager/vm/PageViewModel.kt b/app/src/main/kotlin/org/koitharu/kotatsu/reader/ui/pager/vm/PageViewModel.kt index 50388a9e2..2ff9cb1b3 100644 --- a/app/src/main/kotlin/org/koitharu/kotatsu/reader/ui/pager/vm/PageViewModel.kt +++ b/app/src/main/kotlin/org/koitharu/kotatsu/reader/ui/pager/vm/PageViewModel.kt @@ -1,7 +1,5 @@ package org.koitharu.kotatsu.reader.ui.pager.vm -import android.content.ComponentCallbacks2 -import android.content.res.Configuration import android.graphics.Rect import android.net.Uri import androidx.annotation.WorkerThread @@ -20,6 +18,7 @@ import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.flow.update import kotlinx.coroutines.launch import kotlinx.coroutines.plus +import kotlinx.coroutines.withContext import okio.IOException import org.koitharu.kotatsu.core.exceptions.resolve.ExceptionResolver import org.koitharu.kotatsu.core.os.NetworkState @@ -35,7 +34,7 @@ class PageViewModel( private val networkState: NetworkState, private val exceptionResolver: ExceptionResolver, private val isWebtoon: Boolean, -) : DefaultOnImageEventListener, ComponentCallbacks2 { +) : DefaultOnImageEventListener { private val scope = loader.loaderScope + Dispatchers.Main.immediate private var job: Job? = null @@ -43,12 +42,6 @@ class PageViewModel( val state = MutableStateFlow(PageState.Empty) - init { - scope.launch(Dispatchers.Main) { // the same as post() -- wait until child fields init - // callback.onConfigChanged() - } - } - fun isLoading() = job?.isActive == true fun onBind(page: MangaPage) { @@ -64,13 +57,14 @@ class PageViewModel( job = scope.launch { prevJob?.cancelAndJoin() val e = (state.value as? PageState.Error)?.error - if (e != null && ExceptionResolver.Companion.canResolve(e)) { - if (!isFromUser) { - return@launch + if (e != null && ExceptionResolver.canResolve(e)) { + if (isFromUser) { + exceptionResolver.resolve(e) } - exceptionResolver.resolve(e) } - doLoad(page, force = true) + withContext(Dispatchers.Default) { + doLoad(page, force = true) + } } } @@ -86,8 +80,12 @@ class PageViewModel( } override fun onImageLoaded() { - state.update { - if (it is PageState.Loaded) PageState.Shown(it.source, it.isConverted) else it + state.update { currentState -> + if (currentState is PageState.Loaded) { + PageState.Shown(currentState.source, currentState.isConverted) + } else { + currentState + } } } @@ -109,18 +107,9 @@ class PageViewModel( } } - override fun onConfigurationChanged(newConfig: Configuration) = Unit - - @Suppress("OVERRIDE_DEPRECATION") - override fun onLowMemory() = Unit - - override fun onTrimMemory(level: Int) { - // callback.onTrimMemory() - } - private fun tryConvert(uri: Uri, e: Exception) { val prevJob = job - job = scope.launch { + job = scope.launch(Dispatchers.Default) { prevJob?.join() state.value = PageState.Converting() try { @@ -143,7 +132,7 @@ class PageViewModel( @WorkerThread private suspend fun doLoad(data: MangaPage, force: Boolean) = coroutineScope { - state.value = PageState.Loading(null/* TODO */, -1) + state.value = PageState.Loading(null, -1) val previewJob = launch { val preview = loader.loadPreview(data) ?: return@launch state.update { diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/reader/ui/pager/webtoon/WebtoonHolder.kt b/app/src/main/kotlin/org/koitharu/kotatsu/reader/ui/pager/webtoon/WebtoonHolder.kt index 600196617..78fb958e2 100644 --- a/app/src/main/kotlin/org/koitharu/kotatsu/reader/ui/pager/webtoon/WebtoonHolder.kt +++ b/app/src/main/kotlin/org/koitharu/kotatsu/reader/ui/pager/webtoon/WebtoonHolder.kt @@ -1,9 +1,9 @@ package org.koitharu.kotatsu.reader.ui.pager.webtoon +import android.view.View import androidx.lifecycle.LifecycleOwner import org.koitharu.kotatsu.core.exceptions.resolve.ExceptionResolver import org.koitharu.kotatsu.core.os.NetworkState -import org.koitharu.kotatsu.core.util.GoneOnInvisibleListener import org.koitharu.kotatsu.databinding.ItemPageWebtoonBinding import org.koitharu.kotatsu.reader.domain.PageLoader import org.koitharu.kotatsu.reader.ui.config.ReaderSettings @@ -28,29 +28,9 @@ class WebtoonHolder( override val ssiv = binding.ssiv private var scrollToRestore = 0 - private val goneOnInvisibleListener = GoneOnInvisibleListener(bindingInfo.progressBar) - override fun onConfigChanged(settings: ReaderSettings) { - super.onConfigChanged(settings) - if (settings.applyBitmapConfig(binding.ssiv)) { - reloadImage() - } - binding.ssiv.applyDownSampling(isResumed()) - } - - override fun onRecycled() { - super.onRecycled() - binding.ssiv.recycle() - } - - override fun onAttachedToWindow() { - super.onAttachedToWindow() - goneOnInvisibleListener.attach() - } - - override fun onDetachedFromWindow() { - super.onDetachedFromWindow() - goneOnInvisibleListener.detach() + init { + bindingInfo.progressBar.setVisibilityAfterHide(View.GONE) } override fun onReady() {