fix task reordering

This commit is contained in:
Phillip Thelen 2017-10-19 17:15:37 +02:00
parent a055fb8938
commit c8c828ea7e
8 changed files with 251 additions and 25 deletions

View file

@ -47,7 +47,7 @@ public interface TaskRepository extends BaseRepository {
void swapTaskPosition(int firstPosition, int secondPosition);
Observable<List<String>> updateTaskPosition(int currentPosition);
Observable<List<String>> updateTaskPosition(int oldPosition, int newPosition);
Observable<Task> getUnmanagedTask(String taskid);

View file

@ -219,14 +219,16 @@ public class TaskRepositoryImpl extends BaseRepositoryImpl<TaskLocalRepository>
localRepository.swapTaskPosition(firstPosition, secondPosition);
}
public Observable<List<String>> updateTaskPosition(int currentPosition) {
return localRepository.getTaskAtPosition(currentPosition).first()
public Observable<List<String>> updateTaskPosition(int oldPosition, int newPosition) {
return localRepository.getTaskAtPosition(oldPosition)
.first()
.flatMap(task -> {
if (task.isValid()) {
return apiClient.postTaskNewPosition(task.getId(), currentPosition);
return apiClient.postTaskNewPosition(task.getId(), newPosition);
}
return Observable.just(null);
});
})
.doOnNext(localRepository::updateTaskPositions);
}
@Override

View file

@ -35,4 +35,6 @@ public interface TaskLocalRepository extends BaseLocalRepository {
Observable<Task> getTaskAtPosition(int currentPosition);
Observable<TaskList> updateIsdue(TaskList daily);
void updateTaskPositions(List<String> taskOrder);
}

View file

@ -218,4 +218,18 @@ public class RealmTaskLocalRepository extends RealmBaseLocalRepository implement
return dailies;
});
}
@Override
public void updateTaskPositions(List<String> taskOrder) {
if (taskOrder.size() > 0) {
RealmResults<Task> tasks = realm.where(Task.class).in("id", taskOrder.toArray(new String[0])).findAll();
realm.executeTransaction(realm1 -> {
for (Task task : tasks) {
if (taskOrder.contains(task.getId())) {
task.position = taskOrder.indexOf(task.getId());
}
}
});
}
}
}

View file

