]> git.ktnx.net Git - mobile-ledger.git/commitdiff
first step towards pattern-assisted auto-filling of transactions
authorDamyan Ivanov <dam+mobileledger@ktnx.net>
Mon, 11 Jan 2021 21:27:07 +0000 (23:27 +0200)
committerDamyan Ivanov <dam+mobileledger@ktnx.net>
Mon, 11 Jan 2021 21:28:58 +0000 (21:28 +0000)
in the end will support deducing transactions via QR-code scan,
clipboard paste and text messages

21 files changed:
app/build.gradle
app/src/main/AndroidManifest.xml
app/src/main/java/net/ktnx/mobileledger/model/PatternEntry.java [new file with mode: 0644]
app/src/main/java/net/ktnx/mobileledger/ui/activity/MainActivity.java
app/src/main/java/net/ktnx/mobileledger/ui/activity/NewTransactionFragment.java
app/src/main/java/net/ktnx/mobileledger/ui/activity/PatternsActivity.java [new file with mode: 0644]
app/src/main/java/net/ktnx/mobileledger/ui/patterns/PatternViewHolder.java [new file with mode: 0644]
app/src/main/java/net/ktnx/mobileledger/ui/patterns/PatternsModel.java [new file with mode: 0644]
app/src/main/java/net/ktnx/mobileledger/ui/patterns/PatternsRecyclerViewAdapter.java [new file with mode: 0644]
app/src/main/java/net/ktnx/mobileledger/utils/MLDB.java
app/src/main/res/drawable-anydpi/ic_baseline_auto_graph_24.xml [new file with mode: 0644]
app/src/main/res/drawable-anydpi/ic_baseline_help_24.xml [new file with mode: 0644]
app/src/main/res/drawable-anydpi/ic_baseline_help_24_white.xml [new file with mode: 0644]
app/src/main/res/drawable-anydpi/ic_baseline_qr_code_scanner_24.xml [new file with mode: 0644]
app/src/main/res/layout/activity_main.xml
app/src/main/res/layout/activity_patterns.xml [new file with mode: 0644]
app/src/main/res/layout/pattern_layout.xml [new file with mode: 0644]
app/src/main/res/menu/new_transaction_fragment.xml
app/src/main/res/menu/pattern_list_menu.xml [new file with mode: 0644]
app/src/main/res/values-bg/strings.xml
app/src/main/res/values/strings.xml

