/* * Copyright (C) 2012-2016 The Android Money Manager Ex Project Team * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; either version 3 * of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. */ package com.money.manager.ex.transactions; import android.app.Activity; import android.content.Intent; import android.os.Bundle; import android.support.design.widget.FloatingActionButton; import android.support.v7.widget.LinearLayoutManager; import android.support.v7.widget.RecyclerView; import android.support.v7.widget.helper.ItemTouchHelper; import android.view.Menu; import android.view.MenuItem; import android.view.View; import com.afollestad.materialdialogs.MaterialDialog; import com.money.manager.ex.Constants; import com.money.manager.ex.R; import com.money.manager.ex.common.AmountInputDialog; import com.money.manager.ex.common.Calculator; import com.money.manager.ex.common.CategoryListActivity; import com.money.manager.ex.common.CommonSplitCategoryLogic; import com.money.manager.ex.common.events.AmountEnteredEvent; import com.money.manager.ex.core.Core; import com.money.manager.ex.core.MenuHelper; import com.money.manager.ex.core.RequestCodes; import com.money.manager.ex.core.TransactionTypes; import com.money.manager.ex.common.MmxBaseFragmentActivity; import com.money.manager.ex.database.ISplitTransaction; import com.money.manager.ex.transactions.events.AmountEntryRequestedEvent; import com.money.manager.ex.transactions.events.CategoryRequestedEvent; import com.money.manager.ex.transactions.events.SplitItemRemovedEvent; import org.greenrobot.eventbus.Subscribe; import org.parceler.Parcels; import java.util.ArrayList; import java.util.List; import butterknife.BindView; import butterknife.ButterKnife; import butterknife.OnClick; import info.javaperformance.money.Money; import info.javaperformance.money.MoneyFactory; public class SplitCategoriesActivity extends MmxBaseFragmentActivity { public static final String KEY_SPLIT_TRANSACTION = "SplitCategoriesActivity:ArraysSplitTransaction"; public static final String KEY_SPLIT_TRANSACTION_DELETED = "SplitCategoriesActivity:ArraysSplitTransactionDeleted"; public static final String KEY_TRANSACTION_TYPE = "SplitCategoriesActivity:TransactionType"; public static final String KEY_DATASET_TYPE = "SplitCategoriesActivity:DatasetType"; public static final String KEY_CURRENCY_ID = "SplitCategoriesActivity:CurrencyId"; public static final String INTENT_RESULT_SPLIT_TRANSACTION = "SplitCategoriesActivity:ResultSplitTransaction"; public static final String INTENT_RESULT_SPLIT_TRANSACTION_DELETED = "SplitCategoriesActivity:ResultSplitTransactionDeleted"; private static final int REQUEST_PICK_CATEGORY = 1; /** * The name of the entity to create when adding split transactions. * Needed to distinguish between SplitCategory and SplitRecurringCategory. */ private String entityTypeName = null; private ArrayList<ISplitTransaction> mSplitDeleted = null; private SplitCategoriesAdapter mAdapter; @BindView(R.id.splitsRecyclerView) RecyclerView mRecyclerView; private int amountRequestOffset = 1000; // used to offset the request number for Amounts. @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); mAdapter = new SplitCategoriesAdapter(); handleIntent(); // restore collections if (savedInstanceState != null) { mAdapter.splitTransactions = Parcels.unwrap(savedInstanceState.getParcelable(KEY_SPLIT_TRANSACTION)); mSplitDeleted = Parcels.unwrap(savedInstanceState.getParcelable(KEY_SPLIT_TRANSACTION_DELETED)); } // If this is a new split (no existing split categories), then create the first one. if(mAdapter.splitTransactions == null || mAdapter.splitTransactions.isEmpty()) { addSplitTransaction(); } setContentView(R.layout.activity_split_categories); ButterKnife.bind(this); setDisplayHomeAsUpEnabled(true); initRecyclerView(); } @Override public boolean onCreateOptionsMenu(Menu menu) { super.onCreateOptionsMenu(menu); new MenuHelper(this, menu).addSaveToolbarIcon(); return true; } @Override public boolean onOptionsItemSelected(MenuItem item) { switch (item.getItemId()) { case MenuHelper.save: return onActionDoneClick(); } return super.onOptionsItemSelected(item); } @Override public void onActivityResult(int requestCode, int resultCode, Intent data) { super.onActivityResult(requestCode, resultCode, data); if ((resultCode != Activity.RESULT_OK) || (data == null)) return; switch (requestCode) { case REQUEST_PICK_CATEGORY: int categoryId = data.getIntExtra(CategoryListActivity.INTENT_RESULT_CATEGID, Constants.NOT_SET); int subcategoryId = data.getIntExtra(CategoryListActivity.INTENT_RESULT_SUBCATEGID, Constants.NOT_SET); int location = data.getIntExtra(CategoryListActivity.KEY_REQUEST_ID, Constants.NOT_SET); ISplitTransaction split = mAdapter.splitTransactions.get(location); split.setCategoryId(categoryId); split.setSubcategoryId(subcategoryId); mAdapter.notifyItemChanged(location); break; } if (requestCode >= amountRequestOffset) { int id = requestCode - amountRequestOffset; Money amount = Calculator.getAmountFromResult(data); onAmountEntered(id, amount); } } public boolean onActionDoneClick() { List<ISplitTransaction> allSplitTransactions = mAdapter.splitTransactions; Core core = new Core(this); Money total = MoneyFactory.fromString("0"); // Validate Category. for (int i = 0; i < allSplitTransactions.size(); i++) { ISplitTransaction splitTransaction = allSplitTransactions.get(i); if (splitTransaction.getCategoryId() == Constants.NOT_SET) { core.alert(R.string.error_category_not_selected); return false; } total = total.add(splitTransaction.getAmount()); } // total amount must not be negative. if (total.toDouble() < 0) { core.alert(R.string.split_amount_negative); return false; } Intent result = new Intent(); result.putExtra(INTENT_RESULT_SPLIT_TRANSACTION, Parcels.wrap(allSplitTransactions)); result.putExtra(INTENT_RESULT_SPLIT_TRANSACTION_DELETED, Parcels.wrap(mSplitDeleted)); setResult(RESULT_OK, result); finish(); return true; } @Override protected void onSaveInstanceState(Bundle outState) { super.onSaveInstanceState(outState); outState.putParcelable(KEY_SPLIT_TRANSACTION, Parcels.wrap(mAdapter.splitTransactions)); if (mSplitDeleted != null) { outState.putParcelable(KEY_SPLIT_TRANSACTION_DELETED, Parcels.wrap(mSplitDeleted)); } } @OnClick(R.id.fab) protected void onFabClick() { addSplitTransaction(); } /* Events */ @Subscribe public void onEvent(SplitItemRemovedEvent event) { onRemoveItem(event.entity); } @Subscribe public void onEvent(AmountEntryRequestedEvent event) { Calculator.forActivity(this) .currency(mAdapter.currencyId) .amount(event.amount) .show(event.requestId + amountRequestOffset); } @Subscribe public void onEvent(CategoryRequestedEvent event) { showCategorySelector(event.requestId); } // Private private void addSplitTransaction() { ISplitTransaction entity = SplitItemFactory.create(this.entityTypeName, mAdapter.transactionType); mAdapter.splitTransactions.add(entity); int position = mAdapter.splitTransactions.size() - 1; mAdapter.notifyItemInserted(position); if (mRecyclerView != null) { // mRecyclerView.smoothScrollToPosition(position); mRecyclerView.scrollToPosition(position); } } private void handleIntent() { Intent intent = getIntent(); if (intent == null) return; this.entityTypeName = intent.getStringExtra(KEY_DATASET_TYPE); int transactionType = intent.getIntExtra(KEY_TRANSACTION_TYPE, 0); mAdapter.transactionType = TransactionTypes.values()[transactionType]; mAdapter.currencyId = intent.getIntExtra(KEY_CURRENCY_ID, Constants.NOT_SET); List<ISplitTransaction> splits = Parcels.unwrap(intent.getParcelableExtra(KEY_SPLIT_TRANSACTION)); if (splits != null) { mAdapter.splitTransactions = splits; } mSplitDeleted = Parcels.unwrap(intent.getParcelableExtra(KEY_SPLIT_TRANSACTION_DELETED)); } private void initRecyclerView() { // mRecyclerView = (RecyclerView) findViewById(R.id.splitsRecyclerView); if (mRecyclerView == null) return; // adapter mRecyclerView.setAdapter(mAdapter); // layout manager - LinearLayoutManager mRecyclerView.setLayoutManager(new LinearLayoutManager(this)); // item animator // RecyclerView.ItemAnimator // optimizations mRecyclerView.setHasFixedSize(true); // Support for swipe-to-dismiss ItemTouchHelper.Callback swipeCallback = new SplitItemTouchCallback(mAdapter); ItemTouchHelper touchHelper = new ItemTouchHelper(swipeCallback); touchHelper.attachToRecyclerView(mRecyclerView); } private void onAmountEntered(int requestId, Money amount) { if (amount.toDouble() <= 0) { showInvalidAmountDialog(); return; } // The amount is always positive, ensured by the validation above. // int position = Integer.parseInt(requestId); int position = requestId; ISplitTransaction split = mAdapter.splitTransactions.get(position); Money adjustedAmount = CommonSplitCategoryLogic.getStorageAmount(mAdapter.transactionType, amount, split); split.setAmount(adjustedAmount); mAdapter.notifyItemChanged(position); } private void onRemoveItem(ISplitTransaction splitTransaction) { if (splitTransaction == null) return; if (mSplitDeleted == null) { mSplitDeleted = new ArrayList<>(); } // Add item to delete. Only if not a new, non-saved split item. if (splitTransaction.getId() != null && splitTransaction.getId() != Constants.NOT_SET) { // not new split transaction mSplitDeleted.add(splitTransaction); } } private void showCategorySelector(int requestId) { Intent intent = new Intent(this, CategoryListActivity.class); intent.setAction(Intent.ACTION_PICK); // add id of the item that requested the category. intent.putExtra(CategoryListActivity.KEY_REQUEST_ID, requestId); startActivityForResult(intent, REQUEST_PICK_CATEGORY); } private void showInvalidAmountDialog() { new MaterialDialog.Builder(this) .title(R.string.error) .content(R.string.error_amount_must_be_positive) .positiveText(android.R.string.ok) .show(); } }