org.gnucash.android.ui.transaction.TransactionsActivity.java Source code

Java tutorial

Introduction

Here is the source code for org.gnucash.android.ui.transaction.TransactionsActivity.java

Source

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

import android.content.Context;
import android.content.Intent;
import android.database.Cursor;
import android.graphics.drawable.ColorDrawable;
import android.os.AsyncTask;
import android.os.Build;
import android.os.Bundle;
import android.support.annotation.NonNull;
import android.support.design.widget.FloatingActionButton;
import android.support.design.widget.TabLayout;
import android.support.v4.app.Fragment;
import android.support.v4.app.FragmentManager;
import android.support.v4.app.FragmentStatePagerAdapter;
import android.support.v4.view.PagerAdapter;
import android.support.v4.view.ViewPager;
import android.text.format.DateUtils;
import android.util.Log;
import android.util.SparseArray;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
import android.widget.AdapterView;
import android.widget.Spinner;
import android.widget.SpinnerAdapter;
import android.widget.TextView;

import org.gnucash.android.R;
import org.gnucash.android.app.GnuCashApplication;
import org.gnucash.android.db.DatabaseSchema;
import org.gnucash.android.db.adapter.AccountsDbAdapter;
import org.gnucash.android.db.adapter.TransactionsDbAdapter;
import org.gnucash.android.model.Account;
import org.gnucash.android.model.Money;
import org.gnucash.android.ui.account.AccountsActivity;
import org.gnucash.android.ui.account.AccountsListFragment;
import org.gnucash.android.ui.account.OnAccountClickedListener;
import org.gnucash.android.ui.common.BaseDrawerActivity;
import org.gnucash.android.ui.common.FormActivity;
import org.gnucash.android.ui.common.Refreshable;
import org.gnucash.android.ui.common.UxArgument;
import org.gnucash.android.ui.util.AccountBalanceTask;
import org.gnucash.android.util.QualifiedAccountNameCursorAdapter;
import org.joda.time.LocalDate;

import java.math.BigDecimal;
import java.text.SimpleDateFormat;
import java.util.Date;

import butterknife.Bind;

/**
 * Activity for displaying, creating and editing transactions
 * @author Ngewi Fet <ngewif@gmail.com>
 */
