Blocks

Spending Statistic

A dark analytics screen with month tabs, a smooth spending line chart with a peak marker and category cards.

9:41

Statistic

DecemberJanuaryFebruaryMar

Subscription & Phone

Average level

$ 452

11

Mon

12

Tue

13

Wed

14

Thu

15

Fri

16

Sat

Spending categories

Subscription & Phone

Housing & energy

Installation

caveui components are copy-paste Jetpack Compose built entirely on Material 3 — there's no caveui dependency to add. Make sure Material 3 is on your classpath (it ships with the Compose BOM), then copy the Usage snippet below into your project.

kotlin
// build.gradle.kts (module)
dependencies {
    implementation(platform("androidx.compose:compose-bom:2025.06.00"))
    implementation("androidx.compose.material3:material3")
}

Usage

kotlin
@Composable
fun SpendingStatisticScreen(months: List<String>, points: List<Float>) {
    val accent = Color(0xFF3B82F6)
    Column(Modifier.fillMaxSize().background(Color(0xFF0B1020)).padding(16.dp)) {
        Row(Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.SpaceBetween,
            verticalAlignment = Alignment.CenterVertically) {
            Icon(Icons.AutoMirrored.Filled.ArrowBack, null, tint = Color.White)
            Text("Statistic", color = Color.White, fontWeight = FontWeight.Bold)
            Icon(Icons.Filled.GridView, null, tint = Color.White)
        }
        ScrollableTabRow(selectedTabIndex = 1, edgePadding = 0.dp,
            containerColor = Color.Transparent, contentColor = Color.White,
            modifier = Modifier.padding(top = 12.dp)) {
            months.forEachIndexed { i, m ->
                Tab(selected = i == 1, onClick = {}, text = { Text(m) })
            }
        }
        Text("Subscription & Phone", Modifier.padding(top = 16.dp),
            color = Color.White.copy(alpha = 0.6f), style = MaterialTheme.typography.bodySmall)
        Canvas(Modifier.fillMaxWidth().height(120.dp).padding(top = 12.dp)) {
            val path = Path()
            val stepX = size.width / (points.size - 1)
            points.forEachIndexed { i, p ->
                val x = i * stepX
                val y = size.height * (1 - p)
                if (i == 0) path.moveTo(x, y) else path.lineTo(x, y)
            }
            drawPath(path, color = accent, style = Stroke(width = 3f, cap = StrokeCap.Round))
        }
        Text("Spending categories", Modifier.padding(top = 16.dp),
            color = Color.White, style = MaterialTheme.typography.titleSmall,
            fontWeight = FontWeight.Bold)
        Row(Modifier.padding(top = 12.dp), horizontalArrangement = Arrangement.spacedBy(12.dp)) {
            listOf(
                Icons.Filled.Phone to "Subscription & Phone",
                Icons.Filled.Home to "Housing & energy",
            ).forEach { (icon, label) ->
                Surface(color = Color.White.copy(alpha = 0.05f), shape = RoundedCornerShape(16.dp),
                    modifier = Modifier.weight(1f)) {
                    Column(Modifier.padding(12.dp)) {
                        Surface(color = Color.White.copy(alpha = 0.1f), shape = CircleShape,
                            modifier = Modifier.size(36.dp)) {
                            Box(contentAlignment = Alignment.Center) {
                                Icon(icon, null, tint = Color(0xFF93C5FD))
                            }
                        }
                        Text(label, Modifier.padding(top = 8.dp), color = Color.White,
                            style = MaterialTheme.typography.labelMedium)
                    }
                }
            }
        }
    }
}