Merge branch 'dev' into feature/sync

This commit is contained in:
2020-11-26 15:39:44 -06:00
110 changed files with 1949 additions and 535 deletions

View File

@@ -23,6 +23,8 @@ import androidx.annotation.*;
import org.isoron.uhabits.core.models.*;
import java.util.*;
/**
* Command to toggle a repetition.
*/
@@ -31,20 +33,19 @@ public class CreateRepetitionCommand extends Command
@NonNull
final Habit habit;
private HabitList habitList;
@NonNull
final HabitList habitList;
@NonNull
final Timestamp timestamp;
final int value;
@Nullable
Repetition previousRep;
@Nullable
Repetition newRep;
int previousValue;
public CreateRepetitionCommand(@NonNull HabitList habitList,
@NonNull Habit habit,
Timestamp timestamp,
@NonNull Timestamp timestamp,
int value)
{
this.habitList = habitList;
@@ -57,18 +58,8 @@ public class CreateRepetitionCommand extends Command
public void execute()
{
RepetitionList reps = habit.getRepetitions();
previousRep = reps.getByTimestamp(timestamp);
if (previousRep != null) reps.remove(previousRep);
if (value > 0)
{
newRep = new Repetition(timestamp, value);
reps.add(newRep);
}
habit.invalidateNewerThan(timestamp);
habitList.update(habit);
previousValue = reps.getValue(timestamp);
reps.setValue(timestamp, value);
}
@NonNull
@@ -87,9 +78,7 @@ public class CreateRepetitionCommand extends Command
@Override
public void undo()
{
if(newRep != null) habit.getRepetitions().remove(newRep);
if (previousRep != null) habit.getRepetitions().add(previousRep);
habit.invalidateNewerThan(timestamp);
habit.getRepetitions().setValue(timestamp, previousValue);
}
public static class Record
@@ -129,4 +118,34 @@ public class CreateRepetitionCommand extends Command
return command;
}
}
@Override
public boolean equals(Object o)
{
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
CreateRepetitionCommand that = (CreateRepetitionCommand) o;
return value == that.value &&
habit.equals(that.habit) &&
habitList.equals(that.habitList) &&
timestamp.equals(that.timestamp);
}
@Override
public int hashCode()
{
return Objects.hash(habit, habitList, timestamp, value);
}
@Override
public String toString()
{
return "CreateRepetitionCommand{" +
"habit=" + habit +
", habitList=" + habitList +
", timestamp=" + timestamp +
", value=" + value +
", previousValue=" + previousValue +
'}';
}
}

View File

@@ -31,6 +31,8 @@ import java.util.*;
import javax.inject.*;
import static org.isoron.uhabits.core.models.Checkmark.*;
/**
* Class that imports data from HabitBull CSV files.
@@ -93,8 +95,7 @@ public class HabitBullCSVImporter extends AbstractImporter
map.put(name, h);
}
if (!h.getRepetitions().containsTimestamp(timestamp))
h.getRepetitions().toggle(timestamp);
h.getRepetitions().setValue(timestamp, YES_MANUAL);
}
}
}

View File

@@ -30,6 +30,8 @@ import java.util.*;
import javax.inject.*;
import static org.isoron.uhabits.core.models.Checkmark.*;
/**
* Class that imports database files exported by Rewire.
*/
@@ -165,7 +167,7 @@ public class RewireDBImporter extends AbstractImporter
GregorianCalendar cal = DateUtils.getStartOfTodayCalendar();
cal.set(year, month - 1, day);
habit.getRepetitions().toggle(new Timestamp(cal));
habit.getRepetitions().setValue(new Timestamp(cal), YES_MANUAL);
} while (c.moveToNext());
}
finally

View File

