mirror of
https://github.com/dzeiocom/OpenHealth.git
synced 2025-04-22 19:02:16 +00:00
feat(app): Add weight goal #88
This commit is contained in:
parent
218d23f223
commit
1e0988d5b2
@ -16,4 +16,10 @@ object Settings {
|
||||
*/
|
||||
const val APP_LANGUAGE = "com.dzeio.open-health.app.language"
|
||||
|
||||
}
|
||||
/***************************
|
||||
* Weight related settings *
|
||||
***************************/
|
||||
|
||||
const val WEIGHT_GOAL = "com.dzeio.open-health.weight.goal"
|
||||
|
||||
}
|
||||
|
@ -1,61 +1,17 @@
|
||||
package com.dzeio.openhealth.core
|
||||
|
||||
import android.app.Dialog
|
||||
import android.os.Bundle
|
||||
import android.view.LayoutInflater
|
||||
import androidx.appcompat.app.AlertDialog
|
||||
import androidx.fragment.app.DialogFragment
|
||||
import androidx.lifecycle.ViewModelProvider
|
||||
import androidx.viewbinding.ViewBinding
|
||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||
|
||||
abstract class BaseDialog<VM : BaseViewModel, VB : ViewBinding>(private val viewModelClass: Class<VM>) : DialogFragment() {
|
||||
/**
|
||||
* Base dialog
|
||||
*
|
||||
* note: Dialog crash app with viewmodel error? add @AndroidEntryPoint
|
||||
*/
|
||||
abstract class BaseDialog<VM : BaseViewModel, VB : ViewBinding>(private val viewModelClass: Class<VM>) :
|
||||
BaseSimpleDialog<VB>() {
|
||||
|
||||
val viewModel by lazy {
|
||||
ViewModelProvider(this)[viewModelClass]
|
||||
}
|
||||
|
||||
private var _binding: VB? = null
|
||||
val binding get() = _binding!!
|
||||
|
||||
/**
|
||||
* Setup everything!
|
||||
*/
|
||||
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
|
||||
return activity?.let { act ->
|
||||
val builder = MaterialAlertDialogBuilder(requireContext())
|
||||
|
||||
_binding = bindingInflater(act.layoutInflater)
|
||||
|
||||
builder.setView(binding.root)
|
||||
|
||||
onBuilderInit(builder)
|
||||
|
||||
val dialog = builder.create()
|
||||
|
||||
onDialogInit(dialog)
|
||||
|
||||
onCreated()
|
||||
|
||||
dialog
|
||||
} ?: throw IllegalStateException("Activity cannot be null")
|
||||
}
|
||||
|
||||
open fun onBuilderInit(builder: MaterialAlertDialogBuilder): Unit {}
|
||||
|
||||
open fun onDialogInit(dialog: AlertDialog): Unit {}
|
||||
|
||||
open fun onCreated(): Unit {}
|
||||
|
||||
/**
|
||||
* Function to inflate the Fragment Bindings
|
||||
*/
|
||||
abstract val bindingInflater: (LayoutInflater) -> VB
|
||||
|
||||
/**
|
||||
* Destroy binding
|
||||
*/
|
||||
override fun onDestroyView() {
|
||||
super.onDestroyView()
|
||||
_binding = null
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,57 @@
|
||||
package com.dzeio.openhealth.core
|
||||
|
||||
import android.app.Dialog
|
||||
import android.os.Bundle
|
||||
import android.view.LayoutInflater
|
||||
import androidx.appcompat.app.AlertDialog
|
||||
import androidx.fragment.app.DialogFragment
|
||||
import androidx.viewbinding.ViewBinding
|
||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||
|
||||
abstract class BaseSimpleDialog<VB : ViewBinding> : DialogFragment() {
|
||||
|
||||
private var _binding: VB? = null
|
||||
val binding get() = _binding!!
|
||||
|
||||
/**
|
||||
* Setup everything!
|
||||
*/
|
||||
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
|
||||
return activity?.let { act ->
|
||||
val builder = MaterialAlertDialogBuilder(requireContext())
|
||||
|
||||
_binding = bindingInflater(act.layoutInflater)
|
||||
|
||||
builder.setView(binding.root)
|
||||
|
||||
onBuilderInit(builder)
|
||||
|
||||
val dialog = builder.create()
|
||||
|
||||
onDialogInit(dialog)
|
||||
|
||||
onCreated()
|
||||
|
||||
dialog
|
||||
} ?: throw IllegalStateException("Activity cannot be null")
|
||||
}
|
||||
|
||||
open fun onBuilderInit(builder: MaterialAlertDialogBuilder) {}
|
||||
|
||||
open fun onDialogInit(dialog: AlertDialog) {}
|
||||
|
||||
open fun onCreated() {}
|
||||
|
||||
/**
|
||||
* Function to inflate the Fragment Bindings
|
||||
*/
|
||||
abstract val bindingInflater: (LayoutInflater) -> VB
|
||||
|
||||
/**
|
||||
* Destroy binding
|
||||
*/
|
||||
override fun onDestroyView() {
|
||||
super.onDestroyView()
|
||||
_binding = null
|
||||
}
|
||||
}
|
48
app/src/main/java/com/dzeio/openhealth/core/Observable.kt
Normal file
48
app/src/main/java/com/dzeio/openhealth/core/Observable.kt
Normal file
@ -0,0 +1,48 @@
|
||||
package com.dzeio.openhealth.core
|
||||
|
||||
import androidx.lifecycle.LiveData
|
||||
import androidx.lifecycle.MutableLiveData
|
||||
|
||||
open class Observable<T>(baseValue: T) {
|
||||
|
||||
private val functionObservers: ArrayList<(T) -> Unit> = ArrayList()
|
||||
|
||||
|
||||
fun addObserver(fn: (T) -> Unit) {
|
||||
if (!functionObservers.contains(fn)) {
|
||||
functionObservers.add(fn)
|
||||
}
|
||||
}
|
||||
|
||||
fun removeObserver(fn: (T) -> Unit) {
|
||||
if (functionObservers.contains(fn)) {
|
||||
functionObservers.remove(fn)
|
||||
}
|
||||
}
|
||||
|
||||
open var value: T = baseValue
|
||||
set(value) {
|
||||
field = value
|
||||
notifyObservers()
|
||||
}
|
||||
|
||||
fun notifyObservers() {
|
||||
|
||||
// Notify Functions
|
||||
for (fn in functionObservers) {
|
||||
notifyObserver(fn)
|
||||
}
|
||||
}
|
||||
|
||||
fun notifyObserver(observer: (T) -> Unit) {
|
||||
observer.invoke(value)
|
||||
}
|
||||
|
||||
fun toLiveData(): LiveData<T> {
|
||||
val ld = MutableLiveData(value)
|
||||
addObserver {
|
||||
ld.postValue(it)
|
||||
}
|
||||
return ld
|
||||
}
|
||||
}
|
@ -3,6 +3,7 @@ package com.dzeio.openhealth.di
|
||||
import android.content.Context
|
||||
import android.content.SharedPreferences
|
||||
import androidx.preference.PreferenceManager
|
||||
import com.dzeio.openhealth.utils.Configuration
|
||||
import dagger.Module
|
||||
import dagger.Provides
|
||||
import dagger.hilt.InstallIn
|
||||
@ -19,4 +20,10 @@ class SystemModule {
|
||||
fun provideSettings(@ApplicationContext context: Context): SharedPreferences {
|
||||
return PreferenceManager.getDefaultSharedPreferences(context)
|
||||
}
|
||||
|
||||
@Singleton
|
||||
@Provides
|
||||
fun provideConfig(sharedPreferences: SharedPreferences): Configuration {
|
||||
return Configuration(sharedPreferences)
|
||||
}
|
||||
}
|
||||
|
@ -136,8 +136,10 @@ object WeightChart {
|
||||
limit.lineWidth = 1f
|
||||
limit.textColor = Color.BLACK
|
||||
|
||||
axisRight.removeAllLimitLines()
|
||||
axisRight.addLimitLine(limit)
|
||||
}
|
||||
invalidate()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -108,6 +108,15 @@ class HomeFragment : BaseFragment<HomeViewModel, FragmentHomeBinding>(HomeViewMo
|
||||
updateWater(0)
|
||||
}
|
||||
}
|
||||
|
||||
viewModel.goalWeight.observe(viewLifecycleOwner) {
|
||||
lifecycleScope.launchWhenStarted {
|
||||
viewModel.fetchWeights().collectLatest {
|
||||
updateGraph(it)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
private fun updateGraph(list: List<Weight>) {
|
||||
@ -116,7 +125,7 @@ class HomeFragment : BaseFragment<HomeViewModel, FragmentHomeBinding>(HomeViewMo
|
||||
requireView(),
|
||||
list,
|
||||
viewModel.weightUnit,
|
||||
viewModel.goalWeight?.toFloat()
|
||||
viewModel.goalWeight.value
|
||||
)
|
||||
|
||||
// legend.apply {
|
||||
|
@ -6,22 +6,25 @@ import androidx.lifecycle.LiveData
|
||||
import androidx.lifecycle.MutableLiveData
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import com.dzeio.openhealth.Application.Companion.TAG
|
||||
import com.dzeio.openhealth.Settings
|
||||
import com.dzeio.openhealth.core.BaseViewModel
|
||||
import com.dzeio.openhealth.data.water.Water
|
||||
import com.dzeio.openhealth.data.water.WaterRepository
|
||||
import com.dzeio.openhealth.data.weight.Weight
|
||||
import com.dzeio.openhealth.data.weight.WeightRepository
|
||||
import com.dzeio.openhealth.units.UnitFactory
|
||||
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 HomeViewModel @Inject internal constructor(
|
||||
private val weightRepository: WeightRepository,
|
||||
private val waterRepository: WaterRepository,
|
||||
settings: SharedPreferences
|
||||
settings: SharedPreferences,
|
||||
config: Configuration
|
||||
) : BaseViewModel() {
|
||||
|
||||
private val _water = MutableLiveData<Water?>(null)
|
||||
@ -35,8 +38,7 @@ class HomeViewModel @Inject internal constructor(
|
||||
var weightUnit =
|
||||
UnitFactory.mass(settings.getString("weight_unit", "kilogram") ?: "kilogram")
|
||||
|
||||
val goalWeight: Int? =
|
||||
(settings.getString("weight_goal", null)?.toIntOrNull())
|
||||
val goalWeight = config.getFloat(Settings.WEIGHT_GOAL).toLiveData()
|
||||
|
||||
val dailyWaterIntake: Int =
|
||||
((settings.getString("water_intake", "1200")?.toFloatOrNull() ?: 1200f) * waterUnit.modifier)
|
||||
|
@ -20,7 +20,7 @@ import java.util.Locale
|
||||
class SettingsFragment : PreferenceFragmentCompat() {
|
||||
|
||||
private companion object {
|
||||
const val TAG = "${Application.TAG}/SttngsFrgmnt"
|
||||
const val TAG = "${Application.TAG}/SettingsFragment"
|
||||
}
|
||||
|
||||
val settings: SharedPreferences by lazy {
|
||||
@ -31,12 +31,12 @@ class SettingsFragment : PreferenceFragmentCompat() {
|
||||
setPreferencesFromResource(R.xml.preferences, rootKey)
|
||||
|
||||
// Force only numbers on Goal
|
||||
val weightGoal = findPreference<EditTextPreference>("weight_goal")
|
||||
val weightGoal = findPreference<EditTextPreference>(Settings.WEIGHT_GOAL)
|
||||
weightGoal?.apply {
|
||||
setOnBindEditTextListener {
|
||||
it.inputType = InputType.TYPE_CLASS_NUMBER
|
||||
}
|
||||
val value = settings.getString("weight_goal", null)
|
||||
val value = settings.getString(Settings.WEIGHT_GOAL, null)
|
||||
val modifier = UnitFactory.mass(settings.getString("weight_unit", null) ?: "kilogram")
|
||||
if (value != null && value.isNotEmpty()) {
|
||||
text = (value.toFloat() * modifier.modifier).toString()
|
||||
@ -45,7 +45,7 @@ class SettingsFragment : PreferenceFragmentCompat() {
|
||||
val alue = ((newValue as String).toInt() / modifier.modifier).toInt().toString()
|
||||
settings.edit()
|
||||
.putString(
|
||||
"weight_goal",
|
||||
Settings.WEIGHT_GOAL,
|
||||
alue
|
||||
)
|
||||
.apply()
|
||||
@ -61,7 +61,7 @@ class SettingsFragment : PreferenceFragmentCompat() {
|
||||
setOnPreferenceChangeListener { _, newValue ->
|
||||
val unit = settings.getString("weight_unit", "kilogram")
|
||||
?: return@setOnPreferenceChangeListener true
|
||||
val goal = settings.getString("weight_goal", null)
|
||||
val goal = settings.getString(Settings.WEIGHT_GOAL, null)
|
||||
?: return@setOnPreferenceChangeListener true
|
||||
val modifier = UnitFactory.mass(newValue as String)
|
||||
val oldModifier = UnitFactory.mass(unit)
|
||||
@ -69,7 +69,7 @@ class SettingsFragment : PreferenceFragmentCompat() {
|
||||
(goal.toFloat() / oldModifier.modifier * modifier.modifier).toInt().toString()
|
||||
settings.edit()
|
||||
.putString(
|
||||
"weight_goal",
|
||||
Settings.WEIGHT_GOAL,
|
||||
value
|
||||
)
|
||||
.apply()
|
||||
|
@ -10,10 +10,10 @@ import com.dzeio.openhealth.databinding.DialogAddWeightBinding
|
||||
import com.dzeio.openhealth.ui.home.HomeViewModel
|
||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||
import dagger.hilt.android.AndroidEntryPoint
|
||||
import kotlinx.coroutines.flow.collect
|
||||
|
||||
@AndroidEntryPoint
|
||||
class AddWeightDialog : BaseDialog<HomeViewModel, DialogAddWeightBinding>(HomeViewModel::class.java) {
|
||||
class AddWeightDialog :
|
||||
BaseDialog<HomeViewModel, DialogAddWeightBinding>(HomeViewModel::class.java) {
|
||||
|
||||
override val bindingInflater: (LayoutInflater) -> DialogAddWeightBinding = DialogAddWeightBinding::inflate
|
||||
|
||||
|
@ -2,7 +2,11 @@ package com.dzeio.openhealth.ui.weight
|
||||
|
||||
import android.content.SharedPreferences
|
||||
import android.os.Bundle
|
||||
import android.view.*
|
||||
import android.view.LayoutInflater
|
||||
import android.view.Menu
|
||||
import android.view.MenuItem
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import androidx.navigation.fragment.findNavController
|
||||
import androidx.preference.PreferenceManager
|
||||
@ -36,10 +40,16 @@ class ListWeightFragment :
|
||||
// FIXME: deprecated
|
||||
setHasOptionsMenu(true)
|
||||
|
||||
if (viewModel.goalWeight != null) {
|
||||
if (viewModel.goalWeight.value != null) {
|
||||
binding.goalButton.setText(R.string.edit_goal)
|
||||
}
|
||||
|
||||
binding.goalButton.setOnClickListener {
|
||||
findNavController().navigate(
|
||||
ListWeightFragmentDirections.actionNavListWeightToNavWeightDialog(WeightDialog.DialogTypes.EDIT_GOAL.ordinal)
|
||||
)
|
||||
}
|
||||
|
||||
val recycler = binding.list
|
||||
|
||||
val manager = LinearLayoutManager(requireContext())
|
||||
@ -70,6 +80,14 @@ class ListWeightFragment :
|
||||
}
|
||||
}
|
||||
|
||||
viewModel.goalWeight.observe(viewLifecycleOwner) {
|
||||
viewLifecycleOwner.lifecycleScope.launchWhenCreated {
|
||||
viewModel.fetchWeights().collectLatest {
|
||||
updateGraph(it)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
GraphUtils.lineChartSetup(
|
||||
binding.chart,
|
||||
MaterialColors.getColor(
|
||||
@ -89,7 +107,7 @@ class ListWeightFragment :
|
||||
requireView(),
|
||||
list,
|
||||
viewModel.weightUnit,
|
||||
viewModel.goalWeight?.toFloat(),
|
||||
viewModel.goalWeight.value,
|
||||
false
|
||||
)
|
||||
}
|
||||
|
@ -1,23 +1,29 @@
|
||||
package com.dzeio.openhealth.ui.weight
|
||||
|
||||
import android.content.SharedPreferences
|
||||
import com.dzeio.openhealth.Settings
|
||||
import com.dzeio.openhealth.core.BaseViewModel
|
||||
import com.dzeio.openhealth.data.weight.WeightRepository
|
||||
import com.dzeio.openhealth.units.UnitFactory
|
||||
import com.dzeio.openhealth.utils.Configuration
|
||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||
import javax.inject.Inject
|
||||
|
||||
@HiltViewModel
|
||||
class ListWeightViewModel @Inject internal constructor(
|
||||
private val weightRepository: WeightRepository,
|
||||
settings: SharedPreferences
|
||||
private val settings: Configuration
|
||||
) : BaseViewModel() {
|
||||
|
||||
var weightUnit =
|
||||
UnitFactory.mass(settings.getString("weight_unit", "kilogram") ?: "kilogram")
|
||||
private val _goalWeight = settings.getFloat(Settings.WEIGHT_GOAL)
|
||||
|
||||
val goalWeight: Float? =
|
||||
(settings.getString("weight_goal", null)?.toFloatOrNull())
|
||||
var weightUnit =
|
||||
UnitFactory.mass(settings.getString("weight_unit").value ?: "kilogram")
|
||||
|
||||
val goalWeight = _goalWeight.toLiveData()
|
||||
|
||||
fun fetchWeights() = weightRepository.getWeights()
|
||||
|
||||
fun setWeightGoal(value: Float) {
|
||||
_goalWeight.value = value
|
||||
}
|
||||
}
|
||||
|
111
app/src/main/java/com/dzeio/openhealth/ui/weight/WeightDialog.kt
Normal file
111
app/src/main/java/com/dzeio/openhealth/ui/weight/WeightDialog.kt
Normal file
@ -0,0 +1,111 @@
|
||||
package com.dzeio.openhealth.ui.weight
|
||||
|
||||
import android.graphics.drawable.Drawable
|
||||
import android.util.Log
|
||||
import android.view.LayoutInflater
|
||||
import androidx.navigation.fragment.navArgs
|
||||
import com.dzeio.openhealth.R
|
||||
import com.dzeio.openhealth.core.BaseDialog
|
||||
import com.dzeio.openhealth.databinding.DialogAddWeightBinding
|
||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||
import dagger.hilt.android.AndroidEntryPoint
|
||||
|
||||
@AndroidEntryPoint
|
||||
class WeightDialog :
|
||||
BaseDialog<WeightDialogViewModel, DialogAddWeightBinding>(WeightDialogViewModel::class.java) {
|
||||
|
||||
private val args: WeightDialogArgs by navArgs()
|
||||
|
||||
override val bindingInflater: (LayoutInflater) -> DialogAddWeightBinding =
|
||||
DialogAddWeightBinding::inflate
|
||||
|
||||
override fun onBuilderInit(builder: MaterialAlertDialogBuilder) {
|
||||
super.onBuilderInit(builder)
|
||||
|
||||
val dialogType = DialogTypes.values()[args.dialogType]
|
||||
|
||||
builder.apply {
|
||||
setTitle(dialogType.title)
|
||||
setIcon(dialogType.icon)
|
||||
setPositiveButton(R.string.validate) { dialog, _ ->
|
||||
runAction(0)
|
||||
dialog.dismiss()
|
||||
}
|
||||
setNegativeButton(R.string.cancel) { dialog, _ ->
|
||||
runAction(1)
|
||||
dialog.cancel()
|
||||
}
|
||||
if (dialogType.thirdText != null) {
|
||||
setNeutralButton(dialogType.thirdText) { dialog, _ ->
|
||||
runAction(2)
|
||||
dialog.dismiss()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun onCreated() {
|
||||
super.onCreated()
|
||||
setValue(0f)
|
||||
|
||||
if (args.dialogType == DialogTypes.EDIT_GOAL.ordinal) {
|
||||
Log.d("TAG", viewModel.toString())
|
||||
Log.d("TAG", viewModel.goalWeight.value.toString())
|
||||
viewModel.goalWeight.observe(this) {
|
||||
if (it != null) setValue(it)
|
||||
}
|
||||
}
|
||||
|
||||
// init form
|
||||
binding.kg.maxValue = 999
|
||||
binding.kg.minValue = 0
|
||||
|
||||
binding.gram.maxValue = 9
|
||||
binding.gram.minValue = 0
|
||||
|
||||
}
|
||||
|
||||
private fun setValue(value: Float) {
|
||||
Log.d("TAG", "Setting dialog value to $value")
|
||||
val kg = value.toInt()
|
||||
val g = (value - kg) * 10
|
||||
|
||||
binding.kg.value = kg
|
||||
binding.gram.value = g.toInt()
|
||||
}
|
||||
|
||||
private fun getValue(): Float {
|
||||
var finalValue: Float = binding.kg.value.toFloat()
|
||||
finalValue += binding.gram.value.toFloat() / 10
|
||||
return finalValue
|
||||
}
|
||||
|
||||
/**
|
||||
* @param click 0 = validate, 1 = cancel, 2 = third option
|
||||
*/
|
||||
private fun runAction(click: Int) {
|
||||
when (DialogTypes.values()[args.dialogType]) {
|
||||
DialogTypes.EDIT_GOAL -> {
|
||||
if (click == 1) {
|
||||
return
|
||||
}
|
||||
|
||||
if (click == 2) {
|
||||
viewModel.setWeightGoal(null)
|
||||
return
|
||||
}
|
||||
|
||||
viewModel.setWeightGoal(getValue())
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
enum class DialogTypes(
|
||||
val title: String,
|
||||
val icon: Drawable? = null,
|
||||
val thirdText: Int? = null
|
||||
) {
|
||||
EDIT_GOAL("Edit Goal", null, R.string.goal_remove)
|
||||
}
|
||||
}
|
@ -0,0 +1,27 @@
|
||||
package com.dzeio.openhealth.ui.weight
|
||||
|
||||
import com.dzeio.openhealth.Settings
|
||||
import com.dzeio.openhealth.core.BaseViewModel
|
||||
import com.dzeio.openhealth.data.weight.WeightRepository
|
||||
import com.dzeio.openhealth.units.UnitFactory
|
||||
import com.dzeio.openhealth.utils.Configuration
|
||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||
import javax.inject.Inject
|
||||
|
||||
@HiltViewModel
|
||||
class WeightDialogViewModel @Inject internal constructor(
|
||||
private val weightRepository: WeightRepository,
|
||||
private val settings: Configuration
|
||||
) : BaseViewModel() {
|
||||
|
||||
private val _goalWeight = settings.getFloat(Settings.WEIGHT_GOAL)
|
||||
|
||||
var weightUnit =
|
||||
UnitFactory.mass(settings.getString("weight_unit").value ?: "kilogram")
|
||||
|
||||
val goalWeight = _goalWeight.toLiveData()
|
||||
|
||||
fun setWeightGoal(value: Float?) {
|
||||
_goalWeight.value = value
|
||||
}
|
||||
}
|
177
app/src/main/java/com/dzeio/openhealth/utils/Configuration.kt
Normal file
177
app/src/main/java/com/dzeio/openhealth/utils/Configuration.kt
Normal file
@ -0,0 +1,177 @@
|
||||
package com.dzeio.openhealth.utils
|
||||
|
||||
import android.content.SharedPreferences
|
||||
import android.util.Log
|
||||
import androidx.core.content.edit
|
||||
import com.dzeio.openhealth.Application
|
||||
import com.dzeio.openhealth.core.Observable
|
||||
|
||||
class Configuration(
|
||||
private val prefs: SharedPreferences
|
||||
) : SharedPreferences.OnSharedPreferenceChangeListener {
|
||||
|
||||
private val cache: HashMap<String, Field<*>> = HashMap()
|
||||
|
||||
private companion object {
|
||||
const val TAG = "${Application.TAG}/Config"
|
||||
}
|
||||
|
||||
init {
|
||||
prefs.registerOnSharedPreferenceChangeListener(this)
|
||||
}
|
||||
|
||||
fun getString(key: String): StringField {
|
||||
if (cache[key] == null) {
|
||||
Log.d(TAG, "$key does not exist in cache, creating new instance")
|
||||
cache[key] = StringField(key)
|
||||
} else {
|
||||
Log.d(TAG, "$key in cache")
|
||||
}
|
||||
return cache[key] as StringField
|
||||
}
|
||||
|
||||
fun getLong(key: String): LongField {
|
||||
if (cache[key] == null) {
|
||||
Log.d(TAG, "$key does not exist in cache, creating new instance")
|
||||
cache[key] = LongField(key)
|
||||
} else {
|
||||
Log.d(TAG, "$key in cache")
|
||||
}
|
||||
return cache[key] as LongField
|
||||
}
|
||||
|
||||
fun getBoolean(key: String): BooleanField {
|
||||
if (cache[key] == null) {
|
||||
Log.d(TAG, "$key does not exist in cache, creating new instance")
|
||||
cache[key] = BooleanField(key)
|
||||
} else {
|
||||
Log.d(TAG, "$key in cache")
|
||||
}
|
||||
return cache[key] as BooleanField
|
||||
}
|
||||
|
||||
fun getInt(key: String): IntField {
|
||||
if (cache[key] == null) {
|
||||
Log.d(TAG, "$key does not exist in cache, creating new instance")
|
||||
cache[key] = IntField(key)
|
||||
} else {
|
||||
Log.d(TAG, "$key in cache")
|
||||
}
|
||||
return cache[key] as IntField
|
||||
}
|
||||
|
||||
fun getFloat(key: String): FloatField {
|
||||
if (cache[key] == null) {
|
||||
Log.d(TAG, "$key does not exist in cache, creating new instance")
|
||||
cache[key] = FloatField(key)
|
||||
} else {
|
||||
Log.d(TAG, "$key in cache")
|
||||
}
|
||||
return cache[key] as FloatField
|
||||
}
|
||||
|
||||
fun getStringSet(key: String): StringSetField {
|
||||
if (cache[key] == null) {
|
||||
Log.d(TAG, "$key does not exist in cache, creating new instance")
|
||||
cache[key] = StringSetField(key)
|
||||
} else {
|
||||
Log.d(TAG, "$key in cache")
|
||||
}
|
||||
return cache[key] as StringSetField
|
||||
}
|
||||
|
||||
override fun onSharedPreferenceChanged(u: SharedPreferences, key: String) {
|
||||
Log.d(TAG, "configuration update for key: $key")
|
||||
cache[key]?.needUpdate = true
|
||||
cache[key]?.notifyObservers()
|
||||
}
|
||||
|
||||
abstract class Field<T>(
|
||||
baseValue: T
|
||||
) : Observable<T>(baseValue) {
|
||||
abstract fun exists(): Boolean
|
||||
abstract fun internalGet(): T
|
||||
abstract fun internalSet(value: T)
|
||||
var needUpdate = true
|
||||
|
||||
override var value: T = baseValue
|
||||
get() {
|
||||
if (needUpdate) {
|
||||
field = if (!exists()) {
|
||||
null as T
|
||||
} else {
|
||||
internalGet()
|
||||
}
|
||||
}
|
||||
return field
|
||||
}
|
||||
set(value) {
|
||||
if (field == value) {
|
||||
return
|
||||
}
|
||||
field = value
|
||||
internalSet(value)
|
||||
notifyObservers()
|
||||
}
|
||||
}
|
||||
|
||||
inner class StringField(
|
||||
private val key: String,
|
||||
private val defaultValue: String? = null
|
||||
) : Field<String?>(defaultValue) {
|
||||
override fun exists(): Boolean = prefs.contains(key)
|
||||
override fun internalGet(): String? = prefs.getString(key, defaultValue)
|
||||
override fun internalSet(value: String?) =
|
||||
prefs.edit { if (value == null) remove(key) else putString(key, value) }
|
||||
}
|
||||
|
||||
inner class BooleanField(
|
||||
private val key: String,
|
||||
private val defaultValue: Boolean = false
|
||||
) : Field<Boolean?>(defaultValue) {
|
||||
override fun exists(): Boolean = prefs.contains(key)
|
||||
override fun internalGet(): Boolean = prefs.getBoolean(key, defaultValue)
|
||||
override fun internalSet(value: Boolean?) =
|
||||
prefs.edit { if (value == null) remove(key) else putBoolean(key, value) }
|
||||
}
|
||||
|
||||
inner class IntField(
|
||||
private val key: String,
|
||||
private val defaultValue: Int = -1
|
||||
) : Field<Int?>(defaultValue) {
|
||||
override fun exists(): Boolean = prefs.contains(key)
|
||||
override fun internalGet(): Int = prefs.getInt(key, defaultValue)
|
||||
override fun internalSet(value: Int?) =
|
||||
prefs.edit { if (value == null) remove(key) else putInt(key, value) }
|
||||
}
|
||||
|
||||
inner class FloatField(
|
||||
private val key: String,
|
||||
private val defaultValue: Float = -1f
|
||||
) : Field<Float?>(defaultValue) {
|
||||
override fun exists(): Boolean = prefs.contains(key)
|
||||
override fun internalGet(): Float = prefs.getFloat(key, defaultValue)
|
||||
override fun internalSet(value: Float?) =
|
||||
prefs.edit { if (value == null) remove(key) else putFloat(key, value) }
|
||||
}
|
||||
|
||||
inner class LongField(
|
||||
private val key: String,
|
||||
private val defaultValue: Long = -1
|
||||
) : Field<Long?>(defaultValue) {
|
||||
override fun exists(): Boolean = prefs.contains(key)
|
||||
override fun internalGet(): Long = prefs.getLong(key, defaultValue)
|
||||
override fun internalSet(value: Long?) =
|
||||
prefs.edit { if (value == null) remove(key) else putLong(key, value) }
|
||||
}
|
||||
|
||||
inner class StringSetField(
|
||||
private val key: String,
|
||||
private val defaultValue: MutableSet<String>? = null
|
||||
) : Field<MutableSet<String>?>(defaultValue) {
|
||||
override fun exists(): Boolean = prefs.contains(key)
|
||||
override fun internalGet(): MutableSet<String>? = prefs.getStringSet(key, defaultValue)
|
||||
override fun internalSet(value: MutableSet<String>?) =
|
||||
prefs.edit { if (value == null) remove(key) else putStringSet(key, value) }
|
||||
}
|
||||
}
|
22
app/src/main/res/layout/dialog_weight.xml
Normal file
22
app/src/main/res/layout/dialog_weight.xml
Normal file
@ -0,0 +1,22 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:gravity="center"
|
||||
android:orientation="horizontal">
|
||||
|
||||
<android.widget.NumberPicker
|
||||
android:id="@+id/kg"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
/>
|
||||
|
||||
<android.widget.NumberPicker
|
||||
android:id="@+id/gram"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:progress="0" />
|
||||
|
||||
</LinearLayout>
|
@ -4,6 +4,10 @@
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:id="@+id/mobile_navigation"
|
||||
android:label="@string/app_name"
|
||||
app:enterAnim="@android:anim/slide_in_left"
|
||||
app:exitAnim="@android:anim/slide_out_right"
|
||||
app:popEnterAnim="@android:anim/slide_in_left"
|
||||
app:popExitAnim="@android:anim/slide_out_right"
|
||||
app:startDestination="@+id/nav_home">
|
||||
|
||||
<fragment
|
||||
@ -13,18 +17,10 @@
|
||||
tools:layout="@layout/fragment_home">
|
||||
<action
|
||||
android:id="@+id/action_nav_home_to_nav_list_weight"
|
||||
app:destination="@id/nav_list_weight"
|
||||
app:enterAnim="@android:anim/slide_in_left"
|
||||
app:exitAnim="@android:anim/slide_out_right"
|
||||
app:popEnterAnim="@android:anim/slide_in_left"
|
||||
app:popExitAnim="@android:anim/slide_out_right" />
|
||||
app:destination="@id/nav_list_weight" />
|
||||
<action
|
||||
android:id="@+id/action_nav_home_to_nav_water_home"
|
||||
app:destination="@id/nav_water_home"
|
||||
app:enterAnim="@android:anim/slide_in_left"
|
||||
app:exitAnim="@android:anim/slide_out_right"
|
||||
app:popEnterAnim="@android:anim/slide_in_left"
|
||||
app:popExitAnim="@android:anim/slide_out_right" />
|
||||
app:destination="@id/nav_water_home" />
|
||||
<action
|
||||
android:id="@+id/action_nav_home_to_nav_add_weight_dialog"
|
||||
app:destination="@id/nav_add_weight_dialog" />
|
||||
@ -47,14 +43,13 @@
|
||||
tools:layout="@layout/fragment_list_weight">
|
||||
<action
|
||||
android:id="@+id/action_nav_list_weight_to_nav_edit_weight"
|
||||
app:destination="@id/nav_edit_weight"
|
||||
app:enterAnim="@android:anim/slide_in_left"
|
||||
app:exitAnim="@android:anim/slide_out_right"
|
||||
app:popEnterAnim="@android:anim/slide_in_left"
|
||||
app:popExitAnim="@android:anim/slide_out_right" />
|
||||
app:destination="@id/nav_edit_weight" />
|
||||
<action
|
||||
android:id="@+id/action_nav_list_weight_to_nav_add_weight_dialog"
|
||||
app:destination="@id/nav_add_weight_dialog" />
|
||||
<action
|
||||
android:id="@+id/action_nav_list_weight_to_nav_weight_dialog"
|
||||
app:destination="@id/nav_weight_dialog" />
|
||||
</fragment>
|
||||
|
||||
<fragment
|
||||
@ -76,27 +71,15 @@
|
||||
tools:layout="@layout/fragment_main_water_home">
|
||||
<action
|
||||
android:id="@+id/action_nav_water_home_to_nav_water_edit"
|
||||
app:destination="@id/nav_water_edit"
|
||||
app:enterAnim="@android:anim/slide_in_left"
|
||||
app:exitAnim="@android:anim/slide_out_right"
|
||||
app:popEnterAnim="@android:anim/slide_in_left"
|
||||
app:popExitAnim="@android:anim/slide_out_right" />
|
||||
app:destination="@id/nav_water_edit" />
|
||||
<action
|
||||
android:id="@+id/action_nav_water_home_to_nav_water_size_dialog"
|
||||
app:destination="@id/nav_water_size_dialog"
|
||||
app:enterAnim="@android:anim/slide_in_left"
|
||||
app:exitAnim="@android:anim/slide_out_right"
|
||||
app:popEnterAnim="@android:anim/slide_in_left"
|
||||
app:popExitAnim="@android:anim/slide_out_right" />
|
||||
app:destination="@id/nav_water_size_dialog" />
|
||||
</fragment>
|
||||
|
||||
<dialog
|
||||
android:id="@+id/nav_water_size_dialog"
|
||||
android:name="com.dzeio.openhealth.ui.water.WaterSizeSelectorDialog"
|
||||
app:enterAnim="@android:anim/slide_in_left"
|
||||
app:exitAnim="@android:anim/slide_out_right"
|
||||
app:popEnterAnim="@android:anim/slide_in_left"
|
||||
app:popExitAnim="@android:anim/slide_out_right"
|
||||
tools:layout="@layout/dialog_water_size_selector">
|
||||
|
||||
</dialog>
|
||||
@ -104,10 +87,6 @@
|
||||
<dialog
|
||||
android:id="@+id/nav_add_weight_dialog"
|
||||
android:name="com.dzeio.openhealth.ui.weight.AddWeightDialog"
|
||||
app:enterAnim="@android:anim/slide_in_left"
|
||||
app:exitAnim="@android:anim/slide_out_right"
|
||||
app:popEnterAnim="@android:anim/slide_in_left"
|
||||
app:popExitAnim="@android:anim/slide_out_right"
|
||||
tools:layout="@layout/dialog_water_size_selector">
|
||||
|
||||
</dialog>
|
||||
@ -115,21 +94,13 @@
|
||||
<fragment
|
||||
android:id="@+id/nav_settings"
|
||||
android:name="com.dzeio.openhealth.ui.settings.SettingsFragment"
|
||||
android:label="@string/page_settings"
|
||||
app:enterAnim="@android:anim/slide_in_left"
|
||||
app:exitAnim="@android:anim/slide_out_right"
|
||||
app:popEnterAnim="@android:anim/slide_in_left"
|
||||
app:popExitAnim="@android:anim/slide_out_right">
|
||||
android:label="@string/page_settings">
|
||||
|
||||
</fragment>
|
||||
|
||||
<fragment
|
||||
android:id="@+id/nav_water_edit"
|
||||
android:name="com.dzeio.openhealth.ui.water.EditWaterDialog"
|
||||
app:enterAnim="@android:anim/slide_in_left"
|
||||
app:exitAnim="@android:anim/slide_out_right"
|
||||
app:popEnterAnim="@android:anim/slide_in_left"
|
||||
app:popExitAnim="@android:anim/slide_out_right"
|
||||
tools:layout="@layout/dialog_water_edit_water">
|
||||
|
||||
<argument
|
||||
@ -141,10 +112,6 @@
|
||||
<fragment
|
||||
android:id="@+id/nav_extension"
|
||||
android:name="com.dzeio.openhealth.ui.extension.ExtensionFragment"
|
||||
app:enterAnim="@android:anim/slide_in_left"
|
||||
app:exitAnim="@android:anim/slide_out_right"
|
||||
app:popEnterAnim="@android:anim/slide_in_left"
|
||||
app:popExitAnim="@android:anim/slide_out_right"
|
||||
tools:layout="@layout/fragment_extension">
|
||||
|
||||
<argument
|
||||
@ -184,4 +151,15 @@
|
||||
android:name="com.dzeio.openhealth.ui.steps.StepsHomeFragment"
|
||||
android:label="@string/menu_steps"
|
||||
tools:layout="@layout/fragment_steps_home" />
|
||||
|
||||
<dialog
|
||||
android:id="@+id/nav_weight_dialog"
|
||||
android:name="com.dzeio.openhealth.ui.weight.WeightDialog"
|
||||
tools:layout="@layout/dialog_weight"
|
||||
>
|
||||
|
||||
<argument
|
||||
android:name="dialog_type"
|
||||
app:argType="integer" />
|
||||
</dialog>
|
||||
</navigation>
|
||||
|
@ -36,6 +36,7 @@
|
||||
<string name="menu_browse">Browse</string>
|
||||
<string name="menu_activity">Activity</string>
|
||||
<string name="add_goal">Ajouter un objectif</string>
|
||||
<string name="goal_remove">Supprimer l\'objectif</string>
|
||||
<string name="edit_goal">Modifier l\'objectif</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>
|
||||
|
@ -47,6 +47,7 @@
|
||||
<string name="menu_activity">Activity</string>
|
||||
|
||||
<string name="add_goal">Add Goal</string>
|
||||
<string name="goal_remove">Remove 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="edit_daily_goal">Modifiy daily goal</string>
|
||||
|
@ -1,5 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<locale-config xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<locale android:name="en"/>
|
||||
<locale android:name="fr"/>
|
||||
<locale android:name="en" />
|
||||
<locale android:name="fr" />
|
||||
</locale-config>
|
||||
|
@ -22,7 +22,7 @@
|
||||
android:title="Height" />
|
||||
|
||||
<EditTextPreference
|
||||
android:key="weight_goal"
|
||||
android:key="com.dzeio.open-health.weight.goal"
|
||||
android:selectAllOnFocus="true"
|
||||
android:singleLine="true"
|
||||
android:digits="0123456789"
|
||||
|
Loading…
x
Reference in New Issue
Block a user