Persist unit and target value of a habit

pull/157/merge
Alinson S. Xavier 9 years ago
parent 5653651c0d
commit 7d2e8573f8

@ -12,7 +12,7 @@ android {
minSdkVersion 15 minSdkVersion 15
targetSdkVersion 25 targetSdkVersion 25
buildConfigField "Integer", "databaseVersion", "17" 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"

@ -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 "";

@ -39,8 +39,7 @@ public class CreateBooleanHabitDialog extends BooleanHabitDialog
{ {
modifiedHabit = modelFactory.buildHabit(); modifiedHabit = modelFactory.buildHabit();
modifiedHabit.setFrequency(Frequency.DAILY); modifiedHabit.setFrequency(Frequency.DAILY);
modifiedHabit.setColor( modifiedHabit.setColor(prefs.getDefaultHabitColor(modifiedHabit.getColor()));
prefs.getDefaultHabitColor(modifiedHabit.getColor()));
} }
@Override @Override

@ -42,6 +42,7 @@ public class CreateNumericalHabitDialog extends NumericalHabitDialog
modifiedHabit.setColor( modifiedHabit.setColor(
prefs.getDefaultHabitColor(modifiedHabit.getColor())); prefs.getDefaultHabitColor(modifiedHabit.getColor()));
modifiedHabit.setType(Habit.NUMBER_HABIT); modifiedHabit.setType(Habit.NUMBER_HABIT);
modifiedHabit.setTargetValue(100);
} }
@Override @Override

