Merge branch 'widgets' into dev

pull/30/head
Alinson S. Xavier 10 years ago
commit 141fd30d70

@ -10,11 +10,11 @@
<application <application
android:name="com.activeandroid.app.Application" android:name="com.activeandroid.app.Application"
android:allowBackup="true"
android:backupAgent=".HabitsBackupAgent"
android:icon="@mipmap/ic_launcher" android:icon="@mipmap/ic_launcher"
android:label="@string/main_activity_title" android:label="@string/main_activity_title"
android:theme="@style/AppBaseTheme" android:theme="@style/AppBaseTheme">
android:allowBackup="true"
android:backupAgent=".HabitsBackupAgent">
<meta-data <meta-data
android:name="AA_DB_NAME" android:name="AA_DB_NAME"
@ -39,7 +39,7 @@
</activity> </activity>
<receiver <receiver
android:name=".ReminderAlarmReceiver" /> android:name=".HabitBroadcastReceiver"/>
<activity <activity
android:name=".ShowHabitActivity" android:name=".ShowHabitActivity"
@ -59,9 +59,65 @@
android:value="org.isoron.uhabits.MainActivity"/> android:value="org.isoron.uhabits.MainActivity"/>
</activity> </activity>
<activity android:name=".IntroActivity" <activity
android:name=".IntroActivity"
android:label="" android:label=""
android:theme="@style/Theme.AppCompat.Light.NoActionBar"/> 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> </application>
</manifest> </manifest>

Binary file not shown.

After

Width:  |  Height:  |  Size: 35 KiB

