Compare commits

..

40 Commits

Author SHA1 Message Date
a9b8b7e1e2 Bump version 2016-02-24 12:57:25 -05:00
966e7ccd8a Do not show hint on first run 2016-02-24 11:39:47 -05:00
d58be08fee Minor string change 2016-02-24 11:29:01 -05:00
eb057b51d3 Show one hint per day 2016-02-24 11:28:51 -05:00
8c88e7fd5b Make it more strict to get a star 2016-02-24 08:51:27 -05:00
756d3aa48f Return after case statement 2016-02-24 08:38:58 -05:00
1a4dbd9cba Show alarm only on certain days of the week 2016-02-24 08:22:00 -05:00
c68176ad09 Add timestamp to notifications 2016-02-24 05:50:34 -05:00
b0ccf3464f Implement habit deletion 2016-02-24 04:46:18 -05:00
dcaff3d1b8 Update strings 2016-02-23 19:42:14 -05:00
693e0143b5 Implement multiple selection and drag on press-and-hold 2016-02-23 19:16:33 -05:00
cdc80bdbbd Move DSLV to libs/ 2016-02-22 21:04:35 -05:00
948bca1150 Add Android Wear screenshot 2016-02-22 07:56:28 -05:00
68c4b26031 Implement hints 2016-02-22 07:56:12 -05:00
56bed8206e Material design colors 2016-02-22 07:54:28 -05:00
8e2f06c211 Bump database version to clean all database cache 2016-02-21 22:53:39 -05:00
08f2fe84d7 Fix habit history view 2016-02-21 17:32:31 -05:00
0e5764cf5d Refactor custom views 2016-02-21 16:58:18 -05:00
e6a5751959 Update app name 2016-02-21 11:30:00 -05:00
eefc738ee2 Improve habit positioning and reordering 2016-02-20 21:45:40 -05:00
322650345c Add Chinese translation 2016-02-20 19:35:24 -05:00
5c77a44611 Add Portuguese translation 2016-02-20 18:32:59 -05:00
28900d0981 Internationalize more string 2016-02-20 18:32:48 -05:00
77281e11f5 Refactor app introduction 2016-02-20 16:13:26 -05:00
e06b0e79cc Rename package 2016-02-20 16:06:51 -05:00
d6f31b8775 Remove dead code 2016-02-20 16:00:24 -05:00
d862c85874 Remove debug information 2016-02-20 16:00:12 -05:00
5bd1b70cd9 Remove dead code 2016-02-20 15:59:12 -05:00
191b9b9c1f Refactor ListHabitsFragment 2016-02-20 15:46:19 -05:00
c36cdb1e42 Refactor EditHabitsFragment 2016-02-20 13:53:41 -05:00
4c53bd3763 Version bump 2016-02-20 13:53:27 -05:00
7984670a3c Fix empty message 2016-02-20 07:52:02 -05:00
a58a95f125 Update app name 2016-02-20 07:42:48 -05:00
378b4cb84b Update app name 2016-02-20 07:42:48 -05:00
ec32f3b681 Update .gitignore 2016-02-20 07:30:02 -05:00
ef5f1a8f8c Remove art files 2016-02-20 07:25:49 -05:00
46f152d5d2 Fix width of time picker 2016-02-20 07:24:40 -05:00
f3a096b660 Merge branch 'master' of github.com:iSoron/uhabits 2016-02-20 07:19:28 -05:00
c8de4e13f9 Reformat code 2016-02-20 07:16:40 -05:00
1ee3fc79f0 Choose 12/24h according to system settings 2016-02-20 07:09:12 -05:00
111 changed files with 3571 additions and 11932 deletions

2
.gitignore vendored
View File

@@ -31,3 +31,5 @@ Thumbs.db
.gradle
build/
*.iml
art/

3
.gitmodules vendored Normal file
View File

@@ -0,0 +1,3 @@
[submodule "libs/drag-sort-listview"]
path = libs/drag-sort-listview
url = https://github.com/iSoron/drag-sort-listview.git

View File

@@ -1,7 +1,6 @@
# Habits Tracker
Habits Tracker is a simple Android app that helps you create and maintain good habits. Detailed graphs and statistics show you how your habits improved over time. It is completely ad-free and open source, with no intrusive permissions. Join the open beta at [Google Play Store](https://play.google.com/apps/testing/org.isoron.uhabits).
# Loop Habit Tracker
Loop is a simple Android app that helps you create and maintain good habits. Detailed graphs and statistics show you how your habits improved over time. It is completely ad-free and open source, with no intrusive permissions. Join the open beta at [Google Play Store](https://play.google.com/apps/testing/org.isoron.uhabits).
## Features

View File

@@ -25,7 +25,7 @@ android {
dependencies {
compile 'com.android.support:support-v4:23.1.1'
compile 'com.github.paolorotolo:appintro:3.4.0'
compile project(':libs:drag-sort-listview:library')
compile files('libs/ActiveAndroid.jar')
}

View File

@@ -2,8 +2,8 @@
<manifest
package="org.isoron.uhabits"
xmlns:android="http://schemas.android.com/apk/res/android"
android:versionCode="4"
android:versionName="1.0.0">
android:versionCode="6"
android:versionName="1.1.0">
<uses-permission
android:name="android.permission.VIBRATE"/>
@@ -11,7 +11,7 @@
<application
android:name="com.activeandroid.app.Application"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:label="@string/main_activity_title"
android:theme="@style/AppBaseTheme"
android:backupAgent=".HabitsBackupAgent">
@@ -21,7 +21,7 @@
<meta-data
android:name="AA_DB_VERSION"
android:value="9"/>
android:value="11"/>
<meta-data
android:name="com.google.android.backup.api_key"
@@ -29,8 +29,9 @@
<activity
android:name=".MainActivity"
android:label="@string/app_name">
<intent-filter>
android:label="@string/main_activity_title">
<intent-filter
android:label="@string/app_name">
<action android:name="android.intent.action.MAIN"/>
<category android:name="android.intent.category.LAUNCHER"/>
</intent-filter>
@@ -50,7 +51,7 @@
<activity
android:name=".SettingsActivity"
android:label="Settings"
android:label="@string/settings"
android:parentActivityName=".MainActivity">
<meta-data
android:name="android.support.PARENT_ACTIVITY"

View File

@@ -0,0 +1,3 @@
delete from Score;
delete from Streak;
delete from Checkmarks;

View File

@@ -0,0 +1 @@
alter table habits add column reminder_days integer not null default 127;

View File

@@ -1,471 +0,0 @@
package com.mobeta.android.dslv;
import android.graphics.Point;
import android.view.GestureDetector;
import android.view.HapticFeedbackConstants;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewConfiguration;
import android.widget.AdapterView;
/**
* Class that starts and stops item drags on a {@link DragSortListView}
* based on touch gestures. This class also inherits from
* {@link SimpleFloatViewManager}, which provides basic float View
* creation.
*
* An instance of this class is meant to be passed to the methods
* {@link DragSortListView#setTouchListener()} and
* {@link DragSortListView#setFloatViewManager()} of your
* {@link DragSortListView} instance.
*/
public class DragSortController extends SimpleFloatViewManager implements View.OnTouchListener, GestureDetector.OnGestureListener {
/**
* Drag init mode enum.
*/
public static final int ON_DOWN = 0;
public static final int ON_DRAG = 1;
public static final int ON_LONG_PRESS = 2;
private int mDragInitMode = ON_DOWN;
private boolean mSortEnabled = true;
/**
* Remove mode enum.
*/
public static final int CLICK_REMOVE = 0;
public static final int FLING_REMOVE = 1;
/**
* The current remove mode.
*/
private int mRemoveMode;
private boolean mRemoveEnabled = false;
private boolean mIsRemoving = false;
private GestureDetector mDetector;
private GestureDetector mFlingRemoveDetector;
private int mTouchSlop;
public static final int MISS = -1;
private int mHitPos = MISS;
private int mFlingHitPos = MISS;
private int mClickRemoveHitPos = MISS;
private int[] mTempLoc = new int[2];
private int mItemX;
private int mItemY;
private int mCurrX;
private int mCurrY;
private boolean mDragging = false;
private float mFlingSpeed = 500f;
private int mDragHandleId;
private int mClickRemoveId;
private int mFlingHandleId;
private boolean mCanDrag;
private DragSortListView mDslv;
private int mPositionX;
/**
* Calls {@link #DragSortController(DragSortListView, int)} with a
* 0 drag handle id, FLING_RIGHT_REMOVE remove mode,
* and ON_DOWN drag init. By default, sorting is enabled, and
* removal is disabled.
*
* @param dslv The DSLV instance
*/
public DragSortController(DragSortListView dslv) {
this(dslv, 0, ON_DOWN, FLING_REMOVE);
}
public DragSortController(DragSortListView dslv, int dragHandleId, int dragInitMode, int removeMode) {
this(dslv, dragHandleId, dragInitMode, removeMode, 0);
}
public DragSortController(DragSortListView dslv, int dragHandleId, int dragInitMode, int removeMode, int clickRemoveId) {
this(dslv, dragHandleId, dragInitMode, removeMode, clickRemoveId, 0);
}
/**
* By default, sorting is enabled, and removal is disabled.
*
* @param dslv The DSLV instance
* @param dragHandleId The resource id of the View that represents
* the drag handle in a list item.
*/
public DragSortController(DragSortListView dslv, int dragHandleId, int dragInitMode,
int removeMode, int clickRemoveId, int flingHandleId) {
super(dslv);
mDslv = dslv;
mDetector = new GestureDetector(dslv.getContext(), this);
mFlingRemoveDetector = new GestureDetector(dslv.getContext(), mFlingRemoveListener);
mFlingRemoveDetector.setIsLongpressEnabled(false);
mTouchSlop = ViewConfiguration.get(dslv.getContext()).getScaledTouchSlop();
mDragHandleId = dragHandleId;
mClickRemoveId = clickRemoveId;
mFlingHandleId = flingHandleId;
setRemoveMode(removeMode);
setDragInitMode(dragInitMode);
}
public int getDragInitMode() {
return mDragInitMode;
}
/**
* Set how a drag is initiated. Needs to be one of
* {@link ON_DOWN}, {@link ON_DRAG}, or {@link ON_LONG_PRESS}.
*
* @param mode The drag init mode.
*/
public void setDragInitMode(int mode) {
mDragInitMode = mode;
}
/**
* Enable/Disable list item sorting. Disabling is useful if only item
* removal is desired. Prevents drags in the vertical direction.
*
* @param enabled Set <code>true</code> to enable list
* item sorting.
*/
public void setSortEnabled(boolean enabled) {
mSortEnabled = enabled;
}
public boolean isSortEnabled() {
return mSortEnabled;
}
/**
* One of {@link CLICK_REMOVE}, {@link FLING_RIGHT_REMOVE},
* {@link FLING_LEFT_REMOVE},
* {@link SLIDE_RIGHT_REMOVE}, or {@link SLIDE_LEFT_REMOVE}.
*/
public void setRemoveMode(int mode) {
mRemoveMode = mode;
}
public int getRemoveMode() {
return mRemoveMode;
}
/**
* Enable/Disable item removal without affecting remove mode.
*/
public void setRemoveEnabled(boolean enabled) {
mRemoveEnabled = enabled;
}
public boolean isRemoveEnabled() {
return mRemoveEnabled;
}
/**
* Set the resource id for the View that represents the drag
* handle in a list item.
*
* @param id An android resource id.
*/
public void setDragHandleId(int id) {
mDragHandleId = id;
}
/**
* Set the resource id for the View that represents the fling
* handle in a list item.
*
* @param id An android resource id.
*/
public void setFlingHandleId(int id) {
mFlingHandleId = id;
}
/**
* Set the resource id for the View that represents click
* removal button.
*
* @param id An android resource id.
*/
public void setClickRemoveId(int id) {
mClickRemoveId = id;
}
/**
* Sets flags to restrict certain motions of the floating View
* based on DragSortController settings (such as remove mode).
* Starts the drag on the DragSortListView.
*
* @param position The list item position (includes headers).
* @param deltaX Touch x-coord minus left edge of floating View.
* @param deltaY Touch y-coord minus top edge of floating View.
*
* @return True if drag started, false otherwise.
*/
public boolean startDrag(int position, int deltaX, int deltaY) {
int dragFlags = 0;
if (mSortEnabled && !mIsRemoving) {
dragFlags |= DragSortListView.DRAG_POS_Y | DragSortListView.DRAG_NEG_Y;
}
if (mRemoveEnabled && mIsRemoving) {
dragFlags |= DragSortListView.DRAG_POS_X;
dragFlags |= DragSortListView.DRAG_NEG_X;
}
mDragging = mDslv.startDrag(position - mDslv.getHeaderViewsCount(), dragFlags, deltaX,
deltaY);
return mDragging;
}
@Override
public boolean onTouch(View v, MotionEvent ev) {
if (!mDslv.isDragEnabled() || mDslv.listViewIntercepted()) {
return false;
}
mDetector.onTouchEvent(ev);
if (mRemoveEnabled && mDragging && mRemoveMode == FLING_REMOVE) {
mFlingRemoveDetector.onTouchEvent(ev);
}
int action = ev.getAction() & MotionEvent.ACTION_MASK;
switch (action) {
case MotionEvent.ACTION_DOWN:
mCurrX = (int) ev.getX();
mCurrY = (int) ev.getY();
break;
case MotionEvent.ACTION_UP:
if (mRemoveEnabled && mIsRemoving) {
int x = mPositionX >= 0 ? mPositionX : -mPositionX;
int removePoint = mDslv.getWidth() / 2;
if (x > removePoint) {
mDslv.stopDragWithVelocity(true, 0);
}
}
case MotionEvent.ACTION_CANCEL:
mIsRemoving = false;
mDragging = false;
break;
}
return false;
}
/**
* Overrides to provide fading when slide removal is enabled.
*/
@Override
public void onDragFloatView(View floatView, Point position, Point touch) {
if (mRemoveEnabled && mIsRemoving) {
mPositionX = position.x;
}
}
/**
* Get the position to start dragging based on the ACTION_DOWN
* MotionEvent. This function simply calls
* {@link #dragHandleHitPosition(MotionEvent)}. Override
* to change drag handle behavior;
* this function is called internally when an ACTION_DOWN
* event is detected.
*
* @param ev The ACTION_DOWN MotionEvent.
*
* @return The list position to drag if a drag-init gesture is
* detected; MISS if unsuccessful.
*/
public int startDragPosition(MotionEvent ev) {
return dragHandleHitPosition(ev);
}
public int startFlingPosition(MotionEvent ev) {
return mRemoveMode == FLING_REMOVE ? flingHandleHitPosition(ev) : MISS;
}
/**
* Checks for the touch of an item's drag handle (specified by
* {@link #setDragHandleId(int)}), and returns that item's position
* if a drag handle touch was detected.
*
* @param ev The ACTION_DOWN MotionEvent.
* @return The list position of the item whose drag handle was
* touched; MISS if unsuccessful.
*/
public int dragHandleHitPosition(MotionEvent ev) {
return viewIdHitPosition(ev, mDragHandleId);
}
public int flingHandleHitPosition(MotionEvent ev) {
return viewIdHitPosition(ev, mFlingHandleId);
}
public int viewIdHitPosition(MotionEvent ev, int id) {
final int x = (int) ev.getX();
final int y = (int) ev.getY();
int touchPos = mDslv.pointToPosition(x, y); // includes headers/footers
final int numHeaders = mDslv.getHeaderViewsCount();
final int numFooters = mDslv.getFooterViewsCount();
final int count = mDslv.getCount();
// Log.d("mobeta", "touch down on position " + itemnum);
// We're only interested if the touch was on an
// item that's not a header or footer.
if (touchPos != AdapterView.INVALID_POSITION && touchPos >= numHeaders
&& touchPos < (count - numFooters)) {
final View item = mDslv.getChildAt(touchPos - mDslv.getFirstVisiblePosition());
final int rawX = (int) ev.getRawX();
final int rawY = (int) ev.getRawY();
View dragBox = id == 0 ? item : (View) item.findViewById(id);
if (dragBox != null) {
dragBox.getLocationOnScreen(mTempLoc);
if (rawX > mTempLoc[0] && rawY > mTempLoc[1] &&
rawX < mTempLoc[0] + dragBox.getWidth() &&
rawY < mTempLoc[1] + dragBox.getHeight()) {
mItemX = item.getLeft();
mItemY = item.getTop();
return touchPos;
}
}
}
return MISS;
}
@Override
public boolean onDown(MotionEvent ev) {
if (mRemoveEnabled && mRemoveMode == CLICK_REMOVE) {
mClickRemoveHitPos = viewIdHitPosition(ev, mClickRemoveId);
}
mHitPos = startDragPosition(ev);
if (mHitPos != MISS && mDragInitMode == ON_DOWN) {
startDrag(mHitPos, (int) ev.getX() - mItemX, (int) ev.getY() - mItemY);
}
mIsRemoving = false;
mCanDrag = true;
mPositionX = 0;
mFlingHitPos = startFlingPosition(ev);
return true;
}
@Override
public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) {
if(e1 == null) return false;
if(e2 == null) return false;
final int x1 = (int) e1.getX();
final int y1 = (int) e1.getY();
final int x2 = (int) e2.getX();
final int y2 = (int) e2.getY();
final int deltaX = x2 - mItemX;
final int deltaY = y2 - mItemY;
if (mCanDrag && !mDragging && (mHitPos != MISS || mFlingHitPos != MISS)) {
if (mHitPos != MISS) {
if (mDragInitMode == ON_DRAG && Math.abs(y2 - y1) > mTouchSlop && mSortEnabled) {
startDrag(mHitPos, deltaX, deltaY);
}
else if (mDragInitMode != ON_DOWN && Math.abs(x2 - x1) > mTouchSlop && mRemoveEnabled)
{
mIsRemoving = true;
startDrag(mFlingHitPos, deltaX, deltaY);
}
} else if (mFlingHitPos != MISS) {
if (Math.abs(x2 - x1) > mTouchSlop && mRemoveEnabled) {
mIsRemoving = true;
startDrag(mFlingHitPos, deltaX, deltaY);
} else if (Math.abs(y2 - y1) > mTouchSlop) {
mCanDrag = false; // if started to scroll the list then
// don't allow sorting nor fling-removing
}
}
}
// return whatever
return false;
}
@Override
public void onLongPress(MotionEvent e) {
// Log.d("mobeta", "lift listener long pressed");
if (mHitPos != MISS && mDragInitMode == ON_LONG_PRESS) {
mDslv.performHapticFeedback(HapticFeedbackConstants.LONG_PRESS);
startDrag(mHitPos, mCurrX - mItemX, mCurrY - mItemY);
}
}
// complete the OnGestureListener interface
@Override
public final boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) {
return false;
}
// complete the OnGestureListener interface
@Override
public boolean onSingleTapUp(MotionEvent ev) {
if (mRemoveEnabled && mRemoveMode == CLICK_REMOVE) {
if (mClickRemoveHitPos != MISS) {
mDslv.removeItem(mClickRemoveHitPos - mDslv.getHeaderViewsCount());
}
}
return true;
}
// complete the OnGestureListener interface
@Override
public void onShowPress(MotionEvent ev) {
// do nothing
}
private GestureDetector.OnGestureListener mFlingRemoveListener =
new GestureDetector.SimpleOnGestureListener() {
@Override
public final boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX,
float velocityY) {
// Log.d("mobeta", "on fling remove called");
if (mRemoveEnabled && mIsRemoving) {
int w = mDslv.getWidth();
int minPos = w / 5;
if (velocityX > mFlingSpeed) {
if (mPositionX > -minPos) {
mDslv.stopDragWithVelocity(true, velocityX);
}
} else if (velocityX < -mFlingSpeed) {
if (mPositionX < minPos) {
mDslv.stopDragWithVelocity(true, velocityX);
}
}
mIsRemoving = false;
}
return false;
}
};
}

View File

@@ -1,241 +0,0 @@
package com.mobeta.android.dslv;
import java.util.ArrayList;
import android.content.Context;
import android.database.Cursor;
import android.util.SparseIntArray;
import android.view.View;
import android.view.ViewGroup;
import android.support.v4.widget.CursorAdapter;
/**
* A subclass of {@link android.widget.CursorAdapter} that provides
* reordering of the elements in the Cursor based on completed
* drag-sort operations. The reordering is a simple mapping of
* list positions into Cursor positions (the Cursor is unchanged).
* To persist changes made by drag-sorts, one can retrieve the
* mapping with the {@link #getCursorPositions()} method, which
* returns the reordered list of Cursor positions.
*
* An instance of this class is passed
* to {@link DragSortListView#setAdapter(ListAdapter)} and, since
* this class implements the {@link DragSortListView.DragSortListener}
* interface, it is automatically set as the DragSortListener for
* the DragSortListView instance.
*/
public abstract class DragSortCursorAdapter extends CursorAdapter implements DragSortListView.DragSortListener {
public static final int REMOVED = -1;
/**
* Key is ListView position, value is Cursor position
*/
private SparseIntArray mListMapping = new SparseIntArray();
private ArrayList<Integer> mRemovedCursorPositions = new ArrayList<Integer>();
public DragSortCursorAdapter(Context context, Cursor c) {
super(context, c);
}
public DragSortCursorAdapter(Context context, Cursor c, boolean autoRequery) {
super(context, c, autoRequery);
}
public DragSortCursorAdapter(Context context, Cursor c, int flags) {
super(context, c, flags);
}
/**
* Swaps Cursor and clears list-Cursor mapping.
*
* @see android.widget.CursorAdapter#swapCursor(android.database.Cursor)
*/
@Override
public Cursor swapCursor(Cursor newCursor) {
Cursor old = super.swapCursor(newCursor);
resetMappings();
return old;
}
/**
* Changes Cursor and clears list-Cursor mapping.
*
* @see android.widget.CursorAdapter#changeCursor(android.database.Cursor)
*/
@Override
public void changeCursor(Cursor cursor) {
super.changeCursor(cursor);
resetMappings();
}
/**
* Resets list-cursor mapping.
*/
public void reset() {
resetMappings();
notifyDataSetChanged();
}
private void resetMappings() {
mListMapping.clear();
mRemovedCursorPositions.clear();
}
@Override
public Object getItem(int position) {
return super.getItem(mListMapping.get(position, position));
}
@Override
public long getItemId(int position) {
return super.getItemId(mListMapping.get(position, position));
}
@Override
public View getDropDownView(int position, View convertView, ViewGroup parent) {
return super.getDropDownView(mListMapping.get(position, position), convertView, parent);
}
@Override
public View getView(int position, View convertView, ViewGroup parent) {
return super.getView(mListMapping.get(position, position), convertView, parent);
}
/**
* On drop, this updates the mapping between Cursor positions
* and ListView positions. The Cursor is unchanged. Retrieve
* the current mapping with {@link getCursorPositions()}.
*
* @see DragSortListView.DropListener#drop(int, int)
*/
@Override
public void drop(int from, int to) {
if (from != to) {
int cursorFrom = mListMapping.get(from, from);
if (from > to) {
for (int i = from; i > to; --i) {
mListMapping.put(i, mListMapping.get(i - 1, i - 1));
}
} else {
for (int i = from; i < to; ++i) {
mListMapping.put(i, mListMapping.get(i + 1, i + 1));
}
}
mListMapping.put(to, cursorFrom);
cleanMapping();
notifyDataSetChanged();
}
}
/**
* On remove, this updates the mapping between Cursor positions
* and ListView positions. The Cursor is unchanged. Retrieve
* the current mapping with {@link getCursorPositions()}.
*
* @see DragSortListView.RemoveListener#remove(int)
*/
@Override
public void remove(int which) {
int cursorPos = mListMapping.get(which, which);
if (!mRemovedCursorPositions.contains(cursorPos)) {
mRemovedCursorPositions.add(cursorPos);
}
int newCount = getCount();
for (int i = which; i < newCount; ++i) {
mListMapping.put(i, mListMapping.get(i + 1, i + 1));
}
mListMapping.delete(newCount);
cleanMapping();
notifyDataSetChanged();
}
/**
* Does nothing. Just completes DragSortListener interface.
*/
@Override
public void drag(int from, int to) {
// do nothing
}
/**
* Remove unnecessary mappings from sparse array.
*/
private void cleanMapping() {
ArrayList<Integer> toRemove = new ArrayList<Integer>();
int size = mListMapping.size();
for (int i = 0; i < size; ++i) {
if (mListMapping.keyAt(i) == mListMapping.valueAt(i)) {
toRemove.add(mListMapping.keyAt(i));
}
}
size = toRemove.size();
for (int i = 0; i < size; ++i) {
mListMapping.delete(toRemove.get(i));
}
}
@Override
public int getCount() {
return super.getCount() - mRemovedCursorPositions.size();
}
/**
* Get the Cursor position mapped to by the provided list position
* (given all previously handled drag-sort
* operations).
*
* @param position List position
*
* @return The mapped-to Cursor position
*/
public int getCursorPosition(int position) {
return mListMapping.get(position, position);
}
/**
* Get the current order of Cursor positions presented by the
* list.
*/
public ArrayList<Integer> getCursorPositions() {
ArrayList<Integer> result = new ArrayList<Integer>();
for (int i = 0; i < getCount(); ++i) {
result.add(mListMapping.get(i, i));
}
return result;
}
/**
* Get the list position mapped to by the provided Cursor position.
* If the provided Cursor position has been removed by a drag-sort,
* this returns {@link #REMOVED}.
*
* @param cursorPosition A Cursor position
* @return The mapped-to list position or REMOVED
*/
public int getListPosition(int cursorPosition) {
if (mRemovedCursorPositions.contains(cursorPosition)) {
return REMOVED;
}
int index = mListMapping.indexOfValue(cursorPosition);
if (index < 0) {
return cursorPosition;
} else {
return mListMapping.keyAt(index);
}
}
}

View File

@@ -1,100 +0,0 @@
package com.mobeta.android.dslv;
import android.content.Context;
import android.view.Gravity;
import android.view.View;
import android.view.View.MeasureSpec;
import android.view.ViewGroup;
import android.widget.AbsListView;
import android.util.Log;
/**
* Lightweight ViewGroup that wraps list items obtained from user's
* ListAdapter. ItemView expects a single child that has a definite
* height (i.e. the child's layout height is not MATCH_PARENT).
* The width of
* ItemView will always match the width of its child (that is,
* the width MeasureSpec given to ItemView is passed directly
* to the child, and the ItemView measured width is set to the
* child's measured width). The height of ItemView can be anything;
* the
*
*
* The purpose of this class is to optimize slide
* shuffle animations.
*/
public class DragSortItemView extends ViewGroup {
private int mGravity = Gravity.TOP;
public DragSortItemView(Context context) {
super(context);
// always init with standard ListView layout params
setLayoutParams(new AbsListView.LayoutParams(
ViewGroup.LayoutParams.FILL_PARENT,
ViewGroup.LayoutParams.WRAP_CONTENT));
//setClipChildren(true);
}
public void setGravity(int gravity) {
mGravity = gravity;
}
public int getGravity() {
return mGravity;
}
@Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
final View child = getChildAt(0);
if (child == null) {
return;
}
if (mGravity == Gravity.TOP) {
child.layout(0, 0, getMeasuredWidth(), child.getMeasuredHeight());
} else {
child.layout(0, getMeasuredHeight() - child.getMeasuredHeight(), getMeasuredWidth(), getMeasuredHeight());
}
}
/**
*
*/
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int height = MeasureSpec.getSize(heightMeasureSpec);
int width = MeasureSpec.getSize(widthMeasureSpec);
int heightMode = MeasureSpec.getMode(heightMeasureSpec);
final View child = getChildAt(0);
if (child == null) {
setMeasuredDimension(0, width);
return;
}
if (child.isLayoutRequested()) {
// Always let child be as tall as it wants.
measureChild(child, widthMeasureSpec,
MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED));
}
if (heightMode == MeasureSpec.UNSPECIFIED) {
ViewGroup.LayoutParams lp = getLayoutParams();
if (lp.height > 0) {
height = lp.height;
} else {
height = child.getMeasuredHeight();
}
}
setMeasuredDimension(width, height);
}
}

View File

