/*
* 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()));
}
}
}
}