[优化] 优化Tab栏样式

This commit is contained in:
HapeLee
2026-03-28 00:35:14 +08:00
parent 9b7cd8ec3e
commit f612b7958e
4 changed files with 87 additions and 9 deletions
@@ -421,7 +421,8 @@ fun TocScreen(
selectedTabIndex = pagerState.currentPage, selectedTabIndex = pagerState.currentPage,
edgePadding = 0.dp, edgePadding = 0.dp,
divider = {}, divider = {},
modifier = Modifier.weight(1f) modifier = Modifier.weight(1f),
containerColor = Color.Transparent
) { ) {
Tab( Tab(
selected = pagerState.currentPage == 0, selected = pagerState.currentPage == 0,
@@ -12,10 +12,12 @@ import io.legado.app.data.appDb
import io.legado.app.data.entities.Book import io.legado.app.data.entities.Book
import io.legado.app.data.entities.BookChapter import io.legado.app.data.entities.BookChapter
import io.legado.app.data.entities.Bookmark import io.legado.app.data.entities.Bookmark
import io.legado.app.data.entities.ReplaceRule
import io.legado.app.help.book.BookHelp import io.legado.app.help.book.BookHelp
import io.legado.app.help.book.ContentProcessor import io.legado.app.help.book.ContentProcessor
import io.legado.app.help.book.isLocal import io.legado.app.help.book.isLocal
import io.legado.app.help.bookmark.BookmarkExporter import io.legado.app.help.bookmark.BookmarkExporter
import io.legado.app.help.config.AppConfig
import io.legado.app.model.CacheBook import io.legado.app.model.CacheBook
import io.legado.app.model.ReadBook import io.legado.app.model.ReadBook
import io.legado.app.model.localBook.LocalBook import io.legado.app.model.localBook.LocalBook
@@ -26,6 +28,7 @@ import io.legado.app.ui.widget.components.list.SelectableItem
import io.legado.app.utils.toastOnUi import io.legado.app.utils.toastOnUi
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.Job
import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.SharingStarted
@@ -97,6 +100,14 @@ private data class TocUiConfig(
val isReverse: Boolean val isReverse: Boolean
) )
private data class TitleCacheKey(
val bookUrl: String,
val useReplace: Boolean,
val rulesFingerprint: Int,
val chineseConverterType: Int,
val chapterCount: Int
)
data class FabAction(val icon: ImageVector, val label: String, val action: () -> Unit) data class FabAction(val icon: ImageVector, val label: String, val action: () -> Unit)
@OptIn(ExperimentalCoroutinesApi::class) @OptIn(ExperimentalCoroutinesApi::class)
@@ -209,12 +220,17 @@ class TocViewModel(
TocUiConfig(collapsed, useReplace, showWordCount, isReverse) TocUiConfig(collapsed, useReplace, showWordCount, isReverse)
} }
private val titleReplaceCache = MutableStateFlow<Map<Int, String>>(emptyMap())
private var titleCacheJob: Job? = null
private var lastTitleCacheKey: TitleCacheKey? = null
override val rawDataFlow: Flow<List<TocDomainItem>> = combine( override val rawDataFlow: Flow<List<TocDomainItem>> = combine(
bookState.filterNotNull().map { it.bookUrl }.distinctUntilChanged() bookState.filterNotNull().map { it.bookUrl }.distinctUntilChanged()
.flatMapLatest { appDb.bookChapterDao.getChapterListFlow(it) }, .flatMapLatest { appDb.bookChapterDao.getChapterListFlow(it) },
downloadContextFlow, downloadContextFlow,
uiConfigFlow uiConfigFlow,
) { originalChapters, downloadCtx, config -> titleReplaceCache
) { originalChapters, downloadCtx, config, cachedTitles ->
val book = bookState.value ?: return@combine emptyList() val book = bookState.value ?: return@combine emptyList()
val processedChapters = if (config.isReverse) { val processedChapters = if (config.isReverse) {
@@ -227,11 +243,19 @@ class TocViewModel(
ContentProcessor.get(book.name, book.origin).getTitleReplaceRules() ContentProcessor.get(book.name, book.origin).getTitleReplaceRules()
} else emptyList() } else emptyList()
updateTitleReplaceCacheIfNeeded(
book = book,
chapters = processedChapters,
replaceRules = replaceRules,
useReplace = config.useReplace
)
if (book.isLocal) { if (book.isLocal) {
return@combine processedChapters.map { chapter -> return@combine processedChapters.map { chapter ->
val baseTitle = chapter.getDisplayTitle(useReplace = false)
TocDomainItem( TocDomainItem(
chapter = chapter, chapter = chapter,
displayTitle = chapter.getDisplayTitle(replaceRules, true), displayTitle = cachedTitles[chapter.index] ?: baseTitle,
downloadState = DownloadState.LOCAL downloadState = DownloadState.LOCAL
) )
} }
@@ -251,9 +275,10 @@ class TocViewModel(
else -> DownloadState.NONE else -> DownloadState.NONE
} }
val baseTitle = chapter.getDisplayTitle(useReplace = false)
TocDomainItem( TocDomainItem(
chapter, chapter,
chapter.getDisplayTitle(replaceRules, true), cachedTitles[chapter.index] ?: baseTitle,
downloadState downloadState
) )
} }
@@ -506,4 +531,56 @@ class TocViewModel(
} }
} }
} }
private fun updateTitleReplaceCacheIfNeeded(
book: Book,
chapters: List<BookChapter>,
replaceRules: List<ReplaceRule>,
useReplace: Boolean
) {
val shouldUseReplace = useReplace && book.getUseReplaceRule() && replaceRules.isNotEmpty()
if (!shouldUseReplace) {
titleCacheJob?.cancel()
titleCacheJob = null
lastTitleCacheKey = null
if (titleReplaceCache.value.isNotEmpty()) {
titleReplaceCache.value = emptyMap()
}
return
}
val rulesFingerprint = replaceRules.fold(1) { acc, rule ->
var hash = acc
hash = 31 * hash + rule.id.hashCode()
hash = 31 * hash + rule.pattern.hashCode()
hash = 31 * hash + rule.replacement.hashCode()
hash = 31 * hash + rule.isRegex.hashCode()
hash = 31 * hash + rule.timeoutMillisecond.hashCode()
hash
}
val key = TitleCacheKey(
bookUrl = book.bookUrl,
useReplace = true,
rulesFingerprint = rulesFingerprint,
chineseConverterType = AppConfig.chineseConverterType,
chapterCount = chapters.size
)
val isJobActive = titleCacheJob?.isActive == true
if (key == lastTitleCacheKey && (isJobActive || titleReplaceCache.value.isNotEmpty())) {
return
}
lastTitleCacheKey = key
titleCacheJob?.cancel()
titleReplaceCache.value = emptyMap()
titleCacheJob = viewModelScope.launch(Dispatchers.Default) {
val newCache = HashMap<Int, String>(chapters.size)
chapters.forEach { chapter ->
newCache[chapter.index] = chapter.getDisplayTitle(replaceRules, true)
}
titleReplaceCache.value = newCache
}
}
} }
@@ -50,6 +50,7 @@ import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.runtime.setValue import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.hapticfeedback.HapticFeedbackType import androidx.compose.ui.hapticfeedback.HapticFeedbackType
import androidx.compose.ui.platform.ClipEntry import androidx.compose.ui.platform.ClipEntry
import androidx.compose.ui.platform.LocalClipboard import androidx.compose.ui.platform.LocalClipboard
@@ -65,7 +66,6 @@ import io.legado.app.base.BaseRuleEvent
import io.legado.app.data.entities.ReplaceRule import io.legado.app.data.entities.ReplaceRule
import io.legado.app.ui.widget.components.ActionItem import io.legado.app.ui.widget.components.ActionItem
import io.legado.app.ui.widget.components.DraggableSelectionHandler import io.legado.app.ui.widget.components.DraggableSelectionHandler
import io.legado.app.ui.widget.components.GlassTopAppBarDefaults
import io.legado.app.ui.widget.components.GroupManageBottomSheet import io.legado.app.ui.widget.components.GroupManageBottomSheet
import io.legado.app.ui.widget.components.card.ReorderableSelectionItem import io.legado.app.ui.widget.components.card.ReorderableSelectionItem
import io.legado.app.ui.widget.components.divider.PillDivider import io.legado.app.ui.widget.components.divider.PillDivider
@@ -352,7 +352,7 @@ fun ReplaceRuleScreen(
.coerceAtLeast(0), .coerceAtLeast(0),
edgePadding = 0.dp, edgePadding = 0.dp,
divider = {}, divider = {},
containerColor = GlassTopAppBarDefaults.containerColor(), containerColor = Color.Transparent,
) { ) {
tabItems.forEachIndexed { index, title -> tabItems.forEachIndexed { index, title ->
Tab( Tab(
+1 -1
View File
@@ -1,5 +1,5 @@
VERSION_MAJOR=3 VERSION_MAJOR=3
VERSION_MINOR=26 VERSION_MINOR=26
VERSION_PATCH=8 VERSION_PATCH=9
VERSION_SUFFIX=0 VERSION_SUFFIX=0
# 1 = Pre, 0 = Release # 1 = Pre, 0 = Release