]> git.ktnx.net Git - mobile-ledger.git/commitdiff
show pattern match results in template editor
authorDamyan Ivanov <dam+mobileledger@ktnx.net>
Sat, 13 Feb 2021 15:36:50 +0000 (17:36 +0200)
committerDamyan Ivanov <dam+mobileledger@ktnx.net>
Thu, 18 Feb 2021 07:19:43 +0000 (07:19 +0000)
app/src/main/java/net/ktnx/mobileledger/model/TemplateDetailsItem.java
app/src/main/java/net/ktnx/mobileledger/ui/TemplateDetailSourceSelectorFragment.java
app/src/main/java/net/ktnx/mobileledger/ui/templates/TemplateDetailsAdapter.java
app/src/main/java/net/ktnx/mobileledger/ui/templates/TemplateDetailsViewModel.java
app/src/main/res/layout/template_details_header.xml
app/src/main/res/values-bg/strings.xml
app/src/main/res/values/strings.xml

index 1f3211c4c797de00091ada0403b0a31b0bcb3c49..822f743880f52b2fcac77be209d490511d75b6e6 100644 (file)
 package net.ktnx.mobileledger.model;
 
 import android.content.res.Resources;
+import android.graphics.Color;
+import android.graphics.Typeface;
+import android.text.SpannableString;
+import android.text.Spanned;
+import android.text.style.ForegroundColorSpan;
+import android.text.style.StyleSpan;
+import android.text.style.UnderlineSpan;
 
 import androidx.annotation.NonNull;
 
@@ -30,6 +37,8 @@ import net.ktnx.mobileledger.utils.Misc;
 import org.jetbrains.annotations.Contract;
 import org.jetbrains.annotations.NotNull;
 
+import java.util.ArrayList;
+import java.util.Objects;
 import java.util.regex.Matcher;
 import java.util.regex.Pattern;
 import java.util.regex.PatternSyntaxException;
