From 804edfa64e15b8d2aa3ba19de9b8c260d8063801 Mon Sep 17 00:00:00 2001 From: KristianTashkov Date: Sat, 11 Sep 2021 23:23:52 +0300 Subject: [PATCH 01/12] Implement numerical habits with AT_MOST target type --- .../habits/edit/EditHabitActivity.kt | 10 ++++- .../habits/list/views/HabitCardListView.kt | 9 ++++- .../habits/list/views/HabitCardView.kt | 16 ++++++-- .../habits/list/views/NumberButtonView.kt | 40 ++++++++++++++----- .../habits/list/views/NumberPanelView.kt | 19 ++++++++- .../main/res/layout/activity_edit_habit.xml | 23 +++++++++++ .../src/main/res/values/strings.xml | 3 ++ .../org/isoron/uhabits/core/models/Habit.kt | 11 +++-- .../isoron/uhabits/core/models/ScoreList.kt | 32 ++++++++++++--- .../screens/habits/show/views/HistoryCard.kt | 22 +++++++--- .../uhabits/core/models/ScoreListTest.kt | 4 ++ 11 files changed, 155 insertions(+), 34 deletions(-) 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 1a02d8845..8bd84b98c 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 @@ -117,6 +117,10 @@ class EditHabitActivity : AppCompatActivity() { binding.notesInput.setText(habit.description) binding.unitInput.setText(habit.unit) binding.targetInput.setText(habit.targetValue.toString()) + if (habit.targetType == NumericalHabitType.AT_MOST) { + binding.targetTypeAtMost.isChecked = true + binding.targetTypeAtLeast.isChecked = false + } } else { habitType = HabitType.fromInt(intent.getIntExtra("habitType", HabitType.YES_NO.value)) } @@ -138,6 +142,7 @@ class EditHabitActivity : AppCompatActivity() { HabitType.YES_NO -> { binding.unitOuterBox.visibility = View.GONE binding.targetOuterBox.visibility = View.GONE + binding.targetTypeOuterBox.visibility = View.GONE } HabitType.NUMERICAL -> { binding.nameInput.hint = getString(R.string.measurable_short_example) @@ -262,7 +267,10 @@ class EditHabitActivity : AppCompatActivity() { habit.frequency = Frequency(freqNum, freqDen) if (habitType == HabitType.NUMERICAL) { habit.targetValue = targetInput.text.toString().toDouble() - habit.targetType = NumericalHabitType.AT_LEAST + if (binding.targetTypeAtLeast.isChecked) + habit.targetType = NumericalHabitType.AT_LEAST + else + habit.targetType = NumericalHabitType.AT_MOST habit.unit = unitInput.text.trim().toString() } habit.type = habitType diff --git a/uhabits-android/src/main/java/org/isoron/uhabits/activities/habits/list/views/HabitCardListView.kt b/uhabits-android/src/main/java/org/isoron/uhabits/activities/habits/list/views/HabitCardListView.kt index 58ee2b36a..c1935be75 100644 --- a/uhabits-android/src/main/java/org/isoron/uhabits/activities/habits/list/views/HabitCardListView.kt +++ b/uhabits-android/src/main/java/org/isoron/uhabits/activities/habits/list/views/HabitCardListView.kt @@ -36,6 +36,7 @@ import dagger.Lazy import org.isoron.uhabits.R import org.isoron.uhabits.activities.common.views.BundleSavedState import org.isoron.uhabits.core.models.Habit +import org.isoron.uhabits.core.models.NumericalHabitType import org.isoron.uhabits.inject.ActivityContext import javax.inject.Inject @@ -97,7 +98,13 @@ class HabitCardListView( cardView.dataOffset = dataOffset cardView.score = score cardView.unit = habit.unit - cardView.threshold = habit.targetValue / habit.frequency.denominator + if (habit.targetType == NumericalHabitType.AT_LEAST) { + cardView.higherThreshold = habit.targetValue / habit.frequency.denominator + cardView.lowerThreshold = 0.0 + } else { + cardView.higherThreshold = (habit.targetValue * 2) / habit.frequency.denominator + cardView.lowerThreshold = habit.targetValue / habit.frequency.denominator + } val detector = GestureDetector(context, CardViewGestureDetector(holder)) cardView.setOnTouchListener { _, ev -> diff --git a/uhabits-android/src/main/java/org/isoron/uhabits/activities/habits/list/views/HabitCardView.kt b/uhabits-android/src/main/java/org/isoron/uhabits/activities/habits/list/views/HabitCardView.kt index c59b61ec1..4fca5befa 100644 --- a/uhabits-android/src/main/java/org/isoron/uhabits/activities/habits/list/views/HabitCardView.kt +++ b/uhabits-android/src/main/java/org/isoron/uhabits/activities/habits/list/views/HabitCardView.kt @@ -109,10 +109,16 @@ class HabitCardView( numberPanel.values = values.map { it / 1000.0 }.toDoubleArray() } - var threshold: Double - get() = numberPanel.threshold + var lowerThreshold: Double + get() = numberPanel.lowerThreshold set(value) { - numberPanel.threshold = value + numberPanel.lowerThreshold = value + } + + var higherThreshold: Double + get() = numberPanel.higherThreshold + set(value) { + numberPanel.higherThreshold = value } var checkmarkPanel: CheckmarkPanelView @@ -236,7 +242,9 @@ class HabitCardView( numberPanel.apply { color = c units = h.unit - threshold = h.targetValue + targetType = h.targetType + lowerThreshold = 0.0 + higherThreshold = h.targetValue visibility = when (h.isNumerical) { true -> View.VISIBLE false -> View.GONE 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 3e48ed1a6..1d474a22f 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,13 +29,14 @@ 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.NumericalHabitType import org.isoron.uhabits.core.preferences.Preferences import org.isoron.uhabits.inject.ActivityContext import org.isoron.uhabits.utils.InterfaceUtils.getDimension -import org.isoron.uhabits.utils.StyledResources import org.isoron.uhabits.utils.dim import org.isoron.uhabits.utils.getFontAwesome import org.isoron.uhabits.utils.showMessage +import org.isoron.uhabits.utils.sres import java.text.DecimalFormat import javax.inject.Inject @@ -82,7 +83,19 @@ class NumberButtonView( invalidate() } - var threshold = 0.0 + var lowerThreshold = 0.0 + set(value) { + field = value + invalidate() + } + + var higherThreshold = 0.0 + set(value) { + field = value + invalidate() + } + + var targetType = NumericalHabitType.AT_LEAST set(value) { field = value invalidate() @@ -127,7 +140,6 @@ class NumberButtonView( private val em: Float private val rect: RectF = RectF() - private val sr = StyledResources(context) private val lowContrast: Int private val mediumContrast: Int @@ -148,15 +160,23 @@ class NumberButtonView( init { em = pNumber.measureText("m") - lowContrast = sr.getColor(R.attr.contrast40) - mediumContrast = sr.getColor(R.attr.contrast60) + lowContrast = sres.getColor(R.attr.contrast40) + mediumContrast = sres.getColor(R.attr.contrast60) } fun draw(canvas: Canvas) { - val activeColor = when { - value <= 0.0 -> lowContrast - value < threshold -> mediumContrast - else -> color + var activeColor = if (targetType == NumericalHabitType.AT_LEAST) { + when { + value <= lowerThreshold -> lowContrast + value < higherThreshold -> mediumContrast + else -> color + } + } else { + when { + value >= higherThreshold || value < 0 -> lowContrast + value > lowerThreshold -> mediumContrast + else -> color + } } val label: String @@ -175,7 +195,7 @@ class NumberButtonView( textSize = dim(R.dimen.smallerTextSize) } else -> { - label = "0" + label = if (targetType == NumericalHabitType.AT_LEAST) "0" else "inf" typeface = BOLD_TYPEFACE textSize = dim(R.dimen.smallTextSize) } diff --git a/uhabits-android/src/main/java/org/isoron/uhabits/activities/habits/list/views/NumberPanelView.kt b/uhabits-android/src/main/java/org/isoron/uhabits/activities/habits/list/views/NumberPanelView.kt index 491656a77..94980dfac 100644 --- a/uhabits-android/src/main/java/org/isoron/uhabits/activities/habits/list/views/NumberPanelView.kt +++ b/uhabits-android/src/main/java/org/isoron/uhabits/activities/habits/list/views/NumberPanelView.kt @@ -20,6 +20,7 @@ package org.isoron.uhabits.activities.habits.list.views import android.content.Context +import org.isoron.uhabits.core.models.NumericalHabitType import org.isoron.uhabits.core.models.Timestamp import org.isoron.uhabits.core.preferences.Preferences import org.isoron.uhabits.core.utils.DateUtils @@ -47,7 +48,19 @@ class NumberPanelView( setupButtons() } - var threshold = 0.0 + var targetType = NumericalHabitType.AT_LEAST + set(value) { + field = value + setupButtons() + } + + var lowerThreshold = 0.0 + set(value) { + field = value + setupButtons() + } + + var higherThreshold = 0.0 set(value) { field = value setupButtons() @@ -84,7 +97,9 @@ class NumberPanelView( else -> 0.0 } button.color = color - button.threshold = threshold + button.targetType = targetType + button.lowerThreshold = lowerThreshold + button.higherThreshold = higherThreshold button.units = units button.onEdit = { onEdit(timestamp) } } 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 b3a33c373..e30490bfb 100644 --- a/uhabits-android/src/main/res/layout/activity_edit_habit.xml +++ b/uhabits-android/src/main/res/layout/activity_edit_habit.xml @@ -167,6 +167,29 @@ android:hint="@string/measurable_units_example"/> + + + + + + + + + Change value Calendar Unit + Target Type + At Least + At Most e.g. Did you exercise today? Question Target 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 2d354e7b8..a2dd13846 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 @@ -59,9 +59,10 @@ data class Habit( val today = DateUtils.getTodayWithOffset() val value = computedEntries.get(today).value return if (isNumerical) { + val targetValuePerDay = (targetValue / frequency.denominator) when (targetType) { - NumericalHabitType.AT_LEAST -> value / 1000.0 >= targetValue - NumericalHabitType.AT_MOST -> value / 1000.0 <= targetValue + NumericalHabitType.AT_LEAST -> value / 1000.0 >= targetValuePerDay + NumericalHabitType.AT_MOST -> value / 1000.0 <= targetValuePerDay } } else { value != Entry.NO && value != Entry.UNKNOWN @@ -72,9 +73,10 @@ data class Habit( val today = DateUtils.getTodayWithOffset() val value = computedEntries.get(today).value return if (isNumerical) { + val targetValuePerDay = (targetValue / frequency.denominator) when (targetType) { - NumericalHabitType.AT_LEAST -> value / 1000.0 < targetValue - NumericalHabitType.AT_MOST -> value / 1000.0 > targetValue + NumericalHabitType.AT_LEAST -> value / 1000.0 < targetValuePerDay + NumericalHabitType.AT_MOST -> value / 1000.0 > targetValuePerDay } } else { value == Entry.NO @@ -96,6 +98,7 @@ data class Habit( scores.recompute( frequency = frequency, isNumerical = isNumerical, + numericalHabitType = targetType, targetValue = targetValue, computedEntries = computedEntries, from = from, 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 b5dff17de..037721d82 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 @@ -68,6 +68,7 @@ class ScoreList { fun recompute( frequency: Frequency, isNumerical: Boolean, + numericalHabitType: NumericalHabitType, targetValue: Double, computedEntries: EntryList, from: Timestamp, @@ -91,18 +92,37 @@ class ScoreList { } var previousValue = 0.0 + val numericalUnknownDayValue = (targetValue * 2 * 1000) / denominator for (i in values.indices) { val offset = values.size - i - 1 if (isNumerical) { - rollingSum += max(0, values[offset]) + if (values[offset] >= 0) + rollingSum += values[offset] + else if (numericalHabitType == NumericalHabitType.AT_MOST) + rollingSum += numericalUnknownDayValue if (offset + denominator < values.size) { - rollingSum -= values[offset + denominator] + if (values[offset + denominator] >= 0) { + rollingSum -= values[offset + denominator] + } else if (numericalHabitType == NumericalHabitType.AT_MOST) { + rollingSum -= numericalUnknownDayValue + } } - val percentageCompleted = if (targetValue > 0) { - min(1.0, rollingSum / 1000 / targetValue) - } else { - 1.0 + + var percentageCompleted = 0.0 + val normalizedRollingSum = rollingSum / 1000 + if (numericalHabitType == NumericalHabitType.AT_LEAST) { + percentageCompleted = if (targetValue > 0) + min(1.0, normalizedRollingSum / targetValue) + else + 1.0 + } else if (numericalHabitType == NumericalHabitType.AT_MOST) { + percentageCompleted = if (targetValue > 0 && normalizedRollingSum > targetValue) + max( + 0.0, 1 - ((normalizedRollingSum - targetValue) / targetValue) + ) + else if (normalizedRollingSum <= targetValue) 1.0 else 0.0 } + previousValue = compute(freq, previousValue, percentageCompleted) } else { if (values[offset] == Entry.YES_MANUAL) { 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 bd201e0c5..f1c05861c 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 @@ -29,6 +29,7 @@ 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.Habit import org.isoron.uhabits.core.models.HabitList +import org.isoron.uhabits.core.models.NumericalHabitType import org.isoron.uhabits.core.models.PaletteColor import org.isoron.uhabits.core.models.Timestamp import org.isoron.uhabits.core.preferences.Preferences @@ -105,12 +106,21 @@ class HistoryCardPresenter( val oldest = habit.computedEntries.getKnown().lastOrNull()?.timestamp ?: today val entries = habit.computedEntries.getByInterval(oldest, today) val series = if (habit.isNumerical) { - entries.map { - Entry(it.timestamp, max(0, it.value)) - }.map { - when (it.value) { - 0 -> HistoryChart.Square.OFF - else -> HistoryChart.Square.ON + if (habit.targetType == NumericalHabitType.AT_LEAST) { + entries.map { + when (max(0, it.value)) { + 0 -> HistoryChart.Square.OFF + else -> HistoryChart.Square.ON + } + } + } else { + entries.map { + if (it.value < 0) habit.targetValue * 2.0 * 1000.0 else it.value / 1000.0 + }.map { + when { + it <= habit.targetValue -> HistoryChart.Square.ON + else -> HistoryChart.Square.OFF + } } } } else { 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 f78af6d21..ae2a8eedb 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 @@ -128,6 +128,10 @@ class ScoreListTest : BaseUnitTest() { habit.targetValue = 0.0 habit.recompute() assertTrue(habit.scores[today].value.isFinite()) + + habit.targetType = NumericalHabitType.AT_MOST + habit.recompute() + assertTrue(habit.scores[today].value.isFinite()) } @Test From 697fffbc9935ff689d768797e333c7d4ed179fb6 Mon Sep 17 00:00:00 2001 From: KristianTashkov Date: Sun, 12 Sep 2021 13:50:27 +0300 Subject: [PATCH 02/12] fix tests --- .../activities/habits/list/views/NumberButtonViewTest.kt | 5 ++++- .../activities/habits/list/views/NumberPanelViewTest.kt | 5 ++++- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/uhabits-android/src/androidTest/java/org/isoron/uhabits/activities/habits/list/views/NumberButtonViewTest.kt b/uhabits-android/src/androidTest/java/org/isoron/uhabits/activities/habits/list/views/NumberButtonViewTest.kt index 6f1923aaa..c06aa5fb3 100644 --- a/uhabits-android/src/androidTest/java/org/isoron/uhabits/activities/habits/list/views/NumberButtonViewTest.kt +++ b/uhabits-android/src/androidTest/java/org/isoron/uhabits/activities/habits/list/views/NumberButtonViewTest.kt @@ -24,6 +24,7 @@ import androidx.test.filters.MediumTest import org.hamcrest.CoreMatchers.equalTo import org.hamcrest.MatcherAssert.assertThat import org.isoron.uhabits.BaseViewTest +import org.isoron.uhabits.core.models.NumericalHabitType import org.isoron.uhabits.utils.PaletteUtils import org.junit.Before import org.junit.Test @@ -42,7 +43,9 @@ class NumberButtonViewTest : BaseViewTest() { super.setUp() view = component.getNumberButtonViewFactory().create().apply { units = "steps" - threshold = 100.0 + targetType = NumericalHabitType.AT_LEAST + lowerThreshold = 0.0 + higherThreshold = 100.0 color = PaletteUtils.getAndroidTestColor(8) onEdit = { edited = true } } diff --git a/uhabits-android/src/androidTest/java/org/isoron/uhabits/activities/habits/list/views/NumberPanelViewTest.kt b/uhabits-android/src/androidTest/java/org/isoron/uhabits/activities/habits/list/views/NumberPanelViewTest.kt index bd046acc7..f5d992f62 100644 --- a/uhabits-android/src/androidTest/java/org/isoron/uhabits/activities/habits/list/views/NumberPanelViewTest.kt +++ b/uhabits-android/src/androidTest/java/org/isoron/uhabits/activities/habits/list/views/NumberPanelViewTest.kt @@ -24,6 +24,7 @@ import androidx.test.filters.MediumTest import org.hamcrest.CoreMatchers.equalTo import org.hamcrest.MatcherAssert.assertThat import org.isoron.uhabits.BaseViewTest +import org.isoron.uhabits.core.models.NumericalHabitType import org.isoron.uhabits.core.models.Timestamp import org.isoron.uhabits.utils.PaletteUtils import org.junit.After @@ -55,7 +56,9 @@ class NumberPanelViewTest : BaseViewTest() { buttonCount = 4 color = PaletteUtils.getAndroidTestColor(7) units = "steps" - threshold = 5000.0 + targetType = NumericalHabitType.AT_LEAST + lowerThreshold = 0.0 + higherThreshold = 5000.0 } view.onAttachedToWindow() measureView(view, dpToPixels(200), dpToPixels(200)) From 1a56260757b19dfc7c20cf193b10737a2e4ce6c8 Mon Sep 17 00:00:00 2001 From: KristianTashkov Date: Sun, 12 Sep 2021 14:32:59 +0300 Subject: [PATCH 03/12] add more tests for AT_MOST --- .../NumberButtonView/render_at_most_above.png | Bin 0 -> 2294 bytes .../NumberButtonView/render_at_most_below.png | Bin 0 -> 1838 bytes .../render_at_most_between.png | Bin 0 -> 1986 bytes .../list/NumberButtonView/render_below.png | Bin 1958 -> 1856 bytes .../list/NumberButtonView/render_between.png | Bin 0 -> 1958 bytes .../list/NumberButtonView/render_zero.png | Bin 1856 -> 0 bytes .../habits/list/views/NumberButtonViewTest.kt | 37 ++++++++++++++---- 7 files changed, 29 insertions(+), 8 deletions(-) create mode 100644 uhabits-android/src/androidTest/assets/views/habits/list/NumberButtonView/render_at_most_above.png create mode 100644 uhabits-android/src/androidTest/assets/views/habits/list/NumberButtonView/render_at_most_below.png create mode 100644 uhabits-android/src/androidTest/assets/views/habits/list/NumberButtonView/render_at_most_between.png create mode 100644 uhabits-android/src/androidTest/assets/views/habits/list/NumberButtonView/render_between.png delete mode 100644 uhabits-android/src/androidTest/assets/views/habits/list/NumberButtonView/render_zero.png diff --git a/uhabits-android/src/androidTest/assets/views/habits/list/NumberButtonView/render_at_most_above.png b/uhabits-android/src/androidTest/assets/views/habits/list/NumberButtonView/render_at_most_above.png new file mode 100644 index 0000000000000000000000000000000000000000..5bd20a34f244dd23221010487efa7e8ee62530e4 GIT binary patch literal 2294 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 literal 0 HcmV?d00001 diff --git a/uhabits-android/src/androidTest/assets/views/habits/list/NumberButtonView/render_at_most_between.png b/uhabits-android/src/androidTest/assets/views/habits/list/NumberButtonView/render_at_most_between.png new file mode 100644 index 0000000000000000000000000000000000000000..b407ccd6264a4dfce901652485f8725f1d0af0d2 GIT binary patch literal 1986 zcmaJ?X;71C5`IG>L?FPxsGI`sCkSc)N5Unip!o;}2f4(cXyh2eAw)zFB;qXN!3e~Q z8)qOW5g8#wu1SE2fEfm&*#L_mB8MC?2^$bZ3~b!me_OR(-EUX-kEhu^5ujn*W2dnVyQemc%@n<8 zj>4ZsYHDhpz0oy+1L06Kl2)a6Yw|-Zh0<2ZVzG>)N#8L_$~>OT5;C%3T@JHC6YCKwR}c#Whygh8oS-Vpv63= zxtb7&#D|4(5^~dOW8YQ|tl{elV@bLY69Gt}oHMzrha-!o3g$EE*qi2cbGie7?)Nd> zxvczq_UGUu|I$8UkKL?HLV3Zz5#}8U!-F;5J3W6j^bk_*OA?rS@;@+#9WGxuk*GwR z_uf-R-1wD*m4W;lxOh#T4xMN~Cdnn0QE;N)e{hfu!*K|YQ+@A$hT7o4b=H?lOl1X+ zlygFNKz@3IYAa_R-=G*L_1$}9js~QmOoFi3Rx{)_Y4d|F{Y5n2$G&G>(EZI&+;s{7 z*RX}Ih8zG+>#sjzzNq)JSnO$@D7H#E_|iMu>=TaW9~}RA?rhzR$jG5gcNlp=I_gx( z;V1%N6R2H%y!UP=Scpet9e(}_qLY&VZoliR{dGdUPQ5CKc=AB$`7e!?06=7wJ-d8dL#xy%8Cr8NR z@lGJl(@QnRQ@|SZM!L3+1}M*SmV65I1X@J^0l3(gR9fPYIQGStQJ3M?9w1DG_F5cQ zS%|Z~+xoYXzU{Sl=EUMP`lKVbN)6(`@;0zl#gQnF{=3&Z>DbswD%T0;%e&OEu&`hQ zCm^4q0Thmi*iGwEQ&ZcWw;8MU)Z(+;{09S?JtM&CSdAq*J5irp4i+}iCG__y|2&Gl zU-4Q=1^daY{7I5(#9sahyqljq44B@sNF24#TK`fZDk2};--!6b_KPOpXlI$xU~l;|0@B{{GV-y36(~YwZ~1VDJdqH>~R^i zO`q(B7;j+~9@7hCYj>o)($S;b`WPF7#HZ&^tsqFTxoPH_yl^s^yt1^kRO%>TVo`uz zf#EI4iv@TD&F+0RHE6<-=&8HV-?AxbI`n>ymM2%1r%$fCecqja0W`528^2W+n;H>e zM(tN0!+n>&p-vlNo`Pl&v?>Fcu1mlEF(u?=B>As;EXG~^&*@~#%(tn>YD9M(7g1F8 z4u_jv6P;Wxchl|KL&{yU6;zbW`pD;~K>=w2VM3Sp=;wFH;nfm9$In;9{WFU$xpwX5 zX-<3@Vz%7-;&kX2AHkwUx=MP(dmove!p+aVr*(g$8p_jF2h~||)l9#_aK2SafU?lJ zripnjy?d7LS1Nj|3x)LA%;quU$-$SZW~&zbF6a`oDJ068Xxk)q2l>7XEVE|jT}-{D zv!(vFoOgp}=yllv+SJ3VW}jjrUD4SL`rx6p%cl}=B~fM>*K9Ry5a)o$A>kq@zjb;4 z5Pk=LpnS&lq?IYEGY?KcEEv)hP-05>3Zt{LQ#3m}dm9Af4ZDi2>-WG|#8Z+m37D>A zW~CqPQyuR4wa?K@UIL512&)p3KN6!)OH_?Z&&sNrkm0fCW4nk%i{TW=>=X=5$3uhv5o*GRSaG8ui{^ZP1Mz%qFY(XnL@(tX2Cxs&yUr^j```OY Bv8Mn4 literal 0 HcmV?d00001 diff --git a/uhabits-android/src/androidTest/assets/views/habits/list/NumberButtonView/render_below.png b/uhabits-android/src/androidTest/assets/views/habits/list/NumberButtonView/render_below.png index e5a98fc8987e73c6edc7c3de02ed3801638066bc..5f6bf8c65cf649ef2b9f437feaea7fed323ec774 100644 GIT binary patch delta 1826 zcmaJ?dpy$%8~?G*B^|6obSlbuOO&CxmAlUrHd$Ci3r((_h<7}USbIPz7n*>po(qXtIv5x_@M(EgR1|3a3bGtlTB z7pWs{fRzdF2!`jF^_*Y_wZeQrL(vPv-QKP6){tMA4Vfj640`PABA0lz^-S*ty%}1h z;m^}A^zvedOgpw_FNNaQKhi_{I1h|Y$R>5o{hw6lXVVD@d$Ya0{q*%~%lv_bkb&n~ zGD%5EL*Lw;)V*OqxubvoR+}o|0o*vd7)c;;`8#WO>b7GJ-qRc^#4`%^{$LQ5f^%65 zAyZecT&aku3_P6^FN60>nt#(d!IX4$uVTl~oHtFg^l`HY;UCDj7UfhBbp>oLf82T= z&?%TuobaX;en}fPV4`GndOyDX{!ES+Rg8{0EA{)YYMY-kv55J7l>u%J3v7Wfpylk3 zqPm0Q{#kynVK?aeH%nyab$pLO}_Q(Iz7?`={`^m^mfR6aool-S=mweg&NC{{*J3uvF zdv*Yy%K`&NAD)^UYAE$>sEA=9>*H{UrI zJ(C?<72xHq3H03A2!my}Dn^Oh$RTlfPBe~1`5tEm7n*xl%h)+lZn*RW$CZ*{*V#Br z`GT)aXa7qV*`HqJrAl@2Y(2GV``mVu;@#4QL4AmItq%Xb&E>=xj`C(PB!6B^SU(g((q zL5I2?*uPT-zg7uceRE1mCC}IN_%q>LLmm2D0kAJU@8j#+R5pN^?+kq(s)L{0#STwE zvF(y97HjX0rzZ#MssG?YYDwQ?U31pb)+($ZKo)7PXgy+sya~Hm*|Fl7mE7?`tyET+%hrW9#9;eLQAb!UPgU-pNQtIcsKl7P0kQjMqVH`GH#|>vZIel04)$ z+K-YVQ)x-d2jFIs8|6NAoa-QN*d4s5B;jZj1owg?b0nvdgSN?kX6(Wq@sV~)23%RH+m}cLURJ=Z% zGc}pps4j;bRt_Ys`aToVwD@~wwq|Hpwzia&a$;C0u+#5wH*Ny0luv+g4IPpi2vKb~lD zadD@(yB~fJm#*YjkV9lhaGcw3Yb&Jnyz^3CgQ%2m41Y7=UF8Uwy4j$s6k5J9yLlpv z1_4|0B5|xj3rMYA1<(kpN$u~Vn6s3(9&!{pAV&HksS=07)stI5+ls{9v6Bp?1U`93 z9W!m#SMau;8KF(oNLyf}F|hum2z{7=DLz&eO}R174xn)po>|H}!xmjMvm`|9*)v*2 zP?M9pmp7SArXm~;&$mXH3{^FzkLPPyAmW#H_kEG&qXD?be3KwD`dpdPw}|SI4V8Zg zz#{&a;{$W$)*f2C2p0PScew)$4(GfyW(S@AACo!$!*hTGNre|95ZaMBE+7h9>uU(A ImFMq&1BSkpqW}N^ delta 1929 zcmV;42X^?t4yF%~Gk*tNNklK&Q zShO`^MJN`k?cIgm?d<#b$L^M4xyrH3-cHH;NjA6hzVGwibAR*B%)T=_b6{DPWm%SG zS(as4mStI%Wm%SGS(as4mStI%Wm%RL8)208P^Vp z9Dw2B;q=7B#AXp$3tU_)J}x5Ly1Tm{?Ca~R#2MF49qmq_(eUtadUA4dkBBU6sJcv5 z3*+PCH>v6>5r5G*;@Zt=cLI&dnFHBub`S7)q}>&xqocRQ z5Z88z8UR&Y6kRsPJQt~ZHd-elOV2kh2@*8`z~#{eV}H!Q>hDG5aI{WUua6LxC29af(;GZsZ{DZRh=gyUkAP#sXpG-)qk}q&bW3;}@tld(w(eu20XAQUjB+Nbc-`*}F_d1TVTSRUFK1_Ac9`5Ps`Dv3m zwnR$@AP9ob0$&E+bX|A8=XqBdW99)zfJI|tV_AS)F84Xl^E!Z+0E{trc%Ijh&wuA% z0`Pr*o`^gOltpBv=XsZu%jJtz^$DQ6QmJg7;@95-e5X_@rBbO>8hD^qes>TApFGzb zTcxD~psKgl-aePfWc~(_&*xuN)yIH0MdYUG^dll!phH!Mvf1n}0R{#J%3&DZNA=F= zshj=^o&D-am{n%YnZD3sm(RRJX!Is(NSR zAEv551`bl4cHU`>xd@=Iudgy|)~ppk0CcMA%Gy9JR@JA0rBht9*#+ztksPp!>dODU zh(zz0KIa_cHe=rh6pO{Hfh)r>Jlx;kfB3!H6$*up-rn9D!+$WG$mjE~H2!y!p`oGf zFbwB8jw2Hj6Neh->-9ZARezq#S(as4mStI%Wm%SGS(as4XY)Tye(;%j8fQBI P0000idhs^$HtVU7NLQK4ykspC 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 d9u{C{jQjny?I)zahlzX8SIl?ng= diff --git a/uhabits-android/src/androidTest/java/org/isoron/uhabits/activities/habits/list/views/NumberButtonViewTest.kt b/uhabits-android/src/androidTest/java/org/isoron/uhabits/activities/habits/list/views/NumberButtonViewTest.kt index c06aa5fb3..83609c9a0 100644 --- a/uhabits-android/src/androidTest/java/org/isoron/uhabits/activities/habits/list/views/NumberButtonViewTest.kt +++ b/uhabits-android/src/androidTest/java/org/isoron/uhabits/activities/habits/list/views/NumberButtonViewTest.kt @@ -44,7 +44,7 @@ class NumberButtonViewTest : BaseViewTest() { view = component.getNumberButtonViewFactory().create().apply { units = "steps" targetType = NumericalHabitType.AT_LEAST - lowerThreshold = 0.0 + lowerThreshold = 50.0 higherThreshold = 100.0 color = PaletteUtils.getAndroidTestColor(8) onEdit = { edited = true } @@ -71,28 +71,49 @@ class NumberButtonViewTest : BaseViewTest() { } @Test - fun testRender_aboveThreshold() { + fun testRender_aboveHigherThreshold() { view.value = 500.0 assertRenders(view, "$PATH/render_above.png") } @Test - fun testRender_emptyUnits() { + fun testRender_atMostAboveHigherThreshold() { view.value = 500.0 - view.units = "" - assertRenders(view, "$PATH/render_unitless.png") + view.targetType = NumericalHabitType.AT_MOST + assertRenders(view, "$PATH/render_at_most_above.png") } @Test - fun testRender_belowThreshold() { + fun testRender_betweenThresholds() { view.value = 99.0 + assertRenders(view, "$PATH/render_between.png") + } + + @Test + fun testRender_atMostBetweenThresholds() { + view.value = 99.0 + view.targetType = NumericalHabitType.AT_MOST + assertRenders(view, "$PATH/render_at_most_between.png") + } + + @Test + fun testRender_belowLowerThreshold() { + view.value = 0.0 assertRenders(view, "$PATH/render_below.png") } @Test - fun testRender_zero() { + fun testRender_atMostBelowLowerThreshold() { view.value = 0.0 - assertRenders(view, "$PATH/render_zero.png") + view.targetType = NumericalHabitType.AT_MOST + assertRenders(view, "$PATH/render_at_most_below.png") + } + + @Test + fun testRender_emptyUnits() { + view.value = 500.0 + view.units = "" + assertRenders(view, "$PATH/render_unitless.png") } @Test From 113a5028af7e705c8a5bd164e3d98c65a46d6175 Mon Sep 17 00:00:00 2001 From: KristianTashkov Date: Sun, 12 Sep 2021 15:27:03 +0300 Subject: [PATCH 04/12] Simplify the code --- .../render_at_most_between.png | Bin 1986 -> 2045 bytes .../list/NumberButtonView/render_below.png | Bin 1856 -> 1958 bytes .../list/NumberButtonView/render_between.png | Bin 1958 -> 0 bytes .../list/NumberButtonView/render_zero.png | Bin 0 -> 1856 bytes .../habits/list/views/NumberButtonViewTest.kt | 19 +++++++-------- .../habits/list/views/NumberPanelViewTest.kt | 3 +-- .../habits/list/views/HabitCardListView.kt | 9 +------ .../habits/list/views/HabitCardView.kt | 15 +++--------- .../habits/list/views/NumberButtonView.kt | 23 +++++++----------- .../habits/list/views/NumberPanelView.kt | 11 ++------- .../isoron/uhabits/core/models/ScoreList.kt | 12 ++------- .../screens/habits/show/views/HistoryCard.kt | 4 +-- 12 files changed, 29 insertions(+), 67 deletions(-) delete mode 100644 uhabits-android/src/androidTest/assets/views/habits/list/NumberButtonView/render_between.png create mode 100644 uhabits-android/src/androidTest/assets/views/habits/list/NumberButtonView/render_zero.png diff --git a/uhabits-android/src/androidTest/assets/views/habits/list/NumberButtonView/render_at_most_between.png b/uhabits-android/src/androidTest/assets/views/habits/list/NumberButtonView/render_at_most_between.png index b407ccd6264a4dfce901652485f8725f1d0af0d2..12063c42596410137b13f2f924fdc5ef517f9075 100644 GIT binary patch delta 2002 zcmZ{kc~sK*8pglimg5#uF6D}3S!%|ROPJOT3b#ruH8*Tr(%j0#Wzv33w%~?{ySa34 zR~y30Br!~j#!^Ymu4twrG#5sv%rrM7x3P2Pocq^(&-3^DKIi>B=lM-%RflmHy6c8N zd@8kaN(AK^MHs5hOs6EvTNhgh^|W)uJvOB5lAj;f{-CEtYO4E}RuVGq6D2bFAPZfQ z%H2nNVx|huGKYEEwv~^K#?W|p8vedkfzP2$+oG@iTkl2p!jgB)qq;BL`7e>;PS^Nx z485tjx!Kh=WqRkZs0TN_{Drd&ea1bX0A51u|JUP+vmB+U$1h&I!1IDa3W#WCj-Jgn zK{!U*n|tmJ`u53P=>ziBgXo8i){~s3_|m?Kz+zHLVGqn-Z%YlLpfeyaP(CH%&5=2) znuA1psn?Or%uJhFQnVU%v=|~eFqEl&=f`Z9W_1c$_i=yC3;!bGU8+e8mJ3TFsw!V~ zvfO=iPwv{{YgHS+x3rtQGtI4SROR`~QmN~8**1_5?>aSd?!H0Zl_Ov#W8Dut)42WL zy^ltc+uGW4U?Rku9AcI%4?RTddPE!?{o(p=_sU=TXn47SOo}R?5g}gn6p1yU$MisD z%$L_G>Z2=;xrw(=h2Z1a*ph%~cQ_soTdJZZF@w(ZC8PQgx7IVlzYYJ1sU7ItlUM6o z(Wqs{a}s@Uq8HLv8e+tB+08IQ!cFAIq;K~V70*@sv$Nr>f7azv&&%!a#WrK@A^S?l zL|NDHpKzxBY2*+Ti`@asL!<8hM-h&(`aS8yiTv!-353kvW#v#+wOgqE871L$2GuFf z9DS(nAto#Q6(uIZ935(XK$;dZ?~i)+I<46pLFidlW;h#HIcSD?J07&NYjE^!8(`c< zjc33?E27HVz3HYYUz9%I_CmI&61L;Wpx)u?4aBeFYleWY{`9B@$-QeME}Qm zpIXvKYrzFd;nehW%1%p5F|5JCcQs4(ro;NNLzabBg(6d{fbr!uZ{|)=pI2nML7kY{ zersJF00hqiuYD|=h&k|0f>QJz?NqU5D8J_hV>G3fh>Q5}^UbI5MYI)?6(uve!lIWJ z_a+EJ#utgj;vA+x7SL|Tte#liQQBP%%Isno``;l!yK^AB;O{lJxx>=WTI2>7fa(-bUS^E({jkaWY8AHCdlHL+h70Ds1CSslGro8nAY<< zP|f}&#XXUnn=t;x%0XZPw>v!R+udlp=)Bhkr36OOCC{gw6CfKwLOz{t>vmV@0TUsM zpmxd_#6GtztZm~Es?h`x*XlT&xrcz!8%b81X`of=7BuO9Hapa>X=|?MB}_L0EiEmU z0RaJvDt=AeEjh2a{+!H`h=ct?;T=qQ&y$EVDLGxHcD#|*#QS0OzQOXV#5IdDQBmK7 zYgEO!1jgr1EyG0QXjfFDd8*zAUXS<4} z?>mrYsQkiK4gT)h0q-G-X?a;$>aT3}WR+nuMIA6!S|?llI>Z(*;Leht7|J?pa0dkk z)9fR;r&*J`ySP(K(x(FmE9JiEv2yp?qrpQNOZ;DyzN+oCMYkrb$AB+T?lWSDDnQLv ze>~vro(BU0R?Gg}N4qH!jn|Q!yRoD8$gG!x;2S^#(LQ_o#Y8fhT$`_~z5zT0kR2sC zG7TX`SDcMPg&6--@7_+omZ=qFThr%yyXc#WV2|4xIP~D^`0vk_*d-Ch6$FD)6{xQ{ zqb%;bzfBj6CLpTRajz47j@QtG?HwYnZ7v&;R44wjJV(|kNvMn3K1@dGuoCx|6nIl`hg(Cx*F2C z<=?Fu!%8Bw3R?~Nd(cBFa&mmTEMsX@YU+tM*<9eak2lH-wrVl}nDSgne?Qc?m8xMz znDu2|#)sdbtyIVszFx9NLngUK+xglc4mIn!yLiMTc(51S5fJl!fx5aZCs%t~?p<)6 z?hUe)kFT>Gu-GCaLqkQ`L|WKz>|#{<)05VEqt1PSaR#cL9Ub&n@*xY04|ln7P?Nv) z6~276_}R2+UnECA9o*^nFo=mh9?W1}PlZ<)_a)6QP%VY(QH^P(0O6z+S`>cd%^y+! ftD?U<$!o$jy`JN(e~Wz*Y>XS>IR3#uLa+V>=S|Nu delta 1943 zcmaJ?c~p~k7X5`FL?A$+Dyx9~1VIg8C2X=p!R8|f7TLt0Xk;m25fBjszX%=LDiw$; zyDbDIA{9bpYXTSnQwq^Ez#xdoB1=rd1OyQSlg^p{=A3!w{q_F1=iPJPy-%U@RHuM| z#KB&Kz>B4l5hTwf(o}6>rs#DWo+FOk@_lhNn^vlO&pN&a|J@m^H`OMrnc4RF%{oJxk!#>dd9MMK~GhXDOdWF;${K}_B=xQUDaPyJ# z%b|mn9BJQ}_LKrDDKnF;yp~BW$&j{1@uoe!4gaUl@S?2F;oH(=2{0o-)1=28lJy`a3XnrNXYy8&Zz7AP3g@%vxN8=5 zb9w`S?)xFbrL6p0_NU+@|I#_)fZMDbLixeJk>;I9!-F;5I|;uUc?we;N)niR3f?n^ z9WP%vk*I{6_u5nDxA7|(Cj$jn@$p(bUAmtkg)Em;M!|``|G_~w49B57PxZa~8ES(E z*IA!0F%cz)2WkM&9nl|OtG1Ohk8e;-lKO5xu)qNFP!>s8Y^N1+gS`1(m%$>2@9ogD zF6jPZDDEdJ06*gHx*2f*IBl^0fcd=M*K)C^d7{`l>Cg+WZ1azJntyQor@6CrGa_Th zGQDB+1?i}BC5NL3fK8!xjq%=FS#-ZYOA+VAey7P|>q2Y+|Lkoe%5sRAy9%v}UL`$J zf>-SBZ;s0F*CG@iqqiovjQ)1tIw1^dP#5hYW?(v&$+(sI{m+g(^ul-49%q$%WPMr+ zg7TFz3>lk7AHN;>-yS>W#{JR3VA%<3DkLdspfbpSc6_m*VZvWm2l3APlX;qt0*7<7yFV*OB@r&KKn50 zGTqw)glW)T%i}5waW=PFzbfh5UVC#+EMB8eI&rJiAr35W16x&`{N&O9IQUjNHg+x}o|UFuj^SU_yy1oUGJfWlD`yJgv1mH{;YFTYi#Td}l~=U<6nntFgjlCmK-7 z!NMlGg#J$DpGR?bDqblK;V_95Op?_i_VP~<-F@X@!0ftZ;-~|%{<%U_L^-s-5%tvW zvlicYXPNK)zd|mSJB9@OP!$5>OifKwQ5`D^v6}!PkzADOCoCPXE$f}u2A;aAra+3a zJDkj!Nl)=%2;*QK5VQME?}+8ck5u^b!=t+z1?6t8=<(&4wV|hWzsoaI;%?^RX;nLc zr@TH6zvc*@Pbb@=n?r;!9O7TSU5WkuiFR?Tj*6$hzaW%K6+fcqRShTE~9XaGQRYZSG=5%XSyTx;?^FZ+*ksh_&?bWlPZlR zYY&^!Qd3Q_+2b;3n*qfgHQvI!drUu&t<#bEQdggP{X=XFnwU{AwSpqY=A~O`@xmz- z%F5ExQmK=GiBkpi3M_8{krxYyD4PAd?3ADhCqIJTLVwGqr0LK*x!MG-EZ=}qcjLSV z{{m=YH#UB$q?;BIVV=^jF^2y-V?%>B!aN1dplDTwGCkLRgJVj}$wze4~a=Dvs)E-u5%2rTOGV3iz=2C(J(gVVTuJ16VA&-$CyYrOg4i)czEsdsl@9^)LF)l zc3QTmbHMYka1oSWzr25lxPw1XKI3-M+Dx@GA5K6m7||3^VruvbqqDP9G&?(c0|XO| zx{7`>=z(#l$7Ep=Fk8t&AT8b_Af@kD8%MC9ea9*RRH(mD8#f$E^$41apDCe?8ofdv z5ExsgyV2Uc^mlT#BlgmGU~pjlH2nfGk& z#Y)fg6jDm{QV&^bv#~GUXZClE>BQ_C0Y{d(R9eiA3sK&Q zShO`^MJN`k?cIgm?d<#b$L^M4xyrH3-cHH;NjA6hzVGwibAR*B%)T=_b6{DPWm%SG zS(as4mStI%Wm%SGS(as4mStI%Wm%RL8)208P^Vp z9Dw2B;q=7B#AXp$3tU_)J}x5Ly1Tm{?Ca~R#2MF49qmq_(eUtadUA4dkBBU6sJcv5 z3*+PCH>v6>5r5G*;@Zt=cLI&dnFHBub`S7)q}>&xqocRQ z5Z88z8UR&Y6kRsPJQt~ZHd-elOV2kh2@*8`z~#{eV}H!Q>hDG5aI{WUua6LxC29af(;GZsZ{DZRh=gyUkAP#sXpG-)qk}q&bW3;}@tld(w(eu20XAQUjB+Nbc-`*}F_d1TVTSRUFK1_Ac9`5Ps`Dv3m zwnR$@AP9ob0$&E+bX|A8=XqBdW99)zfJI|tV_AS)F84Xl^E!Z+0E{trc%Ijh&wuA% z0`Pr*o`^gOltpBv=XsZu%jJtz^$DQ6QmJg7;@95-e5X_@rBbO>8hD^qes>TApFGzb zTcxD~psKgl-aePfWc~(_&*xuN)yIH0MdYUG^dll!phH!Mvf1n}0R{#J%3&DZNA=F= zshj=^o&D-am{n%YnZD3sm(RRJX!Is(NSR zAEv551`bl4cHU`>xd@=Iudgy|)~ppk0CcMA%Gy9JR@JA0rBht9*#+ztksPp!>dODU zh(zz0KIa_cHe=rh6pO{Hfh)r>Jlx;kfB3!H6$*up-rn9D!+$WG$mjE~H2!y!p`oGf zFbwB8jw2Hj6Neh->-9ZARezq#S(as4mStI%Wm%SGS(as4XY)Tye(;%j8fQBI P0000h<7}USbIPz7n*>po(qXtIv5x_@M(EgR1|3a3bGtlTB z7pWs{fRzdF2!`jF^_*Y_wZeQrL(vPv-QKP6){tMA4Vfj640`PABA0lz^-S*ty%}1h z;m^}A^zvedOgpw_FNNaQKhi_{I1h|Y$R>5o{hw6lXVVD@d$Ya0{q*%~%lv_bkb&n~ zGD%5EL*Lw;)V*OqxubvoR+}o|0o*vd7)c;;`8#WO>b7GJ-qRc^#4`%^{$LQ5f^%65 zAyZecT&aku3_P6^FN60>nt#(d!IX4$uVTl~oHtFg^l`HY;UCDj7UfhBbp>oLf82T= z&?%TuobaX;en}fPV4`GndOyDX{!ES+Rg8{0EA{)YYMY-kv55J7l>u%J3v7Wfpylk3 zqPm0Q{#kynVK?aeH%nyab$pLO}_Q(Iz7?`={`^m^mfR6aool-S=mweg&NC{{*J3uvF zdv*Yy%K`&NAD)^UYAE$>sEA=9>*H{UrI zJ(C?<72xHq3H03A2!my}Dn^Oh$RTlfPBe~1`5tEm7n*xl%h)+lZn*RW$CZ*{*V#Br z`GT)aXa7qV*`HqJrAl@2Y(2GV``mVu;@#4QL4AmItq%Xb&E>=xj`C(PB!6B^SU(g((q zL5I2?*uPT-zg7uceRE1mCC}IN_%q>LLmm2D0kAJU@8j#+R5pN^?+kq(s)L{0#STwE zvF(y97HjX0rzZ#MssG?YYDwQ?U31pb)+($ZKo)7PXgy+sya~Hm*|Fl7mE7?`tyET+%hrW9#9;eLQAb!UPgU-pNQtIcsKl7P0kQjMqVH`GH#|>vZIel04)$ z+K-YVQ)x-d2jFIs8|6NAoa-QN*d4s5B;jZj1owg?b0nvdgSN?kX6(Wq@sV~)23%RH+m}cLURJ=Z% zGc}pps4j;bRt_Ys`aToVwD@~wwq|Hpwzia&a$;C0u+#5wH*Ny0luv+g4IPpi2vKb~lD zadD@(yB~fJm#*YjkV9lhaGcw3Yb&Jnyz^3CgQ%2m41Y7=UF8Uwy4j$s6k5J9yLlpv z1_4|0B5|xj3rMYA1<(kpN$u~Vn6s3(9&!{pAV&HksS=07)stI5+ls{9v6Bp?1U`93 z9W!m#SMau;8KF(oNLyf}F|hum2z{7=DLz&eO}R174xn)po>|H}!xmjMvm`|9*)v*2 zP?M9pmp7SArXm~;&$mXH3{^FzkLPPyAmW#H_kEG&qXD?be3KwD`dpdPw}|SI4V8Zg zz#{&a;{$W$)*f2C2p0PScew)$4(GfyW(S@AACo!$!*hTGNre|95ZaMBE+7h9>uU(A ImFMq&1BSkpqW}N^ diff --git a/uhabits-android/src/androidTest/assets/views/habits/list/NumberButtonView/render_between.png b/uhabits-android/src/androidTest/assets/views/habits/list/NumberButtonView/render_between.png deleted file mode 100644 index e5a98fc8987e73c6edc7c3de02ed3801638066bc..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1958 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 d9u{C{jQjny?I)zahlzX8SIl?ng= literal 0 HcmV?d00001 diff --git a/uhabits-android/src/androidTest/java/org/isoron/uhabits/activities/habits/list/views/NumberButtonViewTest.kt b/uhabits-android/src/androidTest/java/org/isoron/uhabits/activities/habits/list/views/NumberButtonViewTest.kt index 83609c9a0..75166bfab 100644 --- a/uhabits-android/src/androidTest/java/org/isoron/uhabits/activities/habits/list/views/NumberButtonViewTest.kt +++ b/uhabits-android/src/androidTest/java/org/isoron/uhabits/activities/habits/list/views/NumberButtonViewTest.kt @@ -44,8 +44,7 @@ class NumberButtonViewTest : BaseViewTest() { view = component.getNumberButtonViewFactory().create().apply { units = "steps" targetType = NumericalHabitType.AT_LEAST - lowerThreshold = 50.0 - higherThreshold = 100.0 + threshold = 100.0 color = PaletteUtils.getAndroidTestColor(8) onEdit = { edited = true } } @@ -71,39 +70,39 @@ class NumberButtonViewTest : BaseViewTest() { } @Test - fun testRender_aboveHigherThreshold() { + fun testRender_aboveThreshold() { view.value = 500.0 assertRenders(view, "$PATH/render_above.png") } @Test - fun testRender_atMostAboveHigherThreshold() { + fun testRender_atMostAboveThreshold() { view.value = 500.0 view.targetType = NumericalHabitType.AT_MOST assertRenders(view, "$PATH/render_at_most_above.png") } @Test - fun testRender_betweenThresholds() { + fun testRender_belowThreshold() { view.value = 99.0 - assertRenders(view, "$PATH/render_between.png") + assertRenders(view, "$PATH/render_below.png") } @Test fun testRender_atMostBetweenThresholds() { - view.value = 99.0 + view.value = 110.0 view.targetType = NumericalHabitType.AT_MOST assertRenders(view, "$PATH/render_at_most_between.png") } @Test - fun testRender_belowLowerThreshold() { + fun testRender_zero() { view.value = 0.0 - assertRenders(view, "$PATH/render_below.png") + assertRenders(view, "$PATH/render_zero.png") } @Test - fun testRender_atMostBelowLowerThreshold() { + fun testRender_atMostBelowThreshold() { view.value = 0.0 view.targetType = NumericalHabitType.AT_MOST assertRenders(view, "$PATH/render_at_most_below.png") diff --git a/uhabits-android/src/androidTest/java/org/isoron/uhabits/activities/habits/list/views/NumberPanelViewTest.kt b/uhabits-android/src/androidTest/java/org/isoron/uhabits/activities/habits/list/views/NumberPanelViewTest.kt index f5d992f62..da0a2eb8a 100644 --- a/uhabits-android/src/androidTest/java/org/isoron/uhabits/activities/habits/list/views/NumberPanelViewTest.kt +++ b/uhabits-android/src/androidTest/java/org/isoron/uhabits/activities/habits/list/views/NumberPanelViewTest.kt @@ -57,8 +57,7 @@ class NumberPanelViewTest : BaseViewTest() { color = PaletteUtils.getAndroidTestColor(7) units = "steps" targetType = NumericalHabitType.AT_LEAST - lowerThreshold = 0.0 - higherThreshold = 5000.0 + threshold = 5000.0 } view.onAttachedToWindow() measureView(view, dpToPixels(200), dpToPixels(200)) diff --git a/uhabits-android/src/main/java/org/isoron/uhabits/activities/habits/list/views/HabitCardListView.kt b/uhabits-android/src/main/java/org/isoron/uhabits/activities/habits/list/views/HabitCardListView.kt index c1935be75..58ee2b36a 100644 --- a/uhabits-android/src/main/java/org/isoron/uhabits/activities/habits/list/views/HabitCardListView.kt +++ b/uhabits-android/src/main/java/org/isoron/uhabits/activities/habits/list/views/HabitCardListView.kt @@ -36,7 +36,6 @@ import dagger.Lazy import org.isoron.uhabits.R import org.isoron.uhabits.activities.common.views.BundleSavedState import org.isoron.uhabits.core.models.Habit -import org.isoron.uhabits.core.models.NumericalHabitType import org.isoron.uhabits.inject.ActivityContext import javax.inject.Inject @@ -98,13 +97,7 @@ class HabitCardListView( cardView.dataOffset = dataOffset cardView.score = score cardView.unit = habit.unit - if (habit.targetType == NumericalHabitType.AT_LEAST) { - cardView.higherThreshold = habit.targetValue / habit.frequency.denominator - cardView.lowerThreshold = 0.0 - } else { - cardView.higherThreshold = (habit.targetValue * 2) / habit.frequency.denominator - cardView.lowerThreshold = habit.targetValue / habit.frequency.denominator - } + cardView.threshold = habit.targetValue / habit.frequency.denominator val detector = GestureDetector(context, CardViewGestureDetector(holder)) cardView.setOnTouchListener { _, ev -> diff --git a/uhabits-android/src/main/java/org/isoron/uhabits/activities/habits/list/views/HabitCardView.kt b/uhabits-android/src/main/java/org/isoron/uhabits/activities/habits/list/views/HabitCardView.kt index 4fca5befa..d6e7a2ad3 100644 --- a/uhabits-android/src/main/java/org/isoron/uhabits/activities/habits/list/views/HabitCardView.kt +++ b/uhabits-android/src/main/java/org/isoron/uhabits/activities/habits/list/views/HabitCardView.kt @@ -109,16 +109,10 @@ class HabitCardView( numberPanel.values = values.map { it / 1000.0 }.toDoubleArray() } - var lowerThreshold: Double - get() = numberPanel.lowerThreshold + var threshold: Double + get() = numberPanel.threshold set(value) { - numberPanel.lowerThreshold = value - } - - var higherThreshold: Double - get() = numberPanel.higherThreshold - set(value) { - numberPanel.higherThreshold = value + numberPanel.threshold = value } var checkmarkPanel: CheckmarkPanelView @@ -243,8 +237,7 @@ class HabitCardView( color = c units = h.unit targetType = h.targetType - lowerThreshold = 0.0 - higherThreshold = h.targetValue + threshold = h.targetValue visibility = when (h.isNumerical) { true -> View.VISIBLE false -> View.GONE 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 1d474a22f..08685026d 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 @@ -37,6 +37,7 @@ import org.isoron.uhabits.utils.dim import org.isoron.uhabits.utils.getFontAwesome import org.isoron.uhabits.utils.showMessage import org.isoron.uhabits.utils.sres +import java.lang.Double.max import java.text.DecimalFormat import javax.inject.Inject @@ -83,13 +84,7 @@ class NumberButtonView( invalidate() } - var lowerThreshold = 0.0 - set(value) { - field = value - invalidate() - } - - var higherThreshold = 0.0 + var threshold = 0.0 set(value) { field = value invalidate() @@ -167,15 +162,15 @@ class NumberButtonView( fun draw(canvas: Canvas) { var activeColor = if (targetType == NumericalHabitType.AT_LEAST) { when { - value <= lowerThreshold -> lowContrast - value < higherThreshold -> mediumContrast - else -> color + max(0.0, value) >= threshold -> color + value <= 0 -> lowContrast + else -> mediumContrast } } else { when { - value >= higherThreshold || value < 0 -> lowContrast - value > lowerThreshold -> mediumContrast - else -> color + value <= threshold -> color + value >= 2 * threshold -> lowContrast + else -> mediumContrast } } @@ -195,7 +190,7 @@ class NumberButtonView( textSize = dim(R.dimen.smallerTextSize) } else -> { - label = if (targetType == NumericalHabitType.AT_LEAST) "0" else "inf" + label = "0" typeface = BOLD_TYPEFACE textSize = dim(R.dimen.smallTextSize) } diff --git a/uhabits-android/src/main/java/org/isoron/uhabits/activities/habits/list/views/NumberPanelView.kt b/uhabits-android/src/main/java/org/isoron/uhabits/activities/habits/list/views/NumberPanelView.kt index 94980dfac..0a5339ce0 100644 --- a/uhabits-android/src/main/java/org/isoron/uhabits/activities/habits/list/views/NumberPanelView.kt +++ b/uhabits-android/src/main/java/org/isoron/uhabits/activities/habits/list/views/NumberPanelView.kt @@ -54,13 +54,7 @@ class NumberPanelView( setupButtons() } - var lowerThreshold = 0.0 - set(value) { - field = value - setupButtons() - } - - var higherThreshold = 0.0 + var threshold = 0.0 set(value) { field = value setupButtons() @@ -98,8 +92,7 @@ class NumberPanelView( } button.color = color button.targetType = targetType - button.lowerThreshold = lowerThreshold - button.higherThreshold = higherThreshold + button.threshold = threshold button.units = units button.onEdit = { onEdit(timestamp) } } 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 037721d82..f96d7857e 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 @@ -92,20 +92,12 @@ class ScoreList { } var previousValue = 0.0 - val numericalUnknownDayValue = (targetValue * 2 * 1000) / denominator for (i in values.indices) { val offset = values.size - i - 1 if (isNumerical) { - if (values[offset] >= 0) - rollingSum += values[offset] - else if (numericalHabitType == NumericalHabitType.AT_MOST) - rollingSum += numericalUnknownDayValue + rollingSum += max(0, values[offset]) if (offset + denominator < values.size) { - if (values[offset + denominator] >= 0) { - rollingSum -= values[offset + denominator] - } else if (numericalHabitType == NumericalHabitType.AT_MOST) { - rollingSum -= numericalUnknownDayValue - } + rollingSum -= max(0, values[offset + denominator]) } var percentageCompleted = 0.0 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 f1c05861c..7c97cbc72 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 @@ -115,10 +115,8 @@ class HistoryCardPresenter( } } else { entries.map { - if (it.value < 0) habit.targetValue * 2.0 * 1000.0 else it.value / 1000.0 - }.map { when { - it <= habit.targetValue -> HistoryChart.Square.ON + max(0, it.value) < 2 * habit.targetValue -> HistoryChart.Square.ON else -> HistoryChart.Square.OFF } } From fe1d5c66cbafd889dfc494124ee4288cae1f4615 Mon Sep 17 00:00:00 2001 From: KristianTashkov Date: Sun, 12 Sep 2021 16:23:42 +0300 Subject: [PATCH 05/12] fix bug in history card --- .../uhabits/core/ui/screens/habits/show/views/HistoryCard.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 7c97cbc72..8798f027d 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 @@ -116,7 +116,7 @@ class HistoryCardPresenter( } else { entries.map { when { - max(0, it.value) < 2 * habit.targetValue -> HistoryChart.Square.ON + max(0.0, it.value / 1000.0) < 2 * habit.targetValue -> HistoryChart.Square.ON else -> HistoryChart.Square.OFF } } From 65d237254c945f8f02b6c921dc0ae3df1d01ee62 Mon Sep 17 00:00:00 2001 From: KristianTashkov Date: Sun, 12 Sep 2021 16:56:08 +0300 Subject: [PATCH 06/12] simplify scoring code --- .../org/isoron/uhabits/core/models/ScoreList.kt | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) 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 f96d7857e..a062f7ede 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,19 +100,18 @@ class ScoreList { rollingSum -= max(0, values[offset + denominator]) } - var percentageCompleted = 0.0 val normalizedRollingSum = rollingSum / 1000 - if (numericalHabitType == NumericalHabitType.AT_LEAST) { - percentageCompleted = if (targetValue > 0) + val percentageCompleted = if (numericalHabitType == NumericalHabitType.AT_LEAST) { + if (targetValue > 0) min(1.0, normalizedRollingSum / targetValue) else 1.0 - } else if (numericalHabitType == NumericalHabitType.AT_MOST) { - percentageCompleted = if (targetValue > 0 && normalizedRollingSum > targetValue) - max( - 0.0, 1 - ((normalizedRollingSum - targetValue) / targetValue) - ) - else if (normalizedRollingSum <= targetValue) 1.0 else 0.0 + } else { + 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) From 4355fb4d687e7fed734b2a0635f274e0e86c1098 Mon Sep 17 00:00:00 2001 From: KristianTashkov Date: Wed, 22 Sep 2021 15:49:51 +0300 Subject: [PATCH 07/12] start score from 1.0 for at most and reflect the same in history --- .../NumberButtonView/render_at_most_above.png | Bin 2294 -> 2338 bytes .../list/NumberButtonView/render_zero.png | Bin 1856 -> 1939 bytes .../common/dialogs/HistoryEditorDialog.kt | 2 ++ .../habits/list/views/NumberButtonView.kt | 4 ++-- .../habits/show/views/HistoryCardView.kt | 1 + .../isoron/uhabits/widgets/HistoryWidget.kt | 5 ++++- .../core/commands/CreateHabitCommand.kt | 1 + .../org/isoron/uhabits/core/models/Habit.kt | 5 +++-- .../isoron/uhabits/core/models/ScoreList.kt | 7 +++---- .../screens/habits/show/views/HistoryCard.kt | 6 ++++++ .../uhabits/core/ui/views/HistoryChart.kt | 3 ++- .../uhabits/core/ui/views/HistoryChartTest.kt | 1 + 12 files changed, 25 insertions(+), 10 deletions(-) diff --git a/uhabits-android/src/androidTest/assets/views/habits/list/NumberButtonView/render_at_most_above.png b/uhabits-android/src/androidTest/assets/views/habits/list/NumberButtonView/render_at_most_above.png index 5bd20a34f244dd23221010487efa7e8ee62530e4..4cea804986c070e0879924ed281e4bb417a0e1dc 100644 GIT binary patch delta 2297 zcmaKudpy&N8^^yhq8p8{II>1=TT&6iL559XiZ0qtERoB?`mJ%$cjeNw&?e=6>1VE4 znz=S@mFp4exTNITw2-Kka>9;5 zN9E4t#@zHC&}kQr*`%4GnH+BgQgxyFK7}UR;?@u?J@P*9Be!tAhzQ+b@_oPbV`a52 zByw@4xdAD?`&08X(-!mLit+(G=|9>FuwkP;b=xb6eqK8VNRb)=Sy|Fq-I)2W6yoK( zud{55?Fz(!y|Xbr;qRYYe}4;Jx&Q|;7pfTr@;cIz6K|!n=X6J*-%HitR zr7tQyeSxQ&LeBNy^8$`T2nYK?XPzngi?Ah6A7sd`Bm;O)-t_aV1AC+MX~F!56lx?s zU3nWTdFsRJ*#&n=m!Y+e=v>C1t&UYLq6GX8INbtD)c-D@=7K?MDC!)JIIs}I}%ie>g>cn5gBfK0t-l!Aq7vRQ`7aeGqX3 z^9fj}VSSjKB*7u`TU`;z6xNR;MGK!I@{i3(^#)qk$E1tbyB0XeQh1e>vjRP~I0%Vz zz2@NL^z0E!fO4&1GKXwL1?lY@B4Le#Qb9A4vMjm^4YKQv>86fwpxAbo$j$vE9fJ@-bjv6t8ACc|e$u9FbB8xV2-fPV`2FDEpz%cU#6bjdf;TloD$>wd&;&v;;*G#{XYu7OUK=Y{ ze%!N&LQV3wvZ&sBf^;_NQ0gT)k%r8~Kl}Z@UQqg2Wqb)w`7tq_3)>IZe|ElWp!IOo zogyf^S3=YKn#kJX=@>V`OFUd;PTXQfjrE2Q(FAXu7jNI@YO+QVrpO7!vh)dFlK;(3 zPlxNs+B)SXmdA*MmR}X3^Mm9v)mv~E_q$8s0H4n<#-?smR{?ijfA-CYdnss zFTf?Mcb=Zv@{f2uKM4I%{1IcHY)Rkr*R4ubzSvJHPmy1KZ%}1Rz{G*N!hExT*2*hmBkZ8 z9$mb}6mLzn34?)sVs^7c9d#lWK6s-e4#_jTw>fQ+hPz_; zCa|%|s;twbBQn>nGk^yZJ-`%i>s+h!hu3G9EDe|S)XnAk!!1E31BK*FaTrFh=o7}& z_~2|s6o(m~t@}qkpHEc5aRaqO-~ zK5gLUD43;)4|VZ=4pIA%KYyo@clgGnu|&Qya{8Frwwkjv!;&}iSmL%j;2{W{v$CzY1DzKsaGui>K$85tSzycMgo z<|tqsRjgJPZC57O{k)3sMoZKt?h299KM#q`6T@Y@)6Tb&K2I$! zE@p950^ld1r;bwWI!kdq8_em(u)SsIgDJaL3-bwu52S@ zA4aUy+1T9NYyb?^#WByH?i}xT!(6OEqo%ETb^;MZniky^0L3`dT68F(2!CN4iww*# zTPK~M=Jq+_5j#7(b6Q$jZE{f}Pg$KL`W3M(W#C?g4gz@zpo^uIC#sfh0IO8#LAtB6 zIDzpT&X?0=mw*XF>s+Nf2vxie<&BOPgzWkF;X_$k$K_LZYR|uGOEF3B%&kSwUV$w^ zmrF;6Q6TwTqc>H3`;RqfW#Hi3U4oe#b>~iUg{x%g>sXjQyM;b*TAsh9%BwylU4P&eYm;YyP{nxaIFs?+Zh*0 zGLsYj42LvW#08g zv97ju>-P)<1c04Z84q2z=d(L1*dq1aGs#}Nw7ZaXhW@e*uWok-TB11lyQ0CZox0Eq zT1mF2n8M)GgFUd!$V3^faOD}6jAmA(0xJkGKQ>*QrW(BhsgfHVB>VYB3i-ad&&Z7h+i(-Q9nf~8qW~h995dHUR>GO` z(X)w#g4w1tmO$<2ys{8WS?4r-Nj4FnNmp1~f!e$j)|x4bVqw_SXLpLH5)V)0m6VjwdQNgHD^bx4<4=0kHzoes9=Yf`zgb5|=Ud`o z|7?E>{NwDb&3590>n@_b>M7jox8+rv8^uef!~5C=#r`+> zMK2@}3m2M0%2L5B0JGj$cs&3eWBo59`rjwv^jTM8$+w8G9#RI@1Ly34;U4z8_7|kd BVmbf- delta 2253 zcmaJ@dpOe#8~)8PY9VvZp|wzHN^*D|qBdbkSm|XGwU+Z?ms7}Z_;Q*Yrf3MsM8YeS zZ8;xCsnO>w=CGIV-AD*Ay*~Z*y?=erKhHnUeO>qUT+e;qPsyPTxlC)Y*bZfljVxFy zh)%=0stiewEHhe67pA~9OVJy8ij=Nfwg0af9(XfOj*`n#ar1lJOE6ekTKX;b1Kz)^ z`uF-$p#Ag?4u4X{EXGh>MJeY6P{lX>M~?)4@n zajt&0%n25j6(a=KErD?dd_jsN5CaL(76E$403#?A`jheEg^p`~I$ z3(QkBNk99f(ag+@lkWw;)m`P9D_x;v1w-S{c~sgeUR{#SBp8#6{Hh{`I`e%zl9LgE z)T2aYyR(5!OsqE=k~YG%%<$DGz~r{v7&wnxkq&frx6~Jx0s0009=RG|RD4=o&J<0e zIQt^WXdoV;(fh`pzPeSvgvlRdfzKu-brw zVw_qs&^9#i{8G^A4%>sOx$AuABkL!04|J%*Zyk&bKS|#Pifd|W?t6QCgN6xx?OONR z`^f}@2V{FXiK0T>SQiY@(I-`B&H);iE#a#(2!C(y{~(nu-*% z@OZbcn|bt4o*I9jCjc%Pp;l+J)ix&6l+o zo0x{)cKW(SoE9-pD0u;rMms2*r2*tya3tP$$@=HvBA#m$@ut=T+4GQ?Q@+xmhZB9TIDE zK`22+D;{m*E0^pNau%UalOr87Wv11u-W)qM<@0)ys1O;I9qBHTo9Tc0~Zr+QF2b=fJ0w9%{;vs}@6nj3E^{^PP&BNj)J2 z`jBK4PjscExff$!VS%_rVthf<2g9+IdZ|4+ssXo~3|Vrq>g5{Lez%OTsT=pf7tX$g z%R+Z;+NWr6ZaJas?z5{V($k3EzCSAo}S($Dk>_I zwI~+6CnEM?_Lj9}Q4wFFywpAFu5u#fBUv}qU?s3)%eBH+>z?`4Q_Cpl*KxnVTrv(h zq7U~&^Lzkx`0D@yq4cm=+ih#v%k-L}#fjZ=Fxmg?c|?#1KwTQ?*Fi*|zvMV;*x$|c z-Zm*_v4o||!cAs1OS)Ab)p?BZfH&h@!E-dA5jwu^-Ep_DvD!U)O29{<3|1oOzu5X7 zooS4lz@2!UgLbNyX+7lxyD!>0FYrMVG7+{p=A3AfDp5}ipw2bO{QGsSN8UCl;avq* zHCUE3H0!UNgh|(=_GIiuM)%;t{K_-c^sbqcI{S)XaH`=vo8M47V8~L7f($X9o=N&T z+jhvd+2bffS;)o8?=no9gb7;8F8kaBlWu~d;i-Jycwu2-m(4pf;EP;RlL=H!&EDk& zJYK6&p|$M?wp8MqBVa6g1C#_1%j@PrHxJ$PYf@_c3I8lJtywLIHH}rh>s_^LLqhLJ zs_BcGJoMfCZUHB0=YLcrF>o!zQO0C^h{djUFIV)*QMh2%dcupdmXn>vuiYcL4*hn2 z_Z&OK5(z&BNDmz+uLFNOthSA7&YOISfPC))hii|R8riQJkf9(Do8rp~QzCPK8zG({;M1& diff --git a/uhabits-android/src/androidTest/assets/views/habits/list/NumberButtonView/render_zero.png b/uhabits-android/src/androidTest/assets/views/habits/list/NumberButtonView/render_zero.png index 5f6bf8c65cf649ef2b9f437feaea7fed323ec774..a405c0092d9cc66fdd53d00eba078fe57112d507 100644 GIT binary patch delta 1924 zcmZuyX;jjA7yf|@wrS{?xrW5#R9J&c*{EeoBiPY#tEk);oJ>q9MRWN(c1kKn1>4lp z8kfQtN==NN}mp6rselD7Q3XNPX$cob#UZ-VgV~J@>dRCwoF;R3FX+A zqkioN)}tW?Xe3I-p4rW zwFagCJJ#FjTvW)@qQeEwjWUQZoWgzPKMJ?^w1nd$HfrLfX}`GqbhL>>MvyK{Zihx4 zHUL60m`Tyw=y){oXRpXex!-VeH<1em$kO{*)z83zf%}COW|3wtYS<=krA^Drucpri zJHs1>K74*!VgfK)+wM$p>h)O6bYWTzJ-c-U@-C|kPAB-rEEd&|PrMn17`V#gq1XPl;e!8!lbK=U=3XM( z5~j({Whs9n4U|zIW;2v-dB4|x?&PZMC@F1qHMIahC^U^OV3N#;UB0uem`C!-FcGs3 zo>8$g`tZvI^qY%z8wOA-~@40 zFUkzi1%|*AR9IR;R*C0%6utXZPPaqw&G13wxz{7vfpRqz#n9;Gl@qw8%yiIlMKo?< zXlTeS)1JkuoPaxVYNJM9co)X8AA%{CnvP4C>Yg&vf~0W$y(yl;@_ou`%CL2qlw!eL zK+o9;9ZO0|j6p^P?Tm_-KAwD)KJtOb4gcZ0LXZbdYgI505Oq%J$W=wGKNlT;xtC>Ai^0jYpaIh|HTZ>gkc#u||kNS(swp7({4c0PyfY;nRu~_^F=5;GD!>r>B zni`DXQIUo~vI0)2>XmNdD$uJlgn|D4hCMv%=JwDnP2bz43({l-r?ayBcUM-XhpPi1 zq{fVF(kkbqbtS%7z|xg_s-94u#LSu%j(ybw9+Fmzj`oYPB6^bj3LxnoR(}{eIig-{ zA#$C&vJE>TV~R8qkJg{`6=joaLk1Wx>W;(k${-dEe0hL-OIoXM((~+@hYrQ!k|}Kg z2T+EecLa=o@Si>zqNj`02;G}fHy($S!R?$Q5&PQd; zDE>YpGzq)zAJ1Q=!A9(s(U~~i0Snf>I*%i-1O)NIpV3`)&ljlD#bQ|EK|(t>d^$1mJBZ(57UkwZi3#d`-|*>q=~QA&P$ z`03-Wgi`4;`9Y4#X(OeOUldwi|MLA2z`9LLFaA*2a#0Z9_j&C6XA^)b5r$nFl$iC! z(B>ko3mVUIZdu^p$o*n4u`xTbDDS&7mV^1!N3=A)-fN!GjON- zM(oj;hdNvdTNZUNt|=)XKqo9^Zg=V^eaZQ0BjB1diJ$DgfR z^L6K5pB6Q^xw;O}#B6J)redsD z<0mYB8sSp9?xs-O3a1M;gIJ6;)zSBV6D!ev+2BZWU}1F#IIoLYK-?FA=Xyuw{}%(< BuiO9t delta 1841 zcmaJ?c{JOJ7XFdMQXQm2>9nOa(<-f?v9;DX(IQbPQc6>lT02$yQk9tLSR127V{h$G zNKk@U)3H}-Yh$U3Xe}j0FliLwWzPHSo%7E7{<(kMd%t_X@7$Xq$B<3`1rH9jv~iLW zAy&#e2>>JnHb{h1RQ_r{?%oM!#XkJfP_J|y$^qkAG!FG|Gmd-}Y&O#n+o*xnd<^hW z6pVl6=3l5%OgaYB<1BTg4X`xf9l`P(vYr!cp_bSW7$|08q}#g{-Wu}LKtpEnV}l;M zy2vG7ZT*1vf*zZOk2L&o=7nDF!(r2ot=UVV>mTW%eVhkICu9@5=KfEp^Rw=RguU6` z-hTG_H6m|tA!P8mmP}$|;_x>&M|E!)Q0Cy@ztyG+xC1xNE=Ce6xcr^9J9XPJ2k&Y2 z<>Kl2d%rV?O2IjJ=0eEy)hk!ZV=4nrXUEC-CC-K@4QSc>W9aVS*ngGXYuHUWns@=?5q`ObeBg&K_x1I)Z^*>Wp2x|Yfs0{S zMPY1Z_ze2bw6sDSQgT0}g9Oi!25R*Ru48{Kw<={jM8vNzpnb$&l2@NhUWB&{q5-PY z#UBCaDC%KB$;sJ{XJr7hVSYFqP7ZK=vp?~lx&Zr{?^`hY;Fv|Ch<6VV{(92S7&D$s|z38Yu;9u%Rs{U2I{3*m_O zTz-w%LZ(eKZ@zObnoW9G72xTui3fV_Y=pr`t%_0N)^aE>PP7-3@;%lJE;RS9ma%oD z+;Hv*jx8a>uCu%l^7&tz&i*C~uvFemAMLMY6%N;{h~NQER-Og@TnimZhd6aSMV5DhM;ncNep4 z7vRAs%+=Jau#_`#yVW%O{hiFg1?}Yi-^n}8NobIJQC*l&ftR_yJ9{5zW2j1;DdytR zbr+98=>@&{LVs+#nDU0ndWoZ><8*?^ljN179G{`4em+2s39l~8c7rezvpbv?{?vx~iH z62x?EwM8}pK>)X+q9RhvCD7c5!Z$i-4+H7&xB2tzsX zsXgP98Z#Zz2gXxDhq@lvy;BCiRta2vb4p4j*VpuThH$Q-4s$La*vCuH`}q1cl@223 zJ44@x>P+q8Mkb-Sc1b3axp&9Ig9G)@e{dnCxbKOsIdf@i6_y_$i!xWV8ns5Ua7yz1J45s~>@Mc4H*`aC{#Wi$uo#aG1P8jcLD4EdEj#LjO4A(n)6@gpymT z?zSGaKt6w4Qn;8qijRl+eNCd8OS%NQZ#_D=kDZY)fkcsa(v#3mnpqx&EInuAbH-5eU_fp8DKU zT1a1$lVOPl`c=$RdEel07pY69t;v5-wBX8>Kc9Vt;DymU^(A_0As01fFFQvczlUCl zE^{QN+BGE>t&e0+PvtbK%b`Y;11na2GlVoP{+^kQ83snuma9K zNua9~TAnehc`}R!0V8;kUd#duNUdHu&F=VLGnKaDjnoB3Dg)2Y1<<(h83=hN z*rKy$mV`(>J4UOBX;M=6^5($6fFc|Y&$B|B3|BR#P2_1=Amf&H_kB@iV*y@I_$EPQ z%(+sfZxPj_8!EpDz#{#Z;{tQ!)*e~B2p0PScfJD*jby(xW(A#=P1O7!Uj{gkRCqB8 Tp&gmy0wUO0T|-hWJ%0N)amSc; diff --git a/uhabits-android/src/main/java/org/isoron/uhabits/activities/common/dialogs/HistoryEditorDialog.kt b/uhabits-android/src/main/java/org/isoron/uhabits/activities/common/dialogs/HistoryEditorDialog.kt index fcad26125..5edae637f 100644 --- a/uhabits-android/src/main/java/org/isoron/uhabits/activities/common/dialogs/HistoryEditorDialog.kt +++ b/uhabits-android/src/main/java/org/isoron/uhabits/activities/common/dialogs/HistoryEditorDialog.kt @@ -62,6 +62,7 @@ class HistoryEditorDialog : AppCompatDialogFragment(), CommandRunner.Listener { firstWeekday = preferences.firstWeekday, paletteColor = habit.color, series = emptyList(), + defaultSquare = HistoryChart.Square.OFF, theme = themeSwitcher.currentTheme, today = DateUtils.getTodayWithOffset().toLocalDate(), onDateClickedListener = onDateClickedListener ?: OnDateClickedListener { }, @@ -101,6 +102,7 @@ class HistoryEditorDialog : AppCompatDialogFragment(), CommandRunner.Listener { theme = LightTheme() ) chart?.series = model.series + chart?.defaultSquare = model.defaultSquare dataView.postInvalidate() } 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 08685026d..405bf606d 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 @@ -162,14 +162,14 @@ class NumberButtonView( fun draw(canvas: Canvas) { var activeColor = if (targetType == NumericalHabitType.AT_LEAST) { when { + value < 0.0 && preferences.areQuestionMarksEnabled -> lowContrast max(0.0, value) >= threshold -> color - value <= 0 -> lowContrast else -> mediumContrast } } else { when { + value < 0.0 && preferences.areQuestionMarksEnabled -> lowContrast value <= threshold -> color - value >= 2 * threshold -> lowContrast else -> mediumContrast } } diff --git a/uhabits-android/src/main/java/org/isoron/uhabits/activities/habits/show/views/HistoryCardView.kt b/uhabits-android/src/main/java/org/isoron/uhabits/activities/habits/show/views/HistoryCardView.kt index c60e84bd1..f429e1718 100644 --- a/uhabits-android/src/main/java/org/isoron/uhabits/activities/habits/show/views/HistoryCardView.kt +++ b/uhabits-android/src/main/java/org/isoron/uhabits/activities/habits/show/views/HistoryCardView.kt @@ -43,6 +43,7 @@ class HistoryCardView(context: Context, attrs: AttributeSet) : LinearLayout(cont theme = state.theme, dateFormatter = JavaLocalDateFormatter(Locale.getDefault()), series = state.series, + defaultSquare = state.defaultSquare, firstWeekday = state.firstWeekday, ) binding.chart.postInvalidate() diff --git a/uhabits-android/src/main/java/org/isoron/uhabits/widgets/HistoryWidget.kt b/uhabits-android/src/main/java/org/isoron/uhabits/widgets/HistoryWidget.kt index 2e26a487b..c8d4dce44 100644 --- a/uhabits-android/src/main/java/org/isoron/uhabits/widgets/HistoryWidget.kt +++ b/uhabits-android/src/main/java/org/isoron/uhabits/widgets/HistoryWidget.kt @@ -56,7 +56,9 @@ class HistoryWidget( theme = WidgetTheme(), ) (widgetView.dataView as AndroidDataView).apply { - (this.view as HistoryChart).series = model.series + val historyChart = (this.view as HistoryChart) + historyChart.series = model.series + historyChart.defaultSquare = model.defaultSquare } } @@ -71,6 +73,7 @@ class HistoryWidget( dateFormatter = JavaLocalDateFormatter(Locale.getDefault()), firstWeekday = prefs.firstWeekday, series = listOf(), + defaultSquare = HistoryChart.Square.OFF, ) } ).apply { diff --git a/uhabits-core/src/jvmMain/java/org/isoron/uhabits/core/commands/CreateHabitCommand.kt b/uhabits-core/src/jvmMain/java/org/isoron/uhabits/core/commands/CreateHabitCommand.kt index 49e3bae7a..8ff21f407 100644 --- a/uhabits-core/src/jvmMain/java/org/isoron/uhabits/core/commands/CreateHabitCommand.kt +++ b/uhabits-core/src/jvmMain/java/org/isoron/uhabits/core/commands/CreateHabitCommand.kt @@ -31,5 +31,6 @@ data class CreateHabitCommand( val habit = modelFactory.buildHabit() habit.copyFrom(model) habitList.add(habit) + habit.recompute() } } 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 a2dd13846..5134d45b0 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 @@ -90,9 +90,10 @@ data class Habit( isNumerical = isNumerical, ) - val to = DateUtils.getTodayWithOffset().plus(30) + val today = DateUtils.getTodayWithOffset() + val to = today.plus(30) val entries = computedEntries.getKnown() - var from = entries.lastOrNull()?.timestamp ?: to + var from = entries.lastOrNull()?.timestamp ?: today if (from.isNewerThan(to)) from = to scores.recompute( 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 a062f7ede..23bd88fd1 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 @@ -75,13 +75,12 @@ class ScoreList { to: Timestamp, ) { map.clear() - if (computedEntries.getKnown().isEmpty()) return - if (from.isNewerThan(to)) return var rollingSum = 0.0 var numerator = frequency.numerator var denominator = frequency.denominator val freq = frequency.toDouble() val values = computedEntries.getByInterval(from, to).map { it.value }.toIntArray() + val isAtMost = numericalHabitType == NumericalHabitType.AT_MOST // For non-daily boolean habits, we double the numerator and the denominator to smooth // out irregular repetition schedules (for example, weekly habits performed on different @@ -91,7 +90,7 @@ class ScoreList { denominator *= 2 } - var previousValue = 0.0 + var previousValue = if (isNumerical && isAtMost) 1.0 else 0.0 for (i in values.indices) { val offset = values.size - i - 1 if (isNumerical) { @@ -101,7 +100,7 @@ class ScoreList { } val normalizedRollingSum = rollingSum / 1000 - val percentageCompleted = if (numericalHabitType == NumericalHabitType.AT_LEAST) { + val percentageCompleted = if (!isAtMost) { if (targetValue > 0) min(1.0, normalizedRollingSum / targetValue) else 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 8798f027d..dcc7cba24 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 @@ -45,6 +45,7 @@ data class HistoryCardState( val color: PaletteColor, val firstWeekday: DayOfWeek, val series: List, + val defaultSquare: HistoryChart.Square, val theme: Theme, val today: LocalDate, ) @@ -131,6 +132,10 @@ class HistoryCardPresenter( } } } + val defaultSquare = if (habit.isNumerical && habit.targetType == NumericalHabitType.AT_MOST) + HistoryChart.Square.ON + else + HistoryChart.Square.OFF return HistoryCardState( color = habit.color, @@ -138,6 +143,7 @@ class HistoryCardPresenter( today = today.toLocalDate(), theme = theme, series = series, + defaultSquare = defaultSquare ) } } diff --git a/uhabits-core/src/jvmMain/java/org/isoron/uhabits/core/ui/views/HistoryChart.kt b/uhabits-core/src/jvmMain/java/org/isoron/uhabits/core/ui/views/HistoryChart.kt index 699cc2ee6..f0da7a196 100644 --- a/uhabits-core/src/jvmMain/java/org/isoron/uhabits/core/ui/views/HistoryChart.kt +++ b/uhabits-core/src/jvmMain/java/org/isoron/uhabits/core/ui/views/HistoryChart.kt @@ -41,6 +41,7 @@ class HistoryChart( var firstWeekday: DayOfWeek, var paletteColor: PaletteColor, var series: List, + var defaultSquare: Square, var theme: Theme, var today: LocalDate, var onDateClickedListener: OnDateClickedListener = OnDateClickedListener { }, @@ -189,7 +190,7 @@ class HistoryChart( offset: Int, ) { - val value = if (offset >= series.size) Square.OFF else series[offset] + val value = if (offset >= series.size) defaultSquare else series[offset] val squareColor: Color val color = theme.color(paletteColor.paletteIndex) squareColor = when (value) { diff --git a/uhabits-core/src/jvmTest/java/org/isoron/uhabits/core/ui/views/HistoryChartTest.kt b/uhabits-core/src/jvmTest/java/org/isoron/uhabits/core/ui/views/HistoryChartTest.kt index bce63bcec..a53135f9b 100644 --- a/uhabits-core/src/jvmTest/java/org/isoron/uhabits/core/ui/views/HistoryChartTest.kt +++ b/uhabits-core/src/jvmTest/java/org/isoron/uhabits/core/ui/views/HistoryChartTest.kt @@ -49,6 +49,7 @@ class HistoryChartTest { dateFormatter = JavaLocalDateFormatter(Locale.US), firstWeekday = SUNDAY, onDateClickedListener = dateClickedListener, + defaultSquare = OFF, series = listOf( 2, // today 2, 1, 2, 1, 2, 1, 2, From 17ed85fc1b5f666f718953709fd57606299c09ae Mon Sep 17 00:00:00 2001 From: KristianTashkov Date: Wed, 22 Sep 2021 16:18:50 +0300 Subject: [PATCH 08/12] fix test --- .../habits/list/NumberPanelView/render.png | Bin 7535 -> 6582 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/uhabits-android/src/androidTest/assets/views/habits/list/NumberPanelView/render.png b/uhabits-android/src/androidTest/assets/views/habits/list/NumberPanelView/render.png index 22584afb3d05a46822f721bc66137703c5203e49..a8571c3fac0e4785eb428f1a81c8a351ddfdd281 100644 GIT binary patch literal 6582 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 From 07e55f1c76abded6df2d18a1b8f3d3a15d5d217e Mon Sep 17 00:00:00 2001 From: KristianTashkov Date: Sun, 26 Sep 2021 22:58:24 +0300 Subject: [PATCH 09/12] modify review comments --- .../java/org/isoron/uhabits/core/models/Habit.kt | 10 ++++------ .../core/ui/screens/habits/show/views/HistoryCard.kt | 2 +- 2 files changed, 5 insertions(+), 7 deletions(-) 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 5134d45b0..20c3d7949 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 @@ -59,10 +59,9 @@ data class Habit( val today = DateUtils.getTodayWithOffset() val value = computedEntries.get(today).value return if (isNumerical) { - val targetValuePerDay = (targetValue / frequency.denominator) when (targetType) { - NumericalHabitType.AT_LEAST -> value / 1000.0 >= targetValuePerDay - NumericalHabitType.AT_MOST -> value / 1000.0 <= targetValuePerDay + NumericalHabitType.AT_LEAST -> value / 1000.0 >= targetValue + NumericalHabitType.AT_MOST -> value / 1000.0 <= targetValue } } else { value != Entry.NO && value != Entry.UNKNOWN @@ -73,10 +72,9 @@ data class Habit( val today = DateUtils.getTodayWithOffset() val value = computedEntries.get(today).value return if (isNumerical) { - val targetValuePerDay = (targetValue / frequency.denominator) when (targetType) { - NumericalHabitType.AT_LEAST -> value / 1000.0 < targetValuePerDay - NumericalHabitType.AT_MOST -> value / 1000.0 > targetValuePerDay + NumericalHabitType.AT_LEAST -> value / 1000.0 < targetValue + NumericalHabitType.AT_MOST -> value / 1000.0 > targetValue } } else { value == Entry.NO 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 dcc7cba24..3ce83c8b3 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 @@ -117,7 +117,7 @@ class HistoryCardPresenter( } else { entries.map { when { - max(0.0, it.value / 1000.0) < 2 * habit.targetValue -> HistoryChart.Square.ON + max(0.0, it.value / 1000.0) <= habit.targetValue -> HistoryChart.Square.ON else -> HistoryChart.Square.OFF } } From d6a7fa3d7ad134b04b072b81c4cb0d075f3b8553 Mon Sep 17 00:00:00 2001 From: KristianTashkov Date: Mon, 27 Sep 2021 00:34:27 +0300 Subject: [PATCH 10/12] Add unit tests for numerical habits --- .../isoron/uhabits/core/test/HabitFixtures.kt | 13 + .../uhabits/core/models/ScoreListTest.kt | 245 ++++++++++++++++-- 2 files changed, 234 insertions(+), 24 deletions(-) diff --git a/uhabits-core/src/jvmMain/java/org/isoron/uhabits/core/test/HabitFixtures.kt b/uhabits-core/src/jvmMain/java/org/isoron/uhabits/core/test/HabitFixtures.kt index 38a34f0e1..fffe4ac85 100644 --- a/uhabits-core/src/jvmMain/java/org/isoron/uhabits/core/test/HabitFixtures.kt +++ b/uhabits-core/src/jvmMain/java/org/isoron/uhabits/core/test/HabitFixtures.kt @@ -50,6 +50,19 @@ class HabitFixtures(private val modelFactory: ModelFactory, private val habitLis return habit } + fun createEmptyNumericalHabit(targetType: NumericalHabitType): Habit { + val habit = modelFactory.buildHabit() + habit.type = HabitType.NUMERICAL + habit.name = "Run" + habit.question = "How many miles did you run today?" + habit.unit = "miles" + habit.targetType = targetType + habit.targetValue = 2.0 + habit.color = PaletteColor(1) + saveIfSQLite(habit) + return habit + } + fun createLongHabit(): Habit { val habit = createEmptyHabit() habit.frequency = Frequency(3, 7) 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 ae2a8eedb..6ca3e2806 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 @@ -28,14 +28,36 @@ import org.junit.Before import org.junit.Test import java.util.ArrayList -class ScoreListTest : BaseUnitTest() { - private lateinit var habit: Habit - private lateinit var today: Timestamp +open class BaseScoreListTest : BaseUnitTest() { + protected lateinit var habit: Habit + protected lateinit var today: Timestamp + @Before @Throws(Exception::class) override fun setUp() { super.setUp() today = getToday() + } + + protected fun checkScoreValues(expectedValues: DoubleArray) { + var current = today + val scores = habit.scores + for (expectedValue in expectedValues) { + assertThat(scores[current].value, IsCloseTo.closeTo(expectedValue, E)) + current = current.minus(1) + } + } + + companion object { + const val E = 1e-6 + } +} + +class YesNoScoreListTest : BaseScoreListTest() { + @Before + @Throws(Exception::class) + override fun setUp() { + super.setUp() habit = fixtures.createEmptyHabit() } @@ -122,18 +144,6 @@ class ScoreListTest : BaseUnitTest() { checkScoreValues(expectedValues) } - @Test - fun test_withZeroTarget() { - habit = fixtures.createNumericalHabit() - habit.targetValue = 0.0 - habit.recompute() - assertTrue(habit.scores[today].value.isFinite()) - - habit.targetType = NumericalHabitType.AT_MOST - habit.recompute() - assertTrue(habit.scores[today].value.isFinite()) - } - @Test fun test_imperfectNonDaily() { // If the habit should be performed 3 times per week and the user misses 1 repetition @@ -259,17 +269,204 @@ class ScoreListTest : BaseUnitTest() { val entries = habit.originalEntries entries.add(Entry(today.minus(day), Entry.SKIP)) } +} - private fun checkScoreValues(expectedValues: DoubleArray) { - var current = today - val scores = habit.scores - for (expectedValue in expectedValues) { - assertThat(scores[current].value, IsCloseTo.closeTo(expectedValue, E)) - current = current.minus(1) - } +open class NumericalScoreListTest : BaseScoreListTest() { + protected fun addEntry(day: Int, value: Int) { + val entries = habit.originalEntries + entries.add(Entry(today.minus(day), value)) } - companion object { - private const val E = 1e-6 + protected fun addEntries(from: Int, to: Int, value: Int) { + val entries = habit.originalEntries + for (i in from until to) entries.add(Entry(today.minus(i), value)) + habit.recompute() + } +} + +class NumericalAtLeastScoreListTest : NumericalScoreListTest() { + @Before + @Throws(Exception::class) + override fun setUp() { + super.setUp() + habit = fixtures.createEmptyNumericalHabit(NumericalHabitType.AT_LEAST) + } + + @Test + fun test_withZeroTarget() { + habit = fixtures.createNumericalHabit() + habit.targetValue = 0.0 + habit.recompute() + assertTrue(habit.scores[today].value.isFinite()) + } + + @Test + fun test_getValue() { + addEntries(0, 20, 2000) + val expectedValues = doubleArrayOf( + 0.655747, + 0.636894, + 0.617008, + 0.596033, + 0.573910, + 0.550574, + 0.525961, + 0.500000, + 0.472617, + 0.443734, + 0.413270, + 0.381137, + 0.347244, + 0.311495, + 0.273788, + 0.234017, + 0.192067, + 0.147820, + 0.101149, + 0.051922, + 0.000000, + 0.000000, + 0.000000 + ) + checkScoreValues(expectedValues) + } + + @Test + fun test_recompute() { + assertThat(habit.scores[today].value, IsCloseTo.closeTo(0.0, E)) + addEntries(0, 2, 2000) + assertThat(habit.scores[today].value, IsCloseTo.closeTo(0.101149, E)) + habit.frequency = Frequency(1, 2) + habit.recompute() + assertThat(habit.scores[today].value, IsCloseTo.closeTo(0.072631, E)) + } + + @Test + fun shouldAchieveHighScoreInReasonableTime() { + // Daily habits should achieve at least 99% in 3 months + habit = fixtures.createEmptyNumericalHabit(NumericalHabitType.AT_LEAST) + habit.frequency = Frequency.DAILY + for (i in 0..89) addEntry(i, 2000) + habit.recompute() + assertThat(habit.scores[today].value, OrderingComparison.greaterThan(0.99)) + + // Weekly habits should achieve at least 99% in 9 months + habit = fixtures.createEmptyNumericalHabit(NumericalHabitType.AT_LEAST) + habit.frequency = Frequency.WEEKLY + for (i in 0..38) addEntry(7 * i, 2000) + habit.recompute() + assertThat(habit.scores[today].value, OrderingComparison.greaterThan(0.99)) + + // Monthly habits should achieve at least 99% in 18 months + habit.frequency = Frequency(1, 30) + for (i in 0..17) addEntry(30 * i, 2000) + habit.recompute() + assertThat(habit.scores[today].value, OrderingComparison.greaterThan(0.99)) + } + + @Test + fun shouldAchieveComparableScoreToProgress() { + addEntries(0, 500, 1000) + assertThat(habit.scores[today].value, IsCloseTo.closeTo(0.5, E)) + + addEntries(0, 500, 500) + assertThat(habit.scores[today].value, IsCloseTo.closeTo(0.25, E)) + } + + @Test + fun overeachievingIsntRelevant() { + addEntry(0, 10000000) + habit.recompute() + assertThat(habit.scores[today].value, IsCloseTo.closeTo(0.051922, E)) + } +} + +class NumericalAtMostScoreListTest : NumericalScoreListTest() { + @Before + @Throws(Exception::class) + override fun setUp() { + super.setUp() + habit = fixtures.createEmptyNumericalHabit(NumericalHabitType.AT_MOST) + } + + @Test + fun test_withZeroTarget() { + habit = fixtures.createNumericalHabit() + habit.targetType = NumericalHabitType.AT_MOST + habit.targetValue = 0.0 + habit.recompute() + assertTrue(habit.scores[today].value.isFinite()) + } + + @Test + fun test_getValue() { + addEntry(20, 1000) + addEntries(0, 20, 5000) + val expectedValues = doubleArrayOf( + 0.344253, + 0.363106, + 0.382992, + 0.403967, + 0.426090, + 0.449426, + 0.474039, + 0.500000, + 0.527383, + 0.556266, + 0.586730, + 0.618863, + 0.652756, + 0.688505, + 0.726212, + 0.765983, + 0.807933, + 0.852180, + 0.898851, + 0.948078, + 1.0, + 0.0, + 0.0 + ) + checkScoreValues(expectedValues) + } + + @Test + fun test_recompute() { + habit.recompute() + assertThat(habit.scores[today].value, IsCloseTo.closeTo(1.0, E)) + addEntries(0, 2, 5000) + assertThat(habit.scores[today].value, IsCloseTo.closeTo(0.898850, E)) + habit.frequency = Frequency(1, 2) + habit.recompute() + assertThat(habit.scores[today].value, IsCloseTo.closeTo(0.927369, E)) + } + + @Test + fun shouldAchieveComparableScoreToProgress() { + addEntries(0, 500, 3000) + assertThat(habit.scores[today].value, IsCloseTo.closeTo(0.5, E)) + + addEntries(0, 500, 3500) + assertThat(habit.scores[today].value, IsCloseTo.closeTo(0.25, E)) + } + + @Test + fun undereachievingIsntRelevant() { + addEntry(1, 10000000) + habit.recompute() + assertThat(habit.scores[today].value, IsCloseTo.closeTo(0.950773, E)) + } + + @Test + fun overeachievingIsntRelevant() { + addEntry(0, 5000) + + addEntry(1, 0) + habit.recompute() + assertThat(habit.scores[today].value, IsCloseTo.closeTo(0.948077, E)) + + addEntry(1, 1000) + habit.recompute() + assertThat(habit.scores[today].value, IsCloseTo.closeTo(0.948077, E)) } } From 66a2b41250996bc7a3ac1ad35b12749c04682e82 Mon Sep 17 00:00:00 2001 From: "Alinson S. Xavier" Date: Wed, 29 Sep 2021 03:03:21 -0500 Subject: [PATCH 11/12] Target type: use dropdown instead of radio button --- .../habits/edit/EditHabitActivity.kt | 35 ++++++++++++---- .../main/res/layout/activity_edit_habit.xml | 40 ++++++++----------- .../src/main/res/values/strings.xml | 4 +- 3 files changed, 46 insertions(+), 33 deletions(-) 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 8bd84b98c..572917d10 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 @@ -88,6 +88,7 @@ class EditHabitActivity : AppCompatActivity() { var reminderHour = -1 var reminderMin = -1 var reminderDays: WeekdayList = WeekdayList.EVERY_DAY + var targetType = NumericalHabitType.AT_LEAST override fun onCreate(state: Bundle?) { super.onCreate(state) @@ -107,6 +108,7 @@ class EditHabitActivity : AppCompatActivity() { color = habit.color freqNum = habit.frequency.numerator freqDen = habit.frequency.denominator + targetType = habit.targetType habit.reminder?.let { reminderHour = it.hour reminderMin = it.minute @@ -117,10 +119,6 @@ class EditHabitActivity : AppCompatActivity() { binding.notesInput.setText(habit.description) binding.unitInput.setText(habit.unit) binding.targetInput.setText(habit.targetValue.toString()) - if (habit.targetType == NumericalHabitType.AT_MOST) { - binding.targetTypeAtMost.isChecked = true - binding.targetTypeAtLeast.isChecked = false - } } else { habitType = HabitType.fromInt(intent.getIntExtra("habitType", HabitType.YES_NO.value)) } @@ -177,6 +175,23 @@ class EditHabitActivity : AppCompatActivity() { dialog.show(supportFragmentManager, "frequencyPicker") } + populateTargetType() + binding.targetTypePicker.setOnClickListener { + val builder = AlertDialog.Builder(this) + val arrayAdapter = ArrayAdapter(this, android.R.layout.select_dialog_item) + arrayAdapter.add(getString(R.string.target_type_at_least)) + arrayAdapter.add(getString(R.string.target_type_at_most)) + builder.setAdapter(arrayAdapter) { dialog, which -> + targetType = when (which) { + 0 -> NumericalHabitType.AT_LEAST + else -> NumericalHabitType.AT_MOST + } + populateTargetType() + dialog.dismiss() + } + builder.show() + } + binding.numericalFrequencyPicker.setOnClickListener { val builder = AlertDialog.Builder(this) val arrayAdapter = ArrayAdapter(this, android.R.layout.select_dialog_item) @@ -267,10 +282,7 @@ class EditHabitActivity : AppCompatActivity() { habit.frequency = Frequency(freqNum, freqDen) if (habitType == HabitType.NUMERICAL) { habit.targetValue = targetInput.text.toString().toDouble() - if (binding.targetTypeAtLeast.isChecked) - habit.targetType = NumericalHabitType.AT_LEAST - else - habit.targetType = NumericalHabitType.AT_MOST + habit.targetType = targetType habit.unit = unitInput.text.trim().toString() } habit.type = habitType @@ -332,6 +344,13 @@ class EditHabitActivity : AppCompatActivity() { } } + private fun populateTargetType() { + binding.targetTypePicker.text = when (targetType) { + NumericalHabitType.AT_MOST -> getString(R.string.target_type_at_most) + else -> getString(R.string.target_type_at_least) + } + } + private fun updateColors() { androidColor = themeSwitcher.currentTheme.color(color).toInt() binding.colorButton.backgroundTintList = ColorStateList.valueOf(androidColor) 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 e30490bfb..75dddb551 100644 --- a/uhabits-android/src/main/res/layout/activity_edit_habit.xml +++ b/uhabits-android/src/main/res/layout/activity_edit_habit.xml @@ -167,29 +167,7 @@ android:hint="@string/measurable_units_example"/> - - - - - - - - - + + + + + + + + + diff --git a/uhabits-android/src/main/res/values/strings.xml b/uhabits-android/src/main/res/values/strings.xml index 8c5e9552a..13767b3df 100644 --- a/uhabits-android/src/main/res/values/strings.xml +++ b/uhabits-android/src/main/res/values/strings.xml @@ -185,8 +185,8 @@ Calendar Unit Target Type - At Least - At Most + At least + At most e.g. Did you exercise today? Question Target From 2a0afedb1db01406b6b0d96763cf2f0919761dfd Mon Sep 17 00:00:00 2001 From: "Alinson S. Xavier" Date: Wed, 29 Sep 2021 03:14:41 -0500 Subject: [PATCH 12/12] SubtitleCard: Show at-most icon --- .../activities/habits/show/views/SubtitleCardViewTest.kt | 2 -- .../activities/habits/show/views/SubtitleCardView.kt | 8 +++++++- .../src/main/res/layout/show_habit_subtitle.xml | 1 - uhabits-android/src/main/res/values/fontawesome.xml | 2 +- .../core/ui/screens/habits/show/views/SubtitleCard.kt | 7 +++++-- 5 files changed, 13 insertions(+), 7 deletions(-) diff --git a/uhabits-android/src/androidTest/java/org/isoron/uhabits/activities/habits/show/views/SubtitleCardViewTest.kt b/uhabits-android/src/androidTest/java/org/isoron/uhabits/activities/habits/show/views/SubtitleCardViewTest.kt index a43543936..3323f78df 100644 --- a/uhabits-android/src/androidTest/java/org/isoron/uhabits/activities/habits/show/views/SubtitleCardViewTest.kt +++ b/uhabits-android/src/androidTest/java/org/isoron/uhabits/activities/habits/show/views/SubtitleCardViewTest.kt @@ -53,8 +53,6 @@ class SubtitleCardViewTest : BaseViewTest() { isNumerical = false, question = "Did you meditate this morning?", reminder = Reminder(8, 30, EVERY_DAY), - unit = "", - targetValue = 0.0, theme = LightTheme(), ) ) diff --git a/uhabits-android/src/main/java/org/isoron/uhabits/activities/habits/show/views/SubtitleCardView.kt b/uhabits-android/src/main/java/org/isoron/uhabits/activities/habits/show/views/SubtitleCardView.kt index 49c67aae1..5bbfb46d6 100644 --- a/uhabits-android/src/main/java/org/isoron/uhabits/activities/habits/show/views/SubtitleCardView.kt +++ b/uhabits-android/src/main/java/org/isoron/uhabits/activities/habits/show/views/SubtitleCardView.kt @@ -28,6 +28,7 @@ import org.isoron.platform.gui.toInt import org.isoron.uhabits.R import org.isoron.uhabits.activities.habits.edit.formatFrequency import org.isoron.uhabits.activities.habits.list.views.toShortString +import org.isoron.uhabits.core.models.NumericalHabitType import org.isoron.uhabits.core.ui.screens.habits.show.views.SubtitleCardState import org.isoron.uhabits.databinding.ShowHabitSubtitleBinding import org.isoron.uhabits.utils.InterfaceUtils @@ -65,7 +66,12 @@ class SubtitleCardView(context: Context, attrs: AttributeSet) : LinearLayout(con binding.questionLabel.visibility = View.VISIBLE binding.targetIcon.visibility = View.VISIBLE binding.targetText.visibility = View.VISIBLE - if (!state.isNumerical) { + if (state.isNumerical) { + binding.targetIcon.text = when (state.targetType) { + NumericalHabitType.AT_LEAST -> resources.getString(R.string.fa_arrow_circle_up) + else -> resources.getString(R.string.fa_arrow_circle_down) + } + } else { binding.targetIcon.visibility = View.GONE binding.targetText.visibility = View.GONE } diff --git a/uhabits-android/src/main/res/layout/show_habit_subtitle.xml b/uhabits-android/src/main/res/layout/show_habit_subtitle.xml index f8f3924bc..7361b4565 100644 --- a/uhabits-android/src/main/res/layout/show_habit_subtitle.xml +++ b/uhabits-android/src/main/res/layout/show_habit_subtitle.xml @@ -47,7 +47,6 @@ android:id="@+id/targetIcon" android:layout_width="wrap_content" android:layout_height="wrap_content" - android:text="@string/fa_arrow_circle_up" android:textColor="?attr/contrast60" android:textSize="16sp" /> diff --git a/uhabits-android/src/main/res/values/fontawesome.xml b/uhabits-android/src/main/res/values/fontawesome.xml index 917cf0d73..0ff190382 100644 --- a/uhabits-android/src/main/res/values/fontawesome.xml +++ b/uhabits-android/src/main/res/values/fontawesome.xml @@ -21,6 +21,7 @@ + @@ -181,7 +182,6 @@ - diff --git a/uhabits-core/src/jvmMain/java/org/isoron/uhabits/core/ui/screens/habits/show/views/SubtitleCard.kt b/uhabits-core/src/jvmMain/java/org/isoron/uhabits/core/ui/screens/habits/show/views/SubtitleCard.kt index 525f8fc7c..4cbbd74ec 100644 --- a/uhabits-core/src/jvmMain/java/org/isoron/uhabits/core/ui/screens/habits/show/views/SubtitleCard.kt +++ b/uhabits-core/src/jvmMain/java/org/isoron/uhabits/core/ui/screens/habits/show/views/SubtitleCard.kt @@ -21,6 +21,7 @@ package org.isoron.uhabits.core.ui.screens.habits.show.views import org.isoron.uhabits.core.models.Frequency import org.isoron.uhabits.core.models.Habit +import org.isoron.uhabits.core.models.NumericalHabitType import org.isoron.uhabits.core.models.PaletteColor import org.isoron.uhabits.core.models.Reminder import org.isoron.uhabits.core.ui.views.Theme @@ -31,8 +32,9 @@ data class SubtitleCardState( val isNumerical: Boolean, val question: String, val reminder: Reminder?, - val targetValue: Double, - val unit: String, + val targetValue: Double = 0.0, + val targetType: NumericalHabitType = NumericalHabitType.AT_LEAST, + val unit: String = "", val theme: Theme, ) @@ -48,6 +50,7 @@ class SubtitleCardPresenter { question = habit.question, reminder = habit.reminder, targetValue = habit.targetValue, + targetType = habit.targetType, unit = habit.unit, theme = theme, )