Add option to set default value for habits on unknown days

pull/1106/head
KristianTashkov 4 years ago
parent 2ab6c396d0
commit c7917dc185

@ -62,6 +62,7 @@ class HistoryEditorDialog : AppCompatDialogFragment(), CommandRunner.Listener {
firstWeekday = preferences.firstWeekday, firstWeekday = preferences.firstWeekday,
paletteColor = habit.color, paletteColor = habit.color,
series = emptyList(), series = emptyList(),
defaultSquare = HistoryChart.Square.OFF,
theme = themeSwitcher.currentTheme, theme = themeSwitcher.currentTheme,
today = DateUtils.getTodayWithOffset().toLocalDate(), today = DateUtils.getTodayWithOffset().toLocalDate(),
onDateClickedListener = onDateClickedListener ?: OnDateClickedListener { }, onDateClickedListener = onDateClickedListener ?: OnDateClickedListener { },
@ -101,6 +102,7 @@ class HistoryEditorDialog : AppCompatDialogFragment(), CommandRunner.Listener {
theme = LightTheme() theme = LightTheme()
) )
chart?.series = model.series chart?.series = model.series
chart?.defaultSquare = model.defaultSquare
dataView.postInvalidate() dataView.postInvalidate()
} }

@ -35,11 +35,7 @@ import androidx.appcompat.app.AppCompatActivity
import androidx.fragment.app.DialogFragment import androidx.fragment.app.DialogFragment
import com.android.datetimepicker.time.RadialPickerLayout import com.android.datetimepicker.time.RadialPickerLayout
import com.android.datetimepicker.time.TimePickerDialog import com.android.datetimepicker.time.TimePickerDialog
import kotlinx.android.synthetic.main.activity_edit_habit.nameInput import kotlinx.android.synthetic.main.activity_edit_habit.*
import kotlinx.android.synthetic.main.activity_edit_habit.notesInput
import kotlinx.android.synthetic.main.activity_edit_habit.questionInput
import kotlinx.android.synthetic.main.activity_edit_habit.targetInput
import kotlinx.android.synthetic.main.activity_edit_habit.unitInput
import org.isoron.platform.gui.toInt import org.isoron.platform.gui.toInt
import org.isoron.uhabits.HabitsApplication import org.isoron.uhabits.HabitsApplication
import org.isoron.uhabits.R import org.isoron.uhabits.R
@ -50,6 +46,7 @@ import org.isoron.uhabits.activities.common.dialogs.WeekdayPickerDialog
import org.isoron.uhabits.core.commands.CommandRunner import org.isoron.uhabits.core.commands.CommandRunner
import org.isoron.uhabits.core.commands.CreateHabitCommand import org.isoron.uhabits.core.commands.CreateHabitCommand
import org.isoron.uhabits.core.commands.EditHabitCommand import org.isoron.uhabits.core.commands.EditHabitCommand
import org.isoron.uhabits.core.models.Entry
import org.isoron.uhabits.core.models.Frequency import org.isoron.uhabits.core.models.Frequency
import org.isoron.uhabits.core.models.Habit import org.isoron.uhabits.core.models.Habit
import org.isoron.uhabits.core.models.HabitType import org.isoron.uhabits.core.models.HabitType
@ -116,6 +113,10 @@ class EditHabitActivity : AppCompatActivity() {
binding.questionInput.setText(habit.question) binding.questionInput.setText(habit.question)
binding.notesInput.setText(habit.description) binding.notesInput.setText(habit.description)
binding.unitInput.setText(habit.unit) binding.unitInput.setText(habit.unit)
binding.defaultValueInput.setText((habit.defaultValue / 1000.0).toString())
binding.defaultNo.isChecked = habit.defaultValue == Entry.NO
binding.defaultYes.isChecked = habit.defaultValue == Entry.YES_MANUAL
binding.defaultSkip.isChecked = habit.defaultValue == Entry.SKIP
binding.targetInput.setText(habit.targetValue.toString()) binding.targetInput.setText(habit.targetValue.toString())
} else { } else {
habitType = HabitType.fromInt(intent.getIntExtra("habitType", HabitType.YES_NO.value)) habitType = HabitType.fromInt(intent.getIntExtra("habitType", HabitType.YES_NO.value))
@ -138,11 +139,15 @@ class EditHabitActivity : AppCompatActivity() {
HabitType.YES_NO -> { HabitType.YES_NO -> {
binding.unitOuterBox.visibility = View.GONE binding.unitOuterBox.visibility = View.GONE
binding.targetOuterBox.visibility = View.GONE binding.targetOuterBox.visibility = View.GONE
binding.numericalFrequencyOuterBox.visibility = View.GONE
if (!component.preferences.isSkipEnabled)
binding.defaultSkip.visibility = View.GONE
} }
HabitType.NUMERICAL -> { HabitType.NUMERICAL -> {
binding.nameInput.hint = getString(R.string.measurable_short_example) binding.nameInput.hint = getString(R.string.measurable_short_example)
binding.questionInput.hint = getString(R.string.measurable_question_example) binding.questionInput.hint = getString(R.string.measurable_question_example)
binding.frequencyOuterBox.visibility = View.GONE binding.frequencyOuterBox.visibility = View.GONE
binding.yesNoDefaultOuterBox.visibility = View.GONE
} }
} }
@ -264,6 +269,15 @@ class EditHabitActivity : AppCompatActivity() {
habit.targetValue = targetInput.text.toString().toDouble() habit.targetValue = targetInput.text.toString().toDouble()
habit.targetType = NumericalHabitType.AT_LEAST habit.targetType = NumericalHabitType.AT_LEAST
habit.unit = unitInput.text.trim().toString() habit.unit = unitInput.text.trim().toString()
habit.defaultValue = kotlin.math.floor(
defaultValueInput.text.toString().toDouble() * 1000.0
).toInt()
} else {
habit.defaultValue = when {
defaultYes.isChecked -> Entry.YES_MANUAL
defaultSkip.isChecked -> Entry.SKIP
else -> Entry.NO
}
} }
habit.type = habitType habit.type = habitType

