]> git.ktnx.net Git - mobile-ledger.git/blobdiff - app/src/main/java/net/ktnx/mobileledger/ui/activity/NewTransactionFragment.java
upgrade a couple of library versions
[mobile-ledger.git] / app / src / main / java / net / ktnx / mobileledger / ui / activity / NewTransactionFragment.java
index 13d3ccc8be6b6c5fb8b37c6b47fe10f5c334e594..d9d844a4e078babe960808b5cee42bc1552a9360 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright © 2019 Damyan Ivanov.
+ * Copyright © 2021 Damyan Ivanov.
  * This file is part of MoLe.
  * MoLe is free software: you can distribute it and/or modify it
  * under the term of the GNU General Public License as published by
@@ -19,19 +19,26 @@ package net.ktnx.mobileledger.ui.activity;
 
 import android.app.Activity;
 import android.content.Context;
+import android.content.Intent;
+import android.content.res.Resources;
 import android.os.Bundle;
 import android.renderscript.RSInvalidStateException;
 import android.view.LayoutInflater;
 import android.view.Menu;
 import android.view.MenuInflater;
+import android.view.MenuItem;
 import android.view.View;
 import android.view.ViewGroup;
+import android.widget.ProgressBar;
 
+import androidx.activity.result.ActivityResultLauncher;
+import androidx.activity.result.contract.ActivityResultContract;
 import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
+import androidx.appcompat.app.AlertDialog;
 import androidx.fragment.app.Fragment;
-import androidx.lifecycle.ViewModelProviders;
-import androidx.recyclerview.widget.ItemTouchHelper;
+import androidx.fragment.app.FragmentActivity;
+import androidx.lifecycle.ViewModelProvider;
 import androidx.recyclerview.widget.LinearLayoutManager;
 import androidx.recyclerview.widget.RecyclerView;
 
@@ -39,14 +46,19 @@ import com.google.android.material.floatingactionbutton.FloatingActionButton;
 import com.google.android.material.snackbar.Snackbar;
 
 import net.ktnx.mobileledger.R;
+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.utils.Logger;
+import net.ktnx.mobileledger.utils.Misc;
+import net.ktnx.mobileledger.utils.SimpleDate;
 
 import org.jetbrains.annotations.NotNull;
 
-import java.util.Date;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
 
 /**
  * A simple {@link Fragment} subclass.
@@ -54,10 +66,28 @@ import java.util.Date;
  * {@link OnNewTransactionFragmentInteractionListener} interface
  * to handle interaction events.
  */
