mirror of
https://github.com/iSoron/uhabits.git
synced 2025-12-06 09:08:52 -06:00
Load data asynchronously; cache checkmarks and streaks
This commit is contained in:
@@ -21,7 +21,7 @@
|
|||||||
|
|
||||||
<meta-data
|
<meta-data
|
||||||
android:name="AA_DB_VERSION"
|
android:name="AA_DB_VERSION"
|
||||||
android:value="7"/>
|
android:value="9"/>
|
||||||
|
|
||||||
<meta-data
|
<meta-data
|
||||||
android:name="com.google.android.backup.api_key"
|
android:name="com.google.android.backup.api_key"
|
||||||
|
|||||||
@@ -29,7 +29,7 @@ public abstract class DialogHelper
|
|||||||
|
|
||||||
public static interface OnSavedListener
|
public static interface OnSavedListener
|
||||||
{
|
{
|
||||||
public void onSaved(Command command);
|
public void onSaved(Command command, Object savedObject);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void showSoftKeyboard(View view)
|
public static void showSoftKeyboard(View view)
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ package org.isoron.helpers;
|
|||||||
|
|
||||||
import android.app.Activity;
|
import android.app.Activity;
|
||||||
import android.app.backup.BackupManager;
|
import android.app.backup.BackupManager;
|
||||||
|
import android.os.AsyncTask;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.widget.Toast;
|
import android.widget.Toast;
|
||||||
|
|
||||||
@@ -26,10 +27,9 @@ abstract public class ReplayableActivity extends Activity
|
|||||||
redoList = new LinkedList<>();
|
redoList = new LinkedList<>();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void executeCommand(Command command)
|
public void executeCommand(Command command, Long refreshKey)
|
||||||
{
|
{
|
||||||
executeCommand(command, false);
|
executeCommand(command, false, refreshKey);
|
||||||
BackupManager.dataChanged("org.isoron.uhabits");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void undo()
|
protected void undo()
|
||||||
@@ -54,7 +54,7 @@ abstract public class ReplayableActivity extends Activity
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
Command last = redoList.pop();
|
Command last = redoList.pop();
|
||||||
executeCommand(last, false);
|
executeCommand(last, false, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void showToast(Integer stringId)
|
public void showToast(Integer stringId)
|
||||||
@@ -65,14 +65,36 @@ abstract public class ReplayableActivity extends Activity
|
|||||||
toast.show();
|
toast.show();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void executeCommand(final Command command, Boolean clearRedoStack,
|
||||||
public void executeCommand(Command command, boolean clearRedoStack)
|
final Long refreshKey)
|
||||||
{
|
{
|
||||||
undoList.push(command);
|
undoList.push(command);
|
||||||
|
|
||||||
if (undoList.size() > MAX_UNDO_LEVEL) undoList.removeLast();
|
if (undoList.size() > MAX_UNDO_LEVEL) undoList.removeLast();
|
||||||
if (clearRedoStack) redoList.clear();
|
if (clearRedoStack) redoList.clear();
|
||||||
|
|
||||||
command.execute();
|
new AsyncTask<Void, Void, Void>()
|
||||||
|
{
|
||||||
|
@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());
|
showToast(command.getExecuteStringId());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void onPostExecuteCommand(Long refreshKey)
|
||||||
|
{
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import android.content.SharedPreferences;
|
|||||||
import android.net.Uri;
|
import android.net.Uri;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.preference.PreferenceManager;
|
import android.preference.PreferenceManager;
|
||||||
|
import android.util.Log;
|
||||||
import android.view.Menu;
|
import android.view.Menu;
|
||||||
import android.view.MenuItem;
|
import android.view.MenuItem;
|
||||||
|
|
||||||
@@ -33,6 +34,7 @@ public class MainActivity extends ReplayableActivity
|
|||||||
ReminderHelper.createReminderAlarms(MainActivity.this);
|
ReminderHelper.createReminderAlarms(MainActivity.this);
|
||||||
|
|
||||||
showTutorial();
|
showTutorial();
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void showTutorial()
|
private void showTutorial()
|
||||||
@@ -51,13 +53,6 @@ public class MainActivity extends ReplayableActivity
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void onStart()
|
|
||||||
{
|
|
||||||
super.onStart();
|
|
||||||
listHabitsFragment.notifyDataSetChanged();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean onCreateOptionsMenu(Menu menu)
|
public boolean onCreateOptionsMenu(Menu menu)
|
||||||
{
|
{
|
||||||
@@ -89,9 +84,8 @@ public class MainActivity extends ReplayableActivity
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void executeCommand(Command command)
|
public void onPostExecuteCommand(Long refreshKey)
|
||||||
{
|
{
|
||||||
super.executeCommand(command);
|
listHabitsFragment.onPostExecuteCommand(refreshKey);
|
||||||
listHabitsFragment.notifyDataSetChanged();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -263,14 +263,19 @@ public class EditHabitFragment extends DialogFragment implements OnClickListener
|
|||||||
editor.putInt("pref_default_habit_freq_den", modified_habit.freq_den);
|
editor.putInt("pref_default_habit_freq_den", modified_habit.freq_den);
|
||||||
editor.apply();
|
editor.apply();
|
||||||
|
|
||||||
|
Habit savedHabit = null;
|
||||||
|
|
||||||
if(mode == EDIT_MODE)
|
if(mode == EDIT_MODE)
|
||||||
|
{
|
||||||
command = originalHabit.new EditCommand(modified_habit);
|
command = originalHabit.new EditCommand(modified_habit);
|
||||||
|
savedHabit = originalHabit;
|
||||||
|
}
|
||||||
|
|
||||||
if(mode == CREATE_MODE)
|
if(mode == CREATE_MODE)
|
||||||
command = new Habit.CreateCommand(modified_habit);
|
command = new Habit.CreateCommand(modified_habit);
|
||||||
|
|
||||||
if(onSavedListener != null)
|
if(onSavedListener != null)
|
||||||
onSavedListener.onSaved(command);
|
onSavedListener.onSaved(command, savedHabit);
|
||||||
|
|
||||||
dismiss();
|
dismiss();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,7 +7,9 @@ import android.content.SharedPreferences;
|
|||||||
import android.graphics.Color;
|
import android.graphics.Color;
|
||||||
import android.graphics.Point;
|
import android.graphics.Point;
|
||||||
import android.graphics.Typeface;
|
import android.graphics.Typeface;
|
||||||
|
import android.os.AsyncTask;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
|
import android.os.Handler;
|
||||||
import android.os.Vibrator;
|
import android.os.Vibrator;
|
||||||
import android.preference.PreferenceManager;
|
import android.preference.PreferenceManager;
|
||||||
import android.util.DisplayMetrics;
|
import android.util.DisplayMetrics;
|
||||||
@@ -30,6 +32,7 @@ import android.widget.BaseAdapter;
|
|||||||
import android.widget.Button;
|
import android.widget.Button;
|
||||||
import android.widget.LinearLayout;
|
import android.widget.LinearLayout;
|
||||||
import android.widget.LinearLayout.LayoutParams;
|
import android.widget.LinearLayout.LayoutParams;
|
||||||
|
import android.widget.ProgressBar;
|
||||||
import android.widget.TextView;
|
import android.widget.TextView;
|
||||||
|
|
||||||
import com.mobeta.android.dslv.DragSortController;
|
import com.mobeta.android.dslv.DragSortController;
|
||||||
@@ -47,6 +50,7 @@ import org.isoron.uhabits.models.Habit;
|
|||||||
|
|
||||||
import java.util.Date;
|
import java.util.Date;
|
||||||
import java.util.GregorianCalendar;
|
import java.util.GregorianCalendar;
|
||||||
|
import java.util.HashMap;
|
||||||
import java.util.Locale;
|
import java.util.Locale;
|
||||||
import java.util.TimeZone;
|
import java.util.TimeZone;
|
||||||
|
|
||||||
@@ -54,6 +58,9 @@ public class ListHabitsFragment extends Fragment
|
|||||||
implements OnSavedListener, OnItemClickListener, OnLongClickListener, DropListener,
|
implements OnSavedListener, OnItemClickListener, OnLongClickListener, DropListener,
|
||||||
OnClickListener
|
OnClickListener
|
||||||
{
|
{
|
||||||
|
|
||||||
|
public static final int INACTIVE_COLOR = Color.rgb(230, 230, 230);
|
||||||
|
|
||||||
public interface OnHabitClickListener
|
public interface OnHabitClickListener
|
||||||
{
|
{
|
||||||
void onHabitClicked(Habit habit);
|
void onHabitClicked(Habit habit);
|
||||||
@@ -67,23 +74,39 @@ public class ListHabitsFragment extends Fragment
|
|||||||
private int tvNameWidth;
|
private int tvNameWidth;
|
||||||
private int button_count;
|
private int button_count;
|
||||||
private View llEmpty;
|
private View llEmpty;
|
||||||
|
private ProgressBar progressBar;
|
||||||
|
|
||||||
private OnHabitClickListener habitClickListener;
|
private OnHabitClickListener habitClickListener;
|
||||||
private boolean short_toggle_enabled;
|
private boolean short_toggle_enabled;
|
||||||
|
|
||||||
|
private HashMap<Long, Habit> habits;
|
||||||
|
private HashMap<Integer, Habit> positionToHabit;
|
||||||
|
private HashMap<Long, int[]> checkmarks;
|
||||||
|
private HashMap<Long, Integer> scores;
|
||||||
|
|
||||||
|
private Long lastLoadedTimestamp = null;
|
||||||
|
private AsyncTask<Void, Integer, Void> currentFetchTask = null;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public View onCreateView(LayoutInflater inflater, ViewGroup container,
|
public View onCreateView(LayoutInflater inflater, ViewGroup container,
|
||||||
Bundle savedInstanceState)
|
Bundle savedInstanceState)
|
||||||
{
|
{
|
||||||
View view = inflater.inflate(R.layout.list_habits_fragment, container, false);
|
|
||||||
|
|
||||||
DisplayMetrics dm = getResources().getDisplayMetrics();
|
DisplayMetrics dm = getResources().getDisplayMetrics();
|
||||||
int width = (int) (dm.widthPixels / dm.density);
|
int width = (int) (dm.widthPixels / dm.density);
|
||||||
button_count = (int) ((width - 160) / 42);
|
button_count = (int) ((width - 160) / 42);
|
||||||
tvNameWidth = (int) ((width - 30 - button_count * 42) * dm.density);
|
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);
|
tvNameHeader = (TextView) view.findViewById(R.id.tvNameHeader);
|
||||||
|
|
||||||
|
progressBar = (ProgressBar) view.findViewById(R.id.progressBar);
|
||||||
|
progressBar.setVisibility(View.INVISIBLE);
|
||||||
|
|
||||||
adapter = new ListHabitsAdapter(getActivity());
|
adapter = new ListHabitsAdapter(getActivity());
|
||||||
listView = (DragSortListView) view.findViewById(R.id.listView);
|
listView = (DragSortListView) view.findViewById(R.id.listView);
|
||||||
listView.setAdapter(adapter);
|
listView.setAdapter(adapter);
|
||||||
@@ -107,7 +130,6 @@ public class ListHabitsFragment extends Fragment
|
|||||||
llEmpty = view.findViewById(R.id.llEmpty);
|
llEmpty = view.findViewById(R.id.llEmpty);
|
||||||
|
|
||||||
updateEmptyMessage();
|
updateEmptyMessage();
|
||||||
|
|
||||||
setHasOptionsMenu(true);
|
setHasOptionsMenu(true);
|
||||||
return view;
|
return view;
|
||||||
}
|
}
|
||||||
@@ -124,7 +146,14 @@ public class ListHabitsFragment extends Fragment
|
|||||||
public void onResume()
|
public void onResume()
|
||||||
{
|
{
|
||||||
super.onResume();
|
super.onResume();
|
||||||
updateHeader();
|
if(lastLoadedTimestamp == null || lastLoadedTimestamp != DateHelper.getStartOfToday())
|
||||||
|
{
|
||||||
|
updateHeader();
|
||||||
|
fetchAllHabits();
|
||||||
|
updateEmptyMessage();
|
||||||
|
}
|
||||||
|
|
||||||
|
adapter.notifyDataSetChanged();
|
||||||
|
|
||||||
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(activity);
|
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(activity);
|
||||||
short_toggle_enabled = prefs.getBoolean("pref_short_toggle", false);
|
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<Void, Integer, Void>()
|
||||||
|
{
|
||||||
|
HashMap<Long, Habit> newHabits = Habit.getAll();
|
||||||
|
HashMap<Integer, Habit> newPositionToHabit = new HashMap<>();
|
||||||
|
HashMap<Long, int[]> newCheckmarks = new HashMap<>();
|
||||||
|
HashMap<Long, Integer> 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<Void, Void, Void>()
|
||||||
|
{
|
||||||
|
@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
|
@Override
|
||||||
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater)
|
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);
|
getActivity().getMenuInflater().inflate(R.menu.list_habits_context, menu);
|
||||||
|
|
||||||
AdapterContextMenuInfo info = (AdapterContextMenuInfo) menuInfo;
|
AdapterContextMenuInfo info = (AdapterContextMenuInfo) menuInfo;
|
||||||
final Habit habit = Habit.get(info.id);
|
final Habit habit = habits.get(info.id);
|
||||||
|
|
||||||
if(habit.isArchived())
|
if(habit.isArchived())
|
||||||
menu.findItem(R.id.action_archive_habit).setVisible(false);
|
menu.findItem(R.id.action_archive_habit).setVisible(false);
|
||||||
@@ -197,7 +359,7 @@ public class ListHabitsFragment extends Fragment
|
|||||||
case R.id.action_show_archived:
|
case R.id.action_show_archived:
|
||||||
{
|
{
|
||||||
Habit.setIncludeArchived(!Habit.isIncludeArchived());
|
Habit.setIncludeArchived(!Habit.isIncludeArchived());
|
||||||
notifyDataSetChanged();
|
fetchAllHabits();
|
||||||
activity.invalidateOptionsMenu();
|
activity.invalidateOptionsMenu();
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@@ -212,7 +374,7 @@ public class ListHabitsFragment extends Fragment
|
|||||||
{
|
{
|
||||||
AdapterContextMenuInfo info = (AdapterContextMenuInfo) menuItem.getMenuInfo();
|
AdapterContextMenuInfo info = (AdapterContextMenuInfo) menuItem.getMenuInfo();
|
||||||
final int id = menuItem.getItemId();
|
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)
|
if (id == R.id.action_edit_habit)
|
||||||
{
|
{
|
||||||
@@ -224,12 +386,12 @@ public class ListHabitsFragment extends Fragment
|
|||||||
else if (id == R.id.action_archive_habit)
|
else if (id == R.id.action_archive_habit)
|
||||||
{
|
{
|
||||||
Command c = habit.new ArchiveCommand();
|
Command c = habit.new ArchiveCommand();
|
||||||
executeCommand(c);
|
executeCommand(c, null);
|
||||||
}
|
}
|
||||||
else if (id == R.id.action_unarchive_habit)
|
else if (id == R.id.action_unarchive_habit)
|
||||||
{
|
{
|
||||||
Command c = habit.new UnarchiveCommand();
|
Command c = habit.new UnarchiveCommand();
|
||||||
executeCommand(c);
|
executeCommand(c, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
return super.onContextItemSelected(menuItem);
|
return super.onContextItemSelected(menuItem);
|
||||||
@@ -240,26 +402,28 @@ public class ListHabitsFragment extends Fragment
|
|||||||
{
|
{
|
||||||
if (new Date().getTime() - lastLongClick < 1000) return;
|
if (new Date().getTime() - lastLongClick < 1000) return;
|
||||||
|
|
||||||
Habit habit = Habit.getByPosition(position);
|
Habit habit = positionToHabit.get(position);
|
||||||
habitClickListener.onHabitClicked(habit);
|
habitClickListener.onHabitClicked(habit);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onSaved(Command command)
|
public void onSaved(Command command, Object savedObject)
|
||||||
{
|
{
|
||||||
executeCommand(command);
|
Habit h = (Habit) savedObject;
|
||||||
ReminderHelper.createReminderAlarms(activity);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void notifyDataSetChanged()
|
if(h == null) activity.executeCommand(command, null);
|
||||||
{
|
else activity.executeCommand(command, h.getId());
|
||||||
updateEmptyMessage();
|
|
||||||
adapter.notifyDataSetChanged();
|
adapter.notifyDataSetChanged();
|
||||||
|
|
||||||
|
ReminderHelper.createReminderAlarms(activity);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void updateEmptyMessage()
|
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
|
@Override
|
||||||
@@ -286,24 +450,35 @@ public class ListHabitsFragment extends Fragment
|
|||||||
|
|
||||||
private void toggleCheck(View v)
|
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);
|
int offset = (Integer) v.getTag(R.string.offset_key);
|
||||||
long timestamp = DateHelper.getStartOfDay(
|
long timestamp = DateHelper.getStartOfDay(
|
||||||
DateHelper.getLocalTime() - offset * DateHelper.millisecondsInOneDay);
|
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
|
@Override
|
||||||
public void drop(int from, int to)
|
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);
|
Habit.reorder(from, to);
|
||||||
notifyDataSetChanged();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -337,13 +512,13 @@ public class ListHabitsFragment extends Fragment
|
|||||||
@Override
|
@Override
|
||||||
public int getCount()
|
public int getCount()
|
||||||
{
|
{
|
||||||
return Habit.getCount();
|
return habits.size();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Object getItem(int position)
|
public Object getItem(int position)
|
||||||
{
|
{
|
||||||
return Habit.getByPosition(position);
|
return positionToHabit.get(position);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -355,7 +530,7 @@ public class ListHabitsFragment extends Fragment
|
|||||||
@Override
|
@Override
|
||||||
public View getView(int position, View view, ViewGroup parent)
|
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) !=
|
if (view == null || (Long) view.getTag(R.id.KEY_TIMESTAMP) !=
|
||||||
DateHelper.getStartOfToday())
|
DateHelper.getStartOfToday())
|
||||||
@@ -397,7 +572,6 @@ public class ListHabitsFragment extends Fragment
|
|||||||
LinearLayout llInner = (LinearLayout) view.findViewById(R.id.llInner);
|
LinearLayout llInner = (LinearLayout) view.findViewById(R.id.llInner);
|
||||||
llInner.setTag(R.string.habit_key, habit.getId());
|
llInner.setTag(R.string.habit_key, habit.getId());
|
||||||
|
|
||||||
int inactiveColor = Color.rgb(230, 230, 230);
|
|
||||||
int activeColor = habit.color;
|
int activeColor = habit.color;
|
||||||
|
|
||||||
tvName.setText(habit.name);
|
tvName.setText(habit.name);
|
||||||
@@ -413,17 +587,17 @@ public class ListHabitsFragment extends Fragment
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
int score = habit.getScore();
|
int score = scores.get(habit.getId());
|
||||||
|
|
||||||
if (score < Habit.HALF_STAR_CUTOFF)
|
if (score < Habit.HALF_STAR_CUTOFF)
|
||||||
{
|
{
|
||||||
tvStar.setText(context.getString(R.string.fa_star_o));
|
tvStar.setText(context.getString(R.string.fa_star_o));
|
||||||
tvStar.setTextColor(inactiveColor);
|
tvStar.setTextColor(INACTIVE_COLOR);
|
||||||
}
|
}
|
||||||
else if (score < Habit.FULL_STAR_CUTOFF)
|
else if (score < Habit.FULL_STAR_CUTOFF)
|
||||||
{
|
{
|
||||||
tvStar.setText(context.getString(R.string.fa_star_half_o));
|
tvStar.setText(context.getString(R.string.fa_star_half_o));
|
||||||
tvStar.setTextColor(inactiveColor);
|
tvStar.setTextColor(INACTIVE_COLOR);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@@ -432,14 +606,10 @@ public class ListHabitsFragment extends Fragment
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
LinearLayout llButtons = (LinearLayout) view.findViewById(R.id.llButtons);
|
LinearLayout llButtons = (LinearLayout) view.findViewById(R.id.llButtons);
|
||||||
int m = llButtons.getChildCount();
|
int m = llButtons.getChildCount();
|
||||||
|
|
||||||
long dateTo = DateHelper.getStartOfDay(DateHelper.getLocalTime());
|
int isChecked[] = checkmarks.get(habit.getId());
|
||||||
long dateFrom = dateTo - m * DateHelper.millisecondsInOneDay;
|
|
||||||
|
|
||||||
int isChecked[] = habit.getReps(dateFrom, dateTo);
|
|
||||||
|
|
||||||
for (int i = 0; i < m; i++)
|
for (int i = 0; i < m; i++)
|
||||||
{
|
{
|
||||||
@@ -447,28 +617,40 @@ public class ListHabitsFragment extends Fragment
|
|||||||
TextView tvCheck = (TextView) llButtons.getChildAt(i);
|
TextView tvCheck = (TextView) llButtons.getChildAt(i);
|
||||||
tvCheck.setTag(R.string.habit_key, habit.getId());
|
tvCheck.setTag(R.string.habit_key, habit.getId());
|
||||||
tvCheck.setTag(R.string.offset_key, i);
|
tvCheck.setTag(R.string.offset_key, i);
|
||||||
|
updateCheck(activeColor, tvCheck, isChecked[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;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return view;
|
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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -46,6 +46,8 @@ public class ShowHabitFragment extends Fragment implements DialogHelper.OnSavedL
|
|||||||
activity = (ShowHabitActivity) getActivity();
|
activity = (ShowHabitActivity) getActivity();
|
||||||
habit = activity.habit;
|
habit = activity.habit;
|
||||||
|
|
||||||
|
habit.updateCheckmarks();
|
||||||
|
|
||||||
if (android.os.Build.VERSION.SDK_INT >= 21)
|
if (android.os.Build.VERSION.SDK_INT >= 21)
|
||||||
{
|
{
|
||||||
int darkerHabitColor = ColorHelper.mixColors(habit.color, Color.BLACK, 0.75f);
|
int darkerHabitColor = ColorHelper.mixColors(habit.color, Color.BLACK, 0.75f);
|
||||||
@@ -108,9 +110,13 @@ public class ShowHabitFragment extends Fragment implements DialogHelper.OnSavedL
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@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);
|
ReminderHelper.createReminderAlarms(activity);
|
||||||
activity.recreate();
|
activity.recreate();
|
||||||
}
|
}
|
||||||
|
|||||||
29
app/src/main/java/org/isoron/uhabits/models/Checkmark.java
Normal file
29
app/src/main/java/org/isoron/uhabits/models/Checkmark.java
Normal file
@@ -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;
|
||||||
|
}
|
||||||
@@ -3,9 +3,8 @@ package org.isoron.uhabits.models;
|
|||||||
import android.annotation.SuppressLint;
|
import android.annotation.SuppressLint;
|
||||||
import android.database.Cursor;
|
import android.database.Cursor;
|
||||||
import android.database.sqlite.SQLiteDatabase;
|
import android.database.sqlite.SQLiteDatabase;
|
||||||
import android.os.AsyncTask;
|
|
||||||
import android.util.Log;
|
|
||||||
|
|
||||||
|
import com.activeandroid.ActiveAndroid;
|
||||||
import com.activeandroid.Cache;
|
import com.activeandroid.Cache;
|
||||||
import com.activeandroid.Model;
|
import com.activeandroid.Model;
|
||||||
import com.activeandroid.annotation.Column;
|
import com.activeandroid.annotation.Column;
|
||||||
@@ -21,6 +20,8 @@ import org.isoron.helpers.Command;
|
|||||||
import org.isoron.helpers.DateHelper;
|
import org.isoron.helpers.DateHelper;
|
||||||
import org.isoron.uhabits.R;
|
import org.isoron.uhabits.R;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.HashMap;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
@Table(name = "Habits")
|
@Table(name = "Habits")
|
||||||
@@ -83,6 +84,19 @@ public class Habit extends Model
|
|||||||
return Habit.load(Habit.class, id);
|
return Habit.load(Habit.class, id);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static HashMap<Long, Habit> getAll()
|
||||||
|
{
|
||||||
|
List<Habit> habits = select().execute();
|
||||||
|
HashMap<Long, Habit> map = new HashMap<>();
|
||||||
|
|
||||||
|
for(Habit h : habits)
|
||||||
|
{
|
||||||
|
map.put(h.getId(), h);
|
||||||
|
}
|
||||||
|
|
||||||
|
return map;
|
||||||
|
}
|
||||||
|
|
||||||
@SuppressLint("DefaultLocale")
|
@SuppressLint("DefaultLocale")
|
||||||
public static void updateId(long oldId, long newId)
|
public static void updateId(long oldId, long newId)
|
||||||
{
|
{
|
||||||
@@ -150,15 +164,25 @@ public class Habit extends Model
|
|||||||
|
|
||||||
public static void rebuildOrder()
|
public static void rebuildOrder()
|
||||||
{
|
{
|
||||||
Log.d("X", "rebuilding order");
|
|
||||||
|
|
||||||
List<Habit> habits = select().execute();
|
List<Habit> habits = select().execute();
|
||||||
int i = 0;
|
|
||||||
for (Habit h : habits)
|
ActiveAndroid.beginTransaction();
|
||||||
|
try
|
||||||
{
|
{
|
||||||
h.position = i++;
|
int i = 0;
|
||||||
h.save();
|
for (Habit h : habits)
|
||||||
|
{
|
||||||
|
h.position = i++;
|
||||||
|
h.save();
|
||||||
|
}
|
||||||
|
|
||||||
|
ActiveAndroid.setTransactionSuccessful();
|
||||||
}
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
ActiveAndroid.endTransaction();
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void roundTimestamps()
|
public static void roundTimestamps()
|
||||||
@@ -235,39 +259,127 @@ public class Habit extends Model
|
|||||||
.and("timestamp = ?", timestamp).execute();
|
.and("timestamp = ?", timestamp).execute();
|
||||||
}
|
}
|
||||||
|
|
||||||
public int[] getReps(long timeFrom, long timeTo)
|
public void deleteCheckmarksNewerThan(long timestamp)
|
||||||
{
|
{
|
||||||
long timeFromExtended = timeFrom - (long) (freq_den) * DateHelper.millisecondsInOneDay;
|
new Delete().from(Checkmark.class)
|
||||||
List<Repetition> reps = selectRepsFromTo(timeFromExtended, timeTo).execute();
|
.where("habit = ?", getId())
|
||||||
|
.and("timestamp >= ?", timestamp)
|
||||||
|
.execute();
|
||||||
|
}
|
||||||
|
|
||||||
int nDaysExtended = (int) ((timeTo - timeFromExtended) / DateHelper.millisecondsInOneDay);
|
public void deleteStreaksNewerThan(long timestamp)
|
||||||
int checkExtended[] = new int[nDaysExtended + 1];
|
{
|
||||||
|
new Delete().from(Streak.class)
|
||||||
|
.where("habit = ?", getId())
|
||||||
|
.and("end >= ?", timestamp - DateHelper.millisecondsInOneDay)
|
||||||
|
.execute();
|
||||||
|
}
|
||||||
|
|
||||||
int nDays = (int) ((timeTo - timeFrom) / DateHelper.millisecondsInOneDay);
|
public int[] getCheckmarks(Long fromTimestamp, Long toTimestamp)
|
||||||
|
{
|
||||||
|
updateCheckmarks();
|
||||||
|
|
||||||
// mark explicit checks
|
String query = "select value, timestamp from Checkmarks where " +
|
||||||
for (Repetition rep : reps)
|
"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())
|
||||||
{
|
{
|
||||||
int offset = (int) ((rep.timestamp - timeFrom) / DateHelper.millisecondsInOneDay);
|
do
|
||||||
checkExtended[nDays - offset] = 2;
|
{
|
||||||
|
long timestamp = cursor.getLong(1);
|
||||||
|
int offset = (int) ((timestamp - fromTimestamp) / day);
|
||||||
|
checks[nDays - offset - 1] = cursor.getInt(0);
|
||||||
|
|
||||||
|
} while (cursor.moveToNext());
|
||||||
}
|
}
|
||||||
|
|
||||||
// marks implicit checks
|
return checks;
|
||||||
|
}
|
||||||
|
|
||||||
|
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) (freq_den) * day;
|
||||||
|
List<Repetition> 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++)
|
for (int i = 0; i < nDays; i++)
|
||||||
{
|
{
|
||||||
int counter = 0;
|
int counter = 0;
|
||||||
|
|
||||||
for (int j = 0; j < freq_den; j++)
|
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];
|
ActiveAndroid.beginTransaction();
|
||||||
for (int i = 0; i < nDays + 1; i++)
|
|
||||||
check[i] = checkExtended[i];
|
|
||||||
|
|
||||||
return check;
|
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 getRepsCount(int days)
|
public int getRepsCount(int days)
|
||||||
@@ -280,7 +392,7 @@ public class Habit extends Model
|
|||||||
public boolean hasImplicitRepToday()
|
public boolean hasImplicitRepToday()
|
||||||
{
|
{
|
||||||
long today = DateHelper.getStartOfToday();
|
long today = DateHelper.getStartOfToday();
|
||||||
int reps[] = getReps(today - DateHelper.millisecondsInOneDay, today);
|
int reps[] = getCheckmarks(today - DateHelper.millisecondsInOneDay, today);
|
||||||
return (reps[0] > 0);
|
return (reps[0] > 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -289,12 +401,21 @@ public class Habit extends Model
|
|||||||
return (Repetition) selectReps().limit(1).executeSingle();
|
return (Repetition) selectReps().limit(1).executeSingle();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Repetition getOldestRepNewerThan(long timestamp)
|
||||||
|
{
|
||||||
|
return selectReps()
|
||||||
|
.where("timestamp > ?", timestamp)
|
||||||
|
.limit(1)
|
||||||
|
.executeSingle();
|
||||||
|
}
|
||||||
|
|
||||||
public void toggleRepetition(long timestamp)
|
public void toggleRepetition(long timestamp)
|
||||||
{
|
{
|
||||||
if (hasRep(timestamp))
|
if (hasRep(timestamp))
|
||||||
{
|
{
|
||||||
deleteReps(timestamp);
|
deleteReps(timestamp);
|
||||||
} else
|
}
|
||||||
|
else
|
||||||
{
|
{
|
||||||
Repetition rep = new Repetition();
|
Repetition rep = new Repetition();
|
||||||
rep.habit = this;
|
rep.habit = this;
|
||||||
@@ -303,6 +424,8 @@ public class Habit extends Model
|
|||||||
}
|
}
|
||||||
|
|
||||||
deleteScoresNewerThan(timestamp);
|
deleteScoresNewerThan(timestamp);
|
||||||
|
deleteCheckmarksNewerThan(timestamp);
|
||||||
|
deleteStreaksNewerThan(timestamp);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void archive()
|
public void archive()
|
||||||
@@ -311,7 +434,7 @@ public class Habit extends Model
|
|||||||
position = 9999;
|
position = 9999;
|
||||||
save();
|
save();
|
||||||
|
|
||||||
if(!isIncludeArchived()) Habit.rebuildOrder();
|
Habit.rebuildOrder();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void unarchive()
|
public void unarchive()
|
||||||
@@ -360,7 +483,8 @@ public class Habit extends Model
|
|||||||
if (oldestRep == null) return 0;
|
if (oldestRep == null) return 0;
|
||||||
beginningTime = oldestRep.timestamp;
|
beginningTime = oldestRep.timestamp;
|
||||||
beginningScore = 0;
|
beginningScore = 0;
|
||||||
} else
|
}
|
||||||
|
else
|
||||||
{
|
{
|
||||||
beginningTime = newestScore.timestamp + day;
|
beginningTime = newestScore.timestamp + day;
|
||||||
beginningScore = newestScore.score;
|
beginningScore = newestScore.score;
|
||||||
@@ -369,23 +493,34 @@ public class Habit extends Model
|
|||||||
long nDays = (today - beginningTime) / day;
|
long nDays = (today - beginningTime) / day;
|
||||||
if (nDays < 0) return newestScore.score;
|
if (nDays < 0) return newestScore.score;
|
||||||
|
|
||||||
int reps[] = getReps(beginningTime, today);
|
int reps[] = getCheckmarks(beginningTime, today);
|
||||||
|
|
||||||
|
ActiveAndroid.beginTransaction();
|
||||||
int lastScore = beginningScore;
|
int lastScore = beginningScore;
|
||||||
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, 19259500);
|
|
||||||
}
|
|
||||||
s.save();
|
|
||||||
|
|
||||||
lastScore = s.score;
|
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, 19259500);
|
||||||
|
}
|
||||||
|
s.save();
|
||||||
|
|
||||||
|
lastScore = s.score;
|
||||||
|
}
|
||||||
|
|
||||||
|
ActiveAndroid.setTransactionSuccessful();
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
ActiveAndroid.endTransaction();
|
||||||
}
|
}
|
||||||
|
|
||||||
return lastScore;
|
return lastScore;
|
||||||
@@ -403,41 +538,89 @@ public class Habit extends Model
|
|||||||
offset, divisor).execute();
|
offset, divisor).execute();
|
||||||
}
|
}
|
||||||
|
|
||||||
public long[] getStreaks()
|
public List<Streak> getStreaks()
|
||||||
{
|
{
|
||||||
String query =
|
updateStreaks();
|
||||||
"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 = ?";
|
|
||||||
|
|
||||||
String query2 =
|
return new Select()
|
||||||
"select time from T, (select 0 union select 1) where neighbors = 0 union all\n" +
|
.from(Streak.class)
|
||||||
"select time from T where neighbors = 1 order by time;";
|
.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();
|
public void updateStreaks()
|
||||||
db.beginTransaction();
|
{
|
||||||
db.execSQL(query, args);
|
long beginning;
|
||||||
Cursor cursor = db.rawQuery(query2, null);
|
long today = DateHelper.getStartOfToday();
|
||||||
|
long day = DateHelper.millisecondsInOneDay;
|
||||||
|
|
||||||
long streaks[] = new long[cursor.getCount()];
|
Streak newestStreak = getNewestStreak();
|
||||||
int current = 0;
|
if(newestStreak == null)
|
||||||
|
|
||||||
Log.d("Streaks", String.format("%d rows", cursor.getCount()));
|
|
||||||
|
|
||||||
if (cursor.moveToFirst())
|
|
||||||
{
|
{
|
||||||
do
|
Repetition oldestRep = getOldestRep();
|
||||||
{
|
if (oldestRep == null) return;
|
||||||
streaks[current++] = cursor.getLong(0);
|
|
||||||
} while (cursor.moveToNext());
|
beginning = oldestRep.timestamp;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Repetition oldestRep = getOldestRepNewerThan(newestStreak.end);
|
||||||
|
if (oldestRep == null) return;
|
||||||
|
|
||||||
|
beginning = oldestRep.timestamp;
|
||||||
}
|
}
|
||||||
|
|
||||||
db.endTransaction();
|
if(beginning > today) return;
|
||||||
return streaks;
|
|
||||||
|
int checks[] = getCheckmarks(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 = 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
|
public static class CreateCommand extends Command
|
||||||
@@ -508,28 +691,9 @@ public class Habit extends Model
|
|||||||
habit.save();
|
habit.save();
|
||||||
if (hasIntervalChanged)
|
if (hasIntervalChanged)
|
||||||
{
|
{
|
||||||
new AsyncTask<Habit, Integer, Integer>()
|
habit.deleteCheckmarksNewerThan(0);
|
||||||
{
|
habit.deleteStreaksNewerThan(0);
|
||||||
@Override
|
habit.deleteScoresNewerThan(0);
|
||||||
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);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -540,26 +704,9 @@ public class Habit extends Model
|
|||||||
habit.save();
|
habit.save();
|
||||||
if (hasIntervalChanged)
|
if (hasIntervalChanged)
|
||||||
{
|
{
|
||||||
new AsyncTask<Habit, Integer, Integer>()
|
habit.deleteCheckmarksNewerThan(0);
|
||||||
{
|
habit.deleteStreaksNewerThan(0);
|
||||||
@Override
|
habit.deleteScoresNewerThan(0);
|
||||||
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);
|
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
19
app/src/main/java/org/isoron/uhabits/models/Streak.java
Normal file
19
app/src/main/java/org/isoron/uhabits/models/Streak.java
Normal file
@@ -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;
|
||||||
|
}
|
||||||
@@ -13,17 +13,19 @@ import android.view.View;
|
|||||||
import org.isoron.helpers.ColorHelper;
|
import org.isoron.helpers.ColorHelper;
|
||||||
import org.isoron.helpers.DateHelper;
|
import org.isoron.helpers.DateHelper;
|
||||||
import org.isoron.uhabits.R;
|
import org.isoron.uhabits.R;
|
||||||
|
import org.isoron.uhabits.models.Checkmark;
|
||||||
import org.isoron.uhabits.models.Habit;
|
import org.isoron.uhabits.models.Habit;
|
||||||
|
|
||||||
import java.text.SimpleDateFormat;
|
import java.text.SimpleDateFormat;
|
||||||
import java.util.Calendar;
|
import java.util.Calendar;
|
||||||
import java.util.GregorianCalendar;
|
import java.util.GregorianCalendar;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
public class HabitHistoryView extends View
|
public class HabitHistoryView extends View
|
||||||
{
|
{
|
||||||
|
|
||||||
private Habit habit;
|
private Habit habit;
|
||||||
private int reps[];
|
private int[] checks;
|
||||||
|
|
||||||
private Context context;
|
private Context context;
|
||||||
private Paint pSquareBg, pSquareFg, pTextHeader;
|
private Paint pSquareBg, pSquareFg, pTextHeader;
|
||||||
@@ -93,7 +95,7 @@ public class HabitHistoryView extends View
|
|||||||
for (int i = 0; i < nColumns * 7; i++)
|
for (int i = 0; i < nColumns * 7; i++)
|
||||||
dateFrom -= DateHelper.millisecondsInOneDay;
|
dateFrom -= DateHelper.millisecondsInOneDay;
|
||||||
|
|
||||||
reps = habit.getReps(dateFrom, dateTo);
|
checks = habit.getCheckmarks(dateFrom, dateTo);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -159,7 +161,9 @@ public class HabitHistoryView extends View
|
|||||||
{
|
{
|
||||||
if (!(i == nColumns - 1 && offsetWeeks == 0 && j > todayWeekday))
|
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.drawRect(square, pSquareBg);
|
||||||
canvas.drawText(Integer.toString(currentDate.get(Calendar.DAY_OF_MONTH)),
|
canvas.drawText(Integer.toString(currentDate.get(Calendar.DAY_OF_MONTH)),
|
||||||
square.centerX(), square.centerY() + squareTextOffset, pSquareFg);
|
square.centerX(), square.centerY() + squareTextOffset, pSquareFg);
|
||||||
|
|||||||
@@ -12,10 +12,10 @@ import android.view.View;
|
|||||||
import org.isoron.helpers.ColorHelper;
|
import org.isoron.helpers.ColorHelper;
|
||||||
import org.isoron.helpers.DateHelper;
|
import org.isoron.helpers.DateHelper;
|
||||||
import org.isoron.uhabits.models.Habit;
|
import org.isoron.uhabits.models.Habit;
|
||||||
|
import org.isoron.uhabits.models.Streak;
|
||||||
|
|
||||||
import java.text.SimpleDateFormat;
|
import java.text.SimpleDateFormat;
|
||||||
import java.util.Calendar;
|
import java.util.List;
|
||||||
import java.util.GregorianCalendar;
|
|
||||||
|
|
||||||
public class HabitStreakView extends View
|
public class HabitStreakView extends View
|
||||||
{
|
{
|
||||||
@@ -23,10 +23,9 @@ public class HabitStreakView extends View
|
|||||||
private int columnWidth, columnHeight, nColumns;
|
private int columnWidth, columnHeight, nColumns;
|
||||||
|
|
||||||
private Paint pText, pBar;
|
private Paint pText, pBar;
|
||||||
private long streaks[];
|
private List<Streak> streaks;
|
||||||
private int dataOffset;
|
private int dataOffset;
|
||||||
|
|
||||||
private long streakStart[], streakEnd[], streakLength[];
|
|
||||||
private long maxStreakLength;
|
private long maxStreakLength;
|
||||||
|
|
||||||
private int barHeaderHeight;
|
private int barHeaderHeight;
|
||||||
@@ -68,17 +67,9 @@ public class HabitStreakView extends View
|
|||||||
private void fetchStreaks()
|
private void fetchStreaks()
|
||||||
{
|
{
|
||||||
streaks = habit.getStreaks();
|
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<streaks.length / 2; i++)
|
for(Streak s : streaks)
|
||||||
{
|
maxStreakLength = Math.max(maxStreakLength, s.length);
|
||||||
streakStart[i] = streaks[i * 2];
|
|
||||||
streakEnd[i] = streaks[i * 2 + 1];
|
|
||||||
streakLength[i] = (streakEnd[i] - streakStart[i]) / DateHelper.millisecondsInOneDay + 1;
|
|
||||||
maxStreakLength = Math.max(maxStreakLength, streakLength[i]);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -103,16 +94,17 @@ public class HabitStreakView extends View
|
|||||||
float lineHeight = pText.getFontSpacing();
|
float lineHeight = pText.getFontSpacing();
|
||||||
float barHeaderOffset = lineHeight * 0.4f;
|
float barHeaderOffset = lineHeight * 0.4f;
|
||||||
|
|
||||||
int start = Math.max(0, streakStart.length - nColumns - dataOffset);
|
int nStreaks = streaks.size();
|
||||||
|
int start = Math.max(0, nStreaks - nColumns - dataOffset);
|
||||||
SimpleDateFormat dfMonth = new SimpleDateFormat("MMM");
|
SimpleDateFormat dfMonth = new SimpleDateFormat("MMM");
|
||||||
|
|
||||||
String previousMonth = "";
|
String previousMonth = "";
|
||||||
|
|
||||||
for (int offset = 0; offset < nColumns && start+offset < streakStart.length; offset++)
|
for (int offset = 0; offset < nColumns && start+offset < nStreaks; offset++)
|
||||||
{
|
{
|
||||||
String month = dfMonth.format(streakStart[start+offset]);
|
String month = dfMonth.format(streaks.get(start+offset).start);
|
||||||
|
|
||||||
long l = streakLength[offset+start];
|
long l = streaks.get(offset+start).length;
|
||||||
double lRelative = ((double) l) / maxStreakLength;
|
double lRelative = ((double) l) / maxStreakLength;
|
||||||
|
|
||||||
pBar.setColor(colors[(int) Math.floor(lRelative*3)]);
|
pBar.setColor(colors[(int) Math.floor(lRelative*3)]);
|
||||||
@@ -122,7 +114,7 @@ public class HabitStreakView extends View
|
|||||||
r.offset(offset * columnWidth, barHeaderHeight + columnHeight - height);
|
r.offset(offset * columnWidth, barHeaderHeight + columnHeight - height);
|
||||||
|
|
||||||
canvas.drawRect(r, pBar);
|
canvas.drawRect(r, pBar);
|
||||||
canvas.drawText(Long.toString(streakLength[offset+start]), r.centerX(), r.top - barHeaderOffset, pBar);
|
canvas.drawText(Long.toString(l), r.centerX(), r.top - barHeaderOffset, pBar);
|
||||||
|
|
||||||
if(!month.equals(previousMonth))
|
if(!month.equals(previousMonth))
|
||||||
canvas.drawText(month, r.centerX(), r.bottom + lineHeight * 1.2f, pText);
|
canvas.drawText(month, r.centerX(), r.bottom + lineHeight * 1.2f, pText);
|
||||||
@@ -166,7 +158,7 @@ public class HabitStreakView extends View
|
|||||||
private boolean move(float dx)
|
private boolean move(float dx)
|
||||||
{
|
{
|
||||||
int newDataOffset = dataOffset + (int) (dx / columnWidth);
|
int newDataOffset = dataOffset + (int) (dx / columnWidth);
|
||||||
newDataOffset = Math.max(0, Math.min(streakStart.length - nColumns, newDataOffset));
|
newDataOffset = Math.max(0, Math.min(streaks.size() - nColumns, newDataOffset));
|
||||||
|
|
||||||
if (newDataOffset != dataOffset)
|
if (newDataOffset != dataOffset)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
xmlns:tools="http://schemas.android.com/tools"
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
android:id="@+id/container"
|
android:id="@+id/container"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
@@ -13,4 +13,4 @@
|
|||||||
android:layout_height="match_parent"
|
android:layout_height="match_parent"
|
||||||
tools:layout="@layout/list_habits_fragment" />
|
tools:layout="@layout/list_habits_fragment" />
|
||||||
|
|
||||||
</FrameLayout>
|
</RelativeLayout>
|
||||||
|
|||||||
@@ -52,4 +52,12 @@
|
|||||||
style="@style/habitsListButtonsPanelStyle"/>
|
style="@style/habitsListButtonsPanelStyle"/>
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
|
|
||||||
|
<ProgressBar
|
||||||
|
android:id="@+id/progressBar"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
style="?android:attr/progressBarStyleHorizontal"
|
||||||
|
android:layout_marginTop="37dp"
|
||||||
|
/>
|
||||||
|
|
||||||
</RelativeLayout>
|
</RelativeLayout>
|
||||||
@@ -41,6 +41,7 @@
|
|||||||
<string name="day_of_week_label_typeface">sans-serif</string>
|
<string name="day_of_week_label_typeface">sans-serif</string>
|
||||||
<string name="habit_key"></string>
|
<string name="habit_key"></string>
|
||||||
<string name="offset_key"></string>
|
<string name="offset_key"></string>
|
||||||
|
<string name="toggle_key"></string>
|
||||||
|
|
||||||
<item name="KEY_TIMESTAMP" type="id"/>
|
<item name="KEY_TIMESTAMP" type="id"/>
|
||||||
|
|
||||||
|
|||||||
@@ -43,14 +43,9 @@
|
|||||||
android:action="android.intent.action.VIEW"
|
android:action="android.intent.action.VIEW"
|
||||||
android:data="https://github.com/iSoron/uhabits"/>
|
android:data="https://github.com/iSoron/uhabits"/>
|
||||||
</Preference>
|
</Preference>
|
||||||
</PreferenceCategory>
|
|
||||||
|
|
||||||
<PreferenceCategory
|
|
||||||
android:key="pref_key_misc"
|
|
||||||
android:title="Miscellaneous">
|
|
||||||
|
|
||||||
|
|
||||||
<Preference android:title="Replay app introduction">
|
<Preference android:title="View app introduction">
|
||||||
<intent
|
<intent
|
||||||
android:targetClass="org.isoron.uhabits.IntroActivity"
|
android:targetClass="org.isoron.uhabits.IntroActivity"
|
||||||
android:targetPackage="org.isoron.uhabits"/>
|
android:targetPackage="org.isoron.uhabits"/>
|
||||||
|
|||||||
Reference in New Issue
Block a user