package com.example.jetpackcomposepractise.ui.theme.designutils
import androidx.compose.animation.animateColorAsState
import androidx.compose.animation.core.animateFloatAsState
import androidx.compose.animation.core.animateIntAsState
import androidx.compose.animation.core.tween
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.size
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.drawBehind
import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.geometry.Size
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.StrokeCap
import androidx.compose.ui.graphics.drawscope.DrawScope
import androidx.compose.ui.graphics.drawscope.Stroke
import androidx.compose.ui.text.font.FontWeight
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.TextUnit
import androidx.compose.ui.unit.dp
@Composable
fun CustomCircular(
canvasSize: Dp = 300.dp,
indicatorValue: Int = 0,
maxIndicatorValue: Int = 100,
backgroundIndicatorColor: Color = MaterialTheme.colorScheme.onSurface.copy(alpha = 0.1f),
backgroundIndicatorStrokeWidth: Float = 100f,
foregroundIndicatorColor: Color = MaterialTheme.colorScheme.primary,
foregroundIndicatorStrokeWidth: Float = 100f,
indicatorStrokeCap: StrokeCap = StrokeCap.Round,
bigTextFontSize: TextUnit = MaterialTheme.typography.headlineMedium.fontSize,
bigTextColor: Color = MaterialTheme.colorScheme.onSurface,
bigTextSuffix: String = "GB",
smallText: String = "Remaining",
smallTextFontSize: TextUnit = MaterialTheme.typography.headlineSmall.fontSize,
smallTextColor: Color = MaterialTheme.colorScheme.onSurface.copy(alpha = 0.3f)
) {
var allowedIndicatorValue by remember {
mutableStateOf(maxIndicatorValue)
}
allowedIndicatorValue = if (indicatorValue <= maxIndicatorValue) {
indicatorValue
} else {
maxIndicatorValue
}
var animatedIndicatorValue by remember { mutableStateOf(0f) }
LaunchedEffect(key1 = allowedIndicatorValue) {
animatedIndicatorValue = allowedIndicatorValue.toFloat()
}
val percentage =
(animatedIndicatorValue / maxIndicatorValue) * 100
val sweepAngle by animateFloatAsState(
targetValue = (2.4 * percentage).toFloat(),
animationSpec = tween(1000), label = ""
)
val receivedValue by animateIntAsState(
targetValue = allowedIndicatorValue,
animationSpec = tween(1000), label = ""
)
val animatedBigTextColor by animateColorAsState(
targetValue = if (allowedIndicatorValue == 0)
MaterialTheme.colorScheme.onSurface.copy(alpha = 0.3f)
else
bigTextColor,
animationSpec = tween(1000), label = ""
)
Column(
modifier = Modifier
.size(canvasSize)
.drawBehind {
val componentSize = size / 1.25f
backgroundIndicator(
componentSize = componentSize,
indicatorColor = backgroundIndicatorColor,
indicatorStrokeWidth = backgroundIndicatorStrokeWidth,
indicatorStokeCap = indicatorStrokeCap
)
foregroundIndicator(
sweepAngle = sweepAngle,
componentSize = componentSize,
indicatorColor = foregroundIndicatorColor,
indicatorStrokeWidth = foregroundIndicatorStrokeWidth,
indicatorStokeCap = indicatorStrokeCap
)
},
verticalArrangement = Arrangement.Center,
horizontalAlignment = Alignment.CenterHorizontally
) {
EmbeddedElements(
bigText = receivedValue,
bigTextFontSize = bigTextFontSize,
bigTextColor = animatedBigTextColor,
bigTextSuffix = bigTextSuffix,
smallText = smallText,
smallTextColor = smallTextColor,
smallTextFontSize = smallTextFontSize
)
}
}
fun DrawScope.backgroundIndicator(
componentSize: Size,
indicatorColor: Color,
indicatorStrokeWidth: Float,
indicatorStokeCap: StrokeCap
) {
drawArc(
size = componentSize,
color = indicatorColor,
startAngle = 150f,
sweepAngle = 240f,
useCenter = false,
style = Stroke(
width = indicatorStrokeWidth,
cap = indicatorStokeCap
),
topLeft = Offset(
x = (size.width - componentSize.width) / 2f,
y = (size.height - componentSize.height) / 2f
)
)
}
fun DrawScope.foregroundIndicator(
sweepAngle: Float,
componentSize: Size,
indicatorColor: Color,
indicatorStrokeWidth: Float,
indicatorStokeCap: StrokeCap
) {
drawArc(
size = componentSize,
color = indicatorColor,
startAngle = 150f,
sweepAngle = sweepAngle,
useCenter = false,
style = Stroke(
width = indicatorStrokeWidth,
cap = indicatorStokeCap
),
topLeft = Offset(
x = (size.width - componentSize.width) / 2f,
y = (size.height - componentSize.height) / 2f
)
)
}
@Composable
fun EmbeddedElements(
bigText: Int,
bigTextFontSize: TextUnit,
bigTextColor: Color,
bigTextSuffix: String,
smallText: String,
smallTextColor: Color,
smallTextFontSize: TextUnit
) {
Text(
text = smallText,
color = smallTextColor,
fontSize = smallTextFontSize,
textAlign = TextAlign.Center
)
Text(
text = "$bigText ${bigTextSuffix.take(2)}",
color = bigTextColor,
fontSize = bigTextFontSize,
textAlign = TextAlign.Center,
fontWeight = FontWeight.Bold
)
}
@Composable
@Preview(showBackground = true)
fun CustomComponentPreview() {
CustomCircular()
}
0 Comments