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.HelpDialog;
46 import net.ktnx.mobileledger.ui.QR;
47 import net.ktnx.mobileledger.ui.TemplateDetailSourceSelectorFragment;
48 import net.ktnx.mobileledger.utils.Logger;
49 import net.ktnx.mobileledger.utils.Misc;
51 import org.jetbrains.annotations.NotNull;
53 import java.text.ParseException;
54 import java.util.List;
55 import java.util.Locale;
56 import java.util.regex.Matcher;
57 import java.util.regex.Pattern;
59 class TemplateDetailsAdapter extends RecyclerView.Adapter<TemplateDetailsAdapter.ViewHolder> {
60 private static final String D_TEMPLATE_UI = "template-ui";
61 private final AsyncListDiffer<TemplateDetailsItem> differ;
62 private final TemplateDetailsViewModel mModel;
63 private final ItemTouchHelper itemTouchHelper;
64 public TemplateDetailsAdapter(TemplateDetailsViewModel model) {
67 setHasStableIds(true);
68 differ = new AsyncListDiffer<>(this, new DiffUtil.ItemCallback<TemplateDetailsItem>() {
70 public boolean areItemsTheSame(@NonNull TemplateDetailsItem oldItem,
71 @NonNull TemplateDetailsItem newItem) {
72 if (oldItem.getType() != newItem.getType())
75 .equals(TemplateDetailsItem.Type.HEADER))
76 return true; // only one header item, ever
77 // the rest is comparing two account row items
78 return oldItem.asAccountRowItem()
79 .getId() == newItem.asAccountRowItem()
83 public boolean areContentsTheSame(@NonNull TemplateDetailsItem oldItem,
84 @NonNull TemplateDetailsItem newItem) {
86 .equals(TemplateDetailsItem.Type.HEADER))
88 TemplateDetailsItem.Header oldHeader = oldItem.asHeaderItem();
89 TemplateDetailsItem.Header newHeader = newItem.asHeaderItem();
91 return oldHeader.equalContents(newHeader);
94 TemplateDetailsItem.AccountRow oldAcc = oldItem.asAccountRowItem();
95 TemplateDetailsItem.AccountRow newAcc = newItem.asAccountRowItem();
97 return oldAcc.equalContents(newAcc);
101 itemTouchHelper = new ItemTouchHelper(new ItemTouchHelper.Callback() {
103 public float getMoveThreshold(@NonNull RecyclerView.ViewHolder viewHolder) {
107 public boolean isLongPressDragEnabled() {
111 public RecyclerView.ViewHolder chooseDropTarget(
112 @NonNull RecyclerView.ViewHolder selected,
113 @NonNull List<RecyclerView.ViewHolder> dropTargets, int curX, int curY) {
114 RecyclerView.ViewHolder best = null;
115 int bestDistance = 0;
116 for (RecyclerView.ViewHolder v : dropTargets) {
120 final int viewTop = v.itemView.getTop();
121 int distance = Math.abs(viewTop - curY);
124 bestDistance = distance;
127 if (distance < bestDistance) {
128 bestDistance = distance;
134 Logger.debug("dnd", "Best target is " + best);
138 public boolean canDropOver(@NonNull RecyclerView recyclerView,
139 @NonNull RecyclerView.ViewHolder current,
140 @NonNull RecyclerView.ViewHolder target) {
141 final int adapterPosition = target.getAdapterPosition();
143 // first item is immovable
144 if (adapterPosition == 0)
147 return super.canDropOver(recyclerView, current, target);
150 public int getMovementFlags(@NonNull RecyclerView recyclerView,
151 @NonNull RecyclerView.ViewHolder viewHolder) {
153 // the top item (transaction params) is always there
154 final int adapterPosition = viewHolder.getAdapterPosition();
155 if (adapterPosition > 0)
156 flags |= makeFlag(ItemTouchHelper.ACTION_STATE_DRAG,
157 ItemTouchHelper.UP | ItemTouchHelper.DOWN) |
158 makeFlag(ItemTouchHelper.ACTION_STATE_SWIPE,
159 ItemTouchHelper.START | ItemTouchHelper.END);
164 public boolean onMove(@NonNull RecyclerView recyclerView,
165 @NonNull RecyclerView.ViewHolder viewHolder,
166 @NonNull RecyclerView.ViewHolder target) {
168 final int fromPosition = viewHolder.getAdapterPosition();
169 final int toPosition = target.getAdapterPosition();
170 if (fromPosition == toPosition) {
171 Logger.debug("drag", String.format(Locale.US,
172 "Ignoring request to move an account from position %d to %d",
173 fromPosition, toPosition));
178 String.format(Locale.US, "Moving account from %d to %d", fromPosition,
180 mModel.moveItem(fromPosition, toPosition);
185 public void onSwiped(@NonNull RecyclerView.ViewHolder viewHolder, int direction) {
186 int pos = viewHolder.getAdapterPosition();
187 mModel.removeItem(pos);
192 public void onAttachedToRecyclerView(@NonNull RecyclerView recyclerView) {
193 super.onAttachedToRecyclerView(recyclerView);
195 itemTouchHelper.attachToRecyclerView(recyclerView);
198 public void onDetachedFromRecyclerView(@NonNull RecyclerView recyclerView) {
199 super.onDetachedFromRecyclerView(recyclerView);
201 itemTouchHelper.attachToRecyclerView(null);
204 public long getItemId(int position) {
205 // header item is always first and IDs id may duplicate some of the account IDs
208 TemplateDetailsItem.AccountRow accRow = differ.getCurrentList()
211 return accRow.getId();
214 public int getItemViewType(int position) {
216 return differ.getCurrentList()
223 public TemplateDetailsAdapter.ViewHolder onCreateViewHolder(@NonNull ViewGroup parent,
225 final LayoutInflater inflater = LayoutInflater.from(parent.getContext());
227 case TemplateDetailsItem.TYPE.header:
228 return new Header(TemplateDetailsHeaderBinding.inflate(inflater, parent, false));
229 case TemplateDetailsItem.TYPE.accountItem:
230 return new AccountRow(
231 TemplateDetailsAccountBinding.inflate(inflater, parent, false));
233 throw new IllegalStateException("Unsupported view type " + viewType);
237 public void onBindViewHolder(@NonNull TemplateDetailsAdapter.ViewHolder holder, int position) {
238 TemplateDetailsItem item = differ.getCurrentList()
243 public int getItemCount() {
244 return differ.getCurrentList()
247 public void setItems(List<TemplateDetailsItem> items) {
248 if (BuildConfig.DEBUG) {
249 Logger.debug("tmpl", "Got new list");
250 for (int i = 1; i < items.size(); i++) {
251 final TemplateDetailsItem item = items.get(i);
253 String.format(Locale.US, " %d: id %d, pos %d", i, item.getId(),
254 item.getPosition()));
257 differ.submitList(items);
259 public String getMatchGroupText(int groupNumber) {
260 TemplateDetailsItem.Header header = getHeader();
261 Pattern p = header.getCompiledPattern();
265 final String testText = Misc.nullIsEmpty(header.getTestText());
266 Matcher m = p.matcher(testText);
267 if (m.matches() && m.groupCount() >= groupNumber)
268 return m.group(groupNumber);
272 protected TemplateDetailsItem.Header getHeader() {
273 return differ.getCurrentList()
278 private enum HeaderDetail {DESCRIPTION, COMMENT, DATE_YEAR, DATE_MONTH, DATE_DAY}
280 private enum AccDetail {ACCOUNT, COMMENT, AMOUNT}
282 public abstract static class ViewHolder extends RecyclerView.ViewHolder {
283 ViewHolder(@NonNull View itemView) {
286 abstract void bind(TemplateDetailsItem item);
289 private abstract static class BaseItem extends ViewHolder {
290 boolean updatePropagationDisabled = false;
291 BaseItem(@NonNull View itemView) {
294 void disableUpdatePropagation() {
295 updatePropagationDisabled = true;
297 void enableUpdatePropagation() {
298 updatePropagationDisabled = false;
302 public class Header extends BaseItem {
303 private final TemplateDetailsHeaderBinding b;
304 boolean updatePropagationDisabled = false;
305 public Header(@NonNull TemplateDetailsHeaderBinding binding) {
306 super(binding.getRoot());
309 TextWatcher templateNameWatcher = new TextWatcher() {
311 public void beforeTextChanged(CharSequence s, int start, int count, int after) {}
313 public void onTextChanged(CharSequence s, int start, int before, int count) {}
315 public void afterTextChanged(Editable s) {
316 if (updatePropagationDisabled)
319 final TemplateDetailsItem.Header header = getItem();
320 Logger.debug(D_TEMPLATE_UI,
321 "Storing changed template name " + s + "; header=" + header);
322 header.setName(String.valueOf(s));
325 b.templateName.addTextChangedListener(templateNameWatcher);
327 TextWatcher patternWatcher = new TextWatcher() {
329 public void beforeTextChanged(CharSequence s, int start, int count, int after) {}
331 public void onTextChanged(CharSequence s, int start, int before, int count) {}
333 public void afterTextChanged(Editable s) {
334 if (updatePropagationDisabled)
337 final TemplateDetailsItem.Header header = getItem();
338 Logger.debug(D_TEMPLATE_UI,
339 "Storing changed pattern " + s + "; header=" + header);
340 header.setPattern(String.valueOf(s));
342 checkPatternError(header);
345 b.pattern.addTextChangedListener(patternWatcher);
347 TextWatcher testTextWatcher = new TextWatcher() {
349 public void beforeTextChanged(CharSequence s, int start, int count, int after) {}
351 public void onTextChanged(CharSequence s, int start, int before, int count) {}
353 public void afterTextChanged(Editable s) {
354 if (updatePropagationDisabled)
357 final TemplateDetailsItem.Header header = getItem();
358 Logger.debug(D_TEMPLATE_UI,
359 "Storing changed test text " + s + "; header=" + header);
360 header.setTestText(String.valueOf(s));
362 checkPatternError(header);
365 b.testText.addTextChangedListener(testTextWatcher);
367 TextWatcher transactionDescriptionWatcher = new TextWatcher() {
369 public void beforeTextChanged(CharSequence s, int start, int count, int after) {
372 public void onTextChanged(CharSequence s, int start, int before, int count) {
375 public void afterTextChanged(Editable s) {
376 if (updatePropagationDisabled)
379 final TemplateDetailsItem.Header header = getItem();
380 Logger.debug(D_TEMPLATE_UI,
381 "Storing changed transaction description " + s + "; header=" + header);
382 header.setTransactionDescription(String.valueOf(s));
385 b.transactionDescription.addTextChangedListener(transactionDescriptionWatcher);
386 TextWatcher transactionCommentWatcher = new TextWatcher() {
388 public void beforeTextChanged(CharSequence s, int start, int count, int after) {
391 public void onTextChanged(CharSequence s, int start, int before, int count) {
394 public void afterTextChanged(Editable s) {
395 if (updatePropagationDisabled)
398 final TemplateDetailsItem.Header header = getItem();
399 Logger.debug(D_TEMPLATE_UI,
400 "Storing changed transaction description " + s + "; header=" + header);
401 header.setTransactionComment(String.valueOf(s));
404 b.transactionComment.addTextChangedListener(transactionCommentWatcher);
406 b.templateIsFallbackSwitch.setOnCheckedChangeListener((buttonView, isChecked) -> {
407 if (updatePropagationDisabled)
410 getItem().setFallback(isChecked);
411 b.templateIsFallbackText.setText(isChecked ? R.string.template_is_fallback_yes
412 : R.string.template_is_fallback_no);
414 final View.OnClickListener fallbackLabelClickListener =
415 (view) -> b.templateIsFallbackSwitch.toggle();
416 b.templateIsFallbackLabel.setOnClickListener(fallbackLabelClickListener);
417 b.templateIsFallbackText.setOnClickListener(fallbackLabelClickListener);
418 b.templateDetailsTemplateParamsHelpButton.setOnClickListener(v -> HelpDialog.show(
420 .getContext(), R.string.template_details_template_params_label,
421 R.array.template_params_help));
424 private TemplateDetailsItem.Header getItem() {
425 int pos = getAdapterPosition();
426 return differ.getCurrentList()
430 private void selectHeaderDetailSource(View v, HeaderDetail detail) {
431 TemplateDetailsItem.Header header = getItem();
432 Logger.debug(D_TEMPLATE_UI, "header is " + header);
433 TemplateDetailSourceSelectorFragment sel =
434 TemplateDetailSourceSelectorFragment.newInstance(1, header.getPattern(),
435 header.getTestText());
436 sel.setOnSourceSelectedListener((literal, group) -> {
440 header.switchToLiteralTransactionDescription();
443 header.switchToLiteralTransactionComment();
446 header.switchToLiteralDateYear();
449 header.switchToLiteralDateMonth();
452 header.switchToLiteralDateDay();
455 throw new IllegalStateException("Unexpected detail " + detail);
461 header.setTransactionDescriptionMatchGroup(group);
464 header.setTransactionCommentMatchGroup(group);
467 header.setDateYearMatchGroup(group);
470 header.setDateMonthMatchGroup(group);
473 header.setDateDayMatchGroup(group);
476 throw new IllegalStateException("Unexpected detail " + detail);
480 notifyItemChanged(getAdapterPosition());
482 final AppCompatActivity activity = (AppCompatActivity) v.getContext();
483 sel.show(activity.getSupportFragmentManager(), "template-details-source-selector");
486 void bind(TemplateDetailsItem item) {
487 TemplateDetailsItem.Header header = item.asHeaderItem();
488 Logger.debug(D_TEMPLATE_UI, "Binding to header " + header);
490 disableUpdatePropagation();
492 String groupNoText = b.getRoot()
494 .getString(R.string.template_item_match_group_source);
496 b.templateName.setText(header.getName());
497 b.pattern.setText(header.getPattern());
498 b.testText.setText(header.getTestText());
500 if (header.hasLiteralDateYear()) {
501 b.templateDetailsYearSource.setText(R.string.template_details_source_literal);
502 final Integer dateYear = header.getDateYear();
503 b.templateDetailsDateYear.setText(
504 (dateYear == null) ? null : String.valueOf(dateYear));
505 b.templateDetailsDateYearLayout.setVisibility(View.VISIBLE);
508 b.templateDetailsDateYearLayout.setVisibility(View.GONE);
509 b.templateDetailsYearSource.setText(
510 String.format(Locale.US, groupNoText, header.getDateYearMatchGroup(),
511 getMatchGroupText(header.getDateYearMatchGroup())));
513 b.templateDetailsYearSourceLabel.setOnClickListener(
514 v -> selectHeaderDetailSource(v, HeaderDetail.DATE_YEAR));
515 b.templateDetailsYearSource.setOnClickListener(
516 v -> selectHeaderDetailSource(v, HeaderDetail.DATE_YEAR));
518 if (header.hasLiteralDateMonth()) {
519 b.templateDetailsMonthSource.setText(R.string.template_details_source_literal);
520 final Integer dateMonth = header.getDateMonth();
521 b.templateDetailsDateMonth.setText(
522 (dateMonth == null) ? null : String.valueOf(dateMonth));
523 b.templateDetailsDateMonthLayout.setVisibility(View.VISIBLE);
526 b.templateDetailsDateMonthLayout.setVisibility(View.GONE);
527 b.templateDetailsMonthSource.setText(
528 String.format(Locale.US, groupNoText, header.getDateMonthMatchGroup(),
529 getMatchGroupText(header.getDateMonthMatchGroup())));
531 b.templateDetailsMonthSourceLabel.setOnClickListener(
532 v -> selectHeaderDetailSource(v, HeaderDetail.DATE_MONTH));
533 b.templateDetailsMonthSource.setOnClickListener(
534 v -> selectHeaderDetailSource(v, HeaderDetail.DATE_MONTH));
536 if (header.hasLiteralDateDay()) {
537 b.templateDetailsDaySource.setText(R.string.template_details_source_literal);
538 final Integer dateDay = header.getDateDay();
539 b.templateDetailsDateDay.setText(
540 (dateDay == null) ? null : String.valueOf(dateDay));
541 b.templateDetailsDateDayLayout.setVisibility(View.VISIBLE);
544 b.templateDetailsDateDayLayout.setVisibility(View.GONE);
545 b.templateDetailsDaySource.setText(
546 String.format(Locale.US, groupNoText, header.getDateDayMatchGroup(),
547 getMatchGroupText(header.getDateDayMatchGroup())));
549 b.templateDetailsDaySourceLabel.setOnClickListener(
550 v -> selectHeaderDetailSource(v, HeaderDetail.DATE_DAY));
551 b.templateDetailsDaySource.setOnClickListener(
552 v -> selectHeaderDetailSource(v, HeaderDetail.DATE_DAY));
554 if (header.hasLiteralTransactionDescription()) {
555 b.templateTransactionDescriptionSource.setText(
556 R.string.template_details_source_literal);
557 b.transactionDescription.setText(header.getTransactionDescription());
558 b.transactionDescriptionLayout.setVisibility(View.VISIBLE);
561 b.transactionDescriptionLayout.setVisibility(View.GONE);
562 b.templateTransactionDescriptionSource.setText(
563 String.format(Locale.US, groupNoText,
564 header.getTransactionDescriptionMatchGroup(), getMatchGroupText(
565 header.getTransactionDescriptionMatchGroup())));
568 b.templateTransactionDescriptionSourceLabel.setOnClickListener(
569 v -> selectHeaderDetailSource(v, HeaderDetail.DESCRIPTION));
570 b.templateTransactionDescriptionSource.setOnClickListener(
571 v -> selectHeaderDetailSource(v, HeaderDetail.DESCRIPTION));
573 if (header.hasLiteralTransactionComment()) {
574 b.templateTransactionCommentSource.setText(
575 R.string.template_details_source_literal);
576 b.transactionComment.setText(header.getTransactionComment());
577 b.transactionCommentLayout.setVisibility(View.VISIBLE);
580 b.transactionCommentLayout.setVisibility(View.GONE);
581 b.templateTransactionCommentSource.setText(String.format(Locale.US, groupNoText,
582 header.getTransactionCommentMatchGroup(),
583 getMatchGroupText(header.getTransactionCommentMatchGroup())));
586 b.templateTransactionCommentSourceLabel.setOnClickListener(
587 v -> selectHeaderDetailSource(v, HeaderDetail.COMMENT));
588 b.templateTransactionCommentSource.setOnClickListener(
589 v -> selectHeaderDetailSource(v, HeaderDetail.COMMENT));
591 b.templateDetailsHeadScanQrButton.setOnClickListener(this::scanTestQR);
593 b.templateIsFallbackSwitch.setChecked(header.isFallback());
594 b.templateIsFallbackText.setText(
595 header.isFallback() ? R.string.template_is_fallback_yes
596 : R.string.template_is_fallback_no);
598 checkPatternError(header);
601 enableUpdatePropagation();
604 private void checkPatternError(TemplateDetailsItem.Header item) {
605 if (item.getPatternError() != null) {
606 b.patternLayout.setError(item.getPatternError());
607 b.patternHintTitle.setVisibility(View.GONE);
608 b.patternHintText.setVisibility(View.GONE);
611 b.patternLayout.setError(null);
612 if (item.testMatch() != null) {
613 b.patternHintText.setText(item.testMatch());
614 b.patternHintTitle.setVisibility(View.VISIBLE);
615 b.patternHintText.setVisibility(View.VISIBLE);
618 b.patternLayout.setError(null);
619 b.patternHintTitle.setVisibility(View.GONE);
620 b.patternHintText.setVisibility(View.GONE);
625 private void scanTestQR(View view) {
626 Context ctx = view.getContext();
627 if (ctx instanceof QR.QRScanTrigger)
628 ((QR.QRScanTrigger) ctx).triggerQRScan();
632 public class AccountRow extends BaseItem {
633 private final TemplateDetailsAccountBinding b;
634 public AccountRow(@NonNull TemplateDetailsAccountBinding binding) {
635 super(binding.getRoot());
638 TextWatcher accountNameWatcher = new TextWatcher() {
640 public void beforeTextChanged(CharSequence s, int start, int count, int after) {}
642 public void onTextChanged(CharSequence s, int start, int before, int count) {}
644 public void afterTextChanged(Editable s) {
645 if (updatePropagationDisabled)
648 TemplateDetailsItem.AccountRow accRow = getItem();
649 Logger.debug(D_TEMPLATE_UI,
650 "Storing changed account name " + s + "; accRow=" + accRow);
651 accRow.setAccountName(String.valueOf(s));
653 mModel.applyList(null);
656 b.templateDetailsAccountName.addTextChangedListener(accountNameWatcher);
657 b.templateDetailsAccountName.setAdapter(new AccountAutocompleteAdapter(b.getRoot()
659 b.templateDetailsAccountName.setOnItemClickListener(
660 (parent, view, position, id) -> b.templateDetailsAccountName.setText(
661 ((TextView) view).getText()));
662 TextWatcher accountCommentWatcher = new TextWatcher() {
664 public void beforeTextChanged(CharSequence s, int start, int count, int after) {}
666 public void onTextChanged(CharSequence s, int start, int before, int count) {}
668 public void afterTextChanged(Editable s) {
669 if (updatePropagationDisabled)
672 TemplateDetailsItem.AccountRow accRow = getItem();
673 Logger.debug(D_TEMPLATE_UI,
674 "Storing changed account comment " + s + "; accRow=" + accRow);
675 accRow.setAccountComment(String.valueOf(s));
677 mModel.applyList(null);
680 b.templateDetailsAccountComment.addTextChangedListener(accountCommentWatcher);
682 b.templateDetailsAccountAmount.addTextChangedListener(new TextWatcher() {
684 public void beforeTextChanged(CharSequence s, int start, int count, int after) {
687 public void onTextChanged(CharSequence s, int start, int before, int count) {
690 public void afterTextChanged(Editable s) {
691 if (updatePropagationDisabled)
694 TemplateDetailsItem.AccountRow accRow = getItem();
696 String str = String.valueOf(s);
697 if (Misc.emptyIsNull(str) == null) {
698 accRow.setAmount(null);
702 final float amount = Data.parseNumber(str);
703 accRow.setAmount(amount);
704 b.templateDetailsAccountAmountLayout.setError(null);
706 Logger.debug(D_TEMPLATE_UI, String.format(Locale.US,
707 "Storing changed account amount %s [%4.2f]; accRow=%s", s,
710 catch (NumberFormatException | ParseException e) {
711 b.templateDetailsAccountAmountLayout.setError("!");
715 mModel.applyList(null);
718 b.templateDetailsAccountAmount.setOnFocusChangeListener((v, hasFocus) -> {
722 TemplateDetailsItem.AccountRow accRow = getItem();
723 if (!accRow.hasLiteralAmount())
725 Float amt = accRow.getAmount();
729 b.templateDetailsAccountAmount.setText(Data.formatNumber(amt));
732 b.negateAmountSwitch.setOnCheckedChangeListener((buttonView, isChecked) -> {
733 if (updatePropagationDisabled)
736 getItem().setNegateAmount(isChecked);
737 b.templateDetailsNegateAmountText.setText(
738 isChecked ? R.string.template_account_change_amount_sign
739 : R.string.template_account_keep_amount_sign);
741 final View.OnClickListener negLabelClickListener =
742 (view) -> b.negateAmountSwitch.toggle();
743 b.templateDetailsNegateAmountLabel.setOnClickListener(negLabelClickListener);
744 b.templateDetailsNegateAmountText.setOnClickListener(negLabelClickListener);
745 manageAccountLabelDrag();
747 @SuppressLint("ClickableViewAccessibility")
748 public void manageAccountLabelDrag() {
749 b.patternAccountLabel.setOnTouchListener((v, event) -> {
750 if (event.getAction() == MotionEvent.ACTION_DOWN) {
751 itemTouchHelper.startDrag(this);
757 void bind(TemplateDetailsItem item) {
758 disableUpdatePropagation();
760 final Resources resources = b.getRoot()
762 String groupNoText = resources.getString(R.string.template_item_match_group_source);
764 Logger.debug("drag", String.format(Locale.US, "Binding account id %d, pos %d at %d",
765 item.getId(), item.getPosition(), getAdapterPosition()));
766 TemplateDetailsItem.AccountRow accRow = item.asAccountRowItem();
767 b.patternAccountLabel.setText(String.format(Locale.US,
768 resources.getString(R.string.template_details_account_row_label),
769 accRow.getPosition()));
770 if (accRow.hasLiteralAccountName()) {
771 b.templateDetailsAccountNameLayout.setVisibility(View.VISIBLE);
772 b.templateDetailsAccountName.setText(accRow.getAccountName());
773 b.templateDetailsAccountNameSource.setText(
774 R.string.template_details_source_literal);
777 b.templateDetailsAccountNameLayout.setVisibility(View.GONE);
778 b.templateDetailsAccountNameSource.setText(
779 String.format(Locale.US, groupNoText, accRow.getAccountNameMatchGroup(),
780 getMatchGroupText(accRow.getAccountNameMatchGroup())));
783 if (accRow.hasLiteralAccountComment()) {
784 b.templateDetailsAccountCommentLayout.setVisibility(View.VISIBLE);
785 b.templateDetailsAccountComment.setText(accRow.getAccountComment());
786 b.templateDetailsAccountCommentSource.setText(
787 R.string.template_details_source_literal);
790 b.templateDetailsAccountCommentLayout.setVisibility(View.GONE);
791 b.templateDetailsAccountCommentSource.setText(
792 String.format(Locale.US, groupNoText,
793 accRow.getAccountCommentMatchGroup(),
794 getMatchGroupText(accRow.getAccountCommentMatchGroup())));
797 if (accRow.hasLiteralAmount()) {
798 b.templateDetailsAccountAmountSource.setText(
799 R.string.template_details_source_literal);
800 b.templateDetailsAccountAmount.setVisibility(View.VISIBLE);
801 Float amt = accRow.getAmount();
802 b.templateDetailsAccountAmount.setText((amt == null) ? null : String.format(
803 Data.locale.getValue(), "%,4.2f", (accRow.getAmount())));
804 b.negateAmountSwitch.setVisibility(View.GONE);
805 b.templateDetailsNegateAmountLabel.setVisibility(View.GONE);
806 b.templateDetailsNegateAmountText.setVisibility(View.GONE);
809 b.templateDetailsAccountAmountSource.setText(
810 String.format(Locale.US, groupNoText, accRow.getAmountMatchGroup(),
811 getMatchGroupText(accRow.getAmountMatchGroup())));
812 b.templateDetailsAccountAmountLayout.setVisibility(View.GONE);
813 b.negateAmountSwitch.setVisibility(View.VISIBLE);
814 b.negateAmountSwitch.setChecked(accRow.isNegateAmount());
815 b.templateDetailsNegateAmountText.setText(
816 accRow.isNegateAmount() ? R.string.template_account_change_amount_sign
817 : R.string.template_account_keep_amount_sign);
818 b.templateDetailsNegateAmountLabel.setVisibility(View.VISIBLE);
819 b.templateDetailsNegateAmountText.setVisibility(View.VISIBLE);
822 b.templateAccountNameSourceLabel.setOnClickListener(
823 v -> selectAccountRowDetailSource(v, AccDetail.ACCOUNT));
824 b.templateDetailsAccountNameSource.setOnClickListener(
825 v -> selectAccountRowDetailSource(v, AccDetail.ACCOUNT));
826 b.templateAccountCommentSourceLabel.setOnClickListener(
827 v -> selectAccountRowDetailSource(v, AccDetail.COMMENT));
828 b.templateDetailsAccountCommentSource.setOnClickListener(
829 v -> selectAccountRowDetailSource(v, AccDetail.COMMENT));
830 b.templateAccountAmountSourceLabel.setOnClickListener(
831 v -> selectAccountRowDetailSource(v, AccDetail.AMOUNT));
832 b.templateDetailsAccountAmountSource.setOnClickListener(
833 v -> selectAccountRowDetailSource(v, AccDetail.AMOUNT));
836 enableUpdatePropagation();
839 private @NotNull TemplateDetailsItem.AccountRow getItem() {
840 return differ.getCurrentList()
841 .get(getAdapterPosition())
844 private void selectAccountRowDetailSource(View v, AccDetail detail) {
845 TemplateDetailsItem.AccountRow accRow = getItem();
846 final TemplateDetailsItem.Header header = getHeader();
847 Logger.debug(D_TEMPLATE_UI, "header is " + header);
848 TemplateDetailSourceSelectorFragment sel =
849 TemplateDetailSourceSelectorFragment.newInstance(1, header.getPattern(),
850 header.getTestText());
851 sel.setOnSourceSelectedListener((literal, group) -> {
855 accRow.switchToLiteralAccountName();
858 accRow.switchToLiteralAccountComment();
861 accRow.switchToLiteralAmount();
864 throw new IllegalStateException("Unexpected detail " + detail);
870 accRow.setAccountNameMatchGroup(group);
873 accRow.setAccountCommentMatchGroup(group);
876 accRow.setAmountMatchGroup(group);
879 throw new IllegalStateException("Unexpected detail " + detail);
883 notifyItemChanged(getAdapterPosition());
885 final AppCompatActivity activity = (AppCompatActivity) v.getContext();
886 sel.show(activity.getSupportFragmentManager(), "template-details-source-selector");