@ -18,10 +18,12 @@ package org.isoron.helpers;
import android.content.Context; import android.content.Context;
import android.content.SharedPreferences; import android.content.SharedPreferences;
import android.content.res.Resources;
import android.graphics.Typeface; import android.graphics.Typeface;
import android.os.Vibrator; import android.os.Vibrator;
import android.preference.PreferenceManager; import android.preference.PreferenceManager;
import android.util.AttributeSet; import android.util.AttributeSet;
import android.util.DisplayMetrics;
import android.view.View; import android.view.View;
import android.view.inputmethod.InputMethodManager; import android.view.inputmethod.InputMethodManager;
@ -72,4 +74,11 @@ public abstract class DialogHelper
else else
return attrs.getAttributeValue(ISORON_NAMESPACE, name); 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.os.Handler;
import android.preference.PreferenceManager; import android.preference.PreferenceManager;
import android.support.v4.app.NotificationCompat; import android.support.v4.app.NotificationCompat;
import android.support.v4.content.LocalBroadcastManager;
import org.isoron.helpers.DateHelper; import org.isoron.helpers.DateHelper;
import org.isoron.uhabits.helpers.ReminderHelper; import org.isoron.uhabits.helpers.ReminderHelper;
@ -38,12 +39,11 @@ import org.isoron.uhabits.models.Habit;
import java.util.Date; 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_CHECK = "org.isoron.uhabits.ACTION_CHECK";
public static final String ACTION_DISMISS = "org.isoron.uhabits.ACTION_DISMISS"; 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_SHOW_REMINDER = "org.isoron.uhabits.ACTION_SHOW_REMINDER";
public static final String ACTION_REMOVE_REMINDER = "org.isoron.uhabits.ACTION_REMOVE_REMINDER";
public static final String ACTION_SNOOZE = "org.isoron.uhabits.ACTION_SNOOZE"; public static final String ACTION_SNOOZE = "org.isoron.uhabits.ACTION_SNOOZE";
@Override @Override
@ -51,7 +51,7 @@ public class ReminderAlarmReceiver extends BroadcastReceiver
{ {
switch (intent.getAction()) switch (intent.getAction())
{ {
case ACTION_REMIND: case ACTION_SHOW_REMINDER:
createNotification(context, intent); createNotification(context, intent);
createReminderAlarms(context); createReminderAlarms(context);
break; break;
@ -103,6 +103,12 @@ public class ReminderAlarmReceiver extends BroadcastReceiver
habit.toggleRepetition(timestamp); habit.toggleRepetition(timestamp);
habit.save(); habit.save();
dismissNotification(context, habit); dismissNotification(context, habit);
LocalBroadcastManager manager = LocalBroadcastManager.getInstance(context);
Intent refreshIntent = new Intent(MainActivity.ACTION_REFRESH);
manager.sendBroadcast(refreshIntent);
MainActivity.updateWidgets(context);
} }
private void dismissAllHabits() private void dismissAllHabits()
@ -146,21 +152,9 @@ public class ReminderAlarmReceiver extends BroadcastReceiver
PendingIntent contentPendingIntent = PendingIntent contentPendingIntent =
PendingIntent.getActivity(context, 0, contentIntent, 0); PendingIntent.getActivity(context, 0, contentIntent, 0);
Intent deleteIntent = new Intent(context, ReminderAlarmReceiver.class); PendingIntent dismissPendingIntent = buildDismissIntent(context);
deleteIntent.setAction(ACTION_DISMISS); PendingIntent checkIntentPending = buildCheckIntent(context, habit, timestamp);
PendingIntent deletePendingIntent = PendingIntent.getBroadcast(context, 0, deleteIntent, 0); PendingIntent snoozeIntentPending = buildSnoozeIntent(context, habit);
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);
Uri soundUri = RingtoneManager.getDefaultUri(RingtoneManager.TYPE_NOTIFICATION); Uri soundUri = RingtoneManager.getDefaultUri(RingtoneManager.TYPE_NOTIFICATION);
@ -173,7 +167,7 @@ public class ReminderAlarmReceiver extends BroadcastReceiver
.setContentTitle(habit.name) .setContentTitle(habit.name)
.setContentText(habit.description) .setContentText(habit.description)
.setContentIntent(contentPendingIntent) .setContentIntent(contentPendingIntent)
.setDeleteIntent(deletePendingIntent) .setDeleteIntent(dismissPendingIntent)
.addAction(R.drawable.ic_action_check, .addAction(R.drawable.ic_action_check,
context.getString(R.string.check), checkIntentPending) context.getString(R.string.check), checkIntentPending)
.addAction(R.drawable.ic_action_snooze, .addAction(R.drawable.ic_action_snooze,
@ -193,6 +187,32 @@ public class ReminderAlarmReceiver extends BroadcastReceiver
notificationManager.notify(notificationId, notification); 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) private boolean checkWeekday(Intent intent, Habit habit)
{ {
Long timestamp = intent.getLongExtra("timestamp", DateHelper.getStartOfToday()); Long timestamp = intent.getLongExtra("timestamp", DateHelper.getStartOfToday());

@ -16,11 +16,17 @@
package org.isoron.uhabits; 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.Intent;
import android.content.IntentFilter;
import android.content.SharedPreferences; import android.content.SharedPreferences;
import android.net.Uri; import android.net.Uri;
import android.os.Bundle; import android.os.Bundle;
import android.preference.PreferenceManager; import android.preference.PreferenceManager;
import android.support.v4.content.LocalBroadcastManager;
import android.view.Menu; import android.view.Menu;
import android.view.MenuItem; import android.view.MenuItem;
@ -30,12 +36,21 @@ import org.isoron.helpers.ReplayableActivity;
import org.isoron.uhabits.fragments.ListHabitsFragment; import org.isoron.uhabits.fragments.ListHabitsFragment;
import org.isoron.uhabits.helpers.ReminderHelper; import org.isoron.uhabits.helpers.ReminderHelper;
import org.isoron.uhabits.models.Habit; 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 public class MainActivity extends ReplayableActivity
implements ListHabitsFragment.OnHabitClickListener implements ListHabitsFragment.OnHabitClickListener
{ {
private ListHabitsFragment listHabitsFragment; 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 @Override
protected void onCreate(Bundle savedInstanceState) protected void onCreate(Bundle savedInstanceState)
@ -47,6 +62,10 @@ public class MainActivity extends ReplayableActivity
listHabitsFragment = listHabitsFragment =
(ListHabitsFragment) getFragmentManager().findFragmentById(R.id.fragment1); (ListHabitsFragment) getFragmentManager().findFragmentById(R.id.fragment1);
receiver = new Receiver();
localBroadcastManager = LocalBroadcastManager.getInstance(this);
localBroadcastManager.registerReceiver(receiver, new IntentFilter(ACTION_REFRESH));
onStartup(); onStartup();
} }
@ -56,6 +75,7 @@ public class MainActivity extends ReplayableActivity
ReminderHelper.createReminderAlarms(MainActivity.this); ReminderHelper.createReminderAlarms(MainActivity.this);
DialogHelper.incrementLaunchCount(this); DialogHelper.incrementLaunchCount(this);
showTutorial(); showTutorial();
updateWidgets(this);
} }
private void showTutorial() private void showTutorial()
@ -108,5 +128,40 @@ public class MainActivity extends ReplayableActivity
public void onPostExecuteCommand(Long refreshKey) public void onPostExecuteCommand(Long refreshKey)
{ {
listHabitsFragment.onPostExecuteCommand(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);
}
} }
} }

