mirror of
https://github.com/dzeiocom/OpenHealth.git
synced 2025-08-02 22:41:59 +00:00
feat: Add BMR and TDEE and automate them with the BMI (#154)
This commit is contained in:
@@ -35,6 +35,40 @@ object Settings {
|
||||
*/
|
||||
const val STEPS_GOAL = "com.dzeio.open-health.steps.goal-daily"
|
||||
|
||||
/**
|
||||
* The water intake size for the quick add
|
||||
*/
|
||||
const val WATER_INTAKE_SIZE = "com.dzeio.open-health.water.size"
|
||||
|
||||
/**
|
||||
* the default value for the setting above
|
||||
*/
|
||||
const val WATER_INTAKE_SIZE_DEFAULT = 250
|
||||
|
||||
/**
|
||||
* the user Height in CM
|
||||
*/
|
||||
const val USER_HEIGHT = "com.dzeio.open-health.height"
|
||||
|
||||
/**
|
||||
* the user birthday as an ISO8601 date
|
||||
*/
|
||||
const val USER_BIRTHDAY = "com.dzeio.open-health.birthday"
|
||||
|
||||
/**
|
||||
* the User age
|
||||
*/
|
||||
const val USER_AGE = "com.dzeio.open-health.age"
|
||||
|
||||
/**
|
||||
* the user biologicial age (0 = female, 1 = male)
|
||||
*/
|
||||
const val USER_BIOLOGICAL_SEX = "com.dzeio.open-health.biological_sex"
|
||||
|
||||
/**
|
||||
* the user activity level
|
||||
*
|
||||
* @see com.dzeio.openhealth.units.ActivityLevel
|
||||
*/
|
||||
const val USER_ACTIVITY_LEVEL = "com.dzeio.open-health.activity_level"
|
||||
}
|
||||
|
@@ -31,9 +31,10 @@ import java.util.zip.ZipInputStream
|
||||
Step::class,
|
||||
Food::class
|
||||
],
|
||||
version = 2,
|
||||
version = 3,
|
||||
autoMigrations = [
|
||||
AutoMigration(1, 2)
|
||||
AutoMigration(1, 2),
|
||||
AutoMigration(2, 3)
|
||||
],
|
||||
exportSchema = true
|
||||
)
|
||||
|
@@ -3,8 +3,10 @@ package com.dzeio.openhealth.data.weight
|
||||
import androidx.room.ColumnInfo
|
||||
import androidx.room.Entity
|
||||
import androidx.room.PrimaryKey
|
||||
import com.dzeio.openhealth.units.ActivityLevel
|
||||
import java.sql.Date
|
||||
import java.text.DateFormat.getDateInstance
|
||||
import kotlin.math.roundToInt
|
||||
|
||||
@Entity()
|
||||
data class Weight(
|
||||
@@ -68,12 +70,63 @@ data class Weight(
|
||||
/**
|
||||
* visceral fat in it's own unit?
|
||||
*/
|
||||
var visceralFat: Float? = null
|
||||
var visceralFat: Float? = null,
|
||||
|
||||
/**
|
||||
* the BMR rate
|
||||
*/
|
||||
var basalMetabolicRate: Int? = null,
|
||||
|
||||
var totalDailyEnergyExpendure: Int? = null
|
||||
) {
|
||||
|
||||
/**
|
||||
* Run estimations to calculate & set different elements if they are not set
|
||||
*
|
||||
* @param height the height in `cm`
|
||||
* @param age the user's age
|
||||
* @param biologicalSex the user's biological sex (female = 0, male = 1)
|
||||
* @param activityLevel the level of activity of the user
|
||||
*/
|
||||
fun setItemsWithEstimation(
|
||||
height: Int? = null,
|
||||
age: Int? = null,
|
||||
biologicalSex: Int? = null,
|
||||
activityLevel: ActivityLevel? = null
|
||||
) {
|
||||
if (bmi == null && height != null) {
|
||||
val tmpHeight = (height / 100f)
|
||||
bmi = weight / (tmpHeight * tmpHeight)
|
||||
}
|
||||
|
||||
if (totalBodyWater == null && height != null) {
|
||||
// female = 0, male = 1
|
||||
totalBodyWater = (
|
||||
if (biologicalSex == 0) {
|
||||
((0.34454 * height) + (0.183809 * weight) - 35.270121)
|
||||
} else {
|
||||
((0.194786 * height) + (0.296785 * weight) - 14.012934)
|
||||
}
|
||||
).toFloat()
|
||||
}
|
||||
if (basalMetabolicRate == null && height != null && age != null) {
|
||||
basalMetabolicRate = (
|
||||
if (biologicalSex == 0) {
|
||||
10 * weight + 6.25 * height - 5 * age - 161
|
||||
} else {
|
||||
10 * weight + 6.25 * height - 5 * age + 5
|
||||
}
|
||||
).toInt()
|
||||
}
|
||||
if (totalDailyEnergyExpendure == null && activityLevel != null && basalMetabolicRate != null) {
|
||||
totalDailyEnergyExpendure = (basalMetabolicRate!! * activityLevel.modifier).roundToInt()
|
||||
}
|
||||
}
|
||||
|
||||
fun formatTimestamp(): String = getDateInstance().format(Date(timestamp))
|
||||
|
||||
override fun equals(other: Any?): Boolean {
|
||||
if (!(other is Weight)) {
|
||||
if (other !is Weight) {
|
||||
return super.equals(other)
|
||||
}
|
||||
|
||||
@@ -87,4 +140,19 @@ data class Weight(
|
||||
boneMass == other.boneMass &&
|
||||
visceralFat == other.visceralFat
|
||||
}
|
||||
|
||||
override fun hashCode(): Int {
|
||||
var result = id.hashCode()
|
||||
result = 31 * result + weight.hashCode()
|
||||
result = 31 * result + timestamp.hashCode()
|
||||
result = 31 * result + source.hashCode()
|
||||
result = 31 * result + (bmi?.hashCode() ?: 0)
|
||||
result = 31 * result + (totalBodyWater?.hashCode() ?: 0)
|
||||
result = 31 * result + (muscles?.hashCode() ?: 0)
|
||||
result = 31 * result + (leanBodyMass?.hashCode() ?: 0)
|
||||
result = 31 * result + (bodyFat?.hashCode() ?: 0)
|
||||
result = 31 * result + (boneMass?.hashCode() ?: 0)
|
||||
result = 31 * result + (visceralFat?.hashCode() ?: 0)
|
||||
return result
|
||||
}
|
||||
}
|
||||
|
@@ -72,6 +72,58 @@ class SettingsFragment : PreferenceFragmentCompat() {
|
||||
}
|
||||
}
|
||||
|
||||
val height = findPreference<EditTextPreference>("tmp_height")
|
||||
height?.apply {
|
||||
setOnBindEditTextListener {
|
||||
it.setSelectAllOnFocus(true)
|
||||
it.inputType = InputType.TYPE_CLASS_NUMBER or InputType.TYPE_NUMBER_FLAG_DECIMAL
|
||||
}
|
||||
|
||||
val value = config.getInt(Settings.USER_HEIGHT)
|
||||
|
||||
setOnPreferenceClickListener {
|
||||
text = value.value?.toString()
|
||||
return@setOnPreferenceClickListener true
|
||||
}
|
||||
|
||||
setOnPreferenceChangeListener { _, newValue ->
|
||||
|
||||
value.value = (newValue as String).toInt()
|
||||
|
||||
return@setOnPreferenceChangeListener false
|
||||
}
|
||||
}
|
||||
|
||||
val activityPreference = findPreference<ListPreference>("tmp.com.dzeio.open-health.activitylevel")
|
||||
activityPreference?.apply {
|
||||
val value = config.getInt(Settings.USER_ACTIVITY_LEVEL)
|
||||
setOnPreferenceClickListener {
|
||||
if (value.value != null) {
|
||||
setValueIndex(value.value!!)
|
||||
}
|
||||
return@setOnPreferenceClickListener true
|
||||
}
|
||||
setOnPreferenceChangeListener { _, newValue ->
|
||||
value.value = findIndexOfValue(newValue.toString())
|
||||
return@setOnPreferenceChangeListener false
|
||||
}
|
||||
}
|
||||
|
||||
val biologicalSexPreference = findPreference<ListPreference>("tmp.com.dzeio.open-health.biological_sex")
|
||||
biologicalSexPreference?.apply {
|
||||
val value = config.getInt(Settings.USER_BIOLOGICAL_SEX)
|
||||
setOnPreferenceClickListener {
|
||||
if (value.value != null) {
|
||||
setValueIndex(value.value!!)
|
||||
}
|
||||
return@setOnPreferenceClickListener true
|
||||
}
|
||||
setOnPreferenceChangeListener { _, newValue ->
|
||||
value.value = findIndexOfValue(newValue.toString())
|
||||
return@setOnPreferenceChangeListener false
|
||||
}
|
||||
}
|
||||
|
||||
val languagesPreference = findPreference<ListPreference>(Settings.APP_LANGUAGE)
|
||||
Log.d(TAG, Locale.getDefault().language)
|
||||
languagesPreference?.apply {
|
||||
|
@@ -7,12 +7,13 @@ import com.dzeio.openhealth.Settings
|
||||
import com.dzeio.openhealth.core.BaseViewModel
|
||||
import com.dzeio.openhealth.data.weight.Weight
|
||||
import com.dzeio.openhealth.data.weight.WeightRepository
|
||||
import com.dzeio.openhealth.units.ActivityLevel
|
||||
import com.dzeio.openhealth.units.Units
|
||||
import com.dzeio.openhealth.utils.Configuration
|
||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||
import javax.inject.Inject
|
||||
import kotlinx.coroutines.flow.collectLatest
|
||||
import kotlinx.coroutines.launch
|
||||
import javax.inject.Inject
|
||||
|
||||
@HiltViewModel
|
||||
class WeightDialogViewModel @Inject internal constructor(
|
||||
@@ -21,6 +22,10 @@ class WeightDialogViewModel @Inject internal constructor(
|
||||
) : BaseViewModel() {
|
||||
|
||||
private val _goalWeight = settings.getFloat(Settings.WEIGHT_GOAL)
|
||||
private val age = settings.getInt(Settings.USER_AGE)
|
||||
private val activityLevel = settings.getInt(Settings.USER_ACTIVITY_LEVEL)
|
||||
private val biologicalSex = settings.getInt(Settings.USER_BIOLOGICAL_SEX)
|
||||
private val height = settings.getInt(Settings.USER_HEIGHT)
|
||||
|
||||
val goalWeight = _goalWeight.toLiveData()
|
||||
|
||||
@@ -45,7 +50,18 @@ class WeightDialogViewModel @Inject internal constructor(
|
||||
}
|
||||
|
||||
suspend fun addWeight(weight: Float) {
|
||||
weightRepository.addWeight(Weight(weight = weight / format.modifier))
|
||||
weightRepository.addWeight(
|
||||
Weight(weight = weight / format.modifier).apply {
|
||||
if (height.value != null) {
|
||||
setItemsWithEstimation(
|
||||
height.value!!,
|
||||
age.value,
|
||||
biologicalSex.value,
|
||||
if (activityLevel.value != null) ActivityLevel.values()[activityLevel.value!!] else null
|
||||
)
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
fun setWeightGoal(value: Float?) {
|
||||
|
@@ -0,0 +1,10 @@
|
||||
package com.dzeio.openhealth.units
|
||||
|
||||
enum class ActivityLevel(val modifier: Float) {
|
||||
BEDRIDDEN(1f),
|
||||
SEDENTARY(1.2f),
|
||||
LIGHTLY_ACTIVE(1.375f),
|
||||
MODERATELY_ACTIVE(1.55f),
|
||||
VERY_ACTIVE(1.725f),
|
||||
EXTREMELY_ACTIVE(1.9f)
|
||||
}
|
@@ -104,6 +104,14 @@
|
||||
android:layout_height="200dp"
|
||||
android:minHeight="200dp" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/goal_text"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:visibility="gone"
|
||||
android:layout_marginTop="16dp"
|
||||
/>
|
||||
|
||||
<Button
|
||||
android:id="@+id/goal_button"
|
||||
android:text="@string/add_goal"
|
||||
|
@@ -64,4 +64,17 @@
|
||||
<string name="import_complete">Import réussi, redémarrage de l\'application</string>
|
||||
<string name="export_complete">Export Réussi!</string>
|
||||
<string name="import_export">Importer/Exporter</string>
|
||||
<string name="weight_item">Date: %1$s\nBMI: %2$.2f\nEau corporelle: %3$.2f\nMuscles: %4$.2f\nMasse maigre: %5$.2f\nMasse grasse: %6$.2f\nMasse osseuse: %7$.2f\nGraisse viscérale: %8$.2f\nMétabolisme basal: %9$d\nDépense énergétique quotidienne totale: %10$d</string>
|
||||
<string-array name="activity_levels">
|
||||
<item>Cloué au lit</item>
|
||||
<item>Sédentaire</item>
|
||||
<item>Légèrement actif</item>
|
||||
<item>Modérément actif</item>
|
||||
<item>Très actif</item>
|
||||
<item>Extremement active</item>
|
||||
</string-array>
|
||||
<string-array name="biological_sex">
|
||||
<item>Femme</item>
|
||||
<item>Homme</item>
|
||||
</string-array>
|
||||
</resources>
|
||||
|
@@ -1,7 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<string-array name="genders">
|
||||
<item>Male</item>
|
||||
<item>Female</item>
|
||||
</string-array>
|
||||
</resources>
|
@@ -77,4 +77,17 @@
|
||||
<string name="import_complete">Import successful, Restarting the application</string>
|
||||
<string name="export_complete">Export successful!</string>
|
||||
<string name="import_export">Import/Export</string>
|
||||
<string name="weight_item">Date: %1$s\nBMI: %2$.2f\nBody water: %3$.2f\nMuscles: %4$.2f\nLean body mass: %5$.2f\nBody fat: %6$.2f\nBone mass: %7$.2f\nVisceral fat: %8$.2f\nBasal metabolic rate: %9$d\nTotal daily energy expendure: %10$d\n</string>
|
||||
<string-array name="activity_levels">
|
||||
<item>Bedridden</item>
|
||||
<item>Sedentary</item>
|
||||
<item>Lightly active</item>
|
||||
<item>Moderately active</item>
|
||||
<item>Very active</item>
|
||||
<item>Extremely active</item>
|
||||
</string-array>
|
||||
<string-array name="biological_sex">
|
||||
<item>Female</item>
|
||||
<item>Male</item>
|
||||
</string-array>
|
||||
</resources>
|
||||
|
@@ -4,20 +4,31 @@
|
||||
<PreferenceCategory android:title="@string/settings_global">
|
||||
<ListPreference
|
||||
android:defaultValue="Male"
|
||||
android:entries="@array/genders"
|
||||
android:entryValues="@array/genders"
|
||||
android:key="global_gender"
|
||||
android:title="Gender" />
|
||||
android:entries="@array/biological_sex"
|
||||
android:entryValues="@array/biological_sex"
|
||||
android:key="tmp.com.dzeio.open-health.biological_sex"
|
||||
android:title="Biological sex" />
|
||||
|
||||
<ListPreference
|
||||
android:key="com.dzeio.open-health.app.language"
|
||||
android:title="@string/languages" />
|
||||
|
||||
|
||||
<com.dzeio.openhealth.utils.fields.IntEditTextPreference
|
||||
android:key="com.dzeio.open-health.age"
|
||||
android:title="Age" />
|
||||
|
||||
<ListPreference
|
||||
android:key="tmp.com.dzeio.open-health.activitylevel"
|
||||
android:entries="@array/activity_levels"
|
||||
android:entryValues="@array/activity_levels"
|
||||
android:title="Activity Level" />
|
||||
</PreferenceCategory>
|
||||
|
||||
<PreferenceCategory android:title="Weight Settings">
|
||||
|
||||
<EditTextPreference
|
||||
android:key="global_height"
|
||||
android:key="tmp_height"
|
||||
android:selectAllOnFocus="true"
|
||||
android:singleLine="true"
|
||||
android:title="Height" />
|
||||
|
Reference in New Issue
Block a user