org.gnucash.android.ui.transaction.dialog.SplitEditorDialogFragment.java Source code

Java tutorial

Introduction

Here is the source code for org.gnucash.android.ui.transaction.dialog.SplitEditorDialogFragment.java

Source

/*
 * Copyright (c) 2014 Ngewi Fet <ngewif@gmail.com>
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.gnucash.android.ui.transaction.dialog;

import android.database.Cursor;
import android.os.Bundle;
import android.support.v4.app.DialogFragment;
import android.support.v4.widget.SimpleCursorAdapter;
import android.text.Editable;
import android.text.TextWatcher;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.view.WindowManager;
import android.widget.*;
import org.gnucash.android.R;
import org.gnucash.android.db.AccountsDbAdapter;
import org.gnucash.android.db.DatabaseSchema;
import org.gnucash.android.db.SplitsDbAdapter;
import org.gnucash.android.model.*;
import org.gnucash.android.ui.UxArgument;
import org.gnucash.android.ui.transaction.TransactionFormFragment;
import org.gnucash.android.ui.transaction.TransactionsActivity;
import org.gnucash.android.ui.util.AmountInputFormatter;
import org.gnucash.android.ui.util.TransactionTypeToggleButton;
import org.gnucash.android.util.QualifiedAccountNameCursorAdapter;

import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.Currency;
import java.util.List;
import java.util.UUID;

/**
 * Dialog for editing the splits in a transaction
 *
 * @author Ngewi Fet <ngewif@gmail.com>
 */
public class SplitEditorDialogFragment extends DialogFragment {

    private LinearLayout mSplitsLinearLayout;
    private TextView mImbalanceTextView;
    private Button mAddSplit;
    private Button mSaveButton;
    private Button mCancelButton;

    private AccountsDbAdapter mAccountsDbAdapter;
    private SplitsDbAdapter mSplitsDbAdapter;
    private Cursor mCursor;
    private SimpleCursorAdapter mCursorAdapter;
    private List<View> mSplitItemViewList;
    private String mAccountUID;

    private BalanceTextWatcher mBalanceUpdater = new BalanceTextWatcher();
    private BigDecimal mBaseAmount = BigDecimal.ZERO;

    private List<String> mRemovedSplitUIDs = new ArrayList<String>();

    /**
     * Create and return a new instance of the fragment with the appropriate paramenters
     * @param baseAmountString String with base amount which is being split
     * @return New instance of SplitEditorDialogFragment
     */
    public static SplitEditorDialogFragment newInstance(String baseAmountString) {
        SplitEditorDialogFragment fragment = new SplitEditorDialogFragment();
        Bundle args = new Bundle();
        args.putString(UxArgument.AMOUNT_STRING, baseAmountString);
        fragment.setArguments(args);
        return fragment;
    }

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
        View view = inflater.inflate(R.layout.dialog_split_editor, container, false);
        mSplitsLinearLayout = (LinearLayout) view.findViewById(R.id.split_list_layout);

        mImbalanceTextView = (TextView) view.findViewById(R.id.imbalance_textview);