@@ -1,55 +0,0 @@
package com.mobeta.android.dslv;
import android.content.Context;
import android.view.Gravity;
import android.view.View;
import android.view.View.MeasureSpec;
import android.view.ViewGroup;
import android.widget.AbsListView;
import android.widget.Checkable;
import android.util.Log;
/**
* Lightweight ViewGroup that wraps list items obtained from user's
* ListAdapter. ItemView expects a single child that has a definite
* height (i.e. the child's layout height is not MATCH_PARENT).
* The width of
* ItemView will always match the width of its child (that is,
* the width MeasureSpec given to ItemView is passed directly
* to the child, and the ItemView measured width is set to the
* child's measured width). The height of ItemView can be anything;
* the
*
*
* The purpose of this class is to optimize slide
* shuffle animations.
*/
public class DragSortItemViewCheckable extends DragSortItemView implements Checkable {
public DragSortItemViewCheckable(Context context) {
super(context);
}
@Override
public boolean isChecked() {
View child = getChildAt(0);
if (child instanceof Checkable)
return ((Checkable) child).isChecked();
else
return false;
}
@Override
public void setChecked(boolean checked) {
View child = getChildAt(0);
if (child instanceof Checkable)
((Checkable) child).setChecked(checked);
}
@Override
public void toggle() {
View child = getChildAt(0);
if (child instanceof Checkable)
((Checkable) child).toggle();
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -1,133 +0,0 @@
/*
* Copyright (C) 2011 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.mobeta.android.dslv;
import android.content.Context;
import android.database.Cursor;
import android.view.View;
import android.view.ViewGroup;
import android.view.LayoutInflater;
// taken from v4 rev. 10 ResourceCursorAdapter.java
/**
* Static library support version of the framework's {@link android.widget.ResourceCursorAdapter}.
* Used to write apps that run on platforms prior to Android 3.0. When running
* on Android 3.0 or above, this implementation is still used; it does not try
* to switch to the framework's implementation. See the framework SDK
* documentation for a class overview.
*/
public abstract class ResourceDragSortCursorAdapter extends DragSortCursorAdapter {
private int mLayout;
private int mDropDownLayout;
private LayoutInflater mInflater;
/**
* Constructor the enables auto-requery.
*
* @deprecated This option is discouraged, as it results in Cursor queries
* being performed on the application's UI thread and thus can cause poor
* responsiveness or even Application Not Responding errors. As an alternative,
* use {@link android.app.LoaderManager} with a {@link android.content.CursorLoader}.
*
* @param context The context where the ListView associated with this adapter is running
* @param layout resource identifier of a layout file that defines the views
* for this list item. Unless you override them later, this will
* define both the item views and the drop down views.
*/
@Deprecated
public ResourceDragSortCursorAdapter(Context context, int layout, Cursor c) {
super(context, c);
mLayout = mDropDownLayout = layout;
mInflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
}
/**
* Constructor with default behavior as per
* {@link CursorAdapter#CursorAdapter(Context, Cursor, boolean)}; it is recommended
* you not use this, but instead {@link #ResourceCursorAdapter(Context, int, Cursor, int)}.
* When using this constructor, {@link #FLAG_REGISTER_CONTENT_OBSERVER}
* will always be set.
*
* @param context The context where the ListView associated with this adapter is running
* @param layout resource identifier of a layout file that defines the views
* for this list item. Unless you override them later, this will
* define both the item views and the drop down views.
* @param c The cursor from which to get the data.
* @param autoRequery If true the adapter will call requery() on the
* cursor whenever it changes so the most recent
* data is always displayed. Using true here is discouraged.
*/
public ResourceDragSortCursorAdapter(Context context, int layout, Cursor c, boolean autoRequery) {
super(context, c, autoRequery);
mLayout = mDropDownLayout = layout;
mInflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
}
/**
* Standard constructor.
*
* @param context The context where the ListView associated with this adapter is running
* @param layout Resource identifier of a layout file that defines the views
* for this list item. Unless you override them later, this will
* define both the item views and the drop down views.
* @param c The cursor from which to get the data.
* @param flags Flags used to determine the behavior of the adapter,
* as per {@link CursorAdapter#CursorAdapter(Context, Cursor, int)}.
*/
public ResourceDragSortCursorAdapter(Context context, int layout, Cursor c, int flags) {
super(context, c, flags);
mLayout = mDropDownLayout = layout;
mInflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
}
/**
* Inflates view(s) from the specified XML file.
*
* @see android.widget.CursorAdapter#newView(android.content.Context,
* android.database.Cursor, ViewGroup)
*/
@Override
public View newView(Context context, Cursor cursor, ViewGroup parent) {
return mInflater.inflate(mLayout, parent, false);
}
@Override
public View newDropDownView(Context context, Cursor cursor, ViewGroup parent) {
return mInflater.inflate(mDropDownLayout, parent, false);
}
/**
* <p>Sets the layout resource of the item views.</p>
*
* @param layout the layout resources used to create item views
*/
public void setViewResource(int layout) {
mLayout = layout;
}
/**
* <p>Sets the layout resource of the drop down views.</p>
*
* @param dropDownLayout the layout resources used to create drop down views
*/
public void setDropDownViewResource(int dropDownLayout) {
mDropDownLayout = dropDownLayout;
}
}

View File

@@ -1,422 +0,0 @@
/*
* Copyright (C) 2006 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.mobeta.android.dslv;
import android.content.Context;
import android.database.Cursor;
import android.net.Uri;
import android.view.View;
import android.widget.TextView;
import android.widget.ImageView;
// taken from sdk/sources/android-16/android/widget/SimpleCursorAdapter.java
/**
* An easy adapter to map columns from a cursor to TextViews or ImageViews
* defined in an XML file. You can specify which columns you want, which
* views you want to display the columns, and the XML file that defines
* the appearance of these views.
*
* Binding occurs in two phases. First, if a
* {@link android.widget.SimpleCursorAdapter.ViewBinder} is available,
* {@link ViewBinder#setViewValue(android.view.View, android.database.Cursor, int)}
* is invoked. If the returned value is true, binding has occured. If the
* returned value is false and the view to bind is a TextView,
* {@link #setViewText(TextView, String)} is invoked. If the returned value
* is false and the view to bind is an ImageView,
* {@link #setViewImage(ImageView, String)} is invoked. If no appropriate
* binding can be found, an {@link IllegalStateException} is thrown.
*
* If this adapter is used with filtering, for instance in an
* {@link android.widget.AutoCompleteTextView}, you can use the
* {@link android.widget.SimpleCursorAdapter.CursorToStringConverter} and the
* {@link android.widget.FilterQueryProvider} interfaces
* to get control over the filtering process. You can refer to
* {@link #convertToString(android.database.Cursor)} and
* {@link #runQueryOnBackgroundThread(CharSequence)} for more information.
*/
public class SimpleDragSortCursorAdapter extends ResourceDragSortCursorAdapter {
/**
* A list of columns containing the data to bind to the UI.
* This field should be made private, so it is hidden from the SDK.
* {@hide}
*/
protected int[] mFrom;
/**
* A list of View ids representing the views to which the data must be bound.
* This field should be made private, so it is hidden from the SDK.
* {@hide}
*/
protected int[] mTo;
private int mStringConversionColumn = -1;
private CursorToStringConverter mCursorToStringConverter;
private ViewBinder mViewBinder;
String[] mOriginalFrom;
/**
* Constructor the enables auto-requery.
*
* @deprecated This option is discouraged, as it results in Cursor queries
* being performed on the application's UI thread and thus can cause poor
* responsiveness or even Application Not Responding errors. As an alternative,
* use {@link android.app.LoaderManager} with a {@link android.content.CursorLoader}.
*/
@Deprecated
public SimpleDragSortCursorAdapter(Context context, int layout, Cursor c, String[] from, int[] to) {
super(context, layout, c);
mTo = to;
mOriginalFrom = from;
findColumns(c, from);
}
/**
* Standard constructor.
*
* @param context The context where the ListView associated with this
* SimpleListItemFactory is running
* @param layout resource identifier of a layout file that defines the views
* for this list item. The layout file should include at least
* those named views defined in "to"
* @param c The database cursor. Can be null if the cursor is not available yet.
* @param from A list of column names representing the data to bind to the UI. Can be null
* if the cursor is not available yet.
* @param to The views that should display column in the "from" parameter.
* These should all be TextViews. The first N views in this list
* are given the values of the first N columns in the from
* parameter. Can be null if the cursor is not available yet.
* @param flags Flags used to determine the behavior of the adapter,
* as per {@link CursorAdapter#CursorAdapter(Context, Cursor, int)}.
*/
public SimpleDragSortCursorAdapter(Context context, int layout,
Cursor c, String[] from, int[] to, int flags) {
super(context, layout, c, flags);
mTo = to;
mOriginalFrom = from;
findColumns(c, from);
}
/**
* Binds all of the field names passed into the "to" parameter of the
* constructor with their corresponding cursor columns as specified in the
* "from" parameter.
*
* Binding occurs in two phases. First, if a
* {@link android.widget.SimpleCursorAdapter.ViewBinder} is available,
* {@link ViewBinder#setViewValue(android.view.View, android.database.Cursor, int)}
* is invoked. If the returned value is true, binding has occured. If the
* returned value is false and the view to bind is a TextView,
* {@link #setViewText(TextView, String)} is invoked. If the returned value is
* false and the view to bind is an ImageView,
* {@link #setViewImage(ImageView, String)} is invoked. If no appropriate
* binding can be found, an {@link IllegalStateException} is thrown.
*
* @throws IllegalStateException if binding cannot occur
*
* @see android.widget.CursorAdapter#bindView(android.view.View,
* android.content.Context, android.database.Cursor)
* @see #getViewBinder()
* @see #setViewBinder(android.widget.SimpleCursorAdapter.ViewBinder)
* @see #setViewImage(ImageView, String)
* @see #setViewText(TextView, String)
*/
@Override
public void bindView(View view, Context context, Cursor cursor) {
final ViewBinder binder = mViewBinder;
final int count = mTo.length;
final int[] from = mFrom;
final int[] to = mTo;
for (int i = 0; i < count; i++) {
final View v = view.findViewById(to[i]);
if (v != null) {
boolean bound = false;
if (binder != null) {
bound = binder.setViewValue(v, cursor, from[i]);
}
if (!bound) {
String text = cursor.getString(from[i]);
if (text == null) {
text = "";
}
if (v instanceof TextView) {
setViewText((TextView) v, text);
} else if (v instanceof ImageView) {
setViewImage((ImageView) v, text);
} else {
throw new IllegalStateException(v.getClass().getName() + " is not a " +
" view that can be bounds by this SimpleCursorAdapter");
}
}
}
}
}
/**
* Returns the {@link ViewBinder} used to bind data to views.
*
* @return a ViewBinder or null if the binder does not exist
*
* @see #bindView(android.view.View, android.content.Context, android.database.Cursor)
* @see #setViewBinder(android.widget.SimpleCursorAdapter.ViewBinder)
*/
public ViewBinder getViewBinder() {
return mViewBinder;
}
/**
* Sets the binder used to bind data to views.
*
* @param viewBinder the binder used to bind data to views, can be null to
* remove the existing binder
*
* @see #bindView(android.view.View, android.content.Context, android.database.Cursor)
* @see #getViewBinder()
*/
public void setViewBinder(ViewBinder viewBinder) {
mViewBinder = viewBinder;
}
/**
* Called by bindView() to set the image for an ImageView but only if
* there is no existing ViewBinder or if the existing ViewBinder cannot
* handle binding to an ImageView.
*
* By default, the value will be treated as an image resource. If the
* value cannot be used as an image resource, the value is used as an
* image Uri.
*
* Intended to be overridden by Adapters that need to filter strings
* retrieved from the database.
*
* @param v ImageView to receive an image
* @param value the value retrieved from the cursor
*/
public void setViewImage(ImageView v, String value) {
try {
v.setImageResource(Integer.parseInt(value));
} catch (NumberFormatException nfe) {
v.setImageURI(Uri.parse(value));
}
}
/**
* Called by bindView() to set the text for a TextView but only if
* there is no existing ViewBinder or if the existing ViewBinder cannot
* handle binding to a TextView.
*
* Intended to be overridden by Adapters that need to filter strings
* retrieved from the database.
*
* @param v TextView to receive text
* @param text the text to be set for the TextView
*/
public void setViewText(TextView v, String text) {
v.setText(text);
}
/**
* Return the index of the column used to get a String representation
* of the Cursor.
*
* @return a valid index in the current Cursor or -1
*
* @see android.widget.CursorAdapter#convertToString(android.database.Cursor)
* @see #setStringConversionColumn(int)
* @see #setCursorToStringConverter(android.widget.SimpleCursorAdapter.CursorToStringConverter)
* @see #getCursorToStringConverter()
*/
public int getStringConversionColumn() {
return mStringConversionColumn;
}
/**
* Defines the index of the column in the Cursor used to get a String
* representation of that Cursor. The column is used to convert the
* Cursor to a String only when the current CursorToStringConverter
* is null.
*
* @param stringConversionColumn a valid index in the current Cursor or -1 to use the default
* conversion mechanism
*
* @see android.widget.CursorAdapter#convertToString(android.database.Cursor)
* @see #getStringConversionColumn()
* @see #setCursorToStringConverter(android.widget.SimpleCursorAdapter.CursorToStringConverter)
* @see #getCursorToStringConverter()
*/
public void setStringConversionColumn(int stringConversionColumn) {
mStringConversionColumn = stringConversionColumn;
}
/**
* Returns the converter used to convert the filtering Cursor
* into a String.
*
* @return null if the converter does not exist or an instance of
* {@link android.widget.SimpleCursorAdapter.CursorToStringConverter}
*
* @see #setCursorToStringConverter(android.widget.SimpleCursorAdapter.CursorToStringConverter)
* @see #getStringConversionColumn()
* @see #setStringConversionColumn(int)
* @see android.widget.CursorAdapter#convertToString(android.database.Cursor)
*/
public CursorToStringConverter getCursorToStringConverter() {
return mCursorToStringConverter;
}
/**
* Sets the converter used to convert the filtering Cursor
* into a String.
*
* @param cursorToStringConverter the Cursor to String converter, or
* null to remove the converter
*
* @see #setCursorToStringConverter(android.widget.SimpleCursorAdapter.CursorToStringConverter)
* @see #getStringConversionColumn()
* @see #setStringConversionColumn(int)
* @see android.widget.CursorAdapter#convertToString(android.database.Cursor)
*/
public void setCursorToStringConverter(CursorToStringConverter cursorToStringConverter) {
mCursorToStringConverter = cursorToStringConverter;
}
/**
* Returns a CharSequence representation of the specified Cursor as defined
* by the current CursorToStringConverter. If no CursorToStringConverter
* has been set, the String conversion column is used instead. If the
* conversion column is -1, the returned String is empty if the cursor
* is null or Cursor.toString().
*
* @param cursor the Cursor to convert to a CharSequence
*
* @return a non-null CharSequence representing the cursor
*/
@Override
public CharSequence convertToString(Cursor cursor) {
if (mCursorToStringConverter != null) {
return mCursorToStringConverter.convertToString(cursor);
} else if (mStringConversionColumn > -1) {
return cursor.getString(mStringConversionColumn);
}
return super.convertToString(cursor);
}
/**
* Create a map from an array of strings to an array of column-id integers in cursor c.
* If c is null, the array will be discarded.
*
* @param c the cursor to find the columns from
* @param from the Strings naming the columns of interest
*/
private void findColumns(Cursor c, String[] from) {
if (c != null) {
int i;
int count = from.length;
if (mFrom == null || mFrom.length != count) {
mFrom = new int[count];
}
for (i = 0; i < count; i++) {
mFrom[i] = c.getColumnIndexOrThrow(from[i]);
}
} else {
mFrom = null;
}
}
@Override
public Cursor swapCursor(Cursor c) {
// super.swapCursor() will notify observers before we have
// a valid mapping, make sure we have a mapping before this
// happens
findColumns(c, mOriginalFrom);
return super.swapCursor(c);
}
/**
* Change the cursor and change the column-to-view mappings at the same time.
*
* @param c The database cursor. Can be null if the cursor is not available yet.
* @param from A list of column names representing the data to bind to the UI. Can be null
* if the cursor is not available yet.
* @param to The views that should display column in the "from" parameter.
* These should all be TextViews. The first N views in this list
* are given the values of the first N columns in the from
* parameter. Can be null if the cursor is not available yet.
*/
public void changeCursorAndColumns(Cursor c, String[] from, int[] to) {
mOriginalFrom = from;
mTo = to;
// super.changeCursor() will notify observers before we have
// a valid mapping, make sure we have a mapping before this
// happens
findColumns(c, mOriginalFrom);
super.changeCursor(c);
}
/**
* This class can be used by external clients of SimpleCursorAdapter
* to bind values fom the Cursor to views.
*
* You should use this class to bind values from the Cursor to views
* that are not directly supported by SimpleCursorAdapter or to
* change the way binding occurs for views supported by
* SimpleCursorAdapter.
*
* @see SimpleCursorAdapter#bindView(android.view.View, android.content.Context, android.database.Cursor)
* @see SimpleCursorAdapter#setViewImage(ImageView, String)
* @see SimpleCursorAdapter#setViewText(TextView, String)
*/
public static interface ViewBinder {
/**
* Binds the Cursor column defined by the specified index to the specified view.
*
* When binding is handled by this ViewBinder, this method must return true.
* If this method returns false, SimpleCursorAdapter will attempts to handle
* the binding on its own.
*
* @param view the view to bind the data to
* @param cursor the cursor to get the data from
* @param columnIndex the column at which the data can be found in the cursor
*
* @return true if the data was bound to the view, false otherwise
*/
boolean setViewValue(View view, Cursor cursor, int columnIndex);
}
/**
* This class can be used by external clients of SimpleCursorAdapter
* to define how the Cursor should be converted to a String.
*
* @see android.widget.CursorAdapter#convertToString(android.database.Cursor)
*/
public static interface CursorToStringConverter {
/**
* Returns a CharSequence representing the specified Cursor.
*
* @param cursor the cursor for which a CharSequence representation
* is requested
*
* @return a non-null CharSequence representing the cursor
*/
CharSequence convertToString(Cursor cursor);
}
}

View File

@@ -1,89 +0,0 @@
package com.mobeta.android.dslv;
import android.graphics.Bitmap;
import android.graphics.Point;
import android.graphics.Color;
import android.widget.ListView;
import android.widget.ImageView;
import android.view.View;
import android.view.ViewGroup;
import android.util.Log;
/**
* Simple implementation of the FloatViewManager class. Uses list
* items as they appear in the ListView to create the floating View.
*/
public class SimpleFloatViewManager implements DragSortListView.FloatViewManager {
private Bitmap mFloatBitmap;
private ImageView mImageView;
private int mFloatBGColor = Color.BLACK;
private ListView mListView;
public SimpleFloatViewManager(ListView lv) {
mListView = lv;
}
public void setBackgroundColor(int color) {
mFloatBGColor = color;
}
/**
* This simple implementation creates a Bitmap copy of the
* list item currently shown at ListView <code>position</code>.
*/
@Override
public View onCreateFloatView(int position) {
// Guaranteed that this will not be null? I think so. Nope, got
// a NullPointerException once...
View v = mListView.getChildAt(position + mListView.getHeaderViewsCount() - mListView.getFirstVisiblePosition());
if (v == null) {
return null;
}
v.setPressed(false);
// Create a copy of the drawing cache so that it does not get
// recycled by the framework when the list tries to clean up memory
//v.setDrawingCacheQuality(View.DRAWING_CACHE_QUALITY_HIGH);
v.setDrawingCacheEnabled(true);
mFloatBitmap = Bitmap.createBitmap(v.getDrawingCache());
v.setDrawingCacheEnabled(false);
if (mImageView == null) {
mImageView = new ImageView(mListView.getContext());
}
mImageView.setBackgroundColor(mFloatBGColor);
mImageView.setPadding(0, 0, 0, 0);
mImageView.setImageBitmap(mFloatBitmap);
mImageView.setLayoutParams(new ViewGroup.LayoutParams(v.getWidth(), v.getHeight()));
return mImageView;
}
/**
* This does nothing
*/
@Override
public void onDragFloatView(View floatView, Point position, Point touch) {
// do nothing
}
/**
* Removes the Bitmap from the ImageView created in
* onCreateFloatView() and tells the system to recycle it.
*/
@Override
public void onDestroyFloatView(View floatView) {
((ImageView) floatView).setImageDrawable(null);
mFloatBitmap.recycle();
mFloatBitmap = null;
}
}

View File

@@ -21,40 +21,40 @@ import android.graphics.Color;
public class ColorHelper
{
public static final int[] palette =
{
Color.parseColor("#D32F2F"), // red
{
Color.parseColor("#D32F2F"), // red
Color.parseColor("#E64A19"), // orange
Color.parseColor("#F9A825"), // yellow
Color.parseColor("#F9A825"), // yellow
Color.parseColor("#AFB42B"), // light green
Color.parseColor("#388E3C"), // dark green
Color.parseColor("#388E3C"), // dark green
Color.parseColor("#00897B"), // teal
Color.parseColor("#00ACC1"), // cyan
Color.parseColor("#00ACC1"), // cyan
Color.parseColor("#039BE5"), // blue
Color.parseColor("#5E35B1"), // deep purple
Color.parseColor("#5E35B1"), // deep purple
Color.parseColor("#8E24AA"), // purple
Color.parseColor("#D81B60"), // pink
Color.parseColor("#D81B60"), // pink
Color.parseColor("#303030"), // dark grey
Color.parseColor("#aaaaaa") // light grey
};
Color.parseColor("#aaaaaa") // light grey
};
public static int mixColors(int color1, int color2, float amount)
{
final byte ALPHA_CHANNEL = 24;
final byte RED_CHANNEL = 16;
final byte GREEN_CHANNEL = 8;
final byte BLUE_CHANNEL = 0;
{
final byte ALPHA_CHANNEL = 24;
final byte RED_CHANNEL = 16;
final byte GREEN_CHANNEL = 8;
final byte BLUE_CHANNEL = 0;
final float inverseAmount = 1.0f - amount;
final float inverseAmount = 1.0f - amount;
int a = ((int) (((float) (color1 >> ALPHA_CHANNEL & 0xff) * amount) +
((float) (color2 >> ALPHA_CHANNEL & 0xff) * inverseAmount))) & 0xff;
int r = ((int) (((float) (color1 >> RED_CHANNEL & 0xff) * amount) +
((float) (color2 >> RED_CHANNEL & 0xff) * inverseAmount))) & 0xff;
int g = ((int) (((float) (color1 >> GREEN_CHANNEL & 0xff) * amount) +
((float) (color2 >> GREEN_CHANNEL & 0xff) * inverseAmount))) & 0xff;
int b = ((int) (((float) (color1 & 0xff) * amount) +
((float) (color2 & 0xff) * inverseAmount))) & 0xff;
int a = ((int) (((float) (color1 >> ALPHA_CHANNEL & 0xff) * amount) +
((float) (color2 >> ALPHA_CHANNEL & 0xff) * inverseAmount))) & 0xff;
int r = ((int) (((float) (color1 >> RED_CHANNEL & 0xff) * amount) +
((float) (color2 >> RED_CHANNEL & 0xff) * inverseAmount))) & 0xff;
int g = ((int) (((float) (color1 >> GREEN_CHANNEL & 0xff) * amount) +
((float) (color2 >> GREEN_CHANNEL & 0xff) * inverseAmount))) & 0xff;
int b = ((int) (((float) (color1 & 0xff) * amount) +
((float) (color2 & 0xff) * inverseAmount))) & 0xff;
return a << ALPHA_CHANNEL | r << RED_CHANNEL | g << GREEN_CHANNEL | b << BLUE_CHANNEL;
}
return a << ALPHA_CHANNEL | r << RED_CHANNEL | g << GREEN_CHANNEL | b << BLUE_CHANNEL;
}
}

View File

@@ -16,101 +16,173 @@
package org.isoron.helpers;
import java.text.DecimalFormat;
import java.text.SimpleDateFormat;
import android.content.Context;
import android.text.format.DateFormat;
import org.isoron.uhabits.R;
import java.util.Date;
import java.util.GregorianCalendar;
import java.util.Locale;
import java.util.TimeZone;
public class DateHelper
{
public static int millisecondsInOneDay = 24 * 60 * 60 * 1000;
public static long getLocalTime()
{
TimeZone tz = TimeZone.getDefault();
long now = new Date().getTime();
return now + tz.getOffset(now);
}
public static long getStartOfDay(long timestamp)
{
return (timestamp / millisecondsInOneDay) * millisecondsInOneDay;
}
public static long getStartOfToday()
{
return getStartOfDay(DateHelper.getLocalTime());
}
public static int millisecondsInOneDay = 24 * 60 * 60 * 1000;
// public static Date getStartOfDay(Date date)
// {
// Calendar calendar = Calendar.getInstance();
// calendar.setTime(date);
// calendar.set(Calendar.HOUR_OF_DAY, 0);
// calendar.set(Calendar.MINUTE, 0);
// calendar.set(Calendar.SECOND, 0);
// calendar.set(Calendar.MILLISECOND, 0);
// return calendar.getTime();
// }
public static long getLocalTime()
{
TimeZone tz = TimeZone.getDefault();
long now = new Date().getTime();
return now + tz.getOffset(now);
}
public static int differenceInDays(Date from, Date to)
{
long milliseconds = getStartOfDay(to.getTime()) - getStartOfDay(from.getTime());
public static long toLocalTime(long timestamp)
{
TimeZone tz = TimeZone.getDefault();
long now = new Date(timestamp).getTime();
return now + tz.getOffset(now);
}
public static long getStartOfDay(long timestamp)
{
return (timestamp / millisecondsInOneDay) * millisecondsInOneDay;
}
public static GregorianCalendar getStartOfTodayCalendar()
{
GregorianCalendar day = new GregorianCalendar(TimeZone.getTimeZone("GMT"));
day.setTimeInMillis(DateHelper.getStartOfDay(DateHelper.getLocalTime()));
return day;
}
public static GregorianCalendar getCalendar(long timestamp)
{
GregorianCalendar day = new GregorianCalendar(TimeZone.getTimeZone("GMT"));
day.setTimeInMillis(timestamp);
return day;
}
public static int getWeekday(long timestamp)
{
GregorianCalendar day = getCalendar(timestamp);
return day.get(GregorianCalendar.DAY_OF_WEEK) % 7;
}
public static long getStartOfToday()
{
return getStartOfDay(DateHelper.getLocalTime());
}
public static String formatTime(Context context, int hours, int minutes)
{
int reminderMilliseconds = (hours * 60 + minutes) * 60 * 1000;
Date date = new Date(reminderMilliseconds);
java.text.DateFormat df = DateFormat.getTimeFormat(context);
df.setTimeZone(TimeZone.getTimeZone("UTC"));
return df.format(date);
}
public static String formatHeaderDate(GregorianCalendar day)
{
String dayOfMonth = Integer.toString(day.get(GregorianCalendar.DAY_OF_MONTH));
String dayOfWeek = day.getDisplayName(GregorianCalendar.DAY_OF_WEEK,
GregorianCalendar.SHORT, Locale.getDefault());
return dayOfWeek + "\n" + dayOfMonth;
}
public static int differenceInDays(Date from, Date to)
{
long milliseconds = getStartOfDay(to.getTime()) - getStartOfDay(from.getTime());
return (int) (milliseconds / millisecondsInOneDay);
}
}
public static String differenceInWords(Date from, Date to)
{
Integer days = differenceInDays(from, to);
boolean negative = (days < 0);
days = Math.abs(days);
public static String[] getShortDayNames()
{
return getDayNames(GregorianCalendar.SHORT);
}
Integer weeks = (int) Math.round(days / 7.0);
Double months = days / 30.4;
Double years = days / 365.0;
public static String[] getLongDayNames()
{
return getDayNames(GregorianCalendar.LONG);
}
StringBuffer s = new StringBuffer();
DecimalFormat df = new DecimalFormat("#.#");
if(months > 18)
{
s.append(df.format(years));
s.append(" years");
}
else if(weeks > 6)
{
s.append(df.format(months));
s.append(" months");
}
else if(days > 13)
{
s.append(weeks);
s.append(" weeks");
}
else if(days > 6)
{
s.append(days);
s.append(" days");
}
else
{
if(days == 0)
s.append("Today");
else if(days == 1 && negative)
s.append("Yesterday");
else if(days == 1 && !negative)
s.append("Tomorrow");
else
{
if(negative)
s.append("past ");
s.append(new SimpleDateFormat("EEEE").format(to));
}
}
public static String[] getDayNames(int format)
{
String[] wdays = new String[7];
if(negative && days > 6)
s.append(" ago");
GregorianCalendar day = new GregorianCalendar();
day.set(GregorianCalendar.DAY_OF_WEEK, 0);
for (int i = 0; i < 7; i++)
{
wdays[i] = day.getDisplayName(GregorianCalendar.DAY_OF_WEEK, format,
Locale.getDefault());
day.add(GregorianCalendar.DAY_OF_MONTH, 1);
}
return wdays;
}
public static String formatWeekdayList(Context context, boolean weekday[])
{
String shortDayNames[] = getShortDayNames();
String longDayNames[] = getLongDayNames();
StringBuilder buffer = new StringBuilder();
int count = 0;
int first = 0;
boolean isFirst = true;
for(int i = 0; i < 7; i++)
{
if(weekday[i])
{
if(isFirst) first = i;
else buffer.append(", ");
buffer.append(shortDayNames[i]);
isFirst = false;
count++;
}
}
if(count == 1) return longDayNames[first];
if(count == 2 && weekday[0] && weekday[1]) return context.getString(R.string.weekends);
if(count == 5 && !weekday[0] && !weekday[1]) return context.getString(R.string.any_weekday);
if(count == 7) return context.getString(R.string.any_day);
return buffer.toString();
}
public static Integer packWeekdayList(boolean weekday[])
{
int list = 0;
int current = 1;
for(int i = 0; i < 7; i++)
{
if(weekday[i]) list |= current;
current = current << 1;
}
return list;
}
public static boolean[] unpackWeekdayList(int list)
{
boolean[] weekday = new boolean[7];
int current = 1;
for(int i = 0; i < 7; i++)
{
if((list & current) != 0) weekday[i] = true;
current = current << 1;
}
return weekday;
}
return s.toString();
}
}

View File

@@ -19,39 +19,50 @@ package org.isoron.helpers;
import android.content.Context;
import android.content.DialogInterface;
import android.content.DialogInterface.OnClickListener;
import android.content.SharedPreferences;
import android.graphics.Typeface;
import android.os.Vibrator;
import android.preference.PreferenceManager;
import android.view.View;
import android.view.inputmethod.InputMethodManager;
import android.widget.TextView;
import org.isoron.uhabits.R;
public abstract class DialogHelper
{
// public static AlertDialog alert(Activity context, String title, String message, OnClickListener positiveClickListener) {
// return new AlertDialog.Builder(context)
// .setTitle(title)
// .setMessage(message)
// .setPositiveButton(android.R.string.yes, positiveClickListener)
// .setNegativeButton(android.R.string.no, null).show();
// }
private static Typeface fontawesome;
public static abstract class SimpleClickListener implements OnClickListener
{
public abstract void onClick();
public interface OnSavedListener
{
void onSaved(Command command, Object savedObject);
}
public void onClick(DialogInterface dialog, int whichButton)
{
onClick();
}
}
public static void showSoftKeyboard(View view)
{
InputMethodManager imm = (InputMethodManager) view.getContext()
.getSystemService(Context.INPUT_METHOD_SERVICE);
imm.showSoftInput(view, InputMethodManager.SHOW_IMPLICIT);
}
public static interface OnSavedListener
{
public void onSaved(Command command, Object savedObject);
}
public static void vibrate(Context context, int duration)
{
Vibrator vb = (Vibrator) context.getSystemService(Context.VIBRATOR_SERVICE);
vb.vibrate(duration);
}
public static void showSoftKeyboard(View view)
{
InputMethodManager imm = (InputMethodManager)
view.getContext().getSystemService(Context.INPUT_METHOD_SERVICE);
imm.showSoftInput(view, InputMethodManager.SHOW_IMPLICIT);
}
public static void incrementLaunchCount(Context context)
{
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context);
int count = prefs.getInt("launch_count", 0);
prefs.edit().putInt("launch_count", count + 1).apply();
}
public static int getLaunchCount(Context context)
{
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context);
return prefs.getInt("launch_count", 0);
}
}

View File

