更新界面

This commit is contained in:
HapeLee
2025-08-21 04:58:42 +08:00
parent ebc4135242
commit bf514ffd26
18 changed files with 230 additions and 154 deletions
@@ -5,6 +5,7 @@ import android.graphics.Canvas
import android.graphics.Color
import android.graphics.Paint
import android.graphics.Rect
import android.graphics.Typeface
import android.text.TextPaint
import android.view.View
import androidx.recyclerview.widget.LinearLayoutManager
@@ -28,10 +29,11 @@ class BookmarkDecoration(
}
private val textPaint = TextPaint().apply {
textSize = 16f.spToPx()
textSize = 14f.spToPx()
color =
MaterialColors.getColor(context, androidx.appcompat.R.attr.colorPrimary, Color.BLACK)
isAntiAlias = true
typeface = Typeface.DEFAULT_BOLD
}
private val textRect = Rect()
@@ -8,6 +8,7 @@ import android.net.Uri
import android.os.Bundle
import android.os.Looper
import android.view.Gravity
import android.view.HapticFeedbackConstants
import android.view.InputDevice
import android.view.KeyEvent
import android.view.Menu
@@ -788,21 +789,25 @@ class ReadBookActivity : BaseReadBookActivity(),
/**
* 更新文字选择开始位置
*/
override fun upSelectedStart(x: Float, y: Float, top: Float) = binding.run {
override fun upSelectedStart(x: Float, y: Float, top: Float): Unit = binding.run {
cursorLeft.x = x - cursorLeft.width
cursorLeft.y = y
cursorLeft.visible(true)
textMenuPosition.x = x
textMenuPosition.y = top
root.performHapticFeedback(HapticFeedbackConstants.LONG_PRESS)
}
/**
* 更新文字选择结束位置
*/
override fun upSelectedEnd(x: Float, y: Float) = binding.run {
override fun upSelectedEnd(x: Float, y: Float): Unit = binding.run {
cursorRight.x = x
cursorRight.y = y
cursorRight.visible(true)
root.performHapticFeedback(HapticFeedbackConstants.LONG_PRESS)
}
/**
@@ -32,12 +32,12 @@ class BookmarkAdapter(context: Context, val callback: Callback) :
override fun registerListener(holder: ItemViewHolder, binding: ItemBookmarkBinding) {
binding.root.setOnClickListener {
getItem(holder.layoutPosition)?.let { bookmark ->
callback.onClick(bookmark)
callback.onLongClick(bookmark, holder.layoutPosition)
}
}
binding.root.onLongClick {
getItem(holder.layoutPosition)?.let { bookmark ->
callback.onLongClick(bookmark, holder.layoutPosition)
callback.onClick(bookmark)
}
}
@@ -19,6 +19,7 @@ import io.legato.kazusa.ui.widget.recycler.VerticalDivider
import io.legato.kazusa.utils.applyNavigationBarPadding
import io.legato.kazusa.utils.showDialogFragment
import io.legato.kazusa.utils.viewbindingdelegate.viewBinding
import io.legato.kazusa.utils.visible
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Dispatchers.IO
import kotlinx.coroutines.flow.catch
@@ -51,24 +52,27 @@ class BookmarkFragment : VMBaseFragment<TocViewModel>(R.layout.fragment_bookmark
binding.recyclerView.addItemDecoration(VerticalDivider(requireContext()))
binding.recyclerView.adapter = adapter
binding.recyclerView.applyNavigationBarPadding()
binding.tvEmptyMsg.visible(false)
}
override fun upBookmark(searchKey: String?) {
val book = viewModel.bookData.value ?: return
lifecycleScope.launch {
when {
val flow = when {
searchKey.isNullOrBlank() -> appDb.bookmarkDao.flowByBook(book.name, book.author)
else -> appDb.bookmarkDao.flowSearch(book.name, book.author, searchKey)
}.catch {
}
flow.catch {
AppLog.put("目录界面获取书签数据失败\n${it.localizedMessage}", it)
}.flowOn(IO).collect {
adapter.setItems(it)
}.flowOn(IO).collect { list ->
adapter.setItems(list)
binding.tvEmptyMsg.visible(list.isEmpty())
var scrollPos = 0
withContext(Dispatchers.Default) {
adapter.getItems().forEachIndexed { index, bookmark ->
if (bookmark.chapterIndex >= durChapterIndex) {
return@withContext
}
if (bookmark.chapterIndex >= durChapterIndex) return@withContext
scrollPos = index
}
}
@@ -14,15 +14,17 @@ import io.legato.kazusa.data.entities.BookChapter
import io.legato.kazusa.databinding.ItemChapterListBinding
import io.legato.kazusa.help.book.ContentProcessor
import io.legato.kazusa.help.config.AppConfig
import io.legato.kazusa.help.coroutine.Coroutine
import io.legato.kazusa.lib.theme.ThemeUtils
import io.legato.kazusa.utils.gone
import io.legato.kazusa.utils.longToastOnUi
import io.legato.kazusa.utils.themeColor
import io.legato.kazusa.utils.visible
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job
import kotlinx.coroutines.ensureActive
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import java.util.concurrent.ConcurrentHashMap
class ChapterListAdapter(context: Context, val callback: Callback) :
@@ -30,6 +32,7 @@ class ChapterListAdapter(context: Context, val callback: Callback) :
val cacheFileNames = hashSetOf<String>()
private val displayTitleMap = ConcurrentHashMap<String, String>()
private val handler = Handler(Looper.getMainLooper())
override val diffItemCallback: DiffUtil.ItemCallback<BookChapter>
@@ -58,7 +61,7 @@ class ChapterListAdapter(context: Context, val callback: Callback) :
}
private var upDisplayTileJob: Coroutine<*>? = null
private var upDisplayTileJob: Job? = null
override fun onCurrentListChanged() {
super.onCurrentListChanged()
@@ -72,42 +75,31 @@ class ChapterListAdapter(context: Context, val callback: Callback) :
fun upDisplayTitles(startIndex: Int) {
upDisplayTileJob?.cancel()
upDisplayTileJob = Coroutine.async(callback.scope) {
val book = callback.book ?: return@async
upDisplayTileJob = callback.scope.launch(Dispatchers.Default) {
val book = callback.book ?: return@launch
val replaceRules = ContentProcessor.get(book.name, book.origin).getTitleReplaceRules()
val useReplace = AppConfig.tocUiUseReplace && book.getUseReplaceRule()
val items = getItems()
launch {
for (i in startIndex until items.size) {
val item = items[i]
if (displayTitleMap[item.title] == null) {
ensureActive()
val displayTitle = item.getDisplayTitle(replaceRules, useReplace)
ensureActive()
displayTitleMap[item.title] = displayTitle
handler.post {
notifyItemChanged(i, true)
}
}
}
}
launch {
for (i in startIndex downTo 0) {
val item = items[i]
if (displayTitleMap[item.title] == null) {
ensureActive()
val displayTitle = item.getDisplayTitle(replaceRules, useReplace)
ensureActive()
displayTitleMap[item.title] = displayTitle
handler.post {
notifyItemChanged(i, true)
}
val indices = (startIndex until items.size) + (startIndex downTo 0)
for (i in indices) {
val item = items[i]
val key = item.url
if (displayTitleMap[key] == null) {
ensureActive()
val displayTitle = item.getDisplayTitle(replaceRules, useReplace)
displayTitleMap[key] = displayTitle
ensureActive()
withContext(Dispatchers.Main) {
notifyItemChanged(i, true)
}
}
}
}
}
private fun getDisplayTitle(chapter: BookChapter): String {
return displayTitleMap[chapter.title] ?: chapter.title
}
@@ -128,17 +120,22 @@ class ChapterListAdapter(context: Context, val callback: Callback) :
|| item.isVolume
|| cacheFileNames.contains(item.getFileName())
if (payloads.isEmpty()) {
if (isDur) {
tvChapterName.setTextColor(context.themeColor(androidx.appcompat.R.attr.colorPrimary))
tvChapterItem.setBackgroundColor(context.themeColor(com.google.android.material.R.attr.colorSurfaceContainer))
} else {
tvChapterName.setTextColor(context.themeColor(com.google.android.material.R.attr.colorOnSurface))
tvChapterItem.setBackgroundColor(context.themeColor(com.google.android.material.R.attr.colorSurface))
}
tvChapterName.text = getDisplayTitle(item)
if (item.isVolume) {
tvChapterItem.setBackgroundColor(context.themeColor(com.google.android.material.R.attr.colorSecondaryContainer))
ivVolume.visible()
tvChapterName.text = getDisplayTitle(item)
tvChapterName.textSize = 12f
tvChapterName.setTextColor(context.themeColor(com.google.android.material.R.attr.colorTertiary))
} else {
ivVolume.gone()
if (isDur) {
tvChapterName.setTextColor(context.themeColor(androidx.appcompat.R.attr.colorPrimary))
tvChapterItem.setBackgroundColor(context.themeColor(com.google.android.material.R.attr.colorSurfaceContainer))
} else {
tvChapterName.setTextColor(context.themeColor(com.google.android.material.R.attr.colorOnSurface))
tvChapterItem.setBackgroundColor(context.themeColor(com.google.android.material.R.attr.colorSurface))
}
tvChapterName.text = getDisplayTitle(item)
tvChapterItem.foreground =
ThemeUtils.resolveDrawable(context, android.R.attr.selectableItemBackground)
}
@@ -165,10 +162,10 @@ class ChapterListAdapter(context: Context, val callback: Callback) :
ivLocked.gone()
}
upHasCache(binding, isDur, cached)
upHasCache(binding, cached)
} else {
tvChapterName.text = getDisplayTitle(item)
upHasCache(binding, isDur, cached)
upHasCache(binding, cached)
}
}
}
@@ -187,12 +184,11 @@ class ChapterListAdapter(context: Context, val callback: Callback) :
}
}
private fun upHasCache(binding: ItemChapterListBinding, isDur: Boolean, cached: Boolean) =
private fun upHasCache(binding: ItemChapterListBinding, cached: Boolean) =
binding.apply {
when {
isDur -> ivChecked.gone()
cached -> {
ivChecked.setImageResource(R.drawable.ic_check)
ivChecked.setImageResource(R.drawable.ic_download_done)
ivChecked.visible()
}
@@ -1,7 +1,5 @@
package io.legato.kazusa.ui.book.toc
//import io.legado.app.lib.theme.bottomBackground
//import io.legado.app.lib.theme.getPrimaryTextColor
import android.annotation.SuppressLint
import android.app.Activity.RESULT_OK
import android.content.Intent
@@ -36,7 +34,6 @@ import io.legato.kazusa.utils.themeColor
import io.legato.kazusa.utils.toastOnUi
import io.legato.kazusa.utils.viewbindingdelegate.viewBinding
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers.Default
import kotlinx.coroutines.Dispatchers.IO
import kotlinx.coroutines.Dispatchers.Main
import kotlinx.coroutines.launch
@@ -79,7 +76,7 @@ class ChapterListFragment : VMBaseFragment<TocViewModel>(R.layout.fragment_chapt
toastOnUi("开始下载: ${adapter.getItem(position)?.title}")
CacheBook.start(requireContext(), book, chapter.index, chapter.index)
VibrationUtils.vibrate(context!!, 100)
VibrationUtils.vibratePattern(requireContext(), longArrayOf(0, 50, 30, 50), -1)
adapter.notifyItemChanged(position)
}
@@ -191,16 +188,11 @@ class ChapterListFragment : VMBaseFragment<TocViewModel>(R.layout.fragment_chapt
override fun observeLiveBus() {
observeEvent<Pair<Book, BookChapter>>(EventBus.SAVE_CONTENT) { (book, chapter) ->
viewModel.bookData.value?.bookUrl?.let { bookUrl ->
if (book.bookUrl == bookUrl) {
if (book.bookUrl == viewModel.bookData.value?.bookUrl) {
adapter.cacheFileNames.add(chapter.getFileName())
if (viewModel.searchKey.isNullOrEmpty()) {
adapter.notifyItemChanged(chapter.index, true)
} else {
adapter.getItems().forEachIndexed { index, bookChapter ->
if (bookChapter.index == chapter.index) {
adapter.notifyItemChanged(index, true)
}
}
val index = adapter.getItems().indexOfFirst { it.index == chapter.index }
if (index >= 0) {
adapter.notifyItemChanged(index, true)
}
}
}
@@ -225,15 +217,9 @@ class ChapterListFragment : VMBaseFragment<TocViewModel>(R.layout.fragment_chapt
override fun onListChanged() {
lifecycleScope.launch {
var scrollPos = 0
withContext(Default) {
adapter.getItems().forEachIndexed { index, bookChapter ->
if (bookChapter.index >= durChapterIndex) {
return@withContext
}
scrollPos = index
}
}
val scrollPos = adapter.getItems()
.indexOfLast { it.index < durChapterIndex }
.coerceAtLeast(0)
mLayoutManager.scrollToPositionWithOffset(scrollPos, 0)
adapter.upDisplayTitles(scrollPos)
}
@@ -60,8 +60,9 @@ class TocActivity : VMBaseActivity<ActivityChapterListBinding, TocViewModel>(),
tabLayout = binding.tabLayout
binding.viewPager.adapter = TabFragmentPageAdapter()
tabLayout.setupWithViewPager(binding.viewPager)
viewModel.bookData.observe(this) {
menu?.setGroupVisible(R.id.menu_group_text, it.isLocalTxt)
viewModel.bookData.observe(this) { book ->
menu?.setGroupVisible(R.id.menu_group_text, book.isLocalTxt)
supportActionBar?.title = book.name
}
intent.getStringExtra("bookUrl")?.let {
viewModel.initBook(it)