@ -1,6 +1,8 @@
package com.habitrpg.android.habitica.ui.adapter.tasks;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.support.v7.widget.RecyclerView;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
@ -11,23 +13,192 @@ import com.habitrpg.android.habitica.helpers.TaskFilterHelper;
import com.habitrpg.android.habitica.models.tasks.Task;
import com.habitrpg.android.habitica.ui.viewHolders.tasks.BaseTaskViewHolder;
import io.realm.OrderedCollectionChangeSet;
import io.realm.OrderedRealmCollection;
import io.realm.OrderedRealmCollectionChangeListener;
import io.realm.RealmList;
import io.realm.RealmModel;
import io.realm.RealmQuery;
import io.realm.RealmRecyclerViewAdapter;
import io.realm.RealmResults;
public abstract class RealmBaseTasksRecyclerViewAdapter<VH extends BaseTaskViewHolder> extends RecyclerView.Adapter<VH> implements TaskRecyclerViewAdapter {
private final boolean hasAutoUpdates;
private final boolean updateOnModification;
private boolean ignoreUpdates;
private final OrderedRealmCollectionChangeListener listener;
@Nullable
private OrderedRealmCollection<Task> adapterData;
private OrderedRealmCollectionChangeListener createListener() {
return new OrderedRealmCollectionChangeListener() {
@Override
public void onChange(Object collection, OrderedCollectionChangeSet changeSet) {
if (ignoreUpdates) {
return;
}
// null Changes means the async query returns the first time.
if (changeSet == null) {
notifyDataSetChanged();
return;
}
// For deletions, the adapter has to be notified in reverse order.
OrderedCollectionChangeSet.Range[] deletions = changeSet.getDeletionRanges();
for (int i = deletions.length - 1; i >= 0; i--) {
OrderedCollectionChangeSet.Range range = deletions[i];
notifyItemRangeRemoved(range.startIndex, range.length);
}
OrderedCollectionChangeSet.Range[] insertions = changeSet.getInsertionRanges();
for (OrderedCollectionChangeSet.Range range : insertions) {
notifyItemRangeInserted(range.startIndex, range.length);
}
if (!updateOnModification) {
return;
}
OrderedCollectionChangeSet.Range[] modifications = changeSet.getChangeRanges();
for (OrderedCollectionChangeSet.Range range : modifications) {
notifyItemRangeChanged(range.startIndex, range.length);
}
}
};
}
public RealmBaseTasksRecyclerViewAdapter(@Nullable OrderedRealmCollection<Task> data, boolean autoUpdate, int layoutResource, @Nullable TaskFilterHelper taskFilterHelper) {
this.unfilteredData = data;
this.layoutResource = layoutResource;
this.taskFilterHelper = taskFilterHelper;
if (data != null && !data.isManaged())
throw new IllegalStateException("Only use this adapter with managed RealmCollection, " +
"for un-managed lists you can just use the BaseRecyclerViewAdapter");
this.adapterData = data;
this.hasAutoUpdates = autoUpdate;
this.listener = hasAutoUpdates ? createListener() : null;
this.updateOnModification = true;
}
@Override
public void onAttachedToRecyclerView(final RecyclerView recyclerView) {
super.onAttachedToRecyclerView(recyclerView);
if (hasAutoUpdates && isDataValid()) {
//noinspection ConstantConditions
addListener(adapterData);
}
}
@Override
public void onDetachedFromRecyclerView(final RecyclerView recyclerView) {
super.onDetachedFromRecyclerView(recyclerView);
if (hasAutoUpdates && isDataValid()) {
//noinspection ConstantConditions
removeListener(adapterData);
}
}
/**
* Returns the current ID for an item. Note that item IDs are not stable so you cannot rely on the item ID being the
* same after notifyDataSetChanged() or {@link #updateData(OrderedRealmCollection)} has been called.
*
* @param index position of item in the adapter.
* @return current item ID.
*/
@Override
public long getItemId(final int index) {
return index;
}
@Override
public int getItemCount() {
//noinspection ConstantConditions
return isDataValid() ? adapterData.size() : 0;
}
/**
* Returns the item associated with the specified position.
* Can return {@code null} if provided Realm instance by {@link OrderedRealmCollection} is closed.
*
* @param index index of the item.
* @return the item at the specified position, {@code null} if adapter data is not valid.
*/
@SuppressWarnings("WeakerAccess")
@Nullable
public Task getItem(int index) {
//noinspection ConstantConditions
return isDataValid() ? adapterData.get(index) : null;
}
/**
* Returns data associated with this adapter.
*
* @return adapter data.
*/
@Nullable
public OrderedRealmCollection<Task> getData() {
return adapterData;
}
/**
* Updates the data associated to the Adapter. Useful when the query has been changed.
* If the query does not change you might consider using the automaticUpdate feature.
*
* @param data the new {@link OrderedRealmCollection} to display.
*/
@SuppressWarnings("WeakerAccess")
public void updateData(@Nullable OrderedRealmCollection<Task> data) {
if (hasAutoUpdates) {
if (isDataValid()) {
//noinspection ConstantConditions
removeListener(adapterData);
}
if (data != null) {
addListener(data);
}
}
this.adapterData = data;
notifyDataSetChanged();
}
private void addListener(@NonNull OrderedRealmCollection<Task> data) {
if (data instanceof RealmResults) {
RealmResults<Task> results = (RealmResults<Task>) data;
//noinspection unchecked
results.addChangeListener(listener);
} else if (data instanceof RealmList) {
RealmList<Task> list = (RealmList<Task>) data;
//noinspection unchecked
list.addChangeListener(listener);
} else {
throw new IllegalArgumentException("RealmCollection not supported: " + data.getClass());
}
}
private void removeListener(@NonNull OrderedRealmCollection<Task> data) {
if (data instanceof RealmResults) {
RealmResults<Task> results = (RealmResults<Task>) data;
//noinspection unchecked
results.removeChangeListener(listener);
} else if (data instanceof RealmList) {
RealmList<Task> list = (RealmList<Task>) data;
//noinspection unchecked
list.removeChangeListener(listener);
} else {
throw new IllegalArgumentException("RealmCollection not supported: " + data.getClass());
}
}
private boolean isDataValid() {
return adapterData != null && adapterData.isValid();
}
public abstract class RealmBaseTasksRecyclerViewAdapter<VH extends BaseTaskViewHolder> extends RealmRecyclerViewAdapter<Task, VH> implements TaskRecyclerViewAdapter {
private final int layoutResource;
private final TaskFilterHelper taskFilterHelper;
private final OrderedRealmCollection<Task> unfilteredData;
public RealmBaseTasksRecyclerViewAdapter(@Nullable OrderedRealmCollection<Task> data, boolean autoUpdate, int layoutResource, @Nullable TaskFilterHelper taskFilterHelper) {
super(data, autoUpdate);
this.unfilteredData = data;
this.layoutResource = layoutResource;
this.taskFilterHelper = taskFilterHelper;
}
@Override
public void onBindViewHolder(VH holder, int position) {
Task item = getItem(position);
@ -36,6 +207,7 @@ public abstract class RealmBaseTasksRecyclerViewAdapter<VH extends BaseTaskViewH
}
}
View getContentView(ViewGroup parent) {
return getContentView(parent, layoutResource);
}
@ -56,4 +228,14 @@ public abstract class RealmBaseTasksRecyclerViewAdapter<VH extends BaseTaskViewH
}
}
@Override
public boolean getIgnoreUpdates() {
return ignoreUpdates;
}
@Override
public void setIgnoreUpdates(boolean ignoreUpdates) {
this.ignoreUpdates = ignoreUpdates;
}
}

