From 1551891c15eb5d323cb1d425794876a753316091 Mon Sep 17 00:00:00 2001 From: Ivan Iskandar <12537387+ivaniskandar@users.noreply.github.com> Date: Sun, 10 Jul 2022 03:20:40 +0700 Subject: [PATCH] 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 --- .../kanade/presentation/manga/MangaScreen.kt | 94 +++++++------------ .../manga/MangaScreenConstants.kt | 8 ++ .../manga/components/MangaChapterListItem.kt | 4 +- .../tachiyomi/ui/manga/MangaPresenter.kt | 58 ++++++++++-- 4 files changed, 95 insertions(+), 69 deletions(-) diff --git a/app/src/main/java/eu/kanade/presentation/manga/MangaScreen.kt b/app/src/main/java/eu/kanade/presentation/manga/MangaScreen.kt index 189df5a8fd..a003a90215 100644 --- a/app/src/main/java/eu/kanade/presentation/manga/MangaScreen.kt +++ b/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, - state: MangaScreenState.Success, selected: SnapshotStateList, selectedPositions: Array, onChapterClicked: (Chapter) -> Unit, onDownloadChapter: ((List, 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, diff --git a/app/src/main/java/eu/kanade/presentation/manga/MangaScreenConstants.kt b/app/src/main/java/eu/kanade/presentation/manga/MangaScreenConstants.kt index 5fcac70a70..0e3b3bcdcd 100644 --- a/app/src/main/java/eu/kanade/presentation/manga/MangaScreenConstants.kt +++ b/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, +} diff --git a/app/src/main/java/eu/kanade/presentation/manga/components/MangaChapterListItem.kt b/app/src/main/java/eu/kanade/presentation/manga/components/MangaChapterListItem.kt index 583a0e59b0..80a42dc31e 100644 --- a/app/src/main/java/eu/kanade/presentation/manga/components/MangaChapterListItem.kt +++ b/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 }, diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/manga/MangaPresenter.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/manga/MangaPresenter.kt index d5bb94a303..850bc3cbb7 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/manga/MangaPresenter.kt +++ b/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(), + 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().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.toChapterItems(manga: DomainManga): List { + private fun List.toChapterItems( + context: Context, + manga: DomainManga, + dateRelativeTime: Int, + dateFormat: DateFormat, + ): List { 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, 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 = '.' }, +)