Yet another attempt to make webtoon reader great again

This commit is contained in:
Koitharu
2022-07-29 15:20:54 +03:00
parent d42cd59880
commit d6781e1d14
10 changed files with 81 additions and 17 deletions

View File

@@ -7,6 +7,8 @@ import android.view.View
import androidx.core.view.isVisible import androidx.core.view.isVisible
import com.davemorrissey.labs.subscaleview.ImageSource import com.davemorrissey.labs.subscaleview.ImageSource
import com.davemorrissey.labs.subscaleview.SubsamplingScaleImageView import com.davemorrissey.labs.subscaleview.SubsamplingScaleImageView
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.asExecutor
import org.koitharu.kotatsu.R import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.core.exceptions.resolve.ExceptionResolver import org.koitharu.kotatsu.core.exceptions.resolve.ExceptionResolver
import org.koitharu.kotatsu.core.model.ZoomMode import org.koitharu.kotatsu.core.model.ZoomMode
@@ -26,6 +28,8 @@ open class PageHolder(
View.OnClickListener { View.OnClickListener {
init { init {
binding.ssiv.setExecutor(Dispatchers.Default.asExecutor())
binding.ssiv.setEagerLoadingEnabled(!isLowRamDevice(context))
binding.ssiv.setOnImageEventListener(delegate) binding.ssiv.setOnImageEventListener(delegate)
@Suppress("LeakingThis") @Suppress("LeakingThis")
bindingInfo.buttonRetry.setOnClickListener(this) bindingInfo.buttonRetry.setOnClickListener(this)

View File

@@ -3,13 +3,16 @@ package org.koitharu.kotatsu.reader.ui.pager.webtoon
import android.content.Context import android.content.Context
import android.util.AttributeSet import android.util.AttributeSet
import android.widget.FrameLayout import android.widget.FrameLayout
import androidx.annotation.AttrRes
import org.koitharu.kotatsu.R import org.koitharu.kotatsu.R
class WebtoonFrameLayout @JvmOverloads constructor( class WebtoonFrameLayout @JvmOverloads constructor(
context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0 context: Context,
attrs: AttributeSet? = null,
@AttrRes defStyleAttr: Int = 0,
) : FrameLayout(context, attrs, defStyleAttr) { ) : FrameLayout(context, attrs, defStyleAttr) {
private val target by lazy { private val target by lazy(LazyThreadSafetyMode.NONE) {
findViewById<WebtoonImageView>(R.id.ssiv) findViewById<WebtoonImageView>(R.id.ssiv)
} }

View File

@@ -13,14 +13,14 @@ import org.koitharu.kotatsu.databinding.ItemPageWebtoonBinding
import org.koitharu.kotatsu.reader.domain.PageLoader import org.koitharu.kotatsu.reader.domain.PageLoader
import org.koitharu.kotatsu.reader.ui.pager.BasePageHolder import org.koitharu.kotatsu.reader.ui.pager.BasePageHolder
import org.koitharu.kotatsu.reader.ui.pager.ReaderPage import org.koitharu.kotatsu.reader.ui.pager.ReaderPage
import org.koitharu.kotatsu.utils.GoneOnInvisibleListener
import org.koitharu.kotatsu.utils.ext.* import org.koitharu.kotatsu.utils.ext.*
class WebtoonHolder( class WebtoonHolder(
binding: ItemPageWebtoonBinding, binding: ItemPageWebtoonBinding,
loader: PageLoader, loader: PageLoader,
settings: AppSettings, settings: AppSettings,
exceptionResolver: ExceptionResolver exceptionResolver: ExceptionResolver,
) : BasePageHolder<ItemPageWebtoonBinding>(binding, loader, settings, exceptionResolver), ) : BasePageHolder<ItemPageWebtoonBinding>(binding, loader, settings, exceptionResolver),
View.OnClickListener { View.OnClickListener {
@@ -29,6 +29,7 @@ class WebtoonHolder(
init { init {
binding.ssiv.setOnImageEventListener(delegate) binding.ssiv.setOnImageEventListener(delegate)
bindingInfo.buttonRetry.setOnClickListener(this) bindingInfo.buttonRetry.setOnClickListener(this)
GoneOnInvisibleListener(bindingInfo.progressBar).attach()
} }
override fun onBind(data: ReaderPage) { override fun onBind(data: ReaderPage) {
@@ -61,9 +62,9 @@ class WebtoonHolder(
override fun onImageShowing(zoom: ZoomMode) { override fun onImageShowing(zoom: ZoomMode) {
with(binding.ssiv) { with(binding.ssiv) {
maxScale = 2f * width / sWidth.toFloat()
setMinimumScaleType(SubsamplingScaleImageView.SCALE_TYPE_CUSTOM) setMinimumScaleType(SubsamplingScaleImageView.SCALE_TYPE_CUSTOM)
minScale = width / sWidth.toFloat() minScale = width / sWidth.toFloat()
maxScale = minScale
scrollTo( scrollTo(
when { when {
scrollToRestore != 0 -> scrollToRestore scrollToRestore != 0 -> scrollToRestore

View File

@@ -1,11 +1,15 @@
package org.koitharu.kotatsu.reader.ui.pager.webtoon package org.koitharu.kotatsu.reader.ui.pager.webtoon
import android.app.Activity
import android.content.Context import android.content.Context
import android.graphics.PointF import android.graphics.PointF
import android.util.AttributeSet import android.util.AttributeSet
import androidx.recyclerview.widget.RecyclerView
import com.davemorrissey.labs.subscaleview.SubsamplingScaleImageView import com.davemorrissey.labs.subscaleview.SubsamplingScaleImageView
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.asExecutor
import org.koitharu.kotatsu.parsers.util.toIntUp import org.koitharu.kotatsu.parsers.util.toIntUp
import org.koitharu.kotatsu.utils.ext.isLowRamDevice
import org.koitharu.kotatsu.utils.ext.parents
private const val SCROLL_UNKNOWN = -1 private const val SCROLL_UNKNOWN = -1
@@ -15,15 +19,15 @@ class WebtoonImageView @JvmOverloads constructor(
) : SubsamplingScaleImageView(context, attr) { ) : SubsamplingScaleImageView(context, attr) {
private val ct = PointF() private val ct = PointF()
private val displayHeight = if (context is Activity) {
context.window.decorView.height
} else {
context.resources.displayMetrics.heightPixels
}
private var scrollPos = 0 private var scrollPos = 0
private var scrollRange = SCROLL_UNKNOWN private var scrollRange = SCROLL_UNKNOWN
init {
setExecutor(Dispatchers.Default.asExecutor())
setEagerLoadingEnabled(!isLowRamDevice(context))
}
fun scrollBy(delta: Int) { fun scrollBy(delta: Int) {
val maxScroll = getScrollRange() val maxScroll = getScrollRange()
if (maxScroll == 0) { if (maxScroll == 0) {
@@ -36,6 +40,7 @@ class WebtoonImageView @JvmOverloads constructor(
fun scrollTo(y: Int) { fun scrollTo(y: Int) {
val maxScroll = getScrollRange() val maxScroll = getScrollRange()
if (maxScroll == 0) { if (maxScroll == 0) {
resetScaleAndCenter()
return return
} }
scrollToInternal(y.coerceIn(0, maxScroll)) scrollToInternal(y.coerceIn(0, maxScroll))
@@ -58,8 +63,11 @@ class WebtoonImageView @JvmOverloads constructor(
override fun getSuggestedMinimumHeight(): Int { override fun getSuggestedMinimumHeight(): Int {
var desiredHeight = super.getSuggestedMinimumHeight() var desiredHeight = super.getSuggestedMinimumHeight()
if (sHeight == 0 && desiredHeight < displayHeight) { if (sHeight == 0) {
desiredHeight = displayHeight val parentHeight = parentHeight()
if (desiredHeight < parentHeight) {
desiredHeight = parentHeight
}
} }
return desiredHeight return desiredHeight
} }
@@ -84,7 +92,7 @@ class WebtoonImageView @JvmOverloads constructor(
} }
} }
width = width.coerceAtLeast(suggestedMinimumWidth) width = width.coerceAtLeast(suggestedMinimumWidth)
height = height.coerceIn(suggestedMinimumHeight, displayHeight) height = height.coerceIn(suggestedMinimumHeight, parentHeight())
setMeasuredDimension(width, height) setMeasuredDimension(width, height)
} }
@@ -101,4 +109,8 @@ class WebtoonImageView @JvmOverloads constructor(
val totalHeight = (sHeight * minScale).toIntUp() val totalHeight = (sHeight * minScale).toIntUp()
scrollRange = (totalHeight - height).coerceAtLeast(0) scrollRange = (totalHeight - height).coerceAtLeast(0)
} }
private fun parentHeight(): Int {
return parents.firstNotNullOfOrNull { it as? RecyclerView }?.height ?: 0
}
} }

View File

@@ -6,6 +6,7 @@ import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
import kotlin.math.sign import kotlin.math.sign
@Suppress("unused")
class WebtoonLayoutManager : LinearLayoutManager { class WebtoonLayoutManager : LinearLayoutManager {
private var scrollDirection: Int = 0 private var scrollDirection: Int = 0

View File

@@ -23,7 +23,7 @@ class WebtoonReaderFragment : BaseReader<FragmentReaderWebtoonBinding>() {
override fun onInflateView( override fun onInflateView(
inflater: LayoutInflater, inflater: LayoutInflater,
container: ViewGroup? container: ViewGroup?,
) = FragmentReaderWebtoonBinding.inflate(inflater, container, false) ) = FragmentReaderWebtoonBinding.inflate(inflater, container, false)
override fun onViewCreated(view: View, savedInstanceState: Bundle?) { override fun onViewCreated(view: View, savedInstanceState: Bundle?) {

View File

@@ -3,6 +3,7 @@ package org.koitharu.kotatsu.reader.ui.thumbnails.adapter
import android.graphics.drawable.Drawable import android.graphics.drawable.Drawable
import coil.ImageLoader import coil.ImageLoader
import coil.request.ImageRequest import coil.request.ImageRequest
import coil.size.Scale
import coil.size.Size import coil.size.Size
import com.google.android.material.R as materialR import com.google.android.material.R as materialR
import com.hannesdorfmann.adapterdelegates4.dsl.adapterDelegateViewBinding import com.hannesdorfmann.adapterdelegates4.dsl.adapterDelegateViewBinding
@@ -24,7 +25,6 @@ fun pageThumbnailAD(
) = adapterDelegateViewBinding<PageThumbnail, PageThumbnail, ItemPageThumbBinding>( ) = adapterDelegateViewBinding<PageThumbnail, PageThumbnail, ItemPageThumbBinding>(
{ inflater, parent -> ItemPageThumbBinding.inflate(inflater, parent, false) } { inflater, parent -> ItemPageThumbBinding.inflate(inflater, parent, false) }
) { ) {
var job: Job? = null var job: Job? = null
val gridWidth = itemView.context.resources.getDimensionPixelSize(R.dimen.preferred_grid_width) val gridWidth = itemView.context.resources.getDimensionPixelSize(R.dimen.preferred_grid_width)
val thumbSize = Size( val thumbSize = Size(
@@ -39,6 +39,7 @@ fun pageThumbnailAD(
.data(url) .data(url)
.referer(item.page.referer) .referer(item.page.referer)
.size(thumbSize) .size(thumbSize)
.scale(Scale.FILL)
.allowRgb565(true) .allowRgb565(true)
.build() .build()
).drawable ).drawable

View File

@@ -0,0 +1,23 @@
package org.koitharu.kotatsu.utils
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)
}
}

View File

@@ -1,6 +1,8 @@
package org.koitharu.kotatsu.utils.ext package org.koitharu.kotatsu.utils.ext
import android.app.ActivityManager
import android.content.Context import android.content.Context
import android.content.Context.ACTIVITY_SERVICE
import android.content.SharedPreferences import android.content.SharedPreferences
import android.content.pm.ResolveInfo import android.content.pm.ResolveInfo
import android.net.ConnectivityManager import android.net.ConnectivityManager
@@ -27,6 +29,9 @@ import kotlinx.coroutines.suspendCancellableCoroutine
val Context.connectivityManager: ConnectivityManager val Context.connectivityManager: ConnectivityManager
get() = getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager get() = getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager
val Context.activityManager: ActivityManager?
get() = getSystemService(ACTIVITY_SERVICE) as? ActivityManager
suspend fun ConnectivityManager.waitForNetwork(): Network { suspend fun ConnectivityManager.waitForNetwork(): Network {
val request = NetworkRequest.Builder().build() val request = NetworkRequest.Builder().build()
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
@@ -92,4 +97,8 @@ fun Lifecycle.postDelayed(runnable: Runnable, delay: Long) {
delay(delay) delay(delay)
runnable.run() runnable.run()
} }
}
fun isLowRamDevice(context: Context): Boolean {
return context.activityManager?.isLowRamDevice ?: false
} }

View File

@@ -4,6 +4,7 @@ import android.app.Activity
import android.graphics.Rect import android.graphics.Rect
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import android.view.ViewParent
import android.view.inputmethod.InputMethodManager import android.view.inputmethod.InputMethodManager
import androidx.core.view.children import androidx.core.view.children
import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.LinearLayoutManager
@@ -138,4 +139,13 @@ fun <T : View> ViewGroup.findViewsByType(clazz: Class<T>): Sequence<T> {
} }
} }
} }
} }
val View.parents: Sequence<ViewParent>
get() = sequence {
var p: ViewParent? = parent
while (p != null) {
yield(p)
p = p.parent
}
}