2 * Copyright © 2021 Damyan Ivanov.
3 * This file is part of MoLe.
4 * MoLe is free software: you can distribute it and/or modify it
5 * under the term of the GNU General Public License as published by
6 * the Free Software Foundation, either version 3 of the License, or
7 * (at your opinion), any later version.
9 * MoLe is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU General Public License terms for details.
14 * You should have received a copy of the GNU General Public License
15 * along with MoLe. If not, see <https://www.gnu.org/licenses/>.
18 package net.ktnx.mobileledger.ui.patterns;
20 import android.text.Editable;
21 import android.text.TextWatcher;
22 import android.view.LayoutInflater;
23 import android.view.View;
24 import android.view.ViewGroup;
26 import androidx.annotation.NonNull;
27 import androidx.appcompat.app.AppCompatActivity;
28 import androidx.recyclerview.widget.AsyncListDiffer;
29 import androidx.recyclerview.widget.DiffUtil;
30 import androidx.recyclerview.widget.RecyclerView;
32 import net.ktnx.mobileledger.R;
33 import net.ktnx.mobileledger.databinding.PatternDetailsAccountBinding;
34 import net.ktnx.mobileledger.databinding.PatternDetailsHeaderBinding;
35 import net.ktnx.mobileledger.db.PatternBase;
36 import net.ktnx.mobileledger.model.Data;
37 import net.ktnx.mobileledger.model.PatternDetailsItem;
38 import net.ktnx.mobileledger.ui.PatternDetailSourceSelectorFragment;
39 import net.ktnx.mobileledger.ui.QRScanAbleFragment;
40 import net.ktnx.mobileledger.utils.Logger;
41 import net.ktnx.mobileledger.utils.Misc;
43 import org.jetbrains.annotations.NotNull;
45 import java.util.ArrayList;
46 import java.util.List;
47 import java.util.Locale;
48 import java.util.regex.Matcher;
49 import java.util.regex.Pattern;
51 class PatternDetailsAdapter extends RecyclerView.Adapter<PatternDetailsAdapter.ViewHolder> {
52 private static final String D_PATTERN_UI = "pattern-ui";
53 private final AsyncListDiffer<PatternDetailsItem> differ;
54 public PatternDetailsAdapter() {
56 setHasStableIds(true);
57 differ = new AsyncListDiffer<>(this, new DiffUtil.ItemCallback<PatternDetailsItem>() {
59 public boolean areItemsTheSame(@NonNull PatternDetailsItem oldItem,
60 @NonNull PatternDetailsItem newItem) {
61 if (oldItem.getType() != newItem.getType())
64 .equals(PatternDetailsItem.Type.HEADER))
65 return true; // only one header item, ever
66 // the rest is comparing two account row items
67 return oldItem.asAccountRowItem()
68 .getId() == newItem.asAccountRowItem()
72 public boolean areContentsTheSame(@NonNull PatternDetailsItem oldItem,
73 @NonNull PatternDetailsItem newItem) {
75 .equals(PatternDetailsItem.Type.HEADER))
77 PatternDetailsItem.Header oldHeader = oldItem.asHeaderItem();
78 PatternDetailsItem.Header newHeader = newItem.asHeaderItem();
80 return oldHeader.equalContents(newHeader);
83 PatternDetailsItem.AccountRow oldAcc = oldItem.asAccountRowItem();
84 PatternDetailsItem.AccountRow newAcc = newItem.asAccountRowItem();
86 return oldAcc.equalContents(newAcc);
92 public long getItemId(int position) {
93 // header item is always first and IDs id may duplicate some of the account IDs
96 PatternDetailsItem.AccountRow accRow = differ.getCurrentList()
99 return accRow.getId();
102 public int getItemViewType(int position) {
104 return differ.getCurrentList()
111 public PatternDetailsAdapter.ViewHolder onCreateViewHolder(@NonNull ViewGroup parent,
113 final LayoutInflater inflater = LayoutInflater.from(parent.getContext());
115 case PatternDetailsItem.TYPE.header:
116 return new Header(PatternDetailsHeaderBinding.inflate(inflater, parent, false));
117 case PatternDetailsItem.TYPE.accountItem:
118 return new AccountRow(
119 PatternDetailsAccountBinding.inflate(inflater, parent, false));
121 throw new IllegalStateException("Unsupported view type " + viewType);
125 public void onBindViewHolder(@NonNull PatternDetailsAdapter.ViewHolder holder, int position) {
126 PatternDetailsItem item = differ.getCurrentList()
131 public int getItemCount() {
132 return differ.getCurrentList()
135 public void setPatternItems(List<PatternBase> items) {
136 ArrayList<PatternDetailsItem> list = new ArrayList<>();
137 for (PatternBase p : items) {
138 PatternDetailsItem item = PatternDetailsItem.fromRoomObject(p);
143 public void setItems(List<PatternDetailsItem> items) {
144 differ.submitList(items);
146 public String getMatchGroupText(int groupNumber) {
147 PatternDetailsItem.Header header = getHeader();
148 Pattern p = header.getCompiledPattern();
152 final String testText = Misc.nullIsEmpty(header.getTestText());
153 Matcher m = p.matcher(testText);
154 if (m.matches() && m.groupCount() >= groupNumber)
155 return m.group(groupNumber);
159 protected PatternDetailsItem.Header getHeader() {
160 return differ.getCurrentList()
165 private enum HeaderDetail {DESCRIPTION, COMMENT, DATE_YEAR, DATE_MONTH, DATE_DAY}
167 private enum AccDetail {ACCOUNT, COMMENT, AMOUNT}
169 public abstract static class ViewHolder extends RecyclerView.ViewHolder {
170 ViewHolder(@NonNull View itemView) {
173 abstract void bind(PatternDetailsItem item);
176 public class Header extends ViewHolder {
177 private final PatternDetailsHeaderBinding b;
178 public Header(@NonNull PatternDetailsHeaderBinding binding) {
179 super(binding.getRoot());
182 TextWatcher patternNameWatcher = new TextWatcher() {
184 public void beforeTextChanged(CharSequence s, int start, int count, int after) {}
186 public void onTextChanged(CharSequence s, int start, int before, int count) {}
188 public void afterTextChanged(Editable s) {
189 final PatternDetailsItem.Header header = getItem();
190 Logger.debug(D_PATTERN_UI,
191 "Storing changed pattern name " + s + "; header=" + header);
192 header.setName(String.valueOf(s));
195 b.patternName.addTextChangedListener(patternNameWatcher);
196 TextWatcher patternWatcher = new TextWatcher() {
198 public void beforeTextChanged(CharSequence s, int start, int count, int after) {}
200 public void onTextChanged(CharSequence s, int start, int before, int count) {}
202 public void afterTextChanged(Editable s) {
203 final PatternDetailsItem.Header header = getItem();
204 Logger.debug(D_PATTERN_UI,
205 "Storing changed pattern " + s + "; header=" + header);
206 header.setPattern(String.valueOf(s));
209 b.pattern.addTextChangedListener(patternWatcher);
210 TextWatcher testTextWatcher = new TextWatcher() {
212 public void beforeTextChanged(CharSequence s, int start, int count, int after) {}
214 public void onTextChanged(CharSequence s, int start, int before, int count) {}
216 public void afterTextChanged(Editable s) {
217 final PatternDetailsItem.Header header = getItem();
218 Logger.debug(D_PATTERN_UI,
219 "Storing changed test text " + s + "; header=" + header);
220 header.setTestText(String.valueOf(s));
223 b.testText.addTextChangedListener(testTextWatcher);
224 TextWatcher transactionDescriptionWatcher = new TextWatcher() {
226 public void beforeTextChanged(CharSequence s, int start, int count, int after) {
229 public void onTextChanged(CharSequence s, int start, int before, int count) {
233 public void afterTextChanged(Editable s) {
234 final PatternDetailsItem.Header header = getItem();
235 Logger.debug(D_PATTERN_UI,
236 "Storing changed transaction description " + s + "; header=" + header);
237 header.setTransactionDescription(String.valueOf(s));
240 b.transactionDescription.addTextChangedListener(transactionDescriptionWatcher);
241 TextWatcher transactionCommentWatcher = new TextWatcher() {
243 public void beforeTextChanged(CharSequence s, int start, int count, int after) {
247 public void onTextChanged(CharSequence s, int start, int before, int count) {
251 public void afterTextChanged(Editable s) {
252 final PatternDetailsItem.Header header = getItem();
253 Logger.debug(D_PATTERN_UI,
254 "Storing changed transaction description " + s + "; header=" + header);
255 header.setTransactionComment(String.valueOf(s));
258 b.transactionComment.addTextChangedListener(transactionCommentWatcher);
261 private PatternDetailsItem.Header getItem() {
262 int pos = getAdapterPosition();
263 return differ.getCurrentList()
267 private void selectHeaderDetailSource(View v, HeaderDetail detail) {
268 PatternDetailsItem.Header header = getItem();
269 Logger.debug(D_PATTERN_UI, "header is " + header);
270 PatternDetailSourceSelectorFragment sel =
271 PatternDetailSourceSelectorFragment.newInstance(1, header.getPattern(),
272 header.getTestText());
273 sel.setOnSourceSelectedListener((literal, group) -> {
277 header.switchToLiteralTransactionDescription();
280 header.switchToLiteralTransactionComment();
283 header.switchToLiteralDateYear();
286 header.switchToLiteralDateMonth();
289 header.switchToLiteralDateDay();
292 throw new IllegalStateException("Unexpected detail " + detail);
298 header.setTransactionDescriptionMatchGroup(group);
301 header.setTransactionCommentMatchGroup(group);
304 header.setDateYearMatchGroup(group);
307 header.setDateMonthMatchGroup(group);
310 header.setDateDayMatchGroup(group);
313 throw new IllegalStateException("Unexpected detail " + detail);
317 notifyItemChanged(getAdapterPosition());
319 final AppCompatActivity activity = (AppCompatActivity) v.getContext();
320 sel.show(activity.getSupportFragmentManager(), "pattern-details-source-selector");
323 void bind(PatternDetailsItem item) {
324 PatternDetailsItem.Header header = item.asHeaderItem();
325 Logger.debug(D_PATTERN_UI, "Binding to header " + header);
327 b.patternName.setText(header.getName());
328 b.pattern.setText(header.getPattern());
329 b.testText.setText(header.getTestText());
331 if (header.hasLiteralDateYear()) {
332 b.patternDetailsYearSource.setText(R.string.pattern_details_source_literal);
333 b.patternDetailsDateYear.setText(String.valueOf(header.getDateYear()));
334 b.patternDetailsDateYearLayout.setVisibility(View.VISIBLE);
337 b.patternDetailsDateYearLayout.setVisibility(View.GONE);
338 b.patternDetailsYearSource.setText(
339 String.format(Locale.US, "Group %d (%s)", header.getDateYearMatchGroup(),
340 getMatchGroupText(header.getDateYearMatchGroup())));
342 b.patternDetailsYearSourceLabel.setOnClickListener(
343 v -> selectHeaderDetailSource(v, HeaderDetail.DATE_YEAR));
344 b.patternDetailsYearSource.setOnClickListener(
345 v -> selectHeaderDetailSource(v, HeaderDetail.DATE_YEAR));
347 if (header.hasLiteralDateMonth()) {
348 b.patternDetailsMonthSource.setText(R.string.pattern_details_source_literal);
349 b.patternDetailsDateMonth.setText(String.valueOf(header.getDateMonth()));
350 b.patternDetailsDateMonthLayout.setVisibility(View.VISIBLE);
353 b.patternDetailsDateMonthLayout.setVisibility(View.GONE);
354 b.patternDetailsMonthSource.setText(
355 String.format(Locale.US, "Group %d (%s)", header.getDateMonthMatchGroup(),
356 getMatchGroupText(header.getDateMonthMatchGroup())));
358 b.patternDetailsMonthSourceLabel.setOnClickListener(
359 v -> selectHeaderDetailSource(v, HeaderDetail.DATE_MONTH));
360 b.patternDetailsMonthSource.setOnClickListener(
361 v -> selectHeaderDetailSource(v, HeaderDetail.DATE_MONTH));
363 if (header.hasLiteralDateDay()) {
364 b.patternDetailsDaySource.setText(R.string.pattern_details_source_literal);
365 b.patternDetailsDateDay.setText(String.valueOf(header.getDateDay()));
366 b.patternDetailsDateDayLayout.setVisibility(View.VISIBLE);
369 b.patternDetailsDateDayLayout.setVisibility(View.GONE);
370 b.patternDetailsDaySource.setText(
371 String.format(Locale.US, "Group %d (%s)", header.getDateDayMatchGroup(),
372 getMatchGroupText(header.getDateDayMatchGroup())));
374 b.patternDetailsDaySourceLabel.setOnClickListener(
375 v -> selectHeaderDetailSource(v, HeaderDetail.DATE_DAY));
376 b.patternDetailsDaySource.setOnClickListener(
377 v -> selectHeaderDetailSource(v, HeaderDetail.DATE_DAY));
379 if (header.hasLiteralTransactionDescription()) {
380 b.patternTransactionDescriptionSource.setText(
381 R.string.pattern_details_source_literal);
382 b.transactionDescription.setText(header.getTransactionDescription());
383 b.transactionDescriptionLayout.setVisibility(View.VISIBLE);
386 b.transactionDescriptionLayout.setVisibility(View.GONE);
387 b.patternTransactionDescriptionSource.setText(
388 String.format(Locale.US, "Group %d (%s)",
389 header.getTransactionDescriptionMatchGroup(),
390 getMatchGroupText(header.getTransactionDescriptionMatchGroup())));
393 b.patternTransactionDescriptionSourceLabel.setOnClickListener(
394 v -> selectHeaderDetailSource(v, HeaderDetail.DESCRIPTION));
395 b.patternTransactionDescriptionSource.setOnClickListener(
396 v -> selectHeaderDetailSource(v, HeaderDetail.DESCRIPTION));
398 if (header.hasLiteralTransactionComment()) {
399 b.patternTransactionCommentSource.setText(R.string.pattern_details_source_literal);
400 b.transactionComment.setText(header.getTransactionComment());
401 b.transactionCommentLayout.setVisibility(View.VISIBLE);
404 b.transactionCommentLayout.setVisibility(View.GONE);
405 b.patternTransactionCommentSource.setText(String.format(Locale.US, "Group %d (%s)",
406 header.getTransactionCommentMatchGroup(),
407 getMatchGroupText(header.getTransactionCommentMatchGroup())));
410 b.patternTransactionCommentSourceLabel.setOnClickListener(
411 v -> selectHeaderDetailSource(v, HeaderDetail.COMMENT));
412 b.patternTransactionCommentSource.setOnClickListener(
413 v -> selectHeaderDetailSource(v, HeaderDetail.COMMENT));
415 b.patternDetailsHeadScanQrButton.setOnClickListener(this::scanTestQR);
418 private void scanTestQR(View view) {
419 QRScanAbleFragment.triggerQRScan();
423 public class AccountRow extends ViewHolder {
424 private final PatternDetailsAccountBinding b;
425 public AccountRow(@NonNull PatternDetailsAccountBinding binding) {
426 super(binding.getRoot());
429 TextWatcher accountNameWatcher = new TextWatcher() {
431 public void beforeTextChanged(CharSequence s, int start, int count, int after) {}
433 public void onTextChanged(CharSequence s, int start, int before, int count) {}
435 public void afterTextChanged(Editable s) {
436 PatternDetailsItem.AccountRow accRow = getItem();
437 Logger.debug(D_PATTERN_UI,
438 "Storing changed account name " + s + "; accRow=" + accRow);
439 accRow.setAccountName(String.valueOf(s));
442 b.patternDetailsAccountName.addTextChangedListener(accountNameWatcher);
443 TextWatcher accountCommentWatcher = new TextWatcher() {
445 public void beforeTextChanged(CharSequence s, int start, int count, int after) {}
447 public void onTextChanged(CharSequence s, int start, int before, int count) {}
449 public void afterTextChanged(Editable s) {
450 PatternDetailsItem.AccountRow accRow = getItem();
451 Logger.debug(D_PATTERN_UI,
452 "Storing changed account comment " + s + "; accRow=" + accRow);
453 accRow.setAccountComment(String.valueOf(s));
456 b.patternDetailsAccountComment.addTextChangedListener(accountCommentWatcher);
459 void bind(PatternDetailsItem item) {
460 PatternDetailsItem.AccountRow accRow = item.asAccountRowItem();
461 if (accRow.hasLiteralAccountName()) {
462 b.patternDetailsAccountNameLayout.setVisibility(View.VISIBLE);
463 b.patternDetailsAccountName.setText(accRow.getAccountName());
464 b.patternDetailsAccountNameSource.setText(R.string.pattern_details_source_literal);
467 b.patternDetailsAccountNameLayout.setVisibility(View.GONE);
468 b.patternDetailsAccountNameSource.setText(
469 String.format(Locale.US, "Group %d (%s)", accRow.getAccountNameMatchGroup(),
470 getMatchGroupText(accRow.getAccountNameMatchGroup())));
473 if (accRow.hasLiteralAccountComment()) {
474 b.patternDetailsAccountCommentLayout.setVisibility(View.VISIBLE);
475 b.patternDetailsAccountComment.setText(accRow.getAccountComment());
476 b.patternDetailsAccountCommentSource.setText(
477 R.string.pattern_details_source_literal);
480 b.patternDetailsAccountCommentLayout.setVisibility(View.GONE);
481 b.patternDetailsAccountCommentSource.setText(
482 String.format(Locale.US, "Group %d (%s)",
483 accRow.getAccountCommentMatchGroup(),
484 getMatchGroupText(accRow.getAccountCommentMatchGroup())));
487 if (accRow.hasLiteralAmount()) {
488 b.patternDetailsAccountAmountSource.setText(
489 R.string.pattern_details_source_literal);
490 b.patternDetailsAccountAmount.setVisibility(View.VISIBLE);
491 b.patternDetailsAccountAmount.setText(Data.formatNumber(accRow.getAmount()));
494 b.patternDetailsAccountAmountSource.setText(
495 String.format(Locale.US, "Group %d (%s)", accRow.getAmountMatchGroup(),
496 getMatchGroupText(accRow.getAmountMatchGroup())));
497 b.patternDetailsAccountAmountLayout.setVisibility(View.GONE);
500 b.patternAccountNameSourceLabel.setOnClickListener(
501 v -> selectAccountRowDetailSource(v, AccDetail.ACCOUNT));
502 b.patternDetailsAccountNameSource.setOnClickListener(
503 v -> selectAccountRowDetailSource(v, AccDetail.ACCOUNT));
504 b.patternAccountCommentSourceLabel.setOnClickListener(
505 v -> selectAccountRowDetailSource(v, AccDetail.COMMENT));
506 b.patternDetailsAccountCommentSource.setOnClickListener(
507 v -> selectAccountRowDetailSource(v, AccDetail.COMMENT));
508 b.patternAccountAmountSourceLabel.setOnClickListener(
509 v -> selectAccountRowDetailSource(v, AccDetail.AMOUNT));
510 b.patternDetailsAccountAmountSource.setOnClickListener(
511 v -> selectAccountRowDetailSource(v, AccDetail.AMOUNT));
513 private @NotNull PatternDetailsItem.AccountRow getItem() {
514 return differ.getCurrentList()
515 .get(getAdapterPosition())
518 private void selectAccountRowDetailSource(View v, AccDetail detail) {
519 PatternDetailsItem.AccountRow accRow = getItem();
520 final PatternDetailsItem.Header header = getHeader();
521 Logger.debug(D_PATTERN_UI, "header is " + header);
522 PatternDetailSourceSelectorFragment sel =
523 PatternDetailSourceSelectorFragment.newInstance(1, header.getPattern(),
524 header.getTestText());
525 sel.setOnSourceSelectedListener((literal, group) -> {
529 accRow.switchToLiteralAccountName();
532 accRow.switchToLiteralAccountComment();
535 accRow.switchToLiteralAmount();
538 throw new IllegalStateException("Unexpected detail " + detail);
544 accRow.setAccountNameMatchGroup(group);
547 accRow.setAccountCommentMatchGroup(group);
550 accRow.setAmountMatchGroup(group);
553 throw new IllegalStateException("Unexpected detail " + detail);
557 notifyItemChanged(getAdapterPosition());
559 final AppCompatActivity activity = (AppCompatActivity) v.getContext();
560 sel.show(activity.getSupportFragmentManager(), "pattern-details-source-selector");