View file

@ -76,6 +76,16 @@ public class RewardsRecyclerViewAdapter extends RecyclerView.Adapter<RecyclerVie
}
}
@Override
public void setIgnoreUpdates(boolean ignoreUpdates) {
}
@Override
public boolean getIgnoreUpdates() {
return false;
}
@Override
public int getItemCount() {
int rewardCount = getCustomRewardCount();

View file

@ -10,6 +10,10 @@ public interface TaskRecyclerViewAdapter {
void filter();
void notifyItemMoved(int adapterPosition, int adapterPosition1);
int getItemViewType(int position);
void setIgnoreUpdates(boolean ignoreUpdates);
boolean getIgnoreUpdates();
}

View file

@ -10,6 +10,7 @@ import android.support.v4.widget.SwipeRefreshLayout;
import android.support.v7.widget.DefaultItemAnimator;
import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.RecyclerView;
import android.support.v7.widget.SimpleItemAnimator;
import android.support.v7.widget.helper.ItemTouchHelper;
import android.util.TypedValue;
import android.view.LayoutInflater;
@ -36,7 +37,6 @@ import com.habitrpg.android.habitica.ui.adapter.tasks.RewardsRecyclerViewAdapter
import com.habitrpg.android.habitica.ui.adapter.tasks.TaskRecyclerViewAdapter;
import com.habitrpg.android.habitica.ui.adapter.tasks.TodosRecyclerViewAdapter;
import com.habitrpg.android.habitica.ui.fragments.BaseFragment;
import com.habitrpg.android.habitica.ui.helpers.ItemTouchHelperDropCallback;
import com.habitrpg.android.habitica.ui.helpers.RecyclerViewEmptySupport;
import com.habitrpg.android.habitica.ui.helpers.SafeDefaultItemAnimator;
@ -45,6 +45,7 @@ import org.greenrobot.eventbus.EventBus;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.concurrent.TimeUnit;
import javax.inject.Inject;
import javax.inject.Named;
@ -185,13 +186,16 @@ public class TaskRecyclerViewFragment extends BaseFragment implements View.OnCli
}
mItemTouchCallback = new ItemTouchHelper.Callback() {
private Integer mFromPosition = null;
private Integer fromPosition = null;
@Override
public void onSelectedChanged(RecyclerView.ViewHolder viewHolder, int actionState) {
super.onSelectedChanged(viewHolder, actionState);
if (viewHolder != null) {
viewHolder.itemView.setBackgroundColor(Color.LTGRAY);
if (fromPosition == null) {
fromPosition = viewHolder.getAdapterPosition();
}
}
if (swipeRefreshLayout != null) {
swipeRefreshLayout.setEnabled(false);
@ -199,10 +203,8 @@ public class TaskRecyclerViewFragment extends BaseFragment implements View.OnCli
}
public boolean onMove(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder, RecyclerView.ViewHolder target) {
if (mFromPosition == null) {
mFromPosition = viewHolder.getAdapterPosition();
}
taskRepository.swapTaskPosition(viewHolder.getAdapterPosition(), target.getAdapterPosition());
recyclerAdapter.notifyItemMoved(viewHolder.getAdapterPosition(), target.getAdapterPosition());
//taskRepository.swapTaskPosition(viewHolder.getAdapterPosition(), target.getAdapterPosition());
return true;
}
@ -217,6 +219,16 @@ public class TaskRecyclerViewFragment extends BaseFragment implements View.OnCli
ItemTouchHelper.DOWN | ItemTouchHelper.UP);
}
@Override
public boolean isItemViewSwipeEnabled() {
return false;
}
@Override
public boolean isLongPressDragEnabled() {
return true;
}
@Override
public void clearView(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder) {
super.clearView(recyclerView, viewHolder);
@ -224,12 +236,12 @@ public class TaskRecyclerViewFragment extends BaseFragment implements View.OnCli
swipeRefreshLayout.setEnabled(true);
}
if (viewHolder != null) {
viewHolder.itemView.setBackgroundColor(Color.WHITE);
}
if (mFromPosition != null) {
taskRepository.updateTaskPosition(viewHolder.getAdapterPosition())
.subscribe(taskPositions -> {}, RxErrorHandler.handleEmptyError());
if (fromPosition != null) {
recyclerAdapter.setIgnoreUpdates(true);
taskRepository.updateTaskPosition(fromPosition, viewHolder.getAdapterPosition())
.delay(2, TimeUnit.SECONDS)
.subscribe(taskPositions -> recyclerAdapter.setIgnoreUpdates(false), RxErrorHandler.handleEmptyError());
fromPosition = null;
}
}
};