mirror of
https://github.com/JustinZeus/Wholphin.git
synced 2026-07-08 23:51:21 +02:00
performance: reduce recompositons in PersonCard and SeasonCard (#1184)
<!-- By submitting this pull request, you acknowledge that you have read the [contributing guide](https://github.com/damontecres/Wholphin/blob/main/CONTRIBUTING.md, including the AI/LLM policy, and [developer's guide](https://github.com/damontecres/Wholphin/blob/main/DEVELOPMENT.md) --> ## Description <!-- Describe the changes in detail --> related docs: https://developer.android.com/develop/ui/compose/performance/bestpractices#defer-reads Removed setting padding with animated value, instead moved to lambda offset and layout modifiers to increase performance and reduce recompositions when focusing those cards Split from #1152 ### Related issues <!-- If this is a new feature or a change, there must be a discussion in an issue first, reference the issue here --> <!-- If fixing a bug, reference the bug issue here, or describe the bug, including steps to reproduce --> ### Testing <!-- Describe how this change was tested and on what device(s) --> Tested on a emulato ## Screenshots <!-- Please include screenshots if the PR alters any UI elements --> |Component | Pre | Post | |---|--------|--------| |Person Card|<img width="404" height="366" alt="Screenshot 2026-04-02 at 08 19 18" src="https://github.com/user-attachments/assets/73b38c75-040b-401e-88d6-4bfc9ffc0cc9" /> | <img width="407" height="346" alt="Screenshot 2026-04-02 at 08 22 06" src="https://github.com/user-attachments/assets/add473f7-dea6-4e33-97b8-6954149a81e2" /> | |Season Card| <img width="404" height="341" alt="Screenshot 2026-04-02 at 08 18 31" src="https://github.com/user-attachments/assets/eec594ee-6701-4f99-8fb3-339ed8559ec4" /> |<img width="408" height="299" alt="Screenshot 2026-04-02 at 08 24 06" src="https://github.com/user-attachments/assets/8de445a6-dcb8-4c7e-a5f6-a25bf822c822" /> | ## AI or LLM usage <!-- If you used any AI or LLM assistance, please list where in the code and how you tested it --> Gemini in AS was used for repetitive code completion
This commit is contained in:
parent
44d1ac484f
commit
5ab4bb712b
2 changed files with 40 additions and 12 deletions
|
|
@ -11,6 +11,7 @@ import androidx.compose.foundation.layout.Column
|
||||||
import androidx.compose.foundation.layout.aspectRatio
|
import androidx.compose.foundation.layout.aspectRatio
|
||||||
import androidx.compose.foundation.layout.fillMaxSize
|
import androidx.compose.foundation.layout.fillMaxSize
|
||||||
import androidx.compose.foundation.layout.fillMaxWidth
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
|
import androidx.compose.foundation.layout.offset
|
||||||
import androidx.compose.foundation.layout.padding
|
import androidx.compose.foundation.layout.padding
|
||||||
import androidx.compose.foundation.layout.width
|
import androidx.compose.foundation.layout.width
|
||||||
import androidx.compose.foundation.shape.CircleShape
|
import androidx.compose.foundation.shape.CircleShape
|
||||||
|
|
@ -25,9 +26,11 @@ import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.draw.clip
|
import androidx.compose.ui.draw.clip
|
||||||
import androidx.compose.ui.graphics.Color
|
import androidx.compose.ui.graphics.Color
|
||||||
import androidx.compose.ui.layout.ContentScale
|
import androidx.compose.ui.layout.ContentScale
|
||||||
|
import androidx.compose.ui.layout.layout
|
||||||
import androidx.compose.ui.res.stringResource
|
import androidx.compose.ui.res.stringResource
|
||||||
import androidx.compose.ui.text.font.FontWeight
|
import androidx.compose.ui.text.font.FontWeight
|
||||||
import androidx.compose.ui.text.style.TextAlign
|
import androidx.compose.ui.text.style.TextAlign
|
||||||
|
import androidx.compose.ui.unit.IntOffset
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import androidx.compose.ui.unit.sp
|
import androidx.compose.ui.unit.sp
|
||||||
import androidx.tv.material3.Border
|
import androidx.tv.material3.Border
|
||||||
|
|
@ -80,7 +83,7 @@ fun PersonCard(
|
||||||
) {
|
) {
|
||||||
val hideOverlayDelay = 1_000L
|
val hideOverlayDelay = 1_000L
|
||||||
|
|
||||||
val focused = interactionSource.collectIsFocusedAsState().value
|
val focused by interactionSource.collectIsFocusedAsState()
|
||||||
var focusedAfterDelay by remember { mutableStateOf(false) }
|
var focusedAfterDelay by remember { mutableStateOf(false) }
|
||||||
|
|
||||||
if (focused) {
|
if (focused) {
|
||||||
|
|
@ -95,10 +98,13 @@ fun PersonCard(
|
||||||
} else {
|
} else {
|
||||||
focusedAfterDelay = false
|
focusedAfterDelay = false
|
||||||
}
|
}
|
||||||
val spaceBetween by animateDpAsState(if (focused) 12.dp else 4.dp)
|
|
||||||
val spaceBelow by animateDpAsState(if (focused) 4.dp else 12.dp)
|
// Do not use `by` here, this way we are Defer reads and recompositions to only when modifier calculates
|
||||||
|
val spaceBetweenState = animateDpAsState(if (focused) 12.dp else 4.dp, label = "spaceBetween")
|
||||||
|
val spaceBelowState = animateDpAsState(if (focused) 4.dp else 12.dp, label = "spaceBelow")
|
||||||
|
|
||||||
Column(
|
Column(
|
||||||
verticalArrangement = Arrangement.spacedBy(spaceBetween),
|
verticalArrangement = Arrangement.spacedBy(4.dp), // Fixed base spacing
|
||||||
modifier = modifier,
|
modifier = modifier,
|
||||||
) {
|
) {
|
||||||
Card(
|
Card(
|
||||||
|
|
@ -168,8 +174,16 @@ fun PersonCard(
|
||||||
verticalArrangement = Arrangement.spacedBy(0.dp),
|
verticalArrangement = Arrangement.spacedBy(0.dp),
|
||||||
modifier =
|
modifier =
|
||||||
Modifier
|
Modifier
|
||||||
.padding(bottom = spaceBelow)
|
// Optimization: move animation reads to layout/draw phase
|
||||||
.fillMaxWidth(),
|
.offset {
|
||||||
|
IntOffset(0, (spaceBetweenState.value - 4.dp).roundToPx())
|
||||||
|
}.layout { measurable, constraints ->
|
||||||
|
val paddingPx = spaceBelowState.value.roundToPx()
|
||||||
|
val placeable = measurable.measure(constraints)
|
||||||
|
layout(placeable.width, placeable.height + paddingPx) {
|
||||||
|
placeable.placeRelative(0, 0)
|
||||||
|
}
|
||||||
|
}.fillMaxWidth(),
|
||||||
) {
|
) {
|
||||||
Text(
|
Text(
|
||||||
text = name ?: "",
|
text = name ?: "",
|
||||||
|
|
|
||||||
|
|
@ -9,6 +9,7 @@ import androidx.compose.foundation.layout.Column
|
||||||
import androidx.compose.foundation.layout.aspectRatio
|
import androidx.compose.foundation.layout.aspectRatio
|
||||||
import androidx.compose.foundation.layout.fillMaxSize
|
import androidx.compose.foundation.layout.fillMaxSize
|
||||||
import androidx.compose.foundation.layout.fillMaxWidth
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
|
import androidx.compose.foundation.layout.offset
|
||||||
import androidx.compose.foundation.layout.padding
|
import androidx.compose.foundation.layout.padding
|
||||||
import androidx.compose.foundation.layout.size
|
import androidx.compose.foundation.layout.size
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
|
|
@ -18,10 +19,12 @@ import androidx.compose.runtime.mutableStateOf
|
||||||
import androidx.compose.runtime.remember
|
import androidx.compose.runtime.remember
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.graphics.Color
|
import androidx.compose.ui.graphics.Color
|
||||||
|
import androidx.compose.ui.layout.layout
|
||||||
import androidx.compose.ui.platform.LocalDensity
|
import androidx.compose.ui.platform.LocalDensity
|
||||||
import androidx.compose.ui.text.font.FontWeight
|
import androidx.compose.ui.text.font.FontWeight
|
||||||
import androidx.compose.ui.text.style.TextAlign
|
import androidx.compose.ui.text.style.TextAlign
|
||||||
import androidx.compose.ui.unit.Dp
|
import androidx.compose.ui.unit.Dp
|
||||||
|
import androidx.compose.ui.unit.IntOffset
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import androidx.tv.material3.Card
|
import androidx.tv.material3.Card
|
||||||
import androidx.tv.material3.CardDefaults
|
import androidx.tv.material3.CardDefaults
|
||||||
|
|
@ -52,7 +55,7 @@ fun SeasonCard(
|
||||||
val imageUrlService = LocalImageUrlService.current
|
val imageUrlService = LocalImageUrlService.current
|
||||||
val density = LocalDensity.current
|
val density = LocalDensity.current
|
||||||
val imageUrl =
|
val imageUrl =
|
||||||
remember(item, imageHeight, imageWidth) {
|
remember(item, imageHeight, imageWidth, density) {
|
||||||
if (item != null) {
|
if (item != null) {
|
||||||
val fillHeight =
|
val fillHeight =
|
||||||
if (imageHeight != Dp.Unspecified) {
|
if (imageHeight != Dp.Unspecified) {
|
||||||
|
|
@ -125,14 +128,17 @@ fun SeasonCard(
|
||||||
aspectRatio: Float = AspectRatios.TALL,
|
aspectRatio: Float = AspectRatios.TALL,
|
||||||
) {
|
) {
|
||||||
val focused by interactionSource.collectIsFocusedAsState()
|
val focused by interactionSource.collectIsFocusedAsState()
|
||||||
val spaceBetween by animateDpAsState(if (focused) 12.dp else 4.dp)
|
// Do not use `by` here, this way we are Defer reads and recompositions to only when modifier calculates
|
||||||
val spaceBelow by animateDpAsState(if (focused) 4.dp else 12.dp)
|
val spaceBetween = animateDpAsState(if (focused) 12.dp else 4.dp, label = "spaceBetween")
|
||||||
|
val spaceBelow = animateDpAsState(if (focused) 4.dp else 12.dp, label = "spaceBelow")
|
||||||
|
|
||||||
val focusedAfterDelay by rememberFocusedAfterDelay(interactionSource)
|
val focusedAfterDelay by rememberFocusedAfterDelay(interactionSource)
|
||||||
val aspectRationToUse = aspectRatio.coerceAtLeast(AspectRatios.MIN)
|
val aspectRationToUse = aspectRatio.coerceAtLeast(AspectRatios.MIN)
|
||||||
val width = imageHeight * aspectRationToUse
|
val width = imageHeight * aspectRationToUse
|
||||||
val height = imageWidth * (1f / aspectRationToUse)
|
val height = imageWidth * (1f / aspectRationToUse)
|
||||||
|
|
||||||
Column(
|
Column(
|
||||||
verticalArrangement = Arrangement.spacedBy(spaceBetween),
|
verticalArrangement = Arrangement.spacedBy(4.dp), // Fixed base spacing
|
||||||
modifier = modifier.size(width, height),
|
modifier = modifier.size(width, height),
|
||||||
) {
|
) {
|
||||||
Card(
|
Card(
|
||||||
|
|
@ -173,8 +179,16 @@ fun SeasonCard(
|
||||||
verticalArrangement = Arrangement.spacedBy(0.dp),
|
verticalArrangement = Arrangement.spacedBy(0.dp),
|
||||||
modifier =
|
modifier =
|
||||||
Modifier
|
Modifier
|
||||||
.padding(bottom = spaceBelow)
|
// Optimization: move animation reads to layout/draw phase
|
||||||
.fillMaxWidth(),
|
.offset {
|
||||||
|
IntOffset(0, (spaceBetween.value - 4.dp).roundToPx())
|
||||||
|
}.layout { measurable, constraints ->
|
||||||
|
val paddingPx = spaceBelow.value.roundToPx()
|
||||||
|
val placeable = measurable.measure(constraints)
|
||||||
|
layout(placeable.width, placeable.height + paddingPx) {
|
||||||
|
placeable.placeRelative(0, 0)
|
||||||
|
}
|
||||||
|
}.fillMaxWidth(),
|
||||||
) {
|
) {
|
||||||
Text(
|
Text(
|
||||||
text = title ?: "",
|
text = title ?: "",
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue