From 032421873535936b771b525c63fe15a3dfd0a0ca Mon Sep 17 00:00:00 2001 From: Jakob Nixdorf Date: Fri, 30 Jun 2017 15:59:28 +0200 Subject: [PATCH] Re-implement reorder and delete (drag&drop and swipe) --- .idea/misc.xml | 2 +- README.md | 1 + .../flocke/andotp/EntriesCardAdapter.java | 73 +++++++++-- .../ItemTouchHelperAdapter.java | 58 +++++++++ .../ItemTouchHelperViewHolder.java | 41 ++++++ .../SimpleItemTouchHelperCallback.java | 123 ++++++++++++++++++ .../shadowice/flocke/andotp/MainActivity.java | 22 ++++ 7 files changed, 309 insertions(+), 11 deletions(-) create mode 100644 app/src/main/java/org/shadowice/flocke/andotp/ItemTouchHelper/ItemTouchHelperAdapter.java create mode 100644 app/src/main/java/org/shadowice/flocke/andotp/ItemTouchHelper/ItemTouchHelperViewHolder.java create mode 100644 app/src/main/java/org/shadowice/flocke/andotp/ItemTouchHelper/SimpleItemTouchHelperCallback.java diff --git a/.idea/misc.xml b/.idea/misc.xml index 7158618b..cca2cdae 100644 --- a/.idea/misc.xml +++ b/.idea/misc.xml @@ -37,7 +37,7 @@ - + diff --git a/README.md b/README.md index 4b30f127..c7ff06b9 100644 --- a/README.md +++ b/README.md @@ -39,6 +39,7 @@ goes to Bruno. * [Apache Commons Code](https://commons.apache.org/proper/commons-codec/) * [Code Parts from Google's Android Samples](https://android.googlesource.com/platform/development/+/master/samples/Vault/src/com/example/android/vault) + * [SimpleItemTouchHelperCallback](https://github.com/iPaulPro/Android-ItemTouchHelper-Demo/blob/master/app/src/main/java/co/paulburke/android/itemtouchhelperdemo/helper/SimpleItemTouchHelperCallback.java) from [Android-ItemTouchHelper-Demo](https://github.com/iPaulPro/Android-ItemTouchHelper-Demo) by Paul Burke * [MaterialProgressBar](https://github.com/DreaminginCodeZH/MaterialProgressBar) * [ZXing](https://github.com/zxing/zxing) * [ZXing Android Embedded](https://github.com/journeyapps/zxing-android-embedded) diff --git a/app/src/main/java/org/shadowice/flocke/andotp/EntriesCardAdapter.java b/app/src/main/java/org/shadowice/flocke/andotp/EntriesCardAdapter.java index ae158410..ef3c4e6f 100644 --- a/app/src/main/java/org/shadowice/flocke/andotp/EntriesCardAdapter.java +++ b/app/src/main/java/org/shadowice/flocke/andotp/EntriesCardAdapter.java @@ -28,13 +28,19 @@ import android.view.View; import android.view.ViewGroup; import android.widget.TextView; -import java.util.List; +import org.shadowice.flocke.andotp.ItemTouchHelper.ItemTouchHelperAdapter; +import org.shadowice.flocke.andotp.ItemTouchHelper.ItemTouchHelperViewHolder; -public class EntriesCardAdapter extends RecyclerView.Adapter { +import java.util.ArrayList; +import java.util.Collections; - private List entries; +public class EntriesCardAdapter extends RecyclerView.Adapter + implements ItemTouchHelperAdapter { - public EntriesCardAdapter(List entries) { + private ArrayList entries; + public MoveEventCallback moveEventCallback; + + public EntriesCardAdapter(ArrayList entries) { this.entries = entries; } @@ -50,28 +56,75 @@ public class EntriesCardAdapter extends RecyclerView.Adapter toPosition; i--) { + Collections.swap(entries, i, i - 1); + } + } + notifyItemMoved(fromPosition, toPosition); + + return true; + } + + public void setMoveEventCallback(MoveEventCallback cb) { + this.moveEventCallback = cb; + } + + public static class EntryViewHolder extends RecyclerView.ViewHolder + implements ItemTouchHelperViewHolder { + + private MoveEventCallback moveEventCallback; protected TextView OTPValue; protected TextView OTPLabel; public EntryViewHolder(View v) { super(v); - OTPValue = (TextView) v.findViewById(R.id.textViewOTP); - OTPLabel = (TextView) v.findViewById(R.id.textViewLabel); + + OTPValue = (TextView) v.findViewById(R.id.textViewOTP); + OTPLabel = (TextView) v.findViewById(R.id.textViewLabel); + } + + @Override + public void onItemSelected() { + if (moveEventCallback != null) + moveEventCallback.onMoveEventStart(); + } + + @Override + public void onItemClear() { + if (moveEventCallback != null) + moveEventCallback.onMoveEventStop(); } } + + public interface MoveEventCallback { + void onMoveEventStart(); + void onMoveEventStop(); + } } diff --git a/app/src/main/java/org/shadowice/flocke/andotp/ItemTouchHelper/ItemTouchHelperAdapter.java b/app/src/main/java/org/shadowice/flocke/andotp/ItemTouchHelper/ItemTouchHelperAdapter.java new file mode 100644 index 00000000..d7504656 --- /dev/null +++ b/app/src/main/java/org/shadowice/flocke/andotp/ItemTouchHelper/ItemTouchHelperAdapter.java @@ -0,0 +1,58 @@ +/* + * Copyright (C) 2015 Paul Burke + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.shadowice.flocke.andotp.ItemTouchHelper; + +import android.support.v7.widget.RecyclerView; +import android.support.v7.widget.helper.ItemTouchHelper; + +/** + * Interface to listen for a move or dismissal event from a {@link ItemTouchHelper.Callback}. + * + * @author Paul Burke (ipaulpro) + */ +public interface ItemTouchHelperAdapter { + + /** + * Called when an item has been dragged far enough to trigger a move. This is called every time + * an item is shifted, and not at the end of a "drop" event.
+ *
+ * Implementations should call {@link RecyclerView.Adapter#notifyItemMoved(int, int)} after + * adjusting the underlying data to reflect this move. + * + * @param fromPosition The start position of the moved item. + * @param toPosition Then resolved position of the moved item. + * @return True if the item was moved to the new adapter position. + * + * @see RecyclerView#getAdapterPositionFor(RecyclerView.ViewHolder) + * @see RecyclerView.ViewHolder#getAdapterPosition() + */ + boolean onItemMove(int fromPosition, int toPosition); + + + /** + * Called when an item has been dismissed by a swipe.
+ *
+ * Implementations should call {@link RecyclerView.Adapter#notifyItemRemoved(int)} after + * adjusting the underlying data to reflect this removal. + * + * @param position The position of the item dismissed. + * + * @see RecyclerView#getAdapterPositionFor(RecyclerView.ViewHolder) + * @see RecyclerView.ViewHolder#getAdapterPosition() + */ + void onItemDismiss(int position); +} \ No newline at end of file diff --git a/app/src/main/java/org/shadowice/flocke/andotp/ItemTouchHelper/ItemTouchHelperViewHolder.java b/app/src/main/java/org/shadowice/flocke/andotp/ItemTouchHelper/ItemTouchHelperViewHolder.java new file mode 100644 index 00000000..8051939e --- /dev/null +++ b/app/src/main/java/org/shadowice/flocke/andotp/ItemTouchHelper/ItemTouchHelperViewHolder.java @@ -0,0 +1,41 @@ +/* + * Copyright (C) 2015 Paul Burke + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.shadowice.flocke.andotp.ItemTouchHelper; + +import android.support.v7.widget.helper.ItemTouchHelper; + +/** + * Interface to notify an item ViewHolder of relevant callbacks from {@link + * android.support.v7.widget.helper.ItemTouchHelper.Callback}. + * + * @author Paul Burke (ipaulpro) + */ +public interface ItemTouchHelperViewHolder { + + /** + * Called when the {@link ItemTouchHelper} first registers an item as being moved or swiped. + * Implementations should update the item view to indicate it's active state. + */ + void onItemSelected(); + + + /** + * Called when the {@link ItemTouchHelper} has completed the move or swipe, and the active item + * state should be cleared. + */ + void onItemClear(); +} diff --git a/app/src/main/java/org/shadowice/flocke/andotp/ItemTouchHelper/SimpleItemTouchHelperCallback.java b/app/src/main/java/org/shadowice/flocke/andotp/ItemTouchHelper/SimpleItemTouchHelperCallback.java new file mode 100644 index 00000000..c495806c --- /dev/null +++ b/app/src/main/java/org/shadowice/flocke/andotp/ItemTouchHelper/SimpleItemTouchHelperCallback.java @@ -0,0 +1,123 @@ +/* + * Copyright (C) 2015 Paul Burke + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.shadowice.flocke.andotp.ItemTouchHelper; + +import android.graphics.Canvas; +import android.support.v7.widget.GridLayoutManager; +import android.support.v7.widget.RecyclerView; +import android.support.v7.widget.helper.ItemTouchHelper; + +/** + * An implementation of {@link ItemTouchHelper.Callback} that enables basic drag & drop and + * swipe-to-dismiss. Drag events are automatically started by an item long-press.
+ *
+ * Expects the RecyclerView.Adapter to listen for {@link + * ItemTouchHelperAdapter} callbacks and the RecyclerView.ViewHolder to implement + * {@link ItemTouchHelperViewHolder}. + * + * @author Paul Burke (ipaulpro) + */ +public class SimpleItemTouchHelperCallback extends ItemTouchHelper.Callback { + + public static final float ALPHA_FULL = 1.0f; + + private final ItemTouchHelperAdapter mAdapter; + + public SimpleItemTouchHelperCallback(ItemTouchHelperAdapter adapter) { + mAdapter = adapter; + } + + @Override + public boolean isLongPressDragEnabled() { + return true; + } + + @Override + public boolean isItemViewSwipeEnabled() { + return true; + } + + @Override + public int getMovementFlags(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder) { + // Set movement flags based on the layout manager + if (recyclerView.getLayoutManager() instanceof GridLayoutManager) { + final int dragFlags = ItemTouchHelper.UP | ItemTouchHelper.DOWN | ItemTouchHelper.LEFT | ItemTouchHelper.RIGHT; + final int swipeFlags = 0; + return makeMovementFlags(dragFlags, swipeFlags); + } else { + final int dragFlags = ItemTouchHelper.UP | ItemTouchHelper.DOWN; + final int swipeFlags = ItemTouchHelper.START | ItemTouchHelper.END; + return makeMovementFlags(dragFlags, swipeFlags); + } + } + + @Override + public boolean onMove(RecyclerView recyclerView, RecyclerView.ViewHolder source, RecyclerView.ViewHolder target) { + if (source.getItemViewType() != target.getItemViewType()) { + return false; + } + + // Notify the adapter of the move + mAdapter.onItemMove(source.getAdapterPosition(), target.getAdapterPosition()); + return true; + } + + @Override + public void onSwiped(RecyclerView.ViewHolder viewHolder, int i) { + // Notify the adapter of the dismissal + mAdapter.onItemDismiss(viewHolder.getAdapterPosition()); + } + + @Override + public void onChildDraw(Canvas c, RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder, float dX, float dY, int actionState, boolean isCurrentlyActive) { + if (actionState == ItemTouchHelper.ACTION_STATE_SWIPE) { + // Fade out the view as it is swiped out of the parent's bounds + final float alpha = ALPHA_FULL - Math.abs(dX) / (float) viewHolder.itemView.getWidth(); + viewHolder.itemView.setAlpha(alpha); + viewHolder.itemView.setTranslationX(dX); + } else { + super.onChildDraw(c, recyclerView, viewHolder, dX, dY, actionState, isCurrentlyActive); + } + } + + @Override + public void onSelectedChanged(RecyclerView.ViewHolder viewHolder, int actionState) { + // We only want the active item to change + if (actionState != ItemTouchHelper.ACTION_STATE_IDLE) { + if (viewHolder instanceof ItemTouchHelperViewHolder) { + // Let the view holder know that this item is being moved or dragged + ItemTouchHelperViewHolder itemViewHolder = (ItemTouchHelperViewHolder) viewHolder; + itemViewHolder.onItemSelected(); + } + } + + super.onSelectedChanged(viewHolder, actionState); + } + + @Override + public void clearView(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder) { + super.clearView(recyclerView, viewHolder); + + viewHolder.itemView.setAlpha(ALPHA_FULL); + + if (viewHolder instanceof ItemTouchHelperViewHolder) { + // Tell the view holder it's time to restore the idle state + ItemTouchHelperViewHolder itemViewHolder = (ItemTouchHelperViewHolder) viewHolder; + itemViewHolder.onItemClear(); + } + } +} diff --git a/app/src/main/java/org/shadowice/flocke/andotp/MainActivity.java b/app/src/main/java/org/shadowice/flocke/andotp/MainActivity.java index 3cbaf2bc..5fca8be1 100644 --- a/app/src/main/java/org/shadowice/flocke/andotp/MainActivity.java +++ b/app/src/main/java/org/shadowice/flocke/andotp/MainActivity.java @@ -40,6 +40,7 @@ import android.support.v7.app.AppCompatActivity; import android.support.v7.widget.LinearLayoutManager; import android.support.v7.widget.RecyclerView; import android.support.v7.widget.Toolbar; +import android.support.v7.widget.helper.ItemTouchHelper; import android.view.Menu; import android.view.MenuItem; import android.view.View; @@ -51,6 +52,8 @@ import android.widget.TextView; import com.google.zxing.client.android.Intents; import com.google.zxing.integration.android.IntentIntegrator; +import org.shadowice.flocke.andotp.ItemTouchHelper.SimpleItemTouchHelperCallback; + import java.util.ArrayList; public class MainActivity extends AppCompatActivity { @@ -157,6 +160,7 @@ public class MainActivity extends AppCompatActivity { }); final ProgressBar progressBar = (ProgressBar) findViewById(R.id.progressBar); + RecyclerView recList = (RecyclerView) findViewById(R.id.cardList); recList.setHasFixedSize(true); LinearLayoutManager llm = new LinearLayoutManager(this); @@ -168,6 +172,24 @@ public class MainActivity extends AppCompatActivity { adapter = new EntriesCardAdapter(entries); recList.setAdapter(adapter); + ItemTouchHelper.Callback callback = new SimpleItemTouchHelperCallback(adapter); + ItemTouchHelper touchHelper = new ItemTouchHelper(callback); + touchHelper.attachToRecyclerView(recList); + + adapter.setMoveEventCallback(new EntriesCardAdapter.MoveEventCallback() { + @Override + public void onMoveEventStart() { + stopUpdater(); + } + + @Override + public void onMoveEventStop() { + startUpdater(); + + SettingsHelper.store(getBaseContext(), entries); + } + }); + if(entries.isEmpty()){ showNoAccount(); }