mirror of
				https://github.com/mihonapp/mihon.git
				synced 2025-10-30 22:07:57 +01:00 
			
		
		
		
	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:
		| @@ -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, | ||||
|   | ||||
| @@ -20,3 +20,11 @@ enum class EditCoverAction { | ||||
|     EDIT, | ||||
|     DELETE, | ||||
| } | ||||
|  | ||||
| enum class MangaScreenItem { | ||||
|     INFO_BOX, | ||||
|     ACTION_ROW, | ||||
|     DESCRIPTION_WITH_TAG, | ||||
|     CHAPTER_HEADER, | ||||
|     CHAPTER, | ||||
| } | ||||
|   | ||||
| @@ -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 }, | ||||
|   | ||||
| @@ -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 = '.' }, | ||||
| ) | ||||
|   | ||||
		Reference in New Issue
	
	Block a user