From 3b12ec4bfe08824e3561f709ee290c81ebc65478 Mon Sep 17 00:00:00 2001 From: Jakub Kalinowski Date: Thu, 24 Mar 2022 12:08:31 +0100 Subject: [PATCH] Issue 1316: Skip measurable habit (#1319) Co-authored-by: Jakub Kalinowski --- .../common/dialogs/NumberPickerFactory.kt | 25 +++++++- .../activities/common/views/FrequencyChart.kt | 8 ++- .../habits/list/ListHabitsScreen.kt | 4 +- .../habits/list/views/NumberButtonView.kt | 12 ++++ .../habits/show/ShowHabitActivity.kt | 6 +- .../NumericalCheckmarkWidgetActivity.kt | 1 + .../src/main/res/values/strings.xml | 1 + .../isoron/uhabits/core/models/EntryList.kt | 34 ++++++++++- .../isoron/uhabits/core/models/ScoreList.kt | 27 ++++---- .../screens/habits/list/ListHabitsBehavior.kt | 3 + .../screens/habits/show/views/HistoryCard.kt | 7 ++- .../screens/habits/show/views/TargetCard.kt | 33 ++++++++-- .../uhabits/core/models/ScoreListTest.kt | 61 +++++++++++++++++++ .../habits/list/ListHabitsBehaviorTest.kt | 10 ++- 14 files changed, 205 insertions(+), 27 deletions(-) diff --git a/uhabits-android/src/main/java/org/isoron/uhabits/activities/common/dialogs/NumberPickerFactory.kt b/uhabits-android/src/main/java/org/isoron/uhabits/activities/common/dialogs/NumberPickerFactory.kt index de2401c46..8c62e337a 100644 --- a/uhabits-android/src/main/java/org/isoron/uhabits/activities/common/dialogs/NumberPickerFactory.kt +++ b/uhabits-android/src/main/java/org/isoron/uhabits/activities/common/dialogs/NumberPickerFactory.kt @@ -22,6 +22,7 @@ package org.isoron.uhabits.activities.common.dialogs import android.annotation.SuppressLint import android.content.Context import android.content.DialogInterface +import android.content.DialogInterface.BUTTON_NEGATIVE import android.text.InputFilter import android.text.Spanned import android.view.LayoutInflater @@ -33,7 +34,11 @@ import android.widget.EditText import android.widget.NumberPicker import android.widget.TextView import androidx.appcompat.app.AlertDialog +import org.isoron.uhabits.HabitsApplication import org.isoron.uhabits.R +import org.isoron.uhabits.core.models.Entry +import org.isoron.uhabits.core.models.Frequency +import org.isoron.uhabits.core.models.Frequency.Companion.DAILY import org.isoron.uhabits.core.ui.screens.habits.list.ListHabitsBehavior import org.isoron.uhabits.inject.ActivityContext import org.isoron.uhabits.utils.InterfaceUtils @@ -52,6 +57,7 @@ class NumberPickerFactory unit: String, notes: String, dateString: String, + frequency: Frequency, callback: ListHabitsBehavior.NumberPickerCallback ): AlertDialog { val inflater = LayoutInflater.from(context) @@ -92,7 +98,7 @@ class NumberPickerFactory picker2.value = intValue % 100 etNotes.setText(notes) - val dialog = AlertDialog.Builder(context) + val dialogBuilder = AlertDialog.Builder(context) .setView(view) .setTitle(dateString) .setPositiveButton(R.string.save) { _, _ -> @@ -108,9 +114,24 @@ class NumberPickerFactory .setOnDismissListener { callback.onNumberPickerDismissed() } - .create() + + if (frequency == DAILY) { + dialogBuilder.setNegativeButton(R.string.skip_day) { _, _ -> + picker.clearFocus() + val v = Entry.SKIP.toDouble() / 1000 + val note = etNotes.text.toString() + callback.onNumberPicked(v, note) + } + } + + val dialog = dialogBuilder.create() dialog.setOnShowListener { + val preferences = + (context.applicationContext as HabitsApplication).component.preferences + if (!preferences.isSkipEnabled) { + dialog.getButton(BUTTON_NEGATIVE).visibility = View.GONE + } showSoftInput(dialog, pickerInputText) } diff --git a/uhabits-android/src/main/java/org/isoron/uhabits/activities/common/views/FrequencyChart.kt b/uhabits-android/src/main/java/org/isoron/uhabits/activities/common/views/FrequencyChart.kt index c4bad1a96..93a97dc41 100644 --- a/uhabits-android/src/main/java/org/isoron/uhabits/activities/common/views/FrequencyChart.kt +++ b/uhabits-android/src/main/java/org/isoron/uhabits/activities/common/views/FrequencyChart.kt @@ -173,7 +173,8 @@ class FrequencyChart : ScrollableChart { rect[0f, 0f, baseSize.toFloat()] = baseSize.toFloat() rect.offset(prevRect!!.left, prevRect!!.top + baseSize * j) val i = localeWeekdayList[j] % 7 - if (values != null) drawMarker(canvas, rect, values[i]) + if (values != null) + drawMarker(canvas, rect, values[i]) rect.offset(0f, rowHeight) } drawFooter(canvas, rect, date) @@ -222,11 +223,14 @@ class FrequencyChart : ScrollableChart { } private fun drawMarker(canvas: Canvas, rect: RectF?, value: Int?) { + // value can be negative when the entry is skipped + val valueCopy = value?.let { max(0, it) } + val padding = rect!!.height() * 0.2f // maximal allowed mark radius val maxRadius = (rect.height() - 2 * padding) / 2.0f // the real mark radius is scaled down by a factor depending on the maximal frequency - val scale = 1.0f / maxFreq * value!! + val scale = 1.0f / maxFreq * valueCopy!! val radius = maxRadius * scale val colorIndex = min((colors.size - 1), ((colors.size - 1) * scale).roundToInt()) pGraph!!.color = colors[colorIndex] diff --git a/uhabits-android/src/main/java/org/isoron/uhabits/activities/habits/list/ListHabitsScreen.kt b/uhabits-android/src/main/java/org/isoron/uhabits/activities/habits/list/ListHabitsScreen.kt index 774e5345f..4ec28f9f3 100644 --- a/uhabits-android/src/main/java/org/isoron/uhabits/activities/habits/list/ListHabitsScreen.kt +++ b/uhabits-android/src/main/java/org/isoron/uhabits/activities/habits/list/ListHabitsScreen.kt @@ -40,6 +40,7 @@ import org.isoron.uhabits.core.commands.CreateHabitCommand import org.isoron.uhabits.core.commands.DeleteHabitsCommand import org.isoron.uhabits.core.commands.EditHabitCommand import org.isoron.uhabits.core.commands.UnarchiveHabitsCommand +import org.isoron.uhabits.core.models.Frequency import org.isoron.uhabits.core.models.Habit import org.isoron.uhabits.core.models.PaletteColor import org.isoron.uhabits.core.tasks.TaskRunner @@ -230,9 +231,10 @@ class ListHabitsScreen unit: String, notes: String, dateString: String, + frequency: Frequency, callback: ListHabitsBehavior.NumberPickerCallback ) { - numberPickerFactory.create(value, unit, notes, dateString, callback).show() + numberPickerFactory.create(value, unit, notes, dateString, frequency, callback).show() } override fun showCheckmarkDialog( diff --git a/uhabits-android/src/main/java/org/isoron/uhabits/activities/habits/list/views/NumberButtonView.kt b/uhabits-android/src/main/java/org/isoron/uhabits/activities/habits/list/views/NumberButtonView.kt index d0d705fb3..ed00049ee 100644 --- a/uhabits-android/src/main/java/org/isoron/uhabits/activities/habits/list/views/NumberButtonView.kt +++ b/uhabits-android/src/main/java/org/isoron/uhabits/activities/habits/list/views/NumberButtonView.kt @@ -29,6 +29,7 @@ import android.view.View import android.view.View.OnClickListener import android.view.View.OnLongClickListener import org.isoron.uhabits.R +import org.isoron.uhabits.core.models.Entry import org.isoron.uhabits.core.models.NumericalHabitType.AT_LEAST import org.isoron.uhabits.core.models.NumericalHabitType.AT_MOST import org.isoron.uhabits.core.preferences.Preferences @@ -143,6 +144,12 @@ class NumberButtonView( private val lowContrast: Int private val mediumContrast: Int + private val paint = TextPaint().apply { + typeface = getFontAwesome() + isAntiAlias = true + textAlign = Paint.Align.CENTER + } + private val pUnit: TextPaint = TextPaint().apply { textSize = getDimension(context, R.dimen.smallerTextSize) typeface = NORMAL_TYPEFACE @@ -176,6 +183,11 @@ class NumberButtonView( val textSize: Float when { + value == Entry.SKIP.toDouble() / 1000 -> { + label = resources.getString(R.string.fa_skipped) + textSize = dim(R.dimen.smallTextSize) + typeface = getFontAwesome() + } value >= 0 -> { label = value.toShortString() typeface = BOLD_TYPEFACE diff --git a/uhabits-android/src/main/java/org/isoron/uhabits/activities/habits/show/ShowHabitActivity.kt b/uhabits-android/src/main/java/org/isoron/uhabits/activities/habits/show/ShowHabitActivity.kt index 0b696ad4e..4af6948ad 100644 --- a/uhabits-android/src/main/java/org/isoron/uhabits/activities/habits/show/ShowHabitActivity.kt +++ b/uhabits-android/src/main/java/org/isoron/uhabits/activities/habits/show/ShowHabitActivity.kt @@ -39,6 +39,7 @@ import org.isoron.uhabits.activities.common.dialogs.HistoryEditorDialog import org.isoron.uhabits.activities.common.dialogs.NumberPickerFactory import org.isoron.uhabits.core.commands.Command import org.isoron.uhabits.core.commands.CommandRunner +import org.isoron.uhabits.core.models.Frequency import org.isoron.uhabits.core.models.Habit import org.isoron.uhabits.core.models.PaletteColor import org.isoron.uhabits.core.preferences.Preferences @@ -169,9 +170,10 @@ class ShowHabitActivity : AppCompatActivity(), CommandRunner.Listener { unit: String, notes: String, dateString: String, - callback: ListHabitsBehavior.NumberPickerCallback, + frequency: Frequency, + callback: ListHabitsBehavior.NumberPickerCallback ) { - NumberPickerFactory(this@ShowHabitActivity).create(value, unit, notes, dateString, callback).show() + NumberPickerFactory(this@ShowHabitActivity).create(value, unit, notes, dateString, frequency, callback).show() } override fun showCheckmarkDialog( diff --git a/uhabits-android/src/main/java/org/isoron/uhabits/widgets/activities/NumericalCheckmarkWidgetActivity.kt b/uhabits-android/src/main/java/org/isoron/uhabits/widgets/activities/NumericalCheckmarkWidgetActivity.kt index c557ed942..b7bc7f229 100644 --- a/uhabits-android/src/main/java/org/isoron/uhabits/widgets/activities/NumericalCheckmarkWidgetActivity.kt +++ b/uhabits-android/src/main/java/org/isoron/uhabits/widgets/activities/NumericalCheckmarkWidgetActivity.kt @@ -81,6 +81,7 @@ class NumericalCheckmarkWidgetActivity : Activity(), ListHabitsBehavior.NumberPi data.habit.unit, entry.notes, today.toDialogDateString(), + data.habit.frequency, this ).show() } diff --git a/uhabits-android/src/main/res/values/strings.xml b/uhabits-android/src/main/res/values/strings.xml index 75d7d88a7..9fec1cbca 100644 --- a/uhabits-android/src/main/res/values/strings.xml +++ b/uhabits-android/src/main/res/values/strings.xml @@ -225,6 +225,7 @@ Increment Decrement Enable skip days + Skip Toggle twice to add a skip instead of a checkmark. Skips keep your score unchanged and don\'t break your streak. Show question marks for missing data Differentiate days without data from actual lapses. To enter a lapse, toggle twice. 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 5212923b1..2d9662b21 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 @@ -276,6 +276,8 @@ open class EntryList { * For numerical habits, non-positive entry values are converted to zero. For boolean habits, each * YES_MANUAL value is converted to 1000 and all other values are converted to zero. * + * SKIP values are converted to zero (if they weren't, each SKIP day would count as 0.003). + * * The returned list is sorted by timestamp, with the newest entry coming first and the oldest entry * coming last. If the original list has gaps in it (for example, weeks or months without any * entries), then the list produced by this method will also have gaps. @@ -289,7 +291,10 @@ fun List.groupedSum( ): List { return this.map { (timestamp, value) -> if (isNumerical) { - Entry(timestamp, max(0, value)) + if (value == SKIP) + Entry(timestamp, 0) + else + Entry(timestamp, max(0, value)) } else { Entry(timestamp, if (value == YES_MANUAL) 1000 else 0) } @@ -301,6 +306,31 @@ fun List.groupedSum( }.entries.map { (timestamp, entries) -> Entry(timestamp, entries.sumOf { it.value }) }.sortedBy { (timestamp, _) -> - - timestamp.unixTime + -timestamp.unixTime + } +} + +/** + * Counts the number of days with vaLue SKIP in the given period. + */ +fun List.countSkippedDays( + truncateField: DateUtils.TruncateField, + firstWeekday: Int = Calendar.SATURDAY +): List { + return this.map { (timestamp, value) -> + if (value == SKIP) { + Entry(timestamp, 1) + } else { + Entry(timestamp, 0) + } + }.groupBy { entry -> + entry.timestamp.truncate( + truncateField, + firstWeekday, + ) + }.entries.map { (timestamp, entries) -> + Entry(timestamp, entries.sumOf { it.value }) + }.sortedBy { (timestamp, _) -> + -timestamp.unixTime } } diff --git a/uhabits-core/src/jvmMain/java/org/isoron/uhabits/core/models/ScoreList.kt b/uhabits-core/src/jvmMain/java/org/isoron/uhabits/core/models/ScoreList.kt index 23bd88fd1..385ee0f0c 100644 --- a/uhabits-core/src/jvmMain/java/org/isoron/uhabits/core/models/ScoreList.kt +++ b/uhabits-core/src/jvmMain/java/org/isoron/uhabits/core/models/ScoreList.kt @@ -100,20 +100,25 @@ class ScoreList { } val normalizedRollingSum = rollingSum / 1000 - val percentageCompleted = if (!isAtMost) { - if (targetValue > 0) - min(1.0, normalizedRollingSum / targetValue) - else - 1.0 - } else { - if (targetValue > 0) { - (1 - ((normalizedRollingSum - targetValue) / targetValue)).coerceIn(0.0, 1.0) + if (values[offset] != Entry.SKIP) { + val percentageCompleted = if (!isAtMost) { + if (targetValue > 0) + min(1.0, normalizedRollingSum / targetValue) + else + 1.0 } else { - if (normalizedRollingSum > 0) 0.0 else 1.0 + if (targetValue > 0) { + (1 - ((normalizedRollingSum - targetValue) / targetValue)).coerceIn( + 0.0, + 1.0 + ) + } else { + if (normalizedRollingSum > 0) 0.0 else 1.0 + } } - } - previousValue = compute(freq, previousValue, percentageCompleted) + previousValue = compute(freq, previousValue, percentageCompleted) + } } else { if (values[offset] == Entry.YES_MANUAL) { rollingSum += 1.0 diff --git a/uhabits-core/src/jvmMain/java/org/isoron/uhabits/core/ui/screens/habits/list/ListHabitsBehavior.kt b/uhabits-core/src/jvmMain/java/org/isoron/uhabits/core/ui/screens/habits/list/ListHabitsBehavior.kt index d39227927..278d904db 100644 --- a/uhabits-core/src/jvmMain/java/org/isoron/uhabits/core/ui/screens/habits/list/ListHabitsBehavior.kt +++ b/uhabits-core/src/jvmMain/java/org/isoron/uhabits/core/ui/screens/habits/list/ListHabitsBehavior.kt @@ -21,6 +21,7 @@ package org.isoron.uhabits.core.ui.screens.habits.list import org.isoron.platform.time.LocalDate import org.isoron.uhabits.core.commands.CommandRunner import org.isoron.uhabits.core.commands.CreateRepetitionCommand +import org.isoron.uhabits.core.models.Frequency import org.isoron.uhabits.core.models.Habit import org.isoron.uhabits.core.models.HabitList import org.isoron.uhabits.core.models.HabitType @@ -58,6 +59,7 @@ open class ListHabitsBehavior @Inject constructor( habit.unit, entry.notes, timestamp.toDialogDateString(), + habit.frequency ) { newValue: Double, newNotes: String, -> val value = (newValue * 1000).roundToInt() commandRunner.run(CreateRepetitionCommand(habitList, habit, timestamp, value, newNotes)) @@ -167,6 +169,7 @@ open class ListHabitsBehavior @Inject constructor( unit: String, notes: String, dateString: String, + frequency: Frequency, callback: NumberPickerCallback ) fun showCheckmarkDialog( diff --git a/uhabits-core/src/jvmMain/java/org/isoron/uhabits/core/ui/screens/habits/show/views/HistoryCard.kt b/uhabits-core/src/jvmMain/java/org/isoron/uhabits/core/ui/screens/habits/show/views/HistoryCard.kt index de1820bbd..97d149906 100644 --- a/uhabits-core/src/jvmMain/java/org/isoron/uhabits/core/ui/screens/habits/show/views/HistoryCard.kt +++ b/uhabits-core/src/jvmMain/java/org/isoron/uhabits/core/ui/screens/habits/show/views/HistoryCard.kt @@ -27,6 +27,7 @@ import org.isoron.uhabits.core.models.Entry import org.isoron.uhabits.core.models.Entry.Companion.SKIP import org.isoron.uhabits.core.models.Entry.Companion.YES_AUTO import org.isoron.uhabits.core.models.Entry.Companion.YES_MANUAL +import org.isoron.uhabits.core.models.Frequency import org.isoron.uhabits.core.models.Habit import org.isoron.uhabits.core.models.HabitList import org.isoron.uhabits.core.models.NumericalHabitType.AT_LEAST @@ -123,6 +124,7 @@ class HistoryCardPresenter( habit.unit, entry.notes, timestamp.toDialogDateString(), + frequency = habit.frequency ) { newValue: Double, newNotes: String -> val thousands = (newValue * 1000).roundToInt() commandRunner.run( @@ -154,6 +156,7 @@ class HistoryCardPresenter( entries.map { when { it.value == Entry.UNKNOWN -> OFF + it.value == SKIP -> HATCHED (habit.targetType == AT_MOST) && (it.value / 1000.0 <= habit.targetValue) -> ON (habit.targetType == AT_LEAST) && (it.value / 1000.0 >= habit.targetValue) -> ON else -> GREY @@ -196,8 +199,10 @@ class HistoryCardPresenter( unit: String, notes: String, dateString: String, - callback: ListHabitsBehavior.NumberPickerCallback, + frequency: Frequency, + callback: ListHabitsBehavior.NumberPickerCallback ) + fun showCheckmarkDialog( selectedValue: Int, notes: String, diff --git a/uhabits-core/src/jvmMain/java/org/isoron/uhabits/core/ui/screens/habits/show/views/TargetCard.kt b/uhabits-core/src/jvmMain/java/org/isoron/uhabits/core/ui/screens/habits/show/views/TargetCard.kt index 15d500def..9b8add0dc 100644 --- a/uhabits-core/src/jvmMain/java/org/isoron/uhabits/core/ui/screens/habits/show/views/TargetCard.kt +++ b/uhabits-core/src/jvmMain/java/org/isoron/uhabits/core/ui/screens/habits/show/views/TargetCard.kt @@ -21,6 +21,7 @@ package org.isoron.uhabits.core.ui.screens.habits.show.views import org.isoron.uhabits.core.models.Habit import org.isoron.uhabits.core.models.PaletteColor +import org.isoron.uhabits.core.models.countSkippedDays import org.isoron.uhabits.core.models.groupedSum import org.isoron.uhabits.core.ui.views.Theme import org.isoron.uhabits.core.utils.DateUtils @@ -51,37 +52,59 @@ class TargetCardPresenter { isNumerical = habit.isNumerical ).firstOrNull()?.value ?: 0 + val skippedDayToday = entries.countSkippedDays( + truncateField = DateUtils.TruncateField.DAY + ).firstOrNull()?.value ?: 0 + val valueThisWeek = entries.groupedSum( truncateField = DateUtils.TruncateField.WEEK_NUMBER, firstWeekday = firstWeekday, isNumerical = habit.isNumerical ).firstOrNull()?.value ?: 0 + val skippedDaysThisWeek = entries.countSkippedDays( + truncateField = DateUtils.TruncateField.WEEK_NUMBER, + firstWeekday = firstWeekday + ).firstOrNull()?.value ?: 0 + val valueThisMonth = entries.groupedSum( truncateField = DateUtils.TruncateField.MONTH, isNumerical = habit.isNumerical ).firstOrNull()?.value ?: 0 + val skippedDaysThisMonth = entries.countSkippedDays( + truncateField = DateUtils.TruncateField.MONTH, + ).firstOrNull()?.value ?: 0 + val valueThisQuarter = entries.groupedSum( truncateField = DateUtils.TruncateField.QUARTER, isNumerical = habit.isNumerical ).firstOrNull()?.value ?: 0 + val skippedDaysThisQuarter = entries.countSkippedDays( + truncateField = DateUtils.TruncateField.QUARTER + ).firstOrNull()?.value ?: 0 + val valueThisYear = entries.groupedSum( truncateField = DateUtils.TruncateField.YEAR, isNumerical = habit.isNumerical ).firstOrNull()?.value ?: 0 + val skippedDaysThisYear = entries.countSkippedDays( + truncateField = DateUtils.TruncateField.YEAR + ).firstOrNull()?.value ?: 0 + val cal = DateUtils.getStartOfTodayCalendarWithOffset() val daysInMonth = cal.getActualMaximum(Calendar.DAY_OF_MONTH) val daysInQuarter = 91 val daysInYear = cal.getActualMaximum(Calendar.DAY_OF_YEAR) - val targetToday = habit.targetValue / habit.frequency.denominator - val targetThisWeek = targetToday * 7 - val targetThisMonth = targetToday * daysInMonth - val targetThisQuarter = targetToday * daysInQuarter - val targetThisYear = targetToday * daysInYear + val dailyTarget = habit.targetValue / habit.frequency.denominator + val targetToday = dailyTarget * (1 - skippedDayToday) + val targetThisWeek = dailyTarget * (7 - skippedDaysThisWeek) + val targetThisMonth = dailyTarget * (daysInMonth - skippedDaysThisMonth) + val targetThisQuarter = dailyTarget * (daysInQuarter - skippedDaysThisQuarter) + val targetThisYear = dailyTarget * (daysInYear - skippedDaysThisYear) val values = ArrayList() if (habit.frequency.denominator <= 1) values.add(valueToday / 1e3) diff --git a/uhabits-core/src/jvmTest/java/org/isoron/uhabits/core/models/ScoreListTest.kt b/uhabits-core/src/jvmTest/java/org/isoron/uhabits/core/models/ScoreListTest.kt index 6ca3e2806..a152bdfe0 100644 --- a/uhabits-core/src/jvmTest/java/org/isoron/uhabits/core/models/ScoreListTest.kt +++ b/uhabits-core/src/jvmTest/java/org/isoron/uhabits/core/models/ScoreListTest.kt @@ -23,6 +23,7 @@ import org.hamcrest.MatcherAssert.assertThat import org.hamcrest.number.IsCloseTo import org.hamcrest.number.OrderingComparison import org.isoron.uhabits.core.BaseUnitTest +import org.isoron.uhabits.core.models.Entry.Companion.SKIP import org.isoron.uhabits.core.utils.DateUtils.Companion.getToday import org.junit.Before import org.junit.Test @@ -381,6 +382,66 @@ class NumericalAtLeastScoreListTest : NumericalScoreListTest() { } } +class NumericalAtLeastScoreListWithSkipTest : NumericalScoreListTest() { + @Before + @Throws(Exception::class) + override fun setUp() { + super.setUp() + habit = fixtures.createEmptyNumericalHabit(NumericalHabitType.AT_LEAST) + } + + @Test + fun test_getValue() { + addEntries(0, 10, 2000) + addEntries(10, 11, SKIP) + addEntries(11, 15, 2000) + addEntries(15, 16, SKIP) + addEntries(16, 20, 2000) + val expectedValues = doubleArrayOf( + 0.617008, + 0.596033, + 0.573910, + 0.550574, + 0.525961, + 0.500000, + 0.472617, + 0.443734, + 0.413270, + 0.381137, + 0.347244, // skipped day should have the same score as the previous day + 0.347244, + 0.311495, + 0.273788, + 0.234017, + 0.192067, // skipped day should have the same score as the previous day + 0.192067, + 0.147820, + 0.101149, + 0.051922, + 0.000000, + 0.000000, + 0.000000 + ) + checkScoreValues(expectedValues) + } + + @Test + fun skipsShouldNotAffectScore() { + addEntries(0, 500, 1000) + val initialScore = habit.scores[today].value + + addEntries(500, 1000, SKIP) + assertThat(habit.scores[today].value, IsCloseTo.closeTo(initialScore, E)) + + addEntries(0, 300, 1000) + addEntries(300, 500, SKIP) + addEntries(500, 700, 1000) + + // skipped days should be treated as if they never existed + assertThat(habit.scores[today].value, IsCloseTo.closeTo(initialScore, E)) + } +} + class NumericalAtMostScoreListTest : NumericalScoreListTest() { @Before @Throws(Exception::class) diff --git a/uhabits-core/src/jvmTest/java/org/isoron/uhabits/core/ui/screens/habits/list/ListHabitsBehaviorTest.kt b/uhabits-core/src/jvmTest/java/org/isoron/uhabits/core/ui/screens/habits/list/ListHabitsBehaviorTest.kt index 8192583ad..4df4a28f3 100644 --- a/uhabits-core/src/jvmTest/java/org/isoron/uhabits/core/ui/screens/habits/list/ListHabitsBehaviorTest.kt +++ b/uhabits-core/src/jvmTest/java/org/isoron/uhabits/core/ui/screens/habits/list/ListHabitsBehaviorTest.kt @@ -33,6 +33,7 @@ import org.hamcrest.MatcherAssert.assertThat import org.hamcrest.core.IsEqual.equalTo import org.isoron.uhabits.core.BaseUnitTest import org.isoron.uhabits.core.models.Entry +import org.isoron.uhabits.core.models.Frequency import org.isoron.uhabits.core.models.Habit import org.isoron.uhabits.core.preferences.Preferences import org.isoron.uhabits.core.utils.DateUtils.Companion.getToday @@ -79,7 +80,14 @@ class ListHabitsBehaviorTest : BaseUnitTest() { @Test fun testOnEdit() { behavior.onEdit(habit2, getToday()) - verify(screen).showNumberPicker(eq(0.1), eq("miles"), eq(""), eq("Jan 25, 2015"), picker.capture()) + verify(screen).showNumberPicker( + eq(0.1), + eq("miles"), + eq(""), + eq("Jan 25, 2015"), + eq(Frequency.DAILY), + picker.capture() + ) picker.lastValue.onNumberPicked(100.0, "") val today = getTodayWithOffset() assertThat(habit2.computedEntries.get(today).value, equalTo(100000))