@@ -30,6 +30,8 @@ import java.util.*;
import javax.inject.*;
import static org.isoron.uhabits.core.models.Checkmark.*;
/**
* Class that imports data from database files exported by Tickmate.
*/
@@ -100,7 +102,7 @@ public class TickmateDBImporter extends AbstractImporter
GregorianCalendar cal = DateUtils.getStartOfTodayCalendar();
cal.set(year, month, day);
habit.getRepetitions().toggle(new Timestamp(cal));
habit.getRepetitions().setValue(new Timestamp(cal), YES_MANUAL);
} while (c.moveToNext());
}
finally

View File

@@ -43,27 +43,32 @@ import static org.isoron.uhabits.core.utils.StringUtils.defaultToStringStyle;
public final class Checkmark
{
/**
* Indicates that there was an explicit skip at the timestamp.
* Checkmark value indicating that the habit is not applicable for this timestamp.
*/
public static final int SKIP = 3;
/**
* Indicates that there was a repetition at the timestamp.
* Checkmark value indicating that the user has performed the habit at this 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.
* Checkmark value indicating that the user did not perform the habit, but they were not
* expected to, because of the frequency of the habit.
*/
public static final int YES_AUTO = 1;
/**
* Indicates that there was no repetition at the timestamp, even though a
* repetition was expected.
* Checkmark value indicating that the user did not perform the habit, even though they were
* expected to perform it.
*/
public static final int NO = 0;
/**
* Checkmark value indicating that no data is available for the given timestamp.
*/
public static final int UNKNOWN = -1;
private final Timestamp timestamp;
/**

View File

@@ -62,7 +62,7 @@ public abstract class CheckmarkList
int nDays = begin.daysUntil(today) + 1;
List<Checkmark> checkmarks = new ArrayList<>(nDays);
for (int i = 0; i < nDays; i++)
checkmarks.add(new Checkmark(today.minus(i), NO));
checkmarks.add(new Checkmark(today.minus(i), UNKNOWN));
for (Interval interval : intervals)
{
@@ -79,7 +79,10 @@ public abstract class CheckmarkList
{
Timestamp date = rep.getTimestamp();
int offset = date.daysUntil(today);
checkmarks.set(offset, new Checkmark(date, rep.getValue()));
int value = rep.getValue();
int prevValue = checkmarks.get(offset).getValue();
if (prevValue < value)
checkmarks.set(offset, new Checkmark(date, value));
}
return checkmarks;
@@ -224,7 +227,7 @@ public abstract class CheckmarkList
{
Checkmark today = getToday();
if (today != null) return today.getValue();
else return NO;
else return UNKNOWN;
}
public synchronized int getThisWeekValue(int firstWeekday)

View File

@@ -332,7 +332,7 @@ public class Habit
else
return todayCheckmark / 1000.0 <= data.targetValue;
}
else return (todayCheckmark != NO);
else return (todayCheckmark != NO && todayCheckmark != UNKNOWN);
}
public synchronized boolean isNumerical()

View File

@@ -60,6 +60,7 @@ public final class Repetition
{
switch(value) {
case NO:
case UNKNOWN:
case YES_AUTO:
return YES_MANUAL;
case YES_MANUAL:
@@ -74,6 +75,7 @@ public final class Repetition
{
switch(value) {
case NO:
case UNKNOWN:
case YES_AUTO:
return YES_MANUAL;
default:

View File

@@ -52,19 +52,6 @@ public abstract class RepetitionList
*/
public abstract void add(Repetition repetition);
/**
* Returns true if the list contains a repetition that has the given
* timestamp.
*
* @param timestamp the timestamp to find.
* @return true if list contains repetition with given timestamp, false
* otherwise.
*/
public boolean containsTimestamp(Timestamp timestamp)
{
return (getByTimestamp(timestamp) != null);
}
/**
* Returns the list of repetitions that happened within the given time
* interval.
@@ -90,6 +77,18 @@ public abstract class RepetitionList
@Nullable
public abstract Repetition getByTimestamp(Timestamp timestamp);
/**
* If a repetition with the given timestamp exists, return its value. Otherwise, returns
* Checkmark.NO for boolean habits and zero for numerical habits.
*/
@NonNull
public int getValue(Timestamp timestamp)
{
Repetition rep = getByTimestamp(timestamp);
if (rep == null) return Checkmark.UNKNOWN;
return rep.getValue();
}
@NonNull
public ModelObservable getObservable()
{
@@ -175,39 +174,9 @@ public abstract class RepetitionList
*/
public abstract void remove(@NonNull Repetition repetition);
/**
* Adds or remove a repetition at a certain timestamp.
* <p>
* If there exists a repetition on the list with the given timestamp, the
* method removes this repetition from the list and returns it. If there are
* no repetitions with the given timestamp, creates and adds one to the
* list, then returns it.
*
* @param timestamp the timestamp for the timestamp that should be added or
* removed.
* @return the repetition that has been added or removed.
*/
@NonNull
public synchronized Repetition toggle(Timestamp timestamp)
{
if (habit.isNumerical())
throw new IllegalStateException("habit must NOT be numerical");
Repetition rep = getByTimestamp(timestamp);
if (rep != null) remove(rep);
else
{
rep = new Repetition(timestamp, Checkmark.YES_MANUAL);
add(rep);
}
habit.invalidateNewerThan(timestamp);
return rep;
}
public abstract long getTotalCount();
public void toggle(Timestamp timestamp, int value)
public void setValue(Timestamp timestamp, int value)
{
Repetition rep = getByTimestamp(timestamp);
if (rep != null) remove(rep);

View File

@@ -138,8 +138,8 @@ public abstract class StreakList
current = current.plus(1);
int j = checks.length - i - 1;
if ((checks[j + 1] == 0 && checks[j] > 0)) list.add(current);
if ((checks[j + 1] > 0 && checks[j] == 0)) list.add(current.minus(1));
if ((checks[j + 1] <= 0 && checks[j] > 0)) list.add(current);
if ((checks[j + 1] > 0 && checks[j] <= 0)) list.add(current.minus(1));
}
if (list.size() % 2 == 1) list.add(current);

View File

@@ -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.UNKNOWN));
else
filtered.add(list.get(t.daysUntil(newestComputed)));
}