@ -0,0 +1,55 @@
/*
* 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 org.isoron.uhabits.*;
import org.isoron.uhabits.commands.*;
public class EditNumericalHabitDialog extends NumericalHabitDialog
{
@Override
protected int getTitle()
{
return R.string.edit_habit;
}
@Override
protected void initializeHabits()
{
Long habitId = (Long) getArguments().get("habitId");
if (habitId == null)
throw new IllegalArgumentException("habitId must be specified");
originalHabit = habitList.getById(habitId);
if (originalHabit == null)
throw new IllegalStateException("habit is null");
modifiedHabit = modelFactory.buildHabit();
modifiedHabit.copyFrom(originalHabit);
}
@Override
protected void saveHabit()
{
Command command = appComponent.getEditHabitCommandFactory().
create(habitList, originalHabit, modifiedHabit);
commandRunner.execute(command, originalHabit.getId());
}
}

@ -0,0 +1,47 @@
/*
* 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 org.isoron.uhabits.models.*;
import javax.inject.*;
public class EditNumericalHabitDialogFactory
{
@Inject
public EditNumericalHabitDialogFactory()
{
}
public EditNumericalHabitDialog create(@NonNull Habit habit)
{
if (habit.getId() == null)
throw new IllegalArgumentException("habit not saved");
EditNumericalHabitDialog dialog = new EditNumericalHabitDialog();
Bundle args = new Bundle();
args.putLong("habitId", habit.getId());
dialog.setArguments(args);
return dialog;
}
}

@ -22,10 +22,8 @@ package org.isoron.uhabits.activities.habits.edit;
import android.os.*; import android.os.*;
import android.support.annotation.*; import android.support.annotation.*;
import android.support.v7.app.*; import android.support.v7.app.*;
import android.text.format.*;
import android.view.*; import android.view.*;
import android.widget.*;
import com.android.datetimepicker.time.*;
import org.isoron.uhabits.*; import org.isoron.uhabits.*;
import org.isoron.uhabits.R; import org.isoron.uhabits.R;
@ -34,11 +32,12 @@ import org.isoron.uhabits.activities.common.dialogs.*;
import org.isoron.uhabits.commands.*; import org.isoron.uhabits.commands.*;
import org.isoron.uhabits.models.*; import org.isoron.uhabits.models.*;
import org.isoron.uhabits.preferences.*; import org.isoron.uhabits.preferences.*;
import org.isoron.uhabits.utils.*;
import java.util.*;
import butterknife.*; import butterknife.*;
import static org.isoron.uhabits.R.id.*;
public abstract class NumericalHabitDialog extends AppCompatDialogFragment public abstract class NumericalHabitDialog extends AppCompatDialogFragment
{ {
@Nullable @Nullable
@ -47,9 +46,6 @@ public abstract class NumericalHabitDialog extends AppCompatDialogFragment
@Nullable @Nullable
protected Habit modifiedHabit; protected Habit modifiedHabit;
@Nullable
protected BaseDialogHelper helper;
protected Preferences prefs; protected Preferences prefs;
protected CommandRunner commandRunner; protected CommandRunner commandRunner;
@ -62,6 +58,12 @@ public abstract class NumericalHabitDialog extends AppCompatDialogFragment
private ColorPickerDialogFactory colorPickerDialogFactory; private ColorPickerDialogFactory colorPickerDialogFactory;
private NumericalHabitDialogHelper helper;
private boolean tvDescriptionInitialized = false;
private boolean tvUnitInitialized = false;
@Override @Override
public int getTheme() public int getTheme()
{ {
@ -72,10 +74,9 @@ public abstract class NumericalHabitDialog extends AppCompatDialogFragment
public void onActivityCreated(Bundle savedInstanceState) public void onActivityCreated(Bundle savedInstanceState)
{ {
super.onActivityCreated(savedInstanceState); super.onActivityCreated(savedInstanceState);
BaseActivity activity = (BaseActivity) getActivity(); BaseActivity activity = (BaseActivity) getActivity();
colorPickerDialogFactory = ActivityComponent component = activity.getComponent();
activity.getComponent().getColorPickerDialogFactory(); colorPickerDialogFactory = component.getColorPickerDialogFactory();
} }
@Override @Override
@ -97,7 +98,7 @@ public abstract class NumericalHabitDialog extends AppCompatDialogFragment
ButterKnife.bind(this, view); ButterKnife.bind(this, view);
helper = new BaseDialogHelper(this, view); helper = new NumericalHabitDialogHelper(this, view);
getDialog().setTitle(getTitle()); getDialog().setTitle(getTitle());
initializeHabits(); initializeHabits();
restoreSavedInstance(savedInstanceState); restoreSavedInstance(savedInstanceState);
@ -106,18 +107,10 @@ public abstract class NumericalHabitDialog extends AppCompatDialogFragment
} }
@Override @Override
@SuppressWarnings("ConstantConditions")
public void onSaveInstanceState(Bundle outState) public void onSaveInstanceState(Bundle outState)
{ {
super.onSaveInstanceState(outState); super.onSaveInstanceState(outState);
outState.putInt("color", modifiedHabit.getColor()); 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 int getTitle();
@ -127,73 +120,60 @@ public abstract class NumericalHabitDialog extends AppCompatDialogFragment
protected void restoreSavedInstance(@Nullable Bundle bundle) protected void restoreSavedInstance(@Nullable Bundle bundle)
{ {
if (bundle == null) return; if (bundle == null) return;
modifiedHabit.setColor( if (modifiedHabit == null) return;
bundle.getInt("color", modifiedHabit.getColor()));
modifiedHabit.setReminder(null);
int hour = (bundle.getInt("reminderHour", -1)); int color = bundle.getInt("color", modifiedHabit.getColor());
int minute = (bundle.getInt("reminderMin", -1));
int days = (bundle.getInt("reminderDays", -1));
if (hour >= 0 && minute >= 0) modifiedHabit.setColor(color);
{ modifiedHabit.setReminder(null);
Reminder reminder =
new Reminder(hour, minute, new WeekdayList(days));
modifiedHabit.setReminder(reminder);
}
} }
protected abstract void saveHabit(); protected abstract void saveHabit();
@OnClick(R.id.buttonDiscard) @OnFocusChange(tvDescription)
void onButtonDiscardClick() void clearDefaultDescription(boolean focused)
{ {
dismiss(); if (!focused || tvDescriptionInitialized) return;
tvDescriptionInitialized = true;
clearDefaultText(helper.tvDescription);
} }
@OnClick(R.id.tvReminderTime) private void clearDefaultText(TextView textView)
@SuppressWarnings("ConstantConditions")
void onDateSpinnerClick()
{ {
int defaultHour = 8; StyledResources sr = new StyledResources(getContext());
int defaultMin = 0; int color = sr.getColor(R.attr.highContrastTextColor);
textView.setText("");
textView.setTextColor(color);
}
if (modifiedHabit.hasReminder()) @OnFocusChange(tvUnit)
{ void clearDefaultUnit(boolean focused)
Reminder reminder = modifiedHabit.getReminder(); {
defaultHour = reminder.getHour(); if (!focused || tvUnitInitialized) return;
defaultMin = reminder.getMinute(); tvUnitInitialized = true;
} clearDefaultText(helper.tvUnit);
}
showTimePicker(defaultHour, defaultMin); @OnClick(R.id.buttonDiscard)
void onButtonDiscardClick()
{
dismiss();
} }
@OnClick(R.id.buttonSave) @OnClick(R.id.buttonSave)
void onSaveButtonClick() void onSaveButtonClick()
{ {
helper.parseFormIntoHabit(modifiedHabit); helper.parseForm(modifiedHabit);
if (!helper.validate(modifiedHabit)) return; if (!helper.validate(modifiedHabit)) return;
saveHabit(); saveHabit();
dismiss(); 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) @OnClick(R.id.buttonPickColor)
void showColorPicker() void showColorPicker()
{ {
if (modifiedHabit == null) return;
int color = modifiedHabit.getColor(); int color = modifiedHabit.getColor();
ColorPickerDialog picker = colorPickerDialogFactory.create(color); ColorPickerDialog picker = colorPickerDialogFactory.create(color);
@ -206,55 +186,4 @@ public abstract class NumericalHabitDialog extends AppCompatDialogFragment
picker.show(getFragmentManager(), "picker"); 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;
}
}
} }

@ -0,0 +1,117 @@
/*
* 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.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 NumericalHabitDialogHelper
{
private DialogFragment frag;
@BindView(R.id.tvName)
TextView tvName;
@BindView(R.id.tvDescription)
TextView tvDescription;
@BindView(R.id.tvUnit)
TextView tvUnit;
@BindView(R.id.tvTargetCount)
TextView tvTargetValue;
@BindView(R.id.tvTargetType)
Spinner tvTargetType;
public NumericalHabitDialogHelper(DialogFragment frag, View view)
{
this.frag = frag;
ButterKnife.bind(this, view);
}
public void parseForm(Habit habit)
{
habit.setName(tvName.getText().toString().trim());
habit.setDescription(tvDescription.getText().toString().trim());
habit.setTargetType(tvTargetType.getSelectedItemPosition());
habit.setTargetValue(
Double.parseDouble(tvTargetValue.getText().toString()));
habit.setUnit(tvUnit.getText().toString().trim());
}
public void populateColor(int paletteColor)
{
tvName.setTextColor(
ColorUtils.getColor(frag.getContext(), paletteColor));
}
public void populateForm(final Habit habit)
{
tvName.setText(habit.getName());
if(!habit.getDescription().isEmpty())
tvDescription.setText(habit.getDescription());
if(!habit.getUnit().isEmpty())
tvUnit.setText(habit.getUnit());
tvTargetType.setSelection(habit.getTargetType());
tvTargetValue.setText(String.format("%.0f", habit.getTargetValue()));
populateColor(habit.getColor());
}
public boolean validate(Habit habit)
{
Boolean valid = true;
if (!validateName(habit)) valid = false;
if (!validateTargetValue()) valid = false;
return valid;
}
private boolean validateTargetValue()
{
double value = Double.parseDouble(tvTargetValue.getText().toString());
if(value <= 0)
{
tvTargetValue.setError(frag.getString(R.string.validation_number_should_be_positive));
return false;
}
return true;
}
private Boolean validateName(Habit habit)
{
if (habit.getName().isEmpty())
{
tvName.setError(frag.getString(R.string.validation_name_should_not_be_blank));
return false;
}
return true;
}
}

@ -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)
{ {

@ -97,8 +97,12 @@ public class ListHabitsScreen extends BaseScreen
@NonNull @NonNull
private final ThemeSwitcher themeSwitcher; private final ThemeSwitcher themeSwitcher;
@NonNull
private CreateNumericalHabitDialogFactory createNumericalHabitDialogFactory; private CreateNumericalHabitDialogFactory createNumericalHabitDialogFactory;
@NonNull
private EditNumericalHabitDialogFactory editNumericalHabitDialogFactory;
@Inject @Inject
public ListHabitsScreen(@NonNull BaseActivity activity, public ListHabitsScreen(@NonNull BaseActivity activity,
@NonNull CommandRunner commandRunner, @NonNull CommandRunner commandRunner,
@ -111,6 +115,7 @@ public class ListHabitsScreen extends BaseScreen
@NonNull FilePickerDialogFactory filePickerDialogFactory, @NonNull FilePickerDialogFactory filePickerDialogFactory,
@NonNull ColorPickerDialogFactory colorPickerFactory, @NonNull ColorPickerDialogFactory colorPickerFactory,
@NonNull EditBooleanHabitDialogFactory editBooleanHabitDialogFactory, @NonNull EditBooleanHabitDialogFactory editBooleanHabitDialogFactory,
@NonNull EditNumericalHabitDialogFactory editNumericalHabitDialogFactory,
@NonNull CreateNumericalHabitDialogFactory createNumericalHabitDialogFactory) @NonNull CreateNumericalHabitDialogFactory createNumericalHabitDialogFactory)
{ {
super(activity); super(activity);
@ -121,6 +126,7 @@ public class ListHabitsScreen extends BaseScreen
this.createNumericalHabitDialogFactory = createNumericalHabitDialogFactory; this.createNumericalHabitDialogFactory = createNumericalHabitDialogFactory;
this.createBooleanHabitDialogFactory = createBooleanHabitDialogFactory; this.createBooleanHabitDialogFactory = createBooleanHabitDialogFactory;
this.editBooleanHabitDialogFactory = editBooleanHabitDialogFactory; this.editBooleanHabitDialogFactory = editBooleanHabitDialogFactory;
this.editNumericalHabitDialogFactory = editNumericalHabitDialogFactory;
this.dirFinder = dirFinder; this.dirFinder = dirFinder;
this.filePickerDialogFactory = filePickerDialogFactory; this.filePickerDialogFactory = filePickerDialogFactory;
this.intentFactory = intentFactory; this.intentFactory = intentFactory;
@ -214,9 +220,18 @@ public class ListHabitsScreen extends BaseScreen
public void showEditHabitScreen(Habit habit) public void showEditHabitScreen(Habit habit)
{ {
EditBooleanHabitDialog dialog; if(habit.isNumerical())
dialog = editBooleanHabitDialogFactory.create(habit); {
activity.showDialog(dialog, "editHabit"); EditNumericalHabitDialog dialog;
dialog = editNumericalHabitDialogFactory.create(habit);
activity.showDialog(dialog, "editNumericalHabit");
}
else
{
EditBooleanHabitDialog dialog;
dialog = editBooleanHabitDialogFactory.create(habit);
activity.showDialog(dialog, "editHabit");
}
} }
public void showFAQScreen() public void showFAQScreen()

@ -95,6 +95,8 @@ public class HabitCardListView extends RecyclerView
cardView.setButtonCount(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;
} }

@ -101,12 +101,9 @@ public class HabitCardView extends FrameLayout
numberPanel.setButtonCount(buttonCount); numberPanel.setButtonCount(buttonCount);
} }
public void setValues(int values[]) public void setThreshold(double threshold)
{ {
checkmarkPanel.setValues(values); numberPanel.setThreshold(threshold);
numberPanel.setValues(values);
numberPanel.setThreshold(10);
postInvalidate();
} }
public void setController(Controller controller) public void setController(Controller controller)
@ -153,6 +150,19 @@ public class HabitCardView extends FrameLayout
updateBackground(isSelected); updateBackground(isSelected);
} }
public void setUnit(String unit)
{
numberPanel.setUnit(unit);
}
public void setValues(int values[])
{
checkmarkPanel.setValues(values);
numberPanel.setValues(values);
numberPanel.setThreshold(10);
postInvalidate();
}
public void triggerRipple(long timestamp) public void triggerRipple(long timestamp)
{ {
long today = DateUtils.getStartOfToday(); long today = DateUtils.getStartOfToday();

@ -46,7 +46,7 @@ public class NumberButtonView extends View
private int value; private int value;
private int threshold; private double threshold;
private String unit; private String unit;
@ -86,6 +86,11 @@ public class NumberButtonView extends View
} }
} }
/**
*
* @param v
* @return
*/
private static String formatValue(int v) private static String formatValue(int v)
{ {
double fv = (double) v; double fv = (double) v;
@ -111,7 +116,7 @@ public class NumberButtonView extends View
setOnLongClickListener(v -> controller.onLongClick()); setOnLongClickListener(v -> controller.onLongClick());
} }
public void setThreshold(int threshold) public void setThreshold(double threshold)
{ {
this.threshold = threshold; this.threshold = threshold;
postInvalidate(); postInvalidate();
@ -146,10 +151,9 @@ public class NumberButtonView extends View
String fv = formatValue(value); String fv = formatValue(value);
rect.set(0, 0, getWidth(), getHeight()); rect.set(0, 0, getWidth(), getHeight());
rect.offset(0, - 0.1f * em);
canvas.drawText(fv, rect.centerX(), rect.centerY(), pBold); canvas.drawText(fv, rect.centerX(), rect.centerY(), pBold);
rect.offset(0, 1.25f * em); rect.offset(0, 1.2f * em);
canvas.drawText(unit, rect.centerX(), rect.centerY(), pRegular); canvas.drawText(unit, rect.centerX(), rect.centerY(), pRegular);
} }

