mirror of
https://github.com/iSoron/uhabits.git
synced 2025-12-06 17:18:52 -06:00
Remove EntryList
This commit is contained in:
@@ -77,6 +77,7 @@ public class HabitFixtures
|
|||||||
for (int mark : marks)
|
for (int mark : marks)
|
||||||
habit.getOriginalEntries().add(new Entry(today.minus(mark), YES_MANUAL));
|
habit.getOriginalEntries().add(new Entry(today.minus(mark), YES_MANUAL));
|
||||||
|
|
||||||
|
habit.recompute();
|
||||||
return habit;
|
return habit;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -111,6 +112,7 @@ public class HabitFixtures
|
|||||||
for (int mark : marks)
|
for (int mark : marks)
|
||||||
habit.getOriginalEntries().add(new Entry(today.minus(mark), YES_MANUAL));
|
habit.getOriginalEntries().add(new Entry(today.minus(mark), YES_MANUAL));
|
||||||
|
|
||||||
|
habit.recompute();
|
||||||
return habit;
|
return habit;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -132,6 +134,7 @@ public class HabitFixtures
|
|||||||
timestamp = timestamp.minus(1);
|
timestamp = timestamp.minus(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
habit.recompute();
|
||||||
return habit;
|
return habit;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -150,6 +153,7 @@ public class HabitFixtures
|
|||||||
timestamp = timestamp.minus(1);
|
timestamp = timestamp.minus(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
habit.recompute();
|
||||||
return habit;
|
return habit;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -46,7 +46,7 @@ public class BarChartTest extends BaseViewTest
|
|||||||
Habit habit = fixtures.createLongNumericalHabit();
|
Habit habit = fixtures.createLongNumericalHabit();
|
||||||
view = new BarChart(targetContext);
|
view = new BarChart(targetContext);
|
||||||
Timestamp today = DateUtils.getToday();
|
Timestamp today = DateUtils.getToday();
|
||||||
EntryList entries = habit.getComputedEntries();
|
Entries entries = habit.getComputedEntries();
|
||||||
view.setEntries(entries.getByInterval(today.minus(20), today));
|
view.setEntries(entries.getByInterval(today.minus(20), today));
|
||||||
view.setColor(PaletteUtilsKt.toThemedAndroidColor(habit.getColor(), targetContext));
|
view.setColor(PaletteUtilsKt.toThemedAndroidColor(habit.getColor(), targetContext));
|
||||||
view.setTarget(200.0);
|
view.setTarget(200.0);
|
||||||
|
|||||||
@@ -43,7 +43,7 @@ class HabitCardViewTest : BaseViewTest() {
|
|||||||
habit2 = fixtures.createLongNumericalHabit()
|
habit2 = fixtures.createLongNumericalHabit()
|
||||||
view = component.getHabitCardViewFactory().create().apply {
|
view = component.getHabitCardViewFactory().create().apply {
|
||||||
habit = habit1
|
habit = habit1
|
||||||
values = habit1.computedEntries.allValues
|
values = habit1.computedEntries.getAllValues()
|
||||||
score = habit1.scores.todayValue
|
score = habit1.scores.todayValue
|
||||||
isSelected = false
|
isSelected = false
|
||||||
buttonCount = 5
|
buttonCount = 5
|
||||||
@@ -70,7 +70,7 @@ class HabitCardViewTest : BaseViewTest() {
|
|||||||
fun testRender_numerical() {
|
fun testRender_numerical() {
|
||||||
view.apply {
|
view.apply {
|
||||||
habit = habit2
|
habit = habit2
|
||||||
values = habit2.computedEntries.allValues
|
values = habit2.computedEntries.getAllValues()
|
||||||
}
|
}
|
||||||
assertRenders(view, "$PATH/render_numerical.png")
|
assertRenders(view, "$PATH/render_numerical.png")
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -45,17 +45,6 @@ public class PerformanceTest extends BaseAndroidTest
|
|||||||
habit = fixtures.createLongHabit();
|
habit = fixtures.createLongHabit();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Ignore
|
|
||||||
@Test(timeout = 5000)
|
|
||||||
public void testRepeatedGetTodayValue()
|
|
||||||
{
|
|
||||||
for (int i = 0; i < 100000; i++)
|
|
||||||
{
|
|
||||||
habit.getScores().getTodayValue();
|
|
||||||
habit.getComputedEntries().getTodayValue();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Ignore
|
@Ignore
|
||||||
@Test(timeout = 5000)
|
@Test(timeout = 5000)
|
||||||
public void benchmarkCreateHabitCommand()
|
public void benchmarkCreateHabitCommand()
|
||||||
|
|||||||
@@ -26,6 +26,7 @@ import androidx.test.filters.*;
|
|||||||
|
|
||||||
import org.isoron.uhabits.*;
|
import org.isoron.uhabits.*;
|
||||||
import org.isoron.uhabits.core.models.*;
|
import org.isoron.uhabits.core.models.*;
|
||||||
|
import org.isoron.uhabits.core.utils.*;
|
||||||
import org.junit.*;
|
import org.junit.*;
|
||||||
import org.junit.runner.*;
|
import org.junit.runner.*;
|
||||||
|
|
||||||
@@ -41,10 +42,12 @@ public class CheckmarkWidgetTest extends BaseViewTest
|
|||||||
|
|
||||||
private Habit habit;
|
private Habit habit;
|
||||||
|
|
||||||
private EntryList entries;
|
private Entries entries;
|
||||||
|
|
||||||
private FrameLayout view;
|
private FrameLayout view;
|
||||||
|
|
||||||
|
private Timestamp today = DateUtils.getTodayWithOffset();
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void setUp()
|
public void setUp()
|
||||||
{
|
{
|
||||||
@@ -58,7 +61,7 @@ public class CheckmarkWidgetTest extends BaseViewTest
|
|||||||
CheckmarkWidget widget = new CheckmarkWidget(targetContext, 0, habit);
|
CheckmarkWidget widget = new CheckmarkWidget(targetContext, 0, habit);
|
||||||
view = convertToView(widget, 150, 200);
|
view = convertToView(widget, 150, 200);
|
||||||
|
|
||||||
assertThat(entries.getTodayValue(), equalTo(YES_MANUAL));
|
assertThat(entries.get(today).getValue(), equalTo(YES_MANUAL));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@@ -71,11 +74,11 @@ public class CheckmarkWidgetTest extends BaseViewTest
|
|||||||
// possible to capture intents sent to BroadcastReceivers.
|
// possible to capture intents sent to BroadcastReceivers.
|
||||||
button.performClick();
|
button.performClick();
|
||||||
sleep(1000);
|
sleep(1000);
|
||||||
assertThat(entries.getTodayValue(), equalTo(SKIP));
|
assertThat(entries.get(today).getValue(), equalTo(SKIP));
|
||||||
|
|
||||||
button.performClick();
|
button.performClick();
|
||||||
sleep(1000);
|
sleep(1000);
|
||||||
assertThat(entries.getTodayValue(), equalTo(NO));
|
assertThat(entries.get(today).getValue(), equalTo(NO));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
|||||||
@@ -24,6 +24,7 @@ import androidx.test.filters.*;
|
|||||||
|
|
||||||
import org.isoron.uhabits.*;
|
import org.isoron.uhabits.*;
|
||||||
import org.isoron.uhabits.core.models.*;
|
import org.isoron.uhabits.core.models.*;
|
||||||
|
import org.isoron.uhabits.core.utils.*;
|
||||||
import org.isoron.uhabits.utils.*;
|
import org.isoron.uhabits.utils.*;
|
||||||
import org.junit.*;
|
import org.junit.*;
|
||||||
import org.junit.runner.*;
|
import org.junit.runner.*;
|
||||||
@@ -50,9 +51,10 @@ public class CheckmarkWidgetViewTest extends BaseViewTest
|
|||||||
double score = habit.getScores().getTodayValue();
|
double score = habit.getScores().getTodayValue();
|
||||||
float percentage = (float) score;
|
float percentage = (float) score;
|
||||||
|
|
||||||
|
Timestamp today = DateUtils.getTodayWithOffset();
|
||||||
view.setActiveColor(PaletteUtils.getAndroidTestColor(0));
|
view.setActiveColor(PaletteUtils.getAndroidTestColor(0));
|
||||||
view.setEntryState(habit.getComputedEntries().getTodayValue());
|
view.setEntryState(habit.getComputedEntries().get(today).getValue());
|
||||||
view.setEntryValue(habit.getComputedEntries().getTodayValue());
|
view.setEntryValue(habit.getComputedEntries().get(today).getValue());
|
||||||
view.setPercentage(percentage);
|
view.setPercentage(percentage);
|
||||||
view.setName(habit.getName());
|
view.setName(habit.getName());
|
||||||
view.refresh();
|
view.refresh();
|
||||||
|
|||||||
@@ -66,6 +66,9 @@ class HabitsApplication : Application() {
|
|||||||
|
|
||||||
DateUtils.setStartDayOffset(3, 0)
|
DateUtils.setStartDayOffset(3, 0)
|
||||||
|
|
||||||
|
val habitList = component.habitList
|
||||||
|
for (h in habitList) h.recompute()
|
||||||
|
|
||||||
widgetUpdater = component.widgetUpdater
|
widgetUpdater = component.widgetUpdater
|
||||||
widgetUpdater.startListening()
|
widgetUpdater.startListening()
|
||||||
widgetUpdater.scheduleStartDayWidgetUpdate()
|
widgetUpdater.scheduleStartDayWidgetUpdate()
|
||||||
|
|||||||
@@ -96,9 +96,13 @@ class BarCardPresenter(
|
|||||||
boolBucketSizes[boolSpinnerPosition]
|
boolBucketSizes[boolSpinnerPosition]
|
||||||
}
|
}
|
||||||
val entries = if (bucketSize == 1) {
|
val entries = if (bucketSize == 1) {
|
||||||
habit.computedEntries.all
|
habit.computedEntries.getKnown()
|
||||||
} else {
|
} else {
|
||||||
habit.computedEntries.groupBy(getTruncateField(bucketSize), firstWeekday)
|
habit.computedEntries.groupBy(
|
||||||
|
field = getTruncateField(bucketSize),
|
||||||
|
firstWeekday = firstWeekday,
|
||||||
|
isNumerical = habit.isNumerical,
|
||||||
|
).map { Entry(it.timestamp, it.value * 1000) }
|
||||||
}
|
}
|
||||||
return BarCardViewModel(
|
return BarCardViewModel(
|
||||||
entries = entries,
|
entries = entries,
|
||||||
|
|||||||
@@ -66,7 +66,7 @@ class HistoryCardPresenter(
|
|||||||
val isSkipEnabled: Boolean,
|
val isSkipEnabled: Boolean,
|
||||||
) {
|
) {
|
||||||
fun present() = HistoryCardViewModel(
|
fun present() = HistoryCardViewModel(
|
||||||
entries = habit.computedEntries.allValues,
|
entries = habit.computedEntries.getAllValues(),
|
||||||
color = habit.color,
|
color = habit.color,
|
||||||
firstWeekday = firstWeekday,
|
firstWeekday = firstWeekday,
|
||||||
isNumerical = habit.isNumerical,
|
isNumerical = habit.isNumerical,
|
||||||
|
|||||||
@@ -57,12 +57,13 @@ class TargetCardPresenter(
|
|||||||
val resources: Resources,
|
val resources: Resources,
|
||||||
) {
|
) {
|
||||||
suspend fun present(): TargetCardViewModel = Dispatchers.IO {
|
suspend fun present(): TargetCardViewModel = Dispatchers.IO {
|
||||||
|
val today = DateUtils.getTodayWithOffset()
|
||||||
val entries = habit.computedEntries
|
val entries = habit.computedEntries
|
||||||
val valueToday = entries.todayValue / 1e3
|
val valueToday = entries.get(today).value / 1e3
|
||||||
val valueThisWeek = entries.getThisWeekValue(firstWeekday) / 1e3
|
val valueThisWeek = entries.getThisWeekValue(firstWeekday, habit.isNumerical) / 1e3
|
||||||
val valueThisMonth = entries.thisMonthValue / 1e3
|
val valueThisMonth = entries.getThisMonthValue(habit.isNumerical) / 1e3
|
||||||
val valueThisQuarter = entries.thisQuarterValue / 1e3
|
val valueThisQuarter = entries.getThisQuarterValue(habit.isNumerical) / 1e3
|
||||||
val valueThisYear = entries.thisYearValue / 1e3
|
val valueThisYear = entries.getThisYearValue(habit.isNumerical) / 1e3
|
||||||
|
|
||||||
val cal = DateUtils.getStartOfTodayCalendarWithOffset()
|
val cal = DateUtils.getStartOfTodayCalendarWithOffset()
|
||||||
val daysInMonth = cal.getActualMaximum(Calendar.DAY_OF_MONTH)
|
val daysInMonth = cal.getActualMaximum(Calendar.DAY_OF_MONTH)
|
||||||
|
|||||||
@@ -23,6 +23,7 @@ import android.app.*
|
|||||||
import android.content.*
|
import android.content.*
|
||||||
import android.view.*
|
import android.view.*
|
||||||
import org.isoron.uhabits.core.models.*
|
import org.isoron.uhabits.core.models.*
|
||||||
|
import org.isoron.uhabits.core.utils.*
|
||||||
import org.isoron.uhabits.utils.*
|
import org.isoron.uhabits.utils.*
|
||||||
import org.isoron.uhabits.widgets.views.*
|
import org.isoron.uhabits.widgets.views.*
|
||||||
|
|
||||||
@@ -42,16 +43,16 @@ open class CheckmarkWidget(
|
|||||||
|
|
||||||
override fun refreshData(v: View) {
|
override fun refreshData(v: View) {
|
||||||
(v as CheckmarkWidgetView).apply {
|
(v as CheckmarkWidgetView).apply {
|
||||||
|
val today = DateUtils.getTodayWithOffset()
|
||||||
setBackgroundAlpha(preferedBackgroundAlpha)
|
setBackgroundAlpha(preferedBackgroundAlpha)
|
||||||
|
|
||||||
setActiveColor(habit.color.toThemedAndroidColor(context))
|
setActiveColor(habit.color.toThemedAndroidColor(context))
|
||||||
setName(habit.name)
|
setName(habit.name)
|
||||||
setEntryValue(habit.computedEntries.todayValue)
|
setEntryValue(habit.computedEntries.get(today).value)
|
||||||
if (habit.isNumerical) {
|
if (habit.isNumerical) {
|
||||||
setNumerical(true)
|
setNumerical(true)
|
||||||
setEntryState(getNumericalEntryState())
|
setEntryState(getNumericalEntryState())
|
||||||
} else {
|
} else {
|
||||||
setEntryState(habit.computedEntries.todayValue)
|
setEntryState(habit.computedEntries.get(today).value)
|
||||||
}
|
}
|
||||||
setPercentage(habit.scores.todayValue.toFloat())
|
setPercentage(habit.scores.todayValue.toFloat())
|
||||||
refresh()
|
refresh()
|
||||||
|
|||||||
@@ -46,7 +46,7 @@ class HistoryWidget(
|
|||||||
setFirstWeekday(firstWeekday)
|
setFirstWeekday(firstWeekday)
|
||||||
setSkipEnabled(prefs.isSkipEnabled)
|
setSkipEnabled(prefs.isSkipEnabled)
|
||||||
setColor(habit.color.toThemedAndroidColor(context))
|
setColor(habit.color.toThemedAndroidColor(context))
|
||||||
setEntries(habit.computedEntries.allValues)
|
setEntries(habit.computedEntries.getAllValues())
|
||||||
setNumerical(habit.isNumerical)
|
setNumerical(habit.isNumerical)
|
||||||
setTarget(habit.targetValue / habit.frequency.denominator)
|
setTarget(habit.targetValue / habit.frequency.denominator)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -29,6 +29,7 @@ import org.isoron.uhabits.activities.*
|
|||||||
import org.isoron.uhabits.activities.common.dialogs.*
|
import org.isoron.uhabits.activities.common.dialogs.*
|
||||||
import org.isoron.uhabits.core.ui.screens.habits.list.*
|
import org.isoron.uhabits.core.ui.screens.habits.list.*
|
||||||
import org.isoron.uhabits.core.ui.widgets.*
|
import org.isoron.uhabits.core.ui.widgets.*
|
||||||
|
import org.isoron.uhabits.core.utils.*
|
||||||
import org.isoron.uhabits.intents.*
|
import org.isoron.uhabits.intents.*
|
||||||
import org.isoron.uhabits.utils.*
|
import org.isoron.uhabits.utils.*
|
||||||
import org.isoron.uhabits.widgets.*
|
import org.isoron.uhabits.widgets.*
|
||||||
@@ -71,7 +72,9 @@ class NumericalCheckmarkWidgetActivity : Activity(), ListHabitsBehavior.NumberPi
|
|||||||
val app = this.applicationContext as HabitsApplication
|
val app = this.applicationContext as HabitsApplication
|
||||||
AndroidThemeSwitcher(this, app.component.preferences).apply()
|
AndroidThemeSwitcher(this, app.component.preferences).apply()
|
||||||
val numberPickerFactory = NumberPickerFactory(context)
|
val numberPickerFactory = NumberPickerFactory(context)
|
||||||
numberPickerFactory.create(data.habit.computedEntries.today!!.value.toDouble() / 1000,
|
val today = DateUtils.getTodayWithOffset()
|
||||||
|
val entry = data.habit.computedEntries.get(today)
|
||||||
|
numberPickerFactory.create(entry.value / 1000.0,
|
||||||
data.habit.unit,
|
data.habit.unit,
|
||||||
this).show()
|
this).show()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -142,13 +142,20 @@ public class HabitsCSVExporter
|
|||||||
out.close();
|
out.close();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void writeEntries(String habitDirName, EntryList entries)
|
private void writeEntries(String habitDirName, Entries entries)
|
||||||
throws IOException
|
throws IOException
|
||||||
{
|
{
|
||||||
String filename = habitDirName + "Checkmarks.csv";
|
String filename = habitDirName + "Checkmarks.csv";
|
||||||
FileWriter out = new FileWriter(exportDirName + filename);
|
FileWriter out = new FileWriter(exportDirName + filename);
|
||||||
generateFilenames.add(filename);
|
generateFilenames.add(filename);
|
||||||
entries.writeCSV(out);
|
|
||||||
|
SimpleDateFormat dateFormat = DateFormats.getCSVDateFormat();
|
||||||
|
for (Entry e : entries.getKnown())
|
||||||
|
{
|
||||||
|
String date = dateFormat.format(e.getTimestamp().toJavaDate());
|
||||||
|
out.write(String.format(Locale.US, "%s,%d\n", date, e.getValue()));
|
||||||
|
}
|
||||||
|
|
||||||
out.close();
|
out.close();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -99,7 +99,7 @@ open class Entries {
|
|||||||
if (isNumerical) {
|
if (isNumerical) {
|
||||||
values[values.lastIndex] += truncated[i].value
|
values[values.lastIndex] += truncated[i].value
|
||||||
} else {
|
} else {
|
||||||
if (values[values.lastIndex] == YES_MANUAL) {
|
if (truncated[i].value == YES_MANUAL) {
|
||||||
values[values.lastIndex] += 1
|
values[values.lastIndex] += 1
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -124,7 +124,6 @@ open class Entries {
|
|||||||
original.forEach { add(it) }
|
original.forEach { add(it) }
|
||||||
} else {
|
} else {
|
||||||
val intervals = buildIntervals(frequency, original)
|
val intervals = buildIntervals(frequency, original)
|
||||||
if (intervals.isEmpty()) return
|
|
||||||
snapIntervalsTogether(intervals)
|
snapIntervalsTogether(intervals)
|
||||||
val computed = buildEntriesFromInterval(original, intervals)
|
val computed = buildEntriesFromInterval(original, intervals)
|
||||||
computed.filter { it.value != UNKNOWN }.forEach { add(it) }
|
computed.filter { it.value != UNKNOWN }.forEach { add(it) }
|
||||||
@@ -181,6 +180,7 @@ open class Entries {
|
|||||||
* corresponds to one day older than the previous entry. The boundaries of the time interval
|
* corresponds to one day older than the previous entry. The boundaries of the time interval
|
||||||
* are included.
|
* are included.
|
||||||
*/
|
*/
|
||||||
|
@Deprecated("")
|
||||||
fun getValues(from: Timestamp, to: Timestamp): IntArray {
|
fun getValues(from: Timestamp, to: Timestamp): IntArray {
|
||||||
if (from.isNewerThan(to)) throw IllegalArgumentException()
|
if (from.isNewerThan(to)) throw IllegalArgumentException()
|
||||||
val nDays = from.daysUntil(to) + 1
|
val nDays = from.daysUntil(to) + 1
|
||||||
@@ -194,6 +194,60 @@ open class Entries {
|
|||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Deprecated("")
|
||||||
|
fun getAllValues(): IntArray {
|
||||||
|
val entries = getKnown()
|
||||||
|
if (entries.isEmpty()) return IntArray(0)
|
||||||
|
val (fromTimestamp, _) = entries.last()
|
||||||
|
val toTimestamp = DateUtils.getTodayWithOffset()
|
||||||
|
return getValues(fromTimestamp, toTimestamp)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Deprecated("")
|
||||||
|
open fun getThisWeekValue(firstWeekday: Int, isNumerical: Boolean): Int {
|
||||||
|
return getThisIntervalValue(
|
||||||
|
truncateField = DateUtils.TruncateField.WEEK_NUMBER,
|
||||||
|
firstWeekday = firstWeekday,
|
||||||
|
isNumerical = isNumerical
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Deprecated("")
|
||||||
|
open fun getThisMonthValue(isNumerical: Boolean): Int {
|
||||||
|
return getThisIntervalValue(
|
||||||
|
truncateField = DateUtils.TruncateField.MONTH,
|
||||||
|
firstWeekday = Calendar.SATURDAY,
|
||||||
|
isNumerical = isNumerical
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Deprecated("")
|
||||||
|
open fun getThisQuarterValue(isNumerical: Boolean): Int {
|
||||||
|
return getThisIntervalValue(
|
||||||
|
truncateField = DateUtils.TruncateField.QUARTER,
|
||||||
|
firstWeekday = Calendar.SATURDAY,
|
||||||
|
isNumerical = isNumerical
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Deprecated("")
|
||||||
|
open fun getThisYearValue(isNumerical: Boolean): Int {
|
||||||
|
return getThisIntervalValue(
|
||||||
|
truncateField = DateUtils.TruncateField.YEAR,
|
||||||
|
firstWeekday = Calendar.SATURDAY,
|
||||||
|
isNumerical = isNumerical
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun getThisIntervalValue(
|
||||||
|
truncateField: DateUtils.TruncateField,
|
||||||
|
firstWeekday: Int,
|
||||||
|
isNumerical: Boolean,
|
||||||
|
): Int {
|
||||||
|
val groups: List<Entry> = groupBy(truncateField, firstWeekday, isNumerical)
|
||||||
|
return if (groups.isEmpty()) 0 else groups[0].value
|
||||||
|
}
|
||||||
|
|
||||||
data class Interval(val begin: Timestamp, val center: Timestamp, val end: Timestamp) {
|
data class Interval(val begin: Timestamp, val center: Timestamp, val end: Timestamp) {
|
||||||
val length: Int
|
val length: Int
|
||||||
get() = begin.daysUntil(end) + 1;
|
get() = begin.daysUntil(end) + 1;
|
||||||
@@ -212,13 +266,24 @@ open class Entries {
|
|||||||
original: List<Entry>,
|
original: List<Entry>,
|
||||||
intervals: List<Interval>,
|
intervals: List<Interval>,
|
||||||
): List<Entry> {
|
): List<Entry> {
|
||||||
val toTimestamp = intervals.first().end
|
|
||||||
val fromTimstamp = intervals.last().begin
|
|
||||||
val result = arrayListOf<Entry>()
|
val result = arrayListOf<Entry>()
|
||||||
|
if (original.isEmpty()) return result
|
||||||
|
|
||||||
|
var from = original[0].timestamp
|
||||||
|
var to = original[0].timestamp
|
||||||
|
|
||||||
|
for (e in original) {
|
||||||
|
if (e.timestamp < from) from = e.timestamp
|
||||||
|
if (e.timestamp > to) to = e.timestamp
|
||||||
|
}
|
||||||
|
for (interval in intervals) {
|
||||||
|
if (interval.begin < from) from = interval.begin
|
||||||
|
if (interval.end > to) to = interval.end
|
||||||
|
}
|
||||||
|
|
||||||
// Create unknown entries
|
// Create unknown entries
|
||||||
var current = toTimestamp
|
var current = to
|
||||||
while (current >= fromTimstamp) {
|
while (current >= from) {
|
||||||
result.add(Entry(current, UNKNOWN))
|
result.add(Entry(current, UNKNOWN))
|
||||||
current = current.minus(1)
|
current = current.minus(1)
|
||||||
}
|
}
|
||||||
@@ -227,7 +292,7 @@ open class Entries {
|
|||||||
intervals.forEach { interval ->
|
intervals.forEach { interval ->
|
||||||
current = interval.end
|
current = interval.end
|
||||||
while (current >= interval.begin) {
|
while (current >= interval.begin) {
|
||||||
val offset = current.daysUntil(toTimestamp)
|
val offset = current.daysUntil(to)
|
||||||
result[offset] = Entry(current, YES_AUTO)
|
result[offset] = Entry(current, YES_AUTO)
|
||||||
current = current.minus(1)
|
current = current.minus(1)
|
||||||
}
|
}
|
||||||
@@ -235,7 +300,7 @@ open class Entries {
|
|||||||
|
|
||||||
// Copy original entries
|
// Copy original entries
|
||||||
original.forEach { entry ->
|
original.forEach { entry ->
|
||||||
val offset = entry.timestamp.daysUntil(toTimestamp)
|
val offset = entry.timestamp.daysUntil(to)
|
||||||
result[offset] = entry
|
result[offset] = entry
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,555 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (C) 2016 Álinson Santos Xavier <isoron@gmail.com>
|
|
||||||
*
|
|
||||||
* 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;
|
|
||||||
|
|
||||||
import androidx.annotation.*;
|
|
||||||
|
|
||||||
import org.apache.commons.lang3.builder.*;
|
|
||||||
import org.isoron.uhabits.core.utils.*;
|
|
||||||
|
|
||||||
import java.io.*;
|
|
||||||
import java.text.*;
|
|
||||||
import java.util.*;
|
|
||||||
|
|
||||||
import javax.annotation.concurrent.*;
|
|
||||||
|
|
||||||
import static org.isoron.uhabits.core.models.Entry.*;
|
|
||||||
import static org.isoron.uhabits.core.models.Habit.*;
|
|
||||||
import static org.isoron.uhabits.core.utils.StringUtils.*;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The collection of {@link Entry}s belonging to a habit.
|
|
||||||
*/
|
|
||||||
@ThreadSafe
|
|
||||||
public class EntryList
|
|
||||||
{
|
|
||||||
protected Habit habit = null;
|
|
||||||
|
|
||||||
protected ArrayList<Entry> list;
|
|
||||||
|
|
||||||
public EntryList()
|
|
||||||
{
|
|
||||||
this.list = new ArrayList<>();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setHabit(Habit habit)
|
|
||||||
{
|
|
||||||
this.habit = habit;
|
|
||||||
}
|
|
||||||
|
|
||||||
@NonNull
|
|
||||||
static List<Entry> buildEntriesFromInterval(Entry[] original,
|
|
||||||
ArrayList<Interval> intervals)
|
|
||||||
{
|
|
||||||
if (original.length == 0) throw new IllegalArgumentException();
|
|
||||||
|
|
||||||
Timestamp today = DateUtils.getTodayWithOffset();
|
|
||||||
Timestamp begin = original[0].getTimestamp();
|
|
||||||
if (intervals.size() > 0) begin = Timestamp.oldest(begin, intervals.get(0).begin);
|
|
||||||
|
|
||||||
int nDays = begin.daysUntil(today) + 1;
|
|
||||||
List<Entry> entries = new ArrayList<>(nDays);
|
|
||||||
for (int i = 0; i < nDays; i++)
|
|
||||||
entries.add(new Entry(today.minus(i), UNKNOWN));
|
|
||||||
|
|
||||||
for (Interval interval : intervals)
|
|
||||||
{
|
|
||||||
for (int i = 0; i < interval.length(); i++)
|
|
||||||
{
|
|
||||||
Timestamp date = interval.begin.plus(i);
|
|
||||||
int offset = date.daysUntil(today);
|
|
||||||
if (offset < 0) continue;
|
|
||||||
entries.set(offset, new Entry(date, YES_AUTO));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for (Entry e : original)
|
|
||||||
{
|
|
||||||
Timestamp date = e.getTimestamp();
|
|
||||||
int offset = date.daysUntil(today);
|
|
||||||
int value = e.getValue();
|
|
||||||
int prevValue = entries.get(offset).getValue();
|
|
||||||
if (prevValue < value)
|
|
||||||
entries.set(offset, new Entry(date, value));
|
|
||||||
}
|
|
||||||
|
|
||||||
return entries;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* For non-daily habits, some manual entries generate many
|
|
||||||
* automatic entries. For example, for weekly habits, each repetition generates
|
|
||||||
* seven checkmarks. For twice-a-week habits, two repetitions that are close
|
|
||||||
* enough together also generate seven checkmarks.
|
|
||||||
* <p>
|
|
||||||
* This group of generated entries is represented by an interval. This function
|
|
||||||
* computes the list of intervals for a given list of original entries. It tries
|
|
||||||
* to build the intervals as far away in the future as possible.
|
|
||||||
*/
|
|
||||||
@NonNull
|
|
||||||
static ArrayList<Interval> buildIntervals(@NonNull Frequency freq,
|
|
||||||
@NonNull Entry[] entries)
|
|
||||||
{
|
|
||||||
ArrayList<Entry> filteredEntries = new ArrayList<>();
|
|
||||||
for (Entry e : entries)
|
|
||||||
if (e.getValue() == YES_MANUAL)
|
|
||||||
filteredEntries.add(e);
|
|
||||||
|
|
||||||
int num = freq.getNumerator();
|
|
||||||
int den = freq.getDenominator();
|
|
||||||
|
|
||||||
ArrayList<Interval> intervals = new ArrayList<>();
|
|
||||||
for (int i = 0; i < filteredEntries.size() - num + 1; i++)
|
|
||||||
{
|
|
||||||
Entry first = filteredEntries.get(i);
|
|
||||||
Entry last = filteredEntries.get(i + num - 1);
|
|
||||||
|
|
||||||
long distance = first.getTimestamp().daysUntil(last.getTimestamp());
|
|
||||||
if (distance >= den) continue;
|
|
||||||
|
|
||||||
Timestamp begin = first.getTimestamp();
|
|
||||||
Timestamp center = last.getTimestamp();
|
|
||||||
Timestamp end = begin.plus(den - 1);
|
|
||||||
intervals.add(new Interval(begin, center, end));
|
|
||||||
}
|
|
||||||
|
|
||||||
return intervals;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Starting from the second newest interval, this function tries to slide the
|
|
||||||
* intervals backwards into the past, so that gaps are eliminated and
|
|
||||||
* streaks are maximized.
|
|
||||||
*/
|
|
||||||
static void snapIntervalsTogether(@NonNull ArrayList<Interval> intervals)
|
|
||||||
{
|
|
||||||
int n = intervals.size();
|
|
||||||
for (int i = n - 2; i >= 0; i--)
|
|
||||||
{
|
|
||||||
Interval curr = intervals.get(i);
|
|
||||||
Interval next = intervals.get(i + 1);
|
|
||||||
|
|
||||||
int gapNextToCurrent = next.begin.daysUntil(curr.end);
|
|
||||||
int gapCenterToEnd = curr.center.daysUntil(curr.end);
|
|
||||||
|
|
||||||
if (gapNextToCurrent >= 0)
|
|
||||||
{
|
|
||||||
int shift = Math.min(gapCenterToEnd, gapNextToCurrent + 1);
|
|
||||||
intervals.set(i, new Interval(curr.begin.minus(shift),
|
|
||||||
curr.center,
|
|
||||||
curr.end.minus(shift)));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void add(List<Entry> entries)
|
|
||||||
{
|
|
||||||
list.addAll(entries);
|
|
||||||
Collections.sort(list,
|
|
||||||
(c1, c2) -> c2.getTimestamp().compareTo(c1.getTimestamp()));
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the values for all the checkmarks, since the oldest repetition of
|
|
||||||
* the habit until today.
|
|
||||||
* <p>
|
|
||||||
* If there are no repetitions at all, returns an empty array. The values
|
|
||||||
* are returned in an array containing one integer value for each day since
|
|
||||||
* the first repetition of the habit until today. The first entry
|
|
||||||
* corresponds to today, the second entry corresponds to yesterday, and so
|
|
||||||
* on.
|
|
||||||
*
|
|
||||||
* @return values for the checkmarks in the interval
|
|
||||||
*/
|
|
||||||
@NonNull
|
|
||||||
public synchronized final int[] getAllValues()
|
|
||||||
{
|
|
||||||
List<Entry> entries = habit.getOriginalEntries().getKnown();
|
|
||||||
if(entries.isEmpty()) return new int[0];
|
|
||||||
Entry oldestOriginal = entries.get(entries.size() - 1);
|
|
||||||
|
|
||||||
Timestamp fromTimestamp = oldestOriginal.getTimestamp();
|
|
||||||
Timestamp toTimestamp = DateUtils.getTodayWithOffset();
|
|
||||||
|
|
||||||
return getValues(fromTimestamp, toTimestamp);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the list of checkmarks that fall within the given interval.
|
|
||||||
* <p>
|
|
||||||
* There is exactly one checkmark per day in the interval. The endpoints of
|
|
||||||
* the interval are included. The list is ordered by timestamp (decreasing).
|
|
||||||
* That is, the first checkmark corresponds to the newest timestamp, and the
|
|
||||||
* last checkmark corresponds to the oldest timestamp.
|
|
||||||
*
|
|
||||||
* @param from timestamp of the beginning of the interval.
|
|
||||||
* @param to timestamp of the end of the interval.
|
|
||||||
* @return the list of checkmarks within the interval.
|
|
||||||
*/
|
|
||||||
@NonNull
|
|
||||||
public List<Entry> getByInterval(Timestamp from,
|
|
||||||
Timestamp to)
|
|
||||||
{
|
|
||||||
compute();
|
|
||||||
|
|
||||||
Timestamp newestComputed = new Timestamp(0);
|
|
||||||
Timestamp oldestComputed = new Timestamp(0).plus(1000000);
|
|
||||||
|
|
||||||
Entry newest = getNewestComputed();
|
|
||||||
Entry oldest = getOldestComputed();
|
|
||||||
if (newest != null) newestComputed = newest.getTimestamp();
|
|
||||||
if (oldest != null) oldestComputed = oldest.getTimestamp();
|
|
||||||
|
|
||||||
List<Entry> filtered = new ArrayList<>(
|
|
||||||
Math.max(0, oldestComputed.daysUntil(newestComputed) + 1));
|
|
||||||
|
|
||||||
for (int i = 0; i <= from.daysUntil(to); i++)
|
|
||||||
{
|
|
||||||
Timestamp t = to.minus(i);
|
|
||||||
if (t.isNewerThan(newestComputed) || t.isOlderThan(oldestComputed))
|
|
||||||
filtered.add(new Entry(t, Entry.UNKNOWN));
|
|
||||||
else
|
|
||||||
filtered.add(list.get(t.daysUntil(newestComputed)));
|
|
||||||
}
|
|
||||||
|
|
||||||
return filtered;
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the checkmark for today.
|
|
||||||
*
|
|
||||||
* @return checkmark for today
|
|
||||||
*/
|
|
||||||
@Nullable
|
|
||||||
public synchronized final Entry getToday()
|
|
||||||
{
|
|
||||||
compute();
|
|
||||||
Timestamp today = DateUtils.getTodayWithOffset();
|
|
||||||
return getByInterval(today, today).get(0);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the value of today's checkmark.
|
|
||||||
*
|
|
||||||
* @return value of today's checkmark
|
|
||||||
*/
|
|
||||||
public synchronized int getTodayValue()
|
|
||||||
{
|
|
||||||
Entry today = getToday();
|
|
||||||
if (today != null) return today.getValue();
|
|
||||||
else return UNKNOWN;
|
|
||||||
}
|
|
||||||
|
|
||||||
public synchronized int getThisWeekValue(int firstWeekday)
|
|
||||||
{
|
|
||||||
return getThisIntervalValue(DateUtils.TruncateField.WEEK_NUMBER, firstWeekday);
|
|
||||||
}
|
|
||||||
|
|
||||||
public synchronized int getThisMonthValue()
|
|
||||||
{
|
|
||||||
return getThisIntervalValue(DateUtils.TruncateField.MONTH, Calendar.SATURDAY);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public synchronized int getThisQuarterValue()
|
|
||||||
{
|
|
||||||
return getThisIntervalValue(DateUtils.TruncateField.QUARTER, Calendar.SATURDAY);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public synchronized int getThisYearValue()
|
|
||||||
{
|
|
||||||
return getThisIntervalValue(DateUtils.TruncateField.YEAR, Calendar.SATURDAY);
|
|
||||||
}
|
|
||||||
|
|
||||||
private int getThisIntervalValue(DateUtils.TruncateField truncateField, int firstWeekday)
|
|
||||||
{
|
|
||||||
List<Entry> groups = habit.getComputedEntries().groupBy(truncateField, firstWeekday, 1);
|
|
||||||
if (groups.isEmpty()) return 0;
|
|
||||||
return groups.get(0).getValue();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the values of the checkmarks that fall inside a certain interval
|
|
||||||
* of time.
|
|
||||||
* <p>
|
|
||||||
* The values are returned in an array containing one integer value for each
|
|
||||||
* day of the interval. The first entry corresponds to the most recent day
|
|
||||||
* in the interval. Each subsequent entry corresponds to one day older than
|
|
||||||
* the previous entry. The boundaries of the time interval are included.
|
|
||||||
*
|
|
||||||
* @param from timestamp for the oldest checkmark
|
|
||||||
* @param to timestamp for the newest checkmark
|
|
||||||
* @return values for the checkmarks inside the given interval
|
|
||||||
*/
|
|
||||||
public final int[] getValues(Timestamp from, Timestamp to)
|
|
||||||
{
|
|
||||||
if (from.isNewerThan(to)) return new int[0];
|
|
||||||
|
|
||||||
List<Entry> entries = getByInterval(from, to);
|
|
||||||
int values[] = new int[entries.size()];
|
|
||||||
|
|
||||||
int i = 0;
|
|
||||||
for (Entry c : entries)
|
|
||||||
values[i++] = c.getValue();
|
|
||||||
|
|
||||||
return values;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Marks as invalid every checkmark that has timestamp either equal or newer
|
|
||||||
* than a given timestamp. These checkmarks will be recomputed at the next
|
|
||||||
* time they are queried.
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
public void recompute()
|
|
||||||
{
|
|
||||||
list.clear();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Writes the entire list of checkmarks to the given writer, in CSV format.
|
|
||||||
*
|
|
||||||
* @param out the writer where the CSV will be output
|
|
||||||
* @throws IOException in case write operations fail
|
|
||||||
*/
|
|
||||||
public final void writeCSV(Writer out) throws IOException
|
|
||||||
{
|
|
||||||
int values[];
|
|
||||||
|
|
||||||
synchronized (this)
|
|
||||||
{
|
|
||||||
compute();
|
|
||||||
values = getAllValues();
|
|
||||||
}
|
|
||||||
|
|
||||||
Timestamp timestamp = DateUtils.getToday();
|
|
||||||
SimpleDateFormat dateFormat = DateFormats.getCSVDateFormat();
|
|
||||||
|
|
||||||
for (int value : values)
|
|
||||||
{
|
|
||||||
String date = dateFormat.format(timestamp.toJavaDate());
|
|
||||||
out.write(String.format("%s,%d\n", date, value));
|
|
||||||
timestamp = timestamp.minus(1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Computes and stores one checkmark for each day, from the first habit
|
|
||||||
* repetition to today. If this list is already computed, does nothing.
|
|
||||||
*/
|
|
||||||
protected final synchronized void compute()
|
|
||||||
{
|
|
||||||
final Timestamp today = DateUtils.getTodayWithOffset();
|
|
||||||
|
|
||||||
Entry newest = getNewestComputed();
|
|
||||||
if (newest != null && newest.getTimestamp().equals(today)) return;
|
|
||||||
recompute();
|
|
||||||
|
|
||||||
List<Entry> entries = habit.getOriginalEntries().getKnown();
|
|
||||||
if(entries.isEmpty()) return;
|
|
||||||
final Timestamp from = entries.get(entries.size() - 1).getTimestamp();
|
|
||||||
|
|
||||||
if (from.isNewerThan(today)) return;
|
|
||||||
|
|
||||||
Entry[] reps = entries.stream().filter(e ->
|
|
||||||
!e.getTimestamp().isOlderThan(from) && !e.getTimestamp().isNewerThan(today)
|
|
||||||
).toArray(Entry[]::new);
|
|
||||||
List<Entry> repsAsList = Arrays.asList(reps);
|
|
||||||
Collections.reverse(repsAsList);
|
|
||||||
reps = repsAsList.toArray(reps);
|
|
||||||
|
|
||||||
if (habit.isNumerical()) computeNumerical(reps);
|
|
||||||
else computeYesNo(reps);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Nullable
|
|
||||||
protected Entry getNewestComputed()
|
|
||||||
{
|
|
||||||
if (list.isEmpty()) return null;
|
|
||||||
return list.get(0);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Nullable
|
|
||||||
protected Entry getOldestComputed()
|
|
||||||
{
|
|
||||||
if (list.isEmpty()) return null;
|
|
||||||
return list.get(list.size() - 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void computeNumerical(Entry[] original)
|
|
||||||
{
|
|
||||||
if (original.length == 0) return;
|
|
||||||
|
|
||||||
Timestamp today = DateUtils.getTodayWithOffset();
|
|
||||||
Timestamp begin = original[0].getTimestamp();
|
|
||||||
|
|
||||||
int nDays = begin.daysUntil(today) + 1;
|
|
||||||
List<Entry> computed = new ArrayList<>(nDays);
|
|
||||||
for (int i = 0; i < nDays; i++)
|
|
||||||
computed.add(new Entry(today.minus(i), 0));
|
|
||||||
|
|
||||||
for (Entry e : original)
|
|
||||||
{
|
|
||||||
int offset = e.getTimestamp().daysUntil(today);
|
|
||||||
computed.set(offset, new Entry(e.getTimestamp(), e.getValue()));
|
|
||||||
}
|
|
||||||
|
|
||||||
add(computed);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void computeYesNo(Entry[] original)
|
|
||||||
{
|
|
||||||
ArrayList<Interval> intervals;
|
|
||||||
intervals = buildIntervals(habit.getFrequency(), original);
|
|
||||||
snapIntervalsTogether(intervals);
|
|
||||||
add(buildEntriesFromInterval(original, intervals));
|
|
||||||
}
|
|
||||||
|
|
||||||
public List<Entry> getAll()
|
|
||||||
{
|
|
||||||
List<Entry> entries = habit.getOriginalEntries().getKnown();
|
|
||||||
if(entries.isEmpty()) return new ArrayList<>();
|
|
||||||
Entry oldest = entries.get(entries.size() - 1);
|
|
||||||
return getByInterval(oldest.getTimestamp(), DateUtils.getTodayWithOffset());
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean isCompletedToday()
|
|
||||||
{
|
|
||||||
int todayCheckmark = getTodayValue();
|
|
||||||
if (habit.isNumerical())
|
|
||||||
{
|
|
||||||
if (habit.getTargetType() == AT_LEAST)
|
|
||||||
return todayCheckmark / 1000.0 >= habit.getTargetValue();
|
|
||||||
else
|
|
||||||
return todayCheckmark / 1000.0 <= habit.getTargetValue();
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
return (todayCheckmark != NO && todayCheckmark != UNKNOWN);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static final class Interval
|
|
||||||
{
|
|
||||||
final Timestamp begin;
|
|
||||||
|
|
||||||
final Timestamp center;
|
|
||||||
|
|
||||||
final Timestamp end;
|
|
||||||
|
|
||||||
Interval(Timestamp begin, Timestamp center, Timestamp end)
|
|
||||||
{
|
|
||||||
if(begin.isNewerThan(center)) throw new IllegalArgumentException();
|
|
||||||
if(center.isNewerThan(end)) throw new IllegalArgumentException();
|
|
||||||
|
|
||||||
this.begin = begin;
|
|
||||||
this.center = center;
|
|
||||||
this.end = end;
|
|
||||||
}
|
|
||||||
|
|
||||||
public int length()
|
|
||||||
{
|
|
||||||
return begin.daysUntil(end) + 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean equals(Object o)
|
|
||||||
{
|
|
||||||
if (this == o) return true;
|
|
||||||
|
|
||||||
if (o == null || getClass() != o.getClass()) return false;
|
|
||||||
|
|
||||||
Interval interval = (Interval) o;
|
|
||||||
|
|
||||||
return new EqualsBuilder()
|
|
||||||
.append(begin, interval.begin)
|
|
||||||
.append(center, interval.center)
|
|
||||||
.append(end, interval.end)
|
|
||||||
.isEquals();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int hashCode()
|
|
||||||
{
|
|
||||||
return new HashCodeBuilder(17, 37)
|
|
||||||
.append(begin)
|
|
||||||
.append(center)
|
|
||||||
.append(end)
|
|
||||||
.toHashCode();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String toString()
|
|
||||||
{
|
|
||||||
return new ToStringBuilder(this, defaultToStringStyle())
|
|
||||||
.append("begin", begin)
|
|
||||||
.append("center", center)
|
|
||||||
.append("end", end)
|
|
||||||
.toString();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@NonNull
|
|
||||||
public List<Entry> groupBy(DateUtils.TruncateField field, int firstWeekday)
|
|
||||||
{
|
|
||||||
return groupBy(field, firstWeekday, 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
@NonNull
|
|
||||||
public List<Entry> groupBy(DateUtils.TruncateField field,
|
|
||||||
int firstWeekday,
|
|
||||||
int maxGroups)
|
|
||||||
{
|
|
||||||
List<Entry> checks = getAll();
|
|
||||||
|
|
||||||
int count = 0;
|
|
||||||
Timestamp[] truncatedTimestamps = new Timestamp[checks.size()];
|
|
||||||
int[] values = new int[checks.size()];
|
|
||||||
|
|
||||||
for (Entry rep : checks)
|
|
||||||
{
|
|
||||||
Timestamp tt = rep.getTimestamp().truncate(field, firstWeekday);
|
|
||||||
if (count == 0 || !truncatedTimestamps[count - 1].equals(tt))
|
|
||||||
{
|
|
||||||
if (maxGroups > 0 && count >= maxGroups) break;
|
|
||||||
truncatedTimestamps[count++] = tt;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (habit.isNumerical())
|
|
||||||
values[count - 1] += rep.getValue();
|
|
||||||
else if (rep.getValue() == Entry.YES_MANUAL)
|
|
||||||
values[count - 1] += 1000;
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
ArrayList<Entry> groupedEntries = new ArrayList<>();
|
|
||||||
for (int i = 0; i < count; i++)
|
|
||||||
{
|
|
||||||
Entry rep = new Entry(truncatedTimestamps[i], values[i]);
|
|
||||||
groupedEntries.add(rep);
|
|
||||||
}
|
|
||||||
|
|
||||||
return groupedEntries;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -18,6 +18,8 @@
|
|||||||
*/
|
*/
|
||||||
package org.isoron.uhabits.core.models
|
package org.isoron.uhabits.core.models
|
||||||
|
|
||||||
|
import org.isoron.uhabits.core.utils.*
|
||||||
|
|
||||||
data class Habit(
|
data class Habit(
|
||||||
var color: PaletteColor = PaletteColor(8),
|
var color: PaletteColor = PaletteColor(8),
|
||||||
var description: String = "",
|
var description: String = "",
|
||||||
@@ -33,8 +35,7 @@ data class Habit(
|
|||||||
var type: Int = YES_NO_HABIT,
|
var type: Int = YES_NO_HABIT,
|
||||||
var unit: String = "",
|
var unit: String = "",
|
||||||
var uuid: String? = null,
|
var uuid: String? = null,
|
||||||
val computedEntries: EntryList,
|
val computedEntries: Entries,
|
||||||
val newComputedEntries: Entries,
|
|
||||||
val originalEntries: Entries,
|
val originalEntries: Entries,
|
||||||
val scores: ScoreList,
|
val scores: ScoreList,
|
||||||
val streaks: StreakList,
|
val streaks: StreakList,
|
||||||
@@ -49,13 +50,24 @@ data class Habit(
|
|||||||
|
|
||||||
fun hasReminder(): Boolean = reminder != null
|
fun hasReminder(): Boolean = reminder != null
|
||||||
|
|
||||||
fun isCompletedToday(): Boolean = computedEntries.isCompletedToday
|
fun isCompletedToday(): Boolean {
|
||||||
|
val today = DateUtils.getTodayWithOffset()
|
||||||
|
val value = computedEntries.get(today).value
|
||||||
|
return if (isNumerical) {
|
||||||
|
if (targetType == AT_LEAST) {
|
||||||
|
value / 1000.0 >= targetValue
|
||||||
|
} else {
|
||||||
|
value / 1000.0 <= targetValue
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
value != Entry.NO && value != Entry.UNKNOWN
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fun recompute() {
|
fun recompute() {
|
||||||
scores.recompute()
|
scores.recompute()
|
||||||
computedEntries.recompute()
|
|
||||||
streaks.recompute()
|
streaks.recompute()
|
||||||
newComputedEntries.recomputeFrom(
|
computedEntries.recomputeFrom(
|
||||||
originalEntries = originalEntries,
|
originalEntries = originalEntries,
|
||||||
frequency = frequency,
|
frequency = frequency,
|
||||||
isNumerical = isNumerical,
|
isNumerical = isNumerical,
|
||||||
|
|||||||
@@ -178,7 +178,6 @@ public abstract class HabitList implements Iterable<Habit>
|
|||||||
{
|
{
|
||||||
for (Habit h : this)
|
for (Habit h : this)
|
||||||
{
|
{
|
||||||
h.getComputedEntries().recompute();
|
|
||||||
h.getStreaks().recompute();
|
h.getStreaks().recompute();
|
||||||
h.getScores().recompute();
|
h.getScores().recompute();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -28,25 +28,20 @@ import org.isoron.uhabits.core.models.sqlite.records.*
|
|||||||
interface ModelFactory {
|
interface ModelFactory {
|
||||||
|
|
||||||
fun buildHabit(): Habit {
|
fun buildHabit(): Habit {
|
||||||
val computedEntries = buildEntryList()
|
|
||||||
val scores = buildScoreList()
|
val scores = buildScoreList()
|
||||||
val streaks = buildStreakList()
|
val streaks = buildStreakList()
|
||||||
val habit = Habit(
|
val habit = Habit(
|
||||||
computedEntries = computedEntries,
|
|
||||||
scores = scores,
|
scores = scores,
|
||||||
streaks = streaks,
|
streaks = streaks,
|
||||||
originalEntries = buildOriginalEntries(),
|
originalEntries = buildOriginalEntries(),
|
||||||
newComputedEntries = buildNewComputedEntries(),
|
computedEntries = buildNewComputedEntries(),
|
||||||
)
|
)
|
||||||
computedEntries.setHabit(habit)
|
|
||||||
scores.setHabit(habit)
|
scores.setHabit(habit)
|
||||||
streaks.setHabit(habit)
|
streaks.setHabit(habit)
|
||||||
habit.recompute()
|
|
||||||
return habit
|
return habit
|
||||||
}
|
}
|
||||||
fun buildNewComputedEntries(): Entries
|
fun buildNewComputedEntries(): Entries
|
||||||
fun buildOriginalEntries(): Entries
|
fun buildOriginalEntries(): Entries
|
||||||
fun buildEntryList(): EntryList
|
|
||||||
fun buildHabitList(): HabitList
|
fun buildHabitList(): HabitList
|
||||||
fun buildScoreList(): ScoreList
|
fun buildScoreList(): ScoreList
|
||||||
fun buildStreakList(): StreakList
|
fun buildStreakList(): StreakList
|
||||||
|
|||||||
@@ -22,6 +22,7 @@ package org.isoron.uhabits.core.models.memory;
|
|||||||
import androidx.annotation.*;
|
import androidx.annotation.*;
|
||||||
|
|
||||||
import org.isoron.uhabits.core.models.*;
|
import org.isoron.uhabits.core.models.*;
|
||||||
|
import org.isoron.uhabits.core.utils.*;
|
||||||
|
|
||||||
import java.util.*;
|
import java.util.*;
|
||||||
|
|
||||||
@@ -186,8 +187,9 @@ public class MemoryHabitList extends HabitList
|
|||||||
return h1.isNumerical() ? -1 : 1;
|
return h1.isNumerical() ? -1 : 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
Integer v1 = Objects.requireNonNull(h1.getComputedEntries().getToday()).getValue();
|
Timestamp today = DateUtils.getTodayWithOffset();
|
||||||
Integer v2 = Objects.requireNonNull(h2.getComputedEntries().getToday()).getValue();
|
Integer v1 = h1.getComputedEntries().get(today).getValue();
|
||||||
|
Integer v2 = h2.getComputedEntries().get(today).getValue();
|
||||||
|
|
||||||
return v2.compareTo(v1);
|
return v2.compareTo(v1);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -23,7 +23,6 @@ import org.isoron.uhabits.core.models.*
|
|||||||
class MemoryModelFactory : ModelFactory {
|
class MemoryModelFactory : ModelFactory {
|
||||||
override fun buildNewComputedEntries() = Entries()
|
override fun buildNewComputedEntries() = Entries()
|
||||||
override fun buildOriginalEntries() = Entries()
|
override fun buildOriginalEntries() = Entries()
|
||||||
override fun buildEntryList() = EntryList()
|
|
||||||
override fun buildHabitList() = MemoryHabitList()
|
override fun buildHabitList() = MemoryHabitList()
|
||||||
override fun buildScoreList() = MemoryScoreList()
|
override fun buildScoreList() = MemoryScoreList()
|
||||||
override fun buildStreakList() = MemoryStreakList()
|
override fun buildStreakList() = MemoryStreakList()
|
||||||
|
|||||||
@@ -35,7 +35,6 @@ class SQLModelFactory
|
|||||||
) : ModelFactory {
|
) : ModelFactory {
|
||||||
override fun buildOriginalEntries() = SQLiteEntries(database)
|
override fun buildOriginalEntries() = SQLiteEntries(database)
|
||||||
override fun buildNewComputedEntries() = Entries()
|
override fun buildNewComputedEntries() = Entries()
|
||||||
override fun buildEntryList() = EntryList()
|
|
||||||
override fun buildHabitList() = SQLiteHabitList(this)
|
override fun buildHabitList() = SQLiteHabitList(this)
|
||||||
override fun buildScoreList() = MemoryScoreList()
|
override fun buildScoreList() = MemoryScoreList()
|
||||||
override fun buildStreakList() = MemoryStreakList()
|
override fun buildStreakList() = MemoryStreakList()
|
||||||
|
|||||||
@@ -67,6 +67,7 @@ public class HabitFixtures
|
|||||||
for (int mark : marks)
|
for (int mark : marks)
|
||||||
habit.getOriginalEntries().add(new Entry(today.minus(mark), YES_MANUAL));
|
habit.getOriginalEntries().add(new Entry(today.minus(mark), YES_MANUAL));
|
||||||
|
|
||||||
|
habit.recompute();
|
||||||
return habit;
|
return habit;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -92,6 +93,7 @@ public class HabitFixtures
|
|||||||
habit.getOriginalEntries().add(new Entry(timestamp, values[i]));
|
habit.getOriginalEntries().add(new Entry(timestamp, values[i]));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
habit.recompute();
|
||||||
return habit;
|
return habit;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -128,6 +130,7 @@ public class HabitFixtures
|
|||||||
habit.getOriginalEntries().add(new Entry(timestamp, values[i]));
|
habit.getOriginalEntries().add(new Entry(timestamp, values[i]));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
habit.recompute();
|
||||||
return habit;
|
return habit;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -148,6 +151,7 @@ public class HabitFixtures
|
|||||||
timestamp = timestamp.minus(1);
|
timestamp = timestamp.minus(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
habit.recompute();
|
||||||
return habit;
|
return habit;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -26,6 +26,7 @@ import org.isoron.uhabits.core.commands.*;
|
|||||||
import org.isoron.uhabits.core.models.*;
|
import org.isoron.uhabits.core.models.*;
|
||||||
import org.isoron.uhabits.core.preferences.*;
|
import org.isoron.uhabits.core.preferences.*;
|
||||||
import org.isoron.uhabits.core.tasks.*;
|
import org.isoron.uhabits.core.tasks.*;
|
||||||
|
import org.isoron.uhabits.core.utils.*;
|
||||||
|
|
||||||
import java.util.*;
|
import java.util.*;
|
||||||
|
|
||||||
@@ -178,7 +179,8 @@ public class NotificationTray
|
|||||||
@Override
|
@Override
|
||||||
public void doInBackground()
|
public void doInBackground()
|
||||||
{
|
{
|
||||||
todayValue = habit.getComputedEntries().getTodayValue();
|
Timestamp today = DateUtils.getTodayWithOffset();
|
||||||
|
todayValue = habit.getComputedEntries().get(today).getValue();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
@@ -82,8 +82,8 @@ public class ListHabitsBehavior
|
|||||||
|
|
||||||
public void onEdit(@NonNull Habit habit, Timestamp timestamp)
|
public void onEdit(@NonNull Habit habit, Timestamp timestamp)
|
||||||
{
|
{
|
||||||
EntryList entries = habit.getComputedEntries();
|
Entries entries = habit.getComputedEntries();
|
||||||
double oldValue = entries.getValues(timestamp, timestamp)[0];
|
double oldValue = entries.get(timestamp).getValue();
|
||||||
|
|
||||||
screen.showNumberPicker(oldValue / 1000, habit.getUnit(), newValue ->
|
screen.showNumberPicker(oldValue / 1000, habit.getUnit(), newValue ->
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -23,6 +23,7 @@ import org.isoron.uhabits.core.models.*
|
|||||||
import org.isoron.uhabits.core.preferences.*
|
import org.isoron.uhabits.core.preferences.*
|
||||||
import org.isoron.uhabits.core.ui.callbacks.*
|
import org.isoron.uhabits.core.ui.callbacks.*
|
||||||
import org.isoron.uhabits.core.ui.screens.habits.list.*
|
import org.isoron.uhabits.core.ui.screens.habits.list.*
|
||||||
|
import kotlin.math.*
|
||||||
|
|
||||||
class ShowHabitBehavior(
|
class ShowHabitBehavior(
|
||||||
private val habitList: HabitList,
|
private val habitList: HabitList,
|
||||||
@@ -57,9 +58,9 @@ class ShowHabitBehavior(
|
|||||||
override fun onToggleEntry(timestamp: Timestamp, value: Int) {
|
override fun onToggleEntry(timestamp: Timestamp, value: Int) {
|
||||||
if (habit.isNumerical) {
|
if (habit.isNumerical) {
|
||||||
val entries = habit.computedEntries
|
val entries = habit.computedEntries
|
||||||
val oldValue = entries.getValues(timestamp, timestamp)[0].toDouble()
|
val oldValue = entries.get(timestamp).value
|
||||||
screen.showNumberPicker(oldValue / 1000, habit.unit) { newValue: Double ->
|
screen.showNumberPicker(oldValue / 1000.0, habit.unit) { newValue: Double ->
|
||||||
val thousands = Math.round(newValue * 1000).toInt()
|
val thousands = (newValue * 1000).roundToInt()
|
||||||
commandRunner.execute(
|
commandRunner.execute(
|
||||||
CreateRepetitionCommand(
|
CreateRepetitionCommand(
|
||||||
habitList,
|
habitList,
|
||||||
|
|||||||
@@ -137,7 +137,7 @@ class EntriesTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun testGroupBy() {
|
fun testGroupByNumerical() {
|
||||||
val offsets = intArrayOf(
|
val offsets = intArrayOf(
|
||||||
0, 5, 9, 15, 17, 21, 23, 27, 28, 35, 41, 45, 47, 53, 56, 62, 70, 73, 78,
|
0, 5, 9, 15, 17, 21, 23, 27, 28, 35, 41, 45, 47, 53, 56, 62, 70, 73, 78,
|
||||||
83, 86, 94, 101, 106, 113, 114, 120, 126, 130, 133, 141, 143, 148, 151, 157, 164,
|
83, 86, 94, 101, 106, 113, 114, 120, 126, 130, 133, 141, 143, 148, 151, 157, 164,
|
||||||
@@ -191,6 +191,52 @@ class EntriesTest {
|
|||||||
assertThat(byYear[1], equalTo(Entry(Timestamp.from(2013, Calendar.JANUARY, 1), 16172)))
|
assertThat(byYear[1], equalTo(Entry(Timestamp.from(2013, Calendar.JANUARY, 1), 16172)))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun testGroupByBoolean() {
|
||||||
|
val offsets = intArrayOf(
|
||||||
|
0, 5, 9, 15, 17, 21, 23, 27, 28, 35, 41, 45, 47, 53, 56, 62, 70, 73, 78,
|
||||||
|
83, 86, 94, 101, 106, 113, 114, 120, 126, 130, 133, 141, 143, 148, 151, 157, 164,
|
||||||
|
166, 171, 173, 176, 179, 183, 191, 259, 264, 268, 270, 275, 282, 284, 289, 295,
|
||||||
|
302, 306, 310, 315, 323, 325, 328, 335, 343, 349, 351, 353, 357, 359, 360, 367,
|
||||||
|
372, 376, 380, 385, 393, 400, 404, 412, 415, 418, 422, 425, 433, 437, 444, 449,
|
||||||
|
455, 460, 462, 465, 470, 471, 479, 481, 485, 489, 494, 495, 500, 501, 503, 507)
|
||||||
|
|
||||||
|
val reference = Timestamp.from(2014, Calendar.JUNE, 1)
|
||||||
|
val entries = Entries()
|
||||||
|
offsets.indices.forEach {
|
||||||
|
entries.add(Entry(reference.minus(offsets[it]), YES_MANUAL))
|
||||||
|
}
|
||||||
|
|
||||||
|
val byMonth = entries.groupBy(
|
||||||
|
field = DateUtils.TruncateField.MONTH,
|
||||||
|
firstWeekday = Calendar.SATURDAY,
|
||||||
|
isNumerical = false,
|
||||||
|
)
|
||||||
|
assertThat(byMonth.size, equalTo(17))
|
||||||
|
assertThat(byMonth[0], equalTo(Entry(Timestamp.from(2014, Calendar.JUNE, 1), 1)))
|
||||||
|
assertThat(byMonth[6], equalTo(Entry(Timestamp.from(2013, Calendar.DECEMBER, 1), 7)))
|
||||||
|
assertThat(byMonth[12], equalTo(Entry(Timestamp.from(2013, Calendar.MAY, 1), 6)))
|
||||||
|
|
||||||
|
val byQuarter = entries.groupBy(
|
||||||
|
field = DateUtils.TruncateField.QUARTER,
|
||||||
|
firstWeekday = Calendar.SATURDAY,
|
||||||
|
isNumerical = true,
|
||||||
|
)
|
||||||
|
assertThat(byQuarter.size, equalTo(6))
|
||||||
|
assertThat(byQuarter[0], equalTo(Entry(Timestamp.from(2014, Calendar.APRIL, 1), 30)))
|
||||||
|
assertThat(byQuarter[3], equalTo(Entry(Timestamp.from(2013, Calendar.JULY, 1), 34)))
|
||||||
|
assertThat(byQuarter[5], equalTo(Entry(Timestamp.from(2013, Calendar.JANUARY, 1), 40)))
|
||||||
|
|
||||||
|
val byYear = entries.groupBy(
|
||||||
|
field = DateUtils.TruncateField.YEAR,
|
||||||
|
firstWeekday = Calendar.SATURDAY,
|
||||||
|
isNumerical = true,
|
||||||
|
)
|
||||||
|
assertThat(byYear.size, equalTo(2))
|
||||||
|
assertThat(byYear[0], equalTo(Entry(Timestamp.from(2014, Calendar.JANUARY, 1), 68)))
|
||||||
|
assertThat(byYear[1], equalTo(Entry(Timestamp.from(2013, Calendar.JANUARY, 1), 132)))
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun testAddFromInterval() {
|
fun testAddFromInterval() {
|
||||||
val entries = listOf(
|
val entries = listOf(
|
||||||
|
|||||||
@@ -74,6 +74,7 @@ public class HabitTest extends BaseUnitTest
|
|||||||
Habit h = modelFactory.buildHabit();
|
Habit h = modelFactory.buildHabit();
|
||||||
assertFalse(h.isCompletedToday());
|
assertFalse(h.isCompletedToday());
|
||||||
h.getOriginalEntries().add(new Entry(getToday(), Entry.YES_MANUAL));
|
h.getOriginalEntries().add(new Entry(getToday(), Entry.YES_MANUAL));
|
||||||
|
h.recompute();
|
||||||
assertTrue(h.isCompletedToday());
|
assertTrue(h.isCompletedToday());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -128,6 +128,7 @@ public class ScoreListTest extends BaseUnitTest
|
|||||||
addSkip(5);
|
addSkip(5);
|
||||||
addSkip(10);
|
addSkip(10);
|
||||||
addSkip(11);
|
addSkip(11);
|
||||||
|
habit.recompute();
|
||||||
|
|
||||||
double expectedValues[] = {
|
double expectedValues[] = {
|
||||||
0.596033,
|
0.596033,
|
||||||
@@ -163,6 +164,7 @@ public class ScoreListTest extends BaseUnitTest
|
|||||||
{
|
{
|
||||||
check(5);
|
check(5);
|
||||||
addSkip(4);
|
addSkip(4);
|
||||||
|
habit.recompute();
|
||||||
|
|
||||||
double[] expectedValues = {
|
double[] expectedValues = {
|
||||||
0.041949,
|
0.041949,
|
||||||
@@ -261,17 +263,20 @@ public class ScoreListTest extends BaseUnitTest
|
|||||||
habit = fixtures.createEmptyHabit();
|
habit = fixtures.createEmptyHabit();
|
||||||
habit.setFrequency(Frequency.DAILY);
|
habit.setFrequency(Frequency.DAILY);
|
||||||
for (int i = 0; i < 90; i++) check(i);
|
for (int i = 0; i < 90; i++) check(i);
|
||||||
|
habit.recompute();
|
||||||
assertThat(habit.getScores().getTodayValue(), greaterThan(0.99));
|
assertThat(habit.getScores().getTodayValue(), greaterThan(0.99));
|
||||||
|
|
||||||
// Weekly habits should achieve at least 99% in 9 months
|
// Weekly habits should achieve at least 99% in 9 months
|
||||||
habit = fixtures.createEmptyHabit();
|
habit = fixtures.createEmptyHabit();
|
||||||
habit.setFrequency(Frequency.WEEKLY);
|
habit.setFrequency(Frequency.WEEKLY);
|
||||||
for (int i = 0; i < 39; i++) check(7 * i);
|
for (int i = 0; i < 39; i++) check(7 * i);
|
||||||
|
habit.recompute();
|
||||||
assertThat(habit.getScores().getTodayValue(), greaterThan(0.99));
|
assertThat(habit.getScores().getTodayValue(), greaterThan(0.99));
|
||||||
|
|
||||||
// Monthly habits should achieve at least 99% in 18 months
|
// Monthly habits should achieve at least 99% in 18 months
|
||||||
habit.setFrequency(new Frequency(1, 30));
|
habit.setFrequency(new Frequency(1, 30));
|
||||||
for (int i = 0; i < 18; i++) check(30 * i);
|
for (int i = 0; i < 18; i++) check(30 * i);
|
||||||
|
habit.recompute();
|
||||||
assertThat(habit.getScores().getTodayValue(), greaterThan(0.99));
|
assertThat(habit.getScores().getTodayValue(), greaterThan(0.99));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -349,6 +354,7 @@ public class ScoreListTest extends BaseUnitTest
|
|||||||
for (int i = 0; i < values.size(); i++)
|
for (int i = 0; i < values.size(); i++)
|
||||||
if (values.get(i) == YES_MANUAL)
|
if (values.get(i) == YES_MANUAL)
|
||||||
entries.add(new Entry(today.minus(i), YES_MANUAL));
|
entries.add(new Entry(today.minus(i), YES_MANUAL));
|
||||||
|
habit.recompute();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void addSkip(final int day)
|
private void addSkip(final int day)
|
||||||
|
|||||||
@@ -48,6 +48,7 @@ public class StreakListTest extends BaseUnitTest
|
|||||||
super.setUp();
|
super.setUp();
|
||||||
habit = fixtures.createLongHabit();
|
habit = fixtures.createLongHabit();
|
||||||
habit.setFrequency(Frequency.DAILY);
|
habit.setFrequency(Frequency.DAILY);
|
||||||
|
habit.recompute();
|
||||||
|
|
||||||
streaks = habit.getStreaks();
|
streaks = habit.getStreaks();
|
||||||
streaks.rebuild();
|
streaks.rebuild();
|
||||||
|
|||||||
@@ -104,8 +104,7 @@ public class HabitCardListCacheTest extends BaseUnitTest
|
|||||||
|
|
||||||
Timestamp today = DateUtils.getToday();
|
Timestamp today = DateUtils.getToday();
|
||||||
int[] actualCheckmarks = cache.getCheckmarks(h.getId());
|
int[] actualCheckmarks = cache.getCheckmarks(h.getId());
|
||||||
int[] expectedCheckmarks =
|
int[] expectedCheckmarks = h.getComputedEntries().getValues(today.minus(9), today);
|
||||||
h.getComputedEntries().getValues(today.minus(9), today);
|
|
||||||
|
|
||||||
assertThat(actualCheckmarks, equalTo(expectedCheckmarks));
|
assertThat(actualCheckmarks, equalTo(expectedCheckmarks));
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -86,7 +86,8 @@ public class ListHabitsBehaviorTest extends BaseUnitTest
|
|||||||
behavior.onEdit(habit2, DateUtils.getToday());
|
behavior.onEdit(habit2, DateUtils.getToday());
|
||||||
verify(screen).showNumberPicker(eq(0.1), eq("miles"), picker.capture());
|
verify(screen).showNumberPicker(eq(0.1), eq("miles"), picker.capture());
|
||||||
picker.getValue().onNumberPicked(100);
|
picker.getValue().onNumberPicked(100);
|
||||||
assertThat(habit2.getComputedEntries().getTodayValue(), equalTo(100000));
|
Timestamp today = DateUtils.getTodayWithOffset();
|
||||||
|
assertThat(habit2.getComputedEntries().get(today).getValue(), equalTo(100000));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
|||||||
@@ -107,6 +107,7 @@ public class WidgetBehaviorTest extends BaseUnitTest
|
|||||||
{
|
{
|
||||||
habit = fixtures.createNumericalHabit();
|
habit = fixtures.createNumericalHabit();
|
||||||
habit.getOriginalEntries().add(new Entry(timestamp, 500));
|
habit.getOriginalEntries().add(new Entry(timestamp, 500));
|
||||||
|
habit.recompute();
|
||||||
|
|
||||||
behavior.onIncrement(habit, timestamp, 100);
|
behavior.onIncrement(habit, timestamp, 100);
|
||||||
verify(commandRunner).execute(
|
verify(commandRunner).execute(
|
||||||
@@ -121,6 +122,7 @@ public class WidgetBehaviorTest extends BaseUnitTest
|
|||||||
{
|
{
|
||||||
habit = fixtures.createNumericalHabit();
|
habit = fixtures.createNumericalHabit();
|
||||||
habit.getOriginalEntries().add(new Entry(timestamp, 500));
|
habit.getOriginalEntries().add(new Entry(timestamp, 500));
|
||||||
|
habit.recompute();
|
||||||
|
|
||||||
behavior.onDecrement(habit, timestamp, 100);
|
behavior.onDecrement(habit, timestamp, 100);
|
||||||
verify(commandRunner).execute(
|
verify(commandRunner).execute(
|
||||||
|
|||||||
Reference in New Issue
Block a user