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.ListAdapter;
28 import android.widget.TextView;
30 import androidx.annotation.ColorInt;
31 import androidx.annotation.NonNull;
32 import androidx.recyclerview.widget.RecyclerView;
34 import net.ktnx.mobileledger.R;
35 import net.ktnx.mobileledger.databinding.NewTransactionHeaderRowBinding;
36 import net.ktnx.mobileledger.db.TransactionDescriptionAutocompleteAdapter;
37 import net.ktnx.mobileledger.model.Data;
38 import net.ktnx.mobileledger.model.FutureDates;
39 import net.ktnx.mobileledger.ui.DatePickerFragment;
40 import net.ktnx.mobileledger.utils.Logger;
41 import net.ktnx.mobileledger.utils.Misc;
42 import net.ktnx.mobileledger.utils.SimpleDate;
44 import java.text.DecimalFormatSymbols;
45 import java.text.ParseException;
47 class NewTransactionHeaderItemHolder extends NewTransactionItemViewHolder
48 implements DatePickerFragment.DatePickedListener {
49 private final NewTransactionHeaderRowBinding b;
50 private boolean ignoreFocusChanges = false;
51 private String decimalSeparator;
52 private boolean inUpdate = false;
53 private boolean syncingData = false;
54 //TODO multiple amounts with different currencies per posting?
55 NewTransactionHeaderItemHolder(@NonNull NewTransactionHeaderRowBinding b,
56 NewTransactionItemsAdapter adapter) {
60 b.newTransactionDescription.setNextFocusForwardId(View.NO_ID);
62 b.newTransactionDate.setOnClickListener(v -> pickTransactionDate());
64 b.transactionCommentButton.setOnClickListener(v -> {
65 b.transactionComment.setVisibility(View.VISIBLE);
66 b.transactionComment.requestFocus();
69 @SuppressLint("DefaultLocale") View.OnFocusChangeListener focusMonitor = (v, hasFocus) -> {
70 final int id = v.getId();
72 boolean wasSyncing = syncingData;
75 final int pos = getBindingAdapterPosition();
76 if (id == R.id.transaction_comment) {
77 adapter.noteFocusIsOnTransactionComment(pos);
79 else if (id == R.id.new_transaction_description) {
80 adapter.noteFocusIsOnDescription(pos);
83 throw new IllegalStateException("Where is the focus? " + id);
86 syncingData = wasSyncing;
90 if (id == R.id.transaction_comment) {
91 commentFocusChanged(b.transactionComment, hasFocus);
95 b.newTransactionDescription.setOnFocusChangeListener(focusMonitor);
96 b.transactionComment.setOnFocusChangeListener(focusMonitor);
98 NewTransactionActivity activity = (NewTransactionActivity) b.getRoot()
101 b.newTransactionDescription.setAdapter(
102 new TransactionDescriptionAutocompleteAdapter(activity));
103 b.newTransactionDescription.setOnItemClickListener(
104 (parent, view, position, id) -> activity.descriptionSelected(
105 parent.getItemAtPosition(position)
108 decimalSeparator = "";
109 Data.locale.observe(activity, locale -> decimalSeparator = String.valueOf(
110 DecimalFormatSymbols.getInstance(locale)
111 .getMonetaryDecimalSeparator()));
113 final TextWatcher tw = new TextWatcher() {
115 public void beforeTextChanged(CharSequence s, int start, int count, int after) {
119 public void onTextChanged(CharSequence s, int start, int before, int count) {
123 public void afterTextChanged(Editable s) {
124 // debug("input", "text changed");
128 Logger.debug("textWatcher", "calling syncData()");
130 Logger.debug("textWatcher",
131 "syncData() returned, checking if transaction is submittable");
132 adapter.model.checkTransactionSubmittable(null);
134 Logger.debug("textWatcher", "done");
137 b.newTransactionDescription.addTextChangedListener(tw);
138 monitorComment(b.transactionComment);
140 commentFocusChanged(b.transactionComment, false);
142 adapter.model.getFocusInfo()
143 .observe(activity, this::applyFocus);
145 adapter.model.getShowComments()
146 .observe(activity, show -> b.transactionCommentLayout.setVisibility(
147 show ? View.VISIBLE : View.GONE));
149 private void applyFocus(NewTransactionModel.FocusInfo focusInfo) {
150 if (ignoreFocusChanges) {
151 Logger.debug("new-trans", "Ignoring focus change");
154 ignoreFocusChanges = true;
156 if (((focusInfo == null) || (focusInfo.element == null) ||
157 focusInfo.position != getBindingAdapterPosition()))
160 final NewTransactionModel.Item item = getItem();
164 NewTransactionModel.Item head = item.toTransactionHead();
165 // bad idea - double pop-up, and not really necessary.
166 // the user can tap the input to get the calendar
167 //if (!tvDate.hasFocus()) tvDate.requestFocus();
168 switch (focusInfo.element) {
169 case TransactionComment:
170 b.transactionComment.setVisibility(View.VISIBLE);
171 b.transactionComment.requestFocus();
174 boolean focused = b.newTransactionDescription.requestFocus();
175 // tvDescription.dismissDropDown();
177 Misc.showSoftKeyboard((NewTransactionActivity) b.getRoot()
183 ignoreFocusChanges = false;
186 private void monitorComment(EditText editText) {
187 editText.addTextChangedListener(new TextWatcher() {
189 public void beforeTextChanged(CharSequence s, int start, int count, int after) {
192 public void onTextChanged(CharSequence s, int start, int before, int count) {
195 public void afterTextChanged(Editable s) {
196 // debug("input", "text changed");
200 Logger.debug("textWatcher", "calling syncData()");
202 Logger.debug("textWatcher",
203 "syncData() returned, checking if transaction is submittable");
204 styleComment(editText, s.toString());
205 Logger.debug("textWatcher", "done");
209 private void commentFocusChanged(TextView textView, boolean hasFocus) {
210 @ColorInt int textColor;
211 textColor = b.dummyText.getTextColors()
214 textView.setTypeface(null, Typeface.NORMAL);
215 textView.setHint(R.string.transaction_account_comment_hint);
218 int alpha = (textColor >> 24 & 0xff);
219 alpha = 3 * alpha / 4;
220 textColor = (alpha << 24) | (0x00ffffff & textColor);
221 textView.setTypeface(null, Typeface.ITALIC);
222 textView.setHint("");
223 if (TextUtils.isEmpty(textView.getText())) {
224 textView.setVisibility(View.INVISIBLE);
227 textView.setTextColor(textColor);
230 private void setEditable(Boolean editable) {
231 b.newTransactionDate.setEnabled(editable);
232 b.newTransactionDescription.setEnabled(editable);
234 private void beginUpdates() {
236 throw new RuntimeException("Already in update mode");
239 private void endUpdates() {
241 throw new RuntimeException("Not in update mode");
247 * Stores the data from the UI elements into the model item
248 * Returns true if there were changes made that suggest transaction has to be
249 * checked for being submittable
251 private boolean syncData() {
253 Logger.debug("new-trans", "skipping syncData() loop");
257 if (getBindingAdapterPosition() == RecyclerView.NO_POSITION) {
258 // probably the row was swiped out
259 Logger.debug("new-trans", "Ignoring request to suncData(): adapter position negative");
264 boolean significantChange = false;
268 final NewTransactionModel.Item item = getItem();
272 NewTransactionModel.TransactionHead head = item.toTransactionHead();
274 head.setDate(String.valueOf(b.newTransactionDate.getText()));
276 // transaction description is required
277 if (TextUtils.isEmpty(head.getDescription()) !=
278 TextUtils.isEmpty(b.newTransactionDescription.getText()))
279 significantChange = true;
281 head.setDescription(String.valueOf(b.newTransactionDescription.getText()));
282 head.setComment(String.valueOf(b.transactionComment.getText()));
284 return significantChange;
286 catch (ParseException e) {
287 throw new RuntimeException("Should not happen", e);
293 private void pickTransactionDate() {
294 DatePickerFragment picker = new DatePickerFragment();
295 picker.setFutureDates(FutureDates.valueOf(mProfile.getFutureDates()));
296 picker.setOnDatePickedListener(this);
297 picker.setCurrentDateFromText(b.newTransactionDate.getText());
298 picker.show(((NewTransactionActivity) b.getRoot()
299 .getContext()).getSupportFragmentManager(), null);
304 * @param item updates the UI elements with the data from the model item
306 @SuppressLint("DefaultLocale")
307 public void bind(@NonNull NewTransactionModel.Item item) {
312 NewTransactionModel.TransactionHead head = item.toTransactionHead();
313 b.newTransactionDate.setText(head.getFormattedDate());
315 // avoid triggering completion pop-up
316 ListAdapter a = b.newTransactionDescription.getAdapter();
318 b.newTransactionDescription.setAdapter(null);
319 b.newTransactionDescription.setText(head.getDescription());
322 b.newTransactionDescription.setAdapter(
323 (TransactionDescriptionAutocompleteAdapter) a);
326 b.transactionComment.setText(head.getComment());
327 //styleComment(b.transactionComment, head.getComment());
331 NewTransactionItemsAdapter adapter =
332 (NewTransactionItemsAdapter) getBindingAdapter();
334 applyFocus(adapter.model.getFocusInfo()
345 private void styleComment(EditText editText, String comment) {
346 final View focusedView = editText.findFocus();
347 editText.setTypeface(null, (focusedView == editText) ? Typeface.NORMAL : Typeface.ITALIC);
348 editText.setVisibility(
349 ((focusedView != editText) && TextUtils.isEmpty(comment)) ? View.INVISIBLE
353 public void onDatePicked(int year, int month, int day) {
354 final NewTransactionModel.Item item = getItem();
358 final NewTransactionModel.TransactionHead head = item.toTransactionHead();
359 head.setDate(new SimpleDate(year, month + 1, day));
360 b.newTransactionDate.setText(head.getFormattedDate());
362 boolean focused = b.newTransactionDescription.requestFocus();
364 Misc.showSoftKeyboard((NewTransactionActivity) b.getRoot()