/* * 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.Context; 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.LoaderManager.LoaderCallbacks; import android.support.v4.content.Loader; import android.support.v4.widget.CursorAdapter; import android.support.v7.app.ActionBar; import android.text.TextUtils; import android.view.ContextMenu; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.widget.AdapterView; import android.widget.AdapterView.OnItemClickListener; import android.widget.TextView; import com.afollestad.materialdialogs.DialogAction; import com.afollestad.materialdialogs.MaterialDialog; import com.money.manager.ex.Constants; import com.money.manager.ex.R; import com.money.manager.ex.common.MmxBaseFragmentActivity; import com.money.manager.ex.core.ContextMenuIds; import com.money.manager.ex.core.FormatUtilities; import com.money.manager.ex.core.MenuHelper; import com.money.manager.ex.core.UIHelper; import com.money.manager.ex.datalayer.AccountRepository; import com.money.manager.ex.datalayer.Select; import com.money.manager.ex.datalayer.StockFields; import com.money.manager.ex.datalayer.StockHistoryRepository; import com.money.manager.ex.common.AllDataListFragment; import com.money.manager.ex.common.BaseListFragment; import com.money.manager.ex.common.MmxCursorLoader; import com.money.manager.ex.datalayer.StockRepository; import com.money.manager.ex.domainmodel.Account; import com.money.manager.ex.domainmodel.Stock; import com.money.manager.ex.investment.EditPriceDialog; import com.money.manager.ex.investment.InvestmentTransactionEditActivity; import com.money.manager.ex.investment.StocksCursorAdapter; import com.money.manager.ex.investment.events.PriceUpdateRequestEvent; import com.money.manager.ex.utils.MmxDate; import com.shamanland.fonticon.FontIconDrawable; import org.greenrobot.eventbus.EventBus; import java.lang.reflect.Field; import java.util.ArrayList; import info.javaperformance.money.Money; import timber.log.Timber; /** * The list of securities. */ public class WatchlistItemsFragment extends BaseListFragment implements LoaderCallbacks<Cursor> { public static final int ID_LOADER = 1; public static final String KEY_ACCOUNT_ID = "WatchlistItemsFragment:AccountId"; /** * Create a new instance of the fragment with accountId params * @return new instance AllDataListFragment */ public static WatchlistItemsFragment newInstance() { WatchlistItemsFragment fragment = new WatchlistItemsFragment(); return fragment; } // non-static public Integer accountId; private boolean mAutoStarLoader = true; private View mListHeader = null; private StockRepository mStockRepository; private StockHistoryRepository mStockHistoryRepository; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); // setHasOptionsMenu(true); if (savedInstanceState != null && savedInstanceState.containsKey(KEY_ACCOUNT_ID)) { // get data from saved instance state this.accountId = savedInstanceState.getInt(KEY_ACCOUNT_ID); } else { this.accountId = getArguments().getInt(KEY_ACCOUNT_ID); } } @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { // return super.onCreateView(inflater, container, savedInstanceState); View layout = inflater.inflate(R.layout.fragment_watchlist_item_list, container, false); return layout; } @Override public void onActivityCreated(Bundle savedInstanceState) { super.onActivityCreated(savedInstanceState); // set fragment setEmptyText(getString(R.string.no_stock_data)); setListShown(false); Context context = getActivity(); mStockRepository = new StockRepository(context); // create adapter StocksCursorAdapter adapter = new StocksCursorAdapter(context, null); // e list item click. getListView().setOnItemClickListener(new OnItemClickListener() { @Override public void onItemClick(AdapterView<?> parent, View view, int position, long id) { // Ignore the header row. if (getListView().getHeaderViewsCount() > 0 && position == 0) return; if (getListAdapter() != null && getListAdapter() instanceof StocksCursorAdapter) { getActivity().openContextMenu(view); } } }); // if header is not null add to list view if (getListAdapter() == null) { if (mListHeader != null) { getListView().addHeaderView(mListHeader); } else { getListView().removeHeaderView(mListHeader); } } // set adapter setListAdapter(adapter); // register context menu registerForContextMenu(getListView()); // start loader if (isAutoStarLoader()) { reloadData(); } setFloatingActionButtonVisible(true); attachFloatingActionButtonToListView(); } // context menu @Override public void onCreateContextMenu(ContextMenu menu, View v, ContextMenu.ContextMenuInfo menuInfo) { AdapterView.AdapterContextMenuInfo info = (AdapterView.AdapterContextMenuInfo) menuInfo; // ignore the header row if the headers are shown. if (hasHeaderRow() && info.position == 0) return; Cursor cursor = ((StocksCursorAdapter) getListAdapter()).getCursor(); int cursorPosition = hasHeaderRow() ? info.position - 1 : info.position; cursor.moveToPosition(cursorPosition); menu.setHeaderTitle(cursor.getString(cursor.getColumnIndex(StockFields.SYMBOL))); MenuHelper menuHelper = new MenuHelper(getActivity(), menu); menuHelper.addToContextMenu(ContextMenuIds.DownloadPrice); menuHelper.addToContextMenu(ContextMenuIds.EditPrice); menuHelper.addToContextMenu(ContextMenuIds.DELETE); } /** * Context menu click handler. Update individual price. * @param item selected context menu item. * @return indicator whether the action is handled or not. */ @Override public boolean onContextItemSelected(android.view.MenuItem item) { ContextMenu.ContextMenuInfo menuInfo = item.getMenuInfo(); if (!(menuInfo instanceof AdapterView.AdapterContextMenuInfo)) return false; AdapterView.AdapterContextMenuInfo info = (AdapterView.AdapterContextMenuInfo) menuInfo; // ExpandableListView.ExpandableListContextMenuInfo info = (ExpandableListView.ExpandableListContextMenuInfo) ; Cursor cursor = ((StocksCursorAdapter) getListAdapter()).getCursor(); // long packedPosition = hasHeaderRow() ? info.packedPosition - 1 : info.packedPosition; int cursorPosition = hasHeaderRow() ? info.position - 1 : info.position; cursor.moveToPosition(cursorPosition); Stock stock = Stock.from(cursor); String symbol = stock.getSymbol(); boolean result = false; ContextMenuIds menuId = ContextMenuIds.get(item.getItemId()); switch (menuId) { case DownloadPrice: // Update price EventBus.getDefault().post(new PriceUpdateRequestEvent(symbol)); result = true; break; case EditPrice: // Edit price int accountId = stock.getHeldAt(); Money currentPrice = stock.getCurrentPrice(); //todo Intent intent = IntentFactory.getPriceEditIntent(getActivity()); EditPriceDialog dialog = new EditPriceDialog(); Bundle args = new Bundle(); args.putInt(EditPriceDialog.ARG_ACCOUNT, accountId); args.putString(EditPriceDialog.ARG_SYMBOL, symbol); args.putString(EditPriceDialog.ARG_PRICE, currentPrice.toString()); String dateString = new MmxDate().toIsoString(); args.putString(EditPriceDialog.ARG_DATE, dateString); dialog.setArguments(args); dialog.show(getChildFragmentManager(), "input-amount"); break; case DELETE: showDeleteConfirmationDialog(stock.getId()); break; } return result; } /** * This is just to test: * http://stackoverflow.com/questions/15207305/getting-the-error-java-lang-illegalstateexception-activity-has-been-destroyed */ @Override public void onDetach() { super.onDetach(); try { Field childFragmentManager = Fragment.class.getDeclaredField("mChildFragmentManager"); childFragmentManager.setAccessible(true); childFragmentManager.set(this, null); } catch (Exception e) { // NoSuchFieldException | IllegalAccessException throw new RuntimeException(e); } } @Override public void onDestroy() { super.onDestroy(); } // Loader @Override public Loader<Cursor> onCreateLoader(int id, Bundle args) { MmxCursorLoader result; //animation setListShown(false); switch (id) { case ID_LOADER: // compose selection and sort String selection = ""; if (args != null && args.containsKey(AllDataListFragment.KEY_ARGUMENTS_WHERE)) { ArrayList<String> whereClause = args.getStringArrayList(AllDataListFragment.KEY_ARGUMENTS_WHERE); if (whereClause != null) { for (int i = 0; i < whereClause.size(); i++) { selection += (!TextUtils.isEmpty(selection) ? " AND " : "") + whereClause.get(i); } } } // set sort String sort = ""; if (args != null && args.containsKey(AllDataListFragment.KEY_ARGUMENTS_SORT)) { sort = args.getString(AllDataListFragment.KEY_ARGUMENTS_SORT); } Select query = new Select(mStockRepository.getAllColumns()) .where(selection) .orderBy(sort); result = new MmxCursorLoader(getActivity(), mStockRepository.getUri(), query); break; default: result = null; } return result; } @Override public void onLoaderReset(Loader<Cursor> loader) { // reset the cursor reference to reduce memory leaks ((CursorAdapter) getListAdapter()).changeCursor(null); // ((CursorAdapter) getListAdapter()).swapCursor(null); } @Override public void onLoadFinished(Loader<Cursor> loader, Cursor data) { switch (loader.getId()) { case ID_LOADER: // send the data to the view adapter. StocksCursorAdapter adapter = (StocksCursorAdapter) getListAdapter(); adapter.changeCursor(data); if (isResumed()) { setListShown(true); if (getFloatingActionButton() != null) { getFloatingActionButton().show(true); } } else { setListShownNoAnimation(true); } // update the header displayHeaderData(); } } @Override public void onFloatingActionButtonClicked() { openEditInvestmentActivity(); } @Override public void onSaveInstanceState(Bundle saveInstanceState) { super.onSaveInstanceState(saveInstanceState); saveInstanceState.putInt(KEY_ACCOUNT_ID, this.accountId); } @Override public void onStop() { super.onStop(); try { MmxBaseFragmentActivity activity = (MmxBaseFragmentActivity) getActivity(); if (activity != null) { ActionBar actionBar = activity.getSupportActionBar(); if(actionBar != null) { View customView = actionBar.getCustomView(); if (customView != null) { actionBar.setCustomView(null); } } } } catch (Exception e) { Timber.e(e, "stopping watchlist items fragment"); } } @Override public String getSubTitle() { return null; } public boolean isAutoStarLoader() { return mAutoStarLoader; } /** * Start loader into fragment */ public void reloadData() { Bundle arguments = prepareArgsForChildFragment(); // mLoaderArgs getLoaderManager().restartLoader(ID_LOADER, arguments, this); } /** * @param mAutoStarLoader the mAutoStarLoader to set */ public void setAutoStarLoader(boolean mAutoStarLoader) { this.mAutoStarLoader = mAutoStarLoader; } public void setListHeader(View mHeaderList) { this.mListHeader = mHeaderList; } public StockHistoryRepository getStockHistoryRepository() { if (mStockHistoryRepository == null) { mStockHistoryRepository = new StockHistoryRepository(getActivity()); } return mStockHistoryRepository; } // Private private void displayHeaderData() { TextView label = (TextView) getView().findViewById(R.id.cashBalanceLabel); TextView textView = (TextView) getView().findViewById(R.id.cashBalanceTextView); if (label == null || textView == null) return; // Clear if no account id, i.e. all accounts displayed. if (this.accountId == Constants.NOT_SET) { label.setText(""); textView.setText(""); return; } AccountRepository repo = new AccountRepository(getActivity()); Account account = repo.load(this.accountId); label.setText(getString(R.string.cash)); if (account == null) return; FormatUtilities formatter = new FormatUtilities(getActivity()); textView.setText(formatter.format( account.getInitialBalance(), account.getCurrencyId())); } private boolean hasHeaderRow() { return getListView().getHeaderViewsCount() > 0; } private Bundle prepareArgsForChildFragment() { ArrayList<String> selection = new ArrayList<>(); if (this.accountId != Constants.NOT_SET) { selection.add(StockFields.HELDAT + "=" + Integer.toString(this.accountId)); } Bundle args = new Bundle(); args.putStringArrayList(AllDataListFragment.KEY_ARGUMENTS_WHERE, selection); args.putString(AllDataListFragment.KEY_ARGUMENTS_SORT, StockFields.SYMBOL + " ASC"); return args; } private void openEditInvestmentActivity() { Intent intent = new Intent(getActivity(), InvestmentTransactionEditActivity.class); intent.putExtra(InvestmentTransactionEditActivity.ARG_ACCOUNT_ID, this.accountId); intent.setAction(Intent.ACTION_INSERT); startActivity(intent); } private void showDeleteConfirmationDialog(final int id) { new MaterialDialog.Builder(getActivity()) .title(R.string.delete_transaction) .icon(FontIconDrawable.inflate(getContext(), R.xml.ic_question)) .content(R.string.confirmDelete) .positiveText(android.R.string.ok) .onPositive(new MaterialDialog.SingleButtonCallback() { @Override public void onClick(@NonNull MaterialDialog dialog, @NonNull DialogAction which) { StockRepository repo = new StockRepository(getActivity()); if (!repo.delete(id)) { new UIHelper(getActivity()).showToast(R.string.db_delete_failed); } // restart loader reloadData(); } }) .negativeText(android.R.string.cancel) .onNegative(new MaterialDialog.SingleButtonCallback() { @Override public void onClick(@NonNull MaterialDialog dialog, @NonNull DialogAction which) { // close binaryDialog dialog.cancel(); } }) .show(); } }