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

(cherry picked from commit 1551891c15eb5d323cb1d425794876a753316091)

# Conflicts:
#	app/src/main/java/eu/kanade/presentation/manga/MangaScreen.kt
#	app/src/main/java/eu/kanade/tachiyomi/ui/manga/MangaPresenter.kt
This commit is contained in:
Ivan Iskandar 2022-07-10 03:20:40 +07:00 committed by Jobobby04
parent d3e8ae54d9
commit d3b59768d4
4 changed files with 121 additions and 78 deletions

View File

@ -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
@ -76,18 +74,8 @@ import eu.kanade.tachiyomi.source.getNameForMangaInfo
import eu.kanade.tachiyomi.source.online.MetadataSource
import eu.kanade.tachiyomi.ui.manga.ChapterItem
import eu.kanade.tachiyomi.ui.manga.MangaScreenState
import eu.kanade.tachiyomi.util.lang.toRelativeString
import exh.source.MERGED_SOURCE_ID
import exh.source.getMainSource
import java.text.DecimalFormat
import java.text.DecimalFormatSymbols
import java.util.Date
private val chapterDecimalFormat = DecimalFormat(
"#.###",
DecimalFormatSymbols()
.apply { decimalSeparator = '.' },
)
@Composable
fun MangaScreen(
@ -361,7 +349,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,
@ -377,7 +368,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,
@ -393,7 +387,10 @@ private fun MangaScreenSmallImpl(
// SY -->
if (metadataSource != null) {
item(contentType = "metadata_info") {
item(
key = MangaScreenItem.METADATA_INFO,
contentType = MangaScreenItem.METADATA_INFO,
) {
metadataSource.DescriptionComposable(
state = state,
openMetadataViewer = onMetadataViewerClicked,
@ -403,7 +400,10 @@ private fun MangaScreenSmallImpl(
}
// SY <--
item(contentType = "desc") {
item(
key = MangaScreenItem.DESCRIPTION_WITH_TAG,
contentType = MangaScreenItem.DESCRIPTION_WITH_TAG,
) {
ExpandableMangaDescription(
defaultExpandState = state.isFromSource,
description = state.manga.description,
@ -420,7 +420,10 @@ private fun MangaScreenSmallImpl(
// SY -->
if (!state.showRecommendationsInOverflow || state.showMergeWithAnother) {
item(contentType = "info_buttons") {
item(
key = MangaScreenItem.INFO_BUTTONS,
contentType = MangaScreenItem.INFO_BUTTONS,
) {
MangaInfoButtons(
showRecommendsButton = !state.showRecommendationsInOverflow,
showMergeWithAnotherButton = state.showMergeWithAnother,
@ -431,7 +434,10 @@ private fun MangaScreenSmallImpl(
}
// SY <--
item(contentType = "header") {
item(
key = MangaScreenItem.CHAPTER_HEADER,
contentType = MangaScreenItem.CHAPTER_HEADER,
) {
ChapterHeader(
chapterCount = chapters.size,
isChapterFiltered = state.manga.chaptersFiltered(),
@ -441,7 +447,6 @@ private fun MangaScreenSmallImpl(
sharedChapterItems(
chapters = chapters,
state = state,
selected = selected,
selectedPositions = selectedPositions,
onChapterClicked = onChapterClicked,
@ -680,7 +685,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(),
@ -690,7 +698,6 @@ fun MangaScreenLargeImpl(
sharedChapterItems(
chapters = chapters,
state = state,
selected = selected,
selectedPositions = selectedPositions,
onChapterClicked = onChapterClicked,
@ -753,56 +760,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 { /* SY --> */(!chapter.read || state.alwaysShowPageProgress)/* SY <-- */ && 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,

View File

@ -20,3 +20,21 @@ enum class EditCoverAction {
EDIT,
DELETE,
}
enum class MangaScreenItem {
INFO_BOX,
ACTION_ROW,
// SY -->
METADATA_INFO,
// SY <--
DESCRIPTION_WITH_TAG,
// SY -->
INFO_BUTTONS,
// SY <--
CHAPTER_HEADER,
CHAPTER,
}

View File

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

View File

@ -1,5 +1,6 @@
package eu.kanade.tachiyomi.ui.manga
import android.app.Application
import android.content.Context
import android.os.Bundle
import androidx.compose.runtime.Immutable
@ -60,6 +61,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
@ -80,7 +82,6 @@ import exh.metadata.metadata.base.RaisedSearchMetadata
import exh.source.MERGED_SOURCE_ID
import exh.source.getMainSource
import exh.source.isEhBasedManga
import exh.source.isEhBasedSource
import exh.source.mangaDexSourceIds
import exh.util.nullIfEmpty
import exh.util.trimOrNull
@ -110,6 +111,9 @@ import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get
import uy.kohesive.injekt.injectLazy
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
import eu.kanade.tachiyomi.data.database.models.Manga.Companion as DbManga
@ -284,7 +288,20 @@ class MangaPresenter(
}
// SY <--
.collectLatest { (manga, chapters, flatMetadata, mergedData) ->
val chapterItems = chapters.toChapterItems(manga, mergedData)
val chapterItems = chapters.toChapterItems(
context = view?.activity ?: Injekt.get<Application>(),
manga = manga,
// SY -->
dateRelativeTime = if (manga.isEhBasedManga()) 0 else preferences.relativeTime().get(),
dateFormat = if (manga.isEhBasedManga()) {
MetadataUtil.EX_DATE_FORMAT
} else {
preferences.dateFormat()
},
mergedData = mergedData,
alwaysShowReadingProgress = preferences.preserveReadingPosition().get() && manga.isEhBasedManga(),
// SY <--
)
_state.update { currentState ->
when (currentState) {
// Initialize success state
@ -293,12 +310,6 @@ class MangaPresenter(
MangaScreenState.Success(
manga = manga,
source = source,
dateRelativeTime = if (source.isEhBasedSource()) 0 else preferences.relativeTime().get(),
dateFormat = if (source.isEhBasedSource()) {
MetadataUtil.EX_DATE_FORMAT
} else {
preferences.dateFormat()
},
isFromSource = isFromSource,
trackingAvailable = trackManager.hasLoggedServices(),
chapters = chapterItems,
@ -306,7 +317,6 @@ class MangaPresenter(
mergedData = mergedData,
showRecommendationsInOverflow = preferences.recommendsInOverflow().get(),
showMergeWithAnother = smartSearched,
alwaysShowPageProgress = preferences.preserveReadingPosition().get() && manga.isEhBasedManga(),
)
}
@ -818,7 +828,14 @@ class MangaPresenter(
}
}
private fun List<DomainChapter>.toChapterItems(manga: DomainManga, mergedData: MergedMangaData?): List<ChapterItem> {
private fun List<DomainChapter>.toChapterItems(
context: Context,
manga: DomainManga,
dateRelativeTime: Int,
dateFormat: DateFormat,
mergedData: MergedMangaData?,
alwaysShowReadingProgress: Boolean,
): List<ChapterItem> {
return map { chapter ->
val activeDownload = downloadManager.queue.find { chapter.id == it.chapter.id }
val chapter = chapter.let { if (mergedData != null) it.toMergedDownloadedChapter() else it }
@ -840,6 +857,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 { /* SY --> */(!chapter.read || alwaysShowReadingProgress)/* SY <-- */ && it > 0 }?.let {
context.getString(
R.string.chapter_progress,
it + 1,
)
},
)
}
}
@ -1319,8 +1359,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,
@ -1334,7 +1372,6 @@ sealed class MangaScreenState {
val mergedData: MergedMangaData?,
val showRecommendationsInOverflow: Boolean,
val showMergeWithAnother: Boolean,
val alwaysShowPageProgress: Boolean,
// SY <--
) : MangaScreenState() {
@ -1386,6 +1423,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 = '.' },
)