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/edit/EditHabitActivity.kt b/uhabits-android/src/main/java/org/isoron/uhabits/activities/habits/edit/EditHabitActivity.kt index 1a02d8845..cd5dde85b 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 @@ -35,11 +35,7 @@ import androidx.appcompat.app.AppCompatActivity import androidx.fragment.app.DialogFragment import com.android.datetimepicker.time.RadialPickerLayout import com.android.datetimepicker.time.TimePickerDialog -import kotlinx.android.synthetic.main.activity_edit_habit.nameInput -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 kotlinx.android.synthetic.main.activity_edit_habit.* import org.isoron.platform.gui.toInt import org.isoron.uhabits.HabitsApplication 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.CreateHabitCommand 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.Habit import org.isoron.uhabits.core.models.HabitType @@ -116,6 +113,10 @@ class EditHabitActivity : AppCompatActivity() { binding.questionInput.setText(habit.question) binding.notesInput.setText(habit.description) 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()) } else { habitType = HabitType.fromInt(intent.getIntExtra("habitType", HabitType.YES_NO.value)) @@ -138,11 +139,15 @@ class EditHabitActivity : AppCompatActivity() { HabitType.YES_NO -> { binding.unitOuterBox.visibility = View.GONE binding.targetOuterBox.visibility = View.GONE + binding.numericalFrequencyOuterBox.visibility = View.GONE + if (!component.preferences.isSkipEnabled) + binding.defaultSkip.visibility = View.GONE } HabitType.NUMERICAL -> { binding.nameInput.hint = getString(R.string.measurable_short_example) binding.questionInput.hint = getString(R.string.measurable_question_example) binding.frequencyOuterBox.visibility = View.GONE + binding.yesNoDefaultOuterBox.visibility = View.GONE } } @@ -264,6 +269,15 @@ class EditHabitActivity : AppCompatActivity() { habit.targetValue = targetInput.text.toString().toDouble() habit.targetType = NumericalHabitType.AT_LEAST 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 diff --git a/uhabits-android/src/main/java/org/isoron/uhabits/activities/habits/list/views/CheckmarkButtonView.kt b/uhabits-android/src/main/java/org/isoron/uhabits/activities/habits/list/views/CheckmarkButtonView.kt index 339cc46b4..75aa3fdde 100644 --- a/uhabits-android/src/main/java/org/isoron/uhabits/activities/habits/list/views/CheckmarkButtonView.kt +++ b/uhabits-android/src/main/java/org/isoron/uhabits/activities/habits/list/views/CheckmarkButtonView.kt @@ -65,6 +65,12 @@ class CheckmarkButtonView( invalidate() } + var defaultValue: Int = 0 + set(value) { + field = value + invalidate() + } + var value: Int = 0 set(value) { field = value @@ -128,7 +134,8 @@ class CheckmarkButtonView( } 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 NO -> { if (preferences.areQuestionMarksEnabled) mediumContrastColor @@ -136,15 +143,14 @@ class CheckmarkButtonView( } else -> lowContrastColor } - val id = when (value) { + var id = when (realValue) { SKIP -> R.string.fa_skipped NO -> R.string.fa_times - UNKNOWN -> { - if (preferences.areQuestionMarksEnabled) R.string.fa_question - else R.string.fa_times - } else -> R.string.fa_check } + if (value == UNKNOWN && preferences.areQuestionMarksEnabled) + id = R.string.fa_question + if (value == YES_AUTO) { paint.strokeWidth = 5f paint.style = Paint.Style.STROKE diff --git a/uhabits-android/src/main/java/org/isoron/uhabits/activities/habits/list/views/CheckmarkPanelView.kt b/uhabits-android/src/main/java/org/isoron/uhabits/activities/habits/list/views/CheckmarkPanelView.kt index 136ea0050..0804a09c4 100644 --- a/uhabits-android/src/main/java/org/isoron/uhabits/activities/habits/list/views/CheckmarkPanelView.kt +++ b/uhabits-android/src/main/java/org/isoron/uhabits/activities/habits/list/views/CheckmarkPanelView.kt @@ -48,6 +48,12 @@ class CheckmarkPanelView( setupButtons() } + var defaultValue = 0 + set(value) { + field = value + setupButtons() + } + var color = 0 set(value) { field = value @@ -72,6 +78,7 @@ class CheckmarkPanelView( index + dataOffset < values.size -> values[index + dataOffset] else -> UNKNOWN } + button.defaultValue = defaultValue button.color = color button.onToggle = { value -> onToggle(timestamp, value) } } 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..63ea6a5d2 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 @@ -228,6 +228,7 @@ class HabitCardView( } checkmarkPanel.apply { color = c + defaultValue = h.defaultValue visibility = when (h.isNumerical) { true -> View.GONE false -> View.VISIBLE @@ -235,6 +236,7 @@ class HabitCardView( } numberPanel.apply { color = c + defaultValue = h.defaultValue / 1000.0 units = h.unit threshold = h.targetValue visibility = when (h.isNumerical) { 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..08931affe 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 @@ -76,6 +76,12 @@ class NumberButtonView( invalidate() } + var defaultValue = 0.0 + set(value) { + field = value + invalidate() + } + var value = 0.0 set(value) { field = value @@ -153,9 +159,10 @@ class NumberButtonView( } fun draw(canvas: Canvas) { + val realValue = if (value >= 0) value else defaultValue val activeColor = when { - value <= 0.0 -> lowContrast - value < threshold -> mediumContrast + realValue == 0.0 -> lowContrast + realValue < threshold -> mediumContrast else -> color } @@ -175,7 +182,7 @@ class NumberButtonView( textSize = dim(R.dimen.smallerTextSize) } else -> { - label = "0" + label = defaultValue.toShortString() 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..9b990b9a1 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 @@ -47,6 +47,12 @@ class NumberPanelView( setupButtons() } + var defaultValue = 0.0 + set(value) { + field = value + setupButtons() + } + var threshold = 0.0 set(value) { field = value @@ -83,6 +89,7 @@ class NumberPanelView( index + dataOffset < values.size -> values[index + dataOffset] else -> 0.0 } + button.defaultValue = defaultValue button.color = color button.threshold = threshold button.units = units 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..1467688f6 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-android/src/main/res/layout/activity_edit_habit.xml b/uhabits-android/src/main/res/layout/activity_edit_habit.xml index b3a33c373..a8e91e7f0 100644 --- a/uhabits-android/src/main/res/layout/activity_edit_habit.xml +++ b/uhabits-android/src/main/res/layout/activity_edit_habit.xml @@ -151,6 +151,35 @@ + + + + + + + + + + + + + android:text="@string/startAt" /> + android:text="0"/> - + android:text="@string/target" /> + + + + + + + + + - Developers Version %s Frequency + Default value Checkmark Checkmark Stack Widget Frequency Stack Widget @@ -186,9 +187,11 @@ Unit e.g. Did you exercise today? Question + Starts at Target Yes No + Skip Change sound, vibration, light and other notification settings Customize notifications View privacy policy diff --git a/uhabits-core/src/jvmMain/java/org/isoron/uhabits/core/Constants.kt b/uhabits-core/src/jvmMain/java/org/isoron/uhabits/core/Constants.kt index 1388cb5f8..be6c9634b 100644 --- a/uhabits-core/src/jvmMain/java/org/isoron/uhabits/core/Constants.kt +++ b/uhabits-core/src/jvmMain/java/org/isoron/uhabits/core/Constants.kt @@ -20,4 +20,4 @@ package org.isoron.uhabits.core const val DATABASE_FILENAME = "uhabits.db" -const val DATABASE_VERSION = 24 +const val DATABASE_VERSION = 25 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..be7690894 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 @@ -35,6 +35,7 @@ data class Habit( var targetValue: Double = 0.0, var type: HabitType = HabitType.YES_NO, var unit: String = "", + var defaultValue: Int = 0, var uuid: String? = null, val computedEntries: EntryList, val originalEntries: EntryList, @@ -69,7 +70,7 @@ data class Habit( } fun isFailedToday(): Boolean { - val today = DateUtils.getTodayWithOffset() + var today = DateUtils.getTodayWithOffset() val value = computedEntries.get(today).value return if (isNumerical) { when (targetType) { @@ -88,15 +89,17 @@ 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, targetValue = targetValue, + defaultValue = defaultValue, computedEntries = computedEntries, from = from, to = to, @@ -123,6 +126,7 @@ data class Habit( this.targetValue = other.targetValue this.type = other.type this.unit = other.unit + this.defaultValue = other.defaultValue this.uuid = other.uuid } @@ -143,6 +147,7 @@ data class Habit( if (targetValue != other.targetValue) return false if (type != other.type) return false if (unit != other.unit) return false + if (defaultValue != other.defaultValue) return false if (uuid != other.uuid) return false return true @@ -162,6 +167,7 @@ data class Habit( result = 31 * result + targetValue.hashCode() result = 31 * result + type.value result = 31 * result + unit.hashCode() + result = 31 * result + defaultValue.hashCode() result = 31 * result + (uuid?.hashCode() ?: 0) return result } 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..eff6fad7a 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 @@ -22,7 +22,6 @@ import org.isoron.uhabits.core.models.Score.Companion.compute import java.util.ArrayList import java.util.HashMap import javax.annotation.concurrent.ThreadSafe -import kotlin.math.max import kotlin.math.min @ThreadSafe @@ -69,18 +68,18 @@ class ScoreList { frequency: Frequency, isNumerical: Boolean, targetValue: Double, + defaultValue: Int, computedEntries: EntryList, from: Timestamp, 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 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 // out irregular repetition schedules (for example, weekly habits performed on different @@ -90,19 +89,32 @@ class ScoreList { denominator *= 2 } + var rollingSum = 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) { val offset = values.size - i - 1 if (isNumerical) { - rollingSum += max(0, values[offset]) + rollingSum += values[offset] if (offset + denominator < values.size) { rollingSum -= values[offset + denominator] } - val percentageCompleted = if (targetValue > 0) { - min(1.0, rollingSum / 1000 / targetValue) - } else { - 1.0 - } + val percentageCompleted = numericalPercentageComplete(rollingSum) previousValue = compute(freq, previousValue, percentageCompleted) } else { if (values[offset] == Entry.YES_MANUAL) { diff --git a/uhabits-core/src/jvmMain/java/org/isoron/uhabits/core/models/sqlite/records/HabitRecord.kt b/uhabits-core/src/jvmMain/java/org/isoron/uhabits/core/models/sqlite/records/HabitRecord.kt index dc0386799..627c20171 100644 --- a/uhabits-core/src/jvmMain/java/org/isoron/uhabits/core/models/sqlite/records/HabitRecord.kt +++ b/uhabits-core/src/jvmMain/java/org/isoron/uhabits/core/models/sqlite/records/HabitRecord.kt @@ -82,6 +82,9 @@ class HabitRecord { @field:Column var unit: String? = null + @field:Column + var defaultValue: Int? = null + @field:Column var id: Long? = null @@ -99,6 +102,7 @@ class HabitRecord { targetType = model.targetType.value targetValue = model.targetValue unit = model.unit + defaultValue = model.defaultValue position = model.position question = model.question uuid = model.uuid @@ -128,6 +132,7 @@ class HabitRecord { habit.targetType = NumericalHabitType.fromInt(targetType!!) habit.targetValue = targetValue!! habit.unit = unit!! + habit.defaultValue = defaultValue!! habit.position = position!! habit.uuid = uuid if (reminderHour != null && reminderMin != null) { diff --git a/uhabits-core/src/jvmMain/java/org/isoron/uhabits/core/ui/screens/habits/list/ListHabitsBehavior.kt b/uhabits-core/src/jvmMain/java/org/isoron/uhabits/core/ui/screens/habits/list/ListHabitsBehavior.kt index 89fb09af4..e09066c22 100644 --- a/uhabits-core/src/jvmMain/java/org/isoron/uhabits/core/ui/screens/habits/list/ListHabitsBehavior.kt +++ b/uhabits-core/src/jvmMain/java/org/isoron/uhabits/core/ui/screens/habits/list/ListHabitsBehavior.kt @@ -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.CreateRepetitionCommand +import org.isoron.uhabits.core.models.Entry import org.isoron.uhabits.core.models.Habit import org.isoron.uhabits.core.models.HabitList import org.isoron.uhabits.core.models.Timestamp @@ -48,9 +49,10 @@ open class ListHabitsBehavior @Inject constructor( fun onEdit(habit: Habit, timestamp: Timestamp?) { 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( - oldValue / 1000, + oldValue.toDouble() / 1000, habit.unit ) { newValue: Double -> val value = (newValue * 1000).roundToInt() 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..f988855d9 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 @@ -25,6 +25,7 @@ import org.isoron.uhabits.core.commands.CommandRunner import org.isoron.uhabits.core.commands.CreateRepetitionCommand import org.isoron.uhabits.core.models.Entry 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_MANUAL 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.Theme import org.isoron.uhabits.core.utils.DateUtils -import kotlin.math.max import kotlin.math.roundToInt data class HistoryCardState( val color: PaletteColor, val firstWeekday: DayOfWeek, val series: List, + val defaultSquare: HistoryChart.Square, val theme: Theme, val today: LocalDate, ) @@ -61,7 +62,8 @@ class HistoryCardPresenter( screen.showFeedback() if (habit.isNumerical) { 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 -> val thousands = (newValue * 1000).roundToInt() commandRunner.run( @@ -74,7 +76,8 @@ class HistoryCardPresenter( ) } } 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( value = currentValue, isSkipEnabled = preferences.isSkipEnabled, @@ -103,26 +106,33 @@ class HistoryCardPresenter( ): HistoryCardState { val today = DateUtils.getTodayWithOffset() 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 - } + val entryValues = habit.computedEntries.getByInterval(oldest, today).map { + if (it.value != UNKNOWN) it.value else habit.defaultValue + } + val numericalToSquares = { value: Int -> + when (value) { + 0 -> HistoryChart.Square.OFF + else -> HistoryChart.Square.ON } - } else { - entries.map { - when (it.value) { - YES_MANUAL -> HistoryChart.Square.ON - YES_AUTO -> HistoryChart.Square.DIMMED - SKIP -> HistoryChart.Square.HATCHED - else -> HistoryChart.Square.OFF - } + } + val yesNoToSquares = { value: Int -> + when (value) { + YES_MANUAL -> HistoryChart.Square.ON + YES_AUTO -> HistoryChart.Square.DIMMED + SKIP -> HistoryChart.Square.HATCHED + 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( color = habit.color, @@ -130,6 +140,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/jvmMain/resources/migrations/25.sql b/uhabits-core/src/jvmMain/resources/migrations/25.sql new file mode 100644 index 000000000..7a10e60da --- /dev/null +++ b/uhabits-core/src/jvmMain/resources/migrations/25.sql @@ -0,0 +1 @@ +alter table Habits add column defaultValue integer not null default 0; \ No newline at end of file