mirror of
https://github.com/dzeiocom/OpenHealth.git
synced 2025-04-22 19:02:16 +00:00
feat: Add daily Steps goal
This commit is contained in:
parent
9d3585c42c
commit
1e4267731b
@ -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")
|
||||
|
@ -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"
|
||||
}
|
||||
|
@ -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)
|
||||
|
@ -8,7 +8,7 @@ data class OFFProduct(
|
||||
@SerializedName("product_name")
|
||||
var name: String,
|
||||
|
||||
@SerializedName("serving_quantity")
|
||||
@SerializedName("serving_size")
|
||||
var serving: String,
|
||||
|
||||
@SerializedName("nutriments")
|
||||
|
@ -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")
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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 {
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
@ -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
|
||||
)
|
||||
}
|
||||
}
|
||||
|
@ -91,5 +91,10 @@ class SettingsFragment : PreferenceFragmentCompat() {
|
||||
|
||||
return@setOnPreferenceChangeListener true
|
||||
}
|
||||
|
||||
val stepsGoalPreference = findPreference<EditTextPreference>(Settings.STEPS_GOAL)
|
||||
stepsGoalPreference.apply {
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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()
|
||||
|
@ -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
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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
|
||||
}
|
||||
}
|
@ -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 -->
|
||||
|
@ -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>
|
||||
|
@ -4,6 +4,7 @@ package com.dzeio.charts
|
||||
* A Base entry for any charts
|
||||
*/
|
||||
data class Entry(
|
||||
val x: Double,
|
||||
val y: Float
|
||||
var x: Double,
|
||||
var y: Float,
|
||||
var color: Int? = null
|
||||
)
|
||||
|
@ -14,12 +14,12 @@ class XAxis(
|
||||
override var x: Double = 0.0
|
||||
set(value) {
|
||||
val max = getXMax() - increment * displayCount
|
||||
if (value > max) {
|
||||
val min = getXMin()
|
||||
if (value > max && min <= max) {
|
||||
field = max
|
||||
return
|
||||
}
|
||||
|
||||
val min = getXMin()
|
||||
if (value < min) {
|
||||
field = min
|
||||
return
|
||||
@ -103,4 +103,4 @@ class XAxis(
|
||||
override fun refresh() {
|
||||
// TODO("Not yet implemented")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -6,6 +6,7 @@ import android.graphics.Paint
|
||||
import android.graphics.Rect
|
||||
import android.graphics.RectF
|
||||
import com.dzeio.charts.ChartViewInterface
|
||||
import com.dzeio.charts.utils.drawDottedLine
|
||||
|
||||
class YAxis(
|
||||
private val view: ChartViewInterface
|
||||
@ -25,6 +26,12 @@ class YAxis(
|
||||
color = Color.BLUE
|
||||
}
|
||||
|
||||
override val goalLinePaint = Paint().apply {
|
||||
isAntiAlias = true
|
||||
color = Color.RED
|
||||
strokeWidth = 4f
|
||||
}
|
||||
|
||||
var onValueFormat: (value: Float) -> String = { it -> it.toString() }
|
||||
|
||||
override var labelCount = 5
|
||||
@ -49,15 +56,19 @@ class YAxis(
|
||||
return max!!
|
||||
}
|
||||
if (view.series.isEmpty()) {
|
||||
return 100f
|
||||
return (this.goalLine ?: 90f) + 10f
|
||||
}
|
||||
return view.series
|
||||
val seriesMax = view.series
|
||||
.maxOf { serie ->
|
||||
if (serie.getDisplayedEntries().isEmpty()) {
|
||||
return@maxOf 0f
|
||||
}
|
||||
return@maxOf serie.getDisplayedEntries().maxOf { entry -> entry.y }
|
||||
}
|
||||
if (this.goalLine != null) {
|
||||
return if (seriesMax > this.goalLine!!) seriesMax else this.goalLine!! + 1000f
|
||||
}
|
||||
return seriesMax
|
||||
}
|
||||
|
||||
override fun getYMin(): Float {
|
||||
@ -65,7 +76,7 @@ class YAxis(
|
||||
return min!!
|
||||
}
|
||||
if (view.series.isEmpty()) {
|
||||
return 0f
|
||||
return this.goalLine ?: 0f
|
||||
}
|
||||
return view.series
|
||||
.minOf { serie ->
|
||||
@ -80,6 +91,7 @@ class YAxis(
|
||||
if (!enabled) {
|
||||
return 0f
|
||||
}
|
||||
|
||||
val min = getYMin()
|
||||
val max = getYMax() - min
|
||||
val top = space.top
|
||||
@ -89,7 +101,7 @@ class YAxis(
|
||||
val increment = (bottom - top) / labelCount
|
||||
val valueIncrement = (max - min) / labelCount
|
||||
for (index in 0 until labelCount) {
|
||||
val text = onValueFormat((valueIncrement * (index + 1))).toString()
|
||||
val text = onValueFormat((valueIncrement * (index + 1)))
|
||||
textLabel.getTextBounds(text, 0, text.length, rect)
|
||||
maxWidth = maxWidth.coerceAtLeast(rect.width().toFloat())
|
||||
|
||||
@ -105,10 +117,29 @@ class YAxis(
|
||||
canvas.drawLine(space.left, posY, space.right - maxWidth - 32f, posY, linePaint)
|
||||
|
||||
}
|
||||
|
||||
if (this.goalLine != null) {
|
||||
val pos = (1 - this.goalLine!! / max) * space.height() + space.top
|
||||
canvas.drawDottedLine(
|
||||
0f,
|
||||
pos,
|
||||
space.right - maxWidth - 32f,
|
||||
pos,
|
||||
space.right / 20,
|
||||
goalLinePaint
|
||||
)
|
||||
}
|
||||
|
||||
return maxWidth + 32f
|
||||
}
|
||||
|
||||
override fun refresh() {
|
||||
// TODO("Not yet implemented")
|
||||
}
|
||||
|
||||
private var goalLine: Float? = null
|
||||
|
||||
override fun setGoalLine(height: Float?) {
|
||||
goalLine = height
|
||||
}
|
||||
}
|
||||
|
@ -28,6 +28,11 @@ sealed interface YAxisInterface {
|
||||
*/
|
||||
val linePaint: Paint
|
||||
|
||||
/**
|
||||
* Goal line paint
|
||||
*/
|
||||
val goalLinePaint: Paint
|
||||
|
||||
/**
|
||||
* run when manually refreshing the system
|
||||
*
|
||||
@ -72,4 +77,9 @@ sealed interface YAxisInterface {
|
||||
* @return the width of the sidebar
|
||||
*/
|
||||
fun onDraw(canvas: Canvas, space: RectF): Float
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a Goal line
|
||||
*/
|
||||
fun setGoalLine(height: Float?)
|
||||
}
|
||||
|
@ -5,7 +5,6 @@ import android.graphics.Color
|
||||
import android.graphics.Paint
|
||||
import android.graphics.Rect
|
||||
import android.graphics.RectF
|
||||
import android.util.Log
|
||||
import com.dzeio.charts.ChartView
|
||||
import com.dzeio.charts.utils.drawRoundRect
|
||||
|
||||
@ -47,18 +46,21 @@ class BarSerie(
|
||||
for (entry in displayedEntries) {
|
||||
// calculated height in percent from 0 to 100
|
||||
val top = (1 - entry.y / max) * drawableSpace.height() + drawableSpace.top
|
||||
var posX = drawableSpace.left + (view.xAxis.getPositionOnRect(entry, drawableSpace) - view.xAxis.getXOffset(drawableSpace)).toFloat()
|
||||
var posX = drawableSpace.left + (view.xAxis.getPositionOnRect(
|
||||
entry,
|
||||
drawableSpace
|
||||
) - view.xAxis.getXOffset(drawableSpace)).toFloat()
|
||||
// Log.d(TAG, "gpor = ${view.xAxis.getPositionOnRect(entry, space)}, gxo = ${view.xAxis.getXOffset(space)}")
|
||||
// Log.d(TAG, "max = $max, y = ${entry.y}, height = $height")
|
||||
// Log.d(TAG, "posX: ${posX / 60 / 60 / 1000}, offsetX = ${view.xAxis.x / (1000 * 60 * 60)}, x = ${entry.x / (1000 * 60 * 60)}, pouet: ${(view.xAxis.x + view.xAxis.displayCount * view.xAxis.increment) / (1000 * 60 * 60)}")
|
||||
|
||||
Log.d(
|
||||
TAG, """
|
||||
${posX},
|
||||
$top,
|
||||
${(posX + barWidth)},
|
||||
${drawableSpace.bottom}""".trimIndent()
|
||||
)
|
||||
// Log.d(
|
||||
// TAG, """
|
||||
// ${posX},
|
||||
// $top,
|
||||
// ${(posX + barWidth)},
|
||||
// ${drawableSpace.bottom}""".trimIndent()
|
||||
// )
|
||||
|
||||
val right = (posX + barWidth).coerceAtMost(drawableSpace.right)
|
||||
|
||||
@ -72,6 +74,13 @@ class BarSerie(
|
||||
continue
|
||||
}
|
||||
|
||||
// handle color recoloration
|
||||
val paint = Paint(barPaint)
|
||||
|
||||
if (entry.color != null) {
|
||||
paint.color = entry.color!!
|
||||
}
|
||||
|
||||
canvas.drawRoundRect(
|
||||
posX,
|
||||
top,
|
||||
@ -82,7 +91,7 @@ class BarSerie(
|
||||
32f,
|
||||
0f,
|
||||
0f,
|
||||
barPaint
|
||||
paint
|
||||
)
|
||||
|
||||
// handle text display
|
||||
|
@ -55,7 +55,17 @@ fun Canvas.drawDottedLine(
|
||||
/**
|
||||
* A more customizable drawRoundRect function
|
||||
*/
|
||||
fun Canvas.drawRoundRect(left: Float, top: Float, right: Float, bottom: Float, topLeft: Float, topRight: Float, bottomLeft: Float, bottomRight: Float, paint: Paint) {
|
||||
fun Canvas.drawRoundRect(
|
||||
left: Float,
|
||||
top: Float,
|
||||
right: Float,
|
||||
bottom: Float,
|
||||
topLeft: Float,
|
||||
topRight: Float,
|
||||
bottomLeft: Float,
|
||||
bottomRight: Float,
|
||||
paint: Paint
|
||||
) {
|
||||
val maxRound = arrayOf(topLeft, topRight, bottomLeft, bottomRight).maxOf { it }
|
||||
val width = right - left
|
||||
val height = bottom - top
|
||||
@ -81,14 +91,30 @@ fun Canvas.drawRoundRect(left: Float, top: Float, right: Float, bottom: Float, t
|
||||
if (bottomLeft == 0f) {
|
||||
drawRect(left, bottom - height / 2, left + width / 2, bottom, paint)
|
||||
} else {
|
||||
drawRoundRect(left, bottom - height / 2, left + width / 2, bottom, bottomLeft, bottomLeft, paint)
|
||||
drawRoundRect(
|
||||
left,
|
||||
bottom - height / 2,
|
||||
left + width / 2,
|
||||
bottom,
|
||||
bottomLeft,
|
||||
bottomLeft,
|
||||
paint
|
||||
)
|
||||
}
|
||||
|
||||
// bottom right border
|
||||
if (bottomRight == 0f) {
|
||||
drawRect(right - width / 2, bottom - height / 2, right, bottom, paint)
|
||||
} else {
|
||||
drawRoundRect(right - width / 2, bottom - height / 2, right, bottom, bottomRight, bottomRight, paint)
|
||||
drawRoundRect(
|
||||
right - width / 2,
|
||||
bottom - height / 2,
|
||||
right,
|
||||
bottom,
|
||||
bottomRight,
|
||||
bottomRight,
|
||||
paint
|
||||
)
|
||||
}
|
||||
|
||||
}
|
||||
@ -96,6 +122,23 @@ fun Canvas.drawRoundRect(left: Float, top: Float, right: Float, bottom: Float, t
|
||||
/**
|
||||
* A more customizable drawRoundRect function
|
||||
*/
|
||||
fun Canvas.drawRoundRect(rect: RectF, topLeft: Float, topRight: Float, bottomLeft: Float, bottomRight: Float, paint: Paint) {
|
||||
drawRoundRect(rect.left, rect.top, rect.right, rect.bottom, topLeft, topRight, bottomLeft, bottomRight, paint)
|
||||
}
|
||||
fun Canvas.drawRoundRect(
|
||||
rect: RectF,
|
||||
topLeft: Float,
|
||||
topRight: Float,
|
||||
bottomLeft: Float,
|
||||
bottomRight: Float,
|
||||
paint: Paint
|
||||
) {
|
||||
drawRoundRect(
|
||||
rect.left,
|
||||
rect.top,
|
||||
rect.right,
|
||||
rect.bottom,
|
||||
topLeft,
|
||||
topRight,
|
||||
bottomLeft,
|
||||
bottomRight,
|
||||
paint
|
||||
)
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user