Compatibility with older devices; more statistics

pull/30/head
Alinson S. Xavier 11 years ago
parent 1ff3c1c857
commit a1f05714ba

@ -6,7 +6,7 @@ android {
defaultConfig { defaultConfig {
applicationId "org.isoron.uhabits" applicationId "org.isoron.uhabits"
minSdkVersion 21 minSdkVersion 15
targetSdkVersion 22 targetSdkVersion 22
} }

@ -1,12 +1,13 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android" <manifest
package="org.isoron.uhabits" package="org.isoron.uhabits"
xmlns:android="http://schemas.android.com/apk/res/android"
android:versionCode="1" android:versionCode="1"
android:versionName="1.0"> android:versionName="1.0">
<uses-sdk <uses-sdk
android:minSdkVersion="21" android:minSdkVersion="14"
android:targetSdkVersion="22" /> android:targetSdkVersion="21"/>
<uses-permission android:name="android.permission.VIBRATE"/> <uses-permission android:name="android.permission.VIBRATE"/>
@ -16,9 +17,11 @@
android:icon="@drawable/ic_launcher" android:icon="@drawable/ic_launcher"
android:label="@string/app_name" android:label="@string/app_name"
android:theme="@style/AppTheme"> android:theme="@style/AppTheme">
<meta-data <meta-data
android:name="AA_DB_NAME" android:name="AA_DB_NAME"
android:value="uhabits.db"/> android:value="uhabits.db"/>
<meta-data <meta-data
android:name="AA_DB_VERSION" android:name="AA_DB_VERSION"
android:value="6"/> android:value="6"/>
@ -29,7 +32,6 @@
android:launchMode="singleInstance"> android:launchMode="singleInstance">
<intent-filter> <intent-filter>
<action android:name="android.intent.action.MAIN"/> <action android:name="android.intent.action.MAIN"/>
<category android:name="android.intent.category.LAUNCHER"/> <category android:name="android.intent.category.LAUNCHER"/>
</intent-filter> </intent-filter>
</activity> </activity>

@ -91,8 +91,6 @@ public class DatePickerDialog extends DialogFragment implements
private TextView mYearView; private TextView mYearView;
private DayPickerView mDayPickerView; private DayPickerView mDayPickerView;
private YearPickerView mYearPickerView; private YearPickerView mYearPickerView;
private Button mDoneButton;
private Button mClearButton;
private int mCurrentView = UNINITIALIZED; private int mCurrentView = UNINITIALIZED;
@ -245,12 +243,15 @@ public class DatePickerDialog extends DialogFragment implements
animation2.setDuration(ANIMATION_DURATION); animation2.setDuration(ANIMATION_DURATION);
mAnimator.setOutAnimation(animation2); mAnimator.setOutAnimation(animation2);
mDoneButton = (Button) view.findViewById(R.id.done); Button mDoneButton = (Button) view.findViewById(R.id.done);
mDoneButton.setOnClickListener(new OnClickListener() { mDoneButton.setOnClickListener(new OnClickListener()
{
@Override @Override
public void onClick(View v) { public void onClick(View v)
{
tryVibrate(); tryVibrate();
if (mCallBack != null) { if (mCallBack != null)
{
mCallBack.onDateSet(DatePickerDialog.this, mCalendar.get(Calendar.YEAR), mCallBack.onDateSet(DatePickerDialog.this, mCalendar.get(Calendar.YEAR),
mCalendar.get(Calendar.MONTH), mCalendar.get(Calendar.DAY_OF_MONTH)); mCalendar.get(Calendar.MONTH), mCalendar.get(Calendar.DAY_OF_MONTH));
} }
@ -258,12 +259,15 @@ public class DatePickerDialog extends DialogFragment implements
} }
}); });
mClearButton = (Button) view.findViewById(R.id.clear); Button mClearButton = (Button) view.findViewById(R.id.clear);
mClearButton.setOnClickListener(new OnClickListener() { mClearButton.setOnClickListener(new OnClickListener()
{
@Override @Override
public void onClick(View v) { public void onClick(View v)
{
tryVibrate(); tryVibrate();
if (mCallBack != null) { if (mCallBack != null)
{
mCallBack.onDateCleared(DatePickerDialog.this); mCallBack.onDateCleared(DatePickerDialog.this);
} }
dismiss(); dismiss();

@ -40,8 +40,7 @@ public class DateHelper
public static int differenceInDays(Date from, Date to) public static int differenceInDays(Date from, Date to)
{ {
long milliseconds = getStartOfDay(to.getTime()) - getStartOfDay(from.getTime()); long milliseconds = getStartOfDay(to.getTime()) - getStartOfDay(from.getTime());
int days = (int) (milliseconds / millisecondsInOneDay); return (int) (milliseconds / millisecondsInOneDay);
return days;
} }
public static String differenceInWords(Date from, Date to) public static String differenceInWords(Date from, Date to)

@ -83,6 +83,7 @@ public class MainActivity extends Activity
{ {
super.onCreate(savedInstanceState); super.onCreate(savedInstanceState);
if (android.os.Build.VERSION.SDK_INT >= 21)
getActionBar().setElevation(5); getActionBar().setElevation(5);
setContentView(R.layout.list_habits_activity); setContentView(R.layout.list_habits_activity);

@ -16,22 +16,24 @@ public class ShowHabitActivity extends Activity
{ {
public Habit habit; public Habit habit;
private ShowHabitFragment showHabitFragment;
@Override @Override
protected void onCreate(Bundle savedInstanceState) protected void onCreate(Bundle savedInstanceState)
{ {
super.onCreate(savedInstanceState); super.onCreate(savedInstanceState);
getActionBar().setElevation(5);
Uri data = getIntent().getData(); Uri data = getIntent().getData();
habit = Habit.get(ContentUris.parseId(data)); habit = Habit.get(ContentUris.parseId(data));
getActionBar().setTitle(habit.name); getActionBar().setTitle(habit.name);
if (android.os.Build.VERSION.SDK_INT >= 21)
{
getActionBar().setElevation(5);
getActionBar().setBackgroundDrawable(new ColorDrawable(habit.color)); getActionBar().setBackgroundDrawable(new ColorDrawable(habit.color));
}
setContentView(R.layout.show_habit_activity); setContentView(R.layout.show_habit_activity);
showHabitFragment = (ShowHabitFragment) getFragmentManager().findFragmentById(
R.id.fragment2);
} }
@Override @Override

@ -338,7 +338,6 @@ public class ListHabitsFragment extends Fragment implements OnSavedListener, OnI
Intent intent = new Intent(getActivity(), ShowHabitActivity.class); Intent intent = new Intent(getActivity(), ShowHabitActivity.class);
intent.setData(Uri.parse("content://org.isoron.uhabits/habit/" intent.setData(Uri.parse("content://org.isoron.uhabits/habit/"
+ habit.getId())); + habit.getId()));
getActivity().getWindow().setExitTransition(new Explode());
startActivity(intent); startActivity(intent);
} }

@ -10,6 +10,8 @@ import org.isoron.uhabits.R;
import org.isoron.uhabits.ShowHabitActivity; import org.isoron.uhabits.ShowHabitActivity;
import org.isoron.uhabits.models.Habit; import org.isoron.uhabits.models.Habit;
import org.isoron.uhabits.views.HabitHistoryView; 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;
@ -27,7 +29,6 @@ import android.widget.TextView;
public class ShowHabitFragment extends Fragment public class ShowHabitFragment extends Fragment
{ {
protected ShowHabitActivity activity; protected ShowHabitActivity activity;
private Habit habit;
@Override @Override
public void onStart() public void onStart()
@ -43,24 +44,34 @@ public class ShowHabitFragment extends Fragment
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 = activity.habit; Habit habit = activity.habit;
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);
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);
tvHistory.setTextColor(habit.color); tvHistory.setTextColor(habit.color);
tvOverview.setTextColor(habit.color); tvOverview.setTextColor(habit.color);
tvStreaks.setTextColor(habit.color);
TextView tvStrength = (TextView) view.findViewById(R.id.tvStrength); LinearLayout llOverview = (LinearLayout) view.findViewById(R.id.llOverview);
tvStrength.setText(String.format("%.2f%%", ((float) habit.getScore() / Habit.MAX_SCORE) * 100)); llOverview.addView(new RingView(activity, 200, habit.color, ((float) habit.getScore() / Habit.MAX_SCORE), "Habit strength"));
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, 40); (int) activity.getResources().getDimension(R.dimen.square_size));
llHistory.addView(hhv); llHistory.addView(hhv);
LinearLayout llStreaks = (LinearLayout) view.findViewById(R.id.llStreaks);
HabitStreakView hsv = new HabitStreakView(activity, habit,
(int) activity.getResources().getDimension(R.dimen.square_size));
llStreaks.addView(hsv);
return view; return view;
} }
} }

@ -1,14 +1,11 @@
package org.isoron.uhabits.models; package org.isoron.uhabits.models;
import java.util.List;
import org.isoron.helpers.ColorHelper;
import org.isoron.helpers.Command;
import org.isoron.helpers.DateHelper;
import org.isoron.uhabits.R;
import android.annotation.SuppressLint; import android.annotation.SuppressLint;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import android.util.Log;
import com.activeandroid.Cache;
import com.activeandroid.Model; import com.activeandroid.Model;
import com.activeandroid.annotation.Column; import com.activeandroid.annotation.Column;
import com.activeandroid.annotation.Table; import com.activeandroid.annotation.Table;
@ -18,14 +15,17 @@ import com.activeandroid.query.Select;
import com.activeandroid.query.Update; import com.activeandroid.query.Update;
import com.activeandroid.util.SQLiteUtils; import com.activeandroid.util.SQLiteUtils;
import org.isoron.helpers.ColorHelper;
import org.isoron.helpers.Command;
import org.isoron.helpers.DateHelper;
import org.isoron.uhabits.R;
import java.util.List;
@Table(name = "Habits") @Table(name = "Habits")
public class Habit extends Model public class Habit extends Model
{ {
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
* Fields *
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
public static final int HALF_STAR_CUTOFF = 5999000; public static final int HALF_STAR_CUTOFF = 5999000;
public static final int FULL_STAR_CUTOFF = 12973000; public static final int FULL_STAR_CUTOFF = 12973000;
public static final int MAX_SCORE = 19259500; public static final int MAX_SCORE = 19259500;
@ -57,130 +57,109 @@ public class Habit extends Model
@Column(name = "highlight") @Column(name = "highlight")
public Integer highlight; public Integer highlight;
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * public Habit(Habit model)
* Commands *
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
public static class CreateCommand extends Command
{
private Habit model;
private Long savedId;
public CreateCommand(Habit model)
{ {
this.model = model; copyAttributes(model);
} }
@Override public Habit()
public void execute()
{
Habit savedHabit = new Habit(model);
if(savedId == null)
{
savedHabit.save();
savedId = savedHabit.getId();
}
else
{ {
savedHabit.save(savedId); this.color = ColorHelper.palette[11];
} this.position = Habit.getCount();
this.highlight = 0;
} }
@Override public static Habit get(Long id)
public void undo()
{ {
Habit.get(savedId).delete(); return Habit.load(Habit.class, id);
} }
@Override @SuppressLint("DefaultLocale")
public Integer getExecuteStringId() public static void updateId(long oldId, long newId)
{ {
return R.string.toast_habit_created; SQLiteUtils.execSql(String.format(
"update Habits set Id = %d where Id = %d", newId, oldId));
} }
@Override protected static From select()
public Integer getUndoStringId()
{ {
return R.string.toast_habit_deleted; return new Select().from(Habit.class).orderBy("position");
}
} }
public class EditCommand extends Command public static int getCount()
{
private Habit original;
private Habit modified;
private long savedId;
private boolean hasIntervalChanged;
public EditCommand(Habit modified)
{ {
this.savedId = getId(); return select().count();
this.modified = new Habit(modified);
this.original = new Habit(Habit.this);
hasIntervalChanged = (this.original.freq_den != this.modified.freq_den
|| this.original.freq_num != this.modified.freq_num);
} }
public void execute() public static Habit getByPosition(int position)
{ {
Habit habit = Habit.get(savedId); return select().offset(position).executeSingle();
habit.copyAttributes(modified);
habit.save();
if(hasIntervalChanged)
habit.deleteScoresNewerThan(0);
} }
public void undo() public static java.util.List<Habit> getHabits()
{ {
Habit habit = Habit.get(savedId); return select().execute();
habit.copyAttributes(original);
habit.save();
if(hasIntervalChanged)
habit.deleteScoresNewerThan(0);
} }
public Integer getExecuteStringId() public static java.util.List<Habit> getHighlightedHabits()
{ {
return R.string.toast_habit_changed; return select().where("highlight = 1").orderBy("reminder_hour desc, reminder_min desc")
.execute();
} }
public Integer getUndoStringId() public static java.util.List<Habit> getHabitsWithReminder()
{ {
return R.string.toast_habit_changed_back; return select().where("reminder_hour is not null").execute();
}
} }
public class ToggleRepetitionCommand extends Command public static void reorder(int from, int to)
{ {
private Long offset; if (from == to)
return;
public ToggleRepetitionCommand(long offset) Habit h = Habit.getByPosition(from);
{ if (to < from)
this.offset = offset; new Update(Habit.class).set("position = position + 1")
.where("position >= ? and position < ?", to, from)
.execute();
else
new Update(Habit.class).set("position = position - 1")
.where("position > ? and position <= ?", from, to)
.execute();
h.position = to;
h.save();
} }
@Override public static void rebuildOrder()
public void execute()
{ {
toggleRepetition(offset); List<Habit> habits = select().execute();
int i = 0;
for (Habit h : habits)
{
h.position = i++;
h.save();
}
} }
@Override public static void roundTimestamps()
public void undo()
{ {
execute(); List<Repetition> reps = new Select().from(Repetition.class).execute();
for (Repetition r : reps)
{
r.timestamp = DateHelper.getStartOfDay(r.timestamp);
r.save();
} }
} }
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * public static int getStarCount()
* Accessors *
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
public Habit(Habit model)
{ {
copyAttributes(model); String args[] = {};
return SQLiteUtils.intQuery(
"select count(*) from (select score, max(timestamp) from " +
"score group by habit) as scores where scores.score >= "
+ Integer.toString(12973000), args);
} }
public void copyAttributes(Habit model) public void copyAttributes(Habit model)
@ -196,17 +175,6 @@ public class Habit extends Model
this.highlight = model.highlight; this.highlight = model.highlight;
} }
public Habit()
{
this.color = ColorHelper.palette[11];
this.position = Habit.getCount();
this.highlight = 0;
}
public static Habit get(Long id)
{
return Habit.load(Habit.class, id);
}
public void save(Long id) public void save(Long id)
{ {
@ -214,49 +182,6 @@ public class Habit extends Model
Habit.updateId(getId(), id); Habit.updateId(getId(), id);
} }
@SuppressLint("DefaultLocale")
public static void updateId(long oldId, long newId)
{
SQLiteUtils.execSql(String.format(
"update Habits set Id = %d where Id = %d", newId, oldId));
}
protected static From select()
{
return new Select().from(Habit.class).orderBy("position");
}
public static int getCount()
{
return select().count();
}
public static Habit getByPosition(int position)
{
return select().offset(position).executeSingle();
}
public static java.util.List<Habit> getHabits()
{
return select().execute();
}
public static java.util.List<Habit> getHighlightedHabits()
{
return select().where("highlight = 1").orderBy("reminder_hour desc, reminder_min desc")
.execute();
}
public static java.util.List<Habit> getHabitsWithReminder()
{
return select().where("reminder_hour is not null").execute();
}
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
* Repetitions *
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
protected From selectReps() protected From selectReps()
{ {
return new Select().from(Repetition.class).where("habit = ?", getId()) return new Select().from(Repetition.class).where("habit = ?", getId())
@ -339,8 +264,7 @@ public class Habit extends Model
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;
@ -356,10 +280,6 @@ public class Habit extends Model
toggleRepetition(DateHelper.getStartOfToday()); toggleRepetition(DateHelper.getStartOfToday());
} }
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
* Scoring *
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
public Score getNewestScore() public Score getNewestScore()
{ {
return new Select().from(Score.class).where("habit = ?", getId()) return new Select().from(Score.class).where("habit = ?", getId())
@ -391,8 +311,7 @@ public class Habit extends Model
return 0; 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;
@ -411,7 +330,8 @@ public class Habit extends Model
s.habit = this; s.habit = this;
s.timestamp = beginningTime + day * i; s.timestamp = beginningTime + day * i;
s.score = (int) (lastScore * multiplier); s.score = (int) (lastScore * multiplier);
if(reps[reps.length-i-1] == 2) { if (reps[reps.length - i - 1] == 2)
{
s.score += 1000000; s.score += 1000000;
s.score = Math.min(s.score, 19259500); s.score = Math.min(s.score, 19259500);
} }
@ -423,62 +343,151 @@ public class Habit extends Model
return lastScore; return lastScore;
} }
public long[] getStreaks()
{
String query = "create temporary table T as select distinct r1.habit as habit, r1.timestamp as time,\n" +
" (select count(*) from repetitions r2 where r1.habit = r2.habit and\n" +
" (r1.timestamp = r2.timestamp - 24*60*60*1000 or\n" +
" r1.timestamp = r2.timestamp + 24*60*60*1000)) as neighbors\n" +
"from repetitions r1 where r1.habit = ?";
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * String query2 =
* Ordering * "select time from T, (select 0 union select 1) where neighbors = 0 union all\n" +
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ "select time from T where neighbors = 1 order by time;";
public static void reorder(int from, int to) String args[] = {getId().toString()};
SQLiteDatabase db = Cache.openDatabase();
db.beginTransaction();
db.execSQL(query, args);
Cursor cursor = db.rawQuery(query2, null);
long streaks[] = new long[cursor.getCount()];
int current = 0;
Log.d("Streaks", String.format("%d rows", cursor.getCount()));
if (cursor.moveToFirst())
{ {
if(from == to) do
return; {
streaks[current++] = cursor.getLong(0);
} while (cursor.moveToNext());
}
Habit h = Habit.getByPosition(from); db.endTransaction();
if(to < from) return streaks;
new Update(Habit.class).set("position = position + 1") }
.where("position >= ? and position < ?", to, from)
.execute();
else
new Update(Habit.class).set("position = position - 1")
.where("position > ? and position <= ?", from, to)
.execute();
h.position = to; public static class CreateCommand extends Command
h.save(); {
private Habit model;
private Long savedId;
public CreateCommand(Habit model)
{
this.model = model;
} }
public static void rebuildOrder() @Override
public void execute()
{ {
List<Habit> habits = select().execute(); Habit savedHabit = new Habit(model);
int i = 0; if (savedId == null)
for (Habit h : habits)
{ {
h.position = i++; savedHabit.save();
h.save(); savedId = savedHabit.getId();
} else
{
savedHabit.save(savedId);
} }
} }
public static void roundTimestamps() @Override
public void undo()
{ {
List<Repetition> reps = new Select().from(Repetition.class).execute(); Habit.get(savedId).delete();
for (Repetition r : reps) }
@Override
public Integer getExecuteStringId()
{ {
r.timestamp = DateHelper.getStartOfDay(r.timestamp); return R.string.toast_habit_created;
r.save();
} }
@Override
public Integer getUndoStringId()
{
return R.string.toast_habit_deleted;
} }
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * }
* Statistics *
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
public static int getStarCount() public class EditCommand extends Command
{ {
String args[] = {}; private Habit original;
return SQLiteUtils.intQuery( private Habit modified;
"select count(*) from (select score, max(timestamp) from " + private long savedId;
"score group by habit) as scores where scores.score >= " private boolean hasIntervalChanged;
+ Integer.toString(12973000), args);
public EditCommand(Habit modified)
{
this.savedId = getId();
this.modified = new Habit(modified);
this.original = new Habit(Habit.this);
hasIntervalChanged = (this.original.freq_den != this.modified.freq_den
|| this.original.freq_num != this.modified.freq_num);
}
public void execute()
{
Habit habit = Habit.get(savedId);
habit.copyAttributes(modified);
habit.save();
if (hasIntervalChanged)
habit.deleteScoresNewerThan(0);
}
public void undo()
{
Habit habit = Habit.get(savedId);
habit.copyAttributes(original);
habit.save();
if (hasIntervalChanged)
habit.deleteScoresNewerThan(0);
}
public Integer getExecuteStringId()
{
return R.string.toast_habit_changed;
}
public Integer getUndoStringId()
{
return R.string.toast_habit_changed_back;
}
}
public class ToggleRepetitionCommand extends Command
{
private Long offset;
public ToggleRepetitionCommand(long offset)
{
this.offset = offset;
}
@Override
public void execute()
{
toggleRepetition(offset);
}
@Override
public void undo()
{
execute();
}
} }
} }

@ -30,7 +30,6 @@ public class HabitHistoryView extends View
private Context context; private Context context;
private Paint pSquareBg, pSquareFg, pTextHeader; private Paint pSquareBg, pSquareFg, pTextHeader;
private int width, height;
private int squareSize, squareSpacing; private int squareSize, squareSpacing;
private int nColumns, offsetWeeks; private int nColumns, offsetWeeks;
@ -78,8 +77,6 @@ public class HabitHistoryView extends View
@Override @Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) protected void onSizeChanged(int w, int h, int oldw, int oldh)
{ {
width = w;
height = h;
nColumns = (w / squareSize) - 1; nColumns = (w / squareSize) - 1;
fetchReps(); fetchReps();
} }

