同步上游更改: @lhjgege

新漫画增加墨水屏模式 (#5181)
* 新漫画增加墨水屏模式
* 增加水平滚动动画开关
This commit is contained in:
lhjgege
2025-07-11 13:40:10 +08:00
committed by HapeLee
parent 329cdac774
commit 06dd2ac2c1
16 changed files with 463 additions and 29 deletions
@@ -104,6 +104,11 @@ object PreferKey {
const val enableMangaHorizontalScroll = "enableMangaHorizontalScroll" const val enableMangaHorizontalScroll = "enableMangaHorizontalScroll"
const val hideMangaTitle = "hideMangaTitle" const val hideMangaTitle = "hideMangaTitle"
const val mangaColorFilter = "mangaColorFilter" const val mangaColorFilter = "mangaColorFilter"
const val enableMangaEInk = "enableMangaEInk"
const val mangaEInkThreshold = "mangaEInkThreshold"
const val disableHorizontalAnimator = "disableHorizontalAnimator"
const val enableMangaGray = "enableMangaGray"
const val autoRefresh = "auto_refresh" const val autoRefresh = "auto_refresh"
const val defaultToRead = "defaultToRead" const val defaultToRead = "defaultToRead"
const val exportCharset = "exportCharset" const val exportCharset = "exportCharset"
@@ -496,7 +496,7 @@ object AppConfig : SharedPreferences.OnSharedPreferenceChangeListener {
val defaultHomePage get() = appCtx.getPrefString(PreferKey.defaultHomePage, "bookshelf") val defaultHomePage get() = appCtx.getPrefString(PreferKey.defaultHomePage, "bookshelf")
val updateToVariant get() = appCtx.getPrefString(PreferKey.updateToVariant, "default_version") val updateToVariant get() = appCtx.getPrefString(PreferKey.updateToVariant, "official_version")
val streamReadAloudAudio get() = appCtx.getPrefBoolean(PreferKey.streamReadAloudAudio, false) val streamReadAloudAudio get() = appCtx.getPrefBoolean(PreferKey.streamReadAloudAudio, false)
@@ -676,5 +676,30 @@ object AppConfig : SharedPreferences.OnSharedPreferenceChangeListener {
set(value) { set(value) {
appCtx.putPrefBoolean(PreferKey.hideMangaTitle, value) appCtx.putPrefBoolean(PreferKey.hideMangaTitle, value)
} }
//开启墨水屏模式
var enableMangaEInk
get() = appCtx.getPrefBoolean(PreferKey.enableMangaEInk, false)
set(value) {
appCtx.putPrefBoolean(PreferKey.enableMangaEInk, value)
}
var mangaEInkThreshold
get() = appCtx.getPrefInt(PreferKey.mangaEInkThreshold, 150)
set(value) {
appCtx.putPrefInt(PreferKey.mangaEInkThreshold, value)
}
var disableHorizontalAnimator
get() = appCtx.getPrefBoolean(PreferKey.disableHorizontalAnimator, false)
set(value) {
appCtx.putPrefBoolean(PreferKey.disableHorizontalAnimator, value)
}
var enableMangaGray
get() = appCtx.getPrefBoolean(PreferKey.enableMangaGray, false)
set(value) {
appCtx.putPrefBoolean(PreferKey.enableMangaGray, value)
}
} }
@@ -2,12 +2,14 @@ package io.legato.kazusa.model
import android.annotation.SuppressLint import android.annotation.SuppressLint
import android.content.Context import android.content.Context
import android.graphics.drawable.BitmapDrawable import android.graphics.Bitmap
import android.graphics.drawable.Drawable import android.graphics.drawable.Drawable
import androidx.annotation.Keep import androidx.annotation.Keep
import androidx.core.graphics.drawable.toDrawable
import com.bumptech.glide.Glide import com.bumptech.glide.Glide
import com.bumptech.glide.RequestBuilder import com.bumptech.glide.RequestBuilder
import com.bumptech.glide.load.DataSource import com.bumptech.glide.load.DataSource
import com.bumptech.glide.load.Transformation
import com.bumptech.glide.load.engine.DiskCacheStrategy import com.bumptech.glide.load.engine.DiskCacheStrategy
import com.bumptech.glide.load.engine.GlideException import com.bumptech.glide.load.engine.GlideException
import com.bumptech.glide.load.resource.bitmap.CenterCrop import com.bumptech.glide.load.resource.bitmap.CenterCrop
@@ -74,7 +76,7 @@ object BookCover {
return return
} }
defaultDrawable = kotlin.runCatching { defaultDrawable = kotlin.runCatching {
BitmapDrawable(appCtx.resources, BitmapUtils.decodeBitmap(path, 600, 900)) BitmapUtils.decodeBitmap(path, 600, 900).toDrawable(appCtx.resources)
}.getOrDefault(appCtx.resources.getDrawable(R.drawable.image_cover_default, null)) }.getOrDefault(appCtx.resources.getDrawable(R.drawable.image_cover_default, null))
} }
@@ -87,7 +89,7 @@ object BookCover {
path: String?, path: String?,
loadOnlyWifi: Boolean = false, loadOnlyWifi: Boolean = false,
sourceOrigin: String? = null, sourceOrigin: String? = null,
onLoadFinish: (() -> Unit)? = null onLoadFinish: (() -> Unit)? = null,
): RequestBuilder<Drawable> { ): RequestBuilder<Drawable> {
if (AppConfig.useDefaultCover) { if (AppConfig.useDefaultCover) {
return ImageLoader.load(context, defaultDrawable) return ImageLoader.load(context, defaultDrawable)
@@ -105,7 +107,7 @@ object BookCover {
e: GlideException?, e: GlideException?,
model: Any?, model: Any?,
target: Target<Drawable?>, target: Target<Drawable?>,
isFirstResource: Boolean isFirstResource: Boolean,
): Boolean { ): Boolean {
onLoadFinish.invoke() onLoadFinish.invoke()
return false return false
@@ -116,7 +118,7 @@ object BookCover {
model: Any, model: Any,
target: Target<Drawable?>?, target: Target<Drawable?>?,
dataSource: DataSource, dataSource: DataSource,
isFirstResource: Boolean isFirstResource: Boolean,
): Boolean { ): Boolean {
onLoadFinish.invoke() onLoadFinish.invoke()
return false return false
@@ -133,11 +135,13 @@ object BookCover {
/** /**
* 加载漫画图片 * 加载漫画图片
*/ */
@SuppressLint("CheckResult")
fun loadManga( fun loadManga(
context: Context, context: Context,
path: String?, path: String?,
loadOnlyWifi: Boolean = false, loadOnlyWifi: Boolean = false,
sourceOrigin: String? = null, sourceOrigin: String? = null,
transformation: Transformation<Bitmap>? = null,
): RequestBuilder<Drawable> { ): RequestBuilder<Drawable> {
var options = RequestOptions().set(OkHttpModelLoader.loadOnlyWifiOption, loadOnlyWifi) var options = RequestOptions().set(OkHttpModelLoader.loadOnlyWifiOption, loadOnlyWifi)
.set(OkHttpModelLoader.mangaOption, true) .set(OkHttpModelLoader.mangaOption, true)
@@ -148,7 +152,12 @@ object BookCover {
.apply(options) .apply(options)
.override(context.resources.displayMetrics.widthPixels, SIZE_ORIGINAL) .override(context.resources.displayMetrics.widthPixels, SIZE_ORIGINAL)
.diskCacheStrategy(DiskCacheStrategy.ALL) .diskCacheStrategy(DiskCacheStrategy.ALL)
.skipMemoryCache(true) .skipMemoryCache(true).let {
if (transformation != null) {
it.transform(transformation)
}
it
}
} }
fun preloadManga( fun preloadManga(
@@ -6,8 +6,11 @@ import android.view.View
import io.legato.kazusa.BuildConfig import io.legato.kazusa.BuildConfig
import io.legato.kazusa.R import io.legato.kazusa.R
import io.legato.kazusa.base.BaseBottomSheetDialogFragment import io.legato.kazusa.base.BaseBottomSheetDialogFragment
import io.legato.kazusa.constant.AppConst
import io.legato.kazusa.databinding.DialogUpdateBinding import io.legato.kazusa.databinding.DialogUpdateBinding
import io.legato.kazusa.help.config.AppConfig
import io.legato.kazusa.help.update.AppUpdate import io.legato.kazusa.help.update.AppUpdate
import io.legato.kazusa.help.update.AppVariant
import io.legato.kazusa.model.Download import io.legato.kazusa.model.Download
import io.legato.kazusa.utils.toastOnUi import io.legato.kazusa.utils.toastOnUi
import io.legato.kazusa.utils.viewbindingdelegate.viewBinding import io.legato.kazusa.utils.viewbindingdelegate.viewBinding
@@ -27,6 +30,14 @@ class UpdateDialog() : BaseBottomSheetDialogFragment(R.layout.dialog_update) {
} }
} }
private val checkVariant: AppVariant
get() = when (AppConfig.updateToVariant) {
"official_version" -> AppVariant.OFFICIAL
"beta_release_version" -> AppVariant.BETA_RELEASE
"beta_releaseA_version" -> AppVariant.BETA_RELEASEA
else -> AppConst.appInfo.appVariant
}
val binding by viewBinding(DialogUpdateBinding::bind) val binding by viewBinding(DialogUpdateBinding::bind)
override fun onStart() { override fun onStart() {
@@ -41,6 +52,7 @@ class UpdateDialog() : BaseBottomSheetDialogFragment(R.layout.dialog_update) {
binding.tvAbi.text = Build.SUPPORTED_ABIS.firstOrNull() ?: "unknown" binding.tvAbi.text = Build.SUPPORTED_ABIS.firstOrNull() ?: "unknown"
binding.tvUrl.text = arguments?.getString("url") binding.tvUrl.text = arguments?.getString("url")
binding.tvVariable.text = checkVariant.toString()
val updateBody = arguments?.getString("updateBody") val updateBody = arguments?.getString("updateBody")
if (updateBody == null) { if (updateBody == null) {
@@ -5,7 +5,6 @@ import android.content.Intent
import android.os.Build import android.os.Build
import android.os.Bundle import android.os.Bundle
import android.os.Looper import android.os.Looper
import android.os.PersistableBundle
import android.view.KeyEvent import android.view.KeyEvent
import android.view.Menu import android.view.Menu
import android.view.MenuItem import android.view.MenuItem
@@ -44,6 +43,7 @@ import io.legato.kazusa.ui.book.changesource.ChangeBookSourceDialog
import io.legato.kazusa.ui.book.info.BookInfoActivity import io.legato.kazusa.ui.book.info.BookInfoActivity
import io.legato.kazusa.ui.book.manga.config.MangaColorFilterConfig import io.legato.kazusa.ui.book.manga.config.MangaColorFilterConfig
import io.legato.kazusa.ui.book.manga.config.MangaColorFilterDialog import io.legato.kazusa.ui.book.manga.config.MangaColorFilterDialog
import io.legato.kazusa.ui.book.manga.config.MangaEpaperDialog
import io.legato.kazusa.ui.book.manga.config.MangaFooterConfig import io.legato.kazusa.ui.book.manga.config.MangaFooterConfig
import io.legato.kazusa.ui.book.manga.config.MangaFooterSettingDialog import io.legato.kazusa.ui.book.manga.config.MangaFooterSettingDialog
import io.legato.kazusa.ui.book.manga.entities.BaseMangaPage import io.legato.kazusa.ui.book.manga.entities.BaseMangaPage
@@ -63,7 +63,6 @@ import io.legato.kazusa.utils.canScroll
import io.legato.kazusa.utils.fastBinarySearch import io.legato.kazusa.utils.fastBinarySearch
import io.legato.kazusa.utils.findCenterViewPosition import io.legato.kazusa.utils.findCenterViewPosition
import io.legato.kazusa.utils.fromJsonObject import io.legato.kazusa.utils.fromJsonObject
import io.legato.kazusa.utils.getCompatColor
import io.legato.kazusa.utils.gone import io.legato.kazusa.utils.gone
import io.legato.kazusa.utils.observeEvent import io.legato.kazusa.utils.observeEvent
import io.legato.kazusa.utils.showDialogFragment import io.legato.kazusa.utils.showDialogFragment
@@ -79,7 +78,7 @@ import kotlin.math.ceil
class ReadMangaActivity : VMBaseActivity<ActivityMangaBinding, ReadMangaViewModel>(), class ReadMangaActivity : VMBaseActivity<ActivityMangaBinding, ReadMangaViewModel>(),
ReadManga.Callback, ChangeBookSourceDialog.CallBack, MangaMenu.CallBack, ReadManga.Callback, ChangeBookSourceDialog.CallBack, MangaMenu.CallBack,
MangaColorFilterDialog.Callback, ScrollTimer.ScrollCallback { MangaColorFilterDialog.Callback, ScrollTimer.ScrollCallback, MangaEpaperDialog.Callback {
private val mLayoutManager by lazy { private val mLayoutManager by lazy {
MangaLayoutManager(this) MangaLayoutManager(this)
@@ -191,6 +190,8 @@ class ReadMangaActivity : VMBaseActivity<ActivityMangaBinding, ReadMangaViewMode
?: MangaColorFilterConfig() ?: MangaColorFilterConfig()
mAdapter.run { mAdapter.run {
setMangaImageColorFilter(mangaColorFilter) setMangaImageColorFilter(mangaColorFilter)
enableMangaEInk(AppConfig.enableMangaEInk, AppConfig.mangaEInkThreshold)
enableGray(AppConfig.enableMangaGray)
} }
setHorizontalScroll(AppConfig.enableMangaHorizontalScroll) setHorizontalScroll(AppConfig.enableMangaHorizontalScroll)
binding.recyclerView.run { binding.recyclerView.run {
@@ -541,6 +542,7 @@ class ReadMangaActivity : VMBaseActivity<ActivityMangaBinding, ReadMangaViewMode
R.id.menu_enable_horizontal_scroll -> { R.id.menu_enable_horizontal_scroll -> {
item.isChecked = !item.isChecked item.isChecked = !item.isChecked
AppConfig.enableMangaHorizontalScroll = item.isChecked AppConfig.enableMangaHorizontalScroll = item.isChecked
mMenu?.findItem(R.id.menu_disable_horizontal_animation)?.isVisible = item.isChecked
setHorizontalScroll(item.isChecked) setHorizontalScroll(item.isChecked)
mAdapter.notifyDataSetChanged() mAdapter.notifyDataSetChanged()
} }
@@ -570,6 +572,38 @@ class ReadMangaActivity : VMBaseActivity<ActivityMangaBinding, ReadMangaViewMode
AppConfig.hideMangaTitle = item.isChecked AppConfig.hideMangaTitle = item.isChecked
ReadManga.loadContent() ReadManga.loadContent()
} }
R.id.menu_epaper_manga -> {
item.isChecked = !item.isChecked
AppConfig.enableMangaEInk = item.isChecked
mMenu?.findItem(R.id.menu_gray_manga)?.isChecked = false
AppConfig.enableMangaGray = false
mMenu?.findItem(R.id.menu_epaper_manga_setting)?.isVisible = item.isChecked
mAdapter.enableMangaEInk(item.isChecked, AppConfig.mangaEInkThreshold)
}
R.id.menu_epaper_manga_setting -> {
showDialogFragment(MangaEpaperDialog())
}
R.id.menu_disable_horizontal_animation -> {
item.isChecked = !item.isChecked
AppConfig.disableHorizontalAnimator = item.isChecked
if (item.isChecked) {
mPagerSnapHelper.attachToRecyclerView(null)
} else {
mPagerSnapHelper.attachToRecyclerView(binding.recyclerView)
}
}
R.id.menu_gray_manga -> {
item.isChecked = !item.isChecked
AppConfig.enableMangaGray = item.isChecked
mMenu?.findItem(R.id.menu_epaper_manga)?.isChecked = false
AppConfig.enableMangaEInk = false
mMenu?.findItem(R.id.menu_epaper_manga_setting)?.isVisible = false
mAdapter.enableGray(item.isChecked)
}
} }
return super.onCompatOptionsItemSelected(item) return super.onCompatOptionsItemSelected(item)
} }
@@ -626,6 +660,11 @@ class ReadMangaActivity : VMBaseActivity<ActivityMangaBinding, ReadMangaViewMode
if (enable) { if (enable) {
if (!enableAutoScroll) { if (!enableAutoScroll) {
mPagerSnapHelper.attachToRecyclerView(binding.recyclerView) mPagerSnapHelper.attachToRecyclerView(binding.recyclerView)
if (AppConfig.disableHorizontalAnimator) {
mPagerSnapHelper.attachToRecyclerView(null)
} else {
mPagerSnapHelper.attachToRecyclerView(binding.recyclerView)
}
} }
mLayoutManager.orientation = LinearLayoutManager.HORIZONTAL mLayoutManager.orientation = LinearLayoutManager.HORIZONTAL
} else { } else {
@@ -646,6 +685,14 @@ class ReadMangaActivity : VMBaseActivity<ActivityMangaBinding, ReadMangaViewMode
menu.findItem(R.id.menu_enable_horizontal_scroll).isChecked = menu.findItem(R.id.menu_enable_horizontal_scroll).isChecked =
AppConfig.enableMangaHorizontalScroll AppConfig.enableMangaHorizontalScroll
menu.findItem(R.id.menu_hide_manga_title).isChecked = AppConfig.hideMangaTitle menu.findItem(R.id.menu_hide_manga_title).isChecked = AppConfig.hideMangaTitle
menu.findItem(R.id.menu_epaper_manga).isChecked = AppConfig.enableMangaEInk
menu.findItem(R.id.menu_epaper_manga_setting).isVisible = AppConfig.enableMangaEInk
menu.findItem(R.id.menu_disable_horizontal_animation).run {
isVisible =
AppConfig.enableMangaHorizontalScroll
isChecked = AppConfig.disableHorizontalAnimator
}
menu.findItem(R.id.menu_gray_manga).isChecked = AppConfig.enableMangaGray
} }
private fun setDisableMangaScale(disable: Boolean) { private fun setDisableMangaScale(disable: Boolean) {
@@ -784,4 +831,8 @@ class ReadMangaActivity : VMBaseActivity<ActivityMangaBinding, ReadMangaViewMode
} }
return super.onKeyDown(keyCode, event) return super.onKeyDown(keyCode, event)
} }
override fun updateEepaper(value: Int) {
mAdapter.updateThreshold(value)
}
} }
@@ -0,0 +1,49 @@
package io.legato.kazusa.ui.book.manga.config
import android.content.DialogInterface
import android.os.Bundle
import android.view.View
import io.legato.kazusa.R
import io.legato.kazusa.base.BaseBottomSheetDialogFragment
import io.legato.kazusa.databinding.DialogMangaEpaperBinding
import io.legato.kazusa.help.config.AppConfig
import io.legato.kazusa.utils.viewbindingdelegate.viewBinding
class MangaEpaperDialog : BaseBottomSheetDialogFragment(R.layout.dialog_manga_epaper) {
private val binding by viewBinding(DialogMangaEpaperBinding::bind)
private val callback get() = activity as? Callback
private var mMangaEInkThreshold = 150
// override fun onStart() {
// super.onStart()
// dialog?.window?.clearFlags(WindowManager.LayoutParams.FLAG_DIM_BEHIND)
// setLayout(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT)
// }
override fun onFragmentCreated(view: View, savedInstanceState: Bundle?) {
initData()
initView()
}
private fun initData() {
binding.dsbEpaper.progress = AppConfig.mangaEInkThreshold
}
private fun initView() {
binding.dsbEpaper.onChanged = {
mMangaEInkThreshold = it
callback?.updateEepaper(it)
}
}
override fun onDismiss(dialog: DialogInterface) {
super.onDismiss(dialog)
AppConfig.mangaEInkThreshold = mMangaEInkThreshold
}
interface Callback {
fun updateEepaper(value: Int)
}
}
@@ -0,0 +1,80 @@
package io.legato.kazusa.ui.book.manga.entities
import android.graphics.Bitmap
import android.graphics.Canvas
import android.graphics.Color
import android.graphics.ColorMatrix
import android.graphics.ColorMatrixColorFilter
import android.graphics.Paint
import androidx.annotation.IntRange
import com.bumptech.glide.load.engine.bitmap_recycle.BitmapPool
import com.bumptech.glide.load.resource.bitmap.BitmapTransformation
import java.security.MessageDigest
/**
* 墨水屏图片转换器。
* 将彩色图片转换为灰度图,并可选择进行简单的二值化处理,以提高墨水屏显示效果。
*
* @param threshold 二值化的阈值(0-255)。低于此值的像素变为黑色,高于此值的像素变为白色。
* 仅当applyBinarization为true时有效。
*/
class EpaperTransformation(
@IntRange(0, 255) private val threshold: Int = 128,
) : BitmapTransformation() {
private val ID =
"io.legado.app.model.EpaperTransformation.${threshold}"
private val ID_BYTES = ID.toByteArray(CHARSET)
override fun transform(
pool: BitmapPool,
toTransform: Bitmap,
outWidth: Int,
outHeight: Int,
): Bitmap {
val resultBitmap = pool.get(outWidth, outHeight, Bitmap.Config.ARGB_8888)
val canvas = Canvas(resultBitmap)
val paint = Paint()
val colorMatrix = ColorMatrix()
colorMatrix.setSaturation(0f)
val filter = ColorMatrixColorFilter(colorMatrix)
paint.colorFilter = filter
canvas.drawBitmap(toTransform, 0f, 0f, paint)
val pixels = IntArray(outWidth * outHeight)
resultBitmap.getPixels(pixels, 0, outWidth, 0, 0, outWidth, outHeight)
for (i in pixels.indices) {
val pixel = pixels[i]
val gray = Color.red(pixel)
pixels[i] =
if (gray < threshold) Color.BLACK else Color.WHITE
}
resultBitmap.setPixels(pixels, 0, outWidth, 0, 0, outWidth, outHeight)
return resultBitmap
}
override fun updateDiskCacheKey(messageDigest: MessageDigest) {
messageDigest.update(ID_BYTES)
}
override fun equals(other: Any?): Boolean {
if (this === other) return true
if (javaClass != other?.javaClass) return false
other as EpaperTransformation
if (threshold != other.threshold) return false
if (ID != other.ID) return false
return true
}
override fun hashCode(): Int {
var result = 31 + threshold
result = 31 * result + ID.hashCode()
return result
}
}
@@ -0,0 +1,57 @@
package io.legato.kazusa.ui.book.manga.entities
import android.graphics.Bitmap
import android.graphics.Canvas
import android.graphics.ColorMatrix
import android.graphics.ColorMatrixColorFilter
import android.graphics.Paint
import com.bumptech.glide.load.engine.bitmap_recycle.BitmapPool
import com.bumptech.glide.load.resource.bitmap.BitmapTransformation
import java.nio.charset.StandardCharsets
import java.security.MessageDigest
class GrayscaleTransformation : BitmapTransformation() {
private val ID = "io.legado.app.model.GrayscaleTransformation"
private val ID_BYTES = ID.toByteArray(StandardCharsets.UTF_8)
override fun transform(
pool: BitmapPool,
toTransform: Bitmap,
outWidth: Int,
outHeight: Int,
): Bitmap {
val resultBitmap = pool.get(outWidth, outHeight, Bitmap.Config.ARGB_8888)
val canvas = Canvas(resultBitmap)
val paint = Paint()
val matrix = ColorMatrix(
floatArrayOf(
0.299f, 0.587f, 0.114f, 0f, 0f,
0.299f, 0.587f, 0.114f, 0f, 0f,
0.299f, 0.587f, 0.114f, 0f, 0f,
0f, 0f, 0f, 1f, 0f
)
)
val filter = ColorMatrixColorFilter(matrix)
paint.colorFilter = filter
canvas.drawBitmap(toTransform, 0f, 0f, paint)
return resultBitmap
}
override fun updateDiskCacheKey(messageDigest: MessageDigest) {
messageDigest.update(ID_BYTES)
}
override fun equals(other: Any?): Boolean {
if (this === other) return true
if (javaClass != other?.javaClass) return false
other as GrayscaleTransformation
return ID == other.ID
}
override fun hashCode(): Int {
return ID.hashCode()
}
}
@@ -8,6 +8,7 @@ import android.view.LayoutInflater
import android.view.ViewGroup import android.view.ViewGroup
import android.view.ViewGroup.LayoutParams.MATCH_PARENT import android.view.ViewGroup.LayoutParams.MATCH_PARENT
import androidx.annotation.IntRange import androidx.annotation.IntRange
import androidx.core.util.size
import androidx.core.view.updateLayoutParams import androidx.core.view.updateLayoutParams
import androidx.recyclerview.widget.AsyncListDiffer import androidx.recyclerview.widget.AsyncListDiffer
import androidx.recyclerview.widget.DiffUtil import androidx.recyclerview.widget.DiffUtil
@@ -16,6 +17,7 @@ import androidx.viewbinding.ViewBinding
import com.bumptech.glide.Glide import com.bumptech.glide.Glide
import com.bumptech.glide.ListPreloader.PreloadModelProvider import com.bumptech.glide.ListPreloader.PreloadModelProvider
import com.bumptech.glide.RequestBuilder import com.bumptech.glide.RequestBuilder
import com.bumptech.glide.load.resource.bitmap.BitmapTransformation
import io.legato.kazusa.base.adapter.ItemViewHolder import io.legato.kazusa.base.adapter.ItemViewHolder
import io.legato.kazusa.base.adapter.RecyclerAdapter.Companion.TYPE_FOOTER_VIEW import io.legato.kazusa.base.adapter.RecyclerAdapter.Companion.TYPE_FOOTER_VIEW
import io.legato.kazusa.databinding.ItemBookMangaEdgeBinding import io.legato.kazusa.databinding.ItemBookMangaEdgeBinding
@@ -24,6 +26,8 @@ import io.legato.kazusa.help.glide.progress.ProgressManager
import io.legato.kazusa.model.BookCover import io.legato.kazusa.model.BookCover
import io.legato.kazusa.model.ReadManga import io.legato.kazusa.model.ReadManga
import io.legato.kazusa.ui.book.manga.config.MangaColorFilterConfig import io.legato.kazusa.ui.book.manga.config.MangaColorFilterConfig
import io.legato.kazusa.ui.book.manga.entities.EpaperTransformation
import io.legato.kazusa.ui.book.manga.entities.GrayscaleTransformation
import io.legato.kazusa.ui.book.manga.entities.MangaPage import io.legato.kazusa.ui.book.manga.entities.MangaPage
import io.legato.kazusa.ui.book.manga.entities.ReaderLoading import io.legato.kazusa.ui.book.manga.entities.ReaderLoading
import io.legato.kazusa.utils.dpToPx import io.legato.kazusa.utils.dpToPx
@@ -34,6 +38,8 @@ class MangaAdapter(private val context: Context) :
private val inflater: LayoutInflater = LayoutInflater.from(context) private val inflater: LayoutInflater = LayoutInflater.from(context)
private lateinit var mConfig: MangaColorFilterConfig private lateinit var mConfig: MangaColorFilterConfig
private var mTransformation: BitmapTransformation? = null
private var currentMangaEInkThreshold = 0
companion object { companion object {
private const val LOADING_VIEW = 0 private const val LOADING_VIEW = 0
@@ -91,7 +97,7 @@ class MangaAdapter(private val context: Context) :
if (item is MangaPage) { if (item is MangaPage) {
val isLastImage = item.imageCount > 0 && item.index == item.imageCount - 1 val isLastImage = item.imageCount > 0 && item.index == item.imageCount - 1
loadImageWithRetry( loadImageWithRetry(
item.mImageUrl, isHorizontal, isLastImage item.mImageUrl, isHorizontal, isLastImage, mTransformation
) )
} }
} }
@@ -100,7 +106,7 @@ class MangaAdapter(private val context: Context) :
fun onBind(item: MangaPage) { fun onBind(item: MangaPage) {
setImageColorFilter() setImageColorFilter()
val isLastImage = item.imageCount > 0 && item.index == item.imageCount - 1 val isLastImage = item.imageCount > 0 && item.index == item.imageCount - 1
loadImageWithRetry(item.mImageUrl, isHorizontal, isLastImage) loadImageWithRetry(item.mImageUrl, isHorizontal, isLastImage, mTransformation)
} }
fun setImageColorFilter() { fun setImageColorFilter() {
@@ -167,7 +173,7 @@ class MangaAdapter(private val context: Context) :
} }
} }
fun getFooterCount() = footerItems.size() fun getFooterCount() = footerItems.size
private fun isFooter(position: Int) = position >= getActualItemCount() private fun isFooter(position: Int) = position >= getActualItemCount()
@@ -199,8 +205,8 @@ class MangaAdapter(private val context: Context) :
@Synchronized @Synchronized
fun addFooterView(footer: ((parent: ViewGroup) -> ViewBinding)) { fun addFooterView(footer: ((parent: ViewGroup) -> ViewBinding)) {
kotlin.runCatching { kotlin.runCatching {
val index = getActualItemCount() + footerItems.size() val index = getActualItemCount() + footerItems.size
footerItems.put(TYPE_FOOTER_VIEW + footerItems.size(), footer) footerItems.put(TYPE_FOOTER_VIEW + footerItems.size, footer)
notifyItemInserted(index) notifyItemInserted(index)
} }
} }
@@ -244,4 +250,32 @@ class MangaAdapter(private val context: Context) :
notifyItemRangeChanged(0, itemCount) notifyItemRangeChanged(0, itemCount)
} }
fun enableMangaEInk(enable: Boolean, value: Int) {
if (enable) {
currentMangaEInkThreshold = value
mTransformation = EpaperTransformation(currentMangaEInkThreshold)
} else {
mTransformation = null
}
notifyItemRangeChanged(0, itemCount)
}
fun updateThreshold(mangaEInkThreshold: Int) {
if (currentMangaEInkThreshold != mangaEInkThreshold) {
currentMangaEInkThreshold = mangaEInkThreshold
mTransformation = EpaperTransformation(currentMangaEInkThreshold)
notifyItemRangeChanged(0, itemCount)
}
}
//开启灰色图片
fun enableGray(enable: Boolean) {
mTransformation = if (enable) {
GrayscaleTransformation()
} else {
null
}
notifyItemRangeChanged(0, itemCount)
}
} }
@@ -2,6 +2,7 @@ package io.legato.kazusa.ui.book.manga.recyclerview
import android.annotation.SuppressLint import android.annotation.SuppressLint
import android.content.Context import android.content.Context
import android.graphics.Bitmap
import android.graphics.drawable.Drawable import android.graphics.drawable.Drawable
import android.view.Gravity import android.view.Gravity
import android.view.ViewGroup import android.view.ViewGroup
@@ -17,6 +18,7 @@ import androidx.core.view.updateLayoutParams
import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
import androidx.viewbinding.ViewBinding import androidx.viewbinding.ViewBinding
import com.bumptech.glide.load.DataSource import com.bumptech.glide.load.DataSource
import com.bumptech.glide.load.Transformation
import com.bumptech.glide.load.engine.GlideException import com.bumptech.glide.load.engine.GlideException
import com.bumptech.glide.request.RequestListener import com.bumptech.glide.request.RequestListener
import com.bumptech.glide.request.target.Target import com.bumptech.glide.request.target.Target
@@ -51,7 +53,12 @@ open class MangaVH<VB : ViewBinding>(val binding: VB, private val context: Conte
} }
@SuppressLint("CheckResult") @SuppressLint("CheckResult")
fun loadImageWithRetry(imageUrl: String, isHorizontal: Boolean, isLastImage: Boolean) { fun loadImageWithRetry(
imageUrl: String,
isHorizontal: Boolean,
isLastImage: Boolean,
transformation: Transformation<Bitmap>?
) {
mFlProgress.isVisible = true mFlProgress.isVisible = true
mLoading.isVisible = true mLoading.isVisible = true
mRetry?.isGone = true mRetry?.isGone = true
@@ -67,6 +74,7 @@ open class MangaVH<VB : ViewBinding>(val binding: VB, private val context: Conte
context, context,
imageUrl, imageUrl,
sourceOrigin = ReadManga.book?.origin, sourceOrigin = ReadManga.book?.origin,
transformation = transformation
).addListener(object : RequestListener<Drawable> { ).addListener(object : RequestListener<Drawable> {
override fun onLoadFailed( override fun onLoadFailed(
e: GlideException?, e: GlideException?,
@@ -14,7 +14,7 @@
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:paddingBottom="8dp" android:paddingBottom="8dp"
android:text="滤镜" android:text="@string/manga_color_filter"
android:textSize="18sp" /> android:textSize="18sp" />
<io.legato.kazusa.ui.widget.DetailSeekBar <io.legato.kazusa.ui.widget.DetailSeekBar
@@ -0,0 +1,29 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.core.widget.NestedScrollView xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:padding="16dp">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:paddingBottom="8dp"
android:text="@string/manga_epaper_stting"
android:textSize="18sp" />
<io.legato.kazusa.ui.widget.DetailSeekBar
android:id="@+id/dsb_epaper"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:max="255"
app:title="@string/manga_epaper_value" />
</LinearLayout>
</androidx.core.widget.NestedScrollView>
+35 -4
View File
@@ -98,21 +98,52 @@
</com.google.android.material.card.MaterialCardView> </com.google.android.material.card.MaterialCardView>
</LinearLayout> </LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center_vertical"
android:orientation="horizontal">
<com.google.android.material.card.MaterialCardView <com.google.android.material.card.MaterialCardView
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
app:strokeWidth="0dp" app:cardBackgroundColor="?attr/colorPrimaryContainer"
app:cardBackgroundColor="?attr/colorPrimaryContainer"> app:strokeWidth="0dp">
<TextView <TextView
android:id="@+id/tv_abi" android:id="@+id/tv_abi"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:padding="8dp" android:padding="8dp"
android:text="ABI: arm64-v8a" android:text="ABI: arm64-v8a"
android:textColor="?attr/colorOnSurfaceVariant" android:textAppearance="?attr/textAppearanceBodySmall"
android:textAppearance="?attr/textAppearanceBodySmall" /> android:textColor="?attr/colorOnSurfaceVariant" />
</com.google.android.material.card.MaterialCardView> </com.google.android.material.card.MaterialCardView>
<View
android:layout_width="0dp"
android:layout_height="1dp"
android:layout_marginHorizontal="8dp"
android:layout_weight="1"
android:background="?attr/colorSecondary" />
<com.google.android.material.card.MaterialCardView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:cardBackgroundColor="?attr/colorPrimaryContainer"
app:strokeWidth="0dp">
<TextView
android:id="@+id/tv_variable"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:padding="8dp"
android:text="123"
android:textAppearance="?attr/textAppearanceBodyMedium" />
</com.google.android.material.card.MaterialCardView>
</LinearLayout>
<TextView <TextView
android:id="@+id/tv_url" android:id="@+id/tv_url"
android:layout_width="match_parent" android:layout_width="match_parent"
+28
View File
@@ -71,6 +71,14 @@
android:title="@string/enable_manga_horizontal_scroll" android:title="@string/enable_manga_horizontal_scroll"
app:showAsAction="never" /> app:showAsAction="never" />
<item
android:id="@+id/menu_disable_horizontal_animation"
android:checkable="true"
android:checked="false"
android:title="@string/disable_horizontal_animation"
android:visible="false"
app:showAsAction="never" />
<item <item
android:id="@+id/menu_manga_footer_config" android:id="@+id/menu_manga_footer_config"
android:title="@string/manga_footer_config" android:title="@string/manga_footer_config"
@@ -88,4 +96,24 @@
android:title="@string/hide_manga_title" android:title="@string/hide_manga_title"
app:showAsAction="never" /> app:showAsAction="never" />
<item
android:id="@+id/menu_epaper_manga"
android:checkable="true"
android:checked="false"
android:title="@string/manga_epaper"
app:showAsAction="never" />
<item
android:id="@+id/menu_epaper_manga_setting"
android:title="@string/manga_epaper_stting"
android:visible="false"
app:showAsAction="never" />
<item
android:id="@+id/menu_gray_manga"
android:checkable="true"
android:checked="false"
android:title="@string/enable_manga_gray"
app:showAsAction="never" />
</menu> </menu>
+10
View File
@@ -1217,8 +1217,18 @@
<string name="manga_color_filter">滤镜</string> <string name="manga_color_filter">滤镜</string>
<string name="hide_manga_title">隐藏漫画列表标题</string> <string name="hide_manga_title">隐藏漫画列表标题</string>
<string name="refresh_explore">刷新发现</string> <string name="refresh_explore">刷新发现</string>
<string name="ungroup">未分组</string>
<string name="dynamic_colors">动态取色</string>
<string name="change_icon_success">图标已修改</string> <string name="change_icon_success">图标已修改</string>
<string name="replace_rule_title_setting">配置替换规则</string>
<string name="more_setting">更多设置</string>
<string name="read_chapter_total">共 %d 章</string> <string name="read_chapter_total">共 %d 章</string>
<string name="read_chapter_index">已读 %d 章</string> <string name="read_chapter_index">已读 %d 章</string>
<string name="manga_epaper">墨水屏</string>
<string name="manga_epaper_stting">墨水屏设置</string>
<string name="manga_epaper_value">阈值</string>
<string name="disable_horizontal_animation">禁用水平滚动动画</string>
<string name="enable_manga_gray">开启图片灰色</string>
</resources> </resources>
+6
View File
@@ -1226,4 +1226,10 @@
<string name="read_chapter_total">共 %d 章</string> <string name="read_chapter_total">共 %d 章</string>
<string name="read_chapter_index">已读 %d 章</string> <string name="read_chapter_index">已读 %d 章</string>
<string name="manga_epaper">墨水屏</string>
<string name="manga_epaper_stting">墨水屏设置</string>
<string name="manga_epaper_value">阈值</string>
<string name="disable_horizontal_animation">禁用水平滚动动画</string>
<string name="enable_manga_gray">开启图片灰色</string>
</resources> </resources>