mirror of
https://github.com/iSoron/uhabits.git
synced 2025-12-06 09:08:52 -06:00
Added a notes field and implemented dialog for numeric habits
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -28,10 +28,11 @@ data class CreateRepetitionCommand(
|
||||
val habit: Habit,
|
||||
val timestamp: Timestamp,
|
||||
val value: Int,
|
||||
val notes: String,
|
||||
) : Command {
|
||||
override fun run() {
|
||||
val entries = habit.originalEntries
|
||||
entries.add(Entry(timestamp, value))
|
||||
entries.add(Entry(timestamp, value, notes))
|
||||
habit.recompute()
|
||||
habitList.resort()
|
||||
}
|
||||
|
||||
@@ -77,7 +77,8 @@ class HabitBullCSVImporter
|
||||
logger.info("Creating habit: $name")
|
||||
}
|
||||
if (parseInt(cols[4]) == 1) {
|
||||
h.originalEntries.add(Entry(timestamp, Entry.YES_MANUAL))
|
||||
val notes = cols[5] ?: ""
|
||||
h.originalEntries.add(Entry(timestamp, Entry.YES_MANUAL, notes))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -101,8 +101,8 @@ class LoopDBImporter
|
||||
|
||||
for (r in entryRecords) {
|
||||
val t = Timestamp(r.timestamp!!)
|
||||
val (_, value) = habit!!.originalEntries.get(t)
|
||||
if (value != r.value) CreateRepetitionCommand(habitList, habit, t, r.value!!).run()
|
||||
val (_, value, notes) = habit!!.originalEntries.get(t)
|
||||
if (value != r.value || notes != r.notes) CreateRepetitionCommand(habitList, habit, t, r.value!!, r.notes!!).run()
|
||||
}
|
||||
|
||||
runner.notifyListeners(command)
|
||||
|
||||
@@ -21,6 +21,7 @@ package org.isoron.uhabits.core.models
|
||||
data class Entry(
|
||||
val timestamp: Timestamp,
|
||||
val value: Int,
|
||||
val notes: String = "",
|
||||
) {
|
||||
companion object {
|
||||
/**
|
||||
|
||||
@@ -41,12 +41,16 @@ class EntryRecord {
|
||||
|
||||
@field:Column
|
||||
var id: Long? = null
|
||||
|
||||
@field:Column
|
||||
var notes: String? = null
|
||||
fun copyFrom(entry: Entry) {
|
||||
timestamp = entry.timestamp.unixTime
|
||||
value = entry.value
|
||||
notes = entry.notes
|
||||
}
|
||||
|
||||
fun toEntry(): Entry {
|
||||
return Entry(Timestamp(timestamp!!), value!!)
|
||||
return Entry(Timestamp(timestamp!!), value!!, notes!!)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -78,6 +78,11 @@ class HabitCardListCache @Inject constructor(
|
||||
return data.checkmarks[habitId]!!
|
||||
}
|
||||
|
||||
@Synchronized
|
||||
fun getNoteIndicators(habitId: Long): BooleanArray {
|
||||
return data.notesIndicators[habitId]!!
|
||||
}
|
||||
|
||||
@Synchronized
|
||||
fun hasNoHabit(): Boolean {
|
||||
return allHabits.isEmpty
|
||||
@@ -163,6 +168,7 @@ class HabitCardListCache @Inject constructor(
|
||||
data.habits.removeAt(position)
|
||||
data.idToHabit.remove(id)
|
||||
data.checkmarks.remove(id)
|
||||
data.notesIndicators.remove(id)
|
||||
data.scores.remove(id)
|
||||
listener.onItemRemoved(position)
|
||||
}
|
||||
@@ -207,6 +213,7 @@ class HabitCardListCache @Inject constructor(
|
||||
val habits: MutableList<Habit>
|
||||
val checkmarks: HashMap<Long?, IntArray>
|
||||
val scores: HashMap<Long?, Double>
|
||||
val notesIndicators: HashMap<Long?, BooleanArray>
|
||||
|
||||
@Synchronized
|
||||
fun copyCheckmarksFrom(oldData: CacheData) {
|
||||
@@ -217,6 +224,15 @@ class HabitCardListCache @Inject constructor(
|
||||
}
|
||||
}
|
||||
|
||||
@Synchronized
|
||||
fun copyNoteIndicatorsFrom(oldData: CacheData) {
|
||||
val empty = BooleanArray(checkmarkCount)
|
||||
for (id in idToHabit.keys) {
|
||||
if (oldData.notesIndicators.containsKey(id)) notesIndicators[id] =
|
||||
oldData.notesIndicators[id]!! else notesIndicators[id] = empty
|
||||
}
|
||||
}
|
||||
|
||||
@Synchronized
|
||||
fun copyScoresFrom(oldData: CacheData) {
|
||||
for (id in idToHabit.keys) {
|
||||
@@ -241,6 +257,7 @@ class HabitCardListCache @Inject constructor(
|
||||
habits = LinkedList()
|
||||
checkmarks = HashMap()
|
||||
scores = HashMap()
|
||||
notesIndicators = HashMap()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -271,6 +288,7 @@ class HabitCardListCache @Inject constructor(
|
||||
newData.fetchHabits()
|
||||
newData.copyScoresFrom(data)
|
||||
newData.copyCheckmarksFrom(data)
|
||||
newData.copyNoteIndicatorsFrom(data)
|
||||
val today = getTodayWithOffset()
|
||||
val dateFrom = today.minus(checkmarkCount - 1)
|
||||
if (runner != null) runner!!.publishProgress(this, -1)
|
||||
@@ -280,10 +298,14 @@ class HabitCardListCache @Inject constructor(
|
||||
if (targetId != null && targetId != habit.id) continue
|
||||
newData.scores[habit.id] = habit.scores[today].value
|
||||
val list: MutableList<Int> = ArrayList()
|
||||
for ((_, value) in habit.computedEntries.getByInterval(dateFrom, today))
|
||||
val notesList: MutableList<Boolean> = ArrayList()
|
||||
for ((_, value, note) in habit.computedEntries.getByInterval(dateFrom, today)) {
|
||||
list.add(value)
|
||||
if (note.isNotEmpty()) notesList.add(true) else notesList.add(false)
|
||||
}
|
||||
val entries = list.toTypedArray()
|
||||
newData.checkmarks[habit.id] = ArrayUtils.toPrimitive(entries)
|
||||
newData.notesIndicators[habit.id] = notesList.toBooleanArray()
|
||||
runner!!.publishProgress(this, position)
|
||||
}
|
||||
}
|
||||
@@ -311,6 +333,7 @@ class HabitCardListCache @Inject constructor(
|
||||
data.idToHabit[id] = habit
|
||||
data.scores[id] = newData.scores[id]!!
|
||||
data.checkmarks[id] = newData.checkmarks[id]!!
|
||||
data.notesIndicators[id] = newData.notesIndicators[id]!!
|
||||
listener.onItemInserted(position)
|
||||
}
|
||||
|
||||
@@ -338,14 +361,18 @@ class HabitCardListCache @Inject constructor(
|
||||
private fun performUpdate(id: Long, position: Int) {
|
||||
val oldScore = data.scores[id]!!
|
||||
val oldCheckmarks = data.checkmarks[id]
|
||||
val oldNoteIndicators = data.notesIndicators[id]
|
||||
val newScore = newData.scores[id]!!
|
||||
val newCheckmarks = newData.checkmarks[id]!!
|
||||
val newNoteIndicators = newData.notesIndicators[id]!!
|
||||
var unchanged = true
|
||||
if (oldScore != newScore) unchanged = false
|
||||
if (!Arrays.equals(oldCheckmarks, newCheckmarks)) unchanged = false
|
||||
if (!Arrays.equals(oldNoteIndicators, newNoteIndicators)) unchanged = false
|
||||
if (unchanged) return
|
||||
data.scores[id] = newScore
|
||||
data.checkmarks[id] = newCheckmarks
|
||||
data.notesIndicators[id] = newNoteIndicators
|
||||
listener.onItemChanged(position)
|
||||
}
|
||||
|
||||
|
||||
@@ -47,14 +47,16 @@ open class ListHabitsBehavior @Inject constructor(
|
||||
}
|
||||
|
||||
fun onEdit(habit: Habit, timestamp: Timestamp?) {
|
||||
val entries = habit.computedEntries
|
||||
val oldValue = entries.get(timestamp!!).value.toDouble()
|
||||
val entries = habit.computedEntries.get(timestamp!!)
|
||||
val oldValue = entries.value.toDouble()
|
||||
val notes = entries.notes
|
||||
screen.showNumberPicker(
|
||||
oldValue / 1000,
|
||||
habit.unit
|
||||
) { newValue: Double ->
|
||||
habit.unit,
|
||||
notes
|
||||
) { newValue: Double, newNotes:String, ->
|
||||
val value = (newValue * 1000).roundToInt()
|
||||
commandRunner.run(CreateRepetitionCommand(habitList, habit, timestamp, value))
|
||||
commandRunner.run(CreateRepetitionCommand(habitList, habit, timestamp, value, newNotes))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -104,9 +106,9 @@ open class ListHabitsBehavior @Inject constructor(
|
||||
if (prefs.isFirstRun) onFirstRun()
|
||||
}
|
||||
|
||||
fun onToggle(habit: Habit, timestamp: Timestamp?, value: Int) {
|
||||
fun onToggle(habit: Habit, timestamp: Timestamp?, value: Int, notes: String) {
|
||||
commandRunner.run(
|
||||
CreateRepetitionCommand(habitList, habit, timestamp!!, value)
|
||||
CreateRepetitionCommand(habitList, habit, timestamp!!, value, notes)
|
||||
)
|
||||
}
|
||||
|
||||
@@ -131,7 +133,7 @@ open class ListHabitsBehavior @Inject constructor(
|
||||
}
|
||||
|
||||
fun interface NumberPickerCallback {
|
||||
fun onNumberPicked(newValue: Double)
|
||||
fun onNumberPicked(newValue: Double, notes: String)
|
||||
fun onNumberPickerDismissed() {}
|
||||
}
|
||||
|
||||
@@ -142,6 +144,7 @@ open class ListHabitsBehavior @Inject constructor(
|
||||
fun showNumberPicker(
|
||||
value: Double,
|
||||
unit: String,
|
||||
notes: String,
|
||||
callback: NumberPickerCallback
|
||||
)
|
||||
|
||||
|
||||
@@ -64,7 +64,8 @@ class HistoryCardPresenter(
|
||||
if (habit.isNumerical) {
|
||||
val entries = habit.computedEntries
|
||||
val oldValue = entries.get(timestamp).value
|
||||
screen.showNumberPicker(oldValue / 1000.0, habit.unit) { newValue: Double ->
|
||||
val notes = entries.get(timestamp).notes
|
||||
screen.showNumberPicker(oldValue / 1000.0, habit.unit, notes) { newValue: Double, newNotes: String ->
|
||||
val thousands = (newValue * 1000).roundToInt()
|
||||
commandRunner.run(
|
||||
CreateRepetitionCommand(
|
||||
@@ -72,11 +73,14 @@ class HistoryCardPresenter(
|
||||
habit,
|
||||
timestamp,
|
||||
thousands,
|
||||
newNotes,
|
||||
),
|
||||
)
|
||||
}
|
||||
} else {
|
||||
val currentValue = habit.computedEntries.get(timestamp).value
|
||||
val entry = habit.computedEntries.get(timestamp)
|
||||
val currentValue = entry.value
|
||||
val notes = entry.notes
|
||||
val nextValue = Entry.nextToggleValue(
|
||||
value = currentValue,
|
||||
isSkipEnabled = preferences.isSkipEnabled,
|
||||
@@ -88,6 +92,7 @@ class HistoryCardPresenter(
|
||||
habit,
|
||||
timestamp,
|
||||
nextValue,
|
||||
notes,
|
||||
),
|
||||
)
|
||||
}
|
||||
@@ -154,6 +159,7 @@ class HistoryCardPresenter(
|
||||
fun showNumberPicker(
|
||||
value: Double,
|
||||
unit: String,
|
||||
notes: String,
|
||||
callback: ListHabitsBehavior.NumberPickerCallback,
|
||||
)
|
||||
}
|
||||
|
||||
@@ -46,31 +46,37 @@ class WidgetBehavior @Inject constructor(
|
||||
}
|
||||
|
||||
fun onToggleRepetition(habit: Habit, timestamp: Timestamp) {
|
||||
val currentValue = habit.originalEntries.get(timestamp).value
|
||||
val entry = habit.computedEntries.get(timestamp)
|
||||
val currentValue = entry.value
|
||||
val notes = entry.notes
|
||||
val newValue = nextToggleValue(
|
||||
value = currentValue,
|
||||
isSkipEnabled = preferences.isSkipEnabled,
|
||||
areQuestionMarksEnabled = preferences.areQuestionMarksEnabled
|
||||
)
|
||||
setValue(habit, timestamp, newValue)
|
||||
setValue(habit, timestamp, newValue, notes)
|
||||
notificationTray.cancel(habit)
|
||||
}
|
||||
|
||||
fun onIncrement(habit: Habit, timestamp: Timestamp, amount: Int) {
|
||||
val currentValue = habit.computedEntries.get(timestamp).value
|
||||
setValue(habit, timestamp, currentValue + amount)
|
||||
val entry = habit.computedEntries.get(timestamp)
|
||||
val currentValue = entry.value
|
||||
val notes = entry.notes
|
||||
setValue(habit, timestamp, currentValue + amount, notes)
|
||||
notificationTray.cancel(habit)
|
||||
}
|
||||
|
||||
fun onDecrement(habit: Habit, timestamp: Timestamp, amount: Int) {
|
||||
val currentValue = habit.computedEntries.get(timestamp).value
|
||||
setValue(habit, timestamp, currentValue - amount)
|
||||
val entry = habit.computedEntries.get(timestamp)
|
||||
val currentValue = entry.value
|
||||
val notes = entry.notes
|
||||
setValue(habit, timestamp, currentValue - amount, notes)
|
||||
notificationTray.cancel(habit)
|
||||
}
|
||||
|
||||
fun setValue(habit: Habit, timestamp: Timestamp?, newValue: Int) {
|
||||
fun setValue(habit: Habit, timestamp: Timestamp?, newValue: Int, notes: String = "") {
|
||||
commandRunner.run(
|
||||
CreateRepetitionCommand(habitList, habit, timestamp!!, newValue)
|
||||
CreateRepetitionCommand(habitList, habit, timestamp!!, newValue, notes)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
1
uhabits-core/src/jvmMain/resources/migrations/25.sql
Normal file
1
uhabits-core/src/jvmMain/resources/migrations/25.sql
Normal file
@@ -0,0 +1 @@
|
||||
alter table Repetitions add column notes text;
|
||||
@@ -38,7 +38,7 @@ class CreateRepetitionCommandTest : BaseUnitTest() {
|
||||
habit = fixtures.createShortHabit()
|
||||
habitList.add(habit)
|
||||
today = getToday()
|
||||
command = CreateRepetitionCommand(habitList, habit, today, 100)
|
||||
command = CreateRepetitionCommand(habitList, habit, today, 100, "")
|
||||
}
|
||||
|
||||
@Test
|
||||
|
||||
@@ -79,8 +79,8 @@ class ListHabitsBehaviorTest : BaseUnitTest() {
|
||||
@Test
|
||||
fun testOnEdit() {
|
||||
behavior.onEdit(habit2, getToday())
|
||||
verify(screen).showNumberPicker(eq(0.1), eq("miles"), picker.capture())
|
||||
picker.lastValue.onNumberPicked(100.0)
|
||||
verify(screen).showNumberPicker(eq(0.1), eq("miles"), "", picker.capture())
|
||||
picker.lastValue.onNumberPicked(100.0, "")
|
||||
val today = getTodayWithOffset()
|
||||
assertThat(habit2.computedEntries.get(today).value, equalTo(100000))
|
||||
}
|
||||
@@ -160,7 +160,7 @@ class ListHabitsBehaviorTest : BaseUnitTest() {
|
||||
@Test
|
||||
fun testOnToggle() {
|
||||
assertTrue(habit1.isCompletedToday())
|
||||
behavior.onToggle(habit1, getToday(), Entry.NO)
|
||||
behavior.onToggle(habit1, getToday(), Entry.NO, "")
|
||||
assertFalse(habit1.isCompletedToday())
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user