From d2682358c2e50dd506d06f1da582a77cb906b006 Mon Sep 17 00:00:00 2001 From: Alinson Xavier Date: Fri, 26 Feb 2016 07:44:34 -0500 Subject: [PATCH] Allow custom views to be rendered on the layout editor --- .../java/org/isoron/helpers/DialogHelper.java | 17 ++- .../uhabits/fragments/ShowHabitFragment.java | 31 ++--- .../uhabits/views/HabitHistoryView.java | 62 ++++++++-- .../isoron/uhabits/views/HabitScoreView.java | 116 +++++++++++++----- .../isoron/uhabits/views/HabitStreakView.java | 95 ++++++++++++-- .../org/isoron/uhabits/views/RingView.java | 48 ++++++-- .../uhabits/views/ScrollableDataView.java | 11 ++ app/src/main/res/layout/show_habit.xml | 23 ++++ .../main/res/layout/show_habit_activity.xml | 7 +- 9 files changed, 321 insertions(+), 89 deletions(-) diff --git a/app/src/main/java/org/isoron/helpers/DialogHelper.java b/app/src/main/java/org/isoron/helpers/DialogHelper.java index 04e99d65d..a8215f846 100644 --- a/app/src/main/java/org/isoron/helpers/DialogHelper.java +++ b/app/src/main/java/org/isoron/helpers/DialogHelper.java @@ -17,21 +17,18 @@ package org.isoron.helpers; import android.content.Context; -import android.content.DialogInterface; -import android.content.DialogInterface.OnClickListener; import android.content.SharedPreferences; import android.graphics.Typeface; import android.os.Vibrator; import android.preference.PreferenceManager; +import android.util.AttributeSet; import android.view.View; import android.view.inputmethod.InputMethodManager; -import android.widget.TextView; - -import org.isoron.uhabits.R; public abstract class DialogHelper { + public static final String ISORON_NAMESPACE = "http://isoron.org/android"; private static Typeface fontawesome; public interface OnSavedListener @@ -65,4 +62,14 @@ public abstract class DialogHelper SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context); return prefs.getInt("launch_count", 0); } + + public static String getAttribute(Context context, AttributeSet attrs, String name) + { + int resId = attrs.getAttributeResourceValue(ISORON_NAMESPACE, name, 0); + + if(resId != 0) + return context.getResources().getString(resId); + else + return attrs.getAttributeValue(ISORON_NAMESPACE, name); + } } diff --git a/app/src/main/java/org/isoron/uhabits/fragments/ShowHabitFragment.java b/app/src/main/java/org/isoron/uhabits/fragments/ShowHabitFragment.java index 90b586163..de2cc311d 100644 --- a/app/src/main/java/org/isoron/uhabits/fragments/ShowHabitFragment.java +++ b/app/src/main/java/org/isoron/uhabits/fragments/ShowHabitFragment.java @@ -25,15 +25,14 @@ import android.view.MenuInflater; import android.view.MenuItem; import android.view.View; import android.view.ViewGroup; -import android.widget.LinearLayout; import android.widget.TextView; import org.isoron.helpers.ColorHelper; import org.isoron.helpers.Command; import org.isoron.helpers.DialogHelper; import org.isoron.uhabits.R; -import org.isoron.uhabits.helpers.ReminderHelper; import org.isoron.uhabits.ShowHabitActivity; +import org.isoron.uhabits.helpers.ReminderHelper; import org.isoron.uhabits.models.Habit; import org.isoron.uhabits.views.HabitHistoryView; import org.isoron.uhabits.views.HabitScoreView; @@ -71,29 +70,21 @@ public class ShowHabitFragment extends Fragment implements DialogHelper.OnSavedL TextView tvOverview = (TextView) view.findViewById(R.id.tvOverview); TextView tvStrength = (TextView) view.findViewById(R.id.tvStrength); TextView tvStreaks = (TextView) view.findViewById(R.id.tvStreaks); + RingView scoreRing = (RingView) view.findViewById(R.id.scoreRing); + HabitStreakView streakView = (HabitStreakView) view.findViewById(R.id.streakView); + HabitScoreView scoreView = (HabitScoreView) view.findViewById(R.id.scoreView); + HabitHistoryView historyView = (HabitHistoryView) view.findViewById(R.id.historyView); + tvHistory.setTextColor(habit.color); tvOverview.setTextColor(habit.color); tvStrength.setTextColor(habit.color); tvStreaks.setTextColor(habit.color); - LinearLayout llOverview = (LinearLayout) view.findViewById(R.id.llOverview); - llOverview.addView(new RingView(activity, - (int) activity.getResources().getDimension(R.dimen.small_square_size) * 4, - habit.color, ((float) habit.getScore() / Habit.MAX_SCORE), activity.getString(R.string.habit_strength))); - - LinearLayout llStrength = (LinearLayout) view.findViewById(R.id.llStrength); - llStrength.addView(new HabitScoreView(activity, habit, - (int) activity.getResources().getDimension(R.dimen.small_square_size))); - - LinearLayout llHistory = (LinearLayout) view.findViewById(R.id.llHistory); - HabitHistoryView hhv = new HabitHistoryView(activity, habit, - (int) activity.getResources().getDimension(R.dimen.small_square_size)); - llHistory.addView(hhv); - - LinearLayout llStreaks = (LinearLayout) view.findViewById(R.id.llStreaks); - HabitStreakView hsv = new HabitStreakView(activity, habit, - (int) activity.getResources().getDimension(R.dimen.small_square_size)); - llStreaks.addView(hsv); + scoreRing.setColor(habit.color); + scoreRing.setPercentage((float) habit.getScore() / Habit.MAX_SCORE); + streakView.setHabit(habit); + scoreView.setHabit(habit); + historyView.setHabit(habit); setHasOptionsMenu(true); return view; 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 c64c1d73d..3bcbabe09 100644 --- a/app/src/main/java/org/isoron/uhabits/views/HabitHistoryView.java +++ b/app/src/main/java/org/isoron/uhabits/views/HabitHistoryView.java @@ -22,19 +22,21 @@ import android.graphics.Color; import android.graphics.Paint; import android.graphics.Paint.Align; import android.graphics.Rect; +import android.util.AttributeSet; import org.isoron.helpers.ColorHelper; import org.isoron.helpers.DateHelper; +import org.isoron.uhabits.R; import org.isoron.uhabits.models.Habit; import java.text.SimpleDateFormat; import java.util.Calendar; import java.util.GregorianCalendar; import java.util.Locale; +import java.util.Random; public class HabitHistoryView extends ScrollableDataView { - private Habit habit; private int[] checkmarks; private Paint pSquareBg, pSquareFg, pTextHeader; @@ -52,13 +54,29 @@ 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(); + } - public HabitHistoryView(Context context, Habit habit, int baseSize) + public void setHabit(Habit habit) { - super(context); this.habit = habit; + this.primaryColor = habit.color; + createColors(); + fetchData(); + postInvalidate(); + } - setDimensions(baseSize); + private void init() + { + setDimensions(this.baseSize); createPaints(); createColors(); @@ -90,7 +108,6 @@ public class HabitHistoryView extends ScrollableDataView private void createColors() { - int primaryColor = habit.color; int primaryColorBright = ColorHelper.mixColors(primaryColor, Color.WHITE, 0.5f); int grey = Color.rgb(230, 230, 230); @@ -116,7 +133,7 @@ public class HabitHistoryView extends ScrollableDataView pTextHeader.setAntiAlias(true); pSquareBg = new Paint(); - pSquareBg.setColor(habit.color); + pSquareBg.setColor(primaryColor); pSquareFg = new Paint(); pSquareFg.setColor(Color.WHITE); @@ -130,10 +147,41 @@ public class HabitHistoryView extends ScrollableDataView protected void fetchData() { - checkmarks = habit.getAllCheckmarks(); + if(isInEditMode()) + generateRandomData(); + else + { + if(habit == null) + { + checkmarks = new int[0]; + return; + } + + checkmarks = habit.getAllCheckmarks(); + } + updateDate(); } + private void generateRandomData() + { + Random random = new Random(); + checkmarks = new int[100]; + + for(int i = 0; i < 100; i++) + if(random.nextFloat() < 0.3) checkmarks[i] = 2; + + for(int i = 0; i < 100 - 7; i++) + { + int count = 0; + for (int j = 0; j < 7; j++) + if(checkmarks[i + j] != 0) + count++; + + if(count >= 3) checkmarks[i] = Math.max(checkmarks[i], 1); + } + } + private String previousMonth; private String previousYear; private boolean justPrintedYear; 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 2be9c0698..579cee894 100644 --- a/app/src/main/java/org/isoron/uhabits/views/HabitScoreView.java +++ b/app/src/main/java/org/isoron/uhabits/views/HabitScoreView.java @@ -21,76 +21,132 @@ import android.graphics.Canvas; import android.graphics.Color; import android.graphics.Paint; 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; import java.util.Locale; +import java.util.Random; public class HabitScoreView extends ScrollableDataView { public static final int BUCKET_SIZE = 7; - private final Paint pGrid; - private final float em; + private Paint pGrid; + private float em; private Habit habit; private SimpleDateFormat dfMonth; private SimpleDateFormat dfDay; private Paint pText, pGraph; private RectF rect, prevRect; + private int baseSize; private int[] colors; private int[] scores; + private int primaryColor; - public HabitScoreView(Context context, Habit habit, int columnWidth) + 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(); + } + + public void setHabit(Habit habit) { - super(context); this.habit = habit; + this.primaryColor = habit.color; + fetchData(); + postInvalidate(); + } + + private void init() + { + createPaints(); + setDimensions(); + createColors(); + + dfMonth = new SimpleDateFormat("MMM", Locale.getDefault()); + dfDay = new SimpleDateFormat("d", Locale.getDefault()); + + rect = new RectF(); + 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]; + + 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); + } + private void createPaints() + { pText = new Paint(); pText.setColor(Color.LTGRAY); pText.setTextAlign(Paint.Align.LEFT); - pText.setTextSize(columnWidth * 0.5f); + pText.setTextSize(baseSize * 0.5f); pText.setAntiAlias(true); pGraph = new Paint(); pGraph.setTextAlign(Paint.Align.CENTER); - pGraph.setTextSize(columnWidth * 0.5f); + pGraph.setTextSize(baseSize * 0.5f); pGraph.setAntiAlias(true); - pGraph.setStrokeWidth(columnWidth * 0.1f); + pGraph.setStrokeWidth(baseSize * 0.1f); pGrid = new Paint(); pGrid.setColor(Color.LTGRAY); pGrid.setAntiAlias(true); - pGrid.setStrokeWidth(columnWidth * 0.05f); - - this.columnWidth = columnWidth; - columnHeight = 8 * columnWidth; - headerHeight = columnWidth; - footerHeight = columnWidth; - - em = pText.getFontSpacing(); - - colors = new int[4]; + pGrid.setStrokeWidth(baseSize * 0.05f); + } - colors[0] = Color.rgb(230, 230, 230); - colors[3] = habit.color; - colors[1] = ColorHelper.mixColors(colors[0], colors[3], 0.66f); - colors[2] = ColorHelper.mixColors(colors[0], colors[3], 0.33f); + protected void fetchData() + { + if(isInEditMode()) + generateRandomData(); + else + { + if (habit == null) + { + scores = new int[0]; + return; + } - dfMonth = new SimpleDateFormat("MMM", Locale.getDefault()); - dfDay = new SimpleDateFormat("d", Locale.getDefault()); + scores = habit.getAllScores(BUCKET_SIZE * DateHelper.millisecondsInOneDay); + } - rect = new RectF(); - prevRect = new RectF(); } - protected void fetchData() + private void generateRandomData() { - scores = habit.getAllScores(BUCKET_SIZE * DateHelper.millisecondsInOneDay); + Random random = new Random(); + scores = new int[100]; + scores[0] = Habit.MAX_SCORE / 2; + + for(int i = 1; i < 100; i++) + { + int step = Habit.MAX_SCORE / 10; + scores[i] = scores[i - 1] + random.nextInt(step * 2) - step; + scores[i] = Math.max(0, Math.min(Habit.MAX_SCORE, scores[i])); + } } @Override @@ -106,7 +162,7 @@ public class HabitScoreView extends ScrollableDataView String previousMonth = ""; - pGraph.setColor(habit.color); + pGraph.setColor(primaryColor); prevRect.setEmpty(); long currentDate = DateHelper.getStartOfToday(); @@ -171,7 +227,7 @@ public class HabitScoreView extends ScrollableDataView private void drawLine(Canvas canvas, RectF rectFrom, RectF rectTo) { - pGraph.setColor(habit.color); + pGraph.setColor(primaryColor); canvas.drawLine(rectFrom.centerX(), rectFrom.centerY(), rectTo.centerX(), rectTo.centerY(), pGraph); } @@ -183,7 +239,7 @@ public class HabitScoreView extends ScrollableDataView canvas.drawOval(rect, pGraph); rect.inset(columnWidth * 0.1f, columnWidth * 0.1f); - pGraph.setColor(habit.color); + pGraph.setColor(primaryColor); canvas.drawOval(rect, pGraph); rect.inset(columnWidth * 0.1f, columnWidth * 0.1f); 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 5aa58f4c0..6f8223f41 100644 --- a/app/src/main/java/org/isoron/uhabits/views/HabitStreakView.java +++ b/app/src/main/java/org/isoron/uhabits/views/HabitStreakView.java @@ -21,31 +21,55 @@ import android.graphics.Canvas; import android.graphics.Color; import android.graphics.Paint; import android.graphics.Rect; +import android.util.AttributeSet; import org.isoron.helpers.ColorHelper; +import org.isoron.helpers.DateHelper; +import org.isoron.uhabits.R; import org.isoron.uhabits.models.Habit; import org.isoron.uhabits.models.Streak; import java.text.SimpleDateFormat; import java.util.List; import java.util.Locale; +import java.util.Random; public class HabitStreakView extends ScrollableDataView { private Habit habit; private Paint pText, pBar; - private List streaks; + + private long[] startTimes; + private long[] endTimes; + private long[] lengths; + private long maxStreakLength; private int[] colors; private SimpleDateFormat dfMonth; private Rect rect; + private int baseSize; + private int primaryColor; + + 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(); + } - public HabitStreakView(Context context, Habit habit, int columnWidth) + public void setHabit(Habit habit) { - super(context); this.habit = habit; + this.primaryColor = habit.color; + createColors(); + fetchData(); + postInvalidate(); + } - setDimensions(columnWidth); + private void init() + { + setDimensions(baseSize); createPaints(); createColors(); @@ -65,7 +89,7 @@ public class HabitStreakView extends ScrollableDataView { colors = new int[4]; colors[0] = Color.rgb(230, 230, 230); - colors[3] = habit.color; + colors[3] = primaryColor; colors[1] = ColorHelper.mixColors(colors[0], colors[3], 0.66f); colors[2] = ColorHelper.mixColors(colors[0], colors[3], 0.33f); } @@ -86,12 +110,59 @@ public class HabitStreakView extends ScrollableDataView protected void fetchData() { - streaks = habit.getStreaks(); - - for (Streak s : streaks) - maxStreakLength = Math.max(maxStreakLength, s.length); + if(isInEditMode()) + generateRandomData(); + else + { + if(habit == null) + { + startTimes = endTimes = lengths = new long[0]; + return; + } + + List streaks = habit.getStreaks(); + int size = streaks.size(); + + startTimes = new long[size]; + endTimes = new long[size]; + lengths = new long[size]; + + int k = 0; + for (Streak s : streaks) + { + startTimes[k] = s.start; + endTimes[k] = s.end; + lengths[k] = s.length; + k++; + + maxStreakLength = Math.max(maxStreakLength, s.length); + } + } } + private void generateRandomData() + { + int size = 30; + + startTimes = new long[size]; + endTimes = new long[size]; + lengths = new long[size]; + + Random random = new Random(); + Long date = DateHelper.getStartOfToday(); + + for(int i = 0; i < size; i++) + { + int l = (int) Math.pow(2, random.nextFloat() * 5 + 1); + + endTimes[i] = date; + date -= l * DateHelper.millisecondsInOneDay; + lengths[i] = (long) l; + startTimes[i] = date; + + maxStreakLength = Math.max(maxStreakLength, l); + } + } @Override protected void onDraw(Canvas canvas) @@ -101,7 +172,7 @@ public class HabitStreakView extends ScrollableDataView float lineHeight = pText.getFontSpacing(); float barHeaderOffset = lineHeight * 0.4f; - int nStreaks = streaks.size(); + int nStreaks = startTimes.length; int start = nStreaks - nColumns - dataOffset; String previousMonth = ""; @@ -109,9 +180,9 @@ public class HabitStreakView extends ScrollableDataView for (int offset = 0; offset < nColumns && start + offset < nStreaks; offset++) { if(start + offset < 0) continue; - String month = dfMonth.format(streaks.get(start + offset).start); + String month = dfMonth.format(startTimes[start + offset]); - long l = streaks.get(offset + start).length; + long l = lengths[offset + start]; double lRelative = ((double) l) / maxStreakLength; pBar.setColor(colors[(int) Math.floor(lRelative * 3)]); diff --git a/app/src/main/java/org/isoron/uhabits/views/RingView.java b/app/src/main/java/org/isoron/uhabits/views/RingView.java index 4b49f0ff8..e897fb5fb 100644 --- a/app/src/main/java/org/isoron/uhabits/views/RingView.java +++ b/app/src/main/java/org/isoron/uhabits/views/RingView.java @@ -21,34 +21,57 @@ import android.graphics.Canvas; import android.graphics.Color; import android.graphics.Paint; import android.graphics.RectF; +import android.util.AttributeSet; import android.view.View; +import org.isoron.helpers.ColorHelper; +import org.isoron.helpers.DialogHelper; +import org.isoron.uhabits.R; + public class RingView extends View { private int size; private int color; - private float perc; + private float percentage; private Paint pRing; private float lineHeight; private String label; private RectF rect; - public RingView(Context context, int size, int color, float perc, String label) + public RingView(Context context, AttributeSet attrs) + { + super(context, attrs); + + this.size = (int) context.getResources().getDimension(R.dimen.small_square_size) * 4; + this.label = DialogHelper.getAttribute(context, attrs, "label"); + this.color = ColorHelper.palette[7]; + this.percentage = 0.75f; + init(); + } + + public void setColor(int color) { - super(context); - this.size = size; this.color = color; - this.perc = perc; + pRing.setColor(color); + postInvalidate(); + } + + public void setPercentage(float percentage) + { + this.percentage = percentage; + postInvalidate(); + } + private void init() + { pRing = new Paint(); - pRing.setColor(color); pRing.setAntiAlias(true); + pRing.setColor(color); pRing.setTextAlign(Paint.Align.CENTER); - + pRing.setTextSize(size * 0.2f); + lineHeight = pRing.getFontSpacing(); rect = new RectF(); - - this.label = label; } @Override @@ -66,10 +89,10 @@ public class RingView extends View pRing.setColor(color); rect.set(0, 0, size, size); - canvas.drawArc(rect, -90, 360 * perc, true, pRing); + canvas.drawArc(rect, -90, 360 * percentage, true, pRing); pRing.setColor(Color.rgb(230, 230, 230)); - canvas.drawArc(rect, 360 * perc - 90 + 2, 360 * (1 - perc) - 4, true, pRing); + canvas.drawArc(rect, 360 * percentage - 90 + 2, 360 * (1 - percentage) - 4, true, pRing); pRing.setColor(Color.WHITE); rect.inset(thickness, thickness); @@ -77,8 +100,7 @@ public class RingView extends View pRing.setColor(Color.GRAY); pRing.setTextSize(size * 0.2f); - lineHeight = pRing.getFontSpacing(); - canvas.drawText(String.format("%.0f%%", perc * 100), rect.centerX(), + canvas.drawText(String.format("%.0f%%", percentage * 100), rect.centerX(), rect.centerY() + lineHeight / 3, pRing); pRing.setTextSize(size * 0.15f); 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 cdd7bf99e..8c0ad9dab 100644 --- a/app/src/main/java/org/isoron/uhabits/views/ScrollableDataView.java +++ b/app/src/main/java/org/isoron/uhabits/views/ScrollableDataView.java @@ -21,6 +21,7 @@ package org.isoron.uhabits.views; import android.animation.ValueAnimator; import android.content.Context; +import android.util.AttributeSet; import android.view.GestureDetector; import android.view.MotionEvent; import android.view.View; @@ -42,7 +43,17 @@ public abstract class ScrollableDataView extends View implements GestureDetector public ScrollableDataView(Context context) { super(context); + init(context); + } + public ScrollableDataView(Context context, AttributeSet attrs) + { + super(context, attrs); + init(context); + } + + private void init(Context context) + { detector = new GestureDetector(context, this); scroller = new Scroller(context, null, true); scrollAnimator = ValueAnimator.ofFloat(0, 1); diff --git a/app/src/main/res/layout/show_habit.xml b/app/src/main/res/layout/show_habit.xml index bca7927bf..820567e0b 100644 --- a/app/src/main/res/layout/show_habit.xml +++ b/app/src/main/res/layout/show_habit.xml @@ -1,5 +1,6 @@ + + + + @@ -45,6 +57,12 @@ android:layout_width="match_parent" android:layout_height="wrap_content" android:orientation="horizontal"/> + + + @@ -59,6 +77,11 @@ android:layout_width="match_parent" android:layout_height="wrap_content" android:orientation="horizontal"/> + + \ No newline at end of file diff --git a/app/src/main/res/layout/show_habit_activity.xml b/app/src/main/res/layout/show_habit_activity.xml index 281221efc..1c2bb74d2 100644 --- a/app/src/main/res/layout/show_habit_activity.xml +++ b/app/src/main/res/layout/show_habit_activity.xml @@ -4,12 +4,15 @@ android:layout_width="match_parent" android:layout_height="match_parent" tools:context="org.isoron.uhabits.ShowHabitActivity" - tools:ignore="MergeRootFrame" > + tools:ignore="MergeRootFrame" + tools:menu="show_habit_activity_menu,show_habit_fragment_menu"> + android:layout_height="match_parent" + tools:layout="@layout/show_habit" + android:layout_gravity="center"/>