Refactor Habit

pull/699/head
Alinson S. Xavier 5 years ago
parent 94c78ebb72
commit cf2989d587

@ -80,7 +80,7 @@ class IntentSchedulerTest : BaseAndroidTest() {
@Test
@MediumTest
fun testScheduleShowReminder() {
for (h in habitList) h.setReminder(null)
for (h in habitList) h.reminder = null
ReminderReceiver.clearLastReceivedIntent()
setSystemTime("America/Chicago", 2020, JUNE, 1, 12, 30)

@ -60,7 +60,7 @@ public class PerformanceTest extends BaseAndroidTest
@Test(timeout = 5000)
public void benchmarkCreateHabitCommand()
{
Database db = ((SQLModelFactory) modelFactory).db;
Database db = ((SQLModelFactory) modelFactory).getDatabase();
db.beginTransaction();
for (int i = 0; i < 1_000; i++)
{
@ -75,7 +75,7 @@ public class PerformanceTest extends BaseAndroidTest
@Test(timeout = 5000)
public void benchmarkCreateRepetitionCommand()
{
Database db = ((SQLModelFactory) modelFactory).db;
Database db = ((SQLModelFactory) modelFactory).getDatabase();
db.beginTransaction();
Habit habit = fixtures.createEmptyHabit();
for (int i = 0; i < 5_000; i++)

@ -48,6 +48,8 @@ public class TargetWidgetTest extends BaseViewTest
habit = fixtures.createLongNumericalHabit();
habit.setColor(new PaletteColor(11));
habit.setFrequency(Frequency.WEEKLY);
habit.invalidateNewerThan(Timestamp.ZERO);
TargetWidget widget = new TargetWidget(targetContext, 0, habit);
view = convertToView(widget, 400, 400);
}

@ -73,10 +73,10 @@ class EditHabitActivity : AppCompatActivity() {
color = habit.color
freqNum = habit.frequency.numerator
freqDen = habit.frequency.denominator
if (habit.hasReminder()) {
reminderHour = habit.reminder.hour
reminderMin = habit.reminder.minute
reminderDays = habit.reminder.days
habit.reminder?.let {
reminderHour = it.hour
reminderMin = it.minute
reminderDays = it.days
}
binding.nameInput.setText(habit.name)
binding.questionInput.setText(habit.question)
@ -210,9 +210,9 @@ class EditHabitActivity : AppCompatActivity() {
habit.description = notesInput.text.trim().toString()
habit.color = color
if (reminderHour >= 0) {
habit.setReminder(Reminder(reminderHour, reminderMin, reminderDays))
habit.reminder = Reminder(reminderHour, reminderMin, reminderDays)
} else {
habit.setReminder(null)
habit.reminder = null
}
habit.frequency = Frequency(freqNum, freqDen)

@ -82,7 +82,7 @@ class SubtitleCardPresenter(
fun present(): SubtitleCardViewModel {
val reminderText = if (habit.hasReminder()) {
formatTime(context, habit.reminder.hour, habit.reminder.minute)!!
formatTime(context, habit.reminder!!.hour, habit.reminder!!.minute)!!
} else {
resources.getString(R.string.reminder_off)
}

@ -69,7 +69,7 @@ class TargetCardPresenter(
val daysInQuarter = 91
val daysInYear = cal.getActualMaximum(Calendar.DAY_OF_YEAR)
val targetToday = habit.getTargetValue() / habit.frequency.denominator
val targetToday = habit.targetValue / habit.frequency.denominator
val targetThisWeek = targetToday * 7
val targetThisMonth = targetToday * daysInMonth
val targetThisQuarter = targetToday * daysInQuarter

@ -28,13 +28,13 @@ import org.isoron.uhabits.core.models.*
class EditSettingController(private val activity: Activity) {
fun onSave(habit: Habit, action: Int) {
if (habit.getId() == null) return
if (habit.id == null) return
val actionName = getActionName(action)
val blurb = String.format("%s: %s", actionName, habit.name)
val bundle = Bundle()
bundle.putInt("action", action)
bundle.putLong("habit", habit.getId()!!)
bundle.putLong("habit", habit.id!!)
activity.setResult(Activity.RESULT_OK, Intent().apply {
putExtra(EXTRA_STRING_BLURB, blurb)

@ -76,7 +76,7 @@ class PendingIntentFactory
timestamp: Long): PendingIntent =
PendingIntent.getBroadcast(
context,
(habit.getId()!! % Integer.MAX_VALUE).toInt() + 1,
(habit.id!! % Integer.MAX_VALUE).toInt() + 1,
Intent(context, ReminderReceiver::class.java).apply {
action = ReminderReceiver.ACTION_SHOW_REMINDER
data = Uri.parse(habit.uriString)

@ -81,7 +81,7 @@ public class ReminderReceiver extends BroadcastReceiver
if (habit == null) return;
Log.d("ReminderReceiver", String.format(
"onShowReminder habit=%d timestamp=%d reminderTime=%d",
habit.id,
habit.getId(),
timestamp,
reminderTime));
reminderController.onShowReminder(habit,
@ -90,13 +90,13 @@ public class ReminderReceiver extends BroadcastReceiver
case ACTION_DISMISS_REMINDER:
if (habit == null) return;
Log.d("ReminderReceiver", String.format("onDismiss habit=%d", habit.id));
Log.d("ReminderReceiver", String.format("onDismiss habit=%d", habit.getId()));
reminderController.onDismiss(habit);
break;
case ACTION_SNOOZE_REMINDER:
if (habit == null) return;
Log.d("ReminderReceiver", String.format("onSnoozePressed habit=%d", habit.id));
Log.d("ReminderReceiver", String.format("onSnoozePressed habit=%d", habit.getId()));
reminderController.onSnoozePressed(habit, context);
break;

@ -67,7 +67,7 @@ public class ImportDataTask implements Task
@Override
public void doInBackground()
{
modelFactory.db.beginTransaction();
modelFactory.getDatabase().beginTransaction();
try
{
@ -75,7 +75,7 @@ public class ImportDataTask implements Task
{
importer.importHabitsFromFile(file);
result = SUCCESS;
modelFactory.db.setTransactionSuccessful();
modelFactory.getDatabase().setTransactionSuccessful();
}
else
{
@ -88,7 +88,7 @@ public class ImportDataTask implements Task
Log.e("ImportDataTask", "Import failed", e);
}
modelFactory.db.endTransaction();
modelFactory.getDatabase().endTransaction();
}
@Override

@ -66,7 +66,7 @@ open class CheckmarkWidget(
override fun getDefaultWidth() = 125
private fun getNumericalEntryState(): Int {
return if (habit.isCompletedToday) {
return if (habit.isCompletedToday()) {
Entry.YES_MANUAL
} else {
Entry.NO

@ -119,10 +119,9 @@ public class LoopDBImporter extends AbstractImporter
else
{
Habit modified = modelFactory.buildHabit();
habitRecord.id = habit.id;
habitRecord.id = habit.getId();
habitRecord.copyTo(modified);
if (!modified.getData().equals(habit.getData()))
new EditHabitCommand(modelFactory, habitList, habit, modified).execute();
new EditHabitCommand(modelFactory, habitList, habit, modified).execute();
}
// Reload saved version of the habit

@ -31,6 +31,7 @@ import java.util.*;
import javax.annotation.concurrent.*;
import static org.isoron.uhabits.core.models.Entry.*;
import static org.isoron.uhabits.core.models.Habit.*;
import static org.isoron.uhabits.core.utils.StringUtils.*;
/**
@ -39,16 +40,20 @@ import static org.isoron.uhabits.core.utils.StringUtils.*;
@ThreadSafe
public class EntryList
{
protected final Habit habit;
protected Habit habit = null;
protected ArrayList<Entry> list;
public EntryList(Habit habit)
public EntryList()
{
this.habit = habit;
this.list = new ArrayList<>();
}
public void setHabit(Habit habit)
{
this.habit = habit;
}
@NonNull
static List<Entry> buildEntriesFromInterval(Entry[] original,
ArrayList<Interval> intervals)
@ -425,6 +430,22 @@ public class EntryList
return getByInterval(oldest.getTimestamp(), DateUtils.getTodayWithOffset());
}
public boolean isCompletedToday()
{
int todayCheckmark = getTodayValue();
if (habit.isNumerical())
{
if (habit.getTargetType() == AT_LEAST)
return todayCheckmark / 1000.0 >= habit.getTargetValue();
else
return todayCheckmark / 1000.0 <= habit.getTargetValue();
}
else
{
return (todayCheckmark != NO && todayCheckmark != UNKNOWN);
}
}
static final class Interval
{
final Timestamp begin;

@ -1,510 +0,0 @@
/*
* Copyright (C) 2016 Álinson Santos Xavier <isoron@gmail.com>
*
* This file is part of Loop Habit Tracker.
*
* Loop Habit Tracker is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by the
* Free Software Foundation, either version 3 of the License, or (at your
* option) any later version.
*
* Loop Habit Tracker is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*
* You should have received a copy of the GNU General Public License along
* with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.isoron.uhabits.core.models;
import androidx.annotation.*;
import org.apache.commons.lang3.builder.*;
import java.util.*;
import javax.annotation.concurrent.*;
import javax.inject.*;
import static org.isoron.uhabits.core.models.Entry.*;
import static org.isoron.uhabits.core.utils.StringUtils.defaultToStringStyle;
/**
* The thing that the user wants to track.
*/
@ThreadSafe
public class Habit
{
public static final int AT_LEAST = 0;
public static final int AT_MOST = 1;
public static final String HABIT_URI_FORMAT =
"content://org.isoron.uhabits/habit/%d";
public static final int NUMBER_HABIT = 1;
public static final int YES_NO_HABIT = 0;
@Nullable
public Long id;
@NonNull
private HabitData data;
@NonNull
private StreakList streaks;
@NonNull
private ScoreList scores;
@NonNull
private RepetitionList repetitions;
@NonNull
private EntryList computedEntries;
private ModelObservable observable = new ModelObservable();
/**
* Constructs a habit with default data.
* <p>
* The habit is not archived, not highlighted, has no reminders and is
* placed in the last position of the list of habits.
*/
@Inject
Habit(@NonNull ModelFactory factory)
{
this.data = new HabitData();
computedEntries = factory.buildEntryList(this);
streaks = factory.buildStreakList(this);
scores = factory.buildScoreList(this);
repetitions = factory.buildRepetitionList(this);
}
Habit(@NonNull ModelFactory factory, @NonNull HabitData data)
{
this.data = new HabitData(data);
computedEntries = factory.buildEntryList(this);
streaks = factory.buildStreakList(this);
scores = factory.buildScoreList(this);
repetitions = factory.buildRepetitionList(this);
observable = new ModelObservable();
}
/**
* Clears the reminder for a habit.
*/
public synchronized void clearReminder()
{
data.reminder = null;
observable.notifyListeners();
}
/**
* Copies all the attributes of the specified habit into this habit
*
* @param model the model whose attributes should be copied from
*/
public synchronized void copyFrom(@NonNull Habit model)
{
this.data = new HabitData(model.data);
observable.notifyListeners();
}
@NonNull
public synchronized EntryList getComputedEntries()
{
return computedEntries;
}
@NonNull
public synchronized PaletteColor getColor()
{
return data.color;
}
public synchronized void setColor(@NonNull PaletteColor color)
{
data.color = color;
}
@NonNull
public synchronized String getDescription()
{
return data.description;
}
public synchronized void setDescription(@NonNull String description)
{
data.description = description;
}
@NonNull
public synchronized Frequency getFrequency()
{
return data.frequency;
}
public synchronized void setFrequency(@NonNull Frequency frequency)
{
data.frequency = frequency;
invalidateNewerThan(Timestamp.ZERO);
}
@Nullable
public synchronized Long getId()
{
return id;
}
public synchronized void setId(@Nullable Long id)
{
this.id = id;
}
@NonNull
public synchronized String getName()
{
return data.name;
}
public synchronized void setName(@NonNull String name)
{
data.name = name;
}
public ModelObservable getObservable()
{
return observable;
}
/**
* Returns the reminder for this habit.
* <p>
* Before calling this method, you should call {@link #hasReminder()} to
* verify that a reminder does exist, otherwise an exception will be
* thrown.
*
* @return the reminder for this habit
* @throws IllegalStateException if habit has no reminder
*/
@NonNull
public synchronized Reminder getReminder()
{
if (data.reminder == null) throw new IllegalStateException();
return data.reminder;
}
public synchronized void setReminder(@Nullable Reminder reminder)
{
data.reminder = reminder;
}
public RepetitionList getOriginalEntries()
{
return repetitions;
}
@NonNull
public ScoreList getScores()
{
return scores;
}
@NonNull
public StreakList getStreaks()
{
return streaks;
}
public synchronized int getTargetType()
{
return data.targetType;
}
public synchronized void setTargetType(int targetType)
{
if (targetType != AT_LEAST && targetType != AT_MOST)
throw new IllegalArgumentException(
String.format("invalid targetType: %d", targetType));
data.targetType = targetType;
}
public synchronized double getTargetValue()
{
return data.targetValue;
}
public synchronized void setTargetValue(double targetValue)
{
if (targetValue < 0) throw new IllegalArgumentException();
data.targetValue = targetValue;
}
public synchronized int getType()
{
return data.type;
}
public synchronized void setType(int type)
{
if (type != YES_NO_HABIT && type != NUMBER_HABIT)
throw new IllegalArgumentException();
data.type = type;
}
@NonNull
public synchronized String getUnit()
{
return data.unit;
}
public synchronized void setUnit(@NonNull String unit)
{
data.unit = unit;
}
/**
* Returns the public URI that identifies this habit
*
* @return the URI
*/
public String getUriString()
{
return String.format(Locale.US, HABIT_URI_FORMAT, getId());
}
public synchronized boolean hasId()
{
return getId() != null;
}
/**
* Returns whether the habit has a reminder.
*
* @return true if habit has reminder, false otherwise
*/
public synchronized boolean hasReminder()
{
return data.reminder != null;
}
public void invalidateNewerThan(Timestamp timestamp)
{
getScores().invalidateNewerThan(timestamp);
getComputedEntries().invalidateNewerThan(timestamp);
getStreaks().invalidateNewerThan(timestamp);
}
public synchronized boolean isArchived()
{
return data.archived;
}
public synchronized void setArchived(boolean archived)
{
data.archived = archived;
}
public synchronized boolean isCompletedToday()
{
int todayCheckmark = getComputedEntries().getTodayValue();
if (isNumerical())
{
if(getTargetType() == AT_LEAST)
return todayCheckmark / 1000.0 >= data.targetValue;
else
return todayCheckmark / 1000.0 <= data.targetValue;
}
else return (todayCheckmark != NO && todayCheckmark != UNKNOWN);
}
public synchronized boolean isNumerical()
{
return data.type == NUMBER_HABIT;
}
public HabitData getData()
{
return new HabitData(data);
}
public Integer getPosition()
{
return data.position;
}
public void setPosition(int newPosition)
{
data.position = newPosition;
}
@NonNull
public String getQuestion()
{
return data.question;
}
public void setQuestion(@NonNull String question)
{
data.question = question;
}
@NonNull
public String getUUID()
{
return data.uuid;
}
public void setUUID(@NonNull String uuid)
{
data.uuid = uuid;
}
public static final class HabitData
{
@NonNull
public String name;
@NonNull
public String description;
@NonNull
public String question;
@NonNull
public Frequency frequency;
public PaletteColor color;
public boolean archived;
public int targetType;
public double targetValue;
public int type;
public String uuid;
@NonNull
public String unit;
@Nullable
public Reminder reminder;
public int position;
public HabitData()
{
this.color = new PaletteColor(8);
this.archived = false;
this.frequency = new Frequency(3, 7);
this.type = YES_NO_HABIT;
this.name = "";
this.description = "";
this.question = "";
this.targetType = AT_LEAST;
this.targetValue = 100;
this.unit = "";
this.position = 0;
this.uuid = UUID.randomUUID().toString().replace("-", "");
}
public HabitData(@NonNull HabitData model)
{
this.name = model.name;
this.description = model.description;
this.question = model.question;
this.frequency = model.frequency;
this.color = model.color;
this.archived = model.archived;
this.targetType = model.targetType;
this.targetValue = model.targetValue;
this.type = model.type;
this.unit = model.unit;
this.reminder = model.reminder;
this.position = model.position;
this.uuid = model.uuid;
}
@Override
public String toString()
{
return new ToStringBuilder(this, defaultToStringStyle())
.append("name", name)
.append("description", description)
.append("frequency", frequency)
.append("color", color)
.append("archived", archived)
.append("targetType", targetType)
.append("targetValue", targetValue)
.append("type", type)
.append("unit", unit)
.append("reminder", reminder)
.append("position", position)
.append("question", question)
.append("uuid", uuid)
.toString();
}
@Override
public boolean equals(Object o)
{
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
HabitData habitData = (HabitData) o;
return new EqualsBuilder()
.append(color, habitData.color)
.append(archived, habitData.archived)
.append(targetType, habitData.targetType)
.append(targetValue, habitData.targetValue)
.append(type, habitData.type)
.append(name, habitData.name)
.append(description, habitData.description)
.append(frequency, habitData.frequency)
.append(unit, habitData.unit)
.append(reminder, habitData.reminder)
.append(position, habitData.position)
.append(question, habitData.question)
.append(uuid, habitData.uuid)
.isEquals();
}
@Override
public int hashCode()
{
return new HashCodeBuilder(17, 37)
.append(name)
.append(description)
.append(frequency)
.append(color)
.append(archived)
.append(targetType)
.append(targetValue)
.append(type)
.append(unit)
.append(reminder)
.append(position)
.append(question)
.append(uuid)
.toHashCode();
}
}
@Override
public String toString()
{
return new ToStringBuilder(this, defaultToStringStyle())
.append("id", id)
.append("data", data)
.toString();
}
}

@ -0,0 +1,122 @@
/*
* Copyright (C) 2016 Álinson Santos Xavier <isoron@gmail.com>
*
* This file is part of Loop Habit Tracker.
*
* Loop Habit Tracker is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by the
* Free Software Foundation, either version 3 of the License, or (at your
* option) any later version.
*
* Loop Habit Tracker is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*
* You should have received a copy of the GNU General Public License along
* with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.isoron.uhabits.core.models
data class Habit(
var color: PaletteColor = PaletteColor(8),
var description: String = "",
var frequency: Frequency = Frequency.DAILY,
var id: Long? = null,
var isArchived: Boolean = false,
var name: String = "",
var position: Int = 0,
var question: String = "",
var reminder: Reminder? = null,
var targetType: Int = AT_LEAST,
var targetValue: Double = 0.0,
var type: Int = YES_NO_HABIT,
var unit: String = "",
var uuid: String? = null,
val computedEntries: EntryList,
val originalEntries: RepetitionList,
val scores: ScoreList,
val streaks: StreakList,
) {
var observable = ModelObservable()
val isNumerical: Boolean
get() = type == NUMBER_HABIT
val uriString: String
get() = "content://org.isoron.uhabits/habit/$id"
fun hasReminder(): Boolean = reminder != null
fun isCompletedToday(): Boolean = computedEntries.isCompletedToday
fun invalidateNewerThan(timestamp: Timestamp?) {
scores.invalidateNewerThan(timestamp)
computedEntries.invalidateNewerThan(timestamp)
streaks.invalidateNewerThan(timestamp)
}
fun copyFrom(other: Habit) {
this.color = other.color
this.description = other.description
this.frequency = other.frequency
// this.id should not be copied
this.isArchived = other.isArchived
this.name = other.name
this.position = other.position
this.question = other.question
this.reminder = other.reminder
this.targetType = other.targetType
this.targetValue = other.targetValue
this.type = other.type
this.unit = other.unit
this.uuid = other.uuid
}
override fun equals(other: Any?): Boolean {
if (this === other) return true
if (other !is Habit) return false
if (color != other.color) return false
if (description != other.description) return false
if (frequency != other.frequency) return false
if (id != other.id) return false
if (isArchived != other.isArchived) return false
if (name != other.name) return false
if (position != other.position) return false
if (question != other.question) return false
if (reminder != other.reminder) return false
if (targetType != other.targetType) return false
if (targetValue != other.targetValue) return false
if (type != other.type) return false
if (unit != other.unit) return false
if (uuid != other.uuid) return false
return true
}
override fun hashCode(): Int {
var result = color.hashCode()
result = 31 * result + description.hashCode()
result = 31 * result + frequency.hashCode()
result = 31 * result + (id?.hashCode() ?: 0)
result = 31 * result + isArchived.hashCode()
result = 31 * result + name.hashCode()
result = 31 * result + position
result = 31 * result + question.hashCode()
result = 31 * result + (reminder?.hashCode() ?: 0)
result = 31 * result + targetType
result = 31 * result + targetValue.hashCode()
result = 31 * result + type
result = 31 * result + unit.hashCode()
result = 31 * result + (uuid?.hashCode() ?: 0)
return result
}
companion object {
const val AT_LEAST = 0
const val AT_MOST = 1
const val NUMBER_HABIT = 1
const val YES_NO_HABIT = 0
}
}

@ -1,54 +0,0 @@
/*
* Copyright (C) 2016 Álinson Santos Xavier <isoron@gmail.com>
*
* This file is part of Loop Habit Tracker.
*
* Loop Habit Tracker is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by the
* Free Software Foundation, either version 3 of the License, or (at your
* option) any later version.
*
* Loop Habit Tracker is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*
* You should have received a copy of the GNU General Public License along
* with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.isoron.uhabits.core.models;
import org.isoron.uhabits.core.database.*;
import org.isoron.uhabits.core.models.sqlite.records.*;
/**
* Interface implemented by factories that provide concrete implementations of
* the core model classes.
*/
public interface ModelFactory
{
EntryList buildEntryList(Habit habit);
default Habit buildHabit()
{
return new Habit(this);
}
default Habit buildHabit(Habit.HabitData data)
{
return new Habit(this, data);
}
HabitList buildHabitList();
RepetitionList buildRepetitionList(Habit habit);
ScoreList buildScoreList(Habit habit);
StreakList buildStreakList(Habit habit);
Repository<HabitRecord> buildHabitListRepository();
Repository<RepetitionRecord> buildRepetitionListRepository();
}

@ -0,0 +1,55 @@
/*
* Copyright (C) 2016 Álinson Santos Xavier <isoron@gmail.com>
*
* This file is part of Loop Habit Tracker.
*
* Loop Habit Tracker is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by the
* Free Software Foundation, either version 3 of the License, or (at your
* option) any later version.
*
* Loop Habit Tracker is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*
* You should have received a copy of the GNU General Public License along
* with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.isoron.uhabits.core.models
import org.isoron.uhabits.core.database.*
import org.isoron.uhabits.core.models.sqlite.records.*
/**
* Interface implemented by factories that provide concrete implementations of
* the core model classes.
*/
interface ModelFactory {
fun buildHabit(): Habit {
val computedEntries = buildEntryList()
val originalEntries = buildRepetitionList()
val scores = buildScoreList()
val streaks = buildStreakList()
val habit = Habit(
computedEntries = computedEntries,
originalEntries = originalEntries,
scores = scores,
streaks = streaks,
)
computedEntries.setHabit(habit)
originalEntries.setHabit(habit)
scores.setHabit(habit)
streaks.setHabit(habit)
return habit
}
fun buildEntryList(): EntryList
fun buildHabitList(): HabitList
fun buildRepetitionList(): RepetitionList
fun buildScoreList(): ScoreList
fun buildStreakList(): StreakList
fun buildHabitListRepository(): Repository<HabitRecord>
fun buildRepetitionListRepository(): Repository<RepetitionRecord>
}

@ -27,12 +27,11 @@ import java.util.*;
public class RepetitionList
{
@NonNull
protected final Habit habit;
protected Habit habit;
private final ArrayList<Entry> list = new ArrayList<>();
public RepetitionList(@NonNull Habit habit)
public void setHabit(Habit habit)
{
this.habit = habit;
}

@ -31,22 +31,13 @@ import static org.isoron.uhabits.core.models.Entry.*;
public abstract class ScoreList implements Iterable<Score>
{
protected final Habit habit;
protected Habit habit;
protected ModelObservable observable;
protected ModelObservable observable = new ModelObservable();
/**
* Creates a new ScoreList for the given habit.
* <p>
* The list is populated automatically according to the repetitions that the
* habit has.
*
* @param habit the habit to which the scores belong.
*/
public ScoreList(Habit habit)
public void setHabit(Habit habit)
{
this.habit = habit;
observable = new ModelObservable();
}
/**

@ -32,14 +32,13 @@ import java.util.*;
*/
public abstract class StreakList
{
protected final Habit habit;
protected Habit habit;
protected ModelObservable observable;
protected ModelObservable observable = new ModelObservable();
protected StreakList(Habit habit)
public void setHabit(Habit habit)
{
this.habit = habit;
observable = new ModelObservable();
}
public abstract List<Streak> getAll();

@ -95,7 +95,7 @@ public class MemoryHabitList extends HabitList
@Override
public synchronized Habit getByUUID(String uuid)
{
for (Habit h : list) if (h.getUUID().equals(uuid)) return h;
for (Habit h : list) if (h.getUuid().equals(uuid)) return h;
return null;
}
@ -174,7 +174,7 @@ public class MemoryHabitList extends HabitList
scoreComparatorDesc.compare(h2, h1);
Comparator<Habit> positionComparator = (h1, h2) ->
h1.getPosition().compareTo(h2.getPosition());
Integer.compare(h1.getPosition(), h2.getPosition());
Comparator<Habit> statusComparatorDesc = (h1, h2) ->
{

@ -1,69 +0,0 @@
/*
* Copyright (C) 2016 Álinson Santos Xavier <isoron@gmail.com>
*
* This file is part of Loop Habit Tracker.
*
* Loop Habit Tracker is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by the
* Free Software Foundation, either version 3 of the License, or (at your
* option) any later version.
*
* Loop Habit Tracker is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*
* You should have received a copy of the GNU General Public License along
* with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.isoron.uhabits.core.models.memory;
import org.isoron.uhabits.core.database.*;
import org.isoron.uhabits.core.models.*;
import org.isoron.uhabits.core.models.sqlite.records.*;
public class MemoryModelFactory implements ModelFactory
{
@Override
public EntryList buildEntryList(Habit habit)
{
return new EntryList(habit);
}
@Override
public HabitList buildHabitList()
{
return new MemoryHabitList();
}
@Override
public RepetitionList buildRepetitionList(Habit habit)
{
return new RepetitionList(habit);
}
@Override
public ScoreList buildScoreList(Habit habit)
{
return new MemoryScoreList(habit);
}
@Override
public StreakList buildStreakList(Habit habit)
{
return new MemoryStreakList(habit);
}
@Override
public Repository<HabitRecord> buildHabitListRepository()
{
throw new IllegalStateException();
}
@Override
public Repository<RepetitionRecord> buildRepetitionListRepository()
{
throw new IllegalStateException();
}
}

@ -0,0 +1,31 @@
/*
* Copyright (C) 2016 Álinson Santos Xavier <isoron@gmail.com>
*
* This file is part of Loop Habit Tracker.
*
* Loop Habit Tracker is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by the
* Free Software Foundation, either version 3 of the License, or (at your
* option) any later version.
*
* Loop Habit Tracker is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*
* You should have received a copy of the GNU General Public License along
* with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.isoron.uhabits.core.models.memory
import org.isoron.uhabits.core.models.*
class MemoryModelFactory : ModelFactory {
override fun buildEntryList() = EntryList()
override fun buildHabitList() = MemoryHabitList()
override fun buildRepetitionList() = RepetitionList()
override fun buildScoreList() = MemoryScoreList()
override fun buildStreakList() = MemoryStreakList()
override fun buildHabitListRepository() = throw NotImplementedError()
override fun buildRepetitionListRepository() = throw NotImplementedError()
}

@ -27,13 +27,7 @@ import java.util.*;
public class MemoryScoreList extends ScoreList
{
ArrayList<Score> list;
public MemoryScoreList(Habit habit)
{
super(habit);
list = new ArrayList<>();
}
ArrayList<Score> list = new ArrayList<>();
@Override
public void add(List<Score> scores)

@ -27,13 +27,7 @@ import java.util.*;
public class MemoryStreakList extends StreakList
{
ArrayList<Streak> list;
public MemoryStreakList(Habit habit)
{
super(habit);
list = new ArrayList<>();
}
ArrayList<Streak> list = new ArrayList<>();
@Override
public Streak getNewestComputed()

@ -1,85 +0,0 @@
/*
* Copyright (C) 2017 Álinson Santos Xavier <isoron@gmail.com>
*
* This file is part of Loop Habit Tracker.
*
* Loop Habit Tracker is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by the
* Free Software Foundation, either version 3 of the License, or (at your
* option) any later version.
*
* Loop Habit Tracker is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*
* You should have received a copy of the GNU General Public License along
* with this program. If not, see <http://www.gnu.org/licenses/>.
*
*
*/
package org.isoron.uhabits.core.models.sqlite;
import org.isoron.uhabits.core.database.*;
import org.isoron.uhabits.core.models.*;
import org.isoron.uhabits.core.models.memory.*;
import org.isoron.uhabits.core.models.sqlite.records.*;
import javax.inject.*;
/**
* Factory that provides models backed by an SQLite database.
*/
public class SQLModelFactory implements ModelFactory
{
public final Database db;
@Inject
public SQLModelFactory(Database db)
{
this.db = db;
}
@Override
public EntryList buildEntryList(Habit habit)
{
return new EntryList(habit);
}
@Override
public HabitList buildHabitList()
{
return new SQLiteHabitList(this);
}
@Override
public RepetitionList buildRepetitionList(Habit habit)
{
return new SQLiteRepetitionList(habit, this);
}
@Override
public ScoreList buildScoreList(Habit habit)
{
return new MemoryScoreList(habit);
}
@Override
public StreakList buildStreakList(Habit habit)
{
return new MemoryStreakList(habit);
}
@Override
public Repository<HabitRecord> buildHabitListRepository()
{
return new Repository<>(HabitRecord.class, db);
}
@Override
public Repository<RepetitionRecord> buildRepetitionListRepository()
{
return new Repository<>(RepetitionRecord.class, db);
}
}

@ -0,0 +1,48 @@
/*
* Copyright (C) 2017 Álinson Santos Xavier <isoron@gmail.com>
*
* This file is part of Loop Habit Tracker.
*
* Loop Habit Tracker is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by the
* Free Software Foundation, either version 3 of the License, or (at your
* option) any later version.
*
* Loop Habit Tracker is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*
* You should have received a copy of the GNU General Public License along
* with this program. If not, see <http://www.gnu.org/licenses/>.
*
*
*/
package org.isoron.uhabits.core.models.sqlite
import org.isoron.uhabits.core.database.*
import org.isoron.uhabits.core.models.*
import org.isoron.uhabits.core.models.memory.*
import org.isoron.uhabits.core.models.sqlite.records.*
import javax.inject.*
/**
* Factory that provides models backed by an SQLite database.
*/
class SQLModelFactory
@Inject constructor(
val database: Database,
) : ModelFactory {
override fun buildEntryList() = EntryList()
override fun buildHabitList() = SQLiteHabitList(this)
override fun buildRepetitionList() = SQLiteRepetitionList(this)
override fun buildScoreList() = MemoryScoreList()
override fun buildStreakList() = MemoryStreakList()
override fun buildHabitListRepository() =
Repository(HabitRecord::class.java, database)
override fun buildRepetitionListRepository() =
Repository(RepetitionRecord::class.java, database)
}

@ -91,7 +91,7 @@ public class SQLiteHabitList extends HabitList
HabitRecord record = new HabitRecord();
record.copyFrom(habit);
repository.save(record);
habit.id = record.id;
habit.setId(record.id);
list.add(habit);
getObservable().notifyListeners();

@ -40,10 +40,8 @@ public class SQLiteRepetitionList extends RepetitionList
private boolean loaded = false;
public SQLiteRepetitionList(@NonNull Habit habit,
@NonNull ModelFactory modelFactory)
public SQLiteRepetitionList(@NonNull ModelFactory modelFactory)
{
super(habit);
repository = modelFactory.buildRepetitionListRepository();
}

@ -98,7 +98,7 @@ public class HabitRecord
this.unit = model.getUnit();
this.position = model.getPosition();
this.question = model.getQuestion();
this.uuid = model.getUUID();
this.uuid = model.getUuid();
Frequency freq = model.getFrequency();
this.freqNum = freq.getNumerator();
@ -130,7 +130,7 @@ public class HabitRecord
habit.setTargetValue(this.targetValue);
habit.setUnit(this.unit);
habit.setPosition(this.position);
habit.setUUID(this.uuid);
habit.setUuid(this.uuid);
if (reminderHour != null && reminderMin != null)
{

@ -74,7 +74,7 @@ public class ReminderScheduler implements CommandRunner.Listener
if (!habit.hasReminder())
{
sys.log("ReminderScheduler", "habit=" + habit.id + " has no reminder. Skipping.");
sys.log("ReminderScheduler", "habit=" + habit.getId() + " has no reminder. Skipping.");
return;
}
@ -107,17 +107,17 @@ public class ReminderScheduler implements CommandRunner.Listener
public synchronized void scheduleAtTime(@NonNull Habit habit, long reminderTime)
{
sys.log("ReminderScheduler", "Scheduling alarm for habit=" + habit.id);
sys.log("ReminderScheduler", "Scheduling alarm for habit=" + habit.getId());
if (!habit.hasReminder())
{
sys.log("ReminderScheduler", "habit=" + habit.id + " has no reminder. Skipping.");
sys.log("ReminderScheduler", "habit=" + habit.getId() + " has no reminder. Skipping.");
return;
}
if (habit.isArchived())
{
sys.log("ReminderScheduler", "habit=" + habit.id + " is archived. Skipping.");
sys.log("ReminderScheduler", "habit=" + habit.getId() + " is archived. Skipping.");
return;
}

@ -184,13 +184,13 @@ public class NotificationTray
@Override
public void onPostExecute()
{
systemTray.log("Showing notification for habit=" + habit.id);
systemTray.log("Showing notification for habit=" + habit.getId());
if (todayValue != Entry.UNKNOWN) {
systemTray.log(String.format(
Locale.US,
"Habit %d already checked. Skipping.",
habit.id));
habit.getId()));
return;
}
@ -198,7 +198,7 @@ public class NotificationTray
systemTray.log(String.format(
Locale.US,
"Habit %d does not have a reminder. Skipping.",
habit.id));
habit.getId()));
return;
}
@ -207,7 +207,7 @@ public class NotificationTray
systemTray.log(String.format(
Locale.US,
"Habit %d is archived. Skipping.",
habit.id));
habit.getId()));
return;
}
@ -215,7 +215,7 @@ public class NotificationTray
systemTray.log(String.format(
Locale.US,
"Habit %d not supposed to run today. Skipping.",
habit.id));
habit.getId()));
return;
}

@ -67,7 +67,7 @@ class ShowHabitBehavior(
timestamp,
thousands,
),
habit.getId(),
habit.id,
)
}
} else {

@ -52,10 +52,9 @@ public class EditHabitCommandTest extends BaseUnitTest
}
@Test
public void testExecuteUndoRedo()
public void testExecute()
{
command =
new EditHabitCommand(modelFactory, habitList, habit, modified);
command = new EditHabitCommand(modelFactory, habitList, habit, modified);
double originalScore = habit.getScores().getTodayValue();
assertThat(habit.getName(), equalTo("original"));
@ -66,7 +65,7 @@ public class EditHabitCommandTest extends BaseUnitTest
}
@Test
public void testExecuteUndoRedo_withModifiedInterval()
public void testExecute_withModifiedInterval()
{
modified.setFrequency(Frequency.TWO_TIMES_PER_WEEK);
command =

@ -72,16 +72,13 @@ public class HabitTest extends BaseUnitTest
@Test
public void test_hasReminder_clearReminder()
public void test_hasReminder()
{
Habit h = modelFactory.buildHabit();
assertThat(h.hasReminder(), is(false));
h.setReminder(new Reminder(8, 30, WeekdayList.EVERY_DAY));
assertThat(h.hasReminder(), is(true));
h.clearReminder();
assertThat(h.hasReminder(), is(false));
}
@Test
@ -132,31 +129,9 @@ public class HabitTest extends BaseUnitTest
@Test
public void testEquals() throws Exception
{
EqualsVerifier
.forClass(Habit.HabitData.class)
.suppress(Warning.NONFINAL_FIELDS)
.verify();
EqualsVerifier.forClass(Score.class).verify();
EqualsVerifier.forClass(Streak.class).verify();
EqualsVerifier.forClass(Reminder.class).verify();
EqualsVerifier.forClass(WeekdayList.class).verify();
}
@Test
public void testToString() throws Exception
{
Habit h = modelFactory.buildHabit();
h.setUUID("nnnn");
h.setReminder(new Reminder(22, 30, WeekdayList.EVERY_DAY));
String expected = "{id: <null>, data: {name: , description: ," +
" frequency: {numerator: 3, denominator: 7}," +
" color: PaletteColor(paletteIndex=8), archived: false, targetType: 0," +
" targetValue: 100.0, type: 0, unit: ," +
" reminder: {hour: 22, minute: 30," +
" days: {weekdays: [true,true,true,true,true,true,true]}}," +
" position: 0, question: , uuid: nnnn}}";
assertThat(h.toString(), equalTo(expected));
}
}

@ -219,6 +219,7 @@ public class ScoreListTest extends BaseUnitTest
// Missing 2 repetitions out of 4 per week, the score should converge to 50%
habit.setFrequency(new Frequency(4, 7));
habit.invalidateNewerThan(Timestamp.ZERO);
assertThat(habit.getScores().getTodayValue(), closeTo(0.5, E));
}

@ -50,7 +50,7 @@ public class HabitRecordTest extends BaseUnitTest
Habit duplicate = modelFactory.buildHabit();
record.copyTo(duplicate);
assertThat(original.getData(), equalTo(duplicate.getData()));
assertThat(original, equalTo(duplicate));
}
@Test
@ -76,6 +76,6 @@ public class HabitRecordTest extends BaseUnitTest
Habit duplicate = modelFactory.buildHabit();
record.copyTo(duplicate);
assertThat(original.getData(), equalTo(duplicate.getData()));
assertThat(original, equalTo(duplicate));
}
}

@ -56,7 +56,7 @@ public class ReminderSchedulerTest extends BaseUnitTest
{
super.setUp();
habit = fixtures.createEmptyHabit();
habit.id = habitId;
habit.setId(habitId);
reminderScheduler =
new ReminderScheduler(commandRunner, habitList, sys, widgetPreferences);

@ -63,7 +63,7 @@ public class WidgetBehaviorTest extends BaseUnitTest
behavior.onAddRepetition(habit, timestamp);
verify(commandRunner).execute(
new CreateRepetitionCommand(habitList, habit, timestamp, YES_MANUAL),
habit.id);
habit.getId());
verify(notificationTray).cancel(habit);
verifyZeroInteractions(preferences);
}
@ -74,7 +74,7 @@ public class WidgetBehaviorTest extends BaseUnitTest
behavior.onRemoveRepetition(habit, timestamp);
verify(commandRunner).execute(
new CreateRepetitionCommand(habitList, habit, timestamp, NO),
habit.id);
habit.getId());
verify(notificationTray).cancel(habit);
verifyZeroInteractions(preferences);
}
@ -96,7 +96,7 @@ public class WidgetBehaviorTest extends BaseUnitTest
verify(preferences).isSkipEnabled();
verify(commandRunner).execute(
new CreateRepetitionCommand(habitList, habit, timestamp, nextValue),
habit.id);
habit.getId());
verify(notificationTray).cancel(habit);
reset(preferences, commandRunner, notificationTray);
}
@ -111,7 +111,7 @@ public class WidgetBehaviorTest extends BaseUnitTest
behavior.onIncrement(habit, timestamp, 100);
verify(commandRunner).execute(
new CreateRepetitionCommand(habitList, habit, timestamp, 600),
habit.id);
habit.getId());
verify(notificationTray).cancel(habit);
verifyZeroInteractions(preferences);
}
@ -125,7 +125,7 @@ public class WidgetBehaviorTest extends BaseUnitTest
behavior.onDecrement(habit, timestamp, 100);
verify(commandRunner).execute(
new CreateRepetitionCommand(habitList, habit, timestamp, 400),
habit.id);
habit.getId());
verify(notificationTray).cancel(habit);
verifyZeroInteractions(preferences);
}

Loading…
Cancel
Save