Merge pull request #581 from schrockblock/feature/schrockblock/reorder-tasks

allow habits, dailies, and todos rearranging
This commit is contained in:
Phillip Thelen 2016-07-29 12:13:00 +02:00 committed by GitHub
commit f5f5f0012e
12 changed files with 164 additions and 21 deletions

View file

@ -154,6 +154,7 @@ android {
}
dexOptions{
javaMaxHeapSize "4g"
preDexLibraries = false
}

View file

@ -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">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"

View file

@ -4,7 +4,8 @@
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@color/white"
android:orientation="horizontal">
android:orientation="horizontal"
android:foreground="?selectableItemBackground">
<LinearLayout
android:layout_width="80dp"
android:layout_height="match_parent"

View file

@ -4,7 +4,8 @@
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:background="@color/white">
android:background="@color/white"
android:foreground="?selectableItemBackground">
<LinearLayout
android:id="@+id/card_view"
android:layout_width="match_parent"

View file

@ -6,12 +6,14 @@ import com.habitrpg.android.habitica.ui.viewHolders.tasks.DailyViewHolder;
import android.content.Context;
import android.view.ViewGroup;
public class DailiesRecyclerViewHolder extends BaseTasksRecyclerViewAdapter<DailyViewHolder> {
public class DailiesRecyclerViewHolder extends SortableTasksRecyclerViewAdapter<DailyViewHolder> {
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;
}

View file

@ -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<HabitViewHolder> {
public HabitsRecyclerViewAdapter(String taskType, TagsHelper tagsHelper, int layoutResource, Context newContext, String userID) {
super(taskType, tagsHelper, layoutResource, newContext, userID);
public class HabitsRecyclerViewAdapter extends SortableTasksRecyclerViewAdapter<HabitViewHolder> {
public HabitsRecyclerViewAdapter(String taskType, TagsHelper tagsHelper, int layoutResource, Context newContext, String userID, SortTasksCallback sortCallback) {
super(taskType, tagsHelper, layoutResource, newContext, userID, sortCallback);
}
@Override

View file

@ -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<VH extends BaseTaskViewHolder>
extends BaseTasksRecyclerViewAdapter<VH> 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);
}
}
}

View file

@ -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<TodoViewHolder> {
public class TodosRecyclerViewAdapter extends SortableTasksRecyclerViewAdapter<TodoViewHolder> {
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

View file

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

View file

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

View file

@ -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);
}

View file

@ -97,6 +97,9 @@ public interface ApiService {
@POST("tasks/{id}/score/{direction}")
Observable<TaskDirectionData> postTaskDirection(@Path("id") String id, @Path("direction") String direction);
@POST("tasks/{id}/move/to/{position}")
Observable<Void> postTaskNewPosition(@Path("id") String id, @Path("position") String position);
@POST("tasks/{taskId}/checklist/{itemId}/score")
Observable<Task> scoreChecklistItem(@Path("taskId") String taskId, @Path("itemId") String itemId);