diff --git a/uhabits-android/src/main/java/org/isoron/uhabits/activities/habits/edit/EditHabitActivity.kt b/uhabits-android/src/main/java/org/isoron/uhabits/activities/habits/edit/EditHabitActivity.kt
index 00d7d3c63..e932e8873 100644
--- a/uhabits-android/src/main/java/org/isoron/uhabits/activities/habits/edit/EditHabitActivity.kt
+++ b/uhabits-android/src/main/java/org/isoron/uhabits/activities/habits/edit/EditHabitActivity.kt
@@ -47,6 +47,7 @@ import org.isoron.uhabits.core.commands.EditHabitCommand
import org.isoron.uhabits.core.models.Frequency
import org.isoron.uhabits.core.models.Habit
import org.isoron.uhabits.core.models.HabitType
+import org.isoron.uhabits.core.models.NumericalEmptyDaysMode
import org.isoron.uhabits.core.models.NumericalHabitType
import org.isoron.uhabits.core.models.NumericalHistoryType
import org.isoron.uhabits.core.models.PaletteColor
@@ -86,6 +87,7 @@ class EditHabitActivity : AppCompatActivity() {
var reminderMin = -1
var reminderDays: WeekdayList = WeekdayList.EVERY_DAY
var historyType = NumericalHistoryType.TOTAL
+ var emptyDaysMode = NumericalEmptyDaysMode.EXCLUDE_EMPTY
var targetType = NumericalHabitType.AT_LEAST
override fun onCreate(state: Bundle?) {
@@ -109,6 +111,7 @@ class EditHabitActivity : AppCompatActivity() {
freqNum = habit.frequency.numerator
freqDen = habit.frequency.denominator
historyType = habit.historyType
+ emptyDaysMode = habit.emptyDaysMode
targetType = habit.targetType
habit.reminder?.let {
reminderHour = it.hour
@@ -195,6 +198,24 @@ class EditHabitActivity : AppCompatActivity() {
dialog.dismissCurrentAndShow()
}
+ populateIncludeEmptyDays()
+ binding.includeEmptyDaysPicker.setOnClickListener {
+ val builder = AlertDialog.Builder(this)
+ val arrayAdapter = ArrayAdapter(this, android.R.layout.select_dialog_item)
+ arrayAdapter.add(getString(R.string.yes))
+ arrayAdapter.add(getString(R.string.no))
+ builder.setAdapter(arrayAdapter) { dialog, which ->
+ emptyDaysMode = when (which) {
+ 0 -> NumericalEmptyDaysMode.INCLUDE_EMPTY
+ else -> NumericalEmptyDaysMode.EXCLUDE_EMPTY
+ }
+ populateIncludeEmptyDays()
+ dialog.dismiss()
+ }
+ val dialog = builder.create()
+ dialog.dismissCurrentAndShow()
+ }
+
populateTargetType()
binding.targetTypePicker.setOnClickListener {
val builder = AlertDialog.Builder(this)
@@ -308,6 +329,7 @@ class EditHabitActivity : AppCompatActivity() {
habit.unit = binding.unitInput.text.trim().toString()
}
habit.type = habitType
+ habit.emptyDaysMode = emptyDaysMode
val command = if (habitId >= 0) {
EditHabitCommand(
@@ -372,6 +394,13 @@ class EditHabitActivity : AppCompatActivity() {
else -> getString(R.string.average)
}
}
+
+ private fun populateIncludeEmptyDays() {
+ binding.includeEmptyDaysPicker.text = when (emptyDaysMode) {
+ NumericalEmptyDaysMode.EXCLUDE_EMPTY -> getString(R.string.no)
+ else -> getString(R.string.yes)
+ }
+ }
private fun populateTargetType() {
binding.targetTypePicker.text = when (targetType) {
NumericalHabitType.AT_MOST -> getString(R.string.target_type_at_most)
diff --git a/uhabits-android/src/main/res/layout/activity_edit_habit.xml b/uhabits-android/src/main/res/layout/activity_edit_habit.xml
index de728ba6d..2904ee1fb 100644
--- a/uhabits-android/src/main/res/layout/activity_edit_habit.xml
+++ b/uhabits-android/src/main/res/layout/activity_edit_habit.xml
@@ -172,19 +172,58 @@
-
-
-
-
-
-
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:orientation="horizontal"
+ android:baselineAligned="false">
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Calendar
Unit
History Type
+ Include empty days
Target Type
At least
At most
diff --git a/uhabits-core/src/jvmMain/java/org/isoron/uhabits/core/models/EntryList.kt b/uhabits-core/src/jvmMain/java/org/isoron/uhabits/core/models/EntryList.kt
index 5bad8f507..92289824f 100644
--- a/uhabits-core/src/jvmMain/java/org/isoron/uhabits/core/models/EntryList.kt
+++ b/uhabits-core/src/jvmMain/java/org/isoron/uhabits/core/models/EntryList.kt
@@ -338,34 +338,34 @@ fun List.groupedSum(
fun List.groupedAverage(
truncateField: DateUtils.TruncateField,
firstWeekday: Int = Calendar.SATURDAY,
- isNumerical: Boolean
+ isNumerical: Boolean,
+ emptyDaysMode: NumericalEmptyDaysMode
): List =
this
.map { (timestamp, value) ->
if (isNumerical) {
if (value == SKIP) Entry(timestamp, 0)
- else Entry(timestamp, max(0, value))
+ else Entry(timestamp, max(0, value)) // UNKNOWN/negatives -> 0
} else {
Entry(timestamp, if (value == YES_MANUAL) 1000 else 0)
}
}
.groupBy { entry ->
- when (truncateField) {
- DateUtils.TruncateField.WEEK_NUMBER ->
- entry.timestamp.truncate(truncateField, firstWeekday)
- else ->
- entry.timestamp.truncate(truncateField, firstWeekday)
- }
+ entry.timestamp.truncate(truncateField, firstWeekday)
}
.entries.map { (timestamp, entries) ->
- val nonEmpty = entries.filter { it.value > 0}
- val avg = if (nonEmpty.isEmpty()) 0
- else nonEmpty.map { it.value }.average().roundToInt()
+ val values = when (emptyDaysMode) {
+ NumericalEmptyDaysMode.INCLUDE_EMPTY ->
+ entries.map { it.value }
+ NumericalEmptyDaysMode.EXCLUDE_EMPTY ->
+ entries.map { it.value }.filter { it > 0 }
+ }
+ val avg = if (values.isEmpty()) 0 else values.average().roundToInt()
Entry(timestamp, avg)
}
- // 4) Sort buckets (latest first)
.sortedByDescending { (timestamp, _) -> timestamp.unixTime }
+
/**
* Counts the number of days with vaLue SKIP in the given period.
*/
diff --git a/uhabits-core/src/jvmMain/java/org/isoron/uhabits/core/models/Habit.kt b/uhabits-core/src/jvmMain/java/org/isoron/uhabits/core/models/Habit.kt
index 7e711ea96..d0fb6c550 100644
--- a/uhabits-core/src/jvmMain/java/org/isoron/uhabits/core/models/Habit.kt
+++ b/uhabits-core/src/jvmMain/java/org/isoron/uhabits/core/models/Habit.kt
@@ -32,6 +32,7 @@ data class Habit(
var question: String = "",
var reminder: Reminder? = null,
var historyType: NumericalHistoryType = NumericalHistoryType.TOTAL,
+ var emptyDaysMode: NumericalEmptyDaysMode = NumericalEmptyDaysMode.EXCLUDE_EMPTY,
var targetType: NumericalHabitType = NumericalHabitType.AT_LEAST,
var targetValue: Double = 0.0,
var type: HabitType = HabitType.YES_NO,
@@ -119,6 +120,7 @@ data class Habit(
this.question = other.question
this.reminder = other.reminder
this.historyType = other.historyType
+ this.emptyDaysMode = other.emptyDaysMode
this.targetType = other.targetType
this.targetValue = other.targetValue
this.type = other.type
@@ -140,6 +142,7 @@ data class Habit(
if (question != other.question) return false
if (reminder != other.reminder) return false
if (historyType != other.historyType) return false
+ if (emptyDaysMode != other.emptyDaysMode) return false
if (targetType != other.targetType) return false
if (targetValue != other.targetValue) return false
if (type != other.type) return false
@@ -160,6 +163,7 @@ data class Habit(
result = 31 * result + question.hashCode()
result = 31 * result + (reminder?.hashCode() ?: 0)
result = 31 * result + historyType.value
+ result = 31 * result + emptyDaysMode.value
result = 31 * result + targetType.value
result = 31 * result + targetValue.hashCode()
result = 31 * result + type.value
diff --git a/uhabits-core/src/jvmMain/java/org/isoron/uhabits/core/models/NumericalEmptyDaysMode.kt b/uhabits-core/src/jvmMain/java/org/isoron/uhabits/core/models/NumericalEmptyDaysMode.kt
new file mode 100644
index 000000000..95c001514
--- /dev/null
+++ b/uhabits-core/src/jvmMain/java/org/isoron/uhabits/core/models/NumericalEmptyDaysMode.kt
@@ -0,0 +1,17 @@
+package org.isoron.uhabits.core.models
+
+import java.lang.IllegalStateException
+
+enum class NumericalEmptyDaysMode(val value: Int) {
+ EXCLUDE_EMPTY(0), INCLUDE_EMPTY(1);
+
+ companion object {
+ fun fromInt(value: Int): NumericalEmptyDaysMode {
+ return when (value) {
+ EXCLUDE_EMPTY.value -> EXCLUDE_EMPTY
+ INCLUDE_EMPTY.value -> INCLUDE_EMPTY
+ else -> throw IllegalStateException()
+ }
+ }
+ }
+}
diff --git a/uhabits-core/src/jvmMain/java/org/isoron/uhabits/core/ui/screens/habits/show/views/BarCard.kt b/uhabits-core/src/jvmMain/java/org/isoron/uhabits/core/ui/screens/habits/show/views/BarCard.kt
index bab06a5f7..5baa4079b 100644
--- a/uhabits-core/src/jvmMain/java/org/isoron/uhabits/core/ui/screens/habits/show/views/BarCard.kt
+++ b/uhabits-core/src/jvmMain/java/org/isoron/uhabits/core/ui/screens/habits/show/views/BarCard.kt
@@ -70,7 +70,8 @@ class BarCardPresenter(
NumericalHistoryType.AVERAGE -> habit.computedEntries.getByInterval(oldest, today).groupedAverage(
truncateField = ScoreCardPresenter.getTruncateField(bucketSize),
firstWeekday = firstWeekday,
- isNumerical = habit.isNumerical
+ isNumerical = habit.isNumerical,
+ emptyDaysMode = habit.emptyDaysMode
)
}
return BarCardState(