]> git.ktnx.net Git - mobile-ledger.git/blobdiff - app/src/main/java/net/ktnx/mobileledger/ui/new_transaction/NewTransactionFragment.java
rework new transaction activity/model/etc with proper concept separation
[mobile-ledger.git] / app / src / main / java / net / ktnx / mobileledger / ui / new_transaction / NewTransactionFragment.java
index 5edf252d72858ceccacef8663fe388e897a7810f..d1f19c46bc51c7018904d874cda141e411c095cc 100644 (file)
 package net.ktnx.mobileledger.ui.new_transaction;
 
 import android.content.Context;
-import android.content.Intent;
 import android.content.res.Resources;
-import android.database.AbstractCursor;
 import android.os.Bundle;
-import android.os.ParcelFormatException;
 import android.renderscript.RSInvalidStateException;
 import android.view.LayoutInflater;
 import android.view.Menu;
@@ -37,39 +34,22 @@ import androidx.annotation.Nullable;
 import androidx.appcompat.app.AlertDialog;
 import androidx.fragment.app.Fragment;
 import androidx.fragment.app.FragmentActivity;
-import androidx.lifecycle.LiveData;
 import androidx.lifecycle.ViewModelProvider;
 import androidx.recyclerview.widget.LinearLayoutManager;
 import androidx.recyclerview.widget.RecyclerView;
 
-import com.google.android.material.dialog.MaterialAlertDialogBuilder;
-import com.google.android.material.floatingactionbutton.FloatingActionButton;
-import com.google.android.material.snackbar.BaseTransientBottomBar;
 import com.google.android.material.snackbar.Snackbar;
 
 import net.ktnx.mobileledger.R;
-import net.ktnx.mobileledger.db.DB;
-import net.ktnx.mobileledger.db.TemplateAccount;
-import net.ktnx.mobileledger.db.TemplateHeader;
 import net.ktnx.mobileledger.json.API;
 import net.ktnx.mobileledger.model.Data;
 import net.ktnx.mobileledger.model.LedgerTransaction;
-import net.ktnx.mobileledger.model.LedgerTransactionAccount;
 import net.ktnx.mobileledger.model.MobileLedgerProfile;
-import net.ktnx.mobileledger.ui.QRScanCapableFragment;
-import net.ktnx.mobileledger.ui.templates.TemplatesActivity;
+import net.ktnx.mobileledger.ui.QR;
 import net.ktnx.mobileledger.utils.Logger;
-import net.ktnx.mobileledger.utils.Misc;
-import net.ktnx.mobileledger.utils.SimpleDate;
 
 import org.jetbrains.annotations.NotNull;
 
