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