mirror of
https://github.com/oliexdev/openScale.git
synced 2025-08-24 17:23:03 +02:00
When calculating derived values, the code now fetches the unit from the MeasurementType
and converts the input values (e.g., weight to KG, waist to CM) before passing them to the calculation functions. This ensures calculations are performed with consistent units.
This commit is contained in:
@@ -19,13 +19,17 @@ package com.health.openscale.core.database
|
||||
|
||||
import com.health.openscale.core.data.ActivityLevel
|
||||
import com.health.openscale.core.data.GenderType
|
||||
import com.health.openscale.core.data.MeasureUnit
|
||||
import com.health.openscale.core.data.Measurement
|
||||
import com.health.openscale.core.data.MeasurementType
|
||||
import com.health.openscale.core.data.MeasurementTypeKey
|
||||
import com.health.openscale.core.data.MeasurementValue
|
||||
import com.health.openscale.core.data.UnitType
|
||||
import com.health.openscale.core.data.User
|
||||
import com.health.openscale.core.data.WeightUnit
|
||||
import com.health.openscale.core.model.MeasurementWithValues
|
||||
import com.health.openscale.core.utils.CalculationUtil
|
||||
import com.health.openscale.core.utils.Converters
|
||||
import com.health.openscale.core.utils.LogManager
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
@@ -220,8 +224,9 @@ class DatabaseRepository(
|
||||
}
|
||||
val userId = measurement.userId
|
||||
|
||||
// Fetch all current values for this specific measurement and all global MeasurementType definitions
|
||||
val currentMeasurementValues = measurementValueDao.getValuesForMeasurement(measurementId).first()
|
||||
val allGlobalTypes = measurementTypeDao.getAll().first()
|
||||
val allGlobalTypes = measurementTypeDao.getAll().first() // These are MeasurementType objects, containing unit info
|
||||
val user = userDao.getById(userId).first() ?: run {
|
||||
LogManager.w(DERIVED_VALUES_TAG, "User with ID $userId not found for measurement $measurementId. Cannot recalculate derived values.")
|
||||
return
|
||||
@@ -230,16 +235,22 @@ class DatabaseRepository(
|
||||
LogManager.d(DERIVED_VALUES_TAG, "Fetched ${currentMeasurementValues.size} current values, " +
|
||||
"${allGlobalTypes.size} global types, and user '${user.name}' for measurement $measurementId.")
|
||||
|
||||
val findValue = { key: MeasurementTypeKey ->
|
||||
val type = allGlobalTypes.find { it.key == key }
|
||||
if (type == null) {
|
||||
// Helper to find a raw value and its unit from the persisted MeasurementValues and MeasurementTypes
|
||||
val findValueAndUnit = { key: MeasurementTypeKey ->
|
||||
val measurementTypeObject = allGlobalTypes.find { it.key == key }
|
||||
if (measurementTypeObject == null) {
|
||||
LogManager.w(DERIVED_VALUES_TAG, "MeasurementType for key '$key' not found in global types list.")
|
||||
Pair(null, null) // Return nulls if the type definition is missing
|
||||
} else {
|
||||
val valueObject = currentMeasurementValues.find { it.typeId == measurementTypeObject.id }
|
||||
val value = valueObject?.floatValue
|
||||
val unit = measurementTypeObject.unit // The unit is defined in the MeasurementType object
|
||||
LogManager.v(DERIVED_VALUES_TAG, "findValueAndUnit for $key (typeId: ${measurementTypeObject.id}, unit: $unit): ${value ?: "not found"}")
|
||||
Pair(value, unit)
|
||||
}
|
||||
val value = currentMeasurementValues.find { it.typeId == type?.id }?.floatValue
|
||||
LogManager.v(DERIVED_VALUES_TAG, "findValue for $key (typeId: ${type?.id}): ${value ?: "not found"}")
|
||||
value
|
||||
}
|
||||
|
||||
// Helper to save or update a derived measurement value
|
||||
val saveOrUpdateDerivedValue: suspend (value: Float?, typeKey: MeasurementTypeKey) -> Unit =
|
||||
save@{ derivedValue, derivedValueTypeKey ->
|
||||
val derivedTypeObject = allGlobalTypes.find { it.key == derivedValueTypeKey }
|
||||
@@ -252,6 +263,7 @@ class DatabaseRepository(
|
||||
val existingDerivedValueObject = currentMeasurementValues.find { it.typeId == derivedTypeObject.id }
|
||||
|
||||
if (derivedValue == null) {
|
||||
// If derived value is null, delete any existing persisted value for it
|
||||
if (existingDerivedValueObject != null) {
|
||||
measurementValueDao.deleteById(existingDerivedValueObject.id)
|
||||
LogManager.d(DERIVED_VALUES_TAG, "Derived value for key ${derivedTypeObject.key} is null. Deleted existing value (ID: ${existingDerivedValueObject.id}).")
|
||||
@@ -259,7 +271,8 @@ class DatabaseRepository(
|
||||
LogManager.v(DERIVED_VALUES_TAG, "Derived value for key ${derivedTypeObject.key} is null. No existing value to delete.")
|
||||
}
|
||||
} else {
|
||||
val roundedValue = roundTo(derivedValue)
|
||||
// If derived value is not null, insert or update it
|
||||
val roundedValue = roundTo(derivedValue) // Apply rounding
|
||||
if (existingDerivedValueObject != null) {
|
||||
if (existingDerivedValueObject.floatValue != roundedValue) {
|
||||
measurementValueDao.update(existingDerivedValueObject.copy(floatValue = roundedValue))
|
||||
@@ -280,20 +293,93 @@ class DatabaseRepository(
|
||||
}
|
||||
}
|
||||
|
||||
val weightKg = findValue(MeasurementTypeKey.WEIGHT)
|
||||
val bodyFatPercentage = findValue(MeasurementTypeKey.BODY_FAT)
|
||||
val waistCm = findValue(MeasurementTypeKey.WAIST)
|
||||
val hipsCm = findValue(MeasurementTypeKey.HIPS)
|
||||
val caliper1Cm = findValue(MeasurementTypeKey.CALIPER_1)
|
||||
val caliper2Cm = findValue(MeasurementTypeKey.CALIPER_2)
|
||||
val caliper3Cm = findValue(MeasurementTypeKey.CALIPER_3)
|
||||
// Fetch raw values and their original units
|
||||
val (weightValue, weightUnitType) = findValueAndUnit(MeasurementTypeKey.WEIGHT)
|
||||
val (bodyFatValue, _) = findValueAndUnit(MeasurementTypeKey.BODY_FAT) // Unit usually % (UnitType.PERCENT)
|
||||
val (waistValue, waistUnitType) = findValueAndUnit(MeasurementTypeKey.WAIST)
|
||||
val (hipsValue, hipsUnitType) = findValueAndUnit(MeasurementTypeKey.HIPS)
|
||||
val (caliper1Value, caliper1UnitType) = findValueAndUnit(MeasurementTypeKey.CALIPER_1)
|
||||
val (caliper2Value, caliper2UnitType) = findValueAndUnit(MeasurementTypeKey.CALIPER_2)
|
||||
val (caliper3Value, caliper3UnitType) = findValueAndUnit(MeasurementTypeKey.CALIPER_3)
|
||||
|
||||
processBmiCalculation(weightKg, user.heightCm).also { saveOrUpdateDerivedValue(it, MeasurementTypeKey.BMI) }
|
||||
// --- CONVERT VALUES TO REQUIRED UNITS FOR CALCULATIONS ---
|
||||
|
||||
// Convert weight to Kilograms (KG)
|
||||
val weightKg: Float? = if (weightValue != null && weightUnitType != null) {
|
||||
when (weightUnitType) {
|
||||
UnitType.KG -> weightValue
|
||||
UnitType.LB -> Converters.toKilogram(weightValue, WeightUnit.LB)
|
||||
UnitType.ST -> Converters.toKilogram(weightValue, WeightUnit.ST)
|
||||
else -> {
|
||||
LogManager.w(DERIVED_VALUES_TAG, "Unsupported unit $weightUnitType for weight conversion. Assuming KG if value present for ${MeasurementTypeKey.WEIGHT}.")
|
||||
weightValue // Fallback or handle error appropriately
|
||||
}
|
||||
}
|
||||
} else null
|
||||
|
||||
// Body fat is typically already in percentage
|
||||
val bodyFatPercentage: Float? = bodyFatValue
|
||||
|
||||
// Convert waist circumference to Centimeters (CM)
|
||||
val waistCm: Float? = if (waistValue != null && waistUnitType != null) {
|
||||
when (waistUnitType) {
|
||||
UnitType.CM -> waistValue
|
||||
UnitType.INCH -> Converters.toCentimeter(waistValue, MeasureUnit.INCH)
|
||||
else -> {
|
||||
LogManager.w(DERIVED_VALUES_TAG, "Unsupported unit $waistUnitType for waist conversion. Assuming CM if value present for ${MeasurementTypeKey.WAIST}.")
|
||||
waistValue
|
||||
}
|
||||
}
|
||||
} else null
|
||||
|
||||
// Convert hips circumference to Centimeters (CM)
|
||||
val hipsCm: Float? = if (hipsValue != null && hipsUnitType != null) {
|
||||
when (hipsUnitType) {
|
||||
UnitType.CM -> hipsValue
|
||||
UnitType.INCH -> Converters.toCentimeter(hipsValue, MeasureUnit.INCH)
|
||||
else -> {
|
||||
LogManager.w(DERIVED_VALUES_TAG, "Unsupported unit $hipsUnitType for hips conversion. Assuming CM if value present for ${MeasurementTypeKey.HIPS}.")
|
||||
hipsValue
|
||||
}
|
||||
}
|
||||
} else null
|
||||
|
||||
// Convert caliper measurements to Centimeters (CM)
|
||||
val caliper1Cm: Float? = if (caliper1Value != null && caliper1UnitType != null) {
|
||||
when (caliper1UnitType) {
|
||||
UnitType.CM -> caliper1Value
|
||||
UnitType.INCH -> Converters.toCentimeter(caliper1Value, MeasureUnit.INCH)
|
||||
else -> caliper1Value // Fallback
|
||||
}
|
||||
} else null
|
||||
val caliper2Cm: Float? = if (caliper2Value != null && caliper2UnitType != null) {
|
||||
when (caliper2UnitType) {
|
||||
UnitType.CM -> caliper2Value
|
||||
UnitType.INCH -> Converters.toCentimeter(caliper2Value, MeasureUnit.INCH)
|
||||
else -> caliper2Value
|
||||
}
|
||||
} else null
|
||||
val caliper3Cm: Float? = if (caliper3Value != null && caliper3UnitType != null) {
|
||||
when (caliper3UnitType) {
|
||||
UnitType.CM -> caliper3Value
|
||||
UnitType.INCH -> Converters.toCentimeter(caliper3Value, MeasureUnit.INCH)
|
||||
else -> caliper3Value
|
||||
}
|
||||
} else null
|
||||
|
||||
// User's height is assumed to be stored in CM in the User object
|
||||
val userHeightCm = user.heightCm
|
||||
|
||||
// --- PERFORM DERIVED VALUE CALCULATIONS ---
|
||||
// Pass the converted values (e.g., weightKg, waistCm) to the processing functions
|
||||
|
||||
processBmiCalculation(weightKg, userHeightCm).also { saveOrUpdateDerivedValue(it, MeasurementTypeKey.BMI) }
|
||||
processLbmCalculation(weightKg, bodyFatPercentage).also { saveOrUpdateDerivedValue(it, MeasurementTypeKey.LBM) }
|
||||
processWhrCalculation(waistCm, hipsCm).also { saveOrUpdateDerivedValue(it, MeasurementTypeKey.WHR) }
|
||||
processWhtrCalculation(waistCm, user.heightCm).also { saveOrUpdateDerivedValue(it, MeasurementTypeKey.WHTR) }
|
||||
processBmrCalculation(weightKg, user).also { bmr ->
|
||||
processWhtrCalculation(waistCm, userHeightCm).also { saveOrUpdateDerivedValue(it, MeasurementTypeKey.WHTR) }
|
||||
processBmrCalculation(weightKg, user).also { bmr -> // user object contains heightCm and other necessary details
|
||||
saveOrUpdateDerivedValue(bmr, MeasurementTypeKey.BMR)
|
||||
// TDEE calculation depends on the BMR result
|
||||
processTDEECalculation(bmr, user.activityLevel).also { saveOrUpdateDerivedValue(it, MeasurementTypeKey.TDEE) }
|
||||
}
|
||||
processFatCaliperCalculation(caliper1Cm, caliper2Cm, caliper3Cm, user)
|
||||
|
@@ -1158,7 +1158,7 @@ class SettingsViewModel(
|
||||
*/
|
||||
fun updateMeasurementTypeAndConvertDataViewModelCentric(
|
||||
originalType: MeasurementType,
|
||||
updatedType: MeasurementType,
|
||||
updatedType: MeasurementType, // This contains the new unit and other proposed changes from the UI
|
||||
showSnackbarMaster: Boolean = true
|
||||
) {
|
||||
viewModelScope.launch {
|
||||
@@ -1170,14 +1170,54 @@ class SettingsViewModel(
|
||||
var conversionErrorOccurred = false
|
||||
var valuesConvertedCount = 0
|
||||
|
||||
if (originalType.unit != updatedType.unit && originalType.inputType == InputFieldType.FLOAT && updatedType.inputType == InputFieldType.FLOAT) {
|
||||
// 1. First, update the MeasurementType definition in the database.
|
||||
// This ensures that any subsequent recalculations of derived values
|
||||
// will use the correct (new) unit for this type.
|
||||
val finalTypeToUpdate = MeasurementType(
|
||||
id = originalType.id,
|
||||
key = originalType.key, // Key should be immutable for an existing type
|
||||
name = updatedType.name,
|
||||
color = updatedType.color,
|
||||
icon = updatedType.icon,
|
||||
unit = updatedType.unit, // Crucial: The new unit
|
||||
inputType = updatedType.inputType,
|
||||
displayOrder = originalType.displayOrder,
|
||||
isDerived = originalType.isDerived,
|
||||
isEnabled = updatedType.isEnabled,
|
||||
isPinned = updatedType.isPinned,
|
||||
isOnRightYAxis = updatedType.isOnRightYAxis
|
||||
)
|
||||
|
||||
try {
|
||||
repository.updateMeasurementType(finalTypeToUpdate)
|
||||
LogManager.i(
|
||||
TAG,
|
||||
"Unit changed for FLOAT type ID ${originalType.id} from ${originalType.unit} to ${updatedType.unit}. Converting values."
|
||||
"MeasurementType (ID: ${originalType.id}) definition updated successfully to new unit '${finalTypeToUpdate.unit}'."
|
||||
)
|
||||
} catch (e: Exception) {
|
||||
LogManager.e(TAG, "Error updating MeasurementType (ID: ${originalType.id}) definition itself", e)
|
||||
sharedViewModel.showSnackbar(
|
||||
messageResId = R.string.measurement_type_updated_error,
|
||||
// Consider using context.getString for display names if not available in originalType
|
||||
formatArgs = listOf(originalType.name ?: originalType.key.toString())
|
||||
)
|
||||
conversionErrorOccurred = true // Prevent further steps if this critical update fails
|
||||
}
|
||||
|
||||
// 2. If the type definition was updated successfully AND the unit has changed for a FLOAT type,
|
||||
// convert the associated MeasurementValue entries.
|
||||
if (!conversionErrorOccurred &&
|
||||
originalType.unit != updatedType.unit &&
|
||||
originalType.inputType == InputFieldType.FLOAT &&
|
||||
updatedType.inputType == InputFieldType.FLOAT
|
||||
) {
|
||||
LogManager.i(
|
||||
TAG,
|
||||
"Unit changed for FLOAT type ID ${originalType.id}. Converting values AFTER type definition update."
|
||||
)
|
||||
try {
|
||||
val valuesToConvert = repository.getValuesForType(originalType.id).first() // .first() um den aktuellen Wert des Flows zu erhalten
|
||||
// Fetch all values belonging to this type
|
||||
val valuesToConvert = repository.getValuesForType(originalType.id).first()
|
||||
|
||||
if (valuesToConvert.isNotEmpty()) {
|
||||
LogManager.d(TAG, "Found ${valuesToConvert.size} values of type ID ${originalType.id} to potentially convert.")
|
||||
@@ -1185,13 +1225,14 @@ class SettingsViewModel(
|
||||
|
||||
valuesToConvert.forEach { valueToConvert ->
|
||||
valueToConvert.floatValue?.let { currentFloatVal ->
|
||||
// Convert from the original unit of the values to the new target unit
|
||||
val convertedFloat = Converters.convertFloatValueUnit(
|
||||
value = currentFloatVal,
|
||||
fromUnit = originalType.unit,
|
||||
toUnit = updatedType.unit
|
||||
fromUnit = originalType.unit, // Important: Use the unit the values currently have
|
||||
toUnit = updatedType.unit // The new target unit
|
||||
)
|
||||
|
||||
if (convertedFloat != currentFloatVal) {
|
||||
if (convertedFloat != currentFloatVal) { // Add only if the value actually changes
|
||||
updatedValuesBatch.add(valueToConvert.copy(floatValue = convertedFloat))
|
||||
valuesConvertedCount++
|
||||
}
|
||||
@@ -1200,9 +1241,8 @@ class SettingsViewModel(
|
||||
|
||||
if (updatedValuesBatch.isNotEmpty()) {
|
||||
LogManager.d(TAG, "Updating ${updatedValuesBatch.size} values in batch for type ID ${originalType.id}.")
|
||||
// Aktualisiere jeden Wert einzeln. Dein Repository.updateMeasurementValue
|
||||
// stößt die Neuberechnung der abgeleiteten Werte an.
|
||||
updatedValuesBatch.forEach { repository.updateMeasurementValue(it) }
|
||||
// Consider a repository.justUpdateMeasurementValueData(it)
|
||||
LogManager.d(TAG, "Batch update of ${updatedValuesBatch.size} values completed for type ID ${originalType.id}.")
|
||||
} else {
|
||||
LogManager.i(TAG, "No values required actual conversion or update for type ID ${originalType.id} after checking.")
|
||||
@@ -1213,74 +1253,42 @@ class SettingsViewModel(
|
||||
} catch (e: Exception) {
|
||||
LogManager.e(TAG, "Error during value conversion/update for type ID ${originalType.id}", e)
|
||||
conversionErrorOccurred = true
|
||||
// Verwende hier getDisplayName vom originalType, da updatedType möglicherweise noch nicht committet wurde.
|
||||
sharedViewModel.showSnackbar(
|
||||
messageResId = R.string.measurement_type_update_error_conversion_failed,
|
||||
formatArgs = listOf(originalType.name ?: originalType.key.toString())
|
||||
)
|
||||
// Optional: Consider reverting the MeasurementType update if value conversion fails (adds complexity).
|
||||
}
|
||||
} else if (originalType.unit != updatedType.unit) {
|
||||
} else if (!conversionErrorOccurred && originalType.unit != updatedType.unit) {
|
||||
LogManager.i(
|
||||
TAG,
|
||||
"Unit changed for type ID ${originalType.id}, but InputType is not FLOAT (Original: ${originalType.inputType}, Updated: ${updatedType.inputType}). No value conversion performed."
|
||||
"Unit changed for type ID ${originalType.id}, but InputType is not FLOAT or previous type update failed. " +
|
||||
"No direct value conversion, but affected measurements will be flagged for recalculation."
|
||||
)
|
||||
}
|
||||
|
||||
if (!conversionErrorOccurred) {
|
||||
try {
|
||||
val finalTypeToUpdate = MeasurementType(
|
||||
id = originalType.id,
|
||||
key = originalType.key,
|
||||
name = updatedType.name,
|
||||
color = updatedType.color,
|
||||
icon = updatedType.icon,
|
||||
unit = updatedType.unit,
|
||||
inputType = updatedType.inputType,
|
||||
displayOrder = originalType.displayOrder,
|
||||
isDerived = originalType.isDerived,
|
||||
isEnabled = updatedType.isEnabled,
|
||||
isPinned = updatedType.isPinned,
|
||||
isOnRightYAxis = updatedType.isOnRightYAxis
|
||||
)
|
||||
|
||||
repository.updateMeasurementType(finalTypeToUpdate)
|
||||
LogManager.i(
|
||||
TAG,
|
||||
"MeasurementType (ID: ${originalType.id}) updated successfully to new unit '${finalTypeToUpdate.unit}'."
|
||||
)
|
||||
|
||||
if (showSnackbarMaster) {
|
||||
if (valuesConvertedCount > 0) {
|
||||
sharedViewModel.showSnackbar(
|
||||
messageResId = R.string.measurement_type_updated_and_values_converted_successfully,
|
||||
formatArgs = listOf(updatedType.name ?: updatedType.key.toString(), valuesConvertedCount.toString()) // Context für getDisplayName wäre besser
|
||||
)
|
||||
} else if (originalType.unit != updatedType.unit && originalType.inputType == InputFieldType.FLOAT) {
|
||||
sharedViewModel.showSnackbar(
|
||||
messageResId = R.string.measurement_type_updated_unit_changed_no_values_converted,
|
||||
formatArgs = listOf(updatedType.name ?: updatedType.key.toString()) // Context für getDisplayName wäre besser
|
||||
)
|
||||
}
|
||||
else {
|
||||
sharedViewModel.showSnackbar(
|
||||
messageResId = R.string.measurement_type_updated_successfully,
|
||||
formatArgs = listOf(updatedType.name ?: updatedType.key.toString()) // Context für getDisplayName wäre besser
|
||||
)
|
||||
}
|
||||
}
|
||||
// sharedViewModel.refreshMeasurementTypes() // Optional, wenn die Liste sich nicht automatisch aktualisiert
|
||||
} catch (e: Exception) {
|
||||
LogManager.e(TAG, "Error updating MeasurementType (ID: ${originalType.id}) itself", e)
|
||||
// 3. Show appropriate snackbar message.
|
||||
if (!conversionErrorOccurred && showSnackbarMaster) {
|
||||
if (valuesConvertedCount > 0) {
|
||||
sharedViewModel.showSnackbar(
|
||||
messageResId = R.string.measurement_type_updated_error,
|
||||
formatArgs = listOf(originalType.name ?: originalType.key.toString()) // Context für getDisplayName wäre besser
|
||||
messageResId = R.string.measurement_type_updated_and_values_converted_successfully,
|
||||
formatArgs = listOf(
|
||||
updatedType.name ?: updatedType.key.toString(),
|
||||
valuesConvertedCount.toString()
|
||||
)
|
||||
)
|
||||
} else if (originalType.unit != updatedType.unit && (originalType.inputType == InputFieldType.FLOAT)) {
|
||||
// Also show if unit changed but no values were converted (e.g., none existed or input type wasn't FLOAT but recalculation was still triggered)
|
||||
sharedViewModel.showSnackbar(
|
||||
messageResId = R.string.measurement_type_updated_unit_changed_no_values_converted, // Or a more specific message
|
||||
formatArgs = listOf(updatedType.name ?: updatedType.key.toString())
|
||||
)
|
||||
} else { // Generic success if no unit change or no conversion needed
|
||||
sharedViewModel.showSnackbar(
|
||||
messageResId = R.string.measurement_type_updated_successfully,
|
||||
formatArgs = listOf(updatedType.name ?: updatedType.key.toString())
|
||||
)
|
||||
}
|
||||
} else {
|
||||
LogManager.w(
|
||||
TAG,
|
||||
"Skipped updating MeasurementType definition (ID: ${originalType.id}) due to prior value conversion error."
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Reference in New Issue
Block a user