Refactor and write documentation for Habit

pull/69/head
Alinson S. Xavier 10 years ago
parent 3d42505fb9
commit 075b7812eb

@ -19,6 +19,7 @@
package org.isoron.uhabits.unit.models;
import android.graphics.Color;
import android.support.test.runner.AndroidJUnit4;
import android.test.suitebuilder.annotation.SmallTest;
@ -31,6 +32,8 @@ import java.util.LinkedList;
import java.util.List;
import static org.hamcrest.Matchers.is;
import static org.hamcrest.Matchers.nullValue;
import static org.hamcrest.core.IsNot.not;
import static org.junit.Assert.assertThat;
@RunWith(AndroidJUnit4.class)
@ -43,6 +46,49 @@ public class HabitTest
HabitFixtures.purgeHabits();
}
@Test
public void constructor_default()
{
Habit habit = new Habit();
assertThat(habit.archived, is(0));
assertThat(habit.highlight, is(0));
assertThat(habit.reminderDays, is(nullValue()));
assertThat(habit.reminderHour, is(nullValue()));
assertThat(habit.reminderMin, is(nullValue()));
assertThat(habit.streaks, is(not(nullValue())));
assertThat(habit.scores, is(not(nullValue())));
assertThat(habit.repetitions, is(not(nullValue())));
assertThat(habit.checkmarks, is(not(nullValue())));
}
@Test
public void constructor_habit()
{
Habit model = new Habit();
model.archived = 1;
model.highlight = 1;
model.color = Color.BLACK;
model.freqNum = 10;
model.freqDen = 20;
model.reminderDays = 1;
model.reminderHour = 8;
model.reminderMin = 30;
model.position = 0;
Habit habit = new Habit(model);
assertThat(habit.archived, is(model.archived));
assertThat(habit.highlight, is(model.highlight));
assertThat(habit.color, is(model.color));
assertThat(habit.freqNum, is(model.freqNum));
assertThat(habit.freqDen, is(model.freqDen));
assertThat(habit.reminderDays, is(model.reminderDays));
assertThat(habit.reminderHour, is(model.reminderHour));
assertThat(habit.reminderMin, is(model.reminderMin));
assertThat(habit.position, is(model.position));
}
@Test
public void reorderTest()
{

@ -32,6 +32,8 @@ import java.util.TimeZone;
public class DateHelper
{
public static long millisecondsInOneDay = 24 * 60 * 60 * 1000;
public static int ALL_WEEK_DAYS = 127;
private static Long fixedLocalTime = null;
public static long getLocalTime()

@ -32,6 +32,7 @@ import android.view.View;
import android.view.inputmethod.InputMethodManager;
import org.isoron.uhabits.BuildConfig;
import org.isoron.uhabits.commands.Command;
public abstract class DialogHelper
{

@ -26,6 +26,7 @@ import android.os.Bundle;
import android.widget.Toast;
import org.isoron.uhabits.R;
import org.isoron.uhabits.commands.Command;
import java.util.LinkedList;

@ -19,7 +19,6 @@
package org.isoron.uhabits.commands;
import org.isoron.helpers.Command;
import org.isoron.uhabits.R;
import org.isoron.uhabits.models.Habit;
@ -45,15 +44,13 @@ public class ArchiveHabitsCommand extends Command
@Override
public void execute()
{
for(Habit h : habits)
h.archive();
Habit.archive(habits);
}
@Override
public void undo()
{
for(Habit h : habits)
h.unarchive();
Habit.unarchive(habits);
}
public Integer getExecuteStringId()

@ -21,7 +21,6 @@ package org.isoron.uhabits.commands;
import com.activeandroid.ActiveAndroid;
import org.isoron.helpers.Command;
import org.isoron.uhabits.R;
import org.isoron.uhabits.models.Habit;
@ -47,22 +46,7 @@ public class ChangeHabitColorCommand extends Command
@Override
public void execute()
{
ActiveAndroid.beginTransaction();
try
{
for(Habit h : habits)
{
h.color = newColor;
h.save();
}
ActiveAndroid.setTransactionSuccessful();
}
finally
{
ActiveAndroid.endTransaction();
}
Habit.setColor(habits, newColor);
}
@Override

@ -17,7 +17,7 @@
* with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.isoron.helpers;
package org.isoron.uhabits.commands;
public abstract class Command
{

@ -0,0 +1,33 @@
/*
* 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;
public class CommandFailedException extends RuntimeException
{
public CommandFailedException()
{
super();
}
public CommandFailedException(String message)
{
super(message);
}
}

@ -19,7 +19,6 @@
package org.isoron.uhabits.commands;
import org.isoron.helpers.Command;
import org.isoron.uhabits.R;
import org.isoron.uhabits.models.Habit;
@ -51,7 +50,10 @@ public class CreateHabitCommand extends Command
@Override
public void undo()
{
Habit.get(savedId).delete();
Habit habit = Habit.get(savedId);
if(habit == null) throw new CommandFailedException("Habit not found");
habit.delete();
}
@Override

@ -19,7 +19,6 @@
package org.isoron.uhabits.commands;
import org.isoron.helpers.Command;
import org.isoron.uhabits.R;
import org.isoron.uhabits.models.Habit;

@ -19,7 +19,6 @@
package org.isoron.uhabits.commands;
import org.isoron.helpers.Command;
import org.isoron.uhabits.R;
import org.isoron.uhabits.models.Habit;
@ -43,6 +42,8 @@ public class EditHabitCommand extends Command
public void execute()
{
Habit habit = Habit.get(savedId);
if(habit == null) throw new CommandFailedException("Habit not found");
habit.copyAttributes(modified);
habit.save();
if (hasIntervalChanged)
@ -56,6 +57,8 @@ public class EditHabitCommand extends Command
public void undo()
{
Habit habit = Habit.get(savedId);
if(habit == null) throw new CommandFailedException("Habit not found");
habit.copyAttributes(original);
habit.save();
if (hasIntervalChanged)

@ -19,7 +19,6 @@
package org.isoron.uhabits.commands;
import org.isoron.helpers.Command;
import org.isoron.uhabits.models.Habit;
public class ToggleRepetitionCommand extends Command

@ -19,7 +19,6 @@
package org.isoron.uhabits.commands;
import org.isoron.helpers.Command;
import org.isoron.uhabits.R;
import org.isoron.uhabits.models.Habit;
@ -45,15 +44,13 @@ public class UnarchiveHabitsCommand extends Command
@Override
public void execute()
{
for(Habit h : habits)
h.unarchive();
Habit.unarchive(habits);
}
@Override
public void undo()
{
for(Habit h : habits)
h.archive();
Habit.archive(habits);
}
public Integer getExecuteStringId()

@ -39,16 +39,17 @@ import com.android.datetimepicker.time.RadialPickerLayout;
import com.android.datetimepicker.time.TimePickerDialog;
import org.isoron.helpers.ColorHelper;
import org.isoron.helpers.Command;
import org.isoron.helpers.DateHelper;
import org.isoron.helpers.DialogHelper.OnSavedListener;
import org.isoron.uhabits.R;
import org.isoron.uhabits.commands.Command;
import org.isoron.uhabits.commands.CreateHabitCommand;
import org.isoron.uhabits.commands.EditHabitCommand;
import org.isoron.uhabits.dialogs.WeekdayPickerDialog;
import org.isoron.uhabits.models.Habit;
import java.util.Arrays;
import java.util.Date;
public class EditHabitFragment extends DialogFragment
implements OnClickListener, WeekdayPickerDialog.OnWeekdaysPickedListener,
@ -136,7 +137,10 @@ public class EditHabitFragment extends DialogFragment
}
else if (mode == EDIT_MODE)
{
originalHabit = Habit.get((Long) args.get("habitId"));
Long habitId = (Long) args.get("habitId");
if(habitId == null) throw new IllegalArgumentException("habitId must be specified");
originalHabit = Habit.get(habitId);
modifiedHabit = new Habit(originalHabit);
getDialog().setTitle(R.string.edit_habit);
@ -178,14 +182,18 @@ public class EditHabitFragment extends DialogFragment
editor.apply();
}
@SuppressWarnings("ConstantConditions")
private void updateReminder()
{
if (modifiedHabit.reminderHour != null)
if (modifiedHabit.hasReminder())
{
tvReminderTime.setTextColor(Color.BLACK);
tvReminderTime.setText(DateHelper.formatTime(getActivity(), modifiedHabit.reminderHour,
modifiedHabit.reminderMin));
tvReminderDays.setVisibility(View.VISIBLE);
boolean weekdays[] = DateHelper.unpackWeekdayList(modifiedHabit.reminderDays);
tvReminderDays.setText(DateHelper.formatWeekdayList(getActivity(), weekdays));
}
else
{
@ -193,9 +201,6 @@ public class EditHabitFragment extends DialogFragment
tvReminderTime.setText(R.string.reminder_off);
tvReminderDays.setVisibility(View.GONE);
}
boolean weekdays[] = DateHelper.unpackWeekdayList(modifiedHabit.reminderDays);
tvReminderDays.setText(DateHelper.formatWeekdayList(getActivity(), weekdays));
}
public void setOnSavedListener(OnSavedListener onSavedListener)
@ -303,12 +308,13 @@ public class EditHabitFragment extends DialogFragment
return valid;
}
@SuppressWarnings("ConstantConditions")
private void onDateSpinnerClick()
{
int defaultHour = 8;
int defaultMin = 0;
if (modifiedHabit.reminderHour != null)
if (modifiedHabit.hasReminder())
{
defaultHour = modifiedHabit.reminderHour;
defaultMin = modifiedHabit.reminderMin;
@ -319,8 +325,11 @@ public class EditHabitFragment extends DialogFragment
timePicker.show(getFragmentManager(), "timePicker");
}
@SuppressWarnings("ConstantConditions")
private void onWeekdayClick()
{
if(!modifiedHabit.hasReminder()) return;
WeekdayPickerDialog dialog = new WeekdayPickerDialog();
dialog.setListener(this);
dialog.setSelectedDays(DateHelper.unpackWeekdayList(modifiedHabit.reminderDays));
@ -332,14 +341,14 @@ public class EditHabitFragment extends DialogFragment
{
modifiedHabit.reminderHour = hour;
modifiedHabit.reminderMin = minute;
modifiedHabit.reminderDays = DateHelper.ALL_WEEK_DAYS;
updateReminder();
}
@Override
public void onTimeCleared(RadialPickerLayout view)
{
modifiedHabit.reminderHour = null;
modifiedHabit.reminderMin = null;
modifiedHabit.clearReminder();
updateReminder();
}
@ -357,12 +366,14 @@ public class EditHabitFragment extends DialogFragment
}
@Override
@SuppressWarnings("ConstantConditions")
public void onSaveInstanceState(Bundle outState)
{
super.onSaveInstanceState(outState);
outState.putInt("color", modifiedHabit.color);
if(modifiedHabit.reminderHour != null)
if(modifiedHabit.hasReminder())
{
outState.putInt("reminderMin", modifiedHabit.reminderMin);
outState.putInt("reminderHour", modifiedHabit.reminderHour);

@ -46,7 +46,7 @@ import com.mobeta.android.dslv.DragSortController;
import com.mobeta.android.dslv.DragSortListView;
import com.mobeta.android.dslv.DragSortListView.DropListener;
import org.isoron.helpers.Command;
import org.isoron.uhabits.commands.Command;
import org.isoron.helpers.DateHelper;
import org.isoron.helpers.DialogHelper;
import org.isoron.helpers.DialogHelper.OnSavedListener;

@ -33,7 +33,7 @@ import android.widget.LinearLayout;
import android.widget.TextView;
import org.isoron.helpers.ColorHelper;
import org.isoron.helpers.Command;
import org.isoron.uhabits.commands.Command;
import org.isoron.helpers.DialogHelper;
import org.isoron.uhabits.HabitBroadcastReceiver;
import org.isoron.uhabits.R;

@ -25,6 +25,7 @@ import android.content.Context;
import android.content.Intent;
import android.net.Uri;
import android.os.Build;
import android.support.annotation.Nullable;
import android.util.Log;
import org.isoron.helpers.DateHelper;
@ -43,13 +44,17 @@ public class ReminderHelper
createReminderAlarm(context, habit, null);
}
public static void createReminderAlarm(Context context, Habit habit, Long reminderTime)
public static void createReminderAlarm(Context context, Habit habit, @Nullable Long reminderTime)
{
if(!habit.hasReminder()) return;
if (reminderTime == null)
{
Calendar calendar = Calendar.getInstance();
calendar.setTimeInMillis(System.currentTimeMillis());
//noinspection ConstantConditions
calendar.set(Calendar.HOUR_OF_DAY, habit.reminderHour);
//noinspection ConstantConditions
calendar.set(Calendar.MINUTE, habit.reminderMin);
calendar.set(Calendar.SECOND, 0);

@ -21,6 +21,8 @@ package org.isoron.uhabits.models;
import android.annotation.SuppressLint;
import android.net.Uri;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import com.activeandroid.ActiveAndroid;
import com.activeandroid.Model;
@ -39,50 +41,113 @@ import java.util.List;
@Table(name = "Habits")
public class Habit extends Model
{
/**
* Name of the habit
*/
@Column(name = "name")
public String name;
/**
* Description of the habit
*/
@Column(name = "description")
public String description;
/**
* Frequency numerator. If a habit is performed 3 times in 7 days, this field equals 3.
*/
@Column(name = "freq_num")
public Integer freqNum;
/**
* Frequency denominator. If a habit is performed 3 times in 7 days, this field equals 7.
*/
@Column(name = "freq_den")
public Integer freqDen;
/**
* Color of the habit. The format is the same as android.graphics.Color.
*/
@Column(name = "color")
public Integer color;
/**
* Position of the habit. Habits are usually sorted by this field.
*/
@Column(name = "position")
public Integer position;
/**
* Hour of the day the reminder should be shown. If there is no reminder, this equals to null.
*/
@Nullable
@Column(name = "reminder_hour")
public Integer reminderHour;
/**
* Minute the reminder should be shown. If there is no reminder, this equals to null.
*/
@Nullable
@Column(name = "reminder_min")
public Integer reminderMin;
/**
* Days of the week the reminder should be shown. This field can be converted to a list of
* booleans using the method DateHelper.unpackWeekdayList and converted back to an integer by
* using the method DateHelper.packWeekdayList. If there is no reminder, it equals null.
*/
@Nullable
@Column(name = "reminder_days")
public Integer reminderDays;
/**
* Not currently used.
*/
@Column(name = "highlight")
public Integer highlight;
/**
* Flag that indicates whether the habit is archived. Archived habits are usually omitted from
* listings, unless explicitly included.
*/
@Column(name = "archived")
public Integer archived;
/**
* List of streaks belonging to this habit.
*/
public StreakList streaks;
/**
* List of scores belonging to this habit.
*/
public ScoreList scores;
/**
* List of repetitions belonging to this habit.
*/
public RepetitionList repetitions;
/**
* List of checkmarks belonging to this habit.
*/
public CheckmarkList checkmarks;
/**
* Constructs a habit with the same attributes as the specified habit.
*
* @param model the model whose attributes should be copied from
*/
public Habit(Habit model)
{
copyAttributes(model);
initializeLists();
}
/**
* Constructs a habit with default attributes. The habit is not archived, not highlighted, has
* no reminders and is placed in the last position of the list of habits.
*/
public Habit()
{
this.color = ColorHelper.palette[5];
@ -91,7 +156,6 @@ public class Habit extends Model
this.archived = 0;
this.freqDen = 7;
this.freqNum = 3;
this.reminderDays = 127;
initializeLists();
}
@ -103,17 +167,36 @@ public class Habit extends Model
checkmarks = new CheckmarkList(this);
}
public static Habit get(Long id)
/**
* Returns the habit with specified id.
*
* @param id the id of the habit
* @return the habit, or null if none exist
*/
@Nullable
public static Habit get(@NonNull Long id)
{
return Habit.load(Habit.class, id);
}
/**
* Returns a list of all habits, optionally including archived habits.
*
* @param includeArchive whether archived habits should be included the list
* @return list of all habits
*/
public static List<Habit> getAll(boolean includeArchive)
{
if(includeArchive) return selectWithArchived().execute();
else return select().execute();
}
/**
* Changes the id of a habit on the database.
*
* @param oldId the original id
* @param newId the new id
*/
@SuppressLint("DefaultLocale")
public static void updateId(long oldId, long newId)
{
@ -125,21 +208,32 @@ public class Habit extends Model
return new Select().from(Habit.class).where("archived = 0").orderBy("position");
}
public static From selectWithArchived()
protected static From selectWithArchived()
{
return new Select().from(Habit.class).orderBy("position");
}
/**
* Returns the total number of unarchived habits.
*
* @return number of unarchived habits
*/
public static int count()
{
return select().count();
}
/**
* Returns the total number of habits, including archived habits.
*
* @return number of habits, including archived
*/
public static int countWithArchived()
{
return selectWithArchived().count();
}
public static java.util.List<Habit> getHighlightedHabits()
{
return select().where("highlight = 1")
@ -147,11 +241,22 @@ public class Habit extends Model
.execute();
}
public static java.util.List<Habit> getHabitsWithReminder()
/**
* Returns a list the habits that have a reminder. Does not include archived habits.
*
* @return list of habits with reminder
*/
public static List<Habit> getHabitsWithReminder()
{
return select().where("reminder_hour is not null").execute();
}
/**
* Changes the position of a habit in the list.
*
* @param from the habit that should be moved
* @param to the habit that currently occupies the desired position
*/
public static void reorder(Habit from, Habit to)
{
if(from == to) return;
@ -173,6 +278,10 @@ public class Habit extends Model
from.save();
}
/**
* Recompute the field position for every habit in the database. It should never be necessary
* to call this method.
*/
public static void rebuildOrder()
{
List<Habit> habits = selectWithArchived().execute();
@ -196,6 +305,11 @@ public class Habit extends Model
}
/**
* Copies all the attributes of the specified habit into this habit
*
* @param model the model whose attributes should be copied from
*/
public void copyAttributes(Habit model)
{
this.name = model.name;
@ -211,12 +325,21 @@ public class Habit extends Model
this.archived = model.archived;
}
/**
* Saves the habit on the database, and assigns the specified id to it.
*
* @param id the id that the habit should receive
*/
public void save(Long id)
{
save();
Habit.updateId(getId(), id);
}
/**
* Deletes the habit and all data associated to it, including checkmarks, repetitions and
* scores.
*/
public void cascadeDelete()
{
Long id = getId();
@ -238,25 +361,93 @@ public class Habit extends Model
}
}
/**
* Returns the public URI that identifies this habit
* @return the uri
*/
public Uri getUri()
{
return Uri.parse(String.format("content://org.isoron.uhabits/habit/%d", getId()));
}
public void archive()
/**
* Returns whether the habit is archived or not.
* @return true if archived
*/
public boolean isArchived()
{
archived = 1;
save();
return archived != 0;
}
public void unarchive()
private static void updateAttributes(List<Habit> habits, Integer color, Integer archived)
{
archived = 0;
save();
ActiveAndroid.beginTransaction();
try
{
for (Habit h : habits)
{
if(color != null) h.color = color;
if(archived != null) h.archived = archived;
h.save();
}
public boolean isArchived()
ActiveAndroid.setTransactionSuccessful();
}
finally
{
return archived != 0;
ActiveAndroid.endTransaction();
}
}
/**
* Archives an entire list of habits
*
* @param habits the habits to be archived
*/
public static void archive(List<Habit> habits)
{
updateAttributes(habits, null, 1);
}
/**
* Unarchives an entire list of habits
*
* @param habits the habits to be unarchived
*/
public static void unarchive(List<Habit> habits)
{
updateAttributes(habits, null, 0);
}
/**
* Sets the color for an entire list of habits.
*
* @param habits the habits to be modified
* @param color the new color to be set
*/
public static void setColor(List<Habit> habits, int color)
{
updateAttributes(habits, color, null);
}
/**
* Checks whether the habit has a reminder set.
*
* @return true if habit has reminder
*/
public boolean hasReminder()
{
return (reminderHour != null && reminderMin != null && reminderDays != null);
}
/**
* Clears the reminder for a habit. This sets all the related fields to null.
*/
public void clearReminder()
{
reminderHour = null;
reminderMin = null;
reminderDays = null;
}
}

Loading…
Cancel
Save