package co.smartreceipts.android.receipts.editor; import android.app.Activity; import android.content.Context; import android.os.Bundle; import android.support.annotation.NonNull; import android.support.annotation.Nullable; import android.support.v7.app.ActionBar; import android.support.v7.widget.Toolbar; import android.text.TextUtils; import android.text.format.DateFormat; import android.text.format.Time; import android.text.method.TextKeyListener; import android.view.LayoutInflater; import android.view.Menu; import android.view.MenuInflater; import android.view.MenuItem; import android.view.MotionEvent; import android.view.View; import android.view.ViewGroup; import android.widget.AdapterView; import android.widget.ArrayAdapter; import android.widget.AutoCompleteTextView; import android.widget.CheckBox; import android.widget.EditText; import android.widget.LinearLayout; import android.widget.Spinner; import android.widget.Toast; import java.io.File; import java.math.BigDecimal; import java.sql.Date; import java.text.ParseException; import java.text.SimpleDateFormat; import java.util.Collections; import java.util.GregorianCalendar; import java.util.List; import java.util.Locale; import java.util.TimeZone; import javax.inject.Inject; import co.smartreceipts.android.R; import co.smartreceipts.android.activities.NavigationHandler; import co.smartreceipts.android.activities.SmartReceiptsActivity; import co.smartreceipts.android.adapters.TaxAutoCompleteAdapter; import co.smartreceipts.android.analytics.Analytics; import co.smartreceipts.android.analytics.events.Events; import co.smartreceipts.android.apis.ExchangeRateServiceManager; import co.smartreceipts.android.apis.MemoryLeakSafeCallback; import co.smartreceipts.android.date.DateEditText; import co.smartreceipts.android.date.DateManager; import co.smartreceipts.android.fragments.ChildFragmentNavigationHandler; import co.smartreceipts.android.fragments.ReceiptInputCache; import co.smartreceipts.android.fragments.WBFragment; import co.smartreceipts.android.model.Category; import co.smartreceipts.android.model.PaymentMethod; import co.smartreceipts.android.model.Receipt; import co.smartreceipts.android.model.Trip; import co.smartreceipts.android.model.gson.ExchangeRate; import co.smartreceipts.android.model.utils.ModelUtils; import co.smartreceipts.android.ocr.apis.model.OcrResponse; import co.smartreceipts.android.ocr.util.OcrResponseParser; import co.smartreceipts.android.ocr.widget.tooltip.OcrInformationalTooltipFragment; import co.smartreceipts.android.persistence.DatabaseHelper; import co.smartreceipts.android.persistence.database.controllers.TableEventsListener; import co.smartreceipts.android.persistence.database.controllers.impl.CategoriesTableController; import co.smartreceipts.android.persistence.database.controllers.impl.PaymentMethodsTableController; import co.smartreceipts.android.persistence.database.controllers.impl.StubTableEventsListener; import co.smartreceipts.android.utils.SoftKeyboardManager; import co.smartreceipts.android.utils.cache.FragmentStateCache; import co.smartreceipts.android.utils.log.Logger; import co.smartreceipts.android.widget.NetworkRequestAwareEditText; import co.smartreceipts.android.widget.UserSelectionTrackingOnItemSelectedListener; import dagger.android.support.AndroidSupportInjection; import io.reactivex.android.schedulers.AndroidSchedulers; import io.reactivex.disposables.Disposable; import io.reactivex.schedulers.Schedulers; import retrofit2.Call; import retrofit2.Response; import wb.android.autocomplete.AutoCompleteAdapter; import wb.android.flex.Flex; public class ReceiptCreateEditFragment extends WBFragment implements View.OnFocusChangeListener, NetworkRequestAwareEditText.RetryListener, DatabaseHelper.ReceiptAutoCompleteListener { public static final String ARG_FILE = "arg_file"; public static final String ARG_OCR = "arg_ocr"; private static final String KEY_OUT_STATE_IS_EXCHANGE_RATE_VISIBLE = "key_is_exchange_rate_visible"; @Inject Flex flex; @Inject DateManager dateManager; @Inject DatabaseHelper database; @Inject Analytics analytics; @Inject CategoriesTableController categoriesTableController; @Inject PaymentMethodsTableController paymentMethodsTableController; @Inject NavigationHandler navigationHandler; @Inject FragmentStateCache fragmentStateCache; @Inject ReceiptCreateEditFragmentPresenter presenter; // Metadata private OcrResponse ocrResponse; // Views private AutoCompleteTextView nameBox; private EditText priceBox; private AutoCompleteTextView taxBox; private Spinner currencySpinner; private NetworkRequestAwareEditText exchangeRateBox; private DateEditText dateBox; private AutoCompleteTextView commentBox; private Spinner categoriesSpinner; private CheckBox reimbursable; private CheckBox fullpage; private Spinner paymentMethodsSpinner; private EditText extra_edittext_box_1; private EditText extra_edittext_box_2; private EditText extra_edittext_box_3; private ViewGroup paymentMethodsContainer; private ViewGroup exchangeRateContainer; private Toolbar toolbar; private View focusedView; // Rx private Disposable idDisposable; private TableEventsListener<Category> categoryTableEventsListener; private TableEventsListener<PaymentMethod> paymentMethodTableEventsListener; // Misc private MemoryLeakSafeCallback<ExchangeRate, EditText> lastExchangeRateFetchCallback; private ExchangeRateServiceManager exchangeRateServiceManager; private ReceiptInputCache receiptInputCache; private AutoCompleteAdapter receiptsNameAutoCompleteAdapter, receiptsCommentAutoCompleteAdapter; private ArrayAdapter<CharSequence> currenciesAdapter; private List<Category> categoriesList; private ArrayAdapter<Category> categoriesAdpater; private ArrayAdapter<PaymentMethod> paymentMethodsAdapter; public static ReceiptCreateEditFragment newInstance() { return new ReceiptCreateEditFragment(); } @Override public void onAttach(Context context) { AndroidSupportInjection.inject(this); super.onAttach(context); } @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); Logger.debug(this, "onCreate"); ocrResponse = (OcrResponse) getArguments().getSerializable(ARG_OCR); receiptInputCache = new ReceiptInputCache(getFragmentManager()); exchangeRateServiceManager = new ExchangeRateServiceManager(getFragmentManager()); currenciesAdapter = new ArrayAdapter<>(getActivity(), android.R.layout.simple_spinner_item, database.getCurrenciesList()); categoriesList = Collections.emptyList(); categoriesAdpater = new ArrayAdapter<>(getActivity(), android.R.layout.simple_spinner_item, Collections.<Category>emptyList()); paymentMethodsAdapter = new ArrayAdapter<>(getActivity(), android.R.layout.simple_spinner_item, Collections.<PaymentMethod>emptyList()); setHasOptionsMenu(true); } Trip getParentTrip() { return getArguments().getParcelable(Trip.PARCEL_KEY); } Receipt getReceipt() { return getArguments().getParcelable(Receipt.PARCEL_KEY); } File getFile() { return (File) getArguments().getSerializable(ARG_FILE); } @Nullable @Override public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { return inflater.inflate(R.layout.update_receipt, container, false); } @Override public void onViewCreated(View rootView, @Nullable Bundle savedInstanceState) { super.onViewCreated(rootView, savedInstanceState); if (savedInstanceState == null) { if (getReceipt() == null) { new ChildFragmentNavigationHandler(this).addChild(new OcrInformationalTooltipFragment(), R.id.update_receipt_tooltip); } } this.nameBox = (AutoCompleteTextView) flex.getSubView(getActivity(), rootView, R.id.DIALOG_RECEIPTMENU_NAME); this.priceBox = (EditText) flex.getSubView(getActivity(), rootView, R.id.DIALOG_RECEIPTMENU_PRICE); this.taxBox = (AutoCompleteTextView) flex.getSubView(getActivity(), rootView, R.id.DIALOG_RECEIPTMENU_TAX); this.currencySpinner = (Spinner) flex.getSubView(getActivity(), rootView, R.id.DIALOG_RECEIPTMENU_CURRENCY); this.exchangeRateBox = (NetworkRequestAwareEditText) flex.getSubView(getActivity(), rootView, R.id.DIALOG_RECEIPTMENU_EXCHANGE_RATE); exchangeRateContainer = (ViewGroup) flex.getSubView(getActivity(), rootView, R.id.exchange_rate_container); this.dateBox = (DateEditText) flex.getSubView(getActivity(), rootView, R.id.DIALOG_RECEIPTMENU_DATE); this.commentBox = (AutoCompleteTextView) flex.getSubView(getActivity(), rootView, R.id.DIALOG_RECEIPTMENU_COMMENT); this.categoriesSpinner = (Spinner) flex.getSubView(getActivity(), rootView, R.id.DIALOG_RECEIPTMENU_CATEGORY); this.reimbursable = (CheckBox) flex.getSubView(getActivity(), rootView, R.id.DIALOG_RECEIPTMENU_EXPENSABLE); this.fullpage = (CheckBox) flex.getSubView(getActivity(), rootView, R.id.DIALOG_RECEIPTMENU_FULLPAGE); this.paymentMethodsSpinner = (Spinner) flex.getSubView(getActivity(), rootView, R.id.dialog_receiptmenu_payment_methods_spinner); paymentMethodsContainer = (ViewGroup) flex.getSubView(getActivity(), rootView, R.id.payment_methods_container); // Extras final LinearLayout extras = (LinearLayout) flex.getSubView(getActivity(), rootView, R.id.DIALOG_RECEIPTMENU_EXTRAS); this.extra_edittext_box_1 = (EditText) extras.findViewWithTag(getFlexString(R.string.RECEIPTMENU_TAG_EXTRA_EDITTEXT_1)); this.extra_edittext_box_2 = (EditText) extras.findViewWithTag(getFlexString(R.string.RECEIPTMENU_TAG_EXTRA_EDITTEXT_2)); this.extra_edittext_box_3 = (EditText) extras.findViewWithTag(getFlexString(R.string.RECEIPTMENU_TAG_EXTRA_EDITTEXT_3)); // Toolbar stuff toolbar = (Toolbar) rootView.findViewById(R.id.toolbar); if (navigationHandler.isDualPane()) { toolbar.setVisibility(View.GONE); } else { setSupportActionBar(toolbar); } // Set each focus listener, so we can track the focus view across resume -> pauses this.nameBox.setOnFocusChangeListener(this); this.priceBox.setOnFocusChangeListener(this); this.taxBox.setOnFocusChangeListener(this); this.currencySpinner.setOnFocusChangeListener(this); this.dateBox.setOnFocusChangeListener(this); this.commentBox.setOnFocusChangeListener(this); this.paymentMethodsSpinner.setOnFocusChangeListener(this); // Custom view properties exchangeRateBox.setFailedHint(R.string.DIALOG_RECEIPTMENU_HINT_EXCHANGE_RATE_FAILED); // Set click listeners View.OnTouchListener hideSoftKeyboardOnTouchListener = new View.OnTouchListener() { @Override public boolean onTouch(View view, MotionEvent motionEvent) { if (motionEvent.getAction() == MotionEvent.ACTION_UP) { SoftKeyboardManager.hideKeyboard(view); } return false; } }; dateBox.setOnTouchListener(hideSoftKeyboardOnTouchListener); categoriesSpinner.setOnTouchListener(hideSoftKeyboardOnTouchListener); currencySpinner.setOnTouchListener(hideSoftKeyboardOnTouchListener); paymentMethodsSpinner.setOnTouchListener(hideSoftKeyboardOnTouchListener); // Show default dictionary with auto-complete nameBox.setKeyListener(TextKeyListener.getInstance(true, TextKeyListener.Capitalize.SENTENCES)); // Set-up tax layers if (presenter.isIncludeTaxField()) { priceBox.setHint(getFlexString(R.string.DIALOG_RECEIPTMENU_HINT_PRICE_SHORT)); taxBox.setVisibility(View.VISIBLE); } // Configure dropdown defaults for currencies currenciesAdapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item); currencySpinner.setAdapter(currenciesAdapter); // And the exchange rate processing for our currencies final boolean exchangeRateIsVisible = savedInstanceState != null && savedInstanceState.getBoolean(KEY_OUT_STATE_IS_EXCHANGE_RATE_VISIBLE); if (exchangeRateIsVisible) { // Note: the restoration of selected spinner items (in the currency spinner) is delayed so we use this state tracker to restore immediately exchangeRateContainer.setVisibility(View.VISIBLE); } currencySpinner.setOnItemSelectedListener(new UserSelectionTrackingOnItemSelectedListener() { @Override public void onUserSelectedNewItem(AdapterView<?> parent, View view, int position, long id, int previousPosition) { // Then determine if we should show/hide the box final String baseCurrencyCode = currenciesAdapter.getItem(position).toString(); configureExchangeRateField(baseCurrencyCode); } @Override public void onNothingSelected(AdapterView<?> parent) { // Intentional no-op } }); // Outline date defaults dateBox.setFocusableInTouchMode(false); dateBox.setOnClickListener(dateManager.getDateEditTextListener()); // Lastly, preset adapters for "new" receipts if (getReceipt() == null) { if (presenter.isIncludeTaxField()) { taxBox.setAdapter(new TaxAutoCompleteAdapter(getActivity(), priceBox, taxBox, presenter.isUsePreTaxPrice(), presenter.getDefaultTaxPercentage())); } } } @Override public void onActivityCreated(@Nullable Bundle savedInstanceState) { super.onActivityCreated(savedInstanceState); // Configure things if it's not a restored fragment if (savedInstanceState == null) { if (getReceipt() == null) { // new receipt final Time now = new Time(); now.setToNow(); if (receiptInputCache.getCachedDate() == null) { if (presenter.isReceiptDateDefaultsToReportStartDate()) { dateBox.date = getParentTrip().getStartDate(); } else { dateBox.date = new Date(now.toMillis(false)); } } else { dateBox.date = receiptInputCache.getCachedDate(); } dateBox.setText(DateFormat.getDateFormat(getActivity()).format(dateBox.date)); reimbursable.setChecked(presenter.isReceiptsDefaultAsReimbursable()); if (presenter.isMatchReceiptCommentToCategory() && presenter.isMatchReceiptNameToCategory()) { if (focusedView == null) { focusedView = priceBox; } } else if (presenter.isMatchReceiptNameToCategory()) { if (focusedView == null) { focusedView = priceBox; } } final Trip parentTrip = getParentTrip(); int idx = currenciesAdapter.getPosition((parentTrip != null) ? parentTrip.getDefaultCurrencyCode() : presenter.getDefaultCurrency()); int cachedIdx = (receiptInputCache.getCachedCurrency() != null) ? currenciesAdapter.getPosition(receiptInputCache.getCachedCurrency()) : -1; idx = (cachedIdx >= 0) ? cachedIdx : idx; if (idx >= 0) { currencySpinner.setSelection(idx); } if (!parentTrip.getDefaultCurrencyCode().equals(receiptInputCache.getCachedCurrency())) { configureExchangeRateField(receiptInputCache.getCachedCurrency()); } fullpage.setChecked(presenter.isDefaultToFullPage()); if (ocrResponse != null) { final OcrResponseParser ocrResponseParser = new OcrResponseParser(ocrResponse); if (ocrResponseParser.getMerchant() != null) { nameBox.setText(ocrResponseParser.getMerchant()); } if (ocrResponseParser.getTotalAmount() != null) { priceBox.setText(ocrResponseParser.getTotalAmount()); } if (ocrResponseParser.getTaxAmount() != null) { taxBox.setText(ocrResponseParser.getTaxAmount()); } if (ocrResponseParser.getDate() != null) { dateBox.date = ocrResponseParser.getDate(); dateBox.setText(DateFormat.getDateFormat(getActivity()).format(dateBox.date)); } } } else { // edit receipt final Receipt receipt = getReceipt(); final Trip parentTrip = getParentTrip(); nameBox.setText(receipt.getName()); priceBox.setText(receipt.getPrice().getDecimalFormattedPrice()); dateBox.setText(receipt.getFormattedDate(getActivity(), presenter.getDateSeparator())); dateBox.date = receipt.getDate(); commentBox.setText(receipt.getComment()); taxBox.setText(receipt.getTax().getDecimalFormattedPrice()); final ExchangeRate exchangeRate = receipt.getPrice().getExchangeRate(); if (exchangeRate.supportsExchangeRateFor(parentTrip.getDefaultCurrencyCode())) { exchangeRateBox.setText(exchangeRate.getDecimalFormattedExchangeRate(parentTrip.getDefaultCurrencyCode())); } int idx = currenciesAdapter.getPosition(receipt.getPrice().getCurrencyCode()); if (idx > 0) { currencySpinner.setSelection(idx); } if (receipt.getPrice().getCurrency().equals(parentTrip.getPrice().getCurrency())) { exchangeRateContainer.setVisibility(View.GONE); } else { exchangeRateContainer.setVisibility(View.VISIBLE); } reimbursable.setChecked(receipt.isReimbursable()); fullpage.setChecked(receipt.isFullPage()); if (extra_edittext_box_1 != null && receipt.hasExtraEditText1()) { extra_edittext_box_1.setText(receipt.getExtraEditText1()); } if (extra_edittext_box_2 != null && receipt.hasExtraEditText2()) { extra_edittext_box_2.setText(receipt.getExtraEditText2()); } if (extra_edittext_box_3 != null && receipt.hasExtraEditText3()) { extra_edittext_box_3.setText(receipt.getExtraEditText3()); } } // Focused View if (focusedView == null) { focusedView = nameBox; } } // Configure items that require callbacks categoryTableEventsListener = new StubTableEventsListener<Category>() { @Override public void onGetSuccess(@NonNull List<Category> list) { if (isAdded()) { categoriesList = list; categoriesAdpater = new ArrayAdapter<>(getActivity(), android.R.layout.simple_spinner_item, list); categoriesAdpater.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item); categoriesSpinner.setAdapter(categoriesAdpater); if (getReceipt() == null) { // new receipt if (presenter.isMatchReceiptCommentToCategory() || presenter.isMatchReceiptNameToCategory()) { categoriesSpinner.setOnItemSelectedListener(new SpinnerSelectionListener()); } if (presenter.isPredictCategories()) { // Predict Breakfast, Lunch, Dinner by the hour if (receiptInputCache.getCachedCategory() == null) { final Time now = new Time(); now.setToNow(); String nameToIndex = null; if (now.hour >= 4 && now.hour < 11) { // Breakfast hours nameToIndex = getString(R.string.category_breakfast); } else if (now.hour >= 11 && now.hour < 16) { // Lunch hours nameToIndex = getString(R.string.category_lunch); } else if (now.hour >= 16 && now.hour < 23) { // Dinner hours nameToIndex = getString(R.string.category_dinner); } if (nameToIndex != null) { for (int i = 0; i < categoriesAdpater.getCount(); i++) { if (nameToIndex.equals(categoriesAdpater.getItem(i).getName())) { categoriesSpinner.setSelection(i); break; // Exit loop now } } } } else { int idx = categoriesAdpater.getPosition(receiptInputCache.getCachedCategory()); if (idx > 0) { categoriesSpinner.setSelection(idx); } } } } else { categoriesSpinner.setSelection(categoriesAdpater.getPosition(getReceipt().getCategory())); } } } }; paymentMethodTableEventsListener = new StubTableEventsListener<PaymentMethod>() { @Override public void onGetSuccess(@NonNull List<PaymentMethod> list) { if (isAdded()) { paymentMethodsAdapter = new ArrayAdapter<>(getActivity(), android.R.layout.simple_spinner_item, list); paymentMethodsAdapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item); paymentMethodsSpinner.setAdapter(paymentMethodsAdapter); if (presenter.isUsePaymentMethods()) { paymentMethodsContainer.setVisibility(View.VISIBLE); if (getReceipt() != null) { final PaymentMethod oldPaymentMethod = getReceipt().getPaymentMethod(); if (oldPaymentMethod != null) { final int paymentIdx = paymentMethodsAdapter.getPosition(oldPaymentMethod); if (paymentIdx > 0) { paymentMethodsSpinner.setSelection(paymentIdx); } } } } } } }; categoriesTableController.subscribe(categoryTableEventsListener); paymentMethodsTableController.subscribe(paymentMethodTableEventsListener); categoriesTableController.get(); paymentMethodsTableController.get(); } @Override public void onResume() { super.onResume(); Logger.debug(this, "onResume"); final boolean isNewReceipt = getReceipt() == null; final String title; if (isNewReceipt) { title = getFlexString(R.string.DIALOG_RECEIPTMENU_TITLE_NEW); } else { if (presenter.isShowReceiptId()) { title = String.format(getFlexString(R.string.DIALOG_RECEIPTMENU_TITLE_EDIT_ID), getReceipt().getId()); } else { title = getFlexString(R.string.DIALOG_RECEIPTMENU_TITLE_EDIT); } } final ActionBar actionBar = getSupportActionBar(); if (actionBar != null) { actionBar.setHomeButtonEnabled(true); actionBar.setDisplayHomeAsUpEnabled(true); actionBar.setHomeAsUpIndicator(R.drawable.ic_action_cancel); actionBar.setTitle(title); actionBar.setSubtitle(""); } if (isNewReceipt && presenter.isShowReceiptId()) { idDisposable = database.getNextReceiptAutoIncremenetIdHelper() .subscribeOn(Schedulers.io()) .observeOn(AndroidSchedulers.mainThread()) .subscribe(receiptId -> { if (isResumed()) { final ActionBar bar = getSupportActionBar(); if (bar != null) { final String titleWithId = String.format(getFlexString(R.string.DIALOG_RECEIPTMENU_TITLE_NEW_ID), receiptId); bar.setTitle(titleWithId); } } }); } if (isNewReceipt) { if (presenter.isEnableAutoCompleteSuggestions()) { if (receiptsNameAutoCompleteAdapter == null) { receiptsNameAutoCompleteAdapter = AutoCompleteAdapter.getInstance(getActivity(), DatabaseHelper.TAG_RECEIPTS_NAME, database, database); } else { receiptsNameAutoCompleteAdapter.reset(); } if (receiptsCommentAutoCompleteAdapter == null) { receiptsCommentAutoCompleteAdapter = AutoCompleteAdapter.getInstance(getActivity(), DatabaseHelper.TAG_RECEIPTS_COMMENT, database); } else { receiptsCommentAutoCompleteAdapter.reset(); } nameBox.setAdapter(receiptsNameAutoCompleteAdapter); commentBox.setAdapter(receiptsCommentAutoCompleteAdapter); } } if (focusedView != null) { focusedView.requestFocus(); // Make sure we're focused on the right view } exchangeRateBox.setRetryListener(this); database.registerReceiptAutoCompleteListener(this); } @Override public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) { inflater.inflate(R.menu.menu_save, menu); } @Override public boolean onOptionsItemSelected(MenuItem item) { if (item.getItemId() == android.R.id.home) { navigationHandler.navigateToReportInfoFragment(getParentTrip()); presenter.deleteReceiptFileIfUnused(); return true; } if (item.getItemId() == R.id.action_save) { saveReceipt(); return true; } return super.onOptionsItemSelected(item); } @Override public void onFocusChange(View v, boolean hasFocus) { focusedView = hasFocus ? v : null; if (getReceipt() == null && hasFocus) { // Only launch if we have focus and it's a new receipt SoftKeyboardManager.showKeyboard(v); } } @Override public void onUserRetry() { if (presenter.hasActivePlusPurchase()) { Logger.info(this, "Attempting to retry with valid subscription. Submitting request directly"); submitExchangeRateRequest((String) currencySpinner.getSelectedItem()); } else { Logger.info(this, "Attempting to retry without valid subscription. Directing user to purchase intent"); final Activity activity = getActivity(); if (activity instanceof SmartReceiptsActivity) { presenter.initiatePurchase(); } } } @Override public void onReceiptRowAutoCompleteQueryResult(String name, String price, String category) { if (isAdded()) { if (nameBox != null && name != null) { nameBox.setText(name); nameBox.setSelection(name.length()); } if (priceBox != null && price != null && priceBox.getText().length() == 0) { priceBox.setText(price); } if (categoriesSpinner != null && category != null) { for (int i = 0; i < categoriesList.size(); i++) { if (category.equals(categoriesList.get(i).getName())) { categoriesSpinner.setSelection(categoriesList.indexOf(categoriesList.get(i))); break; } } } } } @Override public void onPause() { // Notify the downstream adapters if (receiptsNameAutoCompleteAdapter != null) { receiptsNameAutoCompleteAdapter.onPause(); } if (receiptsCommentAutoCompleteAdapter != null) { receiptsCommentAutoCompleteAdapter.onPause(); } if (idDisposable != null) { idDisposable.dispose(); idDisposable = null; } // Dismiss the soft keyboard SoftKeyboardManager.hideKeyboard(focusedView); exchangeRateBox.setRetryListener(null); database.unregisterReceiptAutoCompleteListener(); super.onPause(); } @Override public void onSaveInstanceState(Bundle outState) { super.onSaveInstanceState(outState); Logger.debug(this, "onSaveInstanceState"); if (exchangeRateContainer != null && outState != null) { outState.putBoolean(KEY_OUT_STATE_IS_EXCHANGE_RATE_VISIBLE, exchangeRateContainer.getVisibility() == View.VISIBLE); } } @Override public void onDestroy() { categoriesTableController.unsubscribe(categoryTableEventsListener); paymentMethodsTableController.unsubscribe(paymentMethodTableEventsListener); fragmentStateCache.onDestroy(this); super.onDestroy(); } private void configureExchangeRateField(@Nullable String baseCurrencyCode) { final String exchangeRateCurrencyCode = getParentTrip().getDefaultCurrencyCode(); if (exchangeRateCurrencyCode.equals(baseCurrencyCode) || baseCurrencyCode == null) { exchangeRateContainer.setVisibility(View.GONE); exchangeRateBox.setText(""); // Clear out if we're hiding the box } else { exchangeRateContainer.setVisibility(View.VISIBLE); submitExchangeRateRequest(baseCurrencyCode); } } private synchronized void submitExchangeRateRequest(@NonNull String baseCurrencyCode) { exchangeRateBox.setText(""); // Clear results to avoid stale data here if (presenter.hasActivePlusPurchase()) { Logger.info(this, "Submitting exchange rate request"); analytics.record(Events.Receipts.RequestExchangeRate); final String exchangeRateCurrencyCode = getParentTrip().getDefaultCurrencyCode(); exchangeRateBox.setCurrentState(NetworkRequestAwareEditText.State.Loading); if (lastExchangeRateFetchCallback != null) { // Ignore any outstanding results to not confuse ourselves lastExchangeRateFetchCallback.ignoreResult(); } lastExchangeRateFetchCallback = new MemoryLeakSafeCallback<ExchangeRate, EditText>(exchangeRateBox) { @Override public void success(EditText editText, Call<ExchangeRate> call, Response<ExchangeRate> response) { final ExchangeRate exchangeRate = response.body(); if (exchangeRate != null && exchangeRate.supportsExchangeRateFor(exchangeRateCurrencyCode)) { analytics.record(Events.Receipts.RequestExchangeRateSuccess); if (TextUtils.isEmpty(editText.getText())) { editText.setText(exchangeRate.getDecimalFormattedExchangeRate(exchangeRateCurrencyCode)); } else { Logger.warn(ReceiptCreateEditFragment.this, "User already started typing... Ignoring exchange rate result"); } exchangeRateBox.setCurrentState(NetworkRequestAwareEditText.State.Success); } else { Logger.error(ReceiptCreateEditFragment.this, "Received a null exchange rate"); analytics.record(Events.Receipts.RequestExchangeRateFailedWithNull); exchangeRateBox.setCurrentState(NetworkRequestAwareEditText.State.Failure); } } @Override public void failure(EditText editText, Call<ExchangeRate> call, Throwable th) { Logger.error(ReceiptCreateEditFragment.this, th); analytics.record(Events.Receipts.RequestExchangeRateFailed); exchangeRateBox.setCurrentState(NetworkRequestAwareEditText.State.Failure); } }; exchangeRateServiceManager.getService().getExchangeRate(dateBox.date, getString(R.string.exchange_rate_key), baseCurrencyCode).enqueue(lastExchangeRateFetchCallback); } else { exchangeRateBox.setCurrentState(NetworkRequestAwareEditText.State.Ready); Logger.info(this, "Ignoring exchange rate request, since there is no subscription for it"); } } private void saveReceipt() { if (presenter.checkReceipt(dateBox.date)) { final String name = TextUtils.isEmpty(nameBox.getText().toString()) ? "" : nameBox.getText().toString(); final Category category = categoriesAdpater.getItem(categoriesSpinner.getSelectedItemPosition()); final String currency = currencySpinner.getSelectedItem().toString(); final String price = priceBox.getText().toString(); final String tax = taxBox.getText().toString(); final String exchangeRate = exchangeRateBox.getText().toString(); final String comment = commentBox.getText().toString(); final PaymentMethod paymentMethod = (PaymentMethod) (presenter.isUsePaymentMethods() ? paymentMethodsSpinner.getSelectedItem() : null); final String extraText1 = (extra_edittext_box_1 == null) ? null : extra_edittext_box_1.getText().toString(); final String extraText2 = (extra_edittext_box_2 == null) ? null : extra_edittext_box_2.getText().toString(); final String extraText3 = (extra_edittext_box_3 == null) ? null : extra_edittext_box_3.getText().toString(); receiptInputCache.setCachedDate((Date) dateBox.date.clone()); receiptInputCache.setCachedCategory(category); receiptInputCache.setCachedCurrency(currency); presenter.saveReceipt(dateBox.date, price, tax, exchangeRate, comment, paymentMethod, reimbursable.isChecked(), fullpage.isChecked(), name, category, currency, extraText1, extraText2, extraText3); analytics.record(getReceipt() == null ? Events.Receipts.PersistNewReceipt : Events.Receipts.PersistUpdateReceipt); dateManager.setDateEditTextListenerDialogHolder(null); navigationHandler.navigateToReportInfoFragment(getParentTrip()); } } public void showDateError() { Toast.makeText(getActivity(), getFlexString(R.string.CALENDAR_TAB_ERROR), Toast.LENGTH_SHORT).show(); } public void showDateWarning() { Toast.makeText(getActivity(), getFlexString(R.string.DIALOG_RECEIPTMENU_TOAST_BAD_DATE), Toast.LENGTH_LONG).show(); } private class SpinnerSelectionListener implements AdapterView.OnItemSelectedListener { @Override public void onItemSelected(AdapterView<?> parentView, View selectedItemView, int position, long id) { if (presenter.isMatchReceiptNameToCategory()) { nameBox.setText(categoriesAdpater.getItem(position).getName()); } if (presenter.isMatchReceiptCommentToCategory()) { commentBox.setText(categoriesAdpater.getItem(position).getName()); } } @Override public void onNothingSelected(AdapterView<?> arg0) { } } private String getFlexString(int id) { return getFlexString(flex, id); } }