]> git.ktnx.net Git - mobile-ledger.git/blob - app/src/main/java/net/ktnx/mobileledger/ui/patterns/PatternDetailsAdapter.java
1484eab7925a7afd755dc664b65a771b09b4c338
[mobile-ledger.git] / app / src / main / java / net / ktnx / mobileledger / ui / patterns / PatternDetailsAdapter.java
1 /*
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.
8  *
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.
13  *
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/>.
16  */
17
18 package net.ktnx.mobileledger.ui.patterns;
19
20 import android.text.Editable;
21 import android.text.TextWatcher;
22 import android.view.LayoutInflater;
23 import android.view.View;
24 import android.view.ViewGroup;
25
26 import androidx.annotation.NonNull;
27 import androidx.appcompat.app.AppCompatActivity;
28 import androidx.recyclerview.widget.AsyncListDiffer;
29 import androidx.recyclerview.widget.DiffUtil;
30 import androidx.recyclerview.widget.RecyclerView;
31
32 import net.ktnx.mobileledger.R;
33 import net.ktnx.mobileledger.databinding.PatternDetailsAccountBinding;
34 import net.ktnx.mobileledger.databinding.PatternDetailsHeaderBinding;
35 import net.ktnx.mobileledger.db.PatternBase;
36 import net.ktnx.mobileledger.model.Data;
37 import net.ktnx.mobileledger.model.PatternDetailsItem;
38 import net.ktnx.mobileledger.ui.PatternDetailSourceSelectorFragment;
39 import net.ktnx.mobileledger.ui.QRScanCapableFragment;
40 import net.ktnx.mobileledger.utils.Logger;
41 import net.ktnx.mobileledger.utils.Misc;
42
43 import org.jetbrains.annotations.NotNull;
44
45 import java.text.ParseException;
46 import java.util.ArrayList;
47 import java.util.List;
48 import java.util.Locale;
49 import java.util.regex.Matcher;
50 import java.util.regex.Pattern;
51
52 class PatternDetailsAdapter extends RecyclerView.Adapter<PatternDetailsAdapter.ViewHolder> {
53     private static final String D_PATTERN_UI = "pattern-ui";
54     private final AsyncListDiffer<PatternDetailsItem> differ;
55     public PatternDetailsAdapter() {
56         super();
57         setHasStableIds(true);
58         differ = new AsyncListDiffer<>(this, new DiffUtil.ItemCallback<PatternDetailsItem>() {
59             @Override
60             public boolean areItemsTheSame(@NonNull PatternDetailsItem oldItem,
61                                            @NonNull PatternDetailsItem newItem) {
62                 if (oldItem.getType() != newItem.getType())
63                     return false;
64                 if (oldItem.getType()
65                            .equals(PatternDetailsItem.Type.HEADER))
66                     return true;    // only one header item, ever
67                 // the rest is comparing two account row items
68                 return oldItem.asAccountRowItem()
69                               .getId() == newItem.asAccountRowItem()
70                                                  .getId();
71             }
72             @Override
73             public boolean areContentsTheSame(@NonNull PatternDetailsItem oldItem,
74                                               @NonNull PatternDetailsItem newItem) {
75                 if (oldItem.getType()
76                            .equals(PatternDetailsItem.Type.HEADER))
77                 {
78                     PatternDetailsItem.Header oldHeader = oldItem.asHeaderItem();
79                     PatternDetailsItem.Header newHeader = newItem.asHeaderItem();
80
81                     return oldHeader.equalContents(newHeader);
82                 }
83                 else {
84                     PatternDetailsItem.AccountRow oldAcc = oldItem.asAccountRowItem();
85                     PatternDetailsItem.AccountRow newAcc = newItem.asAccountRowItem();
86
87                     return oldAcc.equalContents(newAcc);
88                 }
89             }
90         });
91     }
92     @Override
93     public long getItemId(int position) {
94         // header item is always first and IDs id may duplicate some of the account IDs
95         if (position == 0)
96             return -1;
97         PatternDetailsItem.AccountRow accRow = differ.getCurrentList()
98                                                      .get(position)
99                                                      .asAccountRowItem();
100         return accRow.getId();
101     }
102     @Override
103     public int getItemViewType(int position) {
104
105         return differ.getCurrentList()
106                      .get(position)
107                      .getType()
108                      .toInt();
109     }
110     @NonNull
111     @Override
112     public PatternDetailsAdapter.ViewHolder onCreateViewHolder(@NonNull ViewGroup parent,
113                                                                int viewType) {
114         final LayoutInflater inflater = LayoutInflater.from(parent.getContext());
115         switch (viewType) {
116             case PatternDetailsItem.TYPE.header:
117                 return new Header(PatternDetailsHeaderBinding.inflate(inflater, parent, false));
118             case PatternDetailsItem.TYPE.accountItem:
119                 return new AccountRow(
120                         PatternDetailsAccountBinding.inflate(inflater, parent, false));
121             default:
122                 throw new IllegalStateException("Unsupported view type " + viewType);
123         }
124     }
125     @Override
126     public void onBindViewHolder(@NonNull PatternDetailsAdapter.ViewHolder holder, int position) {
127         PatternDetailsItem item = differ.getCurrentList()
128                                         .get(position);
129         holder.bind(item);
130     }
131     @Override
132     public int getItemCount() {
133         return differ.getCurrentList()
134                      .size();
135     }
136     public void setPatternItems(List<PatternBase> items) {
137         ArrayList<PatternDetailsItem> list = new ArrayList<>();
138         for (PatternBase p : items) {
139             PatternDetailsItem item = PatternDetailsItem.fromRoomObject(p);
140             list.add(item);
141         }
142         setItems(list);
143     }
144     public void setItems(List<PatternDetailsItem> items) {
145         differ.submitList(items);
146     }
147     public String getMatchGroupText(int groupNumber) {
148         PatternDetailsItem.Header header = getHeader();
149         Pattern p = header.getCompiledPattern();
150         if (p == null)
151             return null;
152
153         final String testText = Misc.nullIsEmpty(header.getTestText());
154         Matcher m = p.matcher(testText);
155         if (m.matches() && m.groupCount() >= groupNumber)
156             return m.group(groupNumber);
157         else
158             return null;
159     }
160     protected PatternDetailsItem.Header getHeader() {
161         return differ.getCurrentList()
162                      .get(0)
163                      .asHeaderItem();
164     }
165
166     private enum HeaderDetail {DESCRIPTION, COMMENT, DATE_YEAR, DATE_MONTH, DATE_DAY}
167
168     private enum AccDetail {ACCOUNT, COMMENT, AMOUNT}
169
170     public abstract static class ViewHolder extends RecyclerView.ViewHolder {
171         ViewHolder(@NonNull View itemView) {
172             super(itemView);
173         }
174         abstract void bind(PatternDetailsItem item);
175     }
176
177     public class Header extends ViewHolder {
178         private final PatternDetailsHeaderBinding b;
179         public Header(@NonNull PatternDetailsHeaderBinding binding) {
180             super(binding.getRoot());
181             b = binding;
182
183             TextWatcher patternNameWatcher = new TextWatcher() {
184                 @Override
185                 public void beforeTextChanged(CharSequence s, int start, int count, int after) {}
186                 @Override
187                 public void onTextChanged(CharSequence s, int start, int before, int count) {}
188                 @Override
189                 public void afterTextChanged(Editable s) {
190                     final PatternDetailsItem.Header header = getItem();
191                     Logger.debug(D_PATTERN_UI,
192                             "Storing changed pattern name " + s + "; header=" + header);
193                     header.setName(String.valueOf(s));
194                 }
195             };
196             b.patternName.addTextChangedListener(patternNameWatcher);
197             TextWatcher patternWatcher = new TextWatcher() {
198                 @Override
199                 public void beforeTextChanged(CharSequence s, int start, int count, int after) {}
200                 @Override
201                 public void onTextChanged(CharSequence s, int start, int before, int count) {}
202                 @Override
203                 public void afterTextChanged(Editable s) {
204                     final PatternDetailsItem.Header header = getItem();
205                     Logger.debug(D_PATTERN_UI,
206                             "Storing changed pattern " + s + "; header=" + header);
207                     header.setPattern(String.valueOf(s));
208                 }
209             };
210             b.pattern.addTextChangedListener(patternWatcher);
211             TextWatcher testTextWatcher = new TextWatcher() {
212                 @Override
213                 public void beforeTextChanged(CharSequence s, int start, int count, int after) {}
214                 @Override
215                 public void onTextChanged(CharSequence s, int start, int before, int count) {}
216                 @Override
217                 public void afterTextChanged(Editable s) {
218                     final PatternDetailsItem.Header header = getItem();
219                     Logger.debug(D_PATTERN_UI,
220                             "Storing changed test text " + s + "; header=" + header);
221                     header.setTestText(String.valueOf(s));
222                 }
223             };
224             b.testText.addTextChangedListener(testTextWatcher);
225             TextWatcher transactionDescriptionWatcher = new TextWatcher() {
226                 @Override
227                 public void beforeTextChanged(CharSequence s, int start, int count, int after) {
228                 }
229                 @Override
230                 public void onTextChanged(CharSequence s, int start, int before, int count) {
231
232                 }
233                 @Override
234                 public void afterTextChanged(Editable s) {
235                     final PatternDetailsItem.Header header = getItem();
236                     Logger.debug(D_PATTERN_UI,
237                             "Storing changed transaction description " + s + "; header=" + header);
238                     header.setTransactionDescription(String.valueOf(s));
239                 }
240             };
241             b.transactionDescription.addTextChangedListener(transactionDescriptionWatcher);
242             TextWatcher transactionCommentWatcher = new TextWatcher() {
243                 @Override
244                 public void beforeTextChanged(CharSequence s, int start, int count, int after) {
245
246                 }
247                 @Override
248                 public void onTextChanged(CharSequence s, int start, int before, int count) {
249
250                 }
251                 @Override
252                 public void afterTextChanged(Editable s) {
253                     final PatternDetailsItem.Header header = getItem();
254                     Logger.debug(D_PATTERN_UI,
255                             "Storing changed transaction description " + s + "; header=" + header);
256                     header.setTransactionComment(String.valueOf(s));
257                 }
258             };
259             b.transactionComment.addTextChangedListener(transactionCommentWatcher);
260         }
261         @NotNull
262         private PatternDetailsItem.Header getItem() {
263             int pos = getAdapterPosition();
264             return differ.getCurrentList()
265                          .get(pos)
266                          .asHeaderItem();
267         }
268         private void selectHeaderDetailSource(View v, HeaderDetail detail) {
269             PatternDetailsItem.Header header = getItem();
270             Logger.debug(D_PATTERN_UI, "header is " + header);
271             PatternDetailSourceSelectorFragment sel =
272                     PatternDetailSourceSelectorFragment.newInstance(1, header.getPattern(),
273                             header.getTestText());
274             sel.setOnSourceSelectedListener((literal, group) -> {
275                 if (literal) {
276                     switch (detail) {
277                         case DESCRIPTION:
278                             header.switchToLiteralTransactionDescription();
279                             break;
280                         case COMMENT:
281                             header.switchToLiteralTransactionComment();
282                             break;
283                         case DATE_YEAR:
284                             header.switchToLiteralDateYear();
285                             break;
286                         case DATE_MONTH:
287                             header.switchToLiteralDateMonth();
288                             break;
289                         case DATE_DAY:
290                             header.switchToLiteralDateDay();
291                             break;
292                         default:
293                             throw new IllegalStateException("Unexpected detail " + detail);
294                     }
295                 }
296                 else {
297                     switch (detail) {
298                         case DESCRIPTION:
299                             header.setTransactionDescriptionMatchGroup(group);
300                             break;
301                         case COMMENT:
302                             header.setTransactionCommentMatchGroup(group);
303                             break;
304                         case DATE_YEAR:
305                             header.setDateYearMatchGroup(group);
306                             break;
307                         case DATE_MONTH:
308                             header.setDateMonthMatchGroup(group);
309                             break;
310                         case DATE_DAY:
311                             header.setDateDayMatchGroup(group);
312                             break;
313                         default:
314                             throw new IllegalStateException("Unexpected detail " + detail);
315                     }
316                 }
317
318                 notifyItemChanged(getAdapterPosition());
319             });
320             final AppCompatActivity activity = (AppCompatActivity) v.getContext();
321             sel.show(activity.getSupportFragmentManager(), "pattern-details-source-selector");
322         }
323         @Override
324         void bind(PatternDetailsItem item) {
325             PatternDetailsItem.Header header = item.asHeaderItem();
326             Logger.debug(D_PATTERN_UI, "Binding to header " + header);
327
328             b.patternName.setText(header.getName());
329             b.pattern.setText(header.getPattern());
330             b.testText.setText(header.getTestText());
331
332             if (header.hasLiteralDateYear()) {
333                 b.patternDetailsYearSource.setText(R.string.pattern_details_source_literal);
334                 b.patternDetailsDateYear.setText(String.valueOf(header.getDateYear()));
335                 b.patternDetailsDateYearLayout.setVisibility(View.VISIBLE);
336             }
337             else {
338                 b.patternDetailsDateYearLayout.setVisibility(View.GONE);
339                 b.patternDetailsYearSource.setText(
340                         String.format(Locale.US, "Group %d (%s)", header.getDateYearMatchGroup(),
341                                 getMatchGroupText(header.getDateYearMatchGroup())));
342             }
343             b.patternDetailsYearSourceLabel.setOnClickListener(
344                     v -> selectHeaderDetailSource(v, HeaderDetail.DATE_YEAR));
345             b.patternDetailsYearSource.setOnClickListener(
346                     v -> selectHeaderDetailSource(v, HeaderDetail.DATE_YEAR));
347
348             if (header.hasLiteralDateMonth()) {
349                 b.patternDetailsMonthSource.setText(R.string.pattern_details_source_literal);
350                 b.patternDetailsDateMonth.setText(String.valueOf(header.getDateMonth()));
351                 b.patternDetailsDateMonthLayout.setVisibility(View.VISIBLE);
352             }
353             else {
354                 b.patternDetailsDateMonthLayout.setVisibility(View.GONE);
355                 b.patternDetailsMonthSource.setText(
356                         String.format(Locale.US, "Group %d (%s)", header.getDateMonthMatchGroup(),
357                                 getMatchGroupText(header.getDateMonthMatchGroup())));
358             }
359             b.patternDetailsMonthSourceLabel.setOnClickListener(
360                     v -> selectHeaderDetailSource(v, HeaderDetail.DATE_MONTH));
361             b.patternDetailsMonthSource.setOnClickListener(
362                     v -> selectHeaderDetailSource(v, HeaderDetail.DATE_MONTH));
363
364             if (header.hasLiteralDateDay()) {
365                 b.patternDetailsDaySource.setText(R.string.pattern_details_source_literal);
366                 b.patternDetailsDateDay.setText(String.valueOf(header.getDateDay()));
367                 b.patternDetailsDateDayLayout.setVisibility(View.VISIBLE);
368             }
369             else {
370                 b.patternDetailsDateDayLayout.setVisibility(View.GONE);
371                 b.patternDetailsDaySource.setText(
372                         String.format(Locale.US, "Group %d (%s)", header.getDateDayMatchGroup(),
373                                 getMatchGroupText(header.getDateDayMatchGroup())));
374             }
375             b.patternDetailsDaySourceLabel.setOnClickListener(
376                     v -> selectHeaderDetailSource(v, HeaderDetail.DATE_DAY));
377             b.patternDetailsDaySource.setOnClickListener(
378                     v -> selectHeaderDetailSource(v, HeaderDetail.DATE_DAY));
379
380             if (header.hasLiteralTransactionDescription()) {
381                 b.patternTransactionDescriptionSource.setText(
382                         R.string.pattern_details_source_literal);
383                 b.transactionDescription.setText(header.getTransactionDescription());
384                 b.transactionDescriptionLayout.setVisibility(View.VISIBLE);
385             }
386             else {
387                 b.transactionDescriptionLayout.setVisibility(View.GONE);
388                 b.patternTransactionDescriptionSource.setText(
389                         String.format(Locale.US, "Group %d (%s)",
390                                 header.getTransactionDescriptionMatchGroup(),
391                                 getMatchGroupText(header.getTransactionDescriptionMatchGroup())));
392
393             }
394             b.patternTransactionDescriptionSourceLabel.setOnClickListener(
395                     v -> selectHeaderDetailSource(v, HeaderDetail.DESCRIPTION));
396             b.patternTransactionDescriptionSource.setOnClickListener(
397                     v -> selectHeaderDetailSource(v, HeaderDetail.DESCRIPTION));
398
399             if (header.hasLiteralTransactionComment()) {
400                 b.patternTransactionCommentSource.setText(R.string.pattern_details_source_literal);
401                 b.transactionComment.setText(header.getTransactionComment());
402                 b.transactionCommentLayout.setVisibility(View.VISIBLE);
403             }
404             else {
405                 b.transactionCommentLayout.setVisibility(View.GONE);
406                 b.patternTransactionCommentSource.setText(String.format(Locale.US, "Group %d (%s)",
407                         header.getTransactionCommentMatchGroup(),
408                         getMatchGroupText(header.getTransactionCommentMatchGroup())));
409
410             }
411             b.patternTransactionCommentSourceLabel.setOnClickListener(
412                     v -> selectHeaderDetailSource(v, HeaderDetail.COMMENT));
413             b.patternTransactionCommentSource.setOnClickListener(
414                     v -> selectHeaderDetailSource(v, HeaderDetail.COMMENT));
415
416             b.patternDetailsHeadScanQrButton.setOnClickListener(this::scanTestQR);
417
418         }
419         private void scanTestQR(View view) {
420             QRScanCapableFragment.triggerQRScan();
421         }
422     }
423
424     public class AccountRow extends ViewHolder {
425         private final PatternDetailsAccountBinding b;
426         public AccountRow(@NonNull PatternDetailsAccountBinding binding) {
427             super(binding.getRoot());
428             b = binding;
429
430             TextWatcher accountNameWatcher = new TextWatcher() {
431                 @Override
432                 public void beforeTextChanged(CharSequence s, int start, int count, int after) {}
433                 @Override
434                 public void onTextChanged(CharSequence s, int start, int before, int count) {}
435                 @Override
436                 public void afterTextChanged(Editable s) {
437                     PatternDetailsItem.AccountRow accRow = getItem();
438                     Logger.debug(D_PATTERN_UI,
439                             "Storing changed account name " + s + "; accRow=" + accRow);
440                     accRow.setAccountName(String.valueOf(s));
441                 }
442             };
443             b.patternDetailsAccountName.addTextChangedListener(accountNameWatcher);
444             TextWatcher accountCommentWatcher = new TextWatcher() {
445                 @Override
446                 public void beforeTextChanged(CharSequence s, int start, int count, int after) {}
447                 @Override
448                 public void onTextChanged(CharSequence s, int start, int before, int count) {}
449                 @Override
450                 public void afterTextChanged(Editable s) {
451                     PatternDetailsItem.AccountRow accRow = getItem();
452                     Logger.debug(D_PATTERN_UI,
453                             "Storing changed account comment " + s + "; accRow=" + accRow);
454                     accRow.setAccountComment(String.valueOf(s));
455                 }
456             };
457             b.patternDetailsAccountComment.addTextChangedListener(accountCommentWatcher);
458
459             b.patternDetailsAccountAmount.addTextChangedListener(new TextWatcher() {
460                 @Override
461                 public void beforeTextChanged(CharSequence s, int start, int count, int after) {
462
463                 }
464                 @Override
465                 public void onTextChanged(CharSequence s, int start, int before, int count) {
466
467                 }
468                 @Override
469                 public void afterTextChanged(Editable s) {
470                     PatternDetailsItem.AccountRow accRow = getItem();
471
472                     String str = String.valueOf(s);
473                     if (Misc.emptyIsNull(str) == null) {
474                         accRow.setAmount(null);
475                     }
476                     else {
477                         try {
478                             final float amount = Data.parseNumber(str);
479                             accRow.setAmount(amount);
480                             b.patternDetailsAccountAmountLayout.setError(null);
481
482                             Logger.debug(D_PATTERN_UI, String.format(Locale.US,
483                                     "Storing changed account amount %s [%4.2f]; accRow=%s", s,
484                                     amount, accRow));
485                         }
486                         catch (NumberFormatException | ParseException e) {
487                             b.patternDetailsAccountAmountLayout.setError("!");
488                         }
489                     }
490                 }
491             });
492             b.patternDetailsAccountAmount.setOnFocusChangeListener((v, hasFocus) -> {
493                 if (hasFocus)
494                     return;
495
496                 PatternDetailsItem.AccountRow accRow = getItem();
497                 if (!accRow.hasLiteralAmount())
498                     return;
499                 Float amt = accRow.getAmount();
500                 if (amt == null)
501                     return;
502
503                 b.patternDetailsAccountAmount.setText(Data.formatNumber(amt));
504             });
505         }
506         @Override
507         void bind(PatternDetailsItem item) {
508             PatternDetailsItem.AccountRow accRow = item.asAccountRowItem();
509             if (accRow.hasLiteralAccountName()) {
510                 b.patternDetailsAccountNameLayout.setVisibility(View.VISIBLE);
511                 b.patternDetailsAccountName.setText(accRow.getAccountName());
512                 b.patternDetailsAccountNameSource.setText(R.string.pattern_details_source_literal);
513             }
514             else {
515                 b.patternDetailsAccountNameLayout.setVisibility(View.GONE);
516                 b.patternDetailsAccountNameSource.setText(
517                         String.format(Locale.US, "Group %d (%s)", accRow.getAccountNameMatchGroup(),
518                                 getMatchGroupText(accRow.getAccountNameMatchGroup())));
519             }
520
521             if (accRow.hasLiteralAccountComment()) {
522                 b.patternDetailsAccountCommentLayout.setVisibility(View.VISIBLE);
523                 b.patternDetailsAccountComment.setText(accRow.getAccountComment());
524                 b.patternDetailsAccountCommentSource.setText(
525                         R.string.pattern_details_source_literal);
526             }
527             else {
528                 b.patternDetailsAccountCommentLayout.setVisibility(View.GONE);
529                 b.patternDetailsAccountCommentSource.setText(
530                         String.format(Locale.US, "Group %d (%s)",
531                                 accRow.getAccountCommentMatchGroup(),
532                                 getMatchGroupText(accRow.getAccountCommentMatchGroup())));
533             }
534
535             if (accRow.hasLiteralAmount()) {
536                 b.patternDetailsAccountAmountSource.setText(
537                         R.string.pattern_details_source_literal);
538                 b.patternDetailsAccountAmount.setVisibility(View.VISIBLE);
539                 Float amt = accRow.getAmount();
540                 b.patternDetailsAccountAmount.setText((amt == null) ? null : String.format(
541                         Data.locale.getValue(), "%,4.2f", (accRow.getAmount())));
542             }
543             else {
544                 b.patternDetailsAccountAmountSource.setText(
545                         String.format(Locale.US, "Group %d (%s)", accRow.getAmountMatchGroup(),
546                                 getMatchGroupText(accRow.getAmountMatchGroup())));
547                 b.patternDetailsAccountAmountLayout.setVisibility(View.GONE);
548             }
549
550             b.patternAccountNameSourceLabel.setOnClickListener(
551                     v -> selectAccountRowDetailSource(v, AccDetail.ACCOUNT));
552             b.patternDetailsAccountNameSource.setOnClickListener(
553                     v -> selectAccountRowDetailSource(v, AccDetail.ACCOUNT));
554             b.patternAccountCommentSourceLabel.setOnClickListener(
555                     v -> selectAccountRowDetailSource(v, AccDetail.COMMENT));
556             b.patternDetailsAccountCommentSource.setOnClickListener(
557                     v -> selectAccountRowDetailSource(v, AccDetail.COMMENT));
558             b.patternAccountAmountSourceLabel.setOnClickListener(
559                     v -> selectAccountRowDetailSource(v, AccDetail.AMOUNT));
560             b.patternDetailsAccountAmountSource.setOnClickListener(
561                     v -> selectAccountRowDetailSource(v, AccDetail.AMOUNT));
562         }
563         private @NotNull PatternDetailsItem.AccountRow getItem() {
564             return differ.getCurrentList()
565                          .get(getAdapterPosition())
566                          .asAccountRowItem();
567         }
568         private void selectAccountRowDetailSource(View v, AccDetail detail) {
569             PatternDetailsItem.AccountRow accRow = getItem();
570             final PatternDetailsItem.Header header = getHeader();
571             Logger.debug(D_PATTERN_UI, "header is " + header);
572             PatternDetailSourceSelectorFragment sel =
573                     PatternDetailSourceSelectorFragment.newInstance(1, header.getPattern(),
574                             header.getTestText());
575             sel.setOnSourceSelectedListener((literal, group) -> {
576                 if (literal) {
577                     switch (detail) {
578                         case ACCOUNT:
579                             accRow.switchToLiteralAccountName();
580                             break;
581                         case COMMENT:
582                             accRow.switchToLiteralAccountComment();
583                             break;
584                         case AMOUNT:
585                             accRow.switchToLiteralAmount();
586                             break;
587                         default:
588                             throw new IllegalStateException("Unexpected detail " + detail);
589                     }
590                 }
591                 else {
592                     switch (detail) {
593                         case ACCOUNT:
594                             accRow.setAccountNameMatchGroup(group);
595                             break;
596                         case COMMENT:
597                             accRow.setAccountCommentMatchGroup(group);
598                             break;
599                         case AMOUNT:
600                             accRow.setAmountMatchGroup(group);
601                             break;
602                         default:
603                             throw new IllegalStateException("Unexpected detail " + detail);
604                     }
605                 }
606
607                 notifyItemChanged(getAdapterPosition());
608             });
609             final AppCompatActivity activity = (AppCompatActivity) v.getContext();
610             sel.show(activity.getSupportFragmentManager(), "pattern-details-source-selector");
611         }
612     }
613 }