Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement numerical habits with AT_MOST target type #1101

Merged
merged 12 commits into from
Sep 29, 2021
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -42,6 +43,7 @@ class NumberButtonViewTest : BaseViewTest() {
super.setUp()
view = component.getNumberButtonViewFactory().create().apply {
units = "steps"
targetType = NumericalHabitType.AT_LEAST
threshold = 100.0
color = PaletteUtils.getAndroidTestColor(8)
onEdit = { edited = true }
Expand Down Expand Up @@ -74,10 +76,10 @@ class NumberButtonViewTest : BaseViewTest() {
}

@Test
fun testRender_emptyUnits() {
fun testRender_atMostAboveThreshold() {
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
Expand All @@ -86,12 +88,33 @@ class NumberButtonViewTest : BaseViewTest() {
assertRenders(view, "$PATH/render_below.png")
}

@Test
fun testRender_atMostBetweenThresholds() {
view.value = 110.0
view.targetType = NumericalHabitType.AT_MOST
assertRenders(view, "$PATH/render_at_most_between.png")
}

@Test
fun testRender_zero() {
view.value = 0.0
assertRenders(view, "$PATH/render_zero.png")
}

@Test
fun testRender_atMostBelowThreshold() {
view.value = 0.0
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
fun testClick_shortToggleDisabled() {
prefs.isShortToggleEnabled = false
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -55,6 +56,7 @@ class NumberPanelViewTest : BaseViewTest() {
buttonCount = 4
color = PaletteUtils.getAndroidTestColor(7)
units = "steps"
targetType = NumericalHabitType.AT_LEAST
threshold = 5000.0
}
view.onAttachedToWindow()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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 { },
Expand Down Expand Up @@ -101,6 +102,7 @@ class HistoryEditorDialog : AppCompatDialogFragment(), CommandRunner.Listener {
theme = LightTheme()
)
chart?.series = model.series
chart?.defaultSquare = model.defaultSquare
dataView.postInvalidate()
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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))
}
Expand All @@ -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)
Expand Down Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -236,6 +236,7 @@ class HabitCardView(
numberPanel.apply {
color = c
units = h.unit
targetType = h.targetType
threshold = h.targetValue
visibility = when (h.isNumerical) {
true -> View.VISIBLE
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,13 +29,15 @@ 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.lang.Double.max
import java.text.DecimalFormat
import javax.inject.Inject

Expand Down Expand Up @@ -88,6 +90,12 @@ class NumberButtonView(
invalidate()
}

var targetType = NumericalHabitType.AT_LEAST
set(value) {
field = value
invalidate()
}

var units = ""
set(value) {
field = value
Expand Down Expand Up @@ -127,7 +135,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
Expand All @@ -148,15 +155,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 < 0.0 && preferences.areQuestionMarksEnabled -> lowContrast
max(0.0, value) >= threshold -> color
else -> mediumContrast
}
} else {
when {
value < 0.0 && preferences.areQuestionMarksEnabled -> lowContrast
value <= threshold -> color
else -> mediumContrast
}
}

val label: String
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -47,6 +48,12 @@ class NumberPanelView(
setupButtons()
}

var targetType = NumericalHabitType.AT_LEAST
set(value) {
field = value
setupButtons()
}

var threshold = 0.0
set(value) {
field = value
Expand Down Expand Up @@ -84,6 +91,7 @@ class NumberPanelView(
else -> 0.0
}
button.color = color
button.targetType = targetType
button.threshold = threshold
button.units = units
button.onEdit = { onEdit(timestamp) }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
}

Expand All @@ -71,6 +73,7 @@ class HistoryWidget(
dateFormatter = JavaLocalDateFormatter(Locale.getDefault()),
firstWeekday = prefs.firstWeekday,
series = listOf(),
defaultSquare = HistoryChart.Square.OFF,
)
}
).apply {
Expand Down
23 changes: 23 additions & 0 deletions uhabits-android/src/main/res/layout/activity_edit_habit.xml
Original file line number Diff line number Diff line change
Expand Up @@ -167,6 +167,29 @@
android:hint="@string/measurable_units_example"/>
</LinearLayout>
</FrameLayout>
<FrameLayout
android:id="@+id/targetTypeOuterBox"
style="@style/FormOuterBox">
<LinearLayout style="@style/FormInnerBox">
<TextView
style="@style/FormLabel"
android:text="@string/target_type" />
<RadioGroup
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal">
<RadioButton android:id="@+id/targetTypeAtLeast"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/target_type_at_least"
android:checked="true"/>
<RadioButton android:id="@+id/targetTypeAtMost"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/target_type_at_most"/>
</RadioGroup>
</LinearLayout>
</FrameLayout>
<LinearLayout
android:id="@+id/targetOuterBox"
android:layout_width="match_parent"
Expand Down
3 changes: 3 additions & 0 deletions uhabits-android/src/main/res/values/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -184,6 +184,9 @@
<string name="change_value">Change value</string>
<string name="calendar">Calendar</string>
<string name="unit">Unit</string>
<string name="target_type">Target Type</string>
<string name="target_type_at_least">At Least</string>
<string name="target_type_at_most">At Most</string>
<string name="example_question_boolean">e.g. Did you exercise today?</string>
<string name="question">Question</string>
<string name="target">Target</string>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,5 +31,6 @@ data class CreateHabitCommand(
val habit = modelFactory.buildHabit()
habit.copyFrom(model)
habitList.add(habit)
habit.recompute()
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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
KristianTashkov marked this conversation as resolved.
Show resolved Hide resolved
}
} else {
value != Entry.NO && value != Entry.UNKNOWN
Expand All @@ -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
Expand All @@ -88,14 +90,16 @@ 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(
frequency = frequency,
isNumerical = isNumerical,
numericalHabitType = targetType,
targetValue = targetValue,
computedEntries = computedEntries,
from = from,
Expand Down
Loading