mirror of
https://github.com/iSoron/uhabits.git
synced 2025-12-06 09:08:52 -06:00
Simplify ScoreList
This commit is contained in:
@@ -52,8 +52,12 @@ public class ScoreChartTest extends BaseViewTest
|
||||
fixtures.purgeHabits(habitList);
|
||||
habit = fixtures.createLongHabit();
|
||||
|
||||
Timestamp today = DateUtils.getTodayWithOffset();
|
||||
List<Entry> 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));
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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());
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
|
||||
@@ -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<Entry> 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<int[]> checkmarks = new ArrayList<>();
|
||||
List<double[]> scores = new ArrayList<>();
|
||||
List<ArrayList<Score>> 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);
|
||||
}
|
||||
|
||||
@@ -48,6 +48,7 @@ open class EntryList {
|
||||
*/
|
||||
open fun getByInterval(from: Timestamp, to: Timestamp): List<Entry> {
|
||||
val result = mutableListOf<Entry>()
|
||||
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)
|
||||
}
|
||||
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -179,7 +179,6 @@ public abstract class HabitList implements Iterable<Habit>
|
||||
for (Habit h : this)
|
||||
{
|
||||
h.getStreaks().recompute();
|
||||
h.getScores().recompute();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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<Score>
|
||||
public class ScoreList
|
||||
{
|
||||
ArrayList<Score> list = new ArrayList<>();
|
||||
private final HashMap<Timestamp, Score> list = new HashMap<>();
|
||||
|
||||
protected Habit habit;
|
||||
private Habit habit;
|
||||
|
||||
public void setHabit(Habit habit)
|
||||
{
|
||||
@@ -41,45 +39,13 @@ public class ScoreList implements Iterable<Score>
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds the given scores to the list.
|
||||
* <p>
|
||||
* 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.
|
||||
* 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 void add(List<Score> scores)
|
||||
public final synchronized Score get(Timestamp timestamp)
|
||||
{
|
||||
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.
|
||||
* <p>
|
||||
* 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
|
||||
*/
|
||||
public final synchronized double getValue(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<Score>
|
||||
public List<Score> getByInterval(@NonNull Timestamp fromTimestamp,
|
||||
@NonNull Timestamp toTimestamp)
|
||||
{
|
||||
compute(fromTimestamp, toTimestamp);
|
||||
|
||||
List<Score> filtered = new LinkedList<>();
|
||||
|
||||
for (Score s : list)
|
||||
List<Score> 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.
|
||||
* <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 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<Score> 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<Score> groupBy(DateUtils.TruncateField field, int firstWeekday)
|
||||
{
|
||||
computeAll();
|
||||
HashMap<Timestamp, ArrayList<Double>> groups = getGroupedValues(field, firstWeekday);
|
||||
List<Score> scores = groupsToAvgScores(groups);
|
||||
Collections.sort(scores, (s1, s2) -> s2.compareNewer(s1));
|
||||
@@ -149,155 +86,24 @@ public class ScoreList implements Iterable<Score>
|
||||
public void recompute()
|
||||
{
|
||||
list.clear();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Iterator<Score> 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.
|
||||
* <p>
|
||||
* The scores are sorted by decreasing timestamp. The first score
|
||||
* corresponds to today.
|
||||
*
|
||||
* @return list of scores
|
||||
*/
|
||||
public List<Score> 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.
|
||||
* <p>
|
||||
* 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.
|
||||
* <p>
|
||||
* 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<Entry> 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<Entry> 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.
|
||||
* <p>
|
||||
* 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<Score>
|
||||
denominator *= 2;
|
||||
}
|
||||
|
||||
List<Score> 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<Score>
|
||||
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<Timestamp, ArrayList<Double>> getGroupedValues(DateUtils.TruncateField field,
|
||||
int firstWeekday)
|
||||
{
|
||||
HashMap<Timestamp, ArrayList<Double>> 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<>());
|
||||
|
||||
@@ -169,7 +169,12 @@ public class MemoryHabitList extends HabitList
|
||||
colorComparatorAsc.compare(h2, h1);
|
||||
|
||||
Comparator<Habit> 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<Habit> scoreComparatorAsc = (h1, h2) ->
|
||||
scoreComparatorDesc.compare(h2, h1);
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
double originalScore = habit.getScores().get(today).getValue();
|
||||
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();
|
||||
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));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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();
|
||||
habit.recompute();
|
||||
|
||||
assertThat(habit.getScores().getTodayValue(), closeTo(0.054816, E));
|
||||
}
|
||||
|
||||
@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<Integer> 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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
Reference in New Issue
Block a user