mirror of
https://github.com/iSoron/uhabits.git
synced 2025-12-06 09:08:52 -06:00
Merge branch 'feature/numerical-habits' into dev
This commit is contained in:
@@ -12,7 +12,7 @@ android {
|
|||||||
minSdkVersion 15
|
minSdkVersion 15
|
||||||
targetSdkVersion 25
|
targetSdkVersion 25
|
||||||
|
|
||||||
buildConfigField "Integer", "databaseVersion", "15"
|
buildConfigField "Integer", "databaseVersion", "18"
|
||||||
buildConfigField "String", "databaseFilename", "\"uhabits.db\""
|
buildConfigField "String", "databaseFilename", "\"uhabits.db\""
|
||||||
|
|
||||||
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
|
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
|
||||||
|
|||||||
@@ -91,97 +91,4 @@ public class CheckmarkButtonViewTest extends BaseViewTest
|
|||||||
{
|
{
|
||||||
assertRenders(view, PATH + "render_unchecked.png");
|
assertRenders(view, PATH + "render_unchecked.png");
|
||||||
}
|
}
|
||||||
|
|
||||||
// @Test
|
|
||||||
// public void testLongClick() throws Exception
|
|
||||||
// {
|
|
||||||
// setOnToggleListener();
|
|
||||||
// view.performLongClick();
|
|
||||||
// waitForLatch();
|
|
||||||
// assertRendersCheckedExplicitly();
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// @Test
|
|
||||||
// public void testClick_withShortToggle_fromUnchecked() throws Exception
|
|
||||||
// {
|
|
||||||
// Preferences.getInstance().setShortToggleEnabled(true);
|
|
||||||
// view.setValue(Checkmark.UNCHECKED);
|
|
||||||
// setOnToggleListenerAndPerformClick();
|
|
||||||
// assertRendersCheckedExplicitly();
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// @Test
|
|
||||||
// public void testClick_withShortToggle_fromChecked() throws Exception
|
|
||||||
// {
|
|
||||||
// Preferences.getInstance().setShortToggleEnabled(true);
|
|
||||||
// view.setValue(Checkmark.CHECKED_EXPLICITLY);
|
|
||||||
// setOnToggleListenerAndPerformClick();
|
|
||||||
// assertRendersUnchecked();
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// @Test
|
|
||||||
// public void testClick_withShortToggle_withoutListener() throws Exception
|
|
||||||
// {
|
|
||||||
// Preferences.getInstance().setShortToggleEnabled(true);
|
|
||||||
// view.setValue(Checkmark.CHECKED_EXPLICITLY);
|
|
||||||
// view.setController(null);
|
|
||||||
// view.performClick();
|
|
||||||
// assertRendersUnchecked();
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// protected void setOnToggleListenerAndPerformClick() throws InterruptedException
|
|
||||||
// {
|
|
||||||
// setOnToggleListener();
|
|
||||||
// view.performClick();
|
|
||||||
// waitForLatch();
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// @Test
|
|
||||||
// public void testClick_withoutShortToggle() throws Exception
|
|
||||||
// {
|
|
||||||
// Preferences.getInstance().setShortToggleEnabled(false);
|
|
||||||
// setOnInvalidToggleListener();
|
|
||||||
// view.performClick();
|
|
||||||
// waitForLatch();
|
|
||||||
// assertRendersUnchecked();
|
|
||||||
// }
|
|
||||||
|
|
||||||
// protected void setOnInvalidToggleListener()
|
|
||||||
// {
|
|
||||||
// view.setController(new CheckmarkButtonView.Controller()
|
|
||||||
// {
|
|
||||||
// @Override
|
|
||||||
// public void onToggleCheckmark(CheckmarkButtonView view, long timestamp)
|
|
||||||
// {
|
|
||||||
// fail();
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// @Override
|
|
||||||
// public void onInvalidToggle(CheckmarkButtonView v)
|
|
||||||
// {
|
|
||||||
// assertThat(v, equalTo(view));
|
|
||||||
// latch.countDown();
|
|
||||||
// }
|
|
||||||
// });
|
|
||||||
// }
|
|
||||||
|
|
||||||
// protected void setOnToggleListener()
|
|
||||||
// {
|
|
||||||
// view.setController(new CheckmarkButtonView.Controller()
|
|
||||||
// {
|
|
||||||
// @Override
|
|
||||||
// public void onToggleCheckmark(CheckmarkButtonView v, long t)
|
|
||||||
// {
|
|
||||||
// assertThat(v, equalTo(view));
|
|
||||||
// assertThat(t, equalTo(DateUtils.getStartOfToday()));
|
|
||||||
// latch.countDown();
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// @Override
|
|
||||||
// public void onInvalidToggle(CheckmarkButtonView view)
|
|
||||||
// {
|
|
||||||
// fail();
|
|
||||||
// }
|
|
||||||
// });
|
|
||||||
// }
|
|
||||||
}
|
}
|
||||||
@@ -59,7 +59,7 @@ public class CheckmarkPanelViewTest extends BaseViewTest
|
|||||||
|
|
||||||
view = new CheckmarkPanelView(targetContext);
|
view = new CheckmarkPanelView(targetContext);
|
||||||
view.setHabit(habit);
|
view.setHabit(habit);
|
||||||
view.setCheckmarkValues(checkmarks);
|
view.setValues(checkmarks);
|
||||||
view.setButtonCount(4);
|
view.setButtonCount(4);
|
||||||
view.setColor(ColorUtils.getAndroidTestColor(7));
|
view.setColor(ColorUtils.getAndroidTestColor(7));
|
||||||
|
|
||||||
|
|||||||
@@ -59,7 +59,7 @@ public class HabitCardViewTest extends BaseViewTest
|
|||||||
|
|
||||||
view = new HabitCardView(targetContext);
|
view = new HabitCardView(targetContext);
|
||||||
view.setHabit(habit);
|
view.setHabit(habit);
|
||||||
view.setCheckmarkValues(values);
|
view.setValues(values);
|
||||||
view.setSelected(false);
|
view.setSelected(false);
|
||||||
view.setScore(habit.getScores().getTodayValue());
|
view.setScore(habit.getScores().getTodayValue());
|
||||||
view.setController(controller);
|
view.setController(controller);
|
||||||
|
|||||||
@@ -182,7 +182,7 @@ public class MainActivityActions
|
|||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
onView(allOf(withId(R.id.sFrequency),
|
onView(allOf(withId(R.id.spinner),
|
||||||
withEffectiveVisibility(VISIBLE))).perform(click());
|
withEffectiveVisibility(VISIBLE))).perform(click());
|
||||||
onData(allOf(instanceOf(String.class), startsWith("Custom")))
|
onData(allOf(instanceOf(String.class), startsWith("Custom")))
|
||||||
.inRoot(isPlatformPopup())
|
.inRoot(isPlatformPopup())
|
||||||
@@ -193,7 +193,7 @@ public class MainActivityActions
|
|||||||
// ignored
|
// ignored
|
||||||
}
|
}
|
||||||
|
|
||||||
onView(withId(R.id.tvFreqNum)).perform(replaceText(num));
|
onView(withId(R.id.numerator)).perform(replaceText(num));
|
||||||
onView(withId(R.id.tvFreqDen)).perform(replaceText(den));
|
onView(withId(R.id.denominator)).perform(replaceText(den));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -37,6 +37,7 @@ import java.util.*;
|
|||||||
import static org.hamcrest.MatcherAssert.*;
|
import static org.hamcrest.MatcherAssert.*;
|
||||||
import static org.hamcrest.Matchers.*;
|
import static org.hamcrest.Matchers.*;
|
||||||
import static org.hamcrest.core.IsNot.not;
|
import static org.hamcrest.core.IsNot.not;
|
||||||
|
import static org.isoron.uhabits.models.Checkmark.*;
|
||||||
|
|
||||||
@RunWith(AndroidJUnit4.class)
|
@RunWith(AndroidJUnit4.class)
|
||||||
@MediumTest
|
@MediumTest
|
||||||
@@ -67,7 +68,7 @@ public class SQLiteRepetitionListTest extends BaseAndroidTest
|
|||||||
RepetitionRecord record = getByTimestamp(today + day);
|
RepetitionRecord record = getByTimestamp(today + day);
|
||||||
assertThat(record, is(nullValue()));
|
assertThat(record, is(nullValue()));
|
||||||
|
|
||||||
Repetition rep = new Repetition(today + day);
|
Repetition rep = new Repetition(today + day, CHECKED_EXPLICITLY);
|
||||||
habit.getRepetitions().add(rep);
|
habit.getRepetitions().add(rep);
|
||||||
|
|
||||||
record = getByTimestamp(today + day);
|
record = getByTimestamp(today + day);
|
||||||
|
|||||||
@@ -50,8 +50,8 @@ public class CheckmarkWidgetViewTest extends BaseViewTest
|
|||||||
habit = fixtures.createShortHabit();
|
habit = fixtures.createShortHabit();
|
||||||
view = new CheckmarkWidgetView(targetContext);
|
view = new CheckmarkWidgetView(targetContext);
|
||||||
int color = ColorUtils.getAndroidTestColor(habit.getColor());
|
int color = ColorUtils.getAndroidTestColor(habit.getColor());
|
||||||
int score = habit.getScores().getTodayValue();
|
double score = habit.getScores().getTodayValue();
|
||||||
float percentage = (float) score / Score.MAX_VALUE;
|
float percentage = (float) score;
|
||||||
|
|
||||||
view.setActiveColor(color);
|
view.setActiveColor(color);
|
||||||
view.setCheckmarkValue(habit.getCheckmarks().getTodayValue());
|
view.setCheckmarkValue(habit.getCheckmarks().getTodayValue());
|
||||||
|
|||||||
2
app/src/main/assets/migrations/16.sql
Normal file
2
app/src/main/assets/migrations/16.sql
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
alter table Habits add column type integer not null default 0;
|
||||||
|
alter table Repetitions add column value integer not null default 2;
|
||||||
5
app/src/main/assets/migrations/17.sql
Normal file
5
app/src/main/assets/migrations/17.sql
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
DROP TABLE Score;
|
||||||
|
CREATE TABLE Score (Id INTEGER PRIMARY KEY AUTOINCREMENT, habit INTEGER REFERENCES Habits(Id), score REAL, timestamp INTEGER);
|
||||||
|
CREATE INDEX idx_score_habit_timestamp on score(habit, timestamp);
|
||||||
|
delete from Streak;
|
||||||
|
delete from Checkmarks;
|
||||||
3
app/src/main/assets/migrations/18.sql
Normal file
3
app/src/main/assets/migrations/18.sql
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
alter table Habits add column target_type integer not null default 0;
|
||||||
|
alter table Habits add column target_value real not null default 0;
|
||||||
|
alter table Habits add column unit text not null default "";
|
||||||
@@ -26,6 +26,7 @@ import android.support.v7.app.AlertDialog;
|
|||||||
import android.support.v7.app.*;
|
import android.support.v7.app.*;
|
||||||
|
|
||||||
import org.isoron.uhabits.*;
|
import org.isoron.uhabits.*;
|
||||||
|
import org.isoron.uhabits.models.*;
|
||||||
import org.isoron.uhabits.utils.*;
|
import org.isoron.uhabits.utils.*;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -35,7 +36,6 @@ public class WeekdayPickerDialog extends AppCompatDialogFragment implements
|
|||||||
DialogInterface.OnMultiChoiceClickListener,
|
DialogInterface.OnMultiChoiceClickListener,
|
||||||
DialogInterface.OnClickListener
|
DialogInterface.OnClickListener
|
||||||
{
|
{
|
||||||
|
|
||||||
private boolean[] selectedDays;
|
private boolean[] selectedDays;
|
||||||
|
|
||||||
private OnWeekdaysPickedListener listener;
|
private OnWeekdaysPickedListener listener;
|
||||||
@@ -49,7 +49,8 @@ public class WeekdayPickerDialog extends AppCompatDialogFragment implements
|
|||||||
@Override
|
@Override
|
||||||
public void onClick(DialogInterface dialog, int which)
|
public void onClick(DialogInterface dialog, int which)
|
||||||
{
|
{
|
||||||
if (listener != null) listener.onWeekdaysPicked(selectedDays);
|
if (listener != null)
|
||||||
|
listener.onWeekdaysSet(new WeekdayList(selectedDays));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -73,13 +74,13 @@ public class WeekdayPickerDialog extends AppCompatDialogFragment implements
|
|||||||
this.listener = listener;
|
this.listener = listener;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setSelectedDays(boolean[] selectedDays)
|
public void setSelectedDays(WeekdayList days)
|
||||||
{
|
{
|
||||||
this.selectedDays = selectedDays;
|
this.selectedDays = days.toArray();
|
||||||
}
|
}
|
||||||
|
|
||||||
public interface OnWeekdaysPickedListener
|
public interface OnWeekdaysPickedListener
|
||||||
{
|
{
|
||||||
void onWeekdaysPicked(boolean[] selectedDays);
|
void onWeekdaysSet(WeekdayList days);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,479 @@
|
|||||||
|
/*
|
||||||
|
* 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.support.annotation.*;
|
||||||
|
import android.util.*;
|
||||||
|
|
||||||
|
import org.isoron.uhabits.*;
|
||||||
|
import org.isoron.uhabits.activities.habits.list.views.*;
|
||||||
|
import org.isoron.uhabits.models.*;
|
||||||
|
import org.isoron.uhabits.utils.*;
|
||||||
|
|
||||||
|
import java.text.*;
|
||||||
|
import java.util.*;
|
||||||
|
|
||||||
|
import static org.isoron.uhabits.utils.InterfaceUtils.*;
|
||||||
|
|
||||||
|
public class BarChart extends ScrollableChart
|
||||||
|
{
|
||||||
|
private static final PorterDuffXfermode XFERMODE_CLEAR =
|
||||||
|
new PorterDuffXfermode(PorterDuff.Mode.CLEAR);
|
||||||
|
|
||||||
|
private static final PorterDuffXfermode XFERMODE_SRC =
|
||||||
|
new PorterDuffXfermode(PorterDuff.Mode.SRC);
|
||||||
|
|
||||||
|
private Paint pGrid;
|
||||||
|
|
||||||
|
private float em;
|
||||||
|
|
||||||
|
private SimpleDateFormat dfMonth;
|
||||||
|
|
||||||
|
private SimpleDateFormat dfDay;
|
||||||
|
|
||||||
|
private SimpleDateFormat dfYear;
|
||||||
|
|
||||||
|
private Paint pText, pGraph;
|
||||||
|
|
||||||
|
private RectF rect, prevRect;
|
||||||
|
|
||||||
|
private int baseSize;
|
||||||
|
|
||||||
|
private int paddingTop;
|
||||||
|
|
||||||
|
private float columnWidth;
|
||||||
|
|
||||||
|
private int columnHeight;
|
||||||
|
|
||||||
|
private int nColumns;
|
||||||
|
|
||||||
|
private int textColor;
|
||||||
|
|
||||||
|
private int gridColor;
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
private List<Checkmark> checkmarks;
|
||||||
|
|
||||||
|
private int primaryColor;
|
||||||
|
|
||||||
|
@Deprecated
|
||||||
|
private int bucketSize = 7;
|
||||||
|
|
||||||
|
private int backgroundColor;
|
||||||
|
|
||||||
|
private Bitmap drawingCache;
|
||||||
|
|
||||||
|
private Canvas cacheCanvas;
|
||||||
|
|
||||||
|
private boolean isTransparencyEnabled;
|
||||||
|
|
||||||
|
private int skipYear = 0;
|
||||||
|
|
||||||
|
private String previousYearText;
|
||||||
|
|
||||||
|
private String previousMonthText;
|
||||||
|
|
||||||
|
private double maxValue;
|
||||||
|
|
||||||
|
private double target;
|
||||||
|
|
||||||
|
public BarChart(Context context)
|
||||||
|
{
|
||||||
|
super(context);
|
||||||
|
init();
|
||||||
|
}
|
||||||
|
|
||||||
|
public BarChart(Context context, AttributeSet attrs)
|
||||||
|
{
|
||||||
|
super(context, attrs);
|
||||||
|
init();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void populateWithRandomData()
|
||||||
|
{
|
||||||
|
Random random = new Random();
|
||||||
|
List<Checkmark> checkmarks = new LinkedList<>();
|
||||||
|
|
||||||
|
long timestamp = DateUtils.getStartOfToday();
|
||||||
|
long day = DateUtils.millisecondsInOneDay;
|
||||||
|
|
||||||
|
for (int i = 1; i < 100; i++)
|
||||||
|
{
|
||||||
|
int value = random.nextInt(1000);
|
||||||
|
checkmarks.add(new Checkmark(timestamp, value));
|
||||||
|
timestamp -= day;
|
||||||
|
}
|
||||||
|
|
||||||
|
setCheckmarks(checkmarks);
|
||||||
|
setTarget(0.5);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Deprecated
|
||||||
|
public void setBucketSize(int bucketSize)
|
||||||
|
{
|
||||||
|
this.bucketSize = bucketSize;
|
||||||
|
postInvalidate();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setCheckmarks(@NonNull List<Checkmark> checkmarks)
|
||||||
|
{
|
||||||
|
this.checkmarks = checkmarks;
|
||||||
|
|
||||||
|
maxValue = 1.0;
|
||||||
|
for (Checkmark c : checkmarks)
|
||||||
|
maxValue = Math.max(maxValue, c.getValue());
|
||||||
|
maxValue = Math.ceil(maxValue / 1000 * 1.05) * 1000;
|
||||||
|
|
||||||
|
postInvalidate();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setColor(int primaryColor)
|
||||||
|
{
|
||||||
|
this.primaryColor = primaryColor;
|
||||||
|
postInvalidate();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setIsTransparencyEnabled(boolean enabled)
|
||||||
|
{
|
||||||
|
this.isTransparencyEnabled = enabled;
|
||||||
|
initColors();
|
||||||
|
requestLayout();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setTarget(double target)
|
||||||
|
{
|
||||||
|
this.target = target;
|
||||||
|
postInvalidate();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onDraw(Canvas canvas)
|
||||||
|
{
|
||||||
|
super.onDraw(canvas);
|
||||||
|
Canvas activeCanvas;
|
||||||
|
|
||||||
|
if (isTransparencyEnabled)
|
||||||
|
{
|
||||||
|
if (drawingCache == null) initCache(getWidth(), getHeight());
|
||||||
|
|
||||||
|
activeCanvas = cacheCanvas;
|
||||||
|
drawingCache.eraseColor(Color.TRANSPARENT);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
activeCanvas = canvas;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (checkmarks == null) return;
|
||||||
|
|
||||||
|
rect.set(0, 0, nColumns * columnWidth, columnHeight);
|
||||||
|
rect.offset(0, paddingTop);
|
||||||
|
|
||||||
|
drawGrid(activeCanvas, rect);
|
||||||
|
|
||||||
|
pText.setColor(textColor);
|
||||||
|
pGraph.setColor(primaryColor);
|
||||||
|
prevRect.setEmpty();
|
||||||
|
|
||||||
|
previousMonthText = "";
|
||||||
|
previousYearText = "";
|
||||||
|
skipYear = 0;
|
||||||
|
|
||||||
|
for (int k = 0; k < nColumns; k++)
|
||||||
|
{
|
||||||
|
int offset = nColumns - k - 1 + getDataOffset();
|
||||||
|
if (offset >= checkmarks.size()) continue;
|
||||||
|
|
||||||
|
double value = checkmarks.get(offset).getValue();
|
||||||
|
long timestamp = checkmarks.get(offset).getTimestamp();
|
||||||
|
int height = (int) (columnHeight * value / maxValue);
|
||||||
|
|
||||||
|
rect.set(0, 0, baseSize, height);
|
||||||
|
rect.offset(k * columnWidth + (columnWidth - baseSize) / 2,
|
||||||
|
paddingTop + columnHeight - height);
|
||||||
|
|
||||||
|
drawValue(activeCanvas, rect, value);
|
||||||
|
drawBar(activeCanvas, rect, value);
|
||||||
|
|
||||||
|
prevRect.set(rect);
|
||||||
|
rect.set(0, 0, columnWidth, columnHeight);
|
||||||
|
rect.offset(k * columnWidth, paddingTop);
|
||||||
|
|
||||||
|
drawFooter(activeCanvas, rect, timestamp);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (activeCanvas != canvas) canvas.drawBitmap(drawingCache, 0, 0, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec)
|
||||||
|
{
|
||||||
|
int width = MeasureSpec.getSize(widthMeasureSpec);
|
||||||
|
int height = MeasureSpec.getSize(heightMeasureSpec);
|
||||||
|
setMeasuredDimension(width, height);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onSizeChanged(int width,
|
||||||
|
int height,
|
||||||
|
int oldWidth,
|
||||||
|
int oldHeight)
|
||||||
|
{
|
||||||
|
if (height < 9) height = 200;
|
||||||
|
|
||||||
|
float maxTextSize = getResources().getDimension(R.dimen.tinyTextSize);
|
||||||
|
float textSize = height * 0.06f;
|
||||||
|
pText.setTextSize(Math.min(textSize, maxTextSize));
|
||||||
|
em = pText.getFontSpacing();
|
||||||
|
|
||||||
|
int footerHeight = (int) (3 * em);
|
||||||
|
paddingTop = (int) (em);
|
||||||
|
|
||||||
|
baseSize = (height - footerHeight - paddingTop) / 12;
|
||||||
|
columnWidth = baseSize;
|
||||||
|
columnWidth = Math.max(columnWidth, getMaxDayWidth() * 1.5f);
|
||||||
|
columnWidth = Math.max(columnWidth, getMaxMonthWidth() * 1.2f);
|
||||||
|
|
||||||
|
nColumns = (int) (width / columnWidth);
|
||||||
|
columnWidth = (float) width / nColumns;
|
||||||
|
setScrollerBucketSize((int) columnWidth);
|
||||||
|
|
||||||
|
columnHeight = 12 * baseSize;
|
||||||
|
|
||||||
|
float minStrokeWidth = dpToPixels(getContext(), 1);
|
||||||
|
pGraph.setTextSize(baseSize * 0.5f);
|
||||||
|
pGraph.setStrokeWidth(baseSize * 0.1f);
|
||||||
|
pGrid.setStrokeWidth(Math.min(minStrokeWidth, baseSize * 0.05f));
|
||||||
|
|
||||||
|
if (isTransparencyEnabled) initCache(width, height);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void drawBar(Canvas canvas, RectF rect, double value)
|
||||||
|
{
|
||||||
|
float margin = baseSize * 0.225f;
|
||||||
|
|
||||||
|
int color = textColor;
|
||||||
|
if (value / 1000 >= target) color = primaryColor;
|
||||||
|
|
||||||
|
rect.inset(-margin, 0);
|
||||||
|
setModeOrColor(pGraph, XFERMODE_CLEAR, backgroundColor);
|
||||||
|
canvas.drawRect(rect, pGraph);
|
||||||
|
|
||||||
|
rect.inset(margin, 0);
|
||||||
|
setModeOrColor(pGraph, XFERMODE_SRC, color);
|
||||||
|
canvas.drawRect(rect, pGraph);
|
||||||
|
|
||||||
|
if (isTransparencyEnabled) pGraph.setXfermode(XFERMODE_SRC);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void drawFooter(Canvas canvas, RectF rect, long currentDate)
|
||||||
|
{
|
||||||
|
String yearText = dfYear.format(currentDate);
|
||||||
|
String monthText = dfMonth.format(currentDate);
|
||||||
|
String dayText = dfDay.format(currentDate);
|
||||||
|
|
||||||
|
GregorianCalendar calendar = DateUtils.getCalendar(currentDate);
|
||||||
|
pText.setColor(textColor);
|
||||||
|
|
||||||
|
String text;
|
||||||
|
int year = calendar.get(Calendar.YEAR);
|
||||||
|
|
||||||
|
boolean shouldPrintYear = true;
|
||||||
|
if (yearText.equals(previousYearText)) shouldPrintYear = false;
|
||||||
|
if (bucketSize >= 365 && (year % 2) != 0) shouldPrintYear = false;
|
||||||
|
|
||||||
|
if (skipYear > 0)
|
||||||
|
{
|
||||||
|
skipYear--;
|
||||||
|
shouldPrintYear = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (shouldPrintYear)
|
||||||
|
{
|
||||||
|
previousYearText = yearText;
|
||||||
|
previousMonthText = "";
|
||||||
|
|
||||||
|
pText.setTextAlign(Paint.Align.CENTER);
|
||||||
|
canvas.drawText(yearText, rect.centerX(), rect.bottom + em * 2.2f, pText);
|
||||||
|
skipYear = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (bucketSize < 365)
|
||||||
|
{
|
||||||
|
if (!monthText.equals(previousMonthText))
|
||||||
|
{
|
||||||
|
previousMonthText = monthText;
|
||||||
|
text = monthText;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
text = dayText;
|
||||||
|
}
|
||||||
|
|
||||||
|
canvas.drawText(text, rect.centerX(), rect.bottom + em * 1.2f,
|
||||||
|
pText);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void drawGrid(Canvas canvas, RectF rGrid)
|
||||||
|
{
|
||||||
|
int nRows = 5;
|
||||||
|
float rowHeight = rGrid.height() / nRows;
|
||||||
|
|
||||||
|
pText.setColor(textColor);
|
||||||
|
pGrid.setColor(gridColor);
|
||||||
|
|
||||||
|
for (int i = 0; i < nRows; i++)
|
||||||
|
{
|
||||||
|
canvas.drawLine(rGrid.left, rGrid.top, rGrid.right, rGrid.top,
|
||||||
|
pGrid);
|
||||||
|
rGrid.offset(0, rowHeight);
|
||||||
|
}
|
||||||
|
|
||||||
|
canvas.drawLine(rGrid.left, rGrid.top, rGrid.right, rGrid.top, pGrid);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void drawValue(Canvas canvas, RectF rect, double value)
|
||||||
|
{
|
||||||
|
if (value == 0) return;
|
||||||
|
|
||||||
|
int activeColor = textColor;
|
||||||
|
if (value / 1000 >= target)
|
||||||
|
activeColor = primaryColor;
|
||||||
|
|
||||||
|
String label = NumberButtonView.formatValue(value / 1000);
|
||||||
|
Rect rText = new Rect();
|
||||||
|
pText.getTextBounds(label, 0, label.length(), rText);
|
||||||
|
|
||||||
|
float offset = 0.5f * em;
|
||||||
|
float x = rect.centerX();
|
||||||
|
float y = rect.top - offset;
|
||||||
|
int cap = (int) (-0.1f * em);
|
||||||
|
|
||||||
|
rText.offset((int) x, (int) y);
|
||||||
|
rText.offset(-rText.width() / 2, 0);
|
||||||
|
rText.inset(3 * cap, cap);
|
||||||
|
|
||||||
|
setModeOrColor(pText, XFERMODE_CLEAR, backgroundColor);
|
||||||
|
canvas.drawRect(rText, pText);
|
||||||
|
|
||||||
|
setModeOrColor(pText, XFERMODE_SRC, activeColor);
|
||||||
|
canvas.drawText(label, x, y, pText);
|
||||||
|
}
|
||||||
|
|
||||||
|
private float getMaxDayWidth()
|
||||||
|
{
|
||||||
|
float maxDayWidth = 0;
|
||||||
|
GregorianCalendar day = DateUtils.getStartOfTodayCalendar();
|
||||||
|
|
||||||
|
for (int i = 0; i < 28; i++)
|
||||||
|
{
|
||||||
|
day.set(Calendar.DAY_OF_MONTH, i);
|
||||||
|
float monthWidth = pText.measureText(dfMonth.format(day.getTime()));
|
||||||
|
maxDayWidth = Math.max(maxDayWidth, monthWidth);
|
||||||
|
}
|
||||||
|
|
||||||
|
return maxDayWidth;
|
||||||
|
}
|
||||||
|
|
||||||
|
private float getMaxMonthWidth()
|
||||||
|
{
|
||||||
|
float maxMonthWidth = 0;
|
||||||
|
GregorianCalendar day = DateUtils.getStartOfTodayCalendar();
|
||||||
|
|
||||||
|
for (int i = 0; i < 12; i++)
|
||||||
|
{
|
||||||
|
day.set(Calendar.MONTH, i);
|
||||||
|
float monthWidth = pText.measureText(dfMonth.format(day.getTime()));
|
||||||
|
maxMonthWidth = Math.max(maxMonthWidth, monthWidth);
|
||||||
|
}
|
||||||
|
|
||||||
|
return maxMonthWidth;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void init()
|
||||||
|
{
|
||||||
|
initPaints();
|
||||||
|
initColors();
|
||||||
|
initDateFormats();
|
||||||
|
initRects();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void initCache(int width, int height)
|
||||||
|
{
|
||||||
|
if (drawingCache != null) drawingCache.recycle();
|
||||||
|
drawingCache =
|
||||||
|
Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
|
||||||
|
cacheCanvas = new Canvas(drawingCache);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void initColors()
|
||||||
|
{
|
||||||
|
StyledResources res = new StyledResources(getContext());
|
||||||
|
|
||||||
|
primaryColor = Color.BLACK;
|
||||||
|
textColor = res.getColor(R.attr.mediumContrastTextColor);
|
||||||
|
gridColor = res.getColor(R.attr.lowContrastTextColor);
|
||||||
|
backgroundColor = res.getColor(R.attr.cardBackgroundColor);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void initDateFormats()
|
||||||
|
{
|
||||||
|
if (isInEditMode())
|
||||||
|
{
|
||||||
|
dfYear = new SimpleDateFormat("yyyy", Locale.US);
|
||||||
|
dfMonth = new SimpleDateFormat("MMM", Locale.US);
|
||||||
|
dfDay = new SimpleDateFormat("d", Locale.US);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
dfYear = DateFormats.fromSkeleton("yyyy");
|
||||||
|
dfMonth = DateFormats.fromSkeleton("MMM");
|
||||||
|
dfDay = DateFormats.fromSkeleton("d");
|
||||||
|
}
|
||||||
|
|
||||||
|
private void initPaints()
|
||||||
|
{
|
||||||
|
pText = new Paint();
|
||||||
|
pText.setAntiAlias(true);
|
||||||
|
pText.setTextAlign(Paint.Align.CENTER);
|
||||||
|
|
||||||
|
pGraph = new Paint();
|
||||||
|
pGraph.setTextAlign(Paint.Align.CENTER);
|
||||||
|
pGraph.setAntiAlias(true);
|
||||||
|
|
||||||
|
pGrid = new Paint();
|
||||||
|
pGrid.setAntiAlias(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void initRects()
|
||||||
|
{
|
||||||
|
rect = new RectF();
|
||||||
|
prevRect = new RectF();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void setModeOrColor(Paint p, PorterDuffXfermode mode, int color)
|
||||||
|
{
|
||||||
|
if (isTransparencyEnabled) p.setXfermode(mode);
|
||||||
|
else p.setColor(color);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -38,6 +38,8 @@ public class HistoryChart extends ScrollableChart
|
|||||||
{
|
{
|
||||||
private int[] checkmarks;
|
private int[] checkmarks;
|
||||||
|
|
||||||
|
private int target;
|
||||||
|
|
||||||
private Paint pSquareBg, pSquareFg, pTextHeader;
|
private Paint pSquareBg, pSquareFg, pTextHeader;
|
||||||
|
|
||||||
private float squareSpacing;
|
private float squareSpacing;
|
||||||
@@ -85,6 +87,8 @@ public class HistoryChart extends ScrollableChart
|
|||||||
|
|
||||||
private float headerOverflow = 0;
|
private float headerOverflow = 0;
|
||||||
|
|
||||||
|
private boolean isNumerical = false;
|
||||||
|
|
||||||
@NonNull
|
@NonNull
|
||||||
private Controller controller;
|
private Controller controller;
|
||||||
|
|
||||||
@@ -168,6 +172,11 @@ public class HistoryChart extends ScrollableChart
|
|||||||
this.controller = controller;
|
this.controller = controller;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void setNumerical(boolean numerical)
|
||||||
|
{
|
||||||
|
isNumerical = numerical;
|
||||||
|
}
|
||||||
|
|
||||||
public void setIsBackgroundTransparent(boolean isBackgroundTransparent)
|
public void setIsBackgroundTransparent(boolean isBackgroundTransparent)
|
||||||
{
|
{
|
||||||
this.isBackgroundTransparent = isBackgroundTransparent;
|
this.isBackgroundTransparent = isBackgroundTransparent;
|
||||||
@@ -179,6 +188,12 @@ public class HistoryChart extends ScrollableChart
|
|||||||
this.isEditable = isEditable;
|
this.isEditable = isEditable;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void setTarget(int target)
|
||||||
|
{
|
||||||
|
this.target = target;
|
||||||
|
postInvalidate();
|
||||||
|
}
|
||||||
|
|
||||||
protected void initPaints()
|
protected void initPaints()
|
||||||
{
|
{
|
||||||
pTextHeader = new Paint();
|
pTextHeader = new Paint();
|
||||||
@@ -323,7 +338,16 @@ public class HistoryChart extends ScrollableChart
|
|||||||
int checkmarkOffset)
|
int checkmarkOffset)
|
||||||
{
|
{
|
||||||
if (checkmarkOffset >= checkmarks.length) pSquareBg.setColor(colors[0]);
|
if (checkmarkOffset >= checkmarks.length) pSquareBg.setColor(colors[0]);
|
||||||
else pSquareBg.setColor(colors[checkmarks[checkmarkOffset]]);
|
else
|
||||||
|
{
|
||||||
|
int checkmark = checkmarks[checkmarkOffset];
|
||||||
|
if(checkmark == 0) pSquareBg.setColor(colors[0]);
|
||||||
|
else if(checkmark < target)
|
||||||
|
{
|
||||||
|
pSquareBg.setColor(isNumerical ? textColor : colors[1]);
|
||||||
|
}
|
||||||
|
else pSquareBg.setColor(colors[2]);
|
||||||
|
}
|
||||||
|
|
||||||
pSquareFg.setColor(reverseTextColor);
|
pSquareFg.setColor(reverseTextColor);
|
||||||
canvas.drawRect(location, pSquareBg);
|
canvas.drawRect(location, pSquareBg);
|
||||||
@@ -347,6 +371,7 @@ public class HistoryChart extends ScrollableChart
|
|||||||
isEditable = false;
|
isEditable = false;
|
||||||
checkmarks = new int[0];
|
checkmarks = new int[0];
|
||||||
controller = new Controller() {};
|
controller = new Controller() {};
|
||||||
|
target = 2;
|
||||||
|
|
||||||
initColors();
|
initColors();
|
||||||
initPaints();
|
initPaints();
|
||||||
|
|||||||
@@ -108,15 +108,15 @@ public class ScoreChart extends ScrollableChart
|
|||||||
Random random = new Random();
|
Random random = new Random();
|
||||||
scores = new LinkedList<>();
|
scores = new LinkedList<>();
|
||||||
|
|
||||||
int previous = Score.MAX_VALUE / 2;
|
double previous = 0.5f;
|
||||||
long timestamp = DateUtils.getStartOfToday();
|
long timestamp = DateUtils.getStartOfToday();
|
||||||
long day = DateUtils.millisecondsInOneDay;
|
long day = DateUtils.millisecondsInOneDay;
|
||||||
|
|
||||||
for (int i = 1; i < 100; i++)
|
for (int i = 1; i < 100; i++)
|
||||||
{
|
{
|
||||||
int step = Score.MAX_VALUE / 10;
|
double step = 0.1f;
|
||||||
int current = previous + random.nextInt(step * 2) - step;
|
double current = previous + random.nextDouble() * step * 2 - step;
|
||||||
current = Math.max(0, Math.min(Score.MAX_VALUE, current));
|
current = Math.max(0, Math.min(1.0f, current));
|
||||||
scores.add(new Score(timestamp, current));
|
scores.add(new Score(timestamp, current));
|
||||||
previous = current;
|
previous = current;
|
||||||
timestamp -= day;
|
timestamp -= day;
|
||||||
@@ -187,11 +187,10 @@ public class ScoreChart extends ScrollableChart
|
|||||||
int offset = nColumns - k - 1 + getDataOffset();
|
int offset = nColumns - k - 1 + getDataOffset();
|
||||||
if (offset >= scores.size()) continue;
|
if (offset >= scores.size()) continue;
|
||||||
|
|
||||||
int score = scores.get(offset).getValue();
|
double score = scores.get(offset).getValue();
|
||||||
long timestamp = scores.get(offset).getTimestamp();
|
long timestamp = scores.get(offset).getTimestamp();
|
||||||
|
|
||||||
double relativeScore = ((double) score) / Score.MAX_VALUE;
|
int height = (int) (columnHeight * score);
|
||||||
int height = (int) (columnHeight * relativeScore);
|
|
||||||
|
|
||||||
rect.set(0, 0, baseSize, baseSize);
|
rect.set(0, 0, baseSize, baseSize);
|
||||||
rect.offset(k * columnWidth + (columnWidth - baseSize) / 2,
|
rect.offset(k * columnWidth + (columnWidth - baseSize) / 2,
|
||||||
|
|||||||
@@ -1,263 +0,0 @@
|
|||||||
/*
|
|
||||||
* 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.edit;
|
|
||||||
|
|
||||||
import android.os.*;
|
|
||||||
import android.support.annotation.*;
|
|
||||||
import android.support.v7.app.*;
|
|
||||||
import android.text.format.*;
|
|
||||||
import android.view.*;
|
|
||||||
|
|
||||||
import com.android.datetimepicker.time.*;
|
|
||||||
|
|
||||||
import org.isoron.uhabits.*;
|
|
||||||
import org.isoron.uhabits.R;
|
|
||||||
import org.isoron.uhabits.activities.*;
|
|
||||||
import org.isoron.uhabits.activities.common.dialogs.*;
|
|
||||||
import org.isoron.uhabits.commands.*;
|
|
||||||
import org.isoron.uhabits.models.*;
|
|
||||||
import org.isoron.uhabits.preferences.*;
|
|
||||||
|
|
||||||
import java.util.*;
|
|
||||||
|
|
||||||
import butterknife.*;
|
|
||||||
|
|
||||||
public abstract class BaseDialog extends AppCompatDialogFragment
|
|
||||||
{
|
|
||||||
@Nullable
|
|
||||||
protected Habit originalHabit;
|
|
||||||
|
|
||||||
@Nullable
|
|
||||||
protected Habit modifiedHabit;
|
|
||||||
|
|
||||||
@Nullable
|
|
||||||
protected BaseDialogHelper helper;
|
|
||||||
|
|
||||||
protected Preferences prefs;
|
|
||||||
|
|
||||||
protected CommandRunner commandRunner;
|
|
||||||
|
|
||||||
protected HabitList habitList;
|
|
||||||
|
|
||||||
protected AppComponent appComponent;
|
|
||||||
|
|
||||||
protected ModelFactory modelFactory;
|
|
||||||
|
|
||||||
private ColorPickerDialogFactory colorPickerDialogFactory;
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onActivityCreated(Bundle savedInstanceState)
|
|
||||||
{
|
|
||||||
super.onActivityCreated(savedInstanceState);
|
|
||||||
|
|
||||||
BaseActivity activity = (BaseActivity) getActivity();
|
|
||||||
colorPickerDialogFactory =
|
|
||||||
activity.getComponent().getColorPickerDialogFactory();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public View onCreateView(LayoutInflater inflater,
|
|
||||||
ViewGroup container,
|
|
||||||
Bundle savedInstanceState)
|
|
||||||
{
|
|
||||||
View view = inflater.inflate(R.layout.edit_habit, container, false);
|
|
||||||
|
|
||||||
HabitsApplication app =
|
|
||||||
(HabitsApplication) getContext().getApplicationContext();
|
|
||||||
|
|
||||||
appComponent = app.getComponent();
|
|
||||||
prefs = appComponent.getPreferences();
|
|
||||||
habitList = appComponent.getHabitList();
|
|
||||||
commandRunner = appComponent.getCommandRunner();
|
|
||||||
modelFactory = appComponent.getModelFactory();
|
|
||||||
|
|
||||||
ButterKnife.bind(this, view);
|
|
||||||
|
|
||||||
helper = new BaseDialogHelper(this, view);
|
|
||||||
getDialog().setTitle(getTitle());
|
|
||||||
initializeHabits();
|
|
||||||
restoreSavedInstance(savedInstanceState);
|
|
||||||
helper.populateForm(modifiedHabit);
|
|
||||||
return view;
|
|
||||||
}
|
|
||||||
|
|
||||||
@OnItemSelected(R.id.sFrequency)
|
|
||||||
public void onFrequencySelected(int position)
|
|
||||||
{
|
|
||||||
if (position < 0 || position > 4) throw new IllegalArgumentException();
|
|
||||||
int freqNums[] = { 1, 1, 2, 5, 3 };
|
|
||||||
int freqDens[] = { 1, 7, 7, 7, 7 };
|
|
||||||
modifiedHabit.setFrequency(
|
|
||||||
new Frequency(freqNums[position], freqDens[position]));
|
|
||||||
helper.populateFrequencyFields(modifiedHabit);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
@SuppressWarnings("ConstantConditions")
|
|
||||||
public void onSaveInstanceState(Bundle outState)
|
|
||||||
{
|
|
||||||
super.onSaveInstanceState(outState);
|
|
||||||
outState.putInt("color", modifiedHabit.getColor());
|
|
||||||
if (modifiedHabit.hasReminder())
|
|
||||||
{
|
|
||||||
Reminder reminder = modifiedHabit.getReminder();
|
|
||||||
outState.putInt("reminderMin", reminder.getMinute());
|
|
||||||
outState.putInt("reminderHour", reminder.getHour());
|
|
||||||
outState.putInt("reminderDays", reminder.getDays().toInteger());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
protected abstract int getTitle();
|
|
||||||
|
|
||||||
protected abstract void initializeHabits();
|
|
||||||
|
|
||||||
protected void restoreSavedInstance(@Nullable Bundle bundle)
|
|
||||||
{
|
|
||||||
if (bundle == null) return;
|
|
||||||
modifiedHabit.setColor(
|
|
||||||
bundle.getInt("color", modifiedHabit.getColor()));
|
|
||||||
|
|
||||||
modifiedHabit.setReminder(null);
|
|
||||||
|
|
||||||
int hour = (bundle.getInt("reminderHour", -1));
|
|
||||||
int minute = (bundle.getInt("reminderMin", -1));
|
|
||||||
int days = (bundle.getInt("reminderDays", -1));
|
|
||||||
|
|
||||||
if (hour >= 0 && minute >= 0)
|
|
||||||
{
|
|
||||||
Reminder reminder =
|
|
||||||
new Reminder(hour, minute, new WeekdayList(days));
|
|
||||||
modifiedHabit.setReminder(reminder);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
protected abstract void saveHabit();
|
|
||||||
|
|
||||||
@OnClick(R.id.buttonDiscard)
|
|
||||||
void onButtonDiscardClick()
|
|
||||||
{
|
|
||||||
dismiss();
|
|
||||||
}
|
|
||||||
|
|
||||||
@OnClick(R.id.tvReminderTime)
|
|
||||||
@SuppressWarnings("ConstantConditions")
|
|
||||||
void onDateSpinnerClick()
|
|
||||||
{
|
|
||||||
int defaultHour = 8;
|
|
||||||
int defaultMin = 0;
|
|
||||||
|
|
||||||
if (modifiedHabit.hasReminder())
|
|
||||||
{
|
|
||||||
Reminder reminder = modifiedHabit.getReminder();
|
|
||||||
defaultHour = reminder.getHour();
|
|
||||||
defaultMin = reminder.getMinute();
|
|
||||||
}
|
|
||||||
|
|
||||||
showTimePicker(defaultHour, defaultMin);
|
|
||||||
}
|
|
||||||
|
|
||||||
@OnClick(R.id.buttonSave)
|
|
||||||
void onSaveButtonClick()
|
|
||||||
{
|
|
||||||
helper.parseFormIntoHabit(modifiedHabit);
|
|
||||||
if (!helper.validate(modifiedHabit)) return;
|
|
||||||
saveHabit();
|
|
||||||
dismiss();
|
|
||||||
}
|
|
||||||
|
|
||||||
@OnClick(R.id.tvReminderDays)
|
|
||||||
@SuppressWarnings("ConstantConditions")
|
|
||||||
void onWeekdayClick()
|
|
||||||
{
|
|
||||||
if (!modifiedHabit.hasReminder()) return;
|
|
||||||
Reminder reminder = modifiedHabit.getReminder();
|
|
||||||
|
|
||||||
WeekdayPickerDialog dialog = new WeekdayPickerDialog();
|
|
||||||
dialog.setListener(new OnWeekdaysPickedListener());
|
|
||||||
dialog.setSelectedDays(reminder.getDays().toArray());
|
|
||||||
dialog.show(getFragmentManager(), "weekdayPicker");
|
|
||||||
}
|
|
||||||
|
|
||||||
@OnClick(R.id.buttonPickColor)
|
|
||||||
void showColorPicker()
|
|
||||||
{
|
|
||||||
int color = modifiedHabit.getColor();
|
|
||||||
ColorPickerDialog picker = colorPickerDialogFactory.create(color);
|
|
||||||
|
|
||||||
picker.setListener(c -> {
|
|
||||||
prefs.setDefaultHabitColor(c);
|
|
||||||
modifiedHabit.setColor(c);
|
|
||||||
helper.populateColor(c);
|
|
||||||
});
|
|
||||||
|
|
||||||
picker.show(getFragmentManager(), "picker");
|
|
||||||
}
|
|
||||||
|
|
||||||
private void showTimePicker(int defaultHour, int defaultMin)
|
|
||||||
{
|
|
||||||
boolean is24HourMode = DateFormat.is24HourFormat(getContext());
|
|
||||||
TimePickerDialog timePicker =
|
|
||||||
TimePickerDialog.newInstance(new OnTimeSetListener(), defaultHour,
|
|
||||||
defaultMin, is24HourMode);
|
|
||||||
timePicker.show(getFragmentManager(), "timePicker");
|
|
||||||
}
|
|
||||||
|
|
||||||
private class OnTimeSetListener
|
|
||||||
implements TimePickerDialog.OnTimeSetListener
|
|
||||||
{
|
|
||||||
@Override
|
|
||||||
public void onTimeCleared(RadialPickerLayout view)
|
|
||||||
{
|
|
||||||
modifiedHabit.clearReminder();
|
|
||||||
helper.populateReminderFields(modifiedHabit);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onTimeSet(RadialPickerLayout view, int hour, int minute)
|
|
||||||
{
|
|
||||||
Reminder reminder =
|
|
||||||
new Reminder(hour, minute, WeekdayList.EVERY_DAY);
|
|
||||||
modifiedHabit.setReminder(reminder);
|
|
||||||
helper.populateReminderFields(modifiedHabit);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private class OnWeekdaysPickedListener
|
|
||||||
implements WeekdayPickerDialog.OnWeekdaysPickedListener
|
|
||||||
{
|
|
||||||
@Override
|
|
||||||
public void onWeekdaysPicked(boolean[] selectedDays)
|
|
||||||
{
|
|
||||||
if (isSelectionEmpty(selectedDays)) Arrays.fill(selectedDays, true);
|
|
||||||
|
|
||||||
Reminder oldReminder = modifiedHabit.getReminder();
|
|
||||||
modifiedHabit.setReminder(
|
|
||||||
new Reminder(oldReminder.getHour(), oldReminder.getMinute(),
|
|
||||||
new WeekdayList(selectedDays)));
|
|
||||||
helper.populateReminderFields(modifiedHabit);
|
|
||||||
}
|
|
||||||
|
|
||||||
private boolean isSelectionEmpty(boolean[] selectedDays)
|
|
||||||
{
|
|
||||||
for (boolean d : selectedDays) if (d) return false;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,195 +0,0 @@
|
|||||||
/*
|
|
||||||
* 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.edit;
|
|
||||||
|
|
||||||
import android.annotation.*;
|
|
||||||
import android.support.v4.app.*;
|
|
||||||
import android.view.*;
|
|
||||||
import android.widget.*;
|
|
||||||
|
|
||||||
import org.isoron.uhabits.R;
|
|
||||||
import org.isoron.uhabits.models.*;
|
|
||||||
import org.isoron.uhabits.utils.*;
|
|
||||||
|
|
||||||
import butterknife.*;
|
|
||||||
|
|
||||||
public class BaseDialogHelper
|
|
||||||
{
|
|
||||||
private DialogFragment frag;
|
|
||||||
|
|
||||||
@BindView(R.id.tvName)
|
|
||||||
TextView tvName;
|
|
||||||
|
|
||||||
@BindView(R.id.tvDescription)
|
|
||||||
TextView tvDescription;
|
|
||||||
|
|
||||||
@BindView(R.id.tvFreqNum)
|
|
||||||
TextView tvFreqNum;
|
|
||||||
|
|
||||||
@BindView(R.id.tvFreqDen)
|
|
||||||
TextView tvFreqDen;
|
|
||||||
|
|
||||||
@BindView(R.id.tvReminderTime)
|
|
||||||
TextView tvReminderTime;
|
|
||||||
|
|
||||||
@BindView(R.id.tvReminderDays)
|
|
||||||
TextView tvReminderDays;
|
|
||||||
|
|
||||||
@BindView(R.id.sFrequency)
|
|
||||||
Spinner sFrequency;
|
|
||||||
|
|
||||||
@BindView(R.id.llCustomFrequency)
|
|
||||||
ViewGroup llCustomFrequency;
|
|
||||||
|
|
||||||
@BindView(R.id.llReminderDays)
|
|
||||||
ViewGroup llReminderDays;
|
|
||||||
|
|
||||||
public BaseDialogHelper(DialogFragment frag, View view)
|
|
||||||
{
|
|
||||||
this.frag = frag;
|
|
||||||
ButterKnife.bind(this, view);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected void populateForm(final Habit habit)
|
|
||||||
{
|
|
||||||
if (habit.getName() != null) tvName.setText(habit.getName());
|
|
||||||
if (habit.getDescription() != null)
|
|
||||||
tvDescription.setText(habit.getDescription());
|
|
||||||
|
|
||||||
populateColor(habit.getColor());
|
|
||||||
populateFrequencyFields(habit);
|
|
||||||
populateReminderFields(habit);
|
|
||||||
}
|
|
||||||
|
|
||||||
void parseFormIntoHabit(Habit habit)
|
|
||||||
{
|
|
||||||
habit.setName(tvName.getText().toString().trim());
|
|
||||||
habit.setDescription(tvDescription.getText().toString().trim());
|
|
||||||
String freqNum = tvFreqNum.getText().toString();
|
|
||||||
String freqDen = tvFreqDen.getText().toString();
|
|
||||||
if (!freqNum.isEmpty() && !freqDen.isEmpty())
|
|
||||||
{
|
|
||||||
int numerator = Integer.parseInt(freqNum);
|
|
||||||
int denominator = Integer.parseInt(freqDen);
|
|
||||||
habit.setFrequency(new Frequency(numerator, denominator));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void populateColor(int paletteColor)
|
|
||||||
{
|
|
||||||
tvName.setTextColor(
|
|
||||||
ColorUtils.getColor(frag.getContext(), paletteColor));
|
|
||||||
}
|
|
||||||
|
|
||||||
@SuppressLint("SetTextI18n")
|
|
||||||
void populateFrequencyFields(Habit habit)
|
|
||||||
{
|
|
||||||
int quickSelectPosition = -1;
|
|
||||||
|
|
||||||
Frequency freq = habit.getFrequency();
|
|
||||||
|
|
||||||
if (freq.equals(Frequency.DAILY))
|
|
||||||
quickSelectPosition = 0;
|
|
||||||
|
|
||||||
else if (freq.equals(Frequency.WEEKLY))
|
|
||||||
quickSelectPosition = 1;
|
|
||||||
|
|
||||||
else if (freq.equals(Frequency.TWO_TIMES_PER_WEEK))
|
|
||||||
quickSelectPosition = 2;
|
|
||||||
|
|
||||||
else if (freq.equals(Frequency.FIVE_TIMES_PER_WEEK))
|
|
||||||
quickSelectPosition = 3;
|
|
||||||
|
|
||||||
if (quickSelectPosition >= 0)
|
|
||||||
showSimplifiedFrequency(quickSelectPosition);
|
|
||||||
|
|
||||||
else showCustomFrequency();
|
|
||||||
|
|
||||||
tvFreqNum.setText(Integer.toString(freq.getNumerator()));
|
|
||||||
tvFreqDen.setText(Integer.toString(freq.getDenominator()));
|
|
||||||
}
|
|
||||||
|
|
||||||
@SuppressWarnings("ConstantConditions")
|
|
||||||
void populateReminderFields(Habit habit)
|
|
||||||
{
|
|
||||||
if (!habit.hasReminder())
|
|
||||||
{
|
|
||||||
tvReminderTime.setText(R.string.reminder_off);
|
|
||||||
llReminderDays.setVisibility(View.GONE);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
Reminder reminder = habit.getReminder();
|
|
||||||
|
|
||||||
String time =
|
|
||||||
DateUtils.formatTime(frag.getContext(), reminder.getHour(),
|
|
||||||
reminder.getMinute());
|
|
||||||
tvReminderTime.setText(time);
|
|
||||||
llReminderDays.setVisibility(View.VISIBLE);
|
|
||||||
|
|
||||||
boolean weekdays[] = reminder.getDays().toArray();
|
|
||||||
tvReminderDays.setText(
|
|
||||||
DateUtils.formatWeekdayList(frag.getContext(), weekdays));
|
|
||||||
}
|
|
||||||
|
|
||||||
private void showCustomFrequency()
|
|
||||||
{
|
|
||||||
sFrequency.setVisibility(View.GONE);
|
|
||||||
llCustomFrequency.setVisibility(View.VISIBLE);
|
|
||||||
}
|
|
||||||
|
|
||||||
@SuppressLint("SetTextI18n")
|
|
||||||
private void showSimplifiedFrequency(int quickSelectPosition)
|
|
||||||
{
|
|
||||||
sFrequency.setVisibility(View.VISIBLE);
|
|
||||||
sFrequency.setSelection(quickSelectPosition);
|
|
||||||
llCustomFrequency.setVisibility(View.GONE);
|
|
||||||
}
|
|
||||||
|
|
||||||
boolean validate(Habit habit)
|
|
||||||
{
|
|
||||||
Boolean valid = true;
|
|
||||||
|
|
||||||
if (habit.getName().length() == 0)
|
|
||||||
{
|
|
||||||
tvName.setError(
|
|
||||||
frag.getString(R.string.validation_name_should_not_be_blank));
|
|
||||||
valid = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
Frequency freq = habit.getFrequency();
|
|
||||||
|
|
||||||
if (freq.getNumerator() <= 0)
|
|
||||||
{
|
|
||||||
tvFreqNum.setError(
|
|
||||||
frag.getString(R.string.validation_number_should_be_positive));
|
|
||||||
valid = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (freq.getNumerator() > freq.getDenominator())
|
|
||||||
{
|
|
||||||
tvFreqNum.setError(
|
|
||||||
frag.getString(R.string.validation_at_most_one_rep_per_day));
|
|
||||||
valid = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return valid;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,54 +0,0 @@
|
|||||||
/*
|
|
||||||
* 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.edit;
|
|
||||||
|
|
||||||
import com.google.auto.factory.*;
|
|
||||||
|
|
||||||
import org.isoron.uhabits.*;
|
|
||||||
import org.isoron.uhabits.commands.*;
|
|
||||||
import org.isoron.uhabits.models.*;
|
|
||||||
|
|
||||||
@AutoFactory(allowSubclasses = true)
|
|
||||||
public class CreateHabitDialog extends BaseDialog
|
|
||||||
{
|
|
||||||
@Override
|
|
||||||
protected int getTitle()
|
|
||||||
{
|
|
||||||
return R.string.create_habit;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void initializeHabits()
|
|
||||||
{
|
|
||||||
modifiedHabit = modelFactory.buildHabit();
|
|
||||||
modifiedHabit.setFrequency(Frequency.DAILY);
|
|
||||||
modifiedHabit.setColor(
|
|
||||||
prefs.getDefaultHabitColor(modifiedHabit.getColor()));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void saveHabit()
|
|
||||||
{
|
|
||||||
Command command = appComponent
|
|
||||||
.getCreateHabitCommandFactory()
|
|
||||||
.create(habitList, modifiedHabit);
|
|
||||||
commandRunner.execute(command, null);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -19,34 +19,242 @@
|
|||||||
|
|
||||||
package org.isoron.uhabits.activities.habits.edit;
|
package org.isoron.uhabits.activities.habits.edit;
|
||||||
|
|
||||||
import org.isoron.uhabits.*;
|
import android.content.*;
|
||||||
import org.isoron.uhabits.commands.*;
|
import android.os.*;
|
||||||
|
import android.support.annotation.*;
|
||||||
|
import android.support.v7.app.*;
|
||||||
|
import android.text.format.*;
|
||||||
|
import android.view.*;
|
||||||
|
|
||||||
public class EditHabitDialog extends BaseDialog
|
import com.android.datetimepicker.time.*;
|
||||||
|
|
||||||
|
import org.isoron.uhabits.*;
|
||||||
|
import org.isoron.uhabits.R;
|
||||||
|
import org.isoron.uhabits.activities.*;
|
||||||
|
import org.isoron.uhabits.activities.common.dialogs.*;
|
||||||
|
import org.isoron.uhabits.activities.habits.edit.views.*;
|
||||||
|
import org.isoron.uhabits.commands.*;
|
||||||
|
import org.isoron.uhabits.models.*;
|
||||||
|
import org.isoron.uhabits.preferences.*;
|
||||||
|
|
||||||
|
import butterknife.*;
|
||||||
|
|
||||||
|
import static android.view.View.*;
|
||||||
|
|
||||||
|
public class EditHabitDialog extends AppCompatDialogFragment
|
||||||
{
|
{
|
||||||
|
public static final String BUNDLE_HABIT_ID = "habitId";
|
||||||
|
|
||||||
|
public static final String BUNDLE_HABIT_TYPE = "habitType";
|
||||||
|
|
||||||
|
protected Habit originalHabit;
|
||||||
|
|
||||||
|
protected Preferences prefs;
|
||||||
|
|
||||||
|
protected CommandRunner commandRunner;
|
||||||
|
|
||||||
|
protected HabitList habitList;
|
||||||
|
|
||||||
|
protected AppComponent component;
|
||||||
|
|
||||||
|
protected ModelFactory modelFactory;
|
||||||
|
|
||||||
|
@BindView(R.id.namePanel)
|
||||||
|
NameDescriptionPanel namePanel;
|
||||||
|
|
||||||
|
@BindView(R.id.reminderPanel)
|
||||||
|
ReminderPanel reminderPanel;
|
||||||
|
|
||||||
|
@BindView(R.id.frequencyPanel)
|
||||||
|
FrequencyPanel frequencyPanel;
|
||||||
|
|
||||||
|
@BindView(R.id.targetPanel)
|
||||||
|
TargetPanel targetPanel;
|
||||||
|
|
||||||
|
private ColorPickerDialogFactory colorPickerDialogFactory;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
public int getTheme()
|
||||||
|
{
|
||||||
|
return R.style.DialogWithTitle;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onActivityCreated(Bundle savedInstanceState)
|
||||||
|
{
|
||||||
|
super.onActivityCreated(savedInstanceState);
|
||||||
|
|
||||||
|
BaseActivity activity = (BaseActivity) getActivity();
|
||||||
|
colorPickerDialogFactory =
|
||||||
|
activity.getComponent().getColorPickerDialogFactory();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public View onCreateView(LayoutInflater inflater,
|
||||||
|
ViewGroup container,
|
||||||
|
Bundle savedInstanceState)
|
||||||
|
{
|
||||||
|
View view;
|
||||||
|
view = inflater.inflate(R.layout.edit_habit, container, false);
|
||||||
|
|
||||||
|
initDependencies();
|
||||||
|
ButterKnife.bind(this, view);
|
||||||
|
|
||||||
|
getDialog().setTitle(getTitle());
|
||||||
|
originalHabit = parseHabitFromArguments();
|
||||||
|
|
||||||
|
populateForm();
|
||||||
|
setupReminderController();
|
||||||
|
setupNameController();
|
||||||
|
|
||||||
|
return view;
|
||||||
|
}
|
||||||
|
|
||||||
protected int getTitle()
|
protected int getTitle()
|
||||||
{
|
{
|
||||||
return R.string.edit_habit;
|
if (originalHabit == null) return R.string.edit_habit;
|
||||||
|
else return R.string.create_habit;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
protected void saveHabit(@NonNull Habit habit)
|
||||||
protected void initializeHabits()
|
|
||||||
{
|
{
|
||||||
Long habitId = (Long) getArguments().get("habitId");
|
if (originalHabit == null)
|
||||||
if (habitId == null)
|
{
|
||||||
throw new IllegalArgumentException("habitId must be specified");
|
commandRunner.execute(component
|
||||||
|
.getCreateHabitCommandFactory()
|
||||||
originalHabit = habitList.getById(habitId);
|
.create(habitList, habit), null);
|
||||||
modifiedHabit = modelFactory.buildHabit();
|
}
|
||||||
modifiedHabit.copyFrom(originalHabit);
|
else
|
||||||
|
{
|
||||||
|
commandRunner.execute(component.getEditHabitCommandFactory().
|
||||||
|
create(habitList, originalHabit, habit), originalHabit.getId());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
private int getTypeFromArguments()
|
||||||
protected void saveHabit()
|
|
||||||
{
|
{
|
||||||
Command command = appComponent.getEditHabitCommandFactory().
|
return getArguments().getInt(BUNDLE_HABIT_TYPE);
|
||||||
create(habitList, originalHabit, modifiedHabit);
|
}
|
||||||
commandRunner.execute(command, originalHabit.getId());
|
|
||||||
|
private void initDependencies()
|
||||||
|
{
|
||||||
|
Context appContext = getContext().getApplicationContext();
|
||||||
|
HabitsApplication app = (HabitsApplication) appContext;
|
||||||
|
|
||||||
|
component = app.getComponent();
|
||||||
|
prefs = component.getPreferences();
|
||||||
|
habitList = component.getHabitList();
|
||||||
|
commandRunner = component.getCommandRunner();
|
||||||
|
modelFactory = component.getModelFactory();
|
||||||
|
}
|
||||||
|
|
||||||
|
@OnClick(R.id.buttonDiscard)
|
||||||
|
void onButtonDiscardClick()
|
||||||
|
{
|
||||||
|
dismiss();
|
||||||
|
}
|
||||||
|
|
||||||
|
@OnClick(R.id.buttonSave)
|
||||||
|
void onSaveButtonClick()
|
||||||
|
{
|
||||||
|
int type = getTypeFromArguments();
|
||||||
|
|
||||||
|
if (!namePanel.validate()) return;
|
||||||
|
if (type == Habit.YES_NO_HABIT && !frequencyPanel.validate()) return;
|
||||||
|
if (type == Habit.NUMBER_HABIT && !targetPanel.validate()) return;
|
||||||
|
|
||||||
|
Habit habit = modelFactory.buildHabit();
|
||||||
|
habit.setName(namePanel.getName());
|
||||||
|
habit.setDescription(namePanel.getDescription());
|
||||||
|
habit.setColor(namePanel.getColor());
|
||||||
|
habit.setReminder(reminderPanel.getReminder());
|
||||||
|
habit.setFrequency(frequencyPanel.getFrequency());
|
||||||
|
habit.setUnit(targetPanel.getUnit());
|
||||||
|
habit.setTargetValue(targetPanel.getTargetValue());
|
||||||
|
habit.setType(type);
|
||||||
|
|
||||||
|
saveHabit(habit);
|
||||||
|
dismiss();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
private Habit parseHabitFromArguments()
|
||||||
|
{
|
||||||
|
Bundle arguments = getArguments();
|
||||||
|
if (arguments == null) return null;
|
||||||
|
|
||||||
|
Long id = (Long) arguments.get(BUNDLE_HABIT_ID);
|
||||||
|
if (id == null) return null;
|
||||||
|
|
||||||
|
Habit habit = habitList.getById(id);
|
||||||
|
if (habit == null) throw new IllegalStateException();
|
||||||
|
|
||||||
|
return habit;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void populateForm()
|
||||||
|
{
|
||||||
|
Habit habit = modelFactory.buildHabit();
|
||||||
|
habit.setFrequency(Frequency.DAILY);
|
||||||
|
habit.setColor(prefs.getDefaultHabitColor(habit.getColor()));
|
||||||
|
habit.setType(getTypeFromArguments());
|
||||||
|
|
||||||
|
if (originalHabit != null) habit.copyFrom(originalHabit);
|
||||||
|
|
||||||
|
if (habit.isNumerical()) frequencyPanel.setVisibility(GONE);
|
||||||
|
else targetPanel.setVisibility(GONE);
|
||||||
|
|
||||||
|
namePanel.populateFrom(habit);
|
||||||
|
frequencyPanel.setFrequency(habit.getFrequency());
|
||||||
|
targetPanel.setTargetValue(habit.getTargetValue());
|
||||||
|
targetPanel.setUnit(habit.getUnit());
|
||||||
|
if (habit.hasReminder()) reminderPanel.setReminder(habit.getReminder());
|
||||||
|
}
|
||||||
|
|
||||||
|
private void setupNameController()
|
||||||
|
{
|
||||||
|
namePanel.setController(new NameDescriptionPanel.Controller()
|
||||||
|
{
|
||||||
|
@Override
|
||||||
|
public void onColorPickerClicked(int previousColor)
|
||||||
|
{
|
||||||
|
ColorPickerDialog picker =
|
||||||
|
colorPickerDialogFactory.create(previousColor);
|
||||||
|
|
||||||
|
picker.setListener(c ->
|
||||||
|
{
|
||||||
|
prefs.setDefaultHabitColor(c);
|
||||||
|
namePanel.setColor(c);
|
||||||
|
});
|
||||||
|
|
||||||
|
picker.show(getFragmentManager(), "picker");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private void setupReminderController()
|
||||||
|
{
|
||||||
|
reminderPanel.setController(new ReminderPanel.Controller()
|
||||||
|
{
|
||||||
|
@Override
|
||||||
|
public void onTimeClicked(int currentHour, int currentMin)
|
||||||
|
{
|
||||||
|
TimePickerDialog timePicker;
|
||||||
|
boolean is24HourMode = DateFormat.is24HourFormat(getContext());
|
||||||
|
timePicker =
|
||||||
|
TimePickerDialog.newInstance(reminderPanel, currentHour,
|
||||||
|
currentMin, is24HourMode);
|
||||||
|
timePicker.show(getFragmentManager(), "timePicker");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onWeekdayClicked(WeekdayList currentDays)
|
||||||
|
{
|
||||||
|
WeekdayPickerDialog dialog = new WeekdayPickerDialog();
|
||||||
|
dialog.setListener(reminderPanel);
|
||||||
|
dialog.setSelectedDays(currentDays);
|
||||||
|
dialog.show(getFragmentManager(), "weekdayPicker");
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -26,6 +26,8 @@ import org.isoron.uhabits.models.*;
|
|||||||
|
|
||||||
import javax.inject.*;
|
import javax.inject.*;
|
||||||
|
|
||||||
|
import static org.isoron.uhabits.activities.habits.edit.EditHabitDialog.*;
|
||||||
|
|
||||||
public class EditHabitDialogFactory
|
public class EditHabitDialogFactory
|
||||||
{
|
{
|
||||||
@Inject
|
@Inject
|
||||||
@@ -33,14 +35,33 @@ public class EditHabitDialogFactory
|
|||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
public EditHabitDialog create(@NonNull Habit habit)
|
public EditHabitDialog createBoolean()
|
||||||
|
{
|
||||||
|
EditHabitDialog dialog = new EditHabitDialog();
|
||||||
|
Bundle args = new Bundle();
|
||||||
|
args.putInt(BUNDLE_HABIT_TYPE, Habit.YES_NO_HABIT);
|
||||||
|
dialog.setArguments(args);
|
||||||
|
return dialog;
|
||||||
|
}
|
||||||
|
|
||||||
|
public EditHabitDialog createNumerical()
|
||||||
|
{
|
||||||
|
EditHabitDialog dialog = new EditHabitDialog();
|
||||||
|
Bundle args = new Bundle();
|
||||||
|
args.putInt(BUNDLE_HABIT_TYPE, Habit.NUMBER_HABIT);
|
||||||
|
dialog.setArguments(args);
|
||||||
|
return dialog;
|
||||||
|
}
|
||||||
|
|
||||||
|
public EditHabitDialog edit(@NonNull Habit habit)
|
||||||
{
|
{
|
||||||
if (habit.getId() == null)
|
if (habit.getId() == null)
|
||||||
throw new IllegalArgumentException("habit not saved");
|
throw new IllegalArgumentException("habit not saved");
|
||||||
|
|
||||||
EditHabitDialog dialog = new EditHabitDialog();
|
EditHabitDialog dialog = new EditHabitDialog();
|
||||||
Bundle args = new Bundle();
|
Bundle args = new Bundle();
|
||||||
args.putLong("habitId", habit.getId());
|
args.putLong(BUNDLE_HABIT_ID, habit.getId());
|
||||||
|
args.putInt(BUNDLE_HABIT_TYPE, habit.getType());
|
||||||
dialog.setArguments(args);
|
dialog.setArguments(args);
|
||||||
return dialog;
|
return dialog;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,114 @@
|
|||||||
|
/*
|
||||||
|
* 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.edit.views;
|
||||||
|
|
||||||
|
import android.content.*;
|
||||||
|
import android.support.annotation.*;
|
||||||
|
import android.text.*;
|
||||||
|
import android.util.*;
|
||||||
|
import android.view.*;
|
||||||
|
import android.widget.*;
|
||||||
|
|
||||||
|
import org.isoron.uhabits.*;
|
||||||
|
import org.isoron.uhabits.utils.*;
|
||||||
|
|
||||||
|
import static org.isoron.uhabits.utils.AttributeSetUtils.*;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An EditText that shows an example usage when there is no text
|
||||||
|
* currently set. The example disappears when the widget gains focus.
|
||||||
|
*/
|
||||||
|
public class ExampleEditText extends EditText
|
||||||
|
implements View.OnFocusChangeListener
|
||||||
|
{
|
||||||
|
|
||||||
|
private String example;
|
||||||
|
|
||||||
|
private String realText;
|
||||||
|
|
||||||
|
private int color;
|
||||||
|
|
||||||
|
private int exampleColor;
|
||||||
|
|
||||||
|
private int inputType;
|
||||||
|
|
||||||
|
public ExampleEditText(Context context, @Nullable AttributeSet attrs)
|
||||||
|
{
|
||||||
|
super(context, attrs);
|
||||||
|
|
||||||
|
if (attrs != null)
|
||||||
|
example = getAttribute(context, attrs, "example", "");
|
||||||
|
|
||||||
|
inputType = getInputType();
|
||||||
|
realText = getText().toString();
|
||||||
|
color = getCurrentTextColor();
|
||||||
|
init();
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getRealText()
|
||||||
|
{
|
||||||
|
if(hasFocus()) return getText().toString();
|
||||||
|
else return realText;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onFocusChange(View v, boolean hasFocus)
|
||||||
|
{
|
||||||
|
if (!hasFocus) realText = getText().toString();
|
||||||
|
updateText();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setExample(String example)
|
||||||
|
{
|
||||||
|
this.example = example;
|
||||||
|
updateText();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setRealText(String realText)
|
||||||
|
{
|
||||||
|
this.realText = realText;
|
||||||
|
updateText();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void init()
|
||||||
|
{
|
||||||
|
StyledResources sr = new StyledResources(getContext());
|
||||||
|
exampleColor = sr.getColor(R.attr.mediumContrastTextColor);
|
||||||
|
setOnFocusChangeListener(this);
|
||||||
|
updateText();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void updateText()
|
||||||
|
{
|
||||||
|
if (realText.isEmpty() && !isFocused())
|
||||||
|
{
|
||||||
|
setTextColor(exampleColor);
|
||||||
|
setText(example);
|
||||||
|
setInputType(InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
setText(realText);
|
||||||
|
setTextColor(color);
|
||||||
|
setInputType(inputType);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,164 @@
|
|||||||
|
/*
|
||||||
|
* 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.edit.views;
|
||||||
|
|
||||||
|
import android.annotation.*;
|
||||||
|
import android.content.*;
|
||||||
|
import android.content.res.*;
|
||||||
|
import android.support.annotation.*;
|
||||||
|
import android.util.*;
|
||||||
|
import android.view.*;
|
||||||
|
import android.widget.*;
|
||||||
|
|
||||||
|
import org.isoron.uhabits.R;
|
||||||
|
import org.isoron.uhabits.models.*;
|
||||||
|
|
||||||
|
import butterknife.*;
|
||||||
|
|
||||||
|
import static org.isoron.uhabits.R.id.*;
|
||||||
|
|
||||||
|
|
||||||
|
public class FrequencyPanel extends FrameLayout
|
||||||
|
{
|
||||||
|
@BindView(numerator)
|
||||||
|
TextView tvNumerator;
|
||||||
|
|
||||||
|
@BindView(R.id.denominator)
|
||||||
|
TextView tvDenominator;
|
||||||
|
|
||||||
|
@BindView(R.id.spinner)
|
||||||
|
Spinner spinner;
|
||||||
|
|
||||||
|
@BindView(R.id.customFreqPanel)
|
||||||
|
ViewGroup customFreqPanel;
|
||||||
|
|
||||||
|
public FrequencyPanel(@NonNull Context context,
|
||||||
|
@Nullable AttributeSet attrs)
|
||||||
|
{
|
||||||
|
super(context, attrs);
|
||||||
|
|
||||||
|
View view = inflate(context, R.layout.edit_habit_frequency, null);
|
||||||
|
ButterKnife.bind(this, view);
|
||||||
|
addView(view);
|
||||||
|
}
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
public Frequency getFrequency()
|
||||||
|
{
|
||||||
|
String freqNum = tvNumerator.getText().toString();
|
||||||
|
String freqDen = tvDenominator.getText().toString();
|
||||||
|
|
||||||
|
if (!freqNum.isEmpty() && !freqDen.isEmpty())
|
||||||
|
{
|
||||||
|
int numerator = Integer.parseInt(freqNum);
|
||||||
|
int denominator = Integer.parseInt(freqDen);
|
||||||
|
return new Frequency(numerator, denominator);
|
||||||
|
}
|
||||||
|
|
||||||
|
return Frequency.DAILY;
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressLint("SetTextI18n")
|
||||||
|
public void setFrequency(@NonNull Frequency freq)
|
||||||
|
{
|
||||||
|
int position = getQuickSelectPosition(freq);
|
||||||
|
|
||||||
|
if (position >= 0) showSimplifiedFrequency(position);
|
||||||
|
else showCustomFrequency();
|
||||||
|
|
||||||
|
tvNumerator.setText(Integer.toString(freq.getNumerator()));
|
||||||
|
tvDenominator.setText(Integer.toString(freq.getDenominator()));
|
||||||
|
}
|
||||||
|
|
||||||
|
@OnItemSelected(R.id.spinner)
|
||||||
|
public void onFrequencySelected(int position)
|
||||||
|
{
|
||||||
|
if (position < 0 || position > 4) throw new IllegalArgumentException();
|
||||||
|
int freqNums[] = { 1, 1, 2, 5, 3 };
|
||||||
|
int freqDens[] = { 1, 7, 7, 7, 7 };
|
||||||
|
setFrequency(new Frequency(freqNums[position], freqDens[position]));
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean validate()
|
||||||
|
{
|
||||||
|
boolean valid = true;
|
||||||
|
Resources res = getResources();
|
||||||
|
|
||||||
|
String freqNum = tvNumerator.getText().toString();
|
||||||
|
String freqDen = tvDenominator.getText().toString();
|
||||||
|
|
||||||
|
if (freqDen.isEmpty())
|
||||||
|
{
|
||||||
|
tvDenominator.setError(
|
||||||
|
res.getString(R.string.validation_show_not_be_blank));
|
||||||
|
valid = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (freqNum.isEmpty())
|
||||||
|
{
|
||||||
|
tvNumerator.setError(
|
||||||
|
res.getString(R.string.validation_show_not_be_blank));
|
||||||
|
valid = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!valid) return false;
|
||||||
|
|
||||||
|
int numerator = Integer.parseInt(freqNum);
|
||||||
|
int denominator = Integer.parseInt(freqDen);
|
||||||
|
|
||||||
|
if (numerator <= 0)
|
||||||
|
{
|
||||||
|
tvNumerator.setError(
|
||||||
|
res.getString(R.string.validation_number_should_be_positive));
|
||||||
|
valid = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (numerator > denominator)
|
||||||
|
{
|
||||||
|
tvNumerator.setError(
|
||||||
|
res.getString(R.string.validation_at_most_one_rep_per_day));
|
||||||
|
valid = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return valid;
|
||||||
|
}
|
||||||
|
|
||||||
|
private int getQuickSelectPosition(@NonNull Frequency freq)
|
||||||
|
{
|
||||||
|
if (freq.equals(Frequency.DAILY)) return 0;
|
||||||
|
if (freq.equals(Frequency.WEEKLY)) return 1;
|
||||||
|
if (freq.equals(Frequency.TWO_TIMES_PER_WEEK)) return 2;
|
||||||
|
if (freq.equals(Frequency.FIVE_TIMES_PER_WEEK)) return 3;
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void showCustomFrequency()
|
||||||
|
{
|
||||||
|
spinner.setVisibility(View.GONE);
|
||||||
|
customFreqPanel.setVisibility(View.VISIBLE);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void showSimplifiedFrequency(int quickSelectPosition)
|
||||||
|
{
|
||||||
|
spinner.setVisibility(View.VISIBLE);
|
||||||
|
spinner.setSelection(quickSelectPosition);
|
||||||
|
customFreqPanel.setVisibility(View.GONE);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,152 @@
|
|||||||
|
/*
|
||||||
|
* 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.edit.views;
|
||||||
|
|
||||||
|
import android.content.*;
|
||||||
|
import android.content.res.*;
|
||||||
|
import android.os.*;
|
||||||
|
import android.support.annotation.*;
|
||||||
|
import android.util.*;
|
||||||
|
import android.view.*;
|
||||||
|
import android.widget.*;
|
||||||
|
|
||||||
|
import org.isoron.uhabits.R;
|
||||||
|
import org.isoron.uhabits.activities.common.views.*;
|
||||||
|
import org.isoron.uhabits.models.*;
|
||||||
|
import org.isoron.uhabits.utils.*;
|
||||||
|
|
||||||
|
import butterknife.*;
|
||||||
|
|
||||||
|
|
||||||
|
public class NameDescriptionPanel extends FrameLayout
|
||||||
|
{
|
||||||
|
@BindView(R.id.tvName)
|
||||||
|
EditText tvName;
|
||||||
|
|
||||||
|
@BindView(R.id.tvDescription)
|
||||||
|
ExampleEditText tvDescription;
|
||||||
|
|
||||||
|
private int color;
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
private Controller controller;
|
||||||
|
|
||||||
|
public NameDescriptionPanel(@NonNull Context context,
|
||||||
|
@Nullable AttributeSet attrs)
|
||||||
|
{
|
||||||
|
super(context, attrs);
|
||||||
|
|
||||||
|
View view = inflate(context, R.layout.edit_habit_name, null);
|
||||||
|
ButterKnife.bind(this, view);
|
||||||
|
addView(view);
|
||||||
|
|
||||||
|
controller = new Controller() {};
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getColor()
|
||||||
|
{
|
||||||
|
return color;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setColor(int color)
|
||||||
|
{
|
||||||
|
this.color = color;
|
||||||
|
tvName.setTextColor(ColorUtils.getColor(getContext(), color));
|
||||||
|
}
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
public String getDescription()
|
||||||
|
{
|
||||||
|
return tvDescription.getRealText().trim();
|
||||||
|
}
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
public String getName()
|
||||||
|
{
|
||||||
|
return tvName.getText().toString().trim();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void populateFrom(@NonNull Habit habit)
|
||||||
|
{
|
||||||
|
Resources res = getResources();
|
||||||
|
|
||||||
|
if(habit.isNumerical())
|
||||||
|
tvDescription.setExample(res.getString(R.string.example_question_numerical));
|
||||||
|
else
|
||||||
|
tvDescription.setExample(res.getString(R.string.example_question_boolean));
|
||||||
|
|
||||||
|
setColor(habit.getColor());
|
||||||
|
tvName.setText(habit.getName());
|
||||||
|
tvDescription.setRealText(habit.getDescription());
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean validate()
|
||||||
|
{
|
||||||
|
Resources res = getResources();
|
||||||
|
|
||||||
|
if (getName().isEmpty())
|
||||||
|
{
|
||||||
|
tvName.setError(
|
||||||
|
res.getString(R.string.validation_name_should_not_be_blank));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onRestoreInstanceState(Parcelable state)
|
||||||
|
{
|
||||||
|
BundleSavedState bss = (BundleSavedState) state;
|
||||||
|
setColor(bss.bundle.getInt("color"));
|
||||||
|
super.onRestoreInstanceState(bss.getSuperState());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected Parcelable onSaveInstanceState()
|
||||||
|
{
|
||||||
|
Parcelable superState = super.onSaveInstanceState();
|
||||||
|
Bundle bundle = new Bundle();
|
||||||
|
bundle.putInt("color", color);
|
||||||
|
return new BundleSavedState(superState, bundle);
|
||||||
|
}
|
||||||
|
|
||||||
|
@OnClick(R.id.buttonPickColor)
|
||||||
|
void showColorPicker()
|
||||||
|
{
|
||||||
|
controller.onColorPickerClicked(color);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setController(@NonNull Controller controller)
|
||||||
|
{
|
||||||
|
this.controller = controller;
|
||||||
|
}
|
||||||
|
|
||||||
|
public interface Controller
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Called when the user has clicked the widget to select a new
|
||||||
|
* color for the habit.
|
||||||
|
*
|
||||||
|
* @param previousColor the color previously selected
|
||||||
|
*/
|
||||||
|
default void onColorPickerClicked(int previousColor) {}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,194 @@
|
|||||||
|
/*
|
||||||
|
* 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.edit.views;
|
||||||
|
|
||||||
|
import android.content.*;
|
||||||
|
import android.os.*;
|
||||||
|
import android.support.annotation.*;
|
||||||
|
import android.util.*;
|
||||||
|
import android.view.*;
|
||||||
|
import android.widget.*;
|
||||||
|
|
||||||
|
import com.android.datetimepicker.time.*;
|
||||||
|
|
||||||
|
import org.isoron.uhabits.R;
|
||||||
|
import org.isoron.uhabits.activities.common.dialogs.*;
|
||||||
|
import org.isoron.uhabits.activities.common.views.*;
|
||||||
|
import org.isoron.uhabits.models.*;
|
||||||
|
|
||||||
|
import butterknife.*;
|
||||||
|
|
||||||
|
import static org.isoron.uhabits.utils.DateUtils.*;
|
||||||
|
|
||||||
|
public class ReminderPanel extends FrameLayout
|
||||||
|
implements TimePickerDialog.OnTimeSetListener,
|
||||||
|
WeekdayPickerDialog.OnWeekdaysPickedListener
|
||||||
|
{
|
||||||
|
@BindView(R.id.tvReminderTime)
|
||||||
|
TextView tvReminderTime;
|
||||||
|
|
||||||
|
@BindView(R.id.llReminderDays)
|
||||||
|
ViewGroup llReminderDays;
|
||||||
|
|
||||||
|
@BindView(R.id.tvReminderDays)
|
||||||
|
TextView tvReminderDays;
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
private Reminder reminder;
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
private Controller controller;
|
||||||
|
|
||||||
|
public ReminderPanel(@NonNull Context context, @Nullable AttributeSet attrs)
|
||||||
|
{
|
||||||
|
super(context, attrs);
|
||||||
|
|
||||||
|
View view = inflate(context, R.layout.edit_habit_reminder, null);
|
||||||
|
ButterKnife.bind(this, view);
|
||||||
|
addView(view);
|
||||||
|
|
||||||
|
controller = new Controller() {};
|
||||||
|
setReminder(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
public Reminder getReminder()
|
||||||
|
{
|
||||||
|
return reminder;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setReminder(@Nullable Reminder reminder)
|
||||||
|
{
|
||||||
|
this.reminder = reminder;
|
||||||
|
|
||||||
|
if (reminder == null)
|
||||||
|
{
|
||||||
|
tvReminderTime.setText(R.string.reminder_off);
|
||||||
|
llReminderDays.setVisibility(View.GONE);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Context ctx = getContext();
|
||||||
|
String time = formatTime(ctx, reminder.getHour(), reminder.getMinute());
|
||||||
|
tvReminderTime.setText(time);
|
||||||
|
llReminderDays.setVisibility(View.VISIBLE);
|
||||||
|
|
||||||
|
boolean weekdays[] = reminder.getDays().toArray();
|
||||||
|
tvReminderDays.setText(formatWeekdayList(ctx, weekdays));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onTimeCleared(RadialPickerLayout view)
|
||||||
|
{
|
||||||
|
setReminder(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onTimeSet(RadialPickerLayout view, int hour, int minute)
|
||||||
|
{
|
||||||
|
setReminder(new Reminder(hour, minute, WeekdayList.EVERY_DAY));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onWeekdaysSet(WeekdayList selectedDays)
|
||||||
|
{
|
||||||
|
if (reminder == null) return;
|
||||||
|
if (selectedDays.isEmpty()) selectedDays = WeekdayList.EVERY_DAY;
|
||||||
|
|
||||||
|
setReminder(new Reminder(reminder.getHour(), reminder.getMinute(),
|
||||||
|
selectedDays));
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setController(@NonNull Controller controller)
|
||||||
|
{
|
||||||
|
this.controller = controller;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onRestoreInstanceState(Parcelable state)
|
||||||
|
{
|
||||||
|
BundleSavedState bss = (BundleSavedState) state;
|
||||||
|
if (!bss.bundle.isEmpty())
|
||||||
|
{
|
||||||
|
int days = bss.bundle.getInt("days");
|
||||||
|
int hour = bss.bundle.getInt("hour");
|
||||||
|
int minute = bss.bundle.getInt("minute");
|
||||||
|
reminder = new Reminder(hour, minute, new WeekdayList(days));
|
||||||
|
setReminder(reminder);
|
||||||
|
}
|
||||||
|
super.onRestoreInstanceState(bss.getSuperState());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected Parcelable onSaveInstanceState()
|
||||||
|
{
|
||||||
|
Parcelable superState = super.onSaveInstanceState();
|
||||||
|
Bundle bundle = new Bundle();
|
||||||
|
if (reminder != null)
|
||||||
|
{
|
||||||
|
bundle.putInt("days", reminder.getDays().toInteger());
|
||||||
|
bundle.putInt("hour", reminder.getHour());
|
||||||
|
bundle.putInt("minute", reminder.getMinute());
|
||||||
|
}
|
||||||
|
return new BundleSavedState(superState, bundle);
|
||||||
|
}
|
||||||
|
|
||||||
|
@OnClick(R.id.tvReminderTime)
|
||||||
|
void onDateSpinnerClick()
|
||||||
|
{
|
||||||
|
int hour = 8;
|
||||||
|
int min = 0;
|
||||||
|
|
||||||
|
if (reminder != null)
|
||||||
|
{
|
||||||
|
hour = reminder.getHour();
|
||||||
|
min = reminder.getMinute();
|
||||||
|
}
|
||||||
|
|
||||||
|
controller.onTimeClicked(hour, min);
|
||||||
|
}
|
||||||
|
|
||||||
|
@OnClick(R.id.tvReminderDays)
|
||||||
|
void onWeekdayClicked()
|
||||||
|
{
|
||||||
|
if (reminder == null) return;
|
||||||
|
controller.onWeekdayClicked(reminder.getDays());
|
||||||
|
}
|
||||||
|
|
||||||
|
public interface Controller
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Called when the user has clicked the widget to change the time of
|
||||||
|
* the reminder.
|
||||||
|
*
|
||||||
|
* @param currentHour hour previously picked by the user
|
||||||
|
* @param currentMin minute previously picked by the user
|
||||||
|
*/
|
||||||
|
default void onTimeClicked(int currentHour, int currentMin) {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called when the used has clicked the widget to change the days
|
||||||
|
* of the reminder.
|
||||||
|
*
|
||||||
|
* @param currentDays days previously selected by the user.
|
||||||
|
*/
|
||||||
|
default void onWeekdayClicked(WeekdayList currentDays) {}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,90 @@
|
|||||||
|
/*
|
||||||
|
* 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.edit.views;
|
||||||
|
|
||||||
|
import android.content.*;
|
||||||
|
import android.content.res.*;
|
||||||
|
import android.icu.text.*;
|
||||||
|
import android.support.annotation.*;
|
||||||
|
import android.util.*;
|
||||||
|
import android.view.*;
|
||||||
|
import android.widget.*;
|
||||||
|
|
||||||
|
import org.isoron.uhabits.R;
|
||||||
|
|
||||||
|
import butterknife.*;
|
||||||
|
|
||||||
|
|
||||||
|
public class TargetPanel extends FrameLayout
|
||||||
|
{
|
||||||
|
private DecimalFormat valueFormatter = new DecimalFormat("#.##");
|
||||||
|
|
||||||
|
@BindView(R.id.tvUnit)
|
||||||
|
ExampleEditText tvUnit;
|
||||||
|
|
||||||
|
@BindView(R.id.tvTargetCount)
|
||||||
|
TextView tvTargetValue;
|
||||||
|
|
||||||
|
public TargetPanel(@NonNull Context context, @Nullable AttributeSet attrs)
|
||||||
|
{
|
||||||
|
super(context, attrs);
|
||||||
|
|
||||||
|
View view = inflate(context, R.layout.edit_habit_target, null);
|
||||||
|
ButterKnife.bind(this, view);
|
||||||
|
addView(view);
|
||||||
|
}
|
||||||
|
|
||||||
|
public double getTargetValue()
|
||||||
|
{
|
||||||
|
String sValue = tvTargetValue.getText().toString();
|
||||||
|
return Double.parseDouble(sValue);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setTargetValue(double targetValue)
|
||||||
|
{
|
||||||
|
tvTargetValue.setText(valueFormatter.format(targetValue));
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getUnit()
|
||||||
|
{
|
||||||
|
return tvUnit.getRealText();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setUnit(String unit)
|
||||||
|
{
|
||||||
|
tvUnit.setRealText(unit);
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean validate()
|
||||||
|
{
|
||||||
|
Resources res = getResources();
|
||||||
|
String sValue = tvTargetValue.getText().toString();
|
||||||
|
double value = Double.parseDouble(sValue);
|
||||||
|
|
||||||
|
if (value <= 0)
|
||||||
|
{
|
||||||
|
tvTargetValue.setError(
|
||||||
|
res.getString(R.string.validation_number_should_be_positive));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -32,19 +32,21 @@ import dagger.*;
|
|||||||
dependencies = { AppComponent.class })
|
dependencies = { AppComponent.class })
|
||||||
public interface ListHabitsComponent
|
public interface ListHabitsComponent
|
||||||
{
|
{
|
||||||
CheckmarkButtonControllerFactory getCheckmarkButtonControllerFactory();
|
|
||||||
|
|
||||||
HabitCardListAdapter getAdapter();
|
HabitCardListAdapter getAdapter();
|
||||||
|
|
||||||
|
CheckmarkButtonControllerFactory getCheckmarkButtonControllerFactory();
|
||||||
|
|
||||||
ListHabitsController getController();
|
ListHabitsController getController();
|
||||||
|
|
||||||
ListHabitsMenu getMenu();
|
ListHabitsMenu getMenu();
|
||||||
|
|
||||||
|
MidnightTimer getMidnightTimer();
|
||||||
|
|
||||||
|
NumberButtonControllerFactory getNumberButtonControllerFactory();
|
||||||
|
|
||||||
ListHabitsRootView getRootView();
|
ListHabitsRootView getRootView();
|
||||||
|
|
||||||
ListHabitsScreen getScreen();
|
ListHabitsScreen getScreen();
|
||||||
|
|
||||||
ListHabitsSelectionMenu getSelectionMenu();
|
ListHabitsSelectionMenu getSelectionMenu();
|
||||||
|
|
||||||
MidnightTimer getMidnightTimer();
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -83,8 +83,7 @@ public class ListHabitsController
|
|||||||
@NonNull ReminderScheduler reminderScheduler,
|
@NonNull ReminderScheduler reminderScheduler,
|
||||||
@NonNull TaskRunner taskRunner,
|
@NonNull TaskRunner taskRunner,
|
||||||
@NonNull WidgetUpdater widgetUpdater,
|
@NonNull WidgetUpdater widgetUpdater,
|
||||||
@NonNull
|
@NonNull ImportDataTaskFactory importTaskFactory,
|
||||||
ImportDataTaskFactory importTaskFactory,
|
|
||||||
@NonNull ExportCSVTaskFactory exportCSVFactory,
|
@NonNull ExportCSVTaskFactory exportCSVFactory,
|
||||||
@NonNull ExportDBTaskFactory exportDBFactory)
|
@NonNull ExportDBTaskFactory exportDBFactory)
|
||||||
{
|
{
|
||||||
@@ -157,6 +156,26 @@ public class ListHabitsController
|
|||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onInvalidEdit()
|
||||||
|
{
|
||||||
|
screen.showMessage(R.string.long_press_to_edit);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onEdit(@NonNull Habit habit, long timestamp)
|
||||||
|
{
|
||||||
|
CheckmarkList checkmarks = habit.getCheckmarks();
|
||||||
|
double oldValue = checkmarks.getValues(timestamp, timestamp)[0];
|
||||||
|
|
||||||
|
screen.showNumberPicker(oldValue / 1000, habit.getUnit(), newValue -> {
|
||||||
|
newValue = Math.round(newValue * 1000);
|
||||||
|
commandRunner.execute(
|
||||||
|
new CreateRepetitionCommand(habit, timestamp, (int) newValue),
|
||||||
|
habit.getId());
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onInvalidToggle()
|
public void onInvalidToggle()
|
||||||
|
|||||||
@@ -23,6 +23,10 @@ import android.app.*;
|
|||||||
import android.content.*;
|
import android.content.*;
|
||||||
import android.net.*;
|
import android.net.*;
|
||||||
import android.support.annotation.*;
|
import android.support.annotation.*;
|
||||||
|
import android.support.v7.app.AlertDialog;
|
||||||
|
import android.text.*;
|
||||||
|
import android.view.*;
|
||||||
|
import android.widget.*;
|
||||||
|
|
||||||
import org.isoron.uhabits.*;
|
import org.isoron.uhabits.*;
|
||||||
import org.isoron.uhabits.activities.*;
|
import org.isoron.uhabits.activities.*;
|
||||||
@@ -36,30 +40,33 @@ import org.isoron.uhabits.models.*;
|
|||||||
import org.isoron.uhabits.utils.*;
|
import org.isoron.uhabits.utils.*;
|
||||||
|
|
||||||
import java.io.*;
|
import java.io.*;
|
||||||
|
import java.lang.reflect.*;
|
||||||
|
|
||||||
import javax.inject.*;
|
import javax.inject.*;
|
||||||
|
|
||||||
|
import static android.content.DialogInterface.*;
|
||||||
import static android.os.Build.VERSION.*;
|
import static android.os.Build.VERSION.*;
|
||||||
import static android.os.Build.VERSION_CODES.*;
|
import static android.os.Build.VERSION_CODES.*;
|
||||||
|
import static android.view.inputmethod.EditorInfo.*;
|
||||||
|
|
||||||
@ActivityScope
|
@ActivityScope
|
||||||
public class ListHabitsScreen extends BaseScreen
|
public class ListHabitsScreen extends BaseScreen
|
||||||
implements CommandRunner.Listener
|
implements CommandRunner.Listener
|
||||||
{
|
{
|
||||||
public static final int RESULT_IMPORT_DATA = 1;
|
public static final int REQUEST_OPEN_DOCUMENT = 6;
|
||||||
|
|
||||||
|
public static final int REQUEST_SETTINGS = 7;
|
||||||
|
|
||||||
|
public static final int RESULT_BUG_REPORT = 4;
|
||||||
|
|
||||||
public static final int RESULT_EXPORT_CSV = 2;
|
public static final int RESULT_EXPORT_CSV = 2;
|
||||||
|
|
||||||
public static final int RESULT_EXPORT_DB = 3;
|
public static final int RESULT_EXPORT_DB = 3;
|
||||||
|
|
||||||
public static final int RESULT_BUG_REPORT = 4;
|
public static final int RESULT_IMPORT_DATA = 1;
|
||||||
|
|
||||||
public static final int RESULT_REPAIR_DB = 5;
|
public static final int RESULT_REPAIR_DB = 5;
|
||||||
|
|
||||||
public static final int REQUEST_OPEN_DOCUMENT = 6;
|
|
||||||
|
|
||||||
public static final int REQUEST_SETTINGS = 7;
|
|
||||||
|
|
||||||
@Nullable
|
@Nullable
|
||||||
private ListHabitsController controller;
|
private ListHabitsController controller;
|
||||||
|
|
||||||
@@ -75,9 +82,6 @@ public class ListHabitsScreen extends BaseScreen
|
|||||||
@NonNull
|
@NonNull
|
||||||
private final ConfirmDeleteDialogFactory confirmDeleteDialogFactory;
|
private final ConfirmDeleteDialogFactory confirmDeleteDialogFactory;
|
||||||
|
|
||||||
@NonNull
|
|
||||||
private final CreateHabitDialogFactory createHabitDialogFactory;
|
|
||||||
|
|
||||||
@NonNull
|
@NonNull
|
||||||
private final FilePickerDialogFactory filePickerDialogFactory;
|
private final FilePickerDialogFactory filePickerDialogFactory;
|
||||||
|
|
||||||
@@ -98,18 +102,16 @@ public class ListHabitsScreen extends BaseScreen
|
|||||||
@NonNull IntentFactory intentFactory,
|
@NonNull IntentFactory intentFactory,
|
||||||
@NonNull ThemeSwitcher themeSwitcher,
|
@NonNull ThemeSwitcher themeSwitcher,
|
||||||
@NonNull ConfirmDeleteDialogFactory confirmDeleteDialogFactory,
|
@NonNull ConfirmDeleteDialogFactory confirmDeleteDialogFactory,
|
||||||
@NonNull CreateHabitDialogFactory createHabitDialogFactory,
|
|
||||||
@NonNull FilePickerDialogFactory filePickerDialogFactory,
|
@NonNull FilePickerDialogFactory filePickerDialogFactory,
|
||||||
@NonNull ColorPickerDialogFactory colorPickerFactory,
|
@NonNull ColorPickerDialogFactory colorPickerFactory,
|
||||||
@NonNull EditHabitDialogFactory editHabitDialogFactory)
|
@NonNull EditHabitDialogFactory editHabitDialogFactory)
|
||||||
{
|
{
|
||||||
super(activity);
|
super(activity);
|
||||||
setRootView(rootView);
|
setRootView(rootView);
|
||||||
this.editHabitDialogFactory = editHabitDialogFactory;
|
|
||||||
this.colorPickerFactory = colorPickerFactory;
|
this.colorPickerFactory = colorPickerFactory;
|
||||||
this.commandRunner = commandRunner;
|
this.commandRunner = commandRunner;
|
||||||
this.confirmDeleteDialogFactory = confirmDeleteDialogFactory;
|
this.confirmDeleteDialogFactory = confirmDeleteDialogFactory;
|
||||||
this.createHabitDialogFactory = createHabitDialogFactory;
|
this.editHabitDialogFactory = editHabitDialogFactory;
|
||||||
this.dirFinder = dirFinder;
|
this.dirFinder = dirFinder;
|
||||||
this.filePickerDialogFactory = filePickerDialogFactory;
|
this.filePickerDialogFactory = filePickerDialogFactory;
|
||||||
this.intentFactory = intentFactory;
|
this.intentFactory = intentFactory;
|
||||||
@@ -139,60 +141,7 @@ public class ListHabitsScreen extends BaseScreen
|
|||||||
if (requestCode == REQUEST_OPEN_DOCUMENT)
|
if (requestCode == REQUEST_OPEN_DOCUMENT)
|
||||||
onOpenDocumentResult(resultCode, data);
|
onOpenDocumentResult(resultCode, data);
|
||||||
|
|
||||||
if (requestCode == REQUEST_SETTINGS)
|
if (requestCode == REQUEST_SETTINGS) onSettingsResult(resultCode);
|
||||||
onSettingsResult(resultCode);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void onSettingsResult(int resultCode)
|
|
||||||
{
|
|
||||||
if (controller == null) return;
|
|
||||||
|
|
||||||
switch (resultCode)
|
|
||||||
{
|
|
||||||
case RESULT_IMPORT_DATA:
|
|
||||||
showImportScreen();
|
|
||||||
break;
|
|
||||||
|
|
||||||
case RESULT_EXPORT_CSV:
|
|
||||||
controller.onExportCSV();
|
|
||||||
break;
|
|
||||||
|
|
||||||
case RESULT_EXPORT_DB:
|
|
||||||
controller.onExportDB();
|
|
||||||
break;
|
|
||||||
|
|
||||||
case RESULT_BUG_REPORT:
|
|
||||||
controller.onSendBugReport();
|
|
||||||
break;
|
|
||||||
|
|
||||||
case RESULT_REPAIR_DB:
|
|
||||||
controller.onRepairDB();
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void onOpenDocumentResult(int resultCode, Intent data)
|
|
||||||
{
|
|
||||||
if (controller == null) return;
|
|
||||||
if (resultCode != Activity.RESULT_OK) return;
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
Uri uri = data.getData();
|
|
||||||
ContentResolver cr = activity.getContentResolver();
|
|
||||||
InputStream is = cr.openInputStream(uri);
|
|
||||||
|
|
||||||
File cacheDir = activity.getExternalCacheDir();
|
|
||||||
File tempFile = File.createTempFile("import", "", cacheDir);
|
|
||||||
|
|
||||||
FileUtils.copy(is, tempFile);
|
|
||||||
controller.onImportData(tempFile, () -> tempFile.delete());
|
|
||||||
}
|
|
||||||
catch (IOException e)
|
|
||||||
{
|
|
||||||
showMessage(R.string.could_not_import);
|
|
||||||
e.printStackTrace();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setController(@Nullable ListHabitsController controller)
|
public void setController(@Nullable ListHabitsController controller)
|
||||||
@@ -224,7 +173,29 @@ public class ListHabitsScreen extends BaseScreen
|
|||||||
|
|
||||||
public void showCreateHabitScreen()
|
public void showCreateHabitScreen()
|
||||||
{
|
{
|
||||||
activity.showDialog(createHabitDialogFactory.create(), "editHabit");
|
Dialog dialog = new AlertDialog.Builder(activity)
|
||||||
|
.setTitle("Type of habit")
|
||||||
|
.setItems(R.array.habitTypes, (d, which) -> {
|
||||||
|
if(which == 0) showCreateBooleanHabitScreen();
|
||||||
|
else showCreateNumericalHabitScreen();
|
||||||
|
})
|
||||||
|
.create();
|
||||||
|
|
||||||
|
dialog.show();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void showCreateNumericalHabitScreen()
|
||||||
|
{
|
||||||
|
EditHabitDialog dialog;
|
||||||
|
dialog = editHabitDialogFactory.createNumerical();
|
||||||
|
activity.showDialog(dialog, "editHabit");
|
||||||
|
}
|
||||||
|
|
||||||
|
public void showCreateBooleanHabitScreen()
|
||||||
|
{
|
||||||
|
EditHabitDialog dialog;
|
||||||
|
dialog = editHabitDialogFactory.createBoolean();
|
||||||
|
activity.showDialog(dialog, "editHabit");
|
||||||
}
|
}
|
||||||
|
|
||||||
public void showDeleteConfirmationScreen(ConfirmDeleteDialog.Callback callback)
|
public void showDeleteConfirmationScreen(ConfirmDeleteDialog.Callback callback)
|
||||||
@@ -234,8 +205,9 @@ public class ListHabitsScreen extends BaseScreen
|
|||||||
|
|
||||||
public void showEditHabitScreen(Habit habit)
|
public void showEditHabitScreen(Habit habit)
|
||||||
{
|
{
|
||||||
EditHabitDialog dialog = editHabitDialogFactory.create(habit);
|
EditHabitDialog dialog;
|
||||||
activity.showDialog(dialog, "editHabit");
|
dialog = editHabitDialogFactory.edit(habit);
|
||||||
|
activity.showDialog(dialog, "editNumericalHabit");
|
||||||
}
|
}
|
||||||
|
|
||||||
public void showFAQScreen()
|
public void showFAQScreen()
|
||||||
@@ -278,7 +250,9 @@ public class ListHabitsScreen extends BaseScreen
|
|||||||
FilePickerDialog picker = filePickerDialogFactory.create(dir);
|
FilePickerDialog picker = filePickerDialogFactory.create(dir);
|
||||||
|
|
||||||
if (controller != null)
|
if (controller != null)
|
||||||
picker.setListener(file -> controller.onImportData(file, () -> {}));
|
picker.setListener(file -> controller.onImportData(file, () ->
|
||||||
|
{
|
||||||
|
}));
|
||||||
|
|
||||||
activity.showDialog(picker.getDialog());
|
activity.showDialog(picker.getDialog());
|
||||||
}
|
}
|
||||||
@@ -289,6 +263,74 @@ public class ListHabitsScreen extends BaseScreen
|
|||||||
activity.startActivity(intent);
|
activity.startActivity(intent);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void showNumberPicker(double value,
|
||||||
|
@NonNull String unit,
|
||||||
|
@NonNull NumberPickerCallback callback)
|
||||||
|
{
|
||||||
|
LayoutInflater inflater = activity.getLayoutInflater();
|
||||||
|
View view = inflater.inflate(R.layout.number_picker_dialog, null);
|
||||||
|
|
||||||
|
final NumberPicker picker;
|
||||||
|
final NumberPicker picker2;
|
||||||
|
final TextView tvUnit;
|
||||||
|
|
||||||
|
picker = (NumberPicker) view.findViewById(R.id.picker);
|
||||||
|
picker2 = (NumberPicker) view.findViewById(R.id.picker2);
|
||||||
|
tvUnit = (TextView) view.findViewById(R.id.tvUnit);
|
||||||
|
|
||||||
|
int intValue = (int) Math.round(value * 100);
|
||||||
|
|
||||||
|
picker.setMinValue(0);
|
||||||
|
picker.setMaxValue(Integer.MAX_VALUE / 100);
|
||||||
|
picker.setValue(intValue / 100);
|
||||||
|
picker.setWrapSelectorWheel(false);
|
||||||
|
|
||||||
|
picker2.setMinValue(0);
|
||||||
|
picker2.setMaxValue(19);
|
||||||
|
picker2.setFormatter(v -> String.format("%02d", 5 * v));
|
||||||
|
picker2.setValue((intValue % 100) / 5);
|
||||||
|
refreshInitialValue(picker2);
|
||||||
|
|
||||||
|
tvUnit.setText(unit);
|
||||||
|
|
||||||
|
AlertDialog dialog = new AlertDialog.Builder(activity)
|
||||||
|
.setView(view)
|
||||||
|
.setTitle(R.string.change_value)
|
||||||
|
.setPositiveButton(android.R.string.ok, (d, which) ->
|
||||||
|
{
|
||||||
|
picker.clearFocus();
|
||||||
|
double v = picker.getValue() + 0.05 * picker2.getValue();
|
||||||
|
callback.onNumberPicked(v);
|
||||||
|
})
|
||||||
|
.create();
|
||||||
|
|
||||||
|
InterfaceUtils.setupEditorAction(picker, (v, actionId, event) ->
|
||||||
|
{
|
||||||
|
if (actionId == IME_ACTION_DONE)
|
||||||
|
dialog.getButton(BUTTON_POSITIVE).performClick();
|
||||||
|
return false;
|
||||||
|
});
|
||||||
|
|
||||||
|
dialog.show();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void refreshInitialValue(NumberPicker picker2)
|
||||||
|
{
|
||||||
|
// Workaround for a bug on Android:
|
||||||
|
// https://code.google.com/p/android/issues/detail?id=35482
|
||||||
|
try
|
||||||
|
{
|
||||||
|
Field f = NumberPicker.class.getDeclaredField("mInputText");
|
||||||
|
f.setAccessible(true);
|
||||||
|
EditText inputText = (EditText) f.get(picker2);
|
||||||
|
inputText.setFilters(new InputFilter[0]);
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public void showSettingsScreen()
|
public void showSettingsScreen()
|
||||||
{
|
{
|
||||||
Intent intent = intentFactory.startSettingsActivity(activity);
|
Intent intent = intentFactory.startSettingsActivity(activity);
|
||||||
@@ -300,4 +342,61 @@ public class ListHabitsScreen extends BaseScreen
|
|||||||
themeSwitcher.toggleNightMode();
|
themeSwitcher.toggleNightMode();
|
||||||
activity.restartWithFade();
|
activity.restartWithFade();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void onOpenDocumentResult(int resultCode, Intent data)
|
||||||
|
{
|
||||||
|
if (controller == null) return;
|
||||||
|
if (resultCode != Activity.RESULT_OK) return;
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
Uri uri = data.getData();
|
||||||
|
ContentResolver cr = activity.getContentResolver();
|
||||||
|
InputStream is = cr.openInputStream(uri);
|
||||||
|
|
||||||
|
File cacheDir = activity.getExternalCacheDir();
|
||||||
|
File tempFile = File.createTempFile("import", "", cacheDir);
|
||||||
|
|
||||||
|
FileUtils.copy(is, tempFile);
|
||||||
|
controller.onImportData(tempFile, () -> tempFile.delete());
|
||||||
|
}
|
||||||
|
catch (IOException e)
|
||||||
|
{
|
||||||
|
showMessage(R.string.could_not_import);
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void onSettingsResult(int resultCode)
|
||||||
|
{
|
||||||
|
if (controller == null) return;
|
||||||
|
|
||||||
|
switch (resultCode)
|
||||||
|
{
|
||||||
|
case RESULT_IMPORT_DATA:
|
||||||
|
showImportScreen();
|
||||||
|
break;
|
||||||
|
|
||||||
|
case RESULT_EXPORT_CSV:
|
||||||
|
controller.onExportCSV();
|
||||||
|
break;
|
||||||
|
|
||||||
|
case RESULT_EXPORT_DB:
|
||||||
|
controller.onExportDB();
|
||||||
|
break;
|
||||||
|
|
||||||
|
case RESULT_BUG_REPORT:
|
||||||
|
controller.onSendBugReport();
|
||||||
|
break;
|
||||||
|
|
||||||
|
case RESULT_REPAIR_DB:
|
||||||
|
controller.onRepairDB();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public interface NumberPickerCallback
|
||||||
|
{
|
||||||
|
void onNumberPicked(double newValue);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -21,8 +21,8 @@ package org.isoron.uhabits.activities.habits.list.controllers;
|
|||||||
|
|
||||||
import android.support.annotation.*;
|
import android.support.annotation.*;
|
||||||
|
|
||||||
import org.isoron.uhabits.models.Habit;
|
import org.isoron.uhabits.activities.habits.list.views.*;
|
||||||
import org.isoron.uhabits.activities.habits.list.views.HabitCardView;
|
import org.isoron.uhabits.models.*;
|
||||||
|
|
||||||
public class HabitCardController implements HabitCardView.Controller
|
public class HabitCardController implements HabitCardView.Controller
|
||||||
{
|
{
|
||||||
@@ -32,6 +32,18 @@ public class HabitCardController implements HabitCardView.Controller
|
|||||||
@Nullable
|
@Nullable
|
||||||
private Listener listener;
|
private Listener listener;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onEdit(@NonNull Habit habit, long timestamp)
|
||||||
|
{
|
||||||
|
if(listener != null) listener.onEdit(habit, timestamp);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onInvalidEdit()
|
||||||
|
{
|
||||||
|
if(listener != null) listener.onInvalidEdit();
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onInvalidToggle()
|
public void onInvalidToggle()
|
||||||
{
|
{
|
||||||
@@ -55,7 +67,9 @@ public class HabitCardController implements HabitCardView.Controller
|
|||||||
this.view = view;
|
this.view = view;
|
||||||
}
|
}
|
||||||
|
|
||||||
public interface Listener extends CheckmarkButtonController.Listener
|
public interface Listener extends CheckmarkButtonController.Listener,
|
||||||
|
NumberButtonController.Listener
|
||||||
{
|
{
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -21,9 +21,9 @@ package org.isoron.uhabits.activities.habits.list.controllers;
|
|||||||
|
|
||||||
import android.support.annotation.*;
|
import android.support.annotation.*;
|
||||||
|
|
||||||
import org.isoron.uhabits.models.*;
|
|
||||||
import org.isoron.uhabits.activities.habits.list.model.*;
|
import org.isoron.uhabits.activities.habits.list.model.*;
|
||||||
import org.isoron.uhabits.activities.habits.list.views.*;
|
import org.isoron.uhabits.activities.habits.list.views.*;
|
||||||
|
import org.isoron.uhabits.models.*;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Controller responsible for receiving and processing the events generated by a
|
* Controller responsible for receiving and processing the events generated by a
|
||||||
@@ -75,6 +75,18 @@ public class HabitCardListController implements HabitCardListView.Controller
|
|||||||
habitListener.onHabitReorder(habitFrom, habitTo);
|
habitListener.onHabitReorder(habitFrom, habitTo);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onEdit(@NonNull Habit habit, long timestamp)
|
||||||
|
{
|
||||||
|
if (habitListener != null) habitListener.onEdit(habit, timestamp);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onInvalidEdit()
|
||||||
|
{
|
||||||
|
if (habitListener != null) habitListener.onInvalidEdit();
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Called when the user attempts to perform a toggle, but attempt is
|
* Called when the user attempts to perform a toggle, but attempt is
|
||||||
* rejected.
|
* rejected.
|
||||||
@@ -172,7 +184,8 @@ public class HabitCardListController implements HabitCardListView.Controller
|
|||||||
if (selectionListener != null) selectionListener.onSelectionFinish();
|
if (selectionListener != null) selectionListener.onSelectionFinish();
|
||||||
}
|
}
|
||||||
|
|
||||||
public interface HabitListener extends CheckmarkButtonController.Listener
|
public interface HabitListener extends CheckmarkButtonController.Listener,
|
||||||
|
NumberButtonController.Listener
|
||||||
{
|
{
|
||||||
/**
|
/**
|
||||||
* Called when the user clicks a habit.
|
* Called when the user clicks a habit.
|
||||||
|
|||||||
@@ -0,0 +1,102 @@
|
|||||||
|
/*
|
||||||
|
* 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.list.controllers;
|
||||||
|
|
||||||
|
import android.support.annotation.*;
|
||||||
|
|
||||||
|
import com.google.auto.factory.*;
|
||||||
|
|
||||||
|
import org.isoron.uhabits.activities.habits.list.views.*;
|
||||||
|
import org.isoron.uhabits.models.*;
|
||||||
|
import org.isoron.uhabits.preferences.*;
|
||||||
|
|
||||||
|
@AutoFactory
|
||||||
|
public class NumberButtonController
|
||||||
|
{
|
||||||
|
@Nullable
|
||||||
|
private NumberButtonView view;
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
private Listener listener;
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
private final Preferences prefs;
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
private Habit habit;
|
||||||
|
|
||||||
|
private long timestamp;
|
||||||
|
|
||||||
|
public NumberButtonController(@Provided @NonNull Preferences prefs,
|
||||||
|
@NonNull Habit habit,
|
||||||
|
long timestamp)
|
||||||
|
{
|
||||||
|
this.habit = habit;
|
||||||
|
this.timestamp = timestamp;
|
||||||
|
this.prefs = prefs;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void onClick()
|
||||||
|
{
|
||||||
|
if (prefs.isShortToggleEnabled()) performEdit();
|
||||||
|
else performInvalidToggle();
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean onLongClick()
|
||||||
|
{
|
||||||
|
performEdit();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void performInvalidToggle()
|
||||||
|
{
|
||||||
|
if (listener != null) listener.onInvalidEdit();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void performEdit()
|
||||||
|
{
|
||||||
|
if (listener != null) listener.onEdit(habit, timestamp);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setListener(@Nullable Listener listener)
|
||||||
|
{
|
||||||
|
this.listener = listener;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setView(@Nullable NumberButtonView view)
|
||||||
|
{
|
||||||
|
this.view = view;
|
||||||
|
}
|
||||||
|
|
||||||
|
public interface Listener
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Called when the user's attempt to edit the value is rejected.
|
||||||
|
*/
|
||||||
|
void onInvalidEdit();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called when a the user's attempt to edit the value has been accepted.
|
||||||
|
* @param habit the habit being edited
|
||||||
|
* @param timestamp the timestamp being edited
|
||||||
|
*/
|
||||||
|
void onEdit(@NonNull Habit habit, long timestamp);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -171,7 +171,7 @@ public class HabitCardListAdapter
|
|||||||
if (listView == null) return;
|
if (listView == null) return;
|
||||||
|
|
||||||
Habit habit = cache.getHabitByPosition(position);
|
Habit habit = cache.getHabitByPosition(position);
|
||||||
int score = cache.getScore(habit.getId());
|
double score = cache.getScore(habit.getId());
|
||||||
int checkmarks[] = cache.getCheckmarks(habit.getId());
|
int checkmarks[] = cache.getCheckmarks(habit.getId());
|
||||||
boolean selected = this.selected.contains(habit);
|
boolean selected = this.selected.contains(habit);
|
||||||
|
|
||||||
|
|||||||
@@ -112,7 +112,7 @@ public class HabitCardListCache implements CommandRunner.Listener
|
|||||||
return filteredHabits.getOrder();
|
return filteredHabits.getOrder();
|
||||||
}
|
}
|
||||||
|
|
||||||
public int getScore(long habitId)
|
public double getScore(long habitId)
|
||||||
{
|
{
|
||||||
return data.scores.get(habitId);
|
return data.scores.get(habitId);
|
||||||
}
|
}
|
||||||
@@ -221,7 +221,7 @@ public class HabitCardListCache implements CommandRunner.Listener
|
|||||||
public HashMap<Long, int[]> checkmarks;
|
public HashMap<Long, int[]> checkmarks;
|
||||||
|
|
||||||
@NonNull
|
@NonNull
|
||||||
public HashMap<Long, Integer> scores;
|
public HashMap<Long, Double> scores;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates a new CacheData without any content.
|
* Creates a new CacheData without any content.
|
||||||
@@ -252,7 +252,7 @@ public class HabitCardListCache implements CommandRunner.Listener
|
|||||||
{
|
{
|
||||||
if (oldData.scores.containsKey(id))
|
if (oldData.scores.containsKey(id))
|
||||||
scores.put(id, oldData.scores.get(id));
|
scores.put(id, oldData.scores.get(id));
|
||||||
else scores.put(id, 0);
|
else scores.put(id, 0.0);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -365,14 +365,14 @@ public class HabitCardListCache implements CommandRunner.Listener
|
|||||||
|
|
||||||
private void performUpdate(Long id, int position)
|
private void performUpdate(Long id, int position)
|
||||||
{
|
{
|
||||||
Integer oldScore = data.scores.get(id);
|
double oldScore = data.scores.get(id);
|
||||||
int[] oldCheckmarks = data.checkmarks.get(id);
|
int[] oldCheckmarks = data.checkmarks.get(id);
|
||||||
|
|
||||||
Integer newScore = newData.scores.get(id);
|
double newScore = newData.scores.get(id);
|
||||||
int[] newCheckmarks = newData.checkmarks.get(id);
|
int[] newCheckmarks = newData.checkmarks.get(id);
|
||||||
|
|
||||||
boolean unchanged = true;
|
boolean unchanged = true;
|
||||||
if (!oldScore.equals(newScore)) unchanged = false;
|
if (oldScore != newScore) unchanged = false;
|
||||||
if (!Arrays.equals(oldCheckmarks, newCheckmarks)) unchanged = false;
|
if (!Arrays.equals(oldCheckmarks, newCheckmarks)) unchanged = false;
|
||||||
if (unchanged) return;
|
if (unchanged) return;
|
||||||
|
|
||||||
|
|||||||
@@ -20,6 +20,8 @@
|
|||||||
package org.isoron.uhabits.activities.habits.list.views;
|
package org.isoron.uhabits.activities.habits.list.views;
|
||||||
|
|
||||||
import android.content.*;
|
import android.content.*;
|
||||||
|
import android.support.annotation.*;
|
||||||
|
import android.util.*;
|
||||||
import android.view.*;
|
import android.view.*;
|
||||||
import android.widget.*;
|
import android.widget.*;
|
||||||
|
|
||||||
@@ -28,6 +30,9 @@ import org.isoron.uhabits.activities.habits.list.controllers.*;
|
|||||||
import org.isoron.uhabits.models.*;
|
import org.isoron.uhabits.models.*;
|
||||||
import org.isoron.uhabits.utils.*;
|
import org.isoron.uhabits.utils.*;
|
||||||
|
|
||||||
|
import static org.isoron.uhabits.utils.AttributeSetUtils.*;
|
||||||
|
import static org.isoron.uhabits.utils.ColorUtils.*;
|
||||||
|
|
||||||
public class CheckmarkButtonView extends TextView
|
public class CheckmarkButtonView extends TextView
|
||||||
{
|
{
|
||||||
private int color;
|
private int color;
|
||||||
@@ -36,16 +41,31 @@ public class CheckmarkButtonView extends TextView
|
|||||||
|
|
||||||
private StyledResources res;
|
private StyledResources res;
|
||||||
|
|
||||||
public CheckmarkButtonView(Context context)
|
public CheckmarkButtonView(@Nullable Context context)
|
||||||
{
|
{
|
||||||
super(context);
|
super(context);
|
||||||
init();
|
init();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public CheckmarkButtonView(@Nullable Context context,
|
||||||
|
@Nullable AttributeSet attrs)
|
||||||
|
{
|
||||||
|
super(context, attrs);
|
||||||
|
init();
|
||||||
|
|
||||||
|
if (context != null && attrs != null)
|
||||||
|
{
|
||||||
|
int color = getIntAttribute(context, attrs, "color", 0);
|
||||||
|
int value = getIntAttribute(context, attrs, "value", 0);
|
||||||
|
setColor(getAndroidTestColor(color));
|
||||||
|
setValue(value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public void setColor(int color)
|
public void setColor(int color)
|
||||||
{
|
{
|
||||||
this.color = color;
|
this.color = color;
|
||||||
postInvalidate();
|
updateText();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setController(final CheckmarkButtonController controller)
|
public void setController(final CheckmarkButtonController controller)
|
||||||
|
|||||||
@@ -31,18 +31,23 @@ import org.isoron.uhabits.models.*;
|
|||||||
import org.isoron.uhabits.preferences.*;
|
import org.isoron.uhabits.preferences.*;
|
||||||
import org.isoron.uhabits.utils.*;
|
import org.isoron.uhabits.utils.*;
|
||||||
|
|
||||||
|
import java.util.*;
|
||||||
|
|
||||||
import static android.view.View.MeasureSpec.*;
|
import static android.view.View.MeasureSpec.*;
|
||||||
|
import static org.isoron.uhabits.utils.AttributeSetUtils.*;
|
||||||
|
import static org.isoron.uhabits.utils.ColorUtils.*;
|
||||||
|
|
||||||
public class CheckmarkPanelView extends LinearLayout implements Preferences.Listener
|
public class CheckmarkPanelView extends LinearLayout
|
||||||
|
implements Preferences.Listener
|
||||||
{
|
{
|
||||||
private static final int CHECKMARK_LEFT_TO_RIGHT = 0;
|
private static final int LEFT_TO_RIGHT = 0;
|
||||||
|
|
||||||
private static final int CHECKMARK_RIGHT_TO_LEFT = 1;
|
private static final int RIGHT_TO_LEFT = 1;
|
||||||
|
|
||||||
@Nullable
|
@Nullable
|
||||||
private Preferences prefs;
|
private Preferences prefs;
|
||||||
|
|
||||||
private int checkmarkValues[];
|
private int values[];
|
||||||
|
|
||||||
private int nButtons;
|
private int nButtons;
|
||||||
|
|
||||||
@@ -61,61 +66,89 @@ public class CheckmarkPanelView extends LinearLayout implements Preferences.List
|
|||||||
init();
|
init();
|
||||||
}
|
}
|
||||||
|
|
||||||
public CheckmarkPanelView(Context context, AttributeSet attrs)
|
public CheckmarkPanelView(Context ctx, AttributeSet attrs)
|
||||||
{
|
{
|
||||||
super(context, attrs);
|
super(ctx, attrs);
|
||||||
init();
|
init();
|
||||||
|
|
||||||
|
if (ctx != null && attrs != null)
|
||||||
|
{
|
||||||
|
int paletteColor = getIntAttribute(ctx, attrs, "color", 0);
|
||||||
|
setColor(getAndroidTestColor(paletteColor));
|
||||||
|
setButtonCount(getIntAttribute(ctx, attrs, "button_count", 5));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isInEditMode()) initEditMode();
|
||||||
}
|
}
|
||||||
|
|
||||||
public CheckmarkButtonView indexToButton(int i)
|
public CheckmarkButtonView indexToButton(int i)
|
||||||
{
|
{
|
||||||
int position = i;
|
int position = i;
|
||||||
|
|
||||||
if (getCheckmarkOrder() == CHECKMARK_RIGHT_TO_LEFT)
|
if (getCheckmarkOrder() == RIGHT_TO_LEFT) position = nButtons - i - 1;
|
||||||
position = nButtons - i - 1;
|
|
||||||
|
|
||||||
return (CheckmarkButtonView) getChildAt(position);
|
return (CheckmarkButtonView) getChildAt(position);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setButtonCount(int newButtonCount)
|
@Override
|
||||||
|
public void onCheckmarkOrderChanged()
|
||||||
{
|
{
|
||||||
if(nButtons != newButtonCount)
|
setupButtons();
|
||||||
{
|
|
||||||
nButtons = newButtonCount;
|
|
||||||
addCheckmarkButtons();
|
|
||||||
}
|
|
||||||
|
|
||||||
setupCheckmarkButtons();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setCheckmarkValues(int[] checkmarkValues)
|
public void setButtonCount(int newButtonCount)
|
||||||
{
|
{
|
||||||
this.checkmarkValues = checkmarkValues;
|
if (nButtons != newButtonCount)
|
||||||
setupCheckmarkButtons();
|
{
|
||||||
|
nButtons = newButtonCount;
|
||||||
|
addButtons();
|
||||||
|
}
|
||||||
|
|
||||||
|
setupButtons();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setColor(int color)
|
public void setColor(int color)
|
||||||
{
|
{
|
||||||
this.color = color;
|
this.color = color;
|
||||||
setupCheckmarkButtons();
|
setupButtons();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setController(Controller controller)
|
public void setController(Controller controller)
|
||||||
{
|
{
|
||||||
this.controller = controller;
|
this.controller = controller;
|
||||||
setupCheckmarkButtons();
|
setupButtons();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setDataOffset(int dataOffset)
|
public void setDataOffset(int dataOffset)
|
||||||
{
|
{
|
||||||
this.dataOffset = dataOffset;
|
this.dataOffset = dataOffset;
|
||||||
setupCheckmarkButtons();
|
setupButtons();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setHabit(@NonNull Habit habit)
|
public void setHabit(@NonNull Habit habit)
|
||||||
{
|
{
|
||||||
this.habit = habit;
|
this.habit = habit;
|
||||||
setupCheckmarkButtons();
|
setupButtons();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setValues(int[] values)
|
||||||
|
{
|
||||||
|
this.values = values;
|
||||||
|
setupButtons();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onAttachedToWindow()
|
||||||
|
{
|
||||||
|
super.onAttachedToWindow();
|
||||||
|
if (prefs != null) prefs.addListener(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onDetachedFromWindow()
|
||||||
|
{
|
||||||
|
if (prefs != null) prefs.removeListener(this);
|
||||||
|
super.onDetachedFromWindow();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -133,7 +166,7 @@ public class CheckmarkPanelView extends LinearLayout implements Preferences.List
|
|||||||
super.onMeasure(widthSpec, heightSpec);
|
super.onMeasure(widthSpec, heightSpec);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void addCheckmarkButtons()
|
private void addButtons()
|
||||||
{
|
{
|
||||||
removeAllViews();
|
removeAllViews();
|
||||||
|
|
||||||
@@ -143,21 +176,31 @@ public class CheckmarkPanelView extends LinearLayout implements Preferences.List
|
|||||||
|
|
||||||
private int getCheckmarkOrder()
|
private int getCheckmarkOrder()
|
||||||
{
|
{
|
||||||
if (prefs == null) return CHECKMARK_LEFT_TO_RIGHT;
|
if (prefs == null) return LEFT_TO_RIGHT;
|
||||||
return prefs.shouldReverseCheckmarks() ? CHECKMARK_RIGHT_TO_LEFT :
|
return prefs.shouldReverseCheckmarks() ? RIGHT_TO_LEFT : LEFT_TO_RIGHT;
|
||||||
CHECKMARK_LEFT_TO_RIGHT;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void init()
|
private void init()
|
||||||
{
|
{
|
||||||
Context appContext = getContext().getApplicationContext();
|
Context appContext = getContext().getApplicationContext();
|
||||||
if(appContext instanceof HabitsApplication)
|
if (appContext instanceof HabitsApplication)
|
||||||
{
|
{
|
||||||
HabitsApplication app = (HabitsApplication) appContext;
|
HabitsApplication app = (HabitsApplication) appContext;
|
||||||
prefs = app.getComponent().getPreferences();
|
prefs = app.getComponent().getPreferences();
|
||||||
}
|
}
|
||||||
|
|
||||||
setWillNotDraw(false);
|
setWillNotDraw(false);
|
||||||
|
values = new int[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
private void initEditMode()
|
||||||
|
{
|
||||||
|
int values[] = new int[nButtons];
|
||||||
|
|
||||||
|
for (int i = 0; i < nButtons; i++)
|
||||||
|
values[i] = Math.min(2, new Random().nextInt(4));
|
||||||
|
|
||||||
|
setValues(values);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void setupButtonControllers(long timestamp,
|
private void setupButtonControllers(long timestamp,
|
||||||
@@ -178,7 +221,7 @@ public class CheckmarkPanelView extends LinearLayout implements Preferences.List
|
|||||||
buttonView.setController(buttonController);
|
buttonView.setController(buttonController);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void setupCheckmarkButtons()
|
private void setupButtons()
|
||||||
{
|
{
|
||||||
long timestamp = DateUtils.getStartOfToday();
|
long timestamp = DateUtils.getStartOfToday();
|
||||||
long day = DateUtils.millisecondsInOneDay;
|
long day = DateUtils.millisecondsInOneDay;
|
||||||
@@ -187,34 +230,14 @@ public class CheckmarkPanelView extends LinearLayout implements Preferences.List
|
|||||||
for (int i = 0; i < nButtons; i++)
|
for (int i = 0; i < nButtons; i++)
|
||||||
{
|
{
|
||||||
CheckmarkButtonView buttonView = indexToButton(i);
|
CheckmarkButtonView buttonView = indexToButton(i);
|
||||||
if(i + dataOffset >= checkmarkValues.length) break;
|
if (i + dataOffset >= values.length) break;
|
||||||
buttonView.setValue(checkmarkValues[i + dataOffset]);
|
buttonView.setValue(values[i + dataOffset]);
|
||||||
buttonView.setColor(color);
|
buttonView.setColor(color);
|
||||||
setupButtonControllers(timestamp, buttonView);
|
setupButtonControllers(timestamp, buttonView);
|
||||||
timestamp -= day;
|
timestamp -= day;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void onAttachedToWindow()
|
|
||||||
{
|
|
||||||
super.onAttachedToWindow();
|
|
||||||
if(prefs != null) prefs.addListener(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void onDetachedFromWindow()
|
|
||||||
{
|
|
||||||
if(prefs != null) prefs.removeListener(this);
|
|
||||||
super.onDetachedFromWindow();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onCheckmarkOrderChanged()
|
|
||||||
{
|
|
||||||
setupCheckmarkButtons();
|
|
||||||
}
|
|
||||||
|
|
||||||
public interface Controller extends CheckmarkButtonController.Listener
|
public interface Controller extends CheckmarkButtonController.Listener
|
||||||
{
|
{
|
||||||
|
|
||||||
|
|||||||
@@ -84,17 +84,19 @@ public class HabitCardListView extends RecyclerView
|
|||||||
*/
|
*/
|
||||||
public View bindCardView(@NonNull HabitCardViewHolder holder,
|
public View bindCardView(@NonNull HabitCardViewHolder holder,
|
||||||
@NonNull Habit habit,
|
@NonNull Habit habit,
|
||||||
int score,
|
double score,
|
||||||
int[] checkmarks,
|
int[] checkmarks,
|
||||||
boolean selected)
|
boolean selected)
|
||||||
{
|
{
|
||||||
HabitCardView cardView = (HabitCardView) holder.itemView;
|
HabitCardView cardView = (HabitCardView) holder.itemView;
|
||||||
cardView.setHabit(habit);
|
cardView.setHabit(habit);
|
||||||
cardView.setSelected(selected);
|
cardView.setSelected(selected);
|
||||||
cardView.setCheckmarkValues(checkmarks);
|
cardView.setValues(checkmarks);
|
||||||
cardView.setCheckmarkCount(checkmarkCount);
|
cardView.setButtonCount(checkmarkCount);
|
||||||
cardView.setDataOffset(dataOffset);
|
cardView.setDataOffset(dataOffset);
|
||||||
cardView.setScore(score);
|
cardView.setScore(score);
|
||||||
|
cardView.setUnit(habit.getUnit());
|
||||||
|
cardView.setThreshold(habit.getTargetValue());
|
||||||
if (controller != null) setupCardViewController(holder);
|
if (controller != null) setupCardViewController(holder);
|
||||||
return cardView;
|
return cardView;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -56,6 +56,9 @@ public class HabitCardView extends FrameLayout
|
|||||||
@BindView(R.id.checkmarkPanel)
|
@BindView(R.id.checkmarkPanel)
|
||||||
CheckmarkPanelView checkmarkPanel;
|
CheckmarkPanelView checkmarkPanel;
|
||||||
|
|
||||||
|
@BindView(R.id.numberPanel)
|
||||||
|
NumberPanelView numberPanel;
|
||||||
|
|
||||||
@BindView(R.id.innerFrame)
|
@BindView(R.id.innerFrame)
|
||||||
LinearLayout innerFrame;
|
LinearLayout innerFrame;
|
||||||
|
|
||||||
@@ -92,28 +95,31 @@ public class HabitCardView extends FrameLayout
|
|||||||
new Handler(Looper.getMainLooper()).post(() -> refresh());
|
new Handler(Looper.getMainLooper()).post(() -> refresh());
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setCheckmarkCount(int checkmarkCount)
|
public void setButtonCount(int buttonCount)
|
||||||
{
|
{
|
||||||
checkmarkPanel.setButtonCount(checkmarkCount);
|
checkmarkPanel.setButtonCount(buttonCount);
|
||||||
|
numberPanel.setButtonCount(buttonCount);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setCheckmarkValues(int checkmarks[])
|
public void setThreshold(double threshold)
|
||||||
{
|
{
|
||||||
checkmarkPanel.setCheckmarkValues(checkmarks);
|
numberPanel.setThreshold(threshold);
|
||||||
postInvalidate();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setController(Controller controller)
|
public void setController(Controller controller)
|
||||||
{
|
{
|
||||||
checkmarkPanel.setController(null);
|
checkmarkPanel.setController(null);
|
||||||
|
numberPanel.setController(null);
|
||||||
if (controller == null) return;
|
if (controller == null) return;
|
||||||
checkmarkPanel.setController(controller);
|
checkmarkPanel.setController(controller);
|
||||||
|
numberPanel.setController(controller);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setDataOffset(int dataOffset)
|
public void setDataOffset(int dataOffset)
|
||||||
{
|
{
|
||||||
this.dataOffset = dataOffset;
|
this.dataOffset = dataOffset;
|
||||||
checkmarkPanel.setDataOffset(dataOffset);
|
checkmarkPanel.setDataOffset(dataOffset);
|
||||||
|
numberPanel.setDataOffset(dataOffset);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setHabit(@NonNull Habit habit)
|
public void setHabit(@NonNull Habit habit)
|
||||||
@@ -122,15 +128,16 @@ public class HabitCardView extends FrameLayout
|
|||||||
|
|
||||||
this.habit = habit;
|
this.habit = habit;
|
||||||
checkmarkPanel.setHabit(habit);
|
checkmarkPanel.setHabit(habit);
|
||||||
|
numberPanel.setHabit(habit);
|
||||||
refresh();
|
refresh();
|
||||||
|
|
||||||
attachToHabit();
|
attachToHabit();
|
||||||
postInvalidate();
|
postInvalidate();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setScore(int score)
|
public void setScore(double score)
|
||||||
{
|
{
|
||||||
float percentage = (float) score / Score.MAX_VALUE;
|
float percentage = (float) score;
|
||||||
scoreRing.setPercentage(percentage);
|
scoreRing.setPercentage(percentage);
|
||||||
scoreRing.setPrecision(1.0f / 16);
|
scoreRing.setPrecision(1.0f / 16);
|
||||||
postInvalidate();
|
postInvalidate();
|
||||||
@@ -143,6 +150,23 @@ public class HabitCardView extends FrameLayout
|
|||||||
updateBackground(isSelected);
|
updateBackground(isSelected);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void setUnit(String unit)
|
||||||
|
{
|
||||||
|
numberPanel.setUnit(unit);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setValues(int values[])
|
||||||
|
{
|
||||||
|
double dvalues[] = new double[values.length];
|
||||||
|
for(int i = 0; i < values.length; i++)
|
||||||
|
dvalues[i] = (double) values[i] / 1000;
|
||||||
|
|
||||||
|
checkmarkPanel.setValues(values);
|
||||||
|
numberPanel.setValues(dvalues);
|
||||||
|
numberPanel.setThreshold(10);
|
||||||
|
postInvalidate();
|
||||||
|
}
|
||||||
|
|
||||||
public void triggerRipple(long timestamp)
|
public void triggerRipple(long timestamp)
|
||||||
{
|
{
|
||||||
long today = DateUtils.getStartOfToday();
|
long today = DateUtils.getStartOfToday();
|
||||||
@@ -191,7 +215,8 @@ public class HabitCardView extends FrameLayout
|
|||||||
inflate(context, R.layout.list_habits_card, this);
|
inflate(context, R.layout.list_habits_card, this);
|
||||||
ButterKnife.bind(this);
|
ButterKnife.bind(this);
|
||||||
|
|
||||||
innerFrame.setOnTouchListener((v, event) -> {
|
innerFrame.setOnTouchListener((v, event) ->
|
||||||
|
{
|
||||||
if (SDK_INT >= LOLLIPOP)
|
if (SDK_INT >= LOLLIPOP)
|
||||||
v.getBackground().setHotspot(event.getX(), event.getY());
|
v.getBackground().setHotspot(event.getX(), event.getY());
|
||||||
return false;
|
return false;
|
||||||
@@ -205,15 +230,12 @@ public class HabitCardView extends FrameLayout
|
|||||||
{
|
{
|
||||||
Random rand = new Random();
|
Random rand = new Random();
|
||||||
int color = ColorUtils.getAndroidTestColor(rand.nextInt(10));
|
int color = ColorUtils.getAndroidTestColor(rand.nextInt(10));
|
||||||
int[] values = new int[5];
|
|
||||||
for (int i = 0; i < 5; i++) values[i] = rand.nextInt(3);
|
|
||||||
|
|
||||||
label.setText(EDIT_MODE_HABITS[rand.nextInt(EDIT_MODE_HABITS.length)]);
|
label.setText(EDIT_MODE_HABITS[rand.nextInt(EDIT_MODE_HABITS.length)]);
|
||||||
label.setTextColor(color);
|
label.setTextColor(color);
|
||||||
scoreRing.setColor(color);
|
scoreRing.setColor(color);
|
||||||
scoreRing.setPercentage(rand.nextFloat());
|
scoreRing.setPercentage(rand.nextFloat());
|
||||||
checkmarkPanel.setColor(color);
|
checkmarkPanel.setColor(color);
|
||||||
checkmarkPanel.setCheckmarkValues(values);
|
numberPanel.setColor(color);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void refresh()
|
private void refresh()
|
||||||
@@ -223,6 +245,12 @@ public class HabitCardView extends FrameLayout
|
|||||||
label.setTextColor(color);
|
label.setTextColor(color);
|
||||||
scoreRing.setColor(color);
|
scoreRing.setColor(color);
|
||||||
checkmarkPanel.setColor(color);
|
checkmarkPanel.setColor(color);
|
||||||
|
numberPanel.setColor(color);
|
||||||
|
|
||||||
|
boolean isNumerical = habit.isNumerical();
|
||||||
|
checkmarkPanel.setVisibility(isNumerical ? GONE : VISIBLE);
|
||||||
|
numberPanel.setVisibility(isNumerical ? VISIBLE : GONE);
|
||||||
|
|
||||||
postInvalidate();
|
postInvalidate();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -256,5 +284,9 @@ public class HabitCardView extends FrameLayout
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public interface Controller extends CheckmarkPanelView.Controller {}
|
public interface Controller
|
||||||
|
extends CheckmarkPanelView.Controller, NumberPanelView.Controller
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,189 @@
|
|||||||
|
/*
|
||||||
|
* 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.list.views;
|
||||||
|
|
||||||
|
import android.content.*;
|
||||||
|
import android.content.res.*;
|
||||||
|
import android.graphics.*;
|
||||||
|
import android.icu.text.*;
|
||||||
|
import android.support.annotation.*;
|
||||||
|
import android.text.*;
|
||||||
|
import android.util.*;
|
||||||
|
import android.view.*;
|
||||||
|
|
||||||
|
import org.isoron.uhabits.*;
|
||||||
|
import org.isoron.uhabits.activities.habits.list.controllers.*;
|
||||||
|
import org.isoron.uhabits.utils.*;
|
||||||
|
|
||||||
|
import static org.isoron.uhabits.utils.AttributeSetUtils.*;
|
||||||
|
import static org.isoron.uhabits.utils.ColorUtils.*;
|
||||||
|
|
||||||
|
public class NumberButtonView extends View
|
||||||
|
{
|
||||||
|
private static Typeface BOLD_TYPEFACE =
|
||||||
|
Typeface.create("sans-serif-condensed", Typeface.BOLD);
|
||||||
|
|
||||||
|
private static Typeface NORMAL_TYPEFACE =
|
||||||
|
Typeface.create("sans-serif-condensed", Typeface.NORMAL);
|
||||||
|
|
||||||
|
private int color;
|
||||||
|
|
||||||
|
private double value;
|
||||||
|
|
||||||
|
private double threshold;
|
||||||
|
|
||||||
|
private String unit;
|
||||||
|
|
||||||
|
private RectF rect;
|
||||||
|
|
||||||
|
private TextPaint pRegular;
|
||||||
|
|
||||||
|
private Resources res;
|
||||||
|
|
||||||
|
private TextPaint pBold;
|
||||||
|
|
||||||
|
private int lightGrey;
|
||||||
|
|
||||||
|
private float em;
|
||||||
|
|
||||||
|
private int darkGrey;
|
||||||
|
|
||||||
|
public NumberButtonView(@Nullable Context context)
|
||||||
|
{
|
||||||
|
super(context);
|
||||||
|
init();
|
||||||
|
}
|
||||||
|
|
||||||
|
public NumberButtonView(@Nullable Context ctx, @Nullable AttributeSet attrs)
|
||||||
|
{
|
||||||
|
super(ctx, attrs);
|
||||||
|
init();
|
||||||
|
|
||||||
|
if (ctx != null && attrs != null)
|
||||||
|
{
|
||||||
|
int color = getIntAttribute(ctx, attrs, "color", 0);
|
||||||
|
int value = getIntAttribute(ctx, attrs, "value", 0);
|
||||||
|
int threshold = getIntAttribute(ctx, attrs, "threshold", 1);
|
||||||
|
String unit = getAttribute(ctx, attrs, "unit", "min");
|
||||||
|
setColor(getAndroidTestColor(color));
|
||||||
|
setThreshold(threshold);
|
||||||
|
setValue(value);
|
||||||
|
setUnit(unit);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param v
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public static String formatValue(double v)
|
||||||
|
{
|
||||||
|
if (v >= 1e9) return String.format("%.1fG", v / 1e9);
|
||||||
|
if (v >= 1e8) return String.format("%.0fM", v / 1e6);
|
||||||
|
if (v >= 1e7) return String.format("%.1fM", v / 1e6);
|
||||||
|
if (v >= 1e6) return String.format("%.1fM", v / 1e6);
|
||||||
|
if (v >= 1e5) return String.format("%.0fk", v / 1e3);
|
||||||
|
if (v >= 1e4) return String.format("%.1fk", v / 1e3);
|
||||||
|
if (v >= 1e3) return String.format("%.1fk", v / 1e3);
|
||||||
|
if (v >= 1e1) return new DecimalFormat("#.#").format(v);
|
||||||
|
return new DecimalFormat("#.##").format(v);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setColor(int color)
|
||||||
|
{
|
||||||
|
this.color = color;
|
||||||
|
postInvalidate();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setController(final NumberButtonController controller)
|
||||||
|
{
|
||||||
|
setOnClickListener(v -> controller.onClick());
|
||||||
|
setOnLongClickListener(v -> controller.onLongClick());
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setThreshold(double threshold)
|
||||||
|
{
|
||||||
|
this.threshold = threshold;
|
||||||
|
postInvalidate();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setUnit(String unit)
|
||||||
|
{
|
||||||
|
this.unit = unit;
|
||||||
|
postInvalidate();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setValue(double value)
|
||||||
|
{
|
||||||
|
this.value = value;
|
||||||
|
postInvalidate();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onDraw(Canvas canvas)
|
||||||
|
{
|
||||||
|
int activeColor = lightGrey;
|
||||||
|
if(value > 0 && value < threshold) activeColor = darkGrey;
|
||||||
|
if(value >= threshold) activeColor = color;
|
||||||
|
|
||||||
|
pRegular.setColor(activeColor);
|
||||||
|
pBold.setColor(activeColor);
|
||||||
|
|
||||||
|
String fv = formatValue(value);
|
||||||
|
|
||||||
|
rect.set(0, 0, getWidth(), getHeight());
|
||||||
|
canvas.drawText(fv, rect.centerX(), rect.centerY(), pBold);
|
||||||
|
|
||||||
|
rect.offset(0, 1.2f * em);
|
||||||
|
canvas.drawText(unit, rect.centerX(), rect.centerY(), pRegular);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec)
|
||||||
|
{
|
||||||
|
int width = (int) res.getDimension(R.dimen.checkmarkWidth);
|
||||||
|
int height = (int) res.getDimension(R.dimen.checkmarkHeight);
|
||||||
|
setMeasuredDimension(width, height);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void init()
|
||||||
|
{
|
||||||
|
StyledResources sr = new StyledResources(getContext());
|
||||||
|
res = getContext().getResources();
|
||||||
|
|
||||||
|
rect = new RectF();
|
||||||
|
pRegular = new TextPaint();
|
||||||
|
pRegular.setTextSize(res.getDimension(R.dimen.smallerTextSize));
|
||||||
|
pRegular.setTypeface(NORMAL_TYPEFACE);
|
||||||
|
pRegular.setAntiAlias(true);
|
||||||
|
pRegular.setTextAlign(Paint.Align.CENTER);
|
||||||
|
|
||||||
|
pBold = new TextPaint();
|
||||||
|
pBold.setTextSize(res.getDimension(R.dimen.smallTextSize));
|
||||||
|
pBold.setTypeface(BOLD_TYPEFACE);
|
||||||
|
pBold.setAntiAlias(true);
|
||||||
|
pBold.setTextAlign(Paint.Align.CENTER);
|
||||||
|
|
||||||
|
em = pBold.measureText("m");
|
||||||
|
lightGrey = sr.getColor(R.attr.lowContrastTextColor);
|
||||||
|
darkGrey = sr.getColor(R.attr.mediumContrastTextColor);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,261 @@
|
|||||||
|
/*
|
||||||
|
* 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.list.views;
|
||||||
|
|
||||||
|
import android.content.*;
|
||||||
|
import android.support.annotation.*;
|
||||||
|
import android.util.*;
|
||||||
|
import android.widget.*;
|
||||||
|
|
||||||
|
import org.isoron.uhabits.*;
|
||||||
|
import org.isoron.uhabits.activities.habits.list.*;
|
||||||
|
import org.isoron.uhabits.activities.habits.list.controllers.*;
|
||||||
|
import org.isoron.uhabits.models.*;
|
||||||
|
import org.isoron.uhabits.preferences.*;
|
||||||
|
import org.isoron.uhabits.utils.*;
|
||||||
|
|
||||||
|
import java.util.*;
|
||||||
|
|
||||||
|
import static android.view.View.MeasureSpec.*;
|
||||||
|
import static org.isoron.uhabits.utils.AttributeSetUtils.*;
|
||||||
|
import static org.isoron.uhabits.utils.ColorUtils.*;
|
||||||
|
|
||||||
|
public class NumberPanelView extends LinearLayout
|
||||||
|
implements Preferences.Listener
|
||||||
|
{
|
||||||
|
private static final int LEFT_TO_RIGHT = 0;
|
||||||
|
|
||||||
|
private static final int RIGHT_TO_LEFT = 1;
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
private Preferences prefs;
|
||||||
|
|
||||||
|
private double values[];
|
||||||
|
|
||||||
|
private double threshold;
|
||||||
|
|
||||||
|
private int nButtons;
|
||||||
|
|
||||||
|
private int color;
|
||||||
|
|
||||||
|
private Controller controller;
|
||||||
|
|
||||||
|
private String unit;
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
private Habit habit;
|
||||||
|
|
||||||
|
private int dataOffset;
|
||||||
|
|
||||||
|
public NumberPanelView(Context context)
|
||||||
|
{
|
||||||
|
super(context);
|
||||||
|
init();
|
||||||
|
}
|
||||||
|
|
||||||
|
public NumberPanelView(Context ctx, AttributeSet attrs)
|
||||||
|
{
|
||||||
|
super(ctx, attrs);
|
||||||
|
init();
|
||||||
|
|
||||||
|
if (ctx != null && attrs != null)
|
||||||
|
{
|
||||||
|
int paletteColor = getIntAttribute(ctx, attrs, "color", 0);
|
||||||
|
setColor(getAndroidTestColor(paletteColor));
|
||||||
|
setButtonCount(getIntAttribute(ctx, attrs, "button_count", 5));
|
||||||
|
setThreshold(getIntAttribute(ctx, attrs, "threshold", 1));
|
||||||
|
setUnit(getAttribute(ctx, attrs, "unit", "min"));
|
||||||
|
}
|
||||||
|
|
||||||
|
if(isInEditMode()) initEditMode();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setUnit(String unit)
|
||||||
|
{
|
||||||
|
this.unit = unit;
|
||||||
|
setupButtons();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void initEditMode()
|
||||||
|
{
|
||||||
|
double values[] = new double[nButtons];
|
||||||
|
for(int i = 0; i < nButtons; i++)
|
||||||
|
values[i] = new Random().nextDouble() * (threshold * 3);
|
||||||
|
setValues(values);
|
||||||
|
}
|
||||||
|
|
||||||
|
public NumberButtonView indexToButton(int i)
|
||||||
|
{
|
||||||
|
int position = i;
|
||||||
|
if (getCheckmarkOrder() == RIGHT_TO_LEFT) position = nButtons - i - 1;
|
||||||
|
return (NumberButtonView) getChildAt(position);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onCheckmarkOrderChanged()
|
||||||
|
{
|
||||||
|
setupButtons();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setButtonCount(int newButtonCount)
|
||||||
|
{
|
||||||
|
if (nButtons != newButtonCount)
|
||||||
|
{
|
||||||
|
nButtons = newButtonCount;
|
||||||
|
addButtons();
|
||||||
|
}
|
||||||
|
|
||||||
|
setupButtons();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setColor(int color)
|
||||||
|
{
|
||||||
|
this.color = color;
|
||||||
|
setupButtons();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setController(Controller controller)
|
||||||
|
{
|
||||||
|
this.controller = controller;
|
||||||
|
setupButtons();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setDataOffset(int dataOffset)
|
||||||
|
{
|
||||||
|
this.dataOffset = dataOffset;
|
||||||
|
setupButtons();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setHabit(@NonNull Habit habit)
|
||||||
|
{
|
||||||
|
this.habit = habit;
|
||||||
|
setupButtons();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setThreshold(double threshold)
|
||||||
|
{
|
||||||
|
this.threshold = threshold;
|
||||||
|
setupButtons();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setValues(double[] values)
|
||||||
|
{
|
||||||
|
this.values = values;
|
||||||
|
setupButtons();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onAttachedToWindow()
|
||||||
|
{
|
||||||
|
super.onAttachedToWindow();
|
||||||
|
if (prefs != null) prefs.addListener(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onDetachedFromWindow()
|
||||||
|
{
|
||||||
|
if (prefs != null) prefs.removeListener(this);
|
||||||
|
super.onDetachedFromWindow();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onMeasure(int widthSpec, int heightSpec)
|
||||||
|
{
|
||||||
|
float buttonWidth = getResources().getDimension(R.dimen.checkmarkWidth);
|
||||||
|
float buttonHeight =
|
||||||
|
getResources().getDimension(R.dimen.checkmarkHeight);
|
||||||
|
|
||||||
|
float width = buttonWidth * nButtons;
|
||||||
|
|
||||||
|
widthSpec = makeMeasureSpec((int) width, EXACTLY);
|
||||||
|
heightSpec = makeMeasureSpec((int) buttonHeight, EXACTLY);
|
||||||
|
|
||||||
|
super.onMeasure(widthSpec, heightSpec);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void addButtons()
|
||||||
|
{
|
||||||
|
removeAllViews();
|
||||||
|
|
||||||
|
for (int i = 0; i < nButtons; i++)
|
||||||
|
addView(new NumberButtonView(getContext()));
|
||||||
|
}
|
||||||
|
|
||||||
|
private int getCheckmarkOrder()
|
||||||
|
{
|
||||||
|
if (prefs == null) return LEFT_TO_RIGHT;
|
||||||
|
return prefs.shouldReverseCheckmarks() ? RIGHT_TO_LEFT : LEFT_TO_RIGHT;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void init()
|
||||||
|
{
|
||||||
|
Context appContext = getContext().getApplicationContext();
|
||||||
|
if (appContext instanceof HabitsApplication)
|
||||||
|
{
|
||||||
|
HabitsApplication app = (HabitsApplication) appContext;
|
||||||
|
prefs = app.getComponent().getPreferences();
|
||||||
|
}
|
||||||
|
|
||||||
|
setWillNotDraw(false);
|
||||||
|
values = new double[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
private void setupButtonControllers(long timestamp,
|
||||||
|
NumberButtonView buttonView)
|
||||||
|
{
|
||||||
|
if (controller == null) return;
|
||||||
|
if (!(getContext() instanceof ListHabitsActivity)) return;
|
||||||
|
|
||||||
|
ListHabitsActivity activity = (ListHabitsActivity) getContext();
|
||||||
|
NumberButtonControllerFactory buttonControllerFactory = activity
|
||||||
|
.getListHabitsComponent()
|
||||||
|
.getNumberButtonControllerFactory();
|
||||||
|
|
||||||
|
NumberButtonController buttonController =
|
||||||
|
buttonControllerFactory.create(habit, timestamp);
|
||||||
|
buttonController.setListener(controller);
|
||||||
|
buttonController.setView(buttonView);
|
||||||
|
buttonView.setController(buttonController);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void setupButtons()
|
||||||
|
{
|
||||||
|
long timestamp = DateUtils.getStartOfToday();
|
||||||
|
long day = DateUtils.millisecondsInOneDay;
|
||||||
|
timestamp -= day * dataOffset;
|
||||||
|
|
||||||
|
for (int i = 0; i < nButtons; i++)
|
||||||
|
{
|
||||||
|
NumberButtonView buttonView = indexToButton(i);
|
||||||
|
if (i + dataOffset >= values.length) break;
|
||||||
|
buttonView.setValue(values[i + dataOffset]);
|
||||||
|
buttonView.setColor(color);
|
||||||
|
buttonView.setThreshold(threshold);
|
||||||
|
buttonView.setUnit(unit);
|
||||||
|
setupButtonControllers(timestamp, buttonView);
|
||||||
|
timestamp -= day;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public interface Controller extends NumberButtonController.Listener
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -59,6 +59,9 @@ public class ShowHabitRootView extends BaseRootView
|
|||||||
@BindView(R.id.historyCard)
|
@BindView(R.id.historyCard)
|
||||||
HistoryCard historyCard;
|
HistoryCard historyCard;
|
||||||
|
|
||||||
|
@BindView(R.id.barCard)
|
||||||
|
BarCard barCard;
|
||||||
|
|
||||||
@BindView(R.id.toolbar)
|
@BindView(R.id.toolbar)
|
||||||
Toolbar toolbar;
|
Toolbar toolbar;
|
||||||
|
|
||||||
@@ -149,6 +152,11 @@ public class ShowHabitRootView extends BaseRootView
|
|||||||
historyCard.setHabit(habit);
|
historyCard.setHabit(habit);
|
||||||
streakCard.setHabit(habit);
|
streakCard.setHabit(habit);
|
||||||
frequencyCard.setHabit(habit);
|
frequencyCard.setHabit(habit);
|
||||||
|
|
||||||
|
if(habit.isNumerical())
|
||||||
|
barCard.setHabit(habit);
|
||||||
|
else
|
||||||
|
barCard.setVisibility(GONE);
|
||||||
}
|
}
|
||||||
|
|
||||||
public interface Controller extends HistoryCard.Controller
|
public interface Controller extends HistoryCard.Controller
|
||||||
|
|||||||
@@ -44,7 +44,8 @@ public class ShowHabitScreen extends BaseScreen
|
|||||||
public ShowHabitScreen(@NonNull BaseActivity activity,
|
public ShowHabitScreen(@NonNull BaseActivity activity,
|
||||||
@NonNull Habit habit,
|
@NonNull Habit habit,
|
||||||
@NonNull ShowHabitRootView view,
|
@NonNull ShowHabitRootView view,
|
||||||
@NonNull EditHabitDialogFactory editHabitDialogFactory)
|
@NonNull
|
||||||
|
EditHabitDialogFactory editHabitDialogFactory)
|
||||||
{
|
{
|
||||||
super(activity);
|
super(activity);
|
||||||
setRootView(view);
|
setRootView(view);
|
||||||
@@ -71,8 +72,9 @@ public class ShowHabitScreen extends BaseScreen
|
|||||||
|
|
||||||
public void showEditHabitDialog()
|
public void showEditHabitDialog()
|
||||||
{
|
{
|
||||||
EditHabitDialog dialog = editHabitDialogFactory.create(habit);
|
activity.showDialog(
|
||||||
activity.showDialog(dialog, "editHabit");
|
editHabitDialogFactory.edit(habit),
|
||||||
|
"editHabit");
|
||||||
}
|
}
|
||||||
|
|
||||||
public void showEditHistoryDialog()
|
public void showEditHistoryDialog()
|
||||||
|
|||||||
@@ -0,0 +1,115 @@
|
|||||||
|
/*
|
||||||
|
* 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.support.annotation.*;
|
||||||
|
import android.util.*;
|
||||||
|
import android.widget.*;
|
||||||
|
|
||||||
|
import org.isoron.uhabits.*;
|
||||||
|
import org.isoron.uhabits.R;
|
||||||
|
import org.isoron.uhabits.activities.common.views.*;
|
||||||
|
import org.isoron.uhabits.models.*;
|
||||||
|
import org.isoron.uhabits.tasks.*;
|
||||||
|
import org.isoron.uhabits.utils.*;
|
||||||
|
|
||||||
|
import java.util.*;
|
||||||
|
|
||||||
|
import butterknife.*;
|
||||||
|
|
||||||
|
public class BarCard extends HabitCard
|
||||||
|
{
|
||||||
|
@BindView(R.id.barChart)
|
||||||
|
BarChart chart;
|
||||||
|
|
||||||
|
@BindView(R.id.title)
|
||||||
|
TextView title;
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
private TaskRunner taskRunner;
|
||||||
|
|
||||||
|
public BarCard(Context context)
|
||||||
|
{
|
||||||
|
super(context);
|
||||||
|
init();
|
||||||
|
}
|
||||||
|
|
||||||
|
public BarCard(Context context, AttributeSet attrs)
|
||||||
|
{
|
||||||
|
super(context, attrs);
|
||||||
|
init();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void refreshData()
|
||||||
|
{
|
||||||
|
if(taskRunner == null) return;
|
||||||
|
taskRunner.execute(new RefreshTask(getHabit()));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void init()
|
||||||
|
{
|
||||||
|
inflate(getContext(), R.layout.show_habit_bar, this);
|
||||||
|
ButterKnife.bind(this);
|
||||||
|
|
||||||
|
Context appContext = getContext().getApplicationContext();
|
||||||
|
if (appContext instanceof HabitsApplication)
|
||||||
|
{
|
||||||
|
HabitsApplication app = (HabitsApplication) appContext;
|
||||||
|
taskRunner = app.getComponent().getTaskRunner();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isInEditMode()) initEditMode();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void initEditMode()
|
||||||
|
{
|
||||||
|
int color = ColorUtils.getAndroidTestColor(1);
|
||||||
|
title.setTextColor(color);
|
||||||
|
chart.setColor(color);
|
||||||
|
chart.populateWithRandomData();
|
||||||
|
}
|
||||||
|
|
||||||
|
private class RefreshTask implements Task
|
||||||
|
{
|
||||||
|
private final Habit habit;
|
||||||
|
|
||||||
|
public RefreshTask(Habit habit) {this.habit = habit;}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void doInBackground()
|
||||||
|
{
|
||||||
|
long today = DateUtils.getStartOfToday();
|
||||||
|
List<Checkmark> checkmarks =
|
||||||
|
habit.getCheckmarks().getByInterval(0, today);
|
||||||
|
chart.setCheckmarks(checkmarks);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onPreExecute()
|
||||||
|
{
|
||||||
|
int color = ColorUtils.getColor(getContext(), habit.getColor());
|
||||||
|
title.setTextColor(color);
|
||||||
|
chart.setColor(color);
|
||||||
|
chart.setTarget(habit.getTargetValue());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -126,6 +126,11 @@ public class HistoryCard extends HabitCard
|
|||||||
int color = ColorUtils.getColor(getContext(), habit.getColor());
|
int color = ColorUtils.getColor(getContext(), habit.getColor());
|
||||||
title.setTextColor(color);
|
title.setTextColor(color);
|
||||||
chart.setColor(color);
|
chart.setColor(color);
|
||||||
|
if(habit.isNumerical())
|
||||||
|
{
|
||||||
|
chart.setTarget((int) (habit.getTargetValue() * 1000));
|
||||||
|
chart.setNumerical(true);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -105,9 +105,9 @@ public class OverviewCard extends HabitCard
|
|||||||
private void initEditMode()
|
private void initEditMode()
|
||||||
{
|
{
|
||||||
color = ColorUtils.getAndroidTestColor(1);
|
color = ColorUtils.getAndroidTestColor(1);
|
||||||
cache.todayScore = Score.MAX_VALUE * 0.6f;
|
cache.todayScore = 0.6f;
|
||||||
cache.lastMonthScore = Score.MAX_VALUE * 0.42f;
|
cache.lastMonthScore = 0.42f;
|
||||||
cache.lastYearScore = Score.MAX_VALUE * 0.75f;
|
cache.lastYearScore = 0.75f;
|
||||||
refreshColors();
|
refreshColors();
|
||||||
refreshScore();
|
refreshScore();
|
||||||
}
|
}
|
||||||
@@ -121,11 +121,9 @@ public class OverviewCard extends HabitCard
|
|||||||
|
|
||||||
private void refreshScore()
|
private void refreshScore()
|
||||||
{
|
{
|
||||||
float todayPercentage = cache.todayScore / Score.MAX_VALUE;
|
float todayPercentage = cache.todayScore;
|
||||||
float monthDiff =
|
float monthDiff = todayPercentage - cache.lastMonthScore;
|
||||||
todayPercentage - (cache.lastMonthScore / Score.MAX_VALUE);
|
float yearDiff = todayPercentage - cache.lastYearScore;
|
||||||
float yearDiff =
|
|
||||||
todayPercentage - (cache.lastYearScore / Score.MAX_VALUE);
|
|
||||||
|
|
||||||
scoreRing.setPercentage(todayPercentage);
|
scoreRing.setPercentage(todayPercentage);
|
||||||
scoreLabel.setText(String.format("%.0f%%", todayPercentage * 100));
|
scoreLabel.setText(String.format("%.0f%%", todayPercentage * 100));
|
||||||
|
|||||||
@@ -0,0 +1,78 @@
|
|||||||
|
/*
|
||||||
|
* 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.commands;
|
||||||
|
|
||||||
|
import android.support.annotation.*;
|
||||||
|
|
||||||
|
import org.isoron.uhabits.models.*;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Command to toggle a repetition.
|
||||||
|
*/
|
||||||
|
public class CreateRepetitionCommand extends Command
|
||||||
|
{
|
||||||
|
@NonNull
|
||||||
|
private final Habit habit;
|
||||||
|
|
||||||
|
private final long timestamp;
|
||||||
|
|
||||||
|
private final int value;
|
||||||
|
|
||||||
|
private Repetition previousRep;
|
||||||
|
|
||||||
|
private Repetition newRep;
|
||||||
|
|
||||||
|
public CreateRepetitionCommand(@NonNull Habit habit,
|
||||||
|
long timestamp,
|
||||||
|
int value)
|
||||||
|
{
|
||||||
|
this.timestamp = timestamp;
|
||||||
|
this.habit = habit;
|
||||||
|
this.value = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void execute()
|
||||||
|
{
|
||||||
|
RepetitionList reps = habit.getRepetitions();
|
||||||
|
|
||||||
|
previousRep = reps.getByTimestamp(timestamp);
|
||||||
|
if (previousRep != null) reps.remove(previousRep);
|
||||||
|
|
||||||
|
newRep = new Repetition(timestamp, value);
|
||||||
|
reps.add(newRep);
|
||||||
|
|
||||||
|
habit.invalidateNewerThan(timestamp);
|
||||||
|
}
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
public Habit getHabit()
|
||||||
|
{
|
||||||
|
return habit;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void undo()
|
||||||
|
{
|
||||||
|
habit.getRepetitions().remove(newRep);
|
||||||
|
if (previousRep != null) habit.getRepetitions().add(previousRep);
|
||||||
|
habit.invalidateNewerThan(timestamp);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -42,6 +42,8 @@ public class EditHabitCommand extends Command
|
|||||||
|
|
||||||
private boolean hasFrequencyChanged;
|
private boolean hasFrequencyChanged;
|
||||||
|
|
||||||
|
private final boolean hasTargetChanged;
|
||||||
|
|
||||||
public EditHabitCommand(@Provided @NonNull ModelFactory modelFactory,
|
public EditHabitCommand(@Provided @NonNull ModelFactory modelFactory,
|
||||||
@NonNull HabitList habitList,
|
@NonNull HabitList habitList,
|
||||||
@NonNull Habit original,
|
@NonNull Habit original,
|
||||||
@@ -58,6 +60,9 @@ public class EditHabitCommand extends Command
|
|||||||
Frequency originalFreq = this.original.getFrequency();
|
Frequency originalFreq = this.original.getFrequency();
|
||||||
Frequency modifiedFreq = this.modified.getFrequency();
|
Frequency modifiedFreq = this.modified.getFrequency();
|
||||||
hasFrequencyChanged = (!originalFreq.equals(modifiedFreq));
|
hasFrequencyChanged = (!originalFreq.equals(modifiedFreq));
|
||||||
|
hasTargetChanged =
|
||||||
|
(original.getTargetType() != modified.getTargetType() ||
|
||||||
|
original.getTargetValue() != modified.getTargetValue());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -97,11 +102,7 @@ public class EditHabitCommand extends Command
|
|||||||
|
|
||||||
private void invalidateIfNeeded(Habit habit)
|
private void invalidateIfNeeded(Habit habit)
|
||||||
{
|
{
|
||||||
if (hasFrequencyChanged)
|
if (hasFrequencyChanged || hasTargetChanged)
|
||||||
{
|
habit.invalidateNewerThan(0);
|
||||||
habit.getCheckmarks().invalidateNewerThan(0);
|
|
||||||
habit.getStreaks().invalidateNewerThan(0);
|
|
||||||
habit.getScores().invalidateNewerThan(0);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -178,7 +178,7 @@ public class HabitsCSVExporter
|
|||||||
long newest = DateUtils.getStartOfToday();
|
long newest = DateUtils.getStartOfToday();
|
||||||
|
|
||||||
List<int[]> checkmarks = new ArrayList<>();
|
List<int[]> checkmarks = new ArrayList<>();
|
||||||
List<int[]> scores = new ArrayList<>();
|
List<double[]> scores = new ArrayList<>();
|
||||||
for (Habit h : selectedHabits)
|
for (Habit h : selectedHabits)
|
||||||
{
|
{
|
||||||
checkmarks.add(h.getCheckmarks().getValues(oldest, newest));
|
checkmarks.add(h.getCheckmarks().getValues(oldest, newest));
|
||||||
@@ -202,7 +202,7 @@ public class HabitsCSVExporter
|
|||||||
checksWriter.write(String.valueOf(checkmarks.get(j)[i]));
|
checksWriter.write(String.valueOf(checkmarks.get(j)[i]));
|
||||||
checksWriter.write(DELIMITER);
|
checksWriter.write(DELIMITER);
|
||||||
String score =
|
String score =
|
||||||
String.format("%.4f", ((float) scores.get(j)[i]) / Score.MAX_VALUE);
|
String.format("%.4f", ((float) scores.get(j)[i]));
|
||||||
scoresWriter.write(score);
|
scoresWriter.write(score);
|
||||||
scoresWriter.write(DELIMITER);
|
scoresWriter.write(DELIMITER);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -51,6 +51,15 @@ public final class Checkmark
|
|||||||
|
|
||||||
private final long timestamp;
|
private final long timestamp;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The value of the checkmark.
|
||||||
|
*
|
||||||
|
* For boolean habits, this equals either UNCHECKED, CHECKED_EXPLICITLY,
|
||||||
|
* or CHECKED_IMPLICITLY.
|
||||||
|
*
|
||||||
|
* For numerical habits, this number is stored in thousandths. That
|
||||||
|
* is, if the user enters value 1.50 on the app, it is stored as 1500.
|
||||||
|
*/
|
||||||
private final int value;
|
private final int value;
|
||||||
|
|
||||||
public Checkmark(long timestamp, int value)
|
public Checkmark(long timestamp, int value)
|
||||||
|
|||||||
@@ -27,6 +27,9 @@ import java.io.*;
|
|||||||
import java.text.*;
|
import java.text.*;
|
||||||
import java.util.*;
|
import java.util.*;
|
||||||
|
|
||||||
|
import static org.isoron.uhabits.models.Checkmark.CHECKED_EXPLICITLY;
|
||||||
|
import static org.isoron.uhabits.models.Checkmark.CHECKED_IMPLICITLY;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The collection of {@link Checkmark}s belonging to a habit.
|
* The collection of {@link Checkmark}s belonging to a habit.
|
||||||
*/
|
*/
|
||||||
@@ -239,7 +242,7 @@ public abstract class CheckmarkList
|
|||||||
for (Repetition rep : reps)
|
for (Repetition rep : reps)
|
||||||
{
|
{
|
||||||
int offset = (int) ((rep.getTimestamp() - fromExtended) / day);
|
int offset = (int) ((rep.getTimestamp() - fromExtended) / day);
|
||||||
checks[nDaysExtended - offset - 1] = Checkmark.CHECKED_EXPLICITLY;
|
checks[nDaysExtended - offset - 1] = rep.getValue();
|
||||||
}
|
}
|
||||||
|
|
||||||
for (int i = 0; i < nDays; i++)
|
for (int i = 0; i < nDays; i++)
|
||||||
@@ -247,11 +250,11 @@ public abstract class CheckmarkList
|
|||||||
int counter = 0;
|
int counter = 0;
|
||||||
|
|
||||||
for (int j = 0; j < freq.getDenominator(); j++)
|
for (int j = 0; j < freq.getDenominator(); j++)
|
||||||
if (checks[i + j] == 2) counter++;
|
if (checks[i + j] == CHECKED_EXPLICITLY) counter++;
|
||||||
|
|
||||||
if (counter >= freq.getNumerator())
|
if (counter >= freq.getNumerator())
|
||||||
if (checks[i] != Checkmark.CHECKED_EXPLICITLY)
|
if (checks[i] != CHECKED_EXPLICITLY)
|
||||||
checks[i] = Checkmark.CHECKED_IMPLICITLY;
|
checks[i] = CHECKED_IMPLICITLY;
|
||||||
}
|
}
|
||||||
|
|
||||||
List<Checkmark> checkmarks = new LinkedList<>();
|
List<Checkmark> checkmarks = new LinkedList<>();
|
||||||
|
|||||||
@@ -28,14 +28,24 @@ import java.util.*;
|
|||||||
|
|
||||||
import javax.inject.*;
|
import javax.inject.*;
|
||||||
|
|
||||||
|
import static org.isoron.uhabits.models.Checkmark.*;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The thing that the user wants to track.
|
* The thing that the user wants to track.
|
||||||
*/
|
*/
|
||||||
public class Habit
|
public class Habit
|
||||||
{
|
{
|
||||||
|
public static final int AT_LEAST = 0;
|
||||||
|
|
||||||
|
public static final int AT_MOST = 1;
|
||||||
|
|
||||||
public static final String HABIT_URI_FORMAT =
|
public static final String HABIT_URI_FORMAT =
|
||||||
"content://org.isoron.uhabits/habit/%d";
|
"content://org.isoron.uhabits/habit/%d";
|
||||||
|
|
||||||
|
public static final int NUMBER_HABIT = 1;
|
||||||
|
|
||||||
|
public static final int YES_NO_HABIT = 0;
|
||||||
|
|
||||||
@Nullable
|
@Nullable
|
||||||
private Long id;
|
private Long id;
|
||||||
|
|
||||||
@@ -48,10 +58,8 @@ public class Habit
|
|||||||
@NonNull
|
@NonNull
|
||||||
private Frequency frequency;
|
private Frequency frequency;
|
||||||
|
|
||||||
@NonNull
|
private int color;
|
||||||
private Integer color;
|
|
||||||
|
|
||||||
@NonNull
|
|
||||||
private boolean archived;
|
private boolean archived;
|
||||||
|
|
||||||
@NonNull
|
@NonNull
|
||||||
@@ -60,12 +68,21 @@ public class Habit
|
|||||||
@NonNull
|
@NonNull
|
||||||
private ScoreList scores;
|
private ScoreList scores;
|
||||||
|
|
||||||
|
private int targetType;
|
||||||
|
|
||||||
|
private double targetValue;
|
||||||
|
|
||||||
|
private int type;
|
||||||
|
|
||||||
@NonNull
|
@NonNull
|
||||||
private RepetitionList repetitions;
|
private RepetitionList repetitions;
|
||||||
|
|
||||||
@NonNull
|
@NonNull
|
||||||
private CheckmarkList checkmarks;
|
private CheckmarkList checkmarks;
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
private String unit;
|
||||||
|
|
||||||
@Nullable
|
@Nullable
|
||||||
private Reminder reminder;
|
private Reminder reminder;
|
||||||
|
|
||||||
@@ -83,6 +100,12 @@ public class Habit
|
|||||||
this.color = 5;
|
this.color = 5;
|
||||||
this.archived = false;
|
this.archived = false;
|
||||||
this.frequency = new Frequency(3, 7);
|
this.frequency = new Frequency(3, 7);
|
||||||
|
this.type = YES_NO_HABIT;
|
||||||
|
this.name = "";
|
||||||
|
this.description = "";
|
||||||
|
this.targetType = AT_LEAST;
|
||||||
|
this.targetValue = 100;
|
||||||
|
this.unit = "";
|
||||||
|
|
||||||
checkmarks = factory.buildCheckmarkList(this);
|
checkmarks = factory.buildCheckmarkList(this);
|
||||||
streaks = factory.buildStreakList(this);
|
streaks = factory.buildStreakList(this);
|
||||||
@@ -112,6 +135,10 @@ public class Habit
|
|||||||
this.archived = model.isArchived();
|
this.archived = model.isArchived();
|
||||||
this.frequency = model.frequency;
|
this.frequency = model.frequency;
|
||||||
this.reminder = model.reminder;
|
this.reminder = model.reminder;
|
||||||
|
this.type = model.type;
|
||||||
|
this.targetValue = model.targetValue;
|
||||||
|
this.targetType = model.targetType;
|
||||||
|
this.unit = model.unit;
|
||||||
observable.notifyListeners();
|
observable.notifyListeners();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -138,6 +165,13 @@ public class Habit
|
|||||||
return color;
|
return color;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public boolean isCompletedToday()
|
||||||
|
{
|
||||||
|
int todayCheckmark = getCheckmarks().getTodayValue();
|
||||||
|
if (isNumerical()) return todayCheckmark >= targetValue;
|
||||||
|
else return (todayCheckmark != UNCHECKED);
|
||||||
|
}
|
||||||
|
|
||||||
public void setColor(@NonNull Integer color)
|
public void setColor(@NonNull Integer color)
|
||||||
{
|
{
|
||||||
this.color = color;
|
this.color = color;
|
||||||
@@ -232,6 +266,53 @@ public class Habit
|
|||||||
return streaks;
|
return streaks;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public int getTargetType()
|
||||||
|
{
|
||||||
|
return targetType;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setTargetType(int targetType)
|
||||||
|
{
|
||||||
|
if (targetType != AT_LEAST && targetType != AT_MOST)
|
||||||
|
throw new IllegalArgumentException();
|
||||||
|
this.targetType = targetType;
|
||||||
|
}
|
||||||
|
|
||||||
|
public double getTargetValue()
|
||||||
|
{
|
||||||
|
return targetValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setTargetValue(double targetValue)
|
||||||
|
{
|
||||||
|
if(targetValue < 0) throw new IllegalArgumentException();
|
||||||
|
this.targetValue = targetValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getType()
|
||||||
|
{
|
||||||
|
return type;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setType(int type)
|
||||||
|
{
|
||||||
|
if (type != YES_NO_HABIT && type != NUMBER_HABIT)
|
||||||
|
throw new IllegalArgumentException();
|
||||||
|
|
||||||
|
this.type = type;
|
||||||
|
}
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
public String getUnit()
|
||||||
|
{
|
||||||
|
return unit;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setUnit(@NonNull String unit)
|
||||||
|
{
|
||||||
|
this.unit = unit;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the public URI that identifies this habit
|
* Returns the public URI that identifies this habit
|
||||||
*
|
*
|
||||||
@@ -253,6 +334,13 @@ public class Habit
|
|||||||
return reminder != null;
|
return reminder != null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void invalidateNewerThan(long timestamp)
|
||||||
|
{
|
||||||
|
getScores().invalidateNewerThan(timestamp);
|
||||||
|
getCheckmarks().invalidateNewerThan(timestamp);
|
||||||
|
getStreaks().invalidateNewerThan(timestamp);
|
||||||
|
}
|
||||||
|
|
||||||
public boolean isArchived()
|
public boolean isArchived()
|
||||||
{
|
{
|
||||||
return archived;
|
return archived;
|
||||||
@@ -263,6 +351,11 @@ public class Habit
|
|||||||
this.archived = archived;
|
this.archived = archived;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public boolean isNumerical()
|
||||||
|
{
|
||||||
|
return type == NUMBER_HABIT;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String toString()
|
public String toString()
|
||||||
{
|
{
|
||||||
@@ -272,6 +365,10 @@ public class Habit
|
|||||||
.append("description", description)
|
.append("description", description)
|
||||||
.append("color", color)
|
.append("color", color)
|
||||||
.append("archived", archived)
|
.append("archived", archived)
|
||||||
|
.append("type", type)
|
||||||
|
.append("targetType", targetType)
|
||||||
|
.append("targetValue", targetValue)
|
||||||
|
.append("unit", unit)
|
||||||
.toString();
|
.toString();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -23,8 +23,6 @@ import android.support.annotation.*;
|
|||||||
|
|
||||||
import java.util.*;
|
import java.util.*;
|
||||||
|
|
||||||
import static org.isoron.uhabits.models.Checkmark.*;
|
|
||||||
|
|
||||||
public class HabitMatcher
|
public class HabitMatcher
|
||||||
{
|
{
|
||||||
public static final HabitMatcher WITH_ALARM = new HabitMatcherBuilder()
|
public static final HabitMatcher WITH_ALARM = new HabitMatcherBuilder()
|
||||||
@@ -75,14 +73,8 @@ public class HabitMatcher
|
|||||||
{
|
{
|
||||||
if (!isArchivedAllowed() && habit.isArchived()) return false;
|
if (!isArchivedAllowed() && habit.isArchived()) return false;
|
||||||
if (isReminderRequired() && !habit.hasReminder()) return false;
|
if (isReminderRequired() && !habit.hasReminder()) return false;
|
||||||
|
if (!isCompletedAllowed() && habit.isCompletedToday()) return false;
|
||||||
if(!isCompletedAllowed())
|
if (!allowedColors.contains(habit.getColor())) return false;
|
||||||
{
|
|
||||||
int todayCheckmark = habit.getCheckmarks().getTodayValue();
|
|
||||||
if (todayCheckmark != UNCHECKED) return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if(!allowedColors.contains(habit.getColor())) return false;
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -30,6 +30,17 @@ public final class Repetition
|
|||||||
|
|
||||||
private final long timestamp;
|
private final long timestamp;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The value of the repetition.
|
||||||
|
*
|
||||||
|
* For boolean habits, this equals either Checkmark.UNCHECKED,
|
||||||
|
* Checkmark.CHECKED_EXPLICITLY, or Checkmark.CHECKED_IMPLICITLY.
|
||||||
|
*
|
||||||
|
* For numerical habits, this number is stored in thousandths. That
|
||||||
|
* is, if the user enters value 1.50 on the app, it is stored as 1500.
|
||||||
|
*/
|
||||||
|
private final int value;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates a new repetition with given parameters.
|
* Creates a new repetition with given parameters.
|
||||||
* <p>
|
* <p>
|
||||||
@@ -38,9 +49,24 @@ public final class Repetition
|
|||||||
*
|
*
|
||||||
* @param timestamp the time this repetition occurred.
|
* @param timestamp the time this repetition occurred.
|
||||||
*/
|
*/
|
||||||
public Repetition(long timestamp)
|
public Repetition(long timestamp, int value)
|
||||||
{
|
{
|
||||||
this.timestamp = timestamp;
|
this.timestamp = timestamp;
|
||||||
|
this.value = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean equals(Object o)
|
||||||
|
{
|
||||||
|
if (this == o) return true;
|
||||||
|
if (o == null || getClass() != o.getClass()) return false;
|
||||||
|
|
||||||
|
Repetition that = (Repetition) o;
|
||||||
|
|
||||||
|
return new EqualsBuilder()
|
||||||
|
.append(timestamp, that.timestamp)
|
||||||
|
.append(value, that.value)
|
||||||
|
.isEquals();
|
||||||
}
|
}
|
||||||
|
|
||||||
public long getTimestamp()
|
public long getTimestamp()
|
||||||
@@ -48,11 +74,26 @@ public final class Repetition
|
|||||||
return timestamp;
|
return timestamp;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public int getValue()
|
||||||
|
{
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int hashCode()
|
||||||
|
{
|
||||||
|
return new HashCodeBuilder(17, 37)
|
||||||
|
.append(timestamp)
|
||||||
|
.append(value)
|
||||||
|
.toHashCode();
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String toString()
|
public String toString()
|
||||||
{
|
{
|
||||||
return new ToStringBuilder(this)
|
return new ToStringBuilder(this)
|
||||||
.append("timestamp", timestamp)
|
.append("timestamp", timestamp)
|
||||||
|
.append("value", value)
|
||||||
.toString();
|
.toString();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -192,13 +192,11 @@ public abstract class RepetitionList
|
|||||||
if (rep != null) remove(rep);
|
if (rep != null) remove(rep);
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
rep = new Repetition(timestamp);
|
rep = new Repetition(timestamp, Checkmark.CHECKED_EXPLICITLY);
|
||||||
add(rep);
|
add(rep);
|
||||||
}
|
}
|
||||||
|
|
||||||
habit.getScores().invalidateNewerThan(timestamp);
|
habit.invalidateNewerThan(timestamp);
|
||||||
habit.getCheckmarks().invalidateNewerThan(timestamp);
|
|
||||||
habit.getStreaks().invalidateNewerThan(timestamp);
|
|
||||||
return rep;
|
return rep;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -21,28 +21,25 @@ package org.isoron.uhabits.models;
|
|||||||
|
|
||||||
import org.apache.commons.lang3.builder.*;
|
import org.apache.commons.lang3.builder.*;
|
||||||
|
|
||||||
|
import static java.lang.Math.*;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Represents how strong a habit is at a certain date.
|
* Represents how strong a habit is at a certain date.
|
||||||
*/
|
*/
|
||||||
public final class Score
|
public final class Score
|
||||||
{
|
{
|
||||||
/**
|
|
||||||
* Maximum score value attainable by any habit.
|
|
||||||
*/
|
|
||||||
public static final int MAX_VALUE = 19259478;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Timestamp of the day to which this score applies. Time of day should be
|
* Timestamp of the day to which this score applies. Time of day should be
|
||||||
* midnight (UTC).
|
* midnight (UTC).
|
||||||
*/
|
*/
|
||||||
private final Long timestamp;
|
private final long timestamp;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Value of the score.
|
* Value of the score.
|
||||||
*/
|
*/
|
||||||
private final Integer value;
|
private final double value;
|
||||||
|
|
||||||
public Score(Long timestamp, Integer value)
|
public Score(long timestamp, double value)
|
||||||
{
|
{
|
||||||
this.timestamp = timestamp;
|
this.timestamp = timestamp;
|
||||||
this.value = value;
|
this.value = value;
|
||||||
@@ -55,27 +52,20 @@ public final class Score
|
|||||||
* The frequency of the habit is the number of repetitions divided by the
|
* The frequency of the habit is the number of repetitions divided by the
|
||||||
* length of the interval. For example, a habit that should be repeated 3
|
* length of the interval. For example, a habit that should be repeated 3
|
||||||
* times in 8 days has frequency 3.0 / 8.0 = 0.375.
|
* times in 8 days has frequency 3.0 / 8.0 = 0.375.
|
||||||
* <p>
|
|
||||||
* The checkmarkValue should be UNCHECKED, CHECKED_IMPLICITLY or
|
|
||||||
* CHECK_EXPLICITLY.
|
|
||||||
*
|
*
|
||||||
* @param frequency the frequency of the habit
|
* @param frequency the frequency of the habit
|
||||||
* @param previousScore the previous score of the habit
|
* @param previousScore the previous score of the habit
|
||||||
* @param checkmarkValue the value of the current checkmark
|
* @param checkmarkValue the value of the current checkmark
|
||||||
* @return the current score
|
* @return the current score
|
||||||
*/
|
*/
|
||||||
public static int compute(double frequency,
|
public static double compute(double frequency,
|
||||||
int previousScore,
|
double previousScore,
|
||||||
int checkmarkValue)
|
double checkmarkValue)
|
||||||
{
|
{
|
||||||
double multiplier = Math.pow(0.5, 1.0 / (14.0 / frequency - 1));
|
double multiplier = pow(0.5, frequency / 13.0);
|
||||||
int score = (int) (previousScore * multiplier);
|
|
||||||
|
|
||||||
if (checkmarkValue == Checkmark.CHECKED_EXPLICITLY)
|
double score = previousScore * multiplier;
|
||||||
{
|
score += checkmarkValue * (1 - multiplier);
|
||||||
score += 1000000;
|
|
||||||
score = Math.min(score, Score.MAX_VALUE);
|
|
||||||
}
|
|
||||||
|
|
||||||
return score;
|
return score;
|
||||||
}
|
}
|
||||||
@@ -85,12 +75,12 @@ public final class Score
|
|||||||
return Long.signum(this.getTimestamp() - other.getTimestamp());
|
return Long.signum(this.getTimestamp() - other.getTimestamp());
|
||||||
}
|
}
|
||||||
|
|
||||||
public Long getTimestamp()
|
public long getTimestamp()
|
||||||
{
|
{
|
||||||
return timestamp;
|
return timestamp;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Integer getValue()
|
public double getValue()
|
||||||
{
|
{
|
||||||
return value;
|
return value;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -67,7 +67,7 @@ public abstract class ScoreList implements Iterable<Score>
|
|||||||
*
|
*
|
||||||
* @return value of today's score
|
* @return value of today's score
|
||||||
*/
|
*/
|
||||||
public int getTodayValue()
|
public double getTodayValue()
|
||||||
{
|
{
|
||||||
return getValue(DateUtils.getStartOfToday());
|
return getValue(DateUtils.getStartOfToday());
|
||||||
}
|
}
|
||||||
@@ -81,7 +81,7 @@ public abstract class ScoreList implements Iterable<Score>
|
|||||||
* @param timestamp the timestamp of a day
|
* @param timestamp the timestamp of a day
|
||||||
* @return score value for that day
|
* @return score value for that day
|
||||||
*/
|
*/
|
||||||
public final int getValue(long timestamp)
|
public final double getValue(long timestamp)
|
||||||
{
|
{
|
||||||
compute(timestamp, timestamp);
|
compute(timestamp, timestamp);
|
||||||
Score s = getComputedByTimestamp(timestamp);
|
Score s = getComputedByTimestamp(timestamp);
|
||||||
@@ -118,10 +118,10 @@ public abstract class ScoreList implements Iterable<Score>
|
|||||||
* @param to timestamp for the newest score
|
* @param to timestamp for the newest score
|
||||||
* @return values for the scores inside the given interval
|
* @return values for the scores inside the given interval
|
||||||
*/
|
*/
|
||||||
public final int[] getValues(long from, long to)
|
public final double[] getValues(long from, long to)
|
||||||
{
|
{
|
||||||
List<Score> scores = getByInterval(from, to);
|
List<Score> scores = getByInterval(from, to);
|
||||||
int[] values = new int[scores.size()];
|
double[] values = new double[scores.size()];
|
||||||
|
|
||||||
for(int i = 0; i < values.length; i++)
|
for(int i = 0; i < values.length; i++)
|
||||||
values[i] = scores.get(i).getValue();
|
values[i] = scores.get(i).getValue();
|
||||||
@@ -132,7 +132,7 @@ public abstract class ScoreList implements Iterable<Score>
|
|||||||
public List<Score> groupBy(DateUtils.TruncateField field)
|
public List<Score> groupBy(DateUtils.TruncateField field)
|
||||||
{
|
{
|
||||||
computeAll();
|
computeAll();
|
||||||
HashMap<Long, ArrayList<Long>> groups = getGroupedValues(field);
|
HashMap<Long, ArrayList<Double>> groups = getGroupedValues(field);
|
||||||
List<Score> scores = groupsToAvgScores(groups);
|
List<Score> scores = groupsToAvgScores(groups);
|
||||||
Collections.sort(scores, (s1, s2) -> s2.compareNewer(s1));
|
Collections.sort(scores, (s1, s2) -> s2.compareNewer(s1));
|
||||||
return scores;
|
return scores;
|
||||||
@@ -173,7 +173,7 @@ public abstract class ScoreList implements Iterable<Score>
|
|||||||
{
|
{
|
||||||
String timestamp = dateFormat.format(s.getTimestamp());
|
String timestamp = dateFormat.format(s.getTimestamp());
|
||||||
String score =
|
String score =
|
||||||
String.format("%.4f", ((float) s.getValue()) / Score.MAX_VALUE);
|
String.format("%.4f", s.getValue());
|
||||||
out.write(String.format("%s,%s\n", timestamp, score));
|
out.write(String.format("%s,%s\n", timestamp, score));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -263,7 +263,7 @@ public abstract class ScoreList implements Iterable<Score>
|
|||||||
* @param previousValue value of the score on the day immediately before the
|
* @param previousValue value of the score on the day immediately before the
|
||||||
* interval begins
|
* interval begins
|
||||||
*/
|
*/
|
||||||
private void forceRecompute(long from, long to, int previousValue)
|
private void forceRecompute(long from, long to, double previousValue)
|
||||||
{
|
{
|
||||||
if(from > to) return;
|
if(from > to) return;
|
||||||
|
|
||||||
@@ -276,7 +276,18 @@ public abstract class ScoreList implements Iterable<Score>
|
|||||||
|
|
||||||
for (int i = 0; i < checkmarkValues.length; i++)
|
for (int i = 0; i < checkmarkValues.length; i++)
|
||||||
{
|
{
|
||||||
int value = checkmarkValues[checkmarkValues.length - i - 1];
|
double value = checkmarkValues[checkmarkValues.length - i - 1];
|
||||||
|
|
||||||
|
if(habit.isNumerical())
|
||||||
|
{
|
||||||
|
value /= 1000;
|
||||||
|
value /= habit.getTargetValue();
|
||||||
|
value = Math.min(1, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
if(!habit.isNumerical() && value > 0)
|
||||||
|
value = 1;
|
||||||
|
|
||||||
previousValue = Score.compute(freq, previousValue, value);
|
previousValue = Score.compute(freq, previousValue, value);
|
||||||
scores.add(new Score(from + day * i, previousValue));
|
scores.add(new Score(from + day * i, previousValue));
|
||||||
}
|
}
|
||||||
@@ -285,9 +296,9 @@ public abstract class ScoreList implements Iterable<Score>
|
|||||||
}
|
}
|
||||||
|
|
||||||
@NonNull
|
@NonNull
|
||||||
private HashMap<Long, ArrayList<Long>> getGroupedValues(DateUtils.TruncateField field)
|
private HashMap<Long, ArrayList<Double>> getGroupedValues(DateUtils.TruncateField field)
|
||||||
{
|
{
|
||||||
HashMap<Long, ArrayList<Long>> groups = new HashMap<>();
|
HashMap<Long, ArrayList<Double>> groups = new HashMap<>();
|
||||||
|
|
||||||
for (Score s : this)
|
for (Score s : this)
|
||||||
{
|
{
|
||||||
@@ -296,26 +307,26 @@ public abstract class ScoreList implements Iterable<Score>
|
|||||||
if (!groups.containsKey(groupTimestamp))
|
if (!groups.containsKey(groupTimestamp))
|
||||||
groups.put(groupTimestamp, new ArrayList<>());
|
groups.put(groupTimestamp, new ArrayList<>());
|
||||||
|
|
||||||
groups.get(groupTimestamp).add((long) s.getValue());
|
groups.get(groupTimestamp).add(s.getValue());
|
||||||
}
|
}
|
||||||
|
|
||||||
return groups;
|
return groups;
|
||||||
}
|
}
|
||||||
|
|
||||||
@NonNull
|
@NonNull
|
||||||
private List<Score> groupsToAvgScores(HashMap<Long, ArrayList<Long>> groups)
|
private List<Score> groupsToAvgScores(HashMap<Long, ArrayList<Double>> groups)
|
||||||
{
|
{
|
||||||
List<Score> scores = new LinkedList<>();
|
List<Score> scores = new LinkedList<>();
|
||||||
|
|
||||||
for (Long timestamp : groups.keySet())
|
for (Long timestamp : groups.keySet())
|
||||||
{
|
{
|
||||||
long meanValue = 0L;
|
double meanValue = 0.0;
|
||||||
ArrayList<Long> groupValues = groups.get(timestamp);
|
ArrayList<Double> groupValues = groups.get(timestamp);
|
||||||
|
|
||||||
for (Long v : groupValues) meanValue += v;
|
for (Double v : groupValues) meanValue += v;
|
||||||
meanValue /= groupValues.size();
|
meanValue /= groupValues.size();
|
||||||
|
|
||||||
scores.add(new Score(timestamp, (int) meanValue));
|
scores.add(new Score(timestamp, meanValue));
|
||||||
}
|
}
|
||||||
|
|
||||||
return scores;
|
return scores;
|
||||||
|
|||||||
@@ -23,13 +23,12 @@ import java.util.*;
|
|||||||
|
|
||||||
public class WeekdayList
|
public class WeekdayList
|
||||||
{
|
{
|
||||||
public static WeekdayList EVERY_DAY = new WeekdayList(127);
|
public static final WeekdayList EVERY_DAY = new WeekdayList(127);
|
||||||
|
|
||||||
private final boolean[] weekdays;
|
private final boolean[] weekdays;
|
||||||
|
|
||||||
public WeekdayList(int packedList)
|
public WeekdayList(int packedList)
|
||||||
{
|
{
|
||||||
if(packedList == 0) packedList = 127;
|
|
||||||
weekdays = new boolean[7];
|
weekdays = new boolean[7];
|
||||||
|
|
||||||
int current = 1;
|
int current = 1;
|
||||||
@@ -42,16 +41,18 @@ public class WeekdayList
|
|||||||
|
|
||||||
public WeekdayList(boolean weekdays[])
|
public WeekdayList(boolean weekdays[])
|
||||||
{
|
{
|
||||||
boolean isEmpty = true;
|
|
||||||
for(boolean b : weekdays) if(b) isEmpty = false;
|
|
||||||
if(isEmpty) throw new IllegalArgumentException("empty list");
|
|
||||||
|
|
||||||
this.weekdays = Arrays.copyOf(weekdays, 7);
|
this.weekdays = Arrays.copyOf(weekdays, 7);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public boolean isEmpty()
|
||||||
|
{
|
||||||
|
for (boolean d : weekdays) if (d) return false;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
public boolean[] toArray()
|
public boolean[] toArray()
|
||||||
{
|
{
|
||||||
return weekdays;
|
return Arrays.copyOf(weekdays, 7);
|
||||||
}
|
}
|
||||||
|
|
||||||
public int toInteger()
|
public int toInteger()
|
||||||
|
|||||||
@@ -162,9 +162,9 @@ public class MemoryHabitList extends HabitList
|
|||||||
};
|
};
|
||||||
|
|
||||||
Comparator<Habit> scoreComparator = (h1, h2) -> {
|
Comparator<Habit> scoreComparator = (h1, h2) -> {
|
||||||
int s1 = h1.getScores().getTodayValue();
|
double s1 = h1.getScores().getTodayValue();
|
||||||
int s2 = h2.getScores().getTodayValue();
|
double s2 = h2.getScores().getTodayValue();
|
||||||
return Integer.compare(s2, s1);
|
return Double.compare(s2, s1);
|
||||||
};
|
};
|
||||||
|
|
||||||
if (order == BY_POSITION) return null;
|
if (order == BY_POSITION) return null;
|
||||||
|
|||||||
@@ -86,7 +86,6 @@ public class MemoryRepetitionList extends RepetitionList
|
|||||||
oldestRep = rep;
|
oldestRep = rep;
|
||||||
oldestTime = rep.getTimestamp();
|
oldestTime = rep.getTimestamp();
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return oldestRep;
|
return oldestRep;
|
||||||
@@ -106,7 +105,6 @@ public class MemoryRepetitionList extends RepetitionList
|
|||||||
newestRep = rep;
|
newestRep = rep;
|
||||||
newestTime = rep.getTimestamp();
|
newestTime = rep.getTimestamp();
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return newestRep;
|
return newestRep;
|
||||||
@@ -119,7 +117,6 @@ public class MemoryRepetitionList extends RepetitionList
|
|||||||
observable.notifyListeners();
|
observable.notifyListeners();
|
||||||
}
|
}
|
||||||
|
|
||||||
@NonNull
|
|
||||||
@Override
|
@Override
|
||||||
public long getTotalCount()
|
public long getTotalCount()
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -280,9 +280,9 @@ public class SQLiteHabitList extends HabitList
|
|||||||
if(order == Order.BY_SCORE)
|
if(order == Order.BY_SCORE)
|
||||||
{
|
{
|
||||||
Collections.sort(habits, (lhs, rhs) -> {
|
Collections.sort(habits, (lhs, rhs) -> {
|
||||||
int s1 = lhs.getScores().getTodayValue();
|
double s1 = lhs.getScores().getTodayValue();
|
||||||
int s2 = rhs.getScores().getTodayValue();
|
double s2 = rhs.getScores().getTodayValue();
|
||||||
return Integer.compare(s2, s1);
|
return Double.compare(s2, s1);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -73,7 +73,7 @@ public class SQLiteRepetitionList extends RepetitionList
|
|||||||
public List<Repetition> getByInterval(long timeFrom, long timeTo)
|
public List<Repetition> getByInterval(long timeFrom, long timeTo)
|
||||||
{
|
{
|
||||||
check(habit.getId());
|
check(habit.getId());
|
||||||
String query = "select habit, timestamp " +
|
String query = "select habit, timestamp, value " +
|
||||||
"from Repetitions " +
|
"from Repetitions " +
|
||||||
"where habit = ? and timestamp >= ? and timestamp <= ? " +
|
"where habit = ? and timestamp >= ? and timestamp <= ? " +
|
||||||
"order by timestamp";
|
"order by timestamp";
|
||||||
@@ -93,7 +93,7 @@ public class SQLiteRepetitionList extends RepetitionList
|
|||||||
public Repetition getByTimestamp(long timestamp)
|
public Repetition getByTimestamp(long timestamp)
|
||||||
{
|
{
|
||||||
check(habit.getId());
|
check(habit.getId());
|
||||||
String query = "select habit, timestamp " +
|
String query = "select habit, timestamp, value " +
|
||||||
"from Repetitions " +
|
"from Repetitions " +
|
||||||
"where habit = ? and timestamp = ? " +
|
"where habit = ? and timestamp = ? " +
|
||||||
"limit 1";
|
"limit 1";
|
||||||
@@ -111,7 +111,7 @@ public class SQLiteRepetitionList extends RepetitionList
|
|||||||
public Repetition getOldest()
|
public Repetition getOldest()
|
||||||
{
|
{
|
||||||
check(habit.getId());
|
check(habit.getId());
|
||||||
String query = "select habit, timestamp " +
|
String query = "select habit, timestamp, value " +
|
||||||
"from Repetitions " +
|
"from Repetitions " +
|
||||||
"where habit = ? " +
|
"where habit = ? " +
|
||||||
"order by timestamp asc " +
|
"order by timestamp asc " +
|
||||||
@@ -129,7 +129,7 @@ public class SQLiteRepetitionList extends RepetitionList
|
|||||||
public Repetition getNewest()
|
public Repetition getNewest()
|
||||||
{
|
{
|
||||||
check(habit.getId());
|
check(habit.getId());
|
||||||
String query = "select habit, timestamp " +
|
String query = "select habit, timestamp, value " +
|
||||||
"from Repetitions " +
|
"from Repetitions " +
|
||||||
"where habit = ? " +
|
"where habit = ? " +
|
||||||
"order by timestamp desc " +
|
"order by timestamp desc " +
|
||||||
@@ -182,7 +182,6 @@ public class SQLiteRepetitionList extends RepetitionList
|
|||||||
return reps;
|
return reps;
|
||||||
}
|
}
|
||||||
|
|
||||||
@NonNull
|
|
||||||
@Override
|
@Override
|
||||||
public long getTotalCount()
|
public long getTotalCount()
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -72,7 +72,7 @@ public class SQLiteScoreList extends ScoreList
|
|||||||
{
|
{
|
||||||
statement.bindLong(1, habit.getId());
|
statement.bindLong(1, habit.getId());
|
||||||
statement.bindLong(2, s.getTimestamp());
|
statement.bindLong(2, s.getTimestamp());
|
||||||
statement.bindLong(3, s.getValue());
|
statement.bindDouble(3, s.getValue());
|
||||||
statement.execute();
|
statement.execute();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -22,8 +22,6 @@ package org.isoron.uhabits.models.sqlite.records;
|
|||||||
import android.annotation.*;
|
import android.annotation.*;
|
||||||
import android.database.*;
|
import android.database.*;
|
||||||
import android.support.annotation.*;
|
import android.support.annotation.*;
|
||||||
import android.support.annotation.NonNull;
|
|
||||||
import android.support.annotation.Nullable;
|
|
||||||
|
|
||||||
import com.activeandroid.*;
|
import com.activeandroid.*;
|
||||||
import com.activeandroid.annotation.*;
|
import com.activeandroid.annotation.*;
|
||||||
@@ -44,7 +42,8 @@ public class HabitRecord extends Model implements SQLiteRecord
|
|||||||
public static String SELECT =
|
public static String SELECT =
|
||||||
"select id, color, description, freq_den, freq_num, " +
|
"select id, color, description, freq_den, freq_num, " +
|
||||||
"name, position, reminder_hour, reminder_min, " +
|
"name, position, reminder_hour, reminder_min, " +
|
||||||
"highlight, archived, reminder_days from habits ";
|
"highlight, archived, reminder_days, type, target_type, " +
|
||||||
|
"target_value, unit from habits ";
|
||||||
|
|
||||||
@Column(name = "name")
|
@Column(name = "name")
|
||||||
public String name;
|
public String name;
|
||||||
@@ -82,6 +81,18 @@ public class HabitRecord extends Model implements SQLiteRecord
|
|||||||
@Column(name = "archived")
|
@Column(name = "archived")
|
||||||
public Integer archived;
|
public Integer archived;
|
||||||
|
|
||||||
|
@Column(name = "type")
|
||||||
|
public Integer type;
|
||||||
|
|
||||||
|
@Column(name = "target_value")
|
||||||
|
public Double targetValue;
|
||||||
|
|
||||||
|
@Column(name = "target_type")
|
||||||
|
public Integer targetType;
|
||||||
|
|
||||||
|
@Column(name = "unit")
|
||||||
|
public String unit;
|
||||||
|
|
||||||
public HabitRecord()
|
public HabitRecord()
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
@@ -146,6 +157,11 @@ public class HabitRecord extends Model implements SQLiteRecord
|
|||||||
this.highlight = 0;
|
this.highlight = 0;
|
||||||
this.color = model.getColor();
|
this.color = model.getColor();
|
||||||
this.archived = model.isArchived() ? 1 : 0;
|
this.archived = model.isArchived() ? 1 : 0;
|
||||||
|
this.type = model.getType();
|
||||||
|
this.targetType = model.getTargetType();
|
||||||
|
this.targetValue = model.getTargetValue();
|
||||||
|
this.unit = model.getUnit();
|
||||||
|
|
||||||
Frequency freq = model.getFrequency();
|
Frequency freq = model.getFrequency();
|
||||||
this.freqNum = freq.getNumerator();
|
this.freqNum = freq.getNumerator();
|
||||||
this.freqDen = freq.getDenominator();
|
this.freqDen = freq.getDenominator();
|
||||||
@@ -177,6 +193,10 @@ public class HabitRecord extends Model implements SQLiteRecord
|
|||||||
highlight = c.getInt(9);
|
highlight = c.getInt(9);
|
||||||
archived = c.getInt(10);
|
archived = c.getInt(10);
|
||||||
reminderDays = c.getInt(11);
|
reminderDays = c.getInt(11);
|
||||||
|
type = c.getInt(12);
|
||||||
|
targetType = c.getInt(13);
|
||||||
|
targetValue = c.getDouble(14);
|
||||||
|
unit = c.getString(15);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void copyTo(Habit habit)
|
public void copyTo(Habit habit)
|
||||||
@@ -187,6 +207,10 @@ public class HabitRecord extends Model implements SQLiteRecord
|
|||||||
habit.setColor(this.color);
|
habit.setColor(this.color);
|
||||||
habit.setArchived(this.archived != 0);
|
habit.setArchived(this.archived != 0);
|
||||||
habit.setId(this.getId());
|
habit.setId(this.getId());
|
||||||
|
habit.setType(this.type);
|
||||||
|
habit.setTargetType(this.targetType);
|
||||||
|
habit.setTargetValue(this.targetValue);
|
||||||
|
habit.setUnit(this.unit);
|
||||||
|
|
||||||
if (reminderHour != null && reminderMin != null)
|
if (reminderHour != null && reminderMin != null)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -38,6 +38,9 @@ public class RepetitionRecord extends Model implements SQLiteRecord
|
|||||||
@Column(name = "timestamp")
|
@Column(name = "timestamp")
|
||||||
public Long timestamp;
|
public Long timestamp;
|
||||||
|
|
||||||
|
@Column(name = "value")
|
||||||
|
public int value;
|
||||||
|
|
||||||
public static RepetitionRecord get(Long id)
|
public static RepetitionRecord get(Long id)
|
||||||
{
|
{
|
||||||
return RepetitionRecord.load(RepetitionRecord.class, id);
|
return RepetitionRecord.load(RepetitionRecord.class, id);
|
||||||
@@ -46,16 +49,18 @@ public class RepetitionRecord extends Model implements SQLiteRecord
|
|||||||
public void copyFrom(Repetition repetition)
|
public void copyFrom(Repetition repetition)
|
||||||
{
|
{
|
||||||
timestamp = repetition.getTimestamp();
|
timestamp = repetition.getTimestamp();
|
||||||
|
value = repetition.getValue();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void copyFrom(Cursor c)
|
public void copyFrom(Cursor c)
|
||||||
{
|
{
|
||||||
timestamp = c.getLong(1);
|
timestamp = c.getLong(1);
|
||||||
|
value = c.getInt(2);
|
||||||
}
|
}
|
||||||
|
|
||||||
public Repetition toRepetition()
|
public Repetition toRepetition()
|
||||||
{
|
{
|
||||||
return new Repetition(timestamp);
|
return new Repetition(timestamp, value);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -46,13 +46,13 @@ public class ScoreRecord extends Model implements SQLiteRecord
|
|||||||
* Value of the score.
|
* Value of the score.
|
||||||
*/
|
*/
|
||||||
@Column(name = "score")
|
@Column(name = "score")
|
||||||
public Integer score;
|
public Double score;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void copyFrom(Cursor c)
|
public void copyFrom(Cursor c)
|
||||||
{
|
{
|
||||||
timestamp = c.getLong(1);
|
timestamp = c.getLong(1);
|
||||||
score = c.getInt(2);
|
score = c.getDouble(2);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -74,4 +74,14 @@ public class AttributeSetUtils
|
|||||||
if (number != null) return Float.parseFloat(number);
|
if (number != null) return Float.parseFloat(number);
|
||||||
else return defaultValue;
|
else return defaultValue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static int getIntAttribute(@NonNull Context context,
|
||||||
|
@NonNull AttributeSet attrs,
|
||||||
|
@NonNull String name,
|
||||||
|
int defaultValue)
|
||||||
|
{
|
||||||
|
String number = getAttribute(context, attrs, name, null);
|
||||||
|
if (number != null) return Integer.parseInt(number);
|
||||||
|
else return defaultValue;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -22,7 +22,10 @@ package org.isoron.uhabits.utils;
|
|||||||
import android.content.*;
|
import android.content.*;
|
||||||
import android.content.res.*;
|
import android.content.res.*;
|
||||||
import android.graphics.*;
|
import android.graphics.*;
|
||||||
|
import android.support.annotation.*;
|
||||||
import android.util.*;
|
import android.util.*;
|
||||||
|
import android.view.*;
|
||||||
|
import android.widget.*;
|
||||||
|
|
||||||
import java.util.*;
|
import java.util.*;
|
||||||
|
|
||||||
@@ -39,8 +42,9 @@ public abstract class InterfaceUtils
|
|||||||
|
|
||||||
public static Typeface getFontAwesome(Context context)
|
public static Typeface getFontAwesome(Context context)
|
||||||
{
|
{
|
||||||
if(fontAwesome == null)
|
if(fontAwesome == null) fontAwesome =
|
||||||
fontAwesome = Typeface.createFromAsset(context.getAssets(), "fontawesome-webfont.ttf");
|
Typeface.createFromAsset(context.getAssets(),
|
||||||
|
"fontawesome-webfont.ttf");
|
||||||
|
|
||||||
return fontAwesome;
|
return fontAwesome;
|
||||||
}
|
}
|
||||||
@@ -69,4 +73,18 @@ public abstract class InterfaceUtils
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static void setupEditorAction(@NonNull ViewGroup parent,
|
||||||
|
@NonNull TextView.OnEditorActionListener listener)
|
||||||
|
{
|
||||||
|
for (int i = 0; i < parent.getChildCount(); i++)
|
||||||
|
{
|
||||||
|
View child = parent.getChildAt(i);
|
||||||
|
|
||||||
|
if (child instanceof ViewGroup)
|
||||||
|
setupEditorAction((ViewGroup) child, listener);
|
||||||
|
|
||||||
|
if (child instanceof TextView)
|
||||||
|
((TextView) child).setOnEditorActionListener(listener);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -52,8 +52,8 @@ public class CheckmarkWidget extends BaseWidget
|
|||||||
{
|
{
|
||||||
CheckmarkWidgetView view = (CheckmarkWidgetView) v;
|
CheckmarkWidgetView view = (CheckmarkWidgetView) v;
|
||||||
int color = ColorUtils.getColor(getContext(), habit.getColor());
|
int color = ColorUtils.getColor(getContext(), habit.getColor());
|
||||||
int score = habit.getScores().getTodayValue();
|
double score = habit.getScores().getTodayValue();
|
||||||
float percentage = (float) score / Score.MAX_VALUE;
|
float percentage = (float) score;
|
||||||
int checkmark = habit.getCheckmarks().getTodayValue();
|
int checkmark = habit.getCheckmarks().getTodayValue();
|
||||||
|
|
||||||
view.setPercentage(percentage);
|
view.setPercentage(percentage);
|
||||||
|
|||||||
@@ -22,113 +22,33 @@
|
|||||||
style="@style/dialogForm"
|
style="@style/dialogForm"
|
||||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
xmlns:tools="http://schemas.android.com/tools"
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
tools:context=".activities.habits.edit.BaseDialog"
|
tools:context=".activities.habits.edit.EditHabitDialog"
|
||||||
tools:ignore="MergeRootFrame">
|
tools:ignore="MergeRootFrame">
|
||||||
|
|
||||||
<LinearLayout
|
<LinearLayout
|
||||||
android:id="@+id/formPanel"
|
android:id="@+id/formPanel"
|
||||||
style="@style/dialogFormPanel">
|
style="@style/dialogFormPanel">
|
||||||
|
|
||||||
<LinearLayout
|
<org.isoron.uhabits.activities.habits.edit.views.NameDescriptionPanel
|
||||||
android:id="@+id/namePanel"
|
android:id="@+id/namePanel"
|
||||||
style="@style/dialogFormRow">
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"/>
|
||||||
|
|
||||||
<EditText
|
<org.isoron.uhabits.activities.habits.edit.views.FrequencyPanel
|
||||||
android:id="@+id/tvName"
|
android:id="@+id/frequencyPanel"
|
||||||
style="@style/dialogFormInput"
|
android:layout_width="match_parent"
|
||||||
android:hint="@string/name">
|
android:layout_height="wrap_content"/>
|
||||||
|
|
||||||
<requestFocus/>
|
<org.isoron.uhabits.activities.habits.edit.views.TargetPanel
|
||||||
</EditText>
|
android:id="@+id/targetPanel"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"/>
|
||||||
|
|
||||||
<ImageButton
|
<org.isoron.uhabits.activities.habits.edit.views.ReminderPanel
|
||||||
android:id="@+id/buttonPickColor"
|
|
||||||
style="@style/dialogFormInputColor"
|
|
||||||
android:contentDescription="@string/color_picker_default_title"
|
|
||||||
android:src="?dialogIconChangeColor"/>
|
|
||||||
</LinearLayout>
|
|
||||||
|
|
||||||
<EditText
|
|
||||||
android:id="@+id/tvDescription"
|
|
||||||
style="@style/dialogFormInputMultiline"
|
|
||||||
android:hint="@string/description_hint"/>
|
|
||||||
|
|
||||||
<LinearLayout
|
|
||||||
style="@style/dialogFormRow">
|
|
||||||
|
|
||||||
<TextView
|
|
||||||
android:id="@+id/textView1"
|
|
||||||
style="@style/dialogFormLabel"
|
|
||||||
android:text="@string/repeat"/>
|
|
||||||
|
|
||||||
<android.support.v7.widget.AppCompatSpinner
|
|
||||||
android:id="@+id/sFrequency"
|
|
||||||
android:theme="@style/dialogFormText"
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="25dp"
|
|
||||||
android:minWidth="400dp"
|
|
||||||
android:entries="@array/frequencyQuickSelect"
|
|
||||||
android:visibility="gone"/>
|
|
||||||
|
|
||||||
<org.apmem.tools.layouts.FlowLayout
|
|
||||||
android:id="@+id/llCustomFrequency"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:visibility="visible"
|
|
||||||
android:gravity="fill">
|
|
||||||
|
|
||||||
<EditText
|
|
||||||
android:id="@+id/tvFreqNum"
|
|
||||||
style="@style/dialogFormInputLargeNumber"/>
|
|
||||||
|
|
||||||
<TextView
|
|
||||||
android:id="@+id/textView3"
|
|
||||||
style="@style/dialogFormText"
|
|
||||||
android:text="@string/times_every"
|
|
||||||
android:gravity="center"/>
|
|
||||||
|
|
||||||
<EditText
|
|
||||||
android:id="@+id/tvFreqDen"
|
|
||||||
style="@style/dialogFormInputLargeNumber"/>
|
|
||||||
|
|
||||||
<TextView
|
|
||||||
android:id="@+id/textView5"
|
|
||||||
style="@style/dialogFormText"
|
|
||||||
android:text="@string/days"
|
|
||||||
android:gravity="center_vertical"
|
|
||||||
android:paddingLeft="12dp"/>
|
|
||||||
|
|
||||||
</org.apmem.tools.layouts.FlowLayout>
|
|
||||||
</LinearLayout>
|
|
||||||
|
|
||||||
<LinearLayout
|
|
||||||
android:id="@+id/reminderPanel"
|
android:id="@+id/reminderPanel"
|
||||||
style="@style/dialogFormRow">
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"/>
|
||||||
|
|
||||||
<TextView
|
|
||||||
android:id="@+id/TextView2"
|
|
||||||
style="@style/dialogFormLabel"
|
|
||||||
android:text="@string/reminder"/>
|
|
||||||
|
|
||||||
<TextView
|
|
||||||
android:id="@+id/tvReminderTime"
|
|
||||||
style="@style/dialogFormSpinner"/>
|
|
||||||
</LinearLayout>
|
|
||||||
|
|
||||||
<LinearLayout
|
|
||||||
android:id="@+id/llReminderDays"
|
|
||||||
style="@style/dialogFormRow">
|
|
||||||
|
|
||||||
<TextView
|
|
||||||
android:id="@+id/TextView3"
|
|
||||||
style="@style/dialogFormLabel"
|
|
||||||
android:text=""/>
|
|
||||||
|
|
||||||
<TextView
|
|
||||||
android:id="@+id/tvReminderDays"
|
|
||||||
style="@style/dialogFormSpinner"/>
|
|
||||||
|
|
||||||
</LinearLayout>
|
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
|
|
||||||
<LinearLayout
|
<LinearLayout
|
||||||
|
|||||||
69
app/src/main/res/layout/edit_habit_frequency.xml
Normal file
69
app/src/main/res/layout/edit_habit_frequency.xml
Normal file
@@ -0,0 +1,69 @@
|
|||||||
|
<?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/>.
|
||||||
|
-->
|
||||||
|
|
||||||
|
<LinearLayout style="@style/dialogFormRow"
|
||||||
|
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="12dp"
|
||||||
|
android:orientation="horizontal">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
style="@style/dialogFormLabel"
|
||||||
|
android:text="@string/repeat"/>
|
||||||
|
|
||||||
|
<android.support.v7.widget.AppCompatSpinner
|
||||||
|
android:id="@+id/spinner"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="25dp"
|
||||||
|
android:entries="@array/frequencyQuickSelect"
|
||||||
|
android:minWidth="400dp"
|
||||||
|
android:theme="@style/dialogFormText"
|
||||||
|
android:visibility="gone"/>
|
||||||
|
|
||||||
|
<org.apmem.tools.layouts.FlowLayout
|
||||||
|
android:id="@+id/customFreqPanel"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:gravity="fill"
|
||||||
|
android:visibility="visible">
|
||||||
|
|
||||||
|
<EditText
|
||||||
|
android:id="@+id/numerator"
|
||||||
|
style="@style/dialogFormInputLargeNumber"/>
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
style="@style/dialogFormText"
|
||||||
|
android:gravity="center"
|
||||||
|
android:text="@string/times_every"/>
|
||||||
|
|
||||||
|
<EditText
|
||||||
|
android:id="@+id/denominator"
|
||||||
|
style="@style/dialogFormInputLargeNumber"/>
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
style="@style/dialogFormText"
|
||||||
|
android:gravity="center_vertical"
|
||||||
|
android:paddingLeft="12dp"
|
||||||
|
android:text="@string/days"/>
|
||||||
|
|
||||||
|
</org.apmem.tools.layouts.FlowLayout>
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
70
app/src/main/res/layout/edit_habit_name.xml
Normal file
70
app/src/main/res/layout/edit_habit_name.xml
Normal file
@@ -0,0 +1,70 @@
|
|||||||
|
<?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/>.
|
||||||
|
-->
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:app="http://isoron.org/android"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:orientation="vertical">
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
style="@style/dialogFormRow">
|
||||||
|
|
||||||
|
<android.support.design.widget.TextInputLayout
|
||||||
|
android:id="@+id/tilName"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_weight="6">
|
||||||
|
|
||||||
|
<EditText
|
||||||
|
android:id="@+id/tvName"
|
||||||
|
style="@style/dialogFormInput"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:hint="@string/name">
|
||||||
|
|
||||||
|
<requestFocus/>
|
||||||
|
</EditText>
|
||||||
|
</android.support.design.widget.TextInputLayout>
|
||||||
|
|
||||||
|
<ImageButton
|
||||||
|
android:id="@+id/buttonPickColor"
|
||||||
|
style="@style/dialogFormInputColor"
|
||||||
|
android:layout_weight="1"
|
||||||
|
android:contentDescription="@string/color_picker_default_title"
|
||||||
|
android:src="?dialogIconChangeColor"/>
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
<android.support.design.widget.TextInputLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content">
|
||||||
|
|
||||||
|
<org.isoron.uhabits.activities.habits.edit.views.ExampleEditText
|
||||||
|
android:id="@+id/tvDescription"
|
||||||
|
style="@style/dialogFormInputMultiline"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:hint="@string/question"
|
||||||
|
app:example="@string/example_question_numerical"/>
|
||||||
|
</android.support.design.widget.TextInputLayout>
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
52
app/src/main/res/layout/edit_habit_reminder.xml
Normal file
52
app/src/main/res/layout/edit_habit_reminder.xml
Normal file
@@ -0,0 +1,52 @@
|
|||||||
|
<?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/>.
|
||||||
|
-->
|
||||||
|
|
||||||
|
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:orientation="vertical">
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
style="@style/dialogFormRow"
|
||||||
|
android:layout_marginTop="12dp">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
style="@style/dialogFormLabel"
|
||||||
|
android:text="@string/reminder"/>
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/tvReminderTime"
|
||||||
|
style="@style/dialogFormSpinner"/>
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:id="@+id/llReminderDays"
|
||||||
|
style="@style/dialogFormRow"
|
||||||
|
android:layout_marginTop="12dp">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
style="@style/dialogFormLabel"
|
||||||
|
android:text=""/>
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/tvReminderDays"
|
||||||
|
style="@style/dialogFormSpinner"/>
|
||||||
|
</LinearLayout>
|
||||||
|
</LinearLayout>
|
||||||
62
app/src/main/res/layout/edit_habit_target.xml
Normal file
62
app/src/main/res/layout/edit_habit_target.xml
Normal file
@@ -0,0 +1,62 @@
|
|||||||
|
<?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/>.
|
||||||
|
-->
|
||||||
|
|
||||||
|
<LinearLayout style="@style/dialogFormRow"
|
||||||
|
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:app="http://isoron.org/android"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:orientation="horizontal">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
style="@style/dialogFormLabel"
|
||||||
|
android:text="@string/target"/>
|
||||||
|
|
||||||
|
<android.support.design.widget.TextInputLayout
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_weight="2">
|
||||||
|
|
||||||
|
<EditText
|
||||||
|
android:id="@+id/tvTargetCount"
|
||||||
|
style="@style/dialogFormInput"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:hint="@string/count"
|
||||||
|
android:inputType="numberDecimal"
|
||||||
|
android:text="@string/default_count"
|
||||||
|
/>
|
||||||
|
</android.support.design.widget.TextInputLayout>
|
||||||
|
|
||||||
|
<android.support.design.widget.TextInputLayout
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_weight="2">
|
||||||
|
|
||||||
|
<org.isoron.uhabits.activities.habits.edit.views.ExampleEditText
|
||||||
|
android:id="@+id/tvUnit"
|
||||||
|
style="@style/dialogFormInput"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:hint="@string/unit"
|
||||||
|
android:inputType="text"
|
||||||
|
app:example="@string/example_units"/>
|
||||||
|
</android.support.design.widget.TextInputLayout>
|
||||||
|
</LinearLayout>
|
||||||
67
app/src/main/res/layout/list_habits_button_preview.xml
Normal file
67
app/src/main/res/layout/list_habits_button_preview.xml
Normal file
@@ -0,0 +1,67 @@
|
|||||||
|
<?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/>.
|
||||||
|
-->
|
||||||
|
|
||||||
|
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:app="http://isoron.org/android"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:background="?windowBackgroundColor">
|
||||||
|
|
||||||
|
<org.isoron.uhabits.activities.habits.list.views.CheckmarkButtonView
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
app:color="0"
|
||||||
|
app:value="2"/>
|
||||||
|
|
||||||
|
<org.isoron.uhabits.activities.habits.list.views.CheckmarkButtonView
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
app:color="0"
|
||||||
|
app:value="0"/>
|
||||||
|
|
||||||
|
|
||||||
|
<org.isoron.uhabits.activities.habits.list.views.CheckmarkButtonView
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
app:color="0"
|
||||||
|
app:value="1"/>
|
||||||
|
|
||||||
|
<org.isoron.uhabits.activities.habits.list.views.NumberButtonView
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
app:threshold="10"
|
||||||
|
app:color="1"
|
||||||
|
app:value="5"/>
|
||||||
|
|
||||||
|
<org.isoron.uhabits.activities.habits.list.views.NumberButtonView
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
app:threshold="10"
|
||||||
|
app:color="1"
|
||||||
|
app:value="50"/>
|
||||||
|
|
||||||
|
<org.isoron.uhabits.activities.habits.list.views.NumberButtonView
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
app:threshold="10"
|
||||||
|
app:color="1"
|
||||||
|
app:value="25304"/>
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
@@ -46,6 +46,11 @@
|
|||||||
android:id="@+id/checkmarkPanel"
|
android:id="@+id/checkmarkPanel"
|
||||||
style="@style/ListHabits.CheckmarkPanel"/>
|
style="@style/ListHabits.CheckmarkPanel"/>
|
||||||
|
|
||||||
|
<org.isoron.uhabits.activities.habits.list.views.NumberPanelView
|
||||||
|
android:id="@+id/numberPanel"
|
||||||
|
android:visibility="gone"
|
||||||
|
style="@style/ListHabits.CheckmarkPanel"/>
|
||||||
|
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
|
|
||||||
</merge>
|
</merge>
|
||||||
107
app/src/main/res/layout/list_habits_panel_preview.xml
Normal file
107
app/src/main/res/layout/list_habits_panel_preview.xml
Normal file
@@ -0,0 +1,107 @@
|
|||||||
|
<?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/>.
|
||||||
|
-->
|
||||||
|
|
||||||
|
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:app="http://isoron.org/android"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:background="?windowBackgroundColor"
|
||||||
|
android:orientation="vertical">
|
||||||
|
|
||||||
|
<org.isoron.uhabits.activities.habits.list.views.NumberPanelView
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
app:button_count="8"
|
||||||
|
app:color="1"
|
||||||
|
app:threshold="10000"
|
||||||
|
app:unit="steps"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<org.isoron.uhabits.activities.habits.list.views.NumberPanelView
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
app:button_count="8"
|
||||||
|
app:color="2"
|
||||||
|
app:threshold="2000"
|
||||||
|
app:unit="cals"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<org.isoron.uhabits.activities.habits.list.views.CheckmarkPanelView
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
app:button_count="8"
|
||||||
|
app:color="2"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<org.isoron.uhabits.activities.habits.list.views.CheckmarkPanelView
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
app:button_count="8"
|
||||||
|
app:color="2"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<org.isoron.uhabits.activities.habits.list.views.NumberPanelView
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
app:button_count="8"
|
||||||
|
app:color="6"
|
||||||
|
app:threshold="2"
|
||||||
|
app:unit="min"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<org.isoron.uhabits.activities.habits.list.views.CheckmarkPanelView
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
app:button_count="8"
|
||||||
|
app:color="6"
|
||||||
|
/>
|
||||||
|
<org.isoron.uhabits.activities.habits.list.views.CheckmarkPanelView
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
app:button_count="8"
|
||||||
|
app:color="4"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<org.isoron.uhabits.activities.habits.list.views.NumberPanelView
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
app:button_count="8"
|
||||||
|
app:color="10"
|
||||||
|
app:threshold="750"
|
||||||
|
app:unit="words"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<org.isoron.uhabits.activities.habits.list.views.CheckmarkPanelView
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
app:button_count="8"
|
||||||
|
app:color="10"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<org.isoron.uhabits.activities.habits.list.views.NumberPanelView
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
app:button_count="8"
|
||||||
|
app:color="8"
|
||||||
|
app:threshold="75"
|
||||||
|
app:unit="pages"
|
||||||
|
/>
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
52
app/src/main/res/layout/number_picker_dialog.xml
Normal file
52
app/src/main/res/layout/number_picker_dialog.xml
Normal file
@@ -0,0 +1,52 @@
|
|||||||
|
<?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/>.
|
||||||
|
-->
|
||||||
|
|
||||||
|
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:orientation="horizontal"
|
||||||
|
android:gravity="center"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content">
|
||||||
|
|
||||||
|
<NumberPicker
|
||||||
|
android:id="@+id/picker"
|
||||||
|
android:layout_gravity="center"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"/>
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/tvSeparator"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="."/>
|
||||||
|
|
||||||
|
<NumberPicker
|
||||||
|
android:id="@+id/picker2"
|
||||||
|
android:layout_gravity="center"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:descendantFocusability="blocksDescendants"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/tvUnit"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"/>
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
37
app/src/main/res/layout/show_habit_bar.xml
Normal file
37
app/src/main/res/layout/show_habit_bar.xml
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
<?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"
|
||||||
|
android:clipToPadding="false"
|
||||||
|
android:orientation="vertical"
|
||||||
|
android:paddingBottom="0dp">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/title"
|
||||||
|
style="@style/CardHeader"
|
||||||
|
android:text="@string/history"/>
|
||||||
|
|
||||||
|
<org.isoron.uhabits.activities.common.views.BarChart
|
||||||
|
android:id="@+id/barChart"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="220dp"/>
|
||||||
|
|
||||||
|
</merge>
|
||||||
@@ -27,7 +27,7 @@
|
|||||||
<TextView
|
<TextView
|
||||||
android:id="@+id/title"
|
android:id="@+id/title"
|
||||||
style="@style/CardHeader"
|
style="@style/CardHeader"
|
||||||
android:text="@string/history"/>
|
android:text="@string/calendar"/>
|
||||||
|
|
||||||
<org.isoron.uhabits.activities.common.views.HistoryChart
|
<org.isoron.uhabits.activities.common.views.HistoryChart
|
||||||
android:id="@+id/historyChart"
|
android:id="@+id/historyChart"
|
||||||
|
|||||||
@@ -52,6 +52,11 @@
|
|||||||
style="@style/Card"
|
style="@style/Card"
|
||||||
android:gravity="center"/>
|
android:gravity="center"/>
|
||||||
|
|
||||||
|
<org.isoron.uhabits.activities.habits.show.views.BarCard
|
||||||
|
android:id="@+id/barCard"
|
||||||
|
style="@style/Card"
|
||||||
|
android:gravity="center"/>
|
||||||
|
|
||||||
<org.isoron.uhabits.activities.habits.show.views.HistoryCard
|
<org.isoron.uhabits.activities.habits.show.views.HistoryCard
|
||||||
android:id="@+id/historyCard"
|
android:id="@+id/historyCard"
|
||||||
style="@style/Card"
|
style="@style/Card"
|
||||||
|
|||||||
31
app/src/main/res/layout/show_habit_preview.xml
Normal file
31
app/src/main/res/layout/show_habit_preview.xml
Normal file
@@ -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/>.
|
||||||
|
-->
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
>
|
||||||
|
|
||||||
|
<org.isoron.uhabits.activities.habits.show.views.BarCard
|
||||||
|
android:id="@+id/barCard"
|
||||||
|
style="@style/Card"
|
||||||
|
android:gravity="center"/>
|
||||||
|
</LinearLayout>
|
||||||
@@ -40,7 +40,7 @@
|
|||||||
android:layout_alignParentLeft="true"
|
android:layout_alignParentLeft="true"
|
||||||
android:layout_alignParentStart="true"
|
android:layout_alignParentStart="true"
|
||||||
android:layout_alignParentTop="true"
|
android:layout_alignParentTop="true"
|
||||||
android:text="@string/habit_strength"/>
|
android:text="@string/score"/>
|
||||||
|
|
||||||
<org.isoron.uhabits.activities.common.views.ScoreChart
|
<org.isoron.uhabits.activities.common.views.ScoreChart
|
||||||
android:id="@+id/scoreView"
|
android:id="@+id/scoreView"
|
||||||
|
|||||||
@@ -55,6 +55,11 @@
|
|||||||
<item>@string/custom_frequency</item>
|
<item>@string/custom_frequency</item>
|
||||||
</string-array>
|
</string-array>
|
||||||
|
|
||||||
|
<string-array name="habitTypes" translatable="false">
|
||||||
|
<item>Yes or No</item>
|
||||||
|
<item>Number</item>
|
||||||
|
</string-array>
|
||||||
|
|
||||||
<string-array name="actions" translatable="false">
|
<string-array name="actions" translatable="false">
|
||||||
<item>@string/check</item>
|
<item>@string/check</item>
|
||||||
<item>@string/uncheck</item>
|
<item>@string/uncheck</item>
|
||||||
@@ -77,5 +82,17 @@
|
|||||||
<item>365</item>
|
<item>365</item>
|
||||||
</string-array>
|
</string-array>
|
||||||
|
|
||||||
|
<string-array name="targetValues" translatable="false">
|
||||||
|
<item>At least</item>
|
||||||
|
<item>At most</item>
|
||||||
|
</string-array>
|
||||||
|
|
||||||
|
<string-array name="targetIntervals" translatable="false">
|
||||||
|
<item>daily</item>
|
||||||
|
<item>weekly</item>
|
||||||
|
<item>montly</item>
|
||||||
|
</string-array>
|
||||||
|
|
||||||
<string name="snooze_interval_default" translatable="false">15</string>
|
<string name="snooze_interval_default" translatable="false">15</string>
|
||||||
|
<string name="default_count" translatable="false">100</string>
|
||||||
</resources>
|
</resources>
|
||||||
@@ -19,7 +19,7 @@
|
|||||||
|
|
||||||
<resources>
|
<resources>
|
||||||
<dimen name="baseSize">20dp</dimen>
|
<dimen name="baseSize">20dp</dimen>
|
||||||
<dimen name="checkmarkWidth">42dp</dimen>
|
<dimen name="checkmarkWidth">48dp</dimen>
|
||||||
<dimen name="checkmarkHeight">48dp</dimen>
|
<dimen name="checkmarkHeight">48dp</dimen>
|
||||||
<dimen name="history_editor_max_height">450dp</dimen>
|
<dimen name="history_editor_max_height">450dp</dimen>
|
||||||
<dimen name="history_editor_padding">8dp</dimen>
|
<dimen name="history_editor_padding">8dp</dimen>
|
||||||
|
|||||||
@@ -19,7 +19,6 @@
|
|||||||
-->
|
-->
|
||||||
|
|
||||||
<resources>
|
<resources>
|
||||||
|
|
||||||
<string name="app_name">Loop Habit Tracker</string>
|
<string name="app_name">Loop Habit Tracker</string>
|
||||||
<string name="main_activity_title">Habits</string>
|
<string name="main_activity_title">Habits</string>
|
||||||
<string name="action_settings">Settings</string>
|
<string name="action_settings">Settings</string>
|
||||||
@@ -204,4 +203,16 @@
|
|||||||
<string name="by_score">By score</string>
|
<string name="by_score">By score</string>
|
||||||
<string name="download">Download</string>
|
<string name="download">Download</string>
|
||||||
<string name="export">Export</string>
|
<string name="export">Export</string>
|
||||||
|
<string name="long_press_to_edit">Press-and-hold to change the
|
||||||
|
value</string>
|
||||||
|
<string name="change_value">Change value</string>
|
||||||
|
<string name="calendar">Calendar</string>
|
||||||
|
<string name="unit">Unit</string>
|
||||||
|
<string name="count">Count</string>
|
||||||
|
<string name="validation_show_not_be_blank">This field should not be blank</string>
|
||||||
|
<string name="example_question_numerical">e.g. How many steps did you walk today?</string>
|
||||||
|
<string name="example_units">e.g. steps</string>
|
||||||
|
<string name="example_question_boolean">e.g. Did you exercise today?</string>
|
||||||
|
<string name="question">Question</string>
|
||||||
|
<string name="target">Target</string>
|
||||||
</resources>
|
</resources>
|
||||||
@@ -256,4 +256,8 @@
|
|||||||
<style name="TimePickerDialog" parent="@style/Theme.AppCompat.Light.Dialog">
|
<style name="TimePickerDialog" parent="@style/Theme.AppCompat.Light.Dialog">
|
||||||
<item name="windowNoTitle">true</item>
|
<item name="windowNoTitle">true</item>
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
|
<style name="DialogWithTitle" parent="@style/Theme.AppCompat.Light.Dialog">
|
||||||
|
<item name="windowNoTitle">false</item>
|
||||||
|
</style>
|
||||||
</resources>
|
</resources>
|
||||||
|
|||||||
@@ -89,7 +89,6 @@
|
|||||||
<style name="dialogFormRow">
|
<style name="dialogFormRow">
|
||||||
<item name="android:layout_width">match_parent</item>
|
<item name="android:layout_width">match_parent</item>
|
||||||
<item name="android:layout_height">wrap_content</item>
|
<item name="android:layout_height">wrap_content</item>
|
||||||
<item name="android:layout_marginTop">12dp</item>
|
|
||||||
<item name="android:orientation">horizontal</item>
|
<item name="android:orientation">horizontal</item>
|
||||||
<item name="android:minWidth">300dp</item>
|
<item name="android:minWidth">300dp</item>
|
||||||
<item name="android:gravity">start|center_vertical</item>
|
<item name="android:gravity">start|center_vertical</item>
|
||||||
@@ -104,10 +103,11 @@
|
|||||||
<style name="dialogFormPanel">
|
<style name="dialogFormPanel">
|
||||||
<item name="android:layout_width">match_parent</item>
|
<item name="android:layout_width">match_parent</item>
|
||||||
<item name="android:layout_height">wrap_content</item>
|
<item name="android:layout_height">wrap_content</item>
|
||||||
<item name="android:layout_marginBottom">8dp</item>
|
|
||||||
<item name="android:orientation">vertical</item>
|
<item name="android:orientation">vertical</item>
|
||||||
<item name="android:paddingLeft">24dp</item>
|
<item name="android:paddingLeft">24dp</item>
|
||||||
<item name="android:paddingRight">24dp</item>
|
<item name="android:paddingRight">24dp</item>
|
||||||
|
<item name="android:paddingTop">12dp</item>
|
||||||
|
<item name="android:paddingBottom">12dp</item>
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
</resources>
|
</resources>
|
||||||
@@ -61,8 +61,6 @@ public class ListHabitsScreenTest extends BaseUnitTest
|
|||||||
|
|
||||||
private ConfirmDeleteDialogFactory confirmDeleteDialogFactory;
|
private ConfirmDeleteDialogFactory confirmDeleteDialogFactory;
|
||||||
|
|
||||||
private CreateHabitDialogFactory createHabitDialogFactory;
|
|
||||||
|
|
||||||
private FilePickerDialogFactory filePickerDialogFactory;
|
private FilePickerDialogFactory filePickerDialogFactory;
|
||||||
|
|
||||||
private IntentFactory intentFactory;
|
private IntentFactory intentFactory;
|
||||||
@@ -73,7 +71,7 @@ public class ListHabitsScreenTest extends BaseUnitTest
|
|||||||
|
|
||||||
private ColorPickerDialogFactory colorPickerDialogFactory;
|
private ColorPickerDialogFactory colorPickerDialogFactory;
|
||||||
|
|
||||||
private EditHabitDialogFactory editHabitDialogFactory;
|
private EditHabitDialogFactory dialogFactory;
|
||||||
|
|
||||||
private ThemeSwitcher themeSwitcher;
|
private ThemeSwitcher themeSwitcher;
|
||||||
|
|
||||||
@@ -92,15 +90,13 @@ public class ListHabitsScreenTest extends BaseUnitTest
|
|||||||
intentFactory = mock(IntentFactory.class);
|
intentFactory = mock(IntentFactory.class);
|
||||||
themeSwitcher = mock(ThemeSwitcher.class);
|
themeSwitcher = mock(ThemeSwitcher.class);
|
||||||
confirmDeleteDialogFactory = mock(ConfirmDeleteDialogFactory.class);
|
confirmDeleteDialogFactory = mock(ConfirmDeleteDialogFactory.class);
|
||||||
createHabitDialogFactory = mock(CreateHabitDialogFactory.class);
|
|
||||||
filePickerDialogFactory = mock(FilePickerDialogFactory.class);
|
filePickerDialogFactory = mock(FilePickerDialogFactory.class);
|
||||||
colorPickerDialogFactory = mock(ColorPickerDialogFactory.class);
|
colorPickerDialogFactory = mock(ColorPickerDialogFactory.class);
|
||||||
editHabitDialogFactory = mock(EditHabitDialogFactory.class);
|
dialogFactory = mock(EditHabitDialogFactory.class);
|
||||||
|
|
||||||
screen = spy(new ListHabitsScreen(activity, commandRunner, dirFinder,
|
screen = spy(new ListHabitsScreen(activity, commandRunner, dirFinder,
|
||||||
rootView, intentFactory, themeSwitcher, confirmDeleteDialogFactory,
|
rootView, intentFactory, themeSwitcher, confirmDeleteDialogFactory,
|
||||||
createHabitDialogFactory, filePickerDialogFactory,
|
filePickerDialogFactory, colorPickerDialogFactory, dialogFactory));
|
||||||
colorPickerDialogFactory, editHabitDialogFactory));
|
|
||||||
|
|
||||||
doNothing().when(screen).showMessage(anyInt());
|
doNothing().when(screen).showMessage(anyInt());
|
||||||
|
|
||||||
@@ -111,15 +107,38 @@ public class ListHabitsScreenTest extends BaseUnitTest
|
|||||||
intent = mock(Intent.class);
|
intent = mock(Intent.class);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// @Test
|
||||||
|
// public void testCreateHabitScreen()
|
||||||
|
// {
|
||||||
|
// CreateBooleanHabitDialog dialog = mock(CreateBooleanHabitDialog.class);
|
||||||
|
// when(createHabitDialogFactory.create()).thenReturn(dialog);
|
||||||
|
//
|
||||||
|
// screen.showCreateHabitScreen();
|
||||||
|
//
|
||||||
|
// verify(activity).showDialog(eq(dialog), any());
|
||||||
|
// }
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testCreateHabitScreen()
|
public void testOnAttached()
|
||||||
{
|
{
|
||||||
CreateHabitDialog dialog = mock(CreateHabitDialog.class);
|
screen.onAttached();
|
||||||
when(createHabitDialogFactory.create()).thenReturn(dialog);
|
verify(commandRunner).addListener(screen);
|
||||||
|
}
|
||||||
|
|
||||||
screen.showCreateHabitScreen();
|
@Test
|
||||||
|
public void testOnCommand()
|
||||||
|
{
|
||||||
|
Command c = mock(Command.class);
|
||||||
|
when(c.getExecuteStringId()).thenReturn(R.string.toast_habit_deleted);
|
||||||
|
screen.onCommandExecuted(c, null);
|
||||||
|
verify(screen).showMessage(R.string.toast_habit_deleted);
|
||||||
|
}
|
||||||
|
|
||||||
verify(activity).showDialog(eq(dialog), any());
|
@Test
|
||||||
|
public void testOnDetach()
|
||||||
|
{
|
||||||
|
screen.onDettached();
|
||||||
|
verify(commandRunner).removeListener(screen);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@@ -190,7 +209,7 @@ public class ListHabitsScreenTest extends BaseUnitTest
|
|||||||
public void testShowEditHabitScreen()
|
public void testShowEditHabitScreen()
|
||||||
{
|
{
|
||||||
EditHabitDialog dialog = mock(EditHabitDialog.class);
|
EditHabitDialog dialog = mock(EditHabitDialog.class);
|
||||||
when(editHabitDialogFactory.create(habit)).thenReturn(dialog);
|
when(dialogFactory.edit(habit)).thenReturn(dialog);
|
||||||
|
|
||||||
screen.showEditHabitScreen(habit);
|
screen.showEditHabitScreen(habit);
|
||||||
verify(activity).showDialog(eq(dialog), any());
|
verify(activity).showDialog(eq(dialog), any());
|
||||||
@@ -260,27 +279,4 @@ public class ListHabitsScreenTest extends BaseUnitTest
|
|||||||
verify(themeSwitcher).toggleNightMode();
|
verify(themeSwitcher).toggleNightMode();
|
||||||
verify(activity).restartWithFade();
|
verify(activity).restartWithFade();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testOnAttached()
|
|
||||||
{
|
|
||||||
screen.onAttached();
|
|
||||||
verify(commandRunner).addListener(screen);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testOnDetach()
|
|
||||||
{
|
|
||||||
screen.onDettached();
|
|
||||||
verify(commandRunner).removeListener(screen);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testOnCommand()
|
|
||||||
{
|
|
||||||
Command c = mock(Command.class);
|
|
||||||
when(c.getExecuteStringId()).thenReturn(R.string.toast_habit_deleted);
|
|
||||||
screen.onCommandExecuted(c, null);
|
|
||||||
verify(screen).showMessage(R.string.toast_habit_deleted);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
@@ -106,7 +106,7 @@ public class HabitCardListCacheTest extends BaseUnitTest
|
|||||||
|
|
||||||
Habit h = habitList.getByPosition(3);
|
Habit h = habitList.getByPosition(3);
|
||||||
assertNotNull(h.getId());
|
assertNotNull(h.getId());
|
||||||
int score = h.getScores().getTodayValue();
|
double score = h.getScores().getTodayValue();
|
||||||
|
|
||||||
assertThat(cache.getHabitByPosition(3), equalTo(h));
|
assertThat(cache.getHabitByPosition(3), equalTo(h));
|
||||||
assertThat(cache.getScore(h.getId()), equalTo(score));
|
assertThat(cache.getScore(h.getId()), equalTo(score));
|
||||||
|
|||||||
@@ -0,0 +1,70 @@
|
|||||||
|
/*
|
||||||
|
* 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.commands;
|
||||||
|
|
||||||
|
import org.isoron.uhabits.*;
|
||||||
|
import org.isoron.uhabits.models.*;
|
||||||
|
import org.isoron.uhabits.utils.*;
|
||||||
|
import org.junit.*;
|
||||||
|
|
||||||
|
import static junit.framework.Assert.*;
|
||||||
|
import static org.isoron.uhabits.models.Checkmark.CHECKED_EXPLICITLY;
|
||||||
|
|
||||||
|
public class CreateRepetitionCommandTest extends BaseUnitTest
|
||||||
|
{
|
||||||
|
|
||||||
|
private CreateRepetitionCommand command;
|
||||||
|
|
||||||
|
private Habit habit;
|
||||||
|
|
||||||
|
private long today;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@Before
|
||||||
|
public void setUp()
|
||||||
|
{
|
||||||
|
super.setUp();
|
||||||
|
|
||||||
|
habit = fixtures.createShortHabit();
|
||||||
|
|
||||||
|
today = DateUtils.getStartOfToday();
|
||||||
|
command = new CreateRepetitionCommand(habit, today, 100);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testExecuteUndoRedo()
|
||||||
|
{
|
||||||
|
RepetitionList reps = habit.getRepetitions();
|
||||||
|
|
||||||
|
Repetition rep = reps.getByTimestamp(today);
|
||||||
|
assertNotNull(rep);
|
||||||
|
assertEquals(CHECKED_EXPLICITLY, rep.getValue());
|
||||||
|
|
||||||
|
command.execute();
|
||||||
|
rep = reps.getByTimestamp(today);
|
||||||
|
assertNotNull(rep);
|
||||||
|
assertEquals(100, rep.getValue());
|
||||||
|
|
||||||
|
command.undo();
|
||||||
|
rep = reps.getByTimestamp(today);
|
||||||
|
assertNotNull(rep);
|
||||||
|
assertEquals(CHECKED_EXPLICITLY, rep.getValue());
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -58,7 +58,7 @@ public class EditHabitCommandTest extends BaseUnitTest
|
|||||||
command =
|
command =
|
||||||
new EditHabitCommand(modelFactory, habitList, habit, modified);
|
new EditHabitCommand(modelFactory, habitList, habit, modified);
|
||||||
|
|
||||||
int originalScore = habit.getScores().getTodayValue();
|
double originalScore = habit.getScores().getTodayValue();
|
||||||
assertThat(habit.getName(), equalTo("original"));
|
assertThat(habit.getName(), equalTo("original"));
|
||||||
|
|
||||||
command.execute();
|
command.execute();
|
||||||
@@ -81,13 +81,13 @@ public class EditHabitCommandTest extends BaseUnitTest
|
|||||||
command =
|
command =
|
||||||
new EditHabitCommand(modelFactory, habitList, habit, modified);
|
new EditHabitCommand(modelFactory, habitList, habit, modified);
|
||||||
|
|
||||||
int originalScore = habit.getScores().getTodayValue();
|
double originalScore = habit.getScores().getTodayValue();
|
||||||
assertThat(habit.getName(), equalTo("original"));
|
assertThat(habit.getName(), equalTo("original"));
|
||||||
|
|
||||||
command.execute();
|
command.execute();
|
||||||
assertThat(habit.getName(), equalTo("modified"));
|
assertThat(habit.getName(), equalTo("modified"));
|
||||||
assertThat(habit.getScores().getTodayValue(),
|
assertThat(habit.getScores().getTodayValue(),
|
||||||
greaterThan(originalScore));
|
lessThan(originalScore));
|
||||||
|
|
||||||
command.undo();
|
command.undo();
|
||||||
assertThat(habit.getName(), equalTo("original"));
|
assertThat(habit.getName(), equalTo("original"));
|
||||||
@@ -96,6 +96,6 @@ public class EditHabitCommandTest extends BaseUnitTest
|
|||||||
command.execute();
|
command.execute();
|
||||||
assertThat(habit.getName(), equalTo("modified"));
|
assertThat(habit.getName(), equalTo("modified"));
|
||||||
assertThat(habit.getScores().getTodayValue(),
|
assertThat(habit.getScores().getTodayValue(),
|
||||||
greaterThan(originalScore));
|
lessThan(originalScore));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -28,9 +28,12 @@ import java.util.*;
|
|||||||
|
|
||||||
import static org.hamcrest.CoreMatchers.*;
|
import static org.hamcrest.CoreMatchers.*;
|
||||||
import static org.hamcrest.MatcherAssert.*;
|
import static org.hamcrest.MatcherAssert.*;
|
||||||
|
import static org.hamcrest.number.IsCloseTo.closeTo;
|
||||||
|
|
||||||
public class ScoreListTest extends BaseUnitTest
|
public class ScoreListTest extends BaseUnitTest
|
||||||
{
|
{
|
||||||
|
private static final double E = 1e-6;
|
||||||
|
|
||||||
private Habit habit;
|
private Habit habit;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -46,43 +49,40 @@ public class ScoreListTest extends BaseUnitTest
|
|||||||
{
|
{
|
||||||
toggleRepetitions(0, 20);
|
toggleRepetitions(0, 20);
|
||||||
|
|
||||||
int expectedValues[] = {
|
double expectedValues[] = {
|
||||||
12629351,
|
0.655747,
|
||||||
12266245,
|
0.636894,
|
||||||
11883254,
|
0.617008,
|
||||||
11479288,
|
0.596033,
|
||||||
11053198,
|
0.573910,
|
||||||
10603773,
|
0.550574,
|
||||||
10129735,
|
0.525961,
|
||||||
9629735,
|
0.500000,
|
||||||
9102352,
|
0.472617,
|
||||||
8546087,
|
0.443734,
|
||||||
7959357,
|
0.413270,
|
||||||
7340494,
|
0.381137,
|
||||||
6687738,
|
0.347244,
|
||||||
5999234,
|
0.311495,
|
||||||
5273023,
|
0.273788,
|
||||||
4507040,
|
0.234017,
|
||||||
3699107,
|
0.192067,
|
||||||
2846927,
|
0.147820,
|
||||||
1948077,
|
0.101149,
|
||||||
1000000
|
0.051922,
|
||||||
};
|
};
|
||||||
|
|
||||||
int actualValues[] = new int[expectedValues.length];
|
|
||||||
|
|
||||||
int i = 0;
|
int i = 0;
|
||||||
for (Score s : habit.getScores())
|
for (Score s : habit.getScores())
|
||||||
actualValues[i++] = s.getValue();
|
assertThat(s.getValue(), closeTo(expectedValues[i++], E));
|
||||||
|
|
||||||
assertThat(actualValues, equalTo(expectedValues));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void test_getTodayValue()
|
public void test_getTodayValue()
|
||||||
{
|
{
|
||||||
toggleRepetitions(0, 20);
|
toggleRepetitions(0, 20);
|
||||||
assertThat(habit.getScores().getTodayValue(), equalTo(12629351));
|
double actual = habit.getScores().getTodayValue();
|
||||||
|
assertThat(actual, closeTo(0.655747, E));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@@ -90,37 +90,37 @@ public class ScoreListTest extends BaseUnitTest
|
|||||||
{
|
{
|
||||||
toggleRepetitions(0, 20);
|
toggleRepetitions(0, 20);
|
||||||
|
|
||||||
int expectedValues[] = {
|
double expectedValues[] = {
|
||||||
12629351,
|
0.655747,
|
||||||
12266245,
|
0.636894,
|
||||||
11883254,
|
0.617008,
|
||||||
11479288,
|
0.596033,
|
||||||
11053198,
|
0.573910,
|
||||||
10603773,
|
0.550574,
|
||||||
10129735,
|
0.525961,
|
||||||
9629735,
|
0.500000,
|
||||||
9102352,
|
0.472617,
|
||||||
8546087,
|
0.443734,
|
||||||
7959357,
|
0.413270,
|
||||||
7340494,
|
0.381137,
|
||||||
6687738,
|
0.347244,
|
||||||
5999234,
|
0.311495,
|
||||||
5273023,
|
0.273788,
|
||||||
4507040,
|
0.234017,
|
||||||
3699107,
|
0.192067,
|
||||||
2846927,
|
0.147820,
|
||||||
1948077,
|
0.101149,
|
||||||
1000000,
|
0.051922,
|
||||||
0,
|
0.000000,
|
||||||
0,
|
0.000000,
|
||||||
0
|
0.000000
|
||||||
};
|
};
|
||||||
|
|
||||||
ScoreList scores = habit.getScores();
|
ScoreList scores = habit.getScores();
|
||||||
long current = DateUtils.getStartOfToday();
|
long current = DateUtils.getStartOfToday();
|
||||||
for (int expectedValue : expectedValues)
|
for (double expectedValue : expectedValues)
|
||||||
{
|
{
|
||||||
assertThat(scores.getValue(current), equalTo(expectedValue));
|
assertThat(scores.getValue(current), closeTo(expectedValue, E));
|
||||||
current -= DateUtils.millisecondsInOneDay;
|
current -= DateUtils.millisecondsInOneDay;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -133,23 +133,23 @@ public class ScoreListTest extends BaseUnitTest
|
|||||||
habit.getScores().groupBy(DateUtils.TruncateField.MONTH);
|
habit.getScores().groupBy(DateUtils.TruncateField.MONTH);
|
||||||
|
|
||||||
assertThat(list.size(), equalTo(5));
|
assertThat(list.size(), equalTo(5));
|
||||||
assertThat(list.get(0).getValue(), equalTo(14634077));
|
assertThat(list.get(0).getValue(), closeTo(0.549096, E));
|
||||||
assertThat(list.get(1).getValue(), equalTo(12969133));
|
assertThat(list.get(1).getValue(), closeTo(0.480098, E));
|
||||||
assertThat(list.get(2).getValue(), equalTo(10595391));
|
assertThat(list.get(2).getValue(), closeTo(0.377885, E));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void test_invalidateNewerThan()
|
public void test_invalidateNewerThan()
|
||||||
{
|
{
|
||||||
assertThat(habit.getScores().getTodayValue(), equalTo(0));
|
assertThat(habit.getScores().getTodayValue(), closeTo(0.0, E));
|
||||||
|
|
||||||
toggleRepetitions(0, 2);
|
toggleRepetitions(0, 2);
|
||||||
assertThat(habit.getScores().getTodayValue(), equalTo(1948077));
|
assertThat(habit.getScores().getTodayValue(), closeTo(0.101149, E));
|
||||||
|
|
||||||
habit.setFrequency(new Frequency(1, 2));
|
habit.setFrequency(new Frequency(1, 2));
|
||||||
habit.getScores().invalidateNewerThan(0);
|
habit.getScores().invalidateNewerThan(0);
|
||||||
|
|
||||||
assertThat(habit.getScores().getTodayValue(), equalTo(1974654));
|
assertThat(habit.getScores().getTodayValue(), closeTo(0.051922, E));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@@ -157,16 +157,16 @@ public class ScoreListTest extends BaseUnitTest
|
|||||||
{
|
{
|
||||||
Habit habit = fixtures.createShortHabit();
|
Habit habit = fixtures.createShortHabit();
|
||||||
|
|
||||||
String expectedCSV = "2015-01-25,0.2649\n" +
|
String expectedCSV = "2015-01-25,0.2372\n" +
|
||||||
"2015-01-24,0.2205\n" +
|
"2015-01-24,0.2096\n" +
|
||||||
"2015-01-23,0.2283\n" +
|
"2015-01-23,0.2172\n" +
|
||||||
"2015-01-22,0.2364\n" +
|
"2015-01-22,0.1889\n" +
|
||||||
"2015-01-21,0.1909\n" +
|
"2015-01-21,0.1595\n" +
|
||||||
"2015-01-20,0.1439\n" +
|
"2015-01-20,0.1291\n" +
|
||||||
"2015-01-19,0.0952\n" +
|
"2015-01-19,0.0976\n" +
|
||||||
"2015-01-18,0.0986\n" +
|
"2015-01-18,0.1011\n" +
|
||||||
"2015-01-17,0.1021\n" +
|
"2015-01-17,0.0686\n" +
|
||||||
"2015-01-16,0.0519\n";
|
"2015-01-16,0.0349\n";
|
||||||
|
|
||||||
StringWriter writer = new StringWriter();
|
StringWriter writer = new StringWriter();
|
||||||
habit.getScores().writeCSV(writer);
|
habit.getScores().writeCSV(writer);
|
||||||
@@ -185,14 +185,17 @@ public class ScoreListTest extends BaseUnitTest
|
|||||||
long from = today - 4 * day;
|
long from = today - 4 * day;
|
||||||
long to = today - 2 * day;
|
long to = today - 2 * day;
|
||||||
|
|
||||||
int[] expected = {
|
double[] expected = {
|
||||||
11883254,
|
0.617008,
|
||||||
11479288,
|
0.596033,
|
||||||
11053198,
|
0.573909,
|
||||||
};
|
};
|
||||||
|
|
||||||
int[] actual = habit.getScores().getValues(from, to);
|
double[] actual = habit.getScores().getValues(from, to);
|
||||||
assertThat(actual, equalTo(expected));
|
assertThat(actual.length, equalTo(expected.length));
|
||||||
|
|
||||||
|
for(int i = 0; i < actual.length; i++)
|
||||||
|
assertThat(actual[i], closeTo(expected[i], E));
|
||||||
}
|
}
|
||||||
|
|
||||||
private void toggleRepetitions(final int from, final int to)
|
private void toggleRepetitions(final int from, final int to)
|
||||||
|
|||||||
@@ -19,15 +19,17 @@
|
|||||||
|
|
||||||
package org.isoron.uhabits.models;
|
package org.isoron.uhabits.models;
|
||||||
|
|
||||||
import org.isoron.uhabits.BaseUnitTest;
|
import org.isoron.uhabits.*;
|
||||||
import org.junit.Before;
|
import org.junit.*;
|
||||||
import org.junit.Test;
|
|
||||||
|
|
||||||
import static org.hamcrest.CoreMatchers.equalTo;
|
import static org.hamcrest.number.IsCloseTo.*;
|
||||||
import static org.junit.Assert.assertThat;
|
import static org.isoron.uhabits.models.Score.*;
|
||||||
|
import static org.junit.Assert.*;
|
||||||
|
|
||||||
public class ScoreTest extends BaseUnitTest
|
public class ScoreTest extends BaseUnitTest
|
||||||
{
|
{
|
||||||
|
private static final double E = 1e-6;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@Before
|
@Before
|
||||||
public void setUp()
|
public void setUp()
|
||||||
@@ -38,46 +40,30 @@ public class ScoreTest extends BaseUnitTest
|
|||||||
@Test
|
@Test
|
||||||
public void test_compute_withDailyHabit()
|
public void test_compute_withDailyHabit()
|
||||||
{
|
{
|
||||||
int checkmark = Checkmark.UNCHECKED;
|
int check = 1;
|
||||||
assertThat(Score.compute(1, 0, checkmark), equalTo(0));
|
double freq = 1.0;
|
||||||
assertThat(Score.compute(1, 5000000, checkmark), equalTo(4740387));
|
assertThat(compute(freq, 0, check), closeTo(0.051922, E));
|
||||||
assertThat(Score.compute(1, 10000000, checkmark), equalTo(9480775));
|
assertThat(compute(freq, 0.5, check), closeTo(0.525961, E));
|
||||||
assertThat(Score.compute(1, Score.MAX_VALUE, checkmark),
|
assertThat(compute(freq, 0.75, check), closeTo(0.762981, E));
|
||||||
equalTo(18259478));
|
|
||||||
|
|
||||||
checkmark = Checkmark.CHECKED_IMPLICITLY;
|
check = 0;
|
||||||
assertThat(Score.compute(1, 0, checkmark), equalTo(0));
|
assertThat(compute(freq, 0, check), closeTo(0, E));
|
||||||
assertThat(Score.compute(1, 5000000, checkmark), equalTo(4740387));
|
assertThat(compute(freq, 0.5, check), closeTo(0.474039, E));
|
||||||
assertThat(Score.compute(1, 10000000, checkmark), equalTo(9480775));
|
assertThat(compute(freq, 0.75, check), closeTo(0.711058, E));
|
||||||
assertThat(Score.compute(1, Score.MAX_VALUE, checkmark),
|
|
||||||
equalTo(18259478));
|
|
||||||
|
|
||||||
checkmark = Checkmark.CHECKED_EXPLICITLY;
|
|
||||||
assertThat(Score.compute(1, 0, checkmark), equalTo(1000000));
|
|
||||||
assertThat(Score.compute(1, 5000000, checkmark), equalTo(5740387));
|
|
||||||
assertThat(Score.compute(1, 10000000, checkmark), equalTo(10480775));
|
|
||||||
assertThat(Score.compute(1, Score.MAX_VALUE, checkmark),
|
|
||||||
equalTo(Score.MAX_VALUE));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void test_compute_withNonDailyHabit()
|
public void test_compute_withNonDailyHabit()
|
||||||
{
|
{
|
||||||
int checkmark = Checkmark.CHECKED_EXPLICITLY;
|
int check = 1;
|
||||||
assertThat(Score.compute(1 / 3.0, 0, checkmark), equalTo(1000000));
|
double freq = 1 / 3.0;
|
||||||
assertThat(Score.compute(1 / 3.0, 5000000, checkmark),
|
assertThat(compute(freq, 0, check), closeTo(0.017616, E));
|
||||||
equalTo(5916180));
|
assertThat(compute(freq, 0.5, check), closeTo(0.508808, E));
|
||||||
assertThat(Score.compute(1 / 3.0, 10000000, checkmark),
|
assertThat(compute(freq, 0.75, check), closeTo(0.754404, E));
|
||||||
equalTo(10832360));
|
|
||||||
assertThat(Score.compute(1 / 3.0, Score.MAX_VALUE, checkmark),
|
|
||||||
equalTo(Score.MAX_VALUE));
|
|
||||||
|
|
||||||
assertThat(Score.compute(1 / 7.0, 0, checkmark), equalTo(1000000));
|
check = 0;
|
||||||
assertThat(Score.compute(1 / 7.0, 5000000, checkmark),
|
assertThat(compute(freq, 0, check), closeTo(0.0, E));
|
||||||
equalTo(5964398));
|
assertThat(compute(freq, 0.5, check), closeTo(0.491192, E));
|
||||||
assertThat(Score.compute(1 / 7.0, 10000000, checkmark),
|
assertThat(compute(freq, 0.75, check), closeTo(0.736788, E));
|
||||||
equalTo(10928796));
|
|
||||||
assertThat(Score.compute(1 / 7.0, Score.MAX_VALUE, checkmark),
|
|
||||||
equalTo(Score.MAX_VALUE));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -22,6 +22,7 @@ package org.isoron.uhabits.models;
|
|||||||
import org.isoron.uhabits.*;
|
import org.isoron.uhabits.*;
|
||||||
import org.junit.*;
|
import org.junit.*;
|
||||||
|
|
||||||
|
import static junit.framework.Assert.*;
|
||||||
import static org.hamcrest.MatcherAssert.*;
|
import static org.hamcrest.MatcherAssert.*;
|
||||||
import static org.hamcrest.core.IsEqual.*;
|
import static org.hamcrest.core.IsEqual.*;
|
||||||
|
|
||||||
@@ -48,6 +49,8 @@ public class WeekdayListTest extends BaseUnitTest
|
|||||||
public void testEmpty()
|
public void testEmpty()
|
||||||
{
|
{
|
||||||
WeekdayList list = new WeekdayList(0);
|
WeekdayList list = new WeekdayList(0);
|
||||||
assertThat(list.toArray(), equalTo(WeekdayList.EVERY_DAY.toArray()));
|
assertTrue(list.isEmpty());
|
||||||
|
|
||||||
|
assertFalse(WeekdayList.EVERY_DAY.isEmpty());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user