]> git.ktnx.net Git - mobile-ledger.git/blobdiff - app/src/main/java/net/ktnx/mobileledger/ui/activity/NewTransactionItemsAdapter.java
NT: correctly disable swiping of top and bottom entries, let all the rest be swiped
[mobile-ledger.git] / app / src / main / java / net / ktnx / mobileledger / ui / activity / NewTransactionItemsAdapter.java
index 1585f1d8d48bde6c1cbb85f0ce04b93f3fb9373b..5f2f4c6c4fa653b0a610a0187535a269a8387da1 100644 (file)
@@ -21,9 +21,9 @@ import android.database.Cursor;
 import android.view.LayoutInflater;
 import android.view.ViewGroup;
 import android.widget.LinearLayout;
-import android.widget.TableRow;
 
 import androidx.annotation.NonNull;
+import androidx.recyclerview.widget.ItemTouchHelper;
 import androidx.recyclerview.widget.RecyclerView;
 
 import net.ktnx.mobileledger.App;
@@ -44,6 +44,8 @@ class NewTransactionItemsAdapter extends RecyclerView.Adapter<NewTransactionItem
         implements DescriptionSelectedCallback {
     NewTransactionModel model;
     private MobileLedgerProfile mProfile;
+    private ItemTouchHelper touchHelper;
+    private RecyclerView recyclerView;
     NewTransactionItemsAdapter(NewTransactionModel viewModel, MobileLedgerProfile profile) {
         super();
         model = viewModel;
@@ -54,6 +56,61 @@ class NewTransactionItemsAdapter extends RecyclerView.Adapter<NewTransactionItem
                     String.format(Locale.US, "%d accounts is too little, Calling addRow()", size));
             size = addRow();
         }
+
+        NewTransactionItemsAdapter adapter = this;
+
+        touchHelper = new ItemTouchHelper(new ItemTouchHelper.Callback() {
+            @Override
+            public boolean isLongPressDragEnabled() {
+                return true;
+            }
+            @Override
+            public boolean canDropOver(@NonNull RecyclerView recyclerView,
+                                       @NonNull RecyclerView.ViewHolder current,
+                                       @NonNull RecyclerView.ViewHolder target) {
+                final int adapterPosition = target.getAdapterPosition();
+
+                // first and last items are immovable
+                if (adapterPosition == 0)
+                    return false;
+                if (adapterPosition == adapter.getItemCount() - 1)
+                    return false;
+
+                return super.canDropOver(recyclerView, current, target);
+            }
+            @Override
+            public int getMovementFlags(@NonNull RecyclerView recyclerView,
+                                        @NonNull RecyclerView.ViewHolder viewHolder) {
+                int flags = makeFlag(ItemTouchHelper.ACTION_STATE_IDLE, ItemTouchHelper.END);
+                // the top (date and description) and the bottom (padding) items are always there
+                final int adapterPosition = viewHolder.getAdapterPosition();
+                if ((adapterPosition > 0) && (adapterPosition < adapter.getItemCount() - 1)) {
+                    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) {
+
+                model.swapItems(viewHolder.getAdapterPosition(), target.getAdapterPosition());
+                notifyItemMoved(viewHolder.getAdapterPosition(), target.getAdapterPosition());
+                return true;
+            }
+            @Override
+            public void onSwiped(@NonNull RecyclerView.ViewHolder viewHolder, int direction) {
+                int pos = viewHolder.getAdapterPosition();
+                viewModel.removeItem(pos - 1);
+                notifyItemRemoved(pos);
+                viewModel.sendCountNotifications(); // needed after items re-arrangement
+                viewModel.checkTransactionSubmittable(adapter);
+            }
+        });
     }
     public void setProfile(MobileLedgerProfile profile) {
         mProfile = profile;
@@ -64,6 +121,7 @@ class NewTransactionItemsAdapter extends RecyclerView.Adapter<NewTransactionItem
                 String.format(Locale.US, "invoking notifyItemInserted(%d)", newAccountCount));
         // the header is at position 0
         notifyItemInserted(newAccountCount);
+        model.sendCountNotifications(); // needed after holders' positions have changed
         return newAccountCount;
     }
     @NonNull
@@ -72,34 +130,50 @@ class NewTransactionItemsAdapter extends RecyclerView.Adapter<NewTransactionItem
         LinearLayout row = (LinearLayout) LayoutInflater.from(parent.getContext())
                                                         .inflate(R.layout.new_transaction_row,
                                                                 parent, false);
+
         return new NewTransactionItemHolder(row, this);
     }
     @Override
     public void onBindViewHolder(@NonNull NewTransactionItemHolder holder, int position) {
         Logger.debug("bind", String.format(Locale.US, "Binding item at position %d", position));
-        holder.setData(model.getItem(position));
-        Logger.debug("bind", String.format(Locale.US, "Bound item at position %d", position));
+        NewTransactionModel.Item item = model.getItem(position);
+        holder.setData(item);
+        Logger.debug("bind", String.format(Locale.US, "Bound %s item at position %d", item.getType()
+                                                                                          .toString(),
+                position));
     }
     @Override
     public int getItemCount() {
-        final int itemCount = model.getAccountCount() + 2;
-        Logger.debug("new-transaction",
-                String.format(Locale.US, "getItemCount() returning %d", itemCount));
-        return itemCount;
+        return model.getAccountCount() + 2;
     }
     boolean accountListIsEmpty() {
         for (int i = 0; i < model.getAccountCount(); i++) {
             LedgerTransactionAccount acc = model.getAccount(i);
             if (!acc.getAccountName()
-                    .isEmpty()) return false;
-            if (acc.isAmountSet()) return false;
+                    .isEmpty())
+                return false;
+            if (acc.isAmountSet())
+                return false;
         }
 
         return true;
     }
