From 3cff51ea42a1c5f6262fbeecf218616d964414ad Mon Sep 17 00:00:00 2001 From: KristianTashkov Date: Tue, 21 Jul 2020 14:46:42 +0300 Subject: [PATCH] V1 of skip days --- .../activities/common/views/HistoryChart.java | 8 +++++- .../habits/list/views/CheckmarkButtonView.kt | 8 +++++- .../habits/show/views/OverviewCard.java | 2 +- .../src/main/res/values/fontawesome.xml | 1 + .../commands/ToggleRepetitionCommand.java | 1 + .../isoron/uhabits/core/models/Checkmark.java | 7 +++++- .../uhabits/core/models/CheckmarkList.java | 11 ++++++-- .../uhabits/core/models/Repetition.java | 5 ++-- .../uhabits/core/models/RepetitionList.java | 25 +++++++++++++------ .../isoron/uhabits/core/models/ScoreList.java | 8 ++++-- .../models/memory/MemoryRepetitionList.java | 10 ++++++-- .../models/sqlite/SQLiteRepetitionList.java | 4 +-- .../commands/ToggleRepetitionCommandTest.java | 10 +++++++- .../core/models/RepetitionListTest.java | 13 +++++++--- .../habits/list/ListHabitsBehaviorTest.java | 2 ++ 15 files changed, 89 insertions(+), 26 deletions(-) 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 93b488b78..63fa8b3d6 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 @@ -362,6 +362,7 @@ public class HistoryChart extends ScrollableChart GregorianCalendar date, int checkmarkOffset) { + pSquareFg.setStrikeThruText(false); if (checkmarkOffset >= checkmarks.length) pSquareBg.setColor(colors[0]); else { @@ -371,13 +372,18 @@ public class HistoryChart extends ScrollableChart { pSquareBg.setColor(isNumerical ? textColor : colors[1]); } + else if (!isNumerical && checkmark == 3) { + pSquareFg.setStrikeThruText(true); + pSquareBg.setColor(colors[1]); + } else pSquareBg.setColor(colors[2]); } pSquareFg.setColor(reverseTextColor); + float round = dpToPixels(getContext(), 2); canvas.drawRoundRect(location, round, round, pSquareBg); - String text = Integer.toString(date.get(Calendar.DAY_OF_MONTH)); + String text = Integer.toString(date.get(Calendar.DAY_OF_MONTH)) ; canvas.drawText(text, location.centerX(), location.centerY() + squareTextOffset, pSquareFg); } 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 77981995a..51c21e879 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 @@ -63,7 +63,8 @@ class CheckmarkButtonView( fun performToggle() { onToggle() value = when (value) { - CHECKED_EXPLICITLY -> UNCHECKED + CHECKED_EXPLICITLY -> SKIPPED_EXPLICITLY + SKIPPED_EXPLICITLY -> UNCHECKED else -> CHECKED_EXPLICITLY } performHapticFeedback(HapticFeedbackConstants.LONG_PRESS) @@ -95,6 +96,9 @@ class CheckmarkButtonView( private inner class Drawer { private val rect = RectF() private val lowContrastColor = sres.getColor(R.attr.lowContrastTextColor) + private val mediumContrastTextColor = sres.getColor(R.attr.mediumContrastTextColor) + + private val paint = TextPaint().apply { typeface = getFontAwesome() @@ -106,9 +110,11 @@ class CheckmarkButtonView( fun draw(canvas: Canvas) { paint.color = when (value) { CHECKED_EXPLICITLY -> color + SKIPPED_EXPLICITLY -> mediumContrastTextColor else -> lowContrastColor } val id = when (value) { + SKIPPED_EXPLICITLY -> R.string.fa_skipped UNCHECKED -> R.string.fa_times else -> R.string.fa_check } diff --git a/android/uhabits-android/src/main/java/org/isoron/uhabits/activities/habits/show/views/OverviewCard.java b/android/uhabits-android/src/main/java/org/isoron/uhabits/activities/habits/show/views/OverviewCard.java index 14327643c..251f75c01 100644 --- a/android/uhabits-android/src/main/java/org/isoron/uhabits/activities/habits/show/views/OverviewCard.java +++ b/android/uhabits-android/src/main/java/org/isoron/uhabits/activities/habits/show/views/OverviewCard.java @@ -160,7 +160,7 @@ public class OverviewCard extends HabitCard cache.todayScore = (float) scores.getTodayValue(); cache.lastMonthScore = (float) scores.getValue(lastMonth); cache.lastYearScore = (float) scores.getValue(lastYear); - cache.totalCount = habit.getRepetitions().getTotalCount(); + cache.totalCount = habit.getRepetitions().getTotalSuccessfulCount(); } @Override diff --git a/android/uhabits-android/src/main/res/values/fontawesome.xml b/android/uhabits-android/src/main/res/values/fontawesome.xml index 33c296549..89e0cea55 100644 --- a/android/uhabits-android/src/main/res/values/fontawesome.xml +++ b/android/uhabits-android/src/main/res/values/fontawesome.xml @@ -21,6 +21,7 @@ + diff --git a/android/uhabits-core/src/main/java/org/isoron/uhabits/core/commands/ToggleRepetitionCommand.java b/android/uhabits-core/src/main/java/org/isoron/uhabits/core/commands/ToggleRepetitionCommand.java index 5e2e575c1..ea1d84aef 100644 --- a/android/uhabits-core/src/main/java/org/isoron/uhabits/core/commands/ToggleRepetitionCommand.java +++ b/android/uhabits-core/src/main/java/org/isoron/uhabits/core/commands/ToggleRepetitionCommand.java @@ -70,6 +70,7 @@ public class ToggleRepetitionCommand extends Command public void undo() { execute(); + execute(); } public static class Record 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 6683d6543..7eafafe96 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 @@ -37,6 +37,11 @@ import static org.isoron.uhabits.core.utils.StringUtils.defaultToStringStyle; @ThreadSafe public final class Checkmark { + /** + * Indicates that there was an explicit skip at the timestamp. + */ + public static final int SKIPPED_EXPLICITLY = 3; + /** * Indicates that there was a repetition at the timestamp. */ @@ -59,7 +64,7 @@ public final class Checkmark /** * The value of the checkmark. *

- * For boolean habits, this equals either UNCHECKED, CHECKED_EXPLICITLY, + * For boolean habits, this equals either UNCHECKED, SKIPPED_EXPLICITLY, CHECKED_EXPLICITLY, * or CHECKED_IMPLICITLY. *

* For numerical habits, this number is stored in thousandths. That 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 3fdb8d61f..33d68ee6a 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 @@ -79,7 +79,7 @@ public abstract class CheckmarkList { Timestamp date = rep.getTimestamp(); int offset = date.daysUntil(today); - checkmarks.set(offset, new Checkmark(date, CHECKED_EXPLICITLY)); + checkmarks.set(offset, new Checkmark(date, rep.getValue())); } return checkmarks; @@ -379,7 +379,14 @@ public abstract class CheckmarkList private void computeYesNo(Repetition[] reps) { ArrayList intervals; - intervals = buildIntervals(habit.getFrequency(), reps); + List successful_repetitions = new ArrayList<>(); + for (Repetition rep : reps) { + if (rep.getValue() != SKIPPED_EXPLICITLY) { + successful_repetitions.add(rep); + } + } + intervals = buildIntervals( + habit.getFrequency(), successful_repetitions.toArray(new Repetition[0])); snapIntervalsTogether(intervals); add(buildCheckmarksFromIntervals(reps, intervals)); } 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 413c06364..708a9f762 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 @@ -30,7 +30,7 @@ import java.util.GregorianCalendar; import static org.isoron.uhabits.core.utils.StringUtils.defaultToStringStyle; /** - * Represents a record that the user has performed a certain habit at a certain + * Represents a record that the user has performed or skipped a certain habit at a certain * date. */ public final class Repetition @@ -41,7 +41,8 @@ public final class Repetition /** * The value of the repetition. * - * For boolean habits, this always equals Checkmark.CHECKED_EXPLICITLY. + * For boolean habits, this equals Checkmark.CHECKED_EXPLICITLY if performed + * or Checkmark.SKIPPED_EXPLICITLY 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. */ 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 a7541129e..9cf3205c0 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 @@ -119,15 +119,15 @@ public abstract class RepetitionList public abstract Repetition getNewest(); /** - * Returns the total number of repetitions for each month, from the first + * Returns the total number of successful repetitions for each month, from the first * repetition until today, grouped by day of week. *

* The repetitions are returned in a HashMap. The key is the timestamp for * the first day of the month, at midnight (00:00). The value is an integer * array with 7 entries. The first entry contains the total number of - * repetitions during the specified month that occurred on a Saturday. The + * successful repetitions during the specified month that occurred on a Saturday. The * second entry corresponds to Sunday, and so on. If there are no - * repetitions during a certain month, the value is null. + * successful repetitions during a certain month, the value is null. * * @return total number of repetitions by month versus day of week */ @@ -140,6 +140,11 @@ public abstract class RepetitionList for (Repetition r : reps) { + if ((habit.getData().type == Habit.YES_NO_HABIT) + && (r.getValue() == Checkmark.SKIPPED_EXPLICITLY)) { + continue; + } + Calendar date = r.getTimestamp().toCalendar(); int weekday = r.getTimestamp().getWeekday(); date.set(Calendar.DAY_OF_MONTH, 1); @@ -191,7 +196,13 @@ public abstract class RepetitionList throw new IllegalStateException("habit must NOT be numerical"); Repetition rep = getByTimestamp(timestamp); - if (rep != null) remove(rep); + if (rep != null) { + remove(rep); + if (rep.getValue() == Checkmark.CHECKED_EXPLICITLY) { + rep = new Repetition(timestamp, Checkmark.SKIPPED_EXPLICITLY); + add(rep); + } + } else { rep = new Repetition(timestamp, Checkmark.CHECKED_EXPLICITLY); @@ -203,12 +214,12 @@ public abstract class RepetitionList } /** - * Returns the number of all repetitions + * Returns the number of all successful repetitions * - * @return number of all repetitions + * @return number of all successful repetitions */ @NonNull - public abstract long getTotalCount(); + public abstract long getTotalSuccessfulCount(); public void toggle(Timestamp timestamp, int value) { diff --git a/android/uhabits-core/src/main/java/org/isoron/uhabits/core/models/ScoreList.java b/android/uhabits-core/src/main/java/org/isoron/uhabits/core/models/ScoreList.java index b8d7c1eae..85d726f14 100644 --- a/android/uhabits-core/src/main/java/org/isoron/uhabits/core/models/ScoreList.java +++ b/android/uhabits-core/src/main/java/org/isoron/uhabits/core/models/ScoreList.java @@ -283,9 +283,13 @@ public abstract class ScoreList implements Iterable value = Math.min(1, value); } - if (!habit.isNumerical() && value > 0) value = 1; + if (!habit.isNumerical() & value > 0) { + value = value != Checkmark.SKIPPED_EXPLICITLY ? 1 : -1; + } - previousValue = Score.compute(freq, previousValue, value); + if (value > -1) { + previousValue = Score.compute(freq, previousValue, value); + } scores.add(new Score(from.plus(i), previousValue)); } 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 6dce2f9d3..ad3d6e8b5 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 @@ -119,9 +119,15 @@ public class MemoryRepetitionList extends RepetitionList } @Override - public long getTotalCount() + public long getTotalSuccessfulCount() { - return list.size(); + int count = 0; + for (Repetition rep : list) { + if (rep.getValue() != Checkmark.SKIPPED_EXPLICITLY) { + ++count; + } + } + return count; } @Override diff --git a/android/uhabits-core/src/main/java/org/isoron/uhabits/core/models/sqlite/SQLiteRepetitionList.java b/android/uhabits-core/src/main/java/org/isoron/uhabits/core/models/sqlite/SQLiteRepetitionList.java index 11f70fbf4..63e46e5fa 100644 --- a/android/uhabits-core/src/main/java/org/isoron/uhabits/core/models/sqlite/SQLiteRepetitionList.java +++ b/android/uhabits-core/src/main/java/org/isoron/uhabits/core/models/sqlite/SQLiteRepetitionList.java @@ -129,10 +129,10 @@ public class SQLiteRepetitionList extends RepetitionList } @Override - public long getTotalCount() + public long getTotalSuccessfulCount() { loadRecords(); - return list.getTotalCount(); + return list.getTotalSuccessfulCount(); } public void reload() diff --git a/android/uhabits-core/src/test/java/org/isoron/uhabits/core/commands/ToggleRepetitionCommandTest.java b/android/uhabits-core/src/test/java/org/isoron/uhabits/core/commands/ToggleRepetitionCommandTest.java index 8c698a2d2..763588108 100644 --- a/android/uhabits-core/src/test/java/org/isoron/uhabits/core/commands/ToggleRepetitionCommandTest.java +++ b/android/uhabits-core/src/test/java/org/isoron/uhabits/core/commands/ToggleRepetitionCommandTest.java @@ -53,11 +53,19 @@ public class ToggleRepetitionCommandTest extends BaseUnitTest { assertTrue(habit.getRepetitions().containsTimestamp(today)); + command.execute(); + assertEquals( + habit.getRepetitions().getByTimestamp(today).getValue(), + Checkmark.SKIPPED_EXPLICITLY + ); command.execute(); assertFalse(habit.getRepetitions().containsTimestamp(today)); command.undo(); - assertTrue(habit.getRepetitions().containsTimestamp(today)); + assertEquals( + habit.getRepetitions().getByTimestamp(today).getValue(), + Checkmark.SKIPPED_EXPLICITLY + ); command.execute(); assertFalse(habit.getRepetitions().containsTimestamp(today)); 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..b4efcc919 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 @@ -31,6 +31,7 @@ import static java.util.Calendar.*; import static junit.framework.TestCase.assertFalse; import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.core.IsEqual.*; +import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertTrue; import static org.mockito.Mockito.*; @@ -157,16 +158,20 @@ public class RepetitionListTest extends BaseUnitTest @Test public void test_toggle() { - assertTrue(reps.containsTimestamp(today)); + assertEquals(reps.getByTimestamp(today).getValue(), Checkmark.CHECKED_EXPLICITLY); + reps.toggle(today); + assertEquals(reps.getByTimestamp(today).getValue(), Checkmark.SKIPPED_EXPLICITLY); reps.toggle(today); assertFalse(reps.containsTimestamp(today)); - verify(listener).onModelChange(); + verify(listener, times(3)).onModelChange(); reset(listener); assertFalse(reps.containsTimestamp(today.minus(1))); reps.toggle(today.minus(1)); - assertTrue(reps.containsTimestamp(today.minus(1))); - verify(listener).onModelChange(); + assertEquals(reps.getByTimestamp(today.minus(1)).getValue(), Checkmark.CHECKED_EXPLICITLY); + reps.toggle(today.minus(1)); + assertEquals(reps.getByTimestamp(today.minus(1)).getValue(), Checkmark.SKIPPED_EXPLICITLY); + verify(listener, times(3)).onModelChange(); reset(listener); habit.setType(Habit.NUMBER_HABIT); 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 0ed3fbbab..2be27099c 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 @@ -172,6 +172,8 @@ public class ListHabitsBehaviorTest extends BaseUnitTest @Test public void testOnToggle() { + assertTrue(habit1.isCompletedToday()); + behavior.onToggle(habit1, DateUtils.getToday()); assertTrue(habit1.isCompletedToday()); behavior.onToggle(habit1, DateUtils.getToday()); assertFalse(habit1.isCompletedToday());