Merge branch 'release/1.2.0' into master

pull/30/head
Alinson S. Xavier 10 years ago
commit 83ccfb82ac

@ -0,0 +1,25 @@
# Changelog
### 1.2.0 (March 4, 2016)
* Ability to export habit data as CSV
* Widgets (checkmark, history, score and streaks)
* More natural scrolling on data views (fling)
* Minor UI improvements on pre-Lollipop devices
* Fix crash on Samsung Galaxy TabS 8.4
* Other minor bug fixes
### 1.1.1 (February 24, 2016)
* Show reminder only on chosen days of the week
* Rearrange habits by long-pressing then dragging
* Select and modify multiple habits simultaneously
* 12/24 hour format according to phone preferences
* Permanently delete habits
* Usage hints during startup
* Translation to Brazilian Portuguese and Chinese
* Other minor fixes
### 1.0.0 (February 19, 2016)
* Initial release

@ -1,16 +1,31 @@
# 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).
Loop is a simple Android app that helps you create and maintain good habits, allowing you to achieve your long-term goals. Detailed graphs and statistics show you how your habits improved over time. It is completely ad-free and open source.
<a href="https://play.google.com/store/apps/details?id=org.isoron.uhabits&utm_source=global_co&utm_medium=prtnr&utm_content=Mar2515&utm_campaign=PartBadge&pcampaignid=MKT-AC-global-none-all-co-pr-py-PartBadges-Oct1515-1"><img alt="Get it on Google Play" src="https://play.google.com/intl/en_us/badges/images/apps/en-play-badge-border.png" width="200px"/></a>
## Features
* Simple and beautiful interface, following the Material Design guidelines.
* Advanced algorithms for calculating the strength of your habits. Every repetition makes your habit stronger, and every missed day makes it weaker. A few missed days after a long streak, however, will not completely destroy your entire progress.
* Detailed graphs and statistics, showing how did you habits improve over time. Scroll back to see the complete history of your habit.
* Support for both daily habits and habits with more complex schedules, such as 3 times every week; one time every other week; or every other day.
* Habit reminders at a chosen hour of the day.
* Support for Android Wear. Reminders can be checked or dismissed from the watch.
* Completely ad-free and open source. There are absolutely no advertisements, annoying notifications or intrusive permissions in this app, and there will never be. The complete source code is available under the GPLv3.
<b>Simple, beautiful and modern interface</b>
Loop has a minimalistic interface that is easy to use and follows the material design guidelines.
<b>Habit score</b>
In addition to showing your current streak, Loop has an advanced algorithm for calculating the strength of your habits. Every repetition makes your habit stronger, and every missed day makes it weaker. A few missed days after a long streak, however, will not completely destroy your entire progress.
<b>Detailed graphs and statistics</b>
Clearly see how your habits improved over time with beautiful and detailed graphs. Scroll back to see the complete history of your habits.
<b>Flexible schedules</b>
Supports both daily habits and habits with more complex schedules, such as 3 times every week; one time every other week; or every other day.
<b>Reminders</b>
Create an individual reminder for each habit, at a chosen hour of the day. Easily check, dismiss or snooze your habit directly from the notification, without opening the app.
<b>Optimized for smartwatches</b>
Reminders can be checked, snoozed or dismissed directly from your Android Wear watch.
<b>Completely ad-free and open source</b>
There are absolutely no advertisements, annoying notifications or intrusive permissions in this app, and there will never be. The complete source code is available under the GPLv3.
## Screenshots
@ -18,12 +33,15 @@ Loop is a simple Android app that helps you create and maintain good habits. Det
[![Edit habit][screen2th]][screen2]
[![Habit strength][screen3th]][screen3]
[![Habit history and streaks][screen4th]][screen4]
[![Widgets][screen5th]][screen5]
[screen1]: screenshots/original/uhabits1.png
[screen2]: screenshots/original/uhabits2.png
[screen3]: screenshots/original/uhabits3.png
[screen4]: screenshots/original/uhabits4.png
[screen5]: screenshots/original/uhabits5.png
[screen1th]: screenshots/thumbs/uhabits1.png
[screen2th]: screenshots/thumbs/uhabits2.png
[screen3th]: screenshots/thumbs/uhabits3.png
[screen4th]: screenshots/thumbs/uhabits4.png
[screen5th]: screenshots/thumbs/uhabits5.png

@ -2,18 +2,23 @@
<manifest
package="org.isoron.uhabits"
xmlns:android="http://schemas.android.com/apk/res/android"
android:versionCode="8"
android:versionName="1.1.1">
android:versionCode="9"
android:versionName="1.2.0">
<uses-permission
android:name="android.permission.VIBRATE"/>
<uses-permission
android:name="android.permission.WRITE_EXTERNAL_STORAGE"
android:maxSdkVersion="18" />
<application
android:name="com.activeandroid.app.Application"
android:allowBackup="true"
android:backupAgent=".HabitsBackupAgent"
android:icon="@mipmap/ic_launcher"
android:label="@string/main_activity_title"
android:theme="@style/AppBaseTheme"
android:backupAgent=".HabitsBackupAgent">
android:theme="@style/AppBaseTheme">
<meta-data
android:name="AA_DB_NAME"
@ -21,7 +26,7 @@
<meta-data
android:name="AA_DB_VERSION"
android:value="11"/>
android:value="12"/>
<meta-data
android:name="com.google.android.backup.api_key"
@ -38,7 +43,7 @@
</activity>
<receiver
android:name=".ReminderAlarmReceiver" />
android:name=".HabitBroadcastReceiver"/>
<activity
android:name=".ShowHabitActivity"
@ -58,9 +63,65 @@
android:value="org.isoron.uhabits.MainActivity"/>
</activity>
<activity android:name=".IntroActivity"
<activity
android:name=".IntroActivity"
android:label=""
android:theme="@style/Theme.AppCompat.Light.NoActionBar"/>
<receiver
android:name=".widgets.CheckmarkWidgetProvider"
android:label="Checkmark">
<intent-filter>
<action android:name="android.appwidget.action.APPWIDGET_UPDATE"/>
</intent-filter>
<meta-data
android:name="android.appwidget.provider"
android:resource="@xml/widget_checkmark_info"/>
</receiver>
<receiver
android:name=".widgets.HistoryWidgetProvider"
android:label="History">
<intent-filter>
<action android:name="android.appwidget.action.APPWIDGET_UPDATE"/>
</intent-filter>
<meta-data
android:name="android.appwidget.provider"
android:resource="@xml/widget_history_info"/>
</receiver>
<receiver
android:name=".widgets.ScoreWidgetProvider"
android:label="Score">
<intent-filter>
<action android:name="android.appwidget.action.APPWIDGET_UPDATE"/>
</intent-filter>
<meta-data
android:name="android.appwidget.provider"
android:resource="@xml/widget_score_info"/>
</receiver>
<receiver
android:name=".widgets.StreakWidgetProvider"
android:label="Streaks">
<intent-filter>
<action android:name="android.appwidget.action.APPWIDGET_UPDATE"/>
</intent-filter>
<meta-data
android:name="android.appwidget.provider"
android:resource="@xml/widget_streak_info"/>
</receiver>
<activity
android:name=".widgets.HabitPickerDialog"
android:theme="@style/Theme.AppCompat.Light.Dialog">
<intent-filter>
<action android:name="android.appwidget.action.APPWIDGET_CONFIGURE"/>
</intent-filter>
</activity>
</application>
</manifest>

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 35 KiB

@ -23,6 +23,7 @@ import java.util.Locale;
import org.isoron.uhabits.R;
import android.animation.ObjectAnimator;
import android.annotation.SuppressLint;
import android.app.ActionBar.LayoutParams;
import android.app.DialogFragment;
import android.content.Context;
@ -132,6 +133,7 @@ public class TimePickerDialog extends DialogFragment implements OnValueSelectedL
// Empty constructor required for dialog fragment.
}
@SuppressLint("Java")
public TimePickerDialog(Context context, int theme, OnTimeSetListener callback,
int hourOfDay, int minute, boolean is24HourMode) {
// Empty constructor required for dialog fragment.

@ -57,4 +57,35 @@ public class ColorHelper
return a << ALPHA_CHANNEL | r << RED_CHANNEL | g << GREEN_CHANNEL | b << BLUE_CHANNEL;
}
public static int setHue(int color, float newHue)
{
return setHSVParameter(color, newHue, 0);
}
public static int setSaturation(int color, float newSaturation)
{
return setHSVParameter(color, newSaturation, 1);
}
public static int setValue(int color, float newValue)
{
return setHSVParameter(color, newValue, 2);
}
public static int setMinValue(int color, float newValue)
{
float hsv[] = new float[3];
Color.colorToHSV(color, hsv);
hsv[2] = Math.max(hsv[2], newValue);
return Color.HSVToColor(hsv);
}
private static int setHSVParameter(int color, float newValue, int index)
{
float hsv[] = new float[3];
Color.colorToHSV(color, hsv);
hsv[index] = newValue;
return Color.HSVToColor(hsv);
}
}

@ -17,21 +17,22 @@
package org.isoron.helpers;
import android.content.Context;
import android.content.DialogInterface;
import android.content.DialogInterface.OnClickListener;
import android.content.SharedPreferences;
import android.content.res.Resources;
import android.graphics.Typeface;
import android.os.Vibrator;
import android.preference.PreferenceManager;
import android.util.AttributeSet;
import android.util.DisplayMetrics;
import android.view.View;
import android.view.inputmethod.InputMethodManager;
import android.widget.TextView;
import org.isoron.uhabits.R;
import org.isoron.uhabits.BuildConfig;
public abstract class DialogHelper
{
public static final String ISORON_NAMESPACE = "http://isoron.org/android";
private static Typeface fontawesome;
public interface OnSavedListener
@ -60,9 +61,32 @@ public abstract class DialogHelper
prefs.edit().putInt("launch_count", count + 1).apply();
}
public static void updateLastAppVersion(Context context)
{
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context);
prefs.edit().putInt("last_version", BuildConfig.VERSION_CODE).apply();
}
public static int getLaunchCount(Context context)
{
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context);
return prefs.getInt("launch_count", 0);
}
public static String getAttribute(Context context, AttributeSet attrs, String name)
{
int resId = attrs.getAttributeResourceValue(ISORON_NAMESPACE, name, 0);
if(resId != 0)
return context.getResources().getString(resId);
else
return attrs.getAttributeValue(ISORON_NAMESPACE, name);
}
public static float dpToPixels(Context context, float dp)
{
Resources resources = context.getResources();
DisplayMetrics metrics = resources.getDisplayMetrics();
return dp * (metrics.densityDpi / DisplayMetrics.DENSITY_DEFAULT);
}
}

@ -31,6 +31,7 @@ import android.net.Uri;
import android.os.Handler;
import android.preference.PreferenceManager;
import android.support.v4.app.NotificationCompat;
import android.support.v4.content.LocalBroadcastManager;
import org.isoron.helpers.DateHelper;
import org.isoron.uhabits.helpers.ReminderHelper;
@ -38,12 +39,11 @@ import org.isoron.uhabits.models.Habit;
import java.util.Date;
public class ReminderAlarmReceiver extends BroadcastReceiver
public class HabitBroadcastReceiver extends BroadcastReceiver
{
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_SHOW_REMINDER = "org.isoron.uhabits.ACTION_SHOW_REMINDER";
public static final String ACTION_SNOOZE = "org.isoron.uhabits.ACTION_SNOOZE";
@Override
@ -51,7 +51,7 @@ public class ReminderAlarmReceiver extends BroadcastReceiver
{
switch (intent.getAction())
{
case ACTION_REMIND:
case ACTION_SHOW_REMINDER:
createNotification(context, intent);
createReminderAlarms(context);
break;
@ -97,15 +97,18 @@ public class ReminderAlarmReceiver extends BroadcastReceiver
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);
Long timestamp = intent.getLongExtra("timestamp", DateHelper.getStartOfToday());
Habit habit = Habit.get(ContentUris.parseId(data));
habit.toggleRepetition(timestamp);
habit.repetitions.toggle(timestamp);
habit.save();
dismissNotification(context, habit);
LocalBroadcastManager manager = LocalBroadcastManager.getInstance(context);
Intent refreshIntent = new Intent(MainActivity.ACTION_REFRESH);
manager.sendBroadcast(refreshIntent);
MainActivity.updateWidgets(context);
}
private void dismissAllHabits()
@ -131,8 +134,10 @@ public class ReminderAlarmReceiver extends BroadcastReceiver
{
Uri data = intent.getData();
Habit habit = Habit.get(ContentUris.parseId(data));
Long timestamp = intent.getLongExtra("timestamp", DateHelper.getStartOfToday());
Long reminderTime = intent.getLongExtra("reminderTime", DateHelper.getStartOfToday());
if (habit.hasImplicitRepToday()) return;
if (habit.repetitions.hasImplicitRepToday()) return;
habit.highlight = 1;
habit.save();
@ -147,24 +152,12 @@ public class ReminderAlarmReceiver extends BroadcastReceiver
PendingIntent contentPendingIntent =
PendingIntent.getActivity(context, 0, contentIntent, 0);
Intent deleteIntent = new Intent(context, ReminderAlarmReceiver.class);
deleteIntent.setAction(ACTION_DISMISS);
PendingIntent deletePendingIntent = PendingIntent.getBroadcast(context, 0, deleteIntent, 0);
Intent checkIntent = new Intent(context, ReminderAlarmReceiver.class);
checkIntent.setData(data);
checkIntent.setAction(ACTION_CHECK);
PendingIntent checkIntentPending = PendingIntent.getBroadcast(context, 0, checkIntent, 0);
Intent snoozeIntent = new Intent(context, ReminderAlarmReceiver.class);
snoozeIntent.setData(data);
snoozeIntent.setAction(ACTION_SNOOZE);
PendingIntent snoozeIntentPending = PendingIntent.getBroadcast(context, 0, snoozeIntent, 0);
PendingIntent dismissPendingIntent = buildDismissIntent(context);
PendingIntent checkIntentPending = buildCheckIntent(context, habit, timestamp);
PendingIntent snoozeIntentPending = buildSnoozeIntent(context, habit);
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));
@ -174,7 +167,7 @@ public class ReminderAlarmReceiver extends BroadcastReceiver
.setContentTitle(habit.name)
.setContentText(habit.description)
.setContentIntent(contentPendingIntent)
.setDeleteIntent(deletePendingIntent)
.setDeleteIntent(dismissPendingIntent)
.addAction(R.drawable.ic_action_check,
context.getString(R.string.check), checkIntentPending)
.addAction(R.drawable.ic_action_snooze,
@ -194,6 +187,32 @@ public class ReminderAlarmReceiver extends BroadcastReceiver
notificationManager.notify(notificationId, notification);
}
public static PendingIntent buildSnoozeIntent(Context context, Habit habit)
{
Uri data = habit.getUri();
Intent snoozeIntent = new Intent(context, HabitBroadcastReceiver.class);
snoozeIntent.setData(data);
snoozeIntent.setAction(ACTION_SNOOZE);
return PendingIntent.getBroadcast(context, 0, snoozeIntent, 0);
}
public static PendingIntent buildCheckIntent(Context context, Habit habit, Long timestamp)
{
Uri data = habit.getUri();
Intent checkIntent = new Intent(context, HabitBroadcastReceiver.class);
checkIntent.setData(data);
checkIntent.setAction(ACTION_CHECK);
if(timestamp != null) checkIntent.putExtra("timestamp", timestamp);
return PendingIntent.getBroadcast(context, 0, checkIntent, PendingIntent.FLAG_ONE_SHOT);
}
public static PendingIntent buildDismissIntent(Context context)
{
Intent deleteIntent = new Intent(context, HabitBroadcastReceiver.class);
deleteIntent.setAction(ACTION_DISMISS);
return PendingIntent.getBroadcast(context, 0, deleteIntent, 0);
}
private boolean checkWeekday(Intent intent, Habit habit)
{
Long timestamp = intent.getLongExtra("timestamp", DateHelper.getStartOfToday());

@ -16,11 +16,18 @@
package org.isoron.uhabits;
import android.appwidget.AppWidgetManager;
import android.content.BroadcastReceiver;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.SharedPreferences;
import android.net.Uri;
import android.os.AsyncTask;
import android.os.Bundle;
import android.preference.PreferenceManager;
import android.support.v4.content.LocalBroadcastManager;
import android.view.Menu;
import android.view.MenuItem;
@ -30,12 +37,20 @@ import org.isoron.helpers.ReplayableActivity;
import org.isoron.uhabits.fragments.ListHabitsFragment;
import org.isoron.uhabits.helpers.ReminderHelper;
import org.isoron.uhabits.models.Habit;
import org.isoron.uhabits.widgets.CheckmarkWidgetProvider;
import org.isoron.uhabits.widgets.HistoryWidgetProvider;
import org.isoron.uhabits.widgets.ScoreWidgetProvider;
import org.isoron.uhabits.widgets.StreakWidgetProvider;
public class MainActivity extends ReplayableActivity
implements ListHabitsFragment.OnHabitClickListener
{
private ListHabitsFragment listHabitsFragment;
SharedPreferences prefs;
private SharedPreferences prefs;
private BroadcastReceiver receiver;
private LocalBroadcastManager localBroadcastManager;
public static final String ACTION_REFRESH = "org.isoron.uhabits.ACTION_REFRESH";
@Override
protected void onCreate(Bundle savedInstanceState)
@ -47,15 +62,30 @@ public class MainActivity extends ReplayableActivity
listHabitsFragment =
(ListHabitsFragment) getFragmentManager().findFragmentById(R.id.fragment1);
receiver = new Receiver();
localBroadcastManager = LocalBroadcastManager.getInstance(this);
localBroadcastManager.registerReceiver(receiver, new IntentFilter(ACTION_REFRESH));
onStartup();
}
private void onStartup()
{
PreferenceManager.setDefaultValues(this, R.xml.preferences, false);
ReminderHelper.createReminderAlarms(MainActivity.this);
DialogHelper.incrementLaunchCount(this);
DialogHelper.updateLastAppVersion(this);
showTutorial();
new AsyncTask<Void, Void, Void>() {
@Override
protected Void doInBackground(Void... params)
{
ReminderHelper.createReminderAlarms(MainActivity.this);
updateWidgets(MainActivity.this);
return null;
}
}.execute();
}
private void showTutorial()
@ -108,5 +138,40 @@ public class MainActivity extends ReplayableActivity
public void onPostExecuteCommand(Long refreshKey)
{
listHabitsFragment.onPostExecuteCommand(refreshKey);
updateWidgets(this);
}
public static void updateWidgets(Context context)
{
updateWidgets(context, CheckmarkWidgetProvider.class);
updateWidgets(context, HistoryWidgetProvider.class);
updateWidgets(context, ScoreWidgetProvider.class);
updateWidgets(context, StreakWidgetProvider.class);
}
private static void updateWidgets(Context context, Class providerClass)
{
ComponentName provider = new ComponentName(context, providerClass);
Intent intent = new Intent(context, providerClass);
intent.setAction(AppWidgetManager.ACTION_APPWIDGET_UPDATE);
int ids[] = AppWidgetManager.getInstance(context).getAppWidgetIds(provider);
intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_IDS, ids);
context.sendBroadcast(intent);
}
@Override
protected void onDestroy()
{
localBroadcastManager.unregisterReceiver(receiver);
super.onDestroy();
}
class Receiver extends BroadcastReceiver
{
@Override
public void onReceive(Context context, Intent intent)
{
listHabitsFragment.onPostExecuteCommand(null);
}
}
}

