|
|
@ -19,61 +19,76 @@
|
|
|
|
|
|
|
|
|
|
|
|
package org.isoron.uhabits.ui.habits.show.views;
|
|
|
|
package org.isoron.uhabits.ui.habits.show.views;
|
|
|
|
|
|
|
|
|
|
|
|
import android.content.Context;
|
|
|
|
import android.content.*;
|
|
|
|
import android.graphics.Canvas;
|
|
|
|
import android.graphics.*;
|
|
|
|
import android.graphics.Color;
|
|
|
|
import android.graphics.Paint.*;
|
|
|
|
import android.graphics.Paint;
|
|
|
|
import android.util.*;
|
|
|
|
import android.graphics.Paint.Align;
|
|
|
|
import android.view.*;
|
|
|
|
import android.graphics.RectF;
|
|
|
|
|
|
|
|
import android.util.AttributeSet;
|
|
|
|
import org.isoron.uhabits.*;
|
|
|
|
import android.view.HapticFeedbackConstants;
|
|
|
|
import org.isoron.uhabits.models.*;
|
|
|
|
import android.view.MotionEvent;
|
|
|
|
import org.isoron.uhabits.tasks.*;
|
|
|
|
|
|
|
|
import org.isoron.uhabits.utils.*;
|
|
|
|
import org.isoron.uhabits.R;
|
|
|
|
|
|
|
|
import org.isoron.uhabits.models.Habit;
|
|
|
|
import java.text.*;
|
|
|
|
import org.isoron.uhabits.models.ModelObservable;
|
|
|
|
import java.util.*;
|
|
|
|
import org.isoron.uhabits.tasks.BaseTask;
|
|
|
|
|
|
|
|
import org.isoron.uhabits.tasks.ToggleRepetitionTask;
|
|
|
|
public class HabitHistoryView extends ScrollableDataView implements
|
|
|
|
import org.isoron.uhabits.utils.ColorUtils;
|
|
|
|
HabitDataView,
|
|
|
|
import org.isoron.uhabits.utils.DateUtils;
|
|
|
|
ToggleRepetitionTask.Listener,
|
|
|
|
import org.isoron.uhabits.utils.InterfaceUtils;
|
|
|
|
ModelObservable.Listener
|
|
|
|
|
|
|
|
|
|
|
|
import java.text.SimpleDateFormat;
|
|
|
|
|
|
|
|
import java.util.Calendar;
|
|
|
|
|
|
|
|
import java.util.GregorianCalendar;
|
|
|
|
|
|
|
|
import java.util.Random;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
public class HabitHistoryView extends ScrollableDataView implements HabitDataView,
|
|
|
|
|
|
|
|
ToggleRepetitionTask.Listener, ModelObservable.Listener
|
|
|
|
|
|
|
|
{
|
|
|
|
{
|
|
|
|
private Habit habit;
|
|
|
|
private Habit habit;
|
|
|
|
|
|
|
|
|
|
|
|
private int[] checkmarks;
|
|
|
|
private int[] checkmarks;
|
|
|
|
|
|
|
|
|
|
|
|
private Paint pSquareBg, pSquareFg, pTextHeader;
|
|
|
|
private Paint pSquareBg, pSquareFg, pTextHeader;
|
|
|
|
|
|
|
|
|
|
|
|
private float squareSpacing;
|
|
|
|
private float squareSpacing;
|
|
|
|
|
|
|
|
|
|
|
|
private float squareTextOffset;
|
|
|
|
private float squareTextOffset;
|
|
|
|
|
|
|
|
|
|
|
|
private float headerTextOffset;
|
|
|
|
private float headerTextOffset;
|
|
|
|
|
|
|
|
|
|
|
|
private float columnWidth;
|
|
|
|
private float columnWidth;
|
|
|
|
|
|
|
|
|
|
|
|
private float columnHeight;
|
|
|
|
private float columnHeight;
|
|
|
|
|
|
|
|
|
|
|
|
private int nColumns;
|
|
|
|
private int nColumns;
|
|
|
|
|
|
|
|
|
|
|
|
private SimpleDateFormat dfMonth;
|
|
|
|
private SimpleDateFormat dfMonth;
|
|
|
|
|
|
|
|
|
|
|
|
private SimpleDateFormat dfYear;
|
|
|
|
private SimpleDateFormat dfYear;
|
|
|
|
|
|
|
|
|
|
|
|
private Calendar baseDate;
|
|
|
|
private Calendar baseDate;
|
|
|
|
|
|
|
|
|
|
|
|
private int nDays;
|
|
|
|
private int nDays;
|
|
|
|
/** 0-based-position of today in the column */
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
|
|
* 0-based-position of today in the column
|
|
|
|
|
|
|
|
*/
|
|
|
|
private int todayPositionInColumn;
|
|
|
|
private int todayPositionInColumn;
|
|
|
|
|
|
|
|
|
|
|
|
private int colors[];
|
|
|
|
private int colors[];
|
|
|
|
|
|
|
|
|
|
|
|
private RectF baseLocation;
|
|
|
|
private RectF baseLocation;
|
|
|
|
|
|
|
|
|
|
|
|
private int primaryColor;
|
|
|
|
private int primaryColor;
|
|
|
|
|
|
|
|
|
|
|
|
private boolean isBackgroundTransparent;
|
|
|
|
private boolean isBackgroundTransparent;
|
|
|
|
|
|
|
|
|
|
|
|
private int textColor;
|
|
|
|
private int textColor;
|
|
|
|
|
|
|
|
|
|
|
|
private int reverseTextColor;
|
|
|
|
private int reverseTextColor;
|
|
|
|
|
|
|
|
|
|
|
|
private boolean isEditable;
|
|
|
|
private boolean isEditable;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
private String previousMonth;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
private String previousYear;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
private float headerOverflow = 0;
|
|
|
|
|
|
|
|
|
|
|
|
public HabitHistoryView(Context context)
|
|
|
|
public HabitHistoryView(Context context)
|
|
|
|
{
|
|
|
|
{
|
|
|
|
super(context);
|
|
|
|
super(context);
|
|
|
@ -86,115 +101,89 @@ public class HabitHistoryView extends ScrollableDataView implements HabitDataVie
|
|
|
|
init();
|
|
|
|
init();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
public void setHabit(Habit habit)
|
|
|
|
@Override
|
|
|
|
|
|
|
|
public void onLongPress(MotionEvent e)
|
|
|
|
{
|
|
|
|
{
|
|
|
|
this.habit = habit;
|
|
|
|
onSingleTapUp(e);
|
|
|
|
createColors();
|
|
|
|
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private void init()
|
|
|
|
@Override
|
|
|
|
|
|
|
|
public void onModelChange()
|
|
|
|
{
|
|
|
|
{
|
|
|
|
createColors();
|
|
|
|
refreshData();
|
|
|
|
createPaints();
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
isEditable = false;
|
|
|
|
|
|
|
|
checkmarks = new int[0];
|
|
|
|
|
|
|
|
primaryColor = ColorUtils.getColor(getContext(), 7);
|
|
|
|
|
|
|
|
dfMonth = DateUtils.getDateFormat("MMM");
|
|
|
|
|
|
|
|
dfYear = DateUtils.getDateFormat("yyyy");
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
baseLocation = new RectF();
|
|
|
|
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private void updateDate()
|
|
|
|
@Override
|
|
|
|
|
|
|
|
public boolean onSingleTapUp(MotionEvent e)
|
|
|
|
{
|
|
|
|
{
|
|
|
|
baseDate = DateUtils.getStartOfTodayCalendar();
|
|
|
|
if (!isEditable) return false;
|
|
|
|
baseDate.add(Calendar.DAY_OF_YEAR, -(getDataOffset() - 1) * 7);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
nDays = (nColumns - 1) * 7;
|
|
|
|
performHapticFeedback(HapticFeedbackConstants.KEYBOARD_TAP);
|
|
|
|
int realWeekday = DateUtils.getStartOfTodayCalendar().get(Calendar.DAY_OF_WEEK);
|
|
|
|
|
|
|
|
todayPositionInColumn = (7 + realWeekday - baseDate.getFirstDayOfWeek()) % 7;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
baseDate.add(Calendar.DAY_OF_YEAR, -nDays);
|
|
|
|
int pointerId = e.getPointerId(0);
|
|
|
|
baseDate.add(Calendar.DAY_OF_YEAR, -todayPositionInColumn);
|
|
|
|
float x = e.getX(pointerId);
|
|
|
|
|
|
|
|
float y = e.getY(pointerId);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
final Long timestamp = positionToTimestamp(x, y);
|
|
|
|
|
|
|
|
if (timestamp == null) return false;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
ToggleRepetitionTask task = new ToggleRepetitionTask(habit, timestamp);
|
|
|
|
|
|
|
|
task.setListener(this);
|
|
|
|
|
|
|
|
task.execute();
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
@Override
|
|
|
|
@Override
|
|
|
|
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec)
|
|
|
|
public void onToggleRepetitionFinished()
|
|
|
|
{
|
|
|
|
{
|
|
|
|
int width = MeasureSpec.getSize(widthMeasureSpec);
|
|
|
|
new BaseTask()
|
|
|
|
int height = MeasureSpec.getSize(heightMeasureSpec);
|
|
|
|
{
|
|
|
|
setMeasuredDimension(width, height);
|
|
|
|
@Override
|
|
|
|
|
|
|
|
protected void doInBackground()
|
|
|
|
|
|
|
|
{
|
|
|
|
|
|
|
|
refreshData();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
@Override
|
|
|
|
@Override
|
|
|
|
protected void onSizeChanged(int width, int height, int oldWidth, int oldHeight)
|
|
|
|
protected void onPostExecute(Void aVoid)
|
|
|
|
{
|
|
|
|
{
|
|
|
|
if(height < 8) height = 200;
|
|
|
|
invalidate();
|
|
|
|
float baseSize = height / 8.0f;
|
|
|
|
super.onPostExecute(null);
|
|
|
|
setScrollerBucketSize((int) baseSize);
|
|
|
|
}
|
|
|
|
|
|
|
|
}.execute();
|
|
|
|
squareSpacing = InterfaceUtils.dpToPixels(getContext(), 1.0f);
|
|
|
|
|
|
|
|
float maxTextSize = getResources().getDimension(R.dimen.regularTextSize);
|
|
|
|
|
|
|
|
float textSize = height * 0.06f;
|
|
|
|
|
|
|
|
textSize = Math.min(textSize, maxTextSize);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
pSquareFg.setTextSize(textSize);
|
|
|
|
|
|
|
|
pTextHeader.setTextSize(textSize);
|
|
|
|
|
|
|
|
squareTextOffset = pSquareFg.getFontSpacing() * 0.4f;
|
|
|
|
|
|
|
|
headerTextOffset = pTextHeader.getFontSpacing() * 0.3f;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
float rightLabelWidth = getWeekdayLabelWidth() + headerTextOffset;
|
|
|
|
|
|
|
|
float horizontalPadding = getPaddingRight() + getPaddingLeft();
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
columnWidth = baseSize;
|
|
|
|
|
|
|
|
columnHeight = 8 * baseSize;
|
|
|
|
|
|
|
|
nColumns = (int)((width - rightLabelWidth - horizontalPadding) / baseSize) + 1;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
updateDate();
|
|
|
|
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private float getWeekdayLabelWidth()
|
|
|
|
@Override
|
|
|
|
|
|
|
|
public void refreshData()
|
|
|
|
{
|
|
|
|
{
|
|
|
|
float width = 0;
|
|
|
|
if (isInEditMode()) generateRandomData();
|
|
|
|
|
|
|
|
else
|
|
|
|
for(String w : DateUtils.getLocaleDayNames(Calendar.SHORT))
|
|
|
|
{
|
|
|
|
width = Math.max(width, pSquareFg.measureText(w));
|
|
|
|
if (habit == null) return;
|
|
|
|
|
|
|
|
checkmarks = habit.getCheckmarks().getAllValues();
|
|
|
|
|
|
|
|
createColors();
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return width;
|
|
|
|
updateDate();
|
|
|
|
|
|
|
|
postInvalidate();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private void createColors()
|
|
|
|
public void setHabit(Habit habit)
|
|
|
|
{
|
|
|
|
{
|
|
|
|
if(habit != null)
|
|
|
|
this.habit = habit;
|
|
|
|
this.primaryColor = ColorUtils.getColor(getContext(),
|
|
|
|
createColors();
|
|
|
|
habit.getColor());
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if(isBackgroundTransparent)
|
|
|
|
|
|
|
|
primaryColor = ColorUtils.setMinValue(primaryColor, 0.75f);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
int red = Color.red(primaryColor);
|
|
|
|
|
|
|
|
int green = Color.green(primaryColor);
|
|
|
|
|
|
|
|
int blue = Color.blue(primaryColor);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if(isBackgroundTransparent)
|
|
|
|
public void setIsBackgroundTransparent(boolean isBackgroundTransparent)
|
|
|
|
{
|
|
|
|
{
|
|
|
|
colors = new int[3];
|
|
|
|
this.isBackgroundTransparent = isBackgroundTransparent;
|
|
|
|
colors[0] = Color.argb(16, 255, 255, 255);
|
|
|
|
createColors();
|
|
|
|
colors[1] = Color.argb(128, red, green, blue);
|
|
|
|
|
|
|
|
colors[2] = primaryColor;
|
|
|
|
|
|
|
|
textColor = Color.WHITE;
|
|
|
|
|
|
|
|
reverseTextColor = Color.WHITE;
|
|
|
|
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else
|
|
|
|
|
|
|
|
|
|
|
|
public void setIsEditable(boolean isEditable)
|
|
|
|
{
|
|
|
|
{
|
|
|
|
colors = new int[3];
|
|
|
|
this.isEditable = isEditable;
|
|
|
|
colors[0] = InterfaceUtils.getStyledColor(getContext(), R.attr.lowContrastTextColor);
|
|
|
|
|
|
|
|
colors[1] = Color.argb(127, red, green, blue);
|
|
|
|
|
|
|
|
colors[2] = primaryColor;
|
|
|
|
|
|
|
|
textColor = InterfaceUtils.getStyledColor(getContext(), R.attr.mediumContrastTextColor);
|
|
|
|
|
|
|
|
reverseTextColor = InterfaceUtils.getStyledColor(getContext(), R.attr.highContrastReverseTextColor);
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
protected void createPaints()
|
|
|
|
protected void createPaints()
|
|
|
@ -210,49 +199,37 @@ public class HabitHistoryView extends ScrollableDataView implements HabitDataVie
|
|
|
|
pSquareFg.setTextAlign(Align.CENTER);
|
|
|
|
pSquareFg.setTextAlign(Align.CENTER);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
public void refreshData()
|
|
|
|
@Override
|
|
|
|
|
|
|
|
protected void onAttachedToWindow()
|
|
|
|
{
|
|
|
|
{
|
|
|
|
if(isInEditMode())
|
|
|
|
super.onAttachedToWindow();
|
|
|
|
generateRandomData();
|
|
|
|
new BaseTask()
|
|
|
|
else
|
|
|
|
|
|
|
|
{
|
|
|
|
{
|
|
|
|
if(habit == null) return;
|
|
|
|
@Override
|
|
|
|
checkmarks = habit.getCheckmarks().getAllValues();
|
|
|
|
protected void doInBackground()
|
|
|
|
createColors();
|
|
|
|
{
|
|
|
|
|
|
|
|
refreshData();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
}.execute();
|
|
|
|
updateDate();
|
|
|
|
habit.getObservable().addListener(this);
|
|
|
|
postInvalidate();
|
|
|
|
habit.getCheckmarks().observable.addListener(this);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private void generateRandomData()
|
|
|
|
@Override
|
|
|
|
{
|
|
|
|
protected void onDetachedFromWindow()
|
|
|
|
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;
|
|
|
|
habit.getCheckmarks().observable.removeListener(this);
|
|
|
|
for (int j = 0; j < 7; j++)
|
|
|
|
habit.getObservable().removeListener(this);
|
|
|
|
if(checkmarks[i + j] != 0)
|
|
|
|
super.onDetachedFromWindow();
|
|
|
|
count++;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if(count >= 3) checkmarks[i] = Math.max(checkmarks[i], 1);
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private String previousMonth;
|
|
|
|
|
|
|
|
private String previousYear;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@Override
|
|
|
|
@Override
|
|
|
|
protected void onDraw(Canvas canvas)
|
|
|
|
protected void onDraw(Canvas canvas)
|
|
|
|
{
|
|
|
|
{
|
|
|
|
super.onDraw(canvas);
|
|
|
|
super.onDraw(canvas);
|
|
|
|
|
|
|
|
|
|
|
|
baseLocation.set(0, 0, columnWidth - squareSpacing, columnWidth - squareSpacing);
|
|
|
|
baseLocation.set(0, 0, columnWidth - squareSpacing,
|
|
|
|
|
|
|
|
columnWidth - squareSpacing);
|
|
|
|
baseLocation.offset(getPaddingLeft(), getPaddingTop());
|
|
|
|
baseLocation.offset(getPaddingLeft(), getPaddingTop());
|
|
|
|
|
|
|
|
|
|
|
|
headerOverflow = 0;
|
|
|
|
headerOverflow = 0;
|
|
|
@ -266,41 +243,86 @@ public class HabitHistoryView extends ScrollableDataView implements HabitDataVie
|
|
|
|
for (int column = 0; column < nColumns - 1; column++)
|
|
|
|
for (int column = 0; column < nColumns - 1; column++)
|
|
|
|
{
|
|
|
|
{
|
|
|
|
drawColumn(canvas, baseLocation, currentDate, column);
|
|
|
|
drawColumn(canvas, baseLocation, currentDate, column);
|
|
|
|
baseLocation.offset(columnWidth, - columnHeight);
|
|
|
|
baseLocation.offset(columnWidth, -columnHeight);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
drawAxis(canvas, baseLocation);
|
|
|
|
drawAxis(canvas, baseLocation);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private void drawColumn(Canvas canvas, RectF location, GregorianCalendar date, int column)
|
|
|
|
@Override
|
|
|
|
|
|
|
|
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec)
|
|
|
|
{
|
|
|
|
{
|
|
|
|
drawColumnHeader(canvas, location, date);
|
|
|
|
int width = MeasureSpec.getSize(widthMeasureSpec);
|
|
|
|
location.offset(0, columnWidth);
|
|
|
|
int height = MeasureSpec.getSize(heightMeasureSpec);
|
|
|
|
|
|
|
|
setMeasuredDimension(width, height);
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
for (int j = 0; j < 7; j++)
|
|
|
|
@Override
|
|
|
|
{
|
|
|
|
protected void onSizeChanged(int width,
|
|
|
|
if (!(column == nColumns - 2 && getDataOffset() == 0 && j > todayPositionInColumn))
|
|
|
|
int height,
|
|
|
|
|
|
|
|
int oldWidth,
|
|
|
|
|
|
|
|
int oldHeight)
|
|
|
|
{
|
|
|
|
{
|
|
|
|
int checkmarkOffset = getDataOffset() * 7 + nDays - 7 * (column + 1) +
|
|
|
|
if (height < 8) height = 200;
|
|
|
|
todayPositionInColumn - j;
|
|
|
|
float baseSize = height / 8.0f;
|
|
|
|
drawSquare(canvas, location, date, checkmarkOffset);
|
|
|
|
setScrollerBucketSize((int) baseSize);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
date.add(Calendar.DAY_OF_MONTH, 1);
|
|
|
|
squareSpacing = InterfaceUtils.dpToPixels(getContext(), 1.0f);
|
|
|
|
location.offset(0, columnWidth);
|
|
|
|
float maxTextSize =
|
|
|
|
}
|
|
|
|
getResources().getDimension(R.dimen.regularTextSize);
|
|
|
|
|
|
|
|
float textSize = height * 0.06f;
|
|
|
|
|
|
|
|
textSize = Math.min(textSize, maxTextSize);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
pSquareFg.setTextSize(textSize);
|
|
|
|
|
|
|
|
pTextHeader.setTextSize(textSize);
|
|
|
|
|
|
|
|
squareTextOffset = pSquareFg.getFontSpacing() * 0.4f;
|
|
|
|
|
|
|
|
headerTextOffset = pTextHeader.getFontSpacing() * 0.3f;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
float rightLabelWidth = getWeekdayLabelWidth() + headerTextOffset;
|
|
|
|
|
|
|
|
float horizontalPadding = getPaddingRight() + getPaddingLeft();
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
columnWidth = baseSize;
|
|
|
|
|
|
|
|
columnHeight = 8 * baseSize;
|
|
|
|
|
|
|
|
nColumns =
|
|
|
|
|
|
|
|
(int) ((width - rightLabelWidth - horizontalPadding) / baseSize) +
|
|
|
|
|
|
|
|
1;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
updateDate();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private void drawSquare(Canvas canvas, RectF location, GregorianCalendar date,
|
|
|
|
private void createColors()
|
|
|
|
int checkmarkOffset)
|
|
|
|
|
|
|
|
{
|
|
|
|
{
|
|
|
|
if (checkmarkOffset >= checkmarks.length) pSquareBg.setColor(colors[0]);
|
|
|
|
if (habit != null) this.primaryColor =
|
|
|
|
else pSquareBg.setColor(colors[checkmarks[checkmarkOffset]]);
|
|
|
|
ColorUtils.getColor(getContext(), habit.getColor());
|
|
|
|
|
|
|
|
|
|
|
|
pSquareFg.setColor(reverseTextColor);
|
|
|
|
if (isBackgroundTransparent)
|
|
|
|
canvas.drawRect(location, pSquareBg);
|
|
|
|
primaryColor = ColorUtils.setMinValue(primaryColor, 0.75f);
|
|
|
|
String text = Integer.toString(date.get(Calendar.DAY_OF_MONTH));
|
|
|
|
|
|
|
|
canvas.drawText(text, location.centerX(), location.centerY() + squareTextOffset, pSquareFg);
|
|
|
|
int red = Color.red(primaryColor);
|
|
|
|
|
|
|
|
int green = Color.green(primaryColor);
|
|
|
|
|
|
|
|
int blue = Color.blue(primaryColor);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if (isBackgroundTransparent)
|
|
|
|
|
|
|
|
{
|
|
|
|
|
|
|
|
colors = new int[3];
|
|
|
|
|
|
|
|
colors[0] = Color.argb(16, 255, 255, 255);
|
|
|
|
|
|
|
|
colors[1] = Color.argb(128, red, green, blue);
|
|
|
|
|
|
|
|
colors[2] = primaryColor;
|
|
|
|
|
|
|
|
textColor = Color.WHITE;
|
|
|
|
|
|
|
|
reverseTextColor = Color.WHITE;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
else
|
|
|
|
|
|
|
|
{
|
|
|
|
|
|
|
|
colors = new int[3];
|
|
|
|
|
|
|
|
colors[0] = InterfaceUtils.getStyledColor(getContext(),
|
|
|
|
|
|
|
|
R.attr.lowContrastTextColor);
|
|
|
|
|
|
|
|
colors[1] = Color.argb(127, red, green, blue);
|
|
|
|
|
|
|
|
colors[2] = primaryColor;
|
|
|
|
|
|
|
|
textColor = InterfaceUtils.getStyledColor(getContext(),
|
|
|
|
|
|
|
|
R.attr.mediumContrastTextColor);
|
|
|
|
|
|
|
|
reverseTextColor = InterfaceUtils.getStyledColor(getContext(),
|
|
|
|
|
|
|
|
R.attr.highContrastReverseTextColor);
|
|
|
|
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private void drawAxis(Canvas canvas, RectF location)
|
|
|
|
private void drawAxis(Canvas canvas, RectF location)
|
|
|
@ -315,59 +337,107 @@ public class HabitHistoryView extends ScrollableDataView implements HabitDataVie
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private float headerOverflow = 0;
|
|
|
|
private void drawColumn(Canvas canvas,
|
|
|
|
|
|
|
|
RectF location,
|
|
|
|
|
|
|
|
GregorianCalendar date,
|
|
|
|
|
|
|
|
int column)
|
|
|
|
|
|
|
|
{
|
|
|
|
|
|
|
|
drawColumnHeader(canvas, location, date);
|
|
|
|
|
|
|
|
location.offset(0, columnWidth);
|
|
|
|
|
|
|
|
|
|
|
|
private void drawColumnHeader(Canvas canvas, RectF location, GregorianCalendar date)
|
|
|
|
for (int j = 0; j < 7; j++)
|
|
|
|
|
|
|
|
{
|
|
|
|
|
|
|
|
if (!(column == nColumns - 2 && getDataOffset() == 0 &&
|
|
|
|
|
|
|
|
j > todayPositionInColumn))
|
|
|
|
|
|
|
|
{
|
|
|
|
|
|
|
|
int checkmarkOffset =
|
|
|
|
|
|
|
|
getDataOffset() * 7 + nDays - 7 * (column + 1) +
|
|
|
|
|
|
|
|
todayPositionInColumn - j;
|
|
|
|
|
|
|
|
drawSquare(canvas, location, date, checkmarkOffset);
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
date.add(Calendar.DAY_OF_MONTH, 1);
|
|
|
|
|
|
|
|
location.offset(0, columnWidth);
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
private void drawColumnHeader(Canvas canvas,
|
|
|
|
|
|
|
|
RectF location,
|
|
|
|
|
|
|
|
GregorianCalendar date)
|
|
|
|
{
|
|
|
|
{
|
|
|
|
String month = dfMonth.format(date.getTime());
|
|
|
|
String month = dfMonth.format(date.getTime());
|
|
|
|
String year = dfYear.format(date.getTime());
|
|
|
|
String year = dfYear.format(date.getTime());
|
|
|
|
|
|
|
|
|
|
|
|
String text = null;
|
|
|
|
String text = null;
|
|
|
|
if (!month.equals(previousMonth))
|
|
|
|
if (!month.equals(previousMonth)) text = previousMonth = month;
|
|
|
|
text = previousMonth = month;
|
|
|
|
else if (!year.equals(previousYear)) text = previousYear = year;
|
|
|
|
else if(!year.equals(previousYear))
|
|
|
|
|
|
|
|
text = previousYear = year;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if(text != null)
|
|
|
|
if (text != null)
|
|
|
|
{
|
|
|
|
{
|
|
|
|
canvas.drawText(text, location.left + headerOverflow, location.bottom - headerTextOffset, pTextHeader);
|
|
|
|
canvas.drawText(text, location.left + headerOverflow,
|
|
|
|
headerOverflow += pTextHeader.measureText(text) + columnWidth * 0.2f;
|
|
|
|
location.bottom - headerTextOffset, pTextHeader);
|
|
|
|
|
|
|
|
headerOverflow +=
|
|
|
|
|
|
|
|
pTextHeader.measureText(text) + columnWidth * 0.2f;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
headerOverflow = Math.max(0, headerOverflow - columnWidth);
|
|
|
|
headerOverflow = Math.max(0, headerOverflow - columnWidth);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
public void setIsBackgroundTransparent(boolean isBackgroundTransparent)
|
|
|
|
private void drawSquare(Canvas canvas,
|
|
|
|
|
|
|
|
RectF location,
|
|
|
|
|
|
|
|
GregorianCalendar date,
|
|
|
|
|
|
|
|
int checkmarkOffset)
|
|
|
|
{
|
|
|
|
{
|
|
|
|
this.isBackgroundTransparent = isBackgroundTransparent;
|
|
|
|
if (checkmarkOffset >= checkmarks.length) pSquareBg.setColor(colors[0]);
|
|
|
|
createColors();
|
|
|
|
else pSquareBg.setColor(colors[checkmarks[checkmarkOffset]]);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
pSquareFg.setColor(reverseTextColor);
|
|
|
|
|
|
|
|
canvas.drawRect(location, pSquareBg);
|
|
|
|
|
|
|
|
String text = Integer.toString(date.get(Calendar.DAY_OF_MONTH));
|
|
|
|
|
|
|
|
canvas.drawText(text, location.centerX(),
|
|
|
|
|
|
|
|
location.centerY() + squareTextOffset, pSquareFg);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
@Override
|
|
|
|
private void generateRandomData()
|
|
|
|
public void onLongPress(MotionEvent e)
|
|
|
|
|
|
|
|
{
|
|
|
|
{
|
|
|
|
onSingleTapUp(e);
|
|
|
|
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);
|
|
|
|
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
@Override
|
|
|
|
private float getWeekdayLabelWidth()
|
|
|
|
public boolean onSingleTapUp(MotionEvent e)
|
|
|
|
|
|
|
|
{
|
|
|
|
{
|
|
|
|
if(!isEditable) return false;
|
|
|
|
float width = 0;
|
|
|
|
|
|
|
|
|
|
|
|
performHapticFeedback(HapticFeedbackConstants.KEYBOARD_TAP);
|
|
|
|
for (String w : DateUtils.getLocaleDayNames(Calendar.SHORT))
|
|
|
|
|
|
|
|
width = Math.max(width, pSquareFg.measureText(w));
|
|
|
|
|
|
|
|
|
|
|
|
int pointerId = e.getPointerId(0);
|
|
|
|
return width;
|
|
|
|
float x = e.getX(pointerId);
|
|
|
|
}
|
|
|
|
float y = e.getY(pointerId);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
final Long timestamp = positionToTimestamp(x, y);
|
|
|
|
private void init()
|
|
|
|
if(timestamp == null) return false;
|
|
|
|
{
|
|
|
|
|
|
|
|
createColors();
|
|
|
|
|
|
|
|
createPaints();
|
|
|
|
|
|
|
|
|
|
|
|
ToggleRepetitionTask task = new ToggleRepetitionTask(habit, timestamp);
|
|
|
|
isEditable = false;
|
|
|
|
task.setListener(this);
|
|
|
|
checkmarks = new int[0];
|
|
|
|
task.execute();
|
|
|
|
primaryColor = ColorUtils.getColor(getContext(), 7);
|
|
|
|
|
|
|
|
dfMonth = DateUtils.getDateFormat("MMM");
|
|
|
|
|
|
|
|
dfYear = DateUtils.getDateFormat("yyyy");
|
|
|
|
|
|
|
|
|
|
|
|
return true;
|
|
|
|
baseLocation = new RectF();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private Long positionToTimestamp(float x, float y)
|
|
|
|
private Long positionToTimestamp(float x, float y)
|
|
|
@ -375,71 +445,31 @@ public class HabitHistoryView extends ScrollableDataView implements HabitDataVie
|
|
|
|
int col = (int) (x / columnWidth);
|
|
|
|
int col = (int) (x / columnWidth);
|
|
|
|
int row = (int) (y / columnWidth);
|
|
|
|
int row = (int) (y / columnWidth);
|
|
|
|
|
|
|
|
|
|
|
|
if(row == 0) return null;
|
|
|
|
if (row == 0) return null;
|
|
|
|
if(col == nColumns - 1) return null;
|
|
|
|
if (col == nColumns - 1) return null;
|
|
|
|
|
|
|
|
|
|
|
|
int offset = col * 7 + (row - 1);
|
|
|
|
int offset = col * 7 + (row - 1);
|
|
|
|
Calendar date = (Calendar) baseDate.clone();
|
|
|
|
Calendar date = (Calendar) baseDate.clone();
|
|
|
|
date.add(Calendar.DAY_OF_YEAR, offset);
|
|
|
|
date.add(Calendar.DAY_OF_YEAR, offset);
|
|
|
|
|
|
|
|
|
|
|
|
if(DateUtils.getStartOfDay(date.getTimeInMillis()) > DateUtils.getStartOfToday())
|
|
|
|
if (DateUtils.getStartOfDay(date.getTimeInMillis()) >
|
|
|
|
return null;
|
|
|
|
DateUtils.getStartOfToday()) return null;
|
|
|
|
|
|
|
|
|
|
|
|
return date.getTimeInMillis();
|
|
|
|
return date.getTimeInMillis();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
public void setIsEditable(boolean isEditable)
|
|
|
|
private void updateDate()
|
|
|
|
{
|
|
|
|
|
|
|
|
this.isEditable = isEditable;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@Override
|
|
|
|
|
|
|
|
public void onToggleRepetitionFinished()
|
|
|
|
|
|
|
|
{
|
|
|
|
|
|
|
|
new BaseTask()
|
|
|
|
|
|
|
|
{
|
|
|
|
|
|
|
|
@Override
|
|
|
|
|
|
|
|
protected void doInBackground()
|
|
|
|
|
|
|
|
{
|
|
|
|
|
|
|
|
refreshData();
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@Override
|
|
|
|
|
|
|
|
protected void onPostExecute(Void aVoid)
|
|
|
|
|
|
|
|
{
|
|
|
|
|
|
|
|
invalidate();
|
|
|
|
|
|
|
|
super.onPostExecute(null);
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}.execute();
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@Override
|
|
|
|
|
|
|
|
protected void onAttachedToWindow()
|
|
|
|
|
|
|
|
{
|
|
|
|
|
|
|
|
super.onAttachedToWindow();
|
|
|
|
|
|
|
|
new BaseTask()
|
|
|
|
|
|
|
|
{
|
|
|
|
|
|
|
|
@Override
|
|
|
|
|
|
|
|
protected void doInBackground()
|
|
|
|
|
|
|
|
{
|
|
|
|
{
|
|
|
|
refreshData();
|
|
|
|
baseDate = DateUtils.getStartOfTodayCalendar();
|
|
|
|
}
|
|
|
|
baseDate.add(Calendar.DAY_OF_YEAR, -(getDataOffset() - 1) * 7);
|
|
|
|
}.execute();
|
|
|
|
|
|
|
|
habit.getObservable().addListener(this);
|
|
|
|
|
|
|
|
habit.getCheckmarks().observable.addListener(this);
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@Override
|
|
|
|
nDays = (nColumns - 1) * 7;
|
|
|
|
protected void onDetachedFromWindow()
|
|
|
|
int realWeekday =
|
|
|
|
{
|
|
|
|
DateUtils.getStartOfTodayCalendar().get(Calendar.DAY_OF_WEEK);
|
|
|
|
habit.getCheckmarks().observable.removeListener(this);
|
|
|
|
todayPositionInColumn =
|
|
|
|
habit.getObservable().removeListener(this);
|
|
|
|
(7 + realWeekday - baseDate.getFirstDayOfWeek()) % 7;
|
|
|
|
super.onDetachedFromWindow();
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@Override
|
|
|
|
baseDate.add(Calendar.DAY_OF_YEAR, -nDays);
|
|
|
|
public void onModelChange()
|
|
|
|
baseDate.add(Calendar.DAY_OF_YEAR, -todayPositionInColumn);
|
|
|
|
{
|
|
|
|
|
|
|
|
refreshData();
|
|
|
|
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|