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;
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;
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 =
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);
}
id = origin.id;
name = origin.name;
testText = origin.testText;
+ testMatch = origin.testMatch;
setPattern(origin.pattern);
transactionDescription = new PossiblyMatchedValue<>(origin.transactionDescription);
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;
}
}
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
}
public void setTestText(String testText) {
this.testText = testText;
+
+ checkPatternMatch();
}
public String getTransactionDescription() {
return transactionDescription.getValue();
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) {
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;
+ }
}
}
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++) {
}
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);
}
}
};
b.templateName.addTextChangedListener(templateNameWatcher);
+
TextWatcher patternWatcher = new TextWatcher() {
@Override
public void beforeTextChanged(CharSequence s, int start, int count, int after) {}
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) {}
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) {
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);
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 {
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);
}
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);
}
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();
new MutableLiveData<>(Collections.emptyList());
private Long mPatternId;
private String mDefaultPatternName;
+
public String getDefaultPatternName() {
return mDefaultPatternName;
}
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"
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"
<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>
<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>