Skip to content

Commit c3067d7

Browse files
committed
Move GestureHandler to SDK including some renaming
1 parent 10a0cbd commit c3067d7

File tree

3 files changed

+190
-77
lines changed

3 files changed

+190
-77
lines changed
Lines changed: 130 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,130 @@
1+
package io.snabble.sdk.ui
2+
3+
import android.content.res.Resources
4+
import android.graphics.*
5+
import androidx.core.content.res.ResourcesCompat
6+
import androidx.recyclerview.widget.ItemTouchHelper
7+
import androidx.recyclerview.widget.RecyclerView
8+
import com.google.android.material.color.MaterialColors
9+
import kotlin.math.abs
10+
11+
abstract class GestureHandler<T>(resources: Resources) : ItemTouchHelper.SimpleCallback(0, 0) {
12+
private lateinit var itemTouchHelper: ItemTouchHelper
13+
private val icon =
14+
requireNotNull(
15+
ResourcesCompat.getDrawable(
16+
resources,
17+
R.drawable.snabble_ic_delete,
18+
null
19+
)
20+
).let { delete ->
21+
delete.setTint(Color.WHITE)
22+
val bitmap = Bitmap.createBitmap(
23+
delete.intrinsicWidth,
24+
delete.intrinsicHeight,
25+
Bitmap.Config.ARGB_8888
26+
)
27+
val canvas = Canvas(bitmap)
28+
delete.setBounds(0, 0, canvas.width, canvas.height)
29+
delete.draw(canvas)
30+
bitmap
31+
}
32+
private val paint = Paint().apply {
33+
color = 0xffd32f2f.toInt()
34+
}
35+
36+
open fun onClick(item: T) {}
37+
open fun onLongClick(item: T) {}
38+
39+
fun setItemTouchHelper(itemTouchHelper: ItemTouchHelper) {
40+
this.itemTouchHelper = itemTouchHelper
41+
}
42+
43+
fun startDrag(viewHolder: RecyclerView.ViewHolder): Unit = itemTouchHelper.startDrag(viewHolder)
44+
45+
private val RecyclerView.isInSortMode
46+
get() = (adapter as? SortableAdapter)?.sortMode ?: false
47+
48+
override fun getDragDirs(recyclerView: RecyclerView, viewHolder: RecyclerView.ViewHolder) =
49+
if (recyclerView.isInSortMode) ItemTouchHelper.UP + ItemTouchHelper.DOWN else 0
50+
51+
override fun getSwipeDirs(recyclerView: RecyclerView, viewHolder: RecyclerView.ViewHolder) =
52+
if (recyclerView.isInSortMode) 0 else ItemTouchHelper.LEFT + ItemTouchHelper.RIGHT
53+
54+
override fun onMove(recyclerView: RecyclerView,
55+
viewHolder: RecyclerView.ViewHolder,
56+
target: RecyclerView.ViewHolder
57+
): Boolean {
58+
val adapter = recyclerView.adapter as SortableAdapter
59+
val from = viewHolder.bindingAdapterPosition
60+
val to = target.bindingAdapterPosition
61+
adapter.moveItem(from, to)
62+
return true
63+
}
64+
65+
// based on https://stackoverflow.com/a/45565493/995926
66+
override fun onChildDraw(c: Canvas, recyclerView: RecyclerView, viewHolder: RecyclerView.ViewHolder, dX: Float, dY: Float, actionState: Int, isCurrentlyActive: Boolean) {
67+
if (actionState == ItemTouchHelper.ACTION_STATE_SWIPE) {
68+
val itemView = viewHolder.itemView
69+
val height = itemView.bottom.toFloat() - itemView.top.toFloat()
70+
val width = height / 3f
71+
if (dX < 0) {
72+
val background = Rect(
73+
itemView.right + dX.toInt(), itemView.top,
74+
itemView.right, itemView.bottom
75+
)
76+
c.drawRect(background, paint)
77+
c.clipRect(background)
78+
79+
val iconDest = RectF(
80+
itemView.right - 2 * width,
81+
itemView.top + width,
82+
itemView.right - width,
83+
itemView.bottom - width
84+
)
85+
c.drawBitmap(icon, null, iconDest, paint)
86+
} else if(dX > 0) {
87+
val background = Rect(
88+
itemView.left, itemView.top,
89+
itemView.left + dX.toInt(), itemView.bottom
90+
)
91+
c.drawRect(background, paint)
92+
c.clipRect(background)
93+
94+
val iconDest = RectF(
95+
itemView.left + width,
96+
itemView.top + width,
97+
itemView.left + width * 2,
98+
itemView.bottom - width
99+
)
100+
c.drawBitmap(icon, null, iconDest, paint)
101+
}
102+
} else if (actionState == ItemTouchHelper.ACTION_STATE_DRAG) {
103+
val alpha = MaterialColors.ALPHA_FULL - abs(dY) / viewHolder.itemView.height.toFloat()
104+
viewHolder.itemView.alpha = alpha
105+
viewHolder.itemView.translationY = dY
106+
}
107+
super.onChildDraw(c, recyclerView, viewHolder, dX, dY, actionState, isCurrentlyActive)
108+
}
109+
110+
override fun getMovementFlags(
111+
recyclerView: RecyclerView,
112+
viewHolder: RecyclerView.ViewHolder
113+
): Int {
114+
val adapter = recyclerView.adapter as? DismissibleAdapter
115+
return if(viewHolder.bindingAdapterPosition >= 0 && adapter?.isDismissible(viewHolder.bindingAdapterPosition) == false) {
116+
0
117+
} else {
118+
super.getMovementFlags(recyclerView, viewHolder)
119+
}
120+
}
121+
122+
interface SortableAdapter {
123+
val sortMode: Boolean
124+
fun moveItem(from: Int, to: Int)
125+
}
126+
127+
interface DismissibleAdapter {
128+
fun isDismissible(position: Int): Boolean
129+
}
130+
}