@ -0,0 +1,128 @@
package org.isoron.uhabits.views;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Rect;
import android.view.View;
import org.isoron.helpers.ColorHelper;
import org.isoron.helpers.DateHelper;
import org.isoron.uhabits.models.Habit;
import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.GregorianCalendar;
public class HabitStreakView extends View
{
private Habit habit;
private int columnWidth, columnHeight, nColumns;
private Paint pText, pBar;
private long streaks[];
private long streakStart[], streakEnd[], streakLength[];
private long maxStreakLength;
private int barHeaderHeight;
private int[] colors;
public HabitStreakView(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.CENTER);
pText.setTextSize(columnWidth * 0.5f);
pText.setAntiAlias(true);
pBar = new Paint();
pBar.setTextAlign(Paint.Align.CENTER);
pBar.setTextSize(columnWidth * 0.5f);
pBar.setAntiAlias(true);
columnHeight = 8 * columnWidth;
barHeaderHeight = columnWidth;
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);
fetchStreaks();
}
private void fetchStreaks()
{
streaks = habit.getStreaks();
streakStart = new long[streaks.length / 2];
streakEnd = new long[streaks.length / 2];
streakLength = new long[streaks.length / 2];
for(int i=0; i<streaks.length / 2; i++)
{
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
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;
}
@Override
protected void onDraw(Canvas canvas)
{
super.onDraw(canvas);
float lineHeight = pText.getFontSpacing();
float barHeaderOffset = lineHeight * 0.4f;
int start = Math.max(0, streakStart.length - nColumns);
SimpleDateFormat dfMonth = new SimpleDateFormat("MMM");
String previousMonth = "";
for (int offset = 0; offset < nColumns && start+offset < streakStart.length; offset++)
{
String month = dfMonth.format(streakStart[start+offset]);
long l = streakLength[offset+start];
double lRelative = ((double) l) / maxStreakLength;
pBar.setColor(colors[(int) Math.floor(lRelative*3)]);
int height = (int) (columnHeight * lRelative);
Rect r = new Rect(0,0,columnWidth-2, height);
r.offset(offset * columnWidth, barHeaderHeight + columnHeight - height);
canvas.drawRect(r, pBar);
canvas.drawText(Long.toString(streakLength[offset+start]), r.centerX(), r.top - barHeaderOffset, pBar);
if(!month.equals(previousMonth))
canvas.drawText(month, r.centerX(), r.bottom + lineHeight * 1.2f, pText);
previousMonth = month;
}
}
}

