新增漫画阅读模式[条漫、日漫等]
新增条漫侧边留白功能 更新滤镜功能,现在支持亮度跟随系统 其他界面更新
This commit is contained in:
@@ -190,4 +190,6 @@ object PreferKey {
|
||||
const val themePref = "themePref"
|
||||
const val bookshelfSortOrder = "bookshelfSortOrder"
|
||||
const val showBottomView = "showBottomView"
|
||||
const val mangaScrollMode = "mangaScrollMode"
|
||||
const val webtoonSidePaddingDp = "webtoonSidePaddingDp"
|
||||
}
|
||||
|
||||
@@ -403,6 +403,8 @@ data class Book(
|
||||
var startDate: LocalDate? = null,
|
||||
var startChapter: Int? = null, // 用户设置的起始章节
|
||||
var dailyChapters: Int = 3 // 用户设置的每日更新章节数
|
||||
|
||||
|
||||
) : Parcelable
|
||||
|
||||
class Converters {
|
||||
|
||||
@@ -5,6 +5,7 @@ import android.os.Build
|
||||
import io.legato.kazusa.BuildConfig
|
||||
import io.legato.kazusa.constant.PreferKey
|
||||
import io.legato.kazusa.data.appDb
|
||||
import io.legato.kazusa.ui.book.manga.config.MangaScrollMode
|
||||
import io.legato.kazusa.utils.canvasrecorder.CanvasRecorderFactory
|
||||
import io.legato.kazusa.utils.getPrefBoolean
|
||||
import io.legato.kazusa.utils.getPrefInt
|
||||
@@ -664,6 +665,14 @@ object AppConfig : SharedPreferences.OnSharedPreferenceChangeListener {
|
||||
appCtx.putPrefBoolean(PreferKey.enableMangaHorizontalScroll, value)
|
||||
}
|
||||
|
||||
//漫画滚动方式
|
||||
var mangaScrollMode: Int
|
||||
get() = appCtx.getPrefInt(PreferKey.mangaScrollMode, MangaScrollMode.PAGE_RIGHT_TO_LEFT)
|
||||
set(value) {
|
||||
appCtx.putPrefInt(PreferKey.mangaScrollMode, value)
|
||||
}
|
||||
|
||||
//漫画滤镜
|
||||
var mangaColorFilter
|
||||
get() = appCtx.getPrefString(PreferKey.mangaColorFilter, "")
|
||||
set(value) {
|
||||
@@ -701,5 +710,12 @@ object AppConfig : SharedPreferences.OnSharedPreferenceChangeListener {
|
||||
set(value) {
|
||||
appCtx.putPrefBoolean(PreferKey.enableMangaGray, value)
|
||||
}
|
||||
|
||||
var webtoonSidePaddingDp: Int
|
||||
get() = appCtx.getPrefInt(PreferKey.webtoonSidePaddingDp, 0)
|
||||
set(value) {
|
||||
appCtx.putPrefInt(PreferKey.webtoonSidePaddingDp, value)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
@@ -607,7 +607,7 @@ object ReadManga : CoroutineScope by MainScope() {
|
||||
if (imageCount == 0 && chapter.isVolume) {
|
||||
pages.add(ReaderLoading(chapter.index, -1, chapter.title, true))
|
||||
} else {
|
||||
pages.add(ReaderLoading(chapter.index, -1, "阅读 ${chapter.title}"))
|
||||
pages.add(ReaderLoading(chapter.index, -1, "下一章 ${chapter.title}"))
|
||||
pages.addAll(list)
|
||||
pages.add(ReaderLoading(chapter.index, imageCount, "已读完 ${chapter.title}"))
|
||||
}
|
||||
|
||||
@@ -12,6 +12,7 @@ import android.view.WindowManager
|
||||
import android.view.animation.LinearInterpolator
|
||||
import androidx.activity.viewModels
|
||||
import androidx.appcompat.app.AlertDialog
|
||||
import androidx.core.view.doOnLayout
|
||||
import androidx.core.view.isGone
|
||||
import androidx.core.view.isVisible
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
@@ -45,6 +46,8 @@ 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.MangaFooterConfig
|
||||
import io.legato.kazusa.ui.book.manga.config.MangaFooterSettingDialog
|
||||
import io.legato.kazusa.ui.book.manga.config.MangaScrollMode
|
||||
import io.legato.kazusa.ui.book.manga.config.MangaScrollModeDialog
|
||||
import io.legato.kazusa.ui.book.manga.entities.BaseMangaPage
|
||||
import io.legato.kazusa.ui.book.manga.entities.MangaPage
|
||||
import io.legato.kazusa.ui.book.manga.recyclerview.MangaAdapter
|
||||
@@ -77,11 +80,13 @@ import kotlin.math.ceil
|
||||
|
||||
class ReadMangaActivity : VMBaseActivity<ActivityMangaBinding, ReadMangaViewModel>(),
|
||||
ReadManga.Callback, ChangeBookSourceDialog.CallBack, MangaMenu.CallBack,
|
||||
MangaColorFilterDialog.Callback, ScrollTimer.ScrollCallback, MangaFooterSettingDialog.Callback {
|
||||
MangaColorFilterDialog.Callback, ScrollTimer.ScrollCallback, MangaFooterSettingDialog.Callback,
|
||||
MangaScrollModeDialog.Callback {
|
||||
|
||||
private val mLayoutManager by lazy {
|
||||
MangaLayoutManager(this)
|
||||
}
|
||||
|
||||
private val mAdapter: MangaAdapter by lazy {
|
||||
MangaAdapter(this)
|
||||
}
|
||||
@@ -99,6 +104,8 @@ class ReadMangaActivity : VMBaseActivity<ActivityMangaBinding, ReadMangaViewMode
|
||||
|
||||
private var mMenu: Menu? = null
|
||||
|
||||
private var scrollMode: Int = MangaScrollMode.PAGE_LEFT_TO_RIGHT
|
||||
|
||||
private var mRecyclerViewPreloader: RecyclerViewPreloader<Any>? = null
|
||||
|
||||
private val networkChangedListener by lazy {
|
||||
@@ -192,7 +199,8 @@ class ReadMangaActivity : VMBaseActivity<ActivityMangaBinding, ReadMangaViewMode
|
||||
enableMangaEInk(AppConfig.enableMangaEInk, AppConfig.mangaEInkThreshold)
|
||||
enableGray(AppConfig.enableMangaGray)
|
||||
}
|
||||
setHorizontalScroll(AppConfig.enableMangaHorizontalScroll)
|
||||
//setHorizontalScroll(AppConfig.enableMangaHorizontalScroll)
|
||||
setScrollMode(AppConfig.mangaScrollMode)
|
||||
binding.recyclerView.run {
|
||||
adapter = mAdapter
|
||||
itemAnimator = null
|
||||
@@ -201,6 +209,7 @@ class ReadMangaActivity : VMBaseActivity<ActivityMangaBinding, ReadMangaViewMode
|
||||
setDisableClickScroll(AppConfig.disableClickScroll)
|
||||
setDisableMangaScale(AppConfig.disableMangaScale)
|
||||
setRecyclerViewPreloader(AppConfig.mangaPreDownloadNum)
|
||||
updateWebtoonSidePadding(AppConfig.webtoonSidePaddingDp)
|
||||
setPreScrollListener { _, _, _, position ->
|
||||
if (mAdapter.isNotEmpty()) {
|
||||
val item = mAdapter.getItem(position)
|
||||
@@ -431,10 +440,26 @@ class ReadMangaActivity : VMBaseActivity<ActivityMangaBinding, ReadMangaViewMode
|
||||
binding.recyclerView.smoothScrollBy(10000, 10000, mLinearInterpolator, time)
|
||||
}
|
||||
|
||||
override fun scrollPage() {
|
||||
scrollToNext()
|
||||
override fun scrollPage(direction: Int) {
|
||||
if (scrollMode == MangaScrollMode.WEBTOON || scrollMode == MangaScrollMode.WEBTOON_WITH_GAP) {
|
||||
// 条漫平滑滚动距离
|
||||
val dy =
|
||||
(binding.recyclerView.height - binding.recyclerView.paddingTop - binding.recyclerView.paddingBottom) * direction
|
||||
binding.recyclerView.smoothScrollBy(0, dy)
|
||||
} else {
|
||||
// 分页按 position 翻页
|
||||
val layoutManager = binding.recyclerView.layoutManager as? LinearLayoutManager ?: return
|
||||
val currentPosition = layoutManager.findFirstVisibleItemPosition()
|
||||
val nextPosition = currentPosition + direction
|
||||
val itemCount = binding.recyclerView.adapter?.itemCount ?: return
|
||||
|
||||
if (nextPosition in 0 until itemCount) {
|
||||
binding.recyclerView.smoothScrollToPosition(nextPosition)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
override val oldBook: Book?
|
||||
get() = ReadManga.book
|
||||
|
||||
@@ -495,46 +520,11 @@ class ReadMangaActivity : VMBaseActivity<ActivityMangaBinding, ReadMangaViewMode
|
||||
}
|
||||
}
|
||||
|
||||
R.id.menu_disable_manga_scale -> {
|
||||
item.isChecked = !item.isChecked
|
||||
AppConfig.disableMangaScale = item.isChecked
|
||||
setDisableMangaScale(item.isChecked)
|
||||
}
|
||||
|
||||
R.id.menu_disable_click_scroll -> {
|
||||
item.isChecked = !item.isChecked
|
||||
AppConfig.disableClickScroll = item.isChecked
|
||||
setDisableClickScroll(item.isChecked)
|
||||
}
|
||||
|
||||
R.id.menu_enable_horizontal_scroll -> {
|
||||
item.isChecked = !item.isChecked
|
||||
AppConfig.enableMangaHorizontalScroll = item.isChecked
|
||||
mMenu?.findItem(R.id.menu_disable_horizontal_animation)?.isVisible = item.isChecked
|
||||
setHorizontalScroll(item.isChecked)
|
||||
mAdapter.notifyDataSetChanged()
|
||||
}
|
||||
|
||||
R.id.menu_manga_color_filter -> {
|
||||
binding.mangaMenu.runMenuOut()
|
||||
showDialogFragment(MangaColorFilterDialog())
|
||||
}
|
||||
|
||||
R.id.menu_enable_auto_scroll -> {
|
||||
item.isChecked = !item.isChecked
|
||||
mScrollTimer.isEnabled = item.isChecked
|
||||
|
||||
enableAutoScroll = item.isChecked
|
||||
enableAutoScrollPage = false
|
||||
mScrollTimer.isEnabledPage = false
|
||||
|
||||
if (enableAutoScroll) {
|
||||
mPagerSnapHelper.attachToRecyclerView(null)
|
||||
} else if (AppConfig.enableMangaHorizontalScroll) {
|
||||
mPagerSnapHelper.attachToRecyclerView(binding.recyclerView)
|
||||
}
|
||||
}
|
||||
|
||||
R.id.menu_hide_manga_title -> {
|
||||
item.isChecked = !item.isChecked
|
||||
AppConfig.hideMangaTitle = item.isChecked
|
||||
@@ -577,6 +567,40 @@ class ReadMangaActivity : VMBaseActivity<ActivityMangaBinding, ReadMangaViewMode
|
||||
showDialogFragment(MangaColorFilterDialog())
|
||||
}
|
||||
|
||||
override fun showScrollModeDialog() {
|
||||
showDialogFragment(MangaScrollModeDialog().apply {
|
||||
initialScrollMode = AppConfig.mangaScrollMode
|
||||
callback = this@ReadMangaActivity
|
||||
})
|
||||
}
|
||||
|
||||
//漫画模式
|
||||
override fun onScrollModeChanged(mode: Int) {
|
||||
AppConfig.mangaScrollMode = mode
|
||||
setScrollMode(mode)
|
||||
updateWebtoonSidePadding(AppConfig.webtoonSidePaddingDp)
|
||||
setAutoReadEnabled(false)
|
||||
}
|
||||
|
||||
//点击滑动
|
||||
override fun onClickScrollDisabledChanged(enabled: Boolean) {
|
||||
AppConfig.disableClickScroll = enabled
|
||||
setDisableClickScroll(enabled)
|
||||
}
|
||||
|
||||
//双击缩放
|
||||
override fun onMangaScaleDisabledChanged(enabled: Boolean) {
|
||||
AppConfig.disableMangaScale = enabled
|
||||
setDisableMangaScale(enabled)
|
||||
}
|
||||
|
||||
//侧边留白
|
||||
override fun upSidePadding(paddingDp: Int) {
|
||||
AppConfig.webtoonSidePaddingDp = paddingDp
|
||||
updateWebtoonSidePadding(paddingDp)
|
||||
}
|
||||
|
||||
//墨水屏
|
||||
override fun updateEpaperMode(enabled: Boolean, threshold: Int) {
|
||||
AppConfig.enableMangaEInk = enabled
|
||||
AppConfig.enableMangaGray = false
|
||||
@@ -584,12 +608,28 @@ class ReadMangaActivity : VMBaseActivity<ActivityMangaBinding, ReadMangaViewMode
|
||||
mAdapter.enableMangaEInk(enabled, threshold)
|
||||
}
|
||||
|
||||
//灰度
|
||||
override fun updateGrayMode(enabled: Boolean) {
|
||||
AppConfig.enableMangaGray = enabled
|
||||
AppConfig.enableMangaEInk = false
|
||||
mAdapter.enableGray(enabled)
|
||||
}
|
||||
|
||||
//自动翻页
|
||||
override fun onAutoPageToggle(enabled: Boolean) {
|
||||
setAutoReadEnabled(enabled)
|
||||
}
|
||||
|
||||
//自动翻页速度
|
||||
override fun onAutoPageSpeedChanged(speed: Int) {
|
||||
setAutoReadEnabled(false)
|
||||
AppConfig.mangaAutoPageSpeed = speed
|
||||
mScrollTimer.setSpeed(speed)
|
||||
setAutoReadEnabled(true)
|
||||
// if (enableAutoScrollPage) {
|
||||
// mScrollTimer.isEnabledPage = true
|
||||
// }
|
||||
}
|
||||
|
||||
override fun openBookInfoActivity() {
|
||||
ReadManga.book?.let {
|
||||
@@ -600,22 +640,27 @@ class ReadMangaActivity : VMBaseActivity<ActivityMangaBinding, ReadMangaViewMode
|
||||
}
|
||||
}
|
||||
|
||||
override fun onAutoPageToggle(enabled: Boolean) {
|
||||
mScrollTimer.isEnabledPage = enabled
|
||||
mScrollTimer.isEnabled = false
|
||||
|
||||
enableAutoScrollPage = enabled
|
||||
enableAutoScroll = false
|
||||
|
||||
mMenu?.findItem(R.id.menu_enable_auto_scroll)?.isChecked = false
|
||||
}
|
||||
|
||||
override fun onAutoPageSpeedChanged(speed: Int) {
|
||||
AppConfig.mangaAutoPageSpeed = speed
|
||||
mScrollTimer.setSpeed(speed)
|
||||
|
||||
if (enableAutoScrollPage) {
|
||||
mScrollTimer.isEnabledPage = true
|
||||
//1 自动翻页 2 自动滚动 0 关闭
|
||||
fun setAutoReadEnabled(enabled: Boolean) {
|
||||
val isWebtoonMode =
|
||||
scrollMode == MangaScrollMode.WEBTOON || scrollMode == MangaScrollMode.WEBTOON_WITH_GAP
|
||||
if (enabled) {
|
||||
if (isWebtoonMode) {
|
||||
mScrollTimer.isEnabled = true
|
||||
enableAutoScroll = true
|
||||
enableAutoScrollPage = false
|
||||
mScrollTimer.isEnabledPage = false
|
||||
} else {
|
||||
mScrollTimer.isEnabledPage = true
|
||||
enableAutoScrollPage = true
|
||||
enableAutoScroll = false
|
||||
mScrollTimer.isEnabled = false
|
||||
}
|
||||
} else {
|
||||
enableAutoScroll = false
|
||||
enableAutoScrollPage = false
|
||||
mScrollTimer.isEnabled = false
|
||||
mScrollTimer.isEnabledPage = false
|
||||
}
|
||||
}
|
||||
|
||||
@@ -648,6 +693,70 @@ class ReadMangaActivity : VMBaseActivity<ActivityMangaBinding, ReadMangaViewMode
|
||||
return super.dispatchKeyEvent(event)
|
||||
}
|
||||
|
||||
@SuppressLint("NotifyDataSetChanged")
|
||||
private fun setScrollMode(mode: Int) {
|
||||
scrollMode = mode
|
||||
|
||||
val isHorizontalPage =
|
||||
mode == MangaScrollMode.PAGE_LEFT_TO_RIGHT || mode == MangaScrollMode.PAGE_RIGHT_TO_LEFT
|
||||
|
||||
val isWebtoon = mode == MangaScrollMode.WEBTOON || mode == MangaScrollMode.WEBTOON_WITH_GAP
|
||||
|
||||
when (mode) {
|
||||
MangaScrollMode.PAGE_LEFT_TO_RIGHT -> {
|
||||
mLayoutManager.orientation = LinearLayoutManager.HORIZONTAL
|
||||
mLayoutManager.reverseLayout = false
|
||||
}
|
||||
|
||||
MangaScrollMode.PAGE_RIGHT_TO_LEFT -> {
|
||||
mLayoutManager.orientation = LinearLayoutManager.HORIZONTAL
|
||||
mLayoutManager.reverseLayout = true
|
||||
}
|
||||
|
||||
MangaScrollMode.PAGE_TOP_TO_BOTTOM,
|
||||
MangaScrollMode.WEBTOON,
|
||||
MangaScrollMode.WEBTOON_WITH_GAP -> {
|
||||
mLayoutManager.orientation = LinearLayoutManager.VERTICAL
|
||||
mLayoutManager.reverseLayout = false
|
||||
}
|
||||
}
|
||||
|
||||
mAdapter.isHorizontal = isHorizontalPage || mode == MangaScrollMode.PAGE_TOP_TO_BOTTOM
|
||||
|
||||
if (!isWebtoon) {
|
||||
mPagerSnapHelper.attachToRecyclerView(binding.recyclerView)
|
||||
} else {
|
||||
mPagerSnapHelper.attachToRecyclerView(null)
|
||||
}
|
||||
|
||||
mAdapter.notifyDataSetChanged()
|
||||
}
|
||||
|
||||
/**
|
||||
* 调整条漫侧边留白设置
|
||||
* @param paddingDp 侧边留白距离,单位dp,默认16dp
|
||||
*/
|
||||
private fun updateWebtoonSidePadding(paddingDp: Int) {
|
||||
if (scrollMode != MangaScrollMode.WEBTOON && scrollMode != MangaScrollMode.WEBTOON_WITH_GAP) {
|
||||
// 非条漫模式,取消留白
|
||||
binding.recyclerView.setPadding(0, 0, 0, 0)
|
||||
binding.recyclerView.clipToPadding = true
|
||||
return
|
||||
}
|
||||
|
||||
binding.recyclerView.doOnLayout { recyclerView ->
|
||||
val width = recyclerView.width
|
||||
if (paddingDp == 0) {
|
||||
recyclerView.setPadding(0, 0, 0, 0)
|
||||
} else if (width > 0) {
|
||||
val paddingPx = (width * paddingDp / 100).toInt()
|
||||
recyclerView.setPadding(paddingPx, 0, paddingPx, 0)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
private fun setRecyclerViewPreloader(maxPreload: Int) {
|
||||
if (mRecyclerViewPreloader != null) {
|
||||
binding.recyclerView.removeOnScrollListener(mRecyclerViewPreloader!!)
|
||||
@@ -658,33 +767,11 @@ class ReadMangaActivity : VMBaseActivity<ActivityMangaBinding, ReadMangaViewMode
|
||||
binding.recyclerView.addOnScrollListener(mRecyclerViewPreloader!!)
|
||||
}
|
||||
|
||||
private fun setHorizontalScroll(enable: Boolean) {
|
||||
mAdapter.isHorizontal = enable
|
||||
if (enable) {
|
||||
if (!enableAutoScroll) {
|
||||
mPagerSnapHelper.attachToRecyclerView(binding.recyclerView)
|
||||
if (AppConfig.disableHorizontalAnimator) {
|
||||
mPagerSnapHelper.attachToRecyclerView(null)
|
||||
} else {
|
||||
mPagerSnapHelper.attachToRecyclerView(binding.recyclerView)
|
||||
}
|
||||
}
|
||||
mLayoutManager.orientation = LinearLayoutManager.HORIZONTAL
|
||||
} else {
|
||||
mPagerSnapHelper.attachToRecyclerView(null)
|
||||
mLayoutManager.orientation = LinearLayoutManager.VERTICAL
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressLint("StringFormatMatches")
|
||||
private fun upMenu(menu: Menu) {
|
||||
this.mMenu = menu
|
||||
menu.findItem(R.id.menu_pre_manga_number).title =
|
||||
getString(R.string.pre_download_m, AppConfig.mangaPreDownloadNum)
|
||||
menu.findItem(R.id.menu_disable_manga_scale).isChecked = AppConfig.disableMangaScale
|
||||
menu.findItem(R.id.menu_disable_click_scroll).isChecked = AppConfig.disableClickScroll
|
||||
menu.findItem(R.id.menu_enable_horizontal_scroll).isChecked =
|
||||
AppConfig.enableMangaHorizontalScroll
|
||||
menu.findItem(R.id.menu_hide_manga_title).isChecked = AppConfig.hideMangaTitle
|
||||
|
||||
menu.findItem(R.id.menu_disable_horizontal_animation).run {
|
||||
|
||||
@@ -129,38 +129,14 @@ class MangaFooterSettingDialog :
|
||||
postEvent(EventBus.UP_MANGA_CONFIG, config)
|
||||
}
|
||||
|
||||
|
||||
binding.chipAutoPage.run {
|
||||
isChecked = initialAutoPageEnabled
|
||||
setOnCheckedChangeListener { _, isChecked ->
|
||||
callback?.onAutoPageToggle(isChecked)
|
||||
initAutoPageSpeed(isChecked)
|
||||
}
|
||||
}
|
||||
|
||||
binding.btnColorFilter.setOnClickListener {
|
||||
callback?.showColorFilterConfig()
|
||||
dismiss()
|
||||
}
|
||||
|
||||
initAutoPageSpeed(initialAutoPageEnabled)
|
||||
|
||||
updateChapterText()
|
||||
}
|
||||
|
||||
private fun initAutoPageSpeed(boolean: Boolean) {
|
||||
TransitionManager.beginDelayedTransition(binding.rootView)
|
||||
binding.scvAutoPageSpeed.run {
|
||||
isEnabled = boolean
|
||||
valueFormat = { "$it 秒" }
|
||||
progress = initialAutoPageSpeed
|
||||
|
||||
onChanged = { newValue ->
|
||||
callback?.onAutoPageSpeedChanged(newValue)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun onDismiss(dialog: DialogInterface) {
|
||||
super.onDismiss(dialog)
|
||||
AppConfig.mangaFooterConfig = GSON.toJson(config)
|
||||
|
||||
@@ -0,0 +1,27 @@
|
||||
package io.legato.kazusa.ui.book.manga.config
|
||||
|
||||
object MangaScrollMode {
|
||||
const val PAGE_LEFT_TO_RIGHT = 1 // 单页式(从左到右)
|
||||
const val PAGE_RIGHT_TO_LEFT = 2 // 单页式(从右到左)
|
||||
const val PAGE_TOP_TO_BOTTOM = 3 // 单页式(从上到下)
|
||||
const val WEBTOON = 4 // 条漫
|
||||
const val WEBTOON_WITH_GAP = 5 // 条漫(页面有空隙)
|
||||
|
||||
val ALL = listOf(
|
||||
PAGE_LEFT_TO_RIGHT,
|
||||
PAGE_RIGHT_TO_LEFT,
|
||||
PAGE_TOP_TO_BOTTOM,
|
||||
WEBTOON,
|
||||
WEBTOON_WITH_GAP
|
||||
)
|
||||
|
||||
fun labelOf(mode: Int): String = when (mode) {
|
||||
PAGE_LEFT_TO_RIGHT -> "单页式(从左到右)"
|
||||
PAGE_RIGHT_TO_LEFT -> "单页式(从右到左)"
|
||||
PAGE_TOP_TO_BOTTOM -> "单页式(从上到下)"
|
||||
WEBTOON -> "条漫"
|
||||
WEBTOON_WITH_GAP -> "条漫(页面有空隙)"
|
||||
else -> "未知"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,103 @@
|
||||
package io.legato.kazusa.ui.book.manga.config
|
||||
|
||||
import android.os.Bundle
|
||||
import android.view.View
|
||||
import androidx.transition.TransitionManager
|
||||
import com.google.android.material.chip.Chip
|
||||
import io.legato.kazusa.R
|
||||
import io.legato.kazusa.base.BaseBottomSheetDialogFragment
|
||||
import io.legato.kazusa.databinding.DialogMangaScrollModeBinding
|
||||
import io.legato.kazusa.help.config.AppConfig
|
||||
import io.legato.kazusa.utils.viewbindingdelegate.viewBinding
|
||||
|
||||
class MangaScrollModeDialog : BaseBottomSheetDialogFragment(R.layout.dialog_manga_scroll_mode) {
|
||||
|
||||
private val binding by viewBinding(DialogMangaScrollModeBinding::bind)
|
||||
|
||||
var initialScrollMode: Int = AppConfig.mangaScrollMode
|
||||
var initialAutoPageSpeed: Int = AppConfig.mangaAutoPageSpeed
|
||||
var initialSidePadding: Int = AppConfig.webtoonSidePaddingDp
|
||||
var initialAutoPageEnabled: Boolean = false
|
||||
var callback: Callback? = null
|
||||
|
||||
override fun onFragmentCreated(view: View, savedInstanceState: Bundle?) {
|
||||
initView()
|
||||
}
|
||||
|
||||
private fun initView() {
|
||||
binding.chipGroupScrollMode.removeAllViews()
|
||||
|
||||
MangaScrollMode.ALL.forEach { mode ->
|
||||
binding.chipGroupScrollMode.addView(Chip(requireContext()).apply {
|
||||
text = MangaScrollMode.labelOf(mode)
|
||||
isCheckable = true
|
||||
isChecked = (mode == initialScrollMode)
|
||||
|
||||
setOnClickListener {
|
||||
AppConfig.mangaScrollMode = mode
|
||||
callback?.onScrollModeChanged(mode)
|
||||
binding.chipAutoPage.isChecked = false
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
binding.chipAutoPage.run {
|
||||
isChecked = initialAutoPageEnabled
|
||||
setOnCheckedChangeListener { _, isChecked ->
|
||||
callback?.onAutoPageToggle(isChecked)
|
||||
initAutoPageSpeed(isChecked)
|
||||
}
|
||||
}
|
||||
|
||||
binding.checkboxDisableClickScroll.apply {
|
||||
isChecked = AppConfig.disableClickScroll
|
||||
setOnCheckedChangeListener { _, isChecked ->
|
||||
AppConfig.disableClickScroll = isChecked
|
||||
callback?.onClickScrollDisabledChanged(isChecked)
|
||||
}
|
||||
}
|
||||
|
||||
binding.checkboxDisableMangaScale.apply {
|
||||
isChecked = AppConfig.disableMangaScale
|
||||
setOnCheckedChangeListener { _, isChecked ->
|
||||
AppConfig.disableMangaScale = isChecked
|
||||
callback?.onMangaScaleDisabledChanged(isChecked)
|
||||
}
|
||||
}
|
||||
|
||||
binding.scvPadding.apply {
|
||||
valueFormat = { "$it %" }
|
||||
progress = initialSidePadding
|
||||
onChanged = { newValue ->
|
||||
callback?.upSidePadding(newValue)
|
||||
initialSidePadding = newValue
|
||||
}
|
||||
}
|
||||
|
||||
initAutoPageSpeed(initialAutoPageEnabled)
|
||||
}
|
||||
|
||||
private fun initAutoPageSpeed(boolean: Boolean) {
|
||||
TransitionManager.beginDelayedTransition(binding.rootView)
|
||||
binding.scvAutoPageSpeed.run {
|
||||
isEnabled = boolean
|
||||
valueFormat = { "$it 秒" }
|
||||
progress = initialAutoPageSpeed
|
||||
|
||||
onChanged = { newValue ->
|
||||
callback?.onAutoPageSpeedChanged(newValue)
|
||||
initialAutoPageSpeed = newValue
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
interface Callback {
|
||||
fun onAutoPageToggle(enable: Boolean)
|
||||
fun onAutoPageSpeedChanged(speed: Int)
|
||||
fun onScrollModeChanged(mode: Int)
|
||||
fun onClickScrollDisabledChanged(disabled: Boolean)
|
||||
fun onMangaScaleDisabledChanged(disabled: Boolean)
|
||||
fun upSidePadding(padding: Int)
|
||||
}
|
||||
|
||||
}
|
||||
@@ -30,7 +30,6 @@ 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.ReaderLoading
|
||||
import io.legato.kazusa.utils.dpToPx
|
||||
|
||||
|
||||
class MangaAdapter(private val context: Context) :
|
||||
@@ -47,6 +46,8 @@ class MangaAdapter(private val context: Context) :
|
||||
}
|
||||
|
||||
var isHorizontal = false
|
||||
var enableDoublePageInLandscape = false
|
||||
|
||||
|
||||
private val mDiffCallback: DiffUtil.ItemCallback<Any> = object : DiffUtil.ItemCallback<Any>() {
|
||||
override fun areItemsTheSame(oldItem: Any, newItem: Any): Boolean {
|
||||
@@ -131,19 +132,20 @@ class MangaAdapter(private val context: Context) :
|
||||
inner class PageMoreViewHolder(val binding: ItemBookMangaEdgeBinding) :
|
||||
RecyclerView.ViewHolder(binding.root) {
|
||||
fun onBind(item: ReaderLoading) {
|
||||
val message = item.mMessage
|
||||
binding.text.text = message
|
||||
itemView.updateLayoutParams {
|
||||
height = if (item.isVolume) {
|
||||
MATCH_PARENT
|
||||
} else {
|
||||
96.dpToPx()
|
||||
}
|
||||
val message = item.mMessage ?: ""
|
||||
val parts = message.split(" ", limit = 2)
|
||||
if (parts.size == 2) {
|
||||
binding.text.text = parts[0]
|
||||
binding.textChapter.text = parts[1]
|
||||
} else {
|
||||
binding.text.text = message
|
||||
binding.textChapter.text = ""
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
|
||||
return when {
|
||||
viewType >= TYPE_FOOTER_VIEW -> {
|
||||
|
||||
@@ -63,13 +63,13 @@ class ScrollTimer(
|
||||
mScrollPageJob = coroutineScope.launch {
|
||||
while (isActive) {
|
||||
delay(distance.times(1000L))
|
||||
callback.scrollPage()
|
||||
callback.scrollPage(1)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
interface ScrollCallback {
|
||||
fun scrollBy(distance: Int)
|
||||
fun scrollPage()
|
||||
fun scrollPage(direction: Int)
|
||||
}
|
||||
}
|
||||
@@ -243,7 +243,7 @@ class MangaMenu @JvmOverloads constructor(
|
||||
}
|
||||
|
||||
btnAutoPage.setOnClickListener {
|
||||
callBack.showFooterConfig()
|
||||
callBack.showScrollModeDialog()
|
||||
}
|
||||
|
||||
}
|
||||
@@ -263,6 +263,7 @@ class MangaMenu @JvmOverloads constructor(
|
||||
fun skipToPage(index: Int)
|
||||
fun openCatalog()
|
||||
fun showFooterConfig()
|
||||
fun showScrollModeDialog()
|
||||
}
|
||||
|
||||
}
|
||||
@@ -10,6 +10,7 @@ import android.view.MotionEvent
|
||||
import android.view.ViewGroup
|
||||
import androidx.activity.addCallback
|
||||
import androidx.activity.viewModels
|
||||
import androidx.core.view.get
|
||||
import androidx.core.view.postDelayed
|
||||
import androidx.fragment.app.Fragment
|
||||
import androidx.fragment.app.FragmentManager
|
||||
@@ -17,6 +18,7 @@ import androidx.fragment.app.FragmentStatePagerAdapter
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import androidx.viewpager.widget.ViewPager
|
||||
import com.google.android.material.bottomnavigation.BottomNavigationView
|
||||
import com.google.android.material.navigation.NavigationBarView
|
||||
import io.legato.kazusa.BuildConfig
|
||||
import io.legato.kazusa.R
|
||||
import io.legato.kazusa.base.VMBaseActivity
|
||||
@@ -37,10 +39,12 @@ import io.legato.kazusa.ui.about.CrashLogsDialog
|
||||
import io.legato.kazusa.ui.main.bookshelf.BaseBookshelfFragment
|
||||
import io.legato.kazusa.ui.main.bookshelf.books.BookshelfFragment1
|
||||
import io.legato.kazusa.ui.main.bookshelf.books.BookshelfFragment2
|
||||
import io.legato.kazusa.ui.main.bookshelf.books.BookshelfFragment3
|
||||
import io.legato.kazusa.ui.main.explore.ExploreFragment
|
||||
import io.legato.kazusa.ui.main.my.MyFragment
|
||||
import io.legato.kazusa.ui.main.rss.RssFragment
|
||||
import io.legato.kazusa.ui.widget.dialog.TextDialog
|
||||
import io.legato.kazusa.utils.gone
|
||||
import io.legato.kazusa.utils.hideSoftInput
|
||||
import io.legato.kazusa.utils.isCreated
|
||||
import io.legato.kazusa.utils.observeEvent
|
||||
@@ -48,16 +52,12 @@ import io.legato.kazusa.utils.shouldHideSoftInput
|
||||
import io.legato.kazusa.utils.showDialogFragment
|
||||
import io.legato.kazusa.utils.toastOnUi
|
||||
import io.legato.kazusa.utils.viewbindingdelegate.viewBinding
|
||||
import io.legato.kazusa.utils.visible
|
||||
import kotlinx.coroutines.Dispatchers.IO
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
import kotlin.coroutines.resume
|
||||
import kotlin.coroutines.suspendCoroutine
|
||||
import androidx.core.view.get
|
||||
import com.google.android.material.navigation.NavigationBarView
|
||||
import io.legato.kazusa.ui.main.bookshelf.books.BookshelfFragment3
|
||||
import io.legato.kazusa.utils.gone
|
||||
import io.legato.kazusa.utils.visible
|
||||
|
||||
/**
|
||||
* 主界面
|
||||
@@ -90,7 +90,6 @@ class MainActivity : VMBaseActivity<ActivityMainBinding, MainViewModel>(),
|
||||
getNavigationBarView().getOrCreateBadge(R.id.menu_bookshelf)
|
||||
}
|
||||
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
|
||||
@@ -382,32 +381,25 @@ class MainActivity : VMBaseActivity<ActivityMainBinding, MainViewModel>(),
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
private fun upBottomMenu() {
|
||||
if (AppConfig.showBottomView)
|
||||
{
|
||||
getNavigationBarView().visible()
|
||||
val showDiscovery = AppConfig.showDiscovery
|
||||
val showRss = AppConfig.showRSS
|
||||
val menu = getNavigationBarView().menu
|
||||
menu.findItem(R.id.menu_discovery).isVisible = showDiscovery
|
||||
menu.findItem(R.id.menu_rss).isVisible = showRss
|
||||
|
||||
var index = 0
|
||||
if (showDiscovery) {
|
||||
index++
|
||||
realPositions[index] = idExplore
|
||||
}
|
||||
if (showRss) {
|
||||
index++
|
||||
realPositions[index] = idRss
|
||||
}
|
||||
val menu = getNavigationBarView().menu
|
||||
menu.findItem(R.id.menu_discovery).isVisible = AppConfig.showDiscovery
|
||||
menu.findItem(R.id.menu_rss).isVisible = AppConfig.showRSS
|
||||
var index = 0
|
||||
if (AppConfig.showDiscovery) {
|
||||
index++
|
||||
realPositions[index] = idMy
|
||||
bottomMenuCount = index + 1
|
||||
adapter.notifyDataSetChanged()
|
||||
realPositions[index] = idExplore
|
||||
}
|
||||
else getNavigationBarView().gone()
|
||||
if (AppConfig.showRSS) {
|
||||
index++
|
||||
realPositions[index] = idRss
|
||||
}
|
||||
index++
|
||||
realPositions[index] = idMy
|
||||
bottomMenuCount = index + 1
|
||||
adapter.notifyDataSetChanged()
|
||||
if (AppConfig.showBottomView) getNavigationBarView().visible()
|
||||
else binding.bottomNavigationView?.gone()
|
||||
}
|
||||
|
||||
private fun upHomePage() {
|
||||
@@ -450,7 +442,6 @@ class MainActivity : VMBaseActivity<ActivityMainBinding, MainViewModel>(),
|
||||
|
||||
}
|
||||
|
||||
|
||||
private inner class TabFragmentPageAdapter(fm: FragmentManager) :
|
||||
FragmentStatePagerAdapter(fm, BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT) {
|
||||
|
||||
|
||||
@@ -44,6 +44,12 @@ class DetailSeekBar @JvmOverloads constructor(
|
||||
binding.slider.valueTo = value.toFloat()
|
||||
}
|
||||
|
||||
var min: Int
|
||||
get() = binding.slider.valueFrom.toInt()
|
||||
set(value) {
|
||||
binding.slider.valueFrom = value.toFloat()
|
||||
}
|
||||
|
||||
init {
|
||||
val typedArray = context.obtainStyledAttributes(attrs, R.styleable.DetailSeekBar)
|
||||
isBottomBackground =
|
||||
@@ -57,17 +63,6 @@ class DetailSeekBar @JvmOverloads constructor(
|
||||
|
||||
binding.slider.valueTo = typedArray.getInteger(R.styleable.DetailSeekBar_max, 0).toFloat()
|
||||
typedArray.recycle()
|
||||
binding.ivSeekPlus.setOnClickListener {
|
||||
val newValue = (binding.slider.value + 1).coerceAtMost(binding.slider.valueTo)
|
||||
binding.slider.value = newValue
|
||||
onChanged?.invoke(newValue.toInt())
|
||||
}
|
||||
|
||||
binding.ivSeekReduce.setOnClickListener {
|
||||
val newValue = (binding.slider.value - 1).coerceAtLeast(binding.slider.valueFrom)
|
||||
binding.slider.value = newValue
|
||||
onChanged?.invoke(newValue.toInt())
|
||||
}
|
||||
|
||||
binding.slider.addOnChangeListener { _, value, fromUser ->
|
||||
upValue(value.toInt())
|
||||
|
||||
@@ -23,6 +23,7 @@ class SimpleCounterView @JvmOverloads constructor(
|
||||
|
||||
private var _progress = 0
|
||||
private var _max = 100
|
||||
private var _min = 0
|
||||
private val isBottomBackground: Boolean
|
||||
|
||||
var valueFormat: ((Int) -> String)? = null
|
||||
@@ -39,7 +40,7 @@ class SimpleCounterView @JvmOverloads constructor(
|
||||
var progress: Int
|
||||
get() = _progress
|
||||
set(value) {
|
||||
_progress = value.coerceIn(0, _max)
|
||||
_progress = value.coerceIn(_min, _max)
|
||||
updateValue()
|
||||
}
|
||||
|
||||
@@ -51,11 +52,22 @@ class SimpleCounterView @JvmOverloads constructor(
|
||||
updateValue()
|
||||
}
|
||||
|
||||
var min: Int
|
||||
get() = _min
|
||||
set(value) {
|
||||
_min = value
|
||||
_progress = _progress.coerceAtLeast(_min)
|
||||
updateValue()
|
||||
}
|
||||
|
||||
init {
|
||||
val typedArray = context.obtainStyledAttributes(attrs, R.styleable.DetailSeekBar)
|
||||
isBottomBackground = typedArray.getBoolean(R.styleable.DetailSeekBar_isBottomBackground, false)
|
||||
val title = typedArray.getText(R.styleable.DetailSeekBar_title)
|
||||
_max = typedArray.getInt(R.styleable.DetailSeekBar_max, 100)
|
||||
_min = typedArray.getInt(R.styleable.DetailSeekBar_min, 0)
|
||||
_progress = _progress.coerceIn(_min, _max)
|
||||
|
||||
typedArray.recycle()
|
||||
|
||||
binding.tvSeekTitle.text = title
|
||||
@@ -70,7 +82,7 @@ class SimpleCounterView @JvmOverloads constructor(
|
||||
}
|
||||
|
||||
binding.ivSeekReduce.setOnClickListener {
|
||||
if (_progress > 0) {
|
||||
if (_progress > _min) {
|
||||
_progress--
|
||||
updateValue()
|
||||
onChanged?.invoke(_progress)
|
||||
@@ -119,6 +131,7 @@ class SimpleCounterView @JvmOverloads constructor(
|
||||
customView {
|
||||
val detailSeekBar = DetailSeekBar(ctx).apply {
|
||||
max = _max
|
||||
min = _min
|
||||
progress = _progress
|
||||
valueFormat = this@SimpleCounterView.valueFormat
|
||||
setTitle(binding.tvSeekTitle.text)
|
||||
@@ -168,7 +181,7 @@ class SimpleCounterView @JvmOverloads constructor(
|
||||
|
||||
private val decrementRunnable = object : Runnable {
|
||||
override fun run() {
|
||||
if (_progress > 0) {
|
||||
if (_progress > _min) {
|
||||
_progress--
|
||||
updateValue()
|
||||
onChanged?.invoke(_progress)
|
||||
|
||||
Reference in New Issue
Block a user