public class TransactionsActivity extends BaseDrawerActivity
        implements Refreshable, OnAccountClickedListener, OnTransactionClickedListener {

    /**
     * Logging tag
     */
    protected static final String TAG = "TransactionsActivity";

    /**
     * ViewPager index for sub-accounts fragment
     */
    private static final int INDEX_SUB_ACCOUNTS_FRAGMENT = 0;

    /**
     * ViewPager index for transactions fragment
     */
    private static final int INDEX_TRANSACTIONS_FRAGMENT = 1;

    /**
     * Number of pages to show
     */
    private static final int DEFAULT_NUM_PAGES = 2;
    private static SimpleDateFormat mDayMonthDateFormat = new SimpleDateFormat("EEE, d MMM");

    /**
     * GUID of {@link Account} whose transactions are displayed
     */
    private String mAccountUID = null;

    /**
     * Account database adapter for manipulating the accounts list in navigation
     */
    private AccountsDbAdapter mAccountsDbAdapter;

    /**
     * Hold the accounts cursor that will be used in the Navigation
     */
    private Cursor mAccountsCursor = null;

    @Bind(R.id.pager)
    ViewPager mViewPager;
    @Bind(R.id.toolbar_spinner)
    Spinner mToolbarSpinner;
    @Bind(R.id.tab_layout)
    TabLayout mTabLayout;
    @Bind(R.id.transactions_sum)
    TextView mSumTextView;
    @Bind(R.id.fab_create_transaction)
    FloatingActionButton mCreateFloatingButton;

    private SparseArray<Refreshable> mFragmentPageReferenceMap = new SparseArray<>();

    /**
     * Flag for determining is the currently displayed account is a placeholder account or not.
     * This will determine if the transactions tab is displayed or not
     */
    private boolean mIsPlaceholderAccount;

    private AdapterView.OnItemSelectedListener mTransactionListNavigationListener = new AdapterView.OnItemSelectedListener() {

        @Override
        public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
            mAccountUID = mAccountsDbAdapter.getUID(id);
            getIntent().putExtra(UxArgument.SELECTED_ACCOUNT_UID, mAccountUID); //update the intent in case the account gets rotated
            mIsPlaceholderAccount = mAccountsDbAdapter.isPlaceholderAccount(mAccountUID);
            if (mIsPlaceholderAccount) {
                if (mTabLayout.getTabCount() > 1) {
                    mPagerAdapter.notifyDataSetChanged();
                    mTabLayout.removeTabAt(1);
                }
            } else {
                if (mTabLayout.getTabCount() < 2) {
                    mPagerAdapter.notifyDataSetChanged();
                    mTabLayout.addTab(mTabLayout.newTab().setText(R.string.section_header_transactions));
                }
            }
            //refresh any fragments in the tab with the new account UID
            refresh();
        }

        @Override
        public void onNothingSelected(AdapterView<?> parent) {
            //nothing to see here, move along
        }
    };

    private PagerAdapter mPagerAdapter;

    /**
     * Adapter for managing the sub-account and transaction fragment pages in the accounts view
     */
    private class AccountViewPagerAdapter extends FragmentStatePagerAdapter {

        public AccountViewPagerAdapter(FragmentManager fm) {
            super(fm);
        }

        @Override
        public Fragment getItem(int i) {
            if (mIsPlaceholderAccount) {
                Fragment transactionsListFragment = prepareSubAccountsListFragment();
                mFragmentPageReferenceMap.put(i, (Refreshable) transactionsListFragment);
                return transactionsListFragment;
            }

            Fragment currentFragment;
            switch (i) {
            case INDEX_SUB_ACCOUNTS_FRAGMENT:
                currentFragment = prepareSubAccountsListFragment();
                break;

            case INDEX_TRANSACTIONS_FRAGMENT:
            default:
                currentFragment = prepareTransactionsListFragment();
                break;
            }

            mFragmentPageReferenceMap.put(i, (Refreshable) currentFragment);
            return currentFragment;
        }

        @Override
        public void destroyItem(ViewGroup container, int position, Object object) {
            super.destroyItem(container, position, object);
            mFragmentPageReferenceMap.remove(position);
        }

        @Override
        public CharSequence getPageTitle(int position) {
            if (mIsPlaceholderAccount)
                return getString(R.string.section_header_subaccounts);

            switch (position) {
            case INDEX_SUB_ACCOUNTS_FRAGMENT:
                return getString(R.string.section_header_subaccounts);

            case INDEX_TRANSACTIONS_FRAGMENT:
            default:
                return getString(R.string.section_header_transactions);
            }
        }

        @Override
        public int getCount() {
            if (mIsPlaceholderAccount)
                return 1;
            else
                return DEFAULT_NUM_PAGES;
        }

        /**
         * Creates and initializes the fragment for displaying sub-account list
         * @return {@link AccountsListFragment} initialized with the sub-accounts
         */
        private AccountsListFragment prepareSubAccountsListFragment() {
            AccountsListFragment subAccountsListFragment = new AccountsListFragment();
            Bundle args = new Bundle();
            args.putString(UxArgument.PARENT_ACCOUNT_UID, mAccountUID);
            subAccountsListFragment.setArguments(args);
            return subAccountsListFragment;
        }

        /**
         * Creates and initializes fragment for displaying transactions
         * @return {@link TransactionsListFragment} initialized with the current account transactions
         */
        private TransactionsListFragment prepareTransactionsListFragment() {
            TransactionsListFragment transactionsListFragment = new TransactionsListFragment();
            Bundle args = new Bundle();
            args.putString(UxArgument.SELECTED_ACCOUNT_UID, mAccountUID);
            transactionsListFragment.setArguments(args);
            Log.i(TAG, "Opening transactions for account:  " + mAccountUID);
            return transactionsListFragment;
        }
    }

    /**
     * Refreshes the fragments currently in the transactions activity
     */
    @Override
    public void refresh(String accountUID) {
        for (int i = 0; i < mFragmentPageReferenceMap.size(); i++) {
            mFragmentPageReferenceMap.valueAt(i).refresh(accountUID);
        }

        if (mPagerAdapter != null)
            mPagerAdapter.notifyDataSetChanged();

        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {
            // make sure the account balance task is truely multi-thread
            new AccountBalanceTask(mSumTextView).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, mAccountUID);
        } else {
            new AccountBalanceTask(mSumTextView).execute(mAccountUID);
        }
    }

    @Override
    public void refresh() {
        refresh(mAccountUID);
        setTitleIndicatorColor();
    }

    @Override
    public int getContentView() {
        return R.layout.activity_transactions;
    }

    @Override
    public int getTitleRes() {
        return R.string.title_transactions;
    }

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        getSupportActionBar().setDisplayShowTitleEnabled(false);

        mAccountUID = getIntent().getStringExtra(UxArgument.SELECTED_ACCOUNT_UID);
        mAccountsDbAdapter = AccountsDbAdapter.getInstance();

        mIsPlaceholderAccount = mAccountsDbAdapter.isPlaceholderAccount(mAccountUID);

        mTabLayout.addTab(mTabLayout.newTab().setText(R.string.section_header_subaccounts));
        if (!mIsPlaceholderAccount) {
            mTabLayout.addTab(mTabLayout.newTab().setText(R.string.section_header_transactions));
        }

        setupActionBarNavigation();

        mPagerAdapter = new AccountViewPagerAdapter(getSupportFragmentManager());
        mViewPager.setAdapter(mPagerAdapter);
        mViewPager.addOnPageChangeListener(new TabLayout.TabLayoutOnPageChangeListener(mTabLayout));

        mTabLayout.setOnTabSelectedListener(new TabLayout.OnTabSelectedListener() {
            @Override
            public void onTabSelected(TabLayout.Tab tab) {
                mViewPager.setCurrentItem(tab.getPosition());
            }

            @Override
            public void onTabUnselected(TabLayout.Tab tab) {
                //nothing to see here, move along
            }

            @Override
            public void onTabReselected(TabLayout.Tab tab) {
                //nothing to see here, move along
            }
        });

        //if there are no transactions, and there are sub-accounts, show the sub-accounts
        if (TransactionsDbAdapter.getInstance().getTransactionsCount(mAccountUID) == 0
                && mAccountsDbAdapter.getSubAccountCount(mAccountUID) > 0) {
            mViewPager.setCurrentItem(INDEX_SUB_ACCOUNTS_FRAGMENT);
        } else {
            mViewPager.setCurrentItem(INDEX_TRANSACTIONS_FRAGMENT);
        }

        mCreateFloatingButton.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                switch (mViewPager.getCurrentItem()) {
                case INDEX_SUB_ACCOUNTS_FRAGMENT:
                    Intent addAccountIntent = new Intent(TransactionsActivity.this, FormActivity.class);
                    addAccountIntent.setAction(Intent.ACTION_INSERT_OR_EDIT);
                    addAccountIntent.putExtra(UxArgument.FORM_TYPE, FormActivity.FormType.ACCOUNT.name());
                    addAccountIntent.putExtra(UxArgument.PARENT_ACCOUNT_UID, mAccountUID);
                    startActivityForResult(addAccountIntent, AccountsActivity.REQUEST_EDIT_ACCOUNT);
                    ;
                    break;

                case INDEX_TRANSACTIONS_FRAGMENT:
                    createNewTransaction(mAccountUID);
                    break;

                }
            }
        });
    }

    @Override
    protected void onResume() {
        super.onResume();
        setTitleIndicatorColor();
    }

    /**
     * Sets the color for the ViewPager title indicator to match the account color
     */
    private void setTitleIndicatorColor() {
        int iColor = AccountsDbAdapter.getActiveAccountColorResource(mAccountUID);

        mTabLayout.setBackgroundColor(iColor);

        if (getSupportActionBar() != null)
            getSupportActionBar().setBackgroundDrawable(new ColorDrawable(iColor));

        if (Build.VERSION.SDK_INT > 20)
            getWindow().setStatusBarColor(GnuCashApplication.darken(iColor));
    }

    /**
    * Set up action bar navigation list and listener callbacks
    */
    private void setupActionBarNavigation() {
        // set up spinner adapter for navigation list
        if (mAccountsCursor != null) {
            mAccountsCursor.close();
        }
        mAccountsCursor = mAccountsDbAdapter.fetchAllRecordsOrderedByFullName();

        SpinnerAdapter mSpinnerAdapter = new QualifiedAccountNameCursorAdapter(
                getSupportActionBar().getThemedContext(), mAccountsCursor, R.layout.account_spinner_item);

        mToolbarSpinner.setAdapter(mSpinnerAdapter);
        mToolbarSpinner.setOnItemSelectedListener(mTransactionListNavigationListener);
        getSupportActionBar().setDisplayHomeAsUpEnabled(true);

        updateNavigationSelection();
    }

    /**
     * Updates the action bar navigation list selection to that of the current account
     * whose transactions are being displayed/manipulated
     */
    public void updateNavigationSelection() {
        // set the selected item in the spinner
        int i = 0;
        Cursor accountsCursor = mAccountsDbAdapter.fetchAllRecordsOrderedByFullName();
        while (accountsCursor.moveToNext()) {
            String uid = accountsCursor
                    .getString(accountsCursor.getColumnIndexOrThrow(DatabaseSchema.AccountEntry.COLUMN_UID));
            if (mAccountUID.equals(uid)) {
                mToolbarSpinner.setSelection(i);
                break;
            }
            ++i;
        }
        accountsCursor.close();
    }

    @Override
    public boolean onPrepareOptionsMenu(Menu menu) {
        MenuItem favoriteAccountMenuItem = menu.findItem(R.id.menu_favorite_account);

        if (favoriteAccountMenuItem == null) //when the activity is used to edit a transaction
            return super.onPrepareOptionsMenu(menu);

        boolean isFavoriteAccount = AccountsDbAdapter.getInstance().isFavoriteAccount(mAccountUID);

        int favoriteIcon = isFavoriteAccount ? R.drawable.ic_star_white_24dp : R.drawable.ic_star_border_white_24dp;
        favoriteAccountMenuItem.setIcon(favoriteIcon);
        return super.onPrepareOptionsMenu(menu);

    }

    @Override
    public boolean onOptionsItemSelected(MenuItem item) {
        switch (item.getItemId()) {
        case android.R.id.home:
            return super.onOptionsItemSelected(item);

        case R.id.menu_favorite_account:
            AccountsDbAdapter accountsDbAdapter = AccountsDbAdapter.getInstance();
            long accountId = accountsDbAdapter.getID(mAccountUID);
            boolean isFavorite = accountsDbAdapter.isFavoriteAccount(mAccountUID);
            //toggle favorite preference
            accountsDbAdapter.updateAccount(accountId, DatabaseSchema.AccountEntry.COLUMN_FAVORITE,
                    isFavorite ? "0" : "1");
            supportInvalidateOptionsMenu();
            return true;

        case R.id.menu_edit_account:
            Intent editAccountIntent = new Intent(this, FormActivity.class);
            editAccountIntent.setAction(Intent.ACTION_INSERT_OR_EDIT);
            editAccountIntent.putExtra(UxArgument.SELECTED_ACCOUNT_UID, mAccountUID);
            editAccountIntent.putExtra(UxArgument.FORM_TYPE, FormActivity.FormType.ACCOUNT.name());
            startActivityForResult(editAccountIntent, AccountsActivity.REQUEST_EDIT_ACCOUNT);
            return true;

        default:
            return false;
        }
    }

    @Override
    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
        if (resultCode == RESULT_CANCELED)
            return;

        refresh();
        setupActionBarNavigation();
        super.onActivityResult(requestCode, resultCode, data);
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        mAccountsCursor.close();
    }

    /**
     * Returns the global unique ID of the current account
     * @return GUID of the current account
     */
    public String getCurrentAccountUID() {
        return mAccountUID;
    }

    /**
     * Display the balance of a transaction in a text view and format the text color to match the sign of the amount
     * @param balanceTextView {@link android.widget.TextView} where balance is to be displayed
     * @param balance {@link org.gnucash.android.model.Money} balance to display
     */
    public static void displayBalance(TextView balanceTextView, Money balance) {
        balanceTextView.setText(balance.formattedString());
        Context context = GnuCashApplication.getAppContext();
        int fontColor = balance.isNegative() ? context.getResources().getColor(R.color.debit_red)
                : context.getResources().getColor(R.color.credit_green);
        if (balance.asBigDecimal().compareTo(BigDecimal.ZERO) == 0)
            fontColor = context.getResources().getColor(android.R.color.black);
        balanceTextView.setTextColor(fontColor);
    }

    /**
     * Formats the date to show the the day of the week if the {@code dateMillis} is within 7 days
     * of today. Else it shows the actual date formatted as short string. <br>
     * It also shows "today", "yesterday" or "tomorrow" if the date is on any of those days
     * @param dateMillis
     * @return
     */
    @NonNull
    public static String getPrettyDateFormat(Context context, long dateMillis) {
        LocalDate transactionTime = new LocalDate(dateMillis);
        LocalDate today = new LocalDate();
        String prettyDateText = null;
        if (transactionTime.compareTo(today.minusDays(1)) >= 0
                && transactionTime.compareTo(today.plusDays(1)) <= 0) {
            prettyDateText = DateUtils
                    .getRelativeTimeSpanString(dateMillis, System.currentTimeMillis(), DateUtils.DAY_IN_MILLIS)
                    .toString();
        } else if (transactionTime.getYear() == today.getYear()) {
            prettyDateText = mDayMonthDateFormat.format(new Date(dateMillis));
        } else {
            prettyDateText = DateUtils.formatDateTime(context, dateMillis,
                    DateUtils.FORMAT_ABBREV_MONTH | DateUtils.FORMAT_SHOW_YEAR);
        }

        return prettyDateText;
    }

    @Override
    public void createNewTransaction(String accountUID) {
        Intent createTransactionIntent = new Intent(this.getApplicationContext(), FormActivity.class);
        createTransactionIntent.setAction(Intent.ACTION_INSERT_OR_EDIT);
        createTransactionIntent.putExtra(UxArgument.SELECTED_ACCOUNT_UID, accountUID);
        createTransactionIntent.putExtra(UxArgument.FORM_TYPE, FormActivity.FormType.TRANSACTION.name());
        startActivity(createTransactionIntent);
    }

    @Override
    public void editTransaction(String transactionUID) {
        Intent createTransactionIntent = new Intent(this.getApplicationContext(), FormActivity.class);
        createTransactionIntent.setAction(Intent.ACTION_INSERT_OR_EDIT);
        createTransactionIntent.putExtra(UxArgument.SELECTED_ACCOUNT_UID, mAccountUID);
        createTransactionIntent.putExtra(UxArgument.SELECTED_TRANSACTION_UID, transactionUID);
        createTransactionIntent.putExtra(UxArgument.FORM_TYPE, FormActivity.FormType.TRANSACTION.name());
        startActivity(createTransactionIntent);
    }

    @Override
    public void accountSelected(String accountUID) {
        Intent restartIntent = new Intent(this.getApplicationContext(), TransactionsActivity.class);
        restartIntent.setAction(Intent.ACTION_VIEW);
        restartIntent.putExtra(UxArgument.SELECTED_ACCOUNT_UID, accountUID);
        startActivity(restartIntent);
    }
}