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
This commit is contained in:
parent
7f0f67d752
commit
78aa50bb35
2 changed files with 69 additions and 33 deletions
|
@ -268,8 +268,14 @@ private fun MangaScreenSmallImpl(
|
||||||
|
|
||||||
val chapters = remember(state) { state.processedChapters }
|
val chapters = remember(state) { state.processedChapters }
|
||||||
|
|
||||||
|
val isAnySelected by remember {
|
||||||
|
derivedStateOf {
|
||||||
|
chapters.fastAny { it.selected }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
val internalOnBackPressed = {
|
val internalOnBackPressed = {
|
||||||
if (chapters.fastAny { it.selected }) {
|
if (isAnySelected) {
|
||||||
onAllChapterSelected(false)
|
onAllChapterSelected(false)
|
||||||
} else {
|
} else {
|
||||||
onBackClicked()
|
onBackClicked()
|
||||||
|
@ -279,17 +285,22 @@ private fun MangaScreenSmallImpl(
|
||||||
|
|
||||||
Scaffold(
|
Scaffold(
|
||||||
topBar = {
|
topBar = {
|
||||||
val firstVisibleItemIndex by remember {
|
val selectedChapterCount: Int = remember(chapters) {
|
||||||
derivedStateOf { chapterListState.firstVisibleItemIndex }
|
chapters.count { it.selected }
|
||||||
}
|
}
|
||||||
val firstVisibleItemScrollOffset by remember {
|
val isFirstItemVisible by remember {
|
||||||
derivedStateOf { chapterListState.firstVisibleItemScrollOffset }
|
derivedStateOf { chapterListState.firstVisibleItemIndex == 0 }
|
||||||
|
}
|
||||||
|
val isFirstItemScrolled by remember {
|
||||||
|
derivedStateOf { chapterListState.firstVisibleItemScrollOffset > 0 }
|
||||||
}
|
}
|
||||||
val animatedTitleAlpha by animateFloatAsState(
|
val animatedTitleAlpha by animateFloatAsState(
|
||||||
if (firstVisibleItemIndex > 0) 1f else 0f,
|
if (!isFirstItemVisible) 1f else 0f,
|
||||||
|
label = "Top Bar Title",
|
||||||
)
|
)
|
||||||
val animatedBgAlpha by animateFloatAsState(
|
val animatedBgAlpha by animateFloatAsState(
|
||||||
if (firstVisibleItemIndex > 0 || firstVisibleItemScrollOffset > 0) 1f else 0f,
|
if (!isFirstItemVisible || isFirstItemScrolled) 1f else 0f,
|
||||||
|
label = "Top Bar Background",
|
||||||
)
|
)
|
||||||
MangaToolbar(
|
MangaToolbar(
|
||||||
title = state.manga.title,
|
title = state.manga.title,
|
||||||
|
@ -303,14 +314,17 @@ private fun MangaScreenSmallImpl(
|
||||||
onClickEditCategory = onEditCategoryClicked,
|
onClickEditCategory = onEditCategoryClicked,
|
||||||
onClickRefresh = onRefresh,
|
onClickRefresh = onRefresh,
|
||||||
onClickMigrate = onMigrateClicked,
|
onClickMigrate = onMigrateClicked,
|
||||||
actionModeCounter = chapters.count { it.selected },
|
actionModeCounter = selectedChapterCount,
|
||||||
onSelectAll = { onAllChapterSelected(true) },
|
onSelectAll = { onAllChapterSelected(true) },
|
||||||
onInvertSelection = { onInvertSelection() },
|
onInvertSelection = { onInvertSelection() },
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
bottomBar = {
|
bottomBar = {
|
||||||
|
val selectedChapters = remember(chapters) {
|
||||||
|
chapters.filter { it.selected }
|
||||||
|
}
|
||||||
SharedMangaBottomActionMenu(
|
SharedMangaBottomActionMenu(
|
||||||
selected = chapters.filter { it.selected },
|
selected = selectedChapters,
|
||||||
onMultiBookmarkClicked = onMultiBookmarkClicked,
|
onMultiBookmarkClicked = onMultiBookmarkClicked,
|
||||||
onMultiMarkAsReadClicked = onMultiMarkAsReadClicked,
|
onMultiMarkAsReadClicked = onMultiMarkAsReadClicked,
|
||||||
onMarkPreviousAsReadClicked = onMarkPreviousAsReadClicked,
|
onMarkPreviousAsReadClicked = onMarkPreviousAsReadClicked,
|
||||||
|
@ -321,19 +335,20 @@ private fun MangaScreenSmallImpl(
|
||||||
},
|
},
|
||||||
snackbarHost = { SnackbarHost(hostState = snackbarHostState) },
|
snackbarHost = { SnackbarHost(hostState = snackbarHostState) },
|
||||||
floatingActionButton = {
|
floatingActionButton = {
|
||||||
|
val isFABVisible = remember(chapters) {
|
||||||
|
chapters.fastAny { !it.chapter.read } && !isAnySelected
|
||||||
|
}
|
||||||
AnimatedVisibility(
|
AnimatedVisibility(
|
||||||
visible = chapters.fastAny { !it.chapter.read } && chapters.fastAll { !it.selected },
|
visible = isFABVisible,
|
||||||
enter = fadeIn(),
|
enter = fadeIn(),
|
||||||
exit = fadeOut(),
|
exit = fadeOut(),
|
||||||
) {
|
) {
|
||||||
ExtendedFloatingActionButton(
|
ExtendedFloatingActionButton(
|
||||||
text = {
|
text = {
|
||||||
val id = if (state.chapters.fastAny { it.chapter.read }) {
|
val isReading = remember(state.chapters) {
|
||||||
R.string.action_resume
|
state.chapters.fastAny { it.chapter.read }
|
||||||
} else {
|
|
||||||
R.string.action_start
|
|
||||||
}
|
}
|
||||||
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) },
|
icon = { Icon(imageVector = Icons.Filled.PlayArrow, contentDescription = null) },
|
||||||
onClick = onContinueReading,
|
onClick = onContinueReading,
|
||||||
|
@ -347,7 +362,7 @@ private fun MangaScreenSmallImpl(
|
||||||
PullRefresh(
|
PullRefresh(
|
||||||
refreshing = state.isRefreshingData,
|
refreshing = state.isRefreshingData,
|
||||||
onRefresh = onRefresh,
|
onRefresh = onRefresh,
|
||||||
enabled = chapters.fastAll { !it.selected },
|
enabled = !isAnySelected,
|
||||||
indicatorPadding = WindowInsets.systemBars.only(WindowInsetsSides.Top).asPaddingValues(),
|
indicatorPadding = WindowInsets.systemBars.only(WindowInsetsSides.Top).asPaddingValues(),
|
||||||
) {
|
) {
|
||||||
val layoutDirection = LocalLayoutDirection.current
|
val layoutDirection = LocalLayoutDirection.current
|
||||||
|
@ -419,10 +434,13 @@ private fun MangaScreenSmallImpl(
|
||||||
key = MangaScreenItem.CHAPTER_HEADER,
|
key = MangaScreenItem.CHAPTER_HEADER,
|
||||||
contentType = MangaScreenItem.CHAPTER_HEADER,
|
contentType = MangaScreenItem.CHAPTER_HEADER,
|
||||||
) {
|
) {
|
||||||
|
val missingChapterCount = remember(chapters) {
|
||||||
|
chapters.map { it.chapter.chapterNumber }.missingChaptersCount()
|
||||||
|
}
|
||||||
ChapterHeader(
|
ChapterHeader(
|
||||||
enabled = chapters.fastAll { !it.selected },
|
enabled = !isAnySelected,
|
||||||
chapterCount = chapters.size,
|
chapterCount = chapters.size,
|
||||||
missingChapterCount = chapters.map { it.chapter.chapterNumber }.missingChaptersCount(),
|
missingChapterCount = missingChapterCount,
|
||||||
onClick = onFilterClicked,
|
onClick = onFilterClicked,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -500,12 +518,18 @@ fun MangaScreenLargeImpl(
|
||||||
|
|
||||||
val chapters = remember(state) { state.processedChapters }
|
val chapters = remember(state) { state.processedChapters }
|
||||||
|
|
||||||
|
val isAnySelected by remember {
|
||||||
|
derivedStateOf {
|
||||||
|
chapters.fastAny { it.selected }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
val insetPadding = WindowInsets.systemBars.only(WindowInsetsSides.Horizontal).asPaddingValues()
|
val insetPadding = WindowInsets.systemBars.only(WindowInsetsSides.Horizontal).asPaddingValues()
|
||||||
var topBarHeight by remember { mutableIntStateOf(0) }
|
var topBarHeight by remember { mutableIntStateOf(0) }
|
||||||
PullRefresh(
|
PullRefresh(
|
||||||
refreshing = state.isRefreshingData,
|
refreshing = state.isRefreshingData,
|
||||||
onRefresh = onRefresh,
|
onRefresh = onRefresh,
|
||||||
enabled = chapters.fastAll { !it.selected },
|
enabled = !isAnySelected,
|
||||||
indicatorPadding = PaddingValues(
|
indicatorPadding = PaddingValues(
|
||||||
start = insetPadding.calculateStartPadding(layoutDirection),
|
start = insetPadding.calculateStartPadding(layoutDirection),
|
||||||
top = with(density) { topBarHeight.toDp() },
|
top = with(density) { topBarHeight.toDp() },
|
||||||
|
@ -515,7 +539,7 @@ fun MangaScreenLargeImpl(
|
||||||
val chapterListState = rememberLazyListState()
|
val chapterListState = rememberLazyListState()
|
||||||
|
|
||||||
val internalOnBackPressed = {
|
val internalOnBackPressed = {
|
||||||
if (chapters.fastAny { it.selected }) {
|
if (isAnySelected) {
|
||||||
onAllChapterSelected(false)
|
onAllChapterSelected(false)
|
||||||
} else {
|
} else {
|
||||||
onBackClicked()
|
onBackClicked()
|
||||||
|
@ -525,10 +549,13 @@ fun MangaScreenLargeImpl(
|
||||||
|
|
||||||
Scaffold(
|
Scaffold(
|
||||||
topBar = {
|
topBar = {
|
||||||
|
val selectedChapterCount = remember(chapters) {
|
||||||
|
chapters.count { it.selected }
|
||||||
|
}
|
||||||
MangaToolbar(
|
MangaToolbar(
|
||||||
modifier = Modifier.onSizeChanged { topBarHeight = it.height },
|
modifier = Modifier.onSizeChanged { topBarHeight = it.height },
|
||||||
title = state.manga.title,
|
title = state.manga.title,
|
||||||
titleAlphaProvider = { if (chapters.fastAny { it.selected }) 1f else 0f },
|
titleAlphaProvider = { if (isAnySelected) 1f else 0f },
|
||||||
backgroundAlphaProvider = { 1f },
|
backgroundAlphaProvider = { 1f },
|
||||||
hasFilters = state.manga.chaptersFiltered(),
|
hasFilters = state.manga.chaptersFiltered(),
|
||||||
onBackClicked = internalOnBackPressed,
|
onBackClicked = internalOnBackPressed,
|
||||||
|
@ -538,7 +565,7 @@ fun MangaScreenLargeImpl(
|
||||||
onClickEditCategory = onEditCategoryClicked,
|
onClickEditCategory = onEditCategoryClicked,
|
||||||
onClickRefresh = onRefresh,
|
onClickRefresh = onRefresh,
|
||||||
onClickMigrate = onMigrateClicked,
|
onClickMigrate = onMigrateClicked,
|
||||||
actionModeCounter = chapters.count { it.selected },
|
actionModeCounter = selectedChapterCount,
|
||||||
onSelectAll = { onAllChapterSelected(true) },
|
onSelectAll = { onAllChapterSelected(true) },
|
||||||
onInvertSelection = { onInvertSelection() },
|
onInvertSelection = { onInvertSelection() },
|
||||||
)
|
)
|
||||||
|
@ -548,8 +575,11 @@ fun MangaScreenLargeImpl(
|
||||||
modifier = Modifier.fillMaxWidth(),
|
modifier = Modifier.fillMaxWidth(),
|
||||||
contentAlignment = Alignment.BottomEnd,
|
contentAlignment = Alignment.BottomEnd,
|
||||||
) {
|
) {
|
||||||
|
val selectedChapters = remember(chapters) {
|
||||||
|
chapters.filter { it.selected }
|
||||||
|
}
|
||||||
SharedMangaBottomActionMenu(
|
SharedMangaBottomActionMenu(
|
||||||
selected = chapters.filter { it.selected },
|
selected = selectedChapters,
|
||||||
onMultiBookmarkClicked = onMultiBookmarkClicked,
|
onMultiBookmarkClicked = onMultiBookmarkClicked,
|
||||||
onMultiMarkAsReadClicked = onMultiMarkAsReadClicked,
|
onMultiMarkAsReadClicked = onMultiMarkAsReadClicked,
|
||||||
onMarkPreviousAsReadClicked = onMarkPreviousAsReadClicked,
|
onMarkPreviousAsReadClicked = onMarkPreviousAsReadClicked,
|
||||||
|
@ -561,19 +591,20 @@ fun MangaScreenLargeImpl(
|
||||||
},
|
},
|
||||||
snackbarHost = { SnackbarHost(hostState = snackbarHostState) },
|
snackbarHost = { SnackbarHost(hostState = snackbarHostState) },
|
||||||
floatingActionButton = {
|
floatingActionButton = {
|
||||||
|
val isFABVisible = remember(chapters) {
|
||||||
|
chapters.fastAny { !it.chapter.read } && !isAnySelected
|
||||||
|
}
|
||||||
AnimatedVisibility(
|
AnimatedVisibility(
|
||||||
visible = chapters.fastAny { !it.chapter.read } && chapters.fastAll { !it.selected },
|
visible = isFABVisible,
|
||||||
enter = fadeIn(),
|
enter = fadeIn(),
|
||||||
exit = fadeOut(),
|
exit = fadeOut(),
|
||||||
) {
|
) {
|
||||||
ExtendedFloatingActionButton(
|
ExtendedFloatingActionButton(
|
||||||
text = {
|
text = {
|
||||||
val id = if (state.chapters.fastAny { it.chapter.read }) {
|
val isReading = remember(state.chapters) {
|
||||||
R.string.action_resume
|
state.chapters.fastAny { it.chapter.read }
|
||||||
} else {
|
|
||||||
R.string.action_start
|
|
||||||
}
|
}
|
||||||
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) },
|
icon = { Icon(imageVector = Icons.Filled.PlayArrow, contentDescription = null) },
|
||||||
onClick = onContinueReading,
|
onClick = onContinueReading,
|
||||||
|
@ -644,10 +675,13 @@ fun MangaScreenLargeImpl(
|
||||||
key = MangaScreenItem.CHAPTER_HEADER,
|
key = MangaScreenItem.CHAPTER_HEADER,
|
||||||
contentType = MangaScreenItem.CHAPTER_HEADER,
|
contentType = MangaScreenItem.CHAPTER_HEADER,
|
||||||
) {
|
) {
|
||||||
|
val missingChapterCount = remember(chapters) {
|
||||||
|
chapters.map { it.chapter.chapterNumber }.missingChaptersCount()
|
||||||
|
}
|
||||||
ChapterHeader(
|
ChapterHeader(
|
||||||
enabled = chapters.fastAll { !it.selected },
|
enabled = !isAnySelected,
|
||||||
chapterCount = chapters.size,
|
chapterCount = chapters.size,
|
||||||
missingChapterCount = chapters.map { it.chapter.chapterNumber }.missingChaptersCount(),
|
missingChapterCount = missingChapterCount,
|
||||||
onClick = onFilterButtonClicked,
|
onClick = onFilterButtonClicked,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
@ -286,7 +286,7 @@ fun ExpandableMangaDescription(
|
||||||
) {
|
) {
|
||||||
tags.forEach {
|
tags.forEach {
|
||||||
TagsChip(
|
TagsChip(
|
||||||
modifier = Modifier.padding(vertical = 4.dp),
|
modifier = DefaultTagChipModifier,
|
||||||
text = it,
|
text = it,
|
||||||
onClick = {
|
onClick = {
|
||||||
tagSelected = it
|
tagSelected = it
|
||||||
|
@ -302,7 +302,7 @@ fun ExpandableMangaDescription(
|
||||||
) {
|
) {
|
||||||
items(items = tags) {
|
items(items = tags) {
|
||||||
TagsChip(
|
TagsChip(
|
||||||
modifier = Modifier.padding(vertical = 4.dp),
|
modifier = DefaultTagChipModifier,
|
||||||
text = it,
|
text = it,
|
||||||
onClick = {
|
onClick = {
|
||||||
tagSelected = it
|
tagSelected = it
|
||||||
|
@ -654,6 +654,8 @@ private fun MangaSummary(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private val DefaultTagChipModifier = Modifier.padding(vertical = 4.dp)
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
private fun TagsChip(
|
private fun TagsChip(
|
||||||
text: String,
|
text: String,
|
||||||
|
|
Reference in a new issue