net.alexjf.tmm.fragments.ImmedTransactionEditorFragment.java Source code

Java tutorial

Introduction

Here is the source code for net.alexjf.tmm.fragments.ImmedTransactionEditorFragment.java

Source

/*******************************************************************************
 * Copyright (c) 2013 - Alexandre Jorge Fonseca (alexjf.net)
 * License: GPL v3 (http://www.gnu.org/licenses/gpl-3.0.txt)
 ******************************************************************************/
package net.alexjf.tmm.fragments;

import android.app.Activity;
import android.app.DatePickerDialog.OnDateSetListener;
import android.app.TimePickerDialog.OnTimeSetListener;
import android.content.Intent;
import android.content.res.Resources;
import android.graphics.drawable.Drawable;
import android.os.Bundle;
import android.os.Parcel;
import android.os.Parcelable;
import android.support.v4.app.Fragment;
import android.support.v4.app.FragmentManager;
import android.text.Editable;
import android.text.InputType;
import android.text.TextUtils;
import android.text.TextWatcher;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.View.OnClickListener;
import android.view.ViewGroup;
import android.widget.*;
import android.widget.CompoundButton.OnCheckedChangeListener;
import de.congrace.exp4j.Calculable;
import de.congrace.exp4j.ExpressionBuilder;
import net.alexjf.tmm.R;
import net.alexjf.tmm.activities.CategoryListActivity;
import net.alexjf.tmm.activities.MoneyNodeListActivity;
import net.alexjf.tmm.domain.Category;
import net.alexjf.tmm.domain.ImmediateTransaction;
import net.alexjf.tmm.domain.MoneyNode;
import net.alexjf.tmm.exceptions.DatabaseException;
import net.alexjf.tmm.utils.DrawableResolver;
import net.alexjf.tmm.utils.Utils;
import net.alexjf.tmm.views.SelectorButton;
import net.alexjf.tmm.views.SignToggleButton;
import org.joda.money.CurrencyUnit;
import org.joda.money.Money;

import java.math.RoundingMode;
import java.text.DateFormat;
import java.text.ParseException;
import java.util.ArrayList;
import java.util.Date;
import java.util.GregorianCalendar;

public class ImmedTransactionEditorFragment extends Fragment implements OnDateSetListener, OnTimeSetListener {
    private static final String KEY_CURRENTTRANSACTION = "currentTransaction";
    private static final String KEY_SELECTEDCATEGORY = "selectedCategory";
    private static final String KEY_SELECTEDTRANSFERMONEYNODE = "selectedTransferMoneyNode";

    private static final String TAG_DATEPICKER = "datePicker";
    private static final String TAG_TIMEPICKER = "timePicker";

    private static final int REQCODE_CATEGORYCHOOSE = 0;
    private static final int REQCODE_MONEYNODECHOOSE = 1;

    private OnImmediateTransactionEditListener listener;
    private Category selectedCategory;
    private MoneyNode selectedTransferMoneyNode;
    private ImmediateTransaction transferTransaction;

    private ImmediateTransaction transaction;
    private MoneyNode currentMoneyNode;

    private TimePickerFragment timePicker;
    private DatePickerFragment datePicker;

    private EditText descriptionText;
    private SelectorButton categoryButton;
    private Button executionDateButton;
    private Button executionTimeButton;
    private SignToggleButton valueSignToggle;
    private EditText valueText;
    private TextView currencyTextView;
    private CheckBox transferCheck;
    private LinearLayout transferPanel;
    private TextView transferMoneyNodeLabel;
    private SelectorButton transferMoneyNodeButton;
    private LinearLayout transferConversionPanel;
    private EditText transferConversionAmountText;
    private TextView transferConversionCurrencyLabel;
    private Button addButton;

    private DateFormat dateFormat;
    private DateFormat timeFormat;

    private Bundle savedInstanceState;

    public ImmedTransactionEditorFragment() {
        dateFormat = DateFormat.getDateInstance();
        timeFormat = DateFormat.getTimeInstance(DateFormat.SHORT);
    }

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
        View v = inflater.inflate(R.layout.fragment_immedtransaction_editor, container, false);

