com.money.manager.ex.investment.watchlist.WatchlistFragment.java Source code

Java tutorial

Introduction

Here is the source code for com.money.manager.ex.investment.watchlist.WatchlistFragment.java

Source

/*
 * 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.investment.watchlist;

import android.content.DialogInterface;
import android.content.Intent;
import android.database.Cursor;
import android.os.Bundle;
import android.support.annotation.NonNull;
import android.support.v4.app.Fragment;
import android.support.v4.app.FragmentTransaction;
import android.support.v7.app.ActionBar;
import android.support.v7.app.AppCompatActivity;
import android.text.TextUtils;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.View;
import android.view.View.OnClickListener;
import android.view.ViewGroup;
import android.widget.AdapterView;
import android.widget.ImageView;
import android.widget.SimpleCursorAdapter;
import android.widget.Spinner;
import android.widget.Toast;

import com.afollestad.materialdialogs.DialogAction;
import com.afollestad.materialdialogs.MaterialDialog;
import com.crashlytics.android.answers.Answers;
import com.crashlytics.android.answers.CustomEvent;
import com.mikepenz.google_material_typeface_library.GoogleMaterial;
import com.mikepenz.iconics.IconicsDrawable;
import com.mikepenz.mmex_icon_font_typeface_library.MMXIconFont;
import com.money.manager.ex.Constants;
import com.money.manager.ex.account.AccountEditActivity;
import com.money.manager.ex.R;
import com.money.manager.ex.core.AnswersEvents;
import com.money.manager.ex.core.UIHelper;
import com.money.manager.ex.datalayer.AccountRepository;
import com.money.manager.ex.datalayer.StockFields;
import com.money.manager.ex.datalayer.StockHistoryRepository;
import com.money.manager.ex.log.ErrorRaisedEvent;
import com.money.manager.ex.datalayer.StockRepository;
import com.money.manager.ex.domainmodel.Account;
import com.money.manager.ex.investment.ISecurityPriceUpdater;
import com.money.manager.ex.investment.PriceCsvExport;
import com.money.manager.ex.investment.QuoteProviders;
import com.money.manager.ex.investment.SecurityPriceUpdaterFactory;
import com.money.manager.ex.investment.events.AllPricesDownloadedEvent;
import com.money.manager.ex.investment.events.PriceDownloadedEvent;
import com.money.manager.ex.investment.events.PriceUpdateRequestEvent;
import com.money.manager.ex.servicelayer.AccountService;
import com.money.manager.ex.settings.InvestmentSettings;
import com.money.manager.ex.sync.SyncManager;
import com.shamanland.fonticon.FontIconDrawable;

import org.greenrobot.eventbus.EventBus;
import org.greenrobot.eventbus.Subscribe;
import org.parceler.Parcels;

import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Date;
import java.util.List;

import info.javaperformance.money.Money;
import timber.log.Timber;

/**
 * The main fragment for the watchlist. Contains the list and everything else.
 * Not sure why it was done in two fragments. Probably because the list can not have additional items?
 */
public class WatchlistFragment extends Fragment {

    private static final String KEY_ACCOUNT_ID = "WatchlistFragment:AccountId";
    private static final String KEY_ACCOUNT = "WatchlistFragment:Account";

    /**
     * @param accountId ID Account to be display
     * @return instance of Watchlist fragment with transactions for the given account.
     */
    public static WatchlistFragment newInstance(int accountId) {
        WatchlistFragment fragment = new WatchlistFragment();
        Bundle args = new Bundle();
        args.putInt(KEY_ACCOUNT_ID, accountId);
        fragment.setArguments(args);

        fragment.setFragmentName(WatchlistFragment.class.getSimpleName() + "_" + Integer.toString(accountId));

        return fragment;
    }

    private WatchlistItemsFragment mDataFragment;
    private String mFragmentName;
    private StockRepository mStockRepository;
    private Account mAccount;

    // price update counter. Used to know when all the prices are done downloading.
    private int mUpdateCounter;
    private int mToUpdateTotal;
    private WatchlistViewHolder viewHolder;

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

        loadAccount();

        if ((savedInstanceState != null)) {
            mAccount = Parcels.unwrap(savedInstanceState.getParcelable(KEY_ACCOUNT));
        }

        mUpdateCounter = 0;