@@ -81,8 +81,7 @@ abstract public class ReplayableActivity extends Activity
toast.show();
}
public void executeCommand(final Command command, Boolean clearRedoStack,
final Long refreshKey)
public void executeCommand(final Command command, Boolean clearRedoStack, final Long refreshKey)
{
undoList.push(command);

View File

@@ -16,7 +16,6 @@
package org.isoron.uhabits;
import android.app.Activity;
import android.graphics.Color;
import android.os.Bundle;
@@ -30,27 +29,26 @@ public class IntroActivity extends AppIntro2
{
showStatusBar(false);
addSlide(AppIntroFragment.newInstance("Welcome",
"Habits Tracker helps you create and maintain good habits.", R.drawable.tutorial_1,
addSlide(AppIntroFragment.newInstance(getString(R.string.intro_title_1),
getString(R.string.intro_description_1), R.drawable.intro_icon_1,
Color.parseColor("#194673")));
addSlide(AppIntroFragment.newInstance("Create some new habits",
"Every day, after performing your habit, put a checkmark on the app.",
R.drawable.tutorial_2, Color.parseColor("#ffa726")));
addSlide(AppIntroFragment.newInstance(getString(R.string.intro_title_2),
getString(R.string.intro_description_2), R.drawable.intro_icon_2,
Color.parseColor("#ffa726")));
addSlide(AppIntroFragment.newInstance("Keep doing it",
"Habits performed consistently for a long time will earn a full star.",
R.drawable.tutorial_3, Color.parseColor("#7cb342")));
addSlide(AppIntroFragment.newInstance(getString(R.string.intro_title_3),
getString(R.string.intro_description_3), R.drawable.intro_icon_3,
Color.parseColor("#7cb342")));
addSlide(AppIntroFragment.newInstance("Track your progress",
"Detailed graphs show you how your habits improved over time.",
R.drawable.tutorial_4, Color.parseColor("#9575cd")));
addSlide(AppIntroFragment.newInstance(getString(R.string.intro_title_4),
getString(R.string.intro_description_4), R.drawable.intro_icon_4,
Color.parseColor("#9575cd")));
}
@Override
public void onNextPressed()
{
}
@Override
@@ -62,6 +60,5 @@ public class IntroActivity extends AppIntro2
@Override
public void onSlideChanged()
{
}
}

View File

@@ -24,14 +24,18 @@ import android.preference.PreferenceManager;
import android.view.Menu;
import android.view.MenuItem;
import org.isoron.helpers.DateHelper;
import org.isoron.helpers.DialogHelper;
import org.isoron.helpers.ReplayableActivity;
import org.isoron.uhabits.dialogs.ListHabitsFragment;
import org.isoron.uhabits.fragments.ListHabitsFragment;
import org.isoron.uhabits.helpers.ReminderHelper;
import org.isoron.uhabits.models.Habit;
public class MainActivity extends ReplayableActivity
implements ListHabitsFragment.OnHabitClickListener
{
private ListHabitsFragment listHabitsFragment;
SharedPreferences prefs;
@Override
protected void onCreate(Bundle savedInstanceState)
@@ -39,26 +43,30 @@ public class MainActivity extends ReplayableActivity
super.onCreate(savedInstanceState);
setContentView(R.layout.list_habits_activity);
prefs = PreferenceManager.getDefaultSharedPreferences(this);
listHabitsFragment =
(ListHabitsFragment) getFragmentManager().findFragmentById(R.id.fragment1);
onStartup();
}
private void onStartup()
{
PreferenceManager.setDefaultValues(this, R.xml.preferences, false);
listHabitsFragment = (ListHabitsFragment) getFragmentManager().findFragmentById(
R.id.fragment1);
ReminderHelper.createReminderAlarms(MainActivity.this);
DialogHelper.incrementLaunchCount(this);
showTutorial();
}
private void showTutorial()
{
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this);
Boolean firstRun = prefs.getBoolean("pref_first_run", true);
if(firstRun)
if (firstRun)
{
SharedPreferences.Editor editor = prefs.edit();
editor.putBoolean("pref_first_run", false);
editor.putLong("last_hint_timestamp", DateHelper.getStartOfToday()).apply();
editor.apply();
Intent intent = new Intent(this, IntroActivity.class);

View File

@@ -28,40 +28,63 @@ import android.content.SharedPreferences;
import android.graphics.BitmapFactory;
import android.media.RingtoneManager;
import android.net.Uri;
import android.os.Handler;
import android.preference.PreferenceManager;
import android.support.v4.app.NotificationCompat;
import android.util.Log;
import org.isoron.helpers.DateHelper;
import org.isoron.uhabits.helpers.ReminderHelper;
import org.isoron.uhabits.models.Habit;
import java.util.Date;
public class ReminderAlarmReceiver extends BroadcastReceiver
{
public static String ACTION_CHECK = "org.isoron.uhabits.ACTION_CHECK";
public static String ACTION_DISMISS = "org.isoron.uhabits.ACTION_DISMISS";
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_SNOOZE = "org.isoron.uhabits.ACTION_SNOOZE";
public static final String ACTION_CHECK = "org.isoron.uhabits.ACTION_CHECK";
public static final String ACTION_DISMISS = "org.isoron.uhabits.ACTION_DISMISS";
public static final String ACTION_REMIND = "org.isoron.uhabits.ACTION_REMIND";
public static final String ACTION_REMOVE_REMINDER = "org.isoron.uhabits.ACTION_REMOVE_REMINDER";
public static final String ACTION_SNOOZE = "org.isoron.uhabits.ACTION_SNOOZE";
@Override
public void onReceive(Context context, Intent intent)
public void onReceive(final Context context, Intent intent)
{
String action = intent.getAction();
switch (intent.getAction())
{
case ACTION_REMIND:
createNotification(context, intent);
createReminderAlarms(context);
break;
if (action.equals(ACTION_REMIND)) createNotification(context, intent.getData());
case ACTION_DISMISS:
dismissAllHabits();
break;
else if (action.equals(ACTION_DISMISS)) dismissAllHabits();
case ACTION_CHECK:
checkHabit(context, intent);
break;
else if (action.equals(ACTION_CHECK)) checkHabit(context, intent.getData());
else if (action.equals(ACTION_SNOOZE)) snoozeHabit(context, intent.getData());
case ACTION_SNOOZE:
snoozeHabit(context, intent);
break;
}
}
private void snoozeHabit(Context context, Uri data)
private void createReminderAlarms(final Context context)
{
new Handler().postDelayed(new Runnable()
{
@Override
public void run()
{
ReminderHelper.createReminderAlarms(context);
}
}, 5000);
}
private void snoozeHabit(Context context, Intent intent)
{
Uri data = intent.getData();
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context);
long delayMinutes = Long.parseLong(prefs.getString("pref_snooze_interval", "15"));
@@ -71,10 +94,16 @@ public class ReminderAlarmReceiver extends BroadcastReceiver
dismissNotification(context, habit);
}
private void checkHabit(Context context, Uri data)
private void checkHabit(Context context, Intent intent)
{
Uri data = intent.getData();
Long timestamp = DateHelper.getStartOfToday();
String paramTimestamp = data.getQueryParameter("timestamp");
if(paramTimestamp != null) timestamp = Long.parseLong(paramTimestamp);
Habit habit = Habit.get(ContentUris.parseId(data));
habit.toggleRepetitionToday();
habit.toggleRepetition(timestamp);
habit.save();
dismissNotification(context, habit);
}
@@ -98,19 +127,20 @@ public class ReminderAlarmReceiver extends BroadcastReceiver
}
private void createNotification(Context context, Uri data)
private void createNotification(Context context, Intent intent)
{
Uri data = intent.getData();
Habit habit = Habit.get(ContentUris.parseId(data));
if (habit.hasImplicitRepToday()) return;
Log.d("Alarm", String.format("Applying highlight: %s", habit.name));
habit.highlight = 1;
habit.save();
if (!checkWeekday(intent, habit)) return;
// Check if reminder has been turned off after alarm was scheduled
if (habit.reminder_hour == null) return;
if (habit.reminderHour == null) return;
Intent contentIntent = new Intent(context, MainActivity.class);
contentIntent.setData(data);
@@ -133,6 +163,8 @@ public class ReminderAlarmReceiver extends BroadcastReceiver
Uri soundUri = RingtoneManager.getDefaultUri(RingtoneManager.TYPE_NOTIFICATION);
Long reminderTime = intent.getLongExtra("reminderTime", DateHelper.getStartOfToday());
NotificationCompat.WearableExtender wearableExtender =
new NotificationCompat.WearableExtender().setBackground(
BitmapFactory.decodeResource(context.getResources(), R.drawable.stripe));
@@ -143,10 +175,14 @@ public class ReminderAlarmReceiver extends BroadcastReceiver
.setContentText(habit.description)
.setContentIntent(contentPendingIntent)
.setDeleteIntent(deletePendingIntent)
.addAction(R.drawable.ic_action_check, "Check", checkIntentPending)
.addAction(R.drawable.ic_action_snooze, "Later", snoozeIntentPending)
.addAction(R.drawable.ic_action_check,
context.getString(R.string.check), checkIntentPending)
.addAction(R.drawable.ic_action_snooze,
context.getString(R.string.snooze), snoozeIntentPending)
.setSound(soundUri)
.extend(wearableExtender)
.setWhen(reminderTime)
.setShowWhen(true)
.build();
notification.flags |= Notification.FLAG_AUTO_CANCEL;
@@ -158,4 +194,14 @@ public class ReminderAlarmReceiver extends BroadcastReceiver
notificationManager.notify(notificationId, notification);
}
private boolean checkWeekday(Intent intent, Habit habit)
{
Long timestamp = intent.getLongExtra("timestamp", DateHelper.getStartOfToday());
boolean reminderDays[] = DateHelper.unpackWeekdayList(habit.reminderDays);
int weekday = DateHelper.getWeekday(timestamp);
return reminderDays[weekday];
}
}

View File

@@ -19,7 +19,7 @@ package org.isoron.uhabits;
import android.app.Activity;
import android.os.Bundle;
import org.isoron.uhabits.dialogs.SettingsFragment;
import org.isoron.uhabits.fragments.SettingsFragment;
public class SettingsActivity extends Activity
{
@@ -27,7 +27,8 @@ public class SettingsActivity extends Activity
protected void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
getFragmentManager().beginTransaction().replace(android.R.id.content,
new SettingsFragment()).commit();
getFragmentManager().beginTransaction()
.replace(android.R.id.content, new SettingsFragment())
.commit();
}
}

View File

@@ -16,10 +16,6 @@
package org.isoron.uhabits;
import org.isoron.helpers.ReplayableActivity;
import org.isoron.uhabits.models.Habit;
import android.app.Activity;
import android.content.ContentUris;
import android.content.Intent;
import android.graphics.drawable.ColorDrawable;
@@ -28,6 +24,9 @@ import android.os.Bundle;
import android.view.Menu;
import android.view.MenuItem;
import org.isoron.helpers.ReplayableActivity;
import org.isoron.uhabits.models.Habit;
public class ShowHabitActivity extends ReplayableActivity
{

View File

@@ -0,0 +1,65 @@
/* Copyright (C) 2016 Alinson Santos Xavier
*
* This program is free software: you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by the Free
* Software Foundation, either version 3 of the License, or (at your option)
* any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.isoron.uhabits.commands;
import org.isoron.helpers.Command;
import org.isoron.uhabits.R;
import org.isoron.uhabits.models.Habit;
import java.util.LinkedList;
import java.util.List;
public class ArchiveHabitsCommand extends Command
{
private List<Habit> habits;
public ArchiveHabitsCommand(Habit habit)
{
habits = new LinkedList<>();
habits.add(habit);
}
public ArchiveHabitsCommand(List<Habit> habits)
{
this.habits = habits;
}
@Override
public void execute()
{
for(Habit h : habits)
h.archive();
}
@Override
public void undo()
{
for(Habit h : habits)
h.unarchive();
}
public Integer getExecuteStringId()
{
return R.string.toast_habit_archived;
}
public Integer getUndoStringId()
{
return R.string.toast_habit_unarchived;
}
}

View File

@@ -0,0 +1,97 @@
/* Copyright (C) 2016 Alinson Santos Xavier
*
* This program is free software: you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by the Free
* Software Foundation, either version 3 of the License, or (at your option)
* any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.isoron.uhabits.commands;
import com.activeandroid.ActiveAndroid;
import org.isoron.helpers.Command;
import org.isoron.uhabits.R;
import org.isoron.uhabits.models.Habit;
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;
public class ChangeHabitColorCommand extends Command
{
List<Habit> habits;
List<Integer> originalColors;
Integer newColor;
public ChangeHabitColorCommand(List<Habit> habits, Integer newColor)
{
this.habits = habits;
this.newColor = newColor;
this.originalColors = new ArrayList<>(habits.size());
for(Habit h : habits)
originalColors.add(h.color);
}
@Override
public void execute()
{
ActiveAndroid.beginTransaction();
try
{
for(Habit h : habits)
{
h.color = newColor;
h.save();
}
ActiveAndroid.setTransactionSuccessful();
}
finally
{
ActiveAndroid.endTransaction();
}
}
@Override
public void undo()
{
ActiveAndroid.beginTransaction();
try
{
int k = 0;
for(Habit h : habits)
{
h.color = originalColors.get(k++);
h.save();
}
ActiveAndroid.setTransactionSuccessful();
}
finally
{
ActiveAndroid.endTransaction();
}
}
public Integer getExecuteStringId()
{
return R.string.toast_habit_changed;
}
public Integer getUndoStringId()
{
return R.string.toast_habit_changed;
}
}

View File

@@ -0,0 +1,56 @@
/* Copyright (C) 2016 Alinson Santos Xavier
*
* This program is free software: you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by the Free
* Software Foundation, either version 3 of the License, or (at your option)
* any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.isoron.uhabits.commands;
import org.isoron.helpers.Command;
import org.isoron.uhabits.R;
import org.isoron.uhabits.models.Habit;
import java.util.List;
public class DeleteHabitsCommand extends Command
{
private List<Habit> habits;
public DeleteHabitsCommand(List<Habit> habits)
{
this.habits = habits;
}
@Override
public void execute()
{
for(Habit h : habits)
h.cascadeDelete();
}
@Override
public void undo()
{
}
public Integer getExecuteStringId()
{
return R.string.toast_habit_deleted;
}
public Integer getUndoStringId()
{
return R.string.toast_habit_restored;
}
}

View File

@@ -0,0 +1,65 @@
/* Copyright (C) 2016 Alinson Santos Xavier
*
* This program is free software: you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by the Free
* Software Foundation, either version 3 of the License, or (at your option)
* any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.isoron.uhabits.commands;
import org.isoron.helpers.Command;
import org.isoron.uhabits.R;
import org.isoron.uhabits.models.Habit;
import java.util.LinkedList;
import java.util.List;
public class UnarchiveHabitsCommand extends Command
{
private List<Habit> habits;
public UnarchiveHabitsCommand(Habit habit)
{
habits = new LinkedList<>();
habits.add(habit);
}
public UnarchiveHabitsCommand(List<Habit> habits)
{
this.habits = habits;
}
@Override
public void execute()
{
for(Habit h : habits)
h.unarchive();
}
@Override
public void undo()
{
for(Habit h : habits)
h.archive();
}
public Integer getExecuteStringId()
{
return R.string.toast_habit_unarchived;
}
public Integer getUndoStringId()
{
return R.string.toast_habit_archived;
}
}

View File

@@ -1,305 +0,0 @@
/* Copyright (C) 2016 Alinson Santos Xavier
*
* This program is free software: you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by the Free
* Software Foundation, either version 3 of the License, or (at your option)
* any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.isoron.uhabits.dialogs;
import org.isoron.helpers.ColorHelper;
import org.isoron.helpers.Command;
import org.isoron.helpers.DialogHelper.OnSavedListener;
import org.isoron.uhabits.R;
import org.isoron.uhabits.models.Habit;
import android.app.DialogFragment;
import android.content.SharedPreferences;
import android.graphics.Color;
import android.graphics.ColorMatrix;
import android.os.Bundle;
import android.preference.PreferenceManager;
import android.view.LayoutInflater;
import android.view.View;
import android.view.View.OnClickListener;
import android.view.ViewGroup;
import android.widget.Button;
import android.widget.ImageButton;
import android.widget.TextView;
import com.android.colorpicker.ColorPickerDialog;
import com.android.colorpicker.ColorPickerSwatch;
import com.android.datetimepicker.time.RadialPickerLayout;
import com.android.datetimepicker.time.TimePickerDialog;
import com.android.datetimepicker.time.TimePickerDialog.OnTimeSetListener;
public class EditHabitFragment extends DialogFragment implements OnClickListener
{
private int mode;
static final int EDIT_MODE = 0;
static final int CREATE_MODE = 1;
private OnSavedListener onSavedListener;
private Habit originalHabit, modified_habit;
private TextView tvName, tvDescription, tvFreqNum, tvFreqDen, tvInputReminder;
private SharedPreferences prefs;
static class SolidColorMatrix extends ColorMatrix
{
public SolidColorMatrix(int color)
{
float matrix[] = { 0.0f, 0.0f, 0.0f, 0.0f, Color.red(color), 0.0f, 0.0f, 0.0f, 0.0f,
Color.green(color), 0.0f, 0.0f, 0.0f, 0.0f, Color.blue(color), 0.0f, 0.0f,
0.0f, 1.0f, 0 };
set(matrix);
}
}
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
* Factory *
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
static EditHabitFragment editSingleHabitFragment(long id)
{
EditHabitFragment frag = new EditHabitFragment();
Bundle args = new Bundle();
args.putLong("habitId", id);
args.putInt("editMode", EDIT_MODE);
frag.setArguments(args);
return frag;
}
static EditHabitFragment createHabitFragment()
{
EditHabitFragment frag = new EditHabitFragment();
Bundle args = new Bundle();
args.putInt("editMode", CREATE_MODE);
frag.setArguments(args);
return frag;
}
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
* Creation *
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState)
{
View view = inflater.inflate(R.layout.edit_habit, container, false);
tvName = (TextView) view.findViewById(R.id.input_name);
tvDescription = (TextView) view.findViewById(R.id.input_description);
tvFreqNum = (TextView) view.findViewById(R.id.input_freq_num);
tvFreqDen = (TextView) view.findViewById(R.id.input_freq_den);
tvInputReminder = (TextView) view.findViewById(R.id.input_reminder_time);
Button buttonSave = (Button) view.findViewById(R.id.buttonSave);
Button buttonDiscard = (Button) view.findViewById(R.id.buttonDiscard);
buttonSave.setOnClickListener(this);
buttonDiscard.setOnClickListener(this);
tvInputReminder.setOnClickListener(this);
ImageButton buttonPickColor = (ImageButton) view.findViewById(R.id.button_pick_color);
prefs = PreferenceManager.getDefaultSharedPreferences(getActivity());
Bundle args = getArguments();
mode = (Integer) args.get("editMode");
if(mode == CREATE_MODE)
{
getDialog().setTitle("Create habit");
modified_habit = new Habit();
int defaultNum = prefs.getInt("pref_default_habit_freq_num", modified_habit.freq_num);
int defaultDen = prefs.getInt("pref_default_habit_freq_den", modified_habit.freq_den);
int defaultColor = prefs.getInt("pref_default_habit_color", modified_habit.color);
modified_habit.color = defaultColor;
modified_habit.freq_num = defaultNum;
modified_habit.freq_den = defaultDen;
}
else if(mode == EDIT_MODE)
{
originalHabit = Habit.get((Long) args.get("habitId"));
modified_habit = new Habit(originalHabit);
getDialog().setTitle("Edit habit");
tvName.append(modified_habit.name);
tvDescription.append(modified_habit.description);
}
tvFreqNum.append(modified_habit.freq_num.toString());
tvFreqDen.append(modified_habit.freq_den.toString());
changeColor(modified_habit.color);
updateReminder();
buttonPickColor.setOnClickListener(new OnClickListener()
{
public void onClick(View view)
{
ColorPickerDialog picker = ColorPickerDialog.newInstance(
R.string.color_picker_default_title,
ColorHelper.palette, modified_habit.color, 4, ColorPickerDialog.SIZE_SMALL);
picker.setOnColorSelectedListener(new ColorPickerSwatch.OnColorSelectedListener()
{
public void onColorSelected(int color)
{
changeColor(color);
}
});
picker.show(getFragmentManager(), "picker");
}
});
return view;
}
private void changeColor(Integer color)
{
modified_habit.color = color;
tvName.setTextColor(color);
SharedPreferences.Editor editor = prefs.edit();
editor.putInt("pref_default_habit_color", color);
editor.apply();
}
private void updateReminder()
{
if(modified_habit.reminder_hour != null)
{
tvInputReminder.setTextColor(Color.BLACK);
tvInputReminder.setText(String.format("%02d:%02d", modified_habit.reminder_hour,
modified_habit.reminder_min));
}
else
{
tvInputReminder.setTextColor(Color.GRAY);
tvInputReminder.setText("Off");
}
}
public void setOnSavedListener(OnSavedListener onSavedListener)
{
this.onSavedListener = onSavedListener;
}
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
* Callback *
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
@Override
public void onClick(View v)
{
int id = v.getId();
/* Due date spinner */
if(id == R.id.input_reminder_time)
{
int default_hour = 8;
int default_min = 0;
if(modified_habit.reminder_hour != null) {
default_hour = modified_habit.reminder_hour;
default_min = modified_habit.reminder_min;
}
TimePickerDialog timePicker = TimePickerDialog.newInstance(new OnTimeSetListener()
{
@Override
public void onTimeSet(RadialPickerLayout view, int hour, int minute)
{
modified_habit.reminder_hour = hour;
modified_habit.reminder_min = minute;
updateReminder();
}
@Override
public void onTimeCleared(RadialPickerLayout view)
{
modified_habit.reminder_hour = null;
modified_habit.reminder_min = null;
updateReminder();
}
}, default_hour, default_min, true);
timePicker.show(getFragmentManager(), "timePicker");
}
/* Save button */
if(id == R.id.buttonSave)
{
Command command = null;
modified_habit.name = tvName.getText().toString().trim();
modified_habit.description = tvDescription.getText().toString().trim();
modified_habit.freq_num = Integer.parseInt(tvFreqNum.getText().toString());
modified_habit.freq_den = Integer.parseInt(tvFreqDen.getText().toString());
Boolean valid = true;
if(modified_habit.name.length() == 0)
{
tvName.setError("Name cannot be blank.");
valid = false;
}
if(modified_habit.freq_den <= 0)
{
tvFreqNum.setError("Number must be positive.");
valid = false;
}
if(modified_habit.freq_num > modified_habit.freq_den)
{
tvFreqNum.setError("You can have at most one repetition per day");
valid = false;
}
if(!valid)
return;
SharedPreferences.Editor editor = prefs.edit();
editor.putInt("pref_default_habit_freq_num", modified_habit.freq_num);
editor.putInt("pref_default_habit_freq_den", modified_habit.freq_den);
editor.apply();
Habit savedHabit = null;
if(mode == EDIT_MODE)
{
command = originalHabit.new EditCommand(modified_habit);
savedHabit = originalHabit;
}
if(mode == CREATE_MODE)
command = new Habit.CreateCommand(modified_habit);
if(onSavedListener != null)
onSavedListener.onSaved(command, savedHabit);
dismiss();
}
/* Discard button */
if(id == R.id.buttonDiscard)
{
dismiss();
}
}
}

View File