        descriptionText = (EditText) v.findViewById(R.id.description_text);
        categoryButton = (SelectorButton) v.findViewById(R.id.category_button);
        executionDateButton = (Button) v.findViewById(R.id.executionDate_button);
        executionTimeButton = (Button) v.findViewById(R.id.executionTime_button);
        valueSignToggle = (SignToggleButton) v.findViewById(R.id.value_sign);
        valueText = (EditText) v.findViewById(R.id.value_text);
        currencyTextView = (TextView) v.findViewById(R.id.currency_label);
        transferCheck = (CheckBox) v.findViewById(R.id.transfer_check);
        transferPanel = (LinearLayout) v.findViewById(R.id.transfer_panel);
        transferMoneyNodeLabel = (TextView) v.findViewById(R.id.transfer_moneynode_label);
        transferMoneyNodeButton = (SelectorButton) v.findViewById(R.id.transfer_moneynode_button);
        transferConversionPanel = (LinearLayout) v.findViewById(R.id.transfer_conversion_panel);
        transferConversionAmountText = (EditText) v.findViewById(R.id.transfer_conversion_amount_value);
        transferConversionCurrencyLabel = (TextView) v.findViewById(R.id.transfer_conversion_amount_currency);
        addButton = (Button) v.findViewById(R.id.add_button);

        valueText.setRawInputType(InputType.TYPE_CLASS_NUMBER);

        FragmentManager fm = getFragmentManager();

        timePicker = (TimePickerFragment) fm.findFragmentByTag(TAG_TIMEPICKER);
        datePicker = (DatePickerFragment) fm.findFragmentByTag(TAG_DATEPICKER);

        if (timePicker == null) {
            timePicker = new TimePickerFragment();
        }

        if (datePicker == null) {
            datePicker = new DatePickerFragment();
        }

        timePicker.setListener(this);
        datePicker.setListener(this);

        categoryButton.setOnClickListener(new OnClickListener() {
            public void onClick(View view) {
                Intent intent = new Intent(view.getContext(), CategoryListActivity.class);
                intent.putExtra(CategoryListActivity.KEY_INTENTION, CategoryListActivity.INTENTION_SELECT);
                startActivityForResult(intent, REQCODE_CATEGORYCHOOSE);
            }
        });

        executionDateButton.setOnClickListener(new OnClickListener() {
            public void onClick(View view) {
                try {
                    datePicker.setDate(dateFormat.parse(executionDateButton.getText().toString()));
                } catch (ParseException e) {
                }
                datePicker.show(getFragmentManager(), TAG_DATEPICKER);
            }
        });

        executionTimeButton.setOnClickListener(new OnClickListener() {
            public void onClick(View view) {
                try {
                    timePicker.setTime(timeFormat.parse(executionTimeButton.getText().toString()));
                } catch (ParseException e) {
                }
                timePicker.show(getFragmentManager(), TAG_TIMEPICKER);
            }
        });

        valueSignToggle.setOnChangeListener(new SignToggleButton.SignToggleButtonListener() {
            @Override
            public void onChange(boolean isPositive) {
                if (isPositive) {
                    transferMoneyNodeLabel.setText(getResources().getString(R.string.transfer_moneynode_from));
                } else {
                    transferMoneyNodeLabel.setText(getResources().getString(R.string.transfer_moneynode_to));
                }
            }
        });

        transferCheck.setOnCheckedChangeListener(new OnCheckedChangeListener() {
            public void onCheckedChanged(CompoundButton view, boolean checked) {
                if (checked) {
                    transferPanel.setVisibility(View.VISIBLE);
                } else {
                    transferPanel.setVisibility(View.GONE);
                }
            }

            ;
        });

        transferMoneyNodeButton.setOnClickListener(new OnClickListener() {
            public void onClick(View view) {
                Intent intent = new Intent(view.getContext(), MoneyNodeListActivity.class);
                intent.putExtra(MoneyNodeListActivity.KEY_INTENTION, MoneyNodeListActivity.INTENTION_SELECT);

                ArrayList<MoneyNode> excludedMoneyNodes = new ArrayList<MoneyNode>();
                excludedMoneyNodes.add(currentMoneyNode);
                intent.putParcelableArrayListExtra(MoneyNodeListActivity.KEY_EXCLUDE, excludedMoneyNodes);
                startActivityForResult(intent, REQCODE_MONEYNODECHOOSE);
            }
        });

