/*
* Copyright (c) 2012 - 2014 Ngewi Fet <ngewif@gmail.com>
* Copyright (c) 2014 Yongxin Wang <fefe.wyx@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.account;
import android.app.Activity;
import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import android.content.res.Resources;
import android.content.res.TypedArray;
import android.database.Cursor;
import android.graphics.Color;
import android.os.Bundle;
import android.support.design.widget.TextInputLayout;
import android.support.v4.app.Fragment;
import android.support.v4.app.FragmentManager;
import android.support.v4.widget.SimpleCursorAdapter;
import android.support.v7.app.ActionBar;
import android.support.v7.app.AppCompatActivity;
import android.text.Editable;
import android.text.TextUtils;
import android.text.TextWatcher;
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.view.WindowManager;
import android.view.inputmethod.InputMethodManager;
import android.widget.AdapterView;
import android.widget.ArrayAdapter;
import android.widget.CheckBox;
import android.widget.CompoundButton;
import android.widget.CompoundButton.OnCheckedChangeListener;
import android.widget.EditText;
import android.widget.Spinner;
import org.gnucash.android.R;
import org.gnucash.android.db.DatabaseSchema;
import org.gnucash.android.db.adapter.AccountsDbAdapter;
import org.gnucash.android.db.adapter.CommoditiesDbAdapter;
import org.gnucash.android.db.adapter.DatabaseAdapter;
import org.gnucash.android.model.Account;
import org.gnucash.android.model.AccountType;
import org.gnucash.android.model.Commodity;
import org.gnucash.android.model.Money;
import org.gnucash.android.ui.colorpicker.ColorPickerDialog;
import org.gnucash.android.ui.colorpicker.ColorPickerSwatch;
import org.gnucash.android.ui.colorpicker.ColorSquare;
import org.gnucash.android.ui.common.UxArgument;
import org.gnucash.android.ui.settings.PreferenceActivity;
import org.gnucash.android.util.CommoditiesCursorAdapter;
import org.gnucash.android.util.QualifiedAccountNameCursorAdapter;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import butterknife.BindView;
import butterknife.ButterKnife;
/**
* Fragment used for creating and editing accounts
* @author Ngewi Fet <ngewif@gmail.com>
* @author Yongxin Wang <fefe.wyx@gmail.com>
*/
public class AccountFormFragment extends Fragment {
/**
* Tag for the color picker dialog fragment
*/
private static final String COLOR_PICKER_DIALOG_TAG = "color_picker_dialog";
/**
* EditText for the name of the account to be created/edited
*/
@BindView(R.id.input_account_name) EditText mNameEditText;
@BindView(R.id.name_text_input_layout) TextInputLayout mTextInputLayout;
/**
* Spinner for selecting the currency of the account
* Currencies listed are those specified by ISO 4217
*/
@BindView(R.id.input_currency_spinner) Spinner mCurrencySpinner;
/**
* Accounts database adapter
*/
private AccountsDbAdapter mAccountsDbAdapter;
/**
* GUID of the parent account
* This value is set to the parent account of the transaction being edited or
* the account in which a new sub-account is being created
*/
private String mParentAccountUID = null;
/**
* Account ID of the root account
*/
private long mRootAccountId = -1;
/**
* Account UID of the root account
*/
private String mRootAccountUID = null;
/**
* Reference to account object which will be created at end of dialog
*/
private Account mAccount = null;
/**
* Unique ID string of account being edited
*/
private String mAccountUID = null;
/**
* Cursor which will hold set of eligible parent accounts
*/
private Cursor mParentAccountCursor;
/**
* List of all descendant Account UIDs, if we are modifying an account
* null if creating a new account
*/
private List<String> mDescendantAccountUIDs;
/**
* SimpleCursorAdapter for the parent account spinner
* @see QualifiedAccountNameCursorAdapter
*/
private SimpleCursorAdapter mParentAccountCursorAdapter;
/**
* Spinner for parent account list
*/
@BindView(R.id.input_parent_account) Spinner mParentAccountSpinner;
/**
* Checkbox which activates the parent account spinner when selected
* Leaving this unchecked means it is a top-level root account
*/
@BindView(R.id.checkbox_parent_account) CheckBox mParentCheckBox;
/**
* Spinner for the account type
* @see org.gnucash.android.model.AccountType
*/
@BindView(R.id.input_account_type_spinner) Spinner mAccountTypeSpinner;
/**
* Checkbox for activating the default transfer account spinner
*/
@BindView(R.id.checkbox_default_transfer_account) CheckBox mDefaultTransferAccountCheckBox;
/**
* Spinner for selecting the default transfer account
*/
@BindView(R.id.input_default_transfer_account) Spinner mDefaultTransferAccountSpinner;
/**
* Account description input text view
*/
@BindView(R.id.input_account_description) EditText mDescriptionEditText;
/**
* Checkbox indicating if account is a placeholder account
*/
@BindView(R.id.checkbox_placeholder_account) CheckBox mPlaceholderCheckBox;
/**
* Cursor adapter which binds to the spinner for default transfer account
*/
private SimpleCursorAdapter mDefaultTransferAccountCursorAdapter;
/**
* Flag indicating if double entry transactions are enabled
*/
private boolean mUseDoubleEntry;
private int mSelectedColor = Account.DEFAULT_COLOR;
/**
* Trigger for color picker dialog
*/
@BindView(R.id.input_color_picker) ColorSquare mColorSquare;
private final ColorPickerSwatch.OnColorSelectedListener mColorSelectedListener =
new ColorPickerSwatch.OnColorSelectedListener() {
@Override
public void onColorSelected(int color) {
mColorSquare.setBackgroundColor(color);
mSelectedColor = color;
}
};
/**
* Default constructor
* Required, else the app crashes on screen rotation
*/
public AccountFormFragment() {
//nothing to see here, move along
}
/**
* Construct a new instance of the dialog
* @return New instance of the dialog fragment
*/
static public AccountFormFragment newInstance() {
AccountFormFragment f = new AccountFormFragment();
f.mAccountsDbAdapter = AccountsDbAdapter.getInstance();
return f;
}
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setHasOptionsMenu(true);
mAccountsDbAdapter = AccountsDbAdapter.getInstance();
SharedPreferences sharedPrefs = PreferenceActivity.getActiveBookSharedPreferences();
mUseDoubleEntry = sharedPrefs.getBoolean(getString(R.string.key_use_double_entry), true);
}
/**
* Inflates the dialog view and retrieves references to the dialog elements
*/
@Override public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.fragment_account_form, container, false);
ButterKnife.bind(this, view);
mNameEditText.addTextChangedListener(new TextWatcher() {
@Override
public void beforeTextChanged(CharSequence s, int start, int count, int after) {
//nothing to see here, move along
}
@Override
public void onTextChanged(CharSequence s, int start, int before, int count) {
//nothing to see here, move along
}
@Override
public void afterTextChanged(Editable s) {
if (s.toString().length() > 0) {
mTextInputLayout.setErrorEnabled(false);
}
}
});
mAccountTypeSpinner.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
@Override
public void onItemSelected(AdapterView<?> parentView, View selectedItemView, int position, long id) {
loadParentAccountList(getSelectedAccountType());
if (mParentAccountUID != null)
setParentAccountSelection(mAccountsDbAdapter.getID(mParentAccountUID));
}
@Override
public void onNothingSelected(AdapterView<?> adapterView) {
//nothing to see here, move along
}
});
mParentAccountSpinner.setEnabled(false);
mParentCheckBox.setOnCheckedChangeListener(new OnCheckedChangeListener() {
@Override
public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
mParentAccountSpinner.setEnabled(isChecked);
}
});
mDefaultTransferAccountSpinner.setEnabled(false);
mDefaultTransferAccountCheckBox.setOnCheckedChangeListener(new OnCheckedChangeListener() {
@Override
public void onCheckedChanged(CompoundButton compoundButton, boolean isChecked) {
mDefaultTransferAccountSpinner.setEnabled(isChecked);
}
});
mColorSquare.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
showColorPickerDialog();
}
});
return view;
}
/**
* Initializes the values of the views in the dialog
*/
@Override
public void onActivityCreated(Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
CommoditiesCursorAdapter commoditiesAdapter = new CommoditiesCursorAdapter(
getActivity(), android.R.layout.simple_spinner_item);
commoditiesAdapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
mCurrencySpinner.setAdapter(commoditiesAdapter);
mAccountUID = getArguments().getString(UxArgument.SELECTED_ACCOUNT_UID);
ActionBar supportActionBar = ((AppCompatActivity) getActivity()).getSupportActionBar();
if (mAccountUID != null) {
mAccount = mAccountsDbAdapter.getRecord(mAccountUID);
supportActionBar.setTitle(R.string.title_edit_account);
} else {
supportActionBar.setTitle(R.string.title_create_account);
}
mRootAccountUID = mAccountsDbAdapter.getOrCreateGnuCashRootAccountUID();
if (mRootAccountUID != null)
mRootAccountId = mAccountsDbAdapter.getID(mRootAccountUID);
//need to load the cursor adapters for the spinners before initializing the views
loadAccountTypesList();
loadDefaultTransferAccountList();
setDefaultTransferAccountInputsVisible(mUseDoubleEntry);
if (mAccount != null){
initializeViewsWithAccount(mAccount);
//do not immediately open the keyboard when editing an account
getActivity().getWindow().setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_HIDDEN);
} else {
initializeViews();
}
}
/**
* Initialize view with the properties of <code>account</code>.
* This is applicable when editing an account
* @param account Account whose fields are used to populate the form
*/
private void initializeViewsWithAccount(Account account){
if (account == null)
throw new IllegalArgumentException("Account cannot be null");
loadParentAccountList(account.getAccountType());
mParentAccountUID = account.getParentUID();
if (mParentAccountUID == null) {
// null parent, set Parent as root
mParentAccountUID = mRootAccountUID;
}
if (mParentAccountUID != null) {
setParentAccountSelection(mAccountsDbAdapter.getID(mParentAccountUID));
}
String currencyCode = account.getCommodity().getCurrencyCode();
setSelectedCurrency(currencyCode);
if (mAccountsDbAdapter.getTransactionMaxSplitNum(mAccount.getUID()) > 1)
{
//TODO: Allow changing the currency and effecting the change for all transactions without any currency exchange (purely cosmetic change)
mCurrencySpinner.setEnabled(false);
}
mNameEditText.setText(account.getName());
mNameEditText.setSelection(mNameEditText.getText().length());
mDescriptionEditText.setText(account.getDescription());
if (mUseDoubleEntry) {
if (account.getDefaultTransferAccountUID() != null) {
long doubleDefaultAccountId = mAccountsDbAdapter.getID(account.getDefaultTransferAccountUID());
setDefaultTransferAccountSelection(doubleDefaultAccountId, true);
} else {
String currentAccountUID = account.getParentUID();
String rootAccountUID = mAccountsDbAdapter.getOrCreateGnuCashRootAccountUID();
while (!currentAccountUID.equals(rootAccountUID)) {
long defaultTransferAccountID = mAccountsDbAdapter.getDefaultTransferAccountID(mAccountsDbAdapter.getID(currentAccountUID));
if (defaultTransferAccountID > 0) {
setDefaultTransferAccountSelection(defaultTransferAccountID, false);
break; //we found a parent with default transfer setting
}
currentAccountUID = mAccountsDbAdapter.getParentAccountUID(currentAccountUID);
}
}
}
mPlaceholderCheckBox.setChecked(account.isPlaceholderAccount());
mSelectedColor = account.getColor();
mColorSquare.setBackgroundColor(account.getColor());
setAccountTypeSelection(account.getAccountType());
}
/**
* Initialize views with defaults for new account
*/
private void initializeViews(){
setSelectedCurrency(Money.DEFAULT_CURRENCY_CODE);
mColorSquare.setBackgroundColor(Color.LTGRAY);
mParentAccountUID = getArguments().getString(UxArgument.PARENT_ACCOUNT_UID);
if (mParentAccountUID != null) {
AccountType parentAccountType = mAccountsDbAdapter.getAccountType(mParentAccountUID);
setAccountTypeSelection(parentAccountType);
loadParentAccountList(parentAccountType);
setParentAccountSelection(mAccountsDbAdapter.getID(mParentAccountUID));
}
}
/**
* Selects the corresponding account type in the spinner
* @param accountType AccountType to be set
*/
private void setAccountTypeSelection(AccountType accountType){
String[] accountTypeEntries = getResources().getStringArray(R.array.key_account_type_entries);
int accountTypeIndex = Arrays.asList(accountTypeEntries).indexOf(accountType.name());
mAccountTypeSpinner.setSelection(accountTypeIndex);
}
/**
* Toggles the visibility of the default transfer account input fields.
* This field is irrelevant for users who do not use double accounting
*/
private void setDefaultTransferAccountInputsVisible(boolean visible) {
final int visibility = visible ? View.VISIBLE : View.GONE;
final View view = getView();
assert view != null;
view.findViewById(R.id.layout_default_transfer_account).setVisibility(visibility);
view.findViewById(R.id.label_default_transfer_account).setVisibility(visibility);
}
/**
* Selects the currency with code <code>currencyCode</code> in the spinner
* @param currencyCode ISO 4217 currency code to be selected
*/
private void setSelectedCurrency(String currencyCode){
CommoditiesDbAdapter commodityDbAdapter = CommoditiesDbAdapter.getInstance();
long commodityId = commodityDbAdapter.getID(commodityDbAdapter.getCommodityUID(currencyCode));
int position = 0;
for (int i = 0; i < mCurrencySpinner.getCount(); i++) {
if (commodityId == mCurrencySpinner.getItemIdAtPosition(i)) {
position = i;
}
}
mCurrencySpinner.setSelection(position);
}
/**
* Selects the account with ID <code>parentAccountId</code> in the parent accounts spinner
* @param parentAccountId Record ID of parent account to be selected
*/
private void setParentAccountSelection(long parentAccountId){
if (parentAccountId <= 0 || parentAccountId == mRootAccountId) {
return;
}
for (int pos = 0; pos < mParentAccountCursorAdapter.getCount(); pos++) {
if (mParentAccountCursorAdapter.getItemId(pos) == parentAccountId){
mParentCheckBox.setChecked(true);
mParentAccountSpinner.setEnabled(true);
mParentAccountSpinner.setSelection(pos, true);
break;
}
}
}
/**
* Selects the account with ID <code>parentAccountId</code> in the default transfer account spinner
* @param defaultTransferAccountId Record ID of parent account to be selected
*/
private void setDefaultTransferAccountSelection(long defaultTransferAccountId, boolean enableTransferAccount) {
if (defaultTransferAccountId > 0) {
mDefaultTransferAccountCheckBox.setChecked(enableTransferAccount);
mDefaultTransferAccountSpinner.setEnabled(enableTransferAccount);
} else
return;
for (int pos = 0; pos < mDefaultTransferAccountCursorAdapter.getCount(); pos++) {
if (mDefaultTransferAccountCursorAdapter.getItemId(pos) == defaultTransferAccountId) {
mDefaultTransferAccountSpinner.setSelection(pos);
break;
}
}
}
/**
* Returns an array of colors used for accounts.
* The array returned has the actual color values and not the resource ID.
* @return Integer array of colors used for accounts
*/
private int[] getAccountColorOptions(){
Resources res = getResources();
TypedArray colorTypedArray = res.obtainTypedArray(R.array.account_colors);
int[] colorOptions = new int[colorTypedArray.length()];
for (int i = 0; i < colorTypedArray.length(); i++) {
int color = colorTypedArray.getColor(i, getResources().getColor(R.color.title_green));
colorOptions[i] = color;
}
colorTypedArray.recycle();
return colorOptions;
}
/**
* Shows the color picker dialog
*/
private void showColorPickerDialog(){
FragmentManager fragmentManager = getActivity().getSupportFragmentManager();
int currentColor = Color.LTGRAY;
if (mAccount != null){
currentColor = mAccount.getColor();
}
ColorPickerDialog colorPickerDialogFragment = ColorPickerDialog.newInstance(
R.string.color_picker_default_title,
getAccountColorOptions(),
currentColor, 4, 12);
colorPickerDialogFragment.setOnColorSelectedListener(mColorSelectedListener);
colorPickerDialogFragment.show(fragmentManager, COLOR_PICKER_DIALOG_TAG);
}
@Override
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
super.onCreateOptionsMenu(menu, inflater);
inflater.inflate(R.menu.default_save_actions, menu);
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
case R.id.menu_save:
saveAccount();
return true;
case android.R.id.home:
finishFragment();
return true;
}
return super.onOptionsItemSelected(item);
}
/**
* Initializes the default transfer account spinner with eligible accounts
*/
private void loadDefaultTransferAccountList(){
String condition = DatabaseSchema.AccountEntry.COLUMN_UID + " != '" + mAccountUID + "' " //when creating a new account mAccountUID is null, so don't use whereArgs
+ " AND " + DatabaseSchema.AccountEntry.COLUMN_PLACEHOLDER + "=0"
+ " AND " + DatabaseSchema.AccountEntry.COLUMN_HIDDEN + "=0"
+ " AND " + DatabaseSchema.AccountEntry.COLUMN_TYPE + " != ?";
Cursor defaultTransferAccountCursor = mAccountsDbAdapter.fetchAccountsOrderedByFullName(condition,
new String[]{AccountType.ROOT.name()});
if (mDefaultTransferAccountSpinner.getCount() <= 0) {
setDefaultTransferAccountInputsVisible(false);
}
mDefaultTransferAccountCursorAdapter = new QualifiedAccountNameCursorAdapter(getActivity(),
defaultTransferAccountCursor);
mDefaultTransferAccountSpinner.setAdapter(mDefaultTransferAccountCursorAdapter);
}
/**
* Loads the list of possible accounts which can be set as a parent account and initializes the spinner.
* The allowed parent accounts depends on the account type
* @param accountType AccountType of account whose allowed parent list is to be loaded
*/
private void loadParentAccountList(AccountType accountType){
String condition = DatabaseSchema.SplitEntry.COLUMN_TYPE + " IN ("
+ getAllowedParentAccountTypes(accountType) + ") AND " + DatabaseSchema.AccountEntry.COLUMN_HIDDEN + "!=1 ";
if (mAccount != null){ //if editing an account
mDescendantAccountUIDs = mAccountsDbAdapter.getDescendantAccountUIDs(mAccount.getUID(), null, null);
String rootAccountUID = mAccountsDbAdapter.getOrCreateGnuCashRootAccountUID();
List<String> descendantAccountUIDs = new ArrayList<>(mDescendantAccountUIDs);
if (rootAccountUID != null)
descendantAccountUIDs.add(rootAccountUID);
// limit cyclic account hierarchies.
condition += " AND (" + DatabaseSchema.AccountEntry.COLUMN_UID + " NOT IN ( '"
+ TextUtils.join("','", descendantAccountUIDs) + "','" + mAccountUID + "' ) )";
}
//if we are reloading the list, close the previous cursor first
if (mParentAccountCursor != null)
mParentAccountCursor.close();
mParentAccountCursor = mAccountsDbAdapter.fetchAccountsOrderedByFullName(condition, null);
final View view = getView();
assert view != null;
if (mParentAccountCursor.getCount() <= 0){
mParentCheckBox.setChecked(false); //disable before hiding, else we can still read it when saving
view.findViewById(R.id.layout_parent_account).setVisibility(View.GONE);
view.findViewById(R.id.label_parent_account).setVisibility(View.GONE);
} else {
view.findViewById(R.id.layout_parent_account).setVisibility(View.VISIBLE);
view.findViewById(R.id.label_parent_account).setVisibility(View.VISIBLE);
}
mParentAccountCursorAdapter = new QualifiedAccountNameCursorAdapter(
getActivity(), mParentAccountCursor);
mParentAccountSpinner.setAdapter(mParentAccountCursorAdapter);
}
/**
* Returns a comma separated list of account types which can be parent accounts for the specified <code>type</code>.
* The strings in the list are the {@link org.gnucash.android.model.AccountType#name()}s of the different types.
* @param type {@link org.gnucash.android.model.AccountType}
* @return String comma separated list of account types
*/
private String getAllowedParentAccountTypes(AccountType type) {
switch (type) {
case EQUITY:
return "'" + AccountType.EQUITY.name() + "'";
case INCOME:
case EXPENSE:
return "'" + AccountType.EXPENSE.name() + "', '" + AccountType.INCOME.name() + "'";
case CASH:
case BANK:
case CREDIT:
case ASSET:
case LIABILITY:
case PAYABLE:
case RECEIVABLE:
case CURRENCY:
case STOCK:
case MUTUAL: {
List<String> accountTypeStrings = getAccountTypeStringList();
accountTypeStrings.remove(AccountType.EQUITY.name());
accountTypeStrings.remove(AccountType.EXPENSE.name());
accountTypeStrings.remove(AccountType.INCOME.name());
accountTypeStrings.remove(AccountType.ROOT.name());
return "'" + TextUtils.join("','", accountTypeStrings) + "'";
}
case TRADING:
return "'" + AccountType.TRADING.name() + "'";
case ROOT:
default:
return Arrays.toString(AccountType.values()).replaceAll("\\[|]", "");
}
}
/**
* Returns a list of all the available {@link org.gnucash.android.model.AccountType}s as strings
* @return String list of all account types
*/
private List<String> getAccountTypeStringList(){
String[] accountTypes = Arrays.toString(AccountType.values()).replaceAll("\\[|]", "").split(",");
List<String> accountTypesList = new ArrayList<>();
for (String accountType : accountTypes) {
accountTypesList.add(accountType.trim());
}
return accountTypesList;
}
/**
* Loads the list of account types into the account type selector spinner
*/
private void loadAccountTypesList(){
String[] accountTypes = getResources().getStringArray(R.array.account_type_entry_values);
ArrayAdapter<String> accountTypesAdapter = new ArrayAdapter<>(
getActivity(), android.R.layout.simple_list_item_1, accountTypes);
accountTypesAdapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
mAccountTypeSpinner.setAdapter(accountTypesAdapter);
}
/**
* Finishes the fragment appropriately.
* Depends on how the fragment was loaded, it might have a backstack or not
*/
private void finishFragment() {
InputMethodManager imm = (InputMethodManager) getActivity().getSystemService(
Context.INPUT_METHOD_SERVICE);
imm.hideSoftInputFromWindow(mNameEditText.getWindowToken(), 0);
final String action = getActivity().getIntent().getAction();
if (action != null && action.equals(Intent.ACTION_INSERT_OR_EDIT)){
getActivity().setResult(Activity.RESULT_OK);
getActivity().finish();
} else {
getActivity().getSupportFragmentManager().popBackStack();
}
}
@Override
public void onDestroy() {
super.onDestroy();
if (mParentAccountCursor != null)
mParentAccountCursor.close();
if (mDefaultTransferAccountCursorAdapter != null) {
mDefaultTransferAccountCursorAdapter.getCursor().close();
}
}
/**
* Reads the fields from the account form and saves as a new account
*/
private void saveAccount() {
Log.i("AccountFormFragment", "Saving account");
if (mAccountsDbAdapter == null)
mAccountsDbAdapter = AccountsDbAdapter.getInstance();
// accounts to update, in case we're updating full names of a sub account tree
ArrayList<Account> accountsToUpdate = new ArrayList<>();
boolean nameChanged = false;
if (mAccount == null){
String name = getEnteredName();
if (name.length() == 0){
mTextInputLayout.setErrorEnabled(true);
mTextInputLayout.setError(getString(R.string.toast_no_account_name_entered));
return;
}
mAccount = new Account(getEnteredName());
mAccountsDbAdapter.addRecord(mAccount, DatabaseAdapter.UpdateMethod.insert); //new account, insert it
}
else {
nameChanged = !mAccount.getName().equals(getEnteredName());
mAccount.setName(getEnteredName());
}
long commodityId = mCurrencySpinner.getSelectedItemId();
Commodity commodity = CommoditiesDbAdapter.getInstance().getRecord(commodityId);
mAccount.setCommodity(commodity);
AccountType selectedAccountType = getSelectedAccountType();
mAccount.setAccountType(selectedAccountType);
mAccount.setDescription(mDescriptionEditText.getText().toString());
mAccount.setPlaceHolderFlag(mPlaceholderCheckBox.isChecked());
mAccount.setColor(mSelectedColor);
long newParentAccountId;
String newParentAccountUID;
if (mParentCheckBox.isChecked()) {
newParentAccountId = mParentAccountSpinner.getSelectedItemId();
newParentAccountUID = mAccountsDbAdapter.getUID(newParentAccountId);
mAccount.setParentUID(newParentAccountUID);
} else {
//need to do this explicitly in case user removes parent account
newParentAccountUID = mRootAccountUID;
newParentAccountId = mRootAccountId;
}
mAccount.setParentUID(newParentAccountUID);
if (mDefaultTransferAccountCheckBox.isChecked()
&& mDefaultTransferAccountSpinner.getSelectedItemId() != Spinner.INVALID_ROW_ID){
long id = mDefaultTransferAccountSpinner.getSelectedItemId();
mAccount.setDefaultTransferAccountUID(mAccountsDbAdapter.getUID(id));
} else {
//explicitly set in case of removal of default account
mAccount.setDefaultTransferAccountUID(null);
}
long parentAccountId = mParentAccountUID == null ? -1 : mAccountsDbAdapter.getID(mParentAccountUID);
// update full names
if (nameChanged || mDescendantAccountUIDs == null || newParentAccountId != parentAccountId) {
// current account name changed or new Account or parent account changed
String newAccountFullName;
if (newParentAccountId == mRootAccountId){
newAccountFullName = mAccount.getName();
}
else {
newAccountFullName = mAccountsDbAdapter.getAccountFullName(newParentAccountUID) +
AccountsDbAdapter.ACCOUNT_NAME_SEPARATOR + mAccount.getName();
}
mAccount.setFullName(newAccountFullName);
if (mDescendantAccountUIDs != null) {
// modifying existing account, e.t. name changed and/or parent changed
if ((nameChanged || parentAccountId != newParentAccountId) && mDescendantAccountUIDs.size() > 0) {
// parent change, update all full names of descent accounts
accountsToUpdate.addAll(mAccountsDbAdapter.getSimpleAccountList(
DatabaseSchema.AccountEntry.COLUMN_UID + " IN ('" +
TextUtils.join("','", mDescendantAccountUIDs) + "')", null, null
));
}
HashMap<String, Account> mapAccount = new HashMap<>();
for (Account acct : accountsToUpdate) mapAccount.put(acct.getUID(), acct);
for (String uid: mDescendantAccountUIDs) {
// mAccountsDbAdapter.getDescendantAccountUIDs() will ensure a parent-child order
Account acct = mapAccount.get(uid);
// mAccount cannot be root, so acct here cannot be top level account.
if (mAccount.getUID().equals(acct.getParentUID())) {
acct.setFullName(mAccount.getFullName() + AccountsDbAdapter.ACCOUNT_NAME_SEPARATOR + acct.getName());
}
else {
acct.setFullName(
mapAccount.get(acct.getParentUID()).getFullName() +
AccountsDbAdapter.ACCOUNT_NAME_SEPARATOR +
acct.getName()
);
}
}
}
}
accountsToUpdate.add(mAccount);
// bulk update, will not update transactions
mAccountsDbAdapter.bulkAddRecords(accountsToUpdate, DatabaseAdapter.UpdateMethod.update);
finishFragment();
}
/**
* Returns the currently selected account type in the spinner
* @return {@link org.gnucash.android.model.AccountType} currently selected
*/
private AccountType getSelectedAccountType() {
int selectedAccountTypeIndex = mAccountTypeSpinner.getSelectedItemPosition();
String[] accountTypeEntries = getResources().getStringArray(R.array.key_account_type_entries);
return AccountType.valueOf(accountTypeEntries[selectedAccountTypeIndex]);
}
/**
* Retrieves the name of the account which has been entered in the EditText
* @return Name of the account which has been entered in the EditText
*/
private String getEnteredName(){
return mNameEditText.getText().toString().trim();
}
}