Yet another attempt to make webtoon reader great again
This commit is contained in:
@@ -7,6 +7,8 @@ import android.view.View
|
||||
import androidx.core.view.isVisible
|
||||
import com.davemorrissey.labs.subscaleview.ImageSource
|
||||
import com.davemorrissey.labs.subscaleview.SubsamplingScaleImageView
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.asExecutor
|
||||
import org.koitharu.kotatsu.R
|
||||
import org.koitharu.kotatsu.core.exceptions.resolve.ExceptionResolver
|
||||
import org.koitharu.kotatsu.core.model.ZoomMode
|
||||
@@ -26,6 +28,8 @@ open class PageHolder(
|
||||
View.OnClickListener {
|
||||
|
||||
init {
|
||||
binding.ssiv.setExecutor(Dispatchers.Default.asExecutor())
|
||||
binding.ssiv.setEagerLoadingEnabled(!isLowRamDevice(context))
|
||||
binding.ssiv.setOnImageEventListener(delegate)
|
||||
@Suppress("LeakingThis")
|
||||
bindingInfo.buttonRetry.setOnClickListener(this)
|
||||
|
||||
@@ -3,13 +3,16 @@ package org.koitharu.kotatsu.reader.ui.pager.webtoon
|
||||
import android.content.Context
|
||||
import android.util.AttributeSet
|
||||
import android.widget.FrameLayout
|
||||
import androidx.annotation.AttrRes
|
||||
import org.koitharu.kotatsu.R
|
||||
|
||||
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) {
|
||||
|
||||
private val target by lazy {
|
||||
private val target by lazy(LazyThreadSafetyMode.NONE) {
|
||||
findViewById<WebtoonImageView>(R.id.ssiv)
|
||||
}
|
||||
|
||||
|
||||
@@ -13,14 +13,14 @@ import org.koitharu.kotatsu.databinding.ItemPageWebtoonBinding
|
||||
import org.koitharu.kotatsu.reader.domain.PageLoader
|
||||
import org.koitharu.kotatsu.reader.ui.pager.BasePageHolder
|
||||
import org.koitharu.kotatsu.reader.ui.pager.ReaderPage
|
||||
import org.koitharu.kotatsu.utils.GoneOnInvisibleListener
|
||||
import org.koitharu.kotatsu.utils.ext.*
|
||||
|
||||
|
||||
class WebtoonHolder(
|
||||
binding: ItemPageWebtoonBinding,
|
||||
loader: PageLoader,
|
||||
settings: AppSettings,
|
||||
exceptionResolver: ExceptionResolver
|
||||
exceptionResolver: ExceptionResolver,
|
||||
) : BasePageHolder<ItemPageWebtoonBinding>(binding, loader, settings, exceptionResolver),
|
||||
View.OnClickListener {
|
||||
|
||||
@@ -29,6 +29,7 @@ class WebtoonHolder(
|
||||
init {
|
||||
binding.ssiv.setOnImageEventListener(delegate)
|
||||
bindingInfo.buttonRetry.setOnClickListener(this)
|
||||
GoneOnInvisibleListener(bindingInfo.progressBar).attach()
|
||||
}
|
||||
|
||||
override fun onBind(data: ReaderPage) {
|
||||
@@ -61,9 +62,9 @@ class WebtoonHolder(
|
||||
|
||||
override fun onImageShowing(zoom: ZoomMode) {
|
||||
with(binding.ssiv) {
|
||||
maxScale = 2f * width / sWidth.toFloat()
|
||||
setMinimumScaleType(SubsamplingScaleImageView.SCALE_TYPE_CUSTOM)
|
||||
minScale = width / sWidth.toFloat()
|
||||
maxScale = minScale
|
||||
scrollTo(
|
||||
when {
|
||||
scrollToRestore != 0 -> scrollToRestore
|
||||
|
||||
@@ -1,11 +1,15 @@
|
||||
package org.koitharu.kotatsu.reader.ui.pager.webtoon
|
||||
|
||||
import android.app.Activity
|
||||
import android.content.Context
|
||||
import android.graphics.PointF
|
||||
import android.util.AttributeSet
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
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.utils.ext.isLowRamDevice
|
||||
import org.koitharu.kotatsu.utils.ext.parents
|
||||
|
||||
private const val SCROLL_UNKNOWN = -1
|
||||
|
||||
@@ -15,15 +19,15 @@ class WebtoonImageView @JvmOverloads constructor(
|
||||
) : SubsamplingScaleImageView(context, attr) {
|
||||
|
||||
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 scrollRange = SCROLL_UNKNOWN
|
||||
|
||||
init {
|
||||
setExecutor(Dispatchers.Default.asExecutor())
|
||||
setEagerLoadingEnabled(!isLowRamDevice(context))
|
||||
}
|
||||
|
||||
fun scrollBy(delta: Int) {
|
||||
val maxScroll = getScrollRange()
|
||||
if (maxScroll == 0) {
|
||||
@@ -36,6 +40,7 @@ class WebtoonImageView @JvmOverloads constructor(
|
||||
fun scrollTo(y: Int) {
|
||||
val maxScroll = getScrollRange()
|
||||
if (maxScroll == 0) {
|
||||
resetScaleAndCenter()
|
||||
return
|
||||
}
|
||||
scrollToInternal(y.coerceIn(0, maxScroll))
|
||||
@@ -58,8 +63,11 @@ class WebtoonImageView @JvmOverloads constructor(
|
||||
|
||||
override fun getSuggestedMinimumHeight(): Int {
|
||||
var desiredHeight = super.getSuggestedMinimumHeight()
|
||||
if (sHeight == 0 && desiredHeight < displayHeight) {
|
||||
desiredHeight = displayHeight
|
||||
if (sHeight == 0) {
|
||||
val parentHeight = parentHeight()
|
||||
if (desiredHeight < parentHeight) {
|
||||
desiredHeight = parentHeight
|
||||
}
|
||||
}
|
||||
return desiredHeight
|
||||
}
|
||||
@@ -84,7 +92,7 @@ class WebtoonImageView @JvmOverloads constructor(
|
||||
}
|
||||
}
|
||||
width = width.coerceAtLeast(suggestedMinimumWidth)
|
||||
height = height.coerceIn(suggestedMinimumHeight, displayHeight)
|
||||
height = height.coerceIn(suggestedMinimumHeight, parentHeight())
|
||||
setMeasuredDimension(width, height)
|
||||
}
|
||||
|
||||
@@ -101,4 +109,8 @@ class WebtoonImageView @JvmOverloads constructor(
|
||||
val totalHeight = (sHeight * minScale).toIntUp()
|
||||
scrollRange = (totalHeight - height).coerceAtLeast(0)
|
||||
}
|
||||
|
||||
private fun parentHeight(): Int {
|
||||
return parents.firstNotNullOfOrNull { it as? RecyclerView }?.height ?: 0
|
||||
}
|
||||
}
|
||||
@@ -6,6 +6,7 @@ import androidx.recyclerview.widget.LinearLayoutManager
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import kotlin.math.sign
|
||||
|
||||
@Suppress("unused")
|
||||
class WebtoonLayoutManager : LinearLayoutManager {
|
||||
|
||||
private var scrollDirection: Int = 0
|
||||
|
||||
@@ -23,7 +23,7 @@ class WebtoonReaderFragment : BaseReader<FragmentReaderWebtoonBinding>() {
|
||||
|
||||
override fun onInflateView(
|
||||
inflater: LayoutInflater,
|
||||
container: ViewGroup?
|
||||
container: ViewGroup?,
|
||||
) = FragmentReaderWebtoonBinding.inflate(inflater, container, false)
|
||||
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
|
||||
@@ -3,6 +3,7 @@ package org.koitharu.kotatsu.reader.ui.thumbnails.adapter
|
||||
import android.graphics.drawable.Drawable
|
||||
import coil.ImageLoader
|
||||
import coil.request.ImageRequest
|
||||
import coil.size.Scale
|
||||
import coil.size.Size
|
||||
import com.google.android.material.R as materialR
|
||||
import com.hannesdorfmann.adapterdelegates4.dsl.adapterDelegateViewBinding
|
||||
@@ -24,7 +25,6 @@ fun pageThumbnailAD(
|
||||
) = adapterDelegateViewBinding<PageThumbnail, PageThumbnail, ItemPageThumbBinding>(
|
||||
{ inflater, parent -> ItemPageThumbBinding.inflate(inflater, parent, false) }
|
||||
) {
|
||||
|
||||
var job: Job? = null
|
||||
val gridWidth = itemView.context.resources.getDimensionPixelSize(R.dimen.preferred_grid_width)
|
||||
val thumbSize = Size(
|
||||
@@ -39,6 +39,7 @@ fun pageThumbnailAD(
|
||||
.data(url)
|
||||
.referer(item.page.referer)
|
||||
.size(thumbSize)
|
||||
.scale(Scale.FILL)
|
||||
.allowRgb565(true)
|
||||
.build()
|
||||
).drawable
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,8 @@
|
||||
package org.koitharu.kotatsu.utils.ext
|
||||
|
||||
import android.app.ActivityManager
|
||||
import android.content.Context
|
||||
import android.content.Context.ACTIVITY_SERVICE
|
||||
import android.content.SharedPreferences
|
||||
import android.content.pm.ResolveInfo
|
||||
import android.net.ConnectivityManager
|
||||
@@ -27,6 +29,9 @@ import kotlinx.coroutines.suspendCancellableCoroutine
|
||||
val Context.connectivityManager: ConnectivityManager
|
||||
get() = getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager
|
||||
|
||||
val Context.activityManager: ActivityManager?
|
||||
get() = getSystemService(ACTIVITY_SERVICE) as? ActivityManager
|
||||
|
||||
suspend fun ConnectivityManager.waitForNetwork(): Network {
|
||||
val request = NetworkRequest.Builder().build()
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
|
||||
@@ -92,4 +97,8 @@ fun Lifecycle.postDelayed(runnable: Runnable, delay: Long) {
|
||||
delay(delay)
|
||||
runnable.run()
|
||||
}
|
||||
}
|
||||
|
||||
fun isLowRamDevice(context: Context): Boolean {
|
||||
return context.activityManager?.isLowRamDevice ?: false
|
||||
}
|
||||
@@ -4,6 +4,7 @@ import android.app.Activity
|
||||
import android.graphics.Rect
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.view.ViewParent
|
||||
import android.view.inputmethod.InputMethodManager
|
||||
import androidx.core.view.children
|
||||
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
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user