]> git.ktnx.net Git - mobile-ledger.git/blobdiff - app/src/main/java/net/ktnx/mobileledger/ui/templates/TemplateDetailsAdapter.java
implement swipe-remove and drag-move in template details
[mobile-ledger.git] / app / src / main / java / net / ktnx / mobileledger / ui / templates / TemplateDetailsAdapter.java
index e472049b1e5762f0a5f1ab75968d16b5b282842e..c7e59c3d43ac2b3b3d5990e644741ceefbde4969 100644 (file)
@@ -20,6 +20,7 @@ package net.ktnx.mobileledger.ui.templates;
 import android.text.Editable;
 import android.text.TextWatcher;
 import android.view.LayoutInflater;
+import android.view.MotionEvent;
 import android.view.View;
 import android.view.ViewGroup;
 import android.widget.TextView;
@@ -28,6 +29,7 @@ import androidx.annotation.NonNull;
 import androidx.appcompat.app.AppCompatActivity;
 import androidx.recyclerview.widget.AsyncListDiffer;
 import androidx.recyclerview.widget.DiffUtil;
+import androidx.recyclerview.widget.ItemTouchHelper;
 import androidx.recyclerview.widget.RecyclerView;
 
 import net.ktnx.mobileledger.R;
@@ -54,8 +56,11 @@ import java.util.regex.Pattern;
 class TemplateDetailsAdapter extends RecyclerView.Adapter<TemplateDetailsAdapter.ViewHolder> {
     private static final String D_TEMPLATE_UI = "template-ui";
     private final AsyncListDiffer<TemplateDetailsItem> differ;
-    public TemplateDetailsAdapter() {
+    private final TemplateDetailsViewModel mModel;
+    private final ItemTouchHelper itemTouchHelper;
+    public TemplateDetailsAdapter(TemplateDetailsViewModel model) {
         super();
+        mModel = model;
         setHasStableIds(true);
         differ = new AsyncListDiffer<>(this, new DiffUtil.ItemCallback<TemplateDetailsItem>() {
             @Override
@@ -90,6 +95,97 @@ class TemplateDetailsAdapter extends RecyclerView.Adapter<TemplateDetailsAdapter
                 }
             }
         });
+        itemTouchHelper = new ItemTouchHelper(new ItemTouchHelper.Callback() {
+            @Override
+            public float getMoveThreshold(@NonNull RecyclerView.ViewHolder viewHolder) {
+                return 0.1f;
+            }
+            @Override
+            public boolean isLongPressDragEnabled() {
+                return false;
+            }
+            @Override
+            public RecyclerView.ViewHolder chooseDropTarget(
+                    @NonNull RecyclerView.ViewHolder selected,
+                    @NonNull List<RecyclerView.ViewHolder> dropTargets, int curX, int curY) {
+                RecyclerView.ViewHolder best = null;
+                int bestDistance = 0;
+                for (RecyclerView.ViewHolder v : dropTargets) {
+                    if (v == selected)
+                        continue;
+
+                    final int viewTop = v.itemView.getTop();
+                    int distance = Math.abs(viewTop - curY);
+                    if (best == null) {
+                        best = v;
+                        bestDistance = distance;
+                    }
+                    else {
+                        if (distance < bestDistance) {
+                            bestDistance = distance;
+                            best = v;
+                        }
+                    }
+                }
+
+                Logger.debug("dnd", "Best target is " + best);
+                return best;
+            }
+            @Override
+            public boolean canDropOver(@NonNull RecyclerView recyclerView,
+                                       @NonNull RecyclerView.ViewHolder current,
+                                       @NonNull RecyclerView.ViewHolder target) {
+                final int adapterPosition = target.getAdapterPosition();
+
+                // first item is immovable
+                if (adapterPosition == 0)
+                    return false;
+
+                return super.canDropOver(recyclerView, current, target);
+            }
+            @Override
+            public int getMovementFlags(@NonNull RecyclerView recyclerView,
+                                        @NonNull RecyclerView.ViewHolder viewHolder) {
+                int flags = 0;
+                // the top item (transaction params) is always there
+                final int adapterPosition = viewHolder.getAdapterPosition();
+                if (adapterPosition > 0)
+                    flags |= makeFlag(ItemTouchHelper.ACTION_STATE_DRAG,
+                            ItemTouchHelper.UP | ItemTouchHelper.DOWN) |
+                             makeFlag(ItemTouchHelper.ACTION_STATE_SWIPE,
+                                     ItemTouchHelper.START | ItemTouchHelper.END);
+
+                return flags;
+            }
+            @Override
+            public boolean onMove(@NonNull RecyclerView recyclerView,
+                                  @NonNull RecyclerView.ViewHolder viewHolder,
+                                  @NonNull RecyclerView.ViewHolder target) {
+
+                final int fromPosition = viewHolder.getAdapterPosition();
+                final int toPosition = target.getAdapterPosition();
+                mModel.moveItem(fromPosition, toPosition);
+
+                return true;
+            }
+            @Override
+            public void onSwiped(@NonNull RecyclerView.ViewHolder viewHolder, int direction) {
+                int pos = viewHolder.getAdapterPosition();
+                mModel.removeItem(pos);
+            }
+        });
+    }
+    @Override
+    public void onAttachedToRecyclerView(@NonNull RecyclerView recyclerView) {
+        super.onAttachedToRecyclerView(recyclerView);
+
+        itemTouchHelper.attachToRecyclerView(recyclerView);
+    }
+    @Override
+    public void onDetachedFromRecyclerView(@NonNull RecyclerView recyclerView) {
+        super.onDetachedFromRecyclerView(recyclerView);
+
+        itemTouchHelper.attachToRecyclerView(null);
     }
     @Override
     public long getItemId(int position) {
@@ -334,6 +430,10 @@ class TemplateDetailsAdapter extends RecyclerView.Adapter<TemplateDetailsAdapter
             TemplateDetailsItem.Header header = item.asHeaderItem();
             Logger.debug(D_TEMPLATE_UI, "Binding to header " + header);
 
+            String groupNoText = b.getRoot()
+                                  .getResources()
+                                  .getString(R.string.template_item_match_group_source);
+
             b.templateName.setText(header.getName());
             b.pattern.setText(header.getPattern());
             b.testText.setText(header.getTestText());
@@ -348,12 +448,13 @@ class TemplateDetailsAdapter extends RecyclerView.Adapter<TemplateDetailsAdapter
             else {
                 b.templateDetailsDateYearLayout.setVisibility(View.GONE);
                 b.templateDetailsYearSource.setText(
-                        String.format(Locale.US, "Group %d (%s)", header.getDateYearMatchGroup(),
+                        String.format(Locale.US, groupNoText, header.getDateYearMatchGroup(),
                                 getMatchGroupText(header.getDateYearMatchGroup())));
             }
-            b.templateDetailsYearSourceLabel.setOnClickListener(v -> selectHeaderDetailSource(v, HeaderDetail.DATE_YEAR));
-            b.templateDetailsYearSource.setOnClickListener(v -> selectHeaderDetailSource(v,
-                    HeaderDetail.DATE_YEAR));
+            b.templateDetailsYearSourceLabel.setOnClickListener(
+                    v -> selectHeaderDetailSource(v, HeaderDetail.DATE_YEAR));
+            b.templateDetailsYearSource.setOnClickListener(
+                    v -> selectHeaderDetailSource(v, HeaderDetail.DATE_YEAR));
 
             if (header.hasLiteralDateMonth()) {
                 b.templateDetailsMonthSource.setText(R.string.template_details_source_literal);
@@ -365,52 +466,59 @@ class TemplateDetailsAdapter extends RecyclerView.Adapter<TemplateDetailsAdapter
             else {
                 b.templateDetailsDateMonthLayout.setVisibility(View.GONE);
                 b.templateDetailsMonthSource.setText(
-                        String.format(Locale.US, "Group %d (%s)", header.getDateMonthMatchGroup(),
+                        String.format(Locale.US, groupNoText, header.getDateMonthMatchGroup(),
                                 getMatchGroupText(header.getDateMonthMatchGroup())));
             }
-            b.templateDetailsMonthSourceLabel.setOnClickListener(v -> selectHeaderDetailSource(v,
-                    HeaderDetail.DATE_MONTH));
-            b.templateDetailsMonthSource.setOnClickListener(v -> selectHeaderDetailSource(v,
-                    HeaderDetail.DATE_MONTH));
+            b.templateDetailsMonthSourceLabel.setOnClickListener(
+                    v -> selectHeaderDetailSource(v, HeaderDetail.DATE_MONTH));
+            b.templateDetailsMonthSource.setOnClickListener(
+                    v -> selectHeaderDetailSource(v, HeaderDetail.DATE_MONTH));
 
             if (header.hasLiteralDateDay()) {
                 b.templateDetailsDaySource.setText(R.string.template_details_source_literal);
                 final Integer dateDay = header.getDateDay();
-                b.templateDetailsDateDay.setText((dateDay == null) ? null : String.valueOf(dateDay));
+                b.templateDetailsDateDay.setText(
+                        (dateDay == null) ? null : String.valueOf(dateDay));
                 b.templateDetailsDateDayLayout.setVisibility(View.VISIBLE);
             }
             else {
                 b.templateDetailsDateDayLayout.setVisibility(View.GONE);
                 b.templateDetailsDaySource.setText(
-                        String.format(Locale.US, "Group %d (%s)", header.getDateDayMatchGroup(),
+                        String.format(Locale.US, groupNoText, header.getDateDayMatchGroup(),
                                 getMatchGroupText(header.getDateDayMatchGroup())));
             }
-            b.templateDetailsDaySourceLabel.setOnClickListener(v -> selectHeaderDetailSource(v, HeaderDetail.DATE_DAY));
-            b.templateDetailsDaySource.setOnClickListener(v -> selectHeaderDetailSource(v, HeaderDetail.DATE_DAY));
+            b.templateDetailsDaySourceLabel.setOnClickListener(
+                    v -> selectHeaderDetailSource(v, HeaderDetail.DATE_DAY));
+            b.templateDetailsDaySource.setOnClickListener(
+                    v -> selectHeaderDetailSource(v, HeaderDetail.DATE_DAY));
 
             if (header.hasLiteralTransactionDescription()) {
-                b.templateTransactionDescriptionSource.setText(R.string.template_details_source_literal);
+                b.templateTransactionDescriptionSource.setText(
+                        R.string.template_details_source_literal);
                 b.transactionDescription.setText(header.getTransactionDescription());
                 b.transactionDescriptionLayout.setVisibility(View.VISIBLE);
             }
             else {
                 b.transactionDescriptionLayout.setVisibility(View.GONE);
-                b.templateTransactionDescriptionSource.setText(
-                        String.format(Locale.US, "Group %d (%s)",
-                                header.getTransactionDescriptionMatchGroup(), getMatchGroupText(header.getTransactionDescriptionMatchGroup())));
+                b.templateTransactionDescriptionSource.setText(String.format(Locale.US, groupNoText,
+                        header.getTransactionDescriptionMatchGroup(),
+                        getMatchGroupText(header.getTransactionDescriptionMatchGroup())));
 
             }
-            b.templateTransactionDescriptionSourceLabel.setOnClickListener(v -> selectHeaderDetailSource(v, HeaderDetail.DESCRIPTION));
-            b.templateTransactionDescriptionSource.setOnClickListener(v -> selectHeaderDetailSource(v, HeaderDetail.DESCRIPTION));
+            b.templateTransactionDescriptionSourceLabel.setOnClickListener(
+                    v -> selectHeaderDetailSource(v, HeaderDetail.DESCRIPTION));
+            b.templateTransactionDescriptionSource.setOnClickListener(
+                    v -> selectHeaderDetailSource(v, HeaderDetail.DESCRIPTION));
 
             if (header.hasLiteralTransactionComment()) {
-                b.templateTransactionCommentSource.setText(R.string.template_details_source_literal);
+                b.templateTransactionCommentSource.setText(
+                        R.string.template_details_source_literal);
                 b.transactionComment.setText(header.getTransactionComment());
                 b.transactionCommentLayout.setVisibility(View.VISIBLE);
             }
             else {
                 b.transactionCommentLayout.setVisibility(View.GONE);
-                b.templateTransactionCommentSource.setText(String.format(Locale.US, "Group %d (%s)",
+                b.templateTransactionCommentSource.setText(String.format(Locale.US, groupNoText,
                         header.getTransactionCommentMatchGroup(),
                         getMatchGroupText(header.getTransactionCommentMatchGroup())));
 
@@ -537,11 +645,30 @@ class TemplateDetailsAdapter extends RecyclerView.Adapter<TemplateDetailsAdapter
                 b.templateDetailsAccountAmount.setText(Data.formatNumber(amt));
             });
 
-            b.negateAmountSwitch.setOnCheckedChangeListener(
-                    (buttonView, isChecked) -> getItem().setNegateAmount(isChecked));
+            b.negateAmountSwitch.setOnCheckedChangeListener((buttonView, isChecked) -> {
+                getItem().setNegateAmount(isChecked);
+                b.templateDetailsNegateAmountText.setText(
+                        isChecked ? R.string.template_account_change_amount_sign
+                                  : R.string.template_account_keep_amount_sign);
+            });
+            final View.OnClickListener negLabelClickListener = (view) -> {
+                b.negateAmountSwitch.toggle();
+            };
+            b.templateDetailsNegateAmountLabel.setOnClickListener(negLabelClickListener);
+            b.templateDetailsNegateAmountText.setOnClickListener(negLabelClickListener);
+            b.patternAccountLabel.setOnTouchListener((v, event) -> {
+                if (event.getAction() == MotionEvent.ACTION_DOWN) {
+                    itemTouchHelper.startDrag(this);
+                }
+                return false;
+            });
         }
         @Override
         void bind(TemplateDetailsItem item) {
+            String groupNoText = b.getRoot()
+                                  .getResources()
+                                  .getString(R.string.template_item_match_group_source);
+
             TemplateDetailsItem.AccountRow accRow = item.asAccountRowItem();
             if (accRow.hasLiteralAccountName()) {
                 b.templateDetailsAccountNameLayout.setVisibility(View.VISIBLE);
@@ -552,7 +679,7 @@ class TemplateDetailsAdapter extends RecyclerView.Adapter<TemplateDetailsAdapter
             else {
                 b.templateDetailsAccountNameLayout.setVisibility(View.GONE);
                 b.templateDetailsAccountNameSource.setText(
-                        String.format(Locale.US, "Group %d (%s)", accRow.getAccountNameMatchGroup(),
+                        String.format(Locale.US, groupNoText, accRow.getAccountNameMatchGroup(),
                                 getMatchGroupText(accRow.getAccountNameMatchGroup())));
             }
 
@@ -565,8 +692,7 @@ class TemplateDetailsAdapter extends RecyclerView.Adapter<TemplateDetailsAdapter
             else {
                 b.templateDetailsAccountCommentLayout.setVisibility(View.GONE);
                 b.templateDetailsAccountCommentSource.setText(
-                        String.format(Locale.US, "Group %d (%s)",
-                                accRow.getAccountCommentMatchGroup(),
+                        String.format(Locale.US, groupNoText, accRow.getAccountCommentMatchGroup(),
                                 getMatchGroupText(accRow.getAccountCommentMatchGroup())));
             }
 
@@ -578,14 +704,21 @@ class TemplateDetailsAdapter extends RecyclerView.Adapter<TemplateDetailsAdapter
                 b.templateDetailsAccountAmount.setText((amt == null) ? null : String.format(
                         Data.locale.getValue(), "%,4.2f", (accRow.getAmount())));
                 b.negateAmountSwitch.setVisibility(View.GONE);
+                b.templateDetailsNegateAmountLabel.setVisibility(View.GONE);
+                b.templateDetailsNegateAmountText.setVisibility(View.GONE);
             }
             else {
                 b.templateDetailsAccountAmountSource.setText(
-                        String.format(Locale.US, "Group %d (%s)", accRow.getAmountMatchGroup(),
+                        String.format(Locale.US, groupNoText, accRow.getAmountMatchGroup(),
                                 getMatchGroupText(accRow.getAmountMatchGroup())));
                 b.templateDetailsAccountAmountLayout.setVisibility(View.GONE);
                 b.negateAmountSwitch.setVisibility(View.VISIBLE);
                 b.negateAmountSwitch.setChecked(accRow.isNegateAmount());
+                b.templateDetailsNegateAmountText.setText(
+                        accRow.isNegateAmount() ? R.string.template_account_change_amount_sign
+                                                : R.string.template_account_keep_amount_sign);
+                b.templateDetailsNegateAmountLabel.setVisibility(View.VISIBLE);
+                b.templateDetailsNegateAmountText.setVisibility(View.VISIBLE);
             }
 
             b.templateAccountNameSourceLabel.setOnClickListener(