mirror of https://github.com/iSoron/uhabits.git
parent
8acbc63914
commit
51d1b93d03
@ -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 <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
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<Repetition> 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;
|
||||||
|
}
|
||||||
|
}
|
@ -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 <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
@ -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 <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
@ -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 <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
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<Streak> 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<Long> 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();
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in new issue