1
0
mirror of https://github.com/dzeiocom/OpenHealth.git synced 2025-04-23 19:32:11 +00:00

fix: Allow to use other volumes units than Milliliter (#156)

This commit is contained in:
Florian Bouillon 2023-03-06 09:48:28 +01:00 committed by GitHub
parent fa56970fb6
commit 497cc58057
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
27 changed files with 533 additions and 328 deletions

View File

@ -40,6 +40,7 @@ jobs:
- name: Zip artifacts - name: Zip artifacts
run: zip -r assemble.zip . -i '**/build/*.apk' '**/build/*.aab' '**/build/*.aar' '**/build/*.so' run: zip -r assemble.zip . -i '**/build/*.apk' '**/build/*.aab' '**/build/*.aar' '**/build/*.so'
- name: Upload artifacts - name: Upload artifacts
continue-on-error: true
uses: actions/upload-artifact@v3 uses: actions/upload-artifact@v3
with: with:
name: assemble name: assemble

View File

@ -40,6 +40,13 @@ object Settings {
*/ */
const val WATER_INTAKE_SIZE = "com.dzeio.open-health.water.size" const val WATER_INTAKE_SIZE = "com.dzeio.open-health.water.size"
/**
* volume unit used
*/
const val VOLUME_UNIT = "com.dzeio.open-health.volume-unit"
const val WATER_INTAKE_DAILY_GOAL = "com.dzeio.open-health.water.daily"
/** /**
* the default value for the setting above * the default value for the setting above
*/ */

View File

@ -1,7 +1,6 @@
package com.dzeio.openhealth.ui.home package com.dzeio.openhealth.ui.home
import android.animation.ValueAnimator import android.animation.ValueAnimator
import android.content.SharedPreferences
import android.graphics.Bitmap import android.graphics.Bitmap
import android.graphics.Canvas import android.graphics.Canvas
import android.graphics.Paint import android.graphics.Paint
@ -10,21 +9,22 @@ import android.view.LayoutInflater
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import androidx.navigation.fragment.findNavController import androidx.navigation.fragment.findNavController
import androidx.preference.PreferenceManager
import com.dzeio.charts.Entry import com.dzeio.charts.Entry
import com.dzeio.charts.axis.Line import com.dzeio.charts.axis.Line
import com.dzeio.charts.series.LineSerie import com.dzeio.charts.series.LineSerie
import com.dzeio.openhealth.BuildConfig import com.dzeio.openhealth.BuildConfig
import com.dzeio.openhealth.R
import com.dzeio.openhealth.core.BaseFragment import com.dzeio.openhealth.core.BaseFragment
import com.dzeio.openhealth.data.water.Water import com.dzeio.openhealth.data.water.Water
import com.dzeio.openhealth.data.weight.Weight
import com.dzeio.openhealth.databinding.FragmentHomeBinding import com.dzeio.openhealth.databinding.FragmentHomeBinding
import com.dzeio.openhealth.ui.weight.WeightDialog import com.dzeio.openhealth.ui.weight.WeightDialog
import com.dzeio.openhealth.units.Units
import com.dzeio.openhealth.utils.ChartUtils import com.dzeio.openhealth.utils.ChartUtils
import com.dzeio.openhealth.utils.DrawUtils import com.dzeio.openhealth.utils.DrawUtils
import com.google.android.material.color.MaterialColors import com.google.android.material.color.MaterialColors
import dagger.hilt.android.AndroidEntryPoint import dagger.hilt.android.AndroidEntryPoint
import java.text.DateFormat
import java.util.Date
import java.util.Locale
import kotlin.math.max import kotlin.math.max
import kotlin.math.min import kotlin.math.min
@ -34,10 +34,6 @@ class HomeFragment : BaseFragment<HomeViewModel, FragmentHomeBinding>(HomeViewMo
override val bindingInflater: (LayoutInflater, ViewGroup?, Boolean) -> FragmentHomeBinding override val bindingInflater: (LayoutInflater, ViewGroup?, Boolean) -> FragmentHomeBinding
get() = FragmentHomeBinding::inflate get() = FragmentHomeBinding::inflate
private val settings: SharedPreferences by lazy {
PreferenceManager.getDefaultSharedPreferences(requireContext())
}
override fun onStart() { override fun onStart() {
super.onStart() super.onStart()
// Bindings // Bindings
@ -63,14 +59,14 @@ class HomeFragment : BaseFragment<HomeViewModel, FragmentHomeBinding>(HomeViewMo
viewModel.updateWater(water) viewModel.updateWater(water)
} }
} }
val waterUnit =
Units.Volume.find(settings.getString("water_unit", "milliliter") ?: "Milliliter")
binding.fragmentHomeWaterTotal.text = viewModel.waterUnit.observe(viewLifecycleOwner) {
String.format( updateWaterTotal()
resources.getString(waterUnit.unit), }
viewModel.dailyWaterIntake
) viewModel.dailyWaterIntake.observe(viewLifecycleOwner) {
updateWaterTotal()
}
binding.fragmentHomeWaterRemove.setOnClickListener { binding.fragmentHomeWaterRemove.setOnClickListener {
val water = viewModel.water.value val water = viewModel.water.value
@ -95,12 +91,21 @@ class HomeFragment : BaseFragment<HomeViewModel, FragmentHomeBinding>(HomeViewMo
} }
binding.weightGraph.apply { binding.weightGraph.apply {
val serie = LineSerie(this) LineSerie(this)
animator.enabled = false animator.enabled = false
xAxis.dataWidth = 2.4192e+9 xAxis.apply {
xAxis.scrollEnabled = true dataWidth = 2.4192e+9
scrollEnabled = true
onValueFormat = onValueFormat@{
val formatter = DateFormat.getDateTimeInstance(
DateFormat.SHORT,
DateFormat.SHORT,
Locale.getDefault()
)
return@onValueFormat formatter.format(Date(it.toLong()))
}
}
ChartUtils.materielTheme(this, requireView()) ChartUtils.materielTheme(this, requireView())
series = arrayListOf(serie)
} }
if (BuildConfig.DEBUG) { if (BuildConfig.DEBUG) {
@ -116,75 +121,84 @@ class HomeFragment : BaseFragment<HomeViewModel, FragmentHomeBinding>(HomeViewMo
// Update the water intake Graph when the water intake changes // Update the water intake Graph when the water intake changes
viewModel.water.observe(viewLifecycleOwner) { viewModel.water.observe(viewLifecycleOwner) {
if (it != null) { updateWaterGraph()
updateWater(it.value)
} else {
updateWater(0)
}
} }
// Update the steps Graph when the steps count changes // Update the steps Graph when the steps count changes
viewModel.steps.observe(viewLifecycleOwner) { viewModel.steps.observe(viewLifecycleOwner) {
binding.stepsCurrent.text = it.toString() binding.stepsCurrent.text = getString(
R.string.steps_count,
it
)
} }
// Update the steps Graph when the goal changes // Update the steps Graph when the goal changes
viewModel.stepsGoal.observe(viewLifecycleOwner) { viewModel.stepsGoal.observe(viewLifecycleOwner) {
if (it == null) { if (it == null) {
binding.stepsTotal.text = "" binding.stepsTotal.visibility = View.GONE
return@observe return@observe
} }
binding.stepsTotal.text = it.toString() binding.stepsTotal.visibility = View.VISIBLE
binding.stepsTotal.text = getString(
R.string.steps_count,
it
)
} }
// update the graph when the weight changes // update the graph when the weight changes
viewModel.weights.observe(viewLifecycleOwner) { viewModel.weights.observe(viewLifecycleOwner) {
if (it != null) { updateWeightGraph()
updateGraph(it)
}
} }
// update the graph when the goal weight change // update the graph when the goal weight change
viewModel.goalWeight.observe(viewLifecycleOwner) { viewModel.goalWeight.observe(viewLifecycleOwner) {
if (viewModel.weights.value != null) updateGraph(viewModel.weights.value!!) updateWeightGraph()
} }
// update the graph when the weight unit change // update the graph when the weight unit change
viewModel.massUnit.observe(viewLifecycleOwner) { viewModel.massUnit.observe(viewLifecycleOwner) {
if (viewModel.weights.value != null) updateGraph(viewModel.weights.value!!) updateWeightGraph()
} }
} }
/** /**
* Function that update the graph for the weight * Function that update the graph for the weight
*/ */
private fun updateGraph(list: List<Weight>) { private fun updateWeightGraph() {
val chart = binding.weightGraph val values = viewModel.weights.value ?: arrayListOf()
val goal = viewModel.goalWeight.value
val unit = viewModel.massUnit.value
if (unit == null) {
return
}
val chart = binding.weightGraph.apply {
yAxis.onValueFormat = { getString(unit.unit, unit.fromKilogram(it)) }
}
val serie = chart.series[0] as LineSerie val serie = chart.series[0] as LineSerie
val entries: ArrayList<Entry> = arrayListOf() val entries: ArrayList<Entry> = values.map {
list.forEach {
entries.add(
Entry( Entry(
it.timestamp.toDouble(), it.timestamp.toDouble(),
it.weight it.weight * unit.modifier
) )
) } as ArrayList<Entry>
}
serie.entries = entries serie.entries = entries
if (viewModel.goalWeight.value != null) { chart.yAxis.clearLines()
if (goal != null) {
chart.yAxis.addLine( chart.yAxis.addLine(
viewModel.goalWeight.value!!, goal,
Line(true, Paint(chart.yAxis.linePaint).apply { strokeWidth = 4f }) Line(true, Paint(chart.yAxis.linePaint).apply { strokeWidth = 4f })
) )
} }
if (list.isEmpty()) { if (entries.isEmpty()) {
chart.xAxis.x = 0.0 chart.xAxis.x = 0.0
} else { } else {
chart.xAxis.x = list.last().timestamp - chart.xAxis.dataWidth!! chart.xAxis.x = entries.last().x - chart.xAxis.dataWidth!!
} }
chart.refresh() chart.refresh()
@ -193,29 +207,30 @@ class HomeFragment : BaseFragment<HomeViewModel, FragmentHomeBinding>(HomeViewMo
/** /**
* the waterintake old value to keep for value update * the waterintake old value to keep for value update
*/ */
private var oldValue = 0f private var oldValue = 0
/** /**
* function that update the water count in the home page * function that update the water count in the home page
*/ */
private fun updateWater(newValue: Int) { private fun updateWaterGraph() {
// get the current Unit val newValue = viewModel.water.value?.value ?: 0
val waterUnit = val daily = viewModel.dailyWaterIntake.value
Units.Volume.find(settings.getString("water_unit", "milliliter") ?: "Milliliter") val unit = viewModel.waterUnit.value
if (unit == null) {
return
}
// Update the count // Update the count
binding.fragmentHomeWaterCurrent.text = binding.fragmentHomeWaterCurrent.text =
String.format( getString(
resources.getString(waterUnit.unit), unit.unit,
(newValue * waterUnit.modifier).toInt() (newValue * unit.modifier).toInt()
) )
// TODO: move it elsewhere if (daily == null) {
binding.fragmentHomeWaterTotal.text = return
String.format( }
resources.getString(waterUnit.unit),
viewModel.dailyWaterIntake
)
// get the with/height of the ImageView // get the with/height of the ImageView
var width = 1500 var width = 1500
@ -228,7 +243,7 @@ class HomeFragment : BaseFragment<HomeViewModel, FragmentHomeBinding>(HomeViewMo
// Prepare the update animation // Prepare the update animation
val animator = ValueAnimator.ofInt( val animator = ValueAnimator.ofInt(
this.oldValue.toInt(), this.oldValue,
newValue newValue
) )
animator.duration = 300 // ms animator.duration = 300 // ms
@ -237,8 +252,8 @@ class HomeFragment : BaseFragment<HomeViewModel, FragmentHomeBinding>(HomeViewMo
if (localView == null) { if (localView == null) {
return@addUpdateListener return@addUpdateListener
} }
this.oldValue = (it.animatedValue as Int).toFloat() this.oldValue = (it.animatedValue as Int)
val value = 100 * it.animatedValue as Int / viewModel.dailyWaterIntake val value = 100 * it.animatedValue as Int / daily.toFloat()
val graph = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888) val graph = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888)
val canvas = Canvas(graph) val canvas = Canvas(graph)
@ -283,4 +298,23 @@ class HomeFragment : BaseFragment<HomeViewModel, FragmentHomeBinding>(HomeViewMo
// start the animation // start the animation
animator.start() animator.start()
} }
private fun updateWaterTotal() {
val unit = viewModel.waterUnit.value
val value = viewModel.dailyWaterIntake.value
if (unit == null) {
return
}
if (value == null) {
binding.fragmentHomeWaterTotal.visibility = View.GONE
return
}
binding.fragmentHomeWaterTotal.visibility = View.VISIBLE
binding.fragmentHomeWaterTotal.text = getString(
unit.unit,
unit.fromMilliliter(value)
)
}
} }

View File

@ -10,7 +10,8 @@ import com.dzeio.openhealth.data.water.Water
import com.dzeio.openhealth.data.water.WaterRepository import com.dzeio.openhealth.data.water.WaterRepository
import com.dzeio.openhealth.data.weight.Weight import com.dzeio.openhealth.data.weight.Weight
import com.dzeio.openhealth.data.weight.WeightRepository import com.dzeio.openhealth.data.weight.WeightRepository
import com.dzeio.openhealth.units.Units import com.dzeio.openhealth.units.Mass
import com.dzeio.openhealth.units.Volume
import com.dzeio.openhealth.utils.Configuration import com.dzeio.openhealth.utils.Configuration
import dagger.hilt.android.lifecycle.HiltViewModel import dagger.hilt.android.lifecycle.HiltViewModel
import javax.inject.Inject import javax.inject.Inject
@ -54,26 +55,25 @@ class HomeViewModel @Inject internal constructor(
/** /**
* The size of a cup for the quick water intake add * The size of a cup for the quick water intake add
*/ */
var waterCupSize = config.getInt("water_cup_size").toLiveData() var waterCupSize = config.getInt(Settings.WATER_INTAKE_SIZE).toLiveData()
/** private val _waterUnit = MutableLiveData(Volume.MILLILITER)
* The unit used to display the water intake of the user val waterUnit: LiveData<Volume> = _waterUnit
*/
var waterUnit = Units.Volume.find(config.getString("water_unit").value ?: "ml")
private val _massUnit = MutableLiveData(Units.Mass.KILOGRAM) private val _massUnit = MutableLiveData(Mass.KILOGRAM)
/** /**
* The Mass unit used by the user * The Mass unit used by the user
*/ */
val massUnit: LiveData<Units.Mass> = _massUnit val massUnit: LiveData<Mass> = _massUnit
/** /**
* the User weight goal * the User weight goal
*/ */
val goalWeight = config.getFloat(Settings.WEIGHT_GOAL).toLiveData() val goalWeight = config.getFloat(Settings.WEIGHT_GOAL).toLiveData()
val dailyWaterIntake: Float = (config.getFloat("water_intake").value ?: 1200f) * waterUnit.modifier private val _dailyWaterIntake = MutableLiveData<Int?>(null)
val dailyWaterIntake: LiveData<Int?> = _dailyWaterIntake
init { init {
// Fetch today's water intake // Fetch today's water intake
@ -95,15 +95,31 @@ class HomeViewModel @Inject internal constructor(
} }
} }
config.getString(Settings.MASS_UNIT).apply { config.getInt(Settings.MASS_UNIT).apply {
addObserver { addObserver {
if (it == null) return@addObserver if (it == null) return@addObserver
_massUnit.postValue(Units.Mass.find(it)) _massUnit.postValue(Mass.find(it))
} }
if (value != null) { if (value != null) {
_massUnit.postValue(Units.Mass.find(value!!)) _massUnit.postValue(Mass.find(value!!))
} }
} }
config.getInt((Settings.VOLUME_UNIT)).apply {
addObserver {
if (it === null) return@addObserver
_waterUnit.postValue(Volume.find(it))
}
if (value != null) _waterUnit.postValue(Volume.find(value!!))
}
config.getInt(Settings.WATER_INTAKE_DAILY_GOAL).apply {
addObserver {
if (it === null) return@addObserver
_dailyWaterIntake.postValue((it * (_waterUnit.value?.modifier ?: 0f)).toInt())
}
if (value != null) _dailyWaterIntake.postValue((value!! * (_waterUnit.value?.modifier ?: 0f)).toInt())
}
} }
fun updateWater(water: Water) { fun updateWater(water: Water) {

View File

@ -13,9 +13,11 @@ import com.dzeio.openhealth.Application
import com.dzeio.openhealth.BuildConfig import com.dzeio.openhealth.BuildConfig
import com.dzeio.openhealth.R import com.dzeio.openhealth.R
import com.dzeio.openhealth.Settings import com.dzeio.openhealth.Settings
import com.dzeio.openhealth.units.Units import com.dzeio.openhealth.units.Mass
import com.dzeio.openhealth.units.Volume
import com.dzeio.openhealth.utils.Configuration import com.dzeio.openhealth.utils.Configuration
import com.dzeio.openhealth.utils.LocaleUtils import com.dzeio.openhealth.utils.LocaleUtils
import com.dzeio.openhealth.utils.fields.IntEditTextPreference
import dagger.hilt.android.AndroidEntryPoint import dagger.hilt.android.AndroidEntryPoint
import java.util.Locale import java.util.Locale
import javax.inject.Inject import javax.inject.Inject
@ -48,11 +50,11 @@ class SettingsFragment : PreferenceFragmentCompat() {
val value = config.getFloat(Settings.WEIGHT_GOAL) val value = config.getFloat(Settings.WEIGHT_GOAL)
setOnPreferenceClickListener { setOnPreferenceClickListener {
if (value.value != null) { if (value.value != null) {
val unit = config.getString(Settings.MASS_UNIT).value val unit = config.getInt(Settings.MASS_UNIT).value
text = if (unit == null) { text = if (unit == null) {
value.value!!.toString() value.value!!.toString()
} else { } else {
val modifier = Units.Mass.find(unit).modifier val modifier = Mass.find(unit).modifier
(value.value!! * modifier).toString() (value.value!! * modifier).toString()
} }
} }
@ -60,10 +62,10 @@ class SettingsFragment : PreferenceFragmentCompat() {
} }
setOnPreferenceChangeListener { _, newValue -> setOnPreferenceChangeListener { _, newValue ->
val unit = config.getString(Settings.MASS_UNIT).value val unit = config.getInt(Settings.MASS_UNIT).value
var modifier = Units.Mass.KILOGRAM.modifier var modifier = Mass.KILOGRAM.modifier
if (unit != null) { if (unit != null) {
modifier = Units.Mass.find(unit).modifier modifier = Mass.find(unit).modifier
} }
value.value = ((newValue as String).toFloat() / modifier) value.value = ((newValue as String).toFloat() / modifier)
@ -132,6 +134,52 @@ class SettingsFragment : PreferenceFragmentCompat() {
setDefaultValue(Locale.getDefault().language) setDefaultValue(Locale.getDefault().language)
} }
findPreference<ListPreference>("tmp." + Settings.VOLUME_UNIT)?.apply {
entries = Volume.values().map { getString(it.singular) }.toTypedArray()
entryValues = Volume.values().map { it.ordinal.toString() }.toTypedArray()
val unit = config.getInt(Settings.VOLUME_UNIT)
setOnPreferenceClickListener {
if (unit.value != null) setValueIndex(unit.value!!)
return@setOnPreferenceClickListener true
}
setOnPreferenceChangeListener { _, newValue ->
val nv = (newValue as String).toIntOrNull()
unit.value = nv
return@setOnPreferenceChangeListener false
}
}
findPreference<IntEditTextPreference>("tmp." + Settings.WATER_INTAKE_DAILY_GOAL)?.apply {
val value = config.getInt(Settings.WATER_INTAKE_DAILY_GOAL)
val unit = config.getInt(Settings.VOLUME_UNIT)
setOnPreferenceClickListener {
if (value.value == null) {
return@setOnPreferenceClickListener true
}
text = if (unit.value == null) {
value.value!!.toString()
} else {
Volume.find(unit.value!!).fromMilliliter(value.value!!).toString()
}
return@setOnPreferenceClickListener true
}
setOnPreferenceChangeListener { _, newValue ->
val nv = (newValue as String).toIntOrNull()
value.value = if (unit.value == null || nv == null) {
nv
} else {
Volume.find(unit.value!!).toMilliliter(nv)
}
return@setOnPreferenceChangeListener false
}
}
// Update App Locale // Update App Locale
languagesPreference?.setOnPreferenceChangeListener { _, newValue -> languagesPreference?.setOnPreferenceChangeListener { _, newValue ->
LocaleUtils.setLanguage(requireContext(), newValue as String) LocaleUtils.setLanguage(requireContext(), newValue as String)

View File

@ -13,7 +13,6 @@ import com.dzeio.openhealth.adapters.ItemAdapter
import com.dzeio.openhealth.core.BaseFragment import com.dzeio.openhealth.core.BaseFragment
import com.dzeio.openhealth.data.water.Water import com.dzeio.openhealth.data.water.Water
import com.dzeio.openhealth.databinding.FragmentMainWaterHomeBinding import com.dzeio.openhealth.databinding.FragmentMainWaterHomeBinding
import com.dzeio.openhealth.units.Units
import com.dzeio.openhealth.utils.ChartUtils import com.dzeio.openhealth.utils.ChartUtils
import dagger.hilt.android.AndroidEntryPoint import dagger.hilt.android.AndroidEntryPoint
import java.text.SimpleDateFormat import java.text.SimpleDateFormat
@ -27,17 +26,18 @@ class WaterHomeFragment :
override val bindingInflater: (LayoutInflater, ViewGroup?, Boolean) -> FragmentMainWaterHomeBinding = override val bindingInflater: (LayoutInflater, ViewGroup?, Boolean) -> FragmentMainWaterHomeBinding =
FragmentMainWaterHomeBinding::inflate FragmentMainWaterHomeBinding::inflate
private val adapter = ItemAdapter<Water>()
private val serie by lazy { BarSerie(binding.chart) }
override fun onViewCreated(view: View, savedInstanceState: Bundle?) { override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState) super.onViewCreated(view, savedInstanceState)
viewModel.init()
val recycler = binding.list val recycler = binding.list
val manager = LinearLayoutManager(requireContext()) recycler.layoutManager = LinearLayoutManager(requireContext())
recycler.layoutManager = manager
val adapter = ItemAdapter<Water>().apply { adapter.apply {
onItemClick = { onItemClick = {
findNavController().navigate( findNavController().navigate(
WaterHomeFragmentDirections.actionNavWaterHomeToNavWaterEdit( WaterHomeFragmentDirections.actionNavWaterHomeToNavWaterEdit(
@ -48,20 +48,16 @@ class WaterHomeFragment :
recycler.adapter = this recycler.adapter = this
} }
val chart = binding.chart binding.chart.apply {
serie
val serie = BarSerie(chart) ChartUtils.materielTheme(this, requireView())
chart.apply {
ChartUtils.materielTheme(chart, requireView())
yAxis.apply { yAxis.apply {
// onValueFormat setYMin(0f)
} }
xAxis.apply { xAxis.apply {
dataWidth = 604800000.0 dataWidth = 604800000.0
textPaint.textSize = 32f
scrollEnabled = true scrollEnabled = true
onValueFormat = onValueFormat@{ onValueFormat = onValueFormat@{
return@onValueFormat SimpleDateFormat( return@onValueFormat SimpleDateFormat(
@ -79,14 +75,33 @@ class WaterHomeFragment :
} }
viewModel.items.observe(viewLifecycleOwner) { list -> viewModel.items.observe(viewLifecycleOwner) { list ->
val unit = Units.Volume.MILLILITER updateData()
}
}
private fun updateData() {
val data = viewModel.items.value
val unit = viewModel.unit.value
if (unit == null) {
return
}
if (data.isNullOrEmpty()) {
adapter.clear()
serie.entries = arrayListOf()
binding.chart.refresh()
return
}
// Update adapter
adapter.set( adapter.set(
list.map { data.map {
ItemAdapter.Item( ItemAdapter.Item(
it, it,
getString( getString(
unit.unit, unit.unit,
unit.formatToString(it.value) unit.fromMilliliter(it.value)
), ),
it.formatTimestamp(), it.formatTimestamp(),
icon = R.drawable.ic_outline_edit_24 icon = R.drawable.ic_outline_edit_24
@ -94,22 +109,16 @@ class WaterHomeFragment :
} }
) )
if (list.isEmpty()) { // update graph
return@observe val dataset = data.map {
}
val dataset = list.map {
return@map Entry( return@map Entry(
it.timestamp.toDouble(), it.timestamp.toDouble(),
it.value.toFloat() unit.fromMilliliter(it.value).toFloat()
) )
} }
serie.entries = dataset as ArrayList<Entry> serie.entries = dataset as ArrayList<Entry>
chart.xAxis.x = chart.xAxis.getXMin() binding.chart.xAxis.x = 0.0
chart.xAxis.x = binding.chart.refresh()
chart.xAxis.getXMax() - chart.xAxis.dataWidth!! + chart.xAxis.dataWidth!! / 7
chart.refresh()
}
} }
} }

View File

@ -1,10 +1,14 @@
package com.dzeio.openhealth.ui.water package com.dzeio.openhealth.ui.water
import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.viewModelScope import androidx.lifecycle.viewModelScope
import com.dzeio.openhealth.Settings
import com.dzeio.openhealth.core.BaseViewModel import com.dzeio.openhealth.core.BaseViewModel
import com.dzeio.openhealth.data.water.Water import com.dzeio.openhealth.data.water.Water
import com.dzeio.openhealth.data.water.WaterRepository import com.dzeio.openhealth.data.water.WaterRepository
import com.dzeio.openhealth.units.Volume
import com.dzeio.openhealth.utils.Configuration
import dagger.hilt.android.lifecycle.HiltViewModel import dagger.hilt.android.lifecycle.HiltViewModel
import javax.inject.Inject import javax.inject.Inject
import kotlinx.coroutines.flow.collectLatest import kotlinx.coroutines.flow.collectLatest
@ -12,15 +16,25 @@ import kotlinx.coroutines.launch
@HiltViewModel @HiltViewModel
class WaterHomeViewModel@Inject internal constructor( class WaterHomeViewModel@Inject internal constructor(
private val waterRepository: WaterRepository private val waterRepository: WaterRepository,
config: Configuration
) : BaseViewModel() { ) : BaseViewModel() {
val items: MutableLiveData<List<Water>> = MutableLiveData() val items: MutableLiveData<List<Water>> = MutableLiveData()
fun init() { private val _unit = MutableLiveData<Volume?>()
val unit: LiveData<Volume?> = _unit
init {
viewModelScope.launch { viewModelScope.launch {
waterRepository.getWaters().collectLatest { waterRepository.getWaters().collectLatest {
items.postValue(it) items.postValue(it)
} }
} }
config.getInt((Settings.VOLUME_UNIT)).apply {
addObserver {
if (it === null) return@addObserver
_unit.postValue(Volume.find(it))
}
if (value != null) _unit.postValue(Volume.find(value!!))
}
} }
} }

View File

@ -7,6 +7,7 @@ import com.dzeio.openhealth.R
import com.dzeio.openhealth.Settings import com.dzeio.openhealth.Settings
import com.dzeio.openhealth.core.BaseDialog import com.dzeio.openhealth.core.BaseDialog
import com.dzeio.openhealth.databinding.DialogWaterSizeSelectorBinding import com.dzeio.openhealth.databinding.DialogWaterSizeSelectorBinding
import com.dzeio.openhealth.units.Volume
import dagger.hilt.android.AndroidEntryPoint import dagger.hilt.android.AndroidEntryPoint
@AndroidEntryPoint @AndroidEntryPoint
@ -30,14 +31,30 @@ class WaterSizeSelectorDialog :
} }
viewModel.cupSize.observe(this) { viewModel.cupSize.observe(this) {
var real = it ?: Settings.WATER_INTAKE_SIZE_DEFAULT setCustomSizeText()
binding.customSize.text = String.format(
getString(R.string.custom_amount),
"${real}ml"
)
if (binding.inputParent.visibility == View.GONE) {
binding.input.setText(real.toString())
} }
viewModel.unit.observe(this) {
if (it == null) {
return@observe
}
setCustomSizeText()
binding.size1000ml.text = getString(
it.unit,
it.fromMilliliter(1000)
)
binding.size100ml.text = getString(
it.unit,
it.fromMilliliter(100)
)
binding.size250ml.text = getString(
it.unit,
it.fromMilliliter(250)
)
binding.size500ml.text = getString(
it.unit,
it.fromMilliliter(500)
)
} }
binding.size100ml.setOnClickListener { binding.size100ml.setOnClickListener {
@ -61,10 +78,11 @@ class WaterSizeSelectorDialog :
setTextFieldStatus(true) setTextFieldStatus(true)
} }
binding.input.doOnTextChanged { text, start, before, count -> binding.input.doOnTextChanged { text, _, _, _ ->
val newSize = text.toString().toIntOrNull() val newSize = text.toString().toIntOrNull()
if (newSize != null) { if (newSize != null && binding.inputParent.visibility == View.VISIBLE) {
viewModel.setCupSize(newSize) val unit = viewModel.unit.value ?: Volume.MILLILITER
viewModel.setCupSize(unit.toMilliliter(newSize))
} }
} }
} }
@ -73,4 +91,22 @@ class WaterSizeSelectorDialog :
binding.inputParent.visibility = if (displayed) View.VISIBLE else View.GONE binding.inputParent.visibility = if (displayed) View.VISIBLE else View.GONE
binding.customSize.visibility = if (displayed) View.GONE else View.VISIBLE binding.customSize.visibility = if (displayed) View.GONE else View.VISIBLE
} }
private fun setCustomSizeText() {
val size = viewModel.cupSize.value ?: Settings.WATER_INTAKE_SIZE_DEFAULT
val unit = viewModel.unit.value ?: Volume.MILLILITER
val modified = unit.fromMilliliter(size)
binding.customSize.text = String.format(
getString(
R.string.custom_amount,
getString(unit.unit, modified)
)
)
if (binding.inputParent.visibility == View.GONE) {
binding.input.setText(modified.toString())
}
}
} }

View File

@ -1,7 +1,10 @@
package com.dzeio.openhealth.ui.water package com.dzeio.openhealth.ui.water
import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import com.dzeio.openhealth.Settings import com.dzeio.openhealth.Settings
import com.dzeio.openhealth.core.BaseViewModel import com.dzeio.openhealth.core.BaseViewModel
import com.dzeio.openhealth.units.Volume
import com.dzeio.openhealth.utils.Configuration import com.dzeio.openhealth.utils.Configuration
import dagger.hilt.android.lifecycle.HiltViewModel import dagger.hilt.android.lifecycle.HiltViewModel
import javax.inject.Inject import javax.inject.Inject
@ -12,8 +15,20 @@ class WaterSizeSelectorViewModel @Inject constructor(
) : BaseViewModel() { ) : BaseViewModel() {
val cupSize = settings.getInt(Settings.WATER_INTAKE_SIZE).toLiveData() val cupSize = settings.getInt(Settings.WATER_INTAKE_SIZE).toLiveData()
private val _unit = MutableLiveData<Volume?>()
val unit: LiveData<Volume?> = _unit
fun setCupSize(value: Int) { fun setCupSize(value: Int) {
settings.getInt(Settings.WATER_INTAKE_SIZE).value = value settings.getInt(Settings.WATER_INTAKE_SIZE).value = value
} }
init {
settings.getInt(Settings.VOLUME_UNIT).apply {
addObserver {
if (it === null) return@addObserver
_unit.postValue(Volume.find(it))
}
if (value != null) _unit.postValue(Volume.find(value!!))
}
}
} }

View File

@ -8,7 +8,7 @@ import com.dzeio.openhealth.core.BaseViewModel
import com.dzeio.openhealth.data.water.Water import com.dzeio.openhealth.data.water.Water
import com.dzeio.openhealth.data.weight.Weight import com.dzeio.openhealth.data.weight.Weight
import com.dzeio.openhealth.data.weight.WeightRepository import com.dzeio.openhealth.data.weight.WeightRepository
import com.dzeio.openhealth.units.Units import com.dzeio.openhealth.units.Mass
import com.dzeio.openhealth.utils.Configuration import com.dzeio.openhealth.utils.Configuration
import dagger.hilt.android.lifecycle.HiltViewModel import dagger.hilt.android.lifecycle.HiltViewModel
import javax.inject.Inject import javax.inject.Inject
@ -30,7 +30,7 @@ class EditWeightDialogViewModel @Inject internal constructor(
private val _weights = MutableLiveData<List<Weight>?>(null) private val _weights = MutableLiveData<List<Weight>?>(null)
private val _massUnit = MutableLiveData(Units.Mass.KILOGRAM) private val _massUnit = MutableLiveData(Mass.KILOGRAM)
init { init {
@ -41,13 +41,13 @@ class EditWeightDialogViewModel @Inject internal constructor(
} }
} }
config.getString(Settings.MASS_UNIT).apply { config.getInt(Settings.MASS_UNIT).apply {
addObserver { addObserver {
if (it == null) return@addObserver if (it == null) return@addObserver
_massUnit.postValue(Units.Mass.find(it)) _massUnit.postValue(Mass.find(it))
} }
if (value != null) { if (value != null) {
_massUnit.postValue(Units.Mass.find(value!!)) _massUnit.postValue(Mass.find(value!!))
} }
} }
} }

View File

@ -3,6 +3,7 @@ package com.dzeio.openhealth.ui.weight
import android.Manifest import android.Manifest
import android.content.SharedPreferences import android.content.SharedPreferences
import android.graphics.Paint import android.graphics.Paint
import android.os.Build
import android.os.Bundle import android.os.Bundle
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.Menu import android.view.Menu
@ -25,7 +26,7 @@ import com.dzeio.openhealth.adapters.ItemAdapter
import com.dzeio.openhealth.core.BaseFragment import com.dzeio.openhealth.core.BaseFragment
import com.dzeio.openhealth.data.weight.Weight import com.dzeio.openhealth.data.weight.Weight
import com.dzeio.openhealth.databinding.FragmentListWeightBinding import com.dzeio.openhealth.databinding.FragmentListWeightBinding
import com.dzeio.openhealth.units.Units import com.dzeio.openhealth.units.Mass
import com.dzeio.openhealth.utils.ChartUtils import com.dzeio.openhealth.utils.ChartUtils
import com.dzeio.openhealth.utils.PermissionsUtils import com.dzeio.openhealth.utils.PermissionsUtils
import com.google.android.material.color.MaterialColors import com.google.android.material.color.MaterialColors
@ -99,16 +100,22 @@ class ListWeightFragment :
} }
binding.bluetoothButton.setOnClickListener { binding.bluetoothButton.setOnClickListener {
val permissions = arrayOf( val permissions = arrayListOf(
Manifest.permission.BLUETOOTH, Manifest.permission.BLUETOOTH,
Manifest.permission.BLUETOOTH_CONNECT,
Manifest.permission.BLUETOOTH_SCAN,
Manifest.permission.ACCESS_FINE_LOCATION Manifest.permission.ACCESS_FINE_LOCATION
) )
val hasPermission = PermissionsUtils.hasPermission(requireContext(), permissions) if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
permissions.addAll(
arrayOf(
Manifest.permission.BLUETOOTH_CONNECT,
Manifest.permission.BLUETOOTH_SCAN
)
)
}
val hasPermission = PermissionsUtils.hasPermission(requireContext(), permissions.toTypedArray())
if (!hasPermission) { if (!hasPermission) {
button = binding.bluetoothButton button = binding.bluetoothButton
activityResult.launch(permissions) activityResult.launch(permissions.toTypedArray())
return@setOnClickListener return@setOnClickListener
} }
findNavController().navigate( findNavController().navigate(
@ -140,16 +147,20 @@ class ListWeightFragment :
updateGraph(list, it) updateGraph(list, it)
} }
viewModel.massUnit.observe(viewLifecycleOwner) { unit ->
binding.chart.yAxis.onValueFormat = { getString(unit.unit, unit.fromKilogram(it)) }
}
viewModel.weights.observe(viewLifecycleOwner) { list -> viewModel.weights.observe(viewLifecycleOwner) { list ->
if (list != null) { if (list != null) {
val unit = viewModel.massUnit.value ?: Units.Mass.KILOGRAM val unit = viewModel.massUnit.value ?: Mass.KILOGRAM
adapter.set( adapter.set(
list.map { list.map {
ItemAdapter.Item( ItemAdapter.Item(
it, it,
getString( getString(
unit.unit, unit.unit,
unit.formatToString(it.weight) it.weight
), ),
getString( getString(
R.string.weight_item, R.string.weight_item,
@ -185,8 +196,6 @@ class ListWeightFragment :
yAxis.apply { yAxis.apply {
setYMin(null) setYMin(null)
onValueFormat = { value -> "${value.toInt()}" }
} }
xAxis.apply { xAxis.apply {
@ -228,8 +237,6 @@ class ListWeightFragment :
val serie = chart.series[0] as LineSerie val serie = chart.series[0] as LineSerie
val avSerie = chart.series[1] as LineSerie val avSerie = chart.series[1] as LineSerie
val entries: ArrayList<Entry> = arrayListOf()
val previous = 5 val previous = 5
val next = 5 val next = 5
val averageList = arrayListOf<Entry>() val averageList = arrayListOf<Entry>()
@ -245,6 +252,13 @@ class ListWeightFragment :
) )
} }
val entries: ArrayList<Entry> = list.map {
Entry(
it.timestamp.toDouble(),
it.weight
)
} as ArrayList<Entry>
val direction = averageList.last().y - averageList[averageList.size - 2].y val direction = averageList.last().y - averageList[averageList.size - 2].y
chart.yAxis.clearLines() chart.yAxis.clearLines()
@ -257,39 +271,30 @@ class ListWeightFragment :
} }
val dt = averageList.last().x - averageList[list.size - previous].x val dt = averageList.last().x - averageList[list.size - previous].x
val timeUntilGoal = ((list.last().weight - goal) / abs(direction)).toLong() * dt val timeUntilGoal = ((entries.last().y - goal) / abs(direction)).toLong() * dt
binding.goalText.text = getString( binding.goalText.text = getString(
R.string.days_until_goal_is_achieved, R.string.days_until_goal_is_achieved,
(timeUntilGoal / 1000 / 60 / 60 / 24).toInt() (timeUntilGoal / 1000 / 60 / 60 / 24).toInt()
) )
averageList.add( entries.add(
Entry( Entry(
(list.last().timestamp + timeUntilGoal), (entries.last().x + timeUntilGoal),
goal goal
) )
) )
binding.goalText.visibility = View.VISIBLE binding.goalText.visibility = View.VISIBLE
} else { } else {
averageList.add( entries.add(
Entry( Entry(
averageList.last().x + chart.xAxis.dataWidth!!, entries.last().x + chart.xAxis.dataWidth!!,
averageList.last().y + direction * 7 * 4 entries.last().y + direction * 7 * 4
) )
) )
binding.goalText.visibility = View.GONE binding.goalText.visibility = View.GONE
} }
avSerie.entries = averageList avSerie.entries = averageList
list.forEach {
entries.add(
Entry(
it.timestamp.toDouble(),
it.weight
)
)
}
serie.entries = entries serie.entries = entries
if (list.isEmpty()) { if (list.isEmpty()) {

View File

@ -7,7 +7,7 @@ import com.dzeio.openhealth.Settings
import com.dzeio.openhealth.core.BaseViewModel import com.dzeio.openhealth.core.BaseViewModel
import com.dzeio.openhealth.data.weight.Weight import com.dzeio.openhealth.data.weight.Weight
import com.dzeio.openhealth.data.weight.WeightRepository import com.dzeio.openhealth.data.weight.WeightRepository
import com.dzeio.openhealth.units.Units import com.dzeio.openhealth.units.Mass
import com.dzeio.openhealth.utils.Configuration import com.dzeio.openhealth.utils.Configuration
import dagger.hilt.android.lifecycle.HiltViewModel import dagger.hilt.android.lifecycle.HiltViewModel
import javax.inject.Inject import javax.inject.Inject
@ -21,8 +21,8 @@ class ListWeightViewModel @Inject internal constructor(
settings: Configuration settings: Configuration
) : BaseViewModel() { ) : BaseViewModel() {
private val _massUnit = MutableLiveData(Units.Mass.KILOGRAM) private val _massUnit = MutableLiveData(Mass.KILOGRAM)
val massUnit: LiveData<Units.Mass> = _massUnit val massUnit: LiveData<Mass> = _massUnit
private val _goalWeight = settings.getFloat(Settings.WEIGHT_GOAL) private val _goalWeight = settings.getFloat(Settings.WEIGHT_GOAL)
val goalWeight = _goalWeight.toLiveData() val goalWeight = _goalWeight.toLiveData()
@ -37,13 +37,13 @@ class ListWeightViewModel @Inject internal constructor(
} }
} }
settings.getString(Settings.MASS_UNIT).apply { settings.getInt(Settings.MASS_UNIT).apply {
addObserver { addObserver {
if (it == null) return@addObserver if (it == null) return@addObserver
_massUnit.postValue(Units.Mass.find(it)) _massUnit.postValue(Mass.find(it))
} }
if (value != null) { if (value != null) {
_massUnit.postValue(Units.Mass.find(value!!)) _massUnit.postValue(Mass.find(value!!))
} }
} }
} }

View File

@ -25,6 +25,7 @@ class ScanScalesDialog :
override val bindingInflater: (LayoutInflater) -> DialogSearchBinding = override val bindingInflater: (LayoutInflater) -> DialogSearchBinding =
DialogSearchBinding::inflate DialogSearchBinding::inflate
@SuppressLint("MissingPermission")
override fun onBuilderInit(builder: MaterialAlertDialogBuilder) { override fun onBuilderInit(builder: MaterialAlertDialogBuilder) {
super.onBuilderInit(builder) super.onBuilderInit(builder)
@ -102,7 +103,7 @@ class ScanScalesDialog :
it, it,
it.name, it.name,
"${it.item!!.name} (${it.item!!.address})", "${it.item!!.name} (${it.item!!.address})",
icon = R.drawable.ic_baseline_add_24 icon = R.drawable.vector_add
) )
} }
) )

View File

@ -8,12 +8,12 @@ import com.dzeio.openhealth.core.BaseViewModel
import com.dzeio.openhealth.data.weight.Weight import com.dzeio.openhealth.data.weight.Weight
import com.dzeio.openhealth.data.weight.WeightRepository import com.dzeio.openhealth.data.weight.WeightRepository
import com.dzeio.openhealth.units.ActivityLevel import com.dzeio.openhealth.units.ActivityLevel
import com.dzeio.openhealth.units.Units import com.dzeio.openhealth.units.Mass
import com.dzeio.openhealth.utils.Configuration import com.dzeio.openhealth.utils.Configuration
import dagger.hilt.android.lifecycle.HiltViewModel import dagger.hilt.android.lifecycle.HiltViewModel
import javax.inject.Inject
import kotlinx.coroutines.flow.collectLatest import kotlinx.coroutines.flow.collectLatest
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import javax.inject.Inject
@HiltViewModel @HiltViewModel
class WeightDialogViewModel @Inject internal constructor( class WeightDialogViewModel @Inject internal constructor(
@ -33,7 +33,7 @@ class WeightDialogViewModel @Inject internal constructor(
val weight: LiveData<Weight?> = _weight val weight: LiveData<Weight?> = _weight
val format = val format =
Units.Mass.find(settings.getString(Settings.MASS_UNIT).value ?: Units.Mass.KILOGRAM.id) Mass.find(settings.getInt(Settings.MASS_UNIT).value ?: Mass.KILOGRAM.ordinal)
fun initWithWeight(id: Long?) { fun initWithWeight(id: Long?) {
viewModelScope.launch { viewModelScope.launch {

View File

@ -0,0 +1,48 @@
package com.dzeio.openhealth.units
import com.dzeio.openhealth.R
enum class Mass(
/**
* Value based on the Kilogram
*/
val modifier: Float,
val singular: Int,
val plural: Int,
val unit: Int
) {
KILOGRAM(
1f,
R.string.unit_mass_kilogram_name_singular,
R.string.unit_mass_kilogram_name_plural,
R.string.unit_mass_kilogram_unit
),
POUND(
2.204623f,
R.string.unit_mass_pound_name_singular,
R.string.unit_mass_pound_name_plural,
R.string.unit_mass_pound_unit
);
companion object {
fun find(index: Int): Mass {
return Mass.values().getOrNull(index) ?: KILOGRAM
}
}
fun format(value: Float): Float {
return value * modifier
}
/**
* transform the value from the specified unit to kilograms
*/
fun toKilogram(value: Float): Float = value / modifier
fun toKilogram(value: Int): Int = toKilogram(value.toFloat()).toInt()
/**
* transform the value from the specified unit to kilograms
*/
fun fromKilogram(value: Float): Float = value * modifier
fun fromKilogram(value: Int): Int = fromKilogram(value.toFloat()).toInt()
}

View File

@ -1,104 +0,0 @@
package com.dzeio.openhealth.units
import com.dzeio.openhealth.R
/**
* Object containing the differents units and how they are converted
*/
object Units {
/**
* the Mass Unit
*/
enum class Mass(
val id: String,
/**
* Value based on the Kilogram
*/
val modifier: Float,
val singular: Int,
val plural: Int,
val unit: Int
) {
KILOGRAM(
"kg",
1f,
R.string.unit_mass_kilogram_name_singular,
R.string.unit_mass_kilogram_name_plural,
R.string.unit_mass_kilogram_unit
),
POUND(
"lbs",
2.204623f,
R.string.unit_mass_pound_name_singular,
R.string.unit_mass_pound_name_plural,
R.string.unit_mass_pound_unit
);
companion object {
fun find(value: String): Mass {
return Mass.values().find {
it.id == value
} ?: KILOGRAM
}
}
fun format(value: Float): Float {
return value * modifier
}
/**
* Format the value and let the hundred of grams to be outputed
*/
fun formatToString(value: Float): String {
return String.format("%.1f", value * modifier)
}
}
/**
* the Volume unit
*/
enum class Volume(
val id: String,
/**
* Value based on the Milliliter
*/
val modifier: Float,
val singular: Int,
val plural: Int,
val unit: Int
) {
MILLILITER(
"ml",
1f,
R.string.unit_volume_milliliter_name_singular,
R.string.unit_volume_milliliter_name_plural,
R.string.unit_volume_milliliter_unit
),
IMPERIAL_OUNCE(
"ioz",
0.03519503f,
R.string.unit_volume_imperial_ounce_name_singular,
R.string.unit_volume_imperial_ounce_name_plural,
R.string.unit_volume_ounce_unit
),
US_OUNCE(
"uoz",
0.03381413f,
R.string.unit_volume_us_ounce_name_singular,
R.string.unit_volume_us_ounce_name_plural,
R.string.unit_volume_ounce_unit
);
fun formatToString(value: Int): String {
return String.format("%.0f", (value * modifier))
}
companion object {
fun find(value: String): Volume {
return Volume.values().find {
it.id == value
} ?: MILLILITER
}
}
}
}

View File

@ -0,0 +1,53 @@
package com.dzeio.openhealth.units
import com.dzeio.openhealth.R
/**
* the Volume unit
*/
enum class Volume(
/**
* Value based on the Milliliter
*/
val modifier: Float,
val singular: Int,
val plural: Int,
val unit: Int
) {
MILLILITER(
1f,
R.string.unit_volume_milliliter_name_singular,
R.string.unit_volume_milliliter_name_plural,
R.string.unit_volume_milliliter_unit
),
IMPERIAL_OUNCE(
0.03519503f,
R.string.unit_volume_imperial_ounce_name_singular,
R.string.unit_volume_imperial_ounce_name_plural,
R.string.unit_volume_ounce_unit
),
US_OUNCE(
0.03381413f,
R.string.unit_volume_us_ounce_name_singular,
R.string.unit_volume_us_ounce_name_plural,
R.string.unit_volume_ounce_unit
);
companion object {
fun find(index: Int): Volume {
return Volume.values().getOrNull(index) ?: MILLILITER
}
}
/**
* transform the value from the specified unit to kilograms
*/
fun toMilliliter(value: Float): Float = value / modifier
fun toMilliliter(value: Int): Int = toMilliliter(value.toFloat()).toInt()
/**
* transform the value from the specified unit to kilograms
*/
fun fromMilliliter(value: Float): Float = value * modifier
fun fromMilliliter(value: Int): Int = fromMilliliter(value.toFloat()).toInt()
}

View File

@ -2,7 +2,6 @@ package com.dzeio.openhealth.utils
import android.content.SharedPreferences import android.content.SharedPreferences
import androidx.core.content.edit import androidx.core.content.edit
import com.dzeio.openhealth.Application
import com.dzeio.openhealth.core.Observable import com.dzeio.openhealth.core.Observable
/** /**
@ -17,7 +16,7 @@ class Configuration(
private val cache: HashMap<String, Field<*>> = HashMap() private val cache: HashMap<String, Field<*>> = HashMap()
private companion object { private companion object {
const val TAG = "${Application.TAG}/Config" const val TAG = "Configuration"
} }
init { init {
@ -145,10 +144,12 @@ class Configuration(
) : Field<Int?>(defaultValue) { ) : Field<Int?>(defaultValue) {
override fun exists(): Boolean = prefs.contains(key) override fun exists(): Boolean = prefs.contains(key)
override fun internalGet(): Int? { override fun internalGet(): Int? {
if (exists()) { return try {
return prefs.getInt(key, 0) prefs.getInt(key, 0)
} catch (e: ClassCastException) {
val it = prefs.getString(key, "")
it?.toIntOrNull() ?: defaultValue
} }
return defaultValue
} }
override fun internalSet(value: Int?) = override fun internalSet(value: Int?) =
prefs.edit { if (value == null) remove(key) else putInt(key, value) } prefs.edit { if (value == null) remove(key) else putInt(key, value) }

View File

@ -0,0 +1,5 @@
<vector android:height="24dp" android:tint="?attr/colorControlNormal"
android:viewportHeight="24" android:viewportWidth="24"
android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
<path android:fillColor="@android:color/white" android:pathData="M19,13H5v-2h14v2z"/>
</vector>

View File

@ -29,7 +29,7 @@
<TextView <TextView
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="match_parent" android:layout_height="match_parent"
android:text="Activity" android:text="@string/activity"
/> />
</LinearLayout> </LinearLayout>
@ -69,7 +69,7 @@
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:paddingBottom="4dp" android:paddingBottom="4dp"
android:text="Steps" /> android:text="@string/steps" />
<TextView <TextView
android:layout_width="match_parent" android:layout_width="match_parent"
@ -94,13 +94,13 @@
android:layout_width="24dp" android:layout_width="24dp"
android:layout_height="24dp" android:layout_height="24dp"
android:layout_marginEnd="16dp" android:layout_marginEnd="16dp"
android:src="@drawable/ic_baseline_add_24" /> android:src="@drawable/vector_add" />
<TextView <TextView
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="match_parent" android:layout_height="match_parent"
android:text="Vitals" android:text="@string/vitals"
/> />
</LinearLayout> </LinearLayout>
@ -138,14 +138,14 @@
<TextView <TextView
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:text="Heart Rate" android:text="@string/heart_rate"
android:textColor="?attr/colorSurfaceInverse" /> android:textColor="?attr/colorSurfaceInverse" />
<TextView <TextView
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:text="Coming soon" android:text="@string/coming_soon"
android:textColor="?attr/colorSurfaceInverse" /> android:textColor="?attr/colorSurfaceInverse" />
</LinearLayout> </LinearLayout>
@ -169,7 +169,7 @@
<TextView <TextView
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="match_parent" android:layout_height="match_parent"
android:text="Measurements" android:text="@string/measurements"
/> />
</LinearLayout> </LinearLayout>
@ -207,7 +207,7 @@
<TextView <TextView
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:text="Weight" /> android:text="@string/weight" />
<TextView <TextView
android:layout_width="match_parent" android:layout_width="match_parent"
@ -256,13 +256,13 @@
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:textColor="?attr/colorSurfaceInverse" android:textColor="?attr/colorSurfaceInverse"
android:paddingBottom="4dp" android:paddingBottom="4dp"
android:text="Height" /> android:text="@string/height" />
<TextView <TextView
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:textColor="?attr/colorSurfaceInverse" android:textColor="?attr/colorSurfaceInverse"
android:text="Coming soon" /> android:text="@string/coming_soon" />
</LinearLayout> </LinearLayout>
</LinearLayout> </LinearLayout>
@ -285,7 +285,7 @@
<TextView <TextView
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="match_parent" android:layout_height="match_parent"
android:text="Food" android:text="@string/food"
/> />
</LinearLayout> </LinearLayout>
@ -323,7 +323,7 @@
<TextView <TextView
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:text="Water Intake" /> android:text="@string/water_intake" />
</LinearLayout> </LinearLayout>
</LinearLayout> </LinearLayout>
@ -364,7 +364,7 @@
<TextView <TextView
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:text="Food Calories" /> android:text="@string/food_consumption" />
</LinearLayout> </LinearLayout>

View File

@ -25,7 +25,7 @@
<com.google.android.material.card.MaterialCardView <com.google.android.material.card.MaterialCardView
style="?attr/materialCardViewFilledStyle" style="?attr/materialCardViewFilledStyle"
android:layout_width="0dp" android:layout_width="0dp"
android:layout_marginRight="8dp" android:layout_marginEnd="8dp"
android:layout_height="match_parent" android:layout_height="match_parent"
android:layout_weight="1"> android:layout_weight="1">
@ -72,7 +72,7 @@
android:layout_height="18dp" android:layout_height="18dp"
android:layout_marginEnd="8dp" android:layout_marginEnd="8dp"
android:layout_weight="1" android:layout_weight="1"
android:src="@drawable/ic_outline_hexagon_24" android:src="@drawable/vector_remove"
app:layout_constraintBottom_toBottomOf="@+id/linearLayout" app:layout_constraintBottom_toBottomOf="@+id/linearLayout"
app:layout_constraintEnd_toStartOf="@+id/linearLayout" app:layout_constraintEnd_toStartOf="@+id/linearLayout"
app:layout_constraintTop_toTopOf="@+id/linearLayout" /> app:layout_constraintTop_toTopOf="@+id/linearLayout" />
@ -103,6 +103,8 @@
/> />
<TextView <TextView
android:visibility="gone"
tools:visibility="visible"
android:id="@+id/fragment_home_water_total" android:id="@+id/fragment_home_water_total"
style="@style/TextAppearance.Material3.LabelMedium" style="@style/TextAppearance.Material3.LabelMedium"
android:layout_width="wrap_content" android:layout_width="wrap_content"
@ -120,7 +122,7 @@
android:layout_height="18dp" android:layout_height="18dp"
android:layout_marginStart="8dp" android:layout_marginStart="8dp"
android:layout_weight="1" android:layout_weight="1"
android:src="@drawable/ic_baseline_add_24" android:src="@drawable/vector_add"
app:layout_constraintBottom_toBottomOf="@+id/linearLayout" app:layout_constraintBottom_toBottomOf="@+id/linearLayout"
app:layout_constraintStart_toEndOf="@+id/linearLayout" app:layout_constraintStart_toEndOf="@+id/linearLayout"
app:layout_constraintTop_toTopOf="@+id/linearLayout" /> app:layout_constraintTop_toTopOf="@+id/linearLayout" />
@ -130,7 +132,7 @@
android:id="@+id/background" android:id="@+id/background"
android:layout_width="0dp" android:layout_width="0dp"
android:layout_height="0dp" android:layout_height="0dp"
android:src="@drawable/ic_outline_hexagon_24" tools:src="@drawable/ic_outline_hexagon_24"
app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintDimensionRatio="2:1" app:layout_constraintDimensionRatio="2:1"
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintEnd_toEndOf="parent"
@ -145,7 +147,7 @@
<com.google.android.material.card.MaterialCardView <com.google.android.material.card.MaterialCardView
style="?attr/materialCardViewFilledStyle" style="?attr/materialCardViewFilledStyle"
android:layout_width="0dp" android:layout_width="0dp"
android:layout_marginLeft="8dp" android:layout_marginStart="8dp"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_weight="1"> android:layout_weight="1">
@ -225,6 +227,8 @@
android:layout_height="match_parent" android:layout_height="match_parent"
android:text="@string/unit_volume_milliliter_unit" android:text="@string/unit_volume_milliliter_unit"
android:textAlignment="center" android:textAlignment="center"
android:visibility="gone"
tools:visibility="visible"
android:textColor="?attr/colorOnBackground" /> android:textColor="?attr/colorOnBackground" />
</LinearLayout> </LinearLayout>
@ -298,7 +302,7 @@
android:layout_width="24dp" android:layout_width="24dp"
android:layout_height="24dp" android:layout_height="24dp"
android:layout_marginEnd="16dp" android:layout_marginEnd="16dp"
android:src="@drawable/ic_baseline_add_24" /> android:src="@drawable/vector_add" />
<ImageView <ImageView
android:id="@+id/list_weight" android:id="@+id/list_weight"

View File

@ -5,7 +5,7 @@
<item <item
android:id="@+id/action_add" android:id="@+id/action_add"
android:visible="false" android:visible="false"
android:icon="@drawable/ic_baseline_add_24" android:icon="@drawable/vector_add"
android:title="@string/add" android:title="@string/add"
app:showAsAction="ifRoom" /> app:showAsAction="ifRoom" />

View File

@ -170,9 +170,9 @@
</dialog> </dialog>
<fragment <fragment
android:id="@+id/foodHomeFragment" android:id="@+id/foodHomeFragment"
tools:layout="@layout/fragment_food_home"
android:name="com.dzeio.openhealth.ui.food.FoodHomeFragment" android:name="com.dzeio.openhealth.ui.food.FoodHomeFragment"
android:label="FoodHomeFragment" > android:label="@string/food_consumption"
tools:layout="@layout/fragment_food_home">
<action <action
android:id="@+id/action_foodHomeFragment_to_nav_dialog_food_search" android:id="@+id/action_foodHomeFragment_to_nav_dialog_food_search"
app:destination="@id/nav_dialog_food_search" /> app:destination="@id/nav_dialog_food_search" />

View File

@ -27,7 +27,7 @@
<string name="tagline">Ton Application de santé libre, open source et respectueuse de la vie privée</string> <string name="tagline">Ton Application de santé libre, open source et respectueuse de la vie privée</string>
<string name="add">Ajouter</string> <string name="add">Ajouter</string>
<string name="about">A Propos</string> <string name="about">A Propos</string>
<string name="version_number" formatted="false">Version %1$</string> <string name="version_number" formatted="false">Version %1$s</string>
<string name="about_star_on_github">Mettez une étoile sur Github</string> <string name="about_star_on_github">Mettez une étoile sur Github</string>
<string name="contact_us">Contactez-nous</string> <string name="contact_us">Contactez-nous</string>
<string name="licenses">Licenses</string> <string name="licenses">Licenses</string>
@ -40,7 +40,7 @@
<string name="edit_goal">Modifier l\'objectif</string> <string name="edit_goal">Modifier l\'objectif</string>
<string name="edit_daily_goal">Modifier le but journalier</string> <string name="edit_daily_goal">Modifier le but journalier</string>
<string name="permission_declined">Vous avez décliné une permission, vous ne pouvez pas utiliser cette extension suaf si vous réactivez la permission manuellement</string> <string name="permission_declined">Vous avez décliné une permission, vous ne pouvez pas utiliser cette extension suaf si vous réactivez la permission manuellement</string>
<string name="menu_steps">Pas</string> <string name="steps">Pas</string>
<string name="weight_current">Poid actuel: %1$s%2$s</string> <string name="weight_current">Poid actuel: %1$s%2$s</string>
<string name="delete">Supprimer</string> <string name="delete">Supprimer</string>
<string name="close">Fermer</string> <string name="close">Fermer</string>
@ -57,7 +57,7 @@
<string name="connectivity_error">Il semplerais que nous ne pouvons pas communiquer avec OpenFoodFact, Merci de re-essayer plus tard</string> <string name="connectivity_error">Il semplerais que nous ne pouvons pas communiquer avec OpenFoodFact, Merci de re-essayer plus tard</string>
<string name="bluetooth_scale">Balance bluetooth</string> <string name="bluetooth_scale">Balance bluetooth</string>
<string name="sync">Synchroniser</string> <string name="sync">Synchroniser</string>
<string name="searching_scales">Recherche de balances connecté...</string> <string name="searching_scales">Recherche de balances connecté</string>
<string name="import_export_data">Importer &amp; Exporter les données de l\'application.</string> <string name="import_export_data">Importer &amp; Exporter les données de l\'application.</string>
<string name="export">Exporter</string> <string name="export">Exporter</string>
<string name="importTxt">Importer</string> <string name="importTxt">Importer</string>
@ -78,4 +78,12 @@
<item>Femme</item> <item>Femme</item>
<item>Homme</item> <item>Homme</item>
</string-array> </string-array>
<string name="activity">Acitivité</string>
<string name="vitals">signes vitaux</string>
<string name="heart_rate">Rythme cardiaque</string>
<string name="coming_soon">À venir</string>
<string name="measurements">Mesurements</string>
<string name="height">Taille</string>
<string name="food">Nouriturre</string>
<string name="food_consumption">Consomation Alimentaire</string>
</resources> </resources>

View File

@ -13,21 +13,21 @@
<!-- Units --> <!-- Units -->
<string name="unit_mass_kilogram_name_singular">Kilogram</string> <string name="unit_mass_kilogram_name_singular">Kilogram</string>
<string name="unit_mass_kilogram_name_plural">Kilograms</string> <string name="unit_mass_kilogram_name_plural">Kilograms</string>
<string name="unit_mass_kilogram_unit" translatable="false">%1$skg</string> <string name="unit_mass_kilogram_unit" translatable="false">%1$.2fkg</string>
<string name="unit_mass_pound_name_singular">Pound</string> <string name="unit_mass_pound_name_singular">Pound</string>
<string name="unit_mass_pound_name_plural">Pounds</string> <string name="unit_mass_pound_name_plural">Pounds</string>
<string name="unit_mass_pound_unit" translatable="false">%1$slbs</string> <string name="unit_mass_pound_unit" translatable="false">%1$.2flbs</string>
<string name="unit_volume_milliliter_name_singular">Milliliter</string> <string name="unit_volume_milliliter_name_singular">Milliliter</string>
<string name="unit_volume_milliliter_name_plural">Milliliters</string> <string name="unit_volume_milliliter_name_plural">Milliliters</string>
<string name="unit_volume_milliliter_unit" translatable="false">%1$sml</string> <string name="unit_volume_milliliter_unit" translatable="false">%1$dml</string>
<string name="unit_volume_imperial_ounce_name_singular">Imperial Ounce</string> <string name="unit_volume_imperial_ounce_name_singular">Imperial Ounce</string>
<string name="unit_volume_imperial_ounce_name_plural">Imperial Ounces</string> <string name="unit_volume_imperial_ounce_name_plural">Imperial Ounces</string>
<string name="unit_volume_us_ounce_name_singular">US Ounce</string> <string name="unit_volume_us_ounce_name_singular">US Ounce</string>
<string name="unit_volume_us_ounce_name_plural">US Ounces</string> <string name="unit_volume_us_ounce_name_plural">US Ounces</string>
<string name="unit_volume_ounce_unit" translatable="false">%1$soz</string> <string name="unit_volume_ounce_unit" translatable="false">%1$doz</string>
<string name="languages">Languages</string> <string name="languages">Languages</string>
<string name="settings_global">Global Settings</string> <string name="settings_global">Global Settings</string>
<string name="weight">Weight</string> <string name="weight">Weight</string>
@ -51,7 +51,7 @@
<string name="edit_goal">Modify Goal</string> <string name="edit_goal">Modify Goal</string>
<string name="permission_declined">You declined a permission, you can\'t use this extension unless you enable it manually</string> <string name="permission_declined">You declined a permission, you can\'t use this extension unless you enable it manually</string>
<string name="edit_daily_goal">Modifiy daily goal</string> <string name="edit_daily_goal">Modifiy daily goal</string>
<string name="menu_steps">Steps</string> <string name="steps">Steps</string>
<string name="weight_current">Current weight: %1$s</string> <string name="weight_current">Current weight: %1$s</string>
<string name="delete">Delete</string> <string name="delete">Delete</string>
<string name="close">Close</string> <string name="close">Close</string>
@ -79,6 +79,14 @@
<string name="import_export">Import/Export</string> <string name="import_export">Import/Export</string>
<string name="days_until_goal_is_achieved">Days until goal is achieved: ~%1$d days</string> <string name="days_until_goal_is_achieved">Days until goal is achieved: ~%1$d days</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 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 name="activity">Activity</string>
<string name="vitals">Vitals</string>
<string name="heart_rate">Heart Rate</string>
<string name="coming_soon">Coming soon</string>
<string name="measurements">Measurements</string>
<string name="height">Height</string>
<string name="food">Food</string>
<string name="food_consumption">Food Consumption</string>
<string-array name="activity_levels"> <string-array name="activity_levels">
<item>Bedridden</item> <item>Bedridden</item>
<item>Sedentary</item> <item>Sedentary</item>

View File

@ -53,14 +53,10 @@
android:key="water_hourly_notification" android:key="water_hourly_notification"
android:title="Enable Hourly Notification" /> android:title="Enable Hourly Notification" />
<ListPreference <ListPreference
android:defaultValue="ml" android:key="tmp.com.dzeio.open-health.volume-unit"
android:entries="@array/volume_units"
android:entryValues="@array/volume_units"
android:key="water_unit"
android:title="Volume Unit" /> android:title="Volume Unit" />
<EditTextPreference <com.dzeio.openhealth.utils.fields.IntEditTextPreference
android:defaultValue="2700" android:key="tmp.com.dzeio.open-health.water.daily"
android:key="water_intake"
android:selectAllOnFocus="true" android:selectAllOnFocus="true"
android:singleLine="true" android:singleLine="true"
android:inputType="number" android:inputType="number"
@ -70,7 +66,7 @@
<PreferenceCategory android:title="Steps settings"> <PreferenceCategory android:title="Steps settings">
<com.dzeio.openhealth.utils.fields.IntEditTextPreference <com.dzeio.openhealth.utils.fields.IntEditTextPreference
android:inputType="number" android:inputType="number"
android:key="com.dzeio.open-health.steps.goal-daily" android:key="tmp.com.dzeio.open-health.steps.goal-daily"
android:selectAllOnFocus="true" android:selectAllOnFocus="true"
android:singleLine="true" android:singleLine="true"
android:title="Number of steps each days" /> android:title="Number of steps each days" />