From f5da00a989b1b0b6444be8477f521c25a40bc6c7 Mon Sep 17 00:00:00 2001 From: Dharanish Date: Mon, 20 May 2024 12:56:56 +0200 Subject: [PATCH] All scores, frequency, history, etc. corrected for the skip days features. --- .../habits/edit/EditHabitActivity.kt | 14 ++-- .../habits/show/views/SubtitleCardView.kt | 7 ++ .../main/res/layout/activity_edit_habit.xml | 1 + .../main/res/layout/show_habit_subtitle.xml | 12 ++++ .../assets/main/migrations/009.sql | 2 +- .../assets/main/migrations/024.sql | 2 + .../isoron/uhabits/core/models/EntryList.kt | 66 ++++++++++++++----- .../org/isoron/uhabits/core/models/Habit.kt | 11 ++-- .../isoron/uhabits/core/models/ScoreList.kt | 5 +- .../isoron/uhabits/core/models/SkipDays.kt | 41 ++++++++++++ .../core/models/sqlite/SQLiteEntryList.kt | 11 ++-- .../core/models/sqlite/records/HabitRecord.kt | 8 +-- .../screens/habits/list/HabitCardListCache.kt | 3 +- .../screens/habits/list/ListHabitsBehavior.kt | 4 +- .../habits/show/views/FrequencyCard.kt | 2 +- .../screens/habits/show/views/HistoryCard.kt | 4 +- .../screens/habits/show/views/SubtitleCard.kt | 3 + .../screens/habits/show/views/TargetCard.kt | 51 +++++++++----- .../src/jvmMain/resources/migrations/09.sql | 2 - .../src/jvmMain/resources/migrations/26.sql | 2 + 20 files changed, 186 insertions(+), 65 deletions(-) create mode 100644 uhabits-core-legacy/assets/main/migrations/024.sql create mode 100644 uhabits-core/src/jvmMain/java/org/isoron/uhabits/core/models/SkipDays.kt create mode 100644 uhabits-core/src/jvmMain/resources/migrations/26.sql 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 ab2371afc..7aaaae1da 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 @@ -51,6 +51,7 @@ import org.isoron.uhabits.core.models.HabitType import org.isoron.uhabits.core.models.NumericalHabitType import org.isoron.uhabits.core.models.PaletteColor import org.isoron.uhabits.core.models.Reminder +import org.isoron.uhabits.core.models.SkipDays import org.isoron.uhabits.core.models.WeekdayList import org.isoron.uhabits.databinding.ActivityEditHabitBinding import org.isoron.uhabits.utils.ColorUtils @@ -106,8 +107,8 @@ class EditHabitActivity : AppCompatActivity() { color = habit.color freqNum = habit.frequency.numerator freqDen = habit.frequency.denominator - isSkipDays = habit.skipDays - listSkipDays = habit.skipDaysList + isSkipDays = habit.skipDays.isSkipDays + listSkipDays = habit.skipDays.days targetType = habit.targetType habit.reminder?.let { reminderHour = it.hour @@ -302,8 +303,7 @@ class EditHabitActivity : AppCompatActivity() { } habit.frequency = Frequency(freqNum, freqDen) - habit.skipDays = isSkipDays - habit.skipDaysList = listSkipDays + habit.skipDays = SkipDays(isSkipDays, listSkipDays) if (habitType == HabitType.NUMERICAL) { habit.targetValue = binding.targetInput.text.toString().toDouble() habit.targetType = targetType @@ -358,6 +358,12 @@ class EditHabitActivity : AppCompatActivity() { } 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) { binding.skipDaysPicker.text = listSkipDays.toFormattedString(this) } else { diff --git a/uhabits-android/src/main/java/org/isoron/uhabits/activities/habits/show/views/SubtitleCardView.kt b/uhabits-android/src/main/java/org/isoron/uhabits/activities/habits/show/views/SubtitleCardView.kt index c29894c18..5e49755be 100644 --- a/uhabits-android/src/main/java/org/isoron/uhabits/activities/habits/show/views/SubtitleCardView.kt +++ b/uhabits-android/src/main/java/org/isoron/uhabits/activities/habits/show/views/SubtitleCardView.kt @@ -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.utils.InterfaceUtils import org.isoron.uhabits.utils.formatTime +import org.isoron.uhabits.utils.toFormattedString 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()) { 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() } 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 06ff19f35..b001f8105 100644 --- a/uhabits-android/src/main/res/layout/activity_edit_habit.xml +++ b/uhabits-android/src/main/res/layout/activity_edit_habit.xml @@ -251,6 +251,7 @@ diff --git a/uhabits-android/src/main/res/layout/show_habit_subtitle.xml b/uhabits-android/src/main/res/layout/show_habit_subtitle.xml index 7361b4565..fad407f4a 100644 --- a/uhabits-android/src/main/res/layout/show_habit_subtitle.xml +++ b/uhabits-android/src/main/res/layout/show_habit_subtitle.xml @@ -76,6 +76,17 @@ android:text="@string/every_day" android:textColor="?attr/contrast60" android:layout_marginStart="4dp" + android:layout_marginEnd="8dp" + android:textSize="@dimen/smallTextSize" /> + + @@ -95,6 +106,7 @@ android:textColor="?attr/contrast60" android:text="8:00 AM" android:layout_marginStart="4dp" + android:layout_marginEnd="16dp" android:textSize="@dimen/smallTextSize" /> diff --git a/uhabits-core-legacy/assets/main/migrations/009.sql b/uhabits-core-legacy/assets/main/migrations/009.sql index 8a37d40fd..1fae1a722 100644 --- a/uhabits-core-legacy/assets/main/migrations/009.sql +++ b/uhabits-core-legacy/assets/main/migrations/009.sql @@ -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 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 ) diff --git a/uhabits-core-legacy/assets/main/migrations/024.sql b/uhabits-core-legacy/assets/main/migrations/024.sql new file mode 100644 index 000000000..3899636ea --- /dev/null +++ b/uhabits-core-legacy/assets/main/migrations/024.sql @@ -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 \ No newline at end of file diff --git a/uhabits-core/src/jvmMain/java/org/isoron/uhabits/core/models/EntryList.kt b/uhabits-core/src/jvmMain/java/org/isoron/uhabits/core/models/EntryList.kt index f04207be2..fa5fe7e9f 100644 --- a/uhabits-core/src/jvmMain/java/org/isoron/uhabits/core/models/EntryList.kt +++ b/uhabits-core/src/jvmMain/java/org/isoron/uhabits/core/models/EntryList.kt @@ -42,8 +42,8 @@ open class EntryList { * skip days are enabled and that day is to be skipped */ @Synchronized - open fun get(timestamp: Timestamp, skipDays: Boolean = false, skipDaysList: WeekdayList = WeekdayList.NO_DAY): Entry { - return entriesByTimestamp[timestamp] ?: if (skipDays && skipDaysList.isDayTrue(timestamp.weekday)) Entry(timestamp, SKIP) else Entry(timestamp, UNKNOWN) + open fun get(timestamp: Timestamp, skipDays: SkipDays = SkipDays.NONE): Entry { + return if (skipDays.isDaySkipped(timestamp)) Entry(timestamp, SKIP) else entriesByTimestamp[timestamp] ?: Entry(timestamp, UNKNOWN) } /** @@ -52,12 +52,12 @@ open class EntryList { * included. */ @Synchronized - open fun getByInterval(from: Timestamp, to: Timestamp, skipDays: Boolean = false, skipDaysList: WeekdayList = WeekdayList.NO_DAY): List { + open fun getByInterval(from: Timestamp, to: Timestamp, skipDays: SkipDays = SkipDays.NONE): List { val result = mutableListOf() if (from.isNewerThan(to)) return result var current = to while (current >= from) { - result.add(get(current, skipDays, skipDaysList)) + result.add(get(current, skipDays)) current = current.minus(1) } return result @@ -91,16 +91,18 @@ open class EntryList { open fun recomputeFrom( originalEntries: EntryList, frequency: Frequency, - isNumerical: Boolean + isNumerical: Boolean, + skipDays: SkipDays ) { clear() val original = originalEntries.getKnown() if (isNumerical) { - original.forEach { add(it) } + val computed = addEntriesWithSkipDays(original, skipDays) + computed.forEach { add(it) } } else { val intervals = buildIntervals(frequency, original) snapIntervalsTogether(intervals) - val computed = buildEntriesFromInterval(original, intervals) + val computed = buildEntriesFromInterval(original, intervals, skipDays) computed.filter { it.value != UNKNOWN || it.notes.isNotEmpty() }.forEach { add(it) } } } @@ -129,10 +131,10 @@ open class EntryList { fun computeWeekdayFrequency(isNumerical: Boolean): HashMap> { val entries = getKnown() val map = hashMapOf>() - for ((originalTimestamp, value) in entries) { - val weekday = originalTimestamp.weekday + for ((computedTimestamp, value) in entries) { + val weekday = computedTimestamp.weekday val truncatedTimestamp = Timestamp( - originalTimestamp.toCalendar().apply { + computedTimestamp.toCalendar().apply { set(Calendar.DAY_OF_MONTH, 1) }.timeInMillis ) @@ -143,7 +145,7 @@ open class EntryList { map[truncatedTimestamp] = list } - if (isNumerical) { + if (isNumerical && value != SKIP) { list[weekday] += value } else if (value == YES_MANUAL) { list[weekday] += 1 @@ -168,7 +170,8 @@ open class EntryList { */ fun buildEntriesFromInterval( original: List, - intervals: List + intervals: List, + skipDays: SkipDays ): List { val result = arrayListOf() if (original.isEmpty()) return result @@ -197,15 +200,17 @@ open class EntryList { current = interval.end while (current >= interval.begin) { 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) } } - // Copy original entries + // Copy original entries except for the skipped days original.forEach { entry -> val offset = entry.timestamp.daysUntil(to) - val value = if ( + val value = if (skipDays.isDaySkipped(entry)) { + SKIP + } else if ( result[offset].value == UNKNOWN || entry.value == SKIP || entry.value == YES_MANUAL @@ -272,6 +277,29 @@ open class EntryList { } return intervals } + + fun addEntriesWithSkipDays(original: List, skipDays: SkipDays): List { + if (original.isEmpty()) return original + val earliest = original.last().timestamp + val today = DateUtils.getTodayWithOffset() + val computed = mutableListOf() + 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.groupedSum( */ fun List.countSkippedDays( truncateField: DateUtils.TruncateField, - firstWeekday: Int = Calendar.SATURDAY + firstWeekday: Int = Calendar.SATURDAY, + skipDays: SkipDays ): List { + val thisIntervalStart = DateUtils.getTodayWithOffset().truncate(truncateField, firstWeekday) return this.map { (timestamp, value) -> - if (value == SKIP) { + if (value == SKIP || skipDays.isDaySkipped(timestamp)) { Entry(timestamp, 1) } else { Entry(timestamp, 0) @@ -341,5 +371,5 @@ fun List.countSkippedDays( Entry(timestamp, entries.sumOf { it.value }) }.sortedBy { (timestamp, _) -> -timestamp.unixTime - } + }.filter { it.timestamp == thisIntervalStart } } 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 0f3a7272b..5a87695b7 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 @@ -25,8 +25,7 @@ data class Habit( var color: PaletteColor = PaletteColor(8), var description: String = "", var frequency: Frequency = Frequency.DAILY, - var skipDays: Boolean = false, - var skipDaysList: WeekdayList = WeekdayList.NO_DAY, + var skipDays: SkipDays = SkipDays.NONE, var id: Long? = null, var isArchived: Boolean = false, var name: String = "", @@ -80,7 +79,8 @@ data class Habit( computedEntries.recomputeFrom( originalEntries = originalEntries, frequency = frequency, - isNumerical = isNumerical + isNumerical = isNumerical, + skipDays = skipDays ) val today = DateUtils.getTodayWithOffset() @@ -93,7 +93,6 @@ data class Habit( frequency = frequency, isNumerical = isNumerical, skipDays = skipDays, - skipDaysList = skipDaysList, numericalHabitType = targetType, targetValue = targetValue, computedEntries = computedEntries, @@ -113,7 +112,6 @@ data class Habit( this.description = other.description this.frequency = other.frequency this.skipDays = other.skipDays - this.skipDaysList = other.skipDaysList // this.id should not be copied this.isArchived = other.isArchived this.name = other.name @@ -135,7 +133,6 @@ data class Habit( if (description != other.description) return false if (frequency != other.frequency) return false if (skipDays != other.skipDays) return false - if (skipDaysList != other.skipDaysList) return false if (id != other.id) return false if (isArchived != other.isArchived) return false if (name != other.name) return false @@ -155,7 +152,7 @@ data class Habit( var result = color.hashCode() result = 31 * result + description.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 + isArchived.hashCode() result = 31 * result + name.hashCode() 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 94c093da9..84945ad97 100644 --- a/uhabits-core/src/jvmMain/java/org/isoron/uhabits/core/models/ScoreList.kt +++ b/uhabits-core/src/jvmMain/java/org/isoron/uhabits/core/models/ScoreList.kt @@ -68,8 +68,7 @@ class ScoreList { fun recompute( frequency: Frequency, isNumerical: Boolean, - skipDays: Boolean, - skipDaysList: WeekdayList, + skipDays: SkipDays, numericalHabitType: NumericalHabitType, targetValue: Double, computedEntries: EntryList, @@ -81,7 +80,7 @@ class ScoreList { var numerator = frequency.numerator var denominator = frequency.denominator 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 // For non-daily boolean habits, we double the numerator and the denominator to smooth diff --git a/uhabits-core/src/jvmMain/java/org/isoron/uhabits/core/models/SkipDays.kt b/uhabits-core/src/jvmMain/java/org/isoron/uhabits/core/models/SkipDays.kt new file mode 100644 index 000000000..6815c700e --- /dev/null +++ b/uhabits-core/src/jvmMain/java/org/isoron/uhabits/core/models/SkipDays.kt @@ -0,0 +1,41 @@ +/* + * Copyright (C) 2016-2021 Álinson Santos Xavier + * + * 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 . + */ +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)) + } +} \ No newline at end of file diff --git a/uhabits-core/src/jvmMain/java/org/isoron/uhabits/core/models/sqlite/SQLiteEntryList.kt b/uhabits-core/src/jvmMain/java/org/isoron/uhabits/core/models/sqlite/SQLiteEntryList.kt index e3186fad7..895ff8f0d 100644 --- a/uhabits-core/src/jvmMain/java/org/isoron/uhabits/core/models/sqlite/SQLiteEntryList.kt +++ b/uhabits-core/src/jvmMain/java/org/isoron/uhabits/core/models/sqlite/SQLiteEntryList.kt @@ -24,6 +24,7 @@ import org.isoron.uhabits.core.database.Repository import org.isoron.uhabits.core.models.Entry import org.isoron.uhabits.core.models.EntryList 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.WeekdayList import org.isoron.uhabits.core.models.sqlite.records.EntryRecord @@ -44,13 +45,13 @@ class SQLiteEntryList(database: Database) : EntryList() { isLoaded = true } - override fun get(timestamp: Timestamp, skipDays: Boolean, skipDaysList: WeekdayList): Entry { + override fun get(timestamp: Timestamp, skipDays: SkipDays): Entry { loadRecords() - return super.get(timestamp, skipDays, skipDaysList) + return super.get(timestamp, skipDays) } - override fun getByInterval(from: Timestamp, to: Timestamp, skipDays: Boolean, skipDaysList: WeekdayList): List { + override fun getByInterval(from: Timestamp, to: Timestamp, skipDays: SkipDays): List { loadRecords() - return super.getByInterval(from, to, skipDays, skipDaysList) + return super.getByInterval(from, to, skipDays) } override fun add(entry: Entry) { @@ -78,7 +79,7 @@ class SQLiteEntryList(database: Database) : EntryList() { 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() } 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 c30c94bd2..e4324a214 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 @@ -26,6 +26,7 @@ import org.isoron.uhabits.core.models.HabitType import org.isoron.uhabits.core.models.NumericalHabitType import org.isoron.uhabits.core.models.PaletteColor import org.isoron.uhabits.core.models.Reminder +import org.isoron.uhabits.core.models.SkipDays import org.isoron.uhabits.core.models.WeekdayList import java.util.Objects.requireNonNull @@ -111,8 +112,8 @@ class HabitRecord { val (numerator, denominator) = model.frequency freqNum = numerator freqDen = denominator - skipDays = if (model.skipDays) 1 else 0 - skipDaysList = model.skipDaysList.toInteger() + skipDays = if (model.skipDays.isSkipDays) 1 else 0 + skipDaysList = model.skipDays.days.toInteger() reminderDays = 0 reminderMin = null reminderHour = null @@ -130,8 +131,7 @@ class HabitRecord { habit.description = description!! habit.question = question!! habit.frequency = Frequency(freqNum!!, freqDen!!) - habit.skipDays = (skipDays!! == 1) - habit.skipDaysList = WeekdayList(skipDaysList!!) + habit.skipDays = SkipDays(skipDays!! == 1, WeekdayList(skipDaysList!!)) habit.color = PaletteColor(color!!) habit.isArchived = archived != 0 habit.type = HabitType.fromInt(type!!) diff --git a/uhabits-core/src/jvmMain/java/org/isoron/uhabits/core/ui/screens/habits/list/HabitCardListCache.kt b/uhabits-core/src/jvmMain/java/org/isoron/uhabits/core/ui/screens/habits/list/HabitCardListCache.kt index cbf64201d..6615dcf60 100644 --- a/uhabits-core/src/jvmMain/java/org/isoron/uhabits/core/ui/screens/habits/list/HabitCardListCache.kt +++ b/uhabits-core/src/jvmMain/java/org/isoron/uhabits/core/ui/screens/habits/list/HabitCardListCache.kt @@ -312,8 +312,7 @@ class HabitCardListCache @Inject constructor( val list: MutableList = ArrayList() val notes: MutableList = ArrayList() val skipDays = habit.skipDays - val skipDaysList = habit.skipDaysList - for ((_, value, note) in habit.computedEntries.getByInterval(dateFrom, today, skipDays, skipDaysList)) { + for ((_, value, note) in habit.computedEntries.getByInterval(dateFrom, today, skipDays)) { list.add(value) notes.add(note) } 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 d9682ab04..703a8cf0a 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.Entry.Companion.YES_MANUAL import org.isoron.uhabits.core.models.Habit import org.isoron.uhabits.core.models.HabitList @@ -53,10 +54,11 @@ open class ListHabitsBehavior @Inject constructor( fun onEdit(habit: Habit, timestamp: Timestamp?) { val entry = habit.computedEntries.get(timestamp!!) + if (habit.skipDays.isDaySkipped(timestamp)) return if (habit.type == HabitType.NUMERICAL) { val oldValue = entry.value.toDouble() / 1000 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 ( diff --git a/uhabits-core/src/jvmMain/java/org/isoron/uhabits/core/ui/screens/habits/show/views/FrequencyCard.kt b/uhabits-core/src/jvmMain/java/org/isoron/uhabits/core/ui/screens/habits/show/views/FrequencyCard.kt index 0246daf7d..8a233109e 100644 --- a/uhabits-core/src/jvmMain/java/org/isoron/uhabits/core/ui/screens/habits/show/views/FrequencyCard.kt +++ b/uhabits-core/src/jvmMain/java/org/isoron/uhabits/core/ui/screens/habits/show/views/FrequencyCard.kt @@ -42,7 +42,7 @@ class FrequencyCardPresenter { ) = FrequencyCardState( color = habit.color, isNumerical = habit.isNumerical, - frequency = habit.originalEntries.computeWeekdayFrequency( + frequency = habit.computedEntries.computeWeekdayFrequency( isNumerical = habit.isNumerical ), firstWeekday = firstWeekday, 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 0a28c801f..73f7be416 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 @@ -67,6 +67,7 @@ class HistoryCardPresenter( override fun onDateLongPress(date: LocalDate) { val timestamp = Timestamp.fromLocalDate(date) screen.showFeedback() + if (habit.skipDays.isDaySkipped(timestamp)) return if (habit.isNumerical) { showNumberPopup(timestamp) } else { @@ -81,6 +82,7 @@ class HistoryCardPresenter( override fun onDateShortPress(date: LocalDate) { val timestamp = Timestamp.fromLocalDate(date) screen.showFeedback() + if (habit.skipDays.isDaySkipped(timestamp)) return if (habit.isNumerical) { showNumberPopup(timestamp) } else { @@ -161,7 +163,7 @@ class HistoryCardPresenter( ): HistoryCardState { val today = DateUtils.getTodayWithOffset() 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) { entries.map { when { diff --git a/uhabits-core/src/jvmMain/java/org/isoron/uhabits/core/ui/screens/habits/show/views/SubtitleCard.kt b/uhabits-core/src/jvmMain/java/org/isoron/uhabits/core/ui/screens/habits/show/views/SubtitleCard.kt index fb839933c..01784e2f2 100644 --- a/uhabits-core/src/jvmMain/java/org/isoron/uhabits/core/ui/screens/habits/show/views/SubtitleCard.kt +++ b/uhabits-core/src/jvmMain/java/org/isoron/uhabits/core/ui/screens/habits/show/views/SubtitleCard.kt @@ -24,12 +24,14 @@ import org.isoron.uhabits.core.models.Habit import org.isoron.uhabits.core.models.NumericalHabitType import org.isoron.uhabits.core.models.PaletteColor import org.isoron.uhabits.core.models.Reminder +import org.isoron.uhabits.core.models.SkipDays import org.isoron.uhabits.core.ui.views.Theme data class SubtitleCardState( val color: PaletteColor, val frequency: Frequency, val isNumerical: Boolean, + val skipDays: SkipDays, val question: String, val reminder: Reminder?, val targetValue: Double = 0.0, @@ -47,6 +49,7 @@ class SubtitleCardPresenter { color = habit.color, frequency = habit.frequency, isNumerical = habit.isNumerical, + skipDays = habit.skipDays, question = habit.question, reminder = habit.reminder, targetValue = habit.targetValue, diff --git a/uhabits-core/src/jvmMain/java/org/isoron/uhabits/core/ui/screens/habits/show/views/TargetCard.kt b/uhabits-core/src/jvmMain/java/org/isoron/uhabits/core/ui/screens/habits/show/views/TargetCard.kt index 49c95011d..3ff074424 100644 --- a/uhabits-core/src/jvmMain/java/org/isoron/uhabits/core/ui/screens/habits/show/views/TargetCard.kt +++ b/uhabits-core/src/jvmMain/java/org/isoron/uhabits/core/ui/screens/habits/show/views/TargetCard.kt @@ -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.PaletteColor +import org.isoron.uhabits.core.models.Timestamp import org.isoron.uhabits.core.models.countSkippedDays import org.isoron.uhabits.core.models.groupedSum import org.isoron.uhabits.core.ui.views.Theme @@ -45,54 +46,61 @@ class TargetCardPresenter { theme: Theme ): TargetCardState { val today = DateUtils.getTodayWithOffset() + val (yearBegin, yearEnd) = getYearRange(firstWeekday) 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, isNumerical = habit.isNumerical ).firstOrNull()?.value ?: 0 - val skippedDayToday = entries.countSkippedDays( - truncateField = DateUtils.TruncateField.DAY + val skippedDayToday = entriesForSkip.countSkippedDays( + truncateField = DateUtils.TruncateField.DAY, + skipDays = habit.skipDays, ).firstOrNull()?.value ?: 0 - val valueThisWeek = entries.groupedSum( + val valueThisWeek = entriesForSum.groupedSum( truncateField = DateUtils.TruncateField.WEEK_NUMBER, firstWeekday = firstWeekday, isNumerical = habit.isNumerical ).firstOrNull()?.value ?: 0 - val skippedDaysThisWeek = entries.countSkippedDays( + val skippedDaysThisWeek = entriesForSkip.countSkippedDays( truncateField = DateUtils.TruncateField.WEEK_NUMBER, - firstWeekday = firstWeekday + firstWeekday = firstWeekday, + skipDays = habit.skipDays, ).firstOrNull()?.value ?: 0 - val valueThisMonth = entries.groupedSum( + val valueThisMonth = entriesForSum.groupedSum( truncateField = DateUtils.TruncateField.MONTH, isNumerical = habit.isNumerical ).firstOrNull()?.value ?: 0 - val skippedDaysThisMonth = entries.countSkippedDays( - truncateField = DateUtils.TruncateField.MONTH + val skippedDaysThisMonth = entriesForSkip.countSkippedDays( + truncateField = DateUtils.TruncateField.MONTH, + skipDays = habit.skipDays, ).firstOrNull()?.value ?: 0 - val valueThisQuarter = entries.groupedSum( + val valueThisQuarter = entriesForSum.groupedSum( truncateField = DateUtils.TruncateField.QUARTER, isNumerical = habit.isNumerical ).firstOrNull()?.value ?: 0 - val skippedDaysThisQuarter = entries.countSkippedDays( - truncateField = DateUtils.TruncateField.QUARTER + val skippedDaysThisQuarter = entriesForSkip.countSkippedDays( + truncateField = DateUtils.TruncateField.QUARTER, + skipDays = habit.skipDays, ).firstOrNull()?.value ?: 0 - val valueThisYear = entries.groupedSum( + val valueThisYear = entriesForSum.groupedSum( truncateField = DateUtils.TruncateField.YEAR, isNumerical = habit.isNumerical ).firstOrNull()?.value ?: 0 - val skippedDaysThisYear = entries.countSkippedDays( - truncateField = DateUtils.TruncateField.YEAR + val skippedDaysThisYear = entriesForSkip.countSkippedDays( + truncateField = DateUtils.TruncateField.YEAR, + skipDays = habit.skipDays, ).firstOrNull()?.value ?: 0 val cal = DateUtils.getStartOfTodayCalendarWithOffset() @@ -165,5 +173,16 @@ class TargetCardPresenter { theme = theme ) } + + private fun getYearRange(firstWeekday: Int): Pair { + 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) + } } } diff --git a/uhabits-core/src/jvmMain/resources/migrations/09.sql b/uhabits-core/src/jvmMain/resources/migrations/09.sql index d8dff31f8..5fb4502b4 100644 --- a/uhabits-core/src/jvmMain/resources/migrations/09.sql +++ b/uhabits-core/src/jvmMain/resources/migrations/09.sql @@ -5,8 +5,6 @@ create table Habits ( description text, freq_den integer, freq_num integer, - skip_days integer, - skip_days_list integer, highlight integer, name text, position integer, diff --git a/uhabits-core/src/jvmMain/resources/migrations/26.sql b/uhabits-core/src/jvmMain/resources/migrations/26.sql new file mode 100644 index 000000000..e8dc1d4a3 --- /dev/null +++ b/uhabits-core/src/jvmMain/resources/migrations/26.sql @@ -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; \ No newline at end of file