/*
* 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;
}
}
}
}