@ -0,0 +1,67 @@
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.view.View;
public class RingView extends View
{
private int size;
private int color;
private float perc;
private Paint pRing;
private float lineHeight;
private String label;
public RingView(Context context, int size, int color, float perc, String label)
{
super(context);
this.size = size;
this.color = color;
this.perc = perc;
pRing = new Paint();
pRing.setColor(color);
pRing.setAntiAlias(true);
pRing.setTextAlign(Paint.Align.CENTER);
pRing.setTextSize(size * 0.15f);
this.label = label;
lineHeight = pRing.getFontSpacing();
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec)
{
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
setMeasuredDimension(size, size + (int) (2*lineHeight));
}
@Override
protected void onDraw(Canvas canvas)
{
super.onDraw(canvas);
float thickness = size * 0.15f;
pRing.setColor(color);
RectF r = new RectF(0, 0, size, size);
canvas.drawArc(r, -90, 360 * perc, true, pRing);
pRing.setColor(Color.rgb(230, 230, 230));
canvas.drawArc(r, 360 * perc - 90 + 2, 360 * (1-perc) - 4, true, pRing);
pRing.setColor(Color.WHITE);
r.inset(thickness, thickness);
canvas.drawArc(r, -90, 360, true, pRing);
pRing.setColor(Color.GRAY);
canvas.drawText(String.format("%.2f%%", perc*100), r.centerX(), r.centerY()+lineHeight/3, pRing);
canvas.drawText(label, size/2, size + lineHeight * 1.2f, pRing);
}
}

@ -0,0 +1,39 @@
<?xml version="1.0" encoding="utf-8"?>
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
<item
android:top="0dp"
android:bottom="0dp"
android:left="0dp"
android:right="0dp">
<shape>
<solid android:color="#d6d6d6" />
</shape>
</item>
<item
android:top="0dp"
android:bottom="1dp"
android:left="0dp"
android:right="0dp">
<shape>
<solid android:color="#c0c0c0" />
</shape>
</item>
<item
android:top="0dp"
android:bottom="1.5dp"
android:left="0dp"
android:right="0dp">
<shape>
<solid android:color="#d6d6d6"/>
</shape>
</item>
<item
android:top="0.5dp"
android:bottom="1.5dp"
android:left="0.5dp"
android:right="0.5dp">
<shape>
<solid android:color="@color/white"/>
</shape>
</item>
</layer-list>

@ -0,0 +1,26 @@
<?xml version="1.0" encoding="UTF-8"?>
<layer-list xmlns:android="http://schemas.android.com/apk/res/android" >
<item android:top="40dp">
<shape android:shape="rectangle" >
<gradient
android:startColor="#30000000"
android:endColor="#00000000"
android:angle="270"/>
</shape>
</item>
<item android:top="21dp" android:bottom="2dp">
<shape android:shape="rectangle" >
<gradient
android:angle="270"
android:endColor="#ccffffff"
android:startColor="#ffffff" />
</shape>
</item>
<item android:bottom="21dp">
<shape android:shape="rectangle" >
<solid android:color="#ffffff" />
</shape>
</item>
</layer-list>

@ -26,7 +26,7 @@
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_weight="1" android:layout_weight="1"
android:minHeight="48dp" android:minHeight="48dp"
android:text="Clear" android:text="@string/clear"
android:textSize="@dimen/done_label_size" android:textSize="@dimen/done_label_size"
android:textColor="@color/done_text_color" /> android:textColor="@color/done_text_color" />

@ -29,7 +29,7 @@
<EditText <EditText
android:id="@+id/input_description" android:id="@+id/input_description"
android:hint="Description" android:hint="@string/description"
style="@style/dialogFormInputMultiline" /> style="@style/dialogFormInputMultiline" />
<LinearLayout <LinearLayout
@ -39,29 +39,29 @@
<TextView <TextView
android:id="@+id/textView1" android:id="@+id/textView1"
style="@style/dialogFormLabel" style="@style/dialogFormLabel"
android:text="Repeat " /> android:text="@string/repeat" />
<EditText <EditText
android:id="@+id/input_freq_num" android:id="@+id/input_freq_num"
android:text="3" android:text="@string/default_freq_num"
style="@style/dialogFormInputSmallNumber" /> style="@style/dialogFormInputSmallNumber" />
<TextView <TextView
android:id="@+id/textView3" android:id="@+id/textView3"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:text=" times every " /> android:text="@string/times_every" />
<EditText <EditText
android:id="@+id/input_freq_den" android:id="@+id/input_freq_den"
android:text="7" android:text="@string/default_freq_den"
style="@style/dialogFormInputSmallNumber" /> style="@style/dialogFormInputSmallNumber" />
<TextView <TextView
android:id="@+id/textView5" android:id="@+id/textView5"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:text=" days" /> android:text="@string/days" />
</LinearLayout> </LinearLayout>
<LinearLayout <LinearLayout
@ -71,7 +71,7 @@
<TextView <TextView
android:id="@+id/TextView2" android:id="@+id/TextView2"
style="@style/dialogFormLabel" style="@style/dialogFormLabel"
android:text="Reminder" /> android:text="@string/reminder" />
<TextView <TextView
android:id="@+id/input_reminder_time" android:id="@+id/input_reminder_time"
@ -85,7 +85,6 @@
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:gravity="end" android:gravity="end"
android:onClick="onClick"
android:paddingEnd="16dp"> android:paddingEnd="16dp">
<Button <Button
@ -93,13 +92,13 @@
style="?android:attr/buttonBarButtonStyle" style="?android:attr/buttonBarButtonStyle"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:text="Discard" /> android:text="@string/discard" />
<Button <Button
android:id="@+id/buttonSave" android:id="@+id/buttonSave"
style="?android:attr/buttonBarButtonStyle" style="?android:attr/buttonBarButtonStyle"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:text="Save" /> android:text="@string/save" />
</LinearLayout> </LinearLayout>
</LinearLayout> </LinearLayout>

@ -14,7 +14,8 @@
dslv:float_alpha="0.5" dslv:float_alpha="0.5"
dslv:sort_enabled="true" dslv:sort_enabled="true"
dslv:track_drag_sort="false" dslv:track_drag_sort="false"
dslv:use_default_controller="true" /> dslv:use_default_controller="true"
/>
<LinearLayout style="@style/habitsListHeaderStyle"> <LinearLayout style="@style/habitsListHeaderStyle">

@ -2,43 +2,46 @@
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:fillViewport="true"
android:background="@color/windowBackground">
<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 style="@style/cardStyle"
android:id="@+id/llOverview">
<TextView <TextView
android:id="@+id/tvOverview" android:id="@+id/tvOverview"
style="@style/cardHeaderStyle" style="@style/cardHeaderStyle"
android:text="Overview" /> android:text="@string/overview" />
<LinearLayout style="@style/cardRowStyle"> </LinearLayout>
<TextView <LinearLayout style="@style/cardStyle">
style="@style/cardLabelStyle"
android:text="Habit strength" />
<TextView <TextView
android:id="@+id/tvStrength" android:id="@+id/tvHistory"
android:layout_width="wrap_content" style="@style/cardHeaderStyle"
android:layout_height="wrap_content" android:text="@string/history" />
android:text="" />
</LinearLayout>
<LinearLayout
android:id="@+id/llHistory"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal" />
</LinearLayout> </LinearLayout>
<LinearLayout style="@style/cardStyle"> <LinearLayout style="@style/cardStyle">
<TextView <TextView
android:id="@+id/tvHistory" android:id="@+id/tvStreaks"
style="@style/cardHeaderStyle" style="@style/cardHeaderStyle"
android:text="History" /> android:text="@string/streaks" />
<LinearLayout <LinearLayout
android:id="@+id/llHistory" 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" />

@ -14,4 +14,19 @@
<style name="MyDialogStyle" parent="android:Theme.Material.Light.Dialog"> <style name="MyDialogStyle" parent="android:Theme.Material.Light.Dialog">
</style> </style>
<style name="cardStyle">
<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: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>
</resources> </resources>

@ -0,0 +1,20 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<style name="habitsListHeaderStyle">
<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:elevation">2dp</item>
<item name="android:paddingRight">4dp</item>
</style>
<style name="habitsListCheckStyle">
<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>
</style>
</resources>

@ -4,6 +4,8 @@
<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="square_size">20dp</dimen>
<!-- Color picker --> <!-- Color picker -->
<dimen name="color_swatch_large">64dip</dimen> <dimen name="color_swatch_large">64dip</dimen>
<dimen name="color_swatch_small">48dip</dimen> <dimen name="color_swatch_small">48dip</dimen>

@ -42,5 +42,19 @@
<string name="title_activity_show_habit">ShowHabitActivity</string> <string name="title_activity_show_habit">ShowHabitActivity</string>
<string name="hello_world">Hello world!</string> <string name="hello_world">Hello world!</string>
<string name="overview">Overview</string>
<string name="habit_strength">Habit strength</string>
<string name="history">History</string>
<string name="clear">Clear</string>
<string name="description">Description</string>
<string name="repeat">Repeat</string>
<string name="default_freq_num">3</string>
<string name="times_every">times every</string>
<string name="default_freq_den">7</string>
<string name="days">days</string>
<string name="reminder">Reminder</string>
<string name="discard">Discard</string>
<string name="save">Save</string>
<string name="streaks">Streaks</string>
</resources> </resources>