        addButton.setOnClickListener(new OnClickListener() {
            public void onClick(View view) {
                if (!validateInputFields()) {
                    return;
                }

                String description = descriptionText.getText().toString().trim();
                boolean isTransfer = transferCheck.isChecked();

                Date executionDate;
                Date executionTime;
                Date executionDateTime;
                try {
                    executionDate = dateFormat.parse(executionDateButton.getText().toString());
                    executionTime = timeFormat.parse(executionTimeButton.getText().toString());
                } catch (ParseException e) {
                    executionTime = executionDate = new Date();
                }

                executionDateTime = Utils.combineDateTime(executionDate, executionTime);

                Money value;

                try {
                    Calculable calc = new ExpressionBuilder(valueText.getText().toString()).build();
                    value = Money.of(currentMoneyNode.getCurrency(), calc.calculate(), RoundingMode.HALF_EVEN);
                } catch (Exception e) {
                    Log.e("TMM", "Error calculating value expression: " + e.getMessage(), e);
                    value = Money.zero(currentMoneyNode.getCurrency());
                }

                if (valueSignToggle.isNegative()) {
                    value = value.negated();
                }

                Money currencyConversionValue = null;

                // If an amount was entered for conversion to other currency, set
                // value of transfer transaction to this amount
                if (isTransfer && !TextUtils.isEmpty(transferConversionAmountText.getText())) {
                    try {
                        Calculable calc = new ExpressionBuilder(transferConversionAmountText.getText().toString())
                                .build();
                        currencyConversionValue = Money.of(selectedTransferMoneyNode.getCurrency(),
                                calc.calculate(), RoundingMode.HALF_EVEN);
                        currencyConversionValue = currencyConversionValue.abs();
                    } catch (Exception e) {
                        Log.e("TMM", "Error calculating conversion amount expression: " + e.getMessage(), e);
                        currencyConversionValue = Money.zero(selectedTransferMoneyNode.getCurrency());
                    }

                    if (!valueSignToggle.isNegative()) {
                        currencyConversionValue = currencyConversionValue.negated();
                    }
                }

                // If we are creating a new transaction
                if (transaction == null) {
                    ImmediateTransaction newTransaction = new ImmediateTransaction(currentMoneyNode, value,
                            description, selectedCategory, executionDateTime);

                    // If this new transaction is a transfer
                    if (isTransfer && selectedTransferMoneyNode != null) {
                        ImmediateTransaction otherTransaction = new ImmediateTransaction(newTransaction,
                                selectedTransferMoneyNode);

                        // If a value was specified for the conversion to other currency,
                        // use that value instead of the negation of current one
                        if (currencyConversionValue != null) {
                            otherTransaction.setValue(currencyConversionValue);
                        }

                        newTransaction.setTransferTransaction(otherTransaction);
                        otherTransaction.setTransferTransaction(newTransaction);
                    }

                    listener.onImmediateTransactionCreated(newTransaction);
                }
                // If we are updating an existing transaction
                else {
                    ImmedTransactionEditOldInfo oldInfo = new ImmedTransactionEditOldInfo(transaction);
                    transaction.setDescription(description);
                    transaction.setCategory(selectedCategory);
                    transaction.setExecutionDate(executionDateTime);
                    transaction.setValue(value);

                    if (isTransfer && selectedTransferMoneyNode != null) {
                        ImmediateTransaction otherTransaction = null;

                        // If edited transaction wasn't part of a transfer and
                        // now is, create transfer transaction
                        if (transaction.getTransferTransaction() == null) {
                            otherTransaction = new ImmediateTransaction(transaction, selectedTransferMoneyNode);
                            transaction.setTransferTransaction(otherTransaction);
                            otherTransaction.setTransferTransaction(transaction);
                        }
                        // If edited transaction was already part of a
                        // transfer, update transfer transaction
                        else {
                            otherTransaction = transaction.getTransferTransaction();

                            otherTransaction.setMoneyNode(selectedTransferMoneyNode);
                            otherTransaction.setDescription(description);
                            otherTransaction.setCategory(selectedCategory);
                            otherTransaction.setExecutionDate(executionDateTime);
                            otherTransaction.setValue(value.negated());
                        }

                        // If a value was specified for the conversion to other currency,
                        // use that value instead of the negation of current one
                        if (currencyConversionValue != null) {
                            otherTransaction.setValue(currencyConversionValue);
                        }
                    }
                    // If edited transaction no longer is a transfer but
                    // was a transfer before, we need to remove the opposite
                    // transaction.
                    else if (transaction.getTransferTransaction() != null) {
                        transaction.setTransferTransaction(null);
                    }
                    listener.onImmediateTransactionEdited(transaction, oldInfo);
                }
            }
        });

