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:
parent
b15073fd61
commit
1551891c15
@ -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,
|
||||||
|
@ -20,3 +20,11 @@ enum class EditCoverAction {
|
|||||||
EDIT,
|
EDIT,
|
||||||
DELETE,
|
DELETE,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
enum class MangaScreenItem {
|
||||||
|
INFO_BOX,
|
||||||
|
ACTION_ROW,
|
||||||
|
DESCRIPTION_WITH_TAG,
|
||||||
|
CHAPTER_HEADER,
|
||||||
|
CHAPTER,
|
||||||
|
}
|
||||||
|
@ -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 },
|
||||||
|
@ -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 = '.' },
|
||||||
|
)
|
||||||
|
Loading…
Reference in New Issue
Block a user