1
0
mirror of https://github.com/oliexdev/openScale.git synced 2025-08-20 07:21:40 +02:00

Introduces a new "Chart Settings" screen accessible from the main settings menu.

This commit is contained in:
oliexdev
2025-08-15 15:56:56 +02:00
parent 365b1029c5
commit a3d18a1258
8 changed files with 121 additions and 5 deletions

View File

@@ -57,6 +57,9 @@ object UserPreferenceKeys {
val SAVED_BLUETOOTH_SCALE_ADDRESS = stringPreferencesKey("saved_bluetooth_scale_address") val SAVED_BLUETOOTH_SCALE_ADDRESS = stringPreferencesKey("saved_bluetooth_scale_address")
val SAVED_BLUETOOTH_SCALE_NAME = stringPreferencesKey("saved_bluetooth_scale_name") val SAVED_BLUETOOTH_SCALE_NAME = stringPreferencesKey("saved_bluetooth_scale_name")
// Settings for chart
val SHOW_CHART_DATA_POINTS = booleanPreferencesKey("show_chart_data_points")
// Context strings for screen-specific settings (can be used as prefixes for dynamic keys) // Context strings for screen-specific settings (can be used as prefixes for dynamic keys)
const val OVERVIEW_SCREEN_CONTEXT = "overview_screen" const val OVERVIEW_SCREEN_CONTEXT = "overview_screen"
const val GRAPH_SCREEN_CONTEXT = "graph_screen" const val GRAPH_SCREEN_CONTEXT = "graph_screen"
@@ -90,6 +93,9 @@ interface UserSettingsRepository {
suspend fun saveBluetoothScale(address: String, name: String?) suspend fun saveBluetoothScale(address: String, name: String?)
suspend fun clearSavedBluetoothScale() suspend fun clearSavedBluetoothScale()
val showChartDataPoints: Flow<Boolean>
suspend fun setShowChartDataPoints(show: Boolean)
// Generic Settings Accessors // Generic Settings Accessors
/** /**
* Observes a setting with the given key name and default value. * Observes a setting with the given key name and default value.
@@ -248,6 +254,19 @@ class UserSettingsRepositoryImpl(context: Context) : UserSettingsRepository {
} }
} }
override val showChartDataPoints: Flow<Boolean> = observeSetting(
UserPreferenceKeys.SHOW_CHART_DATA_POINTS.name,
true
).catch { exception ->
LogManager.e(TAG, "Error observing showChartDataPoints", exception)
emit(true)
}
override suspend fun setShowChartDataPoints(show: Boolean) {
LogManager.d(TAG, "Setting showChartDataPoints to: $show")
saveSetting(UserPreferenceKeys.SHOW_CHART_DATA_POINTS.name, show)
}
@Suppress("UNCHECKED_CAST") @Suppress("UNCHECKED_CAST")
override fun <T> observeSetting(keyName: String, defaultValue: T): Flow<T> { override fun <T> observeSetting(keyName: String, defaultValue: T): Flow<T> {
LogManager.v(TAG, "Observing setting: key='$keyName', type='${defaultValue!!::class.simpleName}'") LogManager.v(TAG, "Observing setting: key='$keyName', type='${defaultValue!!::class.simpleName}'")

View File

@@ -121,6 +121,7 @@ import com.health.openscale.ui.screen.overview.MeasurementDetailScreen
import com.health.openscale.ui.screen.overview.OverviewScreen import com.health.openscale.ui.screen.overview.OverviewScreen
import com.health.openscale.ui.screen.settings.AboutScreen import com.health.openscale.ui.screen.settings.AboutScreen
import com.health.openscale.ui.screen.settings.BluetoothScreen import com.health.openscale.ui.screen.settings.BluetoothScreen
import com.health.openscale.ui.screen.settings.ChartSettingsScreen
import com.health.openscale.ui.screen.settings.DataManagementSettingsScreen import com.health.openscale.ui.screen.settings.DataManagementSettingsScreen
import com.health.openscale.ui.screen.settings.GeneralSettingsScreen import com.health.openscale.ui.screen.settings.GeneralSettingsScreen
import com.health.openscale.ui.screen.settings.MeasurementTypeDetailScreen import com.health.openscale.ui.screen.settings.MeasurementTypeDetailScreen
@@ -683,6 +684,13 @@ fun AppNavigation(sharedViewModel: SharedViewModel) {
bluetoothViewModel = bluetoothViewModel bluetoothViewModel = bluetoothViewModel
) )
} }
composable(Routes.CHART_SETTINGS) {
ChartSettingsScreen(
navController = navController,
sharedViewModel = sharedViewModel,
settingsViewModel = settingsViewModel
)
}
composable(Routes.DATA_MANAGEMENT_SETTINGS) { composable(Routes.DATA_MANAGEMENT_SETTINGS) {
DataManagementSettingsScreen( DataManagementSettingsScreen(
navController = navController, navController = navController,

View File

@@ -46,6 +46,7 @@ object Routes {
const val MEASUREMENT_TYPES = "settings/types" const val MEASUREMENT_TYPES = "settings/types"
const val MEASUREMENT_TYPE_DETAIL = "settings/typeDetail" const val MEASUREMENT_TYPE_DETAIL = "settings/typeDetail"
const val BLUETOOTH_SETTINGS = "settings/bluetooth" const val BLUETOOTH_SETTINGS = "settings/bluetooth"
const val CHART_SETTINGS = "settings/chart"
const val DATA_MANAGEMENT_SETTINGS = "settings/dataManagement" const val DATA_MANAGEMENT_SETTINGS = "settings/dataManagement"
const val ABOUT_SETTINGS = "settings/about" const val ABOUT_SETTINGS = "settings/about"

View File

@@ -63,6 +63,7 @@ import com.health.openscale.core.data.MeasurementTypeKey
import com.health.openscale.core.data.TimeRangeFilter import com.health.openscale.core.data.TimeRangeFilter
import com.health.openscale.core.database.UserPreferenceKeys import com.health.openscale.core.database.UserPreferenceKeys
import com.health.openscale.core.database.UserSettingsRepository import com.health.openscale.core.database.UserSettingsRepository
import com.health.openscale.core.database.UserSettingsRepositoryImpl
import com.health.openscale.ui.screen.SharedViewModel import com.health.openscale.ui.screen.SharedViewModel
import com.patrykandpatrick.vico.compose.cartesian.CartesianChartHost import com.patrykandpatrick.vico.compose.cartesian.CartesianChartHost
import com.patrykandpatrick.vico.compose.cartesian.axis.rememberAxisGuidelineComponent import com.patrykandpatrick.vico.compose.cartesian.axis.rememberAxisGuidelineComponent
@@ -142,6 +143,8 @@ fun LineChart(
val scope = rememberCoroutineScope() val scope = rememberCoroutineScope()
val userSettingsRepository = sharedViewModel.userSettingRepository val userSettingsRepository = sharedViewModel.userSettingRepository
val showDataPointsSetting by userSettingsRepository.showChartDataPoints.collectAsState(initial = true)
val uiSelectedTimeRange by rememberContextualTimeRangeFilter( val uiSelectedTimeRange by rememberContextualTimeRangeFilter(
screenContextName = screenContextName, screenContextName = screenContextName,
userSettingsRepository = userSettingsRepository userSettingsRepository = userSettingsRepository
@@ -503,7 +506,11 @@ fun LineChart(
LineCartesianLayer.LineProvider.series( LineCartesianLayer.LineProvider.series(
seriesEntriesForStartAxis.mapIndexedNotNull { index, _ -> seriesEntriesForStartAxis.mapIndexedNotNull { index, _ ->
if (index < typeColorsForStartAxis.size) { if (index < typeColorsForStartAxis.size) {
createLineSpec(typeColorsForStartAxis[index], statisticsMode = targetMeasurementTypeId != null) createLineSpec(
color = typeColorsForStartAxis[index],
statisticsMode = targetMeasurementTypeId != null,
showPoints = showDataPointsSetting
)
} else null } else null
} }
) )
@@ -522,7 +529,11 @@ fun LineChart(
LineCartesianLayer.LineProvider.series( LineCartesianLayer.LineProvider.series(
seriesEntriesForEndAxis.mapIndexedNotNull { index, _ -> seriesEntriesForEndAxis.mapIndexedNotNull { index, _ ->
if (index < typeColorsForEndAxis.size) { if (index < typeColorsForEndAxis.size) {
createLineSpec(typeColorsForEndAxis[index], statisticsMode = targetMeasurementTypeId != null) createLineSpec(
color = typeColorsForEndAxis[index],
statisticsMode = targetMeasurementTypeId != null,
showPoints = showDataPointsSetting
)
} else null } else null
} }
) )
@@ -702,9 +713,10 @@ private fun rememberXAxisValueFormatter(
* @param color The color of the line and points. * @param color The color of the line and points.
* @param statisticsMode If true, an area fill is added below the line, and points are hidden. * @param statisticsMode If true, an area fill is added below the line, and points are hidden.
* This is typically used when `targetMeasurementTypeId` is set. * This is typically used when `targetMeasurementTypeId` is set.
* @param showPoints If true, points are displayed on the line (unless in statisticsMode).
* @return A configured [LineCartesianLayer.Line]. * @return A configured [LineCartesianLayer.Line].
*/ */
private fun createLineSpec(color: Color, statisticsMode : Boolean): LineCartesianLayer.Line { private fun createLineSpec(color: Color, statisticsMode : Boolean, showPoints: Boolean): LineCartesianLayer.Line {
val lineStroke = LineCartesianLayer.LineStroke.Continuous( val lineStroke = LineCartesianLayer.LineStroke.Continuous(
thicknessDp = 2f, thicknessDp = 2f,
) )
@@ -719,10 +731,11 @@ private fun createLineSpec(color: Color, statisticsMode : Boolean): LineCartesia
// Area fill is shown in statistics mode (e.g., when a single type is focused) // Area fill is shown in statistics mode (e.g., when a single type is focused)
areaFill = if (statisticsMode) LineCartesianLayer.AreaFill.single(Fill(color.copy(alpha = 0.2f).toArgb())) else null, areaFill = if (statisticsMode) LineCartesianLayer.AreaFill.single(Fill(color.copy(alpha = 0.2f).toArgb())) else null,
// Points on the line are shown unless in statistics mode // Points on the line are shown unless in statistics mode
pointProvider = if (!statisticsMode) { pointProvider = if (showPoints && !statisticsMode) {
LineCartesianLayer.PointProvider.single( LineCartesianLayer.PointProvider.single(
LineCartesianLayer.point(ShapeComponent(fill(color.copy(alpha = 0.7f)), CorneredShape.Pill), 6.dp) LineCartesianLayer.point(ShapeComponent(fill(color.copy(alpha = 0.7f)), CorneredShape.Pill), 6.dp)
) } else null, )
} else null,
// dataLabel = null, // No data labels on points // dataLabel = null, // No data labels on points
pointConnector = LineCartesianLayer.PointConnector.cubic() pointConnector = LineCartesianLayer.PointConnector.cubic()
) )

View File

@@ -0,0 +1,58 @@
package com.health.openscale.ui.screen.settings
import androidx.activity.result.launch
import androidx.navigation.NavController
import androidx.compose.foundation.layout.*
import androidx.compose.material3.*
import androidx.compose.runtime.*
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp
import com.health.openscale.R
import com.health.openscale.ui.screen.SharedViewModel
import kotlinx.coroutines.launch
@Composable
fun ChartSettingsScreen(
navController: NavController,
sharedViewModel: SharedViewModel,
settingsViewModel: SettingsViewModel
) {
val chartSettingsScreenTitle = stringResource(R.string.settings_item_chart)
LaunchedEffect(Unit) {
sharedViewModel.setTopBarTitle(chartSettingsScreenTitle)
}
val coroutineScope = rememberCoroutineScope()
val showDataPoints by sharedViewModel.userSettingRepository.showChartDataPoints.collectAsState(true)
Column(
modifier = Modifier
.fillMaxSize()
.padding(16.dp)
) {
Row(
modifier = Modifier
.fillMaxWidth()
.padding(vertical = 8.dp),
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.SpaceBetween
) {
Text(
text = stringResource(R.string.setting_show_chart_points),
style = MaterialTheme.typography.bodyLarge
)
Switch(
checked = showDataPoints,
onCheckedChange = { newValue ->
coroutineScope.launch {
sharedViewModel.userSettingRepository.setShowChartDataPoints(newValue)
}
}
)
}
}
}

View File

@@ -23,10 +23,12 @@ import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.padding
import androidx.compose.material.icons.Icons import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.automirrored.filled.ShowChart
import androidx.compose.material.icons.filled.Bluetooth import androidx.compose.material.icons.filled.Bluetooth
import androidx.compose.material.icons.filled.Edit import androidx.compose.material.icons.filled.Edit
import androidx.compose.material.icons.filled.Info import androidx.compose.material.icons.filled.Info
import androidx.compose.material.icons.filled.Person import androidx.compose.material.icons.filled.Person
import androidx.compose.material.icons.filled.ShowChart
import androidx.compose.material.icons.filled.Storage import androidx.compose.material.icons.filled.Storage
import androidx.compose.material.icons.filled.Tune import androidx.compose.material.icons.filled.Tune
import androidx.compose.material3.Card import androidx.compose.material3.Card
@@ -79,6 +81,7 @@ fun SettingsScreen(
val userSettingsLabel = stringResource(R.string.settings_item_user) val userSettingsLabel = stringResource(R.string.settings_item_user)
val measurementTypesLabel = stringResource(R.string.settings_item_measurement_types) val measurementTypesLabel = stringResource(R.string.settings_item_measurement_types)
val bluetoothLabel = stringResource(R.string.settings_item_bluetooth) val bluetoothLabel = stringResource(R.string.settings_item_bluetooth)
val chartSettingsLabel = stringResource(R.string.settings_item_chart)
val dataManagementLabel = stringResource(R.string.settings_item_data_management) val dataManagementLabel = stringResource(R.string.settings_item_data_management)
val aboutLabel = stringResource(R.string.settings_item_about) val aboutLabel = stringResource(R.string.settings_item_about)
@@ -107,6 +110,12 @@ fun SettingsScreen(
route = Routes.BLUETOOTH_SETTINGS, route = Routes.BLUETOOTH_SETTINGS,
contentDescription = bluetoothLabel contentDescription = bluetoothLabel
), ),
SettingsItem(
label = chartSettingsLabel,
icon = Icons.AutoMirrored.Filled.ShowChart,
route = Routes.CHART_SETTINGS,
contentDescription = chartSettingsLabel
),
SettingsItem( SettingsItem(
label = dataManagementLabel, label = dataManagementLabel,
icon = Icons.Filled.Storage, icon = Icons.Filled.Storage,

View File

@@ -281,6 +281,7 @@
<string name="settings_item_user">Benutzer</string> <string name="settings_item_user">Benutzer</string>
<string name="settings_item_measurement_types">Messarten</string> <string name="settings_item_measurement_types">Messarten</string>
<string name="settings_item_bluetooth">Bluetooth</string> <string name="settings_item_bluetooth">Bluetooth</string>
<string name="settings_item_chart">Diagramm</string>
<string name="settings_item_data_management">Datenverwaltung</string> <string name="settings_item_data_management">Datenverwaltung</string>
<string name="settings_item_about">Über</string> <string name="settings_item_about">Über</string>
@@ -288,6 +289,9 @@
<string name="settings_general_title">Allgemeine Einstellungen</string> <string name="settings_general_title">Allgemeine Einstellungen</string>
<string name="settings_language_label">Sprache</string> <string name="settings_language_label">Sprache</string>
<!-- Diagramm Einstellungen -->
<string name="setting_show_chart_points">Datenpunkte anzeigen</string>
<!-- Über-Bildschirm & Diagnose --> <!-- Über-Bildschirm & Diagnose -->
<string name="version_info">Version: %1$s (%2$s)</string> <string name="version_info">Version: %1$s (%2$s)</string>
<string name="project_information_title">Projektinformationen</string> <string name="project_information_title">Projektinformationen</string>

View File

@@ -283,6 +283,7 @@
<string name="settings_item_user">User</string> <string name="settings_item_user">User</string>
<string name="settings_item_measurement_types">Measurement Types</string> <string name="settings_item_measurement_types">Measurement Types</string>
<string name="settings_item_bluetooth">Bluetooth</string> <string name="settings_item_bluetooth">Bluetooth</string>
<string name="settings_item_chart">Chart</string>
<string name="settings_item_data_management">Data Management</string> <string name="settings_item_data_management">Data Management</string>
<string name="settings_item_about">About</string> <string name="settings_item_about">About</string>
@@ -290,6 +291,9 @@
<string name="settings_general_title">General Settings</string> <string name="settings_general_title">General Settings</string>
<string name="settings_language_label">Language</string> <string name="settings_language_label">Language</string>
<!-- Chart Settings Screen -->
<string name="setting_show_chart_points">Show data points</string>
<!-- About Screen & Diagnostics --> <!-- About Screen & Diagnostics -->
<string name="version_info">Version: %1$s (%2$s)</string> <string name="version_info">Version: %1$s (%2$s)</string>
<string name="project_information_title">Project Information</string> <string name="project_information_title">Project Information</string>