mirror of
https://github.com/iSoron/uhabits.git
synced 2025-12-06 09:08:52 -06:00
Move existing source code to subfolder
This commit is contained in:
@@ -1,25 +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.core;
|
||||
|
||||
import javax.inject.*;
|
||||
|
||||
@Scope
|
||||
public @interface AppScope {}
|
||||
@@ -1,26 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2017 Á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.core;
|
||||
|
||||
public class Config
|
||||
{
|
||||
public static final String DATABASE_FILENAME = "uhabits.db";
|
||||
public static int DATABASE_VERSION = 22;
|
||||
}
|
||||
@@ -1,99 +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.core.commands;
|
||||
|
||||
import android.support.annotation.*;
|
||||
|
||||
import org.isoron.uhabits.core.models.*;
|
||||
|
||||
import java.util.*;
|
||||
|
||||
/**
|
||||
* Command to archive a list of habits.
|
||||
*/
|
||||
public class ArchiveHabitsCommand extends Command
|
||||
{
|
||||
final List<Habit> selected;
|
||||
|
||||
final HabitList habitList;
|
||||
|
||||
public ArchiveHabitsCommand(@NonNull HabitList habitList,
|
||||
@NonNull List<Habit> selected)
|
||||
{
|
||||
super();
|
||||
this.habitList = habitList;
|
||||
this.selected = new LinkedList<>(selected);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void execute()
|
||||
{
|
||||
for (Habit h : selected) h.setArchived(true);
|
||||
habitList.update(selected);
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public Record toRecord()
|
||||
{
|
||||
return new Record(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void undo()
|
||||
{
|
||||
for (Habit h : selected) h.setArchived(false);
|
||||
habitList.update(selected);
|
||||
}
|
||||
|
||||
public static class Record
|
||||
{
|
||||
@NonNull
|
||||
public final String id;
|
||||
|
||||
@NonNull
|
||||
public final String event = "Archive";
|
||||
|
||||
@NonNull
|
||||
public final List<Long> habits;
|
||||
|
||||
public Record(@NonNull ArchiveHabitsCommand command)
|
||||
{
|
||||
id = command.getId();
|
||||
habits = new LinkedList<>();
|
||||
for (Habit h : command.selected)
|
||||
{
|
||||
habits.add(h.getId());
|
||||
}
|
||||
}
|
||||
|
||||
@NonNull
|
||||
public ArchiveHabitsCommand toCommand(@NonNull HabitList habitList)
|
||||
{
|
||||
List<Habit> selected = new LinkedList<>();
|
||||
for (Long id : this.habits) selected.add(habitList.getById(id));
|
||||
|
||||
ArchiveHabitsCommand command;
|
||||
command = new ArchiveHabitsCommand(habitList, selected);
|
||||
command.setId(id);
|
||||
return command;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,115 +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.core.commands;
|
||||
|
||||
import android.support.annotation.*;
|
||||
|
||||
import org.isoron.uhabits.core.models.*;
|
||||
|
||||
import java.util.*;
|
||||
|
||||
/**
|
||||
* Command to change the color of a list of habits.
|
||||
*/
|
||||
public class ChangeHabitColorCommand extends Command
|
||||
{
|
||||
@NonNull
|
||||
final HabitList habitList;
|
||||
|
||||
@NonNull
|
||||
final List<Habit> selected;
|
||||
|
||||
@NonNull
|
||||
final List<Integer> originalColors;
|
||||
|
||||
@NonNull
|
||||
final Integer newColor;
|
||||
|
||||
public ChangeHabitColorCommand(@NonNull HabitList habitList,
|
||||
@NonNull List<Habit> selected,
|
||||
@NonNull Integer newColor)
|
||||
{
|
||||
this.habitList = habitList;
|
||||
this.selected = selected;
|
||||
this.newColor = newColor;
|
||||
this.originalColors = new ArrayList<>(selected.size());
|
||||
for (Habit h : selected) originalColors.add(h.getColor());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void execute()
|
||||
{
|
||||
for (Habit h : selected) h.setColor(newColor);
|
||||
habitList.update(selected);
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public Record toRecord()
|
||||
{
|
||||
return new Record(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void undo()
|
||||
{
|
||||
int k = 0;
|
||||
for (Habit h : selected) h.setColor(originalColors.get(k++));
|
||||
habitList.update(selected);
|
||||
}
|
||||
|
||||
public static class Record
|
||||
{
|
||||
@NonNull
|
||||
public String id;
|
||||
|
||||
@NonNull
|
||||
public String event = "ChangeColor";
|
||||
|
||||
@NonNull
|
||||
public List<Long> habits;
|
||||
|
||||
@NonNull
|
||||
public Integer color;
|
||||
|
||||
public Record(ChangeHabitColorCommand command)
|
||||
{
|
||||
id = command.getId();
|
||||
color = command.newColor;
|
||||
habits = new LinkedList<>();
|
||||
for (Habit h : command.selected)
|
||||
{
|
||||
if (!h.hasId()) throw new RuntimeException("Habit not saved");
|
||||
habits.add(h.getId());
|
||||
}
|
||||
}
|
||||
|
||||
public ChangeHabitColorCommand toCommand(@NonNull HabitList habitList)
|
||||
{
|
||||
List<Habit> selected = new LinkedList<>();
|
||||
for (Long id : this.habits) selected.add(habitList.getById(id));
|
||||
|
||||
ChangeHabitColorCommand command;
|
||||
command = new ChangeHabitColorCommand(habitList, selected, color);
|
||||
command.setId(id);
|
||||
return command;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,87 +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.core.commands;
|
||||
|
||||
import android.support.annotation.*;
|
||||
|
||||
import com.google.gson.*;
|
||||
|
||||
import org.isoron.uhabits.core.utils.*;
|
||||
|
||||
/**
|
||||
* A Command represents a desired set of changes that should be performed on the
|
||||
* models.
|
||||
* <p>
|
||||
* A command can be executed and undone. Each of these operations also provide
|
||||
* an string that should be displayed to the user upon their completion.
|
||||
* <p>
|
||||
* In general, commands should always be executed by a {@link CommandRunner}.
|
||||
*/
|
||||
public abstract class Command
|
||||
{
|
||||
private String id;
|
||||
|
||||
private boolean isRemote;
|
||||
|
||||
public Command()
|
||||
{
|
||||
id = StringUtils.getRandomId();
|
||||
isRemote = false;
|
||||
}
|
||||
|
||||
public Command(String id)
|
||||
{
|
||||
this.id = id;
|
||||
isRemote = false;
|
||||
}
|
||||
|
||||
public abstract void execute();
|
||||
|
||||
public String getId()
|
||||
{
|
||||
return id;
|
||||
}
|
||||
|
||||
public void setId(String id)
|
||||
{
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
public boolean isRemote()
|
||||
{
|
||||
return isRemote;
|
||||
}
|
||||
|
||||
public void setRemote(boolean remote)
|
||||
{
|
||||
isRemote = remote;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
public String toJson()
|
||||
{
|
||||
return new GsonBuilder().create().toJson(toRecord());
|
||||
}
|
||||
|
||||
@NonNull
|
||||
public abstract Object toRecord();
|
||||
|
||||
public abstract void undo();
|
||||
}
|
||||
@@ -1,85 +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.core.commands;
|
||||
|
||||
import android.support.annotation.*;
|
||||
|
||||
import com.google.gson.*;
|
||||
|
||||
import org.isoron.uhabits.core.models.*;
|
||||
|
||||
import javax.inject.*;
|
||||
|
||||
public class CommandParser
|
||||
{
|
||||
private HabitList habitList;
|
||||
|
||||
private ModelFactory modelFactory;
|
||||
|
||||
@Inject
|
||||
public CommandParser(@NonNull HabitList habitList,
|
||||
@NonNull ModelFactory modelFactory)
|
||||
{
|
||||
this.habitList = habitList;
|
||||
this.modelFactory = modelFactory;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
public Command parse(@NonNull String json)
|
||||
{
|
||||
JsonObject parsed = new JsonParser().parse(json).getAsJsonObject();
|
||||
String event = parsed.get("event").getAsString();
|
||||
Gson gson = new GsonBuilder().create();
|
||||
|
||||
if (event.equals("Archive")) return gson
|
||||
.fromJson(json, ArchiveHabitsCommand.Record.class)
|
||||
.toCommand(habitList);
|
||||
|
||||
if (event.equals("ChangeColor")) return gson
|
||||
.fromJson(json, ChangeHabitColorCommand.Record.class)
|
||||
.toCommand(habitList);
|
||||
|
||||
if (event.equals("CreateHabit")) return gson
|
||||
.fromJson(json, CreateHabitCommand.Record.class)
|
||||
.toCommand(modelFactory, habitList);
|
||||
|
||||
if (event.equals("CreateRep")) return gson
|
||||
.fromJson(json, CreateRepetitionCommand.Record.class)
|
||||
.toCommand(habitList);
|
||||
|
||||
if (event.equals("DeleteHabit")) return gson
|
||||
.fromJson(json, DeleteHabitsCommand.Record.class)
|
||||
.toCommand(habitList);
|
||||
|
||||
if (event.equals("EditHabit")) return gson
|
||||
.fromJson(json, EditHabitCommand.Record.class)
|
||||
.toCommand(modelFactory, habitList);
|
||||
|
||||
if (event.equals("Toggle")) return gson
|
||||
.fromJson(json, ToggleRepetitionCommand.Record.class)
|
||||
.toCommand(habitList);
|
||||
|
||||
if (event.equals("Unarchive")) return gson
|
||||
.fromJson(json, UnarchiveHabitsCommand.Record.class)
|
||||
.toCommand(habitList);
|
||||
|
||||
throw new IllegalStateException("Unknown command");
|
||||
}
|
||||
}
|
||||
@@ -1,89 +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.core.commands;
|
||||
|
||||
import android.support.annotation.*;
|
||||
|
||||
import org.isoron.uhabits.core.*;
|
||||
import org.isoron.uhabits.core.tasks.*;
|
||||
|
||||
import java.util.*;
|
||||
|
||||
import javax.inject.*;
|
||||
|
||||
/**
|
||||
* A CommandRunner executes and undoes commands.
|
||||
* <p>
|
||||
* CommandRunners also allows objects to subscribe to it, and receive events
|
||||
* whenever a command is performed.
|
||||
*/
|
||||
@AppScope
|
||||
public class CommandRunner
|
||||
{
|
||||
private TaskRunner taskRunner;
|
||||
|
||||
private LinkedList<Listener> listeners;
|
||||
|
||||
@Inject
|
||||
public CommandRunner(@NonNull TaskRunner taskRunner)
|
||||
{
|
||||
this.taskRunner = taskRunner;
|
||||
listeners = new LinkedList<>();
|
||||
}
|
||||
|
||||
public void addListener(Listener l)
|
||||
{
|
||||
listeners.add(l);
|
||||
}
|
||||
|
||||
public void execute(final Command command, final Long refreshKey)
|
||||
{
|
||||
taskRunner.execute(new Task()
|
||||
{
|
||||
@Override
|
||||
public void doInBackground()
|
||||
{
|
||||
command.execute();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPostExecute()
|
||||
{
|
||||
for (Listener l : listeners)
|
||||
l.onCommandExecuted(command, refreshKey);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public void removeListener(Listener l)
|
||||
{
|
||||
listeners.remove(l);
|
||||
}
|
||||
|
||||
/**
|
||||
* Interface implemented by objects that want to receive an event whenever a
|
||||
* command is executed.
|
||||
*/
|
||||
public interface Listener
|
||||
{
|
||||
void onCommandExecuted(@NonNull Command command,
|
||||
@Nullable Long refreshKey);
|
||||
}
|
||||
}
|
||||
@@ -1,115 +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.core.commands;
|
||||
|
||||
import android.support.annotation.*;
|
||||
|
||||
import com.google.auto.factory.*;
|
||||
|
||||
import org.isoron.uhabits.core.models.*;
|
||||
|
||||
/**
|
||||
* Command to create a habit.
|
||||
*/
|
||||
@AutoFactory
|
||||
public class CreateHabitCommand extends Command
|
||||
{
|
||||
ModelFactory modelFactory;
|
||||
|
||||
HabitList habitList;
|
||||
|
||||
@NonNull
|
||||
Habit model;
|
||||
|
||||
@Nullable
|
||||
Long savedId;
|
||||
|
||||
public CreateHabitCommand(@Provided @NonNull ModelFactory modelFactory,
|
||||
@NonNull HabitList habitList,
|
||||
@NonNull Habit model)
|
||||
{
|
||||
this.modelFactory = modelFactory;
|
||||
this.habitList = habitList;
|
||||
this.model = model;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void execute()
|
||||
{
|
||||
Habit savedHabit = modelFactory.buildHabit();
|
||||
savedHabit.copyFrom(model);
|
||||
savedHabit.setId(savedId);
|
||||
|
||||
habitList.add(savedHabit);
|
||||
savedId = savedHabit.getId();
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public Record toRecord()
|
||||
{
|
||||
return new Record(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void undo()
|
||||
{
|
||||
if (savedId == null) throw new IllegalStateException();
|
||||
|
||||
Habit habit = habitList.getById(savedId);
|
||||
if (habit == null) throw new HabitNotFoundException();
|
||||
|
||||
habitList.remove(habit);
|
||||
}
|
||||
|
||||
public static class Record
|
||||
{
|
||||
@NonNull
|
||||
public String id;
|
||||
|
||||
@NonNull
|
||||
public String event = "CreateHabit";
|
||||
|
||||
@NonNull
|
||||
public Habit.HabitData habit;
|
||||
|
||||
@Nullable
|
||||
public Long savedId;
|
||||
|
||||
public Record(CreateHabitCommand command)
|
||||
{
|
||||
id = command.getId();
|
||||
habit = command.model.getData();
|
||||
savedId = command.savedId;
|
||||
}
|
||||
|
||||
public CreateHabitCommand toCommand(@NonNull ModelFactory modelFactory,
|
||||
@NonNull HabitList habitList)
|
||||
{
|
||||
Habit h = modelFactory.buildHabit(habit);
|
||||
|
||||
CreateHabitCommand command;
|
||||
command = new CreateHabitCommand(modelFactory, habitList, h);
|
||||
command.savedId = savedId;
|
||||
command.setId(id);
|
||||
return command;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,127 +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.core.commands;
|
||||
|
||||
import android.support.annotation.*;
|
||||
|
||||
import org.isoron.uhabits.core.models.*;
|
||||
|
||||
/**
|
||||
* Command to toggle a repetition.
|
||||
*/
|
||||
public class CreateRepetitionCommand extends Command
|
||||
{
|
||||
@NonNull
|
||||
final Habit habit;
|
||||
|
||||
final Timestamp timestamp;
|
||||
|
||||
final int value;
|
||||
|
||||
@Nullable
|
||||
Repetition previousRep;
|
||||
|
||||
@Nullable
|
||||
Repetition newRep;
|
||||
|
||||
public CreateRepetitionCommand(@NonNull Habit habit,
|
||||
Timestamp 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
|
||||
@NonNull
|
||||
public Record toRecord()
|
||||
{
|
||||
return new Record(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void undo()
|
||||
{
|
||||
if(newRep == null) throw new IllegalStateException();
|
||||
habit.getRepetitions().remove(newRep);
|
||||
|
||||
if (previousRep != null) habit.getRepetitions().add(previousRep);
|
||||
habit.invalidateNewerThan(timestamp);
|
||||
}
|
||||
|
||||
public static class Record
|
||||
{
|
||||
@NonNull
|
||||
public String id;
|
||||
|
||||
@NonNull
|
||||
public String event = "CreateRep";
|
||||
|
||||
public long habit;
|
||||
|
||||
public long repTimestamp;
|
||||
|
||||
public int value;
|
||||
|
||||
public Record(CreateRepetitionCommand command)
|
||||
{
|
||||
id = command.getId();
|
||||
Long habitId = command.habit.getId();
|
||||
if(habitId == null) throw new RuntimeException("Habit not saved");
|
||||
|
||||
this.habit = habitId;
|
||||
this.repTimestamp = command.timestamp.getUnixTime();
|
||||
this.value = command.value;
|
||||
}
|
||||
|
||||
public CreateRepetitionCommand toCommand(@NonNull HabitList habitList)
|
||||
{
|
||||
Habit h = habitList.getById(habit);
|
||||
if(h == null) throw new HabitNotFoundException();
|
||||
|
||||
CreateRepetitionCommand command;
|
||||
command = new CreateRepetitionCommand(
|
||||
h, new Timestamp(repTimestamp), value);
|
||||
command.setId(id);
|
||||
return command;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,105 +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.core.commands;
|
||||
|
||||
import android.support.annotation.*;
|
||||
|
||||
import org.isoron.uhabits.core.models.*;
|
||||
|
||||
import java.util.*;
|
||||
|
||||
/**
|
||||
* Command to delete a list of habits.
|
||||
*/
|
||||
public class DeleteHabitsCommand extends Command
|
||||
{
|
||||
@NonNull
|
||||
final HabitList habitList;
|
||||
|
||||
@NonNull
|
||||
final List<Habit> selected;
|
||||
|
||||
public DeleteHabitsCommand(@NonNull HabitList habitList,
|
||||
@NonNull List<Habit> selected)
|
||||
{
|
||||
this.selected = new LinkedList<>(selected);
|
||||
this.habitList = habitList;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void execute()
|
||||
{
|
||||
for (Habit h : selected)
|
||||
habitList.remove(h);
|
||||
}
|
||||
|
||||
public List<Habit> getSelected()
|
||||
{
|
||||
return Collections.unmodifiableList(selected);
|
||||
}
|
||||
|
||||
@Override
|
||||
@NonNull
|
||||
public Record toRecord()
|
||||
{
|
||||
return new Record(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void undo()
|
||||
{
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
public static class Record
|
||||
{
|
||||
@NonNull
|
||||
public String id;
|
||||
|
||||
@NonNull
|
||||
public String event = "DeleteHabit";
|
||||
|
||||
@NonNull
|
||||
public List<Long> habits;
|
||||
|
||||
public Record(DeleteHabitsCommand command)
|
||||
{
|
||||
id = command.getId();
|
||||
habits = new LinkedList<>();
|
||||
for (Habit h : command.selected)
|
||||
{
|
||||
if (!h.hasId()) throw new RuntimeException("Habit not saved");
|
||||
habits.add(h.getId());
|
||||
}
|
||||
}
|
||||
|
||||
public DeleteHabitsCommand toCommand(@NonNull HabitList habitList)
|
||||
{
|
||||
List<Habit> selected = new LinkedList<>();
|
||||
for (Long id : this.habits) selected.add(habitList.getById(id));
|
||||
|
||||
DeleteHabitsCommand command;
|
||||
command = new DeleteHabitsCommand(habitList, selected);
|
||||
command.setId(id);
|
||||
return command;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,140 +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.core.commands;
|
||||
|
||||
import android.support.annotation.*;
|
||||
|
||||
import com.google.auto.factory.*;
|
||||
|
||||
import org.isoron.uhabits.core.models.*;
|
||||
|
||||
/**
|
||||
* Command to modify a habit.
|
||||
*/
|
||||
@AutoFactory
|
||||
public class EditHabitCommand extends Command
|
||||
{
|
||||
@NonNull
|
||||
final HabitList habitList;
|
||||
|
||||
@NonNull
|
||||
final Habit original;
|
||||
|
||||
@NonNull
|
||||
final Habit modified;
|
||||
|
||||
final long savedId;
|
||||
|
||||
final boolean hasFrequencyChanged;
|
||||
|
||||
final boolean hasTargetChanged;
|
||||
|
||||
public EditHabitCommand(@Provided @NonNull ModelFactory modelFactory,
|
||||
@NonNull HabitList habitList,
|
||||
@NonNull Habit original,
|
||||
@NonNull Habit modified)
|
||||
{
|
||||
Long habitId = original.getId();
|
||||
if (habitId == null) throw new RuntimeException("Habit not saved");
|
||||
|
||||
this.savedId = habitId;
|
||||
this.habitList = habitList;
|
||||
this.modified = modelFactory.buildHabit();
|
||||
this.original = modelFactory.buildHabit();
|
||||
|
||||
this.modified.copyFrom(modified);
|
||||
this.original.copyFrom(original);
|
||||
|
||||
Frequency originalFreq = this.original.getFrequency();
|
||||
Frequency modifiedFreq = this.modified.getFrequency();
|
||||
hasFrequencyChanged = (!originalFreq.equals(modifiedFreq));
|
||||
hasTargetChanged =
|
||||
(original.getTargetType() != modified.getTargetType() ||
|
||||
original.getTargetValue() != modified.getTargetValue());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void execute()
|
||||
{
|
||||
copyAttributes(this.modified);
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public Record toRecord()
|
||||
{
|
||||
return new Record(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void undo()
|
||||
{
|
||||
copyAttributes(this.original);
|
||||
}
|
||||
|
||||
private void copyAttributes(Habit model)
|
||||
{
|
||||
Habit habit = habitList.getById(savedId);
|
||||
if (habit == null) throw new RuntimeException("Habit not found");
|
||||
|
||||
habit.copyFrom(model);
|
||||
habitList.update(habit);
|
||||
|
||||
if (hasFrequencyChanged || hasTargetChanged)
|
||||
habit.invalidateNewerThan(Timestamp.ZERO);
|
||||
}
|
||||
|
||||
public static class Record
|
||||
{
|
||||
@NonNull
|
||||
public String id;
|
||||
|
||||
@NonNull
|
||||
public String event = "EditHabit";
|
||||
|
||||
@NonNull
|
||||
public Habit.HabitData habit;
|
||||
|
||||
public long habitId;
|
||||
|
||||
public Record(EditHabitCommand command)
|
||||
{
|
||||
id = command.getId();
|
||||
this.habitId = command.savedId;
|
||||
this.habit = command.modified.getData();
|
||||
}
|
||||
|
||||
@NonNull
|
||||
public EditHabitCommand toCommand(@NonNull ModelFactory modelFactory,
|
||||
@NonNull HabitList habitList)
|
||||
{
|
||||
Habit original = habitList.getById(habitId);
|
||||
if(original == null) throw new HabitNotFoundException();
|
||||
|
||||
Habit modified = modelFactory.buildHabit(habit);
|
||||
|
||||
EditHabitCommand command;
|
||||
command = new EditHabitCommand(modelFactory, habitList, original,
|
||||
modified);
|
||||
command.setId(id);
|
||||
return command;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,109 +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.core.commands;
|
||||
|
||||
import android.support.annotation.*;
|
||||
|
||||
import org.isoron.uhabits.core.models.*;
|
||||
|
||||
/**
|
||||
* Command to toggle a repetition.
|
||||
*/
|
||||
public class ToggleRepetitionCommand extends Command
|
||||
{
|
||||
@NonNull
|
||||
private HabitList list;
|
||||
|
||||
final Timestamp timestamp;
|
||||
|
||||
@NonNull
|
||||
final Habit habit;
|
||||
|
||||
public ToggleRepetitionCommand(@NonNull HabitList list,
|
||||
@NonNull Habit habit,
|
||||
Timestamp timestamp)
|
||||
{
|
||||
super();
|
||||
this.list = list;
|
||||
this.timestamp = timestamp;
|
||||
this.habit = habit;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void execute()
|
||||
{
|
||||
habit.getRepetitions().toggle(timestamp);
|
||||
list.update(habit);
|
||||
}
|
||||
|
||||
@NonNull
|
||||
public Habit getHabit()
|
||||
{
|
||||
return habit;
|
||||
}
|
||||
|
||||
@Override
|
||||
@NonNull
|
||||
public Record toRecord()
|
||||
{
|
||||
return new Record(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void undo()
|
||||
{
|
||||
execute();
|
||||
}
|
||||
|
||||
public static class Record
|
||||
{
|
||||
@NonNull
|
||||
public String id;
|
||||
|
||||
@NonNull
|
||||
public String event = "Toggle";
|
||||
|
||||
public long habit;
|
||||
|
||||
public long repTimestamp;
|
||||
|
||||
public Record(@NonNull ToggleRepetitionCommand command)
|
||||
{
|
||||
id = command.getId();
|
||||
Long habitId = command.habit.getId();
|
||||
if (habitId == null) throw new RuntimeException("Habit not saved");
|
||||
|
||||
this.repTimestamp = command.timestamp.getUnixTime();
|
||||
this.habit = habitId;
|
||||
}
|
||||
|
||||
public ToggleRepetitionCommand toCommand(@NonNull HabitList habitList)
|
||||
{
|
||||
Habit h = habitList.getById(habit);
|
||||
if (h == null) throw new HabitNotFoundException();
|
||||
|
||||
ToggleRepetitionCommand command;
|
||||
command = new ToggleRepetitionCommand(
|
||||
habitList, h, new Timestamp(repTimestamp));
|
||||
command.setId(id);
|
||||
return command;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,101 +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.core.commands;
|
||||
|
||||
import android.support.annotation.*;
|
||||
|
||||
import org.isoron.uhabits.core.models.*;
|
||||
|
||||
import java.util.*;
|
||||
|
||||
/**
|
||||
* Command to unarchive a list of habits.
|
||||
*/
|
||||
public class UnarchiveHabitsCommand extends Command
|
||||
{
|
||||
@NonNull
|
||||
final HabitList habitList;
|
||||
|
||||
@NonNull
|
||||
final List<Habit> selected;
|
||||
|
||||
public UnarchiveHabitsCommand(@NonNull HabitList habitList,
|
||||
@NonNull List<Habit> selected)
|
||||
{
|
||||
this.selected = new LinkedList<>(selected);
|
||||
this.habitList = habitList;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void execute()
|
||||
{
|
||||
for (Habit h : selected) h.setArchived(false);
|
||||
habitList.update(selected);
|
||||
}
|
||||
|
||||
@Override
|
||||
@NonNull
|
||||
public Record toRecord()
|
||||
{
|
||||
return new Record(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void undo()
|
||||
{
|
||||
for (Habit h : selected) h.setArchived(true);
|
||||
habitList.update(selected);
|
||||
}
|
||||
|
||||
public static class Record
|
||||
{
|
||||
@NonNull
|
||||
public final String id;
|
||||
|
||||
@NonNull
|
||||
public final String event = "Unarchive";
|
||||
|
||||
@NonNull
|
||||
public final List<Long> habits;
|
||||
|
||||
public Record(@NonNull UnarchiveHabitsCommand command)
|
||||
{
|
||||
id = command.getId();
|
||||
habits = new LinkedList<>();
|
||||
for (Habit h : command.selected)
|
||||
{
|
||||
if (!h.hasId()) throw new RuntimeException("Habit not saved");
|
||||
habits.add(h.getId());
|
||||
}
|
||||
}
|
||||
|
||||
@NonNull
|
||||
public UnarchiveHabitsCommand toCommand(@NonNull HabitList habitList)
|
||||
{
|
||||
List<Habit> selected = new LinkedList<>();
|
||||
for (Long id : this.habits) selected.add(habitList.getById(id));
|
||||
|
||||
UnarchiveHabitsCommand command;
|
||||
command = new UnarchiveHabitsCommand(habitList, selected);
|
||||
command.setId(id);
|
||||
return command;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,24 +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/>.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Provides commands to modify the models, such as {@link
|
||||
* org.isoron.uhabits.core.commands.CreateHabitCommand}.
|
||||
*/
|
||||
package org.isoron.uhabits.core.commands;
|
||||
@@ -1,28 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2017 Á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.core.database;
|
||||
|
||||
import java.lang.annotation.*;
|
||||
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
public @interface Column
|
||||
{
|
||||
String name() default "";
|
||||
}
|
||||
@@ -1,68 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2017 Á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.core.database;
|
||||
|
||||
import android.support.annotation.*;
|
||||
|
||||
public interface Cursor extends AutoCloseable
|
||||
{
|
||||
@Override
|
||||
void close();
|
||||
|
||||
/**
|
||||
* Moves the cursor forward one row from its current position. Returns
|
||||
* true if the current position is valid, or false if the cursor is already
|
||||
* past the last row. The cursor start at position -1, so this method must
|
||||
* be called first.
|
||||
*/
|
||||
boolean moveToNext();
|
||||
|
||||
/**
|
||||
* Retrieves the value of the designated column in the current row of this
|
||||
* Cursor as an Integer. If the value is null, returns null. The first
|
||||
* column has index zero.
|
||||
*/
|
||||
@Nullable
|
||||
Integer getInt(int index);
|
||||
|
||||
/**
|
||||
* Retrieves the value of the designated column in the current row of this
|
||||
* Cursor as a Long. If the value is null, returns null. The first
|
||||
* column has index zero.
|
||||
*/
|
||||
@Nullable
|
||||
Long getLong(int index);
|
||||
|
||||
/**
|
||||
* Retrieves the value of the designated column in the current row of this
|
||||
* Cursor as a Double. If the value is null, returns null. The first
|
||||
* column has index zero.
|
||||
*/
|
||||
@Nullable
|
||||
Double getDouble(int index);
|
||||
|
||||
/**
|
||||
* Retrieves the value of the designated column in the current row of this
|
||||
* Cursor as a String. If the value is null, returns null. The first
|
||||
* column has index zero.
|
||||
*/
|
||||
@Nullable
|
||||
String getString(int index);
|
||||
}
|
||||
@@ -1,61 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2017 Á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.core.database;
|
||||
|
||||
import java.util.*;
|
||||
|
||||
public interface Database
|
||||
{
|
||||
Cursor query(String query, String... params);
|
||||
|
||||
default void query(String query, ProcessCallback callback)
|
||||
{
|
||||
try (Cursor c = query(query)) {
|
||||
c.moveToNext();
|
||||
callback.process(c);
|
||||
}
|
||||
}
|
||||
|
||||
int update(String tableName,
|
||||
Map<String, Object> values,
|
||||
String where,
|
||||
String... params);
|
||||
|
||||
Long insert(String tableName, Map<String, Object> values);
|
||||
|
||||
void delete(String tableName, String where, String... params);
|
||||
|
||||
void execute(String query, Object... params);
|
||||
|
||||
void beginTransaction();
|
||||
|
||||
void setTransactionSuccessful();
|
||||
|
||||
void endTransaction();
|
||||
|
||||
void close();
|
||||
|
||||
int getVersion();
|
||||
|
||||
interface ProcessCallback
|
||||
{
|
||||
void process(Cursor cursor);
|
||||
}
|
||||
}
|
||||
@@ -1,29 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2017 Á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.core.database;
|
||||
|
||||
import android.support.annotation.*;
|
||||
|
||||
import java.io.*;
|
||||
|
||||
public interface DatabaseOpener
|
||||
{
|
||||
Database open(@NonNull File file);
|
||||
}
|
||||
@@ -1,120 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2017 Á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.core.database;
|
||||
|
||||
import java.sql.*;
|
||||
|
||||
public class JdbcCursor implements Cursor
|
||||
{
|
||||
private ResultSet resultSet;
|
||||
|
||||
public JdbcCursor(ResultSet resultSet)
|
||||
{
|
||||
this.resultSet = resultSet;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close()
|
||||
{
|
||||
try
|
||||
{
|
||||
resultSet.close();
|
||||
}
|
||||
catch (SQLException e)
|
||||
{
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean moveToNext()
|
||||
{
|
||||
try
|
||||
{
|
||||
return resultSet.next();
|
||||
}
|
||||
catch (SQLException e)
|
||||
{
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Integer getInt(int index)
|
||||
{
|
||||
try
|
||||
{
|
||||
Integer value = resultSet.getInt(index + 1);
|
||||
if(resultSet.wasNull()) return null;
|
||||
else return value;
|
||||
}
|
||||
catch (SQLException e)
|
||||
{
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Long getLong(int index)
|
||||
{
|
||||
try
|
||||
{
|
||||
Long value = resultSet.getLong(index + 1);
|
||||
if(resultSet.wasNull()) return null;
|
||||
else return value;
|
||||
}
|
||||
catch (SQLException e)
|
||||
{
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Double getDouble(int index)
|
||||
{
|
||||
try
|
||||
{
|
||||
Double value = resultSet.getDouble(index + 1);
|
||||
if(resultSet.wasNull()) return null;
|
||||
else return value;
|
||||
}
|
||||
catch (SQLException e)
|
||||
{
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getString(int index)
|
||||
{
|
||||
try
|
||||
{
|
||||
String value = resultSet.getString(index + 1);
|
||||
if(resultSet.wasNull()) return null;
|
||||
else return value;
|
||||
}
|
||||
catch (SQLException e)
|
||||
{
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,206 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2017 Á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.core.database;
|
||||
|
||||
import org.apache.commons.lang3.*;
|
||||
|
||||
import java.sql.*;
|
||||
import java.util.*;
|
||||
|
||||
public class JdbcDatabase implements Database
|
||||
{
|
||||
private Connection connection;
|
||||
|
||||
private boolean transactionSuccessful;
|
||||
|
||||
public JdbcDatabase(Connection connection)
|
||||
{
|
||||
this.connection = connection;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Cursor query(String query, String... params)
|
||||
{
|
||||
try
|
||||
{
|
||||
PreparedStatement st = buildStatement(query, params);
|
||||
return new JdbcCursor(st.executeQuery());
|
||||
}
|
||||
catch (SQLException e)
|
||||
{
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int update(String tableName,
|
||||
Map<String, Object> map,
|
||||
String where,
|
||||
String... params)
|
||||
{
|
||||
try
|
||||
{
|
||||
ArrayList<String> fields = new ArrayList<>();
|
||||
ArrayList<String> values = new ArrayList<>();
|
||||
|
||||
for (Map.Entry<String, Object> entry : map.entrySet())
|
||||
{
|
||||
if (entry.getValue() == null) continue;
|
||||
fields.add(entry.getKey() + "=?");
|
||||
values.add(entry.getValue().toString());
|
||||
}
|
||||
values.addAll(Arrays.asList(params));
|
||||
|
||||
String query = String.format("update %s set %s where %s", tableName,
|
||||
StringUtils.join(fields, ", "), where);
|
||||
|
||||
PreparedStatement st = buildStatement(query, values.toArray());
|
||||
return st.executeUpdate();
|
||||
}
|
||||
catch (SQLException e)
|
||||
{
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Long insert(String tableName, Map<String, Object> map)
|
||||
{
|
||||
try
|
||||
{
|
||||
ArrayList<String> fields = new ArrayList<>();
|
||||
ArrayList<Object> params = new ArrayList<>();
|
||||
ArrayList<String> questionMarks = new ArrayList<>();
|
||||
|
||||
for (Map.Entry<String, Object> entry : map.entrySet())
|
||||
{
|
||||
if (entry.getValue() == null) continue;
|
||||
fields.add(entry.getKey());
|
||||
params.add(entry.getValue());
|
||||
questionMarks.add("?");
|
||||
}
|
||||
|
||||
String query =
|
||||
String.format("insert into %s(%s) values(%s)", tableName,
|
||||
StringUtils.join(fields, ", "),
|
||||
StringUtils.join(questionMarks, ", "));
|
||||
|
||||
PreparedStatement st = buildStatement(query, params.toArray());
|
||||
st.execute();
|
||||
|
||||
Long id = null;
|
||||
ResultSet keys = st.getGeneratedKeys();
|
||||
if (keys.next()) id = keys.getLong(1);
|
||||
return id;
|
||||
}
|
||||
catch (SQLException e)
|
||||
{
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void delete(String tableName, String where, String... params)
|
||||
{
|
||||
String query =
|
||||
String.format("delete from %s where %s", tableName, where);
|
||||
execute(query, (Object[]) params);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void execute(String query, Object... params)
|
||||
{
|
||||
try
|
||||
{
|
||||
buildStatement(query, params).execute();
|
||||
}
|
||||
catch (SQLException e)
|
||||
{
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
private PreparedStatement buildStatement(String query, Object[] params)
|
||||
throws SQLException
|
||||
{
|
||||
PreparedStatement st = connection.prepareStatement(query);
|
||||
int index = 1;
|
||||
for (Object param : params) st.setString(index++, param.toString());
|
||||
return st;
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized void beginTransaction()
|
||||
{
|
||||
try
|
||||
{
|
||||
connection.setAutoCommit(false);
|
||||
transactionSuccessful = false;
|
||||
}
|
||||
catch (SQLException e)
|
||||
{
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized void setTransactionSuccessful()
|
||||
{
|
||||
transactionSuccessful = true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized void endTransaction()
|
||||
{
|
||||
try
|
||||
{
|
||||
if (transactionSuccessful) connection.commit();
|
||||
else connection.rollback();
|
||||
connection.setAutoCommit(true);
|
||||
}
|
||||
catch (SQLException e)
|
||||
{
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close()
|
||||
{
|
||||
try
|
||||
{
|
||||
connection.close();
|
||||
}
|
||||
catch (SQLException e)
|
||||
{
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getVersion()
|
||||
{
|
||||
try (Cursor c = query("PRAGMA user_version"))
|
||||
{
|
||||
c.moveToNext();
|
||||
return c.getInt(0);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,71 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2017 Á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.core.database;
|
||||
|
||||
import android.support.annotation.*;
|
||||
|
||||
import java.io.*;
|
||||
import java.util.*;
|
||||
import java.util.logging.*;
|
||||
|
||||
public class MigrationHelper
|
||||
{
|
||||
private static final Logger LOGGER =
|
||||
Logger.getLogger(MigrationHelper.class.getName());
|
||||
|
||||
private final Database db;
|
||||
|
||||
public MigrationHelper(@NonNull Database db)
|
||||
{
|
||||
this.db = db;
|
||||
}
|
||||
|
||||
public void migrateTo(int newVersion)
|
||||
{
|
||||
try
|
||||
{
|
||||
for (int v = db.getVersion() + 1; v <= newVersion; v++)
|
||||
{
|
||||
String fname = String.format(Locale.US, "/migrations/%02d.sql", v);
|
||||
for (String command : SQLParser.parse(open(fname)))
|
||||
db.execute(command);
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@NonNull
|
||||
private InputStream open(String fname) throws IOException
|
||||
{
|
||||
InputStream resource = getClass().getResourceAsStream(fname);
|
||||
if(resource != null) return resource;
|
||||
|
||||
// Workaround for bug in Android Studio / IntelliJ. Removing this
|
||||
// causes unit tests to fail when run from within the IDE, although
|
||||
// everything works fine from the command line.
|
||||
File file = new File("uhabits-core/src/main/resources/" + fname);
|
||||
if(file.exists()) return new FileInputStream(file);
|
||||
|
||||
throw new RuntimeException("resource not found: " + fname);
|
||||
}
|
||||
}
|
||||
@@ -1,355 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2017 Á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.core.database;
|
||||
|
||||
import android.support.annotation.*;
|
||||
|
||||
import org.apache.commons.lang3.*;
|
||||
import org.apache.commons.lang3.tuple.*;
|
||||
|
||||
import java.lang.annotation.*;
|
||||
import java.lang.reflect.*;
|
||||
import java.util.*;
|
||||
|
||||
public class Repository<T>
|
||||
{
|
||||
@NonNull
|
||||
private final Class klass;
|
||||
|
||||
@NonNull
|
||||
private final Database db;
|
||||
|
||||
public Repository(@NonNull Class<T> klass, @NonNull Database db)
|
||||
{
|
||||
this.klass = klass;
|
||||
this.db = db;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the record that has the id provided.
|
||||
* If no record is found, returns null.
|
||||
*/
|
||||
@Nullable
|
||||
public T find(@NonNull Long id)
|
||||
{
|
||||
return findFirst(String.format("where %s=?", getIdName()),
|
||||
id.toString());
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns all records matching the given SQL query.
|
||||
* <p>
|
||||
* The query should only contain the "where" part of the SQL query, and
|
||||
* optinally the "order by" part. "Group by" is not allowed. If no matching
|
||||
* records are found, returns an empty list.
|
||||
*/
|
||||
@NonNull
|
||||
public List<T> findAll(@NonNull String query, @NonNull String... params)
|
||||
{
|
||||
try (Cursor c = db.query(buildSelectQuery() + query, params))
|
||||
{
|
||||
return cursorToMultipleRecords(c);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the first record matching the given SQL query.
|
||||
* See findAll for more details about the parameters.
|
||||
*/
|
||||
@Nullable
|
||||
public T findFirst(String query, String... params)
|
||||
{
|
||||
try (Cursor c = db.query(buildSelectQuery() + query, params))
|
||||
{
|
||||
if (!c.moveToNext()) return null;
|
||||
return cursorToSingleRecord(c);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Executes the given SQL query on the repository.
|
||||
* <p>
|
||||
* The query can be of any kind. For example, complex deletes and updates
|
||||
* are allowed. The repository does not perform any checks to guarantee
|
||||
* that the query is valid, however the underlying database might.
|
||||
*/
|
||||
public void execSQL(String query, Object... params)
|
||||
{
|
||||
db.execute(query, params);
|
||||
}
|
||||
|
||||
/**
|
||||
* Executes the given callback inside a database transaction.
|
||||
* <p>
|
||||
* If the callback terminates without throwing any exceptions, the
|
||||
* transaction is considered successful. If any exceptions are thrown,
|
||||
* the transaction is aborted. Nesting transactions is not allowed.
|
||||
*/
|
||||
public void executeAsTransaction(Runnable callback)
|
||||
{
|
||||
db.beginTransaction();
|
||||
try
|
||||
{
|
||||
callback.run();
|
||||
db.setTransactionSuccessful();
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
finally
|
||||
{
|
||||
db.endTransaction();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Saves the record on the database.
|
||||
* <p>
|
||||
* If the id of the given record is null, it is assumed that the record has
|
||||
* not been inserted in the repository yet. The record will be inserted, a
|
||||
* new id will be automatically generated, and the id of the given record
|
||||
* will be updated.
|
||||
* <p>
|
||||
* If the given record has a non-null id, then an update will be performed
|
||||
* instead. That is, the previous record will be overwritten by the one
|
||||
* provided.
|
||||
*/
|
||||
public void save(T record)
|
||||
{
|
||||
try
|
||||
{
|
||||
Field fields[] = getFields();
|
||||
String columns[] = getColumnNames();
|
||||
|
||||
Map<String, Object> values = new HashMap<>();
|
||||
for (int i = 0; i < fields.length; i++)
|
||||
values.put(columns[i], fields[i].get(record));
|
||||
|
||||
Long id = (Long) getIdField().get(record);
|
||||
int affectedRows = 0;
|
||||
|
||||
if (id != null) affectedRows =
|
||||
db.update(getTableName(), values, getIdName() + "=?",
|
||||
id.toString());
|
||||
|
||||
if (id == null || affectedRows == 0)
|
||||
{
|
||||
id = db.insert(getTableName(), values);
|
||||
getIdField().set(record, id);
|
||||
}
|
||||
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes the given record from the repository.
|
||||
* The id of the given record is also set to null.
|
||||
*/
|
||||
public void remove(T record)
|
||||
{
|
||||
try
|
||||
{
|
||||
Long id = (Long) getIdField().get(record);
|
||||
if (id == null) return;
|
||||
|
||||
db.delete(getTableName(), getIdName() + "=?", id.toString());
|
||||
getIdField().set(record, null);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@NonNull
|
||||
private List<T> cursorToMultipleRecords(Cursor c)
|
||||
{
|
||||
List<T> records = new LinkedList<>();
|
||||
while (c.moveToNext()) records.add(cursorToSingleRecord(c));
|
||||
return records;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
private T cursorToSingleRecord(Cursor cursor)
|
||||
{
|
||||
try
|
||||
{
|
||||
Constructor constructor = klass.getDeclaredConstructors()[0];
|
||||
constructor.setAccessible(true);
|
||||
T record = (T) constructor.newInstance();
|
||||
|
||||
int index = 0;
|
||||
for (Field field : getFields())
|
||||
copyFieldFromCursor(record, field, cursor, index++);
|
||||
return record;
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
private void copyFieldFromCursor(T record, Field field, Cursor c, int index)
|
||||
throws IllegalAccessException
|
||||
{
|
||||
if (field.getType().isAssignableFrom(Integer.class))
|
||||
field.set(record, c.getInt(index));
|
||||
else if (field.getType().isAssignableFrom(Long.class))
|
||||
field.set(record, c.getLong(index));
|
||||
else if (field.getType().isAssignableFrom(Double.class))
|
||||
field.set(record, c.getDouble(index));
|
||||
else if (field.getType().isAssignableFrom(String.class))
|
||||
field.set(record, c.getString(index));
|
||||
else throw new RuntimeException(
|
||||
"Type not supported: " + field.getType().getName() + " " +
|
||||
field.getName());
|
||||
}
|
||||
|
||||
private String buildSelectQuery()
|
||||
{
|
||||
return String.format("select %s from %s ",
|
||||
StringUtils.join(getColumnNames(), ", "), getTableName());
|
||||
}
|
||||
|
||||
private List<Pair<Field, Column>> getFieldColumnPairs()
|
||||
{
|
||||
List<Pair<Field, Column>> fields = new ArrayList<>();
|
||||
for (Field field : klass.getDeclaredFields())
|
||||
for (Annotation annotation : field.getAnnotations())
|
||||
{
|
||||
if (!(annotation instanceof Column)) continue;
|
||||
Column column = (Column) annotation;
|
||||
fields.add(new ImmutablePair<>(field, column));
|
||||
}
|
||||
return fields;
|
||||
}
|
||||
|
||||
private Field[] cacheFields = null;
|
||||
|
||||
@NonNull
|
||||
private Field[] getFields()
|
||||
{
|
||||
if (cacheFields == null)
|
||||
{
|
||||
List<Field> fields = new ArrayList<>();
|
||||
List<Pair<Field, Column>> columns = getFieldColumnPairs();
|
||||
for (Pair<Field, Column> pair : columns) fields.add(pair.getLeft());
|
||||
cacheFields = fields.toArray(new Field[]{});
|
||||
}
|
||||
|
||||
return cacheFields;
|
||||
}
|
||||
|
||||
private String[] cacheColumnNames = null;
|
||||
|
||||
@NonNull
|
||||
private String[] getColumnNames()
|
||||
{
|
||||
if (cacheColumnNames == null)
|
||||
{
|
||||
List<String> names = new ArrayList<>();
|
||||
List<Pair<Field, Column>> columns = getFieldColumnPairs();
|
||||
for (Pair<Field, Column> pair : columns)
|
||||
{
|
||||
String cname = pair.getRight().name();
|
||||
if (cname.isEmpty()) cname = pair.getLeft().getName();
|
||||
if (names.contains(cname))
|
||||
throw new RuntimeException("duplicated column : " + cname);
|
||||
names.add(cname);
|
||||
}
|
||||
|
||||
cacheColumnNames = names.toArray(new String[]{});
|
||||
}
|
||||
|
||||
return cacheColumnNames;
|
||||
}
|
||||
|
||||
private String cacheTableName = null;
|
||||
|
||||
@NonNull
|
||||
private String getTableName()
|
||||
{
|
||||
if (cacheTableName == null)
|
||||
{
|
||||
String name = getTableAnnotation().name();
|
||||
if (name.isEmpty()) throw new RuntimeException("Table name is empty");
|
||||
cacheTableName = name;
|
||||
}
|
||||
return cacheTableName;
|
||||
}
|
||||
|
||||
private String cacheIdName = null;
|
||||
|
||||
@NonNull
|
||||
private String getIdName()
|
||||
{
|
||||
if (cacheIdName == null)
|
||||
{
|
||||
String id = getTableAnnotation().id();
|
||||
if (id.isEmpty()) throw new RuntimeException("Table id is empty");
|
||||
cacheIdName = id;
|
||||
}
|
||||
|
||||
return cacheIdName;
|
||||
}
|
||||
|
||||
private Field cacheIdField = null;
|
||||
|
||||
@NonNull
|
||||
private Field getIdField()
|
||||
{
|
||||
if (cacheIdField == null)
|
||||
{
|
||||
Field fields[] = getFields();
|
||||
String idName = getIdName();
|
||||
for (Field f : fields)
|
||||
if (f.getName().equals(idName))
|
||||
{
|
||||
cacheIdField = f;
|
||||
break;
|
||||
}
|
||||
|
||||
if (cacheIdField == null)
|
||||
throw new RuntimeException("Field not found: " + idName);
|
||||
}
|
||||
|
||||
return cacheIdField;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
private Table getTableAnnotation()
|
||||
{
|
||||
Table t = null;
|
||||
for (Annotation annotation : klass.getAnnotations())
|
||||
{
|
||||
if (!(annotation instanceof Table)) continue;
|
||||
t = (Table) annotation;
|
||||
break;
|
||||
}
|
||||
|
||||
if (t == null) throw new RuntimeException("Table annotation not found");
|
||||
return t;
|
||||
}
|
||||
}
|
||||
@@ -1,163 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2014 Markus Pfeiffer
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.isoron.uhabits.core.database;
|
||||
|
||||
import java.io.BufferedInputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
class Tokenizer {
|
||||
|
||||
private final InputStream mStream;
|
||||
|
||||
private boolean mIsNext;
|
||||
private int mCurrent;
|
||||
|
||||
public Tokenizer(final InputStream in) {
|
||||
this.mStream = in;
|
||||
}
|
||||
|
||||
public boolean hasNext() throws IOException {
|
||||
|
||||
if (!this.mIsNext) {
|
||||
this.mIsNext = true;
|
||||
this.mCurrent = this.mStream.read();
|
||||
}
|
||||
return this.mCurrent != -1;
|
||||
}
|
||||
|
||||
public int next() throws IOException {
|
||||
|
||||
if (!this.mIsNext) {
|
||||
this.mCurrent = this.mStream.read();
|
||||
}
|
||||
this.mIsNext = false;
|
||||
return this.mCurrent;
|
||||
}
|
||||
|
||||
public boolean skip(final String s) throws IOException {
|
||||
|
||||
if (s == null || s.length() == 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (s.charAt(0) != this.mCurrent) {
|
||||
return false;
|
||||
}
|
||||
|
||||
final int len = s.length();
|
||||
this.mStream.mark(len - 1);
|
||||
|
||||
for (int n = 1; n < len; n++) {
|
||||
final int value = this.mStream.read();
|
||||
|
||||
if (value != s.charAt(n)) {
|
||||
this.mStream.reset();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public class SQLParser {
|
||||
|
||||
public final static int STATE_NONE = 0;
|
||||
public final static int STATE_STRING = 1;
|
||||
public final static int STATE_COMMENT = 2;
|
||||
public final static int STATE_COMMENT_BLOCK = 3;
|
||||
|
||||
public static List<String> parse(final InputStream stream) throws IOException {
|
||||
|
||||
final BufferedInputStream buffer = new BufferedInputStream(stream);
|
||||
final List<String> commands = new ArrayList<String>();
|
||||
final StringBuffer sb = new StringBuffer();
|
||||
|
||||
try {
|
||||
final Tokenizer tokenizer = new Tokenizer(buffer);
|
||||
int state = STATE_NONE;
|
||||
|
||||
while (tokenizer.hasNext()) {
|
||||
final char c = (char) tokenizer.next();
|
||||
|
||||
if (state == STATE_COMMENT_BLOCK) {
|
||||
if (tokenizer.skip("*/")) {
|
||||
state = STATE_NONE;
|
||||
}
|
||||
continue;
|
||||
|
||||
} else if (state == STATE_COMMENT) {
|
||||
if (isNewLine(c)) {
|
||||
state = STATE_NONE;
|
||||
}
|
||||
continue;
|
||||
|
||||
} else if (state == STATE_NONE && tokenizer.skip("/*")) {
|
||||
state = STATE_COMMENT_BLOCK;
|
||||
continue;
|
||||
|
||||
} else if (state == STATE_NONE && tokenizer.skip("--")) {
|
||||
state = STATE_COMMENT;
|
||||
continue;
|
||||
|
||||
} else if (state == STATE_NONE && c == ';') {
|
||||
final String command = sb.toString().trim();
|
||||
commands.add(command);
|
||||
sb.setLength(0);
|
||||
continue;
|
||||
|
||||
} else if (state == STATE_NONE && c == '\'') {
|
||||
state = STATE_STRING;
|
||||
|
||||
} else if (state == STATE_STRING && c == '\'') {
|
||||
state = STATE_NONE;
|
||||
|
||||
}
|
||||
|
||||
if (state == STATE_NONE || state == STATE_STRING) {
|
||||
if (state == STATE_NONE && isWhitespace(c)) {
|
||||
if (sb.length() > 0 && sb.charAt(sb.length() - 1) != ' ') {
|
||||
sb.append(' ');
|
||||
}
|
||||
} else {
|
||||
sb.append(c);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} finally {
|
||||
buffer.close();
|
||||
}
|
||||
|
||||
if (sb.length() > 0) {
|
||||
commands.add(sb.toString().trim());
|
||||
}
|
||||
|
||||
return commands;
|
||||
}
|
||||
|
||||
private static boolean isNewLine(final char c) {
|
||||
return c == '\r' || c == '\n';
|
||||
}
|
||||
|
||||
private static boolean isWhitespace(final char c) {
|
||||
return c == '\r' || c == '\n' || c == '\t' || c == ' ';
|
||||
}
|
||||
}
|
||||
@@ -1,30 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2017 Á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.core.database;
|
||||
|
||||
import java.lang.annotation.*;
|
||||
|
||||
@Target(ElementType.TYPE)
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
public @interface Table
|
||||
{
|
||||
String name();
|
||||
String id() default "id";
|
||||
}
|
||||
@@ -1,24 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2017 Á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.core.database;
|
||||
|
||||
public class UnsupportedDatabaseVersionException extends RuntimeException
|
||||
{
|
||||
}
|
||||
@@ -1,58 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2017 Á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.core.io;
|
||||
|
||||
import android.support.annotation.*;
|
||||
|
||||
import org.isoron.uhabits.core.models.*;
|
||||
|
||||
import java.io.*;
|
||||
import java.util.*;
|
||||
|
||||
/**
|
||||
* AbstractImporter is the base class for all classes that import data from
|
||||
* files into the app.
|
||||
*/
|
||||
public abstract class AbstractImporter
|
||||
{
|
||||
protected final HabitList habitList;
|
||||
|
||||
public AbstractImporter(HabitList habitList)
|
||||
{
|
||||
this.habitList = habitList;
|
||||
}
|
||||
|
||||
public abstract boolean canHandle(@NonNull File file) throws IOException;
|
||||
|
||||
public abstract void importHabitsFromFile(@NonNull File file) throws IOException;
|
||||
|
||||
public static boolean isSQLite3File(@NonNull File file) throws IOException
|
||||
{
|
||||
FileInputStream fis = new FileInputStream(file);
|
||||
|
||||
byte[] sqliteHeader = "SQLite format 3".getBytes();
|
||||
byte[] buffer = new byte[sqliteHeader.length];
|
||||
|
||||
int count = fis.read(buffer);
|
||||
if(count < sqliteHeader.length) return false;
|
||||
|
||||
return Arrays.equals(buffer, sqliteHeader);
|
||||
}
|
||||
}
|
||||
@@ -1,69 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2017 Á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.core.io;
|
||||
|
||||
import android.support.annotation.*;
|
||||
|
||||
import org.isoron.uhabits.core.models.*;
|
||||
|
||||
import java.io.*;
|
||||
import java.util.*;
|
||||
|
||||
import javax.inject.*;
|
||||
|
||||
/**
|
||||
* A GenericImporter decides which implementation of AbstractImporter is able to
|
||||
* handle a given file and delegates to it the task of importing the data.
|
||||
*/
|
||||
public class GenericImporter extends AbstractImporter
|
||||
{
|
||||
List<AbstractImporter> importers;
|
||||
|
||||
@Inject
|
||||
public GenericImporter(@NonNull HabitList habits,
|
||||
@NonNull LoopDBImporter loopDBImporter,
|
||||
@NonNull RewireDBImporter rewireDBImporter,
|
||||
@NonNull TickmateDBImporter tickmateDBImporter,
|
||||
@NonNull HabitBullCSVImporter habitBullCSVImporter)
|
||||
{
|
||||
super(habits);
|
||||
importers = new LinkedList<>();
|
||||
importers.add(loopDBImporter);
|
||||
importers.add(rewireDBImporter);
|
||||
importers.add(tickmateDBImporter);
|
||||
importers.add(habitBullCSVImporter);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean canHandle(@NonNull File file) throws IOException
|
||||
{
|
||||
for (AbstractImporter importer : importers)
|
||||
if (importer.canHandle(file)) return true;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void importHabitsFromFile(@NonNull File file) throws IOException
|
||||
{
|
||||
for (AbstractImporter importer : importers)
|
||||
if (importer.canHandle(file)) importer.importHabitsFromFile(file);
|
||||
}
|
||||
}
|
||||
@@ -1,100 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2017 Á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.core.io;
|
||||
|
||||
import android.support.annotation.*;
|
||||
|
||||
import com.opencsv.*;
|
||||
|
||||
import org.isoron.uhabits.core.models.*;
|
||||
import org.isoron.uhabits.core.utils.*;
|
||||
|
||||
import java.io.*;
|
||||
import java.util.*;
|
||||
|
||||
import javax.inject.*;
|
||||
|
||||
|
||||
/**
|
||||
* Class that imports data from HabitBull CSV files.
|
||||
*/
|
||||
public class HabitBullCSVImporter extends AbstractImporter
|
||||
{
|
||||
private ModelFactory modelFactory;
|
||||
|
||||
@Inject
|
||||
public HabitBullCSVImporter(@NonNull HabitList habits,
|
||||
@NonNull ModelFactory modelFactory)
|
||||
{
|
||||
super(habits);
|
||||
this.modelFactory = modelFactory;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean canHandle(@NonNull File file) throws IOException
|
||||
{
|
||||
BufferedReader reader = new BufferedReader(new FileReader(file));
|
||||
String line = reader.readLine();
|
||||
return line.startsWith("HabitName,HabitDescription,HabitCategory");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void importHabitsFromFile(@NonNull final File file)
|
||||
throws IOException
|
||||
{
|
||||
CSVReader reader = new CSVReader(new FileReader(file));
|
||||
HashMap<String, Habit> map = new HashMap<>();
|
||||
|
||||
for (String line[] : reader)
|
||||
{
|
||||
String name = line[0];
|
||||
if (name.equals("HabitName")) continue;
|
||||
|
||||
String description = line[1];
|
||||
String dateString[] = line[3].split("-");
|
||||
int year = Integer.parseInt(dateString[0]);
|
||||
int month = Integer.parseInt(dateString[1]);
|
||||
int day = Integer.parseInt(dateString[2]);
|
||||
|
||||
Calendar date = DateUtils.getStartOfTodayCalendar();
|
||||
date.set(year, month - 1, day);
|
||||
|
||||
Timestamp timestamp = new Timestamp(date.getTimeInMillis());
|
||||
|
||||
int value = Integer.parseInt(line[4]);
|
||||
if (value != 1) continue;
|
||||
|
||||
Habit h = map.get(name);
|
||||
|
||||
if (h == null)
|
||||
{
|
||||
h = modelFactory.buildHabit();
|
||||
h.setName(name);
|
||||
h.setDescription(description);
|
||||
h.setFrequency(Frequency.DAILY);
|
||||
habitList.add(h);
|
||||
map.put(name, h);
|
||||
}
|
||||
|
||||
if (!h.getRepetitions().containsTimestamp(timestamp))
|
||||
h.getRepetitions().toggle(timestamp);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,275 +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.core.io;
|
||||
|
||||
import android.support.annotation.*;
|
||||
|
||||
import org.isoron.uhabits.core.models.*;
|
||||
import org.isoron.uhabits.core.utils.*;
|
||||
|
||||
import java.io.*;
|
||||
import java.text.*;
|
||||
import java.util.*;
|
||||
import java.util.zip.*;
|
||||
|
||||
/**
|
||||
* Class that exports the application data to CSV files.
|
||||
*/
|
||||
public class HabitsCSVExporter
|
||||
{
|
||||
private List<Habit> selectedHabits;
|
||||
|
||||
private List<String> generateDirs;
|
||||
|
||||
private List<String> generateFilenames;
|
||||
|
||||
private String exportDirName;
|
||||
/**
|
||||
* Delimiter used in a CSV file.
|
||||
*/
|
||||
private final String DELIMITER = ",";
|
||||
|
||||
@NonNull
|
||||
private final HabitList allHabits;
|
||||
|
||||
public HabitsCSVExporter(@NonNull HabitList allHabits,
|
||||
@NonNull List<Habit> selectedHabits,
|
||||
@NonNull File dir)
|
||||
{
|
||||
this.allHabits = allHabits;
|
||||
this.selectedHabits = selectedHabits;
|
||||
this.exportDirName = dir.getAbsolutePath() + "/";
|
||||
|
||||
generateDirs = new LinkedList<>();
|
||||
generateFilenames = new LinkedList<>();
|
||||
}
|
||||
|
||||
public String writeArchive() throws IOException
|
||||
{
|
||||
String zipFilename;
|
||||
|
||||
writeHabits();
|
||||
zipFilename = writeZipFile();
|
||||
cleanup();
|
||||
|
||||
return zipFilename;
|
||||
}
|
||||
|
||||
private void addFileToZip(ZipOutputStream zos, String filename)
|
||||
throws IOException
|
||||
{
|
||||
FileInputStream fis =
|
||||
new FileInputStream(new File(exportDirName + filename));
|
||||
ZipEntry ze = new ZipEntry(filename);
|
||||
zos.putNextEntry(ze);
|
||||
|
||||
int length;
|
||||
byte bytes[] = new byte[1024];
|
||||
while ((length = fis.read(bytes)) >= 0) zos.write(bytes, 0, length);
|
||||
|
||||
zos.closeEntry();
|
||||
fis.close();
|
||||
}
|
||||
|
||||
private void cleanup()
|
||||
{
|
||||
for (String filename : generateFilenames)
|
||||
new File(exportDirName + filename).delete();
|
||||
|
||||
for (String filename : generateDirs)
|
||||
new File(exportDirName + filename).delete();
|
||||
|
||||
new File(exportDirName).delete();
|
||||
}
|
||||
|
||||
@NonNull
|
||||
private String sanitizeFilename(String name)
|
||||
{
|
||||
String s = name.replaceAll("[^ a-zA-Z0-9\\._-]+", "");
|
||||
return s.substring(0, Math.min(s.length(), 100));
|
||||
}
|
||||
|
||||
private void writeHabits() throws IOException
|
||||
{
|
||||
String filename = "Habits.csv";
|
||||
new File(exportDirName).mkdirs();
|
||||
FileWriter out = new FileWriter(exportDirName + filename);
|
||||
generateFilenames.add(filename);
|
||||
allHabits.writeCSV(out);
|
||||
out.close();
|
||||
|
||||
for (Habit h : selectedHabits)
|
||||
{
|
||||
String sane = sanitizeFilename(h.getName());
|
||||
String habitDirName =
|
||||
String.format("%03d %s", allHabits.indexOf(h) + 1, sane);
|
||||
habitDirName = habitDirName.trim() + "/";
|
||||
|
||||
new File(exportDirName + habitDirName).mkdirs();
|
||||
generateDirs.add(habitDirName);
|
||||
|
||||
writeScores(habitDirName, h.getScores());
|
||||
writeCheckmarks(habitDirName, h.getCheckmarks());
|
||||
}
|
||||
|
||||
writeMultipleHabits();
|
||||
}
|
||||
|
||||
private void writeScores(String habitDirName, ScoreList scores)
|
||||
throws IOException
|
||||
{
|
||||
String path = habitDirName + "Scores.csv";
|
||||
FileWriter out = new FileWriter(exportDirName + path);
|
||||
generateFilenames.add(path);
|
||||
scores.writeCSV(out);
|
||||
out.close();
|
||||
}
|
||||
|
||||
private void writeCheckmarks(String habitDirName, CheckmarkList checkmarks)
|
||||
throws IOException
|
||||
{
|
||||
String filename = habitDirName + "Checkmarks.csv";
|
||||
FileWriter out = new FileWriter(exportDirName + filename);
|
||||
generateFilenames.add(filename);
|
||||
checkmarks.writeCSV(out);
|
||||
out.close();
|
||||
}
|
||||
|
||||
/**
|
||||
* Writes a scores file and a checkmarks file containing scores and checkmarks of every habit.
|
||||
* The first column corresponds to the date. Subsequent columns correspond to a habit.
|
||||
* Habits are taken from the list of selected habits.
|
||||
* Dates are determined from the oldest repetition date to the newest repetition date found in
|
||||
* the list of habits.
|
||||
*
|
||||
* @throws IOException if there was problem writing the files
|
||||
*/
|
||||
private void writeMultipleHabits() throws IOException
|
||||
{
|
||||
String scoresFileName = "Scores.csv";
|
||||
String checksFileName = "Checkmarks.csv";
|
||||
generateFilenames.add(scoresFileName);
|
||||
generateFilenames.add(checksFileName);
|
||||
FileWriter scoresWriter = new FileWriter(exportDirName + scoresFileName);
|
||||
FileWriter checksWriter = new FileWriter(exportDirName + checksFileName);
|
||||
|
||||
writeMultipleHabitsHeader(scoresWriter);
|
||||
writeMultipleHabitsHeader(checksWriter);
|
||||
|
||||
Timestamp[] timeframe = getTimeframe();
|
||||
Timestamp oldest = timeframe[0];
|
||||
Timestamp newest = DateUtils.getToday();
|
||||
|
||||
List<int[]> checkmarks = new ArrayList<>();
|
||||
List<double[]> scores = new ArrayList<>();
|
||||
for (Habit h : selectedHabits)
|
||||
{
|
||||
checkmarks.add(h.getCheckmarks().getValues(oldest, newest));
|
||||
scores.add(h.getScores().getValues(oldest, newest));
|
||||
}
|
||||
|
||||
int days = oldest.daysUntil(newest);
|
||||
SimpleDateFormat dateFormat = DateFormats.getCSVDateFormat();
|
||||
for (int i = 0; i <= days; i++)
|
||||
{
|
||||
Date day = newest.minus(i).toJavaDate();
|
||||
|
||||
String date = dateFormat.format(day);
|
||||
StringBuilder sb = new StringBuilder();
|
||||
sb.append(date).append(DELIMITER);
|
||||
checksWriter.write(sb.toString());
|
||||
scoresWriter.write(sb.toString());
|
||||
|
||||
for(int j = 0; j < selectedHabits.size(); j++)
|
||||
{
|
||||
checksWriter.write(String.valueOf(checkmarks.get(j)[i]));
|
||||
checksWriter.write(DELIMITER);
|
||||
String score =
|
||||
String.format("%.4f", ((float) scores.get(j)[i]));
|
||||
scoresWriter.write(score);
|
||||
scoresWriter.write(DELIMITER);
|
||||
}
|
||||
checksWriter.write("\n");
|
||||
scoresWriter.write("\n");
|
||||
}
|
||||
scoresWriter.close();
|
||||
checksWriter.close();
|
||||
}
|
||||
|
||||
/**
|
||||
* Writes the first row, containing header information, using the given writer.
|
||||
* This consists of the date title and the names of the selected habits.
|
||||
*
|
||||
* @param out the writer to use
|
||||
* @throws IOException if there was a problem writing
|
||||
*/
|
||||
private void writeMultipleHabitsHeader(Writer out) throws IOException
|
||||
{
|
||||
out.write("Date" + DELIMITER);
|
||||
for (Habit h : selectedHabits) {
|
||||
out.write(h.getName());
|
||||
out.write(DELIMITER);
|
||||
}
|
||||
out.write("\n");
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the overall timeframe of the selected habits.
|
||||
* The timeframe is an array containing the oldest timestamp among the habits and the
|
||||
* newest timestamp among the habits.
|
||||
* Both timestamps are in milliseconds.
|
||||
*
|
||||
* @return the timeframe containing the oldest timestamp and the newest timestamp
|
||||
*/
|
||||
private Timestamp[] getTimeframe()
|
||||
{
|
||||
Timestamp oldest = Timestamp.ZERO.plus(1000000);
|
||||
Timestamp newest = Timestamp.ZERO;
|
||||
for (Habit h : selectedHabits)
|
||||
{
|
||||
if(h.getRepetitions().getOldest() == null || h.getRepetitions().getNewest() == null)
|
||||
continue;
|
||||
Timestamp currOld = h.getRepetitions().getOldest().getTimestamp();
|
||||
Timestamp currNew = h.getRepetitions().getNewest().getTimestamp();
|
||||
oldest = currOld.isOlderThan(oldest) ? oldest : currOld;
|
||||
newest = currNew.isNewerThan(newest) ? newest : currNew;
|
||||
}
|
||||
return new Timestamp[]{oldest, newest};
|
||||
}
|
||||
|
||||
private String writeZipFile() throws IOException
|
||||
{
|
||||
SimpleDateFormat dateFormat = DateFormats.getCSVDateFormat();
|
||||
String date = dateFormat.format(DateUtils.getStartOfToday());
|
||||
String zipFilename =
|
||||
String.format("%s/Loop Habits CSV %s.zip", exportDirName, date);
|
||||
|
||||
FileOutputStream fos = new FileOutputStream(zipFilename);
|
||||
ZipOutputStream zos = new ZipOutputStream(fos);
|
||||
|
||||
for (String filename : generateFilenames)
|
||||
addFileToZip(zos, filename);
|
||||
|
||||
zos.close();
|
||||
fos.close();
|
||||
|
||||
return zipFilename;
|
||||
}
|
||||
}
|
||||
@@ -1,115 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2017 Á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.core.io;
|
||||
|
||||
import android.support.annotation.*;
|
||||
|
||||
import org.isoron.uhabits.core.database.*;
|
||||
import org.isoron.uhabits.core.models.*;
|
||||
import org.isoron.uhabits.core.models.sqlite.records.*;
|
||||
|
||||
import java.io.*;
|
||||
import java.util.*;
|
||||
|
||||
import javax.inject.*;
|
||||
|
||||
import static org.isoron.uhabits.core.Config.*;
|
||||
|
||||
/**
|
||||
* Class that imports data from database files exported by Loop Habit Tracker.
|
||||
*/
|
||||
public class LoopDBImporter extends AbstractImporter
|
||||
{
|
||||
@NonNull
|
||||
private final ModelFactory modelFactory;
|
||||
|
||||
@NonNull
|
||||
private final DatabaseOpener opener;
|
||||
|
||||
@Inject
|
||||
public LoopDBImporter(@NonNull HabitList habitList,
|
||||
@NonNull ModelFactory modelFactory,
|
||||
@NonNull DatabaseOpener opener)
|
||||
{
|
||||
super(habitList);
|
||||
this.modelFactory = modelFactory;
|
||||
this.opener = opener;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean canHandle(@NonNull File file) throws IOException
|
||||
{
|
||||
if (!isSQLite3File(file)) return false;
|
||||
|
||||
Database db = opener.open(file);
|
||||
boolean canHandle = true;
|
||||
|
||||
Cursor c = db.query("select count(*) from SQLITE_MASTER " +
|
||||
"where name='Habits' or name='Repetitions'");
|
||||
|
||||
if (!c.moveToNext() || c.getInt(0) != 2)
|
||||
{
|
||||
// Log.w("LoopDBImporter", "Cannot handle file: tables not found");
|
||||
canHandle = false;
|
||||
}
|
||||
|
||||
if (db.getVersion() > DATABASE_VERSION)
|
||||
{
|
||||
// Log.w("LoopDBImporter", String.format(
|
||||
// "Cannot handle file: incompatible version: %d > %d",
|
||||
// db.getVersion(), DATABASE_VERSION));
|
||||
canHandle = false;
|
||||
}
|
||||
|
||||
c.close();
|
||||
db.close();
|
||||
return canHandle;
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized void importHabitsFromFile(@NonNull File file)
|
||||
throws IOException
|
||||
{
|
||||
Database db = opener.open(file);
|
||||
MigrationHelper helper = new MigrationHelper(db);
|
||||
helper.migrateTo(DATABASE_VERSION);
|
||||
|
||||
Repository<HabitRecord> habitsRepository;
|
||||
Repository<RepetitionRecord> repsRepository;
|
||||
habitsRepository = new Repository<>(HabitRecord.class, db);
|
||||
repsRepository = new Repository<>(RepetitionRecord.class, db);
|
||||
|
||||
for (HabitRecord habitRecord : habitsRepository.findAll(
|
||||
"order by position"))
|
||||
{
|
||||
Habit h = modelFactory.buildHabit();
|
||||
habitRecord.copyTo(h);
|
||||
h.setId(null);
|
||||
habitList.add(h);
|
||||
|
||||
List<RepetitionRecord> reps =
|
||||
repsRepository.findAll("where habit = ?",
|
||||
habitRecord.id.toString());
|
||||
|
||||
for (RepetitionRecord r : reps)
|
||||
h.getRepetitions().toggle(new Timestamp(r.timestamp), r.value);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,214 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2017 Á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.core.io;
|
||||
|
||||
import android.support.annotation.*;
|
||||
|
||||
import org.isoron.uhabits.core.database.*;
|
||||
import org.isoron.uhabits.core.models.*;
|
||||
import org.isoron.uhabits.core.utils.*;
|
||||
|
||||
import java.io.*;
|
||||
import java.util.*;
|
||||
|
||||
import javax.inject.*;
|
||||
|
||||
/**
|
||||
* Class that imports database files exported by Rewire.
|
||||
*/
|
||||
public class RewireDBImporter extends AbstractImporter
|
||||
{
|
||||
private ModelFactory modelFactory;
|
||||
|
||||
@NonNull
|
||||
private final DatabaseOpener opener;
|
||||
|
||||
@Inject
|
||||
public RewireDBImporter(@NonNull HabitList habits,
|
||||
@NonNull ModelFactory modelFactory,
|
||||
@NonNull DatabaseOpener opener)
|
||||
{
|
||||
super(habits);
|
||||
this.modelFactory = modelFactory;
|
||||
this.opener = opener;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean canHandle(@NonNull File file) throws IOException
|
||||
{
|
||||
if (!isSQLite3File(file)) return false;
|
||||
|
||||
Database db = opener.open(file);
|
||||
Cursor c = db.query("select count(*) from SQLITE_MASTER " +
|
||||
"where name='CHECKINS' or name='UNIT'");
|
||||
|
||||
boolean result = (c.moveToNext() && c.getInt(0) == 2);
|
||||
|
||||
c.close();
|
||||
db.close();
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void importHabitsFromFile(@NonNull File file) throws IOException
|
||||
{
|
||||
Database db = opener.open(file);
|
||||
db.beginTransaction();
|
||||
createHabits(db);
|
||||
db.setTransactionSuccessful();
|
||||
db.endTransaction();
|
||||
db.close();
|
||||
}
|
||||
|
||||
private void createHabits(Database db)
|
||||
{
|
||||
Cursor c = null;
|
||||
|
||||
try
|
||||
{
|
||||
c = db.query("select _id, name, description, schedule, " +
|
||||
"active_days, repeating_count, days, period " +
|
||||
"from habits");
|
||||
if (!c.moveToNext()) return;
|
||||
|
||||
do
|
||||
{
|
||||
int id = c.getInt(0);
|
||||
String name = c.getString(1);
|
||||
String description = c.getString(2);
|
||||
int schedule = c.getInt(3);
|
||||
String activeDays = c.getString(4);
|
||||
int repeatingCount = c.getInt(5);
|
||||
int days = c.getInt(6);
|
||||
int periodIndex = c.getInt(7);
|
||||
|
||||
Habit habit = modelFactory.buildHabit();
|
||||
habit.setName(name);
|
||||
habit.setDescription(description);
|
||||
|
||||
int periods[] = { 7, 31, 365 };
|
||||
int numerator, denominator;
|
||||
|
||||
switch (schedule)
|
||||
{
|
||||
case 0:
|
||||
numerator = activeDays.split(",").length;
|
||||
denominator = 7;
|
||||
break;
|
||||
|
||||
case 1:
|
||||
numerator = days;
|
||||
denominator = (periods[periodIndex]);
|
||||
break;
|
||||
|
||||
case 2:
|
||||
numerator = 1;
|
||||
denominator = repeatingCount;
|
||||
break;
|
||||
|
||||
default:
|
||||
throw new IllegalStateException();
|
||||
}
|
||||
|
||||
habit.setFrequency(new Frequency(numerator, denominator));
|
||||
habitList.add(habit);
|
||||
|
||||
createReminder(db, habit, id);
|
||||
createCheckmarks(db, habit, id);
|
||||
|
||||
} while (c.moveToNext());
|
||||
}
|
||||
finally
|
||||
{
|
||||
if (c != null) c.close();
|
||||
}
|
||||
}
|
||||
|
||||
private void createCheckmarks(@NonNull Database db,
|
||||
@NonNull Habit habit,
|
||||
int rewireHabitId)
|
||||
{
|
||||
Cursor c = null;
|
||||
|
||||
try
|
||||
{
|
||||
String[] params = { Integer.toString(rewireHabitId) };
|
||||
c = db.query(
|
||||
"select distinct date from checkins where habit_id=? and type=2",
|
||||
params);
|
||||
if (!c.moveToNext()) return;
|
||||
|
||||
do
|
||||
{
|
||||
String date = c.getString(0);
|
||||
int year = Integer.parseInt(date.substring(0, 4));
|
||||
int month = Integer.parseInt(date.substring(4, 6));
|
||||
int day = Integer.parseInt(date.substring(6, 8));
|
||||
|
||||
GregorianCalendar cal = DateUtils.getStartOfTodayCalendar();
|
||||
cal.set(year, month - 1, day);
|
||||
|
||||
habit.getRepetitions().toggle(new Timestamp(cal));
|
||||
} while (c.moveToNext());
|
||||
}
|
||||
finally
|
||||
{
|
||||
if (c != null) c.close();
|
||||
}
|
||||
}
|
||||
|
||||
private void createReminder(Database db, Habit habit, int rewireHabitId)
|
||||
{
|
||||
String[] params = { Integer.toString(rewireHabitId) };
|
||||
Cursor c = null;
|
||||
|
||||
try
|
||||
{
|
||||
c = db.query(
|
||||
"select time, active_days from reminders where habit_id=? limit 1",
|
||||
params);
|
||||
|
||||
if (!c.moveToNext()) return;
|
||||
int rewireReminder = Integer.parseInt(c.getString(0));
|
||||
if (rewireReminder <= 0 || rewireReminder >= 1440) return;
|
||||
|
||||
boolean reminderDays[] = new boolean[7];
|
||||
|
||||
String activeDays[] = c.getString(1).split(",");
|
||||
for (String d : activeDays)
|
||||
{
|
||||
int idx = (Integer.parseInt(d) + 1) % 7;
|
||||
reminderDays[idx] = true;
|
||||
}
|
||||
|
||||
int hour = rewireReminder / 60;
|
||||
int minute = rewireReminder % 60;
|
||||
WeekdayList days = new WeekdayList(reminderDays);
|
||||
|
||||
Reminder reminder = new Reminder(hour, minute, days);
|
||||
habit.setReminder(reminder);
|
||||
habitList.update(habit);
|
||||
}
|
||||
finally
|
||||
{
|
||||
if (c != null) c.close();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,143 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2017 Á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.core.io;
|
||||
|
||||
import android.support.annotation.*;
|
||||
|
||||
import org.isoron.uhabits.core.database.*;
|
||||
import org.isoron.uhabits.core.models.*;
|
||||
import org.isoron.uhabits.core.utils.*;
|
||||
|
||||
import java.io.*;
|
||||
import java.util.*;
|
||||
|
||||
import javax.inject.*;
|
||||
|
||||
/**
|
||||
* Class that imports data from database files exported by Tickmate.
|
||||
*/
|
||||
public class TickmateDBImporter extends AbstractImporter
|
||||
{
|
||||
private ModelFactory modelFactory;
|
||||
|
||||
@NonNull
|
||||
private final DatabaseOpener opener;
|
||||
|
||||
@Inject
|
||||
public TickmateDBImporter(@NonNull HabitList habits,
|
||||
@NonNull ModelFactory modelFactory,
|
||||
@NonNull DatabaseOpener opener)
|
||||
{
|
||||
super(habits);
|
||||
this.modelFactory = modelFactory;
|
||||
this.opener = opener;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean canHandle(@NonNull File file) throws IOException
|
||||
{
|
||||
if (!isSQLite3File(file)) return false;
|
||||
|
||||
Database db = opener.open(file);
|
||||
Cursor c = db.query("select count(*) from SQLITE_MASTER " +
|
||||
"where name='tracks' or name='track2groups'");
|
||||
|
||||
boolean result = (c.moveToNext() && c.getInt(0) == 2);
|
||||
|
||||
c.close();
|
||||
db.close();
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void importHabitsFromFile(@NonNull File file) throws IOException
|
||||
{
|
||||
final Database db = opener.open(file);
|
||||
db.beginTransaction();
|
||||
createHabits(db);
|
||||
db.setTransactionSuccessful();
|
||||
db.endTransaction();
|
||||
db.close();
|
||||
}
|
||||
|
||||
private void createCheckmarks(@NonNull Database db,
|
||||
@NonNull Habit habit,
|
||||
int tickmateTrackId)
|
||||
{
|
||||
Cursor c = null;
|
||||
|
||||
try
|
||||
{
|
||||
String[] params = {Integer.toString(tickmateTrackId)};
|
||||
c = db.query(
|
||||
"select distinct year, month, day from ticks where _track_id=?",
|
||||
params);
|
||||
if (!c.moveToNext()) return;
|
||||
|
||||
do
|
||||
{
|
||||
int year = c.getInt(0);
|
||||
int month = c.getInt(1);
|
||||
int day = c.getInt(2);
|
||||
|
||||
GregorianCalendar cal = DateUtils.getStartOfTodayCalendar();
|
||||
cal.set(year, month, day);
|
||||
|
||||
habit.getRepetitions().toggle(new Timestamp(cal));
|
||||
} while (c.moveToNext());
|
||||
}
|
||||
finally
|
||||
{
|
||||
if (c != null) c.close();
|
||||
}
|
||||
}
|
||||
|
||||
private void createHabits(Database db)
|
||||
{
|
||||
Cursor c = null;
|
||||
|
||||
try
|
||||
{
|
||||
c = db.query("select _id, name, description from tracks",
|
||||
new String[0]);
|
||||
if (!c.moveToNext()) return;
|
||||
|
||||
do
|
||||
{
|
||||
int id = c.getInt(0);
|
||||
String name = c.getString(1);
|
||||
String description = c.getString(2);
|
||||
|
||||
Habit habit = modelFactory.buildHabit();
|
||||
habit.setName(name);
|
||||
habit.setDescription(description);
|
||||
habit.setFrequency(Frequency.DAILY);
|
||||
habitList.add(habit);
|
||||
|
||||
createCheckmarks(db, habit, id);
|
||||
|
||||
} while (c.moveToNext());
|
||||
}
|
||||
finally
|
||||
{
|
||||
if (c != null) c.close();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,118 +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.core.models;
|
||||
|
||||
import org.apache.commons.lang3.builder.*;
|
||||
|
||||
import javax.annotation.concurrent.*;
|
||||
|
||||
import static org.isoron.uhabits.core.utils.StringUtils.defaultToStringStyle;
|
||||
|
||||
/**
|
||||
* A Checkmark represents the completion status of the habit for a given day.
|
||||
* <p>
|
||||
* While repetitions simply record that the habit was performed at a given date,
|
||||
* a checkmark provides more information, such as whether a repetition was
|
||||
* expected at that day or not.
|
||||
* <p>
|
||||
* Checkmarks are computed automatically from the list of repetitions.
|
||||
*/
|
||||
@ThreadSafe
|
||||
public final class Checkmark
|
||||
{
|
||||
/**
|
||||
* Indicates that there was a repetition at the timestamp.
|
||||
*/
|
||||
public static final int CHECKED_EXPLICITLY = 2;
|
||||
|
||||
/**
|
||||
* Indicates that there was no repetition at the timestamp, but one was not
|
||||
* expected in any case, due to the frequency of the habit.
|
||||
*/
|
||||
public static final int CHECKED_IMPLICITLY = 1;
|
||||
|
||||
/**
|
||||
* Indicates that there was no repetition at the timestamp, even though a
|
||||
* repetition was expected.
|
||||
*/
|
||||
public static final int UNCHECKED = 0;
|
||||
|
||||
private final Timestamp timestamp;
|
||||
|
||||
/**
|
||||
* The value of the checkmark.
|
||||
* <p>
|
||||
* For boolean habits, this equals either UNCHECKED, CHECKED_EXPLICITLY,
|
||||
* or CHECKED_IMPLICITLY.
|
||||
* <p>
|
||||
* 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;
|
||||
|
||||
public Checkmark(Timestamp timestamp, int value)
|
||||
{
|
||||
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;
|
||||
|
||||
Checkmark checkmark = (Checkmark) o;
|
||||
|
||||
return new EqualsBuilder()
|
||||
.append(timestamp, checkmark.timestamp)
|
||||
.append(value, checkmark.value)
|
||||
.isEquals();
|
||||
}
|
||||
|
||||
public Timestamp getTimestamp()
|
||||
{
|
||||
return timestamp;
|
||||
}
|
||||
|
||||
public int getValue()
|
||||
{
|
||||
return value;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode()
|
||||
{
|
||||
return new HashCodeBuilder(17, 37)
|
||||
.append(timestamp)
|
||||
.append(value)
|
||||
.toHashCode();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString()
|
||||
{
|
||||
return new ToStringBuilder(this, defaultToStringStyle())
|
||||
.append("timestamp", timestamp)
|
||||
.append("value", value)
|
||||
.toString();
|
||||
}
|
||||
}
|
||||
@@ -1,444 +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.core.models;
|
||||
|
||||
import android.support.annotation.*;
|
||||
|
||||
import org.apache.commons.lang3.builder.*;
|
||||
import org.isoron.uhabits.core.utils.*;
|
||||
|
||||
import java.io.*;
|
||||
import java.text.*;
|
||||
import java.util.*;
|
||||
|
||||
import javax.annotation.concurrent.*;
|
||||
|
||||
import static org.isoron.uhabits.core.models.Checkmark.*;
|
||||
import static org.isoron.uhabits.core.utils.StringUtils.defaultToStringStyle;
|
||||
|
||||
/**
|
||||
* The collection of {@link Checkmark}s belonging to a habit.
|
||||
*/
|
||||
@ThreadSafe
|
||||
public abstract class CheckmarkList
|
||||
{
|
||||
protected final Habit habit;
|
||||
|
||||
public final ModelObservable observable;
|
||||
|
||||
public CheckmarkList(Habit habit)
|
||||
{
|
||||
this.habit = habit;
|
||||
this.observable = new ModelObservable();
|
||||
}
|
||||
|
||||
@NonNull
|
||||
static List<Checkmark> buildCheckmarksFromIntervals(Repetition[] reps,
|
||||
ArrayList<Interval> intervals)
|
||||
{
|
||||
if (reps.length == 0) throw new IllegalArgumentException();
|
||||
|
||||
Timestamp today = DateUtils.getToday();
|
||||
Timestamp begin = reps[0].getTimestamp();
|
||||
if (intervals.size() > 0) begin = Timestamp.oldest(begin, intervals.get(0).begin);
|
||||
|
||||
int nDays = begin.daysUntil(today) + 1;
|
||||
List<Checkmark> checkmarks = new ArrayList<>(nDays);
|
||||
for (int i = 0; i < nDays; i++)
|
||||
checkmarks.add(new Checkmark(today.minus(i), UNCHECKED));
|
||||
|
||||
for (Interval interval : intervals)
|
||||
{
|
||||
for (int i = 0; i < interval.length(); i++)
|
||||
{
|
||||
Timestamp date = interval.begin.plus(i);
|
||||
int offset = date.daysUntil(today);
|
||||
if (offset < 0) continue;
|
||||
checkmarks.set(offset, new Checkmark(date, CHECKED_IMPLICITLY));
|
||||
}
|
||||
}
|
||||
|
||||
for (Repetition rep : reps)
|
||||
{
|
||||
Timestamp date = rep.getTimestamp();
|
||||
int offset = date.daysUntil(today);
|
||||
checkmarks.set(offset, new Checkmark(date, CHECKED_EXPLICITLY));
|
||||
}
|
||||
|
||||
return checkmarks;
|
||||
}
|
||||
|
||||
/**
|
||||
* For non-daily habits, some groups of repetitions generate many
|
||||
* checkmarks. For example, for weekly habits, each repetition generates
|
||||
* seven checkmarks. For twice-a-week habits, two repetitions that are close
|
||||
* enough together also generate seven checkmarks.
|
||||
* <p>
|
||||
* This group of generated checkmarks, for a given set of repetition, is
|
||||
* represented by an interval. This function computes the list of intervals
|
||||
* for a given list of repetitions. It tries to build the intervals as far
|
||||
* away in the future as possible.
|
||||
*/
|
||||
@NonNull
|
||||
static ArrayList<Interval> buildIntervals(@NonNull Frequency freq,
|
||||
@NonNull Repetition[] reps)
|
||||
{
|
||||
int num = freq.getNumerator();
|
||||
int den = freq.getDenominator();
|
||||
|
||||
ArrayList<Interval> intervals = new ArrayList<>();
|
||||
for (int i = 0; i < reps.length - num + 1; i++)
|
||||
{
|
||||
Repetition first = reps[i];
|
||||
Repetition last = reps[i + num - 1];
|
||||
|
||||
long distance = first.getTimestamp().daysUntil(last.getTimestamp());
|
||||
if (distance >= den) continue;
|
||||
|
||||
Timestamp begin = first.getTimestamp();
|
||||
Timestamp center = last.getTimestamp();
|
||||
Timestamp end = begin.plus(den - 1);
|
||||
intervals.add(new Interval(begin, center, end));
|
||||
}
|
||||
|
||||
return intervals;
|
||||
}
|
||||
|
||||
/**
|
||||
* Starting from the oldest interval, this function tries to slide the
|
||||
* intervals backwards into the past, so that gaps are eliminated and
|
||||
* streaks are maximized. When it detects that sliding an interval
|
||||
* would not help fixing any gap, it leaves the interval unchanged.
|
||||
*/
|
||||
static void snapIntervalsTogether(@NonNull ArrayList<Interval> intervals)
|
||||
{
|
||||
for (int i = 1; i < intervals.size(); i++)
|
||||
{
|
||||
Interval curr = intervals.get(i);
|
||||
Interval prev = intervals.get(i - 1);
|
||||
|
||||
int gap = prev.end.daysUntil(curr.begin) - 1;
|
||||
if (gap <= 0 || curr.end.minus(gap).isOlderThan(curr.center)) continue;
|
||||
intervals.set(i, new Interval(curr.begin.minus(gap), curr.center,
|
||||
curr.end.minus(gap)));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds all the given checkmarks to the list.
|
||||
* <p>
|
||||
* This should never be called by the application, since the checkmarks are
|
||||
* computed automatically from the list of repetitions.
|
||||
*
|
||||
* @param checkmarks the checkmarks to be added.
|
||||
*/
|
||||
public abstract void add(List<Checkmark> checkmarks);
|
||||
|
||||
/**
|
||||
* Returns the values for all the checkmarks, since the oldest repetition of
|
||||
* the habit until today.
|
||||
* <p>
|
||||
* If there are no repetitions at all, returns an empty array. The values
|
||||
* are returned in an array containing one integer value for each day since
|
||||
* the first repetition of the habit until today. The first entry
|
||||
* corresponds to today, the second entry corresponds to yesterday, and so
|
||||
* on.
|
||||
*
|
||||
* @return values for the checkmarks in the interval
|
||||
*/
|
||||
@NonNull
|
||||
public synchronized final int[] getAllValues()
|
||||
{
|
||||
Repetition oldestRep = habit.getRepetitions().getOldest();
|
||||
if (oldestRep == null) return new int[0];
|
||||
|
||||
Timestamp fromTimestamp = oldestRep.getTimestamp();
|
||||
Timestamp toTimestamp = DateUtils.getToday();
|
||||
|
||||
return getValues(fromTimestamp, toTimestamp);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the list of checkmarks that fall within the given interval.
|
||||
* <p>
|
||||
* There is exactly one checkmark per day in the interval. The endpoints of
|
||||
* the interval are included. The list is ordered by timestamp (decreasing).
|
||||
* That is, the first checkmark corresponds to the newest timestamp, and the
|
||||
* last checkmark corresponds to the oldest timestamp.
|
||||
*
|
||||
* @param fromTimestamp timestamp of the beginning of the interval.
|
||||
* @param toTimestamp timestamp of the end of the interval.
|
||||
* @return the list of checkmarks within the interval.
|
||||
*/
|
||||
@NonNull
|
||||
public abstract List<Checkmark> getByInterval(Timestamp fromTimestamp,
|
||||
Timestamp toTimestamp);
|
||||
|
||||
/**
|
||||
* Returns the checkmark for today.
|
||||
*
|
||||
* @return checkmark for today
|
||||
*/
|
||||
@Nullable
|
||||
public synchronized final Checkmark getToday()
|
||||
{
|
||||
compute();
|
||||
Timestamp today = DateUtils.getToday();
|
||||
return getByInterval(today, today).get(0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the value of today's checkmark.
|
||||
*
|
||||
* @return value of today's checkmark
|
||||
*/
|
||||
public synchronized int getTodayValue()
|
||||
{
|
||||
Checkmark today = getToday();
|
||||
if (today != null) return today.getValue();
|
||||
else return UNCHECKED;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the values of the checkmarks that fall inside a certain interval
|
||||
* of time.
|
||||
* <p>
|
||||
* The values are returned in an array containing one integer value for each
|
||||
* day of the interval. The first entry corresponds to the most recent day
|
||||
* in the interval. Each subsequent entry corresponds to one day older than
|
||||
* the previous entry. The boundaries of the time interval are included.
|
||||
*
|
||||
* @param from timestamp for the oldest checkmark
|
||||
* @param to timestamp for the newest checkmark
|
||||
* @return values for the checkmarks inside the given interval
|
||||
*/
|
||||
public final int[] getValues(Timestamp from, Timestamp to)
|
||||
{
|
||||
if (from.isNewerThan(to)) return new int[0];
|
||||
|
||||
List<Checkmark> checkmarks = getByInterval(from, to);
|
||||
int values[] = new int[checkmarks.size()];
|
||||
|
||||
int i = 0;
|
||||
for (Checkmark c : checkmarks)
|
||||
values[i++] = c.getValue();
|
||||
|
||||
return values;
|
||||
}
|
||||
|
||||
/**
|
||||
* Marks as invalid every checkmark that has timestamp either equal or newer
|
||||
* than a given timestamp. These checkmarks will be recomputed at the next
|
||||
* time they are queried.
|
||||
*
|
||||
* @param timestamp the timestamp
|
||||
*/
|
||||
public abstract void invalidateNewerThan(Timestamp timestamp);
|
||||
|
||||
/**
|
||||
* Writes the entire list of checkmarks to the given writer, in CSV format.
|
||||
*
|
||||
* @param out the writer where the CSV will be output
|
||||
* @throws IOException in case write operations fail
|
||||
*/
|
||||
public final void writeCSV(Writer out) throws IOException
|
||||
{
|
||||
int values[];
|
||||
|
||||
synchronized (this)
|
||||
{
|
||||
compute();
|
||||
values = getAllValues();
|
||||
}
|
||||
|
||||
Timestamp timestamp = DateUtils.getToday();
|
||||
SimpleDateFormat dateFormat = DateFormats.getCSVDateFormat();
|
||||
|
||||
for (int value : values)
|
||||
{
|
||||
String date = dateFormat.format(timestamp.toJavaDate());
|
||||
out.write(String.format("%s,%d\n", date, value));
|
||||
timestamp = timestamp.minus(1);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Computes and stores one checkmark for each day, from the first habit
|
||||
* repetition to today. If this list is already computed, does nothing.
|
||||
*/
|
||||
protected final synchronized void compute()
|
||||
{
|
||||
final Timestamp today = DateUtils.getToday();
|
||||
|
||||
Checkmark newest = getNewestComputed();
|
||||
if (newest != null && newest.getTimestamp().equals(today)) return;
|
||||
invalidateNewerThan(Timestamp.ZERO);
|
||||
|
||||
Repetition oldestRep = habit.getRepetitions().getOldest();
|
||||
if (oldestRep == null) return;
|
||||
final Timestamp from = oldestRep.getTimestamp();
|
||||
|
||||
Repetition reps[] = habit
|
||||
.getRepetitions()
|
||||
.getByInterval(from, today)
|
||||
.toArray(new Repetition[0]);
|
||||
|
||||
if (habit.isNumerical()) computeNumerical(reps);
|
||||
else computeYesNo(reps);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns newest checkmark that has already been computed.
|
||||
*
|
||||
* @return newest checkmark already computed
|
||||
*/
|
||||
@Nullable
|
||||
protected abstract Checkmark getNewestComputed();
|
||||
|
||||
/**
|
||||
* Returns oldest checkmark that has already been computed.
|
||||
*
|
||||
* @return oldest checkmark already computed
|
||||
*/
|
||||
@Nullable
|
||||
protected abstract Checkmark getOldestComputed();
|
||||
|
||||
private void computeNumerical(Repetition[] reps)
|
||||
{
|
||||
if (reps.length == 0) throw new IllegalArgumentException();
|
||||
|
||||
Timestamp today = DateUtils.getToday();
|
||||
Timestamp begin = reps[0].getTimestamp();
|
||||
|
||||
int nDays = begin.daysUntil(today) + 1;
|
||||
List<Checkmark> checkmarks = new ArrayList<>(nDays);
|
||||
for (int i = 0; i < nDays; i++)
|
||||
checkmarks.add(new Checkmark(today.minus(i), 0));
|
||||
|
||||
for (Repetition rep : reps)
|
||||
{
|
||||
int offset = rep.getTimestamp().daysUntil(today);
|
||||
checkmarks.set(offset, new Checkmark(rep.getTimestamp(), rep.getValue()));
|
||||
}
|
||||
|
||||
add(checkmarks);
|
||||
}
|
||||
|
||||
private void computeYesNo(Repetition[] reps)
|
||||
{
|
||||
ArrayList<Interval> intervals;
|
||||
intervals = buildIntervals(habit.getFrequency(), reps);
|
||||
snapIntervalsTogether(intervals);
|
||||
add(buildCheckmarksFromIntervals(reps, intervals));
|
||||
}
|
||||
|
||||
public List<Checkmark> getAll() {
|
||||
Repetition oldest = habit.getRepetitions().getOldest();
|
||||
if(oldest == null) return new ArrayList<>();
|
||||
return getByInterval(oldest.getTimestamp(), DateUtils.getToday());
|
||||
}
|
||||
|
||||
static final class Interval
|
||||
{
|
||||
final Timestamp begin;
|
||||
|
||||
final Timestamp center;
|
||||
|
||||
final Timestamp end;
|
||||
|
||||
Interval(Timestamp begin, Timestamp center, Timestamp end)
|
||||
{
|
||||
this.begin = begin;
|
||||
this.center = center;
|
||||
this.end = end;
|
||||
}
|
||||
|
||||
public int length() {
|
||||
return begin.daysUntil(end) + 1;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o)
|
||||
{
|
||||
if (this == o) return true;
|
||||
|
||||
if (o == null || getClass() != o.getClass()) return false;
|
||||
|
||||
Interval interval = (Interval) o;
|
||||
|
||||
return new EqualsBuilder()
|
||||
.append(begin, interval.begin)
|
||||
.append(center, interval.center)
|
||||
.append(end, interval.end)
|
||||
.isEquals();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode()
|
||||
{
|
||||
return new HashCodeBuilder(17, 37)
|
||||
.append(begin)
|
||||
.append(center)
|
||||
.append(end)
|
||||
.toHashCode();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString()
|
||||
{
|
||||
return new ToStringBuilder(this, defaultToStringStyle())
|
||||
.append("begin", begin)
|
||||
.append("center", center)
|
||||
.append("end", end)
|
||||
.toString();
|
||||
}
|
||||
}
|
||||
|
||||
@NonNull
|
||||
public List<Checkmark> groupBy(DateUtils.TruncateField field)
|
||||
{
|
||||
List<Checkmark> checks = getAll();
|
||||
|
||||
int count = 0;
|
||||
Timestamp truncatedTimestamps[] = new Timestamp[checks.size()];
|
||||
int values[] = new int[checks.size()];
|
||||
|
||||
for (Checkmark rep : checks)
|
||||
{
|
||||
Timestamp tt = rep.getTimestamp().truncate(field);
|
||||
if (count == 0 || !truncatedTimestamps[count - 1].equals(tt))
|
||||
truncatedTimestamps[count++] = tt;
|
||||
|
||||
if(habit.isNumerical())
|
||||
values[count - 1] += rep.getValue();
|
||||
else if(rep.getValue() == Checkmark.CHECKED_EXPLICITLY)
|
||||
values[count - 1] += 1000;
|
||||
}
|
||||
|
||||
ArrayList<Checkmark> groupedCheckmarks = new ArrayList<>();
|
||||
for (int i = 0; i < count; i++)
|
||||
{
|
||||
Checkmark rep = new Checkmark(truncatedTimestamps[i], values[i]);
|
||||
groupedCheckmarks.add(rep);
|
||||
}
|
||||
|
||||
return groupedCheckmarks;
|
||||
}
|
||||
}
|
||||
@@ -1,102 +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.core.models;
|
||||
|
||||
import org.apache.commons.lang3.builder.*;
|
||||
|
||||
import javax.annotation.concurrent.*;
|
||||
|
||||
import static org.isoron.uhabits.core.utils.StringUtils.defaultToStringStyle;
|
||||
|
||||
/**
|
||||
* Represents how often is the habit repeated.
|
||||
*/
|
||||
@ThreadSafe
|
||||
public class Frequency
|
||||
{
|
||||
public static final Frequency DAILY = new Frequency(1, 1);
|
||||
|
||||
public static final Frequency FIVE_TIMES_PER_WEEK = new Frequency(5, 7);
|
||||
|
||||
public static final Frequency THREE_TIMES_PER_WEEK = new Frequency(3, 7);
|
||||
|
||||
public static final Frequency TWO_TIMES_PER_WEEK = new Frequency(2, 7);
|
||||
|
||||
public static final Frequency WEEKLY = new Frequency(1, 7);
|
||||
|
||||
private final int numerator;
|
||||
|
||||
private final int denominator;
|
||||
|
||||
public Frequency(int numerator, int denominator)
|
||||
{
|
||||
if (numerator == denominator) numerator = denominator = 1;
|
||||
|
||||
this.numerator = numerator;
|
||||
this.denominator = denominator;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o)
|
||||
{
|
||||
if (this == o) return true;
|
||||
if (o == null || getClass() != o.getClass()) return false;
|
||||
|
||||
Frequency frequency = (Frequency) o;
|
||||
|
||||
return new EqualsBuilder()
|
||||
.append(numerator, frequency.numerator)
|
||||
.append(denominator, frequency.denominator)
|
||||
.isEquals();
|
||||
}
|
||||
|
||||
public int getDenominator()
|
||||
{
|
||||
return denominator;
|
||||
}
|
||||
|
||||
public int getNumerator()
|
||||
{
|
||||
return numerator;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode()
|
||||
{
|
||||
return new HashCodeBuilder(17, 37)
|
||||
.append(numerator)
|
||||
.append(denominator)
|
||||
.toHashCode();
|
||||
}
|
||||
|
||||
public double toDouble()
|
||||
{
|
||||
return (double) numerator / denominator;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString()
|
||||
{
|
||||
return new ToStringBuilder(this, defaultToStringStyle())
|
||||
.append("numerator", numerator)
|
||||
.append("denominator", denominator)
|
||||
.toString();
|
||||
}
|
||||
}
|
||||
@@ -1,484 +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.core.models;
|
||||
|
||||
import android.support.annotation.*;
|
||||
|
||||
import org.apache.commons.lang3.builder.*;
|
||||
|
||||
import java.util.*;
|
||||
|
||||
import javax.annotation.concurrent.*;
|
||||
import javax.inject.*;
|
||||
|
||||
import static org.isoron.uhabits.core.models.Checkmark.*;
|
||||
import static org.isoron.uhabits.core.utils.StringUtils.defaultToStringStyle;
|
||||
|
||||
/**
|
||||
* The thing that the user wants to track.
|
||||
*/
|
||||
@ThreadSafe
|
||||
public class Habit
|
||||
{
|
||||
public static final int AT_LEAST = 0;
|
||||
|
||||
public static final int AT_MOST = 1;
|
||||
|
||||
public static final String HABIT_URI_FORMAT =
|
||||
"content://org.isoron.uhabits/habit/%d";
|
||||
|
||||
public static final int NUMBER_HABIT = 1;
|
||||
|
||||
public static final int YES_NO_HABIT = 0;
|
||||
|
||||
@Nullable
|
||||
public Long id;
|
||||
|
||||
@NonNull
|
||||
private HabitData data;
|
||||
|
||||
@NonNull
|
||||
private StreakList streaks;
|
||||
|
||||
@NonNull
|
||||
private ScoreList scores;
|
||||
|
||||
@NonNull
|
||||
private RepetitionList repetitions;
|
||||
|
||||
@NonNull
|
||||
private CheckmarkList checkmarks;
|
||||
|
||||
private ModelObservable observable = new ModelObservable();
|
||||
|
||||
/**
|
||||
* Constructs a habit with default data.
|
||||
* <p>
|
||||
* The habit is not archived, not highlighted, has no reminders and is
|
||||
* placed in the last position of the list of habits.
|
||||
*/
|
||||
@Inject
|
||||
Habit(@NonNull ModelFactory factory)
|
||||
{
|
||||
this.data = new HabitData();
|
||||
checkmarks = factory.buildCheckmarkList(this);
|
||||
streaks = factory.buildStreakList(this);
|
||||
scores = factory.buildScoreList(this);
|
||||
repetitions = factory.buildRepetitionList(this);
|
||||
}
|
||||
|
||||
Habit(@NonNull ModelFactory factory, @NonNull HabitData data)
|
||||
{
|
||||
this.data = new HabitData(data);
|
||||
checkmarks = factory.buildCheckmarkList(this);
|
||||
streaks = factory.buildStreakList(this);
|
||||
scores = factory.buildScoreList(this);
|
||||
repetitions = factory.buildRepetitionList(this);
|
||||
observable = new ModelObservable();
|
||||
}
|
||||
|
||||
/**
|
||||
* Clears the reminder for a habit.
|
||||
*/
|
||||
public synchronized void clearReminder()
|
||||
{
|
||||
data.reminder = null;
|
||||
observable.notifyListeners();
|
||||
}
|
||||
|
||||
/**
|
||||
* Copies all the attributes of the specified habit into this habit
|
||||
*
|
||||
* @param model the model whose attributes should be copied from
|
||||
*/
|
||||
public synchronized void copyFrom(@NonNull Habit model)
|
||||
{
|
||||
this.data = new HabitData(model.data);
|
||||
observable.notifyListeners();
|
||||
}
|
||||
|
||||
/**
|
||||
* List of checkmarks belonging to this habit.
|
||||
*/
|
||||
@NonNull
|
||||
public synchronized CheckmarkList getCheckmarks()
|
||||
{
|
||||
return checkmarks;
|
||||
}
|
||||
|
||||
/**
|
||||
* Color of the habit.
|
||||
* <p>
|
||||
* This number is not an android.graphics.Color, but an index to the
|
||||
* activity color palette, which changes according to the theme. To convert
|
||||
* this color into an android.graphics.Color, use ColorHelper.getColor(context,
|
||||
* habit.color).
|
||||
*/
|
||||
@NonNull
|
||||
public synchronized Integer getColor()
|
||||
{
|
||||
return data.color;
|
||||
}
|
||||
|
||||
public synchronized void setColor(@NonNull Integer color)
|
||||
{
|
||||
data.color = color;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
public synchronized String getDescription()
|
||||
{
|
||||
return data.description;
|
||||
}
|
||||
|
||||
public synchronized void setDescription(@NonNull String description)
|
||||
{
|
||||
data.description = description;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
public synchronized Frequency getFrequency()
|
||||
{
|
||||
return data.frequency;
|
||||
}
|
||||
|
||||
public synchronized void setFrequency(@NonNull Frequency frequency)
|
||||
{
|
||||
data.frequency = frequency;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public synchronized Long getId()
|
||||
{
|
||||
return id;
|
||||
}
|
||||
|
||||
public synchronized void setId(@Nullable Long id)
|
||||
{
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
public synchronized String getName()
|
||||
{
|
||||
return data.name;
|
||||
}
|
||||
|
||||
public synchronized void setName(@NonNull String name)
|
||||
{
|
||||
data.name = name;
|
||||
}
|
||||
|
||||
public ModelObservable getObservable()
|
||||
{
|
||||
return observable;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the reminder for this habit.
|
||||
* <p>
|
||||
* Before calling this method, you should call {@link #hasReminder()} to
|
||||
* verify that a reminder does exist, otherwise an exception will be
|
||||
* thrown.
|
||||
*
|
||||
* @return the reminder for this habit
|
||||
* @throws IllegalStateException if habit has no reminder
|
||||
*/
|
||||
@NonNull
|
||||
public synchronized Reminder getReminder()
|
||||
{
|
||||
if (data.reminder == null) throw new IllegalStateException();
|
||||
return data.reminder;
|
||||
}
|
||||
|
||||
public synchronized void setReminder(@Nullable Reminder reminder)
|
||||
{
|
||||
data.reminder = reminder;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
public RepetitionList getRepetitions()
|
||||
{
|
||||
return repetitions;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
public ScoreList getScores()
|
||||
{
|
||||
return scores;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
public StreakList getStreaks()
|
||||
{
|
||||
return streaks;
|
||||
}
|
||||
|
||||
public synchronized int getTargetType()
|
||||
{
|
||||
return data.targetType;
|
||||
}
|
||||
|
||||
public synchronized void setTargetType(int targetType)
|
||||
{
|
||||
if (targetType != AT_LEAST && targetType != AT_MOST)
|
||||
throw new IllegalArgumentException(
|
||||
String.format("invalid targetType: %d", targetType));
|
||||
data.targetType = targetType;
|
||||
}
|
||||
|
||||
public synchronized double getTargetValue()
|
||||
{
|
||||
return data.targetValue;
|
||||
}
|
||||
|
||||
public synchronized void setTargetValue(double targetValue)
|
||||
{
|
||||
if (targetValue < 0) throw new IllegalArgumentException();
|
||||
data.targetValue = targetValue;
|
||||
}
|
||||
|
||||
public synchronized int getType()
|
||||
{
|
||||
return data.type;
|
||||
}
|
||||
|
||||
public synchronized void setType(int type)
|
||||
{
|
||||
if (type != YES_NO_HABIT && type != NUMBER_HABIT)
|
||||
throw new IllegalArgumentException();
|
||||
|
||||
data.type = type;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
public synchronized String getUnit()
|
||||
{
|
||||
return data.unit;
|
||||
}
|
||||
|
||||
public synchronized void setUnit(@NonNull String unit)
|
||||
{
|
||||
data.unit = unit;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the public URI that identifies this habit
|
||||
*
|
||||
* @return the URI
|
||||
*/
|
||||
public String getUriString()
|
||||
{
|
||||
return String.format(Locale.US, HABIT_URI_FORMAT, getId());
|
||||
}
|
||||
|
||||
public synchronized boolean hasId()
|
||||
{
|
||||
return getId() != null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether the habit has a reminder.
|
||||
*
|
||||
* @return true if habit has reminder, false otherwise
|
||||
*/
|
||||
public synchronized boolean hasReminder()
|
||||
{
|
||||
return data.reminder != null;
|
||||
}
|
||||
|
||||
public void invalidateNewerThan(Timestamp timestamp)
|
||||
{
|
||||
getScores().invalidateNewerThan(timestamp);
|
||||
getCheckmarks().invalidateNewerThan(timestamp);
|
||||
getStreaks().invalidateNewerThan(timestamp);
|
||||
}
|
||||
|
||||
public synchronized boolean isArchived()
|
||||
{
|
||||
return data.archived;
|
||||
}
|
||||
|
||||
public synchronized void setArchived(boolean archived)
|
||||
{
|
||||
data.archived = archived;
|
||||
}
|
||||
|
||||
public synchronized boolean isCompletedToday()
|
||||
{
|
||||
int todayCheckmark = getCheckmarks().getTodayValue();
|
||||
if (isNumerical())
|
||||
{
|
||||
if(getTargetType() == AT_LEAST)
|
||||
return todayCheckmark >= data.targetValue;
|
||||
else
|
||||
return todayCheckmark <= data.targetValue;
|
||||
}
|
||||
else return (todayCheckmark != UNCHECKED);
|
||||
}
|
||||
|
||||
public synchronized boolean isNumerical()
|
||||
{
|
||||
return data.type == NUMBER_HABIT;
|
||||
}
|
||||
|
||||
public HabitData getData()
|
||||
{
|
||||
return new HabitData(data);
|
||||
}
|
||||
|
||||
public Integer getPosition()
|
||||
{
|
||||
return data.position;
|
||||
}
|
||||
|
||||
public void setPosition(int newPosition)
|
||||
{
|
||||
data.position = newPosition;
|
||||
}
|
||||
|
||||
public static final class HabitData
|
||||
{
|
||||
@NonNull
|
||||
public String name;
|
||||
|
||||
@NonNull
|
||||
public String description;
|
||||
|
||||
@NonNull
|
||||
public Frequency frequency;
|
||||
|
||||
public int color;
|
||||
|
||||
public boolean archived;
|
||||
|
||||
public int targetType;
|
||||
|
||||
public double targetValue;
|
||||
|
||||
public int type;
|
||||
|
||||
@NonNull
|
||||
public String unit;
|
||||
|
||||
@Nullable
|
||||
public Reminder reminder;
|
||||
|
||||
public int position;
|
||||
|
||||
public HabitData()
|
||||
{
|
||||
this.color = 8;
|
||||
this.archived = false;
|
||||
this.frequency = new Frequency(3, 7);
|
||||
this.type = YES_NO_HABIT;
|
||||
this.name = "";
|
||||
this.description = "";
|
||||
this.targetType = AT_LEAST;
|
||||
this.targetValue = 100;
|
||||
this.unit = "";
|
||||
this.position = 0;
|
||||
}
|
||||
|
||||
public HabitData(@NonNull HabitData model)
|
||||
{
|
||||
this.name = model.name;
|
||||
this.description = model.description;
|
||||
this.frequency = model.frequency;
|
||||
this.color = model.color;
|
||||
this.archived = model.archived;
|
||||
this.targetType = model.targetType;
|
||||
this.targetValue = model.targetValue;
|
||||
this.type = model.type;
|
||||
this.unit = model.unit;
|
||||
this.reminder = model.reminder;
|
||||
this.position = model.position;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString()
|
||||
{
|
||||
return new ToStringBuilder(this, defaultToStringStyle())
|
||||
.append("name", name)
|
||||
.append("description", description)
|
||||
.append("frequency", frequency)
|
||||
.append("color", color)
|
||||
.append("archived", archived)
|
||||
.append("targetType", targetType)
|
||||
.append("targetValue", targetValue)
|
||||
.append("type", type)
|
||||
.append("unit", unit)
|
||||
.append("reminder", reminder)
|
||||
.append("position", position)
|
||||
.toString();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o)
|
||||
{
|
||||
if (this == o) return true;
|
||||
|
||||
if (o == null || getClass() != o.getClass()) return false;
|
||||
|
||||
HabitData habitData = (HabitData) o;
|
||||
|
||||
return new EqualsBuilder()
|
||||
.append(color, habitData.color)
|
||||
.append(archived, habitData.archived)
|
||||
.append(targetType, habitData.targetType)
|
||||
.append(targetValue, habitData.targetValue)
|
||||
.append(type, habitData.type)
|
||||
.append(name, habitData.name)
|
||||
.append(description, habitData.description)
|
||||
.append(frequency, habitData.frequency)
|
||||
.append(unit, habitData.unit)
|
||||
.append(reminder, habitData.reminder)
|
||||
.append(position, habitData.position)
|
||||
.isEquals();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode()
|
||||
{
|
||||
return new HashCodeBuilder(17, 37)
|
||||
.append(name)
|
||||
.append(description)
|
||||
.append(frequency)
|
||||
.append(color)
|
||||
.append(archived)
|
||||
.append(targetType)
|
||||
.append(targetValue)
|
||||
.append(type)
|
||||
.append(unit)
|
||||
.append(reminder)
|
||||
.append(position)
|
||||
.toHashCode();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString()
|
||||
{
|
||||
return new ToStringBuilder(this, defaultToStringStyle())
|
||||
.append("id", id)
|
||||
.append("data", data)
|
||||
.toString();
|
||||
}
|
||||
}
|
||||
@@ -1,250 +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.core.models;
|
||||
|
||||
import android.support.annotation.*;
|
||||
|
||||
import com.opencsv.*;
|
||||
|
||||
import org.isoron.uhabits.core.utils.*;
|
||||
|
||||
import java.io.*;
|
||||
import java.util.*;
|
||||
|
||||
import javax.annotation.concurrent.*;
|
||||
|
||||
/**
|
||||
* An ordered collection of {@link Habit}s.
|
||||
*/
|
||||
@ThreadSafe
|
||||
public abstract class HabitList implements Iterable<Habit>
|
||||
{
|
||||
private final ModelObservable observable;
|
||||
|
||||
@NonNull
|
||||
protected final HabitMatcher filter;
|
||||
|
||||
/**
|
||||
* Creates a new HabitList.
|
||||
* <p>
|
||||
* Depending on the implementation, this list can either be empty or be
|
||||
* populated by some pre-existing habits, for example, from a certain
|
||||
* database.
|
||||
*/
|
||||
public HabitList()
|
||||
{
|
||||
observable = new ModelObservable();
|
||||
filter = new HabitMatcherBuilder().setArchivedAllowed(true).build();
|
||||
}
|
||||
|
||||
protected HabitList(@NonNull HabitMatcher filter)
|
||||
{
|
||||
observable = new ModelObservable();
|
||||
this.filter = filter;
|
||||
}
|
||||
|
||||
/**
|
||||
* Inserts a new habit in the list.
|
||||
* <p>
|
||||
* If the id of the habit is null, the list will assign it a new id, which
|
||||
* is guaranteed to be unique in the scope of the list. If id is not null,
|
||||
* the caller should make sure that the list does not already contain
|
||||
* another habit with same id, otherwise a RuntimeException will be thrown.
|
||||
*
|
||||
* @param habit the habit to be inserted
|
||||
* @throws IllegalArgumentException if the habit is already on the list.
|
||||
*/
|
||||
public abstract void add(@NonNull Habit habit)
|
||||
throws IllegalArgumentException;
|
||||
|
||||
/**
|
||||
* Returns the habit with specified id.
|
||||
*
|
||||
* @param id the id of the habit
|
||||
* @return the habit, or null if none exist
|
||||
*/
|
||||
@Nullable
|
||||
public abstract Habit getById(long id);
|
||||
|
||||
/**
|
||||
* Returns the habit that occupies a certain position.
|
||||
*
|
||||
* @param position the position of the desired habit
|
||||
* @return the habit at that position
|
||||
* @throws IndexOutOfBoundsException when the position is invalid
|
||||
*/
|
||||
@NonNull
|
||||
public abstract Habit getByPosition(int position);
|
||||
|
||||
/**
|
||||
* Returns the list of habits that match a given condition.
|
||||
*
|
||||
* @param matcher the matcher that checks the condition
|
||||
* @return the list of matching habits
|
||||
*/
|
||||
@NonNull
|
||||
public abstract HabitList getFiltered(HabitMatcher matcher);
|
||||
|
||||
public ModelObservable getObservable()
|
||||
{
|
||||
return observable;
|
||||
}
|
||||
|
||||
public abstract Order getOrder();
|
||||
|
||||
/**
|
||||
* Changes the order of the elements on the list.
|
||||
*
|
||||
* @param order the new order criterea
|
||||
*/
|
||||
public abstract void setOrder(@NonNull Order order);
|
||||
|
||||
/**
|
||||
* Returns the index of the given habit in the list, or -1 if the list does
|
||||
* not contain the habit.
|
||||
*
|
||||
* @param h the habit
|
||||
* @return the index of the habit, or -1 if not in the list
|
||||
*/
|
||||
public abstract int indexOf(@NonNull Habit h);
|
||||
|
||||
public boolean isEmpty()
|
||||
{
|
||||
return size() == 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes the given habit from the list.
|
||||
* <p>
|
||||
* If the given habit is not in the list, does nothing.
|
||||
*
|
||||
* @param h the habit to be removed.
|
||||
*/
|
||||
public abstract void remove(@NonNull Habit h);
|
||||
|
||||
/**
|
||||
* Removes all the habits from the list.
|
||||
*/
|
||||
public void removeAll()
|
||||
{
|
||||
List<Habit> copy = new LinkedList<>();
|
||||
for (Habit h : this) copy.add(h);
|
||||
for (Habit h : copy) remove(h);
|
||||
observable.notifyListeners();
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 abstract void reorder(@NonNull Habit from, @NonNull Habit to);
|
||||
|
||||
public void repair()
|
||||
{
|
||||
for (Habit h : this)
|
||||
{
|
||||
h.getCheckmarks().invalidateNewerThan(Timestamp.ZERO);
|
||||
h.getStreaks().invalidateNewerThan(Timestamp.ZERO);
|
||||
h.getScores().invalidateNewerThan(Timestamp.ZERO);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the number of habits in this list.
|
||||
*
|
||||
* @return number of habits
|
||||
*/
|
||||
public abstract int size();
|
||||
|
||||
/**
|
||||
* Notifies the list that a certain list of habits has been modified.
|
||||
* <p>
|
||||
* Depending on the implementation, this operation might trigger a write to
|
||||
* disk, or do nothing at all. To make sure that the habits get persisted,
|
||||
* this operation must be called.
|
||||
*
|
||||
* @param habits the list of habits that have been modified.
|
||||
*/
|
||||
public abstract void update(List<Habit> habits);
|
||||
|
||||
/**
|
||||
* Notifies the list that a certain habit has been modified.
|
||||
* <p>
|
||||
* See {@link #update(List)} for more details.
|
||||
*
|
||||
* @param habit the habit that has been modified.
|
||||
*/
|
||||
public void update(@NonNull Habit habit)
|
||||
{
|
||||
update(Collections.singletonList(habit));
|
||||
}
|
||||
|
||||
/**
|
||||
* Writes the list of habits to the given writer, in CSV format. There is
|
||||
* one line for each habit, containing the fields name, description,
|
||||
* frequency numerator, frequency denominator and color. The color is
|
||||
* written in HTML format (#000000).
|
||||
*
|
||||
* @param out the writer that will receive the result
|
||||
* @throws IOException if write operations fail
|
||||
*/
|
||||
public void writeCSV(@NonNull Writer out) throws IOException
|
||||
{
|
||||
String header[] = {
|
||||
"Position",
|
||||
"Name",
|
||||
"Description",
|
||||
"NumRepetitions",
|
||||
"Interval",
|
||||
"Color"
|
||||
};
|
||||
|
||||
CSVWriter csv = new CSVWriter(out);
|
||||
csv.writeNext(header, false);
|
||||
|
||||
for (Habit habit : this)
|
||||
{
|
||||
Frequency freq = habit.getFrequency();
|
||||
|
||||
String[] cols = {
|
||||
String.format("%03d", indexOf(habit) + 1),
|
||||
habit.getName(),
|
||||
habit.getDescription(),
|
||||
Integer.toString(freq.getNumerator()),
|
||||
Integer.toString(freq.getDenominator()),
|
||||
ColorConstants.CSV_PALETTE[habit.getColor()]
|
||||
};
|
||||
|
||||
csv.writeNext(cols, false);
|
||||
}
|
||||
|
||||
csv.close();
|
||||
}
|
||||
|
||||
public enum Order
|
||||
{
|
||||
BY_NAME,
|
||||
BY_COLOR,
|
||||
BY_SCORE,
|
||||
BY_POSITION
|
||||
}
|
||||
}
|
||||
@@ -1,66 +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.core.models;
|
||||
|
||||
public class HabitMatcher
|
||||
{
|
||||
public static final HabitMatcher WITH_ALARM = new HabitMatcherBuilder()
|
||||
.setArchivedAllowed(true)
|
||||
.setReminderRequired(true)
|
||||
.build();
|
||||
|
||||
private final boolean archivedAllowed;
|
||||
|
||||
private final boolean reminderRequired;
|
||||
|
||||
private final boolean completedAllowed;
|
||||
|
||||
public HabitMatcher(boolean allowArchived,
|
||||
boolean reminderRequired,
|
||||
boolean completedAllowed)
|
||||
{
|
||||
this.archivedAllowed = allowArchived;
|
||||
this.reminderRequired = reminderRequired;
|
||||
this.completedAllowed = completedAllowed;
|
||||
}
|
||||
|
||||
public boolean isArchivedAllowed()
|
||||
{
|
||||
return archivedAllowed;
|
||||
}
|
||||
|
||||
public boolean isCompletedAllowed()
|
||||
{
|
||||
return completedAllowed;
|
||||
}
|
||||
|
||||
public boolean isReminderRequired()
|
||||
{
|
||||
return reminderRequired;
|
||||
}
|
||||
|
||||
public boolean matches(Habit habit)
|
||||
{
|
||||
if (!isArchivedAllowed() && habit.isArchived()) return false;
|
||||
if (isReminderRequired() && !habit.hasReminder()) return false;
|
||||
if (!isCompletedAllowed() && habit.isCompletedToday()) return false;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -1,53 +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.core.models;
|
||||
|
||||
public class HabitMatcherBuilder
|
||||
{
|
||||
private boolean archivedAllowed = false;
|
||||
|
||||
private boolean reminderRequired = false;
|
||||
|
||||
private boolean completedAllowed = true;
|
||||
|
||||
public HabitMatcher build()
|
||||
{
|
||||
return new HabitMatcher(archivedAllowed, reminderRequired,
|
||||
completedAllowed);
|
||||
}
|
||||
|
||||
public HabitMatcherBuilder setArchivedAllowed(boolean archivedAllowed)
|
||||
{
|
||||
this.archivedAllowed = archivedAllowed;
|
||||
return this;
|
||||
}
|
||||
|
||||
public HabitMatcherBuilder setCompletedAllowed(boolean completedAllowed)
|
||||
{
|
||||
this.completedAllowed = completedAllowed;
|
||||
return this;
|
||||
}
|
||||
|
||||
public HabitMatcherBuilder setReminderRequired(boolean reminderRequired)
|
||||
{
|
||||
this.reminderRequired = reminderRequired;
|
||||
return this;
|
||||
}
|
||||
}
|
||||
@@ -1,23 +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.core.models;
|
||||
|
||||
public class HabitNotFoundException extends RuntimeException {
|
||||
}
|
||||
@@ -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.core.models;
|
||||
|
||||
import org.isoron.uhabits.core.database.*;
|
||||
import org.isoron.uhabits.core.models.sqlite.records.*;
|
||||
|
||||
/**
|
||||
* Interface implemented by factories that provide concrete implementations of
|
||||
* the core model classes.
|
||||
*/
|
||||
public interface ModelFactory
|
||||
{
|
||||
CheckmarkList buildCheckmarkList(Habit habit);
|
||||
|
||||
default Habit buildHabit()
|
||||
{
|
||||
return new Habit(this);
|
||||
}
|
||||
|
||||
default Habit buildHabit(Habit.HabitData data)
|
||||
{
|
||||
return new Habit(this, data);
|
||||
}
|
||||
|
||||
HabitList buildHabitList();
|
||||
|
||||
RepetitionList buildRepetitionList(Habit habit);
|
||||
|
||||
ScoreList buildScoreList(Habit habit);
|
||||
|
||||
StreakList buildStreakList(Habit habit);
|
||||
|
||||
Repository<HabitRecord> buildHabitListRepository();
|
||||
|
||||
Repository<RepetitionRecord> buildRepetitionListRepository();
|
||||
}
|
||||
@@ -1,89 +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.core.models;
|
||||
|
||||
import java.util.*;
|
||||
|
||||
import javax.annotation.concurrent.*;
|
||||
|
||||
/**
|
||||
* A ModelObservable allows objects to subscribe themselves to it and receive
|
||||
* notifications whenever the model is changed.
|
||||
*/
|
||||
@ThreadSafe
|
||||
public class ModelObservable
|
||||
{
|
||||
private List<Listener> listeners;
|
||||
|
||||
/**
|
||||
* Creates a new ModelObservable with no listeners.
|
||||
*/
|
||||
public ModelObservable()
|
||||
{
|
||||
super();
|
||||
listeners = new LinkedList<>();
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds the given listener to the observable.
|
||||
*
|
||||
* @param l the listener to be added.
|
||||
*/
|
||||
public synchronized void addListener(Listener l)
|
||||
{
|
||||
listeners.add(l);
|
||||
}
|
||||
|
||||
/**
|
||||
* Notifies every listener that the model has changed.
|
||||
* <p>
|
||||
* Only models should call this method.
|
||||
*/
|
||||
public synchronized void notifyListeners()
|
||||
{
|
||||
for (Listener l : listeners) l.onModelChange();
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes the given listener.
|
||||
* <p>
|
||||
* The listener will no longer be notified when the model changes. If the
|
||||
* given listener is not subscribed to this observable, does nothing.
|
||||
*
|
||||
* @param l the listener to be removed
|
||||
*/
|
||||
public synchronized void removeListener(Listener l)
|
||||
{
|
||||
listeners.remove(l);
|
||||
}
|
||||
|
||||
/**
|
||||
* Interface implemented by objects that want to be notified when the model
|
||||
* changes.
|
||||
*/
|
||||
public interface Listener
|
||||
{
|
||||
/**
|
||||
* Called whenever the model associated to this observable has been
|
||||
* modified.
|
||||
*/
|
||||
void onModelChange();
|
||||
}
|
||||
}
|
||||
@@ -1,100 +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.core.models;
|
||||
|
||||
import android.support.annotation.*;
|
||||
|
||||
import org.apache.commons.lang3.builder.*;
|
||||
import org.isoron.uhabits.core.utils.*;
|
||||
|
||||
import static org.isoron.uhabits.core.utils.StringUtils.*;
|
||||
|
||||
public final class Reminder
|
||||
{
|
||||
private final int hour;
|
||||
|
||||
private final int minute;
|
||||
|
||||
private final WeekdayList days;
|
||||
|
||||
public Reminder(int hour, int minute, @NonNull WeekdayList days)
|
||||
{
|
||||
this.hour = hour;
|
||||
this.minute = minute;
|
||||
this.days = days;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
public WeekdayList getDays()
|
||||
{
|
||||
return days;
|
||||
}
|
||||
|
||||
public int getHour()
|
||||
{
|
||||
return hour;
|
||||
}
|
||||
|
||||
public int getMinute()
|
||||
{
|
||||
return minute;
|
||||
}
|
||||
|
||||
public long getTimeInMillis()
|
||||
{
|
||||
return DateUtils.getUpcomingTimeInMillis(hour, minute);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o)
|
||||
{
|
||||
if (this == o) return true;
|
||||
|
||||
if (o == null || getClass() != o.getClass()) return false;
|
||||
|
||||
Reminder reminder = (Reminder) o;
|
||||
|
||||
return new EqualsBuilder()
|
||||
.append(hour, reminder.hour)
|
||||
.append(minute, reminder.minute)
|
||||
.append(days, reminder.days)
|
||||
.isEquals();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode()
|
||||
{
|
||||
return new HashCodeBuilder(17, 37)
|
||||
.append(hour)
|
||||
.append(minute)
|
||||
.append(days)
|
||||
.toHashCode();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString()
|
||||
{
|
||||
return new ToStringBuilder(this, defaultToStringStyle())
|
||||
.append("hour", hour)
|
||||
.append("minute", minute)
|
||||
.append("days", days)
|
||||
.toString();
|
||||
}
|
||||
}
|
||||
@@ -1,105 +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.core.models;
|
||||
|
||||
import org.apache.commons.lang3.builder.*;
|
||||
import org.isoron.uhabits.core.utils.DateFormats;
|
||||
import org.isoron.uhabits.core.utils.DateUtils;
|
||||
|
||||
import java.util.Calendar;
|
||||
import java.util.Date;
|
||||
import java.util.GregorianCalendar;
|
||||
|
||||
import static org.isoron.uhabits.core.utils.StringUtils.defaultToStringStyle;
|
||||
|
||||
/**
|
||||
* Represents a record that the user has performed a certain habit at a certain
|
||||
* date.
|
||||
*/
|
||||
public final class Repetition
|
||||
{
|
||||
|
||||
private final Timestamp timestamp;
|
||||
|
||||
/**
|
||||
* The value of the repetition.
|
||||
*
|
||||
* For boolean habits, this always equals Checkmark.CHECKED_EXPLICITLY.
|
||||
* For numerical habits, this number is stored in thousandths. That
|
||||
* is, if the user enters value 1.50 on the app, it is here stored as 1500.
|
||||
*/
|
||||
private final int value;
|
||||
|
||||
/**
|
||||
* Creates a new repetition with given parameters.
|
||||
* <p>
|
||||
* The timestamp corresponds to the days this repetition occurred. Time of
|
||||
* day must be midnight (UTC).
|
||||
*
|
||||
* @param timestamp the time this repetition occurred.
|
||||
*/
|
||||
public Repetition(Timestamp timestamp, int value)
|
||||
{
|
||||
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 Timestamp getTimestamp()
|
||||
{
|
||||
return timestamp;
|
||||
}
|
||||
|
||||
public int getValue()
|
||||
{
|
||||
return value;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode()
|
||||
{
|
||||
return new HashCodeBuilder(17, 37)
|
||||
.append(timestamp)
|
||||
.append(value)
|
||||
.toHashCode();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString()
|
||||
{
|
||||
return new ToStringBuilder(this, defaultToStringStyle())
|
||||
.append("timestamp", timestamp)
|
||||
.append("value", value)
|
||||
.toString();
|
||||
}
|
||||
}
|
||||
@@ -1,222 +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.core.models;
|
||||
|
||||
import android.support.annotation.*;
|
||||
|
||||
import org.isoron.uhabits.core.utils.*;
|
||||
|
||||
import java.util.*;
|
||||
|
||||
/**
|
||||
* The collection of {@link Repetition}s belonging to a habit.
|
||||
*/
|
||||
public abstract class RepetitionList
|
||||
{
|
||||
@NonNull
|
||||
protected final Habit habit;
|
||||
|
||||
@NonNull
|
||||
protected final ModelObservable observable;
|
||||
|
||||
public RepetitionList(@NonNull Habit habit)
|
||||
{
|
||||
this.habit = habit;
|
||||
this.observable = new ModelObservable();
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a repetition to the list.
|
||||
* <p>
|
||||
* Any implementation of this method must call observable.notifyListeners()
|
||||
* after the repetition has been added.
|
||||
*
|
||||
* @param repetition the repetition to be added.
|
||||
*/
|
||||
public abstract void add(Repetition repetition);
|
||||
|
||||
/**
|
||||
* Returns true if the list contains a repetition that has the given
|
||||
* timestamp.
|
||||
*
|
||||
* @param timestamp the timestamp to find.
|
||||
* @return true if list contains repetition with given timestamp, false
|
||||
* otherwise.
|
||||
*/
|
||||
public boolean containsTimestamp(Timestamp timestamp)
|
||||
{
|
||||
return (getByTimestamp(timestamp) != null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the list of repetitions that happened within the given time
|
||||
* interval.
|
||||
* <p>
|
||||
* The list is sorted by timestamp in increasing order. That is, the first
|
||||
* element corresponds to oldest timestamp, while the last element
|
||||
* corresponds to the newest. The endpoints of the interval are included.
|
||||
*
|
||||
* @param fromTimestamp timestamp of the beginning of the interval
|
||||
* @param toTimestamp timestamp of the end of the interval
|
||||
* @return list of repetitions within given time interval
|
||||
*/
|
||||
public abstract List<Repetition> getByInterval(Timestamp fromTimestamp,
|
||||
Timestamp toTimestamp);
|
||||
|
||||
/**
|
||||
* Returns the repetition that has the given timestamp, or null if none
|
||||
* exists.
|
||||
*
|
||||
* @param timestamp the repetition timestamp.
|
||||
* @return the repetition that has the given timestamp.
|
||||
*/
|
||||
@Nullable
|
||||
public abstract Repetition getByTimestamp(Timestamp timestamp);
|
||||
|
||||
@NonNull
|
||||
public ModelObservable getObservable()
|
||||
{
|
||||
return observable;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the oldest repetition in the list.
|
||||
* <p>
|
||||
* If the list is empty, returns null. Repetitions in the future are
|
||||
* discarded.
|
||||
*
|
||||
* @return oldest repetition in the list, or null if list is empty.
|
||||
*/
|
||||
@Nullable
|
||||
public abstract Repetition getOldest();
|
||||
|
||||
@Nullable
|
||||
/**
|
||||
* Returns the newest repetition in the list.
|
||||
* <p>
|
||||
* If the list is empty, returns null. Repetitions in the past are
|
||||
* discarded.
|
||||
*
|
||||
* @return newest repetition in the list, or null if list is empty.
|
||||
*/
|
||||
public abstract Repetition getNewest();
|
||||
|
||||
/**
|
||||
* Returns the total number of repetitions for each month, from the first
|
||||
* repetition until today, grouped by day of week.
|
||||
* <p>
|
||||
* The repetitions are returned in a HashMap. The key is the timestamp for
|
||||
* the first day of the month, at midnight (00:00). The value is an integer
|
||||
* array with 7 entries. The first entry contains the total number of
|
||||
* repetitions during the specified month that occurred on a Saturday. The
|
||||
* second entry corresponds to Sunday, and so on. If there are no
|
||||
* repetitions during a certain month, the value is null.
|
||||
*
|
||||
* @return total number of repetitions by month versus day of week
|
||||
*/
|
||||
@NonNull
|
||||
public HashMap<Timestamp, Integer[]> getWeekdayFrequency()
|
||||
{
|
||||
List<Repetition> reps =
|
||||
getByInterval(Timestamp.ZERO, DateUtils.getToday());
|
||||
HashMap<Timestamp, Integer[]> map = new HashMap<>();
|
||||
|
||||
for (Repetition r : reps)
|
||||
{
|
||||
Calendar date = r.getTimestamp().toCalendar();
|
||||
int weekday = r.getTimestamp().getWeekday();
|
||||
date.set(Calendar.DAY_OF_MONTH, 1);
|
||||
|
||||
Timestamp timestamp = new Timestamp(date.getTimeInMillis());
|
||||
Integer[] list = map.get(timestamp);
|
||||
|
||||
if (list == null)
|
||||
{
|
||||
list = new Integer[7];
|
||||
Arrays.fill(list, 0);
|
||||
map.put(timestamp, list);
|
||||
}
|
||||
|
||||
list[weekday]++;
|
||||
}
|
||||
|
||||
return map;
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes a given repetition from the list.
|
||||
* <p>
|
||||
* If the list does not contain the repetition, it is unchanged.
|
||||
* <p>
|
||||
* Any implementation of this method must call observable.notifyListeners()
|
||||
* after the repetition has been added.
|
||||
*
|
||||
* @param repetition the repetition to be removed
|
||||
*/
|
||||
public abstract void remove(@NonNull Repetition repetition);
|
||||
|
||||
/**
|
||||
* Adds or remove a repetition at a certain timestamp.
|
||||
* <p>
|
||||
* If there exists a repetition on the list with the given timestamp, the
|
||||
* method removes this repetition from the list and returns it. If there are
|
||||
* no repetitions with the given timestamp, creates and adds one to the
|
||||
* list, then returns it.
|
||||
*
|
||||
* @param timestamp the timestamp for the timestamp that should be added or
|
||||
* removed.
|
||||
* @return the repetition that has been added or removed.
|
||||
*/
|
||||
@NonNull
|
||||
public synchronized Repetition toggle(Timestamp timestamp)
|
||||
{
|
||||
if (habit.isNumerical())
|
||||
throw new IllegalStateException("habit must NOT be numerical");
|
||||
|
||||
Repetition rep = getByTimestamp(timestamp);
|
||||
if (rep != null) remove(rep);
|
||||
else
|
||||
{
|
||||
rep = new Repetition(timestamp, Checkmark.CHECKED_EXPLICITLY);
|
||||
add(rep);
|
||||
}
|
||||
|
||||
habit.invalidateNewerThan(timestamp);
|
||||
return rep;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the number of all repetitions
|
||||
*
|
||||
* @return number of all repetitions
|
||||
*/
|
||||
@NonNull
|
||||
public abstract long getTotalCount();
|
||||
|
||||
public void toggle(Timestamp timestamp, int value)
|
||||
{
|
||||
Repetition rep = getByTimestamp(timestamp);
|
||||
if (rep != null) remove(rep);
|
||||
add(new Repetition(timestamp, value));
|
||||
habit.invalidateNewerThan(timestamp);
|
||||
}
|
||||
|
||||
public abstract void removeAll();
|
||||
}
|
||||
@@ -1,121 +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.core.models;
|
||||
|
||||
import org.apache.commons.lang3.builder.*;
|
||||
|
||||
import static java.lang.Math.*;
|
||||
import static org.isoron.uhabits.core.utils.StringUtils.defaultToStringStyle;
|
||||
|
||||
/**
|
||||
* Represents how strong a habit is at a certain date.
|
||||
*/
|
||||
public final class Score
|
||||
{
|
||||
/**
|
||||
* Timestamp of the day to which this score applies. Time of day should be
|
||||
* midnight (UTC).
|
||||
*/
|
||||
private final Timestamp timestamp;
|
||||
|
||||
/**
|
||||
* Value of the score.
|
||||
*/
|
||||
private final double value;
|
||||
|
||||
public Score(Timestamp timestamp, double value)
|
||||
{
|
||||
this.timestamp = timestamp;
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Given the frequency of the habit, the previous score, and the value of
|
||||
* the current checkmark, computes the current score for the habit.
|
||||
* <p>
|
||||
* 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
|
||||
* times in 8 days has frequency 3.0 / 8.0 = 0.375.
|
||||
*
|
||||
* @param frequency the frequency of the habit
|
||||
* @param previousScore the previous score of the habit
|
||||
* @param checkmarkValue the value of the current checkmark
|
||||
* @return the current score
|
||||
*/
|
||||
public static double compute(double frequency,
|
||||
double previousScore,
|
||||
double checkmarkValue)
|
||||
{
|
||||
double multiplier = pow(0.5, frequency / 13.0);
|
||||
|
||||
double score = previousScore * multiplier;
|
||||
score += checkmarkValue * (1 - multiplier);
|
||||
|
||||
return score;
|
||||
}
|
||||
|
||||
public int compareNewer(Score other)
|
||||
{
|
||||
return getTimestamp().compare(other.getTimestamp());
|
||||
}
|
||||
|
||||
public Timestamp getTimestamp()
|
||||
{
|
||||
return timestamp;
|
||||
}
|
||||
|
||||
public double getValue()
|
||||
{
|
||||
return value;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString()
|
||||
{
|
||||
return new ToStringBuilder(this, defaultToStringStyle())
|
||||
.append("timestamp", timestamp)
|
||||
.append("value", value)
|
||||
.toString();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o)
|
||||
{
|
||||
if (this == o) return true;
|
||||
|
||||
if (o == null || getClass() != o.getClass()) return false;
|
||||
|
||||
Score score = (Score) o;
|
||||
|
||||
return new EqualsBuilder()
|
||||
.append(value, score.value)
|
||||
.append(timestamp, score.timestamp)
|
||||
.isEquals();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode()
|
||||
{
|
||||
return new HashCodeBuilder(17, 37)
|
||||
.append(timestamp)
|
||||
.append(value)
|
||||
.toHashCode();
|
||||
}
|
||||
}
|
||||
@@ -1,332 +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.core.models;
|
||||
|
||||
import android.support.annotation.*;
|
||||
|
||||
import org.isoron.uhabits.core.utils.*;
|
||||
|
||||
import java.io.*;
|
||||
import java.text.*;
|
||||
import java.util.*;
|
||||
|
||||
public abstract class ScoreList implements Iterable<Score>
|
||||
{
|
||||
protected final Habit habit;
|
||||
|
||||
protected ModelObservable observable;
|
||||
|
||||
/**
|
||||
* Creates a new ScoreList for the given habit.
|
||||
* <p>
|
||||
* The list is populated automatically according to the repetitions that the
|
||||
* habit has.
|
||||
*
|
||||
* @param habit the habit to which the scores belong.
|
||||
*/
|
||||
public ScoreList(Habit habit)
|
||||
{
|
||||
this.habit = habit;
|
||||
observable = new ModelObservable();
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds the given scores to the list.
|
||||
* <p>
|
||||
* This method should not be called by the application, since the scores are
|
||||
* computed automatically from the list of repetitions.
|
||||
*
|
||||
* @param scores the scores to add.
|
||||
*/
|
||||
public abstract void add(List<Score> scores);
|
||||
|
||||
public ModelObservable getObservable()
|
||||
{
|
||||
return observable;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the value of the score for today.
|
||||
*
|
||||
* @return value of today's score
|
||||
*/
|
||||
public double getTodayValue()
|
||||
{
|
||||
return getValue(DateUtils.getToday());
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the value of the score for a given day.
|
||||
* <p>
|
||||
* If the timestamp given happens before the first repetition of the habit
|
||||
* then returns zero.
|
||||
*
|
||||
* @param timestamp the timestamp of a day
|
||||
* @return score value for that day
|
||||
*/
|
||||
public final synchronized double getValue(Timestamp timestamp)
|
||||
{
|
||||
compute(timestamp, timestamp);
|
||||
Score s = getComputedByTimestamp(timestamp);
|
||||
if (s == null) throw new IllegalStateException();
|
||||
return s.getValue();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the list of scores that fall within the given interval.
|
||||
* <p>
|
||||
* There is exactly one score per day in the interval. The endpoints of
|
||||
* the interval are included. The list is ordered by timestamp (decreasing).
|
||||
* That is, the first score corresponds to the newest timestamp, and the
|
||||
* last score corresponds to the oldest timestamp.
|
||||
*
|
||||
* @param fromTimestamp timestamp of the beginning of the interval.
|
||||
* @param toTimestamp timestamp of the end of the interval.
|
||||
* @return the list of scores within the interval.
|
||||
*/
|
||||
@NonNull
|
||||
public abstract List<Score> getByInterval(@NonNull Timestamp fromTimestamp,
|
||||
@NonNull Timestamp toTimestamp);
|
||||
|
||||
/**
|
||||
* Returns the values of the scores that fall inside a certain interval
|
||||
* of time.
|
||||
* <p>
|
||||
* The values are returned in an array containing one integer value for each
|
||||
* day of the interval. The first entry corresponds to the most recent day
|
||||
* in the interval. Each subsequent entry corresponds to one day older than
|
||||
* the previous entry. The boundaries of the time interval are included.
|
||||
*
|
||||
* @param from timestamp for the oldest score
|
||||
* @param to timestamp for the newest score
|
||||
* @return values for the scores inside the given interval
|
||||
*/
|
||||
public final double[] getValues(Timestamp from, Timestamp to)
|
||||
{
|
||||
List<Score> scores = getByInterval(from, to);
|
||||
double[] values = new double[scores.size()];
|
||||
|
||||
for (int i = 0; i < values.length; i++)
|
||||
values[i] = scores.get(i).getValue();
|
||||
|
||||
return values;
|
||||
}
|
||||
|
||||
public List<Score> groupBy(DateUtils.TruncateField field)
|
||||
{
|
||||
computeAll();
|
||||
HashMap<Timestamp, ArrayList<Double>> groups = getGroupedValues(field);
|
||||
List<Score> scores = groupsToAvgScores(groups);
|
||||
Collections.sort(scores, (s1, s2) -> s2.compareNewer(s1));
|
||||
return scores;
|
||||
}
|
||||
|
||||
/**
|
||||
* Marks all scores that have timestamp equal to or newer than the given
|
||||
* timestamp as invalid. Any following getValue calls will trigger the
|
||||
* scores to be recomputed.
|
||||
*
|
||||
* @param timestamp the oldest timestamp that should be invalidated
|
||||
*/
|
||||
public abstract void invalidateNewerThan(Timestamp timestamp);
|
||||
|
||||
@Override
|
||||
public Iterator<Score> iterator()
|
||||
{
|
||||
return toList().iterator();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a Java list of scores, containing one score for each day, from
|
||||
* the first repetition of the habit until today.
|
||||
* <p>
|
||||
* The scores are sorted by decreasing timestamp. The first score
|
||||
* corresponds to today.
|
||||
*
|
||||
* @return list of scores
|
||||
*/
|
||||
public abstract List<Score> toList();
|
||||
|
||||
public void writeCSV(Writer out) throws IOException
|
||||
{
|
||||
computeAll();
|
||||
SimpleDateFormat dateFormat = DateFormats.getCSVDateFormat();
|
||||
|
||||
for (Score s : this)
|
||||
{
|
||||
String timestamp = dateFormat.format(s.getTimestamp().getUnixTime());
|
||||
String score = String.format((Locale)null, "%.4f", s.getValue());
|
||||
out.write(String.format("%s,%s\n", timestamp, score));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Computes and stores one score for each day inside the given interval.
|
||||
* <p>
|
||||
* Scores that have already been computed are skipped, therefore there is no
|
||||
* harm in calling this function more times, or with larger intervals, than
|
||||
* strictly needed. The endpoints of the interval are included.
|
||||
* <p>
|
||||
* This method assumes the list of computed scores has no holes. That is, if
|
||||
* there is a score computed at time t1 and another at time t2, then every
|
||||
* score between t1 and t2 is also computed.
|
||||
*
|
||||
* @param from timestamp of the beginning of the interval
|
||||
* @param to timestamp of the end of the time interval
|
||||
*/
|
||||
protected synchronized void compute(@NonNull Timestamp from,
|
||||
@NonNull Timestamp to)
|
||||
{
|
||||
Score newest = getNewestComputed();
|
||||
Score oldest = getOldestComputed();
|
||||
|
||||
if (newest == null)
|
||||
{
|
||||
Repetition oldestRep = habit.getRepetitions().getOldest();
|
||||
if (oldestRep != null) from =
|
||||
Timestamp.oldest(from, oldestRep.getTimestamp());
|
||||
forceRecompute(from, to, 0);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (oldest == null) throw new IllegalStateException();
|
||||
forceRecompute(from, oldest.getTimestamp().minus(1), 0);
|
||||
forceRecompute(newest.getTimestamp().plus(1), to,
|
||||
newest.getValue());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Computes and saves the scores that are missing since the first repetition
|
||||
* of the habit.
|
||||
*/
|
||||
protected void computeAll()
|
||||
{
|
||||
Repetition oldestRep = habit.getRepetitions().getOldest();
|
||||
if (oldestRep == null) return;
|
||||
|
||||
Timestamp today = DateUtils.getToday();
|
||||
compute(oldestRep.getTimestamp(), today);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the score that has the given timestamp, if it has already been
|
||||
* computed. If that score has not been computed yet, returns null.
|
||||
*
|
||||
* @param timestamp the timestamp of the score
|
||||
* @return the score with given timestamp, or null not yet computed.
|
||||
*/
|
||||
@Nullable
|
||||
protected abstract Score getComputedByTimestamp(Timestamp timestamp);
|
||||
|
||||
/**
|
||||
* Returns the most recent score that has already been computed. If no score
|
||||
* has been computed yet, returns null.
|
||||
*/
|
||||
@Nullable
|
||||
protected abstract Score getNewestComputed();
|
||||
|
||||
/**
|
||||
* Returns oldest score already computed. If no score has been computed yet,
|
||||
* returns null.
|
||||
*/
|
||||
@Nullable
|
||||
protected abstract Score getOldestComputed();
|
||||
|
||||
/**
|
||||
* Computes and stores one score for each day inside the given interval.
|
||||
* <p>
|
||||
* This function does not check if the scores have already been computed. If
|
||||
* they have, then it stores duplicate scores, which is a bad thing.
|
||||
*
|
||||
* @param from timestamp of the beginning of the interval
|
||||
* @param to timestamp of the end of the interval
|
||||
* @param previousValue value of the score on the day immediately before the
|
||||
* interval begins
|
||||
*/
|
||||
private void forceRecompute(@NonNull Timestamp from,
|
||||
@NonNull Timestamp to,
|
||||
double previousValue)
|
||||
{
|
||||
if (from.isNewerThan(to)) return;
|
||||
|
||||
final double freq = habit.getFrequency().toDouble();
|
||||
final int checkmarkValues[] = habit.getCheckmarks().getValues(from, to);
|
||||
|
||||
List<Score> scores = new LinkedList<>();
|
||||
|
||||
for (int i = 0; i < checkmarkValues.length; i++)
|
||||
{
|
||||
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);
|
||||
scores.add(new Score(from.plus(i), previousValue));
|
||||
}
|
||||
|
||||
add(scores);
|
||||
}
|
||||
|
||||
@NonNull
|
||||
private HashMap<Timestamp, ArrayList<Double>> getGroupedValues(DateUtils.TruncateField field)
|
||||
{
|
||||
HashMap<Timestamp, ArrayList<Double>> groups = new HashMap<>();
|
||||
|
||||
for (Score s : this)
|
||||
{
|
||||
Timestamp groupTimestamp = new Timestamp(
|
||||
DateUtils.truncate(field, s.getTimestamp().getUnixTime()));
|
||||
|
||||
if (!groups.containsKey(groupTimestamp))
|
||||
groups.put(groupTimestamp, new ArrayList<>());
|
||||
|
||||
groups.get(groupTimestamp).add(s.getValue());
|
||||
}
|
||||
|
||||
return groups;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
private List<Score> groupsToAvgScores(HashMap<Timestamp, ArrayList<Double>> groups)
|
||||
{
|
||||
List<Score> scores = new LinkedList<>();
|
||||
|
||||
for (Timestamp timestamp : groups.keySet())
|
||||
{
|
||||
double meanValue = 0.0;
|
||||
ArrayList<Double> groupValues = groups.get(timestamp);
|
||||
|
||||
for (Double v : groupValues) meanValue += v;
|
||||
meanValue /= groupValues.size();
|
||||
|
||||
scores.add(new Score(timestamp, meanValue));
|
||||
}
|
||||
|
||||
return scores;
|
||||
}
|
||||
}
|
||||
@@ -1,98 +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.core.models;
|
||||
|
||||
import org.apache.commons.lang3.builder.*;
|
||||
|
||||
import static org.isoron.uhabits.core.utils.StringUtils.defaultToStringStyle;
|
||||
|
||||
public final class Streak
|
||||
{
|
||||
private final Timestamp start;
|
||||
|
||||
private final Timestamp end;
|
||||
|
||||
public Streak(Timestamp start, Timestamp end)
|
||||
{
|
||||
this.start = start;
|
||||
this.end = end;
|
||||
}
|
||||
|
||||
public int compareLonger(Streak other)
|
||||
{
|
||||
if (this.getLength() != other.getLength())
|
||||
return Long.signum(this.getLength() - other.getLength());
|
||||
|
||||
return compareNewer(other);
|
||||
}
|
||||
|
||||
public int compareNewer(Streak other)
|
||||
{
|
||||
return end.compare(other.end);
|
||||
}
|
||||
|
||||
public Timestamp getEnd()
|
||||
{
|
||||
return end;
|
||||
}
|
||||
|
||||
public int getLength()
|
||||
{
|
||||
return start.daysUntil(end) + 1;
|
||||
}
|
||||
|
||||
public Timestamp getStart()
|
||||
{
|
||||
return start;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString()
|
||||
{
|
||||
return new ToStringBuilder(this, defaultToStringStyle())
|
||||
.append("start", start)
|
||||
.append("end", end)
|
||||
.toString();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o)
|
||||
{
|
||||
if (this == o) return true;
|
||||
|
||||
if (o == null || getClass() != o.getClass()) return false;
|
||||
|
||||
Streak streak = (Streak) o;
|
||||
|
||||
return new EqualsBuilder()
|
||||
.append(start, streak.start)
|
||||
.append(end, streak.end)
|
||||
.isEquals();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode()
|
||||
{
|
||||
return new HashCodeBuilder(17, 37)
|
||||
.append(start)
|
||||
.append(end)
|
||||
.toHashCode();
|
||||
}
|
||||
}
|
||||
@@ -1,153 +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.core.models;
|
||||
|
||||
import android.support.annotation.*;
|
||||
|
||||
import org.isoron.uhabits.core.utils.*;
|
||||
|
||||
import java.util.*;
|
||||
|
||||
/**
|
||||
* The collection of {@link Streak}s that belong to a habit.
|
||||
* <p>
|
||||
* This list is populated automatically from the list of repetitions.
|
||||
*/
|
||||
public abstract class StreakList
|
||||
{
|
||||
protected final Habit habit;
|
||||
|
||||
protected ModelObservable observable;
|
||||
|
||||
protected StreakList(Habit habit)
|
||||
{
|
||||
this.habit = habit;
|
||||
observable = new ModelObservable();
|
||||
}
|
||||
|
||||
public abstract List<Streak> getAll();
|
||||
|
||||
@NonNull
|
||||
public List<Streak> getBest(int limit)
|
||||
{
|
||||
List<Streak> streaks = getAll();
|
||||
Collections.sort(streaks, (s1, s2) -> s2.compareLonger(s1));
|
||||
streaks = streaks.subList(0, Math.min(streaks.size(), limit));
|
||||
Collections.sort(streaks, (s1, s2) -> s2.compareNewer(s1));
|
||||
return streaks;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public abstract Streak getNewestComputed();
|
||||
|
||||
@NonNull
|
||||
public ModelObservable getObservable()
|
||||
{
|
||||
return observable;
|
||||
}
|
||||
|
||||
public abstract void invalidateNewerThan(Timestamp timestamp);
|
||||
|
||||
public synchronized void rebuild()
|
||||
{
|
||||
Timestamp today = DateUtils.getToday();
|
||||
Timestamp beginning = findBeginning();
|
||||
if (beginning == null || beginning.isNewerThan(today)) return;
|
||||
|
||||
int checks[] = habit.getCheckmarks().getValues(beginning, today);
|
||||
List<Streak> streaks = checkmarksToStreaks(beginning, checks);
|
||||
|
||||
removeNewestComputed();
|
||||
add(streaks);
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts a list of checkmark values to a list of streaks.
|
||||
*
|
||||
* @param beginning the timestamp corresponding to the first checkmark
|
||||
* value.
|
||||
* @param checks the checkmarks values, ordered by decreasing timestamp.
|
||||
* @return the list of streaks.
|
||||
*/
|
||||
@NonNull
|
||||
protected List<Streak> checkmarksToStreaks(Timestamp beginning, int[] checks)
|
||||
{
|
||||
ArrayList<Timestamp> transitions = getTransitions(beginning, checks);
|
||||
|
||||
List<Streak> streaks = new LinkedList<>();
|
||||
for (int i = 0; i < transitions.size(); i += 2)
|
||||
{
|
||||
Timestamp start = transitions.get(i);
|
||||
Timestamp end = transitions.get(i + 1);
|
||||
streaks.add(new Streak(start, end));
|
||||
}
|
||||
|
||||
return streaks;
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds the place where we should start when recomputing the streaks.
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
@Nullable
|
||||
protected Timestamp findBeginning()
|
||||
{
|
||||
Streak newestStreak = getNewestComputed();
|
||||
if (newestStreak != null) return newestStreak.getStart();
|
||||
|
||||
Repetition oldestRep = habit.getRepetitions().getOldest();
|
||||
if (oldestRep != null) return oldestRep.getTimestamp();
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the timestamps where there was a transition from performing a
|
||||
* habit to not performing a habit, and vice-versa.
|
||||
*
|
||||
* @param beginning the timestamp for the first checkmark
|
||||
* @param checks the checkmarks, ordered by decresing timestamp
|
||||
* @return the list of transitions
|
||||
*/
|
||||
@NonNull
|
||||
protected ArrayList<Timestamp> getTransitions(Timestamp beginning, int[] checks)
|
||||
{
|
||||
ArrayList<Timestamp> list = new ArrayList<>();
|
||||
Timestamp current = beginning;
|
||||
list.add(current);
|
||||
|
||||
for (int i = 1; i < checks.length; i++)
|
||||
{
|
||||
current = current.plus(1);
|
||||
int j = checks.length - i - 1;
|
||||
|
||||
if ((checks[j + 1] == 0 && checks[j] > 0)) list.add(current);
|
||||
if ((checks[j + 1] > 0 && checks[j] == 0)) list.add(current.minus(1));
|
||||
}
|
||||
|
||||
if (list.size() % 2 == 1) list.add(current);
|
||||
|
||||
return list;
|
||||
}
|
||||
|
||||
protected abstract void add(@NonNull List<Streak> streaks);
|
||||
|
||||
protected abstract void removeNewestComputed();
|
||||
}
|
||||
@@ -1,155 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2015-2017 Á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.core.models;
|
||||
|
||||
import org.apache.commons.lang3.builder.*;
|
||||
import org.isoron.uhabits.core.utils.DateFormats;
|
||||
import org.isoron.uhabits.core.utils.DateUtils;
|
||||
|
||||
import java.util.*;
|
||||
|
||||
import static java.util.Calendar.*;
|
||||
import static org.isoron.uhabits.core.utils.StringUtils.*;
|
||||
|
||||
public final class Timestamp
|
||||
{
|
||||
|
||||
public static final long DAY_LENGTH = 86400000;
|
||||
|
||||
public static final Timestamp ZERO = new Timestamp(0);
|
||||
|
||||
private final long unixTime;
|
||||
|
||||
public Timestamp(long unixTime)
|
||||
{
|
||||
if (unixTime < 0 || unixTime % DAY_LENGTH != 0)
|
||||
throw new IllegalArgumentException(
|
||||
"Invalid unix time: " + unixTime);
|
||||
|
||||
this.unixTime = unixTime;
|
||||
}
|
||||
|
||||
public Timestamp(GregorianCalendar cal)
|
||||
{
|
||||
this(cal.getTimeInMillis());
|
||||
}
|
||||
|
||||
public long getUnixTime()
|
||||
{
|
||||
return unixTime;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns -1 if this timestamp is older than the given timestamp, 1 if this
|
||||
* timestamp is newer, or zero if they are equal.
|
||||
*/
|
||||
public int compare(Timestamp other)
|
||||
{
|
||||
return Long.signum(this.unixTime - other.unixTime);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o)
|
||||
{
|
||||
if (this == o) return true;
|
||||
|
||||
if (o == null || getClass() != o.getClass()) return false;
|
||||
|
||||
Timestamp timestamp = (Timestamp) o;
|
||||
|
||||
return new EqualsBuilder()
|
||||
.append(unixTime, timestamp.unixTime)
|
||||
.isEquals();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode()
|
||||
{
|
||||
return new HashCodeBuilder(17, 37).append(unixTime).toHashCode();
|
||||
}
|
||||
|
||||
/**
|
||||
* Given two timestamps, returns whichever timestamp is the oldest one.
|
||||
*/
|
||||
public static Timestamp oldest(Timestamp first, Timestamp second)
|
||||
{
|
||||
return first.unixTime < second.unixTime ? first : second;
|
||||
}
|
||||
|
||||
public Timestamp minus(int days)
|
||||
{
|
||||
return plus(-days);
|
||||
}
|
||||
|
||||
public Timestamp plus(int days)
|
||||
{
|
||||
return new Timestamp(unixTime + DAY_LENGTH * days);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the number of days between this timestamp and the given one. If
|
||||
* the other timestamp equals this one, returns zero. If the other timestamp
|
||||
* is older than this one, returns a negative number.
|
||||
*/
|
||||
public int daysUntil(Timestamp other)
|
||||
{
|
||||
return (int) ((other.unixTime - this.unixTime) / DAY_LENGTH);
|
||||
}
|
||||
|
||||
public boolean isNewerThan(Timestamp other)
|
||||
{
|
||||
return compare(other) > 0;
|
||||
}
|
||||
|
||||
public boolean isOlderThan(Timestamp other)
|
||||
{
|
||||
return compare(other) < 0;
|
||||
}
|
||||
|
||||
|
||||
public Date toJavaDate()
|
||||
{
|
||||
return new Date(unixTime);
|
||||
}
|
||||
|
||||
public GregorianCalendar toCalendar()
|
||||
{
|
||||
GregorianCalendar day =
|
||||
new GregorianCalendar(TimeZone.getTimeZone("GMT"));
|
||||
day.setTimeInMillis(unixTime);
|
||||
return day;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString()
|
||||
{
|
||||
return DateFormats.getCSVDateFormat().format(new Date(unixTime));
|
||||
}
|
||||
|
||||
public int getWeekday()
|
||||
{
|
||||
return toCalendar().get(DAY_OF_WEEK) % 7;
|
||||
}
|
||||
|
||||
public Timestamp truncate(DateUtils.TruncateField field)
|
||||
{
|
||||
return new Timestamp(DateUtils.truncate(field, unixTime));
|
||||
}
|
||||
}
|
||||
@@ -1,101 +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.core.models;
|
||||
|
||||
import org.apache.commons.lang3.builder.*;
|
||||
|
||||
import java.util.*;
|
||||
|
||||
import static org.isoron.uhabits.core.utils.StringUtils.defaultToStringStyle;
|
||||
|
||||
public final class WeekdayList
|
||||
{
|
||||
public static final WeekdayList EVERY_DAY = new WeekdayList(127);
|
||||
|
||||
private final boolean[] weekdays;
|
||||
|
||||
public WeekdayList(int packedList)
|
||||
{
|
||||
weekdays = new boolean[7];
|
||||
|
||||
int current = 1;
|
||||
for (int i = 0; i < 7; i++)
|
||||
{
|
||||
if ((packedList & current) != 0) weekdays[i] = true;
|
||||
current = current << 1;
|
||||
}
|
||||
}
|
||||
|
||||
public WeekdayList(boolean weekdays[])
|
||||
{
|
||||
this.weekdays = Arrays.copyOf(weekdays, 7);
|
||||
}
|
||||
|
||||
public boolean isEmpty()
|
||||
{
|
||||
for (boolean d : weekdays) if (d) return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
public boolean[] toArray()
|
||||
{
|
||||
return Arrays.copyOf(weekdays, 7);
|
||||
}
|
||||
|
||||
public int toInteger()
|
||||
{
|
||||
int packedList = 0;
|
||||
int current = 1;
|
||||
|
||||
for (int i = 0; i < 7; i++)
|
||||
{
|
||||
if (weekdays[i]) packedList |= current;
|
||||
current = current << 1;
|
||||
}
|
||||
|
||||
return packedList;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o)
|
||||
{
|
||||
if (this == o) return true;
|
||||
|
||||
if (o == null || getClass() != o.getClass()) return false;
|
||||
|
||||
WeekdayList that = (WeekdayList) o;
|
||||
|
||||
return new EqualsBuilder().append(weekdays, that.weekdays).isEquals();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode()
|
||||
{
|
||||
return new HashCodeBuilder(17, 37).append(weekdays).toHashCode();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString()
|
||||
{
|
||||
return new ToStringBuilder(this, defaultToStringStyle())
|
||||
.append("weekdays", weekdays)
|
||||
.toString();
|
||||
}
|
||||
}
|
||||
@@ -1,102 +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.core.models.memory;
|
||||
|
||||
import android.support.annotation.*;
|
||||
|
||||
import org.isoron.uhabits.core.models.*;
|
||||
|
||||
import java.util.*;
|
||||
|
||||
/**
|
||||
* In-memory implementation of {@link CheckmarkList}.
|
||||
*/
|
||||
public class MemoryCheckmarkList extends CheckmarkList
|
||||
{
|
||||
ArrayList<Checkmark> list;
|
||||
|
||||
public MemoryCheckmarkList(Habit habit)
|
||||
{
|
||||
super(habit);
|
||||
list = new ArrayList<>();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void add(List<Checkmark> checkmarks)
|
||||
{
|
||||
list.addAll(checkmarks);
|
||||
Collections.sort(list,
|
||||
(c1, c2) -> c2.getTimestamp().compare(c1.getTimestamp()));
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public synchronized List<Checkmark> getByInterval(Timestamp from,
|
||||
Timestamp to)
|
||||
{
|
||||
compute();
|
||||
|
||||
Timestamp newestComputed = new Timestamp(0);
|
||||
Timestamp oldestComputed = new Timestamp(0).plus(1000000);
|
||||
|
||||
Checkmark newest = getNewestComputed();
|
||||
Checkmark oldest = getOldestComputed();
|
||||
if(newest != null) newestComputed = newest.getTimestamp();
|
||||
if(oldest != null) oldestComputed = oldest.getTimestamp();
|
||||
|
||||
List<Checkmark> filtered = new ArrayList<>(
|
||||
Math.max(0, oldestComputed.daysUntil(newestComputed) + 1));
|
||||
|
||||
for(int i = 0; i <= from.daysUntil(to); i++)
|
||||
{
|
||||
Timestamp t = to.minus(i);
|
||||
if(t.isNewerThan(newestComputed) || t.isOlderThan(oldestComputed))
|
||||
filtered.add(new Checkmark(t, Checkmark.UNCHECKED));
|
||||
else
|
||||
filtered.add(list.get(t.daysUntil(newestComputed)));
|
||||
}
|
||||
|
||||
return filtered;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void invalidateNewerThan(Timestamp timestamp)
|
||||
{
|
||||
list.clear();
|
||||
observable.notifyListeners();
|
||||
}
|
||||
|
||||
@Override
|
||||
@Nullable
|
||||
protected Checkmark getOldestComputed()
|
||||
{
|
||||
if(list.isEmpty()) return null;
|
||||
return list.get(list.size()-1);
|
||||
}
|
||||
|
||||
@Override
|
||||
@Nullable
|
||||
protected Checkmark getNewestComputed()
|
||||
{
|
||||
if(list.isEmpty()) return null;
|
||||
return list.get(0);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,235 +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.core.models.memory;
|
||||
|
||||
import android.support.annotation.*;
|
||||
|
||||
import org.isoron.uhabits.core.models.*;
|
||||
|
||||
import java.util.*;
|
||||
|
||||
import static org.isoron.uhabits.core.models.HabitList.Order.*;
|
||||
|
||||
/**
|
||||
* In-memory implementation of {@link HabitList}.
|
||||
*/
|
||||
public class MemoryHabitList extends HabitList
|
||||
{
|
||||
@NonNull
|
||||
private LinkedList<Habit> list = new LinkedList<>();
|
||||
|
||||
private Comparator<Habit> comparator = null;
|
||||
|
||||
@NonNull
|
||||
private Order order = Order.BY_POSITION;
|
||||
|
||||
@Nullable
|
||||
private MemoryHabitList parent = null;
|
||||
|
||||
public MemoryHabitList()
|
||||
{
|
||||
super();
|
||||
}
|
||||
|
||||
protected MemoryHabitList(@NonNull HabitMatcher matcher,
|
||||
Comparator<Habit> comparator,
|
||||
@NonNull MemoryHabitList parent)
|
||||
{
|
||||
super(matcher);
|
||||
this.parent = parent;
|
||||
this.comparator = comparator;
|
||||
parent.getObservable().addListener(this::loadFromParent);
|
||||
loadFromParent();
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized void add(@NonNull Habit habit)
|
||||
throws IllegalArgumentException
|
||||
{
|
||||
throwIfHasParent();
|
||||
if (list.contains(habit))
|
||||
throw new IllegalArgumentException("habit already added");
|
||||
|
||||
Long id = habit.getId();
|
||||
if (id != null && getById(id) != null)
|
||||
throw new RuntimeException("duplicate id");
|
||||
|
||||
if (id == null) habit.setId((long) list.size());
|
||||
list.addLast(habit);
|
||||
resort();
|
||||
|
||||
getObservable().notifyListeners();
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized Habit getById(long id)
|
||||
{
|
||||
for (Habit h : list)
|
||||
{
|
||||
if (h.getId() == null) throw new IllegalStateException();
|
||||
if (h.getId() == id) return h;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public synchronized Habit getByPosition(int position)
|
||||
{
|
||||
return list.get(position);
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public synchronized HabitList getFiltered(HabitMatcher matcher)
|
||||
{
|
||||
return new MemoryHabitList(matcher, comparator, this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized Order getOrder()
|
||||
{
|
||||
return order;
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized void setOrder(@NonNull Order order)
|
||||
{
|
||||
this.order = order;
|
||||
this.comparator = getComparatorByOrder(order);
|
||||
resort();
|
||||
getObservable().notifyListeners();
|
||||
}
|
||||
|
||||
private Comparator<Habit> getComparatorByOrder(Order order)
|
||||
{
|
||||
Comparator<Habit> nameComparator =
|
||||
(h1, h2) -> h1.getName().compareTo(h2.getName());
|
||||
|
||||
Comparator<Habit> colorComparator = (h1, h2) ->
|
||||
{
|
||||
Integer c1 = h1.getColor();
|
||||
Integer c2 = h2.getColor();
|
||||
if (c1.equals(c2)) return nameComparator.compare(h1, h2);
|
||||
else return c1.compareTo(c2);
|
||||
};
|
||||
|
||||
Comparator<Habit> scoreComparator = (h1, h2) ->
|
||||
{
|
||||
Double s1 = h1.getScores().getTodayValue();
|
||||
Double s2 = h2.getScores().getTodayValue();
|
||||
if (s1.equals(s2)) return nameComparator.compare(h1, h2);
|
||||
else return s2.compareTo(s1);
|
||||
};
|
||||
|
||||
Comparator<Habit> positionComparator = (h1, h2) ->
|
||||
{
|
||||
Integer p1 = h1.getPosition();
|
||||
Integer p2 = h2.getPosition();
|
||||
if (p1.equals(p2)) return nameComparator.compare(h1, h2);
|
||||
else return p1.compareTo(p2);
|
||||
};
|
||||
|
||||
if (order == BY_POSITION) return positionComparator;
|
||||
if (order == BY_NAME) return nameComparator;
|
||||
if (order == BY_COLOR) return colorComparator;
|
||||
if (order == BY_SCORE) return scoreComparator;
|
||||
throw new IllegalStateException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized int indexOf(@NonNull Habit h)
|
||||
{
|
||||
return list.indexOf(h);
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public synchronized Iterator<Habit> iterator()
|
||||
{
|
||||
return new ArrayList<>(list).iterator();
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized void remove(@NonNull Habit habit)
|
||||
{
|
||||
throwIfHasParent();
|
||||
list.remove(habit);
|
||||
getObservable().notifyListeners();
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized void reorder(@NonNull Habit from, @NonNull Habit to)
|
||||
{
|
||||
throwIfHasParent();
|
||||
if (order != BY_POSITION) throw new IllegalStateException(
|
||||
"cannot reorder automatically sorted list");
|
||||
|
||||
if (indexOf(from) < 0) throw new IllegalArgumentException(
|
||||
"list does not contain (from) habit");
|
||||
|
||||
int toPos = indexOf(to);
|
||||
if (toPos < 0) throw new IllegalArgumentException(
|
||||
"list does not contain (to) habit");
|
||||
|
||||
list.remove(from);
|
||||
list.add(toPos, from);
|
||||
|
||||
int position = 0;
|
||||
for(Habit h : list)
|
||||
h.setPosition(position++);
|
||||
|
||||
getObservable().notifyListeners();
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized int size()
|
||||
{
|
||||
return list.size();
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized void update(List<Habit> habits)
|
||||
{
|
||||
resort();
|
||||
getObservable().notifyListeners();
|
||||
}
|
||||
|
||||
private void throwIfHasParent()
|
||||
{
|
||||
if (parent != null) throw new IllegalStateException(
|
||||
"Filtered lists cannot be modified directly. " +
|
||||
"You should modify the parent list instead.");
|
||||
}
|
||||
|
||||
private synchronized void loadFromParent()
|
||||
{
|
||||
if (parent == null) throw new IllegalStateException();
|
||||
|
||||
list.clear();
|
||||
for (Habit h : parent) if (filter.matches(h)) list.add(h);
|
||||
resort();
|
||||
}
|
||||
|
||||
private synchronized void resort()
|
||||
{
|
||||
if (comparator != null) Collections.sort(list, comparator);
|
||||
}
|
||||
}
|
||||
@@ -1,69 +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.core.models.memory;
|
||||
|
||||
import org.isoron.uhabits.core.database.*;
|
||||
import org.isoron.uhabits.core.models.*;
|
||||
import org.isoron.uhabits.core.models.sqlite.records.*;
|
||||
|
||||
public class MemoryModelFactory implements ModelFactory
|
||||
{
|
||||
@Override
|
||||
public CheckmarkList buildCheckmarkList(Habit habit)
|
||||
{
|
||||
return new MemoryCheckmarkList(habit);
|
||||
}
|
||||
|
||||
@Override
|
||||
public HabitList buildHabitList()
|
||||
{
|
||||
return new MemoryHabitList();
|
||||
}
|
||||
|
||||
@Override
|
||||
public RepetitionList buildRepetitionList(Habit habit)
|
||||
{
|
||||
return new MemoryRepetitionList(habit);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ScoreList buildScoreList(Habit habit)
|
||||
{
|
||||
return new MemoryScoreList(habit);
|
||||
}
|
||||
|
||||
@Override
|
||||
public StreakList buildStreakList(Habit habit)
|
||||
{
|
||||
return new MemoryStreakList(habit);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Repository<HabitRecord> buildHabitListRepository()
|
||||
{
|
||||
throw new IllegalStateException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Repository<RepetitionRecord> buildRepetitionListRepository()
|
||||
{
|
||||
throw new IllegalStateException();
|
||||
}
|
||||
}
|
||||
@@ -1,133 +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.core.models.memory;
|
||||
|
||||
import android.support.annotation.*;
|
||||
|
||||
import org.isoron.uhabits.core.models.*;
|
||||
|
||||
import java.util.*;
|
||||
|
||||
/**
|
||||
* In-memory implementation of {@link RepetitionList}.
|
||||
*/
|
||||
public class MemoryRepetitionList extends RepetitionList
|
||||
{
|
||||
ArrayList<Repetition> list;
|
||||
|
||||
public MemoryRepetitionList(Habit habit)
|
||||
{
|
||||
super(habit);
|
||||
list = new ArrayList<>();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void add(Repetition repetition)
|
||||
{
|
||||
list.add(repetition);
|
||||
observable.notifyListeners();
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Repetition> getByInterval(Timestamp fromTimestamp, Timestamp toTimestamp)
|
||||
{
|
||||
ArrayList<Repetition> filtered = new ArrayList<>();
|
||||
|
||||
for (Repetition r : list)
|
||||
{
|
||||
Timestamp t = r.getTimestamp();
|
||||
if (t.isOlderThan(fromTimestamp) || t.isNewerThan(toTimestamp)) continue;
|
||||
filtered.add(r);
|
||||
}
|
||||
|
||||
Collections.sort(filtered,
|
||||
(r1, r2) -> r1.getTimestamp().compare(r2.getTimestamp()));
|
||||
|
||||
return filtered;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public Repetition getByTimestamp(Timestamp timestamp)
|
||||
{
|
||||
for (Repetition r : list)
|
||||
if (r.getTimestamp().equals(timestamp)) return r;
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public Repetition getOldest()
|
||||
{
|
||||
Timestamp oldestTimestamp = Timestamp.ZERO.plus(1000000);
|
||||
Repetition oldestRep = null;
|
||||
|
||||
for (Repetition rep : list)
|
||||
{
|
||||
if (rep.getTimestamp().isOlderThan(oldestTimestamp))
|
||||
{
|
||||
oldestRep = rep;
|
||||
oldestTimestamp = rep.getTimestamp();
|
||||
}
|
||||
}
|
||||
|
||||
return oldestRep;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public Repetition getNewest()
|
||||
{
|
||||
Timestamp newestTimestamp = Timestamp.ZERO;
|
||||
Repetition newestRep = null;
|
||||
|
||||
for (Repetition rep : list)
|
||||
{
|
||||
if (rep.getTimestamp().isNewerThan(newestTimestamp))
|
||||
{
|
||||
newestRep = rep;
|
||||
newestTimestamp = rep.getTimestamp();
|
||||
}
|
||||
}
|
||||
|
||||
return newestRep;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void remove(@NonNull Repetition repetition)
|
||||
{
|
||||
list.remove(repetition);
|
||||
observable.notifyListeners();
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getTotalCount()
|
||||
{
|
||||
return list.size();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeAll()
|
||||
{
|
||||
list.clear();
|
||||
getObservable().notifyListeners();
|
||||
}
|
||||
}
|
||||
@@ -1,106 +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.core.models.memory;
|
||||
|
||||
import android.support.annotation.*;
|
||||
|
||||
import org.isoron.uhabits.core.models.*;
|
||||
|
||||
import java.util.*;
|
||||
|
||||
public class MemoryScoreList extends ScoreList
|
||||
{
|
||||
ArrayList<Score> list;
|
||||
|
||||
public MemoryScoreList(Habit habit)
|
||||
{
|
||||
super(habit);
|
||||
list = new ArrayList<>();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void add(List<Score> scores)
|
||||
{
|
||||
list.addAll(scores);
|
||||
Collections.sort(list,
|
||||
(s1, s2) -> s2.getTimestamp().compare(s1.getTimestamp()));
|
||||
getObservable().notifyListeners();
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public List<Score> getByInterval(@NonNull Timestamp fromTimestamp,
|
||||
@NonNull Timestamp toTimestamp)
|
||||
{
|
||||
compute(fromTimestamp, toTimestamp);
|
||||
|
||||
List<Score> filtered = new LinkedList<>();
|
||||
|
||||
for (Score s : list)
|
||||
{
|
||||
if (s.getTimestamp().isNewerThan(toTimestamp) ||
|
||||
s.getTimestamp().isOlderThan(fromTimestamp)) continue;
|
||||
filtered.add(s);
|
||||
}
|
||||
|
||||
return filtered;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public Score getComputedByTimestamp(Timestamp timestamp)
|
||||
{
|
||||
for (Score s : list)
|
||||
if (s.getTimestamp().equals(timestamp)) return s;
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void invalidateNewerThan(Timestamp timestamp)
|
||||
{
|
||||
list.clear();
|
||||
getObservable().notifyListeners();
|
||||
}
|
||||
|
||||
@Override
|
||||
@NonNull
|
||||
public List<Score> toList()
|
||||
{
|
||||
computeAll();
|
||||
return new LinkedList<>(list);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
protected Score getNewestComputed()
|
||||
{
|
||||
if (list.isEmpty()) return null;
|
||||
return list.get(0);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
protected Score getOldestComputed()
|
||||
{
|
||||
if (list.isEmpty()) return null;
|
||||
return list.get(list.size() - 1);
|
||||
}
|
||||
}
|
||||
@@ -1,78 +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.core.models.memory;
|
||||
|
||||
import android.support.annotation.*;
|
||||
|
||||
import org.isoron.uhabits.core.models.*;
|
||||
|
||||
import java.util.*;
|
||||
|
||||
public class MemoryStreakList extends StreakList
|
||||
{
|
||||
ArrayList<Streak> list;
|
||||
|
||||
public MemoryStreakList(Habit habit)
|
||||
{
|
||||
super(habit);
|
||||
list = new ArrayList<>();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Streak getNewestComputed()
|
||||
{
|
||||
Streak newest = null;
|
||||
|
||||
for (Streak s : list)
|
||||
if (newest == null || s.getEnd().isNewerThan(newest.getEnd()))
|
||||
newest = s;
|
||||
|
||||
return newest;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void invalidateNewerThan(Timestamp timestamp)
|
||||
{
|
||||
list.clear();
|
||||
observable.notifyListeners();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void add(@NonNull List<Streak> streaks)
|
||||
{
|
||||
list.addAll(streaks);
|
||||
Collections.sort(list, (s1, s2) -> s2.compareNewer(s1));
|
||||
observable.notifyListeners();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void removeNewestComputed()
|
||||
{
|
||||
Streak newest = getNewestComputed();
|
||||
if (newest != null) list.remove(newest);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Streak> getAll()
|
||||
{
|
||||
rebuild();
|
||||
return new LinkedList<>(list);
|
||||
}
|
||||
}
|
||||
@@ -1,23 +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/>.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Provides in-memory implementation of core models.
|
||||
*/
|
||||
package org.isoron.uhabits.core.models.memory;
|
||||
@@ -1,24 +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 Licenses along
|
||||
* with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Provides core models classes, such as {@link org.isoron.uhabits.core.models.Habit}
|
||||
* and {@link org.isoron.uhabits.core.models.Repetition}.
|
||||
*/
|
||||
package org.isoron.uhabits.core.models;
|
||||
@@ -1,85 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2017 Á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.core.models.sqlite;
|
||||
|
||||
import org.isoron.uhabits.core.database.*;
|
||||
import org.isoron.uhabits.core.models.*;
|
||||
import org.isoron.uhabits.core.models.memory.*;
|
||||
import org.isoron.uhabits.core.models.sqlite.records.*;
|
||||
|
||||
import javax.inject.*;
|
||||
|
||||
/**
|
||||
* Factory that provides models backed by an SQLite database.
|
||||
*/
|
||||
public class SQLModelFactory implements ModelFactory
|
||||
{
|
||||
public final Database db;
|
||||
|
||||
@Inject
|
||||
public SQLModelFactory(Database db)
|
||||
{
|
||||
this.db = db;
|
||||
}
|
||||
|
||||
@Override
|
||||
public CheckmarkList buildCheckmarkList(Habit habit)
|
||||
{
|
||||
return new MemoryCheckmarkList(habit);
|
||||
}
|
||||
|
||||
@Override
|
||||
public HabitList buildHabitList()
|
||||
{
|
||||
return new SQLiteHabitList(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public RepetitionList buildRepetitionList(Habit habit)
|
||||
{
|
||||
return new SQLiteRepetitionList(habit, this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ScoreList buildScoreList(Habit habit)
|
||||
{
|
||||
return new MemoryScoreList(habit);
|
||||
}
|
||||
|
||||
@Override
|
||||
public StreakList buildStreakList(Habit habit)
|
||||
{
|
||||
return new MemoryStreakList(habit);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Repository<HabitRecord> buildHabitListRepository()
|
||||
{
|
||||
return new Repository<>(HabitRecord.class, db);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Repository<RepetitionRecord> buildRepetitionListRepository()
|
||||
{
|
||||
return new Repository<>(RepetitionRecord.class, db);
|
||||
}
|
||||
}
|
||||
@@ -1,252 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2017 Á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.core.models.sqlite;
|
||||
|
||||
import android.support.annotation.*;
|
||||
|
||||
import org.isoron.uhabits.core.database.*;
|
||||
import org.isoron.uhabits.core.models.*;
|
||||
import org.isoron.uhabits.core.models.memory.*;
|
||||
import org.isoron.uhabits.core.models.sqlite.records.*;
|
||||
|
||||
import java.util.*;
|
||||
|
||||
import javax.inject.*;
|
||||
|
||||
/**
|
||||
* Implementation of a {@link HabitList} that is backed by SQLite.
|
||||
*/
|
||||
public class SQLiteHabitList extends HabitList
|
||||
{
|
||||
private static SQLiteHabitList instance;
|
||||
|
||||
@NonNull
|
||||
private final Repository<HabitRecord> repository;
|
||||
|
||||
@NonNull
|
||||
private final ModelFactory modelFactory;
|
||||
|
||||
@NonNull
|
||||
private final MemoryHabitList list;
|
||||
|
||||
private boolean loaded = false;
|
||||
|
||||
@Inject
|
||||
public SQLiteHabitList(@NonNull ModelFactory modelFactory)
|
||||
{
|
||||
super();
|
||||
this.modelFactory = modelFactory;
|
||||
this.list = new MemoryHabitList();
|
||||
this.repository = modelFactory.buildHabitListRepository();
|
||||
}
|
||||
|
||||
private void loadRecords()
|
||||
{
|
||||
if (loaded) return;
|
||||
loaded = true;
|
||||
|
||||
list.removeAll();
|
||||
List<HabitRecord> records = repository.findAll("order by position");
|
||||
for (HabitRecord rec : records)
|
||||
{
|
||||
Habit h = modelFactory.buildHabit();
|
||||
rec.copyTo(h);
|
||||
list.add(h);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized void add(@NonNull Habit habit)
|
||||
{
|
||||
loadRecords();
|
||||
habit.setPosition(size());
|
||||
list.add(habit);
|
||||
|
||||
HabitRecord record = new HabitRecord();
|
||||
record.copyFrom(habit);
|
||||
repository.save(record);
|
||||
rebuildOrder();
|
||||
|
||||
getObservable().notifyListeners();
|
||||
}
|
||||
|
||||
@Override
|
||||
@Nullable
|
||||
public synchronized Habit getById(long id)
|
||||
{
|
||||
loadRecords();
|
||||
return list.getById(id);
|
||||
}
|
||||
|
||||
@Override
|
||||
@NonNull
|
||||
public synchronized Habit getByPosition(int position)
|
||||
{
|
||||
loadRecords();
|
||||
return list.getByPosition(position);
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public synchronized HabitList getFiltered(HabitMatcher filter)
|
||||
{
|
||||
loadRecords();
|
||||
return list.getFiltered(filter);
|
||||
}
|
||||
|
||||
@Override
|
||||
@NonNull
|
||||
public Order getOrder()
|
||||
{
|
||||
return list.getOrder();
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized void setOrder(@NonNull Order order)
|
||||
{
|
||||
list.setOrder(order);
|
||||
getObservable().notifyListeners();
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized int indexOf(@NonNull Habit h)
|
||||
{
|
||||
loadRecords();
|
||||
return list.indexOf(h);
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized Iterator<Habit> iterator()
|
||||
{
|
||||
loadRecords();
|
||||
return list.iterator();
|
||||
}
|
||||
|
||||
private synchronized void rebuildOrder()
|
||||
{
|
||||
List<HabitRecord> records = repository.findAll("order by position");
|
||||
repository.executeAsTransaction(() -> {
|
||||
int pos = 0;
|
||||
for (HabitRecord r : records) {
|
||||
r.position = pos++;
|
||||
repository.save(r);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized void remove(@NonNull Habit habit)
|
||||
{
|
||||
loadRecords();
|
||||
list.remove(habit);
|
||||
|
||||
HabitRecord record = repository.find(habit.getId());
|
||||
if (record == null) throw new RuntimeException("habit not in database");
|
||||
repository.executeAsTransaction(() ->
|
||||
{
|
||||
((SQLiteRepetitionList) habit.getRepetitions()).removeAll();
|
||||
repository.remove(record);
|
||||
});
|
||||
|
||||
rebuildOrder();
|
||||
getObservable().notifyListeners();
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized void removeAll()
|
||||
{
|
||||
list.removeAll();
|
||||
repository.execSQL("delete from habits");
|
||||
repository.execSQL("delete from repetitions");
|
||||
getObservable().notifyListeners();
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized void reorder(@NonNull Habit from, @NonNull Habit to)
|
||||
{
|
||||
loadRecords();
|
||||
list.reorder(from, to);
|
||||
|
||||
HabitRecord fromRecord = repository.find(from.getId());
|
||||
HabitRecord toRecord = repository.find(to.getId());
|
||||
|
||||
if (fromRecord == null)
|
||||
throw new RuntimeException("habit not in database");
|
||||
if (toRecord == null)
|
||||
throw new RuntimeException("habit not in database");
|
||||
|
||||
if (toRecord.position < fromRecord.position)
|
||||
{
|
||||
repository.execSQL("update habits set position = position + 1 " +
|
||||
"where position >= ? and position < ?",
|
||||
toRecord.position, fromRecord.position);
|
||||
}
|
||||
else
|
||||
{
|
||||
repository.execSQL("update habits set position = position - 1 " +
|
||||
"where position > ? and position <= ?",
|
||||
fromRecord.position, toRecord.position);
|
||||
}
|
||||
|
||||
fromRecord.position = toRecord.position;
|
||||
repository.save(fromRecord);
|
||||
|
||||
getObservable().notifyListeners();
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized void repair()
|
||||
{
|
||||
loadRecords();
|
||||
rebuildOrder();
|
||||
getObservable().notifyListeners();
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized int size()
|
||||
{
|
||||
loadRecords();
|
||||
return list.size();
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized void update(List<Habit> habits)
|
||||
{
|
||||
loadRecords();
|
||||
list.update(habits);
|
||||
|
||||
for (Habit h : habits)
|
||||
{
|
||||
HabitRecord record = repository.find(h.getId());
|
||||
if (record == null) continue;
|
||||
record.copyFrom(h);
|
||||
repository.save(record);
|
||||
}
|
||||
|
||||
getObservable().notifyListeners();
|
||||
}
|
||||
|
||||
public synchronized void reload()
|
||||
{
|
||||
loaded = false;
|
||||
}
|
||||
}
|
||||
@@ -1,148 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2017 Á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.core.models.sqlite;
|
||||
|
||||
import android.support.annotation.*;
|
||||
import android.support.annotation.Nullable;
|
||||
|
||||
import org.isoron.uhabits.core.database.*;
|
||||
import org.isoron.uhabits.core.models.*;
|
||||
import org.isoron.uhabits.core.models.memory.*;
|
||||
import org.isoron.uhabits.core.models.sqlite.records.*;
|
||||
import org.jetbrains.annotations.*;
|
||||
|
||||
import java.util.*;
|
||||
|
||||
/**
|
||||
* Implementation of a {@link RepetitionList} that is backed by SQLite.
|
||||
*/
|
||||
public class SQLiteRepetitionList extends RepetitionList
|
||||
{
|
||||
private final Repository<RepetitionRecord> repository;
|
||||
|
||||
private final MemoryRepetitionList list;
|
||||
|
||||
private boolean loaded = false;
|
||||
|
||||
public SQLiteRepetitionList(@NonNull Habit habit,
|
||||
@NonNull ModelFactory modelFactory)
|
||||
{
|
||||
super(habit);
|
||||
repository = modelFactory.buildRepetitionListRepository();
|
||||
list = new MemoryRepetitionList(habit);
|
||||
}
|
||||
|
||||
private void loadRecords()
|
||||
{
|
||||
if (loaded) return;
|
||||
loaded = true;
|
||||
|
||||
check(habit.getId());
|
||||
List<RepetitionRecord> records =
|
||||
repository.findAll("where habit = ? order by timestamp",
|
||||
habit.getId().toString());
|
||||
|
||||
for (RepetitionRecord rec : records)
|
||||
list.add(rec.toRepetition());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void add(Repetition rep)
|
||||
{
|
||||
loadRecords();
|
||||
list.add(rep);
|
||||
check(habit.getId());
|
||||
RepetitionRecord record = new RepetitionRecord();
|
||||
record.habit_id = habit.getId();
|
||||
record.copyFrom(rep);
|
||||
repository.save(record);
|
||||
observable.notifyListeners();
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Repetition> getByInterval(Timestamp timeFrom, Timestamp timeTo)
|
||||
{
|
||||
loadRecords();
|
||||
return list.getByInterval(timeFrom, timeTo);
|
||||
}
|
||||
|
||||
@Override
|
||||
@Nullable
|
||||
public Repetition getByTimestamp(Timestamp timestamp)
|
||||
{
|
||||
loadRecords();
|
||||
return list.getByTimestamp(timestamp);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Repetition getOldest()
|
||||
{
|
||||
loadRecords();
|
||||
return list.getOldest();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Repetition getNewest()
|
||||
{
|
||||
loadRecords();
|
||||
return list.getNewest();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void remove(@NonNull Repetition repetition)
|
||||
{
|
||||
loadRecords();
|
||||
list.remove(repetition);
|
||||
check(habit.getId());
|
||||
repository.execSQL(
|
||||
"delete from repetitions where habit = ? and timestamp = ?",
|
||||
habit.getId(), repetition.getTimestamp().getUnixTime());
|
||||
observable.notifyListeners();
|
||||
}
|
||||
|
||||
public void removeAll()
|
||||
{
|
||||
loadRecords();
|
||||
list.removeAll();
|
||||
check(habit.getId());
|
||||
repository.execSQL("delete from repetitions where habit = ?",
|
||||
habit.getId());
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getTotalCount()
|
||||
{
|
||||
loadRecords();
|
||||
return list.getTotalCount();
|
||||
}
|
||||
|
||||
public void reload()
|
||||
{
|
||||
loaded = false;
|
||||
}
|
||||
|
||||
@Contract("null -> fail")
|
||||
private void check(Long value)
|
||||
{
|
||||
if (value == null) throw new RuntimeException("null check failed");
|
||||
}
|
||||
}
|
||||
@@ -1,25 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2017 Á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/>.
|
||||
*
|
||||
*
|
||||
*/
|
||||
|
||||
/**
|
||||
* Provides SQLite implementations of the core models.
|
||||
*/
|
||||
package org.isoron.uhabits.core.models.sqlite;
|
||||
@@ -1,131 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2017 Á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.core.models.sqlite.records;
|
||||
|
||||
import org.isoron.uhabits.core.database.*;
|
||||
import org.isoron.uhabits.core.models.*;
|
||||
|
||||
/**
|
||||
* The SQLite database record corresponding to a {@link Habit}.
|
||||
*/
|
||||
@Table(name = "habits")
|
||||
public class HabitRecord
|
||||
{
|
||||
@Column
|
||||
public String description;
|
||||
|
||||
@Column
|
||||
public String name;
|
||||
|
||||
@Column(name = "freq_num")
|
||||
public Integer freqNum;
|
||||
|
||||
@Column(name = "freq_den")
|
||||
public Integer freqDen;
|
||||
|
||||
@Column
|
||||
public Integer color;
|
||||
|
||||
@Column
|
||||
public Integer position;
|
||||
|
||||
@Column(name = "reminder_hour")
|
||||
public Integer reminderHour;
|
||||
|
||||
@Column(name = "reminder_min")
|
||||
public Integer reminderMin;
|
||||
|
||||
@Column(name = "reminder_days")
|
||||
public Integer reminderDays;
|
||||
|
||||
@Column
|
||||
public Integer highlight;
|
||||
|
||||
@Column
|
||||
public Integer archived;
|
||||
|
||||
@Column
|
||||
public Integer type;
|
||||
|
||||
@Column(name = "target_value")
|
||||
public Double targetValue;
|
||||
|
||||
@Column(name = "target_type")
|
||||
public Integer targetType;
|
||||
|
||||
@Column
|
||||
public String unit;
|
||||
|
||||
@Column
|
||||
public Long id;
|
||||
|
||||
public void copyFrom(Habit model)
|
||||
{
|
||||
this.id = model.getId();
|
||||
this.name = model.getName();
|
||||
this.description = model.getDescription();
|
||||
this.highlight = 0;
|
||||
this.color = model.getColor();
|
||||
this.archived = model.isArchived() ? 1 : 0;
|
||||
this.type = model.getType();
|
||||
this.targetType = model.getTargetType();
|
||||
this.targetValue = model.getTargetValue();
|
||||
this.unit = model.getUnit();
|
||||
this.position = model.getPosition();
|
||||
|
||||
Frequency freq = model.getFrequency();
|
||||
this.freqNum = freq.getNumerator();
|
||||
this.freqDen = freq.getDenominator();
|
||||
this.reminderDays = 0;
|
||||
this.reminderMin = null;
|
||||
this.reminderHour = null;
|
||||
|
||||
if (model.hasReminder())
|
||||
{
|
||||
Reminder reminder = model.getReminder();
|
||||
this.reminderHour = reminder.getHour();
|
||||
this.reminderMin = reminder.getMinute();
|
||||
this.reminderDays = reminder.getDays().toInteger();
|
||||
}
|
||||
}
|
||||
|
||||
public void copyTo(Habit habit)
|
||||
{
|
||||
habit.setId(this.id);
|
||||
habit.setName(this.name);
|
||||
habit.setDescription(this.description);
|
||||
habit.setFrequency(new Frequency(this.freqNum, this.freqDen));
|
||||
habit.setColor(this.color);
|
||||
habit.setArchived(this.archived != 0);
|
||||
habit.setType(this.type);
|
||||
habit.setTargetType(this.targetType);
|
||||
habit.setTargetValue(this.targetValue);
|
||||
habit.setUnit(this.unit);
|
||||
habit.setPosition(this.position);
|
||||
|
||||
if (reminderHour != null && reminderMin != null)
|
||||
{
|
||||
habit.setReminder(new Reminder(reminderHour, reminderMin,
|
||||
new WeekdayList(reminderDays)));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,57 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2017 Á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.core.models.sqlite.records;
|
||||
|
||||
import org.isoron.uhabits.core.database.*;
|
||||
import org.isoron.uhabits.core.models.*;
|
||||
|
||||
/**
|
||||
* The SQLite database record corresponding to a {@link Repetition}.
|
||||
*/
|
||||
@Table(name = "Repetitions")
|
||||
public class RepetitionRecord
|
||||
{
|
||||
public HabitRecord habit;
|
||||
|
||||
@Column(name = "habit")
|
||||
public Long habit_id;
|
||||
|
||||
@Column
|
||||
public Long timestamp;
|
||||
|
||||
@Column
|
||||
public Integer value;
|
||||
|
||||
@Column
|
||||
public Long id;
|
||||
|
||||
public void copyFrom(Repetition repetition)
|
||||
{
|
||||
timestamp = repetition.getTimestamp().getUnixTime();
|
||||
value = repetition.getValue();
|
||||
}
|
||||
|
||||
public Repetition toRepetition()
|
||||
{
|
||||
return new Repetition(new Timestamp(timestamp), value);
|
||||
}
|
||||
}
|
||||
@@ -1,385 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2017 Á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.core.preferences;
|
||||
|
||||
import android.support.annotation.*;
|
||||
|
||||
import org.isoron.uhabits.core.models.*;
|
||||
import org.isoron.uhabits.core.ui.*;
|
||||
import org.isoron.uhabits.core.utils.*;
|
||||
|
||||
import java.util.*;
|
||||
|
||||
public class Preferences
|
||||
{
|
||||
|
||||
public static final String DEFAULT_SYNC_SERVER =
|
||||
"https://sync.loophabits.org";
|
||||
|
||||
@NonNull
|
||||
private final Storage storage;
|
||||
|
||||
@NonNull
|
||||
private List<Listener> listeners;
|
||||
|
||||
@Nullable
|
||||
private Boolean shouldReverseCheckmarks = null;
|
||||
|
||||
public Preferences(@NonNull Storage storage)
|
||||
{
|
||||
this.storage = storage;
|
||||
listeners = new LinkedList<>();
|
||||
storage.onAttached(this);
|
||||
}
|
||||
|
||||
public void addListener(Listener listener)
|
||||
{
|
||||
listeners.add(listener);
|
||||
}
|
||||
|
||||
public Integer getDefaultHabitColor(int fallbackColor)
|
||||
{
|
||||
return storage.getInt("pref_default_habit_palette_color",
|
||||
fallbackColor);
|
||||
}
|
||||
|
||||
public HabitList.Order getDefaultOrder()
|
||||
{
|
||||
String name = storage.getString("pref_default_order", "BY_POSITION");
|
||||
|
||||
try
|
||||
{
|
||||
return HabitList.Order.valueOf(name);
|
||||
}
|
||||
catch (IllegalArgumentException e)
|
||||
{
|
||||
setDefaultOrder(HabitList.Order.BY_POSITION);
|
||||
return HabitList.Order.BY_POSITION;
|
||||
}
|
||||
}
|
||||
|
||||
public void setDefaultOrder(HabitList.Order order)
|
||||
{
|
||||
storage.putString("pref_default_order", order.name());
|
||||
}
|
||||
|
||||
public int getDefaultScoreSpinnerPosition()
|
||||
{
|
||||
int defaultScoreInterval =
|
||||
storage.getInt("pref_score_view_interval", 1);
|
||||
|
||||
if (defaultScoreInterval > 5 || defaultScoreInterval < 0)
|
||||
{
|
||||
defaultScoreInterval = 1;
|
||||
storage.putInt("pref_score_view_interval", 1);
|
||||
}
|
||||
|
||||
return defaultScoreInterval;
|
||||
}
|
||||
|
||||
public void setDefaultScoreSpinnerPosition(int position)
|
||||
{
|
||||
storage.putInt("pref_score_view_interval", position);
|
||||
}
|
||||
|
||||
public int getLastHintNumber()
|
||||
{
|
||||
return storage.getInt("last_hint_number", -1);
|
||||
}
|
||||
|
||||
public Timestamp getLastHintTimestamp()
|
||||
{
|
||||
long unixTime = storage.getLong("last_hint_timestamp", -1);
|
||||
if (unixTime < 0) return null;
|
||||
else return new Timestamp(unixTime);
|
||||
}
|
||||
|
||||
public long getLastSync()
|
||||
{
|
||||
return storage.getLong("last_sync", 0);
|
||||
}
|
||||
|
||||
public void setLastSync(long timestamp)
|
||||
{
|
||||
storage.putLong("last_sync", timestamp);
|
||||
}
|
||||
|
||||
public boolean getShowArchived()
|
||||
{
|
||||
return storage.getBoolean("pref_show_archived", false);
|
||||
}
|
||||
|
||||
public void setShowArchived(boolean showArchived)
|
||||
{
|
||||
storage.putBoolean("pref_show_archived", showArchived);
|
||||
}
|
||||
|
||||
public boolean getShowCompleted()
|
||||
{
|
||||
return storage.getBoolean("pref_show_completed", true);
|
||||
}
|
||||
|
||||
public void setShowCompleted(boolean showCompleted)
|
||||
{
|
||||
storage.putBoolean("pref_show_completed", showCompleted);
|
||||
}
|
||||
|
||||
public long getSnoozeInterval()
|
||||
{
|
||||
return Long.parseLong(storage.getString("pref_snooze_interval", "15"));
|
||||
}
|
||||
|
||||
public void setSnoozeInterval(int interval)
|
||||
{
|
||||
storage.putString("pref_snooze_interval", String.valueOf(interval));
|
||||
}
|
||||
|
||||
public String getSyncAddress()
|
||||
{
|
||||
return storage.getString("pref_sync_address", DEFAULT_SYNC_SERVER);
|
||||
}
|
||||
|
||||
public void setSyncAddress(String address)
|
||||
{
|
||||
storage.putString("pref_sync_address", address);
|
||||
for (Listener l : listeners) l.onSyncFeatureChanged();
|
||||
}
|
||||
|
||||
public String getSyncClientId()
|
||||
{
|
||||
String id = storage.getString("pref_sync_client_id", "");
|
||||
if (!id.isEmpty()) return id;
|
||||
|
||||
id = UUID.randomUUID().toString();
|
||||
storage.putString("pref_sync_client_id", id);
|
||||
|
||||
return id;
|
||||
}
|
||||
|
||||
public String getSyncKey()
|
||||
{
|
||||
return storage.getString("pref_sync_key", "");
|
||||
}
|
||||
|
||||
public void setSyncKey(String key)
|
||||
{
|
||||
storage.putString("pref_sync_key", key);
|
||||
for (Listener l : listeners) l.onSyncFeatureChanged();
|
||||
}
|
||||
|
||||
public int getTheme()
|
||||
{
|
||||
return storage.getInt("pref_theme", ThemeSwitcher.THEME_LIGHT);
|
||||
}
|
||||
|
||||
public void setTheme(int theme)
|
||||
{
|
||||
storage.putInt("pref_theme", theme);
|
||||
}
|
||||
|
||||
public void incrementLaunchCount()
|
||||
{
|
||||
storage.putInt("launch_count", getLaunchCount() + 1);
|
||||
}
|
||||
|
||||
public int getLaunchCount()
|
||||
{
|
||||
return storage.getInt("launch_count", 0);
|
||||
}
|
||||
|
||||
public boolean isDeveloper()
|
||||
{
|
||||
return storage.getBoolean("pref_developer", false);
|
||||
}
|
||||
|
||||
public void setDeveloper(boolean isDeveloper)
|
||||
{
|
||||
storage.putBoolean("pref_developer", isDeveloper);
|
||||
}
|
||||
|
||||
public boolean isFirstRun()
|
||||
{
|
||||
return storage.getBoolean("pref_first_run", true);
|
||||
}
|
||||
|
||||
public void setFirstRun(boolean isFirstRun)
|
||||
{
|
||||
storage.putBoolean("pref_first_run", isFirstRun);
|
||||
}
|
||||
|
||||
public boolean isNumericalHabitsFeatureEnabled()
|
||||
{
|
||||
return storage.getBoolean("pref_feature_numerical_habits", false);
|
||||
}
|
||||
|
||||
public void setNumericalHabitsFeatureEnabled(boolean enabled)
|
||||
{
|
||||
storage.putBoolean("pref_feature_numerical_habits", enabled);
|
||||
}
|
||||
|
||||
public boolean isPureBlackEnabled()
|
||||
{
|
||||
return storage.getBoolean("pref_pure_black", false);
|
||||
}
|
||||
|
||||
public void setPureBlackEnabled(boolean enabled)
|
||||
{
|
||||
storage.putBoolean("pref_pure_black", enabled);
|
||||
}
|
||||
|
||||
public boolean isShortToggleEnabled()
|
||||
{
|
||||
return storage.getBoolean("pref_short_toggle", false);
|
||||
}
|
||||
|
||||
public void setShortToggleEnabled(boolean enabled)
|
||||
{
|
||||
storage.putBoolean("pref_short_toggle", enabled);
|
||||
}
|
||||
|
||||
public boolean isSyncEnabled()
|
||||
{
|
||||
return storage.getBoolean("pref_feature_sync", false);
|
||||
}
|
||||
|
||||
public void setSyncEnabled(boolean isEnabled)
|
||||
{
|
||||
storage.putBoolean("pref_feature_sync", isEnabled);
|
||||
for (Listener l : listeners) l.onSyncFeatureChanged();
|
||||
}
|
||||
|
||||
public void removeListener(Listener listener)
|
||||
{
|
||||
listeners.remove(listener);
|
||||
}
|
||||
|
||||
public void clear()
|
||||
{
|
||||
storage.clear();
|
||||
}
|
||||
|
||||
public void setDefaultHabitColor(int color)
|
||||
{
|
||||
storage.putInt("pref_default_habit_palette_color", color);
|
||||
}
|
||||
|
||||
public void setNotificationsSticky(boolean sticky)
|
||||
{
|
||||
storage.putBoolean("pref_sticky_notifications", sticky);
|
||||
for (Listener l : listeners) l.onNotificationsChanged();
|
||||
}
|
||||
|
||||
public void setNotificationsLed(boolean enabled)
|
||||
{
|
||||
storage.putBoolean("pref_led_notifications", enabled);
|
||||
for (Listener l : listeners) l.onNotificationsChanged();
|
||||
}
|
||||
|
||||
public boolean shouldMakeNotificationsSticky()
|
||||
{
|
||||
return storage.getBoolean("pref_sticky_notifications", false);
|
||||
}
|
||||
|
||||
public boolean shouldMakeNotificationsLed()
|
||||
{
|
||||
return storage.getBoolean("pref_led_notifications", false);
|
||||
}
|
||||
|
||||
public boolean isCheckmarkSequenceReversed()
|
||||
{
|
||||
if (shouldReverseCheckmarks == null) shouldReverseCheckmarks =
|
||||
storage.getBoolean("pref_checkmark_reverse_order", false);
|
||||
|
||||
return shouldReverseCheckmarks;
|
||||
}
|
||||
|
||||
public void setCheckmarkSequenceReversed(boolean reverse)
|
||||
{
|
||||
shouldReverseCheckmarks = reverse;
|
||||
storage.putBoolean("pref_checkmark_reverse_order", reverse);
|
||||
for (Listener l : listeners) l.onCheckmarkSequenceChanged();
|
||||
}
|
||||
|
||||
public void updateLastHint(int number, Timestamp timestamp)
|
||||
{
|
||||
storage.putInt("last_hint_number", number);
|
||||
storage.putLong("last_hint_timestamp", timestamp.getUnixTime());
|
||||
}
|
||||
|
||||
public int getLastAppVersion()
|
||||
{
|
||||
return storage.getInt("last_version", 0);
|
||||
}
|
||||
|
||||
public void setLastAppVersion(int version)
|
||||
{
|
||||
storage.putInt("last_version", version);
|
||||
}
|
||||
|
||||
public interface Listener
|
||||
{
|
||||
default void onCheckmarkSequenceChanged()
|
||||
{
|
||||
}
|
||||
|
||||
default void onNotificationsChanged()
|
||||
{
|
||||
}
|
||||
|
||||
default void onSyncFeatureChanged()
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
public interface Storage
|
||||
{
|
||||
void clear();
|
||||
|
||||
boolean getBoolean(String key, boolean defValue);
|
||||
|
||||
int getInt(String key, int defValue);
|
||||
|
||||
long getLong(String key, long defValue);
|
||||
|
||||
String getString(String key, String defValue);
|
||||
|
||||
void onAttached(Preferences preferences);
|
||||
|
||||
void putBoolean(String key, boolean value);
|
||||
|
||||
void putInt(String key, int value);
|
||||
|
||||
void putLong(String key, long value);
|
||||
|
||||
void putString(String key, String value);
|
||||
|
||||
void remove(String key);
|
||||
|
||||
default void putLongArray(String key, long[] values)
|
||||
{
|
||||
putString(key, StringUtils.joinLongs(values));
|
||||
}
|
||||
|
||||
default long[] getLongArray(String key)
|
||||
{
|
||||
return StringUtils.splitLongs(getString(key, ""));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,134 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2015-2017 Á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.core.preferences;
|
||||
|
||||
import android.support.annotation.*;
|
||||
|
||||
import java.io.*;
|
||||
import java.util.*;
|
||||
|
||||
public class PropertiesStorage implements Preferences.Storage
|
||||
{
|
||||
@NonNull
|
||||
private final Properties props;
|
||||
|
||||
@NonNull
|
||||
private File file;
|
||||
|
||||
public PropertiesStorage(@NonNull File file)
|
||||
{
|
||||
try
|
||||
{
|
||||
this.file = file;
|
||||
props = new Properties();
|
||||
props.load(new FileInputStream(file));
|
||||
}
|
||||
catch (IOException e)
|
||||
{
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void clear()
|
||||
{
|
||||
for(String key : props.stringPropertyNames()) props.remove(key);
|
||||
flush();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean getBoolean(String key, boolean defValue)
|
||||
{
|
||||
String value = props.getProperty(key, Boolean.toString(defValue));
|
||||
return Boolean.parseBoolean(value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getInt(String key, int defValue)
|
||||
{
|
||||
String value = props.getProperty(key, Integer.toString(defValue));
|
||||
return Integer.parseInt(value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getLong(String key, long defValue)
|
||||
{
|
||||
String value = props.getProperty(key, Long.toString(defValue));
|
||||
return Long.parseLong(value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getString(String key, String defValue)
|
||||
{
|
||||
return props.getProperty(key, defValue);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAttached(Preferences preferences)
|
||||
{
|
||||
// nop
|
||||
}
|
||||
|
||||
@Override
|
||||
public void putBoolean(String key, boolean value)
|
||||
{
|
||||
props.setProperty(key, Boolean.toString(value));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void putInt(String key, int value)
|
||||
{
|
||||
props.setProperty(key, Integer.toString(value));
|
||||
flush();
|
||||
}
|
||||
|
||||
private void flush()
|
||||
{
|
||||
try
|
||||
{
|
||||
props.store(new FileOutputStream(file), "");
|
||||
}
|
||||
catch (IOException e)
|
||||
{
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void putLong(String key, long value)
|
||||
{
|
||||
props.setProperty(key, Long.toString(value));
|
||||
flush();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void putString(String key, String value)
|
||||
{
|
||||
props.setProperty(key, value);
|
||||
flush();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void remove(String key)
|
||||
{
|
||||
props.remove(key);
|
||||
flush();
|
||||
}
|
||||
}
|
||||
@@ -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.core.preferences;
|
||||
|
||||
import org.isoron.uhabits.core.AppScope;
|
||||
import org.isoron.uhabits.core.models.HabitNotFoundException;
|
||||
|
||||
import javax.inject.Inject;
|
||||
|
||||
@AppScope
|
||||
public class WidgetPreferences {
|
||||
private Preferences.Storage storage;
|
||||
|
||||
@Inject
|
||||
public WidgetPreferences(Preferences.Storage storage) {
|
||||
this.storage = storage;
|
||||
}
|
||||
|
||||
public void addWidget(int widgetId, long habitIds[]) {
|
||||
storage.putLongArray(getHabitIdKey(widgetId), habitIds);
|
||||
}
|
||||
|
||||
public long[] getHabitIdsFromWidgetId(int widgetId) {
|
||||
long habitIds[] = storage.getLongArray(getHabitIdKey(widgetId));
|
||||
if (habitIds.length == 0) throw new HabitNotFoundException();
|
||||
return habitIds;
|
||||
}
|
||||
|
||||
public void removeWidget(int id) {
|
||||
String habitIdKey = getHabitIdKey(id);
|
||||
storage.remove(habitIdKey);
|
||||
}
|
||||
|
||||
private String getHabitIdKey(int id) {
|
||||
return String.format("widget-%06d-habit", id);
|
||||
}
|
||||
}
|
||||
@@ -1,105 +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.core.reminders;
|
||||
|
||||
import android.support.annotation.*;
|
||||
|
||||
import org.isoron.uhabits.core.*;
|
||||
import org.isoron.uhabits.core.commands.*;
|
||||
import org.isoron.uhabits.core.models.*;
|
||||
|
||||
import javax.inject.*;
|
||||
|
||||
import static org.isoron.uhabits.core.utils.DateUtils.*;
|
||||
|
||||
@AppScope
|
||||
public class ReminderScheduler implements CommandRunner.Listener
|
||||
{
|
||||
private CommandRunner commandRunner;
|
||||
|
||||
private HabitList habitList;
|
||||
|
||||
private SystemScheduler sys;
|
||||
|
||||
@Inject
|
||||
public ReminderScheduler(@NonNull CommandRunner commandRunner,
|
||||
@NonNull HabitList habitList,
|
||||
@NonNull SystemScheduler sys)
|
||||
{
|
||||
this.commandRunner = commandRunner;
|
||||
this.habitList = habitList;
|
||||
this.sys = sys;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCommandExecuted(@NonNull Command command,
|
||||
@Nullable Long refreshKey)
|
||||
{
|
||||
if (command instanceof ToggleRepetitionCommand) return;
|
||||
if (command instanceof ChangeHabitColorCommand) return;
|
||||
scheduleAll();
|
||||
}
|
||||
|
||||
public void schedule(@NonNull Habit habit)
|
||||
{
|
||||
if (!habit.hasReminder()) return;
|
||||
Long reminderTime = habit.getReminder().getTimeInMillis();
|
||||
scheduleAtTime(habit, reminderTime);
|
||||
}
|
||||
|
||||
public void scheduleAtTime(@NonNull Habit habit, @NonNull Long reminderTime)
|
||||
{
|
||||
if (reminderTime == null) throw new IllegalArgumentException();
|
||||
if (!habit.hasReminder()) return;
|
||||
if (habit.isArchived()) return;
|
||||
long timestamp = getStartOfDay(removeTimezone(reminderTime));
|
||||
sys.scheduleShowReminder(reminderTime, habit, timestamp);
|
||||
}
|
||||
|
||||
public synchronized void scheduleAll()
|
||||
{
|
||||
HabitList reminderHabits =
|
||||
habitList.getFiltered(HabitMatcher.WITH_ALARM);
|
||||
for (Habit habit : reminderHabits)
|
||||
schedule(habit);
|
||||
}
|
||||
|
||||
public void startListening()
|
||||
{
|
||||
commandRunner.addListener(this);
|
||||
}
|
||||
|
||||
public void stopListening()
|
||||
{
|
||||
commandRunner.removeListener(this);
|
||||
}
|
||||
|
||||
public void scheduleMinutesFromNow(Habit habit, long minutes)
|
||||
{
|
||||
long now = applyTimezone(getLocalTime());
|
||||
long reminderTime = now + minutes * 60 * 1000;
|
||||
scheduleAtTime(habit, reminderTime);
|
||||
}
|
||||
|
||||
public interface SystemScheduler
|
||||
{
|
||||
void scheduleShowReminder(long reminderTime, Habit habit, long timestamp);
|
||||
}
|
||||
}
|
||||
@@ -1,84 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2017 Á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.core.tasks;
|
||||
|
||||
import android.support.annotation.*;
|
||||
|
||||
import com.google.auto.factory.*;
|
||||
|
||||
import org.isoron.uhabits.core.io.*;
|
||||
import org.isoron.uhabits.core.models.*;
|
||||
|
||||
import java.io.*;
|
||||
import java.util.*;
|
||||
|
||||
@AutoFactory(allowSubclasses = true)
|
||||
public class ExportCSVTask implements Task
|
||||
{
|
||||
private String archiveFilename;
|
||||
|
||||
@NonNull
|
||||
private final List<Habit> selectedHabits;
|
||||
|
||||
private File outputDir;
|
||||
|
||||
@NonNull
|
||||
private final ExportCSVTask.Listener listener;
|
||||
|
||||
@NonNull
|
||||
private final HabitList habitList;
|
||||
|
||||
public ExportCSVTask(@Provided @NonNull HabitList habitList,
|
||||
@NonNull List<Habit> selectedHabits,
|
||||
@NonNull File outputDir,
|
||||
@NonNull Listener listener)
|
||||
{
|
||||
this.listener = listener;
|
||||
this.habitList = habitList;
|
||||
this.selectedHabits = selectedHabits;
|
||||
this.outputDir = outputDir;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void doInBackground()
|
||||
{
|
||||
try
|
||||
{
|
||||
HabitsCSVExporter exporter;
|
||||
exporter = new HabitsCSVExporter(habitList, selectedHabits, outputDir);
|
||||
archiveFilename = exporter.writeArchive();
|
||||
}
|
||||
catch (IOException e)
|
||||
{
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPostExecute()
|
||||
{
|
||||
listener.onExportCSVFinished(archiveFilename);
|
||||
}
|
||||
|
||||
public interface Listener
|
||||
{
|
||||
void onExportCSVFinished(@Nullable String archiveFilename);
|
||||
}
|
||||
}
|
||||
@@ -1,62 +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.core.tasks;
|
||||
|
||||
import java.util.*;
|
||||
|
||||
public class SingleThreadTaskRunner implements TaskRunner
|
||||
{
|
||||
private List<Listener> listeners = new LinkedList<>();
|
||||
|
||||
@Override
|
||||
public void addListener(Listener listener)
|
||||
{
|
||||
listeners.add(listener);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void execute(Task task)
|
||||
{
|
||||
for(Listener l : listeners) l.onTaskStarted(task);
|
||||
task.onAttached(this);
|
||||
task.onPreExecute();
|
||||
task.doInBackground();
|
||||
task.onPostExecute();
|
||||
for(Listener l : listeners) l.onTaskFinished(task);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getActiveTaskCount()
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void publishProgress(Task task, int progress)
|
||||
{
|
||||
task.onProgressUpdate(progress);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeListener(Listener listener)
|
||||
{
|
||||
listeners.remove(listener);
|
||||
}
|
||||
}
|
||||
@@ -1,37 +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.core.tasks;
|
||||
|
||||
import android.support.annotation.*;
|
||||
|
||||
public interface Task
|
||||
{
|
||||
default void cancel() {}
|
||||
|
||||
void doInBackground();
|
||||
|
||||
default void onAttached(@NonNull TaskRunner runner) {}
|
||||
|
||||
default void onPostExecute() {}
|
||||
|
||||
default void onPreExecute() {}
|
||||
|
||||
default void onProgressUpdate(int value) {}
|
||||
}
|
||||
@@ -1,40 +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.core.tasks;
|
||||
|
||||
public interface TaskRunner
|
||||
{
|
||||
void addListener(Listener listener);
|
||||
|
||||
void removeListener(Listener listener);
|
||||
|
||||
void execute(Task task);
|
||||
|
||||
void publishProgress(Task task, int progress);
|
||||
|
||||
int getActiveTaskCount();
|
||||
|
||||
interface Listener
|
||||
{
|
||||
void onTaskStarted(Task task);
|
||||
|
||||
void onTaskFinished(Task task);
|
||||
}
|
||||
}
|
||||
@@ -1,155 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2017 Á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.core.test;
|
||||
|
||||
import org.isoron.uhabits.core.models.*;
|
||||
import org.isoron.uhabits.core.models.sqlite.*;
|
||||
import org.isoron.uhabits.core.utils.*;
|
||||
|
||||
public class HabitFixtures
|
||||
{
|
||||
public boolean NON_DAILY_HABIT_CHECKS[] = {
|
||||
true, false, false, true, true, true, false, false, true, true
|
||||
};
|
||||
|
||||
private final ModelFactory modelFactory;
|
||||
|
||||
private HabitList habitList;
|
||||
|
||||
public HabitFixtures(ModelFactory modelFactory, HabitList habitList)
|
||||
{
|
||||
this.modelFactory = modelFactory;
|
||||
this.habitList = habitList;
|
||||
}
|
||||
|
||||
public Habit createEmptyHabit()
|
||||
{
|
||||
Habit habit = modelFactory.buildHabit();
|
||||
habit.setName("Meditate");
|
||||
habit.setDescription("Did you meditate this morning?");
|
||||
habit.setColor(3);
|
||||
habit.setFrequency(Frequency.DAILY);
|
||||
saveIfSQLite(habit);
|
||||
|
||||
return habit;
|
||||
}
|
||||
|
||||
public Habit createLongHabit()
|
||||
{
|
||||
Habit habit = createEmptyHabit();
|
||||
habit.setFrequency(new Frequency(3, 7));
|
||||
habit.setColor(4);
|
||||
|
||||
Timestamp today = DateUtils.getToday();
|
||||
int marks[] = {0, 1, 3, 5, 7, 8, 9, 10, 12, 14, 15, 17, 19, 20, 26, 27,
|
||||
28, 50, 51, 52, 53, 54, 58, 60, 63, 65, 70, 71, 72, 73, 74, 75, 80,
|
||||
81, 83, 89, 90, 91, 95, 102, 103, 108, 109, 120};
|
||||
|
||||
for (int mark : marks)
|
||||
habit.getRepetitions().toggle(today.minus(mark));
|
||||
|
||||
return habit;
|
||||
}
|
||||
|
||||
public Habit createNumericalHabit()
|
||||
{
|
||||
Habit habit = modelFactory.buildHabit();
|
||||
habit.setType(Habit.NUMBER_HABIT);
|
||||
habit.setName("Run");
|
||||
habit.setDescription("How many miles did you run today?");
|
||||
habit.setUnit("miles");
|
||||
habit.setTargetType(Habit.AT_LEAST);
|
||||
habit.setTargetValue(2.0);
|
||||
habit.setColor(1);
|
||||
saveIfSQLite(habit);
|
||||
|
||||
Timestamp today = DateUtils.getToday();
|
||||
int times[] = {0, 1, 3, 5, 7, 8, 9, 10};
|
||||
int values[] = {100, 200, 300, 400, 500, 600, 700, 800};
|
||||
|
||||
for (int i = 0; i < times.length; i++)
|
||||
{
|
||||
Timestamp timestamp = today.minus(times[i]);
|
||||
habit.getRepetitions().add(new Repetition(timestamp, values[i]));
|
||||
}
|
||||
|
||||
return habit;
|
||||
}
|
||||
|
||||
public Habit createLongNumericalHabit(Timestamp reference)
|
||||
{
|
||||
Habit habit = modelFactory.buildHabit();
|
||||
habit.setType(Habit.NUMBER_HABIT);
|
||||
habit.setName("Walk");
|
||||
habit.setDescription("How many steps did you walk today?");
|
||||
habit.setUnit("steps");
|
||||
habit.setTargetType(Habit.AT_LEAST);
|
||||
habit.setTargetValue(100);
|
||||
habit.setColor(1);
|
||||
saveIfSQLite(habit);
|
||||
|
||||
int times[] = {0, 5, 9, 15, 17, 21, 23, 27, 28, 35, 41, 45, 47, 53, 56, 62, 70, 73, 78,
|
||||
83, 86, 94, 101, 106, 113, 114, 120, 126, 130, 133, 141, 143, 148, 151, 157, 164,
|
||||
166, 171, 173, 176, 179, 183, 191, 259, 264, 268, 270, 275, 282, 284, 289, 295,
|
||||
302, 306, 310, 315, 323, 325, 328, 335, 343, 349, 351, 353, 357, 359, 360, 367,
|
||||
372, 376, 380, 385, 393, 400, 404, 412, 415, 418, 422, 425, 433, 437, 444, 449,
|
||||
455, 460, 462, 465, 470, 471, 479, 481, 485, 489, 494, 495, 500, 501, 503, 507};
|
||||
|
||||
int values[] = {230, 306, 148, 281, 134, 285, 104, 158, 325, 236, 303, 210, 118, 124,
|
||||
301, 201, 156, 376, 347, 367, 396, 134, 160, 381, 155, 354, 231, 134, 164, 354,
|
||||
236, 398, 199, 221, 208, 397, 253, 276, 214, 341, 299, 221, 353, 250, 341, 168,
|
||||
374, 205, 182, 217, 297, 321, 104, 237, 294, 110, 136, 229, 102, 271, 250, 294,
|
||||
158, 319, 379, 126, 282, 155, 288, 159, 215, 247, 207, 226, 244, 158, 371, 219,
|
||||
272, 228, 350, 153, 356, 279, 394, 202, 213, 214, 112, 248, 139, 245, 165, 256,
|
||||
370, 187, 208, 231, 341, 312};
|
||||
|
||||
for (int i = 0; i < times.length; i++)
|
||||
{
|
||||
Timestamp timestamp = reference.minus(times[i]);
|
||||
habit.getRepetitions().add(new Repetition(timestamp, values[i]));
|
||||
}
|
||||
|
||||
return habit;
|
||||
}
|
||||
|
||||
public Habit createShortHabit()
|
||||
{
|
||||
Habit habit = modelFactory.buildHabit();
|
||||
habit.setName("Wake up early");
|
||||
habit.setDescription("Did you wake up before 6am?");
|
||||
habit.setFrequency(new Frequency(2, 3));
|
||||
saveIfSQLite(habit);
|
||||
|
||||
Timestamp timestamp = DateUtils.getToday();
|
||||
for (boolean c : NON_DAILY_HABIT_CHECKS)
|
||||
{
|
||||
if (c) habit.getRepetitions().toggle(timestamp);
|
||||
timestamp = timestamp.minus(1);
|
||||
}
|
||||
|
||||
return habit;
|
||||
}
|
||||
|
||||
private void saveIfSQLite(Habit habit)
|
||||
{
|
||||
if (!(habit.getRepetitions() instanceof SQLiteRepetitionList)) return;
|
||||
habitList.add(habit);
|
||||
}
|
||||
}
|
||||
@@ -1,210 +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.core.ui;
|
||||
|
||||
import android.support.annotation.*;
|
||||
|
||||
import org.isoron.uhabits.core.*;
|
||||
import org.isoron.uhabits.core.commands.*;
|
||||
import org.isoron.uhabits.core.models.*;
|
||||
import org.isoron.uhabits.core.preferences.*;
|
||||
import org.isoron.uhabits.core.tasks.*;
|
||||
|
||||
import java.util.*;
|
||||
|
||||
import javax.inject.*;
|
||||
|
||||
|
||||
@AppScope
|
||||
public class NotificationTray
|
||||
implements CommandRunner.Listener, Preferences.Listener
|
||||
{
|
||||
public static final String REMINDERS_CHANNEL_ID = "REMINDERS";
|
||||
|
||||
@NonNull
|
||||
private final TaskRunner taskRunner;
|
||||
|
||||
@NonNull
|
||||
private final CommandRunner commandRunner;
|
||||
|
||||
@NonNull
|
||||
private final Preferences preferences;
|
||||
|
||||
private SystemTray systemTray;
|
||||
|
||||
@NonNull
|
||||
private final HashMap<Habit, NotificationData> active;
|
||||
|
||||
@Inject
|
||||
public NotificationTray(@NonNull TaskRunner taskRunner,
|
||||
@NonNull CommandRunner commandRunner,
|
||||
@NonNull Preferences preferences,
|
||||
@NonNull SystemTray systemTray)
|
||||
{
|
||||
this.taskRunner = taskRunner;
|
||||
this.commandRunner = commandRunner;
|
||||
this.preferences = preferences;
|
||||
this.systemTray = systemTray;
|
||||
this.active = new HashMap<>();
|
||||
}
|
||||
|
||||
public void cancel(@NonNull Habit habit)
|
||||
{
|
||||
int notificationId = getNotificationId(habit);
|
||||
systemTray.removeNotification(notificationId);
|
||||
active.remove(habit);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCommandExecuted(@NonNull Command command,
|
||||
@Nullable Long refreshKey)
|
||||
{
|
||||
if (command instanceof ToggleRepetitionCommand)
|
||||
{
|
||||
ToggleRepetitionCommand toggleCmd =
|
||||
(ToggleRepetitionCommand) command;
|
||||
|
||||
Habit habit = toggleCmd.getHabit();
|
||||
taskRunner.execute(() ->
|
||||
{
|
||||
if (habit.getCheckmarks().getTodayValue() !=
|
||||
Checkmark.UNCHECKED) cancel(habit);
|
||||
});
|
||||
}
|
||||
|
||||
if (command instanceof DeleteHabitsCommand)
|
||||
{
|
||||
DeleteHabitsCommand deleteCommand = (DeleteHabitsCommand) command;
|
||||
List<Habit> deleted = deleteCommand.getSelected();
|
||||
for (Habit habit : deleted)
|
||||
cancel(habit);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onNotificationsChanged()
|
||||
{
|
||||
reshowAll();
|
||||
}
|
||||
|
||||
public void show(@NonNull Habit habit, Timestamp timestamp, long reminderTime)
|
||||
{
|
||||
NotificationData data = new NotificationData(timestamp, reminderTime);
|
||||
active.put(habit, data);
|
||||
taskRunner.execute(new ShowNotificationTask(habit, data));
|
||||
}
|
||||
|
||||
public void startListening()
|
||||
{
|
||||
commandRunner.addListener(this);
|
||||
preferences.addListener(this);
|
||||
}
|
||||
|
||||
public void stopListening()
|
||||
{
|
||||
commandRunner.removeListener(this);
|
||||
preferences.removeListener(this);
|
||||
}
|
||||
|
||||
private int getNotificationId(Habit habit)
|
||||
{
|
||||
Long id = habit.getId();
|
||||
if (id == null) return 0;
|
||||
return (int) (id % Integer.MAX_VALUE);
|
||||
}
|
||||
|
||||
private void reshowAll()
|
||||
{
|
||||
for (Habit habit : active.keySet())
|
||||
{
|
||||
NotificationData data = active.get(habit);
|
||||
taskRunner.execute(new ShowNotificationTask(habit, data));
|
||||
}
|
||||
}
|
||||
|
||||
public interface SystemTray
|
||||
{
|
||||
void removeNotification(int notificationId);
|
||||
|
||||
void showNotification(Habit habit,
|
||||
int notificationId,
|
||||
Timestamp timestamp,
|
||||
long reminderTime);
|
||||
}
|
||||
|
||||
class NotificationData
|
||||
{
|
||||
public final Timestamp timestamp;
|
||||
|
||||
public final long reminderTime;
|
||||
|
||||
public NotificationData(Timestamp timestamp, long reminderTime)
|
||||
{
|
||||
this.timestamp = timestamp;
|
||||
this.reminderTime = reminderTime;
|
||||
}
|
||||
}
|
||||
|
||||
private class ShowNotificationTask implements Task
|
||||
{
|
||||
int todayValue;
|
||||
|
||||
private final Habit habit;
|
||||
|
||||
private final Timestamp timestamp;
|
||||
|
||||
private final long reminderTime;
|
||||
|
||||
public ShowNotificationTask(Habit habit, NotificationData data)
|
||||
{
|
||||
this.habit = habit;
|
||||
this.timestamp = data.timestamp;
|
||||
this.reminderTime = data.reminderTime;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void doInBackground()
|
||||
{
|
||||
todayValue = habit.getCheckmarks().getTodayValue();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPostExecute()
|
||||
{
|
||||
if (todayValue != Checkmark.UNCHECKED) return;
|
||||
if (!shouldShowReminderToday()) return;
|
||||
if (!habit.hasReminder()) return;
|
||||
|
||||
systemTray.showNotification(habit, getNotificationId(habit), timestamp,
|
||||
reminderTime);
|
||||
}
|
||||
|
||||
private boolean shouldShowReminderToday()
|
||||
{
|
||||
if (!habit.hasReminder()) return false;
|
||||
Reminder reminder = habit.getReminder();
|
||||
|
||||
boolean reminderDays[] = reminder.getDays().toArray();
|
||||
int weekday = timestamp.getWeekday();
|
||||
|
||||
return reminderDays[weekday];
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,73 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2017 Á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.core.ui;
|
||||
|
||||
import android.support.annotation.*;
|
||||
|
||||
import org.isoron.uhabits.core.preferences.*;
|
||||
|
||||
public abstract class ThemeSwitcher
|
||||
{
|
||||
public static final int THEME_DARK = 1;
|
||||
|
||||
public static final int THEME_LIGHT = 0;
|
||||
|
||||
private final Preferences preferences;
|
||||
|
||||
public ThemeSwitcher(@NonNull Preferences preferences)
|
||||
{
|
||||
this.preferences = preferences;
|
||||
}
|
||||
|
||||
public void apply()
|
||||
{
|
||||
if (preferences.getTheme() == THEME_DARK)
|
||||
{
|
||||
if (preferences.isPureBlackEnabled()) applyPureBlackTheme();
|
||||
else applyDarkTheme();
|
||||
}
|
||||
else
|
||||
{
|
||||
applyLightTheme();
|
||||
}
|
||||
}
|
||||
|
||||
public abstract void applyDarkTheme();
|
||||
|
||||
public abstract void applyLightTheme();
|
||||
|
||||
public abstract void applyPureBlackTheme();
|
||||
|
||||
public boolean isNightMode()
|
||||
{
|
||||
return preferences.getTheme() == THEME_DARK;
|
||||
}
|
||||
|
||||
public void setTheme(int theme)
|
||||
{
|
||||
preferences.setTheme(theme);
|
||||
}
|
||||
|
||||
public void toggleNightMode()
|
||||
{
|
||||
if (isNightMode()) setTheme(THEME_LIGHT);
|
||||
else setTheme(THEME_DARK);
|
||||
}
|
||||
}
|
||||
@@ -1,25 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2017 Á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.core.ui.callbacks;
|
||||
|
||||
public interface OnColorPickedCallback
|
||||
{
|
||||
void onColorPicked(int color);
|
||||
}
|
||||
@@ -1,25 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2017 Á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.core.ui.callbacks;
|
||||
|
||||
public interface OnConfirmedCallback
|
||||
{
|
||||
void onConfirmed();
|
||||
}
|
||||
@@ -1,25 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2017 Á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.core.ui.callbacks;
|
||||
|
||||
public interface OnFinishedCallback
|
||||
{
|
||||
void onFinish();
|
||||
}
|
||||
@@ -1,93 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2017 Á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.core.ui.screens.about;
|
||||
|
||||
import android.support.annotation.*;
|
||||
|
||||
import org.isoron.uhabits.core.preferences.*;
|
||||
|
||||
import javax.inject.*;
|
||||
|
||||
public class AboutBehavior
|
||||
{
|
||||
private int developerCountdown = 5;
|
||||
|
||||
@NonNull
|
||||
private Preferences prefs;
|
||||
|
||||
@NonNull
|
||||
private Screen screen;
|
||||
|
||||
@Inject
|
||||
public AboutBehavior(@NonNull Preferences prefs, @NonNull Screen screen)
|
||||
{
|
||||
this.prefs = prefs;
|
||||
this.screen = screen;
|
||||
}
|
||||
|
||||
public void onPressDeveloperCountdown()
|
||||
{
|
||||
developerCountdown--;
|
||||
if (developerCountdown == 0)
|
||||
{
|
||||
prefs.setDeveloper(true);
|
||||
screen.showMessage(Message.YOU_ARE_NOW_A_DEVELOPER);
|
||||
}
|
||||
}
|
||||
|
||||
public void onRateApp()
|
||||
{
|
||||
screen.showRateAppWebsite();
|
||||
}
|
||||
|
||||
public void onSendFeedback()
|
||||
{
|
||||
screen.showSendFeedbackScreen();
|
||||
}
|
||||
|
||||
public void onTranslateApp()
|
||||
{
|
||||
screen.showTranslationWebsite();
|
||||
}
|
||||
|
||||
public void onViewSourceCode()
|
||||
{
|
||||
screen.showSourceCodeWebsite();
|
||||
}
|
||||
|
||||
public enum Message
|
||||
{
|
||||
YOU_ARE_NOW_A_DEVELOPER
|
||||
}
|
||||
|
||||
public interface Screen
|
||||
{
|
||||
void showMessage(Message message);
|
||||
|
||||
void showRateAppWebsite();
|
||||
|
||||
void showSendFeedbackScreen();
|
||||
|
||||
void showSourceCodeWebsite();
|
||||
|
||||
void showTranslationWebsite();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,415 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2017 Á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.core.ui.screens.habits.list;
|
||||
|
||||
import android.support.annotation.*;
|
||||
|
||||
import org.isoron.uhabits.core.*;
|
||||
import org.isoron.uhabits.core.commands.*;
|
||||
import org.isoron.uhabits.core.models.*;
|
||||
import org.isoron.uhabits.core.tasks.*;
|
||||
import org.isoron.uhabits.core.utils.*;
|
||||
|
||||
import java.util.*;
|
||||
|
||||
import javax.inject.*;
|
||||
|
||||
/**
|
||||
* A HabitCardListCache fetches and keeps a cache of all the data necessary to
|
||||
* render a HabitCardListView.
|
||||
* <p>
|
||||
* This is needed since performing database lookups during scrolling can make
|
||||
* the ListView very slow. It also registers itself as an observer of the
|
||||
* models, in order to update itself automatically.
|
||||
* <p>
|
||||
* Note that this class is singleton-scoped, therefore it is shared among all
|
||||
* activities.
|
||||
*/
|
||||
@AppScope
|
||||
public class HabitCardListCache implements CommandRunner.Listener
|
||||
{
|
||||
private int checkmarkCount;
|
||||
|
||||
private Task currentFetchTask;
|
||||
|
||||
@NonNull
|
||||
private Listener listener;
|
||||
|
||||
@NonNull
|
||||
private CacheData data;
|
||||
|
||||
@NonNull
|
||||
private HabitList allHabits;
|
||||
|
||||
@NonNull
|
||||
private HabitList filteredHabits;
|
||||
|
||||
private final TaskRunner taskRunner;
|
||||
|
||||
private final CommandRunner commandRunner;
|
||||
|
||||
@Inject
|
||||
public HabitCardListCache(@NonNull HabitList allHabits,
|
||||
@NonNull CommandRunner commandRunner,
|
||||
@NonNull TaskRunner taskRunner)
|
||||
{
|
||||
this.allHabits = allHabits;
|
||||
this.commandRunner = commandRunner;
|
||||
this.filteredHabits = allHabits;
|
||||
this.taskRunner = taskRunner;
|
||||
|
||||
this.listener = new Listener() {};
|
||||
data = new CacheData();
|
||||
}
|
||||
|
||||
public void cancelTasks()
|
||||
{
|
||||
if (currentFetchTask != null) currentFetchTask.cancel();
|
||||
}
|
||||
|
||||
public int[] getCheckmarks(long habitId)
|
||||
{
|
||||
return data.checkmarks.get(habitId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the habits that occupies a certain position on the list.
|
||||
*
|
||||
* @param position the position of the habit
|
||||
* @return the habit at given position or null if position is invalid
|
||||
*/
|
||||
@Nullable
|
||||
public synchronized Habit getHabitByPosition(int position)
|
||||
{
|
||||
if(position < 0 || position >= data.habits.size()) return null;
|
||||
return data.habits.get(position);
|
||||
}
|
||||
|
||||
public int getHabitCount()
|
||||
{
|
||||
return data.habits.size();
|
||||
}
|
||||
|
||||
public HabitList.Order getOrder()
|
||||
{
|
||||
return filteredHabits.getOrder();
|
||||
}
|
||||
|
||||
public double getScore(long habitId)
|
||||
{
|
||||
return data.scores.get(habitId);
|
||||
}
|
||||
|
||||
public void onAttached()
|
||||
{
|
||||
refreshAllHabits();
|
||||
commandRunner.addListener(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCommandExecuted(@NonNull Command command,
|
||||
@Nullable Long refreshKey)
|
||||
{
|
||||
if (refreshKey == null) refreshAllHabits();
|
||||
else refreshHabit(refreshKey);
|
||||
}
|
||||
|
||||
public void onDetached()
|
||||
{
|
||||
commandRunner.removeListener(this);
|
||||
}
|
||||
|
||||
public void refreshAllHabits()
|
||||
{
|
||||
if (currentFetchTask != null) currentFetchTask.cancel();
|
||||
currentFetchTask = new RefreshTask();
|
||||
taskRunner.execute(currentFetchTask);
|
||||
}
|
||||
|
||||
public void refreshHabit(long id)
|
||||
{
|
||||
taskRunner.execute(new RefreshTask(id));
|
||||
}
|
||||
|
||||
public void remove(@NonNull Long id)
|
||||
{
|
||||
Habit h = data.id_to_habit.get(id);
|
||||
if (h == null) return;
|
||||
|
||||
int position = data.habits.indexOf(h);
|
||||
data.habits.remove(position);
|
||||
data.id_to_habit.remove(id);
|
||||
data.checkmarks.remove(id);
|
||||
data.scores.remove(id);
|
||||
|
||||
listener.onItemRemoved(position);
|
||||
}
|
||||
|
||||
public void reorder(int from, int to)
|
||||
{
|
||||
Habit fromHabit = data.habits.get(from);
|
||||
data.habits.remove(from);
|
||||
data.habits.add(to, fromHabit);
|
||||
listener.onItemMoved(from, to);
|
||||
}
|
||||
|
||||
public void setCheckmarkCount(int checkmarkCount)
|
||||
{
|
||||
this.checkmarkCount = checkmarkCount;
|
||||
}
|
||||
|
||||
public void setFilter(HabitMatcher matcher)
|
||||
{
|
||||
filteredHabits = allHabits.getFiltered(matcher);
|
||||
}
|
||||
|
||||
public void setListener(@NonNull Listener listener)
|
||||
{
|
||||
this.listener = listener;
|
||||
}
|
||||
|
||||
public void setOrder(HabitList.Order order)
|
||||
{
|
||||
allHabits.setOrder(order);
|
||||
filteredHabits.setOrder(order);
|
||||
refreshAllHabits();
|
||||
}
|
||||
|
||||
/**
|
||||
* Interface definition for a callback to be invoked when the data on the
|
||||
* cache has been modified.
|
||||
*/
|
||||
public interface Listener
|
||||
{
|
||||
default void onItemChanged(int position) {}
|
||||
|
||||
default void onItemInserted(int position) {}
|
||||
|
||||
default void onItemMoved(int oldPosition, int newPosition) {}
|
||||
|
||||
default void onItemRemoved(int position) {}
|
||||
|
||||
default void onRefreshFinished() {}
|
||||
}
|
||||
|
||||
private class CacheData
|
||||
{
|
||||
@NonNull
|
||||
public HashMap<Long, Habit> id_to_habit;
|
||||
|
||||
@NonNull
|
||||
public List<Habit> habits;
|
||||
|
||||
@NonNull
|
||||
public HashMap<Long, int[]> checkmarks;
|
||||
|
||||
@NonNull
|
||||
public HashMap<Long, Double> scores;
|
||||
|
||||
/**
|
||||
* Creates a new CacheData without any content.
|
||||
*/
|
||||
public CacheData()
|
||||
{
|
||||
id_to_habit = new HashMap<>();
|
||||
habits = new LinkedList<>();
|
||||
checkmarks = new HashMap<>();
|
||||
scores = new HashMap<>();
|
||||
}
|
||||
|
||||
public void copyCheckmarksFrom(@NonNull CacheData oldData)
|
||||
{
|
||||
int[] empty = new int[checkmarkCount];
|
||||
|
||||
for (Long id : id_to_habit.keySet())
|
||||
{
|
||||
if (oldData.checkmarks.containsKey(id))
|
||||
checkmarks.put(id, oldData.checkmarks.get(id));
|
||||
else checkmarks.put(id, empty);
|
||||
}
|
||||
}
|
||||
|
||||
public void copyScoresFrom(@NonNull CacheData oldData)
|
||||
{
|
||||
for (Long id : id_to_habit.keySet())
|
||||
{
|
||||
if (oldData.scores.containsKey(id))
|
||||
scores.put(id, oldData.scores.get(id));
|
||||
else scores.put(id, 0.0);
|
||||
}
|
||||
}
|
||||
|
||||
public void fetchHabits()
|
||||
{
|
||||
for (Habit h : filteredHabits)
|
||||
{
|
||||
habits.add(h);
|
||||
id_to_habit.put(h.getId(), h);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private class RefreshTask implements Task
|
||||
{
|
||||
@NonNull
|
||||
private CacheData newData;
|
||||
|
||||
@Nullable
|
||||
private Long targetId;
|
||||
|
||||
private boolean isCancelled;
|
||||
|
||||
private TaskRunner runner;
|
||||
|
||||
public RefreshTask()
|
||||
{
|
||||
newData = new CacheData();
|
||||
targetId = null;
|
||||
isCancelled = false;
|
||||
}
|
||||
|
||||
public RefreshTask(long targetId)
|
||||
{
|
||||
newData = new CacheData();
|
||||
this.targetId = targetId;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void cancel()
|
||||
{
|
||||
isCancelled = true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void doInBackground()
|
||||
{
|
||||
newData.fetchHabits();
|
||||
newData.copyScoresFrom(data);
|
||||
newData.copyCheckmarksFrom(data);
|
||||
|
||||
Timestamp dateTo = DateUtils.getToday();
|
||||
Timestamp dateFrom = dateTo.minus(checkmarkCount - 1);
|
||||
|
||||
runner.publishProgress(this, -1);
|
||||
|
||||
for (int position = 0; position < newData.habits.size(); position++)
|
||||
{
|
||||
if (isCancelled) return;
|
||||
|
||||
Habit habit = newData.habits.get(position);
|
||||
Long id = habit.getId();
|
||||
if (targetId != null && !targetId.equals(id)) continue;
|
||||
|
||||
newData.scores.put(id, habit.getScores().getTodayValue());
|
||||
newData.checkmarks.put(id, habit
|
||||
.getCheckmarks()
|
||||
.getValues(dateFrom, dateTo));
|
||||
|
||||
runner.publishProgress(this, position);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAttached(@NonNull TaskRunner runner)
|
||||
{
|
||||
this.runner = runner;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPostExecute()
|
||||
{
|
||||
currentFetchTask = null;
|
||||
listener.onRefreshFinished();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onProgressUpdate(int currentPosition)
|
||||
{
|
||||
if (currentPosition < 0) processRemovedHabits();
|
||||
else processPosition(currentPosition);
|
||||
}
|
||||
|
||||
private void performInsert(Habit habit, int position)
|
||||
{
|
||||
Long id = habit.getId();
|
||||
data.habits.add(position, habit);
|
||||
data.id_to_habit.put(id, habit);
|
||||
data.scores.put(id, newData.scores.get(id));
|
||||
data.checkmarks.put(id, newData.checkmarks.get(id));
|
||||
listener.onItemInserted(position);
|
||||
}
|
||||
|
||||
private void performMove(Habit habit, int fromPosition, int toPosition)
|
||||
{
|
||||
data.habits.remove(fromPosition);
|
||||
data.habits.add(toPosition, habit);
|
||||
listener.onItemMoved(fromPosition, toPosition);
|
||||
}
|
||||
|
||||
private void performUpdate(Long id, int position)
|
||||
{
|
||||
double oldScore = data.scores.get(id);
|
||||
int[] oldCheckmarks = data.checkmarks.get(id);
|
||||
|
||||
double newScore = newData.scores.get(id);
|
||||
int[] newCheckmarks = newData.checkmarks.get(id);
|
||||
|
||||
boolean unchanged = true;
|
||||
if (oldScore != newScore) unchanged = false;
|
||||
if (!Arrays.equals(oldCheckmarks, newCheckmarks)) unchanged = false;
|
||||
if (unchanged) return;
|
||||
|
||||
data.scores.put(id, newScore);
|
||||
data.checkmarks.put(id, newCheckmarks);
|
||||
listener.onItemChanged(position);
|
||||
}
|
||||
|
||||
private void processPosition(int currentPosition)
|
||||
{
|
||||
Habit habit = newData.habits.get(currentPosition);
|
||||
Long id = habit.getId();
|
||||
|
||||
int prevPosition = data.habits.indexOf(habit);
|
||||
|
||||
if (prevPosition < 0)
|
||||
{
|
||||
performInsert(habit, currentPosition);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (prevPosition != currentPosition)
|
||||
performMove(habit, prevPosition, currentPosition);
|
||||
|
||||
performUpdate(id, currentPosition);
|
||||
}
|
||||
}
|
||||
|
||||
private void processRemovedHabits()
|
||||
{
|
||||
Set<Long> before = data.id_to_habit.keySet();
|
||||
Set<Long> after = newData.id_to_habit.keySet();
|
||||
|
||||
Set<Long> removed = new TreeSet<>(before);
|
||||
removed.removeAll(after);
|
||||
|
||||
for (Long id : removed) remove(id);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,83 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2017 Á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.core.ui.screens.habits.list;
|
||||
|
||||
import android.support.annotation.*;
|
||||
|
||||
import com.google.auto.factory.*;
|
||||
|
||||
import org.isoron.uhabits.core.models.*;
|
||||
import org.isoron.uhabits.core.preferences.*;
|
||||
import org.isoron.uhabits.core.utils.*;
|
||||
|
||||
/**
|
||||
* Provides a list of hints to be shown at the application startup, and takes
|
||||
* care of deciding when a new hint should be shown.
|
||||
*/
|
||||
@AutoFactory
|
||||
public class HintList
|
||||
{
|
||||
private final Preferences prefs;
|
||||
|
||||
@NonNull
|
||||
private final String[] hints;
|
||||
|
||||
/**
|
||||
* Constructs a new list containing the provided hints.
|
||||
*
|
||||
* @param hints initial list of hints
|
||||
*/
|
||||
public HintList(@Provided @NonNull Preferences prefs,
|
||||
@NonNull String hints[])
|
||||
{
|
||||
this.prefs = prefs;
|
||||
this.hints = hints;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a new hint to be shown to the user.
|
||||
* <p>
|
||||
* The hint returned is marked as read on the list, and will not be returned
|
||||
* again. In case all hints have already been read, and there is nothing
|
||||
* left, returns null.
|
||||
*
|
||||
* @return the next hint to be shown, or null if none
|
||||
*/
|
||||
public String pop()
|
||||
{
|
||||
int next = prefs.getLastHintNumber() + 1;
|
||||
if (next >= hints.length) return null;
|
||||
|
||||
prefs.updateLastHint(next, DateUtils.getToday());
|
||||
return hints[next];
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether it is time to show a new hint or not.
|
||||
*
|
||||
* @return true if hint should be shown, false otherwise
|
||||
*/
|
||||
public boolean shouldShow()
|
||||
{
|
||||
Timestamp today = DateUtils.getToday();
|
||||
Timestamp lastHintTimestamp = prefs.getLastHintTimestamp();
|
||||
return (lastHintTimestamp.isOlderThan(today));
|
||||
}
|
||||
}
|
||||
@@ -1,198 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2017 Á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.core.ui.screens.habits.list;
|
||||
|
||||
import android.support.annotation.*;
|
||||
|
||||
import org.isoron.uhabits.core.commands.*;
|
||||
import org.isoron.uhabits.core.models.*;
|
||||
import org.isoron.uhabits.core.preferences.*;
|
||||
import org.isoron.uhabits.core.tasks.*;
|
||||
import org.isoron.uhabits.core.utils.*;
|
||||
|
||||
import java.io.*;
|
||||
import java.util.*;
|
||||
|
||||
import javax.inject.*;
|
||||
|
||||
public class ListHabitsBehavior
|
||||
{
|
||||
@NonNull
|
||||
private final HabitList habitList;
|
||||
|
||||
@NonNull
|
||||
private final DirFinder dirFinder;
|
||||
|
||||
@NonNull
|
||||
private final TaskRunner taskRunner;
|
||||
|
||||
@NonNull
|
||||
private final Screen screen;
|
||||
|
||||
@NonNull
|
||||
private final CommandRunner commandRunner;
|
||||
|
||||
@NonNull
|
||||
private final Preferences prefs;
|
||||
|
||||
@NonNull
|
||||
private final BugReporter bugReporter;
|
||||
|
||||
@Inject
|
||||
public ListHabitsBehavior(@NonNull HabitList habitList,
|
||||
@NonNull DirFinder dirFinder,
|
||||
@NonNull TaskRunner taskRunner,
|
||||
@NonNull Screen screen,
|
||||
@NonNull CommandRunner commandRunner,
|
||||
@NonNull Preferences prefs,
|
||||
@NonNull BugReporter bugReporter)
|
||||
{
|
||||
this.habitList = habitList;
|
||||
this.dirFinder = dirFinder;
|
||||
this.taskRunner = taskRunner;
|
||||
this.screen = screen;
|
||||
this.commandRunner = commandRunner;
|
||||
this.prefs = prefs;
|
||||
this.bugReporter = bugReporter;
|
||||
}
|
||||
|
||||
public void onClickHabit(@NonNull Habit h)
|
||||
{
|
||||
screen.showHabitScreen(h);
|
||||
}
|
||||
|
||||
public void onEdit(@NonNull Habit habit, Timestamp 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());
|
||||
});
|
||||
}
|
||||
|
||||
public void onExportCSV()
|
||||
{
|
||||
List<Habit> selected = new LinkedList<>();
|
||||
for (Habit h : habitList) selected.add(h);
|
||||
File outputDir = dirFinder.getCSVOutputDir();
|
||||
|
||||
taskRunner.execute(
|
||||
new ExportCSVTask(habitList, selected, outputDir, filename ->
|
||||
{
|
||||
if (filename != null) screen.showSendFileScreen(filename);
|
||||
else screen.showMessage(Message.COULD_NOT_EXPORT);
|
||||
}));
|
||||
}
|
||||
|
||||
public void onFirstRun()
|
||||
{
|
||||
prefs.setFirstRun(false);
|
||||
prefs.updateLastHint(-1, DateUtils.getToday());
|
||||
screen.showIntroScreen();
|
||||
}
|
||||
|
||||
public void onReorderHabit(@NonNull Habit from, @NonNull Habit to)
|
||||
{
|
||||
taskRunner.execute(() -> habitList.reorder(from, to));
|
||||
}
|
||||
|
||||
public void onRepairDB()
|
||||
{
|
||||
taskRunner.execute(() ->
|
||||
{
|
||||
habitList.repair();
|
||||
screen.showMessage(Message.DATABASE_REPAIRED);
|
||||
});
|
||||
}
|
||||
|
||||
public void onSendBugReport()
|
||||
{
|
||||
bugReporter.dumpBugReportToFile();
|
||||
|
||||
try
|
||||
{
|
||||
String log = bugReporter.getBugReport();
|
||||
screen.showSendBugReportToDeveloperScreen(log);
|
||||
}
|
||||
catch (IOException e)
|
||||
{
|
||||
e.printStackTrace();
|
||||
screen.showMessage(Message.COULD_NOT_GENERATE_BUG_REPORT);
|
||||
}
|
||||
}
|
||||
|
||||
public void onStartup()
|
||||
{
|
||||
prefs.incrementLaunchCount();
|
||||
if (prefs.isFirstRun()) onFirstRun();
|
||||
}
|
||||
|
||||
public void onToggle(@NonNull Habit habit, Timestamp timestamp)
|
||||
{
|
||||
commandRunner.execute(
|
||||
new ToggleRepetitionCommand(habitList, habit, timestamp),
|
||||
habit.getId());
|
||||
}
|
||||
|
||||
public enum Message
|
||||
{
|
||||
COULD_NOT_EXPORT, IMPORT_SUCCESSFUL, IMPORT_FAILED, DATABASE_REPAIRED,
|
||||
COULD_NOT_GENERATE_BUG_REPORT, FILE_NOT_RECOGNIZED
|
||||
}
|
||||
|
||||
public interface BugReporter
|
||||
{
|
||||
void dumpBugReportToFile();
|
||||
|
||||
String getBugReport() throws IOException;
|
||||
}
|
||||
|
||||
public interface DirFinder
|
||||
{
|
||||
File getCSVOutputDir();
|
||||
}
|
||||
|
||||
public interface NumberPickerCallback
|
||||
{
|
||||
void onNumberPicked(double newValue);
|
||||
}
|
||||
|
||||
public interface Screen
|
||||
{
|
||||
void showHabitScreen(@NonNull Habit h);
|
||||
|
||||
void showIntroScreen();
|
||||
|
||||
void showMessage(@NonNull Message m);
|
||||
|
||||
void showNumberPicker(double value,
|
||||
@NonNull String unit,
|
||||
@NonNull NumberPickerCallback callback);
|
||||
|
||||
void showSendBugReportToDeveloperScreen(String log);
|
||||
|
||||
void showSendFileScreen(@NonNull String filename);
|
||||
}
|
||||
}
|
||||
@@ -1,154 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2017 Á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.core.ui.screens.habits.list;
|
||||
|
||||
import android.support.annotation.*;
|
||||
|
||||
import org.isoron.uhabits.core.models.*;
|
||||
import org.isoron.uhabits.core.preferences.*;
|
||||
import org.isoron.uhabits.core.ui.*;
|
||||
|
||||
import javax.inject.*;
|
||||
|
||||
public class ListHabitsMenuBehavior
|
||||
{
|
||||
@NonNull
|
||||
private final Screen screen;
|
||||
|
||||
@NonNull
|
||||
private final Adapter adapter;
|
||||
|
||||
@NonNull
|
||||
private final Preferences preferences;
|
||||
|
||||
@NonNull
|
||||
private final ThemeSwitcher themeSwitcher;
|
||||
|
||||
private boolean showCompleted;
|
||||
|
||||
private boolean showArchived;
|
||||
|
||||
@Inject
|
||||
public ListHabitsMenuBehavior(@NonNull Screen screen,
|
||||
@NonNull Adapter adapter,
|
||||
@NonNull Preferences preferences,
|
||||
@NonNull ThemeSwitcher themeSwitcher)
|
||||
{
|
||||
this.screen = screen;
|
||||
this.adapter = adapter;
|
||||
this.preferences = preferences;
|
||||
this.themeSwitcher = themeSwitcher;
|
||||
|
||||
showCompleted = preferences.getShowCompleted();
|
||||
showArchived = preferences.getShowArchived();
|
||||
updateAdapterFilter();
|
||||
}
|
||||
|
||||
public void onCreateHabit()
|
||||
{
|
||||
screen.showCreateHabitScreen();
|
||||
}
|
||||
|
||||
public void onViewFAQ()
|
||||
{
|
||||
screen.showFAQScreen();
|
||||
}
|
||||
|
||||
public void onViewAbout()
|
||||
{
|
||||
screen.showAboutScreen();
|
||||
}
|
||||
|
||||
public void onViewSettings()
|
||||
{
|
||||
screen.showSettingsScreen();
|
||||
}
|
||||
|
||||
public void onToggleShowArchived()
|
||||
{
|
||||
showArchived = !showArchived;
|
||||
preferences.setShowArchived(showArchived);
|
||||
updateAdapterFilter();
|
||||
}
|
||||
|
||||
public void onToggleShowCompleted()
|
||||
{
|
||||
showCompleted = !showCompleted;
|
||||
preferences.setShowCompleted(showCompleted);
|
||||
updateAdapterFilter();
|
||||
}
|
||||
|
||||
public void onSortByColor()
|
||||
{
|
||||
adapter.setOrder(HabitList.Order.BY_COLOR);
|
||||
}
|
||||
|
||||
public void onSortByManually()
|
||||
{
|
||||
adapter.setOrder(HabitList.Order.BY_POSITION);
|
||||
}
|
||||
|
||||
public void onSortByScore()
|
||||
{
|
||||
adapter.setOrder(HabitList.Order.BY_SCORE);
|
||||
}
|
||||
|
||||
public void onSortByName()
|
||||
{
|
||||
adapter.setOrder(HabitList.Order.BY_NAME);
|
||||
}
|
||||
|
||||
public void onToggleNightMode()
|
||||
{
|
||||
themeSwitcher.toggleNightMode();
|
||||
screen.applyTheme();
|
||||
}
|
||||
|
||||
private void updateAdapterFilter()
|
||||
{
|
||||
adapter.setFilter(new HabitMatcherBuilder()
|
||||
.setArchivedAllowed(showArchived)
|
||||
.setCompletedAllowed(showCompleted)
|
||||
.build());
|
||||
adapter.refresh();
|
||||
}
|
||||
|
||||
public interface Adapter
|
||||
{
|
||||
void refresh();
|
||||
|
||||
void setFilter(HabitMatcher build);
|
||||
|
||||
void setOrder(HabitList.Order order);
|
||||
}
|
||||
|
||||
public interface Screen
|
||||
{
|
||||
void applyTheme();
|
||||
|
||||
void showAboutScreen();
|
||||
|
||||
void showCreateHabitScreen();
|
||||
|
||||
void showFAQScreen();
|
||||
|
||||
void showSettingsScreen();
|
||||
}
|
||||
}
|
||||
@@ -1,143 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2017 Á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.core.ui.screens.habits.list;
|
||||
|
||||
import android.support.annotation.*;
|
||||
|
||||
import org.isoron.uhabits.core.commands.*;
|
||||
import org.isoron.uhabits.core.models.*;
|
||||
import org.isoron.uhabits.core.ui.callbacks.*;
|
||||
|
||||
import java.util.*;
|
||||
|
||||
import javax.inject.*;
|
||||
|
||||
public class ListHabitsSelectionMenuBehavior
|
||||
{
|
||||
@NonNull
|
||||
private final Screen screen;
|
||||
|
||||
@NonNull
|
||||
CommandRunner commandRunner;
|
||||
|
||||
@NonNull
|
||||
private final Adapter adapter;
|
||||
|
||||
@NonNull
|
||||
private final HabitList habitList;
|
||||
|
||||
@Inject
|
||||
public ListHabitsSelectionMenuBehavior(@NonNull HabitList habitList,
|
||||
@NonNull Screen screen,
|
||||
@NonNull Adapter adapter,
|
||||
@NonNull CommandRunner commandRunner)
|
||||
{
|
||||
this.habitList = habitList;
|
||||
this.screen = screen;
|
||||
this.adapter = adapter;
|
||||
this.commandRunner = commandRunner;
|
||||
}
|
||||
|
||||
public boolean canArchive()
|
||||
{
|
||||
for (Habit h : adapter.getSelected())
|
||||
if (h.isArchived()) return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public boolean canEdit()
|
||||
{
|
||||
return (adapter.getSelected().size() == 1);
|
||||
}
|
||||
|
||||
public boolean canUnarchive()
|
||||
{
|
||||
for (Habit h : adapter.getSelected())
|
||||
if (!h.isArchived()) return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public void onArchiveHabits()
|
||||
{
|
||||
commandRunner.execute(
|
||||
new ArchiveHabitsCommand(habitList, adapter.getSelected()), null);
|
||||
adapter.clearSelection();
|
||||
}
|
||||
|
||||
public void onChangeColor()
|
||||
{
|
||||
List<Habit> selected = adapter.getSelected();
|
||||
Habit first = selected.get(0);
|
||||
|
||||
screen.showColorPicker(first.getColor(), selectedColor ->
|
||||
{
|
||||
commandRunner.execute(
|
||||
new ChangeHabitColorCommand(habitList, selected, selectedColor),
|
||||
null);
|
||||
adapter.clearSelection();
|
||||
});
|
||||
}
|
||||
|
||||
public void onDeleteHabits()
|
||||
{
|
||||
List<Habit> selected = adapter.getSelected();
|
||||
screen.showDeleteConfirmationScreen(() ->
|
||||
{
|
||||
adapter.performRemove(selected);
|
||||
commandRunner.execute(new DeleteHabitsCommand(habitList, selected),
|
||||
null);
|
||||
adapter.clearSelection();
|
||||
});
|
||||
}
|
||||
|
||||
public void onEditHabits()
|
||||
{
|
||||
screen.showEditHabitsScreen(adapter.getSelected());
|
||||
}
|
||||
|
||||
public void onUnarchiveHabits()
|
||||
{
|
||||
commandRunner.execute(
|
||||
new UnarchiveHabitsCommand(habitList, adapter.getSelected()), null);
|
||||
adapter.clearSelection();
|
||||
}
|
||||
|
||||
public interface Adapter
|
||||
{
|
||||
void clearSelection();
|
||||
|
||||
List<Habit> getSelected();
|
||||
|
||||
void performRemove(List<Habit> selected);
|
||||
}
|
||||
|
||||
public interface Screen
|
||||
{
|
||||
void showColorPicker(int defaultColor,
|
||||
@NonNull OnColorPickedCallback callback);
|
||||
|
||||
void showDeleteConfirmationScreen(
|
||||
@NonNull OnConfirmedCallback callback);
|
||||
|
||||
void showEditHabitsScreen(@NonNull List<Habit> selected);
|
||||
}
|
||||
}
|
||||
@@ -1,69 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2017 Á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.core.ui.screens.habits.show;
|
||||
|
||||
import android.support.annotation.*;
|
||||
|
||||
import org.isoron.uhabits.core.commands.*;
|
||||
import org.isoron.uhabits.core.models.*;
|
||||
|
||||
import javax.inject.*;
|
||||
|
||||
public class ShowHabitBehavior
|
||||
{
|
||||
private HabitList habitList;
|
||||
|
||||
@NonNull
|
||||
private final Habit habit;
|
||||
|
||||
@NonNull
|
||||
private final CommandRunner commandRunner;
|
||||
|
||||
@NonNull
|
||||
private Screen screen;
|
||||
|
||||
@Inject
|
||||
public ShowHabitBehavior(@NonNull HabitList habitList,
|
||||
@NonNull CommandRunner commandRunner,
|
||||
@NonNull Habit habit,
|
||||
@NonNull Screen screen)
|
||||
{
|
||||
this.habitList = habitList;
|
||||
this.habit = habit;
|
||||
this.commandRunner = commandRunner;
|
||||
this.screen = screen;
|
||||
}
|
||||
|
||||
public void onEditHistory()
|
||||
{
|
||||
screen.showEditHistoryScreen();
|
||||
}
|
||||
|
||||
public void onToggleCheckmark(Timestamp timestamp)
|
||||
{
|
||||
commandRunner.execute(
|
||||
new ToggleRepetitionCommand(habitList, habit, timestamp), null);
|
||||
}
|
||||
|
||||
public interface Screen
|
||||
{
|
||||
void showEditHistoryScreen();
|
||||
}
|
||||
}
|
||||
@@ -1,146 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2017 Á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.core.ui.screens.habits.show;
|
||||
|
||||
import android.support.annotation.*;
|
||||
|
||||
import org.isoron.uhabits.core.commands.*;
|
||||
import org.isoron.uhabits.core.models.*;
|
||||
import org.isoron.uhabits.core.tasks.*;
|
||||
import org.isoron.uhabits.core.ui.callbacks.*;
|
||||
import org.isoron.uhabits.core.utils.*;
|
||||
|
||||
import java.io.*;
|
||||
import java.util.*;
|
||||
|
||||
import javax.inject.*;
|
||||
|
||||
import static java.lang.Math.*;
|
||||
|
||||
|
||||
public class ShowHabitMenuBehavior
|
||||
{
|
||||
private HabitList habitList;
|
||||
|
||||
@NonNull
|
||||
private final Habit habit;
|
||||
|
||||
@NonNull
|
||||
private final TaskRunner taskRunner;
|
||||
|
||||
@NonNull
|
||||
private Screen screen;
|
||||
|
||||
@NonNull
|
||||
private System system;
|
||||
|
||||
@NonNull
|
||||
private CommandRunner commandRunner;
|
||||
|
||||
@Inject
|
||||
public ShowHabitMenuBehavior(@NonNull HabitList habitList,
|
||||
@NonNull Habit habit,
|
||||
@NonNull TaskRunner taskRunner,
|
||||
@NonNull Screen screen,
|
||||
@NonNull System system,
|
||||
@NonNull CommandRunner commandRunner)
|
||||
{
|
||||
this.habitList = habitList;
|
||||
this.habit = habit;
|
||||
this.taskRunner = taskRunner;
|
||||
this.screen = screen;
|
||||
this.system = system;
|
||||
this.commandRunner = commandRunner;
|
||||
}
|
||||
|
||||
public void onEditHabit()
|
||||
{
|
||||
screen.showEditHabitScreen(habit);
|
||||
}
|
||||
|
||||
public void onExportCSV()
|
||||
{
|
||||
List<Habit> selected = Collections.singletonList(habit);
|
||||
File outputDir = system.getCSVOutputDir();
|
||||
|
||||
taskRunner.execute(
|
||||
new ExportCSVTask(habitList, selected, outputDir, filename ->
|
||||
{
|
||||
if (filename != null) screen.showSendFileScreen(filename);
|
||||
else screen.showMessage(Message.COULD_NOT_EXPORT);
|
||||
}));
|
||||
}
|
||||
|
||||
public void onDeleteHabit()
|
||||
{
|
||||
List<Habit> selected = Collections.singletonList(habit);
|
||||
|
||||
screen.showDeleteConfirmationScreen(() -> {
|
||||
commandRunner.execute(new DeleteHabitsCommand(habitList, selected),
|
||||
null);
|
||||
screen.close();
|
||||
});
|
||||
}
|
||||
|
||||
public void onRandomize()
|
||||
{
|
||||
Random random = new Random();
|
||||
habit.getRepetitions().removeAll();
|
||||
double strength = 50;
|
||||
|
||||
for (int i = 0; i < 365 * 5; i++)
|
||||
{
|
||||
if (i % 7 == 0) strength = max(0, min(100, strength + 10 * random.nextGaussian()));
|
||||
if (random.nextInt(100) > strength) continue;
|
||||
|
||||
int value = 1;
|
||||
if (habit.isNumerical())
|
||||
value = (int) (1000 + 250 * random.nextGaussian() * strength / 100) * 1000;
|
||||
|
||||
habit.getRepetitions().add(new Repetition(DateUtils.getToday().minus(i), value));
|
||||
}
|
||||
|
||||
habit.invalidateNewerThan(Timestamp.ZERO);
|
||||
}
|
||||
|
||||
public enum Message
|
||||
{
|
||||
COULD_NOT_EXPORT, HABIT_DELETED
|
||||
}
|
||||
|
||||
public interface Screen
|
||||
{
|
||||
void showEditHabitScreen(@NonNull Habit habit);
|
||||
|
||||
void showMessage(Message m);
|
||||
|
||||
void showSendFileScreen(String filename);
|
||||
|
||||
void showDeleteConfirmationScreen(
|
||||
@NonNull OnConfirmedCallback callback);
|
||||
|
||||
void close();
|
||||
}
|
||||
|
||||
public interface System
|
||||
{
|
||||
File getCSVOutputDir();
|
||||
}
|
||||
}
|
||||
@@ -1,76 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2017 Á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.core.ui.widgets;
|
||||
|
||||
import android.support.annotation.*;
|
||||
|
||||
import org.isoron.uhabits.core.commands.*;
|
||||
import org.isoron.uhabits.core.models.*;
|
||||
import org.isoron.uhabits.core.ui.*;
|
||||
|
||||
import javax.inject.*;
|
||||
|
||||
public class WidgetBehavior
|
||||
{
|
||||
private HabitList habitList;
|
||||
|
||||
@NonNull
|
||||
private final CommandRunner commandRunner;
|
||||
|
||||
private NotificationTray notificationTray;
|
||||
|
||||
@Inject
|
||||
public WidgetBehavior(@NonNull HabitList habitList,
|
||||
@NonNull CommandRunner commandRunner,
|
||||
@NonNull NotificationTray notificationTray)
|
||||
{
|
||||
this.habitList = habitList;
|
||||
this.commandRunner = commandRunner;
|
||||
this.notificationTray = notificationTray;
|
||||
}
|
||||
|
||||
public void onAddRepetition(@NonNull Habit habit, Timestamp timestamp)
|
||||
{
|
||||
notificationTray.cancel(habit);
|
||||
Repetition rep = habit.getRepetitions().getByTimestamp(timestamp);
|
||||
if (rep != null) return;
|
||||
performToggle(habit, timestamp);
|
||||
}
|
||||
|
||||
public void onRemoveRepetition(@NonNull Habit habit, Timestamp timestamp)
|
||||
{
|
||||
notificationTray.cancel(habit);
|
||||
Repetition rep = habit.getRepetitions().getByTimestamp(timestamp);
|
||||
if (rep == null) return;
|
||||
performToggle(habit, timestamp);
|
||||
}
|
||||
|
||||
public void onToggleRepetition(@NonNull Habit habit, Timestamp timestamp)
|
||||
{
|
||||
performToggle(habit, timestamp);
|
||||
}
|
||||
|
||||
private void performToggle(@NonNull Habit habit, Timestamp timestamp)
|
||||
{
|
||||
commandRunner.execute(
|
||||
new ToggleRepetitionCommand(habitList, habit, timestamp),
|
||||
habit.getId());
|
||||
}
|
||||
}
|
||||
@@ -1,27 +0,0 @@
|
||||
package org.isoron.uhabits.core.utils;
|
||||
|
||||
public class ColorConstants
|
||||
{
|
||||
public static String CSV_PALETTE[] = {
|
||||
"#D32F2F", // 0 red
|
||||
"#E64A19", // 1 deep orange
|
||||
"#F57C00", // 2 orange
|
||||
"#FF8F00", // 3 amber
|
||||
"#F9A825", // 4 yellow
|
||||
"#AFB42B", // 5 lime
|
||||
"#7CB342", // 6 light green
|
||||
"#388E3C", // 7 green
|
||||
"#00897B", // 8 teal
|
||||
"#00ACC1", // 9 cyan
|
||||
"#039BE5", // 10 light blue
|
||||
"#1976D2", // 11 blue
|
||||
"#303F9F", // 12 indigo
|
||||
"#5E35B1", // 13 deep purple
|
||||
"#8E24AA", // 14 purple
|
||||
"#D81B60", // 15 pink
|
||||
"#5D4037", // 16 brown
|
||||
"#303030", // 17 dark grey
|
||||
"#757575", // 18 grey
|
||||
"#aaaaaa" // 19 light grey
|
||||
};
|
||||
}
|
||||
@@ -1,47 +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.core.utils;
|
||||
|
||||
import android.support.annotation.*;
|
||||
|
||||
import java.text.*;
|
||||
import java.util.*;
|
||||
|
||||
public class DateFormats
|
||||
{
|
||||
@NonNull
|
||||
public static SimpleDateFormat fromSkeleton(@NonNull String skeleton,
|
||||
@NonNull Locale locale)
|
||||
{
|
||||
SimpleDateFormat df = new SimpleDateFormat(skeleton, locale);
|
||||
df.setTimeZone(TimeZone.getTimeZone("UTC"));
|
||||
return df;
|
||||
}
|
||||
|
||||
public static SimpleDateFormat getBackupDateFormat()
|
||||
{
|
||||
return fromSkeleton("yyyy-MM-dd HHmmss", Locale.US);
|
||||
}
|
||||
|
||||
public static SimpleDateFormat getCSVDateFormat()
|
||||
{
|
||||
return fromSkeleton("yyyy-MM-dd", Locale.US);
|
||||
}
|
||||
}
|
||||
@@ -1,261 +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.core.utils;
|
||||
|
||||
import android.support.annotation.*;
|
||||
|
||||
import org.isoron.uhabits.core.models.*;
|
||||
|
||||
import java.util.*;
|
||||
|
||||
import static java.util.Calendar.*;
|
||||
|
||||
public abstract class DateUtils
|
||||
{
|
||||
|
||||
private static Long fixedLocalTime = null;
|
||||
|
||||
private static TimeZone fixedTimeZone = null;
|
||||
|
||||
private static Locale fixedLocale = null;
|
||||
|
||||
/**
|
||||
* Time of the day when the new day starts.
|
||||
*/
|
||||
public static final int NEW_DAY_OFFSET = 3;
|
||||
|
||||
/**
|
||||
* Number of milliseconds in one day.
|
||||
*/
|
||||
public static final long DAY_LENGTH = 24 * 60 * 60 * 1000;
|
||||
|
||||
/**
|
||||
* Number of milliseconds in one hour.
|
||||
*/
|
||||
public static final long HOUR_LENGTH = 60 * 60 * 1000;
|
||||
|
||||
public static long applyTimezone(long localTimestamp)
|
||||
{
|
||||
TimeZone tz = getTimezone();
|
||||
return localTimestamp - tz.getOffset(localTimestamp - tz.getOffset(localTimestamp));
|
||||
}
|
||||
|
||||
public static String formatHeaderDate(GregorianCalendar day)
|
||||
{
|
||||
Locale locale = getLocale();
|
||||
String dayOfMonth = Integer.toString(day.get(DAY_OF_MONTH));
|
||||
String dayOfWeek = day.getDisplayName(DAY_OF_WEEK, SHORT, locale);
|
||||
return dayOfWeek + "\n" + dayOfMonth;
|
||||
}
|
||||
|
||||
private static GregorianCalendar getCalendar(long timestamp)
|
||||
{
|
||||
GregorianCalendar day =
|
||||
new GregorianCalendar(TimeZone.getTimeZone("GMT"), getLocale());
|
||||
day.setTimeInMillis(timestamp);
|
||||
return day;
|
||||
}
|
||||
|
||||
private static String[] getDayNames(int format)
|
||||
{
|
||||
String[] wdays = new String[7];
|
||||
|
||||
Calendar day = new GregorianCalendar();
|
||||
day.set(DAY_OF_WEEK, Calendar.SATURDAY);
|
||||
|
||||
for (int i = 0; i < wdays.length; i++)
|
||||
{
|
||||
wdays[i] =
|
||||
day.getDisplayName(DAY_OF_WEEK, format, getLocale());
|
||||
day.add(DAY_OF_MONTH, 1);
|
||||
}
|
||||
|
||||
return wdays;
|
||||
}
|
||||
|
||||
public static long getLocalTime()
|
||||
{
|
||||
if (fixedLocalTime != null) return fixedLocalTime;
|
||||
|
||||
TimeZone tz = getTimezone();
|
||||
long now = new Date().getTime();
|
||||
return now + tz.getOffset(now);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array with weekday names starting according to locale settings,
|
||||
* e.g. [Mo,Di,Mi,Do,Fr,Sa,So] in Germany
|
||||
*/
|
||||
public static String[] getLocaleDayNames(int format)
|
||||
{
|
||||
String[] days = new String[7];
|
||||
|
||||
Calendar calendar = new GregorianCalendar();
|
||||
calendar.set(DAY_OF_WEEK, calendar.getFirstDayOfWeek());
|
||||
for (int i = 0; i < days.length; i++)
|
||||
{
|
||||
days[i] = calendar.getDisplayName(DAY_OF_WEEK, format,
|
||||
getLocale());
|
||||
calendar.add(DAY_OF_MONTH, 1);
|
||||
}
|
||||
|
||||
return days;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array with week days numbers starting according to locale
|
||||
* settings, e.g. [2,3,4,5,6,7,1] in Europe
|
||||
*/
|
||||
public static Integer[] getLocaleWeekdayList()
|
||||
{
|
||||
Integer[] dayNumbers = new Integer[7];
|
||||
Calendar calendar = new GregorianCalendar();
|
||||
calendar.set(DAY_OF_WEEK, calendar.getFirstDayOfWeek());
|
||||
for (int i = 0; i < dayNumbers.length; i++)
|
||||
{
|
||||
dayNumbers[i] = calendar.get(DAY_OF_WEEK);
|
||||
calendar.add(DAY_OF_MONTH, 1);
|
||||
}
|
||||
return dayNumbers;
|
||||
}
|
||||
|
||||
public static String[] getLongDayNames()
|
||||
{
|
||||
return getDayNames(GregorianCalendar.LONG);
|
||||
}
|
||||
|
||||
public static String[] getShortDayNames()
|
||||
{
|
||||
return getDayNames(SHORT);
|
||||
}
|
||||
|
||||
@NonNull
|
||||
public static Timestamp getToday()
|
||||
{
|
||||
return new Timestamp(getStartOfToday());
|
||||
}
|
||||
|
||||
public static long getStartOfDay(long timestamp)
|
||||
{
|
||||
return (timestamp / DAY_LENGTH) * DAY_LENGTH;
|
||||
}
|
||||
|
||||
public static long getStartOfToday()
|
||||
{
|
||||
return getStartOfDay(getLocalTime() - NEW_DAY_OFFSET * HOUR_LENGTH);
|
||||
}
|
||||
|
||||
public static long millisecondsUntilTomorrow()
|
||||
{
|
||||
return getStartOfToday() + DAY_LENGTH -
|
||||
(getLocalTime() - NEW_DAY_OFFSET * HOUR_LENGTH);
|
||||
}
|
||||
|
||||
public static GregorianCalendar getStartOfTodayCalendar()
|
||||
{
|
||||
return getCalendar(getStartOfToday());
|
||||
}
|
||||
|
||||
private static TimeZone getTimezone()
|
||||
{
|
||||
if(fixedTimeZone != null) return fixedTimeZone;
|
||||
return TimeZone.getDefault();
|
||||
}
|
||||
|
||||
public static void setFixedTimeZone(TimeZone tz)
|
||||
{
|
||||
fixedTimeZone = tz;
|
||||
}
|
||||
|
||||
public static long removeTimezone(long timestamp)
|
||||
{
|
||||
TimeZone tz = getTimezone();
|
||||
return timestamp + tz.getOffset(timestamp);
|
||||
}
|
||||
|
||||
public static void setFixedLocalTime(Long timestamp)
|
||||
{
|
||||
fixedLocalTime = timestamp;
|
||||
}
|
||||
|
||||
public static void setFixedLocale(Locale locale)
|
||||
{
|
||||
fixedLocale = locale;
|
||||
}
|
||||
|
||||
private static Locale getLocale()
|
||||
{
|
||||
if(fixedLocale != null) return fixedLocale;
|
||||
return Locale.getDefault();
|
||||
}
|
||||
|
||||
public static Long truncate(TruncateField field, long timestamp)
|
||||
{
|
||||
GregorianCalendar cal = DateUtils.getCalendar(timestamp);
|
||||
|
||||
switch (field)
|
||||
{
|
||||
case MONTH:
|
||||
cal.set(DAY_OF_MONTH, 1);
|
||||
return cal.getTimeInMillis();
|
||||
|
||||
case WEEK_NUMBER:
|
||||
int firstWeekday = cal.getFirstDayOfWeek();
|
||||
int weekday = cal.get(DAY_OF_WEEK);
|
||||
int delta = weekday - firstWeekday;
|
||||
if (delta < 0) delta += 7;
|
||||
cal.add(Calendar.DAY_OF_YEAR, -delta);
|
||||
return cal.getTimeInMillis();
|
||||
|
||||
case QUARTER:
|
||||
int quarter = cal.get(Calendar.MONTH) / 3;
|
||||
cal.set(DAY_OF_MONTH, 1);
|
||||
cal.set(Calendar.MONTH, quarter * 3);
|
||||
return cal.getTimeInMillis();
|
||||
|
||||
case YEAR:
|
||||
cal.set(Calendar.MONTH, Calendar.JANUARY);
|
||||
cal.set(DAY_OF_MONTH, 1);
|
||||
return cal.getTimeInMillis();
|
||||
|
||||
default:
|
||||
throw new IllegalArgumentException();
|
||||
}
|
||||
}
|
||||
|
||||
public static long getUpcomingTimeInMillis(int hour, int minute)
|
||||
{
|
||||
Calendar calendar = DateUtils.getStartOfTodayCalendar();
|
||||
calendar.set(Calendar.HOUR_OF_DAY, hour);
|
||||
calendar.set(Calendar.MINUTE, minute);
|
||||
calendar.set(Calendar.SECOND, 0);
|
||||
Long time = calendar.getTimeInMillis();
|
||||
|
||||
if (DateUtils.getLocalTime() > time)
|
||||
time += DateUtils.DAY_LENGTH;
|
||||
|
||||
return applyTimezone(time);
|
||||
}
|
||||
|
||||
public enum TruncateField
|
||||
{
|
||||
MONTH, WEEK_NUMBER, YEAR, QUARTER
|
||||
}
|
||||
}
|
||||
@@ -1,77 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2017 Á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.core.utils;
|
||||
|
||||
import org.isoron.uhabits.core.*;
|
||||
|
||||
import java.util.*;
|
||||
import java.util.concurrent.*;
|
||||
|
||||
import javax.inject.*;
|
||||
|
||||
/**
|
||||
* A class that emits events when a new day starts.
|
||||
*/
|
||||
@AppScope
|
||||
public class MidnightTimer
|
||||
{
|
||||
private final List<MidnightListener> listeners;
|
||||
|
||||
private ScheduledExecutorService executor;
|
||||
|
||||
@Inject
|
||||
public MidnightTimer()
|
||||
{
|
||||
this.listeners = new LinkedList<>();
|
||||
}
|
||||
|
||||
public synchronized void addListener(MidnightListener listener)
|
||||
{
|
||||
this.listeners.add(listener);
|
||||
}
|
||||
|
||||
public synchronized void onPause()
|
||||
{
|
||||
executor.shutdownNow();
|
||||
}
|
||||
|
||||
public synchronized void onResume()
|
||||
{
|
||||
executor = Executors.newSingleThreadScheduledExecutor();
|
||||
executor.scheduleAtFixedRate(() -> notifyListeners(),
|
||||
DateUtils.millisecondsUntilTomorrow() + 1000,
|
||||
DateUtils.DAY_LENGTH, TimeUnit.MILLISECONDS);
|
||||
}
|
||||
|
||||
public synchronized void removeListener(MidnightListener listener)
|
||||
{
|
||||
this.listeners.remove(listener);
|
||||
}
|
||||
|
||||
private synchronized void notifyListeners()
|
||||
{
|
||||
for (MidnightListener l : listeners) l.atMidnight();
|
||||
}
|
||||
|
||||
public interface MidnightListener
|
||||
{
|
||||
void atMidnight();
|
||||
}
|
||||
}
|
||||
@@ -1,67 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2017 Á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.core.utils;
|
||||
|
||||
import org.apache.commons.lang3.builder.*;
|
||||
|
||||
import java.math.*;
|
||||
import java.util.*;
|
||||
|
||||
public class StringUtils
|
||||
{
|
||||
private static StandardToStringStyle toStringStyle = null;
|
||||
|
||||
public static String getRandomId()
|
||||
{
|
||||
return new BigInteger(260, new Random()).toString(32).substring(0, 32);
|
||||
}
|
||||
|
||||
public static ToStringStyle defaultToStringStyle()
|
||||
{
|
||||
if (toStringStyle == null)
|
||||
{
|
||||
toStringStyle = new StandardToStringStyle();
|
||||
toStringStyle.setFieldSeparator(", ");
|
||||
toStringStyle.setUseClassName(false);
|
||||
toStringStyle.setUseIdentityHashCode(false);
|
||||
toStringStyle.setContentStart("{");
|
||||
toStringStyle.setContentEnd("}");
|
||||
toStringStyle.setFieldNameValueSeparator(": ");
|
||||
toStringStyle.setArrayStart("[");
|
||||
toStringStyle.setArrayEnd("]");
|
||||
}
|
||||
|
||||
return toStringStyle;
|
||||
}
|
||||
|
||||
public static String joinLongs(long values[])
|
||||
{
|
||||
return org.apache.commons.lang3.StringUtils.join(values, ',');
|
||||
}
|
||||
|
||||
public static long[] splitLongs(String str)
|
||||
{
|
||||
String parts[] = org.apache.commons.lang3.StringUtils.split(str, ',');
|
||||
|
||||
long numbers[] = new long[parts.length];
|
||||
for (int i = 0; i < parts.length; i++) numbers[i] = Long.valueOf(parts[i]);
|
||||
return numbers;
|
||||
}
|
||||
}
|
||||
@@ -1,41 +0,0 @@
|
||||
create table Habits (
|
||||
id integer primary key autoincrement,
|
||||
archived integer,
|
||||
color integer,
|
||||
description text,
|
||||
freq_den integer,
|
||||
freq_num integer,
|
||||
highlight integer,
|
||||
name text,
|
||||
position integer,
|
||||
reminder_hour integer,
|
||||
reminder_min integer
|
||||
);
|
||||
|
||||
create table Checkmarks (
|
||||
id integer primary key autoincrement,
|
||||
habit integer references habits(id),
|
||||
timestamp integer,
|
||||
value integer
|
||||
);
|
||||
|
||||
create table Repetitions (
|
||||
id integer primary key autoincrement,
|
||||
habit integer references habits(id),
|
||||
timestamp integer
|
||||
);
|
||||
|
||||
create table Streak (
|
||||
id integer primary key autoincrement,
|
||||
end integer,
|
||||
habit integer references habits(id),
|
||||
length integer,
|
||||
start integer
|
||||
);
|
||||
|
||||
create table Score (
|
||||
id integer primary key autoincrement,
|
||||
habit integer references habits(id),
|
||||
score integer,
|
||||
timestamp integer
|
||||
);
|
||||
@@ -1,3 +0,0 @@
|
||||
delete from Score;
|
||||
delete from Streak;
|
||||
delete from Checkmarks;
|
||||
@@ -1 +0,0 @@
|
||||
alter table Habits add column reminder_days integer not null default 127;
|
||||
@@ -1,3 +0,0 @@
|
||||
delete from Score;
|
||||
delete from Streak;
|
||||
delete from Checkmarks;
|
||||
@@ -1,4 +0,0 @@
|
||||
create index idx_score_habit_timestamp on Score(habit, timestamp);
|
||||
create index idx_checkmark_habit_timestamp on Checkmarks(habit, timestamp);
|
||||
create index idx_repetitions_habit_timestamp on Repetitions(habit, timestamp);
|
||||
create index idx_streak_habit_end on Streak(habit, end);
|
||||
@@ -1,14 +0,0 @@
|
||||
update habits set color=0 where color=-2937041;
|
||||
update habits set color=1 where color=-1684967;
|
||||
update habits set color=2 where color=-415707;
|
||||
update habits set color=3 where color=-5262293;
|
||||
update habits set color=4 where color=-13070788;
|
||||
update habits set color=5 where color=-16742021;
|
||||
update habits set color=6 where color=-16732991;
|
||||
update habits set color=7 where color=-16540699;
|
||||
update habits set color=8 where color=-10603087;
|
||||
update habits set color=9 where color=-7461718;
|
||||
update habits set color=10 where color=-2614432;
|
||||
update habits set color=11 where color=-13619152;
|
||||
update habits set color=12 where color=-5592406;
|
||||
update habits set color=0 where color<0 or color>12;
|
||||
@@ -1,3 +0,0 @@
|
||||
delete from Score;
|
||||
delete from Streak;
|
||||
delete from Checkmarks;
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user