feat(metric): reimplement RoundedDiagram/RingDiagram in Compose

- Add MetricGauge, RingGauge, MetricGaugeRow composables
- Add MetricGaugeContract with SensorType enum and AQI color mapping
- Add AQI colors (SensorGreen, SensorYellow, etc.) to theme
- Copy ic_temperature, ic_humidity, ic_pressure drawables from legacy
- Integrate MetricGaugeRow into DashboardScreen
- Add 9 preview composables for portfolio readiness

Made-with: Cursor
This commit is contained in:
2026-03-04 19:34:59 +01:00
parent 00ad737e7e
commit ca5cf8c439
8 changed files with 612 additions and 17 deletions

View File

@@ -0,0 +1,351 @@
package org.db3.airmq.features.common.metric
import androidx.compose.foundation.background
import androidx.compose.foundation.clickable
import androidx.compose.foundation.interaction.MutableInteractionSource
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.offset
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.widthIn
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material3.Icon
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Row
import org.db3.airmq.features.common.metric.getColorFromMetrics
import org.db3.airmq.features.common.metric.getSensorIconRes
import org.db3.airmq.ui.theme.AirMQTheme
import org.db3.airmq.ui.theme.LegacyNavGradientEnd
import org.db3.airmq.ui.theme.LegacyNavGradientStart
/**
* Single metric gauge with ring, value, units, and icon.
* Mirrors legacy [RoundedDiagram] layout and behavior.
*
* @param value Current value; null shows "?"
* @param sensorType Sensor type for icon, units, and progress logic
* @param selected When true, shows frosted background
* @param onClick Called when gauge is tapped
* @param modifier Modifier
*/
@Composable
fun MetricGauge(
value: Float?,
sensorType: SensorType,
selected: Boolean,
onClick: () -> Unit,
modifier: Modifier = Modifier
) {
val (progress, progressColor) = when (sensorType) {
SensorType.HUMIDITY -> {
val p = value?.coerceIn(0f, 100f) ?: 0f
p to Color.White
}
SensorType.DUST -> {
val p = value?.coerceIn(0f, 100f) ?: 0f
val color = value?.let { getColorFromMetrics(it, sensorType) } ?: Color.White
p to color
}
else -> 0f to Color.White
}
val valueText = when {
value == null -> "?"
sensorType == SensorType.RADIOACTIVITY -> "%.2f".format(value)
else -> kotlin.math.round(value).toInt().toString()
}
Box(
modifier = modifier
.widthIn(min = 96.dp)
.padding(horizontal = 4.dp)
.clickable(
interactionSource = remember { MutableInteractionSource() },
indication = null
) { onClick() }
) {
if (selected) {
Box(
modifier = Modifier
.matchParentSize()
.clip(RoundedCornerShape(18.dp))
.background(Color.White.copy(alpha = 0.1f))
)
}
Box(
modifier = Modifier
.align(Alignment.Center)
.padding(vertical = 8.dp),
contentAlignment = Alignment.Center
) {
RingGauge(
progress = progress,
progressColor = progressColor,
modifier = Modifier.size(77.dp)
)
Column(
modifier = Modifier.align(Alignment.Center),
horizontalAlignment = Alignment.CenterHorizontally
) {
Text(
text = valueText,
color = Color.White,
fontSize = 16.sp,
fontWeight = FontWeight.Medium
)
Text(
text = sensorType.units(),
color = Color.White.copy(alpha = 0.54f),
fontSize = 12.sp,
fontWeight = FontWeight.Medium
)
}
Icon(
painter = painterResource(id = getSensorIconRes(sensorType)),
contentDescription = null,
modifier = Modifier
.align(Alignment.Center)
.offset(y = 38.dp)
.size(24.dp),
tint = Color.White
)
}
}
}
/** Default sensor order for dashboard gauge row. */
private val DashboardGaugeOrder = listOf(
SensorType.DUST,
SensorType.RADIOACTIVITY,
SensorType.TEMPERATURE
)
/**
* Horizontal row of metric gauges (dust, radioactivity, temperature).
*
* @param selectedSensor Currently selected sensor; null if none
* @param values Map of sensor type to value
* @param onGaugeSelected Called when a gauge is tapped
* @param modifier Modifier
*/
@Composable
fun MetricGaugeRow(
selectedSensor: SensorType?,
values: Map<SensorType, Float?>,
onGaugeSelected: (SensorType) -> Unit,
modifier: Modifier = Modifier
) {
Row(
modifier = modifier,
horizontalArrangement = Arrangement.SpaceEvenly
) {
for (sensorType in DashboardGaugeOrder) {
MetricGauge(
value = values[sensorType],
sensorType = sensorType,
selected = selectedSensor == sensorType,
onClick = { onGaugeSelected(sensorType) }
)
}
}
}
@Preview(showBackground = true, name = "Dust value 6")
@Composable
private fun PreviewMetricGaugeDust() {
AirMQTheme {
Box(
modifier = Modifier
.background(
androidx.compose.ui.graphics.Brush.verticalGradient(
colors = listOf(LegacyNavGradientStart, LegacyNavGradientEnd)
)
)
.padding(16.dp)
) {
MetricGauge(
value = 6f,
sensorType = SensorType.DUST,
selected = false,
onClick = {}
)
}
}
}
@Preview(showBackground = true, name = "Radiation value 0")
@Composable
private fun PreviewMetricGaugeRadiation() {
AirMQTheme {
Box(
modifier = Modifier
.background(
androidx.compose.ui.graphics.Brush.verticalGradient(
colors = listOf(LegacyNavGradientStart, LegacyNavGradientEnd)
)
)
.padding(16.dp)
) {
MetricGauge(
value = 0f,
sensorType = SensorType.RADIOACTIVITY,
selected = false,
onClick = {}
)
}
}
}
@Preview(showBackground = true, name = "Temperature value 3")
@Composable
private fun PreviewMetricGaugeTemperature() {
AirMQTheme {
Box(
modifier = Modifier
.background(
androidx.compose.ui.graphics.Brush.verticalGradient(
colors = listOf(LegacyNavGradientStart, LegacyNavGradientEnd)
)
)
.padding(16.dp)
) {
MetricGauge(
value = 3f,
sensorType = SensorType.TEMPERATURE,
selected = false,
onClick = {}
)
}
}
}
@Preview(showBackground = true, name = "Humidity value 65")
@Composable
private fun PreviewMetricGaugeHumidity() {
AirMQTheme {
Box(
modifier = Modifier
.background(
androidx.compose.ui.graphics.Brush.verticalGradient(
colors = listOf(LegacyNavGradientStart, LegacyNavGradientEnd)
)
)
.padding(16.dp)
) {
MetricGauge(
value = 65f,
sensorType = SensorType.HUMIDITY,
selected = false,
onClick = {}
)
}
}
}
@Preview(showBackground = true, name = "No data")
@Composable
private fun PreviewMetricGaugeNoData() {
AirMQTheme {
Box(
modifier = Modifier
.background(
androidx.compose.ui.graphics.Brush.verticalGradient(
colors = listOf(LegacyNavGradientStart, LegacyNavGradientEnd)
)
)
.padding(16.dp)
) {
MetricGauge(
value = null,
sensorType = SensorType.DUST,
selected = false,
onClick = {}
)
}
}
}
@Preview(showBackground = true, name = "Selected")
@Composable
private fun PreviewMetricGaugeSelected() {
AirMQTheme {
Box(
modifier = Modifier
.background(
androidx.compose.ui.graphics.Brush.verticalGradient(
colors = listOf(LegacyNavGradientStart, LegacyNavGradientEnd)
)
)
.padding(16.dp)
) {
MetricGauge(
value = 6f,
sensorType = SensorType.DUST,
selected = true,
onClick = {}
)
}
}
}
@Preview(showBackground = true, name = "Gauge row")
@Composable
private fun PreviewMetricGaugeRow() {
AirMQTheme {
Box(
modifier = Modifier
.background(
androidx.compose.ui.graphics.Brush.verticalGradient(
colors = listOf(LegacyNavGradientStart, LegacyNavGradientEnd)
)
)
.padding(16.dp)
) {
MetricGaugeRow(
selectedSensor = SensorType.DUST,
values = mapOf(
SensorType.DUST to 6f,
SensorType.RADIOACTIVITY to 0f,
SensorType.TEMPERATURE to 3f
),
onGaugeSelected = {}
)
}
}
}
@Preview(showBackground = true, name = "Dust orange band")
@Composable
private fun PreviewMetricGaugeDustOrange() {
AirMQTheme {
Box(
modifier = Modifier
.background(
androidx.compose.ui.graphics.Brush.verticalGradient(
colors = listOf(LegacyNavGradientStart, LegacyNavGradientEnd)
)
)
.padding(16.dp)
) {
MetricGauge(
value = 40f,
sensorType = SensorType.DUST,
selected = false,
onClick = {}
)
}
}
}