@ -49,7 +49,7 @@ public class NumberPanelView extends LinearLayout
private int values[]; private int values[];
private int threshold; private double threshold;
private int nButtons; private int nButtons;
@ -98,7 +98,7 @@ public class NumberPanelView extends LinearLayout
int values[] = new int[nButtons]; int values[] = new int[nButtons];
for(int i = 0; i < nButtons; i++) for(int i = 0; i < nButtons; i++)
values[i] = new Random().nextInt(threshold * 3); values[i] = new Random().nextInt((int)(threshold * 3));
setValues(values); setValues(values);
} }
@ -153,7 +153,7 @@ public class NumberPanelView extends LinearLayout
setupButtons(); setupButtons();
} }
public void setThreshold(int threshold) public void setThreshold(double threshold)
{ {
this.threshold = threshold; this.threshold = threshold;
setupButtons(); setupButtons();

@ -33,6 +33,10 @@ import javax.inject.*;
*/ */
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";
@ -52,10 +56,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
@ -64,17 +66,24 @@ 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;
private int type;
private ModelObservable observable = new ModelObservable(); private ModelObservable observable = new ModelObservable();
/** /**
@ -90,6 +99,11 @@ public class Habit
this.archived = false; this.archived = false;
this.frequency = new Frequency(3, 7); this.frequency = new Frequency(3, 7);
this.type = YES_NO_HABIT; this.type = YES_NO_HABIT;
this.name = "";
this.description = "";
this.targetType = AT_LEAST;
this.targetValue = 0;
this.unit = "";
checkmarks = factory.buildCheckmarkList(this); checkmarks = factory.buildCheckmarkList(this);
streaks = factory.buildStreakList(this); streaks = factory.buildStreakList(this);
@ -120,6 +134,9 @@ public class Habit
this.frequency = model.frequency; this.frequency = model.frequency;
this.reminder = model.reminder; this.reminder = model.reminder;
this.type = model.type; this.type = model.type;
this.targetValue = model.targetValue;
this.targetType = model.targetType;
this.unit = model.unit;
observable.notifyListeners(); observable.notifyListeners();
} }
@ -240,6 +257,29 @@ 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() public int getType()
{ {
return type; return type;
@ -253,6 +293,17 @@ public class Habit
this.type = type; 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
* *
@ -306,6 +357,9 @@ public class Habit
.append("color", color) .append("color", color)
.append("archived", archived) .append("archived", archived)
.append("type", type) .append("type", type)
.append("targetType", targetType)
.append("targetValue", targetValue)
.append("unit", unit)
.toString(); .toString();
} }
} }

@ -42,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, type 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;
@ -83,6 +84,15 @@ public class HabitRecord extends Model implements SQLiteRecord
@Column(name = "type") @Column(name = "type")
public Integer 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()
{ {
} }
@ -148,6 +158,9 @@ public class HabitRecord extends Model implements SQLiteRecord
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.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();
@ -181,6 +194,9 @@ public class HabitRecord extends Model implements SQLiteRecord
archived = c.getInt(10); archived = c.getInt(10);
reminderDays = c.getInt(11); reminderDays = c.getInt(11);
type = c.getInt(12); 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)
@ -192,6 +208,9 @@ public class HabitRecord extends Model implements SQLiteRecord
habit.setArchived(this.archived != 0); habit.setArchived(this.archived != 0);
habit.setId(this.getId()); habit.setId(this.getId());
habit.setType(this.type); 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)
{ {

@ -16,7 +16,6 @@
~ You should have received a copy of the GNU General Public License along ~ You should have received a copy of the GNU General Public License along
~ with this program. If not, see <http://www.gnu.org/licenses/>. ~ with this program. If not, see <http://www.gnu.org/licenses/>.
--> -->
<LinearLayout <LinearLayout
android:id="@+id/container" android:id="@+id/container"
style="@style/dialogForm" style="@style/dialogForm"
@ -30,56 +29,87 @@
style="@style/dialogFormPanel"> style="@style/dialogFormPanel">
<LinearLayout <LinearLayout
android:id="@+id/namePanel"
style="@style/dialogFormRow"> style="@style/dialogFormRow">
<EditText <android.support.design.widget.TextInputLayout
android:id="@+id/tvName" android:id="@+id/tilName"
style="@style/dialogFormInput" android:layout_width="0dp"
android:hint="@string/name"> android:layout_height="wrap_content"
android:layout_weight="6">
<requestFocus/>
</EditText> <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 <ImageButton
android:id="@+id/buttonPickColor" android:id="@+id/buttonPickColor"
style="@style/dialogFormInputColor" style="@style/dialogFormInputColor"
android:layout_weight="1"
android:contentDescription="@string/color_picker_default_title" android:contentDescription="@string/color_picker_default_title"
android:src="?dialogIconChangeColor"/> android:src="?dialogIconChangeColor"/>
</LinearLayout>
<EditText
android:id="@+id/tvDescription"
style="@style/dialogFormInputMultiline"
android:hint="@string/description_hint"/>
<LinearLayout
android:id="@+id/reminderPanel"
style="@style/dialogFormRow">
<TextView
android:id="@+id/TextView2"
style="@style/dialogFormLabel"
android:text="@string/reminder"/>
<TextView
android:id="@+id/tvReminderTime"
style="@style/dialogFormSpinner"/>
</LinearLayout> </LinearLayout>
<LinearLayout <android.support.design.widget.TextInputLayout
android:id="@+id/llReminderDays" android:layout_width="match_parent"
style="@style/dialogFormRow"> android:layout_height="wrap_content">
<TextView
android:id="@+id/TextView3"
style="@style/dialogFormLabel"
android:text=""/>
<TextView
android:id="@+id/tvReminderDays"
style="@style/dialogFormSpinner"/>
<EditText
android:id="@+id/tvDescription"
style="@style/dialogFormInputMultiline"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="Question"
android:text="e.g. How many steps did you walk today?"
android:textColor="?attr/mediumContrastTextColor"/>
</android.support.design.widget.TextInputLayout>
<LinearLayout style="@style/dialogFormRow">
<Spinner
android:id="@+id/tvTargetType"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="3"
android:entries="@array/targetValues"/>
<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="Count"
android:inputType="numberDecimal"
android:text="100"
/>
</android.support.design.widget.TextInputLayout>
<android.support.design.widget.TextInputLayout
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="2">
<EditText
android:id="@+id/tvUnit"
style="@style/dialogFormInput"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="Unit"
android:text="e.g. steps"
android:textColor="?attr/mediumContrastTextColor"
android:inputType="text"/>
</android.support.design.widget.TextInputLayout>
</LinearLayout> </LinearLayout>
</LinearLayout> </LinearLayout>
@ -87,17 +117,14 @@
style="?android:attr/buttonBarStyle" style="?android:attr/buttonBarStyle"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:gravity="end" android:gravity="end">
android:paddingEnd="16dp"
android:paddingLeft="0dp"
android:paddingRight="16dp"
android:paddingStart="0dp">
<Button <Button
android:id="@+id/buttonDiscard" android:id="@+id/buttonDiscard"
style="?android:attr/buttonBarButtonStyle" style="?android:attr/buttonBarButtonStyle"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_margin="8dp"
android:text="@string/discard"/> android:text="@string/discard"/>
<Button <Button
@ -105,6 +132,7 @@
style="?android:attr/buttonBarButtonStyle" style="?android:attr/buttonBarButtonStyle"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_margin="8dp"
android:text="@string/save"/> android:text="@string/save"/>
</LinearLayout> </LinearLayout>
</LinearLayout> </LinearLayout>

@ -82,5 +82,16 @@
<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>
</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>

@ -81,6 +81,8 @@ public class ListHabitsScreenTest extends BaseUnitTest
private CreateNumericalHabitDialogFactory createNumericalHabitDialogFactory; private CreateNumericalHabitDialogFactory createNumericalHabitDialogFactory;
private EditNumericalHabitDialogFactory editNumericalHabitDialogFactory;
@Before @Before
@Override @Override
public void setUp() public void setUp()
@ -98,12 +100,16 @@ public class ListHabitsScreenTest extends BaseUnitTest
filePickerDialogFactory = mock(FilePickerDialogFactory.class); filePickerDialogFactory = mock(FilePickerDialogFactory.class);
colorPickerDialogFactory = mock(ColorPickerDialogFactory.class); colorPickerDialogFactory = mock(ColorPickerDialogFactory.class);
editHabitDialogFactory = mock(EditBooleanHabitDialogFactory.class); editHabitDialogFactory = mock(EditBooleanHabitDialogFactory.class);
createNumericalHabitDialogFactory = mock(CreateNumericalHabitDialogFactory.class); editNumericalHabitDialogFactory =
mock(EditNumericalHabitDialogFactory.class);
createNumericalHabitDialogFactory =
mock(CreateNumericalHabitDialogFactory.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, createHabitDialogFactory, filePickerDialogFactory,
colorPickerDialogFactory, editHabitDialogFactory, colorPickerDialogFactory, editHabitDialogFactory,
editNumericalHabitDialogFactory,
createNumericalHabitDialogFactory)); createNumericalHabitDialogFactory));
doNothing().when(screen).showMessage(anyInt()); doNothing().when(screen).showMessage(anyInt());
@ -126,6 +132,29 @@ public class ListHabitsScreenTest extends BaseUnitTest
// verify(activity).showDialog(eq(dialog), any()); // verify(activity).showDialog(eq(dialog), any());
// } // }
@Test
public void testOnAttached()
{
screen.onAttached();
verify(commandRunner).addListener(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);
}
@Test
public void testOnDetach()
{
screen.onDettached();
verify(commandRunner).removeListener(screen);
}
@Test @Test
public void testOnResult_bugReport() public void testOnResult_bugReport()
{ {
@ -264,27 +293,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);
}
} }
Loading…
Cancel
Save