Remove BigIcon loading state.

Loading is rendered in the buttons now.
This commit is contained in:
Benoit Marty 2026-01-08 15:53:09 +01:00
parent 6b6ce90fcb
commit 59c175ebe0
3 changed files with 86 additions and 141 deletions

View file

@ -21,10 +21,8 @@ import androidx.compose.ui.Alignment
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.res.stringResource import androidx.compose.ui.res.stringResource
import androidx.compose.ui.semantics.ProgressBarRangeInfo
import androidx.compose.ui.semantics.contentDescription import androidx.compose.ui.semantics.contentDescription
import androidx.compose.ui.semantics.focused import androidx.compose.ui.semantics.focused
import androidx.compose.ui.semantics.progressBarRangeInfo
import androidx.compose.ui.semantics.semantics import androidx.compose.ui.semantics.semantics
import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.tooling.preview.Preview
@ -45,7 +43,6 @@ import io.element.android.libraries.designsystem.components.button.BackButton
import io.element.android.libraries.designsystem.preview.ElementPreview import io.element.android.libraries.designsystem.preview.ElementPreview
import io.element.android.libraries.designsystem.preview.PreviewsDayNight import io.element.android.libraries.designsystem.preview.PreviewsDayNight
import io.element.android.libraries.designsystem.theme.components.Button import io.element.android.libraries.designsystem.theme.components.Button
import io.element.android.libraries.designsystem.theme.components.InvisibleButton
import io.element.android.libraries.designsystem.theme.components.Text import io.element.android.libraries.designsystem.theme.components.Text
import io.element.android.libraries.designsystem.theme.components.TextButton import io.element.android.libraries.designsystem.theme.components.TextButton
import io.element.android.libraries.designsystem.theme.components.TopAppBar import io.element.android.libraries.designsystem.theme.components.TopAppBar
@ -103,19 +100,11 @@ fun IncomingVerificationView(
private fun IncomingVerificationHeader(step: Step, request: VerificationRequest.Incoming) { private fun IncomingVerificationHeader(step: Step, request: VerificationRequest.Incoming) {
val iconStyle = when (step) { val iconStyle = when (step) {
Step.Canceled -> BigIcon.Style.AlertSolid Step.Canceled -> BigIcon.Style.AlertSolid
is Step.Initial -> if (step.isWaiting) { is Step.Initial -> when (request) {
BigIcon.Style.Loading is VerificationRequest.Incoming.OtherSession -> BigIcon.Style.Default(CompoundIcons.LockSolid())
} else { is VerificationRequest.Incoming.User -> BigIcon.Style.Default(CompoundIcons.UserProfileSolid())
when (request) {
is VerificationRequest.Incoming.OtherSession -> BigIcon.Style.Default(CompoundIcons.LockSolid())
is VerificationRequest.Incoming.User -> BigIcon.Style.Default(CompoundIcons.UserProfileSolid())
}
}
is Step.Verifying -> if (step.isWaiting) {
BigIcon.Style.Loading
} else {
BigIcon.Style.Default(CompoundIcons.ReactionSolid())
} }
is Step.Verifying -> BigIcon.Style.Default(CompoundIcons.ReactionSolid())
Step.Completed -> BigIcon.Style.SuccessSolid Step.Completed -> BigIcon.Style.SuccessSolid
Step.Failure -> BigIcon.Style.AlertSolid Step.Failure -> BigIcon.Style.AlertSolid
} }
@ -159,10 +148,6 @@ private fun IncomingVerificationHeader(step: Step, request: VerificationRequest.
.semantics(mergeDescendants = true) { .semantics(mergeDescendants = true) {
contentDescription = timeLimitMessage contentDescription = timeLimitMessage
focused = true focused = true
if (iconStyle == BigIcon.Style.Loading) {
// Same code than Modifier.progressSemantics()
progressBarRangeInfo = ProgressBarRangeInfo.Indeterminate
}
} }
.focusable(), .focusable(),
iconStyle = iconStyle, iconStyle = iconStyle,
@ -232,43 +217,37 @@ private fun IncomingVerificationBottomMenu(
when (step) { when (step) {
is Step.Initial -> { is Step.Initial -> {
if (step.isWaiting) { VerificationBottomMenu {
// Show nothing Button(
} else { modifier = Modifier.fillMaxWidth(),
VerificationBottomMenu { text = stringResource(CommonStrings.action_start_verification),
Button( enabled = !step.isWaiting,
modifier = Modifier.fillMaxWidth(), showProgress = step.isWaiting,
text = stringResource(CommonStrings.action_start_verification), onClick = { eventSink(IncomingVerificationViewEvents.StartVerification) },
onClick = { eventSink(IncomingVerificationViewEvents.StartVerification) }, )
) TextButton(
TextButton( modifier = Modifier.fillMaxWidth(),
modifier = Modifier.fillMaxWidth(), text = stringResource(CommonStrings.action_ignore),
text = stringResource(CommonStrings.action_ignore), enabled = !step.isWaiting,
onClick = { eventSink(IncomingVerificationViewEvents.IgnoreVerification) }, onClick = { eventSink(IncomingVerificationViewEvents.IgnoreVerification) },
) )
}
} }
} }
is Step.Verifying -> { is Step.Verifying -> {
if (step.isWaiting) { VerificationBottomMenu {
// Add invisible buttons to keep the same screen layout Button(
VerificationBottomMenu { modifier = Modifier.fillMaxWidth(),
InvisibleButton() text = stringResource(R.string.screen_session_verification_they_match),
InvisibleButton() enabled = !step.isWaiting,
} showProgress = step.isWaiting,
} else { onClick = { eventSink(IncomingVerificationViewEvents.ConfirmVerification) },
VerificationBottomMenu { )
Button( TextButton(
modifier = Modifier.fillMaxWidth(), modifier = Modifier.fillMaxWidth(),
text = stringResource(R.string.screen_session_verification_they_match), text = stringResource(R.string.screen_session_verification_they_dont_match),
onClick = { eventSink(IncomingVerificationViewEvents.ConfirmVerification) }, enabled = !step.isWaiting,
) onClick = { eventSink(IncomingVerificationViewEvents.DeclineVerification) },
TextButton( )
modifier = Modifier.fillMaxWidth(),
text = stringResource(R.string.screen_session_verification_they_dont_match),
onClick = { eventSink(IncomingVerificationViewEvents.DeclineVerification) },
)
}
} }
} }
Step.Canceled, Step.Canceled,

View file

@ -24,10 +24,8 @@ import androidx.compose.ui.Alignment
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.res.stringResource import androidx.compose.ui.res.stringResource
import androidx.compose.ui.semantics.ProgressBarRangeInfo
import androidx.compose.ui.semantics.contentDescription import androidx.compose.ui.semantics.contentDescription
import androidx.compose.ui.semantics.focused import androidx.compose.ui.semantics.focused
import androidx.compose.ui.semantics.progressBarRangeInfo
import androidx.compose.ui.semantics.semantics import androidx.compose.ui.semantics.semantics
import androidx.compose.ui.tooling.preview.PreviewParameter import androidx.compose.ui.tooling.preview.PreviewParameter
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
@ -129,20 +127,16 @@ fun OutgoingVerificationView(
private fun OutgoingVerificationHeader(step: Step, request: VerificationRequest.Outgoing) { private fun OutgoingVerificationHeader(step: Step, request: VerificationRequest.Outgoing) {
val iconStyle = when (step) { val iconStyle = when (step) {
Step.Loading -> error("Should not happen") Step.Loading -> error("Should not happen")
Step.AwaitingOtherDeviceResponse,
Step.Initial -> when (request) { Step.Initial -> when (request) {
is VerificationRequest.Outgoing.CurrentSession -> BigIcon.Style.Default(CompoundIcons.Devices()) is VerificationRequest.Outgoing.CurrentSession -> BigIcon.Style.Default(CompoundIcons.Devices())
is VerificationRequest.Outgoing.User -> BigIcon.Style.Default(CompoundIcons.LockSolid()) is VerificationRequest.Outgoing.User -> BigIcon.Style.Default(CompoundIcons.LockSolid())
} }
Step.AwaitingOtherDeviceResponse -> BigIcon.Style.Loading
Step.Canceled -> BigIcon.Style.AlertSolid Step.Canceled -> BigIcon.Style.AlertSolid
Step.Ready -> BigIcon.Style.Default(CompoundIcons.ReactionSolid()) Step.Ready -> BigIcon.Style.Default(CompoundIcons.ReactionSolid())
Step.Completed -> BigIcon.Style.SuccessSolid Step.Completed -> BigIcon.Style.SuccessSolid
is Step.Verifying -> { is Step.Verifying -> {
if (step.state is AsyncData.Loading<Unit>) { BigIcon.Style.Default(CompoundIcons.ReactionSolid())
BigIcon.Style.Loading
} else {
BigIcon.Style.Default(CompoundIcons.ReactionSolid())
}
} }
is Step.Exit -> return is Step.Exit -> return
} }
@ -201,10 +195,6 @@ private fun OutgoingVerificationHeader(step: Step, request: VerificationRequest.
.semantics(mergeDescendants = true) { .semantics(mergeDescendants = true) {
contentDescription = timeLimitMessage contentDescription = timeLimitMessage
focused = true focused = true
if (iconStyle == BigIcon.Style.Loading) {
// Same code than Modifier.progressSemantics()
progressBarRangeInfo = ProgressBarRangeInfo.Indeterminate
}
} }
.focusable(), .focusable(),
iconStyle = iconStyle, iconStyle = iconStyle,
@ -264,11 +254,15 @@ private fun OutgoingVerificationViewBottomMenu(
when (verificationViewState) { when (verificationViewState) {
Step.Loading -> error("Should not happen") Step.Loading -> error("Should not happen")
is Step.AwaitingOtherDeviceResponse,
is Step.Initial -> { is Step.Initial -> {
VerificationBottomMenu { VerificationBottomMenu {
val isWaiting = verificationViewState is Step.AwaitingOtherDeviceResponse
Button( Button(
modifier = Modifier.fillMaxWidth(), modifier = Modifier.fillMaxWidth(),
text = stringResource(CommonStrings.action_start_verification), text = stringResource(CommonStrings.action_start_verification),
enabled = !isWaiting,
showProgress = isWaiting,
onClick = { eventSink(OutgoingVerificationViewEvents.RequestVerification) }, onClick = { eventSink(OutgoingVerificationViewEvents.RequestVerification) },
) )
InvisibleButton() InvisibleButton()
@ -298,30 +292,23 @@ private fun OutgoingVerificationViewBottomMenu(
) )
} }
} }
is Step.AwaitingOtherDeviceResponse -> Unit
is Step.Verifying -> { is Step.Verifying -> {
if (isVerifying) { VerificationBottomMenu {
// Add invisible buttons to keep the same screen layout Button(
VerificationBottomMenu { modifier = Modifier.fillMaxWidth(),
InvisibleButton() text = stringResource(R.string.screen_session_verification_they_match),
InvisibleButton() enabled = !isVerifying,
} showProgress = isVerifying,
} else { onClick = {
VerificationBottomMenu { eventSink(OutgoingVerificationViewEvents.ConfirmVerification)
Button( },
modifier = Modifier.fillMaxWidth(), )
text = stringResource(R.string.screen_session_verification_they_match),
onClick = {
eventSink(OutgoingVerificationViewEvents.ConfirmVerification)
},
)
TextButton( TextButton(
modifier = Modifier.fillMaxWidth(), modifier = Modifier.fillMaxWidth(),
text = stringResource(R.string.screen_session_verification_they_dont_match), text = stringResource(R.string.screen_session_verification_they_dont_match),
onClick = { eventSink(OutgoingVerificationViewEvents.DeclineVerification) }, onClick = { eventSink(OutgoingVerificationViewEvents.DeclineVerification) },
) )
}
} }
} }
is Step.Completed -> { is Step.Completed -> {
@ -334,7 +321,7 @@ private fun OutgoingVerificationViewBottomMenu(
InvisibleButton() InvisibleButton()
} }
} }
is Step.Exit -> return is Step.Exit -> Unit
} }
} }

View file

@ -34,7 +34,6 @@ import io.element.android.compound.theme.ElementTheme
import io.element.android.compound.tokens.generated.CompoundIcons import io.element.android.compound.tokens.generated.CompoundIcons
import io.element.android.libraries.designsystem.preview.ElementPreview import io.element.android.libraries.designsystem.preview.ElementPreview
import io.element.android.libraries.designsystem.preview.PreviewsDayNight import io.element.android.libraries.designsystem.preview.PreviewsDayNight
import io.element.android.libraries.designsystem.theme.components.CircularProgressIndicator
import io.element.android.libraries.designsystem.theme.components.Icon import io.element.android.libraries.designsystem.theme.components.Icon
import io.element.android.libraries.ui.strings.CommonStrings import io.element.android.libraries.ui.strings.CommonStrings
@ -82,11 +81,6 @@ object BigIcon {
* A success style with a tinted background. * A success style with a tinted background.
*/ */
data object SuccessSolid : Style data object SuccessSolid : Style
/**
* A loading style with the default background color.
*/
data object Loading : Style
} }
/** /**
@ -110,7 +104,6 @@ object BigIcon {
Style.Success -> Color.Transparent Style.Success -> Color.Transparent
Style.AlertSolid -> ElementTheme.colors.bgCriticalSubtle Style.AlertSolid -> ElementTheme.colors.bgCriticalSubtle
Style.SuccessSolid -> ElementTheme.colors.bgSuccessSubtle Style.SuccessSolid -> ElementTheme.colors.bgSuccessSubtle
Style.Loading -> ElementTheme.colors.bgSubtleSecondary
} }
Box( Box(
modifier = modifier modifier = modifier
@ -119,52 +112,39 @@ object BigIcon {
.background(backgroundColor), .background(backgroundColor),
contentAlignment = Alignment.Center, contentAlignment = Alignment.Center,
) { ) {
if (style is Style.Loading) { val icon = when (style) {
CircularProgressIndicator( is Style.Default -> style.vectorIcon
modifier = Modifier.size(27.dp), Style.Alert,
color = ElementTheme.colors.iconSecondary, Style.AlertSolid -> CompoundIcons.ErrorSolid()
trackColor = Color.Transparent, Style.Success,
strokeWidth = 3.dp, Style.SuccessSolid -> CompoundIcons.CheckCircleSolid()
)
} else {
val icon = when (style) {
is Style.Default -> style.vectorIcon
Style.Alert,
Style.AlertSolid -> CompoundIcons.ErrorSolid()
Style.Success,
Style.SuccessSolid -> CompoundIcons.CheckCircleSolid()
Style.Loading -> error("This should never be reached")
}
val contentDescription = when (style) {
is Style.Default -> style.contentDescription
Style.Alert,
Style.AlertSolid -> stringResource(CommonStrings.common_error)
Style.Success,
Style.SuccessSolid -> stringResource(CommonStrings.common_success)
Style.Loading -> error("This should never be reached")
}
val iconTint = when (style) {
is Style.Default -> if (style.useCriticalTint) {
ElementTheme.colors.iconCriticalPrimary
} else if (style.usePrimaryTint) {
ElementTheme.colors.iconPrimary
} else {
ElementTheme.colors.iconSecondary
}
Style.Alert,
Style.AlertSolid -> ElementTheme.colors.iconCriticalPrimary
Style.Success,
Style.SuccessSolid -> ElementTheme.colors.iconSuccessPrimary
Style.Loading -> error("This should never be reached")
}
Icon(
modifier = Modifier.size(32.dp),
tint = iconTint,
imageVector = icon,
contentDescription = contentDescription
)
} }
val contentDescription = when (style) {
is Style.Default -> style.contentDescription
Style.Alert,
Style.AlertSolid -> stringResource(CommonStrings.common_error)
Style.Success,
Style.SuccessSolid -> stringResource(CommonStrings.common_success)
}
val iconTint = when (style) {
is Style.Default -> if (style.useCriticalTint) {
ElementTheme.colors.iconCriticalPrimary
} else if (style.usePrimaryTint) {
ElementTheme.colors.iconPrimary
} else {
ElementTheme.colors.iconSecondary
}
Style.Alert,
Style.AlertSolid -> ElementTheme.colors.iconCriticalPrimary
Style.Success,
Style.SuccessSolid -> ElementTheme.colors.iconSuccessPrimary
}
Icon(
modifier = Modifier.size(32.dp),
tint = iconTint,
imageVector = icon,
contentDescription = contentDescription
)
} }
} }
} }
@ -199,6 +179,5 @@ internal class BigIconStyleProvider : PreviewParameterProvider<BigIcon.Style> {
BigIcon.Style.Default(Icons.Filled.CatchingPokemon, useCriticalTint = true), BigIcon.Style.Default(Icons.Filled.CatchingPokemon, useCriticalTint = true),
BigIcon.Style.Success, BigIcon.Style.Success,
BigIcon.Style.SuccessSolid, BigIcon.Style.SuccessSolid,
BigIcon.Style.Loading,
) )
} }