View File

@@ -0,0 +1,54 @@
package org.db3.airmq.features.common.metric
import androidx.compose.ui.graphics.Color
import org.db3.airmq.R
import org.db3.airmq.features.common.chart.getUnitsForSensor
import org.db3.airmq.ui.theme.SensorGreen
import org.db3.airmq.ui.theme.SensorOrange
import org.db3.airmq.ui.theme.SensorPink
import org.db3.airmq.ui.theme.SensorPurple
import org.db3.airmq.ui.theme.SensorRed
import org.db3.airmq.ui.theme.SensorYellow
/**
* Sensor types supported by the metric gauge.
* Maps to legacy Chart sensor constants.
*/
enum class SensorType(val legacyKey: String) {
DUST("sensor_dust"),
RADIOACTIVITY("sensor_radioactivity"),
TEMPERATURE("sensor_temperature"),
HUMIDITY("sensor_humidity"),
PRESSURE("sensor_pressure"),
CO2("sensor_co2"),
VOC("sensor_voc");
fun units(): String = getUnitsForSensor(legacyKey)
}
/**
* AQI color bands for dust (PM2.5 µg/m³) per EPA scale.
* Returns progress ring color based on value.
*/
fun getColorFromMetrics(value: Float, sensorType: SensorType): Color = when (sensorType) {
SensorType.DUST -> when {
value <= 12f -> SensorGreen
value <= 35.4f -> SensorYellow
value <= 55.4f -> SensorOrange
value <= 150.4f -> SensorRed
value <= 250.4f -> SensorPink
else -> SensorPurple
}
else -> Color.White
}
/**
* Drawable resource ID for the sensor icon.
*/
fun getSensorIconRes(sensorType: SensorType): Int = when (sensorType) {
SensorType.TEMPERATURE -> R.drawable.ic_temperature
SensorType.HUMIDITY -> R.drawable.ic_humidity
SensorType.PRESSURE -> R.drawable.ic_pressure
SensorType.RADIOACTIVITY -> R.drawable.ic_radiation
else -> R.drawable.ic_dust
}