@ -660,7 +660,7 @@ public class ListHabitsFragment extends Fragment
LinearLayout.LayoutParams params = LinearLayout.LayoutParams params =
new LinearLayout.LayoutParams(tvNameWidth, LayoutParams.WRAP_CONTENT, 1); new LinearLayout.LayoutParams(tvNameWidth, LayoutParams.WRAP_CONTENT, 1);
view.findViewById(R.id.tvName).setLayoutParams(params); view.findViewById(R.id.label).setLayoutParams(params);
inflateCheckmarkButtons(view); inflateCheckmarkButtons(view);
@ -668,7 +668,7 @@ public class ListHabitsFragment extends Fragment
} }
TextView tvStar = ((TextView) view.findViewById(R.id.tvStar)); TextView tvStar = ((TextView) view.findViewById(R.id.tvStar));
TextView tvName = (TextView) view.findViewById(R.id.tvName); TextView tvName = (TextView) view.findViewById(R.id.label);
LinearLayout llInner = (LinearLayout) view.findViewById(R.id.llInner); LinearLayout llInner = (LinearLayout) view.findViewById(R.id.llInner);
LinearLayout llButtons = (LinearLayout) view.findViewById(R.id.llButtons); LinearLayout llButtons = (LinearLayout) view.findViewById(R.id.llButtons);

@ -25,7 +25,7 @@ import android.os.Build;
import android.util.Log; import android.util.Log;
import org.isoron.helpers.DateHelper; import org.isoron.helpers.DateHelper;
import org.isoron.uhabits.ReminderAlarmReceiver; import org.isoron.uhabits.HabitBroadcastReceiver;
import org.isoron.uhabits.models.Habit; import org.isoron.uhabits.models.Habit;
import java.text.DateFormat; import java.text.DateFormat;
@ -58,10 +58,10 @@ public class ReminderHelper
long timestamp = DateHelper.getStartOfDay(DateHelper.toLocalTime(reminderTime)); 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); Intent alarmIntent = new Intent(context, HabitBroadcastReceiver.class);
alarmIntent.setAction(ReminderAlarmReceiver.ACTION_REMIND); alarmIntent.setAction(HabitBroadcastReceiver.ACTION_SHOW_REMINDER);
alarmIntent.setData(uri); alarmIntent.setData(uri);
alarmIntent.putExtra("timestamp", timestamp); alarmIntent.putExtra("timestamp", timestamp);
alarmIntent.putExtra("reminderTime", reminderTime); alarmIntent.putExtra("reminderTime", reminderTime);

@ -19,6 +19,7 @@ package org.isoron.uhabits.models;
import android.annotation.SuppressLint; import android.annotation.SuppressLint;
import android.database.Cursor; import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase; import android.database.sqlite.SQLiteDatabase;
import android.net.Uri;
import com.activeandroid.ActiveAndroid; import com.activeandroid.ActiveAndroid;
import com.activeandroid.Cache; import com.activeandroid.Cache;
@ -383,6 +384,24 @@ public class Habit extends Model
.executeSingle(); .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) public int getRepsCount(int days)
{ {
long timeTo = DateHelper.getStartOfToday(); long timeTo = DateHelper.getStartOfToday();
@ -426,6 +445,11 @@ public class Habit extends Model
deleteStreaksNewerThan(timestamp); deleteStreaksNewerThan(timestamp);
} }
public Uri getUri()
{
return Uri.parse(String.format("content://org.isoron.uhabits/habit/%d", getId()));
}
public void archive() public void archive()
{ {
archived = 1; archived = 1;

@ -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 <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.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);
}
}