@ -23,7 +23,6 @@ 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

@ -0,0 +1,66 @@
/* 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;
public class CreateHabitCommand extends Command
{
private Habit model;
private Long savedId;
public CreateHabitCommand(Habit model)
{
this.model = model;
}
@Override
public void execute()
{
Habit savedHabit = new Habit(model);
if (savedId == null)
{
savedHabit.save();
savedId = savedHabit.getId();
}
else
{
savedHabit.save(savedId);
}
}
@Override
public void undo()
{
Habit.get(savedId).delete();
}
@Override
public Integer getExecuteStringId()
{
return R.string.toast_habit_created;
}
@Override
public Integer getUndoStringId()
{
return R.string.toast_habit_deleted;
}
}

@ -0,0 +1,75 @@
/* 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;
public class EditHabitCommand extends Command
{
private Habit original;
private Habit modified;
private long savedId;
private boolean hasIntervalChanged;
public EditHabitCommand(Habit original, Habit modified)
{
this.savedId = original.getId();
this.modified = new Habit(modified);
this.original = new Habit(original);
hasIntervalChanged = (this.original.freqDen != this.modified.freqDen ||
this.original.freqNum != this.modified.freqNum);
}
public void execute()
{
Habit habit = Habit.get(savedId);
habit.copyAttributes(modified);
habit.save();
if (hasIntervalChanged)
{
habit.checkmarks.deleteNewerThan(0);
habit.streaks.deleteNewerThan(0);
habit.scores.deleteNewerThan(0);
}
}
public void undo()
{
Habit habit = Habit.get(savedId);
habit.copyAttributes(original);
habit.save();
if (hasIntervalChanged)
{
habit.checkmarks.deleteNewerThan(0);
habit.streaks.deleteNewerThan(0);
habit.scores.deleteNewerThan(0);
}
}
public Integer getExecuteStringId()
{
return R.string.toast_habit_changed;
}
public Integer getUndoStringId()
{
return R.string.toast_habit_changed_back;
}
}

@ -0,0 +1,44 @@
/* 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.models.Habit;
public class ToggleRepetitionCommand extends Command
{
private Long offset;
private Habit habit;
public ToggleRepetitionCommand(Habit habit, long offset)
{
this.offset = offset;
this.habit = habit;
}
@Override
public void execute()
{
habit.repetitions.toggle(offset);
}
@Override
public void undo()
{
execute();
}
}

@ -40,6 +40,8 @@ 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.commands.CreateHabitCommand;
import org.isoron.uhabits.commands.EditHabitCommand;
import org.isoron.uhabits.dialogs.WeekdayPickerDialog;
import org.isoron.uhabits.models.Habit;
@ -241,11 +243,11 @@ public class EditHabitFragment extends DialogFragment
if (mode == EDIT_MODE)
{
command = originalHabit.new EditCommand(modifiedHabit);
command = new EditHabitCommand(originalHabit, modifiedHabit);
savedHabit = originalHabit;
}
if (mode == CREATE_MODE) command = new Habit.CreateCommand(modifiedHabit);
if (mode == CREATE_MODE) command = new CreateHabitCommand(modifiedHabit);
if (onSavedListener != null) onSavedListener.onSaved(command, savedHabit);

@ -23,9 +23,12 @@ import android.app.AlertDialog;
import android.app.Fragment;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.SharedPreferences;
import android.graphics.Color;
import android.graphics.Typeface;
import android.net.Uri;
import android.os.AsyncTask;
import android.os.Bundle;
import android.preference.PreferenceManager;
import android.util.DisplayMetrics;
@ -66,11 +69,15 @@ 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.ToggleRepetitionCommand;
import org.isoron.uhabits.commands.UnarchiveHabitsCommand;
import org.isoron.uhabits.helpers.ReminderHelper;
import org.isoron.uhabits.io.CSVExporter;
import org.isoron.uhabits.loaders.HabitListLoader;
import org.isoron.uhabits.models.Habit;
import org.isoron.uhabits.models.Score;
import java.io.File;
import java.util.Date;
import java.util.GregorianCalendar;
import java.util.LinkedList;
@ -199,6 +206,12 @@ public class ListHabitsFragment extends Fragment
return true;
}
case R.id.action_export_csv:
{
onExportHabitsClick(selectedHabits);
return true;
}
}
return false;
@ -248,6 +261,7 @@ public class ListHabitsFragment extends Fragment
private ActionMode actionMode;
private List<Integer> selectedPositions;
private DragSortController dragSortController;
private ProgressBar progressBar;
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
@ -255,7 +269,7 @@ public class ListHabitsFragment extends Fragment
{
DisplayMetrics dm = getResources().getDisplayMetrics();
int width = (int) (dm.widthPixels / dm.density);
buttonCount = (int) ((width - 160) / 42.0);
buttonCount = Math.max(0, (int) ((width - 160) / 42.0));
tvNameWidth = (int) ((width - 30 - buttonCount * 42) * dm.density);
loader = new HabitListLoader();
@ -265,7 +279,7 @@ public class ListHabitsFragment extends Fragment
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);
progressBar = (ProgressBar) view.findViewById(R.id.progressBar);
loader.setProgressBar(progressBar);
adapter = new ListHabitsAdapter(getActivity());
@ -533,7 +547,7 @@ public class ListHabitsFragment extends Fragment
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());
executeCommand(new ToggleRepetitionCommand(habit, timestamp), habit.getId());
}
private void executeCommand(Command c, Long refreshKey)
@ -648,7 +662,7 @@ public class ListHabitsFragment extends Fragment
LinearLayout.LayoutParams params =
new LinearLayout.LayoutParams(tvNameWidth, LayoutParams.WRAP_CONTENT, 1);
view.findViewById(R.id.tvName).setLayoutParams(params);
view.findViewById(R.id.label).setLayoutParams(params);
inflateCheckmarkButtons(view);
@ -656,7 +670,7 @@ public class ListHabitsFragment extends Fragment
}
TextView tvStar = ((TextView) view.findViewById(R.id.tvStar));
TextView tvName = (TextView) view.findViewById(R.id.tvName);
TextView tvName = (TextView) view.findViewById(R.id.label);
LinearLayout llInner = (LinearLayout) view.findViewById(R.id.llInner);
LinearLayout llButtons = (LinearLayout) view.findViewById(R.id.llButtons);
@ -673,7 +687,7 @@ public class ListHabitsFragment extends Fragment
if (android.os.Build.VERSION.SDK_INT >= 21)
llInner.setBackgroundResource(R.drawable.ripple_white);
else
llInner.setBackgroundColor(Color.WHITE);
llInner.setBackgroundResource(R.drawable.card_background);
}
return view;
@ -707,6 +721,7 @@ public class ListHabitsFragment extends Fragment
TextView tvCheck = (TextView) llButtons.getChildAt(i);
tvCheck.setTag(R.string.habit_key, habitId);
tvCheck.setTag(R.string.offset_key, i);
if(isChecked.length > i)
updateCheckmark(activeColor, tvCheck, isChecked[i]);
}
}
@ -727,12 +742,12 @@ public class ListHabitsFragment extends Fragment
{
int score = loader.scores.get(habit.getId());
if (score < Habit.HALF_STAR_CUTOFF)
if (score < Score.HALF_STAR_CUTOFF)
{
tvStar.setText(getString(R.string.fa_star_o));
tvStar.setTextColor(INACTIVE_COLOR);
}
else if (score < Habit.FULL_STAR_CUTOFF)
else if (score < Score.FULL_STAR_CUTOFF)
{
tvStar.setText(getString(R.string.fa_star_half_o));
tvStar.setTextColor(INACTIVE_COLOR);
@ -782,4 +797,43 @@ public class ListHabitsFragment extends Fragment
if (refreshKey == null) loader.updateAllHabits(true);
else loader.updateHabit(refreshKey);
}
private void onExportHabitsClick(final LinkedList<Habit> selectedHabits)
{
new AsyncTask<Void, Void, Void>()
{
String filename;
@Override
protected void onPreExecute()
{
progressBar.setIndeterminate(true);
progressBar.setVisibility(View.VISIBLE);
}
@Override
protected void onPostExecute(Void aVoid)
{
if(filename != null)
{
Intent intent = new Intent();
intent.setAction(Intent.ACTION_SEND);
intent.setType("application/zip");
intent.putExtra(Intent.EXTRA_STREAM, Uri.fromFile(new File(filename)));
startActivity(intent);
}
progressBar.setVisibility(View.GONE);
}
@Override
protected Void doInBackground(Void... params)
{
CSVExporter exporter = new CSVExporter(activity, selectedHabits);
filename = exporter.writeArchive();
return null;
}
}.execute();
}
}

@ -25,16 +25,16 @@ import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
import android.widget.LinearLayout;
import android.widget.TextView;
import org.isoron.helpers.ColorHelper;
import org.isoron.helpers.Command;
import org.isoron.helpers.DialogHelper;
import org.isoron.uhabits.R;
import org.isoron.uhabits.helpers.ReminderHelper;
import org.isoron.uhabits.ShowHabitActivity;
import org.isoron.uhabits.helpers.ReminderHelper;
import org.isoron.uhabits.models.Habit;
import org.isoron.uhabits.models.Score;
import org.isoron.uhabits.views.HabitHistoryView;
import org.isoron.uhabits.views.HabitScoreView;
import org.isoron.uhabits.views.HabitStreakView;
@ -59,7 +59,7 @@ public class ShowHabitFragment extends Fragment implements DialogHelper.OnSavedL
activity = (ShowHabitActivity) getActivity();
habit = activity.habit;
habit.updateCheckmarks();
habit.checkmarks.rebuild();
if (android.os.Build.VERSION.SDK_INT >= 21)
{
@ -71,29 +71,21 @@ public class ShowHabitFragment extends Fragment implements DialogHelper.OnSavedL
TextView tvOverview = (TextView) view.findViewById(R.id.tvOverview);
TextView tvStrength = (TextView) view.findViewById(R.id.tvStrength);
TextView tvStreaks = (TextView) view.findViewById(R.id.tvStreaks);
RingView scoreRing = (RingView) view.findViewById(R.id.scoreRing);
HabitStreakView streakView = (HabitStreakView) view.findViewById(R.id.streakView);
HabitScoreView scoreView = (HabitScoreView) view.findViewById(R.id.scoreView);
HabitHistoryView historyView = (HabitHistoryView) view.findViewById(R.id.historyView);
tvHistory.setTextColor(habit.color);
tvOverview.setTextColor(habit.color);
tvStrength.setTextColor(habit.color);
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), activity.getString(R.string.habit_strength)));
LinearLayout llStrength = (LinearLayout) view.findViewById(R.id.llStrength);
llStrength.addView(new HabitScoreView(activity, habit,
(int) activity.getResources().getDimension(R.dimen.small_square_size)));
LinearLayout llHistory = (LinearLayout) view.findViewById(R.id.llHistory);
HabitHistoryView hhv = new HabitHistoryView(activity, habit,
(int) activity.getResources().getDimension(R.dimen.small_square_size));
llHistory.addView(hhv);
LinearLayout llStreaks = (LinearLayout) view.findViewById(R.id.llStreaks);
HabitStreakView hsv = new HabitStreakView(activity, habit,
(int) activity.getResources().getDimension(R.dimen.small_square_size));
llStreaks.addView(hsv);
scoreRing.setColor(habit.color);
scoreRing.setPercentage((float) habit.scores.getNewestValue() / Score.MAX_SCORE);
streakView.setHabit(habit);
scoreView.setHabit(habit);
historyView.setHabit(habit);
setHasOptionsMenu(true);
return view;

@ -25,7 +25,7 @@ import android.os.Build;
import android.util.Log;
import org.isoron.helpers.DateHelper;
import org.isoron.uhabits.ReminderAlarmReceiver;
import org.isoron.uhabits.HabitBroadcastReceiver;
import org.isoron.uhabits.models.Habit;
import java.text.DateFormat;
@ -58,10 +58,10 @@ public class ReminderHelper
long timestamp = DateHelper.getStartOfDay(DateHelper.toLocalTime(reminderTime));
Uri uri = Uri.parse(String.format("content://org.isoron.uhabits/habit/%d", habit.getId()));
Uri uri = habit.getUri();
Intent alarmIntent = new Intent(context, ReminderAlarmReceiver.class);
alarmIntent.setAction(ReminderAlarmReceiver.ACTION_REMIND);
Intent alarmIntent = new Intent(context, HabitBroadcastReceiver.class);
alarmIntent.setAction(HabitBroadcastReceiver.ACTION_SHOW_REMINDER);
alarmIntent.setData(uri);
alarmIntent.putExtra("timestamp", timestamp);
alarmIntent.putExtra("reminderTime", reminderTime);

@ -0,0 +1,194 @@
package org.isoron.uhabits.io;
import android.content.Context;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import android.util.Log;
import com.activeandroid.Cache;
import org.isoron.helpers.DateHelper;
import org.isoron.uhabits.models.Habit;
import org.isoron.uhabits.models.Score;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.FileWriter;
import java.io.IOException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.LinkedList;
import java.util.List;
import java.util.Locale;
import java.util.TimeZone;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;
public class CSVExporter
{
private List<Habit> habits;
private Context context;
private java.text.DateFormat dateFormat;
private List<String> generateDirs;
private List<String> generateFilenames;
private String basePath;
public CSVExporter(Context context, List<Habit> habits)
{
this.habits = habits;
this.context = context;
generateDirs = new LinkedList<>();
generateFilenames = new LinkedList<>();
basePath = String.format("%s/export/", context.getFilesDir());
dateFormat = new SimpleDateFormat("yyyy-MM-dd", Locale.US);
dateFormat.setTimeZone(TimeZone.getTimeZone("UTC"));
}
public String formatDate(long timestamp)
{
return dateFormat.format(new Date(timestamp));
}
public String formatScore(int score)
{
return String.format("%.2f", ((float) score) / Score.MAX_SCORE);
}
private void writeScores(String dirPath, Habit habit) throws IOException
{
String path = dirPath + "scores.csv";
FileWriter out = new FileWriter(basePath + path);
generateFilenames.add(path);
String query = "select timestamp, score from score where habit = ? order by timestamp";
String params[] = { habit.getId().toString() };
SQLiteDatabase db = Cache.openDatabase();
Cursor cursor = db.rawQuery(query, params);
if(!cursor.moveToFirst()) return;
do
{
String timestamp = formatDate(cursor.getLong(0));
String score = formatScore(cursor.getInt(1));
out.write(String.format("%s,%s\n", timestamp, score));
} while(cursor.moveToNext());
out.close();
cursor.close();
}
private void writeCheckmarks(String dirPath, Habit habit) throws IOException
{
String path = dirPath + "checkmarks.csv";
FileWriter out = new FileWriter(basePath + path);
generateFilenames.add(path);
String query = "select timestamp, value from checkmarks where habit = ? order by timestamp";
String params[] = { habit.getId().toString() };
SQLiteDatabase db = Cache.openDatabase();
Cursor cursor = db.rawQuery(query, params);
if(!cursor.moveToFirst()) return;
do
{
String timestamp = formatDate(cursor.getLong(0));
Integer value = cursor.getInt(1);
out.write(String.format("%s,%d\n", timestamp, value));
} while(cursor.moveToNext());
out.close();
cursor.close();
}
private void writeFiles(Habit habit) throws IOException
{
String path = String.format("%s/", habit.name);
new File(basePath + path).mkdirs();
generateDirs.add(path);
writeScores(path, habit);
writeCheckmarks(path, habit);
}
private void writeZipFile(String zipFilename) throws IOException
{
FileOutputStream fos = new FileOutputStream(zipFilename);
ZipOutputStream zos = new ZipOutputStream(fos);
for(String filename : generateFilenames)
addFileToZip(zos, filename);
zos.close();
fos.close();
}
private void addFileToZip(ZipOutputStream zos, String filename) throws IOException
{
FileInputStream fis = new FileInputStream(new File(basePath + filename));
ZipEntry ze = new ZipEntry(filename);
zos.putNextEntry(ze);
int length;
byte bytes[] = new byte[1024];
while((length = fis.read(bytes)) >= 0)
zos.write(bytes, 0, length);
zos.closeEntry();
fis.close();
}
private void cleanup()
{
for(String filename : generateFilenames)
new File(basePath + filename).delete();
for(String filename : generateDirs)
new File(basePath + filename).delete();
new File(basePath).delete();
}
public String writeArchive()
{
String date = formatDate(DateHelper.getStartOfToday());
File dir = context.getExternalCacheDir();
if(dir == null)
{
Log.e("CSVExporter", "No suitable directory found.");
return null;
}
String zipFilename = String.format("%s/habits-%s.zip", dir, date);
try
{
for (Habit h : habits)
writeFiles(h);
writeZipFile(zipFilename);
cleanup();
}
catch (IOException e)
{
e.printStackTrace();
return null;
}
return zipFilename;
}
}

@ -144,8 +144,8 @@ public class HabitListLoader
if (isCancelled()) return null;
Long id = h.getId();
newScores.put(id, h.getScore());
newCheckmarks.put(id, h.getCheckmarks(dateFrom, dateTo));
newScores.put(id, h.scores.getNewestValue());
newCheckmarks.put(id, h.checkmarks.getValues(dateFrom, dateTo));
publishProgress(current++, newHabits.size());
}
@ -213,8 +213,8 @@ public class HabitListLoader
Habit h = Habit.get(id);
habits.put(id, h);
scores.put(id, h.getScore());
checkmarks.put(id, h.getCheckmarks(dateFrom, dateTo));
scores.put(id, h.scores.getNewestValue());
checkmarks.put(id, h.checkmarks.getValues(dateFrom, dateTo));
return null;
}

@ -0,0 +1,175 @@
/* 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.models;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import com.activeandroid.ActiveAndroid;
import com.activeandroid.Cache;
import com.activeandroid.query.Delete;
import com.activeandroid.query.Select;
import org.isoron.helpers.DateHelper;
import java.util.List;
public class CheckmarkList
{
private Habit habit;
public CheckmarkList(Habit habit)
{
this.habit = habit;
}
public void deleteNewerThan(long timestamp)
{
new Delete().from(Checkmark.class)
.where("habit = ?", habit.getId())
.and("timestamp >= ?", timestamp)
.execute();
}
public int[] getValues(Long fromTimestamp, Long toTimestamp)
{
rebuild();
if(fromTimestamp > toTimestamp) return new int[0];
String query = "select value, timestamp from Checkmarks where " +
"habit = ? and timestamp >= ? and timestamp <= ?";
SQLiteDatabase db = Cache.openDatabase();
String args[] = { habit.getId().toString(), fromTimestamp.toString(),
toTimestamp.toString() };
Cursor cursor = db.rawQuery(query, args);
long day = DateHelper.millisecondsInOneDay;
int nDays = (int) ((toTimestamp - fromTimestamp) / day) + 1;
int[] checks = new int[nDays];
if (cursor.moveToFirst())
{
do
{
long timestamp = cursor.getLong(1);
int offset = (int) ((timestamp - fromTimestamp) / day);
checks[nDays - offset - 1] = cursor.getInt(0);
} while (cursor.moveToNext());
}
cursor.close();
return checks;
}
public int[] getAllValues()
{
Repetition oldestRep = habit.repetitions.getOldest();
if(oldestRep == null) return new int[0];
Long toTimestamp = DateHelper.getStartOfToday();
Long fromTimestamp = oldestRep.timestamp;
return getValues(fromTimestamp, toTimestamp);
}
public void rebuild()
{
long beginning;
long today = DateHelper.getStartOfToday();
long day = DateHelper.millisecondsInOneDay;
Checkmark newestCheckmark = getNewest();
if (newestCheckmark == null)
{
Repetition oldestRep = habit.repetitions.getOldest();
if (oldestRep == null) return;
beginning = oldestRep.timestamp;
}
else
{
beginning = newestCheckmark.timestamp + day;
}
if (beginning > today) return;
long beginningExtended = beginning - (long) (habit.freqDen) * day;
List<Repetition> reps = habit.repetitions.selectFromTo(beginningExtended, today).execute();
int nDays = (int) ((today - beginning) / day) + 1;
int nDaysExtended = (int) ((today - beginningExtended) / day) + 1;
int checks[] = new int[nDaysExtended];
// explicit checks
for (Repetition rep : reps)
{
int offset = (int) ((rep.timestamp - beginningExtended) / day);
checks[nDaysExtended - offset - 1] = 2;
}
// implicit checks
for (int i = 0; i < nDays; i++)
{
int counter = 0;
for (int j = 0; j < habit.freqDen; j++)
if (checks[i + j] == 2) counter++;
if (counter >= habit.freqNum) checks[i] = Math.max(checks[i], 1);
}
ActiveAndroid.beginTransaction();
try
{
for (int i = 0; i < nDays; i++)
{
Checkmark c = new Checkmark();
c.habit = habit;
c.timestamp = today - i * day;
c.value = checks[i];
c.save();
}
ActiveAndroid.setTransactionSuccessful();
} finally
{
ActiveAndroid.endTransaction();
}
}
public Checkmark getNewest()
{
return new Select().from(Checkmark.class)
.where("habit = ?", habit.getId())
.orderBy("timestamp desc")
.limit(1)
.executeSingle();
}
public int getCurrentValue()
{
rebuild();
Checkmark c = getNewest();
if(c != null) return c.value;
else return 0;
}
}

@ -17,11 +17,9 @@
package org.isoron.uhabits.models;
import android.annotation.SuppressLint;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import android.net.Uri;
import com.activeandroid.ActiveAndroid;
import com.activeandroid.Cache;
import com.activeandroid.Model;
import com.activeandroid.annotation.Column;
import com.activeandroid.annotation.Table;
@ -32,21 +30,12 @@ import com.activeandroid.query.Update;
import com.activeandroid.util.SQLiteUtils;
import org.isoron.helpers.ColorHelper;
import org.isoron.helpers.Command;
import org.isoron.helpers.DateHelper;
import org.isoron.uhabits.R;
import java.util.ArrayList;
import java.util.List;
@Table(name = "Habits")
public class Habit extends Model
{
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;
@ -80,20 +69,35 @@ public class Habit extends Model
@Column(name = "archived")
public Integer archived;
public StreakList streaks;
public ScoreList scores;
public RepetitionList repetitions;
public CheckmarkList checkmarks;
public Habit(Habit model)
{
copyAttributes(model);
initializeLists();
}
public Habit()
{
this.color = ColorHelper.palette[5];
this.position = Habit.getCount();
this.position = Habit.countWithArchived();
this.highlight = 0;
this.archived = 0;
this.freqDen = 7;
this.freqNum = 3;
this.reminderDays = 127;
initializeLists();
}
private void initializeLists()
{
streaks = new StreakList(this);
scores = new ScoreList(this);
repetitions = new RepetitionList(this);
checkmarks = new CheckmarkList(this);
}
public static Habit get(Long id)
@ -123,11 +127,16 @@ public class Habit extends Model
return new Select().from(Habit.class).orderBy("position");
}
public static int getCount()
public static int count()
{
return select().count();
}
public static int countWithArchived()
{
return selectWithArchived().count();
}
public static java.util.List<Habit> getHighlightedHabits()
{
return select().where("highlight = 1")
@ -176,7 +185,8 @@ public class Habit extends Model
}
ActiveAndroid.setTransactionSuccessful();
} finally
}
finally
{
ActiveAndroid.endTransaction();
}
@ -204,22 +214,6 @@ public class Habit extends Model
Habit.updateId(getId(), id);
}
protected From selectReps()
{
return new Select().from(Repetition.class).where("habit = ?", getId()).orderBy("timestamp");
}
protected From selectRepsFromTo(long timeFrom, long timeTo)
{
return selectReps().and("timestamp >= ?", timeFrom).and("timestamp <= ?", timeTo);
}
public boolean hasRep(long timestamp)
{
int count = selectReps().where("timestamp = ?", timestamp).count();
return (count > 0);
}
public void cascadeDelete()
{
Long id = getId();
@ -241,176 +235,9 @@ public class Habit extends Model
}
}
public void deleteReps(long timestamp)
{
new Delete().from(Repetition.class)
.where("habit = ?", getId())
.and("timestamp = ?", timestamp)
.execute();
}
public void deleteCheckmarksNewerThan(long timestamp)
{
new Delete().from(Checkmark.class)
.where("habit = ?", getId())
.and("timestamp >= ?", timestamp)
.execute();
}
public void deleteStreaksNewerThan(long timestamp)
{
new Delete().from(Streak.class)
.where("habit = ?", getId())
.and("end >= ?", timestamp - DateHelper.millisecondsInOneDay)
.execute();
}
public int[] getCheckmarks(Long fromTimestamp, Long toTimestamp)
{
updateCheckmarks();
String query = "select value, timestamp from Checkmarks where " +
"habit = ? and timestamp >= ? and timestamp <= ?";
SQLiteDatabase db = Cache.openDatabase();
String args[] = {getId().toString(), fromTimestamp.toString(), toTimestamp.toString()};
Cursor cursor = db.rawQuery(query, args);
long day = DateHelper.millisecondsInOneDay;
int nDays = (int) ((toTimestamp - fromTimestamp) / day) + 1;
int[] checks = new int[nDays];
if (cursor.moveToFirst())
{
do
{
long timestamp = cursor.getLong(1);
int offset = (int) ((timestamp - fromTimestamp) / day);
checks[nDays - offset - 1] = cursor.getInt(0);
} while (cursor.moveToNext());
}
return checks;
}
public void updateCheckmarks()
{
long beginning;
long today = DateHelper.getStartOfToday();
long day = DateHelper.millisecondsInOneDay;
Checkmark newestCheckmark = getNewestCheckmark();
if (newestCheckmark == null)
{
Repetition oldestRep = getOldestRep();
if (oldestRep == null) return;
beginning = oldestRep.timestamp;
}
else
{
beginning = newestCheckmark.timestamp + day;
}
if (beginning > today) return;
long beginningExtended = beginning - (long) (freqDen) * day;
List<Repetition> reps = selectRepsFromTo(beginningExtended, today).execute();
int nDays = (int) ((today - beginning) / day) + 1;
int nDaysExtended = (int) ((today - beginningExtended) / day) + 1;
int checks[] = new int[nDaysExtended];
// explicit checks
for (Repetition rep : reps)
{
int offset = (int) ((rep.timestamp - beginningExtended) / day);
checks[nDaysExtended - offset - 1] = 2;
}
// implicit checks
for (int i = 0; i < nDays; i++)
{
int counter = 0;
for (int j = 0; j < freqDen; j++)
if (checks[i + j] == 2) counter++;
if (counter >= freqNum) checks[i] = Math.max(checks[i], 1);
}
ActiveAndroid.beginTransaction();
try
{
for (int i = 0; i < nDays; i++)
{
Checkmark c = new Checkmark();
c.habit = this;
c.timestamp = today - i * day;
c.value = checks[i];
c.save();
}
ActiveAndroid.setTransactionSuccessful();
} finally
{
ActiveAndroid.endTransaction();
}
}
public Checkmark getNewestCheckmark()
{
return new Select().from(Checkmark.class)
.where("habit = ?", getId())
.orderBy("timestamp desc")
.limit(1)
.executeSingle();
}
public int getRepsCount(int days)
{
long timeTo = DateHelper.getStartOfToday();
long timeFrom = timeTo - DateHelper.millisecondsInOneDay * days;
return selectRepsFromTo(timeFrom, timeTo).count();
}
public boolean hasImplicitRepToday()
{
long today = DateHelper.getStartOfToday();
int reps[] = getCheckmarks(today - DateHelper.millisecondsInOneDay, today);
return (reps[0] > 0);
}
public Repetition getOldestRep()
{
return (Repetition) selectReps().limit(1).executeSingle();
}
public Repetition getOldestRepNewerThan(long timestamp)
{
return selectReps().where("timestamp > ?", timestamp).limit(1).executeSingle();
}
public void toggleRepetition(long timestamp)
{
if (hasRep(timestamp))
public Uri getUri()
{
deleteReps(timestamp);
}
else
{
Repetition rep = new Repetition();
rep.habit = this;
rep.timestamp = timestamp;
rep.save();
}
deleteScoresNewerThan(timestamp);
deleteCheckmarksNewerThan(timestamp);
deleteStreaksNewerThan(timestamp);
return Uri.parse(String.format("content://org.isoron.uhabits/habit/%d", getId()));
}
public void archive()
@ -429,297 +256,4 @@ public class Habit extends Model
{
return archived != 0;
}
public void toggleRepetitionToday()
{
toggleRepetition(DateHelper.getStartOfToday());
}
public Score getNewestScore()
{
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)
.execute();
}
public Integer getScore()
{
int beginningScore;
long beginningTime;
long today = DateHelper.getStartOfDay(DateHelper.getLocalTime());
long day = DateHelper.millisecondsInOneDay;
double freq = ((double) freqNum) / freqDen;
double multiplier = Math.pow(0.5, 1.0 / (14.0 / freq - 1));
Score newestScore = getNewestScore();
if (newestScore == null)
{
Repetition oldestRep = getOldestRep();
if (oldestRep == null) return 0;
beginningTime = oldestRep.timestamp;
beginningScore = 0;
}
else
{
beginningTime = newestScore.timestamp + day;
beginningScore = newestScore.score;
}
long nDays = (today - beginningTime) / day;
if (nDays < 0) return newestScore.score;
int reps[] = getCheckmarks(beginningTime, today);
ActiveAndroid.beginTransaction();
int lastScore = beginningScore;
try
{
for (int i = 0; i < reps.length; i++)
{
Score s = new Score();
s.habit = this;
s.timestamp = beginningTime + day * i;
s.score = (int) (lastScore * multiplier);
if (reps[reps.length - i - 1] == 2)
{
s.score += 1000000;
s.score = Math.min(s.score, MAX_SCORE);
}
s.save();
lastScore = s.score;
}
ActiveAndroid.setTransactionSuccessful();
} finally
{
ActiveAndroid.endTransaction();
}
return lastScore;
}
public List<Score> getScores(long fromTimestamp, long toTimestamp, int divisor, long offset)
{
return new Select().from(Score.class)
.where("habit = ? and timestamp > ? and " +
"timestamp <= ? and (timestamp - ?) % ? = 0", getId(), fromTimestamp,
toTimestamp, offset, divisor)
.execute();
}
public List<Streak> getStreaks()
{
updateStreaks();
return new Select().from(Streak.class)
.where("habit = ?", getId())
.orderBy("end asc")
.execute();
}
public Streak getNewestStreak()
{
return new Select().from(Streak.class)
.where("habit = ?", getId())
.orderBy("end desc")
.limit(1)
.executeSingle();
}
public void updateStreaks()
{
long beginning;
long today = DateHelper.getStartOfToday();
long day = DateHelper.millisecondsInOneDay;
Streak newestStreak = getNewestStreak();
if (newestStreak == null)
{
Repetition oldestRep = getOldestRep();
if (oldestRep == null) return;
beginning = oldestRep.timestamp;
}
else
{
Repetition oldestRep = getOldestRepNewerThan(newestStreak.end);
if (oldestRep == null) return;
beginning = oldestRep.timestamp;
}
if (beginning > today) return;
int checks[] = getCheckmarks(beginning, today);
ArrayList<Long> list = new ArrayList<>();
long current = beginning;
list.add(current);
for (int i = 1; i < checks.length; i++)
{
current += day;
int j = checks.length - i - 1;
if ((checks[j + 1] == 0 && checks[j] > 0)) list.add(current);
if ((checks[j + 1] > 0 && checks[j] == 0)) list.add(current - day);
}
if (list.size() % 2 == 1) list.add(current);
ActiveAndroid.beginTransaction();
try
{
for (int i = 0; i < list.size(); i += 2)
{
Streak streak = new Streak();
streak.habit = this;
streak.start = list.get(i);
streak.end = list.get(i + 1);
streak.length = (streak.end - streak.start) / day + 1;
streak.save();
}
ActiveAndroid.setTransactionSuccessful();
} finally
{
ActiveAndroid.endTransaction();
}
}
public static class CreateCommand extends Command
{
private Habit model;
private Long savedId;
public CreateCommand(Habit model)
{
this.model = model;
}
@Override
public void execute()
{
Habit savedHabit = new Habit(model);
if (savedId == null)
{
savedHabit.save();
savedId = savedHabit.getId();
}
else
{
savedHabit.save(savedId);
}
}
@Override
public void undo()
{
Habit.get(savedId).delete();
}
@Override
public Integer getExecuteStringId()
{
return R.string.toast_habit_created;
}
@Override
public Integer getUndoStringId()
{
return R.string.toast_habit_deleted;
}
}
public class EditCommand extends Command
{
private Habit original;
private Habit modified;
private long savedId;
private boolean hasIntervalChanged;
public EditCommand(Habit modified)
{
this.savedId = getId();
this.modified = new Habit(modified);
this.original = new Habit(Habit.this);
hasIntervalChanged = (this.original.freqDen != this.modified.freqDen ||
this.original.freqNum != this.modified.freqNum);
}
public void execute()
{
Habit habit = Habit.get(savedId);
habit.copyAttributes(modified);
habit.save();
if (hasIntervalChanged)
{
habit.deleteCheckmarksNewerThan(0);
habit.deleteStreaksNewerThan(0);
habit.deleteScoresNewerThan(0);
}
}
public void undo()
{
Habit habit = Habit.get(savedId);
habit.copyAttributes(original);
habit.save();
if (hasIntervalChanged)
{
habit.deleteCheckmarksNewerThan(0);
habit.deleteStreaksNewerThan(0);
habit.deleteScoresNewerThan(0);
}
}
public Integer getExecuteStringId()
{
return R.string.toast_habit_changed;
}
public Integer getUndoStringId()
{
return R.string.toast_habit_changed_back;
}
}
public class ToggleRepetitionCommand extends Command
{
private Long offset;
public ToggleRepetitionCommand(long offset)
{
this.offset = offset;
}
@Override
public void execute()
{
toggleRepetition(offset);
}
@Override
public void undo()
{
execute();
}
}
}

@ -0,0 +1,96 @@
/* 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.models;
import com.activeandroid.query.Delete;
import com.activeandroid.query.From;
import com.activeandroid.query.Select;
import org.isoron.helpers.DateHelper;
public class RepetitionList
{
private Habit habit;
public RepetitionList(Habit habit)
{
this.habit = habit;
}
protected From select()
{
return new Select().from(Repetition.class)
.where("habit = ?", habit.getId())
.orderBy("timestamp");
}
protected From selectFromTo(long timeFrom, long timeTo)
{
return select().and("timestamp >= ?", timeFrom).and("timestamp <= ?", timeTo);
}
public boolean contains(long timestamp)
{
int count = select().where("timestamp = ?", timestamp).count();
return (count > 0);
}
public void delete(long timestamp)
{
new Delete().from(Repetition.class)
.where("habit = ?", habit.getId())
.and("timestamp = ?", timestamp)
.execute();
}
public Repetition getOldestNewerThan(long timestamp)
{
return select().where("timestamp > ?", timestamp).limit(1).executeSingle();
}
public void toggle(long timestamp)
{
if (contains(timestamp))
{
delete(timestamp);
}
else
{
Repetition rep = new Repetition();
rep.habit = habit;
rep.timestamp = timestamp;
rep.save();
}
habit.scores.deleteNewerThan(timestamp);
habit.checkmarks.deleteNewerThan(timestamp);
habit.streaks.deleteNewerThan(timestamp);
}
public Repetition getOldest()
{
return (Repetition) select().limit(1).executeSingle();
}
public boolean hasImplicitRepToday()
{
long today = DateHelper.getStartOfToday();
int reps[] = habit.checkmarks.getValues(today - DateHelper.millisecondsInOneDay, today);
return (reps[0] > 0);
}
}

@ -23,6 +23,10 @@ import com.activeandroid.annotation.Table;
@Table(name = "Score")
public class Score extends Model
{
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 = "habit")
public Habit habit;

@ -0,0 +1,163 @@
/* 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.models;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import com.activeandroid.ActiveAndroid;
import com.activeandroid.Cache;
import com.activeandroid.query.Delete;
import com.activeandroid.query.Select;
import org.isoron.helpers.DateHelper;
public class ScoreList
{
private Habit habit;
public ScoreList(Habit habit)
{
this.habit = habit;
}
public int getCurrentStarStatus()
{
int score = getNewestValue();
if(score >= Score.FULL_STAR_CUTOFF) return 2;
else if(score >= Score.HALF_STAR_CUTOFF) return 1;
else return 0;
}
public Score getNewest()
{
return new Select().from(Score.class)
.where("habit = ?", habit.getId())
.orderBy("timestamp desc")
.limit(1)
.executeSingle();
}
public void deleteNewerThan(long timestamp)
{
new Delete().from(Score.class)
.where("habit = ?", habit.getId())
.and("timestamp >= ?", timestamp)
.execute();
}
public Integer getNewestValue()
{
int beginningScore;
long beginningTime;
long today = DateHelper.getStartOfDay(DateHelper.getLocalTime());
long day = DateHelper.millisecondsInOneDay;
double freq = ((double) habit.freqNum) / habit.freqDen;
double multiplier = Math.pow(0.5, 1.0 / (14.0 / freq - 1));
Score newestScore = getNewest();
if (newestScore == null)
{
Repetition oldestRep = habit.repetitions.getOldest();
if (oldestRep == null) return 0;
beginningTime = oldestRep.timestamp;
beginningScore = 0;
}
else
{
beginningTime = newestScore.timestamp + day;
beginningScore = newestScore.score;
}
long nDays = (today - beginningTime) / day;
if (nDays < 0) return newestScore.score;
int reps[] = habit.checkmarks.getValues(beginningTime, today);
ActiveAndroid.beginTransaction();
int lastScore = beginningScore;
try
{
for (int i = 0; i < reps.length; i++)
{
Score s = new Score();
s.habit = habit;
s.timestamp = beginningTime + day * i;
s.score = (int) (lastScore * multiplier);
if (reps[reps.length - i - 1] == 2)
{
s.score += 1000000;
s.score = Math.min(s.score, Score.MAX_SCORE);
}
s.save();
lastScore = s.score;
}
ActiveAndroid.setTransactionSuccessful();
} finally
{
ActiveAndroid.endTransaction();
}
return lastScore;
}
public int[] getAllValues(Long fromTimestamp, Long toTimestamp, Integer divisor)
{
Long offset = toTimestamp - (divisor - 1) * DateHelper.millisecondsInOneDay;
String query = "select ((timestamp - ?) / ?) as time, avg(score) from Score " +
"where habit = ? and timestamp > ? and timestamp <= ? " +
"group by time order by time desc";
String params[] = { offset.toString(), divisor.toString(), habit.getId().toString(),
fromTimestamp.toString(), toTimestamp.toString()};
SQLiteDatabase db = Cache.openDatabase();
Cursor cursor = db.rawQuery(query, params);
if(!cursor.moveToFirst()) return new int[0];
int k = 0;
int[] scores = new int[cursor.getCount()];
do
{
scores[k++] = (int) cursor.getLong(1);
}
while (cursor.moveToNext());
cursor.close();
return scores;
}
public int[] getAllValues(int divisor)
{
Repetition oldestRep = habit.repetitions.getOldest();
if(oldestRep == null) return new int[0];
long fromTimestamp = oldestRep.timestamp;
long toTimestamp = DateHelper.getStartOfToday();
return getAllValues(fromTimestamp, toTimestamp, divisor);
}
}

@ -0,0 +1,126 @@
/* 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.models;
import com.activeandroid.ActiveAndroid;
import com.activeandroid.query.Delete;
import com.activeandroid.query.Select;
import org.isoron.helpers.DateHelper;
import java.util.ArrayList;
import java.util.List;
public class StreakList
{
private Habit habit;
public StreakList(Habit habit)
{
this.habit = habit;
}
public List<Streak> getAll()
{
rebuild();
return new Select().from(Streak.class)
.where("habit = ?", habit.getId())
.orderBy("end asc")
.execute();
}
public Streak getNewest()
{
return new Select().from(Streak.class)
.where("habit = ?", habit.getId())
.orderBy("end desc")
.limit(1)
.executeSingle();
}
public void rebuild()
{
long beginning;
long today = DateHelper.getStartOfToday();
long day = DateHelper.millisecondsInOneDay;
Streak newestStreak = getNewest();
if (newestStreak != null)
{
beginning = newestStreak.start;
}
else
{
Repetition oldestRep = habit.repetitions.getOldest();
if (oldestRep == null) return;
beginning = oldestRep.timestamp;
}
if (beginning > today) return;
int checks[] = habit.checkmarks.getValues(beginning, today);
ArrayList<Long> list = new ArrayList<>();
long current = beginning;
list.add(current);
for (int i = 1; i < checks.length; i++)
{
current += day;
int j = checks.length - i - 1;
if ((checks[j + 1] == 0 && checks[j] > 0)) list.add(current);
if ((checks[j + 1] > 0 && checks[j] == 0)) list.add(current - day);
}
if (list.size() % 2 == 1) list.add(current);
ActiveAndroid.beginTransaction();
if(newestStreak != null) newestStreak.delete();
try
{
for (int i = 0; i < list.size(); i += 2)
{
Streak streak = new Streak();
streak.habit = habit;
streak.start = list.get(i);
streak.end = list.get(i + 1);
streak.length = (streak.end - streak.start) / day + 1;
streak.save();
}
ActiveAndroid.setTransactionSuccessful();
}
finally
{
ActiveAndroid.endTransaction();
}
}
public void deleteNewerThan(long timestamp)
{
new Delete().from(Streak.class)
.where("habit = ?", habit.getId())
.and("end >= ?", timestamp - DateHelper.millisecondsInOneDay)
.execute();
}
}

@ -0,0 +1,203 @@
/* 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.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Rect;
import android.graphics.Typeface;
import android.os.Build;
import android.text.Layout;
import android.text.StaticLayout;
import android.text.TextPaint;
import android.util.AttributeSet;
import android.view.View;
import org.isoron.helpers.ColorHelper;
import org.isoron.uhabits.R;
import org.isoron.uhabits.models.Habit;
public class CheckmarkView extends View
{
private Paint pCard;
private Paint pIcon;
private int primaryColor;
private int backgroundColor;
private int timesColor;
private int darkGrey;
private int width;
private int height;
private int leftMargin;
private int topMargin;
private int padding;
private String label;
private String fa_check;
private String fa_times;
private String fa_full_star;
private String fa_half_star;
private String fa_empty_star;
private int check_status;
private int star_status;
private Rect rect;
private TextPaint textPaint;
private StaticLayout labelLayout;
public CheckmarkView(Context context)
{
super(context);
init(context);
}
public CheckmarkView(Context context, AttributeSet attrs)
{
super(context, attrs);
init(context);
}
private void init(Context context)
{
Typeface fontawesome =
Typeface.createFromAsset(context.getAssets(), "fontawesome-webfont.ttf");
pCard = new Paint();
pCard.setAntiAlias(true);
pIcon = new Paint();
pIcon.setAntiAlias(true);
pIcon.setTypeface(fontawesome);
pIcon.setTextAlign(Paint.Align.CENTER);
textPaint = new TextPaint();
textPaint.setColor(Color.WHITE);
textPaint.setAntiAlias(true);
fa_check = context.getString(R.string.fa_check);
fa_times = context.getString(R.string.fa_times);
fa_empty_star = context.getString(R.string.fa_star_o);
fa_half_star = context.getString(R.string.fa_star_half_o);
fa_full_star = context.getString(R.string.fa_star);
primaryColor = ColorHelper.palette[10];
backgroundColor = Color.argb(255, 255, 255, 255);
timesColor = Color.argb(128, 255, 255, 255);
darkGrey = Color.argb(64, 0, 0, 0);
rect = new Rect();
check_status = 2;
star_status = 0;
label = "Wake up early";
}
public void setHabit(Habit habit)
{
this.check_status = habit.checkmarks.getCurrentValue();
this.star_status = habit.scores.getCurrentStarStatus();
this.primaryColor = Color.argb(230, Color.red(habit.color), Color.green(habit.color), Color.blue(habit.color));
this.label = habit.name;
updateLabel();
}
@Override
protected void onDraw(Canvas canvas)
{
super.onDraw(canvas);
drawBackground(canvas);
drawCheckmark(canvas);
drawLabel(canvas);
}
private void drawBackground(Canvas canvas)
{
int color = (check_status == 2 ? primaryColor : darkGrey);
pCard.setColor(color);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP)
canvas.drawRoundRect(leftMargin, topMargin, width - leftMargin, height - topMargin, padding,
padding, pCard);
else
canvas.drawRect(leftMargin, topMargin, width - leftMargin, height - topMargin, pCard);
}
private void drawCheckmark(Canvas canvas)
{
String text = (check_status == 0 ? fa_times : fa_check);
int color = (check_status == 2 ? Color.WHITE : timesColor);
pIcon.setColor(color);
pIcon.setTextSize(width * 0.5f);
pIcon.getTextBounds(text, 0, 1, rect);
// canvas.drawLine(0, 0.67f * height, width, 0.67f * height, pIcon);
int y = (int) ((0.67f * height - rect.bottom - rect.top) / 2);
canvas.drawText(text, width / 2, y, pIcon);
}
private void drawLabel(Canvas canvas)
{
canvas.save();
float y;
int nLines = labelLayout.getLineCount();
if(nLines == 1)
y = height * 0.8f - padding;
else
y = height * 0.7f - padding;
canvas.translate(leftMargin + padding, y);
labelLayout.draw(canvas);
canvas.restore();
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec)
{
int width = MeasureSpec.getSize(widthMeasureSpec);
setMeasuredDimension(width, (int) (width * 1.25));
}
@Override
protected void onSizeChanged(int width, int height, int oldWidth, int oldHeight)
{
this.width = getMeasuredWidth();
this.height = getMeasuredHeight();
leftMargin = (int) (width * 0.015);
topMargin = (int) (height * 0.015);
padding = 8 * leftMargin;
textPaint.setTextSize(0.15f * width);
updateLabel();
}
private void updateLabel()
{
textPaint.setColor(Color.WHITE);
labelLayout = new StaticLayout(label, textPaint, width - 2 * leftMargin - 2 * padding,
Layout.Alignment.ALIGN_CENTER, 1.0f, 0.0f, false);
}
}

@ -22,6 +22,7 @@ import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Paint.Align;
import android.graphics.Rect;
import android.util.AttributeSet;
import org.isoron.helpers.ColorHelper;
import org.isoron.helpers.DateHelper;
@ -30,10 +31,11 @@ import org.isoron.uhabits.models.Habit;
import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.GregorianCalendar;
import java.util.Locale;
import java.util.Random;
public class HabitHistoryView extends ScrollableDataView
{
private Habit habit;
private int[] checkmarks;
private Paint pSquareBg, pSquareFg, pTextHeader;
@ -42,6 +44,11 @@ public class HabitHistoryView extends ScrollableDataView
private float squareTextOffset;
private float headerTextOffset;
private int columnWidth;
private int columnHeight;
private int nColumns;
private int baseSize;
private String wdays[];
private SimpleDateFormat dfMonth;
private SimpleDateFormat dfYear;
@ -50,25 +57,43 @@ public class HabitHistoryView extends ScrollableDataView
private int nDays;
private int todayWeekday;
private int colors[];
private Rect baseLocation;
private int primaryColor;
private boolean isBackgroundTransparent;
private int textColor;
public HabitHistoryView(Context context, AttributeSet attrs)
{
super(context, attrs);
this.primaryColor = ColorHelper.palette[7];
init();
}
public HabitHistoryView(Context context, Habit habit, int baseSize)
public void setHabit(Habit habit)
{
super(context);
this.habit = habit;
createColors();
fetchData();
postInvalidate();
}
setDimensions(baseSize);
private void init()
{
createPaints();
createColors();
wdays = DateHelper.getShortDayNames();
dfMonth = new SimpleDateFormat("MMM");
dfYear = new SimpleDateFormat("yyyy");
dfMonth = new SimpleDateFormat("MMM", Locale.getDefault());
dfYear = new SimpleDateFormat("yyyy", Locale.getDefault());
baseLocation = new Rect();
}
private void updateDate()
{
baseDate = new GregorianCalendar();
baseDate.add(Calendar.DAY_OF_YEAR, -(dataOffset - 1) * 7);
baseDate.add(Calendar.DAY_OF_YEAR, -(getDataOffset() - 1) * 7);
nDays = (nColumns - 1) * 7;
todayWeekday = new GregorianCalendar().get(Calendar.DAY_OF_WEEK) % 7;
@ -78,71 +103,114 @@ public class HabitHistoryView extends ScrollableDataView
}
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh)
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec)
{
int width = MeasureSpec.getSize(widthMeasureSpec);
int height = MeasureSpec.getSize(heightMeasureSpec);
setMeasuredDimension(width, height);
}
@Override
protected void onSizeChanged(int width, int height, int oldWidth, int oldHeight)
{
super.onSizeChanged(w, h, oldw, oldh);
if(height < 8) height = 200;
baseSize = height / 8;
setScrollerBucketSize(baseSize);
columnWidth = baseSize;
columnHeight = 8 * baseSize;
nColumns = width / baseSize;
squareSpacing = (int) Math.floor(baseSize / 15.0);
pSquareFg.setTextSize(baseSize * 0.5f);
pTextHeader.setTextSize(baseSize * 0.5f);
squareTextOffset = pSquareFg.getFontSpacing() * 0.4f;
headerTextOffset = pTextHeader.getFontSpacing() * 0.3f;
updateDate();
}
private void createColors()
{
int primaryColor = habit.color;
int primaryColorBright = ColorHelper.mixColors(primaryColor, Color.WHITE, 0.5f);
int grey = Color.rgb(230, 230, 230);
if(habit != null)
this.primaryColor = habit.color;
if(isBackgroundTransparent)
primaryColor = ColorHelper.setMinValue(primaryColor, 0.75f);
int red = Color.red(primaryColor);
int green = Color.green(primaryColor);
int blue = Color.blue(primaryColor);
if(isBackgroundTransparent)
{
colors = new int[3];
colors[0] = grey;
colors[1] = primaryColorBright;
colors[0] = Color.argb(16, 255, 255, 255);
colors[1] = Color.argb(128, red, green, blue);
colors[2] = primaryColor;
textColor = Color.rgb(255, 255, 255);
}
private void setDimensions(int baseSize)
else
{
columnWidth = baseSize;
columnHeight = 8 * baseSize;
squareSpacing = 2;
colors = new int[3];
colors[0] = Color.argb(25, 0, 0, 0);
colors[1] = Color.argb(127, red, green, blue);
colors[2] = primaryColor;
textColor = Color.argb(64, 0, 0, 0);
}
}
private void createPaints()
protected void createPaints()
{
pTextHeader = new Paint();
pTextHeader.setColor(Color.LTGRAY);
pTextHeader.setTextAlign(Align.LEFT);
pTextHeader.setTextSize(columnWidth * 0.5f);
pTextHeader.setAntiAlias(true);
pSquareBg = new Paint();
pSquareBg.setColor(habit.color);
pSquareBg.setColor(primaryColor);
pSquareFg = new Paint();
pSquareFg.setColor(Color.WHITE);
pSquareFg.setAntiAlias(true);
pSquareFg.setTextSize(columnWidth * 0.5f);
pSquareFg.setTextAlign(Align.CENTER);
squareTextOffset = pSquareFg.getFontSpacing() * 0.4f;
headerTextOffset = pTextHeader.getFontSpacing() * 0.3f;
}
protected void fetchData()
{
Calendar currentDate = new GregorianCalendar();
currentDate.add(Calendar.DAY_OF_YEAR, -dataOffset * 7);
int dayOfWeek = currentDate.get(Calendar.DAY_OF_WEEK) % 7;
if(isInEditMode())
generateRandomData();
else
{
if(habit == null)
{
checkmarks = new int[0];
return;
}
long dateTo = DateHelper.getStartOfToday();
for (int i = 0; i < 7 - dayOfWeek; i++)
dateTo += DateHelper.millisecondsInOneDay;
checkmarks = habit.checkmarks.getAllValues();
}
updateDate();
}
for (int i = 0; i < dataOffset * 7; i++)
dateTo -= DateHelper.millisecondsInOneDay;
private void generateRandomData()
{
Random random = new Random();
checkmarks = new int[100];
long dateFrom = dateTo;
for (int i = 0; i < (nColumns - 1) * 7; i++)
dateFrom -= DateHelper.millisecondsInOneDay;
for(int i = 0; i < 100; i++)
if(random.nextFloat() < 0.3) checkmarks[i] = 2;
checkmarks = habit.getCheckmarks(dateFrom, dateTo);
updateDate();
for(int i = 0; i < 100 - 7; i++)
{
int count = 0;
for (int j = 0; j < 7; j++)
if(checkmarks[i + j] != 0)
count++;
if(count >= 3) checkmarks[i] = Math.max(checkmarks[i], 1);
}
}
private String previousMonth;
@ -154,21 +222,24 @@ public class HabitHistoryView extends ScrollableDataView
{
super.onDraw(canvas);
Rect location = new Rect(0, 0, columnWidth - squareSpacing, columnWidth - squareSpacing);
baseLocation.set(0, 0, columnWidth - squareSpacing, columnWidth - squareSpacing);
previousMonth = "";
previousYear = "";
justPrintedYear = false;
pTextHeader.setColor(textColor);
updateDate();
GregorianCalendar currentDate = (GregorianCalendar) baseDate.clone();
for (int column = 0; column < nColumns - 1; column++)
{
drawColumn(canvas, location, currentDate, column);
location.offset(columnWidth, -columnHeight);
drawColumn(canvas, baseLocation, currentDate, column);
baseLocation.offset(columnWidth, - columnHeight);
}
drawAxis(canvas, location);
drawAxis(canvas, baseLocation);
}
private void drawColumn(Canvas canvas, Rect location, GregorianCalendar date, int column)
@ -178,9 +249,9 @@ public class HabitHistoryView extends ScrollableDataView
for (int j = 0; j < 7; j++)
{
if (!(column == nColumns - 2 && dataOffset == 0 && j > todayWeekday))
if (!(column == nColumns - 2 && getDataOffset() == 0 && j > todayWeekday))
{
int checkmarkOffset = nDays - 7 * column - j;
int checkmarkOffset = getDataOffset() * 7 + nDays - 7 * (column + 1) + todayWeekday - j;
drawSquare(canvas, location, date, checkmarkOffset);
}
@ -210,6 +281,8 @@ public class HabitHistoryView extends ScrollableDataView
}
}
private boolean justSkippedColumn = false;
private void drawColumnHeader(Canvas canvas, Rect location, GregorianCalendar date)
{
String month = dfMonth.format(date.getTime());
@ -218,22 +291,39 @@ public class HabitHistoryView extends ScrollableDataView
if (!month.equals(previousMonth))
{
int offset = 0;
if (justPrintedYear) offset += columnWidth;
if (justPrintedYear)
{
offset += columnWidth;
justSkippedColumn = true;
}
canvas.drawText(month, location.left + offset, location.bottom - headerTextOffset,
pTextHeader);
previousMonth = month;
justPrintedYear = false;
}
else if (!year.equals(previousYear))
{
if(!justSkippedColumn)
{
canvas.drawText(year, location.left, location.bottom - headerTextOffset, pTextHeader);
previousYear = year;
justPrintedYear = true;
}
justSkippedColumn = false;
}
else
{
justSkippedColumn = false;
justPrintedYear = false;
}
}
public void setIsBackgroundTransparent(boolean isBackgroundTransparent)
{
this.isBackgroundTransparent = isBackgroundTransparent;
createColors();
}
}

@ -20,7 +20,10 @@ import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.PorterDuff;
import android.graphics.PorterDuffXfermode;
import android.graphics.RectF;
import android.util.AttributeSet;
import org.isoron.helpers.ColorHelper;
import org.isoron.helpers.DateHelper;
@ -28,71 +31,163 @@ import org.isoron.uhabits.models.Habit;
import org.isoron.uhabits.models.Score;
import java.text.SimpleDateFormat;
import java.util.List;
import java.util.Locale;
import java.util.Random;
public class HabitScoreView extends ScrollableDataView
{
public static final int BUCKET_SIZE = 7;
public static final PorterDuffXfermode XFERMODE_CLEAR =
new PorterDuffXfermode(PorterDuff.Mode.CLEAR);
public static final PorterDuffXfermode XFERMODE_SRC =
new PorterDuffXfermode(PorterDuff.Mode.SRC);
private final Paint pGrid;
private final float em;
private Paint pGrid;
private float em;
private Habit habit;
private SimpleDateFormat dfMonth;
private SimpleDateFormat dfDay;
private Paint pText, pGraph;
private RectF rect, prevRect;
private int baseSize;
private int paddingTop;
private int columnWidth;
private int columnHeight;
private int nColumns;
private int textColor;
private int dimmedTextColor;
private int[] colors;
private List<Score> scores;
private int[] scores;
private int primaryColor;
private boolean isBackgroundTransparent;
public HabitScoreView(Context context, Habit habit, int columnWidth)
public HabitScoreView(Context context, AttributeSet attrs)
{
super(context, attrs);
this.primaryColor = ColorHelper.palette[7];
init();
}
public void setHabit(Habit habit)
{
super(context);
this.habit = habit;
createColors();
fetchData();
postInvalidate();
}
private void init()
{
createPaints();
createColors();
dfMonth = new SimpleDateFormat("MMM", Locale.getDefault());
dfDay = new SimpleDateFormat("d", Locale.getDefault());
rect = new RectF();
prevRect = new RectF();
}
private void createColors()
{
if(habit != null)
this.primaryColor = habit.color;
if (isBackgroundTransparent)
{
primaryColor = ColorHelper.setSaturation(primaryColor, 0.75f);
primaryColor = ColorHelper.setValue(primaryColor, 1.0f);
textColor = Color.argb(192, 255, 255, 255);
dimmedTextColor = Color.argb(128, 255, 255, 255);
}
else
{
textColor = Color.argb(64, 0, 0, 0);
dimmedTextColor = Color.argb(16, 0, 0, 0);
}
colors = new int[4];
colors[0] = Color.rgb(230, 230, 230);
colors[3] = primaryColor;
colors[1] = ColorHelper.mixColors(colors[0], colors[3], 0.66f);
colors[2] = ColorHelper.mixColors(colors[0], colors[3], 0.33f);
}
protected void createPaints()
{
pText = new Paint();
pText.setColor(Color.LTGRAY);
pText.setTextAlign(Paint.Align.LEFT);
pText.setTextSize(columnWidth * 0.5f);
pText.setAntiAlias(true);
pGraph = new Paint();
pGraph.setTextAlign(Paint.Align.CENTER);
pGraph.setTextSize(columnWidth * 0.5f);
pGraph.setAntiAlias(true);
pGraph.setStrokeWidth(columnWidth * 0.1f);
pGrid = new Paint();
pGrid.setColor(Color.LTGRAY);
pGrid.setAntiAlias(true);
pGrid.setStrokeWidth(columnWidth * 0.05f);
}
this.columnWidth = columnWidth;
columnHeight = 8 * columnWidth;
headerHeight = columnWidth;
footerHeight = columnWidth;
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec)
{
int width = MeasureSpec.getSize(widthMeasureSpec);
int height = MeasureSpec.getSize(heightMeasureSpec);
setMeasuredDimension(width, height);
}
em = pText.getFontSpacing();
@Override
protected void onSizeChanged(int width, int height, int oldWidth, int oldHeight)
{
if(height < 9) height = 200;
colors = new int[4];
baseSize = height / 9;
setScrollerBucketSize(baseSize);
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);
columnWidth = baseSize;
columnHeight = 8 * baseSize;
nColumns = width / baseSize;
paddingTop = (int) (baseSize * 0.15f);
pText.setTextSize(baseSize * 0.5f);
pGraph.setTextSize(baseSize * 0.5f);
pGraph.setStrokeWidth(baseSize * 0.1f);
pGrid.setStrokeWidth(baseSize * 0.05f);
em = pText.getFontSpacing();
}
protected void fetchData()
{
if(isInEditMode())
generateRandomData();
else
{
if (habit == null)
{
scores = new int[0];
return;
}
long toTimestamp = DateHelper.getStartOfToday();
for (int i = 0; i < dataOffset * BUCKET_SIZE; i++)
toTimestamp -= DateHelper.millisecondsInOneDay;
scores = habit.scores.getAllValues(BUCKET_SIZE * DateHelper.millisecondsInOneDay);
}
}
long fromTimestamp = toTimestamp;
for (int i = 0; i < nColumns * BUCKET_SIZE; i++)
fromTimestamp -= DateHelper.millisecondsInOneDay;
private void generateRandomData()
{
Random random = new Random();
scores = new int[100];
scores[0] = Score.MAX_SCORE / 2;
scores = habit.getScores(fromTimestamp, toTimestamp,
BUCKET_SIZE * DateHelper.millisecondsInOneDay, toTimestamp);
for(int i = 1; i < 100; i++)
{
int step = Score.MAX_SCORE / 10;
scores[i] = scores[i - 1] + random.nextInt(step * 2) - step;
scores[i] = Math.max(0, Math.min(Score.MAX_SCORE, scores[i]));
}
}
@Override
@ -102,52 +197,58 @@ public class HabitScoreView extends ScrollableDataView
float lineHeight = pText.getFontSpacing();
RectF rGrid = new RectF(0, 0, nColumns * columnWidth, columnHeight);
rGrid.offset(0, headerHeight);
drawGrid(canvas, rGrid);
rect.set(0, 0, nColumns * columnWidth, columnHeight);
rect.offset(0, paddingTop);
SimpleDateFormat dfMonth = new SimpleDateFormat("MMM");
SimpleDateFormat dfDay = new SimpleDateFormat("d");
drawGrid(canvas, rect);
String previousMonth = "";
pGraph.setColor(habit.color);
RectF prevR = null;
pText.setTextAlign(Paint.Align.CENTER);
pText.setColor(textColor);
pGraph.setColor(primaryColor);
prevRect.setEmpty();
long currentDate = DateHelper.getStartOfToday();
for(int k = 0; k < nColumns + getDataOffset() - 1; k++)
currentDate -= 7 * DateHelper.millisecondsInOneDay;
for (int offset = nColumns - scores.size(); offset < nColumns; offset++)
for (int k = 0; k < nColumns; k++)
{
Score score = scores.get(offset - nColumns + scores.size());
String month = dfMonth.format(score.timestamp);
String day = dfDay.format(score.timestamp);
String month = dfMonth.format(currentDate);
String day = dfDay.format(currentDate);
long s = score.score;
double sRelative = ((double) s) / Habit.MAX_SCORE;
int score = 0;
int offset = nColumns - k - 1 + getDataOffset();
if(offset < scores.length) score = scores[offset];
double sRelative = ((double) score) / Score.MAX_SCORE;
int height = (int) (columnHeight * sRelative);
RectF r = new RectF(0, 0, columnWidth, columnWidth);
r.offset(offset * columnWidth,
headerHeight + columnHeight - height - columnWidth / 2);
rect.set(0, 0, baseSize, baseSize);
rect.offset(k * columnWidth, paddingTop + columnHeight - height - columnWidth / 2);
if (prevR != null)
if (!prevRect.isEmpty())
{
drawLine(canvas, prevR, r);
drawMarker(canvas, prevR);
drawLine(canvas, prevRect, rect);
drawMarker(canvas, prevRect);
}
if (offset == nColumns - 1) drawMarker(canvas, r);
if (k == nColumns - 1) drawMarker(canvas, rect);
prevR = r;
prevRect.set(rect);
rect.set(0, 0, columnWidth, columnHeight);
rect.offset(k * columnWidth, paddingTop);
r = new RectF(0, 0, columnWidth, columnHeight);
r.offset(offset * columnWidth, headerHeight);
if (!month.equals(previousMonth))
canvas.drawText(month, r.centerX(), r.bottom + lineHeight * 1.2f, pText);
canvas.drawText(month, rect.centerX(), rect.bottom + lineHeight * 1.2f, pText);
else
canvas.drawText(day, r.centerX(), r.bottom + lineHeight * 1.2f, pText);
canvas.drawText(day, rect.centerX(), rect.bottom + lineHeight * 1.2f, pText);
previousMonth = month;
currentDate += 7 * DateHelper.millisecondsInOneDay;
}
}
@ -156,7 +257,10 @@ public class HabitScoreView extends ScrollableDataView
int nRows = 5;
float rowHeight = rGrid.height() / nRows;
pGrid.setColor(Color.rgb(240, 240, 240));
pText.setTextAlign(Paint.Align.LEFT);
pText.setColor(textColor);
pGrid.setColor(dimmedTextColor);
for (int i = 0; i < nRows; i++)
{
canvas.drawText(String.format("%d%%", (100 - i * 100 / nRows)), rGrid.left + 0.5f * em,
@ -170,7 +274,7 @@ public class HabitScoreView extends ScrollableDataView
private void drawLine(Canvas canvas, RectF rectFrom, RectF rectTo)
{
pGraph.setColor(habit.color);
pGraph.setColor(primaryColor);
canvas.drawLine(rectFrom.centerX(), rectFrom.centerY(), rectTo.centerX(), rectTo.centerY(),
pGraph);
}
@ -178,15 +282,32 @@ public class HabitScoreView extends ScrollableDataView
private void drawMarker(Canvas canvas, RectF rect)
{
rect.inset(columnWidth * 0.15f, columnWidth * 0.15f);
pGraph.setColor(Color.WHITE);
setModeOrColor(pGraph, XFERMODE_CLEAR, Color.WHITE);
canvas.drawOval(rect, pGraph);
rect.inset(columnWidth * 0.1f, columnWidth * 0.1f);
pGraph.setColor(habit.color);
setModeOrColor(pGraph, XFERMODE_SRC, primaryColor);
canvas.drawOval(rect, pGraph);
rect.inset(columnWidth * 0.1f, columnWidth * 0.1f);
pGraph.setColor(Color.WHITE);
setModeOrColor(pGraph, XFERMODE_CLEAR, Color.WHITE);
canvas.drawOval(rect, pGraph);
if(isBackgroundTransparent)
pGraph.setXfermode(XFERMODE_SRC);
}
public void setIsBackgroundTransparent(boolean isBackgroundTransparent)
{
this.isBackgroundTransparent = isBackgroundTransparent;
createColors();
}
private void setModeOrColor(Paint p, PorterDuffXfermode mode, int color)
{
if(isBackgroundTransparent)
p.setXfermode(mode);
else
p.setColor(color);
}
}

@ -21,71 +21,194 @@ import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Rect;
import android.util.AttributeSet;
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;
import java.util.Locale;
import java.util.Random;
public class HabitStreakView extends ScrollableDataView
{
private Habit habit;
private Paint pText, pBar;
private List<Streak> streaks;
private long[] startTimes;
private long[] endTimes;
private long[] lengths;
private int columnWidth;
private int columnHeight;
private int headerHeight;
private int nColumns;
private long maxStreakLength;
private int[] colors;
private SimpleDateFormat dfMonth;
private Rect rect;
private int baseSize;
private int primaryColor;
private boolean isBackgroundTransparent;
private int textColor;
private Paint pBarText;
public HabitStreakView(Context context, AttributeSet attrs)
{
super(context, attrs);
this.primaryColor = ColorHelper.palette[7];
init();
}
public HabitStreakView(Context context, Habit habit, int columnWidth)
public void setHabit(Habit habit)
{
super(context);
this.habit = habit;
setDimensions(columnWidth);
createColors();
fetchData();
postInvalidate();
}
private void init()
{
createPaints();
createColors();
dfMonth = new SimpleDateFormat("MMM", Locale.getDefault());
rect = new Rect();
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec)
{
int width = MeasureSpec.getSize(widthMeasureSpec);
int height = MeasureSpec.getSize(heightMeasureSpec);
setMeasuredDimension(width, height);
}
private void setDimensions(int baseSize)
@Override
protected void onSizeChanged(int width, int height, int oldWidth, int oldHeight)
{
this.columnWidth = baseSize;
baseSize = height / 10;
setScrollerBucketSize(baseSize);
columnWidth = baseSize;
columnHeight = 8 * baseSize;
headerHeight = baseSize;
footerHeight = baseSize;
nColumns = width / baseSize - 1;
pText.setTextSize(baseSize * 0.5f);
pBar.setTextSize(baseSize * 0.5f);
}
private void createColors()
{
if(habit != null)
this.primaryColor = habit.color;
if(isBackgroundTransparent)
{
primaryColor = ColorHelper.setSaturation(primaryColor, 0.75f);
primaryColor = ColorHelper.setValue(primaryColor, 1.0f);
}
int red = Color.red(primaryColor);
int green = Color.green(primaryColor);
int blue = Color.blue(primaryColor);
if(isBackgroundTransparent)
{
colors = new int[4];
colors[3] = primaryColor;
colors[2] = Color.argb(213, red, green, blue);
colors[1] = Color.argb(170, red, green, blue);
colors[0] = Color.argb(128, red, green, blue);
textColor = Color.rgb(255, 255, 255);
pBarText = pText;
}
else
{
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);
colors[3] = primaryColor;
colors[2] = Color.argb(192, red, green, blue);
colors[1] = Color.argb(96, red, green, blue);
colors[0] = Color.argb(32, 0, 0, 0);
textColor = Color.argb(64, 0, 0, 0);
pBarText = pBar;
}
}
private void createPaints()
protected void createPaints()
{
pText = new Paint();
pText.setColor(Color.LTGRAY);
pText.setTextAlign(Paint.Align.CENTER);
pText.setTextSize(columnWidth * 0.5f);
pText.setAntiAlias(true);
pBar = new Paint();
pBar.setTextAlign(Paint.Align.CENTER);
pBar.setTextSize(columnWidth * 0.5f);
pBar.setAntiAlias(true);
}
protected void fetchData()
{
streaks = habit.getStreaks();
if(isInEditMode())
generateRandomData();
else
{
if(habit == null)
{
startTimes = endTimes = lengths = new long[0];
return;
}
List<Streak> streaks = habit.streaks.getAll();
int size = streaks.size();
startTimes = new long[size];
endTimes = new long[size];
lengths = new long[size];
int k = 0;
for (Streak s : streaks)
{
startTimes[k] = s.start;
endTimes[k] = s.end;
lengths[k] = s.length;
k++;
maxStreakLength = Math.max(maxStreakLength, s.length);
}
}
}
private void generateRandomData()
{
int size = 30;
startTimes = new long[size];
endTimes = new long[size];
lengths = new long[size];
Random random = new Random();
Long date = DateHelper.getStartOfToday();
for(int i = 0; i < size; i++)
{
int l = (int) Math.pow(2, random.nextFloat() * 5 + 1);
endTimes[i] = date;
date -= l * DateHelper.millisecondsInOneDay;
lengths[i] = (long) l;
startTimes[i] = date;
maxStreakLength = Math.max(maxStreakLength, l);
}
}
@Override
protected void onDraw(Canvas canvas)
@ -95,32 +218,40 @@ public class HabitStreakView extends ScrollableDataView
float lineHeight = pText.getFontSpacing();
float barHeaderOffset = lineHeight * 0.4f;
int nStreaks = streaks.size();
int start = Math.max(0, nStreaks - nColumns - dataOffset);
SimpleDateFormat dfMonth = new SimpleDateFormat("MMM");
int nStreaks = startTimes.length;
int start = nStreaks - nColumns - getDataOffset();
pText.setColor(textColor);
String previousMonth = "";
for (int offset = 0; offset < nColumns && start + offset < nStreaks; offset++)
{
String month = dfMonth.format(streaks.get(start + offset).start);
if(start + offset < 0) continue;
String month = dfMonth.format(startTimes[start + offset]);
long l = streaks.get(offset + start).length;
long l = lengths[offset + start];
double lRelative = ((double) l) / maxStreakLength;
pBar.setColor(colors[(int) Math.floor(lRelative * 3)]);
int height = (int) (columnHeight * lRelative);
Rect r = new Rect(0, 0, columnWidth - 2, height);
r.offset(offset * columnWidth, headerHeight + columnHeight - height);
rect.set(0, 0, columnWidth - 2, height);
rect.offset(offset * columnWidth, headerHeight + columnHeight - height);
canvas.drawRect(r, pBar);
canvas.drawText(Long.toString(l), r.centerX(), r.top - barHeaderOffset, pBar);
canvas.drawRect(rect, pBar);
canvas.drawText(Long.toString(l), rect.centerX(), rect.top - barHeaderOffset, pBarText);
if (!month.equals(previousMonth))
canvas.drawText(month, r.centerX(), r.bottom + lineHeight * 1.2f, pText);
canvas.drawText(month, rect.centerX(), rect.bottom + lineHeight * 1.2f, pText);
previousMonth = month;
}
}
public void setIsBackgroundTransparent(boolean isBackgroundTransparent)
{
this.isBackgroundTransparent = isBackgroundTransparent;
createColors();
}
}

@ -21,31 +21,57 @@ import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.RectF;
import android.util.AttributeSet;
import android.view.View;
import org.isoron.helpers.ColorHelper;
import org.isoron.helpers.DialogHelper;
import org.isoron.uhabits.R;
public class RingView extends View
{
private int size;
private int color;
private float perc;
private float percentage;
private Paint pRing;
private float lineHeight;
private String label;
private RectF rect;
public RingView(Context context, AttributeSet attrs)
{
super(context, attrs);
this.size = (int) context.getResources().getDimension(R.dimen.small_square_size) * 4;
this.label = DialogHelper.getAttribute(context, attrs, "label");
this.color = ColorHelper.palette[7];
this.percentage = 0.75f;
init();
}
public RingView(Context context, int size, int color, float perc, String label)
public void setColor(int color)
{
super(context);
this.size = size;
this.color = color;
this.perc = perc;
pRing.setColor(color);
postInvalidate();
}
public void setPercentage(float percentage)
{
this.percentage = percentage;
postInvalidate();
}
private void init()
{
pRing = new Paint();
pRing.setColor(color);
pRing.setAntiAlias(true);
pRing.setColor(color);
pRing.setTextAlign(Paint.Align.CENTER);
this.label = label;
pRing.setTextSize(size * 0.2f);
lineHeight = pRing.getFontSpacing();
rect = new RectF();
}
@Override
@ -62,21 +88,20 @@ public class RingView extends View
float thickness = size * 0.15f;
pRing.setColor(color);
RectF r = new RectF(0, 0, size, size);
canvas.drawArc(r, -90, 360 * perc, true, pRing);
rect.set(0, 0, size, size);
canvas.drawArc(rect, -90, 360 * percentage, true, pRing);
pRing.setColor(Color.rgb(230, 230, 230));
canvas.drawArc(r, 360 * perc - 90 + 2, 360 * (1 - perc) - 4, true, pRing);
canvas.drawArc(rect, 360 * percentage - 90 + 2, 360 * (1 - percentage) - 4, true, pRing);
pRing.setColor(Color.WHITE);
r.inset(thickness, thickness);
canvas.drawArc(r, -90, 360, true, pRing);
rect.inset(thickness, thickness);
canvas.drawArc(rect, -90, 360, true, pRing);
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%%", percentage * 100), rect.centerX(),
rect.centerY() + lineHeight / 3, pRing);
pRing.setTextSize(size * 0.15f);
canvas.drawText(label, size / 2, size + lineHeight * 1.2f, pRing);

@ -19,87 +19,129 @@
package org.isoron.uhabits.views;
import android.animation.ValueAnimator;
import android.content.Context;
import android.support.v4.view.MotionEventCompat;
import android.util.AttributeSet;
import android.view.GestureDetector;
import android.view.MotionEvent;
import android.view.View;
import android.widget.Scroller;
public abstract class ScrollableDataView extends View
public abstract class ScrollableDataView extends View implements GestureDetector.OnGestureListener,
ValueAnimator.AnimatorUpdateListener
{
protected int dataOffset;
protected int nColumns;
protected int columnWidth, columnHeight;
protected int headerHeight, footerHeight;
private int dataOffset;
private int scrollerBucketSize;
private float prevX, prevY;
private GestureDetector detector;
private Scroller scroller;
private ValueAnimator scrollAnimator;
public ScrollableDataView(Context context)
{
super(context);
init(context);
}
public ScrollableDataView(Context context, AttributeSet attrs)
{
super(context, attrs);
init(context);
}
private void init(Context context)
{
detector = new GestureDetector(context, this);
scroller = new Scroller(context, null, true);
scrollAnimator = ValueAnimator.ofFloat(0, 1);
scrollAnimator.addUpdateListener(this);
}
protected abstract void fetchData();
protected boolean move(float dx)
@Override
public boolean onTouchEvent(MotionEvent event)
{
int newDataOffset = dataOffset + (int) (dx / columnWidth);
newDataOffset = Math.max(0, newDataOffset);
return detector.onTouchEvent(event);
}
if (newDataOffset != dataOffset)
@Override
public boolean onDown(MotionEvent e)
{
dataOffset = newDataOffset;
fetchData();
invalidate();
return true;
}
else return false;
}
@Override
public boolean onTouchEvent(MotionEvent event)
public void onShowPress(MotionEvent e)
{
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)
@Override
public boolean onSingleTapUp(MotionEvent e)
{
prevX = x;
prevY = y;
return false;
}
if (action == MotionEvent.ACTION_MOVE)
@Override
public boolean onScroll(MotionEvent e1, MotionEvent e2, float dx, float dy)
{
float dx = x - prevX;
float dy = y - prevY;
if(scrollerBucketSize == 0)
return false;
if (Math.abs(dy) > Math.abs(dx)) return false;
if(Math.abs(dx) > Math.abs(dy))
getParent().requestDisallowInterceptTouchEvent(true);
if (move(dx))
{
prevX = x;
prevY = y;
}
}
scroller.startScroll(scroller.getCurrX(), scroller.getCurrY(), (int) -dx, (int) dy, 0);
scroller.computeScrollOffset();
dataOffset = Math.max(0, scroller.getCurrX() / scrollerBucketSize);
postInvalidate();
return true;
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec)
public void onLongPress(MotionEvent e)
{
}
@Override
public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY)
{
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
setMeasuredDimension(getMeasuredWidth(), columnHeight + headerHeight + footerHeight);
scroller.fling(scroller.getCurrX(), scroller.getCurrY(), (int) velocityX / 2, 0, 0, 100000,
0, 0);
invalidate();
scrollAnimator.setDuration(scroller.getDuration());
scrollAnimator.start();
return false;
}
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh)
public void onAnimationUpdate(ValueAnimator animation)
{
if (!scroller.isFinished())
{
scroller.computeScrollOffset();
dataOffset = Math.max(0, scroller.getCurrX() / scrollerBucketSize);
postInvalidate();
}
else
{
scrollAnimator.cancel();
}
}
public int getDataOffset()
{
return dataOffset;
}
public void setScrollerBucketSize(int scrollerBucketSize)
{
super.onSizeChanged(w, h, oldw, oldh);
nColumns = w / columnWidth;
fetchData();
this.scrollerBucketSize = scrollerBucketSize;
}
}

@ -0,0 +1,198 @@
/* 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.widgets;
import android.app.PendingIntent;
import android.appwidget.AppWidgetManager;
import android.appwidget.AppWidgetProvider;
import android.content.Context;
import android.content.SharedPreferences;
import android.graphics.Bitmap;
import android.media.Image;
import android.os.Build;
import android.os.Bundle;
import android.preference.PreferenceManager;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.RemoteViews;
import org.isoron.helpers.DialogHelper;
import org.isoron.uhabits.R;
import org.isoron.uhabits.models.Habit;
import java.io.FileOutputStream;
import java.io.IOException;
public abstract class BaseWidgetProvider extends AppWidgetProvider
{
private int width, height;
protected abstract int getDefaultHeight();
protected abstract int getDefaultWidth();
protected abstract PendingIntent getOnClickPendingIntent(Context context, Habit habit);
protected abstract int getLayoutId();
protected abstract View buildCustomView(Context context, Habit habit);
public static String getHabitIdKey(long widgetId)
{
return String.format("widget-%06d-habit", widgetId);
}
@Override
public void onDeleted(Context context, int[] appWidgetIds)
{
Context appContext = context.getApplicationContext();
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(appContext);
for(Integer id : appWidgetIds)
prefs.edit().remove(getHabitIdKey(id));
}
@Override
public void onAppWidgetOptionsChanged(Context context, AppWidgetManager appWidgetManager,
int appWidgetId, Bundle newOptions)
{
updateWidget(context, appWidgetManager, appWidgetId, newOptions);
}
@Override
public void onUpdate(Context context, AppWidgetManager manager, int[] appWidgetIds)
{
for(int id : appWidgetIds)
{
Bundle options = null;
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.JELLY_BEAN)
options = manager.getAppWidgetOptions(id);
updateWidget(context, manager, id, options);
}
}
private void updateWidget(Context context, AppWidgetManager manager, int widgetId, Bundle options)
{
updateWidgetSize(context, options);
Context appContext = context.getApplicationContext();
RemoteViews remoteViews = new RemoteViews(context.getPackageName(), getLayoutId());
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(appContext);
Long habitId = prefs.getLong(getHabitIdKey(widgetId), -1L);
if(habitId < 0) return;
Habit habit = Habit.get(habitId);
View widgetView = buildCustomView(context, habit);
measureCustomView(context, width, height, widgetView);
widgetView.setDrawingCacheEnabled(true);
widgetView.buildDrawingCache(true);
Bitmap drawingCache = widgetView.getDrawingCache();
remoteViews.setTextViewText(R.id.label, habit.name);
remoteViews.setImageViewBitmap(R.id.imageView, drawingCache);
//savePreview(context, widgetId, drawingCache);
PendingIntent onClickIntent = getOnClickPendingIntent(context, habit);
if(onClickIntent != null) remoteViews.setOnClickPendingIntent(R.id.imageView, onClickIntent);
manager.updateAppWidget(widgetId, remoteViews);
}
private void savePreview(Context context, int widgetId, Bitmap widgetCache)
{
try
{
LayoutInflater inflater = LayoutInflater.from(context);
View view = inflater.inflate(getLayoutId(), null);
ImageView iv = (ImageView) view.findViewById(R.id.imageView);
iv.setImageBitmap(widgetCache);
view.measure(width, height);
view.layout(0, 0, view.getMeasuredWidth(), view.getMeasuredHeight());
view.setDrawingCacheEnabled(true);
view.buildDrawingCache();
Bitmap previewCache = view.getDrawingCache();
String filename = String.format("%s/%d.png", context.getExternalCacheDir(), widgetId);
Log.d("BaseWidgetProvider", String.format("Writing %s", filename));
FileOutputStream out = new FileOutputStream(filename);
if(previewCache != null)
previewCache.compress(Bitmap.CompressFormat.PNG, 100, out);
out.close();
}
catch (IOException e)
{
e.printStackTrace();
}
}
private void updateWidgetSize(Context context, Bundle options)
{
int maxWidth = getDefaultWidth();
int minWidth = getDefaultWidth();
int maxHeight = getDefaultHeight();
int minHeight = getDefaultHeight();
if (options != null && Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN)
{
maxWidth = (int) DialogHelper.dpToPixels(context,
options.getInt(AppWidgetManager.OPTION_APPWIDGET_MAX_WIDTH));
maxHeight = (int) DialogHelper.dpToPixels(context,
options.getInt(AppWidgetManager.OPTION_APPWIDGET_MAX_HEIGHT));
minWidth = (int) DialogHelper.dpToPixels(context,
options.getInt(AppWidgetManager.OPTION_APPWIDGET_MIN_WIDTH));
minHeight = (int) DialogHelper.dpToPixels(context,
options.getInt(AppWidgetManager.OPTION_APPWIDGET_MIN_HEIGHT));
}
width = maxWidth;
height = maxHeight;
}
private void measureCustomView(Context context, int w, int h, View customView)
{
LayoutInflater inflater = LayoutInflater.from(context);
View entireView = inflater.inflate(getLayoutId(), null);
int specWidth = View.MeasureSpec.makeMeasureSpec(w, View.MeasureSpec.EXACTLY);
int specHeight = View.MeasureSpec.makeMeasureSpec(h, View.MeasureSpec.EXACTLY);
entireView.measure(specWidth, specHeight);
entireView.layout(0, 0, entireView.getMeasuredWidth(), entireView.getMeasuredHeight());
View imageView = entireView.findViewById(R.id.imageView);
w = imageView.getMeasuredWidth();
h = imageView.getMeasuredHeight();
specWidth = View.MeasureSpec.makeMeasureSpec(w, View.MeasureSpec.EXACTLY);
specHeight = View.MeasureSpec.makeMeasureSpec(h, View.MeasureSpec.EXACTLY);
customView.measure(specWidth, specHeight);
customView.layout(0, 0, customView.getMeasuredWidth(), customView.getMeasuredHeight());
}
}

@ -0,0 +1,60 @@
/* 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.widgets;
import android.app.PendingIntent;
import android.content.Context;
import android.view.View;
import org.isoron.uhabits.HabitBroadcastReceiver;
import org.isoron.uhabits.R;
import org.isoron.uhabits.models.Habit;
import org.isoron.uhabits.views.CheckmarkView;
public class CheckmarkWidgetProvider extends BaseWidgetProvider
{
@Override
protected View buildCustomView(Context context, Habit habit)
{
CheckmarkView view = new CheckmarkView(context);
view.setHabit(habit);
return view;
}
@Override
protected PendingIntent getOnClickPendingIntent(Context context, Habit habit)
{
return HabitBroadcastReceiver.buildCheckIntent(context, habit, null);
}
@Override
protected int getDefaultHeight()
{
return 200;
}
@Override
protected int getDefaultWidth()
{
return 200;
}
@Override
protected int getLayoutId()
{
return R.layout.widget_checkmark;
}
}

@ -0,0 +1,90 @@
/* 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.widgets;
import android.app.Activity;
import android.appwidget.AppWidgetManager;
import android.content.Intent;
import android.content.SharedPreferences;
import android.os.Bundle;
import android.preference.PreferenceManager;
import android.view.View;
import android.widget.AdapterView;
import android.widget.ArrayAdapter;
import android.widget.ListView;
import org.isoron.uhabits.MainActivity;
import org.isoron.uhabits.R;
import org.isoron.uhabits.models.Habit;
import org.isoron.uhabits.widgets.BaseWidgetProvider;
import java.util.ArrayList;
import java.util.List;
public class HabitPickerDialog extends Activity implements AdapterView.OnItemClickListener
{
private Integer widgetId;
private ArrayList<Long> habitIds;
@Override
protected void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
setContentView(R.layout.widget_configure_activity);
Intent intent = getIntent();
Bundle extras = intent.getExtras();
if (extras != null) widgetId = extras.getInt(AppWidgetManager.EXTRA_APPWIDGET_ID,
AppWidgetManager.INVALID_APPWIDGET_ID);
ListView listView = (ListView) findViewById(R.id.listView);
habitIds = new ArrayList<>();
ArrayList<String> habitNames = new ArrayList<>();
List<Habit> habits = Habit.getAll(false);
for(Habit h : habits)
{
habitIds.add(h.getId());
habitNames.add(h.name);
}
ArrayAdapter<String> adapter =
new ArrayAdapter<>(this, android.R.layout.simple_list_item_1, habitNames);
listView.setAdapter(adapter);
listView.setOnItemClickListener(this);
}
@Override
public void onItemClick(AdapterView<?> parent, View view, int position, long id)
{
Long habitId = habitIds.get(position);
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(
getApplicationContext());
prefs.edit().putLong(BaseWidgetProvider.getHabitIdKey(widgetId), habitId).commit();
MainActivity.updateWidgets(this);
Intent resultValue = new Intent();
resultValue.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, widgetId);
setResult(RESULT_OK, resultValue);
finish();
}
}

@ -0,0 +1,60 @@
/* 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.widgets;
import android.app.PendingIntent;
import android.content.Context;
import android.view.View;
import org.isoron.uhabits.R;
import org.isoron.uhabits.models.Habit;
import org.isoron.uhabits.views.HabitHistoryView;
public class HistoryWidgetProvider extends BaseWidgetProvider
{
@Override
protected View buildCustomView(Context context, Habit habit)
{
HabitHistoryView view = new HabitHistoryView(context, null);
view.setHabit(habit);
view.setIsBackgroundTransparent(true);
return view;
}
@Override
protected PendingIntent getOnClickPendingIntent(Context context, Habit habit)
{
return null;
}
@Override
protected int getDefaultHeight()
{
return 200;
}
@Override
protected int getDefaultWidth()
{
return 200;
}
@Override
protected int getLayoutId()
{
return R.layout.widget_graph;
}
}

@ -0,0 +1,60 @@
/* 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.widgets;
import android.app.PendingIntent;
import android.content.Context;
import android.view.View;
import org.isoron.uhabits.R;
import org.isoron.uhabits.models.Habit;
import org.isoron.uhabits.views.HabitScoreView;
public class ScoreWidgetProvider extends BaseWidgetProvider
{
@Override
protected View buildCustomView(Context context, Habit habit)
{
HabitScoreView view = new HabitScoreView(context, null);
view.setIsBackgroundTransparent(true);
view.setHabit(habit);
return view;
}
@Override
protected PendingIntent getOnClickPendingIntent(Context context, Habit habit)
{
return null;
}
@Override
protected int getDefaultHeight()
{
return 200;
}
@Override
protected int getDefaultWidth()
{
return 200;
}
@Override
protected int getLayoutId()
{
return R.layout.widget_graph;
}
}

@ -0,0 +1,60 @@
/* 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.widgets;
import android.app.PendingIntent;
import android.content.Context;
import android.view.View;
import org.isoron.uhabits.R;
import org.isoron.uhabits.models.Habit;
import org.isoron.uhabits.views.HabitStreakView;
public class StreakWidgetProvider extends BaseWidgetProvider
{
@Override
protected View buildCustomView(Context context, Habit habit)
{
HabitStreakView view = new HabitStreakView(context, null);
view.setIsBackgroundTransparent(true);
view.setHabit(habit);
return view;
}
@Override
protected PendingIntent getOnClickPendingIntent(Context context, Habit habit)
{
return null;
}
@Override
protected int getDefaultHeight()
{
return 200;
}
@Override
protected int getDefaultWidth()
{
return 200;
}
@Override
protected int getLayoutId()
{
return R.layout.widget_graph;
}
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 244 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 822 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 552 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 192 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 554 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 394 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 289 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 716 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 439 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.0 KiB

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

@ -1,5 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<layer-list xmlns:android="http://schemas.android.com/apk/res/android" >
</layer-list>

@ -1,14 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<layer-list xmlns:android="http://schemas.android.com/apk/res/android" >
<item android:bottom="28dp">
<shape android:shape="rectangle" >
<gradient
android:angle="270"
android:endColor="#00000000"
android:height="1px"
android:startColor="#08000000" />
</shape>
</item>
</layer-list>

@ -1,7 +0,0 @@
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:drawable="@drawable/habits_item_check_pressed" android:state_pressed="true"></item>
<item android:drawable="@drawable/habits_item_check_pressed" android:state_focused="true"></item>
<item android:drawable="@drawable/habits_item_check_normal"></item>
</selector>

@ -0,0 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">
<solid android:color="#3f000000" />
<corners android:radius="4dp" />
</shape>

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 33 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.6 KiB

@ -24,6 +24,7 @@
<ImageButton
android:id="@+id/buttonPickColor"
android:src="@drawable/ic_action_color_light"
android:contentDescription="@string/color_picker_default_title"
style="@style/dialogFormInputColor" />
</LinearLayout>
@ -77,7 +78,6 @@
<TextView
android:id="@+id/inputReminderDays"
android:text="Any weekday"
style="@style/dialogFormTimePicker" />
</LinearLayout>

@ -12,7 +12,7 @@
style="@style/habitsListStarStyle" />
<TextView
android:id="@+id/tvName"
android:id="@+id/label"
style="@style/habitsListNameStyle" />
<LinearLayout

@ -1,5 +1,6 @@
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
xmlns:app="http://isoron.org/android"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:background="@color/windowBackground"
@ -10,7 +11,6 @@
tools:context="org.isoron.uhabits.ShowHabitActivity">
<LinearLayout
android:id="@+id/llOverview"
style="@style/cardStyle"
android:gravity="center">
@ -19,18 +19,26 @@
style="@style/cardHeaderStyle"
android:text="@string/overview"/>
<org.isoron.uhabits.views.RingView
android:id="@+id/scoreRing"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:label="@string/habit_strength"/>
</LinearLayout>
<LinearLayout
android:id="@+id/llStrength"
style="@style/cardStyle"
android:gravity="center">
<LinearLayout style="@style/cardStyle">
<TextView
android:id="@+id/tvStrength"
style="@style/cardHeaderStyle"
android:text="@string/habit_strength"/>
<org.isoron.uhabits.views.HabitScoreView
android:id="@+id/scoreView"
android:layout_width="match_parent"
android:layout_height="200dp"/>
</LinearLayout>
<LinearLayout style="@style/cardStyle">
@ -45,6 +53,12 @@
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"/>
<org.isoron.uhabits.views.HabitHistoryView
android:id="@+id/historyView"
android:layout_width="match_parent"
android:layout_height="160dp"/>
</LinearLayout>
<LinearLayout style="@style/cardStyle">
@ -59,6 +73,11 @@
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"/>
<org.isoron.uhabits.views.HabitStreakView
android:id="@+id/streakView"
android:layout_width="match_parent"
android:layout_height="200dp"/>
</LinearLayout>
</LinearLayout>
</ScrollView>

@ -4,12 +4,15 @@
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context="org.isoron.uhabits.ShowHabitActivity"
tools:ignore="MergeRootFrame" >
tools:ignore="MergeRootFrame"
tools:menu="show_habit_activity_menu,show_habit_fragment_menu">
<fragment
android:id="@+id/fragment2"
android:name="org.isoron.uhabits.fragments.ShowHabitFragment"
android:layout_width="match_parent"
android:layout_height="match_parent" />
android:layout_height="match_parent"
tools:layout="@layout/show_habit"
android:layout_gravity="center"/>
</FrameLayout>

@ -0,0 +1,17 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center"
android:orientation="vertical"
android:padding="4dp">
<org.isoron.uhabits.views.CheckmarkView
android:id="@+id/imageView"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:adjustViewBounds="true"
/>
</LinearLayout>

@ -0,0 +1,17 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center"
android:orientation="vertical"
android:padding="0dp">
<ImageView
android:id="@+id/imageView"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:adjustViewBounds="true"
/>
</LinearLayout>

@ -0,0 +1,8 @@
<?xml version="1.0" encoding="utf-8"?>
<ListView android:id="@+id/listView"
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
</ListView>

@ -0,0 +1,31 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@drawable/widget_background"
android:gravity="center"
android:orientation="vertical"
android:paddingTop="4dp"
android:paddingBottom="0dp"
android:paddingLeft="8dp"
android:paddingRight="8dp">
<TextView
android:id="@+id/label"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center"
android:text="Habit"
android:textColor="#ffffff"/>
<ImageView
android:id="@+id/imageView"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1"
android:adjustViewBounds="true"
/>
</LinearLayout>

@ -21,6 +21,11 @@
android:title="@string/unarchive"
android:icon="@drawable/ic_action_unarchive_dark"/>
<item
android:id="@+id/action_export_csv"
android:title="@string/export_to_csv"
android:showAsAction="never" />
<item
android:id="@+id/action_delete"
android:title="@string/delete"

@ -21,7 +21,12 @@
android:title="@string/unarchive"
android:icon="@drawable/ic_action_unarchive_light"/>
<item
android:id="@+id/action_export_csv"
android:title="@string/export_to_csv" />
<item
android:id="@+id/action_delete"
android:title="@string/delete" />
</menu>

@ -1,16 +1,18 @@
<menu xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
tools:context="org.isoron.uhabits.MainActivity">
<item android:id="@+id/action_show_archived"
android:title="@string/show_archived"
<item
android:id="@+id/action_show_archived"
android:checkable="true"
android:enabled="true"
android:checkable="true" />
android:title="@string/show_archived"/>
<item
android:id="@+id/action_settings"
android:orderInCategory="100"
android:showAsAction="never"
android:title="@string/action_settings"/>
android:title="@string/action_settings"
app:showAsAction="never"/>
</menu>

@ -4,6 +4,8 @@
<item
android:id="@+id/action_add"
android:title="@string/add_habit" android:showAsAction="always" android:icon="@drawable/ic_action_add"/>
android:icon="@drawable/ic_action_add"
android:title="@string/add_habit"
android:showAsAction="always"/>
</menu>

@ -1,12 +1,12 @@
<menu xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
tools:context="org.isoron.uhabits.ShowHabitActivity">
<item
android:id="@+id/action_settings"
android:orderInCategory="100"
android:showAsAction="never"
android:title="@string/action_settings"/>
android:title="@string/action_settings"
app:showAsAction="never"/>
</menu>

@ -3,8 +3,8 @@
<item
android:id="@+id/action_edit_habit"
android:showAsAction="ifRoom"
android:icon="@drawable/ic_action_edit"
android:title="@string/edit"/>
android:icon="@drawable/ic_action_edit_light"
android:title="@string/edit"
android:showAsAction="ifRoom"/>
</menu>

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.5 KiB

@ -30,7 +30,6 @@
<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>
@ -58,7 +57,6 @@
<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>
@ -67,7 +65,6 @@
<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>
@ -104,4 +101,5 @@
<string name="any_weekday">Segunda a sexta</string>
<string name="any_day">Qualquer dia</string>
<string name="select_weekdays">Selecionar dias</string>
<string name="export_to_csv">Exportar dados</string>
</resources>

@ -17,7 +17,6 @@
<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>
@ -26,16 +25,11 @@
<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>

@ -18,11 +18,9 @@
<color name="primary">#37474f</color>
<color name="primary_darker">#263238</color>
<color name="accent">#9a4000</color>
<color name="windowBackground">#e6e6e6</color>
<color name="white">#ffffff</color>
<color name="grey">#cccccc</color>
<!-- Time and Date picker -->
<color name="circle_background">#f2f2f2</color>
@ -50,281 +48,278 @@
<color name="light_gray">#404040</color>
<color name="dark_gray">#363636</color>
<color name="line_dark">#808080</color>
<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="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="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="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="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="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="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="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="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_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="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="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="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="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="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="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="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="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="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="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_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_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>
<!--<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>

@ -1,6 +1,4 @@
<resources>
<dimen name="activity_horizontal_margin">16dp</dimen>
<dimen name="activity_vertical_margin">16dp</dimen>
<dimen name="small_square_size">20dp</dimen>
<dimen name="check_square_size">42dp</dimen>
@ -13,7 +11,7 @@
<item>@string/interval_8_hour</item>
</string-array>
<string-array name="snooze_interval_values">
<string-array name="snooze_interval_values" translatable="false">
<item>15</item>
<item>30</item>
<item>60</item>

@ -1,372 +1,373 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<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_star_half">&#xf089;</string>-->
<string translatable="false" name="fa_star_half_o">&#xf123;</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>
<!--<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_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_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_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_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_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>

@ -18,7 +18,6 @@
<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">Habits archived.</string>
<string name="toast_habit_unarchived">Habits unarchived.</string>
@ -49,7 +48,6 @@
<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="repeat">Repeat</string>
<string name="times_every">times in</string>
@ -111,6 +109,7 @@
<string name="any_weekday">Weekdays</string>
<string name="any_day">Any day</string>
<string name="select_weekdays">Select days</string>
<string name="export_to_csv">Export data</string>
<string-array name="hints">
<item>@string/hint_drag</item>

@ -0,0 +1,13 @@
<?xml version="1.0" encoding="utf-8"?>
<appwidget-provider xmlns:android="http://schemas.android.com/apk/res/android"
android:minHeight="40dp"
android:minWidth="40dp"
android:initialLayout="@layout/widget_checkmark"
android:previewImage="@drawable/widget_preview_checkmark"
android:resizeMode="none"
android:updatePeriodMillis="3600000"
android:configure="org.isoron.uhabits.widgets.HabitPickerDialog"
android:widgetCategory="home_screen">
</appwidget-provider>

@ -0,0 +1,14 @@
<?xml version="1.0" encoding="utf-8"?>
<appwidget-provider xmlns:android="http://schemas.android.com/apk/res/android"
android:minHeight="80dp"
android:minWidth="80dp"
android:minResizeWidth="40dp"
android:minResizeHeight="40dp"
android:initialLayout="@layout/widget_graph"
android:previewImage="@drawable/widget_preview_history"
android:resizeMode="vertical|horizontal"
android:updatePeriodMillis="3600000"
android:configure="org.isoron.uhabits.widgets.HabitPickerDialog"
android:widgetCategory="home_screen">
</appwidget-provider>

@ -0,0 +1,14 @@
<?xml version="1.0" encoding="utf-8"?>
<appwidget-provider xmlns:android="http://schemas.android.com/apk/res/android"
android:minHeight="80dp"
android:minWidth="80dp"
android:minResizeWidth="40dp"
android:minResizeHeight="40dp"
android:initialLayout="@layout/widget_graph"
android:previewImage="@drawable/widget_preview_score"
android:resizeMode="vertical|horizontal"
android:updatePeriodMillis="3600000"
android:configure="org.isoron.uhabits.widgets.HabitPickerDialog"
android:widgetCategory="home_screen">
</appwidget-provider>

@ -0,0 +1,14 @@
<?xml version="1.0" encoding="utf-8"?>
<appwidget-provider xmlns:android="http://schemas.android.com/apk/res/android"
android:minHeight="80dp"
android:minWidth="80dp"
android:minResizeWidth="40dp"
android:minResizeHeight="40dp"
android:initialLayout="@layout/widget_graph"
android:previewImage="@drawable/widget_preview_streaks"
android:resizeMode="vertical|horizontal"
android:updatePeriodMillis="3600000"
android:configure="org.isoron.uhabits.widgets.HabitPickerDialog"
android:widgetCategory="home_screen">
</appwidget-provider>

@ -1 +1 @@
Subproject commit e8905e2c78d27bc064d03496abea3a0956e49b18
Subproject commit 318d69cf6b2adc287cf8944bb847dd7139c60376

Binary file not shown.

After

Width:  |  Height:  |  Size: 345 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 85 KiB

Loading…
Cancel
Save