View File

@@ -0,0 +1,97 @@
package org.db3.airmq.features.common.metric
import androidx.compose.animation.core.animateFloatAsState
import androidx.compose.animation.core.tween
import androidx.compose.foundation.Canvas
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.StrokeCap
import androidx.compose.ui.graphics.drawscope.Stroke
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp
import org.db3.airmq.ui.theme.SensorGreen
private const val StartAngle = 135f
private const val FullSweepAngle = 270f
/**
* Circular progress gauge drawn as a 270° arc (7 o'clock to 4 o'clock).
* Mirrors legacy [RingDiagram] behavior.
*
* @param progress Progress 0100, maps to arc sweep
* @param progressColor Color of the progress arc
* @param modifier Modifier
* @param size Total size of the gauge
* @param strokeWidth Stroke width of the arc
* @param backgroundColor Background ring color (default white38)
*/
@Composable
fun RingGauge(
progress: Float,
progressColor: Color,
modifier: Modifier = Modifier,
size: Dp = 77.dp,
strokeWidth: Dp = 5.dp,
backgroundColor: Color = Color.White.copy(alpha = 0.38f)
) {
val animatedProgress by animateFloatAsState(
targetValue = progress.coerceIn(0f, 100f),
animationSpec = tween(durationMillis = 400),
label = "ring_progress"
)
val sweepDegrees = (FullSweepAngle / 100f) * animatedProgress
Canvas(
modifier = modifier.size(size)
) {
val strokePx = strokeWidth.toPx()
val halfStroke = strokePx / 2f
val topLeft = Offset(halfStroke, halfStroke)
val arcSize = androidx.compose.ui.geometry.Size(size.toPx() - strokePx, size.toPx() - strokePx)
// Background ring
drawArc(
color = backgroundColor,
startAngle = StartAngle,
sweepAngle = FullSweepAngle,
useCenter = false,
topLeft = topLeft,
size = arcSize,
style = Stroke(width = strokePx, cap = StrokeCap.Round)
)
// Progress ring
if (sweepDegrees > 0f) {
drawArc(
color = progressColor,
startAngle = StartAngle,
sweepAngle = sweepDegrees,
useCenter = false,
topLeft = topLeft,
size = arcSize,
style = Stroke(width = strokePx, cap = StrokeCap.Round)
)
}
}
}
@Preview(showBackground = true, name = "RingGauge 40% progress")
@Composable
private fun PreviewRingGauge() {
Box(
modifier = Modifier
.size(100.dp)
.padding(12.dp)
) {
RingGauge(
progress = 40f,
progressColor = SensorGreen
)
}
}