        this.savedInstanceState = savedInstanceState;

        return v;
    }

    public void onDatabaseReady() {
        if (savedInstanceState != null) {
            selectedCategory = savedInstanceState.getParcelable(KEY_SELECTEDCATEGORY);
            selectedTransferMoneyNode = savedInstanceState.getParcelable(KEY_SELECTEDTRANSFERMONEYNODE);
        }

        updateTransactionFields();

        this.savedInstanceState = null;
    }

    @Override
    public void onDateSet(DatePicker view, int year, int month, int day) {
        GregorianCalendar calendar = new GregorianCalendar(year, month, day);
        executionDateButton.setText(dateFormat.format(calendar.getTime()));
    }

    @Override
    public void onTimeSet(TimePicker view, int hours, int minutes) {
        GregorianCalendar calendar = new GregorianCalendar(0, 0, 0, hours, minutes);
        executionTimeButton.setText(timeFormat.format(calendar.getTime()));
    }

    @Override
    public void onSaveInstanceState(Bundle outState) {
        outState.putParcelable(KEY_SELECTEDCATEGORY, selectedCategory);
        outState.putParcelable(KEY_SELECTEDTRANSFERMONEYNODE, selectedTransferMoneyNode);
        super.onSaveInstanceState(outState);
    }

    @Override
    public void onAttach(Activity activity) {
        super.onAttach(activity);
        try {
            listener = (OnImmediateTransactionEditListener) activity;
        } catch (ClassCastException e) {
            throw new ClassCastException(
                    activity.toString() + " must implement OnImmediateTransactionEditListener");
        }
    }

    @Override
    public void onActivityResult(int requestCode, int resultCode, Intent data) {
        if (resultCode == Activity.RESULT_OK) {
            if (requestCode == REQCODE_CATEGORYCHOOSE) {
                selectedCategory = (Category) data.getParcelableExtra(Category.KEY_CATEGORY);
                updateCategoryFields();
            } else if (requestCode == REQCODE_MONEYNODECHOOSE) {
                selectedTransferMoneyNode = (MoneyNode) data.getParcelableExtra(MoneyNode.KEY_MONEYNODE);
                updateTransferFields();
            }
        }
    }

    /**
     * @return the transaction
     */
    public ImmediateTransaction getTransaction() {
        return transaction;
    }

    /**
     * @param trans the transaction to set
     */
    public void setTransaction(ImmediateTransaction trans) {
        ImmediateTransaction prevTransaction = this.transaction;
        this.transaction = trans;

        if (trans != null) {
            setCurrentMoneyNode(trans.getMoneyNode());
        }

        if (prevTransaction != transaction) {
            updateTransactionFields();
        }
    }

    /**
     * @return the currentMoneyNode
     */
    public MoneyNode getCurrentMoneyNode() {
        return currentMoneyNode;
    }

    /**
     * @param currentMoneyNode the currentMoneyNode to set
     */
    public void setCurrentMoneyNode(MoneyNode currentMoneyNode) {
        MoneyNode prevMoneyNode = this.currentMoneyNode;
        this.currentMoneyNode = currentMoneyNode;

        if (prevMoneyNode != currentMoneyNode) {
            updateTransactionFields();
        }
    }

    private void updateTransactionFields() {
        // If we are adding a new node, reset all fields
        if (transaction == null) {
            descriptionText.setText("");
            executionDateButton.setText(dateFormat.format(new Date()));
            executionTimeButton.setText(timeFormat.format(new Date()));
            valueText.setText("");
            valueSignToggle.setNegative();
            addButton.setText(R.string.add);
            // If we are editing a node, fill fields with current information
        } else {
            try {
                transaction.load();
                descriptionText.setText(transaction.getDescription());
                executionDateButton.setText(dateFormat.format(transaction.getExecutionDate()));
                executionTimeButton.setText(timeFormat.format(transaction.getExecutionDate()));
                Money value = transaction.getValue();
                valueText.setText(value.abs().getAmount().toString());
                valueSignToggle.setToNumberSign(value.getAmount());
                addButton.setText(R.string.edit);
            } catch (DatabaseException e) {
                Log.e("TMM", "Error loading transaction", e);
            }
        }

        if (currentMoneyNode != null) {
            try {
                currentMoneyNode.load();
                currencyTextView.setText(currentMoneyNode.getCurrency().getCurrencyCode());
            } catch (DatabaseException e) {
                Log.e("TMM", "Error loading money node", e);
            }
        }

        updateCategoryFields();
        updateTransferFields();
    }

    private void updateCategoryFields() {
        if (selectedCategory == null && transaction != null) {
            selectedCategory = transaction.getCategory();
        }

        if (selectedCategory == null) {
            categoryButton.setText(R.string.category_nonselected);
            categoryButton.setDrawableId(0);
        } else {
            try {
                selectedCategory.load();
                categoryButton.setText(selectedCategory.getName());
                int drawableId = DrawableResolver.getInstance().getDrawableId(selectedCategory.getIcon());
                categoryButton.setDrawableId(drawableId);
                categoryButton.setError(false);
            } catch (DatabaseException e) {
                Log.e("TMM", e.getMessage(), e);
            }
        }
    }

    private void updateTransferFields() {
        if (selectedTransferMoneyNode == null && transaction != null) {
            transferTransaction = transaction.getTransferTransaction();

            if (transferTransaction != null) {
                try {
                    transferTransaction.load();
                    selectedTransferMoneyNode = transferTransaction.getMoneyNode();
                } catch (DatabaseException e) {
                    Log.e("TMMT", "Error loading transfer transaction", e);
                }
            }
        }

        if (selectedTransferMoneyNode == null) {
            transferCheck.setChecked(false);
            transferMoneyNodeButton.setText(R.string.moneynode_nonselected);
            transferMoneyNodeButton.setDrawableId(0);
        } else {
            try {
                selectedTransferMoneyNode.load();
                currentMoneyNode.load();

                transferCheck.setChecked(true);
                transferMoneyNodeButton.setText(selectedTransferMoneyNode.getName());
                int drawableId = DrawableResolver.getInstance().getDrawableId(selectedTransferMoneyNode.getIcon());
                transferMoneyNodeButton.setDrawableId(drawableId);
                transferMoneyNodeButton.setError(false);

                if (!selectedTransferMoneyNode.getCurrency().equals(currentMoneyNode.getCurrency())) {
                    transferConversionPanel.setVisibility(View.VISIBLE);
                    transferConversionAmountText.setText("");
                    transferConversionCurrencyLabel
                            .setText(selectedTransferMoneyNode.getCurrency().getCurrencyCode());
                } else {
                    transferConversionPanel.setVisibility(View.GONE);
                }

                if (transferTransaction != null) {
                    transferTransaction.load();
                    transferConversionAmountText
                            .setText(transferTransaction.getValue().getAmount().abs().toString());
                }

            } catch (DatabaseException e) {
                Log.e("TMM", "Unable to load transfer transaction", e);
            }
        }
    }

    private boolean validateInputFields() {
        boolean error = false;

        Resources res = getResources();
        Drawable errorDrawable = res.getDrawable(R.drawable.indicator_input_error);
        errorDrawable.setBounds(0, 0, errorDrawable.getIntrinsicWidth(), errorDrawable.getIntrinsicHeight());
        String value = valueText.getText().toString();

        if (TextUtils.isEmpty(value)) {
            valueText.setError(res.getString(R.string.error_trans_value_unspecified), errorDrawable);
            error = true;
        } else {
            try {
                new ExpressionBuilder(value).build();
            } catch (Exception e) {
                valueText.setError(res.getString(R.string.error_trans_value_invalid), errorDrawable);
                error = true;
            }
        }

        if (selectedCategory == null) {
            categoryButton.setError(true);
            error = true;
        }

        if (transferCheck.isChecked()) {
            if (selectedTransferMoneyNode == null) {
                transferMoneyNodeButton.setError(true);
                error = true;
            } else {
                CurrencyUnit thisTransactionCurrency = currentMoneyNode.getCurrency();
                CurrencyUnit otherTransactionCurrency = selectedTransferMoneyNode.getCurrency();

                if (!thisTransactionCurrency.equals(otherTransactionCurrency)) {
                    String conversionAmount = transferConversionAmountText.getText().toString();

                    if (TextUtils.isEmpty(conversionAmount)) {
                        transferConversionAmountText.setError(
                                res.getString(R.string.error_trans_other_currency_unspecified), errorDrawable);
                        error = true;
                    } else {
                        try {
                            new ExpressionBuilder(conversionAmount).build();
                        } catch (Exception e) {
                            transferConversionAmountText.setError(res.getString(R.string.error_trans_value_invalid),
                                    errorDrawable);
                            error = true;
                        }
                    }
                }
            }
        }

        return !error;
    }

    public static class ImmedTransactionEditOldInfo implements Parcelable {
        public static final String KEY_OLDINFO = "oldImmedTransactionInfo";
        private static final DateFormat dateFormat = DateFormat.getDateTimeInstance();

        private String description;
        private Category category;
        private Date executionDate;
        private Money value;
        private ImmediateTransaction transferTransaction;

        public ImmedTransactionEditOldInfo(ImmediateTransaction trans) {
            this(trans.getDescription(), trans.getCategory(), trans.getExecutionDate(), trans.getValue(),
                    trans.getTransferTransaction());
        }

        public ImmedTransactionEditOldInfo(String description, Category category, Date executionDate, Money value,
                ImmediateTransaction transferTransaction) {
            this.description = description;
            this.category = category;
            this.executionDate = executionDate;
            this.value = value;
            this.transferTransaction = transferTransaction;
        }

        public String getDescription() {
            return description;
        }

        public Category getCategory() {
            return category;
        }

        public Date getExecutionDate() {
            return executionDate;
        }

        public Money getValue() {
            return value;
        }

        public ImmediateTransaction getTransferTransaction() {
            return transferTransaction;
        }

        public void writeToParcel(Parcel out, int flags) {
            out.writeString(description);
            out.writeParcelable(category, flags);
            out.writeString(dateFormat.format(executionDate));
            out.writeString(value.toString());
            out.writeParcelable(transferTransaction, flags);
        }

        public int describeContents() {
            return 0;
        }

        public static final Parcelable.Creator<ImmedTransactionEditOldInfo> CREATOR = new Parcelable.Creator<ImmedTransactionEditOldInfo>() {
            public ImmedTransactionEditOldInfo createFromParcel(Parcel in) {
                String description = in.readString();
                Category category = (Category) in.readParcelable(Category.class.getClassLoader());
                Date date = null;
                try {
                    date = dateFormat.parse(in.readString());
                } catch (ParseException e) {
                    Log.e("TMM", e.getMessage(), e);
                }
                Money value = Money.parse(in.readString());
                ImmediateTransaction transferTransaction = (ImmediateTransaction) in
                        .readParcelable(ImmediateTransaction.class.getClassLoader());
                return new ImmedTransactionEditOldInfo(description, category, date, value, transferTransaction);
            }

            public ImmedTransactionEditOldInfo[] newArray(int size) {
                return new ImmedTransactionEditOldInfo[size];
            }
        };
    }

    public interface OnImmediateTransactionEditListener {
        public void onImmediateTransactionCreated(ImmediateTransaction transaction);

        public void onImmediateTransactionEdited(ImmediateTransaction transaction,
                ImmedTransactionEditOldInfo oldInfo);
    }
}