Re-implement reorder and delete (drag&drop and swipe)

This commit is contained in:
Jakob Nixdorf 2017-06-30 15:59:28 +02:00
parent d7df8fcc19
commit 0324218735
No known key found for this signature in database
GPG key ID: BE99BF86574A7DBC
7 changed files with 309 additions and 11 deletions

View file

@ -37,7 +37,7 @@
<ConfirmationsSetting value="0" id="Add" />
<ConfirmationsSetting value="0" id="Remove" />
</component>
<component name="ProjectRootManager" version="2" languageLevel="JDK_1_7" default="true" assert-keyword="true" jdk-15="true" project-jdk-name="1.8" project-jdk-type="JavaSDK">
<component name="ProjectRootManager" version="2" languageLevel="JDK_1_8" default="true" assert-keyword="true" jdk-15="true" project-jdk-name="1.8" project-jdk-type="JavaSDK">
<output url="file://$PROJECT_DIR$/build/classes" />
</component>
<component name="ProjectType">

View file

@ -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)

View file

@ -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<EntriesCardAdapter.EntryViewHolder> {
import java.util.ArrayList;
import java.util.Collections;
private List<Entry> entries;
public class EntriesCardAdapter extends RecyclerView.Adapter<EntriesCardAdapter.EntryViewHolder>
implements ItemTouchHelperAdapter {
public EntriesCardAdapter(List<Entry> entries) {
private ArrayList<Entry> entries;
public MoveEventCallback moveEventCallback;
public EntriesCardAdapter(ArrayList<Entry> entries) {
this.entries = entries;
}
@ -50,28 +56,75 @@ public class EntriesCardAdapter extends RecyclerView.Adapter<EntriesCardAdapter.
@Override
public void onBindViewHolder(EntryViewHolder entryViewHolder, int i) {
Entry entry = entries.get(i);
entryViewHolder.OTPValue.setText(entry.getCurrentOTP());
entryViewHolder.OTPLabel.setText(entry.getLabel());
entryViewHolder.moveEventCallback = moveEventCallback;
}
@Override
public EntryViewHolder onCreateViewHolder(ViewGroup viewGroup, int i) {
View itemView = LayoutInflater.
from(viewGroup.getContext()).
inflate(R.layout.card_layout, viewGroup, false);
View itemView = LayoutInflater.from(viewGroup.getContext()).inflate(R.layout.card_layout, viewGroup, false);
return new EntryViewHolder(itemView);
}
public static class EntryViewHolder extends RecyclerView.ViewHolder {
@Override
public void onItemDismiss(int position) {
entries.remove(position);
notifyItemRemoved(position);
}
@Override
public boolean onItemMove(int fromPosition, int toPosition) {
if (fromPosition < toPosition) {
for (int i = fromPosition; i < toPosition; i++) {
Collections.swap(entries, i, i + 1);
}
} else {
for (int i = fromPosition; i > 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);
}
@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();
}
}

View file

@ -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 <strong>not</strong> at the end of a "drop" event.<br/>
* <br/>
* 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.<br/>
* <br/>
* 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);
}

View file

@ -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();
}

View file

@ -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.<br/>
* </br/>
* Expects the <code>RecyclerView.Adapter</code> to listen for {@link
* ItemTouchHelperAdapter} callbacks and the <code>RecyclerView.ViewHolder</code> 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();
}
}
}

View file

@ -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();
}