Skip days implemented. Scores not correct yet

pull/2012/head
Dharanish 1 year ago
parent 9bfae974eb
commit ec08b602f3

@ -81,6 +81,8 @@ class EditHabitActivity : AppCompatActivity() {
var androidColor = 0 var androidColor = 0
var freqNum = 1 var freqNum = 1
var freqDen = 1 var freqDen = 1
var isSkipDays = false
var listSkipDays: WeekdayList = WeekdayList.NO_DAY
var reminderHour = -1 var reminderHour = -1
var reminderMin = -1 var reminderMin = -1
var reminderDays: WeekdayList = WeekdayList.EVERY_DAY var reminderDays: WeekdayList = WeekdayList.EVERY_DAY
@ -104,6 +106,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
listSkipDays = habit.skipDaysList
targetType = habit.targetType targetType = habit.targetType
habit.reminder?.let { habit.reminder?.let {
reminderHour = it.hour reminderHour = it.hour
@ -125,6 +129,8 @@ class EditHabitActivity : AppCompatActivity() {
color = PaletteColor(state.getInt("paletteColor")) color = PaletteColor(state.getInt("paletteColor"))
freqNum = state.getInt("freqNum") freqNum = state.getInt("freqNum")
freqDen = state.getInt("freqDen") freqDen = state.getInt("freqDen")
isSkipDays = state.getBoolean("isSkipDays", false)
listSkipDays = WeekdayList(state.getInt("listSkipDays", 0))
reminderHour = state.getInt("reminderHour") reminderHour = state.getInt("reminderHour")
reminderMin = state.getInt("reminderMin") reminderMin = state.getInt("reminderMin")
reminderDays = WeekdayList(state.getInt("reminderDays")) reminderDays = WeekdayList(state.getInt("reminderDays"))
@ -241,12 +247,31 @@ class EditHabitActivity : AppCompatActivity() {
dialog.setListener { days: WeekdayList -> dialog.setListener { days: WeekdayList ->
reminderDays = days reminderDays = days
if (reminderDays.isEmpty) reminderDays = WeekdayList.EVERY_DAY if (reminderDays.isEmpty) reminderDays = WeekdayList.EVERY_DAY
if (isSkipDays) reminderDays = WeekdayList(reminderDays.toArray(),listSkipDays.toArray())
populateReminder() populateReminder()
} }
dialog.setSelectedDays(reminderDays) dialog.setSelectedDays(reminderDays)
dialog.dismissCurrentAndShow(supportFragmentManager, "dayPicker") dialog.dismissCurrentAndShow(supportFragmentManager, "dayPicker")
} }
populateSkipDays()
binding.skipDaysPicker.setOnClickListener {
val dialog = WeekdayPickerDialog()
dialog.setListener { days: WeekdayList ->
listSkipDays = days
if (listSkipDays.isEmpty) listSkipDays = WeekdayList.NO_DAY
isSkipDays = (listSkipDays != WeekdayList.NO_DAY)
if (reminderHour >= 0 && isSkipDays) {
reminderDays = WeekdayList(reminderDays.toArray(),listSkipDays.toArray())
populateReminder()
}
populateSkipDays()
}
dialog.setSelectedDays(listSkipDays)
dialog.dismissCurrentAndShow(supportFragmentManager, "dayPicker")
}
binding.buttonSave.setOnClickListener { binding.buttonSave.setOnClickListener {
if (validate()) save() if (validate()) save()
} }
@ -277,6 +302,8 @@ class EditHabitActivity : AppCompatActivity() {
} }
habit.frequency = Frequency(freqNum, freqDen) habit.frequency = Frequency(freqNum, freqDen)
habit.skipDays = isSkipDays
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
@ -330,6 +357,14 @@ class EditHabitActivity : AppCompatActivity() {
} }
} }
private fun populateSkipDays() {
if (isSkipDays) {
binding.skipDaysPicker.text = listSkipDays.toFormattedString(this)
} else {
binding.skipDaysPicker.text = getString(R.string.skip_days_off)
}
}
@SuppressLint("StringFormatMatches") @SuppressLint("StringFormatMatches")
private fun populateFrequency() { private fun populateFrequency() {
binding.booleanFrequencyPicker.text = formatFrequency(freqNum, freqDen, resources) binding.booleanFrequencyPicker.text = formatFrequency(freqNum, freqDen, resources)
@ -372,6 +407,8 @@ class EditHabitActivity : AppCompatActivity() {
putInt("androidColor", androidColor) putInt("androidColor", androidColor)
putInt("freqNum", freqNum) putInt("freqNum", freqNum)
putInt("freqDen", freqDen) putInt("freqDen", freqDen)
putBoolean("isSkipDays", isSkipDays)
putInt("listSkipDays", listSkipDays.toInteger())
putInt("reminderHour", reminderHour) putInt("reminderHour", reminderHour)
putInt("reminderMin", reminderMin) putInt("reminderMin", reminderMin)
putInt("reminderDays", reminderDays.toInteger()) putInt("reminderDays", reminderDays.toInteger())

@ -73,6 +73,7 @@
android:paddingRight="4dp"> android:paddingRight="4dp">
<!-- Title and color --> <!-- Title and color -->
<LinearLayout <LinearLayout
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
@ -249,6 +250,24 @@
<!-- Notes --> <!-- Notes -->
<FrameLayout
style="@style/FormOuterBox"
android:layout_width="match_parent"
android:layout_height="match_parent">
<LinearLayout style="@style/FormInnerBox">
<TextView
style="@style/FormLabel"
android:text="@string/skip_days" />
<TextView
android:id="@+id/skipDaysPicker"
style="@style/FormDropdown"
android:text="@string/skip_days_off" />
</LinearLayout>
</FrameLayout>
<FrameLayout style="@style/FormOuterBox"> <FrameLayout style="@style/FormOuterBox">
<LinearLayout style="@style/FormInnerBox"> <LinearLayout style="@style/FormInnerBox">
<TextView <TextView

@ -54,12 +54,14 @@
<string name="history">History</string> <string name="history">History</string>
<string name="clear">Clear</string> <string name="clear">Clear</string>
<string name="reminder">Reminder</string> <string name="reminder">Reminder</string>
<string name="skip_days">Skip days</string>
<string name="save">Save</string> <string name="save">Save</string>
<string name="streaks">Streaks</string> <string name="streaks">Streaks</string>
<string name="no_habits_found">You have no active habits</string> <string name="no_habits_found">You have no active habits</string>
<string name="no_habits_left_to_do">You\'re all done for today!</string> <string name="no_habits_left_to_do">You\'re all done for today!</string>
<string name="long_press_to_toggle">Press-and-hold to check or uncheck</string> <string name="long_press_to_toggle">Press-and-hold to check or uncheck</string>
<string name="reminder_off">Off</string> <string name="reminder_off">Off</string>
<string name="skip_days_off">Off</string>
<string name="create_habit">Create habit</string> <string name="create_habit">Create habit</string>
<string name="edit_habit">Edit habit</string> <string name="edit_habit">Edit habit</string>
<string name="check">Check</string> <string name="check">Check</string>

@ -1,4 +1,4 @@
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 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 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 )

@ -28,9 +28,9 @@ import org.isoron.platform.io.nextId
class HabitRepository(var db: Database) { class HabitRepository(var db: Database) {
companion object { companion object {
const val SELECT_COLUMNS = "id, name, description, freq_num, freq_den, color, archived, position, unit, target_value, type" const val SELECT_COLUMNS = "id, name, description, freq_num, freq_den, skip_days, skip_days_list, color, archived, position, unit, target_value, type"
const val SELECT_PLACEHOLDERS = "?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?" const val SELECT_PLACEHOLDERS = "?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?"
const val UPDATE_COLUMNS = "id=?, name=?, description=?, freq_num=?, freq_den=?, color=?, archived=?, position=?, unit=?, target_value=?, type=?" const val UPDATE_COLUMNS = "id=?, name=?, description=?, freq_num=?, freq_den=?, skip_days=?, skip_days_list=?, color=?, archived=?, position=?, unit=?, target_value=?, type=?"
} }
private val findAllStatement = db.prepareStatement("select $SELECT_COLUMNS from habits order by position") private val findAllStatement = db.prepareStatement("select $SELECT_COLUMNS from habits order by position")
@ -60,7 +60,7 @@ class HabitRepository(var db: Database) {
fun update(habit: Habit) { fun update(habit: Habit) {
bindHabitToStatement(habit, updateStatement) bindHabitToStatement(habit, updateStatement)
updateStatement.bindInt(11, habit.id) updateStatement.bindInt(13, habit.id)
updateStatement.step() updateStatement.step()
updateStatement.reset() updateStatement.reset()
} }
@ -70,12 +70,14 @@ class HabitRepository(var db: Database) {
name = stmt.getText(1), name = stmt.getText(1),
description = stmt.getText(2), description = stmt.getText(2),
frequency = Frequency(stmt.getInt(3), stmt.getInt(4)), frequency = Frequency(stmt.getInt(3), stmt.getInt(4)),
color = PaletteColor(stmt.getInt(5)), skipDays = (stmt.getInt(5) == 1),
isArchived = stmt.getInt(6) != 0, skipDaysList = WeekDayList(stmt.getInt(6)),
position = stmt.getInt(7), color = PaletteColor(stmt.getInt(7)),
unit = stmt.getText(8), isArchived = stmt.getInt(8) != 0,
target = stmt.getReal(9), position = stmt.getInt(9),
type = if (stmt.getInt(10) == 0) HabitType.BOOLEAN_HABIT else HabitType.NUMERICAL_HABIT) unit = stmt.getText(10),
target = stmt.getReal(11),
type = if (stmt.getInt(12) == 0) HabitType.BOOLEAN_HABIT else HabitType.NUMERICAL_HABIT)
} }
private fun bindHabitToStatement(habit: Habit, statement: PreparedStatement) { private fun bindHabitToStatement(habit: Habit, statement: PreparedStatement) {
@ -84,12 +86,14 @@ class HabitRepository(var db: Database) {
statement.bindText(2, habit.description) statement.bindText(2, habit.description)
statement.bindInt(3, habit.frequency.numerator) statement.bindInt(3, habit.frequency.numerator)
statement.bindInt(4, habit.frequency.denominator) statement.bindInt(4, habit.frequency.denominator)
statement.bindInt(5, habit.color.index) statement.bindInt(5, if (habit.skipDays) 1 else 0)
statement.bindInt(6, if (habit.isArchived) 1 else 0) statement.bindInt(6, habit.skipDaysList.toInteger())
statement.bindInt(7, habit.position) statement.bindInt(7, habit.color.index)
statement.bindText(8, habit.unit) statement.bindInt(8, if (habit.isArchived) 1 else 0)
statement.bindReal(9, habit.target) statement.bindInt(9, habit.position)
statement.bindInt(10, habit.type.code) statement.bindText(10, habit.unit)
statement.bindReal(11, habit.target)
statement.bindInt(12, habit.type.code)
} }
fun delete(habit: Habit) { fun delete(habit: Habit) {

@ -38,11 +38,12 @@ open class EntryList {
/** /**
* Returns the entry corresponding to the given timestamp. If no entry with such timestamp * Returns the entry corresponding to the given timestamp. If no entry with such timestamp
* has been previously added, returns Entry(timestamp, UNKNOWN). * has been previously added, returns Entry(timestamp, UNKNOWN). or Entry(timestamp, SKIP) if
* skip days are enabled and that day is to be skipped
*/ */
@Synchronized @Synchronized
open fun get(timestamp: Timestamp): Entry { open fun get(timestamp: Timestamp, skipDays: Boolean = false, skipDaysList: WeekdayList = WeekdayList.NO_DAY): Entry {
return entriesByTimestamp[timestamp] ?: Entry(timestamp, UNKNOWN) return entriesByTimestamp[timestamp] ?: if (skipDays && skipDaysList.isDayTrue(timestamp.weekday)) Entry(timestamp, SKIP) else Entry(timestamp, UNKNOWN)
} }
/** /**
@ -51,12 +52,12 @@ open class EntryList {
* included. * included.
*/ */
@Synchronized @Synchronized
open fun getByInterval(from: Timestamp, to: Timestamp): List<Entry> { open fun getByInterval(from: Timestamp, to: Timestamp, skipDays: Boolean = false, skipDaysList: WeekdayList = WeekdayList.NO_DAY): 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)) result.add(get(current, skipDays, skipDaysList))
current = current.minus(1) current = current.minus(1)
} }
return result return result

@ -20,7 +20,7 @@ package org.isoron.uhabits.core.models
data class Frequency( data class Frequency(
var numerator: Int, var numerator: Int,
var denominator: Int var denominator: Int,
) { ) {
init { init {
if (numerator == denominator) { if (numerator == denominator) {

@ -25,6 +25,8 @@ 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 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 = "",
@ -90,6 +92,8 @@ data class Habit(
scores.recompute( scores.recompute(
frequency = frequency, frequency = frequency,
isNumerical = isNumerical, isNumerical = isNumerical,
skipDays = skipDays,
skipDaysList = skipDaysList,
numericalHabitType = targetType, numericalHabitType = targetType,
targetValue = targetValue, targetValue = targetValue,
computedEntries = computedEntries, computedEntries = computedEntries,
@ -108,6 +112,8 @@ data class Habit(
this.color = other.color this.color = other.color
this.description = other.description this.description = other.description
this.frequency = other.frequency this.frequency = other.frequency
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
@ -128,6 +134,8 @@ data class Habit(
if (color != other.color) return false if (color != other.color) return false
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 (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
@ -147,6 +155,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 + (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,6 +68,8 @@ class ScoreList {
fun recompute( fun recompute(
frequency: Frequency, frequency: Frequency,
isNumerical: Boolean, isNumerical: Boolean,
skipDays: Boolean,
skipDaysList: WeekdayList,
numericalHabitType: NumericalHabitType, numericalHabitType: NumericalHabitType,
targetValue: Double, targetValue: Double,
computedEntries: EntryList, computedEntries: EntryList,
@ -79,7 +81,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).map { it.value }.toIntArray() val values = computedEntries.getByInterval(from, to, skipDays, skipDaysList).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

@ -38,6 +38,13 @@ class WeekdayList {
this.weekdays = Arrays.copyOf(weekdays, 7) this.weekdays = Arrays.copyOf(weekdays, 7)
} }
constructor(addDays: BooleanArray, removeDays: BooleanArray) {
weekdays = BooleanArray(7)
for (i in 0..6) {
weekdays[i] = addDays[i] && !removeDays[i]
}
}
val isEmpty: Boolean val isEmpty: Boolean
get() { get() {
for (d in weekdays) if (d) return false for (d in weekdays) if (d) return false
@ -58,6 +65,10 @@ class WeekdayList {
return packedList return packedList
} }
fun isDayTrue(dayNum: Int): Boolean {
return weekdays[dayNum]
}
override fun equals(other: Any?): Boolean { override fun equals(other: Any?): Boolean {
if (this === other) return true if (this === other) return true
if (other == null || javaClass != other.javaClass) return false if (other == null || javaClass != other.javaClass) return false
@ -73,5 +84,6 @@ class WeekdayList {
companion object { companion object {
val EVERY_DAY = WeekdayList(127) val EVERY_DAY = WeekdayList(127)
val NO_DAY = WeekdayList(booleanArrayOf(false, false, false, false, false, false, false))
} }
} }

@ -25,6 +25,7 @@ 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.Timestamp import org.isoron.uhabits.core.models.Timestamp
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
class SQLiteEntryList(database: Database) : EntryList() { class SQLiteEntryList(database: Database) : EntryList() {
@ -43,14 +44,13 @@ class SQLiteEntryList(database: Database) : EntryList() {
isLoaded = true isLoaded = true
} }
override fun get(timestamp: Timestamp): Entry { override fun get(timestamp: Timestamp, skipDays: Boolean, skipDaysList: WeekdayList): Entry {
loadRecords() loadRecords()
return super.get(timestamp) return super.get(timestamp, skipDays, skipDaysList)
} }
override fun getByInterval(from: Timestamp, to: Timestamp, skipDays: Boolean, skipDaysList: WeekdayList): List<Entry> {
override fun getByInterval(from: Timestamp, to: Timestamp): List<Entry> {
loadRecords() loadRecords()
return super.getByInterval(from, to) return super.getByInterval(from, to, skipDays, skipDaysList)
} }
override fun add(entry: Entry) { override fun add(entry: Entry) {

@ -49,6 +49,12 @@ class HabitRecord {
@field:Column(name = "freq_den") @field:Column(name = "freq_den")
var freqDen: Int? = null var freqDen: Int? = null
@field:Column(name = "skip_days")
var skipDays: Int? = null
@field:Column(name = "skip_days_list")
var skipDaysList: Int? = null
@field:Column @field:Column
var color: Int? = null var color: Int? = null
@ -105,6 +111,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
skipDaysList = model.skipDaysList.toInteger()
reminderDays = 0 reminderDays = 0
reminderMin = null reminderMin = null
reminderHour = null reminderHour = null
@ -122,6 +130,8 @@ 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.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!!)

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

@ -56,7 +56,8 @@ open class ListHabitsBehavior @Inject constructor(
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 = (newValue * 1000).roundToInt() val value = if (habit.skipDays && habit.skipDaysList.isDayTrue(timestamp.weekday)) 3 else (newValue * 1000).roundToInt()
if (newValue != oldValue) { if (newValue != oldValue) {
if ( if (
(habit.targetType == AT_LEAST && newValue >= habit.targetValue) || (habit.targetType == AT_LEAST && newValue >= habit.targetValue) ||

@ -5,6 +5,8 @@ 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,

Loading…
Cancel
Save