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.lifecycle.LifecycleOwner;
34 import androidx.recyclerview.widget.AsyncListDiffer;
35 import androidx.recyclerview.widget.DiffUtil;
36 import androidx.recyclerview.widget.ItemTouchHelper;
37 import androidx.recyclerview.widget.RecyclerView;
39 import net.ktnx.mobileledger.BuildConfig;
40 import net.ktnx.mobileledger.R;
41 import net.ktnx.mobileledger.databinding.TemplateDetailsAccountBinding;
42 import net.ktnx.mobileledger.databinding.TemplateDetailsHeaderBinding;
43 import net.ktnx.mobileledger.db.AccountAutocompleteAdapter;
44 import net.ktnx.mobileledger.db.DB;
45 import net.ktnx.mobileledger.model.Data;
46 import net.ktnx.mobileledger.model.TemplateDetailsItem;
47 import net.ktnx.mobileledger.ui.CurrencySelectorFragment;
48 import net.ktnx.mobileledger.ui.HelpDialog;
49 import net.ktnx.mobileledger.ui.QR;
50 import net.ktnx.mobileledger.ui.TemplateDetailSourceSelectorFragment;
51 import net.ktnx.mobileledger.utils.Logger;
52 import net.ktnx.mobileledger.utils.Misc;
54 import org.jetbrains.annotations.NotNull;
56 import java.text.ParseException;
57 import java.util.List;
58 import java.util.Locale;
59 import java.util.regex.Matcher;
60 import java.util.regex.Pattern;
62 class TemplateDetailsAdapter extends RecyclerView.Adapter<TemplateDetailsAdapter.ViewHolder> {
63 private static final String D_TEMPLATE_UI = "template-ui";
64 private final AsyncListDiffer<TemplateDetailsItem> differ;
65 private final TemplateDetailsViewModel mModel;
66 private final ItemTouchHelper itemTouchHelper;
67 public TemplateDetailsAdapter(TemplateDetailsViewModel model) {
70 setHasStableIds(true);
71 differ = new AsyncListDiffer<>(this, new DiffUtil.ItemCallback<TemplateDetailsItem>() {
73 public boolean areItemsTheSame(@NonNull TemplateDetailsItem oldItem,
74 @NonNull TemplateDetailsItem newItem) {
75 if (oldItem.getType() != newItem.getType())
78 .equals(TemplateDetailsItem.Type.HEADER))
79 return true; // only one header item, ever
80 // the rest is comparing two account row items
81 return oldItem.asAccountRowItem()
82 .getId() == newItem.asAccountRowItem()
86 public boolean areContentsTheSame(@NonNull TemplateDetailsItem oldItem,
87 @NonNull TemplateDetailsItem newItem) {
89 .equals(TemplateDetailsItem.Type.HEADER))
91 TemplateDetailsItem.Header oldHeader = oldItem.asHeaderItem();
92 TemplateDetailsItem.Header newHeader = newItem.asHeaderItem();
94 return oldHeader.equalContents(newHeader);
97 TemplateDetailsItem.AccountRow oldAcc = oldItem.asAccountRowItem();
98 TemplateDetailsItem.AccountRow newAcc = newItem.asAccountRowItem();
100 return oldAcc.equalContents(newAcc);
104 itemTouchHelper = new ItemTouchHelper(new ItemTouchHelper.Callback() {
106 public float getMoveThreshold(@NonNull RecyclerView.ViewHolder viewHolder) {
110 public boolean isLongPressDragEnabled() {
114 public RecyclerView.ViewHolder chooseDropTarget(
115 @NonNull RecyclerView.ViewHolder selected,
116 @NonNull List<RecyclerView.ViewHolder> dropTargets, int curX, int curY) {
117 RecyclerView.ViewHolder best = null;
118 int bestDistance = 0;
119 for (RecyclerView.ViewHolder v : dropTargets) {
123 final int viewTop = v.itemView.getTop();
124 int distance = Math.abs(viewTop - curY);
127 bestDistance = distance;
130 if (distance < bestDistance) {
131 bestDistance = distance;
137 Logger.debug("dnd", "Best target is " + best);
141 public boolean canDropOver(@NonNull RecyclerView recyclerView,
142 @NonNull RecyclerView.ViewHolder current,
143 @NonNull RecyclerView.ViewHolder target) {
144 final int adapterPosition = target.getBindingAdapterPosition();
146 // first item is immovable
147 if (adapterPosition == 0)
150 return super.canDropOver(recyclerView, current, target);
153 public int getMovementFlags(@NonNull RecyclerView recyclerView,
154 @NonNull RecyclerView.ViewHolder viewHolder) {
156 // the top item (transaction params) is always there
157 final int adapterPosition = viewHolder.getBindingAdapterPosition();
158 if (adapterPosition > 0)
159 flags |= makeFlag(ItemTouchHelper.ACTION_STATE_DRAG,
160 ItemTouchHelper.UP | ItemTouchHelper.DOWN) |
161 makeFlag(ItemTouchHelper.ACTION_STATE_SWIPE,
162 ItemTouchHelper.START | ItemTouchHelper.END);
167 public boolean onMove(@NonNull RecyclerView recyclerView,
168 @NonNull RecyclerView.ViewHolder viewHolder,
169 @NonNull RecyclerView.ViewHolder target) {
171 final int fromPosition = viewHolder.getBindingAdapterPosition();
172 final int toPosition = target.getBindingAdapterPosition();
173 if (fromPosition == toPosition) {
174 Logger.debug("drag", String.format(Locale.US,
175 "Ignoring request to move an account from position %d to %d",
176 fromPosition, toPosition));
181 String.format(Locale.US, "Moving account from %d to %d", fromPosition,
183 mModel.moveItem(fromPosition, toPosition);
188 public void onSwiped(@NonNull RecyclerView.ViewHolder viewHolder, int direction) {
189 int pos = viewHolder.getBindingAdapterPosition();
190 mModel.removeItem(pos);
195 public void onAttachedToRecyclerView(@NonNull RecyclerView recyclerView) {
196 super.onAttachedToRecyclerView(recyclerView);
198 itemTouchHelper.attachToRecyclerView(recyclerView);
201 public void onDetachedFromRecyclerView(@NonNull RecyclerView recyclerView) {
202 super.onDetachedFromRecyclerView(recyclerView);
204 itemTouchHelper.attachToRecyclerView(null);
207 public long getItemId(int position) {
208 // header item is always first and IDs id may duplicate some of the account IDs
211 TemplateDetailsItem.AccountRow accRow = differ.getCurrentList()
214 return accRow.getId();
217 public int getItemViewType(int position) {
219 return differ.getCurrentList()
226 public TemplateDetailsAdapter.ViewHolder onCreateViewHolder(@NonNull ViewGroup parent,
228 final LayoutInflater inflater = LayoutInflater.from(parent.getContext());
230 case TemplateDetailsItem.TYPE.header:
231 return new Header(TemplateDetailsHeaderBinding.inflate(inflater, parent, false));
232 case TemplateDetailsItem.TYPE.accountItem:
233 return new AccountRow(
234 TemplateDetailsAccountBinding.inflate(inflater, parent, false));
236 throw new IllegalStateException("Unsupported view type " + viewType);
240 public void onBindViewHolder(@NonNull TemplateDetailsAdapter.ViewHolder holder, int position) {
241 TemplateDetailsItem item = differ.getCurrentList()
246 public int getItemCount() {
247 return differ.getCurrentList()
250 public void setItems(List<TemplateDetailsItem> items) {
251 if (BuildConfig.DEBUG) {
252 Logger.debug("tmpl", "Got new list");
253 for (int i = 1; i < items.size(); i++) {
254 final TemplateDetailsItem item = items.get(i);
256 String.format(Locale.US, " %d: id %d, pos %d", i, item.getId(),
257 item.getPosition()));
260 differ.submitList(items);
262 public String getMatchGroupText(int groupNumber) {
263 TemplateDetailsItem.Header header = getHeader();
264 Pattern p = header.getCompiledPattern();
268 final String testText = Misc.nullIsEmpty(header.getTestText());
269 Matcher m = p.matcher(testText);
270 if (m.matches() && m.groupCount() >= groupNumber)
271 return m.group(groupNumber);
275 protected TemplateDetailsItem.Header getHeader() {
276 return differ.getCurrentList()
281 private enum HeaderDetail {DESCRIPTION, COMMENT, DATE_YEAR, DATE_MONTH, DATE_DAY}
283 private enum AccDetail {ACCOUNT, COMMENT, AMOUNT, CURRENCY}
285 public abstract static class ViewHolder extends RecyclerView.ViewHolder {
286 ViewHolder(@NonNull View itemView) {
289 abstract void bind(TemplateDetailsItem item);
292 private abstract static class BaseItem extends ViewHolder {
293 boolean updatePropagationDisabled = false;
294 BaseItem(@NonNull View itemView) {
297 void disableUpdatePropagation() {
298 updatePropagationDisabled = true;
300 void enableUpdatePropagation() {
301 updatePropagationDisabled = false;
305 public class Header extends BaseItem {
306 private final TemplateDetailsHeaderBinding b;
307 boolean updatePropagationDisabled = false;
308 public Header(@NonNull TemplateDetailsHeaderBinding binding) {
309 super(binding.getRoot());
312 TextWatcher templateNameWatcher = new TextWatcher() {
314 public void beforeTextChanged(CharSequence s, int start, int count, int after) {}
316 public void onTextChanged(CharSequence s, int start, int before, int count) {}
318 public void afterTextChanged(Editable s) {
319 if (updatePropagationDisabled)
322 final TemplateDetailsItem.Header header = getItem();
323 Logger.debug(D_TEMPLATE_UI,
324 "Storing changed template name " + s + "; header=" + header);
325 header.setName(String.valueOf(s));
328 b.templateName.addTextChangedListener(templateNameWatcher);
330 TextWatcher patternWatcher = new TextWatcher() {
332 public void beforeTextChanged(CharSequence s, int start, int count, int after) {}
334 public void onTextChanged(CharSequence s, int start, int before, int count) {}
336 public void afterTextChanged(Editable s) {
337 if (updatePropagationDisabled)
340 final TemplateDetailsItem.Header header = getItem();
341 Logger.debug(D_TEMPLATE_UI,
342 "Storing changed pattern " + s + "; header=" + header);
343 header.setPattern(String.valueOf(s));
345 checkPatternError(header);
348 b.pattern.addTextChangedListener(patternWatcher);
350 TextWatcher testTextWatcher = new TextWatcher() {
352 public void beforeTextChanged(CharSequence s, int start, int count, int after) {}
354 public void onTextChanged(CharSequence s, int start, int before, int count) {}
356 public void afterTextChanged(Editable s) {
357 if (updatePropagationDisabled)
360 final TemplateDetailsItem.Header header = getItem();
361 Logger.debug(D_TEMPLATE_UI,
362 "Storing changed test text " + s + "; header=" + header);
363 header.setTestText(String.valueOf(s));
365 checkPatternError(header);
368 b.testText.addTextChangedListener(testTextWatcher);
370 TextWatcher transactionDescriptionWatcher = new TextWatcher() {
372 public void beforeTextChanged(CharSequence s, int start, int count, int after) {
375 public void onTextChanged(CharSequence s, int start, int before, int count) {
378 public void afterTextChanged(Editable s) {
379 if (updatePropagationDisabled)
382 final TemplateDetailsItem.Header header = getItem();
383 Logger.debug(D_TEMPLATE_UI,
384 "Storing changed transaction description " + s + "; header=" + header);
385 header.setTransactionDescription(String.valueOf(s));
388 b.transactionDescription.addTextChangedListener(transactionDescriptionWatcher);
389 TextWatcher transactionCommentWatcher = new TextWatcher() {
391 public void beforeTextChanged(CharSequence s, int start, int count, int after) {
394 public void onTextChanged(CharSequence s, int start, int before, int count) {
397 public void afterTextChanged(Editable s) {
398 if (updatePropagationDisabled)
401 final TemplateDetailsItem.Header header = getItem();
402 Logger.debug(D_TEMPLATE_UI,
403 "Storing changed transaction description " + s + "; header=" + header);
404 header.setTransactionComment(String.valueOf(s));
407 b.transactionComment.addTextChangedListener(transactionCommentWatcher);
409 b.templateIsFallbackSwitch.setOnCheckedChangeListener((buttonView, isChecked) -> {
410 if (updatePropagationDisabled)
413 getItem().setFallback(isChecked);
414 b.templateIsFallbackText.setText(isChecked ? R.string.template_is_fallback_yes
415 : R.string.template_is_fallback_no);
417 final View.OnClickListener fallbackLabelClickListener =
418 (view) -> b.templateIsFallbackSwitch.toggle();
419 b.templateIsFallbackLabel.setOnClickListener(fallbackLabelClickListener);
420 b.templateIsFallbackText.setOnClickListener(fallbackLabelClickListener);
421 b.templateParamsHelpButton.setOnClickListener(v -> HelpDialog.show(b.getRoot()
423 R.string.template_details_template_params_label, R.array.template_params_help));
426 private TemplateDetailsItem.Header getItem() {
427 int pos = getBindingAdapterPosition();
428 return differ.getCurrentList()
432 private void selectHeaderDetailSource(View v, HeaderDetail detail) {
433 TemplateDetailsItem.Header header = getItem();
434 Logger.debug(D_TEMPLATE_UI, "header is " + header);
435 TemplateDetailSourceSelectorFragment sel =
436 TemplateDetailSourceSelectorFragment.newInstance(1, header.getPattern(),
437 header.getTestText());
438 sel.setOnSourceSelectedListener((literal, group) -> {
442 header.switchToLiteralTransactionDescription();
445 header.switchToLiteralTransactionComment();
448 header.switchToLiteralDateYear();
451 header.switchToLiteralDateMonth();
454 header.switchToLiteralDateDay();
457 throw new IllegalStateException("Unexpected detail " + detail);
463 header.setTransactionDescriptionMatchGroup(group);
466 header.setTransactionCommentMatchGroup(group);
469 header.setDateYearMatchGroup(group);
472 header.setDateMonthMatchGroup(group);
475 header.setDateDayMatchGroup(group);
478 throw new IllegalStateException("Unexpected detail " + detail);
482 notifyItemChanged(getBindingAdapterPosition());
484 final AppCompatActivity activity = (AppCompatActivity) v.getContext();
485 sel.show(activity.getSupportFragmentManager(), "template-details-source-selector");
488 void bind(TemplateDetailsItem item) {
489 TemplateDetailsItem.Header header = item.asHeaderItem();
490 Logger.debug(D_TEMPLATE_UI, "Binding to header " + header);
492 disableUpdatePropagation();
494 String groupNoText = b.getRoot()
496 .getString(R.string.template_item_match_group_source);
498 b.templateName.setText(header.getName());
499 b.pattern.setText(header.getPattern());
500 b.testText.setText(header.getTestText());
502 if (header.hasLiteralDateYear()) {
503 b.yearSource.setText(R.string.template_details_source_literal);
504 final Integer dateYear = header.getDateYear();
505 b.templateDetailsDateYear.setText(
506 (dateYear == null) ? null : String.valueOf(dateYear));
507 b.yearLayout.setVisibility(View.VISIBLE);
510 b.yearLayout.setVisibility(View.GONE);
511 b.yearSource.setText(
512 String.format(Locale.US, groupNoText, header.getDateYearMatchGroup(),
513 getMatchGroupText(header.getDateYearMatchGroup())));
515 b.yearSourceLabel.setOnClickListener(
516 v -> selectHeaderDetailSource(v, HeaderDetail.DATE_YEAR));
517 b.yearSource.setOnClickListener(
518 v -> selectHeaderDetailSource(v, HeaderDetail.DATE_YEAR));
520 if (header.hasLiteralDateMonth()) {
521 b.monthSource.setText(R.string.template_details_source_literal);
522 final Integer dateMonth = header.getDateMonth();
523 b.templateDetailsDateMonth.setText(
524 (dateMonth == null) ? null : String.valueOf(dateMonth));
525 b.monthLayout.setVisibility(View.VISIBLE);
528 b.monthLayout.setVisibility(View.GONE);
529 b.monthSource.setText(
530 String.format(Locale.US, groupNoText, header.getDateMonthMatchGroup(),
531 getMatchGroupText(header.getDateMonthMatchGroup())));
533 b.monthSourceLabel.setOnClickListener(
534 v -> selectHeaderDetailSource(v, HeaderDetail.DATE_MONTH));
535 b.monthSource.setOnClickListener(
536 v -> selectHeaderDetailSource(v, HeaderDetail.DATE_MONTH));
538 if (header.hasLiteralDateDay()) {
539 b.daySource.setText(R.string.template_details_source_literal);
540 final Integer dateDay = header.getDateDay();
541 b.templateDetailsDateDay.setText(
542 (dateDay == null) ? null : String.valueOf(dateDay));
543 b.dayLayout.setVisibility(View.VISIBLE);
546 b.dayLayout.setVisibility(View.GONE);
548 String.format(Locale.US, groupNoText, header.getDateDayMatchGroup(),
549 getMatchGroupText(header.getDateDayMatchGroup())));
551 b.daySourceLabel.setOnClickListener(
552 v -> selectHeaderDetailSource(v, HeaderDetail.DATE_DAY));
553 b.daySource.setOnClickListener(
554 v -> selectHeaderDetailSource(v, HeaderDetail.DATE_DAY));
556 if (header.hasLiteralTransactionDescription()) {
557 b.templateTransactionDescriptionSource.setText(
558 R.string.template_details_source_literal);
559 b.transactionDescription.setText(header.getTransactionDescription());
560 b.transactionDescriptionLayout.setVisibility(View.VISIBLE);
563 b.transactionDescriptionLayout.setVisibility(View.GONE);
564 b.templateTransactionDescriptionSource.setText(
565 String.format(Locale.US, groupNoText,
566 header.getTransactionDescriptionMatchGroup(), getMatchGroupText(
567 header.getTransactionDescriptionMatchGroup())));
570 b.templateTransactionDescriptionSourceLabel.setOnClickListener(
571 v -> selectHeaderDetailSource(v, HeaderDetail.DESCRIPTION));
572 b.templateTransactionDescriptionSource.setOnClickListener(
573 v -> selectHeaderDetailSource(v, HeaderDetail.DESCRIPTION));
575 if (header.hasLiteralTransactionComment()) {
576 b.templateTransactionCommentSource.setText(
577 R.string.template_details_source_literal);
578 b.transactionComment.setText(header.getTransactionComment());
579 b.transactionCommentLayout.setVisibility(View.VISIBLE);
582 b.transactionCommentLayout.setVisibility(View.GONE);
583 b.templateTransactionCommentSource.setText(String.format(Locale.US, groupNoText,
584 header.getTransactionCommentMatchGroup(),
585 getMatchGroupText(header.getTransactionCommentMatchGroup())));
588 b.templateTransactionCommentSourceLabel.setOnClickListener(
589 v -> selectHeaderDetailSource(v, HeaderDetail.COMMENT));
590 b.templateTransactionCommentSource.setOnClickListener(
591 v -> selectHeaderDetailSource(v, HeaderDetail.COMMENT));
593 b.templateDetailsHeadScanQrButton.setOnClickListener(this::scanTestQR);
595 b.templateIsFallbackSwitch.setChecked(header.isFallback());
596 b.templateIsFallbackText.setText(
597 header.isFallback() ? R.string.template_is_fallback_yes
598 : R.string.template_is_fallback_no);
600 checkPatternError(header);
603 enableUpdatePropagation();
606 private void checkPatternError(TemplateDetailsItem.Header item) {
607 if (item.getPatternError() != null) {
608 b.patternLayout.setError(item.getPatternError());
609 b.patternHintTitle.setVisibility(View.GONE);
610 b.patternHintText.setVisibility(View.GONE);
613 b.patternLayout.setError(null);
614 if (item.testMatch() != null) {
615 b.patternHintText.setText(item.testMatch());
616 b.patternHintTitle.setVisibility(View.VISIBLE);
617 b.patternHintText.setVisibility(View.VISIBLE);
620 b.patternLayout.setError(null);
621 b.patternHintTitle.setVisibility(View.GONE);
622 b.patternHintText.setVisibility(View.GONE);
627 private void scanTestQR(View view) {
628 Context ctx = view.getContext();
629 if (ctx instanceof QR.QRScanTrigger)
630 ((QR.QRScanTrigger) ctx).triggerQRScan();
634 public class AccountRow extends BaseItem {
635 private final TemplateDetailsAccountBinding b;
636 public AccountRow(@NonNull TemplateDetailsAccountBinding binding) {
637 super(binding.getRoot());
640 TextWatcher accountNameWatcher = new TextWatcher() {
642 public void beforeTextChanged(CharSequence s, int start, int count, int after) {}
644 public void onTextChanged(CharSequence s, int start, int before, int count) {}
646 public void afterTextChanged(Editable s) {
647 if (updatePropagationDisabled)
650 TemplateDetailsItem.AccountRow accRow = getItem();
651 Logger.debug(D_TEMPLATE_UI,
652 "Storing changed account name " + s + "; accRow=" + accRow);
653 accRow.setAccountName(String.valueOf(s));
655 mModel.applyList(null);
658 b.templateDetailsAccountName.addTextChangedListener(accountNameWatcher);
659 b.templateDetailsAccountName.setAdapter(new AccountAutocompleteAdapter(b.getRoot()
661 b.templateDetailsAccountName.setOnItemClickListener(
662 (parent, view, position, id) -> b.templateDetailsAccountName.setText(
663 ((TextView) view).getText()));
664 TextWatcher accountCommentWatcher = new TextWatcher() {
666 public void beforeTextChanged(CharSequence s, int start, int count, int after) {}
668 public void onTextChanged(CharSequence s, int start, int before, int count) {}
670 public void afterTextChanged(Editable s) {
671 if (updatePropagationDisabled)
674 TemplateDetailsItem.AccountRow accRow = getItem();
675 Logger.debug(D_TEMPLATE_UI,
676 "Storing changed account comment " + s + "; accRow=" + accRow);
677 accRow.setAccountComment(String.valueOf(s));
679 mModel.applyList(null);
682 b.templateDetailsAccountComment.addTextChangedListener(accountCommentWatcher);
684 b.templateDetailsAccountAmount.addTextChangedListener(new TextWatcher() {
686 public void beforeTextChanged(CharSequence s, int start, int count, int after) {
689 public void onTextChanged(CharSequence s, int start, int before, int count) {
692 public void afterTextChanged(Editable s) {
693 if (updatePropagationDisabled)
696 TemplateDetailsItem.AccountRow accRow = getItem();
698 String str = String.valueOf(s);
699 if (Misc.emptyIsNull(str) == null) {
700 accRow.setAmount(null);
704 final float amount = Data.parseNumber(str);
705 accRow.setAmount(amount);
706 b.templateDetailsAccountAmountLayout.setError(null);
708 Logger.debug(D_TEMPLATE_UI, String.format(Locale.US,
709 "Storing changed account amount %s [%4.2f]; accRow=%s", s,
712 catch (NumberFormatException | ParseException e) {
713 b.templateDetailsAccountAmountLayout.setError("!");
717 mModel.applyList(null);
720 b.templateDetailsAccountAmount.setOnFocusChangeListener((v, hasFocus) -> {
724 TemplateDetailsItem.AccountRow accRow = getItem();
725 if (!accRow.hasLiteralAmount())
727 Float amt = accRow.getAmount();
731 b.templateDetailsAccountAmount.setText(Data.formatNumber(amt));
734 b.negateAmountSwitch.setOnCheckedChangeListener((buttonView, isChecked) -> {
735 if (updatePropagationDisabled)
738 getItem().setNegateAmount(isChecked);
739 b.templateDetailsNegateAmountText.setText(
740 isChecked ? R.string.template_account_change_amount_sign
741 : R.string.template_account_keep_amount_sign);
743 final View.OnClickListener negLabelClickListener =
744 (view) -> b.negateAmountSwitch.toggle();
745 b.templateDetailsNegateAmountLabel.setOnClickListener(negLabelClickListener);
746 b.templateDetailsNegateAmountText.setOnClickListener(negLabelClickListener);
747 manageAccountLabelDrag();
749 @SuppressLint("ClickableViewAccessibility")
750 public void manageAccountLabelDrag() {
751 b.patternAccountLabel.setOnTouchListener((v, event) -> {
752 if (event.getAction() == MotionEvent.ACTION_DOWN) {
753 itemTouchHelper.startDrag(this);
759 void bind(TemplateDetailsItem item) {
760 disableUpdatePropagation();
762 final Resources resources = b.getRoot()
764 String groupNoText = resources.getString(R.string.template_item_match_group_source);
766 Logger.debug("drag", String.format(Locale.US, "Binding account id %d, pos %d at %d",
767 item.getId(), item.getPosition(), getBindingAdapterPosition()));
768 TemplateDetailsItem.AccountRow accRow = item.asAccountRowItem();
769 b.patternAccountLabel.setText(String.format(Locale.US,
770 resources.getString(R.string.template_details_account_row_label),
771 accRow.getPosition()));
772 if (accRow.hasLiteralAccountName()) {
773 b.templateDetailsAccountNameLayout.setVisibility(View.VISIBLE);
774 b.templateDetailsAccountName.setText(accRow.getAccountName());
775 b.templateDetailsAccountNameSource.setText(
776 R.string.template_details_source_literal);
779 b.templateDetailsAccountNameLayout.setVisibility(View.GONE);
780 b.templateDetailsAccountNameSource.setText(
781 String.format(Locale.US, groupNoText, accRow.getAccountNameMatchGroup(),
782 getMatchGroupText(accRow.getAccountNameMatchGroup())));
785 if (accRow.hasLiteralAccountComment()) {
786 b.templateDetailsAccountCommentLayout.setVisibility(View.VISIBLE);
787 b.templateDetailsAccountComment.setText(accRow.getAccountComment());
788 b.templateDetailsAccountCommentSource.setText(
789 R.string.template_details_source_literal);
792 b.templateDetailsAccountCommentLayout.setVisibility(View.GONE);
793 b.templateDetailsAccountCommentSource.setText(
794 String.format(Locale.US, groupNoText,
795 accRow.getAccountCommentMatchGroup(),
796 getMatchGroupText(accRow.getAccountCommentMatchGroup())));
799 if (accRow.hasLiteralAmount()) {
800 b.templateDetailsAccountAmountSource.setText(
801 R.string.template_details_source_literal);
802 b.templateDetailsAccountAmount.setVisibility(View.VISIBLE);
803 Float amt = accRow.getAmount();
804 b.templateDetailsAccountAmount.setText((amt == null) ? null : String.format(
805 Data.locale.getValue(), "%,4.2f", (accRow.getAmount())));
806 b.negateAmountSwitch.setVisibility(View.GONE);
807 b.templateDetailsNegateAmountLabel.setVisibility(View.GONE);
808 b.templateDetailsNegateAmountText.setVisibility(View.GONE);
811 b.templateDetailsAccountAmountSource.setText(
812 String.format(Locale.US, groupNoText, accRow.getAmountMatchGroup(),
813 getMatchGroupText(accRow.getAmountMatchGroup())));
814 b.templateDetailsAccountAmountLayout.setVisibility(View.GONE);
815 b.negateAmountSwitch.setVisibility(View.VISIBLE);
816 b.negateAmountSwitch.setChecked(accRow.isNegateAmount());
817 b.templateDetailsNegateAmountText.setText(
818 accRow.isNegateAmount() ? R.string.template_account_change_amount_sign
819 : R.string.template_account_keep_amount_sign);
820 b.templateDetailsNegateAmountLabel.setVisibility(View.VISIBLE);
821 b.templateDetailsNegateAmountText.setVisibility(View.VISIBLE);
824 if (accRow.hasLiteralCurrency()) {
825 b.templateDetailsAccountCurrencySource.setText(
826 R.string.template_details_source_literal);
827 net.ktnx.mobileledger.db.Currency c = accRow.getCurrency();
829 b.templateDetailsAccountCurrency.setText(R.string.btn_no_currency);
831 b.templateDetailsAccountCurrency.setText(c.getName());
832 b.templateDetailsAccountCurrency.setVisibility(View.VISIBLE);
835 b.templateDetailsAccountCurrencySource.setText(
836 String.format(Locale.US, groupNoText, accRow.getCurrencyMatchGroup(),
837 getMatchGroupText(accRow.getCurrencyMatchGroup())));
838 b.templateDetailsAccountCurrency.setVisibility(View.GONE);
841 b.templateAccountNameSourceLabel.setOnClickListener(
842 v -> selectAccountRowDetailSource(v, AccDetail.ACCOUNT));
843 b.templateDetailsAccountNameSource.setOnClickListener(
844 v -> selectAccountRowDetailSource(v, AccDetail.ACCOUNT));
845 b.templateAccountCommentSourceLabel.setOnClickListener(
846 v -> selectAccountRowDetailSource(v, AccDetail.COMMENT));
847 b.templateDetailsAccountCommentSource.setOnClickListener(
848 v -> selectAccountRowDetailSource(v, AccDetail.COMMENT));
849 b.templateAccountAmountSourceLabel.setOnClickListener(
850 v -> selectAccountRowDetailSource(v, AccDetail.AMOUNT));
851 b.templateDetailsAccountAmountSource.setOnClickListener(
852 v -> selectAccountRowDetailSource(v, AccDetail.AMOUNT));
853 b.templateDetailsAccountCurrencySource.setOnClickListener(
854 v -> selectAccountRowDetailSource(v, AccDetail.CURRENCY));
855 b.templateAccountCurrencySourceLabel.setOnClickListener(
856 v -> selectAccountRowDetailSource(v, AccDetail.CURRENCY));
857 if (accRow.hasLiteralCurrency())
858 b.templateDetailsAccountCurrency.setOnClickListener(v -> {
859 CurrencySelectorFragment cpf = CurrencySelectorFragment.newInstance(
860 CurrencySelectorFragment.DEFAULT_COLUMN_COUNT, false);
861 cpf.setOnCurrencySelectedListener(text -> {
863 b.templateDetailsAccountCurrency.setText(R.string.btn_no_currency);
864 accRow.setCurrency(null);
867 b.templateDetailsAccountCurrency.setText(text);
871 .observe((LifecycleOwner) b.getRoot()
872 .getContext(), accRow::setCurrency);
876 ((TemplatesActivity) b.templateDetailsAccountCurrency.getContext()).getSupportFragmentManager(),
877 "currency-selector");
881 enableUpdatePropagation();
884 private @NotNull TemplateDetailsItem.AccountRow getItem() {
885 return differ.getCurrentList()
886 .get(getBindingAdapterPosition())
889 private void selectAccountRowDetailSource(View v, AccDetail detail) {
890 TemplateDetailsItem.AccountRow accRow = getItem();
891 final TemplateDetailsItem.Header header = getHeader();
892 Logger.debug(D_TEMPLATE_UI, "header is " + header);
893 TemplateDetailSourceSelectorFragment sel =
894 TemplateDetailSourceSelectorFragment.newInstance(1, header.getPattern(),
895 header.getTestText());
896 sel.setOnSourceSelectedListener((literal, group) -> {
900 accRow.switchToLiteralAccountName();
903 accRow.switchToLiteralAccountComment();
906 accRow.switchToLiteralAmount();
909 accRow.switchToLiteralCurrency();
912 throw new IllegalStateException("Unexpected detail " + detail);
918 accRow.setAccountNameMatchGroup(group);
921 accRow.setAccountCommentMatchGroup(group);
924 accRow.setAmountMatchGroup(group);
927 accRow.setCurrencyMatchGroup(group);
930 throw new IllegalStateException("Unexpected detail " + detail);
934 notifyItemChanged(getBindingAdapterPosition());
936 final AppCompatActivity activity = (AppCompatActivity) v.getContext();
937 sel.show(activity.getSupportFragmentManager(), "template-details-source-selector");