        Answers.getInstance().logCustom(new CustomEvent(AnswersEvents.Watchlist.name()));
    }

    @Override
    public void onStart() {
        super.onStart();

        EventBus.getDefault().register(this);
    }

    @Override
    public void onStop() {
        super.onStop();

        EventBus.getDefault().unregister(this);
    }

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
        if ((savedInstanceState != null)) {
            mAccount = Parcels.unwrap(savedInstanceState.getParcelable(KEY_ACCOUNT));
        }

        if (container == null)
            return null;
        View view = inflater.inflate(R.layout.fragment_account_transactions, container, false);

        if (mAccount == null) {
            loadAccount();
        }

        this.viewHolder = new WatchlistViewHolder();

        initializeListHeader(inflater);

        // manage fragment
        FragmentTransaction transaction = getChildFragmentManager().beginTransaction();

        mDataFragment = WatchlistItemsFragment.newInstance();
        // set arguments and preferences of fragment
        Bundle arguments = new Bundle();
        arguments.putInt(WatchlistItemsFragment.KEY_ACCOUNT_ID, getAccountId());
        mDataFragment.setArguments(arguments);

        mDataFragment.setListHeader(this.viewHolder.mListHeader);
        mDataFragment.setAutoStarLoader(false);

        // add fragment
        transaction.replace(R.id.fragmentMain, mDataFragment, getFragmentName());
        transaction.commit();

        // refresh user interface
        if (mAccount != null) {
            setImageViewFavorite();
        }
        setHasOptionsMenu(true);

        //        initializeSwipeToRefresh(view);

        return view;
    }

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

        // hide the title
        ActionBar actionBar = getActionBar();
        if (actionBar != null) {
            actionBar.setDisplayShowTitleEnabled(false);
        }
    }

    @Override
    public void onResume() {
        super.onResume();

        initializeAccountsSelector();
        selectCurrentAccount();

        // restart loader
        reloadData();
    }

    // Menu

    /**
     * Called once when the menu is being created.
     */
    @Override
    public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
        super.onCreateOptionsMenu(menu, inflater);

        // add options menu for watchlist
        inflater.inflate(R.menu.menu_watchlist, menu);

        // custom icons

        UIHelper uiHelper = new UIHelper(getActivity());
        IconicsDrawable icon;
        MenuItem menuItem;

        menuItem = menu.findItem(R.id.menu_update_prices);
        if (menuItem != null) {
            icon = uiHelper.getIcon(GoogleMaterial.Icon.gmd_file_download);
            menuItem.setIcon(icon);
        }

        menuItem = menu.findItem(R.id.menu_export_prices);
        if (menuItem != null) {
            icon = uiHelper.getIcon(GoogleMaterial.Icon.gmd_share);
            menuItem.setIcon(icon);
        }

        menuItem = menu.findItem(R.id.menu_purge_history);
        if (menuItem != null) {
            icon = uiHelper.getIcon(GoogleMaterial.Icon.gmd_content_cut);
            menuItem.setIcon(icon);
        }

        // call create option menu of fragment
        mDataFragment.onCreateOptionsMenu(menu, inflater);
    }

    /**
     * Handle menu item click.
     * Update prices.
     * @param item Menu item selected
     * @return indicator whether the selection was handled
     */
    @Override
    public boolean onOptionsItemSelected(MenuItem item) {
        int itemId = item.getItemId();
        switch (itemId) {
        case R.id.menu_update_prices:
            confirmPriceUpdate();
            break;
        case R.id.menu_export_prices:
            exportPrices();
            break;
        case R.id.menu_purge_history:
            purgePriceHistory();
            break;
        case R.id.menu_change_provider:
            changePriceProvider();
            break;
        default:
            break;
        }

        return super.onOptionsItemSelected(item);
    }

    @Override
    public void onSaveInstanceState(Bundle outState) {
        super.onSaveInstanceState(outState);

        outState.putParcelable(KEY_ACCOUNT, Parcels.wrap(mAccount));
    }

    // Events

    @Subscribe
    public void onEvent(AllPricesDownloadedEvent event) {
        reloadData();
    }

    @Subscribe
    public void onEvent(PriceDownloadedEvent event) {
        onPriceDownloaded(event.symbol, event.price, event.date);
    }

    @Subscribe
    public void onEvent(PriceUpdateRequestEvent event) {
        onPriceUpdateRequested(event.symbol);
    }

    @Subscribe
    public void onEvent(ErrorRaisedEvent event) {
        new UIHelper(getActivity()).showToast(event.message);
    }

    /**
     * Start Loader to retrieve data
     */
    public void reloadData() {
        if (mDataFragment != null) {
            mDataFragment.reloadData();
        }
    }

    public String getFragmentName() {
        return mFragmentName;
    }

    public void setFragmentName(String fragmentName) {
        this.mFragmentName = fragmentName;
    }

    // Private

    /**
     * Called from asynchronous task when a first price is downloaded.
     * @param symbol Stock symbol
     * @param price Stock price
     * @param date Date of the price
     */
    private void onPriceDownloaded(String symbol, Money price, Date date) {
        // prices updated.

        if (TextUtils.isEmpty(symbol))
            return;

        // update the current price of the stock.
        StockRepository repo = getStockRepository();
        repo.updateCurrentPrice(symbol, price);

        // update price history record.
        StockHistoryRepository historyRepo = mDataFragment.getStockHistoryRepository();
        historyRepo.addStockHistoryRecord(symbol, price, date);

        mUpdateCounter += 1;
        if (mUpdateCounter == mToUpdateTotal) {
            completePriceUpdate();
        }
    }

    /**
     * Price update requested from the securities list context menu.
     * @param symbol Stock symbol for which to fetch the price.
     */
    private void onPriceUpdateRequested(String symbol) {
        // reset counter & max.
        mToUpdateTotal = 1;
        mUpdateCounter = 0;

        // http://stackoverflow.com/questions/1005073/initialization-of-an-arraylist-in-one-line
        List<String> symbols = new ArrayList<>();
        symbols.add(symbol);

        ISecurityPriceUpdater updater = SecurityPriceUpdaterFactory.getUpdaterInstance(getActivity());
        updater.downloadPrices(symbols);
        // result received via onEvent.
    }

    /**
     * refresh UI, show favorite icon
     */
    private void setImageViewFavorite() {
        if (mAccount.getFavorite()) {
            this.viewHolder.imgAccountFav.setBackgroundResource(R.drawable.ic_star);
        } else {
            this.viewHolder.imgAccountFav.setBackgroundResource(R.drawable.ic_star_outline);
        }
    }

    private void changePriceProvider() {
        // show the list with provider choices. Preselect the current one.
        InvestmentSettings settings = new InvestmentSettings(getActivity());
        QuoteProviders currentProvider = settings.getQuoteProvider();
        int currentIndex = QuoteProviders.indexOf(currentProvider);

        new MaterialDialog.Builder(getActivity()).title(R.string.quote_provider).items(QuoteProviders.names())
                .itemsCallbackSingleChoice(currentIndex, new MaterialDialog.ListCallbackSingleChoice() {
                    @Override
                    public boolean onSelection(MaterialDialog dialog, View itemView, int which, CharSequence text) {
                        //change provider
                        QuoteProviders newProvider = QuoteProviders.valueOf(text.toString());

                        InvestmentSettings settings = new InvestmentSettings(getActivity());
                        settings.setQuoteProvider(newProvider);
                        return true;
                    }
                }).show();
    }

    private void completePriceUpdate() {
        // this call is made from async task so have to get back to the main thread.
        getActivity().runOnUiThread(new Runnable() {
            @Override
            public void run() {
                // refresh the data.
                mDataFragment.reloadData();

                // notify about db file change.
                new SyncManager(getActivity()).dataChanged();
            }
        });
    }

    private StockRepository getStockRepository() {
        if (mStockRepository == null) {
            mStockRepository = new StockRepository(getActivity());
        }
        return mStockRepository;
    }

    private String[] getAllShownSymbols() {
        int itemCount = mDataFragment.getListAdapter().getCount();
        String[] result = new String[itemCount];

        for (int i = 0; i < itemCount; i++) {
            Cursor cursor = (Cursor) mDataFragment.getListAdapter().getItem(i);
            String symbol = cursor.getString(cursor.getColumnIndex(StockFields.SYMBOL));

            result[i] = symbol;
        }

        return result;
    }

    private void exportPrices() {
        PriceCsvExport export = new PriceCsvExport(getActivity());
        boolean result = false;

        try {
            String prefix;
            if (mAccount != null) {
                prefix = mAccount.getName();
            } else {
                prefix = getActivity().getString(R.string.all_accounts);
            }
            result = export.exportPrices(mDataFragment.getListAdapter(), prefix);
        } catch (IOException ex) {
            Timber.e(ex, "exporting stock prices");
        }

        // todo: e result. (?)
    }

    private void confirmPriceUpdate() {
        new MaterialDialog.Builder(getContext()).title(R.string.download)
                .icon(FontIconDrawable.inflate(getContext(), R.xml.ic_question))
                .content(R.string.confirm_price_download).positiveText(android.R.string.ok)
                .onPositive(new MaterialDialog.SingleButtonCallback() {
                    @Override
                    public void onClick(@NonNull MaterialDialog dialog, @NonNull DialogAction which) {
                        // get the list of symbols
                        String[] symbols = getAllShownSymbols();
                        mToUpdateTotal = symbols.length;
                        mUpdateCounter = 0;

                        // update security prices
                        ISecurityPriceUpdater updater = SecurityPriceUpdaterFactory
                                .getUpdaterInstance(getContext());
                        updater.downloadPrices(Arrays.asList(symbols));
                        // results received via event

                        dialog.dismiss();
                    }
                }).negativeText(android.R.string.cancel).onNegative(new MaterialDialog.SingleButtonCallback() {
                    @Override
                    public void onClick(@NonNull MaterialDialog dialog, @NonNull DialogAction which) {
                        dialog.dismiss();
                    }
                }).build().show();
    }

    private int getAccountId() {
        if (mAccount == null) {
            return Constants.NOT_SET;
        }

        return mAccount.getId();
    }

    private ActionBar getActionBar() {
        if (!(getActivity() instanceof AppCompatActivity))
            return null;

        AppCompatActivity activity = (AppCompatActivity) getActivity();
        ActionBar actionBar = activity.getSupportActionBar();
        return actionBar;
    }

    private Spinner getAccountsSpinner() {
        // get from custom view, not the menu.

        ActionBar actionBar = getActionBar();
        if (actionBar == null)
            return null;

        Spinner spinner = (Spinner) actionBar.getCustomView().findViewById(R.id.spinner);
        return spinner;
    }

    private void initializeAccountsSelector() {
        ActionBar actionBar = getActionBar();
        if (actionBar == null)
            return;

        actionBar.setDisplayShowTitleEnabled(false);

        actionBar.setCustomView(R.layout.spinner);
        actionBar.setDisplayShowCustomEnabled(true);

        Spinner spinner = getAccountsSpinner();
        if (spinner == null)
            return;

        // Load accounts into the spinner.
        AccountService accountService = new AccountService(getActivity());
        accountService.loadInvestmentAccountsToSpinner(spinner, true);

        // e account switching.
        spinner.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
            @Override
            public void onItemSelected(AdapterView<?> adapterView, View view, int i, long l) {
                // switch account.
                Cursor cursor = (Cursor) adapterView.getItemAtPosition(i);
                Account account = Account.from(cursor);

                int accountId = account.getId();
                switchAccount(accountId);
            }

            @Override
            public void onNothingSelected(AdapterView<?> adapterView) {

            }
        });
    }

    private void initializeListHeader(LayoutInflater inflater) {
        this.viewHolder.mListHeader = (ViewGroup) inflater.inflate(R.layout.fragment_watchlist_header, null, false);

        // favorite icon
        this.viewHolder.imgAccountFav = (ImageView) this.viewHolder.mListHeader
                .findViewById(R.id.imageViewAccountFav);
        // set listener click on favorite icon for change image
        this.viewHolder.imgAccountFav.setOnClickListener(new OnClickListener() {
            public void onClick(View v) {
                mAccount.setFavorite(!mAccount.getFavorite());

                AccountRepository repo = new AccountRepository(getActivity());
                boolean saved = repo.save(mAccount);

                if (!saved) {
                    Toast.makeText(getActivity(), getActivity().getResources().getString(R.string.db_update_failed),
                            Toast.LENGTH_LONG).show();
                } else {
                    setImageViewFavorite();
                }
            }
        });

        // Edit account
        this.viewHolder.imgGotoAccount = (ImageView) this.viewHolder.mListHeader
                .findViewById(R.id.imageViewGotoAccount);
        this.viewHolder.imgGotoAccount.setOnClickListener(new OnClickListener() {
            @Override
            public void onClick(View v) {
                Intent intent = new Intent(getActivity(), AccountEditActivity.class);
                intent.putExtra(AccountEditActivity.KEY_ACCOUNT_ID, getAccountId());
                intent.setAction(Intent.ACTION_EDIT);
                startActivity(intent);
            }
        });
    }

    //    private void initializeSwipeToRefresh(View view) {
    //        final SwipeRefreshLayout layout = (SwipeRefreshLayout) view.findViewById(R.id.swipeLayout);
    //
    //        layout.setOnRefreshListener(new SwipeRefreshLayout.OnRefreshListener() {
    //            @Override
    //            public void onRefresh() {
    //                // update prices
    //                // todo: do not display the manual progress binaryDialog
    //                String[] symbols = getAllShownSymbols();
    //                ISecurityPriceUpdater updater = SecurityPriceUpdaterFactory
    //                        .getUpdaterInstance(getContext());
    //                updater.downloadPrices(Arrays.asList(symbols));
    //
    //                layout.setRefreshing(false);
    //            }
    //        });
    //    }

    private void loadAccount() {
        Bundle args = getArguments();
        if (args == null)
            return;
        if (!args.containsKey(KEY_ACCOUNT_ID))
            return;

        int accountId = args.getInt(KEY_ACCOUNT_ID);
        this.mAccount = new AccountRepository(getActivity()).load(accountId);
    }

    private void purgePriceHistory() {
        new MaterialDialog.Builder(getContext()).title(R.string.purge_history)
                .icon(FontIconDrawable.inflate(getContext(), R.xml.ic_question))
                .content(R.string.purge_history_confirmation).positiveText(android.R.string.ok)
                .onPositive(new MaterialDialog.SingleButtonCallback() {
                    @Override
                    public void onClick(@NonNull MaterialDialog dialog, @NonNull DialogAction which) {
                        StockHistoryRepository history = new StockHistoryRepository(getActivity());
                        int deleted = history.deleteAllPriceHistory();

                        if (deleted > 0) {
                            new SyncManager(getActivity()).dataChanged();
                            Toast.makeText(getActivity(), getActivity().getString(R.string.purge_history_complete),
                                    Toast.LENGTH_SHORT).show();
                        } else {
                            Toast.makeText(getActivity(), getActivity().getString(R.string.purge_history_failed),
                                    Toast.LENGTH_SHORT).show();
                        }

                        dialog.dismiss();
                    }
                }).negativeText(android.R.string.cancel).onNegative(new MaterialDialog.SingleButtonCallback() {
                    @Override
                    public void onClick(@NonNull MaterialDialog dialog, @NonNull DialogAction which) {
                        dialog.dismiss();
                    }
                }).build().show();
    }

    /**
     * Select the current account in the accounts dropdown.
     */
    private void selectCurrentAccount() {
        Spinner spinner = getAccountsSpinner();
        if (spinner == null)
            return;

        // find account
        SimpleCursorAdapter adapter = (SimpleCursorAdapter) spinner.getAdapter();
        if (adapter == null)
            return;

        Cursor cursor = adapter.getCursor();
        int position = Constants.NOT_SET;

        for (int i = 0; i < adapter.getCount(); i++) {
            cursor.moveToPosition(i);
            String accountIdString = cursor.getString(cursor.getColumnIndex(Account.ACCOUNTID));
            int accountId = Integer.parseInt(accountIdString);
            if (accountId == getAccountId()) {
                position = i;
                break;
            }
        }

        spinner.setSelection(position);
    }

    private void switchAccount(int accountId) {
        if (accountId == getAccountId())
            return;

        // switch account. Reload transactions.
        mAccount = new AccountRepository(getActivity()).load(accountId);

        mDataFragment.accountId = accountId;
        mDataFragment.reloadData();

        // hide account details bar if all accounts are selected
        if (accountId == Constants.NOT_SET) {
            /*
            Can't remove header view once it has been added.
            Ref: http://stackoverflow.com/questions/13603888/remove-header-from-listview
             */
            //            mDataFragment.getListView().removeHeaderView(mListHeader);
            //            mListHeader.setVisibility(View.GONE);
            /*
            Just hide the contents and the row will automatically shrink (but not disappear).
             */
            this.viewHolder.mListHeader.findViewById(R.id.headerRow).setVisibility(View.GONE);
        } else {
            if (mDataFragment.getListView().getHeaderViewsCount() == 0) {
                mDataFragment.getListView().addHeaderView(this.viewHolder.mListHeader);
            }
            //            mListHeader.setVisibility(View.VISIBLE);
            this.viewHolder.mListHeader.findViewById(R.id.headerRow).setVisibility(View.VISIBLE);
        }
    }
}