1
0
mirror of https://github.com/dzeiocom/OpenHealth.git synced 2025-06-12 17:19:18 +00:00

feat: Add daily Steps goal

This commit is contained in:
2022-12-17 21:15:20 +01:00
parent 9d3585c42c
commit 1e4267731b
21 changed files with 336 additions and 64 deletions

View File

@ -58,7 +58,7 @@ android {
targetSdk = sdkTarget
// Semantic Versioning
versionName = "1.0.0"
versionName = "0.1.0"
versionCode = 1
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
@ -82,6 +82,7 @@ android {
getByName("release") {
// Slimmer version
isMinifyEnabled = true
isDebuggable = true
proguardFiles(getDefaultProguardFile("proguard-android-optimize.txt"), "proguard-rules.pro")
signingConfig = signingConfigs.getByName("release")
}
@ -129,7 +130,7 @@ dependencies {
implementation("androidx.core:core-ktx:1.9.0")
implementation("androidx.appcompat:appcompat:1.7.0-alpha01")
implementation("javax.inject:javax.inject:1")
implementation("com.google.android.material:material:1.8.0-alpha03")
implementation("com.google.android.material:material:1.8.0-beta01")
implementation("androidx.constraintlayout:constraintlayout:2.1.4")
implementation("androidx.lifecycle:lifecycle-livedata-ktx:2.5.1")
implementation("androidx.lifecycle:lifecycle-viewmodel-ktx:2.5.1")
@ -173,13 +174,13 @@ dependencies {
kapt("com.google.dagger:hilt-compiler:2.43.2")
// Google Fit
implementation("com.google.android.gms:play-services-fitness:21.1.0")
implementation("com.google.android.gms:play-services-auth:20.4.0")
implementation("androidx.health.connect:connect-client:1.0.0-alpha07")
// implementation("com.google.android.gms:play-services-fitness:21.1.0")
// implementation("com.google.android.gms:play-services-auth:20.4.0")
// implementation("androidx.health.connect:connect-client:1.0.0-alpha08")
// Samsung Health
implementation(files("libs/samsung-health-data-1.5.0.aar"))
implementation("com.google.code.gson:gson:2.9.1")
// implementation(files("libs/samsung-health-data-1.5.0.aar"))
// implementation("com.google.code.gson:gson:2.9.1")
// ROOM
implementation("androidx.room:room-runtime:2.4.3")
@ -195,6 +196,7 @@ dependencies {
// OSS Licenses
implementation("com.google.android.gms:play-services-oss-licenses:17.0.0")
// Open Food Fact
implementation("com.squareup.retrofit2:retrofit:2.9.0")
implementation("com.squareup.retrofit2:converter-gson:2.9.0")
implementation("com.squareup.okhttp3:logging-interceptor:4.9.1")

View File

@ -26,4 +26,9 @@ object Settings {
* format in which the weight the user want it to be displayed as
*/
const val MASS_UNIT = "com.dzeio.open-health.unit.mass"
/**
* Goal number of steps each days
*/
const val STEPS_GOAL = "com.dzeio.open-health.steps.goal-daily"
}

View File

@ -19,7 +19,7 @@ class StepsAdapter() : BaseAdapter<Step, LayoutItemListBinding>() {
item: Step,
position: Int
) {
holder.binding.value.text = "${item.value}steps"
holder.binding.value.text = "${item.value} steps"
holder.binding.datetime.text = item.formatTimestamp()
holder.binding.edit.setOnClickListener {
onItemClick?.invoke(item)

View File

@ -8,7 +8,7 @@ data class OFFProduct(
@SerializedName("product_name")
var name: String,
@SerializedName("serving_quantity")
@SerializedName("serving_size")
var serving: String,
@SerializedName("nutriments")

View File

@ -11,13 +11,16 @@ interface StepDao : BaseDao<Step> {
@Query("SELECT * FROM Step ORDER BY timestamp DESC")
fun getAll(): Flow<List<Step>>
@Query("SELECT * FROM Step where id = :weightId")
@Query("SELECT * FROM Step WHERE timestamp >= :time")
fun getAfter(time: Long): Flow<List<Step>>
@Query("SELECT * FROM Step WHERE id = :weightId")
fun getOne(weightId: Long): Flow<Step?>
@Query("Select count(*) from Step")
@Query("SELECT count(*) FROM Step")
fun getCount(): Flow<Int>
@Query("Select * FROM Step ORDER BY timestamp DESC LIMIT 1")
@Query("SELECT * FROM Step ORDER BY timestamp DESC LIMIT 1")
fun last(): Flow<Step?>
@Query("DELETE FROM Step where source = :source")

View File

@ -2,7 +2,8 @@ package com.dzeio.openhealth.data.step
import kotlinx.coroutines.flow.filter
import kotlinx.coroutines.flow.firstOrNull
import kotlinx.coroutines.withTimeoutOrNull
import java.util.Calendar
import java.util.TimeZone
import javax.inject.Inject
import javax.inject.Singleton
@ -23,7 +24,13 @@ class StepRepository @Inject constructor(
suspend fun deleteFromSource(value: String) = stepDao.deleteFromSource(value)
suspend fun todaySteps(): Int {
val steps = getSteps().firstOrNull()
val cal = Calendar.getInstance(TimeZone.getTimeZone("UTC"))
cal.set(Calendar.HOUR, 0)
cal.set(Calendar.MINUTE, 0)
cal.set(Calendar.SECOND, 0)
cal.set(Calendar.MILLISECOND, 0)
val steps = stepDao.getAfter(cal.timeInMillis).firstOrNull()
if (steps == null) {
return 0
}

View File

@ -27,7 +27,7 @@ class AboutFragment : BaseStaticFragment<FragmentAboutBinding>() {
resources.getString(R.string.version_number, BuildConfig.VERSION_NAME)
binding.contactUs.setOnClickListener {
openLink("mailto:context.openhealth@dze.io")
openLink("mailto:contact.openhealth@dze.io")
}
binding.github.setOnClickListener {

View File

@ -92,18 +92,29 @@ class BrowseFragment :
}
viewModel.steps.observe(viewLifecycleOwner) {
binding.stepsText.setText("$it of xxx steps")
updateStepsText(it, viewModel.stepsGoal.value)
}
viewModel.stepsGoal.observe(viewLifecycleOwner) {
updateStepsText(viewModel.steps.value, it)
}
viewModel.weight.observe(viewLifecycleOwner) {
binding.weightText.setText(
String.format(
resources.getString(R.string.weight_current),
it,
resources.getString(R.string.unit_mass_kilogram_unit)
String.format(resources.getString(R.string.unit_mass_kilogram_unit), it)
)
)
}
}
private fun updateStepsText(numberOfSteps: Int?, goal: Int?) {
var text = "${numberOfSteps ?: 0} steps"
if (goal != null) {
text = "${numberOfSteps ?: 0} of $goal steps"
}
binding.stepsText.setText(text)
}
}

View File

@ -3,9 +3,11 @@ package com.dzeio.openhealth.ui.browse
import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.viewModelScope
import com.dzeio.openhealth.Settings
import com.dzeio.openhealth.core.BaseViewModel
import com.dzeio.openhealth.data.step.StepRepository
import com.dzeio.openhealth.data.weight.WeightRepository
import com.dzeio.openhealth.utils.Configuration
import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.flow.collectLatest
import kotlinx.coroutines.launch
@ -14,12 +16,16 @@ import javax.inject.Inject
@HiltViewModel
class BrowseViewModel @Inject internal constructor(
stepRepository: StepRepository,
weightRepository: WeightRepository
weightRepository: WeightRepository,
private val config: Configuration
) : BaseViewModel() {
private val _steps = MutableLiveData(0)
val steps: LiveData<Int> = _steps
private val _stepsGoal: MutableLiveData<Int?> = MutableLiveData()
val stepsGoal: LiveData<Int?> = _stepsGoal
private val _weight = MutableLiveData(0f)
val weight: LiveData<Float> = _weight
@ -36,5 +42,9 @@ class BrowseViewModel @Inject internal constructor(
_weight.postValue(it.weight)
}
}
this._stepsGoal.postValue(
config.getInt(Settings.STEPS_GOAL).value
)
}
}

View File

@ -91,5 +91,10 @@ class SettingsFragment : PreferenceFragmentCompat() {
return@setOnPreferenceChangeListener true
}
val stepsGoalPreference = findPreference<EditTextPreference>(Settings.STEPS_GOAL)
stepsGoalPreference.apply {
}
}
}

View File

@ -64,12 +64,16 @@ class StepsHomeFragment :
)
}
val errorColor = MaterialColors.getColor(
requireView(),
com.google.android.material.R.attr.colorError
)
chart.apply {
series = arrayListOf(serie)
// debug = true
yAxis.apply {
setYMax(500f)
textLabel.color = MaterialColors.getColor(
requireView(),
com.google.android.material.R.attr.colorOnPrimaryContainer
@ -78,14 +82,15 @@ class StepsHomeFragment :
requireView(),
com.google.android.material.R.attr.colorOnPrimaryContainer
)
goalLinePaint.color = errorColor
//
onValueFormat = { value -> "${value.toInt()}" }
}
xAxis.apply {
increment = 3600000.0
increment = 86400000.0
// displayCount = 168
displayCount = 10
// displayCount = 10
textPaint.color = MaterialColors.getColor(
requireView(),
com.google.android.material.R.attr.colorOnPrimaryContainer
@ -103,6 +108,11 @@ class StepsHomeFragment :
}
}
viewModel.goal.observe(viewLifecycleOwner) {
chart.yAxis.setGoalLine(it?.toFloat())
chart.refresh()
}
viewModel.items.observe(viewLifecycleOwner) { list ->
adapter.set(list)
@ -110,12 +120,7 @@ class StepsHomeFragment :
return@observe
}
val cal = Calendar.getInstance(TimeZone.getTimeZone("UTC"))
cal.set(Calendar.HOUR, 0)
cal.set(Calendar.MINUTE, 0)
cal.set(Calendar.SECOND, 0)
cal.set(Calendar.MILLISECOND, 0)
// chart.animation.enabled = false
// chart.animation.refreshRate = 60
@ -125,11 +130,33 @@ class StepsHomeFragment :
// chart.xAxis.labels.size = 32f
serie.entries = list.reversed().map {
return@map Entry(it.timestamp.toDouble(), it.value.toFloat())
} as ArrayList<Entry>
val entries: HashMap<Long, Entry> = HashMap()
chart.xAxis.x = serie.entries.first().x
list.forEach {
val cal = Calendar.getInstance(TimeZone.getTimeZone("UTC"))
cal.timeInMillis = it.timestamp
cal.set(Calendar.HOUR, 0)
cal.set(Calendar.AM_PM, Calendar.AM)
val ts = cal.timeInMillis
if (!entries.containsKey(ts)) {
entries[ts] = Entry((ts).toDouble(), 0F, errorColor)
}
entries[ts]!!.y += it.value.toFloat()
if (viewModel.goal.value != null) {
if (entries[ts]!!.y > viewModel.goal.value!!) {
entries[ts]!!.color = null
}
} else {
entries[ts]!!.color = null
}
}
serie.entries = ArrayList(entries.values)
chart.xAxis.x = chart.xAxis.getXMax()
chart.refresh()

View File

@ -1,10 +1,13 @@
package com.dzeio.openhealth.ui.steps
import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.viewModelScope
import com.dzeio.openhealth.Settings
import com.dzeio.openhealth.core.BaseViewModel
import com.dzeio.openhealth.data.step.Step
import com.dzeio.openhealth.data.step.StepRepository
import com.dzeio.openhealth.utils.Configuration
import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.flow.collectLatest
import kotlinx.coroutines.launch
@ -12,15 +15,23 @@ import javax.inject.Inject
@HiltViewModel
class StepsHomeViewModel@Inject internal constructor(
private val stepRepository: StepRepository
private val stepRepository: StepRepository,
private val config: Configuration
) : BaseViewModel() {
val items: MutableLiveData<List<Step>> = MutableLiveData()
private val _goal: MutableLiveData<Int?> = MutableLiveData()
val goal: LiveData<Int?> = _goal
fun init() {
viewModelScope.launch {
stepRepository.getSteps().collectLatest {
items.postValue(it)
}
}
this._goal.postValue(
config.getInt(Settings.STEPS_GOAL).value
)
}
}
}

View File

@ -0,0 +1,87 @@
package com.dzeio.openhealth.utils.fields
import android.content.Context
import android.text.InputType
import android.text.TextUtils
import android.util.AttributeSet
import android.util.Log
import android.widget.EditText
import androidx.preference.EditTextPreference
class IntEditTextPreference : EditTextPreference, EditTextPreference.OnBindEditTextListener {
private var txt: String? = null
constructor(
context: Context,
attrs: AttributeSet?,
defStyleAttr: Int,
defStyleRes: Int
) : super(context, attrs, defStyleAttr, defStyleRes) {
setOnBindEditTextListener(this)
}
constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int) : super(
context,
attrs,
defStyleAttr
) {
setOnBindEditTextListener(this)
}
constructor(context: Context, attrs: AttributeSet?) : super(context, attrs) {
setOnBindEditTextListener(this)
}
constructor(context: Context) : super(context) {
setOnBindEditTextListener(this)
}
/**
* Saves the text to the current data storage.
*
* @param text The text to save
*/
override fun setText(text: String?) {
val wasBlocking = shouldDisableDependents()
val pouet = Integer.parseInt(text.toString())
this.txt = text
pouet::class.simpleName?.let { Log.d("pouet2", it) }
persistInt(pouet)
val isBlocking = shouldDisableDependents()
if (isBlocking != wasBlocking) {
notifyDependencyChange(isBlocking)
}
notifyChanged()
}
override fun getText(): String? {
return this.txt
}
override fun onSetInitialValue(defaultValue: Any?) {
var value: Int
if (defaultValue != null) {
val strDefaultValue = defaultValue as String
val defaultIntValue = strDefaultValue.toInt()
value = getPersistedInt(defaultIntValue)
} else {
try {
value = getPersistedInt(0)
} catch (e: ClassCastException) {
value = 0
}
}
text = value.toString()
}
override fun shouldDisableDependents(): Boolean {
return TextUtils.isEmpty(text) || super.shouldDisableDependents()
}
override fun onBindEditText(editText: EditText) {
editText.inputType = InputType.TYPE_CLASS_NUMBER
}
}

View File

@ -13,11 +13,11 @@
<!-- Units -->
<string name="unit_mass_kilogram_name_singular">Kilogram</string>
<string name="unit_mass_kilogram_name_plural">Kilograms</string>
<string name="unit_mass_kilogram_unit" translatable="false">kg</string>
<string name="unit_mass_kilogram_unit" translatable="false">%1$skg</string>
<string name="unit_mass_pound_name_singular">Pound</string>
<string name="unit_mass_pound_name_plural">Pounds</string>
<string name="unit_mass_pound_unit" translatable="false">lbs</string>
<string name="unit_mass_pound_unit" translatable="false">%1$slbs</string>
<string name="unit_volume_milliliter_name_singular">Milliliter</string>
<string name="unit_volume_milliliter_name_plural">Milliliters</string>
@ -52,7 +52,7 @@
<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="menu_steps">Steps</string>
<string name="weight_current">Current weight: %1$s%2$s</string>
<string name="weight_current">Current weight: %1$s</string>
<!-- Error Activity Translations -->

View File

@ -13,6 +13,7 @@
android:key="com.dzeio.open-health.app.language"
android:title="@string/languages" />
</PreferenceCategory>
<PreferenceCategory android:title="Weight Settings">
<EditTextPreference
@ -32,9 +33,8 @@
android:entryValues="@array/mass_units"
android:key="com.dzeio.open-health.unit.mass"
android:title="Mass Unit" />
</PreferenceCategory>
<PreferenceCategory android:title="Water Settings">
<SwitchPreference
@ -55,4 +55,14 @@
android:inputType="number"
android:title="Daily Water intake" />
</PreferenceCategory>
<PreferenceCategory android:title="Steps settings">
<com.dzeio.openhealth.utils.fields.IntEditTextPreference
android:key="com.dzeio.open-health.steps.goal-daily"
android:title="Number of steps each steps"
android:selectAllOnFocus="true"
android:singleLine="true"
android:inputType="number"
/>
</PreferenceCategory>
</PreferenceScreen>