diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index cd3780be7..786a91380 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -21,7 +21,7 @@
+ android:value="9"/>
();
}
- public void executeCommand(Command command)
+ public void executeCommand(Command command, Long refreshKey)
{
- executeCommand(command, false);
- BackupManager.dataChanged("org.isoron.uhabits");
+ executeCommand(command, false, refreshKey);
}
protected void undo()
@@ -54,7 +54,7 @@ abstract public class ReplayableActivity extends Activity
return;
}
Command last = redoList.pop();
- executeCommand(last, false);
+ executeCommand(last, false, null);
}
public void showToast(Integer stringId)
@@ -65,14 +65,36 @@ abstract public class ReplayableActivity extends Activity
toast.show();
}
-
- public void executeCommand(Command command, boolean clearRedoStack)
+ public void executeCommand(final Command command, Boolean clearRedoStack,
+ final Long refreshKey)
{
undoList.push(command);
+
if (undoList.size() > MAX_UNDO_LEVEL) undoList.removeLast();
if (clearRedoStack) redoList.clear();
- command.execute();
+ new AsyncTask()
+ {
+ @Override
+ protected Void doInBackground(Void... params)
+ {
+ command.execute();
+ return null;
+ }
+
+ @Override
+ protected void onPostExecute(Void aVoid)
+ {
+ ReplayableActivity.this.onPostExecuteCommand(refreshKey);
+ BackupManager.dataChanged("org.isoron.uhabits");
+ }
+ }.execute();
+
+
showToast(command.getExecuteStringId());
}
+
+ public void onPostExecuteCommand(Long refreshKey)
+ {
+ }
}
diff --git a/app/src/main/java/org/isoron/uhabits/MainActivity.java b/app/src/main/java/org/isoron/uhabits/MainActivity.java
index e3adc04f6..007cedf47 100644
--- a/app/src/main/java/org/isoron/uhabits/MainActivity.java
+++ b/app/src/main/java/org/isoron/uhabits/MainActivity.java
@@ -6,6 +6,7 @@ import android.content.SharedPreferences;
import android.net.Uri;
import android.os.Bundle;
import android.preference.PreferenceManager;
+import android.util.Log;
import android.view.Menu;
import android.view.MenuItem;
@@ -33,6 +34,7 @@ public class MainActivity extends ReplayableActivity
ReminderHelper.createReminderAlarms(MainActivity.this);
showTutorial();
+
}
private void showTutorial()
@@ -51,13 +53,6 @@ public class MainActivity extends ReplayableActivity
}
}
- @Override
- protected void onStart()
- {
- super.onStart();
- listHabitsFragment.notifyDataSetChanged();
- }
-
@Override
public boolean onCreateOptionsMenu(Menu menu)
{
@@ -89,9 +84,8 @@ public class MainActivity extends ReplayableActivity
}
@Override
- public void executeCommand(Command command)
+ public void onPostExecuteCommand(Long refreshKey)
{
- super.executeCommand(command);
- listHabitsFragment.notifyDataSetChanged();
+ listHabitsFragment.onPostExecuteCommand(refreshKey);
}
}
diff --git a/app/src/main/java/org/isoron/uhabits/dialogs/EditHabitFragment.java b/app/src/main/java/org/isoron/uhabits/dialogs/EditHabitFragment.java
index c4aea772b..3af652696 100644
--- a/app/src/main/java/org/isoron/uhabits/dialogs/EditHabitFragment.java
+++ b/app/src/main/java/org/isoron/uhabits/dialogs/EditHabitFragment.java
@@ -263,14 +263,19 @@ public class EditHabitFragment extends DialogFragment implements OnClickListener
editor.putInt("pref_default_habit_freq_den", modified_habit.freq_den);
editor.apply();
+ Habit savedHabit = null;
+
if(mode == EDIT_MODE)
+ {
command = originalHabit.new EditCommand(modified_habit);
+ savedHabit = originalHabit;
+ }
if(mode == CREATE_MODE)
command = new Habit.CreateCommand(modified_habit);
if(onSavedListener != null)
- onSavedListener.onSaved(command);
+ onSavedListener.onSaved(command, savedHabit);
dismiss();
}
diff --git a/app/src/main/java/org/isoron/uhabits/dialogs/ListHabitsFragment.java b/app/src/main/java/org/isoron/uhabits/dialogs/ListHabitsFragment.java
index 6b7936346..b6a0b08aa 100644
--- a/app/src/main/java/org/isoron/uhabits/dialogs/ListHabitsFragment.java
+++ b/app/src/main/java/org/isoron/uhabits/dialogs/ListHabitsFragment.java
@@ -7,7 +7,9 @@ import android.content.SharedPreferences;
import android.graphics.Color;
import android.graphics.Point;
import android.graphics.Typeface;
+import android.os.AsyncTask;
import android.os.Bundle;
+import android.os.Handler;
import android.os.Vibrator;
import android.preference.PreferenceManager;
import android.util.DisplayMetrics;
@@ -30,6 +32,7 @@ import android.widget.BaseAdapter;
import android.widget.Button;
import android.widget.LinearLayout;
import android.widget.LinearLayout.LayoutParams;
+import android.widget.ProgressBar;
import android.widget.TextView;
import com.mobeta.android.dslv.DragSortController;
@@ -47,6 +50,7 @@ import org.isoron.uhabits.models.Habit;
import java.util.Date;
import java.util.GregorianCalendar;
+import java.util.HashMap;
import java.util.Locale;
import java.util.TimeZone;
@@ -54,6 +58,9 @@ public class ListHabitsFragment extends Fragment
implements OnSavedListener, OnItemClickListener, OnLongClickListener, DropListener,
OnClickListener
{
+
+ public static final int INACTIVE_COLOR = Color.rgb(230, 230, 230);
+
public interface OnHabitClickListener
{
void onHabitClicked(Habit habit);
@@ -67,23 +74,39 @@ public class ListHabitsFragment extends Fragment
private int tvNameWidth;
private int button_count;
private View llEmpty;
+ private ProgressBar progressBar;
private OnHabitClickListener habitClickListener;
private boolean short_toggle_enabled;
+ private HashMap habits;
+ private HashMap positionToHabit;
+ private HashMap checkmarks;
+ private HashMap scores;
+
+ private Long lastLoadedTimestamp = null;
+ private AsyncTask currentFetchTask = null;
+
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState)
{
- View view = inflater.inflate(R.layout.list_habits_fragment, container, false);
-
DisplayMetrics dm = getResources().getDisplayMetrics();
int width = (int) (dm.widthPixels / dm.density);
button_count = (int) ((width - 160) / 42);
tvNameWidth = (int) ((width - 30 - button_count * 42) * dm.density);
+ habits = new HashMap<>();
+ positionToHabit = new HashMap<>();
+ checkmarks = new HashMap<>();
+ scores = new HashMap<>();
+
+ View view = inflater.inflate(R.layout.list_habits_fragment, container, false);
tvNameHeader = (TextView) view.findViewById(R.id.tvNameHeader);
+ progressBar = (ProgressBar) view.findViewById(R.id.progressBar);
+ progressBar.setVisibility(View.INVISIBLE);
+
adapter = new ListHabitsAdapter(getActivity());
listView = (DragSortListView) view.findViewById(R.id.listView);
listView.setAdapter(adapter);
@@ -107,7 +130,6 @@ public class ListHabitsFragment extends Fragment
llEmpty = view.findViewById(R.id.llEmpty);
updateEmptyMessage();
-
setHasOptionsMenu(true);
return view;
}
@@ -124,7 +146,14 @@ public class ListHabitsFragment extends Fragment
public void onResume()
{
super.onResume();
- updateHeader();
+ if(lastLoadedTimestamp == null || lastLoadedTimestamp != DateHelper.getStartOfToday())
+ {
+ updateHeader();
+ fetchAllHabits();
+ updateEmptyMessage();
+ }
+
+ adapter.notifyDataSetChanged();
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(activity);
short_toggle_enabled = prefs.getBoolean("pref_short_toggle", false);
@@ -156,6 +185,139 @@ public class ListHabitsFragment extends Fragment
}
}
+ private void fetchAllHabits()
+ {
+ if(currentFetchTask != null) currentFetchTask.cancel(true);
+
+ currentFetchTask = new AsyncTask()
+ {
+ HashMap newHabits = Habit.getAll();
+ HashMap newPositionToHabit = new HashMap<>();
+ HashMap newCheckmarks = new HashMap<>();
+ HashMap newScores = new HashMap<>();
+
+ @Override
+ protected Void doInBackground(Void... params)
+ {
+ long dateTo = DateHelper.getStartOfDay(DateHelper.getLocalTime());
+ long dateFrom = dateTo - (button_count - 1) * DateHelper.millisecondsInOneDay;
+ int[] empty = new int[button_count];
+
+ for(Habit h : newHabits.values())
+ {
+ newScores.put(h.getId(), 0);
+ newPositionToHabit.put(h.position, h);
+ newCheckmarks.put(h.getId(), empty);
+ }
+
+ int current = 0;
+ for(int i = 0; i < newHabits.size(); i++)
+ {
+ if(isCancelled()) return null;
+
+ Habit h = newPositionToHabit.get(i);
+ newScores.put(h.getId(), h.getScore());
+ newCheckmarks.put(h.getId(), h.getCheckmarks(dateFrom, dateTo));
+
+ publishProgress(current++, newHabits.size());
+ }
+
+ commit();
+
+ return null;
+ }
+
+ private void commit()
+ {
+ habits = newHabits;
+ positionToHabit = newPositionToHabit;
+ checkmarks = newCheckmarks;
+ scores = newScores;
+ }
+
+ @Override
+ protected void onPreExecute()
+ {
+ progressBar.setIndeterminate(false);
+ progressBar.setProgress(0);
+ progressBar.setVisibility(View.VISIBLE);
+ }
+
+ @Override
+ protected void onProgressUpdate(Integer... values)
+ {
+ progressBar.setMax(values[1]);
+ progressBar.setProgress(values[0]);
+
+ if(lastLoadedTimestamp == null)
+ {
+ commit();
+ adapter.notifyDataSetChanged();
+ }
+ }
+
+ @Override
+ protected void onPostExecute(Void aVoid)
+ {
+ if(isCancelled()) return;
+
+ adapter.notifyDataSetChanged();
+ updateEmptyMessage();
+
+ progressBar.setVisibility(View.INVISIBLE);
+ currentFetchTask = null;
+ lastLoadedTimestamp = DateHelper.getStartOfToday();
+ }
+
+ };
+
+ currentFetchTask.execute();
+ }
+
+ private void fetchHabit(final Long id)
+ {
+ new AsyncTask()
+ {
+ @Override
+ protected Void doInBackground(Void... params)
+ {
+ long dateTo = DateHelper.getStartOfDay(DateHelper.getLocalTime());
+ long dateFrom = dateTo - (button_count - 1) * DateHelper.millisecondsInOneDay;
+
+ Habit h = Habit.get(id);
+ habits.put(id, h);
+ scores.put(id, h.getScore());
+ checkmarks.put(id, h.getCheckmarks(dateFrom, dateTo));
+
+ return null;
+ }
+
+ @Override
+ protected void onPreExecute()
+ {
+ new Handler().postDelayed(new Runnable()
+ {
+ @Override
+ public void run()
+ {
+ if(getStatus() == Status.RUNNING)
+ {
+ progressBar.setIndeterminate(true);
+ progressBar.setVisibility(View.VISIBLE);
+ }
+ }
+ }, 500);
+ }
+
+ @Override
+ protected void onPostExecute(Void aVoid)
+ {
+ progressBar.setVisibility(View.GONE);
+ adapter.notifyDataSetChanged();
+ }
+ }.execute();
+ }
+
@Override
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater)
{
@@ -173,7 +335,7 @@ public class ListHabitsFragment extends Fragment
getActivity().getMenuInflater().inflate(R.menu.list_habits_context, menu);
AdapterContextMenuInfo info = (AdapterContextMenuInfo) menuInfo;
- final Habit habit = Habit.get(info.id);
+ final Habit habit = habits.get(info.id);
if(habit.isArchived())
menu.findItem(R.id.action_archive_habit).setVisible(false);
@@ -197,7 +359,7 @@ public class ListHabitsFragment extends Fragment
case R.id.action_show_archived:
{
Habit.setIncludeArchived(!Habit.isIncludeArchived());
- notifyDataSetChanged();
+ fetchAllHabits();
activity.invalidateOptionsMenu();
return true;
}
@@ -212,7 +374,7 @@ public class ListHabitsFragment extends Fragment
{
AdapterContextMenuInfo info = (AdapterContextMenuInfo) menuItem.getMenuInfo();
final int id = menuItem.getItemId();
- final Habit habit = Habit.get(info.id);
+ final Habit habit = habits.get(info.id);
if (id == R.id.action_edit_habit)
{
@@ -224,12 +386,12 @@ public class ListHabitsFragment extends Fragment
else if (id == R.id.action_archive_habit)
{
Command c = habit.new ArchiveCommand();
- executeCommand(c);
+ executeCommand(c, null);
}
else if (id == R.id.action_unarchive_habit)
{
Command c = habit.new UnarchiveCommand();
- executeCommand(c);
+ executeCommand(c, null);
}
return super.onContextItemSelected(menuItem);
@@ -240,26 +402,28 @@ public class ListHabitsFragment extends Fragment
{
if (new Date().getTime() - lastLongClick < 1000) return;
- Habit habit = Habit.getByPosition(position);
+ Habit habit = positionToHabit.get(position);
habitClickListener.onHabitClicked(habit);
}
@Override
- public void onSaved(Command command)
+ public void onSaved(Command command, Object savedObject)
{
- executeCommand(command);
- ReminderHelper.createReminderAlarms(activity);
- }
+ Habit h = (Habit) savedObject;
- public void notifyDataSetChanged()
- {
- updateEmptyMessage();
+ if(h == null) activity.executeCommand(command, null);
+ else activity.executeCommand(command, h.getId());
adapter.notifyDataSetChanged();
+
+ ReminderHelper.createReminderAlarms(activity);
}
private void updateEmptyMessage()
{
- llEmpty.setVisibility(Habit.getCount() > 0 ? View.GONE : View.VISIBLE);
+ if(lastLoadedTimestamp == null)
+ llEmpty.setVisibility(View.GONE);
+ else
+ llEmpty.setVisibility(habits.size() > 0 ? View.GONE : View.VISIBLE);
}
@Override
@@ -286,24 +450,35 @@ public class ListHabitsFragment extends Fragment
private void toggleCheck(View v)
{
- Habit habit = Habit.get((Long) v.getTag(R.string.habit_key));
+ Habit habit = habits.get((Long) v.getTag(R.string.habit_key));
+
int offset = (Integer) v.getTag(R.string.offset_key);
long timestamp = DateHelper.getStartOfDay(
DateHelper.getLocalTime() - offset * DateHelper.millisecondsInOneDay);
- executeCommand(habit.new ToggleRepetitionCommand(timestamp));
+ if(v.getTag(R.string.toggle_key).equals(2))
+ updateCheck(habit.color, (TextView) v, 0);
+ else
+ updateCheck(habit.color, (TextView) v, 2);
+
+ executeCommand(habit.new ToggleRepetitionCommand(timestamp), habit.getId());
}
- private void executeCommand(Command c)
+ private void executeCommand(Command c, Long refreshKey)
{
- activity.executeCommand(c);
+ activity.executeCommand(c, refreshKey);
}
@Override
public void drop(int from, int to)
{
+ Habit fromHabit = positionToHabit.get(from);
+ Habit toHabit = positionToHabit.get(to);
+ positionToHabit.put(to, fromHabit);
+ positionToHabit.put(from, toHabit);
+ adapter.notifyDataSetChanged();
+
Habit.reorder(from, to);
- notifyDataSetChanged();
}
@Override
@@ -337,13 +512,13 @@ public class ListHabitsFragment extends Fragment
@Override
public int getCount()
{
- return Habit.getCount();
+ return habits.size();
}
@Override
public Object getItem(int position)
{
- return Habit.getByPosition(position);
+ return positionToHabit.get(position);
}
@Override
@@ -355,7 +530,7 @@ public class ListHabitsFragment extends Fragment
@Override
public View getView(int position, View view, ViewGroup parent)
{
- final Habit habit = (Habit) getItem(position);
+ final Habit habit = positionToHabit.get(position);
if (view == null || (Long) view.getTag(R.id.KEY_TIMESTAMP) !=
DateHelper.getStartOfToday())
@@ -397,7 +572,6 @@ public class ListHabitsFragment extends Fragment
LinearLayout llInner = (LinearLayout) view.findViewById(R.id.llInner);
llInner.setTag(R.string.habit_key, habit.getId());
- int inactiveColor = Color.rgb(230, 230, 230);
int activeColor = habit.color;
tvName.setText(habit.name);
@@ -413,17 +587,17 @@ public class ListHabitsFragment extends Fragment
}
else
{
- int score = habit.getScore();
+ int score = scores.get(habit.getId());
if (score < Habit.HALF_STAR_CUTOFF)
{
tvStar.setText(context.getString(R.string.fa_star_o));
- tvStar.setTextColor(inactiveColor);
+ tvStar.setTextColor(INACTIVE_COLOR);
}
else if (score < Habit.FULL_STAR_CUTOFF)
{
tvStar.setText(context.getString(R.string.fa_star_half_o));
- tvStar.setTextColor(inactiveColor);
+ tvStar.setTextColor(INACTIVE_COLOR);
}
else
{
@@ -432,14 +606,10 @@ public class ListHabitsFragment extends Fragment
}
}
-
LinearLayout llButtons = (LinearLayout) view.findViewById(R.id.llButtons);
int m = llButtons.getChildCount();
- long dateTo = DateHelper.getStartOfDay(DateHelper.getLocalTime());
- long dateFrom = dateTo - m * DateHelper.millisecondsInOneDay;
-
- int isChecked[] = habit.getReps(dateFrom, dateTo);
+ int isChecked[] = checkmarks.get(habit.getId());
for (int i = 0; i < m; i++)
{
@@ -447,28 +617,40 @@ public class ListHabitsFragment extends Fragment
TextView tvCheck = (TextView) llButtons.getChildAt(i);
tvCheck.setTag(R.string.habit_key, habit.getId());
tvCheck.setTag(R.string.offset_key, i);
-
- switch (isChecked[i])
- {
- case 2:
- tvCheck.setText(R.string.fa_check);
- tvCheck.setTextColor(activeColor);
- break;
-
- case 1:
- tvCheck.setText(R.string.fa_check);
- tvCheck.setTextColor(inactiveColor);
- break;
-
- case 0:
- tvCheck.setText(R.string.fa_times);
- tvCheck.setTextColor(inactiveColor);
- break;
- }
+ updateCheck(activeColor, tvCheck, isChecked[i]);
}
return view;
}
+ }
+ private void updateCheck(int activeColor, TextView tvCheck, int check)
+ {
+ switch (check)
+ {
+ case 2:
+ tvCheck.setText(R.string.fa_check);
+ tvCheck.setTextColor(activeColor);
+ tvCheck.setTag(R.string.toggle_key, 2);
+ break;
+
+ case 1:
+ tvCheck.setText(R.string.fa_check);
+ tvCheck.setTextColor(INACTIVE_COLOR);
+ tvCheck.setTag(R.string.toggle_key, 1);
+ break;
+
+ case 0:
+ tvCheck.setText(R.string.fa_times);
+ tvCheck.setTextColor(INACTIVE_COLOR);
+ tvCheck.setTag(R.string.toggle_key, 0);
+ break;
+ }
+ }
+
+ public void onPostExecuteCommand(Long refreshKey)
+ {
+ if(refreshKey == null) fetchAllHabits();
+ else fetchHabit(refreshKey);
}
}
diff --git a/app/src/main/java/org/isoron/uhabits/dialogs/ShowHabitFragment.java b/app/src/main/java/org/isoron/uhabits/dialogs/ShowHabitFragment.java
index b0eeef791..7b37a5a3e 100644
--- a/app/src/main/java/org/isoron/uhabits/dialogs/ShowHabitFragment.java
+++ b/app/src/main/java/org/isoron/uhabits/dialogs/ShowHabitFragment.java
@@ -46,6 +46,8 @@ public class ShowHabitFragment extends Fragment implements DialogHelper.OnSavedL
activity = (ShowHabitActivity) getActivity();
habit = activity.habit;
+ habit.updateCheckmarks();
+
if (android.os.Build.VERSION.SDK_INT >= 21)
{
int darkerHabitColor = ColorHelper.mixColors(habit.color, Color.BLACK, 0.75f);
@@ -108,9 +110,13 @@ public class ShowHabitFragment extends Fragment implements DialogHelper.OnSavedL
}
@Override
- public void onSaved(Command command)
+ public void onSaved(Command command, Object savedObject)
{
- activity.executeCommand(command);
+ Habit h = (Habit) savedObject;
+
+ if(h == null) activity.executeCommand(command, null);
+ else activity.executeCommand(command, h.getId());
+
ReminderHelper.createReminderAlarms(activity);
activity.recreate();
}
diff --git a/app/src/main/java/org/isoron/uhabits/models/Checkmark.java b/app/src/main/java/org/isoron/uhabits/models/Checkmark.java
new file mode 100644
index 000000000..2d5ea0315
--- /dev/null
+++ b/app/src/main/java/org/isoron/uhabits/models/Checkmark.java
@@ -0,0 +1,29 @@
+package org.isoron.uhabits.models;
+
+import com.activeandroid.Model;
+import com.activeandroid.annotation.Column;
+import com.activeandroid.annotation.Table;
+
+@Table(name = "Checkmarks")
+public class Checkmark extends Model
+{
+
+ public static final int UNCHECKED = 0;
+ public static final int CHECKED_IMPLICITLY = 1;
+ public static final int CHECKED_EXPLICITLY = 2;
+
+ @Column(name = "habit")
+ public Habit habit;
+
+ @Column(name = "timestamp")
+ 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.
+ */
+ @Column(name = "value")
+ public Integer value;
+}
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 996245d79..284375e75 100644
--- a/app/src/main/java/org/isoron/uhabits/models/Habit.java
+++ b/app/src/main/java/org/isoron/uhabits/models/Habit.java
@@ -3,9 +3,8 @@ package org.isoron.uhabits.models;
import android.annotation.SuppressLint;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
-import android.os.AsyncTask;
-import android.util.Log;
+import com.activeandroid.ActiveAndroid;
import com.activeandroid.Cache;
import com.activeandroid.Model;
import com.activeandroid.annotation.Column;
@@ -21,6 +20,8 @@ import org.isoron.helpers.Command;
import org.isoron.helpers.DateHelper;
import org.isoron.uhabits.R;
+import java.util.ArrayList;
+import java.util.HashMap;
import java.util.List;
@Table(name = "Habits")
@@ -83,6 +84,19 @@ public class Habit extends Model
return Habit.load(Habit.class, id);
}
+ public static HashMap getAll()
+ {
+ List habits = select().execute();
+ HashMap map = new HashMap<>();
+
+ for(Habit h : habits)
+ {
+ map.put(h.getId(), h);
+ }
+
+ return map;
+ }
+
@SuppressLint("DefaultLocale")
public static void updateId(long oldId, long newId)
{
@@ -150,15 +164,25 @@ public class Habit extends Model
public static void rebuildOrder()
{
- Log.d("X", "rebuilding order");
-
List habits = select().execute();
- int i = 0;
- for (Habit h : habits)
+
+ ActiveAndroid.beginTransaction();
+ try
{
- h.position = i++;
- h.save();
+ int i = 0;
+ for (Habit h : habits)
+ {
+ h.position = i++;
+ h.save();
+ }
+
+ ActiveAndroid.setTransactionSuccessful();
}
+ finally
+ {
+ ActiveAndroid.endTransaction();
+ }
+
}
public static void roundTimestamps()
@@ -235,39 +259,127 @@ public class Habit extends Model
.and("timestamp = ?", timestamp).execute();
}
- public int[] getReps(long timeFrom, long timeTo)
+ 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();
+
+ 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());
+ }
+
+ return checks;
+ }
+
+ public void updateCheckmarks()
{
- long timeFromExtended = timeFrom - (long) (freq_den) * DateHelper.millisecondsInOneDay;
- List reps = selectRepsFromTo(timeFromExtended, timeTo).execute();
+ long beginning;
+ long today = DateHelper.getStartOfToday();
+ long day = DateHelper.millisecondsInOneDay;
- int nDaysExtended = (int) ((timeTo - timeFromExtended) / DateHelper.millisecondsInOneDay);
- int checkExtended[] = new int[nDaysExtended + 1];
+ Checkmark newestCheckmark = getNewestCheckmark();
+ if(newestCheckmark == null)
+ {
+ Repetition oldestRep = getOldestRep();
+ if (oldestRep == null) return;
- int nDays = (int) ((timeTo - timeFrom) / DateHelper.millisecondsInOneDay);
+ beginning = oldestRep.timestamp;
+ }
+ else
+ {
+ beginning = newestCheckmark.timestamp + day;
+ }
- // mark explicit checks
+ if(beginning > today)
+ return;
+
+ long beginningExtended = beginning - (long) (freq_den) * 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 - timeFrom) / DateHelper.millisecondsInOneDay);
- checkExtended[nDays - offset] = 2;
+ int offset = (int) ((rep.timestamp - beginningExtended) / day);
+ checks[nDaysExtended - offset - 1] = 2;
}
- // marks implicit checks
+ // implicit checks
for (int i = 0; i < nDays; i++)
{
int counter = 0;
for (int j = 0; j < freq_den; j++)
- if (checkExtended[i + j] == 2) counter++;
+ if (checks[i + j] == 2) counter++;
- if (counter >= freq_num) checkExtended[i] = Math.max(checkExtended[i], 1);
+ if (counter >= freq_num) checks[i] = Math.max(checks[i], 1);
}
- int check[] = new int[nDays + 1];
- for (int i = 0; i < nDays + 1; i++)
- check[i] = checkExtended[i];
+ 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();
+ }
+ }
- return check;
+ public Checkmark getNewestCheckmark()
+ {
+ return new Select().from(Checkmark.class)
+ .where("habit = ?", getId())
+ .orderBy("timestamp desc")
+ .limit(1)
+ .executeSingle();
}
public int getRepsCount(int days)
@@ -280,7 +392,7 @@ public class Habit extends Model
public boolean hasImplicitRepToday()
{
long today = DateHelper.getStartOfToday();
- int reps[] = getReps(today - DateHelper.millisecondsInOneDay, today);
+ int reps[] = getCheckmarks(today - DateHelper.millisecondsInOneDay, today);
return (reps[0] > 0);
}
@@ -289,12 +401,21 @@ public class Habit extends Model
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
+ }
+ else
{
Repetition rep = new Repetition();
rep.habit = this;
@@ -303,6 +424,8 @@ public class Habit extends Model
}
deleteScoresNewerThan(timestamp);
+ deleteCheckmarksNewerThan(timestamp);
+ deleteStreaksNewerThan(timestamp);
}
public void archive()
@@ -311,7 +434,7 @@ public class Habit extends Model
position = 9999;
save();
- if(!isIncludeArchived()) Habit.rebuildOrder();
+ Habit.rebuildOrder();
}
public void unarchive()
@@ -360,7 +483,8 @@ public class Habit extends Model
if (oldestRep == null) return 0;
beginningTime = oldestRep.timestamp;
beginningScore = 0;
- } else
+ }
+ else
{
beginningTime = newestScore.timestamp + day;
beginningScore = newestScore.score;
@@ -369,23 +493,34 @@ public class Habit extends Model
long nDays = (today - beginningTime) / day;
if (nDays < 0) return newestScore.score;
- int reps[] = getReps(beginningTime, today);
+ int reps[] = getCheckmarks(beginningTime, today);
+ ActiveAndroid.beginTransaction();
int lastScore = beginningScore;
- for (int i = 0; i < reps.length; i++)
+
+ try
{
- Score s = new Score();
- s.habit = this;
- s.timestamp = beginningTime + day * i;
- s.score = (int) (lastScore * multiplier);
- if (reps[reps.length - i - 1] == 2)
+ for (int i = 0; i < reps.length; i++)
{
- s.score += 1000000;
- s.score = Math.min(s.score, 19259500);
+ 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, 19259500);
+ }
+ s.save();
+
+ lastScore = s.score;
}
- s.save();
- lastScore = s.score;
+ ActiveAndroid.setTransactionSuccessful();
+ }
+ finally
+ {
+ ActiveAndroid.endTransaction();
}
return lastScore;
@@ -403,41 +538,89 @@ public class Habit extends Model
offset, divisor).execute();
}
- public long[] getStreaks()
+ public List getStreaks()
{
- String query =
- "create temporary table T as select distinct r1.habit as habit, r1.timestamp as time,\n" +
- " (select count(*) from repetitions r2 where r1.habit = r2.habit and\n" +
- " (r1.timestamp = r2.timestamp - 24*60*60*1000 or\n" +
- " r1.timestamp = r2.timestamp + 24*60*60*1000)) as neighbors\n" +
- "from repetitions r1 where r1.habit = ?";
+ updateStreaks();
- String query2 =
- "select time from T, (select 0 union select 1) where neighbors = 0 union all\n" +
- "select time from T where neighbors = 1 order by time;";
+ return new Select()
+ .from(Streak.class)
+ .where("habit = ?", getId())
+ .orderBy("end asc")
+ .execute();
+ }
- String args[] = {getId().toString()};
+ public Streak getNewestStreak()
+ {
+ return new Select()
+ .from(Streak.class)
+ .where("habit = ?", getId())
+ .orderBy("end desc")
+ .limit(1)
+ .executeSingle();
+ }
- SQLiteDatabase db = Cache.openDatabase();
- db.beginTransaction();
- db.execSQL(query, args);
- Cursor cursor = db.rawQuery(query2, null);
+ 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;
- long streaks[] = new long[cursor.getCount()];
- int current = 0;
+ int checks[] = getCheckmarks(beginning, today);
+ ArrayList list = new ArrayList<>();
- Log.d("Streaks", String.format("%d rows", cursor.getCount()));
+ long current = beginning;
+ list.add(current);
- if (cursor.moveToFirst())
+ for(int i = 1; i < checks.length; i++)
{
- do
- {
- streaks[current++] = cursor.getLong(0);
- } while (cursor.moveToNext());
+ 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);
}
- db.endTransaction();
- return streaks;
+ 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();
+ }
}
public static class CreateCommand extends Command
@@ -508,28 +691,9 @@ public class Habit extends Model
habit.save();
if (hasIntervalChanged)
{
- new AsyncTask()
- {
- @Override
- protected Integer doInBackground(Habit... habits)
- {
- // HACK: We wait one second before deleting old score, otherwise the view will
- // trigger the very slow getScore on the main thread at the same time, or even
- // before us.
- try
- {
- Thread.sleep(1000);
- } catch (InterruptedException e)
- {
- e.printStackTrace();
- }
-
- habits[0].deleteScoresNewerThan(0);
- habits[0].getScore();
-
- return 0;
- }
- }.execute(habit);
+ habit.deleteCheckmarksNewerThan(0);
+ habit.deleteStreaksNewerThan(0);
+ habit.deleteScoresNewerThan(0);
}
}
@@ -540,26 +704,9 @@ public class Habit extends Model
habit.save();
if (hasIntervalChanged)
{
- new AsyncTask()
- {
- @Override
- protected Integer doInBackground(Habit... habits)
- {
- try
- {
- Thread.sleep(1000);
- } catch (InterruptedException e)
- {
- e.printStackTrace();
- }
-
- habits[0].deleteScoresNewerThan(0);
- habits[0].getScore();
-
- return 0;
- }
- }.execute(habit);
-
+ habit.deleteCheckmarksNewerThan(0);
+ habit.deleteStreaksNewerThan(0);
+ habit.deleteScoresNewerThan(0);
}
}
diff --git a/app/src/main/java/org/isoron/uhabits/models/Streak.java b/app/src/main/java/org/isoron/uhabits/models/Streak.java
new file mode 100644
index 000000000..17b223c8d
--- /dev/null
+++ b/app/src/main/java/org/isoron/uhabits/models/Streak.java
@@ -0,0 +1,19 @@
+package org.isoron.uhabits.models;
+
+import com.activeandroid.Model;
+import com.activeandroid.annotation.Column;
+
+public class Streak extends Model
+{
+ @Column(name = "habit")
+ public Habit habit;
+
+ @Column(name = "start")
+ public Long start;
+
+ @Column(name = "end")
+ public Long end;
+
+ @Column(name = "length")
+ public Long length;
+}
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 68c9bd082..2a561c2b6 100644
--- a/app/src/main/java/org/isoron/uhabits/views/HabitHistoryView.java
+++ b/app/src/main/java/org/isoron/uhabits/views/HabitHistoryView.java
@@ -13,17 +13,19 @@ import android.view.View;
import org.isoron.helpers.ColorHelper;
import org.isoron.helpers.DateHelper;
import org.isoron.uhabits.R;
+import org.isoron.uhabits.models.Checkmark;
import org.isoron.uhabits.models.Habit;
import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.GregorianCalendar;
+import java.util.List;
public class HabitHistoryView extends View
{
private Habit habit;
- private int reps[];
+ private int[] checks;
private Context context;
private Paint pSquareBg, pSquareFg, pTextHeader;
@@ -93,7 +95,7 @@ public class HabitHistoryView extends View
for (int i = 0; i < nColumns * 7; i++)
dateFrom -= DateHelper.millisecondsInOneDay;
- reps = habit.getReps(dateFrom, dateTo);
+ checks = habit.getCheckmarks(dateFrom, dateTo);
}
@Override
@@ -159,7 +161,9 @@ public class HabitHistoryView extends View
{
if (!(i == nColumns - 1 && offsetWeeks == 0 && j > todayWeekday))
{
- pSquareBg.setColor(colors[reps[k]]);
+ if(k >= checks.length) pSquareBg.setColor(colors[0]);
+ else pSquareBg.setColor(colors[checks[k]]);
+
canvas.drawRect(square, pSquareBg);
canvas.drawText(Integer.toString(currentDate.get(Calendar.DAY_OF_MONTH)),
square.centerX(), square.centerY() + squareTextOffset, pSquareFg);
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 ec02a7a40..eaf8bbd42 100644
--- a/app/src/main/java/org/isoron/uhabits/views/HabitStreakView.java
+++ b/app/src/main/java/org/isoron/uhabits/views/HabitStreakView.java
@@ -12,10 +12,10 @@ import android.view.View;
import org.isoron.helpers.ColorHelper;
import org.isoron.helpers.DateHelper;
import org.isoron.uhabits.models.Habit;
+import org.isoron.uhabits.models.Streak;
import java.text.SimpleDateFormat;
-import java.util.Calendar;
-import java.util.GregorianCalendar;
+import java.util.List;
public class HabitStreakView extends View
{
@@ -23,10 +23,9 @@ public class HabitStreakView extends View
private int columnWidth, columnHeight, nColumns;
private Paint pText, pBar;
- private long streaks[];
+ private List streaks;
private int dataOffset;
- private long streakStart[], streakEnd[], streakLength[];
private long maxStreakLength;
private int barHeaderHeight;
@@ -68,17 +67,9 @@ public class HabitStreakView extends View
private void fetchStreaks()
{
streaks = habit.getStreaks();
- streakStart = new long[streaks.length / 2];
- streakEnd = new long[streaks.length / 2];
- streakLength = new long[streaks.length / 2];
- for(int i=0; i
-
+
diff --git a/app/src/main/res/layout/list_habits_fragment.xml b/app/src/main/res/layout/list_habits_fragment.xml
index f458d0c2b..e3970fc48 100644
--- a/app/src/main/res/layout/list_habits_fragment.xml
+++ b/app/src/main/res/layout/list_habits_fragment.xml
@@ -52,4 +52,12 @@
style="@style/habitsListButtonsPanelStyle"/>
+
+
\ No newline at end of file
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index d89e8b58f..3bc9ea90e 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -41,6 +41,7 @@
sans-serif
+
diff --git a/app/src/main/res/xml/preferences.xml b/app/src/main/res/xml/preferences.xml
index 625b63db6..eb3cabc62 100644
--- a/app/src/main/res/xml/preferences.xml
+++ b/app/src/main/res/xml/preferences.xml
@@ -43,14 +43,9 @@
android:action="android.intent.action.VIEW"
android:data="https://github.com/iSoron/uhabits"/>
-
-
-
-
+