@@ -1,672 +0,0 @@
/* Copyright (C) 2016 Alinson Santos Xavier
*
* This program is free software: you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by the Free
* Software Foundation, either version 3 of the License, or (at your option)
* any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.isoron.uhabits.dialogs;
import android.app.Activity;
import android.app.Fragment;
import android.content.Context;
import android.content.SharedPreferences;
import android.graphics.Color;
import android.graphics.Point;
import android.graphics.Typeface;
import android.os.AsyncTask;
import android.os.Bundle;
import android.os.Handler;
import android.os.Vibrator;
import android.preference.PreferenceManager;
import android.util.DisplayMetrics;
import android.view.ContextMenu;
import android.view.ContextMenu.ContextMenuInfo;
import android.view.Display;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.View;
import android.view.View.OnClickListener;
import android.view.View.OnLongClickListener;
import android.view.ViewGroup;
import android.view.WindowManager;
import android.widget.AdapterView;
import android.widget.AdapterView.AdapterContextMenuInfo;
import android.widget.AdapterView.OnItemClickListener;
import android.widget.BaseAdapter;
import android.widget.Button;
import android.widget.LinearLayout;
import android.widget.LinearLayout.LayoutParams;
import android.widget.ProgressBar;
import android.widget.TextView;
import com.mobeta.android.dslv.DragSortController;
import com.mobeta.android.dslv.DragSortListView;
import com.mobeta.android.dslv.DragSortListView.DropListener;
import org.isoron.helpers.ColorHelper;
import org.isoron.helpers.Command;
import org.isoron.helpers.DateHelper;
import org.isoron.helpers.DialogHelper.OnSavedListener;
import org.isoron.helpers.ReplayableActivity;
import org.isoron.uhabits.R;
import org.isoron.uhabits.ReminderHelper;
import org.isoron.uhabits.models.Habit;
import java.util.Date;
import java.util.GregorianCalendar;
import java.util.HashMap;
import java.util.Locale;
import java.util.TimeZone;
public class ListHabitsFragment extends Fragment
implements OnSavedListener, OnItemClickListener, OnLongClickListener, DropListener,
OnClickListener
{
public static final int INACTIVE_COLOR = Color.rgb(230, 230, 230);
public interface OnHabitClickListener
{
void onHabitClicked(Habit habit);
}
ListHabitsAdapter adapter;
DragSortListView listView;
ReplayableActivity activity;
TextView tvNameHeader;
long lastLongClick = 0;
private int tvNameWidth;
private int button_count;
private View llEmpty;
private ProgressBar progressBar;
private OnHabitClickListener habitClickListener;
private boolean short_toggle_enabled;
private HashMap<Long, Habit> habits;
private HashMap<Integer, Habit> positionToHabit;
private HashMap<Long, int[]> checkmarks;
private HashMap<Long, Integer> scores;
private Long lastLoadedTimestamp = null;
private AsyncTask<Void, Integer, Void> currentFetchTask = null;
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState)
{
DisplayMetrics dm = getResources().getDisplayMetrics();
int width = (int) (dm.widthPixels / dm.density);
button_count = (int) ((width - 160) / 42);
tvNameWidth = (int) ((width - 30 - button_count * 42) * dm.density);
habits = new HashMap<>();
positionToHabit = new HashMap<>();
checkmarks = new HashMap<>();
scores = new HashMap<>();
View view = inflater.inflate(R.layout.list_habits_fragment, container, false);
tvNameHeader = (TextView) view.findViewById(R.id.tvNameHeader);
progressBar = (ProgressBar) view.findViewById(R.id.progressBar);
progressBar.setVisibility(View.INVISIBLE);
adapter = new ListHabitsAdapter(getActivity());
listView = (DragSortListView) view.findViewById(R.id.listView);
listView.setAdapter(adapter);
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);
Typeface fontawesome = Typeface.createFromAsset(getActivity().getAssets(),
"fontawesome-webfont.ttf");
((TextView) view.findViewById(R.id.tvStarEmpty)).setTypeface(fontawesome);
llEmpty = view.findViewById(R.id.llEmpty);
updateEmptyMessage();
setHasOptionsMenu(true);
return view;
}
@Override
public void onAttach(Activity activity)
{
super.onAttach(activity);
habitClickListener = (OnHabitClickListener) activity;
this.activity = (ReplayableActivity) activity;
}
@Override
public void onResume()
{
super.onResume();
if(lastLoadedTimestamp == null || lastLoadedTimestamp != DateHelper.getStartOfToday())
{
updateHeader();
fetchAllHabits();
updateEmptyMessage();
}
adapter.notifyDataSetChanged();
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(activity);
short_toggle_enabled = prefs.getBoolean("pref_short_toggle", false);
}
private void updateHeader()
{
LayoutInflater inflater = activity.getLayoutInflater();
View view = getView();
if (view == null) return;
GregorianCalendar day = new GregorianCalendar(TimeZone.getTimeZone("GMT"));
day.setTimeInMillis(DateHelper.getStartOfDay(DateHelper.getLocalTime()));
LinearLayout llButtonsHeader = (LinearLayout) view.findViewById(R.id.llButtonsHeader);
llButtonsHeader.removeAllViews();
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)));
llButtonsHeader.addView(check);
day.add(GregorianCalendar.DAY_OF_MONTH, -1);
}
}
private void fetchAllHabits()
{
if(currentFetchTask != null) currentFetchTask.cancel(true);
currentFetchTask = new AsyncTask<Void, Integer, Void>()
{
HashMap<Long, Habit> newHabits = Habit.getAll();
HashMap<Integer, Habit> newPositionToHabit = new HashMap<>();
HashMap<Long, int[]> newCheckmarks = new HashMap<>();
HashMap<Long, Integer> newScores = new HashMap<>();
@Override
protected Void doInBackground(Void... params)
{
long dateTo = DateHelper.getStartOfDay(DateHelper.getLocalTime());
long dateFrom = dateTo - (button_count - 1) * DateHelper.millisecondsInOneDay;
int[] empty = new int[button_count];
for(Habit h : newHabits.values())
{
newScores.put(h.getId(), 0);
newPositionToHabit.put(h.position, h);
newCheckmarks.put(h.getId(), empty);
}
int current = 0;
for(int i = 0; i < newHabits.size(); i++)
{
if(isCancelled()) return null;
Habit h = newPositionToHabit.get(i);
newScores.put(h.getId(), h.getScore());
newCheckmarks.put(h.getId(), h.getCheckmarks(dateFrom, dateTo));
publishProgress(current++, newHabits.size());
}
commit();
return null;
}
private void commit()
{
habits = newHabits;
positionToHabit = newPositionToHabit;
checkmarks = newCheckmarks;
scores = newScores;
}
@Override
protected void onPreExecute()
{
progressBar.setIndeterminate(false);
progressBar.setProgress(0);
progressBar.setVisibility(View.VISIBLE);
}
@Override
protected void onProgressUpdate(Integer... values)
{
progressBar.setMax(values[1]);
progressBar.setProgress(values[0]);
if(lastLoadedTimestamp == null)
{
commit();
adapter.notifyDataSetChanged();
}
}
@Override
protected void onPostExecute(Void aVoid)
{
if(isCancelled()) return;
adapter.notifyDataSetChanged();
updateEmptyMessage();
progressBar.setVisibility(View.INVISIBLE);
currentFetchTask = null;
lastLoadedTimestamp = DateHelper.getStartOfToday();
}
};
currentFetchTask.execute();
}
private void fetchHabit(final Long id)
{
new AsyncTask<Void, Void, Void>()
{
@Override
protected Void doInBackground(Void... params)
{
long dateTo = DateHelper.getStartOfDay(DateHelper.getLocalTime());
long dateFrom = dateTo - (button_count - 1) * DateHelper.millisecondsInOneDay;
Habit h = Habit.get(id);
habits.put(id, h);
scores.put(id, h.getScore());
checkmarks.put(id, h.getCheckmarks(dateFrom, dateTo));
return null;
}
@Override
protected void onPreExecute()
{
new Handler().postDelayed(new Runnable()
{
@Override
public void run()
{
if(getStatus() == Status.RUNNING)
{
progressBar.setIndeterminate(true);
progressBar.setVisibility(View.VISIBLE);
}
}
}, 500);
}
@Override
protected void onPostExecute(Void aVoid)
{
progressBar.setVisibility(View.GONE);
adapter.notifyDataSetChanged();
}
}.execute();
}
@Override
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater)
{
super.onCreateOptionsMenu(menu, inflater);
inflater.inflate(R.menu.list_habits_options, menu);
MenuItem showArchivedItem = menu.findItem(R.id.action_show_archived);
showArchivedItem.setChecked(Habit.isIncludeArchived());
}
@Override
public void onCreateContextMenu(ContextMenu menu, View view, ContextMenuInfo menuInfo)
{
super.onCreateContextMenu(menu, view, menuInfo);
getActivity().getMenuInflater().inflate(R.menu.list_habits_context, menu);
AdapterContextMenuInfo info = (AdapterContextMenuInfo) menuInfo;
final Habit habit = habits.get(info.id);
if(habit.isArchived())
menu.findItem(R.id.action_archive_habit).setVisible(false);
else
menu.findItem(R.id.action_unarchive_habit).setVisible(false);
}
@Override
public boolean onOptionsItemSelected(MenuItem item)
{
switch(item.getItemId())
{
case R.id.action_add:
{
EditHabitFragment frag = EditHabitFragment.createHabitFragment();
frag.setOnSavedListener(this);
frag.show(getFragmentManager(), "dialog");
return true;
}
case R.id.action_show_archived:
{
Habit.setIncludeArchived(!Habit.isIncludeArchived());
fetchAllHabits();
activity.invalidateOptionsMenu();
return true;
}
default:
return super.onOptionsItemSelected(item);
}
}
@Override
public boolean onContextItemSelected(MenuItem menuItem)
{
AdapterContextMenuInfo info = (AdapterContextMenuInfo) menuItem.getMenuInfo();
final int id = menuItem.getItemId();
final Habit habit = habits.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;
}
else if (id == R.id.action_archive_habit)
{
Command c = habit.new ArchiveCommand();
executeCommand(c, null);
}
else if (id == R.id.action_unarchive_habit)
{
Command c = habit.new UnarchiveCommand();
executeCommand(c, null);
}
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 = positionToHabit.get(position);
habitClickListener.onHabitClicked(habit);
}
@Override
public void onSaved(Command command, Object savedObject)
{
Habit h = (Habit) savedObject;
if(h == null) activity.executeCommand(command, null);
else activity.executeCommand(command, h.getId());
adapter.notifyDataSetChanged();
ReminderHelper.createReminderAlarms(activity);
}
private void updateEmptyMessage()
{
if(lastLoadedTimestamp == null)
llEmpty.setVisibility(View.GONE);
else
llEmpty.setVisibility(habits.size() > 0 ? View.GONE : View.VISIBLE);
}
@Override
public boolean onLongClick(View v)
{
switch(v.getId())
{
case R.id.tvCheck:
{
lastLongClick = new Date().getTime();
if(!short_toggle_enabled)
{
toggleCheck(v);
Vibrator vb = (Vibrator) getActivity().getSystemService(Context.VIBRATOR_SERVICE);
vb.vibrate(100);
}
return true;
}
}
return false;
}
private void toggleCheck(View v)
{
Habit habit = habits.get((Long) v.getTag(R.string.habit_key));
int offset = (Integer) v.getTag(R.string.offset_key);
long timestamp = DateHelper.getStartOfDay(
DateHelper.getLocalTime() - offset * DateHelper.millisecondsInOneDay);
if(v.getTag(R.string.toggle_key).equals(2))
updateCheck(habit.color, (TextView) v, 0);
else
updateCheck(habit.color, (TextView) v, 2);
executeCommand(habit.new ToggleRepetitionCommand(timestamp), habit.getId());
}
private void executeCommand(Command c, Long refreshKey)
{
activity.executeCommand(c, refreshKey);
}
@Override
public void drop(int from, int to)
{
Habit fromHabit = positionToHabit.get(from);
Habit toHabit = positionToHabit.get(to);
positionToHabit.put(to, fromHabit);
positionToHabit.put(from, toHabit);
adapter.notifyDataSetChanged();
Habit.reorder(from, to);
}
@Override
public void onClick(View v)
{
switch(v.getId())
{
case R.id.tvCheck:
if(short_toggle_enabled)
toggleCheck(v);
else
activity.showToast(R.string.long_press_to_toggle);
return;
}
}
class ListHabitsAdapter extends BaseAdapter
{
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 habits.size();
}
@Override
public Object getItem(int position)
{
return positionToHabit.get(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 = positionToHabit.get(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);
view.findViewById(R.id.tvName).setLayoutParams(params);
Display display = ((WindowManager) context.getSystemService(Context.WINDOW_SERVICE))
.getDefaultDisplay();
Point size = new Point();
display.getSize(size);
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.setOnClickListener(ListHabitsFragment.this);
((LinearLayout) view.findViewById(R.id.llButtons)).addView(check);
}
view.setTag(R.id.KEY_TIMESTAMP, DateHelper.getStartOfToday());
}
TextView tvStar = (TextView) view.findViewById(R.id.tvStar);
TextView tvName = (TextView) view.findViewById(R.id.tvName);
if (habit == null)
{
tvName.setText(null);
return view;
}
LinearLayout llInner = (LinearLayout) view.findViewById(R.id.llInner);
llInner.setTag(R.string.habit_key, habit.getId());
int activeColor = habit.color;
tvName.setText(habit.name);
tvName.setTextColor(activeColor);
if(habit.isArchived())
{
activeColor = ColorHelper.palette[12];
tvName.setTextColor(activeColor);
tvStar.setText(context.getString(R.string.fa_archive));
tvStar.setTextColor(activeColor);
}
else
{
int score = scores.get(habit.getId());
if (score < Habit.HALF_STAR_CUTOFF)
{
tvStar.setText(context.getString(R.string.fa_star_o));
tvStar.setTextColor(INACTIVE_COLOR);
}
else if (score < Habit.FULL_STAR_CUTOFF)
{
tvStar.setText(context.getString(R.string.fa_star_half_o));
tvStar.setTextColor(INACTIVE_COLOR);
}
else
{
tvStar.setText(context.getString(R.string.fa_star));
tvStar.setTextColor(activeColor);
}
}
LinearLayout llButtons = (LinearLayout) view.findViewById(R.id.llButtons);
int m = llButtons.getChildCount();
int isChecked[] = checkmarks.get(habit.getId());
for (int i = 0; i < m; i++)
{
TextView tvCheck = (TextView) llButtons.getChildAt(i);
tvCheck.setTag(R.string.habit_key, habit.getId());
tvCheck.setTag(R.string.offset_key, i);
updateCheck(activeColor, tvCheck, isChecked[i]);
}
return view;
}
}
private void updateCheck(int activeColor, TextView tvCheck, int check)
{
switch (check)
{
case 2:
tvCheck.setText(R.string.fa_check);
tvCheck.setTextColor(activeColor);
tvCheck.setTag(R.string.toggle_key, 2);
break;
case 1:
tvCheck.setText(R.string.fa_check);
tvCheck.setTextColor(INACTIVE_COLOR);
tvCheck.setTag(R.string.toggle_key, 1);
break;
case 0:
tvCheck.setText(R.string.fa_times);
tvCheck.setTextColor(INACTIVE_COLOR);
tvCheck.setTag(R.string.toggle_key, 0);
break;
}
}
public void onPostExecuteCommand(Long refreshKey)
{
if(refreshKey == null) fetchAllHabits();
else fetchHabit(refreshKey);
}
}

View File

@@ -0,0 +1,63 @@
package org.isoron.uhabits.dialogs;
import android.app.AlertDialog;
import android.app.Dialog;
import android.app.DialogFragment;
import android.content.DialogInterface;
import android.os.Bundle;
import org.isoron.helpers.DateHelper;
public class WeekdayPickerDialog extends DialogFragment
implements DialogInterface.OnMultiChoiceClickListener, DialogInterface.OnClickListener
{
public interface OnWeekdaysPickedListener
{
void onWeekdaysPicked(boolean[] selectedDays);
}
private boolean[] selectedDays;
private OnWeekdaysPickedListener listener;
public void setListener(OnWeekdaysPickedListener listener)
{
this.listener = listener;
}
public void setSelectedDays(boolean[] selectedDays)
{
this.selectedDays = selectedDays;
}
@Override
public Dialog onCreateDialog(Bundle savedInstanceState)
{
AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
builder.setTitle("Select weekdays")
.setMultiChoiceItems(DateHelper.getLongDayNames(), selectedDays, this)
.setPositiveButton(android.R.string.yes, this)
.setNegativeButton(android.R.string.cancel, new DialogInterface.OnClickListener()
{
@Override
public void onClick(DialogInterface dialog, int which)
{
dismiss();
}
});
return builder.create();
}
@Override
public void onClick(DialogInterface dialog, int which, boolean isChecked)
{
selectedDays[which] = isChecked;
}
@Override
public void onClick(DialogInterface dialog, int which)
{
if(listener != null) listener.onWeekdaysPicked(selectedDays);
}
}

View File

@@ -0,0 +1,324 @@
/* Copyright (C) 2016 Alinson Santos Xavier
*
* This program is free software: you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by the Free
* Software Foundation, either version 3 of the License, or (at your option)
* any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.isoron.uhabits.fragments;
import android.app.DialogFragment;
import android.content.SharedPreferences;
import android.graphics.Color;
import android.os.Bundle;
import android.preference.PreferenceManager;
import android.text.format.DateFormat;
import android.view.LayoutInflater;
import android.view.View;
import android.view.View.OnClickListener;
import android.view.ViewGroup;
import android.widget.Button;
import android.widget.ImageButton;
import android.widget.TextView;
import com.android.colorpicker.ColorPickerDialog;
import com.android.colorpicker.ColorPickerSwatch;
import com.android.datetimepicker.time.RadialPickerLayout;
import com.android.datetimepicker.time.TimePickerDialog;
import org.isoron.helpers.ColorHelper;
import org.isoron.helpers.Command;
import org.isoron.helpers.DateHelper;
import org.isoron.helpers.DialogHelper.OnSavedListener;
import org.isoron.uhabits.R;
import org.isoron.uhabits.dialogs.WeekdayPickerDialog;
import org.isoron.uhabits.models.Habit;
public class EditHabitFragment extends DialogFragment
implements OnClickListener, WeekdayPickerDialog.OnWeekdaysPickedListener,
TimePickerDialog.OnTimeSetListener
{
private Integer mode;
static final int EDIT_MODE = 0;
static final int CREATE_MODE = 1;
private OnSavedListener onSavedListener;
private Habit originalHabit, modifiedHabit;
private TextView tvName, tvDescription, tvFreqNum, tvFreqDen, tvReminderTime, tvReminderDays;
private SharedPreferences prefs;
private boolean is24HourMode;
static EditHabitFragment editSingleHabitFragment(long id)
{
EditHabitFragment frag = new EditHabitFragment();
Bundle args = new Bundle();
args.putLong("habitId", id);
args.putInt("editMode", EDIT_MODE);
frag.setArguments(args);
return frag;
}
static EditHabitFragment createHabitFragment()
{
EditHabitFragment frag = new EditHabitFragment();
Bundle args = new Bundle();
args.putInt("editMode", CREATE_MODE);
frag.setArguments(args);
return frag;
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState)
{
View view = inflater.inflate(R.layout.edit_habit, container, false);
tvName = (TextView) view.findViewById(R.id.input_name);
tvDescription = (TextView) view.findViewById(R.id.input_description);
tvFreqNum = (TextView) view.findViewById(R.id.input_freq_num);
tvFreqDen = (TextView) view.findViewById(R.id.input_freq_den);
tvReminderTime = (TextView) view.findViewById(R.id.inputReminderTime);
tvReminderDays = (TextView) view.findViewById(R.id.inputReminderDays);
Button buttonSave = (Button) view.findViewById(R.id.buttonSave);
Button buttonDiscard = (Button) view.findViewById(R.id.buttonDiscard);
buttonSave.setOnClickListener(this);
buttonDiscard.setOnClickListener(this);
tvReminderTime.setOnClickListener(this);
tvReminderDays.setOnClickListener(this);
ImageButton buttonPickColor = (ImageButton) view.findViewById(R.id.buttonPickColor);
prefs = PreferenceManager.getDefaultSharedPreferences(getActivity());
Bundle args = getArguments();
mode = (Integer) args.get("editMode");
is24HourMode = DateFormat.is24HourFormat(getActivity());
if (mode == CREATE_MODE)
{
getDialog().setTitle(R.string.create_habit);
modifiedHabit = new Habit();
int defaultNum = prefs.getInt("pref_default_habit_freq_num", modifiedHabit.freqNum);
int defaultDen = prefs.getInt("pref_default_habit_freq_den", modifiedHabit.freqDen);
int defaultColor = prefs.getInt("pref_default_habit_color", modifiedHabit.color);
modifiedHabit.color = defaultColor;
modifiedHabit.freqNum = defaultNum;
modifiedHabit.freqDen = defaultDen;
}
else if (mode == EDIT_MODE)
{
originalHabit = Habit.get((Long) args.get("habitId"));
modifiedHabit = new Habit(originalHabit);
getDialog().setTitle(R.string.edit_habit);
tvName.append(modifiedHabit.name);
tvDescription.append(modifiedHabit.description);
}
tvFreqNum.append(modifiedHabit.freqNum.toString());
tvFreqDen.append(modifiedHabit.freqDen.toString());
changeColor(modifiedHabit.color);
updateReminder();
buttonPickColor.setOnClickListener(this);
return view;
}
private void changeColor(Integer color)
{
modifiedHabit.color = color;
tvName.setTextColor(color);
SharedPreferences.Editor editor = prefs.edit();
editor.putInt("pref_default_habit_color", color);
editor.apply();
}
private void updateReminder()
{
if (modifiedHabit.reminderHour != null)
{
tvReminderTime.setTextColor(Color.BLACK);
tvReminderTime.setText(DateHelper.formatTime(getActivity(), modifiedHabit.reminderHour,
modifiedHabit.reminderMin));
tvReminderDays.setVisibility(View.VISIBLE);
}
else
{
tvReminderTime.setTextColor(Color.GRAY);
tvReminderTime.setText(R.string.reminder_off);
tvReminderDays.setVisibility(View.GONE);
}
boolean weekdays[] = DateHelper.unpackWeekdayList(modifiedHabit.reminderDays);
tvReminderDays.setText(DateHelper.formatWeekdayList(getActivity(), weekdays));
}
public void setOnSavedListener(OnSavedListener onSavedListener)
{
this.onSavedListener = onSavedListener;
}
@Override
public void onClick(View v)
{
switch(v.getId())
{
case R.id.inputReminderTime:
onDateSpinnerClick();
break;
case R.id.inputReminderDays:
onWeekdayClick();
break;
case R.id.buttonSave:
onSaveButtonClick();
break;
case R.id.buttonDiscard:
dismiss();
break;
case R.id.buttonPickColor:
onColorButtonClick();
break;
}
}
private void onColorButtonClick()
{
ColorPickerDialog picker = ColorPickerDialog.newInstance(
R.string.color_picker_default_title, ColorHelper.palette, modifiedHabit.color, 4,
ColorPickerDialog.SIZE_SMALL);
picker.setOnColorSelectedListener(new ColorPickerSwatch.OnColorSelectedListener()
{
public void onColorSelected(int color)
{
changeColor(color);
}
});
picker.show(getFragmentManager(), "picker");
}
private void onSaveButtonClick()
{
Command command = null;
modifiedHabit.name = tvName.getText().toString().trim();
modifiedHabit.description = tvDescription.getText().toString().trim();
modifiedHabit.freqNum = Integer.parseInt(tvFreqNum.getText().toString());
modifiedHabit.freqDen = Integer.parseInt(tvFreqDen.getText().toString());
if (!validate()) return;
SharedPreferences.Editor editor = prefs.edit();
editor.putInt("pref_default_habit_freq_num", modifiedHabit.freqNum);
editor.putInt("pref_default_habit_freq_den", modifiedHabit.freqDen);
editor.apply();
Habit savedHabit = null;
if (mode == EDIT_MODE)
{
command = originalHabit.new EditCommand(modifiedHabit);
savedHabit = originalHabit;
}
if (mode == CREATE_MODE) command = new Habit.CreateCommand(modifiedHabit);
if (onSavedListener != null) onSavedListener.onSaved(command, savedHabit);
dismiss();
}
private boolean validate()
{
Boolean valid = true;
if (modifiedHabit.name.length() == 0)
{
tvName.setError(getString(R.string.validation_name_should_not_be_blank));
valid = false;
}
if (modifiedHabit.freqNum <= 0)
{
tvFreqNum.setError(getString(R.string.validation_number_should_be_positive));
valid = false;
}
if (modifiedHabit.freqNum > modifiedHabit.freqDen)
{
tvFreqNum.setError(getString(R.string.validation_at_most_one_rep_per_day));
valid = false;
}
return valid;
}
private void onDateSpinnerClick()
{
int defaultHour = 8;
int defaultMin = 0;
if (modifiedHabit.reminderHour != null)
{
defaultHour = modifiedHabit.reminderHour;
defaultMin = modifiedHabit.reminderMin;
}
TimePickerDialog timePicker =
TimePickerDialog.newInstance(this, defaultHour, defaultMin, is24HourMode);
timePicker.show(getFragmentManager(), "timePicker");
}
private void onWeekdayClick()
{
WeekdayPickerDialog dialog = new WeekdayPickerDialog();
dialog.setListener(this);
dialog.setSelectedDays(DateHelper.unpackWeekdayList(modifiedHabit.reminderDays));
dialog.show(getFragmentManager(), "weekdayPicker");
}
@Override
public void onTimeSet(RadialPickerLayout view, int hour, int minute)
{
modifiedHabit.reminderHour = hour;
modifiedHabit.reminderMin = minute;
updateReminder();
}
@Override
public void onTimeCleared(RadialPickerLayout view)
{
modifiedHabit.reminderHour = null;
modifiedHabit.reminderMin = null;
updateReminder();
}
@Override
public void onWeekdaysPicked(boolean[] selectedDays)
{
modifiedHabit.reminderDays = DateHelper.packWeekdayList(selectedDays);
updateReminder();
}
}

View File

@@ -0,0 +1,785 @@
/* Copyright (C) 2016 Alinson Santos Xavier
*
* This program is free software: you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by the Free
* Software Foundation, either version 3 of the License, or (at your option)
* any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.isoron.uhabits.fragments;
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.app.Activity;
import android.app.AlertDialog;
import android.app.Fragment;
import android.content.Context;
import android.content.DialogInterface;
import android.content.SharedPreferences;
import android.graphics.Color;
import android.graphics.Typeface;
import android.os.Bundle;
import android.preference.PreferenceManager;
import android.util.DisplayMetrics;
import android.view.ActionMode;
import android.view.ContextMenu;
import android.view.ContextMenu.ContextMenuInfo;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.View;
import android.view.View.OnClickListener;
import android.view.View.OnLongClickListener;
import android.view.ViewGroup;
import android.widget.AdapterView;
import android.widget.AdapterView.AdapterContextMenuInfo;
import android.widget.AdapterView.OnItemClickListener;
import android.widget.BaseAdapter;
import android.widget.Button;
import android.widget.LinearLayout;
import android.widget.LinearLayout.LayoutParams;
import android.widget.ProgressBar;
import android.widget.TextView;
import com.android.colorpicker.ColorPickerDialog;
import com.android.colorpicker.ColorPickerSwatch;
import com.mobeta.android.dslv.DragSortController;
import com.mobeta.android.dslv.DragSortListView;
import com.mobeta.android.dslv.DragSortListView.DropListener;
import org.isoron.helpers.ColorHelper;
import org.isoron.helpers.Command;
import org.isoron.helpers.DateHelper;
import org.isoron.helpers.DialogHelper;
import org.isoron.helpers.DialogHelper.OnSavedListener;
import org.isoron.helpers.ReplayableActivity;
import org.isoron.uhabits.R;
import org.isoron.uhabits.commands.ArchiveHabitsCommand;
import org.isoron.uhabits.commands.ChangeHabitColorCommand;
import org.isoron.uhabits.commands.DeleteHabitsCommand;
import org.isoron.uhabits.commands.UnarchiveHabitsCommand;
import org.isoron.uhabits.helpers.ReminderHelper;
import org.isoron.uhabits.loaders.HabitListLoader;
import org.isoron.uhabits.models.Habit;
import java.util.Date;
import java.util.GregorianCalendar;
import java.util.LinkedList;
import java.util.List;
public class ListHabitsFragment extends Fragment
implements OnSavedListener, OnItemClickListener, OnLongClickListener, DropListener,
OnClickListener, HabitListLoader.Listener, AdapterView.OnItemLongClickListener
{
private class ListHabitsActionBarCallback implements ActionMode.Callback
{
@Override
public boolean onCreateActionMode(ActionMode mode, Menu menu)
{
getActivity().getMenuInflater().inflate(R.menu.list_habits_context, menu);
updateTitle(mode);
updateActions(menu);
return true;
}
@Override
public boolean onPrepareActionMode(ActionMode mode, Menu menu)
{
updateTitle(mode);
updateActions(menu);
return true;
}
private void updateActions(Menu menu)
{
boolean showEdit = (selectedPositions.size() == 1);
boolean showColor = true;
boolean showArchive = true;
boolean showUnarchive = true;
if(showEdit) showColor = false;
for(int i : selectedPositions)
{
Habit h = loader.habitsList.get(i);
if(h.isArchived())
{
showColor = false;
showArchive = false;
}
else showUnarchive = false;
}
MenuItem itemEdit = menu.findItem(R.id.action_edit_habit);
MenuItem itemColor = menu.findItem(R.id.action_color);
MenuItem itemArchive = menu.findItem(R.id.action_archive_habit);
MenuItem itemUnarchive = menu.findItem(R.id.action_unarchive_habit);
itemEdit.setVisible(showEdit);
itemColor.setVisible(showColor);
itemArchive.setVisible(showArchive);
itemUnarchive.setVisible(showUnarchive);
}
private void updateTitle(ActionMode mode)
{
mode.setTitle("" + selectedPositions.size());
}
@Override
public boolean onActionItemClicked(final ActionMode mode, MenuItem item)
{
final LinkedList<Habit> selectedHabits = new LinkedList<>();
for(int i : selectedPositions)
selectedHabits.add(loader.habitsList.get(i));
Habit firstHabit = selectedHabits.getFirst();
switch(item.getItemId())
{
case R.id.action_archive_habit:
executeCommand(new ArchiveHabitsCommand(selectedHabits), null);
mode.finish();
return true;
case R.id.action_unarchive_habit:
executeCommand(new UnarchiveHabitsCommand(selectedHabits), null);
mode.finish();
return true;
case R.id.action_edit_habit:
{
EditHabitFragment frag = EditHabitFragment.editSingleHabitFragment(firstHabit.getId());
frag.setOnSavedListener(ListHabitsFragment.this);
frag.show(getFragmentManager(), "dialog");
return true;
}
case R.id.action_color:
{
ColorPickerDialog picker = ColorPickerDialog.newInstance(
R.string.color_picker_default_title, ColorHelper.palette,
firstHabit.color, 4, ColorPickerDialog.SIZE_SMALL);
picker.setOnColorSelectedListener(new ColorPickerSwatch.OnColorSelectedListener()
{
public void onColorSelected(int color)
{
executeCommand(new ChangeHabitColorCommand(selectedHabits, color), null);
mode.finish();
}
});
picker.show(getFragmentManager(), "picker");
return true;
}
case R.id.action_delete:
{
new AlertDialog.Builder(activity)
.setTitle(R.string.delete_habits)
.setMessage(R.string.delete_habits_message)
.setPositiveButton(android.R.string.yes, new DialogInterface.OnClickListener()
{
@Override
public void onClick(DialogInterface dialog, int which)
{
executeCommand(new DeleteHabitsCommand(selectedHabits), null);
mode.finish();
}
}).setNegativeButton(android.R.string.no, null)
.show();
return true;
}
}
return false;
}
@Override
public void onDestroyActionMode(ActionMode mode)
{
actionMode = null;
selectedPositions.clear();
adapter.notifyDataSetChanged();
listView.setDragEnabled(true);
}
}
public static final int INACTIVE_COLOR = Color.rgb(200, 200, 200);
public static final int INACTIVE_CHECKMARK_COLOR = Color.rgb(230, 230, 230);
public static final int HINT_INTERVAL = 5;
public static final int HINT_INTERVAL_OFFSET = 2;
public interface OnHabitClickListener
{
void onHabitClicked(Habit habit);
}
ListHabitsAdapter adapter;
DragSortListView listView;
ReplayableActivity activity;
TextView tvNameHeader;
long lastLongClick = 0;
private int tvNameWidth;
private int buttonCount;
private View llEmpty;
private View llHint;
private OnHabitClickListener habitClickListener;
private boolean isShortToggleEnabled;
private HabitListLoader loader;
private boolean showArchived;
private SharedPreferences prefs;
private ActionMode actionMode;
private List<Integer> selectedPositions;
private DragSortController dragSortController;
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState)
{
DisplayMetrics dm = getResources().getDisplayMetrics();
int width = (int) (dm.widthPixels / dm.density);
buttonCount = (int) ((width - 160) / 42.0);
tvNameWidth = (int) ((width - 30 - buttonCount * 42) * dm.density);
loader = new HabitListLoader();
loader.setListener(this);
loader.setCheckmarkCount(buttonCount);
View view = inflater.inflate(R.layout.list_habits_fragment, container, false);
tvNameHeader = (TextView) view.findViewById(R.id.tvNameHeader);
ProgressBar progressBar = (ProgressBar) view.findViewById(R.id.progressBar);
loader.setProgressBar(progressBar);
adapter = new ListHabitsAdapter(getActivity());
listView = (DragSortListView) view.findViewById(R.id.listView);
listView.setAdapter(adapter);
listView.setOnItemClickListener(this);
listView.setOnItemLongClickListener(this);
listView.setDropListener(this);
listView.setDragListener(new DragSortListView.DragListener()
{
@Override
public void drag(int from, int to)
{
}
@Override
public void startDrag(int position)
{
selectItem(position);
}
});
dragSortController = new DragSortController(listView) {
@Override
public View onCreateFloatView(int position)
{
return adapter.getView(position, null, null);
}
@Override
public void onDestroyFloatView(View floatView)
{
}
};
dragSortController.setRemoveEnabled(false);
listView.setFloatViewManager(dragSortController);
listView.setDragEnabled(true);
listView.setLongClickable(true);
llHint = view.findViewById(R.id.llHint);
llHint.setOnClickListener(this);
Typeface fontawesome = Typeface.createFromAsset(getActivity().getAssets(),
"fontawesome-webfont.ttf");
((TextView) view.findViewById(R.id.tvStarEmpty)).setTypeface(fontawesome);
llEmpty = view.findViewById(R.id.llEmpty);
loader.updateAllHabits(true);
setHasOptionsMenu(true);
selectedPositions = new LinkedList<>();
return view;
}
@Override
@SuppressWarnings("deprecation")
public void onAttach(Activity activity)
{
super.onAttach(activity);
this.activity = (ReplayableActivity) activity;
habitClickListener = (OnHabitClickListener) activity;
prefs = PreferenceManager.getDefaultSharedPreferences(activity);
}
@Override
public void onResume()
{
super.onResume();
Long timestamp = loader.getLastLoadTimestamp();
if (timestamp != null && timestamp != DateHelper.getStartOfToday())
loader.updateAllHabits(true);
updateEmptyMessage();
updateHeader();
showNextHint();
adapter.notifyDataSetChanged();
isShortToggleEnabled = prefs.getBoolean("pref_short_toggle", false);
}
private void updateHeader()
{
LayoutInflater inflater = activity.getLayoutInflater();
View view = getView();
if (view == null) return;
GregorianCalendar day = DateHelper.getStartOfTodayCalendar();
LinearLayout llButtonsHeader = (LinearLayout) view.findViewById(R.id.llButtonsHeader);
llButtonsHeader.removeAllViews();
for (int i = 0; i < buttonCount; i++)
{
View tvDay = inflater.inflate(R.layout.list_habits_header_check, null);
Button btCheck = (Button) tvDay.findViewById(R.id.tvCheck);
btCheck.setText(DateHelper.formatHeaderDate(day));
llButtonsHeader.addView(tvDay);
day.add(GregorianCalendar.DAY_OF_MONTH, -1);
}
}
@Override
public void onLoadFinished()
{
adapter.notifyDataSetChanged();
updateEmptyMessage();
}
@Override
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater)
{
super.onCreateOptionsMenu(menu, inflater);
inflater.inflate(R.menu.list_habits_options, menu);
MenuItem showArchivedItem = menu.findItem(R.id.action_show_archived);
showArchivedItem.setChecked(showArchived);
}
@Override
public void onCreateContextMenu(ContextMenu menu, View view, ContextMenuInfo menuInfo)
{
super.onCreateContextMenu(menu, view, menuInfo);
getActivity().getMenuInflater().inflate(R.menu.list_habits_context, menu);
AdapterContextMenuInfo info = (AdapterContextMenuInfo) menuInfo;
final Habit habit = loader.habits.get(info.id);
if (habit.isArchived()) menu.findItem(R.id.action_archive_habit).setVisible(false);
else menu.findItem(R.id.action_unarchive_habit).setVisible(false);
}
@Override
public boolean onOptionsItemSelected(MenuItem item)
{
switch (item.getItemId())
{
case R.id.action_add:
{
EditHabitFragment frag = EditHabitFragment.createHabitFragment();
frag.setOnSavedListener(this);
frag.show(getFragmentManager(), "dialog");
return true;
}
case R.id.action_show_archived:
{
showArchived = !showArchived;
loader.setIncludeArchived(showArchived);
loader.updateAllHabits(true);
activity.invalidateOptionsMenu();
return true;
}
default:
return super.onOptionsItemSelected(item);
}
}
@Override
public void onItemClick(AdapterView parent, View view, int position, long id)
{
if (new Date().getTime() - lastLongClick < 1000) return;
if(actionMode == null)
{
Habit habit = loader.habitsList.get(position);
habitClickListener.onHabitClicked(habit);
}
else
{
int k = selectedPositions.indexOf(position);
if(k < 0)
selectedPositions.add(position);
else
selectedPositions.remove(k);
if(selectedPositions.isEmpty()) actionMode.finish();
else actionMode.invalidate();
adapter.notifyDataSetChanged();
}
}
@Override
public boolean onItemLongClick(AdapterView<?> parent, View view, int position, long id)
{
selectItem(position);
return true;
}
private void selectItem(int position)
{
if(!selectedPositions.contains(position))
selectedPositions.add(position);
adapter.notifyDataSetChanged();
if(actionMode == null)
{
actionMode = getActivity().startActionMode(new ListHabitsActionBarCallback());
// listView.setDragEnabled(false);
}
if(actionMode != null) actionMode.invalidate();
}
@Override
public void onSaved(Command command, Object savedObject)
{
Habit h = (Habit) savedObject;
if (h == null) activity.executeCommand(command, null);
else activity.executeCommand(command, h.getId());
adapter.notifyDataSetChanged();
ReminderHelper.createReminderAlarms(activity);
if(actionMode != null) actionMode.finish();
}
private void updateEmptyMessage()
{
if (loader.getLastLoadTimestamp() == null) llEmpty.setVisibility(View.GONE);
else llEmpty.setVisibility(loader.habits.size() > 0 ? View.GONE : View.VISIBLE);
}
@Override
public boolean onLongClick(View v)
{
lastLongClick = new Date().getTime();
switch (v.getId())
{
case R.id.tvCheck:
onCheckmarkLongClick(v);
return true;
}
return false;
}
private void onCheckmarkLongClick(View v)
{
if (isShortToggleEnabled) return;
toggleCheck(v);
DialogHelper.vibrate(activity, 100);
}
private void toggleCheck(View v)
{
Long tag = (Long) v.getTag(R.string.habit_key);
Habit habit = loader.habits.get(tag);
int offset = (Integer) v.getTag(R.string.offset_key);
long timestamp = DateHelper.getStartOfDay(
DateHelper.getLocalTime() - offset * DateHelper.millisecondsInOneDay);
if (v.getTag(R.string.toggle_key).equals(2)) updateCheckmark(habit.color, (TextView) v, 0);
else updateCheckmark(habit.color, (TextView) v, 2);
executeCommand(habit.new ToggleRepetitionCommand(timestamp), habit.getId());
}
private void executeCommand(Command c, Long refreshKey)
{
activity.executeCommand(c, refreshKey);
}
private void hideHint()
{
llHint.animate().alpha(0f).setDuration(500).setListener(new AnimatorListenerAdapter()
{
@Override
public void onAnimationEnd(Animator animation)
{
llHint.setVisibility(View.GONE);
}
});
}
private void showNextHint()
{
Integer lastHintNumber = prefs.getInt("last_hint_number", -1);
Long lastHintTimestamp = prefs.getLong("last_hint_timestamp", -1);
if(DateHelper.getStartOfToday() > lastHintTimestamp)
showHint(lastHintNumber + 1);
}
private void showHint(int hintNumber)
{
String[] hints = activity.getResources().getStringArray(R.array.hints);
if(hintNumber >= hints.length) return;
prefs.edit().putInt("last_hint_number", hintNumber).apply();
prefs.edit().putLong("last_hint_timestamp", DateHelper.getStartOfToday()).apply();
TextView tvContent = (TextView) llHint.findViewById(R.id.hintContent);
tvContent.setText(hints[hintNumber]);
llHint.setAlpha(0.0f);
llHint.setVisibility(View.VISIBLE);
llHint.animate().alpha(1f).setDuration(500);
}
@Override
public void drop(int from, int to)
{
if(from == to) return;
if(actionMode != null) actionMode.finish();
loader.reorder(from, to);
adapter.notifyDataSetChanged();
loader.updateAllHabits(false);
}
@Override
public void onClick(View v)
{
switch (v.getId())
{
case R.id.tvCheck:
if (isShortToggleEnabled) toggleCheck(v);
else activity.showToast(R.string.long_press_to_toggle);
break;
case R.id.llHint:
hideHint();
break;
}
}
class ListHabitsAdapter extends BaseAdapter
{
private LayoutInflater inflater;
private Typeface fontawesome;
public ListHabitsAdapter(Context context)
{
inflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
fontawesome = Typeface.createFromAsset(context.getAssets(), "fontawesome-webfont.ttf");
}
@Override
public int getCount()
{
return loader.habits.size();
}
@Override
public Object getItem(int position)
{
return loader.habitsList.get(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 = loader.habitsList.get(position);
if (view == null ||
(Long) view.getTag(R.id.timestamp_key) != 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);
view.findViewById(R.id.tvName).setLayoutParams(params);
inflateCheckmarkButtons(view);
view.setTag(R.id.timestamp_key, DateHelper.getStartOfToday());
}
TextView tvStar = ((TextView) view.findViewById(R.id.tvStar));
TextView tvName = (TextView) view.findViewById(R.id.tvName);
LinearLayout llInner = (LinearLayout) view.findViewById(R.id.llInner);
LinearLayout llButtons = (LinearLayout) view.findViewById(R.id.llButtons);
llInner.setTag(R.string.habit_key, habit.getId());
updateNameAndIcon(habit, tvStar, tvName);
updateCheckmarkButtons(habit, llButtons);
boolean selected = selectedPositions.contains(position);
if(selected)
llInner.setBackgroundResource(R.drawable.selected_box);
else
{
if (android.os.Build.VERSION.SDK_INT >= 21)
llInner.setBackgroundResource(R.drawable.ripple_white);
else
llInner.setBackgroundColor(Color.WHITE);
}
return view;
}
private void inflateCheckmarkButtons(View view)
{
for (int i = 0; i < buttonCount; 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.setOnClickListener(ListHabitsFragment.this);
((LinearLayout) view.findViewById(R.id.llButtons)).addView(check);
}
}
}
private void updateCheckmarkButtons(Habit habit, LinearLayout llButtons)
{
int activeColor = getActiveColor(habit);
int m = llButtons.getChildCount();
Long habitId = habit.getId();
int isChecked[] = loader.checkmarks.get(habitId);
for (int i = 0; i < m; i++)
{
TextView tvCheck = (TextView) llButtons.getChildAt(i);
tvCheck.setTag(R.string.habit_key, habitId);
tvCheck.setTag(R.string.offset_key, i);
updateCheckmark(activeColor, tvCheck, isChecked[i]);
}
}
private void updateNameAndIcon(Habit habit, TextView tvStar, TextView tvName)
{
int activeColor = getActiveColor(habit);
tvName.setText(habit.name);
tvName.setTextColor(activeColor);
if (habit.isArchived())
{
tvStar.setText(getString(R.string.fa_archive));
tvStar.setTextColor(activeColor);
}
else
{
int score = loader.scores.get(habit.getId());
if (score < Habit.HALF_STAR_CUTOFF)
{
tvStar.setText(getString(R.string.fa_star_o));
tvStar.setTextColor(INACTIVE_COLOR);
}
else if (score < Habit.FULL_STAR_CUTOFF)
{
tvStar.setText(getString(R.string.fa_star_half_o));
tvStar.setTextColor(INACTIVE_COLOR);
}
else
{
tvStar.setText(getString(R.string.fa_star));
tvStar.setTextColor(activeColor);
}
}
}
private int getActiveColor(Habit habit)
{
int activeColor = habit.color;
if(habit.isArchived()) activeColor = INACTIVE_COLOR;
return activeColor;
}
private void updateCheckmark(int activeColor, TextView tvCheck, int check)
{
switch (check)
{
case 2:
tvCheck.setText(R.string.fa_check);
tvCheck.setTextColor(activeColor);
tvCheck.setTag(R.string.toggle_key, 2);
break;
case 1:
tvCheck.setText(R.string.fa_check);
tvCheck.setTextColor(INACTIVE_CHECKMARK_COLOR);
tvCheck.setTag(R.string.toggle_key, 1);
break;
case 0:
tvCheck.setText(R.string.fa_times);
tvCheck.setTextColor(INACTIVE_CHECKMARK_COLOR);
tvCheck.setTag(R.string.toggle_key, 0);
break;
}
}
public void onPostExecuteCommand(Long refreshKey)
{
if (refreshKey == null) loader.updateAllHabits(true);
else loader.updateHabit(refreshKey);
}
}

View File

@@ -14,7 +14,7 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.isoron.uhabits.dialogs;
package org.isoron.uhabits.fragments;
import android.app.backup.BackupManager;
import android.content.SharedPreferences;
@@ -23,7 +23,8 @@ import android.preference.PreferenceFragment;
import org.isoron.uhabits.R;
public class SettingsFragment extends PreferenceFragment implements SharedPreferences.OnSharedPreferenceChangeListener
public class SettingsFragment extends PreferenceFragment
implements SharedPreferences.OnSharedPreferenceChangeListener
{
@Override
public void onCreate(Bundle savedInstanceState)

View File

@@ -14,12 +14,11 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.isoron.uhabits.dialogs;
package org.isoron.uhabits.fragments;
import android.app.Fragment;
import android.graphics.Color;
import android.os.Bundle;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuInflater;
@@ -33,7 +32,7 @@ import org.isoron.helpers.ColorHelper;
import org.isoron.helpers.Command;
import org.isoron.helpers.DialogHelper;
import org.isoron.uhabits.R;
import org.isoron.uhabits.ReminderHelper;
import org.isoron.uhabits.helpers.ReminderHelper;
import org.isoron.uhabits.ShowHabitActivity;
import org.isoron.uhabits.models.Habit;
import org.isoron.uhabits.views.HabitHistoryView;
@@ -56,8 +55,6 @@ public class ShowHabitFragment extends Fragment implements DialogHelper.OnSavedL
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState)
{
Log.d("ShowHabitActivity", "Creating view...");
View view = inflater.inflate(R.layout.show_habit, container, false);
activity = (ShowHabitActivity) getActivity();
habit = activity.habit;
@@ -80,9 +77,9 @@ public class ShowHabitFragment extends Fragment implements DialogHelper.OnSavedL
tvStreaks.setTextColor(habit.color);
LinearLayout llOverview = (LinearLayout) view.findViewById(R.id.llOverview);
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"));
llOverview.addView(new RingView(activity,
(int) activity.getResources().getDimension(R.dimen.small_square_size) * 4,
habit.color, ((float) habit.getScore() / Habit.MAX_SCORE), activity.getString(R.string.habit_strength)));
LinearLayout llStrength = (LinearLayout) view.findViewById(R.id.llStrength);
llStrength.addView(new HabitScoreView(activity, habit,
@@ -130,7 +127,7 @@ public class ShowHabitFragment extends Fragment implements DialogHelper.OnSavedL
{
Habit h = (Habit) savedObject;
if(h == null) activity.executeCommand(command, null);
if (h == null) activity.executeCommand(command, null);
else activity.executeCommand(command, h.getId());
ReminderHelper.createReminderAlarms(activity);

View File

@@ -14,7 +14,7 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.isoron.uhabits;
package org.isoron.uhabits.helpers;
import android.app.AlarmManager;
import android.app.PendingIntent;
@@ -24,6 +24,8 @@ import android.net.Uri;
import android.os.Build;
import android.util.Log;
import org.isoron.helpers.DateHelper;
import org.isoron.uhabits.ReminderAlarmReceiver;
import org.isoron.uhabits.models.Habit;
import java.text.DateFormat;
@@ -40,43 +42,41 @@ public class ReminderHelper
public static void createReminderAlarm(Context context, Habit habit, Long reminderTime)
{
Uri uri = Uri.parse("content://org.isoron.uhabits/habit/" + habit.getId());
if (reminderTime == null)
{
Calendar calendar = Calendar.getInstance();
calendar.setTimeInMillis(System.currentTimeMillis());
calendar.set(Calendar.HOUR_OF_DAY, habit.reminder_hour);
calendar.set(Calendar.MINUTE, habit.reminder_min);
calendar.set(Calendar.HOUR_OF_DAY, habit.reminderHour);
calendar.set(Calendar.MINUTE, habit.reminderMin);
calendar.set(Calendar.SECOND, 0);
reminderTime = calendar.getTimeInMillis();
if (System.currentTimeMillis() > reminderTime)
{
reminderTime += AlarmManager.INTERVAL_DAY;
}
}
long timestamp = DateHelper.getStartOfDay(DateHelper.toLocalTime(reminderTime));
Uri uri = Uri.parse(String.format("content://org.isoron.uhabits/habit/%d", habit.getId()));
Intent alarmIntent = new Intent(context, ReminderAlarmReceiver.class);
alarmIntent.setAction(ReminderAlarmReceiver.ACTION_REMIND);
alarmIntent.setData(uri);
alarmIntent.putExtra("timestamp", timestamp);
alarmIntent.putExtra("reminderTime", reminderTime);
PendingIntent pendingIntent = PendingIntent.getBroadcast(context,
((int) (habit.getId() % Integer.MAX_VALUE)) + 1, alarmIntent,
PendingIntent.FLAG_UPDATE_CURRENT);
PendingIntent pendingIntent =
PendingIntent.getBroadcast(context, ((int) (habit.getId() % Integer.MAX_VALUE)) + 1,
alarmIntent, PendingIntent.FLAG_UPDATE_CURRENT);
AlarmManager manager = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE);
if (Build.VERSION.SDK_INT >= 19)
{
manager.setExact(AlarmManager.RTC_WAKEUP, reminderTime, pendingIntent);
}
else
{
manager.set(AlarmManager.RTC_WAKEUP, reminderTime, pendingIntent);
}
Log.d("Alarm", String.format("Setting alarm (%s): %s",
Log.d("ReminderHelper", String.format("Setting alarm (%s): %s",
DateFormat.getDateTimeInstance().format(new Date(reminderTime)), habit.name));
}
}

View File

@@ -0,0 +1,253 @@
/*
* Copyright (C) 2016 Alinson Santos Xavier
*
* This program is free software: you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by the Free
* Software Foundation, either version 3 of the License, or (at your option)
* any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*
*/
package org.isoron.uhabits.loaders;
import android.os.AsyncTask;
import android.os.Handler;
import android.view.View;
import android.widget.ProgressBar;
import org.isoron.helpers.DateHelper;
import org.isoron.uhabits.models.Habit;
import java.util.HashMap;
import java.util.List;
public class HabitListLoader
{
public interface Listener
{
void onLoadFinished();
}
private AsyncTask<Void, Integer, Void> currentFetchTask;
private int checkmarkCount;
private ProgressBar progressBar;
private Listener listener;
private Long lastLoadTimestamp;
public HashMap<Long, Habit> habits;
public List<Habit> habitsList;
public HashMap<Long, int[]> checkmarks;
public HashMap<Long, Integer> scores;
boolean includeArchived;
public void setIncludeArchived(boolean includeArchived)
{
this.includeArchived = includeArchived;
}
public void setProgressBar(ProgressBar progressBar)
{
this.progressBar = progressBar;
}
public void setCheckmarkCount(int checkmarkCount)
{
this.checkmarkCount = checkmarkCount;
}
public void setListener(Listener listener)
{
this.listener = listener;
}
public Long getLastLoadTimestamp()
{
return lastLoadTimestamp;
}
public HabitListLoader()
{
habits = new HashMap<>();
checkmarks = new HashMap<>();
scores = new HashMap<>();
}
public void reorder(int from, int to)
{
Habit fromHabit = habitsList.get(from);
Habit toHabit = habitsList.get(to);
habitsList.remove(from);
habitsList.add(to, fromHabit);
Habit.reorder(fromHabit, toHabit);
}
public void updateAllHabits(final boolean updateScoresAndCheckmarks)
{
if (currentFetchTask != null) currentFetchTask.cancel(true);
currentFetchTask = new AsyncTask<Void, Integer, Void>()
{
public HashMap<Long, Habit> newHabits;
public HashMap<Long, int[]> newCheckmarks;
public HashMap<Long, Integer> newScores;
public List<Habit> newHabitList;
@Override
protected Void doInBackground(Void... params)
{
newHabits = new HashMap<>();
newCheckmarks = new HashMap<>();
newScores = new HashMap<>();
newHabitList = Habit.getAll(includeArchived);
long dateTo = DateHelper.getStartOfDay(DateHelper.getLocalTime());
long dateFrom = dateTo - (checkmarkCount - 1) * DateHelper.millisecondsInOneDay;
int[] empty = new int[checkmarkCount];
for(Habit h : newHabitList)
{
Long id = h.getId();
newHabits.put(id, h);
if(checkmarks.containsKey(id))
newCheckmarks.put(id, checkmarks.get(id));
else
newCheckmarks.put(id, empty);
if(scores.containsKey(id))
newScores.put(id, scores.get(id));
else
newScores.put(id, 0);
}
commit();
if(!updateScoresAndCheckmarks) return null;
int current = 0;
for (Habit h : newHabitList)
{
if (isCancelled()) return null;
Long id = h.getId();
newScores.put(id, h.getScore());
newCheckmarks.put(id, h.getCheckmarks(dateFrom, dateTo));
publishProgress(current++, newHabits.size());
}
return null;
}
private void commit()
{
habits = newHabits;
scores = newScores;
checkmarks = newCheckmarks;
habitsList = newHabitList;
}
@Override
protected void onPreExecute()
{
if(progressBar != null)
{
progressBar.setIndeterminate(false);
progressBar.setProgress(0);
progressBar.setVisibility(View.VISIBLE);
}
}
@Override
protected void onProgressUpdate(Integer... values)
{
if(progressBar != null)
{
progressBar.setMax(values[1]);
progressBar.setProgress(values[0]);
}
if(listener != null) listener.onLoadFinished();
}
@Override
protected void onPostExecute(Void aVoid)
{
if (isCancelled()) return;
if(progressBar != null) progressBar.setVisibility(View.INVISIBLE);
lastLoadTimestamp = DateHelper.getStartOfToday();
currentFetchTask = null;
if(listener != null) listener.onLoadFinished();
}
};
currentFetchTask.execute();
}
public void updateHabit(final Long id)
{
new AsyncTask<Void, Void, Void>()
{
@Override
protected Void doInBackground(Void... params)
{
long dateTo = DateHelper.getStartOfDay(DateHelper.getLocalTime());
long dateFrom = dateTo - (checkmarkCount - 1) * DateHelper.millisecondsInOneDay;
Habit h = Habit.get(id);
habits.put(id, h);
scores.put(id, h.getScore());
checkmarks.put(id, h.getCheckmarks(dateFrom, dateTo));
return null;
}
@Override
protected void onPreExecute()
{
new Handler().postDelayed(new Runnable()
{
@Override
public void run()
{
if (getStatus() == Status.RUNNING)
{
if(progressBar != null)
{
progressBar.setIndeterminate(true);
progressBar.setVisibility(View.VISIBLE);
}
}
}
}, 500);
}
@Override
protected void onPostExecute(Void aVoid)
{
if(progressBar != null) progressBar.setVisibility(View.GONE);
if(listener != null)
listener.onLoadFinished();
}
}.execute();
}
}