@ -65,6 +65,12 @@ class CheckmarkButtonView(
invalidate() invalidate()
} }
var defaultValue: Int = 0
set(value) {
field = value
invalidate()
}
var value: Int = 0 var value: Int = 0
set(value) { set(value) {
field = value field = value
@ -128,7 +134,8 @@ class CheckmarkButtonView(
} }
fun draw(canvas: Canvas) { fun draw(canvas: Canvas) {
paint.color = when (value) { val realValue = if (value != UNKNOWN) value else defaultValue
paint.color = when (realValue) {
YES_MANUAL, YES_AUTO, SKIP -> color YES_MANUAL, YES_AUTO, SKIP -> color
NO -> { NO -> {
if (preferences.areQuestionMarksEnabled) mediumContrastColor if (preferences.areQuestionMarksEnabled) mediumContrastColor
@ -136,15 +143,14 @@ class CheckmarkButtonView(
} }
else -> lowContrastColor else -> lowContrastColor
} }
val id = when (value) { var id = when (realValue) {
SKIP -> R.string.fa_skipped SKIP -> R.string.fa_skipped
NO -> R.string.fa_times NO -> R.string.fa_times
UNKNOWN -> {
if (preferences.areQuestionMarksEnabled) R.string.fa_question
else R.string.fa_times
}
else -> R.string.fa_check else -> R.string.fa_check
} }
if (value == UNKNOWN && preferences.areQuestionMarksEnabled)
id = R.string.fa_question
if (value == YES_AUTO) { if (value == YES_AUTO) {
paint.strokeWidth = 5f paint.strokeWidth = 5f
paint.style = Paint.Style.STROKE paint.style = Paint.Style.STROKE

@ -48,6 +48,12 @@ class CheckmarkPanelView(
setupButtons() setupButtons()
} }
var defaultValue = 0
set(value) {
field = value
setupButtons()
}
var color = 0 var color = 0
set(value) { set(value) {
field = value field = value
@ -72,6 +78,7 @@ class CheckmarkPanelView(
index + dataOffset < values.size -> values[index + dataOffset] index + dataOffset < values.size -> values[index + dataOffset]
else -> UNKNOWN else -> UNKNOWN
} }
button.defaultValue = defaultValue
button.color = color button.color = color
button.onToggle = { value -> onToggle(timestamp, value) } button.onToggle = { value -> onToggle(timestamp, value) }
} }

@ -228,6 +228,7 @@ class HabitCardView(
} }
checkmarkPanel.apply { checkmarkPanel.apply {
color = c color = c
defaultValue = h.defaultValue
visibility = when (h.isNumerical) { visibility = when (h.isNumerical) {
true -> View.GONE true -> View.GONE
false -> View.VISIBLE false -> View.VISIBLE
@ -235,6 +236,7 @@ class HabitCardView(
} }
numberPanel.apply { numberPanel.apply {
color = c color = c
defaultValue = h.defaultValue / 1000.0
units = h.unit units = h.unit
threshold = h.targetValue threshold = h.targetValue
visibility = when (h.isNumerical) { visibility = when (h.isNumerical) {

@ -76,6 +76,12 @@ class NumberButtonView(
invalidate() invalidate()
} }
var defaultValue = 0.0
set(value) {
field = value
invalidate()
}
var value = 0.0 var value = 0.0
set(value) { set(value) {
field = value field = value
@ -153,9 +159,10 @@ class NumberButtonView(
} }
fun draw(canvas: Canvas) { fun draw(canvas: Canvas) {
val realValue = if (value >= 0) value else defaultValue
val activeColor = when { val activeColor = when {
value <= 0.0 -> lowContrast realValue == 0.0 -> lowContrast
value < threshold -> mediumContrast realValue < threshold -> mediumContrast
else -> color else -> color
} }
@ -175,7 +182,7 @@ class NumberButtonView(
textSize = dim(R.dimen.smallerTextSize) textSize = dim(R.dimen.smallerTextSize)
} }
else -> { else -> {
label = "0" label = defaultValue.toShortString()
typeface = BOLD_TYPEFACE typeface = BOLD_TYPEFACE
textSize = dim(R.dimen.smallTextSize) textSize = dim(R.dimen.smallTextSize)
} }

@ -47,6 +47,12 @@ class NumberPanelView(
setupButtons() setupButtons()
} }
var defaultValue = 0.0
set(value) {
field = value
setupButtons()
}
var threshold = 0.0 var threshold = 0.0
set(value) { set(value) {
field = value field = value
@ -83,6 +89,7 @@ class NumberPanelView(
index + dataOffset < values.size -> values[index + dataOffset] index + dataOffset < values.size -> values[index + dataOffset]
else -> 0.0 else -> 0.0
} }
button.defaultValue = defaultValue
button.color = color button.color = color
button.threshold = threshold button.threshold = threshold
button.units = units button.units = units

@ -43,6 +43,7 @@ class HistoryCardView(context: Context, attrs: AttributeSet) : LinearLayout(cont
theme = state.theme, theme = state.theme,
dateFormatter = JavaLocalDateFormatter(Locale.getDefault()), dateFormatter = JavaLocalDateFormatter(Locale.getDefault()),
series = state.series, series = state.series,
defaultSquare = state.defaultSquare,
firstWeekday = state.firstWeekday, firstWeekday = state.firstWeekday,
) )
binding.chart.postInvalidate() binding.chart.postInvalidate()

@ -56,7 +56,9 @@ class HistoryWidget(
theme = WidgetTheme(), theme = WidgetTheme(),
) )
(widgetView.dataView as AndroidDataView).apply { (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()), dateFormatter = JavaLocalDateFormatter(Locale.getDefault()),
firstWeekday = prefs.firstWeekday, firstWeekday = prefs.firstWeekday,
series = listOf(), series = listOf(),
defaultSquare = HistoryChart.Square.OFF
) )
} }
).apply { ).apply {

@ -151,6 +151,35 @@
</LinearLayout> </LinearLayout>
</FrameLayout> </FrameLayout>
<!-- DefaultValue -->
<FrameLayout
android:id="@+id/yesNoDefaultOuterBox"
style="@style/FormOuterBox">
<LinearLayout style="@style/FormInnerBox">
<TextView
style="@style/FormLabel"
android:text="@string/defaultValue" />
<RadioGroup
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal">
<RadioButton android:id="@+id/defaultNo"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:checked="true"
android:text="@string/no"/>
<RadioButton android:id="@+id/defaultYes"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/yes"/>
<RadioButton android:id="@+id/defaultSkip"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/skip"/>
</RadioGroup>
</LinearLayout>
</FrameLayout>
<!-- Target value, unit and frequency --> <!-- Target value, unit and frequency -->
<FrameLayout <FrameLayout
android:id="@+id/unitOuterBox" android:id="@+id/unitOuterBox"
@ -180,13 +209,13 @@
<LinearLayout style="@style/FormInnerBox"> <LinearLayout style="@style/FormInnerBox">
<TextView <TextView
style="@style/FormLabel" style="@style/FormLabel"
android:text="@string/target" /> android:text="@string/startAt" />
<EditText <EditText
style="@style/FormInput" style="@style/FormInput"
android:id="@+id/targetInput" android:id="@+id/defaultValueInput"
android:maxLines="1" android:maxLines="1"
android:inputType="numberDecimal" android:inputType="numberDecimal"
android:hint="@string/example_target"/> android:text="0"/>
</LinearLayout> </LinearLayout>
</FrameLayout> </FrameLayout>
<FrameLayout <FrameLayout
@ -196,24 +225,40 @@
<LinearLayout style="@style/FormInnerBox"> <LinearLayout style="@style/FormInnerBox">
<TextView <TextView
style="@style/FormLabel" style="@style/FormLabel"
android:text="@string/frequency" /> android:text="@string/target" />
<TextView <EditText
style="@style/FormDropdown" style="@style/FormInput"
android:id="@+id/numericalFrequencyPicker" android:id="@+id/targetInput"
android:text="@string/every_week" android:maxLines="1"
android:textColor="?attr/contrast100" android:inputType="numberDecimal"
/> android:hint="@string/example_target"/>
</LinearLayout> </LinearLayout>
</FrameLayout> </FrameLayout>
</LinearLayout> </LinearLayout>
<FrameLayout
android:id='@+id/numericalFrequencyOuterBox'
style="@style/FormOuterBox">
<LinearLayout style="@style/FormInnerBox">
<TextView
style="@style/FormLabel"
android:text="@string/frequency" />
<TextView
style="@style/FormDropdown"
android:id="@+id/numericalFrequencyPicker"
android:text="@string/every_week"
android:textColor="?attr/contrast100"
/>
</LinearLayout>
</FrameLayout>
<!-- Reminder --> <!-- Reminder -->
<FrameLayout style="@style/FormOuterBox"> <FrameLayout style="@style/FormOuterBox">
<LinearLayout style="@style/FormInnerBox"> <LinearLayout style="@style/FormInnerBox">
<TextView <TextView
style="@style/FormLabel" style="@style/FormLabel"
android:text="@string/reminder" /> android:text="@string/reminder" />
<TextView <TextView
style="@style/FormDropdown" style="@style/FormDropdown"
android:id="@+id/reminderTimePicker" android:id="@+id/reminderTimePicker"

@ -118,6 +118,7 @@
<string name="developers">Developers</string> <string name="developers">Developers</string>
<string name="version_n">Version %s</string> <string name="version_n">Version %s</string>
<string name="frequency">Frequency</string> <string name="frequency">Frequency</string>
<string name="defaultValue">Default value</string>
<string name="checkmark">Checkmark</string> <string name="checkmark">Checkmark</string>
<string name="checkmark_stack_widget" formatted="false">Checkmark Stack Widget</string> <string name="checkmark_stack_widget" formatted="false">Checkmark Stack Widget</string>
<string name="frequency_stack_widget" formatted="false">Frequency Stack Widget</string> <string name="frequency_stack_widget" formatted="false">Frequency Stack Widget</string>
@ -186,9 +187,11 @@
<string name="unit">Unit</string> <string name="unit">Unit</string>
<string name="example_question_boolean">e.g. Did you exercise today?</string> <string name="example_question_boolean">e.g. Did you exercise today?</string>
<string name="question">Question</string> <string name="question">Question</string>
<string name="startAt">Starts at</string>
<string name="target">Target</string> <string name="target">Target</string>
<string name="yes">Yes</string> <string name="yes">Yes</string>
<string name="no">No</string> <string name="no">No</string>
<string name="skip">Skip</string>
<string name="customize_notification_summary">Change sound, vibration, light and other notification settings</string> <string name="customize_notification_summary">Change sound, vibration, light and other notification settings</string>
<string name="customize_notification">Customize notifications</string> <string name="customize_notification">Customize notifications</string>
<string name="pref_view_privacy">View privacy policy</string> <string name="pref_view_privacy">View privacy policy</string>

@ -20,4 +20,4 @@ package org.isoron.uhabits.core
const val DATABASE_FILENAME = "uhabits.db" const val DATABASE_FILENAME = "uhabits.db"
const val DATABASE_VERSION = 24 const val DATABASE_VERSION = 25

@ -35,6 +35,7 @@ data class Habit(
var targetValue: Double = 0.0, var targetValue: Double = 0.0,
var type: HabitType = HabitType.YES_NO, var type: HabitType = HabitType.YES_NO,
var unit: String = "", var unit: String = "",
var defaultValue: Int = 0,
var uuid: String? = null, var uuid: String? = null,
val computedEntries: EntryList, val computedEntries: EntryList,
val originalEntries: EntryList, val originalEntries: EntryList,
@ -69,7 +70,7 @@ data class Habit(
} }
fun isFailedToday(): Boolean { fun isFailedToday(): Boolean {
val today = DateUtils.getTodayWithOffset() var today = DateUtils.getTodayWithOffset()
val value = computedEntries.get(today).value val value = computedEntries.get(today).value
return if (isNumerical) { return if (isNumerical) {
when (targetType) { when (targetType) {
@ -88,15 +89,17 @@ data class Habit(
isNumerical = isNumerical, isNumerical = isNumerical,
) )
val to = DateUtils.getTodayWithOffset().plus(30) val today = DateUtils.getTodayWithOffset()
val to = today.plus(30)
val entries = computedEntries.getKnown() val entries = computedEntries.getKnown()
var from = entries.lastOrNull()?.timestamp ?: to var from = entries.lastOrNull()?.timestamp ?: today
if (from.isNewerThan(to)) from = to if (from.isNewerThan(to)) from = to
scores.recompute( scores.recompute(
frequency = frequency, frequency = frequency,
isNumerical = isNumerical, isNumerical = isNumerical,
targetValue = targetValue, targetValue = targetValue,
defaultValue = defaultValue,
computedEntries = computedEntries, computedEntries = computedEntries,
from = from, from = from,
to = to, to = to,
@ -123,6 +126,7 @@ data class Habit(
this.targetValue = other.targetValue this.targetValue = other.targetValue
this.type = other.type this.type = other.type
this.unit = other.unit this.unit = other.unit
this.defaultValue = other.defaultValue
this.uuid = other.uuid this.uuid = other.uuid
} }
@ -143,6 +147,7 @@ data class Habit(
if (targetValue != other.targetValue) return false if (targetValue != other.targetValue) return false
if (type != other.type) return false if (type != other.type) return false
if (unit != other.unit) return false if (unit != other.unit) return false
if (defaultValue != other.defaultValue) return false
if (uuid != other.uuid) return false if (uuid != other.uuid) return false
return true return true
@ -162,6 +167,7 @@ data class Habit(
result = 31 * result + targetValue.hashCode() result = 31 * result + targetValue.hashCode()
result = 31 * result + type.value result = 31 * result + type.value
result = 31 * result + unit.hashCode() result = 31 * result + unit.hashCode()
result = 31 * result + defaultValue.hashCode()
result = 31 * result + (uuid?.hashCode() ?: 0) result = 31 * result + (uuid?.hashCode() ?: 0)
return result return result
} }

@ -22,7 +22,6 @@ import org.isoron.uhabits.core.models.Score.Companion.compute
import java.util.ArrayList import java.util.ArrayList
import java.util.HashMap import java.util.HashMap
import javax.annotation.concurrent.ThreadSafe import javax.annotation.concurrent.ThreadSafe
import kotlin.math.max
import kotlin.math.min import kotlin.math.min
@ThreadSafe @ThreadSafe
@ -69,18 +68,18 @@ class ScoreList {
frequency: Frequency, frequency: Frequency,
isNumerical: Boolean, isNumerical: Boolean,
targetValue: Double, targetValue: Double,
defaultValue: Int,
computedEntries: EntryList, computedEntries: EntryList,
from: Timestamp, from: Timestamp,
to: Timestamp, to: Timestamp,
) { ) {
map.clear() map.clear()
if (computedEntries.getKnown().isEmpty()) return
if (from.isNewerThan(to)) return
var rollingSum = 0.0
var numerator = frequency.numerator var numerator = frequency.numerator
var denominator = frequency.denominator var denominator = frequency.denominator
val freq = frequency.toDouble() val freq = frequency.toDouble()
val values = computedEntries.getByInterval(from, to).map { it.value }.toIntArray() val values = computedEntries.getByInterval(from, to).map {
if (it.value >= 0) it.value else defaultValue
}.toIntArray()
// For non-daily boolean habits, we double the numerator and the denominator to smooth // 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 // out irregular repetition schedules (for example, weekly habits performed on different
@ -90,19 +89,32 @@ class ScoreList {
denominator *= 2 denominator *= 2
} }
var rollingSum = 0.0
var previousValue = 0.0 var previousValue = 0.0
val numericalPercentageComplete = { valueAccumulated: Double ->
if (targetValue > 0) {
min(1.0, valueAccumulated / 1000.0 / targetValue)
} else {
1.0
}
}
if (isNumerical) {
rollingSum = defaultValue.toDouble() * denominator
previousValue = numericalPercentageComplete(rollingSum)
rollingSum -= defaultValue
} else if (defaultValue == Entry.YES_MANUAL) {
previousValue = 1.0
rollingSum = denominator.toDouble() - 1
}
for (i in values.indices) { for (i in values.indices) {
val offset = values.size - i - 1 val offset = values.size - i - 1
if (isNumerical) { if (isNumerical) {
rollingSum += max(0, values[offset]) rollingSum += values[offset]
if (offset + denominator < values.size) { if (offset + denominator < values.size) {
rollingSum -= values[offset + denominator] rollingSum -= values[offset + denominator]
} }
val percentageCompleted = if (targetValue > 0) { val percentageCompleted = numericalPercentageComplete(rollingSum)
min(1.0, rollingSum / 1000 / targetValue)
} else {
1.0
}
previousValue = compute(freq, previousValue, percentageCompleted) previousValue = compute(freq, previousValue, percentageCompleted)
} else { } else {
if (values[offset] == Entry.YES_MANUAL) { if (values[offset] == Entry.YES_MANUAL) {

@ -82,6 +82,9 @@ class HabitRecord {
@field:Column @field:Column
var unit: String? = null var unit: String? = null
@field:Column
var defaultValue: Int? = null
@field:Column @field:Column
var id: Long? = null var id: Long? = null
@ -99,6 +102,7 @@ class HabitRecord {
targetType = model.targetType.value targetType = model.targetType.value
targetValue = model.targetValue targetValue = model.targetValue
unit = model.unit unit = model.unit
defaultValue = model.defaultValue
position = model.position position = model.position
question = model.question question = model.question
uuid = model.uuid uuid = model.uuid
@ -128,6 +132,7 @@ class HabitRecord {
habit.targetType = NumericalHabitType.fromInt(targetType!!) habit.targetType = NumericalHabitType.fromInt(targetType!!)
habit.targetValue = targetValue!! habit.targetValue = targetValue!!
habit.unit = unit!! habit.unit = unit!!
habit.defaultValue = defaultValue!!
habit.position = position!! habit.position = position!!
habit.uuid = uuid habit.uuid = uuid
if (reminderHour != null && reminderMin != null) { if (reminderHour != null && reminderMin != null) {

@ -20,6 +20,7 @@ package org.isoron.uhabits.core.ui.screens.habits.list
import org.isoron.uhabits.core.commands.CommandRunner import org.isoron.uhabits.core.commands.CommandRunner
import org.isoron.uhabits.core.commands.CreateRepetitionCommand import org.isoron.uhabits.core.commands.CreateRepetitionCommand
import org.isoron.uhabits.core.models.Entry
import org.isoron.uhabits.core.models.Habit import org.isoron.uhabits.core.models.Habit
import org.isoron.uhabits.core.models.HabitList import org.isoron.uhabits.core.models.HabitList
import org.isoron.uhabits.core.models.Timestamp import org.isoron.uhabits.core.models.Timestamp
@ -48,9 +49,10 @@ open class ListHabitsBehavior @Inject constructor(
fun onEdit(habit: Habit, timestamp: Timestamp?) { fun onEdit(habit: Habit, timestamp: Timestamp?) {
val entries = habit.computedEntries val entries = habit.computedEntries
val oldValue = entries.get(timestamp!!).value.toDouble() var oldValue = entries.get(timestamp!!).value
oldValue = if (oldValue != Entry.UNKNOWN) oldValue else habit.defaultValue
screen.showNumberPicker( screen.showNumberPicker(
oldValue / 1000, oldValue.toDouble() / 1000,
habit.unit habit.unit
) { newValue: Double -> ) { newValue: Double ->
val value = (newValue * 1000).roundToInt() val value = (newValue * 1000).roundToInt()

@ -25,6 +25,7 @@ import org.isoron.uhabits.core.commands.CommandRunner
import org.isoron.uhabits.core.commands.CreateRepetitionCommand import org.isoron.uhabits.core.commands.CreateRepetitionCommand
import org.isoron.uhabits.core.models.Entry import org.isoron.uhabits.core.models.Entry
import org.isoron.uhabits.core.models.Entry.Companion.SKIP import org.isoron.uhabits.core.models.Entry.Companion.SKIP
import org.isoron.uhabits.core.models.Entry.Companion.UNKNOWN
import org.isoron.uhabits.core.models.Entry.Companion.YES_AUTO 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.Entry.Companion.YES_MANUAL
import org.isoron.uhabits.core.models.Habit import org.isoron.uhabits.core.models.Habit
@ -37,13 +38,13 @@ import org.isoron.uhabits.core.ui.views.HistoryChart
import org.isoron.uhabits.core.ui.views.OnDateClickedListener import org.isoron.uhabits.core.ui.views.OnDateClickedListener
import org.isoron.uhabits.core.ui.views.Theme import org.isoron.uhabits.core.ui.views.Theme
import org.isoron.uhabits.core.utils.DateUtils import org.isoron.uhabits.core.utils.DateUtils
import kotlin.math.max
import kotlin.math.roundToInt import kotlin.math.roundToInt
data class HistoryCardState( data class HistoryCardState(
val color: PaletteColor, val color: PaletteColor,
val firstWeekday: DayOfWeek, val firstWeekday: DayOfWeek,
val series: List<HistoryChart.Square>, val series: List<HistoryChart.Square>,
val defaultSquare: HistoryChart.Square,
val theme: Theme, val theme: Theme,
val today: LocalDate, val today: LocalDate,
) )
@ -61,7 +62,8 @@ class HistoryCardPresenter(
screen.showFeedback() screen.showFeedback()
if (habit.isNumerical) { if (habit.isNumerical) {
val entries = habit.computedEntries val entries = habit.computedEntries
val oldValue = entries.get(timestamp).value var oldValue = entries.get(timestamp).value
oldValue = if (oldValue != UNKNOWN) oldValue else habit.defaultValue
screen.showNumberPicker(oldValue / 1000.0, habit.unit) { newValue: Double -> screen.showNumberPicker(oldValue / 1000.0, habit.unit) { newValue: Double ->
val thousands = (newValue * 1000).roundToInt() val thousands = (newValue * 1000).roundToInt()
commandRunner.run( commandRunner.run(
@ -74,7 +76,8 @@ class HistoryCardPresenter(
) )
} }
} else { } else {
val currentValue = habit.computedEntries.get(timestamp).value var currentValue = habit.computedEntries.get(timestamp).value
currentValue = if (currentValue != UNKNOWN) currentValue else habit.defaultValue
val nextValue = Entry.nextToggleValue( val nextValue = Entry.nextToggleValue(
value = currentValue, value = currentValue,
isSkipEnabled = preferences.isSkipEnabled, isSkipEnabled = preferences.isSkipEnabled,
@ -103,26 +106,33 @@ class HistoryCardPresenter(
): HistoryCardState { ): HistoryCardState {
val today = DateUtils.getTodayWithOffset() val today = DateUtils.getTodayWithOffset()
val oldest = habit.computedEntries.getKnown().lastOrNull()?.timestamp ?: today val oldest = habit.computedEntries.getKnown().lastOrNull()?.timestamp ?: today
val entries = habit.computedEntries.getByInterval(oldest, today) val entryValues = habit.computedEntries.getByInterval(oldest, today).map {
val series = if (habit.isNumerical) { if (it.value != UNKNOWN) it.value else habit.defaultValue
entries.map { }
Entry(it.timestamp, max(0, it.value)) val numericalToSquares = { value: Int ->
}.map { when (value) {
when (it.value) { 0 -> HistoryChart.Square.OFF
0 -> HistoryChart.Square.OFF else -> HistoryChart.Square.ON
else -> HistoryChart.Square.ON
}
} }
} else { }
entries.map { val yesNoToSquares = { value: Int ->
when (it.value) { when (value) {
YES_MANUAL -> HistoryChart.Square.ON YES_MANUAL -> HistoryChart.Square.ON
YES_AUTO -> HistoryChart.Square.DIMMED YES_AUTO -> HistoryChart.Square.DIMMED
SKIP -> HistoryChart.Square.HATCHED SKIP -> HistoryChart.Square.HATCHED
else -> HistoryChart.Square.OFF else -> HistoryChart.Square.OFF
}
} }
} }
val series = if (habit.isNumerical) {
entryValues.map(numericalToSquares)
} else {
entryValues.map(yesNoToSquares)
}
val defaultSquare = if (habit.isNumerical) {
numericalToSquares(habit.defaultValue)
} else {
yesNoToSquares(habit.defaultValue)
}
return HistoryCardState( return HistoryCardState(
color = habit.color, color = habit.color,
@ -130,6 +140,7 @@ class HistoryCardPresenter(
today = today.toLocalDate(), today = today.toLocalDate(),
theme = theme, theme = theme,
series = series, series = series,
defaultSquare = defaultSquare
) )
} }
} }

@ -41,6 +41,7 @@ class HistoryChart(
var firstWeekday: DayOfWeek, var firstWeekday: DayOfWeek,
var paletteColor: PaletteColor, var paletteColor: PaletteColor,
var series: List<Square>, var series: List<Square>,
var defaultSquare: Square,
var theme: Theme, var theme: Theme,
var today: LocalDate, var today: LocalDate,
var onDateClickedListener: OnDateClickedListener = OnDateClickedListener { }, var onDateClickedListener: OnDateClickedListener = OnDateClickedListener { },
@ -189,7 +190,7 @@ class HistoryChart(
offset: Int, 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 squareColor: Color
val color = theme.color(paletteColor.paletteIndex) val color = theme.color(paletteColor.paletteIndex)
squareColor = when (value) { squareColor = when (value) {

@ -0,0 +1 @@
alter table Habits add column defaultValue integer not null default 0;
Loading…
Cancel
Save