Reuse reader's image view in MangaFullCoverDialog (#5824)
* MangaFullCoverDialog: Support animated drawable * Scaled zoom duration * Wrap reader's image view to be reused in MangaFullCoverDialog * Cleanups * Forgot animated stuff for webtoon view * Cleanups * Oopsie * Cleanups * Consistent max scale for SubsamplingScaleImageView The max scale will be obtained from the default scale times 3 for consistent 3x zoom scale.
This commit is contained in:
parent
9a7a03e327
commit
746d35b52b
@ -1,7 +1,6 @@
|
|||||||
package eu.kanade.tachiyomi.ui.manga.info
|
package eu.kanade.tachiyomi.ui.manga.info
|
||||||
|
|
||||||
import android.app.Dialog
|
import android.app.Dialog
|
||||||
import android.graphics.drawable.BitmapDrawable
|
|
||||||
import android.graphics.drawable.ColorDrawable
|
import android.graphics.drawable.ColorDrawable
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.util.TypedValue
|
import android.util.TypedValue
|
||||||
@ -12,7 +11,6 @@ import androidx.core.view.WindowCompat
|
|||||||
import coil.imageLoader
|
import coil.imageLoader
|
||||||
import coil.request.Disposable
|
import coil.request.Disposable
|
||||||
import coil.request.ImageRequest
|
import coil.request.ImageRequest
|
||||||
import com.davemorrissey.labs.subscaleview.ImageSource
|
|
||||||
import dev.chrisbanes.insetter.applyInsetter
|
import dev.chrisbanes.insetter.applyInsetter
|
||||||
import eu.kanade.tachiyomi.R
|
import eu.kanade.tachiyomi.R
|
||||||
import eu.kanade.tachiyomi.data.database.DatabaseHelper
|
import eu.kanade.tachiyomi.data.database.DatabaseHelper
|
||||||
@ -20,6 +18,7 @@ import eu.kanade.tachiyomi.data.database.models.Manga
|
|||||||
import eu.kanade.tachiyomi.databinding.MangaFullCoverDialogBinding
|
import eu.kanade.tachiyomi.databinding.MangaFullCoverDialogBinding
|
||||||
import eu.kanade.tachiyomi.ui.base.controller.DialogController
|
import eu.kanade.tachiyomi.ui.base.controller.DialogController
|
||||||
import eu.kanade.tachiyomi.ui.manga.MangaController
|
import eu.kanade.tachiyomi.ui.manga.MangaController
|
||||||
|
import eu.kanade.tachiyomi.ui.reader.viewer.ReaderPageImageView
|
||||||
import eu.kanade.tachiyomi.util.view.setNavigationBarTransparentCompat
|
import eu.kanade.tachiyomi.util.view.setNavigationBarTransparentCompat
|
||||||
import eu.kanade.tachiyomi.widget.TachiyomiFullscreenDialog
|
import eu.kanade.tachiyomi.widget.TachiyomiFullscreenDialog
|
||||||
import uy.kohesive.injekt.Injekt
|
import uy.kohesive.injekt.Injekt
|
||||||
@ -63,12 +62,6 @@ class MangaFullCoverDialog : DialogController {
|
|||||||
menu?.findItem(R.id.action_edit_cover)?.isVisible = manga?.favorite ?: false
|
menu?.findItem(R.id.action_edit_cover)?.isVisible = manga?.favorite ?: false
|
||||||
}
|
}
|
||||||
|
|
||||||
binding?.fullCover?.apply {
|
|
||||||
setOnClickListener {
|
|
||||||
dialog?.dismiss()
|
|
||||||
}
|
|
||||||
setMinimumDpi(45)
|
|
||||||
}
|
|
||||||
setImage(manga)
|
setImage(manga)
|
||||||
|
|
||||||
binding?.appbar?.applyInsetter {
|
binding?.appbar?.applyInsetter {
|
||||||
@ -77,11 +70,10 @@ class MangaFullCoverDialog : DialogController {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
binding?.fullCover?.applyInsetter {
|
binding?.container?.onViewClicked = { dialog?.dismiss() }
|
||||||
|
binding?.container?.applyInsetter {
|
||||||
type(navigationBars = true) {
|
type(navigationBars = true) {
|
||||||
// Padding will make to image top align
|
padding(bottom = true)
|
||||||
// This is likely an issue with SubsamplingScaleImageView
|
|
||||||
margin(bottom = true)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -108,12 +100,16 @@ class MangaFullCoverDialog : DialogController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun setImage(manga: Manga?) {
|
fun setImage(manga: Manga?) {
|
||||||
val manga = manga ?: return
|
if (manga == null) return
|
||||||
val request = ImageRequest.Builder(applicationContext!!)
|
val request = ImageRequest.Builder(applicationContext!!)
|
||||||
.data(manga)
|
.data(manga)
|
||||||
.target {
|
.target {
|
||||||
val bitmap = (it as BitmapDrawable).bitmap
|
binding?.container?.setImage(
|
||||||
binding?.fullCover?.setImage(ImageSource.cachedBitmap(bitmap))
|
it,
|
||||||
|
ReaderPageImageView.Config(
|
||||||
|
zoomDuration = 500
|
||||||
|
)
|
||||||
|
)
|
||||||
}
|
}
|
||||||
.build()
|
.build()
|
||||||
|
|
||||||
|
@ -65,7 +65,6 @@ import eu.kanade.tachiyomi.ui.reader.viewer.BaseViewer
|
|||||||
import eu.kanade.tachiyomi.ui.reader.viewer.ReaderProgressIndicator
|
import eu.kanade.tachiyomi.ui.reader.viewer.ReaderProgressIndicator
|
||||||
import eu.kanade.tachiyomi.ui.reader.viewer.pager.R2LPagerViewer
|
import eu.kanade.tachiyomi.ui.reader.viewer.pager.R2LPagerViewer
|
||||||
import eu.kanade.tachiyomi.util.storage.getUriCompat
|
import eu.kanade.tachiyomi.util.storage.getUriCompat
|
||||||
import eu.kanade.tachiyomi.util.system.GLUtil
|
|
||||||
import eu.kanade.tachiyomi.util.system.applySystemAnimatorScale
|
import eu.kanade.tachiyomi.util.system.applySystemAnimatorScale
|
||||||
import eu.kanade.tachiyomi.util.system.createReaderThemeContext
|
import eu.kanade.tachiyomi.util.system.createReaderThemeContext
|
||||||
import eu.kanade.tachiyomi.util.system.getThemeColor
|
import eu.kanade.tachiyomi.util.system.getThemeColor
|
||||||
@ -109,11 +108,6 @@ class ReaderActivity : BaseRxActivity<ReaderActivityBinding, ReaderPresenter>()
|
|||||||
|
|
||||||
private val preferences: PreferencesHelper by injectLazy()
|
private val preferences: PreferencesHelper by injectLazy()
|
||||||
|
|
||||||
/**
|
|
||||||
* The maximum bitmap size supported by the device.
|
|
||||||
*/
|
|
||||||
val maxBitmapSize by lazy { GLUtil.maxTextureSize }
|
|
||||||
|
|
||||||
val hasCutout by lazy { hasDisplayCutout() }
|
val hasCutout by lazy { hasDisplayCutout() }
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -0,0 +1,264 @@
|
|||||||
|
package eu.kanade.tachiyomi.ui.reader.viewer
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import android.graphics.PointF
|
||||||
|
import android.graphics.drawable.Animatable
|
||||||
|
import android.graphics.drawable.BitmapDrawable
|
||||||
|
import android.graphics.drawable.Drawable
|
||||||
|
import android.util.AttributeSet
|
||||||
|
import android.view.GestureDetector
|
||||||
|
import android.view.MotionEvent
|
||||||
|
import android.view.View
|
||||||
|
import android.view.ViewGroup.LayoutParams.MATCH_PARENT
|
||||||
|
import android.widget.FrameLayout
|
||||||
|
import androidx.annotation.AttrRes
|
||||||
|
import androidx.annotation.CallSuper
|
||||||
|
import androidx.annotation.StyleRes
|
||||||
|
import androidx.appcompat.widget.AppCompatImageView
|
||||||
|
import androidx.core.view.isVisible
|
||||||
|
import coil.clear
|
||||||
|
import coil.imageLoader
|
||||||
|
import coil.request.CachePolicy
|
||||||
|
import coil.request.ImageRequest
|
||||||
|
import com.davemorrissey.labs.subscaleview.ImageSource
|
||||||
|
import com.davemorrissey.labs.subscaleview.SubsamplingScaleImageView
|
||||||
|
import com.davemorrissey.labs.subscaleview.SubsamplingScaleImageView.SCALE_TYPE_CENTER_INSIDE
|
||||||
|
import com.github.chrisbanes.photoview.PhotoView
|
||||||
|
import eu.kanade.tachiyomi.ui.reader.viewer.webtoon.WebtoonSubsamplingImageView
|
||||||
|
import eu.kanade.tachiyomi.util.system.GLUtil
|
||||||
|
import eu.kanade.tachiyomi.util.system.animatorDurationScale
|
||||||
|
import java.io.InputStream
|
||||||
|
import java.nio.ByteBuffer
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A wrapper view for showing page image.
|
||||||
|
*
|
||||||
|
* Animated image will be drawn by [PhotoView] while [SubsamplingScaleImageView] will take non-animated image.
|
||||||
|
*
|
||||||
|
* @param isWebtoon if true, [WebtoonSubsamplingImageView] will be used instead of [SubsamplingScaleImageView]
|
||||||
|
* and [AppCompatImageView] will be used instead of [PhotoView]
|
||||||
|
*/
|
||||||
|
open class ReaderPageImageView @JvmOverloads constructor(
|
||||||
|
context: Context,
|
||||||
|
attrs: AttributeSet? = null,
|
||||||
|
@AttrRes defStyleAttrs: Int = 0,
|
||||||
|
@StyleRes defStyleRes: Int = 0,
|
||||||
|
private val isWebtoon: Boolean = false
|
||||||
|
) : FrameLayout(context, attrs, defStyleAttrs, defStyleRes) {
|
||||||
|
|
||||||
|
private var pageView: View? = null
|
||||||
|
|
||||||
|
var onImageLoaded: (() -> Unit)? = null
|
||||||
|
var onImageLoadError: (() -> Unit)? = null
|
||||||
|
var onScaleChanged: ((newScale: Float) -> Unit)? = null
|
||||||
|
var onViewClicked: (() -> Unit)? = null
|
||||||
|
|
||||||
|
@CallSuper
|
||||||
|
open fun onImageLoaded() {
|
||||||
|
onImageLoaded?.invoke()
|
||||||
|
}
|
||||||
|
|
||||||
|
@CallSuper
|
||||||
|
open fun onImageLoadError() {
|
||||||
|
onImageLoadError?.invoke()
|
||||||
|
}
|
||||||
|
|
||||||
|
@CallSuper
|
||||||
|
open fun onScaleChanged(newScale: Float) {
|
||||||
|
onScaleChanged?.invoke(newScale)
|
||||||
|
}
|
||||||
|
|
||||||
|
@CallSuper
|
||||||
|
open fun onViewClicked() {
|
||||||
|
onViewClicked?.invoke()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun setImage(drawable: Drawable, config: Config) {
|
||||||
|
if (drawable is Animatable) {
|
||||||
|
prepareAnimatedImageView()
|
||||||
|
setAnimatedImage(drawable, config)
|
||||||
|
} else {
|
||||||
|
prepareNonAnimatedImageView()
|
||||||
|
setNonAnimatedImage(drawable, config)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun setImage(inputStream: InputStream, isAnimated: Boolean, config: Config) {
|
||||||
|
if (isAnimated) {
|
||||||
|
prepareAnimatedImageView()
|
||||||
|
setAnimatedImage(inputStream, config)
|
||||||
|
} else {
|
||||||
|
prepareNonAnimatedImageView()
|
||||||
|
setNonAnimatedImage(inputStream, config)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun recycle() = pageView?.let {
|
||||||
|
when (it) {
|
||||||
|
is SubsamplingScaleImageView -> it.recycle()
|
||||||
|
is AppCompatImageView -> it.clear()
|
||||||
|
}
|
||||||
|
it.isVisible = false
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun prepareNonAnimatedImageView() {
|
||||||
|
if (pageView is SubsamplingScaleImageView) return
|
||||||
|
removeView(pageView)
|
||||||
|
|
||||||
|
pageView = if (isWebtoon) {
|
||||||
|
WebtoonSubsamplingImageView(context)
|
||||||
|
} else {
|
||||||
|
SubsamplingScaleImageView(context)
|
||||||
|
}.apply {
|
||||||
|
setMaxTileSize(GLUtil.maxTextureSize)
|
||||||
|
setDoubleTapZoomStyle(SubsamplingScaleImageView.ZOOM_FOCUS_CENTER)
|
||||||
|
setPanLimit(SubsamplingScaleImageView.PAN_LIMIT_INSIDE)
|
||||||
|
setMinimumTileDpi(180)
|
||||||
|
setOnStateChangedListener(
|
||||||
|
object : SubsamplingScaleImageView.OnStateChangedListener {
|
||||||
|
override fun onScaleChanged(newScale: Float, origin: Int) {
|
||||||
|
this@ReaderPageImageView.onScaleChanged(newScale)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onCenterChanged(newCenter: PointF?, origin: Int) {
|
||||||
|
// Not used
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
setOnClickListener { this@ReaderPageImageView.onViewClicked() }
|
||||||
|
}
|
||||||
|
addView(pageView, MATCH_PARENT, MATCH_PARENT)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun setNonAnimatedImage(
|
||||||
|
image: Any,
|
||||||
|
config: Config
|
||||||
|
) = (pageView as? SubsamplingScaleImageView)?.apply {
|
||||||
|
setDoubleTapZoomDuration(config.zoomDuration.getSystemScaledDuration())
|
||||||
|
setMinimumScaleType(config.minimumScaleType)
|
||||||
|
setMinimumDpi(1) // Just so that very small image will be fit for initial load
|
||||||
|
setCropBorders(config.cropBorders)
|
||||||
|
setOnImageEventListener(
|
||||||
|
object : SubsamplingScaleImageView.DefaultOnImageEventListener() {
|
||||||
|
override fun onReady() {
|
||||||
|
// 3x zoom
|
||||||
|
maxScale = scale * MAX_ZOOM_SCALE
|
||||||
|
setDoubleTapZoomScale(scale * 2)
|
||||||
|
|
||||||
|
when (config.zoomStartPosition) {
|
||||||
|
ZoomStartPosition.LEFT -> setScaleAndCenter(scale, PointF(0F, 0F))
|
||||||
|
ZoomStartPosition.RIGHT -> setScaleAndCenter(scale, PointF(sWidth.toFloat(), 0F))
|
||||||
|
ZoomStartPosition.CENTER -> setScaleAndCenter(scale, center.also { it?.y = 0F })
|
||||||
|
}
|
||||||
|
this@ReaderPageImageView.onImageLoaded()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onImageLoadError(e: Exception) {
|
||||||
|
this@ReaderPageImageView.onImageLoadError()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
when (image) {
|
||||||
|
is Drawable -> {
|
||||||
|
val bitmap = (image as BitmapDrawable).bitmap
|
||||||
|
setImage(ImageSource.bitmap(bitmap))
|
||||||
|
}
|
||||||
|
is InputStream -> setImage(ImageSource.inputStream(image))
|
||||||
|
else -> throw IllegalArgumentException("Not implemented for class ${image::class.simpleName}")
|
||||||
|
}
|
||||||
|
isVisible = true
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun prepareAnimatedImageView() {
|
||||||
|
if (pageView is AppCompatImageView) return
|
||||||
|
removeView(pageView)
|
||||||
|
|
||||||
|
pageView = if (isWebtoon) {
|
||||||
|
AppCompatImageView(context)
|
||||||
|
} else {
|
||||||
|
PhotoView(context)
|
||||||
|
}.apply {
|
||||||
|
adjustViewBounds = true
|
||||||
|
|
||||||
|
if (this is PhotoView) {
|
||||||
|
setScaleLevels(1F, 2F, MAX_ZOOM_SCALE)
|
||||||
|
// Force 2 scale levels on double tap
|
||||||
|
setOnDoubleTapListener(
|
||||||
|
object : GestureDetector.SimpleOnGestureListener() {
|
||||||
|
override fun onDoubleTap(e: MotionEvent): Boolean {
|
||||||
|
if (scale > 1F) {
|
||||||
|
setScale(1F, e.x, e.y, true)
|
||||||
|
} else {
|
||||||
|
setScale(2F, e.x, e.y, true)
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onSingleTapConfirmed(e: MotionEvent?): Boolean {
|
||||||
|
this@ReaderPageImageView.onViewClicked()
|
||||||
|
return super.onSingleTapConfirmed(e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
setOnScaleChangeListener { _, _, _ ->
|
||||||
|
this@ReaderPageImageView.onScaleChanged(scale)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
addView(pageView, MATCH_PARENT, MATCH_PARENT)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun setAnimatedImage(
|
||||||
|
image: Any,
|
||||||
|
config: Config
|
||||||
|
) = (pageView as? AppCompatImageView)?.apply {
|
||||||
|
if (this is PhotoView) {
|
||||||
|
setZoomTransitionDuration(config.zoomDuration.getSystemScaledDuration())
|
||||||
|
}
|
||||||
|
|
||||||
|
val data = when (image) {
|
||||||
|
is Drawable -> image
|
||||||
|
is InputStream -> ByteBuffer.wrap(image.readBytes())
|
||||||
|
else -> throw IllegalArgumentException("Not implemented for class ${image::class.simpleName}")
|
||||||
|
}
|
||||||
|
val request = ImageRequest.Builder(context)
|
||||||
|
.data(data)
|
||||||
|
.memoryCachePolicy(CachePolicy.DISABLED)
|
||||||
|
.diskCachePolicy(CachePolicy.DISABLED)
|
||||||
|
.target(
|
||||||
|
onSuccess = { result ->
|
||||||
|
setImageDrawable(result)
|
||||||
|
(result as? Animatable)?.start()
|
||||||
|
isVisible = true
|
||||||
|
this@ReaderPageImageView.onImageLoaded()
|
||||||
|
},
|
||||||
|
onError = {
|
||||||
|
this@ReaderPageImageView.onImageLoadError()
|
||||||
|
}
|
||||||
|
)
|
||||||
|
.crossfade(false)
|
||||||
|
.build()
|
||||||
|
context.imageLoader.enqueue(request)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun Int.getSystemScaledDuration(): Int {
|
||||||
|
return (this * context.animatorDurationScale).toInt().coerceAtLeast(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* All of the config except [zoomDuration] will only be used for non-animated image.
|
||||||
|
*/
|
||||||
|
data class Config(
|
||||||
|
val zoomDuration: Int,
|
||||||
|
val minimumScaleType: Int = SCALE_TYPE_CENTER_INSIDE,
|
||||||
|
val cropBorders: Boolean = false,
|
||||||
|
val zoomStartPosition: ZoomStartPosition = ZoomStartPosition.CENTER
|
||||||
|
)
|
||||||
|
|
||||||
|
enum class ZoomStartPosition {
|
||||||
|
LEFT, CENTER, RIGHT
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private const val MAX_ZOOM_SCALE = 3F
|
@ -1,6 +1,7 @@
|
|||||||
package eu.kanade.tachiyomi.ui.reader.viewer.pager
|
package eu.kanade.tachiyomi.ui.reader.viewer.pager
|
||||||
|
|
||||||
import eu.kanade.tachiyomi.data.preference.PreferencesHelper
|
import eu.kanade.tachiyomi.data.preference.PreferencesHelper
|
||||||
|
import eu.kanade.tachiyomi.ui.reader.viewer.ReaderPageImageView
|
||||||
import eu.kanade.tachiyomi.ui.reader.viewer.ViewerConfig
|
import eu.kanade.tachiyomi.ui.reader.viewer.ViewerConfig
|
||||||
import eu.kanade.tachiyomi.ui.reader.viewer.ViewerNavigation
|
import eu.kanade.tachiyomi.ui.reader.viewer.ViewerNavigation
|
||||||
import eu.kanade.tachiyomi.ui.reader.viewer.navigation.EdgeNavigation
|
import eu.kanade.tachiyomi.ui.reader.viewer.navigation.EdgeNavigation
|
||||||
@ -34,7 +35,7 @@ class PagerConfig(
|
|||||||
var imageScaleType = 1
|
var imageScaleType = 1
|
||||||
private set
|
private set
|
||||||
|
|
||||||
var imageZoomType = ZoomType.Left
|
var imageZoomType = ReaderPageImageView.ZoomStartPosition.LEFT
|
||||||
private set
|
private set
|
||||||
|
|
||||||
var imageCropBorders = false
|
var imageCropBorders = false
|
||||||
@ -86,16 +87,16 @@ class PagerConfig(
|
|||||||
imageZoomType = when (value) {
|
imageZoomType = when (value) {
|
||||||
// Auto
|
// Auto
|
||||||
1 -> when (viewer) {
|
1 -> when (viewer) {
|
||||||
is L2RPagerViewer -> ZoomType.Left
|
is L2RPagerViewer -> ReaderPageImageView.ZoomStartPosition.LEFT
|
||||||
is R2LPagerViewer -> ZoomType.Right
|
is R2LPagerViewer -> ReaderPageImageView.ZoomStartPosition.RIGHT
|
||||||
else -> ZoomType.Center
|
else -> ReaderPageImageView.ZoomStartPosition.CENTER
|
||||||
}
|
}
|
||||||
// Left
|
// Left
|
||||||
2 -> ZoomType.Left
|
2 -> ReaderPageImageView.ZoomStartPosition.LEFT
|
||||||
// Right
|
// Right
|
||||||
3 -> ZoomType.Right
|
3 -> ReaderPageImageView.ZoomStartPosition.RIGHT
|
||||||
// Center
|
// Center
|
||||||
else -> ZoomType.Center
|
else -> ReaderPageImageView.ZoomStartPosition.CENTER
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -122,8 +123,4 @@ class PagerConfig(
|
|||||||
}
|
}
|
||||||
navigationModeChangedListener?.invoke()
|
navigationModeChangedListener?.invoke()
|
||||||
}
|
}
|
||||||
|
|
||||||
enum class ZoomType {
|
|
||||||
Left, Center, Right
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -1,35 +1,21 @@
|
|||||||
package eu.kanade.tachiyomi.ui.reader.viewer.pager
|
package eu.kanade.tachiyomi.ui.reader.viewer.pager
|
||||||
|
|
||||||
import android.annotation.SuppressLint
|
import android.annotation.SuppressLint
|
||||||
import android.app.ActionBar
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.graphics.PointF
|
|
||||||
import android.graphics.drawable.Animatable
|
|
||||||
import android.view.GestureDetector
|
|
||||||
import android.view.Gravity
|
import android.view.Gravity
|
||||||
import android.view.MotionEvent
|
|
||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
import android.view.ViewGroup.LayoutParams.MATCH_PARENT
|
|
||||||
import android.view.ViewGroup.LayoutParams.WRAP_CONTENT
|
import android.view.ViewGroup.LayoutParams.WRAP_CONTENT
|
||||||
import android.widget.FrameLayout
|
|
||||||
import android.widget.ImageView
|
|
||||||
import android.widget.LinearLayout
|
import android.widget.LinearLayout
|
||||||
import android.widget.TextView
|
import android.widget.TextView
|
||||||
import androidx.core.view.isVisible
|
import androidx.core.view.isVisible
|
||||||
import androidx.core.view.setMargins
|
import androidx.core.view.setMargins
|
||||||
import androidx.core.view.updateLayoutParams
|
import androidx.core.view.updateLayoutParams
|
||||||
import coil.imageLoader
|
|
||||||
import coil.request.CachePolicy
|
|
||||||
import coil.request.ImageRequest
|
|
||||||
import com.davemorrissey.labs.subscaleview.ImageSource
|
|
||||||
import com.davemorrissey.labs.subscaleview.SubsamplingScaleImageView
|
|
||||||
import com.github.chrisbanes.photoview.PhotoView
|
|
||||||
import eu.kanade.tachiyomi.R
|
import eu.kanade.tachiyomi.R
|
||||||
import eu.kanade.tachiyomi.source.model.Page
|
import eu.kanade.tachiyomi.source.model.Page
|
||||||
import eu.kanade.tachiyomi.ui.reader.model.InsertPage
|
import eu.kanade.tachiyomi.ui.reader.model.InsertPage
|
||||||
import eu.kanade.tachiyomi.ui.reader.model.ReaderPage
|
import eu.kanade.tachiyomi.ui.reader.model.ReaderPage
|
||||||
|
import eu.kanade.tachiyomi.ui.reader.viewer.ReaderPageImageView
|
||||||
import eu.kanade.tachiyomi.ui.reader.viewer.ReaderProgressIndicator
|
import eu.kanade.tachiyomi.ui.reader.viewer.ReaderProgressIndicator
|
||||||
import eu.kanade.tachiyomi.ui.reader.viewer.pager.PagerConfig.ZoomType
|
|
||||||
import eu.kanade.tachiyomi.ui.webview.WebViewActivity
|
import eu.kanade.tachiyomi.ui.webview.WebViewActivity
|
||||||
import eu.kanade.tachiyomi.util.system.ImageUtil
|
import eu.kanade.tachiyomi.util.system.ImageUtil
|
||||||
import eu.kanade.tachiyomi.util.system.dpToPx
|
import eu.kanade.tachiyomi.util.system.dpToPx
|
||||||
@ -40,7 +26,6 @@ import rx.android.schedulers.AndroidSchedulers
|
|||||||
import rx.schedulers.Schedulers
|
import rx.schedulers.Schedulers
|
||||||
import java.io.ByteArrayInputStream
|
import java.io.ByteArrayInputStream
|
||||||
import java.io.InputStream
|
import java.io.InputStream
|
||||||
import java.nio.ByteBuffer
|
|
||||||
import java.util.concurrent.TimeUnit
|
import java.util.concurrent.TimeUnit
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -51,7 +36,7 @@ class PagerPageHolder(
|
|||||||
readerThemedContext: Context,
|
readerThemedContext: Context,
|
||||||
val viewer: PagerViewer,
|
val viewer: PagerViewer,
|
||||||
val page: ReaderPage
|
val page: ReaderPage
|
||||||
) : FrameLayout(readerThemedContext), ViewPagerAdapter.PositionableView {
|
) : ReaderPageImageView(readerThemedContext), ViewPagerAdapter.PositionableView {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Item that identifies this view. Needed by the adapter to not recreate views.
|
* Item that identifies this view. Needed by the adapter to not recreate views.
|
||||||
@ -62,17 +47,11 @@ class PagerPageHolder(
|
|||||||
/**
|
/**
|
||||||
* Loading progress bar to indicate the current progress.
|
* Loading progress bar to indicate the current progress.
|
||||||
*/
|
*/
|
||||||
private val progressIndicator: ReaderProgressIndicator
|
private val progressIndicator: ReaderProgressIndicator = ReaderProgressIndicator(readerThemedContext).apply {
|
||||||
|
updateLayoutParams<LayoutParams> {
|
||||||
/**
|
gravity = Gravity.CENTER
|
||||||
* Image view that supports subsampling on zoom.
|
}
|
||||||
*/
|
}
|
||||||
private var subsamplingImageView: SubsamplingScaleImageView? = null
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Simple image view only used on GIFs.
|
|
||||||
*/
|
|
||||||
private var imageView: ImageView? = null
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Retry button used to allow retrying.
|
* Retry button used to allow retrying.
|
||||||
@ -100,36 +79,9 @@ class PagerPageHolder(
|
|||||||
*/
|
*/
|
||||||
private var readImageHeaderSubscription: Subscription? = null
|
private var readImageHeaderSubscription: Subscription? = null
|
||||||
|
|
||||||
val stateChangedListener = object : SubsamplingScaleImageView.OnStateChangedListener {
|
|
||||||
override fun onScaleChanged(newScale: Float, origin: Int) {
|
|
||||||
viewer.activity.hideMenu()
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onCenterChanged(newCenter: PointF?, origin: Int) {
|
|
||||||
viewer.activity.hideMenu()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
private var visibilityListener = ActionBar.OnMenuVisibilityListener { isVisible ->
|
|
||||||
if (isVisible.not()) {
|
|
||||||
subsamplingImageView?.setOnStateChangedListener(null)
|
|
||||||
return@OnMenuVisibilityListener
|
|
||||||
}
|
|
||||||
subsamplingImageView?.setOnStateChangedListener(stateChangedListener)
|
|
||||||
}
|
|
||||||
|
|
||||||
init {
|
init {
|
||||||
progressIndicator = ReaderProgressIndicator(readerThemedContext).apply {
|
|
||||||
updateLayoutParams<LayoutParams> {
|
|
||||||
gravity = Gravity.CENTER
|
|
||||||
}
|
|
||||||
}
|
|
||||||
addView(progressIndicator)
|
addView(progressIndicator)
|
||||||
observeStatus()
|
observeStatus()
|
||||||
viewer.activity.addOnMenuVisibilityListener(visibilityListener)
|
|
||||||
if (viewer.activity.menuVisible) {
|
|
||||||
// Listener will not be available if user changed page with seek bar
|
|
||||||
subsamplingImageView?.setOnStateChangedListener(stateChangedListener)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -141,9 +93,6 @@ class PagerPageHolder(
|
|||||||
unsubscribeProgress()
|
unsubscribeProgress()
|
||||||
unsubscribeStatus()
|
unsubscribeStatus()
|
||||||
unsubscribeReadImageHeader()
|
unsubscribeReadImageHeader()
|
||||||
subsamplingImageView?.setOnImageEventListener(null)
|
|
||||||
subsamplingImageView?.setOnStateChangedListener(null)
|
|
||||||
viewer.activity.removeOnMenuVisibilityListener(visibilityListener)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -284,13 +233,18 @@ class PagerPageHolder(
|
|||||||
.observeOn(AndroidSchedulers.mainThread())
|
.observeOn(AndroidSchedulers.mainThread())
|
||||||
.doOnNext { (bais, isAnimated, background) ->
|
.doOnNext { (bais, isAnimated, background) ->
|
||||||
bais.use {
|
bais.use {
|
||||||
|
setImage(
|
||||||
|
it,
|
||||||
|
isAnimated,
|
||||||
|
Config(
|
||||||
|
zoomDuration = viewer.config.doubleTapAnimDuration,
|
||||||
|
minimumScaleType = viewer.config.imageScaleType,
|
||||||
|
cropBorders = viewer.config.imageCropBorders,
|
||||||
|
zoomStartPosition = viewer.config.imageZoomType
|
||||||
|
)
|
||||||
|
)
|
||||||
if (!isAnimated) {
|
if (!isAnimated) {
|
||||||
this.background = background
|
this.background = background
|
||||||
initSubsamplingImageView().apply {
|
|
||||||
setImage(ImageSource.inputStream(it))
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
initImageView().setImage(it)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -351,76 +305,18 @@ class PagerPageHolder(
|
|||||||
/**
|
/**
|
||||||
* Called when an image fails to decode.
|
* Called when an image fails to decode.
|
||||||
*/
|
*/
|
||||||
private fun onImageDecodeError() {
|
override fun onImageLoadError() {
|
||||||
|
super.onImageLoadError()
|
||||||
progressIndicator.hide()
|
progressIndicator.hide()
|
||||||
initDecodeErrorLayout().isVisible = true
|
initDecodeErrorLayout().isVisible = true
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Initializes a subsampling scale view.
|
* Called when an image is zoomed in/out.
|
||||||
*/
|
*/
|
||||||
private fun initSubsamplingImageView(): SubsamplingScaleImageView {
|
override fun onScaleChanged(newScale: Float) {
|
||||||
if (subsamplingImageView != null) return subsamplingImageView!!
|
super.onScaleChanged(newScale)
|
||||||
|
viewer.activity.hideMenu()
|
||||||
val config = viewer.config
|
|
||||||
|
|
||||||
subsamplingImageView = SubsamplingScaleImageView(context).apply {
|
|
||||||
layoutParams = LayoutParams(MATCH_PARENT, MATCH_PARENT)
|
|
||||||
setMaxTileSize(viewer.activity.maxBitmapSize)
|
|
||||||
setDoubleTapZoomStyle(SubsamplingScaleImageView.ZOOM_FOCUS_CENTER)
|
|
||||||
setDoubleTapZoomDuration(config.doubleTapAnimDuration)
|
|
||||||
setPanLimit(SubsamplingScaleImageView.PAN_LIMIT_INSIDE)
|
|
||||||
setMinimumScaleType(config.imageScaleType)
|
|
||||||
setMinimumDpi(90)
|
|
||||||
setMinimumTileDpi(180)
|
|
||||||
setCropBorders(config.imageCropBorders)
|
|
||||||
setOnImageEventListener(
|
|
||||||
object : SubsamplingScaleImageView.DefaultOnImageEventListener() {
|
|
||||||
override fun onReady() {
|
|
||||||
when (config.imageZoomType) {
|
|
||||||
ZoomType.Left -> setScaleAndCenter(scale, PointF(0f, 0f))
|
|
||||||
ZoomType.Right -> setScaleAndCenter(scale, PointF(sWidth.toFloat(), 0f))
|
|
||||||
ZoomType.Center -> setScaleAndCenter(scale, center.also { it?.y = 0f })
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onImageLoadError(e: Exception) {
|
|
||||||
onImageDecodeError()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
)
|
|
||||||
}
|
|
||||||
addView(subsamplingImageView)
|
|
||||||
return subsamplingImageView!!
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Initializes an image view, used for GIFs.
|
|
||||||
*/
|
|
||||||
private fun initImageView(): ImageView {
|
|
||||||
if (imageView != null) return imageView!!
|
|
||||||
|
|
||||||
imageView = PhotoView(context, null).apply {
|
|
||||||
layoutParams = LayoutParams(MATCH_PARENT, MATCH_PARENT)
|
|
||||||
adjustViewBounds = true
|
|
||||||
setZoomTransitionDuration(viewer.config.doubleTapAnimDuration)
|
|
||||||
setScaleLevels(1f, 2f, 3f)
|
|
||||||
// Force 2 scale levels on double tap
|
|
||||||
setOnDoubleTapListener(
|
|
||||||
object : GestureDetector.SimpleOnGestureListener() {
|
|
||||||
override fun onDoubleTap(e: MotionEvent): Boolean {
|
|
||||||
if (scale > 1f) {
|
|
||||||
setScale(1f, e.x, e.y, true)
|
|
||||||
} else {
|
|
||||||
setScale(2f, e.x, e.y, true)
|
|
||||||
}
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
)
|
|
||||||
}
|
|
||||||
addView(imageView)
|
|
||||||
return imageView!!
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -497,28 +393,4 @@ class PagerPageHolder(
|
|||||||
addView(decodeLayout)
|
addView(decodeLayout)
|
||||||
return decodeLayout
|
return decodeLayout
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Extension method to set a [stream] into this ImageView.
|
|
||||||
*/
|
|
||||||
private fun ImageView.setImage(stream: InputStream) {
|
|
||||||
val request = ImageRequest.Builder(context)
|
|
||||||
.data(ByteBuffer.wrap(stream.readBytes()))
|
|
||||||
.memoryCachePolicy(CachePolicy.DISABLED)
|
|
||||||
.diskCachePolicy(CachePolicy.DISABLED)
|
|
||||||
.target(
|
|
||||||
onSuccess = { result ->
|
|
||||||
if (result is Animatable) {
|
|
||||||
result.start()
|
|
||||||
}
|
|
||||||
setImageDrawable(result)
|
|
||||||
},
|
|
||||||
onError = {
|
|
||||||
onImageDecodeError()
|
|
||||||
}
|
|
||||||
)
|
|
||||||
.crossfade(false)
|
|
||||||
.build()
|
|
||||||
context.imageLoader.enqueue(request)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -1,7 +1,6 @@
|
|||||||
package eu.kanade.tachiyomi.ui.reader.viewer.webtoon
|
package eu.kanade.tachiyomi.ui.reader.viewer.webtoon
|
||||||
|
|
||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
import android.widget.FrameLayout
|
|
||||||
import android.widget.LinearLayout
|
import android.widget.LinearLayout
|
||||||
import androidx.recyclerview.widget.DiffUtil
|
import androidx.recyclerview.widget.DiffUtil
|
||||||
import androidx.recyclerview.widget.RecyclerView
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
@ -9,6 +8,7 @@ import eu.kanade.tachiyomi.ui.reader.model.ChapterTransition
|
|||||||
import eu.kanade.tachiyomi.ui.reader.model.ReaderChapter
|
import eu.kanade.tachiyomi.ui.reader.model.ReaderChapter
|
||||||
import eu.kanade.tachiyomi.ui.reader.model.ReaderPage
|
import eu.kanade.tachiyomi.ui.reader.model.ReaderPage
|
||||||
import eu.kanade.tachiyomi.ui.reader.model.ViewerChapters
|
import eu.kanade.tachiyomi.ui.reader.model.ViewerChapters
|
||||||
|
import eu.kanade.tachiyomi.ui.reader.viewer.ReaderPageImageView
|
||||||
import eu.kanade.tachiyomi.ui.reader.viewer.hasMissingChapters
|
import eu.kanade.tachiyomi.ui.reader.viewer.hasMissingChapters
|
||||||
import eu.kanade.tachiyomi.util.system.createReaderThemeContext
|
import eu.kanade.tachiyomi.util.system.createReaderThemeContext
|
||||||
|
|
||||||
@ -112,7 +112,7 @@ class WebtoonAdapter(val viewer: WebtoonViewer) : RecyclerView.Adapter<RecyclerV
|
|||||||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
|
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
|
||||||
return when (viewType) {
|
return when (viewType) {
|
||||||
PAGE_VIEW -> {
|
PAGE_VIEW -> {
|
||||||
val view = FrameLayout(readerThemedContext)
|
val view = ReaderPageImageView(readerThemedContext, isWebtoon = true)
|
||||||
WebtoonPageHolder(view, viewer)
|
WebtoonPageHolder(view, viewer)
|
||||||
}
|
}
|
||||||
TRANSITION_VIEW -> {
|
TRANSITION_VIEW -> {
|
||||||
|
@ -1,29 +1,22 @@
|
|||||||
package eu.kanade.tachiyomi.ui.reader.viewer.webtoon
|
package eu.kanade.tachiyomi.ui.reader.viewer.webtoon
|
||||||
|
|
||||||
import android.content.res.Resources
|
import android.content.res.Resources
|
||||||
import android.graphics.drawable.Animatable
|
|
||||||
import android.view.Gravity
|
import android.view.Gravity
|
||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
import android.view.ViewGroup.LayoutParams.MATCH_PARENT
|
import android.view.ViewGroup.LayoutParams.MATCH_PARENT
|
||||||
import android.view.ViewGroup.LayoutParams.WRAP_CONTENT
|
import android.view.ViewGroup.LayoutParams.WRAP_CONTENT
|
||||||
import android.widget.FrameLayout
|
import android.widget.FrameLayout
|
||||||
import android.widget.ImageView
|
|
||||||
import android.widget.LinearLayout
|
import android.widget.LinearLayout
|
||||||
import android.widget.TextView
|
import android.widget.TextView
|
||||||
import androidx.appcompat.widget.AppCompatButton
|
import androidx.appcompat.widget.AppCompatButton
|
||||||
import androidx.appcompat.widget.AppCompatImageView
|
|
||||||
import androidx.core.view.isVisible
|
import androidx.core.view.isVisible
|
||||||
import androidx.core.view.updateLayoutParams
|
import androidx.core.view.updateLayoutParams
|
||||||
import androidx.core.view.updateMargins
|
import androidx.core.view.updateMargins
|
||||||
import coil.clear
|
|
||||||
import coil.imageLoader
|
|
||||||
import coil.request.CachePolicy
|
|
||||||
import coil.request.ImageRequest
|
|
||||||
import com.davemorrissey.labs.subscaleview.ImageSource
|
|
||||||
import com.davemorrissey.labs.subscaleview.SubsamplingScaleImageView
|
import com.davemorrissey.labs.subscaleview.SubsamplingScaleImageView
|
||||||
import eu.kanade.tachiyomi.R
|
import eu.kanade.tachiyomi.R
|
||||||
import eu.kanade.tachiyomi.source.model.Page
|
import eu.kanade.tachiyomi.source.model.Page
|
||||||
import eu.kanade.tachiyomi.ui.reader.model.ReaderPage
|
import eu.kanade.tachiyomi.ui.reader.model.ReaderPage
|
||||||
|
import eu.kanade.tachiyomi.ui.reader.viewer.ReaderPageImageView
|
||||||
import eu.kanade.tachiyomi.ui.reader.viewer.ReaderProgressIndicator
|
import eu.kanade.tachiyomi.ui.reader.viewer.ReaderProgressIndicator
|
||||||
import eu.kanade.tachiyomi.ui.webview.WebViewActivity
|
import eu.kanade.tachiyomi.ui.webview.WebViewActivity
|
||||||
import eu.kanade.tachiyomi.util.system.ImageUtil
|
import eu.kanade.tachiyomi.util.system.ImageUtil
|
||||||
@ -33,7 +26,6 @@ import rx.Subscription
|
|||||||
import rx.android.schedulers.AndroidSchedulers
|
import rx.android.schedulers.AndroidSchedulers
|
||||||
import rx.schedulers.Schedulers
|
import rx.schedulers.Schedulers
|
||||||
import java.io.InputStream
|
import java.io.InputStream
|
||||||
import java.nio.ByteBuffer
|
|
||||||
import java.util.concurrent.TimeUnit
|
import java.util.concurrent.TimeUnit
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -44,7 +36,7 @@ import java.util.concurrent.TimeUnit
|
|||||||
* @constructor creates a new webtoon holder.
|
* @constructor creates a new webtoon holder.
|
||||||
*/
|
*/
|
||||||
class WebtoonPageHolder(
|
class WebtoonPageHolder(
|
||||||
private val frame: FrameLayout,
|
private val frame: ReaderPageImageView,
|
||||||
viewer: WebtoonViewer
|
viewer: WebtoonViewer
|
||||||
) : WebtoonBaseHolder(frame, viewer) {
|
) : WebtoonBaseHolder(frame, viewer) {
|
||||||
|
|
||||||
@ -59,17 +51,6 @@ class WebtoonPageHolder(
|
|||||||
*/
|
*/
|
||||||
private lateinit var progressContainer: ViewGroup
|
private lateinit var progressContainer: ViewGroup
|
||||||
|
|
||||||
/**
|
|
||||||
* Image view that supports subsampling on zoom.
|
|
||||||
*/
|
|
||||||
private var subsamplingImageView: SubsamplingScaleImageView? = null
|
|
||||||
private var cropBorders: Boolean = false
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Simple image view only used on GIFs.
|
|
||||||
*/
|
|
||||||
private var imageView: ImageView? = null
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Retry button container used to allow retrying.
|
* Retry button container used to allow retrying.
|
||||||
*/
|
*/
|
||||||
@ -109,6 +90,10 @@ class WebtoonPageHolder(
|
|||||||
|
|
||||||
init {
|
init {
|
||||||
refreshLayoutParams()
|
refreshLayoutParams()
|
||||||
|
|
||||||
|
frame.onImageLoaded = { onImageDecoded() }
|
||||||
|
frame.onImageLoadError = { onImageDecodeError() }
|
||||||
|
frame.onScaleChanged = { viewer.activity.hideMenu() }
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -141,10 +126,7 @@ class WebtoonPageHolder(
|
|||||||
unsubscribeReadImageHeader()
|
unsubscribeReadImageHeader()
|
||||||
|
|
||||||
removeDecodeErrorLayout()
|
removeDecodeErrorLayout()
|
||||||
subsamplingImageView?.recycle()
|
frame.recycle()
|
||||||
subsamplingImageView?.isVisible = false
|
|
||||||
imageView?.clear()
|
|
||||||
imageView?.isVisible = false
|
|
||||||
progressIndicator.setProgress(0, animated = false)
|
progressIndicator.setProgress(0, animated = false)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -283,15 +265,15 @@ class WebtoonPageHolder(
|
|||||||
.subscribeOn(Schedulers.io())
|
.subscribeOn(Schedulers.io())
|
||||||
.observeOn(AndroidSchedulers.mainThread())
|
.observeOn(AndroidSchedulers.mainThread())
|
||||||
.doOnNext { isAnimated ->
|
.doOnNext { isAnimated ->
|
||||||
if (!isAnimated) {
|
frame.setImage(
|
||||||
val subsamplingView = initSubsamplingImageView()
|
openStream!!,
|
||||||
subsamplingView.isVisible = true
|
isAnimated,
|
||||||
subsamplingView.setImage(ImageSource.inputStream(openStream!!))
|
ReaderPageImageView.Config(
|
||||||
} else {
|
zoomDuration = viewer.config.doubleTapAnimDuration,
|
||||||
val imageView = initImageView()
|
minimumScaleType = SubsamplingScaleImageView.SCALE_TYPE_FIT_WIDTH,
|
||||||
imageView.isVisible = true
|
cropBorders = viewer.config.imageCropBorders
|
||||||
imageView.setImage(openStream!!)
|
)
|
||||||
}
|
)
|
||||||
}
|
}
|
||||||
// Keep the Rx stream alive to close the input stream only when unsubscribed
|
// Keep the Rx stream alive to close the input stream only when unsubscribed
|
||||||
.flatMap { Observable.never<Unit>() }
|
.flatMap { Observable.never<Unit>() }
|
||||||
@ -355,58 +337,6 @@ class WebtoonPageHolder(
|
|||||||
return progress
|
return progress
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Initializes a subsampling scale view.
|
|
||||||
*/
|
|
||||||
private fun initSubsamplingImageView(): SubsamplingScaleImageView {
|
|
||||||
val config = viewer.config
|
|
||||||
|
|
||||||
if (subsamplingImageView != null) {
|
|
||||||
if (config.imageCropBorders != cropBorders) {
|
|
||||||
cropBorders = config.imageCropBorders
|
|
||||||
subsamplingImageView!!.setCropBorders(config.imageCropBorders)
|
|
||||||
}
|
|
||||||
|
|
||||||
return subsamplingImageView!!
|
|
||||||
}
|
|
||||||
|
|
||||||
cropBorders = config.imageCropBorders
|
|
||||||
subsamplingImageView = WebtoonSubsamplingImageView(context).apply {
|
|
||||||
setMaxTileSize(viewer.activity.maxBitmapSize)
|
|
||||||
setPanLimit(SubsamplingScaleImageView.PAN_LIMIT_INSIDE)
|
|
||||||
setMinimumScaleType(SubsamplingScaleImageView.SCALE_TYPE_FIT_WIDTH)
|
|
||||||
setMinimumDpi(90)
|
|
||||||
setMinimumTileDpi(180)
|
|
||||||
setCropBorders(cropBorders)
|
|
||||||
setOnImageEventListener(
|
|
||||||
object : SubsamplingScaleImageView.DefaultOnImageEventListener() {
|
|
||||||
override fun onReady() {
|
|
||||||
onImageDecoded()
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onImageLoadError(e: Exception) {
|
|
||||||
onImageDecodeError()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
)
|
|
||||||
}
|
|
||||||
frame.addView(subsamplingImageView, MATCH_PARENT, MATCH_PARENT)
|
|
||||||
return subsamplingImageView!!
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Initializes an image view, used for GIFs.
|
|
||||||
*/
|
|
||||||
private fun initImageView(): ImageView {
|
|
||||||
if (imageView != null) return imageView!!
|
|
||||||
|
|
||||||
imageView = AppCompatImageView(context).apply {
|
|
||||||
adjustViewBounds = true
|
|
||||||
}
|
|
||||||
frame.addView(imageView, MATCH_PARENT, MATCH_PARENT)
|
|
||||||
return imageView!!
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Initializes a button to retry pages.
|
* Initializes a button to retry pages.
|
||||||
*/
|
*/
|
||||||
@ -500,29 +430,4 @@ class WebtoonPageHolder(
|
|||||||
decodeErrorLayout = null
|
decodeErrorLayout = null
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Extension method to set a [stream] into this ImageView.
|
|
||||||
*/
|
|
||||||
private fun ImageView.setImage(stream: InputStream) {
|
|
||||||
val request = ImageRequest.Builder(context)
|
|
||||||
.data(ByteBuffer.wrap(stream.readBytes()))
|
|
||||||
.memoryCachePolicy(CachePolicy.DISABLED)
|
|
||||||
.diskCachePolicy(CachePolicy.DISABLED)
|
|
||||||
.target(
|
|
||||||
onSuccess = { result ->
|
|
||||||
if (result is Animatable) {
|
|
||||||
result.start()
|
|
||||||
}
|
|
||||||
setImageDrawable(result)
|
|
||||||
onImageDecoded()
|
|
||||||
},
|
|
||||||
onError = {
|
|
||||||
onImageDecodeError()
|
|
||||||
}
|
|
||||||
)
|
|
||||||
.crossfade(false)
|
|
||||||
.build()
|
|
||||||
context.imageLoader.enqueue(request)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -23,10 +23,12 @@
|
|||||||
|
|
||||||
</com.google.android.material.appbar.AppBarLayout>
|
</com.google.android.material.appbar.AppBarLayout>
|
||||||
|
|
||||||
<com.davemorrissey.labs.subscaleview.SubsamplingScaleImageView
|
<eu.kanade.tachiyomi.ui.reader.viewer.ReaderPageImageView
|
||||||
android:id="@+id/full_cover"
|
android:id="@+id/container"
|
||||||
android:layout_width="0dp"
|
android:layout_width="0dp"
|
||||||
android:layout_height="0dp"
|
android:layout_height="0dp"
|
||||||
|
android:clipToPadding="false"
|
||||||
|
android:clipChildren="false"
|
||||||
app:layout_constraintBottom_toBottomOf="parent"
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
app:layout_constraintEnd_toEndOf="parent"
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
app:layout_constraintStart_toStartOf="parent"
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
Loading…
Reference in New Issue
Block a user