From 5403ca3ef4bc1942a7ad7599b494accc9ce7fb85 Mon Sep 17 00:00:00 2001 From: Christoph Hennemann Date: Mon, 14 Sep 2020 02:06:08 +0200 Subject: [PATCH] feat: Decouple the checkmark state from the source of the input --- .../common/views/HistoryChartTest.java | 4 +- .../common/dialogs/HistoryEditorDialog.java | 6 +- .../activities/common/views/BarChart.java | 2 +- .../activities/common/views/HistoryChart.java | 42 ++- .../habits/list/views/CheckmarkButtonView.kt | 15 +- .../habits/list/views/CheckmarkPanelView.kt | 8 +- .../list/views/HabitCardListAdapter.java | 5 +- .../habits/list/views/HabitCardListView.kt | 5 +- .../habits/list/views/HabitCardView.kt | 6 +- .../habits/show/ShowHabitScreen.java | 4 +- .../habits/show/views/HistoryCard.java | 4 +- .../isoron/uhabits/widgets/CheckmarkWidget.kt | 14 +- .../isoron/uhabits/widgets/HistoryWidget.kt | 2 +- .../widgets/views/CheckmarkWidgetView.java | 121 +++--- .../receivers/WidgetControllerTest.java | 12 +- .../java/org/isoron/uhabits/core/Config.java | 2 +- .../commands/CreateRepetitionCommand.java | 16 +- .../uhabits/core/io/LoopDBImporter.java | 2 +- .../isoron/uhabits/core/models/Checkmark.java | 44 +-- .../uhabits/core/models/CheckmarkList.java | 353 ++++++++++++------ .../uhabits/core/models/CheckmarkState.java | 103 +++++ .../uhabits/core/models/Repetition.java | 42 ++- .../uhabits/core/models/RepetitionList.java | 8 +- .../models/memory/MemoryCheckmarkList.java | 2 +- .../models/memory/MemoryRepetitionList.java | 2 +- .../sqlite/records/RepetitionRecord.java | 19 +- .../uhabits/core/test/HabitFixtures.java | 4 +- .../habits/list/HabitCardListCache.java | 30 +- .../habits/list/ListHabitsBehavior.java | 6 +- .../habits/show/ShowHabitBehavior.java | 6 +- .../habits/show/ShowHabitMenuBehavior.java | 4 +- .../core/ui/widgets/WidgetBehavior.java | 19 +- .../src/main/resources/migrations/24.sql | 4 + .../org/isoron/uhabits/core/BaseUnitTest.java | 3 +- .../core/commands/CommandParserTest.java | 5 +- .../commands/CreateRepetitionCommandTest.java | 9 +- .../core/models/CheckmarkListTest.java | 211 +++++------ .../isoron/uhabits/core/models/HabitTest.java | 14 +- .../core/models/RepetitionListTest.java | 12 +- .../sqlite/SQLiteRepetitionListTest.java | 5 +- .../sqlite/records/RepetitionRecordTest.java | 2 +- .../habits/list/HabitCardListCacheTest.java | 10 +- .../habits/list/ListHabitsBehaviorTest.java | 4 +- 43 files changed, 740 insertions(+), 451 deletions(-) create mode 100644 android/uhabits-core/src/main/java/org/isoron/uhabits/core/models/CheckmarkState.java create mode 100644 android/uhabits-core/src/main/resources/migrations/24.sql diff --git a/android/uhabits-android/src/androidTest/java/org/isoron/uhabits/activities/common/views/HistoryChartTest.java b/android/uhabits-android/src/androidTest/java/org/isoron/uhabits/activities/common/views/HistoryChartTest.java index 1c88ed265..e4dfb2075 100644 --- a/android/uhabits-android/src/androidTest/java/org/isoron/uhabits/activities/common/views/HistoryChartTest.java +++ b/android/uhabits-android/src/androidTest/java/org/isoron/uhabits/activities/common/views/HistoryChartTest.java @@ -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); diff --git a/android/uhabits-android/src/main/java/org/isoron/uhabits/activities/common/dialogs/HistoryEditorDialog.java b/android/uhabits-android/src/main/java/org/isoron/uhabits/activities/common/dialogs/HistoryEditorDialog.java index dd5bd5525..b6f7f62db 100644 --- a/android/uhabits-android/src/main/java/org/isoron/uhabits/activities/common/dialogs/HistoryEditorDialog.java +++ b/android/uhabits-android/src/main/java/org/isoron/uhabits/activities/common/dialogs/HistoryEditorDialog.java @@ -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()); } diff --git a/android/uhabits-android/src/main/java/org/isoron/uhabits/activities/common/views/BarChart.java b/android/uhabits-android/src/main/java/org/isoron/uhabits/activities/common/views/BarChart.java index 3a420c811..443d1ee72 100644 --- a/android/uhabits-android/src/main/java/org/isoron/uhabits/activities/common/views/BarChart.java +++ b/android/uhabits-android/src/main/java/org/isoron/uhabits/activities/common/views/BarChart.java @@ -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); diff --git a/android/uhabits-android/src/main/java/org/isoron/uhabits/activities/common/views/HistoryChart.java b/android/uhabits-android/src/main/java/org/isoron/uhabits/activities/common/views/HistoryChart.java index 611271cbd..21d494c91 100644 --- a/android/uhabits-android/src/main/java/org/isoron/uhabits/activities/common/views/HistoryChart.java +++ b/android/uhabits-android/src/main/java/org/isoron/uhabits/activities/common/views/HistoryChart.java @@ -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) {} } } diff --git a/android/uhabits-android/src/main/java/org/isoron/uhabits/activities/habits/list/views/CheckmarkButtonView.kt b/android/uhabits-android/src/main/java/org/isoron/uhabits/activities/habits/list/views/CheckmarkButtonView.kt index eded35446..d1053c6eb 100644 --- a/android/uhabits-android/src/main/java/org/isoron/uhabits/activities/habits/list/views/CheckmarkButtonView.kt +++ b/android/uhabits-android/src/main/java/org/isoron/uhabits/activities/habits/list/views/CheckmarkButtonView.kt @@ -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 diff --git a/android/uhabits-android/src/main/java/org/isoron/uhabits/activities/habits/list/views/CheckmarkPanelView.kt b/android/uhabits-android/src/main/java/org/isoron/uhabits/activities/habits/list/views/CheckmarkPanelView.kt index 53194abae..004d8ed60 100644 --- a/android/uhabits-android/src/main/java/org/isoron/uhabits/activities/habits/list/views/CheckmarkPanelView.kt +++ b/android/uhabits-android/src/main/java/org/isoron/uhabits/activities/habits/list/views/CheckmarkPanelView.kt @@ -34,7 +34,7 @@ class CheckmarkPanelView( @Provided private val buttonFactory: CheckmarkButtonViewFactory ) : ButtonPanelView(context, preferences) { - var values = IntArray(0) + var values: Array = 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) } } } } diff --git a/android/uhabits-android/src/main/java/org/isoron/uhabits/activities/habits/list/views/HabitCardListAdapter.java b/android/uhabits-android/src/main/java/org/isoron/uhabits/activities/habits/list/views/HabitCardListAdapter.java index 62394690e..ff9fd1a24 100644 --- a/android/uhabits-android/src/main/java/org/isoron/uhabits/activities/habits/list/views/HabitCardListAdapter.java +++ b/android/uhabits-android/src/main/java/org/isoron/uhabits/activities/habits/list/views/HabitCardListAdapter.java @@ -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 diff --git a/android/uhabits-android/src/main/java/org/isoron/uhabits/activities/habits/list/views/HabitCardListView.kt b/android/uhabits-android/src/main/java/org/isoron/uhabits/activities/habits/list/views/HabitCardListView.kt index 02f1d6421..87782fbf7 100644 --- a/android/uhabits-android/src/main/java/org/isoron/uhabits/activities/habits/list/views/HabitCardListView.kt +++ b/android/uhabits-android/src/main/java/org/isoron/uhabits/activities/habits/list/views/HabitCardListView.kt @@ -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, 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 diff --git a/android/uhabits-android/src/main/java/org/isoron/uhabits/activities/habits/list/views/HabitCardView.kt b/android/uhabits-android/src/main/java/org/isoron/uhabits/activities/habits/list/views/HabitCardView.kt index e7891253d..0e6195e82 100644 --- a/android/uhabits-android/src/main/java/org/isoron/uhabits/activities/habits/list/views/HabitCardView.kt +++ b/android/uhabits-android/src/main/java/org/isoron/uhabits/activities/habits/list/views/HabitCardView.kt @@ -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) } } } diff --git a/android/uhabits-android/src/main/java/org/isoron/uhabits/activities/habits/show/ShowHabitScreen.java b/android/uhabits-android/src/main/java/org/isoron/uhabits/activities/habits/show/ShowHabitScreen.java index cf1dcfbd2..0df45f70e 100644 --- a/android/uhabits-android/src/main/java/org/isoron/uhabits/activities/habits/show/ShowHabitScreen.java +++ b/android/uhabits-android/src/main/java/org/isoron/uhabits/activities/habits/show/ShowHabitScreen.java @@ -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 diff --git a/android/uhabits-android/src/main/java/org/isoron/uhabits/activities/habits/show/views/HistoryCard.java b/android/uhabits-android/src/main/java/org/isoron/uhabits/activities/habits/show/views/HistoryCard.java index 82110d410..34844f125 100644 --- a/android/uhabits-android/src/main/java/org/isoron/uhabits/activities/habits/show/views/HistoryCard.java +++ b/android/uhabits-android/src/main/java/org/isoron/uhabits/activities/habits/show/views/HistoryCard.java @@ -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 diff --git a/android/uhabits-android/src/main/java/org/isoron/uhabits/widgets/CheckmarkWidget.kt b/android/uhabits-android/src/main/java/org/isoron/uhabits/widgets/CheckmarkWidget.kt index 4b97b38ba..3ee78938b 100644 --- a/android/uhabits-android/src/main/java/org/isoron/uhabits/widgets/CheckmarkWidget.kt +++ b/android/uhabits-android/src/main/java/org/isoron/uhabits/widgets/CheckmarkWidget.kt @@ -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) } } diff --git a/android/uhabits-android/src/main/java/org/isoron/uhabits/widgets/HistoryWidget.kt b/android/uhabits-android/src/main/java/org/isoron/uhabits/widgets/HistoryWidget.kt index a1abb02b2..8235c08e9 100644 --- a/android/uhabits-android/src/main/java/org/isoron/uhabits/widgets/HistoryWidget.kt +++ b/android/uhabits-android/src/main/java/org/isoron/uhabits/widgets/HistoryWidget.kt @@ -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) } diff --git a/android/uhabits-android/src/main/java/org/isoron/uhabits/widgets/views/CheckmarkWidgetView.java b/android/uhabits-android/src/main/java/org/isoron/uhabits/widgets/views/CheckmarkWidgetView.java index 8b4e08bc8..ba30c8610 100644 --- a/android/uhabits-android/src/main/java/org/isoron/uhabits/widgets/views/CheckmarkWidgetView.java +++ b/android/uhabits-android/src/main/java/org/isoron/uhabits/widgets/views/CheckmarkWidgetView.java @@ -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(); } } diff --git a/android/uhabits-android/src/test/java/org/isoron/uhabits/receivers/WidgetControllerTest.java b/android/uhabits-android/src/test/java/org/isoron/uhabits/receivers/WidgetControllerTest.java index 8ab95144d..d9b5ed454 100644 --- a/android/uhabits-android/src/test/java/org/isoron/uhabits/receivers/WidgetControllerTest.java +++ b/android/uhabits-android/src/test/java/org/isoron/uhabits/receivers/WidgetControllerTest.java @@ -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()); } @@ -102,4 +104,4 @@ public class WidgetControllerTest extends BaseAndroidJVMTest controller.onToggleRepetition(habit, today); verify(commandRunner).execute(any(), isNull()); } -} \ No newline at end of file +} diff --git a/android/uhabits-core/src/main/java/org/isoron/uhabits/core/Config.java b/android/uhabits-core/src/main/java/org/isoron/uhabits/core/Config.java index 286755a33..1a5383cee 100644 --- a/android/uhabits-core/src/main/java/org/isoron/uhabits/core/Config.java +++ b/android/uhabits-core/src/main/java/org/isoron/uhabits/core/Config.java @@ -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; } diff --git a/android/uhabits-core/src/main/java/org/isoron/uhabits/core/commands/CreateRepetitionCommand.java b/android/uhabits-core/src/main/java/org/isoron/uhabits/core/commands/CreateRepetitionCommand.java index dfcedb5ee..f0df94fb5 100644 --- a/android/uhabits-core/src/main/java/org/isoron/uhabits/core/commands/CreateRepetitionCommand.java +++ b/android/uhabits-core/src/main/java/org/isoron/uhabits/core/commands/CreateRepetitionCommand.java @@ -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,10 +130,9 @@ 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; } } -} \ No newline at end of file +} diff --git a/android/uhabits-core/src/main/java/org/isoron/uhabits/core/io/LoopDBImporter.java b/android/uhabits-core/src/main/java/org/isoron/uhabits/core/io/LoopDBImporter.java index 31553d75f..6da680d73 100644 --- a/android/uhabits-core/src/main/java/org/isoron/uhabits/core/io/LoopDBImporter.java +++ b/android/uhabits-core/src/main/java/org/isoron/uhabits/core/io/LoopDBImporter.java @@ -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()); } } } diff --git a/android/uhabits-core/src/main/java/org/isoron/uhabits/core/models/Checkmark.java b/android/uhabits-core/src/main/java/org/isoron/uhabits/core/models/Checkmark.java index a3150e150..982a64ec4 100644 --- a/android/uhabits-core/src/main/java/org/isoron/uhabits/core/models/Checkmark.java +++ b/android/uhabits-core/src/main/java/org/isoron/uhabits/core/models/Checkmark.java @@ -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. - *

