/* * Copyright (C) 2012-2016 The Android Money Manager Ex Project Team * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; either version 3 * of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. */ package com.money.manager.ex.transactions; import android.app.Activity; import android.content.Intent; import android.database.Cursor; import android.os.Bundle; import android.os.Parcelable; import android.support.annotation.NonNull; import android.support.v4.app.DialogFragment; import android.support.v4.content.ContextCompat; import android.text.Editable; import android.text.Html; import android.text.TextUtils; import android.text.TextWatcher; import android.view.View; import android.widget.AdapterView; import android.widget.ArrayAdapter; import android.widget.TextView; import android.widget.Toast; import com.afollestad.materialdialogs.DialogAction; import com.afollestad.materialdialogs.MaterialDialog; import com.codetroopers.betterpickers.calendardatepicker.CalendarDatePickerDialogFragment; import com.money.manager.ex.Constants; import com.money.manager.ex.MoneyManagerApplication; import com.money.manager.ex.PayeeActivity; import com.money.manager.ex.R; import com.money.manager.ex.account.AccountListActivity; import com.money.manager.ex.common.Calculator; import com.money.manager.ex.common.CommonSplitCategoryLogic; import com.money.manager.ex.core.RequestCodes; import com.money.manager.ex.core.UIHelper; import com.money.manager.ex.database.ISplitTransaction; import com.money.manager.ex.database.ITransactionEntity; import com.money.manager.ex.datalayer.CategoryRepository; import com.money.manager.ex.datalayer.IRepository; import com.money.manager.ex.datalayer.PayeeRepository; import com.money.manager.ex.datalayer.SubcategoryRepository; import com.money.manager.ex.domainmodel.Category; import com.money.manager.ex.domainmodel.Subcategory; import com.money.manager.ex.servicelayer.AccountService; import com.money.manager.ex.common.MmxBaseFragmentActivity; import com.money.manager.ex.common.CategoryListActivity; import com.money.manager.ex.core.Core; import com.money.manager.ex.core.TransactionTypes; import com.money.manager.ex.currency.CurrencyService; import com.money.manager.ex.datalayer.AccountRepository; import com.money.manager.ex.datalayer.AccountTransactionRepository; import com.money.manager.ex.domainmodel.Account; import com.money.manager.ex.domainmodel.Payee; import com.money.manager.ex.settings.AppSettings; import com.money.manager.ex.utils.MmxDate; import com.money.manager.ex.utils.MmxDateTimeUtils; import com.shamanland.fonticon.FontIconView; import com.squareup.sqlbrite.BriteDatabase; import org.parceler.Parcels; import java.util.ArrayList; import java.util.Arrays; import java.util.Date; import java.util.List; import javax.inject.Inject; import dagger.Lazy; import info.javaperformance.money.Money; import info.javaperformance.money.MoneyFactory; import timber.log.Timber; /** * Functions shared between Checking Account activity and Recurring Transactions activity. */ public class EditTransactionCommonFunctions { private static final String DATEPICKER_TAG = "datepicker"; public EditTransactionCommonFunctions(MmxBaseFragmentActivity parentActivity, ITransactionEntity transactionEntity, BriteDatabase database) { super(); activity = parentActivity; this.transactionEntity = transactionEntity; this.mDatabase = database; MoneyManagerApplication.getApp().iocComponent.inject(this); } // @Inject Lazy<MmxJodaDateTimeUtils> dateTimeUtilsLazy; @Inject Lazy<MmxDateTimeUtils> dateTimeUtilsLazy; // Model public ITransactionEntity transactionEntity; public String payeeName; public String mToAccountName; public String categoryName, subCategoryName; public ArrayList<ISplitTransaction> mSplitTransactions; public ArrayList<ISplitTransaction> mSplitTransactionsDeleted; // Controls public EditTransactionViewHolder viewHolder; private MmxBaseFragmentActivity activity; private boolean mSplitSelected; private boolean mDirty = false; // indicate whether the data has been modified by the user. private String mSplitCategoryEntityName; private BriteDatabase mDatabase; private List<Account> AccountList; private ArrayList<String> mAccountNameList = new ArrayList<>(); private ArrayList<Integer> mAccountIdList = new ArrayList<>(); private TransactionTypes previousTransactionType = TransactionTypes.Withdrawal; private String[] mStatusItems, mStatusValues; // arrays to manage trans.code and status private String mUserDateFormat; public boolean deleteMarkedSplits(IRepository repository) { for (int i = 0; i < mSplitTransactionsDeleted.size(); i++) { ISplitTransaction splitToDelete = mSplitTransactionsDeleted.get(i); // Ignore unsaved entities. if (!splitToDelete.hasId()) continue; if (!repository.delete(splitToDelete)) { Toast.makeText(getContext(), R.string.db_checking_update_failed, Toast.LENGTH_SHORT).show(); Timber.w("Delete split transaction failed!"); return false; } } return true; } public void displayCategoryName() { // validation if (this.viewHolder.categoryTextView == null) return; this.viewHolder.categoryTextView.setText(""); if (isSplitSelected()) { // Split transaction. Show ... this.viewHolder.categoryTextView.setText("\u2026"); } else { if (!TextUtils.isEmpty(categoryName)) { this.viewHolder.categoryTextView.setText(categoryName); if (!TextUtils.isEmpty(subCategoryName)) { this.viewHolder.categoryTextView.setText(Html.fromHtml( this.viewHolder.categoryTextView.getText() + " : <i>" + subCategoryName + "</i>")); } } } } public void findControls(Activity view) { this.viewHolder = new EditTransactionViewHolder(view); } public Integer getAccountCurrencyId(int accountId) { if (accountId == Constants.NOT_SET) return Constants.NOT_SET; AccountRepository repo = new AccountRepository(getContext()); Integer currencyId = repo.loadCurrencyIdFor(accountId); if (currencyId == null) { new UIHelper(getContext()).showToast(R.string.error_loading_currency); currencyId = Constants.NOT_SET; } return currencyId; } public String getTransactionType() { if (this.transactionEntity.getTransactionType() == null) { return null; } return transactionEntity.getTransactionType().name(); } public FontIconView getDepositButtonIcon() { return (FontIconView) getActivity().findViewById(R.id.depositButtonIcon); } public Integer getDestinationCurrencyId() { Integer accountId = this.transactionEntity.getAccountToId(); // The destination account/currency is hidden by default and may be uninitialized. if (!transactionEntity.hasAccountTo() && !mAccountIdList.isEmpty()) { accountId = mAccountIdList.get(0); } // Handling some invalid values. if (accountId == null || accountId == 0) accountId = Constants.NOT_SET; return getAccountCurrencyId(accountId); } public ArrayList<ISplitTransaction> getDeletedSplitCategories() { if(mSplitTransactionsDeleted == null){ mSplitTransactionsDeleted = new ArrayList<>(); } return mSplitTransactionsDeleted; } public boolean getDirty() { return mDirty; } public Integer getSourceCurrencyId() { Integer accountId = this.transactionEntity.getAccountId(); //if (!transactionEntity.has) if (accountId == null && !mAccountIdList.isEmpty()) { accountId = mAccountIdList.get(0); } if (accountId == null || accountId == 0) accountId = Constants.NOT_SET; return getAccountCurrencyId(accountId); } public FontIconView getTransferButtonIcon() { return (FontIconView) getActivity().findViewById(R.id.transferButtonIcon); } public FontIconView getWithdrawalButtonIcon() { return (FontIconView) getActivity().findViewById(R.id.withdrawalButtonIcon); } public boolean hasPayee() { return this.transactionEntity.getPayeeId() > 0; } public boolean hasSplitCategories() { return !getSplitTransactions().isEmpty(); } /** * Initialize account selectors. */ public void initAccountSelectors() { AppSettings settings = new AppSettings(getContext()); // Account list as the data source to populate the drop-downs. AccountService accountService = new AccountService(getContext()); this.AccountList = accountService.getTransactionAccounts( settings.getLookAndFeelSettings().getViewOpenAccounts(), settings.getLookAndFeelSettings().getViewFavouriteAccounts()); if (this.AccountList == null) return; for(Account account : this.AccountList) { mAccountNameList.add(account.getName()); mAccountIdList.add(account.getId()); } AccountRepository accountRepository = new AccountRepository(getContext()); Integer accountId = transactionEntity.getAccountId(); if (accountId != null) { addMissingAccountToSelectors(accountRepository, accountId); } addMissingAccountToSelectors(accountRepository, transactionEntity.getAccountToId()); // add the default account, if any. Integer defaultAccount = settings.getGeneralSettings().getDefaultAccountId(); // Set the current account, if not set already. if ((accountId != null && accountId == Constants.NOT_SET) && (defaultAccount != null && defaultAccount != Constants.NOT_SET)) { accountId = defaultAccount; addMissingAccountToSelectors(accountRepository, accountId); // Set the default account as the active account. transactionEntity.setAccountId(accountId); } // Adapter for account selectors. ArrayAdapter<String> accountAdapter = new ArrayAdapter<>(getActivity(), android.R.layout.simple_spinner_item, mAccountNameList); accountAdapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item); viewHolder.spinAccount.setAdapter(accountAdapter); viewHolder.spinAccountTo.setAdapter(accountAdapter); // Selection handler. AdapterView.OnItemSelectedListener listener = new AdapterView.OnItemSelectedListener() { @Override public void onItemSelected(AdapterView<?> parent, View view, int position, long id) { if ((position < 0) || (position > mAccountIdList.size())) { return; } setDirty(true); boolean isSource = parent == viewHolder.spinAccount; boolean isTransfer = transactionEntity.getTransactionType() == TransactionTypes.Transfer; Integer accountId = mAccountIdList.get(position); if (isSource) { int originalCurrencyId = getSourceCurrencyId(); transactionEntity.setAccountId(accountId); if (isTransfer) { // calculate the exchange amount if it is 0. if (transactionEntity.getAmountTo().isZero()) { Money convertedAmount = calculateAmountTo(); transactionEntity.setAmountTo(convertedAmount); displayAmountTo(); } // Recalculate the original amount when the currency changes. if (originalCurrencyId != getSourceCurrencyId()) { Money exchangeAmount = calculateAmountFrom(); transactionEntity.setAmount(exchangeAmount); displayAmountFrom(); } } else { displayAmountFrom(); } } else { int originalCurrencyId = getDestinationCurrencyId(); transactionEntity.setAccountToId(accountId); if (isTransfer) { // calculate the exchange amount if it is 0. if (transactionEntity.getAmount().isZero()) { Money convertedAmount = calculateAmountFrom(); transactionEntity.setAmount(convertedAmount); displayAmountFrom(); } // Recalculate the original amount when the currency changes. if (originalCurrencyId != getDestinationCurrencyId()) { Money exchangeAmount = calculateAmountTo(); transactionEntity.setAmountTo(exchangeAmount); displayAmountTo(); } } else { displayAmountTo(); } } refreshControlTitles(); } @Override public void onNothingSelected(AdapterView<?> parent) { } }; // Account int accountIndex = mAccountIdList.indexOf(accountId); if (accountIndex >= 0) { viewHolder.spinAccount.setSelection(accountIndex, true); } viewHolder.spinAccount.setOnItemSelectedListener(listener); // To Account if (transactionEntity.hasAccountTo() && mAccountIdList.indexOf(transactionEntity.getAccountToId()) >= 0) { viewHolder.spinAccountTo.setSelection(mAccountIdList.indexOf(transactionEntity.getAccountToId()), true); } viewHolder.spinAccountTo.setOnItemSelectedListener(listener); } public void initAmountSelectors() { // View.OnClickListener onClickAmount = new View.OnClickListener() { // // @Override // public void onClick(View v) { // // Get currency id from the account for which the amount has been modified. // Integer currencyId; // Money amount; // // if (v.equals(viewHolder.txtAmountTo)) { // // clicked Amount To. // currencyId = getDestinationCurrencyId(); // amount = transactionEntity.getAmountTo(); // } else { // // clicked Amount. // currencyId = getSourceCurrencyId(); // amount = transactionEntity.getAmount(); // } // // AmountInputDialog dialog = AmountInputDialog.getInstance(v.getId(), amount, currencyId); // dialog.show(activity.getSupportFragmentManager(), dialog.getClass().getSimpleName()); // // // The result is received in onFinishedInputAmountDialog. // } // }; // amount displayAmountFrom(); viewHolder.txtAmount.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { int currencyId = getSourceCurrencyId(); Money amount = transactionEntity.getAmount(); // Intent intent = IntentFactory.getNumericInputIntent(getContext(), amount, currencyId); // getActivity().startActivityForResult(intent, REQUEST_AMOUNT); Calculator.forActivity(getActivity()) .currency(currencyId) .amount(amount) .show(RequestCodes.AMOUNT); } }); // amount to displayAmountTo(); viewHolder.txtAmountTo.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { int currencyId = getDestinationCurrencyId(); Money amount = transactionEntity.getAmountTo(); // Intent intent = IntentFactory.getNumericInputIntent(getContext(), amount, currencyId); // getActivity().startActivityForResult(intent, REQUEST_AMOUNT_TO); Calculator.forActivity(getActivity()) .amount(amount).currency(currencyId) .show(RequestCodes.AMOUNT_TO); } }); } /** * Initialize Category selector. * @param datasetName name of the dataset (TableBudgetSplitTransactions.class.getSimpleName()) */ public void initCategoryControls(final String datasetName) { // keep the dataset name for later. this.mSplitCategoryEntityName = datasetName; this.viewHolder.categoryTextView.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { if (!isSplitSelected()) { // select first category. Intent intent = new Intent(getActivity(), CategoryListActivity.class); intent.setAction(Intent.ACTION_PICK); getActivity().startActivityForResult(intent, RequestCodes.CATEGORY); } else { // select split categories. showSplitCategoriesForm(mSplitCategoryEntityName); } // results are handled in onActivityResult. } }); } /** * Due Date picker */ public void initDateSelector() { Date date = this.transactionEntity.getDate(); if (date == null) { date = new MmxDate().toDate(); transactionEntity.setDate(date); } showDate(date); viewHolder.dateTextView.setOnClickListener(new View.OnClickListener() { CalendarDatePickerDialogFragment.OnDateSetListener listener = new CalendarDatePickerDialogFragment.OnDateSetListener() { @Override public void onDateSet(CalendarDatePickerDialogFragment dialog, int year, int monthOfYear, int dayOfMonth) { Date dateTime = dateTimeUtilsLazy.get().from(year, monthOfYear, dayOfMonth); setDate(dateTime); } }; @Override public void onClick(View v) { MmxDate dateTime = new MmxDate(transactionEntity.getDate()); CalendarDatePickerDialogFragment datePicker = new CalendarDatePickerDialogFragment() .setOnDateSetListener(listener) .setFirstDayOfWeek(dateTimeUtilsLazy.get().getFirstDayOfWeek()) .setPreselectedDate(dateTime.getYear(), dateTime.getMonthOfYear(), dateTime.getDayOfMonth()); if (new UIHelper(getContext()).isUsingDarkTheme()) { datePicker.setThemeDark(); } datePicker.show(getActivity().getSupportFragmentManager(), DATEPICKER_TAG); } }); viewHolder.previousDayButton.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { Date dateTime = new MmxDate(transactionEntity.getDate()).minusDays(1).toDate(); setDate(dateTime); } }); viewHolder.nextDayButton.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { Date dateTime = new MmxDate(transactionEntity.getDate()).plusDays(1).toDate(); setDate(dateTime); } }); } public void initializeToolbar() { // activity.showStandardToolbarActions(); // String title // ActionBar actionBar = activity.getSupportActionBar(); // actionBar.setTitle(title); // Title // CollapsingToolbarLayout collapsingToolbarLayout = (CollapsingToolbarLayout) findViewById(R.id.collapsing_toolbar); // collapsingToolbarLayout.setTitle(getString(R.string.budget)); // Back arrow / cancel. getActivity().setDisplayHomeAsUpEnabled(true); // todo: add Save button } public void initNotesControls() { if (!(TextUtils.isEmpty(transactionEntity.getNotes()))) { viewHolder.edtNotes.setText(transactionEntity.getNotes()); } viewHolder.edtNotes.addTextChangedListener(new TextWatcher() { @Override public void beforeTextChanged(CharSequence charSequence, int i, int i1, int i2) { } @Override public void onTextChanged(CharSequence charSequence, int i, int i1, int i2) { } @Override public void afterTextChanged(Editable editable) { setDirty(true); transactionEntity.setNotes(editable.toString()); } }); } public void initPayeeControls() { this.viewHolder.txtSelectPayee.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { Intent intent = new Intent(getContext(), PayeeActivity.class); intent.setAction(Intent.ACTION_PICK); getActivity().startActivityForResult(intent, RequestCodes.PAYEE); // the result is handled in onActivityResult } }); viewHolder.removePayeeButton.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { setDirty(true); transactionEntity.setPayeeId(Constants.NOT_SET); payeeName = ""; showPayeeName(); } }); } /** * Initialize Split Categories button & controls. */ public void initSplitCategories() { viewHolder.splitButton.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { boolean splitting = !isSplitSelected(); if (splitting) { showSplitCategoriesForm(mSplitCategoryEntityName); } else { // User wants to remove split. int splitCount = getSplitTransactions().size(); switch (splitCount) { case 0: // just remove split setSplit(false); break; case 1: convertOneSplitIntoRegularTransaction(); break; default: showSplitResetNotice(); break; } } } }); refreshSplitControls(); } public void initStatusSelector() { mStatusItems = activity.getResources().getStringArray(R.array.status_items); mStatusValues = activity.getResources().getStringArray(R.array.status_values); // create adapter for spinnerStatus ArrayAdapter<String> adapterStatus = new ArrayAdapter<>(getActivity(), android.R.layout.simple_spinner_item, mStatusItems); adapterStatus.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item); viewHolder.spinStatus.setAdapter(adapterStatus); // select current value if (!(TextUtils.isEmpty(transactionEntity.getStatus()))) { if (Arrays.asList(mStatusValues).indexOf(transactionEntity.getStatus()) >= 0) { viewHolder.spinStatus.setSelection(Arrays.asList(mStatusValues).indexOf(transactionEntity.getStatus()), true); } } else { transactionEntity.setStatus(mStatusValues[0]); } viewHolder.spinStatus.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() { @Override public void onItemSelected(AdapterView<?> parent, View view, int position, long id) { if ((position >= 0) && (position <= mStatusValues.length)) { String selectedStatus = mStatusValues[position]; // If Status has been changed manually, mark data as dirty. if (!selectedStatus.equalsIgnoreCase(transactionEntity.getStatus())) { setDirty(true); } transactionEntity.setStatus(selectedStatus); } } @Override public void onNothingSelected(AdapterView<?> parent) { } }); } public void initTransactionNumberControls() { // Transaction number if (!TextUtils.isEmpty(transactionEntity.getTransactionNumber())) { viewHolder.edtTransNumber.setText(transactionEntity.getTransactionNumber()); } viewHolder.edtTransNumber.addTextChangedListener(new TextWatcher() { @Override public void beforeTextChanged(CharSequence charSequence, int i, int i1, int i2) { } @Override public void onTextChanged(CharSequence charSequence, int i, int i1, int i2) { } @Override public void afterTextChanged(Editable editable) { setDirty(true); transactionEntity.setTransactionNumber(editable.toString()); } }); viewHolder.btnTransNumber.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { AccountTransactionRepository repo = new AccountTransactionRepository(getContext()); String sql = "SELECT MAX(CAST(" + ITransactionEntity.TRANSACTIONNUMBER + " AS INTEGER)) FROM " + repo.getSource() + " WHERE " + ITransactionEntity.ACCOUNTID + "=?"; String accountId = transactionEntity.getAccountId().toString(); Cursor cursor = mDatabase.query(sql, accountId); if (cursor == null) return; if (cursor.moveToFirst()) { String transNumber = cursor.getString(0); if (TextUtils.isEmpty(transNumber)) { transNumber = "0"; } if ((!TextUtils.isEmpty(transNumber)) && TextUtils.isDigitsOnly(transNumber)) { try { // Use Money type to support very large numbers. Money transactionNumber = MoneyFactory.fromString(transNumber); viewHolder.edtTransNumber.setText(transactionNumber.add(MoneyFactory.fromString("1")) .toString()); } catch (Exception e) { Timber.e(e, "increasing transaction number"); } } } cursor.close(); } }); } public void initTransactionTypeSelector() { // Handle click events. View.OnClickListener onClickListener = new View.OnClickListener() { @Override public void onClick(View v) { setDirty(true); // find which transaction type this is. TransactionTypes type = (TransactionTypes) v.getTag(); changeTransactionTypeTo(type); } }; if (viewHolder.withdrawalButton != null) { viewHolder.withdrawalButton.setTag(TransactionTypes.Withdrawal); viewHolder.withdrawalButton.setOnClickListener(onClickListener); } if (viewHolder.depositButton != null) { viewHolder.depositButton.setTag(TransactionTypes.Deposit); viewHolder.depositButton.setOnClickListener(onClickListener); } if (viewHolder.transferButton != null) { viewHolder.transferButton.setTag(TransactionTypes.Transfer); viewHolder.transferButton.setOnClickListener(onClickListener); } // Check if the transaction type has been set (for example, when editing an existing transaction). TransactionTypes current = transactionEntity.getTransactionType() == null ? TransactionTypes.Withdrawal : transactionEntity.getTransactionType(); changeTransactionTypeTo(current); } /** * Indicate whether the Split Categories is selected/checked. * @return boolean */ public boolean isSplitSelected() { return mSplitSelected; } /** * Loads info for Category and Subcategory * @return A boolean indicating whether the operation was successful. */ public boolean loadCategoryName() { if(!this.transactionEntity.hasCategory() && this.transactionEntity.getSubcategoryId() <= 0) return false; CategoryRepository categoryRepository = new CategoryRepository(getContext()); Category category = categoryRepository.load(this.transactionEntity.getCategoryId()); if (category != null) { this.categoryName = category.getName(); } else { this.categoryName = null; } SubcategoryRepository subRepo = new SubcategoryRepository(getContext()); Subcategory subcategory = subRepo.load(this.transactionEntity.getSubcategoryId()); if (subcategory != null) { this.subCategoryName = subcategory.getName(); } else { this.subCategoryName = null; } return true; } public boolean onActionCancelClick() { if (getDirty()) { final MaterialDialog dialog = new MaterialDialog.Builder(getContext()) .title(android.R.string.cancel) .content(R.string.transaction_cancel_confirm) .positiveText(R.string.discard) .negativeText(R.string.keep_editing) .cancelable(false) .onPositive(new MaterialDialog.SingleButtonCallback() { @Override public void onClick(@NonNull MaterialDialog dialog, @NonNull DialogAction which) { cancelActivity(); } }) .build(); dialog.show(); } else { // Just close activity cancelActivity(); } return true; } public void onActivityResult(int requestCode, int resultCode, Intent data) { if ((resultCode != Activity.RESULT_OK) || (data == null)) return; setDirty(true); String stringExtra; switch (requestCode) { case RequestCodes.PAYEE: this.transactionEntity.setPayeeId(data.getIntExtra(PayeeActivity.INTENT_RESULT_PAYEEID, Constants.NOT_SET)); payeeName = data.getStringExtra(PayeeActivity.INTENT_RESULT_PAYEENAME); // select last category used from payee. Only if category has not been entered earlier. if (!isSplitSelected() && !this.transactionEntity.hasCategory() ) { if (setCategoryFromPayee(this.transactionEntity.getPayeeId())) { displayCategoryName(); // refresh UI } } // refresh UI showPayeeName(); break; case RequestCodes.ACCOUNT: transactionEntity.setAccountToId(data.getIntExtra(AccountListActivity.INTENT_RESULT_ACCOUNTID, Constants.NOT_SET)); mToAccountName = data.getStringExtra(AccountListActivity.INTENT_RESULT_ACCOUNTNAME); break; case RequestCodes.AMOUNT: onFinishedInputAmountDialog(R.id.textViewAmount, Calculator.getAmountFromResult(data)); break; case RequestCodes.AMOUNT_TO: onFinishedInputAmountDialog(R.id.textViewToAmount, Calculator.getAmountFromResult(data)); break; case RequestCodes.CATEGORY: this.transactionEntity.setCategoryId(data.getIntExtra(CategoryListActivity.INTENT_RESULT_CATEGID, Constants.NOT_SET)); categoryName = data.getStringExtra(CategoryListActivity.INTENT_RESULT_CATEGNAME); this.transactionEntity.setSubcategoryId(data.getIntExtra(CategoryListActivity.INTENT_RESULT_SUBCATEGID, Constants.NOT_SET)); subCategoryName = data.getStringExtra(CategoryListActivity.INTENT_RESULT_SUBCATEGNAME); // refresh UI category displayCategoryName(); break; case RequestCodes.SPLIT_TX: mSplitTransactions = Parcels.unwrap(data.getParcelableExtra(SplitCategoriesActivity.INTENT_RESULT_SPLIT_TRANSACTION)); // deleted items Parcelable parcelDeletedSplits = data.getParcelableExtra(SplitCategoriesActivity.INTENT_RESULT_SPLIT_TRANSACTION_DELETED); if (parcelDeletedSplits != null) { mSplitTransactionsDeleted = Parcels.unwrap(parcelDeletedSplits); } // Splits and deleted splits must be restored before any action takes place. onSplitConfirmed(getSplitTransactions()); break; } } public void onFinishedInputAmountDialog(int id, Money amount) { View view = getActivity().findViewById(id); if (view == null || !(view instanceof TextView)) return; setDirty(true); boolean isTransfer = transactionEntity.getTransactionType().equals(TransactionTypes.Transfer); boolean isAmountFrom = id == R.id.textViewAmount; // Set and display the selected amount. if (isAmountFrom) { this.transactionEntity.setAmount(amount); displayAmountFrom(); } else { this.transactionEntity.setAmountTo(amount); displayAmountTo(); } // Handle currency exchange on Transfers. if (isTransfer) { Integer fromCurrencyId = getSourceCurrencyId(); Integer toCurrencyId = getDestinationCurrencyId(); if (fromCurrencyId.equals(toCurrencyId)) { // Same currency. Update both values if the transfer is in the same currency. this.transactionEntity.setAmount(amount); this.transactionEntity.setAmountTo(amount); displayAmountFrom(); displayAmountTo(); // Exit here. return; } // Different currency. Recalculate the other amount only if it has not been set. boolean shouldConvert = isAmountFrom ? transactionEntity.getAmountTo().isZero() : transactionEntity.getAmount().isZero(); if (shouldConvert){ // Convert the value and write the amount into the other input box. Money convertedAmount; if (isAmountFrom) { convertedAmount = calculateAmountTo(); transactionEntity.setAmountTo(convertedAmount); displayAmountTo(); } else { convertedAmount = calculateAmountFrom(); transactionEntity.setAmount(convertedAmount); displayAmountFrom(); } } } } /** * Handle the controls after the split is checked. */ public void refreshSplitControls() { // display category field displayCategoryName(); // enable/disable Amount field. viewHolder.txtAmount.setEnabled(!mSplitSelected); viewHolder.txtAmountTo.setEnabled(!mSplitSelected); updateSplitButton(); } /** * Reflect the transaction type change. Show and hide controls appropriately. */ public void onTransactionTypeChanged(TransactionTypes transactionType) { transactionEntity.setTransactionType(transactionType); boolean isTransfer = transactionType.equals(TransactionTypes.Transfer); viewHolder.accountFromLabel.setText(isTransfer ? R.string.from_account : R.string.account); viewHolder.tableRowAccountTo.setVisibility(isTransfer ? View.VISIBLE : View.GONE); viewHolder.tableRowPayee.setVisibility(!isTransfer ? View.VISIBLE : View.GONE); viewHolder.tableRowAmountTo.setVisibility(isTransfer ? View.VISIBLE : View.GONE); refreshControlTitles(); if (isTransfer) { onTransferSelected(); viewHolder.splitButton.setEnabled(false); } else { // Change sign for the split records. Transfers should delete split records. CommonSplitCategoryLogic.changeSign(getSplitTransactions()); viewHolder.splitButton.setEnabled(true); } } /** * Update input control titles to reflect the transaction type. */ public void refreshControlTitles() { if (viewHolder.amountHeaderTextView == null || viewHolder.amountToHeaderTextView == null) return; boolean isTransfer = transactionEntity.getTransactionType().equals(TransactionTypes.Transfer); if (!isTransfer) { viewHolder.amountHeaderTextView.setText(R.string.amount); } else { // Transfer. Adjust the headers on amount text boxes. int index = mAccountIdList.indexOf(transactionEntity.getAccountId()); if (index >= 0) { // the title depends on whether we are showing the destination amount. if (areCurrenciesSame()) { viewHolder.amountHeaderTextView.setText(getContext().getString(R.string.transfer_amount)); } else { viewHolder.amountHeaderTextView.setText(getContext().getString(R.string.withdrawal_from, this.AccountList.get(index).getName())); } } index = mAccountIdList.indexOf(transactionEntity.getAccountToId()); if (index >= 0) { viewHolder.amountToHeaderTextView.setText(getContext().getString(R.string.deposit_to, this.AccountList.get(index).getName())); } } } /** * update UI interface with PayeeName */ public void showPayeeName() { // write into text button payee name if (this.viewHolder.txtSelectPayee != null) { String text = !TextUtils.isEmpty(payeeName) ? payeeName : ""; this.viewHolder.txtSelectPayee.setText(text); } } /** * Reset the effects of transfer when switching to Withdrawal/Deposit. */ public void resetTransfer() { // reset destination account and amount transactionEntity.setAccountToId(Constants.NOT_SET); transactionEntity.setAmountTo(MoneyFactory.fromDouble(0)); } public void setSplit(boolean checked) { mSplitSelected = checked; refreshSplitControls(); } /** * query info payee * @param payeeId id payee * @return true if the data selected */ public boolean loadPayeeName(int payeeId) { PayeeRepository repo = new PayeeRepository(getContext()); Payee payee = repo.load(payeeId); if (payee != null) { this.payeeName = payee.getName(); } else { this.payeeName = ""; } return true; } /** * setCategoryFromPayee set last category used from payee * @param payeeId Identify of payee * @return true if category set */ public boolean setCategoryFromPayee(int payeeId) { if (payeeId == Constants.NOT_SET) return false; PayeeRepository repo = new PayeeRepository(getContext()); Payee payee = repo.load(payeeId); if (payee == null) return false; if (!payee.hasCategory()) return false; // otherwise this.transactionEntity.setCategoryId(payee.getCategoryId()); this.transactionEntity.setSubcategoryId(payee.getSubcategoryId()); loadCategoryName(); return true; } public void setDirty(boolean dirty) { mDirty = dirty; } /** * Select, or change, the type of transaction (withdrawal, deposit, transfer). * Entry point and the handler for the type selector input control. * @param transactionType The type to set the transaction to. */ public void changeTransactionTypeTo(TransactionTypes transactionType) { this.previousTransactionType = this.transactionEntity.getTransactionType(); this.transactionEntity.setTransactionType(transactionType); // Clear all buttons. Core core = new Core(activity); int backgroundInactive = core.getColourFromAttribute(R.attr.button_background_inactive); viewHolder.withdrawalButton.setBackgroundColor(backgroundInactive); getWithdrawalButtonIcon().setTextColor(ContextCompat.getColor(activity, R.color.material_red_700)); viewHolder.depositButton.setBackgroundColor(backgroundInactive); getDepositButtonIcon().setTextColor(ContextCompat.getColor(activity, R.color.material_green_700)); viewHolder.transferButton.setBackgroundColor(backgroundInactive); getTransferButtonIcon().setTextColor(ContextCompat.getColor(activity, R.color.material_grey_700)); // Style the selected button. UIHelper uiHelper = new UIHelper(getContext()); int backgroundSelected = ContextCompat.getColor(getContext(), R.color.md_accent); int foregroundSelected = uiHelper.getToolbarItemColor(); switch (transactionType) { case Deposit: viewHolder.depositButton.setBackgroundColor(backgroundSelected); getDepositButtonIcon().setTextColor(foregroundSelected); break; case Withdrawal: viewHolder.withdrawalButton.setBackgroundColor(backgroundSelected); getWithdrawalButtonIcon().setTextColor(foregroundSelected); break; case Transfer: viewHolder.transferButton.setBackgroundColor(backgroundSelected); getTransferButtonIcon().setTextColor(foregroundSelected); break; } // Handle the change. onTransactionTypeChanged(transactionType); } public boolean validateData() { boolean isTransfer = transactionEntity.getTransactionType().equals(TransactionTypes.Transfer); Core core = new Core(getContext()); if (isTransfer) { if (transactionEntity.getAccountToId() == Constants.NOT_SET) { core.alert(R.string.error_toaccount_not_selected); return false; } if (transactionEntity.getAccountToId().equals(transactionEntity.getAccountId())) { core.alert(R.string.error_transfer_to_same_account); return false; } // Amount To is required and has to be positive. if (this.transactionEntity.getAmountTo().toDouble() <= 0) { core.alert(R.string.error_amount_must_be_positive); return false; } } // Amount is required and must be positive. Sign is determined by transaction type. if (transactionEntity.getAmount().toDouble() <= 0) { core.alert(R.string.error_amount_must_be_positive); return false; } // Category is required if tx is not a split or transfer. boolean hasCategory = transactionEntity.hasCategory(); if (!hasCategory && (!isSplitSelected()) && !isTransfer) { core.alert(R.string.error_category_not_selected); return false; } // Split records must exist if split is checked. if (isSplitSelected() && getSplitTransactions().isEmpty()) { core.alert(R.string.error_split_transaction_empty); return false; } // Splits sum must be positive. if (!CommonSplitCategoryLogic.validateSumSign(getSplitTransactions())){ core.alert(R.string.split_amount_negative); return false; } return true; } /** * Remove splits when switching to Transfer. */ public void confirmDeletingCategories() { removeAllSplitCategories(); setSplit(false); transactionEntity.setTransactionType(TransactionTypes.Transfer); onTransactionTypeChanged(TransactionTypes.Transfer); } /** * When cancelling changing the transaction type to Transfer, revert back to the * previous transaction type. */ public void cancelChangingTransactionToTransfer() { // Select the previous transaction type. changeTransactionTypeTo(previousTransactionType); } /** * After the user accepts, remove any split categories. */ public void removeAllSplitCategories() { List<ISplitTransaction> splitTransactions = getSplitTransactions(); for(int i = 0; i < splitTransactions.size(); i++) { ISplitTransaction split = splitTransactions.get(i); // How do we get this? //if (split == null) continue; int id = split.getId(); ArrayList<ISplitTransaction> deletedSplits = getDeletedSplitCategories(); if(id == -1) { // Remove any newly created splits. splitTransactions.remove(i); i--; } else { // Delete any splits already in the database. Avoid adding duplicate records. if(!deletedSplits.contains(split)) { deletedSplits.add(split); } } } } /** * Check if there is only one Split Category and transforms the transaction to a non-split * transaction, removing the split category record. * @return True if there is only one split. Need to update the transaction. */ public boolean convertOneSplitIntoRegularTransaction() { if (getSplitTransactions().size() != 1) return false; // use the first split category record. ISplitTransaction splitTransaction = getSplitTransactions().get(0); // reuse the amount & category transactionEntity.setAmount(splitTransaction.getAmount()); displayAmountFrom(); transactionEntity.setCategoryId(splitTransaction.getCategoryId()); transactionEntity.setSubcategoryId(splitTransaction.getSubcategoryId()); loadCategoryName(); // displayCategoryName(); // reset split indicator & display category setSplit(false); getDeletedSplitCategories().add(splitTransaction); getSplitTransactions().remove(splitTransaction); // e deletion in the specific implementation. return true; } /* Private */ private void addMissingAccountToSelectors(AccountRepository accountRepository, Integer accountId) { if (accountId == null || accountId <= 0) return; // #316. In case the account from recurring transaction is not in the visible list, // load it separately. if (!mAccountIdList.contains(accountId)) { Account savedAccount = accountRepository.load(accountId); if (savedAccount != null) { this.AccountList.add(savedAccount); mAccountNameList.add(savedAccount.getName()); mAccountIdList.add(savedAccount.getId()); } } } private boolean areCurrenciesSame() { if (transactionEntity.getAccountId() == null) return false; if (transactionEntity.getAccountToId() == null) return false; AccountRepository repo = new AccountRepository(getContext()); Account accountFrom = repo.load(transactionEntity.getAccountId()); if (accountFrom == null) return false; Account accountTo = repo.load(transactionEntity.getAccountToId()); if (accountTo == null) return false; return accountFrom.getCurrencyId().equals(accountTo.getCurrencyId()); } /** * Perform currency exchange to get the Amount From. */ private Money calculateAmountFrom() { CurrencyService currencyService = new CurrencyService(getContext()); return currencyService.doCurrencyExchange(getSourceCurrencyId(), transactionEntity.getAmountTo(), getDestinationCurrencyId()); } private Money calculateAmountTo() { CurrencyService currencyService = new CurrencyService(getContext()); return currencyService.doCurrencyExchange(getDestinationCurrencyId(), transactionEntity.getAmount(), getSourceCurrencyId()); } private void cancelActivity() { getActivity().setResult(Activity.RESULT_CANCELED); getActivity().finish(); } /** * Create a split item using the amount and category from the existing transaction. * if there is a Category selected, and we are enabling Splits, use the selected category for * the initial split record. */ private ISplitTransaction createSplitFromTransaction() { // Add the new split record of the same type as the parent. ISplitTransaction entity = SplitItemFactory.create(this.mSplitCategoryEntityName, transactionEntity.getTransactionType()); entity.setAmount(this.transactionEntity.getAmount()); if (this.transactionEntity.hasCategory()) { entity.setCategoryId(this.transactionEntity.getCategoryId()); entity.setSubcategoryId(this.transactionEntity.getSubcategoryId()); } return entity; } private void displayAmountFrom() { Money amount = transactionEntity.getAmount() == null ? MoneyFactory.fromDouble(0) : transactionEntity.getAmount(); displayAmountFormatted(viewHolder.txtAmount, amount, getSourceCurrencyId()); } private void displayAmountTo() { // if the currencies are the same, show only one Amount field. int amountToVisibility = areCurrenciesSame() ? View.GONE : View.VISIBLE; viewHolder.tableRowAmountTo.setVisibility(amountToVisibility); Money amount = transactionEntity.getAmountTo() == null ? MoneyFactory.fromDouble(0) : transactionEntity.getAmountTo(); //displayAmountTo(amount); displayAmountFormatted(viewHolder.txtAmountTo, amount, getDestinationCurrencyId()); } private void displayAmountFormatted(TextView view, Money amount, Integer currencyId) { if (amount == null) return; if (currencyId == null || currencyId == Constants.NOT_SET) return; CurrencyService currencyService = new CurrencyService(getContext()); String amountDisplay = currencyService.getCurrencyFormatted(currencyId, amount); view.setText(amountDisplay); view.setTag(amount.toString()); } private MmxBaseFragmentActivity getActivity() { return (MmxBaseFragmentActivity) activity; } private MmxBaseFragmentActivity getContext() { return activity; } private ArrayList<ISplitTransaction> getSplitTransactions() { if (mSplitTransactions == null) { mSplitTransactions = new ArrayList<>(); } return mSplitTransactions; } private String getUserDateFormat() { if (TextUtils.isEmpty(mUserDateFormat)) { mUserDateFormat = dateTimeUtilsLazy.get().getUserDatePattern(getContext()); } return mUserDateFormat; } /** * Returning from the Split Categories form after OK button was pressed. */ private void onSplitConfirmed(List<ISplitTransaction> splits) { if (splits.isEmpty()) { // All split categories removed. resetCategory(); setSplit(false); return; } // if there is only one split item, e it immediately. if (splits.size() == 1) { convertOneSplitIntoRegularTransaction(); return; } // Multiple split categories exist at this point. resetCategory(); // indicate that the split is active & refresh display setSplit(true); // Use the sum of all splits as the Amount. Money splitSum = MoneyFactory.fromString("0"); for (int i = 0; i < splits.size(); i++) { splitSum = splitSum.add(splits.get(i).getAmount()); } transactionEntity.setAmount(splitSum); displayAmountFrom(); } /** * The user is switching to Transfer transaction type. */ private void onTransferSelected() { // Check whether to delete split categories, if any. if(hasSplitCategories()) { // Prompt the user to confirm deleting split categories. // Use DialogFragment in order to redraw the binaryDialog when switching device orientation. DialogFragment dialog = new YesNoDialog(); Bundle args = new Bundle(); args.putString("title", getContext().getString(R.string.warning)); args.putString("message", getContext().getString(R.string.no_transfer_splits)); args.putString("purpose", YesNoDialog.PURPOSE_DELETE_SPLITS_WHEN_SWITCHING_TO_TRANSFER); dialog.setArguments(args); dialog.show(getActivity().getSupportFragmentManager(), "tag"); // Dialog result is handled in onEvent handlers in the listeners. return; } // un-check split. setSplit(false); // Set the destination account, if not already. if (transactionEntity.getAccountToId() == null || transactionEntity.getAccountToId().equals(Constants.NOT_SET)) { if (mAccountIdList.size() == 0) { // notify the user and exit. new MaterialDialog.Builder(getContext()) .title(R.string.warning) .content(R.string.no_accounts_available_for_selection) .positiveText(android.R.string.ok) .show(); return; } else { transactionEntity.setAccountToId(mAccountIdList.get(0)); } } // calculate AmountTo only if not set previously. if (transactionEntity.getAmountTo().isZero()) { Money amountTo = calculateAmountTo(); transactionEntity.setAmountTo(amountTo); } displayAmountTo(); } private void resetCategory() { // Reset the Sub/Category on the transaction. transactionEntity.setCategoryId(Constants.NOT_SET); transactionEntity.setSubcategoryId(Constants.NOT_SET); } private void showDate(Date dateTime) { // Constants.LONG_DATE_MEDIUM_DAY_PATTERN String format = "EEE, " + getUserDateFormat(); //String display = dateTime.toString(format); String display = dateTimeUtilsLazy.get().format(dateTime, format); viewHolder.dateTextView.setText(display); } private void showSplitCategoriesForm(String datasetName) { // If there are no splits, use the current values for the initial split record. List<ISplitTransaction> splitsToShow = getSplitTransactions(); if (getSplitTransactions().isEmpty()) { ISplitTransaction currentTransaction = createSplitFromTransaction(); splitsToShow.add(currentTransaction); } Intent intent = new Intent(getContext(), SplitCategoriesActivity.class); intent.putExtra(SplitCategoriesActivity.KEY_DATASET_TYPE, datasetName); intent.putExtra(SplitCategoriesActivity.KEY_TRANSACTION_TYPE, transactionEntity.getTransactionType().getCode()); intent.putExtra(SplitCategoriesActivity.KEY_SPLIT_TRANSACTION, Parcels.wrap(splitsToShow)); intent.putExtra(SplitCategoriesActivity.KEY_SPLIT_TRANSACTION_DELETED, Parcels.wrap(mSplitTransactionsDeleted)); Integer fromCurrencyId = getSourceCurrencyId(); intent.putExtra(SplitCategoriesActivity.KEY_CURRENCY_ID, fromCurrencyId); getActivity().startActivityForResult(intent, RequestCodes.SPLIT_TX); } /** * If the user wants to reset the Split but there are multiple records, show the notice * that the records must be adjusted manually. */ private void showSplitResetNotice() { new MaterialDialog.Builder(getContext()) .title(R.string.split_transaction) .content(R.string.split_reset_notice) .positiveText(android.R.string.ok) .show(); } private void setDate(Date dateTime) { setDirty(true); transactionEntity.setDate(dateTime); showDate(dateTime); } private void updateSplitButton() { // update Split button int buttonColour, buttonBackground; if (isSplitSelected()) { // buttonColour = R.color.button_foreground_active; buttonColour = R.color.md_accent; buttonBackground = R.color.md_primary; // #188: if there is a Category selected and we are switching to Split Categories. } else { buttonColour = R.color.button_foreground_inactive; buttonBackground = new UIHelper(getContext()).isUsingDarkTheme() ? R.color.button_background_inactive_dark : R.color.button_background_inactive_light; } viewHolder.splitButton.setTextColor(ContextCompat.getColor(getContext(), buttonColour)); viewHolder.splitButton.setBackgroundColor(ContextCompat.getColor(getContext(), buttonBackground)); } }