Reorganizing

pull/30/head
Alinson S. Xavier 10 years ago
parent a1f05714ba
commit de02e119d1

@ -1,5 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<module external.linked.project.path="$MODULE_DIR$" external.root.project.path="$MODULE_DIR$/.." external.system.id="GRADLE" external.system.module.group="uHabits" external.system.module.version="unspecified" type="JAVA_MODULE" version="4"> <module external.linked.project.id=":app" external.linked.project.path="$MODULE_DIR$" external.root.project.path="$USER_HOME$/Android/uHabits" external.system.id="GRADLE" external.system.module.group="uHabits" external.system.module.version="unspecified" type="JAVA_MODULE" version="4">
<component name="FacetManager"> <component name="FacetManager">
<facet type="android-gradle" name="Android-Gradle"> <facet type="android-gradle" name="Android-Gradle">
<configuration> <configuration>
@ -12,8 +12,9 @@
<option name="SELECTED_TEST_ARTIFACT" value="_android_test_" /> <option name="SELECTED_TEST_ARTIFACT" value="_android_test_" />
<option name="ASSEMBLE_TASK_NAME" value="assembleDebug" /> <option name="ASSEMBLE_TASK_NAME" value="assembleDebug" />
<option name="COMPILE_JAVA_TASK_NAME" value="compileDebugSources" /> <option name="COMPILE_JAVA_TASK_NAME" value="compileDebugSources" />
<option name="ASSEMBLE_TEST_TASK_NAME" value="assembleDebugAndroidTest" />
<option name="SOURCE_GEN_TASK_NAME" value="generateDebugSources" /> <option name="SOURCE_GEN_TASK_NAME" value="generateDebugSources" />
<option name="ASSEMBLE_TEST_TASK_NAME" value="assembleDebugAndroidTest" />
<option name="COMPILE_JAVA_TEST_TASK_NAME" value="compileDebugAndroidTestSources" />
<option name="TEST_SOURCE_GEN_TASK_NAME" value="generateDebugAndroidTestSources" /> <option name="TEST_SOURCE_GEN_TASK_NAME" value="generateDebugAndroidTestSources" />
<option name="ALLOW_USER_CONFIGURATION" value="false" /> <option name="ALLOW_USER_CONFIGURATION" value="false" />
<option name="MANIFEST_FILE_RELATIVE_PATH" value="/src/main/AndroidManifest.xml" /> <option name="MANIFEST_FILE_RELATIVE_PATH" value="/src/main/AndroidManifest.xml" />
@ -90,4 +91,3 @@
<orderEntry type="library" exported="" name="support-annotations-22.0.0" level="project" /> <orderEntry type="library" exported="" name="support-annotations-22.0.0" level="project" />
</component> </component>
</module> </module>

@ -14,7 +14,7 @@
<application <application
android:name="com.activeandroid.app.Application" android:name="com.activeandroid.app.Application"
android:allowBackup="true" android:allowBackup="true"
android:icon="@drawable/ic_launcher" android:icon="@mipmap/ic_launcher"
android:label="@string/app_name" android:label="@string/app_name"
android:theme="@style/AppTheme"> android:theme="@style/AppTheme">

Binary file not shown.

After

Width:  |  Height:  |  Size: 40 KiB

@ -6,6 +6,7 @@ import android.app.PendingIntent;
import android.content.Context; import android.content.Context;
import android.content.Intent; import android.content.Intent;
import android.net.Uri; import android.net.Uri;
import android.os.AsyncTask;
import android.os.Build; import android.os.Build;
import android.os.Bundle; import android.os.Bundle;
import android.util.Log; import android.util.Log;
@ -125,25 +126,24 @@ public class MainActivity extends Activity
return super.onOptionsItemSelected(item); return super.onOptionsItemSelected(item);
} }
public void executeCommand(Command command, boolean datasetChanged) public void executeCommand(Command command)
{ {
executeCommand(command, datasetChanged, true); executeCommand(command, false);
} }
public void executeCommand(Command command, boolean datasetChanged, boolean clearRedoStack)
public void executeCommand(Command command, boolean clearRedoStack)
{ {
undoList.push(command); undoList.push(command);
if (undoList.size() > MAX_UNDO_LEVEL) if (undoList.size() > MAX_UNDO_LEVEL)
undoList.removeLast(); undoList.removeLast();
if (clearRedoStack) if (clearRedoStack)
redoList.clear(); redoList.clear();
command.execute(); command.execute();
listHabitsFragment.notifyDataSetChanged();
showToast(command.getExecuteStringId()); showToast(command.getExecuteStringId());
if (datasetChanged)
{
listHabitsFragment.notifyDataSetChanged();
}
} }
public void undo() public void undo()
@ -170,7 +170,7 @@ public class MainActivity extends Activity
return; return;
} }
Command last = redoList.pop(); Command last = redoList.pop();
executeCommand(last, true, false); executeCommand(last, false);
} }
private void showToast(Integer stringId) private void showToast(Integer stringId)

@ -1,12 +1,6 @@
package org.isoron.uhabits; package org.isoron.uhabits;
import java.util.Date;
import java.util.List;
import org.isoron.uhabits.models.Habit;
import android.app.Activity; import android.app.Activity;
import android.app.AlarmManager;
import android.app.Notification; import android.app.Notification;
import android.app.NotificationManager; import android.app.NotificationManager;
import android.app.PendingIntent; import android.app.PendingIntent;
@ -14,148 +8,133 @@ import android.content.BroadcastReceiver;
import android.content.ContentUris; import android.content.ContentUris;
import android.content.Context; import android.content.Context;
import android.content.Intent; import android.content.Intent;
import android.graphics.BitmapFactory;
import android.media.RingtoneManager; import android.media.RingtoneManager;
import android.net.Uri; import android.net.Uri;
import android.support.v4.app.NotificationCompat; import android.support.v4.app.NotificationCompat;
import android.util.Log; import android.util.Log;
import org.isoron.uhabits.models.Habit;
import java.util.Date;
public class ReminderAlarmReceiver extends BroadcastReceiver public class ReminderAlarmReceiver extends BroadcastReceiver
{ {
public static String ACTION_CHECK = "org.isoron.uhabits.ACTION_CHECK"; public static String ACTION_CHECK = "org.isoron.uhabits.ACTION_CHECK";
public static String ACTION_DISMISS = "org.isoron.uhabits.ACTION_DISMISS"; public static String ACTION_DISMISS = "org.isoron.uhabits.ACTION_DISMISS";
public static String ACTION_REMIND = "org.isoron.uhabits.ACTION_REMIND"; public static String ACTION_REMIND = "org.isoron.uhabits.ACTION_REMIND";
public static String ACTION_REMOVE_REMINDER = "org.isoron.uhabits.ACTION_REMOVE_REMINDER"; public static String ACTION_REMOVE_REMINDER = "org.isoron.uhabits.ACTION_REMOVE_REMINDER";
public static String ACTION_SNOOZE = "org.isoron.uhabits.ACTION_SNOOZE"; public static String ACTION_SNOOZE = "org.isoron.uhabits.ACTION_SNOOZE";
@Override @Override
public void onReceive(Context context, Intent intent) public void onReceive(Context context, Intent intent)
{ {
String action = intent.getAction(); String action = intent.getAction();
if(action.equals(ACTION_REMIND)) if (action.equals(ACTION_REMIND)) createNotification(context, intent.getData());
createNotification(context, intent.getData());
else if (action.equals(ACTION_DISMISS)) dismissAllHabits();
else if(action.equals(ACTION_DISMISS))
dismissAllHabits(); else if (action.equals(ACTION_CHECK)) checkHabit(context, intent.getData());
else if(action.equals(ACTION_CHECK)) else if (action.equals(ACTION_SNOOZE)) snoozeHabit(context, intent.getData());
checkHabit(context, intent.getData()); }
else if(action.equals(ACTION_SNOOZE)) private void snoozeHabit(Context context, Uri data)
snoozeHabit(context, intent.getData()); {
} int delayMinutes = 60;
Habit habit = Habit.get(ContentUris.parseId(data));
private void snoozeHabit(Context context, Uri data) MainActivity.createReminderAlarm(context, habit,
{ new Date().getTime() + delayMinutes * 60 * 1000);
int delayMinutes = 15; dismissNotification(context, habit);
Habit habit = Habit.get(ContentUris.parseId(data)); }
MainActivity.createReminderAlarm(context, habit, new Date().getTime() + delayMinutes * 1000);
dismissNotification(context); private void checkHabit(Context context, Uri data)
} {
Habit habit = Habit.get(ContentUris.parseId(data));
private void checkHabit(Context context, Uri data) habit.toggleRepetitionToday();
{ habit.save();
Habit habit = Habit.get(ContentUris.parseId(data)); dismissNotification(context, habit);
habit.toggleRepetitionToday(); }
habit.save();
dismissNotification(context); private void dismissAllHabits()
} {
for (Habit h : Habit.getHighlightedHabits())
private void dismissAllHabits() {
{ h.highlight = 0;
for(Habit h : Habit.getHighlightedHabits()) h.save();
{ }
Log.d("Alarm", String.format("Removing highlight from: %s", h.name)); }
h.highlight = 0;
h.save(); private void dismissNotification(Context context, Habit habit)
} {
} NotificationManager notificationManager =
(NotificationManager) context.getSystemService(Activity.NOTIFICATION_SERVICE);
private void dismissNotification(Context context)
{ int notificationId = (int) (habit.getId() % Integer.MAX_VALUE);
NotificationManager notificationManager = (NotificationManager) context notificationManager.cancel(notificationId);
.getSystemService(Activity.NOTIFICATION_SERVICE); }
notificationManager.cancel(1);
} private void createNotification(Context context, Uri data)
{
private void createNotification(Context context, Uri data) Habit habit = Habit.get(ContentUris.parseId(data));
{
Log.d("Alarm", "Alarm received!"); if (habit.hasImplicitRepToday()) return;
Habit habit = Habit.get(ContentUris.parseId(data)); Log.d("Alarm", String.format("Applying highlight: %s", habit.name));
habit.highlight = 1;
if(habit.hasImplicitRepToday()) habit.save();
{
Log.d("Alarm", String.format("(%s) has implicit rep today", habit.name)); // Check if reminder has been turned off after alarm was scheduled
return; if (habit.reminder_hour == null) return;
}
Intent contentIntent = new Intent(context, MainActivity.class);
Log.d("Alarm", String.format("Applying highlight: %s", habit.name)); contentIntent.setData(data);
habit.highlight = 1; PendingIntent contentPendingIntent =
habit.save(); PendingIntent.getActivity(context, 0, contentIntent, 0);
// Check if reminder has been turned off after alarm was scheduled Intent deleteIntent = new Intent(context, ReminderAlarmReceiver.class);
if(habit.reminder_hour == null) deleteIntent.setAction(ACTION_DISMISS);
return; PendingIntent deletePendingIntent = PendingIntent.getBroadcast(context, 0, deleteIntent, 0);
Intent contentIntent = new Intent(context, MainActivity.class); Intent checkIntent = new Intent(context, ReminderAlarmReceiver.class);
contentIntent.setData(data); checkIntent.setData(data);
PendingIntent contentPendingIntent = PendingIntent.getActivity(context, 0, contentIntent, 0); checkIntent.setAction(ACTION_CHECK);
PendingIntent checkIntentPending = PendingIntent.getBroadcast(context, 0, checkIntent, 0);
Intent deleteIntent = new Intent(context, ReminderAlarmReceiver.class);
deleteIntent.setAction(ACTION_DISMISS); Intent snoozeIntent = new Intent(context, ReminderAlarmReceiver.class);
PendingIntent deletePendingIntent = PendingIntent.getBroadcast(context, 0, deleteIntent, 0); snoozeIntent.setData(data);
snoozeIntent.setAction(ACTION_SNOOZE);
Intent checkIntent = new Intent(context, ReminderAlarmReceiver.class); PendingIntent snoozeIntentPending = PendingIntent.getBroadcast(context, 0, snoozeIntent, 0);
checkIntent.setData(data);
checkIntent.setAction(ACTION_CHECK); Uri soundUri = RingtoneManager.getDefaultUri(RingtoneManager.TYPE_NOTIFICATION);
PendingIntent checkIntentPending = PendingIntent.getBroadcast(context, 0, checkIntent, 0);
NotificationCompat.WearableExtender wearableExtender =
Intent snoozeIntent = new Intent(context, ReminderAlarmReceiver.class); new NotificationCompat.WearableExtender().setBackground(
snoozeIntent.setData(data); BitmapFactory.decodeResource(context.getResources(), R.drawable.stripe));
snoozeIntent.setAction(ACTION_SNOOZE);
PendingIntent snoozeIntentPending = PendingIntent.getBroadcast(context, 0, snoozeIntent, 0); Notification notification =
new NotificationCompat.Builder(context).setSmallIcon(R.drawable.ic_notification)
Uri soundUri = RingtoneManager.getDefaultUri(RingtoneManager.TYPE_NOTIFICATION); .setContentTitle(habit.name)
.setContentText(habit.description)
NotificationCompat.InboxStyle inboxStyle = new NotificationCompat.InboxStyle(); .setContentIntent(contentPendingIntent)
inboxStyle.setBigContentTitle("Habit Reminder:"); .setDeleteIntent(deletePendingIntent)
List<Habit> pendingHabits = Habit.getHighlightedHabits(); .addAction(R.drawable.ic_action_check, "Check", checkIntentPending)
StringBuffer contentText = new StringBuffer(); .addAction(R.drawable.ic_action_snooze, "Later", snoozeIntentPending)
for(Habit h : pendingHabits) .setSound(soundUri)
{ .extend(wearableExtender)
if(h.hasImplicitRepToday()) .build();
continue;
notification.flags |= Notification.FLAG_AUTO_CANCEL;
inboxStyle.addLine(h.name);
if(contentText.length() > 0) NotificationManager notificationManager =
contentText.append(", "); (NotificationManager) context.getSystemService(Activity.NOTIFICATION_SERVICE);
contentText.append(h.name);
Log.d("Alarm", String.format("Found highlighted: %s", h.name)); int notificationId = (int) (habit.getId() % Integer.MAX_VALUE);
} notificationManager.notify(notificationId, notification);
}
Notification notification =
new NotificationCompat.Builder(context)
.setSmallIcon(R.drawable.ic_notification)
.setContentTitle("Habit Reminder")
.setContentText(contentText)
.setContentIntent(contentPendingIntent)
.setDeleteIntent(deletePendingIntent)
.addAction(R.drawable.ic_action_check, "Check", checkIntentPending)
.addAction(R.drawable.ic_action_snooze, "Later", snoozeIntentPending)
.setSound(soundUri)
.setStyle(inboxStyle)
.build();
notification.flags |= Notification.FLAG_AUTO_CANCEL;
NotificationManager notificationManager = (NotificationManager) context
.getSystemService(Activity.NOTIFICATION_SERVICE);
notificationManager.notify(1, notification);
}
} }