View File

@@ -37,18 +37,15 @@ import org.isoron.helpers.DateHelper;
import org.isoron.uhabits.R;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
@Table(name = "Habits")
public class Habit extends Model
{
public static final int HALF_STAR_CUTOFF = 5999000;
public static final int FULL_STAR_CUTOFF = 12973000;
public static final int MAX_SCORE = 19259500;
private static boolean includeArchived = false;
public static final int HALF_STAR_CUTOFF = 9629750;
public static final int FULL_STAR_CUTOFF = 15407600;
public static final int MAX_SCORE = 19259500;
@Column(name = "name")
public String name;
@@ -57,10 +54,10 @@ public class Habit extends Model
public String description;
@Column(name = "freq_num")
public Integer freq_num;
public Integer freqNum;
@Column(name = "freq_den")
public Integer freq_den;
public Integer freqDen;
@Column(name = "color")
public Integer color;
@@ -69,10 +66,13 @@ public class Habit extends Model
public Integer position;
@Column(name = "reminder_hour")
public Integer reminder_hour;
public Integer reminderHour;
@Column(name = "reminder_min")
public Integer reminder_min;
public Integer reminderMin;
@Column(name = "reminder_days")
public Integer reminderDays;
@Column(name = "highlight")
public Integer highlight;
@@ -91,8 +91,9 @@ public class Habit extends Model
this.position = Habit.getCount();
this.highlight = 0;
this.archived = 0;
this.freq_den = 7;
this.freq_num = 3;
this.freqDen = 7;
this.freqNum = 3;
this.reminderDays = 127;
}
public static Habit get(Long id)
@@ -100,17 +101,10 @@ public class Habit extends Model
return Habit.load(Habit.class, id);
}
public static HashMap<Long, Habit> getAll()
public static List<Habit> getAll(boolean includeArchive)
{
List<Habit> habits = select().execute();
HashMap<Long, Habit> map = new HashMap<>();
for(Habit h : habits)
{
map.put(h.getId(), h);
}
return map;
if(includeArchive) return selectWithArchived().execute();
else return select().execute();
}
@SuppressLint("DefaultLocale")
@@ -121,21 +115,12 @@ public class Habit extends Model
protected static From select()
{
if(includeArchived)
return new Select().from(Habit.class).orderBy("position");
else
return new Select().from(Habit.class).where("archived = 0").orderBy("position");
return new Select().from(Habit.class).where("archived = 0").orderBy("position");
}
public static void setIncludeArchived(boolean includeArchived)
public static From selectWithArchived()
{
Habit.includeArchived = includeArchived;
rebuildOrder();
}
public static boolean isIncludeArchived()
{
return Habit.includeArchived;
return new Select().from(Habit.class).orderBy("position");
}
public static int getCount()
@@ -143,19 +128,10 @@ public class Habit extends Model
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")
return select().where("highlight = 1")
.orderBy("reminder_hour desc, reminder_min desc")
.execute();
}
@@ -164,23 +140,30 @@ public class Habit extends Model
return select().where("reminder_hour is not null").execute();
}
public static void reorder(int from, int to)
public static void reorder(Habit from, Habit to)
{
if (from == to) return;
if(from == to) return;
Habit h = Habit.getByPosition(from);
if (to < from) 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();
if (to.position < from.position)
{
new Update(Habit.class).set("position = position + 1")
.where("position >= ? and position < ?", to.position, from.position)
.execute();
}
else
{
new Update(Habit.class).set("position = position - 1")
.where("position > ? and position <= ?", from.position, to.position)
.execute();
}
h.position = to;
h.save();
from.position = to.position;
from.save();
}
public static void rebuildOrder()
{
List<Habit> habits = select().execute();
List<Habit> habits = selectWithArchived().execute();
ActiveAndroid.beginTransaction();
try
@@ -193,51 +176,24 @@ public class Habit extends Model
}
ActiveAndroid.setTransactionSuccessful();
}
finally
} finally
{
ActiveAndroid.endTransaction();
}
}
public static void roundTimestamps()
{
List<Repetition> reps = new Select().from(Repetition.class).execute();
for (Repetition r : reps)
{
r.timestamp = DateHelper.getStartOfDay(r.timestamp);
r.save();
}
}
public static void recomputeAllScores()
{
for (Habit habit : getHabits())
{
habit.deleteScoresNewerThan(0);
}
}
public static int getStarCount()
{
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)
{
this.name = model.name;
this.description = model.description;
this.freq_num = model.freq_num;
this.freq_den = model.freq_den;
this.freqNum = model.freqNum;
this.freqDen = model.freqDen;
this.color = model.color;
this.position = model.position;
this.reminder_hour = model.reminder_hour;
this.reminder_min = model.reminder_min;
this.reminderHour = model.reminderHour;
this.reminderMin = model.reminderMin;
this.reminderDays = model.reminderDays;
this.highlight = model.highlight;
this.archived = model.archived;
}
@@ -264,15 +220,33 @@ public class Habit extends Model
return (count > 0);
}
public boolean hasRepToday()
public void cascadeDelete()
{
return hasRep(DateHelper.getStartOfToday());
Long id = getId();
ActiveAndroid.beginTransaction();
try
{
new Delete().from(Checkmark.class).where("habit = ?", id).execute();
new Delete().from(Repetition.class).where("habit = ?", id).execute();
new Delete().from(Score.class).where("habit = ?", id).execute();
new Delete().from(Streak.class).where("habit = ?", id).execute();
delete();
ActiveAndroid.setTransactionSuccessful();
}
finally
{
ActiveAndroid.endTransaction();
}
}
public void deleteReps(long timestamp)
{
new Delete().from(Repetition.class).where("habit = ?", getId())
.and("timestamp = ?", timestamp).execute();
new Delete().from(Repetition.class)
.where("habit = ?", getId())
.and("timestamp = ?", timestamp)
.execute();
}
public void deleteCheckmarksNewerThan(long timestamp)
@@ -306,7 +280,7 @@ public class Habit extends Model
int nDays = (int) ((toTimestamp - fromTimestamp) / day) + 1;
int[] checks = new int[nDays];
if(cursor.moveToFirst())
if (cursor.moveToFirst())
{
do
{
@@ -327,7 +301,7 @@ public class Habit extends Model
long day = DateHelper.millisecondsInOneDay;
Checkmark newestCheckmark = getNewestCheckmark();
if(newestCheckmark == null)
if (newestCheckmark == null)
{
Repetition oldestRep = getOldestRep();
if (oldestRep == null) return;
@@ -339,10 +313,9 @@ public class Habit extends Model
beginning = newestCheckmark.timestamp + day;
}
if(beginning > today)
return;
if (beginning > today) return;
long beginningExtended = beginning - (long) (freq_den) * day;
long beginningExtended = beginning - (long) (freqDen) * day;
List<Repetition> reps = selectRepsFromTo(beginningExtended, today).execute();
int nDays = (int) ((today - beginning) / day) + 1;
@@ -362,10 +335,10 @@ public class Habit extends Model
{
int counter = 0;
for (int j = 0; j < freq_den; j++)
for (int j = 0; j < freqDen; j++)
if (checks[i + j] == 2) counter++;
if (counter >= freq_num) checks[i] = Math.max(checks[i], 1);
if (counter >= freqNum) checks[i] = Math.max(checks[i], 1);
}
ActiveAndroid.beginTransaction();
@@ -382,8 +355,7 @@ public class Habit extends Model
}
ActiveAndroid.setTransactionSuccessful();
}
finally
} finally
{
ActiveAndroid.endTransaction();
}
@@ -419,10 +391,7 @@ public class Habit extends Model
public Repetition getOldestRepNewerThan(long timestamp)
{
return selectReps()
.where("timestamp > ?", timestamp)
.limit(1)
.executeSingle();
return selectReps().where("timestamp > ?", timestamp).limit(1).executeSingle();
}
public void toggleRepetition(long timestamp)
@@ -447,10 +416,7 @@ public class Habit extends Model
public void archive()
{
archived = 1;
position = 9999;
save();
Habit.rebuildOrder();
}
public void unarchive()
@@ -471,13 +437,18 @@ public class Habit extends Model
public Score getNewestScore()
{
return new Select().from(Score.class).where("habit = ?", getId()).orderBy("timestamp desc")
.limit(1).executeSingle();
return new Select().from(Score.class)
.where("habit = ?", getId())
.orderBy("timestamp desc")
.limit(1)
.executeSingle();
}
public void deleteScoresNewerThan(long timestamp)
{
new Delete().from(Score.class).where("habit = ?", getId()).and("timestamp >= ?", timestamp)
new Delete().from(Score.class)
.where("habit = ?", getId())
.and("timestamp >= ?", timestamp)
.execute();
}
@@ -489,7 +460,7 @@ public class Habit extends Model
long today = DateHelper.getStartOfDay(DateHelper.getLocalTime());
long day = DateHelper.millisecondsInOneDay;
double freq = ((double) freq_num) / freq_den;
double freq = ((double) freqNum) / freqDen;
double multiplier = Math.pow(0.5, 1.0 / (14.0 / freq - 1));
Score newestScore = getNewestScore();
@@ -525,7 +496,7 @@ public class Habit extends Model
if (reps[reps.length - i - 1] == 2)
{
s.score += 1000000;
s.score = Math.min(s.score, 19259500);
s.score = Math.min(s.score, MAX_SCORE);
}
s.save();
@@ -533,8 +504,7 @@ public class Habit extends Model
}
ActiveAndroid.setTransactionSuccessful();
}
finally
} finally
{
ActiveAndroid.endTransaction();
}
@@ -542,24 +512,20 @@ public class Habit extends Model
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();
return new Select().from(Score.class)
.where("habit = ? and timestamp > ? and " +
"timestamp <= ? and (timestamp - ?) % ? = 0", getId(), fromTimestamp,
toTimestamp, offset, divisor)
.execute();
}
public List<Streak> getStreaks()
{
updateStreaks();
return new Select()
.from(Streak.class)
return new Select().from(Streak.class)
.where("habit = ?", getId())
.orderBy("end asc")
.execute();
@@ -567,8 +533,7 @@ public class Habit extends Model
public Streak getNewestStreak()
{
return new Select()
.from(Streak.class)
return new Select().from(Streak.class)
.where("habit = ?", getId())
.orderBy("end desc")
.limit(1)
@@ -582,7 +547,7 @@ public class Habit extends Model
long day = DateHelper.millisecondsInOneDay;
Streak newestStreak = getNewestStreak();
if(newestStreak == null)
if (newestStreak == null)
{
Repetition oldestRep = getOldestRep();
if (oldestRep == null) return;
@@ -597,7 +562,7 @@ public class Habit extends Model
beginning = oldestRep.timestamp;
}
if(beginning > today) return;
if (beginning > today) return;
int checks[] = getCheckmarks(beginning, today);
ArrayList<Long> list = new ArrayList<>();
@@ -605,17 +570,16 @@ public class Habit extends Model
long current = beginning;
list.add(current);
for(int i = 1; i < checks.length; i++)
for (int i = 1; i < checks.length; i++)
{
current += day;
int j = checks.length - i - 1;
if((checks[j + 1] == 0 && checks[j] > 0)) list.add(current);
if((checks[j + 1] > 0 && checks[j] == 0)) list.add(current - day);
if ((checks[j + 1] == 0 && checks[j] > 0)) list.add(current);
if ((checks[j + 1] > 0 && checks[j] == 0)) list.add(current - day);
}
if(list.size() % 2 == 1)
list.add(current);
if (list.size() % 2 == 1) list.add(current);
ActiveAndroid.beginTransaction();
@@ -632,8 +596,7 @@ public class Habit extends Model
}
ActiveAndroid.setTransactionSuccessful();
}
finally
} finally
{
ActiveAndroid.endTransaction();
}
@@ -657,7 +620,8 @@ public class Habit extends Model
{
savedHabit.save();
savedId = savedHabit.getId();
} else
}
else
{
savedHabit.save(savedId);
}
@@ -696,8 +660,8 @@ public class Habit extends Model
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);
hasIntervalChanged = (this.original.freqDen != this.modified.freqDen ||
this.original.freqNum != this.modified.freqNum);
}
public void execute()
@@ -758,54 +722,4 @@ public class Habit extends Model
execute();
}
}
public class ArchiveCommand extends Command
{
@Override
public void execute()
{
archive();
}
@Override
public void undo()
{
unarchive();
}
public Integer getExecuteStringId()
{
return R.string.toast_habit_archived;
}
public Integer getUndoStringId()
{
return R.string.toast_habit_unarchived;
}
}
public class UnarchiveCommand extends Command
{
@Override
public void execute()
{
unarchive();
}
@Override
public void undo()
{
archive();
}
public Integer getExecuteStringId()
{
return R.string.toast_habit_unarchived;
}
public Integer getUndoStringId()
{
return R.string.toast_habit_archived;
}
}
}

View File

@@ -21,11 +21,12 @@ import com.activeandroid.annotation.Column;
import com.activeandroid.annotation.Table;
@Table(name = "Repetitions")
public class Repetition extends Model {
public class Repetition extends Model
{
@Column(name = "habit")
public Habit habit;
@Column(name = "timestamp")
public Long timestamp;
@Column(name = "habit")
public Habit habit;
@Column(name = "timestamp")
public Long timestamp;
}

View File

@@ -23,12 +23,12 @@ import com.activeandroid.annotation.Table;
@Table(name = "Score")
public class Score extends Model
{
@Column(name = "habit")
public Habit habit;
@Column(name = "timestamp")
public Long timestamp;
@Column(name = "score")
public Integer score;
@Column(name = "habit")
public Habit habit;
@Column(name = "timestamp")
public Long timestamp;
@Column(name = "score")
public Integer score;
}

View File

@@ -22,52 +22,93 @@ import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Paint.Align;
import android.graphics.Rect;
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.R;
import org.isoron.uhabits.models.Checkmark;
import org.isoron.uhabits.models.Habit;
import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.GregorianCalendar;
import java.util.List;
public class HabitHistoryView extends View
public class HabitHistoryView extends ScrollableDataView
{
private Habit habit;
private int[] checks;
private Context context;
private int[] checkmarks;
private Paint pSquareBg, pSquareFg, pTextHeader;
private int squareSpacing;
private int squareSize, squareSpacing;
private int nColumns, offsetWeeks;
private float squareTextOffset;
private float headerTextOffset;
private int colorPrimary, colorPrimaryBrighter, grey;
private Float prevX, prevY;
private String wdays[];
private SimpleDateFormat dfMonth;
private SimpleDateFormat dfYear;
public HabitHistoryView(Context context, Habit habit, int squareSize)
private Calendar baseDate;
private int nDays;
private int todayWeekday;
private int colors[];
public HabitHistoryView(Context context, Habit habit, int baseSize)
{
super(context);
this.habit = habit;
this.context = context;
this.squareSize = squareSize;
colorPrimary = habit.color;
colorPrimaryBrighter = ColorHelper.mixColors(colorPrimary, Color.WHITE, 0.5f);
grey = Color.rgb(230, 230, 230);
setDimensions(baseSize);
createPaints();
createColors();
wdays = DateHelper.getShortDayNames();
dfMonth = new SimpleDateFormat("MMM");
dfYear = new SimpleDateFormat("yyyy");
}
private void updateDate()
{
baseDate = new GregorianCalendar();
baseDate.add(Calendar.DAY_OF_YEAR, -(dataOffset - 1) * 7);
nDays = (nColumns - 1) * 7;
todayWeekday = new GregorianCalendar().get(Calendar.DAY_OF_WEEK) % 7;
baseDate.add(Calendar.DAY_OF_YEAR, -nDays);
baseDate.add(Calendar.DAY_OF_YEAR, -todayWeekday);
}
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh)
{
super.onSizeChanged(w, h, oldw, oldh);
updateDate();
}
private void createColors()
{
int primaryColor = habit.color;
int primaryColorBright = ColorHelper.mixColors(primaryColor, Color.WHITE, 0.5f);
int grey = Color.rgb(230, 230, 230);
colors = new int[3];
colors[0] = grey;
colors[1] = primaryColorBright;
colors[2] = primaryColor;
}
private void setDimensions(int baseSize)
{
columnWidth = baseSize;
columnHeight = 8 * baseSize;
squareSpacing = 2;
}
private void createPaints()
{
pTextHeader = new Paint();
pTextHeader.setColor(Color.LTGRAY);
pTextHeader.setTextAlign(Align.LEFT);
pTextHeader.setTextSize(squareSize * 0.5f);
pTextHeader.setTextSize(columnWidth * 0.5f);
pTextHeader.setAntiAlias(true);
pSquareBg = new Paint();
@@ -76,178 +117,123 @@ public class HabitHistoryView extends View
pSquareFg = new Paint();
pSquareFg.setColor(Color.WHITE);
pSquareFg.setAntiAlias(true);
pSquareFg.setTextSize(squareSize * 0.5f);
pSquareFg.setTextSize(columnWidth * 0.5f);
pSquareFg.setTextAlign(Align.CENTER);
squareTextOffset = pSquareFg.getFontSpacing() * 0.4f;
headerTextOffset = pTextHeader.getFontSpacing() * 0.3f;
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec)
{
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
setMeasuredDimension(getMeasuredWidth(), 8 * squareSize);
}
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh)
{
nColumns = (w / squareSize) - 1;
fetchReps();
}
private void fetchReps()
protected void fetchData()
{
Calendar currentDate = new GregorianCalendar();
currentDate.add(Calendar.DAY_OF_YEAR, -offsetWeeks * 7);
currentDate.add(Calendar.DAY_OF_YEAR, -dataOffset * 7);
int dayOfWeek = currentDate.get(Calendar.DAY_OF_WEEK) % 7;
long dateTo = DateHelper.getStartOfToday();
for (int i = 0; i < 7 - dayOfWeek; i++)
dateTo += DateHelper.millisecondsInOneDay;
for (int i = 0; i < offsetWeeks * 7; i++)
for (int i = 0; i < dataOffset * 7; i++)
dateTo -= DateHelper.millisecondsInOneDay;
long dateFrom = dateTo;
for (int i = 0; i < nColumns * 7; i++)
for (int i = 0; i < (nColumns - 1) * 7; i++)
dateFrom -= DateHelper.millisecondsInOneDay;
checks = habit.getCheckmarks(dateFrom, dateTo);
checkmarks = habit.getCheckmarks(dateFrom, dateTo);
updateDate();
}
private String previousMonth;
private String previousYear;
private boolean justPrintedYear;
@Override
protected void onDraw(Canvas canvas)
{
super.onDraw(canvas);
Rect square = new Rect(0, 0, squareSize - squareSpacing, squareSize - squareSpacing);
Rect location = new Rect(0, 0, columnWidth - squareSpacing, columnWidth - squareSpacing);
Calendar currentDate = new GregorianCalendar();
currentDate.add(Calendar.DAY_OF_YEAR, - (offsetWeeks - 1) * 7);
previousMonth = "";
previousYear = "";
justPrintedYear = false;
int nDays = nColumns * 7;
int todayWeekday = new GregorianCalendar().get(Calendar.DAY_OF_WEEK) % 7;
GregorianCalendar currentDate = (GregorianCalendar) baseDate.clone();
currentDate.add(Calendar.DAY_OF_YEAR, -nDays);
currentDate.add(Calendar.DAY_OF_YEAR, -todayWeekday);
SimpleDateFormat dfMonth = new SimpleDateFormat("MMM");
SimpleDateFormat dfYear = new SimpleDateFormat("yyyy");
String previousMonth = "";
String previousYear = "";
int colors[] = {grey, colorPrimaryBrighter, colorPrimary};
String markers[] =
{context.getString(R.string.fa_times), context.getString(R.string.fa_check),
context.getString(R.string.fa_check)};
float squareTextOffset = pSquareFg.getFontSpacing() * 0.4f;
float headerTextOffset = pTextHeader.getFontSpacing() * 0.3f;
boolean justPrintedYear = false;
int k = nDays;
for (int i = 0; i < nColumns; i++)
for (int column = 0; column < nColumns - 1; column++)
{
String month = dfMonth.format(currentDate.getTime());
String year = dfYear.format(currentDate.getTime());
if (!month.equals(previousMonth))
{
int offset = 0;
if (justPrintedYear) offset += squareSize;
canvas.drawText(month, square.left + offset, square.bottom - headerTextOffset,
pTextHeader);
previousMonth = month;
justPrintedYear = false;
} else if (!year.equals(previousYear))
{
canvas.drawText(year, square.left, square.bottom - headerTextOffset, pTextHeader);
previousYear = year;
justPrintedYear = true;
} else
{
justPrintedYear = false;
}
square.offset(0, squareSize);
for (int j = 0; j < 7; j++)
{
if (!(i == nColumns - 1 && offsetWeeks == 0 && j > todayWeekday))
{
if(k >= checks.length) pSquareBg.setColor(colors[0]);
else pSquareBg.setColor(colors[checks[k]]);
canvas.drawRect(square, pSquareBg);
canvas.drawText(Integer.toString(currentDate.get(Calendar.DAY_OF_MONTH)),
square.centerX(), square.centerY() + squareTextOffset, pSquareFg);
}
currentDate.add(Calendar.DAY_OF_MONTH, 1);
square.offset(0, squareSize);
k--;
}
square.offset(squareSize, -8 * squareSize);
drawColumn(canvas, location, currentDate, column);
location.offset(columnWidth, -columnHeight);
}
String wdays[] = {"Sat", "Sun", "Mon", "Tue", "Wed", "Thu", "Fri"};
drawAxis(canvas, location);
}
private void drawColumn(Canvas canvas, Rect location, GregorianCalendar date, int column)
{
drawColumnHeader(canvas, location, date);
location.offset(0, columnWidth);
for (int j = 0; j < 7; j++)
{
if (!(column == nColumns - 2 && dataOffset == 0 && j > todayWeekday))
{
int checkmarkOffset = nDays - 7 * column - j;
drawSquare(canvas, location, date, checkmarkOffset);
}
date.add(Calendar.DAY_OF_MONTH, 1);
location.offset(0, columnWidth);
}
}
private void drawSquare(Canvas canvas, Rect location, GregorianCalendar date,
int checkmarkOffset)
{
if (checkmarkOffset >= checkmarks.length) pSquareBg.setColor(colors[0]);
else pSquareBg.setColor(colors[checkmarks[checkmarkOffset]]);
canvas.drawRect(location, pSquareBg);
String text = Integer.toString(date.get(Calendar.DAY_OF_MONTH));
canvas.drawText(text, location.centerX(), location.centerY() + squareTextOffset, pSquareFg);
}
private void drawAxis(Canvas canvas, Rect location)
{
for (int i = 0; i < 7; i++)
{
square.offset(0, squareSize);
canvas.drawText(wdays[i], square.left + headerTextOffset,
square.bottom - headerTextOffset, pTextHeader);
location.offset(0, columnWidth);
canvas.drawText(wdays[i], location.left + headerTextOffset,
location.bottom - headerTextOffset, pTextHeader);
}
}
@Override
public boolean onTouchEvent(MotionEvent event)
private void drawColumnHeader(Canvas canvas, Rect location, GregorianCalendar date)
{
int action = event.getAction();
String month = dfMonth.format(date.getTime());
String year = dfYear.format(date.getTime());
int pointerIndex = MotionEventCompat.getActionIndex(event);
final float x = MotionEventCompat.getX(event, pointerIndex);
final float y = MotionEventCompat.getY(event, pointerIndex);
if (action == MotionEvent.ACTION_DOWN)
if (!month.equals(previousMonth))
{
prevX = x;
prevY = y;
int offset = 0;
if (justPrintedYear) offset += columnWidth;
canvas.drawText(month, location.left + offset, location.bottom - headerTextOffset,
pTextHeader);
previousMonth = month;
justPrintedYear = false;
}
if (action == MotionEvent.ACTION_MOVE)
else if (!year.equals(previousYear))
{
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 newOffsetWeeks = offsetWeeks + (int) (dx / squareSize);
newOffsetWeeks = Math.max(0, newOffsetWeeks);
if (newOffsetWeeks != offsetWeeks)
{
offsetWeeks = newOffsetWeeks;
fetchReps();
invalidate();
return true;
canvas.drawText(year, location.left, location.bottom - headerTextOffset, pTextHeader);
previousYear = year;
justPrintedYear = true;
}
else
return false;
{
justPrintedYear = false;
}
}
}