-import java.util.ArrayList;
-import java.util.List;
-import java.util.Locale;
-import java.util.regex.Matcher;
-import java.util.regex.Pattern;
-
 /**
  * A simple {@link Fragment} subclass.
  * Activities that contain this fragment must implement the
@@ -79,310 +59,15 @@ import java.util.regex.Pattern;
 
 // TODO: offer to undo account remove-on-swipe
 
-public class NewTransactionFragment extends QRScanCapableFragment {
+public class NewTransactionFragment extends Fragment {
     private NewTransactionItemsAdapter listAdapter;
     private NewTransactionModel viewModel;
-    private FloatingActionButton fab;
     private OnNewTransactionFragmentInteractionListener mListener;
     private MobileLedgerProfile mProfile;
     public NewTransactionFragment() {
         // Required empty public constructor
         setHasOptionsMenu(true);
     }
-    private void startNewPatternActivity(String scanned) {
-        Intent intent = new Intent(requireContext(), TemplatesActivity.class);
-        Bundle args = new Bundle();
-        args.putString(TemplatesActivity.ARG_ADD_TEMPLATE, scanned);
-        requireContext().startActivity(intent, args);
-    }
-    private void alertNoTemplateMatch(String scanned) {
-        MaterialAlertDialogBuilder builder = new MaterialAlertDialogBuilder(requireContext());
-        builder.setCancelable(true)
-               .setMessage(R.string.no_template_matches)
-               .setPositiveButton(R.string.add_button,
-                       (dialog, which) -> startNewPatternActivity(scanned))
-               .create()
-               .show();
-    }
-    protected void onQrScanned(String text) {
-        Logger.debug("qr", String.format("Got QR scan result [%s]", text));
-
-        if (Misc.emptyIsNull(text) == null)
-            return;
-
-        LiveData<List<TemplateHeader>> allTemplates = DB.get()
-                                                        .getTemplateDAO()
-                                                        .getTemplates();
-        allTemplates.observe(getViewLifecycleOwner(), templateHeaders -> {
-            ArrayList<TemplateHeader> matchingFallbackTemplates = new ArrayList<>();
-            ArrayList<TemplateHeader> matchingTemplates = new ArrayList<>();
-
-            for (TemplateHeader ph : templateHeaders) {
-                String patternSource = ph.getRegularExpression();
-                if (Misc.emptyIsNull(patternSource) == null)
-                    continue;
-                try {
-                    Pattern pattern = Pattern.compile(patternSource);
-                    Matcher matcher = pattern.matcher(text);
-                    if (!matcher.matches())
-                        continue;
-
-                    Logger.debug("pattern",
-                            String.format("Pattern '%s' [%s] matches '%s'", ph.getName(),
-                                    patternSource, text));
-                    if (ph.isFallback())
-                        matchingFallbackTemplates.add(ph);
-                    else
-                        matchingTemplates.add(ph);
-                }
-                catch (ParcelFormatException e) {
-                    // ignored
-                    Logger.debug("pattern",
-                            String.format("Error compiling regular expression '%s'", patternSource),
-                            e);
-                }
-            }
-
-            if (matchingTemplates.isEmpty())
-                matchingTemplates = matchingFallbackTemplates;
-
-            if (matchingTemplates.isEmpty())
-                alertNoTemplateMatch(text);
-            else if (matchingTemplates.size() == 1)
-                applyTemplate(matchingTemplates.get(0), text);
-            else
-                chooseTemplate(matchingTemplates, text);
-        });
-    }
-    private void chooseTemplate(ArrayList<TemplateHeader> matchingTemplates, String matchedText) {
-        final String templateNameColumn = "name";
-        AbstractCursor cursor = new AbstractCursor() {
-            @Override
-            public int getCount() {
-                return matchingTemplates.size();
-            }
-            @Override
-            public String[] getColumnNames() {
-                return new String[]{"_id", templateNameColumn};
-            }
-            @Override
-            public String getString(int column) {
-                if (column == 0)
-                    return String.valueOf(getPosition());
-                return matchingTemplates.get(getPosition())
-                                        .getName();
-            }
-            @Override
-            public short getShort(int column) {
-                if (column == 0)
-                    return (short) getPosition();
-                return -1;
-            }
-            @Override
-            public int getInt(int column) {
-                return getShort(column);
-            }
-            @Override
-            public long getLong(int column) {
-                return getShort(column);
-            }
-            @Override
-            public float getFloat(int column) {
-                return getShort(column);
-            }
-            @Override
-            public double getDouble(int column) {
-                return getShort(column);
-            }
-            @Override
-            public boolean isNull(int column) {
-                return false;
-            }
-            @Override
-            public int getColumnCount() {
-                return 2;
-            }
-        };
-
-        MaterialAlertDialogBuilder builder = new MaterialAlertDialogBuilder(requireContext());
-        builder.setCancelable(true)
-               .setTitle(R.string.choose_template_to_apply)
-               .setIcon(R.drawable.ic_baseline_auto_graph_24)
-               .setSingleChoiceItems(cursor, -1, templateNameColumn, (dialog, which) -> {
-                   applyTemplate(matchingTemplates.get(which), matchedText);
-                   dialog.dismiss();
-               })
-               .create()
-               .show();
-    }
-    private void applyTemplate(TemplateHeader patternHeader, String text) {
-        Pattern pattern = Pattern.compile(patternHeader.getRegularExpression());
-
-        Matcher m = pattern.matcher(text);
-
-        if (!m.matches()) {
-            Snackbar.make(requireView(), R.string.pattern_does_not_match,
-                    BaseTransientBottomBar.LENGTH_INDEFINITE)
-                    .show();
-            return;
-        }
-
-        SimpleDate transactionDate;
-        {
-            int day = extractIntFromMatches(m, patternHeader.getDateDayMatchGroup(),
-                    patternHeader.getDateDay());
-            int month = extractIntFromMatches(m, patternHeader.getDateMonthMatchGroup(),
-                    patternHeader.getDateMonth());
-            int year = extractIntFromMatches(m, patternHeader.getDateYearMatchGroup(),
-                    patternHeader.getDateYear());
-
-            SimpleDate today = SimpleDate.today();
-            if (year <= 0)
-                year = today.year;
-            if (month <= 0)
-                month = today.month;
-            if (day <= 0)
-                day = today.day;
-
-            transactionDate = new SimpleDate(year, month, day);
-
-            Logger.debug("pattern", "setting transaction date to " + transactionDate);
-        }
-
-        NewTransactionModel.Item head = viewModel.getItem(0);
-        head.ensureType(NewTransactionModel.ItemType.generalData);
-        final String transactionDescription =
-                extractStringFromMatches(m, patternHeader.getTransactionDescriptionMatchGroup(),
-                        patternHeader.getTransactionDescription());
-        head.setDescription(transactionDescription);
-        Logger.debug("pattern", "Setting transaction description to " + transactionDescription);
-        final String transactionComment =
-                extractStringFromMatches(m, patternHeader.getTransactionCommentMatchGroup(),
-                        patternHeader.getTransactionComment());
-        head.setTransactionComment(transactionComment);
-        Logger.debug("pattern", "Setting transaction comment to " + transactionComment);
-        head.setDate(transactionDate);
-        listAdapter.notifyItemChanged(0);
-
-        DB.get()
-          .getTemplateDAO()
-          .getTemplateWithAccounts(patternHeader.getId())
-          .observe(getViewLifecycleOwner(), entry -> {
-              int rowIndex = 0;
-              final boolean accountsInInitialState = viewModel.accountsInInitialState();
-              for (TemplateAccount acc : entry.accounts) {
-                  rowIndex++;
-
-                  String accountName = extractStringFromMatches(m, acc.getAccountNameMatchGroup(),
-                          acc.getAccountName());
-                  String accountComment =
-                          extractStringFromMatches(m, acc.getAccountCommentMatchGroup(),
-                                  acc.getAccountComment());
-                  Float amount =
-                          extractFloatFromMatches(m, acc.getAmountMatchGroup(), acc.getAmount());
-                  if (amount != null && acc.getNegateAmount() != null && acc.getNegateAmount())
-                      amount = -amount;
-
-                  if (accountsInInitialState) {
-                      NewTransactionModel.Item item = viewModel.getItem(rowIndex);
-                      if (item == null) {
-                          Logger.debug("pattern", String.format(Locale.US,
-                                  "Adding new account item [%s][c:%s][a:%s]", accountName,
-                                  accountComment, amount));
-                          final LedgerTransactionAccount ledgerAccount =
-                                  new LedgerTransactionAccount(accountName);
-                          ledgerAccount.setComment(accountComment);
-                          if (amount != null)
-                              ledgerAccount.setAmount(amount);
-                          // TODO currency
-                          viewModel.addAccount(ledgerAccount);
-                          listAdapter.notifyItemInserted(viewModel.items.size() - 1);
-                      }
-                      else {
-                          Logger.debug("pattern", String.format(Locale.US,
-                                  "Stamping account item #%d [%s][c:%s][a:%s]", rowIndex,
-                                  accountName, accountComment, amount));
-
-                          item.setAccountName(accountName);
-                          item.setComment(accountComment);
-                          if (amount != null)
-                              item.getAccount()
-                                  .setAmount(amount);
-
-                          listAdapter.notifyItemChanged(rowIndex);
-                      }
-                  }
-                  else {
-                      final LedgerTransactionAccount transactionAccount =
-                              new LedgerTransactionAccount(accountName);
-                      transactionAccount.setComment(accountComment);
-                      if (amount != null)
-                          transactionAccount.setAmount(amount);
-                      // TODO currency
-                      Logger.debug("pattern", String.format(Locale.US,
-                              "Adding trailing account item [%s][c:%s][a:%s]", accountName,
-                              accountComment, amount));
-
-                      viewModel.addAccount(transactionAccount);
-                      listAdapter.notifyItemInserted(viewModel.items.size() - 1);
-                  }
-              }
-
-              listAdapter.checkTransactionSubmittable();
-          });
-    }
-    private int extractIntFromMatches(Matcher m, Integer group, Integer literal) {
-        if (literal != null)
-            return literal;
-
-        if (group != null) {
-            int grp = group;
-            if (grp > 0 & grp <= m.groupCount())
-                try {
-                    return Integer.parseInt(m.group(grp));
-                }
-                catch (NumberFormatException e) {
-                    Snackbar.make(requireView(),
-                            "Error extracting transaction date: " + e.getMessage(),
-                            BaseTransientBottomBar.LENGTH_INDEFINITE)
-                            .show();
-                }
-        }
-
-        return 0;
-    }
-    private String extractStringFromMatches(Matcher m, Integer group, String literal) {
-        if (literal != null)
-            return literal;
-
-        if (group != null) {
-            int grp = group;
-            if (grp > 0 & grp <= m.groupCount())
-                return m.group(grp);
-        }
-
-        return null;
-    }
-    private Float extractFloatFromMatches(Matcher m, Integer group, Float literal) {
-        if (literal != null)
-            return literal;
-
-        if (group != null) {
-            int grp = group;
-            if (grp > 0 & grp <= m.groupCount())
-                try {
-                    return Float.valueOf(m.group(grp));
-                }
-                catch (NumberFormatException e) {
-                    Snackbar.make(requireView(),
-                            "Error extracting transaction amount: " + e.getMessage(),
-                            BaseTransientBottomBar.LENGTH_INDEFINITE)
-                            .show();
-                }
-        }
-
-        return null;
-    }
     @Override
     public void onCreateOptionsMenu(@NonNull Menu menu, @NonNull MenuInflater inflater) {
         super.onCreateOptionsMenu(menu, inflater);
@@ -395,7 +80,7 @@ public class NewTransactionFragment extends QRScanCapableFragment {
 
         menu.findItem(R.id.action_reset_new_transaction_activity)
             .setOnMenuItemClickListener(item -> {
-                listAdapter.reset();
+                viewModel.reset();
                 return true;
             });
 
@@ -405,7 +90,8 @@ public class NewTransactionFragment extends QRScanCapableFragment {
             return true;
         });
         if (activity != null)
-            viewModel.showCurrency.observe(activity, toggleCurrencyItem::setChecked);
+            viewModel.getShowCurrency()
+                     .observe(activity, toggleCurrencyItem::setChecked);
 
         final MenuItem toggleCommentsItem = menu.findItem(R.id.toggle_comments);
         toggleCommentsItem.setOnMenuItemClickListener(item -> {
@@ -413,11 +99,14 @@ public class NewTransactionFragment extends QRScanCapableFragment {
             return true;
         });
         if (activity != null)
-            viewModel.showComments.observe(activity, toggleCommentsItem::setChecked);
+            viewModel.getShowComments()
+                     .observe(activity, toggleCommentsItem::setChecked);
     }
     private boolean onScanQrAction(MenuItem item) {
         try {
-            scanQrLauncher.launch(null);
+            Context ctx = requireContext();
+            if (ctx instanceof QR.QRScanTrigger)
+                ((QR.QRScanTrigger) ctx).triggerQRScan();
         }
         catch (Exception e) {
             Logger.debug("qr", "Error launching QR scanner", e);
@@ -445,6 +134,9 @@ public class NewTransactionFragment extends QRScanCapableFragment {
         mProfile = Data.getProfile();
         listAdapter = new NewTransactionItemsAdapter(viewModel, mProfile);
 
+        viewModel.getItems()
+                 .observe(getViewLifecycleOwner(), newList -> listAdapter.setItems(newList));
+
         RecyclerView list = activity.findViewById(R.id.new_transaction_accounts);
         list.setAdapter(listAdapter);
         list.setLayoutManager(new LinearLayoutManager(activity));
@@ -453,25 +145,6 @@ public class NewTransactionFragment extends QRScanCapableFragment {
             mProfile = profile;
             listAdapter.setProfile(profile);
         });
-        listAdapter.notifyDataSetChanged();
-        viewModel.isSubmittable()
-                 .observe(getViewLifecycleOwner(), isSubmittable -> {
-                     if (isSubmittable) {
-                         if (fab != null) {
-                             fab.show();
-                         }
-                     }
-                     else {
-                         if (fab != null) {
-                             fab.hide();
-                         }
-                     }
-                 });
-//        viewModel.checkTransactionSubmittable(listAdapter);
-
-        fab = activity.findViewById(R.id.fabAdd);
-        fab.setOnClickListener(v -> onFabPressed());
-
         boolean keep = false;
 
         Bundle args = getArguments();
@@ -512,73 +185,45 @@ public class NewTransactionFragment extends QRScanCapableFragment {
         }
 
         int focused = 0;
+        FocusedElement element = null;
         if (savedInstanceState != null) {
             keep |= savedInstanceState.getBoolean("keep", true);
-            focused = savedInstanceState.getInt("focused", 0);
+            focused = savedInstanceState.getInt("focused-item", 0);
+            element = FocusedElement.valueOf(savedInstanceState.getString("focused-element"));
         }
 
         if (!keep)
             viewModel.reset();
         else {
-            viewModel.setFocusedItem(focused);
+            viewModel.noteFocusChanged(focused, element);
         }
 
         ProgressBar p = activity.findViewById(R.id.progressBar);
-        viewModel.observeBusyFlag(getViewLifecycleOwner(), isBusy -> {
-            if (isBusy) {
+        viewModel.getBusyFlag()
+                 .observe(getViewLifecycleOwner(), isBusy -> {
+                     if (isBusy) {
 //                Handler h = new Handler();
 //                h.postDelayed(() -> {
 //                    if (viewModel.getBusyFlag())
 //                        p.setVisibility(View.VISIBLE);
 //
 //                }, 10);
-                p.setVisibility(View.VISIBLE);
-            }
-            else
-                p.setVisibility(View.INVISIBLE);
-        });
+                         p.setVisibility(View.VISIBLE);
+                     }
+                     else
+                         p.setVisibility(View.INVISIBLE);
+                 });
     }
     @Override
     public void onSaveInstanceState(@NonNull Bundle outState) {
         super.onSaveInstanceState(outState);
         outState.putBoolean("keep", true);
-        final int focusedItem = viewModel.getFocusedItem();
-        outState.putInt("focused", focusedItem);
-    }
-    private void onFabPressed() {
-        fab.hide();
-        Misc.hideSoftKeyboard(this);
-        if (mListener != null) {
-            SimpleDate date = viewModel.getDate();
-            LedgerTransaction tr =
-                    new LedgerTransaction(null, date, viewModel.getDescription(), mProfile);
-
-            tr.setComment(viewModel.getComment());
-            LedgerTransactionAccount emptyAmountAccount = null;
-            float emptyAmountAccountBalance = 0;
-            for (int i = 0; i < viewModel.getAccountCount(); i++) {
-                LedgerTransactionAccount acc =
-                        new LedgerTransactionAccount(viewModel.getAccount(i));
-                if (acc.getAccountName()
-                       .trim()
-                       .isEmpty())
-                    continue;
-
-                if (acc.isAmountSet()) {
-                    emptyAmountAccountBalance += acc.getAmount();
-                }
-                else {
-                    emptyAmountAccount = acc;
-                }
-
-                tr.addAccount(acc);
-            }
-
-            if (emptyAmountAccount != null)
-                emptyAmountAccount.setAmount(-emptyAmountAccountBalance);
-
-            mListener.onTransactionSave(tr);
-        }
+        final NewTransactionModel.FocusInfo focusInfo = viewModel.getFocusInfo()
+                                                                 .getValue();
+        final int focusedItem = focusInfo.position;
+        if (focusedItem >= 0)
+            outState.putInt("focused-item", focusedItem);
+        outState.putString("focused-element", focusInfo.element.toString());
     }
 
     @Override