- * For boolean habits, this equals either UNCHECKED, CHECKED_EXPLICITLY, CHECKED_IMPLICITLY
- * or SKIPPED.
+ * For boolean habits, this equals either UNCHECKED, SKIPPED, CHECKED_EXPLICITLY,
+ * CHECKED_IMPLICITLY, UNCHECKED_EXPLICITLY.
*
* 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.
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 7de1b8e67..27ce74290 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,13 @@ public abstract class CheckmarkList
{
Timestamp date = rep.getTimestamp();
int offset = date.daysUntil(today);
- checkmarks.set(offset, new Checkmark(date, rep.getValue()));
+ int checkmarkValue = rep.getValue();
+ int oldValue = checkmarks.get(offset).getValue();
+ if (checkmarkValue == UNCHECKED_EXPLICITLY && oldValue == CHECKED_IMPLICITLY)
+ {
+ checkmarkValue = oldValue;
+ }
+ checkmarks.set(offset, new Checkmark(date, checkmarkValue));
}
return checkmarks;
@@ -379,7 +385,15 @@ public abstract class CheckmarkList
private void computeYesNo(Repetition[] reps)
{
ArrayList intervals;
- intervals = buildIntervals(habit.getFrequency(), reps);
+ List checkedRepetitions = new ArrayList<>();
+ for (Repetition rep : reps)
+ {
+ int value = rep.getValue();
+ if (value == CHECKED_EXPLICITLY || value == SKIPPED)
+ checkedRepetitions.add(rep);
+ }
+ intervals = buildIntervals(
+ habit.getFrequency(), checkedRepetitions.toArray(new Repetition[0]));
snapIntervalsTogether(intervals);
add(buildCheckmarksFromIntervals(reps, intervals));
}
diff --git a/android/uhabits-core/src/main/java/org/isoron/uhabits/core/models/Habit.java b/android/uhabits-core/src/main/java/org/isoron/uhabits/core/models/Habit.java
index 3f5e79782..1f332988a 100644
--- a/android/uhabits-core/src/main/java/org/isoron/uhabits/core/models/Habit.java
+++ b/android/uhabits-core/src/main/java/org/isoron/uhabits/core/models/Habit.java
@@ -331,7 +331,7 @@ public class Habit
else
return todayCheckmark / 1000.0 <= data.targetValue;
}
- else return (todayCheckmark != UNCHECKED);
+ else return (todayCheckmark != UNCHECKED && todayCheckmark != UNCHECKED_EXPLICITLY);
}
public synchronized boolean isNumerical()
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 b2ef09040..5127e651a 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
@@ -31,8 +31,8 @@ import static org.isoron.uhabits.core.models.Checkmark.*;
import static org.isoron.uhabits.core.utils.StringUtils.defaultToStringStyle;
/**
- * Represents a record that the user has performed or skipped a certain habit at a certain
- * date.
+ * Represents a record that the user has performed, didn't perform or skipped a certain habit at
+ * a certain date.
*/
public final class Repetition
{
@@ -42,9 +42,12 @@ public final class Repetition
/**
* The value of the repetition.
*
- * For boolean habits, this equals CHECKED_EXPLICITLY if performed or SKIPPED 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.
+ * For boolean habits, this equals:
+ * Checkmark.CHECKED_EXPLICITLY if performed
+ * Checkmark.UNCHECKED_EXPLICITLY if not performed.
+ * Checkmark.SKIPPED 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.
*/
private final int value;
@@ -66,6 +69,7 @@ public final class Repetition
{
switch(value) {
case UNCHECKED:
+ case UNCHECKED_EXPLICITLY:
case CHECKED_IMPLICITLY:
return CHECKED_EXPLICITLY;
case CHECKED_EXPLICITLY:
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 3330fd08e..bc88087e8 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
@@ -211,7 +211,7 @@ public abstract class RepetitionList
{
Repetition rep = getByTimestamp(timestamp);
if (rep != null) remove(rep);
- add(new Repetition(timestamp, value));
+ if (value > 0) add(new Repetition(timestamp, value));
habit.invalidateNewerThan(timestamp);
}
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 24784d549..d4f7652d3 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
@@ -275,16 +275,18 @@ public abstract class ScoreList implements Iterable
for (int i = 0; i < checkmarkValues.length; i++)
{
double value = checkmarkValues[checkmarkValues.length - i - 1];
- if (!habit.isNumerical() || value != Checkmark.SKIPPED)
+ if (habit.isNumerical())
{
- if (habit.isNumerical())
- {
- value /= 1000;
- value /= habit.getTargetValue();
- }
+ value /= 1000;
+ value /= habit.getTargetValue();
value = Math.min(1, value);
- previousValue = Score.compute(freq, previousValue, value);
}
+ else if (value > 0)
+ {
+ if (value == Checkmark.UNCHECKED_EXPLICITLY) value = 0;
+ else 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/StreakList.java b/android/uhabits-core/src/main/java/org/isoron/uhabits/core/models/StreakList.java
index 542ed9962..70dcba0b3 100644
--- a/android/uhabits-core/src/main/java/org/isoron/uhabits/core/models/StreakList.java
+++ b/android/uhabits-core/src/main/java/org/isoron/uhabits/core/models/StreakList.java
@@ -131,18 +131,39 @@ public abstract class StreakList
{
ArrayList list = new ArrayList<>();
Timestamp current = beginning;
- list.add(current);
+ Timestamp lastChecked = beginning;
+ boolean isInStreak = false;
- for (int i = 1; i < checks.length; i++)
+ for (int i = checks.length - 1; i >= 0; --i)
{
- current = current.plus(1);
- int j = checks.length - i - 1;
+ boolean isCurrentChecked = (
+ checks[i] == Checkmark.CHECKED_EXPLICITLY ||
+ checks[i] == Checkmark.CHECKED_IMPLICITLY ||
+ checks[i] == Checkmark.SKIPPED
+ );
+ boolean isCurrentUnchecked= (
+ checks[i] == Checkmark.UNCHECKED ||
+ checks[i] == Checkmark.UNCHECKED_EXPLICITLY
+ );
+ if (isCurrentChecked)
+ lastChecked = current;
+
+ if (isInStreak && isCurrentUnchecked)
+ {
+ list.add(lastChecked);
+ isInStreak = false;
+ }
+ if (!isInStreak && isCurrentChecked)
+ {
+ list.add(current);
+ isInStreak = true;
+ }
- if ((checks[j + 1] == 0 && checks[j] > 0)) list.add(current);
- if ((checks[j + 1] > 0 && checks[j] == 0)) list.add(current.minus(1));
+ current = current.plus(1);
}
- if (list.size() % 2 == 1) list.add(current);
+ if (isInStreak)
+ list.add(lastChecked);
return list;
}
diff --git a/android/uhabits-core/src/main/java/org/isoron/uhabits/core/preferences/Preferences.java b/android/uhabits-core/src/main/java/org/isoron/uhabits/core/preferences/Preferences.java
index 6907e302e..506510043 100644
--- a/android/uhabits-core/src/main/java/org/isoron/uhabits/core/preferences/Preferences.java
+++ b/android/uhabits-core/src/main/java/org/isoron/uhabits/core/preferences/Preferences.java
@@ -244,6 +244,16 @@ public class Preferences
storage.putBoolean("pref_short_toggle", enabled);
}
+ public boolean isAdvancedCheckmarksEnabled()
+ {
+ return storage.getBoolean("pref_advanced_checkmarks", false);
+ }
+
+ public void setAdvancedCheckmarksEnabled(boolean enabled)
+ {
+ storage.putBoolean("pref_advanced_checkmarks", enabled);
+ }
+
public boolean isSyncEnabled()
{
return storage.getBoolean("pref_feature_sync", false);
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..f57341db9 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
@@ -156,6 +156,18 @@ public class ListHabitsBehavior
habit.getId());
}
+ public void onToggleWithOptions(@NonNull Habit habit, Timestamp timestamp)
+ {
+ CheckmarkList checkmarks = habit.getCheckmarks();
+ int oldValue = checkmarks.getValues(timestamp, timestamp)[0];
+ screen.showCheckmarkOptions(habit, timestamp, oldValue, newValue ->
+ {
+ commandRunner.execute(
+ new CreateRepetitionCommand(habitList, habit, timestamp, newValue),
+ habit.getId());
+ });
+ }
+
public enum Message
{
COULD_NOT_EXPORT, IMPORT_SUCCESSFUL, IMPORT_FAILED, DATABASE_REPAIRED,
@@ -181,6 +193,13 @@ public class ListHabitsBehavior
default void onNumberPickerDismissed() {}
}
+ public interface CheckmarkOptionsCallback
+ {
+ void onCheckmarkOptionPicked(int newValue);
+
+ default void onCheckmarkOptionDismissed() {}
+ }
+
public interface Screen
{
void showHabitScreen(@NonNull Habit h);
@@ -193,6 +212,11 @@ public class ListHabitsBehavior
@NonNull String unit,
@NonNull NumberPickerCallback callback);
+ void showCheckmarkOptions(Habit habit,
+ Timestamp timestamp,
+ int value,
+ @NonNull CheckmarkOptionsCallback callback);
+
void showSendBugReportToDeveloperScreen(String log);
void showSendFileScreen(@NonNull String filename);
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 66824be3a..03bb11392 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
@@ -82,4 +82,7 @@ public class WidgetBehavior
habit.getId());
}
+ public void setYesNoValue(@NonNull Habit habit, Timestamp timestamp, int newValue) {
+ performToggle(habit, timestamp, newValue);
+ }
}