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.Context;
22 import android.content.res.Resources;
23 import android.text.Editable;
24 import android.text.TextWatcher;
25 import android.view.LayoutInflater;
26 import android.view.MotionEvent;
27 import android.view.View;
28 import android.view.ViewGroup;
29 import android.widget.TextView;
31 import androidx.annotation.NonNull;
32 import androidx.appcompat.app.AppCompatActivity;
33 import androidx.recyclerview.widget.AsyncListDiffer;
34 import androidx.recyclerview.widget.DiffUtil;
35 import androidx.recyclerview.widget.ItemTouchHelper;
36 import androidx.recyclerview.widget.RecyclerView;
38 import net.ktnx.mobileledger.BuildConfig;
39 import net.ktnx.mobileledger.R;
40 import net.ktnx.mobileledger.databinding.TemplateDetailsAccountBinding;
41 import net.ktnx.mobileledger.databinding.TemplateDetailsHeaderBinding;
42 import net.ktnx.mobileledger.db.AccountAutocompleteAdapter;
43 import net.ktnx.mobileledger.model.Data;
44 import net.ktnx.mobileledger.model.TemplateDetailsItem;
45 import net.ktnx.mobileledger.ui.QR;
46 import net.ktnx.mobileledger.ui.TemplateDetailSourceSelectorFragment;
47 import net.ktnx.mobileledger.utils.Logger;
48 import net.ktnx.mobileledger.utils.Misc;
50 import org.jetbrains.annotations.NotNull;
52 import java.text.ParseException;
53 import java.util.List;
54 import java.util.Locale;
55 import java.util.regex.Matcher;
56 import java.util.regex.Pattern;
58 class TemplateDetailsAdapter extends RecyclerView.Adapter<TemplateDetailsAdapter.ViewHolder> {
59 private static final String D_TEMPLATE_UI = "template-ui";
60 private final AsyncListDiffer<TemplateDetailsItem> differ;
61 private final TemplateDetailsViewModel mModel;
62 private final ItemTouchHelper itemTouchHelper;
63 public TemplateDetailsAdapter(TemplateDetailsViewModel model) {
66 setHasStableIds(true);
67 differ = new AsyncListDiffer<>(this, new DiffUtil.ItemCallback<TemplateDetailsItem>() {
69 public boolean areItemsTheSame(@NonNull TemplateDetailsItem oldItem,
70 @NonNull TemplateDetailsItem newItem) {
71 if (oldItem.getType() != newItem.getType())
74 .equals(TemplateDetailsItem.Type.HEADER))
75 return true; // only one header item, ever
76 // the rest is comparing two account row items
77 return oldItem.asAccountRowItem()
78 .getId() == newItem.asAccountRowItem()
82 public boolean areContentsTheSame(@NonNull TemplateDetailsItem oldItem,
83 @NonNull TemplateDetailsItem newItem) {
85 .equals(TemplateDetailsItem.Type.HEADER))
87 TemplateDetailsItem.Header oldHeader = oldItem.asHeaderItem();
88 TemplateDetailsItem.Header newHeader = newItem.asHeaderItem();
90 return oldHeader.equalContents(newHeader);
93 TemplateDetailsItem.AccountRow oldAcc = oldItem.asAccountRowItem();
94 TemplateDetailsItem.AccountRow newAcc = newItem.asAccountRowItem();
96 return oldAcc.equalContents(newAcc);
100 itemTouchHelper = new ItemTouchHelper(new ItemTouchHelper.Callback() {
102 public float getMoveThreshold(@NonNull RecyclerView.ViewHolder viewHolder) {
106 public boolean isLongPressDragEnabled() {
110 public RecyclerView.ViewHolder chooseDropTarget(
111 @NonNull RecyclerView.ViewHolder selected,
112 @NonNull List<RecyclerView.ViewHolder> dropTargets, int curX, int curY) {
113 RecyclerView.ViewHolder best = null;
114 int bestDistance = 0;
115 for (RecyclerView.ViewHolder v : dropTargets) {
119 final int viewTop = v.itemView.getTop();
120 int distance = Math.abs(viewTop - curY);
123 bestDistance = distance;
126 if (distance < bestDistance) {
127 bestDistance = distance;
133 Logger.debug("dnd", "Best target is " + best);
137 public boolean canDropOver(@NonNull RecyclerView recyclerView,
138 @NonNull RecyclerView.ViewHolder current,
139 @NonNull RecyclerView.ViewHolder target) {
140 final int adapterPosition = target.getAdapterPosition();
142 // first item is immovable
143 if (adapterPosition == 0)
146 return super.canDropOver(recyclerView, current, target);
149 public int getMovementFlags(@NonNull RecyclerView recyclerView,
150 @NonNull RecyclerView.ViewHolder viewHolder) {
152 // the top item (transaction params) is always there
153 final int adapterPosition = viewHolder.getAdapterPosition();
154 if (adapterPosition > 0)
155 flags |= makeFlag(ItemTouchHelper.ACTION_STATE_DRAG,
156 ItemTouchHelper.UP | ItemTouchHelper.DOWN) |
157 makeFlag(ItemTouchHelper.ACTION_STATE_SWIPE,
158 ItemTouchHelper.START | ItemTouchHelper.END);
163 public boolean onMove(@NonNull RecyclerView recyclerView,
164 @NonNull RecyclerView.ViewHolder viewHolder,
165 @NonNull RecyclerView.ViewHolder target) {
167 final int fromPosition = viewHolder.getAdapterPosition();
168 final int toPosition = target.getAdapterPosition();
169 if (fromPosition == toPosition) {
170 Logger.debug("drag", String.format(Locale.US,
171 "Ignoring request to move an account from position %d to %d",
172 fromPosition, toPosition));
177 String.format(Locale.US, "Moving account from %d to %d", fromPosition,
179 mModel.moveItem(fromPosition, toPosition);
184 public void onSwiped(@NonNull RecyclerView.ViewHolder viewHolder, int direction) {
185 int pos = viewHolder.getAdapterPosition();
186 mModel.removeItem(pos);
191 public void onAttachedToRecyclerView(@NonNull RecyclerView recyclerView) {
192 super.onAttachedToRecyclerView(recyclerView);
194 itemTouchHelper.attachToRecyclerView(recyclerView);
197 public void onDetachedFromRecyclerView(@NonNull RecyclerView recyclerView) {
198 super.onDetachedFromRecyclerView(recyclerView);
200 itemTouchHelper.attachToRecyclerView(null);
203 public long getItemId(int position) {
204 // header item is always first and IDs id may duplicate some of the account IDs
207 TemplateDetailsItem.AccountRow accRow = differ.getCurrentList()
210 return accRow.getId();
213 public int getItemViewType(int position) {
215 return differ.getCurrentList()
222 public TemplateDetailsAdapter.ViewHolder onCreateViewHolder(@NonNull ViewGroup parent,
224 final LayoutInflater inflater = LayoutInflater.from(parent.getContext());
226 case TemplateDetailsItem.TYPE.header:
227 return new Header(TemplateDetailsHeaderBinding.inflate(inflater, parent, false));
228 case TemplateDetailsItem.TYPE.accountItem:
229 return new AccountRow(
230 TemplateDetailsAccountBinding.inflate(inflater, parent, false));
232 throw new IllegalStateException("Unsupported view type " + viewType);
236 public void onBindViewHolder(@NonNull TemplateDetailsAdapter.ViewHolder holder, int position) {
237 TemplateDetailsItem item = differ.getCurrentList()
242 public int getItemCount() {
243 return differ.getCurrentList()
246 public void setItems(List<TemplateDetailsItem> items) {
247 if (BuildConfig.DEBUG) {
248 Logger.debug("tmpl", "Got new list");
249 for (int i = 1; i < items.size(); i++) {
250 final TemplateDetailsItem item = items.get(i);
252 String.format(Locale.US, " %d: id %d, pos %d", i, item.getId(),
253 item.getPosition()));
256 differ.submitList(items);
258 public String getMatchGroupText(int groupNumber) {
259 TemplateDetailsItem.Header header = getHeader();
260 Pattern p = header.getCompiledPattern();
264 final String testText = Misc.nullIsEmpty(header.getTestText());
265 Matcher m = p.matcher(testText);
266 if (m.matches() && m.groupCount() >= groupNumber)
267 return m.group(groupNumber);
271 protected TemplateDetailsItem.Header getHeader() {
272 return differ.getCurrentList()
277 private enum HeaderDetail {DESCRIPTION, COMMENT, DATE_YEAR, DATE_MONTH, DATE_DAY}
279 private enum AccDetail {ACCOUNT, COMMENT, AMOUNT}
281 public abstract static class ViewHolder extends RecyclerView.ViewHolder {
282 ViewHolder(@NonNull View itemView) {
285 abstract void bind(TemplateDetailsItem item);
288 private abstract static class BaseItem extends ViewHolder {
289 boolean updatePropagationDisabled = false;
290 BaseItem(@NonNull View itemView) {
293 void disableUpdatePropagation() {
294 updatePropagationDisabled = true;
296 void enableUpdatePropagation() {
297 updatePropagationDisabled = false;
301 public class Header extends BaseItem {
302 private final TemplateDetailsHeaderBinding b;
303 boolean updatePropagationDisabled = false;
304 public Header(@NonNull TemplateDetailsHeaderBinding binding) {
305 super(binding.getRoot());
308 TextWatcher templateNameWatcher = new TextWatcher() {
310 public void beforeTextChanged(CharSequence s, int start, int count, int after) {}
312 public void onTextChanged(CharSequence s, int start, int before, int count) {}
314 public void afterTextChanged(Editable s) {
315 if (updatePropagationDisabled)
318 final TemplateDetailsItem.Header header = getItem();
319 Logger.debug(D_TEMPLATE_UI,
320 "Storing changed template name " + s + "; header=" + header);
321 header.setName(String.valueOf(s));
324 b.templateName.addTextChangedListener(templateNameWatcher);
326 TextWatcher patternWatcher = new TextWatcher() {
328 public void beforeTextChanged(CharSequence s, int start, int count, int after) {}
330 public void onTextChanged(CharSequence s, int start, int before, int count) {}
332 public void afterTextChanged(Editable s) {
333 if (updatePropagationDisabled)
336 final TemplateDetailsItem.Header header = getItem();
337 Logger.debug(D_TEMPLATE_UI,
338 "Storing changed pattern " + s + "; header=" + header);
339 header.setPattern(String.valueOf(s));
341 checkPatternError(header);
344 b.pattern.addTextChangedListener(patternWatcher);
346 TextWatcher testTextWatcher = new TextWatcher() {
348 public void beforeTextChanged(CharSequence s, int start, int count, int after) {}
350 public void onTextChanged(CharSequence s, int start, int before, int count) {}
352 public void afterTextChanged(Editable s) {
353 if (updatePropagationDisabled)
356 final TemplateDetailsItem.Header header = getItem();
357 Logger.debug(D_TEMPLATE_UI,
358 "Storing changed test text " + s + "; header=" + header);
359 header.setTestText(String.valueOf(s));
361 checkPatternError(header);
364 b.testText.addTextChangedListener(testTextWatcher);
366 TextWatcher transactionDescriptionWatcher = new TextWatcher() {
368 public void beforeTextChanged(CharSequence s, int start, int count, int after) {
371 public void onTextChanged(CharSequence s, int start, int before, int count) {
374 public void afterTextChanged(Editable s) {
375 if (updatePropagationDisabled)
378 final TemplateDetailsItem.Header header = getItem();
379 Logger.debug(D_TEMPLATE_UI,
380 "Storing changed transaction description " + s + "; header=" + header);
381 header.setTransactionDescription(String.valueOf(s));
384 b.transactionDescription.addTextChangedListener(transactionDescriptionWatcher);
385 TextWatcher transactionCommentWatcher = new TextWatcher() {
387 public void beforeTextChanged(CharSequence s, int start, int count, int after) {
390 public void onTextChanged(CharSequence s, int start, int before, int count) {
393 public void afterTextChanged(Editable s) {
394 if (updatePropagationDisabled)
397 final TemplateDetailsItem.Header header = getItem();
398 Logger.debug(D_TEMPLATE_UI,
399 "Storing changed transaction description " + s + "; header=" + header);
400 header.setTransactionComment(String.valueOf(s));
403 b.transactionComment.addTextChangedListener(transactionCommentWatcher);
405 b.templateIsFallbackSwitch.setOnCheckedChangeListener((buttonView, isChecked) -> {
406 if (updatePropagationDisabled)
409 getItem().setFallback(isChecked);
410 b.templateIsFallbackText.setText(isChecked ? R.string.template_is_fallback_yes
411 : R.string.template_is_fallback_no);
413 final View.OnClickListener fallbackLabelClickListener =
414 (view) -> b.templateIsFallbackSwitch.toggle();
415 b.templateIsFallbackLabel.setOnClickListener(fallbackLabelClickListener);
416 b.templateIsFallbackText.setOnClickListener(fallbackLabelClickListener);
419 private TemplateDetailsItem.Header getItem() {
420 int pos = getAdapterPosition();
421 return differ.getCurrentList()
425 private void selectHeaderDetailSource(View v, HeaderDetail detail) {
426 TemplateDetailsItem.Header header = getItem();
427 Logger.debug(D_TEMPLATE_UI, "header is " + header);
428 TemplateDetailSourceSelectorFragment sel =
429 TemplateDetailSourceSelectorFragment.newInstance(1, header.getPattern(),
430 header.getTestText());
431 sel.setOnSourceSelectedListener((literal, group) -> {
435 header.switchToLiteralTransactionDescription();
438 header.switchToLiteralTransactionComment();
441 header.switchToLiteralDateYear();
444 header.switchToLiteralDateMonth();
447 header.switchToLiteralDateDay();
450 throw new IllegalStateException("Unexpected detail " + detail);
456 header.setTransactionDescriptionMatchGroup(group);
459 header.setTransactionCommentMatchGroup(group);
462 header.setDateYearMatchGroup(group);
465 header.setDateMonthMatchGroup(group);
468 header.setDateDayMatchGroup(group);
471 throw new IllegalStateException("Unexpected detail " + detail);
475 notifyItemChanged(getAdapterPosition());
477 final AppCompatActivity activity = (AppCompatActivity) v.getContext();
478 sel.show(activity.getSupportFragmentManager(), "template-details-source-selector");
481 void bind(TemplateDetailsItem item) {
482 TemplateDetailsItem.Header header = item.asHeaderItem();
483 Logger.debug(D_TEMPLATE_UI, "Binding to header " + header);
485 disableUpdatePropagation();
487 String groupNoText = b.getRoot()
489 .getString(R.string.template_item_match_group_source);
491 b.templateName.setText(header.getName());
492 b.pattern.setText(header.getPattern());
493 b.testText.setText(header.getTestText());
495 if (header.hasLiteralDateYear()) {
496 b.templateDetailsYearSource.setText(R.string.template_details_source_literal);
497 final Integer dateYear = header.getDateYear();
498 b.templateDetailsDateYear.setText(
499 (dateYear == null) ? null : String.valueOf(dateYear));
500 b.templateDetailsDateYearLayout.setVisibility(View.VISIBLE);
503 b.templateDetailsDateYearLayout.setVisibility(View.GONE);
504 b.templateDetailsYearSource.setText(
505 String.format(Locale.US, groupNoText, header.getDateYearMatchGroup(),
506 getMatchGroupText(header.getDateYearMatchGroup())));
508 b.templateDetailsYearSourceLabel.setOnClickListener(
509 v -> selectHeaderDetailSource(v, HeaderDetail.DATE_YEAR));
510 b.templateDetailsYearSource.setOnClickListener(
511 v -> selectHeaderDetailSource(v, HeaderDetail.DATE_YEAR));
513 if (header.hasLiteralDateMonth()) {
514 b.templateDetailsMonthSource.setText(R.string.template_details_source_literal);
515 final Integer dateMonth = header.getDateMonth();
516 b.templateDetailsDateMonth.setText(
517 (dateMonth == null) ? null : String.valueOf(dateMonth));
518 b.templateDetailsDateMonthLayout.setVisibility(View.VISIBLE);
521 b.templateDetailsDateMonthLayout.setVisibility(View.GONE);
522 b.templateDetailsMonthSource.setText(
523 String.format(Locale.US, groupNoText, header.getDateMonthMatchGroup(),
524 getMatchGroupText(header.getDateMonthMatchGroup())));
526 b.templateDetailsMonthSourceLabel.setOnClickListener(
527 v -> selectHeaderDetailSource(v, HeaderDetail.DATE_MONTH));
528 b.templateDetailsMonthSource.setOnClickListener(
529 v -> selectHeaderDetailSource(v, HeaderDetail.DATE_MONTH));
531 if (header.hasLiteralDateDay()) {
532 b.templateDetailsDaySource.setText(R.string.template_details_source_literal);
533 final Integer dateDay = header.getDateDay();
534 b.templateDetailsDateDay.setText(
535 (dateDay == null) ? null : String.valueOf(dateDay));
536 b.templateDetailsDateDayLayout.setVisibility(View.VISIBLE);
539 b.templateDetailsDateDayLayout.setVisibility(View.GONE);
540 b.templateDetailsDaySource.setText(
541 String.format(Locale.US, groupNoText, header.getDateDayMatchGroup(),
542 getMatchGroupText(header.getDateDayMatchGroup())));
544 b.templateDetailsDaySourceLabel.setOnClickListener(
545 v -> selectHeaderDetailSource(v, HeaderDetail.DATE_DAY));
546 b.templateDetailsDaySource.setOnClickListener(
547 v -> selectHeaderDetailSource(v, HeaderDetail.DATE_DAY));
549 if (header.hasLiteralTransactionDescription()) {
550 b.templateTransactionDescriptionSource.setText(
551 R.string.template_details_source_literal);
552 b.transactionDescription.setText(header.getTransactionDescription());
553 b.transactionDescriptionLayout.setVisibility(View.VISIBLE);
556 b.transactionDescriptionLayout.setVisibility(View.GONE);
557 b.templateTransactionDescriptionSource.setText(
558 String.format(Locale.US, groupNoText,
559 header.getTransactionDescriptionMatchGroup(), getMatchGroupText(
560 header.getTransactionDescriptionMatchGroup())));
563 b.templateTransactionDescriptionSourceLabel.setOnClickListener(
564 v -> selectHeaderDetailSource(v, HeaderDetail.DESCRIPTION));
565 b.templateTransactionDescriptionSource.setOnClickListener(
566 v -> selectHeaderDetailSource(v, HeaderDetail.DESCRIPTION));
568 if (header.hasLiteralTransactionComment()) {
569 b.templateTransactionCommentSource.setText(
570 R.string.template_details_source_literal);
571 b.transactionComment.setText(header.getTransactionComment());
572 b.transactionCommentLayout.setVisibility(View.VISIBLE);
575 b.transactionCommentLayout.setVisibility(View.GONE);
576 b.templateTransactionCommentSource.setText(String.format(Locale.US, groupNoText,
577 header.getTransactionCommentMatchGroup(),
578 getMatchGroupText(header.getTransactionCommentMatchGroup())));
581 b.templateTransactionCommentSourceLabel.setOnClickListener(
582 v -> selectHeaderDetailSource(v, HeaderDetail.COMMENT));
583 b.templateTransactionCommentSource.setOnClickListener(
584 v -> selectHeaderDetailSource(v, HeaderDetail.COMMENT));
586 b.templateDetailsHeadScanQrButton.setOnClickListener(this::scanTestQR);
588 b.templateIsFallbackSwitch.setChecked(header.isFallback());
589 b.templateIsFallbackText.setText(
590 header.isFallback() ? R.string.template_is_fallback_yes
591 : R.string.template_is_fallback_no);
593 checkPatternError(header);
596 enableUpdatePropagation();
599 private void checkPatternError(TemplateDetailsItem.Header item) {
600 if (item.getPatternError() != null) {
601 b.patternLayout.setError(item.getPatternError());
602 b.patternHintTitle.setVisibility(View.GONE);
603 b.patternHintText.setVisibility(View.GONE);
606 b.patternLayout.setError(null);
607 if (item.testMatch() != null) {
608 b.patternHintText.setText(item.testMatch());
609 b.patternHintTitle.setVisibility(View.VISIBLE);
610 b.patternHintText.setVisibility(View.VISIBLE);
613 b.patternLayout.setError(null);
614 b.patternHintTitle.setVisibility(View.GONE);
615 b.patternHintText.setVisibility(View.GONE);
620 private void scanTestQR(View view) {
621 Context ctx = view.getContext();
622 if (ctx instanceof QR.QRScanTrigger)
623 ((QR.QRScanTrigger) ctx).triggerQRScan();
627 public class AccountRow extends BaseItem {
628 private final TemplateDetailsAccountBinding b;
629 public AccountRow(@NonNull TemplateDetailsAccountBinding binding) {
630 super(binding.getRoot());
633 TextWatcher accountNameWatcher = new TextWatcher() {
635 public void beforeTextChanged(CharSequence s, int start, int count, int after) {}
637 public void onTextChanged(CharSequence s, int start, int before, int count) {}
639 public void afterTextChanged(Editable s) {
640 if (updatePropagationDisabled)
643 TemplateDetailsItem.AccountRow accRow = getItem();
644 Logger.debug(D_TEMPLATE_UI,
645 "Storing changed account name " + s + "; accRow=" + accRow);
646 accRow.setAccountName(String.valueOf(s));
648 mModel.applyList(null);
651 b.templateDetailsAccountName.addTextChangedListener(accountNameWatcher);
652 b.templateDetailsAccountName.setAdapter(new AccountAutocompleteAdapter(b.getRoot()
654 b.templateDetailsAccountName.setOnItemClickListener(
655 (parent, view, position, id) -> b.templateDetailsAccountName.setText(
656 ((TextView) view).getText()));
657 TextWatcher accountCommentWatcher = new TextWatcher() {
659 public void beforeTextChanged(CharSequence s, int start, int count, int after) {}
661 public void onTextChanged(CharSequence s, int start, int before, int count) {}
663 public void afterTextChanged(Editable s) {
664 if (updatePropagationDisabled)
667 TemplateDetailsItem.AccountRow accRow = getItem();
668 Logger.debug(D_TEMPLATE_UI,
669 "Storing changed account comment " + s + "; accRow=" + accRow);
670 accRow.setAccountComment(String.valueOf(s));
672 mModel.applyList(null);
675 b.templateDetailsAccountComment.addTextChangedListener(accountCommentWatcher);
677 b.templateDetailsAccountAmount.addTextChangedListener(new TextWatcher() {
679 public void beforeTextChanged(CharSequence s, int start, int count, int after) {
682 public void onTextChanged(CharSequence s, int start, int before, int count) {
685 public void afterTextChanged(Editable s) {
686 if (updatePropagationDisabled)
689 TemplateDetailsItem.AccountRow accRow = getItem();
691 String str = String.valueOf(s);
692 if (Misc.emptyIsNull(str) == null) {
693 accRow.setAmount(null);
697 final float amount = Data.parseNumber(str);
698 accRow.setAmount(amount);
699 b.templateDetailsAccountAmountLayout.setError(null);
701 Logger.debug(D_TEMPLATE_UI, String.format(Locale.US,
702 "Storing changed account amount %s [%4.2f]; accRow=%s", s,
705 catch (NumberFormatException | ParseException e) {
706 b.templateDetailsAccountAmountLayout.setError("!");
710 mModel.applyList(null);
713 b.templateDetailsAccountAmount.setOnFocusChangeListener((v, hasFocus) -> {
717 TemplateDetailsItem.AccountRow accRow = getItem();
718 if (!accRow.hasLiteralAmount())
720 Float amt = accRow.getAmount();
724 b.templateDetailsAccountAmount.setText(Data.formatNumber(amt));
727 b.negateAmountSwitch.setOnCheckedChangeListener((buttonView, isChecked) -> {
728 if (updatePropagationDisabled)
731 getItem().setNegateAmount(isChecked);
732 b.templateDetailsNegateAmountText.setText(
733 isChecked ? R.string.template_account_change_amount_sign
734 : R.string.template_account_keep_amount_sign);
736 final View.OnClickListener negLabelClickListener =
737 (view) -> b.negateAmountSwitch.toggle();
738 b.templateDetailsNegateAmountLabel.setOnClickListener(negLabelClickListener);
739 b.templateDetailsNegateAmountText.setOnClickListener(negLabelClickListener);
740 manageAccountLabelDrag();
742 @SuppressLint("ClickableViewAccessibility")
743 public void manageAccountLabelDrag() {
744 b.patternAccountLabel.setOnTouchListener((v, event) -> {
745 if (event.getAction() == MotionEvent.ACTION_DOWN) {
746 itemTouchHelper.startDrag(this);
752 void bind(TemplateDetailsItem item) {
753 disableUpdatePropagation();
755 final Resources resources = b.getRoot()
757 String groupNoText = resources.getString(R.string.template_item_match_group_source);
759 Logger.debug("drag", String.format(Locale.US, "Binding account id %d, pos %d at %d",
760 item.getId(), item.getPosition(), getAdapterPosition()));
761 TemplateDetailsItem.AccountRow accRow = item.asAccountRowItem();
762 b.patternAccountLabel.setText(String.format(Locale.US,
763 resources.getString(R.string.template_details_account_row_label),
764 accRow.getPosition()));
765 if (accRow.hasLiteralAccountName()) {
766 b.templateDetailsAccountNameLayout.setVisibility(View.VISIBLE);
767 b.templateDetailsAccountName.setText(accRow.getAccountName());
768 b.templateDetailsAccountNameSource.setText(
769 R.string.template_details_source_literal);
772 b.templateDetailsAccountNameLayout.setVisibility(View.GONE);
773 b.templateDetailsAccountNameSource.setText(
774 String.format(Locale.US, groupNoText, accRow.getAccountNameMatchGroup(),
775 getMatchGroupText(accRow.getAccountNameMatchGroup())));
778 if (accRow.hasLiteralAccountComment()) {
779 b.templateDetailsAccountCommentLayout.setVisibility(View.VISIBLE);
780 b.templateDetailsAccountComment.setText(accRow.getAccountComment());
781 b.templateDetailsAccountCommentSource.setText(
782 R.string.template_details_source_literal);
785 b.templateDetailsAccountCommentLayout.setVisibility(View.GONE);
786 b.templateDetailsAccountCommentSource.setText(
787 String.format(Locale.US, groupNoText,
788 accRow.getAccountCommentMatchGroup(),
789 getMatchGroupText(accRow.getAccountCommentMatchGroup())));
792 if (accRow.hasLiteralAmount()) {
793 b.templateDetailsAccountAmountSource.setText(
794 R.string.template_details_source_literal);
795 b.templateDetailsAccountAmount.setVisibility(View.VISIBLE);
796 Float amt = accRow.getAmount();
797 b.templateDetailsAccountAmount.setText((amt == null) ? null : String.format(
798 Data.locale.getValue(), "%,4.2f", (accRow.getAmount())));
799 b.negateAmountSwitch.setVisibility(View.GONE);
800 b.templateDetailsNegateAmountLabel.setVisibility(View.GONE);
801 b.templateDetailsNegateAmountText.setVisibility(View.GONE);
804 b.templateDetailsAccountAmountSource.setText(
805 String.format(Locale.US, groupNoText, accRow.getAmountMatchGroup(),
806 getMatchGroupText(accRow.getAmountMatchGroup())));
807 b.templateDetailsAccountAmountLayout.setVisibility(View.GONE);
808 b.negateAmountSwitch.setVisibility(View.VISIBLE);
809 b.negateAmountSwitch.setChecked(accRow.isNegateAmount());
810 b.templateDetailsNegateAmountText.setText(
811 accRow.isNegateAmount() ? R.string.template_account_change_amount_sign
812 : R.string.template_account_keep_amount_sign);
813 b.templateDetailsNegateAmountLabel.setVisibility(View.VISIBLE);
814 b.templateDetailsNegateAmountText.setVisibility(View.VISIBLE);
817 b.templateAccountNameSourceLabel.setOnClickListener(
818 v -> selectAccountRowDetailSource(v, AccDetail.ACCOUNT));
819 b.templateDetailsAccountNameSource.setOnClickListener(
820 v -> selectAccountRowDetailSource(v, AccDetail.ACCOUNT));
821 b.templateAccountCommentSourceLabel.setOnClickListener(
822 v -> selectAccountRowDetailSource(v, AccDetail.COMMENT));
823 b.templateDetailsAccountCommentSource.setOnClickListener(
824 v -> selectAccountRowDetailSource(v, AccDetail.COMMENT));
825 b.templateAccountAmountSourceLabel.setOnClickListener(
826 v -> selectAccountRowDetailSource(v, AccDetail.AMOUNT));
827 b.templateDetailsAccountAmountSource.setOnClickListener(
828 v -> selectAccountRowDetailSource(v, AccDetail.AMOUNT));
831 enableUpdatePropagation();
834 private @NotNull TemplateDetailsItem.AccountRow getItem() {
835 return differ.getCurrentList()
836 .get(getAdapterPosition())
839 private void selectAccountRowDetailSource(View v, AccDetail detail) {
840 TemplateDetailsItem.AccountRow accRow = getItem();
841 final TemplateDetailsItem.Header header = getHeader();
842 Logger.debug(D_TEMPLATE_UI, "header is " + header);
843 TemplateDetailSourceSelectorFragment sel =
844 TemplateDetailSourceSelectorFragment.newInstance(1, header.getPattern(),
845 header.getTestText());
846 sel.setOnSourceSelectedListener((literal, group) -> {
850 accRow.switchToLiteralAccountName();
853 accRow.switchToLiteralAccountComment();
856 accRow.switchToLiteralAmount();
859 throw new IllegalStateException("Unexpected detail " + detail);
865 accRow.setAccountNameMatchGroup(group);
868 accRow.setAccountCommentMatchGroup(group);
871 accRow.setAmountMatchGroup(group);
874 throw new IllegalStateException("Unexpected detail " + detail);
878 notifyItemChanged(getAdapterPosition());
880 final AppCompatActivity activity = (AppCompatActivity) v.getContext();
881 sel.show(activity.getSupportFragmentManager(), "template-details-source-selector");