- * For boolean habits, this equals either NO, YES_AUTO, YES_MANUAL or SKIP. - *

- * 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(); } } diff --git a/android/uhabits-core/src/main/java/org/isoron/uhabits/core/models/CheckmarkList.java b/android/uhabits-core/src/main/java/org/isoron/uhabits/core/models/CheckmarkList.java index 5b3f3aed7..77580fe10 100644 --- a/android/uhabits-core/src/main/java/org/isoron/uhabits/core/models/CheckmarkList.java +++ b/android/uhabits-core/src/main/java/org/isoron/uhabits/core/models/CheckmarkList.java @@ -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 buildCheckmarksFromIntervals(Repetition[] reps, - ArrayList intervals) - { - if (reps.length == 0) throw new IllegalArgumentException(); + static List buildCheckmarksFromIntervals( + Repetition[] reps, + ArrayList 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 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 buildIntervals(@NonNull Frequency freq, - @NonNull Repetition[] reps) - { + static ArrayList buildIntervals( + @NonNull Frequency freq, + @NonNull Repetition[] reps + ) { ArrayList 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 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 intervals) - { + static void snapIntervalsTogether(@NonNull ArrayList 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. *

@@ -162,6 +178,7 @@ public abstract class CheckmarkList */ public abstract void add(List 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. + *

+ * 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. *

@@ -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 getByInterval(Timestamp fromTimestamp, - Timestamp toTimestamp); + public abstract List 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 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 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. + *

+ * 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 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 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 intervals; intervals = buildIntervals(habit.getFrequency(), reps); snapIntervalsTogether(intervals); add(buildCheckmarksFromIntervals(reps, intervals)); } + public List 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 groupBy(DateUtils.TruncateField field, int firstWeekday) - { + public List groupBy(DateUtils.TruncateField field, int firstWeekday) { return groupBy(field, firstWeekday, 0); } @NonNull - public List groupBy(DateUtils.TruncateField field, - int firstWeekday, - int maxGroups) - { + public List groupBy( + DateUtils.TruncateField field, + int firstWeekday, + int maxGroups + ) { List 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 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); } diff --git a/android/uhabits-core/src/main/java/org/isoron/uhabits/core/models/CheckmarkState.java b/android/uhabits-core/src/main/java/org/isoron/uhabits/core/models/CheckmarkState.java new file mode 100644 index 000000000..4d5e084ea --- /dev/null +++ b/android/uhabits-core/src/main/java/org/isoron/uhabits/core/models/CheckmarkState.java @@ -0,0 +1,103 @@ +/* + * Copyright (C) 2016 Álinson Santos Xavier + * + * 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 . + */ + +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. + *

+ * For boolean habits, this equals either NO, YES or SKIP. + *

+ * 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(); + } +} diff --git a/android/uhabits-core/src/main/java/org/isoron/uhabits/core/models/Repetition.java b/android/uhabits-core/src/main/java/org/isoron/uhabits/core/models/Repetition.java index 8b5fa2ff9..4e6787bee 100644 --- a/android/uhabits-core/src/main/java/org/isoron/uhabits/core/models/Repetition.java +++ b/android/uhabits-core/src/main/java/org/isoron/uhabits/core/models/Repetition.java @@ -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(); } } diff --git a/android/uhabits-core/src/main/java/org/isoron/uhabits/core/models/RepetitionList.java b/android/uhabits-core/src/main/java/org/isoron/uhabits/core/models/RepetitionList.java index 2196c7304..52bb01424 100644 --- a/android/uhabits-core/src/main/java/org/isoron/uhabits/core/models/RepetitionList.java +++ b/android/uhabits-core/src/main/java/org/isoron/uhabits/core/models/RepetitionList.java @@ -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); } diff --git a/android/uhabits-core/src/main/java/org/isoron/uhabits/core/models/memory/MemoryCheckmarkList.java b/android/uhabits-core/src/main/java/org/isoron/uhabits/core/models/memory/MemoryCheckmarkList.java index 3504c2a3d..7c06f3343 100644 --- a/android/uhabits-core/src/main/java/org/isoron/uhabits/core/models/memory/MemoryCheckmarkList.java +++ b/android/uhabits-core/src/main/java/org/isoron/uhabits/core/models/memory/MemoryCheckmarkList.java @@ -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))); } diff --git a/android/uhabits-core/src/main/java/org/isoron/uhabits/core/models/memory/MemoryRepetitionList.java b/android/uhabits-core/src/main/java/org/isoron/uhabits/core/models/memory/MemoryRepetitionList.java index b5189c57c..cedb94bf0 100644 --- a/android/uhabits-core/src/main/java/org/isoron/uhabits/core/models/memory/MemoryRepetitionList.java +++ b/android/uhabits-core/src/main/java/org/isoron/uhabits/core/models/memory/MemoryRepetitionList.java @@ -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; } diff --git a/android/uhabits-core/src/main/java/org/isoron/uhabits/core/models/sqlite/records/RepetitionRecord.java b/android/uhabits-core/src/main/java/org/isoron/uhabits/core/models/sqlite/records/RepetitionRecord.java index 0bd5d2552..cc19038bc 100644 --- a/android/uhabits-core/src/main/java/org/isoron/uhabits/core/models/sqlite/records/RepetitionRecord.java +++ b/android/uhabits-core/src/main/java/org/isoron/uhabits/core/models/sqlite/records/RepetitionRecord.java @@ -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()); } } diff --git a/android/uhabits-core/src/main/java/org/isoron/uhabits/core/test/HabitFixtures.java b/android/uhabits-core/src/main/java/org/isoron/uhabits/core/test/HabitFixtures.java index 5e1e3edb5..06b2fee77 100644 --- a/android/uhabits-core/src/main/java/org/isoron/uhabits/core/test/HabitFixtures.java +++ b/android/uhabits-core/src/main/java/org/isoron/uhabits/core/test/HabitFixtures.java @@ -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; diff --git a/android/uhabits-core/src/main/java/org/isoron/uhabits/core/ui/screens/habits/list/HabitCardListCache.java b/android/uhabits-core/src/main/java/org/isoron/uhabits/core/ui/screens/habits/list/HabitCardListCache.java index 27c8ecdda..05ea61e63 100644 --- a/android/uhabits-core/src/main/java/org/isoron/uhabits/core/ui/screens/habits/list/HabitCardListCache.java +++ b/android/uhabits-core/src/main/java/org/isoron/uhabits/core/ui/screens/habits/list/HabitCardListCache.java @@ -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 habits; @NonNull - public final HashMap checkmarks; + public final HashMap checkmarkStates; @NonNull public final HashMap 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); } diff --git a/android/uhabits-core/src/main/java/org/isoron/uhabits/core/ui/screens/habits/list/ListHabitsBehavior.java b/android/uhabits-core/src/main/java/org/isoron/uhabits/core/ui/screens/habits/list/ListHabitsBehavior.java index 870ad65f3..e00e08e01 100644 --- a/android/uhabits-core/src/main/java/org/isoron/uhabits/core/ui/screens/habits/list/ListHabitsBehavior.java +++ b/android/uhabits-core/src/main/java/org/isoron/uhabits/core/ui/screens/habits/list/ListHabitsBehavior.java @@ -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()); } diff --git a/android/uhabits-core/src/main/java/org/isoron/uhabits/core/ui/screens/habits/show/ShowHabitBehavior.java b/android/uhabits-core/src/main/java/org/isoron/uhabits/core/ui/screens/habits/show/ShowHabitBehavior.java index 3f81dfeb2..848ddbd75 100644 --- a/android/uhabits-core/src/main/java/org/isoron/uhabits/core/ui/screens/habits/show/ShowHabitBehavior.java +++ b/android/uhabits-core/src/main/java/org/isoron/uhabits/core/ui/screens/habits/show/ShowHabitBehavior.java @@ -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); } } diff --git a/android/uhabits-core/src/main/java/org/isoron/uhabits/core/ui/screens/habits/show/ShowHabitMenuBehavior.java b/android/uhabits-core/src/main/java/org/isoron/uhabits/core/ui/screens/habits/show/ShowHabitMenuBehavior.java index 6e47293a2..8b5487579 100644 --- a/android/uhabits-core/src/main/java/org/isoron/uhabits/core/ui/screens/habits/show/ShowHabitMenuBehavior.java +++ b/android/uhabits-core/src/main/java/org/isoron/uhabits/core/ui/screens/habits/show/ShowHabitMenuBehavior.java @@ -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); diff --git a/android/uhabits-core/src/main/java/org/isoron/uhabits/core/ui/widgets/WidgetBehavior.java b/android/uhabits-core/src/main/java/org/isoron/uhabits/core/ui/widgets/WidgetBehavior.java index afe7ed724..c91155692 100644 --- a/android/uhabits-core/src/main/java/org/isoron/uhabits/core/ui/widgets/WidgetBehavior.java +++ b/android/uhabits-core/src/main/java/org/isoron/uhabits/core/ui/widgets/WidgetBehavior.java @@ -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); } diff --git a/android/uhabits-core/src/main/resources/migrations/24.sql b/android/uhabits-core/src/main/resources/migrations/24.sql new file mode 100644 index 000000000..f4f22ce99 --- /dev/null +++ b/android/uhabits-core/src/main/resources/migrations/24.sql @@ -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 diff --git a/android/uhabits-core/src/test/java/org/isoron/uhabits/core/BaseUnitTest.java b/android/uhabits-core/src/test/java/org/isoron/uhabits/core/BaseUnitTest.java index d16902678..ef20667cf 100644 --- a/android/uhabits-core/src/test/java/org/isoron/uhabits/core/BaseUnitTest.java +++ b/android/uhabits-core/src/test/java/org/isoron/uhabits/core/BaseUnitTest.java @@ -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) diff --git a/android/uhabits-core/src/test/java/org/isoron/uhabits/core/commands/CommandParserTest.java b/android/uhabits-core/src/test/java/org/isoron/uhabits/core/commands/CommandParserTest.java index b78aaee88..e9ff777fc 100644 --- a/android/uhabits-core/src/test/java/org/isoron/uhabits/core/commands/CommandParserTest.java +++ b/android/uhabits-core/src/test/java/org/isoron/uhabits/core/commands/CommandParserTest.java @@ -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)); } @@ -146,4 +147,4 @@ public class CommandParserTest extends BaseUnitTest MatcherAssert.assertThat(decoded.getId(), equalTo(original.getId())); MatcherAssert.assertThat(decoded.selected, equalTo(original.selected)); } -} \ No newline at end of file +} diff --git a/android/uhabits-core/src/test/java/org/isoron/uhabits/core/commands/CreateRepetitionCommandTest.java b/android/uhabits-core/src/test/java/org/isoron/uhabits/core/commands/CreateRepetitionCommandTest.java index 340031ee7..34afbcbdf 100644 --- a/android/uhabits-core/src/test/java/org/isoron/uhabits/core/commands/CreateRepetitionCommandTest.java +++ b/android/uhabits-core/src/test/java/org/isoron/uhabits/core/commands/CreateRepetitionCommandTest.java @@ -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 diff --git a/android/uhabits-core/src/test/java/org/isoron/uhabits/core/models/CheckmarkListTest.java b/android/uhabits-core/src/test/java/org/isoron/uhabits/core/models/CheckmarkListTest.java index 802ca9e23..d0eb1b611 100644 --- a/android/uhabits-core/src/test/java/org/isoron/uhabits/core/models/CheckmarkListTest.java +++ b/android/uhabits-core/src/test/java/org/isoron/uhabits/core/models/CheckmarkListTest.java @@ -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 intervals = new ArrayList<>(); @@ -80,17 +80,17 @@ public class CheckmarkListTest extends BaseUnitTest intervals.add(new CheckmarkList.Interval(day(2), day(2), day(1))); List 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 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 intervals = new ArrayList<>(); intervals.add(new CheckmarkList.Interval(day(0), day(0), day(-10))); List expected = new ArrayList<>(); - expected.add(new Checkmark(day(0), YES_MANUAL)); + expected.add(new Checkmark(day(0), YES, true)); List 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 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 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 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 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 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 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 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 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 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 diff --git a/android/uhabits-core/src/test/java/org/isoron/uhabits/core/models/HabitTest.java b/android/uhabits-core/src/test/java/org/isoron/uhabits/core/models/HabitTest.java index e99e954c0..fb8a0a5c6 100644 --- a/android/uhabits-core/src/test/java/org/isoron/uhabits/core/models/HabitTest.java +++ b/android/uhabits-core/src/test/java/org/isoron/uhabits/core/models/HabitTest.java @@ -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()); } @@ -159,4 +159,4 @@ public class HabitTest extends BaseUnitTest assertThat(h.toString(), equalTo(expected)); } -} \ No newline at end of file +} diff --git a/android/uhabits-core/src/test/java/org/isoron/uhabits/core/models/RepetitionListTest.java b/android/uhabits-core/src/test/java/org/isoron/uhabits/core/models/RepetitionListTest.java index 920ebed4f..84d59d0dc 100644 --- a/android/uhabits-core/src/test/java/org/isoron/uhabits/core/models/RepetitionListTest.java +++ b/android/uhabits-core/src/test/java/org/isoron/uhabits/core/models/RepetitionListTest.java @@ -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}")); } -} \ No newline at end of file +} diff --git a/android/uhabits-core/src/test/java/org/isoron/uhabits/core/models/sqlite/SQLiteRepetitionListTest.java b/android/uhabits-core/src/test/java/org/isoron/uhabits/core/models/sqlite/SQLiteRepetitionListTest.java index 60693fff4..1320c21dc 100644 --- a/android/uhabits-core/src/test/java/org/isoron/uhabits/core/models/sqlite/SQLiteRepetitionListTest.java +++ b/android/uhabits-core/src/test/java/org/isoron/uhabits/core/models/sqlite/SQLiteRepetitionListTest.java @@ -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 diff --git a/android/uhabits-core/src/test/java/org/isoron/uhabits/core/models/sqlite/records/RepetitionRecordTest.java b/android/uhabits-core/src/test/java/org/isoron/uhabits/core/models/sqlite/records/RepetitionRecordTest.java index 1ac7851e1..10dfceb14 100644 --- a/android/uhabits-core/src/test/java/org/isoron/uhabits/core/models/sqlite/records/RepetitionRecordTest.java +++ b/android/uhabits-core/src/test/java/org/isoron/uhabits/core/models/sqlite/records/RepetitionRecordTest.java @@ -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())); diff --git a/android/uhabits-core/src/test/java/org/isoron/uhabits/core/ui/screens/habits/list/HabitCardListCacheTest.java b/android/uhabits-core/src/test/java/org/isoron/uhabits/core/ui/screens/habits/list/HabitCardListCacheTest.java index 3f0d14efb..40ffa546d 100644 --- a/android/uhabits-core/src/test/java/org/isoron/uhabits/core/ui/screens/habits/list/HabitCardListCacheTest.java +++ b/android/uhabits-core/src/test/java/org/isoron/uhabits/core/ui/screens/habits/list/HabitCardListCacheTest.java @@ -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)); } @@ -181,4 +181,4 @@ public class HabitCardListCacheTest extends BaseUnitTest habitList.remove(h); } -} \ No newline at end of file +} diff --git a/android/uhabits-core/src/test/java/org/isoron/uhabits/core/ui/screens/habits/list/ListHabitsBehaviorTest.java b/android/uhabits-core/src/test/java/org/isoron/uhabits/core/ui/screens/habits/list/ListHabitsBehaviorTest.java index 4434d3b33..822be41cc 100644 --- a/android/uhabits-core/src/test/java/org/isoron/uhabits/core/ui/screens/habits/list/ListHabitsBehaviorTest.java +++ b/android/uhabits-core/src/test/java/org/isoron/uhabits/core/ui/screens/habits/list/ListHabitsBehaviorTest.java @@ -173,8 +173,8 @@ 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()); } -} \ No newline at end of file +}