View File

@@ -21,9 +21,6 @@ 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;
@@ -33,30 +30,23 @@ import org.isoron.uhabits.models.Score;
import java.text.SimpleDateFormat;
import java.util.List;
public class HabitScoreView extends View
public class HabitScoreView extends ScrollableDataView
{
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);
@@ -75,8 +65,11 @@ public class HabitScoreView extends View
pGrid.setAntiAlias(true);
pGrid.setStrokeWidth(columnWidth * 0.05f);
this.columnWidth = columnWidth;
columnHeight = 8 * columnWidth;
barHeaderHeight = columnWidth;
headerHeight = columnWidth;
footerHeight = columnWidth;
em = pText.getFontSpacing();
colors = new int[4];
@@ -87,7 +80,7 @@ public class HabitScoreView extends View
colors[2] = ColorHelper.mixColors(colors[0], colors[3], 0.33f);
}
private void fetchScores()
protected void fetchData()
{
long toTimestamp = DateHelper.getStartOfToday();
@@ -98,23 +91,8 @@ public class HabitScoreView extends View
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();
scores = habit.getScores(fromTimestamp, toTimestamp,
BUCKET_SIZE * DateHelper.millisecondsInOneDay, toTimestamp);
}
@Override
@@ -123,10 +101,9 @@ public class HabitScoreView extends View
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);
rGrid.offset(0, headerHeight);
drawGrid(canvas, rGrid);
SimpleDateFormat dfMonth = new SimpleDateFormat("MMM");
@@ -150,7 +127,7 @@ public class HabitScoreView extends View
RectF r = new RectF(0, 0, columnWidth, columnWidth);
r.offset(offset * columnWidth,
barHeaderHeight + columnHeight - height - columnWidth / 2);
headerHeight + columnHeight - height - columnWidth / 2);
if (prevR != null)
{
@@ -163,14 +140,11 @@ public class HabitScoreView extends View
prevR = r;
r = new RectF(0, 0, columnWidth, columnHeight);
r.offset(offset * columnWidth, barHeaderHeight);
r.offset(offset * columnWidth, headerHeight);
if (!month.equals(previousMonth))
{
canvas.drawText(month, r.centerX(), r.bottom + lineHeight * 1.2f, pText);
} else
{
else
canvas.drawText(day, r.centerX(), r.bottom + lineHeight * 1.2f, pText);
}
previousMonth = month;
@@ -179,10 +153,6 @@ public class HabitScoreView extends View
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;
@@ -219,50 +189,4 @@ public class HabitScoreView extends View
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;
}
}

View File

@@ -21,41 +21,51 @@ import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Rect;
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.Streak;
import java.text.SimpleDateFormat;
import java.util.List;
public class HabitStreakView extends View
public class HabitStreakView extends ScrollableDataView
{
private Habit habit;
private int columnWidth, columnHeight, nColumns;
private Paint pText, pBar;
private List<Streak> streaks;
private int dataOffset;
private long maxStreakLength;
private int barHeaderHeight;
private int[] colors;
private float prevX;
private float prevY;
public HabitStreakView(Context context, Habit habit, int columnWidth)
{
super(context);
this.habit = habit;
this.columnWidth = columnWidth;
setDimensions(columnWidth);
createPaints();
createColors();
}
private void setDimensions(int baseSize)
{
this.columnWidth = baseSize;
columnHeight = 8 * baseSize;
headerHeight = baseSize;
footerHeight = baseSize;
}
private void createColors()
{
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 createPaints()
{
pText = new Paint();
pText.setColor(Color.LTGRAY);
pText.setTextAlign(Paint.Align.CENTER);
@@ -66,41 +76,16 @@ public class HabitStreakView extends View
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()
protected void fetchData()
{
streaks = habit.getStreaks();
for(Streak s : streaks)
for (Streak s : streaks)
maxStreakLength = Math.max(maxStreakLength, s.length);
}
@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)
@@ -116,73 +101,26 @@ public class HabitStreakView extends View
String previousMonth = "";
for (int offset = 0; offset < nColumns && start+offset < nStreaks; offset++)
for (int offset = 0; offset < nColumns && start + offset < nStreaks; offset++)
{
String month = dfMonth.format(streaks.get(start+offset).start);
String month = dfMonth.format(streaks.get(start + offset).start);
long l = streaks.get(offset+start).length;
long l = streaks.get(offset + start).length;
double lRelative = ((double) l) / maxStreakLength;
pBar.setColor(colors[(int) Math.floor(lRelative*3)]);
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);
Rect r = new Rect(0, 0, columnWidth - 2, height);
r.offset(offset * columnWidth, headerHeight + columnHeight - height);
canvas.drawRect(r, pBar);
canvas.drawText(Long.toString(l), r.centerX(), r.top - barHeaderOffset, pBar);
if(!month.equals(previousMonth))
if (!month.equals(previousMonth))
canvas.drawText(month, r.centerX(), r.bottom + lineHeight * 1.2f, pText);
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(streaks.size() - nColumns, newDataOffset));
if (newDataOffset != dataOffset)
{
dataOffset = newDataOffset;
invalidate();
return true;
}
else
return false;
}
}

View File

@@ -52,7 +52,7 @@ public class RingView extends View
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec)
{
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
setMeasuredDimension(size, size + (int) (2*lineHeight));
setMeasuredDimension(size, size + (int) (2 * lineHeight));
}
@Override
@@ -75,9 +75,10 @@ public class RingView extends View
pRing.setColor(Color.GRAY);
pRing.setTextSize(size * 0.2f);
lineHeight = pRing.getFontSpacing();
canvas.drawText(String.format("%.0f%%", perc * 100), r.centerX(), r.centerY()+lineHeight/3, pRing);
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);
}
}

View File

