mirror of
https://github.com/iSoron/uhabits.git
synced 2025-12-06 09:08:52 -06:00
@@ -106,6 +106,7 @@ public class ShowHabitFragment extends Fragment
|
||||
sStrengthInterval.setOnItemSelectedListener(this);
|
||||
|
||||
dataViews.add((HabitStreakView) view.findViewById(R.id.streakView));
|
||||
dataViews.add((HabitStreakView) view.findViewById(R.id.smallStreakView));
|
||||
dataViews.add((HabitScoreView) view.findViewById(R.id.scoreView));
|
||||
dataViews.add((HabitHistoryView) view.findViewById(R.id.historyView));
|
||||
dataViews.add((HabitFrequencyView) view.findViewById(R.id.punchcardView));
|
||||
|
||||
@@ -19,13 +19,18 @@
|
||||
|
||||
package org.isoron.uhabits.models;
|
||||
|
||||
import android.database.Cursor;
|
||||
import android.database.sqlite.SQLiteDatabase;
|
||||
|
||||
import com.activeandroid.ActiveAndroid;
|
||||
import com.activeandroid.Cache;
|
||||
import com.activeandroid.query.Delete;
|
||||
import com.activeandroid.query.Select;
|
||||
|
||||
import org.isoron.uhabits.helpers.DateHelper;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
|
||||
public class StreakList
|
||||
@@ -37,14 +42,37 @@ public class StreakList
|
||||
this.habit = habit;
|
||||
}
|
||||
|
||||
public List<Streak> getAll()
|
||||
public List<Streak> getAll(int limit)
|
||||
{
|
||||
rebuild();
|
||||
|
||||
return new Select().from(Streak.class)
|
||||
.where("habit = ?", habit.getId())
|
||||
.orderBy("end asc")
|
||||
.execute();
|
||||
String query = "select * from (select * from streak where habit=? " +
|
||||
"order by end <> ?, length desc, end desc limit ?) order by end desc";
|
||||
|
||||
String params[] = {habit.getId().toString(), Long.toString(DateHelper.getStartOfToday()),
|
||||
Integer.toString(limit)};
|
||||
|
||||
SQLiteDatabase db = Cache.openDatabase();
|
||||
Cursor cursor = db.rawQuery(query, params);
|
||||
|
||||
if(!cursor.moveToFirst())
|
||||
{
|
||||
cursor.close();
|
||||
return new LinkedList<>();
|
||||
}
|
||||
|
||||
List<Streak> streaks = new LinkedList<>();
|
||||
|
||||
do
|
||||
{
|
||||
Streak s = Streak.load(Streak.class, cursor.getInt(0));
|
||||
streaks.add(s);
|
||||
}
|
||||
while (cursor.moveToNext());
|
||||
|
||||
cursor.close();
|
||||
return streaks;
|
||||
|
||||
}
|
||||
|
||||
public Streak getNewest()
|
||||
|
||||
@@ -23,49 +23,50 @@ import android.content.Context;
|
||||
import android.graphics.Canvas;
|
||||
import android.graphics.Color;
|
||||
import android.graphics.Paint;
|
||||
import android.graphics.Rect;
|
||||
import android.graphics.RectF;
|
||||
import android.util.AttributeSet;
|
||||
import android.view.View;
|
||||
|
||||
import org.isoron.uhabits.R;
|
||||
import org.isoron.uhabits.helpers.ColorHelper;
|
||||
import org.isoron.uhabits.helpers.DateHelper;
|
||||
import org.isoron.uhabits.models.Habit;
|
||||
import org.isoron.uhabits.models.Streak;
|
||||
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.text.DateFormat;
|
||||
import java.util.Collections;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Random;
|
||||
import java.util.TimeZone;
|
||||
|
||||
public class HabitStreakView extends ScrollableDataView implements HabitDataView
|
||||
public class HabitStreakView extends View implements HabitDataView
|
||||
{
|
||||
private Habit habit;
|
||||
private Paint pText, pBar;
|
||||
private Paint paint;
|
||||
|
||||
private long[] startTimes;
|
||||
private long[] endTimes;
|
||||
private long[] lengths;
|
||||
private long minLength;
|
||||
private long maxLength;
|
||||
|
||||
private int columnWidth;
|
||||
private int columnHeight;
|
||||
private int headerHeight;
|
||||
private int nColumns;
|
||||
|
||||
private long maxStreakLength;
|
||||
private int[] colors;
|
||||
private SimpleDateFormat dfMonth;
|
||||
private Rect rect;
|
||||
private RectF rect;
|
||||
private int baseSize;
|
||||
private int primaryColor;
|
||||
private List<Streak> streaks;
|
||||
|
||||
private boolean isBackgroundTransparent;
|
||||
private int textColor;
|
||||
private Paint pBarText;
|
||||
private DateFormat dateFormat;
|
||||
private int width;
|
||||
private float em;
|
||||
private float maxLabelWidth;
|
||||
private float textMargin;
|
||||
private boolean shouldShowLabels;
|
||||
private int maxStreakCount;
|
||||
|
||||
public HabitStreakView(Context context, AttributeSet attrs)
|
||||
{
|
||||
super(context, attrs);
|
||||
this.primaryColor = ColorHelper.palette[7];
|
||||
startTimes = endTimes = lengths = new long[0];
|
||||
streaks = Collections.emptyList();
|
||||
init();
|
||||
}
|
||||
|
||||
@@ -74,18 +75,19 @@ public class HabitStreakView extends ScrollableDataView implements HabitDataView
|
||||
this.habit = habit;
|
||||
|
||||
createColors();
|
||||
refreshData();
|
||||
postInvalidate();
|
||||
}
|
||||
|
||||
private void init()
|
||||
{
|
||||
refreshData();
|
||||
createPaints();
|
||||
createColors();
|
||||
|
||||
dfMonth = new SimpleDateFormat("MMM", Locale.getDefault());
|
||||
rect = new Rect();
|
||||
dateFormat = DateFormat.getDateInstance(DateFormat.MEDIUM);
|
||||
dateFormat.setTimeZone(TimeZone.getTimeZone("GMT"));
|
||||
rect = new RectF();
|
||||
|
||||
baseSize = getResources().getDimensionPixelSize(R.dimen.baseSize);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -99,16 +101,18 @@ public class HabitStreakView extends ScrollableDataView implements HabitDataView
|
||||
@Override
|
||||
protected void onSizeChanged(int width, int height, int oldWidth, int oldHeight)
|
||||
{
|
||||
baseSize = height / 10;
|
||||
setScrollerBucketSize(baseSize);
|
||||
maxStreakCount = height / baseSize;
|
||||
this.width = width;
|
||||
|
||||
columnWidth = baseSize;
|
||||
columnHeight = 8 * baseSize;
|
||||
headerHeight = baseSize;
|
||||
nColumns = width / baseSize - 1;
|
||||
int maxTextSize = getResources().getDimensionPixelSize(R.dimen.regularTextSize);
|
||||
float regularTextSize = Math.min(baseSize * 0.56f, maxTextSize);
|
||||
|
||||
pText.setTextSize(baseSize * 0.5f);
|
||||
pBar.setTextSize(baseSize * 0.5f);
|
||||
paint.setTextSize(regularTextSize);
|
||||
em = paint.getFontSpacing();
|
||||
textMargin = 0.5f * em;
|
||||
|
||||
refreshData();
|
||||
updateMaxMin();
|
||||
}
|
||||
|
||||
private void createColors()
|
||||
@@ -134,7 +138,6 @@ public class HabitStreakView extends ScrollableDataView implements HabitDataView
|
||||
colors[1] = Color.argb(170, red, green, blue);
|
||||
colors[0] = Color.argb(128, red, green, blue);
|
||||
textColor = Color.rgb(255, 255, 255);
|
||||
pBarText = pText;
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -144,111 +147,106 @@ public class HabitStreakView extends ScrollableDataView implements HabitDataView
|
||||
colors[1] = Color.argb(96, red, green, blue);
|
||||
colors[0] = Color.argb(32, 0, 0, 0);
|
||||
textColor = Color.argb(64, 0, 0, 0);
|
||||
pBarText = pBar;
|
||||
}
|
||||
}
|
||||
|
||||
protected void createPaints()
|
||||
{
|
||||
pText = new Paint();
|
||||
pText.setTextAlign(Paint.Align.CENTER);
|
||||
pText.setAntiAlias(true);
|
||||
|
||||
pBar = new Paint();
|
||||
pBar.setTextAlign(Paint.Align.CENTER);
|
||||
pBar.setAntiAlias(true);
|
||||
paint = new Paint();
|
||||
paint.setTextAlign(Paint.Align.CENTER);
|
||||
paint.setAntiAlias(true);
|
||||
}
|
||||
|
||||
public void refreshData()
|
||||
{
|
||||
if(isInEditMode())
|
||||
generateRandomData();
|
||||
else
|
||||
{
|
||||
if(habit == null) return;
|
||||
|
||||
List<Streak> streaks = habit.streaks.getAll();
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
invalidate();
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
streaks = habit.streaks.getAll(maxStreakCount);
|
||||
updateMaxMin();
|
||||
postInvalidate();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onDraw(Canvas canvas)
|
||||
{
|
||||
super.onDraw(canvas);
|
||||
if(streaks.size() == 0) return;
|
||||
|
||||
float lineHeight = pText.getFontSpacing();
|
||||
float barHeaderOffset = lineHeight * 0.4f;
|
||||
rect.set(0, 0, width, baseSize);
|
||||
|
||||
int nStreaks = startTimes.length;
|
||||
int start = nStreaks - nColumns - getDataOffset();
|
||||
|
||||
pText.setColor(textColor);
|
||||
|
||||
String previousMonth = "";
|
||||
|
||||
for (int offset = 0; offset < nColumns && start + offset < nStreaks; offset++)
|
||||
for(Streak s : streaks)
|
||||
{
|
||||
if(start + offset < 0) continue;
|
||||
String month = dfMonth.format(startTimes[start + offset]);
|
||||
drawRow(canvas, s, rect);
|
||||
rect.offset(0, baseSize);
|
||||
}
|
||||
}
|
||||
|
||||
long l = lengths[offset + start];
|
||||
double lRelative = ((double) l) / maxStreakLength;
|
||||
private void updateMaxMin()
|
||||
{
|
||||
maxLength = 0;
|
||||
minLength = Long.MAX_VALUE;
|
||||
shouldShowLabels = true;
|
||||
|
||||
pBar.setColor(colors[(int) Math.floor(lRelative * 3)]);
|
||||
for (Streak s : streaks)
|
||||
{
|
||||
maxLength = Math.max(maxLength, s.length);
|
||||
minLength = Math.min(minLength, s.length);
|
||||
|
||||
int height = (int) (columnHeight * lRelative);
|
||||
rect.set(0, 0, columnWidth - 2, height);
|
||||
rect.offset(offset * columnWidth, headerHeight + columnHeight - height);
|
||||
float lw1 = paint.measureText(dateFormat.format(new Date(s.start)));
|
||||
float lw2 = paint.measureText(dateFormat.format(new Date(s.end)));
|
||||
maxLabelWidth = Math.max(maxLabelWidth, Math.max(lw1, lw2));
|
||||
}
|
||||
|
||||
canvas.drawRect(rect, pBar);
|
||||
canvas.drawText(Long.toString(l), rect.centerX(), rect.top - barHeaderOffset, pBarText);
|
||||
if(width - 2 * maxLabelWidth < width * 0.25f)
|
||||
{
|
||||
maxLabelWidth = 0;
|
||||
shouldShowLabels = false;
|
||||
}
|
||||
}
|
||||
|
||||
if (!month.equals(previousMonth))
|
||||
canvas.drawText(month, rect.centerX(), rect.bottom + lineHeight * 1.2f, pText);
|
||||
private void drawRow(Canvas canvas, Streak streak, RectF rect)
|
||||
{
|
||||
if(maxLength == 0) return;
|
||||
|
||||
previousMonth = month;
|
||||
float percentage = (float) streak.length / maxLength;
|
||||
float availableWidth = width - 2 * maxLabelWidth;
|
||||
if(shouldShowLabels) availableWidth -= 2 * textMargin;
|
||||
|
||||
float barWidth = percentage * availableWidth;
|
||||
float minBarWidth = paint.measureText(streak.length.toString());
|
||||
barWidth = Math.max(barWidth, minBarWidth);
|
||||
|
||||
float gap = (width - barWidth) / 2;
|
||||
float paddingTopBottom = baseSize * 0.05f;
|
||||
|
||||
float croppedPercentage;
|
||||
if (maxLength == minLength)
|
||||
croppedPercentage = 1.0f;
|
||||
else
|
||||
croppedPercentage = (float) (streak.length - minLength) / (maxLength - minLength);
|
||||
|
||||
int c = (int) (croppedPercentage * 3);
|
||||
paint.setColor(colors[(c)]);
|
||||
|
||||
canvas.drawRect(rect.left + gap, rect.top + paddingTopBottom, rect.right - gap,
|
||||
rect.bottom - paddingTopBottom, paint);
|
||||
|
||||
float yOffset = rect.centerY() + 0.3f * em;
|
||||
|
||||
paint.setColor(Color.WHITE);
|
||||
paint.setTextAlign(Paint.Align.CENTER);
|
||||
canvas.drawText(streak.length.toString(), rect.centerX(), yOffset, paint);
|
||||
|
||||
if(shouldShowLabels)
|
||||
{
|
||||
String startLabel = dateFormat.format(new Date(streak.start));
|
||||
String endLabel = dateFormat.format(new Date(streak.end));
|
||||
|
||||
paint.setColor(textColor);
|
||||
paint.setTextAlign(Paint.Align.RIGHT);
|
||||
canvas.drawText(startLabel, gap - textMargin, yOffset, paint);
|
||||
|
||||
paint.setTextAlign(Paint.Align.LEFT);
|
||||
canvas.drawText(endLabel, width - gap + textMargin, yOffset, paint);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -42,16 +42,36 @@
|
||||
android:id="@+id/llOverview"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center"
|
||||
android:gravity="center"
|
||||
android:orientation="horizontal">
|
||||
|
||||
<org.isoron.uhabits.views.RingView
|
||||
android:id="@+id/scoreRing"
|
||||
style="@style/smallDataViewStyle"
|
||||
app:label="@string/habit_strength"
|
||||
app:maxDiameter="70"
|
||||
android:layout_width="100dp"
|
||||
app:label="@string/strength"
|
||||
app:maxDiameter="80"
|
||||
app:textSize="@dimen/smallTextSize"/>
|
||||
|
||||
<LinearLayout
|
||||
style="@style/smallDataViewStyle"
|
||||
android:orientation="vertical">
|
||||
|
||||
<org.isoron.uhabits.views.HabitStreakView
|
||||
android:id="@+id/smallStreakView"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="80dp"/>
|
||||
|
||||
<TextView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/best_streaks"
|
||||
android:layout_marginTop="9dp"
|
||||
android:textColor="@color/fadedTextColor"
|
||||
android:gravity="center"/>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</LinearLayout>
|
||||
@@ -173,7 +193,7 @@
|
||||
<TextView
|
||||
android:id="@+id/tvStreaks"
|
||||
style="@style/cardHeaderStyle"
|
||||
android:text="@string/streaks"/>
|
||||
android:text="@string/best_streaks"/>
|
||||
|
||||
<org.isoron.uhabits.views.HabitStreakView
|
||||
android:id="@+id/streakView"
|
||||
|
||||
@@ -18,7 +18,7 @@
|
||||
-->
|
||||
|
||||
<resources>
|
||||
<dimen name="small_square_size">20dp</dimen>
|
||||
<dimen name="baseSize">20dp</dimen>
|
||||
<dimen name="check_square_size">42dp</dimen>
|
||||
<dimen name="history_editor_max_height">450dp</dimen>
|
||||
<dimen name="history_editor_padding">8dp</dimen>
|
||||
|
||||
@@ -151,4 +151,6 @@
|
||||
<string name="bug_report_failed">Failed to generate bug report.</string>
|
||||
<string name="generate_bug_report">Generate bug report</string>
|
||||
<string name="troubleshooting">Troubleshooting</string>
|
||||
<string name="best_streaks">Best streaks</string>
|
||||
<string name="strength">Strength</string>
|
||||
</resources>
|
||||
@@ -96,11 +96,11 @@
|
||||
</style>
|
||||
|
||||
<style name="smallDataViewStyle">
|
||||
<item name="android:layout_width">0dp</item>
|
||||
<item name="android:layout_width">100dp</item>
|
||||
<item name="android:layout_height">wrap_content</item>
|
||||
<item name="android:layout_weight">1</item>
|
||||
<item name="android:layout_marginLeft">8dp</item>
|
||||
<item name="android:layout_marginRight">8dp</item>
|
||||
<item name="android:textColor">@color/fadedTextColor</item>
|
||||
</style>
|
||||
|
||||
</resources>
|
||||
|
||||
Reference in New Issue
Block a user