]> git.ktnx.net Git - mobile-ledger.git/blob - app/src/main/java/net/ktnx/mobileledger/ui/activity/NewTransactionItemHolder.java
e311eb94ba450c9542ef9f0151328ddb2003853d
[mobile-ledger.git] / app / src / main / java / net / ktnx / mobileledger / ui / activity / NewTransactionItemHolder.java
1 /*
2  * Copyright © 2020 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.activity;
19
20 import android.annotation.SuppressLint;
21 import android.graphics.Typeface;
22 import android.text.Editable;
23 import android.text.TextWatcher;
24 import android.view.Gravity;
25 import android.view.View;
26 import android.view.ViewGroup;
27 import android.view.inputmethod.EditorInfo;
28 import android.widget.AutoCompleteTextView;
29 import android.widget.EditText;
30 import android.widget.FrameLayout;
31 import android.widget.TextView;
32
33 import androidx.annotation.ColorInt;
34 import androidx.annotation.NonNull;
35 import androidx.appcompat.app.AppCompatActivity;
36 import androidx.constraintlayout.widget.ConstraintLayout;
37 import androidx.lifecycle.Observer;
38 import androidx.recyclerview.widget.RecyclerView;
39
40 import net.ktnx.mobileledger.R;
41 import net.ktnx.mobileledger.async.DescriptionSelectedCallback;
42 import net.ktnx.mobileledger.model.Currency;
43 import net.ktnx.mobileledger.model.Data;
44 import net.ktnx.mobileledger.model.LedgerTransactionAccount;
45 import net.ktnx.mobileledger.model.MobileLedgerProfile;
46 import net.ktnx.mobileledger.ui.CurrencySelectorFragment;
47 import net.ktnx.mobileledger.ui.DatePickerFragment;
48 import net.ktnx.mobileledger.ui.TextViewClearHelper;
49 import net.ktnx.mobileledger.utils.DimensionUtils;
50 import net.ktnx.mobileledger.utils.Logger;
51 import net.ktnx.mobileledger.utils.MLDB;
52 import net.ktnx.mobileledger.utils.Misc;
53 import net.ktnx.mobileledger.utils.SimpleDate;
54
55 import java.text.DecimalFormatSymbols;
56 import java.text.ParseException;
57 import java.util.Date;
58 import java.util.Locale;
59
60 import static net.ktnx.mobileledger.ui.activity.NewTransactionModel.ItemType;
61
62 class NewTransactionItemHolder extends RecyclerView.ViewHolder
63         implements DatePickerFragment.DatePickedListener, DescriptionSelectedCallback {
64     private final String decimalDot;
65     private final TextView tvCurrency;
66     private final Observer<Boolean> showCommentsObserver;
67     private final TextView tvTransactionComment;
68     private String decimalSeparator;
69     private NewTransactionModel.Item item;
70     private TextView tvDate;
71     private AutoCompleteTextView tvDescription;
72     private TextView tvDummy;
73     private AutoCompleteTextView tvAccount;
74     private TextView tvComment;
75     private EditText tvAmount;
76     private ViewGroup lHead;
77     private ViewGroup lAccount;
78     private FrameLayout lPadding;
79     private MobileLedgerProfile mProfile;
80     private Date date;
81     private Observer<SimpleDate> dateObserver;
82     private Observer<String> descriptionObserver;
83     private Observer<String> transactionCommentObserver;
84     private Observer<String> hintObserver;
85     private Observer<Integer> focusedAccountObserver;
86     private Observer<Integer> accountCountObserver;
87     private Observer<Boolean> editableObserver;
88     private Observer<Currency.Position> currencyPositionObserver;
89     private Observer<Boolean> currencyGapObserver;
90     private Observer<Locale> localeObserver;
91     private Observer<Currency> currencyObserver;
92     private Observer<Boolean> showCurrencyObserver;
93     private Observer<String> commentObserver;
94     private Observer<Boolean> amountValidityObserver;
95     private boolean inUpdate = false;
96     private boolean syncingData = false;
97     private View commentButton;
98     //TODO multiple amounts with different currencies per posting
99     NewTransactionItemHolder(@NonNull View itemView, NewTransactionItemsAdapter adapter) {
100         super(itemView);
101         lAccount = itemView.findViewById(R.id.ntr_account);
102         tvAccount = lAccount.findViewById(R.id.account_row_acc_name);
103         tvComment = lAccount.findViewById(R.id.comment);
104         tvTransactionComment = itemView.findViewById(R.id.transaction_comment);
105         new TextViewClearHelper().attachToTextView((EditText) tvComment);
106         commentButton = lAccount.findViewById(R.id.comment_button);
107         tvAmount = itemView.findViewById(R.id.account_row_acc_amounts);
108         tvCurrency = itemView.findViewById(R.id.currency);
109         tvDate = itemView.findViewById(R.id.new_transaction_date);
110         tvDescription = itemView.findViewById(R.id.new_transaction_description);
111         tvDummy = itemView.findViewById(R.id.dummy_text);
112         lHead = itemView.findViewById(R.id.ntr_data);
113         lPadding = itemView.findViewById(R.id.ntr_padding);
114         final View commentLayout = itemView.findViewById(R.id.comment_layout);
115         final View transactionCommentLayout =
116                 itemView.findViewById(R.id.transaction_comment_layout);
117
118         tvDescription.setNextFocusForwardId(View.NO_ID);
119         tvAccount.setNextFocusForwardId(View.NO_ID);
120         tvAmount.setNextFocusForwardId(View.NO_ID); // magic!
121
122         tvDate.setOnClickListener(v -> pickTransactionDate());
123
124         commentButton.setOnClickListener(v -> {
125             tvComment.setVisibility(View.VISIBLE);
126             tvComment.requestFocus();
127         });
128
129         transactionCommentLayout.findViewById(R.id.comment_button)
130                                 .setOnClickListener(v -> {
131                                     tvTransactionComment.setVisibility(View.VISIBLE);
132                                     tvTransactionComment.requestFocus();
133                                 });
134
135         mProfile = Data.getProfile();
136
137         View.OnFocusChangeListener focusMonitor = (v, hasFocus) -> {
138             final int id = v.getId();
139             if (hasFocus) {
140                 boolean wasSyncing = syncingData;
141                 syncingData = true;
142                 try {
143                     final int pos = getAdapterPosition();
144                     adapter.updateFocusedItem(pos);
145                     switch (id) {
146                         case R.id.account_row_acc_name:
147                             adapter.noteFocusIsOnAccount(pos);
148                             break;
149                         case R.id.account_row_acc_amounts:
150                             adapter.noteFocusIsOnAmount(pos);
151                             break;
152                         case R.id.comment:
153                             adapter.noteFocusIsOnComment(pos);
154                             break;
155                         case R.id.transaction_comment:
156                             adapter.noteFocusIsOnTransactionComment(pos);
157                             break;
158                         case R.id.new_transaction_description:
159                             adapter.noteFocusIsOnDescription(pos);
160                             break;
161                     }
162                 }
163                 finally {
164                     syncingData = wasSyncing;
165                 }
166             }
167
168             if (id == R.id.comment) {
169                 commentFocusChanged(tvComment, hasFocus);
170             }
171             else if (id == R.id.transaction_comment) {
172                 commentFocusChanged(tvTransactionComment, hasFocus);
173             }
174         };
175
176         tvDescription.setOnFocusChangeListener(focusMonitor);
177         tvAccount.setOnFocusChangeListener(focusMonitor);
178         tvAmount.setOnFocusChangeListener(focusMonitor);
179         tvComment.setOnFocusChangeListener(focusMonitor);
180         tvTransactionComment.setOnFocusChangeListener(focusMonitor);
181
182         MLDB.hookAutocompletionAdapter(tvDescription.getContext(), tvDescription,
183                 MLDB.DESCRIPTION_HISTORY_TABLE, "description", false, adapter, mProfile);
184         MLDB.hookAutocompletionAdapter(tvAccount.getContext(), tvAccount, MLDB.ACCOUNTS_TABLE,
185                 "name", true, this, mProfile);
186
187         decimalSeparator = String.valueOf(DecimalFormatSymbols.getInstance()
188                                                               .getMonetaryDecimalSeparator());
189         localeObserver = locale -> {
190             decimalSeparator = String.valueOf(DecimalFormatSymbols.getInstance(locale)
191                                                                   .getMonetaryDecimalSeparator());
192         };
193
194         decimalDot = ".";
195
196         final TextWatcher tw = new TextWatcher() {
197             @Override
198             public void beforeTextChanged(CharSequence s, int start, int count, int after) {
199             }
200
201             @Override
202             public void onTextChanged(CharSequence s, int start, int before, int count) {
203             }
204
205             @Override
206             public void afterTextChanged(Editable s) {
207 //                debug("input", "text changed");
208                 if (inUpdate)
209                     return;
210
211                 Logger.debug("textWatcher", "calling syncData()");
212                 syncData();
213                 Logger.debug("textWatcher",
214                         "syncData() returned, checking if transaction is submittable");
215                 adapter.checkTransactionSubmittable();
216                 Logger.debug("textWatcher", "done");
217             }
218         };
219         final TextWatcher amountWatcher = new TextWatcher() {
220             @Override
221             public void beforeTextChanged(CharSequence s, int start, int count, int after) {
222                 Logger.debug("num",
223                         String.format(Locale.US, "beforeTextChanged: start=%d, count=%d, after=%d",
224                                 start, count, after));
225             }
226             @Override
227             public void onTextChanged(CharSequence s, int start, int before, int count) {}
228             @Override
229             public void afterTextChanged(Editable s) {
230
231                 if (syncData())
232                     adapter.checkTransactionSubmittable();
233             }
234         };
235         tvDescription.addTextChangedListener(tw);
236         tvTransactionComment.addTextChangedListener(tw);
237         tvAccount.addTextChangedListener(tw);
238         tvComment.addTextChangedListener(tw);
239         tvAmount.addTextChangedListener(amountWatcher);
240
241         tvCurrency.setOnClickListener(v -> {
242             CurrencySelectorFragment cpf = new CurrencySelectorFragment();
243             cpf.showPositionAndPadding();
244             cpf.setOnCurrencySelectedListener(c -> item.setCurrency(c));
245             final AppCompatActivity activity = (AppCompatActivity) v.getContext();
246             cpf.show(activity.getSupportFragmentManager(), "currency-selector");
247         });
248
249         dateObserver = date -> {
250             if (syncingData)
251                 return;
252             syncingData = true;
253             try {
254                 tvDate.setText(item.getFormattedDate());
255             }
256             finally {
257                 syncingData = false;
258             }
259         };
260         descriptionObserver = description -> {
261             if (syncingData)
262                 return;
263             syncingData = true;
264             try {
265                 tvDescription.setText(description);
266             }
267             finally {
268                 syncingData = false;
269             }
270         };
271         transactionCommentObserver = transactionComment -> {
272             final View focusedView = tvTransactionComment.findFocus();
273             tvTransactionComment.setTypeface(null,
274                     (focusedView == tvTransactionComment) ? Typeface.NORMAL : Typeface.ITALIC);
275             tvTransactionComment.setVisibility(((focusedView != tvTransactionComment) &&
276                                                 Misc.isEmptyOrNull(transactionComment))
277                                                ? View.INVISIBLE : View.VISIBLE);
278
279         };
280         hintObserver = hint -> {
281             if (syncingData)
282                 return;
283             syncingData = true;
284             try {
285                 if (hint == null)
286                     tvAmount.setHint(R.string.zero_amount);
287                 else
288                     tvAmount.setHint(hint);
289             }
290             finally {
291                 syncingData = false;
292             }
293         };
294         editableObserver = this::setEditable;
295         commentFocusChanged(tvTransactionComment, false);
296         commentFocusChanged(tvComment, false);
297         focusedAccountObserver = index -> {
298             if ((index == null) || !index.equals(getAdapterPosition()) || itemView.hasFocus())
299                 return;
300
301             switch (item.getType()) {
302                 case generalData:
303                     // bad idea - double pop-up, and not really necessary.
304                     // the user can tap the input to get the calendar
305                     //if (!tvDate.hasFocus()) tvDate.requestFocus();
306                     switch (item.getFocusedElement()) {
307                         case TransactionComment:
308                             tvTransactionComment.setVisibility(View.VISIBLE);
309                             tvTransactionComment.requestFocus();
310                             break;
311                         case Description:
312                             boolean focused = tvDescription.requestFocus();
313                             tvDescription.dismissDropDown();
314                             if (focused)
315                                 Misc.showSoftKeyboard(
316                                         (NewTransactionActivity) tvDescription.getContext());
317                             break;
318                     }
319                     break;
320                 case transactionRow:
321                     switch (item.getFocusedElement()) {
322                         case Amount:
323                             tvAmount.requestFocus();
324                             break;
325                         case Comment:
326                             tvComment.setVisibility(View.VISIBLE);
327                             tvComment.requestFocus();
328                             break;
329                         case Account:
330                             boolean focused = tvAccount.requestFocus();
331                             tvAccount.dismissDropDown();
332                             if (focused)
333                                 Misc.showSoftKeyboard(
334                                         (NewTransactionActivity) tvAccount.getContext());
335                             break;
336                     }
337
338                     break;
339             }
340         };
341         accountCountObserver = count -> {
342             final int adapterPosition = getAdapterPosition();
343             final int layoutPosition = getLayoutPosition();
344             Logger.debug("holder",
345                     String.format(Locale.US, "count=%d; pos=%d, layoutPos=%d [%s]", count,
346                             adapterPosition, layoutPosition, item.getType()
347                                                                  .toString()
348                                                                  .concat(item.getType() ==
349                                                                          ItemType.transactionRow
350                                                                          ? String.format(Locale.US,
351                                                                          "'%s'=%s",
352                                                                          item.getAccount()
353                                                                              .getAccountName(),
354                                                                          item.getAccount()
355                                                                              .isAmountSet()
356                                                                          ? String.format(Locale.US,
357                                                                                  "%.2f",
358                                                                                  item.getAccount()
359                                                                                      .getAmount())
360                                                                          : "unset") : "")));
361             if (adapterPosition == count)
362                 tvAmount.setImeOptions(EditorInfo.IME_ACTION_DONE);
363             else
364                 tvAmount.setImeOptions(EditorInfo.IME_ACTION_NEXT);
365         };
366
367         currencyObserver = currency -> {
368             setCurrency(currency);
369             adapter.checkTransactionSubmittable();
370         };
371
372         currencyGapObserver =
373                 hasGap -> updateCurrencyPositionAndPadding(Data.currencySymbolPosition.getValue(),
374                         hasGap);
375
376         currencyPositionObserver =
377                 position -> updateCurrencyPositionAndPadding(position, Data.currencyGap.getValue());
378
379         showCurrencyObserver = showCurrency -> {
380             if (showCurrency) {
381                 tvCurrency.setVisibility(View.VISIBLE);
382             }
383             else {
384                 tvCurrency.setVisibility(View.GONE);
385                 item.setCurrency(null);
386             }
387         };
388
389         commentObserver = comment -> {
390             final View focusedView = tvComment.findFocus();
391             tvComment.setTypeface(null,
392                     (focusedView == tvComment) ? Typeface.NORMAL : Typeface.ITALIC);
393             tvComment.setVisibility(
394                     ((focusedView != tvComment) && Misc.isEmptyOrNull(comment)) ? View.INVISIBLE
395                                                                                 : View.VISIBLE);
396         };
397
398         showCommentsObserver = show -> {
399             final View amountLayout = itemView.findViewById(R.id.amount_layout);
400             ConstraintLayout.LayoutParams amountLayoutParams =
401                     (ConstraintLayout.LayoutParams) amountLayout.getLayoutParams();
402             ConstraintLayout.LayoutParams accountParams =
403                     (ConstraintLayout.LayoutParams) tvAccount.getLayoutParams();
404             if (show) {
405                 accountParams.endToStart = ConstraintLayout.LayoutParams.UNSET;
406                 accountParams.endToEnd = ConstraintLayout.LayoutParams.PARENT_ID;
407
408                 amountLayoutParams.topToTop = ConstraintLayout.LayoutParams.UNSET;
409                 amountLayoutParams.topToBottom = tvAccount.getId();
410
411                 commentLayout.setVisibility(View.VISIBLE);
412             }
413             else {
414                 accountParams.endToStart = amountLayout.getId();
415                 accountParams.endToEnd = ConstraintLayout.LayoutParams.UNSET;
416
417                 amountLayoutParams.topToBottom = ConstraintLayout.LayoutParams.UNSET;
418                 amountLayoutParams.topToTop = ConstraintLayout.LayoutParams.PARENT_ID;
419
420                 commentLayout.setVisibility(View.GONE);
421             }
422
423             tvAccount.setLayoutParams(accountParams);
424             amountLayout.setLayoutParams(amountLayoutParams);
425
426             transactionCommentLayout.setVisibility(show ? View.VISIBLE : View.GONE);
427         };
428
429         amountValidityObserver = valid -> {
430             tvAmount.setCompoundDrawablesRelativeWithIntrinsicBounds(
431                     valid ? 0 : R.drawable.ic_error_outline_black_24dp, 0, 0, 0);
432             tvAmount.setMinEms(valid ? 4 : 5);
433         };
434     }
435     private void commentFocusChanged(TextView textView, boolean hasFocus) {
436         @ColorInt int textColor;
437         textColor = tvDummy.getTextColors()
438                            .getDefaultColor();
439         if (hasFocus) {
440             textView.setTypeface(null, Typeface.NORMAL);
441             textView.setHint(R.string.transaction_account_comment_hint);
442         }
443         else {
444             int alpha = (textColor >> 24 & 0xff);
445             alpha = 3 * alpha / 4;
446             textColor = (alpha << 24) | (0x00ffffff & textColor);
447             textView.setTypeface(null, Typeface.ITALIC);
448             textView.setHint("");
449             if (Misc.isEmptyOrNull(textView.getText())) {
450                 textView.setVisibility(View.INVISIBLE);
451             }
452         }
453         textView.setTextColor(textColor);
454
455     }
456     private void updateCurrencyPositionAndPadding(Currency.Position position, boolean hasGap) {
457         ConstraintLayout.LayoutParams amountLP =
458                 (ConstraintLayout.LayoutParams) tvAmount.getLayoutParams();
459         ConstraintLayout.LayoutParams currencyLP =
460                 (ConstraintLayout.LayoutParams) tvCurrency.getLayoutParams();
461
462         if (position == Currency.Position.before) {
463             currencyLP.startToStart = ConstraintLayout.LayoutParams.PARENT_ID;
464             currencyLP.endToEnd = ConstraintLayout.LayoutParams.UNSET;
465
466             amountLP.endToEnd = ConstraintLayout.LayoutParams.PARENT_ID;
467             amountLP.endToStart = ConstraintLayout.LayoutParams.UNSET;
468             amountLP.startToStart = ConstraintLayout.LayoutParams.UNSET;
469             amountLP.startToEnd = tvCurrency.getId();
470
471             tvCurrency.setGravity(Gravity.END);
472         }
473         else {
474             currencyLP.startToStart = ConstraintLayout.LayoutParams.UNSET;
475             currencyLP.endToEnd = ConstraintLayout.LayoutParams.PARENT_ID;
476
477             amountLP.startToStart = ConstraintLayout.LayoutParams.PARENT_ID;
478             amountLP.startToEnd = ConstraintLayout.LayoutParams.UNSET;
479             amountLP.endToEnd = ConstraintLayout.LayoutParams.UNSET;
480             amountLP.endToStart = tvCurrency.getId();
481
482             tvCurrency.setGravity(Gravity.START);
483         }
484
485         amountLP.resolveLayoutDirection(tvAmount.getLayoutDirection());
486         currencyLP.resolveLayoutDirection(tvCurrency.getLayoutDirection());
487
488         tvAmount.setLayoutParams(amountLP);
489         tvCurrency.setLayoutParams(currencyLP);
490
491         // distance between the amount and the currency symbol
492         int gapSize = DimensionUtils.sp2px(tvCurrency.getContext(), 5);
493
494         if (position == Currency.Position.before) {
495             tvCurrency.setPaddingRelative(0, 0, hasGap ? gapSize : 0, 0);
496         }
497         else {
498             tvCurrency.setPaddingRelative(hasGap ? gapSize : 0, 0, 0, 0);
499         }
500     }
501     private void setCurrencyString(String currency) {
502         @ColorInt int textColor = tvDummy.getTextColors()
503                                          .getDefaultColor();
504         if ((currency == null) || currency.isEmpty()) {
505             tvCurrency.setText(R.string.currency_symbol);
506             int alpha = (textColor >> 24) & 0xff;
507             alpha = alpha * 3 / 4;
508             tvCurrency.setTextColor((alpha << 24) | (0x00ffffff & textColor));
509         }
510         else {
511             tvCurrency.setText(currency);
512             tvCurrency.setTextColor(textColor);
513         }
514     }
515     private void setCurrency(Currency currency) {
516         setCurrencyString((currency == null) ? null : currency.getName());
517     }
518     private void setEditable(Boolean editable) {
519         tvDate.setEnabled(editable);
520         tvDescription.setEnabled(editable);
521         tvAccount.setEnabled(editable);
522         tvAmount.setEnabled(editable);
523     }
524     private void beginUpdates() {
525         if (inUpdate)
526             throw new RuntimeException("Already in update mode");
527         inUpdate = true;
528     }
529     private void endUpdates() {
530         if (!inUpdate)
531             throw new RuntimeException("Not in update mode");
532         inUpdate = false;
533     }
534     /**
535      * syncData()
536      * <p>
537      * Stores the data from the UI elements into the model item
538      * Returns true if there were changes made that suggest transaction has to be
539      * checked for being submittable
540      */
541     private boolean syncData() {
542         if (item == null)
543             return false;
544
545         if (syncingData) {
546             Logger.debug("new-trans", "skipping syncData() loop");
547             return false;
548         }
549
550         syncingData = true;
551
552         try {
553             switch (item.getType()) {
554                 case generalData:
555                     item.setDate(String.valueOf(tvDate.getText()));
556                     item.setDescription(String.valueOf(tvDescription.getText()));
557                     item.setTransactionComment(String.valueOf(tvTransactionComment.getText()));
558                     break;
559                 case transactionRow:
560                     final LedgerTransactionAccount account = item.getAccount();
561                     account.setAccountName(String.valueOf(tvAccount.getText()));
562
563                     item.setComment(String.valueOf(tvComment.getText()));
564
565                     String amount = String.valueOf(tvAmount.getText());
566                     amount = amount.trim();
567
568                     if (amount.isEmpty()) {
569                         account.resetAmount();
570                         item.validateAmount();
571                     }
572                     else {
573                         try {
574                             amount = amount.replace(decimalSeparator, decimalDot);
575                             account.setAmount(Float.parseFloat(amount));
576                             item.validateAmount();
577                         }
578                         catch (NumberFormatException e) {
579                             Logger.debug("new-trans", String.format(
580                                     "assuming amount is not set due to number format exception. " +
581                                     "input was '%s'", amount));
582                             account.invalidateAmount();
583                             item.invalidateAmount();
584                         }
585                         final String curr = String.valueOf(tvCurrency.getText());
586                         if (curr.equals(tvCurrency.getContext()
587                                                   .getResources()
588                                                   .getString(R.string.currency_symbol)) ||
589                             curr.isEmpty())
590                             account.setCurrency(null);
591                         else
592                             account.setCurrency(curr);
593                     }
594
595                     break;
596                 case bottomFiller:
597                     throw new RuntimeException("Should not happen");
598             }
599
600             return true;
601         }
602         catch (ParseException e) {
603             throw new RuntimeException("Should not happen", e);
604         }
605         finally {
606             syncingData = false;
607         }
608     }
609     private void pickTransactionDate() {
610         DatePickerFragment picker = new DatePickerFragment();
611         picker.setFutureDates(mProfile.getFutureDates());
612         picker.setOnDatePickedListener(this);
613         picker.setCurrentDateFromText(tvDate.getText());
614         picker.show(((NewTransactionActivity) tvDate.getContext()).getSupportFragmentManager(),
615                 null);
616     }
617     /**
618      * setData
619      *
620      * @param item updates the UI elements with the data from the model item
621      */
622     @SuppressLint("DefaultLocale")
623     public void setData(NewTransactionModel.Item item) {
624         beginUpdates();
625         try {
626             if (this.item != null && !this.item.equals(item)) {
627                 this.item.stopObservingDate(dateObserver);
628                 this.item.stopObservingDescription(descriptionObserver);
629                 this.item.stopObservingTransactionComment(transactionCommentObserver);
630                 this.item.stopObservingAmountHint(hintObserver);
631                 this.item.stopObservingEditableFlag(editableObserver);
632                 this.item.getModel()
633                          .stopObservingFocusedItem(focusedAccountObserver);
634                 this.item.getModel()
635                          .stopObservingAccountCount(accountCountObserver);
636                 Data.currencySymbolPosition.removeObserver(currencyPositionObserver);
637                 Data.currencyGap.removeObserver(currencyGapObserver);
638                 Data.locale.removeObserver(localeObserver);
639                 this.item.stopObservingCurrency(currencyObserver);
640                 this.item.getModel().showCurrency.removeObserver(showCurrencyObserver);
641                 this.item.stopObservingComment(commentObserver);
642                 this.item.getModel().showComments.removeObserver(showCommentsObserver);
643                 this.item.stopObservingAmountValidity(amountValidityObserver);
644
645                 this.item = null;
646             }
647
648             switch (item.getType()) {
649                 case generalData:
650                     tvDate.setText(item.getFormattedDate());
651                     tvDescription.setText(item.getDescription());
652                     tvTransactionComment.setText(item.getTransactionComment());
653                     lHead.setVisibility(View.VISIBLE);
654                     lAccount.setVisibility(View.GONE);
655                     lPadding.setVisibility(View.GONE);
656                     setEditable(true);
657                     break;
658                 case transactionRow:
659                     LedgerTransactionAccount acc = item.getAccount();
660                     tvAccount.setText(acc.getAccountName());
661                     tvComment.setText(acc.getComment());
662                     if (acc.isAmountSet()) {
663                         tvAmount.setText(String.format("%1.2f", acc.getAmount()));
664                     }
665                     else {
666                         tvAmount.setText("");
667 //                        tvAmount.setHint(R.string.zero_amount);
668                     }
669                     tvAmount.setHint(item.getAmountHint());
670                     setCurrencyString(acc.getCurrency());
671                     lHead.setVisibility(View.GONE);
672                     lAccount.setVisibility(View.VISIBLE);
673                     lPadding.setVisibility(View.GONE);
674                     setEditable(true);
675                     break;
676                 case bottomFiller:
677                     lHead.setVisibility(View.GONE);
678                     lAccount.setVisibility(View.GONE);
679                     lPadding.setVisibility(View.VISIBLE);
680                     setEditable(false);
681                     break;
682             }
683             if (this.item == null) { // was null or has changed
684                 this.item = item;
685                 final NewTransactionActivity activity =
686                         (NewTransactionActivity) tvDescription.getContext();
687
688                 if (!item.isOfType(ItemType.bottomFiller)) {
689                     item.observeEditableFlag(activity, editableObserver);
690                     item.getModel()
691                         .observeFocusedItem(activity, focusedAccountObserver);
692                     item.getModel()
693                         .observeShowComments(activity, showCommentsObserver);
694                 }
695                 switch (item.getType()) {
696                     case generalData:
697                         item.observeDate(activity, dateObserver);
698                         item.observeDescription(activity, descriptionObserver);
699                         item.observeTransactionComment(activity, transactionCommentObserver);
700                         break;
701                     case transactionRow:
702                         item.observeAmountHint(activity, hintObserver);
703                         Data.currencySymbolPosition.observe(activity, currencyPositionObserver);
704                         Data.currencyGap.observe(activity, currencyGapObserver);
705                         Data.locale.observe(activity, localeObserver);
706                         item.observeCurrency(activity, currencyObserver);
707                         item.getModel().showCurrency.observe(activity, showCurrencyObserver);
708                         item.observeComment(activity, commentObserver);
709                         item.getModel()
710                             .observeAccountCount(activity, accountCountObserver);
711                         item.observeAmountValidity(activity, amountValidityObserver);
712                         break;
713                 }
714             }
715         }
716         finally {
717             endUpdates();
718         }
719     }
720     @Override
721     public void onDatePicked(int year, int month, int day) {
722         item.setDate(new SimpleDate(year, month+1, day));
723         boolean focused = tvDescription.requestFocus();
724         if (focused)
725             Misc.showSoftKeyboard((NewTransactionActivity) tvAccount.getContext());
726
727     }
728     @Override
729     public void descriptionSelected(String description) {
730         tvAccount.setText(description);
731         tvAmount.requestFocus(View.FOCUS_FORWARD);
732     }
733 }