mirror of
https://github.com/oliexdev/openScale.git
synced 2025-08-31 03:59:56 +02:00
Add setting to show measurement type on right Y-axis
This commit is contained in:
@@ -2,7 +2,7 @@
|
||||
"formatVersion": 1,
|
||||
"database": {
|
||||
"version": 7,
|
||||
"identityHash": "d539026586245a45a135ae5bf3aaf73c",
|
||||
"identityHash": "877ad250be34067d136497a388177415",
|
||||
"entities": [
|
||||
{
|
||||
"tableName": "User",
|
||||
@@ -214,7 +214,7 @@
|
||||
},
|
||||
{
|
||||
"tableName": "MeasurementType",
|
||||
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `key` TEXT NOT NULL, `name` TEXT, `color` INTEGER NOT NULL, `icon` TEXT NOT NULL, `unit` TEXT NOT NULL, `inputType` TEXT NOT NULL, `displayOrder` INTEGER NOT NULL, `isDerived` INTEGER NOT NULL, `isEnabled` INTEGER NOT NULL, `isPinned` INTEGER NOT NULL)",
|
||||
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `key` TEXT NOT NULL, `name` TEXT, `color` INTEGER NOT NULL, `icon` TEXT NOT NULL, `unit` TEXT NOT NULL, `inputType` TEXT NOT NULL, `displayOrder` INTEGER NOT NULL, `isDerived` INTEGER NOT NULL, `isEnabled` INTEGER NOT NULL, `isPinned` INTEGER NOT NULL, `isOnRightYAxis` INTEGER NOT NULL)",
|
||||
"fields": [
|
||||
{
|
||||
"fieldPath": "id",
|
||||
@@ -280,6 +280,12 @@
|
||||
"columnName": "isPinned",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "isOnRightYAxis",
|
||||
"columnName": "isOnRightYAxis",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
}
|
||||
],
|
||||
"primaryKey": {
|
||||
@@ -292,7 +298,7 @@
|
||||
],
|
||||
"setupQueries": [
|
||||
"CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)",
|
||||
"INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, 'd539026586245a45a135ae5bf3aaf73c')"
|
||||
"INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, '877ad250be34067d136497a388177415')"
|
||||
]
|
||||
}
|
||||
}
|
@@ -59,7 +59,7 @@ import kotlinx.coroutines.launch
|
||||
*/
|
||||
fun getDefaultMeasurementTypes(): List<MeasurementType> {
|
||||
return listOf(
|
||||
MeasurementType(key = MeasurementTypeKey.WEIGHT, unit = UnitType.KG, color = 0xFFEF2929.toInt(), icon = "ic_weight", isPinned = true, isEnabled = true),
|
||||
MeasurementType(key = MeasurementTypeKey.WEIGHT, unit = UnitType.KG, color = 0xFFEF2929.toInt(), icon = "ic_weight", isPinned = true, isEnabled = true, isOnRightYAxis = true),
|
||||
MeasurementType(key = MeasurementTypeKey.BMI, color = 0xFFF57900.toInt(), icon = "ic_bmi", isDerived = true, isPinned = true, isEnabled = true),
|
||||
MeasurementType(key = MeasurementTypeKey.BODY_FAT, color = 0xFFFFCE44.toInt(), icon = "ic_fat", isPinned = true, isEnabled = true),
|
||||
MeasurementType(key = MeasurementTypeKey.WATER, color = 0xFF8AE234.toInt(), icon = "ic_water", isPinned = true, isEnabled = true),
|
||||
|
@@ -34,7 +34,8 @@ data class MeasurementType(
|
||||
val displayOrder: Int = 0,
|
||||
val isDerived: Boolean = false,
|
||||
val isEnabled : Boolean = true,
|
||||
val isPinned : Boolean = false
|
||||
val isPinned : Boolean = false,
|
||||
val isOnRightYAxis : Boolean = false
|
||||
){
|
||||
/**
|
||||
* Gets the appropriate display name for UI purposes.
|
||||
|
@@ -18,6 +18,7 @@
|
||||
package com.health.openscale.ui.screen.components
|
||||
|
||||
import android.text.Layout
|
||||
import android.util.Log
|
||||
import androidx.compose.animation.AnimatedVisibility
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Box
|
||||
@@ -66,6 +67,7 @@ import com.health.openscale.ui.screen.SharedViewModel
|
||||
import com.patrykandpatrick.vico.compose.cartesian.CartesianChartHost
|
||||
import com.patrykandpatrick.vico.compose.cartesian.axis.rememberAxisGuidelineComponent
|
||||
import com.patrykandpatrick.vico.compose.cartesian.axis.rememberBottom
|
||||
import com.patrykandpatrick.vico.compose.cartesian.axis.rememberEnd
|
||||
import com.patrykandpatrick.vico.compose.cartesian.axis.rememberStart
|
||||
import com.patrykandpatrick.vico.compose.cartesian.layer.point
|
||||
import com.patrykandpatrick.vico.compose.cartesian.layer.rememberLineCartesianLayer
|
||||
@@ -80,9 +82,12 @@ import com.patrykandpatrick.vico.compose.common.fill
|
||||
import com.patrykandpatrick.vico.compose.common.insets
|
||||
import com.patrykandpatrick.vico.compose.common.shape.markerCorneredShape
|
||||
import com.patrykandpatrick.vico.core.cartesian.Zoom
|
||||
import com.patrykandpatrick.vico.core.cartesian.axis.Axis
|
||||
import com.patrykandpatrick.vico.core.cartesian.axis.HorizontalAxis
|
||||
import com.patrykandpatrick.vico.core.cartesian.axis.VerticalAxis
|
||||
import com.patrykandpatrick.vico.core.cartesian.axis.VerticalAxis.ItemPlacer.Companion.count
|
||||
import com.patrykandpatrick.vico.core.cartesian.data.CartesianChartModelProducer
|
||||
import com.patrykandpatrick.vico.core.cartesian.data.CartesianLayerRangeProvider
|
||||
import com.patrykandpatrick.vico.core.cartesian.data.CartesianValueFormatter
|
||||
import com.patrykandpatrick.vico.core.cartesian.data.lineSeries
|
||||
import com.patrykandpatrick.vico.core.cartesian.layer.LineCartesianLayer
|
||||
@@ -99,6 +104,8 @@ import java.time.Instant
|
||||
import java.time.LocalDate
|
||||
import java.time.ZoneId
|
||||
import java.time.format.DateTimeFormatter
|
||||
import kotlin.math.ceil
|
||||
import kotlin.math.floor
|
||||
|
||||
internal val DATE_FORMATTER: DateTimeFormatter = DateTimeFormatter.ofPattern("d MMM")
|
||||
internal val X_TO_DATE_MAP_KEY = ExtraStore.Key<Map<Float, LocalDate>>() // Key for storing date mapping in chart model
|
||||
@@ -390,34 +397,62 @@ fun LineChart(
|
||||
return@Column // Exits the Column Composable early
|
||||
}
|
||||
|
||||
// Determine colors for each series, using type's predefined color or gray as fallback.
|
||||
val typeColors = remember(seriesEntries) {
|
||||
seriesEntries.map { (type, _) -> // Index not used here
|
||||
val seriesEntriesForStartAxis = remember(seriesEntries) {
|
||||
seriesEntries.filter { (type, _) ->
|
||||
!type.isOnRightYAxis
|
||||
}
|
||||
}
|
||||
val typeColorsForStartAxis = remember(seriesEntriesForStartAxis) {
|
||||
seriesEntriesForStartAxis.map { (type, _) ->
|
||||
if (type.color != 0) Color(type.color) else Color.Gray
|
||||
}
|
||||
}
|
||||
|
||||
val seriesEntriesForEndAxis = remember(seriesEntries) {
|
||||
seriesEntries.filter { (type, _) ->
|
||||
type.isOnRightYAxis
|
||||
}
|
||||
}
|
||||
val typeColorsForEndAxis = remember(seriesEntriesForEndAxis) {
|
||||
seriesEntriesForEndAxis.map { (type, _) ->
|
||||
if (type.color != 0) Color(type.color) else Color.Gray
|
||||
}
|
||||
}
|
||||
|
||||
val modelProducer = remember { CartesianChartModelProducer() }
|
||||
|
||||
// Update the chart model when series data or the date map changes.
|
||||
LaunchedEffect(seriesEntries, xToDatesMapForStore) {
|
||||
if (seriesEntries.isNotEmpty()) {
|
||||
LaunchedEffect(seriesEntriesForStartAxis, seriesEntriesForEndAxis, xToDatesMapForStore) {
|
||||
if (seriesEntriesForStartAxis.isNotEmpty() || seriesEntriesForEndAxis.isNotEmpty()) {
|
||||
modelProducer.runTransaction {
|
||||
lineSeries { // Vico's DSL for defining line series
|
||||
seriesEntries.forEach { (_, sortedDateValuePairs) ->
|
||||
val xValues = sortedDateValuePairs.map { it.first.toEpochDay().toFloat() }
|
||||
val yValues = sortedDateValuePairs.map { it.second }
|
||||
if (xValues.isNotEmpty()) {
|
||||
series(x = xValues, y = yValues)
|
||||
if (seriesEntriesForStartAxis.isNotEmpty()) {
|
||||
lineSeries {
|
||||
seriesEntriesForStartAxis.forEach { (_, sortedDateValuePairs) ->
|
||||
val xValues = sortedDateValuePairs.map { it.first.toEpochDay().toFloat() }
|
||||
val yValues = sortedDateValuePairs.map { it.second }
|
||||
if (xValues.isNotEmpty()) {
|
||||
series(x = xValues, y = yValues)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
extras { it[X_TO_DATE_MAP_KEY] = xToDatesMapForStore } // Store date map in model extras
|
||||
|
||||
if (seriesEntriesForEndAxis.isNotEmpty()) {
|
||||
lineSeries {
|
||||
seriesEntriesForEndAxis.forEach { (_, sortedDateValuePairs) ->
|
||||
val xValues = sortedDateValuePairs.map { it.first.toEpochDay().toFloat() }
|
||||
val yValues = sortedDateValuePairs.map { it.second }
|
||||
if (xValues.isNotEmpty()) {
|
||||
series(x = xValues, y = yValues)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
extras { it[X_TO_DATE_MAP_KEY] = xToDatesMapForStore }
|
||||
}
|
||||
} else {
|
||||
// Clear the model if there are no series
|
||||
modelProducer.runTransaction {
|
||||
lineSeries {} // Empty series
|
||||
lineSeries {}
|
||||
lineSeries {}
|
||||
extras { it.remove(X_TO_DATE_MAP_KEY) }
|
||||
}
|
||||
}
|
||||
@@ -442,31 +477,75 @@ fun LineChart(
|
||||
null // Hide X-axis when showing a single, targeted measurement type
|
||||
}
|
||||
|
||||
val rangeProvider = remember {
|
||||
object : CartesianLayerRangeProvider {
|
||||
override fun getMinY(minY: Double, maxY: Double, extraStore: ExtraStore): Double {
|
||||
val r = maxY - minY
|
||||
return if (r == 0.0) minY - 1.0 else floor(minY - 0.1 * r)
|
||||
}
|
||||
override fun getMaxY(minY: Double, maxY: Double, extraStore: ExtraStore): Double {
|
||||
val r = maxY - minY
|
||||
return if (r == 0.0) maxY + 1.0 else ceil(maxY + 0.1 * r)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Conditionally create Y-axis.
|
||||
val yAxis = if (showYAxis) {
|
||||
VerticalAxis.rememberStart(
|
||||
valueFormatter = yAxisValueFormatter,
|
||||
// guideline = rememberAxisGuidelineComponent(), // Optionally add Y-axis guidelines
|
||||
val startYAxis = if (showYAxis) {
|
||||
VerticalAxis.rememberStart(valueFormatter = yAxisValueFormatter)
|
||||
} else { null }
|
||||
|
||||
val endYAxis = if (showYAxis) {
|
||||
VerticalAxis.rememberEnd(valueFormatter = yAxisValueFormatter)
|
||||
} else { null }
|
||||
|
||||
val lineProviderForStartAxis = remember(seriesEntriesForStartAxis, typeColorsForStartAxis) {
|
||||
LineCartesianLayer.LineProvider.series(
|
||||
seriesEntriesForStartAxis.mapIndexedNotNull { index, _ ->
|
||||
if (index < typeColorsForStartAxis.size) {
|
||||
createLineSpec(typeColorsForStartAxis[index], statisticsMode = targetMeasurementTypeId != null)
|
||||
} else null
|
||||
}
|
||||
)
|
||||
}
|
||||
val lineLayerForStartAxis = if (seriesEntriesForStartAxis.isNotEmpty()) {
|
||||
rememberLineCartesianLayer(
|
||||
lineProvider = lineProviderForStartAxis,
|
||||
verticalAxisPosition = Axis.Position.Vertical.Start,
|
||||
rangeProvider = rangeProvider
|
||||
)
|
||||
} else {
|
||||
null
|
||||
}
|
||||
|
||||
// Define how lines are drawn (color, thickness, etc.)
|
||||
val lineProvider = remember(seriesEntries, typeColors) {
|
||||
val lineProviderForEndAxis = remember(seriesEntriesForEndAxis, typeColorsForEndAxis) {
|
||||
LineCartesianLayer.LineProvider.series(
|
||||
seriesEntries.mapIndexedNotNull { index, _ ->
|
||||
if (index < typeColors.size) createLineSpec(typeColors[index], statisticsMode = targetMeasurementTypeId != null) else null
|
||||
seriesEntriesForEndAxis.mapIndexedNotNull { index, _ ->
|
||||
if (index < typeColorsForEndAxis.size) {
|
||||
createLineSpec(typeColorsForEndAxis[index], statisticsMode = targetMeasurementTypeId != null)
|
||||
} else null
|
||||
}
|
||||
)
|
||||
}
|
||||
val lineLayerForEndAxis = if (seriesEntriesForEndAxis.isNotEmpty()) {
|
||||
rememberLineCartesianLayer(
|
||||
lineProvider = lineProviderForEndAxis,
|
||||
verticalAxisPosition = Axis.Position.Vertical.End,
|
||||
rangeProvider = rangeProvider
|
||||
)
|
||||
} else {
|
||||
null
|
||||
}
|
||||
|
||||
val lineLayer = rememberLineCartesianLayer(lineProvider = lineProvider)
|
||||
val layers : List<LineCartesianLayer> = remember(lineLayerForStartAxis, lineLayerForEndAxis) {
|
||||
listOfNotNull(lineLayerForStartAxis, lineLayerForEndAxis)
|
||||
}
|
||||
|
||||
val chart = rememberCartesianChart(
|
||||
lineLayer,
|
||||
startAxis = yAxis, // Y-axis
|
||||
layers = layers.toTypedArray(),
|
||||
startAxis = startYAxis, // left Y-axis
|
||||
bottomAxis = xAxis, // X-axis
|
||||
endAxis = endYAxis, // right Y-axis
|
||||
marker = rememberMarker() // Interactive marker for data points
|
||||
)
|
||||
|
||||
|
@@ -110,6 +110,7 @@ fun MeasurementTypeDetailScreen(
|
||||
var selectedIcon by remember { mutableStateOf(existingType?.icon ?: "ic_weight") } // Default icon
|
||||
var isEnabled by remember { mutableStateOf(existingType?.isEnabled ?: true) } // Default to true for new types
|
||||
var isPinned by remember { mutableStateOf(existingType?.isPinned ?: false) } // Default to false for new types
|
||||
var isOnRightYAxis by remember { mutableStateOf(existingType?.isOnRightYAxis ?: false) }
|
||||
|
||||
var expandedUnit by remember { mutableStateOf(false) }
|
||||
var expandedInputType by remember { mutableStateOf(false) }
|
||||
@@ -138,7 +139,8 @@ fun MeasurementTypeDetailScreen(
|
||||
isEnabled = isEnabled,
|
||||
isPinned = isPinned,
|
||||
key = existingType?.key ?: MeasurementTypeKey.CUSTOM, // New types are custom
|
||||
isDerived = existingType?.isDerived ?: false // New types are not derived by default
|
||||
isDerived = existingType?.isDerived ?: false, // New types are not derived by default
|
||||
isOnRightYAxis = isOnRightYAxis
|
||||
)
|
||||
|
||||
if (isEdit) {
|
||||
@@ -311,6 +313,13 @@ fun MeasurementTypeDetailScreen(
|
||||
onCheckedChange = { isPinned = it }
|
||||
)
|
||||
}
|
||||
|
||||
OutlinedSettingRow(label = stringResource(R.string.measurement_type_label_on_right_y_axis)) {
|
||||
Switch(
|
||||
checked = isOnRightYAxis,
|
||||
onCheckedChange = { isOnRightYAxis = it }
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// Color Picker Dialog
|
||||
|
@@ -141,6 +141,7 @@
|
||||
<string name="measurement_type_label_unit">Einheit</string>
|
||||
<string name="measurement_type_label_input_type">Eingabetyp</string>
|
||||
<string name="measurement_type_label_pinned">Angeheftet</string>
|
||||
<string name="measurement_type_label_on_right_y_axis">Auf rechter Y-Achse</string>
|
||||
<string name="content_desc_selected_icon_preview">Vorschau des ausgewählten Symbols</string>
|
||||
<string name="content_desc_drag_handle_sort">Sortieren</string>
|
||||
<string name="content_desc_edit_type">Bearbeiten</string>
|
||||
|
@@ -142,6 +142,7 @@
|
||||
<string name="measurement_type_label_unit">Unit</string>
|
||||
<string name="measurement_type_label_input_type">Input Type</string>
|
||||
<string name="measurement_type_label_pinned">Pinned</string>
|
||||
<string name="measurement_type_label_on_right_y_axis">On right Y-axis</string>
|
||||
<string name="content_desc_selected_icon_preview">Selected icon preview</string>
|
||||
<string name="content_desc_drag_handle_sort">Sort</string>
|
||||
<string name="content_desc_edit_type">Edit</string>
|
||||
|
Reference in New Issue
Block a user