@ -26,7 +26,6 @@ import android.util.AttributeSet;
import org.isoron.helpers.ColorHelper; import org.isoron.helpers.ColorHelper;
import org.isoron.helpers.DateHelper; import org.isoron.helpers.DateHelper;
import org.isoron.uhabits.R;
import org.isoron.uhabits.models.Habit; import org.isoron.uhabits.models.Habit;
import java.text.SimpleDateFormat; import java.text.SimpleDateFormat;
@ -45,6 +44,11 @@ public class HabitHistoryView extends ScrollableDataView
private float squareTextOffset; private float squareTextOffset;
private float headerTextOffset; private float headerTextOffset;
private int columnWidth;
private int columnHeight;
private int nColumns;
private int baseSize;
private String wdays[]; private String wdays[];
private SimpleDateFormat dfMonth; private SimpleDateFormat dfMonth;
private SimpleDateFormat dfYear; private SimpleDateFormat dfYear;
@ -54,14 +58,12 @@ public class HabitHistoryView extends ScrollableDataView
private int todayWeekday; private int todayWeekday;
private int colors[]; private int colors[];
private Rect baseLocation; private Rect baseLocation;
private int baseSize;
private int primaryColor; private int primaryColor;
public HabitHistoryView(Context context, AttributeSet attrs) public HabitHistoryView(Context context, AttributeSet attrs)
{ {
super(context, attrs); super(context, attrs);
this.primaryColor = ColorHelper.palette[7]; this.primaryColor = ColorHelper.palette[7];
this.baseSize = (int) context.getResources().getDimension(R.dimen.small_square_size);
init(); init();
} }
@ -76,7 +78,6 @@ public class HabitHistoryView extends ScrollableDataView
private void init() private void init()
{ {
setDimensions(this.baseSize);
createPaints(); createPaints();
createColors(); createColors();
@ -90,7 +91,7 @@ public class HabitHistoryView extends ScrollableDataView
private void updateDate() private void updateDate()
{ {
baseDate = new GregorianCalendar(); 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; nDays = (nColumns - 1) * 7;
todayWeekday = new GregorianCalendar().get(Calendar.DAY_OF_WEEK) % 7; todayWeekday = new GregorianCalendar().get(Calendar.DAY_OF_WEEK) % 7;
@ -100,16 +101,42 @@ public class HabitHistoryView extends ScrollableDataView
} }
@Override @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(); updateDate();
} }
private void createColors() private void createColors()
{ {
int primaryColorBright = ColorHelper.mixColors(primaryColor, Color.WHITE, 0.5f); int primaryColorBright = Color.argb(127, Color.red(primaryColor), Color.green(primaryColor),
int grey = Color.rgb(230, 230, 230); Color.blue(primaryColor));
int grey = Color.argb(25, 0, 0, 0);
colors = new int[3]; colors = new int[3];
colors[0] = grey; colors[0] = grey;
@ -117,19 +144,11 @@ public class HabitHistoryView extends ScrollableDataView
colors[2] = primaryColor; colors[2] = primaryColor;
} }
private void setDimensions(int baseSize) protected void createPaints()
{
columnWidth = baseSize;
columnHeight = 8 * baseSize;
squareSpacing = 2;
}
private void createPaints()
{ {
pTextHeader = new Paint(); pTextHeader = new Paint();
pTextHeader.setColor(Color.LTGRAY); pTextHeader.setColor(Color.argb(64, 0, 0, 0));
pTextHeader.setTextAlign(Align.LEFT); pTextHeader.setTextAlign(Align.LEFT);
pTextHeader.setTextSize(columnWidth * 0.5f);
pTextHeader.setAntiAlias(true); pTextHeader.setAntiAlias(true);
pSquareBg = new Paint(); pSquareBg = new Paint();
@ -138,11 +157,7 @@ public class HabitHistoryView extends ScrollableDataView
pSquareFg = new Paint(); pSquareFg = new Paint();
pSquareFg.setColor(Color.WHITE); pSquareFg.setColor(Color.WHITE);
pSquareFg.setAntiAlias(true); pSquareFg.setAntiAlias(true);
pSquareFg.setTextSize(columnWidth * 0.5f);
pSquareFg.setTextAlign(Align.CENTER); pSquareFg.setTextAlign(Align.CENTER);
squareTextOffset = pSquareFg.getFontSpacing() * 0.4f;
headerTextOffset = pTextHeader.getFontSpacing() * 0.3f;
} }
protected void fetchData() protected void fetchData()
@ -216,9 +231,9 @@ public class HabitHistoryView extends ScrollableDataView
for (int j = 0; j < 7; j++) 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); 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) private void drawColumnHeader(Canvas canvas, Rect location, GregorianCalendar date)
{ {
String month = dfMonth.format(date.getTime()); String month = dfMonth.format(date.getTime());
@ -256,21 +273,32 @@ public class HabitHistoryView extends ScrollableDataView
if (!month.equals(previousMonth)) if (!month.equals(previousMonth))
{ {
int offset = 0; int offset = 0;
if (justPrintedYear) offset += columnWidth; if (justPrintedYear)
{
offset += columnWidth;
justSkippedColumn = true;
}
canvas.drawText(month, location.left + offset, location.bottom - headerTextOffset, canvas.drawText(month, location.left + offset, location.bottom - headerTextOffset,
pTextHeader); pTextHeader);
previousMonth = month; previousMonth = month;
justPrintedYear = false; justPrintedYear = false;
} }
else if (!year.equals(previousYear)) else if (!year.equals(previousYear))
{
if(!justSkippedColumn)
{ {
canvas.drawText(year, location.left, location.bottom - headerTextOffset, pTextHeader); canvas.drawText(year, location.left, location.bottom - headerTextOffset, pTextHeader);
previousYear = year; previousYear = year;
justPrintedYear = true; justPrintedYear = true;
} }
justSkippedColumn = false;
}
else else
{ {
justSkippedColumn = false;
justPrintedYear = false; justPrintedYear = false;
} }
} }

