diff --git a/app/src/main/java/org/isoron/uhabits/HabitBroadcastReceiver.java b/app/src/main/java/org/isoron/uhabits/HabitBroadcastReceiver.java
index 846760e87..3942f8e52 100644
--- a/app/src/main/java/org/isoron/uhabits/HabitBroadcastReceiver.java
+++ b/app/src/main/java/org/isoron/uhabits/HabitBroadcastReceiver.java
@@ -100,7 +100,7 @@ public class HabitBroadcastReceiver extends BroadcastReceiver
Long timestamp = intent.getLongExtra("timestamp", DateHelper.getStartOfToday());
Habit habit = Habit.get(ContentUris.parseId(data));
- habit.toggleRepetition(timestamp);
+ habit.repetitions.toggle(timestamp);
habit.save();
dismissNotification(context, habit);
@@ -137,7 +137,7 @@ public class HabitBroadcastReceiver extends BroadcastReceiver
Long timestamp = intent.getLongExtra("timestamp", DateHelper.getStartOfToday());
Long reminderTime = intent.getLongExtra("reminderTime", DateHelper.getStartOfToday());
- if (habit.hasImplicitRepToday()) return;
+ if (habit.repetitions.hasImplicitRepToday()) return;
habit.highlight = 1;
habit.save();
diff --git a/app/src/main/java/org/isoron/uhabits/commands/EditHabitCommand.java b/app/src/main/java/org/isoron/uhabits/commands/EditHabitCommand.java
index f70a841b4..53b4bbf1b 100644
--- a/app/src/main/java/org/isoron/uhabits/commands/EditHabitCommand.java
+++ b/app/src/main/java/org/isoron/uhabits/commands/EditHabitCommand.java
@@ -44,9 +44,9 @@ public class EditHabitCommand extends Command
habit.save();
if (hasIntervalChanged)
{
- habit.deleteCheckmarksNewerThan(0);
- habit.deleteStreaksNewerThan(0);
- habit.deleteScoresNewerThan(0);
+ habit.checkmarks.deleteNewerThan(0);
+ habit.streaks.deleteNewerThan(0);
+ habit.scores.deleteNewerThan(0);
}
}
@@ -57,9 +57,9 @@ public class EditHabitCommand extends Command
habit.save();
if (hasIntervalChanged)
{
- habit.deleteCheckmarksNewerThan(0);
- habit.deleteStreaksNewerThan(0);
- habit.deleteScoresNewerThan(0);
+ habit.checkmarks.deleteNewerThan(0);
+ habit.streaks.deleteNewerThan(0);
+ habit.scores.deleteNewerThan(0);
}
}
diff --git a/app/src/main/java/org/isoron/uhabits/commands/ToggleRepetitionCommand.java b/app/src/main/java/org/isoron/uhabits/commands/ToggleRepetitionCommand.java
index 53a95438d..c19ff5a5d 100644
--- a/app/src/main/java/org/isoron/uhabits/commands/ToggleRepetitionCommand.java
+++ b/app/src/main/java/org/isoron/uhabits/commands/ToggleRepetitionCommand.java
@@ -33,7 +33,7 @@ public class ToggleRepetitionCommand extends Command
@Override
public void execute()
{
- habit.toggleRepetition(offset);
+ habit.repetitions.toggle(offset);
}
@Override
diff --git a/app/src/main/java/org/isoron/uhabits/fragments/ListHabitsFragment.java b/app/src/main/java/org/isoron/uhabits/fragments/ListHabitsFragment.java
index 2b1df0750..37c0e4e13 100644
--- a/app/src/main/java/org/isoron/uhabits/fragments/ListHabitsFragment.java
+++ b/app/src/main/java/org/isoron/uhabits/fragments/ListHabitsFragment.java
@@ -75,6 +75,7 @@ import org.isoron.uhabits.helpers.ReminderHelper;
import org.isoron.uhabits.io.CSVExporter;
import org.isoron.uhabits.loaders.HabitListLoader;
import org.isoron.uhabits.models.Habit;
+import org.isoron.uhabits.models.Score;
import java.io.File;
import java.util.Date;
@@ -741,12 +742,12 @@ public class ListHabitsFragment extends Fragment
{
int score = loader.scores.get(habit.getId());
- if (score < Habit.HALF_STAR_CUTOFF)
+ if (score < Score.HALF_STAR_CUTOFF)
{
tvStar.setText(getString(R.string.fa_star_o));
tvStar.setTextColor(INACTIVE_COLOR);
}
- else if (score < Habit.FULL_STAR_CUTOFF)
+ else if (score < Score.FULL_STAR_CUTOFF)
{
tvStar.setText(getString(R.string.fa_star_half_o));
tvStar.setTextColor(INACTIVE_COLOR);
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 de2cc311d..812f832ef 100644
--- a/app/src/main/java/org/isoron/uhabits/fragments/ShowHabitFragment.java
+++ b/app/src/main/java/org/isoron/uhabits/fragments/ShowHabitFragment.java
@@ -34,6 +34,7 @@ import org.isoron.uhabits.R;
import org.isoron.uhabits.ShowHabitActivity;
import org.isoron.uhabits.helpers.ReminderHelper;
import org.isoron.uhabits.models.Habit;
+import org.isoron.uhabits.models.Score;
import org.isoron.uhabits.views.HabitHistoryView;
import org.isoron.uhabits.views.HabitScoreView;
import org.isoron.uhabits.views.HabitStreakView;
@@ -58,7 +59,7 @@ public class ShowHabitFragment extends Fragment implements DialogHelper.OnSavedL
activity = (ShowHabitActivity) getActivity();
habit = activity.habit;
- habit.updateCheckmarks();
+ habit.checkmarks.rebuild();
if (android.os.Build.VERSION.SDK_INT >= 21)
{
@@ -81,7 +82,7 @@ public class ShowHabitFragment extends Fragment implements DialogHelper.OnSavedL
tvStreaks.setTextColor(habit.color);
scoreRing.setColor(habit.color);
- scoreRing.setPercentage((float) habit.getScore() / Habit.MAX_SCORE);
+ scoreRing.setPercentage((float) habit.scores.getNewestValue() / Score.MAX_SCORE);
streakView.setHabit(habit);
scoreView.setHabit(habit);
historyView.setHabit(habit);
diff --git a/app/src/main/java/org/isoron/uhabits/io/CSVExporter.java b/app/src/main/java/org/isoron/uhabits/io/CSVExporter.java
index ee0bde879..1feb81bb2 100644
--- a/app/src/main/java/org/isoron/uhabits/io/CSVExporter.java
+++ b/app/src/main/java/org/isoron/uhabits/io/CSVExporter.java
@@ -8,6 +8,7 @@ import com.activeandroid.Cache;
import org.isoron.helpers.DateHelper;
import org.isoron.uhabits.models.Habit;
+import org.isoron.uhabits.models.Score;
import java.io.File;
import java.io.FileInputStream;
@@ -54,7 +55,7 @@ public class CSVExporter
public String formatScore(int score)
{
- return String.format("%.2f", ((float) score) / Habit.MAX_SCORE);
+ return String.format("%.2f", ((float) score) / Score.MAX_SCORE);
}
private void writeScores(String dirPath, Habit habit) throws IOException
diff --git a/app/src/main/java/org/isoron/uhabits/loaders/HabitListLoader.java b/app/src/main/java/org/isoron/uhabits/loaders/HabitListLoader.java
index bd7443626..204b5e243 100644
--- a/app/src/main/java/org/isoron/uhabits/loaders/HabitListLoader.java
+++ b/app/src/main/java/org/isoron/uhabits/loaders/HabitListLoader.java
@@ -144,8 +144,8 @@ public class HabitListLoader
if (isCancelled()) return null;
Long id = h.getId();
- newScores.put(id, h.getScore());
- newCheckmarks.put(id, h.getCheckmarks(dateFrom, dateTo));
+ newScores.put(id, h.scores.getNewestValue());
+ newCheckmarks.put(id, h.checkmarks.getValues(dateFrom, dateTo));
publishProgress(current++, newHabits.size());
}
@@ -213,8 +213,8 @@ public class HabitListLoader
Habit h = Habit.get(id);
habits.put(id, h);
- scores.put(id, h.getScore());
- checkmarks.put(id, h.getCheckmarks(dateFrom, dateTo));
+ scores.put(id, h.scores.getNewestValue());
+ checkmarks.put(id, h.checkmarks.getValues(dateFrom, dateTo));
return null;
}
diff --git a/app/src/main/java/org/isoron/uhabits/models/CheckmarkList.java b/app/src/main/java/org/isoron/uhabits/models/CheckmarkList.java
new file mode 100644
index 000000000..61187ea30
--- /dev/null
+++ b/app/src/main/java/org/isoron/uhabits/models/CheckmarkList.java
@@ -0,0 +1,175 @@
+/* Copyright (C) 2016 Alinson Santos Xavier
+ *
+ * This program 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.
+ *
+ * This program 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.models;
+
+import android.database.Cursor;
+import android.database.sqlite.SQLiteDatabase;
+
+import com.activeandroid.ActiveAndroid;
+import com.activeandroid.Cache;
+import com.activeandroid.query.Delete;
+import com.activeandroid.query.Select;
+
+import org.isoron.helpers.DateHelper;
+
+import java.util.List;
+
+public class CheckmarkList
+{
+ private Habit habit;
+
+ public CheckmarkList(Habit habit)
+ {
+ this.habit = habit;
+ }
+
+ public void deleteNewerThan(long timestamp)
+ {
+ new Delete().from(Checkmark.class)
+ .where("habit = ?", habit.getId())
+ .and("timestamp >= ?", timestamp)
+ .execute();
+ }
+
+ public int[] getValues(Long fromTimestamp, Long toTimestamp)
+ {
+ rebuild();
+
+ 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(), fromTimestamp.toString(),
+ toTimestamp.toString() };
+ Cursor cursor = db.rawQuery(query, args);
+
+ long day = DateHelper.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;
+ }
+
+ public int[] getAllValues()
+ {
+ Repetition oldestRep = habit.repetitions.getOldest();
+ if(oldestRep == null) return new int[0];
+
+ Long toTimestamp = DateHelper.getStartOfToday();
+ Long fromTimestamp = oldestRep.timestamp;
+ return getValues(fromTimestamp, toTimestamp);
+ }
+
+ public void rebuild()
+ {
+ 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;
+
+ long beginningExtended = beginning - (long) (habit.freqDen) * day;
+ List reps = habit.repetitions.selectFromTo(beginningExtended, today).execute();
+
+ int nDays = (int) ((today - beginning) / day) + 1;
+ int nDaysExtended = (int) ((today - beginningExtended) / 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;
+ }
+
+ // implicit checks
+ for (int i = 0; i < nDays; i++)
+ {
+ int counter = 0;
+
+ for (int j = 0; j < habit.freqDen; j++)
+ if (checks[i + j] == 2) counter++;
+
+ if (counter >= habit.freqNum) checks[i] = Math.max(checks[i], 1);
+ }
+
+ ActiveAndroid.beginTransaction();
+
+ try
+ {
+ for (int i = 0; i < nDays; i++)
+ {
+ Checkmark c = new Checkmark();
+ c.habit = habit;
+ c.timestamp = today - i * day;
+ c.value = checks[i];
+ c.save();
+ }
+
+ ActiveAndroid.setTransactionSuccessful();
+ } finally
+ {
+ ActiveAndroid.endTransaction();
+ }
+ }
+
+ public Checkmark getNewest()
+ {
+ return new Select().from(Checkmark.class)
+ .where("habit = ?", habit.getId())
+ .orderBy("timestamp desc")
+ .limit(1)
+ .executeSingle();
+ }
+
+ public int getCurrentValue()
+ {
+ rebuild();
+ Checkmark c = getNewest();
+
+ if(c != null) return c.value;
+ else return 0;
+ }
+}
diff --git a/app/src/main/java/org/isoron/uhabits/models/Habit.java b/app/src/main/java/org/isoron/uhabits/models/Habit.java
index 23cc5296f..5bcc696f5 100644
--- a/app/src/main/java/org/isoron/uhabits/models/Habit.java
+++ b/app/src/main/java/org/isoron/uhabits/models/Habit.java
@@ -17,12 +17,9 @@
package org.isoron.uhabits.models;
import android.annotation.SuppressLint;
-import android.database.Cursor;
-import android.database.sqlite.SQLiteDatabase;
import android.net.Uri;
import com.activeandroid.ActiveAndroid;
-import com.activeandroid.Cache;
import com.activeandroid.Model;
import com.activeandroid.annotation.Column;
import com.activeandroid.annotation.Table;
@@ -33,21 +30,12 @@ import com.activeandroid.query.Update;
import com.activeandroid.util.SQLiteUtils;
import org.isoron.helpers.ColorHelper;
-import org.isoron.helpers.Command;
-import org.isoron.helpers.DateHelper;
-import org.isoron.uhabits.R;
-import java.util.ArrayList;
import java.util.List;
@Table(name = "Habits")
public class Habit extends Model
{
-
- public static final int HALF_STAR_CUTOFF = 9629750;
- public static final int FULL_STAR_CUTOFF = 15407600;
- public static final int MAX_SCORE = 19259500;
-
@Column(name = "name")
public String name;
@@ -81,20 +69,35 @@ public class Habit extends Model
@Column(name = "archived")
public Integer archived;
+ public StreakList streaks;
+ public ScoreList scores;
+ public RepetitionList repetitions;
+ public CheckmarkList checkmarks;
+
public Habit(Habit model)
{
copyAttributes(model);
+ initializeLists();
}
public Habit()
{
this.color = ColorHelper.palette[5];
- this.position = Habit.getCount();
+ this.position = Habit.count();
this.highlight = 0;
this.archived = 0;
this.freqDen = 7;
this.freqNum = 3;
this.reminderDays = 127;
+ initializeLists();
+ }
+
+ private void initializeLists()
+ {
+ streaks = new StreakList(this);
+ scores = new ScoreList(this);
+ repetitions = new RepetitionList(this);
+ checkmarks = new CheckmarkList(this);
}
public static Habit get(Long id)
@@ -124,7 +127,7 @@ public class Habit extends Model
return new Select().from(Habit.class).orderBy("position");
}
- public static int getCount()
+ public static int count()
{
return select().count();
}
@@ -177,7 +180,8 @@ public class Habit extends Model
}
ActiveAndroid.setTransactionSuccessful();
- } finally
+ }
+ finally
{
ActiveAndroid.endTransaction();
}
@@ -205,22 +209,6 @@ public class Habit extends Model
Habit.updateId(getId(), id);
}
- protected From selectReps()
- {
- return new Select().from(Repetition.class).where("habit = ?", getId()).orderBy("timestamp");
- }
-
- protected From selectRepsFromTo(long timeFrom, long timeTo)
- {
- return selectReps().and("timestamp >= ?", timeFrom).and("timestamp <= ?", timeTo);
- }
-
- public boolean hasRep(long timestamp)
- {
- int count = selectReps().where("timestamp = ?", timestamp).count();
- return (count > 0);
- }
-
public void cascadeDelete()
{
Long id = getId();
@@ -242,209 +230,6 @@ public class Habit extends Model
}
}
- public void deleteReps(long timestamp)
- {
- new Delete().from(Repetition.class)
- .where("habit = ?", getId())
- .and("timestamp = ?", timestamp)
- .execute();
- }
-
- public void deleteCheckmarksNewerThan(long timestamp)
- {
- new Delete().from(Checkmark.class)
- .where("habit = ?", getId())
- .and("timestamp >= ?", timestamp)
- .execute();
- }
-
- public void deleteStreaksNewerThan(long timestamp)
- {
- new Delete().from(Streak.class)
- .where("habit = ?", getId())
- .and("end >= ?", timestamp - DateHelper.millisecondsInOneDay)
- .execute();
- }
-
- public int[] getCheckmarks(Long fromTimestamp, Long toTimestamp)
- {
- updateCheckmarks();
-
- 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[] = {getId().toString(), fromTimestamp.toString(), toTimestamp.toString()};
- Cursor cursor = db.rawQuery(query, args);
-
- long day = DateHelper.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;
- }
-
- public int[] getAllCheckmarks()
- {
- Repetition oldestRep = getOldestRep();
- if(oldestRep == null) return new int[0];
-
- Long toTimestamp = DateHelper.getStartOfToday();
- Long fromTimestamp = oldestRep.timestamp;
- return getCheckmarks(fromTimestamp, toTimestamp);
- }
-
- public void updateCheckmarks()
- {
- long beginning;
- long today = DateHelper.getStartOfToday();
- long day = DateHelper.millisecondsInOneDay;
-
- Checkmark newestCheckmark = getNewestCheckmark();
- if (newestCheckmark == null)
- {
- Repetition oldestRep = getOldestRep();
- if (oldestRep == null) return;
-
- beginning = oldestRep.timestamp;
- }
- else
- {
- beginning = newestCheckmark.timestamp + day;
- }
-
- if (beginning > today) return;
-
- long beginningExtended = beginning - (long) (freqDen) * day;
- List reps = selectRepsFromTo(beginningExtended, today).execute();
-
- int nDays = (int) ((today - beginning) / day) + 1;
- int nDaysExtended = (int) ((today - beginningExtended) / 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;
- }
-
- // implicit checks
- for (int i = 0; i < nDays; i++)
- {
- int counter = 0;
-
- for (int j = 0; j < freqDen; j++)
- if (checks[i + j] == 2) counter++;
-
- if (counter >= freqNum) checks[i] = Math.max(checks[i], 1);
- }
-
- ActiveAndroid.beginTransaction();
-
- try
- {
- for (int i = 0; i < nDays; i++)
- {
- Checkmark c = new Checkmark();
- c.habit = this;
- c.timestamp = today - i * day;
- c.value = checks[i];
- c.save();
- }
-
- ActiveAndroid.setTransactionSuccessful();
- } finally
- {
- ActiveAndroid.endTransaction();
- }
- }
-
- public Checkmark getNewestCheckmark()
- {
- return new Select().from(Checkmark.class)
- .where("habit = ?", getId())
- .orderBy("timestamp desc")
- .limit(1)
- .executeSingle();
- }
-
- public int getCurrentCheckmarkStatus()
- {
- updateCheckmarks();
- Checkmark c = getNewestCheckmark();
-
- if(c != null) return c.value;
- else return 0;
- }
-
- public int getCurrentStarStatus()
- {
- int score = getScore();
-
- if(score >= FULL_STAR_CUTOFF) return 2;
- else if(score >= HALF_STAR_CUTOFF) return 1;
- else return 0;
- }
-
- public int getRepsCount(int days)
- {
- long timeTo = DateHelper.getStartOfToday();
- long timeFrom = timeTo - DateHelper.millisecondsInOneDay * days;
- return selectRepsFromTo(timeFrom, timeTo).count();
- }
-
- public boolean hasImplicitRepToday()
- {
- long today = DateHelper.getStartOfToday();
- int reps[] = getCheckmarks(today - DateHelper.millisecondsInOneDay, today);
- return (reps[0] > 0);
- }
-
- public Repetition getOldestRep()
- {
- return (Repetition) selectReps().limit(1).executeSingle();
- }
-
- public Repetition getOldestRepNewerThan(long timestamp)
- {
- return selectReps().where("timestamp > ?", timestamp).limit(1).executeSingle();
- }
-
- public void toggleRepetition(long timestamp)
- {
- if (hasRep(timestamp))
- {
- deleteReps(timestamp);
- }
- else
- {
- Repetition rep = new Repetition();
- rep.habit = this;
- rep.timestamp = timestamp;
- rep.save();
- }
-
- deleteScoresNewerThan(timestamp);
- deleteCheckmarksNewerThan(timestamp);
- deleteStreaksNewerThan(timestamp);
- }
-
public Uri getUri()
{
return Uri.parse(String.format("content://org.isoron.uhabits/habit/%d", getId()));
@@ -466,204 +251,4 @@ public class Habit extends Model
{
return archived != 0;
}
-
- public void toggleRepetitionToday()
- {
- toggleRepetition(DateHelper.getStartOfToday());
- }
-
- public Score getNewestScore()
- {
- return new Select().from(Score.class)
- .where("habit = ?", getId())
- .orderBy("timestamp desc")
- .limit(1)
- .executeSingle();
- }
-
- public void deleteScoresNewerThan(long timestamp)
- {
- new Delete().from(Score.class)
- .where("habit = ?", getId())
- .and("timestamp >= ?", timestamp)
- .execute();
- }
-
- public Integer getScore()
- {
- int beginningScore;
- long beginningTime;
-
- long today = DateHelper.getStartOfDay(DateHelper.getLocalTime());
- long day = DateHelper.millisecondsInOneDay;
-
- double freq = ((double) freqNum) / freqDen;
- double multiplier = Math.pow(0.5, 1.0 / (14.0 / freq - 1));
-
- Score newestScore = getNewestScore();
- if (newestScore == null)
- {
- Repetition oldestRep = getOldestRep();
- if (oldestRep == null) return 0;
- beginningTime = oldestRep.timestamp;
- beginningScore = 0;
- }
- else
- {
- beginningTime = newestScore.timestamp + day;
- beginningScore = newestScore.score;
- }
-
- long nDays = (today - beginningTime) / day;
- if (nDays < 0) return newestScore.score;
-
- int reps[] = getCheckmarks(beginningTime, today);
-
- ActiveAndroid.beginTransaction();
- int lastScore = beginningScore;
-
- try
- {
- for (int i = 0; i < reps.length; i++)
- {
- Score s = new Score();
- s.habit = this;
- s.timestamp = beginningTime + day * i;
- s.score = (int) (lastScore * multiplier);
- if (reps[reps.length - i - 1] == 2)
- {
- s.score += 1000000;
- s.score = Math.min(s.score, MAX_SCORE);
- }
- s.save();
-
- lastScore = s.score;
- }
-
- ActiveAndroid.setTransactionSuccessful();
- } finally
- {
- ActiveAndroid.endTransaction();
- }
-
- return lastScore;
- }
-
- public int[] getScores(Long fromTimestamp, Long toTimestamp, Integer divisor, Long offset)
- {
- String query = "select score from Score where habit = ? and timestamp > ? and " +
- "timestamp <= ? and (timestamp - ?) % ? = 0 order by timestamp desc";
-
- String params[] = {getId().toString(), fromTimestamp.toString(), toTimestamp.toString(),
- offset.toString(), divisor.toString()};
-
- 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++] = cursor.getInt(0);
- }
- while (cursor.moveToNext());
-
- cursor.close();
- return scores;
-
- }
-
- public int[] getAllScores(int divisor)
- {
- Repetition oldestRep = getOldestRep();
- if(oldestRep == null) return new int[0];
-
- long fromTimestamp = oldestRep.timestamp;
- long toTimestamp = DateHelper.getStartOfToday();
- return getScores(fromTimestamp, toTimestamp, divisor, toTimestamp);
- }
-
- public List getStreaks()
- {
- updateStreaks();
-
- return new Select().from(Streak.class)
- .where("habit = ?", getId())
- .orderBy("end asc")
- .execute();
- }
-
- public Streak getNewestStreak()
- {
- return new Select().from(Streak.class)
- .where("habit = ?", getId())
- .orderBy("end desc")
- .limit(1)
- .executeSingle();
- }
-
- public void updateStreaks()
- {
- long beginning;
- long today = DateHelper.getStartOfToday();
- long day = DateHelper.millisecondsInOneDay;
-
- Streak newestStreak = getNewestStreak();
- if (newestStreak == null)
- {
- Repetition oldestRep = getOldestRep();
- if (oldestRep == null) return;
-
- beginning = oldestRep.timestamp;
- }
- else
- {
- Repetition oldestRep = getOldestRepNewerThan(newestStreak.end);
- if (oldestRep == null) return;
-
- beginning = oldestRep.timestamp;
- }
-
- if (beginning > today) return;
-
- int checks[] = getCheckmarks(beginning, today);
- ArrayList list = new ArrayList<>();
-
- long current = beginning;
- list.add(current);
-
- for (int i = 1; i < checks.length; i++)
- {
- current += day;
- int j = checks.length - i - 1;
-
- if ((checks[j + 1] == 0 && checks[j] > 0)) list.add(current);
- if ((checks[j + 1] > 0 && checks[j] == 0)) list.add(current - day);
- }
-
- if (list.size() % 2 == 1) list.add(current);
-
- ActiveAndroid.beginTransaction();
-
- try
- {
- for (int i = 0; i < list.size(); i += 2)
- {
- Streak streak = new Streak();
- streak.habit = this;
- streak.start = list.get(i);
- streak.end = list.get(i + 1);
- streak.length = (streak.end - streak.start) / day + 1;
- streak.save();
- }
-
- ActiveAndroid.setTransactionSuccessful();
- } finally
- {
- ActiveAndroid.endTransaction();
- }
- }
}
diff --git a/app/src/main/java/org/isoron/uhabits/models/RepetitionList.java b/app/src/main/java/org/isoron/uhabits/models/RepetitionList.java
new file mode 100644
index 000000000..6828101ab
--- /dev/null
+++ b/app/src/main/java/org/isoron/uhabits/models/RepetitionList.java
@@ -0,0 +1,96 @@
+/* Copyright (C) 2016 Alinson Santos Xavier
+ *
+ * This program 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.
+ *
+ * This program 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.models;
+
+import com.activeandroid.query.Delete;
+import com.activeandroid.query.From;
+import com.activeandroid.query.Select;
+
+import org.isoron.helpers.DateHelper;
+
+public class RepetitionList
+{
+
+ private Habit habit;
+
+ public RepetitionList(Habit habit)
+ {
+ this.habit = habit;
+ }
+
+ protected From select()
+ {
+ return new Select().from(Repetition.class)
+ .where("habit = ?", habit.getId())
+ .orderBy("timestamp");
+ }
+
+ protected From selectFromTo(long timeFrom, long timeTo)
+ {
+ return select().and("timestamp >= ?", timeFrom).and("timestamp <= ?", timeTo);
+ }
+
+ public boolean contains(long timestamp)
+ {
+ int count = select().where("timestamp = ?", timestamp).count();
+ return (count > 0);
+ }
+
+ public void delete(long timestamp)
+ {
+ new Delete().from(Repetition.class)
+ .where("habit = ?", habit.getId())
+ .and("timestamp = ?", timestamp)
+ .execute();
+ }
+
+ public Repetition getOldestNewerThan(long timestamp)
+ {
+ return select().where("timestamp > ?", timestamp).limit(1).executeSingle();
+ }
+
+ public void toggle(long timestamp)
+ {
+ if (contains(timestamp))
+ {
+ delete(timestamp);
+ }
+ else
+ {
+ Repetition rep = new Repetition();
+ rep.habit = habit;
+ rep.timestamp = timestamp;
+ rep.save();
+ }
+
+ habit.scores.deleteNewerThan(timestamp);
+ habit.checkmarks.deleteNewerThan(timestamp);
+ habit.streaks.deleteNewerThan(timestamp);
+ }
+
+ 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);
+ }
+}
diff --git a/app/src/main/java/org/isoron/uhabits/models/Score.java b/app/src/main/java/org/isoron/uhabits/models/Score.java
index 33f1723cc..44af97588 100644
--- a/app/src/main/java/org/isoron/uhabits/models/Score.java
+++ b/app/src/main/java/org/isoron/uhabits/models/Score.java
@@ -23,6 +23,10 @@ import com.activeandroid.annotation.Table;
@Table(name = "Score")
public class Score extends Model
{
+ public static final int HALF_STAR_CUTOFF = 9629750;
+ public static final int FULL_STAR_CUTOFF = 15407600;
+ public static final int MAX_SCORE = 19259500;
+
@Column(name = "habit")
public Habit habit;
diff --git a/app/src/main/java/org/isoron/uhabits/models/ScoreList.java b/app/src/main/java/org/isoron/uhabits/models/ScoreList.java
new file mode 100644
index 000000000..95b3c72db
--- /dev/null
+++ b/app/src/main/java/org/isoron/uhabits/models/ScoreList.java
@@ -0,0 +1,160 @@
+/* Copyright (C) 2016 Alinson Santos Xavier
+ *
+ * This program 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.
+ *
+ * This program 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.models;
+
+import android.database.Cursor;
+import android.database.sqlite.SQLiteDatabase;
+
+import com.activeandroid.ActiveAndroid;
+import com.activeandroid.Cache;
+import com.activeandroid.query.Delete;
+import com.activeandroid.query.Select;
+
+import org.isoron.helpers.DateHelper;
+
+public class ScoreList
+{
+ private Habit habit;
+
+ public ScoreList(Habit habit)
+ {
+ this.habit = habit;
+ }
+
+ public int getCurrentStarStatus()
+ {
+ int score = getNewestValue();
+
+ if(score >= Score.FULL_STAR_CUTOFF) return 2;
+ else if(score >= Score.HALF_STAR_CUTOFF) return 1;
+ else return 0;
+ }
+
+ public Score getNewest()
+ {
+ return new Select().from(Score.class)
+ .where("habit = ?", habit.getId())
+ .orderBy("timestamp desc")
+ .limit(1)
+ .executeSingle();
+ }
+
+ public void deleteNewerThan(long timestamp)
+ {
+ new Delete().from(Score.class)
+ .where("habit = ?", habit.getId())
+ .and("timestamp >= ?", timestamp)
+ .execute();
+ }
+
+ public Integer getNewestValue()
+ {
+ int beginningScore;
+ long beginningTime;
+
+ long today = DateHelper.getStartOfDay(DateHelper.getLocalTime());
+ long day = DateHelper.millisecondsInOneDay;
+
+ double freq = ((double) habit.freqNum) / habit.freqDen;
+ double multiplier = Math.pow(0.5, 1.0 / (14.0 / freq - 1));
+
+ Score newestScore = getNewest();
+ if (newestScore == null)
+ {
+ Repetition oldestRep = habit.repetitions.getOldest();
+ if (oldestRep == null) return 0;
+ beginningTime = oldestRep.timestamp;
+ beginningScore = 0;
+ }
+ else
+ {
+ beginningTime = newestScore.timestamp + day;
+ beginningScore = newestScore.score;
+ }
+
+ long nDays = (today - beginningTime) / day;
+ if (nDays < 0) return newestScore.score;
+
+ int reps[] = habit.checkmarks.getValues(beginningTime, today);
+
+ ActiveAndroid.beginTransaction();
+ int lastScore = beginningScore;
+
+ try
+ {
+ for (int i = 0; i < reps.length; i++)
+ {
+ Score s = new Score();
+ s.habit = habit;
+ s.timestamp = beginningTime + day * i;
+ s.score = (int) (lastScore * multiplier);
+ if (reps[reps.length - i - 1] == 2)
+ {
+ s.score += 1000000;
+ s.score = Math.min(s.score, Score.MAX_SCORE);
+ }
+ s.save();
+
+ lastScore = s.score;
+ }
+
+ ActiveAndroid.setTransactionSuccessful();
+ } finally
+ {
+ ActiveAndroid.endTransaction();
+ }
+
+ return lastScore;
+ }
+
+ public int[] getAllValues(Long fromTimestamp, Long toTimestamp, Integer divisor, Long offset)
+ {
+ String query = "select score from Score where habit = ? and timestamp > ? and " +
+ "timestamp <= ? and (timestamp - ?) % ? = 0 order by timestamp desc";
+
+ String params[] = { habit.getId().toString(), fromTimestamp.toString(),
+ toTimestamp.toString(), offset.toString(), divisor.toString()};
+
+ 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++] = cursor.getInt(0);
+ }
+ while (cursor.moveToNext());
+
+ cursor.close();
+ return scores;
+
+ }
+
+ public int[] getAllValues(int divisor)
+ {
+ Repetition oldestRep = habit.repetitions.getOldest();
+ if(oldestRep == null) return new int[0];
+
+ long fromTimestamp = oldestRep.timestamp;
+ long toTimestamp = DateHelper.getStartOfToday();
+ return getAllValues(fromTimestamp, toTimestamp, divisor, toTimestamp);
+ }
+}
diff --git a/app/src/main/java/org/isoron/uhabits/models/StreakList.java b/app/src/main/java/org/isoron/uhabits/models/StreakList.java
new file mode 100644
index 000000000..9459acab0
--- /dev/null
+++ b/app/src/main/java/org/isoron/uhabits/models/StreakList.java
@@ -0,0 +1,127 @@
+/* Copyright (C) 2016 Alinson Santos Xavier
+ *
+ * This program 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.
+ *
+ * This program 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.models;
+
+import com.activeandroid.ActiveAndroid;
+import com.activeandroid.query.Delete;
+import com.activeandroid.query.Select;
+
+import org.isoron.helpers.DateHelper;
+
+import java.util.ArrayList;
+import java.util.List;
+
+public class StreakList
+{
+ private Habit habit;
+
+ public StreakList(Habit habit)
+ {
+ this.habit = habit;
+ }
+
+ public List getAll()
+ {
+ rebuild();
+
+ return new Select().from(Streak.class)
+ .where("habit = ?", habit.getId())
+ .orderBy("end asc")
+ .execute();
+ }
+
+ public Streak getNewest()
+ {
+ return new Select().from(Streak.class)
+ .where("habit = ?", habit.getId())
+ .orderBy("end desc")
+ .limit(1)
+ .executeSingle();
+ }
+
+ public void rebuild()
+ {
+ long beginning;
+ long today = DateHelper.getStartOfToday();
+ long day = DateHelper.millisecondsInOneDay;
+
+ Streak newestStreak = getNewest();
+ if (newestStreak == null)
+ {
+ Repetition oldestRep = habit.repetitions.getOldest();
+ if (oldestRep == null) return;
+
+ beginning = oldestRep.timestamp;
+ }
+ else
+ {
+ Repetition oldestRep = habit.repetitions.getOldestNewerThan(newestStreak.end);
+ if (oldestRep == null) return;
+
+ beginning = oldestRep.timestamp;
+ }
+
+ if (beginning > today) return;
+
+ int checks[] = habit.checkmarks.getValues(beginning, today);
+ ArrayList list = new ArrayList<>();
+
+ long current = beginning;
+ list.add(current);
+
+ for (int i = 1; i < checks.length; i++)
+ {
+ current += day;
+ int j = checks.length - i - 1;
+
+ if ((checks[j + 1] == 0 && checks[j] > 0)) list.add(current);
+ if ((checks[j + 1] > 0 && checks[j] == 0)) list.add(current - day);
+ }
+
+ if (list.size() % 2 == 1) list.add(current);
+
+ ActiveAndroid.beginTransaction();
+
+ try
+ {
+ for (int i = 0; i < list.size(); i += 2)
+ {
+ Streak streak = new Streak();
+ streak.habit = habit;
+ streak.start = list.get(i);
+ streak.end = list.get(i + 1);
+ streak.length = (streak.end - streak.start) / day + 1;
+ streak.save();
+ }
+
+ ActiveAndroid.setTransactionSuccessful();
+ }
+ finally
+ {
+ ActiveAndroid.endTransaction();
+ }
+ }
+
+
+ public void deleteNewerThan(long timestamp)
+ {
+ new Delete().from(Streak.class)
+ .where("habit = ?", habit.getId())
+ .and("end >= ?", timestamp - DateHelper.millisecondsInOneDay)
+ .execute();
+ }
+}
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 e44096bce..223217aad 100644
--- a/app/src/main/java/org/isoron/uhabits/views/CheckmarkView.java
+++ b/app/src/main/java/org/isoron/uhabits/views/CheckmarkView.java
@@ -27,7 +27,6 @@ import android.text.Layout;
import android.text.StaticLayout;
import android.text.TextPaint;
import android.util.AttributeSet;
-import android.util.Log;
import android.view.View;
import org.isoron.helpers.ColorHelper;
@@ -112,8 +111,8 @@ public class CheckmarkView extends View
public void setHabit(Habit habit)
{
- this.check_status = habit.getCurrentCheckmarkStatus();
- this.star_status = habit.getCurrentStarStatus();
+ this.check_status = habit.checkmarks.getCurrentValue();
+ 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;
updateLabel();
diff --git a/app/src/main/java/org/isoron/uhabits/views/HabitHistoryView.java b/app/src/main/java/org/isoron/uhabits/views/HabitHistoryView.java
index 4d841b824..d99ba765b 100644
--- a/app/src/main/java/org/isoron/uhabits/views/HabitHistoryView.java
+++ b/app/src/main/java/org/isoron/uhabits/views/HabitHistoryView.java
@@ -23,7 +23,6 @@ import android.graphics.Paint;
import android.graphics.Paint.Align;
import android.graphics.Rect;
import android.util.AttributeSet;
-import android.util.Log;
import org.isoron.helpers.ColorHelper;
import org.isoron.helpers.DateHelper;
@@ -189,7 +188,7 @@ public class HabitHistoryView extends ScrollableDataView
return;
}
- checkmarks = habit.getAllCheckmarks();
+ checkmarks = habit.checkmarks.getAllValues();
}
updateDate();
diff --git a/app/src/main/java/org/isoron/uhabits/views/HabitScoreView.java b/app/src/main/java/org/isoron/uhabits/views/HabitScoreView.java
index 3405342aa..248341059 100644
--- a/app/src/main/java/org/isoron/uhabits/views/HabitScoreView.java
+++ b/app/src/main/java/org/isoron/uhabits/views/HabitScoreView.java
@@ -28,6 +28,7 @@ import android.util.AttributeSet;
import org.isoron.helpers.ColorHelper;
import org.isoron.helpers.DateHelper;
import org.isoron.uhabits.models.Habit;
+import org.isoron.uhabits.models.Score;
import java.text.SimpleDateFormat;
import java.util.Locale;
@@ -168,7 +169,7 @@ public class HabitScoreView extends ScrollableDataView
return;
}
- scores = habit.getAllScores(BUCKET_SIZE * DateHelper.millisecondsInOneDay);
+ scores = habit.scores.getAllValues(BUCKET_SIZE * DateHelper.millisecondsInOneDay);
}
}
@@ -177,13 +178,13 @@ public class HabitScoreView extends ScrollableDataView
{
Random random = new Random();
scores = new int[100];
- scores[0] = Habit.MAX_SCORE / 2;
+ scores[0] = Score.MAX_SCORE / 2;
for(int i = 1; i < 100; i++)
{
- int step = Habit.MAX_SCORE / 10;
+ int step = Score.MAX_SCORE / 10;
scores[i] = scores[i - 1] + random.nextInt(step * 2) - step;
- scores[i] = Math.max(0, Math.min(Habit.MAX_SCORE, scores[i]));
+ scores[i] = Math.max(0, Math.min(Score.MAX_SCORE, scores[i]));
}
}
@@ -220,7 +221,7 @@ public class HabitScoreView extends ScrollableDataView
int offset = nColumns - k - 1 + getDataOffset();
if(offset < scores.length) score = scores[offset];
- double sRelative = ((double) score) / Habit.MAX_SCORE;
+ double sRelative = ((double) score) / Score.MAX_SCORE;
int height = (int) (columnHeight * sRelative);
rect.set(0, 0, baseSize, baseSize);
diff --git a/app/src/main/java/org/isoron/uhabits/views/HabitStreakView.java b/app/src/main/java/org/isoron/uhabits/views/HabitStreakView.java
index 646ead665..5872d96c9 100644
--- a/app/src/main/java/org/isoron/uhabits/views/HabitStreakView.java
+++ b/app/src/main/java/org/isoron/uhabits/views/HabitStreakView.java
@@ -166,7 +166,7 @@ public class HabitStreakView extends ScrollableDataView
return;
}
- List streaks = habit.getStreaks();
+ List streaks = habit.streaks.getAll();
int size = streaks.size();
startTimes = new long[size];