feat(stats): add tooltips in HorizontalStackedBar
This commit is contained in:
@@ -18,6 +18,7 @@
|
|||||||
package org.nsh07.pomodoro.ui.statsScreen.components
|
package org.nsh07.pomodoro.ui.statsScreen.components
|
||||||
|
|
||||||
import androidx.compose.foundation.background
|
import androidx.compose.foundation.background
|
||||||
|
import androidx.compose.foundation.clickable
|
||||||
import androidx.compose.foundation.layout.Arrangement
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
import androidx.compose.foundation.layout.Box
|
import androidx.compose.foundation.layout.Box
|
||||||
import androidx.compose.foundation.layout.Column
|
import androidx.compose.foundation.layout.Column
|
||||||
@@ -33,23 +34,34 @@ import androidx.compose.foundation.lazy.grid.LazyHorizontalGrid
|
|||||||
import androidx.compose.foundation.lazy.grid.itemsIndexed
|
import androidx.compose.foundation.lazy.grid.itemsIndexed
|
||||||
import androidx.compose.foundation.shape.CornerSize
|
import androidx.compose.foundation.shape.CornerSize
|
||||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||||
|
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||||
import androidx.compose.material3.MaterialTheme.colorScheme
|
import androidx.compose.material3.MaterialTheme.colorScheme
|
||||||
import androidx.compose.material3.MaterialTheme.shapes
|
import androidx.compose.material3.MaterialTheme.shapes
|
||||||
import androidx.compose.material3.MaterialTheme.typography
|
import androidx.compose.material3.MaterialTheme.typography
|
||||||
import androidx.compose.material3.Surface
|
import androidx.compose.material3.Surface
|
||||||
import androidx.compose.material3.Text
|
import androidx.compose.material3.Text
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.getValue
|
||||||
|
import androidx.compose.runtime.mutableStateOf
|
||||||
import androidx.compose.runtime.remember
|
import androidx.compose.runtime.remember
|
||||||
|
import androidx.compose.runtime.setValue
|
||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.draw.clip
|
import androidx.compose.ui.draw.clip
|
||||||
|
import androidx.compose.ui.platform.LocalDensity
|
||||||
|
import androidx.compose.ui.res.stringResource
|
||||||
import androidx.compose.ui.tooling.preview.Preview
|
import androidx.compose.ui.tooling.preview.Preview
|
||||||
import androidx.compose.ui.unit.Dp
|
import androidx.compose.ui.unit.Dp
|
||||||
|
import androidx.compose.ui.unit.IntOffset
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import androidx.compose.ui.util.fastForEach
|
import androidx.compose.ui.util.fastForEach
|
||||||
import androidx.compose.ui.util.fastForEachIndexed
|
import androidx.compose.ui.util.fastForEachIndexed
|
||||||
import androidx.compose.ui.util.fastMaxBy
|
import androidx.compose.ui.util.fastMaxBy
|
||||||
|
import androidx.compose.ui.window.Popup
|
||||||
|
import org.nsh07.pomodoro.R
|
||||||
import org.nsh07.pomodoro.ui.theme.TomatoTheme
|
import org.nsh07.pomodoro.ui.theme.TomatoTheme
|
||||||
|
import org.nsh07.pomodoro.utils.millisecondsToHoursMinutes
|
||||||
|
import org.nsh07.pomodoro.utils.millisecondsToMinutes
|
||||||
import java.time.LocalDate
|
import java.time.LocalDate
|
||||||
import java.time.format.TextStyle
|
import java.time.format.TextStyle
|
||||||
import java.util.Locale
|
import java.util.Locale
|
||||||
@@ -67,6 +79,7 @@ import kotlin.math.roundToInt
|
|||||||
* @param height Height of the bar
|
* @param height Height of the bar
|
||||||
* @param gap Gap between each part of the bar
|
* @param gap Gap between each part of the bar
|
||||||
*/
|
*/
|
||||||
|
@OptIn(ExperimentalMaterial3Api::class)
|
||||||
@Composable
|
@Composable
|
||||||
fun HorizontalStackedBar(
|
fun HorizontalStackedBar(
|
||||||
values: List<Long>,
|
values: List<Long>,
|
||||||
@@ -81,12 +94,41 @@ fun HorizontalStackedBar(
|
|||||||
|
|
||||||
ranks
|
ranks
|
||||||
},
|
},
|
||||||
|
labelFormatter: @Composable (Int, Long, Long) -> String = { index, value, total ->
|
||||||
|
buildString {
|
||||||
|
append(
|
||||||
|
when (index) {
|
||||||
|
0 -> "[00:00 - 06:00] "
|
||||||
|
1 -> "[06:00 - 12:00] "
|
||||||
|
2 -> "[12:00 - 18:00] "
|
||||||
|
else -> "[18:00 - 24:00] "
|
||||||
|
}
|
||||||
|
)
|
||||||
|
if (value < 60 * 60 * 1000)
|
||||||
|
append(
|
||||||
|
millisecondsToMinutes(
|
||||||
|
value,
|
||||||
|
stringResource(R.string.minutes_format)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
else
|
||||||
|
append(
|
||||||
|
millisecondsToHoursMinutes(
|
||||||
|
value,
|
||||||
|
stringResource(R.string.hours_and_minutes_format)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
append(" (%.2f".format((value.toFloat() / total) * 100) + "%)")
|
||||||
|
}
|
||||||
|
},
|
||||||
height: Dp = 40.dp,
|
height: Dp = 40.dp,
|
||||||
gap: Dp = 2.dp
|
gap: Dp = 2.dp
|
||||||
) {
|
) {
|
||||||
val firstNonZeroIndex = remember(values) { values.indexOfFirst { it > 0L } }
|
val firstNonZeroIndex = remember(values) { values.indexOfFirst { it > 0L } }
|
||||||
val lastNonZeroIndex = remember(values) { values.indexOfLast { it > 0L } }
|
val lastNonZeroIndex = remember(values) { values.indexOfLast { it > 0L } }
|
||||||
|
|
||||||
|
val tooltipOffset = with(LocalDensity.current) { (24 + 4).dp.toPx().roundToInt() }
|
||||||
|
|
||||||
if (firstNonZeroIndex != -1)
|
if (firstNonZeroIndex != -1)
|
||||||
Row(
|
Row(
|
||||||
horizontalArrangement = Arrangement.spacedBy(gap),
|
horizontalArrangement = Arrangement.spacedBy(gap),
|
||||||
@@ -94,6 +136,7 @@ fun HorizontalStackedBar(
|
|||||||
) {
|
) {
|
||||||
values.fastForEachIndexed { index, item ->
|
values.fastForEachIndexed { index, item ->
|
||||||
if (item > 0L) {
|
if (item > 0L) {
|
||||||
|
var showTooltip by remember { mutableStateOf(false) }
|
||||||
val shape =
|
val shape =
|
||||||
if (firstNonZeroIndex == lastNonZeroIndex) shapes.large
|
if (firstNonZeroIndex == lastNonZeroIndex) shapes.large
|
||||||
else when (index) {
|
else when (index) {
|
||||||
@@ -109,7 +152,7 @@ fun HorizontalStackedBar(
|
|||||||
|
|
||||||
else -> shapes.extraSmall
|
else -> shapes.extraSmall
|
||||||
}
|
}
|
||||||
Spacer(
|
Box(
|
||||||
Modifier
|
Modifier
|
||||||
.weight(item.toFloat())
|
.weight(item.toFloat())
|
||||||
.height(height)
|
.height(height)
|
||||||
@@ -122,7 +165,31 @@ fun HorizontalStackedBar(
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
)
|
.clickable { showTooltip = true }
|
||||||
|
) {
|
||||||
|
if (showTooltip) {
|
||||||
|
Popup(
|
||||||
|
alignment = Alignment.TopCenter,
|
||||||
|
offset = IntOffset(0, -tooltipOffset),
|
||||||
|
onDismissRequest = {
|
||||||
|
showTooltip = false
|
||||||
|
}
|
||||||
|
) {
|
||||||
|
Text(
|
||||||
|
text = labelFormatter(index, item, values.sum()),
|
||||||
|
style = typography.bodySmall,
|
||||||
|
color = colorScheme.inverseOnSurface,
|
||||||
|
modifier = Modifier
|
||||||
|
.padding(horizontal = 8.dp)
|
||||||
|
.background(
|
||||||
|
color = colorScheme.inverseSurface,
|
||||||
|
shape = shapes.extraSmall
|
||||||
|
)
|
||||||
|
.padding(horizontal = 8.dp, vertical = 4.dp)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user