ui/src/main/java/io/snabble/sdk/ui/cart/ShoppingCartView.java

Lines changed: 53 additions & 77 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
import android.widget.TextView;
2121

2222
import androidx.annotation.DrawableRes;
23+
import androidx.annotation.NonNull;
2324
import androidx.appcompat.app.AlertDialog;
2425
import androidx.fragment.app.FragmentActivity;
2526
import androidx.recyclerview.widget.DefaultItemAnimator;
@@ -33,8 +34,6 @@
3334
import com.google.android.material.snackbar.Snackbar;
3435
import com.squareup.picasso.Picasso;
3536

36-
import org.jetbrains.annotations.NotNull;
37-
3837
import java.util.ArrayList;
3938
import java.util.Collections;
4039
import java.util.List;
@@ -45,6 +44,7 @@
4544
import io.snabble.sdk.ShoppingCart;
4645
import io.snabble.sdk.Snabble;
4746
import io.snabble.sdk.Unit;
47+
import io.snabble.sdk.ui.GestureHandler;
4848
import io.snabble.sdk.ui.R;
4949
import io.snabble.sdk.ui.SnabbleUI;
5050
import io.snabble.sdk.ui.telemetry.Telemetry;
@@ -60,14 +60,11 @@ public class ShoppingCartView extends FrameLayout {
6060
private ShoppingCartAdapter recyclerViewAdapter;
6161
private SwipeRefreshLayout swipeRefreshLayout;
6262
private ShoppingCart cart;
63-
private View coordinatorLayout;
6463
private ViewGroup emptyState;
6564
private View restore;
6665
private TextView scanProducts;
67-
private Snackbar snackbar;
6866
private boolean hasAnyImages;
6967
private List<Product> lastInvalidProducts;
70-
private PaymentSelectionHelper paymentSelectionHelper;
7168
private AlertDialog alertDialog;
7269
private View paymentContainer;
7370
private boolean hasAlreadyShownInvalidDeposit;
@@ -147,7 +144,7 @@ private void inflateView(Context context, AttributeSet attrs) {
147144
cart = project.getShoppingCart();
148145

149146
recyclerView = findViewById(R.id.recycler_view);
150-
recyclerViewAdapter = new ShoppingCartAdapter(getContext(), undoHelper);
147+
recyclerViewAdapter = new ShoppingCartAdapter(recyclerView, cart);
151148
recyclerView.setAdapter(recyclerViewAdapter);
152149

153150
LinearLayoutManager layoutManager = new LinearLayoutManager(context);
@@ -163,7 +160,6 @@ private void inflateView(Context context, AttributeSet attrs) {
163160
swipeRefreshLayout = findViewById(R.id.swipe_refresh_layout);
164161
swipeRefreshLayout.setOnRefreshListener(() -> cart.updatePrices(false));
165162

166-
coordinatorLayout = findViewById(R.id.coordinator_layout);
167163
emptyState = findViewById(R.id.empty_state);
168164

169165
paymentContainer = findViewById(R.id.bottom_payment_container);
@@ -179,79 +175,35 @@ private void inflateView(Context context, AttributeSet attrs) {
179175
restore = findViewById(R.id.restore);
180176
restore.setOnClickListener(v -> cart.restore());
181177

182-
paymentSelectionHelper = PaymentSelectionHelper.getInstance();
183-
paymentSelectionHelper.getSelectedEntry().observe((FragmentActivity)UIUtils.getHostActivity(getContext()), entry -> update());
178+
PaymentSelectionHelper
179+
.getInstance()
180+
.getSelectedEntry()
181+
.observe((FragmentActivity)UIUtils.getHostActivity(getContext()), entry -> update());
184182

185-
createItemTouchHelper();
183+
createItemTouchHelper(context.getResources());
186184
submitList();
187185
update();
188186
}
189187

190-
private void createItemTouchHelper() {
191-
ItemTouchHelper itemTouchHelper = new ItemTouchHelper(new ItemTouchHelper.SimpleCallback(0,
192-
ItemTouchHelper.LEFT | ItemTouchHelper.RIGHT) {
193-
@Override
194-
public boolean onMove(@NotNull RecyclerView recyclerView,
195-
@NotNull RecyclerView.ViewHolder viewHolder,
196-
@NotNull RecyclerView.ViewHolder target) {
197-
return false;
198-
}
199-
188+
private void createItemTouchHelper(Resources resources) {
189+
GestureHandler<Void> gestureHandler = new GestureHandler<Void>(resources) {
200190
@Override
201-
public int getMovementFlags(@NotNull RecyclerView recyclerView, @NotNull RecyclerView.ViewHolder viewHolder) {
202-
if (viewHolder.getBindingAdapterPosition() == -1) {
203-
return super.getMovementFlags(recyclerView, viewHolder);
204-
}
205-
206-
if (!recyclerViewAdapter.isDismissable(viewHolder.getBindingAdapterPosition())) {
207-
return 0;
208-
}
209-
210-
return super.getMovementFlags(recyclerView, viewHolder);
211-
}
212-
213-
@Override
214-
public void onSwiped(@NotNull final RecyclerView.ViewHolder viewHolder, int direction) {
191+
public void onSwiped(@NonNull RecyclerView.ViewHolder viewHolder, int direction) {
215192
if (viewHolder instanceof ViewHolder) {
216193
ViewHolder holder = (ViewHolder) viewHolder;
217194
holder.hideInput();
218195
}
219196

220197
final int pos = viewHolder.getBindingAdapterPosition();
221198
ShoppingCart.Item item = cart.get(pos);
222-
undoHelper.removeAndShowUndoSnackbar(pos, item);
199+
recyclerViewAdapter.removeAndShowUndoSnackbar(pos, item);
223200
}
224-
});
225-
201+
};
202+
ItemTouchHelper itemTouchHelper = new ItemTouchHelper(gestureHandler);
203+
gestureHandler.setItemTouchHelper(itemTouchHelper);
226204
itemTouchHelper.attachToRecyclerView(recyclerView);
227205
}
228206

229-
public interface UndoHelper {
230-
void removeAndShowUndoSnackbar(final int adapterPosition, final ShoppingCart.Item item);
231-
}
232-
233-
private final UndoHelper undoHelper = new UndoHelper() {
234-
@Override
235-
public void removeAndShowUndoSnackbar(int adapterPosition, ShoppingCart.Item item) {
236-
if (adapterPosition == -1) {
237-
Logger.d("Invalid adapter position, ignoring");
238-
return;
239-
}
240-
241-
cart.remove(adapterPosition);
242-
Telemetry.event(Telemetry.Event.DeletedFromCart, item.getProduct());
243-
244-
snackbar = Snackbar.make(coordinatorLayout,
245-
R.string.Snabble_Shoppingcart_articleRemoved, UIUtils.SNACKBAR_LENGTH_VERY_LONG);
246-
snackbar.setAction(R.string.Snabble_undo, v -> {
247-
cart.insert(item, adapterPosition);
248-
Telemetry.event(Telemetry.Event.UndoDeleteFromCart, item.getProduct());
249-
});
250-
251-
snackbar.show();
252-
}
253-
};
254-
255207
private void updateEmptyState() {
256208
if (cart.size() > 0) {
257209
paymentContainer.setVisibility(View.VISIBLE);
@@ -436,7 +388,7 @@ public static List<Row> buildRows(Resources resources, ShoppingCart cart) {
436388
SimpleRow row = new SimpleRow();
437389
row.title = resources.getString(R.string.Snabble_Shoppingcart_coupon);
438390
row.text = item.getDisplayName();
439-
row.isDismissable = true;
391+
row.isDismissible = true;
440392
rows.add(row);
441393
} else if (item.getType() == ShoppingCart.ItemType.PRODUCT) {
442394
final ProductRow row = new ProductRow();
@@ -454,7 +406,7 @@ public static List<Row> buildRows(Resources resources, ShoppingCart cart) {
454406
row.quantity = quantity;
455407
row.quantityText = sanitize(item.getQuantityText());
456408
row.editable = item.isEditable();
457-
row.isDismissable = true;
409+
row.isDismissible = true;
458410
row.item = item;
459411
rows.add(row);
460412
}
@@ -487,7 +439,7 @@ private static void setTextOrHide(TextView textView, String text) {
487439
}
488440

489441
private static abstract class Row {
490-
boolean isDismissable;
442+
boolean isDismissible;
491443
}
492444

493445
private static class ProductRow extends Row {
@@ -781,18 +733,21 @@ public void update(SimpleRow row, boolean hasAnyImages) {
781733
}
782734
}
783735

784-
public static class ShoppingCartAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
736+
public static class ShoppingCartAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder>
737+
implements UndoHelper, GestureHandler.DismissibleAdapter {
785738
private static final int TYPE_PRODUCT = 0;
786739
private static final int TYPE_SIMPLE = 1;
787740
private List<Row> list = Collections.emptyList();
788741
private final Context context;
742+
private final ShoppingCart cart;
743+
private final View parentView;
789744
private boolean hasAnyImages = false;
790-
private final UndoHelper undoHelper;
791745

792-
public ShoppingCartAdapter(Context context, UndoHelper undoHelper) {
746+
public ShoppingCartAdapter(View parentView, ShoppingCart cart) {
793747
super();
794-
this.context = context;
795-
this.undoHelper = undoHelper;
748+
this.context = parentView.getContext();
749+
this.parentView = parentView;
750+
this.cart = cart;
796751
}
797752

798753
@Override
@@ -804,8 +759,9 @@ public int getItemViewType(int position) {
804759
return TYPE_PRODUCT;
805760
}
806761

807-
public boolean isDismissable(int position) {
808-
return getItem(position).isDismissable;
762+
@Override
763+
public boolean isDismissible(int position) {
764+
return getItem(position).isDismissible;
809765
}
810766

811767
@Override
@@ -879,9 +835,9 @@ public Row getItem(int position) {
879835
return list.get(position);
880836
}
881837

882-
@NotNull
838+
@NonNull
883839
@Override
884-
public RecyclerView.ViewHolder onCreateViewHolder(@NotNull ViewGroup parent, int viewType) {
840+
public RecyclerView.ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
885841
if (viewType == TYPE_SIMPLE) {
886842
View v = View.inflate(context, R.layout.snabble_item_shoppingcart_simple, null);
887843
v.setLayoutParams(new ViewGroup.LayoutParams(
@@ -893,12 +849,12 @@ public RecyclerView.ViewHolder onCreateViewHolder(@NotNull ViewGroup parent, int
893849
v.setLayoutParams(new ViewGroup.LayoutParams(
894850
ViewGroup.LayoutParams.MATCH_PARENT,
895851
ViewGroup.LayoutParams.WRAP_CONTENT));
896-
return new ViewHolder(v, undoHelper);
852+
return new ViewHolder(v, this);
897853
}
898854
}
899855

900856
@Override
901-
public void onBindViewHolder(@NotNull final RecyclerView.ViewHolder holder, final int position) {
857+
public void onBindViewHolder(@NonNull final RecyclerView.ViewHolder holder, final int position) {
902858
int type = getItemViewType(position);
903859

904860
if (type == TYPE_PRODUCT) {
@@ -909,5 +865,25 @@ public void onBindViewHolder(@NotNull final RecyclerView.ViewHolder holder, fina
909865
viewHolder.update((SimpleRow) getItem(position), hasAnyImages);
910866
}
911867
}
868+
869+
@Override
870+
public void removeAndShowUndoSnackbar(int adapterPosition, ShoppingCart.Item item) {
871+
if (adapterPosition == -1) {
872+
Logger.d("Invalid adapter position, ignoring");
873+
return;
874+
}
875+
876+
cart.remove(adapterPosition);
877+
Telemetry.event(Telemetry.Event.DeletedFromCart, item.getProduct());
878+
879+
Snackbar snackbar = Snackbar.make(parentView,
880+
R.string.Snabble_Shoppingcart_articleRemoved, UIUtils.SNACKBAR_LENGTH_VERY_LONG);
881+
snackbar.setAction(R.string.Snabble_undo, v -> {
882+
cart.insert(item, adapterPosition);
883+
Telemetry.event(Telemetry.Event.UndoDeleteFromCart, item.getProduct());
884+
});
885+
886+
snackbar.show();
887+
}
912888
}
913889
}

0 commit comments

Comments
 (0)