index c73b915723a97986d4a4eae3de5da659a06bc840..d21f3ee58449b9361753f818d85380bf7cd716f7 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright © 2020 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
@@ -50,6 +50,8 @@ android {
 }
 
 dependencies {
+    implementation 'androidx.lifecycle:lifecycle-livedata-ktx:2.2.0'
+    implementation 'androidx.lifecycle:lifecycle-viewmodel-ktx:2.2.0'
     def nav_version = '2.3.1'
     implementation fileTree(include: ['*.jar'], dir: 'libs')
     implementation 'androidx.legacy:legacy-support-v4:1.0.0'
index aca6ce21ddb8b0a56653e3701c32541d246545eb..a865bf9d67aca8cca0ab9cbf41bf08fad098edb0 100644 (file)
@@ -1,5 +1,5 @@
 <?xml version="1.0" encoding="utf-8"?><!--
-  ~ Copyright © 2020 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
         android:roundIcon="@drawable/app_icon_round"
         android:supportsRtl="true"
         tools:ignore="GoogleAppIndexingWarning">
+        <activity
+            android:name=".ui.activity.PatternsActivity"
+            android:label="@string/title_activity_patterns"
+            android:theme="@style/AppTheme.default" />
         <activity
             android:name=".ui.activity.SplashActivity"
             android:label="@string/app_name"
diff --git a/app/src/main/java/net/ktnx/mobileledger/model/PatternEntry.java b/app/src/main/java/net/ktnx/mobileledger/model/PatternEntry.java
new file mode 100644 (file)
index 0000000..b9f7529
--- /dev/null
@@ -0,0 +1,33 @@
+/*
+ * 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
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your opinion), any later version.
+ *
+ * MoLe is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License terms for details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with MoLe. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+package net.ktnx.mobileledger.model;
+
+public class PatternEntry {
+    private final int id;
+    private String name;
+    public PatternEntry(int id) {
+        this.id = id;
+    }
+    public String getName() {
+        return name;
+    }
+    public void setName(String name) {
+        this.name = name;
+    }
+    public int getId() { return id; }
+}
index 92cb1f4b87110a77da85aaf96d938c028ddde3d6..490471d9cbdb84e9725a7697680ee8f480198b05 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright © 2020 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
@@ -146,8 +146,7 @@ public class MainActivity extends ProfileThemedActivity {
         Data.backgroundTasksRunning.observe(this, this::onRetrieveRunningChanged);
 
         if (barDrawerToggle == null) {
-            barDrawerToggle = new ActionBarDrawerToggle(this, b.drawerLayout, b.toolbar,
-                    R.string.navigation_drawer_open, R.string.navigation_drawer_close);
+            barDrawerToggle = new ActionBarDrawerToggle(this, b.drawerLayout, b.toolbar, R.string.navigation_drawer_open, R.string.navigation_drawer_close);
             b.drawerLayout.addDrawerListener(barDrawerToggle);
         }
         barDrawerToggle.syncState();
@@ -156,8 +155,7 @@ public class MainActivity extends ProfileThemedActivity {
             PackageInfo pi = getApplicationContext().getPackageManager()
                                                     .getPackageInfo(getPackageName(), 0);
             ((TextView) b.navUpper.findViewById(R.id.drawer_version_text)).setText(pi.versionName);
-            ((TextView) b.noProfilesLayout.findViewById(R.id.drawer_version_text)).setText(
-                    pi.versionName);
+            ((TextView) b.noProfilesLayout.findViewById(R.id.drawer_version_text)).setText(pi.versionName);
         }
         catch (Exception e) {
             e.printStackTrace();
@@ -200,13 +198,11 @@ public class MainActivity extends ProfileThemedActivity {
                      .setValue(savedInstanceState.getString(STATE_ACC_FILTER, null));
         }
 
-        b.btnNoProfilesAdd.setOnClickListener(
-                v -> MobileLedgerProfile.startEditProfileActivity(this, null));
+        b.btnNoProfilesAdd.setOnClickListener(v -> MobileLedgerProfile.startEditProfileActivity(this, null));
 
         b.btnAddTransaction.setOnClickListener(this::fabNewTransactionClicked);
 
-        b.navNewProfileButton.setOnClickListener(
-                v -> MobileLedgerProfile.startEditProfileActivity(this, null));
+        b.navNewProfileButton.setOnClickListener(v -> MobileLedgerProfile.startEditProfileActivity(this, null));
 
         b.transactionListCancelDownload.setOnClickListener(this::onStopTransactionRefreshClick);
 
@@ -251,10 +247,8 @@ public class MainActivity extends ProfileThemedActivity {
         b.navProfileList.setLayoutManager(llm);
 
         b.navProfilesStartEdit.setOnClickListener((v) -> mProfileListAdapter.flipEditingProfiles());
-        b.navProfilesCancelEdit.setOnClickListener(
-                (v) -> mProfileListAdapter.flipEditingProfiles());
-        b.navProfileListHeadButtons.setOnClickListener(
-                (v) -> mProfileListAdapter.flipEditingProfiles());
+        b.navProfilesCancelEdit.setOnClickListener((v) -> mProfileListAdapter.flipEditingProfiles());
+        b.navProfileListHeadButtons.setOnClickListener((v) -> mProfileListAdapter.flipEditingProfiles());
         if (drawerListener == null) {
             drawerListener = new DrawerLayout.SimpleDrawerListener() {
                 @Override
@@ -303,6 +297,11 @@ public class MainActivity extends ProfileThemedActivity {
         Data.lastUpdateAccountCount.observe(this, date -> refreshLastUpdateInfo());
         b.navAccountSummary.setOnClickListener(this::onAccountSummaryClicked);
         b.navLatestTransactions.setOnClickListener(this::onLatestTransactionsClicked);
+        b.navPatterns.setOnClickListener(this::onPatternsClick);
+    }
+    private void onPatternsClick(View view) {
+        Intent intent = new Intent(this, PatternsActivity.class);
+        startActivity(intent);
     }
     private void scheduleDataRetrievalIfStale(long lastUpdate) {
         long now = new Date().getTime();
index 15fe95e6dec2b3017d40bc133042eca2fdeaac59..21d6394080991ad598f81f6e7adc279235f273aa 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright © 2020 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
@@ -17,7 +17,9 @@
 
 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;
@@ -29,6 +31,8 @@ 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;
@@ -53,6 +57,9 @@ import net.ktnx.mobileledger.utils.SimpleDate;
 
 import org.jetbrains.annotations.NotNull;
 
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
 /**
  * A simple {@link Fragment} subclass.
  * Activities that contain this fragment must implement the
@@ -65,6 +72,22 @@ import org.jetbrains.annotations.NotNull;
 public class NewTransactionFragment extends Fragment {
     private NewTransactionItemsAdapter listAdapter;
     private NewTransactionModel viewModel;
+    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;
@@ -72,12 +95,66 @@ 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();
@@ -100,6 +177,16 @@ public class NewTransactionFragment extends Fragment {
         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,
                              Bundle savedInstanceState) {
diff --git a/app/src/main/java/net/ktnx/mobileledger/ui/activity/PatternsActivity.java b/app/src/main/java/net/ktnx/mobileledger/ui/activity/PatternsActivity.java
new file mode 100644 (file)
index 0000000..cc057f4
--- /dev/null
@@ -0,0 +1,66 @@
+/*
+ * 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
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your opinion), any later version.
+ *
+ * MoLe is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License terms for details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with MoLe. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+package net.ktnx.mobileledger.ui.activity;
+
+import android.os.Bundle;
+import android.view.Menu;
+import android.view.View;
+
+import androidx.recyclerview.widget.LinearLayoutManager;
+import androidx.recyclerview.widget.RecyclerView;
+
+import com.google.android.material.snackbar.Snackbar;
+
+import net.ktnx.mobileledger.R;
+import net.ktnx.mobileledger.databinding.ActivityPatternsBinding;
+import net.ktnx.mobileledger.ui.patterns.PatternsModel;
+import net.ktnx.mobileledger.ui.patterns.PatternsRecyclerViewAdapter;
+
+public class PatternsActivity extends CrashReportingActivity {
+
+    @Override
+    public boolean onCreateOptionsMenu(Menu menu) {
+        super.onCreateOptionsMenu(menu);
+        getMenuInflater().inflate(R.menu.pattern_list_menu, menu);
+
+        return true;
+    }
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        ActivityPatternsBinding b = ActivityPatternsBinding.inflate(getLayoutInflater());
+        setContentView(b.getRoot());
+        setSupportActionBar(b.toolbar);
+        b.toolbarLayout.setTitle(getTitle());
+
+        b.fab.setOnClickListener(this::fabClicked);
+
+        PatternsRecyclerViewAdapter modelAdapter = new PatternsRecyclerViewAdapter();
+
+        b.patternList.setAdapter(modelAdapter);
+        PatternsModel.retrievePatterns(modelAdapter);
+        LinearLayoutManager llm = new LinearLayoutManager(this);
+        llm.setOrientation(RecyclerView.VERTICAL);
+        b.patternList.setLayoutManager(llm);
+    }
+    private void fabClicked(View view) {
+        Snackbar.make(view, "Replace with your own action", Snackbar.LENGTH_INDEFINITE)
+                .setAction("Action", null)
+                .show();
+    }
+}
\ No newline at end of file
diff --git a/app/src/main/java/net/ktnx/mobileledger/ui/patterns/PatternViewHolder.java b/app/src/main/java/net/ktnx/mobileledger/ui/patterns/PatternViewHolder.java
new file mode 100644 (file)
index 0000000..0a60ab3
--- /dev/null
@@ -0,0 +1,35 @@
+/*
+ * 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
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your opinion), any later version.
+ *
+ * MoLe is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License terms for details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with MoLe. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+package net.ktnx.mobileledger.ui.patterns;
+
+import androidx.annotation.NonNull;
+import androidx.recyclerview.widget.RecyclerView;
+
+import net.ktnx.mobileledger.databinding.PatternLayoutBinding;
+import net.ktnx.mobileledger.model.PatternEntry;
+
+class PatternViewHolder extends RecyclerView.ViewHolder {
+    final PatternLayoutBinding b;
+    public PatternViewHolder(@NonNull PatternLayoutBinding binding) {
+        super(binding.getRoot());
+        b = binding;
+    }
+    public void bindToItem(PatternEntry item) {
+        b.patternName.setText(item.getName());
+    }
+}
diff --git a/app/src/main/java/net/ktnx/mobileledger/ui/patterns/PatternsModel.java b/app/src/main/java/net/ktnx/mobileledger/ui/patterns/PatternsModel.java
new file mode 100644 (file)
index 0000000..e1cb5ba
--- /dev/null
@@ -0,0 +1,58 @@
+/*
+ * 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
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your opinion), any later version.
+ *
+ * MoLe is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License terms for details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with MoLe. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+package net.ktnx.mobileledger.ui.patterns;
+
+import android.database.Cursor;
+
+import androidx.annotation.NonNull;
+import androidx.lifecycle.MutableLiveData;
+
+import net.ktnx.mobileledger.model.PatternEntry;
+import net.ktnx.mobileledger.utils.Logger;
+import net.ktnx.mobileledger.utils.MLDB;
+
+import java.lang.ref.WeakReference;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.Locale;
+import java.util.Map;
+
+public class PatternsModel {
+    private static final MutableLiveData<Integer> patternCount = new MutableLiveData<>(0);
+    private static final Map<Integer, WeakReference<PatternEntry>> cachedEntries =
+            new HashMap<Integer, WeakReference<PatternEntry>>() {};
+    public synchronized static void retrievePatterns(PatternsRecyclerViewAdapter modelAdapter) {
+        final ArrayList<PatternEntry> idList = new ArrayList<>();
+        MLDB.queryInBackground("select id, name from patterns", null, new MLDB.CallbackHelper() {
+            @Override
+            public void onDone() {
+                modelAdapter.setPatterns(idList);
+                Logger.debug("patterns",
+                        String.format(Locale.US, "Pattern list loaded from db with %d entries",
+                                idList.size()));
+            }
+            @Override
+            public boolean onRow(@NonNull Cursor cursor) {
+                final PatternEntry entry = new PatternEntry(cursor.getInt(0));
+                entry.setName(cursor.getString(1));
+                idList.add(entry);
+                return true;
+            }
+        });
+    }
+}
diff --git a/app/src/main/java/net/ktnx/mobileledger/ui/patterns/PatternsRecyclerViewAdapter.java b/app/src/main/java/net/ktnx/mobileledger/ui/patterns/PatternsRecyclerViewAdapter.java
new file mode 100644 (file)
index 0000000..c11a685
--- /dev/null
@@ -0,0 +1,74 @@
+/*
+ * 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
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your opinion), any later version.
+ *
+ * MoLe is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License terms for details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with MoLe. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+package net.ktnx.mobileledger.ui.patterns;
+
+import android.view.LayoutInflater;
+import android.view.ViewGroup;
+
+import androidx.annotation.NonNull;
+import androidx.recyclerview.widget.AsyncListDiffer;
+import androidx.recyclerview.widget.DiffUtil;
+import androidx.recyclerview.widget.RecyclerView;
+
+import net.ktnx.mobileledger.databinding.PatternLayoutBinding;
+import net.ktnx.mobileledger.model.PatternEntry;
+
+import org.jetbrains.annotations.NotNull;
+
+import java.util.List;
+import java.util.Objects;
+
+public class PatternsRecyclerViewAdapter extends RecyclerView.Adapter<PatternViewHolder> {
+    private final AsyncListDiffer<PatternEntry> listDiffer;
+    public PatternsRecyclerViewAdapter() {
+        listDiffer = new AsyncListDiffer<>(this, new DiffUtil.ItemCallback<PatternEntry>() {
+            @Override
+            public boolean areItemsTheSame(@NotNull PatternEntry oldItem,
+                                           @NotNull PatternEntry newItem) {
+                return oldItem.getId() == newItem.getId();
+            }
+            @Override
+            public boolean areContentsTheSame(@NotNull PatternEntry oldItem,
+                                              @NotNull PatternEntry newItem) {
+                return Objects.equals(oldItem.getName(), newItem.getName());
+            }
+        });
+    }
+    @NonNull
+    @Override
+    public PatternViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
+        PatternLayoutBinding b =
+                PatternLayoutBinding.inflate(LayoutInflater.from(parent.getContext()), parent,
+                        false);
+
+        return new PatternViewHolder(b);
+    }
+    @Override
+    public void onBindViewHolder(@NonNull PatternViewHolder holder, int position) {
+        holder.bindToItem(listDiffer.getCurrentList()
+                                    .get(position));
+    }
+    @Override
+    public int getItemCount() {
+        return listDiffer.getCurrentList()
+                         .size();
+    }
+    public void setPatterns(List<PatternEntry> newList) {
+        listDiffer.submitList(newList);
+    }
+}
index 178b7b2017484f6e93fa16bf09a60dbc8671b76e..16e46e68a6439d8b6fd6725fa928291375af18db 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
@@ -199,8 +199,8 @@ public final class MLDB {
                     (parent, itemView, position, id) -> callback.descriptionSelected(
                             String.valueOf(view.getText())));
     }
-    public static void queryInBackground(@NonNull String statement, @NonNull String[] params,
-                                         @NonNull CallbackHelper callbackHelper) {
+    public static void queryInBackground(@NonNull String statement, String[] params,
+                                         @NonNull final CallbackHelper callbackHelper) {
         /* All callbacks are called in the new (asynchronous) thread! */
         Thread t = new Thread(() -> {
             callbackHelper.onStart();
diff --git a/app/src/main/res/drawable-anydpi/ic_baseline_auto_graph_24.xml b/app/src/main/res/drawable-anydpi/ic_baseline_auto_graph_24.xml
new file mode 100644 (file)
index 0000000..955af72
--- /dev/null
@@ -0,0 +1,30 @@
+<!--
+  ~ 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
+  ~ the Free Software Foundation, either version 3 of the License, or
+  ~ (at your opinion), any later version.
+  ~
+  ~ MoLe is distributed in the hope that it will be useful,
+  ~ but WITHOUT ANY WARRANTY; without even the implied warranty of
+  ~ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+  ~ GNU General Public License terms for details.
+  ~
+  ~ You should have received a copy of the GNU General Public License
+  ~ along with MoLe. If not, see <https://www.gnu.org/licenses/>.
+  -->
+
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="24dp"
+    android:height="24dp"
+    android:autoMirrored="true"
+    android:tint="?colorPrimary"
+    android:viewportWidth="24"
+    android:viewportHeight="24"
+    >
+    <path
+        android:fillColor="@android:color/white"
+        android:pathData="M14.06,9.94L12,9l2.06,-0.94L15,6l0.94,2.06L18,9l-2.06,0.94L15,12L14.06,9.94zM4,14l0.94,-2.06L7,11l-2.06,-0.94L4,8l-0.94,2.06L1,11l2.06,0.94L4,14zM8.5,9l1.09,-2.41L12,5.5L9.59,4.41L8.5,2L7.41,4.41L5,5.5l2.41,1.09L8.5,9zM4.5,20.5l6,-6.01l4,4L23,8.93l-1.41,-1.41l-7.09,7.97l-4,-4L3,19L4.5,20.5z"
+        />
+</vector>
diff --git a/app/src/main/res/drawable-anydpi/ic_baseline_help_24.xml b/app/src/main/res/drawable-anydpi/ic_baseline_help_24.xml
new file mode 100644 (file)
index 0000000..47d7b42
--- /dev/null
@@ -0,0 +1,30 @@
+<!--
+  ~ 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
+  ~ the Free Software Foundation, either version 3 of the License, or
+  ~ (at your opinion), any later version.
+  ~
+  ~ MoLe is distributed in the hope that it will be useful,
+  ~ but WITHOUT ANY WARRANTY; without even the implied warranty of
+  ~ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+  ~ GNU General Public License terms for details.
+  ~
+  ~ You should have received a copy of the GNU General Public License
+  ~ along with MoLe. If not, see <https://www.gnu.org/licenses/>.
+  -->
+
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="24dp"
+    android:height="24dp"
+    android:autoMirrored="true"
+    android:tint="?colorPrimary"
+    android:viewportWidth="24"
+    android:viewportHeight="24"
+    >
+    <path
+        android:fillColor="@android:color/white"
+        android:pathData="M12,2C6.48,2 2,6.48 2,12s4.48,10 10,10 10,-4.48 10,-10S17.52,2 12,2zM13,19h-2v-2h2v2zM15.07,11.25l-0.9,0.92C13.45,12.9 13,13.5 13,15h-2v-0.5c0,-1.1 0.45,-2.1 1.17,-2.83l1.24,-1.26c0.37,-0.36 0.59,-0.86 0.59,-1.41 0,-1.1 -0.9,-2 -2,-2s-2,0.9 -2,2L8,9c0,-2.21 1.79,-4 4,-4s4,1.79 4,4c0,0.88 -0.36,1.68 -0.93,2.25z"
+        />
+</vector>
diff --git a/app/src/main/res/drawable-anydpi/ic_baseline_help_24_white.xml b/app/src/main/res/drawable-anydpi/ic_baseline_help_24_white.xml
new file mode 100644 (file)
index 0000000..3cbf715
--- /dev/null
@@ -0,0 +1,30 @@
+<!--
+  ~ 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
+  ~ the Free Software Foundation, either version 3 of the License, or
+  ~ (at your opinion), any later version.
+  ~
+  ~ MoLe is distributed in the hope that it will be useful,
+  ~ but WITHOUT ANY WARRANTY; without even the implied warranty of
+  ~ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+  ~ GNU General Public License terms for details.
+  ~
+  ~ You should have received a copy of the GNU General Public License
+  ~ along with MoLe. If not, see <https://www.gnu.org/licenses/>.
+  -->
+
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="24dp"
+    android:height="24dp"
+    android:autoMirrored="true"
+    android:tint="?colorOnPrimary"
+    android:viewportWidth="24"
+    android:viewportHeight="24"
+    >
+    <path
+        android:fillColor="@android:color/white"
+        android:pathData="M12,2C6.48,2 2,6.48 2,12s4.48,10 10,10 10,-4.48 10,-10S17.52,2 12,2zM13,19h-2v-2h2v2zM15.07,11.25l-0.9,0.92C13.45,12.9 13,13.5 13,15h-2v-0.5c0,-1.1 0.45,-2.1 1.17,-2.83l1.24,-1.26c0.37,-0.36 0.59,-0.86 0.59,-1.41 0,-1.1 -0.9,-2 -2,-2s-2,0.9 -2,2L8,9c0,-2.21 1.79,-4 4,-4s4,1.79 4,4c0,0.88 -0.36,1.68 -0.93,2.25z"
+        />
+</vector>
diff --git a/app/src/main/res/drawable-anydpi/ic_baseline_qr_code_scanner_24.xml b/app/src/main/res/drawable-anydpi/ic_baseline_qr_code_scanner_24.xml
new file mode 100644 (file)
index 0000000..051367b
--- /dev/null
@@ -0,0 +1,30 @@
+<!--
+  ~ 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
+  ~ the Free Software Foundation, either version 3 of the License, or
+  ~ (at your opinion), any later version.
+  ~
+  ~ MoLe is distributed in the hope that it will be useful,
+  ~ but WITHOUT ANY WARRANTY; without even the implied warranty of
+  ~ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+  ~ GNU General Public License terms for details.
+  ~
+  ~ You should have received a copy of the GNU General Public License
+  ~ along with MoLe. If not, see <https://www.gnu.org/licenses/>.
+  -->
+
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="24dp"
+    android:height="24dp"
+    android:autoMirrored="true"
+    android:tint="#EEEEEE"
+    android:viewportWidth="24"
+    android:viewportHeight="24"
+    >
+    <path
+        android:fillColor="@android:color/white"
+        android:pathData="M9.5,6.5v3h-3v-3H9.5M11,5H5v6h6V5L11,5zM9.5,14.5v3h-3v-3H9.5M11,13H5v6h6V13L11,13zM17.5,6.5v3h-3v-3H17.5M19,5h-6v6h6V5L19,5zM13,13h1.5v1.5H13V13zM14.5,14.5H16V16h-1.5V14.5zM16,13h1.5v1.5H16V13zM13,16h1.5v1.5H13V16zM14.5,17.5H16V19h-1.5V17.5zM16,16h1.5v1.5H16V16zM17.5,14.5H19V16h-1.5V14.5zM17.5,17.5H19V19h-1.5V17.5zM22,7h-2V4h-3V2h5V7zM22,22v-5h-2v3h-3v2H22zM2,22h5v-2H4v-3H2V22zM2,2v5h2V4h3V2H2z"
+        />
+</vector>
index d30b88a1015dd95dffed8db4a71e3dd2dac56518..ede0f620caea7d06b558f391f861a5a1bbc22e04 100644 (file)
@@ -1,5 +1,5 @@
 <?xml version="1.0" encoding="utf-8"?><!--
-  ~ Copyright © 2020 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
         android:layout_height="match_parent"
         android:background="?android:attr/colorBackground"
         android:orientation="vertical"
-        android:visibility="gone"
+        android:visibility="visible"
         >
 
         <com.google.android.material.floatingactionbutton.FloatingActionButton
                                 </LinearLayout>
 
                             </LinearLayout>
+                            <TextView
+                                android:id="@+id/nav_patterns"
+                                style="@style/nav_button"
+                                android:text="@string/nav_patterns"
+                                app:drawableStartCompat="@drawable/ic_baseline_auto_graph_24"
+                                />
 
                         </LinearLayout>
                     </ScrollView>
diff --git a/app/src/main/res/layout/activity_patterns.xml b/app/src/main/res/layout/activity_patterns.xml
new file mode 100644 (file)
index 0000000..52ea065
--- /dev/null
@@ -0,0 +1,82 @@
+<?xml version="1.0" encoding="utf-8"?><!--
+  ~ 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
+  ~ the Free Software Foundation, either version 3 of the License, or
+  ~ (at your opinion), any later version.
+  ~
+  ~ MoLe is distributed in the hope that it will be useful,
+  ~ but WITHOUT ANY WARRANTY; without even the implied warranty of
+  ~ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+  ~ GNU General Public License terms for details.
+  ~
+  ~ You should have received a copy of the GNU General Public License
+  ~ along with MoLe. If not, see <https://www.gnu.org/licenses/>.
+  -->
+
+<androidx.coordinatorlayout.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto"
+    xmlns:tools="http://schemas.android.com/tools"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:fitsSystemWindows="true"
+
+    tools:context=".ui.activity.PatternsActivity"
+    >
+
+    <com.google.android.material.appbar.AppBarLayout
+        android:id="@+id/app_bar"
+        android:layout_width="match_parent"
+        android:layout_height="@dimen/app_bar_height"
+        android:fitsSystemWindows="true"
+        >
+
+        <com.google.android.material.appbar.CollapsingToolbarLayout
+            android:id="@+id/toolbar_layout"
+            android:layout_width="match_parent"
+            android:layout_height="match_parent"
+            android:fitsSystemWindows="true"
+            app:contentScrim="?attr/colorPrimary"
+            app:layout_scrollFlags="scroll|exitUntilCollapsed"
+            app:toolbarId="@+id/toolbar"
+            >
+
+            <androidx.appcompat.widget.Toolbar
+                android:id="@+id/toolbar"
+                android:layout_width="match_parent"
+                android:layout_height="?attr/actionBarSize"
+                app:layout_collapseMode="pin"
+                app:popupTheme="@style/AppTheme.PopupOverlay"
+                />
+
+        </com.google.android.material.appbar.CollapsingToolbarLayout>
+    </com.google.android.material.appbar.AppBarLayout>
+
+    <androidx.core.widget.NestedScrollView
+        android:layout_width="match_parent"
+        android:layout_height="match_parent"
+        app:layout_behavior="@string/appbar_scrolling_view_behavior"
+        tools:context=".ui.activity.PatternsActivity"
+        tools:showIn="@layout/activity_patterns"
+        >
+
+        <androidx.recyclerview.widget.RecyclerView
+            android:id="@+id/pattern_list"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            />
+
+    </androidx.core.widget.NestedScrollView>
+    <com.google.android.material.floatingactionbutton.FloatingActionButton
+        android:id="@+id/fab"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_margin="@dimen/fab_margin"
+        app:layout_anchor="@id/pattern_list"
+        app:layout_anchorGravity="top|end"
+        app:layout_behavior="com.google.android.material.floatingactionbutton.FloatingActionButton$Behavior"
+        app:srcCompat="@drawable/ic_add_white_24dp"
+        />
+
+</androidx.coordinatorlayout.widget.CoordinatorLayout>
\ No newline at end of file
diff --git a/app/src/main/res/layout/pattern_layout.xml b/app/src/main/res/layout/pattern_layout.xml
new file mode 100644 (file)
index 0000000..6c1928f
--- /dev/null
@@ -0,0 +1,44 @@
+<?xml version="1.0" encoding="utf-8"?><!--
+  ~ 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
+  ~ the Free Software Foundation, either version 3 of the License, or
+  ~ (at your opinion), any later version.
+  ~
+  ~ MoLe is distributed in the hope that it will be useful,
+  ~ but WITHOUT ANY WARRANTY; without even the implied warranty of
+  ~ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+  ~ GNU General Public License terms for details.
+  ~
+  ~ You should have received a copy of the GNU General Public License
+  ~ along with MoLe. If not, see <https://www.gnu.org/licenses/>.
+  -->
+
+<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto"
+    android:layout_width="match_parent"
+    android:layout_height="wrap_content"
+    >
+    <TextView
+        android:id="@+id/pattern_name"
+        android:layout_width="0dp"
+        android:layout_height="wrap_content"
+        android:layout_marginStart="@dimen/text_margin"
+        android:layout_marginEnd="@dimen/text_margin"
+        app:layout_constraintBottom_toBottomOf="parent"
+        app:layout_constraintEnd_toStartOf="@id/edit_buton"
+        app:layout_constraintStart_toStartOf="parent"
+        app:layout_constraintTop_toTopOf="parent"
+        />
+    <ImageButton
+        android:id="@+id/edit_buton"
+        android:layout_width="@dimen/toolbar_height"
+        android:layout_height="@dimen/toolbar_height"
+        android:contentDescription="@string/edit_button_description"
+        android:src="@drawable/ic_mode_edit_black_24dp"
+        app:layout_constraintBottom_toBottomOf="parent"
+        app:layout_constraintEnd_toEndOf="parent"
+        app:layout_constraintTop_toTopOf="parent"
+        />
+</androidx.constraintlayout.widget.ConstraintLayout>
\ No newline at end of file
index c44acf51d777bafc5dc13b390f963b83dfec82f6..1fe3824330b8fc54a396d97f8c111835b01df46d 100644 (file)
@@ -1,6 +1,6 @@
 <?xml version="1.0" encoding="utf-8"?>
 <!--
-  ~ Copyright © 2020 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
   -->
 
 <menu xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:app="http://schemas.android.com/apk/res-auto">
+    xmlns:app="http://schemas.android.com/apk/res-auto"
+    >
+    <item
+        android:id="@+id/scan_qr"
+        android:icon="@drawable/ic_baseline_qr_code_scanner_24"
+        android:title="@string/scan_qr"
+        app:showAsAction="ifRoom"
+        />
     <item
         android:id="@+id/toggle_currency"
         android:title="@string/show_currency_input"
         android:checkable="true"
         android:checked="false"
         app:actionLayout="@layout/switch_item"
-        app:showAsAction="never" />
+        app:showAsAction="never"
+        />
     <item
         android:id="@+id/toggle_comments"
         android:checkable="true"
diff --git a/app/src/main/res/menu/pattern_list_menu.xml b/app/src/main/res/menu/pattern_list_menu.xml
new file mode 100644 (file)
index 0000000..a231f5f
--- /dev/null
@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="utf-8"?><!--
+  ~ 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
+  ~ the Free Software Foundation, either version 3 of the License, or
+  ~ (at your opinion), any later version.
+  ~
+  ~ MoLe is distributed in the hope that it will be useful,
+  ~ but WITHOUT ANY WARRANTY; without even the implied warranty of
+  ~ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+  ~ GNU General Public License terms for details.
+  ~
+  ~ You should have received a copy of the GNU General Public License
+  ~ along with MoLe. If not, see <https://www.gnu.org/licenses/>.
+  -->
+
+<menu xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto"
+    >
+
+    <item
+        android:icon="@drawable/ic_baseline_help_24_white"
+        android:title="@string/help_menu_item_title"
+        app:showAsAction="ifRoom"
+        />
+</menu>
\ No newline at end of file
index d52c59cee0a9eabb8c575c28cb6dc533ce16842d..17f038ecedd413c24f03e32b08003c11fac5a5d0 100644 (file)
@@ -1,6 +1,6 @@
 <?xml version="1.0" encoding="utf-8"?>
 <!--
-  ~ Copyright © 2020 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
     <string name="btn_profile_options">Настройка на профила</string>
     <string name="err_json_send_error_head">Грешка при изпращане на движението към сървъра</string>
     <string name="err_json_send_error_tail">Вероятно настроената версия на протокола не се поддържа.</string>
+    <string name="err_json_send_error_unsupported">Възможно е програмния интерфейс на сървъра да не се поддържа от MoLe</string>
+    <string name="scan_qr">Сканиране на QR код</string>
+    <string name="nav_patterns">Шаблони</string>
+    <string name="title_activity_patterns">Шаблони</string>
 </resources>
index 63fb70e4a84db6895df65552262b67791afff3e0..0d1a17a0ebfebf70cc3ffb0aaa9f71a26124bf0a 100644 (file)
@@ -1,5 +1,5 @@
 <!--
-  ~ Copyright © 2020 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
     <string name="err_json_send_error_head">Error storing transaction on backend server</string>
     <string name="err_json_send_error_tail">A mismatch in the configured API version could be causing this</string>
     <string name="err_json_send_error_unsupported">Perhaps the API of the backend server is not supported by MoLe</string>
+    <string name="scan_qr">Scan QR code</string>
+    <string name="nav_patterns">Patterns</string>
+    <string name="title_activity_patterns">Patterns</string>
+    <string name="pattern_regex_hint">Pattern (regular expression)</string>
+    <string name="help_menu_item_title">Help</string>
+    <string name="edit_button_description">Edit button</string>
 </resources>