@ -20,12 +20,13 @@ import android.content.Context;
import android.graphics.Canvas; import android.graphics.Canvas;
import android.graphics.Color; import android.graphics.Color;
import android.graphics.Paint; import android.graphics.Paint;
import android.graphics.PorterDuff;
import android.graphics.PorterDuffXfermode;
import android.graphics.RectF; import android.graphics.RectF;
import android.util.AttributeSet; import android.util.AttributeSet;
import org.isoron.helpers.ColorHelper; import org.isoron.helpers.ColorHelper;
import org.isoron.helpers.DateHelper; import org.isoron.helpers.DateHelper;
import org.isoron.uhabits.R;
import org.isoron.uhabits.models.Habit; import org.isoron.uhabits.models.Habit;
import java.text.SimpleDateFormat; import java.text.SimpleDateFormat;
@ -35,6 +36,10 @@ import java.util.Random;
public class HabitScoreView extends ScrollableDataView public class HabitScoreView extends ScrollableDataView
{ {
public static final int BUCKET_SIZE = 7; 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 Paint pGrid;
private float em; private float em;
@ -46,14 +51,18 @@ public class HabitScoreView extends ScrollableDataView
private RectF rect, prevRect; private RectF rect, prevRect;
private int baseSize; private int baseSize;
private int columnWidth;
private int columnHeight;
private int nColumns;
private int[] colors; private int[] colors;
private int[] scores; private int[] scores;
private int primaryColor; private int primaryColor;
private boolean isBackgroundTransparent;
public HabitScoreView(Context context, AttributeSet attrs) public HabitScoreView(Context context, AttributeSet attrs)
{ {
super(context, attrs); super(context, attrs);
this.baseSize = (int) context.getResources().getDimension(R.dimen.small_square_size);
this.primaryColor = ColorHelper.palette[7]; this.primaryColor = ColorHelper.palette[7];
init(); init();
} }
@ -69,7 +78,6 @@ public class HabitScoreView extends ScrollableDataView
private void init() private void init()
{ {
createPaints(); createPaints();
setDimensions();
createColors(); createColors();
dfMonth = new SimpleDateFormat("MMM", Locale.getDefault()); dfMonth = new SimpleDateFormat("MMM", Locale.getDefault());
@ -79,15 +87,6 @@ public class HabitScoreView extends ScrollableDataView
prevRect = new RectF(); prevRect = new RectF();
} }
private void setDimensions()
{
this.columnWidth = baseSize;
columnHeight = 8 * baseSize;
headerHeight = baseSize;
footerHeight = baseSize;
em = pText.getFontSpacing();
}
private void createColors() private void createColors()
{ {
colors = new int[4]; colors = new int[4];
@ -98,24 +97,54 @@ public class HabitScoreView extends ScrollableDataView
colors[2] = ColorHelper.mixColors(colors[0], colors[3], 0.33f); colors[2] = ColorHelper.mixColors(colors[0], colors[3], 0.33f);
} }
private void createPaints() protected void createPaints()
{ {
pText = new Paint(); pText = new Paint();
pText.setColor(Color.LTGRAY); pText.setColor(Color.argb(64, 0, 0, 0));
pText.setTextAlign(Paint.Align.LEFT); pText.setTextAlign(Paint.Align.LEFT);
pText.setTextSize(baseSize * 0.5f);
pText.setAntiAlias(true); pText.setAntiAlias(true);
pGraph = new Paint(); pGraph = new Paint();
pGraph.setTextAlign(Paint.Align.CENTER); pGraph.setTextAlign(Paint.Align.CENTER);
pGraph.setTextSize(baseSize * 0.5f);
pGraph.setAntiAlias(true); pGraph.setAntiAlias(true);
pGraph.setStrokeWidth(baseSize * 0.1f);
pGrid = new Paint(); pGrid = new Paint();
pGrid.setColor(Color.LTGRAY); pGrid.setColor(Color.argb(64, 0, 0, 0));
pGrid.setAntiAlias(true); 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); pGrid.setStrokeWidth(baseSize * 0.05f);
em = pText.getFontSpacing();
} }
protected void fetchData() protected void fetchData()
@ -157,7 +186,6 @@ public class HabitScoreView extends ScrollableDataView
float lineHeight = pText.getFontSpacing(); float lineHeight = pText.getFontSpacing();
rect.set(0, 0, nColumns * columnWidth, columnHeight); rect.set(0, 0, nColumns * columnWidth, columnHeight);
rect.offset(0, headerHeight);
drawGrid(canvas, rect); drawGrid(canvas, rect);
String previousMonth = ""; String previousMonth = "";
@ -167,7 +195,7 @@ public class HabitScoreView extends ScrollableDataView
long currentDate = DateHelper.getStartOfToday(); 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; currentDate -= 7 * DateHelper.millisecondsInOneDay;
for (int k = 0; k < nColumns; k++) for (int k = 0; k < nColumns; k++)
@ -176,15 +204,14 @@ public class HabitScoreView extends ScrollableDataView
String day = dfDay.format(currentDate); String day = dfDay.format(currentDate);
int score = 0; int score = 0;
int offset = nColumns - k - 1 + dataOffset; int offset = nColumns - k - 1 + getDataOffset();
if(offset < scores.length) score = scores[offset]; if(offset < scores.length) score = scores[offset];
double sRelative = ((double) score) / Habit.MAX_SCORE; double sRelative = ((double) score) / Habit.MAX_SCORE;
int height = (int) (columnHeight * sRelative); int height = (int) (columnHeight * sRelative);
rect.set(0, 0, columnWidth, columnWidth); rect.set(0, 0, baseSize, baseSize);
rect.offset(k * columnWidth, rect.offset(k * columnWidth, columnHeight - height - columnWidth / 2);
headerHeight + columnHeight - height - columnWidth / 2);
if (!prevRect.isEmpty()) if (!prevRect.isEmpty())
{ {
@ -197,7 +224,7 @@ public class HabitScoreView extends ScrollableDataView
prevRect.set(rect); prevRect.set(rect);
rect.set(0, 0, columnWidth, columnHeight); rect.set(0, 0, columnWidth, columnHeight);
rect.offset(k * columnWidth, headerHeight); rect.offset(k * columnWidth, 0);
if (!month.equals(previousMonth)) if (!month.equals(previousMonth))
canvas.drawText(month, rect.centerX(), rect.bottom + lineHeight * 1.2f, pText); canvas.drawText(month, rect.centerX(), rect.bottom + lineHeight * 1.2f, pText);
else else
@ -213,7 +240,7 @@ public class HabitScoreView extends ScrollableDataView
int nRows = 5; int nRows = 5;
float rowHeight = rGrid.height() / nRows; 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++) for (int i = 0; i < nRows; i++)
{ {
canvas.drawText(String.format("%d%%", (100 - i * 100 / nRows)), rGrid.left + 0.5f * em, 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) private void drawMarker(Canvas canvas, RectF rect)
{ {
rect.inset(columnWidth * 0.15f, columnWidth * 0.15f); rect.inset(columnWidth * 0.15f, columnWidth * 0.15f);
pGraph.setColor(Color.WHITE); setModeOrColor(pGraph, XFERMODE_CLEAR, Color.WHITE);
canvas.drawOval(rect, pGraph); canvas.drawOval(rect, pGraph);
rect.inset(columnWidth * 0.1f, columnWidth * 0.1f); rect.inset(columnWidth * 0.1f, columnWidth * 0.1f);
pGraph.setColor(primaryColor); setModeOrColor(pGraph, XFERMODE_SRC, primaryColor);
canvas.drawOval(rect, pGraph); canvas.drawOval(rect, pGraph);
rect.inset(columnWidth * 0.1f, columnWidth * 0.1f); rect.inset(columnWidth * 0.1f, columnWidth * 0.1f);
pGraph.setColor(Color.WHITE); setModeOrColor(pGraph, XFERMODE_CLEAR, Color.WHITE);
canvas.drawOval(rect, pGraph); 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);
} }
} }

@ -43,6 +43,11 @@ public class HabitStreakView extends ScrollableDataView
private long[] endTimes; private long[] endTimes;
private long[] lengths; private long[] lengths;
private int columnWidth;
private int columnHeight;
private int headerHeight;
private int nColumns;
private long maxStreakLength; private long maxStreakLength;
private int[] colors; private int[] colors;
private SimpleDateFormat dfMonth; private SimpleDateFormat dfMonth;
@ -53,7 +58,6 @@ public class HabitStreakView extends ScrollableDataView
public HabitStreakView(Context context, AttributeSet attrs) public HabitStreakView(Context context, AttributeSet attrs)
{ {
super(context, attrs); super(context, attrs);
this.baseSize = (int) context.getResources().getDimension(R.dimen.small_square_size);
this.primaryColor = ColorHelper.palette[7]; this.primaryColor = ColorHelper.palette[7];
init(); init();
} }
@ -62,6 +66,7 @@ public class HabitStreakView extends ScrollableDataView
{ {
this.habit = habit; this.habit = habit;
this.primaryColor = habit.color; this.primaryColor = habit.color;
createColors(); createColors();
fetchData(); fetchData();
postInvalidate(); postInvalidate();
@ -69,7 +74,6 @@ public class HabitStreakView extends ScrollableDataView
private void init() private void init()
{ {
setDimensions(baseSize);
createPaints(); createPaints();
createColors(); createColors();
@ -77,34 +81,54 @@ public class HabitStreakView extends ScrollableDataView
rect = new Rect(); 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; columnHeight = 8 * baseSize;
headerHeight = baseSize; headerHeight = baseSize;
footerHeight = baseSize; nColumns = width / baseSize - 1;
pText.setTextSize(baseSize * 0.5f);
pBar.setTextSize(baseSize * 0.5f);
} }
private void createColors() private void createColors()
{ {
colors = new int[4]; colors = new int[4];
colors[0] = Color.rgb(230, 230, 230);
colors[3] = primaryColor; colors[3] = primaryColor;
colors[1] = ColorHelper.mixColors(colors[0], colors[3], 0.66f); colors[1] = Color.argb(80, Color.red(primaryColor), Color.green(primaryColor), Color.blue(
colors[2] = ColorHelper.mixColors(colors[0], colors[3], 0.33f); 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 = new Paint();
pText.setColor(Color.LTGRAY); pText.setColor(Color.argb(64, 0, 0, 0));
pText.setTextAlign(Paint.Align.CENTER); pText.setTextAlign(Paint.Align.CENTER);
pText.setTextSize(columnWidth * 0.5f);
pText.setAntiAlias(true); pText.setAntiAlias(true);
pBar = new Paint(); pBar = new Paint();
pBar.setTextAlign(Paint.Align.CENTER); pBar.setTextAlign(Paint.Align.CENTER);
pBar.setTextSize(columnWidth * 0.5f);
pBar.setAntiAlias(true); pBar.setAntiAlias(true);
} }
@ -173,7 +197,7 @@ public class HabitStreakView extends ScrollableDataView
float barHeaderOffset = lineHeight * 0.4f; float barHeaderOffset = lineHeight * 0.4f;
int nStreaks = startTimes.length; int nStreaks = startTimes.length;
int start = nStreaks - nColumns - dataOffset; int start = nStreaks - nColumns - getDataOffset();
String previousMonth = ""; String previousMonth = "";

@ -31,10 +31,8 @@ public abstract class ScrollableDataView extends View implements GestureDetector
ValueAnimator.AnimatorUpdateListener ValueAnimator.AnimatorUpdateListener
{ {
protected int dataOffset; private int dataOffset;
protected int nColumns; private int scrollerBucketSize;
protected int columnWidth, columnHeight;
protected int headerHeight, footerHeight;
private GestureDetector detector; private GestureDetector detector;
private Scroller scroller; private Scroller scroller;
@ -68,21 +66,6 @@ public abstract class ScrollableDataView extends View implements GestureDetector
return detector.onTouchEvent(event); 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 @Override
public boolean onDown(MotionEvent e) public boolean onDown(MotionEvent e)
{ {
@ -104,13 +87,17 @@ public abstract class ScrollableDataView extends View implements GestureDetector
@Override @Override
public boolean onScroll(MotionEvent e1, MotionEvent e2, float dx, float dy) public boolean onScroll(MotionEvent e1, MotionEvent e2, float dx, float dy)
{ {
if(scrollerBucketSize == 0)
return false;
if(Math.abs(dx) > Math.abs(dy)) if(Math.abs(dx) > Math.abs(dy))
getParent().requestDisallowInterceptTouchEvent(true); getParent().requestDisallowInterceptTouchEvent(true);
scroller.startScroll(scroller.getCurrX(), scroller.getCurrY(), (int) -dx, (int) dy, 0); scroller.startScroll(scroller.getCurrX(), scroller.getCurrY(), (int) -dx, (int) dy, 0);
scroller.computeScrollOffset(); scroller.computeScrollOffset();
dataOffset = Math.max(0, scroller.getCurrX() / columnWidth); dataOffset = Math.max(0, scroller.getCurrX() / scrollerBucketSize);
postInvalidate(); postInvalidate();
return true; return true;
} }
@ -139,7 +126,7 @@ public abstract class ScrollableDataView extends View implements GestureDetector
if (!scroller.isFinished()) if (!scroller.isFinished())
{ {
scroller.computeScrollOffset(); scroller.computeScrollOffset();
dataOffset = Math.max(0, scroller.getCurrX() / columnWidth); dataOffset = Math.max(0, scroller.getCurrX() / scrollerBucketSize);
postInvalidate(); postInvalidate();
} }
else else
@ -147,4 +134,14 @@ public abstract class ScrollableDataView extends View implements GestureDetector
scrollAnimator.cancel(); scrollAnimator.cancel();
} }
} }
public int getDataOffset()
{
return dataOffset;
}
public void setScrollerBucketSize(int scrollerBucketSize)
{
this.scrollerBucketSize = scrollerBucketSize;
}
} }

@ -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 <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.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);
}
}

