diff --git a/Habitica/AndroidManifest.xml b/Habitica/AndroidManifest.xml
index 734f1b000..48965b7b1 100644
--- a/Habitica/AndroidManifest.xml
+++ b/Habitica/AndroidManifest.xml
@@ -17,6 +17,7 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/Habitica/res/values/strings.xml b/Habitica/res/values/strings.xml
index 6dff54431..bbea539fe 100644
--- a/Habitica/res/values/strings.xml
+++ b/Habitica/res/values/strings.xml
@@ -80,6 +80,7 @@
Positive ( + )
Negative ( - )
Checklist
+ Reminders
Actions
Attributes
diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/APIHelper.java b/Habitica/src/main/java/com/habitrpg/android/habitica/APIHelper.java
index 39270d77e..b0a2ea261 100644
--- a/Habitica/src/main/java/com/habitrpg/android/habitica/APIHelper.java
+++ b/Habitica/src/main/java/com/habitrpg/android/habitica/APIHelper.java
@@ -35,6 +35,7 @@ import com.magicmicky.habitrpgwrapper.lib.models.inventory.QuestContent;
import com.magicmicky.habitrpgwrapper.lib.models.responses.FeedResponse;
import com.magicmicky.habitrpgwrapper.lib.models.tasks.ChecklistItem;
import com.magicmicky.habitrpgwrapper.lib.models.tasks.ItemData;
+import com.magicmicky.habitrpgwrapper.lib.models.tasks.RemindersItem;
import com.magicmicky.habitrpgwrapper.lib.models.tasks.Task;
import com.magicmicky.habitrpgwrapper.lib.models.tasks.TaskList;
import com.magicmicky.habitrpgwrapper.lib.models.tasks.TaskTag;
@@ -55,6 +56,7 @@ import com.magicmicky.habitrpgwrapper.lib.utils.MountListDeserializer;
import com.magicmicky.habitrpgwrapper.lib.utils.PetListDeserializer;
import com.magicmicky.habitrpgwrapper.lib.utils.PurchasedDeserializer;
import com.magicmicky.habitrpgwrapper.lib.utils.QuestListDeserializer;
+import com.magicmicky.habitrpgwrapper.lib.utils.RemindersItemSerializer;
import com.magicmicky.habitrpgwrapper.lib.utils.SkillDeserializer;
import com.magicmicky.habitrpgwrapper.lib.utils.TaskListDeserializer;
import com.magicmicky.habitrpgwrapper.lib.utils.TaskSerializer;
@@ -217,6 +219,7 @@ public class APIHelper implements Action1 {
.registerTypeAdapter(boolean.class, new BooleanAsIntAdapter())
.registerTypeAdapter(skillListType, new SkillDeserializer())
.registerTypeAdapter(ChecklistItem.class, new ChecklistItemSerializer())
+ .registerTypeAdapter(RemindersItem.class, new RemindersItemSerializer())
.registerTypeAdapter(TaskList.class, new TaskListDeserializer())
.registerTypeAdapter(Purchases.class, new PurchasedDeserializer())
.registerTypeAdapter(customizationListType, new CustomizationDeserializer())
diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/HabitDatabase.java b/Habitica/src/main/java/com/habitrpg/android/habitica/HabitDatabase.java
index 0e7764bdd..5a688bde6 100644
--- a/Habitica/src/main/java/com/habitrpg/android/habitica/HabitDatabase.java
+++ b/Habitica/src/main/java/com/habitrpg/android/habitica/HabitDatabase.java
@@ -7,5 +7,5 @@ public class HabitDatabase {
public static final String NAME = "Habitica";
- public static final int VERSION = 20;
+ public static final int VERSION = 21;
}
diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/modules/ApiModule.java b/Habitica/src/main/java/com/habitrpg/android/habitica/modules/ApiModule.java
index d3c41dae3..642f27d31 100644
--- a/Habitica/src/main/java/com/habitrpg/android/habitica/modules/ApiModule.java
+++ b/Habitica/src/main/java/com/habitrpg/android/habitica/modules/ApiModule.java
@@ -27,6 +27,7 @@ import com.magicmicky.habitrpgwrapper.lib.models.inventory.QuestContent;
import com.magicmicky.habitrpgwrapper.lib.models.responses.FeedResponse;
import com.magicmicky.habitrpgwrapper.lib.models.tasks.ChecklistItem;
import com.magicmicky.habitrpgwrapper.lib.models.tasks.ItemData;
+import com.magicmicky.habitrpgwrapper.lib.models.tasks.RemindersItem;
import com.magicmicky.habitrpgwrapper.lib.models.tasks.Task;
import com.magicmicky.habitrpgwrapper.lib.models.tasks.TaskList;
import com.magicmicky.habitrpgwrapper.lib.models.tasks.TaskTag;
@@ -47,6 +48,7 @@ import com.magicmicky.habitrpgwrapper.lib.utils.MountListDeserializer;
import com.magicmicky.habitrpgwrapper.lib.utils.PetListDeserializer;
import com.magicmicky.habitrpgwrapper.lib.utils.PurchasedDeserializer;
import com.magicmicky.habitrpgwrapper.lib.utils.QuestListDeserializer;
+import com.magicmicky.habitrpgwrapper.lib.utils.RemindersItemSerializer;
import com.magicmicky.habitrpgwrapper.lib.utils.SkillDeserializer;
import com.magicmicky.habitrpgwrapper.lib.utils.TaskListDeserializer;
import com.magicmicky.habitrpgwrapper.lib.utils.TaskSerializer;
diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/receivers/TaskAlarmBootReceiver.java b/Habitica/src/main/java/com/habitrpg/android/habitica/receivers/TaskAlarmBootReceiver.java
new file mode 100644
index 000000000..78b7ce645
--- /dev/null
+++ b/Habitica/src/main/java/com/habitrpg/android/habitica/receivers/TaskAlarmBootReceiver.java
@@ -0,0 +1,22 @@
+package com.habitrpg.android.habitica.receivers;
+
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.os.Bundle;
+import android.util.Log;
+
+import com.habitrpg.android.habitica.ui.helpers.TaskAlarmManager;
+
+/**
+ * Created by keithholliday on 5/29/16.
+ */
+public class TaskAlarmBootReceiver extends BroadcastReceiver {
+
+ @Override
+ public void onReceive(Context arg0, Intent arg1) {
+ TaskAlarmManager taskAlarmManager = TaskAlarmManager.getInstance(arg0);
+ taskAlarmManager.scheduleAllSavedAlarms();
+ }
+
+}
diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/receivers/TaskReceiver.java b/Habitica/src/main/java/com/habitrpg/android/habitica/receivers/TaskReceiver.java
new file mode 100644
index 000000000..32844831f
--- /dev/null
+++ b/Habitica/src/main/java/com/habitrpg/android/habitica/receivers/TaskReceiver.java
@@ -0,0 +1,57 @@
+package com.habitrpg.android.habitica.receivers;
+
+import android.app.Notification;
+import android.app.NotificationManager;
+import android.app.PendingIntent;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.os.Bundle;
+import android.util.Log;
+
+import com.habitrpg.android.habitica.R;
+import com.habitrpg.android.habitica.ui.activities.MainActivity;
+import com.habitrpg.android.habitica.ui.helpers.TaskAlarmManager;
+
+/**
+ * Created by keithholliday on 5/29/16.
+ */
+public class TaskReceiver extends BroadcastReceiver {
+
+ private Context context;
+
+ private String taskId;
+ private String taskTitle;
+ private TaskAlarmManager taskAlarmManager;
+
+ @Override
+ public void onReceive(Context arg0, Intent arg1) {
+ context = arg0;
+ taskAlarmManager = TaskAlarmManager.getInstance(context);
+
+ Bundle extras = arg1.getExtras();
+
+ if (extras != null) {
+ taskTitle = extras.getString(TaskAlarmManager.TASK_NAME_INTENT_KEY);
+ taskId = extras.getString(TaskAlarmManager.TASK_ID_INTENT_KEY);
+ //This will set up the next reminders for dailies
+ taskAlarmManager.addAlarmForTaskId(taskId);
+ createNotification();
+ }
+ }
+
+ public void createNotification() {
+ Intent intent = new Intent(context, MainActivity.class);
+ PendingIntent pendingIntent = PendingIntent.getActivity(context, (int) System.currentTimeMillis(), intent, 0);
+
+ Notification notification = new Notification.Builder(context)
+ .setContentTitle(taskTitle)
+ .setContentText(taskTitle).setSmallIcon(R.drawable.ic_gryphon)
+ .setContentIntent(pendingIntent)
+ .build();
+ NotificationManager notificationManager = (NotificationManager) context.getSystemService(context.NOTIFICATION_SERVICE);
+ notification.flags |= Notification.FLAG_AUTO_CANCEL;
+
+ notificationManager.notify((int) System.currentTimeMillis(), notification);
+ }
+}
\ No newline at end of file
diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/activities/TaskFormActivity.java b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/activities/TaskFormActivity.java
index b03652070..40805f11f 100644
--- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/activities/TaskFormActivity.java
+++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/activities/TaskFormActivity.java
@@ -8,12 +8,15 @@ import com.habitrpg.android.habitica.events.TaskSaveEvent;
import com.habitrpg.android.habitica.events.commands.DeleteTaskCommand;
import com.habitrpg.android.habitica.ui.WrapContentRecyclerViewLayoutManager;
import com.habitrpg.android.habitica.ui.adapter.tasks.CheckListAdapter;
+import com.habitrpg.android.habitica.ui.adapter.tasks.RemindersAdapter;
import com.habitrpg.android.habitica.ui.helpers.MarkdownParser;
import com.habitrpg.android.habitica.ui.helpers.SimpleItemTouchHelperCallback;
+import com.habitrpg.android.habitica.ui.helpers.TaskAlarmManager;
import com.habitrpg.android.habitica.ui.helpers.ViewHelper;
import com.magicmicky.habitrpgwrapper.lib.models.Tag;
import com.magicmicky.habitrpgwrapper.lib.models.tasks.ChecklistItem;
import com.magicmicky.habitrpgwrapper.lib.models.tasks.Days;
+import com.magicmicky.habitrpgwrapper.lib.models.tasks.RemindersItem;
import com.magicmicky.habitrpgwrapper.lib.models.tasks.Task;
import com.magicmicky.habitrpgwrapper.lib.models.tasks.TaskTag;
import com.raizlabs.android.dbflow.runtime.transaction.BaseTransaction;
@@ -25,6 +28,7 @@ import com.raizlabs.android.dbflow.sql.language.Set;
import org.greenrobot.eventbus.EventBus;
import android.app.DatePickerDialog;
+import android.app.TimePickerDialog;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
@@ -37,7 +41,6 @@ import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.RecyclerView;
import android.support.v7.widget.helper.ItemTouchHelper;
import android.text.TextUtils;
-import android.util.ArraySet;
import android.view.KeyEvent;
import android.view.Menu;
import android.view.MenuItem;
@@ -55,6 +58,7 @@ import android.widget.NumberPicker;
import android.widget.Spinner;
import android.widget.TableRow;
import android.widget.TextView;
+import android.widget.TimePicker;
import java.text.DateFormat;
import java.text.DecimalFormat;
@@ -67,6 +71,7 @@ import java.util.HashSet;
import java.util.List;
import java.util.Locale;
import java.util.TreeSet;
+import java.util.UUID;
import butterknife.BindView;
import rx.Observable;
@@ -91,8 +96,11 @@ public class TaskFormActivity extends BaseActivity implements AdapterView.OnItem
private NumberPicker frequencyPicker;
private List tags;
private CheckListAdapter checklistAdapter;
+ private RemindersAdapter remindersAdapter;
private List tagCheckBoxList;
+ private TaskAlarmManager taskAlarmManager;
+
@BindView(R.id.task_value_edittext)
EditText taskValue;
@@ -159,6 +167,18 @@ public class TaskFormActivity extends BaseActivity implements AdapterView.OnItem
@BindView(R.id.add_checklist_button)
Button button;
+ @BindView(R.id.task_reminders_wrapper)
+ LinearLayout remindersWrapper;
+
+ @BindView(R.id.new_reminder_edittext)
+ EditText newRemindersEditText;
+
+ @BindView(R.id.reminders_recycler_view)
+ RecyclerView remindersRecyclerView;
+
+ @BindView(R.id.add_reminder_button)
+ Button addReminderButton;
+
@BindView(R.id.emoji_toggle_btn0)
ImageButton emojiToggle0;
@@ -215,6 +235,8 @@ public class TaskFormActivity extends BaseActivity implements AdapterView.OnItem
return;
}
+ taskAlarmManager = TaskAlarmManager.getInstance(this);
+
dueDateListener = new DateEditTextListener(dueDatePickerText);
startDateListener = new DateEditTextListener(startDatePickerText);
@@ -255,6 +277,7 @@ public class TaskFormActivity extends BaseActivity implements AdapterView.OnItem
taskWrapper.removeView(startDateLayout);
mainWrapper.removeView(checklistWrapper);
+ mainWrapper.removeView(remindersWrapper);
positiveCheckBox.setChecked(true);
negativeCheckBox.setChecked(true);
@@ -292,6 +315,7 @@ public class TaskFormActivity extends BaseActivity implements AdapterView.OnItem
} else {
mainWrapper.removeView(checklistWrapper);
+ mainWrapper.removeView(remindersWrapper);
difficultyWrapper.setVisibility(View.GONE);
attributeWrapper.setVisibility(View.GONE);
@@ -314,6 +338,7 @@ public class TaskFormActivity extends BaseActivity implements AdapterView.OnItem
if (taskType.equals("todo") || taskType.equals("daily")) {
createCheckListRecyclerView();
+ createRemindersRecyclerView();
}
// Emoji keyboard stuff
@@ -521,6 +546,65 @@ public class TaskFormActivity extends BaseActivity implements AdapterView.OnItem
});
}
+ private void createRemindersRecyclerView() {
+ List reminders = new ArrayList<>();
+ if (task != null && task.getReminders() != null) {
+ reminders = task.getReminders();
+ }
+ remindersAdapter = new RemindersAdapter(reminders);
+
+ LinearLayoutManager llm = new LinearLayoutManager(this);
+ llm.setOrientation(LinearLayoutManager.VERTICAL);
+
+ remindersRecyclerView.setLayoutManager(llm);
+ remindersRecyclerView.setAdapter(remindersAdapter);
+
+ remindersRecyclerView.setLayoutManager(new WrapContentRecyclerViewLayoutManager(this));
+
+ ItemTouchHelper.Callback callback = new SimpleItemTouchHelperCallback(remindersAdapter);
+ ItemTouchHelper mItemTouchHelper = new ItemTouchHelper(callback);
+ mItemTouchHelper.attachToRecyclerView(remindersRecyclerView);
+
+ newRemindersEditText.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ Calendar mcurrentTime = Calendar.getInstance();
+ int hour = mcurrentTime.get(Calendar.HOUR_OF_DAY);
+ int minute = mcurrentTime.get(Calendar.MINUTE);
+ TimePickerDialog mTimePicker;
+ mTimePicker = new TimePickerDialog(TaskFormActivity.this, new TimePickerDialog.OnTimeSetListener() {
+ @Override
+ public void onTimeSet(TimePicker timePicker, int selectedHour, int selectedMinute) {
+ newRemindersEditText.setText( selectedHour + ":" + selectedMinute);
+ }
+ }, hour, minute, true);
+ mTimePicker.setTitle("Select Time");
+ mTimePicker.show();
+ }
+ });
+
+ addReminderButton.setOnClickListener(v -> {
+ Date startDate = new Date(startDateListener.getCalendar().getTimeInMillis());
+
+ Date time = startDate;
+ String reminderTimeString = newRemindersEditText.getText().toString();
+ if (reminderTimeString.isEmpty()) return;
+ String[] reminderTimeSplit = reminderTimeString.split(":");
+ time.setHours(Integer.parseInt(reminderTimeSplit[0]));
+ time.setMinutes(Integer.parseInt(reminderTimeSplit[1]));
+ time.setSeconds(0);
+
+ RemindersItem item = new RemindersItem();
+ UUID randomUUID = UUID.randomUUID();
+ item.setId(randomUUID.toString());
+ item.setStartDate(startDate);
+ item.setTime(time);
+
+ remindersAdapter.addItem(item);
+ newRemindersEditText.setText("");
+ });
+ }
+
private void createTagsCheckBoxes() {
int position = 0;
@@ -747,6 +831,12 @@ public class TaskFormActivity extends BaseActivity implements AdapterView.OnItem
}
}
+ if (remindersAdapter != null) {
+ if (remindersAdapter.getRemindersItems() != null) {
+ task.setReminders(remindersAdapter.getRemindersItems());
+ }
+ }
+
if (task.text.isEmpty())
return false;
diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/adapter/tasks/RemindersAdapter.java b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/adapter/tasks/RemindersAdapter.java
new file mode 100644
index 000000000..35ca95e8e
--- /dev/null
+++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/adapter/tasks/RemindersAdapter.java
@@ -0,0 +1,143 @@
+package com.habitrpg.android.habitica.ui.adapter.tasks;
+
+import android.app.TimePickerDialog;
+import android.graphics.Color;
+import android.support.v7.widget.RecyclerView;
+import android.text.Editable;
+import android.text.TextWatcher;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.Button;
+import android.widget.EditText;
+import android.widget.TimePicker;
+
+import com.github.data5tream.emojilib.EmojiEditText;
+import com.habitrpg.android.habitica.R;
+import com.habitrpg.android.habitica.ui.helpers.ItemTouchHelperAdapter;
+import com.habitrpg.android.habitica.ui.helpers.ItemTouchHelperViewHolder;
+import com.magicmicky.habitrpgwrapper.lib.models.tasks.RemindersItem;
+import com.magicmicky.habitrpgwrapper.lib.models.tasks.RemindersItem;
+
+import java.util.Calendar;
+import java.util.Date;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+import butterknife.BindView;
+import butterknife.ButterKnife;
+
+/**
+ * Created by keithholliday on 5/31/16.
+ */
+public class RemindersAdapter extends RecyclerView.Adapter
+ implements ItemTouchHelperAdapter {
+ private final List reminders = new ArrayList<>();
+
+ public RemindersAdapter(List remindersInc) {
+ reminders.addAll(remindersInc);
+ }
+
+ @Override
+ public ItemViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
+ View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.reminder_item, parent, false);
+ ItemViewHolder itemViewHolder = new ItemViewHolder(view);
+ return itemViewHolder;
+ }
+
+ @Override
+ public void onBindViewHolder(final ItemViewHolder holder, int position) {
+ Date time = reminders.get(position).getTime();
+ holder.reminderItemTextView.setText(time.getHours() + ":" + time.getMinutes());
+ }
+
+ public void addItem(RemindersItem item){
+ reminders.add(item);
+ notifyItemInserted(reminders.size() - 1);
+ }
+
+ public List getRemindersItems(){
+ return reminders;
+ }
+
+ @Override
+ public int getItemCount() {
+ return reminders.size();
+ }
+
+
+ @Override
+ public void onItemDismiss(int position) {
+ if(position >= 0 && position < reminders.size()){
+ reminders.get(position).async().delete();
+ reminders.remove(position);
+ notifyItemRemoved(position);
+ }
+ }
+
+ @Override
+ public void onItemMove(int fromPosition, int toPosition) {
+ Collections.swap(reminders, fromPosition, toPosition);
+ notifyItemMoved(fromPosition, toPosition);
+ }
+
+ public class ItemViewHolder extends RecyclerView.ViewHolder implements
+ ItemTouchHelperViewHolder, Button.OnClickListener {
+
+ @BindView(R.id.item_edittext)
+ EditText reminderItemTextView;
+
+ @BindView(R.id.delete_item_button)
+ Button deleteButton;
+
+ public ItemViewHolder(View itemView) {
+ super(itemView);
+ ButterKnife.bind(this, itemView);
+ deleteButton.setOnClickListener(this);
+
+ reminderItemTextView.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ Calendar mcurrentTime = Calendar.getInstance();
+ int hour = mcurrentTime.get(Calendar.HOUR_OF_DAY);
+ int minute = mcurrentTime.get(Calendar.MINUTE);
+ TimePickerDialog mTimePicker;
+ mTimePicker = new TimePickerDialog(v.getContext(), new TimePickerDialog.OnTimeSetListener() {
+ @Override
+ public void onTimeSet(TimePicker timePicker, int selectedHour, int selectedMinute) {
+ reminderItemTextView.setText( selectedHour + ":" + selectedMinute);
+
+ RemindersItem reminder = reminders.get(getAdapterPosition());
+ Date time = reminder.getTime();
+ time.setHours(selectedHour);
+ time.setMinutes(selectedMinute);
+ time.setSeconds(0);
+ reminder.setTime(time);
+ reminder.async().save();
+ }
+ }, hour, minute, true);
+ mTimePicker.setTitle("Select Time");
+ mTimePicker.show();
+ }
+ });
+ }
+
+ @Override
+ public void onItemSelected() {
+ itemView.setBackgroundColor(Color.LTGRAY);
+ }
+
+ @Override
+ public void onItemClear() {
+ itemView.setBackgroundColor(0);
+ }
+
+ @Override
+ public void onClick(View v) {
+ if (v == deleteButton) {
+ RemindersAdapter.this.onItemDismiss(getAdapterPosition());
+ }
+ }
+ }
+}
diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/helpers/TaskAlarmManager.java b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/helpers/TaskAlarmManager.java
new file mode 100644
index 000000000..c92d94dfc
--- /dev/null
+++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/helpers/TaskAlarmManager.java
@@ -0,0 +1,135 @@
+package com.habitrpg.android.habitica.ui.helpers;
+
+import android.app.AlarmManager;
+import android.app.PendingIntent;
+import android.content.Context;
+import android.content.Intent;
+
+import com.habitrpg.android.habitica.events.TaskSaveEvent;
+import com.habitrpg.android.habitica.receivers.TaskReceiver;
+import com.magicmicky.habitrpgwrapper.lib.models.tasks.RemindersItem;
+import com.magicmicky.habitrpgwrapper.lib.models.tasks.Task;
+import com.raizlabs.android.dbflow.sql.builder.Condition;
+import com.raizlabs.android.dbflow.sql.language.Select;
+
+
+import org.greenrobot.eventbus.EventBus;
+import org.greenrobot.eventbus.Subscribe;
+
+import java.util.Calendar;
+import java.util.Date;
+import java.util.List;
+
+/**
+ * Created by keithholliday on 5/29/16.
+ */
+public class TaskAlarmManager {
+ private static TaskAlarmManager instance = null;
+
+ private Context context;
+
+ public static final String TASK_ID_INTENT_KEY = "TASK_ID";
+ public static final String TASK_NAME_INTENT_KEY = "TASK_NAME";
+
+ private TaskAlarmManager(Context context) {
+ this.context = context;
+ EventBus.getDefault().register(this);
+ }
+
+ public static TaskAlarmManager getInstance(Context context){
+ if(instance == null)
+ {
+ instance = new TaskAlarmManager(context);
+ }
+ return instance;
+ }
+
+ @Subscribe
+ public void onEvent(TaskSaveEvent evnt) {
+ Task task = (Task) evnt.task;
+ List reminders = task.getReminders();
+ for (RemindersItem reminder : reminders) {
+ if (task.getType().equals(Task.TYPE_DAILY)) {
+ //Ensure that we set to the next available time
+ reminder = this.setTimeForDailyReminder(reminder, task);
+ }
+ this.setAlarmForRemindersItem(reminder);
+ reminder.async().save();
+ }
+ }
+
+ //This function is used from the TaskReceiver since we do not have access to the task
+ //We currently only use this function to schedule the next reminder for dailies
+ //We may be able to use repeating alarms instead of this in the future
+ public void addAlarmForTaskId(String taskId) {
+ List tasks = new Select()
+ .from(Task.class)
+ .where(Condition.column("id").eq(taskId))
+ .queryList();
+
+ Task task = tasks.get(0);
+ if (task == null) return;
+
+ if (!task.getType().equals(Task.TYPE_DAILY)) {
+ return;
+ }
+
+ List reminders = task.getReminders();
+ for (RemindersItem remindersItem : reminders) {
+ //Ensure that we set to the next available time
+ remindersItem = this.setTimeForDailyReminder(remindersItem, task);
+ this.setAlarmForRemindersItem(remindersItem);
+ remindersItem.async().save();
+ }
+ }
+
+ public void scheduleAllSavedAlarms() {
+ List reminders = new Select()
+ .from(RemindersItem.class)
+ .where(Condition.column("time").greaterThan(new Date()))
+ .orderBy(true, "time")
+ .queryList();
+
+ for (RemindersItem remindersItem : reminders) {
+ this.setAlarmForRemindersItem(remindersItem);
+ }
+ }
+
+ private RemindersItem setTimeForDailyReminder (RemindersItem remindersItem, Task task) {
+ Date oldTime = remindersItem.getTime();
+ Date newTime = task.getNextActiveDateAfter(oldTime);
+ newTime.setHours(oldTime.getHours());
+ newTime.setMinutes(oldTime.getMinutes());
+ newTime.setSeconds(0);
+ remindersItem.setTime(newTime);
+ return remindersItem;
+ }
+
+ private void setAlarmForRemindersItem(RemindersItem remindersItem) {
+ Task reminderItemTask = remindersItem.getTask();
+ Integer alarmId = remindersItem.getAlarmId();
+
+ if (alarmId == null) {
+ alarmId = (int) System.currentTimeMillis();
+ remindersItem.setAlarmId(alarmId);
+ }
+
+ Date now = new Date();
+
+ if (remindersItem.getTime().before(now)) {
+ return;
+ }
+
+ Calendar cal = Calendar.getInstance();
+ cal.setTime(remindersItem.getTime());
+
+ Intent intent = new Intent(context, TaskReceiver.class);
+ intent.putExtra(TASK_NAME_INTENT_KEY, reminderItemTask.getText());
+ intent.putExtra(TASK_ID_INTENT_KEY, reminderItemTask.getId());
+
+ PendingIntent sender = PendingIntent.getBroadcast(context, alarmId, intent, PendingIntent.FLAG_UPDATE_CURRENT);
+
+ AlarmManager am = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE);
+ am.set(AlarmManager.RTC_WAKEUP, cal.getTimeInMillis(), sender);
+ }
+}
diff --git a/Habitica/src/main/java/com/magicmicky/habitrpgwrapper/lib/models/tasks/RemindersItem.java b/Habitica/src/main/java/com/magicmicky/habitrpgwrapper/lib/models/tasks/RemindersItem.java
new file mode 100644
index 000000000..2b6542fb3
--- /dev/null
+++ b/Habitica/src/main/java/com/magicmicky/habitrpgwrapper/lib/models/tasks/RemindersItem.java
@@ -0,0 +1,91 @@
+package com.magicmicky.habitrpgwrapper.lib.models.tasks;
+
+import com.habitrpg.android.habitica.HabitDatabase;
+import com.habitrpg.android.habitica.database.ExcludeCheckListItem;
+import com.raizlabs.android.dbflow.annotation.Column;
+import com.raizlabs.android.dbflow.annotation.ForeignKey;
+import com.raizlabs.android.dbflow.annotation.ForeignKeyReference;
+import com.raizlabs.android.dbflow.annotation.PrimaryKey;
+import com.raizlabs.android.dbflow.annotation.Table;
+import com.raizlabs.android.dbflow.structure.BaseModel;
+import com.raizlabs.android.dbflow.structure.container.ForeignKeyContainer;
+
+import java.util.Date;
+
+/**
+ * Created by keithholliday on 5/31/16.
+ */
+@Table(databaseName = HabitDatabase.NAME)
+public class RemindersItem extends BaseModel {
+ @Column
+ @PrimaryKey
+ private String id;
+
+ @Column
+ private Date startDate;
+
+ @Column
+ private Date time;
+
+ @Column
+ private Integer alarmId;
+
+ @Column
+ @ForeignKey(
+ references = {@ForeignKeyReference(columnName = "task_id",
+ columnType = String.class,
+ foreignColumnName = "id")},
+ saveForeignKeyModel = false)
+ @ExcludeCheckListItem
+ ForeignKeyContainer task;
+
+ public String getId() {
+ return id;
+ }
+ public void setId(String id) {
+ this.id = id;
+ }
+
+ public Integer getAlarmId() {
+ return this.alarmId;
+ }
+ public void setAlarmId(Integer alarmId) {
+ this.alarmId = alarmId;
+ }
+
+ public Date getStartDate() {
+ return startDate;
+ }
+ public void setStartDate(Date startDate) {
+ this.startDate = startDate;
+ }
+
+ public Date getTime() {
+ return time;
+ }
+ public void setTime(Date time) {
+ this.time = time;
+ }
+
+ public Task getTask() {
+ if (task != null) {
+ return task.toModel();
+ } else {
+ return null;
+ }
+ }
+
+ public void setTask(Task task) {
+ this.task = new ForeignKeyContainer<>(Task.class);
+ this.task.setModel(task);
+ this.task.put("id", task.getId());
+ }
+
+ @Override
+ public void save() {
+ if (this.getId() == null) {
+ return;
+ }
+ super.save();
+ }
+}
diff --git a/Habitica/src/main/java/com/magicmicky/habitrpgwrapper/lib/models/tasks/Task.java b/Habitica/src/main/java/com/magicmicky/habitrpgwrapper/lib/models/tasks/Task.java
index 53b44f480..37aa3cc67 100644
--- a/Habitica/src/main/java/com/magicmicky/habitrpgwrapper/lib/models/tasks/Task.java
+++ b/Habitica/src/main/java/com/magicmicky/habitrpgwrapper/lib/models/tasks/Task.java
@@ -78,6 +78,7 @@ public class Task extends BaseModel {
public boolean completed;
public List checklist;
+ public List reminders;
//dailies
@@ -291,6 +292,25 @@ public class Task extends BaseModel {
this.checklist = checklist;
}
+ @OneToMany(methods = {OneToMany.Method.SAVE, OneToMany.Method.DELETE}, variableName = "reminders")
+ public List getReminders() {
+ if(this.reminders == null) {
+ this.reminders = new Select()
+ .from(RemindersItem.class)
+ .where(Condition.column("task_id").eq(this.id))
+ .orderBy(true, "time")
+ .queryList();
+ }
+ return this.reminders;
+ }
+
+ public void setReminders(List reminders) {
+ for (RemindersItem remindersItem : reminders) {
+ remindersItem.setTask(this);
+ }
+ this.reminders = reminders;
+ }
+
public Integer getCompletedChecklistCount() {
Integer count = 0;
for (ChecklistItem item : this.getChecklist()) {
@@ -387,11 +407,13 @@ public class Task extends BaseModel {
}
List tmpTags = tags;
List tmpChecklist = checklist;
+ List tmpReminders = reminders;
// remove them, so that the database don't add empty entries
tags = null;
checklist = null;
+ reminders = null;
if(repeat != null)
repeat.task_id = this.id;
@@ -400,6 +422,7 @@ public class Task extends BaseModel {
tags = tmpTags;
checklist = tmpChecklist;
+ reminders = tmpReminders;
if (this.tags != null) {
for (TaskTag tag : this.tags) {
@@ -407,6 +430,7 @@ public class Task extends BaseModel {
tag.async().save();
}
}
+
int position = 0;
if (this.checklist != null) {
for (ChecklistItem item : this.checklist) {
@@ -418,6 +442,20 @@ public class Task extends BaseModel {
position++;
}
}
+
+ int index = 0;
+ if (this.reminders != null) {
+ for (RemindersItem item : this.reminders) {
+ if(item.getTask() == null) {
+ item.setTask(this);
+ }
+ if(item.getId() == null) {
+ item.setId(this.id + "task-reminder" + index);
+ }
+ item.async().save();
+ index++;
+ }
+ }
}
@Override
@@ -537,4 +575,35 @@ public class Task extends BaseModel {
public Boolean isChecklistDisplayActive(int offset) {
return this.isDisplayedActive(offset) && (this.checklist.size() != this.getCompletedChecklistCount());
}
+
+ public Date getNextActiveDateAfter(Date oldTime) {
+ Calendar today = new GregorianCalendar();
+ today.set(Calendar.MILLISECOND, 0);
+ today.set(Calendar.SECOND, 0);
+
+ Calendar newTime = new GregorianCalendar();
+ newTime.setTime(oldTime);
+
+ if (this.getFrequency().equals(FREQUENCY_DAILY)) {
+ TimeUnit timeUnit = TimeUnit.DAYS;
+ long diffInMillies = newTime.getTimeInMillis() - today.getTimeInMillis();
+ long daySinceStart = timeUnit.convert(diffInMillies, TimeUnit.MILLISECONDS);
+ long daysUntilNextReminder = this.getEveryX() - (daySinceStart % this.getEveryX()) - 1;
+
+ if (newTime.before(today)) {
+ daysUntilNextReminder += this.getEveryX();
+ }
+
+ newTime.add(Calendar.DATE, (int) daysUntilNextReminder);
+ } else {
+ int nextActiveDayOfTheWeek = today.get(Calendar.DAY_OF_WEEK);
+ while (!this.getRepeat().getForDay(nextActiveDayOfTheWeek) || newTime.before(today)) {
+ if (nextActiveDayOfTheWeek == 6) nextActiveDayOfTheWeek = 0;
+ nextActiveDayOfTheWeek += 1;
+ newTime.add(Calendar.DATE, 1);
+ }
+ }
+
+ return newTime.getTime();
+ }
}
diff --git a/Habitica/src/main/java/com/magicmicky/habitrpgwrapper/lib/utils/RemindersItemSerializer.java b/Habitica/src/main/java/com/magicmicky/habitrpgwrapper/lib/utils/RemindersItemSerializer.java
new file mode 100644
index 000000000..34b1a603f
--- /dev/null
+++ b/Habitica/src/main/java/com/magicmicky/habitrpgwrapper/lib/utils/RemindersItemSerializer.java
@@ -0,0 +1,25 @@
+package com.magicmicky.habitrpgwrapper.lib.utils;
+
+import com.google.gson.JsonElement;
+import com.google.gson.JsonObject;
+import com.google.gson.JsonSerializationContext;
+import com.google.gson.JsonSerializer;
+import com.magicmicky.habitrpgwrapper.lib.models.tasks.RemindersItem;
+
+import java.lang.reflect.Type;
+
+/**
+ * Created by keithholliday on 6/4/16.
+ */
+public class RemindersItemSerializer
+ implements JsonSerializer {
+
+ @Override
+ public JsonElement serialize(RemindersItem src, Type typeOfSrc, JsonSerializationContext context) {
+ JsonObject object = new JsonObject();
+ object.addProperty("id", src.getId());
+ object.addProperty("startDate", src.getStartDate().toString());
+ object.addProperty("time", src.getTime().toString());
+ return object;
+ }
+}
diff --git a/Habitica/src/main/java/com/magicmicky/habitrpgwrapper/lib/utils/TaskSerializer.java b/Habitica/src/main/java/com/magicmicky/habitrpgwrapper/lib/utils/TaskSerializer.java
index 0404ac8cd..064022beb 100644
--- a/Habitica/src/main/java/com/magicmicky/habitrpgwrapper/lib/utils/TaskSerializer.java
+++ b/Habitica/src/main/java/com/magicmicky/habitrpgwrapper/lib/utils/TaskSerializer.java
@@ -39,6 +39,7 @@ public class TaskSerializer implements JsonSerializer {
obj.add("startDate", context.serialize(task.getStartDate()));
obj.addProperty("streak", task.getStreak());
obj.add("checklist", context.serialize(task.getChecklist()));
+ obj.add("reminders", context.serialize(task.getReminders()));
obj.addProperty("completed", task.getCompleted());
break;
case "todo":
@@ -48,6 +49,7 @@ public class TaskSerializer implements JsonSerializer {
obj.add("date", context.serialize(task.getDueDate()));
}
obj.add("checklist", context.serialize(task.getChecklist()));
+ obj.add("reminders", context.serialize(task.getReminders()));
obj.addProperty("completed", task.getCompleted());
break;
}
diff --git a/Habitica/src/test/java/com/habitrpg/android/habitica/api/TaskAPITests.java b/Habitica/src/test/java/com/habitrpg/android/habitica/api/TaskAPITests.java
index 6b557846e..92dfcddbc 100644
--- a/Habitica/src/test/java/com/habitrpg/android/habitica/api/TaskAPITests.java
+++ b/Habitica/src/test/java/com/habitrpg/android/habitica/api/TaskAPITests.java
@@ -70,6 +70,7 @@ public class TaskAPITests extends BaseAPITests {
task.setType(type);
task.setTags(new ArrayList<>());
task.setChecklist(new ArrayList<>());
+ task.setReminders(new ArrayList<>());
return task;
}
diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties
index 22d6f6602..73e9ac19c 100644
--- a/gradle/wrapper/gradle-wrapper.properties
+++ b/gradle/wrapper/gradle-wrapper.properties
@@ -4,3 +4,4 @@ distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-2.13-all.zip
+org.gradle.jvmargs=-Xmx3072M