@ -1,18 +1,5 @@
package org.isoron.uhabits.dialogs; package org.isoron.uhabits.dialogs;
import java.util.Date;
import java.util.GregorianCalendar;
import java.util.Locale;
import java.util.TimeZone;
import org.isoron.helpers.Command;
import org.isoron.helpers.DateHelper;
import org.isoron.helpers.DialogHelper.OnSavedListener;
import org.isoron.uhabits.MainActivity;
import org.isoron.uhabits.R;
import org.isoron.uhabits.ShowHabitActivity;
import org.isoron.uhabits.models.Habit;
import android.app.Fragment; import android.app.Fragment;
import android.content.Context; import android.content.Context;
import android.content.Intent; import android.content.Intent;
@ -20,9 +7,9 @@ import android.graphics.Color;
import android.graphics.Point; import android.graphics.Point;
import android.graphics.Typeface; import android.graphics.Typeface;
import android.net.Uri; import android.net.Uri;
import android.os.AsyncTask;
import android.os.Bundle; import android.os.Bundle;
import android.os.Vibrator; import android.os.Vibrator;
import android.transition.Explode;
import android.util.DisplayMetrics; import android.util.DisplayMetrics;
import android.util.Log; import android.util.Log;
import android.view.ContextMenu; import android.view.ContextMenu;
@ -50,362 +37,359 @@ import com.mobeta.android.dslv.DragSortController;
import com.mobeta.android.dslv.DragSortListView; import com.mobeta.android.dslv.DragSortListView;
import com.mobeta.android.dslv.DragSortListView.DropListener; import com.mobeta.android.dslv.DragSortListView.DropListener;
public class ListHabitsFragment extends Fragment implements OnSavedListener, OnItemClickListener, import org.isoron.helpers.Command;
OnLongClickListener, DropListener, OnClickListener import org.isoron.helpers.DateHelper;
import org.isoron.helpers.DialogHelper.OnSavedListener;
import org.isoron.uhabits.MainActivity;
import org.isoron.uhabits.R;
import org.isoron.uhabits.ShowHabitActivity;
import org.isoron.uhabits.models.Habit;
import java.util.Date;
import java.util.GregorianCalendar;
import java.util.Locale;
import java.util.TimeZone;
public class ListHabitsFragment extends Fragment
implements OnSavedListener, OnItemClickListener, OnLongClickListener, DropListener,
OnClickListener
{ {
private int tvNameWidth; ListHabitsAdapter adapter;
private int button_count; DragSortListView listView;
ListHabitsAdapter adapter; MainActivity mainActivity;
DragSortListView listView; TextView tvNameHeader;
MainActivity mainActivity; long lastLongClick = 0;
TextView tvNameHeader; private int tvNameWidth;
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * private int button_count;
* Adapter *
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ @Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
class ListHabitsAdapter extends BaseAdapter Bundle savedInstanceState)
{ {
View view = inflater.inflate(R.layout.list_habits_fragment, container, false);
private Context context;
private LayoutInflater inflater; DisplayMetrics dm = getResources().getDisplayMetrics();
private Typeface fontawesome; int width = (int) (dm.widthPixels / dm.density);
button_count = (int) ((width - 160) / 42);
String habits[] = { "wake up early", "work out", "meditate", "take vitamins", tvNameWidth = (int) ((width - 30 - button_count * 42) * dm.density);
"go to school",
"cook dinner & lunch" }; tvNameHeader = (TextView) view.findViewById(R.id.tvNameHeader);
// updateStarCount();
public ListHabitsAdapter(Context context)
{ adapter = new ListHabitsAdapter(getActivity());
this.context = context; listView = (DragSortListView) view.findViewById(R.id.listView);
listView.setAdapter(adapter);
inflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE); listView.setOnItemClickListener(this);
fontawesome = Typeface.createFromAsset(context.getAssets(), "fontawesome-webfont.ttf"); registerForContextMenu(listView);
} listView.setDropListener(this);
@Override DragSortController controller = new DragSortController(listView);
public int getCount() controller.setDragHandleId(R.id.tvStar);
{ controller.setRemoveEnabled(false);
return Habit.getCount(); controller.setSortEnabled(true);
} controller.setDragInitMode(1);
@Override listView.setFloatViewManager(controller);
public Object getItem(int position) listView.setOnTouchListener(controller);
{ listView.setDragEnabled(true);
return Habit.getByPosition(position);
} GregorianCalendar day = new GregorianCalendar(TimeZone.getTimeZone("GMT"));
day.setTimeInMillis(DateHelper.getStartOfDay(DateHelper.getLocalTime()));
@Override
public long getItemId(int position) for (int i = 0; i < button_count; i++)
{ {
return ((Habit) getItem(position)).getId(); View check = inflater.inflate(R.layout.list_habits_header_check, null);
} Button btCheck = (Button) check.findViewById(R.id.tvCheck);
btCheck.setText(
@Override day.getDisplayName(GregorianCalendar.DAY_OF_WEEK, GregorianCalendar.SHORT,
public View getView(int position, View view, ViewGroup parent) Locale.US) + "\n" +
{ Integer.toString(day.get(GregorianCalendar.DAY_OF_MONTH)));
final Habit habit = (Habit) getItem(position); ((LinearLayout) view.findViewById(R.id.llButtonsHeader)).addView(check);
if(view == null || (Long) view.getTag(R.id.KEY_TIMESTAMP) != DateHelper.getStartOfToday()) day.add(GregorianCalendar.DAY_OF_MONTH, -1);
{ }
view = inflater.inflate(R.layout.list_habits_item, null);
((TextView) view.findViewById(R.id.tvStar)).setTypeface(fontawesome); mainActivity = (MainActivity) getActivity();
LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(tvNameWidth, setHasOptionsMenu(true);
LayoutParams.WRAP_CONTENT, 1); return view;
((TextView) view.findViewById(R.id.tvName)).setLayoutParams(params); }
Display display = ((WindowManager) context.getSystemService(Context.WINDOW_SERVICE)) @Override
.getDefaultDisplay(); public void onCreateOptionsMenu(Menu menu, MenuInflater inflater)
Point size = new Point(); {
display.getSize(size); inflater.inflate(R.menu.show_habits_options, menu);
super.onCreateOptionsMenu(menu, inflater);
LinearLayout.LayoutParams llp = new LinearLayout.LayoutParams( }
LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT);
llp.setMargins(2, 0, 2, 0); @Override
public void onCreateContextMenu(ContextMenu menu, View view, ContextMenuInfo menuInfo)
for (int i = 0; i < button_count; i++) {
{ super.onCreateContextMenu(menu, view, menuInfo);
View check = inflater.inflate(R.layout.list_habits_item_check, null); getActivity().getMenuInflater().inflate(R.menu.show_habits_context, menu);
TextView btCheck = (TextView) check.findViewById(R.id.tvCheck); }
btCheck.setTypeface(fontawesome);
btCheck.setOnLongClickListener(ListHabitsFragment.this); @Override
public boolean onOptionsItemSelected(MenuItem item)
{
int id = item.getItemId();
if (id == R.id.action_add)
{
EditHabitFragment frag = EditHabitFragment.createHabitFragment();
frag.setOnSavedListener(this);
frag.show(getFragmentManager(), "dialog");
return true;
}
return super.onOptionsItemSelected(item);
}
@Override
public boolean onContextItemSelected(MenuItem menuItem)
{
AdapterContextMenuInfo info = (AdapterContextMenuInfo) menuItem.getMenuInfo();
final int id = menuItem.getItemId();
final Habit habit = Habit.get(info.id);
if (id == R.id.action_edit_habit)
{
EditHabitFragment frag = EditHabitFragment.editSingleHabitFragment(habit.getId());
frag.setOnSavedListener(this);
frag.show(getFragmentManager(), "dialog");
return true;
}
return super.onContextItemSelected(menuItem);
}
@Override
public void onItemClick(AdapterView<?> parent, View view, int position, long id)
{
if (new Date().getTime() - lastLongClick < 1000) return;
Habit habit = Habit.getByPosition(position);
Log.d("ItemClick", Long.toString(id));
Intent intent = new Intent(getActivity(), ShowHabitActivity.class);
intent.setData(Uri.parse("content://org.isoron.uhabits/habit/" + habit.getId()));
startActivity(intent);
}
@Override
public void onSaved(Command command)
{
executeCommand(command);
MainActivity.createReminderAlarms(mainActivity);
}
public void notifyDataSetChanged()
{
adapter.notifyDataSetChanged();
}
@Override
public boolean onLongClick(View v)
{
int id = v.getId();
if (id == R.id.tvCheck)
{
lastLongClick = new Date().getTime();
Habit habit = Habit.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));
Vibrator vb = (Vibrator) getActivity().getSystemService(Context.VIBRATOR_SERVICE);
vb.vibrate(100);
return true;
}
return false;
}
private void executeCommand(Command c)
{
mainActivity.executeCommand(c);
}
@Override
public void drop(int from, int to)
{
Habit.reorder(from, to);
notifyDataSetChanged();
}
@Override
public void onClick(View v)
{
}
void updateStarCount()
{
Log.d("StarCount", "updating star count");
String msg = "";
int starCount = Habit.getStarCount();
if (starCount == 1) msg = String.format("%d star", starCount);
else if (starCount > 1) msg = String.format("%d stars", starCount);
tvNameHeader.setText(msg);
}
class ListHabitsAdapter extends BaseAdapter
{
String habits[] = {"wake up early", "work out", "meditate", "take vitamins", "go to school",
"cook dinner & lunch"};
private Context context;
private LayoutInflater inflater;
private Typeface fontawesome;
public ListHabitsAdapter(Context context)
{
this.context = context;
inflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
fontawesome = Typeface.createFromAsset(context.getAssets(), "fontawesome-webfont.ttf");
}
@Override
public int getCount()
{
return Habit.getCount();
}
@Override
public Object getItem(int position)
{
return Habit.getByPosition(position);
}
@Override
public long getItemId(int position)
{
return ((Habit) getItem(position)).getId();
}
@Override
public View getView(int position, View view, ViewGroup parent)
{
final Habit habit = (Habit) getItem(position);
if (view == null ||
(Long) view.getTag(R.id.KEY_TIMESTAMP) != DateHelper.getStartOfToday())
{
view = inflater.inflate(R.layout.list_habits_item, null);
((TextView) view.findViewById(R.id.tvStar)).setTypeface(fontawesome);
LinearLayout.LayoutParams params =
new LinearLayout.LayoutParams(tvNameWidth, LayoutParams.WRAP_CONTENT, 1);
((TextView) view.findViewById(R.id.tvName)).setLayoutParams(params);
Display display = ((WindowManager) context.getSystemService(Context.WINDOW_SERVICE))
.getDefaultDisplay();
Point size = new Point();
display.getSize(size);
LinearLayout.LayoutParams llp =
new LinearLayout.LayoutParams(LayoutParams.WRAP_CONTENT,
LayoutParams.WRAP_CONTENT);
llp.setMargins(2, 0, 2, 0);
for (int i = 0; i < button_count; i++)
{
View check = inflater.inflate(R.layout.list_habits_item_check, null);
TextView btCheck = (TextView) check.findViewById(R.id.tvCheck);
btCheck.setTypeface(fontawesome);
btCheck.setOnLongClickListener(ListHabitsFragment.this);
// btCheck.setLayoutParams(llp); // btCheck.setLayoutParams(llp);
((LinearLayout) view.findViewById(R.id.llButtons)).addView(check); ((LinearLayout) view.findViewById(R.id.llButtons)).addView(check);
} }
// LinearLayout llInner = (LinearLayout) view.findViewById(R.id.llInner); // LinearLayout llInner = (LinearLayout) view.findViewById(R.id.llInner);
// llInner.setOnClickListener(ListHabitsFragment.this); // llInner.setOnClickListener(ListHabitsFragment.this);
view.setTag(R.id.KEY_TIMESTAMP, DateHelper.getStartOfToday()); view.setTag(R.id.KEY_TIMESTAMP, DateHelper.getStartOfToday());
} }
TextView tvStar = (TextView) view.findViewById(R.id.tvStar); TextView tvStar = (TextView) view.findViewById(R.id.tvStar);
TextView tvName = (TextView) view.findViewById(R.id.tvName); TextView tvName = (TextView) view.findViewById(R.id.tvName);
if(habit == null) if (habit == null)
{ {
tvName.setText(null); tvName.setText(null);
return view; return view;
} }
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 inactiveColor = Color.rgb(230, 230, 230);
int activeColor = habit.color; int activeColor = habit.color;
tvName.setText(habit.name); tvName.setText(habit.name);
tvName.setTextColor(activeColor); tvName.setTextColor(activeColor);
int score = habit.getScore(); int score = habit.getScore();
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(inactiveColor);
} } 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(inactiveColor); } else
} {
else tvStar.setText(context.getString(R.string.fa_star));
{ tvStar.setTextColor(activeColor);
tvStar.setText(context.getString(R.string.fa_star)); }
tvStar.setTextColor(activeColor);
} LinearLayout llButtons = (LinearLayout) view.findViewById(R.id.llButtons);
int m = llButtons.getChildCount();
LinearLayout llButtons = (LinearLayout) view.findViewById(R.id.llButtons);
int m = llButtons.getChildCount(); long dateTo = DateHelper.getStartOfDay(DateHelper.getLocalTime());
long dateFrom = dateTo - m * DateHelper.millisecondsInOneDay;
long dateTo = DateHelper.getStartOfDay(DateHelper.getLocalTime());
long dateFrom = dateTo - m * DateHelper.millisecondsInOneDay; int isChecked[] = habit.getReps(dateFrom, dateTo);
int isChecked[] = habit.getReps(dateFrom, dateTo); for (int i = 0; i < m; i++)
{
for (int i = 0; i < m; i++)
{ TextView tvCheck = (TextView) llButtons.getChildAt(i);
tvCheck.setTag(R.string.habit_key, habit.getId());
TextView tvCheck = (TextView) llButtons.getChildAt(i); tvCheck.setTag(R.string.offset_key, i);
tvCheck.setTag(R.string.habit_key, habit.getId());
tvCheck.setTag(R.string.offset_key, i); switch (isChecked[i])
{
switch(isChecked[i]) case 2:
{ tvCheck.setText(R.string.fa_check);
case 2: tvCheck.setTextColor(activeColor);
tvCheck.setText(R.string.fa_check); break;
tvCheck.setTextColor(activeColor);
break; case 1:
tvCheck.setText(R.string.fa_check);
case 1: tvCheck.setTextColor(inactiveColor);
tvCheck.setText(R.string.fa_check); break;
tvCheck.setTextColor(inactiveColor);
break; case 0:
tvCheck.setText(R.string.fa_times);
case 0: tvCheck.setTextColor(inactiveColor);
tvCheck.setText(R.string.fa_times); break;
tvCheck.setTextColor(inactiveColor); }
break; }
}
} return view;
}
return view;
}
}
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
* Creation *
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
@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);
tvNameHeader = (TextView) view.findViewById(R.id.tvNameHeader);
// updateStarCount();
adapter = new ListHabitsAdapter(getActivity()); }
listView = (DragSortListView) view.findViewById(R.id.listView);
listView.setAdapter(adapter);
listView.setOnItemClickListener(this);
registerForContextMenu(listView);
listView.setDropListener(this);
DragSortController controller = new DragSortController(listView);
controller.setDragHandleId(R.id.tvStar);
controller.setRemoveEnabled(false);
controller.setSortEnabled(true);
controller.setDragInitMode(1);
listView.setFloatViewManager(controller);
listView.setOnTouchListener(controller);
listView.setDragEnabled(true);
GregorianCalendar day = new GregorianCalendar(TimeZone.getTimeZone("GMT"));
day.setTimeInMillis(DateHelper.getStartOfDay(DateHelper.getLocalTime()));
for (int i = 0; i < button_count; i++)
{
View check = inflater.inflate(R.layout.list_habits_header_check, null);
Button btCheck = (Button) check.findViewById(R.id.tvCheck);
btCheck.setText(day.getDisplayName(GregorianCalendar.DAY_OF_WEEK,
GregorianCalendar.SHORT, Locale.US) + "\n"
+ Integer.toString(day.get(GregorianCalendar.DAY_OF_MONTH)));
((LinearLayout) view.findViewById(R.id.llButtonsHeader)).addView(check);
day.add(GregorianCalendar.DAY_OF_MONTH, -1);
}
mainActivity = (MainActivity) getActivity();
setHasOptionsMenu(true);
return view;
}
@Override
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater)
{
inflater.inflate(R.menu.show_habits_options, menu);
super.onCreateOptionsMenu(menu, inflater);
}
@Override
public void onCreateContextMenu(ContextMenu menu, View view, ContextMenuInfo menuInfo)
{
super.onCreateContextMenu(menu, view, menuInfo);
getActivity().getMenuInflater().inflate(R.menu.show_habits_context, menu);
}
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
* Callback *
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
@Override
public boolean onOptionsItemSelected(MenuItem item)
{
int id = item.getItemId();
if(id == R.id.action_add)
{
EditHabitFragment frag = EditHabitFragment.createHabitFragment();
frag.setOnSavedListener(this);
frag.show(getFragmentManager(), "dialog");
return true;
}
return super.onOptionsItemSelected(item);
}
@Override
public boolean onContextItemSelected(MenuItem menuItem)
{
AdapterContextMenuInfo info = (AdapterContextMenuInfo) menuItem.getMenuInfo();
final int id = menuItem.getItemId();
final Habit habit = Habit.get(info.id);
if(id == R.id.action_edit_habit)
{
EditHabitFragment frag = EditHabitFragment.editSingleHabitFragment(habit.getId());
frag.setOnSavedListener(this);
frag.show(getFragmentManager(), "dialog");
return true;
}
return super.onContextItemSelected(menuItem);
}
long lastLongClick = 0;
@Override
public void onItemClick(AdapterView<?> parent, View view, int position, long id)
{
if(new Date().getTime() - lastLongClick < 1000) return;
Habit habit = Habit.getByPosition(position);
Log.d("ItemClick", Long.toString(id));
Intent intent = new Intent(getActivity(), ShowHabitActivity.class);
intent.setData(Uri.parse("content://org.isoron.uhabits/habit/"
+ habit.getId()));
startActivity(intent);
}
@Override
public void onSaved(Command command)
{
executeCommand(command);
MainActivity.createReminderAlarms(mainActivity);
}
public void notifyDataSetChanged()
{
adapter.notifyDataSetChanged();
}
@Override
public boolean onLongClick(View v)
{
int id = v.getId();
if(id == R.id.tvCheck)
{
lastLongClick = new Date().getTime();
Habit habit = Habit.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));
Vibrator vb = (Vibrator) getActivity().getSystemService(Context.VIBRATOR_SERVICE);
vb.vibrate(100);
return true;
}
return false;
}
private void executeCommand(Command c)
{
mainActivity.executeCommand(c, false);
notifyDataSetChanged();
}
@Override
public void drop(int from, int to)
{
Habit.reorder(from, to);
notifyDataSetChanged();
}
@Override
public void onClick(View v)
{
}
void updateStarCount()
{
Log.d("StarCount", "updating star count");
String msg = "";
int starCount = Habit.getStarCount();
if(starCount == 1)
msg = String.format("%d star", starCount);
else if(starCount > 1)
msg = String.format("%d stars", starCount);
tvNameHeader.setText(msg);
}
} }