@ -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 <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, 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;
}
}

@ -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,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 <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.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;
}
}

@ -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 <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.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;
}
}

@ -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 <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.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;
}
}

@ -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="#3fffffff" />
<corners android:radius="10dp" />
</shape>

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

@ -11,7 +11,6 @@
tools:context="org.isoron.uhabits.ShowHabitActivity"> tools:context="org.isoron.uhabits.ShowHabitActivity">
<LinearLayout <LinearLayout
android:id="@+id/llOverview"
style="@style/cardStyle" style="@style/cardStyle"
android:gravity="center"> android:gravity="center">
@ -28,10 +27,7 @@
</LinearLayout> </LinearLayout>
<LinearLayout <LinearLayout style="@style/cardStyle">
android:id="@+id/llStrength"
style="@style/cardStyle"
android:gravity="center">
<TextView <TextView
android:id="@+id/tvStrength" android:id="@+id/tvStrength"
@ -41,7 +37,7 @@
<org.isoron.uhabits.views.HabitScoreView <org.isoron.uhabits.views.HabitScoreView
android:id="@+id/scoreView" android:id="@+id/scoreView"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent"/> android:layout_height="200dp"/>
</LinearLayout> </LinearLayout>
@ -61,7 +57,7 @@
<org.isoron.uhabits.views.HabitHistoryView <org.isoron.uhabits.views.HabitHistoryView
android:id="@+id/historyView" android:id="@+id/historyView"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent"/> android:layout_height="180dp"/>
</LinearLayout> </LinearLayout>
@ -81,7 +77,7 @@
<org.isoron.uhabits.views.HabitStreakView <org.isoron.uhabits.views.HabitStreakView
android:id="@+id/streakView" android:id="@+id/streakView"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent"/> android:layout_height="200dp"/>
</LinearLayout> </LinearLayout>
</LinearLayout> </LinearLayout>
</ScrollView> </ScrollView>