View File

@@ -345,6 +345,10 @@ public class Preferences
if(enabled) for (Listener l : listeners) l.onSyncEnabled();
}
public boolean areQuestionMarksEnabled()
{
return storage.getBoolean("pref_unknown_enabled", false);
}
/**
* @return An integer representing the first day of the week. Sunday

View File

@@ -23,6 +23,8 @@ import org.isoron.uhabits.core.models.*;
import org.isoron.uhabits.core.models.sqlite.*;
import org.isoron.uhabits.core.utils.*;
import static org.isoron.uhabits.core.models.Checkmark.*;
public class HabitFixtures
{
public boolean NON_DAILY_HABIT_CHECKS[] = {
@@ -63,7 +65,7 @@ public class HabitFixtures
81, 83, 89, 90, 91, 95, 102, 103, 108, 109, 120};
for (int mark : marks)
habit.getRepetitions().toggle(today.minus(mark));
habit.getRepetitions().setValue(today.minus(mark), YES_MANUAL);
return habit;
}
@@ -87,7 +89,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().setValue(timestamp, values[i]);
}
return habit;
@@ -123,7 +125,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().setValue(timestamp, values[i]);
}
return habit;
@@ -140,7 +142,9 @@ public class HabitFixtures
Timestamp timestamp = DateUtils.getToday();
for (boolean c : NON_DAILY_HABIT_CHECKS)
{
if (c) habit.getRepetitions().toggle(timestamp);
int value = NO;
if (c) value = YES_MANUAL;
habit.getRepetitions().setValue(timestamp, value);
timestamp = timestamp.minus(1);
}

View File

@@ -186,7 +186,7 @@ public class NotificationTray
{
systemTray.log("Showing notification for habit=" + habit.id);
if (todayValue != Checkmark.NO) {
if (todayValue != Checkmark.UNKNOWN) {
systemTray.log(String.format(
Locale.US,
"Habit %d already checked. Skipping.",

View File

@@ -114,7 +114,7 @@ public class ShowHabitMenuBehavior
if (habit.isNumerical())
value = (int) (1000 + 250 * random.nextGaussian() * strength / 100) * 1000;
habit.getRepetitions().add(new Repetition(DateUtils.getToday().minus(i), value));
habit.getRepetitions().setValue(DateUtils.getToday().minus(i), value);
}
habit.invalidateNewerThan(Timestamp.ZERO);

View File

@@ -29,6 +29,8 @@ import org.jetbrains.annotations.*;
import javax.inject.*;
import static org.isoron.uhabits.core.models.Checkmark.*;
public class WidgetBehavior
{
private HabitList habitList;
@@ -57,57 +59,42 @@ public class WidgetBehavior
public void onAddRepetition(@NonNull Habit habit, Timestamp timestamp)
{
notificationTray.cancel(habit);
Repetition rep = habit.getRepetitions().getByTimestamp(timestamp);
if (rep != null) return;
performToggle(habit, timestamp, Checkmark.YES_MANUAL);
setValue(habit, timestamp, YES_MANUAL);
}
public void onRemoveRepetition(@NonNull Habit habit, Timestamp timestamp)
{
notificationTray.cancel(habit);
Repetition rep = habit.getRepetitions().getByTimestamp(timestamp);
if (rep == null) return;
performToggle(habit, timestamp, Checkmark.NO);
setValue(habit, timestamp, NO);
}
public void onToggleRepetition(@NonNull Habit habit, Timestamp timestamp)
{
Repetition previous = habit.getRepetitions().getByTimestamp(timestamp);
if(previous == null)
{
performToggle(habit, timestamp, Checkmark.YES_MANUAL);
}
int currentValue = habit.getRepetitions().getValue(timestamp);
int newValue;
if(preferences.isSkipEnabled())
newValue = Repetition.nextToggleValueWithSkip(currentValue);
else
{
int value;
if(preferences.isSkipEnabled())
value = Repetition.nextToggleValueWithSkip(previous.getValue());
else
value = Repetition.nextToggleValueWithoutSkip(previous.getValue());
performToggle(habit, timestamp, value);
}
}
private void performToggle(@NonNull Habit habit, Timestamp timestamp, int value)
{
commandRunner.execute(
new CreateRepetitionCommand(habitList, habit, timestamp, value),
habit.getId());
}
public void setNumericValue(@NonNull Habit habit, Timestamp timestamp, int newValue) {
commandRunner.execute(
new CreateRepetitionCommand(habitList, habit, timestamp, newValue),
habit.getId());
newValue = Repetition.nextToggleValueWithoutSkip(currentValue);
setValue(habit, timestamp, newValue);
notificationTray.cancel(habit);
}
public void onIncrement(@NotNull Habit habit, @NotNull Timestamp timestamp, int amount) {
int currentValue = habit.getCheckmarks().getValues(timestamp, timestamp)[0];
setNumericValue(habit, timestamp, currentValue + amount);
setValue(habit, timestamp, currentValue + amount);
notificationTray.cancel(habit);
}
public void onDecrement(@NotNull Habit habit, @NotNull Timestamp timestamp, int amount) {
int currentValue = habit.getCheckmarks().getValues(timestamp, timestamp)[0];
setNumericValue(habit, timestamp, currentValue - amount);
setValue(habit, timestamp, currentValue - amount);
notificationTray.cancel(habit);
}
public void setValue(@NonNull Habit habit, Timestamp timestamp, int newValue) {
commandRunner.execute(
new CreateRepetitionCommand(habitList, habit, timestamp, newValue),
habit.getId());
}
}