@ -1,49 +1,42 @@
package org.isoron.uhabits.dialogs; package org.isoron.uhabits.dialogs;
import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.GregorianCalendar;
import org.isoron.helpers.ColorHelper;
import org.isoron.helpers.DateHelper;
import org.isoron.uhabits.R;
import org.isoron.uhabits.ShowHabitActivity;
import org.isoron.uhabits.models.Habit;
import org.isoron.uhabits.views.HabitHistoryView;
import org.isoron.uhabits.views.HabitStreakView;
import org.isoron.uhabits.views.RingView;
import android.app.Fragment; import android.app.Fragment;
import android.graphics.Color; import android.graphics.Color;
import android.graphics.Typeface;
import android.os.Bundle; import android.os.Bundle;
import android.util.DisplayMetrics;
import android.util.Log; import android.util.Log;
import android.view.LayoutInflater; import android.view.LayoutInflater;
import android.view.View; import android.view.View;
import android.view.ViewGroup; import android.view.ViewGroup;
import android.widget.LinearLayout; import android.widget.LinearLayout;
import android.widget.LinearLayout.LayoutParams;
import android.widget.TextView; import android.widget.TextView;
import org.isoron.helpers.ColorHelper;
import org.isoron.uhabits.R;
import org.isoron.uhabits.ShowHabitActivity;
import org.isoron.uhabits.models.Habit;
import org.isoron.uhabits.views.HabitHistoryView;
import org.isoron.uhabits.views.HabitScoreView;
import org.isoron.uhabits.views.HabitStreakView;
import org.isoron.uhabits.views.RingView;
public class ShowHabitFragment extends Fragment public class ShowHabitFragment extends Fragment
{ {
protected ShowHabitActivity activity; protected ShowHabitActivity activity;
@Override @Override
public void onStart() public void onStart()
{ {
super.onStart(); super.onStart();
} }
@Override @Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) Bundle savedInstanceState)
{ {
Log.d("ShowHabitActivity", "Creating view..."); Log.d("ShowHabitActivity", "Creating view...");
View view = inflater.inflate(R.layout.show_habit, container, false); View view = inflater.inflate(R.layout.show_habit, container, false);
activity = (ShowHabitActivity) getActivity(); activity = (ShowHabitActivity) getActivity();
Habit habit = activity.habit; Habit habit = activity.habit;
if (android.os.Build.VERSION.SDK_INT >= 21) if (android.os.Build.VERSION.SDK_INT >= 21)
@ -52,26 +45,34 @@ public class ShowHabitFragment extends Fragment
activity.getWindow().setStatusBarColor(darkerHabitColor); activity.getWindow().setStatusBarColor(darkerHabitColor);
} }
TextView tvHistory = (TextView) view.findViewById(R.id.tvHistory); TextView tvHistory = (TextView) view.findViewById(R.id.tvHistory);
TextView tvOverview = (TextView) view.findViewById(R.id.tvOverview); TextView tvOverview = (TextView) view.findViewById(R.id.tvOverview);
TextView tvStreaks= (TextView) view.findViewById(R.id.tvStreaks); TextView tvStrength = (TextView) view.findViewById(R.id.tvStrength);
tvHistory.setTextColor(habit.color); TextView tvStreaks = (TextView) view.findViewById(R.id.tvStreaks);
tvOverview.setTextColor(habit.color); tvHistory.setTextColor(habit.color);
tvOverview.setTextColor(habit.color);
tvStrength.setTextColor(habit.color);
tvStreaks.setTextColor(habit.color); tvStreaks.setTextColor(habit.color);
LinearLayout llOverview = (LinearLayout) view.findViewById(R.id.llOverview); LinearLayout llOverview = (LinearLayout) view.findViewById(R.id.llOverview);
llOverview.addView(new RingView(activity, 200, habit.color, ((float) habit.getScore() / Habit.MAX_SCORE), "Habit strength")); llOverview.addView(new RingView(activity,
(int) activity.getResources().getDimension(R.dimen.small_square_size) * 4, habit.color,
((float) habit.getScore() / Habit.MAX_SCORE), "Habit strength"));
LinearLayout llStrength = (LinearLayout) view.findViewById(R.id.llStrength);
llStrength.addView(new HabitScoreView(activity, habit,
(int) activity.getResources().getDimension(R.dimen.small_square_size)));
LinearLayout llHistory = (LinearLayout) view.findViewById(R.id.llHistory); LinearLayout llHistory = (LinearLayout) view.findViewById(R.id.llHistory);
HabitHistoryView hhv = new HabitHistoryView(activity, habit, HabitHistoryView hhv = new HabitHistoryView(activity, habit,
(int) activity.getResources().getDimension(R.dimen.square_size)); (int) activity.getResources().getDimension(R.dimen.small_square_size));
llHistory.addView(hhv); llHistory.addView(hhv);
LinearLayout llStreaks = (LinearLayout) view.findViewById(R.id.llStreaks); LinearLayout llStreaks = (LinearLayout) view.findViewById(R.id.llStreaks);
HabitStreakView hsv = new HabitStreakView(activity, habit, HabitStreakView hsv = new HabitStreakView(activity, habit,
(int) activity.getResources().getDimension(R.dimen.square_size)); (int) activity.getResources().getDimension(R.dimen.small_square_size));
llStreaks.addView(hsv); llStreaks.addView(hsv);
return view; return view;
} }
} }

@ -3,6 +3,7 @@ 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 android.util.Log;
import com.activeandroid.Cache; import com.activeandroid.Cache;
@ -20,6 +21,7 @@ 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.Date;
import java.util.List; import java.util.List;
@Table(name = "Habits") @Table(name = "Habits")
@ -77,8 +79,7 @@ public class Habit extends Model
@SuppressLint("DefaultLocale") @SuppressLint("DefaultLocale")
public static void updateId(long oldId, long newId) public static void updateId(long oldId, long newId)
{ {
SQLiteUtils.execSql(String.format( SQLiteUtils.execSql(String.format("update Habits set Id = %d where Id = %d", newId, oldId));
"update Habits set Id = %d where Id = %d", newId, oldId));
} }
protected static From select() protected static From select()
@ -114,18 +115,13 @@ public class Habit extends Model
public static void reorder(int from, int to) public static void reorder(int from, int to)
{ {
if (from == to) if (from == to) return;
return;
Habit h = Habit.getByPosition(from); Habit h = Habit.getByPosition(from);
if (to < from) if (to < from) new Update(Habit.class).set("position = position + 1")
new Update(Habit.class).set("position = position + 1") .where("position >= ? and position < ?", to, from).execute();
.where("position >= ? and position < ?", to, from) else new Update(Habit.class).set("position = position - 1")
.execute(); .where("position > ? and position <= ?", from, to).execute();
else
new Update(Habit.class).set("position = position - 1")
.where("position > ? and position <= ?", from, to)
.execute();
h.position = to; h.position = to;
h.save(); h.save();
@ -152,13 +148,20 @@ public class Habit extends Model
} }
} }
public static void recomputeAllScores()
{
for (Habit habit : getHabits())
{
habit.deleteScoresNewerThan(0);
}
}
public static int getStarCount() public static int getStarCount()
{ {
String args[] = {}; String args[] = {};
return SQLiteUtils.intQuery( return SQLiteUtils.intQuery("select count(*) from (select score, max(timestamp) from " +
"select count(*) from (select score, max(timestamp) from " + "score group by habit) as scores where scores.score >= " +
"score group by habit) as scores where scores.score >= " Integer.toString(12973000), args);
+ Integer.toString(12973000), args);
} }
@ -184,14 +187,12 @@ public class Habit extends Model
protected From selectReps() protected From selectReps()
{ {
return new Select().from(Repetition.class).where("habit = ?", getId()) return new Select().from(Repetition.class).where("habit = ?", getId()).orderBy("timestamp");
.orderBy("timestamp");
} }
protected From selectRepsFromTo(long timeFrom, long timeTo) protected From selectRepsFromTo(long timeFrom, long timeTo)
{ {
return selectReps().and("timestamp >= ?", timeFrom).and( return selectReps().and("timestamp >= ?", timeFrom).and("timestamp <= ?", timeTo);
"timestamp <= ?", timeTo);
} }
public boolean hasRep(long timestamp) public boolean hasRep(long timestamp)
@ -236,8 +237,7 @@ public class Habit extends Model
for (int j = 0; j < freq_den; j++) for (int j = 0; j < freq_den; j++)
if (checkExtended[i + j] == 2) counter++; if (checkExtended[i + j] == 2) counter++;
if (counter >= freq_num) if (counter >= freq_num) checkExtended[i] = Math.max(checkExtended[i], 1);
checkExtended[i] = Math.max(checkExtended[i], 1);
} }
int check[] = new int[nDays + 1]; int check[] = new int[nDays + 1];
@ -247,6 +247,13 @@ public class Habit extends Model
return check; return check;
} }
public int getRepsCount(int days)
{
long timeTo = DateHelper.getStartOfToday();
long timeFrom = timeTo - DateHelper.millisecondsInOneDay * days;
return selectRepsFromTo(timeFrom, timeTo).count();
}
public boolean hasImplicitRepToday() public boolean hasImplicitRepToday()
{ {
long today = DateHelper.getStartOfToday(); long today = DateHelper.getStartOfToday();
@ -282,14 +289,14 @@ public class Habit extends Model
public Score getNewestScore() public Score getNewestScore()
{ {
return new Select().from(Score.class).where("habit = ?", getId()) return new Select().from(Score.class).where("habit = ?", getId()).orderBy("timestamp desc")
.orderBy("timestamp desc").limit(1).executeSingle(); .limit(1).executeSingle();
} }
public void deleteScoresNewerThan(long timestamp) public void deleteScoresNewerThan(long timestamp)
{ {
new Delete().from(Score.class).where("habit = ?", getId()) new Delete().from(Score.class).where("habit = ?", getId()).and("timestamp >= ?", timestamp)
.and("timestamp >= ?", timestamp).execute(); .execute();
} }
public Integer getScore() public Integer getScore()
@ -307,8 +314,7 @@ public class Habit extends Model
if (newestScore == null) if (newestScore == null)
{ {
Repetition oldestRep = getOldestRep(); Repetition oldestRep = getOldestRep();
if (oldestRep == null) if (oldestRep == null) return 0;
return 0;
beginningTime = oldestRep.timestamp; beginningTime = oldestRep.timestamp;
beginningScore = 0; beginningScore = 0;
} else } else
@ -318,8 +324,7 @@ public class Habit extends Model
} }
long nDays = (today - beginningTime) / day; long nDays = (today - beginningTime) / day;
if (nDays < 0) if (nDays < 0) return newestScore.score;
return newestScore.score;
int reps[] = getReps(beginningTime, today); int reps[] = getReps(beginningTime, today);
@ -343,13 +348,26 @@ public class Habit extends Model
return lastScore; return lastScore;
} }
public List<Score> getScores(long fromTimestamp, long toTimestamp)
{
return getScores(fromTimestamp, toTimestamp, 1, 0);
}
public List<Score> getScores(long fromTimestamp, long toTimestamp, int divisor, long offset)
{
return new Select().from(Score.class).where("habit = ? and timestamp > ? and " +
"timestamp <= ? and (timestamp - ?) % ? = 0", getId(), fromTimestamp, toTimestamp,
offset, divisor).execute();
}
public long[] getStreaks() public long[] getStreaks()
{ {
String query = "create temporary table T as select distinct r1.habit as habit, r1.timestamp as time,\n" + String query =
" (select count(*) from repetitions r2 where r1.habit = r2.habit and\n" + "create temporary table T as select distinct r1.habit as habit, r1.timestamp as time,\n" +
" (r1.timestamp = r2.timestamp - 24*60*60*1000 or\n" + " (select count(*) from repetitions r2 where r1.habit = r2.habit and\n" +
" r1.timestamp = r2.timestamp + 24*60*60*1000)) as neighbors\n" + " (r1.timestamp = r2.timestamp - 24*60*60*1000 or\n" +
"from repetitions r1 where r1.habit = ?"; " r1.timestamp = r2.timestamp + 24*60*60*1000)) as neighbors\n" +
"from repetitions r1 where r1.habit = ?";
String query2 = String query2 =
"select time from T, (select 0 union select 1) where neighbors = 0 union all\n" + "select time from T, (select 0 union select 1) where neighbors = 0 union all\n" +
@ -436,8 +454,8 @@ public class Habit extends Model
this.modified = new Habit(modified); this.modified = new Habit(modified);
this.original = new Habit(Habit.this); this.original = new Habit(Habit.this);
hasIntervalChanged = (this.original.freq_den != this.modified.freq_den hasIntervalChanged = (this.original.freq_den != this.modified.freq_den ||
|| this.original.freq_num != this.modified.freq_num); this.original.freq_num != this.modified.freq_num);
} }
public void execute() public void execute()
@ -446,7 +464,30 @@ public class Habit extends Model
habit.copyAttributes(modified); habit.copyAttributes(modified);
habit.save(); habit.save();
if (hasIntervalChanged) if (hasIntervalChanged)
habit.deleteScoresNewerThan(0); {
new AsyncTask<Habit, Integer, Integer>()
{
@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);
}
} }
public void undo() public void undo()
@ -455,7 +496,28 @@ public class Habit extends Model
habit.copyAttributes(original); habit.copyAttributes(original);
habit.save(); habit.save();
if (hasIntervalChanged) if (hasIntervalChanged)
habit.deleteScoresNewerThan(0); {
new AsyncTask<Habit, Integer, Integer>()
{
@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);
}
} }
public Integer getExecuteStringId() public Integer getExecuteStringId()

@ -1,233 +1,233 @@
package org.isoron.uhabits.views; package org.isoron.uhabits.views;
import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.GregorianCalendar;
import org.isoron.helpers.ColorHelper;
import org.isoron.helpers.DateHelper;
import org.isoron.uhabits.R;
import org.isoron.uhabits.models.Habit;
import android.content.Context; import android.content.Context;
import android.graphics.Canvas; import android.graphics.Canvas;
import android.graphics.Color; import android.graphics.Color;
import android.graphics.Paint; import android.graphics.Paint;
import android.graphics.Paint.Align; import android.graphics.Paint.Align;
import android.graphics.Rect; import android.graphics.Rect;
import android.graphics.Typeface;
import android.support.v4.view.MotionEventCompat; import android.support.v4.view.MotionEventCompat;
import android.util.Log;
import android.view.MotionEvent; import android.view.MotionEvent;
import android.view.View; import android.view.View;
import org.isoron.helpers.ColorHelper;
import org.isoron.helpers.DateHelper;
import org.isoron.uhabits.R;
import org.isoron.uhabits.models.Habit;
import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.GregorianCalendar;
public class HabitHistoryView extends View public class HabitHistoryView extends View
{ {
private Habit habit; private Habit habit;
private int reps[]; private int reps[];
private Context context; private Context context;
private Paint pSquareBg, pSquareFg, pTextHeader; private Paint pSquareBg, pSquareFg, pTextHeader;
private int squareSize, squareSpacing; private int squareSize, squareSpacing;
private int nColumns, offsetWeeks; private int nColumns, offsetWeeks;
private int colorPrimary, colorPrimaryBrighter, grey; private int colorPrimary, colorPrimaryBrighter, grey;
private Float prevX, prevY;
public HabitHistoryView(Context context, Habit habit, int squareSize)
{ public HabitHistoryView(Context context, Habit habit, int squareSize)
super(context); {
this.habit = habit; super(context);
this.context = context; this.habit = habit;
this.squareSize = squareSize; this.context = context;
this.squareSize = squareSize;
Typeface fontawesome = Typeface.createFromAsset(context.getAssets(),
"fontawesome-webfont.ttf"); colorPrimary = habit.color;
colorPrimaryBrighter = ColorHelper.mixColors(colorPrimary, Color.WHITE, 0.5f);
colorPrimary = habit.color; grey = Color.rgb(230, 230, 230);
colorPrimaryBrighter = ColorHelper.mixColors(colorPrimary, Color.WHITE, 0.5f); squareSpacing = 2;
grey = Color.rgb(230, 230, 230);
squareSpacing = 2; pTextHeader = new Paint();
pTextHeader.setColor(Color.LTGRAY);
pTextHeader = new Paint(); pTextHeader.setTextAlign(Align.LEFT);
pTextHeader.setColor(Color.LTGRAY); pTextHeader.setTextSize(squareSize * 0.5f);
pTextHeader.setTextAlign(Align.LEFT); pTextHeader.setAntiAlias(true);
pTextHeader.setTextSize(squareSize * 0.5f);
pTextHeader.setAntiAlias(true); pSquareBg = new Paint();
pSquareBg.setColor(habit.color);
pSquareBg = new Paint();
pSquareBg.setColor(habit.color); pSquareFg = new Paint();
pSquareFg.setColor(Color.WHITE);
pSquareFg = new Paint(); pSquareFg.setAntiAlias(true);
pSquareFg.setColor(Color.WHITE); pSquareFg.setTextSize(squareSize * 0.5f);
pSquareFg.setAntiAlias(true); pSquareFg.setTextAlign(Align.CENTER);
// pSquareFg.setTypeface(fontawesome); }
pSquareFg.setTextSize(squareSize * 0.5f);
pSquareFg.setTextAlign(Align.CENTER); @Override
} protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec)
{
@Override super.onMeasure(widthMeasureSpec, heightMeasureSpec);
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) setMeasuredDimension(getMeasuredWidth(), 8 * squareSize);
{ }
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
setMeasuredDimension(getMeasuredWidth(), 8 * squareSize); @Override
} protected void onSizeChanged(int w, int h, int oldw, int oldh)
{
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh)
{
nColumns = (w / squareSize) - 1; nColumns = (w / squareSize) - 1;
fetchReps(); fetchReps();
} }
private void fetchReps() private void fetchReps()
{ {
Calendar currentDate = new GregorianCalendar(); Calendar currentDate = new GregorianCalendar();
currentDate.add(Calendar.DAY_OF_YEAR, -offsetWeeks * 7); currentDate.add(Calendar.DAY_OF_YEAR, -offsetWeeks * 7);
int dayOfWeek = currentDate.get(Calendar.DAY_OF_WEEK) % 7; int dayOfWeek = currentDate.get(Calendar.DAY_OF_WEEK) % 7;
long dateTo = DateHelper.getStartOfToday(); long dateTo = DateHelper.getStartOfToday();
for (int i = 0; i < 7 - dayOfWeek; i++) for (int i = 0; i < 7 - dayOfWeek; i++)
dateTo += DateHelper.millisecondsInOneDay; dateTo += DateHelper.millisecondsInOneDay;
for (int i = 0; i < offsetWeeks * 7; i++) for (int i = 0; i < offsetWeeks * 7; i++)
dateTo -= DateHelper.millisecondsInOneDay; dateTo -= DateHelper.millisecondsInOneDay;
long dateFrom = dateTo; long dateFrom = dateTo;
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); reps = habit.getReps(dateFrom, dateTo);
} }
@Override @Override
protected void onDraw(Canvas canvas) protected void onDraw(Canvas canvas)
{ {
super.onDraw(canvas); super.onDraw(canvas);
Rect square = new Rect(0, 0, squareSize - squareSpacing, squareSize - squareSpacing); Rect square = new Rect(0, 0, squareSize - squareSpacing, squareSize - squareSpacing);
Calendar currentDate = new GregorianCalendar(); Calendar currentDate = new GregorianCalendar();
currentDate.add(Calendar.DAY_OF_YEAR, -(offsetWeeks-1) * 7); currentDate.add(Calendar.DAY_OF_YEAR, - (offsetWeeks - 1) * 7);
int nDays = nColumns * 7; int nDays = nColumns * 7;
int todayWeekday = new GregorianCalendar().get(Calendar.DAY_OF_WEEK) % 7; int todayWeekday = new GregorianCalendar().get(Calendar.DAY_OF_WEEK) % 7;
currentDate.add(Calendar.DAY_OF_YEAR, -nDays); currentDate.add(Calendar.DAY_OF_YEAR, -nDays);
currentDate.add(Calendar.DAY_OF_YEAR, -todayWeekday);
SimpleDateFormat dfMonth = new SimpleDateFormat("MMM");
SimpleDateFormat dfYear = new SimpleDateFormat("yyyy"); SimpleDateFormat dfMonth = new SimpleDateFormat("MMM");
SimpleDateFormat dfYear = new SimpleDateFormat("yyyy");
String previousMonth = "";
String previousYear = ""; String previousMonth = "";
String previousYear = "";
int colors[] = { grey, colorPrimaryBrighter, colorPrimary };
String markers[] = { context.getString(R.string.fa_times), int colors[] = {grey, colorPrimaryBrighter, colorPrimary};
context.getString(R.string.fa_check), context.getString(R.string.fa_check) }; String markers[] =
{context.getString(R.string.fa_times), context.getString(R.string.fa_check),
float squareTextOffset = pSquareFg.getFontSpacing() * 0.4f; context.getString(R.string.fa_check)};
float headerTextOffset = pTextHeader.getFontSpacing() * 0.3f;
boolean justPrintedYear = false; float squareTextOffset = pSquareFg.getFontSpacing() * 0.4f;
float headerTextOffset = pTextHeader.getFontSpacing() * 0.3f;
int k = nDays; boolean justPrintedYear = false;
for (int i = 0; i < nColumns; i++)
{ int k = nDays;
String month = dfMonth.format(currentDate.getTime()); for (int i = 0; i < nColumns; i++)
String year = dfYear.format(currentDate.getTime()); {
String month = dfMonth.format(currentDate.getTime());
if(!month.equals(previousMonth)) String year = dfYear.format(currentDate.getTime());
{
int offset = 0; if (!month.equals(previousMonth))
if(justPrintedYear) {
offset += squareSize; int offset = 0;
if (justPrintedYear) offset += squareSize;
canvas.drawText(month, square.left + offset, square.bottom - headerTextOffset,
pTextHeader); canvas.drawText(month, square.left + offset, square.bottom - headerTextOffset,
previousMonth = month; pTextHeader);
justPrintedYear = false; previousMonth = month;
} justPrintedYear = false;
else if(!year.equals(previousYear)) } else if (!year.equals(previousYear))
{ {
canvas.drawText(year, square.left, square.bottom - headerTextOffset, pTextHeader); canvas.drawText(year, square.left, square.bottom - headerTextOffset, pTextHeader);
previousYear = year; previousYear = year;
justPrintedYear = true; justPrintedYear = true;
} } else
else {
{ justPrintedYear = false;
justPrintedYear = false; }
}
square.offset(0, squareSize);
square.offset(0, squareSize);
for (int j = 0; j < 7; j++)
for (int j = 0; j < 7; j++) {
{ if (!(i == nColumns - 1 && offsetWeeks == 0 && j > todayWeekday))
if(!(i == nColumns - 1 && offsetWeeks == 0 && j > todayWeekday)) {
{ pSquareBg.setColor(colors[reps[k]]);
pSquareBg.setColor(colors[reps[k]]); canvas.drawRect(square, pSquareBg);
canvas.drawRect(square, pSquareBg); canvas.drawText(Integer.toString(currentDate.get(Calendar.DAY_OF_MONTH)),
// canvas.drawText(markers[reps[k]], square.centerX(), square.centerY() square.centerX(), square.centerY() + squareTextOffset, pSquareFg);
// + squareTextOffset, pSquareFg); }
canvas.drawText(Integer.toString(currentDate.get(Calendar.DAY_OF_MONTH)),
square.centerX(), square.centerY() + squareTextOffset, pSquareFg); currentDate.add(Calendar.DAY_OF_MONTH, 1);
} square.offset(0, squareSize);
k--;
currentDate.add(Calendar.DAY_OF_MONTH, 1); }
square.offset(0, squareSize);
k--; square.offset(squareSize, -8 * squareSize);
} }
square.offset(squareSize, -8 * squareSize); String wdays[] = {"Sat", "Sun", "Mon", "Tue", "Wed", "Thu", "Fri"};
}
for (int i = 0; i < 7; i++)
String wdays[] = { "Sat", "Sun", "Mon", "Tue", "Wed", "Thu", "Fri" }; {
square.offset(0, squareSize);
for (int i = 0; i < 7; i++) canvas.drawText(wdays[i], square.left + headerTextOffset,
{ square.bottom - headerTextOffset, pTextHeader);
square.offset(0, squareSize); }
canvas.drawText(wdays[i], square.left + headerTextOffset, square.bottom }
- headerTextOffset, pTextHeader);
} @Override
} public boolean onTouchEvent(MotionEvent event)
{
private Float prevX, prevY; int action = event.getAction();
@Override int pointerIndex = MotionEventCompat.getActionIndex(event);
public boolean onTouchEvent(MotionEvent event) final float x = MotionEventCompat.getX(event, pointerIndex);
{ final float y = MotionEventCompat.getY(event, pointerIndex);
int action = event.getAction();
if (action == MotionEvent.ACTION_DOWN)
int pointerIndex = MotionEventCompat.getActionIndex(event); {
final float x = MotionEventCompat.getX(event, pointerIndex); prevX = x;
final float y = MotionEventCompat.getY(event, pointerIndex); prevY = y;
}
if(action == MotionEvent.ACTION_DOWN)
{ if (action == MotionEvent.ACTION_MOVE)
prevX = x; {
prevY = y; float dx = x - prevX;
} float dy = y - prevY;
if(action == MotionEvent.ACTION_MOVE) if (Math.abs(dy) > Math.abs(dx)) return false;
{ getParent().requestDisallowInterceptTouchEvent(true);
float dx = x - prevX; if(move(dx))
float dy = y - prevY; {
prevX = x;
int newOffsetWeeks = offsetWeeks + (int) (dx / squareSize); prevY = y;
newOffsetWeeks = Math.max(0, newOffsetWeeks); }
}
if(newOffsetWeeks != offsetWeeks)
{ return true;
prevX = x; }
prevY = y;
offsetWeeks = newOffsetWeeks; private boolean move(float dx)
{
fetchReps(); int newOffsetWeeks = offsetWeeks + (int) (dx / squareSize);
invalidate(); newOffsetWeeks = Math.max(0, newOffsetWeeks);
}
if (newOffsetWeeks != offsetWeeks)
} {
return true; offsetWeeks = newOffsetWeeks;
} fetchReps();
invalidate();
return true;
}
else
return false;
}
} }

@ -0,0 +1,252 @@
package org.isoron.uhabits.views;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.RectF;
import android.support.v4.view.MotionEventCompat;
import android.view.MotionEvent;
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.Score;
import java.text.SimpleDateFormat;
import java.util.List;
public class HabitScoreView extends View
{
public static final int BUCKET_SIZE = 7;
private final Paint pGrid;
private final float em;
private Habit habit;
private int columnWidth, columnHeight, nColumns;
private Paint pText, pGraph;
private int dataOffset;
private int barHeaderHeight;
private int[] colors;
private float prevX;
private float prevY;
private List<Score> scores;
public HabitScoreView(Context context, Habit habit, int columnWidth)
{
super(context);
this.habit = habit;
this.columnWidth = columnWidth;
pText = new Paint();
pText.setColor(Color.LTGRAY);
pText.setTextAlign(Paint.Align.LEFT);
pText.setTextSize(columnWidth * 0.5f);
pText.setAntiAlias(true);
pGraph = new Paint();
pGraph.setTextAlign(Paint.Align.CENTER);
pGraph.setTextSize(columnWidth * 0.5f);
pGraph.setAntiAlias(true);
pGraph.setStrokeWidth(columnWidth * 0.1f);
pGrid = new Paint();
pGrid.setColor(Color.LTGRAY);
pGrid.setAntiAlias(true);
pGrid.setStrokeWidth(columnWidth * 0.05f);
columnHeight = 8 * columnWidth;
barHeaderHeight = columnWidth;
em = pText.getFontSpacing();
colors = new int[4];
colors[0] = Color.rgb(230, 230, 230);
colors[3] = habit.color;
colors[1] = ColorHelper.mixColors(colors[0], colors[3], 0.66f);
colors[2] = ColorHelper.mixColors(colors[0], colors[3], 0.33f);
}
private void fetchScores()
{
long toTimestamp = DateHelper.getStartOfToday();
for (int i = 0; i < dataOffset * BUCKET_SIZE; i++)
toTimestamp -= DateHelper.millisecondsInOneDay;
long fromTimestamp = toTimestamp;
for (int i = 0; i < nColumns * BUCKET_SIZE; i++)
fromTimestamp -= DateHelper.millisecondsInOneDay;
scores = habit.getScores(fromTimestamp, toTimestamp, BUCKET_SIZE * DateHelper.millisecondsInOneDay,
toTimestamp);
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec)
{
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
setMeasuredDimension(getMeasuredWidth(), columnHeight + 2 * barHeaderHeight);
}
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh)
{
super.onSizeChanged(w, h, oldw, oldh);
nColumns = w / columnWidth;
fetchScores();
}
@Override
protected void onDraw(Canvas canvas)
{
super.onDraw(canvas);
float lineHeight = pText.getFontSpacing();
float barHeaderOffset = lineHeight * 0.4f;
RectF rGrid = new RectF(0, 0, nColumns * columnWidth, columnHeight);
rGrid.offset(0, barHeaderHeight);
drawGrid(canvas, rGrid);
SimpleDateFormat dfMonth = new SimpleDateFormat("MMM");
SimpleDateFormat dfDay = new SimpleDateFormat("d");
String previousMonth = "";
pGraph.setColor(habit.color);
RectF prevR = null;
for (int offset = nColumns - scores.size(); offset < nColumns; offset++)
{
Score score = scores.get(offset - nColumns + scores.size());
String month = dfMonth.format(score.timestamp);
String day = dfDay.format(score.timestamp);
long s = score.score;
double sRelative = ((double) s) / Habit.MAX_SCORE;
int height = (int) (columnHeight * sRelative);
RectF r = new RectF(0, 0, columnWidth, columnWidth);
r.offset(offset * columnWidth,
barHeaderHeight + columnHeight - height - columnWidth / 2);
if (prevR != null)
{
drawLine(canvas, prevR, r);
drawMarker(canvas, prevR);
}
if (offset == nColumns - 1) drawMarker(canvas, r);
prevR = r;
r = new RectF(0, 0, columnWidth, columnHeight);
r.offset(offset * columnWidth, barHeaderHeight);
if (!month.equals(previousMonth))
{
canvas.drawText(month, r.centerX(), r.bottom + lineHeight * 1.2f, pText);
} else
{
canvas.drawText(day, r.centerX(), r.bottom + lineHeight * 1.2f, pText);
}
previousMonth = month;
}
}
private void drawGrid(Canvas canvas, RectF rGrid)
{
// pGrid.setColor(Color.rgb(230, 230, 230));
// pGrid.setStyle(Paint.Style.STROKE);
// canvas.drawRect(rGrid, pGrid);
int nRows = 5;
float rowHeight = rGrid.height() / nRows;
pGrid.setColor(Color.rgb(240, 240, 240));
for (int i = 0; i < nRows; i++)
{
canvas.drawText(String.format("%d%%", (100 - i * 100 / nRows)), rGrid.left + 0.5f * em,
rGrid.top + 1f * em, pText);
canvas.drawLine(rGrid.left, rGrid.top, rGrid.right, rGrid.top, pGrid);
rGrid.offset(0, rowHeight);
}
canvas.drawLine(rGrid.left, rGrid.top, rGrid.right, rGrid.top, pGrid);
}
private void drawLine(Canvas canvas, RectF rectFrom, RectF rectTo)
{
pGraph.setColor(habit.color);
canvas.drawLine(rectFrom.centerX(), rectFrom.centerY(), rectTo.centerX(), rectTo.centerY(),
pGraph);
}
private void drawMarker(Canvas canvas, RectF rect)
{
rect.inset(columnWidth * 0.15f, columnWidth * 0.15f);
pGraph.setColor(Color.WHITE);
canvas.drawOval(rect, pGraph);
rect.inset(columnWidth * 0.1f, columnWidth * 0.1f);
pGraph.setColor(habit.color);
canvas.drawOval(rect, pGraph);
rect.inset(columnWidth * 0.1f, columnWidth * 0.1f);
pGraph.setColor(Color.WHITE);
canvas.drawOval(rect, pGraph);
}
@Override
public boolean onTouchEvent(MotionEvent event)
{
int action = event.getAction();
int pointerIndex = MotionEventCompat.getActionIndex(event);
final float x = MotionEventCompat.getX(event, pointerIndex);
final float y = MotionEventCompat.getY(event, pointerIndex);
if (action == MotionEvent.ACTION_DOWN)
{
prevX = x;
prevY = y;
}
if (action == MotionEvent.ACTION_MOVE)
{
float dx = x - prevX;
float dy = y - prevY;
if (Math.abs(dy) > Math.abs(dx)) return false;
getParent().requestDisallowInterceptTouchEvent(true);
if (move(dx))
{
prevX = x;
prevY = y;
}
}
return true;
}
private boolean move(float dx)
{
int newDataOffset = dataOffset + (int) (dx / columnWidth);
newDataOffset = Math.max(0, newDataOffset);
if (newDataOffset != dataOffset)
{
dataOffset = newDataOffset;
fetchScores();
invalidate();
return true;
} else return false;
}
}

@ -5,6 +5,8 @@ import android.graphics.Canvas;
import android.graphics.Color; import android.graphics.Color;
import android.graphics.Paint; import android.graphics.Paint;
import android.graphics.Rect; import android.graphics.Rect;
import android.support.v4.view.MotionEventCompat;
import android.view.MotionEvent;
import android.view.View; import android.view.View;
import org.isoron.helpers.ColorHelper; import org.isoron.helpers.ColorHelper;
@ -22,6 +24,7 @@ public class HabitStreakView extends View
private Paint pText, pBar; private Paint pText, pBar;
private long streaks[]; private long streaks[];
private int dataOffset;
private long streakStart[], streakEnd[], streakLength[]; private long streakStart[], streakEnd[], streakLength[];
private long maxStreakLength; private long maxStreakLength;
@ -29,6 +32,8 @@ public class HabitStreakView extends View
private int barHeaderHeight; private int barHeaderHeight;
private int[] colors; private int[] colors;
private float prevX;
private float prevY;
public HabitStreakView(Context context, Habit habit, int columnWidth) public HabitStreakView(Context context, Habit habit, int columnWidth)
{ {
@ -98,7 +103,7 @@ 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); int start = Math.max(0, streakStart.length - nColumns - dataOffset);
SimpleDateFormat dfMonth = new SimpleDateFormat("MMM"); SimpleDateFormat dfMonth = new SimpleDateFormat("MMM");
String previousMonth = ""; String previousMonth = "";
@ -125,4 +130,51 @@ public class HabitStreakView extends View
previousMonth = month; previousMonth = month;
} }
} }
@Override
public boolean onTouchEvent(MotionEvent event)
{
int action = event.getAction();
int pointerIndex = MotionEventCompat.getActionIndex(event);
final float x = MotionEventCompat.getX(event, pointerIndex);
final float y = MotionEventCompat.getY(event, pointerIndex);
if (action == MotionEvent.ACTION_DOWN)
{
prevX = x;
prevY = y;
}
if (action == MotionEvent.ACTION_MOVE)
{
float dx = x - prevX;
float dy = y - prevY;
if (Math.abs(dy) > Math.abs(dx)) return false;
getParent().requestDisallowInterceptTouchEvent(true);
if(move(dx))
{
prevX = x;
prevY = y;
}
}
return true;
}
private boolean move(float dx)
{
int newDataOffset = dataOffset + (int) (dx / columnWidth);
newDataOffset = Math.max(0, Math.min(streakStart.length - nColumns, newDataOffset));
if (newDataOffset != dataOffset)
{
dataOffset = newDataOffset;
invalidate();
return true;
}
else
return false;
}
} }

@ -28,11 +28,8 @@ public class RingView extends View
pRing.setColor(color); pRing.setColor(color);
pRing.setAntiAlias(true); pRing.setAntiAlias(true);
pRing.setTextAlign(Paint.Align.CENTER); pRing.setTextAlign(Paint.Align.CENTER);
pRing.setTextSize(size * 0.15f);
this.label = label; this.label = label;
lineHeight = pRing.getFontSpacing();
} }
@Override @Override
@ -52,16 +49,19 @@ public class RingView extends View
RectF r = new RectF(0, 0, size, size); RectF r = new RectF(0, 0, size, size);
canvas.drawArc(r, -90, 360 * perc, true, pRing); canvas.drawArc(r, -90, 360 * perc, true, pRing);
pRing.setColor(Color.rgb(230, 230, 230)); pRing.setColor(Color.rgb(230, 230, 230));
canvas.drawArc(r, 360 * perc - 90 + 2, 360 * (1-perc) - 4, true, pRing); canvas.drawArc(r, 360 * perc - 90 + 2, 360 * (1 - perc) - 4, true, pRing);
pRing.setColor(Color.WHITE); pRing.setColor(Color.WHITE);
r.inset(thickness, thickness); r.inset(thickness, thickness);
canvas.drawArc(r, -90, 360, true, pRing); canvas.drawArc(r, -90, 360, true, pRing);
pRing.setColor(Color.GRAY); pRing.setColor(Color.GRAY);
canvas.drawText(String.format("%.2f%%", perc*100), r.centerX(), r.centerY()+lineHeight/3, pRing); pRing.setTextSize(size * 0.2f);
lineHeight = pRing.getFontSpacing();
canvas.drawText(String.format("%.0f%%", perc * 100), r.centerX(), r.centerY()+lineHeight/3, pRing);
pRing.setTextSize(size * 0.15f);
canvas.drawText(label, size/2, size + lineHeight * 1.2f, pRing); canvas.drawText(label, size/2, size + lineHeight * 1.2f, pRing);
} }
} }

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 344 B

@ -1,21 +1,35 @@
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android" <ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools" xmlns:tools="http://schemas.android.com/tools"
android:layout_width="fill_parent" android:layout_width="fill_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:fillViewport="true" android:background="@color/windowBackground"
android:background="@color/windowBackground"> android:fillViewport="true">
<LinearLayout <LinearLayout
style="@style/cardsListStyle" style="@style/cardsListStyle"
tools:context="org.isoron.uhabits.ShowHabitActivity"> tools:context="org.isoron.uhabits.ShowHabitActivity">
<LinearLayout style="@style/cardStyle" <LinearLayout
android:id="@+id/llOverview"> android:id="@+id/llOverview"
style="@style/cardStyle"
android:gravity="center">
<TextView <TextView
android:id="@+id/tvOverview" android:id="@+id/tvOverview"
style="@style/cardHeaderStyle" style="@style/cardHeaderStyle"
android:text="@string/overview" /> android:text="@string/overview"/>
</LinearLayout>
<LinearLayout
android:id="@+id/llStrength"
style="@style/cardStyle"
android:gravity="center">
<TextView
android:id="@+id/tvStrength"
style="@style/cardHeaderStyle"
android:text="@string/habit_strength"/>
</LinearLayout> </LinearLayout>
@ -24,13 +38,13 @@
<TextView <TextView
android:id="@+id/tvHistory" android:id="@+id/tvHistory"
style="@style/cardHeaderStyle" style="@style/cardHeaderStyle"
android:text="@string/history" /> android:text="@string/history"/>
<LinearLayout <LinearLayout
android:id="@+id/llHistory" android:id="@+id/llHistory"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:orientation="horizontal" /> android:orientation="horizontal"/>
</LinearLayout> </LinearLayout>
<LinearLayout style="@style/cardStyle"> <LinearLayout style="@style/cardStyle">
@ -38,13 +52,13 @@
<TextView <TextView
android:id="@+id/tvStreaks" android:id="@+id/tvStreaks"
style="@style/cardHeaderStyle" style="@style/cardHeaderStyle"
android:text="@string/streaks" /> android:text="@string/streaks"/>
<LinearLayout <LinearLayout
android:id="@+id/llStreaks" android:id="@+id/llStreaks"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:orientation="horizontal" /> android:orientation="horizontal"/>
</LinearLayout> </LinearLayout>
</LinearLayout> </LinearLayout>
</ScrollView> </ScrollView>

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

@ -14,19 +14,9 @@
<style name="MyDialogStyle" parent="android:Theme.Material.Light.Dialog"> <style name="MyDialogStyle" parent="android:Theme.Material.Light.Dialog">
</style> </style>
<style name="cardStyle"> <style name="cardStyle" parent="cardBasicStyle">
<item name="android:layout_width">match_parent</item>
<item name="android:layout_height">wrap_content</item>
<item name="android:background">@color/white</item> <item name="android:background">@color/white</item>
<item name="android:elevation">1dp</item> <item name="android:elevation">1dp</item>
<item name="android:orientation">vertical</item>
<item name="android:paddingTop">16dp</item>
<item name="android:paddingBottom">16dp</item>
<item name="android:paddingLeft">16dp</item>
<item name="android:paddingRight">4dp</item>
<item name="android:layout_marginBottom">3dp</item>
<item name="android:layout_marginLeft">3dp</item>
<item name="android:layout_marginRight">3dp</item>
</style> </style>
</resources> </resources>

@ -1,20 +1,13 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<resources> <resources>
<style name="habitsListHeaderStyle"> <style name="habitsListHeaderStyle" parent="habitsListHeaderBasicStyle">
<item name="android:layout_width">match_parent</item>
<item name="android:layout_height">wrap_content</item>
<item name="android:layout_alignParentTop">true</item>
<item name="android:background">#f0f0f0</item> <item name="android:background">#f0f0f0</item>
<item name="android:elevation">2dp</item> <item name="android:elevation">2dp</item>
<item name="android:paddingRight">4dp</item>
</style> </style>
<style name="habitsListCheckStyle"> <style name="habitsListCheckStyle" parent="habitsListCheckBasicStyle">
<item name="android:focusable">false</item>
<item name="android:minHeight">42dp</item>
<item name="android:minWidth">42dp</item>
<item name="android:gravity">center</item>
<item name="android:background">@drawable/ripple_background</item> <item name="android:background">@drawable/ripple_background</item>
</style> </style>
</resources> </resources>

@ -0,0 +1,30 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<declare-styleable name="DragSortListView">
<attr name="collapsed_height" format="dimension"/>
<attr name="drag_scroll_start" format="float"/>
<attr name="max_drag_scroll_speed" format="float"/>
<attr name="float_background_color" format="color"/>
<attr name="remove_mode">
<enum name="clickRemove" value="0"/>
<enum name="flingRemove" value="1"/>
</attr>
<attr name="track_drag_sort" format="boolean"/>
<attr name="float_alpha" format="float"/>
<attr name="slide_shuffle_speed" format="float"/>
<attr name="remove_animation_duration" format="integer"/>
<attr name="drop_animation_duration" format="integer"/>
<attr name="drag_enabled" format="boolean"/>
<attr name="sort_enabled" format="boolean"/>
<attr name="remove_enabled" format="boolean"/>
<attr name="drag_start_mode">
<enum name="onDown" value="0"/>
<enum name="onMove" value="1"/>
<enum name="onLongPress" value="2"/>
</attr>
<attr name="drag_handle_id" format="integer"/>
<attr name="fling_handle_id" format="integer"/>
<attr name="click_remove_id" format="integer"/>
<attr name="use_default_controller" format="boolean"/>
</declare-styleable>
</resources>

@ -1,54 +1,6 @@
<resources> <resources>
<!-- Default screen margins, per the Android Design guidelines. -->
<dimen name="activity_horizontal_margin">16dp</dimen> <dimen name="activity_horizontal_margin">16dp</dimen>
<dimen name="activity_vertical_margin">16dp</dimen> <dimen name="activity_vertical_margin">16dp</dimen>
<dimen name="small_square_size">20dp</dimen>
<dimen name="square_size">20dp</dimen> <dimen name="check_square_size">42dp</dimen>
<!-- Color picker -->
<dimen name="color_swatch_large">64dip</dimen>
<dimen name="color_swatch_small">48dip</dimen>
<dimen name="color_swatch_margins_large">8dip</dimen>
<dimen name="color_swatch_margins_small">4dip</dimen>
<!-- Date and time picker -->
<item name="circle_radius_multiplier" format="float" type="string">0.82</item>
<item name="circle_radius_multiplier_24HourMode" format="float" type="string">0.85</item>
<item name="selection_radius_multiplier" format="float" type="string">0.16</item>
<item name="ampm_circle_radius_multiplier" format="float" type="string">0.19</item>
<item name="numbers_radius_multiplier_normal" format="float" type="string">0.81</item>
<item name="numbers_radius_multiplier_inner" format="float" type="string">0.60</item>
<item name="numbers_radius_multiplier_outer" format="float" type="string">0.83</item>
<item name="text_size_multiplier_normal" format="float" type="string">0.17</item>
<item name="text_size_multiplier_inner" format="float" type="string">0.14</item>
<item name="text_size_multiplier_outer" format="float" type="string">0.11</item>
<dimen name="time_label_size">60sp</dimen>
<dimen name="extra_time_label_margin">-30dp</dimen>
<dimen name="ampm_label_size">16sp</dimen>
<dimen name="done_label_size">14sp</dimen>
<dimen name="ampm_left_padding">6dip</dimen>
<dimen name="separator_padding">4dip</dimen>
<dimen name="header_height">96dip</dimen>
<dimen name="footer_height">48dip</dimen>
<dimen name="minimum_margin_sides">48dip</dimen>
<dimen name="minimum_margin_top_bottom">24dip</dimen>
<dimen name="picker_dimen">270dip</dimen>
<dimen name="date_picker_component_width">270dp</dimen>
<dimen name="date_picker_header_height">30dp</dimen>
<dimen name="selected_calendar_layout_height">155dp</dimen>
<dimen name="date_picker_view_animator_height">270dp</dimen>
<dimen name="done_button_height">42dp</dimen>
<dimen name="month_list_item_header_height">50dp</dimen>
<dimen name="month_day_label_text_size">10sp</dimen>
<dimen name="day_number_select_circle_radius">16dp</dimen>
<dimen name="month_select_circle_radius">45dp</dimen>
<dimen name="selected_date_year_size">30dp</dimen>
<dimen name="selected_date_day_size">75dp</dimen>
<dimen name="selected_date_month_size">30dp</dimen>
<dimen name="date_picker_header_text_size">14dp</dimen>
<dimen name="month_label_size">16sp</dimen>
<dimen name="day_number_size">16sp</dimen>
<dimen name="year_label_height">64dp</dimen>
<dimen name="year_label_text_size">22dp</dimen>
</resources> </resources>

@ -0,0 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<dimen name="color_swatch_large">64dip</dimen>
<dimen name="color_swatch_small">48dip</dimen>
<dimen name="color_swatch_margins_large">8dip</dimen>
<dimen name="color_swatch_margins_small">4dip</dimen>
</resources>

@ -0,0 +1,42 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<item name="circle_radius_multiplier" format="float" type="string">0.82</item>
<item name="circle_radius_multiplier_24HourMode" format="float" type="string">0.85</item>
<item name="selection_radius_multiplier" format="float" type="string">0.16</item>
<item name="ampm_circle_radius_multiplier" format="float" type="string">0.19</item>
<item name="numbers_radius_multiplier_normal" format="float" type="string">0.81</item>
<item name="numbers_radius_multiplier_inner" format="float" type="string">0.60</item>
<item name="numbers_radius_multiplier_outer" format="float" type="string">0.83</item>
<item name="text_size_multiplier_normal" format="float" type="string">0.17</item>
<item name="text_size_multiplier_inner" format="float" type="string">0.14</item>
<item name="text_size_multiplier_outer" format="float" type="string">0.11</item>
<dimen name="time_label_size">60sp</dimen>
<dimen name="extra_time_label_margin">-30dp</dimen>
<dimen name="ampm_label_size">16sp</dimen>
<dimen name="done_label_size">14sp</dimen>
<dimen name="ampm_left_padding">6dip</dimen>
<dimen name="separator_padding">4dip</dimen>
<dimen name="header_height">96dip</dimen>
<dimen name="footer_height">48dip</dimen>
<dimen name="minimum_margin_sides">48dip</dimen>
<dimen name="minimum_margin_top_bottom">24dip</dimen>
<dimen name="picker_dimen">270dip</dimen>
<dimen name="date_picker_component_width">270dp</dimen>
<dimen name="date_picker_header_height">30dp</dimen>
<dimen name="selected_calendar_layout_height">155dp</dimen>
<dimen name="date_picker_view_animator_height">270dp</dimen>
<dimen name="done_button_height">42dp</dimen>
<dimen name="month_list_item_header_height">50dp</dimen>
<dimen name="month_day_label_text_size">10sp</dimen>
<dimen name="day_number_select_circle_radius">16dp</dimen>
<dimen name="month_select_circle_radius">45dp</dimen>
<dimen name="selected_date_year_size">30dp</dimen>
<dimen name="selected_date_day_size">75dp</dimen>
<dimen name="selected_date_month_size">30dp</dimen>
<dimen name="date_picker_header_text_size">14dp</dimen>
<dimen name="month_label_size">16sp</dimen>
<dimen name="day_number_size">16sp</dimen>
<dimen name="year_label_height">64dp</dimen>
<dimen name="year_label_text_size">22dp</dimen>
</resources>

@ -25,20 +25,24 @@
<item name="android:orientation">vertical</item> <item name="android:orientation">vertical</item>
</style> </style>
<style name="cardStyle"> <style name="cardBasicStyle">
<item name="android:layout_width">match_parent</item> <item name="android:layout_width">match_parent</item>
<item name="android:layout_height">wrap_content</item> <item name="android:layout_height">wrap_content</item>
<item name="android:background">@drawable/card_background</item>
<item name="android:orientation">vertical</item> <item name="android:orientation">vertical</item>
<item name="android:paddingTop">16dp</item> <item name="android:paddingTop">16dp</item>
<item name="android:paddingBottom">16dp</item> <item name="android:paddingBottom">16dp</item>
<item name="android:paddingLeft">16dp</item> <item name="android:paddingLeft">16dp</item>
<item name="android:paddingRight">4dp</item> <item name="android:paddingRight">4dp</item>
<item name="android:layout_marginBottom">1dp</item> <item name="android:layout_marginBottom">3dp</item>
<item name="android:layout_marginLeft">3dp</item> <item name="android:layout_marginLeft">3dp</item>
<item name="android:layout_marginRight">3dp</item> <item name="android:layout_marginRight">3dp</item>
</style> </style>
<style name="cardStyle" parent="cardBasicStyle">
<item name="android:background">@drawable/card_background</item>
<item name="android:layout_marginBottom">1dp</item>
</style>
<style name="cardHeaderStyle"> <style name="cardHeaderStyle">
<item name="android:layout_width">match_parent</item> <item name="android:layout_width">match_parent</item>
<item name="android:layout_height">wrap_content</item> <item name="android:layout_height">wrap_content</item>

@ -10,11 +10,15 @@
<item name="android:background">@color/windowBackground</item> <item name="android:background">@color/windowBackground</item>
</style> </style>
<style name="habitsListHeaderStyle">
<style name="habitsListHeaderBasicStyle">
<item name="android:layout_width">match_parent</item> <item name="android:layout_width">match_parent</item>
<item name="android:layout_height">wrap_content</item> <item name="android:layout_height">wrap_content</item>
<item name="android:layout_alignParentTop">true</item> <item name="android:layout_alignParentTop">true</item>
<item name="android:paddingRight">4dp</item> <item name="android:paddingRight">4dp</item>
</style>
<style name="habitsListHeaderStyle" parent="habitsListHeaderBasicStyle">
<item name="android:background">@drawable/habits_list_header_background</item> <item name="android:background">@drawable/habits_list_header_background</item>
</style> </style>
@ -58,13 +62,16 @@
<item name="android:padding">3dp</item> <item name="android:padding">3dp</item>
</style> </style>
<style name="habitsListCheckStyle"> <style name="habitsListCheckBasicStyle">
<item name="android:focusable">false</item> <item name="android:focusable">false</item>
<item name="android:minHeight">42dp</item> <item name="android:minHeight">@dimen/check_square_size</item>
<item name="android:minWidth">42dp</item> <item name="android:minWidth">@dimen/check_square_size</item>
<item name="android:gravity">center</item> <item name="android:gravity">center</item>
</style> </style>
<style name="habitsListCheckStyle" parent="habitsListCheckBasicStyle">
</style>
<style name="habitsListHeaderCheckStyle" parent="habitsListCheckStyle"> <style name="habitsListHeaderCheckStyle" parent="habitsListCheckStyle">
<item name="android:layout_width">42dp</item> <item name="android:layout_width">42dp</item>
<item name="android:layout_height">match_parent</item> <item name="android:layout_height">match_parent</item>

Loading…
Cancel
Save