@ -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="4dp">
<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,30 @@
<?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:paddingLeft="10dp"
android:paddingRight="0dp">
<TextView
android:id="@+id/label"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center"
android:text="Wake up early"
android:textColor="#3f000000"/>
<ImageView
android:id="@+id/imageView"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1"
android:adjustViewBounds="true"
/>
</LinearLayout>

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

@ -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="@mipmap/ic_small_widget_preview"
android:resizeMode="none"
android:updatePeriodMillis="3600000"
android:configure="org.isoron.uhabits.widgets.HabitPickerDialog"
android:widgetCategory="home_screen">
</appwidget-provider>

@ -0,0 +1,15 @@
<?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="@mipmap/ic_small_widget_preview"
android:resizeMode="vertical|horizontal"
android:updatePeriodMillis="3600000"
android:configure="org.isoron.uhabits.widgets.HabitPickerDialog"
android:widgetCategory="home_screen">
</appwidget-provider>

@ -0,0 +1,15 @@
<?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="@mipmap/ic_small_widget_preview"
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="@mipmap/ic_small_widget_preview"
android:resizeMode="vertical|horizontal"
android:updatePeriodMillis="3600000"
android:configure="org.isoron.uhabits.widgets.HabitPickerDialog"
android:widgetCategory="home_screen">
</appwidget-provider>
Loading…
Cancel
Save