新漫画增加墨水屏模式 (#5181)

* 新漫画增加墨水屏模式

* 增加水平滚动动画开关

* ....

* ....

* ....

* ....

* ....

* ....
This commit is contained in:
lhjgege
2025-06-17 20:52:29 +08:00
committed by GitHub
parent cdc916a49f
commit cbdfbaffe1
20 changed files with 424 additions and 15 deletions
@@ -102,6 +102,10 @@ 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"
@@ -180,5 +184,4 @@ object PreferKey {
const val showReadTitleAddition = "showReadTitleAddition" const val showReadTitleAddition = "showReadTitleAddition"
const val readBarStyleFollowPage = "readBarStyleFollowPage" const val readBarStyleFollowPage = "readBarStyleFollowPage"
const val contentSelectSpeakMod = "contentReadAloudMod" const val contentSelectSpeakMod = "contentReadAloudMod"
} }
@@ -668,5 +668,31 @@ 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.legado.app.model
import android.annotation.SuppressLint import android.annotation.SuppressLint
import android.content.Context import android.content.Context
import android.graphics.Bitmap
import android.graphics.drawable.BitmapDrawable import android.graphics.drawable.BitmapDrawable
import android.graphics.drawable.Drawable import android.graphics.drawable.Drawable
import androidx.annotation.Keep import androidx.annotation.Keep
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
@@ -29,6 +31,7 @@ import io.legado.app.help.glide.OkHttpModelLoader
import io.legado.app.model.analyzeRule.AnalyzeRule import io.legado.app.model.analyzeRule.AnalyzeRule
import io.legado.app.model.analyzeRule.AnalyzeRule.Companion.setCoroutineContext import io.legado.app.model.analyzeRule.AnalyzeRule.Companion.setCoroutineContext
import io.legado.app.model.analyzeRule.AnalyzeUrl import io.legado.app.model.analyzeRule.AnalyzeUrl
import io.legado.app.ui.book.manga.entities.EpaperTransformation
import io.legado.app.utils.BitmapUtils import io.legado.app.utils.BitmapUtils
import io.legado.app.utils.GSON import io.legado.app.utils.GSON
import io.legado.app.utils.fromJsonObject import io.legado.app.utils.fromJsonObject
@@ -86,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)
@@ -104,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
@@ -115,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
@@ -130,11 +133,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)
@@ -145,7 +150,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(
@@ -43,6 +43,7 @@ import io.legado.app.ui.book.changesource.ChangeBookSourceDialog
import io.legado.app.ui.book.info.BookInfoActivity import io.legado.app.ui.book.info.BookInfoActivity
import io.legado.app.ui.book.manga.config.MangaColorFilterConfig import io.legado.app.ui.book.manga.config.MangaColorFilterConfig
import io.legado.app.ui.book.manga.config.MangaColorFilterDialog import io.legado.app.ui.book.manga.config.MangaColorFilterDialog
import io.legado.app.ui.book.manga.config.MangaEpaperDialog
import io.legado.app.ui.book.manga.config.MangaFooterConfig import io.legado.app.ui.book.manga.config.MangaFooterConfig
import io.legado.app.ui.book.manga.config.MangaFooterSettingDialog import io.legado.app.ui.book.manga.config.MangaFooterSettingDialog
import io.legado.app.ui.book.manga.entities.BaseMangaPage import io.legado.app.ui.book.manga.entities.BaseMangaPage
@@ -78,7 +79,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)
@@ -192,6 +193,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 {
@@ -542,6 +545,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()
} }
@@ -571,6 +575,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,7 +662,11 @@ class ReadMangaActivity : VMBaseActivity<ActivityMangaBinding, ReadMangaViewMode
mAdapter.isHorizontal = enable mAdapter.isHorizontal = enable
if (enable) { if (enable) {
if (!enableAutoScroll) { if (!enableAutoScroll) {
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,7 +686,14 @@ class ReadMangaActivity : VMBaseActivity<ActivityMangaBinding, ReadMangaViewMode
getString(R.string.manga_auto_page_speed, AppConfig.mangaAutoPageSpeed) getString(R.string.manga_auto_page_speed, AppConfig.mangaAutoPageSpeed)
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_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) {
@@ -785,4 +832,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,50 @@
package io.legado.app.ui.book.manga.config
import android.content.DialogInterface
import android.os.Bundle
import android.view.View
import android.view.ViewGroup
import android.view.WindowManager
import io.legado.app.R
import io.legado.app.base.BaseDialogFragment
import io.legado.app.databinding.DialogMangaEpaperBinding
import io.legado.app.help.config.AppConfig
import io.legado.app.utils.setLayout
import io.legado.app.utils.viewbindingdelegate.viewBinding
class MangaEpaperDialog : BaseDialogFragment(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.legado.app.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.legado.app.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.legado.app.base.adapter.ItemViewHolder import io.legado.app.base.adapter.ItemViewHolder
import io.legado.app.base.adapter.RecyclerAdapter.Companion.TYPE_FOOTER_VIEW import io.legado.app.base.adapter.RecyclerAdapter.Companion.TYPE_FOOTER_VIEW
import io.legado.app.databinding.ItemBookMangaEdgeBinding import io.legado.app.databinding.ItemBookMangaEdgeBinding
@@ -24,6 +26,8 @@ import io.legado.app.help.glide.progress.ProgressManager
import io.legado.app.model.BookCover import io.legado.app.model.BookCover
import io.legado.app.model.ReadManga import io.legado.app.model.ReadManga
import io.legado.app.ui.book.manga.config.MangaColorFilterConfig import io.legado.app.ui.book.manga.config.MangaColorFilterConfig
import io.legado.app.ui.book.manga.entities.EpaperTransformation
import io.legado.app.ui.book.manga.entities.GrayscaleTransformation
import io.legado.app.ui.book.manga.entities.MangaPage import io.legado.app.ui.book.manga.entities.MangaPage
import io.legado.app.ui.book.manga.entities.ReaderLoading import io.legado.app.ui.book.manga.entities.ReaderLoading
import io.legado.app.utils.dpToPx import io.legado.app.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,31 @@ 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.legado.app.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,7 @@ 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 +69,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.legado.app.ui.widget.DetailSeekBar <io.legado.app.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">
<io.legado.app.ui.widget.text.AccentTextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:paddingBottom="8dp"
android:text="@string/manga_epaper_stting"
android:textSize="18sp" />
<io.legado.app.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>
+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:visible="false"
android:title="@string/disable_horizontal_animation"
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>
@@ -1216,4 +1216,9 @@
<string name="hide_manga_title">隐藏漫画列表标题</string> <string name="hide_manga_title">隐藏漫画列表标题</string>
<string name="refresh_explore">刷新发现</string> <string name="refresh_explore">刷新发现</string>
<string name="padding_display_cutouts">Padding display cutouts</string> <string name="padding_display_cutouts">Padding display cutouts</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>
@@ -1219,4 +1219,9 @@
<string name="hide_manga_title">隐藏漫画列表标题</string> <string name="hide_manga_title">隐藏漫画列表标题</string>
<string name="refresh_explore">刷新发现</string> <string name="refresh_explore">刷新发现</string>
<string name="padding_display_cutouts">Padding display cutouts</string> <string name="padding_display_cutouts">Padding display cutouts</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>
@@ -1219,4 +1219,9 @@
<string name="hide_manga_title">隐藏漫画列表标题</string> <string name="hide_manga_title">隐藏漫画列表标题</string>
<string name="refresh_explore">刷新发现</string> <string name="refresh_explore">刷新发现</string>
<string name="padding_display_cutouts">Padding display cutouts</string> <string name="padding_display_cutouts">Padding display cutouts</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>
+5
View File
@@ -1215,4 +1215,9 @@ Còn </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="padding_display_cutouts">Padding display cutouts</string> <string name="padding_display_cutouts">Padding display cutouts</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>
@@ -1214,4 +1214,9 @@
<string name="system_media_control_compatibility_change">系統媒體控件相容性更改</string> <string name="system_media_control_compatibility_change">系統媒體控件相容性更改</string>
<string name="system_media_control_compatibility_change_summary">當鎖屏不顯示系統媒體控件時可以嘗試開啟,比如 OneUI7.0 或 vivo 等</string> <string name="system_media_control_compatibility_change_summary">當鎖屏不顯示系統媒體控件時可以嘗試開啟,比如 OneUI7.0 或 vivo 等</string>
<string name="read_aloud_pause_resume">朗讀暫停/繼續</string> <string name="read_aloud_pause_resume">朗讀暫停/繼續</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>
@@ -1216,4 +1216,8 @@
<string name="system_media_control_compatibility_change">系統媒體控件相容性更改</string> <string name="system_media_control_compatibility_change">系統媒體控件相容性更改</string>
<string name="system_media_control_compatibility_change_summary">當鎖屏不顯示系統媒體控件時可以嘗試開啟,比如 OneUI7.0 或 vivo 等</string> <string name="system_media_control_compatibility_change_summary">當鎖屏不顯示系統媒體控件時可以嘗試開啟,比如 OneUI7.0 或 vivo 等</string>
<string name="read_aloud_pause_resume">朗讀暫停/繼續</string> <string name="read_aloud_pause_resume">朗讀暫停/繼續</string>
<string name="manga_epaper">墨水屏</string>
<string name="manga_epaper_value">阈值</string>
<string name="disable_horizontal_animation">禁用水平滚动动画</string>
<string name="enable_manga_gray">开启图片灰色</string>
</resources> </resources>
+5
View File
@@ -1221,4 +1221,9 @@
<string name="system_media_control_compatibility_change">系统媒体控件兼容性更改</string> <string name="system_media_control_compatibility_change">系统媒体控件兼容性更改</string>
<string name="system_media_control_compatibility_change_summary">当锁屏不显示系统媒体控件时可以尝试开启,比如oneui7.0或vivo等</string> <string name="system_media_control_compatibility_change_summary">当锁屏不显示系统媒体控件时可以尝试开启,比如oneui7.0或vivo等</string>
<string name="read_aloud_pause_resume">朗读暂停/继续</string> <string name="read_aloud_pause_resume">朗读暂停/继续</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>
+5
View File
@@ -1222,4 +1222,9 @@
<string name="system_media_control_compatibility_change">System Media Control Compatibility Change</string> <string name="system_media_control_compatibility_change">System Media Control Compatibility Change</string>
<string name="system_media_control_compatibility_change_summary">If system media controls do not appear on the lock screen, you can try enabling this, for example on OneUI 7.0 or Vivo devices.</string> <string name="system_media_control_compatibility_change_summary">If system media controls do not appear on the lock screen, you can try enabling this, for example on OneUI 7.0 or Vivo devices.</string>
<string name="read_aloud_pause_resume">Pause/Resume Speech</string> <string name="read_aloud_pause_resume">Pause/Resume Speech</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>