Jelajahi Sumber

[优化] 优化Tab栏样式

HapeLee 7 bulan lalu
induk
melakukan
f612b7958e

+ 2 - 1
app/src/main/java/io/legado/app/ui/book/toc/TocScreen.kt

@@ -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,

+ 82 - 5
app/src/main/java/io/legado/app/ui/book/toc/TocViewModel.kt

@@ -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
-    ) { originalChapters, downloadCtx, config ->
+        uiConfigFlow,
+        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
+        }
+    }
+}

+ 2 - 2
app/src/main/java/io/legado/app/ui/replace/ReplaceRuleScreen.kt

@@ -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
app/version.properties

@@ -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