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.new_transaction;
20 import android.annotation.SuppressLint;
21 import android.graphics.Typeface;
22 import android.text.Editable;
23 import android.text.TextUtils;
24 import android.text.TextWatcher;
25 import android.view.View;
26 import android.widget.EditText;
27 import android.widget.SimpleCursorAdapter;
28 import android.widget.TextView;
30 import androidx.annotation.ColorInt;
31 import androidx.annotation.NonNull;
33 import net.ktnx.mobileledger.R;
34 import net.ktnx.mobileledger.databinding.NewTransactionHeaderRowBinding;
35 import net.ktnx.mobileledger.model.Data;
36 import net.ktnx.mobileledger.ui.DatePickerFragment;
37 import net.ktnx.mobileledger.utils.Logger;
38 import net.ktnx.mobileledger.utils.MLDB;
39 import net.ktnx.mobileledger.utils.Misc;
40 import net.ktnx.mobileledger.utils.SimpleDate;
42 import java.text.DecimalFormatSymbols;
43 import java.text.ParseException;
45 class NewTransactionHeaderItemHolder extends NewTransactionItemViewHolder
46 implements DatePickerFragment.DatePickedListener {
47 private final NewTransactionHeaderRowBinding b;
48 private boolean ignoreFocusChanges = false;
49 private String decimalSeparator;
50 private boolean inUpdate = false;
51 private boolean syncingData = false;
52 //TODO multiple amounts with different currencies per posting?
53 NewTransactionHeaderItemHolder(@NonNull NewTransactionHeaderRowBinding b,
54 NewTransactionItemsAdapter adapter) {
55 super(b.getRoot(), adapter);
58 b.newTransactionDescription.setNextFocusForwardId(View.NO_ID);
60 b.newTransactionDate.setOnClickListener(v -> pickTransactionDate());
62 b.transactionCommentButton.setOnClickListener(v -> {
63 b.transactionComment.setVisibility(View.VISIBLE);
64 b.transactionComment.requestFocus();
67 @SuppressLint("DefaultLocale") View.OnFocusChangeListener focusMonitor = (v, hasFocus) -> {
68 final int id = v.getId();
70 boolean wasSyncing = syncingData;
73 final int pos = getAdapterPosition();
74 if (id == R.id.transaction_comment) {
75 adapter.noteFocusIsOnTransactionComment(pos);
77 else if (id == R.id.new_transaction_description) {
78 adapter.noteFocusIsOnDescription(pos);
81 throw new IllegalStateException("Where is the focus? " + id);
84 syncingData = wasSyncing;
88 if (id == R.id.transaction_comment) {
89 commentFocusChanged(b.transactionComment, hasFocus);
93 b.newTransactionDescription.setOnFocusChangeListener(focusMonitor);
94 b.transactionComment.setOnFocusChangeListener(focusMonitor);
96 NewTransactionActivity activity = (NewTransactionActivity) b.getRoot()
99 MLDB.hookAutocompletionAdapter(activity, b.newTransactionDescription,
100 MLDB.DESCRIPTION_HISTORY_TABLE, "description", false, activity, mProfile);
102 decimalSeparator = "";
103 Data.locale.observe(activity, locale -> decimalSeparator = String.valueOf(
104 DecimalFormatSymbols.getInstance(locale)
105 .getMonetaryDecimalSeparator()));
107 final TextWatcher tw = new TextWatcher() {
109 public void beforeTextChanged(CharSequence s, int start, int count, int after) {
113 public void onTextChanged(CharSequence s, int start, int before, int count) {
117 public void afterTextChanged(Editable s) {
118 // debug("input", "text changed");
122 Logger.debug("textWatcher", "calling syncData()");
124 Logger.debug("textWatcher",
125 "syncData() returned, checking if transaction is submittable");
126 adapter.model.checkTransactionSubmittable(null);
128 Logger.debug("textWatcher", "done");
131 b.newTransactionDescription.addTextChangedListener(tw);
132 monitorComment(b.transactionComment);
134 commentFocusChanged(b.transactionComment, false);
136 adapter.model.getFocusInfo()
137 .observe(activity, this::applyFocus);
139 adapter.model.getShowComments()
140 .observe(activity, show -> b.transactionCommentLayout.setVisibility(
141 show ? View.VISIBLE : View.GONE));
143 private void applyFocus(NewTransactionModel.FocusInfo focusInfo) {
144 if (ignoreFocusChanges) {
145 Logger.debug("new-trans", "Ignoring focus change");
148 ignoreFocusChanges = true;
150 if (((focusInfo == null) || (focusInfo.element == null) ||
151 focusInfo.position != getAdapterPosition()))
154 NewTransactionModel.Item head = getItem().toTransactionHead();
155 // bad idea - double pop-up, and not really necessary.
156 // the user can tap the input to get the calendar
157 //if (!tvDate.hasFocus()) tvDate.requestFocus();
158 switch (focusInfo.element) {
159 case TransactionComment:
160 b.transactionComment.setVisibility(View.VISIBLE);
161 b.transactionComment.requestFocus();
164 boolean focused = b.newTransactionDescription.requestFocus();
165 // tvDescription.dismissDropDown();
167 Misc.showSoftKeyboard((NewTransactionActivity) b.getRoot()
173 ignoreFocusChanges = false;
176 private void monitorComment(EditText editText) {
177 editText.addTextChangedListener(new TextWatcher() {
179 public void beforeTextChanged(CharSequence s, int start, int count, int after) {
182 public void onTextChanged(CharSequence s, int start, int before, int count) {
185 public void afterTextChanged(Editable s) {
186 // debug("input", "text changed");
190 Logger.debug("textWatcher", "calling syncData()");
192 Logger.debug("textWatcher",
193 "syncData() returned, checking if transaction is submittable");
194 styleComment(editText, s.toString());
195 Logger.debug("textWatcher", "done");
199 private void commentFocusChanged(TextView textView, boolean hasFocus) {
200 @ColorInt int textColor;
201 textColor = b.dummyText.getTextColors()
204 textView.setTypeface(null, Typeface.NORMAL);
205 textView.setHint(R.string.transaction_account_comment_hint);
208 int alpha = (textColor >> 24 & 0xff);
209 alpha = 3 * alpha / 4;
210 textColor = (alpha << 24) | (0x00ffffff & textColor);
211 textView.setTypeface(null, Typeface.ITALIC);
212 textView.setHint("");
213 if (TextUtils.isEmpty(textView.getText())) {
214 textView.setVisibility(View.INVISIBLE);
217 textView.setTextColor(textColor);
220 private void setEditable(Boolean editable) {
221 b.newTransactionDate.setEnabled(editable);
222 b.newTransactionDescription.setEnabled(editable);
224 private void beginUpdates() {
226 throw new RuntimeException("Already in update mode");
229 private void endUpdates() {
231 throw new RuntimeException("Not in update mode");
237 * Stores the data from the UI elements into the model item
238 * Returns true if there were changes made that suggest transaction has to be
239 * checked for being submittable
241 private boolean syncData() {
243 Logger.debug("new-trans", "skipping syncData() loop");
247 if (getAdapterPosition() < 0) {
248 // probably the row was swiped out
249 Logger.debug("new-trans", "Ignoring request to suncData(): adapter position negative");
254 boolean significantChange = false;
258 NewTransactionModel.TransactionHead head = getItem().toTransactionHead();
260 head.setDate(String.valueOf(b.newTransactionDate.getText()));
262 // transaction description is required
263 if (TextUtils.isEmpty(head.getDescription()) !=
264 TextUtils.isEmpty(b.newTransactionDescription.getText()))
265 significantChange = true;
267 head.setDescription(String.valueOf(b.newTransactionDescription.getText()));
268 head.setComment(String.valueOf(b.transactionComment.getText()));
270 return significantChange;
272 catch (ParseException e) {
273 throw new RuntimeException("Should not happen", e);
279 private void pickTransactionDate() {
280 DatePickerFragment picker = new DatePickerFragment();
281 picker.setFutureDates(mProfile.getFutureDates());
282 picker.setOnDatePickedListener(this);
283 picker.setCurrentDateFromText(b.newTransactionDate.getText());
284 picker.show(((NewTransactionActivity) b.getRoot()
285 .getContext()).getSupportFragmentManager(), null);
290 * @param item updates the UI elements with the data from the model item
292 @SuppressLint("DefaultLocale")
293 public void bind(@NonNull NewTransactionModel.Item item) {
298 NewTransactionModel.TransactionHead head = item.toTransactionHead();
299 b.newTransactionDate.setText(head.getFormattedDate());
301 // avoid triggering completion pop-up
302 SimpleCursorAdapter a =
303 (SimpleCursorAdapter) b.newTransactionDescription.getAdapter();
305 b.newTransactionDescription.setAdapter(null);
306 b.newTransactionDescription.setText(head.getDescription());
309 b.newTransactionDescription.setAdapter(a);
312 b.transactionComment.setText(head.getComment());
313 //styleComment(b.transactionComment, head.getComment());
317 applyFocus(mAdapter.model.getFocusInfo()
328 private void styleComment(EditText editText, String comment) {
329 final View focusedView = editText.findFocus();
330 editText.setTypeface(null, (focusedView == editText) ? Typeface.NORMAL : Typeface.ITALIC);
331 editText.setVisibility(
332 ((focusedView != editText) && TextUtils.isEmpty(comment)) ? View.INVISIBLE
336 public void onDatePicked(int year, int month, int day) {
337 final NewTransactionModel.TransactionHead head = getItem().toTransactionHead();
338 head.setDate(new SimpleDate(year, month + 1, day));
339 b.newTransactionDate.setText(head.getFormattedDate());
341 boolean focused = b.newTransactionDescription.requestFocus();
343 Misc.showSoftKeyboard((NewTransactionActivity) b.getRoot()