@ -28,14 +28,13 @@
<style name="cardStyle"> <style name="cardStyle">
<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">@color/white</item> <item name="android:background">@drawable/card_background</item>
<item name="android:elevation">1dp</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">3dp</item> <item name="android:layout_marginBottom">1dp</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>

@ -14,9 +14,8 @@
<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:background">#f0f0f0</item>
<item name="android:elevation">2dp</item>
<item name="android:paddingRight">4dp</item> <item name="android:paddingRight">4dp</item>
<item name="android:background">@drawable/habits_list_header_background</item>
</style> </style>
<style name="habitsListStarStyle"> <style name="habitsListStarStyle">
@ -64,13 +63,12 @@
<item name="android:minHeight">42dp</item> <item name="android:minHeight">42dp</item>
<item name="android:minWidth">42dp</item> <item name="android:minWidth">42dp</item>
<item name="android:gravity">center</item> <item name="android:gravity">center</item>
<item name="android:background">@drawable/ripple_background</item>
</style> </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>
<item name="android:background">#f0f0f0</item> <item name="android:background">@color/transparent</item>
<item name="android:focusable">false</item> <item name="android:focusable">false</item>
<item name="android:textSize">10sp</item> <item name="android:textSize">10sp</item>
<item name="android:textColor">#606060</item> <item name="android:textColor">#606060</item>

Loading…
Cancel
Save