org.gnucash.android.ui.budget.BudgetFormFragment.java Source code

Java tutorial

Introduction

Here is the source code for org.gnucash.android.ui.budget.BudgetFormFragment.java

Source

/*
 * Copyright (c) 2015 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.budget;

import android.app.Activity;
import android.content.Intent;
import android.database.Cursor;
import android.inputmethodservice.KeyboardView;
import android.os.Bundle;
import android.support.annotation.Nullable;
import android.support.design.widget.TextInputLayout;
import android.support.v4.app.Fragment;
import android.support.v7.app.ActionBar;
import android.support.v7.app.AppCompatActivity;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
import android.widget.Button;
import android.widget.EditText;
import android.widget.Spinner;
import android.widget.TextView;
import android.widget.Toast;

import com.codetroopers.betterpickers.calendardatepicker.CalendarDatePickerDialogFragment;
import com.codetroopers.betterpickers.recurrencepicker.EventRecurrence;
import com.codetroopers.betterpickers.recurrencepicker.EventRecurrenceFormatter;
import com.codetroopers.betterpickers.recurrencepicker.RecurrencePickerDialogFragment;

import org.gnucash.android.R;
import org.gnucash.android.db.DatabaseSchema;
import org.gnucash.android.db.adapter.AccountsDbAdapter;
import org.gnucash.android.db.adapter.BudgetsDbAdapter;
import org.gnucash.android.db.adapter.DatabaseAdapter;
import org.gnucash.android.model.Budget;
import org.gnucash.android.model.BudgetAmount;
import org.gnucash.android.model.Commodity;
import org.gnucash.android.model.Money;
import org.gnucash.android.model.Recurrence;
import org.gnucash.android.ui.common.FormActivity;
import org.gnucash.android.ui.common.UxArgument;
import org.gnucash.android.ui.transaction.TransactionFormFragment;
import org.gnucash.android.ui.util.RecurrenceParser;
import org.gnucash.android.ui.util.RecurrenceViewClickListener;
import org.gnucash.android.ui.util.widget.CalculatorEditText;
import org.gnucash.android.util.QualifiedAccountNameCursorAdapter;

import java.math.BigDecimal;
import java.sql.Timestamp;
import java.text.ParseException;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Date;
import java.util.GregorianCalendar;

import butterknife.BindView;
import butterknife.ButterKnife;
import butterknife.OnClick;

/**
 * Fragment for creating or editing Budgets
 */
