Improve usability of drag-and-drop feature

This commit is contained in:
Connor Lim 2020-08-07 00:20:38 +08:00
parent c73c29d2ec
commit 5886462d2c
8 changed files with 177 additions and 43 deletions

View file

@ -43,7 +43,7 @@ public class SimpleItemTouchHelperCallback extends ItemTouchHelper.Callback {
int position = viewHolder.getAdapterPosition(); int position = viewHolder.getAdapterPosition();
EntryAdapter adapter = (EntryAdapter)recyclerView.getAdapter(); EntryAdapter adapter = (EntryAdapter)recyclerView.getAdapter();
if (adapter.getEntryAt(position) != _selectedEntry) if (adapter.getEntryAt(position) != _selectedEntry || !isLongPressDragEnabled())
{ {
dragFlags = 0; dragFlags = 0;
} }

View file

@ -2,6 +2,7 @@ package com.beemdevelopment.aegis.ui.views;
import android.os.Handler; import android.os.Handler;
import android.view.LayoutInflater; import android.view.LayoutInflater;
import android.view.MotionEvent;
import android.view.View; import android.view.View;
import android.view.ViewGroup; import android.view.ViewGroup;
@ -49,6 +50,7 @@ public class EntryAdapter extends RecyclerView.Adapter<EntryHolder> implements I
// keeps track of the viewholders that are currently bound // keeps track of the viewholders that are currently bound
private List<EntryHolder> _holders; private List<EntryHolder> _holders;
private EntryHolder _dragHandleHolder; // holder with enabled drag handle
public EntryAdapter(EntryListView view) { public EntryAdapter(EntryListView view) {
_entries = new ArrayList<>(); _entries = new ArrayList<>();
@ -386,7 +388,29 @@ public class EntryAdapter extends RecyclerView.Adapter<EntryHolder> implements I
holder.setFocusedAndAnimate(true); holder.setFocusedAndAnimate(true);
} }
return _view.onLongEntryClick(_shownEntries.get(position)); boolean returnVal = _view.onLongEntryClick(_shownEntries.get(position));
boolean dragEnabled = _selectedEntries.size() == 0
|| _selectedEntries.size() == 1 && _selectedEntries.get(0) == holder.getEntry();
if (dragEnabled && isDragAndDropAllowed()) {
_view.startDrag(_dragHandleHolder);
}
return returnVal;
}
});
holder.itemView.setOnTouchListener(new View.OnTouchListener() {
@Override
public boolean onTouch(View v, MotionEvent event) {
// Start drag if this is the only item selected
if (event.getActionMasked() == MotionEvent.ACTION_MOVE
&& _selectedEntries.size() == 1
&& _selectedEntries.get(0) == holder.getEntry()
&& isDragAndDropAllowed()) {
_view.startDrag(_dragHandleHolder);
return true;
}
return false;
} }
}); });
holder.setOnRefreshClickListener(new View.OnClickListener() { holder.setOnRefreshClickListener(new View.OnClickListener() {
@ -508,8 +532,33 @@ public class EntryAdapter extends RecyclerView.Adapter<EntryHolder> implements I
_focusedEntry = null; _focusedEntry = null;
} }
private void updateDraggableStatus() {
if (!isDragAndDropAllowed()) {
return;
}
if (_selectedEntries.size() == 1 && _dragHandleHolder == null) {
// Find and enable dragging for the single selected EntryHolder
// Not nice but this is the best method I could find
for (int i = 0; i < _holders.size(); i++) {
if (_holders.get(i).getEntry() == _selectedEntries.get(0)) {
_dragHandleHolder = _holders.get(i);
_dragHandleHolder.setShowDragHandle(true);
_view.setSelectedEntry(_selectedEntries.get(0));
return;
}
}
} else if (_dragHandleHolder != null) {
// Disable dragging if necessary when more/less than 1 selected entry
_dragHandleHolder.setShowDragHandle(false);
_dragHandleHolder = null;
_view.setSelectedEntry(null);
}
}
public void removeSelectedEntry(VaultEntry entry) { public void removeSelectedEntry(VaultEntry entry) {
_selectedEntries.remove(entry); _selectedEntries.remove(entry);
updateDraggableStatus();
} }
public void addSelectedEntry(VaultEntry entry) { public void addSelectedEntry(VaultEntry entry) {
@ -518,6 +567,7 @@ public class EntryAdapter extends RecyclerView.Adapter<EntryHolder> implements I
} }
_selectedEntries.add(entry); _selectedEntries.add(entry);
updateDraggableStatus();
} }
public void deselectAllEntries() { public void deselectAllEntries() {
@ -531,6 +581,8 @@ public class EntryAdapter extends RecyclerView.Adapter<EntryHolder> implements I
} }
_selectedEntries.clear(); _selectedEntries.clear();
updateDraggableStatus();
} }
public boolean isDragAndDropAllowed() { public boolean isDragAndDropAllowed() {

View file

@ -39,6 +39,7 @@ public class EntryHolder extends RecyclerView.ViewHolder {
private VaultEntry _entry; private VaultEntry _entry;
private ImageView _buttonRefresh; private ImageView _buttonRefresh;
private RelativeLayout _description; private RelativeLayout _description;
private ImageView _dragHandle;
private final ImageView _selected; private final ImageView _selected;
private final Handler _selectedHandler; private final Handler _selectedHandler;
@ -69,6 +70,8 @@ public class EntryHolder extends RecyclerView.ViewHolder {
_profileDrawable = view.findViewById(R.id.ivTextDrawable); _profileDrawable = view.findViewById(R.id.ivTextDrawable);
_buttonRefresh = view.findViewById(R.id.buttonRefresh); _buttonRefresh = view.findViewById(R.id.buttonRefresh);
_selected = view.findViewById(R.id.ivSelected); _selected = view.findViewById(R.id.ivSelected);
_dragHandle = view.findViewById(R.id.drag_handle);
_selectedHandler = new Handler(); _selectedHandler = new Handler();
_animationHandler = new Handler(); _animationHandler = new Handler();
@ -158,6 +161,14 @@ public class EntryHolder extends RecyclerView.ViewHolder {
_buttonRefresh.setOnClickListener(listener); _buttonRefresh.setOnClickListener(listener);
} }
public void setShowDragHandle(boolean showDragHandle) {
if (showDragHandle) {
_dragHandle.setVisibility(View.VISIBLE);
} else {
_dragHandle.setVisibility(View.INVISIBLE);
}
}
public void setShowProgress(boolean showProgress) { public void setShowProgress(boolean showProgress) {
if (_entry.getInfo() instanceof HotpInfo) { if (_entry.getInfo() instanceof HotpInfo) {
showProgress = false; showProgress = false;

View file

@ -42,6 +42,7 @@ public class EntryListView extends Fragment implements EntryAdapter.Listener {
private EntryAdapter _adapter; private EntryAdapter _adapter;
private Listener _listener; private Listener _listener;
private SimpleItemTouchHelperCallback _touchCallback; private SimpleItemTouchHelperCallback _touchCallback;
private ItemTouchHelper _touchHelper;
private RecyclerView _recyclerView; private RecyclerView _recyclerView;
private RecyclerView.ItemDecoration _dividerDecoration; private RecyclerView.ItemDecoration _dividerDecoration;
@ -90,8 +91,8 @@ public class EntryListView extends Fragment implements EntryAdapter.Listener {
LinearLayoutManager layoutManager = new LinearLayoutManager(view.getContext()); LinearLayoutManager layoutManager = new LinearLayoutManager(view.getContext());
_recyclerView.setLayoutManager(layoutManager); _recyclerView.setLayoutManager(layoutManager);
_touchCallback = new SimpleItemTouchHelperCallback(_adapter); _touchCallback = new SimpleItemTouchHelperCallback(_adapter);
ItemTouchHelper touchHelper = new ItemTouchHelper(_touchCallback); _touchHelper = new ItemTouchHelper(_touchCallback);
touchHelper.attachToRecyclerView(_recyclerView); _touchHelper.attachToRecyclerView(_recyclerView);
_recyclerView.setAdapter(_adapter); _recyclerView.setAdapter(_adapter);
int resId = R.anim.layout_animation_fall_down; int resId = R.anim.layout_animation_fall_down;
@ -167,12 +168,20 @@ public class EntryListView extends Fragment implements EntryAdapter.Listener {
_touchCallback.setIsLongPressDragEnabled(_adapter.isDragAndDropAllowed()); _touchCallback.setIsLongPressDragEnabled(_adapter.isDragAndDropAllowed());
} }
public void setSelectedEntry(VaultEntry entry) {
_touchCallback.setSelectedEntry(entry);
}
public void setViewMode(ViewMode mode) { public void setViewMode(ViewMode mode) {
_viewMode = mode; _viewMode = mode;
updateDividerDecoration(); updateDividerDecoration();
_adapter.setViewMode(_viewMode); _adapter.setViewMode(_viewMode);
} }
public void startDrag(RecyclerView.ViewHolder viewHolder) {
_touchHelper.startDrag(viewHolder);
}
public void refresh(boolean hard) { public void refresh(boolean hard) {
if (_showProgress) { if (_showProgress) {
_progressBar.restart(); _progressBar.restart();

View file

@ -0,0 +1,5 @@
<vector android:height="32dp" android:tint="?attr/colorControlNormal"
android:viewportHeight="24" android:viewportWidth="24"
android:width="32dp" xmlns:android="http://schemas.android.com/apk/res/android">
<path android:fillColor="@android:color/white" android:pathData="M3,18h18v-2L3,16v2zM3,13h18v-2L3,11v2zM3,6v2h18L21,6L3,6z"/>
</vector>

View file

@ -129,20 +129,39 @@
android:textStyle="normal|bold"/> android:textStyle="normal|bold"/>
</RelativeLayout> </RelativeLayout>
<ImageView
android:id="@+id/buttonRefresh" <LinearLayout
android:visibility="gone" android:orientation="horizontal"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="match_parent">
android:layout_gravity="center_vertical"
android:layout_marginStart="16dp" <ImageView
android:layout_marginEnd="16dp" android:id="@+id/buttonRefresh"
android:padding="8dp" android:visibility="gone"
android:clickable="true" android:layout_width="wrap_content"
android:focusable="true" android:layout_height="wrap_content"
android:src="@drawable/ic_refresh_black_24dp" android:layout_gravity="center_vertical"
android:tint="?attr/iconColorPrimary" android:layout_marginStart="16dp"
android:background="?android:attr/selectableItemBackground" /> android:layout_marginEnd="4dp"
android:padding="8dp"
android:clickable="true"
android:focusable="true"
android:src="@drawable/ic_refresh_black_24dp"
android:tint="?attr/iconColorPrimary"
android:background="?android:attr/selectableItemBackground" />
<ImageView
android:id="@+id/drag_handle"
android:layout_width="24dp"
android:layout_height="24dp"
android:layout_gravity="center_vertical"
android:layout_marginEnd="-12dp"
android:visibility="invisible"
android:scaleType="fitXY"
android:src="@drawable/ic_baseline_menu_black_32" />
</LinearLayout>
</LinearLayout> </LinearLayout>
<LinearLayout <LinearLayout

View file

@ -128,20 +128,39 @@
android:textStyle="normal|bold"/> android:textStyle="normal|bold"/>
</RelativeLayout> </RelativeLayout>
<ImageView
android:id="@+id/buttonRefresh" <LinearLayout
android:visibility="gone" android:orientation="horizontal"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="match_parent">
android:layout_gravity="center_vertical"
android:layout_marginStart="8dp" <ImageView
android:layout_marginEnd="8dp" android:id="@+id/buttonRefresh"
android:padding="8dp" android:visibility="gone"
android:clickable="true" android:layout_width="wrap_content"
android:focusable="true" android:layout_height="wrap_content"
android:src="@drawable/ic_refresh_black_24dp" android:layout_gravity="center_vertical"
android:tint="?attr/iconColorPrimary" android:layout_marginStart="8dp"
android:background="?android:attr/selectableItemBackground" /> android:layout_marginEnd="0dp"
android:padding="8dp"
android:clickable="true"
android:focusable="true"
android:src="@drawable/ic_refresh_black_24dp"
android:tint="?attr/iconColorPrimary"
android:background="?android:attr/selectableItemBackground" />
<ImageView
android:id="@+id/drag_handle"
android:layout_width="24dp"
android:layout_height="24dp"
android:layout_gravity="center_vertical"
android:layout_marginEnd="-12dp"
android:visibility="invisible"
android:scaleType="fitXY"
android:src="@drawable/ic_baseline_menu_black_32" />
</LinearLayout>
</LinearLayout> </LinearLayout>
<LinearLayout <LinearLayout

View file

@ -127,20 +127,39 @@
android:textStyle="normal|bold"/> android:textStyle="normal|bold"/>
</RelativeLayout> </RelativeLayout>
<ImageView
android:id="@+id/buttonRefresh" <LinearLayout
android:visibility="gone" android:orientation="horizontal"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="match_parent">
android:layout_gravity="center_vertical"
android:layout_marginStart="8dp" <ImageView
android:layout_marginEnd="8dp" android:id="@+id/buttonRefresh"
android:padding="8dp" android:visibility="gone"
android:clickable="true" android:layout_width="wrap_content"
android:focusable="true" android:layout_height="wrap_content"
android:src="@drawable/ic_refresh_black_24dp" android:layout_gravity="center_vertical"
android:tint="?attr/iconColorPrimary" android:layout_marginStart="8dp"
android:background="?android:attr/selectableItemBackground" /> android:layout_marginEnd="0dp"
android:padding="8dp"
android:clickable="true"
android:focusable="true"
android:src="@drawable/ic_refresh_black_24dp"
android:tint="?attr/iconColorPrimary"
android:background="?android:attr/selectableItemBackground" />
<ImageView
android:id="@+id/drag_handle"
android:layout_width="24dp"
android:layout_height="24dp"
android:layout_gravity="center_vertical"
android:layout_marginEnd="-12dp"
android:visibility="invisible"
android:scaleType="fitXY"
android:src="@drawable/ic_baseline_menu_black_32" />
</LinearLayout>
</LinearLayout> </LinearLayout>
<LinearLayout <LinearLayout