From 11863cd7b0ef1a9af3d9c62750d077bcc5db7b01 Mon Sep 17 00:00:00 2001 From: "Alinson S. Xavier" Date: Tue, 29 Dec 2020 09:38:29 -0600 Subject: [PATCH] Simplify ScoreList --- .../common/views/ScoreChartTest.java | 6 +- .../habits/list/views/HabitCardViewTest.kt | 5 +- .../views/CheckmarkWidgetViewTest.java | 5 +- .../habits/show/views/OverviewCard.kt | 6 +- .../activities/habits/show/views/ScoreCard.kt | 5 +- .../isoron/uhabits/widgets/CheckmarkWidget.kt | 2 +- .../org/isoron/uhabits/widgets/ScoreWidget.kt | 6 +- .../uhabits/core/io/HabitsCSVExporter.java | 25 +- .../isoron/uhabits/core/models/EntryList.kt | 4 +- .../org/isoron/uhabits/core/models/Habit.kt | 2 +- .../isoron/uhabits/core/models/HabitList.java | 1 - .../org/isoron/uhabits/core/models/Score.kt | 4 +- .../isoron/uhabits/core/models/ScoreList.java | 262 +++--------------- .../core/models/memory/MemoryHabitList.java | 7 +- .../habits/list/HabitCardListCache.java | 8 +- .../core/commands/EditHabitCommandTest.java | 26 +- .../core/io/HabitsCSVExporterTest.java | 26 +- .../uhabits/core/models/ScoreListTest.java | 118 ++------ .../habits/list/HabitCardListCacheTest.java | 6 +- .../core/ui/widgets/WidgetBehaviorTest.java | 30 +- 20 files changed, 161 insertions(+), 393 deletions(-) diff --git a/android/uhabits-android/src/androidTest/java/org/isoron/uhabits/activities/common/views/ScoreChartTest.java b/android/uhabits-android/src/androidTest/java/org/isoron/uhabits/activities/common/views/ScoreChartTest.java index f07b591fd..2bccb6a00 100644 --- a/android/uhabits-android/src/androidTest/java/org/isoron/uhabits/activities/common/views/ScoreChartTest.java +++ b/android/uhabits-android/src/androidTest/java/org/isoron/uhabits/activities/common/views/ScoreChartTest.java @@ -52,8 +52,12 @@ public class ScoreChartTest extends BaseViewTest fixtures.purgeHabits(habitList); habit = fixtures.createLongHabit(); + Timestamp today = DateUtils.getTodayWithOffset(); + List known = habit.getComputedEntries().getKnown(); + Timestamp oldest = known.get(known.size() - 1).getTimestamp(); + view = new ScoreChart(targetContext); - view.setScores(habit.getScores().toList()); + view.setScores(habit.getScores().getByInterval(oldest, today)); view.setColor(PaletteUtilsKt.toThemedAndroidColor(habit.getColor(), targetContext)); view.setBucketSize(7); measureView(view, dpToPixels(300), dpToPixels(200)); diff --git a/android/uhabits-android/src/androidTest/java/org/isoron/uhabits/activities/habits/list/views/HabitCardViewTest.kt b/android/uhabits-android/src/androidTest/java/org/isoron/uhabits/activities/habits/list/views/HabitCardViewTest.kt index fd9d1058b..341763c8b 100644 --- a/android/uhabits-android/src/androidTest/java/org/isoron/uhabits/activities/habits/list/views/HabitCardViewTest.kt +++ b/android/uhabits-android/src/androidTest/java/org/isoron/uhabits/activities/habits/list/views/HabitCardViewTest.kt @@ -23,6 +23,7 @@ import androidx.test.ext.junit.runners.* import androidx.test.filters.* import org.isoron.uhabits.* import org.isoron.uhabits.core.models.* +import org.isoron.uhabits.core.utils.* import org.junit.* import org.junit.runner.* @@ -41,10 +42,12 @@ class HabitCardViewTest : BaseViewTest() { habit1 = fixtures.createLongHabit() habit2 = fixtures.createLongNumericalHabit() + val today = DateUtils.getTodayWithOffset() + view = component.getHabitCardViewFactory().create().apply { habit = habit1 values = habit1.computedEntries.getAllValues() - score = habit1.scores.todayValue + score = habit1.scores.get(today).value isSelected = false buttonCount = 5 } diff --git a/android/uhabits-android/src/androidTest/java/org/isoron/uhabits/widgets/views/CheckmarkWidgetViewTest.java b/android/uhabits-android/src/androidTest/java/org/isoron/uhabits/widgets/views/CheckmarkWidgetViewTest.java index b23010bb2..797ca466f 100644 --- a/android/uhabits-android/src/androidTest/java/org/isoron/uhabits/widgets/views/CheckmarkWidgetViewTest.java +++ b/android/uhabits-android/src/androidTest/java/org/isoron/uhabits/widgets/views/CheckmarkWidgetViewTest.java @@ -47,11 +47,12 @@ public class CheckmarkWidgetViewTest extends BaseViewTest setTheme(R.style.WidgetTheme); Habit habit = fixtures.createShortHabit(); + Timestamp today = DateUtils.getTodayWithOffset(); + view = new CheckmarkWidgetView(targetContext); - double score = habit.getScores().getTodayValue(); + double score = habit.getScores().get(today).getValue(); float percentage = (float) score; - Timestamp today = DateUtils.getTodayWithOffset(); view.setActiveColor(PaletteUtils.getAndroidTestColor(0)); view.setEntryState(habit.getComputedEntries().get(today).getValue()); view.setEntryValue(habit.getComputedEntries().get(today).getValue()); diff --git a/android/uhabits-android/src/main/java/org/isoron/uhabits/activities/habits/show/views/OverviewCard.kt b/android/uhabits-android/src/main/java/org/isoron/uhabits/activities/habits/show/views/OverviewCard.kt index 88776c3d6..2e1e2dfdb 100644 --- a/android/uhabits-android/src/main/java/org/isoron/uhabits/activities/habits/show/views/OverviewCard.kt +++ b/android/uhabits-android/src/main/java/org/isoron/uhabits/activities/habits/show/views/OverviewCard.kt @@ -72,9 +72,9 @@ class OverviewCardPresenter(val habit: Habit) { val lastMonth = today.minus(30) val lastYear = today.minus(365) val scores = habit.scores - val scoreToday = scores.todayValue.toFloat() - val scoreLastMonth = scores.getValue(lastMonth).toFloat() - val scoreLastYear = scores.getValue(lastYear).toFloat() + val scoreToday = scores.get(today).value.toFloat() + val scoreLastMonth = scores.get(lastMonth).value.toFloat() + val scoreLastYear = scores.get(lastYear).value.toFloat() val totalCount = habit.originalEntries.getKnown() .filter { it.value == YES_MANUAL } .count() diff --git a/android/uhabits-android/src/main/java/org/isoron/uhabits/activities/habits/show/views/ScoreCard.kt b/android/uhabits-android/src/main/java/org/isoron/uhabits/activities/habits/show/views/ScoreCard.kt index 406deb8f1..f22febedb 100644 --- a/android/uhabits-android/src/main/java/org/isoron/uhabits/activities/habits/show/views/ScoreCard.kt +++ b/android/uhabits-android/src/main/java/org/isoron/uhabits/activities/habits/show/views/ScoreCard.kt @@ -79,7 +79,10 @@ class ScoreCardPresenter( fun present(spinnerPosition: Int): ScoreCardViewModel { val bucketSize = BUCKET_SIZES[spinnerPosition] val scoreList = habit.scores - val scores = if (bucketSize == 1) scoreList.toList() + val today = DateUtils.getTodayWithOffset() + val oldest = habit.computedEntries.getKnown().lastOrNull()?.timestamp ?: today + + val scores = if (bucketSize == 1) scoreList.getByInterval(oldest, today) else scoreList.groupBy(getTruncateField(bucketSize), firstWeekday) return ScoreCardViewModel( color = habit.color, diff --git a/android/uhabits-android/src/main/java/org/isoron/uhabits/widgets/CheckmarkWidget.kt b/android/uhabits-android/src/main/java/org/isoron/uhabits/widgets/CheckmarkWidget.kt index 9203e442c..c09ac580c 100644 --- a/android/uhabits-android/src/main/java/org/isoron/uhabits/widgets/CheckmarkWidget.kt +++ b/android/uhabits-android/src/main/java/org/isoron/uhabits/widgets/CheckmarkWidget.kt @@ -54,7 +54,7 @@ open class CheckmarkWidget( } else { setEntryState(habit.computedEntries.get(today).value) } - setPercentage(habit.scores.todayValue.toFloat()) + setPercentage(habit.scores.get(today).value.toFloat()) refresh() } } diff --git a/android/uhabits-android/src/main/java/org/isoron/uhabits/widgets/ScoreWidget.kt b/android/uhabits-android/src/main/java/org/isoron/uhabits/widgets/ScoreWidget.kt index 3c73f3f13..0efc7c496 100644 --- a/android/uhabits-android/src/main/java/org/isoron/uhabits/widgets/ScoreWidget.kt +++ b/android/uhabits-android/src/main/java/org/isoron/uhabits/widgets/ScoreWidget.kt @@ -24,6 +24,7 @@ import android.view.* import org.isoron.uhabits.activities.common.views.* import org.isoron.uhabits.activities.habits.show.views.* import org.isoron.uhabits.core.models.* +import org.isoron.uhabits.core.utils.* import org.isoron.uhabits.utils.* import org.isoron.uhabits.widgets.views.* @@ -38,8 +39,11 @@ class ScoreWidget( override fun refreshData(view: View) { val size = ScoreCardPresenter.BUCKET_SIZES[prefs.scoreCardSpinnerPosition] + val today = DateUtils.getTodayWithOffset() + val oldest = habit.computedEntries.getKnown().lastOrNull()?.timestamp ?: today + val scores = when(size) { - 1 -> habit.scores.toList() + 1 -> habit.scores.getByInterval(oldest, today) else -> habit.scores.groupBy(ScoreCardPresenter.getTruncateField(size), prefs.firstWeekday) } diff --git a/android/uhabits-core/src/main/java/org/isoron/uhabits/core/io/HabitsCSVExporter.java b/android/uhabits-core/src/main/java/org/isoron/uhabits/core/io/HabitsCSVExporter.java index 836199f19..5f809bd1d 100644 --- a/android/uhabits-core/src/main/java/org/isoron/uhabits/core/io/HabitsCSVExporter.java +++ b/android/uhabits-core/src/main/java/org/isoron/uhabits/core/io/HabitsCSVExporter.java @@ -125,20 +125,32 @@ public class HabitsCSVExporter new File(exportDirName + habitDirName).mkdirs(); generateDirs.add(habitDirName); - writeScores(habitDirName, h.getScores()); + writeScores(habitDirName, h); writeEntries(habitDirName, h.getComputedEntries()); } writeMultipleHabits(); } - private void writeScores(String habitDirName, ScoreList scores) + private void writeScores(String habitDirName, Habit habit) throws IOException { String path = habitDirName + "Scores.csv"; FileWriter out = new FileWriter(exportDirName + path); generateFilenames.add(path); - scores.writeCSV(out); + SimpleDateFormat dateFormat = DateFormats.getCSVDateFormat(); + + Timestamp today = DateUtils.getTodayWithOffset(); + Timestamp oldest = today; + List known = habit.getComputedEntries().getKnown(); + if(!known.isEmpty()) oldest = known.get(known.size() - 1).getTimestamp(); + + for (Score s : habit.getScores().getByInterval(oldest, today)) + { + String timestamp = dateFormat.format(s.getTimestamp().getUnixTime()); + String score = String.format(Locale.US, "%.4f", s.getValue()); + out.write(String.format("%s,%s\n", timestamp, score)); + } out.close(); } @@ -185,11 +197,11 @@ public class HabitsCSVExporter Timestamp newest = DateUtils.getToday(); List checkmarks = new ArrayList<>(); - List scores = new ArrayList<>(); + List> scores = new ArrayList<>(); for (Habit h : selectedHabits) { checkmarks.add(h.getComputedEntries().getValues(oldest, newest)); - scores.add(h.getScores().getValues(oldest, newest)); + scores.add(new ArrayList<>(h.getScores().getByInterval(oldest, newest))); } int days = oldest.daysUntil(newest); @@ -208,8 +220,7 @@ public class HabitsCSVExporter { checksWriter.write(String.valueOf(checkmarks.get(j)[i])); checksWriter.write(DELIMITER); - String score = - String.format(Locale.US, "%.4f", ((float) scores.get(j)[i])); + String score = String.format(Locale.US, "%.4f", scores.get(j).get(i).getValue()); scoresWriter.write(score); scoresWriter.write(DELIMITER); } diff --git a/android/uhabits-core/src/main/java/org/isoron/uhabits/core/models/EntryList.kt b/android/uhabits-core/src/main/java/org/isoron/uhabits/core/models/EntryList.kt index fa4cd3fa1..c2463103b 100644 --- a/android/uhabits-core/src/main/java/org/isoron/uhabits/core/models/EntryList.kt +++ b/android/uhabits-core/src/main/java/org/isoron/uhabits/core/models/EntryList.kt @@ -48,6 +48,7 @@ open class EntryList { */ open fun getByInterval(from: Timestamp, to: Timestamp): List { val result = mutableListOf() + if (from.isNewerThan(to)) return result var current = to while (current >= from) { result.add(get(current)) @@ -201,8 +202,9 @@ open class EntryList { fun getAllValues(): IntArray { val entries = getKnown() if (entries.isEmpty()) return IntArray(0) - val (fromTimestamp, _) = entries.last() + var (fromTimestamp, _) = entries.last() val toTimestamp = DateUtils.getTodayWithOffset() + if (fromTimestamp.isNewerThan(toTimestamp)) fromTimestamp = toTimestamp return getValues(fromTimestamp, toTimestamp) } diff --git a/android/uhabits-core/src/main/java/org/isoron/uhabits/core/models/Habit.kt b/android/uhabits-core/src/main/java/org/isoron/uhabits/core/models/Habit.kt index 012f62ca6..ef560dc6c 100644 --- a/android/uhabits-core/src/main/java/org/isoron/uhabits/core/models/Habit.kt +++ b/android/uhabits-core/src/main/java/org/isoron/uhabits/core/models/Habit.kt @@ -70,13 +70,13 @@ data class Habit( } fun recompute() { - scores.recompute() streaks.recompute() computedEntries.recomputeFrom( originalEntries = originalEntries, frequency = frequency, isNumerical = isNumerical, ) + scores.recompute() } fun copyFrom(other: Habit) { diff --git a/android/uhabits-core/src/main/java/org/isoron/uhabits/core/models/HabitList.java b/android/uhabits-core/src/main/java/org/isoron/uhabits/core/models/HabitList.java index 5c8e94871..631ad2918 100644 --- a/android/uhabits-core/src/main/java/org/isoron/uhabits/core/models/HabitList.java +++ b/android/uhabits-core/src/main/java/org/isoron/uhabits/core/models/HabitList.java @@ -179,7 +179,6 @@ public abstract class HabitList implements Iterable for (Habit h : this) { h.getStreaks().recompute(); - h.getScores().recompute(); } } diff --git a/android/uhabits-core/src/main/java/org/isoron/uhabits/core/models/Score.kt b/android/uhabits-core/src/main/java/org/isoron/uhabits/core/models/Score.kt index 23b9d2487..e1bc9e9ea 100644 --- a/android/uhabits-core/src/main/java/org/isoron/uhabits/core/models/Score.kt +++ b/android/uhabits-core/src/main/java/org/isoron/uhabits/core/models/Score.kt @@ -26,9 +26,11 @@ data class Score( ) { fun compareNewer(other: Score): Int { - return timestamp.compareTo(other.timestamp) + return this.timestamp.compareTo(other.timestamp) } + fun compareOlder(other: Score) = -compareNewer(other) + companion object { /** * Given the frequency of the habit, the previous score, and the value of diff --git a/android/uhabits-core/src/main/java/org/isoron/uhabits/core/models/ScoreList.java b/android/uhabits-core/src/main/java/org/isoron/uhabits/core/models/ScoreList.java index a9ea7caf5..772806142 100644 --- a/android/uhabits-core/src/main/java/org/isoron/uhabits/core/models/ScoreList.java +++ b/android/uhabits-core/src/main/java/org/isoron/uhabits/core/models/ScoreList.java @@ -23,17 +23,15 @@ import androidx.annotation.*; import org.isoron.uhabits.core.utils.*; -import java.io.*; -import java.text.*; import java.util.*; import static org.isoron.uhabits.core.models.Entry.*; -public class ScoreList implements Iterable +public class ScoreList { - ArrayList list = new ArrayList<>(); + private final HashMap list = new HashMap<>(); - protected Habit habit; + private Habit habit; public void setHabit(Habit habit) { @@ -41,45 +39,13 @@ public class ScoreList implements Iterable } /** - * Adds the given scores to the list. - *

- * This method should not be called by the application, since the scores are - * computed automatically from the list of repetitions. - * - * @param scores the scores to add. - */ - public void add(List scores) - { - list.addAll(scores); - Collections.sort(list, - (s1, s2) -> s2.getTimestamp().compareTo(s1.getTimestamp())); - } - - /** - * Returns the value of the score for today. - * - * @return value of today's score - */ - public double getTodayValue() - { - return getValue(DateUtils.getTodayWithOffset()); - } - - /** - * Returns the value of the score for a given day. - *

- * If the timestamp given happens before the first repetition of the habit - * then returns zero. - * - * @param timestamp the timestamp of a day - * @return score value for that day + * Returns the score for a given day. If the timestamp given happens before the first + * repetition of the habit or after the last computed score, returns a score with value zero. */ - public final synchronized double getValue(Timestamp timestamp) + public final synchronized Score get(Timestamp timestamp) { - compute(timestamp, timestamp); - Score s = getComputedByTimestamp(timestamp); - if (s == null) throw new IllegalStateException(); - return s.getValue(); + if (list.containsKey(timestamp)) return list.get(timestamp); + return new Score(timestamp, 0); } /** @@ -98,48 +64,19 @@ public class ScoreList implements Iterable public List getByInterval(@NonNull Timestamp fromTimestamp, @NonNull Timestamp toTimestamp) { - compute(fromTimestamp, toTimestamp); - - List filtered = new LinkedList<>(); - - for (Score s : list) + List result = new LinkedList<>(); + if (fromTimestamp.isNewerThan(toTimestamp)) return result; + Timestamp current = toTimestamp; + while(!current.isOlderThan(fromTimestamp)) { - if (s.getTimestamp().isNewerThan(toTimestamp) || - s.getTimestamp().isOlderThan(fromTimestamp)) continue; - filtered.add(s); + result.add(get(current)); + current = current.minus(1); } - - return filtered; - - } - - /** - * Returns the values of the scores that fall inside a certain interval - * of time. - *

- * 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 score - * @param to timestamp for the newest score - * @return values for the scores inside the given interval - */ - public final double[] getValues(Timestamp from, Timestamp to) - { - List scores = getByInterval(from, to); - double[] values = new double[scores.size()]; - - for (int i = 0; i < values.length; i++) - values[i] = scores.get(i).getValue(); - - return values; + return result; } public List groupBy(DateUtils.TruncateField field, int firstWeekday) { - computeAll(); HashMap> groups = getGroupedValues(field, firstWeekday); List scores = groupsToAvgScores(groups); Collections.sort(scores, (s1, s2) -> s2.compareNewer(s1)); @@ -149,155 +86,24 @@ public class ScoreList implements Iterable public void recompute() { list.clear(); - } - @Override - public Iterator iterator() - { - return toList().iterator(); - } - - /** - * Returns a Java list of scores, containing one score for each day, from - * the first repetition of the habit until today. - *

- * The scores are sorted by decreasing timestamp. The first score - * corresponds to today. - * - * @return list of scores - */ - public List toList() - { - computeAll(); - return new LinkedList<>(list); - } - - public void writeCSV(Writer out) throws IOException - { - computeAll(); - SimpleDateFormat dateFormat = DateFormats.getCSVDateFormat(); - - for (Score s : this) - { - String timestamp = dateFormat.format(s.getTimestamp().getUnixTime()); - String score = String.format(Locale.US, "%.4f", s.getValue()); - out.write(String.format("%s,%s\n", timestamp, score)); - } - } - - /** - * Computes and stores one score for each day inside the given interval. - *

- * Scores that have already been computed are skipped, therefore there is no - * harm in calling this function more times, or with larger intervals, than - * strictly needed. The endpoints of the interval are included. - *

- * This method assumes the list of computed scores has no holes. That is, if - * there is a score computed at time t1 and another at time t2, then every - * score between t1 and t2 is also computed. - * - * @param from timestamp of the beginning of the interval - * @param to timestamp of the end of the time interval - */ - protected synchronized void compute(@NonNull Timestamp from, - @NonNull Timestamp to) - { - Score newestComputed = getNewestComputed(); - Score oldestComputed = getOldestComputed(); - - if (newestComputed == null) - { - List entries = habit.getOriginalEntries().getKnown(); - if (!entries.isEmpty()) - from = Timestamp.oldest( - from, - entries.get(entries.size() - 1).getTimestamp()); - forceRecompute(from, to, 0); - } - else - { - if (oldestComputed == null) throw new IllegalStateException(); - forceRecompute(from, oldestComputed.getTimestamp().minus(1), 0); - forceRecompute(newestComputed.getTimestamp().plus(1), to, - newestComputed.getValue()); - } - } - - /** - * Computes and saves the scores that are missing since the first repetition - * of the habit. - */ - protected void computeAll() - { List entries = habit.getOriginalEntries().getKnown(); - if(entries.isEmpty()) return; + if (entries.isEmpty()) return; Entry oldest = entries.get(entries.size() - 1); Timestamp today = DateUtils.getTodayWithOffset(); - compute(oldest.getTimestamp(), today); - } - - /** - * Returns the score that has the given timestamp, if it has already been - * computed. If that score has not been computed yet, returns null. - * - * @param timestamp the timestamp of the score - * @return the score with given timestamp, or null not yet computed. - */ - @Nullable - protected Score getComputedByTimestamp(Timestamp timestamp) - { - for (Score s : list) - if (s.getTimestamp().equals(timestamp)) return s; - - return null; - } - - /** - * Returns the most recent score that has already been computed. If no score - * has been computed yet, returns null. - */ - @Nullable - protected Score getNewestComputed() - { - if (list.isEmpty()) return null; - return list.get(0); - - } - - /** - * Returns oldest score already computed. If no score has been computed yet, - * returns null. - */ - @Nullable - protected Score getOldestComputed() - { - if (list.isEmpty()) return null; - return list.get(list.size() - 1); - } - - /** - * Computes and stores one score for each day inside the given interval. - *

- * This function does not check if the scores have already been computed. If - * they have, then it stores duplicate scores, which is a bad thing. - * - * @param from timestamp of the beginning of the interval - * @param to timestamp of the end of the interval - * @param previousValue value of the score on the day immediately before the - * interval begins - */ - private void forceRecompute(@NonNull Timestamp from, - @NonNull Timestamp to, - double previousValue) - { - if (from.isNewerThan(to)) return; + Timestamp from = oldest.getTimestamp(); + if (from.isNewerThan(today)) return; double rollingSum = 0.0; int numerator = habit.getFrequency().getNumerator(); int denominator = habit.getFrequency().getDenominator(); final double freq = habit.getFrequency().toDouble(); - final int[] values = habit.getComputedEntries().getValues(from, to); + final Integer[] values = habit.getComputedEntries() + .getByInterval(from, today) + .stream() + .map(Entry::getValue) + .toArray(Integer[]::new); // For non-daily boolean habits, we double the numerator and the denominator to smooth // out irregular repetition schedules (for example, weekly habits performed on different @@ -308,15 +114,15 @@ public class ScoreList implements Iterable denominator *= 2; } - List scores = new LinkedList<>(); - + double previousValue = 0; for (int i = 0; i < values.length; i++) { int offset = values.length - i - 1; if (habit.isNumerical()) { rollingSum += values[offset]; - if (offset + denominator < values.length) { + if (offset + denominator < values.length) + { rollingSum -= values[offset + denominator]; } double percentageCompleted = Math.min(1, rollingSum / 1000 / habit.getTargetValue()); @@ -335,25 +141,25 @@ public class ScoreList implements Iterable previousValue = Score.compute(freq, previousValue, percentageCompleted); } } - scores.add(new Score(from.plus(i), previousValue)); + Timestamp timestamp = from.plus(i); + list.put(timestamp, new Score(timestamp, previousValue)); } - - add(scores); } + @NonNull private HashMap> getGroupedValues(DateUtils.TruncateField field, int firstWeekday) { HashMap> groups = new HashMap<>(); - for (Score s : this) + for (Score s : list.values()) { Timestamp groupTimestamp = new Timestamp( - DateUtils.truncate( - field, - s.getTimestamp().getUnixTime(), - firstWeekday)); + DateUtils.truncate( + field, + s.getTimestamp().getUnixTime(), + firstWeekday)); if (!groups.containsKey(groupTimestamp)) groups.put(groupTimestamp, new ArrayList<>()); diff --git a/android/uhabits-core/src/main/java/org/isoron/uhabits/core/models/memory/MemoryHabitList.java b/android/uhabits-core/src/main/java/org/isoron/uhabits/core/models/memory/MemoryHabitList.java index 2524c22a8..a253220c1 100644 --- a/android/uhabits-core/src/main/java/org/isoron/uhabits/core/models/memory/MemoryHabitList.java +++ b/android/uhabits-core/src/main/java/org/isoron/uhabits/core/models/memory/MemoryHabitList.java @@ -169,7 +169,12 @@ public class MemoryHabitList extends HabitList colorComparatorAsc.compare(h2, h1); Comparator scoreComparatorDesc = (h1, h2) -> - Double.compare(h1.getScores().getTodayValue(), h2.getScores().getTodayValue()); + { + Timestamp today = DateUtils.getTodayWithOffset(); + return Double.compare( + h1.getScores().get(today).getValue(), + h2.getScores().get(today).getValue()); + }; Comparator scoreComparatorAsc = (h1, h2) -> scoreComparatorDesc.compare(h2, h1); diff --git a/android/uhabits-core/src/main/java/org/isoron/uhabits/core/ui/screens/habits/list/HabitCardListCache.java b/android/uhabits-core/src/main/java/org/isoron/uhabits/core/ui/screens/habits/list/HabitCardListCache.java index 976348ebd..c9de67bf1 100644 --- a/android/uhabits-core/src/main/java/org/isoron/uhabits/core/ui/screens/habits/list/HabitCardListCache.java +++ b/android/uhabits-core/src/main/java/org/isoron/uhabits/core/ui/screens/habits/list/HabitCardListCache.java @@ -349,8 +349,8 @@ public class HabitCardListCache implements CommandRunner.Listener newData.copyScoresFrom(data); newData.copyCheckmarksFrom(data); - Timestamp dateTo = DateUtils.getTodayWithOffset(); - Timestamp dateFrom = dateTo.minus(checkmarkCount - 1); + Timestamp today = DateUtils.getTodayWithOffset(); + Timestamp dateFrom = today.minus(checkmarkCount - 1); if (runner != null) runner.publishProgress(this, -1); @@ -362,10 +362,10 @@ public class HabitCardListCache implements CommandRunner.Listener Long id = habit.getId(); if (targetId != null && !targetId.equals(id)) continue; - newData.scores.put(id, habit.getScores().getTodayValue()); + newData.scores.put(id, habit.getScores().get(today).getValue()); newData.checkmarks.put( id, - habit.getComputedEntries().getValues(dateFrom, dateTo)); + habit.getComputedEntries().getValues(dateFrom, today)); runner.publishProgress(this, position); } diff --git a/android/uhabits-core/src/test/java/org/isoron/uhabits/core/commands/EditHabitCommandTest.java b/android/uhabits-core/src/test/java/org/isoron/uhabits/core/commands/EditHabitCommandTest.java index 1243890d5..df16b60be 100644 --- a/android/uhabits-core/src/test/java/org/isoron/uhabits/core/commands/EditHabitCommandTest.java +++ b/android/uhabits-core/src/test/java/org/isoron/uhabits/core/commands/EditHabitCommandTest.java @@ -21,6 +21,7 @@ package org.isoron.uhabits.core.commands; import org.isoron.uhabits.core.*; import org.isoron.uhabits.core.models.*; +import org.isoron.uhabits.core.utils.*; import org.junit.*; import static org.hamcrest.MatcherAssert.*; @@ -34,6 +35,8 @@ public class EditHabitCommandTest extends BaseUnitTest private Habit modified; + private Timestamp today; + @Override @Before public void setUp() throws Exception @@ -43,12 +46,15 @@ public class EditHabitCommandTest extends BaseUnitTest habit = fixtures.createShortHabit(); habit.setName("original"); habit.setFrequency(Frequency.DAILY); + habit.recompute(); habitList.add(habit); modified = fixtures.createEmptyHabit(); modified.copyFrom(habit); modified.setName("modified"); habitList.add(modified); + + today = DateUtils.getTodayWithOffset(); } @Test @@ -56,27 +62,11 @@ public class EditHabitCommandTest extends BaseUnitTest { command = new EditHabitCommand(habitList, habit.getId(), modified); - double originalScore = habit.getScores().getTodayValue(); - assertThat(habit.getName(), equalTo("original")); - - command.run(); - assertThat(habit.getName(), equalTo("modified")); - assertThat(habit.getScores().getTodayValue(), equalTo(originalScore)); - } - - @Test - public void testExecute_withModifiedInterval() - { - modified.setFrequency(Frequency.TWO_TIMES_PER_WEEK); - command = - new EditHabitCommand(habitList, habit.getId(), modified); - - double originalScore = habit.getScores().getTodayValue(); + double originalScore = habit.getScores().get(today).getValue(); assertThat(habit.getName(), equalTo("original")); command.run(); assertThat(habit.getName(), equalTo("modified")); - assertThat(habit.getScores().getTodayValue(), - lessThan(originalScore)); + assertThat(habit.getScores().get(today).getValue(), equalTo(originalScore)); } } diff --git a/android/uhabits-core/src/test/java/org/isoron/uhabits/core/io/HabitsCSVExporterTest.java b/android/uhabits-core/src/test/java/org/isoron/uhabits/core/io/HabitsCSVExporterTest.java index ec9e56f28..d88b5e7a0 100644 --- a/android/uhabits-core/src/test/java/org/isoron/uhabits/core/io/HabitsCSVExporterTest.java +++ b/android/uhabits-core/src/test/java/org/isoron/uhabits/core/io/HabitsCSVExporterTest.java @@ -29,8 +29,7 @@ import java.nio.file.*; import java.util.*; import java.util.zip.*; -import static org.junit.Assert.assertNotNull; -import static org.junit.Assert.assertTrue; +import static org.junit.Assert.*; public class HabitsCSVExporterTest extends BaseUnitTest { @@ -100,6 +99,29 @@ public class HabitsCSVExporterTest extends BaseUnitTest zip.close(); } +// @Test +// public void test_writeCSV() throws IOException +// { +// Habit habit = fixtures.createShortHabit(); +// +// String expectedCSV = +// "2015-01-25,0.2557\n" + +// "2015-01-24,0.2226\n" + +// "2015-01-23,0.1991\n" + +// "2015-01-22,0.1746\n" + +// "2015-01-21,0.1379\n" + +// "2015-01-20,0.0995\n" + +// "2015-01-19,0.0706\n" + +// "2015-01-18,0.0515\n" + +// "2015-01-17,0.0315\n" + +// "2015-01-16,0.0107\n"; +// +// StringWriter writer = new StringWriter(); +// habit.getScores().writeCSV(writer); +// +// assertThat(writer.toString(), equalTo(expectedCSV)); +// } + private void assertPathExists(String s) { assertAbsolutePathExists( diff --git a/android/uhabits-core/src/test/java/org/isoron/uhabits/core/models/ScoreListTest.java b/android/uhabits-core/src/test/java/org/isoron/uhabits/core/models/ScoreListTest.java index 457708bbc..c1c5e50d4 100644 --- a/android/uhabits-core/src/test/java/org/isoron/uhabits/core/models/ScoreListTest.java +++ b/android/uhabits-core/src/test/java/org/isoron/uhabits/core/models/ScoreListTest.java @@ -38,61 +38,23 @@ public class ScoreListTest extends BaseUnitTest private Habit habit; + private Timestamp today; + @Override @Before public void setUp() throws Exception { super.setUp(); + today = DateUtils.getToday(); habit = fixtures.createEmptyHabit(); } - @Test - public void test_getAll() - { - check(0, 20); - - double expectedValues[] = { - 0.655747, - 0.636894, - 0.617008, - 0.596033, - 0.573910, - 0.550574, - 0.525961, - 0.500000, - 0.472617, - 0.443734, - 0.413270, - 0.381137, - 0.347244, - 0.311495, - 0.273788, - 0.234017, - 0.192067, - 0.147820, - 0.101149, - 0.051922, - }; - - int i = 0; - for (Score s : habit.getScores()) - assertThat(s.getValue(), closeTo(expectedValues[i++], E)); - } - - @Test - public void test_getTodayValue() - { - check(0, 20); - double actual = habit.getScores().getTodayValue(); - assertThat(actual, closeTo(0.655747, E)); - } - @Test public void test_getValue() { check(0, 20); - double expectedValues[] = { + double[] expectedValues = { 0.655747, 0.636894, 0.617008, @@ -179,26 +141,6 @@ public class ScoreListTest extends BaseUnitTest checkScoreValues(expectedValues); } - @Test - public void test_getValues() - { - check(0, 20); - - Timestamp today = DateUtils.getToday(); - Timestamp from = today.minus(4); - Timestamp to = today.minus(2); - - double[] expected = { - 0.617008, 0.596033, 0.573909, - }; - - double[] actual = habit.getScores().getValues(from, to); - assertThat(actual.length, equalTo(expected.length)); - - for (int i = 0; i < actual.length; i++) - assertThat(actual[i], closeTo(expected[i], E)); - } - @Test public void test_imperfectNonDaily() { @@ -217,12 +159,12 @@ public class ScoreListTest extends BaseUnitTest values.add(NO); } check(values); - assertThat(habit.getScores().getTodayValue(), closeTo(2/3.0, E)); + assertThat(habit.getScores().get(today).getValue(), closeTo(2/3.0, E)); // Missing 2 repetitions out of 4 per week, the score should converge to 50% habit.setFrequency(new Frequency(4, 7)); habit.recompute(); - assertThat(habit.getScores().getTodayValue(), closeTo(0.5, E)); + assertThat(habit.getScores().get(today).getValue(), closeTo(0.5, E)); } @Test @@ -253,7 +195,7 @@ public class ScoreListTest extends BaseUnitTest values.add(YES_MANUAL); } check(values); - assertThat(habit.getScores().getTodayValue(), closeTo(1.0, 1e-3)); + assertThat(habit.getScores().get(today).getValue(), closeTo(1.0, 1e-3)); } @Test @@ -264,20 +206,20 @@ public class ScoreListTest extends BaseUnitTest habit.setFrequency(Frequency.DAILY); for (int i = 0; i < 90; i++) check(i); habit.recompute(); - assertThat(habit.getScores().getTodayValue(), greaterThan(0.99)); + assertThat(habit.getScores().get(today).getValue(), greaterThan(0.99)); // Weekly habits should achieve at least 99% in 9 months habit = fixtures.createEmptyHabit(); habit.setFrequency(Frequency.WEEKLY); for (int i = 0; i < 39; i++) check(7 * i); habit.recompute(); - assertThat(habit.getScores().getTodayValue(), greaterThan(0.99)); + assertThat(habit.getScores().get(today).getValue(), greaterThan(0.99)); // Monthly habits should achieve at least 99% in 18 months habit.setFrequency(new Frequency(1, 30)); for (int i = 0; i < 18; i++) check(30 * i); habit.recompute(); - assertThat(habit.getScores().getTodayValue(), greaterThan(0.99)); + assertThat(habit.getScores().get(today).getValue(), greaterThan(0.99)); } @Test @@ -296,52 +238,26 @@ public class ScoreListTest extends BaseUnitTest @Test public void test_recompute() { - assertThat(habit.getScores().getTodayValue(), closeTo(0.0, E)); + assertThat(habit.getScores().get(today).getValue(), closeTo(0.0, E)); check(0, 2); - assertThat(habit.getScores().getTodayValue(), closeTo(0.101149, E)); + assertThat(habit.getScores().get(today).getValue(), closeTo(0.101149, E)); habit.setFrequency(new Frequency(1, 2)); - habit.getScores().recompute(); - - assertThat(habit.getScores().getTodayValue(), closeTo(0.054816, E)); - } + habit.recompute(); - @Test - public void test_writeCSV() throws IOException - { - Habit habit = fixtures.createShortHabit(); - - String expectedCSV = - "2015-01-25,0.2557\n" + - "2015-01-24,0.2226\n" + - "2015-01-23,0.1991\n" + - "2015-01-22,0.1746\n" + - "2015-01-21,0.1379\n" + - "2015-01-20,0.0995\n" + - "2015-01-19,0.0706\n" + - "2015-01-18,0.0515\n" + - "2015-01-17,0.0315\n" + - "2015-01-16,0.0107\n"; - - StringWriter writer = new StringWriter(); - habit.getScores().writeCSV(writer); - - assertThat(writer.toString(), equalTo(expectedCSV)); + assertThat(habit.getScores().get(today).getValue(), closeTo(0.054816, E)); } private void check(final int offset) { EntryList entries = habit.getOriginalEntries(); - Timestamp today = DateUtils.getToday(); entries.add(new Entry(today.minus(offset), YES_MANUAL)); } private void check(final int from, final int to) { EntryList entries = habit.getOriginalEntries(); - Timestamp today = DateUtils.getToday(); - for (int i = from; i < to; i++) entries.add(new Entry(today.minus(i), YES_MANUAL)); habit.recompute(); @@ -350,7 +266,6 @@ public class ScoreListTest extends BaseUnitTest private void check(ArrayList values) { EntryList entries = habit.getOriginalEntries(); - Timestamp today = DateUtils.getToday(); for (int i = 0; i < values.size(); i++) if (values.get(i) == YES_MANUAL) entries.add(new Entry(today.minus(i), YES_MANUAL)); @@ -360,17 +275,16 @@ public class ScoreListTest extends BaseUnitTest private void addSkip(final int day) { EntryList entries = habit.getOriginalEntries(); - Timestamp today = DateUtils.getToday(); entries.add(new Entry(today.minus(day), Entry.SKIP)); } private void checkScoreValues(double[] expectedValues) { - Timestamp current = DateUtils.getToday(); + Timestamp current = today; ScoreList scores = habit.getScores(); for (double expectedValue : expectedValues) { - assertThat(scores.getValue(current), closeTo(expectedValue, E)); + assertThat(scores.get(current).getValue(), closeTo(expectedValue, E)); current = current.minus(1); } } diff --git a/android/uhabits-core/src/test/java/org/isoron/uhabits/core/ui/screens/habits/list/HabitCardListCacheTest.java b/android/uhabits-core/src/test/java/org/isoron/uhabits/core/ui/screens/habits/list/HabitCardListCacheTest.java index bd69f7642..2cf3e979f 100644 --- a/android/uhabits-core/src/test/java/org/isoron/uhabits/core/ui/screens/habits/list/HabitCardListCacheTest.java +++ b/android/uhabits-core/src/test/java/org/isoron/uhabits/core/ui/screens/habits/list/HabitCardListCacheTest.java @@ -37,6 +37,8 @@ public class HabitCardListCacheTest extends BaseUnitTest private HabitCardListCache.Listener listener; + Timestamp today = DateUtils.getToday(); + @Override public void setUp() throws Exception { @@ -83,7 +85,6 @@ public class HabitCardListCacheTest extends BaseUnitTest public void testCommandListener_single() { Habit h2 = habitList.getByPosition(2); - Timestamp today = DateUtils.getToday(); commandRunner.run(new CreateRepetitionCommand(habitList, h2, today, Entry.NO)); verify(listener).onItemChanged(2); verify(listener).onRefreshFinished(); @@ -97,12 +98,11 @@ public class HabitCardListCacheTest extends BaseUnitTest Habit h = habitList.getByPosition(3); Assert.assertNotNull(h.getId()); - double score = h.getScores().getTodayValue(); + double score = h.getScores().get(today).getValue(); assertThat(cache.getHabitByPosition(3), equalTo(h)); assertThat(cache.getScore(h.getId()), equalTo(score)); - Timestamp today = DateUtils.getToday(); int[] actualCheckmarks = cache.getCheckmarks(h.getId()); int[] expectedCheckmarks = h.getComputedEntries().getValues(today.minus(9), today); diff --git a/android/uhabits-core/src/test/java/org/isoron/uhabits/core/ui/widgets/WidgetBehaviorTest.java b/android/uhabits-core/src/test/java/org/isoron/uhabits/core/ui/widgets/WidgetBehaviorTest.java index d91a14d4f..4786adae3 100644 --- a/android/uhabits-core/src/test/java/org/isoron/uhabits/core/ui/widgets/WidgetBehaviorTest.java +++ b/android/uhabits-core/src/test/java/org/isoron/uhabits/core/ui/widgets/WidgetBehaviorTest.java @@ -24,6 +24,7 @@ import org.isoron.uhabits.core.commands.*; import org.isoron.uhabits.core.models.*; import org.isoron.uhabits.core.preferences.*; import org.isoron.uhabits.core.ui.*; +import org.isoron.uhabits.core.utils.*; import org.junit.*; import java.util.*; @@ -43,7 +44,7 @@ public class WidgetBehaviorTest extends BaseUnitTest private Habit habit; - private Timestamp timestamp = new Timestamp(0L); + private Timestamp today; @Before @Override @@ -55,14 +56,15 @@ public class WidgetBehaviorTest extends BaseUnitTest notificationTray = mock(NotificationTray.class); preferences = mock(Preferences.class); behavior = new WidgetBehavior(habitList, commandRunner, notificationTray, preferences); + today = DateUtils.getTodayWithOffset(); } @Test public void testOnAddRepetition() { - behavior.onAddRepetition(habit, timestamp); + behavior.onAddRepetition(habit, today); verify(commandRunner).run( - new CreateRepetitionCommand(habitList, habit, timestamp, YES_MANUAL) + new CreateRepetitionCommand(habitList, habit, today, YES_MANUAL) ); verify(notificationTray).cancel(habit); verifyZeroInteractions(preferences); @@ -71,9 +73,9 @@ public class WidgetBehaviorTest extends BaseUnitTest @Test public void testOnRemoveRepetition() { - behavior.onRemoveRepetition(habit, timestamp); + behavior.onRemoveRepetition(habit, today); verify(commandRunner).run( - new CreateRepetitionCommand(habitList, habit, timestamp, NO) + new CreateRepetitionCommand(habitList, habit, today, NO) ); verify(notificationTray).cancel(habit); verifyZeroInteractions(preferences); @@ -91,11 +93,11 @@ public class WidgetBehaviorTest extends BaseUnitTest if(skipEnabled) nextValue = Entry.Companion.nextToggleValueWithSkip(currentValue); else nextValue = Entry.Companion.nextToggleValueWithoutSkip(currentValue); - habit.getOriginalEntries().add(new Entry(timestamp, currentValue)); - behavior.onToggleRepetition(habit, timestamp); + habit.getOriginalEntries().add(new Entry(today, currentValue)); + behavior.onToggleRepetition(habit, today); verify(preferences).isSkipEnabled(); verify(commandRunner).run( - new CreateRepetitionCommand(habitList, habit, timestamp, nextValue) + new CreateRepetitionCommand(habitList, habit, today, nextValue) ); verify(notificationTray).cancel(habit); reset(preferences, commandRunner, notificationTray); @@ -106,12 +108,12 @@ public class WidgetBehaviorTest extends BaseUnitTest public void testOnIncrement() { habit = fixtures.createNumericalHabit(); - habit.getOriginalEntries().add(new Entry(timestamp, 500)); + habit.getOriginalEntries().add(new Entry(today, 500)); habit.recompute(); - behavior.onIncrement(habit, timestamp, 100); + behavior.onIncrement(habit, today, 100); verify(commandRunner).run( - new CreateRepetitionCommand(habitList, habit, timestamp, 600) + new CreateRepetitionCommand(habitList, habit, today, 600) ); verify(notificationTray).cancel(habit); verifyZeroInteractions(preferences); @@ -121,12 +123,12 @@ public class WidgetBehaviorTest extends BaseUnitTest public void testOnDecrement() { habit = fixtures.createNumericalHabit(); - habit.getOriginalEntries().add(new Entry(timestamp, 500)); + habit.getOriginalEntries().add(new Entry(today, 500)); habit.recompute(); - behavior.onDecrement(habit, timestamp, 100); + behavior.onDecrement(habit, today, 100); verify(commandRunner).run( - new CreateRepetitionCommand(habitList, habit, timestamp, 400) + new CreateRepetitionCommand(habitList, habit, today, 400) ); verify(notificationTray).cancel(habit); verifyZeroInteractions(preferences);