@@ -0,0 +1,105 @@
/*
* Copyright (C) 2016 Alinson Santos Xavier
*
* This program is free software: you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by the Free
* Software Foundation, either version 3 of the License, or (at your option)
* any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*
*/
package org.isoron.uhabits.views;
import android.content.Context;
import android.support.v4.view.MotionEventCompat;
import android.view.MotionEvent;
import android.view.View;
public abstract class ScrollableDataView extends View
{
protected int dataOffset;
protected int nColumns;
protected int columnWidth, columnHeight;
protected int headerHeight, footerHeight;
private float prevX, prevY;
public ScrollableDataView(Context context)
{
super(context);
}
protected abstract void fetchData();
protected boolean move(float dx)
{
int newDataOffset = dataOffset + (int) (dx / columnWidth);
newDataOffset = Math.max(0, newDataOffset);
if (newDataOffset != dataOffset)
{
dataOffset = newDataOffset;
fetchData();
invalidate();
return true;
}
else return false;
}
@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;
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec)
{
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
setMeasuredDimension(getMeasuredWidth(), columnHeight + headerHeight + footerHeight);
}
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh)
{
super.onSizeChanged(w, h, oldw, oldh);
nColumns = w / columnWidth;
fetchData();
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 338 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 393 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 492 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 563 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 822 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 337 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 410 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 214 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 244 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 289 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 319 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 554 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 207 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 243 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 338 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 390 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 541 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 602 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 337 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 381 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 593 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 674 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 922 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 575 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 683 B

View File

Before

Width:  |  Height:  |  Size: 48 KiB

After

Width:  |  Height:  |  Size: 48 KiB

View File

Before

Width:  |  Height:  |  Size: 16 KiB

After

Width:  |  Height:  |  Size: 16 KiB

View File

Before

Width:  |  Height:  |  Size: 50 KiB

After

Width:  |  Height:  |  Size: 50 KiB

View File

Before

Width:  |  Height:  |  Size: 42 KiB

After

Width:  |  Height:  |  Size: 42 KiB

View File

@@ -1,6 +0,0 @@
<ripple xmlns:android="http://schemas.android.com/apk/res/android"
android:color="@color/grey"> <!-- ripple color -->
<item android:drawable="@color/white"/> <!-- normal color -->
</ripple>

View File

@@ -0,0 +1,8 @@
<ripple xmlns:android="http://schemas.android.com/apk/res/android"
android:color="?android:colorControlHighlight">
<item android:id="@android:id/mask">
<color android:color="@android:color/white" />
</item>
</ripple>

View File

@@ -0,0 +1,5 @@
<ripple xmlns:android="http://schemas.android.com/apk/res/android"
android:color="?android:colorControlHighlight">
<item android:drawable="@color/white" />
</ripple>

View File

@@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android" android:shape="rectangle" >
<solid android:color="@color/grey_100" />
<stroke android:width="2dip" android:color="@color/grey_500"/>
</shape>

View File

@@ -1,7 +1,7 @@
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/container"
tools:context="org.isoron.uhabits.dialogs.EditHabitFragment"
tools:context="org.isoron.uhabits.fragments.EditHabitFragment"
tools:ignore="MergeRootFrame"
style="@style/dialogForm">
@@ -15,15 +15,15 @@
<EditText
android:id="@+id/input_name"
android:hint="Name"
android:hint="@string/name"
style="@style/dialogFormInput">
<requestFocus />
</EditText>
<ImageButton
android:id="@+id/button_pick_color"
android:src="@drawable/ic_action_pick_color"
android:id="@+id/buttonPickColor"
android:src="@drawable/ic_action_color_light"
style="@style/dialogFormInputColor" />
</LinearLayout>
@@ -72,7 +72,12 @@
android:text="@string/reminder" />
<TextView
android:id="@+id/input_reminder_time"
android:id="@+id/inputReminderTime"
style="@style/dialogFormTimePicker" />
<TextView
android:id="@+id/inputReminderDays"
android:text="Any weekday"
style="@style/dialogFormTimePicker" />
</LinearLayout>

View File

@@ -8,7 +8,7 @@
<fragment
android:id="@+id/fragment1"
android:name="org.isoron.uhabits.dialogs.ListHabitsFragment"
android:name="org.isoron.uhabits.fragments.ListHabitsFragment"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:layout="@layout/list_habits_fragment" />

View File

@@ -9,12 +9,9 @@
android:id="@+id/listView"
style="@style/habitsListStyle"
dslv:drag_enabled="true"
dslv:drag_handle_id="@drawable/habits_header_check"
dslv:drag_start_mode="onMove"
dslv:float_alpha="0.5"
dslv:drag_start_mode="onLongPress"
dslv:sort_enabled="true"
dslv:track_drag_sort="false"
dslv:use_default_controller="true"
/>
<LinearLayout
@@ -60,4 +57,32 @@
android:layout_marginTop="37dp"
/>
<LinearLayout
android:id="@+id/llHint"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@color/indigo_500"
android:layout_alignParentBottom="true"
android:orientation="vertical"
android:animateLayoutChanges="true"
android:visibility="invisible"
style="@style/cardStyle">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/hint_title"
android:textStyle="bold"
android:textColor="@color/white"
android:layout_weight="5"/>
<TextView
android:id="@+id/hintContent"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textColor="@color/white"
android:layout_weight="5"/>
</LinearLayout>
</RelativeLayout>

View File

@@ -8,7 +8,7 @@
<fragment
android:id="@+id/fragment2"
android:name="org.isoron.uhabits.dialogs.ShowHabitFragment"
android:name="org.isoron.uhabits.fragments.ShowHabitFragment"
android:layout_width="match_parent"
android:layout_height="match_parent" />

View File

@@ -0,0 +1,29 @@
<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android">
<item
android:id="@+id/action_edit_habit"
android:title="@string/edit"
android:icon="@drawable/ic_action_edit_dark"/>
<item
android:id="@+id/action_color"
android:title="@string/color_picker_default_title"
android:icon="@drawable/ic_action_color_dark"/>
<item
android:id="@+id/action_archive_habit"
android:title="@string/archive"
android:icon="@drawable/ic_action_archive_dark" />
<item
android:id="@+id/action_unarchive_habit"
android:title="@string/unarchive"
android:icon="@drawable/ic_action_unarchive_dark"/>
<item
android:id="@+id/action_delete"
android:title="@string/delete"
android:showAsAction="never" />
</menu>

View File

@@ -1,19 +1,27 @@
<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android" >
<menu xmlns:android="http://schemas.android.com/apk/res/android">
<item
android:id="@+id/action_edit_habit"
android:title="@string/edit">
</item>
android:title="@string/edit"
android:icon="@drawable/ic_action_edit_light"/>
<item
android:id="@+id/action_color"
android:title="@string/color_picker_default_title"
android:icon="@drawable/ic_action_color_light"/>
<item
android:id="@+id/action_archive_habit"
android:title="@string/archive">
</item>
android:title="@string/archive"
android:icon="@drawable/ic_action_archive_light" />
<item
android:id="@+id/action_unarchive_habit"
android:title="@string/unarchive">
</item>
android:title="@string/unarchive"
android:icon="@drawable/ic_action_unarchive_light"/>
<item
android:id="@+id/action_delete"
android:title="@string/delete" />
</menu>

View File

@@ -3,7 +3,7 @@
tools:context="org.isoron.uhabits.MainActivity" >
<item android:id="@+id/action_show_archived"
android:title="Show archived"
android:title="@string/show_archived"
android:enabled="true"
android:checkable="true" />

View File

@@ -0,0 +1,103 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
~ Copyright (C) 2016 Alinson Santos Xavier
~
~ This program is free software: you can redistribute it and/or modify it
~ under the terms of the GNU General Public License as published by the Free
~ Software Foundation, either version 3 of the License, or (at your option)
~ any later version.
~
~ This program is distributed in the hope that it will be useful, but WITHOUT
~ ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
~ FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
~ more details.
~
~ You should have received a copy of the GNU General Public License
~ along with this program. If not, see <http://www.gnu.org/licenses/>.
~
~
-->
<resources>
<string name="app_name">Hábitos</string>
<string name="action_settings">Configurações</string>
<string name="add_habit">Adicionar hábito</string>
<string name="archive">Arquivar</string>
<string name="check">Marcar</string>
<string name="clear">Limpar</string>
<string name="clear_label">Limpar</string>
<string name="color_picker_default_title">Mudar cor</string>
<string name="create_habit">Criar hábito</string>
<string name="days">dias</string>
<string name="delete">Deletar</string>
<string name="description">Descrição</string>
<string name="description_hint">Pergunta (por ex., \"você meditou hoje?\")</string>
<string name="discard">Cancelar</string>
<string name="edit">Editar</string>
<string name="edit_habit">Editar hábito</string>
<string name="habit_strength">Estabilidade</string>
<string name="history">Histórico</string>
<string name="interval_15_minutes">15 minutos</string>
<string name="interval_1_hour">1 hora</string>
<string name="interval_2_hour">2 horas</string>
<string name="interval_30_minutes">30 minutos</string>
<string name="interval_4_hour">4 horas</string>
<string name="interval_8_hour">8 horas</string>
<string name="intro_title_1">Bem vindo</string>
<string name="long_press_to_toggle">Sustente por um segundo para marcar ou desmarcar</string>
<string name="main_activity_title">Hábitos</string>
<string name="overview">Visão geral</string>
<string name="reminder">Lembrete</string>
<string name="reminder_off">Desligado</string>
<string name="repeat">Repetir</string>
<string name="save">Salvar</string>
<string name="select_day">Selecionar mês e dia</string>
<string name="select_hours">Selecionar horas</string>
<string name="select_minutes">Selecionar minutos</string>
<string name="select_year">Selecionar ano</string>
<string name="snooze">Mais tarde</string>
<string name="streaks">Correntes</string>
<string name="no_habits_found">Você não tem nenhum hábito ativo</string>
<string name="time_separator"></string>
<string name="toast_habit_archived">Hábitos arquivados.</string>
<string name="toast_habit_changed">Hábito modificado.</string>
<string name="toast_habit_changed_back">Hábito restaurado.</string>
<string name="toast_habit_created">Hábito criado.</string>
<string name="toast_habit_deleted">Hábito deletado.</string>
<string name="toast_habit_unarchived">Hábitos restaurados.</string>
<string name="toast_nothing_to_redo">Nada para refazer.</string>
<string name="toast_nothing_to_undo">Nada para desfazer.</string>
<string name="toast_repetition_toggled">Marcado.</string>
<string name="unarchive">Desarquivar</string>
<string name="validation_at_most_one_rep_per_day">Você pode ter no máximo uma repetição por dia.</string>
<string name="validation_name_should_not_be_blank">Nome não pode ficar em branco.</string>
<string name="validation_number_should_be_positive">Número precisa ser positivo.</string>
<string name="done_label">Pronto</string>
<string name="pref_view_source_code">Ver código-fonte no GitHub</string>
<string name="pref_view_app_introduction">Assistir introdução ao aplicativo</string>
<string name="pref_toggle_title">Marcar repetições com um toque curto</string>
<string name="pref_toggle_description">Mais conveniente, mas pode causar marcações acidentais</string>
<string name="pref_snooze_interval_title">Duração do \"mais tarde\" nos lembretes</string>
<string name="pref_send_feedback">Mandar sugestões para o desenvolvedor</string>
<string name="pref_rate_this_app">Avaliar esse app no Google Play</string>
<string name="links">Links</string>
<string name="behavior">Interação</string>
<string name="name">Nome</string>
<string name="times_every">vezes em</string>
<string name="show_archived">Mostrar hábitos arquivados</string>
<string name="snooze_interval">Duração do \"mais tarde\"</string>
<string name="settings">Configurações</string>
<string name="intro_description_1">Loop é um aplicativo que te ajuda a criar e manter bons hábitos.</string>
<string name="intro_title_2">Adicione alguns hábitos</string>
<string name="intro_description_2">Todo dia, depois de praticar o seu hábito, marque no aplicativo.</string>
<string name="intro_description_3">Hábitos praticados regularmente por um longo período recebem uma estrela.</string>
<string name="intro_title_3">Continue praticando</string>
<string name="intro_title_4">Acompanhe o seu progresso</string>
<string name="intro_description_4">Veja como seus hábitos estão progredindo através de diagramas.</string>
<string name="hint_title">Dica</string>
<string name="hint_drag">Para mudar a ordem dos hábitos, aperte no nome do hábito, sustente e arraste.</string>
<string name="hint_landscape">Para ver mais dias, coloque seu aparelho em modo paisagem.</string>
<string name="delete_habits">Deletar hábitos</string>
<string name="toast_habit_restored">Hábitos restaurados.</string>
<string name="delete_habits_message">Os hábitos escolhidos serão deletados permanentemente. Esta ação não pode ser desfeita.</string>
</resources>

View File

@@ -7,6 +7,13 @@
</style>
<style name="habitsListCheckStyle" parent="habitsListCheckBasicStyle">
<item name="android:background">@drawable/ripple_background</item>
<item name="android:background">@drawable/ripple_transparent</item>
</style>
<style name="habitsListItemInnerPanelStyle" parent="cardStyle">
<item name="android:orientation">horizontal</item>
<item name="android:padding">3dp</item>
<item name="android:background">@drawable/ripple_white</item>
<item name="android:elevation">1dp</item>
</style>
</resources>

View File

@@ -0,0 +1,91 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="app_name">循环习惯记录</string>
<string name="main_activity_title">习惯</string>
<string name="action_settings">设置</string>
<string name="edit">编辑</string>
<string name="delete">删除</string>
<string name="archive">存档</string>
<string name="unarchive">取消存档</string>
<string name="add_habit">添加新习惯</string>
<string name="color_picker_default_title">选择颜色</string>
<string name="toast_habit_created">习惯建成.</string>
<string name="toast_habit_deleted">习惯删除.</string>
<string name="toast_nothing_to_undo">Nothing to undo.</string>
<string name="toast_nothing_to_redo">Nothing to redo.</string>
<string name="toast_habit_changed">习惯修改了.</string>
<string name="toast_habit_changed_back">取消了修改.</string>
<string name="toast_repetition_toggled">Repetition toggled.</string>
<string name="toast_habit_archived">习惯存档成功.</string>
<string name="toast_habit_unarchived">习惯取消存档成功.</string>
<!-- Date and time picker -->
<string name="done_label">完陈</string>
<string name="clear_label">取消</string>
<string name="select_hours">选择小时</string>
<string name="select_minutes">选择分钟</string>
<string name="year_picker_description">Year list</string>
<string name="select_day">Select month and day</string>
<string name="select_year">Select year</string>
<string name="title_activity_show_habit" />
<string name="overview">总览</string>
<string name="habit_strength">习惯强度</string>
<string name="history">历史</string>
<string name="clear">取消</string>
<string name="description">描叙</string>
<string name="description_hint">提醒问题你xxx了吗</string>
<string name="repeat">重复</string>
<string name="times_every">次每</string>
<string name="days"></string>
<string name="reminder">提醒</string>
<string name="discard">丢弃</string>
<string name="save">保存</string>
<string name="streaks">链条</string>
<string name="no_habits_found">你没有习惯</string>
<string name="long_press_to_toggle">长按 标记/取消标记</string>
<string name="reminder_off"></string>
<string name="validation_name_should_not_be_blank">名字不能是空白.</string>
<string name="validation_number_should_be_positive">数字必须大于零.</string>
<string name="validation_at_most_one_rep_per_day">每天最多重复一次</string>
<string name="create_habit">新建习惯</string>
<string name="edit_habit">编辑习惯</string>
<string name="check">标记</string>
<string name="snooze">以后再说</string>
<!-- App introduction -->
<string name="intro_title_1">欢迎</string>
<string name="intro_description_1">循环习惯记录器帮助你建立和维持好习惯.</string>
<string name="intro_title_2">建立一些新习惯</string>
<string name="intro_description_2">每天,你完成一项习惯后,在应用上做一个标记</string>
<string name="intro_title_3">保持习惯</string>
<string name="intro_description_3">你可以得到一个完整的星星在连续性的完成习惯达到一定时间</string>
<string name="intro_title_4">记录你的进步</string>
<string name="intro_description_4">详细的图形显示你的进步</string>
<string name="interval_15_minutes">15分钟</string>
<string name="interval_30_minutes">30分钟</string>
<string name="interval_1_hour">1小时</string>
<string name="interval_2_hour">2小时</string>
<string name="interval_4_hour">4小时</string>
<string name="interval_8_hour">8小时</string>
<string name="pref_toggle_title">短时间按来记录习惯</string>
<string name="pref_toggle_description">更加方便,但有可能造成意外记录</string>
<string name="pref_snooze_interval_title">提醒延迟间隔</string>
<string name="pref_rate_this_app">评价这个应用在Play商店</string>
<string name="pref_send_feedback">发送反馈给开发者</string>
<string name="pref_view_source_code">看源代码在GitHub</string>
<string name="pref_view_app_introduction">阅读应用介绍</string>
<string name="links">链接</string>
<string name="behavior">功能</string>
<string name="name">名字</string>
<string name="show_archived">显示存档的习惯</string>
<string name="settings">设置</string>
<string name="snooze_interval">延迟时间间隔</string>
</resources>

View File

@@ -1,30 +0,0 @@
<?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>

View File

@@ -53,4 +53,278 @@
<color name="done_text_color_dark_normal">#ffffff</color>
<color name="done_text_color_dark_disabled">#888888</color>
<color name="done_disabled_dark">#bfbfbf</color>
<!-- Material design color palette -->
<color name="red_50">#FFEBEE</color>
<color name="red_100">#FFCDD2</color>
<color name="red_200">#EF9A9A</color>
<color name="red_300">#E57373</color>
<color name="red_400">#EF5350</color>
<color name="red_500">#F44336</color>
<color name="red_600">#E53935</color>
<color name="red_700">#D32F2F</color>
<color name="red_800">#C62828</color>
<color name="red_900">#B71C1C</color>
<color name="red_A100">#FF8A80</color>
<color name="red_A200">#FF5252</color>
<color name="red_A400">#FF1744</color>
<color name="red_A700">#D50000</color>
<color name="deep_purple_50">#EDE7F6</color>
<color name="deep_purple_100">#D1C4E9</color>
<color name="deep_purple_200">#B39DDB</color>
<color name="deep_purple_300">#9575CD</color>
<color name="deep_purple_400">#7E57C2</color>
<color name="deep_purple_500">#673AB7</color>
<color name="deep_purple_600">#5E35B1</color>
<color name="deep_purple_700">#512DA8</color>
<color name="deep_purple_800">#4527A0</color>
<color name="deep_purple_900">#311B92</color>
<color name="deep_purple_A100">#B388FF</color>
<color name="deep_purple_A200">#7C4DFF</color>
<color name="deep_purple_A400">#651FFF</color>
<color name="deep_purple_A700">#6200EA</color>
<color name="light_blue_50">#E1F5FE</color>
<color name="light_blue_100">#B3E5FC</color>
<color name="light_blue_200">#81D4FA</color>
<color name="light_blue_300">#4FC3F7</color>
<color name="light_blue_400">#29B6F6</color>
<color name="light_blue_500">#03A9F4</color>
<color name="light_blue_600">#039BE5</color>
<color name="light_blue_700">#0288D1</color>
<color name="light_blue_800">#0277BD</color>
<color name="light_blue_900">#01579B</color>
<color name="light_blue_A100">#80D8FF</color>
<color name="light_blue_A200">#40C4FF</color>
<color name="light_blue_A400">#00B0FF</color>
<color name="light_blue_A700">#0091EA</color>
<color name="green_50">#E8F5E9</color>
<color name="green_100">#C8E6C9</color>
<color name="green_200">#A5D6A7</color>
<color name="green_300">#81C784</color>
<color name="green_400">#66BB6A</color>
<color name="green_500">#4CAF50</color>
<color name="green_600">#43A047</color>
<color name="green_700">#388E3C</color>
<color name="green_800">#2E7D32</color>
<color name="green_900">#1B5E20</color>
<color name="green_A100">#B9F6CA</color>
<color name="green_A200">#69F0AE</color>
<color name="green_A400">#00E676</color>
<color name="green_A700">#00C853</color>
<color name="yellow_50">#FFFDE7</color>
<color name="yellow_100">#FFF9C4</color>
<color name="yellow_200">#FFF59D</color>
<color name="yellow_300">#FFF176</color>
<color name="yellow_400">#FFEE58</color>
<color name="yellow_500">#FFEB3B</color>
<color name="yellow_600">#FDD835</color>
<color name="yellow_700">#FBC02D</color>
<color name="yellow_800">#F9A825</color>
<color name="yellow_900">#F57F17</color>
<color name="yellow_A100">#FFFF8D</color>
<color name="yellow_A200">#FFFF00</color>
<color name="yellow_A400">#FFEA00</color>
<color name="yellow_A700">#FFD600</color>
<color name="deep_orange_50">#FBE9E7</color>
<color name="deep_orange_100">#FFCCBC</color>
<color name="deep_orange_200">#FFAB91</color>
<color name="deep_orange_300">#FF8A65</color>
<color name="deep_orange_400">#FF7043</color>
<color name="deep_orange_500">#FF5722</color>
<color name="deep_orange_600">#F4511E</color>
<color name="deep_orange_700">#E64A19</color>
<color name="deep_orange_800">#D84315</color>
<color name="deep_orange_900">#BF360C</color>
<color name="deep_orange_A100">#FF9E80</color>
<color name="deep_orange_A200">#FF6E40</color>
<color name="deep_orange_A400">#FF3D00</color>
<color name="deep_orange_A700">#DD2C00</color>
<color name="blue_grey_50">#ECEFF1</color>
<color name="blue_grey_100">#CFD8DC</color>
<color name="blue_grey_200">#B0BEC5</color>
<color name="blue_grey_300">#90A4AE</color>
<color name="blue_grey_400">#78909C</color>
<color name="blue_grey_500">#607D8B</color>
<color name="blue_grey_600">#546E7A</color>
<color name="blue_grey_700">#455A64</color>
<color name="blue_grey_800">#37474F</color>
<color name="blue_grey_900">#263238</color>
<color name="pink_50">#FCE4EC</color>
<color name="pink_100">#F8BBD0</color>
<color name="pink_200">#F48FB1</color>
<color name="pink_300">#F06292</color>
<color name="pink_400">#EC407A</color>
<color name="pink_500">#E91E63</color>
<color name="pink_600">#D81B60</color>
<color name="pink_700">#C2185B</color>
<color name="pink_800">#AD1457</color>
<color name="pink_900">#880E4F</color>
<color name="pink_A100">#FF80AB</color>
<color name="pink_A200">#FF4081</color>
<color name="pink_A400">#F50057</color>
<color name="pink_A700">#C51162</color>
<color name="indigo_50">#E8EAF6</color>
<color name="indigo_100">#C5CAE9</color>
<color name="indigo_200">#9FA8DA</color>
<color name="indigo_300">#7986CB</color>
<color name="indigo_400">#5C6BC0</color>
<color name="indigo_500">#3F51B5</color>
<color name="indigo_600">#3949AB</color>
<color name="indigo_700">#303F9F</color>
<color name="indigo_800">#283593</color>
<color name="indigo_900">#1A237E</color>
<color name="indigo_A100">#8C9EFF</color>
<color name="indigo_A200">#536DFE</color>
<color name="indigo_A400">#3D5AFE</color>
<color name="indigo_A700">#304FFE</color>
<color name="cyan_50">#E0F7FA</color>
<color name="cyan_100">#B2EBF2</color>
<color name="cyan_200">#80DEEA</color>
<color name="cyan_300">#4DD0E1</color>
<color name="cyan_400">#26C6DA</color>
<color name="cyan_500">#00BCD4</color>
<color name="cyan_600">#00ACC1</color>
<color name="cyan_700">#0097A7</color>
<color name="cyan_800">#00838F</color>
<color name="cyan_900">#006064</color>
<color name="cyan_A100">#84FFFF</color>
<color name="cyan_A200">#18FFFF</color>
<color name="cyan_A400">#00E5FF</color>
<color name="cyan_A700">#00B8D4</color>
<color name="light_green_50">#F1F8E9</color>
<color name="light_green_100">#DCEDC8</color>
<color name="light_green_200">#C5E1A5</color>
<color name="light_green_300">#AED581</color>
<color name="light_green_400">#9CCC65</color>
<color name="light_green_500">#8BC34A</color>
<color name="light_green_600">#7CB342</color>
<color name="light_green_700">#689F38</color>
<color name="light_green_800">#558B2F</color>
<color name="light_green_900">#33691E</color>
<color name="light_green_A100">#CCFF90</color>
<color name="light_green_A200">#B2FF59</color>
<color name="light_green_A400">#76FF03</color>
<color name="light_green_A700">#64DD17</color>
<color name="amber_50">#FFF8E1</color>
<color name="amber_100">#FFECB3</color>
<color name="amber_200">#FFE082</color>
<color name="amber_300">#FFD54F</color>
<color name="amber_400">#FFCA28</color>
<color name="amber_500">#FFC107</color>
<color name="amber_600">#FFB300</color>
<color name="amber_700">#FFA000</color>
<color name="amber_800">#FF8F00</color>
<color name="amber_900">#FF6F00</color>
<color name="amber_A100">#FFE57F</color>
<color name="amber_A200">#FFD740</color>
<color name="amber_A400">#FFC400</color>
<color name="amber_A700">#FFAB00</color>
<color name="brown_50">#EFEBE9</color>
<color name="brown_100">#D7CCC8</color>
<color name="brown_200">#BCAAA4</color>
<color name="brown_300">#A1887F</color>
<color name="brown_400">#8D6E63</color>
<color name="brown_500">#795548</color>
<color name="brown_600">#6D4C41</color>
<color name="brown_700">#5D4037</color>
<color name="brown_800">#4E342E</color>
<color name="brown_900">#3E2723</color>
<color name="purple_50">#F3E5F5</color>
<color name="purple_100">#E1BEE7</color>
<color name="purple_200">#CE93D8</color>
<color name="purple_300">#BA68C8</color>
<color name="purple_400">#AB47BC</color>
<color name="purple_500">#9C27B0</color>
<color name="purple_600">#8E24AA</color>
<color name="purple_700">#7B1FA2</color>
<color name="purple_800">#6A1B9A</color>
<color name="purple_900">#4A148C</color>
<color name="purple_A100">#EA80FC</color>
<color name="purple_A200">#E040FB</color>
<color name="purple_A400">#D500F9</color>
<color name="purple_A700">#AA00FF</color>
<color name="blue_50">#E3F2FD</color>
<color name="blue_100">#BBDEFB</color>
<color name="blue_200">#90CAF9</color>
<color name="blue_300">#64B5F6</color>
<color name="blue_400">#42A5F5</color>
<color name="blue_500">#2196F3</color>
<color name="blue_600">#1E88E5</color>
<color name="blue_700">#1976D2</color>
<color name="blue_800">#1565C0</color>
<color name="blue_900">#0D47A1</color>
<color name="blue_A100">#82B1FF</color>
<color name="blue_A200">#448AFF</color>
<color name="blue_A400">#2979FF</color>
<color name="blue_A700">#2962FF</color>
<color name="teal_50">#E0F2F1</color>
<color name="teal_100">#B2DFDB</color>
<color name="teal_200">#80CBC4</color>
<color name="teal_300">#4DB6AC</color>
<color name="teal_400">#26A69A</color>
<color name="teal_500">#009688</color>
<color name="teal_600">#00897B</color>
<color name="teal_700">#00796B</color>
<color name="teal_800">#00695C</color>
<color name="teal_900">#004D40</color>
<color name="teal_A100">#A7FFEB</color>
<color name="teal_A200">#64FFDA</color>
<color name="teal_A400">#1DE9B6</color>
<color name="teal_A700">#00BFA5</color>
<color name="lime_50">#F9FBE7</color>
<color name="lime_100">#F0F4C3</color>
<color name="lime_200">#E6EE9C</color>
<color name="lime_300">#DCE775</color>
<color name="lime_400">#D4E157</color>
<color name="lime_500">#CDDC39</color>
<color name="lime_600">#C0CA33</color>
<color name="lime_700">#AFB42B</color>
<color name="lime_800">#9E9D24</color>
<color name="lime_900">#827717</color>
<color name="lime_A100">#F4FF81</color>
<color name="lime_A200">#EEFF41</color>
<color name="lime_A400">#C6FF00</color>
<color name="lime_A700">#AEEA00</color>
<color name="orange_50">#FFF3E0</color>
<color name="orange_100">#FFE0B2</color>
<color name="orange_200">#FFCC80</color>
<color name="orange_300">#FFB74D</color>
<color name="orange_400">#FFA726</color>
<color name="orange_500">#FF9800</color>
<color name="orange_600">#FB8C00</color>
<color name="orange_700">#F57C00</color>
<color name="orange_800">#EF6C00</color>
<color name="orange_900">#E65100</color>
<color name="orange_A100">#FFD180</color>
<color name="orange_A200">#FFAB40</color>
<color name="orange_A400">#FF9100</color>
<color name="orange_A700">#FF6D00</color>
<color name="grey_50">#FAFAFA</color>
<color name="grey_100">#F5F5F5</color>
<color name="grey_200">#EEEEEE</color>
<color name="grey_300">#E0E0E0</color>
<color name="grey_400">#BDBDBD</color>
<color name="grey_500">#9E9E9E</color>
<color name="grey_600">#757575</color>
<color name="grey_700">#616161</color>
<color name="grey_800">#424242</color>
<color name="grey_900">#212121</color>
</resources>

View File

@@ -5,12 +5,12 @@
<dimen name="check_square_size">42dp</dimen>
<string-array name="snooze_interval_names">
<item>15 minutes</item>
<item>30 minutes</item>
<item>1 hour</item>
<item>2 hours</item>
<item>4 hours</item>
<item>8 hours</item>
<item>@string/interval_15_minutes</item>
<item>@string/interval_30_minutes</item>
<item>@string/interval_1_hour</item>
<item>@string/interval_2_hour</item>
<item>@string/interval_4_hour</item>
<item>@string/interval_8_hour</item>
</string-array>
<string-array name="snooze_interval_values">
@@ -22,5 +22,5 @@
<item>480</item>
</string-array>
<string name="snooze_interval_default">15 minutes</string>
<string name="snooze_interval_default" translatable="false">15</string>
</resources>

View File

@@ -1,15 +1,15 @@
<?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>
<item name="circle_radius_multiplier" format="float" type="string" translatable="false">0.82</item>
<item name="circle_radius_multiplier_24HourMode" format="float" type="string" translatable="false">0.85</item>
<item name="selection_radius_multiplier" format="float" type="string" translatable="false">0.16</item>
<item name="ampm_circle_radius_multiplier" format="float" type="string" translatable="false">0.19</item>
<item name="numbers_radius_multiplier_normal" format="float" type="string" translatable="false">0.81</item>
<item name="numbers_radius_multiplier_inner" format="float" type="string" translatable="false">0.60</item>
<item name="numbers_radius_multiplier_outer" format="float" type="string" translatable="false">0.83</item>
<item name="text_size_multiplier_normal" format="float" type="string" translatable="false">0.17</item>
<item name="text_size_multiplier_inner" format="float" type="string" translatable="false">0.14</item>
<item name="text_size_multiplier_outer" format="float" type="string" translatable="false">0.11</item>
<dimen name="time_label_size">60sp</dimen>
<dimen name="extra_time_label_margin">-30dp</dimen>

View File

@@ -1,30 +0,0 @@
<?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>

View File

@@ -1,372 +1,372 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="fa_glass">&#xf000;</string>
<string name="fa_music">&#xf001;</string>
<string name="fa_search">&#xf002;</string>
<string name="fa_envelope_o">&#xf003;</string>
<string name="fa_heart">&#xf004;</string>
<string name="fa_star">&#xf005;</string>
<string name="fa_star_o">&#xf006;</string>
<string name="fa_user">&#xf007;</string>
<string name="fa_film">&#xf008;</string>
<string name="fa_th_large">&#xf009;</string>
<string name="fa_th">&#xf00a;</string>
<string name="fa_th_list">&#xf00b;</string>
<string name="fa_check">&#xf00c;</string>
<string name="fa_times">&#xf00d;</string>
<string name="fa_search_plus">&#xf00e;</string>
<string name="fa_search_minus">&#xf010;</string>
<string name="fa_power_off">&#xf011;</string>
<string name="fa_signal">&#xf012;</string>
<string name="fa_cog">&#xf013;</string>
<string name="fa_trash_o">&#xf014;</string>
<string name="fa_home">&#xf015;</string>
<string name="fa_file_o">&#xf016;</string>
<string name="fa_clock_o">&#xf017;</string>
<string name="fa_road">&#xf018;</string>
<string name="fa_download">&#xf019;</string>
<string name="fa_arrow_circle_o_down">&#xf01a;</string>
<string name="fa_arrow_circle_o_up">&#xf01b;</string>
<string name="fa_inbox">&#xf01c;</string>
<string name="fa_play_circle_o">&#xf01d;</string>
<string name="fa_repeat">&#xf01e;</string>
<string name="fa_refresh">&#xf021;</string>
<string name="fa_list_alt">&#xf022;</string>
<string name="fa_lock">&#xf023;</string>
<string name="fa_flag">&#xf024;</string>
<string name="fa_headphones">&#xf025;</string>
<string name="fa_volume_off">&#xf026;</string>
<string name="fa_volume_down">&#xf027;</string>
<string name="fa_volume_up">&#xf028;</string>
<string name="fa_qrcode">&#xf029;</string>
<string name="fa_barcode">&#xf02a;</string>
<string name="fa_tag">&#xf02b;</string>
<string name="fa_tags">&#xf02c;</string>
<string name="fa_book">&#xf02d;</string>
<string name="fa_bookmark">&#xf02e;</string>
<string name="fa_print">&#xf02f;</string>
<string name="fa_camera">&#xf030;</string>
<string name="fa_font">&#xf031;</string>
<string name="fa_bold">&#xf032;</string>
<string name="fa_italic">&#xf033;</string>
<string name="fa_text_height">&#xf034;</string>
<string name="fa_text_width">&#xf035;</string>
<string name="fa_align_left">&#xf036;</string>
<string name="fa_align_center">&#xf037;</string>
<string name="fa_align_right">&#xf038;</string>
<string name="fa_align_justify">&#xf039;</string>
<string name="fa_list">&#xf03a;</string>
<string name="fa_outdent">&#xf03b;</string>
<string name="fa_indent">&#xf03c;</string>
<string name="fa_video_camera">&#xf03d;</string>
<string name="fa_picture_o">&#xf03e;</string>
<string name="fa_pencil">&#xf040;</string>
<string name="fa_map_marker">&#xf041;</string>
<string name="fa_adjust">&#xf042;</string>
<string name="fa_tint">&#xf043;</string>
<string name="fa_pencil_square_o">&#xf044;</string>
<string name="fa_share_square_o">&#xf045;</string>
<string name="fa_check_square_o">&#xf046;</string>
<string name="fa_arrows">&#xf047;</string>
<string name="fa_step_backward">&#xf048;</string>
<string name="fa_fast_backward">&#xf049;</string>
<string name="fa_backward">&#xf04a;</string>
<string name="fa_play">&#xf04b;</string>
<string name="fa_pause">&#xf04c;</string>
<string name="fa_stop">&#xf04d;</string>
<string name="fa_forward">&#xf04e;</string>
<string name="fa_fast_forward">&#xf050;</string>
<string name="fa_step_forward">&#xf051;</string>
<string name="fa_eject">&#xf052;</string>
<string name="fa_chevron_left">&#xf053;</string>
<string name="fa_chevron_right">&#xf054;</string>
<string name="fa_plus_circle">&#xf055;</string>
<string name="fa_minus_circle">&#xf056;</string>
<string name="fa_times_circle">&#xf057;</string>
<string name="fa_check_circle">&#xf058;</string>
<string name="fa_question_circle">&#xf059;</string>
<string name="fa_info_circle">&#xf05a;</string>
<string name="fa_crosshairs">&#xf05b;</string>
<string name="fa_times_circle_o">&#xf05c;</string>
<string name="fa_check_circle_o">&#xf05d;</string>
<string name="fa_ban">&#xf05e;</string>
<string name="fa_arrow_left">&#xf060;</string>
<string name="fa_arrow_right">&#xf061;</string>
<string name="fa_arrow_up">&#xf062;</string>
<string name="fa_arrow_down">&#xf063;</string>
<string name="fa_share">&#xf064;</string>
<string name="fa_expand">&#xf065;</string>
<string name="fa_compress">&#xf066;</string>
<string name="fa_plus">&#xf067;</string>
<string name="fa_minus">&#xf068;</string>
<string name="fa_asterisk">&#xf069;</string>
<string name="fa_exclamation_circle">&#xf06a;</string>
<string name="fa_gift">&#xf06b;</string>
<string name="fa_leaf">&#xf06c;</string>
<string name="fa_fire">&#xf06d;</string>
<string name="fa_eye">&#xf06e;</string>
<string name="fa_eye_slash">&#xf070;</string>
<string name="fa_exclamation_triangle">&#xf071;</string>
<string name="fa_plane">&#xf072;</string>
<string name="fa_calendar">&#xf073;</string>
<string name="fa_random">&#xf074;</string>
<string name="fa_comment">&#xf075;</string>
<string name="fa_magnet">&#xf076;</string>
<string name="fa_chevron_up">&#xf077;</string>
<string name="fa_chevron_down">&#xf078;</string>
<string name="fa_retweet">&#xf079;</string>
<string name="fa_shopping_cart">&#xf07a;</string>
<string name="fa_folder">&#xf07b;</string>
<string name="fa_folder_open">&#xf07c;</string>
<string name="fa_arrows_v">&#xf07d;</string>
<string name="fa_arrows_h">&#xf07e;</string>
<string name="fa_bar_chart_o">&#xf080;</string>
<string name="fa_twitter_square">&#xf081;</string>
<string name="fa_facebook_square">&#xf082;</string>
<string name="fa_camera_retro">&#xf083;</string>
<string name="fa_key">&#xf084;</string>
<string name="fa_cogs">&#xf085;</string>
<string name="fa_comments">&#xf086;</string>
<string name="fa_thumbs_o_up">&#xf087;</string>
<string name="fa_thumbs_o_down">&#xf088;</string>
<string name="fa_star_half">&#xf089;</string>
<string name="fa_heart_o">&#xf08a;</string>
<string name="fa_sign_out">&#xf08b;</string>
<string name="fa_linkedin_square">&#xf08c;</string>
<string name="fa_thumb_tack">&#xf08d;</string>
<string name="fa_external_link">&#xf08e;</string>
<string name="fa_sign_in">&#xf090;</string>
<string name="fa_trophy">&#xf091;</string>
<string name="fa_github_square">&#xf092;</string>
<string name="fa_upload">&#xf093;</string>
<string name="fa_lemon_o">&#xf094;</string>
<string name="fa_phone">&#xf095;</string>
<string name="fa_square_o">&#xf096;</string>
<string name="fa_bookmark_o">&#xf097;</string>
<string name="fa_phone_square">&#xf098;</string>
<string name="fa_twitter">&#xf099;</string>
<string name="fa_facebook">&#xf09a;</string>
<string name="fa_github">&#xf09b;</string>
<string name="fa_unlock">&#xf09c;</string>
<string name="fa_credit_card">&#xf09d;</string>
<string name="fa_rss">&#xf09e;</string>
<string name="fa_hdd_o">&#xf0a0;</string>
<string name="fa_bullhorn">&#xf0a1;</string>
<string name="fa_bell">&#xf0f3;</string>
<string name="fa_certificate">&#xf0a3;</string>
<string name="fa_hand_o_right">&#xf0a4;</string>
<string name="fa_hand_o_left">&#xf0a5;</string>
<string name="fa_hand_o_up">&#xf0a6;</string>
<string name="fa_hand_o_down">&#xf0a7;</string>
<string name="fa_arrow_circle_left">&#xf0a8;</string>
<string name="fa_arrow_circle_right">&#xf0a9;</string>
<string name="fa_arrow_circle_up">&#xf0aa;</string>
<string name="fa_arrow_circle_down">&#xf0ab;</string>
<string name="fa_globe">&#xf0ac;</string>
<string name="fa_wrench">&#xf0ad;</string>
<string name="fa_tasks">&#xf0ae;</string>
<string name="fa_filter">&#xf0b0;</string>
<string name="fa_briefcase">&#xf0b1;</string>
<string name="fa_arrows_alt">&#xf0b2;</string>
<string name="fa_users">&#xf0c0;</string>
<string name="fa_link">&#xf0c1;</string>
<string name="fa_cloud">&#xf0c2;</string>
<string name="fa_flask">&#xf0c3;</string>
<string name="fa_scissors">&#xf0c4;</string>
<string name="fa_files_o">&#xf0c5;</string>
<string name="fa_paperclip">&#xf0c6;</string>
<string name="fa_floppy_o">&#xf0c7;</string>
<string name="fa_square">&#xf0c8;</string>
<string name="fa_bars">&#xf0c9;</string>
<string name="fa_list_ul">&#xf0ca;</string>
<string name="fa_list_ol">&#xf0cb;</string>
<string name="fa_strikethrough">&#xf0cc;</string>
<string name="fa_underline">&#xf0cd;</string>
<string name="fa_table">&#xf0ce;</string>
<string name="fa_magic">&#xf0d0;</string>
<string name="fa_truck">&#xf0d1;</string>
<string name="fa_pinterest">&#xf0d2;</string>
<string name="fa_pinterest_square">&#xf0d3;</string>
<string name="fa_google_plus_square">&#xf0d4;</string>
<string name="fa_google_plus">&#xf0d5;</string>
<string name="fa_money">&#xf0d6;</string>
<string name="fa_caret_down">&#xf0d7;</string>
<string name="fa_caret_up">&#xf0d8;</string>
<string name="fa_caret_left">&#xf0d9;</string>
<string name="fa_caret_right">&#xf0da;</string>
<string name="fa_columns">&#xf0db;</string>
<string name="fa_sort">&#xf0dc;</string>
<string name="fa_sort_asc">&#xf0dd;</string>
<string name="fa_sort_desc">&#xf0de;</string>
<string name="fa_envelope">&#xf0e0;</string>
<string name="fa_linkedin">&#xf0e1;</string>
<string name="fa_undo">&#xf0e2;</string>
<string name="fa_gavel">&#xf0e3;</string>
<string name="fa_tachometer">&#xf0e4;</string>
<string name="fa_comment_o">&#xf0e5;</string>
<string name="fa_comments_o">&#xf0e6;</string>
<string name="fa_bolt">&#xf0e7;</string>
<string name="fa_sitemap">&#xf0e8;</string>
<string name="fa_umbrella">&#xf0e9;</string>
<string name="fa_clipboard">&#xf0ea;</string>
<string name="fa_lightbulb_o">&#xf0eb;</string>
<string name="fa_exchange">&#xf0ec;</string>
<string name="fa_cloud_download">&#xf0ed;</string>
<string name="fa_cloud_upload">&#xf0ee;</string>
<string name="fa_user_md">&#xf0f0;</string>
<string name="fa_stethoscope">&#xf0f1;</string>
<string name="fa_suitcase">&#xf0f2;</string>
<string name="fa_bell_o">&#xf0a2;</string>
<string name="fa_coffee">&#xf0f4;</string>
<string name="fa_cutlery">&#xf0f5;</string>
<string name="fa_file_text_o">&#xf0f6;</string>
<string name="fa_building_o">&#xf0f7;</string>
<string name="fa_hospital_o">&#xf0f8;</string>
<string name="fa_ambulance">&#xf0f9;</string>
<string name="fa_medkit">&#xf0fa;</string>
<string name="fa_fighter_jet">&#xf0fb;</string>
<string name="fa_beer">&#xf0fc;</string>
<string name="fa_h_square">&#xf0fd;</string>
<string name="fa_plus_square">&#xf0fe;</string>
<string name="fa_angle_double_left">&#xf100;</string>
<string name="fa_angle_double_right">&#xf101;</string>
<string name="fa_angle_double_up">&#xf102;</string>
<string name="fa_angle_double_down">&#xf103;</string>
<string name="fa_angle_left">&#xf104;</string>
<string name="fa_angle_right">&#xf105;</string>
<string name="fa_angle_up">&#xf106;</string>
<string name="fa_angle_down">&#xf107;</string>
<string name="fa_desktop">&#xf108;</string>
<string name="fa_laptop">&#xf109;</string>
<string name="fa_tablet">&#xf10a;</string>
<string name="fa_mobile">&#xf10b;</string>
<string name="fa_circle_o">&#xf10c;</string>
<string name="fa_quote_left">&#xf10d;</string>
<string name="fa_quote_right">&#xf10e;</string>
<string name="fa_spinner">&#xf110;</string>
<string name="fa_circle">&#xf111;</string>
<string name="fa_reply">&#xf112;</string>
<string name="fa_github_alt">&#xf113;</string>
<string name="fa_folder_o">&#xf114;</string>
<string name="fa_folder_open_o">&#xf115;</string>
<string name="fa_smile_o">&#xf118;</string>
<string name="fa_frown_o">&#xf119;</string>
<string name="fa_meh_o">&#xf11a;</string>
<string name="fa_gamepad">&#xf11b;</string>
<string name="fa_keyboard_o">&#xf11c;</string>
<string name="fa_flag_o">&#xf11d;</string>
<string name="fa_flag_checkered">&#xf11e;</string>
<string name="fa_terminal">&#xf120;</string>
<string name="fa_code">&#xf121;</string>
<string name="fa_reply_all">&#xf122;</string>
<string name="fa_mail_reply_all">&#xf122;</string>
<string name="fa_star_half_o">&#xf123;</string>
<string name="fa_location_arrow">&#xf124;</string>
<string name="fa_crop">&#xf125;</string>
<string name="fa_code_fork">&#xf126;</string>
<string name="fa_chain_broken">&#xf127;</string>
<string name="fa_question">&#xf128;</string>
<string name="fa_info">&#xf129;</string>
<string name="fa_exclamation">&#xf12a;</string>
<string name="fa_superscript">&#xf12b;</string>
<string name="fa_subscript">&#xf12c;</string>
<string name="fa_eraser">&#xf12d;</string>
<string name="fa_puzzle_piece">&#xf12e;</string>
<string name="fa_microphone">&#xf130;</string>
<string name="fa_microphone_slash">&#xf131;</string>
<string name="fa_shield">&#xf132;</string>
<string name="fa_calendar_o">&#xf133;</string>
<string name="fa_fire_extinguisher">&#xf134;</string>
<string name="fa_rocket">&#xf135;</string>
<string name="fa_maxcdn">&#xf136;</string>
<string name="fa_chevron_circle_left">&#xf137;</string>
<string name="fa_chevron_circle_right">&#xf138;</string>
<string name="fa_chevron_circle_up">&#xf139;</string>
<string name="fa_chevron_circle_down">&#xf13a;</string>
<string name="fa_html5">&#xf13b;</string>
<string name="fa_css3">&#xf13c;</string>
<string name="fa_anchor">&#xf13d;</string>
<string name="fa_unlock_alt">&#xf13e;</string>
<string name="fa_bullseye">&#xf140;</string>
<string name="fa_ellipsis_h">&#xf141;</string>
<string name="fa_ellipsis_v">&#xf142;</string>
<string name="fa_rss_square">&#xf143;</string>
<string name="fa_play_circle">&#xf144;</string>
<string name="fa_ticket">&#xf145;</string>
<string name="fa_minus_square">&#xf146;</string>
<string name="fa_minus_square_o">&#xf147;</string>
<string name="fa_level_up">&#xf148;</string>
<string name="fa_level_down">&#xf149;</string>
<string name="fa_check_square">&#xf14a;</string>
<string name="fa_pencil_square">&#xf14b;</string>
<string name="fa_external_link_square">&#xf14c;</string>
<string name="fa_share_square">&#xf14d;</string>
<string name="fa_compass">&#xf14e;</string>
<string name="fa_caret_square_o_down">&#xf150;</string>
<string name="fa_caret_square_o_up">&#xf151;</string>
<string name="fa_caret_square_o_right">&#xf152;</string>
<string name="fa_eur">&#xf153;</string>
<string name="fa_gbp">&#xf154;</string>
<string name="fa_usd">&#xf155;</string>
<string name="fa_inr">&#xf156;</string>
<string name="fa_jpy">&#xf157;</string>
<string name="fa_rub">&#xf158;</string>
<string name="fa_krw">&#xf159;</string>
<string name="fa_btc">&#xf15a;</string>
<string name="fa_file">&#xf15b;</string>
<string name="fa_file_text">&#xf15c;</string>
<string name="fa_sort_alpha_asc">&#xf15d;</string>
<string name="fa_sort_alpha_desc">&#xf15e;</string>
<string name="fa_sort_amount_asc">&#xf160;</string>
<string name="fa_sort_amount_desc">&#xf161;</string>
<string name="fa_sort_numeric_asc">&#xf162;</string>
<string name="fa_sort_numeric_desc">&#xf163;</string>
<string name="fa_thumbs_up">&#xf164;</string>
<string name="fa_thumbs_down">&#xf165;</string>
<string name="fa_youtube_square">&#xf166;</string>
<string name="fa_youtube">&#xf167;</string>
<string name="fa_xing">&#xf168;</string>
<string name="fa_xing_square">&#xf169;</string>
<string name="fa_youtube_play">&#xf16a;</string>
<string name="fa_dropbox">&#xf16b;</string>
<string name="fa_stack_overflow">&#xf16c;</string>
<string name="fa_instagram">&#xf16d;</string>
<string name="fa_flickr">&#xf16e;</string>
<string name="fa_adn">&#xf170;</string>
<string name="fa_bitbucket">&#xf171;</string>
<string name="fa_bitbucket_square">&#xf172;</string>
<string name="fa_tumblr">&#xf173;</string>
<string name="fa_tumblr_square">&#xf174;</string>
<string name="fa_long_arrow_down">&#xf175;</string>
<string name="fa_long_arrow_up">&#xf176;</string>
<string name="fa_long_arrow_left">&#xf177;</string>
<string name="fa_long_arrow_right">&#xf178;</string>
<string name="fa_apple">&#xf179;</string>
<string name="fa_windows">&#xf17a;</string>
<string name="fa_android">&#xf17b;</string>
<string name="fa_linux">&#xf17c;</string>
<string name="fa_dribbble">&#xf17d;</string>
<string name="fa_skype">&#xf17e;</string>
<string name="fa_foursquare">&#xf180;</string>
<string name="fa_trello">&#xf181;</string>
<string name="fa_female">&#xf182;</string>
<string name="fa_male">&#xf183;</string>
<string name="fa_gittip">&#xf184;</string>
<string name="fa_sun_o">&#xf185;</string>
<string name="fa_moon_o">&#xf186;</string>
<string name="fa_archive">&#xf187;</string>
<string name="fa_bug">&#xf188;</string>
<string name="fa_vk">&#xf189;</string>
<string name="fa_weibo">&#xf18a;</string>
<string name="fa_renren">&#xf18b;</string>
<string name="fa_pagelines">&#xf18c;</string>
<string name="fa_stack_exchange">&#xf18d;</string>
<string name="fa_arrow_circle_o_right">&#xf18e;</string>
<string name="fa_arrow_circle_o_left">&#xf190;</string>
<string name="fa_caret_square_o_left">&#xf191;</string>
<string name="fa_dot_circle_o">&#xf192;</string>
<string name="fa_wheelchair">&#xf193;</string>
<string name="fa_vimeo_square">&#xf194;</string>
<string name="fa_try">&#xf195;</string>
<string name="fa_plus_square_o">&#xf196;</string>
<string translatable="false" name="fa_glass">&#xf000;</string>
<string translatable="false" name="fa_music">&#xf001;</string>
<string translatable="false" name="fa_search">&#xf002;</string>
<string translatable="false" name="fa_envelope_o">&#xf003;</string>
<string translatable="false" name="fa_heart">&#xf004;</string>
<string translatable="false" name="fa_star">&#xf005;</string>
<string translatable="false" name="fa_star_o">&#xf006;</string>
<string translatable="false" name="fa_user">&#xf007;</string>
<string translatable="false" name="fa_film">&#xf008;</string>
<string translatable="false" name="fa_th_large">&#xf009;</string>
<string translatable="false" name="fa_th">&#xf00a;</string>
<string translatable="false" name="fa_th_list">&#xf00b;</string>
<string translatable="false" name="fa_check">&#xf00c;</string>
<string translatable="false" name="fa_times">&#xf00d;</string>
<string translatable="false" name="fa_search_plus">&#xf00e;</string>
<string translatable="false" name="fa_search_minus">&#xf010;</string>
<string translatable="false" name="fa_power_off">&#xf011;</string>
<string translatable="false" name="fa_signal">&#xf012;</string>
<string translatable="false" name="fa_cog">&#xf013;</string>
<string translatable="false" name="fa_trash_o">&#xf014;</string>
<string translatable="false" name="fa_home">&#xf015;</string>
<string translatable="false" name="fa_file_o">&#xf016;</string>
<string translatable="false" name="fa_clock_o">&#xf017;</string>
<string translatable="false" name="fa_road">&#xf018;</string>
<string translatable="false" name="fa_download">&#xf019;</string>
<string translatable="false" name="fa_arrow_circle_o_down">&#xf01a;</string>
<string translatable="false" name="fa_arrow_circle_o_up">&#xf01b;</string>
<string translatable="false" name="fa_inbox">&#xf01c;</string>
<string translatable="false" name="fa_play_circle_o">&#xf01d;</string>
<string translatable="false" name="fa_repeat">&#xf01e;</string>
<string translatable="false" name="fa_refresh">&#xf021;</string>
<string translatable="false" name="fa_list_alt">&#xf022;</string>
<string translatable="false" name="fa_lock">&#xf023;</string>
<string translatable="false" name="fa_flag">&#xf024;</string>
<string translatable="false" name="fa_headphones">&#xf025;</string>
<string translatable="false" name="fa_volume_off">&#xf026;</string>
<string translatable="false" name="fa_volume_down">&#xf027;</string>
<string translatable="false" name="fa_volume_up">&#xf028;</string>
<string translatable="false" name="fa_qrcode">&#xf029;</string>
<string translatable="false" name="fa_barcode">&#xf02a;</string>
<string translatable="false" name="fa_tag">&#xf02b;</string>
<string translatable="false" name="fa_tags">&#xf02c;</string>
<string translatable="false" name="fa_book">&#xf02d;</string>
<string translatable="false" name="fa_bookmark">&#xf02e;</string>
<string translatable="false" name="fa_print">&#xf02f;</string>
<string translatable="false" name="fa_camera">&#xf030;</string>
<string translatable="false" name="fa_font">&#xf031;</string>
<string translatable="false" name="fa_bold">&#xf032;</string>
<string translatable="false" name="fa_italic">&#xf033;</string>
<string translatable="false" name="fa_text_height">&#xf034;</string>
<string translatable="false" name="fa_text_width">&#xf035;</string>
<string translatable="false" name="fa_align_left">&#xf036;</string>
<string translatable="false" name="fa_align_center">&#xf037;</string>
<string translatable="false" name="fa_align_right">&#xf038;</string>
<string translatable="false" name="fa_align_justify">&#xf039;</string>
<string translatable="false" name="fa_list">&#xf03a;</string>
<string translatable="false" name="fa_outdent">&#xf03b;</string>
<string translatable="false" name="fa_indent">&#xf03c;</string>
<string translatable="false" name="fa_video_camera">&#xf03d;</string>
<string translatable="false" name="fa_picture_o">&#xf03e;</string>
<string translatable="false" name="fa_pencil">&#xf040;</string>
<string translatable="false" name="fa_map_marker">&#xf041;</string>
<string translatable="false" name="fa_adjust">&#xf042;</string>
<string translatable="false" name="fa_tint">&#xf043;</string>
<string translatable="false" name="fa_pencil_square_o">&#xf044;</string>
<string translatable="false" name="fa_share_square_o">&#xf045;</string>
<string translatable="false" name="fa_check_square_o">&#xf046;</string>
<string translatable="false" name="fa_arrows">&#xf047;</string>
<string translatable="false" name="fa_step_backward">&#xf048;</string>
<string translatable="false" name="fa_fast_backward">&#xf049;</string>
<string translatable="false" name="fa_backward">&#xf04a;</string>
<string translatable="false" name="fa_play">&#xf04b;</string>
<string translatable="false" name="fa_pause">&#xf04c;</string>
<string translatable="false" name="fa_stop">&#xf04d;</string>
<string translatable="false" name="fa_forward">&#xf04e;</string>
<string translatable="false" name="fa_fast_forward">&#xf050;</string>
<string translatable="false" name="fa_step_forward">&#xf051;</string>
<string translatable="false" name="fa_eject">&#xf052;</string>
<string translatable="false" name="fa_chevron_left">&#xf053;</string>
<string translatable="false" name="fa_chevron_right">&#xf054;</string>
<string translatable="false" name="fa_plus_circle">&#xf055;</string>
<string translatable="false" name="fa_minus_circle">&#xf056;</string>
<string translatable="false" name="fa_times_circle">&#xf057;</string>
<string translatable="false" name="fa_check_circle">&#xf058;</string>
<string translatable="false" name="fa_question_circle">&#xf059;</string>
<string translatable="false" name="fa_info_circle">&#xf05a;</string>
<string translatable="false" name="fa_crosshairs">&#xf05b;</string>
<string translatable="false" name="fa_times_circle_o">&#xf05c;</string>
<string translatable="false" name="fa_check_circle_o">&#xf05d;</string>
<string translatable="false" name="fa_ban">&#xf05e;</string>
<string translatable="false" name="fa_arrow_left">&#xf060;</string>
<string translatable="false" name="fa_arrow_right">&#xf061;</string>
<string translatable="false" name="fa_arrow_up">&#xf062;</string>
<string translatable="false" name="fa_arrow_down">&#xf063;</string>
<string translatable="false" name="fa_share">&#xf064;</string>
<string translatable="false" name="fa_expand">&#xf065;</string>
<string translatable="false" name="fa_compress">&#xf066;</string>
<string translatable="false" name="fa_plus">&#xf067;</string>
<string translatable="false" name="fa_minus">&#xf068;</string>
<string translatable="false" name="fa_asterisk">&#xf069;</string>
<string translatable="false" name="fa_exclamation_circle">&#xf06a;</string>
<string translatable="false" name="fa_gift">&#xf06b;</string>
<string translatable="false" name="fa_leaf">&#xf06c;</string>
<string translatable="false" name="fa_fire">&#xf06d;</string>
<string translatable="false" name="fa_eye">&#xf06e;</string>
<string translatable="false" name="fa_eye_slash">&#xf070;</string>
<string translatable="false" name="fa_exclamation_triangle">&#xf071;</string>
<string translatable="false" name="fa_plane">&#xf072;</string>
<string translatable="false" name="fa_calendar">&#xf073;</string>
<string translatable="false" name="fa_random">&#xf074;</string>
<string translatable="false" name="fa_comment">&#xf075;</string>
<string translatable="false" name="fa_magnet">&#xf076;</string>
<string translatable="false" name="fa_chevron_up">&#xf077;</string>
<string translatable="false" name="fa_chevron_down">&#xf078;</string>
<string translatable="false" name="fa_retweet">&#xf079;</string>
<string translatable="false" name="fa_shopping_cart">&#xf07a;</string>
<string translatable="false" name="fa_folder">&#xf07b;</string>
<string translatable="false" name="fa_folder_open">&#xf07c;</string>
<string translatable="false" name="fa_arrows_v">&#xf07d;</string>
<string translatable="false" name="fa_arrows_h">&#xf07e;</string>
<string translatable="false" name="fa_bar_chart_o">&#xf080;</string>
<string translatable="false" name="fa_twitter_square">&#xf081;</string>
<string translatable="false" name="fa_facebook_square">&#xf082;</string>
<string translatable="false" name="fa_camera_retro">&#xf083;</string>
<string translatable="false" name="fa_key">&#xf084;</string>
<string translatable="false" name="fa_cogs">&#xf085;</string>
<string translatable="false" name="fa_comments">&#xf086;</string>
<string translatable="false" name="fa_thumbs_o_up">&#xf087;</string>
<string translatable="false" name="fa_thumbs_o_down">&#xf088;</string>
<string translatable="false" name="fa_star_half">&#xf089;</string>
<string translatable="false" name="fa_heart_o">&#xf08a;</string>
<string translatable="false" name="fa_sign_out">&#xf08b;</string>
<string translatable="false" name="fa_linkedin_square">&#xf08c;</string>
<string translatable="false" name="fa_thumb_tack">&#xf08d;</string>
<string translatable="false" name="fa_external_link">&#xf08e;</string>
<string translatable="false" name="fa_sign_in">&#xf090;</string>
<string translatable="false" name="fa_trophy">&#xf091;</string>
<string translatable="false" name="fa_github_square">&#xf092;</string>
<string translatable="false" name="fa_upload">&#xf093;</string>
<string translatable="false" name="fa_lemon_o">&#xf094;</string>
<string translatable="false" name="fa_phone">&#xf095;</string>
<string translatable="false" name="fa_square_o">&#xf096;</string>
<string translatable="false" name="fa_bookmark_o">&#xf097;</string>
<string translatable="false" name="fa_phone_square">&#xf098;</string>
<string translatable="false" name="fa_twitter">&#xf099;</string>
<string translatable="false" name="fa_facebook">&#xf09a;</string>
<string translatable="false" name="fa_github">&#xf09b;</string>
<string translatable="false" name="fa_unlock">&#xf09c;</string>
<string translatable="false" name="fa_credit_card">&#xf09d;</string>
<string translatable="false" name="fa_rss">&#xf09e;</string>
<string translatable="false" name="fa_hdd_o">&#xf0a0;</string>
<string translatable="false" name="fa_bullhorn">&#xf0a1;</string>
<string translatable="false" name="fa_bell">&#xf0f3;</string>
<string translatable="false" name="fa_certificate">&#xf0a3;</string>
<string translatable="false" name="fa_hand_o_right">&#xf0a4;</string>
<string translatable="false" name="fa_hand_o_left">&#xf0a5;</string>
<string translatable="false" name="fa_hand_o_up">&#xf0a6;</string>
<string translatable="false" name="fa_hand_o_down">&#xf0a7;</string>
<string translatable="false" name="fa_arrow_circle_left">&#xf0a8;</string>
<string translatable="false" name="fa_arrow_circle_right">&#xf0a9;</string>
<string translatable="false" name="fa_arrow_circle_up">&#xf0aa;</string>
<string translatable="false" name="fa_arrow_circle_down">&#xf0ab;</string>
<string translatable="false" name="fa_globe">&#xf0ac;</string>
<string translatable="false" name="fa_wrench">&#xf0ad;</string>
<string translatable="false" name="fa_tasks">&#xf0ae;</string>
<string translatable="false" name="fa_filter">&#xf0b0;</string>
<string translatable="false" name="fa_briefcase">&#xf0b1;</string>
<string translatable="false" name="fa_arrows_alt">&#xf0b2;</string>
<string translatable="false" name="fa_users">&#xf0c0;</string>
<string translatable="false" name="fa_link">&#xf0c1;</string>
<string translatable="false" name="fa_cloud">&#xf0c2;</string>
<string translatable="false" name="fa_flask">&#xf0c3;</string>
<string translatable="false" name="fa_scissors">&#xf0c4;</string>
<string translatable="false" name="fa_files_o">&#xf0c5;</string>
<string translatable="false" name="fa_paperclip">&#xf0c6;</string>
<string translatable="false" name="fa_floppy_o">&#xf0c7;</string>
<string translatable="false" name="fa_square">&#xf0c8;</string>
<string translatable="false" name="fa_bars">&#xf0c9;</string>
<string translatable="false" name="fa_list_ul">&#xf0ca;</string>
<string translatable="false" name="fa_list_ol">&#xf0cb;</string>
<string translatable="false" name="fa_strikethrough">&#xf0cc;</string>
<string translatable="false" name="fa_underline">&#xf0cd;</string>
<string translatable="false" name="fa_table">&#xf0ce;</string>
<string translatable="false" name="fa_magic">&#xf0d0;</string>
<string translatable="false" name="fa_truck">&#xf0d1;</string>
<string translatable="false" name="fa_pinterest">&#xf0d2;</string>
<string translatable="false" name="fa_pinterest_square">&#xf0d3;</string>
<string translatable="false" name="fa_google_plus_square">&#xf0d4;</string>
<string translatable="false" name="fa_google_plus">&#xf0d5;</string>
<string translatable="false" name="fa_money">&#xf0d6;</string>
<string translatable="false" name="fa_caret_down">&#xf0d7;</string>
<string translatable="false" name="fa_caret_up">&#xf0d8;</string>
<string translatable="false" name="fa_caret_left">&#xf0d9;</string>
<string translatable="false" name="fa_caret_right">&#xf0da;</string>
<string translatable="false" name="fa_columns">&#xf0db;</string>
<string translatable="false" name="fa_sort">&#xf0dc;</string>
<string translatable="false" name="fa_sort_asc">&#xf0dd;</string>
<string translatable="false" name="fa_sort_desc">&#xf0de;</string>
<string translatable="false" name="fa_envelope">&#xf0e0;</string>
<string translatable="false" name="fa_linkedin">&#xf0e1;</string>
<string translatable="false" name="fa_undo">&#xf0e2;</string>
<string translatable="false" name="fa_gavel">&#xf0e3;</string>
<string translatable="false" name="fa_tachometer">&#xf0e4;</string>
<string translatable="false" name="fa_comment_o">&#xf0e5;</string>
<string translatable="false" name="fa_comments_o">&#xf0e6;</string>
<string translatable="false" name="fa_bolt">&#xf0e7;</string>
<string translatable="false" name="fa_sitemap">&#xf0e8;</string>
<string translatable="false" name="fa_umbrella">&#xf0e9;</string>
<string translatable="false" name="fa_clipboard">&#xf0ea;</string>
<string translatable="false" name="fa_lightbulb_o">&#xf0eb;</string>
<string translatable="false" name="fa_exchange">&#xf0ec;</string>
<string translatable="false" name="fa_cloud_download">&#xf0ed;</string>
<string translatable="false" name="fa_cloud_upload">&#xf0ee;</string>
<string translatable="false" name="fa_user_md">&#xf0f0;</string>
<string translatable="false" name="fa_stethoscope">&#xf0f1;</string>
<string translatable="false" name="fa_suitcase">&#xf0f2;</string>
<string translatable="false" name="fa_bell_o">&#xf0a2;</string>
<string translatable="false" name="fa_coffee">&#xf0f4;</string>
<string translatable="false" name="fa_cutlery">&#xf0f5;</string>
<string translatable="false" name="fa_file_text_o">&#xf0f6;</string>
<string translatable="false" name="fa_building_o">&#xf0f7;</string>
<string translatable="false" name="fa_hospital_o">&#xf0f8;</string>
<string translatable="false" name="fa_ambulance">&#xf0f9;</string>
<string translatable="false" name="fa_medkit">&#xf0fa;</string>
<string translatable="false" name="fa_fighter_jet">&#xf0fb;</string>
<string translatable="false" name="fa_beer">&#xf0fc;</string>
<string translatable="false" name="fa_h_square">&#xf0fd;</string>
<string translatable="false" name="fa_plus_square">&#xf0fe;</string>
<string translatable="false" name="fa_angle_double_left">&#xf100;</string>
<string translatable="false" name="fa_angle_double_right">&#xf101;</string>
<string translatable="false" name="fa_angle_double_up">&#xf102;</string>
<string translatable="false" name="fa_angle_double_down">&#xf103;</string>
<string translatable="false" name="fa_angle_left">&#xf104;</string>
<string translatable="false" name="fa_angle_right">&#xf105;</string>
<string translatable="false" name="fa_angle_up">&#xf106;</string>
<string translatable="false" name="fa_angle_down">&#xf107;</string>
<string translatable="false" name="fa_desktop">&#xf108;</string>
<string translatable="false" name="fa_laptop">&#xf109;</string>
<string translatable="false" name="fa_tablet">&#xf10a;</string>
<string translatable="false" name="fa_mobile">&#xf10b;</string>
<string translatable="false" name="fa_circle_o">&#xf10c;</string>
<string translatable="false" name="fa_quote_left">&#xf10d;</string>
<string translatable="false" name="fa_quote_right">&#xf10e;</string>
<string translatable="false" name="fa_spinner">&#xf110;</string>
<string translatable="false" name="fa_circle">&#xf111;</string>
<string translatable="false" name="fa_reply">&#xf112;</string>
<string translatable="false" name="fa_github_alt">&#xf113;</string>
<string translatable="false" name="fa_folder_o">&#xf114;</string>
<string translatable="false" name="fa_folder_open_o">&#xf115;</string>
<string translatable="false" name="fa_smile_o">&#xf118;</string>
<string translatable="false" name="fa_frown_o">&#xf119;</string>
<string translatable="false" name="fa_meh_o">&#xf11a;</string>
<string translatable="false" name="fa_gamepad">&#xf11b;</string>
<string translatable="false" name="fa_keyboard_o">&#xf11c;</string>
<string translatable="false" name="fa_flag_o">&#xf11d;</string>
<string translatable="false" name="fa_flag_checkered">&#xf11e;</string>
<string translatable="false" name="fa_terminal">&#xf120;</string>
<string translatable="false" name="fa_code">&#xf121;</string>
<string translatable="false" name="fa_reply_all">&#xf122;</string>
<string translatable="false" name="fa_mail_reply_all">&#xf122;</string>
<string translatable="false" name="fa_star_half_o">&#xf123;</string>
<string translatable="false" name="fa_location_arrow">&#xf124;</string>
<string translatable="false" name="fa_crop">&#xf125;</string>
<string translatable="false" name="fa_code_fork">&#xf126;</string>
<string translatable="false" name="fa_chain_broken">&#xf127;</string>
<string translatable="false" name="fa_question">&#xf128;</string>
<string translatable="false" name="fa_info">&#xf129;</string>
<string translatable="false" name="fa_exclamation">&#xf12a;</string>
<string translatable="false" name="fa_superscript">&#xf12b;</string>
<string translatable="false" name="fa_subscript">&#xf12c;</string>
<string translatable="false" name="fa_eraser">&#xf12d;</string>
<string translatable="false" name="fa_puzzle_piece">&#xf12e;</string>
<string translatable="false" name="fa_microphone">&#xf130;</string>
<string translatable="false" name="fa_microphone_slash">&#xf131;</string>
<string translatable="false" name="fa_shield">&#xf132;</string>
<string translatable="false" name="fa_calendar_o">&#xf133;</string>
<string translatable="false" name="fa_fire_extinguisher">&#xf134;</string>
<string translatable="false" name="fa_rocket">&#xf135;</string>
<string translatable="false" name="fa_maxcdn">&#xf136;</string>
<string translatable="false" name="fa_chevron_circle_left">&#xf137;</string>
<string translatable="false" name="fa_chevron_circle_right">&#xf138;</string>
<string translatable="false" name="fa_chevron_circle_up">&#xf139;</string>
<string translatable="false" name="fa_chevron_circle_down">&#xf13a;</string>
<string translatable="false" name="fa_html5">&#xf13b;</string>
<string translatable="false" name="fa_css3">&#xf13c;</string>
<string translatable="false" name="fa_anchor">&#xf13d;</string>
<string translatable="false" name="fa_unlock_alt">&#xf13e;</string>
<string translatable="false" name="fa_bullseye">&#xf140;</string>
<string translatable="false" name="fa_ellipsis_h">&#xf141;</string>
<string translatable="false" name="fa_ellipsis_v">&#xf142;</string>
<string translatable="false" name="fa_rss_square">&#xf143;</string>
<string translatable="false" name="fa_play_circle">&#xf144;</string>
<string translatable="false" name="fa_ticket">&#xf145;</string>
<string translatable="false" name="fa_minus_square">&#xf146;</string>
<string translatable="false" name="fa_minus_square_o">&#xf147;</string>
<string translatable="false" name="fa_level_up">&#xf148;</string>
<string translatable="false" name="fa_level_down">&#xf149;</string>
<string translatable="false" name="fa_check_square">&#xf14a;</string>
<string translatable="false" name="fa_pencil_square">&#xf14b;</string>
<string translatable="false" name="fa_external_link_square">&#xf14c;</string>
<string translatable="false" name="fa_share_square">&#xf14d;</string>
<string translatable="false" name="fa_compass">&#xf14e;</string>
<string translatable="false" name="fa_caret_square_o_down">&#xf150;</string>
<string translatable="false" name="fa_caret_square_o_up">&#xf151;</string>
<string translatable="false" name="fa_caret_square_o_right">&#xf152;</string>
<string translatable="false" name="fa_eur">&#xf153;</string>
<string translatable="false" name="fa_gbp">&#xf154;</string>
<string translatable="false" name="fa_usd">&#xf155;</string>
<string translatable="false" name="fa_inr">&#xf156;</string>
<string translatable="false" name="fa_jpy">&#xf157;</string>
<string translatable="false" name="fa_rub">&#xf158;</string>
<string translatable="false" name="fa_krw">&#xf159;</string>
<string translatable="false" name="fa_btc">&#xf15a;</string>
<string translatable="false" name="fa_file">&#xf15b;</string>
<string translatable="false" name="fa_file_text">&#xf15c;</string>
<string translatable="false" name="fa_sort_alpha_asc">&#xf15d;</string>
<string translatable="false" name="fa_sort_alpha_desc">&#xf15e;</string>
<string translatable="false" name="fa_sort_amount_asc">&#xf160;</string>
<string translatable="false" name="fa_sort_amount_desc">&#xf161;</string>
<string translatable="false" name="fa_sort_numeric_asc">&#xf162;</string>
<string translatable="false" name="fa_sort_numeric_desc">&#xf163;</string>
<string translatable="false" name="fa_thumbs_up">&#xf164;</string>
<string translatable="false" name="fa_thumbs_down">&#xf165;</string>
<string translatable="false" name="fa_youtube_square">&#xf166;</string>
<string translatable="false" name="fa_youtube">&#xf167;</string>
<string translatable="false" name="fa_xing">&#xf168;</string>
<string translatable="false" name="fa_xing_square">&#xf169;</string>
<string translatable="false" name="fa_youtube_play">&#xf16a;</string>
<string translatable="false" name="fa_dropbox">&#xf16b;</string>
<string translatable="false" name="fa_stack_overflow">&#xf16c;</string>
<string translatable="false" name="fa_instagram">&#xf16d;</string>
<string translatable="false" name="fa_flickr">&#xf16e;</string>
<string translatable="false" name="fa_adn">&#xf170;</string>
<string translatable="false" name="fa_bitbucket">&#xf171;</string>
<string translatable="false" name="fa_bitbucket_square">&#xf172;</string>
<string translatable="false" name="fa_tumblr">&#xf173;</string>
<string translatable="false" name="fa_tumblr_square">&#xf174;</string>
<string translatable="false" name="fa_long_arrow_down">&#xf175;</string>
<string translatable="false" name="fa_long_arrow_up">&#xf176;</string>
<string translatable="false" name="fa_long_arrow_left">&#xf177;</string>
<string translatable="false" name="fa_long_arrow_right">&#xf178;</string>
<string translatable="false" name="fa_apple">&#xf179;</string>
<string translatable="false" name="fa_windows">&#xf17a;</string>
<string translatable="false" name="fa_android">&#xf17b;</string>
<string translatable="false" name="fa_linux">&#xf17c;</string>
<string translatable="false" name="fa_dribbble">&#xf17d;</string>
<string translatable="false" name="fa_skype">&#xf17e;</string>
<string translatable="false" name="fa_foursquare">&#xf180;</string>
<string translatable="false" name="fa_trello">&#xf181;</string>
<string translatable="false" name="fa_female">&#xf182;</string>
<string translatable="false" name="fa_male">&#xf183;</string>
<string translatable="false" name="fa_gittip">&#xf184;</string>
<string translatable="false" name="fa_sun_o">&#xf185;</string>
<string translatable="false" name="fa_moon_o">&#xf186;</string>
<string translatable="false" name="fa_archive">&#xf187;</string>
<string translatable="false" name="fa_bug">&#xf188;</string>
<string translatable="false" name="fa_vk">&#xf189;</string>
<string translatable="false" name="fa_weibo">&#xf18a;</string>
<string translatable="false" name="fa_renren">&#xf18b;</string>
<string translatable="false" name="fa_pagelines">&#xf18c;</string>
<string translatable="false" name="fa_stack_exchange">&#xf18d;</string>
<string translatable="false" name="fa_arrow_circle_o_right">&#xf18e;</string>
<string translatable="false" name="fa_arrow_circle_o_left">&#xf190;</string>
<string translatable="false" name="fa_caret_square_o_left">&#xf191;</string>
<string translatable="false" name="fa_dot_circle_o">&#xf192;</string>
<string translatable="false" name="fa_wheelchair">&#xf193;</string>
<string translatable="false" name="fa_vimeo_square">&#xf194;</string>
<string translatable="false" name="fa_try">&#xf195;</string>
<string translatable="false" name="fa_plus_square_o">&#xf196;</string>
</resources>

View File

@@ -0,0 +1,26 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
~ Copyright (C) 2016 Alinson Santos Xavier
~
~ This program is free software: you can redistribute it and/or modify it
~ under the terms of the GNU General Public License as published by the Free
~ Software Foundation, either version 3 of the License, or (at your option)
~ any later version.
~
~ This program is distributed in the hope that it will be useful, but WITHOUT
~ ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
~ FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
~ more details.
~
~ You should have received a copy of the GNU General Public License
~ along with this program. If not, see <http://www.gnu.org/licenses/>.
~
~
-->
<resources>
<string name="habit_key" translatable="false" />
<string name="offset_key" translatable="false" />
<string name="toggle_key" translatable="false" />
<item name="timestamp_key" type="id"/>
</resources>

View File

@@ -1,66 +1,119 @@
<?xml version="1.0" encoding="utf-8"?>
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="app_name">Habits</string>
<string name="app_name">Loop Habit Tracker</string>
<string name="main_activity_title">Habits</string>
<string name="action_settings">Settings</string>
<string name="edit">Edit</string>
<string name="delete">Delete</string>
<string name="archive">Archive</string>
<string name="unarchive">Unarchive</string>
<string name="add_habit">Add habit</string>
<string name="color_picker_default_title">Select a Color</string>
<string name="color_swatch_description">Color <xliff:g id="color_index" example="14">%1$d</xliff:g></string>
<string name="color_swatch_description_selected">Color <xliff:g id="color_index" example="14">%1$d</xliff:g> selected</string>
<string name="color_picker_default_title">Change color</string>
<string name="toast_habit_created">Habit created.</string>
<string name="toast_habit_deleted">Habit deleted.</string>
<string name="toast_habit_deleted">Habits deleted.</string>
<string name="toast_habit_restored">Habits restored.</string>
<string name="toast_nothing_to_undo">Nothing to undo.</string>
<string name="toast_nothing_to_redo">Nothing to redo.</string>
<string name="toast_habit_changed">Habit changed.</string>
<string name="toast_habit_changed_back">Habit changed back.</string>
<string name="toast_repetition_toggled">Repetition toggled.</string>
<string name="toast_habit_archived">Habit archived.</string>
<string name="toast_habit_unarchived">Habit unarchived.</string>
<string name="toast_habit_archived">Habits archived.</string>
<string name="toast_habit_unarchived">Habits unarchived.</string>
<string name="color_swatch_description" translatable="false">Color <xliff:g id="color_index" example="14">%1$d</xliff:g></string>
<string name="color_swatch_description_selected" translatable="false">Color <xliff:g id="color_index" example="14">%1$d</xliff:g> selected</string>
<!-- Date and time picker -->
<string name="done_label">Done</string>
<string name="clear_label">Clear</string>
<string name="hour_picker_description">Hours circular slider</string>
<string name="minute_picker_description">Minutes circular slider</string>
<string name="hour_picker_description" translatable="false">Hours circular slider</string>
<string name="minute_picker_description" translatable="false">Minutes circular slider</string>
<string name="select_hours">Select hours</string>
<string name="select_minutes">Select minutes</string>
<string name="day_picker_description">Month grid of days</string>
<string name="year_picker_description">Year list</string>
<string name="day_picker_description" translatable="false">Month grid of days</string>
<string name="year_picker_description" translatable="false">Year list</string>
<string name="select_day">Select month and day</string>
<string name="select_year">Select year</string>
<string name="item_is_selected"><xliff:g id="item" example="2013">%1$s</xliff:g> selected</string>
<string name="deleted_key"><xliff:g id="key" example="4">%1$s</xliff:g> deleted</string>
<string name="time_placeholder">--</string>
<string name="time_separator">:</string>
<string name="radial_numbers_typeface">sans-serif</string>
<string name="sans_serif">sans-serif</string>
<string name="day_of_week_label_typeface">sans-serif</string>
<string name="habit_key"></string>
<string name="offset_key"></string>
<string name="toggle_key"></string>
<string name="item_is_selected" translatable="false"><xliff:g id="item" example="2013">%1$s</xliff:g> selected</string>
<string name="deleted_key" translatable="false"><xliff:g id="key" example="4">%1$s</xliff:g> deleted</string>
<string name="time_placeholder" translatable="false">--</string>
<string name="time_separator" translatable="false">:</string>
<string name="radial_numbers_typeface" translatable="false">sans-serif</string>
<string name="sans_serif" translatable="false">sans-serif</string>
<string name="day_of_week_label_typeface" translatable="false">sans-serif</string>
<item name="KEY_TIMESTAMP" type="id"/>
<string name="title_activity_show_habit"></string>
<string name="hello_world">Hello world!</string>
<string name="title_activity_show_habit" translatable="false"/>
<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="description_hint">Question (Did you ... today?)</string>
<string name="description_hint">Question (Did you today?)</string>
<string name="repeat">Repeat</string>
<string name="times_every">times every</string>
<string name="times_every">times in</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>
<string name="no_habits_found">You have no active habits</string>
<string name="long_press_to_toggle">Long press to check or uncheck</string>
<string name="long_press_to_toggle">Press-and-hold to check or uncheck</string>
<string name="reminder_off">Off</string>
<string name="validation_name_should_not_be_blank">Name cannot be blank.</string>
<string name="validation_number_should_be_positive">Number must be positive.</string>
<string name="validation_at_most_one_rep_per_day">You can have at most one repetition per day</string>
<string name="create_habit">Create habit</string>
<string name="edit_habit">Edit habit</string>
<string name="check">Check</string>
<string name="snooze">Later</string>
<!-- App introduction -->
<string name="intro_title_1">Welcome</string>
<string name="intro_description_1">Loop Habit Tracker helps you create and maintain good habits.</string>
<string name="intro_title_2">Create some new habits</string>
<string name="intro_description_2">Every day, after performing your habit, put a checkmark on the app.</string>
<string name="intro_title_3">Keep doing it</string>
<string name="intro_description_3">Habits performed consistently for a long time will earn a full star.</string>
<string name="intro_title_4">Track your progress</string>
<string name="intro_description_4">Detailed graphs show you how your habits improved over time.</string>
<string name="interval_15_minutes">15 minutes</string>
<string name="interval_30_minutes">30 minutes</string>
<string name="interval_1_hour">1 hour</string>
<string name="interval_2_hour">2 hours</string>
<string name="interval_4_hour">4 hours</string>
<string name="interval_8_hour">8 hours</string>
<string name="pref_toggle_title">Toggle repetitions with short press</string>
<string name="pref_toggle_description">More convenient, but might cause accidental toggles.</string>
<string name="pref_snooze_interval_title">Snooze interval on reminders</string>
<string name="pref_rate_this_app">Rate this app in Google Play</string>
<string name="pref_send_feedback">Send feedback to developer</string>
<string name="pref_view_source_code">View source code at GitHub</string>
<string name="pref_view_app_introduction">View app introduction</string>
<string name="links">Links</string>
<string name="behavior">Behavior</string>
<string name="name">Name</string>
<string name="show_archived">Show archived</string>
<string name="settings">Settings</string>
<string name="snooze_interval">Snooze interval</string>
<string name="hint_title">Did you know?</string>
<string name="hint_drag">To rearrange the entries, press-and-hold on the name of the habit, then drag it to the correct place.</string>
<string name="hint_landscape">You can see more days by putting your phone in landscape mode.</string>
<string name="delete_habits">Delete Habits</string>
<string name="delete_habits_message">The habits will be permanently deleted. This action cannot be undone.</string>
<string name="weekends">Weekends</string>
<string name="any_weekday">Any weekday</string>
<string name="any_day">Any day</string>
<string-array name="hints">
<item>@string/hint_drag</item>
<item>@string/hint_landscape</item>
</string-array>
</resources>

View File

@@ -40,7 +40,7 @@
</style>
<style name="dialogFormTimePicker" parent="android:Widget.DeviceDefault.Light.Spinner">
<item name="android:layout_width">100dp</item>
<item name="android:layout_width">wrap_content</item>
<item name="android:layout_height">wrap_content</item>
<item name="android:paddingLeft">12dp</item>
</style>

Some files were not shown because too many files have changed in this diff Show More