@@ -393,9 +402,9 @@ abstract public class TemplateDetailsItem {
     public static class Header extends TemplateDetailsItem {
         private String pattern = "";
         private String testText = "";
+        private String name = "";
         private Pattern compiledPattern;
         private String patternError;
-        private String name = "";
         private PossiblyMatchedValue<String> transactionDescription =
                 PossiblyMatchedValue.withLiteralString("");
         private PossiblyMatchedValue<String> transactionComment =
@@ -403,6 +412,7 @@ abstract public class TemplateDetailsItem {
         private PossiblyMatchedValue<Integer> dateYear = PossiblyMatchedValue.withLiteralInt(null);
         private PossiblyMatchedValue<Integer> dateMonth = PossiblyMatchedValue.withLiteralInt(null);
         private PossiblyMatchedValue<Integer> dateDay = PossiblyMatchedValue.withLiteralInt(null);
+        private SpannableString testMatch;
         private Header() {
             super(Type.HEADER);
         }
@@ -411,6 +421,7 @@ abstract public class TemplateDetailsItem {
             id = origin.id;
             name = origin.name;
             testText = origin.testText;
+            testMatch = origin.testMatch;
             setPattern(origin.pattern);
 
             transactionDescription = new PossiblyMatchedValue<>(origin.transactionDescription);
@@ -420,6 +431,11 @@ abstract public class TemplateDetailsItem {
             dateMonth = new PossiblyMatchedValue<>(origin.dateMonth);
             dateDay = new PossiblyMatchedValue<>(origin.dateDay);
         }
+        private static StyleSpan capturedSpan() { return new StyleSpan(Typeface.BOLD); }
+        private static UnderlineSpan matchedSpan() { return new UnderlineSpan(); }
+        private static ForegroundColorSpan notMatchedSpan() {
+            return new ForegroundColorSpan(Color.GRAY);
+        }
         public String getName() {
             return name;
         }
@@ -431,18 +447,17 @@ abstract public class TemplateDetailsItem {
         }
         public void setPattern(String pattern) {
             this.pattern = pattern;
-            if (pattern != null) {
-                try {
-                    this.compiledPattern = Pattern.compile(pattern);
-                    this.patternError = null;
-                }
-                catch (PatternSyntaxException e) {
-                    this.compiledPattern = null;
-                    this.patternError = e.getMessage();
-                }
+            try {
+                this.compiledPattern = Pattern.compile(pattern);
+                checkPatternMatch();
             }
-            else {
-                patternError = "Missing pattern";
+            catch (PatternSyntaxException ex) {
+                patternError = ex.getDescription();
+                compiledPattern = null;
+
+                testMatch = new SpannableString(testText);
+                testMatch.setSpan(notMatchedSpan(), 0, testText.length() - 1,
+                        Spanned.SPAN_INCLUSIVE_INCLUSIVE);
             }
         }
         @NonNull
@@ -457,6 +472,8 @@ abstract public class TemplateDetailsItem {
         }
         public void setTestText(String testText) {
             this.testText = testText;
+
+            checkPatternMatch();
         }
         public String getTransactionDescription() {
             return transactionDescription.getValue();
@@ -551,7 +568,9 @@ abstract public class TemplateDetailsItem {
                 return true;
 
             return Misc.equalStrings(name, o.name) && Misc.equalStrings(pattern, o.pattern) &&
-                   Misc.equalStrings(testText, o.testText);
+                   Misc.equalStrings(testText, o.testText) &&
+                   Misc.equalStrings(patternError, o.patternError) &&
+                   Objects.equals(testMatch, o.testMatch);
         }
         public String getMatchGroupText(int group) {
             if (compiledPattern != null && testText != null) {
@@ -623,5 +642,60 @@ abstract public class TemplateDetailsItem {
 
             return result;
         }
+        public SpannableString getTestMatch() {
+            return testMatch;
+        }
+        public void checkPatternMatch() {
+            patternError = null;
+            testMatch = null;
+
+            if (pattern != null) {
+                try {
+                    if (Misc.emptyIsNull(testText) != null) {
+                        SpannableString ss = new SpannableString(testText);
+                        Matcher m = compiledPattern.matcher(testText);
+                        if (m.find()) {
+                            ArrayList<String> notMatched = new ArrayList<>();
+                            if (m.start() > 0)
+                                ss.setSpan(notMatchedSpan(), 0, m.start(),
+                                        Spanned.SPAN_INCLUSIVE_INCLUSIVE);
+                            if (m.end() < testText.length() - 1)
+                                ss.setSpan(notMatchedSpan(), m.end(), testText.length() - 1,
+                                        Spanned.SPAN_INCLUSIVE_INCLUSIVE);
+
+                            ss.setSpan(matchedSpan(), m.start(0), m.end(0),
+                                    Spanned.SPAN_INCLUSIVE_INCLUSIVE);
+
+                            if (m.groupCount() > 0) {
+                                for (int g = 1; g <= m.groupCount(); g++) {
+                                    ss.setSpan(capturedSpan(), m.start(g), m.end(g),
+                                            Spanned.SPAN_INCLUSIVE_INCLUSIVE);
+                                }
+                            }
+                        }
+                        else {
+                            patternError = "Pattern does not match";
+                            ss.setSpan(new ForegroundColorSpan(Color.GRAY), 0,
+                                    testText.length() - 1, Spanned.SPAN_INCLUSIVE_INCLUSIVE);
+                        }
+
+                        testMatch = ss;
+                    }
+                }
+                catch (PatternSyntaxException e) {
+                    this.compiledPattern = null;
+                    this.patternError = e.getMessage();
+                }
+            }
+            else {
+                patternError = "Missing pattern";
+            }
+        }
+        public String getPatternError() {
+            return patternError;
+        }
+        public SpannableString testMatch() {
+            return testMatch;
+        }
     }
 }
index 1bb48404e9ace7181686887d95c8f37e76d9488b..97c88785e5dc20f361c6d5a6977832c10b3f63fd 100644 (file)
@@ -106,7 +106,7 @@ public class TemplateDetailSourceSelectorFragment extends AppCompatDialogFragmen
                     Logger.debug("templates",
                             String.format("Trying to match pattern '%s' against text '%s'",
                                     patternText, testText));
-                    if (matcher.matches()) {
+                    if (matcher.find()) {
                         if (matcher.groupCount() >= 0) {
                             ArrayList<TemplateDetailSource> list = new ArrayList<>();
                             for (short g = 1; g <= matcher.groupCount(); g++) {
@@ -159,7 +159,8 @@ public class TemplateDetailSourceSelectorFragment extends AppCompatDialogFragmen
         }
         else {
             b.list.setVisibility(View.GONE);
-            b.templateError.setText(mPatternProblem);
+            b.templateError.setText(
+                    (mPatternProblem != 0) ? mPatternProblem : R.string.pattern_without_groups);
             b.templateError.setVisibility(View.VISIBLE);
         }
 
index 46bdba55280bdc182a7b4a0d6be761a5f6a0dd51..e472049b1e5762f0a5f1ab75968d16b5b282842e 100644 (file)
@@ -196,6 +196,7 @@ class TemplateDetailsAdapter extends RecyclerView.Adapter<TemplateDetailsAdapter
                 }
             };
             b.templateName.addTextChangedListener(templateNameWatcher);
+
             TextWatcher patternWatcher = new TextWatcher() {
                 @Override
                 public void beforeTextChanged(CharSequence s, int start, int count, int after) {}
@@ -207,9 +208,12 @@ class TemplateDetailsAdapter extends RecyclerView.Adapter<TemplateDetailsAdapter
                     Logger.debug(D_TEMPLATE_UI,
                             "Storing changed pattern " + s + "; header=" + header);
                     header.setPattern(String.valueOf(s));
+
+                    checkPatternError(header);
                 }
             };
             b.pattern.addTextChangedListener(patternWatcher);
+
             TextWatcher testTextWatcher = new TextWatcher() {
                 @Override
                 public void beforeTextChanged(CharSequence s, int start, int count, int after) {}
@@ -221,9 +225,12 @@ class TemplateDetailsAdapter extends RecyclerView.Adapter<TemplateDetailsAdapter
                     Logger.debug(D_TEMPLATE_UI,
                             "Storing changed test text " + s + "; header=" + header);
                     header.setTestText(String.valueOf(s));
+
+                    checkPatternError(header);
                 }
             };
             b.testText.addTextChangedListener(testTextWatcher);
+
             TextWatcher transactionDescriptionWatcher = new TextWatcher() {
                 @Override
                 public void beforeTextChanged(CharSequence s, int start, int count, int after) {
@@ -344,10 +351,9 @@ class TemplateDetailsAdapter extends RecyclerView.Adapter<TemplateDetailsAdapter
                         String.format(Locale.US, "Group %d (%s)", header.getDateYearMatchGroup(),
                                 getMatchGroupText(header.getDateYearMatchGroup())));
             }
-            b.templateDetailsYearSourceLabel.setOnClickListener(
-                    v -> selectHeaderDetailSource(v, HeaderDetail.DATE_YEAR));
-            b.templateDetailsYearSource.setOnClickListener(
-                    v -> selectHeaderDetailSource(v, HeaderDetail.DATE_YEAR));
+            b.templateDetailsYearSourceLabel.setOnClickListener(v -> selectHeaderDetailSource(v, HeaderDetail.DATE_YEAR));
+            b.templateDetailsYearSource.setOnClickListener(v -> selectHeaderDetailSource(v,
+                    HeaderDetail.DATE_YEAR));
 
             if (header.hasLiteralDateMonth()) {
                 b.templateDetailsMonthSource.setText(R.string.template_details_source_literal);
@@ -362,16 +368,15 @@ class TemplateDetailsAdapter extends RecyclerView.Adapter<TemplateDetailsAdapter
                         String.format(Locale.US, "Group %d (%s)", header.getDateMonthMatchGroup(),
                                 getMatchGroupText(header.getDateMonthMatchGroup())));
             }
-            b.templateDetailsMonthSourceLabel.setOnClickListener(
-                    v -> selectHeaderDetailSource(v, HeaderDetail.DATE_MONTH));
-            b.templateDetailsMonthSource.setOnClickListener(
-                    v -> selectHeaderDetailSource(v, HeaderDetail.DATE_MONTH));
+            b.templateDetailsMonthSourceLabel.setOnClickListener(v -> selectHeaderDetailSource(v,
+                    HeaderDetail.DATE_MONTH));
+            b.templateDetailsMonthSource.setOnClickListener(v -> selectHeaderDetailSource(v,
+                    HeaderDetail.DATE_MONTH));
 
             if (header.hasLiteralDateDay()) {
                 b.templateDetailsDaySource.setText(R.string.template_details_source_literal);
                 final Integer dateDay = header.getDateDay();
-                b.templateDetailsDateDay.setText(
-                        (dateDay == null) ? null : String.valueOf(dateDay));
+                b.templateDetailsDateDay.setText((dateDay == null) ? null : String.valueOf(dateDay));
                 b.templateDetailsDateDayLayout.setVisibility(View.VISIBLE);
             }
             else {
@@ -380,14 +385,11 @@ class TemplateDetailsAdapter extends RecyclerView.Adapter<TemplateDetailsAdapter
                         String.format(Locale.US, "Group %d (%s)", header.getDateDayMatchGroup(),
                                 getMatchGroupText(header.getDateDayMatchGroup())));
             }
-            b.templateDetailsDaySourceLabel.setOnClickListener(
-                    v -> selectHeaderDetailSource(v, HeaderDetail.DATE_DAY));
-            b.templateDetailsDaySource.setOnClickListener(
-                    v -> selectHeaderDetailSource(v, HeaderDetail.DATE_DAY));
+            b.templateDetailsDaySourceLabel.setOnClickListener(v -> selectHeaderDetailSource(v, HeaderDetail.DATE_DAY));
+            b.templateDetailsDaySource.setOnClickListener(v -> selectHeaderDetailSource(v, HeaderDetail.DATE_DAY));
 
             if (header.hasLiteralTransactionDescription()) {
-                b.templateTransactionDescriptionSource.setText(
-                        R.string.template_details_source_literal);
+                b.templateTransactionDescriptionSource.setText(R.string.template_details_source_literal);
                 b.transactionDescription.setText(header.getTransactionDescription());
                 b.transactionDescriptionLayout.setVisibility(View.VISIBLE);
             }
@@ -395,18 +397,14 @@ class TemplateDetailsAdapter extends RecyclerView.Adapter<TemplateDetailsAdapter
                 b.transactionDescriptionLayout.setVisibility(View.GONE);
                 b.templateTransactionDescriptionSource.setText(
                         String.format(Locale.US, "Group %d (%s)",
-                                header.getTransactionDescriptionMatchGroup(),
-                                getMatchGroupText(header.getTransactionDescriptionMatchGroup())));
+                                header.getTransactionDescriptionMatchGroup(), getMatchGroupText(header.getTransactionDescriptionMatchGroup())));
 
             }
-            b.templateTransactionDescriptionSourceLabel.setOnClickListener(
-                    v -> selectHeaderDetailSource(v, HeaderDetail.DESCRIPTION));
-            b.templateTransactionDescriptionSource.setOnClickListener(
-                    v -> selectHeaderDetailSource(v, HeaderDetail.DESCRIPTION));
+            b.templateTransactionDescriptionSourceLabel.setOnClickListener(v -> selectHeaderDetailSource(v, HeaderDetail.DESCRIPTION));
+            b.templateTransactionDescriptionSource.setOnClickListener(v -> selectHeaderDetailSource(v, HeaderDetail.DESCRIPTION));
 
             if (header.hasLiteralTransactionComment()) {
-                b.templateTransactionCommentSource.setText(
-                        R.string.template_details_source_literal);
+                b.templateTransactionCommentSource.setText(R.string.template_details_source_literal);
                 b.transactionComment.setText(header.getTransactionComment());
                 b.transactionCommentLayout.setVisibility(View.VISIBLE);
             }
@@ -424,6 +422,28 @@ class TemplateDetailsAdapter extends RecyclerView.Adapter<TemplateDetailsAdapter
 
             b.templateDetailsHeadScanQrButton.setOnClickListener(this::scanTestQR);
 
+            checkPatternError(header);
+        }
+        private void checkPatternError(TemplateDetailsItem.Header item) {
+            if (item.getPatternError() != null) {
+                b.patternLayout.setError(item.getPatternError());
+                b.patternHintTitle.setVisibility(View.GONE);
+                b.patternHintText.setVisibility(View.GONE);
+            }
+            else {
+                b.patternLayout.setError(null);
+                if (item.testMatch() != null) {
+                    b.patternHintText.setText(item.testMatch());
+                    b.patternHintTitle.setVisibility(View.VISIBLE);
+                    b.patternHintText.setVisibility(View.VISIBLE);
+                }
+                else {
+                    b.patternLayout.setError(null);
+                    b.patternHintTitle.setVisibility(View.GONE);
+                    b.patternHintText.setVisibility(View.GONE);
+                }
+            }
+
         }
         private void scanTestQR(View view) {
             QRScanCapableFragment.triggerQRScan();
index a5f5e0f9bac7e59e8967af0ca791965feb5c0f54..f5dacdc1de2f57cff00b74a5db84429b44810c20 100644 (file)
@@ -44,6 +44,7 @@ public class TemplateDetailsViewModel extends ViewModel {
             new MutableLiveData<>(Collections.emptyList());
     private Long mPatternId;
     private String mDefaultPatternName;
+
     public String getDefaultPatternName() {
         return mDefaultPatternName;
     }
index d16ce4ebc58d4032659e64e70d201c6c792cdfe4..58b5535f1101fd8e5caf082ddfabdfd64db5e919 100644 (file)
@@ -43,6 +43,8 @@
         android:layout_width="0dp"
         android:layout_height="wrap_content"
         android:textAppearance="?attr/textAppearanceListItem"
+        app:endIconMode="clear_text"
+        app:layout_constraintBottom_toTopOf="@id/pattern_hint_title"
         app:layout_constraintEnd_toEndOf="parent"
         app:layout_constraintStart_toStartOf="parent"
         app:layout_constraintTop_toBottomOf="@id/pattern_name_layout"
             android:inputType="text"
             />
     </com.google.android.material.textfield.TextInputLayout>
+    <TextView
+        android:id="@+id/pattern_hint_title"
+        android:layout_width="0dp"
+        android:layout_height="wrap_content"
+        android:text="@string/pattern_match_result"
+        android:textAppearance="@style/TextAppearance.MaterialComponents.Body2"
+        app:layout_constraintBottom_toTopOf="@+id/pattern_hint_text"
+        app:layout_constraintEnd_toEndOf="parent"
+        app:layout_constraintStart_toStartOf="parent"
+        app:layout_constraintTop_toBottomOf="@id/pattern_layout"
+        />
+    <TextView
+        android:id="@+id/pattern_hint_text"
+        android:layout_width="0dp"
+        android:layout_height="match_parent"
+        android:layout_marginBottom="@dimen/text_margin"
+        android:textAppearance="@style/TextAppearance.MaterialComponents.Body1"
+        app:layout_constraintBottom_toTopOf="@+id/test_text_layout"
+        app:layout_constraintEnd_toEndOf="parent"
+        app:layout_constraintStart_toStartOf="parent"
+        app:layout_constraintTop_toBottomOf="@id/pattern_hint_title"
+        />
     <com.google.android.material.textfield.TextInputLayout
         android:id="@+id/test_text_layout"
         android:layout_width="0dp"
@@ -62,7 +86,7 @@
         android:textAppearance="?attr/textAppearanceListItem"
         app:layout_constraintEnd_toStartOf="@id/template_details_head_scan_qr_button"
         app:layout_constraintStart_toStartOf="parent"
-        app:layout_constraintTop_toBottomOf="@id/pattern_layout"
+        app:layout_constraintTop_toBottomOf="@id/pattern_hint_text"
         >
         <com.google.android.material.textfield.TextInputEditText
             android:id="@+id/test_text"
index 2654275a611d8a7e53f9644a41e179cae4c17205..dd5d06f3ea36dcfb8819acc77e3fc77206025443 100644 (file)
     <string name="account_amount_source_label">Източник на името на сметката</string>
     <string name="template_xxx_deleted">Макетът „%s“ е изтрит</string>
     <string name="action_undo">Връщане</string>
+    <string name="pattern_match_result">Резултат от прилагането на шаблона</string>
 </resources>
index 8e126f281cdaeb506865d30ea30f27ccc121feaf..fd13e0665b0cf1f41f04b7cbd5601b3eddc6a717 100644 (file)
     <string name="title_new_template">New template</string>
     <string name="template_xxx_deleted">Template \'%s\' deleted</string>
     <string name="action_undo">Undo</string>
+    <string name="pattern_match_result">Pattern match result</string>
 </resources>