Updated on 2026-08-14

This commit is contained in:
Tangem 2025-01-23 11:50:39 +05:00
parent ec9bd02e35
commit 8c6a582d4a
9 changed files with 195 additions and 48 deletions

View file

@ -3,11 +3,11 @@
package com.tangem.core.ui.components.progressbar
import android.content.res.Configuration
import androidx.compose.animation.core.*
import androidx.compose.foundation.Canvas
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.runtime.Composable
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.*
import androidx.compose.runtime.*
import androidx.compose.ui.Modifier
import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.graphics.Color
@ -39,7 +39,7 @@ import kotlin.math.abs
* @param strokeCap stroke cap to use for the ends of this progress indicator
*/
@Composable
fun LinearProgressIndicator(
fun TangemLinearProgressIndicator(
progress: () -> Float,
modifier: Modifier = Modifier,
color: Color = TangemTheme.colors.text.accent,
@ -61,6 +61,99 @@ fun LinearProgressIndicator(
}
}
/**
* Progress indicators loading status with infinite animation
*
* @param modifier the [Modifier] to be applied to this progress indicator
* @param color The color of the progress indicator.
* @param backgroundColor The color of the background behind the indicator, visible when the
* progress has not reached that area of the overall indicator yet.
* @param strokeCap stroke cap to use for the ends of this progress indicator
*/
@Suppress("LongMethod")
@Composable
fun TangemLinearProgressIndicator(
modifier: Modifier = Modifier,
color: Color = TangemTheme.colors.text.accent,
backgroundColor: Color = TangemTheme.colors.background.tertiary,
strokeCap: StrokeCap = StrokeCap.Round,
) {
val infiniteTransition = rememberInfiniteTransition()
val firstLineHead by infiniteTransition.animateFloat(
0f,
1f,
infiniteRepeatable(
animation = keyframes {
durationMillis = LINEAR_ANIMATION_DURATION
0f at FIRST_LINE_HEAD_DELAY using FIRST_LINE_HEAD_EASING
1f at FIRST_LINE_HEAD_DURATION + FIRST_LINE_HEAD_DELAY
},
),
)
val firstLineTail by infiniteTransition.animateFloat(
0f,
1f,
infiniteRepeatable(
animation = keyframes {
durationMillis = LINEAR_ANIMATION_DURATION
0f at FIRST_LINE_TAIL_DELAY using FIRST_LINE_TAIL_EASING
1f at FIRST_LINE_TAIL_DURATION + FIRST_LINE_TAIL_DELAY
},
),
)
val secondLineHead by infiniteTransition.animateFloat(
0f,
1f,
infiniteRepeatable(
animation = keyframes {
durationMillis = LINEAR_ANIMATION_DURATION
0f at SECOND_LINE_HEAD_DELAY using SECOND_LINE_HEAD_EASING
1f at SECOND_LINE_HEAD_DURATION + SECOND_LINE_HEAD_DELAY
},
),
)
val secondLineTail by infiniteTransition.animateFloat(
0f,
1f,
infiniteRepeatable(
animation = keyframes {
durationMillis = LINEAR_ANIMATION_DURATION
0f at SECOND_LINE_TAIL_DELAY using SECOND_LINE_TAIL_EASING
1f at SECOND_LINE_TAIL_DURATION + SECOND_LINE_TAIL_DELAY
},
),
)
Canvas(
modifier
.increaseSemanticsBounds()
.semantics(mergeDescendants = true) {
progressBarRangeInfo = ProgressBarRangeInfo.Indeterminate
}
.size(height = 4.dp, width = 24.dp),
) {
val strokeWidth = size.height
drawLinearIndicatorBackground(backgroundColor, strokeWidth, strokeCap)
if (firstLineHead - firstLineTail > 0) {
drawLinearIndicator(
firstLineHead,
firstLineTail,
color,
strokeWidth,
strokeCap,
)
}
if ((secondLineHead - secondLineTail) > 0) {
drawLinearIndicator(
secondLineHead,
secondLineTail,
color,
strokeWidth,
strokeCap,
)
}
}
}
internal fun Modifier.increaseSemanticsBounds(): Modifier {
val padding = 10.dp
return this
@ -129,16 +222,51 @@ private fun DrawScope.drawLinearIndicator(
private fun DrawScope.drawLinearIndicatorBackground(color: Color, strokeWidth: Float, strokeCap: StrokeCap) =
drawLinearIndicator(0f, 1f, color, strokeWidth, strokeCap)
// Indeterminate linear indicator transition specs
// Total duration for one cycle
private const val LINEAR_ANIMATION_DURATION = 1800
// Duration of the head and tail animations for both lines
private const val FIRST_LINE_HEAD_DURATION = 750
private const val FIRST_LINE_TAIL_DURATION = 850
private const val SECOND_LINE_HEAD_DURATION = 567
private const val SECOND_LINE_TAIL_DURATION = 533
// Delay before the start of the head and tail animations for both lines
private const val FIRST_LINE_HEAD_DELAY = 0
private const val FIRST_LINE_TAIL_DELAY = 333
private const val SECOND_LINE_HEAD_DELAY = 1000
private const val SECOND_LINE_TAIL_DELAY = 1267
private val FIRST_LINE_HEAD_EASING = CubicBezierEasing(0.2f, 0f, 0.8f, 1f)
private val FIRST_LINE_TAIL_EASING = CubicBezierEasing(0.4f, 0f, 1f, 1f)
private val SECOND_LINE_HEAD_EASING = CubicBezierEasing(0f, 0f, 0.65f, 1f)
private val SECOND_LINE_TAIL_EASING = CubicBezierEasing(0.1f, 0f, 0.45f, 1f)
// region Preview
@Preview(showBackground = true, widthDp = 360)
@Preview(showBackground = true, widthDp = 360, uiMode = Configuration.UI_MODE_NIGHT_YES)
@Composable
private fun LinearProgressIndicator_Preview() {
TangemThemePreview {
LinearProgressIndicator(
progress = { 0.5f },
modifier = Modifier.fillMaxWidth(),
)
Column(
modifier = Modifier
.background(TangemTheme.colors.background.secondary)
.padding(12.dp),
verticalArrangement = Arrangement.spacedBy(12.dp),
) {
TangemLinearProgressIndicator(
progress = { 0.6f },
modifier = Modifier.fillMaxWidth(),
color = TangemTheme.colors.icon.primary1,
backgroundColor = TangemTheme.colors.background.tertiary,
)
TangemLinearProgressIndicator(
modifier = Modifier.fillMaxWidth(),
color = TangemTheme.colors.icon.primary1,
backgroundColor = TangemTheme.colors.background.tertiary,
)
}
}
}
// endregion