[优化] 优化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,
edgePadding = 0.dp,
divider = {},
modifier = Modifier.weight(1f)
modifier = Modifier.weight(1f),
containerColor = Color.Transparent
) {
Tab(
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.BookChapter
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.ContentProcessor
import io.legado.app.help.book.isLocal
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.ReadBook
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 kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.Job
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.SharingStarted
@@ -97,6 +100,14 @@ private data class TocUiConfig(
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)
@OptIn(ExperimentalCoroutinesApi::class)
@@ -209,12 +220,17 @@ class TocViewModel(
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(
bookState.filterNotNull().map { it.bookUrl }.distinctUntilChanged()
.flatMapLatest { appDb.bookChapterDao.getChapterListFlow(it) },
downloadContextFlow,
uiConfigFlow
) { originalChapters, downloadCtx, config ->
uiConfigFlow,
titleReplaceCache
) { originalChapters, downloadCtx, config, cachedTitles ->
val book = bookState.value ?: return@combine emptyList()
val processedChapters = if (config.isReverse) {
@@ -227,11 +243,19 @@ class TocViewModel(
ContentProcessor.get(book.name, book.origin).getTitleReplaceRules()
} else emptyList()
updateTitleReplaceCacheIfNeeded(
book = book,
chapters = processedChapters,
replaceRules = replaceRules,
useReplace = config.useReplace
)
if (book.isLocal) {
return@combine processedChapters.map { chapter ->
val baseTitle = chapter.getDisplayTitle(useReplace = false)
TocDomainItem(
chapter = chapter,
displayTitle = chapter.getDisplayTitle(replaceRules, true),
displayTitle = cachedTitles[chapter.index] ?: baseTitle,
downloadState = DownloadState.LOCAL
)
}
@@ -251,9 +275,10 @@ class TocViewModel(
else -> DownloadState.NONE
}
val baseTitle = chapter.getDisplayTitle(useReplace = false)
TocDomainItem(
chapter,
chapter.getDisplayTitle(replaceRules, true),
cachedTitles[chapter.index] ?: baseTitle,
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.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.hapticfeedback.HapticFeedbackType
import androidx.compose.ui.platform.ClipEntry
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.ui.widget.components.ActionItem
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.card.ReorderableSelectionItem
import io.legado.app.ui.widget.components.divider.PillDivider
@@ -352,7 +352,7 @@ fun ReplaceRuleScreen(
.coerceAtLeast(0),
edgePadding = 0.dp,
divider = {},
containerColor = GlassTopAppBarDefaults.containerColor(),
containerColor = Color.Transparent,
) {
tabItems.forEachIndexed { index, title ->
Tab(