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
This commit is contained in:
Ivan Iskandar 2022-07-10 03:20:40 +07:00 committed by GitHub
parent b15073fd61
commit 1551891c15
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 95 additions and 69 deletions

View File

@ -44,7 +44,6 @@ import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.hapticfeedback.HapticFeedbackType import androidx.compose.ui.hapticfeedback.HapticFeedbackType
import androidx.compose.ui.layout.onSizeChanged import androidx.compose.ui.layout.onSizeChanged
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.platform.LocalDensity import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.platform.LocalHapticFeedback import androidx.compose.ui.platform.LocalHapticFeedback
import androidx.compose.ui.platform.LocalLayoutDirection 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.SwipeRefresh
import com.google.accompanist.swiperefresh.rememberSwipeRefreshState import com.google.accompanist.swiperefresh.rememberSwipeRefreshState
import eu.kanade.domain.chapter.model.Chapter 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.ExtendedFloatingActionButton
import eu.kanade.presentation.components.Scaffold import eu.kanade.presentation.components.Scaffold
import eu.kanade.presentation.components.SwipeRefreshIndicator 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.source.getNameForMangaInfo
import eu.kanade.tachiyomi.ui.manga.ChapterItem import eu.kanade.tachiyomi.ui.manga.ChapterItem
import eu.kanade.tachiyomi.ui.manga.MangaScreenState 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 @Composable
fun MangaScreen( fun MangaScreen(
@ -321,7 +309,10 @@ private fun MangaScreenSmallImpl(
state = chapterListState, state = chapterListState,
contentPadding = noTopContentPadding, contentPadding = noTopContentPadding,
) { ) {
item(contentType = "info_box") { item(
key = MangaScreenItem.INFO_BOX,
contentType = MangaScreenItem.INFO_BOX,
) {
MangaInfoBox( MangaInfoBox(
windowWidthSizeClass = WindowWidthSizeClass.Compact, windowWidthSizeClass = WindowWidthSizeClass.Compact,
appBarPadding = topPadding, appBarPadding = topPadding,
@ -337,7 +328,10 @@ private fun MangaScreenSmallImpl(
) )
} }
item(contentType = "action_row") { item(
key = MangaScreenItem.ACTION_ROW,
contentType = MangaScreenItem.ACTION_ROW,
) {
MangaActionRow( MangaActionRow(
favorite = state.manga.favorite, favorite = state.manga.favorite,
trackingCount = state.trackingCount, 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( ExpandableMangaDescription(
defaultExpandState = state.isFromSource, defaultExpandState = state.isFromSource,
description = state.manga.description, description = state.manga.description,
@ -357,7 +354,10 @@ private fun MangaScreenSmallImpl(
) )
} }
item(contentType = "header") { item(
key = MangaScreenItem.CHAPTER_HEADER,
contentType = MangaScreenItem.CHAPTER_HEADER,
) {
ChapterHeader( ChapterHeader(
chapterCount = chapters.size, chapterCount = chapters.size,
isChapterFiltered = state.manga.chaptersFiltered(), isChapterFiltered = state.manga.chaptersFiltered(),
@ -367,7 +367,6 @@ private fun MangaScreenSmallImpl(
sharedChapterItems( sharedChapterItems(
chapters = chapters, chapters = chapters,
state = state,
selected = selected, selected = selected,
selectedPositions = selectedPositions, selectedPositions = selectedPositions,
onChapterClicked = onChapterClicked, onChapterClicked = onChapterClicked,
@ -564,7 +563,10 @@ fun MangaScreenLargeImpl(
state = chapterListState, state = chapterListState,
contentPadding = withNavBarContentPadding, contentPadding = withNavBarContentPadding,
) { ) {
item(contentType = "header") { item(
key = MangaScreenItem.CHAPTER_HEADER,
contentType = MangaScreenItem.CHAPTER_HEADER,
) {
ChapterHeader( ChapterHeader(
chapterCount = chapters.size, chapterCount = chapters.size,
isChapterFiltered = state.manga.chaptersFiltered(), isChapterFiltered = state.manga.chaptersFiltered(),
@ -574,7 +576,6 @@ fun MangaScreenLargeImpl(
sharedChapterItems( sharedChapterItems(
chapters = chapters, chapters = chapters,
state = state,
selected = selected, selected = selected,
selectedPositions = selectedPositions, selectedPositions = selectedPositions,
onChapterClicked = onChapterClicked, onChapterClicked = onChapterClicked,
@ -637,56 +638,27 @@ private fun SharedMangaBottomActionMenu(
private fun LazyListScope.sharedChapterItems( private fun LazyListScope.sharedChapterItems(
chapters: List<ChapterItem>, chapters: List<ChapterItem>,
state: MangaScreenState.Success,
selected: SnapshotStateList<ChapterItem>, selected: SnapshotStateList<ChapterItem>,
selectedPositions: Array<Int>, selectedPositions: Array<Int>,
onChapterClicked: (Chapter) -> Unit, onChapterClicked: (Chapter) -> Unit,
onDownloadChapter: ((List<ChapterItem>, ChapterDownloadAction) -> Unit)?, onDownloadChapter: ((List<ChapterItem>, ChapterDownloadAction) -> Unit)?,
) { ) {
items(items = chapters) { chapterItem -> items(
val context = LocalContext.current items = chapters,
key = { it.chapter.id },
contentType = { MangaScreenItem.CHAPTER },
) { chapterItem ->
val haptic = LocalHapticFeedback.current 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( MangaChapterListItem(
title = chapterTitle, title = chapterItem.chapterTitleString,
date = date, date = chapterItem.dateUploadString,
readProgress = lastPageRead?.let { readProgress = chapterItem.readProgressString,
stringResource( scanlator = chapterItem.chapter.scanlator.takeIf { !it.isNullOrBlank() },
id = R.string.chapter_progress, read = chapterItem.chapter.read,
it + 1, bookmark = chapterItem.chapter.bookmark,
)
},
scanlator = scanlator,
read = chapter.read,
bookmark = chapter.bookmark,
selected = selected.contains(chapterItem), selected = selected.contains(chapterItem),
downloadStateProvider = { downloadState }, downloadStateProvider = { chapterItem.downloadState },
downloadProgressProvider = { downloadProgress }, downloadProgressProvider = { chapterItem.downloadProgress },
onLongClick = { onLongClick = {
val dispatched = onChapterItemLongClick( val dispatched = onChapterItemLongClick(
chapterItem = chapterItem, chapterItem = chapterItem,

View File

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

View File

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

View File

@ -1,5 +1,7 @@
package eu.kanade.tachiyomi.ui.manga package eu.kanade.tachiyomi.ui.manga
import android.app.Application
import android.content.Context
import android.os.Bundle import android.os.Bundle
import androidx.compose.runtime.Immutable import androidx.compose.runtime.Immutable
import eu.kanade.domain.category.interactor.GetCategories 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.interactor.InsertTrack
import eu.kanade.domain.track.model.toDbTrack import eu.kanade.domain.track.model.toDbTrack
import eu.kanade.domain.track.model.toDomainTrack 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.database.models.Track
import eu.kanade.tachiyomi.data.download.DownloadManager import eu.kanade.tachiyomi.data.download.DownloadManager
import eu.kanade.tachiyomi.data.download.model.Download 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.chapter.getChapterSort
import eu.kanade.tachiyomi.util.lang.launchIO import eu.kanade.tachiyomi.util.lang.launchIO
import eu.kanade.tachiyomi.util.lang.launchUI 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.lang.withUIContext
import eu.kanade.tachiyomi.util.preference.asImmediateFlow import eu.kanade.tachiyomi.util.preference.asImmediateFlow
import eu.kanade.tachiyomi.util.removeCovers import eu.kanade.tachiyomi.util.removeCovers
@ -68,6 +72,9 @@ import rx.schedulers.Schedulers
import uy.kohesive.injekt.Injekt import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get import uy.kohesive.injekt.api.get
import java.text.DateFormat 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.chapter.model.Chapter as DomainChapter
import eu.kanade.domain.manga.model.Manga as DomainManga import eu.kanade.domain.manga.model.Manga as DomainManga
@ -154,15 +161,18 @@ class MangaPresenter(
getMangaAndChapters.subscribe(mangaId) getMangaAndChapters.subscribe(mangaId)
.collectLatest { (manga, chapters) -> .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 -> _state.update { currentState ->
when (currentState) { when (currentState) {
// Initialize success state // Initialize success state
MangaScreenState.Loading -> MangaScreenState.Success( MangaScreenState.Loading -> MangaScreenState.Success(
manga = manga, manga = manga,
source = Injekt.get<SourceManager>().getOrStub(manga.source), source = Injekt.get<SourceManager>().getOrStub(manga.source),
dateRelativeTime = preferences.relativeTime().get(),
dateFormat = preferences.dateFormat(),
isFromSource = isFromSource, isFromSource = isFromSource,
trackingAvailable = trackManager.hasLoggedServices(), trackingAvailable = trackManager.hasLoggedServices(),
chapters = chapterItems, 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 -> return map { chapter ->
val activeDownload = downloadManager.queue.find { chapter.id == it.chapter.id } val activeDownload = downloadManager.queue.find { chapter.id == it.chapter.id }
val downloaded = downloadManager.isChapterDownloaded(chapter.name, chapter.scanlator, manga.title, manga.source) val downloaded = downloadManager.isChapterDownloaded(chapter.name, chapter.scanlator, manga.title, manga.source)
@ -441,6 +456,29 @@ class MangaPresenter(
chapter = chapter, chapter = chapter,
downloadState = downloadState, downloadState = downloadState,
downloadProgress = activeDownload?.progress ?: 0, 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( data class Success(
val manga: DomainManga, val manga: DomainManga,
val source: Source, val source: Source,
val dateRelativeTime: Int,
val dateFormat: DateFormat,
val isFromSource: Boolean, val isFromSource: Boolean,
val chapters: List<ChapterItem>, val chapters: List<ChapterItem>,
val trackingAvailable: Boolean = false, val trackingAvailable: Boolean = false,
@ -909,6 +945,16 @@ data class ChapterItem(
val chapter: DomainChapter, val chapter: DomainChapter,
val downloadState: Download.State, val downloadState: Download.State,
val downloadProgress: Int, val downloadProgress: Int,
val chapterTitleString: String,
val dateUploadString: String?,
val readProgressString: String?,
) { ) {
val isDownloaded = downloadState == Download.State.DOWNLOADED val isDownloaded = downloadState == Download.State.DOWNLOADED
} }
private val chapterDecimalFormat = DecimalFormat(
"#.###",
DecimalFormatSymbols()
.apply { decimalSeparator = '.' },
)