+
+// TODO: offer to undo account remove-on-swipe
+
 public class NewTransactionFragment extends Fragment {
     private NewTransactionItemsAdapter listAdapter;
     private NewTransactionModel viewModel;
-    private RecyclerView list;
+    final ActivityResultLauncher<Void> scanQrLauncher =
+            registerForActivityResult(new ActivityResultContract<Void, String>() {
+                @NonNull
+                @Override
+                public Intent createIntent(@NonNull Context context, Void input) {
+                    final Intent intent = new Intent("com.google.zxing.client.android.SCAN");
+                    intent.putExtra("SCAN_MODE", "QR_CODE_MODE");
+                    return intent;
+                }
+                @Override
+                public String parseResult(int resultCode, @Nullable Intent intent) {
+                    if (resultCode == Activity.RESULT_CANCELED)
+                        return null;
+                    return intent.getStringExtra("SCAN_RESULT");
+                }
+            }, this::onQrScanned);
     private FloatingActionButton fab;
     private OnNewTransactionFragmentInteractionListener mListener;
     private MobileLedgerProfile mProfile;
@@ -65,15 +95,97 @@ public class NewTransactionFragment extends Fragment {
         // Required empty public constructor
         setHasOptionsMenu(true);
     }
+    private void onQrScanned(String text) {
+        Logger.debug("qr", String.format("Got QR scan result [%s]", text));
+        Pattern p =
+                Pattern.compile("^(\\d+)\\*(\\d+)\\*(\\d+)-(\\d+)-(\\d+)\\*([:\\d]+)\\*([\\d.]+)$");
+        Matcher m = p.matcher(text);
+        if (m.matches()) {
+            float amount = Float.parseFloat(m.group(7));
+            viewModel.setDate(
+                    new SimpleDate(Integer.parseInt(m.group(3)), Integer.parseInt(m.group(4)),
+                            Integer.parseInt(m.group(5))));
+
+            if (viewModel.accountsInInitialState()) {
+                {
+                    NewTransactionModel.Item firstItem = viewModel.getItem(1);
+                    if (firstItem == null) {
+                        viewModel.addAccount(new LedgerTransactionAccount("разход:пазар"));
+                        listAdapter.notifyItemInserted(viewModel.items.size() - 1);
+                    }
+                    else {
+                        firstItem.setAccountName("разход:пазар");
+                        firstItem.getAccount()
+                                 .resetAmount();
+                        listAdapter.notifyItemChanged(1);
+                    }
+                }
+                {
+                    NewTransactionModel.Item secondItem = viewModel.getItem(2);
+                    if (secondItem == null) {
+                        viewModel.addAccount(
+                                new LedgerTransactionAccount("актив:кеш:дам", -amount, null, null));
+                        listAdapter.notifyItemInserted(viewModel.items.size() - 1);
+                    }
+                    else {
+                        secondItem.setAccountName("актив:кеш:дам");
+                        secondItem.getAccount()
+                                  .setAmount(-amount);
+                        listAdapter.notifyItemChanged(2);
+                    }
+                }
+            }
+            else {
+                viewModel.addAccount(new LedgerTransactionAccount("разход:пазар"));
+                viewModel.addAccount(
+                        new LedgerTransactionAccount("актив:кеш:дам", -amount, null, null));
+                listAdapter.notifyItemRangeInserted(viewModel.items.size() - 1, 2);
+            }
+
+            listAdapter.checkTransactionSubmittable();
+        }
+    }
     @Override
     public void onCreateOptionsMenu(@NonNull Menu menu, @NonNull MenuInflater inflater) {
         super.onCreateOptionsMenu(menu, inflater);
+        final FragmentActivity activity = getActivity();
+
         inflater.inflate(R.menu.new_transaction_fragment, menu);
+
+        menu.findItem(R.id.scan_qr)
+            .setOnMenuItemClickListener(this::onScanQrAction);
+
         menu.findItem(R.id.action_reset_new_transaction_activity)
             .setOnMenuItemClickListener(item -> {
                 listAdapter.reset();
                 return true;
             });
+
+        final MenuItem toggleCurrencyItem = menu.findItem(R.id.toggle_currency);
+        toggleCurrencyItem.setOnMenuItemClickListener(item -> {
+            viewModel.toggleCurrencyVisible();
+            return true;
+        });
+        if (activity != null)
+            viewModel.showCurrency.observe(activity, toggleCurrencyItem::setChecked);
+
+        final MenuItem toggleCommentsItem = menu.findItem(R.id.toggle_comments);
+        toggleCommentsItem.setOnMenuItemClickListener(item -> {
+            viewModel.toggleShowComments();
+            return true;
+        });
+        if (activity != null)
+            viewModel.showComments.observe(activity, toggleCommentsItem::setChecked);
+    }
+    private boolean onScanQrAction(MenuItem item) {
+        try {
+            scanQrLauncher.launch(null);
+        }
+        catch (Exception e) {
+            Logger.debug("qr", "Error launching QR scanner", e);
+        }
+
+        return true;
     }
     @Override
     public View onCreateView(LayoutInflater inflater, ViewGroup container,
@@ -83,69 +195,32 @@ public class NewTransactionFragment extends Fragment {
     }
 
     @Override
-    public void onActivityCreated(@Nullable Bundle savedInstanceState) {
-        super.onActivityCreated(savedInstanceState);
-        Activity activity = getActivity();
+    public void onViewCreated(@NotNull View view, @Nullable Bundle savedInstanceState) {
+        super.onViewCreated(view, savedInstanceState);
+        FragmentActivity activity = getActivity();
         if (activity == null)
             throw new RSInvalidStateException(
                     "getActivity() returned null within onActivityCreated()");
 
-        list = activity.findViewById(R.id.new_transaction_accounts);
-        viewModel = ViewModelProviders.of(this)
-                                      .get(NewTransactionModel.class);
-        mProfile = Data.profile.getValue();
+        viewModel = new ViewModelProvider(activity).get(NewTransactionModel.class);
+        viewModel.observeDataProfile(this);
+        mProfile = Data.getProfile();
         listAdapter = new NewTransactionItemsAdapter(viewModel, mProfile);
+
+        RecyclerView list = activity.findViewById(R.id.new_transaction_accounts);
         list.setAdapter(listAdapter);
         list.setLayoutManager(new LinearLayoutManager(activity));
-        Data.profile.observe(this, profile -> {
+
+        Data.observeProfile(getViewLifecycleOwner(), profile -> {
             mProfile = profile;
             listAdapter.setProfile(profile);
         });
         listAdapter.notifyDataSetChanged();
-        new ItemTouchHelper(new ItemTouchHelper.Callback() {
-            @Override
-            public int getMovementFlags(@NonNull RecyclerView recyclerView,
-                                        @NonNull RecyclerView.ViewHolder viewHolder) {
-                int flags = makeFlag(ItemTouchHelper.ACTION_STATE_IDLE, ItemTouchHelper.END);
-                // the top item is always there (date and description)
-                if (viewHolder.getAdapterPosition() > 0) {
-                    if (viewModel.getAccountCount() > 2) {
-                        flags |= 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) {
-                return false;
-            }
-            @Override
-            public void onSwiped(@NonNull RecyclerView.ViewHolder viewHolder, int direction) {
-                if (viewModel.getAccountCount() == 2)
-                    Snackbar.make(list, R.string.msg_at_least_two_accounts_are_required,
-                            Snackbar.LENGTH_LONG)
-                            .setAction("Action", null)
-                            .show();
-                else {
-                    int pos = viewHolder.getAdapterPosition();
-                    viewModel.removeItem(pos - 1);
-                    listAdapter.notifyItemRemoved(pos);
-                    viewModel.sendCountNotifications(); // needed after items re-arrangement
-                    viewModel.checkTransactionSubmittable(listAdapter);
-                }
-            }
-        }).attachToRecyclerView(list);
-
         viewModel.isSubmittable()
-                 .observe(this, isSubmittable -> {
+                 .observe(getViewLifecycleOwner(), isSubmittable -> {
                      if (isSubmittable) {
                          if (fab != null) {
                              fab.show();
-                             fab.setEnabled(true);
                          }
                      }
                      else {
@@ -154,30 +229,76 @@ public class NewTransactionFragment extends Fragment {
                          }
                      }
                  });
-        viewModel.checkTransactionSubmittable(listAdapter);
+//        viewModel.checkTransactionSubmittable(listAdapter);
 
         fab = activity.findViewById(R.id.fab);
         fab.setOnClickListener(v -> onFabPressed());
 
+        boolean keep = false;
+
         Bundle args = getArguments();
         if (args != null) {
             String error = args.getString("error");
             if (error != null) {
-                // TODO display error
-            }
-            else {
+                Logger.debug("new-trans-f", String.format("Got error: %s", error));
+
+                Context context = getContext();
+                if (context != null) {
+                    AlertDialog.Builder builder = new AlertDialog.Builder(context);
+                    final Resources resources = context.getResources();
+                    final StringBuilder message = new StringBuilder();
+                    message.append(resources.getString(R.string.err_json_send_error_head));
+                    message.append("\n\n");
+                    message.append(error);
+                    if (mProfile.getApiVersion()
+                                .equals(API.auto))
+                        message.append(
+                                resources.getString(R.string.err_json_send_error_unsupported));
+                    else {
+                        message.append(resources.getString(R.string.err_json_send_error_tail));
+                        builder.setPositiveButton(R.string.btn_profile_options, (dialog, which) -> {
+                            Logger.debug("error", "will start profile editor");
+                            MobileLedgerProfile.startEditProfileActivity(context, mProfile);
+                        });
+                    }
+                    builder.setMessage(message);
+                    builder.create()
+                           .show();
+                }
+                else {
+                    Snackbar.make(list, error, Snackbar.LENGTH_INDEFINITE)
+                            .show();
+                }
+                keep = true;
             }
         }
 
+        int focused = 0;
         if (savedInstanceState != null) {
-            boolean keep = savedInstanceState.getBoolean("keep", true);
-            if (!keep)
-                viewModel.reset();
-            else {
-                final int focused = savedInstanceState.getInt("focused", 0);
-                viewModel.setFocusedItem(focused);
-            }
+            keep |= savedInstanceState.getBoolean("keep", true);
+            focused = savedInstanceState.getInt("focused", 0);
+        }
+
+        if (!keep)
+            viewModel.reset();
+        else {
+            viewModel.setFocusedItem(focused);
         }
+
+        ProgressBar p = activity.findViewById(R.id.progressBar);
+        viewModel.observeBusyFlag(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);
+        });
     }
     @Override
     public void onSaveInstanceState(@NonNull Bundle outState) {
@@ -187,16 +308,19 @@ public class NewTransactionFragment extends Fragment {
         outState.putInt("focused", focusedItem);
     }
     private void onFabPressed() {
-        fab.setEnabled(false);
+        fab.hide();
+        Misc.hideSoftKeyboard(this);
         if (mListener != null) {
-            Date date = viewModel.getDate();
+            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 = viewModel.getAccount(i);
+                LedgerTransactionAccount acc =
+                        new LedgerTransactionAccount(viewModel.getAccount(i));
                 if (acc.getAccountName()
                        .trim()
                        .isEmpty())