public class BudgetFormFragment extends Fragment implements RecurrencePickerDialogFragment.OnRecurrenceSetListener,
        CalendarDatePickerDialogFragment.OnDateSetListener {

    public static final int REQUEST_EDIT_BUDGET_AMOUNTS = 0xBA;
    @BindView(R.id.input_budget_name)
    EditText mBudgetNameInput;
    @BindView(R.id.input_description)
    EditText mDescriptionInput;
    @BindView(R.id.input_recurrence)
    TextView mRecurrenceInput;
    @BindView(R.id.name_text_input_layout)
    TextInputLayout mNameTextInputLayout;
    @BindView(R.id.calculator_keyboard)
    KeyboardView mKeyboardView;
    @BindView(R.id.input_budget_amount)
    CalculatorEditText mBudgetAmountInput;
    @BindView(R.id.input_budget_account_spinner)
    Spinner mBudgetAccountSpinner;
    @BindView(R.id.btn_add_budget_amount)
    Button mAddBudgetAmount;
    @BindView(R.id.input_start_date)
    TextView mStartDateInput;
    @BindView(R.id.budget_amount_layout)
    View mBudgetAmountLayout;

    EventRecurrence mEventRecurrence = new EventRecurrence();
    String mRecurrenceRule;

    private BudgetsDbAdapter mBudgetsDbAdapter;

    private Budget mBudget;
    private Calendar mStartDate;
    private ArrayList<BudgetAmount> mBudgetAmounts;
    private AccountsDbAdapter mAccountsDbAdapter;
    private QualifiedAccountNameCursorAdapter mAccountsCursorAdapter;

    @Nullable
    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
        View view = inflater.inflate(R.layout.fragment_budget_form, container, false);
        ButterKnife.bind(this, view);

        view.findViewById(R.id.btn_remove_item).setVisibility(View.GONE);
        mBudgetAmountInput.bindListeners(mKeyboardView);
        mStartDateInput.setText(TransactionFormFragment.DATE_FORMATTER.format(mStartDate.getTime()));
        return view;
    }

    @Override
    public void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        mBudgetsDbAdapter = BudgetsDbAdapter.getInstance();
        mStartDate = Calendar.getInstance();
        mBudgetAmounts = new ArrayList<>();
        String conditions = "(" + DatabaseSchema.AccountEntry.COLUMN_HIDDEN + " = 0 )";
        mAccountsDbAdapter = AccountsDbAdapter.getInstance();
        Cursor accountCursor = mAccountsDbAdapter.fetchAccountsOrderedByFavoriteAndFullName(conditions, null);
        mAccountsCursorAdapter = new QualifiedAccountNameCursorAdapter(getActivity(), accountCursor);
    }

    @Override
    public void onActivityCreated(@Nullable Bundle savedInstanceState) {
        super.onActivityCreated(savedInstanceState);

        setHasOptionsMenu(true);

        mBudgetAccountSpinner.setAdapter(mAccountsCursorAdapter);
        String budgetUID = getArguments().getString(UxArgument.BUDGET_UID);
        if (budgetUID != null) { //if we are editing the budget
            initViews(mBudget = mBudgetsDbAdapter.getRecord(budgetUID));
        }
        ActionBar actionbar = ((AppCompatActivity) getActivity()).getSupportActionBar();
        assert actionbar != null;
        if (mBudget == null)
            actionbar.setTitle("Create Budget");
        else
            actionbar.setTitle("Edit Budget");

        mRecurrenceInput.setOnClickListener(
                new RecurrenceViewClickListener((AppCompatActivity) getActivity(), mRecurrenceRule, this));
    }

    /**
     * Initialize views when editing an existing budget
     * @param budget Budget to use to initialize the views
     */
    private void initViews(Budget budget) {
        mBudgetNameInput.setText(budget.getName());
        mDescriptionInput.setText(budget.getDescription());

        String recurrenceRuleString = budget.getRecurrence().getRuleString();
        mRecurrenceRule = recurrenceRuleString;
        mEventRecurrence.parse(recurrenceRuleString);
        mRecurrenceInput.setText(budget.getRecurrence().getRepeatString());

        mBudgetAmounts = (ArrayList<BudgetAmount>) budget.getCompactedBudgetAmounts();
        toggleAmountInputVisibility();
    }

    /**
     * Extracts the budget amounts from the form
     * <p>If the budget amount was input using the simple form, then read the values.<br>
     *     Else return the values gotten from the BudgetAmountEditor</p>
     * @return List of budget amounts
     */
    private ArrayList<BudgetAmount> extractBudgetAmounts() {
        BigDecimal value = mBudgetAmountInput.getValue();
        if (value == null)
            return mBudgetAmounts;

        if (mBudgetAmounts.isEmpty()) { //has not been set in budget amounts editor
            ArrayList<BudgetAmount> budgetAmounts = new ArrayList<>();
            Money amount = new Money(value, Commodity.DEFAULT_COMMODITY);
            String accountUID = mAccountsDbAdapter.getUID(mBudgetAccountSpinner.getSelectedItemId());
            BudgetAmount budgetAmount = new BudgetAmount(amount, accountUID);
            budgetAmounts.add(budgetAmount);
            return budgetAmounts;
        } else {
            return mBudgetAmounts;
        }
    }

    /**
     * Checks that this budget can be saved
     * Also sets the appropriate error messages on the relevant views
     * <p>For a budget to be saved, it needs to have a name, an amount and a schedule</p>
     * @return {@code true} if the budget can be saved, {@code false} otherwise
     */
    private boolean canSave() {
        if (mEventRecurrence.until != null && mEventRecurrence.until.length() > 0 || mEventRecurrence.count <= 0) {
            Toast.makeText(getActivity(), "Set a number periods in the recurrence dialog to save the budget",
                    Toast.LENGTH_SHORT).show();
            return false;
        }

        mBudgetAmounts = extractBudgetAmounts();
        String budgetName = mBudgetNameInput.getText().toString();
        boolean canSave = mRecurrenceRule != null && !budgetName.isEmpty() && !mBudgetAmounts.isEmpty();

        if (!canSave) {
            if (budgetName.isEmpty()) {
                mNameTextInputLayout.setError("A name is required");
                mNameTextInputLayout.setErrorEnabled(true);
            } else {
                mNameTextInputLayout.setErrorEnabled(false);
            }

            if (mBudgetAmounts.isEmpty()) {
                mBudgetAmountInput.setError("Enter an amount for the budget");
                Toast.makeText(getActivity(), "Add budget amounts in order to save the budget", Toast.LENGTH_SHORT)
                        .show();
            }

            if (mRecurrenceRule == null) {
                Toast.makeText(getActivity(), "Set a repeat pattern to create a budget!", Toast.LENGTH_SHORT)
                        .show();
            }
        }

        return canSave;
    }

    /**
     * Extracts the information from the form and saves the budget
     */
    private void saveBudget() {
        if (!canSave())
            return;
        String name = mBudgetNameInput.getText().toString().trim();

        if (mBudget == null) {
            mBudget = new Budget(name);
        } else {
            mBudget.setName(name);
        }

        // TODO: 22.10.2015 set the period num of the budget amount
        extractBudgetAmounts();
        mBudget.setBudgetAmounts(mBudgetAmounts);

        mBudget.setDescription(mDescriptionInput.getText().toString().trim());

        Recurrence recurrence = RecurrenceParser.parse(mEventRecurrence);
        recurrence.setPeriodStart(new Timestamp(mStartDate.getTimeInMillis()));
        mBudget.setRecurrence(recurrence);

        mBudgetsDbAdapter.addRecord(mBudget, DatabaseAdapter.UpdateMethod.insert);
        getActivity().finish();
    }

    @Override
    public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
        inflater.inflate(R.menu.default_save_actions, menu);
    }

    @Override
    public boolean onOptionsItemSelected(MenuItem item) {
        switch (item.getItemId()) {
        case R.id.menu_save:
            saveBudget();
            return true;
        }
        return false;
    }

    @OnClick(R.id.input_start_date)
    public void onClickBudgetStartDate(View v) {
        long dateMillis = 0;
        try {
            Date date = TransactionFormFragment.DATE_FORMATTER.parse(((TextView) v).getText().toString());
            dateMillis = date.getTime();
        } catch (ParseException e) {
            Log.e(getTag(), "Error converting input time to Date object");
        }
        Calendar calendar = Calendar.getInstance();
        calendar.setTimeInMillis(dateMillis);

        int year = calendar.get(Calendar.YEAR);
        int monthOfYear = calendar.get(Calendar.MONTH);
        int dayOfMonth = calendar.get(Calendar.DAY_OF_MONTH);
        CalendarDatePickerDialogFragment datePickerDialog = new CalendarDatePickerDialogFragment();
        datePickerDialog.setOnDateSetListener(BudgetFormFragment.this);
        datePickerDialog.setPreselectedDate(year, monthOfYear, dayOfMonth);
        datePickerDialog.show(getFragmentManager(), "date_picker_fragment");
    }

    @OnClick(R.id.btn_add_budget_amount)
    public void onOpenBudgetAmountEditor(View v) {
        Intent intent = new Intent(getActivity(), FormActivity.class);
        intent.putExtra(UxArgument.FORM_TYPE, FormActivity.FormType.BUDGET_AMOUNT_EDITOR.name());
        mBudgetAmounts = extractBudgetAmounts();
        intent.putParcelableArrayListExtra(UxArgument.BUDGET_AMOUNT_LIST, mBudgetAmounts);
        startActivityForResult(intent, REQUEST_EDIT_BUDGET_AMOUNTS);
    }

    @Override
    public void onRecurrenceSet(String rrule) {
        mRecurrenceRule = rrule;
        String repeatString = getString(R.string.label_tap_to_create_schedule);
        if (mRecurrenceRule != null) {
            mEventRecurrence.parse(mRecurrenceRule);
            repeatString = EventRecurrenceFormatter.getRepeatString(getActivity(), getResources(), mEventRecurrence,
                    true);
        }

        mRecurrenceInput.setText(repeatString);
    }

    @Override
    public void onDateSet(CalendarDatePickerDialogFragment dialog, int year, int monthOfYear, int dayOfMonth) {
        Calendar cal = new GregorianCalendar(year, monthOfYear, dayOfMonth);
        mStartDateInput.setText(TransactionFormFragment.DATE_FORMATTER.format(cal.getTime()));
        mStartDate.set(Calendar.YEAR, year);
        mStartDate.set(Calendar.MONTH, monthOfYear);
        mStartDate.set(Calendar.DAY_OF_MONTH, dayOfMonth);
    }

    @Override
    public void onActivityResult(int requestCode, int resultCode, Intent data) {
        if (requestCode == REQUEST_EDIT_BUDGET_AMOUNTS) {
            if (resultCode == Activity.RESULT_OK) {
                ArrayList<BudgetAmount> budgetAmounts = data
                        .getParcelableArrayListExtra(UxArgument.BUDGET_AMOUNT_LIST);
                if (budgetAmounts != null) {
                    mBudgetAmounts = budgetAmounts;
                    toggleAmountInputVisibility();
                }
                return;
            }
        }
        super.onActivityResult(requestCode, resultCode, data);
    }

    /**
     * Toggles the visibility of the amount input based on {@link #mBudgetAmounts}
     */
    private void toggleAmountInputVisibility() {
        if (mBudgetAmounts.size() > 1) {
            mBudgetAmountLayout.setVisibility(View.GONE);
            mAddBudgetAmount.setText("Edit Budget Amounts");
        } else {
            mAddBudgetAmount.setText("Add Budget Amounts");
            mBudgetAmountLayout.setVisibility(View.VISIBLE);
            if (!mBudgetAmounts.isEmpty()) {
                BudgetAmount budgetAmount = mBudgetAmounts.get(0);
                mBudgetAmountInput.setValue(budgetAmount.getAmount().asBigDecimal());
                mBudgetAccountSpinner
                        .setSelection(mAccountsCursorAdapter.getPosition(budgetAmount.getAccountUID()));
            }
        }
    }
}