diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index 79203bf14..276fb6be6 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -10,11 +10,11 @@
+ android:theme="@style/AppBaseTheme">
+ android:value="AEdPqrEAAAAI6aeWncbnMNo8E5GWeZ44dlc5cQ7tCROwFhOtiw"/>
+ android:name=".HabitBroadcastReceiver"/>
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/ic_small_widget_preview-web.png b/app/src/main/ic_small_widget_preview-web.png
new file mode 100644
index 000000000..5d6fa2451
Binary files /dev/null and b/app/src/main/ic_small_widget_preview-web.png differ
diff --git a/app/src/main/java/org/isoron/helpers/DialogHelper.java b/app/src/main/java/org/isoron/helpers/DialogHelper.java
index a8215f846..680ca04f2 100644
--- a/app/src/main/java/org/isoron/helpers/DialogHelper.java
+++ b/app/src/main/java/org/isoron/helpers/DialogHelper.java
@@ -18,10 +18,12 @@ package org.isoron.helpers;
import android.content.Context;
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;
@@ -72,4 +74,11 @@ public abstract class DialogHelper
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);
+ }
}
diff --git a/app/src/main/java/org/isoron/uhabits/ReminderAlarmReceiver.java b/app/src/main/java/org/isoron/uhabits/HabitBroadcastReceiver.java
similarity index 80%
rename from app/src/main/java/org/isoron/uhabits/ReminderAlarmReceiver.java
rename to app/src/main/java/org/isoron/uhabits/HabitBroadcastReceiver.java
index 556b586ac..846760e87 100644
--- a/app/src/main/java/org/isoron/uhabits/ReminderAlarmReceiver.java
+++ b/app/src/main/java/org/isoron/uhabits/HabitBroadcastReceiver.java
@@ -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;
@@ -103,6 +103,12 @@ public class ReminderAlarmReceiver extends BroadcastReceiver
habit.toggleRepetition(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()
@@ -146,21 +152,9 @@ 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);
- checkIntent.putExtra("timestamp", timestamp);
- PendingIntent checkIntentPending =
- PendingIntent.getBroadcast(context, 0, checkIntent, PendingIntent.FLAG_ONE_SHOT);
-
- 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);
@@ -173,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,
@@ -193,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());
diff --git a/app/src/main/java/org/isoron/uhabits/MainActivity.java b/app/src/main/java/org/isoron/uhabits/MainActivity.java
index 4b7656154..6fd9dc729 100644
--- a/app/src/main/java/org/isoron/uhabits/MainActivity.java
+++ b/app/src/main/java/org/isoron/uhabits/MainActivity.java
@@ -16,11 +16,17 @@
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.Bundle;
import android.preference.PreferenceManager;
+import android.support.v4.content.LocalBroadcastManager;
import android.view.Menu;
import android.view.MenuItem;
@@ -30,12 +36,21 @@ 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.BaseWidgetProvider;
+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,6 +62,10 @@ 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();
}
@@ -56,6 +75,7 @@ public class MainActivity extends ReplayableActivity
ReminderHelper.createReminderAlarms(MainActivity.this);
DialogHelper.incrementLaunchCount(this);
showTutorial();
+ updateWidgets(this);
}
private void showTutorial()
@@ -108,5 +128,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);
+ }
}
}
diff --git a/app/src/main/java/org/isoron/uhabits/fragments/ListHabitsFragment.java b/app/src/main/java/org/isoron/uhabits/fragments/ListHabitsFragment.java
index 208e07d96..d435c1404 100644
--- a/app/src/main/java/org/isoron/uhabits/fragments/ListHabitsFragment.java
+++ b/app/src/main/java/org/isoron/uhabits/fragments/ListHabitsFragment.java
@@ -660,7 +660,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);
@@ -668,7 +668,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);
diff --git a/app/src/main/java/org/isoron/uhabits/helpers/ReminderHelper.java b/app/src/main/java/org/isoron/uhabits/helpers/ReminderHelper.java
index 94d10e5f2..cbc1bd9c1 100644
--- a/app/src/main/java/org/isoron/uhabits/helpers/ReminderHelper.java
+++ b/app/src/main/java/org/isoron/uhabits/helpers/ReminderHelper.java
@@ -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);
diff --git a/app/src/main/java/org/isoron/uhabits/models/Habit.java b/app/src/main/java/org/isoron/uhabits/models/Habit.java
index 5f44ae936..967e9367d 100644
--- a/app/src/main/java/org/isoron/uhabits/models/Habit.java
+++ b/app/src/main/java/org/isoron/uhabits/models/Habit.java
@@ -19,6 +19,7 @@ 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;
@@ -383,6 +384,24 @@ public class Habit extends Model
.executeSingle();
}
+ public int getCurrentCheckmarkStatus()
+ {
+ updateCheckmarks();
+ Checkmark c = getNewestCheckmark();
+
+ if(c != null) return c.value;
+ else return 0;
+ }
+
+ public int getCurrentStarStatus()
+ {
+ int score = getScore();
+
+ if(score >= FULL_STAR_CUTOFF) return 2;
+ else if(score >= HALF_STAR_CUTOFF) return 1;
+ else return 0;
+ }
+
public int getRepsCount(int days)
{
long timeTo = DateHelper.getStartOfToday();
@@ -426,6 +445,11 @@ public class Habit extends Model
deleteStreaksNewerThan(timestamp);
}
+ public Uri getUri()
+ {
+ return Uri.parse(String.format("content://org.isoron.uhabits/habit/%d", getId()));
+ }
+
public void archive()
{
archived = 1;
diff --git a/app/src/main/java/org/isoron/uhabits/views/CheckmarkView.java b/app/src/main/java/org/isoron/uhabits/views/CheckmarkView.java
new file mode 100644
index 000000000..916952275
--- /dev/null
+++ b/app/src/main/java/org/isoron/uhabits/views/CheckmarkView.java
@@ -0,0 +1,212 @@
+/* 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 .
+ */
+
+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.getCurrentCheckmarkStatus();
+ this.star_status = habit.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 width, int height)
+ {
+ super.onMeasure(width, height);
+ setMeasuredDimension(width, height);
+ }
+
+ @Override
+ protected void onSizeChanged(int w, int h, int oldw, int oldh)
+ {
+ super.onSizeChanged(w, h, oldw, oldh);
+ updateSize(w, h);
+ updateLabel();
+ }
+
+ private void updateSize(int width, int height)
+ {
+ this.width = width;
+ this.height = height;
+
+ leftMargin = (int) (width * 0.015);
+ topMargin = (int) (height * 0.015);
+ padding = 8 * leftMargin;
+ textPaint.setTextSize(0.15f * width);
+ }
+
+ 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);
+ }
+
+}
diff --git a/app/src/main/java/org/isoron/uhabits/views/HabitHistoryView.java b/app/src/main/java/org/isoron/uhabits/views/HabitHistoryView.java
index 3bcbabe09..55a2a249a 100644
--- a/app/src/main/java/org/isoron/uhabits/views/HabitHistoryView.java
+++ b/app/src/main/java/org/isoron/uhabits/views/HabitHistoryView.java
@@ -26,7 +26,6 @@ import android.util.AttributeSet;
import org.isoron.helpers.ColorHelper;
import org.isoron.helpers.DateHelper;
-import org.isoron.uhabits.R;
import org.isoron.uhabits.models.Habit;
import java.text.SimpleDateFormat;
@@ -45,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;
@@ -54,14 +58,12 @@ public class HabitHistoryView extends ScrollableDataView
private int todayWeekday;
private int colors[];
private Rect baseLocation;
- private int baseSize;
private int primaryColor;
public HabitHistoryView(Context context, AttributeSet attrs)
{
super(context, attrs);
this.primaryColor = ColorHelper.palette[7];
- this.baseSize = (int) context.getResources().getDimension(R.dimen.small_square_size);
init();
}
@@ -76,7 +78,6 @@ public class HabitHistoryView extends ScrollableDataView
private void init()
{
- setDimensions(this.baseSize);
createPaints();
createColors();
@@ -90,7 +91,7 @@ public class HabitHistoryView extends ScrollableDataView
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;
@@ -100,16 +101,42 @@ 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);
+
+ int b = height / 8;
+ height = b * 8;
+ width = (width / b) * b;
+
+ setMeasuredDimension(width, height);
+ }
+
+ @Override
+ protected void onSizeChanged(int width, int height, int oldWidth, int oldHeight)
{
- super.onSizeChanged(w, h, oldw, oldh);
+ baseSize = height / 8;
+ setScrollerBucketSize(baseSize);
+
+ columnWidth = baseSize;
+ columnHeight = 8 * baseSize;
+ nColumns = width / baseSize;
+
+ squareSpacing = baseSize / 10;
+ 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 primaryColorBright = ColorHelper.mixColors(primaryColor, Color.WHITE, 0.5f);
- int grey = Color.rgb(230, 230, 230);
+ int primaryColorBright = Color.argb(127, Color.red(primaryColor), Color.green(primaryColor),
+ Color.blue(primaryColor));
+ int grey = Color.argb(25, 0, 0, 0);
colors = new int[3];
colors[0] = grey;
@@ -117,19 +144,11 @@ public class HabitHistoryView extends ScrollableDataView
colors[2] = primaryColor;
}
- private void setDimensions(int baseSize)
- {
- columnWidth = baseSize;
- columnHeight = 8 * baseSize;
- squareSpacing = 2;
- }
-
- private void createPaints()
+ protected void createPaints()
{
pTextHeader = new Paint();
- pTextHeader.setColor(Color.LTGRAY);
+ pTextHeader.setColor(Color.argb(64, 0, 0, 0));
pTextHeader.setTextAlign(Align.LEFT);
- pTextHeader.setTextSize(columnWidth * 0.5f);
pTextHeader.setAntiAlias(true);
pSquareBg = new Paint();
@@ -138,11 +157,7 @@ public class HabitHistoryView extends ScrollableDataView
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()
@@ -203,7 +218,7 @@ public class HabitHistoryView extends ScrollableDataView
for (int column = 0; column < nColumns - 1; column++)
{
drawColumn(canvas, baseLocation, currentDate, column);
- baseLocation.offset(columnWidth, -columnHeight);
+ baseLocation.offset(columnWidth, - columnHeight);
}
drawAxis(canvas, baseLocation);
@@ -216,9 +231,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 = dataOffset * 7 + nDays - 7 * (column + 1) + todayWeekday - j;
+ int checkmarkOffset = getDataOffset() * 7 + nDays - 7 * (column + 1) + todayWeekday - j;
drawSquare(canvas, location, date, checkmarkOffset);
}
@@ -248,6 +263,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());
@@ -256,21 +273,32 @@ 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))
{
- canvas.drawText(year, location.left, location.bottom - headerTextOffset, pTextHeader);
- previousYear = year;
- justPrintedYear = true;
+ if(!justSkippedColumn)
+ {
+ canvas.drawText(year, location.left, location.bottom - headerTextOffset, pTextHeader);
+ previousYear = year;
+ justPrintedYear = true;
+ }
+
+ justSkippedColumn = false;
}
else
{
+ justSkippedColumn = false;
justPrintedYear = false;
}
}
diff --git a/app/src/main/java/org/isoron/uhabits/views/HabitScoreView.java b/app/src/main/java/org/isoron/uhabits/views/HabitScoreView.java
index 579cee894..0397b78b2 100644
--- a/app/src/main/java/org/isoron/uhabits/views/HabitScoreView.java
+++ b/app/src/main/java/org/isoron/uhabits/views/HabitScoreView.java
@@ -20,12 +20,13 @@ 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;
-import org.isoron.uhabits.R;
import org.isoron.uhabits.models.Habit;
import java.text.SimpleDateFormat;
@@ -35,6 +36,10 @@ 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 Paint pGrid;
private float em;
@@ -46,14 +51,18 @@ public class HabitScoreView extends ScrollableDataView
private RectF rect, prevRect;
private int baseSize;
+ private int columnWidth;
+ private int columnHeight;
+ private int nColumns;
+
private int[] colors;
private int[] scores;
private int primaryColor;
+ private boolean isBackgroundTransparent;
public HabitScoreView(Context context, AttributeSet attrs)
{
super(context, attrs);
- this.baseSize = (int) context.getResources().getDimension(R.dimen.small_square_size);
this.primaryColor = ColorHelper.palette[7];
init();
}
@@ -69,7 +78,6 @@ public class HabitScoreView extends ScrollableDataView
private void init()
{
createPaints();
- setDimensions();
createColors();
dfMonth = new SimpleDateFormat("MMM", Locale.getDefault());
@@ -79,15 +87,6 @@ public class HabitScoreView extends ScrollableDataView
prevRect = new RectF();
}
- private void setDimensions()
- {
- this.columnWidth = baseSize;
- columnHeight = 8 * baseSize;
- headerHeight = baseSize;
- footerHeight = baseSize;
- em = pText.getFontSpacing();
- }
-
private void createColors()
{
colors = new int[4];
@@ -98,24 +97,54 @@ public class HabitScoreView extends ScrollableDataView
colors[2] = ColorHelper.mixColors(colors[0], colors[3], 0.33f);
}
- private void createPaints()
+ protected void createPaints()
{
pText = new Paint();
- pText.setColor(Color.LTGRAY);
+ pText.setColor(Color.argb(64, 0, 0, 0));
pText.setTextAlign(Paint.Align.LEFT);
- pText.setTextSize(baseSize * 0.5f);
pText.setAntiAlias(true);
pGraph = new Paint();
pGraph.setTextAlign(Paint.Align.CENTER);
- pGraph.setTextSize(baseSize * 0.5f);
pGraph.setAntiAlias(true);
- pGraph.setStrokeWidth(baseSize * 0.1f);
pGrid = new Paint();
- pGrid.setColor(Color.LTGRAY);
+ pGrid.setColor(Color.argb(64, 0, 0, 0));
pGrid.setAntiAlias(true);
+
+ }
+
+ @Override
+ protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec)
+ {
+ int width = MeasureSpec.getSize(widthMeasureSpec);
+ int height = MeasureSpec.getSize(heightMeasureSpec);
+
+ if(height > 0)
+ {
+ int b = height / 9;
+ height = b * 9;
+ width = (width / b) * b;
+ }
+
+ setMeasuredDimension(width, height);
+ }
+
+ @Override
+ protected void onSizeChanged(int width, int height, int oldWidth, int oldHeight)
+ {
+ baseSize = height / 9;
+ setScrollerBucketSize(baseSize);
+
+ columnWidth = baseSize;
+ columnHeight = 8 * baseSize;
+ nColumns = width / baseSize;
+
+ 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()
@@ -157,7 +186,6 @@ public class HabitScoreView extends ScrollableDataView
float lineHeight = pText.getFontSpacing();
rect.set(0, 0, nColumns * columnWidth, columnHeight);
- rect.offset(0, headerHeight);
drawGrid(canvas, rect);
String previousMonth = "";
@@ -167,7 +195,7 @@ public class HabitScoreView extends ScrollableDataView
long currentDate = DateHelper.getStartOfToday();
- for(int k = 0; k < nColumns + dataOffset - 1; k++)
+ for(int k = 0; k < nColumns + getDataOffset() - 1; k++)
currentDate -= 7 * DateHelper.millisecondsInOneDay;
for (int k = 0; k < nColumns; k++)
@@ -176,15 +204,14 @@ public class HabitScoreView extends ScrollableDataView
String day = dfDay.format(currentDate);
int score = 0;
- int offset = nColumns - k - 1 + dataOffset;
+ int offset = nColumns - k - 1 + getDataOffset();
if(offset < scores.length) score = scores[offset];
double sRelative = ((double) score) / Habit.MAX_SCORE;
int height = (int) (columnHeight * sRelative);
- rect.set(0, 0, columnWidth, columnWidth);
- rect.offset(k * columnWidth,
- headerHeight + columnHeight - height - columnWidth / 2);
+ rect.set(0, 0, baseSize, baseSize);
+ rect.offset(k * columnWidth, columnHeight - height - columnWidth / 2);
if (!prevRect.isEmpty())
{
@@ -197,7 +224,7 @@ public class HabitScoreView extends ScrollableDataView
prevRect.set(rect);
rect.set(0, 0, columnWidth, columnHeight);
- rect.offset(k * columnWidth, headerHeight);
+ rect.offset(k * columnWidth, 0);
if (!month.equals(previousMonth))
canvas.drawText(month, rect.centerX(), rect.bottom + lineHeight * 1.2f, pText);
else
@@ -213,7 +240,7 @@ public class HabitScoreView extends ScrollableDataView
int nRows = 5;
float rowHeight = rGrid.height() / nRows;
- pGrid.setColor(Color.rgb(240, 240, 240));
+ pGrid.setColor(Color.argb(20, 0, 0, 0));
for (int i = 0; i < nRows; i++)
{
canvas.drawText(String.format("%d%%", (100 - i * 100 / nRows)), rGrid.left + 0.5f * em,
@@ -235,15 +262,31 @@ 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(primaryColor);
+ 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;
+ }
+
+ private void setModeOrColor(Paint p, PorterDuffXfermode mode, int color)
+ {
+ if(isBackgroundTransparent)
+ p.setXfermode(mode);
+ else
+ p.setColor(color);
}
}
diff --git a/app/src/main/java/org/isoron/uhabits/views/HabitStreakView.java b/app/src/main/java/org/isoron/uhabits/views/HabitStreakView.java
index 6f8223f41..d8691facb 100644
--- a/app/src/main/java/org/isoron/uhabits/views/HabitStreakView.java
+++ b/app/src/main/java/org/isoron/uhabits/views/HabitStreakView.java
@@ -43,6 +43,11 @@ public class HabitStreakView extends ScrollableDataView
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;
@@ -53,7 +58,6 @@ public class HabitStreakView extends ScrollableDataView
public HabitStreakView(Context context, AttributeSet attrs)
{
super(context, attrs);
- this.baseSize = (int) context.getResources().getDimension(R.dimen.small_square_size);
this.primaryColor = ColorHelper.palette[7];
init();
}
@@ -62,6 +66,7 @@ public class HabitStreakView extends ScrollableDataView
{
this.habit = habit;
this.primaryColor = habit.color;
+
createColors();
fetchData();
postInvalidate();
@@ -69,7 +74,6 @@ public class HabitStreakView extends ScrollableDataView
private void init()
{
- setDimensions(baseSize);
createPaints();
createColors();
@@ -77,34 +81,54 @@ public class HabitStreakView extends ScrollableDataView
rect = new Rect();
}
- private void setDimensions(int baseSize)
+ @Override
+ protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec)
{
- this.columnWidth = baseSize;
+ int width = MeasureSpec.getSize(widthMeasureSpec);
+ int height = MeasureSpec.getSize(heightMeasureSpec);
+
+ int b = height / 10;
+ height = b * 10;
+ width = (width / b) * b;
+
+ setMeasuredDimension(width, height);
+ }
+
+ @Override
+ protected void onSizeChanged(int width, int height, int oldWidth, int oldHeight)
+ {
+ 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()
{
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);
+ colors[1] = Color.argb(80, Color.red(primaryColor), Color.green(primaryColor), Color.blue(
+ primaryColor));
+ colors[2] = Color.argb(170, Color.red(primaryColor), Color.green(primaryColor),
+ Color.blue(primaryColor));
+ colors[0] = Color.argb(30, 0, 0, 0);
}
- private void createPaints()
+ protected void createPaints()
{
pText = new Paint();
- pText.setColor(Color.LTGRAY);
+ pText.setColor(Color.argb(64, 0, 0, 0));
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);
}
@@ -173,7 +197,7 @@ public class HabitStreakView extends ScrollableDataView
float barHeaderOffset = lineHeight * 0.4f;
int nStreaks = startTimes.length;
- int start = nStreaks - nColumns - dataOffset;
+ int start = nStreaks - nColumns - getDataOffset();
String previousMonth = "";
diff --git a/app/src/main/java/org/isoron/uhabits/views/ScrollableDataView.java b/app/src/main/java/org/isoron/uhabits/views/ScrollableDataView.java
index 8c0ad9dab..7360456da 100644
--- a/app/src/main/java/org/isoron/uhabits/views/ScrollableDataView.java
+++ b/app/src/main/java/org/isoron/uhabits/views/ScrollableDataView.java
@@ -31,10 +31,8 @@ public abstract class ScrollableDataView extends View implements GestureDetector
ValueAnimator.AnimatorUpdateListener
{
- protected int dataOffset;
- protected int nColumns;
- protected int columnWidth, columnHeight;
- protected int headerHeight, footerHeight;
+ private int dataOffset;
+ private int scrollerBucketSize;
private GestureDetector detector;
private Scroller scroller;
@@ -68,21 +66,6 @@ public abstract class ScrollableDataView extends View implements GestureDetector
return detector.onTouchEvent(event);
}
- @Override
- protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec)
- {
- super.onMeasure(widthMeasureSpec, heightMeasureSpec);
- setMeasuredDimension(getMeasuredWidth(), columnHeight + headerHeight + footerHeight);
- }
-
- @Override
- protected void onSizeChanged(int w, int h, int oldw, int oldh)
- {
- super.onSizeChanged(w, h, oldw, oldh);
- nColumns = w / columnWidth;
- fetchData();
- }
-
@Override
public boolean onDown(MotionEvent e)
{
@@ -104,13 +87,17 @@ public abstract class ScrollableDataView extends View implements GestureDetector
@Override
public boolean onScroll(MotionEvent e1, MotionEvent e2, float dx, float dy)
{
+ if(scrollerBucketSize == 0)
+ return false;
+
if(Math.abs(dx) > Math.abs(dy))
getParent().requestDisallowInterceptTouchEvent(true);
scroller.startScroll(scroller.getCurrX(), scroller.getCurrY(), (int) -dx, (int) dy, 0);
scroller.computeScrollOffset();
- dataOffset = Math.max(0, scroller.getCurrX() / columnWidth);
+ dataOffset = Math.max(0, scroller.getCurrX() / scrollerBucketSize);
postInvalidate();
+
return true;
}
@@ -139,7 +126,7 @@ public abstract class ScrollableDataView extends View implements GestureDetector
if (!scroller.isFinished())
{
scroller.computeScrollOffset();
- dataOffset = Math.max(0, scroller.getCurrX() / columnWidth);
+ dataOffset = Math.max(0, scroller.getCurrX() / scrollerBucketSize);
postInvalidate();
}
else
@@ -147,4 +134,14 @@ public abstract class ScrollableDataView extends View implements GestureDetector
scrollAnimator.cancel();
}
}
+
+ public int getDataOffset()
+ {
+ return dataOffset;
+ }
+
+ public void setScrollerBucketSize(int scrollerBucketSize)
+ {
+ this.scrollerBucketSize = scrollerBucketSize;
+ }
}
diff --git a/app/src/main/java/org/isoron/uhabits/widgets/BaseWidgetProvider.java b/app/src/main/java/org/isoron/uhabits/widgets/BaseWidgetProvider.java
new file mode 100644
index 000000000..970a82aaf
--- /dev/null
+++ b/app/src/main/java/org/isoron/uhabits/widgets/BaseWidgetProvider.java
@@ -0,0 +1,120 @@
+/* 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 .
+ */
+
+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.os.Build;
+import android.os.Bundle;
+import android.preference.PreferenceManager;
+import android.util.Log;
+import android.view.View;
+import android.widget.RemoteViews;
+
+import org.isoron.helpers.DialogHelper;
+import org.isoron.uhabits.R;
+import org.isoron.uhabits.models.Habit;
+
+public abstract class BaseWidgetProvider extends AppWidgetProvider
+{
+
+ 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, int max_height, int max_width,
+ 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)
+ {
+ int maxWidth = getDefaultWidth();
+ int maxHeight = 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));
+ }
+
+ 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, maxHeight, maxWidth, habit);
+ widgetView.setDrawingCacheEnabled(true);
+ widgetView.buildDrawingCache(true);
+ Bitmap drawingCache = widgetView.getDrawingCache();
+
+ remoteViews.setTextViewText(R.id.label, habit.name);
+ remoteViews.setImageViewBitmap(R.id.imageView, drawingCache);
+
+ PendingIntent onClickIntent = getOnClickPendingIntent(context, habit);
+ if(onClickIntent != null) remoteViews.setOnClickPendingIntent(R.id.imageView, onClickIntent);
+
+ manager.updateAppWidget(widgetId, remoteViews);
+ }
+}
diff --git a/app/src/main/java/org/isoron/uhabits/widgets/CheckmarkWidgetProvider.java b/app/src/main/java/org/isoron/uhabits/widgets/CheckmarkWidgetProvider.java
new file mode 100644
index 000000000..fb81d7133
--- /dev/null
+++ b/app/src/main/java/org/isoron/uhabits/widgets/CheckmarkWidgetProvider.java
@@ -0,0 +1,69 @@
+/* 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 .
+ */
+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, int maxHeight, int maxWidth, Habit habit)
+ {
+ CheckmarkView widgetView = new CheckmarkView(context);
+
+ widgetView.setHabit(habit);
+ widgetView.measure(maxWidth, maxHeight);
+ widgetView.layout(0, 0, maxWidth, maxHeight);
+
+ int width = widgetView.getMeasuredWidth();
+ int height = widgetView.getMeasuredHeight();
+ widgetView.measure(width, height);
+ widgetView.layout(0, 0, width, height);
+
+ return widgetView;
+ }
+
+ @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 160;
+ }
+
+ @Override
+ protected int getLayoutId()
+ {
+ return R.layout.widget_checkmark;
+ }
+}
diff --git a/app/src/main/java/org/isoron/uhabits/widgets/HabitPickerDialog.java b/app/src/main/java/org/isoron/uhabits/widgets/HabitPickerDialog.java
new file mode 100644
index 000000000..f44127cac
--- /dev/null
+++ b/app/src/main/java/org/isoron/uhabits/widgets/HabitPickerDialog.java
@@ -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 .
+ */
+
+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 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 habitNames = new ArrayList<>();
+
+ List habits = Habit.getAll(false);
+ for(Habit h : habits)
+ {
+ habitIds.add(h.getId());
+ habitNames.add(h.name);
+ }
+
+ ArrayAdapter 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();
+ }
+
+}
diff --git a/app/src/main/java/org/isoron/uhabits/widgets/HistoryWidgetProvider.java b/app/src/main/java/org/isoron/uhabits/widgets/HistoryWidgetProvider.java
new file mode 100644
index 000000000..2a382ef01
--- /dev/null
+++ b/app/src/main/java/org/isoron/uhabits/widgets/HistoryWidgetProvider.java
@@ -0,0 +1,69 @@
+/* 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 .
+ */
+package org.isoron.uhabits.widgets;
+
+import android.app.PendingIntent;
+import android.content.Context;
+import android.view.View;
+
+import org.isoron.helpers.DialogHelper;
+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, int maxHeight, int maxWidth, Habit habit)
+ {
+ HabitHistoryView view = new HabitHistoryView(context, null);
+ view.setHabit(habit);
+ view.measure(maxWidth, maxHeight);
+ view.layout(0, 0, maxWidth, maxHeight);
+
+ int width = view.getMeasuredWidth();
+ int height = view.getMeasuredHeight();
+ height -= DialogHelper.dpToPixels(context, 12);
+ view.measure(width, height);
+ view.layout(0, 0, width, height);
+
+ 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;
+ }
+}
diff --git a/app/src/main/java/org/isoron/uhabits/widgets/ScoreWidgetProvider.java b/app/src/main/java/org/isoron/uhabits/widgets/ScoreWidgetProvider.java
new file mode 100644
index 000000000..6dd3ae246
--- /dev/null
+++ b/app/src/main/java/org/isoron/uhabits/widgets/ScoreWidgetProvider.java
@@ -0,0 +1,70 @@
+/* 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 .
+ */
+package org.isoron.uhabits.widgets;
+
+import android.app.PendingIntent;
+import android.content.Context;
+import android.view.View;
+
+import org.isoron.helpers.DialogHelper;
+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, int maxHeight, int maxWidth, Habit habit)
+ {
+ HabitScoreView view = new HabitScoreView(context, null);
+ view.setIsBackgroundTransparent(true);
+ view.setHabit(habit);
+ view.measure(maxWidth, maxHeight);
+ view.layout(0, 0, maxWidth, maxHeight);
+
+ int width = view.getMeasuredWidth();
+ int height = view.getMeasuredHeight();
+ height -= DialogHelper.dpToPixels(context, 12);
+ view.measure(width, height);
+ view.layout(0, 0, width, height);
+
+ 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;
+ }
+}
diff --git a/app/src/main/java/org/isoron/uhabits/widgets/StreakWidgetProvider.java b/app/src/main/java/org/isoron/uhabits/widgets/StreakWidgetProvider.java
new file mode 100644
index 000000000..e7685748c
--- /dev/null
+++ b/app/src/main/java/org/isoron/uhabits/widgets/StreakWidgetProvider.java
@@ -0,0 +1,70 @@
+/* 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 .
+ */
+package org.isoron.uhabits.widgets;
+
+import android.app.PendingIntent;
+import android.content.Context;
+import android.view.View;
+
+import org.isoron.helpers.DialogHelper;
+import org.isoron.uhabits.R;
+import org.isoron.uhabits.models.Habit;
+import org.isoron.uhabits.views.HabitScoreView;
+import org.isoron.uhabits.views.HabitStreakView;
+
+public class StreakWidgetProvider extends BaseWidgetProvider
+{
+ @Override
+ protected View buildCustomView(Context context, int maxHeight, int maxWidth, Habit habit)
+ {
+ HabitStreakView view = new HabitStreakView(context, null);
+ view.setHabit(habit);
+ view.measure(maxWidth, maxHeight);
+ view.layout(0, 0, maxWidth, maxHeight);
+
+ int width = view.getMeasuredWidth();
+ int height = view.getMeasuredHeight();
+ height -= DialogHelper.dpToPixels(context, 12);
+ view.measure(width, height);
+ view.layout(0, 0, width, height);
+
+ 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;
+ }
+}
diff --git a/app/src/main/res/drawable/widget_background.xml b/app/src/main/res/drawable/widget_background.xml
new file mode 100644
index 000000000..98fd95f07
--- /dev/null
+++ b/app/src/main/res/drawable/widget_background.xml
@@ -0,0 +1,7 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/list_habits_item.xml b/app/src/main/res/layout/list_habits_item.xml
index b1f07dc7a..2e4704952 100644
--- a/app/src/main/res/layout/list_habits_item.xml
+++ b/app/src/main/res/layout/list_habits_item.xml
@@ -12,7 +12,7 @@
style="@style/habitsListStarStyle" />
@@ -28,10 +27,7 @@
-
+
+ android:layout_height="200dp"/>
@@ -61,7 +57,7 @@
+ android:layout_height="180dp"/>
@@ -81,7 +77,7 @@
+ android:layout_height="200dp"/>
\ No newline at end of file
diff --git a/app/src/main/res/layout/small_widget_preview.xml b/app/src/main/res/layout/small_widget_preview.xml
new file mode 100644
index 000000000..d9a8c5ac1
--- /dev/null
+++ b/app/src/main/res/layout/small_widget_preview.xml
@@ -0,0 +1,17 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/widget_checkmark.xml b/app/src/main/res/layout/widget_checkmark.xml
new file mode 100644
index 000000000..90ddbbb4d
--- /dev/null
+++ b/app/src/main/res/layout/widget_checkmark.xml
@@ -0,0 +1,17 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/widget_configure_activity.xml b/app/src/main/res/layout/widget_configure_activity.xml
new file mode 100644
index 000000000..8c653b697
--- /dev/null
+++ b/app/src/main/res/layout/widget_configure_activity.xml
@@ -0,0 +1,8 @@
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/widget_graph.xml b/app/src/main/res/layout/widget_graph.xml
new file mode 100644
index 000000000..0c62235a9
--- /dev/null
+++ b/app/src/main/res/layout/widget_graph.xml
@@ -0,0 +1,30 @@
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/mipmap-hdpi/ic_small_widget_preview.png b/app/src/main/res/mipmap-hdpi/ic_small_widget_preview.png
new file mode 100644
index 000000000..7ee617aa6
Binary files /dev/null and b/app/src/main/res/mipmap-hdpi/ic_small_widget_preview.png differ
diff --git a/app/src/main/res/mipmap-mdpi/ic_small_widget_preview.png b/app/src/main/res/mipmap-mdpi/ic_small_widget_preview.png
new file mode 100644
index 000000000..ff862e252
Binary files /dev/null and b/app/src/main/res/mipmap-mdpi/ic_small_widget_preview.png differ
diff --git a/app/src/main/res/mipmap-xhdpi/ic_small_widget_preview.png b/app/src/main/res/mipmap-xhdpi/ic_small_widget_preview.png
new file mode 100644
index 000000000..8d90b8901
Binary files /dev/null and b/app/src/main/res/mipmap-xhdpi/ic_small_widget_preview.png differ
diff --git a/app/src/main/res/mipmap-xxhdpi/ic_small_widget_preview.png b/app/src/main/res/mipmap-xxhdpi/ic_small_widget_preview.png
new file mode 100644
index 000000000..734f9630f
Binary files /dev/null and b/app/src/main/res/mipmap-xxhdpi/ic_small_widget_preview.png differ
diff --git a/app/src/main/res/mipmap-xxxhdpi/ic_small_widget_preview.png b/app/src/main/res/mipmap-xxxhdpi/ic_small_widget_preview.png
new file mode 100644
index 000000000..f049e8332
Binary files /dev/null and b/app/src/main/res/mipmap-xxxhdpi/ic_small_widget_preview.png differ
diff --git a/app/src/main/res/xml/widget_checkmark_info.xml b/app/src/main/res/xml/widget_checkmark_info.xml
new file mode 100644
index 000000000..0b16f1cef
--- /dev/null
+++ b/app/src/main/res/xml/widget_checkmark_info.xml
@@ -0,0 +1,13 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/xml/widget_history_info.xml b/app/src/main/res/xml/widget_history_info.xml
new file mode 100644
index 000000000..dd8068375
--- /dev/null
+++ b/app/src/main/res/xml/widget_history_info.xml
@@ -0,0 +1,15 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/xml/widget_score_info.xml b/app/src/main/res/xml/widget_score_info.xml
new file mode 100644
index 000000000..dd8068375
--- /dev/null
+++ b/app/src/main/res/xml/widget_score_info.xml
@@ -0,0 +1,15 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/xml/widget_streak_info.xml b/app/src/main/res/xml/widget_streak_info.xml
new file mode 100644
index 000000000..28ae1a512
--- /dev/null
+++ b/app/src/main/res/xml/widget_streak_info.xml
@@ -0,0 +1,14 @@
+
+
+
+
\ No newline at end of file