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.templates;
20 import android.annotation.SuppressLint;
21 import android.content.res.Resources;
22 import android.text.Editable;
23 import android.text.TextWatcher;
24 import android.view.LayoutInflater;
25 import android.view.MotionEvent;
26 import android.view.View;
27 import android.view.ViewGroup;
28 import android.widget.TextView;
30 import androidx.annotation.NonNull;
31 import androidx.appcompat.app.AppCompatActivity;
32 import androidx.recyclerview.widget.AsyncListDiffer;
33 import androidx.recyclerview.widget.DiffUtil;
34 import androidx.recyclerview.widget.ItemTouchHelper;
35 import androidx.recyclerview.widget.RecyclerView;
37 import net.ktnx.mobileledger.BuildConfig;
38 import net.ktnx.mobileledger.R;
39 import net.ktnx.mobileledger.databinding.TemplateDetailsAccountBinding;
40 import net.ktnx.mobileledger.databinding.TemplateDetailsHeaderBinding;
41 import net.ktnx.mobileledger.db.AccountAutocompleteAdapter;
42 import net.ktnx.mobileledger.model.Data;
43 import net.ktnx.mobileledger.model.TemplateDetailsItem;
44 import net.ktnx.mobileledger.ui.QRScanCapableFragment;
45 import net.ktnx.mobileledger.ui.TemplateDetailSourceSelectorFragment;
46 import net.ktnx.mobileledger.utils.Logger;
47 import net.ktnx.mobileledger.utils.Misc;
49 import org.jetbrains.annotations.NotNull;
51 import java.text.ParseException;
52 import java.util.List;
53 import java.util.Locale;
54 import java.util.regex.Matcher;
55 import java.util.regex.Pattern;
57 class TemplateDetailsAdapter extends RecyclerView.Adapter<TemplateDetailsAdapter.ViewHolder> {
58 private static final String D_TEMPLATE_UI = "template-ui";
59 private final AsyncListDiffer<TemplateDetailsItem> differ;
60 private final TemplateDetailsViewModel mModel;
61 private final ItemTouchHelper itemTouchHelper;
62 public TemplateDetailsAdapter(TemplateDetailsViewModel model) {
65 setHasStableIds(true);
66 differ = new AsyncListDiffer<>(this, new DiffUtil.ItemCallback<TemplateDetailsItem>() {
68 public boolean areItemsTheSame(@NonNull TemplateDetailsItem oldItem,
69 @NonNull TemplateDetailsItem newItem) {
70 if (oldItem.getType() != newItem.getType())
73 .equals(TemplateDetailsItem.Type.HEADER))
74 return true; // only one header item, ever
75 // the rest is comparing two account row items
76 return oldItem.asAccountRowItem()
77 .getId() == newItem.asAccountRowItem()
81 public boolean areContentsTheSame(@NonNull TemplateDetailsItem oldItem,
82 @NonNull TemplateDetailsItem newItem) {
84 .equals(TemplateDetailsItem.Type.HEADER))
86 TemplateDetailsItem.Header oldHeader = oldItem.asHeaderItem();
87 TemplateDetailsItem.Header newHeader = newItem.asHeaderItem();
89 return oldHeader.equalContents(newHeader);
92 TemplateDetailsItem.AccountRow oldAcc = oldItem.asAccountRowItem();
93 TemplateDetailsItem.AccountRow newAcc = newItem.asAccountRowItem();
95 return oldAcc.equalContents(newAcc);
99 itemTouchHelper = new ItemTouchHelper(new ItemTouchHelper.Callback() {
101 public float getMoveThreshold(@NonNull RecyclerView.ViewHolder viewHolder) {
105 public boolean isLongPressDragEnabled() {
109 public RecyclerView.ViewHolder chooseDropTarget(
110 @NonNull RecyclerView.ViewHolder selected,
111 @NonNull List<RecyclerView.ViewHolder> dropTargets, int curX, int curY) {
112 RecyclerView.ViewHolder best = null;
113 int bestDistance = 0;
114 for (RecyclerView.ViewHolder v : dropTargets) {
118 final int viewTop = v.itemView.getTop();
119 int distance = Math.abs(viewTop - curY);
122 bestDistance = distance;
125 if (distance < bestDistance) {
126 bestDistance = distance;
132 Logger.debug("dnd", "Best target is " + best);
136 public boolean canDropOver(@NonNull RecyclerView recyclerView,
137 @NonNull RecyclerView.ViewHolder current,
138 @NonNull RecyclerView.ViewHolder target) {
139 final int adapterPosition = target.getAdapterPosition();
141 // first item is immovable
142 if (adapterPosition == 0)
145 return super.canDropOver(recyclerView, current, target);
148 public int getMovementFlags(@NonNull RecyclerView recyclerView,
149 @NonNull RecyclerView.ViewHolder viewHolder) {
151 // the top item (transaction params) is always there
152 final int adapterPosition = viewHolder.getAdapterPosition();
153 if (adapterPosition > 0)
154 flags |= makeFlag(ItemTouchHelper.ACTION_STATE_DRAG,
155 ItemTouchHelper.UP | ItemTouchHelper.DOWN) |
156 makeFlag(ItemTouchHelper.ACTION_STATE_SWIPE,
157 ItemTouchHelper.START | ItemTouchHelper.END);
162 public boolean onMove(@NonNull RecyclerView recyclerView,
163 @NonNull RecyclerView.ViewHolder viewHolder,
164 @NonNull RecyclerView.ViewHolder target) {
166 final int fromPosition = viewHolder.getAdapterPosition();
167 final int toPosition = target.getAdapterPosition();
168 if (fromPosition == toPosition) {
169 Logger.debug("drag", String.format(Locale.US,
170 "Ignoring request to move an account from position %d to %d",
171 fromPosition, toPosition));
176 String.format(Locale.US, "Moving account from %d to %d", fromPosition,
178 mModel.moveItem(fromPosition, toPosition);
183 public void onSwiped(@NonNull RecyclerView.ViewHolder viewHolder, int direction) {
184 int pos = viewHolder.getAdapterPosition();
185 mModel.removeItem(pos);
190 public void onAttachedToRecyclerView(@NonNull RecyclerView recyclerView) {
191 super.onAttachedToRecyclerView(recyclerView);
193 itemTouchHelper.attachToRecyclerView(recyclerView);
196 public void onDetachedFromRecyclerView(@NonNull RecyclerView recyclerView) {
197 super.onDetachedFromRecyclerView(recyclerView);
199 itemTouchHelper.attachToRecyclerView(null);
202 public long getItemId(int position) {
203 // header item is always first and IDs id may duplicate some of the account IDs
206 TemplateDetailsItem.AccountRow accRow = differ.getCurrentList()
209 return accRow.getId();
212 public int getItemViewType(int position) {
214 return differ.getCurrentList()
221 public TemplateDetailsAdapter.ViewHolder onCreateViewHolder(@NonNull ViewGroup parent,
223 final LayoutInflater inflater = LayoutInflater.from(parent.getContext());
225 case TemplateDetailsItem.TYPE.header:
226 return new Header(TemplateDetailsHeaderBinding.inflate(inflater, parent, false));
227 case TemplateDetailsItem.TYPE.accountItem:
228 return new AccountRow(
229 TemplateDetailsAccountBinding.inflate(inflater, parent, false));
231 throw new IllegalStateException("Unsupported view type " + viewType);
235 public void onBindViewHolder(@NonNull TemplateDetailsAdapter.ViewHolder holder, int position) {
236 TemplateDetailsItem item = differ.getCurrentList()
241 public int getItemCount() {
242 return differ.getCurrentList()
245 public void setItems(List<TemplateDetailsItem> items) {
246 if (BuildConfig.DEBUG) {
247 Logger.debug("tmpl", "Got new list");
248 for (int i = 1; i < items.size(); i++) {
249 final TemplateDetailsItem item = items.get(i);
251 String.format(Locale.US, " %d: id %d, pos %d", i, item.getId(), item.getPosition()));
254 differ.submitList(items);
256 public String getMatchGroupText(int groupNumber) {
257 TemplateDetailsItem.Header header = getHeader();
258 Pattern p = header.getCompiledPattern();
262 final String testText = Misc.nullIsEmpty(header.getTestText());
263 Matcher m = p.matcher(testText);
264 if (m.matches() && m.groupCount() >= groupNumber)
265 return m.group(groupNumber);
269 protected TemplateDetailsItem.Header getHeader() {
270 return differ.getCurrentList()
275 private enum HeaderDetail {DESCRIPTION, COMMENT, DATE_YEAR, DATE_MONTH, DATE_DAY}
277 private enum AccDetail {ACCOUNT, COMMENT, AMOUNT}
279 public abstract static class ViewHolder extends RecyclerView.ViewHolder {
280 ViewHolder(@NonNull View itemView) {
283 abstract void bind(TemplateDetailsItem item);
286 private abstract static class BaseItem extends ViewHolder {
287 boolean updatePropagationDisabled = false;
288 BaseItem(@NonNull View itemView) {
291 void disableUpdatePropagation() {
292 updatePropagationDisabled = true;
294 void enableUpdatePropagation() {
295 updatePropagationDisabled = false;
299 public class Header extends BaseItem {
300 private final TemplateDetailsHeaderBinding b;
301 boolean updatePropagationDisabled = false;
302 public Header(@NonNull TemplateDetailsHeaderBinding binding) {
303 super(binding.getRoot());
306 TextWatcher templateNameWatcher = new TextWatcher() {
308 public void beforeTextChanged(CharSequence s, int start, int count, int after) {}
310 public void onTextChanged(CharSequence s, int start, int before, int count) {}
312 public void afterTextChanged(Editable s) {
313 if (updatePropagationDisabled)
316 final TemplateDetailsItem.Header header = getItem();
317 Logger.debug(D_TEMPLATE_UI,
318 "Storing changed template name " + s + "; header=" + header);
319 header.setName(String.valueOf(s));
322 b.templateName.addTextChangedListener(templateNameWatcher);
324 TextWatcher patternWatcher = new TextWatcher() {
326 public void beforeTextChanged(CharSequence s, int start, int count, int after) {}
328 public void onTextChanged(CharSequence s, int start, int before, int count) {}
330 public void afterTextChanged(Editable s) {
331 if (updatePropagationDisabled)
334 final TemplateDetailsItem.Header header = getItem();
335 Logger.debug(D_TEMPLATE_UI,
336 "Storing changed pattern " + s + "; header=" + header);
337 header.setPattern(String.valueOf(s));
339 checkPatternError(header);
342 b.pattern.addTextChangedListener(patternWatcher);
344 TextWatcher testTextWatcher = new TextWatcher() {
346 public void beforeTextChanged(CharSequence s, int start, int count, int after) {}
348 public void onTextChanged(CharSequence s, int start, int before, int count) {}
350 public void afterTextChanged(Editable s) {
351 if (updatePropagationDisabled)
354 final TemplateDetailsItem.Header header = getItem();
355 Logger.debug(D_TEMPLATE_UI,
356 "Storing changed test text " + s + "; header=" + header);
357 header.setTestText(String.valueOf(s));
359 checkPatternError(header);
362 b.testText.addTextChangedListener(testTextWatcher);
364 TextWatcher transactionDescriptionWatcher = new TextWatcher() {
366 public void beforeTextChanged(CharSequence s, int start, int count, int after) {
369 public void onTextChanged(CharSequence s, int start, int before, int count) {
372 public void afterTextChanged(Editable s) {
373 if (updatePropagationDisabled)
376 final TemplateDetailsItem.Header header = getItem();
377 Logger.debug(D_TEMPLATE_UI,
378 "Storing changed transaction description " + s + "; header=" + header);
379 header.setTransactionDescription(String.valueOf(s));
382 b.transactionDescription.addTextChangedListener(transactionDescriptionWatcher);
383 TextWatcher transactionCommentWatcher = new TextWatcher() {
385 public void beforeTextChanged(CharSequence s, int start, int count, int after) {
388 public void onTextChanged(CharSequence s, int start, int before, int count) {
391 public void afterTextChanged(Editable s) {
392 if (updatePropagationDisabled)
395 final TemplateDetailsItem.Header header = getItem();
396 Logger.debug(D_TEMPLATE_UI,
397 "Storing changed transaction description " + s + "; header=" + header);
398 header.setTransactionComment(String.valueOf(s));
401 b.transactionComment.addTextChangedListener(transactionCommentWatcher);
403 b.templateIsFallbackSwitch.setOnCheckedChangeListener((buttonView, isChecked) -> {
404 if (updatePropagationDisabled)
407 getItem().setFallback(isChecked);
408 b.templateIsFallbackText.setText(isChecked ? R.string.template_is_fallback_yes
409 : R.string.template_is_fallback_no);
411 final View.OnClickListener fallbackLabelClickListener =
412 (view) -> b.templateIsFallbackSwitch.toggle();
413 b.templateIsFallbackLabel.setOnClickListener(fallbackLabelClickListener);
414 b.templateIsFallbackText.setOnClickListener(fallbackLabelClickListener);
417 private TemplateDetailsItem.Header getItem() {
418 int pos = getAdapterPosition();
419 return differ.getCurrentList()
423 private void selectHeaderDetailSource(View v, HeaderDetail detail) {
424 TemplateDetailsItem.Header header = getItem();
425 Logger.debug(D_TEMPLATE_UI, "header is " + header);
426 TemplateDetailSourceSelectorFragment sel =
427 TemplateDetailSourceSelectorFragment.newInstance(1, header.getPattern(),
428 header.getTestText());
429 sel.setOnSourceSelectedListener((literal, group) -> {
433 header.switchToLiteralTransactionDescription();
436 header.switchToLiteralTransactionComment();
439 header.switchToLiteralDateYear();
442 header.switchToLiteralDateMonth();
445 header.switchToLiteralDateDay();
448 throw new IllegalStateException("Unexpected detail " + detail);
454 header.setTransactionDescriptionMatchGroup(group);
457 header.setTransactionCommentMatchGroup(group);
460 header.setDateYearMatchGroup(group);
463 header.setDateMonthMatchGroup(group);
466 header.setDateDayMatchGroup(group);
469 throw new IllegalStateException("Unexpected detail " + detail);
473 notifyItemChanged(getAdapterPosition());
475 final AppCompatActivity activity = (AppCompatActivity) v.getContext();
476 sel.show(activity.getSupportFragmentManager(), "template-details-source-selector");
479 void bind(TemplateDetailsItem item) {
480 TemplateDetailsItem.Header header = item.asHeaderItem();
481 Logger.debug(D_TEMPLATE_UI, "Binding to header " + header);
483 disableUpdatePropagation();
485 String groupNoText = b.getRoot()
487 .getString(R.string.template_item_match_group_source);
489 b.templateName.setText(header.getName());
490 b.pattern.setText(header.getPattern());
491 b.testText.setText(header.getTestText());
493 if (header.hasLiteralDateYear()) {
494 b.templateDetailsYearSource.setText(R.string.template_details_source_literal);
495 final Integer dateYear = header.getDateYear();
496 b.templateDetailsDateYear.setText(
497 (dateYear == null) ? null : String.valueOf(dateYear));
498 b.templateDetailsDateYearLayout.setVisibility(View.VISIBLE);
501 b.templateDetailsDateYearLayout.setVisibility(View.GONE);
502 b.templateDetailsYearSource.setText(
503 String.format(Locale.US, groupNoText, header.getDateYearMatchGroup(),
504 getMatchGroupText(header.getDateYearMatchGroup())));
506 b.templateDetailsYearSourceLabel.setOnClickListener(
507 v -> selectHeaderDetailSource(v, HeaderDetail.DATE_YEAR));
508 b.templateDetailsYearSource.setOnClickListener(
509 v -> selectHeaderDetailSource(v, HeaderDetail.DATE_YEAR));
511 if (header.hasLiteralDateMonth()) {
512 b.templateDetailsMonthSource.setText(R.string.template_details_source_literal);
513 final Integer dateMonth = header.getDateMonth();
514 b.templateDetailsDateMonth.setText(
515 (dateMonth == null) ? null : String.valueOf(dateMonth));
516 b.templateDetailsDateMonthLayout.setVisibility(View.VISIBLE);
519 b.templateDetailsDateMonthLayout.setVisibility(View.GONE);
520 b.templateDetailsMonthSource.setText(
521 String.format(Locale.US, groupNoText, header.getDateMonthMatchGroup(),
522 getMatchGroupText(header.getDateMonthMatchGroup())));
524 b.templateDetailsMonthSourceLabel.setOnClickListener(
525 v -> selectHeaderDetailSource(v, HeaderDetail.DATE_MONTH));
526 b.templateDetailsMonthSource.setOnClickListener(
527 v -> selectHeaderDetailSource(v, HeaderDetail.DATE_MONTH));
529 if (header.hasLiteralDateDay()) {
530 b.templateDetailsDaySource.setText(R.string.template_details_source_literal);
531 final Integer dateDay = header.getDateDay();
532 b.templateDetailsDateDay.setText(
533 (dateDay == null) ? null : String.valueOf(dateDay));
534 b.templateDetailsDateDayLayout.setVisibility(View.VISIBLE);
537 b.templateDetailsDateDayLayout.setVisibility(View.GONE);
538 b.templateDetailsDaySource.setText(
539 String.format(Locale.US, groupNoText, header.getDateDayMatchGroup(),
540 getMatchGroupText(header.getDateDayMatchGroup())));
542 b.templateDetailsDaySourceLabel.setOnClickListener(
543 v -> selectHeaderDetailSource(v, HeaderDetail.DATE_DAY));
544 b.templateDetailsDaySource.setOnClickListener(
545 v -> selectHeaderDetailSource(v, HeaderDetail.DATE_DAY));
547 if (header.hasLiteralTransactionDescription()) {
548 b.templateTransactionDescriptionSource.setText(
549 R.string.template_details_source_literal);
550 b.transactionDescription.setText(header.getTransactionDescription());
551 b.transactionDescriptionLayout.setVisibility(View.VISIBLE);
554 b.transactionDescriptionLayout.setVisibility(View.GONE);
555 b.templateTransactionDescriptionSource.setText(
556 String.format(Locale.US, groupNoText,
557 header.getTransactionDescriptionMatchGroup(), getMatchGroupText(
558 header.getTransactionDescriptionMatchGroup())));
561 b.templateTransactionDescriptionSourceLabel.setOnClickListener(
562 v -> selectHeaderDetailSource(v, HeaderDetail.DESCRIPTION));
563 b.templateTransactionDescriptionSource.setOnClickListener(
564 v -> selectHeaderDetailSource(v, HeaderDetail.DESCRIPTION));
566 if (header.hasLiteralTransactionComment()) {
567 b.templateTransactionCommentSource.setText(
568 R.string.template_details_source_literal);
569 b.transactionComment.setText(header.getTransactionComment());
570 b.transactionCommentLayout.setVisibility(View.VISIBLE);
573 b.transactionCommentLayout.setVisibility(View.GONE);
574 b.templateTransactionCommentSource.setText(String.format(Locale.US, groupNoText,
575 header.getTransactionCommentMatchGroup(),
576 getMatchGroupText(header.getTransactionCommentMatchGroup())));
579 b.templateTransactionCommentSourceLabel.setOnClickListener(
580 v -> selectHeaderDetailSource(v, HeaderDetail.COMMENT));
581 b.templateTransactionCommentSource.setOnClickListener(
582 v -> selectHeaderDetailSource(v, HeaderDetail.COMMENT));
584 b.templateDetailsHeadScanQrButton.setOnClickListener(this::scanTestQR);
586 b.templateIsFallbackSwitch.setChecked(header.isFallback());
587 b.templateIsFallbackText.setText(
588 header.isFallback() ? R.string.template_is_fallback_yes
589 : R.string.template_is_fallback_no);
591 checkPatternError(header);
594 enableUpdatePropagation();
597 private void checkPatternError(TemplateDetailsItem.Header item) {
598 if (item.getPatternError() != null) {
599 b.patternLayout.setError(item.getPatternError());
600 b.patternHintTitle.setVisibility(View.GONE);
601 b.patternHintText.setVisibility(View.GONE);
604 b.patternLayout.setError(null);
605 if (item.testMatch() != null) {
606 b.patternHintText.setText(item.testMatch());
607 b.patternHintTitle.setVisibility(View.VISIBLE);
608 b.patternHintText.setVisibility(View.VISIBLE);
611 b.patternLayout.setError(null);
612 b.patternHintTitle.setVisibility(View.GONE);
613 b.patternHintText.setVisibility(View.GONE);
618 private void scanTestQR(View view) {
619 QRScanCapableFragment.triggerQRScan();
623 public class AccountRow extends BaseItem {
624 private final TemplateDetailsAccountBinding b;
625 public AccountRow(@NonNull TemplateDetailsAccountBinding binding) {
626 super(binding.getRoot());
629 TextWatcher accountNameWatcher = new TextWatcher() {
631 public void beforeTextChanged(CharSequence s, int start, int count, int after) {}
633 public void onTextChanged(CharSequence s, int start, int before, int count) {}
635 public void afterTextChanged(Editable s) {
636 if (updatePropagationDisabled)
639 TemplateDetailsItem.AccountRow accRow = getItem();
640 Logger.debug(D_TEMPLATE_UI,
641 "Storing changed account name " + s + "; accRow=" + accRow);
642 accRow.setAccountName(String.valueOf(s));
644 mModel.applyList(null);
647 b.templateDetailsAccountName.addTextChangedListener(accountNameWatcher);
648 b.templateDetailsAccountName.setAdapter(new AccountAutocompleteAdapter(b.getRoot()
650 b.templateDetailsAccountName.setOnItemClickListener(
651 (parent, view, position, id) -> b.templateDetailsAccountName.setText(
652 ((TextView) view).getText()));
653 TextWatcher accountCommentWatcher = new TextWatcher() {
655 public void beforeTextChanged(CharSequence s, int start, int count, int after) {}
657 public void onTextChanged(CharSequence s, int start, int before, int count) {}
659 public void afterTextChanged(Editable s) {
660 if (updatePropagationDisabled)
663 TemplateDetailsItem.AccountRow accRow = getItem();
664 Logger.debug(D_TEMPLATE_UI,
665 "Storing changed account comment " + s + "; accRow=" + accRow);
666 accRow.setAccountComment(String.valueOf(s));
668 mModel.applyList(null);
671 b.templateDetailsAccountComment.addTextChangedListener(accountCommentWatcher);
673 b.templateDetailsAccountAmount.addTextChangedListener(new TextWatcher() {
675 public void beforeTextChanged(CharSequence s, int start, int count, int after) {
678 public void onTextChanged(CharSequence s, int start, int before, int count) {
681 public void afterTextChanged(Editable s) {
682 if (updatePropagationDisabled)
685 TemplateDetailsItem.AccountRow accRow = getItem();
687 String str = String.valueOf(s);
688 if (Misc.emptyIsNull(str) == null) {
689 accRow.setAmount(null);
693 final float amount = Data.parseNumber(str);
694 accRow.setAmount(amount);
695 b.templateDetailsAccountAmountLayout.setError(null);
697 Logger.debug(D_TEMPLATE_UI, String.format(Locale.US,
698 "Storing changed account amount %s [%4.2f]; accRow=%s", s,
701 catch (NumberFormatException | ParseException e) {
702 b.templateDetailsAccountAmountLayout.setError("!");
706 mModel.applyList(null);
709 b.templateDetailsAccountAmount.setOnFocusChangeListener((v, hasFocus) -> {
713 TemplateDetailsItem.AccountRow accRow = getItem();
714 if (!accRow.hasLiteralAmount())
716 Float amt = accRow.getAmount();
720 b.templateDetailsAccountAmount.setText(Data.formatNumber(amt));
723 b.negateAmountSwitch.setOnCheckedChangeListener((buttonView, isChecked) -> {
724 if (updatePropagationDisabled)
727 getItem().setNegateAmount(isChecked);
728 b.templateDetailsNegateAmountText.setText(
729 isChecked ? R.string.template_account_change_amount_sign
730 : R.string.template_account_keep_amount_sign);
732 final View.OnClickListener negLabelClickListener =
733 (view) -> b.negateAmountSwitch.toggle();
734 b.templateDetailsNegateAmountLabel.setOnClickListener(negLabelClickListener);
735 b.templateDetailsNegateAmountText.setOnClickListener(negLabelClickListener);
736 manageAccountLabelDrag();
738 @SuppressLint("ClickableViewAccessibility")
739 public void manageAccountLabelDrag() {
740 b.patternAccountLabel.setOnTouchListener((v, event) -> {
741 if (event.getAction() == MotionEvent.ACTION_DOWN) {
742 itemTouchHelper.startDrag(this);
748 void bind(TemplateDetailsItem item) {
749 disableUpdatePropagation();
751 final Resources resources = b.getRoot()
753 String groupNoText = resources.getString(R.string.template_item_match_group_source);
755 Logger.debug("drag", String.format(Locale.US, "Binding account id %d, pos %d at %d",
756 item.getId(), item.getPosition(), getAdapterPosition()));
757 TemplateDetailsItem.AccountRow accRow = item.asAccountRowItem();
758 b.patternAccountLabel.setText(String.format(Locale.US,
759 resources.getString(R.string.template_details_account_row_label),
760 accRow.getPosition()));
761 if (accRow.hasLiteralAccountName()) {
762 b.templateDetailsAccountNameLayout.setVisibility(View.VISIBLE);
763 b.templateDetailsAccountName.setText(accRow.getAccountName());
764 b.templateDetailsAccountNameSource.setText(
765 R.string.template_details_source_literal);
768 b.templateDetailsAccountNameLayout.setVisibility(View.GONE);
769 b.templateDetailsAccountNameSource.setText(
770 String.format(Locale.US, groupNoText, accRow.getAccountNameMatchGroup(),
771 getMatchGroupText(accRow.getAccountNameMatchGroup())));
774 if (accRow.hasLiteralAccountComment()) {
775 b.templateDetailsAccountCommentLayout.setVisibility(View.VISIBLE);
776 b.templateDetailsAccountComment.setText(accRow.getAccountComment());
777 b.templateDetailsAccountCommentSource.setText(
778 R.string.template_details_source_literal);
781 b.templateDetailsAccountCommentLayout.setVisibility(View.GONE);
782 b.templateDetailsAccountCommentSource.setText(
783 String.format(Locale.US, groupNoText,
784 accRow.getAccountCommentMatchGroup(),
785 getMatchGroupText(accRow.getAccountCommentMatchGroup())));
788 if (accRow.hasLiteralAmount()) {
789 b.templateDetailsAccountAmountSource.setText(
790 R.string.template_details_source_literal);
791 b.templateDetailsAccountAmount.setVisibility(View.VISIBLE);
792 Float amt = accRow.getAmount();
793 b.templateDetailsAccountAmount.setText((amt == null) ? null : String.format(
794 Data.locale.getValue(), "%,4.2f", (accRow.getAmount())));
795 b.negateAmountSwitch.setVisibility(View.GONE);
796 b.templateDetailsNegateAmountLabel.setVisibility(View.GONE);
797 b.templateDetailsNegateAmountText.setVisibility(View.GONE);
800 b.templateDetailsAccountAmountSource.setText(
801 String.format(Locale.US, groupNoText, accRow.getAmountMatchGroup(),
802 getMatchGroupText(accRow.getAmountMatchGroup())));
803 b.templateDetailsAccountAmountLayout.setVisibility(View.GONE);
804 b.negateAmountSwitch.setVisibility(View.VISIBLE);
805 b.negateAmountSwitch.setChecked(accRow.isNegateAmount());
806 b.templateDetailsNegateAmountText.setText(
807 accRow.isNegateAmount() ? R.string.template_account_change_amount_sign
808 : R.string.template_account_keep_amount_sign);
809 b.templateDetailsNegateAmountLabel.setVisibility(View.VISIBLE);
810 b.templateDetailsNegateAmountText.setVisibility(View.VISIBLE);
813 b.templateAccountNameSourceLabel.setOnClickListener(
814 v -> selectAccountRowDetailSource(v, AccDetail.ACCOUNT));
815 b.templateDetailsAccountNameSource.setOnClickListener(
816 v -> selectAccountRowDetailSource(v, AccDetail.ACCOUNT));
817 b.templateAccountCommentSourceLabel.setOnClickListener(
818 v -> selectAccountRowDetailSource(v, AccDetail.COMMENT));
819 b.templateDetailsAccountCommentSource.setOnClickListener(
820 v -> selectAccountRowDetailSource(v, AccDetail.COMMENT));
821 b.templateAccountAmountSourceLabel.setOnClickListener(
822 v -> selectAccountRowDetailSource(v, AccDetail.AMOUNT));
823 b.templateDetailsAccountAmountSource.setOnClickListener(
824 v -> selectAccountRowDetailSource(v, AccDetail.AMOUNT));
827 enableUpdatePropagation();
830 private @NotNull TemplateDetailsItem.AccountRow getItem() {
831 return differ.getCurrentList()
832 .get(getAdapterPosition())
835 private void selectAccountRowDetailSource(View v, AccDetail detail) {
836 TemplateDetailsItem.AccountRow accRow = getItem();
837 final TemplateDetailsItem.Header header = getHeader();
838 Logger.debug(D_TEMPLATE_UI, "header is " + header);
839 TemplateDetailSourceSelectorFragment sel =
840 TemplateDetailSourceSelectorFragment.newInstance(1, header.getPattern(),
841 header.getTestText());
842 sel.setOnSourceSelectedListener((literal, group) -> {
846 accRow.switchToLiteralAccountName();
849 accRow.switchToLiteralAccountComment();
852 accRow.switchToLiteralAmount();
855 throw new IllegalStateException("Unexpected detail " + detail);
861 accRow.setAccountNameMatchGroup(group);
864 accRow.setAccountCommentMatchGroup(group);
867 accRow.setAmountMatchGroup(group);
870 throw new IllegalStateException("Unexpected detail " + detail);
874 notifyItemChanged(getAdapterPosition());
876 final AppCompatActivity activity = (AppCompatActivity) v.getContext();
877 sel.show(activity.getSupportFragmentManager(), "template-details-source-selector");