diff --git a/Habitica/build.gradle b/Habitica/build.gradle index 126fc3e38..e0d8ff629 100644 --- a/Habitica/build.gradle +++ b/Habitica/build.gradle @@ -154,6 +154,7 @@ android { } dexOptions{ + javaMaxHeapSize "4g" preDexLibraries = false } diff --git a/Habitica/res/layout/daily_item_card.xml b/Habitica/res/layout/daily_item_card.xml index 9cdd0a653..54b22287f 100644 --- a/Habitica/res/layout/daily_item_card.xml +++ b/Habitica/res/layout/daily_item_card.xml @@ -4,7 +4,8 @@ android:layout_width="match_parent" android:layout_height="wrap_content" android:background="@color/white" - android:orientation="vertical"> + android:orientation="vertical" + android:foreground="?selectableItemBackground"> + android:orientation="horizontal" + android:foreground="?selectableItemBackground"> + android:background="@color/white" + android:foreground="?selectableItemBackground"> { +public class DailiesRecyclerViewHolder extends SortableTasksRecyclerViewAdapter { public int dailyResetOffset; - public DailiesRecyclerViewHolder(String taskType, TagsHelper tagsHelper, int layoutResource, Context newContext, String userID, int dailyResetOffset) { - super(taskType, tagsHelper, layoutResource, newContext, userID); + public DailiesRecyclerViewHolder(String taskType, TagsHelper tagsHelper, int layoutResource, + Context newContext, String userID, int dailyResetOffset, + SortTasksCallback sortTasksCallback) { + super(taskType, tagsHelper, layoutResource, newContext, userID, sortTasksCallback); this.dailyResetOffset = dailyResetOffset; } diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/adapter/tasks/HabitsRecyclerViewAdapter.java b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/adapter/tasks/HabitsRecyclerViewAdapter.java index dc44a2711..e6bbf73eb 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/adapter/tasks/HabitsRecyclerViewAdapter.java +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/adapter/tasks/HabitsRecyclerViewAdapter.java @@ -6,9 +6,11 @@ import com.habitrpg.android.habitica.ui.viewHolders.tasks.HabitViewHolder; import android.content.Context; import android.view.ViewGroup; -public class HabitsRecyclerViewAdapter extends BaseTasksRecyclerViewAdapter { - public HabitsRecyclerViewAdapter(String taskType, TagsHelper tagsHelper, int layoutResource, Context newContext, String userID) { - super(taskType, tagsHelper, layoutResource, newContext, userID); +public class HabitsRecyclerViewAdapter extends SortableTasksRecyclerViewAdapter { + + + public HabitsRecyclerViewAdapter(String taskType, TagsHelper tagsHelper, int layoutResource, Context newContext, String userID, SortTasksCallback sortCallback) { + super(taskType, tagsHelper, layoutResource, newContext, userID, sortCallback); } @Override diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/adapter/tasks/SortableTasksRecyclerViewAdapter.java b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/adapter/tasks/SortableTasksRecyclerViewAdapter.java new file mode 100644 index 000000000..03b541180 --- /dev/null +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/adapter/tasks/SortableTasksRecyclerViewAdapter.java @@ -0,0 +1,48 @@ +package com.habitrpg.android.habitica.ui.adapter.tasks; + +import android.content.Context; + +import com.habitrpg.android.habitica.helpers.TagsHelper; +import com.habitrpg.android.habitica.ui.helpers.ItemTouchHelperAdapter; +import com.habitrpg.android.habitica.ui.helpers.ItemTouchHelperDropCallback; +import com.habitrpg.android.habitica.ui.viewHolders.tasks.BaseTaskViewHolder; +import com.magicmicky.habitrpgwrapper.lib.models.tasks.Task; + +import java.util.Collections; + +/** + * Created by ell on 7/21/16. + */ +public abstract class SortableTasksRecyclerViewAdapter + extends BaseTasksRecyclerViewAdapter implements ItemTouchHelperAdapter, ItemTouchHelperDropCallback { + + public interface SortTasksCallback { + void onMove(Task task, int from, int to); + } + + private SortTasksCallback sortCallback; + + public SortableTasksRecyclerViewAdapter(String taskType, TagsHelper tagsHelper, int layoutResource, + Context newContext, String userID, SortTasksCallback sortCallback) { + super(taskType, tagsHelper, layoutResource, newContext, userID); + this.sortCallback = sortCallback; + } + + @Override + public void onItemMove(int fromPosition, int toPosition) { + Collections.swap(filteredContent, fromPosition, toPosition); + notifyItemMoved(fromPosition, toPosition); + } + + @Override + public void onItemDismiss(int position) { + //NO OP + } + + @Override + public void onDrop(int from, int to) { + if (this.sortCallback != null && from != to){ + this.sortCallback.onMove(filteredContent.get(to), from, to); + } + } +} diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/adapter/tasks/TodosRecyclerViewAdapter.java b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/adapter/tasks/TodosRecyclerViewAdapter.java index 2e903bbba..068e466b1 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/adapter/tasks/TodosRecyclerViewAdapter.java +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/adapter/tasks/TodosRecyclerViewAdapter.java @@ -6,10 +6,11 @@ import com.habitrpg.android.habitica.ui.viewHolders.tasks.TodoViewHolder; import android.content.Context; import android.view.ViewGroup; -public class TodosRecyclerViewAdapter extends BaseTasksRecyclerViewAdapter { +public class TodosRecyclerViewAdapter extends SortableTasksRecyclerViewAdapter { - public TodosRecyclerViewAdapter(String taskType, TagsHelper tagsHelper, int layoutResource, Context newContext, String userID) { - super(taskType, tagsHelper, layoutResource, newContext, userID); + public TodosRecyclerViewAdapter(String taskType, TagsHelper tagsHelper, int layoutResource, + Context newContext, String userID, SortTasksCallback sortCallback) { + super(taskType, tagsHelper, layoutResource, newContext, userID, sortCallback); } @Override diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/tasks/TaskRecyclerViewFragment.java b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/tasks/TaskRecyclerViewFragment.java index dae2bebb9..61e258d50 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/tasks/TaskRecyclerViewFragment.java +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/tasks/TaskRecyclerViewFragment.java @@ -2,6 +2,7 @@ package com.habitrpg.android.habitica.ui.fragments.tasks; import com.habitrpg.android.habitica.APIHelper; import com.habitrpg.android.habitica.R; +import com.habitrpg.android.habitica.callbacks.HabitRPGUserCallback; import com.habitrpg.android.habitica.components.AppComponent; import com.habitrpg.android.habitica.events.commands.AddNewTaskCommand; import com.habitrpg.android.habitica.helpers.TagsHelper; @@ -9,22 +10,32 @@ import com.habitrpg.android.habitica.ui.adapter.tasks.BaseTasksRecyclerViewAdapt import com.habitrpg.android.habitica.ui.adapter.tasks.DailiesRecyclerViewHolder; import com.habitrpg.android.habitica.ui.adapter.tasks.HabitsRecyclerViewAdapter; import com.habitrpg.android.habitica.ui.adapter.tasks.RewardsRecyclerViewAdapter; +import com.habitrpg.android.habitica.ui.adapter.tasks.SortableTasksRecyclerViewAdapter; import com.habitrpg.android.habitica.ui.adapter.tasks.TodosRecyclerViewAdapter; import com.habitrpg.android.habitica.ui.fragments.BaseFragment; +import com.habitrpg.android.habitica.ui.helpers.ItemTouchHelperAdapter; +import com.habitrpg.android.habitica.ui.helpers.ItemTouchHelperDropCallback; import com.habitrpg.android.habitica.ui.menu.DividerItemDecoration; import com.magicmicky.habitrpgwrapper.lib.models.HabitRPGUser; import com.magicmicky.habitrpgwrapper.lib.models.tasks.Task; import org.greenrobot.eventbus.EventBus; +import android.graphics.Color; import android.os.Bundle; import android.support.annotation.Nullable; import android.support.v7.widget.LinearLayoutManager; import android.support.v7.widget.RecyclerView; +import android.support.v7.widget.helper.ItemTouchHelper; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + import javax.inject.Inject; import javax.inject.Named; @@ -49,12 +60,16 @@ public class TaskRecyclerViewFragment extends BaseFragment implements View.OnCli private String classType; private HabitRPGUser user; private View view; + private SortableTasksRecyclerViewAdapter.SortTasksCallback sortCallback; + private ItemTouchHelper.Callback mItemTouchCallback; - public static TaskRecyclerViewFragment newInstance(HabitRPGUser user, String classType) { + public static TaskRecyclerViewFragment newInstance(HabitRPGUser user, String classType, + SortableTasksRecyclerViewAdapter.SortTasksCallback sortCallback) { TaskRecyclerViewFragment fragment = new TaskRecyclerViewFragment(); fragment.setRetainInstance(true); fragment.user = user; fragment.classType = classType; + fragment.sortCallback = sortCallback; return fragment; } @@ -65,7 +80,8 @@ public class TaskRecyclerViewFragment extends BaseFragment implements View.OnCli switch (this.classType) { case Task.TYPE_HABIT: layoutOfType = R.layout.habit_item_card; - this.recyclerAdapter = new HabitsRecyclerViewAdapter(Task.TYPE_HABIT, tagsHelper, layoutOfType, getContext(), userID); + this.recyclerAdapter = new HabitsRecyclerViewAdapter(Task.TYPE_HABIT, tagsHelper, layoutOfType, getContext(), userID, sortCallback); + allowReordering(); break; case Task.TYPE_DAILY: layoutOfType = R.layout.daily_item_card; @@ -73,11 +89,13 @@ public class TaskRecyclerViewFragment extends BaseFragment implements View.OnCli if (user != null) { dailyResetOffset = user.getPreferences().getDayStart(); } - this.recyclerAdapter = new DailiesRecyclerViewHolder(Task.TYPE_DAILY, tagsHelper, layoutOfType, getContext(), userID, dailyResetOffset); + this.recyclerAdapter = new DailiesRecyclerViewHolder(Task.TYPE_DAILY, tagsHelper, layoutOfType, getContext(), userID, dailyResetOffset, sortCallback); + allowReordering(); break; case Task.TYPE_TODO: layoutOfType = R.layout.todo_item_card; - this.recyclerAdapter = new TodosRecyclerViewAdapter(Task.TYPE_TODO, tagsHelper, layoutOfType, getContext(), userID); + this.recyclerAdapter = new TodosRecyclerViewAdapter(Task.TYPE_TODO, tagsHelper, layoutOfType, getContext(), userID, sortCallback); + allowReordering(); return; case Task.TYPE_REWARD: layoutOfType = R.layout.reward_item_card; @@ -87,8 +105,51 @@ public class TaskRecyclerViewFragment extends BaseFragment implements View.OnCli } } + private void allowReordering(){ + ItemTouchHelper itemTouchHelper = new ItemTouchHelper(mItemTouchCallback); + itemTouchHelper.attachToRecyclerView(recyclerView); + } + @Override public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { + mItemTouchCallback = new ItemTouchHelper.Callback() { + private Integer mFromPosition = null; + + @Override + public void onSelectedChanged(RecyclerView.ViewHolder viewHolder, int actionState) { + super.onSelectedChanged(viewHolder, actionState); + if (viewHolder != null){ + viewHolder.itemView.setBackgroundColor(Color.LTGRAY); + } + } + + public boolean onMove(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder, RecyclerView.ViewHolder target) { + if (mFromPosition == null) mFromPosition = viewHolder.getAdapterPosition(); + ((ItemTouchHelperAdapter)recyclerAdapter).onItemMove(viewHolder.getAdapterPosition(), target.getAdapterPosition()); + return true; + } + + @Override + public void onSwiped(RecyclerView.ViewHolder viewHolder, int direction) {} + + //defines the enabled move directions in each state (idle, swiping, dragging). + @Override + public int getMovementFlags(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder) { + return makeFlag(ItemTouchHelper.ACTION_STATE_DRAG, + ItemTouchHelper.DOWN | ItemTouchHelper.UP); + } + + @Override + public void clearView(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder) { + super.clearView(recyclerView, viewHolder); + + viewHolder.itemView.setBackgroundColor(Color.WHITE); + if (mFromPosition != null){ + ((ItemTouchHelperDropCallback)recyclerAdapter).onDrop(mFromPosition, viewHolder.getAdapterPosition()); + } + } + }; + if (view == null) { view = inflater.inflate(R.layout.fragment_recyclerview, container, false); diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/tasks/TasksFragment.java b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/tasks/TasksFragment.java index d65567b18..f997c6c45 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/tasks/TasksFragment.java +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/tasks/TasksFragment.java @@ -23,6 +23,7 @@ import com.habitrpg.android.habitica.ui.activities.MainActivity; import com.habitrpg.android.habitica.ui.activities.TaskFormActivity; import com.habitrpg.android.habitica.ui.adapter.tasks.BaseTasksRecyclerViewAdapter; import com.habitrpg.android.habitica.ui.adapter.tasks.DailiesRecyclerViewHolder; +import com.habitrpg.android.habitica.ui.adapter.tasks.SortableTasksRecyclerViewAdapter; import com.habitrpg.android.habitica.ui.fragments.BaseMainFragment; import com.habitrpg.android.habitica.ui.helpers.Debounce; import com.habitrpg.android.habitica.ui.helpers.UiUtils; @@ -47,6 +48,7 @@ import android.support.v4.app.Fragment; import android.support.v4.app.FragmentPagerAdapter; import android.support.v4.view.GravityCompat; import android.support.v4.view.ViewPager; +import android.util.Log; import android.view.LayoutInflater; import android.view.Menu; import android.view.MenuInflater; @@ -66,6 +68,9 @@ import java.util.Map; import javax.inject.Inject; +import rx.Observer; +import rx.functions.Action1; + public class TasksFragment extends BaseMainFragment implements OnCheckedChangeListener { private static final int TASK_CREATED_RESULT = 1; @@ -200,20 +205,29 @@ public class TasksFragment extends BaseMainFragment implements OnCheckedChangeLi @Override public Fragment getItem(int position) { TaskRecyclerViewFragment fragment; - BaseTasksRecyclerViewAdapter adapter; + SortableTasksRecyclerViewAdapter.SortTasksCallback sortCallback = + (task, from, to) -> { + if (apiHelper != null){ + apiHelper.apiService.postTaskNewPosition(task.getId(), String.valueOf(to)) + .compose(apiHelper.configureApiCallObserver()) + .subscribe(aVoid -> { + new HabitRPGUserCallback(activity); + }); + } + }; switch (position) { case 0: - fragment = TaskRecyclerViewFragment.newInstance(user, Task.TYPE_HABIT); + fragment = TaskRecyclerViewFragment.newInstance(user, Task.TYPE_HABIT, sortCallback); break; case 1: - fragment = TaskRecyclerViewFragment.newInstance(user, Task.TYPE_DAILY); + fragment = TaskRecyclerViewFragment.newInstance(user, Task.TYPE_DAILY, sortCallback); break; case 3: - fragment = TaskRecyclerViewFragment.newInstance(user, Task.TYPE_REWARD); + fragment = TaskRecyclerViewFragment.newInstance(user, Task.TYPE_REWARD, null); break; default: - fragment = TaskRecyclerViewFragment.newInstance(user, Task.TYPE_TODO); + fragment = TaskRecyclerViewFragment.newInstance(user, Task.TYPE_TODO, sortCallback); } ViewFragmentsDictionary.put(position, fragment); diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/helpers/ItemTouchHelperDropCallback.java b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/helpers/ItemTouchHelperDropCallback.java new file mode 100644 index 000000000..3bc3d03ce --- /dev/null +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/helpers/ItemTouchHelperDropCallback.java @@ -0,0 +1,8 @@ +package com.habitrpg.android.habitica.ui.helpers; + +/** + * Created by ell on 3/30/16. + */ +public interface ItemTouchHelperDropCallback { + void onDrop(int from, int to); +} diff --git a/Habitica/src/main/java/com/magicmicky/habitrpgwrapper/lib/api/ApiService.java b/Habitica/src/main/java/com/magicmicky/habitrpgwrapper/lib/api/ApiService.java index e3c715774..8a0a5f4ef 100644 --- a/Habitica/src/main/java/com/magicmicky/habitrpgwrapper/lib/api/ApiService.java +++ b/Habitica/src/main/java/com/magicmicky/habitrpgwrapper/lib/api/ApiService.java @@ -97,6 +97,9 @@ public interface ApiService { @POST("tasks/{id}/score/{direction}") Observable postTaskDirection(@Path("id") String id, @Path("direction") String direction); + @POST("tasks/{id}/move/to/{position}") + Observable postTaskNewPosition(@Path("id") String id, @Path("position") String position); + @POST("tasks/{taskId}/checklist/{itemId}/score") Observable scoreChecklistItem(@Path("taskId") String taskId, @Path("itemId") String itemId);