mirror of
https://github.com/iSoron/uhabits.git
synced 2025-12-06 09:08:52 -06:00
Separate ActiveAndroid from models
This commit is contained in:
@@ -1,6 +1,5 @@
|
|||||||
apply plugin: 'com.android.application'
|
apply plugin: 'com.android.application'
|
||||||
apply plugin: 'com.neenbedankt.android-apt'
|
apply plugin: 'com.neenbedankt.android-apt'
|
||||||
apply plugin: 'com.getkeepsafe.dexcount'
|
|
||||||
apply plugin: 'me.tatarka.retrolambda'
|
apply plugin: 'me.tatarka.retrolambda'
|
||||||
|
|
||||||
android {
|
android {
|
||||||
@@ -31,6 +30,7 @@ android {
|
|||||||
lintOptions {
|
lintOptions {
|
||||||
checkReleaseBuilds false
|
checkReleaseBuilds false
|
||||||
}
|
}
|
||||||
|
|
||||||
compileOptions {
|
compileOptions {
|
||||||
targetCompatibility 1.8
|
targetCompatibility 1.8
|
||||||
sourceCompatibility 1.8
|
sourceCompatibility 1.8
|
||||||
@@ -77,14 +77,3 @@ dependencies {
|
|||||||
exclude group: 'com.android.support'
|
exclude group: 'com.android.support'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
task grantAnimationPermission(type: Exec, dependsOn: 'installDebug') {
|
|
||||||
commandLine "adb shell pm grant org.isoron.uhabits android.permission.SET_ANIMATION_SCALE".split(' ')
|
|
||||||
}
|
|
||||||
|
|
||||||
tasks.whenTaskAdded { task ->
|
|
||||||
if (task.name.startsWith('connected')) {
|
|
||||||
task.dependsOn grantAnimationPermission
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -24,9 +24,11 @@ import android.os.Build;
|
|||||||
import android.os.Looper;
|
import android.os.Looper;
|
||||||
import android.support.test.InstrumentationRegistry;
|
import android.support.test.InstrumentationRegistry;
|
||||||
|
|
||||||
|
import org.isoron.uhabits.models.HabitList;
|
||||||
|
import org.isoron.uhabits.tasks.BaseTask;
|
||||||
|
import org.isoron.uhabits.unit.HabitFixtures;
|
||||||
import org.isoron.uhabits.utils.DateUtils;
|
import org.isoron.uhabits.utils.DateUtils;
|
||||||
import org.isoron.uhabits.utils.InterfaceUtils;
|
import org.isoron.uhabits.utils.InterfaceUtils;
|
||||||
import org.isoron.uhabits.tasks.BaseTask;
|
|
||||||
import org.isoron.uhabits.utils.Preferences;
|
import org.isoron.uhabits.utils.Preferences;
|
||||||
import org.junit.Before;
|
import org.junit.Before;
|
||||||
|
|
||||||
@@ -36,16 +38,25 @@ import javax.inject.Inject;
|
|||||||
|
|
||||||
public class BaseAndroidTest
|
public class BaseAndroidTest
|
||||||
{
|
{
|
||||||
protected Context testContext;
|
// 8:00am, January 25th, 2015 (UTC)
|
||||||
protected Context targetContext;
|
public static final long FIXED_LOCAL_TIME = 1422172800000L;
|
||||||
|
|
||||||
private static boolean isLooperPrepared;
|
private static boolean isLooperPrepared;
|
||||||
|
|
||||||
public static final long FIXED_LOCAL_TIME = 1422172800000L; // 8:00am, January 25th, 2015 (UTC)
|
protected Context testContext;
|
||||||
|
|
||||||
|
protected Context targetContext;
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
protected Preferences prefs;
|
protected Preferences prefs;
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
protected HabitList habitList;
|
||||||
|
|
||||||
protected AndroidTestComponent androidTestComponent;
|
protected AndroidTestComponent androidTestComponent;
|
||||||
|
|
||||||
|
protected HabitFixtures habitFixtures;
|
||||||
|
|
||||||
@Before
|
@Before
|
||||||
public void setUp()
|
public void setUp()
|
||||||
{
|
{
|
||||||
@@ -64,9 +75,12 @@ public class BaseAndroidTest
|
|||||||
androidTestComponent = DaggerAndroidTestComponent.builder().build();
|
androidTestComponent = DaggerAndroidTestComponent.builder().build();
|
||||||
HabitsApplication.setComponent(androidTestComponent);
|
HabitsApplication.setComponent(androidTestComponent);
|
||||||
androidTestComponent.inject(this);
|
androidTestComponent.inject(this);
|
||||||
|
|
||||||
|
habitFixtures = new HabitFixtures(habitList);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void waitForAsyncTasks() throws InterruptedException, TimeoutException
|
protected void waitForAsyncTasks()
|
||||||
|
throws InterruptedException, TimeoutException
|
||||||
{
|
{
|
||||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN)
|
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -39,7 +39,7 @@ public class HabitMatchers
|
|||||||
@Override
|
@Override
|
||||||
public boolean matchesSafely(Habit habit)
|
public boolean matchesSafely(Habit habit)
|
||||||
{
|
{
|
||||||
return habit.name.equals(name);
|
return habit.getName().equals(name);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -51,7 +51,7 @@ public class HabitMatchers
|
|||||||
@Override
|
@Override
|
||||||
public void describeMismatchSafely(Habit habit, Description description)
|
public void describeMismatchSafely(Habit habit, Description description)
|
||||||
{
|
{
|
||||||
description.appendText("was ").appendText(habit.name);
|
description.appendText("was ").appendText(habit.getName());
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ import android.support.test.espresso.NoMatchingViewException;
|
|||||||
import android.support.test.espresso.contrib.RecyclerViewActions;
|
import android.support.test.espresso.contrib.RecyclerViewActions;
|
||||||
|
|
||||||
import org.isoron.uhabits.R;
|
import org.isoron.uhabits.R;
|
||||||
import org.isoron.uhabits.models.Habit;
|
import org.isoron.uhabits.models.sqlite.HabitRecord;
|
||||||
|
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.LinkedList;
|
import java.util.LinkedList;
|
||||||
@@ -93,7 +93,7 @@ public class MainActivityActions
|
|||||||
onView(withId(R.id.buttonSave))
|
onView(withId(R.id.buttonSave))
|
||||||
.perform(click());
|
.perform(click());
|
||||||
|
|
||||||
onData(allOf(is(instanceOf(Habit.class)), withName(name)))
|
onData(allOf(is(instanceOf(HabitRecord.class)), withName(name)))
|
||||||
.onChildView(withId(R.id.label));
|
.onChildView(withId(R.id.label));
|
||||||
|
|
||||||
return name;
|
return name;
|
||||||
@@ -135,7 +135,7 @@ public class MainActivityActions
|
|||||||
boolean first = true;
|
boolean first = true;
|
||||||
for(String name : names)
|
for(String name : names)
|
||||||
{
|
{
|
||||||
onData(allOf(is(instanceOf(Habit.class)), withName(name)))
|
onData(allOf(is(instanceOf(HabitRecord.class)), withName(name)))
|
||||||
.onChildView(withId(R.id.label))
|
.onChildView(withId(R.id.label))
|
||||||
.perform(first ? longClick() : click());
|
.perform(first ? longClick() : click());
|
||||||
|
|
||||||
@@ -160,7 +160,7 @@ public class MainActivityActions
|
|||||||
public static void assertHabitsExist(List<String> names)
|
public static void assertHabitsExist(List<String> names)
|
||||||
{
|
{
|
||||||
for(String name : names)
|
for(String name : names)
|
||||||
onData(allOf(is(instanceOf(Habit.class)), withName(name)))
|
onData(allOf(is(instanceOf(HabitRecord.class)), withName(name)))
|
||||||
.check(matches(isDisplayed()));
|
.check(matches(isDisplayed()));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -30,8 +30,8 @@ import android.support.test.runner.AndroidJUnit4;
|
|||||||
import android.test.suitebuilder.annotation.LargeTest;
|
import android.test.suitebuilder.annotation.LargeTest;
|
||||||
|
|
||||||
import org.isoron.uhabits.R;
|
import org.isoron.uhabits.R;
|
||||||
|
import org.isoron.uhabits.models.sqlite.HabitRecord;
|
||||||
import org.isoron.uhabits.utils.DateUtils;
|
import org.isoron.uhabits.utils.DateUtils;
|
||||||
import org.isoron.uhabits.models.Habit;
|
|
||||||
import org.isoron.uhabits.MainActivity;
|
import org.isoron.uhabits.MainActivity;
|
||||||
import org.junit.After;
|
import org.junit.After;
|
||||||
import org.junit.Before;
|
import org.junit.Before;
|
||||||
@@ -190,13 +190,13 @@ public class MainTest
|
|||||||
{
|
{
|
||||||
String name = addHabit(true);
|
String name = addHabit(true);
|
||||||
|
|
||||||
onData(allOf(is(instanceOf(Habit.class)), withName(name)))
|
onData(allOf(is(instanceOf(HabitRecord.class)), withName(name)))
|
||||||
.onChildView(withId(R.id.checkmarkPanel))
|
.onChildView(withId(R.id.checkmarkPanel))
|
||||||
.perform(toggleAllCheckmarks());
|
.perform(toggleAllCheckmarks());
|
||||||
|
|
||||||
Thread.sleep(1200);
|
Thread.sleep(1200);
|
||||||
|
|
||||||
onData(allOf(is(instanceOf(Habit.class)), withName(name)))
|
onData(allOf(is(instanceOf(HabitRecord.class)), withName(name)))
|
||||||
.onChildView(withId(R.id.label))
|
.onChildView(withId(R.id.label))
|
||||||
.perform(click());
|
.perform(click());
|
||||||
|
|
||||||
@@ -217,7 +217,7 @@ public class MainTest
|
|||||||
{
|
{
|
||||||
String name = addHabit();
|
String name = addHabit();
|
||||||
|
|
||||||
onData(allOf(is(instanceOf(Habit.class)), withName(name)))
|
onData(allOf(is(instanceOf(HabitRecord.class)), withName(name)))
|
||||||
.onChildView(withId(R.id.label))
|
.onChildView(withId(R.id.label))
|
||||||
.perform(longClick());
|
.perform(longClick());
|
||||||
|
|
||||||
@@ -247,7 +247,7 @@ public class MainTest
|
|||||||
{
|
{
|
||||||
String name = addHabit();
|
String name = addHabit();
|
||||||
|
|
||||||
onData(allOf(is(instanceOf(Habit.class)), withName(name)))
|
onData(allOf(is(instanceOf(HabitRecord.class)), withName(name)))
|
||||||
.onChildView(withId(R.id.label))
|
.onChildView(withId(R.id.label))
|
||||||
.perform(click());
|
.perform(click());
|
||||||
|
|
||||||
|
|||||||
@@ -19,151 +19,76 @@
|
|||||||
|
|
||||||
package org.isoron.uhabits.unit;
|
package org.isoron.uhabits.unit;
|
||||||
|
|
||||||
import android.content.Context;
|
|
||||||
import android.support.annotation.Nullable;
|
|
||||||
import android.util.Log;
|
|
||||||
|
|
||||||
import org.isoron.uhabits.utils.DatabaseUtils;
|
|
||||||
import org.isoron.uhabits.utils.FileUtils;
|
|
||||||
import org.isoron.uhabits.utils.DateUtils;
|
|
||||||
import org.isoron.uhabits.models.Habit;
|
import org.isoron.uhabits.models.Habit;
|
||||||
import org.isoron.uhabits.tasks.BaseTask;
|
import org.isoron.uhabits.models.HabitList;
|
||||||
import org.isoron.uhabits.tasks.ExportDBTask;
|
import org.isoron.uhabits.utils.DateUtils;
|
||||||
import org.isoron.uhabits.tasks.ImportDataTask;
|
|
||||||
|
|
||||||
import java.io.File;
|
|
||||||
import java.io.InputStream;
|
|
||||||
import java.util.Random;
|
|
||||||
|
|
||||||
import static org.junit.Assert.fail;
|
|
||||||
|
|
||||||
public class HabitFixtures
|
public class HabitFixtures
|
||||||
{
|
{
|
||||||
public static boolean NON_DAILY_HABIT_CHECKS[] = { true, false, false, true, true, true, false,
|
public boolean NON_DAILY_HABIT_CHECKS[] = {
|
||||||
false, true, true };
|
true, false, false, true, true, true, false, false, true, true
|
||||||
|
};
|
||||||
|
|
||||||
public static Habit createShortHabit()
|
private final HabitList habitList;
|
||||||
|
|
||||||
|
public HabitFixtures(HabitList habitList)
|
||||||
|
{
|
||||||
|
this.habitList = habitList;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Habit createEmptyHabit()
|
||||||
{
|
{
|
||||||
Habit habit = new Habit();
|
Habit habit = new Habit();
|
||||||
habit.name = "Wake up early";
|
habit.setName("Meditate");
|
||||||
habit.description = "Did you wake up before 6am?";
|
habit.setDescription("Did you meditate this morning?");
|
||||||
habit.freqNum = 2;
|
habit.setColor(3);
|
||||||
habit.freqDen = 3;
|
habit.setFreqNum(1);
|
||||||
habit.save();
|
habit.setFreqDen(1);
|
||||||
|
habitList.add(habit);
|
||||||
|
return habit;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Habit createLongHabit()
|
||||||
|
{
|
||||||
|
Habit habit = createEmptyHabit();
|
||||||
|
habit.setFreqNum(3);
|
||||||
|
habit.setFreqDen(7);
|
||||||
|
habit.setColor(4);
|
||||||
|
|
||||||
|
long day = DateUtils.millisecondsInOneDay;
|
||||||
|
long today = DateUtils.getStartOfToday();
|
||||||
|
int marks[] = { 0, 1, 3, 5, 7, 8, 9, 10, 12, 14, 15, 17, 19, 20, 26, 27,
|
||||||
|
28, 50, 51, 52, 53, 54, 58, 60, 63, 65, 70, 71, 72, 73, 74, 75, 80,
|
||||||
|
81, 83, 89, 90, 91, 95, 102, 103, 108, 109, 120};
|
||||||
|
|
||||||
|
for (int mark : marks)
|
||||||
|
habit.getRepetitions().toggleTimestamp(today - mark * day);
|
||||||
|
|
||||||
|
return habit;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Habit createShortHabit()
|
||||||
|
{
|
||||||
|
Habit habit = new Habit();
|
||||||
|
habit.setName("Wake up early");
|
||||||
|
habit.setDescription("Did you wake up before 6am?");
|
||||||
|
habit.setFreqNum(2);
|
||||||
|
habit.setFreqDen(3);
|
||||||
|
habitList.add(habit);
|
||||||
|
|
||||||
long timestamp = DateUtils.getStartOfToday();
|
long timestamp = DateUtils.getStartOfToday();
|
||||||
for (boolean c : NON_DAILY_HABIT_CHECKS)
|
for (boolean c : NON_DAILY_HABIT_CHECKS)
|
||||||
{
|
{
|
||||||
if(c) habit.repetitions.toggle(timestamp);
|
if (c) habit.getRepetitions().toggleTimestamp(timestamp);
|
||||||
timestamp -= DateUtils.millisecondsInOneDay;
|
timestamp -= DateUtils.millisecondsInOneDay;
|
||||||
}
|
}
|
||||||
|
|
||||||
return habit;
|
return habit;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static Habit createEmptyHabit()
|
public void purgeHabits(HabitList habitList)
|
||||||
{
|
{
|
||||||
Habit habit = new Habit();
|
for (Habit h : habitList.getAll(true))
|
||||||
habit.name = "Meditate";
|
habitList.remove(h);
|
||||||
habit.description = "Did you meditate this morning?";
|
|
||||||
habit.color = 3;
|
|
||||||
habit.freqNum = 1;
|
|
||||||
habit.freqDen = 1;
|
|
||||||
habit.save();
|
|
||||||
return habit;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static Habit createLongHabit()
|
|
||||||
{
|
|
||||||
Habit habit = createEmptyHabit();
|
|
||||||
habit.freqNum = 3;
|
|
||||||
habit.freqDen = 7;
|
|
||||||
habit.color = 4;
|
|
||||||
habit.save();
|
|
||||||
|
|
||||||
long day = DateUtils.millisecondsInOneDay;
|
|
||||||
long today = DateUtils.getStartOfToday();
|
|
||||||
int marks[] = { 0, 1, 3, 5, 7, 8, 9, 10, 12, 14, 15, 17, 19, 20, 26, 27, 28, 50, 51, 52,
|
|
||||||
53, 54, 58, 60, 63, 65, 70, 71, 72, 73, 74, 75, 80, 81, 83, 89, 90, 91, 95,
|
|
||||||
102, 103, 108, 109, 120};
|
|
||||||
|
|
||||||
for(int mark : marks)
|
|
||||||
habit.repetitions.toggle(today - mark * day);
|
|
||||||
|
|
||||||
return habit;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void generateHugeDataSet() throws Throwable
|
|
||||||
{
|
|
||||||
final int nHabits = 30;
|
|
||||||
final int nYears = 5;
|
|
||||||
|
|
||||||
DatabaseUtils.executeAsTransaction(new DatabaseUtils.Command()
|
|
||||||
{
|
|
||||||
@Override
|
|
||||||
public void execute()
|
|
||||||
{
|
|
||||||
Random rand = new Random();
|
|
||||||
|
|
||||||
for(int i = 0; i < nHabits; i++)
|
|
||||||
{
|
|
||||||
Log.i("HabitFixture", String.format("Creating habit %d / %d", i, nHabits));
|
|
||||||
|
|
||||||
Habit habit = new Habit();
|
|
||||||
habit.name = String.format("Habit %d", i);
|
|
||||||
habit.save();
|
|
||||||
|
|
||||||
long today = DateUtils.getStartOfToday();
|
|
||||||
long day = DateUtils.millisecondsInOneDay;
|
|
||||||
|
|
||||||
|
|
||||||
for(int j = 0; j < 365 * nYears; j++)
|
|
||||||
{
|
|
||||||
if(rand.nextBoolean())
|
|
||||||
habit.repetitions.toggle(today - j * day);
|
|
||||||
}
|
|
||||||
|
|
||||||
habit.scores.getTodayValue();
|
|
||||||
habit.streaks.getAll(1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
ExportDBTask task = new ExportDBTask(null);
|
|
||||||
task.setListener(new ExportDBTask.Listener()
|
|
||||||
{
|
|
||||||
@Override
|
|
||||||
public void onExportDBFinished(@Nullable String filename)
|
|
||||||
{
|
|
||||||
if(filename != null)
|
|
||||||
Log.i("HabitFixture", String.format("Huge data set exported to %s", filename));
|
|
||||||
else
|
|
||||||
Log.i("HabitFixture", "Failed to save database");
|
|
||||||
}
|
|
||||||
});
|
|
||||||
task.execute();
|
|
||||||
|
|
||||||
BaseTask.waitForTasks(30000);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void loadHugeDataSet(Context testContext) throws Throwable
|
|
||||||
{
|
|
||||||
File baseDir = FileUtils.getFilesDir("Backups");
|
|
||||||
if(baseDir == null) fail("baseDir should not be null");
|
|
||||||
|
|
||||||
File dst = new File(String.format("%s/%s", baseDir.getPath(), "loopHuge.db"));
|
|
||||||
InputStream in = testContext.getAssets().open("fixtures/loopHuge.db");
|
|
||||||
FileUtils.copy(in, dst);
|
|
||||||
|
|
||||||
ImportDataTask task = new ImportDataTask(dst, null);
|
|
||||||
task.execute();
|
|
||||||
|
|
||||||
BaseTask.waitForTasks(30000);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void purgeHabits()
|
|
||||||
{
|
|
||||||
for(Habit h : Habit.getAll(true))
|
|
||||||
h.cascadeDelete();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -48,7 +48,7 @@ public class ArchiveHabitsCommandTest extends BaseAndroidTest
|
|||||||
{
|
{
|
||||||
super.setUp();
|
super.setUp();
|
||||||
|
|
||||||
habit = HabitFixtures.createShortHabit();
|
habit = habitFixtures.createShortHabit();
|
||||||
command = new ArchiveHabitsCommand(Collections.singletonList(habit));
|
command = new ArchiveHabitsCommand(Collections.singletonList(habit));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -51,9 +51,8 @@ public class ChangeHabitColorCommandTest extends BaseAndroidTest
|
|||||||
|
|
||||||
for(int i = 0; i < 3; i ++)
|
for(int i = 0; i < 3; i ++)
|
||||||
{
|
{
|
||||||
Habit habit = HabitFixtures.createShortHabit();
|
Habit habit = habitFixtures.createShortHabit();
|
||||||
habit.color = i+1;
|
habit.setColor(i + 1);
|
||||||
habit.save();
|
|
||||||
habits.add(habit);
|
habits.add(habit);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -79,12 +78,12 @@ public class ChangeHabitColorCommandTest extends BaseAndroidTest
|
|||||||
{
|
{
|
||||||
int k = 0;
|
int k = 0;
|
||||||
for(Habit h : habits)
|
for(Habit h : habits)
|
||||||
assertThat(h.color, equalTo(++k));
|
assertThat(h.getColor(), equalTo(++k));
|
||||||
}
|
}
|
||||||
|
|
||||||
private void checkNewColors()
|
private void checkNewColors()
|
||||||
{
|
{
|
||||||
for(Habit h : habits)
|
for(Habit h : habits)
|
||||||
assertThat(h.color, equalTo(0));
|
assertThat(h.getColor(), equalTo(0));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -23,9 +23,9 @@ import android.support.test.runner.AndroidJUnit4;
|
|||||||
import android.test.suitebuilder.annotation.SmallTest;
|
import android.test.suitebuilder.annotation.SmallTest;
|
||||||
|
|
||||||
import org.isoron.uhabits.BaseAndroidTest;
|
import org.isoron.uhabits.BaseAndroidTest;
|
||||||
|
import org.isoron.uhabits.HabitsApplication;
|
||||||
import org.isoron.uhabits.commands.CreateHabitCommand;
|
import org.isoron.uhabits.commands.CreateHabitCommand;
|
||||||
import org.isoron.uhabits.models.Habit;
|
import org.isoron.uhabits.models.Habit;
|
||||||
import org.isoron.uhabits.unit.HabitFixtures;
|
|
||||||
import org.junit.Before;
|
import org.junit.Before;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
import org.junit.runner.RunWith;
|
import org.junit.runner.RunWith;
|
||||||
@@ -42,6 +42,7 @@ public class CreateHabitCommandTest extends BaseAndroidTest
|
|||||||
{
|
{
|
||||||
|
|
||||||
private CreateHabitCommand command;
|
private CreateHabitCommand command;
|
||||||
|
|
||||||
private Habit model;
|
private Habit model;
|
||||||
|
|
||||||
@Before
|
@Before
|
||||||
@@ -50,36 +51,36 @@ public class CreateHabitCommandTest extends BaseAndroidTest
|
|||||||
super.setUp();
|
super.setUp();
|
||||||
|
|
||||||
model = new Habit();
|
model = new Habit();
|
||||||
model.name = "New habit";
|
model.setName("New habit");
|
||||||
command = new CreateHabitCommand(model);
|
command = new CreateHabitCommand(model);
|
||||||
|
|
||||||
HabitFixtures.purgeHabits();
|
habitFixtures.purgeHabits(habitList);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testExecuteUndoRedo()
|
public void testExecuteUndoRedo()
|
||||||
{
|
{
|
||||||
assertTrue(Habit.getAll(true).isEmpty());
|
assertTrue(habitList.getAll(true).isEmpty());
|
||||||
|
|
||||||
command.execute();
|
command.execute();
|
||||||
|
|
||||||
List<Habit> allHabits = Habit.getAll(true);
|
List<Habit> allHabits = habitList.getAll(true);
|
||||||
assertThat(allHabits.size(), equalTo(1));
|
assertThat(allHabits.size(), equalTo(1));
|
||||||
|
|
||||||
Habit habit = allHabits.get(0);
|
Habit habit = allHabits.get(0);
|
||||||
Long id = habit.getId();
|
Long id = habit.getId();
|
||||||
assertThat(habit.name, equalTo(model.name));
|
assertThat(habit.getName(), equalTo(model.getName()));
|
||||||
|
|
||||||
command.undo();
|
command.undo();
|
||||||
assertTrue(Habit.getAll(true).isEmpty());
|
assertTrue(habitList.getAll(true).isEmpty());
|
||||||
|
|
||||||
command.execute();
|
command.execute();
|
||||||
allHabits = Habit.getAll(true);
|
allHabits = habitList.getAll(true);
|
||||||
assertThat(allHabits.size(), equalTo(1));
|
assertThat(allHabits.size(), equalTo(1));
|
||||||
|
|
||||||
habit = allHabits.get(0);
|
habit = allHabits.get(0);
|
||||||
Long newId = habit.getId();
|
Long newId = habit.getId();
|
||||||
assertThat(id, equalTo(newId));
|
assertThat(id, equalTo(newId));
|
||||||
assertThat(habit.name, equalTo(model.name));
|
assertThat(habit.getName(), equalTo(model.getName()));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -25,7 +25,6 @@ import android.test.suitebuilder.annotation.SmallTest;
|
|||||||
import org.isoron.uhabits.BaseAndroidTest;
|
import org.isoron.uhabits.BaseAndroidTest;
|
||||||
import org.isoron.uhabits.commands.DeleteHabitsCommand;
|
import org.isoron.uhabits.commands.DeleteHabitsCommand;
|
||||||
import org.isoron.uhabits.models.Habit;
|
import org.isoron.uhabits.models.Habit;
|
||||||
import org.isoron.uhabits.unit.HabitFixtures;
|
|
||||||
import org.junit.Before;
|
import org.junit.Before;
|
||||||
import org.junit.Rule;
|
import org.junit.Rule;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
@@ -42,30 +41,31 @@ import static org.hamcrest.Matchers.equalTo;
|
|||||||
public class DeleteHabitsCommandTest extends BaseAndroidTest
|
public class DeleteHabitsCommandTest extends BaseAndroidTest
|
||||||
{
|
{
|
||||||
private DeleteHabitsCommand command;
|
private DeleteHabitsCommand command;
|
||||||
|
|
||||||
private LinkedList<Habit> habits;
|
private LinkedList<Habit> habits;
|
||||||
|
|
||||||
@Rule
|
@Rule
|
||||||
public ExpectedException thrown = ExpectedException.none();
|
public ExpectedException thrown = ExpectedException.none();
|
||||||
|
|
||||||
|
@Override
|
||||||
@Before
|
@Before
|
||||||
public void setUp()
|
public void setUp()
|
||||||
{
|
{
|
||||||
super.setUp();
|
super.setUp();
|
||||||
|
|
||||||
HabitFixtures.purgeHabits();
|
habitFixtures.purgeHabits(habitList);
|
||||||
habits = new LinkedList<>();
|
habits = new LinkedList<>();
|
||||||
|
|
||||||
// Habits that shuold be deleted
|
// Habits that should be deleted
|
||||||
for (int i = 0; i < 3; i++)
|
for (int i = 0; i < 3; i++)
|
||||||
{
|
{
|
||||||
Habit habit = HabitFixtures.createShortHabit();
|
Habit habit = habitFixtures.createShortHabit();
|
||||||
habits.add(habit);
|
habits.add(habit);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Extra habit that should not be deleted
|
// Extra habit that should not be deleted
|
||||||
Habit extraHabit = HabitFixtures.createShortHabit();
|
Habit extraHabit = habitFixtures.createShortHabit();
|
||||||
extraHabit.name = "extra";
|
extraHabit.setName("extra");
|
||||||
extraHabit.save();
|
|
||||||
|
|
||||||
command = new DeleteHabitsCommand(habits);
|
command = new DeleteHabitsCommand(habits);
|
||||||
}
|
}
|
||||||
@@ -73,11 +73,11 @@ public class DeleteHabitsCommandTest extends BaseAndroidTest
|
|||||||
@Test
|
@Test
|
||||||
public void testExecuteUndoRedo()
|
public void testExecuteUndoRedo()
|
||||||
{
|
{
|
||||||
assertThat(Habit.getAll(true).size(), equalTo(4));
|
assertThat(habitList.getAll(true).size(), equalTo(4));
|
||||||
|
|
||||||
command.execute();
|
command.execute();
|
||||||
assertThat(Habit.getAll(true).size(), equalTo(1));
|
assertThat(habitList.getAll(true).size(), equalTo(1));
|
||||||
assertThat(Habit.getAll(true).get(0).name, equalTo("extra"));
|
assertThat(habitList.getAll(true).get(0).getName(), equalTo("extra"));
|
||||||
|
|
||||||
thrown.expect(UnsupportedOperationException.class);
|
thrown.expect(UnsupportedOperationException.class);
|
||||||
command.undo();
|
command.undo();
|
||||||
|
|||||||
@@ -25,12 +25,10 @@ import android.test.suitebuilder.annotation.SmallTest;
|
|||||||
import org.isoron.uhabits.BaseAndroidTest;
|
import org.isoron.uhabits.BaseAndroidTest;
|
||||||
import org.isoron.uhabits.commands.EditHabitCommand;
|
import org.isoron.uhabits.commands.EditHabitCommand;
|
||||||
import org.isoron.uhabits.models.Habit;
|
import org.isoron.uhabits.models.Habit;
|
||||||
import org.isoron.uhabits.unit.HabitFixtures;
|
|
||||||
import org.junit.Before;
|
import org.junit.Before;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
import org.junit.runner.RunWith;
|
import org.junit.runner.RunWith;
|
||||||
|
|
||||||
import static junit.framework.Assert.assertTrue;
|
|
||||||
import static org.hamcrest.MatcherAssert.assertThat;
|
import static org.hamcrest.MatcherAssert.assertThat;
|
||||||
import static org.hamcrest.Matchers.equalTo;
|
import static org.hamcrest.Matchers.equalTo;
|
||||||
import static org.hamcrest.Matchers.greaterThan;
|
import static org.hamcrest.Matchers.greaterThan;
|
||||||
@@ -41,25 +39,25 @@ public class EditHabitCommandTest extends BaseAndroidTest
|
|||||||
{
|
{
|
||||||
|
|
||||||
private EditHabitCommand command;
|
private EditHabitCommand command;
|
||||||
private Habit habit;
|
|
||||||
private Habit modified;
|
|
||||||
private Long id;
|
|
||||||
|
|
||||||
|
private Habit habit;
|
||||||
|
|
||||||
|
private Habit modified;
|
||||||
|
|
||||||
|
@Override
|
||||||
@Before
|
@Before
|
||||||
public void setUp()
|
public void setUp()
|
||||||
{
|
{
|
||||||
super.setUp();
|
super.setUp();
|
||||||
|
|
||||||
habit = HabitFixtures.createShortHabit();
|
habit = habitFixtures.createShortHabit();
|
||||||
habit.name = "original";
|
habit.setName("original");
|
||||||
habit.freqDen = 1;
|
habit.setFreqDen(1);
|
||||||
habit.freqNum = 1;
|
habit.setFreqNum(1);
|
||||||
habit.save();
|
|
||||||
|
|
||||||
id = habit.getId();
|
modified = new Habit();
|
||||||
|
modified.copyFrom(habit);
|
||||||
modified = new Habit(habit);
|
modified.setName("modified");
|
||||||
modified.name = "modified";
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@@ -67,54 +65,44 @@ public class EditHabitCommandTest extends BaseAndroidTest
|
|||||||
{
|
{
|
||||||
command = new EditHabitCommand(habit, modified);
|
command = new EditHabitCommand(habit, modified);
|
||||||
|
|
||||||
int originalScore = habit.scores.getTodayValue();
|
int originalScore = habit.getScores().getTodayValue();
|
||||||
assertThat(habit.name, equalTo("original"));
|
assertThat(habit.getName(), equalTo("original"));
|
||||||
|
|
||||||
command.execute();
|
command.execute();
|
||||||
refreshHabit();
|
assertThat(habit.getName(), equalTo("modified"));
|
||||||
assertThat(habit.name, equalTo("modified"));
|
assertThat(habit.getScores().getTodayValue(), equalTo(originalScore));
|
||||||
assertThat(habit.scores.getTodayValue(), equalTo(originalScore));
|
|
||||||
|
|
||||||
command.undo();
|
command.undo();
|
||||||
refreshHabit();
|
assertThat(habit.getName(), equalTo("original"));
|
||||||
assertThat(habit.name, equalTo("original"));
|
assertThat(habit.getScores().getTodayValue(), equalTo(originalScore));
|
||||||
assertThat(habit.scores.getTodayValue(), equalTo(originalScore));
|
|
||||||
|
|
||||||
command.execute();
|
command.execute();
|
||||||
refreshHabit();
|
assertThat(habit.getName(), equalTo("modified"));
|
||||||
assertThat(habit.name, equalTo("modified"));
|
assertThat(habit.getScores().getTodayValue(), equalTo(originalScore));
|
||||||
assertThat(habit.scores.getTodayValue(), equalTo(originalScore));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testExecuteUndoRedo_withModifiedInterval()
|
public void testExecuteUndoRedo_withModifiedInterval()
|
||||||
{
|
{
|
||||||
modified.freqNum = 1;
|
modified.setFreqNum(1);
|
||||||
modified.freqDen = 7;
|
modified.setFreqDen(7);
|
||||||
command = new EditHabitCommand(habit, modified);
|
command = new EditHabitCommand(habit, modified);
|
||||||
|
|
||||||
int originalScore = habit.scores.getTodayValue();
|
int originalScore = habit.getScores().getTodayValue();
|
||||||
assertThat(habit.name, equalTo("original"));
|
assertThat(habit.getName(), equalTo("original"));
|
||||||
|
|
||||||
command.execute();
|
command.execute();
|
||||||
refreshHabit();
|
assertThat(habit.getName(), equalTo("modified"));
|
||||||
assertThat(habit.name, equalTo("modified"));
|
assertThat(habit.getScores().getTodayValue(),
|
||||||
assertThat(habit.scores.getTodayValue(), greaterThan(originalScore));
|
greaterThan(originalScore));
|
||||||
|
|
||||||
command.undo();
|
command.undo();
|
||||||
refreshHabit();
|
assertThat(habit.getName(), equalTo("original"));
|
||||||
assertThat(habit.name, equalTo("original"));
|
assertThat(habit.getScores().getTodayValue(), equalTo(originalScore));
|
||||||
assertThat(habit.scores.getTodayValue(), equalTo(originalScore));
|
|
||||||
|
|
||||||
command.execute();
|
command.execute();
|
||||||
refreshHabit();
|
assertThat(habit.getName(), equalTo("modified"));
|
||||||
assertThat(habit.name, equalTo("modified"));
|
assertThat(habit.getScores().getTodayValue(),
|
||||||
assertThat(habit.scores.getTodayValue(), greaterThan(originalScore));
|
greaterThan(originalScore));
|
||||||
}
|
|
||||||
|
|
||||||
private void refreshHabit()
|
|
||||||
{
|
|
||||||
habit = Habit.get(id);
|
|
||||||
assertTrue(habit != null);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -24,9 +24,8 @@ import android.test.suitebuilder.annotation.SmallTest;
|
|||||||
|
|
||||||
import org.isoron.uhabits.BaseAndroidTest;
|
import org.isoron.uhabits.BaseAndroidTest;
|
||||||
import org.isoron.uhabits.commands.ToggleRepetitionCommand;
|
import org.isoron.uhabits.commands.ToggleRepetitionCommand;
|
||||||
import org.isoron.uhabits.utils.DateUtils;
|
|
||||||
import org.isoron.uhabits.models.Habit;
|
import org.isoron.uhabits.models.Habit;
|
||||||
import org.isoron.uhabits.unit.HabitFixtures;
|
import org.isoron.uhabits.utils.DateUtils;
|
||||||
import org.junit.Before;
|
import org.junit.Before;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
import org.junit.runner.RunWith;
|
import org.junit.runner.RunWith;
|
||||||
@@ -48,7 +47,7 @@ public class ToggleRepetitionCommandTest extends BaseAndroidTest
|
|||||||
{
|
{
|
||||||
super.setUp();
|
super.setUp();
|
||||||
|
|
||||||
habit = HabitFixtures.createShortHabit();
|
habit = habitFixtures.createShortHabit();
|
||||||
|
|
||||||
today = DateUtils.getStartOfToday();
|
today = DateUtils.getStartOfToday();
|
||||||
command = new ToggleRepetitionCommand(habit, today);
|
command = new ToggleRepetitionCommand(habit, today);
|
||||||
@@ -57,15 +56,15 @@ public class ToggleRepetitionCommandTest extends BaseAndroidTest
|
|||||||
@Test
|
@Test
|
||||||
public void testExecuteUndoRedo()
|
public void testExecuteUndoRedo()
|
||||||
{
|
{
|
||||||
assertTrue(habit.repetitions.contains(today));
|
assertTrue(habit.getRepetitions().containsTimestamp(today));
|
||||||
|
|
||||||
command.execute();
|
command.execute();
|
||||||
assertFalse(habit.repetitions.contains(today));
|
assertFalse(habit.getRepetitions().containsTimestamp(today));
|
||||||
|
|
||||||
command.undo();
|
command.undo();
|
||||||
assertTrue(habit.repetitions.contains(today));
|
assertTrue(habit.getRepetitions().containsTimestamp(today));
|
||||||
|
|
||||||
command.execute();
|
command.execute();
|
||||||
assertFalse(habit.repetitions.contains(today));
|
assertFalse(habit.getRepetitions().containsTimestamp(today));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -25,7 +25,6 @@ import android.test.suitebuilder.annotation.SmallTest;
|
|||||||
import org.isoron.uhabits.BaseAndroidTest;
|
import org.isoron.uhabits.BaseAndroidTest;
|
||||||
import org.isoron.uhabits.commands.UnarchiveHabitsCommand;
|
import org.isoron.uhabits.commands.UnarchiveHabitsCommand;
|
||||||
import org.isoron.uhabits.models.Habit;
|
import org.isoron.uhabits.models.Habit;
|
||||||
import org.isoron.uhabits.unit.HabitFixtures;
|
|
||||||
import org.junit.Before;
|
import org.junit.Before;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
import org.junit.runner.RunWith;
|
import org.junit.runner.RunWith;
|
||||||
@@ -39,17 +38,18 @@ import static junit.framework.Assert.assertTrue;
|
|||||||
@SmallTest
|
@SmallTest
|
||||||
public class UnarchiveHabitsCommandTest extends BaseAndroidTest
|
public class UnarchiveHabitsCommandTest extends BaseAndroidTest
|
||||||
{
|
{
|
||||||
|
|
||||||
private UnarchiveHabitsCommand command;
|
private UnarchiveHabitsCommand command;
|
||||||
private Habit habit;
|
private Habit habit;
|
||||||
|
|
||||||
|
@Override
|
||||||
@Before
|
@Before
|
||||||
public void setUp()
|
public void setUp()
|
||||||
{
|
{
|
||||||
super.setUp();
|
super.setUp();
|
||||||
|
|
||||||
habit = HabitFixtures.createShortHabit();
|
habit = habitFixtures.createShortHabit();
|
||||||
Habit.archive(Collections.singletonList(habit));
|
habit.setArchived(1);
|
||||||
|
habitList.update(habit);
|
||||||
|
|
||||||
command = new UnarchiveHabitsCommand(Collections.singletonList(habit));
|
command = new UnarchiveHabitsCommand(Collections.singletonList(habit));
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -25,10 +25,9 @@ import android.support.test.runner.AndroidJUnit4;
|
|||||||
import android.test.suitebuilder.annotation.SmallTest;
|
import android.test.suitebuilder.annotation.SmallTest;
|
||||||
|
|
||||||
import org.isoron.uhabits.BaseAndroidTest;
|
import org.isoron.uhabits.BaseAndroidTest;
|
||||||
import org.isoron.uhabits.utils.FileUtils;
|
|
||||||
import org.isoron.uhabits.io.HabitsCSVExporter;
|
import org.isoron.uhabits.io.HabitsCSVExporter;
|
||||||
import org.isoron.uhabits.models.Habit;
|
import org.isoron.uhabits.models.Habit;
|
||||||
import org.isoron.uhabits.unit.HabitFixtures;
|
import org.isoron.uhabits.utils.FileUtils;
|
||||||
import org.junit.Before;
|
import org.junit.Before;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
import org.junit.runner.RunWith;
|
import org.junit.runner.RunWith;
|
||||||
@@ -54,41 +53,18 @@ public class HabitsCSVExporterTest extends BaseAndroidTest
|
|||||||
{
|
{
|
||||||
super.setUp();
|
super.setUp();
|
||||||
|
|
||||||
HabitFixtures.purgeHabits();
|
habitFixtures.purgeHabits(habitList);
|
||||||
HabitFixtures.createShortHabit();
|
habitFixtures.createShortHabit();
|
||||||
HabitFixtures.createEmptyHabit();
|
habitFixtures.createEmptyHabit();
|
||||||
|
|
||||||
Context targetContext = InstrumentationRegistry.getTargetContext();
|
Context targetContext = InstrumentationRegistry.getTargetContext();
|
||||||
baseDir = targetContext.getCacheDir();
|
baseDir = targetContext.getCacheDir();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void unzip(File file) throws IOException
|
|
||||||
{
|
|
||||||
ZipFile zip = new ZipFile(file);
|
|
||||||
Enumeration<? extends ZipEntry> e = zip.entries();
|
|
||||||
|
|
||||||
while(e.hasMoreElements())
|
|
||||||
{
|
|
||||||
ZipEntry entry = e.nextElement();
|
|
||||||
InputStream stream = zip.getInputStream(entry);
|
|
||||||
|
|
||||||
String outputFilename = String.format("%s/%s", baseDir.getAbsolutePath(),
|
|
||||||
entry.getName());
|
|
||||||
File outputFile = new File(outputFilename);
|
|
||||||
|
|
||||||
File parent = outputFile.getParentFile();
|
|
||||||
if(parent != null) parent.mkdirs();
|
|
||||||
|
|
||||||
FileUtils.copy(stream, outputFile);
|
|
||||||
}
|
|
||||||
|
|
||||||
zip.close();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testExportCSV() throws IOException
|
public void testExportCSV() throws IOException
|
||||||
{
|
{
|
||||||
List<Habit> habits = Habit.getAll(true);
|
List<Habit> habits = habitList.getAll(true);
|
||||||
|
|
||||||
HabitsCSVExporter exporter = new HabitsCSVExporter(habits, baseDir);
|
HabitsCSVExporter exporter = new HabitsCSVExporter(habits, baseDir);
|
||||||
String filename = exporter.writeArchive();
|
String filename = exporter.writeArchive();
|
||||||
@@ -105,14 +81,41 @@ public class HabitsCSVExporterTest extends BaseAndroidTest
|
|||||||
assertPathExists("002 Meditate/Scores.csv");
|
assertPathExists("002 Meditate/Scores.csv");
|
||||||
}
|
}
|
||||||
|
|
||||||
private void assertPathExists(String s)
|
|
||||||
{
|
|
||||||
assertAbsolutePathExists(String.format("%s/%s", baseDir.getAbsolutePath(), s));
|
|
||||||
}
|
|
||||||
|
|
||||||
private void assertAbsolutePathExists(String s)
|
private void assertAbsolutePathExists(String s)
|
||||||
{
|
{
|
||||||
File file = new File(s);
|
File file = new File(s);
|
||||||
assertTrue(String.format("File %s should exist", file.getAbsolutePath()), file.exists());
|
assertTrue(
|
||||||
|
String.format("File %s should exist", file.getAbsolutePath()),
|
||||||
|
file.exists());
|
||||||
|
}
|
||||||
|
|
||||||
|
private void assertPathExists(String s)
|
||||||
|
{
|
||||||
|
assertAbsolutePathExists(
|
||||||
|
String.format("%s/%s", baseDir.getAbsolutePath(), s));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void unzip(File file) throws IOException
|
||||||
|
{
|
||||||
|
ZipFile zip = new ZipFile(file);
|
||||||
|
Enumeration<? extends ZipEntry> e = zip.entries();
|
||||||
|
|
||||||
|
while (e.hasMoreElements())
|
||||||
|
{
|
||||||
|
ZipEntry entry = e.nextElement();
|
||||||
|
InputStream stream = zip.getInputStream(entry);
|
||||||
|
|
||||||
|
String outputFilename =
|
||||||
|
String.format("%s/%s", baseDir.getAbsolutePath(),
|
||||||
|
entry.getName());
|
||||||
|
File outputFile = new File(outputFilename);
|
||||||
|
|
||||||
|
File parent = outputFile.getParentFile();
|
||||||
|
if (parent != null) parent.mkdirs();
|
||||||
|
|
||||||
|
FileUtils.copy(stream, outputFile);
|
||||||
|
}
|
||||||
|
|
||||||
|
zip.close();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -25,11 +25,10 @@ import android.support.test.runner.AndroidJUnit4;
|
|||||||
import android.test.suitebuilder.annotation.SmallTest;
|
import android.test.suitebuilder.annotation.SmallTest;
|
||||||
|
|
||||||
import org.isoron.uhabits.BaseAndroidTest;
|
import org.isoron.uhabits.BaseAndroidTest;
|
||||||
|
import org.isoron.uhabits.models.Habit;
|
||||||
import org.isoron.uhabits.utils.FileUtils;
|
import org.isoron.uhabits.utils.FileUtils;
|
||||||
import org.isoron.uhabits.utils.DateUtils;
|
import org.isoron.uhabits.utils.DateUtils;
|
||||||
import org.isoron.uhabits.io.GenericImporter;
|
import org.isoron.uhabits.io.GenericImporter;
|
||||||
import org.isoron.uhabits.models.Habit;
|
|
||||||
import org.isoron.uhabits.unit.HabitFixtures;
|
|
||||||
import org.junit.Before;
|
import org.junit.Before;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
import org.junit.runner.RunWith;
|
import org.junit.runner.RunWith;
|
||||||
@@ -60,7 +59,7 @@ public class ImportTest extends BaseAndroidTest
|
|||||||
super.setUp();
|
super.setUp();
|
||||||
DateUtils.setFixedLocalTime(null);
|
DateUtils.setFixedLocalTime(null);
|
||||||
|
|
||||||
HabitFixtures.purgeHabits();
|
habitFixtures.purgeHabits(habitList);
|
||||||
context = InstrumentationRegistry.getInstrumentation().getContext();
|
context = InstrumentationRegistry.getInstrumentation().getContext();
|
||||||
baseDir = FileUtils.getFilesDir("Backups");
|
baseDir = FileUtils.getFilesDir("Backups");
|
||||||
if(baseDir == null) fail("baseDir should not be null");
|
if(baseDir == null) fail("baseDir should not be null");
|
||||||
@@ -89,7 +88,7 @@ public class ImportTest extends BaseAndroidTest
|
|||||||
{
|
{
|
||||||
GregorianCalendar date = DateUtils.getStartOfTodayCalendar();
|
GregorianCalendar date = DateUtils.getStartOfTodayCalendar();
|
||||||
date.set(year, month - 1, day);
|
date.set(year, month - 1, day);
|
||||||
return h.repetitions.contains(date.getTimeInMillis());
|
return h.getRepetitions().containsTimestamp(date.getTimeInMillis());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@@ -97,11 +96,11 @@ public class ImportTest extends BaseAndroidTest
|
|||||||
{
|
{
|
||||||
importFromFile("tickmate.db");
|
importFromFile("tickmate.db");
|
||||||
|
|
||||||
List<Habit> habits = Habit.getAll(true);
|
List<Habit> habits = habitList.getAll(true);
|
||||||
assertThat(habits.size(), equalTo(3));
|
assertThat(habits.size(), equalTo(3));
|
||||||
|
|
||||||
Habit h = habits.get(0);
|
Habit h = habits.get(0);
|
||||||
assertThat(h.name, equalTo("Vegan"));
|
assertThat(h.getName(), equalTo("Vegan"));
|
||||||
assertTrue(containsRepetition(h, 2016, 1, 24));
|
assertTrue(containsRepetition(h, 2016, 1, 24));
|
||||||
assertTrue(containsRepetition(h, 2016, 2, 5));
|
assertTrue(containsRepetition(h, 2016, 2, 5));
|
||||||
assertTrue(containsRepetition(h, 2016, 3, 18));
|
assertTrue(containsRepetition(h, 2016, 3, 18));
|
||||||
@@ -113,13 +112,13 @@ public class ImportTest extends BaseAndroidTest
|
|||||||
{
|
{
|
||||||
importFromFile("rewire.db");
|
importFromFile("rewire.db");
|
||||||
|
|
||||||
List<Habit> habits = Habit.getAll(true);
|
List<Habit> habits = habitList.getAll(true);
|
||||||
assertThat(habits.size(), equalTo(3));
|
assertThat(habits.size(), equalTo(3));
|
||||||
|
|
||||||
Habit habit = habits.get(0);
|
Habit habit = habits.get(0);
|
||||||
assertThat(habit.name, equalTo("Wake up early"));
|
assertThat(habit.getName(), equalTo("Wake up early"));
|
||||||
assertThat(habit.freqNum, equalTo(3));
|
assertThat(habit.getFreqNum(), equalTo(3));
|
||||||
assertThat(habit.freqDen, equalTo(7));
|
assertThat(habit.getFreqDen(), equalTo(7));
|
||||||
assertFalse(habit.hasReminder());
|
assertFalse(habit.hasReminder());
|
||||||
assertFalse(containsRepetition(habit, 2015, 12, 31));
|
assertFalse(containsRepetition(habit, 2015, 12, 31));
|
||||||
assertTrue(containsRepetition(habit, 2016, 1, 18));
|
assertTrue(containsRepetition(habit, 2016, 1, 18));
|
||||||
@@ -127,13 +126,13 @@ public class ImportTest extends BaseAndroidTest
|
|||||||
assertFalse(containsRepetition(habit, 2016, 3, 10));
|
assertFalse(containsRepetition(habit, 2016, 3, 10));
|
||||||
|
|
||||||
habit = habits.get(1);
|
habit = habits.get(1);
|
||||||
assertThat(habit.name, equalTo("brush teeth"));
|
assertThat(habit.getName(), equalTo("brush teeth"));
|
||||||
assertThat(habit.freqNum, equalTo(3));
|
assertThat(habit.getFreqNum(), equalTo(3));
|
||||||
assertThat(habit.freqDen, equalTo(7));
|
assertThat(habit.getFreqDen(), equalTo(7));
|
||||||
assertThat(habit.reminderHour, equalTo(8));
|
assertThat(habit.getReminderHour(), equalTo(8));
|
||||||
assertThat(habit.reminderMin, equalTo(0));
|
assertThat(habit.getReminderMin(), equalTo(0));
|
||||||
boolean[] reminderDays = {false, true, true, true, true, true, false};
|
boolean[] reminderDays = {false, true, true, true, true, true, false};
|
||||||
assertThat(habit.reminderDays, equalTo(DateUtils.packWeekdayList(reminderDays)));
|
assertThat(habit.getReminderDays(), equalTo(DateUtils.packWeekdayList(reminderDays)));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@@ -141,14 +140,14 @@ public class ImportTest extends BaseAndroidTest
|
|||||||
{
|
{
|
||||||
importFromFile("habitbull.csv");
|
importFromFile("habitbull.csv");
|
||||||
|
|
||||||
List<Habit> habits = Habit.getAll(true);
|
List<Habit> habits = habitList.getAll(true);
|
||||||
assertThat(habits.size(), equalTo(4));
|
assertThat(habits.size(), equalTo(4));
|
||||||
|
|
||||||
Habit habit = habits.get(0);
|
Habit habit = habits.get(0);
|
||||||
assertThat(habit.name, equalTo("Breed dragons"));
|
assertThat(habit.getName(), equalTo("Breed dragons"));
|
||||||
assertThat(habit.description, equalTo("with love and fire"));
|
assertThat(habit.getDescription(), equalTo("with love and fire"));
|
||||||
assertThat(habit.freqNum, equalTo(1));
|
assertThat(habit.getFreqNum(), equalTo(1));
|
||||||
assertThat(habit.freqDen, equalTo(1));
|
assertThat(habit.getFreqDen(), equalTo(1));
|
||||||
assertTrue(containsRepetition(habit, 2016, 3, 18));
|
assertTrue(containsRepetition(habit, 2016, 3, 18));
|
||||||
assertTrue(containsRepetition(habit, 2016, 3, 19));
|
assertTrue(containsRepetition(habit, 2016, 3, 19));
|
||||||
assertFalse(containsRepetition(habit, 2016, 3, 20));
|
assertFalse(containsRepetition(habit, 2016, 3, 20));
|
||||||
@@ -159,13 +158,13 @@ public class ImportTest extends BaseAndroidTest
|
|||||||
{
|
{
|
||||||
importFromFile("loop.db");
|
importFromFile("loop.db");
|
||||||
|
|
||||||
List<Habit> habits = Habit.getAll(true);
|
List<Habit> habits = habitList.getAll(true);
|
||||||
assertThat(habits.size(), equalTo(9));
|
assertThat(habits.size(), equalTo(9));
|
||||||
|
|
||||||
Habit habit = habits.get(0);
|
Habit habit = habits.get(0);
|
||||||
assertThat(habit.name, equalTo("Wake up early"));
|
assertThat(habit.getName(), equalTo("Wake up early"));
|
||||||
assertThat(habit.freqNum, equalTo(3));
|
assertThat(habit.getFreqNum(), equalTo(3));
|
||||||
assertThat(habit.freqDen, equalTo(7));
|
assertThat(habit.getFreqDen(), equalTo(7));
|
||||||
assertTrue(containsRepetition(habit, 2016, 3, 14));
|
assertTrue(containsRepetition(habit, 2016, 3, 14));
|
||||||
assertTrue(containsRepetition(habit, 2016, 3, 16));
|
assertTrue(containsRepetition(habit, 2016, 3, 16));
|
||||||
assertFalse(containsRepetition(habit, 2016, 3, 17));
|
assertFalse(containsRepetition(habit, 2016, 3, 17));
|
||||||
|
|||||||
@@ -1,175 +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.unit.models;
|
|
||||||
|
|
||||||
import android.support.test.runner.AndroidJUnit4;
|
|
||||||
import android.test.suitebuilder.annotation.SmallTest;
|
|
||||||
|
|
||||||
import org.isoron.uhabits.BaseAndroidTest;
|
|
||||||
import org.isoron.uhabits.utils.DateUtils;
|
|
||||||
import org.isoron.uhabits.models.Habit;
|
|
||||||
import org.isoron.uhabits.unit.HabitFixtures;
|
|
||||||
import org.junit.After;
|
|
||||||
import org.junit.Before;
|
|
||||||
import org.junit.Test;
|
|
||||||
import org.junit.runner.RunWith;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.io.StringWriter;
|
|
||||||
|
|
||||||
import static org.hamcrest.MatcherAssert.assertThat;
|
|
||||||
import static org.hamcrest.Matchers.equalTo;
|
|
||||||
import static org.isoron.uhabits.models.Checkmark.CHECKED_EXPLICITLY;
|
|
||||||
import static org.isoron.uhabits.models.Checkmark.CHECKED_IMPLICITLY;
|
|
||||||
import static org.isoron.uhabits.models.Checkmark.UNCHECKED;
|
|
||||||
|
|
||||||
@RunWith(AndroidJUnit4.class)
|
|
||||||
@SmallTest
|
|
||||||
public class CheckmarkListTest extends BaseAndroidTest
|
|
||||||
{
|
|
||||||
Habit nonDailyHabit;
|
|
||||||
private Habit emptyHabit;
|
|
||||||
|
|
||||||
@Before
|
|
||||||
public void setUp()
|
|
||||||
{
|
|
||||||
super.setUp();
|
|
||||||
|
|
||||||
HabitFixtures.purgeHabits();
|
|
||||||
nonDailyHabit = HabitFixtures.createShortHabit();
|
|
||||||
emptyHabit = HabitFixtures.createEmptyHabit();
|
|
||||||
}
|
|
||||||
|
|
||||||
@After
|
|
||||||
public void tearDown()
|
|
||||||
{
|
|
||||||
DateUtils.setFixedLocalTime(null);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void test_getAllValues_withNonDailyHabit()
|
|
||||||
{
|
|
||||||
int[] expectedValues = { CHECKED_EXPLICITLY, UNCHECKED, CHECKED_IMPLICITLY,
|
|
||||||
CHECKED_EXPLICITLY, CHECKED_EXPLICITLY, CHECKED_EXPLICITLY, UNCHECKED,
|
|
||||||
CHECKED_IMPLICITLY, CHECKED_EXPLICITLY, CHECKED_EXPLICITLY };
|
|
||||||
|
|
||||||
int[] actualValues = nonDailyHabit.checkmarks.getAllValues();
|
|
||||||
|
|
||||||
assertThat(actualValues, equalTo(expectedValues));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void test_getAllValues_withEmptyHabit()
|
|
||||||
{
|
|
||||||
int[] expectedValues = new int[0];
|
|
||||||
int[] actualValues = emptyHabit.checkmarks.getAllValues();
|
|
||||||
|
|
||||||
assertThat(actualValues, equalTo(expectedValues));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void test_getAllValues_moveForwardInTime()
|
|
||||||
{
|
|
||||||
travelInTime(3);
|
|
||||||
|
|
||||||
int[] expectedValues = { UNCHECKED, UNCHECKED, UNCHECKED, CHECKED_EXPLICITLY, UNCHECKED,
|
|
||||||
CHECKED_IMPLICITLY, CHECKED_EXPLICITLY, CHECKED_EXPLICITLY, CHECKED_EXPLICITLY,
|
|
||||||
UNCHECKED, CHECKED_IMPLICITLY, CHECKED_EXPLICITLY, CHECKED_EXPLICITLY };
|
|
||||||
|
|
||||||
int[] actualValues = nonDailyHabit.checkmarks.getAllValues();
|
|
||||||
|
|
||||||
assertThat(actualValues, equalTo(expectedValues));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void test_getAllValues_moveBackwardsInTime()
|
|
||||||
{
|
|
||||||
travelInTime(-3);
|
|
||||||
|
|
||||||
int[] expectedValues = { CHECKED_EXPLICITLY, CHECKED_EXPLICITLY, CHECKED_EXPLICITLY,
|
|
||||||
UNCHECKED, CHECKED_IMPLICITLY, CHECKED_EXPLICITLY, CHECKED_EXPLICITLY };
|
|
||||||
|
|
||||||
int[] actualValues = nonDailyHabit.checkmarks.getAllValues();
|
|
||||||
|
|
||||||
assertThat(actualValues, equalTo(expectedValues));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void test_getValues_withInvalidInterval()
|
|
||||||
{
|
|
||||||
int values[] = nonDailyHabit.checkmarks.getValues(100L, -100L);
|
|
||||||
assertThat(values, equalTo(new int[0]));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void test_getValues_withValidInterval()
|
|
||||||
{
|
|
||||||
long from = DateUtils.getStartOfToday() - 15 * DateUtils.millisecondsInOneDay;
|
|
||||||
long to = DateUtils.getStartOfToday() - 5 * DateUtils.millisecondsInOneDay;
|
|
||||||
|
|
||||||
int[] expectedValues = { CHECKED_EXPLICITLY, UNCHECKED, CHECKED_IMPLICITLY,
|
|
||||||
CHECKED_EXPLICITLY, CHECKED_EXPLICITLY, UNCHECKED, UNCHECKED, UNCHECKED, UNCHECKED,
|
|
||||||
UNCHECKED, UNCHECKED };
|
|
||||||
|
|
||||||
int[] actualValues = nonDailyHabit.checkmarks.getValues(from, to);
|
|
||||||
|
|
||||||
assertThat(actualValues, equalTo(expectedValues));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void test_getTodayValue()
|
|
||||||
{
|
|
||||||
travelInTime(-1);
|
|
||||||
assertThat(nonDailyHabit.checkmarks.getTodayValue(), equalTo(UNCHECKED));
|
|
||||||
|
|
||||||
travelInTime(0);
|
|
||||||
assertThat(nonDailyHabit.checkmarks.getTodayValue(), equalTo(CHECKED_EXPLICITLY));
|
|
||||||
|
|
||||||
travelInTime(1);
|
|
||||||
assertThat(nonDailyHabit.checkmarks.getTodayValue(), equalTo(UNCHECKED));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void test_writeCSV() throws IOException
|
|
||||||
{
|
|
||||||
String expectedCSV =
|
|
||||||
"2015-01-16,2\n" +
|
|
||||||
"2015-01-17,2\n" +
|
|
||||||
"2015-01-18,1\n" +
|
|
||||||
"2015-01-19,0\n" +
|
|
||||||
"2015-01-20,2\n" +
|
|
||||||
"2015-01-21,2\n" +
|
|
||||||
"2015-01-22,2\n" +
|
|
||||||
"2015-01-23,1\n" +
|
|
||||||
"2015-01-24,0\n" +
|
|
||||||
"2015-01-25,2\n";
|
|
||||||
|
|
||||||
StringWriter writer = new StringWriter();
|
|
||||||
nonDailyHabit.checkmarks.writeCSV(writer);
|
|
||||||
|
|
||||||
assertThat(writer.toString(), equalTo(expectedCSV));
|
|
||||||
}
|
|
||||||
|
|
||||||
private void travelInTime(int days)
|
|
||||||
{
|
|
||||||
DateUtils.setFixedLocalTime(FIXED_LOCAL_TIME +
|
|
||||||
days * DateUtils.millisecondsInOneDay);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,378 +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.unit.models;
|
|
||||||
|
|
||||||
import android.support.test.runner.AndroidJUnit4;
|
|
||||||
import android.test.suitebuilder.annotation.SmallTest;
|
|
||||||
|
|
||||||
import org.hamcrest.MatcherAssert;
|
|
||||||
import org.isoron.uhabits.BaseAndroidTest;
|
|
||||||
import org.isoron.uhabits.utils.DateUtils;
|
|
||||||
import org.isoron.uhabits.models.Habit;
|
|
||||||
import org.isoron.uhabits.unit.HabitFixtures;
|
|
||||||
import org.junit.Before;
|
|
||||||
import org.junit.Test;
|
|
||||||
import org.junit.runner.RunWith;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.io.StringWriter;
|
|
||||||
import java.util.LinkedList;
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
import static org.hamcrest.Matchers.equalTo;
|
|
||||||
import static org.hamcrest.Matchers.is;
|
|
||||||
import static org.hamcrest.Matchers.nullValue;
|
|
||||||
import static org.hamcrest.core.IsNot.not;
|
|
||||||
import static org.junit.Assert.assertThat;
|
|
||||||
import static org.junit.Assert.fail;
|
|
||||||
|
|
||||||
@RunWith(AndroidJUnit4.class)
|
|
||||||
@SmallTest
|
|
||||||
public class HabitTest extends BaseAndroidTest
|
|
||||||
{
|
|
||||||
@Before
|
|
||||||
public void setUp()
|
|
||||||
{
|
|
||||||
super.setUp();
|
|
||||||
HabitFixtures.purgeHabits();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testConstructor_default()
|
|
||||||
{
|
|
||||||
Habit habit = new Habit();
|
|
||||||
assertThat(habit.archived, is(0));
|
|
||||||
assertThat(habit.highlight, is(0));
|
|
||||||
|
|
||||||
assertThat(habit.reminderHour, is(nullValue()));
|
|
||||||
assertThat(habit.reminderMin, is(nullValue()));
|
|
||||||
|
|
||||||
assertThat(habit.reminderDays, is(not(nullValue())));
|
|
||||||
assertThat(habit.streaks, is(not(nullValue())));
|
|
||||||
assertThat(habit.scores, is(not(nullValue())));
|
|
||||||
assertThat(habit.repetitions, is(not(nullValue())));
|
|
||||||
assertThat(habit.checkmarks, is(not(nullValue())));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testConstructor_habit()
|
|
||||||
{
|
|
||||||
Habit model = new Habit();
|
|
||||||
model.archived = 1;
|
|
||||||
model.highlight = 1;
|
|
||||||
model.color = 0;
|
|
||||||
model.freqNum = 10;
|
|
||||||
model.freqDen = 20;
|
|
||||||
model.reminderDays = 1;
|
|
||||||
model.reminderHour = 8;
|
|
||||||
model.reminderMin = 30;
|
|
||||||
model.position = 0;
|
|
||||||
|
|
||||||
Habit habit = new Habit(model);
|
|
||||||
assertThat(habit.archived, is(model.archived));
|
|
||||||
assertThat(habit.highlight, is(model.highlight));
|
|
||||||
assertThat(habit.color, is(model.color));
|
|
||||||
assertThat(habit.freqNum, is(model.freqNum));
|
|
||||||
assertThat(habit.freqDen, is(model.freqDen));
|
|
||||||
assertThat(habit.reminderDays, is(model.reminderDays));
|
|
||||||
assertThat(habit.reminderHour, is(model.reminderHour));
|
|
||||||
assertThat(habit.reminderMin, is(model.reminderMin));
|
|
||||||
assertThat(habit.position, is(model.position));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void test_get_withValidId()
|
|
||||||
{
|
|
||||||
Habit habit = new Habit();
|
|
||||||
habit.save();
|
|
||||||
|
|
||||||
Habit habit2 = Habit.get(habit.getId());
|
|
||||||
assertThat(habit, equalTo(habit2));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void test_get_withInvalidId()
|
|
||||||
{
|
|
||||||
Habit habit = Habit.get(123456L);
|
|
||||||
assertThat(habit, is(nullValue()));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void test_getAll_withoutArchived()
|
|
||||||
{
|
|
||||||
List<Habit> habits = new LinkedList<>();
|
|
||||||
List<Habit> habitsWithArchived = new LinkedList<>();
|
|
||||||
|
|
||||||
for(int i = 0; i < 10; i++)
|
|
||||||
{
|
|
||||||
Habit h = new Habit();
|
|
||||||
|
|
||||||
if(i % 2 == 0)
|
|
||||||
h.archived = 1;
|
|
||||||
else
|
|
||||||
habits.add(h);
|
|
||||||
|
|
||||||
habitsWithArchived.add(h);
|
|
||||||
h.save();
|
|
||||||
}
|
|
||||||
|
|
||||||
assertThat(habits, equalTo(Habit.getAll(false)));
|
|
||||||
assertThat(habitsWithArchived, equalTo(Habit.getAll(true)));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void test_getByPosition()
|
|
||||||
{
|
|
||||||
List<Habit> habits = new LinkedList<>();
|
|
||||||
|
|
||||||
for(int i = 0; i < 10; i++)
|
|
||||||
{
|
|
||||||
Habit h = new Habit();
|
|
||||||
h.save();
|
|
||||||
habits.add(h);
|
|
||||||
}
|
|
||||||
|
|
||||||
for(int i = 0; i < 10; i++)
|
|
||||||
{
|
|
||||||
Habit h = Habit.getByPosition(i);
|
|
||||||
if(h == null) fail();
|
|
||||||
assertThat(h, equalTo(habits.get(i)));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void test_count()
|
|
||||||
{
|
|
||||||
for(int i = 0; i < 10; i++)
|
|
||||||
{
|
|
||||||
Habit h = new Habit();
|
|
||||||
if(i % 2 == 0) h.archived = 1;
|
|
||||||
h.save();
|
|
||||||
}
|
|
||||||
|
|
||||||
assertThat(Habit.count(), equalTo(5));
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void test_countWithArchived()
|
|
||||||
{
|
|
||||||
for(int i = 0; i < 10; i++)
|
|
||||||
{
|
|
||||||
Habit h = new Habit();
|
|
||||||
if(i % 2 == 0) h.archived = 1;
|
|
||||||
h.save();
|
|
||||||
}
|
|
||||||
|
|
||||||
assertThat(Habit.countWithArchived(), equalTo(10));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void test_updateId()
|
|
||||||
{
|
|
||||||
Habit habit = new Habit();
|
|
||||||
habit.name = "Hello World";
|
|
||||||
habit.save();
|
|
||||||
|
|
||||||
Long oldId = habit.getId();
|
|
||||||
Long newId = 123456L;
|
|
||||||
Habit.updateId(oldId, newId);
|
|
||||||
|
|
||||||
Habit newHabit = Habit.get(newId);
|
|
||||||
if(newHabit == null) fail();
|
|
||||||
assertThat(newHabit, is(not(nullValue())));
|
|
||||||
assertThat(newHabit.name, equalTo(habit.name));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void test_reorder()
|
|
||||||
{
|
|
||||||
List<Long> ids = new LinkedList<>();
|
|
||||||
|
|
||||||
int n = 10;
|
|
||||||
for (int i = 0; i < n; i++)
|
|
||||||
{
|
|
||||||
Habit h = new Habit();
|
|
||||||
h.save();
|
|
||||||
ids.add(h.getId());
|
|
||||||
assertThat(h.position, is(i));
|
|
||||||
}
|
|
||||||
|
|
||||||
int operations[][] = {
|
|
||||||
{5, 2},
|
|
||||||
{3, 7},
|
|
||||||
{4, 4},
|
|
||||||
{3, 2}
|
|
||||||
};
|
|
||||||
|
|
||||||
int expectedPosition[][] = {
|
|
||||||
{0, 1, 3, 4, 5, 2, 6, 7, 8, 9},
|
|
||||||
{0, 1, 7, 3, 4, 2, 5, 6, 8, 9},
|
|
||||||
{0, 1, 7, 3, 4, 2, 5, 6, 8, 9},
|
|
||||||
{0, 1, 7, 2, 4, 3, 5, 6, 8, 9},
|
|
||||||
};
|
|
||||||
|
|
||||||
for(int i = 0; i < operations.length; i++)
|
|
||||||
{
|
|
||||||
int from = operations[i][0];
|
|
||||||
int to = operations[i][1];
|
|
||||||
|
|
||||||
Habit fromHabit = Habit.getByPosition(from);
|
|
||||||
Habit toHabit = Habit.getByPosition(to);
|
|
||||||
Habit.reorder(fromHabit, toHabit);
|
|
||||||
|
|
||||||
int actualPositions[] = new int[n];
|
|
||||||
|
|
||||||
for (int j = 0; j < n; j++)
|
|
||||||
{
|
|
||||||
Habit h = Habit.get(ids.get(j));
|
|
||||||
if (h == null) fail();
|
|
||||||
actualPositions[j] = h.position;
|
|
||||||
}
|
|
||||||
|
|
||||||
assertThat(actualPositions, equalTo(expectedPosition[i]));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void test_rebuildOrder()
|
|
||||||
{
|
|
||||||
List<Long> ids = new LinkedList<>();
|
|
||||||
int originalPositions[] = { 0, 1, 1, 4, 6, 8, 10, 10, 13};
|
|
||||||
|
|
||||||
for (int p : originalPositions)
|
|
||||||
{
|
|
||||||
Habit h = new Habit();
|
|
||||||
h.position = p;
|
|
||||||
h.save();
|
|
||||||
ids.add(h.getId());
|
|
||||||
}
|
|
||||||
|
|
||||||
Habit.rebuildOrder();
|
|
||||||
|
|
||||||
for (int i = 0; i < originalPositions.length; i++)
|
|
||||||
{
|
|
||||||
Habit h = Habit.get(ids.get(i));
|
|
||||||
if(h == null) fail();
|
|
||||||
assertThat(h.position, is(i));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void test_getHabitsWithReminder()
|
|
||||||
{
|
|
||||||
List<Habit> habitsWithReminder = new LinkedList<>();
|
|
||||||
|
|
||||||
for(int i = 0; i < 10; i++)
|
|
||||||
{
|
|
||||||
Habit habit = new Habit();
|
|
||||||
if(i % 2 == 0)
|
|
||||||
{
|
|
||||||
habit.reminderDays = DateUtils.ALL_WEEK_DAYS;
|
|
||||||
habit.reminderHour = 8;
|
|
||||||
habit.reminderMin = 30;
|
|
||||||
habitsWithReminder.add(habit);
|
|
||||||
}
|
|
||||||
habit.save();
|
|
||||||
}
|
|
||||||
|
|
||||||
assertThat(habitsWithReminder, equalTo(Habit.getHabitsWithReminder()));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void test_archive_unarchive()
|
|
||||||
{
|
|
||||||
List<Habit> allHabits = new LinkedList<>();
|
|
||||||
List<Habit> archivedHabits = new LinkedList<>();
|
|
||||||
List<Habit> unarchivedHabits = new LinkedList<>();
|
|
||||||
|
|
||||||
for(int i = 0; i < 10; i++)
|
|
||||||
{
|
|
||||||
Habit habit = new Habit();
|
|
||||||
habit.save();
|
|
||||||
allHabits.add(habit);
|
|
||||||
|
|
||||||
if(i % 2 == 0)
|
|
||||||
archivedHabits.add(habit);
|
|
||||||
else
|
|
||||||
unarchivedHabits.add(habit);
|
|
||||||
}
|
|
||||||
|
|
||||||
Habit.archive(archivedHabits);
|
|
||||||
assertThat(Habit.getAll(false), equalTo(unarchivedHabits));
|
|
||||||
assertThat(Habit.getAll(true), equalTo(allHabits));
|
|
||||||
|
|
||||||
Habit.unarchive(archivedHabits);
|
|
||||||
assertThat(Habit.getAll(false), equalTo(allHabits));
|
|
||||||
assertThat(Habit.getAll(true), equalTo(allHabits));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void test_setColor()
|
|
||||||
{
|
|
||||||
List<Habit> habits = new LinkedList<>();
|
|
||||||
|
|
||||||
for(int i = 0; i < 10; i++)
|
|
||||||
{
|
|
||||||
Habit habit = new Habit();
|
|
||||||
habit.color = i;
|
|
||||||
habit.save();
|
|
||||||
habits.add(habit);
|
|
||||||
}
|
|
||||||
|
|
||||||
int newColor = 100;
|
|
||||||
Habit.setColor(habits, newColor);
|
|
||||||
|
|
||||||
for(Habit h : habits)
|
|
||||||
assertThat(h.color, equalTo(newColor));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void test_hasReminder_clearReminder()
|
|
||||||
{
|
|
||||||
Habit h = new Habit();
|
|
||||||
assertThat(h.hasReminder(), is(false));
|
|
||||||
|
|
||||||
h.reminderDays = DateUtils.ALL_WEEK_DAYS;
|
|
||||||
h.reminderHour = 8;
|
|
||||||
h.reminderMin = 30;
|
|
||||||
assertThat(h.hasReminder(), is(true));
|
|
||||||
|
|
||||||
h.clearReminder();
|
|
||||||
assertThat(h.hasReminder(), is(false));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void test_writeCSV() throws IOException
|
|
||||||
{
|
|
||||||
HabitFixtures.createEmptyHabit();
|
|
||||||
HabitFixtures.createShortHabit();
|
|
||||||
|
|
||||||
String expectedCSV =
|
|
||||||
"Position,Name,Description,NumRepetitions,Interval,Color\n" +
|
|
||||||
"001,Meditate,Did you meditate this morning?,1,1,#AFB42B\n" +
|
|
||||||
"002,Wake up early,Did you wake up before 6am?,2,3,#00897B\n";
|
|
||||||
|
|
||||||
StringWriter writer = new StringWriter();
|
|
||||||
Habit.writeCSV(Habit.getAll(true), writer);
|
|
||||||
|
|
||||||
MatcherAssert.assertThat(writer.toString(), equalTo(expectedCSV));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,191 +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.unit.models;
|
|
||||||
|
|
||||||
import android.support.test.runner.AndroidJUnit4;
|
|
||||||
import android.test.suitebuilder.annotation.SmallTest;
|
|
||||||
|
|
||||||
import org.isoron.uhabits.BaseAndroidTest;
|
|
||||||
import org.isoron.uhabits.utils.DateUtils;
|
|
||||||
import org.isoron.uhabits.models.Habit;
|
|
||||||
import org.isoron.uhabits.models.Repetition;
|
|
||||||
import org.isoron.uhabits.unit.HabitFixtures;
|
|
||||||
import org.junit.After;
|
|
||||||
import org.junit.Before;
|
|
||||||
import org.junit.Test;
|
|
||||||
import org.junit.runner.RunWith;
|
|
||||||
|
|
||||||
import java.util.Arrays;
|
|
||||||
import java.util.Calendar;
|
|
||||||
import java.util.GregorianCalendar;
|
|
||||||
import java.util.HashMap;
|
|
||||||
import java.util.Random;
|
|
||||||
|
|
||||||
import static junit.framework.Assert.assertFalse;
|
|
||||||
import static org.hamcrest.MatcherAssert.assertThat;
|
|
||||||
import static org.hamcrest.Matchers.equalTo;
|
|
||||||
|
|
||||||
@RunWith(AndroidJUnit4.class)
|
|
||||||
@SmallTest
|
|
||||||
public class RepetitionListTest extends BaseAndroidTest
|
|
||||||
{
|
|
||||||
private Habit habit;
|
|
||||||
private Habit emptyHabit;
|
|
||||||
|
|
||||||
@Before
|
|
||||||
public void setUp()
|
|
||||||
{
|
|
||||||
super.setUp();
|
|
||||||
|
|
||||||
HabitFixtures.purgeHabits();
|
|
||||||
habit = HabitFixtures.createShortHabit();
|
|
||||||
emptyHabit = HabitFixtures.createEmptyHabit();
|
|
||||||
}
|
|
||||||
|
|
||||||
@After
|
|
||||||
public void tearDown()
|
|
||||||
{
|
|
||||||
DateUtils.setFixedLocalTime(null);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void test_contains()
|
|
||||||
{
|
|
||||||
long current = DateUtils.getStartOfToday();
|
|
||||||
|
|
||||||
for(boolean b : HabitFixtures.NON_DAILY_HABIT_CHECKS)
|
|
||||||
{
|
|
||||||
assertThat(habit.repetitions.contains(current), equalTo(b));
|
|
||||||
current -= DateUtils.millisecondsInOneDay;
|
|
||||||
}
|
|
||||||
|
|
||||||
for(int i = 0; i < 3; i++)
|
|
||||||
{
|
|
||||||
assertThat(habit.repetitions.contains(current), equalTo(false));
|
|
||||||
current -= DateUtils.millisecondsInOneDay;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void test_delete()
|
|
||||||
{
|
|
||||||
long timestamp = DateUtils.getStartOfToday();
|
|
||||||
assertThat(habit.repetitions.contains(timestamp), equalTo(true));
|
|
||||||
|
|
||||||
habit.repetitions.delete(timestamp);
|
|
||||||
assertThat(habit.repetitions.contains(timestamp), equalTo(false));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void test_toggle()
|
|
||||||
{
|
|
||||||
long timestamp = DateUtils.getStartOfToday();
|
|
||||||
assertThat(habit.repetitions.contains(timestamp), equalTo(true));
|
|
||||||
|
|
||||||
habit.repetitions.toggle(timestamp);
|
|
||||||
assertThat(habit.repetitions.contains(timestamp), equalTo(false));
|
|
||||||
|
|
||||||
habit.repetitions.toggle(timestamp);
|
|
||||||
assertThat(habit.repetitions.contains(timestamp), equalTo(true));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void test_getWeekDayFrequency()
|
|
||||||
{
|
|
||||||
Random random = new Random();
|
|
||||||
Integer weekdayCount[][] = new Integer[12][7];
|
|
||||||
Integer monthCount[] = new Integer[12];
|
|
||||||
|
|
||||||
Arrays.fill(monthCount, 0);
|
|
||||||
for(Integer row[] : weekdayCount)
|
|
||||||
Arrays.fill(row, 0);
|
|
||||||
|
|
||||||
GregorianCalendar day = DateUtils.getStartOfTodayCalendar();
|
|
||||||
|
|
||||||
// Sets the current date to the end of November
|
|
||||||
day.set(2015, 10, 30);
|
|
||||||
DateUtils.setFixedLocalTime(day.getTimeInMillis());
|
|
||||||
|
|
||||||
// Add repetitions randomly from January to December
|
|
||||||
// Leaves the month of March empty, to check that it returns null
|
|
||||||
day.set(2015, 0, 1);
|
|
||||||
for(int i = 0; i < 365; i ++)
|
|
||||||
{
|
|
||||||
if(random.nextBoolean())
|
|
||||||
{
|
|
||||||
int month = day.get(Calendar.MONTH);
|
|
||||||
int week = day.get(Calendar.DAY_OF_WEEK) % 7;
|
|
||||||
|
|
||||||
if(month != 2)
|
|
||||||
{
|
|
||||||
if (month <= 10)
|
|
||||||
{
|
|
||||||
weekdayCount[month][week]++;
|
|
||||||
monthCount[month]++;
|
|
||||||
}
|
|
||||||
emptyHabit.repetitions.toggle(day.getTimeInMillis());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
day.add(Calendar.DAY_OF_YEAR, 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
HashMap<Long, Integer[]> freq = emptyHabit.repetitions.getWeekdayFrequency();
|
|
||||||
|
|
||||||
// Repetitions until November should be counted correctly
|
|
||||||
for(int month = 0; month < 11; month++)
|
|
||||||
{
|
|
||||||
day.set(2015, month, 1);
|
|
||||||
Integer actualCount[] = freq.get(day.getTimeInMillis());
|
|
||||||
if(monthCount[month] == 0)
|
|
||||||
assertThat(actualCount, equalTo(null));
|
|
||||||
else
|
|
||||||
assertThat(actualCount, equalTo(weekdayCount[month]));
|
|
||||||
}
|
|
||||||
|
|
||||||
// Repetitions in December should be discarded
|
|
||||||
day.set(2015, 11, 1);
|
|
||||||
assertThat(freq.get(day.getTimeInMillis()), equalTo(null));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void test_count()
|
|
||||||
{
|
|
||||||
long to = DateUtils.getStartOfToday();
|
|
||||||
long from = to - 9 * DateUtils.millisecondsInOneDay;
|
|
||||||
assertThat(habit.repetitions.count(from, to), equalTo(6));
|
|
||||||
|
|
||||||
to = DateUtils.getStartOfToday() - DateUtils.millisecondsInOneDay;
|
|
||||||
from = to - 5 * DateUtils.millisecondsInOneDay;
|
|
||||||
assertThat(habit.repetitions.count(from, to), equalTo(3));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void test_getOldest()
|
|
||||||
{
|
|
||||||
long expectedOldestTimestamp = DateUtils.getStartOfToday() - 9 * DateUtils.millisecondsInOneDay;
|
|
||||||
|
|
||||||
assertThat(habit.repetitions.getOldestTimestamp(), equalTo(expectedOldestTimestamp));
|
|
||||||
|
|
||||||
Repetition oldest = habit.repetitions.getOldest();
|
|
||||||
assertFalse(oldest == null);
|
|
||||||
assertThat(oldest.timestamp, equalTo(expectedOldestTimestamp));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -23,11 +23,9 @@ import android.support.test.runner.AndroidJUnit4;
|
|||||||
import android.test.suitebuilder.annotation.SmallTest;
|
import android.test.suitebuilder.annotation.SmallTest;
|
||||||
|
|
||||||
import org.isoron.uhabits.BaseAndroidTest;
|
import org.isoron.uhabits.BaseAndroidTest;
|
||||||
import org.isoron.uhabits.utils.DateUtils;
|
|
||||||
import org.isoron.uhabits.utils.DatabaseUtils;
|
|
||||||
import org.isoron.uhabits.models.Habit;
|
import org.isoron.uhabits.models.Habit;
|
||||||
import org.isoron.uhabits.models.Score;
|
import org.isoron.uhabits.utils.DatabaseUtils;
|
||||||
import org.isoron.uhabits.unit.HabitFixtures;
|
import org.isoron.uhabits.utils.DateUtils;
|
||||||
import org.junit.After;
|
import org.junit.After;
|
||||||
import org.junit.Before;
|
import org.junit.Before;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
@@ -50,8 +48,8 @@ public class ScoreListTest extends BaseAndroidTest
|
|||||||
{
|
{
|
||||||
super.setUp();
|
super.setUp();
|
||||||
|
|
||||||
HabitFixtures.purgeHabits();
|
habitFixtures.purgeHabits(habitList);
|
||||||
habit = HabitFixtures.createEmptyHabit();
|
habit = habitFixtures.createEmptyHabit();
|
||||||
}
|
}
|
||||||
|
|
||||||
@After
|
@After
|
||||||
@@ -61,55 +59,14 @@ public class ScoreListTest extends BaseAndroidTest
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void test_invalidateNewerThan()
|
public void test_getAllValues_withGroups()
|
||||||
{
|
|
||||||
assertThat(habit.scores.getTodayValue(), equalTo(0));
|
|
||||||
|
|
||||||
toggleRepetitions(0, 2);
|
|
||||||
assertThat(habit.scores.getTodayValue(), equalTo(1948077));
|
|
||||||
|
|
||||||
habit.freqNum = 1;
|
|
||||||
habit.freqDen = 2;
|
|
||||||
habit.scores.invalidateNewerThan(0);
|
|
||||||
|
|
||||||
assertThat(habit.scores.getTodayValue(), equalTo(1974654));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void test_getTodayStarValue()
|
|
||||||
{
|
|
||||||
assertThat(habit.scores.getTodayStarStatus(), equalTo(Score.EMPTY_STAR));
|
|
||||||
|
|
||||||
int k = 0;
|
|
||||||
while(habit.scores.getTodayValue() < Score.HALF_STAR_CUTOFF) toggleRepetitions(k, ++k);
|
|
||||||
assertThat(habit.scores.getTodayStarStatus(), equalTo(Score.HALF_STAR));
|
|
||||||
|
|
||||||
while(habit.scores.getTodayValue() < Score.FULL_STAR_CUTOFF) toggleRepetitions(k, ++k);
|
|
||||||
assertThat(habit.scores.getTodayStarStatus(), equalTo(Score.FULL_STAR));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void test_getTodayValue()
|
|
||||||
{
|
|
||||||
toggleRepetitions(0, 20);
|
|
||||||
assertThat(habit.scores.getTodayValue(), equalTo(12629351));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void test_getValue()
|
|
||||||
{
|
{
|
||||||
toggleRepetitions(0, 20);
|
toggleRepetitions(0, 20);
|
||||||
|
|
||||||
int expectedValues[] = { 12629351, 12266245, 11883254, 11479288, 11053198, 10603773,
|
int expectedValues[] = {11434978, 7894999, 3212362};
|
||||||
10129735, 9629735, 9102352, 8546087, 7959357, 7340494, 6687738, 5999234, 5273023,
|
|
||||||
4507040, 3699107, 2846927, 1948077, 1000000 };
|
|
||||||
|
|
||||||
long current = DateUtils.getStartOfToday();
|
int actualValues[] = habit.getScores().getAllValues(7);
|
||||||
for(int expectedValue : expectedValues)
|
assertThat(actualValues, equalTo(expectedValues));
|
||||||
{
|
|
||||||
assertThat(habit.scores.getValue(current), equalTo(expectedValue));
|
|
||||||
current -= DateUtils.millisecondsInOneDay;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@@ -117,33 +74,99 @@ public class ScoreListTest extends BaseAndroidTest
|
|||||||
{
|
{
|
||||||
toggleRepetitions(0, 20);
|
toggleRepetitions(0, 20);
|
||||||
|
|
||||||
int expectedValues[] = { 12629351, 12266245, 11883254, 11479288, 11053198, 10603773,
|
int expectedValues[] = {
|
||||||
10129735, 9629735, 9102352, 8546087, 7959357, 7340494, 6687738, 5999234, 5273023,
|
12629351,
|
||||||
4507040, 3699107, 2846927, 1948077, 1000000 };
|
12266245,
|
||||||
|
11883254,
|
||||||
|
11479288,
|
||||||
|
11053198,
|
||||||
|
10603773,
|
||||||
|
10129735,
|
||||||
|
9629735,
|
||||||
|
9102352,
|
||||||
|
8546087,
|
||||||
|
7959357,
|
||||||
|
7340494,
|
||||||
|
6687738,
|
||||||
|
5999234,
|
||||||
|
5273023,
|
||||||
|
4507040,
|
||||||
|
3699107,
|
||||||
|
2846927,
|
||||||
|
1948077,
|
||||||
|
1000000
|
||||||
|
};
|
||||||
|
|
||||||
int actualValues[] = habit.scores.getAllValues(1);
|
int actualValues[] = habit.getScores().getAllValues(1);
|
||||||
assertThat(actualValues, equalTo(expectedValues));
|
assertThat(actualValues, equalTo(expectedValues));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void test_getAllValues_withGroups()
|
public void test_getTodayValue()
|
||||||
|
{
|
||||||
|
toggleRepetitions(0, 20);
|
||||||
|
assertThat(habit.getScores().getTodayValue(), equalTo(12629351));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void test_getValue()
|
||||||
{
|
{
|
||||||
toggleRepetitions(0, 20);
|
toggleRepetitions(0, 20);
|
||||||
|
|
||||||
int expectedValues[] = { 11434978, 7894999, 3212362 };
|
int expectedValues[] = {
|
||||||
|
12629351,
|
||||||
|
12266245,
|
||||||
|
11883254,
|
||||||
|
11479288,
|
||||||
|
11053198,
|
||||||
|
10603773,
|
||||||
|
10129735,
|
||||||
|
9629735,
|
||||||
|
9102352,
|
||||||
|
8546087,
|
||||||
|
7959357,
|
||||||
|
7340494,
|
||||||
|
6687738,
|
||||||
|
5999234,
|
||||||
|
5273023,
|
||||||
|
4507040,
|
||||||
|
3699107,
|
||||||
|
2846927,
|
||||||
|
1948077,
|
||||||
|
1000000
|
||||||
|
};
|
||||||
|
|
||||||
int actualValues[] = habit.scores.getAllValues(7);
|
long current = DateUtils.getStartOfToday();
|
||||||
assertThat(actualValues, equalTo(expectedValues));
|
for (int expectedValue : expectedValues)
|
||||||
|
{
|
||||||
|
assertThat(habit.getScores().getValue(current),
|
||||||
|
equalTo(expectedValue));
|
||||||
|
current -= DateUtils.millisecondsInOneDay;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void test_invalidateNewerThan()
|
||||||
|
{
|
||||||
|
assertThat(habit.getScores().getTodayValue(), equalTo(0));
|
||||||
|
|
||||||
|
toggleRepetitions(0, 2);
|
||||||
|
assertThat(habit.getScores().getTodayValue(), equalTo(1948077));
|
||||||
|
|
||||||
|
habit.setFreqNum(1);
|
||||||
|
habit.setFreqDen(2);
|
||||||
|
habit.getScores().invalidateNewerThan(0);
|
||||||
|
|
||||||
|
assertThat(habit.getScores().getTodayValue(), equalTo(1974654));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void test_writeCSV() throws IOException
|
public void test_writeCSV() throws IOException
|
||||||
{
|
{
|
||||||
HabitFixtures.purgeHabits();
|
habitFixtures.purgeHabits(habitList);
|
||||||
Habit habit = HabitFixtures.createShortHabit();
|
Habit habit = habitFixtures.createShortHabit();
|
||||||
|
|
||||||
String expectedCSV =
|
String expectedCSV = "2015-01-16,0.0519\n" +
|
||||||
"2015-01-16,0.0519\n" +
|
|
||||||
"2015-01-17,0.1021\n" +
|
"2015-01-17,0.1021\n" +
|
||||||
"2015-01-18,0.0986\n" +
|
"2015-01-18,0.0986\n" +
|
||||||
"2015-01-19,0.0952\n" +
|
"2015-01-19,0.0952\n" +
|
||||||
@@ -155,22 +178,19 @@ public class ScoreListTest extends BaseAndroidTest
|
|||||||
"2015-01-25,0.2649\n";
|
"2015-01-25,0.2649\n";
|
||||||
|
|
||||||
StringWriter writer = new StringWriter();
|
StringWriter writer = new StringWriter();
|
||||||
habit.scores.writeCSV(writer);
|
habit.getScores().writeCSV(writer);
|
||||||
|
|
||||||
assertThat(writer.toString(), equalTo(expectedCSV));
|
assertThat(writer.toString(), equalTo(expectedCSV));
|
||||||
}
|
}
|
||||||
|
|
||||||
private void toggleRepetitions(final int from, final int to)
|
private void toggleRepetitions(final int from, final int to)
|
||||||
{
|
{
|
||||||
DatabaseUtils.executeAsTransaction(new DatabaseUtils.Command()
|
DatabaseUtils.executeAsTransaction(() -> {
|
||||||
{
|
|
||||||
@Override
|
|
||||||
public void execute()
|
|
||||||
{
|
|
||||||
long today = DateUtils.getStartOfToday();
|
long today = DateUtils.getStartOfToday();
|
||||||
for (int i = from; i < to; i++)
|
for (int i = from; i < to; i++)
|
||||||
habit.repetitions.toggle(today - i * DateUtils.millisecondsInOneDay);
|
habit
|
||||||
}
|
.getRepetitions()
|
||||||
|
.toggleTimestamp(today - i * DateUtils.millisecondsInOneDay);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -36,6 +36,7 @@ import static org.junit.Assert.assertThat;
|
|||||||
@SmallTest
|
@SmallTest
|
||||||
public class ScoreTest extends BaseAndroidTest
|
public class ScoreTest extends BaseAndroidTest
|
||||||
{
|
{
|
||||||
|
@Override
|
||||||
@Before
|
@Before
|
||||||
public void setUp()
|
public void setUp()
|
||||||
{
|
{
|
||||||
@@ -61,7 +62,8 @@ public class ScoreTest extends BaseAndroidTest
|
|||||||
assertThat(Score.compute(1, 0, checkmark), equalTo(1000000));
|
assertThat(Score.compute(1, 0, checkmark), equalTo(1000000));
|
||||||
assertThat(Score.compute(1, 5000000, checkmark), equalTo(5740387));
|
assertThat(Score.compute(1, 5000000, checkmark), equalTo(5740387));
|
||||||
assertThat(Score.compute(1, 10000000, checkmark), equalTo(10480775));
|
assertThat(Score.compute(1, 10000000, checkmark), equalTo(10480775));
|
||||||
assertThat(Score.compute(1, Score.MAX_VALUE, checkmark), equalTo(Score.MAX_VALUE));
|
assertThat(Score.compute(1, Score.MAX_VALUE, checkmark), equalTo(
|
||||||
|
Score.MAX_VALUE));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@@ -71,38 +73,13 @@ public class ScoreTest extends BaseAndroidTest
|
|||||||
assertThat(Score.compute(1 / 3.0, 0, checkmark), equalTo(1000000));
|
assertThat(Score.compute(1 / 3.0, 0, checkmark), equalTo(1000000));
|
||||||
assertThat(Score.compute(1 / 3.0, 5000000, checkmark), equalTo(5916180));
|
assertThat(Score.compute(1 / 3.0, 5000000, checkmark), equalTo(5916180));
|
||||||
assertThat(Score.compute(1 / 3.0, 10000000, checkmark), equalTo(10832360));
|
assertThat(Score.compute(1 / 3.0, 10000000, checkmark), equalTo(10832360));
|
||||||
assertThat(Score.compute(1/3.0, Score.MAX_VALUE, checkmark), equalTo(Score.MAX_VALUE));
|
assertThat(Score.compute(1 / 3.0, Score.MAX_VALUE, checkmark), equalTo(
|
||||||
|
Score.MAX_VALUE));
|
||||||
|
|
||||||
assertThat(Score.compute(1 / 7.0, 0, checkmark), equalTo(1000000));
|
assertThat(Score.compute(1 / 7.0, 0, checkmark), equalTo(1000000));
|
||||||
assertThat(Score.compute(1 / 7.0, 5000000, checkmark), equalTo(5964398));
|
assertThat(Score.compute(1 / 7.0, 5000000, checkmark), equalTo(5964398));
|
||||||
assertThat(Score.compute(1 / 7.0, 10000000, checkmark), equalTo(10928796));
|
assertThat(Score.compute(1 / 7.0, 10000000, checkmark), equalTo(10928796));
|
||||||
assertThat(Score.compute(1/7.0, Score.MAX_VALUE, checkmark), equalTo(Score.MAX_VALUE));
|
assertThat(Score.compute(1 / 7.0, Score.MAX_VALUE, checkmark), equalTo(
|
||||||
}
|
Score.MAX_VALUE));
|
||||||
|
|
||||||
@Test
|
|
||||||
public void test_getStarStatus()
|
|
||||||
{
|
|
||||||
Score s = new Score();
|
|
||||||
|
|
||||||
s.score = Score.FULL_STAR_CUTOFF + 1;
|
|
||||||
assertThat(s.getStarStatus(), equalTo(Score.FULL_STAR));
|
|
||||||
|
|
||||||
s.score = Score.FULL_STAR_CUTOFF;
|
|
||||||
assertThat(s.getStarStatus(), equalTo(Score.FULL_STAR));
|
|
||||||
|
|
||||||
s.score = Score.FULL_STAR_CUTOFF - 1;
|
|
||||||
assertThat(s.getStarStatus(), equalTo(Score.HALF_STAR));
|
|
||||||
|
|
||||||
s.score = Score.HALF_STAR_CUTOFF + 1;
|
|
||||||
assertThat(s.getStarStatus(), equalTo(Score.HALF_STAR));
|
|
||||||
|
|
||||||
s.score = Score.HALF_STAR_CUTOFF;
|
|
||||||
assertThat(s.getStarStatus(), equalTo(Score.HALF_STAR));
|
|
||||||
|
|
||||||
s.score = Score.HALF_STAR_CUTOFF - 1;
|
|
||||||
assertThat(s.getStarStatus(), equalTo(Score.EMPTY_STAR));
|
|
||||||
|
|
||||||
s.score = 0;
|
|
||||||
assertThat(s.getStarStatus(), equalTo(Score.EMPTY_STAR));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -52,21 +52,16 @@ public class ExportCSVTaskTest extends BaseAndroidTest
|
|||||||
@Test
|
@Test
|
||||||
public void testExportCSV() throws Throwable
|
public void testExportCSV() throws Throwable
|
||||||
{
|
{
|
||||||
HabitFixtures.createShortHabit();
|
habitFixtures.createShortHabit();
|
||||||
List<Habit> habits = Habit.getAll(true);
|
List<Habit> habits = habitList.getAll(true);
|
||||||
|
|
||||||
ExportCSVTask task = new ExportCSVTask(habits, null);
|
ExportCSVTask task = new ExportCSVTask(habits, null);
|
||||||
task.setListener(new ExportCSVTask.Listener()
|
task.setListener(archiveFilename -> {
|
||||||
{
|
|
||||||
@Override
|
|
||||||
public void onExportCSVFinished(String archiveFilename)
|
|
||||||
{
|
|
||||||
assertThat(archiveFilename, is(not(nullValue())));
|
assertThat(archiveFilename, is(not(nullValue())));
|
||||||
|
|
||||||
File f = new File(archiveFilename);
|
File f = new File(archiveFilename);
|
||||||
assertTrue(f.exists());
|
assertTrue(f.exists());
|
||||||
assertTrue(f.canRead());
|
assertTrue(f.canRead());
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
|
||||||
task.execute();
|
task.execute();
|
||||||
|
|||||||
@@ -40,8 +40,10 @@ public class CheckmarkButtonViewTest extends ViewTest
|
|||||||
public static final String PATH = "ui/habits/list/CheckmarkButtonView/";
|
public static final String PATH = "ui/habits/list/CheckmarkButtonView/";
|
||||||
|
|
||||||
private CountDownLatch latch;
|
private CountDownLatch latch;
|
||||||
|
|
||||||
private CheckmarkButtonView view;
|
private CheckmarkButtonView view;
|
||||||
|
|
||||||
|
@Override
|
||||||
@Before
|
@Before
|
||||||
public void setUp()
|
public void setUp()
|
||||||
{
|
{
|
||||||
@@ -51,33 +53,11 @@ public class CheckmarkButtonViewTest extends ViewTest
|
|||||||
latch = new CountDownLatch(1);
|
latch = new CountDownLatch(1);
|
||||||
view = new CheckmarkButtonView(targetContext);
|
view = new CheckmarkButtonView(targetContext);
|
||||||
view.setValue(Checkmark.UNCHECKED);
|
view.setValue(Checkmark.UNCHECKED);
|
||||||
view.setColor(ColorUtils.CSV_PALETTE[7]);
|
view.setColor(ColorUtils.getAndroidTestColor(7));
|
||||||
|
|
||||||
measureView(dpToPixels(40), dpToPixels(40), view);
|
measureView(dpToPixels(40), dpToPixels(40), view);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void assertRendersCheckedExplicitly() throws IOException
|
|
||||||
{
|
|
||||||
assertRenders(view, PATH + "render_explicit_check.png");
|
|
||||||
}
|
|
||||||
|
|
||||||
protected void assertRendersUnchecked() throws IOException
|
|
||||||
{
|
|
||||||
assertRenders(view, PATH + "render_unchecked.png");
|
|
||||||
}
|
|
||||||
|
|
||||||
protected void assertRendersCheckedImplicitly() throws IOException
|
|
||||||
{
|
|
||||||
assertRenders(view, PATH + "render_implicit_check.png");
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testRender_unchecked() throws Exception
|
|
||||||
{
|
|
||||||
view.setValue(Checkmark.UNCHECKED);
|
|
||||||
assertRendersUnchecked();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testRender_explicitCheck() throws Exception
|
public void testRender_explicitCheck() throws Exception
|
||||||
{
|
{
|
||||||
@@ -92,6 +72,28 @@ public class CheckmarkButtonViewTest extends ViewTest
|
|||||||
assertRendersCheckedImplicitly();
|
assertRendersCheckedImplicitly();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testRender_unchecked() throws Exception
|
||||||
|
{
|
||||||
|
view.setValue(Checkmark.UNCHECKED);
|
||||||
|
assertRendersUnchecked();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void assertRendersCheckedExplicitly() throws IOException
|
||||||
|
{
|
||||||
|
assertRenders(view, PATH + "render_explicit_check.png");
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void assertRendersCheckedImplicitly() throws IOException
|
||||||
|
{
|
||||||
|
assertRenders(view, PATH + "render_implicit_check.png");
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void assertRendersUnchecked() throws IOException
|
||||||
|
{
|
||||||
|
assertRenders(view, PATH + "render_unchecked.png");
|
||||||
|
}
|
||||||
|
|
||||||
// @Test
|
// @Test
|
||||||
// public void testLongClick() throws Exception
|
// public void testLongClick() throws Exception
|
||||||
// {
|
// {
|
||||||
|
|||||||
@@ -54,13 +54,14 @@ public class CheckmarkPanelViewTest extends ViewTest
|
|||||||
Habit habit = new Habit();
|
Habit habit = new Habit();
|
||||||
|
|
||||||
latch = new CountDownLatch(1);
|
latch = new CountDownLatch(1);
|
||||||
checkmarks = new int[]{Checkmark.CHECKED_EXPLICITLY, Checkmark.UNCHECKED,
|
checkmarks = new int[]{
|
||||||
|
Checkmark.CHECKED_EXPLICITLY, Checkmark.UNCHECKED,
|
||||||
Checkmark.CHECKED_IMPLICITLY, Checkmark.CHECKED_EXPLICITLY};
|
Checkmark.CHECKED_IMPLICITLY, Checkmark.CHECKED_EXPLICITLY};
|
||||||
|
|
||||||
view = new CheckmarkPanelView(targetContext);
|
view = new CheckmarkPanelView(targetContext);
|
||||||
view.setHabit(habit);
|
view.setHabit(habit);
|
||||||
view.setCheckmarkValues(checkmarks);
|
view.setCheckmarkValues(checkmarks);
|
||||||
view.setColor(ColorUtils.CSV_PALETTE[7]);
|
view.setColor(ColorUtils.getAndroidTestColor(7));
|
||||||
|
|
||||||
measureView(dpToPixels(200), dpToPixels(200), view);
|
measureView(dpToPixels(200), dpToPixels(200), view);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -23,11 +23,10 @@ import android.support.test.runner.AndroidJUnit4;
|
|||||||
import android.test.suitebuilder.annotation.SmallTest;
|
import android.test.suitebuilder.annotation.SmallTest;
|
||||||
|
|
||||||
import org.isoron.uhabits.R;
|
import org.isoron.uhabits.R;
|
||||||
|
import org.isoron.uhabits.models.Habit;
|
||||||
import org.isoron.uhabits.utils.DateUtils;
|
import org.isoron.uhabits.utils.DateUtils;
|
||||||
import org.isoron.uhabits.utils.InterfaceUtils;
|
import org.isoron.uhabits.utils.InterfaceUtils;
|
||||||
import org.isoron.uhabits.models.Habit;
|
import org.isoron.uhabits.widgets.views.CheckmarkWidgetView;
|
||||||
import org.isoron.uhabits.unit.HabitFixtures;
|
|
||||||
import org.isoron.uhabits.views.CheckmarkWidgetView;
|
|
||||||
import org.junit.Before;
|
import org.junit.Before;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
import org.junit.runner.RunWith;
|
import org.junit.runner.RunWith;
|
||||||
@@ -39,6 +38,7 @@ import java.io.IOException;
|
|||||||
public class CheckmarkWidgetViewTest extends ViewTest
|
public class CheckmarkWidgetViewTest extends ViewTest
|
||||||
{
|
{
|
||||||
private CheckmarkWidgetView view;
|
private CheckmarkWidgetView view;
|
||||||
|
|
||||||
private Habit habit;
|
private Habit habit;
|
||||||
|
|
||||||
@Before
|
@Before
|
||||||
@@ -47,7 +47,7 @@ public class CheckmarkWidgetViewTest extends ViewTest
|
|||||||
super.setUp();
|
super.setUp();
|
||||||
InterfaceUtils.setFixedTheme(R.style.TransparentWidgetTheme);
|
InterfaceUtils.setFixedTheme(R.style.TransparentWidgetTheme);
|
||||||
|
|
||||||
habit = HabitFixtures.createShortHabit();
|
habit = habitFixtures.createShortHabit();
|
||||||
view = new CheckmarkWidgetView(targetContext);
|
view = new CheckmarkWidgetView(targetContext);
|
||||||
view.setHabit(habit);
|
view.setHabit(habit);
|
||||||
refreshData(view);
|
refreshData(view);
|
||||||
@@ -60,23 +60,14 @@ public class CheckmarkWidgetViewTest extends ViewTest
|
|||||||
assertRenders(view, "CheckmarkView/checked.png");
|
assertRenders(view, "CheckmarkView/checked.png");
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testRender_unchecked() throws IOException
|
|
||||||
{
|
|
||||||
habit.repetitions.toggle(DateUtils.getStartOfToday());
|
|
||||||
view.refreshData();
|
|
||||||
|
|
||||||
assertRenders(view, "CheckmarkView/unchecked.png");
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testRender_implicitlyChecked() throws IOException
|
public void testRender_implicitlyChecked() throws IOException
|
||||||
{
|
{
|
||||||
long today = DateUtils.getStartOfToday();
|
long today = DateUtils.getStartOfToday();
|
||||||
long day = DateUtils.millisecondsInOneDay;
|
long day = DateUtils.millisecondsInOneDay;
|
||||||
habit.repetitions.toggle(today);
|
habit.getRepetitions().toggleTimestamp(today);
|
||||||
habit.repetitions.toggle(today - day);
|
habit.getRepetitions().toggleTimestamp(today - day);
|
||||||
habit.repetitions.toggle(today - 2 * day);
|
habit.getRepetitions().toggleTimestamp(today - 2 * day);
|
||||||
view.refreshData();
|
view.refreshData();
|
||||||
|
|
||||||
assertRenders(view, "CheckmarkView/implicitly_checked.png");
|
assertRenders(view, "CheckmarkView/implicitly_checked.png");
|
||||||
@@ -88,4 +79,13 @@ public class CheckmarkWidgetViewTest extends ViewTest
|
|||||||
measureView(dpToPixels(300), dpToPixels(300), view);
|
measureView(dpToPixels(300), dpToPixels(300), view);
|
||||||
assertRenders(view, "CheckmarkView/large_size.png");
|
assertRenders(view, "CheckmarkView/large_size.png");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testRender_unchecked() throws IOException
|
||||||
|
{
|
||||||
|
habit.getRepetitions().toggleTimestamp(DateUtils.getStartOfToday());
|
||||||
|
view.refreshData();
|
||||||
|
|
||||||
|
assertRenders(view, "CheckmarkView/unchecked.png");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -23,8 +23,7 @@ import android.support.test.runner.AndroidJUnit4;
|
|||||||
import android.test.suitebuilder.annotation.SmallTest;
|
import android.test.suitebuilder.annotation.SmallTest;
|
||||||
|
|
||||||
import org.isoron.uhabits.models.Habit;
|
import org.isoron.uhabits.models.Habit;
|
||||||
import org.isoron.uhabits.unit.HabitFixtures;
|
import org.isoron.uhabits.ui.habits.show.views.HabitFrequencyView;
|
||||||
import org.isoron.uhabits.views.HabitFrequencyView;
|
|
||||||
import org.junit.Before;
|
import org.junit.Before;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
import org.junit.runner.RunWith;
|
import org.junit.runner.RunWith;
|
||||||
@@ -40,8 +39,8 @@ public class HabitFrequencyViewTest extends ViewTest
|
|||||||
{
|
{
|
||||||
super.setUp();
|
super.setUp();
|
||||||
|
|
||||||
HabitFixtures.purgeHabits();
|
habitFixtures.purgeHabits(habitList);
|
||||||
Habit habit = HabitFixtures.createLongHabit();
|
Habit habit = habitFixtures.createLongHabit();
|
||||||
|
|
||||||
view = new HabitFrequencyView(targetContext);
|
view = new HabitFrequencyView(targetContext);
|
||||||
view.setHabit(habit);
|
view.setHabit(habit);
|
||||||
@@ -56,10 +55,12 @@ public class HabitFrequencyViewTest extends ViewTest
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testRender_withTransparentBackground() throws Throwable
|
public void testRender_withDataOffset() throws Throwable
|
||||||
{
|
{
|
||||||
view.setIsBackgroundTransparent(true);
|
view.onScroll(null, null, -dpToPixels(150), 0);
|
||||||
assertRenders(view, "HabitFrequencyView/renderTransparent.png");
|
view.invalidate();
|
||||||
|
|
||||||
|
assertRenders(view, "HabitFrequencyView/renderDataOffset.png");
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@@ -70,11 +71,9 @@ public class HabitFrequencyViewTest extends ViewTest
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testRender_withDataOffset() throws Throwable
|
public void testRender_withTransparentBackground() throws Throwable
|
||||||
{
|
{
|
||||||
view.onScroll(null, null, -dpToPixels(150), 0);
|
view.setIsBackgroundTransparent(true);
|
||||||
view.invalidate();
|
assertRenders(view, "HabitFrequencyView/renderTransparent.png");
|
||||||
|
|
||||||
assertRenders(view, "HabitFrequencyView/renderDataOffset.png");
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -22,10 +22,9 @@ package org.isoron.uhabits.unit.views;
|
|||||||
import android.support.test.runner.AndroidJUnit4;
|
import android.support.test.runner.AndroidJUnit4;
|
||||||
import android.test.suitebuilder.annotation.SmallTest;
|
import android.test.suitebuilder.annotation.SmallTest;
|
||||||
|
|
||||||
import org.isoron.uhabits.utils.DateUtils;
|
|
||||||
import org.isoron.uhabits.models.Habit;
|
import org.isoron.uhabits.models.Habit;
|
||||||
import org.isoron.uhabits.unit.HabitFixtures;
|
import org.isoron.uhabits.utils.DateUtils;
|
||||||
import org.isoron.uhabits.views.HabitHistoryView;
|
import org.isoron.uhabits.ui.habits.show.views.HabitHistoryView;
|
||||||
import org.junit.Before;
|
import org.junit.Before;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
import org.junit.runner.RunWith;
|
import org.junit.runner.RunWith;
|
||||||
@@ -40,6 +39,7 @@ import static org.hamcrest.Matchers.equalTo;
|
|||||||
public class HabitHistoryViewTest extends ViewTest
|
public class HabitHistoryViewTest extends ViewTest
|
||||||
{
|
{
|
||||||
private Habit habit;
|
private Habit habit;
|
||||||
|
|
||||||
private HabitHistoryView view;
|
private HabitHistoryView view;
|
||||||
|
|
||||||
@Before
|
@Before
|
||||||
@@ -47,8 +47,8 @@ public class HabitHistoryViewTest extends ViewTest
|
|||||||
{
|
{
|
||||||
super.setUp();
|
super.setUp();
|
||||||
|
|
||||||
HabitFixtures.purgeHabits();
|
habitFixtures.purgeHabits(habitList);
|
||||||
habit = HabitFixtures.createLongHabit();
|
habit = habitFixtures.createLongHabit();
|
||||||
|
|
||||||
view = new HabitHistoryView(targetContext);
|
view = new HabitHistoryView(targetContext);
|
||||||
view.setHabit(habit);
|
view.setHabit(habit);
|
||||||
@@ -56,26 +56,49 @@ public class HabitHistoryViewTest extends ViewTest
|
|||||||
refreshData(view);
|
refreshData(view);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void tapDate_atInvalidLocations() throws Throwable
|
||||||
|
{
|
||||||
|
int expectedCheckmarkValues[] = habit.getCheckmarks().getAllValues();
|
||||||
|
|
||||||
|
view.setIsEditable(true);
|
||||||
|
tap(view, 118, 13); // header
|
||||||
|
tap(view, 336, 60); // tomorrow's square
|
||||||
|
tap(view, 370, 60); // right axis
|
||||||
|
waitForAsyncTasks();
|
||||||
|
|
||||||
|
int actualCheckmarkValues[] = habit.getCheckmarks().getAllValues();
|
||||||
|
assertThat(actualCheckmarkValues, equalTo(expectedCheckmarkValues));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void tapDate_withEditableView() throws Throwable
|
||||||
|
{
|
||||||
|
view.setIsEditable(true);
|
||||||
|
tap(view, 340, 40); // today's square
|
||||||
|
waitForAsyncTasks();
|
||||||
|
|
||||||
|
long today = DateUtils.getStartOfToday();
|
||||||
|
assertFalse(habit.getRepetitions().containsTimestamp(today));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void tapDate_withReadOnlyView() throws Throwable
|
||||||
|
{
|
||||||
|
view.setIsEditable(false);
|
||||||
|
tap(view, 340, 40); // today's square
|
||||||
|
waitForAsyncTasks();
|
||||||
|
|
||||||
|
long today = DateUtils.getStartOfToday();
|
||||||
|
assertTrue(habit.getRepetitions().containsTimestamp(today));
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testRender() throws Throwable
|
public void testRender() throws Throwable
|
||||||
{
|
{
|
||||||
assertRenders(view, "HabitHistoryView/render.png");
|
assertRenders(view, "HabitHistoryView/render.png");
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testRender_withTransparentBackground() throws Throwable
|
|
||||||
{
|
|
||||||
view.setIsBackgroundTransparent(true);
|
|
||||||
assertRenders(view, "HabitHistoryView/renderTransparent.png");
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testRender_withDifferentSize() throws Throwable
|
|
||||||
{
|
|
||||||
measureView(dpToPixels(200), dpToPixels(200), view);
|
|
||||||
assertRenders(view, "HabitHistoryView/renderDifferentSize.png");
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testRender_withDataOffset() throws Throwable
|
public void testRender_withDataOffset() throws Throwable
|
||||||
{
|
{
|
||||||
@@ -86,40 +109,17 @@ public class HabitHistoryViewTest extends ViewTest
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void tapDate_withEditableView() throws Throwable
|
public void testRender_withDifferentSize() throws Throwable
|
||||||
{
|
{
|
||||||
view.setIsEditable(true);
|
measureView(dpToPixels(200), dpToPixels(200), view);
|
||||||
tap(view, 340, 40); // today's square
|
assertRenders(view, "HabitHistoryView/renderDifferentSize.png");
|
||||||
waitForAsyncTasks();
|
|
||||||
|
|
||||||
long today = DateUtils.getStartOfToday();
|
|
||||||
assertFalse(habit.repetitions.contains(today));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void tapDate_atInvalidLocations() throws Throwable
|
public void testRender_withTransparentBackground() throws Throwable
|
||||||
{
|
{
|
||||||
int expectedCheckmarkValues[] = habit.checkmarks.getAllValues();
|
view.setIsBackgroundTransparent(true);
|
||||||
|
assertRenders(view, "HabitHistoryView/renderTransparent.png");
|
||||||
view.setIsEditable(true);
|
|
||||||
tap(view, 118, 13); // header
|
|
||||||
tap(view, 336, 60); // tomorrow's square
|
|
||||||
tap(view, 370, 60); // right axis
|
|
||||||
waitForAsyncTasks();
|
|
||||||
|
|
||||||
int actualCheckmarkValues[] = habit.checkmarks.getAllValues();
|
|
||||||
assertThat(actualCheckmarkValues, equalTo(expectedCheckmarkValues));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void tapDate_withReadOnlyView() throws Throwable
|
|
||||||
{
|
|
||||||
view.setIsEditable(false);
|
|
||||||
tap(view, 340, 40); // today's square
|
|
||||||
waitForAsyncTasks();
|
|
||||||
|
|
||||||
long today = DateUtils.getStartOfToday();
|
|
||||||
assertTrue(habit.repetitions.contains(today));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -24,8 +24,7 @@ import android.test.suitebuilder.annotation.SmallTest;
|
|||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
|
|
||||||
import org.isoron.uhabits.models.Habit;
|
import org.isoron.uhabits.models.Habit;
|
||||||
import org.isoron.uhabits.unit.HabitFixtures;
|
import org.isoron.uhabits.ui.habits.show.views.HabitScoreView;
|
||||||
import org.isoron.uhabits.views.HabitScoreView;
|
|
||||||
import org.junit.Before;
|
import org.junit.Before;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
import org.junit.runner.RunWith;
|
import org.junit.runner.RunWith;
|
||||||
@@ -35,6 +34,7 @@ import org.junit.runner.RunWith;
|
|||||||
public class HabitScoreViewTest extends ViewTest
|
public class HabitScoreViewTest extends ViewTest
|
||||||
{
|
{
|
||||||
private Habit habit;
|
private Habit habit;
|
||||||
|
|
||||||
private HabitScoreView view;
|
private HabitScoreView view;
|
||||||
|
|
||||||
@Before
|
@Before
|
||||||
@@ -42,8 +42,8 @@ public class HabitScoreViewTest extends ViewTest
|
|||||||
{
|
{
|
||||||
super.setUp();
|
super.setUp();
|
||||||
|
|
||||||
HabitFixtures.purgeHabits();
|
habitFixtures.purgeHabits(habitList);
|
||||||
habit = HabitFixtures.createLongHabit();
|
habit = habitFixtures.createLongHabit();
|
||||||
|
|
||||||
view = new HabitScoreView(targetContext);
|
view = new HabitScoreView(targetContext);
|
||||||
view.setHabit(habit);
|
view.setHabit(habit);
|
||||||
@@ -55,24 +55,11 @@ public class HabitScoreViewTest extends ViewTest
|
|||||||
@Test
|
@Test
|
||||||
public void testRender() throws Throwable
|
public void testRender() throws Throwable
|
||||||
{
|
{
|
||||||
Log.d("HabitScoreViewTest", String.format("height=%d", dpToPixels(100)));
|
Log.d("HabitScoreViewTest",
|
||||||
|
String.format("height=%d", dpToPixels(100)));
|
||||||
assertRenders(view, "HabitScoreView/render.png");
|
assertRenders(view, "HabitScoreView/render.png");
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testRender_withTransparentBackground() throws Throwable
|
|
||||||
{
|
|
||||||
view.setIsTransparencyEnabled(true);
|
|
||||||
assertRenders(view, "HabitScoreView/renderTransparent.png");
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testRender_withDifferentSize() throws Throwable
|
|
||||||
{
|
|
||||||
measureView(dpToPixels(200), dpToPixels(200), view);
|
|
||||||
assertRenders(view, "HabitScoreView/renderDifferentSize.png");
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testRender_withDataOffset() throws Throwable
|
public void testRender_withDataOffset() throws Throwable
|
||||||
{
|
{
|
||||||
@@ -82,6 +69,13 @@ public class HabitScoreViewTest extends ViewTest
|
|||||||
assertRenders(view, "HabitScoreView/renderDataOffset.png");
|
assertRenders(view, "HabitScoreView/renderDataOffset.png");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testRender_withDifferentSize() throws Throwable
|
||||||
|
{
|
||||||
|
measureView(dpToPixels(200), dpToPixels(200), view);
|
||||||
|
assertRenders(view, "HabitScoreView/renderDifferentSize.png");
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testRender_withMonthlyBucket() throws Throwable
|
public void testRender_withMonthlyBucket() throws Throwable
|
||||||
{
|
{
|
||||||
@@ -92,6 +86,13 @@ public class HabitScoreViewTest extends ViewTest
|
|||||||
assertRenders(view, "HabitScoreView/renderMonthly.png");
|
assertRenders(view, "HabitScoreView/renderMonthly.png");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testRender_withTransparentBackground() throws Throwable
|
||||||
|
{
|
||||||
|
view.setIsTransparencyEnabled(true);
|
||||||
|
assertRenders(view, "HabitScoreView/renderTransparent.png");
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testRender_withYearlyBucket() throws Throwable
|
public void testRender_withYearlyBucket() throws Throwable
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -23,8 +23,7 @@ import android.support.test.runner.AndroidJUnit4;
|
|||||||
import android.test.suitebuilder.annotation.SmallTest;
|
import android.test.suitebuilder.annotation.SmallTest;
|
||||||
|
|
||||||
import org.isoron.uhabits.models.Habit;
|
import org.isoron.uhabits.models.Habit;
|
||||||
import org.isoron.uhabits.unit.HabitFixtures;
|
import org.isoron.uhabits.ui.habits.show.views.HabitStreakView;
|
||||||
import org.isoron.uhabits.views.HabitStreakView;
|
|
||||||
import org.junit.Before;
|
import org.junit.Before;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
import org.junit.runner.RunWith;
|
import org.junit.runner.RunWith;
|
||||||
@@ -35,13 +34,14 @@ public class HabitStreakViewTest extends ViewTest
|
|||||||
{
|
{
|
||||||
private HabitStreakView view;
|
private HabitStreakView view;
|
||||||
|
|
||||||
|
@Override
|
||||||
@Before
|
@Before
|
||||||
public void setUp()
|
public void setUp()
|
||||||
{
|
{
|
||||||
super.setUp();
|
super.setUp();
|
||||||
|
|
||||||
HabitFixtures.purgeHabits();
|
habitFixtures.purgeHabits(habitList);
|
||||||
Habit habit = HabitFixtures.createLongHabit();
|
Habit habit = habitFixtures.createLongHabit();
|
||||||
|
|
||||||
view = new HabitStreakView(targetContext);
|
view = new HabitStreakView(targetContext);
|
||||||
measureView(dpToPixels(300), dpToPixels(100), view);
|
measureView(dpToPixels(300), dpToPixels(100), view);
|
||||||
@@ -56,13 +56,6 @@ public class HabitStreakViewTest extends ViewTest
|
|||||||
assertRenders(view, "HabitStreakView/render.png");
|
assertRenders(view, "HabitStreakView/render.png");
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testRender_withTransparentBackground() throws Throwable
|
|
||||||
{
|
|
||||||
view.setIsBackgroundTransparent(true);
|
|
||||||
assertRenders(view, "HabitStreakView/renderTransparent.png");
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testRender_withSmallSize() throws Throwable
|
public void testRender_withSmallSize() throws Throwable
|
||||||
{
|
{
|
||||||
@@ -71,4 +64,11 @@ public class HabitStreakViewTest extends ViewTest
|
|||||||
|
|
||||||
assertRenders(view, "HabitStreakView/renderSmallSize.png");
|
assertRenders(view, "HabitStreakView/renderSmallSize.png");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testRender_withTransparentBackground() throws Throwable
|
||||||
|
{
|
||||||
|
view.setIsBackgroundTransparent(true);
|
||||||
|
assertRenders(view, "HabitStreakView/renderTransparent.png");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,77 +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.unit.views;
|
|
||||||
|
|
||||||
import android.support.test.runner.AndroidJUnit4;
|
|
||||||
import android.test.suitebuilder.annotation.SmallTest;
|
|
||||||
|
|
||||||
import org.isoron.uhabits.R;
|
|
||||||
import org.isoron.uhabits.utils.ColorUtils;
|
|
||||||
import org.isoron.uhabits.views.NumberView;
|
|
||||||
import org.junit.Before;
|
|
||||||
import org.junit.Test;
|
|
||||||
import org.junit.runner.RunWith;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
|
|
||||||
@RunWith(AndroidJUnit4.class)
|
|
||||||
@SmallTest
|
|
||||||
public class NumberViewTest extends ViewTest
|
|
||||||
{
|
|
||||||
private NumberView view;
|
|
||||||
|
|
||||||
@Before
|
|
||||||
public void setUp()
|
|
||||||
{
|
|
||||||
super.setUp();
|
|
||||||
|
|
||||||
view = new NumberView(targetContext);
|
|
||||||
view.setLabel("Hello world");
|
|
||||||
view.setNumber(31);
|
|
||||||
view.setColor(ColorUtils.CSV_PALETTE[0]);
|
|
||||||
measureView(dpToPixels(100), dpToPixels(100), view);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testRender_base() throws IOException
|
|
||||||
{
|
|
||||||
assertRenders(view, "NumberView/render.png");
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testRender_withLongLabel() throws IOException
|
|
||||||
{
|
|
||||||
view.setLabel("The quick brown fox jumps over the lazy fox");
|
|
||||||
|
|
||||||
measureView(dpToPixels(100), dpToPixels(100), view);
|
|
||||||
assertRenders(view, "NumberView/renderLongLabel.png");
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testRender_withDifferentParams() throws IOException
|
|
||||||
{
|
|
||||||
view.setNumber(500);
|
|
||||||
view.setColor(ColorUtils.CSV_PALETTE[5]);
|
|
||||||
view.setTextSize(targetContext.getResources().getDimension(R.dimen.tinyTextSize));
|
|
||||||
|
|
||||||
measureView(dpToPixels(200), dpToPixels(200), view);
|
|
||||||
assertRenders(view, "NumberView/renderDifferentParams.png");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -24,7 +24,7 @@ import android.support.test.runner.AndroidJUnit4;
|
|||||||
import android.test.suitebuilder.annotation.SmallTest;
|
import android.test.suitebuilder.annotation.SmallTest;
|
||||||
|
|
||||||
import org.isoron.uhabits.utils.ColorUtils;
|
import org.isoron.uhabits.utils.ColorUtils;
|
||||||
import org.isoron.uhabits.views.RingView;
|
import org.isoron.uhabits.ui.habits.show.views.RingView;
|
||||||
import org.junit.Before;
|
import org.junit.Before;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
import org.junit.runner.RunWith;
|
import org.junit.runner.RunWith;
|
||||||
@@ -45,7 +45,7 @@ public class RingViewTest extends ViewTest
|
|||||||
view = new RingView(targetContext);
|
view = new RingView(targetContext);
|
||||||
view.setPercentage(0.6f);
|
view.setPercentage(0.6f);
|
||||||
view.setText("60%");
|
view.setText("60%");
|
||||||
view.setColor(ColorUtils.CSV_PALETTE[0]);
|
view.setColor(ColorUtils.getAndroidTestColor(0));
|
||||||
view.setBackgroundColor(Color.WHITE);
|
view.setBackgroundColor(Color.WHITE);
|
||||||
view.setThickness(dpToPixels(3));
|
view.setThickness(dpToPixels(3));
|
||||||
}
|
}
|
||||||
@@ -61,7 +61,7 @@ public class RingViewTest extends ViewTest
|
|||||||
public void testRender_withDifferentParams() throws IOException
|
public void testRender_withDifferentParams() throws IOException
|
||||||
{
|
{
|
||||||
view.setPercentage(0.25f);
|
view.setPercentage(0.25f);
|
||||||
view.setColor(ColorUtils.CSV_PALETTE[5]);
|
view.setColor(ColorUtils.getAndroidTestColor(5));
|
||||||
|
|
||||||
measureView(dpToPixels(200), dpToPixels(200), view);
|
measureView(dpToPixels(200), dpToPixels(200), view);
|
||||||
assertRenders(view, "RingView/renderDifferentParams.png");
|
assertRenders(view, "RingView/renderDifferentParams.png");
|
||||||
|
|||||||
@@ -30,7 +30,7 @@ import org.isoron.uhabits.BaseAndroidTest;
|
|||||||
import org.isoron.uhabits.utils.FileUtils;
|
import org.isoron.uhabits.utils.FileUtils;
|
||||||
import org.isoron.uhabits.utils.InterfaceUtils;
|
import org.isoron.uhabits.utils.InterfaceUtils;
|
||||||
import org.isoron.uhabits.tasks.BaseTask;
|
import org.isoron.uhabits.tasks.BaseTask;
|
||||||
import org.isoron.uhabits.views.HabitDataView;
|
import org.isoron.uhabits.ui.habits.show.views.HabitDataView;
|
||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.io.FileOutputStream;
|
import java.io.FileOutputStream;
|
||||||
|
|||||||
@@ -23,6 +23,9 @@ import javax.inject.Singleton;
|
|||||||
|
|
||||||
import dagger.Component;
|
import dagger.Component;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Dependency injection component for classes that are specific to Android.
|
||||||
|
*/
|
||||||
@Singleton
|
@Singleton
|
||||||
@Component(modules = {AndroidModule.class})
|
@Component(modules = {AndroidModule.class})
|
||||||
public interface AndroidComponent extends BaseComponent
|
public interface AndroidComponent extends BaseComponent
|
||||||
|
|||||||
@@ -20,6 +20,10 @@
|
|||||||
package org.isoron.uhabits;
|
package org.isoron.uhabits;
|
||||||
|
|
||||||
import org.isoron.uhabits.commands.CommandRunner;
|
import org.isoron.uhabits.commands.CommandRunner;
|
||||||
|
import org.isoron.uhabits.models.HabitList;
|
||||||
|
import org.isoron.uhabits.models.ModelFactory;
|
||||||
|
import org.isoron.uhabits.models.sqlite.SQLModelFactory;
|
||||||
|
import org.isoron.uhabits.models.sqlite.SQLiteHabitList;
|
||||||
import org.isoron.uhabits.ui.habits.list.model.HabitCardListCache;
|
import org.isoron.uhabits.ui.habits.list.model.HabitCardListCache;
|
||||||
import org.isoron.uhabits.utils.Preferences;
|
import org.isoron.uhabits.utils.Preferences;
|
||||||
|
|
||||||
@@ -28,16 +32,15 @@ import javax.inject.Singleton;
|
|||||||
import dagger.Module;
|
import dagger.Module;
|
||||||
import dagger.Provides;
|
import dagger.Provides;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Module that provides dependencies when the application is running on
|
||||||
|
* Android.
|
||||||
|
* <p>
|
||||||
|
* This module is also used for instrumented tests.
|
||||||
|
*/
|
||||||
@Module
|
@Module
|
||||||
public class AndroidModule
|
public class AndroidModule
|
||||||
{
|
{
|
||||||
@Provides
|
|
||||||
@Singleton
|
|
||||||
Preferences providePreferences()
|
|
||||||
{
|
|
||||||
return new Preferences();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Provides
|
@Provides
|
||||||
@Singleton
|
@Singleton
|
||||||
CommandRunner provideCommandRunner()
|
CommandRunner provideCommandRunner()
|
||||||
@@ -51,4 +54,24 @@ public class AndroidModule
|
|||||||
{
|
{
|
||||||
return new HabitCardListCache();
|
return new HabitCardListCache();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Provides
|
||||||
|
@Singleton
|
||||||
|
HabitList provideHabitList()
|
||||||
|
{
|
||||||
|
return SQLiteHabitList.getInstance();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Provides
|
||||||
|
ModelFactory provideModelFactory()
|
||||||
|
{
|
||||||
|
return new SQLModelFactory();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Provides
|
||||||
|
@Singleton
|
||||||
|
Preferences providePreferences()
|
||||||
|
{
|
||||||
|
return new Preferences();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -19,16 +19,34 @@
|
|||||||
|
|
||||||
package org.isoron.uhabits;
|
package org.isoron.uhabits;
|
||||||
|
|
||||||
|
import org.isoron.uhabits.commands.ArchiveHabitsCommand;
|
||||||
|
import org.isoron.uhabits.commands.ChangeHabitColorCommand;
|
||||||
|
import org.isoron.uhabits.commands.CreateHabitCommand;
|
||||||
|
import org.isoron.uhabits.commands.DeleteHabitsCommand;
|
||||||
|
import org.isoron.uhabits.commands.EditHabitCommand;
|
||||||
|
import org.isoron.uhabits.commands.UnarchiveHabitsCommand;
|
||||||
|
import org.isoron.uhabits.io.AbstractImporter;
|
||||||
|
import org.isoron.uhabits.io.HabitsCSVExporter;
|
||||||
|
import org.isoron.uhabits.models.Habit;
|
||||||
import org.isoron.uhabits.tasks.ToggleRepetitionTask;
|
import org.isoron.uhabits.tasks.ToggleRepetitionTask;
|
||||||
|
import org.isoron.uhabits.ui.BaseSystem;
|
||||||
import org.isoron.uhabits.ui.habits.edit.BaseDialogFragment;
|
import org.isoron.uhabits.ui.habits.edit.BaseDialogFragment;
|
||||||
|
import org.isoron.uhabits.ui.habits.edit.HistoryEditorDialog;
|
||||||
|
import org.isoron.uhabits.ui.habits.list.ListHabitsActivity;
|
||||||
|
import org.isoron.uhabits.ui.habits.list.ListHabitsController;
|
||||||
import org.isoron.uhabits.ui.habits.list.ListHabitsSelectionMenu;
|
import org.isoron.uhabits.ui.habits.list.ListHabitsSelectionMenu;
|
||||||
|
import org.isoron.uhabits.ui.habits.list.controllers.CheckmarkButtonController;
|
||||||
import org.isoron.uhabits.ui.habits.list.model.HabitCardListAdapter;
|
import org.isoron.uhabits.ui.habits.list.model.HabitCardListAdapter;
|
||||||
import org.isoron.uhabits.ui.habits.list.model.HabitCardListCache;
|
import org.isoron.uhabits.ui.habits.list.model.HabitCardListCache;
|
||||||
import org.isoron.uhabits.ui.habits.list.ListHabitsController;
|
|
||||||
import org.isoron.uhabits.ui.habits.list.controllers.CheckmarkButtonController;
|
|
||||||
import org.isoron.uhabits.ui.habits.list.model.HintList;
|
import org.isoron.uhabits.ui.habits.list.model.HintList;
|
||||||
import org.isoron.uhabits.ui.habits.list.views.CheckmarkPanelView;
|
import org.isoron.uhabits.ui.habits.list.views.CheckmarkPanelView;
|
||||||
|
import org.isoron.uhabits.ui.habits.show.ShowHabitActivity;
|
||||||
|
import org.isoron.uhabits.widgets.BaseWidgetProvider;
|
||||||
|
import org.isoron.uhabits.widgets.HabitPickerDialog;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Base component for dependency injection.
|
||||||
|
*/
|
||||||
public interface BaseComponent
|
public interface BaseComponent
|
||||||
{
|
{
|
||||||
void inject(CheckmarkButtonController checkmarkButtonController);
|
void inject(CheckmarkButtonController checkmarkButtonController);
|
||||||
@@ -50,4 +68,36 @@ public interface BaseComponent
|
|||||||
void inject(HintList hintList);
|
void inject(HintList hintList);
|
||||||
|
|
||||||
void inject(HabitCardListAdapter habitCardListAdapter);
|
void inject(HabitCardListAdapter habitCardListAdapter);
|
||||||
|
|
||||||
|
void inject(ArchiveHabitsCommand archiveHabitsCommand);
|
||||||
|
|
||||||
|
void inject(ChangeHabitColorCommand changeHabitColorCommand);
|
||||||
|
|
||||||
|
void inject(UnarchiveHabitsCommand unarchiveHabitsCommand);
|
||||||
|
|
||||||
|
void inject(EditHabitCommand editHabitCommand);
|
||||||
|
|
||||||
|
void inject(CreateHabitCommand createHabitCommand);
|
||||||
|
|
||||||
|
void inject(HabitPickerDialog habitPickerDialog);
|
||||||
|
|
||||||
|
void inject(BaseWidgetProvider baseWidgetProvider);
|
||||||
|
|
||||||
|
void inject(ShowHabitActivity showHabitActivity);
|
||||||
|
|
||||||
|
void inject(DeleteHabitsCommand deleteHabitsCommand);
|
||||||
|
|
||||||
|
void inject(ListHabitsActivity listHabitsActivity);
|
||||||
|
|
||||||
|
void inject(BaseSystem baseSystem);
|
||||||
|
|
||||||
|
void inject(HistoryEditorDialog historyEditorDialog);
|
||||||
|
|
||||||
|
void inject(HabitsApplication application);
|
||||||
|
|
||||||
|
void inject(Habit habit);
|
||||||
|
|
||||||
|
void inject(AbstractImporter abstractImporter);
|
||||||
|
|
||||||
|
void inject(HabitsCSVExporter habitsCSVExporter);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -40,6 +40,7 @@ import org.isoron.uhabits.commands.CommandRunner;
|
|||||||
import org.isoron.uhabits.commands.ToggleRepetitionCommand;
|
import org.isoron.uhabits.commands.ToggleRepetitionCommand;
|
||||||
import org.isoron.uhabits.models.Checkmark;
|
import org.isoron.uhabits.models.Checkmark;
|
||||||
import org.isoron.uhabits.models.Habit;
|
import org.isoron.uhabits.models.Habit;
|
||||||
|
import org.isoron.uhabits.models.HabitList;
|
||||||
import org.isoron.uhabits.tasks.BaseTask;
|
import org.isoron.uhabits.tasks.BaseTask;
|
||||||
import org.isoron.uhabits.ui.habits.show.ShowHabitActivity;
|
import org.isoron.uhabits.ui.habits.show.ShowHabitActivity;
|
||||||
import org.isoron.uhabits.utils.DateUtils;
|
import org.isoron.uhabits.utils.DateUtils;
|
||||||
@@ -50,12 +51,26 @@ import java.util.Date;
|
|||||||
|
|
||||||
import javax.inject.Inject;
|
import javax.inject.Inject;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The Android BroadacastReceiver for Loop Habit Tracker.
|
||||||
|
* <p>
|
||||||
|
* Currently, all broadcast messages are received and processed by this class.
|
||||||
|
*/
|
||||||
public class HabitBroadcastReceiver extends BroadcastReceiver
|
public class HabitBroadcastReceiver extends BroadcastReceiver
|
||||||
{
|
{
|
||||||
public static final String ACTION_CHECK = "org.isoron.uhabits.ACTION_CHECK";
|
public static final String ACTION_CHECK = "org.isoron.uhabits.ACTION_CHECK";
|
||||||
public static final String ACTION_DISMISS = "org.isoron.uhabits.ACTION_DISMISS";
|
|
||||||
public static final String ACTION_SHOW_REMINDER = "org.isoron.uhabits.ACTION_SHOW_REMINDER";
|
public static final String ACTION_DISMISS =
|
||||||
public static final String ACTION_SNOOZE = "org.isoron.uhabits.ACTION_SNOOZE";
|
"org.isoron.uhabits.ACTION_DISMISS";
|
||||||
|
|
||||||
|
public static final String ACTION_SHOW_REMINDER =
|
||||||
|
"org.isoron.uhabits.ACTION_SHOW_REMINDER";
|
||||||
|
|
||||||
|
public static final String ACTION_SNOOZE =
|
||||||
|
"org.isoron.uhabits.ACTION_SNOOZE";
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
HabitList habitList;
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
CommandRunner commandRunner;
|
CommandRunner commandRunner;
|
||||||
@@ -66,6 +81,68 @@ public class HabitBroadcastReceiver extends BroadcastReceiver
|
|||||||
HabitsApplication.getComponent().inject(this);
|
HabitsApplication.getComponent().inject(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static PendingIntent buildCheckIntent(Context context,
|
||||||
|
Habit habit,
|
||||||
|
Long timestamp)
|
||||||
|
{
|
||||||
|
Uri data = habit.getUri();
|
||||||
|
Intent checkIntent = new Intent(context, HabitBroadcastReceiver.class);
|
||||||
|
checkIntent.setData(data);
|
||||||
|
checkIntent.setAction(ACTION_CHECK);
|
||||||
|
if (timestamp != null) checkIntent.putExtra("timestamp", timestamp);
|
||||||
|
return PendingIntent.getBroadcast(context, 0, checkIntent,
|
||||||
|
PendingIntent.FLAG_ONE_SHOT);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static PendingIntent buildDismissIntent(Context context)
|
||||||
|
{
|
||||||
|
Intent deleteIntent = new Intent(context, HabitBroadcastReceiver.class);
|
||||||
|
deleteIntent.setAction(ACTION_DISMISS);
|
||||||
|
return PendingIntent.getBroadcast(context, 0, deleteIntent, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static PendingIntent buildSnoozeIntent(Context context, Habit habit)
|
||||||
|
{
|
||||||
|
Uri data = habit.getUri();
|
||||||
|
Intent snoozeIntent = new Intent(context, HabitBroadcastReceiver.class);
|
||||||
|
snoozeIntent.setData(data);
|
||||||
|
snoozeIntent.setAction(ACTION_SNOOZE);
|
||||||
|
return PendingIntent.getBroadcast(context, 0, snoozeIntent, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static PendingIntent buildViewHabitIntent(Context context,
|
||||||
|
Habit habit)
|
||||||
|
{
|
||||||
|
Intent intent = new Intent(context, ShowHabitActivity.class);
|
||||||
|
intent.setData(
|
||||||
|
Uri.parse("content://org.isoron.uhabits/habit/" + habit.getId()));
|
||||||
|
|
||||||
|
return TaskStackBuilder
|
||||||
|
.create(context.getApplicationContext())
|
||||||
|
.addNextIntentWithParentStack(intent)
|
||||||
|
.getPendingIntent(0, PendingIntent.FLAG_UPDATE_CURRENT);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void dismissNotification(Context context, Habit habit)
|
||||||
|
{
|
||||||
|
NotificationManager notificationManager =
|
||||||
|
(NotificationManager) context.getSystemService(
|
||||||
|
Activity.NOTIFICATION_SERVICE);
|
||||||
|
|
||||||
|
int notificationId = (int) (habit.getId() % Integer.MAX_VALUE);
|
||||||
|
notificationManager.cancel(notificationId);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void sendRefreshBroadcast(Context context)
|
||||||
|
{
|
||||||
|
LocalBroadcastManager manager =
|
||||||
|
LocalBroadcastManager.getInstance(context);
|
||||||
|
Intent refreshIntent = new Intent(HabitsApplication.ACTION_REFRESH);
|
||||||
|
manager.sendBroadcast(refreshIntent);
|
||||||
|
|
||||||
|
WidgetManager.updateWidgets(context);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onReceive(final Context context, Intent intent)
|
public void onReceive(final Context context, Intent intent)
|
||||||
{
|
{
|
||||||
@@ -89,40 +166,23 @@ public class HabitBroadcastReceiver extends BroadcastReceiver
|
|||||||
break;
|
break;
|
||||||
|
|
||||||
case Intent.ACTION_BOOT_COMPLETED:
|
case Intent.ACTION_BOOT_COMPLETED:
|
||||||
ReminderUtils.createReminderAlarms(context);
|
ReminderUtils.createReminderAlarms(context, habitList);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void createReminderAlarmsDelayed(final Context context)
|
|
||||||
{
|
|
||||||
new Handler().postDelayed(() -> ReminderUtils.createReminderAlarms(context), 5000);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void snoozeHabit(Context context, Intent intent)
|
|
||||||
{
|
|
||||||
Uri data = intent.getData();
|
|
||||||
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context);
|
|
||||||
long delayMinutes = Long.parseLong(prefs.getString("pref_snooze_interval", "15"));
|
|
||||||
|
|
||||||
long habitId = ContentUris.parseId(data);
|
|
||||||
Habit habit = Habit.get(habitId);
|
|
||||||
if(habit != null)
|
|
||||||
ReminderUtils.createReminderAlarm(context, habit,
|
|
||||||
new Date().getTime() + delayMinutes * 60 * 1000);
|
|
||||||
dismissNotification(context, habitId);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void checkHabit(Context context, Intent intent)
|
private void checkHabit(Context context, Intent intent)
|
||||||
{
|
{
|
||||||
Uri data = intent.getData();
|
Uri data = intent.getData();
|
||||||
Long timestamp = intent.getLongExtra("timestamp", DateUtils.getStartOfToday());
|
Long timestamp =
|
||||||
|
intent.getLongExtra("timestamp", DateUtils.getStartOfToday());
|
||||||
|
|
||||||
long habitId = ContentUris.parseId(data);
|
long habitId = ContentUris.parseId(data);
|
||||||
Habit habit = Habit.get(habitId);
|
Habit habit = habitList.getById(habitId);
|
||||||
if (habit != null)
|
if (habit != null)
|
||||||
{
|
{
|
||||||
ToggleRepetitionCommand command = new ToggleRepetitionCommand(habit, timestamp);
|
ToggleRepetitionCommand command =
|
||||||
|
new ToggleRepetitionCommand(habit, timestamp);
|
||||||
commandRunner.execute(command, habitId);
|
commandRunner.execute(command, habitId);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -130,36 +190,26 @@ public class HabitBroadcastReceiver extends BroadcastReceiver
|
|||||||
sendRefreshBroadcast(context);
|
sendRefreshBroadcast(context);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void sendRefreshBroadcast(Context context)
|
private boolean checkWeekday(Intent intent, Habit habit)
|
||||||
{
|
{
|
||||||
LocalBroadcastManager manager = LocalBroadcastManager.getInstance(context);
|
Long timestamp =
|
||||||
Intent refreshIntent = new Intent(HabitsApplication.ACTION_REFRESH);
|
intent.getLongExtra("timestamp", DateUtils.getStartOfToday());
|
||||||
manager.sendBroadcast(refreshIntent);
|
|
||||||
|
|
||||||
WidgetManager.updateWidgets(context);
|
boolean reminderDays[] =
|
||||||
|
DateUtils.unpackWeekdayList(habit.getReminderDays());
|
||||||
|
int weekday = DateUtils.getWeekday(timestamp);
|
||||||
|
|
||||||
|
return reminderDays[weekday];
|
||||||
}
|
}
|
||||||
|
|
||||||
private void dismissAllHabits()
|
|
||||||
{
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
private void dismissNotification(Context context, Long habitId)
|
|
||||||
{
|
|
||||||
NotificationManager notificationManager =
|
|
||||||
(NotificationManager) context.getSystemService(Activity.NOTIFICATION_SERVICE);
|
|
||||||
|
|
||||||
int notificationId = (int) (habitId % Integer.MAX_VALUE);
|
|
||||||
notificationManager.cancel(notificationId);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
private void createNotification(final Context context, final Intent intent)
|
private void createNotification(final Context context, final Intent intent)
|
||||||
{
|
{
|
||||||
final Uri data = intent.getData();
|
final Uri data = intent.getData();
|
||||||
final Habit habit = Habit.get(ContentUris.parseId(data));
|
final Habit habit = habitList.getById(ContentUris.parseId(data));
|
||||||
final Long timestamp = intent.getLongExtra("timestamp", DateUtils.getStartOfToday());
|
final Long timestamp =
|
||||||
final Long reminderTime = intent.getLongExtra("reminderTime", DateUtils.getStartOfToday());
|
intent.getLongExtra("timestamp", DateUtils.getStartOfToday());
|
||||||
|
final Long reminderTime =
|
||||||
|
intent.getLongExtra("reminderTime", DateUtils.getStartOfToday());
|
||||||
|
|
||||||
if (habit == null) return;
|
if (habit == null) return;
|
||||||
|
|
||||||
@@ -170,7 +220,7 @@ public class HabitBroadcastReceiver extends BroadcastReceiver
|
|||||||
@Override
|
@Override
|
||||||
protected void doInBackground()
|
protected void doInBackground()
|
||||||
{
|
{
|
||||||
todayValue = habit.checkmarks.getTodayValue();
|
todayValue = habit.getCheckmarks().getTodayValue();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -185,9 +235,12 @@ public class HabitBroadcastReceiver extends BroadcastReceiver
|
|||||||
PendingIntent contentPendingIntent =
|
PendingIntent contentPendingIntent =
|
||||||
PendingIntent.getActivity(context, 0, contentIntent, 0);
|
PendingIntent.getActivity(context, 0, contentIntent, 0);
|
||||||
|
|
||||||
PendingIntent dismissPendingIntent = buildDismissIntent(context);
|
PendingIntent dismissPendingIntent =
|
||||||
PendingIntent checkIntentPending = buildCheckIntent(context, habit, timestamp);
|
buildDismissIntent(context);
|
||||||
PendingIntent snoozeIntentPending = buildSnoozeIntent(context, habit);
|
PendingIntent checkIntentPending =
|
||||||
|
buildCheckIntent(context, habit, timestamp);
|
||||||
|
PendingIntent snoozeIntentPending =
|
||||||
|
buildSnoozeIntent(context, habit);
|
||||||
|
|
||||||
Uri ringtoneUri = ReminderUtils.getRingtoneUri(context);
|
Uri ringtoneUri = ReminderUtils.getRingtoneUri(context);
|
||||||
|
|
||||||
@@ -199,14 +252,16 @@ public class HabitBroadcastReceiver extends BroadcastReceiver
|
|||||||
Notification notification =
|
Notification notification =
|
||||||
new NotificationCompat.Builder(context)
|
new NotificationCompat.Builder(context)
|
||||||
.setSmallIcon(R.drawable.ic_notification)
|
.setSmallIcon(R.drawable.ic_notification)
|
||||||
.setContentTitle(habit.name)
|
.setContentTitle(habit.getName())
|
||||||
.setContentText(habit.description)
|
.setContentText(habit.getDescription())
|
||||||
.setContentIntent(contentPendingIntent)
|
.setContentIntent(contentPendingIntent)
|
||||||
.setDeleteIntent(dismissPendingIntent)
|
.setDeleteIntent(dismissPendingIntent)
|
||||||
.addAction(R.drawable.ic_action_check,
|
.addAction(R.drawable.ic_action_check,
|
||||||
context.getString(R.string.check), checkIntentPending)
|
context.getString(R.string.check),
|
||||||
|
checkIntentPending)
|
||||||
.addAction(R.drawable.ic_action_snooze,
|
.addAction(R.drawable.ic_action_snooze,
|
||||||
context.getString(R.string.snooze), snoozeIntentPending)
|
context.getString(R.string.snooze),
|
||||||
|
snoozeIntentPending)
|
||||||
.setSound(ringtoneUri)
|
.setSound(ringtoneUri)
|
||||||
.extend(wearableExtender)
|
.extend(wearableExtender)
|
||||||
.setWhen(reminderTime)
|
.setWhen(reminderTime)
|
||||||
@@ -227,59 +282,39 @@ public class HabitBroadcastReceiver extends BroadcastReceiver
|
|||||||
}.execute();
|
}.execute();
|
||||||
}
|
}
|
||||||
|
|
||||||
public static PendingIntent buildSnoozeIntent(Context context, Habit habit)
|
private void createReminderAlarmsDelayed(final Context context)
|
||||||
{
|
{
|
||||||
Uri data = habit.getUri();
|
new Handler().postDelayed(
|
||||||
Intent snoozeIntent = new Intent(context, HabitBroadcastReceiver.class);
|
() -> ReminderUtils.createReminderAlarms(context, habitList), 5000);
|
||||||
snoozeIntent.setData(data);
|
|
||||||
snoozeIntent.setAction(ACTION_SNOOZE);
|
|
||||||
return PendingIntent.getBroadcast(context, 0, snoozeIntent, 0);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public static PendingIntent buildCheckIntent(Context context, Habit habit, Long timestamp)
|
private void dismissAllHabits()
|
||||||
{
|
{
|
||||||
Uri data = habit.getUri();
|
|
||||||
Intent checkIntent = new Intent(context, HabitBroadcastReceiver.class);
|
|
||||||
checkIntent.setData(data);
|
|
||||||
checkIntent.setAction(ACTION_CHECK);
|
|
||||||
if(timestamp != null) checkIntent.putExtra("timestamp", timestamp);
|
|
||||||
return PendingIntent.getBroadcast(context, 0, checkIntent, PendingIntent.FLAG_ONE_SHOT);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public static PendingIntent buildDismissIntent(Context context)
|
private void dismissNotification(Context context, Long habitId)
|
||||||
{
|
|
||||||
Intent deleteIntent = new Intent(context, HabitBroadcastReceiver.class);
|
|
||||||
deleteIntent.setAction(ACTION_DISMISS);
|
|
||||||
return PendingIntent.getBroadcast(context, 0, deleteIntent, 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static PendingIntent buildViewHabitIntent(Context context, Habit habit)
|
|
||||||
{
|
|
||||||
Intent intent = new Intent(context, ShowHabitActivity.class);
|
|
||||||
intent.setData(Uri.parse("content://org.isoron.uhabits/habit/" + habit.getId()));
|
|
||||||
|
|
||||||
return TaskStackBuilder.create(context.getApplicationContext())
|
|
||||||
.addNextIntentWithParentStack(intent)
|
|
||||||
.getPendingIntent(0, PendingIntent.FLAG_UPDATE_CURRENT);
|
|
||||||
}
|
|
||||||
|
|
||||||
private boolean checkWeekday(Intent intent, Habit habit)
|
|
||||||
{
|
|
||||||
Long timestamp = intent.getLongExtra("timestamp", DateUtils.getStartOfToday());
|
|
||||||
|
|
||||||
boolean reminderDays[] = DateUtils.unpackWeekdayList(habit.reminderDays);
|
|
||||||
int weekday = DateUtils.getWeekday(timestamp);
|
|
||||||
|
|
||||||
return reminderDays[weekday];
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void dismissNotification(Context context, Habit habit)
|
|
||||||
{
|
{
|
||||||
NotificationManager notificationManager =
|
NotificationManager notificationManager =
|
||||||
(NotificationManager) context.getSystemService(
|
(NotificationManager) context.getSystemService(
|
||||||
Activity.NOTIFICATION_SERVICE);
|
Activity.NOTIFICATION_SERVICE);
|
||||||
|
|
||||||
int notificationId = (int) (habit.getId() % Integer.MAX_VALUE);
|
int notificationId = (int) (habitId % Integer.MAX_VALUE);
|
||||||
notificationManager.cancel(notificationId);
|
notificationManager.cancel(notificationId);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void snoozeHabit(Context context, Intent intent)
|
||||||
|
{
|
||||||
|
Uri data = intent.getData();
|
||||||
|
SharedPreferences prefs =
|
||||||
|
PreferenceManager.getDefaultSharedPreferences(context);
|
||||||
|
long delayMinutes =
|
||||||
|
Long.parseLong(prefs.getString("pref_snooze_interval", "15"));
|
||||||
|
|
||||||
|
long habitId = ContentUris.parseId(data);
|
||||||
|
Habit habit = habitList.getById(habitId);
|
||||||
|
if (habit != null) ReminderUtils.createReminderAlarm(context, habit,
|
||||||
|
new Date().getTime() + delayMinutes * 60 * 1000);
|
||||||
|
dismissNotification(context, habitId);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -25,37 +25,53 @@ import android.support.annotation.Nullable;
|
|||||||
|
|
||||||
import com.activeandroid.ActiveAndroid;
|
import com.activeandroid.ActiveAndroid;
|
||||||
|
|
||||||
|
import org.isoron.uhabits.models.HabitList;
|
||||||
import org.isoron.uhabits.utils.DatabaseUtils;
|
import org.isoron.uhabits.utils.DatabaseUtils;
|
||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
|
|
||||||
|
import javax.inject.Inject;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The Android application for Loop Habit Tracker.
|
||||||
|
*/
|
||||||
public class HabitsApplication extends Application
|
public class HabitsApplication extends Application
|
||||||
{
|
{
|
||||||
public static final String ACTION_REFRESH = "org.isoron.uhabits.ACTION_REFRESH";
|
public static final String ACTION_REFRESH =
|
||||||
public static final int RESULT_IMPORT_DATA = 1;
|
"org.isoron.uhabits.ACTION_REFRESH";
|
||||||
public static final int RESULT_EXPORT_CSV = 2;
|
|
||||||
public static final int RESULT_EXPORT_DB = 3;
|
|
||||||
public static final int RESULT_BUG_REPORT = 4;
|
public static final int RESULT_BUG_REPORT = 4;
|
||||||
|
|
||||||
|
public static final int RESULT_EXPORT_CSV = 2;
|
||||||
|
|
||||||
|
public static final int RESULT_EXPORT_DB = 3;
|
||||||
|
|
||||||
|
public static final int RESULT_IMPORT_DATA = 1;
|
||||||
|
|
||||||
@Nullable
|
@Nullable
|
||||||
private static HabitsApplication application;
|
private static HabitsApplication application;
|
||||||
|
|
||||||
@Nullable
|
|
||||||
private static Context context;
|
|
||||||
private static BaseComponent component;
|
private static BaseComponent component;
|
||||||
|
|
||||||
public static boolean isTestMode()
|
@Nullable
|
||||||
|
private static Context context;
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
HabitList habitList;
|
||||||
|
|
||||||
|
public static BaseComponent getComponent()
|
||||||
{
|
{
|
||||||
try
|
return component;
|
||||||
{
|
|
||||||
if(context != null)
|
|
||||||
context.getClassLoader().loadClass("org.isoron.uhabits.unit.models.HabitTest");
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
catch (final Exception e)
|
|
||||||
|
public HabitList getHabitList()
|
||||||
{
|
{
|
||||||
return false;
|
return habitList;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static void setComponent(BaseComponent component)
|
||||||
|
{
|
||||||
|
HabitsApplication.component = component;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Nullable
|
@Nullable
|
||||||
@@ -70,14 +86,19 @@ public class HabitsApplication extends Application
|
|||||||
return application;
|
return application;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static BaseComponent getComponent()
|
public static boolean isTestMode()
|
||||||
{
|
{
|
||||||
return component;
|
try
|
||||||
|
{
|
||||||
|
if (context != null) context
|
||||||
|
.getClassLoader()
|
||||||
|
.loadClass("org.isoron.uhabits.unit.models.HabitTest");
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
catch (final Exception e)
|
||||||
public static void setComponent(BaseComponent component)
|
|
||||||
{
|
{
|
||||||
HabitsApplication.component = component;
|
return false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -94,6 +115,7 @@ public class HabitsApplication extends Application
|
|||||||
if (db.exists()) db.delete();
|
if (db.exists()) db.delete();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
component.inject(this);
|
||||||
DatabaseUtils.initializeActiveAndroid();
|
DatabaseUtils.initializeActiveAndroid();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -23,6 +23,9 @@ import android.app.backup.BackupAgentHelper;
|
|||||||
import android.app.backup.FileBackupHelper;
|
import android.app.backup.FileBackupHelper;
|
||||||
import android.app.backup.SharedPreferencesBackupHelper;
|
import android.app.backup.SharedPreferencesBackupHelper;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An Android BackupAgentHelper customized for this application.
|
||||||
|
*/
|
||||||
public class HabitsBackupAgent extends BackupAgentHelper
|
public class HabitsBackupAgent extends BackupAgentHelper
|
||||||
{
|
{
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
@@ -21,6 +21,9 @@ package org.isoron.uhabits;
|
|||||||
|
|
||||||
import org.isoron.uhabits.ui.habits.list.ListHabitsActivity;
|
import org.isoron.uhabits.ui.habits.list.ListHabitsActivity;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Application that starts upon clicking the launcher icon.
|
||||||
|
*/
|
||||||
public class MainActivity extends ListHabitsActivity
|
public class MainActivity extends ListHabitsActivity
|
||||||
{
|
{
|
||||||
/*
|
/*
|
||||||
|
|||||||
@@ -19,38 +19,52 @@
|
|||||||
|
|
||||||
package org.isoron.uhabits.commands;
|
package org.isoron.uhabits.commands;
|
||||||
|
|
||||||
|
import org.isoron.uhabits.HabitsApplication;
|
||||||
import org.isoron.uhabits.R;
|
import org.isoron.uhabits.R;
|
||||||
import org.isoron.uhabits.models.Habit;
|
import org.isoron.uhabits.models.Habit;
|
||||||
|
import org.isoron.uhabits.models.HabitList;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
|
import javax.inject.Inject;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Command to archive a list of habits.
|
||||||
|
*/
|
||||||
public class ArchiveHabitsCommand extends Command
|
public class ArchiveHabitsCommand extends Command
|
||||||
{
|
{
|
||||||
|
@Inject
|
||||||
|
HabitList habitList;
|
||||||
|
|
||||||
private List<Habit> habits;
|
private List<Habit> habits;
|
||||||
|
|
||||||
public ArchiveHabitsCommand(List<Habit> habits)
|
public ArchiveHabitsCommand(List<Habit> habits)
|
||||||
{
|
{
|
||||||
|
HabitsApplication.getComponent().inject(this);
|
||||||
this.habits = habits;
|
this.habits = habits;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void execute()
|
public void execute()
|
||||||
{
|
{
|
||||||
Habit.archive(habits);
|
for(Habit h : habits) h.setArchived(1);
|
||||||
|
habitList.update(habits);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void undo()
|
public void undo()
|
||||||
{
|
{
|
||||||
Habit.unarchive(habits);
|
for(Habit h : habits) h.setArchived(0);
|
||||||
|
habitList.update(habits);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
public Integer getExecuteStringId()
|
public Integer getExecuteStringId()
|
||||||
{
|
{
|
||||||
return R.string.toast_habit_archived;
|
return R.string.toast_habit_archived;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
public Integer getUndoStringId()
|
public Integer getUndoStringId()
|
||||||
{
|
{
|
||||||
return R.string.toast_habit_unarchived;
|
return R.string.toast_habit_unarchived;
|
||||||
|
|||||||
@@ -19,60 +19,65 @@
|
|||||||
|
|
||||||
package org.isoron.uhabits.commands;
|
package org.isoron.uhabits.commands;
|
||||||
|
|
||||||
|
import org.isoron.uhabits.HabitsApplication;
|
||||||
import org.isoron.uhabits.R;
|
import org.isoron.uhabits.R;
|
||||||
import org.isoron.uhabits.utils.DatabaseUtils;
|
|
||||||
import org.isoron.uhabits.models.Habit;
|
import org.isoron.uhabits.models.Habit;
|
||||||
|
import org.isoron.uhabits.models.HabitList;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
|
import javax.inject.Inject;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Command to change the color of a list of habits.
|
||||||
|
*/
|
||||||
public class ChangeHabitColorCommand extends Command
|
public class ChangeHabitColorCommand extends Command
|
||||||
{
|
{
|
||||||
|
@Inject
|
||||||
|
HabitList habitList;
|
||||||
|
|
||||||
List<Habit> habits;
|
List<Habit> habits;
|
||||||
|
|
||||||
List<Integer> originalColors;
|
List<Integer> originalColors;
|
||||||
|
|
||||||
Integer newColor;
|
Integer newColor;
|
||||||
|
|
||||||
public ChangeHabitColorCommand(List<Habit> habits, Integer newColor)
|
public ChangeHabitColorCommand(List<Habit> habits, Integer newColor)
|
||||||
{
|
{
|
||||||
|
HabitsApplication.getComponent().inject(this);
|
||||||
|
|
||||||
this.habits = habits;
|
this.habits = habits;
|
||||||
this.newColor = newColor;
|
this.newColor = newColor;
|
||||||
this.originalColors = new ArrayList<>(habits.size());
|
this.originalColors = new ArrayList<>(habits.size());
|
||||||
|
|
||||||
for(Habit h : habits)
|
for (Habit h : habits) originalColors.add(h.getColor());
|
||||||
originalColors.add(h.color);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void execute()
|
public void execute()
|
||||||
{
|
{
|
||||||
Habit.setColor(habits, newColor);
|
for(Habit h : habits) h.setColor(newColor);
|
||||||
|
habitList.update(habits);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void undo()
|
|
||||||
{
|
|
||||||
DatabaseUtils.executeAsTransaction(new DatabaseUtils.Command()
|
|
||||||
{
|
|
||||||
@Override
|
|
||||||
public void execute()
|
|
||||||
{
|
|
||||||
int k = 0;
|
|
||||||
for(Habit h : habits)
|
|
||||||
{
|
|
||||||
h.color = originalColors.get(k++);
|
|
||||||
h.save();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
public Integer getExecuteStringId()
|
public Integer getExecuteStringId()
|
||||||
{
|
{
|
||||||
return R.string.toast_habit_changed;
|
return R.string.toast_habit_changed;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
public Integer getUndoStringId()
|
public Integer getUndoStringId()
|
||||||
{
|
{
|
||||||
return R.string.toast_habit_changed;
|
return R.string.toast_habit_changed;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void undo()
|
||||||
|
{
|
||||||
|
int k = 0;
|
||||||
|
for (Habit h : habits) h.setColor(originalColors.get(k++));
|
||||||
|
habitList.update(habits);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -19,12 +19,19 @@
|
|||||||
|
|
||||||
package org.isoron.uhabits.commands;
|
package org.isoron.uhabits.commands;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A Command represents a desired set of changes that should be performed on the
|
||||||
|
* models.
|
||||||
|
* <p>
|
||||||
|
* A command can be executed and undone. Each of these operations also provide
|
||||||
|
* an string that should be displayed to the user upon their completion.
|
||||||
|
* <p>
|
||||||
|
* In general, commands should always be executed by a {@link CommandRunner}.
|
||||||
|
*/
|
||||||
public abstract class Command
|
public abstract class Command
|
||||||
{
|
{
|
||||||
public abstract void execute();
|
public abstract void execute();
|
||||||
|
|
||||||
public abstract void undo();
|
|
||||||
|
|
||||||
public Integer getExecuteStringId()
|
public Integer getExecuteStringId()
|
||||||
{
|
{
|
||||||
return null;
|
return null;
|
||||||
@@ -34,4 +41,6 @@ public abstract class Command
|
|||||||
{
|
{
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public abstract void undo();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -26,6 +26,12 @@ import org.isoron.uhabits.tasks.BaseTask;
|
|||||||
|
|
||||||
import java.util.LinkedList;
|
import java.util.LinkedList;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A CommandRunner executes and undoes commands.
|
||||||
|
* <p>
|
||||||
|
* CommandRunners also allows objects to subscribe to it, and receive events
|
||||||
|
* whenever a command is performed.
|
||||||
|
*/
|
||||||
public class CommandRunner
|
public class CommandRunner
|
||||||
{
|
{
|
||||||
private LinkedList<Listener> listeners;
|
private LinkedList<Listener> listeners;
|
||||||
@@ -71,6 +77,10 @@ public class CommandRunner
|
|||||||
listeners.remove(l);
|
listeners.remove(l);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Interface implemented by objects that want to receive an event whenever a
|
||||||
|
* command is executed.
|
||||||
|
*/
|
||||||
public interface Listener
|
public interface Listener
|
||||||
{
|
{
|
||||||
void onCommandExecuted(@NonNull Command command,
|
void onCommandExecuted(@NonNull Command command,
|
||||||
|
|||||||
@@ -19,41 +19,48 @@
|
|||||||
|
|
||||||
package org.isoron.uhabits.commands;
|
package org.isoron.uhabits.commands;
|
||||||
|
|
||||||
|
import org.isoron.uhabits.HabitsApplication;
|
||||||
import org.isoron.uhabits.R;
|
import org.isoron.uhabits.R;
|
||||||
import org.isoron.uhabits.models.Habit;
|
import org.isoron.uhabits.models.Habit;
|
||||||
|
import org.isoron.uhabits.models.HabitList;
|
||||||
|
|
||||||
|
import javax.inject.Inject;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Command to create a habit.
|
||||||
|
*/
|
||||||
public class CreateHabitCommand extends Command
|
public class CreateHabitCommand extends Command
|
||||||
{
|
{
|
||||||
|
@Inject
|
||||||
|
HabitList habitList;
|
||||||
|
|
||||||
private Habit model;
|
private Habit model;
|
||||||
private Long savedId;
|
private Long savedId;
|
||||||
|
|
||||||
public CreateHabitCommand(Habit model)
|
public CreateHabitCommand(Habit model)
|
||||||
{
|
{
|
||||||
this.model = model;
|
this.model = model;
|
||||||
|
HabitsApplication.getComponent().inject(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void execute()
|
public void execute()
|
||||||
{
|
{
|
||||||
Habit savedHabit = new Habit(model);
|
Habit savedHabit = new Habit();
|
||||||
if (savedId == null)
|
savedHabit.copyFrom(model);
|
||||||
{
|
savedHabit.setId(savedId);
|
||||||
savedHabit.save();
|
|
||||||
|
habitList.add(savedHabit);
|
||||||
savedId = savedHabit.getId();
|
savedId = savedHabit.getId();
|
||||||
}
|
}
|
||||||
else
|
|
||||||
{
|
|
||||||
savedHabit.save(savedId);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void undo()
|
public void undo()
|
||||||
{
|
{
|
||||||
Habit habit = Habit.get(savedId);
|
Habit habit = habitList.getById(savedId);
|
||||||
if(habit == null) throw new RuntimeException("Habit not found");
|
if(habit == null) throw new RuntimeException("Habit not found");
|
||||||
|
|
||||||
habit.cascadeDelete();
|
habitList.remove(habit);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
@@ -19,27 +19,36 @@
|
|||||||
|
|
||||||
package org.isoron.uhabits.commands;
|
package org.isoron.uhabits.commands;
|
||||||
|
|
||||||
|
import org.isoron.uhabits.HabitsApplication;
|
||||||
import org.isoron.uhabits.R;
|
import org.isoron.uhabits.R;
|
||||||
import org.isoron.uhabits.models.Habit;
|
import org.isoron.uhabits.models.Habit;
|
||||||
|
import org.isoron.uhabits.models.HabitList;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
|
import javax.inject.Inject;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Command to delete a list of habits.
|
||||||
|
*/
|
||||||
public class DeleteHabitsCommand extends Command
|
public class DeleteHabitsCommand extends Command
|
||||||
{
|
{
|
||||||
|
@Inject
|
||||||
|
HabitList habitList;
|
||||||
|
|
||||||
private List<Habit> habits;
|
private List<Habit> habits;
|
||||||
|
|
||||||
public DeleteHabitsCommand(List<Habit> habits)
|
public DeleteHabitsCommand(List<Habit> habits)
|
||||||
{
|
{
|
||||||
this.habits = habits;
|
this.habits = habits;
|
||||||
|
HabitsApplication.getComponent().inject(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void execute()
|
public void execute()
|
||||||
{
|
{
|
||||||
for(Habit h : habits)
|
for(Habit h : habits)
|
||||||
h.cascadeDelete();
|
habitList.remove(h);
|
||||||
|
|
||||||
Habit.rebuildOrder();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -48,11 +57,13 @@ public class DeleteHabitsCommand extends Command
|
|||||||
throw new UnsupportedOperationException();
|
throw new UnsupportedOperationException();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
public Integer getExecuteStringId()
|
public Integer getExecuteStringId()
|
||||||
{
|
{
|
||||||
return R.string.toast_habit_deleted;
|
return R.string.toast_habit_deleted;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
public Integer getUndoStringId()
|
public Integer getUndoStringId()
|
||||||
{
|
{
|
||||||
return R.string.toast_habit_restored;
|
return R.string.toast_habit_restored;
|
||||||
|
|||||||
@@ -19,24 +19,43 @@
|
|||||||
|
|
||||||
package org.isoron.uhabits.commands;
|
package org.isoron.uhabits.commands;
|
||||||
|
|
||||||
|
import org.isoron.uhabits.HabitsApplication;
|
||||||
import org.isoron.uhabits.R;
|
import org.isoron.uhabits.R;
|
||||||
import org.isoron.uhabits.models.Habit;
|
import org.isoron.uhabits.models.Habit;
|
||||||
|
import org.isoron.uhabits.models.HabitList;
|
||||||
|
|
||||||
|
import javax.inject.Inject;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Command to modify a habit.
|
||||||
|
*/
|
||||||
public class EditHabitCommand extends Command
|
public class EditHabitCommand extends Command
|
||||||
{
|
{
|
||||||
|
@Inject
|
||||||
|
HabitList habitList;
|
||||||
|
|
||||||
private Habit original;
|
private Habit original;
|
||||||
|
|
||||||
private Habit modified;
|
private Habit modified;
|
||||||
|
|
||||||
private long savedId;
|
private long savedId;
|
||||||
|
|
||||||
private boolean hasIntervalChanged;
|
private boolean hasIntervalChanged;
|
||||||
|
|
||||||
public EditHabitCommand(Habit original, Habit modified)
|
public EditHabitCommand(Habit original, Habit modified)
|
||||||
{
|
{
|
||||||
this.savedId = original.getId();
|
HabitsApplication.getComponent().inject(this);
|
||||||
this.modified = new Habit(modified);
|
|
||||||
this.original = new Habit(original);
|
|
||||||
|
|
||||||
hasIntervalChanged = (!this.original.freqDen.equals(this.modified.freqDen) ||
|
this.savedId = original.getId();
|
||||||
!this.original.freqNum.equals(this.modified.freqNum));
|
this.modified = new Habit();
|
||||||
|
this.original = new Habit();
|
||||||
|
|
||||||
|
this.modified.copyFrom(modified);
|
||||||
|
this.original.copyFrom(original);
|
||||||
|
|
||||||
|
hasIntervalChanged =
|
||||||
|
(!this.original.getFreqDen().equals(this.modified.getFreqDen()) ||
|
||||||
|
!this.original.getFreqNum().equals(this.modified.getFreqNum()));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -45,6 +64,18 @@ public class EditHabitCommand extends Command
|
|||||||
copyAttributes(this.modified);
|
copyAttributes(this.modified);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Integer getExecuteStringId()
|
||||||
|
{
|
||||||
|
return R.string.toast_habit_changed;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Integer getUndoStringId()
|
||||||
|
{
|
||||||
|
return R.string.toast_habit_changed_back;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void undo()
|
public void undo()
|
||||||
{
|
{
|
||||||
@@ -53,11 +84,11 @@ public class EditHabitCommand extends Command
|
|||||||
|
|
||||||
private void copyAttributes(Habit model)
|
private void copyAttributes(Habit model)
|
||||||
{
|
{
|
||||||
Habit habit = Habit.get(savedId);
|
Habit habit = habitList.getById(savedId);
|
||||||
if (habit == null) throw new RuntimeException("Habit not found");
|
if (habit == null) throw new RuntimeException("Habit not found");
|
||||||
|
|
||||||
habit.copyAttributes(model);
|
habit.copyFrom(model);
|
||||||
habit.save();
|
habitList.update(habit);
|
||||||
|
|
||||||
invalidateIfNeeded(habit);
|
invalidateIfNeeded(habit);
|
||||||
}
|
}
|
||||||
@@ -66,19 +97,9 @@ public class EditHabitCommand extends Command
|
|||||||
{
|
{
|
||||||
if (hasIntervalChanged)
|
if (hasIntervalChanged)
|
||||||
{
|
{
|
||||||
habit.checkmarks.deleteNewerThan(0);
|
habit.getCheckmarks().invalidateNewerThan(0);
|
||||||
habit.streaks.deleteNewerThan(0);
|
habit.getStreaks().invalidateNewerThan(0);
|
||||||
habit.scores.invalidateNewerThan(0);
|
habit.getScores().invalidateNewerThan(0);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public Integer getExecuteStringId()
|
|
||||||
{
|
|
||||||
return R.string.toast_habit_changed;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Integer getUndoStringId()
|
|
||||||
{
|
|
||||||
return R.string.toast_habit_changed_back;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
@@ -21,6 +21,9 @@ package org.isoron.uhabits.commands;
|
|||||||
|
|
||||||
import org.isoron.uhabits.models.Habit;
|
import org.isoron.uhabits.models.Habit;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Command to toggle a repetition.
|
||||||
|
*/
|
||||||
public class ToggleRepetitionCommand extends Command
|
public class ToggleRepetitionCommand extends Command
|
||||||
{
|
{
|
||||||
private Long offset;
|
private Long offset;
|
||||||
@@ -35,7 +38,7 @@ public class ToggleRepetitionCommand extends Command
|
|||||||
@Override
|
@Override
|
||||||
public void execute()
|
public void execute()
|
||||||
{
|
{
|
||||||
habit.repetitions.toggle(offset);
|
habit.getRepetitions().toggleTimestamp(offset);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
@@ -19,38 +19,52 @@
|
|||||||
|
|
||||||
package org.isoron.uhabits.commands;
|
package org.isoron.uhabits.commands;
|
||||||
|
|
||||||
|
import org.isoron.uhabits.HabitsApplication;
|
||||||
import org.isoron.uhabits.R;
|
import org.isoron.uhabits.R;
|
||||||
import org.isoron.uhabits.models.Habit;
|
import org.isoron.uhabits.models.Habit;
|
||||||
|
import org.isoron.uhabits.models.HabitList;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
|
import javax.inject.Inject;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Command to unarchive a list of habits.
|
||||||
|
*/
|
||||||
public class UnarchiveHabitsCommand extends Command
|
public class UnarchiveHabitsCommand extends Command
|
||||||
{
|
{
|
||||||
|
@Inject
|
||||||
|
HabitList habitList;
|
||||||
|
|
||||||
private List<Habit> habits;
|
private List<Habit> habits;
|
||||||
|
|
||||||
public UnarchiveHabitsCommand(List<Habit> habits)
|
public UnarchiveHabitsCommand(List<Habit> habits)
|
||||||
{
|
{
|
||||||
this.habits = habits;
|
this.habits = habits;
|
||||||
|
HabitsApplication.getComponent().inject(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void execute()
|
public void execute()
|
||||||
{
|
{
|
||||||
Habit.unarchive(habits);
|
for(Habit h : habits) h.setArchived(0);
|
||||||
|
habitList.update(habits);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void undo()
|
public void undo()
|
||||||
{
|
{
|
||||||
Habit.archive(habits);
|
for(Habit h : habits) h.setArchived(1);
|
||||||
|
habitList.update(habits);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
public Integer getExecuteStringId()
|
public Integer getExecuteStringId()
|
||||||
{
|
{
|
||||||
return R.string.toast_habit_unarchived;
|
return R.string.toast_habit_unarchived;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
public Integer getUndoStringId()
|
public Integer getUndoStringId()
|
||||||
{
|
{
|
||||||
return R.string.toast_habit_archived;
|
return R.string.toast_habit_archived;
|
||||||
|
|||||||
@@ -0,0 +1,24 @@
|
|||||||
|
/*
|
||||||
|
* 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/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Provides commands to modify the models, such as {@link
|
||||||
|
* org.isoron.uhabits.commands.CreateHabitCommand}.
|
||||||
|
*/
|
||||||
|
package org.isoron.uhabits.commands;
|
||||||
@@ -21,13 +21,30 @@ package org.isoron.uhabits.io;
|
|||||||
|
|
||||||
import android.support.annotation.NonNull;
|
import android.support.annotation.NonNull;
|
||||||
|
|
||||||
|
import org.isoron.uhabits.HabitsApplication;
|
||||||
|
import org.isoron.uhabits.models.HabitList;
|
||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.io.FileInputStream;
|
import java.io.FileInputStream;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
|
|
||||||
|
import javax.inject.Inject;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* AbstractImporter is the base class for all classes that import data from
|
||||||
|
* files into the app.
|
||||||
|
*/
|
||||||
public abstract class AbstractImporter
|
public abstract class AbstractImporter
|
||||||
{
|
{
|
||||||
|
@Inject
|
||||||
|
HabitList habitList;
|
||||||
|
|
||||||
|
public AbstractImporter()
|
||||||
|
{
|
||||||
|
HabitsApplication.getComponent().inject(this);
|
||||||
|
}
|
||||||
|
|
||||||
public abstract boolean canHandle(@NonNull File file) throws IOException;
|
public abstract boolean canHandle(@NonNull File file) throws IOException;
|
||||||
|
|
||||||
public abstract void importHabitsFromFile(@NonNull File file) throws IOException;
|
public abstract void importHabitsFromFile(@NonNull File file) throws IOException;
|
||||||
|
|||||||
@@ -26,6 +26,10 @@ import java.io.IOException;
|
|||||||
import java.util.LinkedList;
|
import java.util.LinkedList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A GenericImporter decides which implementation of AbstractImporter is able to
|
||||||
|
* handle a given file and delegates to it the task of importing the data.
|
||||||
|
*/
|
||||||
public class GenericImporter extends AbstractImporter
|
public class GenericImporter extends AbstractImporter
|
||||||
{
|
{
|
||||||
List<AbstractImporter> importers;
|
List<AbstractImporter> importers;
|
||||||
@@ -52,7 +56,6 @@ public class GenericImporter extends AbstractImporter
|
|||||||
public void importHabitsFromFile(@NonNull File file) throws IOException
|
public void importHabitsFromFile(@NonNull File file) throws IOException
|
||||||
{
|
{
|
||||||
for (AbstractImporter importer : importers)
|
for (AbstractImporter importer : importers)
|
||||||
if(importer.canHandle(file))
|
if (importer.canHandle(file)) importer.importHabitsFromFile(file);
|
||||||
importer.importHabitsFromFile(file);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -24,8 +24,8 @@ import android.support.annotation.NonNull;
|
|||||||
import com.activeandroid.ActiveAndroid;
|
import com.activeandroid.ActiveAndroid;
|
||||||
import com.opencsv.CSVReader;
|
import com.opencsv.CSVReader;
|
||||||
|
|
||||||
import org.isoron.uhabits.utils.DateUtils;
|
|
||||||
import org.isoron.uhabits.models.Habit;
|
import org.isoron.uhabits.models.Habit;
|
||||||
|
import org.isoron.uhabits.utils.DateUtils;
|
||||||
|
|
||||||
import java.io.BufferedReader;
|
import java.io.BufferedReader;
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
@@ -34,6 +34,9 @@ import java.io.IOException;
|
|||||||
import java.util.Calendar;
|
import java.util.Calendar;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Class that imports data from HabitBull CSV files.
|
||||||
|
*/
|
||||||
public class HabitBullCSVImporter extends AbstractImporter
|
public class HabitBullCSVImporter extends AbstractImporter
|
||||||
{
|
{
|
||||||
@Override
|
@Override
|
||||||
@@ -89,16 +92,16 @@ public class HabitBullCSVImporter extends AbstractImporter
|
|||||||
if(h == null)
|
if(h == null)
|
||||||
{
|
{
|
||||||
h = new Habit();
|
h = new Habit();
|
||||||
h.name = name;
|
h.setName(name);
|
||||||
h.description = description;
|
h.setDescription(description);
|
||||||
h.freqNum = h.freqDen = 1;
|
h.setFreqDen(1);
|
||||||
h.save();
|
h.setFreqNum(1);
|
||||||
|
habitList.add(h);
|
||||||
habits.put(name, h);
|
habits.put(name, h);
|
||||||
}
|
}
|
||||||
|
|
||||||
if(!h.repetitions.contains(timestamp))
|
if(!h.getRepetitions().containsTimestamp(timestamp))
|
||||||
h.repetitions.toggle(timestamp);
|
h.getRepetitions().toggleTimestamp(timestamp);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -21,8 +21,10 @@ package org.isoron.uhabits.io;
|
|||||||
|
|
||||||
import android.support.annotation.NonNull;
|
import android.support.annotation.NonNull;
|
||||||
|
|
||||||
|
import org.isoron.uhabits.HabitsApplication;
|
||||||
import org.isoron.uhabits.models.CheckmarkList;
|
import org.isoron.uhabits.models.CheckmarkList;
|
||||||
import org.isoron.uhabits.models.Habit;
|
import org.isoron.uhabits.models.Habit;
|
||||||
|
import org.isoron.uhabits.models.HabitList;
|
||||||
import org.isoron.uhabits.models.ScoreList;
|
import org.isoron.uhabits.models.ScoreList;
|
||||||
import org.isoron.uhabits.utils.DateUtils;
|
import org.isoron.uhabits.utils.DateUtils;
|
||||||
|
|
||||||
@@ -37,6 +39,11 @@ import java.util.List;
|
|||||||
import java.util.zip.ZipEntry;
|
import java.util.zip.ZipEntry;
|
||||||
import java.util.zip.ZipOutputStream;
|
import java.util.zip.ZipOutputStream;
|
||||||
|
|
||||||
|
import javax.inject.Inject;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Class that exports the application data to CSV files.
|
||||||
|
*/
|
||||||
public class HabitsCSVExporter
|
public class HabitsCSVExporter
|
||||||
{
|
{
|
||||||
private List<Habit> habits;
|
private List<Habit> habits;
|
||||||
@@ -46,8 +53,13 @@ public class HabitsCSVExporter
|
|||||||
|
|
||||||
private String exportDirName;
|
private String exportDirName;
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
HabitList habitList;
|
||||||
|
|
||||||
public HabitsCSVExporter(List<Habit> habits, File dir)
|
public HabitsCSVExporter(List<Habit> habits, File dir)
|
||||||
{
|
{
|
||||||
|
HabitsApplication.getComponent().inject(this);
|
||||||
|
|
||||||
this.habits = habits;
|
this.habits = habits;
|
||||||
this.exportDirName = dir.getAbsolutePath() + "/";
|
this.exportDirName = dir.getAbsolutePath() + "/";
|
||||||
|
|
||||||
@@ -61,20 +73,20 @@ public class HabitsCSVExporter
|
|||||||
new File(exportDirName).mkdirs();
|
new File(exportDirName).mkdirs();
|
||||||
FileWriter out = new FileWriter(exportDirName + filename);
|
FileWriter out = new FileWriter(exportDirName + filename);
|
||||||
generateFilenames.add(filename);
|
generateFilenames.add(filename);
|
||||||
Habit.writeCSV(habits, out);
|
habitList.writeCSV(out);
|
||||||
out.close();
|
out.close();
|
||||||
|
|
||||||
for(Habit h : habits)
|
for(Habit h : habits)
|
||||||
{
|
{
|
||||||
String sane = sanitizeFilename(h.name);
|
String sane = sanitizeFilename(h.getName());
|
||||||
String habitDirName = String.format("%03d %s", h.position + 1, sane);
|
String habitDirName = String.format("%03d %s", habitList.indexOf(h) + 1, sane);
|
||||||
habitDirName = habitDirName.trim() + "/";
|
habitDirName = habitDirName.trim() + "/";
|
||||||
|
|
||||||
new File(exportDirName + habitDirName).mkdirs();
|
new File(exportDirName + habitDirName).mkdirs();
|
||||||
generateDirs.add(habitDirName);
|
generateDirs.add(habitDirName);
|
||||||
|
|
||||||
writeScores(habitDirName, h.scores);
|
writeScores(habitDirName, h.getScores());
|
||||||
writeCheckmarks(habitDirName, h.checkmarks);
|
writeCheckmarks(habitDirName, h.getCheckmarks());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -31,6 +31,9 @@ import org.isoron.uhabits.utils.FileUtils;
|
|||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Class that imports data from database files exported by Loop Habit Tracker.
|
||||||
|
*/
|
||||||
public class LoopDBImporter extends AbstractImporter
|
public class LoopDBImporter extends AbstractImporter
|
||||||
{
|
{
|
||||||
@Override
|
@Override
|
||||||
@@ -41,7 +44,8 @@ public class LoopDBImporter extends AbstractImporter
|
|||||||
SQLiteDatabase db = SQLiteDatabase.openDatabase(file.getPath(), null,
|
SQLiteDatabase db = SQLiteDatabase.openDatabase(file.getPath(), null,
|
||||||
SQLiteDatabase.OPEN_READONLY);
|
SQLiteDatabase.OPEN_READONLY);
|
||||||
|
|
||||||
Cursor c = db.rawQuery("select count(*) from SQLITE_MASTER where name=? or name=?",
|
Cursor c = db.rawQuery(
|
||||||
|
"select count(*) from SQLITE_MASTER where name=? or name=?",
|
||||||
new String[]{"Checkmarks", "Repetitions"});
|
new String[]{"Checkmarks", "Repetitions"});
|
||||||
|
|
||||||
boolean result = (c.moveToFirst() && c.getInt(0) == 2);
|
boolean result = (c.moveToFirst() && c.getInt(0) == 2);
|
||||||
|
|||||||
@@ -23,14 +23,17 @@ import android.database.Cursor;
|
|||||||
import android.database.sqlite.SQLiteDatabase;
|
import android.database.sqlite.SQLiteDatabase;
|
||||||
import android.support.annotation.NonNull;
|
import android.support.annotation.NonNull;
|
||||||
|
|
||||||
|
import org.isoron.uhabits.models.Habit;
|
||||||
import org.isoron.uhabits.utils.DatabaseUtils;
|
import org.isoron.uhabits.utils.DatabaseUtils;
|
||||||
import org.isoron.uhabits.utils.DateUtils;
|
import org.isoron.uhabits.utils.DateUtils;
|
||||||
import org.isoron.uhabits.models.Habit;
|
|
||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.util.GregorianCalendar;
|
import java.util.GregorianCalendar;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Class that imports database files exported by Rewire.
|
||||||
|
*/
|
||||||
public class RewireDBImporter extends AbstractImporter
|
public class RewireDBImporter extends AbstractImporter
|
||||||
{
|
{
|
||||||
@Override
|
@Override
|
||||||
@@ -57,7 +60,7 @@ public class RewireDBImporter extends AbstractImporter
|
|||||||
final SQLiteDatabase db = SQLiteDatabase.openDatabase(file.getPath(), null,
|
final SQLiteDatabase db = SQLiteDatabase.openDatabase(file.getPath(), null,
|
||||||
SQLiteDatabase.OPEN_READONLY);
|
SQLiteDatabase.OPEN_READONLY);
|
||||||
|
|
||||||
DatabaseUtils.executeAsTransaction(new DatabaseUtils.Command()
|
DatabaseUtils.executeAsTransaction(new DatabaseUtils.Callback()
|
||||||
{
|
{
|
||||||
@Override
|
@Override
|
||||||
public void execute()
|
public void execute()
|
||||||
@@ -91,30 +94,30 @@ public class RewireDBImporter extends AbstractImporter
|
|||||||
int periodIndex = c.getInt(7);
|
int periodIndex = c.getInt(7);
|
||||||
|
|
||||||
Habit habit = new Habit();
|
Habit habit = new Habit();
|
||||||
habit.name = name;
|
habit.setName(name);
|
||||||
habit.description = description;
|
habit.setDescription(description);
|
||||||
|
|
||||||
int periods[] = { 7, 31, 365 };
|
int periods[] = { 7, 31, 365 };
|
||||||
|
|
||||||
switch (schedule)
|
switch (schedule)
|
||||||
{
|
{
|
||||||
case 0:
|
case 0:
|
||||||
habit.freqNum = activeDays.split(",").length;
|
habit.setFreqNum(activeDays.split(",").length);
|
||||||
habit.freqDen = 7;
|
habit.setFreqDen(7);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 1:
|
case 1:
|
||||||
habit.freqNum = days;
|
habit.setFreqNum(days);
|
||||||
habit.freqDen = periods[periodIndex];
|
habit.setFreqDen(periods[periodIndex]);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 2:
|
case 2:
|
||||||
habit.freqNum = 1;
|
habit.setFreqNum(1);
|
||||||
habit.freqDen = repeatingCount;
|
habit.setFreqDen(repeatingCount);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
habit.save();
|
habitList.add(habit);
|
||||||
|
|
||||||
createReminder(db, habit, id);
|
createReminder(db, habit, id);
|
||||||
createCheckmarks(db, habit, id);
|
createCheckmarks(db, habit, id);
|
||||||
@@ -150,10 +153,10 @@ public class RewireDBImporter extends AbstractImporter
|
|||||||
reminderDays[idx] = true;
|
reminderDays[idx] = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
habit.reminderDays = DateUtils.packWeekdayList(reminderDays);
|
habit.setReminderDays(DateUtils.packWeekdayList(reminderDays));
|
||||||
habit.reminderHour = rewireReminder / 60;
|
habit.setReminderHour(rewireReminder / 60);
|
||||||
habit.reminderMin = rewireReminder % 60;
|
habit.setReminderMin(rewireReminder % 60);
|
||||||
habit.save();
|
habitList.update(habit);
|
||||||
}
|
}
|
||||||
finally
|
finally
|
||||||
{
|
{
|
||||||
@@ -161,7 +164,8 @@ public class RewireDBImporter extends AbstractImporter
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void createCheckmarks(@NonNull SQLiteDatabase db, @NonNull Habit habit, int rewireHabitId)
|
private void createCheckmarks(@NonNull SQLiteDatabase db, @NonNull
|
||||||
|
Habit habit, int rewireHabitId)
|
||||||
{
|
{
|
||||||
Cursor c = null;
|
Cursor c = null;
|
||||||
|
|
||||||
@@ -181,7 +185,7 @@ public class RewireDBImporter extends AbstractImporter
|
|||||||
GregorianCalendar cal = DateUtils.getStartOfTodayCalendar();
|
GregorianCalendar cal = DateUtils.getStartOfTodayCalendar();
|
||||||
cal.set(year, month - 1, day);
|
cal.set(year, month - 1, day);
|
||||||
|
|
||||||
habit.repetitions.toggle(cal.getTimeInMillis());
|
habit.getRepetitions().toggleTimestamp(cal.getTimeInMillis());
|
||||||
}
|
}
|
||||||
while (c.moveToNext());
|
while (c.moveToNext());
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -23,14 +23,17 @@ import android.database.Cursor;
|
|||||||
import android.database.sqlite.SQLiteDatabase;
|
import android.database.sqlite.SQLiteDatabase;
|
||||||
import android.support.annotation.NonNull;
|
import android.support.annotation.NonNull;
|
||||||
|
|
||||||
|
import org.isoron.uhabits.models.Habit;
|
||||||
import org.isoron.uhabits.utils.DatabaseUtils;
|
import org.isoron.uhabits.utils.DatabaseUtils;
|
||||||
import org.isoron.uhabits.utils.DateUtils;
|
import org.isoron.uhabits.utils.DateUtils;
|
||||||
import org.isoron.uhabits.models.Habit;
|
|
||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.util.GregorianCalendar;
|
import java.util.GregorianCalendar;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Class that imports data from database files exported by Tickmate.
|
||||||
|
*/
|
||||||
public class TickmateDBImporter extends AbstractImporter
|
public class TickmateDBImporter extends AbstractImporter
|
||||||
{
|
{
|
||||||
@Override
|
@Override
|
||||||
@@ -41,7 +44,8 @@ public class TickmateDBImporter extends AbstractImporter
|
|||||||
SQLiteDatabase db = SQLiteDatabase.openDatabase(file.getPath(), null,
|
SQLiteDatabase db = SQLiteDatabase.openDatabase(file.getPath(), null,
|
||||||
SQLiteDatabase.OPEN_READONLY);
|
SQLiteDatabase.OPEN_READONLY);
|
||||||
|
|
||||||
Cursor c = db.rawQuery("select count(*) from SQLITE_MASTER where name=? or name=?",
|
Cursor c = db.rawQuery(
|
||||||
|
"select count(*) from SQLITE_MASTER where name=? or name=?",
|
||||||
new String[]{"tracks", "track2groups"});
|
new String[]{"tracks", "track2groups"});
|
||||||
|
|
||||||
boolean result = (c.moveToFirst() && c.getInt(0) == 2);
|
boolean result = (c.moveToFirst() && c.getInt(0) == 2);
|
||||||
@@ -54,62 +58,26 @@ public class TickmateDBImporter extends AbstractImporter
|
|||||||
@Override
|
@Override
|
||||||
public void importHabitsFromFile(@NonNull File file) throws IOException
|
public void importHabitsFromFile(@NonNull File file) throws IOException
|
||||||
{
|
{
|
||||||
final SQLiteDatabase db = SQLiteDatabase.openDatabase(file.getPath(), null,
|
final SQLiteDatabase db =
|
||||||
|
SQLiteDatabase.openDatabase(file.getPath(), null,
|
||||||
SQLiteDatabase.OPEN_READONLY);
|
SQLiteDatabase.OPEN_READONLY);
|
||||||
|
|
||||||
DatabaseUtils.executeAsTransaction(new DatabaseUtils.Command()
|
DatabaseUtils.executeAsTransaction(() -> createHabits(db));
|
||||||
{
|
|
||||||
@Override
|
|
||||||
public void execute()
|
|
||||||
{
|
|
||||||
createHabits(db);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
db.close();
|
db.close();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void createHabits(SQLiteDatabase db)
|
private void createCheckmarks(@NonNull SQLiteDatabase db,
|
||||||
{
|
@NonNull Habit habit,
|
||||||
Cursor c = null;
|
int tickmateTrackId)
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
c = db.rawQuery("select _id, name, description from tracks", new String[0]);
|
|
||||||
if (!c.moveToFirst()) return;
|
|
||||||
|
|
||||||
do
|
|
||||||
{
|
|
||||||
int id = c.getInt(0);
|
|
||||||
String name = c.getString(1);
|
|
||||||
String description = c.getString(2);
|
|
||||||
|
|
||||||
Habit habit = new Habit();
|
|
||||||
habit.name = name;
|
|
||||||
habit.description = description;
|
|
||||||
habit.freqNum = 1;
|
|
||||||
habit.freqDen = 1;
|
|
||||||
habit.save();
|
|
||||||
|
|
||||||
createCheckmarks(db, habit, id);
|
|
||||||
|
|
||||||
}
|
|
||||||
while (c.moveToNext());
|
|
||||||
}
|
|
||||||
finally
|
|
||||||
{
|
|
||||||
if (c != null) c.close();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void createCheckmarks(@NonNull SQLiteDatabase db, @NonNull Habit habit, int tickmateTrackId)
|
|
||||||
{
|
{
|
||||||
Cursor c = null;
|
Cursor c = null;
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
String[] params = {Integer.toString(tickmateTrackId)};
|
String[] params = {Integer.toString(tickmateTrackId)};
|
||||||
c = db.rawQuery("select distinct year, month, day from ticks where _track_id=?", params);
|
c = db.rawQuery(
|
||||||
|
"select distinct year, month, day from ticks where _track_id=?",
|
||||||
|
params);
|
||||||
if (!c.moveToFirst()) return;
|
if (!c.moveToFirst()) return;
|
||||||
|
|
||||||
do
|
do
|
||||||
@@ -121,9 +89,41 @@ public class TickmateDBImporter extends AbstractImporter
|
|||||||
GregorianCalendar cal = DateUtils.getStartOfTodayCalendar();
|
GregorianCalendar cal = DateUtils.getStartOfTodayCalendar();
|
||||||
cal.set(year, month, day);
|
cal.set(year, month, day);
|
||||||
|
|
||||||
habit.repetitions.toggle(cal.getTimeInMillis());
|
habit.getRepetitions().toggleTimestamp(cal.getTimeInMillis());
|
||||||
|
} while (c.moveToNext());
|
||||||
}
|
}
|
||||||
while (c.moveToNext());
|
finally
|
||||||
|
{
|
||||||
|
if (c != null) c.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void createHabits(SQLiteDatabase db)
|
||||||
|
{
|
||||||
|
Cursor c = null;
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
c = db.rawQuery("select _id, name, description from tracks",
|
||||||
|
new String[0]);
|
||||||
|
if (!c.moveToFirst()) return;
|
||||||
|
|
||||||
|
do
|
||||||
|
{
|
||||||
|
int id = c.getInt(0);
|
||||||
|
String name = c.getString(1);
|
||||||
|
String description = c.getString(2);
|
||||||
|
|
||||||
|
Habit habit = new Habit();
|
||||||
|
habit.setName(name);
|
||||||
|
habit.setDescription(description);
|
||||||
|
habit.setFreqNum(1);
|
||||||
|
habit.setFreqDen(1);
|
||||||
|
habitList.add(habit);
|
||||||
|
|
||||||
|
createCheckmarks(db, habit, id);
|
||||||
|
|
||||||
|
} while (c.moveToNext());
|
||||||
}
|
}
|
||||||
finally
|
finally
|
||||||
{
|
{
|
||||||
|
|||||||
23
app/src/main/java/org/isoron/uhabits/io/package-info.java
Normal file
23
app/src/main/java/org/isoron/uhabits/io/package-info.java
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
/*
|
||||||
|
* 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/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Provides classes that deal with importing from and exporting to files.
|
||||||
|
*/
|
||||||
|
package org.isoron.uhabits.io;
|
||||||
@@ -19,48 +19,65 @@
|
|||||||
|
|
||||||
package org.isoron.uhabits.models;
|
package org.isoron.uhabits.models;
|
||||||
|
|
||||||
import com.activeandroid.Model;
|
import org.apache.commons.lang3.builder.ToStringBuilder;
|
||||||
import com.activeandroid.annotation.Column;
|
|
||||||
import com.activeandroid.annotation.Table;
|
|
||||||
|
|
||||||
@Table(name = "Checkmarks")
|
/**
|
||||||
public class Checkmark extends Model
|
* A Checkmark represents the completion status of the habit for a given day.
|
||||||
|
* <p>
|
||||||
|
* While repetitions simply record that the habit was performed at a given date,
|
||||||
|
* a checkmark provides more information, such as whether a repetition was
|
||||||
|
* expected at that day or not.
|
||||||
|
* <p>
|
||||||
|
* Checkmarks are computed automatically from the list of repetitions.
|
||||||
|
*/
|
||||||
|
public class Checkmark
|
||||||
{
|
{
|
||||||
/**
|
|
||||||
* Indicates that there was no repetition at the timestamp, even though a repetition was
|
|
||||||
* expected.
|
|
||||||
*/
|
|
||||||
public static final int UNCHECKED = 0;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 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 CHECKED_IMPLICITLY = 1;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Indicates that there was a repetition at the timestamp.
|
* Indicates that there was a repetition at the timestamp.
|
||||||
*/
|
*/
|
||||||
public static final int CHECKED_EXPLICITLY = 2;
|
public static final int CHECKED_EXPLICITLY = 2;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The habit to which this checkmark belongs.
|
* Indicates that there was no repetition at the timestamp, but one was not
|
||||||
|
* expected in any case, due to the frequency of the habit.
|
||||||
*/
|
*/
|
||||||
@Column(name = "habit")
|
public static final int CHECKED_IMPLICITLY = 1;
|
||||||
public Habit habit;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Timestamp of the day to which this checkmark corresponds. Time of the day must be midnight
|
* Indicates that there was no repetition at the timestamp, even though a
|
||||||
* (UTC).
|
* repetition was expected.
|
||||||
*/
|
*/
|
||||||
@Column(name = "timestamp")
|
public static final int UNCHECKED = 0;
|
||||||
public Long timestamp;
|
|
||||||
|
|
||||||
/**
|
final Habit habit;
|
||||||
* Indicates whether there is a repetition at the given timestamp or not, and whether the
|
|
||||||
* repetition was expected. Assumes one of the values UNCHECKED, CHECKED_EXPLICITLY or
|
final long timestamp;
|
||||||
* CHECKED_IMPLICITLY.
|
|
||||||
*/
|
final int value;
|
||||||
@Column(name = "value")
|
|
||||||
public Integer value;
|
public Checkmark(Habit habit, long timestamp, int value)
|
||||||
|
{
|
||||||
|
this.habit = habit;
|
||||||
|
this.timestamp = timestamp;
|
||||||
|
this.value = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
public long getTimestamp()
|
||||||
|
{
|
||||||
|
return timestamp;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getValue()
|
||||||
|
{
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString()
|
||||||
|
{
|
||||||
|
return new ToStringBuilder(this)
|
||||||
|
.append("timestamp", timestamp)
|
||||||
|
.append("value", value)
|
||||||
|
.toString();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -19,18 +19,10 @@
|
|||||||
|
|
||||||
package org.isoron.uhabits.models;
|
package org.isoron.uhabits.models;
|
||||||
|
|
||||||
import android.database.Cursor;
|
|
||||||
import android.database.sqlite.SQLiteDatabase;
|
|
||||||
import android.database.sqlite.SQLiteStatement;
|
|
||||||
import android.support.annotation.NonNull;
|
import android.support.annotation.NonNull;
|
||||||
import android.support.annotation.Nullable;
|
import android.support.annotation.Nullable;
|
||||||
|
|
||||||
import com.activeandroid.Cache;
|
|
||||||
import com.activeandroid.query.Delete;
|
|
||||||
import com.activeandroid.query.Select;
|
|
||||||
|
|
||||||
import org.isoron.uhabits.utils.DateUtils;
|
import org.isoron.uhabits.utils.DateUtils;
|
||||||
import org.isoron.uhabits.utils.InterfaceUtils;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.Writer;
|
import java.io.Writer;
|
||||||
@@ -38,9 +30,13 @@ import java.text.SimpleDateFormat;
|
|||||||
import java.util.Date;
|
import java.util.Date;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
public class CheckmarkList
|
/**
|
||||||
|
* The collection of {@link Checkmark}s belonging to a habit.
|
||||||
|
*/
|
||||||
|
public abstract class CheckmarkList
|
||||||
{
|
{
|
||||||
private Habit habit;
|
protected Habit habit;
|
||||||
|
|
||||||
public ModelObservable observable = new ModelObservable();
|
public ModelObservable observable = new ModelObservable();
|
||||||
|
|
||||||
public CheckmarkList(Habit habit)
|
public CheckmarkList(Habit habit)
|
||||||
@@ -49,200 +45,29 @@ public class CheckmarkList
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Deletes every checkmark that has timestamp either equal or newer than a given timestamp.
|
* Returns the values for all the checkmarks, since the oldest repetition of
|
||||||
* These checkmarks will be recomputed at the next time they are queried.
|
* the habit until today. If there are no repetitions at all, returns an
|
||||||
*
|
* empty array.
|
||||||
* @param timestamp the timestamp
|
* <p>
|
||||||
*/
|
* The values are returned in an array containing one integer value for each
|
||||||
public void deleteNewerThan(long timestamp)
|
* day since the first repetition of the habit until today. The first entry
|
||||||
{
|
* corresponds to today, the second entry corresponds to yesterday, and so
|
||||||
new Delete().from(Checkmark.class)
|
* on.
|
||||||
.where("habit = ?", habit.getId())
|
|
||||||
.and("timestamp >= ?", timestamp)
|
|
||||||
.execute();
|
|
||||||
|
|
||||||
observable.notifyListeners();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the values of the checkmarks that fall inside a certain interval of time.
|
|
||||||
*
|
|
||||||
* The values are returned in an array containing one integer value 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 fromTimestamp timestamp for the oldest checkmark
|
|
||||||
* @param toTimestamp timestamp for the newest checkmark
|
|
||||||
* @return values for the checkmarks inside the given interval
|
|
||||||
*/
|
|
||||||
@NonNull
|
|
||||||
public int[] getValues(long fromTimestamp, long toTimestamp)
|
|
||||||
{
|
|
||||||
compute(fromTimestamp, toTimestamp);
|
|
||||||
|
|
||||||
if(fromTimestamp > toTimestamp) return new int[0];
|
|
||||||
|
|
||||||
String query = "select value, timestamp from Checkmarks where " +
|
|
||||||
"habit = ? and timestamp >= ? and timestamp <= ?";
|
|
||||||
|
|
||||||
SQLiteDatabase db = Cache.openDatabase();
|
|
||||||
String args[] = { habit.getId().toString(), Long.toString(fromTimestamp),
|
|
||||||
Long.toString(toTimestamp) };
|
|
||||||
Cursor cursor = db.rawQuery(query, args);
|
|
||||||
|
|
||||||
long day = DateUtils.millisecondsInOneDay;
|
|
||||||
int nDays = (int) ((toTimestamp - fromTimestamp) / day) + 1;
|
|
||||||
int[] checks = new int[nDays];
|
|
||||||
|
|
||||||
if (cursor.moveToFirst())
|
|
||||||
{
|
|
||||||
do
|
|
||||||
{
|
|
||||||
long timestamp = cursor.getLong(1);
|
|
||||||
int offset = (int) ((timestamp - fromTimestamp) / day);
|
|
||||||
checks[nDays - offset - 1] = cursor.getInt(0);
|
|
||||||
|
|
||||||
} while (cursor.moveToNext());
|
|
||||||
}
|
|
||||||
|
|
||||||
cursor.close();
|
|
||||||
return checks;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 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
|
* @return values for the checkmarks in the interval
|
||||||
*/
|
*/
|
||||||
@NonNull
|
@NonNull
|
||||||
public int[] getAllValues()
|
public int[] getAllValues()
|
||||||
{
|
{
|
||||||
Repetition oldestRep = habit.repetitions.getOldest();
|
Repetition oldestRep = habit.getRepetitions().getOldest();
|
||||||
if (oldestRep == null) return new int[0];
|
if (oldestRep == null) return new int[0];
|
||||||
|
|
||||||
Long fromTimestamp = oldestRep.timestamp;
|
Long fromTimestamp = oldestRep.getTimestamp();
|
||||||
Long toTimestamp = DateUtils.getStartOfToday();
|
Long toTimestamp = DateUtils.getStartOfToday();
|
||||||
|
|
||||||
return getValues(fromTimestamp, toTimestamp);
|
return getValues(fromTimestamp, toTimestamp);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Computes and stores one checkmark for each day, since the first repetition until today.
|
|
||||||
* Days that already have a corresponding checkmark are skipped.
|
|
||||||
*/
|
|
||||||
protected void computeAll()
|
|
||||||
{
|
|
||||||
long fromTimestamp = habit.repetitions.getOldestTimestamp();
|
|
||||||
if(fromTimestamp == 0) return;
|
|
||||||
|
|
||||||
Long toTimestamp = DateUtils.getStartOfToday();
|
|
||||||
|
|
||||||
compute(fromTimestamp, toTimestamp);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Computes and stores one checkmark for each day that falls inside the specified interval of
|
|
||||||
* time. Days that already have a corresponding checkmark are skipped.
|
|
||||||
*
|
|
||||||
* @param from timestamp for the beginning of the interval
|
|
||||||
* @param to timestamp for the end of the interval
|
|
||||||
*/
|
|
||||||
protected void compute(long from, final long to)
|
|
||||||
{
|
|
||||||
InterfaceUtils.throwIfMainThread();
|
|
||||||
|
|
||||||
final long day = DateUtils.millisecondsInOneDay;
|
|
||||||
|
|
||||||
Checkmark newestCheckmark = findNewest();
|
|
||||||
if(newestCheckmark != null) from = newestCheckmark.timestamp + day;
|
|
||||||
|
|
||||||
if(from > to) return;
|
|
||||||
|
|
||||||
long fromExtended = from - (long) (habit.freqDen) * day;
|
|
||||||
List<Repetition> reps = habit.repetitions
|
|
||||||
.selectFromTo(fromExtended, to)
|
|
||||||
.execute();
|
|
||||||
|
|
||||||
final int nDays = (int) ((to - from) / day) + 1;
|
|
||||||
int nDaysExtended = (int) ((to - fromExtended) / day) + 1;
|
|
||||||
final int checks[] = new int[nDaysExtended];
|
|
||||||
|
|
||||||
for (Repetition rep : reps)
|
|
||||||
{
|
|
||||||
int offset = (int) ((rep.timestamp - fromExtended) / day);
|
|
||||||
checks[nDaysExtended - offset - 1] = Checkmark.CHECKED_EXPLICITLY;
|
|
||||||
}
|
|
||||||
|
|
||||||
for (int i = 0; i < nDays; i++)
|
|
||||||
{
|
|
||||||
int counter = 0;
|
|
||||||
|
|
||||||
for (int j = 0; j < habit.freqDen; j++)
|
|
||||||
if (checks[i + j] == 2) counter++;
|
|
||||||
|
|
||||||
if (counter >= habit.freqNum)
|
|
||||||
if(checks[i] != Checkmark.CHECKED_EXPLICITLY)
|
|
||||||
checks[i] = Checkmark.CHECKED_IMPLICITLY;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
long timestamps[] = new long[nDays];
|
|
||||||
for (int i = 0; i < nDays; i++)
|
|
||||||
timestamps[i] = to - i * day;
|
|
||||||
|
|
||||||
insert(timestamps, checks);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void insert(long timestamps[], int values[])
|
|
||||||
{
|
|
||||||
String query = "insert into Checkmarks(habit, timestamp, value) values (?,?,?)";
|
|
||||||
|
|
||||||
SQLiteDatabase db = Cache.openDatabase();
|
|
||||||
db.beginTransaction();
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
SQLiteStatement statement = db.compileStatement(query);
|
|
||||||
|
|
||||||
for (int i = 0; i < timestamps.length; i++)
|
|
||||||
{
|
|
||||||
statement.bindLong(1, habit.getId());
|
|
||||||
statement.bindLong(2, timestamps[i]);
|
|
||||||
statement.bindLong(3, values[i]);
|
|
||||||
statement.execute();
|
|
||||||
}
|
|
||||||
|
|
||||||
db.setTransactionSuccessful();
|
|
||||||
}
|
|
||||||
finally
|
|
||||||
{
|
|
||||||
db.endTransaction();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns newest checkmark that has already been computed. Ignores any checkmark that has
|
|
||||||
* timestamp in the future. This does not update the cache.
|
|
||||||
*
|
|
||||||
* @return newest checkmark already computed
|
|
||||||
*/
|
|
||||||
@Nullable
|
|
||||||
protected Checkmark findNewest()
|
|
||||||
{
|
|
||||||
return new Select().from(Checkmark.class)
|
|
||||||
.where("habit = ?", habit.getId())
|
|
||||||
.and("timestamp <= ?", DateUtils.getStartOfToday())
|
|
||||||
.orderBy("timestamp desc")
|
|
||||||
.limit(1)
|
|
||||||
.executeSingle();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the checkmark for today.
|
* Returns the checkmark for today.
|
||||||
*
|
*
|
||||||
@@ -253,7 +78,7 @@ public class CheckmarkList
|
|||||||
{
|
{
|
||||||
long today = DateUtils.getStartOfToday();
|
long today = DateUtils.getStartOfToday();
|
||||||
compute(today, today);
|
compute(today, today);
|
||||||
return findNewest();
|
return getNewest();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -264,41 +89,133 @@ public class CheckmarkList
|
|||||||
public int getTodayValue()
|
public int getTodayValue()
|
||||||
{
|
{
|
||||||
Checkmark today = getToday();
|
Checkmark today = getToday();
|
||||||
if(today != null) return today.value;
|
if (today != null) return today.getValue();
|
||||||
else return Checkmark.UNCHECKED;
|
else return Checkmark.UNCHECKED;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Writes the entire list of checkmarks to the given writer, in CSV format. There is one
|
* Returns the values of the checkmarks that fall inside a certain interval
|
||||||
* line for each checkmark. Each line contains two fields: timestamp and value.
|
* of time.
|
||||||
|
* <p>
|
||||||
|
* The values are returned in an array containing one integer value 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 values for the checkmarks inside the given interval
|
||||||
|
*/
|
||||||
|
public abstract int[] getValues(long from, long to);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Marks as invalid every checkmark that has timestamp either equal or newer
|
||||||
|
* than a given timestamp. These checkmarks will be recomputed at the next
|
||||||
|
* time they are queried.
|
||||||
|
*
|
||||||
|
* @param timestamp the timestamp
|
||||||
|
*/
|
||||||
|
public abstract void invalidateNewerThan(long timestamp);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Writes the entire list of checkmarks to the given writer, in CSV format.
|
||||||
|
* There is one line for each checkmark. Each line contains two fields:
|
||||||
|
* timestamp and value.
|
||||||
*
|
*
|
||||||
* @param out the writer where the CSV will be output
|
* @param out the writer where the CSV will be output
|
||||||
* @throws IOException in case write operations fail
|
* @throws IOException in case write operations fail
|
||||||
*/
|
*/
|
||||||
|
|
||||||
public void writeCSV(Writer out) throws IOException
|
public void writeCSV(Writer out) throws IOException
|
||||||
{
|
{
|
||||||
computeAll();
|
computeAll();
|
||||||
|
|
||||||
|
int values[] = getAllValues();
|
||||||
|
long timestamp = DateUtils.getStartOfToday();
|
||||||
SimpleDateFormat dateFormat = DateUtils.getCSVDateFormat();
|
SimpleDateFormat dateFormat = DateUtils.getCSVDateFormat();
|
||||||
|
|
||||||
String query = "select timestamp, value from checkmarks where habit = ? order by timestamp";
|
for (int value : values)
|
||||||
String params[] = { habit.getId().toString() };
|
|
||||||
|
|
||||||
SQLiteDatabase db = Cache.openDatabase();
|
|
||||||
Cursor cursor = db.rawQuery(query, params);
|
|
||||||
|
|
||||||
if(!cursor.moveToFirst()) return;
|
|
||||||
|
|
||||||
do
|
|
||||||
{
|
{
|
||||||
String timestamp = dateFormat.format(new Date(cursor.getLong(0)));
|
String date = dateFormat.format(new Date(timestamp));
|
||||||
Integer value = cursor.getInt(1);
|
out.write(String.format("%s,%d\n", date, value));
|
||||||
out.write(String.format("%s,%d\n", timestamp, value));
|
timestamp -= DateUtils.millisecondsInOneDay;
|
||||||
|
|
||||||
} while(cursor.moveToNext());
|
|
||||||
|
|
||||||
cursor.close();
|
|
||||||
out.close();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Computes and stores one checkmark for each day that falls inside the
|
||||||
|
* specified interval of time. Days that already have a corresponding
|
||||||
|
* checkmark are skipped.
|
||||||
|
*
|
||||||
|
* @param from timestamp for the beginning of the interval
|
||||||
|
* @param to timestamp for the end of the interval
|
||||||
|
*/
|
||||||
|
protected void compute(long from, final long to)
|
||||||
|
{
|
||||||
|
final long day = DateUtils.millisecondsInOneDay;
|
||||||
|
|
||||||
|
Checkmark newestCheckmark = getNewest();
|
||||||
|
if (newestCheckmark != null)
|
||||||
|
from = newestCheckmark.getTimestamp() + day;
|
||||||
|
|
||||||
|
if (from > to) return;
|
||||||
|
|
||||||
|
long fromExtended = from - (long) (habit.getFreqDen()) * day;
|
||||||
|
List<Repetition> reps =
|
||||||
|
habit.getRepetitions().getByInterval(fromExtended, to);
|
||||||
|
|
||||||
|
final int nDays = (int) ((to - from) / day) + 1;
|
||||||
|
int nDaysExtended = (int) ((to - fromExtended) / day) + 1;
|
||||||
|
final int checks[] = new int[nDaysExtended];
|
||||||
|
|
||||||
|
for (Repetition rep : reps)
|
||||||
|
{
|
||||||
|
int offset = (int) ((rep.getTimestamp() - fromExtended) / day);
|
||||||
|
checks[nDaysExtended - offset - 1] = Checkmark.CHECKED_EXPLICITLY;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int i = 0; i < nDays; i++)
|
||||||
|
{
|
||||||
|
int counter = 0;
|
||||||
|
|
||||||
|
for (int j = 0; j < habit.getFreqDen(); j++)
|
||||||
|
if (checks[i + j] == 2) counter++;
|
||||||
|
|
||||||
|
if (counter >= habit.getFreqNum())
|
||||||
|
if (checks[i] != Checkmark.CHECKED_EXPLICITLY)
|
||||||
|
checks[i] = Checkmark.CHECKED_IMPLICITLY;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
long timestamps[] = new long[nDays];
|
||||||
|
for (int i = 0; i < nDays; i++)
|
||||||
|
timestamps[i] = to - i * day;
|
||||||
|
|
||||||
|
insert(timestamps, checks);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Computes and stores one checkmark for each day, since the first
|
||||||
|
* repetition until today. Days that already have a corresponding checkmark
|
||||||
|
* are skipped.
|
||||||
|
*/
|
||||||
|
protected void computeAll()
|
||||||
|
{
|
||||||
|
Repetition oldest = habit.getRepetitions().getOldest();
|
||||||
|
if (oldest == null) return;
|
||||||
|
|
||||||
|
Long today = DateUtils.getStartOfToday();
|
||||||
|
|
||||||
|
compute(oldest.getTimestamp(), today);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns newest checkmark that has already been computed. Ignores any
|
||||||
|
* checkmark that has timestamp in the future. This does not update the
|
||||||
|
* cache.
|
||||||
|
*
|
||||||
|
* @return newest checkmark already computed
|
||||||
|
*/
|
||||||
|
protected abstract Checkmark getNewest();
|
||||||
|
|
||||||
|
protected abstract void insert(long timestamps[], int values[]);
|
||||||
|
}
|
||||||
|
|||||||
@@ -19,135 +19,75 @@
|
|||||||
|
|
||||||
package org.isoron.uhabits.models;
|
package org.isoron.uhabits.models;
|
||||||
|
|
||||||
import android.annotation.SuppressLint;
|
|
||||||
import android.net.Uri;
|
import android.net.Uri;
|
||||||
import android.support.annotation.NonNull;
|
import android.support.annotation.NonNull;
|
||||||
import android.support.annotation.Nullable;
|
import android.support.annotation.Nullable;
|
||||||
|
|
||||||
import com.activeandroid.ActiveAndroid;
|
import org.apache.commons.lang3.builder.ToStringBuilder;
|
||||||
import com.activeandroid.Model;
|
import org.isoron.uhabits.HabitsApplication;
|
||||||
import com.activeandroid.annotation.Column;
|
|
||||||
import com.activeandroid.annotation.Table;
|
|
||||||
import com.activeandroid.query.Delete;
|
|
||||||
import com.activeandroid.query.From;
|
|
||||||
import com.activeandroid.query.Select;
|
|
||||||
import com.activeandroid.query.Update;
|
|
||||||
import com.activeandroid.util.SQLiteUtils;
|
|
||||||
import com.opencsv.CSVWriter;
|
|
||||||
|
|
||||||
import org.isoron.uhabits.utils.ColorUtils;
|
|
||||||
import org.isoron.uhabits.utils.DateUtils;
|
import org.isoron.uhabits.utils.DateUtils;
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.io.Writer;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Locale;
|
import java.util.Locale;
|
||||||
|
|
||||||
@Table(name = "Habits")
|
import javax.inject.Inject;
|
||||||
public class Habit extends Model
|
|
||||||
|
/**
|
||||||
|
* The thing that the user wants to track.
|
||||||
|
*/
|
||||||
|
public class Habit
|
||||||
{
|
{
|
||||||
/**
|
public static final String HABIT_URI_FORMAT =
|
||||||
* Name of the habit
|
"content://org.isoron.uhabits/habit/%d";
|
||||||
*/
|
|
||||||
@Column(name = "name")
|
|
||||||
public String name;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Description of the habit
|
|
||||||
*/
|
|
||||||
@Column(name = "description")
|
|
||||||
public String description;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Frequency numerator. If a habit is performed 3 times in 7 days, this field equals 3.
|
|
||||||
*/
|
|
||||||
@Column(name = "freq_num")
|
|
||||||
public Integer freqNum;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Frequency denominator. If a habit is performed 3 times in 7 days, this field equals 7.
|
|
||||||
*/
|
|
||||||
@Column(name = "freq_den")
|
|
||||||
public Integer freqDen;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Color of the habit.
|
|
||||||
*
|
|
||||||
* This number is not an android.graphics.Color, but an index to the activity color palette,
|
|
||||||
* which changes according to the theme. To convert this color into an android.graphics.Color,
|
|
||||||
* use ColorHelper.getColor(context, habit.color).
|
|
||||||
*/
|
|
||||||
@Column(name = "color")
|
|
||||||
public Integer color;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Position of the habit. Habits are usually sorted by this field.
|
|
||||||
*/
|
|
||||||
@Column(name = "position")
|
|
||||||
public Integer position;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Hour of the day the reminder should be shown. If there is no reminder, this equals to null.
|
|
||||||
*/
|
|
||||||
@Nullable
|
@Nullable
|
||||||
@Column(name = "reminder_hour")
|
private Long id;
|
||||||
public Integer reminderHour;
|
|
||||||
|
@NonNull
|
||||||
|
private String name;
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
private String description;
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
private Integer freqNum;
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
private Integer freqDen;
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
private Integer color;
|
||||||
|
|
||||||
/**
|
|
||||||
* Minute the reminder should be shown. If there is no reminder, this equals to null.
|
|
||||||
*/
|
|
||||||
@Nullable
|
@Nullable
|
||||||
@Column(name = "reminder_min")
|
private Integer reminderHour;
|
||||||
public Integer reminderMin;
|
|
||||||
|
@Nullable
|
||||||
|
private Integer reminderMin;
|
||||||
|
|
||||||
/**
|
|
||||||
* Days of the week the reminder should be shown. This field can be converted to a list of
|
|
||||||
* booleans using the method DateHelper.unpackWeekdayList and converted back to an integer by
|
|
||||||
* using the method DateHelper.packWeekdayList. If the habit has no reminders, this value
|
|
||||||
* should be ignored.
|
|
||||||
*/
|
|
||||||
@NonNull
|
@NonNull
|
||||||
@Column(name = "reminder_days")
|
private Integer reminderDays;
|
||||||
public Integer reminderDays;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Not currently used.
|
|
||||||
*/
|
|
||||||
@Column(name = "highlight")
|
|
||||||
public Integer highlight;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Flag that indicates whether the habit is archived. Archived habits are usually omitted from
|
|
||||||
* listings, unless explicitly included.
|
|
||||||
*/
|
|
||||||
@Column(name = "archived")
|
|
||||||
public Integer archived;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* List of streaks belonging to this habit.
|
|
||||||
*/
|
|
||||||
@NonNull
|
@NonNull
|
||||||
public StreakList streaks;
|
private Integer highlight;
|
||||||
|
|
||||||
/**
|
|
||||||
* List of scores belonging to this habit.
|
|
||||||
*/
|
|
||||||
@NonNull
|
@NonNull
|
||||||
public ScoreList scores;
|
private Integer archived;
|
||||||
|
|
||||||
/**
|
|
||||||
* List of repetitions belonging to this habit.
|
|
||||||
*/
|
|
||||||
@NonNull
|
@NonNull
|
||||||
public RepetitionList repetitions;
|
private StreakList streaks;
|
||||||
|
|
||||||
/**
|
|
||||||
* List of checkmarks belonging to this habit.
|
|
||||||
*/
|
|
||||||
@NonNull
|
@NonNull
|
||||||
public CheckmarkList checkmarks;
|
private ScoreList scores;
|
||||||
|
|
||||||
public ModelObservable observable = new ModelObservable();
|
@NonNull
|
||||||
|
private RepetitionList repetitions;
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
private CheckmarkList checkmarks;
|
||||||
|
|
||||||
|
private ModelObservable observable = new ModelObservable();
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
ModelFactory factory;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Constructs a habit with the same attributes as the specified habit.
|
* Constructs a habit with the same attributes as the specified habit.
|
||||||
@@ -156,328 +96,44 @@ public class Habit extends Model
|
|||||||
*/
|
*/
|
||||||
public Habit(Habit model)
|
public Habit(Habit model)
|
||||||
{
|
{
|
||||||
|
HabitsApplication.getComponent().inject(this);
|
||||||
|
|
||||||
reminderDays = DateUtils.ALL_WEEK_DAYS;
|
reminderDays = DateUtils.ALL_WEEK_DAYS;
|
||||||
|
|
||||||
copyAttributes(model);
|
copyFrom(model);
|
||||||
|
|
||||||
checkmarks = new CheckmarkList(this);
|
checkmarks = factory.buildCheckmarkList(this);
|
||||||
streaks = new StreakList(this);
|
streaks = factory.buildStreakList(this);
|
||||||
scores = new ScoreList(this);
|
scores = factory.buildScoreList(this);
|
||||||
repetitions = new RepetitionList(this);
|
repetitions = factory.buidRepetitionList(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Constructs a habit with default attributes. The habit is not archived, not highlighted, has
|
* Constructs a habit with default attributes.
|
||||||
* no reminders and is placed in the last position of the list of habits.
|
* <p>
|
||||||
|
* The habit is not archived, not highlighted, has no reminders and is
|
||||||
|
* placed in the last position of the list of habits.
|
||||||
*/
|
*/
|
||||||
public Habit()
|
public Habit()
|
||||||
{
|
{
|
||||||
|
HabitsApplication.getComponent().inject(this);
|
||||||
|
|
||||||
this.color = 5;
|
this.color = 5;
|
||||||
this.position = Habit.countWithArchived();
|
|
||||||
this.highlight = 0;
|
this.highlight = 0;
|
||||||
this.archived = 0;
|
this.archived = 0;
|
||||||
this.freqDen = 7;
|
this.freqDen = 7;
|
||||||
this.freqNum = 3;
|
this.freqNum = 3;
|
||||||
this.reminderDays = DateUtils.ALL_WEEK_DAYS;
|
this.reminderDays = DateUtils.ALL_WEEK_DAYS;
|
||||||
|
|
||||||
checkmarks = new CheckmarkList(this);
|
checkmarks = factory.buildCheckmarkList(this);
|
||||||
streaks = new StreakList(this);
|
streaks = factory.buildStreakList(this);
|
||||||
scores = new ScoreList(this);
|
scores = factory.buildScoreList(this);
|
||||||
repetitions = new RepetitionList(this);
|
repetitions = factory.buidRepetitionList(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the habit with specified id.
|
* Clears the reminder for a habit. This sets all the related fields to
|
||||||
*
|
* null.
|
||||||
* @param id the id of the habit
|
|
||||||
* @return the habit, or null if none exist
|
|
||||||
*/
|
|
||||||
@Nullable
|
|
||||||
public static Habit get(long id)
|
|
||||||
{
|
|
||||||
return Habit.load(Habit.class, id);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns a list of all habits, optionally including archived habits.
|
|
||||||
*
|
|
||||||
* @param includeArchive whether archived habits should be included the list
|
|
||||||
* @return list of all habits
|
|
||||||
*/
|
|
||||||
@NonNull
|
|
||||||
public static List<Habit> getAll(boolean includeArchive)
|
|
||||||
{
|
|
||||||
if(includeArchive) return selectWithArchived().execute();
|
|
||||||
else return select().execute();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the habit that occupies a certain position.
|
|
||||||
*
|
|
||||||
* @param position the position of the desired habit
|
|
||||||
* @return the habit at that position, or null if there is none
|
|
||||||
*/
|
|
||||||
@Nullable
|
|
||||||
public static Habit getByPosition(int position)
|
|
||||||
{
|
|
||||||
return selectWithArchived().where("position = ?", position).executeSingle();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Changes the id of a habit on the database.
|
|
||||||
*
|
|
||||||
* @param oldId the original id
|
|
||||||
* @param newId the new id
|
|
||||||
*/
|
|
||||||
@SuppressLint("DefaultLocale")
|
|
||||||
public static void updateId(long oldId, long newId)
|
|
||||||
{
|
|
||||||
SQLiteUtils.execSql(String.format("update Habits set Id = %d where Id = %d", newId, oldId));
|
|
||||||
}
|
|
||||||
|
|
||||||
@NonNull
|
|
||||||
protected static From select()
|
|
||||||
{
|
|
||||||
return new Select().from(Habit.class).where("archived = 0").orderBy("position");
|
|
||||||
}
|
|
||||||
|
|
||||||
@NonNull
|
|
||||||
protected static From selectWithArchived()
|
|
||||||
{
|
|
||||||
return new Select().from(Habit.class).orderBy("position");
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the total number of unarchived habits.
|
|
||||||
*
|
|
||||||
* @return number of unarchived habits
|
|
||||||
*/
|
|
||||||
public static int count()
|
|
||||||
{
|
|
||||||
return select().count();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the total number of habits, including archived habits.
|
|
||||||
*
|
|
||||||
* @return number of habits, including archived
|
|
||||||
*/
|
|
||||||
public static int countWithArchived()
|
|
||||||
{
|
|
||||||
return selectWithArchived().count();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns a list the habits that have a reminder. Does not include archived habits.
|
|
||||||
*
|
|
||||||
* @return list of habits with reminder
|
|
||||||
*/
|
|
||||||
@NonNull
|
|
||||||
public static List<Habit> getHabitsWithReminder()
|
|
||||||
{
|
|
||||||
return select().where("reminder_hour is not null").execute();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Changes the position of a habit on the list.
|
|
||||||
*
|
|
||||||
* @param from the habit that should be moved
|
|
||||||
* @param to the habit that currently occupies the desired position
|
|
||||||
*/
|
|
||||||
public static void reorder(Habit from, Habit to)
|
|
||||||
{
|
|
||||||
if(from == to) return;
|
|
||||||
|
|
||||||
if (to.position < from.position)
|
|
||||||
{
|
|
||||||
new Update(Habit.class).set("position = position + 1")
|
|
||||||
.where("position >= ? and position < ?", to.position, from.position)
|
|
||||||
.execute();
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
new Update(Habit.class).set("position = position - 1")
|
|
||||||
.where("position > ? and position <= ?", from.position, to.position)
|
|
||||||
.execute();
|
|
||||||
}
|
|
||||||
|
|
||||||
from.position = to.position;
|
|
||||||
from.save();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Recomputes the position for every habit in the database. It should never be necessary
|
|
||||||
* to call this method.
|
|
||||||
*/
|
|
||||||
public static void rebuildOrder()
|
|
||||||
{
|
|
||||||
List<Habit> habits = selectWithArchived().execute();
|
|
||||||
|
|
||||||
ActiveAndroid.beginTransaction();
|
|
||||||
try
|
|
||||||
{
|
|
||||||
int i = 0;
|
|
||||||
for (Habit h : habits)
|
|
||||||
{
|
|
||||||
h.position = i++;
|
|
||||||
h.save();
|
|
||||||
}
|
|
||||||
|
|
||||||
ActiveAndroid.setTransactionSuccessful();
|
|
||||||
}
|
|
||||||
finally
|
|
||||||
{
|
|
||||||
ActiveAndroid.endTransaction();
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Copies all the attributes of the specified habit into this habit
|
|
||||||
*
|
|
||||||
* @param model the model whose attributes should be copied from
|
|
||||||
*/
|
|
||||||
public void copyAttributes(@NonNull Habit model)
|
|
||||||
{
|
|
||||||
this.name = model.name;
|
|
||||||
this.description = model.description;
|
|
||||||
this.freqNum = model.freqNum;
|
|
||||||
this.freqDen = model.freqDen;
|
|
||||||
this.color = model.color;
|
|
||||||
this.position = model.position;
|
|
||||||
this.reminderHour = model.reminderHour;
|
|
||||||
this.reminderMin = model.reminderMin;
|
|
||||||
this.reminderDays = model.reminderDays;
|
|
||||||
this.highlight = model.highlight;
|
|
||||||
this.archived = model.archived;
|
|
||||||
|
|
||||||
observable.notifyListeners();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Saves the habit on the database, and assigns the specified id to it.
|
|
||||||
*
|
|
||||||
* @param id the id that the habit should receive
|
|
||||||
*/
|
|
||||||
public void save(long id)
|
|
||||||
{
|
|
||||||
save();
|
|
||||||
Habit.updateId(getId(), id);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Deletes the habit and all data associated to it, including checkmarks, repetitions and
|
|
||||||
* scores.
|
|
||||||
*/
|
|
||||||
public void cascadeDelete()
|
|
||||||
{
|
|
||||||
Long id = getId();
|
|
||||||
|
|
||||||
ActiveAndroid.beginTransaction();
|
|
||||||
try
|
|
||||||
{
|
|
||||||
new Delete().from(Checkmark.class).where("habit = ?", id).execute();
|
|
||||||
new Delete().from(Repetition.class).where("habit = ?", id).execute();
|
|
||||||
new Delete().from(Score.class).where("habit = ?", id).execute();
|
|
||||||
new Delete().from(Streak.class).where("habit = ?", id).execute();
|
|
||||||
delete();
|
|
||||||
|
|
||||||
ActiveAndroid.setTransactionSuccessful();
|
|
||||||
}
|
|
||||||
finally
|
|
||||||
{
|
|
||||||
ActiveAndroid.endTransaction();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the public URI that identifies this habit
|
|
||||||
* @return the uri
|
|
||||||
*/
|
|
||||||
public Uri getUri()
|
|
||||||
{
|
|
||||||
String s = String.format(Locale.US, "content://org.isoron.uhabits/habit/%d", getId());
|
|
||||||
return Uri.parse(s);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns whether the habit is archived or not.
|
|
||||||
* @return true if archived
|
|
||||||
*/
|
|
||||||
public boolean isArchived()
|
|
||||||
{
|
|
||||||
return archived != 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static void updateAttributes(@NonNull List<Habit> habits, @Nullable Integer color,
|
|
||||||
@Nullable Integer archived)
|
|
||||||
{
|
|
||||||
ActiveAndroid.beginTransaction();
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
for (Habit h : habits)
|
|
||||||
{
|
|
||||||
if(color != null) h.color = color;
|
|
||||||
if(archived != null) h.archived = archived;
|
|
||||||
h.save();
|
|
||||||
}
|
|
||||||
|
|
||||||
ActiveAndroid.setTransactionSuccessful();
|
|
||||||
}
|
|
||||||
finally
|
|
||||||
{
|
|
||||||
ActiveAndroid.endTransaction();
|
|
||||||
for(Habit h : habits)
|
|
||||||
h.observable.notifyListeners();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Archives an entire list of habits
|
|
||||||
*
|
|
||||||
* @param habits the habits to be archived
|
|
||||||
*/
|
|
||||||
public static void archive(@NonNull List<Habit> habits)
|
|
||||||
{
|
|
||||||
updateAttributes(habits, null, 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Unarchives an entire list of habits
|
|
||||||
*
|
|
||||||
* @param habits the habits to be unarchived
|
|
||||||
*/
|
|
||||||
public static void unarchive(@NonNull List<Habit> habits)
|
|
||||||
{
|
|
||||||
updateAttributes(habits, null, 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Sets the color for an entire list of habits.
|
|
||||||
*
|
|
||||||
* @param habits the habits to be modified
|
|
||||||
* @param color the new color to be set
|
|
||||||
*/
|
|
||||||
public static void setColor(@NonNull List<Habit> habits, int color)
|
|
||||||
{
|
|
||||||
updateAttributes(habits, color, null);
|
|
||||||
for(Habit h : habits)
|
|
||||||
h.observable.notifyListeners();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Checks whether the habit has a reminder set.
|
|
||||||
*
|
|
||||||
* @return true if habit has reminder
|
|
||||||
*/
|
|
||||||
public boolean hasReminder()
|
|
||||||
{
|
|
||||||
return (reminderHour != null && reminderMin != null);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Clears the reminder for a habit. This sets all the related fields to null.
|
|
||||||
*/
|
*/
|
||||||
public void clearReminder()
|
public void clearReminder()
|
||||||
{
|
{
|
||||||
@@ -488,36 +144,269 @@ public class Habit extends Model
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Writes the list of habits to the given writer, in CSV format. There is one line for each
|
* Copies all the attributes of the specified habit into this habit
|
||||||
* habit, containing the fields name, description, frequency numerator, frequency denominator
|
|
||||||
* and color. The color is written in HTML format (#000000).
|
|
||||||
*
|
*
|
||||||
* @param habits the list of habits to write
|
* @param model the model whose attributes should be copied from
|
||||||
* @param out the writer that will receive the result
|
|
||||||
* @throws IOException if write operations fail
|
|
||||||
*/
|
*/
|
||||||
public static void writeCSV(List<Habit> habits, Writer out) throws IOException
|
public void copyFrom(@NonNull Habit model)
|
||||||
{
|
{
|
||||||
String header[] = { "Position", "Name", "Description", "NumRepetitions", "Interval", "Color" };
|
this.name = model.getName();
|
||||||
|
this.description = model.getDescription();
|
||||||
CSVWriter csv = new CSVWriter(out);
|
this.freqNum = model.getFreqNum();
|
||||||
csv.writeNext(header, false);
|
this.freqDen = model.getFreqDen();
|
||||||
|
this.color = model.getColor();
|
||||||
for(Habit habit : habits)
|
this.reminderHour = model.getReminderHour();
|
||||||
{
|
this.reminderMin = model.getReminderMin();
|
||||||
String[] cols =
|
this.reminderDays = model.getReminderDays();
|
||||||
{
|
this.highlight = model.getHighlight();
|
||||||
String.format("%03d", habit.position + 1),
|
this.archived = model.getArchived();
|
||||||
habit.name,
|
observable.notifyListeners();
|
||||||
habit.description,
|
|
||||||
Integer.toString(habit.freqNum),
|
|
||||||
Integer.toString(habit.freqDen),
|
|
||||||
ColorUtils.toHTML(ColorUtils.CSV_PALETTE[habit.color])
|
|
||||||
};
|
|
||||||
|
|
||||||
csv.writeNext(cols, false);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
csv.close();
|
/**
|
||||||
|
* Flag that indicates whether the habit is archived. Archived habits are
|
||||||
|
* usually omitted from listings, unless explicitly included.
|
||||||
|
*/
|
||||||
|
public Integer getArchived()
|
||||||
|
{
|
||||||
|
return archived;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* List of checkmarks belonging to this habit.
|
||||||
|
*/
|
||||||
|
@NonNull
|
||||||
|
public CheckmarkList getCheckmarks()
|
||||||
|
{
|
||||||
|
return checkmarks;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Color of the habit.
|
||||||
|
* <p>
|
||||||
|
* This number is not an android.graphics.Color, but an index to the
|
||||||
|
* activity color palette, which changes according to the theme. To convert
|
||||||
|
* this color into an android.graphics.Color, use ColorHelper.getColor(context,
|
||||||
|
* habit.color).
|
||||||
|
*/
|
||||||
|
public Integer getColor()
|
||||||
|
{
|
||||||
|
return color;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setColor(Integer color)
|
||||||
|
{
|
||||||
|
this.color = color;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Description of the habit
|
||||||
|
*/
|
||||||
|
public String getDescription()
|
||||||
|
{
|
||||||
|
return description;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setDescription(String description)
|
||||||
|
{
|
||||||
|
this.description = description;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Frequency denominator. If a habit is performed 3 times in 7 days, this
|
||||||
|
* field equals 7.
|
||||||
|
*/
|
||||||
|
public Integer getFreqDen()
|
||||||
|
{
|
||||||
|
return freqDen;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setFreqDen(Integer freqDen)
|
||||||
|
{
|
||||||
|
this.freqDen = freqDen;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Frequency numerator. If a habit is performed 3 times in 7 days, this
|
||||||
|
* field equals 3.
|
||||||
|
*/
|
||||||
|
public Integer getFreqNum()
|
||||||
|
{
|
||||||
|
return freqNum;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setFreqNum(Integer freqNum)
|
||||||
|
{
|
||||||
|
this.freqNum = freqNum;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Not currently used.
|
||||||
|
*/
|
||||||
|
public Integer getHighlight()
|
||||||
|
{
|
||||||
|
return highlight;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setHighlight(Integer highlight)
|
||||||
|
{
|
||||||
|
this.highlight = highlight;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Long getId()
|
||||||
|
{
|
||||||
|
return id;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setId(Long id)
|
||||||
|
{
|
||||||
|
this.id = id;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Name of the habit
|
||||||
|
*/
|
||||||
|
public String getName()
|
||||||
|
{
|
||||||
|
return name;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setName(String name)
|
||||||
|
{
|
||||||
|
this.name = name;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ModelObservable getObservable()
|
||||||
|
{
|
||||||
|
return observable;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Days of the week the reminder should be shown. This field can be
|
||||||
|
* converted to a list of booleans using the method DateHelper.unpackWeekdayList
|
||||||
|
* and converted back to an integer by using the method
|
||||||
|
* DateHelper.packWeekdayList. If the habit has no reminders, this value
|
||||||
|
* should be ignored.
|
||||||
|
*/
|
||||||
|
@NonNull
|
||||||
|
public Integer getReminderDays()
|
||||||
|
{
|
||||||
|
return reminderDays;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setReminderDays(@NonNull Integer reminderDays)
|
||||||
|
{
|
||||||
|
this.reminderDays = reminderDays;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Hour of the day the reminder should be shown. If there is no reminder,
|
||||||
|
* this equals to null.
|
||||||
|
*/
|
||||||
|
@Nullable
|
||||||
|
public Integer getReminderHour()
|
||||||
|
{
|
||||||
|
return reminderHour;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setReminderHour(@Nullable Integer reminderHour)
|
||||||
|
{
|
||||||
|
this.reminderHour = reminderHour;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Minute the reminder should be shown. If there is no reminder, this equals
|
||||||
|
* to null.
|
||||||
|
*/
|
||||||
|
@Nullable
|
||||||
|
public Integer getReminderMin()
|
||||||
|
{
|
||||||
|
return reminderMin;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setReminderMin(@Nullable Integer reminderMin)
|
||||||
|
{
|
||||||
|
this.reminderMin = reminderMin;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* List of repetitions belonging to this habit.
|
||||||
|
*/
|
||||||
|
@NonNull
|
||||||
|
public RepetitionList getRepetitions()
|
||||||
|
{
|
||||||
|
return repetitions;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* List of scores belonging to this habit.
|
||||||
|
*/
|
||||||
|
@NonNull
|
||||||
|
public ScoreList getScores()
|
||||||
|
{
|
||||||
|
return scores;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* List of streaks belonging to this habit.
|
||||||
|
*/
|
||||||
|
@NonNull
|
||||||
|
public StreakList getStreaks()
|
||||||
|
{
|
||||||
|
return streaks;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the public URI that identifies this habit
|
||||||
|
*
|
||||||
|
* @return the uri
|
||||||
|
*/
|
||||||
|
public Uri getUri()
|
||||||
|
{
|
||||||
|
String s = String.format(Locale.US, HABIT_URI_FORMAT, getId());
|
||||||
|
return Uri.parse(s);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks whether the habit has a reminder set.
|
||||||
|
*
|
||||||
|
* @return true if habit has reminder, false otherwise
|
||||||
|
*/
|
||||||
|
public boolean hasReminder()
|
||||||
|
{
|
||||||
|
return (reminderHour != null && reminderMin != null);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns whether the habit is archived or not.
|
||||||
|
*
|
||||||
|
* @return true if archived
|
||||||
|
*/
|
||||||
|
public boolean isArchived()
|
||||||
|
{
|
||||||
|
return archived != 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setArchived(Integer archived)
|
||||||
|
{
|
||||||
|
this.archived = archived;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString()
|
||||||
|
{
|
||||||
|
return new ToStringBuilder(this)
|
||||||
|
.append("id", id)
|
||||||
|
.append("name", name)
|
||||||
|
.append("description", description)
|
||||||
|
.append("freqNum", freqNum)
|
||||||
|
.append("freqDen", freqDen)
|
||||||
|
.append("color", color)
|
||||||
|
.append("reminderHour", reminderHour)
|
||||||
|
.append("reminderMin", reminderMin)
|
||||||
|
.append("reminderDays", reminderDays)
|
||||||
|
.append("highlight", highlight)
|
||||||
|
.append("archived", archived)
|
||||||
|
.toString();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
235
app/src/main/java/org/isoron/uhabits/models/HabitList.java
Normal file
235
app/src/main/java/org/isoron/uhabits/models/HabitList.java
Normal file
@@ -0,0 +1,235 @@
|
|||||||
|
/*
|
||||||
|
* 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.models;
|
||||||
|
|
||||||
|
import android.support.annotation.NonNull;
|
||||||
|
import android.support.annotation.Nullable;
|
||||||
|
|
||||||
|
import com.opencsv.CSVWriter;
|
||||||
|
|
||||||
|
import org.isoron.uhabits.utils.ColorUtils;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.Writer;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.LinkedList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An ordered collection of {@link Habit}s.
|
||||||
|
*/
|
||||||
|
public abstract class HabitList
|
||||||
|
{
|
||||||
|
private ModelObservable observable;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a new HabitList.
|
||||||
|
* <p>
|
||||||
|
* Depending on the implementation, this list can either be empty or be
|
||||||
|
* populated by some pre-existing habits.
|
||||||
|
*/
|
||||||
|
public HabitList()
|
||||||
|
{
|
||||||
|
observable = new ModelObservable();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Inserts a new habit in the list.
|
||||||
|
*
|
||||||
|
* @param habit the habit to be inserted
|
||||||
|
*/
|
||||||
|
public abstract void add(Habit habit);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the total number of unarchived habits.
|
||||||
|
*
|
||||||
|
* @return number of unarchived habits
|
||||||
|
*/
|
||||||
|
public abstract int count();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the total number of habits, including archived habits.
|
||||||
|
*
|
||||||
|
* @return number of habits, including archived
|
||||||
|
*/
|
||||||
|
public abstract int countWithArchived();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a list of all habits, optionally including archived habits.
|
||||||
|
*
|
||||||
|
* @param includeArchive whether archived habits should be included the
|
||||||
|
* list
|
||||||
|
* @return list of all habits
|
||||||
|
*/
|
||||||
|
@NonNull
|
||||||
|
public abstract List<Habit> getAll(boolean includeArchive);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the habit with specified id.
|
||||||
|
*
|
||||||
|
* @param id the id of the habit
|
||||||
|
* @return the habit, or null if none exist
|
||||||
|
*/
|
||||||
|
public abstract Habit getById(long id);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the habit that occupies a certain position.
|
||||||
|
*
|
||||||
|
* @param position the position of the desired habit
|
||||||
|
* @return the habit at that position, or null if there is none
|
||||||
|
*/
|
||||||
|
@Nullable
|
||||||
|
public abstract Habit getByPosition(int position);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the list of habits that match a given condition.
|
||||||
|
*
|
||||||
|
* @param matcher the matcher that checks the condition
|
||||||
|
* @return the list of matching habits
|
||||||
|
*/
|
||||||
|
@NonNull
|
||||||
|
public List<Habit> getFiltered(HabitMatcher matcher)
|
||||||
|
{
|
||||||
|
LinkedList<Habit> habits = new LinkedList<>();
|
||||||
|
for (Habit h : getAll(true)) if (matcher.matches(h)) habits.add(h);
|
||||||
|
return habits;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ModelObservable getObservable()
|
||||||
|
{
|
||||||
|
return observable;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a list the habits that have a reminder. Does not include archived
|
||||||
|
* habits.
|
||||||
|
*
|
||||||
|
* @return list of habits with reminder
|
||||||
|
*/
|
||||||
|
@NonNull
|
||||||
|
public List<Habit> getWithReminder()
|
||||||
|
{
|
||||||
|
return getFiltered(habit -> habit.hasReminder());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the index of the given habit in the list, or -1 if the list does
|
||||||
|
* not contain the habit.
|
||||||
|
*
|
||||||
|
* @param h the habit
|
||||||
|
* @return the index of the habit, or -1 if not in the list
|
||||||
|
*/
|
||||||
|
public abstract int indexOf(Habit h);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Removes the given habit from the list.
|
||||||
|
* <p>
|
||||||
|
* If the given habit is not in the list, does nothing.
|
||||||
|
*
|
||||||
|
* @param h the habit to be removed.
|
||||||
|
*/
|
||||||
|
public abstract void remove(@NonNull Habit h);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Changes the position of a habit in the list.
|
||||||
|
*
|
||||||
|
* @param from the habit that should be moved
|
||||||
|
* @param to the habit that currently occupies the desired position
|
||||||
|
*/
|
||||||
|
public abstract void reorder(Habit from, Habit to);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Notifies the list that a certain list of habits has been modified.
|
||||||
|
* <p>
|
||||||
|
* Depending on the implementation, this operation might trigger a write to
|
||||||
|
* disk, or do nothing at all. To make sure that the habits get persisted,
|
||||||
|
* this operation must be called.
|
||||||
|
*
|
||||||
|
* @param habits the list of habits that have been modified.
|
||||||
|
*/
|
||||||
|
public abstract void update(List<Habit> habits);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Notifies the list that a certain habit has been modified.
|
||||||
|
* <p>
|
||||||
|
* See {@link #update(List)} for more details.
|
||||||
|
*
|
||||||
|
* @param habit the habit that has been modified.
|
||||||
|
*/
|
||||||
|
public void update(Habit habit)
|
||||||
|
{
|
||||||
|
update(Collections.singletonList(habit));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Writes the list of habits to the given writer, in CSV format. There is
|
||||||
|
* one line for each habit, containing the fields name, description,
|
||||||
|
* frequency numerator, frequency denominator and color. The color is
|
||||||
|
* written in HTML format (#000000).
|
||||||
|
*
|
||||||
|
* @param out the writer that will receive the result
|
||||||
|
* @throws IOException if write operations fail
|
||||||
|
*/
|
||||||
|
public void writeCSV(Writer out) throws IOException
|
||||||
|
{
|
||||||
|
String header[] = {
|
||||||
|
"Position",
|
||||||
|
"Name",
|
||||||
|
"Description",
|
||||||
|
"NumRepetitions",
|
||||||
|
"Interval",
|
||||||
|
"Color"
|
||||||
|
};
|
||||||
|
|
||||||
|
CSVWriter csv = new CSVWriter(out);
|
||||||
|
csv.writeNext(header, false);
|
||||||
|
|
||||||
|
for (Habit habit : getAll(true))
|
||||||
|
{
|
||||||
|
String[] cols = {
|
||||||
|
String.format("%03d", indexOf(habit) + 1),
|
||||||
|
habit.getName(),
|
||||||
|
habit.getDescription(),
|
||||||
|
Integer.toString(habit.getFreqNum()),
|
||||||
|
Integer.toString(habit.getFreqDen()),
|
||||||
|
ColorUtils.CSV_PALETTE[habit.getColor()]
|
||||||
|
};
|
||||||
|
|
||||||
|
csv.writeNext(cols, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
csv.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A HabitMatcher decides whether habits match or not a certain condition.
|
||||||
|
* They can be used to produce filtered lists of habits.
|
||||||
|
*/
|
||||||
|
public interface HabitMatcher
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Returns true if the given habit matches.
|
||||||
|
*
|
||||||
|
* @param habit the habit to be checked.
|
||||||
|
* @return true if matches, false otherwise.
|
||||||
|
*/
|
||||||
|
boolean matches(Habit habit);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,37 @@
|
|||||||
|
/*
|
||||||
|
* 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.models;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Interface implemented by factories that provide concrete implementations
|
||||||
|
* of the core model classes.
|
||||||
|
*/
|
||||||
|
public interface ModelFactory
|
||||||
|
{
|
||||||
|
RepetitionList buidRepetitionList(Habit habit);
|
||||||
|
|
||||||
|
HabitList buildHabitList();
|
||||||
|
|
||||||
|
CheckmarkList buildCheckmarkList(Habit habit);
|
||||||
|
|
||||||
|
ScoreList buildScoreList(Habit habit);
|
||||||
|
|
||||||
|
StreakList buildStreakList(Habit habit);
|
||||||
|
}
|
||||||
@@ -22,33 +22,62 @@ package org.isoron.uhabits.models;
|
|||||||
import java.util.LinkedList;
|
import java.util.LinkedList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A ModelObservable allows objects to subscribe themselves to it and receive
|
||||||
|
* notifications whenever the model is changed.
|
||||||
|
*/
|
||||||
public class ModelObservable
|
public class ModelObservable
|
||||||
{
|
{
|
||||||
List<Listener> listeners;
|
List<Listener> listeners;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a new ModelObservable with no listeners.
|
||||||
|
*/
|
||||||
public ModelObservable()
|
public ModelObservable()
|
||||||
{
|
{
|
||||||
super();
|
super();
|
||||||
listeners = new LinkedList<>();
|
listeners = new LinkedList<>();
|
||||||
}
|
}
|
||||||
|
|
||||||
public interface Listener
|
/**
|
||||||
{
|
* Adds the given listener to the observable.
|
||||||
void onModelChange();
|
*
|
||||||
}
|
* @param l the listener to be added.
|
||||||
|
*/
|
||||||
public void addListener(Listener l)
|
public void addListener(Listener l)
|
||||||
{
|
{
|
||||||
listeners.add(l);
|
listeners.add(l);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Notifies every listener that the model has changed.
|
||||||
|
* <p>
|
||||||
|
* Only models should call this method.
|
||||||
|
*/
|
||||||
|
public void notifyListeners()
|
||||||
|
{
|
||||||
|
for (Listener l : listeners) l.onModelChange();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Removes the given listener.
|
||||||
|
* <p>
|
||||||
|
* The listener will no longer be notified when the model changes. If the
|
||||||
|
* given listener is not subscrined to this observable, does nothing.
|
||||||
|
*
|
||||||
|
* @param l the listener to be removed
|
||||||
|
*/
|
||||||
public void removeListener(Listener l)
|
public void removeListener(Listener l)
|
||||||
{
|
{
|
||||||
listeners.remove(l);
|
listeners.remove(l);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void notifyListeners()
|
/**
|
||||||
|
* Interface implemented by objects that want to be notified when the model
|
||||||
|
* changes.
|
||||||
|
*/
|
||||||
|
public interface Listener
|
||||||
{
|
{
|
||||||
for(Listener l : listeners) l.onModelChange();
|
void onModelChange();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -19,22 +19,51 @@
|
|||||||
|
|
||||||
package org.isoron.uhabits.models;
|
package org.isoron.uhabits.models;
|
||||||
|
|
||||||
import com.activeandroid.Model;
|
import android.support.annotation.NonNull;
|
||||||
import com.activeandroid.annotation.Column;
|
|
||||||
import com.activeandroid.annotation.Table;
|
|
||||||
|
|
||||||
@Table(name = "Repetitions")
|
import org.apache.commons.lang3.builder.ToStringBuilder;
|
||||||
public class Repetition extends Model
|
|
||||||
|
/**
|
||||||
|
* Represents a record that the user has performed a certain habit at a certain
|
||||||
|
* date.
|
||||||
|
*/
|
||||||
|
public class Repetition
|
||||||
{
|
{
|
||||||
/**
|
@NonNull
|
||||||
* Habit to which this repetition belong.
|
private final Habit habit;
|
||||||
*/
|
|
||||||
@Column(name = "habit")
|
private final long timestamp;
|
||||||
public Habit habit;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Timestamp of the day this repetition occurred. Time of day should be midnight (UTC).
|
* Creates a new repetition with given parameters.
|
||||||
|
* <p>
|
||||||
|
* The timestamp corresponds to the days this repetition occurred. Time of
|
||||||
|
* day must be midnight (UTC).
|
||||||
|
*
|
||||||
|
* @param habit the habit to which this repetition belongs.
|
||||||
|
* @param timestamp the time this repetition occurred.
|
||||||
*/
|
*/
|
||||||
@Column(name = "timestamp")
|
public Repetition(Habit habit, long timestamp)
|
||||||
public Long timestamp;
|
{
|
||||||
|
this.habit = habit;
|
||||||
|
this.timestamp = timestamp;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Habit getHabit()
|
||||||
|
{
|
||||||
|
return habit;
|
||||||
|
}
|
||||||
|
|
||||||
|
public long getTimestamp()
|
||||||
|
{
|
||||||
|
return timestamp;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString()
|
||||||
|
{
|
||||||
|
return new ToStringBuilder(this)
|
||||||
|
.append("timestamp", timestamp)
|
||||||
|
.toString();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -19,173 +19,124 @@
|
|||||||
|
|
||||||
package org.isoron.uhabits.models;
|
package org.isoron.uhabits.models;
|
||||||
|
|
||||||
import android.database.Cursor;
|
|
||||||
import android.database.sqlite.SQLiteDatabase;
|
|
||||||
import android.support.annotation.NonNull;
|
import android.support.annotation.NonNull;
|
||||||
import android.support.annotation.Nullable;
|
import android.support.annotation.Nullable;
|
||||||
|
|
||||||
import com.activeandroid.Cache;
|
|
||||||
import com.activeandroid.query.Delete;
|
|
||||||
import com.activeandroid.query.From;
|
|
||||||
import com.activeandroid.query.Select;
|
|
||||||
import com.activeandroid.util.SQLiteUtils;
|
|
||||||
|
|
||||||
import org.isoron.uhabits.utils.DatabaseUtils;
|
|
||||||
import org.isoron.uhabits.utils.DateUtils;
|
import org.isoron.uhabits.utils.DateUtils;
|
||||||
|
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.GregorianCalendar;
|
import java.util.Calendar;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
public class RepetitionList
|
/**
|
||||||
|
* The collection of {@link Repetition}s belonging to a habit.
|
||||||
|
*/
|
||||||
|
public abstract class RepetitionList
|
||||||
{
|
{
|
||||||
@NonNull
|
@NonNull
|
||||||
private Habit habit;
|
protected final Habit habit;
|
||||||
public ModelObservable observable = new ModelObservable();
|
|
||||||
|
@NonNull
|
||||||
|
protected final ModelObservable observable;
|
||||||
|
|
||||||
public RepetitionList(@NonNull Habit habit)
|
public RepetitionList(@NonNull Habit habit)
|
||||||
{
|
{
|
||||||
this.habit = habit;
|
this.habit = habit;
|
||||||
}
|
this.observable = new ModelObservable();
|
||||||
|
|
||||||
@NonNull
|
|
||||||
protected From select()
|
|
||||||
{
|
|
||||||
return new Select().from(Repetition.class)
|
|
||||||
.where("habit = ?", habit.getId())
|
|
||||||
.and("timestamp <= ?", DateUtils.getStartOfToday())
|
|
||||||
.orderBy("timestamp");
|
|
||||||
}
|
|
||||||
|
|
||||||
@NonNull
|
|
||||||
protected From selectFromTo(long timeFrom, long timeTo)
|
|
||||||
{
|
|
||||||
return select().and("timestamp >= ?", timeFrom).and("timestamp <= ?", timeTo);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Checks whether there is a repetition at a given timestamp.
|
* Adds a repetition to the list.
|
||||||
|
* <p>
|
||||||
|
* Any implementation of this method must call observable.notifyListeners()
|
||||||
|
* after the repetition has been added.
|
||||||
*
|
*
|
||||||
* @param timestamp the timestamp to check
|
* @param repetition the repetition to be added.
|
||||||
* @return true if there is a repetition
|
|
||||||
*/
|
*/
|
||||||
public boolean contains(long timestamp)
|
public abstract void add(Repetition repetition);
|
||||||
{
|
|
||||||
int count = select().where("timestamp = ?", timestamp).count();
|
|
||||||
return (count > 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Deletes the repetition at a given timestamp, if it exists.
|
* Returns true if the list contains a repetition that has the given
|
||||||
|
* timestamp.
|
||||||
*
|
*
|
||||||
* @param timestamp the timestamp of the repetition to delete
|
* @param timestamp the timestamp to find.
|
||||||
|
* @return true if list contains repetition with given timestamp, false
|
||||||
|
* otherwise.
|
||||||
*/
|
*/
|
||||||
public void delete(long timestamp)
|
public boolean containsTimestamp(long timestamp)
|
||||||
{
|
{
|
||||||
new Delete().from(Repetition.class)
|
return (getByTimestamp(timestamp) != null);
|
||||||
.where("habit = ?", habit.getId())
|
|
||||||
.and("timestamp = ?", timestamp)
|
|
||||||
.execute();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Toggles the repetition at a certain timestamp. That is, deletes the repetition if it exists
|
* Returns the list of repetitions that happened within the given time
|
||||||
* or creates one if it does not.
|
* interval.
|
||||||
*
|
*
|
||||||
* @param timestamp the timestamp of the repetition to toggle
|
* The list is sorted by timestamp in decreasing order. That is, the first
|
||||||
|
* element corresponds to the most recent timestamp. The endpoints of the
|
||||||
|
* interval are included.
|
||||||
|
*
|
||||||
|
* @param fromTimestamp timestamp of the beginning of the interval
|
||||||
|
* @param toTimestamp timestamp of the end of the interval
|
||||||
|
* @return list of repetitions within given time interval
|
||||||
*/
|
*/
|
||||||
public void toggle(long timestamp)
|
public abstract List<Repetition> getByInterval(long fromTimestamp,
|
||||||
{
|
long toTimestamp);
|
||||||
timestamp = DateUtils.getStartOfDay(timestamp);
|
|
||||||
|
|
||||||
if (contains(timestamp))
|
|
||||||
delete(timestamp);
|
|
||||||
else
|
|
||||||
insert(timestamp);
|
|
||||||
|
|
||||||
habit.scores.invalidateNewerThan(timestamp);
|
|
||||||
habit.checkmarks.deleteNewerThan(timestamp);
|
|
||||||
habit.streaks.deleteNewerThan(timestamp);
|
|
||||||
observable.notifyListeners();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void insert(long timestamp)
|
|
||||||
{
|
|
||||||
String[] args = { habit.getId().toString(), Long.toString(timestamp) };
|
|
||||||
SQLiteUtils.execSql("insert into Repetitions(habit, timestamp) values (?,?)", args);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the oldest repetition for the habit. If there is no repetition, returns null.
|
* Returns the repetition that has the given timestamp, or null if none
|
||||||
* Repetitions in the future are discarded.
|
* exists.
|
||||||
*
|
*
|
||||||
* @return oldest repetition for the habit
|
* @param timestamp the repetition timestamp.
|
||||||
|
* @return the repetition that has the given timestamp.
|
||||||
*/
|
*/
|
||||||
@Nullable
|
@Nullable
|
||||||
public Repetition getOldest()
|
public abstract Repetition getByTimestamp(long timestamp);
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
public ModelObservable getObservable()
|
||||||
{
|
{
|
||||||
return (Repetition) select().limit(1).executeSingle();
|
return observable;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the timestamp of the oldest repetition. If there are no repetitions, returns zero.
|
* Returns the oldest repetition in the list.
|
||||||
* Repetitions in the future are discarded.
|
* <p>
|
||||||
|
* If the list is empty, returns null. Repetitions in the future are
|
||||||
|
* discarded.
|
||||||
*
|
*
|
||||||
* @return timestamp of the oldest repetition
|
* @return oldest repetition in the list, or null if list is empty.
|
||||||
*/
|
*/
|
||||||
public long getOldestTimestamp()
|
@Nullable
|
||||||
{
|
public abstract Repetition getOldest();
|
||||||
String[] args = { habit.getId().toString(), Long.toString(DateUtils.getStartOfToday()) };
|
|
||||||
String query = "select timestamp from Repetitions where habit = ? and timestamp <= ? " +
|
|
||||||
"order by timestamp limit 1";
|
|
||||||
|
|
||||||
return DatabaseUtils.longQuery(query, args);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the total number of repetitions for each month, from the first repetition until
|
* Returns the total number of repetitions for each month, from the first
|
||||||
* today, grouped by day of week. The repetitions are returned in a HashMap. The key is the
|
* repetition until today, grouped by day of week.
|
||||||
* timestamp for the first day of the month, at midnight (00:00). The value is an integer
|
* <p>
|
||||||
* array with 7 entries. The first entry contains the total number of repetitions during
|
* The repetitions are returned in a HashMap. The key is the timestamp for
|
||||||
* the specified month that occurred on a Saturday. The second entry corresponds to Sunday,
|
* the first day of the month, at midnight (00:00). The value is an integer
|
||||||
* and so on. If there are no repetitions during a certain month, the value is null.
|
* array with 7 entries. The first entry contains the total number of
|
||||||
|
* 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.
|
||||||
*
|
*
|
||||||
* @return total number of repetitions by month versus day of week
|
* @return total number of repetitions by month versus day of week
|
||||||
*/
|
*/
|
||||||
@NonNull
|
@NonNull
|
||||||
public HashMap<Long, Integer[]> getWeekdayFrequency()
|
public HashMap<Long, Integer[]> getWeekdayFrequency()
|
||||||
{
|
{
|
||||||
Repetition oldestRep = getOldest();
|
List<Repetition> reps = getByInterval(0, DateUtils.getStartOfToday());
|
||||||
if(oldestRep == null) return new HashMap<>();
|
|
||||||
|
|
||||||
String query = "select strftime('%Y', timestamp / 1000, 'unixepoch') as year," +
|
|
||||||
"strftime('%m', timestamp / 1000, 'unixepoch') as month," +
|
|
||||||
"strftime('%w', timestamp / 1000, 'unixepoch') as weekday, " +
|
|
||||||
"count(*) from repetitions " +
|
|
||||||
"where habit = ? and timestamp <= ? " +
|
|
||||||
"group by year, month, weekday";
|
|
||||||
|
|
||||||
String[] params = { habit.getId().toString(),
|
|
||||||
Long.toString(DateUtils.getStartOfToday()) };
|
|
||||||
|
|
||||||
SQLiteDatabase db = Cache.openDatabase();
|
|
||||||
Cursor cursor = db.rawQuery(query, params);
|
|
||||||
|
|
||||||
if(!cursor.moveToFirst()) return new HashMap<>();
|
|
||||||
|
|
||||||
HashMap<Long, Integer[]> map = new HashMap<>();
|
HashMap<Long, Integer[]> map = new HashMap<>();
|
||||||
GregorianCalendar date = DateUtils.getStartOfTodayCalendar();
|
|
||||||
|
|
||||||
do
|
for (Repetition r : reps)
|
||||||
{
|
{
|
||||||
int year = Integer.parseInt(cursor.getString(0));
|
Calendar date = DateUtils.getCalendar(r.getTimestamp());
|
||||||
int month = Integer.parseInt(cursor.getString(1));
|
int weekday = date.get(Calendar.DAY_OF_WEEK) % 7;
|
||||||
int weekday = (Integer.parseInt(cursor.getString(2)) + 1) % 7;
|
date.set(Calendar.DAY_OF_MONTH, 1);
|
||||||
int count = cursor.getInt(3);
|
|
||||||
|
|
||||||
date.set(year, month - 1, 1);
|
|
||||||
long timestamp = date.getTimeInMillis();
|
long timestamp = date.getTimeInMillis();
|
||||||
|
|
||||||
Integer[] list = map.get(timestamp);
|
Integer[] list = map.get(timestamp);
|
||||||
|
|
||||||
if (list == null)
|
if (list == null)
|
||||||
@@ -195,23 +146,52 @@ public class RepetitionList
|
|||||||
map.put(timestamp, list);
|
map.put(timestamp, list);
|
||||||
}
|
}
|
||||||
|
|
||||||
list[weekday] = count;
|
list[weekday]++;
|
||||||
}
|
}
|
||||||
while (cursor.moveToNext());
|
|
||||||
cursor.close();
|
|
||||||
|
|
||||||
return map;
|
return map;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the total number of repetitions that happened within the specified interval of time.
|
* Removes a given repetition from the list.
|
||||||
|
* <p>
|
||||||
|
* If the list does not contain the repetition, it is unchanged.
|
||||||
|
* <p>
|
||||||
|
* Any implementation of this method must call observable.notifyListeners()
|
||||||
|
* after the repetition has been added.
|
||||||
*
|
*
|
||||||
* @param from beginning of the interval
|
* @param repetition the repetition to be removed
|
||||||
* @param to end of the interval
|
|
||||||
* @return number of repetition in the given interval
|
|
||||||
*/
|
*/
|
||||||
public int count(long from, long to)
|
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 Repetition toggleTimestamp(long timestamp)
|
||||||
{
|
{
|
||||||
return selectFromTo(from, to).count();
|
timestamp = DateUtils.getStartOfDay(timestamp);
|
||||||
|
Repetition rep = getByTimestamp(timestamp);
|
||||||
|
|
||||||
|
if (rep != null) remove(rep);
|
||||||
|
else
|
||||||
|
{
|
||||||
|
rep = new Repetition(habit, timestamp);
|
||||||
|
add(rep);
|
||||||
|
}
|
||||||
|
|
||||||
|
// habit.getScores().invalidateNewerThan(timestamp);
|
||||||
|
// habit.getCheckmarks().invalidateNewerThan(timestamp);
|
||||||
|
// habit.getStreaks().invalidateNewerThan(timestamp);
|
||||||
|
return rep;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -19,78 +19,60 @@
|
|||||||
|
|
||||||
package org.isoron.uhabits.models;
|
package org.isoron.uhabits.models;
|
||||||
|
|
||||||
import com.activeandroid.Model;
|
import org.apache.commons.lang3.builder.ToStringBuilder;
|
||||||
import com.activeandroid.annotation.Column;
|
|
||||||
import com.activeandroid.annotation.Table;
|
|
||||||
|
|
||||||
@Table(name = "Score")
|
/**
|
||||||
public class Score extends Model
|
* Represents how strong a habit is at a certain date.
|
||||||
|
*/
|
||||||
|
public class Score
|
||||||
{
|
{
|
||||||
/**
|
/**
|
||||||
* Minimum score value required to earn half a star.
|
* Habit to which this score belongs to.
|
||||||
*/
|
*/
|
||||||
public static final int HALF_STAR_CUTOFF = 9629750;
|
private Habit habit;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Minimum score value required to earn a full star.
|
* Timestamp of the day to which this score applies. Time of day should be
|
||||||
|
* midnight (UTC).
|
||||||
*/
|
*/
|
||||||
public static final int FULL_STAR_CUTOFF = 15407600;
|
private Long timestamp;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Value of the score.
|
||||||
|
*/
|
||||||
|
private Integer value;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Maximum score value attainable by any habit.
|
* Maximum score value attainable by any habit.
|
||||||
*/
|
*/
|
||||||
public static final int MAX_VALUE = 19259478;
|
public static final int MAX_VALUE = 19259478;
|
||||||
|
|
||||||
/**
|
public Score(Habit habit, Long timestamp, Integer value)
|
||||||
* Status indicating that the habit has not earned any star.
|
{
|
||||||
*/
|
this.habit = habit;
|
||||||
public static final int EMPTY_STAR = 0;
|
this.timestamp = timestamp;
|
||||||
|
this.value = value;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Status indicating that the habit has earned half a star.
|
* Given the frequency of the habit, the previous score, and the value of
|
||||||
*/
|
* the current checkmark, computes the current score for the habit.
|
||||||
public static final int HALF_STAR = 1;
|
* <p>
|
||||||
|
* The frequency of the habit is the number of repetitions divided by the
|
||||||
/**
|
* length of the interval. For example, a habit that should be repeated 3
|
||||||
* Status indicating that the habit has earned a full star.
|
* times in 8 days has frequency 3.0 / 8.0 = 0.375.
|
||||||
*/
|
* <p>
|
||||||
public static final int FULL_STAR = 2;
|
* The checkmarkValue should be UNCHECKED, CHECKED_IMPLICITLY or
|
||||||
|
* CHECK_EXPLICITLY.
|
||||||
/**
|
|
||||||
* Habit to which this score belongs to.
|
|
||||||
*/
|
|
||||||
@Column(name = "habit")
|
|
||||||
public Habit habit;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Timestamp of the day to which this score applies. Time of day should be midnight (UTC).
|
|
||||||
*/
|
|
||||||
@Column(name = "timestamp")
|
|
||||||
public Long timestamp;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Value of the score.
|
|
||||||
*/
|
|
||||||
@Column(name = "score")
|
|
||||||
public Integer score;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Given the frequency of the habit, the previous score, and the value of the current checkmark,
|
|
||||||
* computes the current score for the habit.
|
|
||||||
*
|
|
||||||
* The frequency of the habit is the number of repetitions divided by the length of the
|
|
||||||
* interval. For example, a habit that should be repeated 3 times in 8 days has frequency 3.0 /
|
|
||||||
* 8.0 = 0.375.
|
|
||||||
*
|
|
||||||
* The checkmarkValue should be UNCHECKED, CHECKED_IMPLICITLY or CHECK_EXPLICITLY.
|
|
||||||
*
|
*
|
||||||
* @param frequency the frequency of the habit
|
* @param frequency the frequency of the habit
|
||||||
* @param previousScore the previous score of the habit
|
* @param previousScore the previous score of the habit
|
||||||
* @param checkmarkValue the value of the current checkmark
|
* @param checkmarkValue the value of the current checkmark
|
||||||
*
|
|
||||||
* @return the current score
|
* @return the current score
|
||||||
*/
|
*/
|
||||||
public static int compute(double frequency, int previousScore, int checkmarkValue)
|
public static int compute(double frequency,
|
||||||
|
int previousScore,
|
||||||
|
int checkmarkValue)
|
||||||
{
|
{
|
||||||
double multiplier = Math.pow(0.5, 1.0 / (14.0 / frequency - 1));
|
double multiplier = Math.pow(0.5, 1.0 / (14.0 / frequency - 1));
|
||||||
int score = (int) (previousScore * multiplier);
|
int score = (int) (previousScore * multiplier);
|
||||||
@@ -104,16 +86,27 @@ public class Score extends Model
|
|||||||
return score;
|
return score;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
public Habit getHabit()
|
||||||
* Return the current star status for the habit, which can one of EMPTY_STAR, HALF_STAR or
|
|
||||||
* FULL_STAR.
|
|
||||||
*
|
|
||||||
* @return current star status
|
|
||||||
*/
|
|
||||||
public int getStarStatus()
|
|
||||||
{
|
{
|
||||||
if(score >= Score.FULL_STAR_CUTOFF) return Score.FULL_STAR;
|
return habit;
|
||||||
if(score >= Score.HALF_STAR_CUTOFF) return Score.HALF_STAR;
|
}
|
||||||
return Score.EMPTY_STAR;
|
|
||||||
|
public Long getTimestamp()
|
||||||
|
{
|
||||||
|
return timestamp;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Integer getValue()
|
||||||
|
{
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString()
|
||||||
|
{
|
||||||
|
return new ToStringBuilder(this)
|
||||||
|
.append("timestamp", timestamp)
|
||||||
|
.append("value", value)
|
||||||
|
.toString();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -21,215 +21,59 @@ package org.isoron.uhabits.models;
|
|||||||
|
|
||||||
import android.database.Cursor;
|
import android.database.Cursor;
|
||||||
import android.database.sqlite.SQLiteDatabase;
|
import android.database.sqlite.SQLiteDatabase;
|
||||||
import android.database.sqlite.SQLiteStatement;
|
|
||||||
import android.support.annotation.NonNull;
|
import android.support.annotation.NonNull;
|
||||||
import android.support.annotation.Nullable;
|
import android.support.annotation.Nullable;
|
||||||
|
|
||||||
import com.activeandroid.Cache;
|
import com.activeandroid.Cache;
|
||||||
import com.activeandroid.query.Delete;
|
|
||||||
import com.activeandroid.query.From;
|
|
||||||
import com.activeandroid.query.Select;
|
|
||||||
import com.activeandroid.util.SQLiteUtils;
|
|
||||||
|
|
||||||
import org.isoron.uhabits.utils.DatabaseUtils;
|
|
||||||
import org.isoron.uhabits.utils.DateUtils;
|
import org.isoron.uhabits.utils.DateUtils;
|
||||||
import org.isoron.uhabits.utils.InterfaceUtils;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.Writer;
|
import java.io.Writer;
|
||||||
import java.text.SimpleDateFormat;
|
import java.text.SimpleDateFormat;
|
||||||
import java.util.Date;
|
import java.util.Date;
|
||||||
|
import java.util.LinkedList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
public class ScoreList
|
public abstract class ScoreList
|
||||||
{
|
{
|
||||||
@NonNull
|
protected final Habit habit;
|
||||||
private Habit habit;
|
|
||||||
public ModelObservable observable = new ModelObservable();
|
protected ModelObservable observable;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Constructs a new ScoreList associated with the given habit.
|
* 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 this list should be associated with
|
* @param habit the habit to which the scores belong.
|
||||||
*/
|
*/
|
||||||
public ScoreList(@NonNull Habit habit)
|
public ScoreList(Habit habit)
|
||||||
{
|
{
|
||||||
this.habit = habit;
|
this.habit = habit;
|
||||||
}
|
observable = new ModelObservable();
|
||||||
|
|
||||||
protected From select()
|
|
||||||
{
|
|
||||||
return new Select()
|
|
||||||
.from(Score.class)
|
|
||||||
.where("habit = ?", habit.getId())
|
|
||||||
.orderBy("timestamp desc");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Marks all scores that have timestamp equal to or newer than the given timestamp as invalid.
|
* Returns the values of all the scores, from day of the first repetition
|
||||||
* Any following getValue calls will trigger the scores to be recomputed.
|
* until today, grouped in chunks of specified size.
|
||||||
*
|
* <p>
|
||||||
* @param timestamp the oldest timestamp that should be invalidated
|
* If the group size is one, then the value of each score is returned
|
||||||
*/
|
* individually. If the group is, for example, seven, then the days are
|
||||||
public void invalidateNewerThan(long timestamp)
|
* grouped in groups of seven consecutive days.
|
||||||
{
|
* <p>
|
||||||
new Delete().from(Score.class)
|
* The values are returned in an array of integers, with one entry for each
|
||||||
.where("habit = ?", habit.getId())
|
* group of days in the interval. This value corresponds to the average of
|
||||||
.and("timestamp >= ?", timestamp)
|
* the scores for the days inside the group. The first entry corresponds to
|
||||||
.execute();
|
* the ending of the interval (that is, the most recent group of days). The
|
||||||
|
* last entry corresponds to the beginning of the interval. As usual, the
|
||||||
observable.notifyListeners();
|
* time of the day for the timestamps should be midnight (UTC). The
|
||||||
}
|
* endpoints of the interval are included.
|
||||||
|
* <p>
|
||||||
/**
|
* The values are returned in an integer array. There is one entry for each
|
||||||
* Computes and saves the scores that are missing since the first repetition of the habit.
|
* day inside the interval. The first entry corresponds to today, while the
|
||||||
*/
|
* last entry corresponds to the day of the oldest repetition.
|
||||||
private void computeAll()
|
|
||||||
{
|
|
||||||
long fromTimestamp = habit.repetitions.getOldestTimestamp();
|
|
||||||
if(fromTimestamp == 0) return;
|
|
||||||
|
|
||||||
long toTimestamp = DateUtils.getStartOfToday();
|
|
||||||
compute(fromTimestamp, toTimestamp);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Computes and saves the scores that are missing inside a given time interval. Scores that
|
|
||||||
* have already been computed are skipped, therefore there is no harm in calling this function
|
|
||||||
* more times, or with larger intervals, than strictly needed. The endpoints of the interval are
|
|
||||||
* included.
|
|
||||||
*
|
|
||||||
* This function assumes that there are no gaps on the scores. That is, if the newest score has
|
|
||||||
* timestamp t, then every score with timestamp lower than t has already been computed.
|
|
||||||
*
|
|
||||||
* @param from timestamp of the beginning of the interval
|
|
||||||
* @param to timestamp of the end of the time interval
|
|
||||||
*/
|
|
||||||
protected void compute(long from, long to)
|
|
||||||
{
|
|
||||||
InterfaceUtils.throwIfMainThread();
|
|
||||||
|
|
||||||
final long day = DateUtils.millisecondsInOneDay;
|
|
||||||
final double freq = ((double) habit.freqNum) / habit.freqDen;
|
|
||||||
|
|
||||||
int newestScoreValue = findNewestValue();
|
|
||||||
long newestTimestamp = findNewestTimestamp();
|
|
||||||
|
|
||||||
if(newestTimestamp > 0)
|
|
||||||
from = newestTimestamp + day;
|
|
||||||
|
|
||||||
final int checkmarkValues[] = habit.checkmarks.getValues(from, to);
|
|
||||||
final long beginning = from;
|
|
||||||
|
|
||||||
int lastScore = newestScoreValue;
|
|
||||||
int size = checkmarkValues.length;
|
|
||||||
|
|
||||||
long timestamps[] = new long[size];
|
|
||||||
long values[] = new long[size];
|
|
||||||
|
|
||||||
for (int i = 0; i < checkmarkValues.length; i++)
|
|
||||||
{
|
|
||||||
int checkmarkValue = checkmarkValues[checkmarkValues.length - i - 1];
|
|
||||||
lastScore = Score.compute(freq, lastScore, checkmarkValue);
|
|
||||||
timestamps[i] = beginning + day * i;
|
|
||||||
values[i] = lastScore;
|
|
||||||
}
|
|
||||||
|
|
||||||
insert(timestamps, values);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the value of the most recent score that was already computed. If no score has been
|
|
||||||
* computed yet, returns zero.
|
|
||||||
*
|
|
||||||
* @return value of newest score, or zero if none exist
|
|
||||||
*/
|
|
||||||
protected int findNewestValue()
|
|
||||||
{
|
|
||||||
String args[] = { habit.getId().toString() };
|
|
||||||
String query = "select score from Score where habit = ? order by timestamp desc limit 1";
|
|
||||||
return SQLiteUtils.intQuery(query, args);
|
|
||||||
}
|
|
||||||
|
|
||||||
private long findNewestTimestamp()
|
|
||||||
{
|
|
||||||
String args[] = { habit.getId().toString() };
|
|
||||||
String query = "select timestamp from Score where habit = ? order by timestamp desc limit 1";
|
|
||||||
return DatabaseUtils.longQuery(query, args);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void insert(long timestamps[], long values[])
|
|
||||||
{
|
|
||||||
String query = "insert into Score(habit, timestamp, score) values (?,?,?)";
|
|
||||||
|
|
||||||
SQLiteDatabase db = Cache.openDatabase();
|
|
||||||
db.beginTransaction();
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
SQLiteStatement statement = db.compileStatement(query);
|
|
||||||
|
|
||||||
for (int i = 0; i < timestamps.length; i++)
|
|
||||||
{
|
|
||||||
statement.bindLong(1, habit.getId());
|
|
||||||
statement.bindLong(2, timestamps[i]);
|
|
||||||
statement.bindLong(3, values[i]);
|
|
||||||
statement.execute();
|
|
||||||
}
|
|
||||||
|
|
||||||
db.setTransactionSuccessful();
|
|
||||||
}
|
|
||||||
finally
|
|
||||||
{
|
|
||||||
db.endTransaction();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the score for a certain day.
|
|
||||||
*
|
|
||||||
* @param timestamp the timestamp for the day
|
|
||||||
* @return the score for the day
|
|
||||||
*/
|
|
||||||
@Nullable
|
|
||||||
protected Score get(long timestamp)
|
|
||||||
{
|
|
||||||
Repetition oldestRep = habit.repetitions.getOldest();
|
|
||||||
if(oldestRep == null) return null;
|
|
||||||
|
|
||||||
compute(oldestRep.timestamp, timestamp);
|
|
||||||
|
|
||||||
return select().where("timestamp = ?", timestamp).executeSingle();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the value of the score for a given day.
|
|
||||||
*
|
|
||||||
* @param timestamp the timestamp of a day
|
|
||||||
* @return score for that day
|
|
||||||
*/
|
|
||||||
public int getValue(long timestamp)
|
|
||||||
{
|
|
||||||
computeAll();
|
|
||||||
String[] args = { habit.getId().toString(), Long.toString(timestamp) };
|
|
||||||
return SQLiteUtils.intQuery("select score from Score where habit = ? and timestamp = ?", args);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the values of all the scores, from day of the first repetition until today, grouped
|
|
||||||
* in chunks of specified size.
|
|
||||||
*
|
|
||||||
* If the group size is one, then the value of each score is returned individually. If the group
|
|
||||||
* is, for example, seven, then the days are grouped in groups of seven consecutive days.
|
|
||||||
*
|
|
||||||
* The values are returned in an array of integers, with one entry for each group of days in the
|
|
||||||
* interval. This value corresponds to the average of the scores for the days inside the group.
|
|
||||||
* The first entry corresponds to the ending of the interval (that is, the most recent group of
|
|
||||||
* days). The last entry corresponds to the beginning of the interval. As usual, the time of the
|
|
||||||
* day for the timestamps should be midnight (UTC). The endpoints of the interval are included.
|
|
||||||
*
|
|
||||||
* The values are returned in an integer array. There is one entry for each day inside the
|
|
||||||
* interval. The first entry corresponds to today, while the last entry corresponds to the
|
|
||||||
* day of the oldest repetition.
|
|
||||||
*
|
*
|
||||||
* @param divisor the size of the groups
|
* @param divisor the size of the groups
|
||||||
* @return array of values, with one entry for each group of days
|
* @return array of values, with one entry for each group of days
|
||||||
@@ -237,64 +81,17 @@ public class ScoreList
|
|||||||
@NonNull
|
@NonNull
|
||||||
public int[] getAllValues(long divisor)
|
public int[] getAllValues(long divisor)
|
||||||
{
|
{
|
||||||
Repetition oldestRep = habit.repetitions.getOldest();
|
Repetition oldestRep = habit.getRepetitions().getOldest();
|
||||||
if (oldestRep == null) return new int[0];
|
if (oldestRep == null) return new int[0];
|
||||||
|
|
||||||
long fromTimestamp = oldestRep.timestamp;
|
long fromTimestamp = oldestRep.getTimestamp();
|
||||||
long toTimestamp = DateUtils.getStartOfToday();
|
long toTimestamp = DateUtils.getStartOfToday();
|
||||||
return getValues(fromTimestamp, toTimestamp, divisor);
|
return getValues(fromTimestamp, toTimestamp, divisor);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
public ModelObservable getObservable()
|
||||||
* Same as getAllValues(long), but using a specified interval.
|
|
||||||
*
|
|
||||||
* @param from beginning of the interval (included)
|
|
||||||
* @param to end of the interval (included)
|
|
||||||
* @param divisor size of the groups
|
|
||||||
* @return array of values, with one entry for each group of days
|
|
||||||
*/
|
|
||||||
@NonNull
|
|
||||||
protected int[] getValues(long from, long to, long divisor)
|
|
||||||
{
|
{
|
||||||
compute(from, to);
|
return observable;
|
||||||
|
|
||||||
divisor *= DateUtils.millisecondsInOneDay;
|
|
||||||
Long offset = to + divisor;
|
|
||||||
|
|
||||||
String query = "select ((timestamp - ?) / ?) as time, avg(score) from Score " +
|
|
||||||
"where habit = ? and timestamp >= ? and timestamp <= ? " +
|
|
||||||
"group by time order by time desc";
|
|
||||||
|
|
||||||
String params[] = { offset.toString(), Long.toString(divisor), habit.getId().toString(),
|
|
||||||
Long.toString(from), Long.toString(to) };
|
|
||||||
|
|
||||||
SQLiteDatabase db = Cache.openDatabase();
|
|
||||||
Cursor cursor = db.rawQuery(query, params);
|
|
||||||
|
|
||||||
if(!cursor.moveToFirst()) return new int[0];
|
|
||||||
|
|
||||||
int k = 0;
|
|
||||||
int[] scores = new int[cursor.getCount()];
|
|
||||||
|
|
||||||
do
|
|
||||||
{
|
|
||||||
scores[k++] = (int) cursor.getFloat(1);
|
|
||||||
}
|
|
||||||
while (cursor.moveToNext());
|
|
||||||
|
|
||||||
cursor.close();
|
|
||||||
return scores;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the score for today.
|
|
||||||
*
|
|
||||||
* @return score for today
|
|
||||||
*/
|
|
||||||
@Nullable
|
|
||||||
protected Score getToday()
|
|
||||||
{
|
|
||||||
return get(DateUtils.getStartOfToday());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -308,17 +105,21 @@ public class ScoreList
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the star status for today. The returned value is either Score.EMPTY_STAR,
|
* Returns the value of the score for a given day.
|
||||||
* Score.HALF_STAR or Score.FULL_STAR.
|
|
||||||
*
|
*
|
||||||
* @return star status for today
|
* @param timestamp the timestamp of a day
|
||||||
|
* @return score for that day
|
||||||
*/
|
*/
|
||||||
public int getTodayStarStatus()
|
public abstract int getValue(long timestamp);
|
||||||
{
|
|
||||||
Score score = getToday();
|
/**
|
||||||
if(score != null) return score.getStarStatus();
|
* Marks all scores that have timestamp equal to or newer than the given
|
||||||
else return Score.EMPTY_STAR;
|
* timestamp as invalid. Any following getValue calls will trigger the
|
||||||
}
|
* scores to be recomputed.
|
||||||
|
*
|
||||||
|
* @param timestamp the oldest timestamp that should be invalidated
|
||||||
|
*/
|
||||||
|
public abstract void invalidateNewerThan(long timestamp);
|
||||||
|
|
||||||
public void writeCSV(Writer out) throws IOException
|
public void writeCSV(Writer out) throws IOException
|
||||||
{
|
{
|
||||||
@@ -326,7 +127,8 @@ public class ScoreList
|
|||||||
|
|
||||||
SimpleDateFormat dateFormat = DateUtils.getCSVDateFormat();
|
SimpleDateFormat dateFormat = DateUtils.getCSVDateFormat();
|
||||||
|
|
||||||
String query = "select timestamp, score from score where habit = ? order by timestamp";
|
String query =
|
||||||
|
"select timestamp, score from score where habit = ? order by timestamp";
|
||||||
String params[] = {habit.getId().toString()};
|
String params[] = {habit.getId().toString()};
|
||||||
|
|
||||||
SQLiteDatabase db = Cache.openDatabase();
|
SQLiteDatabase db = Cache.openDatabase();
|
||||||
@@ -337,7 +139,8 @@ public class ScoreList
|
|||||||
do
|
do
|
||||||
{
|
{
|
||||||
String timestamp = dateFormat.format(new Date(cursor.getLong(0)));
|
String timestamp = dateFormat.format(new Date(cursor.getLong(0)));
|
||||||
String score = String.format("%.4f", ((float) cursor.getInt(1)) / Score.MAX_VALUE);
|
String score = String.format("%.4f",
|
||||||
|
((float) cursor.getInt(1)) / Score.MAX_VALUE);
|
||||||
out.write(String.format("%s,%s\n", timestamp, score));
|
out.write(String.format("%s,%s\n", timestamp, score));
|
||||||
|
|
||||||
} while (cursor.moveToNext());
|
} while (cursor.moveToNext());
|
||||||
@@ -345,4 +148,95 @@ public class ScoreList
|
|||||||
cursor.close();
|
cursor.close();
|
||||||
out.close();
|
out.close();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected abstract void add(List<Score> scores);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Computes and saves the scores that are missing inside a given time
|
||||||
|
* interval.
|
||||||
|
* <p>
|
||||||
|
* Scores that have already been computed are skipped, therefore there is no
|
||||||
|
* harm in calling this function more times, or with larger intervals, than
|
||||||
|
* strictly needed. The endpoints of the interval are included.
|
||||||
|
* <p>
|
||||||
|
* This function assumes that there are no gaps on the scores. That is, if
|
||||||
|
* the newest score has timestamp t, then every score with timestamp lower
|
||||||
|
* than t has already been computed.
|
||||||
|
*
|
||||||
|
* @param from timestamp of the beginning of the interval
|
||||||
|
* @param to timestamp of the end of the time interval
|
||||||
|
*/
|
||||||
|
protected void compute(long from, long to)
|
||||||
|
{
|
||||||
|
final long day = DateUtils.millisecondsInOneDay;
|
||||||
|
final double freq = ((double) habit.getFreqNum()) / habit.getFreqDen();
|
||||||
|
|
||||||
|
int newestValue = 0;
|
||||||
|
long newestTimestamp = 0;
|
||||||
|
|
||||||
|
Score newest = getNewestComputed();
|
||||||
|
if(newest != null)
|
||||||
|
{
|
||||||
|
newestValue = newest.getValue();
|
||||||
|
newestTimestamp = newest.getTimestamp();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (newestTimestamp > 0) from = newestTimestamp + day;
|
||||||
|
|
||||||
|
final int checkmarkValues[] = habit.getCheckmarks().getValues(from, to);
|
||||||
|
final long beginning = from;
|
||||||
|
|
||||||
|
int lastScore = newestValue;
|
||||||
|
List<Score> scores = new LinkedList<>();
|
||||||
|
|
||||||
|
for (int i = 0; i < checkmarkValues.length; i++)
|
||||||
|
{
|
||||||
|
int value = checkmarkValues[checkmarkValues.length - i - 1];
|
||||||
|
lastScore = Score.compute(freq, lastScore, value);
|
||||||
|
scores.add(new Score(habit, beginning + day * i, lastScore));
|
||||||
|
}
|
||||||
|
|
||||||
|
add(scores);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Computes and saves the scores that are missing since the first repetition
|
||||||
|
* of the habit.
|
||||||
|
*/
|
||||||
|
protected void computeAll()
|
||||||
|
{
|
||||||
|
Repetition oldestRep = habit.getRepetitions().getOldest();
|
||||||
|
if (oldestRep == null) return;
|
||||||
|
|
||||||
|
long toTimestamp = DateUtils.getStartOfToday();
|
||||||
|
compute(oldestRep.getTimestamp(), toTimestamp);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the score for a certain day.
|
||||||
|
*
|
||||||
|
* @param timestamp the timestamp for the day
|
||||||
|
* @return the score for the day
|
||||||
|
*/
|
||||||
|
protected abstract Score get(long timestamp);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the most recent score that was already computed.
|
||||||
|
* <p>
|
||||||
|
* If no score has been computed yet, returns null.
|
||||||
|
*
|
||||||
|
* @return the newest score computed, or null if none exist
|
||||||
|
*/
|
||||||
|
@Nullable
|
||||||
|
protected abstract Score getNewestComputed();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Same as getAllValues(long), but using a specified interval.
|
||||||
|
*
|
||||||
|
* @param from beginning of the interval (included)
|
||||||
|
* @param to end of the interval (included)
|
||||||
|
* @param divisor size of the groups
|
||||||
|
* @return array of values, with one entry for each group of days
|
||||||
|
*/
|
||||||
|
protected abstract int[] getValues(long from, long to, long divisor);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -19,20 +19,63 @@
|
|||||||
|
|
||||||
package org.isoron.uhabits.models;
|
package org.isoron.uhabits.models;
|
||||||
|
|
||||||
import com.activeandroid.Model;
|
import org.apache.commons.lang3.builder.ToStringBuilder;
|
||||||
import com.activeandroid.annotation.Column;
|
import org.isoron.uhabits.utils.DateUtils;
|
||||||
|
|
||||||
public class Streak extends Model
|
public class Streak
|
||||||
{
|
{
|
||||||
@Column(name = "habit")
|
private Habit habit;
|
||||||
public Habit habit;
|
|
||||||
|
|
||||||
@Column(name = "start")
|
private long start;
|
||||||
public Long start;
|
|
||||||
|
|
||||||
@Column(name = "end")
|
private long end;
|
||||||
public Long end;
|
|
||||||
|
|
||||||
@Column(name = "length")
|
public Streak(Habit habit, long start, long end)
|
||||||
public Long length;
|
{
|
||||||
|
this.habit = habit;
|
||||||
|
this.start = start;
|
||||||
|
this.end = end;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int compareLonger(Streak other)
|
||||||
|
{
|
||||||
|
if (this.getLength() != other.getLength())
|
||||||
|
return Long.signum(this.getLength() - other.getLength());
|
||||||
|
|
||||||
|
return Long.signum(this.getEnd() - other.getEnd());
|
||||||
|
}
|
||||||
|
|
||||||
|
public int compareNewer(Streak other)
|
||||||
|
{
|
||||||
|
return Long.signum(this.getEnd() - other.getEnd());
|
||||||
|
}
|
||||||
|
|
||||||
|
public long getEnd()
|
||||||
|
{
|
||||||
|
return end;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Habit getHabit()
|
||||||
|
{
|
||||||
|
return habit;
|
||||||
|
}
|
||||||
|
|
||||||
|
public long getLength()
|
||||||
|
{
|
||||||
|
return (end - start) / DateUtils.millisecondsInOneDay + 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
public long getStart()
|
||||||
|
{
|
||||||
|
return start;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString()
|
||||||
|
{
|
||||||
|
return new ToStringBuilder(this)
|
||||||
|
.append("start", start)
|
||||||
|
.append("end", end)
|
||||||
|
.toString();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -19,100 +19,123 @@
|
|||||||
|
|
||||||
package org.isoron.uhabits.models;
|
package org.isoron.uhabits.models;
|
||||||
|
|
||||||
import android.database.Cursor;
|
import android.support.annotation.NonNull;
|
||||||
import android.database.sqlite.SQLiteDatabase;
|
import android.support.annotation.Nullable;
|
||||||
|
|
||||||
import com.activeandroid.ActiveAndroid;
|
|
||||||
import com.activeandroid.Cache;
|
|
||||||
import com.activeandroid.query.Delete;
|
|
||||||
import com.activeandroid.query.Select;
|
|
||||||
|
|
||||||
import org.isoron.uhabits.utils.DateUtils;
|
import org.isoron.uhabits.utils.DateUtils;
|
||||||
import org.isoron.uhabits.utils.InterfaceUtils;
|
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
|
import java.util.Collections;
|
||||||
import java.util.LinkedList;
|
import java.util.LinkedList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
public class StreakList
|
/**
|
||||||
|
* The collection of {@link Streak}s that belong to a habit.
|
||||||
|
* <p>
|
||||||
|
* This list is populated automatically from the list of repetitions.
|
||||||
|
*/
|
||||||
|
public abstract class StreakList
|
||||||
{
|
{
|
||||||
private Habit habit;
|
protected final Habit habit;
|
||||||
public ModelObservable observable = new ModelObservable();
|
|
||||||
|
|
||||||
public StreakList(Habit habit)
|
protected ModelObservable observable;
|
||||||
|
|
||||||
|
protected StreakList(Habit habit)
|
||||||
{
|
{
|
||||||
this.habit = habit;
|
this.habit = habit;
|
||||||
|
observable = new ModelObservable();
|
||||||
}
|
}
|
||||||
|
|
||||||
public List<Streak> getAll(int limit)
|
public abstract List<Streak> getAll();
|
||||||
|
|
||||||
|
public List<Streak> getBest(int limit)
|
||||||
{
|
{
|
||||||
rebuild();
|
List<Streak> streaks = getAll();
|
||||||
|
Collections.sort(streaks, (s1, s2) -> s2.compareLonger(s1));
|
||||||
String query = "select * from (select * from streak where habit=? " +
|
streaks = streaks.subList(0, Math.min(streaks.size(), limit));
|
||||||
"order by end <> ?, length desc, end desc limit ?) order by end desc";
|
Collections.sort(streaks, (s1, s2) -> s2.compareNewer(s1));
|
||||||
|
|
||||||
String params[] = {habit.getId().toString(), Long.toString(DateUtils.getStartOfToday()),
|
|
||||||
Integer.toString(limit)};
|
|
||||||
|
|
||||||
SQLiteDatabase db = Cache.openDatabase();
|
|
||||||
Cursor cursor = db.rawQuery(query, params);
|
|
||||||
|
|
||||||
if(!cursor.moveToFirst())
|
|
||||||
{
|
|
||||||
cursor.close();
|
|
||||||
return new LinkedList<>();
|
|
||||||
}
|
|
||||||
|
|
||||||
List<Streak> streaks = new LinkedList<>();
|
|
||||||
|
|
||||||
do
|
|
||||||
{
|
|
||||||
Streak s = Streak.load(Streak.class, cursor.getInt(0));
|
|
||||||
streaks.add(s);
|
|
||||||
}
|
|
||||||
while (cursor.moveToNext());
|
|
||||||
|
|
||||||
cursor.close();
|
|
||||||
return streaks;
|
return streaks;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public Streak getNewest()
|
public abstract Streak getNewestComputed();
|
||||||
|
|
||||||
|
public ModelObservable getObservable()
|
||||||
{
|
{
|
||||||
return new Select().from(Streak.class)
|
return observable;
|
||||||
.where("habit = ?", habit.getId())
|
|
||||||
.orderBy("end desc")
|
|
||||||
.limit(1)
|
|
||||||
.executeSingle();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public abstract void invalidateNewerThan(long timestamp);
|
||||||
|
|
||||||
public void rebuild()
|
public void rebuild()
|
||||||
{
|
{
|
||||||
InterfaceUtils.throwIfMainThread();
|
|
||||||
|
|
||||||
long beginning;
|
|
||||||
long today = DateUtils.getStartOfToday();
|
long today = DateUtils.getStartOfToday();
|
||||||
|
|
||||||
|
Long beginning = findBeginning();
|
||||||
|
if (beginning == null || beginning > today) return;
|
||||||
|
|
||||||
|
int checks[] = habit.getCheckmarks().getValues(beginning, today);
|
||||||
|
List<Streak> streaks = checkmarksToStreaks(beginning, checks);
|
||||||
|
|
||||||
|
removeNewestComputed();
|
||||||
|
insert(streaks);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Converts a list of checkmark values to a list of streaks.
|
||||||
|
*
|
||||||
|
* @param beginning the timestamp corresponding to the first checkmark
|
||||||
|
* value.
|
||||||
|
* @param checks the checkmarks values, ordered by decreasing timestamp.
|
||||||
|
* @return the list of streaks.
|
||||||
|
*/
|
||||||
|
@NonNull
|
||||||
|
protected List<Streak> checkmarksToStreaks(Long beginning, int[] checks)
|
||||||
|
{
|
||||||
|
ArrayList<Long> transitions = getTransitions(beginning, checks);
|
||||||
|
|
||||||
|
List<Streak> streaks = new LinkedList<>();
|
||||||
|
for (int i = 0; i < transitions.size(); i += 2)
|
||||||
|
{
|
||||||
|
long start = transitions.get(i);
|
||||||
|
long end = transitions.get(i + 1);
|
||||||
|
streaks.add(new Streak(habit, start, end));
|
||||||
|
}
|
||||||
|
|
||||||
|
return streaks;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Finds the place where we should start when recomputing the streaks.
|
||||||
|
*
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
@Nullable
|
||||||
|
protected Long findBeginning()
|
||||||
|
{
|
||||||
|
Streak newestStreak = getNewestComputed();
|
||||||
|
if (newestStreak != null) return newestStreak.getStart();
|
||||||
|
|
||||||
|
Repetition oldestRep = habit.getRepetitions().getOldest();
|
||||||
|
if (oldestRep != null) return oldestRep.getTimestamp();
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the timestamps where there was a transition from performing a
|
||||||
|
* habit to not performing a habit, and vice-versa.
|
||||||
|
*
|
||||||
|
* @param beginning the timestamp for the first checkmark
|
||||||
|
* @param checks the checkmarks, ordered by decresing timestamp
|
||||||
|
* @return the list of transitions
|
||||||
|
*/
|
||||||
|
@NonNull
|
||||||
|
protected ArrayList<Long> getTransitions(Long beginning, int[] checks)
|
||||||
|
{
|
||||||
long day = DateUtils.millisecondsInOneDay;
|
long day = DateUtils.millisecondsInOneDay;
|
||||||
|
|
||||||
Streak newestStreak = getNewest();
|
|
||||||
if (newestStreak != null)
|
|
||||||
{
|
|
||||||
beginning = newestStreak.start;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
Repetition oldestRep = habit.repetitions.getOldest();
|
|
||||||
if (oldestRep == null) return;
|
|
||||||
|
|
||||||
beginning = oldestRep.timestamp;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (beginning > today) return;
|
|
||||||
|
|
||||||
int checks[] = habit.checkmarks.getValues(beginning, today);
|
|
||||||
ArrayList<Long> list = new ArrayList<>();
|
|
||||||
|
|
||||||
long current = beginning;
|
long current = beginning;
|
||||||
|
|
||||||
|
ArrayList<Long> list = new ArrayList<>();
|
||||||
list.add(current);
|
list.add(current);
|
||||||
|
|
||||||
for (int i = 1; i < checks.length; i++)
|
for (int i = 1; i < checks.length; i++)
|
||||||
@@ -126,38 +149,10 @@ public class StreakList
|
|||||||
|
|
||||||
if (list.size() % 2 == 1) list.add(current);
|
if (list.size() % 2 == 1) list.add(current);
|
||||||
|
|
||||||
ActiveAndroid.beginTransaction();
|
return list;
|
||||||
|
|
||||||
if(newestStreak != null) newestStreak.delete();
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
for (int i = 0; i < list.size(); i += 2)
|
|
||||||
{
|
|
||||||
Streak streak = new Streak();
|
|
||||||
streak.habit = habit;
|
|
||||||
streak.start = list.get(i);
|
|
||||||
streak.end = list.get(i + 1);
|
|
||||||
streak.length = (streak.end - streak.start) / day + 1;
|
|
||||||
streak.save();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
ActiveAndroid.setTransactionSuccessful();
|
protected abstract void insert(List<Streak> streaks);
|
||||||
}
|
|
||||||
finally
|
|
||||||
{
|
|
||||||
ActiveAndroid.endTransaction();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
protected abstract void removeNewestComputed();
|
||||||
public void deleteNewerThan(long timestamp)
|
|
||||||
{
|
|
||||||
new Delete().from(Streak.class)
|
|
||||||
.where("habit = ?", habit.getId())
|
|
||||||
.and("end >= ?", timestamp - DateUtils.millisecondsInOneDay)
|
|
||||||
.execute();
|
|
||||||
|
|
||||||
observable.notifyListeners();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,102 @@
|
|||||||
|
/*
|
||||||
|
* 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.models.memory;
|
||||||
|
|
||||||
|
import org.isoron.uhabits.models.Checkmark;
|
||||||
|
import org.isoron.uhabits.models.CheckmarkList;
|
||||||
|
import org.isoron.uhabits.models.Habit;
|
||||||
|
import org.isoron.uhabits.utils.DateUtils;
|
||||||
|
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.LinkedList;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* In-memory implementation of {@link CheckmarkList}.
|
||||||
|
*/
|
||||||
|
public class MemoryCheckmarkList extends CheckmarkList
|
||||||
|
{
|
||||||
|
LinkedList<Checkmark> list;
|
||||||
|
|
||||||
|
public MemoryCheckmarkList(Habit habit)
|
||||||
|
{
|
||||||
|
super(habit);
|
||||||
|
list = new LinkedList<>();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int[] getValues(long from, long to)
|
||||||
|
{
|
||||||
|
compute(from, to);
|
||||||
|
if (from > to) return new int[0];
|
||||||
|
|
||||||
|
int length = (int) ((to - from) / DateUtils.millisecondsInOneDay + 1);
|
||||||
|
int values[] = new int[length];
|
||||||
|
|
||||||
|
int k = 0;
|
||||||
|
for (Checkmark c : list)
|
||||||
|
if(c.getTimestamp() >= from && c.getTimestamp() <= to)
|
||||||
|
values[k++] = c.getValue();
|
||||||
|
|
||||||
|
return values;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void invalidateNewerThan(long timestamp)
|
||||||
|
{
|
||||||
|
LinkedList<Checkmark> invalid = new LinkedList<>();
|
||||||
|
|
||||||
|
for (Checkmark c : list)
|
||||||
|
if (c.getTimestamp() >= timestamp) invalid.add(c);
|
||||||
|
|
||||||
|
list.removeAll(invalid);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected Checkmark getNewest()
|
||||||
|
{
|
||||||
|
long newestTimestamp = 0;
|
||||||
|
Checkmark newestCheck = null;
|
||||||
|
|
||||||
|
for (Checkmark c : list)
|
||||||
|
{
|
||||||
|
if (c.getTimestamp() > newestTimestamp)
|
||||||
|
{
|
||||||
|
newestCheck = c;
|
||||||
|
newestTimestamp = c.getTimestamp();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return newestCheck;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void insert(long[] timestamps, int[] values)
|
||||||
|
{
|
||||||
|
for (int i = 0; i < timestamps.length; i++)
|
||||||
|
{
|
||||||
|
long t = timestamps[i];
|
||||||
|
int v = values[i];
|
||||||
|
list.add(new Checkmark(habit, t, v));
|
||||||
|
}
|
||||||
|
|
||||||
|
Collections.sort(list,
|
||||||
|
(c1, c2) -> (int) (c2.getTimestamp() - c1.getTimestamp()));
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,111 @@
|
|||||||
|
/*
|
||||||
|
* 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.models.memory;
|
||||||
|
|
||||||
|
import android.support.annotation.NonNull;
|
||||||
|
import android.support.annotation.Nullable;
|
||||||
|
|
||||||
|
import org.isoron.uhabits.models.Habit;
|
||||||
|
import org.isoron.uhabits.models.HabitList;
|
||||||
|
|
||||||
|
import java.util.LinkedList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* In-memory implementation of {@link HabitList}.
|
||||||
|
*/
|
||||||
|
public class MemoryHabitList extends HabitList
|
||||||
|
{
|
||||||
|
@NonNull
|
||||||
|
private LinkedList<Habit> list;
|
||||||
|
|
||||||
|
public MemoryHabitList()
|
||||||
|
{
|
||||||
|
list = new LinkedList<>();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void add(Habit habit)
|
||||||
|
{
|
||||||
|
list.addLast(habit);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int count()
|
||||||
|
{
|
||||||
|
int count = 0;
|
||||||
|
for (Habit h : list) if (!h.isArchived()) count++;
|
||||||
|
return count;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int countWithArchived()
|
||||||
|
{
|
||||||
|
return list.size();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Habit getById(long id)
|
||||||
|
{
|
||||||
|
for (Habit h : list) if (h.getId() == id) return h;
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
@Override
|
||||||
|
public List<Habit> getAll(boolean includeArchive)
|
||||||
|
{
|
||||||
|
if (includeArchive) return new LinkedList<>(list);
|
||||||
|
return getFiltered(habit -> !habit.isArchived());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
@Override
|
||||||
|
public Habit getByPosition(int position)
|
||||||
|
{
|
||||||
|
return list.get(position);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int indexOf(Habit h)
|
||||||
|
{
|
||||||
|
return list.indexOf(h);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void remove(@NonNull Habit habit)
|
||||||
|
{
|
||||||
|
list.remove(habit);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void reorder(Habit from, Habit to)
|
||||||
|
{
|
||||||
|
int toPos = indexOf(to);
|
||||||
|
list.remove(from);
|
||||||
|
list.add(toPos, from);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void update(List<Habit> habits)
|
||||||
|
{
|
||||||
|
// NOP
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,61 @@
|
|||||||
|
/*
|
||||||
|
* 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.models.memory;
|
||||||
|
|
||||||
|
import org.isoron.uhabits.models.CheckmarkList;
|
||||||
|
import org.isoron.uhabits.models.Habit;
|
||||||
|
import org.isoron.uhabits.models.HabitList;
|
||||||
|
import org.isoron.uhabits.models.ModelFactory;
|
||||||
|
import org.isoron.uhabits.models.RepetitionList;
|
||||||
|
import org.isoron.uhabits.models.ScoreList;
|
||||||
|
import org.isoron.uhabits.models.StreakList;
|
||||||
|
|
||||||
|
public class MemoryModelFactory implements ModelFactory
|
||||||
|
{
|
||||||
|
@Override
|
||||||
|
public RepetitionList buidRepetitionList(Habit habit)
|
||||||
|
{
|
||||||
|
return new MemoryRepetitionList(habit);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public HabitList buildHabitList()
|
||||||
|
{
|
||||||
|
return new MemoryHabitList();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public CheckmarkList buildCheckmarkList(Habit habit)
|
||||||
|
{
|
||||||
|
return new MemoryCheckmarkList(habit);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ScoreList buildScoreList(Habit habit)
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public StreakList buildStreakList(Habit habit)
|
||||||
|
{
|
||||||
|
return new MemoryStreakList(habit);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,106 @@
|
|||||||
|
/*
|
||||||
|
* 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.models.memory;
|
||||||
|
|
||||||
|
import android.support.annotation.NonNull;
|
||||||
|
import android.support.annotation.Nullable;
|
||||||
|
|
||||||
|
import org.isoron.uhabits.models.Habit;
|
||||||
|
import org.isoron.uhabits.models.Repetition;
|
||||||
|
import org.isoron.uhabits.models.RepetitionList;
|
||||||
|
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.LinkedList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* In-memory implementation of {@link RepetitionList}.
|
||||||
|
*/
|
||||||
|
public class MemoryRepetitionList extends RepetitionList
|
||||||
|
{
|
||||||
|
LinkedList<Repetition> list;
|
||||||
|
|
||||||
|
public MemoryRepetitionList(Habit habit)
|
||||||
|
{
|
||||||
|
super(habit);
|
||||||
|
list = new LinkedList<>();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void add(Repetition repetition)
|
||||||
|
{
|
||||||
|
list.add(repetition);
|
||||||
|
observable.notifyListeners();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<Repetition> getByInterval(long fromTimestamp, long toTimestamp)
|
||||||
|
{
|
||||||
|
LinkedList<Repetition> filtered = new LinkedList<>();
|
||||||
|
for (Repetition r : list)
|
||||||
|
{
|
||||||
|
long t = r.getTimestamp();
|
||||||
|
if (t >= fromTimestamp && t <= toTimestamp) filtered.add(r);
|
||||||
|
}
|
||||||
|
|
||||||
|
Collections.sort(filtered,
|
||||||
|
(r1, r2) -> (int) (r1.getTimestamp() - r2.getTimestamp()));
|
||||||
|
|
||||||
|
return filtered;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
@Override
|
||||||
|
public Repetition getByTimestamp(long timestamp)
|
||||||
|
{
|
||||||
|
for (Repetition r : list)
|
||||||
|
if (r.getTimestamp() == timestamp) return r;
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
@Override
|
||||||
|
public Repetition getOldest()
|
||||||
|
{
|
||||||
|
long oldestTime = Long.MAX_VALUE;
|
||||||
|
Repetition oldestRep = null;
|
||||||
|
|
||||||
|
for (Repetition rep : list)
|
||||||
|
{
|
||||||
|
if (rep.getTimestamp() < oldestTime)
|
||||||
|
{
|
||||||
|
oldestRep = rep;
|
||||||
|
oldestTime = rep.getTimestamp();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
return oldestRep;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void remove(@NonNull Repetition repetition)
|
||||||
|
{
|
||||||
|
list.remove(repetition);
|
||||||
|
observable.notifyListeners();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,86 @@
|
|||||||
|
/*
|
||||||
|
* 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.models.memory;
|
||||||
|
|
||||||
|
import org.isoron.uhabits.models.Habit;
|
||||||
|
import org.isoron.uhabits.models.Streak;
|
||||||
|
import org.isoron.uhabits.models.StreakList;
|
||||||
|
import org.isoron.uhabits.utils.DateUtils;
|
||||||
|
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.LinkedList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
public class MemoryStreakList extends StreakList
|
||||||
|
{
|
||||||
|
LinkedList<Streak> list;
|
||||||
|
|
||||||
|
public MemoryStreakList(Habit habit)
|
||||||
|
{
|
||||||
|
super(habit);
|
||||||
|
list = new LinkedList<>();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Streak getNewestComputed()
|
||||||
|
{
|
||||||
|
Streak newest = null;
|
||||||
|
|
||||||
|
for(Streak s : list)
|
||||||
|
if(newest == null || s.getEnd() > newest.getEnd())
|
||||||
|
newest = s;
|
||||||
|
|
||||||
|
return newest;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void invalidateNewerThan(long timestamp)
|
||||||
|
{
|
||||||
|
LinkedList<Streak> discard = new LinkedList<>();
|
||||||
|
|
||||||
|
for(Streak s : list)
|
||||||
|
if(s.getEnd() >= timestamp - DateUtils.millisecondsInOneDay)
|
||||||
|
discard.add(s);
|
||||||
|
|
||||||
|
list.removeAll(discard);
|
||||||
|
observable.notifyListeners();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void insert(List<Streak> streaks)
|
||||||
|
{
|
||||||
|
list.addAll(streaks);
|
||||||
|
Collections.sort(list, (s1, s2) -> s2.compareNewer(s1));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void removeNewestComputed()
|
||||||
|
{
|
||||||
|
Streak newest = getNewestComputed();
|
||||||
|
if(newest != null) list.remove(newest);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<Streak> getAll()
|
||||||
|
{
|
||||||
|
rebuild();
|
||||||
|
return new LinkedList<>(list);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
/*
|
||||||
|
* 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/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Provides in-memory implementation of core models.
|
||||||
|
*/
|
||||||
|
package org.isoron.uhabits.models.memory;
|
||||||
@@ -0,0 +1,24 @@
|
|||||||
|
/*
|
||||||
|
* 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 Licenses along
|
||||||
|
* with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Provides core models classes, such as {@link org.isoron.uhabits.models.Habit}
|
||||||
|
* and {@link org.isoron.uhabits.models.Repetition}.
|
||||||
|
*/
|
||||||
|
package org.isoron.uhabits.models;
|
||||||
@@ -0,0 +1,62 @@
|
|||||||
|
/*
|
||||||
|
* 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.models.sqlite;
|
||||||
|
|
||||||
|
import com.activeandroid.Model;
|
||||||
|
import com.activeandroid.annotation.Column;
|
||||||
|
import com.activeandroid.annotation.Table;
|
||||||
|
|
||||||
|
import org.isoron.uhabits.models.Checkmark;
|
||||||
|
import org.isoron.uhabits.models.Habit;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The SQLite database record corresponding to a {@link Checkmark}.
|
||||||
|
*/
|
||||||
|
@Table(name = "Checkmarks")
|
||||||
|
public class CheckmarkRecord extends Model
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* The habit to which this checkmark belongs.
|
||||||
|
*/
|
||||||
|
@Column(name = "habit")
|
||||||
|
public HabitRecord habit;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Timestamp of the day to which this checkmark corresponds. Time of the day
|
||||||
|
* must be midnight (UTC).
|
||||||
|
*/
|
||||||
|
@Column(name = "timestamp")
|
||||||
|
public Long timestamp;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Indicates whether there is a repetition at the given timestamp or not,
|
||||||
|
* and whether the repetition was expected. Assumes one of the values
|
||||||
|
* UNCHECKED, CHECKED_EXPLICITLY or CHECKED_IMPLICITLY.
|
||||||
|
*/
|
||||||
|
@Column(name = "value")
|
||||||
|
public Integer value;
|
||||||
|
|
||||||
|
public Checkmark toCheckmark()
|
||||||
|
{
|
||||||
|
SQLiteHabitList habitList = SQLiteHabitList.getInstance();
|
||||||
|
Habit h = habitList.getById(habit.getId());
|
||||||
|
return new Checkmark(h, timestamp, value);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,177 @@
|
|||||||
|
/*
|
||||||
|
* 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.models.sqlite;
|
||||||
|
|
||||||
|
import android.annotation.SuppressLint;
|
||||||
|
import android.support.annotation.NonNull;
|
||||||
|
import android.support.annotation.Nullable;
|
||||||
|
|
||||||
|
import com.activeandroid.Model;
|
||||||
|
import com.activeandroid.annotation.Column;
|
||||||
|
import com.activeandroid.annotation.Table;
|
||||||
|
import com.activeandroid.query.Delete;
|
||||||
|
import com.activeandroid.util.SQLiteUtils;
|
||||||
|
|
||||||
|
import org.isoron.uhabits.models.Habit;
|
||||||
|
import org.isoron.uhabits.utils.DatabaseUtils;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The SQLite database record corresponding to a {@link Habit}.
|
||||||
|
*/
|
||||||
|
@Table(name = "Habits")
|
||||||
|
public class HabitRecord extends Model
|
||||||
|
{
|
||||||
|
public static final String HABIT_URI_FORMAT =
|
||||||
|
"content://org.isoron.uhabits/habit/%d";
|
||||||
|
|
||||||
|
@Column(name = "name")
|
||||||
|
public String name;
|
||||||
|
|
||||||
|
@Column(name = "description")
|
||||||
|
public String description;
|
||||||
|
|
||||||
|
@Column(name = "freq_num")
|
||||||
|
public Integer freqNum;
|
||||||
|
|
||||||
|
@Column(name = "freq_den")
|
||||||
|
public Integer freqDen;
|
||||||
|
|
||||||
|
@Column(name = "color")
|
||||||
|
public Integer color;
|
||||||
|
|
||||||
|
@Column(name = "position")
|
||||||
|
public Integer position;
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
@Column(name = "reminder_hour")
|
||||||
|
public Integer reminderHour;
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
@Column(name = "reminder_min")
|
||||||
|
public Integer reminderMin;
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
@Column(name = "reminder_days")
|
||||||
|
public Integer reminderDays;
|
||||||
|
|
||||||
|
@Column(name = "highlight")
|
||||||
|
public Integer highlight;
|
||||||
|
|
||||||
|
@Column(name = "archived")
|
||||||
|
public Integer archived;
|
||||||
|
|
||||||
|
public HabitRecord()
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
public static HabitRecord get(Long id)
|
||||||
|
{
|
||||||
|
return HabitRecord.load(HabitRecord.class, id);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Changes the id of a habit on the database.
|
||||||
|
*
|
||||||
|
* @param oldId the original id
|
||||||
|
* @param newId the new id
|
||||||
|
*/
|
||||||
|
@SuppressLint("DefaultLocale")
|
||||||
|
public static void updateId(long oldId, long newId)
|
||||||
|
{
|
||||||
|
SQLiteUtils.execSql(
|
||||||
|
String.format("update Habits set Id = %d where Id = %d", newId,
|
||||||
|
oldId));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Deletes the habit and all data associated to it, including checkmarks,
|
||||||
|
* repetitions and scores.
|
||||||
|
*/
|
||||||
|
public void cascadeDelete()
|
||||||
|
{
|
||||||
|
Long id = getId();
|
||||||
|
|
||||||
|
DatabaseUtils.executeAsTransaction(() -> {
|
||||||
|
new Delete()
|
||||||
|
.from(CheckmarkRecord.class)
|
||||||
|
.where("habit = ?", id)
|
||||||
|
.execute();
|
||||||
|
|
||||||
|
new Delete()
|
||||||
|
.from(RepetitionRecord.class)
|
||||||
|
.where("habit = ?", id)
|
||||||
|
.execute();
|
||||||
|
|
||||||
|
new Delete()
|
||||||
|
.from(ScoreRecord.class)
|
||||||
|
.where("habit = ?", id)
|
||||||
|
.execute();
|
||||||
|
|
||||||
|
new Delete()
|
||||||
|
.from(StreakRecord.class)
|
||||||
|
.where("habit = ?", id)
|
||||||
|
.execute();
|
||||||
|
|
||||||
|
delete();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public void copyFrom(Habit model)
|
||||||
|
{
|
||||||
|
this.name = model.getName();
|
||||||
|
this.description = model.getDescription();
|
||||||
|
this.freqNum = model.getFreqNum();
|
||||||
|
this.freqDen = model.getFreqDen();
|
||||||
|
this.color = model.getColor();
|
||||||
|
this.reminderHour = model.getReminderHour();
|
||||||
|
this.reminderMin = model.getReminderMin();
|
||||||
|
this.reminderDays = model.getReminderDays();
|
||||||
|
this.highlight = model.getHighlight();
|
||||||
|
this.archived = model.getArchived();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void copyTo(Habit habit)
|
||||||
|
{
|
||||||
|
habit.setName(this.name);
|
||||||
|
habit.setDescription(this.description);
|
||||||
|
habit.setFreqNum(this.freqNum);
|
||||||
|
habit.setFreqDen(this.freqDen);
|
||||||
|
habit.setColor(this.color);
|
||||||
|
habit.setReminderHour(this.reminderHour);
|
||||||
|
habit.setReminderMin(this.reminderMin);
|
||||||
|
habit.setReminderDays(this.reminderDays);
|
||||||
|
habit.setHighlight(this.highlight);
|
||||||
|
habit.setArchived(this.archived);
|
||||||
|
habit.setId(this.getId());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Saves the habit on the database, and assigns the specified id to it.
|
||||||
|
*
|
||||||
|
* @param id the id that the habit should receive
|
||||||
|
*/
|
||||||
|
public void save(long id)
|
||||||
|
{
|
||||||
|
save();
|
||||||
|
updateId(getId(), id);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,58 @@
|
|||||||
|
/*
|
||||||
|
* 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.models.sqlite;
|
||||||
|
|
||||||
|
import com.activeandroid.Model;
|
||||||
|
import com.activeandroid.annotation.Column;
|
||||||
|
import com.activeandroid.annotation.Table;
|
||||||
|
|
||||||
|
import org.isoron.uhabits.models.Habit;
|
||||||
|
import org.isoron.uhabits.models.Repetition;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The SQLite database record corresponding to a {@link Repetition}.
|
||||||
|
*/
|
||||||
|
@Table(name = "Repetitions")
|
||||||
|
public class RepetitionRecord extends Model
|
||||||
|
{
|
||||||
|
@Column(name = "habit")
|
||||||
|
public HabitRecord habit;
|
||||||
|
|
||||||
|
@Column(name = "timestamp")
|
||||||
|
public Long timestamp;
|
||||||
|
|
||||||
|
public void copyFrom(Repetition repetition)
|
||||||
|
{
|
||||||
|
habit = HabitRecord.get(repetition.getHabit().getId());
|
||||||
|
timestamp = repetition.getTimestamp();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static RepetitionRecord get(Long id)
|
||||||
|
{
|
||||||
|
return RepetitionRecord.load(RepetitionRecord.class, id);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Repetition toRepetition()
|
||||||
|
{
|
||||||
|
SQLiteHabitList habitList = SQLiteHabitList.getInstance();
|
||||||
|
Habit h = habitList.getById(habit.getId());
|
||||||
|
return new Repetition(h, timestamp);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,64 @@
|
|||||||
|
/*
|
||||||
|
* 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.models.sqlite;
|
||||||
|
|
||||||
|
import org.isoron.uhabits.models.CheckmarkList;
|
||||||
|
import org.isoron.uhabits.models.Habit;
|
||||||
|
import org.isoron.uhabits.models.HabitList;
|
||||||
|
import org.isoron.uhabits.models.ModelFactory;
|
||||||
|
import org.isoron.uhabits.models.RepetitionList;
|
||||||
|
import org.isoron.uhabits.models.ScoreList;
|
||||||
|
import org.isoron.uhabits.models.StreakList;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Factory that provides models backed by an SQLite database.
|
||||||
|
*/
|
||||||
|
public class SQLModelFactory implements ModelFactory
|
||||||
|
{
|
||||||
|
@Override
|
||||||
|
public RepetitionList buidRepetitionList(Habit habit)
|
||||||
|
{
|
||||||
|
return new SQLiteRepetitionList(habit);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public CheckmarkList buildCheckmarkList(Habit habit)
|
||||||
|
{
|
||||||
|
return new SQLiteCheckmarkList(habit);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public HabitList buildHabitList()
|
||||||
|
{
|
||||||
|
return new SQLiteHabitList();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ScoreList buildScoreList(Habit habit)
|
||||||
|
{
|
||||||
|
return new SQLiteScoreList(habit);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public StreakList buildStreakList(Habit habit)
|
||||||
|
{
|
||||||
|
return new SQLiteStreakList(habit);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,139 @@
|
|||||||
|
/*
|
||||||
|
* 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.models.sqlite;
|
||||||
|
|
||||||
|
import android.database.Cursor;
|
||||||
|
import android.database.sqlite.SQLiteDatabase;
|
||||||
|
import android.database.sqlite.SQLiteStatement;
|
||||||
|
import android.support.annotation.NonNull;
|
||||||
|
import android.support.annotation.Nullable;
|
||||||
|
|
||||||
|
import com.activeandroid.Cache;
|
||||||
|
import com.activeandroid.query.Delete;
|
||||||
|
import com.activeandroid.query.Select;
|
||||||
|
|
||||||
|
import org.isoron.uhabits.models.Checkmark;
|
||||||
|
import org.isoron.uhabits.models.CheckmarkList;
|
||||||
|
import org.isoron.uhabits.models.Habit;
|
||||||
|
import org.isoron.uhabits.utils.DateUtils;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Implementation of a {@link CheckmarkList} that is backed by SQLite.
|
||||||
|
*/
|
||||||
|
public class SQLiteCheckmarkList extends CheckmarkList
|
||||||
|
{
|
||||||
|
public SQLiteCheckmarkList(Habit habit)
|
||||||
|
{
|
||||||
|
super(habit);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void invalidateNewerThan(long timestamp)
|
||||||
|
{
|
||||||
|
new Delete()
|
||||||
|
.from(CheckmarkRecord.class)
|
||||||
|
.where("habit = ?", habit.getId())
|
||||||
|
.and("timestamp >= ?", timestamp)
|
||||||
|
.execute();
|
||||||
|
|
||||||
|
observable.notifyListeners();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@NonNull
|
||||||
|
public int[] getValues(long fromTimestamp, long toTimestamp)
|
||||||
|
{
|
||||||
|
compute(fromTimestamp, toTimestamp);
|
||||||
|
|
||||||
|
if (fromTimestamp > toTimestamp) return new int[0];
|
||||||
|
|
||||||
|
String query = "select value, timestamp from Checkmarks where " +
|
||||||
|
"habit = ? and timestamp >= ? and timestamp <= ?";
|
||||||
|
|
||||||
|
SQLiteDatabase db = Cache.openDatabase();
|
||||||
|
String args[] = {
|
||||||
|
habit.getId().toString(),
|
||||||
|
Long.toString(fromTimestamp),
|
||||||
|
Long.toString(toTimestamp)
|
||||||
|
};
|
||||||
|
Cursor cursor = db.rawQuery(query, args);
|
||||||
|
|
||||||
|
long day = DateUtils.millisecondsInOneDay;
|
||||||
|
int nDays = (int) ((toTimestamp - fromTimestamp) / day) + 1;
|
||||||
|
int[] checks = new int[nDays];
|
||||||
|
|
||||||
|
if (cursor.moveToFirst())
|
||||||
|
{
|
||||||
|
do
|
||||||
|
{
|
||||||
|
long timestamp = cursor.getLong(1);
|
||||||
|
int offset = (int) ((timestamp - fromTimestamp) / day);
|
||||||
|
checks[nDays - offset - 1] = cursor.getInt(0);
|
||||||
|
|
||||||
|
} while (cursor.moveToNext());
|
||||||
|
}
|
||||||
|
|
||||||
|
cursor.close();
|
||||||
|
return checks;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@Nullable
|
||||||
|
protected Checkmark getNewest()
|
||||||
|
{
|
||||||
|
CheckmarkRecord record = new Select()
|
||||||
|
.from(CheckmarkRecord.class)
|
||||||
|
.where("habit = ?", habit.getId())
|
||||||
|
.and("timestamp <= ?", DateUtils.getStartOfToday())
|
||||||
|
.orderBy("timestamp desc")
|
||||||
|
.limit(1)
|
||||||
|
.executeSingle();
|
||||||
|
|
||||||
|
return record.toCheckmark();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void insert(long timestamps[], int values[])
|
||||||
|
{
|
||||||
|
String query =
|
||||||
|
"insert into Checkmarks(habit, timestamp, value) values (?,?,?)";
|
||||||
|
|
||||||
|
SQLiteDatabase db = Cache.openDatabase();
|
||||||
|
db.beginTransaction();
|
||||||
|
try
|
||||||
|
{
|
||||||
|
SQLiteStatement statement = db.compileStatement(query);
|
||||||
|
|
||||||
|
for (int i = 0; i < timestamps.length; i++)
|
||||||
|
{
|
||||||
|
statement.bindLong(1, habit.getId());
|
||||||
|
statement.bindLong(2, timestamps[i]);
|
||||||
|
statement.bindLong(3, values[i]);
|
||||||
|
statement.execute();
|
||||||
|
}
|
||||||
|
|
||||||
|
db.setTransactionSuccessful();
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
db.endTransaction();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,232 @@
|
|||||||
|
/*
|
||||||
|
* 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.models.sqlite;
|
||||||
|
|
||||||
|
import android.support.annotation.NonNull;
|
||||||
|
import android.support.annotation.Nullable;
|
||||||
|
|
||||||
|
import com.activeandroid.query.From;
|
||||||
|
import com.activeandroid.query.Select;
|
||||||
|
import com.activeandroid.query.Update;
|
||||||
|
|
||||||
|
import org.isoron.uhabits.models.Habit;
|
||||||
|
import org.isoron.uhabits.models.HabitList;
|
||||||
|
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.LinkedList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Implementation of a {@link HabitList} that is backed by SQLite.
|
||||||
|
*/
|
||||||
|
public class SQLiteHabitList extends HabitList
|
||||||
|
{
|
||||||
|
private static SQLiteHabitList instance;
|
||||||
|
|
||||||
|
private HashMap<Long, Habit> cache;
|
||||||
|
|
||||||
|
public SQLiteHabitList()
|
||||||
|
{
|
||||||
|
cache = new HashMap<>();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static SQLiteHabitList getInstance()
|
||||||
|
{
|
||||||
|
if (instance == null) instance = new SQLiteHabitList();
|
||||||
|
return instance;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void add(Habit habit)
|
||||||
|
{
|
||||||
|
if(cache.containsValue(habit))
|
||||||
|
throw new RuntimeException("habit already in cache");
|
||||||
|
|
||||||
|
HabitRecord record = new HabitRecord();
|
||||||
|
record.copyFrom(habit);
|
||||||
|
record.position = countWithArchived();
|
||||||
|
|
||||||
|
Long id = habit.getId();
|
||||||
|
if(id == null) id = record.save();
|
||||||
|
else record.save(id);
|
||||||
|
|
||||||
|
habit.setId(id);
|
||||||
|
cache.put(id, habit);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int count()
|
||||||
|
{
|
||||||
|
return select().count();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int countWithArchived()
|
||||||
|
{
|
||||||
|
return selectWithArchived().count();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@NonNull
|
||||||
|
public List<Habit> getAll(boolean includeArchive)
|
||||||
|
{
|
||||||
|
List<HabitRecord> recordList;
|
||||||
|
if (includeArchive) recordList = selectWithArchived().execute();
|
||||||
|
else recordList = select().execute();
|
||||||
|
|
||||||
|
List<Habit> habits = new LinkedList<>();
|
||||||
|
for (HabitRecord record : recordList)
|
||||||
|
{
|
||||||
|
Habit habit = getById(record.getId());
|
||||||
|
if (habit == null)
|
||||||
|
throw new RuntimeException("habit not in database");
|
||||||
|
habits.add(habit);
|
||||||
|
}
|
||||||
|
|
||||||
|
return habits;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@Nullable
|
||||||
|
public Habit getById(long id)
|
||||||
|
{
|
||||||
|
if (!cache.containsKey(id))
|
||||||
|
{
|
||||||
|
HabitRecord record = HabitRecord.get(id);
|
||||||
|
if (record == null) return null;
|
||||||
|
|
||||||
|
Habit habit = new Habit();
|
||||||
|
record.copyTo(habit);
|
||||||
|
cache.put(id, habit);
|
||||||
|
}
|
||||||
|
|
||||||
|
return cache.get(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@Nullable
|
||||||
|
public Habit getByPosition(int position)
|
||||||
|
{
|
||||||
|
HabitRecord record = selectWithArchived()
|
||||||
|
.where("position = ?", position)
|
||||||
|
.executeSingle();
|
||||||
|
|
||||||
|
return getById(record.getId());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int indexOf(Habit h)
|
||||||
|
{
|
||||||
|
HabitRecord record = HabitRecord.get(h.getId());
|
||||||
|
if (record == null) return -1;
|
||||||
|
return record.position;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Deprecated
|
||||||
|
public void rebuildOrder()
|
||||||
|
{
|
||||||
|
List<Habit> habits = getAll(true);
|
||||||
|
|
||||||
|
int i = 0;
|
||||||
|
for (Habit h : habits)
|
||||||
|
{
|
||||||
|
HabitRecord record = HabitRecord.get(h.getId());
|
||||||
|
if (record == null)
|
||||||
|
throw new RuntimeException("habit not in database");
|
||||||
|
|
||||||
|
record.position = i++;
|
||||||
|
record.save();
|
||||||
|
}
|
||||||
|
|
||||||
|
update(habits);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void remove(@NonNull Habit habit)
|
||||||
|
{
|
||||||
|
if (!cache.containsKey(habit.getId()))
|
||||||
|
throw new RuntimeException("habit not in cache");
|
||||||
|
|
||||||
|
cache.remove(habit.getId());
|
||||||
|
HabitRecord record = HabitRecord.get(habit.getId());
|
||||||
|
if (record == null) throw new RuntimeException("habit not in database");
|
||||||
|
record.cascadeDelete();
|
||||||
|
rebuildOrder();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void reorder(Habit from, Habit to)
|
||||||
|
{
|
||||||
|
if (from == to) return;
|
||||||
|
|
||||||
|
Integer toPos = indexOf(to);
|
||||||
|
Integer fromPos = indexOf(from);
|
||||||
|
|
||||||
|
if (toPos < fromPos)
|
||||||
|
{
|
||||||
|
new Update(HabitRecord.class)
|
||||||
|
.set("position = position + 1")
|
||||||
|
.where("position >= ? and position < ?", toPos, fromPos)
|
||||||
|
.execute();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
new Update(HabitRecord.class)
|
||||||
|
.set("position = position - 1")
|
||||||
|
.where("position > ? and position <= ?", fromPos, toPos)
|
||||||
|
.execute();
|
||||||
|
}
|
||||||
|
|
||||||
|
HabitRecord record = HabitRecord.get(from.getId());
|
||||||
|
if (record == null) throw new RuntimeException("habit not in database");
|
||||||
|
record.position = toPos;
|
||||||
|
record.save();
|
||||||
|
|
||||||
|
update(from);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void update(List<Habit> habits)
|
||||||
|
{
|
||||||
|
for (Habit h : habits)
|
||||||
|
{
|
||||||
|
HabitRecord record = HabitRecord.get(h.getId());
|
||||||
|
if (record == null)
|
||||||
|
throw new RuntimeException("habit not in database");
|
||||||
|
record.copyFrom(h);
|
||||||
|
record.save();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
private From select()
|
||||||
|
{
|
||||||
|
return new Select()
|
||||||
|
.from(HabitRecord.class)
|
||||||
|
.where("archived = 0")
|
||||||
|
.orderBy("position");
|
||||||
|
}
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
private From selectWithArchived()
|
||||||
|
{
|
||||||
|
return new Select().from(HabitRecord.class).orderBy("position");
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,143 @@
|
|||||||
|
/*
|
||||||
|
* 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.models.sqlite;
|
||||||
|
|
||||||
|
import android.support.annotation.NonNull;
|
||||||
|
import android.support.annotation.Nullable;
|
||||||
|
|
||||||
|
import com.activeandroid.query.Delete;
|
||||||
|
import com.activeandroid.query.From;
|
||||||
|
import com.activeandroid.query.Select;
|
||||||
|
|
||||||
|
import org.isoron.uhabits.models.Habit;
|
||||||
|
import org.isoron.uhabits.models.Repetition;
|
||||||
|
import org.isoron.uhabits.models.RepetitionList;
|
||||||
|
import org.isoron.uhabits.utils.DateUtils;
|
||||||
|
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.LinkedList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Implementation of a {@link RepetitionList} that is backed by SQLite.
|
||||||
|
*/
|
||||||
|
public class SQLiteRepetitionList extends RepetitionList
|
||||||
|
{
|
||||||
|
HashMap<Long, Repetition> cache;
|
||||||
|
|
||||||
|
public SQLiteRepetitionList(@NonNull Habit habit)
|
||||||
|
{
|
||||||
|
super(habit);
|
||||||
|
this.cache = new HashMap<>();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void add(Repetition rep)
|
||||||
|
{
|
||||||
|
RepetitionRecord record = new RepetitionRecord();
|
||||||
|
record.copyFrom(rep);
|
||||||
|
long id = record.save();
|
||||||
|
cache.put(id, rep);
|
||||||
|
observable.notifyListeners();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<Repetition> getByInterval(long timeFrom, long timeTo)
|
||||||
|
{
|
||||||
|
return getFromRecord(selectFromTo(timeFrom, timeTo).execute());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Repetition getByTimestamp(long timestamp)
|
||||||
|
{
|
||||||
|
RepetitionRecord record =
|
||||||
|
select().where("timestamp = ?", timestamp).executeSingle();
|
||||||
|
return getFromRecord(record);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Repetition getOldest()
|
||||||
|
{
|
||||||
|
RepetitionRecord record = select().limit(1).executeSingle();
|
||||||
|
return getFromRecord(record);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void remove(@NonNull Repetition repetition)
|
||||||
|
{
|
||||||
|
new Delete()
|
||||||
|
.from(RepetitionRecord.class)
|
||||||
|
.where("habit = ?", habit.getId())
|
||||||
|
.and("timestamp = ?", repetition.getTimestamp())
|
||||||
|
.execute();
|
||||||
|
|
||||||
|
observable.notifyListeners();
|
||||||
|
}
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
private List<Repetition> getFromRecord(
|
||||||
|
@Nullable List<RepetitionRecord> records)
|
||||||
|
{
|
||||||
|
List<Repetition> reps = new LinkedList<>();
|
||||||
|
if (records == null) return reps;
|
||||||
|
|
||||||
|
for (RepetitionRecord record : records)
|
||||||
|
{
|
||||||
|
Repetition rep = getFromRecord(record);
|
||||||
|
reps.add(rep);
|
||||||
|
}
|
||||||
|
|
||||||
|
return reps;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
private Repetition getFromRecord(@Nullable RepetitionRecord record)
|
||||||
|
{
|
||||||
|
if (record == null) return null;
|
||||||
|
|
||||||
|
Long id = record.getId();
|
||||||
|
|
||||||
|
if (!cache.containsKey(id))
|
||||||
|
{
|
||||||
|
Repetition repetition = record.toRepetition();
|
||||||
|
cache.put(id, repetition);
|
||||||
|
}
|
||||||
|
|
||||||
|
return cache.get(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
private From select()
|
||||||
|
{
|
||||||
|
return new Select()
|
||||||
|
.from(RepetitionRecord.class)
|
||||||
|
.where("habit = ?", habit.getId())
|
||||||
|
.and("timestamp <= ?", DateUtils.getStartOfToday())
|
||||||
|
.orderBy("timestamp");
|
||||||
|
}
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
private From selectFromTo(long timeFrom, long timeTo)
|
||||||
|
{
|
||||||
|
return select()
|
||||||
|
.and("timestamp >= ?", timeFrom)
|
||||||
|
.and("timestamp <= ?", timeTo);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,173 @@
|
|||||||
|
/*
|
||||||
|
* 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.models.sqlite;
|
||||||
|
|
||||||
|
import android.database.Cursor;
|
||||||
|
import android.database.sqlite.SQLiteDatabase;
|
||||||
|
import android.database.sqlite.SQLiteStatement;
|
||||||
|
import android.support.annotation.NonNull;
|
||||||
|
import android.support.annotation.Nullable;
|
||||||
|
|
||||||
|
import com.activeandroid.Cache;
|
||||||
|
import com.activeandroid.query.Delete;
|
||||||
|
import com.activeandroid.query.From;
|
||||||
|
import com.activeandroid.query.Select;
|
||||||
|
import com.activeandroid.util.SQLiteUtils;
|
||||||
|
|
||||||
|
import org.isoron.uhabits.models.Habit;
|
||||||
|
import org.isoron.uhabits.models.Repetition;
|
||||||
|
import org.isoron.uhabits.models.Score;
|
||||||
|
import org.isoron.uhabits.models.ScoreList;
|
||||||
|
import org.isoron.uhabits.utils.DateUtils;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Implementation of a ScoreList that is backed by SQLite.
|
||||||
|
*/
|
||||||
|
public class SQLiteScoreList extends ScoreList
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Constructs a new ScoreList associated with the given habit.
|
||||||
|
*
|
||||||
|
* @param habit the habit this list should be associated with
|
||||||
|
*/
|
||||||
|
public SQLiteScoreList(@NonNull Habit habit)
|
||||||
|
{
|
||||||
|
super(habit);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getValue(long timestamp)
|
||||||
|
{
|
||||||
|
computeAll();
|
||||||
|
String[] args = {habit.getId().toString(), Long.toString(timestamp)};
|
||||||
|
return SQLiteUtils.intQuery(
|
||||||
|
"select score from Score where habit = ? and timestamp = ?", args);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void invalidateNewerThan(long timestamp)
|
||||||
|
{
|
||||||
|
new Delete()
|
||||||
|
.from(ScoreRecord.class)
|
||||||
|
.where("habit = ?", habit.getId())
|
||||||
|
.and("timestamp >= ?", timestamp)
|
||||||
|
.execute();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
@Override
|
||||||
|
protected Score getNewestComputed()
|
||||||
|
{
|
||||||
|
ScoreRecord record = select().limit(1).executeSingle();
|
||||||
|
return record.toScore();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@Nullable
|
||||||
|
protected Score get(long timestamp)
|
||||||
|
{
|
||||||
|
Repetition oldestRep = habit.getRepetitions().getOldest();
|
||||||
|
if (oldestRep == null) return null;
|
||||||
|
compute(oldestRep.getTimestamp(), timestamp);
|
||||||
|
|
||||||
|
ScoreRecord record =
|
||||||
|
select().where("timestamp = ?", timestamp).executeSingle();
|
||||||
|
|
||||||
|
return record.toScore();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@NonNull
|
||||||
|
protected int[] getValues(long from, long to, long divisor)
|
||||||
|
{
|
||||||
|
compute(from, to);
|
||||||
|
|
||||||
|
divisor *= DateUtils.millisecondsInOneDay;
|
||||||
|
Long offset = to + divisor;
|
||||||
|
|
||||||
|
String query =
|
||||||
|
"select ((timestamp - ?) / ?) as time, avg(score) from Score " +
|
||||||
|
"where habit = ? and timestamp >= ? and timestamp <= ? " +
|
||||||
|
"group by time order by time desc";
|
||||||
|
|
||||||
|
String params[] = {
|
||||||
|
offset.toString(),
|
||||||
|
Long.toString(divisor),
|
||||||
|
habit.getId().toString(),
|
||||||
|
Long.toString(from),
|
||||||
|
Long.toString(to)
|
||||||
|
};
|
||||||
|
|
||||||
|
SQLiteDatabase db = Cache.openDatabase();
|
||||||
|
Cursor cursor = db.rawQuery(query, params);
|
||||||
|
|
||||||
|
if (!cursor.moveToFirst()) return new int[0];
|
||||||
|
|
||||||
|
int k = 0;
|
||||||
|
int[] scores = new int[cursor.getCount()];
|
||||||
|
|
||||||
|
do
|
||||||
|
{
|
||||||
|
scores[k++] = (int) cursor.getFloat(1);
|
||||||
|
} while (cursor.moveToNext());
|
||||||
|
|
||||||
|
cursor.close();
|
||||||
|
return scores;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void add(List<Score> scores)
|
||||||
|
{
|
||||||
|
String query =
|
||||||
|
"insert into Score(habit, timestamp, score) values (?,?,?)";
|
||||||
|
|
||||||
|
SQLiteDatabase db = Cache.openDatabase();
|
||||||
|
db.beginTransaction();
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
SQLiteStatement statement = db.compileStatement(query);
|
||||||
|
|
||||||
|
for (Score s : scores)
|
||||||
|
{
|
||||||
|
statement.bindLong(1, habit.getId());
|
||||||
|
statement.bindLong(2, s.getTimestamp());
|
||||||
|
statement.bindLong(3, s.getValue());
|
||||||
|
statement.execute();
|
||||||
|
}
|
||||||
|
|
||||||
|
db.setTransactionSuccessful();
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
db.endTransaction();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected From select()
|
||||||
|
{
|
||||||
|
return new Select()
|
||||||
|
.from(ScoreRecord.class)
|
||||||
|
.where("habit = ?", habit.getId())
|
||||||
|
.orderBy("timestamp desc");
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,115 @@
|
|||||||
|
/*
|
||||||
|
* 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.models.sqlite;
|
||||||
|
|
||||||
|
import com.activeandroid.query.Delete;
|
||||||
|
import com.activeandroid.query.Select;
|
||||||
|
|
||||||
|
import org.isoron.uhabits.models.Habit;
|
||||||
|
import org.isoron.uhabits.models.Streak;
|
||||||
|
import org.isoron.uhabits.models.StreakList;
|
||||||
|
import org.isoron.uhabits.utils.DatabaseUtils;
|
||||||
|
import org.isoron.uhabits.utils.DateUtils;
|
||||||
|
|
||||||
|
import java.util.LinkedList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Implementation of a StreakList that is backed by SQLite.
|
||||||
|
*/
|
||||||
|
public class SQLiteStreakList extends StreakList
|
||||||
|
{
|
||||||
|
public SQLiteStreakList(Habit habit)
|
||||||
|
{
|
||||||
|
super(habit);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<Streak> getAll()
|
||||||
|
{
|
||||||
|
rebuild();
|
||||||
|
List<StreakRecord> records = new Select()
|
||||||
|
.from(StreakRecord.class)
|
||||||
|
.where("habit = ?", habit.getId())
|
||||||
|
.orderBy("end desc")
|
||||||
|
.execute();
|
||||||
|
|
||||||
|
return recordsToStreaks(records);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Streak getNewestComputed()
|
||||||
|
{
|
||||||
|
rebuild();
|
||||||
|
return getNewestRecord().toStreak();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void invalidateNewerThan(long timestamp)
|
||||||
|
{
|
||||||
|
new Delete()
|
||||||
|
.from(StreakRecord.class)
|
||||||
|
.where("habit = ?", habit.getId())
|
||||||
|
.and("end >= ?", timestamp - DateUtils.millisecondsInOneDay)
|
||||||
|
.execute();
|
||||||
|
|
||||||
|
observable.notifyListeners();
|
||||||
|
}
|
||||||
|
|
||||||
|
private StreakRecord getNewestRecord()
|
||||||
|
{
|
||||||
|
return new Select()
|
||||||
|
.from(StreakRecord.class)
|
||||||
|
.where("habit = ?", habit.getId())
|
||||||
|
.orderBy("end desc")
|
||||||
|
.limit(1)
|
||||||
|
.executeSingle();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void insert(List<Streak> streaks)
|
||||||
|
{
|
||||||
|
DatabaseUtils.executeAsTransaction(() -> {
|
||||||
|
for (Streak streak : streaks)
|
||||||
|
{
|
||||||
|
StreakRecord record = new StreakRecord();
|
||||||
|
record.copyFrom(streak);
|
||||||
|
record.save();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private List<Streak> recordsToStreaks(List<StreakRecord> records)
|
||||||
|
{
|
||||||
|
LinkedList<Streak> streaks = new LinkedList<>();
|
||||||
|
|
||||||
|
for (StreakRecord record : records)
|
||||||
|
streaks.add(record.toStreak());
|
||||||
|
|
||||||
|
return streaks;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void removeNewestComputed()
|
||||||
|
{
|
||||||
|
StreakRecord newestStreak = getNewestRecord();
|
||||||
|
if (newestStreak != null) newestStreak.delete();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,65 @@
|
|||||||
|
/*
|
||||||
|
* 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.models.sqlite;
|
||||||
|
|
||||||
|
import com.activeandroid.Model;
|
||||||
|
import com.activeandroid.annotation.Column;
|
||||||
|
import com.activeandroid.annotation.Table;
|
||||||
|
|
||||||
|
import org.isoron.uhabits.models.Habit;
|
||||||
|
import org.isoron.uhabits.models.Score;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The SQLite database record corresponding to a Score.
|
||||||
|
*/
|
||||||
|
@Table(name = "Score")
|
||||||
|
public class ScoreRecord extends Model
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Habit to which this score belongs to.
|
||||||
|
*/
|
||||||
|
@Column(name = "habit")
|
||||||
|
public HabitRecord habit;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Timestamp of the day to which this score applies. Time of day should be
|
||||||
|
* midnight (UTC).
|
||||||
|
*/
|
||||||
|
@Column(name = "timestamp")
|
||||||
|
public Long timestamp;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Value of the score.
|
||||||
|
*/
|
||||||
|
@Column(name = "score")
|
||||||
|
public Integer score;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructs and returns a {@link Score} based on this record's data.
|
||||||
|
*
|
||||||
|
* @return a {@link Score} with this record's data
|
||||||
|
*/
|
||||||
|
public Score toScore()
|
||||||
|
{
|
||||||
|
SQLiteHabitList habitList = SQLiteHabitList.getInstance();
|
||||||
|
Habit h = habitList.getById(habit.getId());
|
||||||
|
return new Score(h, timestamp, score);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,66 @@
|
|||||||
|
/*
|
||||||
|
* 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.models.sqlite;
|
||||||
|
|
||||||
|
import com.activeandroid.Model;
|
||||||
|
import com.activeandroid.annotation.Column;
|
||||||
|
import com.activeandroid.annotation.Table;
|
||||||
|
|
||||||
|
import org.isoron.uhabits.models.Habit;
|
||||||
|
import org.isoron.uhabits.models.Streak;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The SQLite database record corresponding to a Streak.
|
||||||
|
*/
|
||||||
|
@Table(name = "Streak")
|
||||||
|
public class StreakRecord extends Model
|
||||||
|
{
|
||||||
|
@Column(name = "habit")
|
||||||
|
public HabitRecord habit;
|
||||||
|
|
||||||
|
@Column(name = "start")
|
||||||
|
public Long start;
|
||||||
|
|
||||||
|
@Column(name = "end")
|
||||||
|
public Long end;
|
||||||
|
|
||||||
|
@Column(name = "length")
|
||||||
|
public Long length;
|
||||||
|
|
||||||
|
public static StreakRecord get(Long id)
|
||||||
|
{
|
||||||
|
return StreakRecord.load(StreakRecord.class, id);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void copyFrom(Streak streak)
|
||||||
|
{
|
||||||
|
habit = HabitRecord.get(streak.getHabit().getId());
|
||||||
|
start = streak.getStart();
|
||||||
|
end = streak.getEnd();
|
||||||
|
length = streak.getLength();
|
||||||
|
}
|
||||||
|
|
||||||
|
public Streak toStreak()
|
||||||
|
{
|
||||||
|
SQLiteHabitList habitList = SQLiteHabitList.getInstance();
|
||||||
|
Habit h = habitList.getById(habit.getId());
|
||||||
|
return new Streak(h, start, end);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
/*
|
||||||
|
* 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/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Provides SQLite implementations of the core models.
|
||||||
|
*/
|
||||||
|
package org.isoron.uhabits.models.sqlite;
|
||||||
23
app/src/main/java/org/isoron/uhabits/package-info.java
Normal file
23
app/src/main/java/org/isoron/uhabits/package-info.java
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
/*
|
||||||
|
* 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/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Provides classes for the Loop Habit Tracker app.
|
||||||
|
*/
|
||||||
|
package org.isoron.uhabits;
|
||||||
24
app/src/main/java/org/isoron/uhabits/tasks/package-info.java
Normal file
24
app/src/main/java/org/isoron/uhabits/tasks/package-info.java
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
/*
|
||||||
|
* 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/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Provides async tasks for useful operations such as {@link
|
||||||
|
* org.isoron.uhabits.tasks.ExportCSVTask}.
|
||||||
|
*/
|
||||||
|
package org.isoron.uhabits.tasks;
|
||||||
@@ -25,6 +25,8 @@ import android.support.annotation.NonNull;
|
|||||||
import android.view.WindowManager;
|
import android.view.WindowManager;
|
||||||
|
|
||||||
import org.isoron.uhabits.BuildConfig;
|
import org.isoron.uhabits.BuildConfig;
|
||||||
|
import org.isoron.uhabits.HabitsApplication;
|
||||||
|
import org.isoron.uhabits.models.HabitList;
|
||||||
import org.isoron.uhabits.tasks.BaseTask;
|
import org.isoron.uhabits.tasks.BaseTask;
|
||||||
import org.isoron.uhabits.utils.DateUtils;
|
import org.isoron.uhabits.utils.DateUtils;
|
||||||
import org.isoron.uhabits.utils.FileUtils;
|
import org.isoron.uhabits.utils.FileUtils;
|
||||||
@@ -38,13 +40,19 @@ import java.io.IOException;
|
|||||||
import java.io.InputStreamReader;
|
import java.io.InputStreamReader;
|
||||||
import java.util.LinkedList;
|
import java.util.LinkedList;
|
||||||
|
|
||||||
|
import javax.inject.Inject;
|
||||||
|
|
||||||
public class BaseSystem
|
public class BaseSystem
|
||||||
{
|
{
|
||||||
private Context context;
|
private Context context;
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
HabitList habitList;
|
||||||
|
|
||||||
public BaseSystem(Context context)
|
public BaseSystem(Context context)
|
||||||
{
|
{
|
||||||
this.context = context;
|
this.context = context;
|
||||||
|
HabitsApplication.getComponent().inject(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getLogcat() throws IOException
|
public String getLogcat() throws IOException
|
||||||
@@ -146,7 +154,7 @@ public class BaseSystem
|
|||||||
@Override
|
@Override
|
||||||
protected void doInBackground()
|
protected void doInBackground()
|
||||||
{
|
{
|
||||||
ReminderUtils.createReminderAlarms(context);
|
ReminderUtils.createReminderAlarms(context, habitList);
|
||||||
}
|
}
|
||||||
}.execute();
|
}.execute();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -18,6 +18,6 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Contains classes for AboutActivity
|
* Provides activity that shows information about the app.
|
||||||
*/
|
*/
|
||||||
package org.isoron.uhabits.ui.about;
|
package org.isoron.uhabits.ui.about;
|
||||||
@@ -85,8 +85,8 @@ public abstract class BaseDialogFragment extends AppCompatDialogFragment
|
|||||||
if (position < 0 || position > 4) throw new IllegalArgumentException();
|
if (position < 0 || position > 4) throw new IllegalArgumentException();
|
||||||
int freqNums[] = {1, 1, 2, 5, 3};
|
int freqNums[] = {1, 1, 2, 5, 3};
|
||||||
int freqDens[] = {1, 7, 7, 7, 7};
|
int freqDens[] = {1, 7, 7, 7, 7};
|
||||||
modifiedHabit.freqNum = freqNums[position];
|
modifiedHabit.setFreqNum(freqNums[position]);
|
||||||
modifiedHabit.freqDen = freqDens[position];
|
modifiedHabit.setFreqDen(freqDens[position]);
|
||||||
helper.populateFrequencyFields(modifiedHabit);
|
helper.populateFrequencyFields(modifiedHabit);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -95,12 +95,12 @@ public abstract class BaseDialogFragment extends AppCompatDialogFragment
|
|||||||
public void onSaveInstanceState(Bundle outState)
|
public void onSaveInstanceState(Bundle outState)
|
||||||
{
|
{
|
||||||
super.onSaveInstanceState(outState);
|
super.onSaveInstanceState(outState);
|
||||||
outState.putInt("color", modifiedHabit.color);
|
outState.putInt("color", modifiedHabit.getColor());
|
||||||
if (modifiedHabit.hasReminder())
|
if (modifiedHabit.hasReminder())
|
||||||
{
|
{
|
||||||
outState.putInt("reminderMin", modifiedHabit.reminderMin);
|
outState.putInt("reminderMin", modifiedHabit.getReminderMin());
|
||||||
outState.putInt("reminderHour", modifiedHabit.reminderHour);
|
outState.putInt("reminderHour", modifiedHabit.getReminderHour());
|
||||||
outState.putInt("reminderDays", modifiedHabit.reminderDays);
|
outState.putInt("reminderDays", modifiedHabit.getReminderDays());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -123,8 +123,8 @@ public abstract class BaseDialogFragment extends AppCompatDialogFragment
|
|||||||
|
|
||||||
if (modifiedHabit.hasReminder())
|
if (modifiedHabit.hasReminder())
|
||||||
{
|
{
|
||||||
defaultHour = modifiedHabit.reminderHour;
|
defaultHour = modifiedHabit.getReminderHour();
|
||||||
defaultMin = modifiedHabit.reminderMin;
|
defaultMin = modifiedHabit.getReminderMin();
|
||||||
}
|
}
|
||||||
|
|
||||||
showTimePicker(defaultHour, defaultMin);
|
showTimePicker(defaultHour, defaultMin);
|
||||||
@@ -147,18 +147,19 @@ public abstract class BaseDialogFragment extends AppCompatDialogFragment
|
|||||||
WeekdayPickerDialog dialog = new WeekdayPickerDialog();
|
WeekdayPickerDialog dialog = new WeekdayPickerDialog();
|
||||||
dialog.setListener(new OnWeekdaysPickedListener());
|
dialog.setListener(new OnWeekdaysPickedListener());
|
||||||
dialog.setSelectedDays(
|
dialog.setSelectedDays(
|
||||||
DateUtils.unpackWeekdayList(modifiedHabit.reminderDays));
|
DateUtils.unpackWeekdayList(modifiedHabit.getReminderDays()));
|
||||||
dialog.show(getFragmentManager(), "weekdayPicker");
|
dialog.show(getFragmentManager(), "weekdayPicker");
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void restoreSavedInstance(@Nullable Bundle bundle)
|
protected void restoreSavedInstance(@Nullable Bundle bundle)
|
||||||
{
|
{
|
||||||
if (bundle == null) return;
|
if (bundle == null) return;
|
||||||
modifiedHabit.color = bundle.getInt("color", modifiedHabit.color);
|
modifiedHabit.setColor(
|
||||||
modifiedHabit.reminderMin = bundle.getInt("reminderMin", -1);
|
bundle.getInt("color", modifiedHabit.getColor()));
|
||||||
modifiedHabit.reminderHour = bundle.getInt("reminderHour", -1);
|
modifiedHabit.setReminderMin(bundle.getInt("reminderMin", -1));
|
||||||
modifiedHabit.reminderDays = bundle.getInt("reminderDays", -1);
|
modifiedHabit.setReminderHour(bundle.getInt("reminderHour", -1));
|
||||||
if (modifiedHabit.reminderMin < 0) modifiedHabit.clearReminder();
|
modifiedHabit.setReminderDays(bundle.getInt("reminderDays", -1));
|
||||||
|
if (modifiedHabit.getReminderMin() < 0) modifiedHabit.clearReminder();
|
||||||
}
|
}
|
||||||
|
|
||||||
protected abstract void saveHabit();
|
protected abstract void saveHabit();
|
||||||
@@ -167,7 +168,7 @@ public abstract class BaseDialogFragment extends AppCompatDialogFragment
|
|||||||
void showColorPicker()
|
void showColorPicker()
|
||||||
{
|
{
|
||||||
int androidColor =
|
int androidColor =
|
||||||
ColorUtils.getColor(getContext(), modifiedHabit.color);
|
ColorUtils.getColor(getContext(), modifiedHabit.getColor());
|
||||||
|
|
||||||
ColorPickerDialog picker =
|
ColorPickerDialog picker =
|
||||||
ColorPickerDialog.newInstance(R.string.color_picker_default_title,
|
ColorPickerDialog.newInstance(R.string.color_picker_default_title,
|
||||||
@@ -196,7 +197,7 @@ public abstract class BaseDialogFragment extends AppCompatDialogFragment
|
|||||||
int paletteColor =
|
int paletteColor =
|
||||||
ColorUtils.colorToPaletteIndex(getActivity(), androidColor);
|
ColorUtils.colorToPaletteIndex(getActivity(), androidColor);
|
||||||
prefs.setDefaultHabitColor(paletteColor);
|
prefs.setDefaultHabitColor(paletteColor);
|
||||||
modifiedHabit.color = paletteColor;
|
modifiedHabit.setColor(paletteColor);
|
||||||
helper.populateColor(paletteColor);
|
helper.populateColor(paletteColor);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -214,9 +215,9 @@ public abstract class BaseDialogFragment extends AppCompatDialogFragment
|
|||||||
@Override
|
@Override
|
||||||
public void onTimeSet(RadialPickerLayout view, int hour, int minute)
|
public void onTimeSet(RadialPickerLayout view, int hour, int minute)
|
||||||
{
|
{
|
||||||
modifiedHabit.reminderHour = hour;
|
modifiedHabit.setReminderHour(hour);
|
||||||
modifiedHabit.reminderMin = minute;
|
modifiedHabit.setReminderMin(minute);
|
||||||
modifiedHabit.reminderDays = DateUtils.ALL_WEEK_DAYS;
|
modifiedHabit.setReminderDays(DateUtils.ALL_WEEK_DAYS);
|
||||||
helper.populateReminderFields(modifiedHabit);
|
helper.populateReminderFields(modifiedHabit);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -229,8 +230,8 @@ public abstract class BaseDialogFragment extends AppCompatDialogFragment
|
|||||||
{
|
{
|
||||||
if (isSelectionEmpty(selectedDays)) Arrays.fill(selectedDays, true);
|
if (isSelectionEmpty(selectedDays)) Arrays.fill(selectedDays, true);
|
||||||
|
|
||||||
modifiedHabit.reminderDays =
|
modifiedHabit.setReminderDays(
|
||||||
DateUtils.packWeekdayList(selectedDays);
|
DateUtils.packWeekdayList(selectedDays));
|
||||||
helper.populateReminderFields(modifiedHabit);
|
helper.populateReminderFields(modifiedHabit);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -73,12 +73,12 @@ public class BaseDialogHelper
|
|||||||
|
|
||||||
void parseFormIntoHabit(Habit habit)
|
void parseFormIntoHabit(Habit habit)
|
||||||
{
|
{
|
||||||
habit.name = tvName.getText().toString().trim();
|
habit.setName(tvName.getText().toString().trim());
|
||||||
habit.description = tvDescription.getText().toString().trim();
|
habit.setDescription(tvDescription.getText().toString().trim());
|
||||||
String freqNum = tvFreqNum.getText().toString();
|
String freqNum = tvFreqNum.getText().toString();
|
||||||
String freqDen = tvFreqDen.getText().toString();
|
String freqDen = tvFreqDen.getText().toString();
|
||||||
if (!freqNum.isEmpty()) habit.freqNum = Integer.parseInt(freqNum);
|
if (!freqNum.isEmpty()) habit.setFreqNum(Integer.parseInt(freqNum));
|
||||||
if (!freqDen.isEmpty()) habit.freqDen = Integer.parseInt(freqDen);
|
if (!freqDen.isEmpty()) habit.setFreqDen(Integer.parseInt(freqDen));
|
||||||
}
|
}
|
||||||
|
|
||||||
void populateColor(int paletteColor)
|
void populateColor(int paletteColor)
|
||||||
@@ -89,10 +89,11 @@ public class BaseDialogHelper
|
|||||||
|
|
||||||
protected void populateForm(final Habit habit)
|
protected void populateForm(final Habit habit)
|
||||||
{
|
{
|
||||||
if (habit.name != null) tvName.setText(habit.name);
|
if (habit.getName() != null) tvName.setText(habit.getName());
|
||||||
if (habit.description != null) tvDescription.setText(habit.description);
|
if (habit.getDescription() != null) tvDescription.setText(
|
||||||
|
habit.getDescription());
|
||||||
|
|
||||||
populateColor(habit.color);
|
populateColor(habit.getColor());
|
||||||
populateFrequencyFields(habit);
|
populateFrequencyFields(habit);
|
||||||
populateReminderFields(habit);
|
populateReminderFields(habit);
|
||||||
}
|
}
|
||||||
@@ -102,15 +103,15 @@ public class BaseDialogHelper
|
|||||||
{
|
{
|
||||||
int quickSelectPosition = -1;
|
int quickSelectPosition = -1;
|
||||||
|
|
||||||
if (habit.freqNum.equals(habit.freqDen)) quickSelectPosition = 0;
|
if (habit.getFreqNum().equals(habit.getFreqDen())) quickSelectPosition = 0;
|
||||||
|
|
||||||
else if (habit.freqNum == 1 && habit.freqDen == 7)
|
else if (habit.getFreqNum() == 1 && habit.getFreqDen() == 7)
|
||||||
quickSelectPosition = 1;
|
quickSelectPosition = 1;
|
||||||
|
|
||||||
else if (habit.freqNum == 2 && habit.freqDen == 7)
|
else if (habit.getFreqNum() == 2 && habit.getFreqDen() == 7)
|
||||||
quickSelectPosition = 2;
|
quickSelectPosition = 2;
|
||||||
|
|
||||||
else if (habit.freqNum == 5 && habit.freqDen == 7)
|
else if (habit.getFreqNum() == 5 && habit.getFreqDen() == 7)
|
||||||
quickSelectPosition = 3;
|
quickSelectPosition = 3;
|
||||||
|
|
||||||
if (quickSelectPosition >= 0)
|
if (quickSelectPosition >= 0)
|
||||||
@@ -118,8 +119,8 @@ public class BaseDialogHelper
|
|||||||
|
|
||||||
else showCustomFrequency();
|
else showCustomFrequency();
|
||||||
|
|
||||||
tvFreqNum.setText(habit.freqNum.toString());
|
tvFreqNum.setText(habit.getFreqNum().toString());
|
||||||
tvFreqDen.setText(habit.freqDen.toString());
|
tvFreqDen.setText(habit.getFreqDen().toString());
|
||||||
}
|
}
|
||||||
|
|
||||||
@SuppressWarnings("ConstantConditions")
|
@SuppressWarnings("ConstantConditions")
|
||||||
@@ -133,12 +134,13 @@ public class BaseDialogHelper
|
|||||||
}
|
}
|
||||||
|
|
||||||
String time =
|
String time =
|
||||||
DateUtils.formatTime(frag.getContext(), habit.reminderHour,
|
DateUtils.formatTime(frag.getContext(), habit.getReminderHour(),
|
||||||
habit.reminderMin);
|
habit.getReminderMin());
|
||||||
tvReminderTime.setText(time);
|
tvReminderTime.setText(time);
|
||||||
llReminderDays.setVisibility(View.VISIBLE);
|
llReminderDays.setVisibility(View.VISIBLE);
|
||||||
|
|
||||||
boolean weekdays[] = DateUtils.unpackWeekdayList(habit.reminderDays);
|
boolean weekdays[] = DateUtils.unpackWeekdayList(
|
||||||
|
habit.getReminderDays());
|
||||||
tvReminderDays.setText(
|
tvReminderDays.setText(
|
||||||
DateUtils.formatWeekdayList(frag.getContext(), weekdays));
|
DateUtils.formatWeekdayList(frag.getContext(), weekdays));
|
||||||
}
|
}
|
||||||
@@ -161,21 +163,21 @@ public class BaseDialogHelper
|
|||||||
{
|
{
|
||||||
Boolean valid = true;
|
Boolean valid = true;
|
||||||
|
|
||||||
if (habit.name.length() == 0)
|
if (habit.getName().length() == 0)
|
||||||
{
|
{
|
||||||
tvName.setError(
|
tvName.setError(
|
||||||
frag.getString(R.string.validation_name_should_not_be_blank));
|
frag.getString(R.string.validation_name_should_not_be_blank));
|
||||||
valid = false;
|
valid = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (habit.freqNum <= 0)
|
if (habit.getFreqNum() <= 0)
|
||||||
{
|
{
|
||||||
tvFreqNum.setError(
|
tvFreqNum.setError(
|
||||||
frag.getString(R.string.validation_number_should_be_positive));
|
frag.getString(R.string.validation_number_should_be_positive));
|
||||||
valid = false;
|
valid = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (habit.freqNum > habit.freqDen)
|
if (habit.getFreqNum() > habit.getFreqDen())
|
||||||
{
|
{
|
||||||
tvFreqNum.setError(
|
tvFreqNum.setError(
|
||||||
frag.getString(R.string.validation_at_most_one_rep_per_day));
|
frag.getString(R.string.validation_at_most_one_rep_per_day));
|
||||||
|
|||||||
@@ -19,6 +19,7 @@
|
|||||||
|
|
||||||
package org.isoron.uhabits.ui.habits.edit;
|
package org.isoron.uhabits.ui.habits.edit;
|
||||||
|
|
||||||
|
import org.isoron.uhabits.HabitsApplication;
|
||||||
import org.isoron.uhabits.R;
|
import org.isoron.uhabits.R;
|
||||||
import org.isoron.uhabits.commands.Command;
|
import org.isoron.uhabits.commands.Command;
|
||||||
import org.isoron.uhabits.commands.CreateHabitCommand;
|
import org.isoron.uhabits.commands.CreateHabitCommand;
|
||||||
@@ -36,9 +37,10 @@ public class CreateHabitDialogFragment extends BaseDialogFragment
|
|||||||
protected void initializeHabits()
|
protected void initializeHabits()
|
||||||
{
|
{
|
||||||
modifiedHabit = new Habit();
|
modifiedHabit = new Habit();
|
||||||
modifiedHabit.freqNum = 1;
|
modifiedHabit.setFreqNum(1);
|
||||||
modifiedHabit.freqDen = 1;
|
modifiedHabit.setFreqDen(1);
|
||||||
modifiedHabit.color = prefs.getDefaultHabitColor(modifiedHabit.color);
|
modifiedHabit.setColor(
|
||||||
|
prefs.getDefaultHabitColor(modifiedHabit.getColor()));
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void saveHabit()
|
protected void saveHabit()
|
||||||
|
|||||||
@@ -21,13 +21,20 @@ package org.isoron.uhabits.ui.habits.edit;
|
|||||||
|
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
|
|
||||||
|
import org.isoron.uhabits.HabitsApplication;
|
||||||
import org.isoron.uhabits.R;
|
import org.isoron.uhabits.R;
|
||||||
import org.isoron.uhabits.commands.Command;
|
import org.isoron.uhabits.commands.Command;
|
||||||
import org.isoron.uhabits.commands.EditHabitCommand;
|
import org.isoron.uhabits.commands.EditHabitCommand;
|
||||||
import org.isoron.uhabits.models.Habit;
|
import org.isoron.uhabits.models.Habit;
|
||||||
|
import org.isoron.uhabits.models.HabitList;
|
||||||
|
|
||||||
|
import javax.inject.Inject;
|
||||||
|
|
||||||
public class EditHabitDialogFragment extends BaseDialogFragment
|
public class EditHabitDialogFragment extends BaseDialogFragment
|
||||||
{
|
{
|
||||||
|
@Inject
|
||||||
|
HabitList habitList;
|
||||||
|
|
||||||
public static EditHabitDialogFragment newInstance(long habitId)
|
public static EditHabitDialogFragment newInstance(long habitId)
|
||||||
{
|
{
|
||||||
EditHabitDialogFragment frag = new EditHabitDialogFragment();
|
EditHabitDialogFragment frag = new EditHabitDialogFragment();
|
||||||
@@ -46,14 +53,18 @@ public class EditHabitDialogFragment extends BaseDialogFragment
|
|||||||
@Override
|
@Override
|
||||||
protected void initializeHabits()
|
protected void initializeHabits()
|
||||||
{
|
{
|
||||||
|
HabitsApplication.getComponent().inject(this);
|
||||||
|
|
||||||
Long habitId = (Long) getArguments().get("habitId");
|
Long habitId = (Long) getArguments().get("habitId");
|
||||||
if (habitId == null)
|
if (habitId == null)
|
||||||
throw new IllegalArgumentException("habitId must be specified");
|
throw new IllegalArgumentException("habitId must be specified");
|
||||||
|
|
||||||
originalHabit = Habit.get(habitId);
|
originalHabit = habitList.getById(habitId);
|
||||||
modifiedHabit = new Habit(originalHabit);
|
modifiedHabit = new Habit();
|
||||||
|
modifiedHabit.copyFrom(originalHabit);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
protected void saveHabit()
|
protected void saveHabit()
|
||||||
{
|
{
|
||||||
Command command = new EditHabitCommand(originalHabit, modifiedHabit);
|
Command command = new EditHabitCommand(originalHabit, modifiedHabit);
|
||||||
|
|||||||
@@ -27,10 +27,14 @@ import android.support.v7.app.AlertDialog;
|
|||||||
import android.support.v7.app.AppCompatDialogFragment;
|
import android.support.v7.app.AppCompatDialogFragment;
|
||||||
import android.util.DisplayMetrics;
|
import android.util.DisplayMetrics;
|
||||||
|
|
||||||
|
import org.isoron.uhabits.HabitsApplication;
|
||||||
import org.isoron.uhabits.R;
|
import org.isoron.uhabits.R;
|
||||||
import org.isoron.uhabits.models.Habit;
|
import org.isoron.uhabits.models.Habit;
|
||||||
|
import org.isoron.uhabits.models.HabitList;
|
||||||
import org.isoron.uhabits.tasks.BaseTask;
|
import org.isoron.uhabits.tasks.BaseTask;
|
||||||
import org.isoron.uhabits.views.HabitHistoryView;
|
import org.isoron.uhabits.ui.habits.show.views.HabitHistoryView;
|
||||||
|
|
||||||
|
import javax.inject.Inject;
|
||||||
|
|
||||||
public class HistoryEditorDialog extends AppCompatDialogFragment
|
public class HistoryEditorDialog extends AppCompatDialogFragment
|
||||||
implements DialogInterface.OnClickListener
|
implements DialogInterface.OnClickListener
|
||||||
@@ -41,16 +45,20 @@ public class HistoryEditorDialog extends AppCompatDialogFragment
|
|||||||
|
|
||||||
HabitHistoryView historyView;
|
HabitHistoryView historyView;
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
HabitList habitList;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Dialog onCreateDialog(Bundle savedInstanceState)
|
public Dialog onCreateDialog(Bundle savedInstanceState)
|
||||||
{
|
{
|
||||||
Context context = getActivity();
|
Context context = getActivity();
|
||||||
|
HabitsApplication.getComponent().inject(this);
|
||||||
historyView = new HabitHistoryView(context, null);
|
historyView = new HabitHistoryView(context, null);
|
||||||
|
|
||||||
if (savedInstanceState != null)
|
if (savedInstanceState != null)
|
||||||
{
|
{
|
||||||
long id = savedInstanceState.getLong("habit", -1);
|
long id = savedInstanceState.getLong("habit", -1);
|
||||||
if (id > 0) this.habit = Habit.get(id);
|
if (id > 0) this.habit = habitList.getById(id);
|
||||||
}
|
}
|
||||||
|
|
||||||
int padding =
|
int padding =
|
||||||
|
|||||||
@@ -0,0 +1,23 @@
|
|||||||
|
/*
|
||||||
|
* 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/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Provides dialogs for editing habits and related classes.
|
||||||
|
*/
|
||||||
|
package org.isoron.uhabits.ui.habits.edit;
|
||||||
@@ -21,25 +21,33 @@ package org.isoron.uhabits.ui.habits.list;
|
|||||||
|
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
|
|
||||||
|
import org.isoron.uhabits.HabitsApplication;
|
||||||
|
import org.isoron.uhabits.models.HabitList;
|
||||||
import org.isoron.uhabits.ui.BaseActivity;
|
import org.isoron.uhabits.ui.BaseActivity;
|
||||||
import org.isoron.uhabits.ui.BaseSystem;
|
import org.isoron.uhabits.ui.BaseSystem;
|
||||||
|
|
||||||
|
import javax.inject.Inject;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Activity that allows the user to see and modify the list of habits.
|
* Activity that allows the user to see and modify the list of habits.
|
||||||
*/
|
*/
|
||||||
public class ListHabitsActivity extends BaseActivity
|
public class ListHabitsActivity extends BaseActivity
|
||||||
{
|
{
|
||||||
|
@Inject
|
||||||
|
HabitList habitList;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void onCreate(Bundle savedInstanceState)
|
protected void onCreate(Bundle savedInstanceState)
|
||||||
{
|
{
|
||||||
super.onCreate(savedInstanceState);
|
super.onCreate(savedInstanceState);
|
||||||
|
HabitsApplication.getComponent().inject(this);
|
||||||
|
|
||||||
BaseSystem system = new BaseSystem(this);
|
BaseSystem system = new BaseSystem(this);
|
||||||
ListHabitsScreen screen = new ListHabitsScreen(this);
|
ListHabitsScreen screen = new ListHabitsScreen(this);
|
||||||
ListHabitsController controller =
|
ListHabitsController controller =
|
||||||
new ListHabitsController(screen, system);
|
new ListHabitsController(screen, system, habitList);
|
||||||
|
|
||||||
screen.setController(controller);
|
screen.setController(controller);
|
||||||
|
|
||||||
setScreen(screen);
|
setScreen(screen);
|
||||||
controller.onStartup();
|
controller.onStartup();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -26,6 +26,7 @@ import org.isoron.uhabits.R;
|
|||||||
import org.isoron.uhabits.commands.CommandRunner;
|
import org.isoron.uhabits.commands.CommandRunner;
|
||||||
import org.isoron.uhabits.commands.ToggleRepetitionCommand;
|
import org.isoron.uhabits.commands.ToggleRepetitionCommand;
|
||||||
import org.isoron.uhabits.models.Habit;
|
import org.isoron.uhabits.models.Habit;
|
||||||
|
import org.isoron.uhabits.models.HabitList;
|
||||||
import org.isoron.uhabits.tasks.ExportCSVTask;
|
import org.isoron.uhabits.tasks.ExportCSVTask;
|
||||||
import org.isoron.uhabits.tasks.ExportDBTask;
|
import org.isoron.uhabits.tasks.ExportDBTask;
|
||||||
import org.isoron.uhabits.tasks.ImportDataTask;
|
import org.isoron.uhabits.tasks.ImportDataTask;
|
||||||
@@ -48,6 +49,9 @@ public class ListHabitsController
|
|||||||
@NonNull
|
@NonNull
|
||||||
private final BaseSystem system;
|
private final BaseSystem system;
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
private final HabitList habitList;
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
Preferences prefs;
|
Preferences prefs;
|
||||||
|
|
||||||
@@ -55,17 +59,19 @@ public class ListHabitsController
|
|||||||
CommandRunner commandRunner;
|
CommandRunner commandRunner;
|
||||||
|
|
||||||
public ListHabitsController(@NonNull ListHabitsScreen screen,
|
public ListHabitsController(@NonNull ListHabitsScreen screen,
|
||||||
@NonNull BaseSystem system)
|
@NonNull BaseSystem system,
|
||||||
|
@NonNull HabitList habitList)
|
||||||
{
|
{
|
||||||
this.screen = screen;
|
this.screen = screen;
|
||||||
this.system = system;
|
this.system = system;
|
||||||
|
this.habitList = habitList;
|
||||||
HabitsApplication.getComponent().inject(this);
|
HabitsApplication.getComponent().inject(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void onExportCSV()
|
public void onExportCSV()
|
||||||
{
|
{
|
||||||
ExportCSVTask task =
|
ExportCSVTask task =
|
||||||
new ExportCSVTask(Habit.getAll(true), screen.getProgressBar());
|
new ExportCSVTask(habitList.getAll(true), screen.getProgressBar());
|
||||||
task.setListener(filename -> {
|
task.setListener(filename -> {
|
||||||
if (filename != null) screen.showSendFileScreen(filename);
|
if (filename != null) screen.showSendFileScreen(filename);
|
||||||
else screen.showMessage(R.string.could_not_export);
|
else screen.showMessage(R.string.could_not_export);
|
||||||
@@ -92,7 +98,7 @@ public class ListHabitsController
|
|||||||
@Override
|
@Override
|
||||||
public void onHabitReorder(@NonNull Habit from, @NonNull Habit to)
|
public void onHabitReorder(@NonNull Habit from, @NonNull Habit to)
|
||||||
{
|
{
|
||||||
Habit.reorder(from, to);
|
habitList.reorder(from, to);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void onImportData(File file)
|
public void onImportData(File file)
|
||||||
@@ -133,7 +139,8 @@ public class ListHabitsController
|
|||||||
try
|
try
|
||||||
{
|
{
|
||||||
system.dumpBugReportToFile();
|
system.dumpBugReportToFile();
|
||||||
} catch (IOException e)
|
}
|
||||||
|
catch (IOException e)
|
||||||
{
|
{
|
||||||
// ignored
|
// ignored
|
||||||
}
|
}
|
||||||
@@ -146,7 +153,8 @@ public class ListHabitsController
|
|||||||
String to = "dev@loophabits.org";
|
String to = "dev@loophabits.org";
|
||||||
String subject = "Bug Report - Loop Habit Tracker";
|
String subject = "Bug Report - Loop Habit Tracker";
|
||||||
screen.showSendEmailScreen(log, to, subject);
|
screen.showSendEmailScreen(log, to, subject);
|
||||||
} catch (IOException e)
|
}
|
||||||
|
catch (IOException e)
|
||||||
{
|
{
|
||||||
e.printStackTrace();
|
e.printStackTrace();
|
||||||
screen.showMessage(R.string.bug_report_failed);
|
screen.showMessage(R.string.bug_report_failed);
|
||||||
|
|||||||
@@ -115,7 +115,7 @@ public class ListHabitsScreen extends BaseScreen
|
|||||||
|
|
||||||
public void showColorPicker(Habit habit, OnColorSelectedListener callback)
|
public void showColorPicker(Habit habit, OnColorSelectedListener callback)
|
||||||
{
|
{
|
||||||
int color = ColorUtils.getColor(activity, habit.color);
|
int color = ColorUtils.getColor(activity, habit.getColor());
|
||||||
|
|
||||||
ColorPickerDialog picker =
|
ColorPickerDialog picker =
|
||||||
ColorPickerDialog.newInstance(R.string.color_picker_default_title,
|
ColorPickerDialog.newInstance(R.string.color_picker_default_title,
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user