+    @Override
+    public void onAttachedToRecyclerView(@NonNull RecyclerView recyclerView) {
+        super.onAttachedToRecyclerView(recyclerView);
+        this.recyclerView = recyclerView;
+        touchHelper.attachToRecyclerView(recyclerView);
+    }
+    @Override
+    public void onDetachedFromRecyclerView(@NonNull RecyclerView recyclerView) {
+        touchHelper.attachToRecyclerView(null);
+        super.onDetachedFromRecyclerView(recyclerView);
+        this.recyclerView = null;
+    }
     public void descriptionSelected(String description) {
         debug("descr selected", description);
-        if (!accountListIsEmpty()) return;
+        if (!accountListIsEmpty())
+            return;
 
         String accFilter = mProfile.getPreferredAccountsFilter();
 
@@ -126,24 +200,30 @@ class NewTransactionItemsAdapter extends RecyclerView.Adapter<NewTransactionItem
         try (Cursor c = App.getDatabase()
                            .rawQuery(sql, params.toArray(new String[]{})))
         {
-            if (!c.moveToNext()) return;
+            if (!c.moveToNext())
+                return;
 
             String profileUUID = c.getString(0);
             int transactionId = c.getInt(1);
             LedgerTransaction tr;
             MobileLedgerProfile profile = Data.getProfile(profileUUID);
-            if (profile == null) throw new RuntimeException(String.format(
-                    "Unable to find profile %s, which is supposed to contain " +
-                    "transaction %d with description %s", profileUUID, transactionId, description));
+            if (profile == null)
+                throw new RuntimeException(String.format(
+                        "Unable to find profile %s, which is supposed to contain " +
+                        "transaction %d with description %s", profileUUID, transactionId,
+                        description));
 
             tr = profile.loadTransaction(transactionId);
             ArrayList<LedgerTransactionAccount> accounts = tr.getAccounts();
-            TableRow firstNegative = null;
+            NewTransactionModel.Item firstNegative = null;
+            NewTransactionModel.Item firstPositive = null;
+            int singleNegativeIndex = -1;
+            int singlePositiveIndex = -1;
             int negativeCount = 0;
             for (int i = 0; i < accounts.size(); i++) {
                 LedgerTransactionAccount acc = accounts.get(i);
                 NewTransactionModel.Item item;
-                if (model.getAccountCount() < i) {
+                if (model.getAccountCount() < i + 1) {
                     model.addAccount(acc);
                     notifyItemInserted(i + 1);
                 }
@@ -151,22 +231,53 @@ class NewTransactionItemsAdapter extends RecyclerView.Adapter<NewTransactionItem
 
                 item.getAccount()
                     .setAccountName(acc.getAccountName());
-                if (acc.isAmountSet()) item.getAccount()
-                                           .setAmount(acc.getAmount());
-                else item.getAccount()
-                         .resetAmount();
+                if (acc.isAmountSet()) {
+                    item.getAccount()
+                        .setAmount(acc.getAmount());
+                    if (acc.getAmount() < 0) {
+                        if (firstNegative == null) {
+                            firstNegative = item;
+                            singleNegativeIndex = i;
+                        }
+                        else
+                            singleNegativeIndex = -1;
+                    }
+                    else {
+                        if (firstPositive == null) {
+                            firstPositive = item;
+                            singlePositiveIndex = i;
+                        }
+                        else
+                            singlePositiveIndex = -1;
+                    }
+                }
+                else
+                    item.getAccount()
+                        .resetAmount();
                 notifyItemChanged(i + 1);
             }
+
+            if (singleNegativeIndex != -1) {
+                firstNegative.getAccount()
+                             .resetAmount();
+                model.moveItemLast(singleNegativeIndex);
+            }
+            else if (singlePositiveIndex != -1) {
+                firstPositive.getAccount()
+                             .resetAmount();
+                model.moveItemLast(singlePositiveIndex);
+            }
         }
         model.checkTransactionSubmittable(this);
         model.setFocusedItem(1);
     }
     public void toggleAllEditing(boolean editable) {
-        for (int i = 0; i < model.getAccountCount(); i++) {
-            model.getItem(i + 1)
+        // item 0 is the header
+        for (int i = 0; i <= model.getAccountCount(); i++) {
+            model.getItem(i)
                  .setEditable(editable);
-            notifyItemChanged(i + 1);
-            // TODO perhaps do only one notification about the whole range [1…count]?
+            notifyItemChanged(i);
+            // TODO perhaps do only one notification about the whole range (notifyDatasetChanged)?
         }
     }
     public void reset() {
@@ -177,8 +288,19 @@ class NewTransactionItemsAdapter extends RecyclerView.Adapter<NewTransactionItem
         if (presentItemCount > 2)
             notifyItemRangeRemoved(3, presentItemCount - 2); // all the rest are gone
     }
-    public void removeItem(int pos) {
-        model.removeItem(pos - 1, this);
-        notifyItemRemoved(pos);
+    public void updateFocusedItem(int position) {
+        model.updateFocusedItem(position);
+    }
+    public void noteFocusIsOnAccount(int position) {
+        model.noteFocusChanged(position, NewTransactionModel.FocusedElement.Account);
+    }
+    public void noteFocusIsOnAmount(int position) {
+        model.noteFocusChanged(position, NewTransactionModel.FocusedElement.Amount);
+    }
+    public void noteFocusIsOnComment(int position) {
+        model.noteFocusChanged(position, NewTransactionModel.FocusedElement.Comment);
+    }
+    public void toggleComment(int position) {
+        model.toggleComment(position);
     }
 }