        mAddSplit = (Button) view.findViewById(R.id.btn_add_split);
        mSaveButton = (Button) view.findViewById(R.id.btn_save);
        mCancelButton = (Button) view.findViewById(R.id.btn_cancel);
        return view;
    }

    @Override
    public void onActivityCreated(Bundle savedInstanceState) {
        super.onActivityCreated(savedInstanceState);
        getDialog().getWindow().setLayout(WindowManager.LayoutParams.MATCH_PARENT,
                WindowManager.LayoutParams.MATCH_PARENT);

        getDialog().setTitle("Transaction splits");

        initArgs();
        mSplitItemViewList = new ArrayList<View>();
        mSplitsDbAdapter = new SplitsDbAdapter(getActivity());

        //we are editing splits for a new transaction.
        // But the user may have already created some splits before. Let's check
        List<Split> splitList = ((TransactionFormFragment) getTargetFragment()).getSplitList();
        if (!splitList.isEmpty()) {
            //aha! there are some splits. Let's load those instead
            loadSplitViews(splitList);
        } else {
            final Currency currency = Currency.getInstance(mAccountsDbAdapter.getCurrencyCode(mAccountUID));
            Split split = new Split(new Money(mBaseAmount, currency), mAccountUID);
            AccountType accountType = mAccountsDbAdapter.getAccountType(mAccountUID);
            TransactionType transactionType = Transaction.getTypeForBalance(accountType, mBaseAmount.signum() < 0);
            split.setType(transactionType);
            View view = addSplitView(split);
            view.findViewById(R.id.input_accounts_spinner).setEnabled(false);
            view.findViewById(R.id.btn_remove_split).setVisibility(View.GONE);
        }

        setListeners();
        updateTotal();
    }

    private void loadSplitViews(List<Split> splitList) {
        for (Split split : splitList) {
            addSplitView(split);
        }
    }

    /**
     * Add a split view and initialize it with <code>split</code>
     * @param split Split to initialize the contents to
     * @return Returns the split view which was added
     */
    private View addSplitView(Split split) {
        LayoutInflater layoutInflater = getActivity().getLayoutInflater();
        View splitView = layoutInflater.inflate(R.layout.item_split_entry, mSplitsLinearLayout, false);
        mSplitsLinearLayout.addView(splitView, 0);
        bindSplitView(splitView, split);
        mSplitItemViewList.add(splitView);
        return splitView;
    }

    /**
     * Extracts arguments passed to the view and initializes necessary adapters and cursors
     */
    private void initArgs() {
        mAccountsDbAdapter = new AccountsDbAdapter(getActivity());

        Bundle args = getArguments();
        mAccountUID = ((TransactionsActivity) getActivity()).getCurrentAccountUID();
        mBaseAmount = new BigDecimal(args.getString(UxArgument.AMOUNT_STRING));

        String conditions = "(" //+ AccountEntry._ID + " != " + mAccountId + " AND "
                + DatabaseSchema.AccountEntry.COLUMN_CURRENCY + " = '"
                + mAccountsDbAdapter.getCurrencyCode(mAccountUID) + "' AND "
                + DatabaseSchema.AccountEntry.COLUMN_UID + " != '" + mAccountsDbAdapter.getGnuCashRootAccountUID()
                + "' AND " + DatabaseSchema.AccountEntry.COLUMN_PLACEHOLDER + " = 0" + ")";
        mCursor = mAccountsDbAdapter.fetchAccountsOrderedByFullName(conditions);
    }

    /**
     * Binds the different UI elements of an inflated list view to corresponding actions
     * @param splitView Split item view
     * @param split {@link org.gnucash.android.model.Split} to use to populate the view
     */
    private void bindSplitView(final View splitView, Split split) {
        EditText splitMemoEditText = (EditText) splitView.findViewById(R.id.input_split_memo);
        final EditText splitAmountEditText = (EditText) splitView.findViewById(R.id.input_split_amount);
        ImageButton removeSplitButton = (ImageButton) splitView.findViewById(R.id.btn_remove_split);
        Spinner accountsSpinner = (Spinner) splitView.findViewById(R.id.input_accounts_spinner);
        final TextView splitCurrencyTextView = (TextView) splitView.findViewById(R.id.split_currency_symbol);
        final TextView splitUidTextView = (TextView) splitView.findViewById(R.id.split_uid);
        final TransactionTypeToggleButton splitTypeButton = (TransactionTypeToggleButton) splitView
                .findViewById(R.id.btn_split_type);

        splitAmountEditText.addTextChangedListener(new AmountInputFormatter(splitAmountEditText));

        removeSplitButton.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                mRemovedSplitUIDs.add(splitUidTextView.getText().toString());
                mSplitsLinearLayout.removeView(splitView);
                mSplitItemViewList.remove(splitView);
                updateTotal();
            }
        });

        updateTransferAccountsList(accountsSpinner);
        accountsSpinner.setOnItemSelectedListener(new TypeButtonLabelUpdater(splitTypeButton));

        Currency accountCurrency = Currency.getInstance(mAccountsDbAdapter.getCurrencyCode(mAccountUID));
        splitCurrencyTextView.setText(accountCurrency.getSymbol());
        splitTypeButton.setAmountFormattingListener(splitAmountEditText, splitCurrencyTextView);
        splitTypeButton.setChecked(mBaseAmount.signum() > 0);
        splitUidTextView.setText(UUID.randomUUID().toString());

        if (split != null) {
            splitAmountEditText.setText(split.getAmount().toPlainString());
            splitMemoEditText.setText(split.getMemo());
            splitUidTextView.setText(split.getUID());
            String splitAccountUID = split.getAccountUID();
            setSelectedTransferAccount(mAccountsDbAdapter.getAccountID(splitAccountUID), accountsSpinner);
            splitTypeButton.setAccountType(mAccountsDbAdapter.getAccountType(splitAccountUID));
            splitTypeButton.setChecked(split.getType());
        }

        //put these balance update triggers last last so as to avoid computing while still loading
        splitAmountEditText.addTextChangedListener(mBalanceUpdater);
        splitTypeButton.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                updateTotal();
            }
        });
    }

    /**
     * Updates the spinner to the selected transfer account
     * @param accountId Database ID of the transfer account
     */
    private void setSelectedTransferAccount(long accountId, final Spinner accountsSpinner) {
        for (int pos = 0; pos < mCursorAdapter.getCount(); pos++) {
            if (mCursorAdapter.getItemId(pos) == accountId) {
                accountsSpinner.setSelection(pos);
                break;
            }
        }
    }

    /**
     * Updates the list of possible transfer accounts.
     * Only accounts with the same currency can be transferred to
     */
    private void updateTransferAccountsList(Spinner transferAccountSpinner) {

        mCursorAdapter = new QualifiedAccountNameCursorAdapter(getActivity(), android.R.layout.simple_spinner_item,
                mCursor);
        mCursorAdapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
        transferAccountSpinner.setAdapter(mCursorAdapter);
    }

    /**
     * Attaches listeners for the buttons of the dialog
     */
    protected void setListeners() {
        mCancelButton.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                dismiss();
            }
        });

        mSaveButton.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                List<Split> splitList = extractSplitsFromView();
                ((TransactionFormFragment) getTargetFragment()).setSplitList(splitList, mRemovedSplitUIDs);

                dismiss();
            }
        });

        mAddSplit.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                addSplitView(null);
            }
        });
    }

    /**
     * Extracts the input from the views and builds {@link org.gnucash.android.model.Split}s to correspond to the input.
     * @return List of {@link org.gnucash.android.model.Split}s represented in the view
     */
    private List<Split> extractSplitsFromView() {
        List<Split> splitList = new ArrayList<Split>();
        for (View splitView : mSplitItemViewList) {
            EditText splitMemoEditText = (EditText) splitView.findViewById(R.id.input_split_memo);
            EditText splitAmountEditText = (EditText) splitView.findViewById(R.id.input_split_amount);
            Spinner accountsSpinner = (Spinner) splitView.findViewById(R.id.input_accounts_spinner);
            TextView splitUidTextView = (TextView) splitView.findViewById(R.id.split_uid);
            TransactionTypeToggleButton splitTypeButton = (TransactionTypeToggleButton) splitView
                    .findViewById(R.id.btn_split_type);

            BigDecimal amountBigDecimal = TransactionFormFragment
                    .parseInputToDecimal(splitAmountEditText.getText().toString());
            String currencyCode = mAccountsDbAdapter.getCurrencyCode(accountsSpinner.getSelectedItemId());
            String accountUID = mAccountsDbAdapter.getAccountUID(accountsSpinner.getSelectedItemId());
            Money amount = new Money(amountBigDecimal, Currency.getInstance(currencyCode));
            Split split = new Split(amount, accountUID);
            split.setMemo(splitMemoEditText.getText().toString());
            split.setType(splitTypeButton.getTransactionType());
            split.setUID(splitUidTextView.getText().toString().trim());
            splitList.add(split);
        }
        return splitList;
    }

    /**
     * Updates the displayed total for the transaction.
     * Computes the total of the splits, the unassigned balance and the split sum
     */
    private void updateTotal() {
        List<Split> splitList = extractSplitsFromView();
        String currencyCode = mAccountsDbAdapter.getCurrencyCode(mAccountUID);
        Money splitSum = Money.createZeroInstance(currencyCode);

        for (Split split : splitList) {
            Money amount = split.getAmount().absolute();
            if (split.getType() == TransactionType.DEBIT)
                splitSum = splitSum.subtract(amount);
            else
                splitSum = splitSum.add(amount);
        }

        TransactionsActivity.displayBalance(mImbalanceTextView, splitSum);
    }

    @Override
    public void onDestroy() {
        super.onDestroy();
        mAccountsDbAdapter.close();
        mSplitsDbAdapter.close();
    }

    /**
     * Updates the displayed balance of the accounts when the amount of a split is changed
     */
    private class BalanceTextWatcher implements TextWatcher {

        @Override
        public void beforeTextChanged(CharSequence charSequence, int i, int i2, int i3) {

        }

        @Override
        public void onTextChanged(CharSequence charSequence, int i, int i2, int i3) {

        }

        @Override
        public void afterTextChanged(Editable editable) {
            updateTotal();
        }
    }

    /**
     * Updates the account type for the TransactionTypeButton when the selected account is changed in the spinner
     */
    private class TypeButtonLabelUpdater implements AdapterView.OnItemSelectedListener {
        TransactionTypeToggleButton mTypeToggleButton;

        public TypeButtonLabelUpdater(TransactionTypeToggleButton typeToggleButton) {
            this.mTypeToggleButton = typeToggleButton;
        }

        @Override
        public void onItemSelected(AdapterView<?> parentView, View selectedItemView, int position, long id) {
            AccountType accountType = mAccountsDbAdapter.getAccountType(id);
            mTypeToggleButton.setAccountType(accountType);
        }

        @Override
        public void onNothingSelected(AdapterView<?> adapterView) {

        }
    }
}