mirror of
https://github.com/sudoxnym/habitica-android.git
synced 2026-04-14 19:56:32 +00:00
fix task reordering
This commit is contained in:
parent
a055fb8938
commit
c8c828ea7e
8 changed files with 251 additions and 25 deletions
|
|
@ -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);
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -35,4 +35,6 @@ public interface TaskLocalRepository extends BaseLocalRepository {
|
|||
Observable<Task> getTaskAtPosition(int currentPosition);
|
||||
|
||||
Observable<TaskList> updateIsdue(TaskList daily);
|
||||
|
||||
void updateTaskPositions(List<String> taskOrder);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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());
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
|
|
|||
Loading…
Reference in a new issue