diff --git a/README.md b/README.md
index e3c7796c8..271d6aa9a 100644
--- a/README.md
+++ b/README.md
@@ -1,3 +1,7 @@
+
+
+
+
# Loop Habit Tracker
Loop is a simple Android app that helps you create and maintain good habits,
diff --git a/app/build.gradle b/app/build.gradle
index a1a1bb4bf..14e4da3d0 100644
--- a/app/build.gradle
+++ b/app/build.gradle
@@ -2,13 +2,16 @@ apply plugin: 'com.android.application'
android {
compileSdkVersion 23
- buildToolsVersion "21.1.2"
+ buildToolsVersion "23.0.1"
defaultConfig {
applicationId "org.isoron.uhabits"
minSdkVersion 15
targetSdkVersion 23
+ buildConfigField "Integer", "databaseVersion", "12"
+ buildConfigField "String", "databaseFilename", "\"uhabits.db\""
+
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
}
@@ -40,3 +43,13 @@ dependencies {
androidTestCompile 'com.android.support.test.espresso:espresso-intents:2.2.1'
}
+
+task grantAnimationPermission(type: Exec, dependsOn: 'installDebug') {
+ commandLine "adb shell pm grant org.isoron.uhabits android.permission.SET_ANIMATION_SCALE".split(' ')
+}
+
+tasks.whenTaskAdded { task ->
+ if (task.name.startsWith('connected')) {
+ task.dependsOn grantAnimationPermission
+ }
+}
\ No newline at end of file
diff --git a/app/src/androidTest/java/org/isoron/uhabits/HabitMatchers.java b/app/src/androidTest/java/org/isoron/uhabits/ui/HabitMatchers.java
similarity index 98%
rename from app/src/androidTest/java/org/isoron/uhabits/HabitMatchers.java
rename to app/src/androidTest/java/org/isoron/uhabits/ui/HabitMatchers.java
index c96b24c16..0fbb13f34 100644
--- a/app/src/androidTest/java/org/isoron/uhabits/HabitMatchers.java
+++ b/app/src/androidTest/java/org/isoron/uhabits/ui/HabitMatchers.java
@@ -17,7 +17,7 @@
* with this program. If not, see .
*/
-package org.isoron.uhabits;
+package org.isoron.uhabits.ui;
import android.view.View;
import android.widget.Adapter;
diff --git a/app/src/androidTest/java/org/isoron/uhabits/HabitViewActions.java b/app/src/androidTest/java/org/isoron/uhabits/ui/HabitViewActions.java
similarity index 98%
rename from app/src/androidTest/java/org/isoron/uhabits/HabitViewActions.java
rename to app/src/androidTest/java/org/isoron/uhabits/ui/HabitViewActions.java
index 7c9a7e0a2..afa630ea0 100644
--- a/app/src/androidTest/java/org/isoron/uhabits/HabitViewActions.java
+++ b/app/src/androidTest/java/org/isoron/uhabits/ui/HabitViewActions.java
@@ -17,7 +17,7 @@
* with this program. If not, see .
*/
-package org.isoron.uhabits;
+package org.isoron.uhabits.ui;
import android.support.test.espresso.UiController;
import android.support.test.espresso.ViewAction;
@@ -32,6 +32,7 @@ import android.widget.LinearLayout;
import android.widget.TextView;
import org.hamcrest.Matcher;
+import org.isoron.uhabits.R;
import java.security.InvalidParameterException;
import java.util.Random;
diff --git a/app/src/androidTest/java/org/isoron/uhabits/MainActivityActions.java b/app/src/androidTest/java/org/isoron/uhabits/ui/MainActivityActions.java
similarity index 95%
rename from app/src/androidTest/java/org/isoron/uhabits/MainActivityActions.java
rename to app/src/androidTest/java/org/isoron/uhabits/ui/MainActivityActions.java
index 3ce26da37..412bee5f9 100644
--- a/app/src/androidTest/java/org/isoron/uhabits/MainActivityActions.java
+++ b/app/src/androidTest/java/org/isoron/uhabits/ui/MainActivityActions.java
@@ -17,11 +17,11 @@
* with this program. If not, see .
*/
-package org.isoron.uhabits;
+package org.isoron.uhabits.ui;
-import android.content.Context;
-import android.support.test.InstrumentationRegistry;
+import android.support.test.espresso.matcher.ViewMatchers;
+import org.isoron.uhabits.R;
import org.isoron.uhabits.models.Habit;
import java.util.Collections;
@@ -45,8 +45,8 @@ import static org.hamcrest.Matchers.allOf;
import static org.hamcrest.Matchers.instanceOf;
import static org.hamcrest.Matchers.is;
import static org.hamcrest.Matchers.not;
-import static org.isoron.uhabits.HabitMatchers.containsHabit;
-import static org.isoron.uhabits.HabitMatchers.withName;
+import static org.isoron.uhabits.ui.HabitMatchers.containsHabit;
+import static org.isoron.uhabits.ui.HabitMatchers.withName;
public class MainActivityActions
{
diff --git a/app/src/androidTest/java/org/isoron/uhabits/MainTest.java b/app/src/androidTest/java/org/isoron/uhabits/ui/MainTest.java
similarity index 79%
rename from app/src/androidTest/java/org/isoron/uhabits/MainTest.java
rename to app/src/androidTest/java/org/isoron/uhabits/ui/MainTest.java
index b6047c69a..05877fcb0 100644
--- a/app/src/androidTest/java/org/isoron/uhabits/MainTest.java
+++ b/app/src/androidTest/java/org/isoron/uhabits/ui/MainTest.java
@@ -1,4 +1,4 @@
-package org.isoron.uhabits;
+package org.isoron.uhabits.ui;
import android.content.Context;
import android.support.test.InstrumentationRegistry;
@@ -7,6 +7,8 @@ import android.support.test.espresso.intent.rule.IntentsTestRule;
import android.support.test.runner.AndroidJUnit4;
import android.test.suitebuilder.annotation.LargeTest;
+import org.isoron.uhabits.MainActivity;
+import org.isoron.uhabits.R;
import org.isoron.uhabits.models.Habit;
import org.junit.Before;
import org.junit.Rule;
@@ -37,20 +39,20 @@ import static org.hamcrest.Matchers.allOf;
import static org.hamcrest.Matchers.endsWith;
import static org.hamcrest.Matchers.instanceOf;
import static org.hamcrest.Matchers.is;
-import static org.isoron.uhabits.HabitMatchers.withName;
-import static org.isoron.uhabits.HabitViewActions.clickAtRandomLocations;
-import static org.isoron.uhabits.HabitViewActions.toggleAllCheckmarks;
-import static org.isoron.uhabits.MainActivityActions.addHabit;
-import static org.isoron.uhabits.MainActivityActions.assertHabitExists;
-import static org.isoron.uhabits.MainActivityActions.assertHabitsDontExist;
-import static org.isoron.uhabits.MainActivityActions.assertHabitsExist;
-import static org.isoron.uhabits.MainActivityActions.clickActionModeMenuItem;
-import static org.isoron.uhabits.MainActivityActions.deleteHabit;
-import static org.isoron.uhabits.MainActivityActions.deleteHabits;
-import static org.isoron.uhabits.MainActivityActions.selectHabit;
-import static org.isoron.uhabits.MainActivityActions.selectHabits;
-import static org.isoron.uhabits.MainActivityActions.typeHabitData;
-import static org.isoron.uhabits.ShowHabitActivityActions.openHistoryEditor;
+import static org.isoron.uhabits.ui.HabitMatchers.withName;
+import static org.isoron.uhabits.ui.HabitViewActions.clickAtRandomLocations;
+import static org.isoron.uhabits.ui.HabitViewActions.toggleAllCheckmarks;
+import static org.isoron.uhabits.ui.MainActivityActions.addHabit;
+import static org.isoron.uhabits.ui.MainActivityActions.assertHabitExists;
+import static org.isoron.uhabits.ui.MainActivityActions.assertHabitsDontExist;
+import static org.isoron.uhabits.ui.MainActivityActions.assertHabitsExist;
+import static org.isoron.uhabits.ui.MainActivityActions.clickActionModeMenuItem;
+import static org.isoron.uhabits.ui.MainActivityActions.deleteHabit;
+import static org.isoron.uhabits.ui.MainActivityActions.deleteHabits;
+import static org.isoron.uhabits.ui.MainActivityActions.selectHabit;
+import static org.isoron.uhabits.ui.MainActivityActions.selectHabits;
+import static org.isoron.uhabits.ui.MainActivityActions.typeHabitData;
+import static org.isoron.uhabits.ui.ShowHabitActivityActions.openHistoryEditor;
@RunWith(AndroidJUnit4.class)
@LargeTest
@@ -61,6 +63,16 @@ public class MainTest
MainActivity.class);
@Before
+ public void setup()
+ {
+ Context context = InstrumentationRegistry.getInstrumentation().getContext();
+ SystemHelper sys = new SystemHelper(context);
+ sys.disableAllAnimations();
+ sys.unlockScreen();
+
+ skipTutorial();
+ }
+
public void skipTutorial()
{
try
@@ -118,7 +130,7 @@ public class MainTest
}
@Test
- public void testAddHabitAndViewStats()
+ public void testAddHabitAndViewStats() throws InterruptedException
{
String name = addHabit(true);
@@ -126,6 +138,8 @@ public class MainTest
.onChildView(withId(R.id.llButtons))
.perform(toggleAllCheckmarks());
+ Thread.sleep(1200);
+
onData(allOf(is(instanceOf(Habit.class)), withName(name)))
.onChildView(withId(R.id.label))
.perform(click());
diff --git a/app/src/androidTest/java/org/isoron/uhabits/ShowHabitActivityActions.java b/app/src/androidTest/java/org/isoron/uhabits/ui/ShowHabitActivityActions.java
similarity index 86%
rename from app/src/androidTest/java/org/isoron/uhabits/ShowHabitActivityActions.java
rename to app/src/androidTest/java/org/isoron/uhabits/ui/ShowHabitActivityActions.java
index cacc9f21f..31a89c397 100644
--- a/app/src/androidTest/java/org/isoron/uhabits/ShowHabitActivityActions.java
+++ b/app/src/androidTest/java/org/isoron/uhabits/ui/ShowHabitActivityActions.java
@@ -17,18 +17,21 @@
* with this program. If not, see .
*/
-package org.isoron.uhabits;
+package org.isoron.uhabits.ui;
+
+import android.support.test.espresso.matcher.ViewMatchers;
+
+import org.isoron.uhabits.R;
import static android.support.test.espresso.Espresso.onView;
import static android.support.test.espresso.action.ViewActions.click;
import static android.support.test.espresso.action.ViewActions.scrollTo;
-import static android.support.test.espresso.matcher.ViewMatchers.withId;
public class ShowHabitActivityActions
{
public static void openHistoryEditor()
{
- onView(withId(R.id.btEditHistory))
+ onView(ViewMatchers.withId(R.id.btEditHistory))
.perform(scrollTo(), click());
}
}
diff --git a/app/src/androidTest/java/org/isoron/uhabits/ui/SystemHelper.java b/app/src/androidTest/java/org/isoron/uhabits/ui/SystemHelper.java
new file mode 100644
index 000000000..5fa69e80e
--- /dev/null
+++ b/app/src/androidTest/java/org/isoron/uhabits/ui/SystemHelper.java
@@ -0,0 +1,89 @@
+package org.isoron.uhabits.ui;
+
+import android.app.KeyguardManager;
+import android.content.Context;
+import android.content.pm.PackageManager;
+import android.os.IBinder;
+import android.support.test.runner.AndroidJUnitRunner;
+import android.util.Log;
+
+import java.lang.reflect.Method;
+
+public final class SystemHelper extends AndroidJUnitRunner
+{
+ private static final String ANIMATION_PERMISSION = "android.permission.SET_ANIMATION_SCALE";
+ private static final float DISABLED = 0.0f;
+ private static final float DEFAULT = 1.0f;
+
+ private final Context context;
+
+ SystemHelper(Context context)
+ {
+ this.context = context;
+ }
+
+ void unlockScreen()
+ {
+ try
+ {
+ KeyguardManager mKeyGuardManager = (KeyguardManager) context
+ .getSystemService(Context.KEYGUARD_SERVICE);
+ KeyguardManager.KeyguardLock mLock = mKeyGuardManager.newKeyguardLock("lock");
+ mLock.disableKeyguard();
+ }
+ catch (Exception e)
+ {
+ e.printStackTrace();
+ }
+ }
+
+ void disableAllAnimations()
+ {
+ Log.i("SystemAnimations", "Trying to disable animations");
+ int permStatus = context.checkCallingOrSelfPermission(ANIMATION_PERMISSION);
+ if (permStatus == PackageManager.PERMISSION_GRANTED)
+ setSystemAnimationsScale(DISABLED);
+ else
+ Log.e("SystemAnimations", "Permission denied");
+
+ }
+
+ void enableAllAnimations()
+ {
+ int permStatus = context.checkCallingOrSelfPermission(ANIMATION_PERMISSION);
+ if (permStatus == PackageManager.PERMISSION_GRANTED)
+ {
+ setSystemAnimationsScale(DEFAULT);
+ }
+ }
+
+ private void setSystemAnimationsScale(float animationScale)
+ {
+ try
+ {
+ Class> windowManagerStubClazz = Class.forName("android.view.IWindowManager$Stub");
+ Method asInterface =
+ windowManagerStubClazz.getDeclaredMethod("asInterface", IBinder.class);
+ Class> serviceManagerClazz = Class.forName("android.os.ServiceManager");
+ Method getService = serviceManagerClazz.getDeclaredMethod("getService", String.class);
+ Class> windowManagerClazz = Class.forName("android.view.IWindowManager");
+ Method setAnimationScales =
+ windowManagerClazz.getDeclaredMethod("setAnimationScales", float[].class);
+ Method getAnimationScales = windowManagerClazz.getDeclaredMethod("getAnimationScales");
+
+ IBinder windowManagerBinder = (IBinder) getService.invoke(null, "window");
+ Object windowManagerObj = asInterface.invoke(null, windowManagerBinder);
+ float[] currentScales = (float[]) getAnimationScales.invoke(windowManagerObj);
+ for (int i = 0; i < currentScales.length; i++)
+ currentScales[i] = animationScale;
+
+ setAnimationScales.invoke(windowManagerObj, new Object[]{currentScales});
+ Log.i("SystemAnimations", "All animations successfully disabled");
+ }
+ catch (Exception e)
+ {
+ Log.e("SystemAnimations",
+ "Could not change animation scale to " + animationScale + " :'(");
+ }
+ }
+}
\ No newline at end of file
diff --git a/app/src/androidTest/java/org/isoron/uhabits/unit/models/CheckmarkListTest.java b/app/src/androidTest/java/org/isoron/uhabits/unit/models/CheckmarkListTest.java
new file mode 100644
index 000000000..5c017da75
--- /dev/null
+++ b/app/src/androidTest/java/org/isoron/uhabits/unit/models/CheckmarkListTest.java
@@ -0,0 +1,148 @@
+/*
+ * Copyright (C) 2016 Álinson Santos Xavier
+ *
+ * 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 .
+ */
+
+package org.isoron.uhabits.unit.models;
+
+import android.support.test.runner.AndroidJUnit4;
+import android.test.suitebuilder.annotation.SmallTest;
+
+import org.isoron.helpers.DateHelper;
+import org.isoron.uhabits.models.Habit;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+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
+{
+ Habit nonDailyHabit;
+ private Habit emptyHabit;
+
+ @Before
+ public void prepare()
+ {
+ HabitFixtures.purgeHabits();
+ DateHelper.setFixedLocalTime(HabitFixtures.FIXED_LOCAL_TIME);
+ nonDailyHabit = HabitFixtures.createNonDailyHabit();
+ emptyHabit = HabitFixtures.createEmptyHabit();
+ }
+
+ @After
+ public void tearDown()
+ {
+ DateHelper.setFixedLocalTime(null);
+ }
+
+ @Test
+ public void getAllValues_testNonDailyHabit()
+ {
+ 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 getAllValues_testMoveForwardInTime()
+ {
+ 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 getAllValues_testMoveBackwardsInTime()
+ {
+ 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 getAllValues_testEmptyHabit()
+ {
+ int[] expectedValues = new int[0];
+ int[] actualValues = emptyHabit.checkmarks.getAllValues();
+
+ assertThat(actualValues, equalTo(expectedValues));
+ }
+
+ @Test
+ public void getValues_testInvalidInterval()
+ {
+ int values[] = nonDailyHabit.checkmarks.getValues(100L, -100L);
+ assertThat(values, equalTo(new int[0]));
+ }
+
+ @Test
+ public void getValues_testValidInterval()
+ {
+ long from = DateHelper.getStartOfToday() - 15 * DateHelper.millisecondsInOneDay;
+ long to = DateHelper.getStartOfToday() - 5 * DateHelper.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 getTodayValue_testNonDailyHabit()
+ {
+ 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));
+ }
+
+ private void travelInTime(int days)
+ {
+ DateHelper.setFixedLocalTime(HabitFixtures.FIXED_LOCAL_TIME +
+ days * DateHelper.millisecondsInOneDay);
+ }
+}
diff --git a/app/src/androidTest/java/org/isoron/uhabits/unit/models/HabitFixtures.java b/app/src/androidTest/java/org/isoron/uhabits/unit/models/HabitFixtures.java
new file mode 100644
index 000000000..3cbea7a0d
--- /dev/null
+++ b/app/src/androidTest/java/org/isoron/uhabits/unit/models/HabitFixtures.java
@@ -0,0 +1,60 @@
+/*
+ * Copyright (C) 2016 Álinson Santos Xavier
+ *
+ * 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 .
+ */
+
+package org.isoron.uhabits.unit.models;
+
+import org.isoron.helpers.DateHelper;
+import org.isoron.uhabits.models.Habit;
+
+public class HabitFixtures
+{
+ public static final long FIXED_LOCAL_TIME = 1422172800000L; // 8:00am, January 25th, 2015 (UTC)
+ public static boolean NON_DAILY_HABIT_CHECKS[] = { true, false, false, true, true, true, false,
+ false, true, true };
+
+ static Habit createNonDailyHabit()
+ {
+ Habit habit = new Habit();
+ habit.freqNum = 2;
+ habit.freqDen = 3;
+ habit.save();
+
+ long timestamp = DateHelper.getStartOfToday();
+ for(boolean c : NON_DAILY_HABIT_CHECKS)
+ {
+ if(c) habit.repetitions.toggle(timestamp);
+ timestamp -= DateHelper.millisecondsInOneDay;
+ }
+
+ return habit;
+ }
+
+ static Habit createEmptyHabit()
+ {
+ Habit habit = new Habit();
+ habit.save();
+ return habit;
+ }
+
+ static void purgeHabits()
+ {
+ for(Habit h : Habit.getAll(true))
+ h.cascadeDelete();
+ }
+}
diff --git a/app/src/androidTest/java/org/isoron/uhabits/unit/models/HabitTest.java b/app/src/androidTest/java/org/isoron/uhabits/unit/models/HabitTest.java
new file mode 100644
index 000000000..d46826974
--- /dev/null
+++ b/app/src/androidTest/java/org/isoron/uhabits/unit/models/HabitTest.java
@@ -0,0 +1,95 @@
+/*
+ * Copyright (C) 2016 Álinson Santos Xavier
+ *
+ * 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 .
+ */
+
+package org.isoron.uhabits.unit.models;
+
+import android.support.test.runner.AndroidJUnit4;
+import android.test.suitebuilder.annotation.SmallTest;
+
+import org.isoron.uhabits.models.Habit;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.LinkedList;
+import java.util.List;
+
+import static org.hamcrest.Matchers.is;
+import static org.junit.Assert.assertThat;
+
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public class HabitTest
+{
+ @Before
+ public void prepare()
+ {
+ HabitFixtures.purgeHabits();
+ }
+
+ @Test
+ public void reorderTest()
+ {
+ List ids = new LinkedList<>();
+
+ for (int i = 0; i < 10; i++)
+ {
+ Habit h = new Habit();
+ h.save();
+ ids.add(h.getId());
+ assertThat(h.position, is(i));
+ }
+
+ int from = 5, to = 2;
+ int expectedPosition[] = {0, 1, 3, 4, 5, 2, 6, 7, 8, 9};
+
+ Habit fromHabit = Habit.get(ids.get(from));
+ Habit toHabit = Habit.get(ids.get(to));
+ Habit.reorder(fromHabit, toHabit);
+
+ for (int i = 0; i < 10; i++)
+ {
+ Habit h = Habit.get(ids.get(i));
+ assertThat(h.position, is(expectedPosition[i]));
+ }
+ }
+
+ @Test
+ public void rebuildOrderTest()
+ {
+ List 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));
+ assertThat(h.position, is(i));
+ }
+ }
+}
diff --git a/app/src/androidTest/java/org/isoron/uhabits/unit/models/RepetitionListTest.java b/app/src/androidTest/java/org/isoron/uhabits/unit/models/RepetitionListTest.java
new file mode 100644
index 000000000..1c30c28d0
--- /dev/null
+++ b/app/src/androidTest/java/org/isoron/uhabits/unit/models/RepetitionListTest.java
@@ -0,0 +1,162 @@
+/*
+ * Copyright (C) 2016 Álinson Santos Xavier
+ *
+ * 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 .
+ */
+
+package org.isoron.uhabits.unit.models;
+
+import android.support.test.runner.AndroidJUnit4;
+import android.test.suitebuilder.annotation.SmallTest;
+
+import org.isoron.helpers.DateHelper;
+import org.isoron.uhabits.models.Habit;
+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 org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.Matchers.equalTo;
+
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public class RepetitionListTest
+{
+ Habit habit;
+ private Habit emptyHabit;
+
+ @Before
+ public void prepare()
+ {
+ HabitFixtures.purgeHabits();
+ DateHelper.setFixedLocalTime(HabitFixtures.FIXED_LOCAL_TIME);
+ habit = HabitFixtures.createNonDailyHabit();
+ emptyHabit = HabitFixtures.createEmptyHabit();
+ }
+
+ @After
+ public void tearDown()
+ {
+ DateHelper.setFixedLocalTime(null);
+ }
+
+ @Test
+ public void contains_testNonDailyHabit()
+ {
+ long current = DateHelper.getStartOfToday();
+
+ for(boolean b : HabitFixtures.NON_DAILY_HABIT_CHECKS)
+ {
+ assertThat(habit.repetitions.contains(current), equalTo(b));
+ current -= DateHelper.millisecondsInOneDay;
+ }
+
+ for(int i = 0; i < 3; i++)
+ {
+ assertThat(habit.repetitions.contains(current), equalTo(false));
+ current -= DateHelper.millisecondsInOneDay;
+ }
+ }
+
+ @Test
+ public void delete_test()
+ {
+ long timestamp = DateHelper.getStartOfToday();
+ assertThat(habit.repetitions.contains(timestamp), equalTo(true));
+
+ habit.repetitions.delete(timestamp);
+ assertThat(habit.repetitions.contains(timestamp), equalTo(false));
+ }
+
+ @Test
+ public void toggle_test()
+ {
+ long timestamp = DateHelper.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 getWeekDayFrequency_test()
+ {
+ 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 = DateHelper.getStartOfTodayCalendar();
+
+ // Sets the current date to the end of November
+ day.set(2015, 10, 30);
+ DateHelper.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 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));
+ }
+}
diff --git a/app/src/debug/AndroidManifest.xml b/app/src/debug/AndroidManifest.xml
new file mode 100644
index 000000000..10d453a48
--- /dev/null
+++ b/app/src/debug/AndroidManifest.xml
@@ -0,0 +1,27 @@
+
+
+
+
+
+
+
+
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index 545c02988..6bbdfba86 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -35,19 +35,13 @@
android:maxSdkVersion="18"/>
-
-
diff --git a/app/src/main/java/org/isoron/helpers/DateHelper.java b/app/src/main/java/org/isoron/helpers/DateHelper.java
index 0aef8c0c6..dbc20b35b 100644
--- a/app/src/main/java/org/isoron/helpers/DateHelper.java
+++ b/app/src/main/java/org/isoron/helpers/DateHelper.java
@@ -32,14 +32,22 @@ import java.util.TimeZone;
public class DateHelper
{
public static int millisecondsInOneDay = 24 * 60 * 60 * 1000;
+ private static Long fixedLocalTime = null;
public static long getLocalTime()
{
+ if(fixedLocalTime != null) return fixedLocalTime;
+
TimeZone tz = TimeZone.getDefault();
long now = new Date().getTime();
return now + tz.getOffset(now);
}
+ public static void setFixedLocalTime(Long timestamp)
+ {
+ fixedLocalTime = timestamp;
+ }
+
public static long toLocalTime(long timestamp)
{
TimeZone tz = TimeZone.getDefault();
@@ -54,9 +62,7 @@ public class DateHelper
public static GregorianCalendar getStartOfTodayCalendar()
{
- GregorianCalendar day = new GregorianCalendar(TimeZone.getTimeZone("GMT"));
- day.setTimeInMillis(DateHelper.getStartOfDay(DateHelper.getLocalTime()));
- return day;
+ return getCalendar(getStartOfToday());
}
public static GregorianCalendar getCalendar(long timestamp)
@@ -187,5 +193,4 @@ public class DateHelper
return weekday;
}
-
}
diff --git a/app/src/main/java/org/isoron/uhabits/HabitBroadcastReceiver.java b/app/src/main/java/org/isoron/uhabits/HabitBroadcastReceiver.java
index a43757c84..c4969b835 100644
--- a/app/src/main/java/org/isoron/uhabits/HabitBroadcastReceiver.java
+++ b/app/src/main/java/org/isoron/uhabits/HabitBroadcastReceiver.java
@@ -38,6 +38,7 @@ import android.support.v4.content.LocalBroadcastManager;
import org.isoron.helpers.DateHelper;
import org.isoron.uhabits.helpers.ReminderHelper;
+import org.isoron.uhabits.models.Checkmark;
import org.isoron.uhabits.models.Habit;
import java.util.Date;
@@ -145,7 +146,7 @@ public class HabitBroadcastReceiver extends BroadcastReceiver
Long timestamp = intent.getLongExtra("timestamp", DateHelper.getStartOfToday());
Long reminderTime = intent.getLongExtra("reminderTime", DateHelper.getStartOfToday());
- if (habit.repetitions.hasImplicitRepToday()) return;
+ if (habit.checkmarks.getTodayValue() != Checkmark.UNCHECKED) return;
habit.highlight = 1;
habit.save();
diff --git a/app/src/main/java/org/isoron/uhabits/HabitsApplication.java b/app/src/main/java/org/isoron/uhabits/HabitsApplication.java
new file mode 100644
index 000000000..a7573d946
--- /dev/null
+++ b/app/src/main/java/org/isoron/uhabits/HabitsApplication.java
@@ -0,0 +1,78 @@
+/*
+ * Copyright (C) 2016 Álinson Santos Xavier
+ *
+ * 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 .
+ */
+
+package org.isoron.uhabits;
+
+import android.app.Application;
+
+import com.activeandroid.ActiveAndroid;
+import com.activeandroid.Configuration;
+
+import java.io.File;
+
+public class HabitsApplication extends Application
+{
+ private boolean isTestMode()
+ {
+ try
+ {
+ getClassLoader().loadClass("org.isoron.uhabits.unit.models.HabitTest");
+ return true;
+ }
+ catch (final Exception e)
+ {
+ return false;
+ }
+ }
+
+ private void deleteDB(String databaseFilename)
+ {
+ File databaseFile = new File(String.format("%s/../databases/%s",
+ getApplicationContext().getFilesDir().getPath(), databaseFilename));
+
+ if(databaseFile.exists()) databaseFile.delete();
+ }
+
+ @Override
+ public void onCreate()
+ {
+ super.onCreate();
+ String databaseFilename = BuildConfig.databaseFilename;
+
+ if (isTestMode())
+ {
+ databaseFilename = "test.db";
+ deleteDB(databaseFilename);
+ }
+
+ Configuration dbConfig = new Configuration.Builder(this)
+ .setDatabaseName(databaseFilename)
+ .setDatabaseVersion(BuildConfig.databaseVersion)
+ .create();
+
+ ActiveAndroid.initialize(dbConfig);
+ }
+
+ @Override
+ public void onTerminate()
+ {
+ ActiveAndroid.dispose();
+ super.onTerminate();
+ }
+}
diff --git a/app/src/main/java/org/isoron/uhabits/fragments/ShowHabitFragment.java b/app/src/main/java/org/isoron/uhabits/fragments/ShowHabitFragment.java
index e1dde9c66..6f57abc9c 100644
--- a/app/src/main/java/org/isoron/uhabits/fragments/ShowHabitFragment.java
+++ b/app/src/main/java/org/isoron/uhabits/fragments/ShowHabitFragment.java
@@ -71,8 +71,6 @@ public class ShowHabitFragment extends Fragment
activity = (ShowHabitActivity) getActivity();
habit = activity.habit;
- habit.checkmarks.rebuild();
-
Button btEditHistory = (Button) view.findViewById(R.id.btEditHistory);
streakView = (HabitStreakView) view.findViewById(R.id.streakView);
scoreView = (HabitScoreView) view.findViewById(R.id.scoreView);
diff --git a/app/src/main/java/org/isoron/uhabits/models/Checkmark.java b/app/src/main/java/org/isoron/uhabits/models/Checkmark.java
index e1007a26f..089008efd 100644
--- a/app/src/main/java/org/isoron/uhabits/models/Checkmark.java
+++ b/app/src/main/java/org/isoron/uhabits/models/Checkmark.java
@@ -26,9 +26,21 @@ import com.activeandroid.annotation.Table;
@Table(name = "Checkmarks")
public class Checkmark extends Model
{
-
+ /**
+ * Indicates that there was no repetition at the timestamp, even though a repetition was
+ * expected.
+ */
public static final int UNCHECKED = 0;
+
+ /**
+ * Indicates that there was no repetition at the timestamp, but one was not expected in any
+ * case, due to the frequency of the habit.
+ */
public static final int CHECKED_IMPLICITLY = 1;
+
+ /**
+ * Indicates that there was a repetition at the timestamp.
+ */
public static final int CHECKED_EXPLICITLY = 2;
@Column(name = "habit")
@@ -38,10 +50,9 @@ public class Checkmark extends Model
public Long timestamp;
/**
- * Indicates whether there is a checkmark at the given timestamp or not, and whether the
- * checkmark is explicit or implicit. An explicit checkmark indicates that there is a
- * repetition at that day. An implicit checkmark indicates that there is no repetition at that
- * day, but a repetition was not needed, due to the frequency of the habit.
+ * Indicates whether there is a repetition at the given timestamp or not, and whether the
+ * repetition was expected. Assumes one of the values UNCHECKED, CHECKED_EXPLICITLY or
+ * CHECKED_IMPLICITLY.
*/
@Column(name = "value")
public Integer value;
diff --git a/app/src/main/java/org/isoron/uhabits/models/CheckmarkList.java b/app/src/main/java/org/isoron/uhabits/models/CheckmarkList.java
index 04c8fe458..af638e903 100644
--- a/app/src/main/java/org/isoron/uhabits/models/CheckmarkList.java
+++ b/app/src/main/java/org/isoron/uhabits/models/CheckmarkList.java
@@ -40,6 +40,12 @@ public class CheckmarkList
this.habit = habit;
}
+ /**
+ * Deletes every checkmark that has timestamp either equal or newer than a given timestamp.
+ * These checkmarks will be recomputed at the next time they are queried.
+ *
+ * @param timestamp the timestamp
+ */
public void deleteNewerThan(long timestamp)
{
new Delete().from(Checkmark.class)
@@ -48,10 +54,21 @@ public class CheckmarkList
.execute();
}
+ /**
+ * Returns the values of the checkmarks that fall inside a certain interval of time.
+ *
+ * The values are returned in an array containing one integer value for each day of the
+ * interval. The first entry corresponds to the most recent day in the interval. Each subsequent
+ * entry corresponds to one day older than the previous entry. The boundaries of the time
+ * interval are included.
+ *
+ * @param fromTimestamp timestamp for the oldest checkmark
+ * @param toTimestamp timestamp for the newest checkmark
+ * @return values for the checkmarks inside the given interval
+ */
public int[] getValues(Long fromTimestamp, Long toTimestamp)
{
- rebuild();
-
+ buildCache(fromTimestamp, toTimestamp);
if(fromTimestamp > toTimestamp) return new int[0];
String query = "select value, timestamp from Checkmarks where " +
@@ -81,53 +98,59 @@ public class CheckmarkList
return checks;
}
+ /**
+ * Computes and returns the values for all the checkmarks, since the oldest repetition of the
+ * habit until today. If there are no repetitions at all, returns an empty array.
+ *
+ * The values are returned in an array containing one integer value for each day since the
+ * first repetition of the habit until today. The first entry corresponds to today, the second
+ * entry corresponds to yesterday, and so on.
+ *
+ * @return values for the checkmarks in the interval
+ */
public int[] getAllValues()
{
Repetition oldestRep = habit.repetitions.getOldest();
if(oldestRep == null) return new int[0];
- Long toTimestamp = DateHelper.getStartOfToday();
Long fromTimestamp = oldestRep.timestamp;
+ Long toTimestamp = DateHelper.getStartOfToday();
+
return getValues(fromTimestamp, toTimestamp);
}
- public void rebuild()
+ /**
+ * Computes and stores one checkmark for each day that falls inside the specified interval of
+ * time. Days that already have a corresponding checkmark are skipped.
+ *
+ * @param from timestamp for the beginning of the interval
+ * @param to timestamp for the end of the interval
+ */
+ public void buildCache(long from, long to)
{
- long beginning;
- long today = DateHelper.getStartOfToday();
long day = DateHelper.millisecondsInOneDay;
- Checkmark newestCheckmark = getNewest();
- if (newestCheckmark == null)
- {
- Repetition oldestRep = habit.repetitions.getOldest();
- if (oldestRep == null) return;
-
- beginning = oldestRep.timestamp;
- }
- else
- {
- beginning = newestCheckmark.timestamp + day;
- }
-
- if (beginning > today) return;
+ Checkmark newestCheckmark = findNewest();
+ if(newestCheckmark != null)
+ from = Math.max(from, newestCheckmark.timestamp + day);
- long beginningExtended = beginning - (long) (habit.freqDen) * day;
- List reps = habit.repetitions.selectFromTo(beginningExtended, today).execute();
+ if(from > to) return;
- int nDays = (int) ((today - beginning) / day) + 1;
- int nDaysExtended = (int) ((today - beginningExtended) / day) + 1;
+ long fromExtended = from - (long) (habit.freqDen) * day;
+ List reps = habit.repetitions
+ .selectFromTo(fromExtended, to)
+ .execute();
+ int nDays = (int) ((to - from) / day) + 1;
+ int nDaysExtended = (int) ((to - fromExtended) / day) + 1;
int checks[] = new int[nDaysExtended];
- // explicit checks
for (Repetition rep : reps)
{
- int offset = (int) ((rep.timestamp - beginningExtended) / day);
- checks[nDaysExtended - offset - 1] = 2;
+ int offset = (int) ((rep.timestamp - fromExtended) / day);
+ checks[nDaysExtended - offset - 1] = Checkmark.CHECKED_EXPLICITLY;
}
- // implicit checks
for (int i = 0; i < nDays; i++)
{
int counter = 0;
@@ -135,7 +158,9 @@ public class CheckmarkList
for (int j = 0; j < habit.freqDen; j++)
if (checks[i + j] == 2) counter++;
- if (counter >= habit.freqNum) checks[i] = Math.max(checks[i], 1);
+ if (counter >= habit.freqNum)
+ if(checks[i] != Checkmark.CHECKED_EXPLICITLY)
+ checks[i] = Checkmark.CHECKED_IMPLICITLY;
}
ActiveAndroid.beginTransaction();
@@ -146,33 +171,48 @@ public class CheckmarkList
{
Checkmark c = new Checkmark();
c.habit = habit;
- c.timestamp = today - i * day;
+ c.timestamp = to - i * day;
c.value = checks[i];
c.save();
}
ActiveAndroid.setTransactionSuccessful();
- } finally
+ }
+ finally
{
ActiveAndroid.endTransaction();
}
}
- public Checkmark getNewest()
+ /**
+ * Returns newest checkmark that has already been computed. Ignores any checkmark that has
+ * timestamp in the future. This does not update the cache.
+ */
+ private Checkmark findNewest()
{
return new Select().from(Checkmark.class)
.where("habit = ?", habit.getId())
+ .and("timestamp <= ?", DateHelper.getStartOfToday())
.orderBy("timestamp desc")
.limit(1)
.executeSingle();
}
- public int getCurrentValue()
+ /**
+ * Returns the checkmark for today.
+ */
+ public Checkmark getToday()
{
- rebuild();
- Checkmark c = getNewest();
+ long today = DateHelper.getStartOfToday();
+ buildCache(today, today);
+ return findNewest();
+ }
- if(c != null) return c.value;
- else return 0;
+ /**
+ * Returns the value of today's checkmark.
+ */
+ public int getTodayValue()
+ {
+ return getToday().value;
}
}
diff --git a/app/src/main/java/org/isoron/uhabits/models/RepetitionList.java b/app/src/main/java/org/isoron/uhabits/models/RepetitionList.java
index f21038748..ebdf35c66 100644
--- a/app/src/main/java/org/isoron/uhabits/models/RepetitionList.java
+++ b/app/src/main/java/org/isoron/uhabits/models/RepetitionList.java
@@ -30,7 +30,6 @@ import com.activeandroid.query.Select;
import org.isoron.helpers.DateHelper;
import java.util.Arrays;
-import java.util.Calendar;
import java.util.GregorianCalendar;
import java.util.HashMap;
@@ -48,6 +47,7 @@ public class RepetitionList
{
return new Select().from(Repetition.class)
.where("habit = ?", habit.getId())
+ .and("timestamp <= ?", DateHelper.getStartOfToday())
.orderBy("timestamp");
}
@@ -56,12 +56,23 @@ public class RepetitionList
return select().and("timestamp >= ?", timeFrom).and("timestamp <= ?", timeTo);
}
+ /**
+ * Checks whether there is a repetition at a given timestamp.
+ *
+ * @param timestamp the timestamp to check
+ * @return true if there is a repetition
+ */
public boolean contains(long timestamp)
{
int count = select().where("timestamp = ?", timestamp).count();
return (count > 0);
}
+ /**
+ * Deletes the repetition at a given timestamp, if it exists.
+ *
+ * @param timestamp the timestamp of the repetition to delete
+ */
public void delete(long timestamp)
{
new Delete().from(Repetition.class)
@@ -70,11 +81,12 @@ public class RepetitionList
.execute();
}
- public Repetition getOldestNewerThan(long timestamp)
- {
- return select().where("timestamp > ?", timestamp).limit(1).executeSingle();
- }
-
+ /**
+ * Toggles the repetition at a certain timestamp. That is, deletes the repetition if it exists
+ * or creates one if it does not.
+ *
+ * @param timestamp the timestamp of the repetition to toggle
+ */
public void toggle(long timestamp)
{
timestamp = DateHelper.getStartOfDay(timestamp);
@@ -96,18 +108,27 @@ public class RepetitionList
habit.streaks.deleteNewerThan(timestamp);
}
+ /**
+ * Returns the oldest repetition for the habit. If there is no repetition, returns null.
+ * Repetitions in the future are discarded.
+ *
+ * @return oldest repetition for the habit
+ */
public Repetition getOldest()
{
return (Repetition) select().limit(1).executeSingle();
}
- public boolean hasImplicitRepToday()
- {
- long today = DateHelper.getStartOfToday();
- int reps[] = habit.checkmarks.getValues(today - DateHelper.millisecondsInOneDay, today);
- return (reps[0] > 0);
- }
-
+ /**
+ * Returns the total number of repetitions for each month, from the first repetition until
+ * today, grouped by day of week. The repetitions are returned in a HashMap. The key is the
+ * timestamp for the first day of the month, at midnight (00:00). The value is an integer
+ * array with 7 entries. The first entry contains the total number of repetitions during
+ * the specified month that occurred on a Saturday. The second entry corresponds to Sunday,
+ * and so on. If there are no repetitions during a certain month, the value is null.
+ *
+ * @return total number of repetitions by month versus day of week
+ */
public HashMap getWeekdayFrequency()
{
Repetition oldestRep = getOldest();
@@ -117,10 +138,11 @@ public class RepetitionList
"strftime('%m', timestamp / 1000, 'unixepoch') as month," +
"strftime('%w', timestamp / 1000, 'unixepoch') as weekday, " +
"count(*) from repetitions " +
- "where habit = ? " +
+ "where habit = ? and timestamp <= ? " +
"group by year, month, weekday";
- String[] params = { habit.getId().toString() };
+ String[] params = { habit.getId().toString(),
+ Long.toString(DateHelper.getStartOfToday()) };
SQLiteDatabase db = Cache.openDatabase();
Cursor cursor = db.rawQuery(query, params);
diff --git a/app/src/main/java/org/isoron/uhabits/views/CheckmarkView.java b/app/src/main/java/org/isoron/uhabits/views/CheckmarkView.java
index d6b6fcd5c..ba310c70f 100644
--- a/app/src/main/java/org/isoron/uhabits/views/CheckmarkView.java
+++ b/app/src/main/java/org/isoron/uhabits/views/CheckmarkView.java
@@ -114,7 +114,7 @@ public class CheckmarkView extends View
public void setHabit(Habit habit)
{
- this.check_status = habit.checkmarks.getCurrentValue();
+ this.check_status = habit.checkmarks.getTodayValue();
this.star_status = habit.scores.getCurrentStarStatus();
this.primaryColor = Color.argb(230, Color.red(habit.color), Color.green(habit.color), Color.blue(habit.color));
this.label = habit.name;
diff --git a/build.gradle b/build.gradle
index f4d8c542e..5e38f58a7 100644
--- a/build.gradle
+++ b/build.gradle
@@ -4,7 +4,7 @@ buildscript {
jcenter()
}
dependencies {
- classpath 'com.android.tools.build:gradle:1.5.0'
+ classpath 'com.android.tools.build:gradle:2.1.0-alpha2'
}
}
diff --git a/circle.yml b/circle.yml
new file mode 100644
index 000000000..368dbe466
--- /dev/null
+++ b/circle.yml
@@ -0,0 +1,15 @@
+checkout:
+ post:
+ - git submodule sync
+ - git submodule update --init
+
+test:
+ override:
+ - emulator -avd circleci-android22 -no-audio -no-window:
+ background: true
+ parallel: true
+ - circle-android wait-for-boot
+ - adb shell input keyevent 82
+ - ./gradlew connectedAndroidTest
+ - cp -r app/build/outputs $CIRCLE_ARTIFACTS || echo ok
+ - cp -r app/build/reports/androidTests/connected/* $CIRCLE_TEST_REPORTS || echo ok
\ No newline at end of file
diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties
index 0c71e760d..d57051703 100644
--- a/gradle/wrapper/gradle-wrapper.properties
+++ b/gradle/wrapper/gradle-wrapper.properties
@@ -3,4 +3,4 @@ distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
-distributionUrl=https\://services.gradle.org/distributions/gradle-2.2.1-all.zip
+distributionUrl=https\://services.gradle.org/distributions/gradle-2.10-all.zip
diff --git a/libs/drag-sort-listview b/libs/drag-sort-listview
index 318d69cf6..54ca667d4 160000
--- a/libs/drag-sort-listview
+++ b/libs/drag-sort-listview
@@ -1 +1 @@
-Subproject commit 318d69cf6b2adc287cf8944bb847dd7139c60376
+Subproject commit 54ca667d4cfb0e38d0c9df816360059ac0675afe