View File

@@ -2,10 +2,16 @@ package org.db3.airmq.features.dashboard
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Brush
import androidx.compose.ui.graphics.Color
@@ -13,6 +19,8 @@ import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp
import org.db3.airmq.R
import org.db3.airmq.features.common.MockScreenScaffold
import org.db3.airmq.features.common.metric.MetricGaugeRow
import org.db3.airmq.features.common.metric.SensorType
import org.db3.airmq.features.common.ScreenAction
import org.db3.airmq.features.common.chart.AirMQChart
import org.db3.airmq.features.common.chart.ChartConfig
@@ -36,30 +44,46 @@ fun DashboardScreen(
title = stringResource(id = R.string.title_dashboard),
subtitle = stringResource(id = R.string.dashboard_subtitle),
content = {
Box(
var selectedSensor by remember { mutableStateOf<SensorType?>(SensorType.DUST) }
Column(
modifier = Modifier
.fillMaxWidth()
.height(144.dp)
.background(
Brush.verticalGradient(
colors = listOf(LegacyNavGradientStart, LegacyNavGradientEnd)
)
)
) {
AirMQChart(
data = ChartDataset.Single(generateSineWaveData()),
config = ChartConfig(
lineColor = Color.White,
fillColor = ChartFill,
backgroundColor = ChartBackground,
labelColor = Color.White,
leftTimeLabel = stringResource(R.string.text_yesterday),
rightTimeLabel = stringResource(R.string.text_now),
unit = "°C"
),
sensorType = "sensor_temperature",
modifier = Modifier.fillMaxSize()
)
MetricGaugeRow(
selectedSensor = selectedSensor,
values = mapOf(
SensorType.DUST to 6f,
SensorType.RADIOACTIVITY to 0f,
SensorType.TEMPERATURE to 3f
),
onGaugeSelected = { selectedSensor = it },
modifier = Modifier.padding(vertical = 8.dp)
)
Box(
modifier = Modifier
.fillMaxWidth()
.height(144.dp)
) {
AirMQChart(
data = ChartDataset.Single(generateSineWaveData()),
config = ChartConfig(
lineColor = Color.White,
fillColor = ChartFill,
backgroundColor = ChartBackground,
labelColor = Color.White,
leftTimeLabel = stringResource(R.string.text_yesterday),
rightTimeLabel = stringResource(R.string.text_now),
unit = "°C"
),
sensorType = "sensor_temperature",
modifier = Modifier.fillMaxSize()
)
}
}
},
actions = listOf(

View File

@@ -43,4 +43,13 @@ val SensorPressure = Color(0xFF4FC3F7)
val SensorDust25 = Color(0xFF4A90D9)
val SensorDust10 = Color(0xFF81C784)
val SensorDust1 = Color(0xFF90A4AE)
val SensorRadioactivity = Color(0xFFE57373)
val SensorRadioactivity = Color(0xFFE57373)
// AQI / metric gauge colors (EPA scale for dust)
val SensorGreen = Color(0xFF00FF1E)
val SensorYellow = Color(0xFFFFBF00)
val SensorOrange = Color(0xFFFF6F00)
val SensorRed = Color(0xFFFF0000)
val SensorPink = Color(0xFFFF006A)
val SensorPurple = Color(0xFF6200FF)
val SensorGrey = Color(0xFF858585)

View File

@@ -0,0 +1,15 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:fillColor="#FF000000"
android:pathData="M9.5,12.1c-0.11,-0.13 -0.27,-0.19 -0.46,-0.19c-0.2,0 -0.35,0.06 -0.46,0.19c-0.12,0.12 -0.17,0.29 -0.17,0.52v0.43c0,0.21 0.05,0.37 0.17,0.5c0.11,0.12 0.27,0.19 0.47,0.19c0.41,0 0.62,-0.27 0.62,-0.79V12.6C9.67,12.4 9.61,12.23 9.5,12.1zM9.5,12.1c-0.11,-0.13 -0.27,-0.19 -0.46,-0.19c-0.2,0 -0.35,0.06 -0.46,0.19c-0.12,0.12 -0.17,0.29 -0.17,0.52v0.43c0,0.21 0.05,0.37 0.17,0.5c0.11,0.12 0.27,0.19 0.47,0.19c0.41,0 0.62,-0.27 0.62,-0.79V12.6C9.67,12.4 9.61,12.23 9.5,12.1zM18.71,13.23c-0.43,-1.84 -1.3,-3.46 -2.29,-5.03C14.77,5.59 13,3.06 12.01,0c-0.24,0.61 -0.44,1.15 -0.66,1.68c-0.86,2.03 -2.07,3.86 -3.26,5.71C7.04,9 6.03,10.63 5.48,12.49c-0.55,1.9 -0.6,3.79 0.24,5.63c1.65,3.62 6.61,5.18 10.37,2.59C18.44,19.08 19.4,16.2 18.71,13.23zM7.65,14.33c-0.35,-0.32 -0.52,-0.76 -0.52,-1.31V12.6c0,-0.53 0.17,-0.96 0.52,-1.28c0.34,-0.33 0.81,-0.49 1.39,-0.49c0.59,0 1.05,0.16 1.39,0.48c0.34,0.33 0.51,0.77 0.51,1.32v0.42c0,0.53 -0.17,0.96 -0.51,1.28c-0.34,0.32 -0.8,0.48 -1.38,0.48S8.01,14.65 7.65,14.33zM10.5,18.34L9.31,17.6l4.19,-6.69l1.18,0.74L10.5,18.34zM16.88,16.58c0,0.53 -0.18,0.96 -0.52,1.28c-0.34,0.32 -0.79,0.48 -1.39,0.48c-0.57,0 -1.04,-0.16 -1.39,-0.48c-0.35,-0.32 -0.53,-0.76 -0.53,-1.31v-0.43c0,-0.53 0.18,-0.95 0.52,-1.28c0.35,-0.32 0.81,-0.48 1.39,-0.48c0.58,0 1.05,0.16 1.39,0.48c0.35,0.31 0.53,0.76 0.53,1.32V16.58zM9.04,11.91c-0.2,0 -0.35,0.06 -0.46,0.19c-0.12,0.12 -0.17,0.29 -0.17,0.52v0.43c0,0.21 0.05,0.37 0.17,0.5c0.11,0.12 0.27,0.19 0.47,0.19c0.41,0 0.62,-0.27 0.62,-0.79V12.6c0,-0.2 -0.06,-0.37 -0.17,-0.5S9.23,11.91 9.04,11.91zM9.5,12.1c-0.11,-0.13 -0.27,-0.19 -0.46,-0.19c-0.2,0 -0.35,0.06 -0.46,0.19c-0.12,0.12 -0.17,0.29 -0.17,0.52v0.43c0,0.21 0.05,0.37 0.17,0.5c0.11,0.12 0.27,0.19 0.47,0.19c0.41,0 0.62,-0.27 0.62,-0.79V12.6C9.67,12.4 9.61,12.23 9.5,12.1z"/>
<path
android:fillColor="#FF000000"
android:pathData="M15.6,16.12l-0.01,0.6c-0.02,0.37 -0.24,0.54 -0.62,0.54c-0.04,0 -0.09,-0.01 -0.13,-0.02c-0.01,0 -0.01,0 -0.02,0c-0.12,-0.02 -0.22,-0.08 -0.31,-0.18c-0.12,-0.14 -0.19,-0.3 -0.19,-0.48V16.1c0.02,-0.21 0.08,-0.37 0.18,-0.49c0.09,-0.08 0.2,-0.14 0.32,-0.16c0.05,-0.01 0.09,-0.01 0.14,-0.01c0.2,0 0.36,0.06 0.46,0.18C15.54,15.75 15.6,15.92 15.6,16.12z"/>
<path
android:fillColor="#FF000000"
android:pathData="M9.67,12.6v0.35c0,0.52 -0.21,0.79 -0.62,0.79c-0.2,0 -0.36,-0.07 -0.47,-0.19c-0.12,-0.13 -0.17,-0.29 -0.17,-0.5v-0.43c0,-0.23 0.05,-0.4 0.17,-0.52c0.11,-0.13 0.26,-0.19 0.46,-0.19c0.19,0 0.35,0.06 0.46,0.19S9.67,12.4 9.67,12.6z"/>
</vector>

View File

@@ -0,0 +1,36 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:fillColor="#FF000000"
android:pathData="M4.59,19.15c-0.33,0 -0.65,-0.15 -0.87,-0.43c-1.54,-2.02 -2.29,-4.52 -2.11,-7.05C2.03,5.94 7.01,1.62 12.74,2.03c2.77,0.2 5.3,1.46 7.12,3.56c1.82,2.1 2.71,4.78 2.52,7.56c-0.14,2 -0.85,3.9 -2.05,5.5c-0.36,0.48 -1.04,0.58 -1.52,0.22c-0.48,-0.36 -0.58,-1.04 -0.22,-1.52c0.95,-1.26 1.51,-2.76 1.62,-4.35c0.16,-2.19 -0.55,-4.32 -1.99,-5.98c-1.44,-1.66 -3.44,-2.66 -5.63,-2.82c-4.52,-0.32 -8.47,3.1 -8.8,7.63c-0.14,2 0.45,3.98 1.67,5.58c0.36,0.48 0.27,1.16 -0.2,1.52C5.05,19.07 4.82,19.15 4.59,19.15z"/>
<path
android:fillColor="#FF000000"
android:pathData="M9.63,20.32v1.62H8.68v-4.59h1.79c0.34,0 0.65,0.06 0.91,0.19c0.26,0.13 0.46,0.31 0.6,0.54c0.14,0.23 0.21,0.5 0.21,0.79c0,0.45 -0.15,0.8 -0.46,1.06c-0.31,0.26 -0.73,0.39 -1.28,0.39H9.63zM9.63,19.55h0.85c0.25,0 0.44,-0.06 0.57,-0.18c0.13,-0.12 0.2,-0.29 0.2,-0.5c0,-0.22 -0.07,-0.41 -0.2,-0.55c-0.13,-0.14 -0.32,-0.21 -0.55,-0.21H9.63V19.55z"/>
<path
android:fillColor="#FF000000"
android:pathData="M14.73,21.94c-0.04,-0.08 -0.07,-0.18 -0.09,-0.31C14.42,21.88 14.13,22 13.78,22c-0.33,0 -0.61,-0.1 -0.83,-0.29c-0.22,-0.19 -0.33,-0.44 -0.33,-0.73c0,-0.36 0.13,-0.64 0.4,-0.83c0.27,-0.19 0.66,-0.29 1.16,-0.29h0.42v-0.2c0,-0.16 -0.04,-0.28 -0.12,-0.38c-0.08,-0.09 -0.21,-0.14 -0.38,-0.14c-0.15,0 -0.27,0.04 -0.36,0.11c-0.09,0.07 -0.13,0.17 -0.13,0.3h-0.91c0,-0.2 0.06,-0.38 0.18,-0.55c0.12,-0.17 0.29,-0.3 0.52,-0.4c0.22,-0.1 0.47,-0.14 0.75,-0.14c0.42,0 0.75,0.11 1,0.32c0.25,0.21 0.37,0.51 0.37,0.89v1.48c0,0.32 0.05,0.57 0.14,0.73v0.05H14.73zM13.98,21.3c0.13,0 0.26,-0.03 0.37,-0.09c0.11,-0.06 0.2,-0.14 0.25,-0.24v-0.59h-0.34c-0.46,0 -0.7,0.16 -0.73,0.47l0,0.05c0,0.11 0.04,0.21 0.12,0.28C13.73,21.27 13.84,21.3 13.98,21.3z"/>
<path
android:fillColor="#FF000000"
android:pathData="M16.75,7.72l-2.58,3.37c0.04,0.11 0.08,0.23 0.1,0.35c0.24,1.2 -0.54,2.37 -1.75,2.61c-0.22,0.04 -0.43,0.05 -0.64,0.03l-1.01,1.32c-0.08,0.11 -0.2,0.18 -0.34,0.21c-0.02,0 -0.05,0.01 -0.07,0.01c-0.18,0.01 -0.36,-0.05 -0.5,-0.18l-1.34,-1.28c-0.14,-0.13 -0.21,-0.31 -0.2,-0.49c0.01,-0.17 0.08,-0.32 0.21,-0.42l1.27,-1.06c-0.16,-1.16 0.61,-2.26 1.77,-2.49c0.39,-0.08 0.77,-0.05 1.12,0.07l3.25,-2.72c0.2,-0.16 0.47,-0.15 0.66,0.02C16.88,7.24 16.91,7.52 16.75,7.72z"/>
<path
android:fillColor="#FF000000"
android:pathData="M7.04,16.22m-0.64,0a0.64,0.64 0,1 1,1.28 0a0.64,0.64 0,1 1,-1.28 0"/>
<path
android:fillColor="#FF000000"
android:pathData="M5.75,11.91m-0.64,0a0.64,0.64 0,1 1,1.28 0a0.64,0.64 0,1 1,-1.28 0"/>
<path
android:fillColor="#FF000000"
android:pathData="M7.74,7.83m-0.64,0a0.64,0.64 0,1 1,1.28 0a0.64,0.64 0,1 1,-1.28 0"/>
<path
android:fillColor="#FF000000"
android:pathData="M11.98,6.14m-0.64,0a0.64,0.64 0,1 1,1.28 0a0.64,0.64 0,1 1,-1.28 0"/>
<path
android:fillColor="#FF000000"
android:pathData="M16.95,16.22m-0.64,0a0.64,0.64 0,1 1,1.28 0a0.64,0.64 0,1 1,-1.28 0"/>
<path
android:fillColor="#FF000000"
android:pathData="M18.25,11.91m-0.64,0a0.64,0.64 0,1 1,1.28 0a0.64,0.64 0,1 1,-1.28 0"/>
</vector>

View File

@@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="36dp"
android:height="36dp"
android:viewportWidth="36"
android:viewportHeight="36">
<path
android:pathData="M22.68,8h-3.52V7.83c0,-0.7 -0.28,-1.33 -0.74,-1.79c-0.46,-0.46 -1.09,-0.74 -1.79,-0.74c-1.4,0 -2.53,1.13 -2.53,2.53v15.74c-0.9,0.74 -1.48,1.86 -1.48,3.11c0,2.21 1.8,4.01 4.01,4.01c2.21,0 4.01,-1.79 4.01,-4.01c0,-1.26 -0.58,-2.38 -1.48,-3.11v-1.41h1.96c0.39,0 0.7,-0.31 0.7,-0.7c0,-0.39 -0.31,-0.7 -0.7,-0.7h-1.96v-1.79h1.96c0.39,0 0.7,-0.31 0.7,-0.7c0,-0.39 -0.31,-0.7 -0.7,-0.7h-1.96v-1.79h1.96c0.39,0 0.7,-0.31 0.7,-0.7c0,-0.39 -0.31,-0.7 -0.7,-0.7h-1.96v-1.79h1.96c0.39,0 0.7,-0.31 0.7,-0.7c0,-0.39 -0.31,-0.7 -0.7,-0.7h-1.96V9.4h3.52c0.39,0 0.7,-0.31 0.7,-0.7S23.07,8 22.68,8zM16.63,28.43c-0.96,0 -1.74,-0.78 -1.74,-1.74c0,-0.21 0.04,-0.41 0.11,-0.6c0.16,-0.44 0.5,-0.8 0.93,-0.99V14.9c0,-0.19 0.08,-0.37 0.2,-0.49c0.13,-0.13 0.3,-0.2 0.49,-0.2c0.39,0 0.7,0.31 0.7,0.7v10.2c0.43,0.19 0.77,0.55 0.93,0.99c0.07,0.19 0.11,0.39 0.11,0.6C18.37,27.65 17.59,28.43 16.63,28.43z"
android:fillColor="#FFFFFF"/>
</vector>