/* * 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.home; import android.app.NotificationManager; import android.content.Context; import android.content.Intent; import android.content.SharedPreferences; import android.content.res.Configuration; import android.graphics.drawable.Drawable; import android.os.Bundle; import android.os.Handler; import android.preference.PreferenceManager; import android.support.annotation.NonNull; import android.support.v4.app.Fragment; import android.support.v4.app.FragmentTransaction; import android.support.v4.view.GravityCompat; import android.support.v4.widget.DrawerLayout; import android.support.v7.widget.Toolbar; import android.text.Html; import android.text.TextUtils; import android.view.KeyEvent; import android.view.Menu; import android.view.MenuInflater; import android.view.MenuItem; import android.view.View; import android.view.animation.Animation; import android.view.animation.LinearInterpolator; import android.view.animation.RotateAnimation; import android.widget.ExpandableListView; import android.widget.ImageView; import android.widget.LinearLayout; import android.widget.TextView; import android.widget.Toast; import com.afollestad.materialdialogs.MaterialDialog; import com.mikepenz.google_material_typeface_library.GoogleMaterial; import com.mikepenz.mmex_icon_font_typeface_library.MMXIconFont; import com.money.manager.ex.Constants; import com.money.manager.ex.DonateActivity; import com.money.manager.ex.HelpActivity; import com.money.manager.ex.MoneyManagerApplication; import com.money.manager.ex.PasscodeActivity; import com.money.manager.ex.R; import com.money.manager.ex.about.AboutActivity; import com.money.manager.ex.account.AccountTransactionListFragment; import com.money.manager.ex.assetallocation.AssetAllocationReportActivity; import com.money.manager.ex.assetallocation.overview.AssetAllocationOverviewActivity; import com.money.manager.ex.budget.BudgetsActivity; import com.money.manager.ex.common.MmxBaseFragmentActivity; import com.money.manager.ex.core.InfoKeys; import com.money.manager.ex.core.IntentFactory; import com.money.manager.ex.core.RequestCodes; import com.money.manager.ex.core.UIHelper; import com.money.manager.ex.settings.PreferenceConstants; import com.money.manager.ex.settings.SyncPreferences; import com.money.manager.ex.sync.events.DbFileDownloadedEvent; import com.money.manager.ex.home.events.AccountsTotalLoadedEvent; import com.money.manager.ex.home.events.RequestAccountFragmentEvent; import com.money.manager.ex.home.events.RequestOpenDatabaseEvent; import com.money.manager.ex.home.events.RequestPortfolioFragmentEvent; import com.money.manager.ex.home.events.RequestWatchlistFragmentEvent; import com.money.manager.ex.home.events.UsernameLoadedEvent; import com.money.manager.ex.investment.PortfolioFragment; import com.money.manager.ex.servicelayer.InfoService; import com.money.manager.ex.common.CategoryListFragment; import com.money.manager.ex.core.Core; import com.money.manager.ex.currency.list.CurrencyListActivity; import com.money.manager.ex.core.RecurringTransactionBootReceiver; import com.money.manager.ex.core.Passcode; import com.money.manager.ex.core.TransactionTypes; import com.money.manager.ex.account.AccountListFragment; import com.money.manager.ex.fragment.PayeeListFragment; import com.money.manager.ex.investment.watchlist.WatchlistFragment; import com.money.manager.ex.notifications.RecurringTransactionNotifications; import com.money.manager.ex.recurring.transactions.RecurringTransactionListFragment; import com.money.manager.ex.reports.CategoriesReportActivity; import com.money.manager.ex.reports.IncomeVsExpensesActivity; import com.money.manager.ex.reports.PayeesReportActivity; import com.money.manager.ex.search.SearchActivity; import com.money.manager.ex.settings.AppSettings; import com.money.manager.ex.settings.SettingsActivity; import com.money.manager.ex.sync.SyncConstants; import com.money.manager.ex.sync.SyncManager; import com.money.manager.ex.sync.events.SyncStartingEvent; import com.money.manager.ex.sync.events.SyncStoppingEvent; import com.money.manager.ex.tutorial.TutorialActivity; import com.money.manager.ex.utils.MmxDatabaseUtils; import org.greenrobot.eventbus.Subscribe; import java.io.File; import java.net.URLDecoder; import java.util.ArrayList; import java.util.concurrent.Callable; import javax.inject.Inject; import dagger.Lazy; import icepick.State; import rx.Single; import rx.android.schedulers.AndroidSchedulers; import timber.log.Timber; /** * Main activity of the application. */ public class MainActivity extends MmxBaseFragmentActivity { public static final String EXTRA_DATABASE_PATH = "dbPath"; public static final String EXTRA_SKIP_REMOTE_CHECK = "skipRemoteCheck"; /** * @return the mRestart */ public static boolean isRestartActivitySet() { return mRestartActivity; } public static void setRestartActivity(boolean mRestart) { MainActivity.mRestartActivity = mRestart; } // private private static final String KEY_IN_AUTHENTICATION = "MainActivity:isInAuthenticated"; private static final String KEY_RECURRING_TRANSACTION = "MainActivity:RecurringTransaction"; // state if restart activity private static boolean mRestartActivity = false; @Inject Lazy<RecentDatabasesProvider> mDatabases; @State boolean dbUpdateCheckDone = false; @State boolean mIsSynchronizing = false; @State boolean isAuthenticated = false; @State int deviceOrientation = Constants.NOT_SET; private boolean isInAuthentication = false; private boolean isRecurringTransactionStarted = false; // navigation drawer private LinearLayout mDrawerLayout; private DrawerLayout mDrawer; private MyActionBarDrawerToggle mDrawerToggle; private TextView mDrawerTextUserName; private TextView mDrawerTextTotalAccounts; // state dual panel private boolean mIsDualPanel = false; // sync rotating icon private MenuItem mSyncMenuItem = null; private UIHelper mUiHelper; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); MoneyManagerApplication.getApp().iocComponent.inject(this); if (showPrerequisite()) { finish(); return; } // todo: remove this after the users upgrade the recent files list. migrateRecentDatabases(); // Reset the request for restart. If we are in onCreate, we are restarting already. setRestartActivity(false); // Layout setContentView(R.layout.main_activity); Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar); if (toolbar != null) setSupportActionBar(toolbar); LinearLayout fragmentDetail = (LinearLayout) findViewById(R.id.fragmentDetail); setDualPanel(fragmentDetail != null && fragmentDetail.getVisibility() == View.VISIBLE); // Initialize current device orientation. if (deviceOrientation == Constants.NOT_SET) { deviceOrientation = getResources().getConfiguration().orientation; } // Intent. Opening from the notification or the file system. handleIntent(); // Restore state. Check authentication, etc. if (savedInstanceState != null) { restoreInstanceState(savedInstanceState); } handleDeviceRotation(); // Close any existing notifications. ((NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE)).cancel(SyncConstants.NOTIFICATION_SYNC_OPEN_FILE); showCurrentDatabasePath(this); // Read something from the database at this stage so that the db file gets created. InfoService infoService = new InfoService(getApplicationContext()); String username = infoService.getInfoValue(InfoKeys.USERNAME); // fragments initHomeFragment(); // start notification for recurring transaction if (!isRecurringTransactionStarted) { AppSettings settings = new AppSettings(this); boolean showNotification = settings.getBehaviourSettings().getNotificationRecurringTransaction(); if (showNotification) { RecurringTransactionNotifications notifications = new RecurringTransactionNotifications(this); notifications.notifyRepeatingTransaction(); isRecurringTransactionStarted = true; } } // notification send broadcast Intent serviceRepeatingTransaction = new Intent(getApplicationContext(), RecurringTransactionBootReceiver.class); getApplicationContext().sendBroadcast(serviceRepeatingTransaction); initializeDrawer(); initializeSync(); } @Override protected void onStart() { super.onStart(); // check if has pass-code and authenticate if (!isAuthenticated) { Passcode passcode = new Passcode(getApplicationContext()); if (passcode.hasPasscode() && !isInAuthentication) { Intent intent = new Intent(this, PasscodeActivity.class); // set action and data intent.setAction(PasscodeActivity.INTENT_REQUEST_PASSWORD); intent.putExtra(PasscodeActivity.INTENT_MESSAGE_TEXT, getString(R.string.enter_your_passcode)); // start activity startActivityForResult(intent, RequestCodes.PASSCODE); // set in authentication isInAuthentication = true; } } // todo: mark the active database file in the navigation panel. // mDrawer } @Override protected void onResume() { try { super.onResume(); } catch (Exception e) { Timber.e(e, "resuming main activity"); } // check if restart activity if (isRestartActivitySet()) { restartActivity(); // restart and exit } } @Override protected void onPostCreate(Bundle savedInstanceState) { super.onPostCreate(savedInstanceState); if (mDrawerToggle != null) { try { mDrawerToggle.syncState(); } catch (Exception e) { Timber.e(e, "drawer sync state"); } } } @Override protected void onActivityResult(int requestCode, int resultCode, Intent data) { super.onActivityResult(requestCode, resultCode, data); switch (requestCode) { case RequestCodes.SELECT_FILE: // Opening from intent. if (resultCode != RESULT_OK) return; String selectedPath = UIHelper.getSelectedFile(data); if(TextUtils.isEmpty(selectedPath)) { new UIHelper(this).showToast(R.string.invalid_database); return; } DatabaseMetadata db = DatabaseMetadataFactory.getInstance(selectedPath); changeDatabase(db); break; case RequestCodes.PASSCODE: isAuthenticated = false; isInAuthentication = false; if (resultCode == RESULT_OK && data != null) { Passcode passcode = new Passcode(getApplicationContext()); String passIntent = data.getStringExtra(PasscodeActivity.INTENT_RESULT_PASSCODE); String passDb = passcode.getPasscode(); if (passIntent != null && passDb != null) { isAuthenticated = passIntent.equals(passDb); if (!isAuthenticated) { Toast.makeText(getApplicationContext(), R.string.passocde_no_macth, Toast.LENGTH_LONG).show(); } } } // close if not authenticated if (!isAuthenticated) { this.finish(); } break; } } @Override public void onConfigurationChanged(Configuration newConfig) { super.onConfigurationChanged(newConfig); setRestartActivity(true); } // Menu @Override public boolean onCreateOptionsMenu(Menu menu) { super.onCreateOptionsMenu(menu); if (mIsSynchronizing) { createSyncToolbarItem(menu); startSyncIconRotation(mSyncMenuItem); } else { stopSyncIconRotation(mSyncMenuItem); destroySyncToolbarItem(menu); } return true; } @Override public boolean onOptionsItemSelected(MenuItem item) { switch (item.getItemId()) { case android.R.id.home: // toggle drawer with the menu hardware button. if (mDrawer != null) { if (mDrawer.isDrawerOpen(mDrawerLayout)) { mDrawer.closeDrawer(mDrawerLayout); } else { mDrawer.openDrawer(mDrawerLayout); } } return true; default: // nothing break; } return super.onOptionsItemSelected(item); } // End Menu. @Override public boolean onKeyDown(int keyCode, KeyEvent event) { // Hardware Menu key pressed. if (keyCode == KeyEvent.KEYCODE_MENU) { if (mDrawer.isDrawerOpen(mDrawerLayout)) { mDrawer.closeDrawer(mDrawerLayout); } else { mDrawer.openDrawer(mDrawerLayout); } // Do not propagate the event further. Mark the event as handled. return true; } return super.onKeyDown(keyCode, event); } @Override protected void onSaveInstanceState(Bundle outState) { outState.putBoolean(KEY_IN_AUTHENTICATION, isInAuthentication); outState.putBoolean(KEY_RECURRING_TRANSACTION, isRecurringTransactionStarted); super.onSaveInstanceState(outState); } @Override protected void onDestroy() { NotificationManager notificationManager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE); if (notificationManager != null) notificationManager.cancelAll(); super.onDestroy(); } @Override public void onBackPressed() { if (mDrawer.isDrawerOpen(GravityCompat.START)) { mDrawer.closeDrawer(GravityCompat.START); } else { try { super.onBackPressed(); } catch (IllegalStateException e) { Timber.e(e, "on back pressed"); } } } // Events (EventBus) @Subscribe public void onEvent(RequestAccountFragmentEvent event) { showAccountFragment(event.accountId); } @Subscribe public void onEvent(RequestWatchlistFragmentEvent event) { showWatchlistFragment(event.accountId); } @Subscribe public void onEvent(RequestPortfolioFragmentEvent event) { showPortfolioFragment(event.accountId); } @Subscribe public void onEvent(RequestOpenDatabaseEvent event) { openDatabasePicker(); } @Subscribe public void onEvent(UsernameLoadedEvent event) { setDrawerUserName(MoneyManagerApplication.getApp().getUserName()); } @Subscribe public void onEvent(AccountsTotalLoadedEvent event) { setDrawerTotalAccounts(event.amount); } /** * Force execution on the main thread as the event can be received on the service thread. * @param event Sync started event. */ @Subscribe public void onEvent(SyncStartingEvent event) { Single.fromCallable(new Callable<Void>() { @Override public Void call() throws Exception { mIsSynchronizing = true; invalidateOptionsMenu(); return null; } }) .subscribeOn(AndroidSchedulers.mainThread()) // .observeOn(AndroidSchedulers.mainThread()) .subscribe(); } /** * Force execution on the main thread as the event can be received on the service thread. * @param event Sync stopped event. */ @Subscribe public void onEvent(SyncStoppingEvent event) { Single.fromCallable(new Callable<Void>() { @Override public Void call() throws Exception { mIsSynchronizing = false; invalidateOptionsMenu(); return null; } }) .subscribeOn(AndroidSchedulers.mainThread()) // .observeOn(AndroidSchedulers.mainThread()) .subscribe(); } /** * A newer database file has just been downloaded. Reload. */ @Subscribe public void onEvent(DbFileDownloadedEvent event) { // open the new database. new SyncManager(this).useDownloadedDatabase(); } /* Custom methods */ public void changeDatabase(@NonNull DatabaseMetadata database) { // Reuse existing metadata, if found. DatabaseMetadata existing = mDatabases.get().get(database.localPath); if (existing != null) { Timber.v("Existing database found. Reusing metadata."); database = existing; } try { new MmxDatabaseUtils(this) .useDatabase(database); } catch (Exception e) { if (e instanceof IllegalArgumentException) { Timber.w(e.getMessage()); } else { Timber.e(e, "changing the database"); } return; } // Refresh the recent files list. getDatabases().load(); setRestartActivity(true); restartActivity(); } /** * @return the mIsDualPanel */ public boolean isDualPanel() { return mIsDualPanel; } public int getContentId() { return isDualPanel() ? R.id.fragmentDetail : R.id.fragmentMain; } public int getNavigationId() { return isDualPanel() ? R.id.fragmentMain : R.id.fragmentDetail; } /** * Handle the drawer item click. Invoked by the actual click handler. * @param item selected DrawerMenuItem * @return boolean indicating whether the action was handled or not. */ public boolean onDrawerMenuAndOptionMenuSelected(DrawerMenuItem item) { boolean result = true; Intent intent; // Recent database? if (item.getId() == null && item.getTag() != null) { String key = item.getTag().toString(); DatabaseMetadata selectedDatabase = getDatabases().get(key); if (selectedDatabase != null) { onOpenDatabaseClick(selectedDatabase); } } if (item.getId() == null) return false; switch (item.getId()) { case R.id.menu_home: showFragment(HomeFragment.class); break; case R.id.menu_sync: SyncManager sync = new SyncManager(this); sync.triggerSynchronization(); // re-set the sync timer. sync.startSyncServiceHeartbeat(); break; case R.id.menu_open_database: openDatabasePicker(); break; case R.id.menu_account: showFragment(AccountListFragment.class); break; case R.id.menu_category: showFragment(CategoryListFragment.class); break; case R.id.menu_currency: // Show Currency list. intent = new Intent(MainActivity.this, CurrencyListActivity.class); // intent = new Intent(MainActivity.this, CurrencyRecyclerListActivity.class); intent.setAction(Intent.ACTION_EDIT); startActivity(intent); break; case R.id.menu_payee: showFragment(PayeeListFragment.class); break; case R.id.menu_recurring_transaction: showFragment(RecurringTransactionListFragment.class); break; case R.id.menu_budgets: intent = new Intent(this, BudgetsActivity.class); startActivity(intent); break; case R.id.menu_asset_allocation: intent = new Intent(this, AssetAllocationOverviewActivity.class); startActivity(intent); break; case R.id.menu_search_transaction: startActivity(new Intent(MainActivity.this, SearchActivity.class)); break; case R.id.menu_report_categories: startActivity(new Intent(this, CategoriesReportActivity.class)); break; case R.id.menu_settings: startActivity(new Intent(MainActivity.this, SettingsActivity.class)); break; case R.id.menu_reports: showReportsSelector(item.getText()); break; case R.id.menu_report_payees: startActivity(new Intent(this, PayeesReportActivity.class)); break; case R.id.menu_report_where_money_goes: intent = new Intent(this, CategoriesReportActivity.class); intent.putExtra(CategoriesReportActivity.REPORT_FILTERS, TransactionTypes.Withdrawal.name()); intent.putExtra(CategoriesReportActivity.REPORT_TITLE, getString(R.string.menu_report_where_money_goes)); startActivity(intent); break; case R.id.menu_report_where_money_comes_from: intent = new Intent(this, CategoriesReportActivity.class); intent.putExtra(CategoriesReportActivity.REPORT_FILTERS, TransactionTypes.Deposit.name()); intent.putExtra(CategoriesReportActivity.REPORT_TITLE, getString(R.string.menu_report_where_money_comes_from)); startActivity(intent); break; case R.id.menu_report_income_vs_expenses: startActivity(new Intent(this, IncomeVsExpensesActivity.class)); break; case R.id.menu_asset_allocation_overview: startActivity(new Intent(this, AssetAllocationReportActivity.class)); break; case R.id.menu_help: startActivity(new Intent(MainActivity.this, HelpActivity.class)); break; case R.id.menu_about: startActivity(new Intent(MainActivity.this, AboutActivity.class)); break; case R.id.menu_donate: startActivity(new Intent(this, DonateActivity.class)); break; default: // if no match, return false result = false; } return result; } /** * for the change setting restart process application */ public void restartActivity() { if (!mRestartActivity) return; setRestartActivity(false); // kill process // android.os.Process.killProcess(android.os.Process.myPid()); // New api. This will keep the Intent, which hangs after the db download! // this.recreate(); Intent intent = IntentFactory.getMainActivityNew(this); startActivity(intent); finish(); } // private void shutdownApp() { // finish(); // android.os.Process.killProcess(android.os.Process.myPid()); // System.exit(1); // } /** * @param mIsDualPanel the mIsDualPanel to set */ public void setDualPanel(boolean mIsDualPanel) { this.mIsDualPanel = mIsDualPanel; } /** * Show fragment using reflection from class */ public void showFragment(Class<?> fragmentClass) { if (fragmentClass == null) return; Fragment fragment = getSupportFragmentManager().findFragmentByTag(fragmentClass.getName()); if (fragment == null || fragment.getId() != getContentId()) { ClassLoader loader = getClassLoader(); if (loader != null) { try { Class<?> classFragment = loader.loadClass(fragmentClass.getName()); fragment = (Fragment) classFragment.newInstance(); } catch (Exception e) { Timber.e(e, "creating new fragment"); } } } // check if fragment is not null if (fragment != null) { showFragment(fragment); } } /** * Displays the fragment without indicating the tag. The tag will be the classname of the fragment */ public void showFragment(Fragment fragment) { showFragment(fragment, fragment.getClass().getName()); } /** * Displays the fragment and associate the tag * * @param fragment Fragment to display * @param tag Tag/name to search for. */ public void showFragment(Fragment fragment, String tag) { try { showFragment_Internal(fragment, tag); } catch (Exception e) { Timber.e(e, "showing fragment with tag"); } } /** * Shows a fragment with the selected account (id) and transactions. * Called from Home Fragment when an account is clicked in the main list. * * @param accountId id of the account for which to show the transactions */ public void showAccountFragment(int accountId) { String tag = AccountTransactionListFragment.class.getSimpleName() + "_" + Integer.toString(accountId); AccountTransactionListFragment fragment = (AccountTransactionListFragment) getSupportFragmentManager().findFragmentByTag(tag); if (fragment == null || fragment.getId() != getContentId()) { fragment = AccountTransactionListFragment.newInstance(accountId); } showFragment(fragment, tag); } public void showPortfolioFragment(int accountId) { String tag = PortfolioFragment.class.getSimpleName() + "_" + Integer.toString(accountId); PortfolioFragment fragment = (PortfolioFragment) getSupportFragmentManager().findFragmentByTag(tag); if (fragment == null) { fragment = PortfolioFragment.newInstance(accountId); } showFragment(fragment, tag); } public void showWatchlistFragment(int accountId) { String tag = WatchlistFragment.class.getSimpleName() + "_" + Integer.toString(accountId); WatchlistFragment fragment = (WatchlistFragment) getSupportFragmentManager().findFragmentByTag(tag); if (fragment == null || fragment.getId() != getContentId()) { fragment = WatchlistFragment.newInstance(accountId); } showFragment(fragment, tag); } /** * Show tutorial activity. */ public void showTutorial() { Intent intent = new Intent(this, TutorialActivity.class); // make top-level so there's no going back. intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP); startActivity(intent); // Tutorial is marked as seen when OK on the last page is clicked. // Close this activity. A new one will start from Tutorial. } public void setDrawerUserName(String userName) { if (mDrawerTextUserName != null) mDrawerTextUserName.setText(userName); } public void setDrawerTotalAccounts(String totalAccounts) { if (mDrawerTextTotalAccounts != null) mDrawerTextTotalAccounts.setText(totalAccounts); } public void onClickCardViewIncomesVsExpenses(View v) { startActivity(new Intent(this, IncomeVsExpensesActivity.class)); } public void openDatabasePicker() { //pickFile(Environment.getDefaultDatabaseDirectory()); MmxDatabaseUtils dbUtils = new MmxDatabaseUtils(this); String dbDirectory = dbUtils.getDefaultDatabaseDirectory(); // Environment.getDefaultDatabaseDirectory().getPath() try { UIHelper.pickFileDialog(this, dbDirectory, RequestCodes.SELECT_FILE); } catch (Exception e) { Timber.e(e, "displaying the open-database picker"); } // continues in onActivityResult } /* Private */ private void createExpandableDrawer() { UIHelper uiHelper = new UIHelper(this); int iconColor = uiHelper.getSecondaryTextColor(); // Menu. final ArrayList<DrawerMenuItem> groupItems = getDrawerMenuItems(); final ArrayList<Object> childItems = new ArrayList<>(); // Home childItems.add(null); // Open Database. Display the recent db list. ArrayList<DrawerMenuItem> childDatabases = getRecentDatabasesDrawerMenuItems(); childItems.add(childDatabases); // Synchronization if (new SyncManager(this).isActive()) { childItems.add(null); } // Entities ArrayList<DrawerMenuItem> childTools = new ArrayList<>(); // manage: account childTools.add(new DrawerMenuItem().withId(R.id.menu_account) .withText(getString(R.string.accounts)) .withIconDrawable(uiHelper.getIcon(MMXIconFont.Icon.mmx_temple) .color(iconColor))); // manage: categories childTools.add(new DrawerMenuItem().withId(R.id.menu_category) .withText(getString(R.string.categories)) .withIconDrawable(uiHelper.getIcon(MMXIconFont.Icon.mmx_tag_empty) .color(iconColor))); // manage: currencies childTools.add(new DrawerMenuItem().withId(R.id.menu_currency) .withText(getString(R.string.currencies)) .withIconDrawable(uiHelper.getIcon(GoogleMaterial.Icon.gmd_euro_symbol) .color(iconColor))); // manage: payees childTools.add(new DrawerMenuItem().withId(R.id.menu_payee) .withText(getString(R.string.payees)) .withIconDrawable(uiHelper.getIcon(GoogleMaterial.Icon.gmd_group) .color(iconColor))); childItems.add(childTools); // Recurring Transactions childItems.add(null); // Budgets childItems.add(null); // Asset Allocation //if (BuildConfig.DEBUG) <- this was used to hide the menu item while testing. childItems.add(null); // Search transaction childItems.add(null); // reports childItems.add(null); // Settings childItems.add(null); // Donate childItems.add(null); // Help childItems.add(null); // Adapter. final ExpandableListView drawerList = (ExpandableListView) findViewById(R.id.drawerExpandableList); DrawerMenuGroupAdapter adapter = new DrawerMenuGroupAdapter(this, groupItems, childItems); drawerList.setAdapter(adapter); // set listener on item click drawerList.setOnGroupClickListener(new ExpandableListView.OnGroupClickListener() { @Override public boolean onGroupClick(ExpandableListView parent, View v, int groupPosition, long id) { if (mDrawer == null) return false; // if the group has child items, do not e. ArrayList<String> children = (ArrayList<String>) childItems.get(groupPosition); if (children != null) return false; // Highlight the selected item, update the title, and close the drawer drawerList.setItemChecked(groupPosition, true); // You should reset item counter mDrawer.closeDrawer(mDrawerLayout); // check item selected final DrawerMenuItem item = (DrawerMenuItem) drawerList.getExpandableListAdapter() .getGroup(groupPosition); if (item != null) { new Handler().postDelayed(new Runnable() { @Override public void run() { // execute operation onDrawerMenuAndOptionMenuSelected(item); } }, 200); } return true; } }); drawerList.setOnChildClickListener(new ExpandableListView.OnChildClickListener() { @Override public boolean onChildClick(ExpandableListView parent, View v, int groupPosition, int childPosition, long id) { if (mDrawer == null) return false; mDrawer.closeDrawer(mDrawerLayout); ArrayList<Object> children = (ArrayList) childItems.get(groupPosition); final DrawerMenuItem selectedItem = (DrawerMenuItem) children.get(childPosition); if (selectedItem != null) { new Handler().postDelayed(new Runnable() { @Override public void run() { onDrawerMenuAndOptionMenuSelected(selectedItem); } }, 200); return true; } else { return false; } } }); } private void createSyncToolbarItem(Menu menu) { if (menu == null) return; int id = R.id.menuSyncProgress; if (new SyncManager(this).isActive()) { // add rotating icon if (menu.findItem(id) == null) { boolean hasAnimation = false; if (mSyncMenuItem != null && mSyncMenuItem.getActionView() != null) { hasAnimation = true; // There is a running animation. Clear it on the old reference. stopSyncIconRotation(mSyncMenuItem); } // create (new) menu item. MenuInflater inflater = getMenuInflater(); inflater.inflate(R.menu.menu_item_sync_progress, menu); mSyncMenuItem = menu.findItem(id); UIHelper ui = new UIHelper(this); Drawable syncIcon = ui.getIcon(GoogleMaterial.Icon.gmd_cached); mSyncMenuItem.setIcon(syncIcon); if (hasAnimation) { // continue animation. startSyncIconRotation(mSyncMenuItem); } } } else { if (mSyncMenuItem != null) { stopSyncIconRotation(mSyncMenuItem); mSyncMenuItem = null; } } } private void destroySyncToolbarItem(Menu menu) { if (menu == null) return; int id = R.id.menuSyncProgress; if (menu.findItem(id) != null) { menu.removeItem(id); } if (mSyncMenuItem != null) { stopSyncIconRotation(mSyncMenuItem); mSyncMenuItem = null; } } private RecentDatabasesProvider getDatabases() { return mDatabases.get(); } private ArrayList<DrawerMenuItem> getDrawerMenuItems() { ArrayList<DrawerMenuItem> menuItems = new ArrayList<>(); UIHelper uiHelper = new UIHelper(this); int iconColor = uiHelper.getSecondaryTextColor(); // Home menuItems.add(new DrawerMenuItem().withId(R.id.menu_home) .withText(getString(R.string.home)) .withIconDrawable(uiHelper.getIcon(GoogleMaterial.Icon.gmd_home) .color(iconColor))); // Open database menuItems.add(new DrawerMenuItem().withId(R.id.menu_open_database) .withText(getString(R.string.open_database)) .withIconDrawable(uiHelper.getIcon(GoogleMaterial.Icon.gmd_folder_open) .color(iconColor))); // Cloud synchronize if (new SyncManager(this).isActive()) { menuItems.add(new DrawerMenuItem().withId(R.id.menu_sync) .withText(getString(R.string.synchronize)) .withIconDrawable(uiHelper.getIcon(GoogleMaterial.Icon.gmd_cached) .color(iconColor))); } // Entities menuItems.add(new DrawerMenuItem().withId(R.id.menu_group_main) .withText(getString(R.string.entities)) .withIconDrawable(uiHelper.getIcon(GoogleMaterial.Icon.gmd_business) .color(iconColor))); // Recurring Transactions menuItems.add(new DrawerMenuItem().withId(R.id.menu_recurring_transaction) .withText(getString(R.string.recurring_transactions)) .withIconDrawable(uiHelper.getIcon(MMXIconFont.Icon.mmx_back_in_time) .color(iconColor))); // Budgets menuItems.add(new DrawerMenuItem().withId(R.id.menu_budgets) .withText(getString(R.string.budgets)) .withIconDrawable(uiHelper.getIcon(MMXIconFont.Icon.mmx_law) .color(iconColor))); // Asset Allocation menuItems.add(new DrawerMenuItem().withId(R.id.menu_asset_allocation) .withText(getString(R.string.asset_allocation)) .withIconDrawable(uiHelper.getIcon(MMXIconFont.Icon.mmx_chart_pie) .color(iconColor))); // Search transaction menuItems.add(new DrawerMenuItem().withId(R.id.menu_search_transaction) .withText(getString(R.string.search)) .withIconDrawable(uiHelper.getIcon(MMXIconFont.Icon.mmx_magnifier) .color(iconColor))); // reports menuItems.add(new DrawerMenuItem().withId(R.id.menu_reports) .withText(getString(R.string.menu_reports)) .withIconDrawable(uiHelper.getIcon(GoogleMaterial.Icon.gmd_equalizer) .color(iconColor)) .withDivider(true)); // Settings menuItems.add(new DrawerMenuItem().withId(R.id.menu_settings) .withText(getString(R.string.settings)) .withIconDrawable(uiHelper.getIcon(GoogleMaterial.Icon.gmd_settings) .color(iconColor))); // Donate menuItems.add(new DrawerMenuItem().withId(R.id.menu_donate) .withText(getString(R.string.donate)) .withIconDrawable(uiHelper.getIcon(GoogleMaterial.Icon.gmd_card_giftcard) .color(iconColor)) .withDivider(Boolean.TRUE)); // Help menuItems.add(new DrawerMenuItem().withId(R.id.menu_about) .withText(getString(R.string.about)) // .withIconDrawable(uiHelper.getIcon(MMXIconFont.Icon.mmx_question))) .withIconDrawable(uiHelper.getIcon(GoogleMaterial.Icon.gmd_help_outline) .color(iconColor))); return menuItems; } private ArrayList<DrawerMenuItem> getRecentDatabasesDrawerMenuItems() { UIHelper ui = new UIHelper(this); int iconColor = ui.getSecondaryTextColor(); ArrayList<DrawerMenuItem> childDatabases = new ArrayList<>(); RecentDatabasesProvider databases = getDatabases(); if (databases.count() > 0) { for (DatabaseMetadata entry : databases.map.values()) { String title = entry.getFileName(); DrawerMenuItem item = new DrawerMenuItem().withText(title); item.setTag(entry.localPath); if (entry.isSynchronised()) { item.withIconDrawable(ui.getIcon(GoogleMaterial.Icon.gmd_cloud) .color(iconColor)); } else { item.withIconDrawable(ui.getIcon(MMXIconFont.Icon.mmx_floppy_disk) .color(iconColor)); } childDatabases.add(item); } } // Menu item 'Other'. Simply open the file picker, as before. DrawerMenuItem item = new DrawerMenuItem() .withId(R.id.menu_open_database) .withIconDrawable(getUiHelper().getIcon(GoogleMaterial.Icon.gmd_folder_shared) .color(iconColor)) .withText(getString(R.string.other)); childDatabases.add(item); return childDatabases; } private UIHelper getUiHelper() { if (mUiHelper == null) { mUiHelper = new UIHelper(this); } return mUiHelper; } private void handleDeviceRotation() { // Remove items from back stack on device rotation. int currentOrientation = getResources().getConfiguration().orientation; boolean isTablet = new Core(this).isTablet(); if (isTablet && deviceOrientation != currentOrientation) { for (int i = 0; i < getSupportFragmentManager().getBackStackEntryCount(); i++) { getSupportFragmentManager().popBackStack(); } } // update the current orientation. deviceOrientation = currentOrientation; } private void handleIntent() { Intent intent = getIntent(); if (intent == null) return; // Open a db file if (intent.getData() != null) { String pathFile = getIntent().getData().getEncodedPath(); // decode try { String filePath = URLDecoder.decode(pathFile, "UTF-8"); // decode file path Timber.d("Path intent file to open: %s", filePath); // Open this database. DatabaseMetadata db = DatabaseMetadataFactory.getInstance(filePath); changeDatabase(db); return; } catch (Exception e) { Timber.e(e, "opening database from intent"); } } boolean skipRemoteCheck = intent.getBooleanExtra(EXTRA_SKIP_REMOTE_CHECK, false); this.dbUpdateCheckDone = skipRemoteCheck; } private void initializeSync() { SyncManager sync = new SyncManager(this); if (!sync.isActive()) return; SyncPreferences preferences = new SyncPreferences(this); // Start the sync timer in case it was stopped for whatever reason. if (preferences.getSyncInterval() != 0) { sync.startSyncServiceHeartbeat(); } // Check cloud storage for updates? boolean syncOnStart = preferences.get(R.string.pref_sync_on_app_start, true); if (syncOnStart && !this.dbUpdateCheckDone) { sync.triggerSynchronization(); // This is to avoid checking for online updates on every device rotation. dbUpdateCheckDone = true; } } private void initializeDrawer() { // navigation drawer mDrawer = (DrawerLayout) findViewById(R.id.drawerLayout); // set a custom shadow that overlays the main content when the drawer opens if (mDrawer == null) return; mDrawerToggle = new MyActionBarDrawerToggle(this, mDrawer, R.string.open, R.string.closed); mDrawer.addDrawerListener(mDrawerToggle); // create drawer menu initializeDrawerVariables(); createExpandableDrawer(); // enable ActionBar app icon to behave as action to toggle nav drawer setDisplayHomeAsUpEnabled(true); getSupportActionBar().setDisplayShowTitleEnabled(true); } private void initHomeFragment() { String tag = HomeFragment.class.getSimpleName(); // See if the fragment is already there. Fragment existingFragment = getSupportFragmentManager().findFragmentByTag(tag); if (existingFragment != null) return; // Create new Home fragment. HomeFragment fragment = new HomeFragment(); int containerId = isDualPanel() ? getNavigationId() : getContentId(); getSupportFragmentManager().beginTransaction() .replace(containerId, fragment, tag) .commit(); // .commitAllowingStateLoss(); } private void initializeDrawerVariables() { mDrawerLayout = (LinearLayout) findViewById(R.id.linearLayoutDrawer); // repeating transaction LinearLayout mDrawerLinearRepeating = (LinearLayout) findViewById(R.id.linearLayoutRepeatingTransaction); if (mDrawerLinearRepeating != null) { mDrawerLinearRepeating.setVisibility(View.GONE); } mDrawerTextUserName = (TextView) findViewById(R.id.textViewUserName); mDrawerTextTotalAccounts = (TextView) findViewById(R.id.textViewTotalAccounts); } private boolean isDatabaseAvailable() { // Do we have a database set? String dbPath = new AppSettings(this).getDatabaseSettings().getDatabasePath(); if (TextUtils.isEmpty(dbPath)) return false; // Does the database file exist? File dbFile = new File(dbPath); return dbFile.exists(); } private boolean isTutorialNeeded() { return new AppSettings(this).getBehaviourSettings().getShowTutorial(); } /** * called when quick-switching the recent databases from the navigation menu. * @param recentDb selected recent database entry */ private void onOpenDatabaseClick(DatabaseMetadata recentDb) { // do nothing if selecting the currently open database String currentDb = MoneyManagerApplication.getDatabasePath(this); if (recentDb.localPath.equals(currentDb)) return; changeDatabase(recentDb); } // /** // * Pick the database file to use with any registered provider in the user's system. // * @param startFolder start folder // */ // private void pickFile(File startFolder) { // Intent intent = new Intent(Intent.ACTION_GET_CONTENT); // intent.setDataAndType(Uri.fromFile(startFolder), "vnd.android.cursor.dir/*"); // intent.setType("file/*"); // // if (MoneyManagerApplication.getApp().isUriAvailable(this, intent)) { // try { // startActivityForResult(intent, REQUEST_PICKFILE); // } catch (Exception e) { // Timber.e(e, "selecting a database file"); // } // } else { // Toast.makeText(this, R.string.error_intent_pick_file, // Toast.LENGTH_LONG).show(); // } // // // Note that the selected file is handled in onActivityResult. // } // private void requestDatabasePassword() { // // request password for the current database. // requestDatabasePassword(null); // } // private void requestDatabasePassword(String dbFilePath) { // // request password // Intent intent = new Intent(this, PasswordActivity.class); // intent.putExtra(EXTRA_DATABASE_PATH, dbFilePath); // startActivityForResult(intent, REQUEST_PASSWORD); // // continues in onActivityResult. // } private void restoreInstanceState(Bundle savedInstanceState) { if (savedInstanceState.containsKey(KEY_IN_AUTHENTICATION)) isInAuthentication = savedInstanceState.getBoolean(KEY_IN_AUTHENTICATION); if (savedInstanceState.containsKey(KEY_RECURRING_TRANSACTION)) { isRecurringTransactionStarted = savedInstanceState.getBoolean(KEY_RECURRING_TRANSACTION); } } private void startSyncIconRotation(MenuItem item) { if (item == null) return; // define the animation for rotation Animation animation = new RotateAnimation(360.0f, 0.0f, Animation.RELATIVE_TO_SELF, 0.5f, Animation.RELATIVE_TO_SELF, 0.5f); animation.setInterpolator(new LinearInterpolator()); animation.setDuration(1200); // animRotate = AnimationUtils.loadAnimation(this, R.anim.rotation); animation.setRepeatCount(Animation.INFINITE); ImageView imageView = new ImageView(this); UIHelper uiHelper = new UIHelper(this); imageView.setImageDrawable(uiHelper.getIcon(GoogleMaterial.Icon.gmd_cached) .color(uiHelper.getToolbarItemColor())); imageView.setPadding(8, 8, 8, 8); // imageView.setLayoutParams(new Toolbar.LayoutParams()); imageView.startAnimation(animation); item.setActionView(imageView); } private void stopSyncIconRotation(MenuItem item) { if (item == null) return; View actionView = item.getActionView(); if (actionView == null) return; actionView.clearAnimation(); item.setActionView(null); } /** * Shown database path with toast message * @param context Executing context. */ private void showCurrentDatabasePath(Context context) { String currentPath = MoneyManagerApplication.getDatabasePath(context); SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(context); String lastPath = preferences.getString(context.getString(PreferenceConstants.PREF_LAST_DB_PATH_SHOWN), ""); if (!lastPath.equals(currentPath)) { preferences.edit() .putString(context.getString(PreferenceConstants.PREF_LAST_DB_PATH_SHOWN), currentPath) .apply(); // .commit(); try { Toast.makeText(context, Html.fromHtml(context.getString(R.string.path_database_using, "<b>" + currentPath + "</b>")), Toast.LENGTH_LONG) .show(); } catch (Exception e) { Timber.e(e, "showing the current database path"); } } } private void showFragment_Internal(Fragment fragment, String tag) { // Check if fragment is already added. if (fragment.isAdded()) return; // In tablet layout, do not try to display the Home Fragment again. Show empty fragment. if (isDualPanel() && tag.equalsIgnoreCase(HomeFragment.class.getName())) { fragment = new Fragment(); tag = "Empty"; } FragmentTransaction transaction = getSupportFragmentManager().beginTransaction(); transaction.setCustomAnimations(R.anim.slide_in_right, R.anim.slide_out_left, R.anim.slide_in_right, R.anim.slide_out_left); // Replace whatever is in the fragment_container view with this fragment. if (isDualPanel()) { transaction.replace(R.id.fragmentDetail, fragment, tag); } else { transaction.replace(R.id.fragmentMain, fragment, tag); } transaction.addToBackStack(null); // Commit the transaction transaction.commit(); // use this call to prevent exception in some cases -> commitAllowingStateLoss() // The exception is "fragment already added". // transaction.commitAllowingStateLoss(); } /** * display any screens that need to be shown before the app actually runs. * This usually happens only on the first run of the app. * @return Indicator if another screen is showing. This means that this activity should close * and not proceed with initialisation. */ private boolean showPrerequisite() { // show tutorial on the first run if (isTutorialNeeded()) { showTutorial(); return true; } // show database chooser if no valid database if (!isDatabaseAvailable()) { showSelectDatabaseActivity(); return true; } // todo: show webview changelog? // // show change log dialog // Core core = new Core(this); // if (core.isToDisplayChangelog()) { // core.showChangelog(); // } return false; } private void showReportsSelector(String text) { final DrawerMenuItemAdapter adapter = new DrawerMenuItemAdapter(this); UIHelper uiHelper = new UIHelper(this); int iconColor = uiHelper.getSecondaryTextColor(); // payee adapter.add(new DrawerMenuItem().withId(R.id.menu_report_payees) .withText(getString(R.string.payees)) .withIconDrawable(uiHelper.getIcon(GoogleMaterial.Icon.gmd_donut_large) .color(iconColor))); // where money goes adapter.add(new DrawerMenuItem().withId(R.id.menu_report_where_money_goes) .withText(getString(R.string.menu_report_where_money_goes)) .withIconDrawable(uiHelper.getIcon(GoogleMaterial.Icon.gmd_donut_large) .color(iconColor))); // where money comes from adapter.add(new DrawerMenuItem().withId(R.id.menu_report_where_money_comes_from) .withText(getString(R.string.menu_report_where_money_comes_from)) .withIconDrawable(uiHelper.getIcon(GoogleMaterial.Icon.gmd_donut_large) .color(iconColor))); // where money comes from adapter.add(new DrawerMenuItem().withId(R.id.menu_report_categories) .withText(getString(R.string.categories)) .withIconDrawable(uiHelper.getIcon(GoogleMaterial.Icon.gmd_donut_large) .color(iconColor))); // income vs. expenses adapter.add(new DrawerMenuItem().withId(R.id.menu_report_income_vs_expenses) .withText(getString(R.string.menu_report_income_vs_expenses)) .withIconDrawable(uiHelper.getIcon(MMXIconFont.Icon.mmx_reports) .color(iconColor))); // Asset Allocation Overview adapter.add(new DrawerMenuItem().withId(R.id.menu_asset_allocation_overview) .withText(getString(R.string.asset_allocation)) .withIconDrawable(uiHelper.getIcon(MMXIconFont.Icon.mmx_chart_pie) .color(iconColor))); new MaterialDialog.Builder(this) .title(text) .adapter(adapter, new MaterialDialog.ListCallback() { @Override public void onSelection(MaterialDialog dialog, View itemView, int which, CharSequence text) { onDrawerMenuAndOptionMenuSelected(adapter.getItem(which)); dialog.dismiss(); } }) .build() .show(); } private void showSelectDatabaseActivity() { Intent intent = new Intent(this, SelectDatabaseActivity.class); // make top-level so there's no going back. intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP); startActivity(intent); } private void migrateRecentDatabases() { RecentDatabasesProvider databases = getDatabases(); // if there are entries with empty name, migrate: // - clean up the list // - create the default entry // - save the default entry. for (DatabaseMetadata entry : databases.map.values()) { if (TextUtils.isEmpty(entry.localPath)) { databases.clear(); // create & save the default entry. DatabaseMetadata currentEntry = databases.getCurrent(); return; } } } }