fix: correct re-centering animation making flicker

This commit is contained in:
2026-07-02 16:54:56 +02:00
parent 24d39d2a6f
commit c307111ec1
@@ -18,8 +18,9 @@ import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.derivedStateOf
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.snapshotFlow
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
@@ -27,6 +28,7 @@ import androidx.compose.ui.text.font.FontStyle
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import fr.ajaury.gwenedeg.core.model.Phrase
import fr.ajaury.gwenedeg.preferences.model.SubtitlePreferences
import fr.ajaury.gwenedeg.subtitle.model.SubtitleLine
@@ -44,19 +46,24 @@ fun SubtitleList(
) {
val listState = rememberLazyListState()
val isDragged by listState.interactionSource.collectIsDraggedAsState()
var targetIndex by remember { mutableStateOf(0) }
var isDragging by remember { mutableStateOf(false) }
val centeredIndex by remember {
// Detect beginning of dragging.
LaunchedEffect(isDragged) {
if (isDragged) {
isDragging = true
}
}
val centeredIndex by remember(currentIndex, isDragged) {
derivedStateOf {
val info = listState.layoutInfo
if (info.visibleItemsInfo.isEmpty()) {
-1
if (isDragging) {
listState.getCenteredIndex().also {
targetIndex = it
}
} else {
val viewportCenter = (info.viewportStartOffset + info.viewportEndOffset) / 2f
info.visibleItemsInfo
.minByOrNull {
abs((it.offset + it.size / 2f) - viewportCenter)
}?.index
?: -1
currentIndex ?: -1
}
}
}
@@ -69,21 +76,15 @@ fun SubtitleList(
}
// Snap to the centered line and seek to it once the user finishes scrolling.
LaunchedEffect(listState, lines) {
var wasDragged = false
snapshotFlow { isDragged to listState.isScrollInProgress }
.collect { (dragged, scrolling) ->
if (dragged) {
wasDragged = true
} else if (wasDragged && !scrolling) {
wasDragged = false
val targetIndex = centeredIndex
if (targetIndex in lines.indices) {
listState.centerItem(targetIndex)
onSeek(targetIndex)
}
}
LaunchedEffect(isDragged) {
if (isDragging && !isDragged) {
if (targetIndex in lines.indices) {
listState.centerItem(targetIndex)
onSeek(targetIndex)
}
isDragging = false
}
}
BoxWithConstraints(modifier = modifier) {
@@ -107,6 +108,20 @@ fun SubtitleList(
}
}
private fun LazyListState.getCenteredIndex(): Int {
val info = this.layoutInfo
return if (info.visibleItemsInfo.isEmpty()) {
-1
} else {
val viewportCenter = (info.viewportStartOffset + info.viewportEndOffset) / 2f
info.visibleItemsInfo
.minByOrNull {
abs((it.offset + it.size / 2f) - viewportCenter)
}?.index
?: -1
}
}
private suspend fun LazyListState.centerItem(index: Int) {
if (index < 0 || index >= this.layoutInfo.totalItemsCount) return
@@ -126,11 +141,8 @@ private fun Subtitle(
isCurrent: Boolean,
subtitlePreferences: SubtitlePreferences = SubtitlePreferences(),
) {
val contentColor = if (isCurrent) {
Color.Unspecified
} else {
MaterialTheme.colorScheme.onSurface.copy(alpha = 0.5f)
}
val textAlpha = if (isCurrent) 1f else 0.35f
val contentColor = MaterialTheme.colorScheme.onSurface.copy(alpha = textAlpha)
Column(
modifier = Modifier.fillMaxWidth(),
@@ -140,7 +152,6 @@ private fun Subtitle(
if (subtitlePreferences.showTranscription) {
TranscriptionLine(
text = line.phrase.transcription,
isCurrent = isCurrent,
contentColor = contentColor,
)
}
@@ -149,7 +160,6 @@ private fun Subtitle(
?.let { translation ->
TranslationLine(
text = translation,
isCurrent = isCurrent,
contentColor = contentColor,
)
}
@@ -159,18 +169,11 @@ private fun Subtitle(
@Composable
private fun TranscriptionLine(
text: String,
isCurrent: Boolean,
contentColor: Color,
) {
val mainTextStyle = if (isCurrent) {
MaterialTheme.typography.headlineSmall
} else {
MaterialTheme.typography.bodyLarge
}
Text(
text = text,
style = mainTextStyle,
style = MaterialTheme.typography.bodyLarge.copy(fontSize = 20.sp),
color = contentColor,
textAlign = TextAlign.Center,
modifier = Modifier.fillMaxWidth(),
@@ -180,18 +183,11 @@ private fun TranscriptionLine(
@Composable
private fun TranslationLine(
text: String,
isCurrent: Boolean,
contentColor: Color,
) {
val translationStyle = if (isCurrent) {
MaterialTheme.typography.bodyLarge
} else {
MaterialTheme.typography.bodySmall
}
Text(
text = text,
style = translationStyle.copy(fontStyle = FontStyle.Italic),
style = MaterialTheme.typography.bodySmall.copy(fontStyle = FontStyle.Italic),
color = contentColor,
textAlign = TextAlign.Center,
modifier = Modifier.fillMaxWidth(),