feat: Decouple the checkmark state from the source of the input

pull/643/head
Christoph Hennemann 5 years ago
parent 87dda92d37
commit 5403ca3ef4

@ -56,7 +56,7 @@ public class HistoryChartTest extends BaseViewTest
today = new Timestamp(DateUtils.getStartOfToday());
chart = new HistoryChart(targetContext);
chart.setCheckmarks(habit.getCheckmarks().getAllValues());
chart.setCheckmarkStates(habit.getCheckmarks().getAllValues());
chart.setColor(PaletteUtils.getAndroidTestColor(habit.getColor()));
measureView(chart, dpToPixels(400), dpToPixels(200));
@ -92,7 +92,7 @@ public class HistoryChartTest extends BaseViewTest
public void tapDate_withEmptyHabit()
{
chart.setIsEditable(true);
chart.setCheckmarks(new int[]{});
chart.setCheckmarkStates(new int[]{});
chart.tap(dpToPixels(340), dpToPixels(40));
verify(controller).onToggleCheckmark(today, Checkmark.YES_MANUAL);
verifyNoMoreInteractions(controller);

@ -164,12 +164,12 @@ public class HistoryEditorDialog extends AppCompatDialogFragment
private class RefreshTask implements Task
{
public int[] checkmarks;
public CheckmarkState[] checkmarks;
@Override
public void doInBackground()
{
checkmarks = habit.getCheckmarks().getAllValues();
checkmarks = habit.getCheckmarks().getAllStates();
}
@Override
@ -180,7 +180,7 @@ public class HistoryEditorDialog extends AppCompatDialogFragment
int color = PaletteUtils.getColor(getContext(), habit.getColor());
historyChart.setColor(color);
historyChart.setCheckmarks(checkmarks);
historyChart.setCheckmarkStates(checkmarks);
historyChart.setNumerical(habit.isNumerical());
historyChart.setTarget(habit.getTargetValue() / habit.getFrequency().getDenominator());
}

@ -123,7 +123,7 @@ public class BarChart extends ScrollableChart
for (int i = 1; i < 100; i++)
{
int value = random.nextInt(1000);
checkmarks.add(new Checkmark(today.minus(i), value));
checkmarks.add(new Checkmark(today.minus(i), value, false));
}
setCheckmarks(checkmarks);

@ -42,7 +42,7 @@ import static org.isoron.uhabits.core.models.Checkmark.*;
public class HistoryChart extends ScrollableChart
{
private int[] checkmarks;
private CheckmarkState[] checkmarks;
private double target;
@ -149,15 +149,15 @@ public class HistoryChart extends ScrollableChart
if (timestamp == null) return false;
Timestamp today = DateUtils.getToday();
int newValue = YES_MANUAL;
int newValue = YES;
int offset = timestamp.daysUntil(today);
if (offset < checkmarks.length)
{
newValue = Repetition.nextToggleValue(checkmarks[offset]);
checkmarks[offset] = newValue;
newValue = Repetition.nextToggleValue(checkmarks[offset].getValue());
checkmarks[offset] = new CheckmarkState(newValue, true);
}
controller.onToggleCheckmark(timestamp, newValue);
controller.onToggleCheckmark(timestamp, newValue, true);
postInvalidate();
return true;
@ -166,22 +166,28 @@ public class HistoryChart extends ScrollableChart
public void populateWithRandomData()
{
Random random = new Random();
checkmarks = new int[100];
checkmarks = new CheckmarkState[100];
for (int i = 0; i < 100; i++)
if (random.nextFloat() < 0.3) checkmarks[i] = 2;
if (random.nextFloat() < 0.3)
{
checkmarks[i] = new CheckmarkState(2, random.nextBoolean());
}
for (int i = 0; i < 100 - 7; i++)
{
int count = 0;
for (int j = 0; j < 7; j++)
if (checkmarks[i + j] != 0) count++;
if (checkmarks[i + j].getValue() != 0) count++;
if (count >= 3) checkmarks[i] = Math.max(checkmarks[i], 1);
if (count >= 3)
{
checkmarks[i] = new CheckmarkState(Math.max(checkmarks[i].getValue(), 1), random.nextBoolean());
}
}
}
public void setCheckmarks(int[] checkmarks)
public void setCheckmarkStates(CheckmarkState[] checkmarks)
{
this.checkmarks = checkmarks;
postInvalidate();
@ -372,7 +378,8 @@ public class HistoryChart extends ScrollableChart
int checkmarkOffset)
{
int checkmark = 0;
int value = NO;
boolean manualInput = false;
if (checkmarkOffset >= checkmarks.length)
{
pSquareBg.setColor(colors[0]);
@ -380,13 +387,14 @@ public class HistoryChart extends ScrollableChart
}
else
{
checkmark = checkmarks[checkmarkOffset];
if(checkmark == 0)
value = checkmarks[checkmarkOffset].getValue();
manualInput = checkmarks[checkmarkOffset].isManualInput();
if(value == NO)
{
pSquareBg.setColor(colors[0]);
pSquareFg.setColor(textColors[1]);
}
else if ((isNumerical && (checkmark / 1000f >= target) || (!isNumerical && checkmark == YES_MANUAL)))
else if ((isNumerical && (value / 1000f >= target) || (!isNumerical && value == YES && manualInput)))
{
pSquareBg.setColor(colors[2]);
pSquareFg.setColor(textColors[2]);
@ -401,7 +409,7 @@ public class HistoryChart extends ScrollableChart
float round = dpToPixels(getContext(), 2);
canvas.drawRoundRect(location, round, round, pSquareBg);
if (!isNumerical && checkmark == SKIP)
if (!isNumerical && value == SKIP)
{
pSquareBg.setColor(backgroundColor);
pSquareBg.setStrokeWidth(columnWidth * 0.025f);
@ -439,7 +447,7 @@ public class HistoryChart extends ScrollableChart
private void init()
{
isEditable = false;
checkmarks = new int[0];
checkmarks = new CheckmarkState[0];
controller = new Controller() {};
target = 2;
@ -545,6 +553,6 @@ public class HistoryChart extends ScrollableChart
public interface Controller
{
default void onToggleCheckmark(Timestamp timestamp, int value) {}
default void onToggleCheckmark(Timestamp timestamp, int value, boolean manualInput) {}
}
}

@ -46,13 +46,13 @@ class CheckmarkButtonView(
invalidate()
}
var value: Int = 0
var value: CheckmarkState = CheckmarkState(0, false)
set(value) {
field = value
invalidate()
}
var onToggle: (Int) -> Unit = {}
var onToggle: (CheckmarkState) -> Unit = {}
private var drawer = Drawer()
init {
@ -62,7 +62,7 @@ class CheckmarkButtonView(
}
fun performToggle() {
value = Repetition.nextToggleValue(value)
value = CheckmarkState(Repetition.nextToggleValue(value.value), true)
onToggle(value)
performHapticFeedback(HapticFeedbackConstants.LONG_PRESS)
invalidate()
@ -102,12 +102,11 @@ class CheckmarkButtonView(
}
fun draw(canvas: Canvas) {
paint.color = when (value) {
YES_MANUAL -> color
SKIP -> color
else -> lowContrastColor
paint.color = when (value.isManualInput) {
true -> color
false -> lowContrastColor
}
val id = when (value) {
val id = when (value.value) {
SKIP -> R.string.fa_skipped
NO -> R.string.fa_times
else -> R.string.fa_check

@ -34,7 +34,7 @@ class CheckmarkPanelView(
@Provided private val buttonFactory: CheckmarkButtonViewFactory
) : ButtonPanelView<CheckmarkButtonView>(context, preferences) {
var values = IntArray(0)
var values: Array<CheckmarkState> = emptyArray()
set(values) {
field = values
setupButtons()
@ -46,7 +46,7 @@ class CheckmarkPanelView(
setupButtons()
}
var onToggle: (Timestamp, Int) -> Unit = {_, _ ->}
var onToggle: (Timestamp, Int, Boolean) -> Unit = {_, _, _ ->}
set(value) {
field = value
setupButtons()
@ -62,10 +62,10 @@ class CheckmarkPanelView(
val timestamp = today.minus(index + dataOffset)
button.value = when {
index + dataOffset < values.size -> values[index + dataOffset]
else -> NO
else -> CheckmarkState(NO, false)
}
button.color = color
button.onToggle = { value -> onToggle(timestamp, value) }
button.onToggle = { state -> onToggle(timestamp, state.value, state.isManualInput) }
}
}
}

@ -21,7 +21,6 @@ package org.isoron.uhabits.activities.habits.list.views;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.appcompat.widget.*;
import android.view.*;
import androidx.recyclerview.widget.RecyclerView;
@ -181,10 +180,10 @@ public class HabitCardListAdapter
Habit habit = cache.getHabitByPosition(position);
double score = cache.getScore(habit.getId());
int checkmarks[] = cache.getCheckmarks(habit.getId());
CheckmarkState[] checkmarkStates = cache.getCheckmarkStates(habit.getId());
boolean selected = this.selected.contains(habit);
listView.bindCardView(holder, habit, score, checkmarks, selected);
listView.bindCardView(holder, habit, score, checkmarkStates, selected);
}
@Override

@ -21,7 +21,6 @@ package org.isoron.uhabits.activities.habits.list.views
import android.content.*
import android.os.*
import androidx.appcompat.widget.*
import androidx.recyclerview.widget.*
import androidx.recyclerview.widget.ItemTouchHelper.*
import android.view.*
@ -69,12 +68,12 @@ class HabitCardListView(
fun bindCardView(holder: HabitCardViewHolder,
habit: Habit,
score: Double,
checkmarks: IntArray,
checkmarkStates: Array<CheckmarkState>,
selected: Boolean): View {
val cardView = holder.itemView as HabitCardView
cardView.habit = habit
cardView.isSelected = selected
cardView.values = checkmarks
cardView.values = checkmarkStates
cardView.buttonCount = checkmarkCount
cardView.dataOffset = dataOffset
cardView.score = score

@ -86,7 +86,7 @@ class HabitCardView(
get() = checkmarkPanel.values
set(values) {
checkmarkPanel.values = values
numberPanel.values = values.map { it / 1000.0 }.toDoubleArray()
numberPanel.values = values.map { it.value / 1000.0 }.toDoubleArray()
}
var threshold: Double
@ -121,9 +121,9 @@ class HabitCardView(
}
checkmarkPanel = checkmarkPanelFactory.create().apply {
onToggle = { timestamp, value ->
onToggle = { timestamp, value, manualInput ->
triggerRipple(timestamp)
habit?.let { behavior.onToggle(it, timestamp, value) }
habit?.let { behavior.onToggle(it, timestamp, value, manualInput) }
}
}

@ -94,9 +94,9 @@ public class ShowHabitScreen extends BaseScreen
}
@Override
public void onToggleCheckmark(Timestamp timestamp, int value)
public void onToggleCheckmark(Timestamp timestamp, int value, boolean manualInput)
{
behavior.get().onToggleCheckmark(timestamp, value);
behavior.get().onToggleCheckmark(timestamp, value, manualInput);
}
@Override

@ -119,9 +119,9 @@ public class HistoryCard extends HabitCard
public void doInBackground()
{
if (isCanceled()) return;
int[] checkmarks = habit.getCheckmarks().getAllValues();
CheckmarkState[] checkmarkStates = habit.getCheckmarks().getAllStates();
if(prefs != null) chart.setFirstWeekday(prefs.getFirstWeekday());
chart.setCheckmarks(checkmarks);
chart.setCheckmarkStates(checkmarkStates);
}
@Override

@ -23,6 +23,7 @@ import android.app.*
import android.content.*
import android.view.*
import org.isoron.uhabits.core.models.*
import org.isoron.uhabits.core.models.Checkmark.NO
import org.isoron.uhabits.utils.*
import org.isoron.uhabits.widgets.views.*
@ -46,12 +47,13 @@ open class CheckmarkWidget(
setActiveColor(PaletteUtils.getColor(context, habit.color))
setName(habit.name)
setCheckmarkValue(habit.checkmarks.todayValue)
val state = habit.checkmarks.todayState
if (habit.isNumerical) {
setNumerical(true)
setCheckmarkState(getNumericalCheckmarkState())
setCheckmarkState(getNumericalCheckmarkState(state))
} else {
setCheckmarkState(habit.checkmarks.todayValue)
setCheckmarkState(state)
}
setPercentage(habit.scores.todayValue.toFloat())
refresh()
@ -65,11 +67,11 @@ open class CheckmarkWidget(
override fun getDefaultHeight() = 125
override fun getDefaultWidth() = 125
private fun getNumericalCheckmarkState(): Int {
private fun getNumericalCheckmarkState(state: CheckmarkState): CheckmarkState {
return if (habit.isCompletedToday) {
Checkmark.YES_MANUAL
CheckmarkState(state.value, true)
} else {
Checkmark.NO
CheckmarkState(state.value, false)
}
}

@ -45,7 +45,7 @@ class HistoryWidget(
(widgetView.dataView as HistoryChart).apply {
setFirstWeekday(firstWeekday)
setColor(PaletteUtils.getColor(context, habit.color))
setCheckmarks(habit.checkmarks.allValues)
setCheckmarkStates(habit.checkmarks.allStates)
setNumerical(habit.isNumerical)
setTarget(habit.targetValue / habit.frequency.denominator)
}

@ -19,6 +19,7 @@
package org.isoron.uhabits.widgets.views;
import android.content.*;
import android.util.*;
import android.widget.*;
@ -35,6 +36,9 @@ import org.isoron.uhabits.utils.*;
import static org.isoron.androidbase.utils.InterfaceUtils.getDimension;
public class CheckmarkWidgetView extends HabitWidgetView {
protected int activeColor;
@ -49,74 +53,76 @@ public class CheckmarkWidgetView extends HabitWidgetView {
protected int checkmarkValue;
protected int checkmarkState;
protected CheckmarkState checkmarkState;
protected boolean isNumerical;
public CheckmarkWidgetView(Context context)
{
public CheckmarkWidgetView(Context context) {
super(context);
init();
}
public CheckmarkWidgetView(Context context, AttributeSet attrs)
{
public CheckmarkWidgetView(Context context, AttributeSet attrs) {
super(context, attrs);
init();
}
public void refresh()
{
if (backgroundPaint == null || frame == null || ring == null) return;
public void refresh() {
if (backgroundPaint == null || frame == null || ring == null) {
return;
}
StyledResources res = new StyledResources(getContext());
int bgColor;
int fgColor;
switch (checkmarkState) {
case Checkmark.YES_MANUAL:
case Checkmark.SKIP:
bgColor = activeColor;
fgColor = res.getColor(R.attr.highContrastReverseTextColor);
setShadowAlpha(0x4f);
backgroundPaint.setColor(bgColor);
frame.setBackgroundDrawable(background);
break;
case Checkmark.YES_AUTO:
case Checkmark.NO:
default:
getResources().getString(R.string.fa_times);
bgColor = res.getColor(R.attr.cardBgColor);
fgColor = res.getColor(R.attr.mediumContrastTextColor);
setShadowAlpha(0x00);
break;
if (checkmarkState.isManualInput()) {
bgColor = activeColor;
fgColor = res.getColor(R.attr.highContrastReverseTextColor);
setShadowAlpha(0x4f);
backgroundPaint.setColor(bgColor);
frame.setBackgroundDrawable(background);
} else {
bgColor = res.getColor(R.attr.cardBgColor);
fgColor = res.getColor(R.attr.mediumContrastTextColor);
setShadowAlpha(0x00);
}
ring.setPercentage(percentage);
ring.setColor(fgColor);
ring.setBackgroundColor(bgColor);
ring.setText(getText());
ring.setText(
getText());
label.setText(name);
label.setTextColor(fgColor);
requestLayout();
postInvalidate();
}
public void setCheckmarkState(int checkmarkState)
{
public void setCheckmarkState(CheckmarkState checkmarkState) {
this.checkmarkState = checkmarkState;
}
protected String getText()
{
if (isNumerical) return NumberButtonViewKt.toShortString(checkmarkValue / 1000.0);
switch (checkmarkState) {
case Checkmark.YES_MANUAL:
case Checkmark.YES_AUTO:
protected String getText() {
if (isNumerical) {
return NumberButtonViewKt.toShortString(checkmarkState.getValue() / 1000.0);
}
switch (checkmarkState.getValue()) {
case Checkmark.YES:
return getResources().getString(R.string.fa_check);
case Checkmark.SKIP:
return getResources().getString(R.string.fa_skipped);
@ -126,41 +132,36 @@ public class CheckmarkWidgetView extends HabitWidgetView {
}
}
public void setActiveColor(int activeColor)
{
public void setActiveColor(int activeColor) {
this.activeColor = activeColor;
}
public void setCheckmarkValue(int checkmarkValue)
{
this.checkmarkValue = checkmarkValue;
}
public void setName(@NonNull String name)
{
public void setName(@NonNull String name) {
this.name = name;
}
public void setPercentage(float percentage)
{
public void setPercentage(float percentage) {
this.percentage = percentage;
}
public void setNumerical(boolean isNumerical)
{
public void setNumerical(boolean isNumerical) {
this.isNumerical = isNumerical;
}
@Override
@NonNull
protected Integer getInnerLayoutId()
{
protected Integer getInnerLayoutId() {
return R.layout.widget_checkmark;
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec)
{
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int width = MeasureSpec.getSize(widthMeasureSpec);
int height = MeasureSpec.getSize(heightMeasureSpec);
@ -171,10 +172,11 @@ public class CheckmarkWidgetView extends HabitWidgetView {
w *= scale;
h *= scale;
if (h < getDimension(getContext(), R.dimen.checkmarkWidget_heightBreakpoint))
if (h < getDimension(getContext(), R.dimen.checkmarkWidget_heightBreakpoint)) {
ring.setVisibility(GONE);
else
} else {
ring.setVisibility(VISIBLE);
}
widthMeasureSpec =
MeasureSpec.makeMeasureSpec((int) w, MeasureSpec.EXACTLY);
@ -192,19 +194,20 @@ public class CheckmarkWidgetView extends HabitWidgetView {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
}
private void init()
{
private void init() {
ring = (RingView) findViewById(R.id.scoreRing);
label = (TextView) findViewById(R.id.label);
if (ring != null) ring.setIsTransparencyEnabled(true);
if (ring != null) {
ring.setIsTransparencyEnabled(true);
}
if (isInEditMode())
{
if (isInEditMode()) {
percentage = 0.75f;
name = "Wake up early";
activeColor = PaletteUtils.getAndroidTestColor(6);
checkmarkValue = Checkmark.YES_MANUAL;
checkmarkState = new CheckmarkState(Checkmark.YES, true);
refresh();
}
}

@ -61,8 +61,9 @@ public class WidgetControllerTest extends BaseAndroidJVMTest
public void testOnAddRepetition_whenChecked() throws Exception
{
habit.getRepetitions().toggle(today);
int todayValue = habit.getCheckmarks().getTodayValue();
assertThat(todayValue, equalTo(YES_MANUAL));
CheckmarkState todayValue = habit.getCheckmarks().getTodayState();
assertThat(todayValue.getValue(), equalTo(YES));
assertThat(todayValue.isManualInput(), equalTo(true));
controller.onAddRepetition(habit, today);
verifyZeroInteractions(commandRunner);
}
@ -81,8 +82,9 @@ public class WidgetControllerTest extends BaseAndroidJVMTest
public void testOnRemoveRepetition_whenChecked() throws Exception
{
habit.getRepetitions().toggle(today);
int todayValue = habit.getCheckmarks().getTodayValue();
assertThat(todayValue, equalTo(YES_MANUAL));
CheckmarkState todayValue = habit.getCheckmarks().getTodayState();
assertThat(todayValue.getValue(), equalTo(YES));
assertThat(todayValue.isManualInput(), equalTo(true));
controller.onRemoveRepetition(habit, today);
verify(commandRunner).execute(any(), isNull());
}

@ -22,5 +22,5 @@ package org.isoron.uhabits.core;
public class Config
{
public static final String DATABASE_FILENAME = "uhabits.db";
public static int DATABASE_VERSION = 23;
public static int DATABASE_VERSION = 24;
}

@ -36,6 +36,8 @@ public class CreateRepetitionCommand extends Command
final int value;
final boolean manualInput;
@Nullable
Repetition previousRep;
@ -45,12 +47,14 @@ public class CreateRepetitionCommand extends Command
public CreateRepetitionCommand(@NonNull HabitList habitList,
@NonNull Habit habit,
Timestamp timestamp,
int value)
int value,
boolean manualInput)
{
this.habitList = habitList;
this.timestamp = timestamp;
this.habit = habit;
this.value = value;
this.manualInput = manualInput;
}
@Override
@ -63,7 +67,7 @@ public class CreateRepetitionCommand extends Command
if (value > 0)
{
newRep = new Repetition(timestamp, value);
newRep = new Repetition(timestamp, value, manualInput);
reps.add(newRep);
}
@ -106,6 +110,8 @@ public class CreateRepetitionCommand extends Command
public int value;
public boolean manualInput;
public Record(CreateRepetitionCommand command)
{
id = command.getId();
@ -115,6 +121,7 @@ public class CreateRepetitionCommand extends Command
this.habit = habitId;
this.repTimestamp = command.timestamp.getUnixTime();
this.value = command.value;
this.manualInput = command.manualInput;
}
public CreateRepetitionCommand toCommand(@NonNull HabitList habitList)
@ -123,8 +130,7 @@ public class CreateRepetitionCommand extends Command
if(h == null) throw new HabitNotFoundException();
CreateRepetitionCommand command;
command = new CreateRepetitionCommand(
habitList, h, new Timestamp(repTimestamp), value);
command = new CreateRepetitionCommand(habitList, h, new Timestamp(repTimestamp), value, manualInput);
command.setId(id);
return command;
}

@ -109,7 +109,7 @@ public class LoopDBImporter extends AbstractImporter
habitRecord.id.toString());
for (RepetitionRecord r : reps)
h.getRepetitions().toggle(new Timestamp(r.timestamp), r.value);
h.getRepetitions().toggle(new Timestamp(r.timestamp), r.value, r.isManualInput());
}
}
}

@ -19,6 +19,7 @@
package org.isoron.uhabits.core.models;
import androidx.annotation.NonNull;
import org.apache.commons.lang3.builder.*;
import javax.annotation.concurrent.*;
@ -40,18 +41,12 @@ public final class Checkmark
/**
* Indicates that there was an explicit skip at the timestamp.
*/
public static final int SKIP = 3;
public static final int SKIP = 2;
/**
* Indicates that there was a repetition at the timestamp.
*/
public static final int YES_MANUAL = 2;
/**
* Indicates that there was no repetition at the timestamp, but one was not
* expected in any case, due to the frequency of the habit.
*/
public static final int YES_AUTO = 1;
public static final int YES = 1;
/**
* Indicates that there was no repetition at the timestamp, even though a
@ -62,19 +57,15 @@ public final class Checkmark
private final Timestamp timestamp;
/**
* The value of the checkmark.
* <p>
* For boolean habits, this equals either NO, YES_AUTO, YES_MANUAL or SKIP.
* <p>
* For numerical habits, this number is stored in thousandths. That
* is, if the user enters value 1.50 on the app, it is stored as 1500.
* The state of the checkmark.
*/
private final int value;
@NonNull
private final CheckmarkState state;
public Checkmark(Timestamp timestamp, int value)
public Checkmark(Timestamp timestamp, int value, boolean manualInput)
{
this.timestamp = timestamp;
this.value = value;
this.state = new CheckmarkState(value, manualInput);
}
@Override
@ -88,7 +79,7 @@ public final class Checkmark
return new EqualsBuilder()
.append(timestamp, checkmark.timestamp)
.append(value, checkmark.value)
.append(getState(), checkmark.getState())
.isEquals();
}
@ -97,9 +88,19 @@ public final class Checkmark
return timestamp;
}
public CheckmarkState getState()
{
return state;
}
public int getValue()
{
return value;
return state.getValue();
}
public boolean isManualInput()
{
return state.isManualInput();
}
@Override
@ -107,7 +108,7 @@ public final class Checkmark
{
return new HashCodeBuilder(17, 37)
.append(timestamp)
.append(value)
.append(getState())
.toHashCode();
}
@ -116,7 +117,8 @@ public final class Checkmark
{
return new ToStringBuilder(this, defaultToStringStyle())
.append("timestamp", timestamp)
.append("value", value)
.append("value", getValue())
.append("manualInput", isManualInput())
.toString();
}
}

@ -19,8 +19,10 @@
package org.isoron.uhabits.core.models;
import androidx.annotation.*;
import java.util.stream.Collectors;
import org.apache.commons.lang3.builder.*;
import org.isoron.uhabits.core.utils.*;
@ -33,58 +35,67 @@ import javax.annotation.concurrent.*;
import static org.isoron.uhabits.core.models.Checkmark.*;
import static org.isoron.uhabits.core.utils.StringUtils.defaultToStringStyle;
/**
* The collection of {@link Checkmark}s belonging to a habit.
*/
@ThreadSafe
public abstract class CheckmarkList
{
public abstract class CheckmarkList {
protected final Habit habit;
public final ModelObservable observable;
public CheckmarkList(Habit habit)
{
public CheckmarkList(Habit habit) {
this.habit = habit;
this.observable = new ModelObservable();
}
@NonNull
static List<Checkmark> buildCheckmarksFromIntervals(Repetition[] reps,
ArrayList<Interval> intervals)
{
if (reps.length == 0) throw new IllegalArgumentException();
static List<Checkmark> buildCheckmarksFromIntervals(
Repetition[] reps,
ArrayList<Interval> intervals
) {
if (reps.length == 0) {
throw new IllegalArgumentException();
}
Timestamp today = DateUtils.getToday();
Timestamp begin = reps[0].getTimestamp();
if (intervals.size() > 0) begin = Timestamp.oldest(begin, intervals.get(0).begin);
if (intervals.size() > 0) {
begin = Timestamp.oldest(begin, intervals.get(0).begin);
}
int nDays = begin.daysUntil(today) + 1;
List<Checkmark> checkmarks = new ArrayList<>(nDays);
for (int i = 0; i < nDays; i++)
checkmarks.add(new Checkmark(today.minus(i), NO));
for (int i = 0; i < nDays; i++) {
checkmarks.add(new Checkmark(today.minus(i), NO, false));
}
for (Interval interval : intervals)
{
for (int i = 0; i < interval.length(); i++)
{
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;
checkmarks.set(offset, new Checkmark(date, YES_AUTO));
if (offset < 0) {
continue;
}
checkmarks.set(offset, new Checkmark(date, YES, false));
}
}
for (Repetition rep : reps)
{
for (Repetition rep : reps) {
Timestamp date = rep.getTimestamp();
int offset = date.daysUntil(today);
checkmarks.set(offset, new Checkmark(date, rep.getValue()));
checkmarks.set(offset, new Checkmark(date, rep.getValue(), rep.isManualInput()));
}
return checkmarks;
}
/**
* For non-daily habits, some groups of repetitions generate many
* checkmarks. For example, for weekly habits, each repetition generates
@ -97,25 +108,29 @@ public abstract class CheckmarkList
* away in the future as possible.
*/
@NonNull
static ArrayList<Interval> buildIntervals(@NonNull Frequency freq,
@NonNull Repetition[] reps)
{
static ArrayList<Interval> buildIntervals(
@NonNull Frequency freq,
@NonNull Repetition[] reps
) {
ArrayList<Repetition> filteredReps = new ArrayList<>();
for (Repetition r : reps)
if (r.getValue() == YES_MANUAL)
for (Repetition r : reps) {
if (r.getValue() == YES && r.isManualInput()) {
filteredReps.add(r);
}
}
int num = freq.getNumerator();
int den = freq.getDenominator();
ArrayList<Interval> intervals = new ArrayList<>();
for (int i = 0; i < filteredReps.size() - num + 1; i++)
{
for (int i = 0; i < filteredReps.size() - num + 1; i++) {
Repetition first = filteredReps.get(i);
Repetition last = filteredReps.get(i + num - 1);
long distance = first.getTimestamp().daysUntil(last.getTimestamp());
if (distance >= den) continue;
if (distance >= den) {
continue;
}
Timestamp begin = first.getTimestamp();
Timestamp center = last.getTimestamp();
@ -126,32 +141,33 @@ public abstract class CheckmarkList
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)
{
static void snapIntervalsTogether(@NonNull ArrayList<Interval> intervals) {
int n = intervals.size();
for (int i = n - 2; i >= 0; i--)
{
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)
{
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)));
intervals.set(i, new Interval(
curr.begin.minus(shift),
curr.center,
curr.end.minus(shift)
));
}
}
}
/**
* Adds all the given checkmarks to the list.
* <p>
@ -162,6 +178,7 @@ public abstract class CheckmarkList
*/
public abstract void add(List<Checkmark> checkmarks);
/**
* Returns the values for all the checkmarks, since the oldest repetition of
* the habit until today.
@ -175,10 +192,11 @@ public abstract class CheckmarkList
* @return values for the checkmarks in the interval
*/
@NonNull
public synchronized final int[] getAllValues()
{
public synchronized final int[] getAllValues() {
Repetition oldestRep = habit.getRepetitions().getOldest();
if (oldestRep == null) return new int[0];
if (oldestRep == null) {
return new int[0];
}
Timestamp fromTimestamp = oldestRep.getTimestamp();
Timestamp toTimestamp = DateUtils.getToday();
@ -186,6 +204,33 @@ public abstract class CheckmarkList
return getValues(fromTimestamp, toTimestamp);
}
/**
* 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 CheckmarkState[] getAllStates() {
Repetition oldestRep = habit.getRepetitions().getOldest();
if (oldestRep == null) {
return new CheckmarkState[0];
}
Timestamp fromTimestamp = oldestRep.getTimestamp();
Timestamp toTimestamp = DateUtils.getToday();
return getCheckmarkStates(fromTimestamp, toTimestamp);
}
/**
* Returns the list of checkmarks that fall within the given interval.
* <p>
@ -196,11 +241,15 @@ public abstract class CheckmarkList
*
* @param fromTimestamp timestamp of the beginning of the interval.
* @param toTimestamp timestamp of the end of the interval.
*
* @return the list of checkmarks within the interval.
*/
@NonNull
public abstract List<Checkmark> getByInterval(Timestamp fromTimestamp,
Timestamp toTimestamp);
public abstract List<Checkmark> getByInterval(
Timestamp fromTimestamp,
Timestamp toTimestamp
);
/**
* Returns the checkmark for today.
@ -208,54 +257,72 @@ public abstract class CheckmarkList
* @return checkmark for today
*/
@Nullable
public synchronized final Checkmark getToday()
{
public synchronized final Checkmark getToday() {
compute();
Timestamp today = DateUtils.getToday();
return getByInterval(today, today).get(0);
}
/**
* Returns the value of today's checkmark.
*
* @return value of today's checkmark
*/
public synchronized int getTodayValue()
{
public synchronized int getTodayValue() {
Checkmark today = getToday();
if (today != null) return today.getValue();
else return NO;
if (today != null) {
return today.getValue();
} else {
return NO;
}
}
public synchronized int getThisWeekValue(int firstWeekday)
{
/**
* Returns the value of today's checkmark.
*
* @return value of today's checkmark
*/
public synchronized CheckmarkState getTodayState() {
Checkmark today = getToday();
if (today != null) {
return new CheckmarkState(today.getValue(), today.isManualInput());
} else {
return new CheckmarkState(NO, false);
}
}
public synchronized int getThisWeekValue(int firstWeekday) {
return getThisIntervalValue(DateUtils.TruncateField.WEEK_NUMBER, firstWeekday);
}
public synchronized int getThisMonthValue()
{
public synchronized int getThisMonthValue() {
return getThisIntervalValue(DateUtils.TruncateField.MONTH, Calendar.SATURDAY);
}
public synchronized int getThisQuarterValue()
{
public synchronized int getThisQuarterValue() {
return getThisIntervalValue(DateUtils.TruncateField.QUARTER, Calendar.SATURDAY);
}
public synchronized int getThisYearValue()
{
public synchronized int getThisYearValue() {
return getThisIntervalValue(DateUtils.TruncateField.YEAR, Calendar.SATURDAY);
}
private int getThisIntervalValue(DateUtils.TruncateField truncateField, int firstWeekday)
{
private int getThisIntervalValue(DateUtils.TruncateField truncateField, int firstWeekday) {
List<Checkmark> groups = habit.getCheckmarks().groupBy(truncateField, firstWeekday, 1);
if (groups.isEmpty()) return 0;
if (groups.isEmpty()) {
return 0;
}
return groups.get(0).getValue();
}
/**
* Returns the values of the checkmarks that fall inside a certain interval
* of time.
@ -267,22 +334,54 @@ public abstract class CheckmarkList
*
* @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];
public final int[] getValues(Timestamp from, Timestamp to) {
if (from.isNewerThan(to)) {
return new int[0];
}
List<Checkmark> checkmarks = getByInterval(from, to);
int values[] = new int[checkmarks.size()];
int i = 0;
for (Checkmark c : checkmarks)
for (Checkmark c : checkmarks) {
values[i++] = c.getValue();
}
return values;
}
/**
* Returns the checkmarkStates that fall inside a certain interval
* of time.
* <p>
* The values are returned in an array containing the checkmark 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 the checkmarks inside the given interval
*/
public final CheckmarkState[] getCheckmarkStates(Timestamp from, Timestamp to) {
if (from.isNewerThan(to)) {
return new CheckmarkState[0];
}
final List<Checkmark> checkmarks = getByInterval(from, to);
final CheckmarkState[] checkmarkStates = new CheckmarkState[checkmarks.size()];
for (int i = 0; i < checkmarks.size(); i++) {
checkmarkStates[i] = checkmarks.get(i).getState();
}
return checkmarkStates;
}
/**
* Marks as invalid every checkmark that has timestamp either equal or newer
* than a given timestamp. These checkmarks will be recomputed at the next
@ -292,47 +391,50 @@ public abstract class CheckmarkList
*/
public abstract void invalidateNewerThan(Timestamp timestamp);
/**
* 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[];
public final void writeCSV(Writer out) throws IOException {
CheckmarkState[] states;
synchronized (this)
{
synchronized (this) {
compute();
values = getAllValues();
states = getAllStates();
}
Timestamp timestamp = DateUtils.getToday();
SimpleDateFormat dateFormat = DateFormats.getCSVDateFormat();
for (int value : values)
{
for (CheckmarkState state : states) {
String date = dateFormat.format(timestamp.toJavaDate());
out.write(String.format("%s,%d\n", date, value));
out.write(String.format("%s,%d,%d\n", date, state.getValue(), state.isManualInput() ? 1 : 0));
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()
{
protected final synchronized void compute() {
final Timestamp today = DateUtils.getToday();
Checkmark newest = getNewestComputed();
if (newest != null && newest.getTimestamp().equals(today)) return;
if (newest != null && newest.getTimestamp().equals(today)) {
return;
}
invalidateNewerThan(Timestamp.ZERO);
Repetition oldestRep = habit.getRepetitions().getOldest();
if (oldestRep == null) return;
if (oldestRep == null) {
return;
}
final Timestamp from = oldestRep.getTimestamp();
Repetition reps[] = habit
@ -340,10 +442,14 @@ public abstract class CheckmarkList
.getByInterval(from, today)
.toArray(new Repetition[0]);
if (habit.isNumerical()) computeNumerical(reps);
else computeYesNo(reps);
if (habit.isNumerical()) {
computeNumerical(reps);
} else {
computeYesNo(reps);
}
}
/**
* Returns newest checkmark that has already been computed.
*
@ -360,66 +466,76 @@ public abstract class CheckmarkList
@Nullable
protected abstract Checkmark getOldestComputed();
private void computeNumerical(Repetition[] reps)
{
if (reps.length == 0) return;
private void computeNumerical(Repetition[] reps) {
if (reps.length == 0) {
return;
}
Timestamp today = DateUtils.getToday();
Timestamp begin = reps[0].getTimestamp();
int nDays = begin.daysUntil(today) + 1;
List<Checkmark> checkmarks = new ArrayList<>(nDays);
for (int i = 0; i < nDays; i++)
checkmarks.add(new Checkmark(today.minus(i), 0));
for (int i = 0; i < nDays; i++) {
checkmarks.add(new Checkmark(today.minus(i), 0, false));
}
for (Repetition rep : reps)
{
for (Repetition rep : reps) {
int offset = rep.getTimestamp().daysUntil(today);
checkmarks.set(offset, new Checkmark(rep.getTimestamp(), rep.getValue()));
checkmarks.set(offset, new Checkmark(rep.getTimestamp(), rep.getValue(), rep.isManualInput()));
}
add(checkmarks);
}
private void computeYesNo(Repetition[] reps)
{
private void computeYesNo(Repetition[] reps) {
ArrayList<Interval> intervals;
intervals = buildIntervals(habit.getFrequency(), reps);
snapIntervalsTogether(intervals);
add(buildCheckmarksFromIntervals(reps, intervals));
}
public List<Checkmark> getAll() {
Repetition oldest = habit.getRepetitions().getOldest();
if(oldest == null) return new ArrayList<>();
if (oldest == null) {
return new ArrayList<>();
}
return getByInterval(oldest.getTimestamp(), DateUtils.getToday());
}
static final class Interval
{
static final class Interval {
final Timestamp begin;
final Timestamp center;
final Timestamp end;
Interval(Timestamp begin, Timestamp center, Timestamp end)
{
Interval(Timestamp begin, Timestamp center, Timestamp end) {
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;
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) return false;
if (o == null || getClass() != o.getClass()) {
return false;
}
Interval interval = (Interval) o;
@ -430,9 +546,9 @@ public abstract class CheckmarkList
.isEquals();
}
@Override
public int hashCode()
{
public int hashCode() {
return new HashCodeBuilder(17, 37)
.append(begin)
.append(center)
@ -440,9 +556,9 @@ public abstract class CheckmarkList
.toHashCode();
}
@Override
public String toString()
{
public String toString() {
return new ToStringBuilder(this, defaultToStringStyle())
.append("begin", begin)
.append("center", center)
@ -451,44 +567,47 @@ public abstract class CheckmarkList
}
}
@NonNull
public List<Checkmark> groupBy(DateUtils.TruncateField field, int firstWeekday)
{
public List<Checkmark> groupBy(DateUtils.TruncateField field, int firstWeekday) {
return groupBy(field, firstWeekday, 0);
}
@NonNull
public List<Checkmark> groupBy(DateUtils.TruncateField field,
int firstWeekday,
int maxGroups)
{
public List<Checkmark> groupBy(
DateUtils.TruncateField field,
int firstWeekday,
int maxGroups
) {
List<Checkmark> checks = getAll();
int count = 0;
Timestamp[] truncatedTimestamps = new Timestamp[checks.size()];
int[] values = new int[checks.size()];
boolean[] manualInputs = new boolean[checks.size()];
for (Checkmark rep : checks)
{
for (Checkmark rep : checks) {
Timestamp tt = rep.getTimestamp().truncate(field, firstWeekday);
if (count == 0 || !truncatedTimestamps[count - 1].equals(tt))
{
if (maxGroups > 0 && count >= maxGroups) break;
if (count == 0 || !truncatedTimestamps[count - 1].equals(tt)) {
if (maxGroups > 0 && count >= maxGroups) {
break;
}
truncatedTimestamps[count++] = tt;
}
if(habit.isNumerical())
if (habit.isNumerical()) {
values[count - 1] += rep.getValue();
else if(rep.getValue() == Checkmark.YES_MANUAL)
} else if (rep.getValue() == Checkmark.YES && rep.isManualInput()) {
values[count - 1] += 1000;
}
manualInputs[count - 1] = rep.isManualInput();
}
ArrayList<Checkmark> groupedCheckmarks = new ArrayList<>();
for (int i = 0; i < count; i++)
{
Checkmark rep = new Checkmark(truncatedTimestamps[i], values[i]);
for (int i = 0; i < count; i++) {
Checkmark rep = new Checkmark(truncatedTimestamps[i], values[i], manualInputs[i]);
groupedCheckmarks.add(rep);
}

@ -0,0 +1,103 @@
/*
* 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 javax.annotation.concurrent.ThreadSafe;
import org.apache.commons.lang3.builder.EqualsBuilder;
import org.apache.commons.lang3.builder.HashCodeBuilder;
import org.apache.commons.lang3.builder.ToStringBuilder;
import static org.isoron.uhabits.core.utils.StringUtils.defaultToStringStyle;
/**
* A CheckmarkState represents the completion status of the habit.
*/
@ThreadSafe
public final class CheckmarkState
{
/**
* The value of the checkmark.
* <p>
* For boolean habits, this equals either NO, YES or SKIP.
* <p>
* For numerical habits, this number is stored in thousandths. That
* is, if the user enters value 1.50 on the app, it is stored as 1500.
*/
private final int value;
/**
* The indicator whether the checkmark value was added manually or computed by the algorithm
*/
private final boolean manualInput;
public CheckmarkState(int value, boolean manualInput)
{
this.value = value;
this.manualInput = manualInput;
}
@Override
public boolean equals(Object o)
{
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
CheckmarkState checkmark = (CheckmarkState) o;
return new EqualsBuilder()
.append(value, checkmark.value)
.append(manualInput, checkmark.manualInput)
.isEquals();
}
public int getValue()
{
return value;
}
public boolean isManualInput()
{
return manualInput;
}
@Override
public int hashCode()
{
return new HashCodeBuilder(17, 37)
.append(value)
.append(manualInput)
.toHashCode();
}
@Override
public String toString()
{
return new ToStringBuilder(this, defaultToStringStyle())
.append("value", value)
.append("manualInput", manualInput)
.toString();
}
}

@ -19,6 +19,7 @@
package org.isoron.uhabits.core.models;
import androidx.annotation.NonNull;
import org.apache.commons.lang3.builder.*;
import static org.isoron.uhabits.core.models.Checkmark.*;
@ -34,13 +35,10 @@ public final class Repetition
private final Timestamp timestamp;
/**
* The value of the repetition.
*
* For boolean habits, this equals YES_MANUAL if performed or SKIP if skipped.
* For numerical habits, this number is stored in thousandths. That is, if the user enters
* value 1.50 on the app, it is here stored as 1500.
* The state of the repetition.
*/
private final int value;
@NonNull
private final CheckmarkState state;
/**
* Creates a new repetition with given parameters.
@ -50,19 +48,18 @@ public final class Repetition
*
* @param timestamp the time this repetition occurred.
*/
public Repetition(Timestamp timestamp, int value)
public Repetition(Timestamp timestamp, int value, boolean manualInput)
{
this.timestamp = timestamp;
this.value = value;
this.state = new CheckmarkState(value, manualInput);
}
public static int nextToggleValue(int value)
{
switch(value) {
case NO:
case YES_AUTO:
return YES_MANUAL;
case YES_MANUAL:
return YES;
case YES:
return SKIP;
default:
case SKIP:
@ -81,7 +78,8 @@ public final class Repetition
return new EqualsBuilder()
.append(timestamp, that.timestamp)
.append(value, that.value)
.append(getValue(), that.getValue())
.append(isManualInput(), that.isManualInput())
.isEquals();
}
@ -90,17 +88,30 @@ public final class Repetition
return timestamp;
}
public CheckmarkState getState()
{
return state;
}
public int getValue()
{
return value;
return state.getValue();
}
public boolean isManualInput()
{
return state.isManualInput();
}
@Override
public int hashCode()
{
return new HashCodeBuilder(17, 37)
.append(timestamp)
.append(value)
.append(getValue())
.append(isManualInput())
.toHashCode();
}
@ -109,7 +120,8 @@ public final class Repetition
{
return new ToStringBuilder(this, defaultToStringStyle())
.append("timestamp", timestamp)
.append("value", value)
.append("value", getValue())
.append("manualInput", isManualInput())
.toString();
}
}

@ -140,7 +140,7 @@ public abstract class RepetitionList
for (Repetition r : reps)
{
if (!habit.isNumerical() && r.getValue() != Checkmark.YES_MANUAL)
if (!habit.isNumerical() && !(r.getValue() == Checkmark.YES && r.isManualInput()))
continue;
Calendar date = r.getTimestamp().toCalendar();
@ -197,7 +197,7 @@ public abstract class RepetitionList
if (rep != null) remove(rep);
else
{
rep = new Repetition(timestamp, Checkmark.YES_MANUAL);
rep = new Repetition(timestamp, Checkmark.YES, true);
add(rep);
}
@ -207,11 +207,11 @@ public abstract class RepetitionList
public abstract long getTotalCount();
public void toggle(Timestamp timestamp, int value)
public void toggle(Timestamp timestamp, int value, boolean manualInput)
{
Repetition rep = getByTimestamp(timestamp);
if (rep != null) remove(rep);
add(new Repetition(timestamp, value));
add(new Repetition(timestamp, value, manualInput));
habit.invalidateNewerThan(timestamp);
}

@ -68,7 +68,7 @@ public class MemoryCheckmarkList extends CheckmarkList
{
Timestamp t = to.minus(i);
if(t.isNewerThan(newestComputed) || t.isOlderThan(oldestComputed))
filtered.add(new Checkmark(t, Checkmark.NO));
filtered.add(new Checkmark(t, Checkmark.NO, false));
else
filtered.add(list.get(t.daysUntil(newestComputed)));
}

@ -123,7 +123,7 @@ public class MemoryRepetitionList extends RepetitionList
{
int count = 0;
for (Repetition rep : list)
if (rep.getValue() == Checkmark.YES_MANUAL)
if (rep.getValue() == Checkmark.YES && rep.isManualInput())
count++;
return count;
}

@ -41,17 +41,34 @@ public class RepetitionRecord
@Column
public Integer value;
@Column
public Integer manualInput;
@Column
public Long id;
public boolean isManualInput() {
return manualInput == 1;
}
public void copyFrom(Repetition repetition)
{
timestamp = repetition.getTimestamp().getUnixTime();
value = repetition.getValue();
manualInput = convertToInt(repetition);
}
private int convertToInt(final Repetition repetition) {
if(repetition.isManualInput()) {
return 1;
} else {
return 0;
}
}
public Repetition toRepetition()
{
return new Repetition(new Timestamp(timestamp), value);
return new Repetition(new Timestamp(timestamp), value, isManualInput());
}
}

@ -87,7 +87,7 @@ public class HabitFixtures
for (int i = 0; i < times.length; i++)
{
Timestamp timestamp = today.minus(times[i]);
habit.getRepetitions().add(new Repetition(timestamp, values[i]));
habit.getRepetitions().add(new Repetition(timestamp, values[i], true));
}
return habit;
@ -123,7 +123,7 @@ public class HabitFixtures
for (int i = 0; i < times.length; i++)
{
Timestamp timestamp = reference.minus(times[i]);
habit.getRepetitions().add(new Repetition(timestamp, values[i]));
habit.getRepetitions().add(new Repetition(timestamp, values[i], true));
}
return habit;

@ -93,9 +93,9 @@ public class HabitCardListCache implements CommandRunner.Listener
if (currentFetchTask != null) currentFetchTask.cancel();
}
public synchronized int[] getCheckmarks(long habitId)
public synchronized CheckmarkState[] getCheckmarkStates(long habitId)
{
return data.checkmarks.get(habitId);
return data.checkmarkStates.get(habitId);
}
/**
@ -165,7 +165,7 @@ public class HabitCardListCache implements CommandRunner.Listener
int position = data.habits.indexOf(h);
data.habits.remove(position);
data.id_to_habit.remove(id);
data.checkmarks.remove(id);
data.checkmarkStates.remove(id);
data.scores.remove(id);
listener.onItemRemoved(position);
@ -240,7 +240,7 @@ public class HabitCardListCache implements CommandRunner.Listener
public final List<Habit> habits;
@NonNull
public final HashMap<Long, int[]> checkmarks;
public final HashMap<Long, CheckmarkState[]> checkmarkStates;
@NonNull
public final HashMap<Long, Double> scores;
@ -252,7 +252,7 @@ public class HabitCardListCache implements CommandRunner.Listener
{
id_to_habit = new HashMap<>();
habits = new LinkedList<>();
checkmarks = new HashMap<>();
checkmarkStates = new HashMap<>();
scores = new HashMap<>();
}
@ -260,13 +260,13 @@ public class HabitCardListCache implements CommandRunner.Listener
{
if (oldData == null) throw new NullPointerException();
int[] empty = new int[checkmarkCount];
CheckmarkState[] empty = new CheckmarkState[checkmarkCount];
for (Long id : id_to_habit.keySet())
{
if (oldData.checkmarks.containsKey(id))
checkmarks.put(id, oldData.checkmarks.get(id));
else checkmarks.put(id, empty);
if (oldData.checkmarkStates.containsKey(id))
checkmarkStates.put(id, oldData.checkmarkStates.get(id));
else checkmarkStates.put(id, empty);
}
}
@ -346,9 +346,9 @@ public class HabitCardListCache implements CommandRunner.Listener
if (targetId != null && !targetId.equals(id)) continue;
newData.scores.put(id, habit.getScores().getTodayValue());
newData.checkmarks.put(
newData.checkmarkStates.put(
id,
habit.getCheckmarks().getValues(dateFrom, dateTo));
habit.getCheckmarks().getCheckmarkStates(dateFrom, dateTo));
runner.publishProgress(this, position);
}
@ -381,7 +381,7 @@ public class HabitCardListCache implements CommandRunner.Listener
data.habits.add(position, habit);
data.id_to_habit.put(id, habit);
data.scores.put(id, newData.scores.get(id));
data.checkmarks.put(id, newData.checkmarks.get(id));
data.checkmarkStates.put(id, newData.checkmarkStates.get(id));
listener.onItemInserted(position);
}
@ -398,10 +398,10 @@ public class HabitCardListCache implements CommandRunner.Listener
private synchronized void performUpdate(long id, int position)
{
double oldScore = data.scores.get(id);
int[] oldCheckmarks = data.checkmarks.get(id);
CheckmarkState[] oldCheckmarks = data.checkmarkStates.get(id);
double newScore = newData.scores.get(id);
int[] newCheckmarks = newData.checkmarks.get(id);
CheckmarkState[] newCheckmarks = newData.checkmarkStates.get(id);
boolean unchanged = true;
if (oldScore != newScore) unchanged = false;
@ -409,7 +409,7 @@ public class HabitCardListCache implements CommandRunner.Listener
if (unchanged) return;
data.scores.put(id, newScore);
data.checkmarks.put(id, newCheckmarks);
data.checkmarkStates.put(id, newCheckmarks);
listener.onItemChanged(position);
}

@ -87,7 +87,7 @@ public class ListHabitsBehavior
{
newValue = Math.round(newValue * 1000);
commandRunner.execute(
new CreateRepetitionCommand(habitList, habit, timestamp, (int) newValue),
new CreateRepetitionCommand(habitList, habit, timestamp, (int) newValue, true),
habit.getId());
});
}
@ -149,10 +149,10 @@ public class ListHabitsBehavior
if (prefs.isFirstRun()) onFirstRun();
}
public void onToggle(@NonNull Habit habit, Timestamp timestamp, int value)
public void onToggle(@NonNull Habit habit, Timestamp timestamp, int value, boolean manualInput)
{
commandRunner.execute(
new CreateRepetitionCommand(habitList, habit, timestamp, value),
new CreateRepetitionCommand(habitList, habit, timestamp, value, manualInput),
habit.getId());
}

@ -57,7 +57,7 @@ public class ShowHabitBehavior
screen.showEditHistoryScreen();
}
public void onToggleCheckmark(Timestamp timestamp, int value)
public void onToggleCheckmark(Timestamp timestamp, int value, boolean manualInput)
{
if (habit.isNumerical()) {
CheckmarkList checkmarks = habit.getCheckmarks();
@ -67,12 +67,12 @@ public class ShowHabitBehavior
{
newValue = Math.round(newValue * 1000);
commandRunner.execute(
new CreateRepetitionCommand(habitList, habit, timestamp, (int) newValue),
new CreateRepetitionCommand(habitList, habit, timestamp, (int) newValue, manualInput),
habit.getId());
});
} else {
commandRunner.execute(
new CreateRepetitionCommand(habitList, habit, timestamp, value), null);
new CreateRepetitionCommand(habitList, habit, timestamp, value, manualInput), null);
}
}

@ -110,11 +110,11 @@ public class ShowHabitMenuBehavior
if (i % 7 == 0) strength = max(0, min(100, strength + 10 * random.nextGaussian()));
if (random.nextInt(100) > strength) continue;
int value = Checkmark.YES_MANUAL;
int value = Checkmark.YES;
if (habit.isNumerical())
value = (int) (1000 + 250 * random.nextGaussian() * strength / 100) * 1000;
habit.getRepetitions().add(new Repetition(DateUtils.getToday().minus(i), value));
habit.getRepetitions().add(new Repetition(DateUtils.getToday().minus(i), value, true));
}
habit.invalidateNewerThan(Timestamp.ZERO);

@ -52,7 +52,7 @@ public class WidgetBehavior
notificationTray.cancel(habit);
Repetition rep = habit.getRepetitions().getByTimestamp(timestamp);
if (rep != null) return;
performToggle(habit, timestamp, Checkmark.YES_MANUAL);
performToggle(habit, timestamp, Checkmark.YES);
}
public void onRemoveRepetition(@NonNull Habit habit, Timestamp timestamp)
@ -66,29 +66,34 @@ public class WidgetBehavior
public void onToggleRepetition(@NonNull Habit habit, Timestamp timestamp)
{
Repetition previous = habit.getRepetitions().getByTimestamp(timestamp);
if(previous == null) performToggle(habit, timestamp, Checkmark.YES_MANUAL);
if(previous == null) performToggle(habit, timestamp, Checkmark.YES);
else performToggle(habit, timestamp, Repetition.nextToggleValue(previous.getValue()));
}
private void performToggle(@NonNull Habit habit, Timestamp timestamp, int value)
{
final boolean manualInput = true;
commandRunner.execute(
new CreateRepetitionCommand(habitList, habit, timestamp, value),
new CreateRepetitionCommand(habitList, habit, timestamp, value, manualInput),
habit.getId());
}
public void setNumericValue(@NonNull Habit habit, Timestamp timestamp, int newValue) {
public void setNumericValue(@NonNull Habit habit, Timestamp timestamp, int newValue)
{
final boolean manualInput = true;
commandRunner.execute(
new CreateRepetitionCommand(habitList, habit, timestamp, newValue),
new CreateRepetitionCommand(habitList, habit, timestamp, newValue, manualInput),
habit.getId());
}
public void onIncrement(@NotNull Habit habit, @NotNull Timestamp timestamp, int amount) {
public void onIncrement(@NotNull Habit habit, @NotNull Timestamp timestamp, int amount)
{
int currentValue = habit.getCheckmarks().getValues(timestamp, timestamp)[0];
setNumericValue(habit, timestamp, currentValue + amount);
}
public void onDecrement(@NotNull Habit habit, @NotNull Timestamp timestamp, int amount) {
public void onDecrement(@NotNull Habit habit, @NotNull Timestamp timestamp, int amount)
{
int currentValue = habit.getCheckmarks().getValues(timestamp, timestamp)[0];
setNumericValue(habit, timestamp, currentValue - amount);
}

@ -0,0 +1,4 @@
alter table Repetitions add column manualInput INTEGER not null default 0;
update repetitions set manualInput=1 where value>1 and (select type from habits where id=repetitions.habit)=0; -- yes/no habit
update repetitions set manualInput=1 where value>0 and (select type from habits where id=repetitions.habit)=1; -- numerical habit

@ -38,6 +38,7 @@ import java.io.*;
import java.sql.*;
import java.util.*;
import static org.isoron.uhabits.core.Config.DATABASE_VERSION;
import static org.mockito.Mockito.*;
@RunWith(MockitoJUnitRunner.class)
@ -125,7 +126,7 @@ public class BaseUnitTest
DriverManager.getConnection("jdbc:sqlite::memory:"));
db.execute("pragma user_version=8;");
MigrationHelper helper = new MigrationHelper(db);
helper.migrateTo(23);
helper.migrateTo(DATABASE_VERSION);
return db;
}
catch (SQLException e)

@ -96,13 +96,14 @@ public class CommandParserTest extends BaseUnitTest
public void testDecodeCreateRepCommand() throws JSONException
{
CreateRepetitionCommand original, decoded;
original = new CreateRepetitionCommand(habitList, habit, Timestamp.ZERO.plus(100), 5);
original = new CreateRepetitionCommand(habitList, habit, Timestamp.ZERO.plus(100), 5, true);
decoded = (CreateRepetitionCommand) parser.parse(original.toJson());
MatcherAssert.assertThat(decoded.getId(), equalTo(original.getId()));
MatcherAssert.assertThat(decoded.timestamp, equalTo(original
.timestamp));
MatcherAssert.assertThat(decoded.value, equalTo(original.value));
MatcherAssert.assertThat(decoded.manualInput, equalTo(original.manualInput));
MatcherAssert.assertThat(decoded.habit, equalTo(original.habit));
}

@ -47,7 +47,7 @@ public class CreateRepetitionCommandTest extends BaseUnitTest
habitList.add(habit);
today = DateUtils.getToday();
command = new CreateRepetitionCommand(habitList, habit, today, 100);
command = new CreateRepetitionCommand(habitList, habit, today, 100, true);
}
@Test
@ -57,17 +57,20 @@ public class CreateRepetitionCommandTest extends BaseUnitTest
Repetition rep = reps.getByTimestamp(today);
assertNotNull(rep);
assertEquals(YES_MANUAL, rep.getValue());
assertEquals(YES, rep.getValue());
assertTrue(rep.isManualInput());
command.execute();
rep = reps.getByTimestamp(today);
assertNotNull(rep);
assertEquals(100, rep.getValue());
assertTrue(rep.isManualInput());
command.undo();
rep = reps.getByTimestamp(today);
assertNotNull(rep);
assertEquals(YES_MANUAL, rep.getValue());
assertEquals(YES, rep.getValue());
assertTrue(rep.isManualInput());
}
@Test

@ -68,10 +68,10 @@ public class CheckmarkListTest extends BaseUnitTest
public void test_buildCheckmarksFromIntervals_1() throws Exception
{
Repetition reps[] = new Repetition[]{
new Repetition(day(10), YES_MANUAL),
new Repetition(day(5), YES_MANUAL),
new Repetition(day(2), YES_MANUAL),
new Repetition(day(1), YES_MANUAL),
new Repetition(day(10), YES, true),
new Repetition(day(5), YES, true),
new Repetition(day(2), YES, true),
new Repetition(day(1), YES, true),
};
ArrayList<CheckmarkList.Interval> intervals = new ArrayList<>();
@ -80,17 +80,17 @@ public class CheckmarkListTest extends BaseUnitTest
intervals.add(new CheckmarkList.Interval(day(2), day(2), day(1)));
List<Checkmark> expected = new ArrayList<>();
expected.add(new Checkmark(day(0), NO));
expected.add(new Checkmark(day(1), YES_MANUAL));
expected.add(new Checkmark(day(2), YES_MANUAL));
expected.add(new Checkmark(day(3), NO));
expected.add(new Checkmark(day(4), YES_AUTO));
expected.add(new Checkmark(day(5), YES_MANUAL));
expected.add(new Checkmark(day(6), YES_AUTO));
expected.add(new Checkmark(day(7), NO));
expected.add(new Checkmark(day(8), YES_AUTO));
expected.add(new Checkmark(day(9), YES_AUTO));
expected.add(new Checkmark(day(10), YES_MANUAL));
expected.add(new Checkmark(day(0), NO, false));
expected.add(new Checkmark(day(1), YES, true));
expected.add(new Checkmark(day(2), YES, true));
expected.add(new Checkmark(day(3), NO, false));
expected.add(new Checkmark(day(4), YES, false));
expected.add(new Checkmark(day(5), YES, true));
expected.add(new Checkmark(day(6), YES, false));
expected.add(new Checkmark(day(7), NO, false));
expected.add(new Checkmark(day(8), YES, false));
expected.add(new Checkmark(day(9), YES, false));
expected.add(new Checkmark(day(10), YES, true));
List<Checkmark> actual =
CheckmarkList.buildCheckmarksFromIntervals(reps, intervals);
@ -101,14 +101,14 @@ public class CheckmarkListTest extends BaseUnitTest
public void test_buildCheckmarksFromIntervals_2() throws Exception
{
Repetition reps[] = new Repetition[]{
new Repetition(day(0), YES_MANUAL),
new Repetition(day(0), YES, true),
};
ArrayList<CheckmarkList.Interval> intervals = new ArrayList<>();
intervals.add(new CheckmarkList.Interval(day(0), day(0), day(-10)));
List<Checkmark> expected = new ArrayList<>();
expected.add(new Checkmark(day(0), YES_MANUAL));
expected.add(new Checkmark(day(0), YES, true));
List<Checkmark> actual =
CheckmarkList.buildCheckmarksFromIntervals(reps, intervals);
@ -119,9 +119,9 @@ public class CheckmarkListTest extends BaseUnitTest
public void test_buildIntervals_1() throws Exception
{
Repetition reps[] = new Repetition[]{
new Repetition(day(23), YES_MANUAL),
new Repetition(day(18), YES_MANUAL),
new Repetition(day(8), YES_MANUAL),
new Repetition(day(23), YES, true),
new Repetition(day(18), YES, true),
new Repetition(day(8), YES, true),
};
ArrayList<CheckmarkList.Interval> expected = new ArrayList<>();
@ -138,9 +138,9 @@ public class CheckmarkListTest extends BaseUnitTest
public void test_buildIntervals_2() throws Exception
{
Repetition reps[] = new Repetition[]{
new Repetition(day(23), YES_MANUAL),
new Repetition(day(18), YES_MANUAL),
new Repetition(day(8), YES_MANUAL),
new Repetition(day(23), YES, true),
new Repetition(day(18), YES, true),
new Repetition(day(8), YES, true),
};
ArrayList<CheckmarkList.Interval> expected = new ArrayList<>();
@ -157,11 +157,11 @@ public class CheckmarkListTest extends BaseUnitTest
public void test_buildIntervals_3() throws Exception
{
Repetition reps[] = new Repetition[]{
new Repetition(day(23), YES_MANUAL),
new Repetition(day(22), YES_MANUAL),
new Repetition(day(18), YES_MANUAL),
new Repetition(day(15), YES_MANUAL),
new Repetition(day(8), YES_MANUAL),
new Repetition(day(23), YES, true),
new Repetition(day(22), YES, true),
new Repetition(day(18), YES, true),
new Repetition(day(15), YES, true),
new Repetition(day(8), YES, true),
};
ArrayList<CheckmarkList.Interval> expected = new ArrayList<>();
@ -180,9 +180,9 @@ public class CheckmarkListTest extends BaseUnitTest
public void test_buildIntervals_4() throws Exception
{
Repetition[] reps = new Repetition[]{
new Repetition(day(30), YES_MANUAL),
new Repetition(day(20), SKIP),
new Repetition(day(10), YES_MANUAL),
new Repetition(day(30), YES, true),
new Repetition(day(20), SKIP, true),
new Repetition(day(10), YES, true),
};
ArrayList<CheckmarkList.Interval> expected = new ArrayList<>();
@ -199,17 +199,17 @@ public class CheckmarkListTest extends BaseUnitTest
{
travelInTime(-3);
int[] expectedValues = {
YES_MANUAL,
YES_MANUAL,
YES_MANUAL,
YES_AUTO,
YES_AUTO,
YES_MANUAL,
YES_MANUAL
CheckmarkState[] expectedValues = {
new CheckmarkState(YES, true),
new CheckmarkState(YES, true),
new CheckmarkState(YES, true),
new CheckmarkState(YES, false),
new CheckmarkState(YES, false),
new CheckmarkState(YES, true),
new CheckmarkState(YES, true)
};
int[] actualValues = nonDailyHabit.getCheckmarks().getAllValues();
CheckmarkState[] actualValues = nonDailyHabit.getCheckmarks().getAllStates();
assertThat(actualValues, equalTo(expectedValues));
}
@ -219,23 +219,23 @@ public class CheckmarkListTest extends BaseUnitTest
{
travelInTime(3);
int[] expectedValues = {
NO,
NO,
NO,
YES_MANUAL,
NO,
YES_AUTO,
YES_MANUAL,
YES_MANUAL,
YES_MANUAL,
YES_AUTO,
YES_AUTO,
YES_MANUAL,
YES_MANUAL
CheckmarkState[] expectedValues = {
new CheckmarkState(NO, false),
new CheckmarkState(NO, false),
new CheckmarkState(NO, false),
new CheckmarkState(YES, true),
new CheckmarkState(NO, false),
new CheckmarkState(YES, false),
new CheckmarkState(YES, true),
new CheckmarkState(YES, true),
new CheckmarkState(YES, true),
new CheckmarkState(YES, false),
new CheckmarkState(YES, false),
new CheckmarkState(YES, true),
new CheckmarkState(YES, true)
};
int[] actualValues = nonDailyHabit.getCheckmarks().getAllValues();
CheckmarkState[] actualValues = nonDailyHabit.getCheckmarks().getAllStates();
assertThat(actualValues, equalTo(expectedValues));
}
@ -243,8 +243,8 @@ public class CheckmarkListTest extends BaseUnitTest
@Test
public void test_getAllValues_withEmptyHabit()
{
int[] expectedValues = new int[0];
int[] actualValues = emptyHabit.getCheckmarks().getAllValues();
CheckmarkState[] expectedValues = new CheckmarkState[0];
CheckmarkState[] actualValues = emptyHabit.getCheckmarks().getAllStates();
assertThat(actualValues, equalTo(expectedValues));
}
@ -252,20 +252,20 @@ public class CheckmarkListTest extends BaseUnitTest
@Test
public void test_getAllValues_withNonDailyHabit()
{
int[] expectedValues = {
YES_MANUAL,
NO,
YES_AUTO,
YES_MANUAL,
YES_MANUAL,
YES_MANUAL,
YES_AUTO,
YES_AUTO,
YES_MANUAL,
YES_MANUAL
CheckmarkState[] expectedValues = {
new CheckmarkState(YES, true),
new CheckmarkState(NO, false),
new CheckmarkState(YES, false),
new CheckmarkState(YES, true),
new CheckmarkState(YES, true),
new CheckmarkState(YES, true),
new CheckmarkState(YES, false),
new CheckmarkState(YES, false),
new CheckmarkState(YES, true),
new CheckmarkState(YES, true)
};
int[] actualValues = nonDailyHabit.getCheckmarks().getAllValues();
CheckmarkState[] actualValues = nonDailyHabit.getCheckmarks().getAllStates();
assertThat(actualValues, equalTo(expectedValues));
}
@ -276,9 +276,9 @@ public class CheckmarkListTest extends BaseUnitTest
CheckmarkList checkmarks = numericalHabit.getCheckmarks();
List<Checkmark> expected =
Arrays.asList(new Checkmark(day(1), 200), new Checkmark(day(2), 0),
new Checkmark(day(3), 300), new Checkmark(day(4), 0),
new Checkmark(day(5), 400));
Arrays.asList(new Checkmark(day(1), 200, true), new Checkmark(day(2), 0, false),
new Checkmark(day(3), 300, true), new Checkmark(day(4), 0, false),
new Checkmark(day(5), 400, true));
List<Checkmark> actual = checkmarks.getByInterval(day(5), day(1));
assertThat(actual, equalTo(expected));
@ -293,7 +293,8 @@ public class CheckmarkListTest extends BaseUnitTest
assertThat(checkmarks.getTodayValue(), equalTo(NO));
travelInTime(0);
assertThat(checkmarks.getTodayValue(), equalTo(YES_MANUAL));
assertThat(checkmarks.getTodayValue(), equalTo(YES));
assertThat(checkmarks.getTodayState().isManualInput(), equalTo(true));
travelInTime(1);
assertThat(checkmarks.getTodayValue(), equalTo(NO));
@ -302,10 +303,10 @@ public class CheckmarkListTest extends BaseUnitTest
@Test
public void test_getValues_withInvalidInterval()
{
int values[] = nonDailyHabit
CheckmarkState[] values = nonDailyHabit
.getCheckmarks()
.getValues(new Timestamp(0L).plus(100), new Timestamp(0L));
assertThat(values, equalTo(new int[0]));
.getCheckmarkStates(new Timestamp(0L).plus(100), new Timestamp(0L));
assertThat(values, equalTo(new CheckmarkState[0]));
}
@Test
@ -314,21 +315,21 @@ public class CheckmarkListTest extends BaseUnitTest
Timestamp from = today.minus(15);
Timestamp to = today.minus(5);
int[] expectedValues = {
YES_MANUAL,
YES_AUTO,
YES_AUTO,
YES_MANUAL,
YES_MANUAL,
NO,
NO,
NO,
NO,
NO,
NO
CheckmarkState[] expectedValues = {
new CheckmarkState(YES, true),
new CheckmarkState(YES, false),
new CheckmarkState(YES, false),
new CheckmarkState(YES, true),
new CheckmarkState(YES, true),
new CheckmarkState(NO, false),
new CheckmarkState(NO, false),
new CheckmarkState(NO, false),
new CheckmarkState(NO, false),
new CheckmarkState(NO, false),
new CheckmarkState(NO, false)
};
int[] actualValues = nonDailyHabit.getCheckmarks().getValues(from, to);
CheckmarkState[] actualValues = nonDailyHabit.getCheckmarks().getCheckmarkStates(from, to);
assertThat(actualValues, equalTo(expectedValues));
}
@ -369,10 +370,10 @@ public class CheckmarkListTest extends BaseUnitTest
@Test
public void test_writeCSV() throws IOException
{
String expectedCSV = "2015-01-25,2\n2015-01-24,0\n2015-01-23,1\n" +
"2015-01-22,2\n2015-01-21,2\n2015-01-20,2\n" +
"2015-01-19,1\n2015-01-18,1\n2015-01-17,2\n" +
"2015-01-16,2\n";
String expectedCSV = "2015-01-25,1,1\n2015-01-24,0,0\n2015-01-23,1,0\n" +
"2015-01-22,1,1\n2015-01-21,1,1\n2015-01-20,1,1\n" +
"2015-01-19,1,0\n2015-01-18,1,0\n2015-01-17,1,1\n" +
"2015-01-16,1,1\n";
StringWriter writer = new StringWriter();
@ -396,9 +397,9 @@ public class CheckmarkListTest extends BaseUnitTest
public void testToString() throws Exception
{
Timestamp t = Timestamp.ZERO.plus(100);
Checkmark checkmark = new Checkmark(t, 2);
Checkmark checkmark = new Checkmark(t, 2, true);
assertThat(checkmark.toString(),
equalTo("{timestamp: 1970-04-11, value: 2}"));
equalTo("{timestamp: 1970-04-11, value: 2, manualInput: true}"));
CheckmarkList.Interval interval =
new CheckmarkList.Interval(t, t.plus(1), t.plus(2));
@ -422,22 +423,22 @@ public class CheckmarkListTest extends BaseUnitTest
List<Checkmark> byMonth = checkmarks.groupBy(MONTH, Calendar.SATURDAY);
assertThat(byMonth.size(), equalTo(25)); // from 2013-01-01 to 2015-01-01
assertThat(byMonth.get(0), equalTo(new Checkmark(timestamp(2015, JANUARY, 1), 0)));
assertThat(byMonth.get(6), equalTo(new Checkmark(timestamp(2014, JULY, 1), 0)));
assertThat(byMonth.get(12), equalTo(new Checkmark(timestamp(2014, JANUARY, 1), 1706)));
assertThat(byMonth.get(18), equalTo(new Checkmark(timestamp(2013, JULY, 1), 1379)));
assertThat(byMonth.get(0), equalTo(new Checkmark(timestamp(2015, JANUARY, 1), 0, false)));
assertThat(byMonth.get(6), equalTo(new Checkmark(timestamp(2014, JULY, 1), 0, false)));
assertThat(byMonth.get(12), equalTo(new Checkmark(timestamp(2014, JANUARY, 1), 1706, true)));
assertThat(byMonth.get(18), equalTo(new Checkmark(timestamp(2013, JULY, 1), 1379, true)));
List<Checkmark> byQuarter = checkmarks.groupBy(QUARTER, Calendar.SATURDAY);
assertThat(byQuarter.size(), equalTo(9)); // from 2013-Q1 to 2015-Q1
assertThat(byQuarter.get(0), equalTo(new Checkmark(timestamp(2015, JANUARY, 1), 0)));
assertThat(byQuarter.get(4), equalTo(new Checkmark(timestamp(2014, JANUARY, 1), 4964)));
assertThat(byQuarter.get(8), equalTo(new Checkmark(timestamp(2013, JANUARY, 1), 4975)));
assertThat(byQuarter.get(0), equalTo(new Checkmark(timestamp(2015, JANUARY, 1), 0, false)));
assertThat(byQuarter.get(4), equalTo(new Checkmark(timestamp(2014, JANUARY, 1), 4964, true)));
assertThat(byQuarter.get(8), equalTo(new Checkmark(timestamp(2013, JANUARY, 1), 4975, true)));
List<Checkmark> byYear = checkmarks.groupBy(YEAR, Calendar.SATURDAY);
assertThat(byYear.size(), equalTo(3)); // from 2013 to 2015
assertThat(byYear.get(0), equalTo(new Checkmark(timestamp(2015, JANUARY, 1), 0)));
assertThat(byYear.get(1), equalTo(new Checkmark(timestamp(2014, JANUARY, 1), 8227)));
assertThat(byYear.get(2), equalTo(new Checkmark(timestamp(2013, JANUARY, 1), 16172)));
assertThat(byYear.get(0), equalTo(new Checkmark(timestamp(2015, JANUARY, 1), 0, false)));
assertThat(byYear.get(1), equalTo(new Checkmark(timestamp(2014, JANUARY, 1), 8227, true)));
assertThat(byYear.get(2), equalTo(new Checkmark(timestamp(2013, JANUARY, 1), 16172, true)));
}
@Test

@ -102,19 +102,19 @@ public class HabitTest extends BaseUnitTest
h.setTargetValue(100.0);
assertFalse(h.isCompletedToday());
h.getRepetitions().toggle(getToday(), 200_000);
h.getRepetitions().toggle(getToday(), 200_000, true);
assertTrue(h.isCompletedToday());
h.getRepetitions().toggle(getToday(), 100_000);
h.getRepetitions().toggle(getToday(), 100_000, true);
assertTrue(h.isCompletedToday());
h.getRepetitions().toggle(getToday(), 50_000);
h.getRepetitions().toggle(getToday(), 50_000, true);
assertFalse(h.isCompletedToday());
h.setTargetType(Habit.AT_MOST);
h.getRepetitions().toggle(getToday(), 200_000);
h.getRepetitions().toggle(getToday(), 200_000, true);
assertFalse(h.isCompletedToday());
h.getRepetitions().toggle(getToday(), 100_000);
h.getRepetitions().toggle(getToday(), 100_000, true);
assertTrue(h.isCompletedToday());
h.getRepetitions().toggle(getToday(), 50_000);
h.getRepetitions().toggle(getToday(), 50_000, true);
assertTrue(h.isCompletedToday());
}

@ -170,17 +170,19 @@ public class RepetitionListTest extends BaseUnitTest
reset(listener);
habit.setType(Habit.NUMBER_HABIT);
reps.toggle(today, 100);
reps.toggle(today, 100, true);
Repetition check = reps.getByTimestamp(today);
assertNotNull(check);
assertThat(check.getValue(), equalTo(100));
assertThat(check.isManualInput(), equalTo(true));
verify(listener).onModelChange();
reset(listener);
reps.toggle(today, 500);
reps.toggle(today, 500, true);
check = reps.getByTimestamp(today);
assertNotNull(check);
assertThat(check.getValue(), equalTo(500));
assertThat(check.isManualInput(), equalTo(true));
verify(listener, times(2)).onModelChange();
reset(listener);
}
@ -188,7 +190,7 @@ public class RepetitionListTest extends BaseUnitTest
@Test
public void testToString() throws Exception
{
Repetition rep = new Repetition(Timestamp.ZERO.plus(100), 20);
assertThat(rep.toString(), equalTo("{timestamp: 1970-04-11, value: 20}"));
Repetition rep = new Repetition(Timestamp.ZERO.plus(100), 20, true);
assertThat(rep.toString(), equalTo("{timestamp: 1970-04-11, value: 20, manualInput: true}"));
}
}

@ -70,12 +70,13 @@ public class SQLiteRepetitionListTest extends BaseUnitTest
RepetitionRecord record = getByTimestamp(today.plus(1));
assertNull(record);
Repetition rep = new Repetition(today.plus(1), YES_MANUAL);
Repetition rep = new Repetition(today.plus(1), YES, true);
habit.getRepetitions().add(rep);
record = getByTimestamp(today.plus(1));
assertNotNull(record);
assertThat(record.value, equalTo(YES_MANUAL));
assertThat(record.value, equalTo(YES));
assertThat(record.isManualInput(), equalTo(true));
}
@Test

@ -33,7 +33,7 @@ public class RepetitionRecordTest extends BaseUnitTest
@Test
public void testRecord() throws Exception
{
Repetition rep = new Repetition(Timestamp.ZERO.plus(100), 50);
Repetition rep = new Repetition(Timestamp.ZERO.plus(100), 50, true);
RepetitionRecord record = new RepetitionRecord();
record.copyFrom(rep);
assertThat(rep, equalTo(record.toRepetition()));

@ -84,7 +84,7 @@ public class HabitCardListCacheTest extends BaseUnitTest
{
Habit h2 = habitList.getByPosition(2);
Timestamp today = DateUtils.getToday();
commandRunner.execute(new CreateRepetitionCommand(habitList, h2, today, Checkmark.NO), h2.getId());
commandRunner.execute(new CreateRepetitionCommand(habitList, h2, today, Checkmark.NO, false), h2.getId());
verify(listener).onItemChanged(2);
verify(listener).onRefreshFinished();
verifyNoMoreInteractions(listener);
@ -103,9 +103,9 @@ public class HabitCardListCacheTest extends BaseUnitTest
assertThat(cache.getScore(h.getId()), equalTo(score));
Timestamp today = DateUtils.getToday();
int[] actualCheckmarks = cache.getCheckmarks(h.getId());
int[] expectedCheckmarks =
h.getCheckmarks().getValues(today.minus(9), today);
CheckmarkState[] actualCheckmarks = cache.getCheckmarkStates(h.getId());
CheckmarkState[] expectedCheckmarks =
h.getCheckmarks().getCheckmarkStates(today.minus(9), today);
assertThat(actualCheckmarks, equalTo(expectedCheckmarks));
}

@ -173,7 +173,7 @@ public class ListHabitsBehaviorTest extends BaseUnitTest
public void testOnToggle()
{
assertTrue(habit1.isCompletedToday());
behavior.onToggle(habit1, DateUtils.getToday(), Checkmark.NO);
behavior.onToggle(habit1, DateUtils.getToday(), Checkmark.NO, false);
assertFalse(habit1.isCompletedToday());
}

Loading…
Cancel
Save