package com.balch.mocktrade; import android.Manifest; import android.app.Activity; import android.content.BroadcastReceiver; import android.content.Context; import android.content.DialogInterface; import android.content.Intent; import android.content.IntentFilter; import android.content.pm.ActivityInfo; import android.content.pm.PackageManager; import android.content.res.ColorStateList; import android.graphics.drawable.Drawable; import android.os.AsyncTask; import android.os.Bundle; import android.os.Handler; import android.os.Looper; import android.support.annotation.NonNull; import android.support.design.widget.AppBarLayout; import android.support.design.widget.Snackbar; import android.support.v4.app.ActivityCompat; import android.support.v4.app.LoaderManager; import android.support.v4.content.ContextCompat; import android.support.v4.content.Loader; import android.support.v4.content.LocalBroadcastManager; import android.support.v4.graphics.drawable.DrawableCompat; import android.support.v7.app.AlertDialog; import android.support.v7.widget.Toolbar; import android.text.TextUtils; import android.util.Log; import android.view.Menu; import android.view.MenuItem; import android.widget.Toast; import com.balch.android.app.framework.PresenterActivity; import com.balch.android.app.framework.domain.EditActivity; import com.balch.android.app.framework.types.Money; import com.balch.mocktrade.account.Account; import com.balch.mocktrade.account.AccountEditController; import com.balch.mocktrade.investment.Investment; import com.balch.mocktrade.order.Order; import com.balch.mocktrade.order.OrderEditController; import com.balch.mocktrade.order.OrderListActivity; import com.balch.mocktrade.portfolio.AccountViewHolder; import com.balch.mocktrade.portfolio.GraphDataLoader; import com.balch.mocktrade.portfolio.PortfolioAdapter; import com.balch.mocktrade.portfolio.PortfolioData; import com.balch.mocktrade.portfolio.PortfolioLoader; import com.balch.mocktrade.portfolio.PortfolioModel; import com.balch.mocktrade.portfolio.PortfolioSqliteModel; import com.balch.mocktrade.services.PerformanceItemUpdateBroadcaster; import com.balch.mocktrade.services.QuoteService; import com.balch.mocktrade.settings.Settings; import com.balch.mocktrade.settings.SettingsActivity; import com.balch.mocktrade.shared.PerformanceItem; import java.lang.ref.WeakReference; import java.util.Date; import java.util.List; public class MainActivity extends PresenterActivity<MainPortfolioView, TradeModelProvider> { private static final String TAG = MainActivity.class.getSimpleName(); private static final int PORTFOLIO_LOADER_ID = 0; private static final int GRAPH_LOADER_ID = 1; private static final int NEW_ACCOUNT_RESULT = 0; private static final int NEW_ORDER_RESULT = 1; private static final int PERMS_REQUEST_BACKUP = 0; private static final int PERMS_REQUEST_RESTORE = 1; private PortfolioModel mPortfolioModel; private PortfolioAdapter mPortfolioAdapter; private MenuItem mMenuProgressBar; private MenuItem mMenuRefreshButton; private MenuItem mMenuHideExcludeAccounts; private MenuItem mMenuDemoMode; private Handler mUIHandler = new Handler(Looper.getMainLooper()); private Settings mSettings; private GraphDataLoader mGraphDataLoader; private PortfolioLoader mPortfolioLoader; private AccountUpdateReceiver mAccountUpdateReceiver; private LoaderManager.LoaderCallbacks<PortfolioData> mPortfolioDataLoaderCallback = new LoaderManager.LoaderCallbacks<PortfolioData>() { @Override public Loader<PortfolioData> onCreateLoader(int id, Bundle args) { return new PortfolioLoader(MainActivity.this, mPortfolioModel, mSettings); } @Override public void onLoadFinished(Loader<PortfolioData> loader, PortfolioData data) { PerformanceItem performanceItem = new PerformanceItem(-1, new Date(), new Money(), new Money(), new Money()); int accountsWithTotals = 0; Date timestamp = new Date(); boolean demoMode = mSettings.getBoolean(Settings.Key.PREF_DEMO_MODE); for (Account account : data.getAccounts()) { if (demoMode || !account.getExcludeFromTotals()) { List<Investment> investments = data.getInvestments(account.getId()); performanceItem.aggregate(account.getPerformanceItem(investments, timestamp)); accountsWithTotals++; } } view.setSyncTimes(data.getLastSyncTime(), data.getLastQuoteTime()); ViewProvider viewProvider = (ViewProvider) getApplication(); boolean showTotals = (!viewProvider.isLandscape(MainActivity.this) || viewProvider.isTablet(MainActivity.this)) && (accountsWithTotals > 1); view.setTotals(showTotals, performanceItem); mPortfolioAdapter.bind(data); view.setDailyGraphDataAccounts(data.getAccounts()); hideProgress(); } @Override public void onLoaderReset(Loader<PortfolioData> loader) { mPortfolioAdapter.clear(true); } }; private LoaderManager.LoaderCallbacks<List<PerformanceItem>> mGraphDataLoaderCallback = new LoaderManager.LoaderCallbacks<List<PerformanceItem>>() { @Override public Loader<List<PerformanceItem>> onCreateLoader(int id, Bundle args) { return new GraphDataLoader(MainActivity.this, mPortfolioModel); } @Override public void onLoadFinished(Loader<List<PerformanceItem>> loader, List<PerformanceItem> data) { view.setDailyGraphData(data); // mMainPortfolioView.setDailyGraphData(generateRandomTestData()); hideProgress(); } @Override public void onLoaderReset(Loader<List<PerformanceItem>> loader) { } }; @Override public void onCreateBase(Bundle bundle) { ViewProvider viewProvider = (ViewProvider) getApplication(); if (viewProvider.isTablet(this)) { setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE); } Toolbar toolbar = (Toolbar) findViewById(R.id.portfolio_view_toolbar); if (toolbar != null) { toolbar.setTitle(""); setSupportActionBar(toolbar); // tint the overflow icon ColorStateList colorSelector = ContextCompat.getColorStateList(this, R.color.nav_on_color); Drawable icon = toolbar.getOverflowIcon(); if (icon != null) { DrawableCompat.setTintList(icon, colorSelector); toolbar.setOverflowIcon(icon); } } setupAdapter(); final AppBarLayout appBarLayout = (AppBarLayout) findViewById(R.id.portfolio_view_app_bar); if (appBarLayout != null) { if (bundle == null) { mUIHandler.postDelayed(new Runnable() { @Override public void run() { appBarLayout.setExpanded(false); } }, 500); } else { appBarLayout.setExpanded(false, false); } } } @Override public MainPortfolioView createView() { return new MainPortfolioView(this, new MainPortfolioView.MainPortfolioViewListener() { @Override public void onGraphSelectionChanged(long accountId, int daysToReturn) { mGraphDataLoader.setSelectionCriteria(accountId, daysToReturn); } }); } @Override protected void createModel(TradeModelProvider modelProvider) { mSettings = modelProvider.getSettings(); mPortfolioModel = new PortfolioSqliteModel(modelProvider.getContext(), modelProvider.getSqlConnection(), modelProvider.getNetworkRequestProvider(), modelProvider.getSettings()); } @Override public void onResumeBase() { // onRestoreInstanceState is called after onStart but before OnResume // creating the loaders in onResume ensure the views are correctly restored if ((mPortfolioLoader == null) || (mGraphDataLoader == null)) { LoaderManager loaderManager = getSupportLoaderManager(); mPortfolioLoader = (PortfolioLoader) loaderManager.initLoader(PORTFOLIO_LOADER_ID, null, mPortfolioDataLoaderCallback); mGraphDataLoader = (GraphDataLoader) loaderManager.initLoader(GRAPH_LOADER_ID, null, mGraphDataLoaderCallback); } } @Override public void onStartBase() { if (mAccountUpdateReceiver == null) { mAccountUpdateReceiver = new AccountUpdateReceiver(); LocalBroadcastManager.getInstance(this) .registerReceiver(mAccountUpdateReceiver, new IntentFilter(PerformanceItemUpdateBroadcaster.ACTION)); } } @Override public void onStopBase() { if (mAccountUpdateReceiver != null) { LocalBroadcastManager.getInstance(this).unregisterReceiver(mAccountUpdateReceiver); mAccountUpdateReceiver = null; } } @Override public boolean onCreateOptionsMenu(Menu menu) { super.onCreateOptionsMenu(menu); getMenuInflater().inflate(R.menu.main_menu, menu); mMenuProgressBar = menu.findItem(R.id.menu_progress_bar); mMenuRefreshButton = menu.findItem(R.id.menu_refresh); mMenuHideExcludeAccounts = menu.findItem(R.id.menu_hide_exclude_accounts); mMenuDemoMode = menu.findItem(R.id.menu_demo_mode); // tint all the menu item icons ColorStateList colorSelector = ContextCompat.getColorStateList(this, R.color.nav_on_color); for (int x = 0; x < menu.size(); x++) { MenuItem item = menu.getItem(x); Drawable icon = item.getIcon(); if (icon != null) { DrawableCompat.setTintList(icon, colorSelector); item.setIcon(icon); } } return true; } @Override public boolean onPrepareOptionsMenu(Menu menu) { super.onPrepareOptionsMenu(menu); mMenuHideExcludeAccounts.setChecked(mSettings.getBoolean(Settings.Key.PREF_HIDE_EXCLUDE_ACCOUNTS)); mMenuDemoMode.setChecked(mSettings.getBoolean(Settings.Key.PREF_DEMO_MODE)); return true; } @Override public boolean onOptionsItemSelected(MenuItem item) { boolean handled = false; switch (item.getItemId()) { case R.id.menu_settings: startActivity(SettingsActivity.newIntent(this)); handled = true; break; case R.id.menu_refresh: refresh(); handled = true; break; case R.id.menu_new_portfolio: showNewAccountActivity(); handled = true; break; case R.id.menu_backup_db: backupDatabaseToSDCard(); handled = true; break; case R.id.menu_restore_db: restoreLatestDatabase(); handled = true; break; case R.id.menu_hide_exclude_accounts: boolean hideExcludeAccounts = !mMenuHideExcludeAccounts.isChecked(); mSettings.setBoolean(Settings.Key.PREF_HIDE_EXCLUDE_ACCOUNTS, hideExcludeAccounts); mMenuHideExcludeAccounts.setChecked(hideExcludeAccounts); view.resetSelectedAccountID(); updateView(); handled = true; break; case R.id.menu_demo_mode: boolean demoMode = !mMenuDemoMode.isChecked(); mSettings.setBoolean(Settings.Key.PREF_DEMO_MODE, demoMode); mMenuDemoMode.setChecked(demoMode); updateView(); handled = true; break; } return handled; } @Override public boolean onHandleException(String logMsg, Exception ex) { String displayMsg = ex.getLocalizedMessage(); if (TextUtils.isEmpty(displayMsg)) { displayMsg = ex.toString(); } getSnackbar(view, displayMsg, Snackbar.LENGTH_LONG, R.color.snackbar_background, R.color.failure, android.support.design.R.id.snackbar_text) .show(); return true; } private void backupDatabaseToSDCard() { if (isStoragePermissionsGranted(PERMS_REQUEST_BACKUP)) { boolean success = TradeApplication.backupDatabase(this, false); String msg = getResources().getString(success ? R.string.menu_backup_db_success : R.string.menu_backup_db_fail); getSnackbar(view, msg, Snackbar.LENGTH_LONG, R.color.snackbar_background, success ? R.color.success : R.color.failure, android.support.design.R.id.snackbar_text) .show(); } } private void restoreLatestDatabase() { if (isStoragePermissionsGranted(PERMS_REQUEST_RESTORE)) { boolean success = TradeApplication.restoreDatabase(this); String msg = getResources().getString(success ? R.string.menu_restore_db_success : R.string.menu_restore_db_fail); getSnackbar(view, msg, Snackbar.LENGTH_LONG, R.color.snackbar_background, success ? R.color.success : R.color.failure, android.support.design.R.id.snackbar_text) .show(); } } private boolean isStoragePermissionsGranted(int requestCode) { boolean hasPerms = true; if ((ContextCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) || (ContextCompat.checkSelfPermission(this, Manifest.permission.READ_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED)) { hasPerms = false; ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE, Manifest.permission.READ_EXTERNAL_STORAGE}, requestCode); } return hasPerms; } @Override public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) { switch (requestCode) { case PERMS_REQUEST_BACKUP: { if (grantResults[0] == PackageManager.PERMISSION_GRANTED) { backupDatabaseToSDCard(); } else { Log.w(TAG, "User Refused Read Storage"); } break; } case PERMS_REQUEST_RESTORE: { if (grantResults[0] == PackageManager.PERMISSION_GRANTED) { restoreLatestDatabase(); } else { Log.w(TAG, "User Refused Read Storage"); } break; } default: super.onRequestPermissionsResult(requestCode, permissions, grantResults); break; } } protected void setupAdapter() { ViewProvider viewProvider = ((ViewProvider) this.getApplication()); mPortfolioAdapter = new PortfolioAdapter(mSettings, viewProvider); mPortfolioAdapter.setListener(new PortfolioAdapter.PortfolioAdapterListener() { @Override public boolean onLongClickAccount(final Account account) { new AlertDialog.Builder(MainActivity.this) .setTitle(R.string.account_delete_dlg_title) .setMessage(String.format(getString(R.string.account_delete_dlg_message_format), account.getName())) .setIcon(android.R.drawable.ic_dialog_alert) .setPositiveButton(android.R.string.yes, new DialogInterface.OnClickListener() { public void onClick(DialogInterface dialog, int whichButton) { try { mPortfolioModel.deleteAccount(account); updateView(); } catch (Exception ex) { Log.e(TAG, "Error Deleting account", ex); Toast.makeText(MainActivity.this, "Error deleting account", Toast.LENGTH_LONG).show(); } } }) .setNegativeButton(android.R.string.no, null).show(); return true; } @Override public boolean onLongClickInvestment(Investment investment) { showNewSellOrderActivity(investment); return true; } @Override public void createNewAccount() { showNewAccountActivity(); } @Override public void createNewDogsAccount() { String name = getResources().getString(R.string.quickstart_account_name); String desc = getResources().getString(R.string.quickstart_account_desc); Account newAccount = new Account(name, desc, new Money(100000.0), Account.Strategy.DOGS_OF_THE_DOW, false); new CreateAccountTask(MainActivity.this).execute(newAccount); } }); mPortfolioAdapter.setAccountItemViewListener(new AccountViewHolder.AccountItemViewListener() { @Override public void onTradeButtonClicked(Account account) { showNewBuyOrderActivity(account); } @Override public void onShowOpenOrdersClicked(Account account) { startActivity(OrderListActivity.newIntent(MainActivity.this, account.getId())); } }); this.view.setPortfolioAdapter(mPortfolioAdapter); } /** * Refresh launches the quote service which will call the QuoteUpdateReceiver * to update the UI once the quotes are fetched */ public void refresh() { showProgress(); startService(QuoteService.getIntent(this)); } protected void showNewAccountActivity() { Intent intent = EditActivity.getIntent(view.getContext(), R.string.account_create_title, new Account("", "", new Money(100000.0), Account.Strategy.NONE, false), new AccountEditController(), 0, 0); startActivityForResult(intent, NEW_ACCOUNT_RESULT); } protected void showNewBuyOrderActivity(Account account) { Order order = new Order(); order.setAccount(account); order.setAction(Order.OrderAction.BUY); order.setStrategy(Order.OrderStrategy.MARKET); Intent intent = EditActivity.getIntent(view.getContext(), R.string.order_create_buy_title, order, new OrderEditController(), R.string.order_edit_ok_button_new, 0); startActivityForResult(intent, NEW_ORDER_RESULT); } protected void showNewSellOrderActivity(Investment investment) { Order order = new Order(); order.setSymbol(investment.getSymbol()); order.setQuantity(investment.getQuantity()); order.setAccount(investment.getAccount()); order.setAction(Order.OrderAction.SELL); order.setStrategy(Order.OrderStrategy.MARKET); Intent intent = EditActivity.getIntent(view.getContext(), R.string.order_create_sell_title, order, new OrderEditController(), R.string.order_edit_ok_button_new, 0); startActivityForResult(intent, NEW_ORDER_RESULT); } @Override public void onActivityResultBase(int requestCode, int resultCode, Intent data) { if (resultCode == Activity.RESULT_OK) { if (requestCode == NEW_ACCOUNT_RESULT) { Account account = EditActivity.getResult(data); if (account != null) { Account newAccount = new Account(account.getName(), account.getDescription(), account.getInitialBalance(), account.getStrategy(), account.getExcludeFromTotals()); new CreateAccountTask(this).execute(newAccount); } } else if (requestCode == NEW_ORDER_RESULT) { Order order = EditActivity.getResult(data); if (order != null) { mPortfolioModel.createOrder(order); mPortfolioLoader.onContentChanged(); mPortfolioModel.processOrders(this, (order.getStrategy() == Order.OrderStrategy.MANUAL)); } } } } public void showProgress() { if (mMenuProgressBar != null) { mMenuProgressBar.setVisible(true); mMenuRefreshButton.setVisible(false); } } public void hideProgress() { if (mMenuProgressBar != null) { mMenuProgressBar.setVisible(false); mMenuRefreshButton.setVisible(true); } } /* private List<PerformanceItem> generateRandomTestData() { List<PerformanceItem> performanceItems = new ArrayList<>(); long currentTime = 1450967400000L; long fiveMinutes = 1000L * 60 * 5; double costBasis = 100000.0; double value = 100000.0; double todayChange = 0.0; int onePercentCostBases = (int) (costBasis * .1); int randomOffset = onePercentCostBases / 2; Random random = new Random(); int samples = (int) (12 * 6.5f); for (int x = 0; x < samples; x++) { performanceItems.add(new PerformanceItem(-1L, new Date(currentTime), new Money(costBasis), new Money(value), new Money(todayChange))); double randChange = randomOffset - (random.nextInt(onePercentCostBases * 100) / 100.0); todayChange += randChange; value += randChange; currentTime += fiveMinutes; } return performanceItems; } */ private void updateView() { mGraphDataLoader.forceLoad(); mPortfolioLoader.forceLoad(); } private static class CreateAccountTask extends AsyncTask<Account, Void, Void> { private final WeakReference<MainActivity> mWeakActivity; private final PortfolioModel mPortfolioModel; private CreateAccountTask(MainActivity activity) { this.mWeakActivity = new WeakReference<>(activity); // the models are application scope so it is OK to hold a ref to it mPortfolioModel = activity.mPortfolioModel; } @Override protected Void doInBackground(Account... accounts) { for (Account account : accounts) { mPortfolioModel.createAccount(account); if (isCancelled()) { break; } } return null; } @Override protected void onPreExecute() { MainActivity activity = mWeakActivity.get(); if ((activity != null) && !activity.isFinishing()) { activity.showProgress(); } } @Override protected void onPostExecute(Void v) { MainActivity activity = mWeakActivity.get(); if ((activity != null) && !activity.isFinishing()) { activity.hideProgress(); activity.mPortfolioLoader.onContentChanged(); } } } private class AccountUpdateReceiver extends BroadcastReceiver { private AccountUpdateReceiver() { } @Override public void onReceive(Context context, Intent intent) { PerformanceItemUpdateBroadcaster.PerformanceItemUpdateData data = PerformanceItemUpdateBroadcaster.getData(intent); mGraphDataLoader.setSelectionCriteria(data.accountId, data.days); mGraphDataLoader.forceLoad(); view.setAccountSpinner(data.accountId); } } }