mirror of https://github.com/iSoron/uhabits.git
parent
18e8390aed
commit
78d4f86cab
@ -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));
|
||||
}
|
||||
}
|
@ -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");
|
||||
}
|
||||
}
|
@ -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;
|
@ -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;
|
@ -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);
|
||||
}
|
@ -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;
|
@ -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;
|
@ -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;
|
@ -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;
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in new issue