From 78aa50bb350b0142a3e0407b3d2f6084b9c1a835 Mon Sep 17 00:00:00 2001
From: LooKeR <mohit2002ss@gmail.com>
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,