All scores, frequency, history, etc. corrected for the skip days features.

pull/2012/head
Dharanish 1 year ago
parent ec08b602f3
commit f5da00a989

@ -51,6 +51,7 @@ import org.isoron.uhabits.core.models.HabitType
import org.isoron.uhabits.core.models.NumericalHabitType import org.isoron.uhabits.core.models.NumericalHabitType
import org.isoron.uhabits.core.models.PaletteColor import org.isoron.uhabits.core.models.PaletteColor
import org.isoron.uhabits.core.models.Reminder import org.isoron.uhabits.core.models.Reminder
import org.isoron.uhabits.core.models.SkipDays
import org.isoron.uhabits.core.models.WeekdayList import org.isoron.uhabits.core.models.WeekdayList
import org.isoron.uhabits.databinding.ActivityEditHabitBinding import org.isoron.uhabits.databinding.ActivityEditHabitBinding
import org.isoron.uhabits.utils.ColorUtils import org.isoron.uhabits.utils.ColorUtils
@ -106,8 +107,8 @@ class EditHabitActivity : AppCompatActivity() {
color = habit.color color = habit.color
freqNum = habit.frequency.numerator freqNum = habit.frequency.numerator
freqDen = habit.frequency.denominator freqDen = habit.frequency.denominator
isSkipDays = habit.skipDays isSkipDays = habit.skipDays.isSkipDays
listSkipDays = habit.skipDaysList listSkipDays = habit.skipDays.days
targetType = habit.targetType targetType = habit.targetType
habit.reminder?.let { habit.reminder?.let {
reminderHour = it.hour reminderHour = it.hour
@ -302,8 +303,7 @@ class EditHabitActivity : AppCompatActivity() {
} }
habit.frequency = Frequency(freqNum, freqDen) habit.frequency = Frequency(freqNum, freqDen)
habit.skipDays = isSkipDays habit.skipDays = SkipDays(isSkipDays, listSkipDays)
habit.skipDaysList = listSkipDays
if (habitType == HabitType.NUMERICAL) { if (habitType == HabitType.NUMERICAL) {
habit.targetValue = binding.targetInput.text.toString().toDouble() habit.targetValue = binding.targetInput.text.toString().toDouble()
habit.targetType = targetType habit.targetType = targetType
@ -358,6 +358,12 @@ class EditHabitActivity : AppCompatActivity() {
} }
private fun populateSkipDays() { private fun populateSkipDays() {
val preferences = (application as HabitsApplication).component.preferences
if (preferences.isSkipEnabled || isSkipDays) {
binding.skipDaysOuterBox.visibility = View.VISIBLE
} else {
binding.skipDaysOuterBox.visibility = View.GONE
}
if (isSkipDays) { if (isSkipDays) {
binding.skipDaysPicker.text = listSkipDays.toFormattedString(this) binding.skipDaysPicker.text = listSkipDays.toFormattedString(this)
} else { } else {

@ -33,6 +33,7 @@ import org.isoron.uhabits.core.ui.screens.habits.show.views.SubtitleCardState
import org.isoron.uhabits.databinding.ShowHabitSubtitleBinding import org.isoron.uhabits.databinding.ShowHabitSubtitleBinding
import org.isoron.uhabits.utils.InterfaceUtils import org.isoron.uhabits.utils.InterfaceUtils
import org.isoron.uhabits.utils.formatTime import org.isoron.uhabits.utils.formatTime
import org.isoron.uhabits.utils.toFormattedString
class SubtitleCardView(context: Context, attrs: AttributeSet) : LinearLayout(context, attrs) { class SubtitleCardView(context: Context, attrs: AttributeSet) : LinearLayout(context, attrs) {
@ -78,6 +79,12 @@ class SubtitleCardView(context: Context, attrs: AttributeSet) : LinearLayout(con
if (state.question.isEmpty()) { if (state.question.isEmpty()) {
binding.questionLabel.visibility = View.GONE binding.questionLabel.visibility = View.GONE
} }
if (state.skipDays.isSkipDays) {
binding.skipLabel.visibility = View.VISIBLE
binding.skipLabel.text = context.getString(R.string.skip_day) + " " + state.skipDays.days.toFormattedString(context)
} else {
binding.skipLabel.visibility = View.GONE
}
postInvalidate() postInvalidate()
} }

@ -251,6 +251,7 @@
<!-- Notes --> <!-- Notes -->
<FrameLayout <FrameLayout
android:id="@+id/skipDaysOuterBox"
style="@style/FormOuterBox" style="@style/FormOuterBox"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent"> android:layout_height="match_parent">

@ -76,6 +76,17 @@
android:text="@string/every_day" android:text="@string/every_day"
android:textColor="?attr/contrast60" android:textColor="?attr/contrast60"
android:layout_marginStart="4dp" android:layout_marginStart="4dp"
android:layout_marginEnd="8dp"
android:textSize="@dimen/smallTextSize" />
<TextView
android:id="@+id/skipLabel"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:paddingTop="1dp"
android:textColor="?attr/contrast60"
android:text=""
android:layout_marginStart="4dp"
android:layout_marginEnd="16dp" android:layout_marginEnd="16dp"
android:textSize="@dimen/smallTextSize" /> android:textSize="@dimen/smallTextSize" />
@ -95,6 +106,7 @@
android:textColor="?attr/contrast60" android:textColor="?attr/contrast60"
android:text="8:00 AM" android:text="8:00 AM"
android:layout_marginStart="4dp" android:layout_marginStart="4dp"
android:layout_marginEnd="16dp"
android:textSize="@dimen/smallTextSize" /> android:textSize="@dimen/smallTextSize" />
</LinearLayout> </LinearLayout>

@ -1,4 +1,4 @@
create table Habits ( id integer primary key autoincrement, archived integer, color integer, description text, freq_den integer, freq_num integer, skip_days integer, skip_days_list integer, highlight integer, name text, position integer, reminder_hour integer, reminder_min integer ) create table Habits ( id integer primary key autoincrement, archived integer, color integer, description text, freq_den integer, freq_num integer highlight integer, name text, position integer, reminder_hour integer, reminder_min integer )
create table Checkmarks ( id integer primary key autoincrement, habit integer references habits(id), timestamp integer, value integer ) create table Checkmarks ( id integer primary key autoincrement, habit integer references habits(id), timestamp integer, value integer )
create table Repetitions ( id integer primary key autoincrement, habit integer references habits(id), timestamp integer ) create table Repetitions ( id integer primary key autoincrement, habit integer references habits(id), timestamp integer )
create table Streak ( id integer primary key autoincrement, end integer, habit integer references habits(id), length integer, start integer ) create table Streak ( id integer primary key autoincrement, end integer, habit integer references habits(id), length integer, start integer )

@ -0,0 +1,2 @@
alter table Habits add column skip_days integer not null default 0
alter table Habits add column skip_days_list integer not null default 0

@ -42,8 +42,8 @@ open class EntryList {
* skip days are enabled and that day is to be skipped * skip days are enabled and that day is to be skipped
*/ */
@Synchronized @Synchronized
open fun get(timestamp: Timestamp, skipDays: Boolean = false, skipDaysList: WeekdayList = WeekdayList.NO_DAY): Entry { open fun get(timestamp: Timestamp, skipDays: SkipDays = SkipDays.NONE): Entry {
return entriesByTimestamp[timestamp] ?: if (skipDays && skipDaysList.isDayTrue(timestamp.weekday)) Entry(timestamp, SKIP) else Entry(timestamp, UNKNOWN) return if (skipDays.isDaySkipped(timestamp)) Entry(timestamp, SKIP) else entriesByTimestamp[timestamp] ?: Entry(timestamp, UNKNOWN)
} }
/** /**
@ -52,12 +52,12 @@ open class EntryList {
* included. * included.
*/ */
@Synchronized @Synchronized
open fun getByInterval(from: Timestamp, to: Timestamp, skipDays: Boolean = false, skipDaysList: WeekdayList = WeekdayList.NO_DAY): List<Entry> { open fun getByInterval(from: Timestamp, to: Timestamp, skipDays: SkipDays = SkipDays.NONE): List<Entry> {
val result = mutableListOf<Entry>() val result = mutableListOf<Entry>()
if (from.isNewerThan(to)) return result if (from.isNewerThan(to)) return result
var current = to var current = to
while (current >= from) { while (current >= from) {
result.add(get(current, skipDays, skipDaysList)) result.add(get(current, skipDays))
current = current.minus(1) current = current.minus(1)
} }
return result return result
@ -91,16 +91,18 @@ open class EntryList {
open fun recomputeFrom( open fun recomputeFrom(
originalEntries: EntryList, originalEntries: EntryList,
frequency: Frequency, frequency: Frequency,
isNumerical: Boolean isNumerical: Boolean,
skipDays: SkipDays
) { ) {
clear() clear()
val original = originalEntries.getKnown() val original = originalEntries.getKnown()
if (isNumerical) { if (isNumerical) {
original.forEach { add(it) } val computed = addEntriesWithSkipDays(original, skipDays)
computed.forEach { add(it) }
} else { } else {
val intervals = buildIntervals(frequency, original) val intervals = buildIntervals(frequency, original)
snapIntervalsTogether(intervals) snapIntervalsTogether(intervals)
val computed = buildEntriesFromInterval(original, intervals) val computed = buildEntriesFromInterval(original, intervals, skipDays)
computed.filter { it.value != UNKNOWN || it.notes.isNotEmpty() }.forEach { add(it) } computed.filter { it.value != UNKNOWN || it.notes.isNotEmpty() }.forEach { add(it) }
} }
} }
@ -129,10 +131,10 @@ open class EntryList {
fun computeWeekdayFrequency(isNumerical: Boolean): HashMap<Timestamp, Array<Int>> { fun computeWeekdayFrequency(isNumerical: Boolean): HashMap<Timestamp, Array<Int>> {
val entries = getKnown() val entries = getKnown()
val map = hashMapOf<Timestamp, Array<Int>>() val map = hashMapOf<Timestamp, Array<Int>>()
for ((originalTimestamp, value) in entries) { for ((computedTimestamp, value) in entries) {
val weekday = originalTimestamp.weekday val weekday = computedTimestamp.weekday
val truncatedTimestamp = Timestamp( val truncatedTimestamp = Timestamp(
originalTimestamp.toCalendar().apply { computedTimestamp.toCalendar().apply {
set(Calendar.DAY_OF_MONTH, 1) set(Calendar.DAY_OF_MONTH, 1)
}.timeInMillis }.timeInMillis
) )
@ -143,7 +145,7 @@ open class EntryList {
map[truncatedTimestamp] = list map[truncatedTimestamp] = list
} }
if (isNumerical) { if (isNumerical && value != SKIP) {
list[weekday] += value list[weekday] += value
} else if (value == YES_MANUAL) { } else if (value == YES_MANUAL) {
list[weekday] += 1 list[weekday] += 1
@ -168,7 +170,8 @@ open class EntryList {
*/ */
fun buildEntriesFromInterval( fun buildEntriesFromInterval(
original: List<Entry>, original: List<Entry>,
intervals: List<Interval> intervals: List<Interval>,
skipDays: SkipDays
): List<Entry> { ): List<Entry> {
val result = arrayListOf<Entry>() val result = arrayListOf<Entry>()
if (original.isEmpty()) return result if (original.isEmpty()) return result
@ -197,15 +200,17 @@ open class EntryList {
current = interval.end current = interval.end
while (current >= interval.begin) { while (current >= interval.begin) {
val offset = current.daysUntil(to) val offset = current.daysUntil(to)
result[offset] = Entry(current, YES_AUTO) result[offset] = if (skipDays.isDaySkipped(current)) Entry(current, SKIP) else Entry(current, YES_AUTO)
current = current.minus(1) current = current.minus(1)
} }
} }
// Copy original entries // Copy original entries except for the skipped days
original.forEach { entry -> original.forEach { entry ->
val offset = entry.timestamp.daysUntil(to) val offset = entry.timestamp.daysUntil(to)
val value = if ( val value = if (skipDays.isDaySkipped(entry)) {
SKIP
} else if (
result[offset].value == UNKNOWN || result[offset].value == UNKNOWN ||
entry.value == SKIP || entry.value == SKIP ||
entry.value == YES_MANUAL entry.value == YES_MANUAL
@ -272,6 +277,29 @@ open class EntryList {
} }
return intervals return intervals
} }
fun addEntriesWithSkipDays(original: List<Entry>, skipDays: SkipDays): List<Entry> {
if (original.isEmpty()) return original
val earliest = original.last().timestamp
val today = DateUtils.getTodayWithOffset()
val computed = mutableListOf<Entry>()
var current = today
var offset = 0
while (current >= earliest) {
if (current == original[offset].timestamp) {
if (!skipDays.isDaySkipped(current)) {
computed.add(original[offset])
} else {
computed.add(Entry(current, SKIP))
}
offset++
} else if (skipDays.isDaySkipped(current)) {
computed.add(Entry(current, SKIP))
}
current = current.minus(1)
}
return computed
}
} }
} }
@ -324,10 +352,12 @@ fun List<Entry>.groupedSum(
*/ */
fun List<Entry>.countSkippedDays( fun List<Entry>.countSkippedDays(
truncateField: DateUtils.TruncateField, truncateField: DateUtils.TruncateField,
firstWeekday: Int = Calendar.SATURDAY firstWeekday: Int = Calendar.SATURDAY,
skipDays: SkipDays
): List<Entry> { ): List<Entry> {
val thisIntervalStart = DateUtils.getTodayWithOffset().truncate(truncateField, firstWeekday)
return this.map { (timestamp, value) -> return this.map { (timestamp, value) ->
if (value == SKIP) { if (value == SKIP || skipDays.isDaySkipped(timestamp)) {
Entry(timestamp, 1) Entry(timestamp, 1)
} else { } else {
Entry(timestamp, 0) Entry(timestamp, 0)
@ -341,5 +371,5 @@ fun List<Entry>.countSkippedDays(
Entry(timestamp, entries.sumOf { it.value }) Entry(timestamp, entries.sumOf { it.value })
}.sortedBy { (timestamp, _) -> }.sortedBy { (timestamp, _) ->
-timestamp.unixTime -timestamp.unixTime
} }.filter { it.timestamp == thisIntervalStart }
} }

@ -25,8 +25,7 @@ data class Habit(
var color: PaletteColor = PaletteColor(8), var color: PaletteColor = PaletteColor(8),
var description: String = "", var description: String = "",
var frequency: Frequency = Frequency.DAILY, var frequency: Frequency = Frequency.DAILY,
var skipDays: Boolean = false, var skipDays: SkipDays = SkipDays.NONE,
var skipDaysList: WeekdayList = WeekdayList.NO_DAY,
var id: Long? = null, var id: Long? = null,
var isArchived: Boolean = false, var isArchived: Boolean = false,
var name: String = "", var name: String = "",
@ -80,7 +79,8 @@ data class Habit(
computedEntries.recomputeFrom( computedEntries.recomputeFrom(
originalEntries = originalEntries, originalEntries = originalEntries,
frequency = frequency, frequency = frequency,
isNumerical = isNumerical isNumerical = isNumerical,
skipDays = skipDays
) )
val today = DateUtils.getTodayWithOffset() val today = DateUtils.getTodayWithOffset()
@ -93,7 +93,6 @@ data class Habit(
frequency = frequency, frequency = frequency,
isNumerical = isNumerical, isNumerical = isNumerical,
skipDays = skipDays, skipDays = skipDays,
skipDaysList = skipDaysList,
numericalHabitType = targetType, numericalHabitType = targetType,
targetValue = targetValue, targetValue = targetValue,
computedEntries = computedEntries, computedEntries = computedEntries,
@ -113,7 +112,6 @@ data class Habit(
this.description = other.description this.description = other.description
this.frequency = other.frequency this.frequency = other.frequency
this.skipDays = other.skipDays this.skipDays = other.skipDays
this.skipDaysList = other.skipDaysList
// this.id should not be copied // this.id should not be copied
this.isArchived = other.isArchived this.isArchived = other.isArchived
this.name = other.name this.name = other.name
@ -135,7 +133,6 @@ data class Habit(
if (description != other.description) return false if (description != other.description) return false
if (frequency != other.frequency) return false if (frequency != other.frequency) return false
if (skipDays != other.skipDays) return false if (skipDays != other.skipDays) return false
if (skipDaysList != other.skipDaysList) return false
if (id != other.id) return false if (id != other.id) return false
if (isArchived != other.isArchived) return false if (isArchived != other.isArchived) return false
if (name != other.name) return false if (name != other.name) return false
@ -155,7 +152,7 @@ data class Habit(
var result = color.hashCode() var result = color.hashCode()
result = 31 * result + description.hashCode() result = 31 * result + description.hashCode()
result = 31 * result + frequency.hashCode() result = 31 * result + frequency.hashCode()
result = 31 * result + skipDaysList.hashCode() result = 31 * result + skipDays.hashCode()
result = 31 * result + (id?.hashCode() ?: 0) result = 31 * result + (id?.hashCode() ?: 0)
result = 31 * result + isArchived.hashCode() result = 31 * result + isArchived.hashCode()
result = 31 * result + name.hashCode() result = 31 * result + name.hashCode()

@ -68,8 +68,7 @@ class ScoreList {
fun recompute( fun recompute(
frequency: Frequency, frequency: Frequency,
isNumerical: Boolean, isNumerical: Boolean,
skipDays: Boolean, skipDays: SkipDays,
skipDaysList: WeekdayList,
numericalHabitType: NumericalHabitType, numericalHabitType: NumericalHabitType,
targetValue: Double, targetValue: Double,
computedEntries: EntryList, computedEntries: EntryList,
@ -81,7 +80,7 @@ class ScoreList {
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, skipDays, skipDaysList).map { it.value }.toIntArray() val values = computedEntries.getByInterval(from, to, skipDays).map { it.value }.toIntArray()
val isAtMost = numericalHabitType == NumericalHabitType.AT_MOST val isAtMost = numericalHabitType == NumericalHabitType.AT_MOST
// 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

@ -0,0 +1,41 @@
/*
* Copyright (C) 2016-2021 Álinson Santos Xavier <git@axavier.org>
*
* This file is part of Loop Habit Tracker.
*
* Loop Habit Tracker is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by the
* Free Software Foundation, either version 3 of the License, or (at your
* option) any later version.
*
* Loop Habit Tracker is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*
* You should have received a copy of the GNU General Public License along
* with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.isoron.uhabits.core.models
data class SkipDays(
val isSkipDays: Boolean,
val days: WeekdayList
) {
fun isDaySkipped(day: Int): Boolean {
return isSkipDays && days.isDayTrue(day)
}
fun isDaySkipped(day: Timestamp): Boolean {
return isSkipDays && days.isDayTrue(day.weekday)
}
fun isDaySkipped(entry: Entry): Boolean {
return isSkipDays && days.isDayTrue(entry.timestamp.weekday)
}
companion object {
@JvmField
val NONE = SkipDays(false, WeekdayList(0))
}
}

@ -24,6 +24,7 @@ import org.isoron.uhabits.core.database.Repository
import org.isoron.uhabits.core.models.Entry import org.isoron.uhabits.core.models.Entry
import org.isoron.uhabits.core.models.EntryList import org.isoron.uhabits.core.models.EntryList
import org.isoron.uhabits.core.models.Frequency import org.isoron.uhabits.core.models.Frequency
import org.isoron.uhabits.core.models.SkipDays
import org.isoron.uhabits.core.models.Timestamp import org.isoron.uhabits.core.models.Timestamp
import org.isoron.uhabits.core.models.WeekdayList import org.isoron.uhabits.core.models.WeekdayList
import org.isoron.uhabits.core.models.sqlite.records.EntryRecord import org.isoron.uhabits.core.models.sqlite.records.EntryRecord
@ -44,13 +45,13 @@ class SQLiteEntryList(database: Database) : EntryList() {
isLoaded = true isLoaded = true
} }
override fun get(timestamp: Timestamp, skipDays: Boolean, skipDaysList: WeekdayList): Entry { override fun get(timestamp: Timestamp, skipDays: SkipDays): Entry {
loadRecords() loadRecords()
return super.get(timestamp, skipDays, skipDaysList) return super.get(timestamp, skipDays)
} }
override fun getByInterval(from: Timestamp, to: Timestamp, skipDays: Boolean, skipDaysList: WeekdayList): List<Entry> { override fun getByInterval(from: Timestamp, to: Timestamp, skipDays: SkipDays): List<Entry> {
loadRecords() loadRecords()
return super.getByInterval(from, to, skipDays, skipDaysList) return super.getByInterval(from, to, skipDays)
} }
override fun add(entry: Entry) { override fun add(entry: Entry) {
@ -78,7 +79,7 @@ class SQLiteEntryList(database: Database) : EntryList() {
return super.getKnown() return super.getKnown()
} }
override fun recomputeFrom(originalEntries: EntryList, frequency: Frequency, isNumerical: Boolean) { override fun recomputeFrom(originalEntries: EntryList, frequency: Frequency, isNumerical: Boolean, skipDays: SkipDays) {
throw UnsupportedOperationException() throw UnsupportedOperationException()
} }

@ -26,6 +26,7 @@ import org.isoron.uhabits.core.models.HabitType
import org.isoron.uhabits.core.models.NumericalHabitType import org.isoron.uhabits.core.models.NumericalHabitType
import org.isoron.uhabits.core.models.PaletteColor import org.isoron.uhabits.core.models.PaletteColor
import org.isoron.uhabits.core.models.Reminder import org.isoron.uhabits.core.models.Reminder
import org.isoron.uhabits.core.models.SkipDays
import org.isoron.uhabits.core.models.WeekdayList import org.isoron.uhabits.core.models.WeekdayList
import java.util.Objects.requireNonNull import java.util.Objects.requireNonNull
@ -111,8 +112,8 @@ class HabitRecord {
val (numerator, denominator) = model.frequency val (numerator, denominator) = model.frequency
freqNum = numerator freqNum = numerator
freqDen = denominator freqDen = denominator
skipDays = if (model.skipDays) 1 else 0 skipDays = if (model.skipDays.isSkipDays) 1 else 0
skipDaysList = model.skipDaysList.toInteger() skipDaysList = model.skipDays.days.toInteger()
reminderDays = 0 reminderDays = 0
reminderMin = null reminderMin = null
reminderHour = null reminderHour = null
@ -130,8 +131,7 @@ class HabitRecord {
habit.description = description!! habit.description = description!!
habit.question = question!! habit.question = question!!
habit.frequency = Frequency(freqNum!!, freqDen!!) habit.frequency = Frequency(freqNum!!, freqDen!!)
habit.skipDays = (skipDays!! == 1) habit.skipDays = SkipDays(skipDays!! == 1, WeekdayList(skipDaysList!!))
habit.skipDaysList = WeekdayList(skipDaysList!!)
habit.color = PaletteColor(color!!) habit.color = PaletteColor(color!!)
habit.isArchived = archived != 0 habit.isArchived = archived != 0
habit.type = HabitType.fromInt(type!!) habit.type = HabitType.fromInt(type!!)

@ -312,8 +312,7 @@ class HabitCardListCache @Inject constructor(
val list: MutableList<Int> = ArrayList() val list: MutableList<Int> = ArrayList()
val notes: MutableList<String> = ArrayList() val notes: MutableList<String> = ArrayList()
val skipDays = habit.skipDays val skipDays = habit.skipDays
val skipDaysList = habit.skipDaysList for ((_, value, note) in habit.computedEntries.getByInterval(dateFrom, today, skipDays)) {
for ((_, value, note) in habit.computedEntries.getByInterval(dateFrom, today, skipDays, skipDaysList)) {
list.add(value) list.add(value)
notes.add(note) notes.add(note)
} }

@ -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.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
import org.isoron.uhabits.core.models.HabitList import org.isoron.uhabits.core.models.HabitList
@ -53,10 +54,11 @@ open class ListHabitsBehavior @Inject constructor(
fun onEdit(habit: Habit, timestamp: Timestamp?) { fun onEdit(habit: Habit, timestamp: Timestamp?) {
val entry = habit.computedEntries.get(timestamp!!) val entry = habit.computedEntries.get(timestamp!!)
if (habit.skipDays.isDaySkipped(timestamp)) return
if (habit.type == HabitType.NUMERICAL) { if (habit.type == HabitType.NUMERICAL) {
val oldValue = entry.value.toDouble() / 1000 val oldValue = entry.value.toDouble() / 1000
screen.showNumberPopup(oldValue, entry.notes) { newValue: Double, newNotes: String, x: Float, y: Float -> screen.showNumberPopup(oldValue, entry.notes) { newValue: Double, newNotes: String, x: Float, y: Float ->
val value = if (habit.skipDays && habit.skipDaysList.isDayTrue(timestamp.weekday)) 3 else (newValue * 1000).roundToInt() val value = if (habit.skipDays.isDaySkipped(timestamp)) Entry.SKIP else (newValue * 1000).roundToInt()
if (newValue != oldValue) { if (newValue != oldValue) {
if ( if (

@ -42,7 +42,7 @@ class FrequencyCardPresenter {
) = FrequencyCardState( ) = FrequencyCardState(
color = habit.color, color = habit.color,
isNumerical = habit.isNumerical, isNumerical = habit.isNumerical,
frequency = habit.originalEntries.computeWeekdayFrequency( frequency = habit.computedEntries.computeWeekdayFrequency(
isNumerical = habit.isNumerical isNumerical = habit.isNumerical
), ),
firstWeekday = firstWeekday, firstWeekday = firstWeekday,

@ -67,6 +67,7 @@ class HistoryCardPresenter(
override fun onDateLongPress(date: LocalDate) { override fun onDateLongPress(date: LocalDate) {
val timestamp = Timestamp.fromLocalDate(date) val timestamp = Timestamp.fromLocalDate(date)
screen.showFeedback() screen.showFeedback()
if (habit.skipDays.isDaySkipped(timestamp)) return
if (habit.isNumerical) { if (habit.isNumerical) {
showNumberPopup(timestamp) showNumberPopup(timestamp)
} else { } else {
@ -81,6 +82,7 @@ class HistoryCardPresenter(
override fun onDateShortPress(date: LocalDate) { override fun onDateShortPress(date: LocalDate) {
val timestamp = Timestamp.fromLocalDate(date) val timestamp = Timestamp.fromLocalDate(date)
screen.showFeedback() screen.showFeedback()
if (habit.skipDays.isDaySkipped(timestamp)) return
if (habit.isNumerical) { if (habit.isNumerical) {
showNumberPopup(timestamp) showNumberPopup(timestamp)
} else { } else {
@ -161,7 +163,7 @@ 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 entries = habit.computedEntries.getByInterval(oldest, today, habit.skipDays)
val series = if (habit.isNumerical) { val series = if (habit.isNumerical) {
entries.map { entries.map {
when { when {

@ -24,12 +24,14 @@ import org.isoron.uhabits.core.models.Habit
import org.isoron.uhabits.core.models.NumericalHabitType import org.isoron.uhabits.core.models.NumericalHabitType
import org.isoron.uhabits.core.models.PaletteColor import org.isoron.uhabits.core.models.PaletteColor
import org.isoron.uhabits.core.models.Reminder import org.isoron.uhabits.core.models.Reminder
import org.isoron.uhabits.core.models.SkipDays
import org.isoron.uhabits.core.ui.views.Theme import org.isoron.uhabits.core.ui.views.Theme
data class SubtitleCardState( data class SubtitleCardState(
val color: PaletteColor, val color: PaletteColor,
val frequency: Frequency, val frequency: Frequency,
val isNumerical: Boolean, val isNumerical: Boolean,
val skipDays: SkipDays,
val question: String, val question: String,
val reminder: Reminder?, val reminder: Reminder?,
val targetValue: Double = 0.0, val targetValue: Double = 0.0,
@ -47,6 +49,7 @@ class SubtitleCardPresenter {
color = habit.color, color = habit.color,
frequency = habit.frequency, frequency = habit.frequency,
isNumerical = habit.isNumerical, isNumerical = habit.isNumerical,
skipDays = habit.skipDays,
question = habit.question, question = habit.question,
reminder = habit.reminder, reminder = habit.reminder,
targetValue = habit.targetValue, targetValue = habit.targetValue,

@ -21,6 +21,7 @@ package org.isoron.uhabits.core.ui.screens.habits.show.views
import org.isoron.uhabits.core.models.Habit import org.isoron.uhabits.core.models.Habit
import org.isoron.uhabits.core.models.PaletteColor import org.isoron.uhabits.core.models.PaletteColor
import org.isoron.uhabits.core.models.Timestamp
import org.isoron.uhabits.core.models.countSkippedDays import org.isoron.uhabits.core.models.countSkippedDays
import org.isoron.uhabits.core.models.groupedSum import org.isoron.uhabits.core.models.groupedSum
import org.isoron.uhabits.core.ui.views.Theme import org.isoron.uhabits.core.ui.views.Theme
@ -45,54 +46,61 @@ class TargetCardPresenter {
theme: Theme theme: Theme
): TargetCardState { ): TargetCardState {
val today = DateUtils.getTodayWithOffset() val today = DateUtils.getTodayWithOffset()
val (yearBegin, yearEnd) = getYearRange(firstWeekday)
val oldest = habit.computedEntries.getKnown().lastOrNull()?.timestamp ?: today val oldest = habit.computedEntries.getKnown().lastOrNull()?.timestamp ?: today
val entries = habit.computedEntries.getByInterval(oldest, today) val entriesForSkip = habit.computedEntries.getByInterval(yearBegin, yearEnd, habit.skipDays)
val entriesForSum = habit.computedEntries.getByInterval(oldest, today)
val valueToday = entries.groupedSum( val valueToday = entriesForSum.groupedSum(
truncateField = DateUtils.TruncateField.DAY, truncateField = DateUtils.TruncateField.DAY,
isNumerical = habit.isNumerical isNumerical = habit.isNumerical
).firstOrNull()?.value ?: 0 ).firstOrNull()?.value ?: 0
val skippedDayToday = entries.countSkippedDays( val skippedDayToday = entriesForSkip.countSkippedDays(
truncateField = DateUtils.TruncateField.DAY truncateField = DateUtils.TruncateField.DAY,
skipDays = habit.skipDays,
).firstOrNull()?.value ?: 0 ).firstOrNull()?.value ?: 0
val valueThisWeek = entries.groupedSum( val valueThisWeek = entriesForSum.groupedSum(
truncateField = DateUtils.TruncateField.WEEK_NUMBER, truncateField = DateUtils.TruncateField.WEEK_NUMBER,
firstWeekday = firstWeekday, firstWeekday = firstWeekday,
isNumerical = habit.isNumerical isNumerical = habit.isNumerical
).firstOrNull()?.value ?: 0 ).firstOrNull()?.value ?: 0
val skippedDaysThisWeek = entries.countSkippedDays( val skippedDaysThisWeek = entriesForSkip.countSkippedDays(
truncateField = DateUtils.TruncateField.WEEK_NUMBER, truncateField = DateUtils.TruncateField.WEEK_NUMBER,
firstWeekday = firstWeekday firstWeekday = firstWeekday,
skipDays = habit.skipDays,
).firstOrNull()?.value ?: 0 ).firstOrNull()?.value ?: 0
val valueThisMonth = entries.groupedSum( val valueThisMonth = entriesForSum.groupedSum(
truncateField = DateUtils.TruncateField.MONTH, truncateField = DateUtils.TruncateField.MONTH,
isNumerical = habit.isNumerical isNumerical = habit.isNumerical
).firstOrNull()?.value ?: 0 ).firstOrNull()?.value ?: 0
val skippedDaysThisMonth = entries.countSkippedDays( val skippedDaysThisMonth = entriesForSkip.countSkippedDays(
truncateField = DateUtils.TruncateField.MONTH truncateField = DateUtils.TruncateField.MONTH,
skipDays = habit.skipDays,
).firstOrNull()?.value ?: 0 ).firstOrNull()?.value ?: 0
val valueThisQuarter = entries.groupedSum( val valueThisQuarter = entriesForSum.groupedSum(
truncateField = DateUtils.TruncateField.QUARTER, truncateField = DateUtils.TruncateField.QUARTER,
isNumerical = habit.isNumerical isNumerical = habit.isNumerical
).firstOrNull()?.value ?: 0 ).firstOrNull()?.value ?: 0
val skippedDaysThisQuarter = entries.countSkippedDays( val skippedDaysThisQuarter = entriesForSkip.countSkippedDays(
truncateField = DateUtils.TruncateField.QUARTER truncateField = DateUtils.TruncateField.QUARTER,
skipDays = habit.skipDays,
).firstOrNull()?.value ?: 0 ).firstOrNull()?.value ?: 0
val valueThisYear = entries.groupedSum( val valueThisYear = entriesForSum.groupedSum(
truncateField = DateUtils.TruncateField.YEAR, truncateField = DateUtils.TruncateField.YEAR,
isNumerical = habit.isNumerical isNumerical = habit.isNumerical
).firstOrNull()?.value ?: 0 ).firstOrNull()?.value ?: 0
val skippedDaysThisYear = entries.countSkippedDays( val skippedDaysThisYear = entriesForSkip.countSkippedDays(
truncateField = DateUtils.TruncateField.YEAR truncateField = DateUtils.TruncateField.YEAR,
skipDays = habit.skipDays,
).firstOrNull()?.value ?: 0 ).firstOrNull()?.value ?: 0
val cal = DateUtils.getStartOfTodayCalendarWithOffset() val cal = DateUtils.getStartOfTodayCalendarWithOffset()
@ -165,5 +173,16 @@ class TargetCardPresenter {
theme = theme theme = theme
) )
} }
private fun getYearRange(firstWeekday: Int): Pair<Timestamp, Timestamp> {
val today = DateUtils.getTodayWithOffset()
val yearBegin = today.truncate(DateUtils.TruncateField.YEAR, firstWeekday)
val cali = yearBegin.toCalendar()
cali.add(Calendar.YEAR, 1)
var newest = Timestamp(cali)
val thisWeek = today.truncate(DateUtils.TruncateField.WEEK_NUMBER, firstWeekday)
if (thisWeek.daysUntil(newest) < 7) newest = thisWeek.plus(7)
return Pair(yearBegin, newest)
}
} }
} }

@ -5,8 +5,6 @@ create table Habits (
description text, description text,
freq_den integer, freq_den integer,
freq_num integer, freq_num integer,
skip_days integer,
skip_days_list integer,
highlight integer, highlight integer,
name text, name text,
position integer, position integer,

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