Skip to content

Commit

Permalink
fix(android): edit move issues with ListView/TableView (#13120)
Browse files Browse the repository at this point in the history
Fixes TIMOB-28552, TIMOB-28553, TIMOB-28554, TIMOB-28555
  • Loading branch information
jquick-axway committed Oct 20, 2021
1 parent 18b4eaf commit e525889
Show file tree
Hide file tree
Showing 9 changed files with 204 additions and 54 deletions.
Expand Up @@ -65,7 +65,6 @@ public class TableViewProxy extends RecyclerViewProxy
private static final String TAG = "TableViewProxy";

private final List<TableViewSectionProxy> sections = new ArrayList<>();

private KrollDict contentOffset = null;

public TableViewProxy()
Expand Down Expand Up @@ -248,38 +247,65 @@ public void swipeItem(int adapterIndex)
*
* @param fromAdapterIndex Index of item in adapter.
* @param toAdapterIndex Index of item in adapter.
* @return
* Returns adapter index the item was moved to after updating adapter list,
* which might not match given "toAdapterIndex" if moved to an empty section placeholder.
* <p/>
* Returns -1 if item was not moved. Can happen if indexes are invalid or if move to destination is not allowed.
*/
public void moveItem(int fromAdapterIndex, int toAdapterIndex)
public int moveItem(int fromAdapterIndex, int toAdapterIndex)
{
final TiTableView tableView = getTableView();

if (tableView != null) {
final TableViewRowProxy fromItem = tableView.getAdapterItem(fromAdapterIndex);
final TableViewSectionProxy fromSection = (TableViewSectionProxy) fromItem.getParent();
final TableViewRowProxy toItem = tableView.getAdapterItem(toAdapterIndex);
final TableViewSectionProxy toSection = (TableViewSectionProxy) toItem.getParent();
final int toIndex = toItem.getIndexInSection();

fromSection.remove(fromItem);
toSection.add(toIndex, fromItem);

update();
final TiViewProxy parentProxy = toItem.getParent();
if (parentProxy instanceof TableViewSectionProxy) {
final TableViewSectionProxy toSection = (TableViewSectionProxy) parentProxy;
final int toIndex = Math.max(toItem.getIndexInSection(), 0);
fromSection.remove(fromItem);
toSection.add(toIndex, fromItem);
update();
return tableView.getAdapterIndex(fromItem);
}
}
return -1;
}

/**
* Fire `move` event upon finalized movement of an item.
* Called when row drag-and-drop movement is about to start.
*
* @param fromAdapterIndex Index of item in adapter.
* @param adapterIndex Index of row in adapter that is about to be moved.
* @return Returns true if row movement is allowed. Returns false to prevent row movement.
*/
public void fireMoveEvent(int fromAdapterIndex)
public boolean onMoveItemStarting(int adapterIndex)
{
final TiTableView tableView = getTableView();
if ((tableView != null) && (adapterIndex >= 0)) {
final TableViewRowProxy rowProxy = tableView.getAdapterItem(adapterIndex);
if ((rowProxy != null) && (rowProxy.getParent() instanceof TableViewSectionProxy)) {
return true;
}
}
return false;
}

if (tableView != null) {
final TableViewRowProxy fromItem = tableView.getAdapterItem(fromAdapterIndex);

fromItem.fireEvent(TiC.EVENT_MOVE, null);
/**
* Called when row drag-and-drop movement has ended.
*
* @param adapterIndex Index of position the row was dragged in adapter list.
*/
public void onMoveItemEnded(int adapterIndex)
{
// Fire a "move" event.
final TiTableView tableView = getTableView();
if ((tableView != null) && (adapterIndex >= 0)) {
final TableViewRowProxy rowProxy = tableView.getAdapterItem(adapterIndex);
if (rowProxy != null) {
rowProxy.fireEvent(TiC.EVENT_MOVE, null);
}
}
}

Expand Down
Expand Up @@ -21,7 +21,6 @@
import android.view.ViewParent;

import androidx.annotation.NonNull;
import androidx.core.util.Pair;
import androidx.recyclerview.widget.ItemTouchHelper;
import androidx.recyclerview.widget.RecyclerView;

Expand All @@ -37,8 +36,7 @@ public class ItemTouchHandler extends ItemTouchHelper.SimpleCallback

private TiRecyclerViewAdapter adapter;
private RecyclerViewProxy recyclerViewProxy;
private Pair<Integer, Integer> movePair = null;

private int moveEndIndex = -1;
private Drawable icon;
private final ColorDrawable background;

Expand All @@ -59,14 +57,10 @@ public ItemTouchHandler(@NonNull TiRecyclerViewAdapter adapter,
public boolean onTouch(View v, MotionEvent event)
{
if (event.getAction() == MotionEvent.ACTION_UP) {

// Only fire `move` event upon final movement location.

if (movePair != null) {
final int fromIndex = movePair.first;

recyclerViewProxy.fireMoveEvent(fromIndex);
movePair = null;
if (moveEndIndex >= 0) {
// Notify owner that item movement has ended. Will fire a "move" event.
recyclerViewProxy.onMoveItemEnded(moveEndIndex);
moveEndIndex = -1;
}
}
return false;
Expand Down Expand Up @@ -274,12 +268,26 @@ public boolean onMove(@NonNull RecyclerView recyclerView,
@NonNull RecyclerView.ViewHolder fromHolder,
@NonNull RecyclerView.ViewHolder toHolder)
{
// Fetch index positions of items to swap.
final int fromIndex = fromHolder.getAdapterPosition();
final int toIndex = toHolder.getAdapterPosition();

movePair = new Pair<>(fromIndex, toIndex);
this.recyclerViewProxy.moveItem(fromIndex, toIndex);
return true;
// Notify owner if this is the start of item movement.
if (this.moveEndIndex < 0) {
boolean canMove = this.recyclerViewProxy.onMoveItemStarting(fromIndex);
if (!canMove) {
return false;
}
}

// Swap items and store destination index position which is needed by onMoveItemEnded() call.
// Note: "fromIndex" and "toIndex" will become invalid if item was moved into an empty placeholder section.
// This causes placeholder to be removed and adapter collection length to be reduced by one.
int newToIndex = this.recyclerViewProxy.moveItem(fromIndex, toIndex);
if (newToIndex >= 0) {
this.moveEndIndex = newToIndex;
}
return (newToIndex >= 0);
}

/**
Expand Down
Expand Up @@ -132,9 +132,9 @@ public Object handleEvent(String eventName, Object data, boolean fireItemClick)
final ListSectionProxy section = (ListSectionProxy) parent;

// Include section specific properties.
payload.put(TiC.PROPERTY_SECTION, section);
payload.put(TiC.PROPERTY_SECTION_INDEX, listViewProxy.getIndexOfSection(section));
payload.put(TiC.PROPERTY_ITEM_INDEX, getIndexInSection());
payload.putIfAbsent(TiC.PROPERTY_SECTION, section);
payload.putIfAbsent(TiC.PROPERTY_SECTION_INDEX, listViewProxy.getIndexOfSection(section));
payload.putIfAbsent(TiC.PROPERTY_ITEM_INDEX, getIndexInSection());
}

final Object itemId = getProperties().get(TiC.PROPERTY_ITEM_ID);
Expand Down
Expand Up @@ -62,6 +62,7 @@ public class ListViewProxy extends RecyclerViewProxy
private List<ListSectionProxy> sections = new ArrayList<>();
private HashMap<Integer, Set<Integer>> markers = new HashMap<>();
private KrollDict contentOffset = null;
private final MoveEventInfo moveEventInfo = new MoveEventInfo();

public ListViewProxy()
{
Expand Down Expand Up @@ -160,11 +161,12 @@ public void swipeItem(int adapterIndex)

if (listView != null) {
final ListItemProxy item = listView.getAdapterItem(adapterIndex);
final ListSectionProxy section = (ListSectionProxy) item.getParent();

item.fireSyncEvent(TiC.EVENT_DELETE, null);

section.deleteItemsAt(item.getIndexInSection(), 1, null);
final TiViewProxy parentProxy = item.getParent();
if (parentProxy instanceof ListSectionProxy) {
final ListSectionProxy section = (ListSectionProxy) parentProxy;
item.fireSyncEvent(TiC.EVENT_DELETE, null);
section.deleteItemsAt(item.getIndexInSection(), 1, null);
}
}
}

Expand All @@ -173,8 +175,13 @@ public void swipeItem(int adapterIndex)
*
* @param fromAdapterIndex Index of item in adapter.
* @param toAdapterIndex Index of item in adapter.
* @return
* Returns adapter index the item was moved to after updating adapter list,
* which might not match given "toAdapterIndex" if moved to an empty section placeholder.
* <p/>
* Returns -1 if item was not moved. Can happen if indexes are invalid or if move to destination is not allowed.
*/
public void moveItem(int fromAdapterIndex, int toAdapterIndex)
public int moveItem(int fromAdapterIndex, int toAdapterIndex)
{
final TiListView listView = getListView();

Expand All @@ -183,28 +190,71 @@ public void moveItem(int fromAdapterIndex, int toAdapterIndex)
final ListSectionProxy fromSection = (ListSectionProxy) fromItem.getParent();
final int fromIndex = fromItem.getIndexInSection();
final ListItemProxy toItem = listView.getAdapterItem(toAdapterIndex);
final ListSectionProxy toSection = (ListSectionProxy) toItem.getParent();
final int toIndex = toItem.getIndexInSection();

fromSection.deleteItemsAt(fromIndex, 1, null);
toSection.insertItemsAt(toIndex, fromItem, null);
final TiViewProxy parentProxy = toItem.getParent();
if (parentProxy instanceof ListSectionProxy) {
final ListSectionProxy toSection = (ListSectionProxy) parentProxy;
final int toIndex = Math.max(toItem.getIndexInSection(), 0);
fromSection.deleteItemsAt(fromIndex, 1, null);
toSection.insertItemsAt(toIndex, fromItem, null);
return listView.getAdapterIndex(fromItem);
}
}
return -1;
}

/**
* Fire `move` event upon finalized movement of an item.
* Called when item drag-and-drop movement is about to start.
*
* @param fromAdapterIndex Index of item in adapter.
* @param adapterIndex Index of item in adapter that is about to be moved.
* @return Returns true if item movement is allowed. Returns false to prevent item movement.
*/
public void fireMoveEvent(int fromAdapterIndex)
public boolean onMoveItemStarting(int adapterIndex)
{
final TiListView listView = getListView();
if ((listView != null) && (adapterIndex >= 0)) {
final ListItemProxy itemProxy = listView.getAdapterItem(adapterIndex);
if (itemProxy != null) {
final TiViewProxy parentProxy = itemProxy.getParent();
if (parentProxy instanceof ListSectionProxy) {
this.moveEventInfo.sectionProxy = (ListSectionProxy) parentProxy;
this.moveEventInfo.sectionIndex = getIndexOfSection(this.moveEventInfo.sectionProxy);
this.moveEventInfo.itemIndex = itemProxy.getIndexInSection();
return true;
}
}
}
return false;
}

if (listView != null) {
final ListItemProxy fromItem = listView.getAdapterItem(fromAdapterIndex);

fromItem.fireEvent(TiC.EVENT_MOVE, null);
/**
* Called when item drag-and-drop movement has ended.
*
* @param adapterIndex Index of position the item was dragged in adapter list.
*/
public void onMoveItemEnded(int adapterIndex)
{
// Fire a "move" event.
final TiListView listView = getListView();
if ((listView != null) && this.moveEventInfo.isMoving()) {
final ListItemProxy targetItemProxy = listView.getAdapterItem(adapterIndex);
if (targetItemProxy != null) {
final TiViewProxy targetParentProxy = targetItemProxy.getParent();
if (targetParentProxy instanceof ListSectionProxy) {
ListSectionProxy targetSectionProxy = (ListSectionProxy) targetParentProxy;
KrollDict data = new KrollDict();
data.put(TiC.PROPERTY_SECTION, this.moveEventInfo.sectionProxy);
data.put(TiC.PROPERTY_SECTION_INDEX, this.moveEventInfo.sectionIndex);
data.put(TiC.PROPERTY_ITEM_INDEX, this.moveEventInfo.itemIndex);
data.put(TiC.PROPERTY_TARGET_SECTION, targetSectionProxy);
data.put(TiC.PROPERTY_TARGET_SECTION_INDEX, getIndexOfSection(targetSectionProxy));
data.put(TiC.PROPERTY_TARGET_ITEM_INDEX, targetItemProxy.getIndexInSection());
targetItemProxy.fireEvent(TiC.EVENT_MOVE, data);
}
}
}

// Clear last "move" event info.
this.moveEventInfo.clear();
}

/**
Expand Down Expand Up @@ -848,4 +898,34 @@ public void update()
listView.update();
}
}

/** Stores starting position info of an item being dragged-and-dropped. */
private static class MoveEventInfo
{
/** Section proxy the item being dragged originally belonged to. */
public ListSectionProxy sectionProxy;

/** Index of section in list the item being dragged originally belonged to. */
public int sectionIndex = -1;

/** Original index position of the item being dragged. */
public int itemIndex = -1;

/**
* Determines if this object contains start position info.
* @return Returns true if start position info is stored. Returns false if not.
*/
public boolean isMoving()
{
return (this.itemIndex >= 0);
}

/** Clears start position info. Should be called at end of drag-and-drop event. */
public void clear()
{
this.sectionProxy = null;
this.sectionIndex = -1;
this.itemIndex = -1;
}
}
}
Expand Up @@ -14,7 +14,9 @@ public abstract class RecyclerViewProxy extends TiViewProxy
{
public abstract void swipeItem(int index);

public abstract void moveItem(int from, int to);
public abstract int moveItem(int fromIndex, int toIndex);

public abstract void fireMoveEvent(int from);
public abstract boolean onMoveItemStarting(int index);

public abstract void onMoveItemEnded(int index);
}
Expand Up @@ -502,6 +502,17 @@ public int getAdapterIndex(int index)
return -1;
}

/**
* Obtains adapter index from list item reference.
*
* @param itemProxy The list item to search for by reference. Can be null.
* @return Returns the adapter index position of the given item. Returns -1 if not found.
*/
public int getAdapterIndex(ListItemProxy itemProxy)
{
return this.items.indexOf(itemProxy);
}

/**
* Obtain item from adapter index.
*
Expand Down Expand Up @@ -692,7 +703,7 @@ public void update()
item.getProperties().put(TiC.PROPERTY_FOOTER_VIEW,
sectionProperties.get(TiC.PROPERTY_FOOTER_VIEW));

item.setParent(this.proxy);
item.setParent(section);
this.items.add(item);
}
}
Expand Down
Expand Up @@ -489,6 +489,17 @@ public int getAdapterIndex(int index)
return -1;
}

/**
* Obtains adapter index from list item reference.
*
* @param rowProxy The row object to search for by reference. Can be null.
* @return Returns the adapter index position of the given row. Returns -1 if not found.
*/
public int getAdapterIndex(TableViewRowProxy rowProxy)
{
return this.rows.indexOf(rowProxy);
}

/**
* Obtain row from adapter index.
*
Expand Down

0 comments on commit e525889

Please sign in to comment.