瀏覽代碼

MangaScreen: Improve chapter list scrolling performance (#7491)

* MangaScreen: Improve chapter list scrolling performance

Process chapter title, date and read progress string ahead of time

* Use enum for contentType and add key
Ivan Iskandar 2 年之前
父節點
當前提交
1551891c15

+ 33 - 61
app/src/main/java/eu/kanade/presentation/manga/MangaScreen.kt

@@ -44,7 +44,6 @@ import androidx.compose.ui.Alignment
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.hapticfeedback.HapticFeedbackType
 import androidx.compose.ui.layout.onSizeChanged
-import androidx.compose.ui.platform.LocalContext
 import androidx.compose.ui.platform.LocalDensity
 import androidx.compose.ui.platform.LocalHapticFeedback
 import androidx.compose.ui.platform.LocalLayoutDirection
@@ -52,7 +51,6 @@ import androidx.compose.ui.res.stringResource
 import com.google.accompanist.swiperefresh.SwipeRefresh
 import com.google.accompanist.swiperefresh.rememberSwipeRefreshState
 import eu.kanade.domain.chapter.model.Chapter
-import eu.kanade.domain.manga.model.Manga.Companion.CHAPTER_DISPLAY_NUMBER
 import eu.kanade.presentation.components.ExtendedFloatingActionButton
 import eu.kanade.presentation.components.Scaffold
 import eu.kanade.presentation.components.SwipeRefreshIndicator
@@ -73,16 +71,6 @@ import eu.kanade.tachiyomi.source.SourceManager
 import eu.kanade.tachiyomi.source.getNameForMangaInfo
 import eu.kanade.tachiyomi.ui.manga.ChapterItem
 import eu.kanade.tachiyomi.ui.manga.MangaScreenState
-import eu.kanade.tachiyomi.util.lang.toRelativeString
-import java.text.DecimalFormat
-import java.text.DecimalFormatSymbols
-import java.util.Date
-
-private val chapterDecimalFormat = DecimalFormat(
-    "#.###",
-    DecimalFormatSymbols()
-        .apply { decimalSeparator = '.' },
-)
 
 @Composable
 fun MangaScreen(
@@ -321,7 +309,10 @@ private fun MangaScreenSmallImpl(
                     state = chapterListState,
                     contentPadding = noTopContentPadding,
                 ) {
-                    item(contentType = "info_box") {
+                    item(
+                        key = MangaScreenItem.INFO_BOX,
+                        contentType = MangaScreenItem.INFO_BOX,
+                    ) {
                         MangaInfoBox(
                             windowWidthSizeClass = WindowWidthSizeClass.Compact,
                             appBarPadding = topPadding,
@@ -337,7 +328,10 @@ private fun MangaScreenSmallImpl(
                         )
                     }
 
-                    item(contentType = "action_row") {
+                    item(
+                        key = MangaScreenItem.ACTION_ROW,
+                        contentType = MangaScreenItem.ACTION_ROW,
+                    ) {
                         MangaActionRow(
                             favorite = state.manga.favorite,
                             trackingCount = state.trackingCount,
@@ -348,7 +342,10 @@ private fun MangaScreenSmallImpl(
                         )
                     }
 
-                    item(contentType = "desc") {
+                    item(
+                        key = MangaScreenItem.DESCRIPTION_WITH_TAG,
+                        contentType = MangaScreenItem.DESCRIPTION_WITH_TAG,
+                    ) {
                         ExpandableMangaDescription(
                             defaultExpandState = state.isFromSource,
                             description = state.manga.description,
@@ -357,7 +354,10 @@ private fun MangaScreenSmallImpl(
                         )
                     }
 
-                    item(contentType = "header") {
+                    item(
+                        key = MangaScreenItem.CHAPTER_HEADER,
+                        contentType = MangaScreenItem.CHAPTER_HEADER,
+                    ) {
                         ChapterHeader(
                             chapterCount = chapters.size,
                             isChapterFiltered = state.manga.chaptersFiltered(),
@@ -367,7 +367,6 @@ private fun MangaScreenSmallImpl(
 
                     sharedChapterItems(
                         chapters = chapters,
-                        state = state,
                         selected = selected,
                         selectedPositions = selectedPositions,
                         onChapterClicked = onChapterClicked,
@@ -564,7 +563,10 @@ fun MangaScreenLargeImpl(
                         state = chapterListState,
                         contentPadding = withNavBarContentPadding,
                     ) {
-                        item(contentType = "header") {
+                        item(
+                            key = MangaScreenItem.CHAPTER_HEADER,
+                            contentType = MangaScreenItem.CHAPTER_HEADER,
+                        ) {
                             ChapterHeader(
                                 chapterCount = chapters.size,
                                 isChapterFiltered = state.manga.chaptersFiltered(),
@@ -574,7 +576,6 @@ fun MangaScreenLargeImpl(
 
                         sharedChapterItems(
                             chapters = chapters,
-                            state = state,
                             selected = selected,
                             selectedPositions = selectedPositions,
                             onChapterClicked = onChapterClicked,
@@ -637,56 +638,27 @@ private fun SharedMangaBottomActionMenu(
 
 private fun LazyListScope.sharedChapterItems(
     chapters: List<ChapterItem>,
-    state: MangaScreenState.Success,
     selected: SnapshotStateList<ChapterItem>,
     selectedPositions: Array<Int>,
     onChapterClicked: (Chapter) -> Unit,
     onDownloadChapter: ((List<ChapterItem>, ChapterDownloadAction) -> Unit)?,
 ) {
-    items(items = chapters) { chapterItem ->
-        val context = LocalContext.current
+    items(
+        items = chapters,
+        key = { it.chapter.id },
+        contentType = { MangaScreenItem.CHAPTER },
+    ) { chapterItem ->
         val haptic = LocalHapticFeedback.current
-
-        val (chapter, downloadState, downloadProgress) = chapterItem
-        val chapterTitle = if (state.manga.displayMode == CHAPTER_DISPLAY_NUMBER) {
-            stringResource(
-                id = R.string.display_mode_chapter,
-                chapterDecimalFormat.format(chapter.chapterNumber.toDouble()),
-            )
-        } else {
-            chapter.name
-        }
-        val date = remember(chapter.dateUpload) {
-            chapter.dateUpload
-                .takeIf { it > 0 }
-                ?.let {
-                    Date(it).toRelativeString(
-                        context,
-                        state.dateRelativeTime,
-                        state.dateFormat,
-                    )
-                }
-        }
-        val lastPageRead = remember(chapter.lastPageRead) {
-            chapter.lastPageRead.takeIf { !chapter.read && it > 0 }
-        }
-        val scanlator = remember(chapter.scanlator) { chapter.scanlator.takeIf { !it.isNullOrBlank() } }
-
         MangaChapterListItem(
-            title = chapterTitle,
-            date = date,
-            readProgress = lastPageRead?.let {
-                stringResource(
-                    id = R.string.chapter_progress,
-                    it + 1,
-                )
-            },
-            scanlator = scanlator,
-            read = chapter.read,
-            bookmark = chapter.bookmark,
+            title = chapterItem.chapterTitleString,
+            date = chapterItem.dateUploadString,
+            readProgress = chapterItem.readProgressString,
+            scanlator = chapterItem.chapter.scanlator.takeIf { !it.isNullOrBlank() },
+            read = chapterItem.chapter.read,
+            bookmark = chapterItem.chapter.bookmark,
             selected = selected.contains(chapterItem),
-            downloadStateProvider = { downloadState },
-            downloadProgressProvider = { downloadProgress },
+            downloadStateProvider = { chapterItem.downloadState },
+            downloadProgressProvider = { chapterItem.downloadProgress },
             onLongClick = {
                 val dispatched = onChapterItemLongClick(
                     chapterItem = chapterItem,

+ 8 - 0
app/src/main/java/eu/kanade/presentation/manga/MangaScreenConstants.kt

@@ -20,3 +20,11 @@ enum class EditCoverAction {
     EDIT,
     DELETE,
 }
+
+enum class MangaScreenItem {
+    INFO_BOX,
+    ACTION_ROW,
+    DESCRIPTION_WITH_TAG,
+    CHAPTER_HEADER,
+    CHAPTER,
+}

+ 2 - 2
app/src/main/java/eu/kanade/presentation/manga/components/MangaChapterListItem.kt

@@ -81,8 +81,8 @@ fun MangaChapterListItem(
                 }
                 Text(
                     text = title,
-                    style = MaterialTheme.typography.bodyMedium
-                        .copy(color = textColor),
+                    color = textColor,
+                    style = MaterialTheme.typography.bodyMedium,
                     maxLines = 1,
                     overflow = TextOverflow.Ellipsis,
                     onTextLayout = { textHeight = it.size.height },

+ 52 - 6
app/src/main/java/eu/kanade/tachiyomi/ui/manga/MangaPresenter.kt

@@ -1,5 +1,7 @@
 package eu.kanade.tachiyomi.ui.manga
 
+import android.app.Application
+import android.content.Context
 import android.os.Bundle
 import androidx.compose.runtime.Immutable
 import eu.kanade.domain.category.interactor.GetCategories
@@ -24,6 +26,7 @@ import eu.kanade.domain.track.interactor.GetTracks
 import eu.kanade.domain.track.interactor.InsertTrack
 import eu.kanade.domain.track.model.toDbTrack
 import eu.kanade.domain.track.model.toDomainTrack
+import eu.kanade.tachiyomi.R
 import eu.kanade.tachiyomi.data.database.models.Track
 import eu.kanade.tachiyomi.data.download.DownloadManager
 import eu.kanade.tachiyomi.data.download.model.Download
@@ -40,6 +43,7 @@ import eu.kanade.tachiyomi.util.chapter.ChapterSettingsHelper
 import eu.kanade.tachiyomi.util.chapter.getChapterSort
 import eu.kanade.tachiyomi.util.lang.launchIO
 import eu.kanade.tachiyomi.util.lang.launchUI
+import eu.kanade.tachiyomi.util.lang.toRelativeString
 import eu.kanade.tachiyomi.util.lang.withUIContext
 import eu.kanade.tachiyomi.util.preference.asImmediateFlow
 import eu.kanade.tachiyomi.util.removeCovers
@@ -68,6 +72,9 @@ import rx.schedulers.Schedulers
 import uy.kohesive.injekt.Injekt
 import uy.kohesive.injekt.api.get
 import java.text.DateFormat
+import java.text.DecimalFormat
+import java.text.DecimalFormatSymbols
+import java.util.Date
 import eu.kanade.domain.chapter.model.Chapter as DomainChapter
 import eu.kanade.domain.manga.model.Manga as DomainManga
 
@@ -154,15 +161,18 @@ class MangaPresenter(
 
             getMangaAndChapters.subscribe(mangaId)
                 .collectLatest { (manga, chapters) ->
-                    val chapterItems = chapters.toChapterItems(manga)
+                    val chapterItems = chapters.toChapterItems(
+                        context = view?.activity ?: Injekt.get<Application>(),
+                        manga = manga,
+                        dateRelativeTime = preferences.relativeTime().get(),
+                        dateFormat = preferences.dateFormat(),
+                    )
                     _state.update { currentState ->
                         when (currentState) {
                             // Initialize success state
                             MangaScreenState.Loading -> MangaScreenState.Success(
                                 manga = manga,
                                 source = Injekt.get<SourceManager>().getOrStub(manga.source),
-                                dateRelativeTime = preferences.relativeTime().get(),
-                                dateFormat = preferences.dateFormat(),
                                 isFromSource = isFromSource,
                                 trackingAvailable = trackManager.hasLoggedServices(),
                                 chapters = chapterItems,
@@ -428,7 +438,12 @@ class MangaPresenter(
         }
     }
 
-    private fun List<DomainChapter>.toChapterItems(manga: DomainManga): List<ChapterItem> {
+    private fun List<DomainChapter>.toChapterItems(
+        context: Context,
+        manga: DomainManga,
+        dateRelativeTime: Int,
+        dateFormat: DateFormat,
+    ): List<ChapterItem> {
         return map { chapter ->
             val activeDownload = downloadManager.queue.find { chapter.id == it.chapter.id }
             val downloaded = downloadManager.isChapterDownloaded(chapter.name, chapter.scanlator, manga.title, manga.source)
@@ -441,6 +456,29 @@ class MangaPresenter(
                 chapter = chapter,
                 downloadState = downloadState,
                 downloadProgress = activeDownload?.progress ?: 0,
+                chapterTitleString = if (manga.displayMode == DomainManga.CHAPTER_DISPLAY_NUMBER) {
+                    context.getString(
+                        R.string.display_mode_chapter,
+                        chapterDecimalFormat.format(chapter.chapterNumber.toDouble()),
+                    )
+                } else {
+                    chapter.name
+                },
+                dateUploadString = chapter.dateUpload
+                    .takeIf { it > 0 }
+                    ?.let {
+                        Date(it).toRelativeString(
+                            context,
+                            dateRelativeTime,
+                            dateFormat,
+                        )
+                    },
+                readProgressString = chapter.lastPageRead.takeIf { !chapter.read && it > 0 }?.let {
+                    context.getString(
+                        R.string.chapter_progress,
+                        it + 1,
+                    )
+                },
             )
         }
     }
@@ -853,8 +891,6 @@ sealed class MangaScreenState {
     data class Success(
         val manga: DomainManga,
         val source: Source,
-        val dateRelativeTime: Int,
-        val dateFormat: DateFormat,
         val isFromSource: Boolean,
         val chapters: List<ChapterItem>,
         val trackingAvailable: Boolean = false,
@@ -909,6 +945,16 @@ data class ChapterItem(
     val chapter: DomainChapter,
     val downloadState: Download.State,
     val downloadProgress: Int,
+
+    val chapterTitleString: String,
+    val dateUploadString: String?,
+    val readProgressString: String?,
 ) {
     val isDownloaded = downloadState == Download.State.DOWNLOADED
 }
+
+private val chapterDecimalFormat = DecimalFormat(
+    "#.###",
+    DecimalFormatSymbols()
+        .apply { decimalSeparator = '.' },
+)