Create new TargetCard and TargetChart

pull/605/head
Alinson S. Xavier 5 years ago
parent 6ec9d51a1e
commit a6060f468d

@ -0,0 +1,217 @@
/*
* Copyright (C) 2016 Álinson Santos Xavier <isoron@gmail.com>
*
* This file is part of Loop Habit Tracker.
*
* Loop Habit Tracker 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.
*
* Loop Habit Tracker 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.activities.common.views;
import android.content.*;
import android.graphics.*;
import android.util.*;
import android.view.*;
import org.isoron.androidbase.utils.*;
import org.isoron.uhabits.*;
import org.isoron.uhabits.activities.habits.list.views.*;
import java.util.*;
import static android.view.View.MeasureSpec.*;
import static org.isoron.androidbase.utils.InterfaceUtils.*;
public class TargetChart extends View
{
private Paint paint;
private int baseSize;
private int primaryColor;
private int mediumContrastTextColor;
private int highContrastReverseTextColor;
private int lowContrastTextColor;
private RectF rect = new RectF();
private RectF barRect = new RectF();
private List<Double> values = Collections.emptyList();
private List<String> labels = Collections.emptyList();
private List<Double> targets = Collections.emptyList();
private float maxLabelSize;
private float tinyTextSize;
public TargetChart(Context context)
{
super(context);
init();
}
public TargetChart(Context context, AttributeSet attrs)
{
super(context, attrs);
init();
}
public void populateWithRandomData()
{
labels = new ArrayList<>();
values = new ArrayList<>();
targets = new ArrayList<>();
for (int i = 0; i < 5; i++) {
double percentage = new Random().nextDouble();
targets.add(new Random().nextDouble() * 1000.0);
values.add(targets.get(i) * percentage * 1.2);
labels.add(String.format(Locale.US, "Label %d", i + 1));
}
}
public void setColor(int color)
{
this.primaryColor = color;
postInvalidate();
}
@Override
protected void onDraw(Canvas canvas)
{
super.onDraw(canvas);
if (labels.size() == 0) return;
maxLabelSize = 0;
for (String label : labels) {
paint.setTextSize(tinyTextSize);
float len = paint.measureText(label);
maxLabelSize = Math.max(maxLabelSize, len);
}
rect.set(0, 0, getWidth(), baseSize);
for (int i = 0; i < labels.size(); i++) {
drawRow(canvas, i, rect);
rect.offset(0, baseSize);
}
}
@Override
protected void onMeasure(int widthSpec, int heightSpec)
{
int width = getSize(widthSpec);
int height = labels.size() * baseSize;
heightSpec = makeMeasureSpec(height, EXACTLY);
widthSpec = makeMeasureSpec(width, EXACTLY);
setMeasuredDimension(widthSpec, heightSpec);
}
private void drawRow(Canvas canvas, int row, RectF rect)
{
float padding = dpToPixels(getContext(), 4);
float round = dpToPixels(getContext(), 2);
float stop = maxLabelSize + padding * 2;
paint.setColor(mediumContrastTextColor);
// Draw label
paint.setTextSize(tinyTextSize);
paint.setTextAlign(Paint.Align.RIGHT);
float yTextAdjust = (paint.descent() + paint.ascent()) / 2.0f;
canvas.drawText(labels.get(row),
rect.left + stop - padding,
rect.centerY() - yTextAdjust,
paint);
// Draw background box
paint.setColor(lowContrastTextColor);
barRect.set(rect.left + stop + padding,
rect.top + baseSize * 0.05f,
rect.right - padding,
rect.bottom - baseSize * 0.05f);
canvas.drawRoundRect(barRect, round, round, paint);
float percentage = (float) (values.get(row) / targets.get(row));
percentage = Math.min(1.0f, percentage);
// Draw completed box
float completedWidth = percentage * barRect.width();
if (completedWidth > 0 && completedWidth < 2 * round) {
completedWidth = 2 * round;
}
float remainingWidth = barRect.width() - completedWidth;
paint.setColor(primaryColor);
barRect.set(barRect.left,
barRect.top,
barRect.left + completedWidth,
barRect.bottom);
canvas.drawRoundRect(barRect, round, round, paint);
// Draw values
paint.setColor(Color.WHITE);
paint.setTextSize(tinyTextSize);
paint.setTextAlign(Paint.Align.CENTER);
yTextAdjust = (paint.descent() + paint.ascent()) / 2.0f;
double remaining = targets.get(row) - values.get(row);
String completedText = NumberButtonViewKt.toShortString(values.get(row));
String remainingText = NumberButtonViewKt.toShortString(remaining);
if (completedWidth > paint.measureText(completedText) + 2 * padding) {
paint.setColor(highContrastReverseTextColor);
canvas.drawText(completedText,
barRect.centerX(),
barRect.centerY() - yTextAdjust,
paint);
}
if (remainingWidth > paint.measureText(remainingText) + 2 * padding) {
paint.setColor(mediumContrastTextColor);
barRect.set(rect.left + stop + padding + completedWidth,
barRect.top,
rect.right - padding,
barRect.bottom);
canvas.drawText(remainingText,
barRect.centerX(),
barRect.centerY() - yTextAdjust,
paint);
}
}
private void init()
{
paint = new Paint();
paint.setTextAlign(Paint.Align.CENTER);
paint.setAntiAlias(true);
StyledResources res = new StyledResources(getContext());
lowContrastTextColor = res.getColor(R.attr.lowContrastTextColor);
mediumContrastTextColor = res.getColor(R.attr.mediumContrastTextColor);
highContrastReverseTextColor = res.getColor(R.attr.highContrastReverseTextColor);
tinyTextSize = getDimension(getContext(), R.dimen.tinyTextSize);
baseSize = getResources().getDimensionPixelSize(R.dimen.baseSize);
}
public void setValues(List<Double> values)
{
this.values = values;
requestLayout();
}
public void setLabels(List<String> labels)
{
this.labels = labels;
requestLayout();
}
public void setTargets(List<Double> targets)
{
this.targets = targets;
requestLayout();
}
}

@ -21,6 +21,7 @@ package org.isoron.uhabits.activities.habits.show;
import android.content.*; import android.content.*;
import android.os.*; import android.os.*;
import android.view.*;
import android.widget.TextView; import android.widget.TextView;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
@ -74,6 +75,9 @@ public class ShowHabitRootView extends BaseRootView
@BindView(R.id.toolbar) @BindView(R.id.toolbar)
Toolbar toolbar; Toolbar toolbar;
@BindView(R.id.targetCard)
TargetCard targetCard;
@NonNull @NonNull
private Controller controller; private Controller controller;
@ -150,6 +154,13 @@ public class ShowHabitRootView extends BaseRootView
streakCard.setHabit(habit); streakCard.setHabit(habit);
frequencyCard.setHabit(habit); frequencyCard.setHabit(habit);
barCard.setHabit(habit); barCard.setHabit(habit);
targetCard.setHabit(habit);
if(habit.isNumerical()) {
overviewCard.setVisibility(View.GONE);
} else {
targetCard.setVisibility(View.GONE);
}
} }
public interface Controller extends HistoryCard.Controller public interface Controller extends HistoryCard.Controller

@ -0,0 +1,159 @@
/*
* Copyright (C) 2016 Álinson Santos Xavier <isoron@gmail.com>
*
* This file is part of Loop Habit Tracker.
*
* Loop Habit Tracker 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.
*
* Loop Habit Tracker 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.activities.habits.show.views;
import android.content.*;
import android.content.res.*;
import android.util.*;
import android.widget.*;
import org.isoron.uhabits.R;
import org.isoron.uhabits.*;
import org.isoron.uhabits.activities.common.views.*;
import org.isoron.uhabits.core.models.*;
import org.isoron.uhabits.core.preferences.*;
import org.isoron.uhabits.core.tasks.*;
import org.isoron.uhabits.core.utils.*;
import org.isoron.uhabits.utils.*;
import java.util.*;
import butterknife.*;
public class TargetCard extends HabitCard
{
@BindView(R.id.title)
TextView title;
@BindView(R.id.targetChart)
TargetChart targetChart;
int firstWeekday = Calendar.SATURDAY;
public TargetCard(Context context)
{
super(context);
init();
}
public TargetCard(Context context, AttributeSet attrs)
{
super(context, attrs);
init();
}
private void init()
{
inflate(getContext(), R.layout.show_habit_target, this);
ButterKnife.bind(this);
setOrientation(VERTICAL);
Context app = getContext().getApplicationContext();
if (app instanceof HabitsApplication) {
HabitsApplication habitsApp = (HabitsApplication) app;
Preferences prefs = habitsApp.getComponent().getPreferences();
firstWeekday = prefs.getFirstWeekday();
}
if (isInEditMode()) initEditMode();
}
private void initEditMode()
{
int color = PaletteUtils.getAndroidTestColor(1);
title.setTextColor(color);
targetChart.setColor(color);
targetChart.populateWithRandomData();
}
@Override
protected Task createRefreshTask()
{
return new RefreshTask();
}
private class RefreshTask extends CancelableTask
{
double todayValue;
double thisWeekValue;
double thisMonthValue;
double thisQuarterValue;
double thisYearValue;
@Override
public void doInBackground()
{
if (isCanceled()) return;
CheckmarkList checkmarks = getHabit().getCheckmarks();
todayValue = checkmarks.getTodayValue() / 1e3;
thisWeekValue = checkmarks.getThisWeekValue(firstWeekday) / 1e3;
thisMonthValue = checkmarks.getThisMonthValue() / 1e3;
thisQuarterValue = checkmarks.getThisQuarterValue() / 1e3;
thisYearValue = checkmarks.getThisYearValue() / 1e3;
}
@Override
public void onPostExecute()
{
if (isCanceled()) return;
Calendar cal = DateUtils.getStartOfTodayCalendar();
int daysInMonth = cal.getActualMaximum(Calendar.DAY_OF_MONTH);
int daysInQuarter = 91;
int daysInYear = cal.getActualMaximum(Calendar.DAY_OF_YEAR);
int den = getHabit().getFrequency().getDenominator();
double dailyTarget = getHabit().getTargetValue() / den;
Resources res = getResources();
ArrayList<Double> values = new ArrayList<>();
if (den <= 1) values.add(todayValue);
if (den <= 7) values.add(thisWeekValue);
values.add(thisMonthValue);
values.add(thisQuarterValue);
values.add(thisYearValue);
targetChart.setValues(values);
ArrayList<Double> targets = new ArrayList<>();
if (den <= 1) targets.add(dailyTarget);
if (den <= 7) targets.add(dailyTarget * 7);
targets.add(dailyTarget * daysInMonth);
targets.add(dailyTarget * daysInQuarter);
targets.add(dailyTarget * daysInYear);
targetChart.setTargets(targets);
ArrayList<String> labels = new ArrayList<>();
if (den <= 1) labels.add(res.getString(R.string.today));
if (den <= 7) labels.add(res.getString(R.string.week));
labels.add(res.getString(R.string.month));
labels.add(res.getString(R.string.quarter));
labels.add(res.getString(R.string.year));
targetChart.setLabels(labels);
}
@Override
public void onPreExecute()
{
int color = PaletteUtils.getColor(getContext(), getHabit().getColor());
title.setTextColor(color);
targetChart.setColor(color);
}
}
}

@ -34,7 +34,7 @@
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="160dp"/> android:layout_height="160dp"/>
<Button <androidx.appcompat.widget.AppCompatButton
android:id="@+id/edit" android:id="@+id/edit"
style="?android:borderlessButtonStyle" style="?android:borderlessButtonStyle"
android:layout_width="wrap_content" android:layout_width="wrap_content"

@ -43,15 +43,16 @@
style="@style/Card" style="@style/Card"
android:gravity="center" /> android:gravity="center" />
<View
android:id="@+id/headerShadow"
style="@style/ToolbarShadow"/>
<org.isoron.uhabits.activities.habits.show.views.OverviewCard <org.isoron.uhabits.activities.habits.show.views.OverviewCard
android:id="@+id/overviewCard" android:id="@+id/overviewCard"
style="@style/Card" style="@style/Card"
android:paddingTop="12dp"/> android:paddingTop="12dp"/>
<org.isoron.uhabits.activities.habits.show.views.TargetCard
android:id="@+id/targetCard"
style="@style/Card"
android:paddingTop="12dp"/>
<org.isoron.uhabits.activities.habits.show.views.ScoreCard <org.isoron.uhabits.activities.habits.show.views.ScoreCard
android:id="@+id/scoreCard" android:id="@+id/scoreCard"
style="@style/Card" style="@style/Card"

@ -23,9 +23,8 @@
android:layout_height="match_parent" android:layout_height="match_parent"
android:orientation="vertical"> android:orientation="vertical">
<org.isoron.uhabits.activities.habits.show.views.ScoreCard <org.isoron.uhabits.activities.habits.show.views.TargetCard
android:id="@+id/targetCard"
style="@style/Card" style="@style/Card"
android:layout_width="match_parent" android:gravity="center"/>
android:layout_height="match_parent"
android:gravity="center" />
</LinearLayout> </LinearLayout>

@ -0,0 +1,31 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
~ Copyright (C) 2016 Álinson Santos Xavier <isoron@gmail.com>
~
~ This file is part of Loop Habit Tracker.
~
~ Loop Habit Tracker 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.
~
~ Loop Habit Tracker 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/>.
-->
<merge xmlns:android="http://schemas.android.com/apk/res/android">
<TextView
android:id="@+id/title"
style="@style/CardHeader"
android:text="@string/target"/>
<org.isoron.uhabits.activities.common.views.TargetChart
android:id="@+id/targetChart"
android:layout_width="match_parent"
android:layout_height="300dp"/>
</merge>

@ -262,6 +262,7 @@
<string name="measurable_units_example">e.g. miles</string> <string name="measurable_units_example">e.g. miles</string>
<string name="every_month">Every month</string> <string name="every_month">Every month</string>
<string name="validation_should_not_be_blank">Cannot be blank</string> <string name="validation_should_not_be_blank">Cannot be blank</string>
<string name="today">Today</string>
</resources> </resources>
Loading…
Cancel
Save