Add nicer call quality animation.

This commit is contained in:
Alex Hart
2025-12-10 12:24:56 -04:00
committed by Michelle Tang
parent 86aa866626
commit 224503f6bb
2 changed files with 351 additions and 101 deletions

View File

@@ -9,7 +9,6 @@ import androidx.annotation.DrawableRes
import androidx.annotation.StringRes
import androidx.compose.animation.AnimatedContent
import androidx.compose.animation.AnimatedVisibility
import androidx.compose.animation.animateContentSize
import androidx.compose.animation.core.CubicBezierEasing
import androidx.compose.animation.core.tween
import androidx.compose.animation.expandVertically
@@ -23,7 +22,6 @@ import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Arrangement.spacedBy
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.ColumnScope
import androidx.compose.foundation.layout.FlowRow
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
@@ -65,6 +63,8 @@ import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.text.withLink
import androidx.compose.ui.tooling.preview.PreviewLightDark
import androidx.compose.ui.unit.dp
import org.signal.core.ui.compose.AllDevicePreviews
import org.signal.core.ui.compose.AnimatedFlowRow
import org.signal.core.ui.compose.Buttons
import org.signal.core.ui.compose.Dialogs
import org.signal.core.ui.compose.IconButtons
@@ -221,85 +221,103 @@ private fun WhatIssuesDidYouHave(
val isAudioExpanded = CallQualityIssue.AUDIO_ISSUE in selectedQualityIssues
val isVideoExpanded = CallQualityIssue.VIDEO_ISSUE in selectedQualityIssues
FlowRow(
AnimatedFlowRow(
modifier = Modifier
.animateContentSize()
.fillMaxWidth()
.horizontalGutters(),
horizontalArrangement = Arrangement.Center
.padding(top = 24.dp)
.horizontalGutters()
) {
IssueChip(
issue = CallQualityIssue.AUDIO_ISSUE,
isSelected = isAudioExpanded,
onClick = { onCallQualityIssueClick(CallQualityIssue.AUDIO_ISSUE) }
)
item(CallQualityIssue.AUDIO_ISSUE) {
IssueChip(
issue = CallQualityIssue.AUDIO_ISSUE,
isSelected = isAudioExpanded,
onClick = { onCallQualityIssueClick(CallQualityIssue.AUDIO_ISSUE) }
)
}
AnimatedIssueChip(
visible = isAudioExpanded,
issue = CallQualityIssue.AUDIO_STUTTERING,
isSelected = CallQualityIssue.AUDIO_STUTTERING in selectedQualityIssues,
onClick = { onCallQualityIssueClick(CallQualityIssue.AUDIO_STUTTERING) }
)
if (isAudioExpanded) {
item(CallQualityIssue.AUDIO_STUTTERING) {
IssueChip(
issue = CallQualityIssue.AUDIO_STUTTERING,
isSelected = CallQualityIssue.AUDIO_STUTTERING in selectedQualityIssues,
onClick = { onCallQualityIssueClick(CallQualityIssue.AUDIO_STUTTERING) }
)
}
AnimatedIssueChip(
visible = isAudioExpanded,
issue = CallQualityIssue.AUDIO_CUT_OUT,
isSelected = CallQualityIssue.AUDIO_CUT_OUT in selectedQualityIssues,
onClick = { onCallQualityIssueClick(CallQualityIssue.AUDIO_CUT_OUT) }
)
item(CallQualityIssue.AUDIO_CUT_OUT) {
IssueChip(
issue = CallQualityIssue.AUDIO_CUT_OUT,
isSelected = CallQualityIssue.AUDIO_CUT_OUT in selectedQualityIssues,
onClick = { onCallQualityIssueClick(CallQualityIssue.AUDIO_CUT_OUT) }
)
}
AnimatedIssueChip(
visible = isAudioExpanded,
issue = CallQualityIssue.AUDIO_I_HEARD_ECHO,
isSelected = CallQualityIssue.AUDIO_I_HEARD_ECHO in selectedQualityIssues,
onClick = { onCallQualityIssueClick(CallQualityIssue.AUDIO_I_HEARD_ECHO) }
)
item(CallQualityIssue.AUDIO_I_HEARD_ECHO) {
IssueChip(
issue = CallQualityIssue.AUDIO_I_HEARD_ECHO,
isSelected = CallQualityIssue.AUDIO_I_HEARD_ECHO in selectedQualityIssues,
onClick = { onCallQualityIssueClick(CallQualityIssue.AUDIO_I_HEARD_ECHO) }
)
}
AnimatedIssueChip(
visible = isAudioExpanded,
issue = CallQualityIssue.AUDIO_OTHERS_HEARD_ECHO,
isSelected = CallQualityIssue.AUDIO_OTHERS_HEARD_ECHO in selectedQualityIssues,
onClick = { onCallQualityIssueClick(CallQualityIssue.AUDIO_OTHERS_HEARD_ECHO) }
)
item(CallQualityIssue.AUDIO_OTHERS_HEARD_ECHO) {
IssueChip(
issue = CallQualityIssue.AUDIO_OTHERS_HEARD_ECHO,
isSelected = CallQualityIssue.AUDIO_OTHERS_HEARD_ECHO in selectedQualityIssues,
onClick = { onCallQualityIssueClick(CallQualityIssue.AUDIO_OTHERS_HEARD_ECHO) }
)
}
}
IssueChip(
issue = CallQualityIssue.VIDEO_ISSUE,
isSelected = isVideoExpanded,
onClick = { onCallQualityIssueClick(CallQualityIssue.VIDEO_ISSUE) }
)
item(CallQualityIssue.VIDEO_ISSUE) {
IssueChip(
issue = CallQualityIssue.VIDEO_ISSUE,
isSelected = isVideoExpanded,
onClick = { onCallQualityIssueClick(CallQualityIssue.VIDEO_ISSUE) }
)
}
AnimatedIssueChip(
visible = isVideoExpanded,
issue = CallQualityIssue.VIDEO_POOR_QUALITY,
isSelected = CallQualityIssue.VIDEO_POOR_QUALITY in selectedQualityIssues,
onClick = { onCallQualityIssueClick(CallQualityIssue.VIDEO_POOR_QUALITY) }
)
if (isVideoExpanded) {
item(CallQualityIssue.VIDEO_POOR_QUALITY) {
IssueChip(
issue = CallQualityIssue.VIDEO_POOR_QUALITY,
isSelected = CallQualityIssue.VIDEO_POOR_QUALITY in selectedQualityIssues,
onClick = { onCallQualityIssueClick(CallQualityIssue.VIDEO_POOR_QUALITY) }
)
}
AnimatedIssueChip(
visible = isVideoExpanded,
issue = CallQualityIssue.VIDEO_LOW_RESOLUTION,
isSelected = CallQualityIssue.VIDEO_LOW_RESOLUTION in selectedQualityIssues,
onClick = { onCallQualityIssueClick(CallQualityIssue.VIDEO_LOW_RESOLUTION) }
)
item(CallQualityIssue.VIDEO_LOW_RESOLUTION) {
IssueChip(
issue = CallQualityIssue.VIDEO_LOW_RESOLUTION,
isSelected = CallQualityIssue.VIDEO_LOW_RESOLUTION in selectedQualityIssues,
onClick = { onCallQualityIssueClick(CallQualityIssue.VIDEO_LOW_RESOLUTION) }
)
}
AnimatedIssueChip(
visible = isVideoExpanded,
issue = CallQualityIssue.VIDEO_CAMERA_MALFUNCTION,
isSelected = CallQualityIssue.VIDEO_CAMERA_MALFUNCTION in selectedQualityIssues,
onClick = { onCallQualityIssueClick(CallQualityIssue.VIDEO_CAMERA_MALFUNCTION) }
)
item(CallQualityIssue.VIDEO_CAMERA_MALFUNCTION) {
IssueChip(
issue = CallQualityIssue.VIDEO_CAMERA_MALFUNCTION,
isSelected = CallQualityIssue.VIDEO_CAMERA_MALFUNCTION in selectedQualityIssues,
onClick = { onCallQualityIssueClick(CallQualityIssue.VIDEO_CAMERA_MALFUNCTION) }
)
}
}
IssueChip(
issue = CallQualityIssue.CALL_DROPPED,
isSelected = CallQualityIssue.CALL_DROPPED in selectedQualityIssues,
onClick = { onCallQualityIssueClick(CallQualityIssue.CALL_DROPPED) }
)
item(CallQualityIssue.CALL_DROPPED) {
IssueChip(
issue = CallQualityIssue.CALL_DROPPED,
isSelected = CallQualityIssue.CALL_DROPPED in selectedQualityIssues,
onClick = { onCallQualityIssueClick(CallQualityIssue.CALL_DROPPED) }
)
}
IssueChip(
issue = CallQualityIssue.SOMETHING_ELSE,
isSelected = CallQualityIssue.SOMETHING_ELSE in selectedQualityIssues,
onClick = { onCallQualityIssueClick(CallQualityIssue.SOMETHING_ELSE) }
)
item(CallQualityIssue.SOMETHING_ELSE) {
IssueChip(
issue = CallQualityIssue.SOMETHING_ELSE,
isSelected = CallQualityIssue.SOMETHING_ELSE in selectedQualityIssues,
onClick = { onCallQualityIssueClick(CallQualityIssue.SOMETHING_ELSE) }
)
}
}
AnimatedVisibility(
@@ -404,41 +422,10 @@ private fun IssueChip(
label = {
Text(text = stringResource(issue.label))
},
modifier = modifier.padding(horizontal = 4.dp)
modifier = modifier.padding(horizontal = 8.dp)
)
}
@Composable
private fun AnimatedIssueChip(
visible: Boolean,
issue: CallQualityIssue,
isSelected: Boolean,
onClick: () -> Unit
) {
AnimatedVisibility(
visible = visible,
enter = fadeIn(
animationSpec = tween(
durationMillis = 300,
delayMillis = 300,
easing = CubicBezierEasing(0f, 0f, 0.58f, 1f)
)
),
exit = fadeOut(
animationSpec = tween(
durationMillis = 300,
easing = CubicBezierEasing(0f, 0f, 0.58f, 1f)
)
)
) {
IssueChip(
issue = issue,
isSelected = isSelected,
onClick = onClick
)
}
}
@OptIn(ExperimentalMaterial3Api::class)
@Composable
private fun HelpUsImprove(
@@ -639,7 +626,7 @@ fun CancelButton(
}
}
@PreviewLightDark
@AllDevicePreviews
@Composable
private fun CallQualityScreenPreview() {
var state by remember { mutableStateOf(CallQualitySheetState()) }