From 78aa50bb350b0142a3e0407b3d2f6084b9c1a835 Mon Sep 17 00:00:00 2001 From: LooKeR Date: Sat, 7 Oct 2023 03:54:43 +0530 Subject: [PATCH] Reduce recomposition of MangaHeader (#9985) * Reduce recomposition of MangaHeader * Reuse `Modifier` for `Tags` Reference: https://developer.android.com/jetpack/compose/modifiers#reusing-modifiers * Don't recalculate Read State on recomposition * Fix Linting issue * Optimize chapter state calculations --- .../kanade/presentation/manga/MangaScreen.kt | 96 +++++++++++++------ .../manga/components/MangaInfoHeader.kt | 6 +- 2 files changed, 69 insertions(+), 33 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 65963cef3..82c266f65 100644 --- a/app/src/main/java/eu/kanade/presentation/manga/MangaScreen.kt +++ b/app/src/main/java/eu/kanade/presentation/manga/MangaScreen.kt @@ -268,8 +268,14 @@ private fun MangaScreenSmallImpl( val chapters = remember(state) { state.processedChapters } + val isAnySelected by remember { + derivedStateOf { + chapters.fastAny { it.selected } + } + } + val internalOnBackPressed = { - if (chapters.fastAny { it.selected }) { + if (isAnySelected) { onAllChapterSelected(false) } else { onBackClicked() @@ -279,17 +285,22 @@ private fun MangaScreenSmallImpl( Scaffold( topBar = { - val firstVisibleItemIndex by remember { - derivedStateOf { chapterListState.firstVisibleItemIndex } + val selectedChapterCount: Int = remember(chapters) { + chapters.count { it.selected } } - val firstVisibleItemScrollOffset by remember { - derivedStateOf { chapterListState.firstVisibleItemScrollOffset } + val isFirstItemVisible by remember { + derivedStateOf { chapterListState.firstVisibleItemIndex == 0 } + } + val isFirstItemScrolled by remember { + derivedStateOf { chapterListState.firstVisibleItemScrollOffset > 0 } } val animatedTitleAlpha by animateFloatAsState( - if (firstVisibleItemIndex > 0) 1f else 0f, + if (!isFirstItemVisible) 1f else 0f, + label = "Top Bar Title", ) val animatedBgAlpha by animateFloatAsState( - if (firstVisibleItemIndex > 0 || firstVisibleItemScrollOffset > 0) 1f else 0f, + if (!isFirstItemVisible || isFirstItemScrolled) 1f else 0f, + label = "Top Bar Background", ) MangaToolbar( title = state.manga.title, @@ -303,14 +314,17 @@ private fun MangaScreenSmallImpl( onClickEditCategory = onEditCategoryClicked, onClickRefresh = onRefresh, onClickMigrate = onMigrateClicked, - actionModeCounter = chapters.count { it.selected }, + actionModeCounter = selectedChapterCount, onSelectAll = { onAllChapterSelected(true) }, onInvertSelection = { onInvertSelection() }, ) }, bottomBar = { + val selectedChapters = remember(chapters) { + chapters.filter { it.selected } + } SharedMangaBottomActionMenu( - selected = chapters.filter { it.selected }, + selected = selectedChapters, onMultiBookmarkClicked = onMultiBookmarkClicked, onMultiMarkAsReadClicked = onMultiMarkAsReadClicked, onMarkPreviousAsReadClicked = onMarkPreviousAsReadClicked, @@ -321,19 +335,20 @@ private fun MangaScreenSmallImpl( }, snackbarHost = { SnackbarHost(hostState = snackbarHostState) }, floatingActionButton = { + val isFABVisible = remember(chapters) { + chapters.fastAny { !it.chapter.read } && !isAnySelected + } AnimatedVisibility( - visible = chapters.fastAny { !it.chapter.read } && chapters.fastAll { !it.selected }, + visible = isFABVisible, enter = fadeIn(), exit = fadeOut(), ) { ExtendedFloatingActionButton( text = { - val id = if (state.chapters.fastAny { it.chapter.read }) { - R.string.action_resume - } else { - R.string.action_start + val isReading = remember(state.chapters) { + state.chapters.fastAny { it.chapter.read } } - Text(text = stringResource(id)) + Text(text = stringResource(if (isReading) R.string.action_resume else R.string.action_start)) }, icon = { Icon(imageVector = Icons.Filled.PlayArrow, contentDescription = null) }, onClick = onContinueReading, @@ -347,7 +362,7 @@ private fun MangaScreenSmallImpl( PullRefresh( refreshing = state.isRefreshingData, onRefresh = onRefresh, - enabled = chapters.fastAll { !it.selected }, + enabled = !isAnySelected, indicatorPadding = WindowInsets.systemBars.only(WindowInsetsSides.Top).asPaddingValues(), ) { val layoutDirection = LocalLayoutDirection.current @@ -419,10 +434,13 @@ private fun MangaScreenSmallImpl( key = MangaScreenItem.CHAPTER_HEADER, contentType = MangaScreenItem.CHAPTER_HEADER, ) { + val missingChapterCount = remember(chapters) { + chapters.map { it.chapter.chapterNumber }.missingChaptersCount() + } ChapterHeader( - enabled = chapters.fastAll { !it.selected }, + enabled = !isAnySelected, chapterCount = chapters.size, - missingChapterCount = chapters.map { it.chapter.chapterNumber }.missingChaptersCount(), + missingChapterCount = missingChapterCount, onClick = onFilterClicked, ) } @@ -500,12 +518,18 @@ fun MangaScreenLargeImpl( val chapters = remember(state) { state.processedChapters } + val isAnySelected by remember { + derivedStateOf { + chapters.fastAny { it.selected } + } + } + val insetPadding = WindowInsets.systemBars.only(WindowInsetsSides.Horizontal).asPaddingValues() var topBarHeight by remember { mutableIntStateOf(0) } PullRefresh( refreshing = state.isRefreshingData, onRefresh = onRefresh, - enabled = chapters.fastAll { !it.selected }, + enabled = !isAnySelected, indicatorPadding = PaddingValues( start = insetPadding.calculateStartPadding(layoutDirection), top = with(density) { topBarHeight.toDp() }, @@ -515,7 +539,7 @@ fun MangaScreenLargeImpl( val chapterListState = rememberLazyListState() val internalOnBackPressed = { - if (chapters.fastAny { it.selected }) { + if (isAnySelected) { onAllChapterSelected(false) } else { onBackClicked() @@ -525,10 +549,13 @@ fun MangaScreenLargeImpl( Scaffold( topBar = { + val selectedChapterCount = remember(chapters) { + chapters.count { it.selected } + } MangaToolbar( modifier = Modifier.onSizeChanged { topBarHeight = it.height }, title = state.manga.title, - titleAlphaProvider = { if (chapters.fastAny { it.selected }) 1f else 0f }, + titleAlphaProvider = { if (isAnySelected) 1f else 0f }, backgroundAlphaProvider = { 1f }, hasFilters = state.manga.chaptersFiltered(), onBackClicked = internalOnBackPressed, @@ -538,7 +565,7 @@ fun MangaScreenLargeImpl( onClickEditCategory = onEditCategoryClicked, onClickRefresh = onRefresh, onClickMigrate = onMigrateClicked, - actionModeCounter = chapters.count { it.selected }, + actionModeCounter = selectedChapterCount, onSelectAll = { onAllChapterSelected(true) }, onInvertSelection = { onInvertSelection() }, ) @@ -548,8 +575,11 @@ fun MangaScreenLargeImpl( modifier = Modifier.fillMaxWidth(), contentAlignment = Alignment.BottomEnd, ) { + val selectedChapters = remember(chapters) { + chapters.filter { it.selected } + } SharedMangaBottomActionMenu( - selected = chapters.filter { it.selected }, + selected = selectedChapters, onMultiBookmarkClicked = onMultiBookmarkClicked, onMultiMarkAsReadClicked = onMultiMarkAsReadClicked, onMarkPreviousAsReadClicked = onMarkPreviousAsReadClicked, @@ -561,19 +591,20 @@ fun MangaScreenLargeImpl( }, snackbarHost = { SnackbarHost(hostState = snackbarHostState) }, floatingActionButton = { + val isFABVisible = remember(chapters) { + chapters.fastAny { !it.chapter.read } && !isAnySelected + } AnimatedVisibility( - visible = chapters.fastAny { !it.chapter.read } && chapters.fastAll { !it.selected }, + visible = isFABVisible, enter = fadeIn(), exit = fadeOut(), ) { ExtendedFloatingActionButton( text = { - val id = if (state.chapters.fastAny { it.chapter.read }) { - R.string.action_resume - } else { - R.string.action_start + val isReading = remember(state.chapters) { + state.chapters.fastAny { it.chapter.read } } - Text(text = stringResource(id)) + Text(text = stringResource(if (isReading) R.string.action_resume else R.string.action_start)) }, icon = { Icon(imageVector = Icons.Filled.PlayArrow, contentDescription = null) }, onClick = onContinueReading, @@ -644,10 +675,13 @@ fun MangaScreenLargeImpl( key = MangaScreenItem.CHAPTER_HEADER, contentType = MangaScreenItem.CHAPTER_HEADER, ) { + val missingChapterCount = remember(chapters) { + chapters.map { it.chapter.chapterNumber }.missingChaptersCount() + } ChapterHeader( - enabled = chapters.fastAll { !it.selected }, + enabled = !isAnySelected, chapterCount = chapters.size, - missingChapterCount = chapters.map { it.chapter.chapterNumber }.missingChaptersCount(), + missingChapterCount = missingChapterCount, onClick = onFilterButtonClicked, ) } diff --git a/app/src/main/java/eu/kanade/presentation/manga/components/MangaInfoHeader.kt b/app/src/main/java/eu/kanade/presentation/manga/components/MangaInfoHeader.kt index 5e0efe42b..4d9e320fe 100644 --- a/app/src/main/java/eu/kanade/presentation/manga/components/MangaInfoHeader.kt +++ b/app/src/main/java/eu/kanade/presentation/manga/components/MangaInfoHeader.kt @@ -286,7 +286,7 @@ fun ExpandableMangaDescription( ) { tags.forEach { TagsChip( - modifier = Modifier.padding(vertical = 4.dp), + modifier = DefaultTagChipModifier, text = it, onClick = { tagSelected = it @@ -302,7 +302,7 @@ fun ExpandableMangaDescription( ) { items(items = tags) { TagsChip( - modifier = Modifier.padding(vertical = 4.dp), + modifier = DefaultTagChipModifier, text = it, onClick = { tagSelected = it @@ -654,6 +654,8 @@ private fun MangaSummary( } } +private val DefaultTagChipModifier = Modifier.padding(vertical = 4.dp) + @Composable private fun TagsChip( text: String,