transactionList){
return rowInserted;
}
- /**
- * Retrieves a transaction object from a database with database ID rowId
- * @param rowId Identifier of the transaction record to be retrieved
- * @return {@link Transaction} object corresponding to database record
- */
- public Transaction getTransaction(long rowId) {
- Log.v(LOG_TAG, "Fetching transaction with id " + rowId);
- Cursor c = fetchRecord(rowId);
- try {
- if (c.moveToFirst()) {
- return buildTransactionInstance(c);
- } else {
- throw new IllegalArgumentException("row " + rowId + " does not exist");
- }
- } finally {
- c.close();
+ @Override
+ protected SQLiteStatement compileReplaceStatement(@NonNull final Transaction transaction) {
+ if (mReplaceStatement == null) {
+ mReplaceStatement = mDb.compileStatement("REPLACE INTO " + TransactionEntry.TABLE_NAME + " ( "
+ + TransactionEntry.COLUMN_UID + " , "
+ + TransactionEntry.COLUMN_DESCRIPTION + " , "
+ + TransactionEntry.COLUMN_NOTES + " , "
+ + TransactionEntry.COLUMN_TIMESTAMP + " , "
+ + TransactionEntry.COLUMN_EXPORTED + " , "
+ + TransactionEntry.COLUMN_CURRENCY + " , "
+ + TransactionEntry.COLUMN_COMMODITY_UID + " , "
+ + TransactionEntry.COLUMN_CREATED_AT + " , "
+ + TransactionEntry.COLUMN_SCHEDX_ACTION_UID + " , "
+ + TransactionEntry.COLUMN_TEMPLATE + " ) VALUES ( ? , ? , ? , ?, ? , ? , ? , ?, ? , ?)");
}
+
+ mReplaceStatement.clearBindings();
+ mReplaceStatement.bindString(1, transaction.getUID());
+ mReplaceStatement.bindString(2, transaction.getDescription());
+ mReplaceStatement.bindString(3, transaction.getNote());
+ mReplaceStatement.bindLong(4, transaction.getTimeMillis());
+ mReplaceStatement.bindLong(5, transaction.isExported() ? 1 : 0);
+ mReplaceStatement.bindString(6, transaction.getCurrencyCode());
+
+ String commodityUID = transaction.getCommodityUID();
+ if (commodityUID == null)
+ commodityUID = getCommodityUID(transaction.getCurrency().getCurrencyCode());
+
+ mReplaceStatement.bindString(7, commodityUID);
+ mReplaceStatement.bindString(8, transaction.getCreatedTimestamp().toString());
+
+ if (transaction.getScheduledActionUID() == null)
+ mReplaceStatement.bindNull(9);
+ else
+ mReplaceStatement.bindString(9, transaction.getScheduledActionUID());
+ mReplaceStatement.bindLong(10, transaction.isTemplate() ? 1 : 0);
+
+ return mReplaceStatement;
}
-
- /**
+
+ /**
* Returns a cursor to a set of all transactions which have a split belonging to the accound with unique ID
* accountUID
.
* @param accountUID UID of the account whose transactions are to be retrieved
@@ -237,6 +219,28 @@ public Cursor fetchAllTransactionsForAccount(String accountUID){
return queryBuilder.query(mDb, projectionIn, selection, selectionArgs, null, null, sortOrder);
}
+ /**
+ * Returns a cursor to all scheduled transactions which have at least one split in the account
+ * This is basically a set of all template transactions for this account
+ * @param accountUID GUID of account
+ * @return Cursor with set of transactions
+ */
+ public Cursor fetchScheduledTransactionsForAccount(String accountUID){
+ SQLiteQueryBuilder queryBuilder = new SQLiteQueryBuilder();
+ queryBuilder.setTables(TransactionEntry.TABLE_NAME
+ + " INNER JOIN " + SplitEntry.TABLE_NAME + " ON "
+ + TransactionEntry.TABLE_NAME + "." + TransactionEntry.COLUMN_UID + " = "
+ + SplitEntry.TABLE_NAME + "." + SplitEntry.COLUMN_TRANSACTION_UID);
+ queryBuilder.setDistinct(true);
+ String[] projectionIn = new String[]{TransactionEntry.TABLE_NAME + ".*"};
+ String selection = SplitEntry.TABLE_NAME + "." + SplitEntry.COLUMN_ACCOUNT_UID + " = ?"
+ + " AND " + TransactionEntry.TABLE_NAME + "." + TransactionEntry.COLUMN_TEMPLATE + " = 1";
+ String[] selectionArgs = new String[]{accountUID};
+ String sortOrder = TransactionEntry.TABLE_NAME + "." + TransactionEntry.COLUMN_TIMESTAMP + " DESC";
+
+ return queryBuilder.query(mDb, projectionIn, selection, selectionArgs, null, null, sortOrder);
+ }
+
/**
* Deletes all transactions which contain a split in the account.
* Note:As long as the transaction has one split which belongs to the account {@code accountUID},
@@ -304,7 +308,7 @@ public List getAllTransactionsForAccount(String accountUID){
ArrayList transactionsList = new ArrayList<>();
try {
while (c.moveToNext()) {
- transactionsList.add(buildTransactionInstance(c));
+ transactionsList.add(buildModelInstance(c));
}
} finally {
c.close();
@@ -321,7 +325,7 @@ public List getAllTransactions(){
List transactions = new ArrayList();
try {
while (cursor.moveToNext()) {
- transactions.add(buildTransactionInstance(cursor));
+ transactions.add(buildModelInstance(cursor));
}
} finally {
cursor.close();
@@ -361,19 +365,19 @@ public Cursor fetchTransactionsWithSplitsWithTransactionAccount(String [] column
* Return number of transactions in the database which are non recurring
* @return Number of transactions
*/
- public int getTotalTransactionsCount() {
+ public long getRecordsCount() {
String queryCount = "SELECT COUNT(*) FROM " + TransactionEntry.TABLE_NAME +
" WHERE " + TransactionEntry.COLUMN_TEMPLATE + " =0";
Cursor cursor = mDb.rawQuery(queryCount, null);
try {
cursor.moveToFirst();
- return cursor.getInt(0);
+ return cursor.getLong(0);
} finally {
cursor.close();
}
}
- public int getTotalTransactionsCount(@Nullable String where, @Nullable String[] whereArgs) {
+ public long getRecordsCount(@Nullable String where, @Nullable String[] whereArgs) {
Cursor cursor = mDb.query(true, TransactionEntry.TABLE_NAME + " , trans_extra_info ON "
+ TransactionEntry.TABLE_NAME + "." + TransactionEntry.COLUMN_UID
+ " = trans_extra_info.trans_acct_t_uid",
@@ -386,7 +390,7 @@ public int getTotalTransactionsCount(@Nullable String where, @Nullable String[]
null);
try{
cursor.moveToFirst();
- return cursor.getInt(0);
+ return cursor.getLong(0);
} finally {
cursor.close();
}
@@ -398,10 +402,11 @@ public int getTotalTransactionsCount(@Nullable String where, @Nullable String[]
* @param c Cursor pointing to transaction record in database
* @return {@link Transaction} object constructed from database record
*/
- public Transaction buildTransactionInstance(Cursor c){
+ @Override
+ public Transaction buildModelInstance(@NonNull final Cursor c){
String name = c.getString(c.getColumnIndexOrThrow(TransactionEntry.COLUMN_DESCRIPTION));
Transaction transaction = new Transaction(name);
- populateModel(c, transaction);
+ populateBaseModelAttributes(c, transaction);
transaction.setTime(c.getLong(c.getColumnIndexOrThrow(TransactionEntry.COLUMN_TIMESTAMP)));
transaction.setNote(c.getString(c.getColumnIndexOrThrow(TransactionEntry.COLUMN_NOTES)));
@@ -456,7 +461,7 @@ public int moveTransaction(String transactionUID, String srcAccountUID, String d
for (Split split : splits) {
split.setAccountUID(dstAccountUID);
}
- mSplitsDbAdapter.bulkAddSplits(splits);
+ mSplitsDbAdapter.bulkAddRecords(splits);
return splits.size();
}
@@ -502,6 +507,37 @@ public long getTemplateTransactionsCount(){
return statement.simpleQueryForLong();
}
+ /**
+ * Returns a list of all scheduled transactions in the database
+ * @return List of all scheduled transactions
+ */
+ public List getScheduledTransactionsForAccount(String accountUID){
+ Cursor cursor = fetchScheduledTransactionsForAccount(accountUID);
+ List scheduledTransactions = new ArrayList<>();
+ try {
+ while (cursor.moveToNext()) {
+ scheduledTransactions.add(buildModelInstance(cursor));
+ }
+ return scheduledTransactions;
+ } finally {
+ cursor.close();
+ }
+ }
+
+ /**
+ * Returns the number of splits for the transaction in the database
+ * @param transactionUID GUID of the transaction
+ * @return Number of splits belonging to the transaction
+ */
+ public long getSplitCount(@NonNull String transactionUID){
+ if (transactionUID == null)
+ return 0;
+ String sql = "SELECT COUNT(*) FROM " + SplitEntry.TABLE_NAME
+ + " WHERE " + SplitEntry.COLUMN_TRANSACTION_UID + "= '" + transactionUID + "'";
+ SQLiteStatement statement = mDb.compileStatement(sql);
+ return statement.simpleQueryForLong();
+ }
+
/**
* Returns a cursor to transactions whose name (UI: description) start with the prefix
* This method is used for autocomplete suggestions when creating new transactions.
@@ -540,15 +576,6 @@ public int updateTransaction(ContentValues contentValues, String whereClause, St
return mDb.update(TransactionEntry.TABLE_NAME, contentValues, whereClause, whereArgs);
}
- /**
- * Returns a transaction for the given transaction GUID
- * @param transactionUID GUID of the transaction
- * @return Retrieves a transaction from the database
- */
- public Transaction getTransaction(String transactionUID) {
- return getTransaction(getID(transactionUID));
- }
-
/**
* Return the number of currencies used in the transaction.
* For example if there are different splits with different currencies
diff --git a/app/src/main/java/org/gnucash/android/export/ExportAsyncTask.java b/app/src/main/java/org/gnucash/android/export/ExportAsyncTask.java
index 601070ceb..94391f824 100644
--- a/app/src/main/java/org/gnucash/android/export/ExportAsyncTask.java
+++ b/app/src/main/java/org/gnucash/android/export/ExportAsyncTask.java
@@ -23,6 +23,7 @@
import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
+import android.content.pm.ResolveInfo;
import android.net.Uri;
import android.os.AsyncTask;
import android.os.Build;
@@ -51,7 +52,6 @@
import org.gnucash.android.db.TransactionsDbAdapter;
import org.gnucash.android.export.ofx.OfxExporter;
import org.gnucash.android.export.qif.QifExporter;
-import org.gnucash.android.export.qif.QifHelper;
import org.gnucash.android.export.xml.GncXmlExporter;
import org.gnucash.android.model.Transaction;
import org.gnucash.android.ui.account.AccountsActivity;
@@ -59,13 +59,10 @@
import org.gnucash.android.ui.settings.SettingsActivity;
import org.gnucash.android.ui.transaction.TransactionsActivity;
-import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
-import java.io.FileReader;
-import java.io.FileWriter;
import java.io.IOException;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
@@ -92,7 +89,7 @@ public class ExportAsyncTask extends AsyncTask {
/**
* Log tag
*/
- public static final String TAG = "ExporterAsyncTask";
+ public static final String TAG = "ExportAsyncTask";
/**
* Export parameters
@@ -149,6 +146,7 @@ protected Boolean doInBackground(ExportParams... params) {
File file = new File(mExportParams.getTargetFilepath());
BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(file), "UTF-8"));
try {
+ // FIXME: detect if there aren't transactions to export and inform the user
mExporter.generateExport(writer);
writer.flush();
}
@@ -176,7 +174,8 @@ public void run() {
switch (mExportParams.getExportTarget()) {
case SHARING:
- shareFile(mExportParams.getTargetFilepath());
+ File output = moveExportToSDCard();
+ shareFile(output.getAbsolutePath());
return true;
case DROPBOX:
@@ -188,7 +187,7 @@ public void run() {
return true;
case SD_CARD:
- copyExportToSDCard();
+ moveExportToSDCard();
return true;
}
@@ -197,6 +196,7 @@ public void run() {
/**
* Transmits the exported transactions to the designated location, either SD card or third-party application
+ * Finishes the activity if the export was starting in the context of an activity
* @param exportResult Result of background export execution
*/
@Override
@@ -246,6 +246,7 @@ protected void onPostExecute(Boolean exportResult) {
if (mContext instanceof Activity) {
if (mProgressDialog != null && mProgressDialog.isShowing())
mProgressDialog.dismiss();
+ ((Activity) mContext).finish();
}
}
@@ -366,7 +367,7 @@ private List getExportedFiles() throws IOException {
List exportedFilePaths;
if (mExportParams.getExportFormat() == ExportFormat.QIF) {
String path = mExportParams.getTargetFilepath();
- exportedFilePaths = splitQIF(new File(path), new File(path));
+ exportedFilePaths = QifExporter.splitQIF(new File(path));
} else {
exportedFilePaths = new ArrayList<>();
exportedFilePaths.add(mExportParams.getTargetFilepath());
@@ -375,10 +376,11 @@ private List getExportedFiles() throws IOException {
}
/**
- * Copies the exported file from the internal storage where it is generated to external storage
- * which is accessible to the user
+ * Moves the exported file from the internal storage where it is generated to external storage
+ * which is accessible to the user.
+ * @return File to which the export was moved.
*/
- private void copyExportToSDCard() {
+ private File moveExportToSDCard() {
Log.i(TAG, "Moving exported file to external storage");
File src = new File(mExportParams.getTargetFilepath());
File dst = Exporter.createExportFile(mExportParams.getExportFormat());
@@ -386,6 +388,7 @@ private void copyExportToSDCard() {
try {
copyFile(src, dst);
src.delete();
+ return dst;
} catch (IOException e) {
Crashlytics.logException(e);
Log.e(TAG, e.getMessage());
@@ -409,7 +412,7 @@ private void backupAndDeleteTransactions(){
transactionsDbAdapter.deleteAllNonTemplateTransactions();
if (preserveOpeningBalances) {
- transactionsDbAdapter.bulkAddTransactions(openingBalances);
+ transactionsDbAdapter.bulkAddRecords(openingBalances);
}
}
@@ -426,7 +429,7 @@ private void shareFile(String path) {
ArrayList exportFiles = new ArrayList<>();
if (mExportParams.getExportFormat() == ExportFormat.QIF) {
try {
- List splitFiles = splitQIF(new File(path), new File(path));
+ List splitFiles = QifExporter.splitQIF(new File(path));
for (String file : splitFiles) {
exportFiles.add(Uri.parse("file://" + file));
}
@@ -446,34 +449,18 @@ private void shareFile(String path) {
}
SimpleDateFormat formatter = (SimpleDateFormat) SimpleDateFormat.getDateTimeInstance();
- ArrayList extraText = new ArrayList();
+ ArrayList extraText = new ArrayList<>();
extraText.add(mContext.getString(R.string.description_export_email)
+ " " + formatter.format(new Date(System.currentTimeMillis())));
shareIntent.putExtra(Intent.EXTRA_TEXT, extraText);
- if (mContext instanceof Activity)
- mContext.startActivity(Intent.createChooser(shareIntent, mContext.getString(R.string.title_select_export_destination)));
- }
-
- /**
- * Copies a file from src
to dst
- * @param src Absolute path to the source file
- * @param dst Absolute path to the destination file
- * @throws IOException if the file could not be copied
- */
- public void copyFile(File src, File dst) throws IOException {
- //TODO: Make this asynchronous at some time, t in the future.
- if (mExportParams.getExportFormat() == ExportFormat.QIF) {
- splitQIF(src, dst);
- } else {
- FileChannel inChannel = new FileInputStream(src).getChannel();
- FileChannel outChannel = new FileOutputStream(dst).getChannel();
- try {
- inChannel.transferTo(0, inChannel.size(), outChannel);
- } finally {
- if (inChannel != null)
- inChannel.close();
- outChannel.close();
+ if (mContext instanceof Activity) {
+ List activities = mContext.getPackageManager().queryIntentActivities(shareIntent, 0);
+ if (activities != null && !activities.isEmpty()) {
+ mContext.startActivity(Intent.createChooser(shareIntent, mContext.getString(R.string.title_select_export_destination)));
+ } else {
+ Toast.makeText(mContext, R.string.toast_no_compatible_apps_to_receive_export,
+ Toast.LENGTH_LONG).show();
}
}
}
@@ -484,37 +471,17 @@ public void copyFile(File src, File dst) throws IOException {
* @param dst Absolute path to the destination file
* @throws IOException if the file could not be copied
*/
- private static List splitQIF(File src, File dst) throws IOException {
- // split only at the last dot
- String[] pathParts = dst.getPath().split("(?=\\.[^\\.]+$)");
- ArrayList splitFiles = new ArrayList<>();
- String line;
- BufferedReader in = new BufferedReader(new FileReader(src));
- BufferedWriter out = null;
+ public void copyFile(File src, File dst) throws IOException {
+ //TODO: Make this asynchronous at some time, t in the future.
+ FileChannel inChannel = new FileInputStream(src).getChannel();
+ FileChannel outChannel = new FileOutputStream(dst).getChannel();
try {
- while ((line = in.readLine()) != null) {
- if (line.startsWith(QifHelper.INTERNAL_CURRENCY_PREFIX)) {
- String currencyCode = line.substring(1);
- if (out != null) {
- out.close();
- }
- String newFileName = pathParts[0] + "_" + currencyCode + pathParts[1];
- splitFiles.add(newFileName);
- out = new BufferedWriter(new FileWriter(newFileName));
- } else {
- if (out == null) {
- throw new IllegalArgumentException(src.getPath() + " format is not correct");
- }
- out.append(line).append('\n');
- }
- }
+ inChannel.transferTo(0, inChannel.size(), outChannel);
} finally {
- in.close();
- if (out != null) {
- out.close();
- }
+ if (inChannel != null)
+ inChannel.close();
+ outChannel.close();
}
- return splitFiles;
}
}
diff --git a/app/src/main/java/org/gnucash/android/export/ExportParams.java b/app/src/main/java/org/gnucash/android/export/ExportParams.java
index 120d052b3..6b23ede19 100644
--- a/app/src/main/java/org/gnucash/android/export/ExportParams.java
+++ b/app/src/main/java/org/gnucash/android/export/ExportParams.java
@@ -17,13 +17,13 @@
package org.gnucash.android.export;
import org.gnucash.android.app.GnuCashApplication;
-import org.gnucash.android.ui.export.ExportDialogFragment;
+import org.gnucash.android.ui.export.ExportFormFragment;
/**
* Encapsulation of the parameters used for exporting transactions.
* The parameters are determined by the user in the export dialog and are then transmitted to the asynchronous task which
* actually performs the export.
- * @see ExportDialogFragment
+ * @see ExportFormFragment
* @see ExportAsyncTask
*
* @author Ngewi Fet
@@ -85,7 +85,8 @@ public ExportFormat getExportFormat() {
*/
public void setExportFormat(ExportFormat exportFormat) {
this.mExportFormat = exportFormat;
- mTargetFilepath = GnuCashApplication.getAppContext().getExternalFilesDir(null) + "/" + Exporter.buildExportFilename(mExportFormat);
+ this.mTargetFilepath = GnuCashApplication.getAppContext().getFilesDir() + "/"
+ + Exporter.buildExportFilename(mExportFormat);
}
/**
@@ -145,14 +146,6 @@ public String getTargetFilepath() {
return mTargetFilepath;
}
- /**
- * Sets target file path for transactions in private application storage
- * @param mTargetFilepath String path to file
- */
- public void setTargetFilepath(String mTargetFilepath) {
- this.mTargetFilepath = mTargetFilepath;
- }
-
@Override
public String toString() {
return "Export " + mExportFormat.name() + " to " + mExportTarget.name() + " at "
diff --git a/app/src/main/java/org/gnucash/android/export/Exporter.java b/app/src/main/java/org/gnucash/android/export/Exporter.java
index 29ce1e791..41f041661 100644
--- a/app/src/main/java/org/gnucash/android/export/Exporter.java
+++ b/app/src/main/java/org/gnucash/android/export/Exporter.java
@@ -20,6 +20,7 @@
import android.content.Context;
import android.database.sqlite.SQLiteDatabase;
import android.os.Environment;
+import android.support.annotation.NonNull;
import android.util.Log;
import com.crashlytics.android.Crashlytics;
@@ -27,6 +28,8 @@
import org.gnucash.android.BuildConfig;
import org.gnucash.android.app.GnuCashApplication;
import org.gnucash.android.db.AccountsDbAdapter;
+import org.gnucash.android.db.CommoditiesDbAdapter;
+import org.gnucash.android.db.PricesDbAdapter;
import org.gnucash.android.db.ScheduledActionDbAdapter;
import org.gnucash.android.db.SplitsDbAdapter;
import org.gnucash.android.db.TransactionsDbAdapter;
@@ -34,6 +37,7 @@
import java.io.File;
import java.io.FileFilter;
import java.io.Writer;
+import java.sql.Timestamp;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
@@ -73,6 +77,13 @@ public abstract class Exporter {
protected ExportParams mParameters;
private static final SimpleDateFormat EXPORT_FILENAME_DATE_FORMAT = new SimpleDateFormat("yyyyMMdd_HHmmss", Locale.US);
+
+ /**
+ * last export time in preferences
+ */
+ public static final String PREF_LAST_EXPORT_TIME = "last_export_time";
+
+ public static final String TIMESTAMP_ZERO = new Timestamp(0).toString();
/**
* Adapter for retrieving accounts to export
* Subclasses should close this object when they are done with exporting
@@ -81,6 +92,8 @@ public abstract class Exporter {
protected TransactionsDbAdapter mTransactionsDbAdapter;
protected SplitsDbAdapter mSplitsDbAdapter;
protected ScheduledActionDbAdapter mScheduledActionDbAdapter;
+ protected PricesDbAdapter mPricesDbAdpater;
+ protected CommoditiesDbAdapter mCommoditiesDbAdapter;
protected Context mContext;
public Exporter(ExportParams params, SQLiteDatabase db) {
@@ -91,11 +104,15 @@ public Exporter(ExportParams params, SQLiteDatabase db) {
mTransactionsDbAdapter = TransactionsDbAdapter.getInstance();
mSplitsDbAdapter = SplitsDbAdapter.getInstance();
mScheduledActionDbAdapter = ScheduledActionDbAdapter.getInstance();
+ mPricesDbAdpater = PricesDbAdapter.getInstance();
+ mCommoditiesDbAdapter = CommoditiesDbAdapter.getInstance();
} else {
mSplitsDbAdapter = new SplitsDbAdapter(db);
mTransactionsDbAdapter = new TransactionsDbAdapter(db, mSplitsDbAdapter);
mAccountsDbAdapter = new AccountsDbAdapter(db, mTransactionsDbAdapter);
mScheduledActionDbAdapter = new ScheduledActionDbAdapter(db);
+ mPricesDbAdpater = new PricesDbAdapter(db);
+ mCommoditiesDbAdapter = new CommoditiesDbAdapter(db);
}
}
@@ -192,6 +209,10 @@ public ExporterException(ExportParams params){
super("Failed to generate " + params.getExportFormat().toString());
}
+ public ExporterException(@NonNull ExportParams params, @NonNull String msg) {
+ super("Failed to generate " + params.getExportFormat().toString() + "-" + msg);
+ }
+
public ExporterException(ExportParams params, Throwable throwable){
super("Failed to generate " + params.getExportFormat().toString() +"-"+ throwable.getMessage(),
throwable);
diff --git a/app/src/main/java/org/gnucash/android/export/ofx/OfxExporter.java b/app/src/main/java/org/gnucash/android/export/ofx/OfxExporter.java
index f7eb81e0c..ff9b144ea 100644
--- a/app/src/main/java/org/gnucash/android/export/ofx/OfxExporter.java
+++ b/app/src/main/java/org/gnucash/android/export/ofx/OfxExporter.java
@@ -23,6 +23,7 @@
import com.crashlytics.android.Crashlytics;
import org.gnucash.android.R;
+import org.gnucash.android.app.GnuCashApplication;
import org.gnucash.android.db.AccountsDbAdapter;
import org.gnucash.android.export.ExportParams;
import org.gnucash.android.export.Exporter;
@@ -36,6 +37,7 @@
import java.io.IOException;
import java.io.StringWriter;
import java.io.Writer;
+import java.sql.Timestamp;
import java.util.List;
import javax.xml.parsers.DocumentBuilder;
@@ -43,7 +45,6 @@
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.transform.OutputKeys;
import javax.xml.transform.Transformer;
-import javax.xml.transform.TransformerConfigurationException;
import javax.xml.transform.TransformerException;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.dom.DOMSource;
@@ -91,7 +92,12 @@ private void generateOfx(Document doc, Element parent){
for (Account account : mAccountsList) {
if (account.getTransactionCount() == 0)
continue;
-
+
+ //do not export imbalance accounts for OFX transactions and double-entry disabled
+ if (!GnuCashApplication.isDoubleEntryEnabled() && account.getName().contains(mContext.getString(R.string.imbalance_account_name)))
+ continue;
+
+
//add account details (transactions) to the XML document
account.toOfx(doc, statementTransactionResponse, mParameters.shouldExportAllTransactions());
@@ -103,7 +109,7 @@ private void generateOfx(Document doc, Element parent){
public String generateExport() throws ExporterException {
mAccountsList = mParameters.shouldExportAllTransactions() ?
- mAccountsDbAdapter.getAllAccounts() : mAccountsDbAdapter.getExportableAccounts();
+ mAccountsDbAdapter.getAllRecords() : mAccountsDbAdapter.getExportableAccounts();
DocumentBuilderFactory docFactory = DocumentBuilderFactory
.newInstance();
@@ -126,10 +132,13 @@ public String generateExport() throws ExporterException {
boolean useXmlHeader = PreferenceManager.getDefaultSharedPreferences(mContext)
.getBoolean(mContext.getString(R.string.key_xml_ofx_header), false);
+ String timeStamp = new Timestamp(System.currentTimeMillis()).toString();
+
StringWriter stringWriter = new StringWriter();
//if we want SGML OFX headers, write first to string and then prepend header
if (useXmlHeader){
write(document, stringWriter, false);
+ PreferenceManager.getDefaultSharedPreferences(mContext).edit().putString(Exporter.PREF_LAST_EXPORT_TIME, timeStamp).apply();
return stringWriter.toString();
} else {
Node ofxNode = document.getElementsByTagName("OFX").item(0);
@@ -139,6 +148,7 @@ public String generateExport() throws ExporterException {
StringBuffer stringBuffer = new StringBuffer(OfxHelper.OFX_SGML_HEADER);
stringBuffer.append('\n');
stringBuffer.append(stringWriter.toString());
+ PreferenceManager.getDefaultSharedPreferences(mContext).edit().putString(Exporter.PREF_LAST_EXPORT_TIME, timeStamp).apply();
return stringBuffer.toString();
}
}
diff --git a/app/src/main/java/org/gnucash/android/export/qif/QifExporter.java b/app/src/main/java/org/gnucash/android/export/qif/QifExporter.java
index db8349ea4..27f362719 100644
--- a/app/src/main/java/org/gnucash/android/export/qif/QifExporter.java
+++ b/app/src/main/java/org/gnucash/android/export/qif/QifExporter.java
@@ -18,16 +18,27 @@
import android.content.ContentValues;
import android.database.Cursor;
+import android.preference.PreferenceManager;
import org.gnucash.android.db.AccountsDbAdapter;
+import org.gnucash.android.db.DatabaseSchema;
import org.gnucash.android.db.TransactionsDbAdapter;
import org.gnucash.android.export.ExportParams;
import org.gnucash.android.export.Exporter;
+import org.gnucash.android.model.Transaction;
+import java.io.BufferedReader;
+import java.io.BufferedWriter;
+import java.io.File;
+import java.io.FileReader;
+import java.io.FileWriter;
import java.io.IOException;
import java.io.Writer;
import java.math.BigDecimal;
+import java.sql.Timestamp;
+import java.util.ArrayList;
import java.util.Currency;
+import java.util.List;
import static org.gnucash.android.db.DatabaseSchema.AccountEntry;
import static org.gnucash.android.db.DatabaseSchema.SplitEntry;
@@ -50,12 +61,14 @@ public void generateExport(Writer writer) throws ExporterException {
final String newLine = "\n";
TransactionsDbAdapter transactionsDbAdapter = mTransactionsDbAdapter;
try {
+ String lastExportTimeStamp = PreferenceManager.getDefaultSharedPreferences(mContext).getString(Exporter.PREF_LAST_EXPORT_TIME, Exporter.TIMESTAMP_ZERO);
Cursor cursor = transactionsDbAdapter.fetchTransactionsWithSplitsWithTransactionAccount(
new String[]{
TransactionEntry.TABLE_NAME + "_" + TransactionEntry.COLUMN_UID + " AS trans_uid",
TransactionEntry.TABLE_NAME + "_" + TransactionEntry.COLUMN_TIMESTAMP + " AS trans_time",
TransactionEntry.TABLE_NAME + "_" + TransactionEntry.COLUMN_DESCRIPTION + " AS trans_desc",
- SplitEntry.TABLE_NAME + "_" + SplitEntry.COLUMN_AMOUNT + " AS split_amount",
+ SplitEntry.TABLE_NAME + "_" + SplitEntry.COLUMN_QUANTITY_NUM + " AS split_quantity_num",
+ SplitEntry.TABLE_NAME + "_" + SplitEntry.COLUMN_QUANTITY_DENOM + " AS split_quantity_denom",
SplitEntry.TABLE_NAME + "_" + SplitEntry.COLUMN_TYPE + " AS split_type",
SplitEntry.TABLE_NAME + "_" + SplitEntry.COLUMN_MEMO + " AS split_memo",
"trans_extra_info.trans_acct_balance AS trans_acct_balance",
@@ -76,7 +89,8 @@ public void generateExport(Writer writer) throws ExporterException {
"trans_split_count == 1 )" +
(
mParameters.shouldExportAllTransactions() ?
- "" : " AND " + TransactionEntry.TABLE_NAME + "_" + TransactionEntry.COLUMN_EXPORTED + "== 0"
+ //"" : " AND " + TransactionEntry.TABLE_NAME + "_" + TransactionEntry.COLUMN_EXPORTED + "== 0"
+ "" : " AND " + TransactionEntry.TABLE_NAME + "_" + DatabaseSchema.CommonColumns.COLUMN_MODIFIED_AT + " > \"" + lastExportTimeStamp + "\""
),
null,
// trans_time ASC : put transactions in time order
@@ -156,9 +170,34 @@ public void generateExport(Writer writer) throws ExporterException {
.append(newLine);
}
String splitType = cursor.getString(cursor.getColumnIndexOrThrow("split_type"));
+ Double quantity_num = cursor.getDouble(cursor.getColumnIndexOrThrow("split_quantity_num"));
+ int quantity_denom = cursor.getInt(cursor.getColumnIndexOrThrow("split_quantity_denom"));
+ int precision = 0;
+ switch (quantity_denom) {
+ case 0: // will sometimes happen for zero values
+ break;
+ case 1:
+ precision = 0;
+ break;
+ case 10:
+ precision = 1;
+ break;
+ case 100:
+ precision = 2;
+ break;
+ case 1000:
+ precision = 3;
+ break;
+ default:
+ throw new ExporterException(mParameters, "split quantity has illegal denominator: "+ quantity_denom);
+ }
+ Double quantity = 0.0;
+ if (quantity_denom != 0) {
+ quantity = quantity_num / quantity_denom;
+ }
writer.append(QifHelper.SPLIT_AMOUNT_PREFIX)
.append(splitType.equals("DEBIT") ? "-" : "")
- .append(cursor.getString(cursor.getColumnIndexOrThrow("split_amount")))
+ .append(String.format("%." + precision + "f", quantity))
.append(newLine);
}
if (!currentTransactionUID.equals("")) {
@@ -177,5 +216,49 @@ public void generateExport(Writer writer) throws ExporterException {
{
throw new ExporterException(mParameters, e);
}
+
+ /// export successful
+ String timeStamp = new Timestamp(System.currentTimeMillis()).toString();
+ PreferenceManager.getDefaultSharedPreferences(mContext).edit().putString(Exporter.PREF_LAST_EXPORT_TIME, timeStamp).apply();
+ }
+
+ /**
+ * Splits a Qif file into several ones for each currency.
+ *
+ * @param file File object of the Qif file to split.
+ * @return a list of paths of the newly created Qif files.
+ * @throws IOException if something went wrong while splitting the file.
+ */
+ public static List splitQIF(File file) throws IOException {
+ // split only at the last dot
+ String[] pathParts = file.getPath().split("(?=\\.[^\\.]+$)");
+ ArrayList splitFiles = new ArrayList<>();
+ String line;
+ BufferedReader in = new BufferedReader(new FileReader(file));
+ BufferedWriter out = null;
+ try {
+ while ((line = in.readLine()) != null) {
+ if (line.startsWith(QifHelper.INTERNAL_CURRENCY_PREFIX)) {
+ String currencyCode = line.substring(1);
+ if (out != null) {
+ out.close();
+ }
+ String newFileName = pathParts[0] + "_" + currencyCode + pathParts[1];
+ splitFiles.add(newFileName);
+ out = new BufferedWriter(new FileWriter(newFileName));
+ } else {
+ if (out == null) {
+ throw new IllegalArgumentException(file.getPath() + " format is not correct");
+ }
+ out.append(line).append('\n');
+ }
+ }
+ } finally {
+ in.close();
+ if (out != null) {
+ out.close();
+ }
+ }
+ return splitFiles;
}
}
diff --git a/app/src/main/java/org/gnucash/android/export/xml/GncXmlExporter.java b/app/src/main/java/org/gnucash/android/export/xml/GncXmlExporter.java
index fd3fef1e9..8577781f7 100644
--- a/app/src/main/java/org/gnucash/android/export/xml/GncXmlExporter.java
+++ b/app/src/main/java/org/gnucash/android/export/xml/GncXmlExporter.java
@@ -30,6 +30,8 @@
import org.gnucash.android.export.Exporter;
import org.gnucash.android.model.Account;
import org.gnucash.android.model.AccountType;
+import org.gnucash.android.model.BaseModel;
+import org.gnucash.android.model.Money;
import org.gnucash.android.model.PeriodType;
import org.gnucash.android.model.ScheduledAction;
import org.gnucash.android.model.TransactionType;
@@ -113,6 +115,8 @@ private void exportSlots(XmlSerializer xmlSerializer,
}
private void exportAccounts(XmlSerializer xmlSerializer) throws IOException {
+ // gnucash desktop requires that parent account appears before its descendants.
+ // sort by full-name to fulfill the request
Cursor cursor = mAccountsDbAdapter.fetchAccounts(null, null, DatabaseSchema.AccountEntry.COLUMN_FULL_NAME + " ASC");
while (cursor.moveToNext()) {
// write account
@@ -147,9 +151,12 @@ private void exportAccounts(XmlSerializer xmlSerializer) throws IOException {
xmlSerializer.text(Integer.toString((int) Math.pow(10, Currency.getInstance(acctCurrencyCode).getDefaultFractionDigits())));
xmlSerializer.endTag(null, GncXmlHelper.TAG_COMMODITY_SCU);
// account description
- // this is optional in Gnc XML, and currently not in the db, so description node
- // is omitted
- //
+ String description = cursor.getString(cursor.getColumnIndexOrThrow(DatabaseSchema.AccountEntry.COLUMN_DESCRIPTION));
+ if (description != null && !description.equals("")) {
+ xmlSerializer.startTag(null, GncXmlHelper.TAG_ACCT_DESCRIPTION);
+ xmlSerializer.text(description);
+ xmlSerializer.endTag(null, GncXmlHelper.TAG_ACCT_DESCRIPTION);
+ }
// account slots, color, placeholder, default transfer account, favorite
ArrayList slotKey = new ArrayList<>();
ArrayList slotType = new ArrayList<>();
@@ -268,8 +275,10 @@ private void exportTransactions(XmlSerializer xmlSerializer, boolean exportTempl
SplitEntry.TABLE_NAME+"."+ SplitEntry.COLUMN_UID + " AS split_uid",
SplitEntry.TABLE_NAME+"."+ SplitEntry.COLUMN_MEMO + " AS split_memo",
SplitEntry.TABLE_NAME+"."+ SplitEntry.COLUMN_TYPE + " AS split_type",
- SplitEntry.TABLE_NAME+"."+ SplitEntry.COLUMN_AMOUNT + " AS split_amount",
- SplitEntry.TABLE_NAME+"."+ SplitEntry.COLUMN_ACCOUNT_UID + " AS split_acct_uid"},
+ SplitEntry.TABLE_NAME+"."+ SplitEntry.COLUMN_VALUE_NUM + " AS split_value_num",
+ SplitEntry.TABLE_NAME+"."+ SplitEntry.COLUMN_VALUE_DENOM + " AS split_value_denom",
+ SplitEntry.TABLE_NAME+"."+ SplitEntry.COLUMN_QUANTITY_NUM + " AS split_quantity_num",
+ SplitEntry.TABLE_NAME+"."+ SplitEntry.COLUMN_QUANTITY_DENOM + " AS split_quantity_denom", SplitEntry.TABLE_NAME+"."+ SplitEntry.COLUMN_ACCOUNT_UID + " AS split_acct_uid"},
where, null,
TransactionEntry.TABLE_NAME + "." + TransactionEntry.COLUMN_TIMESTAMP + " ASC , " +
TransactionEntry.TABLE_NAME + "." + TransactionEntry.COLUMN_UID + " ASC ");
@@ -282,7 +291,7 @@ private void exportTransactions(XmlSerializer xmlSerializer, boolean exportTempl
mRootTemplateAccount.setAccountType(AccountType.ROOT);
mTransactionToTemplateAccountMap.put(" ", mRootTemplateAccount);
while (cursor.moveToNext()) {
- Account account = new Account(UUID.randomUUID().toString().replaceAll("-", ""));
+ Account account = new Account(BaseModel.generateUID());
account.setAccountType(AccountType.BANK);
String trnUID = cursor.getString(cursor.getColumnIndexOrThrow("trans_uid"));
mTransactionToTemplateAccountMap.put(trnUID, account);
@@ -392,16 +401,22 @@ private void exportTransactions(XmlSerializer xmlSerializer, boolean exportTempl
xmlSerializer.endTag(null, GncXmlHelper.TAG_RECONCILED_STATE);
// value, in the transaction's currency
String trxType = cursor.getString(cursor.getColumnIndexOrThrow("split_type"));
- BigDecimal splitAmount = new BigDecimal(cursor.getString(cursor.getColumnIndexOrThrow("split_amount")));
+ int splitValueNum = cursor.getInt(cursor.getColumnIndexOrThrow("split_value_num"));
+ int splitValueDenom = cursor.getInt(cursor.getColumnIndexOrThrow("split_value_denom"));
+ BigDecimal splitAmount = Money.getBigDecimal(splitValueNum, splitValueDenom);
String strValue = "0/" + denomString;
if (!exportTemplates) { //when doing normal transaction export
- strValue = (trxType.equals("CREDIT") ? "-" : "") + GncXmlHelper.formatSplitAmount(splitAmount, trxCurrency);
+ strValue = (trxType.equals("CREDIT") ? "-" : "") + splitValueNum + "/" + splitValueDenom;
}
xmlSerializer.startTag(null, GncXmlHelper.TAG_SPLIT_VALUE);
xmlSerializer.text(strValue);
xmlSerializer.endTag(null, GncXmlHelper.TAG_SPLIT_VALUE);
// quantity, in the split account's currency
- // TODO: multi currency support.
+ String splitQuantityNum = cursor.getString(cursor.getColumnIndexOrThrow("split_quantity_num"));
+ String splitQuantityDenom = cursor.getString(cursor.getColumnIndexOrThrow("split_quantity_denom"));
+ if (!exportTemplates) {
+ strValue = (trxType.equals("CREDIT") ? "-" : "") + splitQuantityNum + "/" + splitQuantityDenom;
+ }
xmlSerializer.startTag(null, GncXmlHelper.TAG_SPLIT_QUANTITY);
xmlSerializer.text(strValue);
xmlSerializer.endTag(null, GncXmlHelper.TAG_SPLIT_QUANTITY);
@@ -615,6 +630,67 @@ private void exportCommodity(XmlSerializer xmlSerializer, List currenc
}
}
+ private void exportPrices(XmlSerializer xmlSerializer) throws IOException {
+ xmlSerializer.startTag(null, GncXmlHelper.TAG_PRICEDB);
+ xmlSerializer.attribute(null, GncXmlHelper.ATTR_KEY_VERSION, "1");
+ Cursor cursor = mPricesDbAdpater.fetchAllRecords();
+ try {
+ while(cursor.moveToNext()) {
+ xmlSerializer.startTag(null, GncXmlHelper.TAG_PRICE);
+ // GUID
+ xmlSerializer.startTag(null, GncXmlHelper.TAG_PRICE_ID);
+ xmlSerializer.attribute(null, GncXmlHelper.ATTR_KEY_TYPE, GncXmlHelper.ATTR_VALUE_GUID);
+ xmlSerializer.text(cursor.getString(cursor.getColumnIndexOrThrow(DatabaseSchema.CommonColumns.COLUMN_UID)));
+ xmlSerializer.endTag(null, GncXmlHelper.TAG_PRICE_ID);
+ // commodity
+ xmlSerializer.startTag(null, GncXmlHelper.TAG_PRICE_COMMODITY);
+ xmlSerializer.startTag(null, GncXmlHelper.TAG_COMMODITY_SPACE);
+ xmlSerializer.text("ISO4217");
+ xmlSerializer.endTag(null, GncXmlHelper.TAG_COMMODITY_SPACE);
+ xmlSerializer.startTag(null, GncXmlHelper.TAG_COMMODITY_ID);;
+ xmlSerializer.text(mCommoditiesDbAdapter.getCurrencyCode(cursor.getString(cursor.getColumnIndexOrThrow(DatabaseSchema.PriceEntry.COLUMN_COMMODITY_UID))));
+ xmlSerializer.endTag(null, GncXmlHelper.TAG_COMMODITY_ID);
+ xmlSerializer.endTag(null, GncXmlHelper.TAG_PRICE_COMMODITY);
+ // currency
+ xmlSerializer.startTag(null, GncXmlHelper.TAG_PRICE_CURRENCY);
+ xmlSerializer.startTag(null, GncXmlHelper.TAG_COMMODITY_SPACE);
+ xmlSerializer.text("ISO4217");
+ xmlSerializer.endTag(null, GncXmlHelper.TAG_COMMODITY_SPACE);
+ xmlSerializer.startTag(null, GncXmlHelper.TAG_COMMODITY_ID);;
+ xmlSerializer.text(mCommoditiesDbAdapter.getCurrencyCode(cursor.getString(cursor.getColumnIndexOrThrow(DatabaseSchema.PriceEntry.COLUMN_CURRENCY_UID))));
+ xmlSerializer.endTag(null, GncXmlHelper.TAG_COMMODITY_ID);
+ xmlSerializer.endTag(null, GncXmlHelper.TAG_PRICE_CURRENCY);
+ // time
+ String strDate = GncXmlHelper.formatDate(Timestamp.valueOf(cursor.getString(cursor.getColumnIndexOrThrow(DatabaseSchema.PriceEntry.COLUMN_DATE))).getTime());
+ xmlSerializer.startTag(null, GncXmlHelper.TAG_PRICE_TIME);
+ xmlSerializer.startTag(null, GncXmlHelper.TAG_TS_DATE);
+ xmlSerializer.text(strDate);
+ xmlSerializer.endTag(null, GncXmlHelper.TAG_TS_DATE);
+ xmlSerializer.endTag(null, GncXmlHelper.TAG_PRICE_TIME);
+ // source
+ xmlSerializer.startTag(null, GncXmlHelper.TAG_PRICE_SOURCE);
+ xmlSerializer.text(cursor.getString(cursor.getColumnIndexOrThrow(DatabaseSchema.PriceEntry.COLUMN_SOURCE)));
+ xmlSerializer.endTag(null, GncXmlHelper.TAG_PRICE_SOURCE);
+ // type, optional
+ String type = cursor.getString(cursor.getColumnIndexOrThrow(DatabaseSchema.PriceEntry.COLUMN_TYPE));
+ if (type != null && !type.equals("")) {
+ xmlSerializer.startTag(null, GncXmlHelper.TAG_PRICE_TYPE);
+ xmlSerializer.text(type);
+ xmlSerializer.endTag(null, GncXmlHelper.TAG_PRICE_TYPE);
+ }
+ // value
+ xmlSerializer.startTag(null, GncXmlHelper.TAG_PRICE_VALUE);
+ xmlSerializer.text(cursor.getLong(cursor.getColumnIndexOrThrow(DatabaseSchema.PriceEntry.COLUMN_VALUE_NUM))
+ + "/" + cursor.getLong(cursor.getColumnIndexOrThrow(DatabaseSchema.PriceEntry.COLUMN_VALUE_DENOM)));
+ xmlSerializer.endTag(null, GncXmlHelper.TAG_PRICE_VALUE);
+ xmlSerializer.endTag(null, GncXmlHelper.TAG_PRICE);
+ }
+ } finally {
+ cursor.close();
+ }
+ xmlSerializer.endTag(null, GncXmlHelper.TAG_PRICEDB);
+ }
+
@Override
public void generateExport(Writer writer) throws ExporterException{
try {
@@ -639,10 +715,10 @@ public void generateExport(Writer writer) throws ExporterException{
// book_id
xmlSerializer.startTag(null, GncXmlHelper.TAG_BOOK_ID);
xmlSerializer.attribute(null, GncXmlHelper.ATTR_KEY_TYPE, GncXmlHelper.ATTR_VALUE_GUID);
- xmlSerializer.text(UUID.randomUUID().toString().replaceAll("-", ""));
+ xmlSerializer.text(BaseModel.generateUID());
xmlSerializer.endTag(null, GncXmlHelper.TAG_BOOK_ID);
//commodity count
- List currencies = mAccountsDbAdapter.getCurrencies();
+ List currencies = mAccountsDbAdapter.getCurrenciesInUse();
for (int i = 0; i< currencies.size();i++) {
if (currencies.get(i).getCurrencyCode().equals("XXX")) {
currencies.remove(i);
@@ -660,12 +736,23 @@ public void generateExport(Writer writer) throws ExporterException{
//transaction count
xmlSerializer.startTag(null, GncXmlHelper.TAG_COUNT_DATA);
xmlSerializer.attribute(null, GncXmlHelper.ATTR_KEY_CD_TYPE, "transaction");
- xmlSerializer.text(mTransactionsDbAdapter.getTotalTransactionsCount() + "");
+ xmlSerializer.text(mTransactionsDbAdapter.getRecordsCount() + "");
xmlSerializer.endTag(null, GncXmlHelper.TAG_COUNT_DATA);
+ //price count
+ long priceCount = mPricesDbAdpater.getRecordsCount();
+ if (priceCount > 0) {
+ xmlSerializer.startTag(null, GncXmlHelper.TAG_COUNT_DATA);
+ xmlSerializer.attribute(null, GncXmlHelper.ATTR_KEY_CD_TYPE, "price");
+ xmlSerializer.text(priceCount + "");
+ xmlSerializer.endTag(null, GncXmlHelper.TAG_COUNT_DATA);
+ }
// export the commodities used in the DB
exportCommodity(xmlSerializer, currencies);
- // accounts. bulk import does not rely on account order
- // the cursor gather account in arbitrary order
+ // prices
+ if (priceCount > 0) {
+ exportPrices(xmlSerializer);
+ }
+ // accounts.
exportAccounts(xmlSerializer);
// transactions.
exportTransactions(xmlSerializer, false);
diff --git a/app/src/main/java/org/gnucash/android/export/xml/GncXmlHelper.java b/app/src/main/java/org/gnucash/android/export/xml/GncXmlHelper.java
index 53f479e3d..92de9512e 100644
--- a/app/src/main/java/org/gnucash/android/export/xml/GncXmlHelper.java
+++ b/app/src/main/java/org/gnucash/android/export/xml/GncXmlHelper.java
@@ -72,7 +72,7 @@ public abstract class GncXmlHelper {
public static final String TAG_SLOT_VALUE = "slot:value";
public static final String TAG_ACT_SLOTS = "act:slots";
public static final String TAG_SLOT = "slot";
- public static final String TAG_ACCT_DESCRIPTION = "act:description"; //TODO: Use this when we add descriptions to the database
+ public static final String TAG_ACCT_DESCRIPTION = "act:description";
public static final String TAG_TRANSACTION = "gnc:transaction";
public static final String TAG_TRX_ID = "trn:id";
@@ -94,6 +94,16 @@ public abstract class GncXmlHelper {
public static final String TAG_SPLIT_QUANTITY = "split:quantity";
public static final String TAG_SPLIT_SLOTS = "split:slots";
+ public static final String TAG_PRICEDB = "gnc:pricedb";
+ public static final String TAG_PRICE = "price";
+ public static final String TAG_PRICE_ID = "price:id";
+ public static final String TAG_PRICE_COMMODITY = "price:commodity";
+ public static final String TAG_PRICE_CURRENCY = "price:currency";
+ public static final String TAG_PRICE_TIME = "price:time";
+ public static final String TAG_PRICE_SOURCE = "price:source";
+ public static final String TAG_PRICE_TYPE = "price:type";
+ public static final String TAG_PRICE_VALUE = "price:value";
+
@Deprecated
public static final String TAG_RECURRENCE_PERIOD = "trn:recurrence_period";
@@ -161,7 +171,7 @@ public static long parseDate(String dateString) throws ParseException {
/**
* Parses amount strings from GnuCash XML into {@link java.math.BigDecimal}s.
- * The amounts are formatted as 12345/4100
+ * The amounts are formatted as 12345/100
* @param amountString String containing the amount
* @return BigDecimal with numerical value
* @throws ParseException if the amount could not be parsed
@@ -174,7 +184,9 @@ public static BigDecimal parseSplitAmount(String amountString) throws ParseExcep
}
int scale = amountString.length() - pos - 2; //do this before, because we could modify the string
- String numerator = TransactionFormFragment.stripCurrencyFormatting(amountString.substring(0, pos));
+ //String numerator = TransactionFormFragment.stripCurrencyFormatting(amountString.substring(0, pos));
+ String numerator = amountString.substring(0,pos);
+ numerator = TransactionFormFragment.stripCurrencyFormatting(numerator);
BigInteger numeratorInt = new BigInteger(numerator);
return new BigDecimal(numeratorInt, scale);
}
@@ -201,7 +213,6 @@ public static String formatSplitAmount(BigDecimal amount, Currency trxCurrency){
* So we will use the device locale here and hope that the user has the same locale on the desktop GnuCash
* @param amount Amount to be formatted
* @return String representation of amount
- * @see #parseTemplateSplitAmount(String)
*/
public static String formatTemplateSplitAmount(BigDecimal amount){
//TODO: If we ever implement an application-specific locale setting, use it here as well
diff --git a/app/src/main/java/org/gnucash/android/importer/CommoditiesXmlHandler.java b/app/src/main/java/org/gnucash/android/importer/CommoditiesXmlHandler.java
new file mode 100644
index 000000000..6b4ce3a1c
--- /dev/null
+++ b/app/src/main/java/org/gnucash/android/importer/CommoditiesXmlHandler.java
@@ -0,0 +1,71 @@
+package org.gnucash.android.importer;
+
+import android.database.sqlite.SQLiteDatabase;
+
+import org.gnucash.android.app.GnuCashApplication;
+import org.gnucash.android.db.CommoditiesDbAdapter;
+import org.gnucash.android.model.Commodity;
+import org.xml.sax.Attributes;
+import org.xml.sax.SAXException;
+import org.xml.sax.helpers.DefaultHandler;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * XML stream handler for parsing currencies to add to the database
+ */
+public class CommoditiesXmlHandler extends DefaultHandler {
+
+ public static final String TAG_CURRENCY = "currency";
+ public static final String ATTR_ISO_CODE = "isocode";
+ public static final String ATTR_FULL_NAME = "fullname";
+ public static final String ATTR_NAMESPACE = "namespace";
+ public static final String ATTR_EXCHANGE_CODE = "exchange-code";
+ public static final String ATTR_SMALLEST_FRACTION = "smallest-fraction";
+ public static final String ATTR_LOCAL_SYMBOL = "local-symbol";
+ /**
+ * List of commodities parsed from the XML file.
+ * They will be all added to db at once at the end of the document
+ */
+ private List mCommodities;
+
+ private CommoditiesDbAdapter mCommoditiesDbAdapter;
+
+ public CommoditiesXmlHandler(SQLiteDatabase db){
+ if (db == null){
+ mCommoditiesDbAdapter = GnuCashApplication.getCommoditiesDbAdapter();
+ } else {
+ mCommoditiesDbAdapter = new CommoditiesDbAdapter(db);
+ }
+ mCommodities = new ArrayList<>();
+ }
+
+ @Override
+ public void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException {
+ if (qName.equals(TAG_CURRENCY)) {
+ String isoCode = attributes.getValue(ATTR_ISO_CODE);
+ String fullname = attributes.getValue(ATTR_FULL_NAME);
+ String namespace = attributes.getValue(ATTR_NAMESPACE);
+ String cusip = attributes.getValue(ATTR_EXCHANGE_CODE);
+ //TODO: investigate how up-to-date the currency XML list is and use of parts-per-unit vs smallest-fraction.
+ //some currencies like XAF have smallest fraction 100, but parts-per-unit of 1.
+ // However java.util.Currency agrees only with the parts-per-unit although we use smallest-fraction in the app
+ // This could lead to inconsistencies over time
+ String smallestFraction = attributes.getValue(ATTR_SMALLEST_FRACTION);
+ String localSymbol = attributes.getValue(ATTR_LOCAL_SYMBOL);
+
+ Commodity commodity = new Commodity(fullname, isoCode, Integer.parseInt(smallestFraction));
+ commodity.setNamespace(Commodity.Namespace.valueOf(namespace));
+ commodity.setCusip(cusip);
+ commodity.setLocalSymbol(localSymbol);
+
+ mCommodities.add(commodity);
+ }
+ }
+
+ @Override
+ public void endDocument() throws SAXException {
+ mCommoditiesDbAdapter.bulkAddRecords(mCommodities);
+ }
+}
diff --git a/app/src/main/java/org/gnucash/android/importer/GncXmlHandler.java b/app/src/main/java/org/gnucash/android/importer/GncXmlHandler.java
index 75b5dd9ac..fec03cfa7 100644
--- a/app/src/main/java/org/gnucash/android/importer/GncXmlHandler.java
+++ b/app/src/main/java/org/gnucash/android/importer/GncXmlHandler.java
@@ -24,14 +24,18 @@
import com.crashlytics.android.Crashlytics;
import org.gnucash.android.db.AccountsDbAdapter;
+import org.gnucash.android.db.CommoditiesDbAdapter;
+import org.gnucash.android.db.PricesDbAdapter;
import org.gnucash.android.db.ScheduledActionDbAdapter;
import org.gnucash.android.db.SplitsDbAdapter;
import org.gnucash.android.db.TransactionsDbAdapter;
import org.gnucash.android.export.xml.GncXmlHelper;
import org.gnucash.android.model.Account;
import org.gnucash.android.model.AccountType;
+import org.gnucash.android.model.BaseModel;
import org.gnucash.android.model.Money;
import org.gnucash.android.model.PeriodType;
+import org.gnucash.android.model.Price;
import org.gnucash.android.model.ScheduledAction;
import org.gnucash.android.model.Split;
import org.gnucash.android.model.Transaction;
@@ -132,10 +136,25 @@ public class GncXmlHandler extends DefaultHandler {
Split mSplit;
/**
- * (Absolute) quantity of the split
+ * (Absolute) quantity of the split, which uses split account currency
*/
BigDecimal mQuantity;
+ /**
+ * (Absolute) value of the split, which uses transaction currency
+ */
+ BigDecimal mValue;
+
+ /**
+ * price table entry
+ */
+ Price mPrice;
+
+ boolean mPriceCommodity;
+ boolean mPriceCurrency;
+
+ List mPriceList;
+
/**
* Whether the quantity is negative
*/
@@ -208,6 +227,10 @@ public class GncXmlHandler extends DefaultHandler {
private ScheduledActionDbAdapter mScheduledActionsDbAdapter;
+ private CommoditiesDbAdapter mCommoditiesDbAdapter;
+
+ private PricesDbAdapter mPricesDbAdapter;
+
/**
* Creates a handler for handling XML stream events when parsing the XML backup file
*/
@@ -229,10 +252,14 @@ private void init(@Nullable SQLiteDatabase db) {
mAccountsDbAdapter = AccountsDbAdapter.getInstance();
mTransactionsDbAdapter = TransactionsDbAdapter.getInstance();
mScheduledActionsDbAdapter = ScheduledActionDbAdapter.getInstance();
+ mCommoditiesDbAdapter = CommoditiesDbAdapter.getInstance();
+ mPricesDbAdapter = PricesDbAdapter.getInstance();
} else {
mTransactionsDbAdapter = new TransactionsDbAdapter(db, new SplitsDbAdapter(db));
mAccountsDbAdapter = new AccountsDbAdapter(db, mTransactionsDbAdapter);
mScheduledActionsDbAdapter = new ScheduledActionDbAdapter(db);
+ mCommoditiesDbAdapter = new CommoditiesDbAdapter(db);
+ mPricesDbAdapter = new PricesDbAdapter(db);
}
mContent = new StringBuilder();
@@ -247,6 +274,8 @@ private void init(@Nullable SQLiteDatabase db) {
mTemplateAccountToTransactionMap = new HashMap<>();
mAutoBalanceSplits = new ArrayList<>();
+
+ mPriceList = new ArrayList<>();
}
@Override
@@ -263,7 +292,7 @@ public void startElement(String uri, String localName,
mISO4217Currency = false;
break;
case GncXmlHelper.TAG_TRN_SPLIT:
- mSplit = new Split(Money.getZeroInstance(),"");
+ mSplit = new Split(Money.getZeroInstance(), "");
break;
case GncXmlHelper.TAG_DATE_POSTED:
mIsDatePosted = true;
@@ -290,6 +319,19 @@ public void startElement(String uri, String localName,
case GncXmlHelper.TAG_RX_START:
mIsRecurrenceStart = true;
break;
+ case GncXmlHelper.TAG_PRICE:
+ mPrice = new Price();
+ break;
+ case GncXmlHelper.TAG_PRICE_CURRENCY:
+ mPriceCurrency = true;
+ mPriceCommodity = false;
+ mISO4217Currency = false;
+ break;
+ case GncXmlHelper.TAG_PRICE_COMMODITY:
+ mPriceCurrency = false;
+ mPriceCommodity = true;
+ mISO4217Currency = false;
+ break;
}
}
@@ -322,6 +364,9 @@ public void endElement(String uri, String localName, String qualifiedName) throw
case GncXmlHelper.TAG_COMMODITY_SPACE:
if (characterString.equals("ISO4217")) {
mISO4217Currency = true;
+ } else {
+ // price of non-ISO4217 commodities cannot be handled
+ mPrice = null;
}
break;
case GncXmlHelper.TAG_COMMODITY_ID:
@@ -332,6 +377,19 @@ public void endElement(String uri, String localName, String qualifiedName) throw
if (mTransaction != null) {
mTransaction.setCurrencyCode(currencyCode);
}
+ if (mPrice != null) {
+ if (mPriceCommodity) {
+ mPrice.setCommodityUID(mCommoditiesDbAdapter.getCommodityUID(currencyCode));
+ mPriceCommodity = false;
+ }
+ if (mPriceCurrency) {
+ mPrice.setCurrencyUID(mCommoditiesDbAdapter.getCommodityUID(currencyCode));
+ mPriceCurrency = false;
+ }
+ }
+ break;
+ case GncXmlHelper.TAG_ACCT_DESCRIPTION:
+ mAccount.setDescription(characterString);
break;
case GncXmlHelper.TAG_PARENT_UID:
mAccount.setParentUID(characterString);
@@ -387,11 +445,11 @@ public void endElement(String uri, String localName, String qualifiedName) throw
break;
case GncXmlHelper.TAG_SLOT_VALUE:
if (mInPlaceHolderSlot) {
- Log.v(LOG_TAG, "Setting account placeholder flag");
+ //Log.v(LOG_TAG, "Setting account placeholder flag");
mAccount.setPlaceHolderFlag(Boolean.parseBoolean(characterString));
mInPlaceHolderSlot = false;
} else if (mInColorSlot) {
- Log.d(LOG_TAG, "Parsing color code: " + characterString);
+ //Log.d(LOG_TAG, "Parsing color code: " + characterString);
String color = characterString.trim();
//Gnucash exports the account color in format #rrrgggbbb, but we need only #rrggbb.
//so we trim the last digit in each block, doesn't affect the color much
@@ -452,6 +510,9 @@ public void endElement(String uri, String localName, String qualifiedName) throw
mTransaction.setCreatedTimestamp(timestamp);
mIsDateEntered = false;
}
+ if (mPrice != null) {
+ mPrice.setDate(new Timestamp(GncXmlHelper.parseDate(characterString)));
+ }
} catch (ParseException e) {
Crashlytics.logException(e);
String message = "Unable to parse transaction time - " + characterString;
@@ -470,9 +531,10 @@ public void endElement(String uri, String localName, String qualifiedName) throw
case GncXmlHelper.TAG_SPLIT_MEMO:
mSplit.setMemo(characterString);
break;
- case GncXmlHelper.TAG_SPLIT_QUANTITY:
- // delay the assignment of currency when the split account is seen
+ case GncXmlHelper.TAG_SPLIT_VALUE:
try {
+ // The value and quantity can have different sign for custom currency(stock).
+ // Use the sign of value for split, as it would not be custom currency
String q = characterString;
if (q.charAt(0) == '-') {
mNegativeQuantity = true;
@@ -480,7 +542,18 @@ public void endElement(String uri, String localName, String qualifiedName) throw
} else {
mNegativeQuantity = false;
}
- mQuantity = GncXmlHelper.parseSplitAmount(q);
+ mValue = GncXmlHelper.parseSplitAmount(characterString).abs(); // use sign from quantity
+ } catch (ParseException e) {
+ String msg = "Error parsing split quantity - " + characterString;
+ Crashlytics.log(msg);
+ Crashlytics.logException(e);
+ throw new SAXException(msg, e);
+ }
+ break;
+ case GncXmlHelper.TAG_SPLIT_QUANTITY:
+ // delay the assignment of currency when the split account is seen
+ try {
+ mQuantity = GncXmlHelper.parseSplitAmount(characterString).abs();
} catch (ParseException e) {
String msg = "Error parsing split quantity - " + characterString;
Crashlytics.log(msg);
@@ -490,11 +563,12 @@ public void endElement(String uri, String localName, String qualifiedName) throw
break;
case GncXmlHelper.TAG_SPLIT_ACCOUNT:
if (!mInTemplates) {
- //the split amount uses the account currency
- Money amount = new Money(mQuantity, getCurrencyForAccount(characterString));
//this is intentional: GnuCash XML formats split amounts, credits are negative, debits are positive.
mSplit.setType(mNegativeQuantity ? TransactionType.CREDIT : TransactionType.DEBIT);
- mSplit.setAmount(amount);
+ //the split amount uses the account currency
+ mSplit.setQuantity(new Money(mQuantity, getCurrencyForAccount(characterString)));
+ //the split value uses the transaction currency
+ mSplit.setValue(new Money(mValue, mTransaction.getCurrency()));
mSplit.setAccountUID(characterString);
} else {
if (!mIgnoreTemplateTransaction)
@@ -598,7 +672,7 @@ public void endElement(String uri, String localName, String qualifiedName) throw
if (mScheduledAction.getActionType() == ScheduledAction.ActionType.TRANSACTION) {
mScheduledAction.setActionUID(mTemplateAccountToTransactionMap.get(characterString));
} else {
- mScheduledAction.setActionUID(UUID.randomUUID().toString().replaceAll("-",""));
+ mScheduledAction.setActionUID(BaseModel.generateUID());
}
break;
case GncXmlHelper.TAG_SCHEDULED_ACTION:
@@ -610,6 +684,42 @@ public void endElement(String uri, String localName, String qualifiedName) throw
mRecurrenceMultiplier = 1; //reset it, even though it will be parsed from XML each time
mIgnoreScheduledAction = false;
break;
+ // price table
+ case GncXmlHelper.TAG_PRICE_ID:
+ mPrice.setUID(characterString);
+ break;
+ case GncXmlHelper.TAG_PRICE_SOURCE:
+ if (mPrice != null) {
+ mPrice.setSource(characterString);
+ }
+ break;
+ case GncXmlHelper.TAG_PRICE_VALUE:
+ if (mPrice != null) {
+ String[] parts = characterString.split("/");
+ if (parts.length != 2) {
+ String message = "Illegal price - " + characterString;
+ Log.e(LOG_TAG, message);
+ Crashlytics.log(message);
+ throw new SAXException(message);
+ } else {
+ mPrice.setValueNum(Long.valueOf(parts[0]));
+ mPrice.setValueDenom(Long.valueOf(parts[1]));
+ Log.d(getClass().getName(), "price " + characterString +
+ " .. " + mPrice.getValueNum() + "/" + mPrice.getValueDenom());
+ }
+ }
+ break;
+ case GncXmlHelper.TAG_PRICE_TYPE:
+ if (mPrice != null) {
+ mPrice.setType(characterString);
+ }
+ break;
+ case GncXmlHelper.TAG_PRICE:
+ if (mPrice != null) {
+ mPriceList.add(mPrice);
+ mPrice = null;
+ }
+ break;
}
//reset the accumulated characters
@@ -707,24 +817,29 @@ public void endDocument() throws SAXException {
}
long startTime = System.nanoTime();
mAccountsDbAdapter.beginTransaction();
+ Log.d(getClass().getSimpleName(), "bulk insert starts");
try {
+ Log.d(getClass().getSimpleName(), "before clean up db");
mAccountsDbAdapter.deleteAllRecords();
-
- long nAccounts = mAccountsDbAdapter.bulkAddAccounts(mAccountList);
+ Log.d(getClass().getSimpleName(), String.format("deb clean up done %d ns", System.nanoTime()-startTime));
+ long nAccounts = mAccountsDbAdapter.bulkAddRecords(mAccountList);
Log.d("Handler:", String.format("%d accounts inserted", nAccounts));
//We need to add scheduled actions first because there is a foreign key constraint on transactions
//which are generated from scheduled actions (we do auto-create some transactions during import)
- int nSchedActions = mScheduledActionsDbAdapter.bulkAddScheduledActions(mScheduledActionsList);
+ long nSchedActions = mScheduledActionsDbAdapter.bulkAddRecords(mScheduledActionsList);
Log.d("Handler:", String.format("%d scheduled actions inserted", nSchedActions));
- long nTempTransactions = mTransactionsDbAdapter.bulkAddTransactions(mTemplateTransactions);
+ long nTempTransactions = mTransactionsDbAdapter.bulkAddRecords(mTemplateTransactions);
Log.d("Handler:", String.format("%d template transactions inserted", nTempTransactions));
- long nTransactions = mTransactionsDbAdapter.bulkAddTransactions(mTransactionList);
+ long nTransactions = mTransactionsDbAdapter.bulkAddRecords(mTransactionList);
Log.d("Handler:", String.format("%d transactions inserted", nTransactions));
+ long nPrices = mPricesDbAdapter.bulkAddRecords(mPriceList);
+ Log.d(getClass().getSimpleName(), String.format("%d prices inserted", nPrices));
+
long endTime = System.nanoTime();
- Log.d("Handler:", String.format(" bulk insert time: %d", endTime - startTime));
+ Log.d(getClass().getSimpleName(), String.format("bulk insert time: %d", endTime - startTime));
mAccountsDbAdapter.setTransactionSuccessful();
} finally {
@@ -756,7 +871,7 @@ private void handleEndOfTemplateNumericSlot(String characterString, TransactionT
try {
BigDecimal amountBigD = GncXmlHelper.parseSplitAmount(characterString);
Money amount = new Money(amountBigD, getCurrencyForAccount(mSplit.getAccountUID()));
- mSplit.setAmount(amount.absolute());
+ mSplit.setValue(amount.absolute());
mSplit.setType(splitType);
mIgnoreTemplateTransaction = false; //we have successfully parsed an amount
} catch (NumberFormatException | ParseException e) {
diff --git a/app/src/main/java/org/gnucash/android/importer/GncXmlImporter.java b/app/src/main/java/org/gnucash/android/importer/GncXmlImporter.java
index 0ebefd85b..43ea7c614 100644
--- a/app/src/main/java/org/gnucash/android/importer/GncXmlImporter.java
+++ b/app/src/main/java/org/gnucash/android/importer/GncXmlImporter.java
@@ -17,8 +17,11 @@
package org.gnucash.android.importer;
import android.database.sqlite.SQLiteDatabase;
+import android.preference.PreferenceManager;
import android.util.Log;
+import org.gnucash.android.app.GnuCashApplication;
+import org.gnucash.android.export.Exporter;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;
import org.xml.sax.XMLReader;
@@ -27,6 +30,7 @@
import java.io.IOException;
import java.io.InputStream;
import java.io.PushbackInputStream;
+import java.sql.Timestamp;
import java.util.zip.GZIPInputStream;
import javax.xml.parsers.ParserConfigurationException;
@@ -82,12 +86,16 @@ public static void parse(InputStream gncXmlInputStream) throws ParserConfigurati
bos = new BufferedInputStream(pb);
//TODO: Set an error handler which can log errors
-
+ Log.d(GncXmlImporter.class.getSimpleName(), "Start import");
GncXmlHandler handler = new GncXmlHandler();
xr.setContentHandler(handler);
long startTime = System.nanoTime();
xr.parse(new InputSource(bos));
long endTime = System.nanoTime();
- Log.d("Import", String.format("%d ns spent on importing the file", endTime-startTime));
+
+ String timeStamp = new Timestamp(System.currentTimeMillis()).toString();
+ PreferenceManager.getDefaultSharedPreferences(GnuCashApplication.getAppContext()).edit().putString(Exporter.PREF_LAST_EXPORT_TIME, timeStamp).apply();
+
+ Log.d(GncXmlImporter.class.getSimpleName(), String.format("%d ns spent on importing the file", endTime-startTime));
}
}
diff --git a/app/src/main/java/org/gnucash/android/importer/ImportAsyncTask.java b/app/src/main/java/org/gnucash/android/importer/ImportAsyncTask.java
index 55c6f138b..a0232003a 100644
--- a/app/src/main/java/org/gnucash/android/importer/ImportAsyncTask.java
+++ b/app/src/main/java/org/gnucash/android/importer/ImportAsyncTask.java
@@ -73,6 +73,7 @@ protected Boolean doInBackground(InputStream... inputStreams) {
} catch (Exception exception){
Log.e(ImportAsyncTask.class.getName(), "" + exception.getMessage());
Crashlytics.logException(exception);
+ exception.printStackTrace();
final String err_msg = exception.getLocalizedMessage();
context.runOnUiThread(new Runnable() {
diff --git a/app/src/main/java/org/gnucash/android/model/Account.java b/app/src/main/java/org/gnucash/android/model/Account.java
index 3da83bf07..42ca52790 100644
--- a/app/src/main/java/org/gnucash/android/model/Account.java
+++ b/app/src/main/java/org/gnucash/android/model/Account.java
@@ -17,11 +17,16 @@
package org.gnucash.android.model;
+import android.preference.PreferenceManager;
+
import org.gnucash.android.BuildConfig;
+import org.gnucash.android.app.GnuCashApplication;
+import org.gnucash.android.export.Exporter;
import org.gnucash.android.export.ofx.OfxHelper;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
+import java.sql.Timestamp;
import java.util.ArrayList;
import java.util.Currency;
import java.util.List;
@@ -80,11 +85,18 @@ public enum OfxAccountType {CHECKING, SAVINGS, MONEYMRKT, CREDITLINE }
*/
private String mFullName;
+ /**
+ * Account description
+ */
+ private String mDescription;
+
/**
* Currency used by transactions in this account
*/
private Currency mCurrency;
-
+
+ private String mCommodityUID;
+
/**
* Type of account
* Defaults to {@link AccountType#CASH}
@@ -94,7 +106,7 @@ public enum OfxAccountType {CHECKING, SAVINGS, MONEYMRKT, CREDITLINE }
/**
* List of transactions in this account
*/
- private List mTransactionsList = new ArrayList();
+ private List mTransactionsList = new ArrayList<>();
/**
* Account UID of the parent account. Can be null
@@ -194,6 +206,22 @@ public void setFullName(String fullName) {
this.mFullName = fullName;
}
+ /**
+ * Returns the account mDescription
+ * @return String with mDescription
+ */
+ public String getDescription() {
+ return mDescription;
+ }
+
+ /**
+ * Sets the account mDescription
+ * @param description String mDescription
+ */
+ public void setDescription(String description) {
+ this.mDescription = description;
+ }
+
/**
* Get the type of account
* @return {@link AccountType} type of account
@@ -308,14 +336,30 @@ public Currency getCurrency() {
/**
* Sets the currency to be used by this account
- * @param mCurrency the mCurrency to set
+ * @param currency the mCurrency to set
*/
- public void setCurrency(Currency mCurrency) {
- this.mCurrency = mCurrency;
+ public void setCurrency(Currency currency) {
+ this.mCurrency = currency;
//TODO: Maybe at some time t, this method should convert all
//transaction values to the corresponding value in the new currency
}
+ /**
+ * Returns the commodity GUID for this account
+ * @return String GUID of commodity
+ */
+ public String getCommodityUID() {
+ return mCommodityUID;
+ }
+
+ /**
+ * Sets the commodity GUID for this account
+ * @param commodityUID String commodity GUID
+ */
+ public void setCommodityUID(String commodityUID) {
+ this.mCommodityUID = commodityUID;
+ }
+
/**
* Sets the Unique Account Identifier of the parent account
* @param parentUID String Unique ID of parent account
@@ -480,9 +524,10 @@ public void toOfx(Document doc, Element parent, boolean exportAllTransactions){
Element bankTransactionsList = doc.createElement(OfxHelper.TAG_BANK_TRANSACTION_LIST);
bankTransactionsList.appendChild(dtstart);
bankTransactionsList.appendChild(dtend);
-
+
+ Timestamp lastExportedTimestamp = Timestamp.valueOf(PreferenceManager.getDefaultSharedPreferences(GnuCashApplication.getAppContext()).getString(Exporter.PREF_LAST_EXPORT_TIME, Exporter.TIMESTAMP_ZERO));
for (Transaction transaction : mTransactionsList) {
- if (!exportAllTransactions && transaction.isExported())
+ if (!exportAllTransactions && /*transaction.isExported()*/ transaction.getModifiedTimestamp().before(lastExportedTimestamp))
continue;
bankTransactionsList.appendChild(transaction.toOFX(doc, getUID()));
}
diff --git a/app/src/main/java/org/gnucash/android/model/BaseModel.java b/app/src/main/java/org/gnucash/android/model/BaseModel.java
index eff68b1fe..eefde82a5 100644
--- a/app/src/main/java/org/gnucash/android/model/BaseModel.java
+++ b/app/src/main/java/org/gnucash/android/model/BaseModel.java
@@ -45,12 +45,11 @@ public BaseModel(){
}
/**
- * Method for generating the Global Unique ID for the object and sets the internal variable which can be retrieved with {@link #getUID()}.
- * Subclasses can override this method to provide a different implementation
+ * Method for generating the Global Unique ID for the model object
* @return Random GUID for the model object
*/
- protected String generateUID(){
- return mUID = UUID.randomUUID().toString().replaceAll("-", "");
+ public static String generateUID(){
+ return UUID.randomUUID().toString().replaceAll("-", "");
}
/**
@@ -74,7 +73,7 @@ public void setUID(String uid) {
this.mUID = uid;
}
- /**
+ /**8
* Returns the timestamp when this model entry was created in the database
* @return Timestamp of creation of model
*/
diff --git a/app/src/main/java/org/gnucash/android/model/Commodity.java b/app/src/main/java/org/gnucash/android/model/Commodity.java
new file mode 100644
index 000000000..169e2d15b
--- /dev/null
+++ b/app/src/main/java/org/gnucash/android/model/Commodity.java
@@ -0,0 +1,106 @@
+package org.gnucash.android.model;
+
+/**
+ * Commodities are the currencies used in the application.
+ * At the moment only ISO4217 currencies are supported
+ */
+public class Commodity extends BaseModel {
+ public enum Namespace { ISO4217 } //Namespace for commodities
+
+ private Namespace mNamespace = Namespace.ISO4217;
+
+ /**
+ * This is the currency code for ISO4217 currencies
+ */
+ private String mMnemonic;
+ private String mFullname;
+ private String mCusip;
+ private String mLocalSymbol = "";
+ private int mFraction;
+ private int mQuoteFlag;
+
+ /**
+ * Create a new commodity
+ * @param fullname Official full name of the currency
+ * @param mnemonic Official abbreviated designation for the currency
+ * @param fraction Number of sub-units that the basic commodity can be divided into
+ */
+ public Commodity(String fullname, String mnemonic, int fraction){
+ this.mFullname = fullname;
+ this.mMnemonic = mnemonic;
+ this.mFraction = fraction;
+ }
+
+ public Namespace getNamespace() {
+ return mNamespace;
+ }
+
+ public void setNamespace(Namespace namespace) {
+ this.mNamespace = namespace;
+ }
+
+ /**
+ * Returns the mnemonic, or currency code for ISO4217 currencies
+ * @return Mnemonic of the commodity
+ */
+ public String getMnemonic() {
+ return mMnemonic;
+ }
+
+ public void setMnemonic(String mMnemonic) {
+ this.mMnemonic = mMnemonic;
+ }
+
+ public String getFullname() {
+ return mFullname;
+ }
+
+ public void setFullname(String mFullname) {
+ this.mFullname = mFullname;
+ }
+
+ public String getCusip() {
+ return mCusip;
+ }
+
+ public void setCusip(String mCusip) {
+ this.mCusip = mCusip;
+ }
+
+ public String getLocalSymbol() {
+ return mLocalSymbol;
+ }
+
+ /**
+ * Returns the symbol for this commodity.
+ * Normally this would be the local symbol, but in it's absence, the mnemonic (currency code)
+ * is returned.
+ * @return
+ */
+ public String getSymbol(){
+ if (mLocalSymbol == null || mLocalSymbol.isEmpty()){
+ return mMnemonic;
+ }
+ return mLocalSymbol;
+ }
+
+ public void setLocalSymbol(String localSymbol) {
+ this.mLocalSymbol = localSymbol;
+ }
+
+ public int getFraction() {
+ return mFraction;
+ }
+
+ public void setFraction(int fraction) {
+ this.mFraction = fraction;
+ }
+
+ public int getQuoteFlag() {
+ return mQuoteFlag;
+ }
+
+ public void setQuoteFlag(int quoteFlag) {
+ this.mQuoteFlag = quoteFlag;
+ }
+}
diff --git a/app/src/main/java/org/gnucash/android/model/Money.java b/app/src/main/java/org/gnucash/android/model/Money.java
index 7013dd309..20024b1c3 100644
--- a/app/src/main/java/org/gnucash/android/model/Money.java
+++ b/app/src/main/java/org/gnucash/android/model/Money.java
@@ -18,14 +18,16 @@
import android.support.annotation.NonNull;
+import android.util.Log;
import com.crashlytics.android.Crashlytics;
import org.gnucash.android.app.GnuCashApplication;
import java.math.BigDecimal;
-import java.math.MathContext;
+import java.math.BigInteger;
import java.math.RoundingMode;
+import java.security.InvalidParameterException;
import java.text.DecimalFormat;
import java.text.DecimalFormatSymbols;
import java.text.NumberFormat;
@@ -74,12 +76,6 @@ public final class Money implements Comparable{
* Defaults to {@link #DEFAULT_ROUNDING_MODE}
*/
protected RoundingMode ROUNDING_MODE = DEFAULT_ROUNDING_MODE;
-
- /**
- * Number of decimal places to limit fractions to in arithmetic operations
- * Defaults to {@link #DEFAULT_DECIMAL_PLACES}
- */
- protected int DECIMAL_PLACES = DEFAULT_DECIMAL_PLACES;
/**
* Default currency code (according ISO 4217)
@@ -113,7 +109,23 @@ public static Money getZeroInstance(){
public Money() {
init();
}
-
+
+ public static BigDecimal getBigDecimal(long numerator, long denominator) {
+ int scale;
+ if (numerator == 0 && denominator == 0) {
+ denominator = 1;
+ }
+ switch ((int)denominator) {
+ case 1: scale = 0; break;
+ case 10: scale = 1; break;
+ case 100: scale = 2; break;
+ case 1000: scale = 3; break;
+ default:
+ throw new InvalidParameterException("invalid denominator " + denominator);
+ }
+ return new BigDecimal(BigInteger.valueOf(numerator), scale);
+ }
+
/**
* Overloaded constructor
* @param amount {@link BigDecimal} value of the money instance
@@ -121,7 +133,7 @@ public Money() {
*/
public Money(BigDecimal amount, Currency currency){
this.mAmount = amount;
- this.mCurrency = currency;
+ setCurrency(currency);
}
/**
@@ -131,24 +143,22 @@ public Money(BigDecimal amount, Currency currency){
* @param currencyCode Currency code as specified by ISO 4217
*/
public Money(String amount, String currencyCode){
- setAmount(amount);
setCurrency(Currency.getInstance(currencyCode));
+ setAmount(amount);
}
-
+
/**
- * Overloaded constructor
- * Accepts context
options for rounding mode during operations on this money object
- * @param amount {@link BigDecimal} value of the money instance
- * @param currency {@link Currency} associated with the amount
- * @param context {@link MathContext} specifying rounding mode during operations
+ * Constructs a new money amount given the numerator and denominator of the amount.
+ * The rounding mode used for the division is {@link BigDecimal#ROUND_HALF_EVEN}
+ * @param numerator Numerator as integer
+ * @param denominator Denominator as integer
+ * @param currencyCode 3-character currency code string
*/
- public Money(BigDecimal amount, Currency currency, MathContext context){
- setAmount(amount);
- setCurrency(currency);
- ROUNDING_MODE = context.getRoundingMode();
- DECIMAL_PLACES = context.getPrecision();
+ public Money(long numerator, long denominator, String currencyCode){
+ mAmount = getBigDecimal(numerator, denominator);
+ setCurrency(Currency.getInstance(currencyCode));
}
-
+
/**
* Overloaded constructor.
* Initializes the currency to that specified by {@link Money#DEFAULT_CURRENCY_CODE}
@@ -165,8 +175,8 @@ public Money(String amount){
* @param money Money instance to be cloned
*/
public Money(Money money){
- setAmount(money.asBigDecimal());
- setCurrency(money.getCurrency());
+ setCurrency(money.getCurrency());
+ setAmount(money.asBigDecimal());
}
/**
@@ -213,10 +223,62 @@ public Money withCurrency(Currency currency){
* @param currency {@link Currency} to assign to the Money object
*/
private void setCurrency(Currency currency) {
- //TODO: Consider doing a conversion of the value as well in the future
this.mCurrency = currency;
}
+ /**
+ * Returns the GnuCash format numerator for this amount.
+ * Example: Given an amount 32.50$, the numerator will be 3250
+ * @return GnuCash numerator for this amount
+ */
+ public long getNumerator() {
+ try {
+ return mAmount.scaleByPowerOfTen(getScale()).longValueExact();
+ } catch (ArithmeticException e) {
+ Log.e(getClass().getName(), "Currency " + mCurrency.getCurrencyCode() +
+ " with scale " + getScale() +
+ " has amount " + mAmount.toString());
+ throw e;
+ }
+ }
+
+ /**
+ * Returns the GnuCash amount format denominator for this amount
+ * The denominator is 10 raised to the power of number of fractional digits in the currency
+ * @return GnuCash format denominator
+ */
+ public long getDenominator() {
+ switch (getScale()) {
+ case 0:
+ return 1;
+ case 1:
+ return 10;
+ case 2:
+ return 100;
+ case 3:
+ return 1000;
+ case 4:
+ return 10000;
+ }
+ throw new RuntimeException("Unsupported number of fraction digits " + getScale());
+ }
+
+ /**
+ * Returns the scale (precision) used for the decimal places of this amount.
+ * The scale used depends on the currency
+ * @return Scale of amount as integer
+ */
+ private int getScale() {
+ int scale = mCurrency.getDefaultFractionDigits();
+ if (scale < 0) {
+ scale = mAmount.scale();
+ }
+ if (scale < 0) {
+ scale = 0;
+ }
+ return scale;
+ }
+
/**
* Returns the amount represented by this Money object
* @return {@link BigDecimal} valure of amount in object
@@ -232,7 +294,17 @@ public BigDecimal asBigDecimal() {
public double asDouble(){
return mAmount.doubleValue();
}
-
+
+ /**
+ * Returns integer value of this Money amount.
+ * The fractional part is discarded
+ * @return Integer representation of this amount
+ * @see BigDecimal#intValue()
+ */
+ public int intValue(){
+ return mAmount.intValue();
+ }
+
/**
* An alias for {@link #toPlainString()}
* @return Money formatted as a string (excludes the currency)
@@ -244,14 +316,14 @@ public String asString(){
/**
* Returns a string representation of the Money object formatted according to
* the locale
and includes the currency symbol.
- * The output precision is limited to {@link #DECIMAL_PLACES}.
+ * The output precision is limited to the number of fractional digits supported by the currency
* @param locale Locale to use when formatting the object
* @return String containing formatted Money representation
*/
public String formattedString(Locale locale){
NumberFormat formatter = NumberFormat.getInstance(locale);
- formatter.setMinimumFractionDigits(DECIMAL_PLACES);
- formatter.setMaximumFractionDigits(DECIMAL_PLACES);
+ formatter.setMinimumFractionDigits(mCurrency.getDefaultFractionDigits());
+ formatter.setMaximumFractionDigits(mCurrency.getDefaultFractionDigits());
return formatter.format(asDouble()) + " " + mCurrency.getSymbol(locale);
}
@@ -277,7 +349,7 @@ public Money negate(){
* @param amount {@link BigDecimal} amount to be set
*/
private void setAmount(BigDecimal amount) {
- mAmount = amount.setScale(DECIMAL_PLACES, ROUNDING_MODE);
+ mAmount = amount.setScale(mCurrency.getDefaultFractionDigits(), ROUNDING_MODE);
}
/**
@@ -365,8 +437,9 @@ public Money multiply(Money money){
}
/**
- * Returns a new Money
object whose value is the product of the division of this objects
- * value by the factor multiplier
+ * Returns a new Money
object whose value is the product of this object
+ * and the factor multiplier
+ * The currency of the returned object is the same as the current object
* @param multiplier Factor to multiply the amount by.
* @return Money object whose value is the product of this objects values and multiplier
*/
@@ -374,7 +447,17 @@ public Money multiply(int multiplier){
Money moneyFactor = new Money(new BigDecimal(multiplier), mCurrency);
return multiply(moneyFactor);
}
-
+
+ /**
+ * Returns a new Money
object whose value is the product of this object
+ * and the factor multiplier
+ * @param multiplier Factor to multiply the amount by.
+ * @return Money object whose value is the product of this objects values and multiplier
+ */
+ public Money multiply(BigDecimal multiplier){
+ return new Money(mAmount.multiply(multiplier), mCurrency);
+ }
+
/**
* Returns true if the amount held by this Money object is negative
* @return true
if the amount is negative, false
otherwise.
@@ -384,13 +467,27 @@ public boolean isNegative(){
}
/**
- * Returns the string representation of the amount (without currency) of the Money object
+ * Returns the string representation of the amount (without currency) of the Money object.
+ * This string is not locale-formatted. The decimal operator is a period (.)
* @return String representation of the amount (without currency) of the Money object
*/
public String toPlainString(){
- return mAmount.setScale(DECIMAL_PLACES, ROUNDING_MODE).toPlainString();
+ return mAmount.setScale(mCurrency.getDefaultFractionDigits(), ROUNDING_MODE).toPlainString();
}
-
+
+ /**
+ * Returns the formatted amount in the default locale
+ * This prints the money amount with locale formatting like the decimal separation character
+ * @return Locale-formatted amount
+ */
+ public String formattedAmount(){
+ NumberFormat formatter = NumberFormat.getInstance();
+ formatter.setMinimumFractionDigits(mCurrency.getDefaultFractionDigits());
+ formatter.setMaximumFractionDigits(mCurrency.getDefaultFractionDigits());
+ formatter.setGroupingUsed(false);
+ return formatter.format(asDouble());
+ }
+
/**
* Returns the string representation of the Money object (value + currency) formatted according
* to the default locale
@@ -410,7 +507,7 @@ public int hashCode() {
return result;
}
- /**
+ /** //FIXME: equality failing for money objects
* Two Money objects are only equal if their amount (value) and currencies are equal
* @param obj Object to compare with
* @return true
if the objects are equal, false
otherwise
diff --git a/app/src/main/java/org/gnucash/android/model/Price.java b/app/src/main/java/org/gnucash/android/model/Price.java
new file mode 100644
index 000000000..084da5501
--- /dev/null
+++ b/app/src/main/java/org/gnucash/android/model/Price.java
@@ -0,0 +1,124 @@
+package org.gnucash.android.model;
+
+import java.sql.Timestamp;
+
+/**
+ * Model for commodity prices
+ */
+public class Price extends BaseModel {
+
+ private String mCommodityUID;
+ private String mCurrencyUID;
+ private Timestamp mDate;
+ private String mSource;
+ private String mType;
+ private long mValueNum;
+ private long mValueDenom;
+
+ /**
+ * String indicating that the price was provided by the user
+ */
+ public static final String SOURCE_USER = "user:xfer-dialog";
+
+ public Price(){
+ mDate = new Timestamp(System.currentTimeMillis());
+ }
+
+ /**
+ * Create new instance with the GUIDs of the commodities
+ * @param commodityUID GUID of the origin commodity
+ * @param currencyUID GUID of the target commodity
+ */
+ public Price(String commodityUID, String currencyUID){
+ this.mCommodityUID = commodityUID;
+ this.mCurrencyUID = currencyUID;
+ mDate = new Timestamp(System.currentTimeMillis());
+ }
+
+ public String getCommodityUID() {
+ return mCommodityUID;
+ }
+
+ public void setCommodityUID(String mCommodityUID) {
+ this.mCommodityUID = mCommodityUID;
+ }
+
+ public String getCurrencyUID() {
+ return mCurrencyUID;
+ }
+
+ public void setCurrencyUID(String currencyUID) {
+ this.mCurrencyUID = currencyUID;
+ }
+
+ public Timestamp getDate() {
+ return mDate;
+ }
+
+ public void setDate(Timestamp date) {
+ this.mDate = date;
+ }
+
+ public String getSource() {
+ return mSource;
+ }
+
+ public void setSource(String source) {
+ this.mSource = source;
+ }
+
+ public String getType() {
+ return mType;
+ }
+
+ public void setType(String type) {
+ this.mType = type;
+ }
+
+ public long getValueNum() {
+ return mValueNum;
+ }
+
+ public void setValueNum(long valueNum) {
+ this.mValueNum = valueNum;
+ }
+
+ public long getValueDenom() {
+ return mValueDenom;
+ }
+
+ public void setValueDenom(long valueDenom) {
+ this.mValueDenom = valueDenom;
+ }
+
+ public void reduce() {
+ if (mValueDenom < 0) {
+ mValueDenom = -mValueDenom;
+ mValueNum = -mValueNum;
+ }
+ if (mValueDenom != 0 && mValueNum != 0) {
+ long num1 = mValueNum;
+ if (num1 < 0) {
+ num1 = -num1;
+ }
+ long num2 = mValueDenom;
+ long commonDivisor = 1;
+ for(;;) {
+ long r = num1 % num2;
+ if (r == 0) {
+ commonDivisor = num2;
+ break;
+ }
+ num1 = r;
+ r = num2 % num1;
+ if (r == 0) {
+ commonDivisor = num1;
+ break;
+ }
+ num2 = r;
+ }
+ mValueNum /= commonDivisor;
+ mValueDenom /= commonDivisor;
+ }
+ }
+}
diff --git a/app/src/main/java/org/gnucash/android/model/Split.java b/app/src/main/java/org/gnucash/android/model/Split.java
index 297b83df7..3b14cdc33 100644
--- a/app/src/main/java/org/gnucash/android/model/Split.java
+++ b/app/src/main/java/org/gnucash/android/model/Split.java
@@ -2,6 +2,9 @@
import android.support.annotation.NonNull;
+import android.util.Log;
+
+import org.gnucash.android.db.AccountsDbAdapter;
/**
* A split amount in a transaction.
@@ -15,9 +18,14 @@
*/
public class Split extends BaseModel{
/**
- * Amount value of this split
+ * Amount value of this split which is in the currency of the transaction
+ */
+ private Money mValue;
+
+ /**
+ * Amount of the split in the currency of the account to which the split belongs
*/
- private Money mAmount;
+ private Money mQuantity;
/**
* Transaction UID which this split belongs to
@@ -40,12 +48,28 @@ public class Split extends BaseModel{
private String mMemo;
/**
- * Initialize split with an amount and account
- * @param amount Money amount of this split
+ * Initialize split with a value amount and account
+ * @param value Money value amount of this split
+ * @param accountUID String UID of transfer account
+ */
+ public Split(@NonNull Money value, @NonNull Money quantity, String accountUID){
+ setQuantity(quantity);
+ setValue(value);
+ setAccountUID(accountUID);
+ //NOTE: This is a rather simplististic approach to the split type.
+ //It typically also depends on the account type of the account. But we do not want to access
+ //the database everytime a split is created. So we keep it simple here. Set the type you want explicity.
+ mSplitType = value.isNegative() ? TransactionType.DEBIT : TransactionType.CREDIT;
+ }
+
+ /**
+ * Initialize split with a value amount and account
+ * @param amount Money value amount of this split. Value is always in the currency the owning transaction
* @param accountUID String UID of transfer account
*/
public Split(@NonNull Money amount, String accountUID){
- setAmount(amount);
+ setQuantity(amount);
+ setValue(amount);
setAccountUID(accountUID);
//NOTE: This is a rather simplististic approach to the split type.
//It typically also depends on the account type of the account. But we do not want to access
@@ -53,6 +77,7 @@ public Split(@NonNull Money amount, String accountUID){
mSplitType = amount.isNegative() ? TransactionType.DEBIT : TransactionType.CREDIT;
}
+
/**
* Clones the sourceSplit
to create a new instance with same fields
* @param sourceSplit Split to be cloned
@@ -63,7 +88,8 @@ public Split(Split sourceSplit, boolean generateUID){
this.mAccountUID = sourceSplit.mAccountUID;
this.mSplitType = sourceSplit.mSplitType;
this.mTransactionUID = sourceSplit.mTransactionUID;
- this.mAmount = sourceSplit.mAmount.absolute();
+ this.mValue = new Money(sourceSplit.mValue);
+ this.mQuantity = new Money(sourceSplit.mQuantity);
if (generateUID){
generateUID();
@@ -73,19 +99,41 @@ public Split(Split sourceSplit, boolean generateUID){
}
/**
- * Returns the amount of the split
- * @return Money amount of the split
+ * Returns the value amount of the split
+ * @return Money amount of the split with the currency of the transaction
+ * @see #getQuantity()
+ */
+ public Money getValue() {
+ return mValue;
+ }
+
+ /**
+ * Sets the value amount of the split.
+ * The value is in the currency of the containing transaction
+ * @param value Money value of this split
+ * @see #setQuantity(Money)
+ */
+ public void setValue(Money value) {
+ mValue = value;
+ }
+
+ /**
+ * Returns the quantity amount of the split.
+ * The quantity is in the currency of the account to which the split is associated
+ * @return Money quantity amount
+ * @see #getValue()
*/
- public Money getAmount() {
- return mAmount;
+ public Money getQuantity() {
+ return mQuantity;
}
/**
- * Sets the amount of the split
- * @param amount Money amount of this split
+ * Sets the quantity value of the split
+ * @param quantity Money quantity amount
+ * @see #setValue(Money)
*/
- public void setAmount(Money amount) {
- this.mAmount = amount;
+ public void setQuantity(Money quantity) {
+ this.mQuantity = quantity;
}
/**
@@ -161,11 +209,11 @@ public void setMemo(String memo) {
* @see TransactionType#invert()
*/
public Split createPair(String accountUID){
- Split pair = new Split(mAmount.absolute(), accountUID);
+ Split pair = new Split(mValue.absolute(), accountUID);
pair.setType(mSplitType.invert());
pair.setMemo(mMemo);
pair.setTransactionUID(mTransactionUID);
-
+ pair.setQuantity(mQuantity);
return pair;
}
@@ -175,11 +223,12 @@ public Split createPair(String accountUID){
*/
protected Split clone() throws CloneNotSupportedException {
super.clone();
- Split split = new Split(mAmount, mAccountUID);
+ Split split = new Split(mValue, mAccountUID);
split.setUID(getUID());
split.setType(mSplitType);
split.setMemo(mMemo);
split.setTransactionUID(mTransactionUID);
+ split.setQuantity(mQuantity);
return split;
}
@@ -190,45 +239,122 @@ protected Split clone() throws CloneNotSupportedException {
* @return whether the two splits are a pair
*/
public boolean isPairOf(Split other) {
- return mAmount.absolute().equals(other.mAmount.absolute())
+ return mValue.absolute().equals(other.mValue.absolute())
&& mSplitType.invert().equals(other.mSplitType);
}
+ /**
+ * Returns the formatted amount (with or without negation sign) for the split value
+ * @return Money amount of value
+ * @see #getFormattedAmount(Money, String, TransactionType)
+ */
+ public Money getFormattedValue(){
+ return getFormattedAmount(mValue, mAccountUID, mSplitType);
+ }
+
+ /**
+ * Returns the formatted amount (with or without negation sign) for the quantity
+ * @return Money amount of quantity
+ * @see #getFormattedAmount(Money, String, TransactionType)
+ */
+ public Money getFormattedQuantity(){
+ return getFormattedAmount(mQuantity, mAccountUID, mSplitType);
+ }
+
+ /**
+ * Splits are saved as absolute values to the database, with no negative numbers.
+ * The type of movement the split causes to the balance of an account determines its sign, and
+ * that depends on the split type and the account type
+ * @param amount Money amount to format
+ * @param accountUID GUID of the account
+ * @param splitType Transaction type of the split
+ * @return -{@code amount} if the amount would reduce the balance of {@code account}, otherwise +{@code amount}
+ */
+ public static Money getFormattedAmount(Money amount, String accountUID, TransactionType splitType){
+ boolean isDebitAccount = AccountsDbAdapter.getInstance().getAccountType(accountUID).hasDebitNormalBalance();
+ Money absAmount = amount.absolute();
+
+ boolean isDebitSplit = splitType == TransactionType.DEBIT;
+ if (isDebitAccount) {
+ if (isDebitSplit) {
+ return absAmount;
+ } else {
+ return absAmount.negate();
+ }
+ } else {
+ if (isDebitSplit) {
+ return absAmount.negate();
+ } else {
+ return absAmount;
+ }
+ }
+ }
+
@Override
public String toString() {
- return mSplitType.name() + " of " + mAmount.toString() + " in account: " + mAccountUID;
+ return mSplitType.name() + " of " + mValue.toString() + " in account: " + mAccountUID;
}
/**
* Returns a string representation of the split which can be parsed again using {@link org.gnucash.android.model.Split#parseSplit(String)}
+ * The string is formatted as:
+ * "<uid>;<valueNum>;<valueDenom>;<valueCurrencyCode>;<quantityNum>;<quantityDenom>;<quantityCurrencyCode>;<transaction_uid>;<account_uid>;<type>;<memo>"
+ *
+ * Only the memo field is allowed to be null
* @return the converted CSV string of this split
*/
public String toCsv(){
String sep = ";";
- String splitString = mAmount.asString() + sep + mAmount.getCurrency().getCurrencyCode()
- + sep + mAccountUID + sep + mTransactionUID + sep + mSplitType.name();
+
+ String splitString = getUID() + sep + mValue.getNumerator() + sep + mValue.getDenominator() + sep + mValue.getCurrency().getCurrencyCode() + sep
+ + mQuantity.getNumerator() + sep + mQuantity.getDenominator() + sep + mQuantity.getCurrency().getCurrencyCode()
+ + sep + mTransactionUID + sep + mAccountUID + sep + mSplitType.name();
if (mMemo != null){
- splitString = splitString + ";" + mMemo;
+ splitString = splitString + sep + mMemo;
}
return splitString;
}
/**
- * Parses a split which is in the format ";;;;".
+ * Parses a split which is in the format:
+ * ";;;;;;;;;;".
+ * Also supports parsing of the deprecated format ";;;;;".
* The split input string is the same produced by the {@link Split#toCsv()} method
- *
- * @param splitString String containing formatted split
+ *
+ * @param splitCsvString String containing formatted split
* @return Split instance parsed from the string
*/
- public static Split parseSplit(String splitString) {
- String[] tokens = splitString.split(";");
- Money amount = new Money(tokens[0], tokens[1]);
- Split split = new Split(amount, tokens[2]);
- split.setTransactionUID(tokens[3]);
- split.setType(TransactionType.valueOf(tokens[4]));
- if (tokens.length == 6){
- split.setMemo(tokens[5]);
+ public static Split parseSplit(String splitCsvString) {
+ String[] tokens = splitCsvString.split(";");
+ if (tokens.length < 8) { //old format splits
+ Money amount = new Money(tokens[0], tokens[1]);
+ Split split = new Split(amount, tokens[2]);
+ split.setTransactionUID(tokens[3]);
+ split.setType(TransactionType.valueOf(tokens[4]));
+ if (tokens.length == 6) {
+ split.setMemo(tokens[5]);
+ }
+ return split;
+ } else {
+ int valueNum = Integer.parseInt(tokens[1]);
+ int valueDenom = Integer.parseInt(tokens[2]);
+ String valueCurrencyCode = tokens[3];
+ int quantityNum = Integer.parseInt(tokens[4]);
+ int quantityDenom = Integer.parseInt(tokens[5]);
+ String qtyCurrencyCode = tokens[6];
+
+ Money value = new Money(valueNum, valueDenom, valueCurrencyCode);
+ Money quantity = new Money(quantityNum, quantityDenom, qtyCurrencyCode);
+
+ Split split = new Split(value, tokens[8]);
+ split.setUID(tokens[0]);
+ split.setQuantity(quantity);
+ split.setTransactionUID(tokens[7]);
+ split.setType(TransactionType.valueOf(tokens[9]));
+ if (tokens.length == 11) {
+ split.setMemo(tokens[10]);
+ }
+ return split;
}
- return split;
}
}
diff --git a/app/src/main/java/org/gnucash/android/model/Transaction.java b/app/src/main/java/org/gnucash/android/model/Transaction.java
index 00c9368b0..f6fc9beaa 100644
--- a/app/src/main/java/org/gnucash/android/model/Transaction.java
+++ b/app/src/main/java/org/gnucash/android/model/Transaction.java
@@ -19,7 +19,9 @@
import android.content.Intent;
import org.gnucash.android.BuildConfig;
+import org.gnucash.android.app.GnuCashApplication;
import org.gnucash.android.db.AccountsDbAdapter;
+import org.gnucash.android.db.CommoditiesDbAdapter;
import org.gnucash.android.export.ofx.OfxHelper;
import org.gnucash.android.model.Account.OfxAccountType;
import org.w3c.dom.Document;
@@ -85,10 +87,15 @@ public class Transaction extends BaseModel{
*/
private String mCurrencyCode = Money.DEFAULT_CURRENCY_CODE;
+ /**
+ * GUID of commodity associated with this transaction
+ */
+ private String mCommodityUID;
+
/**
* The splits making up this transaction
*/
- private List mSplitList = new ArrayList();
+ private List mSplitList = new ArrayList<>();
/**
* Name describing the transaction
@@ -161,6 +168,7 @@ public Transaction(Transaction transaction, boolean generateNewUID){
* Initializes the different fields to their default values.
*/
private void initDefaults(){
+ mCurrencyCode = Money.DEFAULT_CURRENCY_CODE;
this.mTimestamp = System.currentTimeMillis();
}
@@ -173,22 +181,6 @@ private void initDefaults(){
* @return Split whose amount is the imbalance of this transaction
*/
public Split getAutoBalanceSplit(){
- //FIXME: when multiple currencies per transaction are supported
- Currency lastCurrency = null;
- for (Split split : mSplitList) {
- Currency currentCurrency = split.getAmount().getCurrency();
- if (lastCurrency == null)
- lastCurrency = currentCurrency;
- else if (lastCurrency != currentCurrency){
- return null; //for now we will not autobalance multi-currency transactions
- }
- lastCurrency = currentCurrency;
- }
-
- //if all the splits are the same currency but the transaction is another
- if (!lastCurrency.getCurrencyCode().equals(mCurrencyCode))
- return null;
-
Money imbalance = getImbalance();
if (!imbalance.isAmountZero()){
Currency currency = Currency.getInstance(mCurrencyCode);
@@ -214,7 +206,7 @@ public List getSplits(){
* @return List of {@link org.gnucash.android.model.Split}s
*/
public List getSplits(String accountUID){
- List splits = new ArrayList();
+ List splits = new ArrayList<>();
for (Split split : mSplitList) {
if (split.getAccountUID().equals(accountUID)){
splits.add(split);
@@ -266,10 +258,11 @@ public Money getBalance(String accountUID){
public Money getImbalance(){
Money imbalance = Money.createZeroInstance(mCurrencyCode);
for (Split split : mSplitList) {
- //TODO: Handle this better when multi-currency support is introduced
- if (!split.getAmount().getCurrency().getCurrencyCode().equals(mCurrencyCode))
- return Money.createZeroInstance(mCurrencyCode); //abort
- Money amount = split.getAmount().absolute();
+ if (!split.getValue().getCurrency().getCurrencyCode().equals(mCurrencyCode)) {
+ // values in transactions are always in the same currency
+ throw new RuntimeException("Splits values in transaction are not in the same currency");
+ }
+ Money amount = split.getValue().absolute();
if (split.getType() == TransactionType.DEBIT)
imbalance = imbalance.subtract(amount);
else
@@ -291,13 +284,19 @@ public static Money computeBalance(String accountUID, List splitList) {
AccountsDbAdapter accountsDbAdapter = AccountsDbAdapter.getInstance();
AccountType accountType = accountsDbAdapter.getAccountType(accountUID);
String currencyCode = accountsDbAdapter.getAccountCurrencyCode(accountUID);
+ Currency accountCurrency = Currency.getInstance(currencyCode);
boolean isDebitAccount = accountType.hasDebitNormalBalance();
Money balance = Money.createZeroInstance(currencyCode);
for (Split split : splitList) {
if (!split.getAccountUID().equals(accountUID))
continue;
- Money absAmount = split.getAmount().absolute().withCurrency(Currency.getInstance(currencyCode));
+ Money absAmount;
+ if (split.getValue().getCurrency() == accountCurrency){
+ absAmount = split.getValue().absolute();
+ } else { //if this split belongs to the account, then either its value or quantity is in the account currency
+ absAmount = split.getQuantity().absolute();
+ }
boolean isDebitSplit = split.getType() == TransactionType.DEBIT;
if (isDebitAccount) {
if (isDebitSplit) {
@@ -343,7 +342,23 @@ public Currency getCurrency(){
return Currency.getInstance(this.mCurrencyCode);
}
- /**
+ /**
+ * Returns the GUID of the commodity for this transaction
+ * @return GUID of commodity
+ */
+ public String getCommodityUID() {
+ return mCommodityUID;
+ }
+
+ /**
+ * Sets the commodity for this transaction
+ * @param commodityUID GUID of commodity
+ */
+ public void setCommodityUID(String commodityUID) {
+ this.mCommodityUID = commodityUID;
+ }
+
+ /**
* Returns the description of the transaction
* @return Transaction description
*/
diff --git a/app/src/main/java/org/gnucash/android/receivers/AccountCreator.java b/app/src/main/java/org/gnucash/android/receivers/AccountCreator.java
index 8f0c172f4..3985e8504 100644
--- a/app/src/main/java/org/gnucash/android/receivers/AccountCreator.java
+++ b/app/src/main/java/org/gnucash/android/receivers/AccountCreator.java
@@ -57,7 +57,7 @@ public void onReceive(Context context, Intent intent) {
if (uid != null)
account.setUID(uid);
- AccountsDbAdapter.getInstance().addAccount(account);
+ AccountsDbAdapter.getInstance().addRecord(account);
}
}
diff --git a/app/src/main/java/org/gnucash/android/receivers/TransactionAppWidgetProvider.java b/app/src/main/java/org/gnucash/android/receivers/TransactionAppWidgetProvider.java
index 34974ff8c..053c2a0f2 100644
--- a/app/src/main/java/org/gnucash/android/receivers/TransactionAppWidgetProvider.java
+++ b/app/src/main/java/org/gnucash/android/receivers/TransactionAppWidgetProvider.java
@@ -21,8 +21,8 @@
import android.content.SharedPreferences.Editor;
import android.preference.PreferenceManager;
-import org.gnucash.android.ui.UxArgument;
-import org.gnucash.android.ui.widget.WidgetConfigurationActivity;
+import org.gnucash.android.ui.common.UxArgument;
+import org.gnucash.android.ui.homescreen.WidgetConfigurationActivity;
/**
* {@link AppWidgetProvider} which is responsible for managing widgets on the homescreen
diff --git a/app/src/main/java/org/gnucash/android/receivers/TransactionRecorder.java b/app/src/main/java/org/gnucash/android/receivers/TransactionRecorder.java
index 99c826d61..3e192af09 100644
--- a/app/src/main/java/org/gnucash/android/receivers/TransactionRecorder.java
+++ b/app/src/main/java/org/gnucash/android/receivers/TransactionRecorder.java
@@ -30,12 +30,13 @@
import org.gnucash.android.model.Split;
import org.gnucash.android.model.Transaction;
import org.gnucash.android.model.TransactionType;
-import org.gnucash.android.ui.widget.WidgetConfigurationActivity;
+import org.gnucash.android.ui.homescreen.WidgetConfigurationActivity;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.StringReader;
import java.math.BigDecimal;
+import java.math.MathContext;
import java.util.Currency;
/**
@@ -72,6 +73,7 @@ public void onReceive(Context context, Intent intent) {
if (accountUID != null) {
TransactionType type = TransactionType.valueOf(args.getString(Transaction.EXTRA_TRANSACTION_TYPE));
BigDecimal amountBigDecimal = (BigDecimal) args.getSerializable(Transaction.EXTRA_AMOUNT);
+ amountBigDecimal = amountBigDecimal.setScale(Currency.getInstance(currencyCode).getDefaultFractionDigits(), BigDecimal.ROUND_HALF_EVEN).round(MathContext.DECIMAL128);
Money amount = new Money(amountBigDecimal, Currency.getInstance(currencyCode));
Split split = new Split(amount.absolute(), accountUID);
split.setType(type);
@@ -98,7 +100,7 @@ public void onReceive(Context context, Intent intent) {
}
}
- TransactionsDbAdapter.getInstance().addTransaction(transaction);
+ TransactionsDbAdapter.getInstance().addRecord(transaction);
WidgetConfigurationActivity.updateAllWidgets(context);
}
diff --git a/app/src/main/java/org/gnucash/android/service/SchedulerService.java b/app/src/main/java/org/gnucash/android/service/SchedulerService.java
index aa85d9857..232f48c6f 100644
--- a/app/src/main/java/org/gnucash/android/service/SchedulerService.java
+++ b/app/src/main/java/org/gnucash/android/service/SchedulerService.java
@@ -99,7 +99,7 @@ private void executeScheduledEvent(ScheduledAction scheduledAction){
case TRANSACTION:
String eventUID = scheduledAction.getActionUID();
TransactionsDbAdapter transactionsDbAdapter = TransactionsDbAdapter.getInstance();
- Transaction trxnTemplate = transactionsDbAdapter.getTransaction(eventUID);
+ Transaction trxnTemplate = transactionsDbAdapter.getRecord(eventUID);
Transaction recurringTrxn = new Transaction(trxnTemplate, true);
//we may be executing scheduled action significantly after scheduled time (depending on when Android fires the alarm)
@@ -112,7 +112,7 @@ private void executeScheduledEvent(ScheduledAction scheduledAction){
}
recurringTrxn.setTime(transactionTime);
recurringTrxn.setCreatedTimestamp(new Timestamp(transactionTime));
- transactionsDbAdapter.addTransaction(recurringTrxn);
+ transactionsDbAdapter.addRecord(recurringTrxn);
break;
case BACKUP:
diff --git a/app/src/main/java/org/gnucash/android/ui/BaseDrawerActivity.java b/app/src/main/java/org/gnucash/android/ui/BaseDrawerActivity.java
deleted file mode 100644
index c6e1aa9f1..000000000
--- a/app/src/main/java/org/gnucash/android/ui/BaseDrawerActivity.java
+++ /dev/null
@@ -1,266 +0,0 @@
-/*
- * Copyright (c) 2015 Ngewi Fet
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.gnucash.android.ui;
-
-import android.app.Activity;
-import android.content.Intent;
-import android.content.res.Configuration;
-import android.os.Bundle;
-import android.support.v4.app.ActionBarDrawerToggle;
-import android.support.v4.widget.DrawerLayout;
-import android.view.LayoutInflater;
-import android.view.View;
-import android.widget.AdapterView;
-import android.widget.ArrayAdapter;
-import android.widget.ListView;
-import android.widget.TextView;
-import android.widget.Toast;
-
-import com.actionbarsherlock.app.SherlockFragmentActivity;
-import com.actionbarsherlock.view.MenuItem;
-import com.commonsware.cwac.merge.MergeAdapter;
-import com.crashlytics.android.Crashlytics;
-
-import org.gnucash.android.R;
-import org.gnucash.android.export.xml.GncXmlExporter;
-import org.gnucash.android.importer.ImportAsyncTask;
-import org.gnucash.android.ui.account.AccountsActivity;
-import org.gnucash.android.ui.chart.ChartReportActivity;
-import org.gnucash.android.ui.settings.SettingsActivity;
-import org.gnucash.android.ui.transaction.ScheduledActionsActivity;
-
-import java.io.FileNotFoundException;
-import java.io.InputStream;
-import java.util.ArrayList;
-
-
-/**
- * Base activity implementing the navigation drawer, to be extended by all activities requiring one
- *
- * @author Ngewi Fet
- */
-public class BaseDrawerActivity extends SherlockFragmentActivity {
- protected DrawerLayout mDrawerLayout;
- protected ListView mDrawerList;
-
- protected CharSequence mTitle;
- private ActionBarDrawerToggle mDrawerToggle;
-
- private class DrawerItemClickListener implements ListView.OnItemClickListener {
- @Override
- public void onItemClick(AdapterView parent, View view, int position, long id) {
- selectItem(position);
- }
- }
-
- @Override
- protected void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
-
- mDrawerLayout = (DrawerLayout) findViewById(R.id.drawer_layout);
- mDrawerList = (ListView) findViewById(R.id.left_drawer);
-
- MergeAdapter mergeAdapter = createNavDrawerMergeAdapter();
-
- mDrawerList.setAdapter(mergeAdapter);
- mDrawerList.setOnItemClickListener(new DrawerItemClickListener());
-
- //FIXME: Migrate to the non-deprecated version when we remove ActionBarSherlock and support only API level 15 and above
- mDrawerToggle = new ActionBarDrawerToggle(
- this, /* host Activity */
- mDrawerLayout, /* DrawerLayout object */
- R.drawable.ic_drawer, /* nav drawer icon to replace 'Up' caret */
- R.string.drawer_open, /* "open drawer" description */
- R.string.drawer_close /* "close drawer" description */
- ) {
-
- /** Called when a drawer has settled in a completely closed state. */
- public void onDrawerClosed(View view) {
- super.onDrawerClosed(view);
- }
-
- /** Called when a drawer has settled in a completely open state. */
- public void onDrawerOpened(View drawerView) {
- super.onDrawerOpened(drawerView);
- getSupportActionBar().setTitle("GnuCash");
- }
- };
-
- mDrawerLayout.setDrawerListener(mDrawerToggle);
- getSupportActionBar().setHomeButtonEnabled(true);
- getSupportActionBar().setDisplayHomeAsUpEnabled(true);
- }
-
- private MergeAdapter createNavDrawerMergeAdapter() {
- //TODO: Localize nav drawer entries when features are finalized
- ArrayList accountNavOptions = new ArrayList<>();
- accountNavOptions.add(getString(R.string.nav_menu_open));
- accountNavOptions.add(getString(R.string.nav_menu_favorites));
- accountNavOptions.add(getString(R.string.nav_menu_reports));
-
- ArrayAdapter accountsNavAdapter = new ArrayAdapter<>(this,
- R.layout.drawer_list_item, accountNavOptions);
-
- int titleColorGreen = getResources().getColor(R.color.title_green);
-
- ArrayList transactionsNavOptions = new ArrayList<>();
- transactionsNavOptions.add(getString(R.string.nav_menu_scheduled_transactions));
- transactionsNavOptions.add(getString(R.string.nav_menu_export));
-
- ArrayAdapter transactionsNavAdapter = new ArrayAdapter<>(this,
- R.layout.drawer_list_item, transactionsNavOptions);
-
- LayoutInflater inflater = getLayoutInflater();
- TextView accountHeader = (TextView) inflater.inflate(R.layout.drawer_section_header, null);
- accountHeader.setText(R.string.title_accounts);
- accountHeader.setTextColor(titleColorGreen);
-
- TextView transactionHeader = (TextView) inflater.inflate(R.layout.drawer_section_header, null);
- transactionHeader.setText(R.string.title_transactions);
- transactionHeader.setTextColor(titleColorGreen);
- MergeAdapter mergeAdapter = new MergeAdapter();
- mergeAdapter.addView(accountHeader);
- mergeAdapter.addAdapter(accountsNavAdapter);
- mergeAdapter.addView(transactionHeader);
- mergeAdapter.addAdapter(transactionsNavAdapter);
-
- mergeAdapter.addView(inflater.inflate(R.layout.horizontal_line, null));
- TextView settingsHeader = (TextView) inflater.inflate(R.layout.drawer_section_header, null);
- settingsHeader.setText(R.string.title_settings);
- settingsHeader.setTextColor(titleColorGreen);
-
- ArrayList aboutNavOptions = new ArrayList<>();
- aboutNavOptions.add(getString(R.string.nav_menu_scheduled_backups));
- aboutNavOptions.add(getString(R.string.nav_menu_settings));
- //TODO: add help view
- ArrayAdapter aboutNavAdapter = new ArrayAdapter<>(this,
- R.layout.drawer_list_item, aboutNavOptions);
-
- mergeAdapter.addView(settingsHeader);
- mergeAdapter.addAdapter(aboutNavAdapter);
- return mergeAdapter;
- }
-
- @Override
- protected void onPostCreate(Bundle savedInstanceState) {
- super.onPostCreate(savedInstanceState);
- mDrawerToggle.syncState();
- }
-
- @Override
- public void onConfigurationChanged(Configuration newConfig) {
- super.onConfigurationChanged(newConfig);
- mDrawerToggle.onConfigurationChanged(newConfig);
- }
-
- @Override
- public boolean onOptionsItemSelected(MenuItem item) {
- if (!mDrawerLayout.isDrawerOpen(mDrawerList))
- mDrawerLayout.openDrawer(mDrawerList);
- else
- mDrawerLayout.closeDrawer(mDrawerList);
-
- return super.onOptionsItemSelected(item);
- }
-
- /**
- * Handler for the navigation drawer items
- * */
- protected void selectItem(int position) {
- switch (position){
- case 1: { //Open... files
- Intent pickIntent = new Intent(Intent.ACTION_GET_CONTENT);
- pickIntent.setType("application/*");
- Intent chooser = Intent.createChooser(pickIntent, getString(R.string.title_select_gnucash_xml_file));
-
- startActivityForResult(chooser, AccountsActivity.REQUEST_PICK_ACCOUNTS_FILE);
- }
- break;
-
- case 2: { //favorite accounts
- Intent intent = new Intent(this, AccountsActivity.class);
- intent.putExtra(AccountsActivity.EXTRA_TAB_INDEX,
- AccountsActivity.INDEX_FAVORITE_ACCOUNTS_FRAGMENT);
- intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP|Intent.FLAG_ACTIVITY_SINGLE_TOP);
- startActivity(intent);
- }
- break;
-
- case 3:
- startActivity(new Intent(this, ChartReportActivity.class));
- break;
-
- case 5: { //show scheduled transactions
- Intent intent = new Intent(this, ScheduledActionsActivity.class);
- intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP|Intent.FLAG_ACTIVITY_SINGLE_TOP);
- intent.putExtra(ScheduledActionsActivity.EXTRA_DISPLAY_MODE,
- ScheduledActionsActivity.DisplayMode.TRANSACTION_ACTIONS);
- startActivity(intent);
- }
- break;
-
- case 6:{
- AccountsActivity.showExportDialog(this);
- }
- break;
-
- case 9: //scheduled backup
- Intent intent = new Intent(this, ScheduledActionsActivity.class);
- intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP|Intent.FLAG_ACTIVITY_SINGLE_TOP);
- intent.putExtra(ScheduledActionsActivity.EXTRA_DISPLAY_MODE,
- ScheduledActionsActivity.DisplayMode.EXPORT_ACTIONS);
- startActivity(intent);
- break;
-
- case 10: //Settings activity
- startActivity(new Intent(this, SettingsActivity.class));
- break;
-
- //TODO: add help option
- }
-
- // Highlight the selected item, update the title, and close the drawer
- mDrawerList.setItemChecked(position, true);
-// setTitle(mNavDrawerEntries[position]);
- mDrawerLayout.closeDrawer(mDrawerList);
- }
-
- @Override
- public void setTitle(CharSequence title) {
- mTitle = title;
- getSupportActionBar().setTitle(mTitle);
- }
-
- @Override
- protected void onActivityResult(int requestCode, int resultCode, Intent data) {
- if (resultCode == Activity.RESULT_CANCELED){
- return;
- }
-
- switch (requestCode) {
- case AccountsActivity.REQUEST_PICK_ACCOUNTS_FILE:
- try {
- GncXmlExporter.createBackup();
- InputStream accountInputStream = getContentResolver().openInputStream(data.getData());
- new ImportAsyncTask(this).execute(accountInputStream);
- } catch (FileNotFoundException e) {
- Crashlytics.logException(e);
- Toast.makeText(this, R.string.toast_error_importing_accounts, Toast.LENGTH_SHORT).show();
- }
- break;
- }
- }
-}
diff --git a/app/src/main/java/org/gnucash/android/ui/account/AccountFormFragment.java b/app/src/main/java/org/gnucash/android/ui/account/AccountFormFragment.java
index 5d583bdd9..c5cca688a 100644
--- a/app/src/main/java/org/gnucash/android/ui/account/AccountFormFragment.java
+++ b/app/src/main/java/org/gnucash/android/ui/account/AccountFormFragment.java
@@ -27,13 +27,23 @@
import android.graphics.Color;
import android.os.Bundle;
import android.preference.PreferenceManager;
+import android.support.design.widget.TextInputLayout;
+import android.support.v4.app.Fragment;
import android.support.v4.app.FragmentManager;
import android.support.v4.widget.SimpleCursorAdapter;
+import android.support.v7.app.ActionBar;
+import android.support.v7.app.AppCompatActivity;
+import android.text.Editable;
import android.text.TextUtils;
+import android.text.TextWatcher;
import android.util.Log;
import android.view.LayoutInflater;
+import android.view.Menu;
+import android.view.MenuInflater;
+import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
+import android.view.WindowManager;
import android.view.inputmethod.InputMethodManager;
import android.widget.AdapterView;
import android.widget.ArrayAdapter;
@@ -42,23 +52,20 @@
import android.widget.CompoundButton.OnCheckedChangeListener;
import android.widget.EditText;
import android.widget.Spinner;
-import android.widget.Toast;
-
-import com.actionbarsherlock.app.SherlockFragment;
-import com.actionbarsherlock.view.Menu;
-import com.actionbarsherlock.view.MenuInflater;
-import com.actionbarsherlock.view.MenuItem;
import org.gnucash.android.R;
import org.gnucash.android.db.AccountsDbAdapter;
+import org.gnucash.android.db.CommoditiesDbAdapter;
import org.gnucash.android.db.DatabaseSchema;
import org.gnucash.android.model.Account;
import org.gnucash.android.model.AccountType;
+import org.gnucash.android.model.Commodity;
import org.gnucash.android.model.Money;
-import org.gnucash.android.ui.UxArgument;
+import org.gnucash.android.ui.common.UxArgument;
import org.gnucash.android.ui.colorpicker.ColorPickerDialog;
import org.gnucash.android.ui.colorpicker.ColorPickerSwatch;
import org.gnucash.android.ui.colorpicker.ColorSquare;
+import org.gnucash.android.util.CommoditiesCursorAdapter;
import org.gnucash.android.util.QualifiedAccountNameCursorAdapter;
import java.util.ArrayList;
@@ -67,12 +74,15 @@
import java.util.HashMap;
import java.util.List;
+import butterknife.Bind;
+import butterknife.ButterKnife;
+
/**
* Fragment used for creating and editing accounts
* @author Ngewi Fet
* @author Yongxin Wang
*/
-public class AccountFormFragment extends SherlockFragment {
+public class AccountFormFragment extends Fragment {
/**
* Tag for the color picker dialog fragment
@@ -82,13 +92,15 @@ public class AccountFormFragment extends SherlockFragment {
/**
* EditText for the name of the account to be created/edited
*/
- private EditText mNameEditText;
-
+ @Bind(R.id.input_account_name) EditText mNameEditText;
+
+ @Bind(R.id.name_text_input_layout) TextInputLayout mTextInputLayout;
+
/**
* Spinner for selecting the currency of the account
* Currencies listed are those specified by ISO 4217
*/
- private Spinner mCurrencySpinner;
+ @Bind(R.id.input_currency_spinner) Spinner mCurrencySpinner;
/**
* Accounts database adapter
@@ -148,34 +160,39 @@ public class AccountFormFragment extends SherlockFragment {
/**
* Spinner for parent account list
*/
- private Spinner mParentAccountSpinner;
+ @Bind(R.id.input_parent_account) Spinner mParentAccountSpinner;
/**
* Checkbox which activates the parent account spinner when selected
* Leaving this unchecked means it is a top-level root account
*/
- private CheckBox mParentCheckBox;
+ @Bind(R.id.checkbox_parent_account) CheckBox mParentCheckBox;
/**
* Spinner for the account type
* @see org.gnucash.android.model.AccountType
*/
- private Spinner mAccountTypeSpinner;
+ @Bind(R.id.input_account_type_spinner) Spinner mAccountTypeSpinner;
/**
* Checkbox for activating the default transfer account spinner
*/
- private CheckBox mDefaultTransferAccountCheckBox;
+ @Bind(R.id.checkbox_default_transfer_account) CheckBox mDefaultTransferAccountCheckBox;
/**
* Spinner for selecting the default transfer account
*/
- private Spinner mDefaulTransferAccountSpinner;
+ @Bind(R.id.input_default_transfer_account) Spinner mDefaulTransferAccountSpinner;
+
+ /**
+ * Account description input text view
+ */
+ @Bind(R.id.input_account_description) EditText mDescriptionEditText;
/**
* Checkbox indicating if account is a placeholder account
*/
- private CheckBox mPlaceholderCheckBox;
+ @Bind(R.id.checkbox_placeholder_account) CheckBox mPlaceholderCheckBox;
/**
* Cursor adapter which binds to the spinner for default transfer account
@@ -195,7 +212,7 @@ public class AccountFormFragment extends SherlockFragment {
/**
* Trigger for color picker dialog
*/
- private ColorSquare mColorSquare;
+ @Bind(R.id.input_color_picker) ColorSquare mColorSquare;
private ColorPickerSwatch.OnColorSelectedListener mColorSelectedListener = new ColorPickerSwatch.OnColorSelectedListener() {
@Override
@@ -239,13 +256,28 @@ public void onCreate(Bundle savedInstanceState) {
*/
@Override public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
- View view = inflater.inflate(R.layout.fragment_new_account, container, false);
- getSherlockActivity().getSupportActionBar().setTitle(R.string.label_create_account);
- mCurrencySpinner = (Spinner) view.findViewById(R.id.input_currency_spinner);
- mNameEditText = (EditText) view.findViewById(R.id.input_account_name);
- //mNameEditText.requestFocus();
+ View view = inflater.inflate(R.layout.fragment_account_form, container, false);
+ ButterKnife.bind(this, view);
+
+ mNameEditText.addTextChangedListener(new TextWatcher() {
+ @Override
+ public void beforeTextChanged(CharSequence s, int start, int count, int after) {
+ //nothing to see here, move along
+ }
+
+ @Override
+ public void onTextChanged(CharSequence s, int start, int before, int count) {
+ //nothing to see here, move along
+ }
+
+ @Override
+ public void afterTextChanged(Editable s) {
+ if (s.toString().length() > 0) {
+ mTextInputLayout.setErrorEnabled(false);
+ }
+ }
+ });
- mAccountTypeSpinner = (Spinner) view.findViewById(R.id.input_account_type_spinner);
mAccountTypeSpinner.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
@Override
public void onItemSelected(AdapterView> parentView, View selectedItemView, int position, long id) {
@@ -260,24 +292,18 @@ public void onNothingSelected(AdapterView> adapterView) {
}
});
- mPlaceholderCheckBox = (CheckBox) view.findViewById(R.id.checkbox_placeholder_account);
- mParentAccountSpinner = (Spinner) view.findViewById(R.id.input_parent_account);
mParentAccountSpinner.setEnabled(false);
- mParentCheckBox = (CheckBox) view.findViewById(R.id.checkbox_parent_account);
mParentCheckBox.setOnCheckedChangeListener(new OnCheckedChangeListener() {
-
- @Override
- public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
- mParentAccountSpinner.setEnabled(isChecked);
- }
- });
- mDefaulTransferAccountSpinner = (Spinner) view.findViewById(R.id.input_default_transfer_account);
- mDefaulTransferAccountSpinner.setEnabled(false);
+ @Override
+ public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
+ mParentAccountSpinner.setEnabled(isChecked);
+ }
+ });
- mDefaultTransferAccountCheckBox = (CheckBox) view.findViewById(R.id.checkbox_default_transfer_account);
+ mDefaulTransferAccountSpinner.setEnabled(false);
mDefaultTransferAccountCheckBox.setOnCheckedChangeListener(new OnCheckedChangeListener() {
@Override
public void onCheckedChanged(CompoundButton compoundButton, boolean isChecked) {
@@ -285,7 +311,6 @@ public void onCheckedChanged(CompoundButton compoundButton, boolean isChecked) {
}
});
- mColorSquare = (ColorSquare) view.findViewById(R.id.input_color_picker);
mColorSquare.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
@@ -303,19 +328,23 @@ public void onClick(View view) {
@Override
public void onActivityCreated(Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
-
- ArrayAdapter currencyArrayAdapter = new ArrayAdapter(
- getActivity(),
- android.R.layout.simple_spinner_item,
- getResources().getStringArray(R.array.currency_names));
- currencyArrayAdapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
- mCurrencySpinner.setAdapter(currencyArrayAdapter);
+
+ Cursor cursor = CommoditiesDbAdapter.getInstance().fetchAllRecords();
+ CommoditiesCursorAdapter commoditiesAdapter = new CommoditiesCursorAdapter(
+ getActivity(), android.R.layout.simple_spinner_item);
+ commoditiesAdapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
+
+ mCurrencySpinner.setAdapter(commoditiesAdapter);
+
mAccountUID = getArguments().getString(UxArgument.SELECTED_ACCOUNT_UID);
+ ActionBar supportActionBar = ((AppCompatActivity) getActivity()).getSupportActionBar();
if (mAccountUID != null) {
- mAccount = mAccountsDbAdapter.getAccount(mAccountUID);
- getSherlockActivity().getSupportActionBar().setTitle(R.string.title_edit_account);
+ mAccount = mAccountsDbAdapter.getRecord(mAccountUID);
+ supportActionBar.setTitle(R.string.title_edit_account);
+ } else {
+ supportActionBar.setTitle(R.string.title_create_account);
}
mRootAccountUID = mAccountsDbAdapter.getOrCreateGnuCashRootAccountUID();
@@ -329,11 +358,12 @@ public void onActivityCreated(Bundle savedInstanceState) {
if (mAccount != null){
initializeViewsWithAccount(mAccount);
+ //do not immediately open the keyboard when editing an account
+ getActivity().getWindow().setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_HIDDEN);
} else {
initializeViews();
}
-
}
/**
@@ -361,14 +391,33 @@ private void initializeViewsWithAccount(Account account){
if (mAccountsDbAdapter.getTransactionMaxSplitNum(mAccount.getUID()) > 1)
{
+ //TODO: Allow changing the currency and effecting the change for all transactions without any currency exchange (purely cosmetic change)
mCurrencySpinner.setEnabled(false);
}
mNameEditText.setText(account.getName());
-
- if (mUseDoubleEntry && account.getDefaultTransferAccountUID() != null) {
- long doubleDefaultAccountId = mAccountsDbAdapter.getID(account.getDefaultTransferAccountUID());
- setDefaultTransferAccountSelection(doubleDefaultAccountId);
+ mNameEditText.setSelection(mNameEditText.getText().length());
+
+ if (account.getDescription() != null)
+ mDescriptionEditText.setText(account.getDescription());
+
+ if (mUseDoubleEntry) {
+ if (account.getDefaultTransferAccountUID() != null) {
+ long doubleDefaultAccountId = mAccountsDbAdapter.getID(account.getDefaultTransferAccountUID());
+ setDefaultTransferAccountSelection(doubleDefaultAccountId, true);
+ } else {
+ String currentAccountUID = account.getParentUID();
+ long defaultTransferAccountID = 0;
+ String rootAccountUID = mAccountsDbAdapter.getOrCreateGnuCashRootAccountUID();
+ while (!currentAccountUID.equals(rootAccountUID)) {
+ defaultTransferAccountID = mAccountsDbAdapter.getDefaultTransferAccountID(mAccountsDbAdapter.getID(currentAccountUID));
+ if (defaultTransferAccountID > 0) {
+ setDefaultTransferAccountSelection(defaultTransferAccountID, false);
+ break; //we found a parent with default transfer setting
+ }
+ currentAccountUID = mAccountsDbAdapter.getParentAccountUID(currentAccountUID);
+ }
+ }
}
mPlaceholderCheckBox.setChecked(account.isPlaceholderAccount());
@@ -433,10 +482,15 @@ private void setDefaultTransferAccountInputsVisible(boolean visible) {
* @param currencyCode ISO 4217 currency code to be selected
*/
private void setSelectedCurrency(String currencyCode){
- mCurrencyCodes = Arrays.asList(getResources().getStringArray(R.array.key_currency_codes));
- if (mCurrencyCodes.contains(currencyCode)){
- mCurrencySpinner.setSelection(mCurrencyCodes.indexOf(currencyCode));
+ CommoditiesDbAdapter commodityDbAdapter = CommoditiesDbAdapter.getInstance();
+ long commodityId = commodityDbAdapter.getID(commodityDbAdapter.getCommodityUID(currencyCode));
+ int position = 0;
+ for (int i = 0; i < mCurrencySpinner.getCount(); i++) {
+ if (commodityId == mCurrencySpinner.getItemIdAtPosition(i)) {
+ position = i;
+ }
}
+ mCurrencySpinner.setSelection(position);
}
/**
@@ -462,15 +516,15 @@ private void setParentAccountSelection(long parentAccountId){
* Selects the account with ID parentAccountId
in the default transfer account spinner
* @param defaultTransferAccountId Record ID of parent account to be selected
*/
- private void setDefaultTransferAccountSelection(long defaultTransferAccountId){
- if (defaultTransferAccountId > 0){
- mDefaultTransferAccountCheckBox.setChecked(true);
- mDefaulTransferAccountSpinner.setEnabled(true);
+ private void setDefaultTransferAccountSelection(long defaultTransferAccountId, boolean enableTransferAccount) {
+ if (defaultTransferAccountId > 0) {
+ mDefaultTransferAccountCheckBox.setChecked(enableTransferAccount);
+ mDefaulTransferAccountSpinner.setEnabled(enableTransferAccount);
} else
return;
for (int pos = 0; pos < mDefaultTransferAccountCursorAdapter.getCount(); pos++) {
- if (mDefaultTransferAccountCursorAdapter.getItemId(pos) == defaultTransferAccountId){
+ if (mDefaultTransferAccountCursorAdapter.getItemId(pos) == defaultTransferAccountId) {
mDefaulTransferAccountSpinner.setSelection(pos);
break;
}
@@ -514,7 +568,7 @@ private void showColorPickerDialog(){
}
@Override
- public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
+ public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
super.onCreateOptionsMenu(menu, inflater);
inflater.inflate(R.menu.default_save_actions, menu);
}
@@ -526,7 +580,7 @@ public boolean onOptionsItemSelected(MenuItem item) {
saveAccount();
return true;
- case R.id.menu_cancel:
+ case android.R.id.home:
finishFragment();
return true;
}
@@ -551,9 +605,7 @@ private void loadDefaultTransferAccountList(){
}
mDefaultTransferAccountCursorAdapter = new QualifiedAccountNameCursorAdapter(getActivity(),
- android.R.layout.simple_spinner_item,
defaultTransferAccountCursor);
- mDefaultTransferAccountCursorAdapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
mDefaulTransferAccountSpinner.setAdapter(mDefaultTransferAccountCursorAdapter);
}
@@ -569,7 +621,7 @@ private void loadParentAccountList(AccountType accountType){
if (mAccount != null){ //if editing an account
mDescendantAccountUIDs = mAccountsDbAdapter.getDescendantAccountUIDs(mAccount.getUID(), null, null);
String rootAccountUID = mAccountsDbAdapter.getOrCreateGnuCashRootAccountUID();
- List descendantAccountUIDs = new ArrayList(mDescendantAccountUIDs);
+ List descendantAccountUIDs = new ArrayList<>(mDescendantAccountUIDs);
if (rootAccountUID != null)
descendantAccountUIDs.add(rootAccountUID);
// limit cyclic account hierarchies.
@@ -594,10 +646,7 @@ private void loadParentAccountList(AccountType accountType){
}
mParentAccountCursorAdapter = new QualifiedAccountNameCursorAdapter(
- getActivity(),
- android.R.layout.simple_spinner_item,
- mParentAccountCursor);
- mParentAccountCursorAdapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
+ getActivity(), mParentAccountCursor);
mParentAccountSpinner.setAdapter(mParentAccountCursorAdapter);
}
@@ -675,7 +724,7 @@ private void loadAccountTypesList(){
* Depends on how the fragment was loaded, it might have a backstack or not
*/
private void finishFragment() {
- InputMethodManager imm = (InputMethodManager) getSherlockActivity().getSystemService(
+ InputMethodManager imm = (InputMethodManager) getActivity().getSystemService(
Context.INPUT_METHOD_SERVICE);
imm.hideSoftInputFromWindow(mNameEditText.getWindowToken(), 0);
@@ -684,7 +733,7 @@ private void finishFragment() {
getActivity().setResult(Activity.RESULT_OK);
getActivity().finish();
} else {
- getSherlockActivity().getSupportFragmentManager().popBackStack();
+ getActivity().getSupportFragmentManager().popBackStack();
}
}
@@ -709,9 +758,8 @@ private void saveAccount() {
if (mAccount == null){
String name = getEnteredName();
if (name == null || name.length() == 0){
- Toast.makeText(getSherlockActivity(),
- R.string.toast_no_account_name_entered,
- Toast.LENGTH_LONG).show();
+ mTextInputLayout.setErrorEnabled(true);
+ mTextInputLayout.setError(getString(R.string.toast_no_account_name_entered));
return;
}
mAccount = new Account(getEnteredName());
@@ -720,14 +768,16 @@ private void saveAccount() {
nameChanged = !mAccount.getName().equals(getEnteredName());
mAccount.setName(getEnteredName());
}
-
- String curCode = mCurrencyCodes.get(mCurrencySpinner
- .getSelectedItemPosition());
- mAccount.setCurrency(Currency.getInstance(curCode));
+
+ long commodityId = mCurrencySpinner.getSelectedItemId();
+ Commodity commodity = CommoditiesDbAdapter.getInstance().getRecord(commodityId);
+ mAccount.setCommodityUID(commodity.getUID());
+ mAccount.setCurrency(Currency.getInstance(commodity.getMnemonic()));
AccountType selectedAccountType = getSelectedAccountType();
mAccount.setAccountType(selectedAccountType);
+ mAccount.setDescription(mDescriptionEditText.getText().toString());
mAccount.setPlaceHolderFlag(mPlaceholderCheckBox.isChecked());
mAccount.setColorCode(mSelectedColor);
@@ -799,7 +849,7 @@ private void saveAccount() {
if (mAccountsDbAdapter == null)
mAccountsDbAdapter = AccountsDbAdapter.getInstance();
// bulk update, will not update transactions
- mAccountsDbAdapter.bulkAddAccounts(accountsToUpdate);
+ mAccountsDbAdapter.bulkAddRecords(accountsToUpdate);
finishFragment();
}
diff --git a/app/src/main/java/org/gnucash/android/ui/account/AccountsActivity.java b/app/src/main/java/org/gnucash/android/ui/account/AccountsActivity.java
index f0660c74c..a9a260c97 100644
--- a/app/src/main/java/org/gnucash/android/ui/account/AccountsActivity.java
+++ b/app/src/main/java/org/gnucash/android/ui/account/AccountsActivity.java
@@ -17,63 +17,70 @@
package org.gnucash.android.ui.account;
+import android.Manifest;
+import android.annotation.TargetApi;
import android.app.Activity;
import android.app.AlertDialog;
+import android.content.ActivityNotFoundException;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.SharedPreferences;
import android.content.SharedPreferences.Editor;
import android.content.pm.PackageInfo;
+import android.content.pm.PackageManager;
import android.content.pm.PackageManager.NameNotFoundException;
import android.content.res.Resources;
import android.net.Uri;
+import android.os.Build;
import android.os.Bundle;
import android.preference.PreferenceManager;
-import android.support.v4.app.DialogFragment;
+import android.support.design.widget.CoordinatorLayout;
+import android.support.design.widget.FloatingActionButton;
+import android.support.design.widget.Snackbar;
+import android.support.design.widget.TabLayout;
import android.support.v4.app.Fragment;
import android.support.v4.app.FragmentActivity;
import android.support.v4.app.FragmentManager;
import android.support.v4.app.FragmentStatePagerAdapter;
-import android.support.v4.app.FragmentTransaction;
import android.support.v4.view.PagerAdapter;
import android.support.v4.view.ViewPager;
+import android.support.v7.widget.Toolbar;
import android.util.Log;
import android.util.SparseArray;
+import android.view.Menu;
+import android.view.MenuInflater;
+import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
-import android.widget.ArrayAdapter;
+import android.widget.Toast;
-import com.actionbarsherlock.view.Menu;
-import com.actionbarsherlock.view.MenuInflater;
-import com.actionbarsherlock.view.MenuItem;
import com.crashlytics.android.Crashlytics;
-import com.viewpagerindicator.TitlePageIndicator;
+import com.kobakei.ratethisapp.RateThisApp;
+import org.gnucash.android.BuildConfig;
import org.gnucash.android.R;
import org.gnucash.android.app.GnuCashApplication;
import org.gnucash.android.db.AccountsDbAdapter;
import org.gnucash.android.db.DatabaseSchema;
-import org.gnucash.android.ui.export.ExportDialogFragment;
import org.gnucash.android.export.xml.GncXmlExporter;
import org.gnucash.android.importer.ImportAsyncTask;
import org.gnucash.android.model.Money;
-import org.gnucash.android.ui.UxArgument;
-import org.gnucash.android.ui.chart.ChartReportActivity;
-import org.gnucash.android.ui.passcode.PassLockActivity;
-import org.gnucash.android.ui.settings.SettingsActivity;
-import org.gnucash.android.ui.transaction.ScheduledActionsActivity;
+import org.gnucash.android.ui.common.BaseDrawerActivity;
+import org.gnucash.android.ui.common.FormActivity;
+import org.gnucash.android.ui.common.UxArgument;
import org.gnucash.android.ui.transaction.TransactionsActivity;
import org.gnucash.android.ui.util.OnAccountClickedListener;
import org.gnucash.android.ui.util.Refreshable;
import org.gnucash.android.ui.util.TaskDelegate;
+import org.gnucash.android.ui.wizard.FirstRunWizardActivity;
import java.io.FileNotFoundException;
import java.io.InputStream;
-import java.util.Arrays;
-import java.util.Currency;
-import java.util.List;
-import java.util.Locale;
+import java.util.ArrayList;
+
+import butterknife.Bind;
+import butterknife.ButterKnife;
/**
* Manages actions related to accounts, displaying, exporting and creating new accounts
@@ -82,7 +89,7 @@
* @author Ngewi Fet
* @author Oleksandr Tyshkovets
*/
-public class AccountsActivity extends PassLockActivity implements OnAccountClickedListener {
+public class AccountsActivity extends BaseDrawerActivity implements OnAccountClickedListener {
/**
* Request code for GnuCash account structure file to import
@@ -95,16 +102,6 @@ public class AccountsActivity extends PassLockActivity implements OnAccountClick
public static final int REQUEST_EDIT_ACCOUNT = 0x10;
/**
- * Tag used for identifying the account export fragment
- */
- public static final String FRAGMENT_EXPORT_DIALOG = "export_fragment";
-
- /**
- * Tag for identifying the "New account" fragment
- */
- protected static final String FRAGMENT_NEW_ACCOUNT = "new_account_dialog";
-
- /**
* Logging tag
*/
protected static final String LOG_TAG = "AccountsActivity";
@@ -138,6 +135,7 @@ public class AccountsActivity extends PassLockActivity implements OnAccountClick
* Key for putting argument for tab into bundle arguments
*/
public static final String EXTRA_TAB_INDEX = "org.gnucash.android.extra.TAB_INDEX";
+ public static final int REQUEST_PERMISSION_WRITE_SD_CARD = 0xAB;
/**
* Map containing fragments for the different tabs
@@ -147,14 +145,14 @@ public class AccountsActivity extends PassLockActivity implements OnAccountClick
/**
* ViewPager which manages the different tabs
*/
- private ViewPager mPager;
-
- /**
- * Dialog which is shown to the user on first start prompting the user to create some accounts
- */
- private AlertDialog mDefaultAccountsDialog;
- private TitlePageIndicator mTitlePageIndicator;
+ @Bind(R.id.pager) ViewPager mViewPager;
+ @Bind(R.id.fab_create_account) FloatingActionButton mFloatingActionButton;
+ @Bind(R.id.coordinatorLayout) CoordinatorLayout mCoordinatorLayout;
+ /**
+ * Configuration for rating the app
+ */
+ public static RateThisApp.Config rateAppConfig = new RateThisApp.Config(14, 30);;
/**
* Adapter for managing the sub-account and transaction fragment pages in the accounts view
@@ -215,52 +213,81 @@ public int getCount() {
}
public AccountsListFragment getCurrentAccountListFragment(){
- int index = mPager.getCurrentItem();
+ int index = mViewPager.getCurrentItem();
return (AccountsListFragment)(mFragmentPageReferenceMap.get(index));
}
@Override
public void onCreate(Bundle savedInstanceState) {
- //it is necessary to set the view first before calling super because of the nav drawer in BaseDrawerActivity
- setContentView(R.layout.activity_accounts);
super.onCreate(savedInstanceState);
+ setContentView(R.layout.activity_accounts);
+ setUpDrawer();
+ ButterKnife.bind(this);
+
+ Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
+ setSupportActionBar(toolbar);
final Intent intent = getIntent();
handleOpenFileIntent(intent);
init();
- mPager = (ViewPager) findViewById(R.id.pager);
- mTitlePageIndicator = (TitlePageIndicator) findViewById(R.id.titles);
-
- String action = intent.getAction();
- if (action != null && action.equals(Intent.ACTION_INSERT_OR_EDIT)) {
- //enter account creation/edit mode if that was specified
- mPager.setVisibility(View.GONE);
- mTitlePageIndicator.setVisibility(View.GONE);
-
- String accountUID = intent.getStringExtra(UxArgument.SELECTED_ACCOUNT_UID);
- if (accountUID != null)
- showEditAccountFragment(accountUID);
- else {
- String parentAccountUID = intent.getStringExtra(UxArgument.PARENT_ACCOUNT_UID);
- showAddAccountFragment(parentAccountUID);
+ TabLayout tabLayout = (TabLayout) findViewById(R.id.tab_layout);
+ tabLayout.addTab(tabLayout.newTab().setText(R.string.title_recent_accounts));
+ tabLayout.addTab(tabLayout.newTab().setText(R.string.title_all_accounts));
+ tabLayout.addTab(tabLayout.newTab().setText(R.string.title_favorite_accounts));
+ tabLayout.setTabGravity(TabLayout.GRAVITY_FILL);
+
+ //show the simple accounts list
+ PagerAdapter mPagerAdapter = new AccountViewPagerAdapter(getSupportFragmentManager());
+ mViewPager.setAdapter(mPagerAdapter);
+
+ mViewPager.addOnPageChangeListener(new TabLayout.TabLayoutOnPageChangeListener(tabLayout));
+ tabLayout.setOnTabSelectedListener(new TabLayout.OnTabSelectedListener() {
+ @Override
+ public void onTabSelected(TabLayout.Tab tab) {
+ mViewPager.setCurrentItem(tab.getPosition());
}
- } else {
- //show the simple accounts list
- PagerAdapter mPagerAdapter = new AccountViewPagerAdapter(getSupportFragmentManager());
- mPager.setAdapter(mPagerAdapter);
- mTitlePageIndicator.setViewPager(mPager);
-
- SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(this);
- int lastTabIndex = preferences.getInt(LAST_OPEN_TAB_INDEX, INDEX_TOP_LEVEL_ACCOUNTS_FRAGMENT);
- int index = intent.getIntExtra(EXTRA_TAB_INDEX, lastTabIndex);
- mPager.setCurrentItem(index);
- }
+ @Override
+ public void onTabUnselected(TabLayout.Tab tab) {
+
+ }
+
+ @Override
+ public void onTabReselected(TabLayout.Tab tab) {
+
+ }
+ });
+
+ SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(this);
+ int lastTabIndex = preferences.getInt(LAST_OPEN_TAB_INDEX, INDEX_TOP_LEVEL_ACCOUNTS_FRAGMENT);
+ int index = intent.getIntExtra(EXTRA_TAB_INDEX, lastTabIndex);
+ mViewPager.setCurrentItem(index);
+
+ mFloatingActionButton.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ Intent addAccountIntent = new Intent(AccountsActivity.this, FormActivity.class);
+ addAccountIntent.setAction(Intent.ACTION_INSERT_OR_EDIT);
+ addAccountIntent.putExtra(UxArgument.FORM_TYPE, FormActivity.FormType.ACCOUNT.name());
+ startActivityForResult(addAccountIntent, AccountsActivity.REQUEST_EDIT_ACCOUNT);
+ }
+ });
}
+ @Override
+ protected void onStart() {
+ super.onStart();
+
+ if (BuildConfig.CAN_REQUEST_RATING) {
+ RateThisApp.init(rateAppConfig);
+ RateThisApp.onStart(this);
+ RateThisApp.showRateDialogIfNeeded(this);
+ }
+ }
+
/**
* Handles the case where another application has selected to open a (.gnucash or .gnca) file with this app
* @param intent Intent containing the data to be imported
@@ -285,6 +312,50 @@ private void handleOpenFileIntent(Intent intent) {
}
}
+ /**
+ * Get permission for WRITING SD card
+ */
+ @TargetApi(23)
+ private void getSDWritePermission(){
+ if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
+ if (checkSelfPermission(Manifest.permission.WRITE_EXTERNAL_STORAGE)
+ != PackageManager.PERMISSION_GRANTED) {
+// if (shouldShowRequestPermissionRationale(Manifest.permission.WRITE_EXTERNAL_STORAGE)){
+ Snackbar.make(mCoordinatorLayout,
+ "GnuCash requires permission to access the SD card for backup and restore",
+ Snackbar.LENGTH_INDEFINITE).setAction("GRANT",
+ new View.OnClickListener() {
+ @Override
+ public void onClick(View view) {
+ requestPermissions(new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE,
+ Manifest.permission.READ_EXTERNAL_STORAGE}, REQUEST_PERMISSION_WRITE_SD_CARD);
+ }
+ })
+ .setActionTextColor(getResources().getColor(R.color.theme_accent))
+ .show();
+// }
+ }
+ }
+ }
+
+ @Override
+ public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) {
+ switch (requestCode){
+ case REQUEST_PERMISSION_WRITE_SD_CARD:{
+ if (grantResults[0] == PackageManager.PERMISSION_GRANTED) {
+
+ //TODO: permission was granted, yay! do the
+ // calendar task you need to do.
+
+ } else {
+
+ // TODO: permission denied, boo! Disable the
+ // functionality that depends on this permission.
+ }
+ } return;
+ }
+ }
+
@Override
protected void onNewIntent(Intent intent) {
super.onNewIntent(intent);
@@ -299,7 +370,7 @@ protected void onNewIntent(Intent intent) {
* @param index Index of fragment to be loaded
*/
public void setTab(int index){
- mPager.setCurrentItem(index);
+ mViewPager.setCurrentItem(index);
}
/**
@@ -313,21 +384,18 @@ private void init() {
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this);
boolean firstRun = prefs.getBoolean(getString(R.string.key_first_run), true);
+
if (firstRun){
- showFirstRunDialog();
+ startActivity(new Intent(this, FirstRunWizardActivity.class));
+
//default to using double entry and save the preference explicitly
prefs.edit().putBoolean(getString(R.string.key_use_double_entry), true).apply();
+ } else {
+ getSDWritePermission();
}
if (hasNewFeatures()){
- AlertDialog dialog = showWhatsNewDialog(this);
- //TODO: remove this when we upgrade to 1.7.0. Users will already know the nav drawer then
- dialog.setOnDismissListener(new DialogInterface.OnDismissListener() {
- @Override
- public void onDismiss(DialogInterface dialog) {
- mDrawerLayout.openDrawer(mDrawerList);
- }
- });
+ showWhatsNewDialog(this);
}
GnuCashApplication.startScheduledActionExecutionService(this);
}
@@ -336,7 +404,7 @@ public void onDismiss(DialogInterface dialog) {
protected void onDestroy() {
super.onDestroy();
SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(this);
- preferences.edit().putInt(LAST_OPEN_TAB_INDEX, mPager.getCurrentItem()).apply();
+ preferences.edit().putInt(LAST_OPEN_TAB_INDEX, mViewPager.getCurrentItem()).apply();
}
/**
@@ -389,23 +457,15 @@ public void onClick(DialogInterface dialog, int which) {
/**
* Displays the dialog for exporting transactions
*/
- public static void showExportDialog(FragmentActivity activity) {
- FragmentManager manager = activity.getSupportFragmentManager();
- FragmentTransaction ft = manager.beginTransaction();
- Fragment prev = manager.findFragmentByTag(FRAGMENT_EXPORT_DIALOG);
- if (prev != null) {
- ft.remove(prev);
- }
- ft.addToBackStack(null);
-
- // Create and show the dialog.
- DialogFragment exportFragment = new ExportDialogFragment();
- exportFragment.show(ft, FRAGMENT_EXPORT_DIALOG);
+ public static void openExportFragment(FragmentActivity activity) {
+ Intent intent = new Intent(activity, FormActivity.class);
+ intent.putExtra(UxArgument.FORM_TYPE, FormActivity.FormType.EXPORT.name());
+ activity.startActivity(intent);
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
- MenuInflater inflater = getSupportMenuInflater();
+ MenuInflater inflater = getMenuInflater();
inflater.inflate(R.menu.global_actions, menu);
return true;
}
@@ -416,179 +476,11 @@ public boolean onOptionsItemSelected(MenuItem item) {
case android.R.id.home:
return super.onOptionsItemSelected(item);
- case R.id.menu_recurring_transactions:
- Intent intent = new Intent(this, ScheduledActionsActivity.class);
- intent.putExtra(ScheduledActionsActivity.EXTRA_DISPLAY_MODE,
- ScheduledActionsActivity.DisplayMode.TRANSACTION_ACTIONS);
- startActivity(intent);
- return true;
-
- case R.id.menu_settings:
- startActivity(new Intent(this, SettingsActivity.class));
- return true;
-
- case R.id.menu_reports:
- startActivity(new Intent(this, ChartReportActivity.class));
- return true;
-
default:
return false;
}
}
- /**
- * Creates an intent which can be used start activity for creating new account
- * @return Intent which can be used to start activity for creating new account
- */
- private Intent createNewAccountIntent(){
- Intent addAccountIntent = new Intent(this, AccountsActivity.class);
- addAccountIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
- addAccountIntent.setAction(Intent.ACTION_INSERT_OR_EDIT);
- return addAccountIntent;
- }
-
- /**
- * Shows form fragment for creating a new account
- * @param parentAccountUID GUID of the parent account present. Can be 0 for top-level account
- */
- private void showAddAccountFragment(String parentAccountUID){
- Bundle args = new Bundle();
- args.putString(UxArgument.PARENT_ACCOUNT_UID, parentAccountUID);
- showAccountFormFragment(args);
- }
-
- /**
- * Shows the form fragment for editing the account with record ID accountId
- * @param accountUID GUID of the account to be edited
- */
- private void showEditAccountFragment(String accountUID) {
- Bundle args = new Bundle();
- args.putString(UxArgument.SELECTED_ACCOUNT_UID, accountUID);
- showAccountFormFragment(args);
- }
-
- /**
- * Shows the form for creating/editing accounts
- * @param args Arguments to use for initializing the form.
- * This could be an account to edit or a preset for the parent account
- */
- private void showAccountFormFragment(Bundle args){
- FragmentManager fragmentManager = getSupportFragmentManager();
- FragmentTransaction fragmentTransaction = fragmentManager
- .beginTransaction();
-
- AccountFormFragment accountFormFragment = AccountFormFragment.newInstance();
- accountFormFragment.setArguments(args);
-
- fragmentTransaction.replace(R.id.fragment_container,
- accountFormFragment, AccountsActivity.FRAGMENT_NEW_ACCOUNT);
-
- fragmentTransaction.commit();
- }
-
- /**
- * Opens a dialog fragment to create a new account
- * @param v View which triggered this callback
- */
- public void onNewAccountClick(View v) {
- startActivity(createNewAccountIntent());
- }
-
- /**
- * Shows the user dialog to create default account structure or import existing account structure
- */
- private void showFirstRunDialog() {
- AlertDialog.Builder builder = new AlertDialog.Builder(this);
- builder.setTitle(R.string.title_default_accounts);
- builder.setMessage(R.string.msg_confirm_create_default_accounts_first_run);
-
- builder.setPositiveButton(R.string.btn_create_accounts, new DialogInterface.OnClickListener() {
- AlertDialog currencyDialog;
- @Override
- public void onClick(DialogInterface dialog, int which) {
- final AlertDialog.Builder adb = new AlertDialog.Builder(AccountsActivity.this);
- adb.setTitle(R.string.title_choose_currency);
- ArrayAdapter arrayAdapter = new ArrayAdapter<>(
- AccountsActivity.this,
- android.R.layout.select_dialog_singlechoice,
- getResources().getStringArray(R.array.currency_names));
-
- final List currencyCodes = Arrays.asList(
- getResources().getStringArray(R.array.key_currency_codes));
- String userCurrencyCode = Currency.getInstance(Locale.getDefault()).getCurrencyCode();
- int currencyIndex = currencyCodes.indexOf(userCurrencyCode.toUpperCase());
-
- adb.setSingleChoiceItems(arrayAdapter, currencyIndex, new DialogInterface.OnClickListener() {
- @Override
- public void onClick(DialogInterface dialog, int which) {
- String currency = currencyCodes.get(which);
- PreferenceManager.getDefaultSharedPreferences(AccountsActivity.this)
- .edit()
- .putString(getString(R.string.key_default_currency), currency)
- .commit();
-
- createDefaultAccounts(currency, AccountsActivity.this);
- currencyDialog.dismiss();
- removeFirstRunFlag();
- }
- });
- currencyDialog = adb.create();
- currencyDialog.show();
- }
- });
-
- builder.setNegativeButton(R.string.btn_cancel, new DialogInterface.OnClickListener() {
-
- @Override
- public void onClick(DialogInterface dialog, int which) {
- mDefaultAccountsDialog.dismiss();
- }
- });
-
- builder.setNeutralButton(R.string.btn_import_accounts, new DialogInterface.OnClickListener() {
- @Override
- public void onClick(DialogInterface dialogInterface, int i) {
- importAccounts();
- }
- });
-
- mDefaultAccountsDialog = builder.create();
- mDefaultAccountsDialog.setOnDismissListener(new DialogInterface.OnDismissListener() {
- @Override
- public void onDismiss(DialogInterface dialog) {
- removeFirstRunFlag();
- mDrawerLayout.openDrawer(mDrawerList);
- }
- });
- mDefaultAccountsDialog.show();
-
-/*
- //TODO: For now logging is disabled only for production. In the future, consider enabling for production
- //show dialog to get user consent for logging
- new AlertDialog.Builder(this)
- .setTitle(getString(R.string.title_enable_crashlytics))
- .setMessage(getString(R.string.msg_enable_crashlytics))
- .setPositiveButton(R.string.label_enable, new DialogInterface.OnClickListener() {
- @Override
- public void onClick(DialogInterface dialogInterface, int i) {
- SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(AccountsActivity.this);
- Editor editor = sharedPreferences.edit();
- editor.putBoolean(getString(R.string.key_enable_crashlytics), true);
- editor.apply();
- }
- })
- .setNegativeButton(R.string.btn_cancel, new DialogInterface.OnClickListener() {
- @Override
- public void onClick(DialogInterface dialogInterface, int i) {
- SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(AccountsActivity.this);
- Editor editor = sharedPreferences.edit();
- editor.putBoolean(getString(R.string.key_enable_crashlytics), false);
- editor.apply();
- }
- }).create().show();
-*/
- }
-
/**
* Creates default accounts with the specified currency code.
* If the currency parameter is null, then locale currency will be used if available
@@ -613,15 +505,48 @@ public void onTaskComplete() {
/**
* Starts Intent chooser for selecting a GnuCash accounts file to import.
- * The accounts are actually imported in onActivityResult
+ * The {@code activity} is responsible for the actual import of the file and can do so by calling {@link #importXmlFileFromIntent(Activity, Intent)}
+ * The calling class should respond to the request code {@link AccountsActivity#REQUEST_PICK_ACCOUNTS_FILE} in its {@link #onActivityResult(int, int, Intent)} method
+ * @param activity Activity starting the request and will also handle the response
+ * @see #importXmlFileFromIntent(Activity, Intent)
*/
- public void importAccounts() {
+ public static void startXmlFileChooser(Activity activity) {
Intent pickIntent = new Intent(Intent.ACTION_GET_CONTENT);
- pickIntent.setType("application/octet-stream");
+// ArrayList mimeTypes = new ArrayList<>();
+// mimeTypes.add("application/*");
+// mimeTypes.add("file/*");
+// mimeTypes.add("text/*");
+// mimeTypes.add("application/vnd.google-apps.file");
+// pickIntent.putStringArrayListExtra(Intent.EXTRA_MIME_TYPES, mimeTypes);
+ pickIntent.addCategory(Intent.CATEGORY_OPENABLE);
+ pickIntent.setType("*/*");
Intent chooser = Intent.createChooser(pickIntent, "Select GnuCash account file");
- startActivityForResult(chooser, REQUEST_PICK_ACCOUNTS_FILE);
+ try {
+ activity.startActivityForResult(chooser, REQUEST_PICK_ACCOUNTS_FILE);
+ } catch (ActivityNotFoundException ex){
+ Crashlytics.log("No file manager for selecting files available");
+ Crashlytics.logException(ex);
+ Toast.makeText(activity, R.string.toast_install_file_manager, Toast.LENGTH_LONG).show();
+ }
+ }
+
+ /**
+ * Reads and XML file from an intent and imports it into the database
+ * This method is usually called in response to {@link AccountsActivity#startXmlFileChooser(Activity)}
+ * @param context Activity context
+ * @param data Intent data containing the XML uri
+ */
+ public static void importXmlFileFromIntent(Activity context, Intent data) {
+ try {
+ GncXmlExporter.createBackup();
+ InputStream accountInputStream = context.getContentResolver().openInputStream(data.getData());
+ new ImportAsyncTask(context).execute(accountInputStream);
+ } catch (FileNotFoundException e) {
+ Crashlytics.logException(e);
+ Toast.makeText(context, R.string.toast_error_importing_accounts, Toast.LENGTH_SHORT).show();
+ }
}
/**
@@ -648,9 +573,10 @@ public void accountSelected(String accountUID) {
* Removes the flag indicating that the app is being run for the first time.
* This is called every time the app is started because the next time won't be the first time
*/
- private void removeFirstRunFlag(){
- Editor editor = PreferenceManager.getDefaultSharedPreferences(this).edit();
- editor.putBoolean(getString(R.string.key_first_run), false);
+ public static void removeFirstRunFlag(){
+ Context context = GnuCashApplication.getAppContext();
+ Editor editor = PreferenceManager.getDefaultSharedPreferences(context).edit();
+ editor.putBoolean(context.getString(R.string.key_first_run), false);
editor.commit();
}
diff --git a/app/src/main/java/org/gnucash/android/ui/account/AccountsListFragment.java b/app/src/main/java/org/gnucash/android/ui/account/AccountsListFragment.java
index ff8da4aef..74deec211 100644
--- a/app/src/main/java/org/gnucash/android/ui/account/AccountsListFragment.java
+++ b/app/src/main/java/org/gnucash/android/ui/account/AccountsListFragment.java
@@ -18,58 +18,66 @@
import android.app.Activity;
import android.app.SearchManager;
+import android.content.ContentValues;
import android.content.Context;
import android.content.Intent;
+import android.content.res.Configuration;
import android.database.Cursor;
import android.graphics.Color;
-import android.graphics.Rect;
+import android.os.AsyncTask;
+import android.os.Build;
import android.os.Bundle;
+import android.support.v4.app.Fragment;
import android.support.v4.app.LoaderManager.LoaderCallbacks;
import android.support.v4.content.Loader;
-import android.support.v4.widget.SimpleCursorAdapter;
+import android.support.v4.view.MenuItemCompat;
+import android.support.v7.app.ActionBar;
+import android.support.v7.app.AppCompatActivity;
+import android.support.v7.widget.GridLayoutManager;
+import android.support.v7.widget.LinearLayoutManager;
+import android.support.v7.widget.PopupMenu;
+import android.support.v7.widget.RecyclerView;
import android.text.TextUtils;
import android.util.Log;
import android.view.LayoutInflater;
-import android.view.TouchDelegate;
+import android.view.Menu;
+import android.view.MenuInflater;
+import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
-import android.widget.AdapterView;
-import android.widget.AdapterView.OnItemLongClickListener;
-import android.widget.ImageButton;
-import android.widget.ListAdapter;
-import android.widget.ListView;
+import android.widget.ImageView;
import android.widget.TextView;
-import com.actionbarsherlock.app.ActionBar;
-import com.actionbarsherlock.app.SherlockListFragment;
-import com.actionbarsherlock.view.ActionMode;
-import com.actionbarsherlock.view.ActionMode.Callback;
-import com.actionbarsherlock.view.Menu;
-import com.actionbarsherlock.view.MenuInflater;
-import com.actionbarsherlock.view.MenuItem;
-
import org.gnucash.android.R;
import org.gnucash.android.db.AccountsDbAdapter;
import org.gnucash.android.db.DatabaseCursorLoader;
import org.gnucash.android.db.DatabaseSchema;
-import org.gnucash.android.db.TransactionsDbAdapter;
import org.gnucash.android.model.Account;
-import org.gnucash.android.ui.UxArgument;
-import org.gnucash.android.ui.transaction.TransactionsActivity;
+import org.gnucash.android.ui.common.FormActivity;
+import org.gnucash.android.ui.common.UxArgument;
import org.gnucash.android.ui.util.AccountBalanceTask;
+import org.gnucash.android.ui.util.CursorRecyclerAdapter;
+import org.gnucash.android.ui.util.widget.EmptyRecyclerView;
import org.gnucash.android.ui.util.OnAccountClickedListener;
import org.gnucash.android.ui.util.Refreshable;
+import butterknife.Bind;
+import butterknife.ButterKnife;
+
/**
* Fragment for displaying the list of accounts in the database
*
* @author Ngewi Fet
*/
-public class AccountsListFragment extends SherlockListFragment implements
+public class AccountsListFragment extends Fragment implements
Refreshable,
- LoaderCallbacks, OnItemLongClickListener,
- com.actionbarsherlock.widget.SearchView.OnQueryTextListener,
- com.actionbarsherlock.widget.SearchView.OnCloseListener {
+ LoaderCallbacks,
+ android.support.v7.widget.SearchView.OnQueryTextListener,
+ android.support.v7.widget.SearchView.OnCloseListener {
+
+ AccountRecyclerAdapter mAccountRecyclerAdapter;
+ @Bind(R.id.account_recycler_view) EmptyRecyclerView mRecyclerView;
+ @Bind(R.id.empty_view) TextView mEmptyTextView;
/**
* Describes the kinds of accounts that should be loaded in the accounts list.
@@ -90,11 +98,6 @@ public enum DisplayMode {
*/
protected static final String TAG = "AccountsListFragment";
-
- /**
- * {@link ListAdapter} for the accounts which will be bound to the list
- */
- AccountsCursorAdapter mAccountsCursorAdapter;
/**
* Database adapter for loading Account records from the database
*/
@@ -103,30 +106,6 @@ public enum DisplayMode {
* Listener to be notified when an account is clicked
*/
private OnAccountClickedListener mAccountSelectedListener;
- /**
- * Flag to indicate if the fragment is in edit mode
- * Edit mode means an account has been selected (through long press) and the
- * context action bar (CAB) is activated
- */
- private boolean mInEditMode = false;
- /**
- * Android action mode
- * Is not null only when an accoun is selected and the Context ActionBar (CAB) is activated
- */
- private ActionMode mActionMode = null;
-
- /**
- * Stores the database ID of the currently selected account when in action mode.
- * This is necessary because getSelectedItemId() does not work properly (by design)
- * in touch mode (which is the majority of devices today)
- */
- private long mSelectedItemId = -1;
-
- /**
- * Database record ID of the account whose children will be loaded by the list fragment.
- * If no parent account is specified, then all top-level accounts are loaded.
- */
-// private long mParentAccountId = -1;
/**
* GUID of the account whose children will be loaded in the list fragment.
@@ -142,69 +121,7 @@ public enum DisplayMode {
/**
* Search view for searching accounts
*/
- private com.actionbarsherlock.widget.SearchView mSearchView;
-
- /**
- * Callbacks for the CAB menu
- */
- private ActionMode.Callback mActionModeCallbacks = new Callback() {
-
- String mSelectedAccountUID;
-
- @Override
- public boolean onCreateActionMode(ActionMode mode, Menu menu) {
- MenuInflater inflater = mode.getMenuInflater();
- inflater.inflate(R.menu.account_context_menu, menu);
- mode.setTitle(getString(R.string.title_selected, 1));
- mSelectedAccountUID = mAccountsDbAdapter.getUID(mSelectedItemId);
- return true;
- }
-
- @Override
- public boolean onPrepareActionMode(ActionMode mode, Menu menu) {
- // nothing to see here, move along
- MenuItem favoriteAccountMenuItem = menu.findItem(R.id.menu_favorite_account);
- boolean isFavoriteAccount = AccountsDbAdapter.getInstance().isFavoriteAccount(mSelectedAccountUID);
-
- int favoriteIcon = isFavoriteAccount ? android.R.drawable.btn_star_big_on : android.R.drawable.btn_star_big_off;
- favoriteAccountMenuItem.setIcon(favoriteIcon);
-
- return true;
- }
-
- @Override
- public boolean onActionItemClicked(ActionMode mode, MenuItem item) {
- switch (item.getItemId()) {
- case R.id.menu_favorite_account:
- boolean isFavorite = mAccountsDbAdapter.isFavoriteAccount(mSelectedAccountUID);
- //toggle favorite preference
- mAccountsDbAdapter.updateAccount(mSelectedItemId,
- DatabaseSchema.AccountEntry.COLUMN_FAVORITE, isFavorite ? "0" : "1");
- mode.invalidate();
- return true;
-
- case R.id.context_menu_edit_accounts:
- openCreateOrEditActivity(mSelectedItemId);
- mode.finish();
- mActionMode = null;
- return true;
-
- case R.id.context_menu_delete:
- tryDeleteAccount(mSelectedItemId);
- mode.finish();
- mActionMode = null;
- return true;
-
- default:
- return false;
- }
- }
-
- @Override
- public void onDestroyActionMode(ActionMode mode) {
- finishEditMode();
- }
- };
+ private android.support.v7.widget.SearchView mSearchView;
public static AccountsListFragment newInstance(DisplayMode displayMode){
AccountsListFragment fragment = new AccountsListFragment();
@@ -217,8 +134,31 @@ public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
View v = inflater.inflate(R.layout.fragment_accounts_list, container,
false);
- TextView sumlabelTextView = (TextView) v.findViewById(R.id.label_sum);
- sumlabelTextView.setText(R.string.account_balance);
+
+ ButterKnife.bind(this, v);
+ mRecyclerView.setHasFixedSize(true);
+ mRecyclerView.setEmptyView(mEmptyTextView);
+
+ switch (mDisplayMode){
+
+ case TOP_LEVEL:
+ mEmptyTextView.setText(R.string.label_no_accounts);
+ break;
+ case RECENT:
+ mEmptyTextView.setText(R.string.label_no_recent_accounts);
+ break;
+ case FAVORITES:
+ mEmptyTextView.setText(R.string.label_no_favorite_accounts);
+ break;
+ }
+
+ if (getResources().getConfiguration().orientation == Configuration.ORIENTATION_LANDSCAPE) {
+ GridLayoutManager gridLayoutManager = new GridLayoutManager(getActivity(), 2);
+ mRecyclerView.setLayoutManager(gridLayoutManager);
+ } else {
+ LinearLayoutManager mLayoutManager = new LinearLayoutManager(getActivity());
+ mRecyclerView.setLayoutManager(mLayoutManager);
+ }
return v;
}
@@ -231,34 +171,30 @@ public void onCreate(Bundle savedInstanceState) {
mParentAccountUID = args.getString(UxArgument.PARENT_ACCOUNT_UID);
mAccountsDbAdapter = AccountsDbAdapter.getInstance();
- mAccountsCursorAdapter = new AccountsCursorAdapter(
- getActivity().getApplicationContext(),
- R.layout.list_item_account, null,
- new String[]{DatabaseSchema.AccountEntry.COLUMN_NAME},
- new int[]{R.id.primary_text});
-
- setListAdapter(mAccountsCursorAdapter);
}
@Override
public void onActivityCreated(Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
- ActionBar actionbar = getSherlockActivity().getSupportActionBar();
+ ActionBar actionbar = ((AppCompatActivity) getActivity()).getSupportActionBar();
actionbar.setTitle(R.string.title_accounts);
actionbar.setDisplayHomeAsUpEnabled(true);
-
setHasOptionsMenu(true);
- ListView lv = getListView();
- lv.setOnItemLongClickListener(this);
- lv.setChoiceMode(ListView.CHOICE_MODE_SINGLE);
+
+ // specify an adapter (see also next example)
+ mAccountRecyclerAdapter = new AccountRecyclerAdapter(null);
+ mRecyclerView.setAdapter(mAccountRecyclerAdapter);
+
getLoaderManager().initLoader(0, null, this);
}
@Override
public void onResume() {
super.onResume();
+ ActionBar actionbar = ((AppCompatActivity) getActivity()).getSupportActionBar();
+ actionbar.setTitle(R.string.title_accounts);
refresh();
}
@@ -272,31 +208,8 @@ public void onAttach(Activity activity) {
}
}
- @Override
- public void onListItemClick(ListView listView, View view, int position, long id) {
- super.onListItemClick(listView, view, position, id);
- if (mInEditMode) {
- mSelectedItemId = id;
- listView.setItemChecked(position, true);
- return;
- }
- mAccountSelectedListener.accountSelected(mAccountsDbAdapter.getUID(id));
- }
-
- @Override
- public boolean onItemLongClick(AdapterView> parent, View view, int position,
- long id) {
- if (mActionMode != null) {
- return false;
- }
- mInEditMode = true;
- mSelectedItemId = id;
- // Start the CAB using the ActionMode.Callback defined above
- mActionMode = getSherlockActivity().startActionMode(
- mActionModeCallbacks);
-
- getListView().setItemChecked(position, true);
- return true;
+ public void onListItemClick(String accountUID) {
+ mAccountSelectedListener.accountSelected(accountUID);
}
@Override
@@ -315,12 +228,11 @@ public void onActivityResult(int requestCode, int resultCode, Intent data) {
* @param rowId The record ID of the account
*/
public void tryDeleteAccount(long rowId) {
- Account acc = mAccountsDbAdapter.getAccount(rowId);
+ Account acc = mAccountsDbAdapter.getRecord(rowId);
if (acc.getTransactionCount() > 0 || mAccountsDbAdapter.getSubAccountCount(acc.getUID()) > 0) {
showConfirmationDialog(rowId);
} else {
mAccountsDbAdapter.deleteRecord(rowId);
- mAccountsCursorAdapter.swapCursor(null);
refresh();
}
}
@@ -334,18 +246,7 @@ public void showConfirmationDialog(long id) {
DeleteAccountDialogFragment alertFragment =
DeleteAccountDialogFragment.newInstance(mAccountsDbAdapter.getUID(id));
alertFragment.setTargetFragment(this, 0);
- alertFragment.show(getSherlockActivity().getSupportFragmentManager(), "delete_confirmation_dialog");
- }
-
- /**
- * Finish the edit mode and dismisses the Contextual ActionBar
- * Any selected (highlighted) accounts are deselected
- */
- public void finishEditMode() {
- mInEditMode = false;
- getListView().setItemChecked(getListView().getCheckedItemPosition(), false);
- mActionMode = null;
- mSelectedItemId = -1;
+ alertFragment.show(getActivity().getSupportFragmentManager(), "delete_confirmation_dialog");
}
@Override
@@ -357,8 +258,8 @@ public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
// Associate searchable configuration with the SearchView
SearchManager searchManager =
(SearchManager) getActivity().getSystemService(Context.SEARCH_SERVICE);
- mSearchView =
- (com.actionbarsherlock.widget.SearchView) menu.findItem(R.id.menu_search).getActionView();
+ mSearchView = (android.support.v7.widget.SearchView)
+ MenuItemCompat.getActionView(menu.findItem(R.id.menu_search));
if (mSearchView == null)
return;
@@ -369,25 +270,6 @@ public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
}
}
- @Override
- public boolean onOptionsItemSelected(MenuItem item) {
- switch (item.getItemId()) {
-
- case R.id.menu_add_account:
- Intent addAccountIntent = new Intent(getActivity(), AccountsActivity.class);
- addAccountIntent.setAction(Intent.ACTION_INSERT_OR_EDIT);
- addAccountIntent.putExtra(UxArgument.PARENT_ACCOUNT_UID, mParentAccountUID);
- startActivityForResult(addAccountIntent, AccountsActivity.REQUEST_EDIT_ACCOUNT);
- return true;
-
- case R.id.menu_export:
- AccountsActivity.showExportDialog(getActivity());
- return true;
-
- default:
- return super.onOptionsItemSelected(item);
- }
- }
@Override
public void refresh(String parentAccountUID) {
@@ -418,9 +300,10 @@ public void onDestroy() {
* @param accountId Long record ID of account to be edited. Pass 0 to create a new account.
*/
public void openCreateOrEditActivity(long accountId){
- Intent editAccountIntent = new Intent(AccountsListFragment.this.getActivity(), AccountsActivity.class);
+ Intent editAccountIntent = new Intent(AccountsListFragment.this.getActivity(), FormActivity.class);
editAccountIntent.setAction(Intent.ACTION_INSERT_OR_EDIT);
editAccountIntent.putExtra(UxArgument.SELECTED_ACCOUNT_UID, mAccountsDbAdapter.getUID(accountId));
+ editAccountIntent.putExtra(UxArgument.FORM_TYPE, FormActivity.FormType.ACCOUNT.name());
startActivityForResult(editAccountIntent, AccountsActivity.REQUEST_EDIT_ACCOUNT);
}
@@ -440,14 +323,14 @@ public Loader onCreateLoader(int id, Bundle args) {
@Override
public void onLoadFinished(Loader loaderCursor, Cursor cursor) {
Log.d(TAG, "Accounts loader finished. Swapping in cursor");
- mAccountsCursorAdapter.swapCursor(cursor);
- mAccountsCursorAdapter.notifyDataSetChanged();
+ mAccountRecyclerAdapter.swapCursor(cursor);
+ mAccountRecyclerAdapter.notifyDataSetChanged();
}
@Override
public void onLoaderReset(Loader arg0) {
Log.d(TAG, "Resetting the accounts loader");
- mAccountsCursorAdapter.swapCursor(null);
+ mAccountRecyclerAdapter.swapCursor(null);
}
@Override
@@ -552,108 +435,138 @@ public Cursor loadInBackground() {
}
}
- /**
- * Overrides the {@link SimpleCursorAdapter} to provide custom binding of the
- * information from the database to the views
- *
- * @author Ngewi Fet
- */
- private class AccountsCursorAdapter extends SimpleCursorAdapter {
- TransactionsDbAdapter transactionsDBAdapter;
- public AccountsCursorAdapter(Context context, int layout, Cursor c,
- String[] from, int[] to) {
- super(context, layout, c, from, to, 0);
- transactionsDBAdapter = TransactionsDbAdapter.getInstance();
+ class AccountRecyclerAdapter extends CursorRecyclerAdapter {
+
+ public AccountRecyclerAdapter(Cursor cursor){
+ super(cursor);
}
@Override
- public void bindView(View v, Context context, Cursor cursor) {
- // perform the default binding
- super.bindView(v, context, cursor);
+ public AccountViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
+ View v = LayoutInflater.from(parent.getContext())
+ .inflate(R.layout.cardview_account, parent, false);
+ return new AccountViewHolder(v);
+ }
+
+ @Override
+ public void onBindViewHolderCursor(final AccountViewHolder holder, final Cursor cursor) {
final String accountUID = cursor.getString(cursor.getColumnIndexOrThrow(DatabaseSchema.AccountEntry.COLUMN_UID));
+ holder.accoundId = mAccountsDbAdapter.getID(accountUID);
- TextView subAccountTextView = (TextView) v.findViewById(R.id.secondary_text);
+ holder.accountName.setText(cursor.getString(cursor.getColumnIndexOrThrow(DatabaseSchema.AccountEntry.COLUMN_NAME)));
int subAccountCount = mAccountsDbAdapter.getSubAccountCount(accountUID);
if (subAccountCount > 0) {
- subAccountTextView.setVisibility(View.VISIBLE);
+ holder.description.setVisibility(View.VISIBLE);
String text = getResources().getQuantityString(R.plurals.label_sub_accounts, subAccountCount, subAccountCount);
- subAccountTextView.setText(text);
+ holder.description.setText(text);
} else
- subAccountTextView.setVisibility(View.GONE);
+ holder.description.setVisibility(View.GONE);
// add a summary of transactions to the account view
- TextView accountBalanceTextView = (TextView) v
- .findViewById(R.id.transactions_summary);
- new AccountBalanceTask(accountBalanceTextView).execute(accountUID);
-
- View colorStripView = v.findViewById(R.id.account_color_strip);
- String accountColor = cursor.getString(cursor.getColumnIndexOrThrow(DatabaseSchema.AccountEntry.COLUMN_COLOR_CODE));
- if (accountColor != null){
- int color = Color.parseColor(accountColor);
- colorStripView.setBackgroundColor(color);
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {
+ // Make sure the balance task is truely multithread
+ new AccountBalanceTask(holder.accountBalance).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, accountUID);
} else {
- colorStripView.setBackgroundColor(Color.TRANSPARENT);
+ new AccountBalanceTask(holder.accountBalance).execute(accountUID);
}
+ String accountColor = cursor.getString(cursor.getColumnIndexOrThrow(DatabaseSchema.AccountEntry.COLUMN_COLOR_CODE));
+ int colorCode = accountColor == null ? Color.TRANSPARENT : Color.parseColor(accountColor);
+ holder.colorStripView.setBackgroundColor(colorCode);
boolean isPlaceholderAccount = mAccountsDbAdapter.isPlaceholderAccount(accountUID);
- ImageButton newTransactionButton = (ImageButton) v.findViewById(R.id.btn_new_transaction);
- if (isPlaceholderAccount){
- newTransactionButton.setVisibility(View.GONE);
- v.findViewById(R.id.vertical_line).setVisibility(View.GONE);
+ if (isPlaceholderAccount) {
+ holder.createTransaction.setVisibility(View.GONE);
} else {
- newTransactionButton.setOnClickListener(new View.OnClickListener() {
+ holder.createTransaction.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
- Intent intent = new Intent(getActivity(), TransactionsActivity.class);
+ Intent intent = new Intent(getActivity(), FormActivity.class);
intent.setAction(Intent.ACTION_INSERT_OR_EDIT);
intent.putExtra(UxArgument.SELECTED_ACCOUNT_UID, accountUID);
+ intent.putExtra(UxArgument.FORM_TYPE, FormActivity.FormType.TRANSACTION.name());
getActivity().startActivity(intent);
}
});
}
- newTransactionButton.setFocusable(false);
- }
- @Override
- public View getView(int position, View convertView, ViewGroup parent) {
- View itemView = super.getView(position, convertView, parent);
- TextView secondaryText = (TextView) itemView.findViewById(R.id.secondary_text);
-
- ListView listView = (ListView) parent;
- if (mInEditMode && listView.isItemChecked(position)){
- itemView.setBackgroundColor(getResources().getColor(R.color.abs__holo_blue_light));
- secondaryText.setTextColor(getResources().getColor(android.R.color.white));
+ if (mAccountsDbAdapter.isFavoriteAccount(accountUID)){
+ holder.favoriteStatus.setImageResource(R.drawable.ic_star_black_24dp);
} else {
- itemView.setBackgroundColor(getResources().getColor(android.R.color.transparent));
- secondaryText.setTextColor(getResources().getColor(android.R.color.secondary_text_light_nodisable));
+ holder.favoriteStatus.setImageResource(R.drawable.ic_star_border_black_24dp);
}
+ holder.favoriteStatus.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ boolean isFavoriteAccount = mAccountsDbAdapter.isFavoriteAccount(accountUID);
+
+ ContentValues contentValues = new ContentValues();
+ contentValues.put(DatabaseSchema.AccountEntry.COLUMN_FAVORITE, !isFavoriteAccount);
+ mAccountsDbAdapter.updateRecord(accountUID, contentValues);
+
+ int drawableResource = !isFavoriteAccount ?
+ R.drawable.ic_star_black_24dp : R.drawable.ic_star_border_black_24dp;
+ holder.favoriteStatus.setImageResource(drawableResource);
+ if (mDisplayMode == DisplayMode.FAVORITES)
+ refresh();
+ }
+ });
- //increase the touch target area for the add new transaction button
-
- final View addTransactionButton = itemView.findViewById(R.id.btn_new_transaction);
- final View parentView = itemView;
- parentView.post(new Runnable() {
+ holder.itemView.setOnClickListener(new View.OnClickListener() {
@Override
- public void run() {
- if (isAdded()){ //may be run when fragment has been unbound from activity
- final android.graphics.Rect hitRect = new Rect();
- float extraPadding = getResources().getDimension(R.dimen.edge_padding);
- addTransactionButton.getHitRect(hitRect);
- hitRect.right += extraPadding;
- hitRect.bottom += extraPadding;
- hitRect.top -= extraPadding;
- hitRect.left -= extraPadding;
- parentView.setTouchDelegate(new TouchDelegate(hitRect, addTransactionButton));
- }
+ public void onClick(View v) {
+ onListItemClick(accountUID);
}
});
+ }
+
+
+ class AccountViewHolder extends RecyclerView.ViewHolder implements PopupMenu.OnMenuItemClickListener{
+ @Bind(R.id.primary_text) TextView accountName;
+ @Bind(R.id.secondary_text) TextView description;
+ @Bind(R.id.account_balance) TextView accountBalance;
+ @Bind(R.id.create_transaction) ImageView createTransaction;
+ @Bind(R.id.favorite_status) ImageView favoriteStatus;
+ @Bind(R.id.options_menu) ImageView optionsMenu;
+ @Bind(R.id.account_color_strip) View colorStripView;
+ long accoundId;
+
+ public AccountViewHolder(View itemView) {
+ super(itemView);
+ ButterKnife.bind(this, itemView);
+
+ optionsMenu.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ PopupMenu popup = new PopupMenu(getActivity(), v);
+ popup.setOnMenuItemClickListener(AccountViewHolder.this);
+ MenuInflater inflater = popup.getMenuInflater();
+ inflater.inflate(R.menu.account_context_menu, popup.getMenu());
+ popup.show();
+ }
+ });
+
+ }
- return itemView;
+
+ @Override
+ public boolean onMenuItemClick(MenuItem item) {
+ switch (item.getItemId()){
+ case R.id.context_menu_edit_accounts:
+ openCreateOrEditActivity(accoundId);
+ return true;
+
+ case R.id.context_menu_delete:
+ tryDeleteAccount(accoundId);
+ return true;
+
+ default:
+ return false;
+ }
+ }
}
}
-
}
diff --git a/app/src/main/java/org/gnucash/android/ui/account/DeleteAccountDialogFragment.java b/app/src/main/java/org/gnucash/android/ui/account/DeleteAccountDialogFragment.java
index ef70aab87..91663c3c2 100644
--- a/app/src/main/java/org/gnucash/android/ui/account/DeleteAccountDialogFragment.java
+++ b/app/src/main/java/org/gnucash/android/ui/account/DeleteAccountDialogFragment.java
@@ -18,6 +18,7 @@
import android.database.Cursor;
import android.os.Bundle;
import android.support.annotation.Nullable;
+import android.support.v4.app.DialogFragment;
import android.support.v4.widget.SimpleCursorAdapter;
import android.text.TextUtils;
import android.view.LayoutInflater;
@@ -29,8 +30,6 @@
import android.widget.Spinner;
import android.widget.TextView;
-import com.actionbarsherlock.app.SherlockDialogFragment;
-
import org.gnucash.android.R;
import org.gnucash.android.app.GnuCashApplication;
import org.gnucash.android.db.AccountsDbAdapter;
@@ -39,7 +38,7 @@
import org.gnucash.android.db.TransactionsDbAdapter;
import org.gnucash.android.model.AccountType;
import org.gnucash.android.ui.util.Refreshable;
-import org.gnucash.android.ui.widget.WidgetConfigurationActivity;
+import org.gnucash.android.ui.homescreen.WidgetConfigurationActivity;
import org.gnucash.android.util.QualifiedAccountNameCursorAdapter;
import java.util.Currency;
@@ -53,7 +52,7 @@
*
* @author Ngewi Fet
*/
-public class DeleteAccountDialogFragment extends SherlockDialogFragment {
+public class DeleteAccountDialogFragment extends DialogFragment {
/**
* Spinner for selecting the account to move the transactions to
@@ -125,7 +124,6 @@ public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container,
mCancelButton = (Button) view.findViewById(R.id.btn_cancel);
mOkButton = (Button) view.findViewById(R.id.btn_save);
mOkButton.setText(R.string.alert_dialog_ok_delete);
- mOkButton.setCompoundDrawablesWithIntrinsicBounds(R.drawable.content_discard_holo_light,0,0,0);
return view;
}
@@ -149,9 +147,7 @@ public void onActivityCreated(Bundle savedInstanceState) {
Cursor cursor = accountsDbAdapter.fetchAccountsOrderedByFullName(transactionDeleteConditions,
new String[]{mOriginAccountUID, currencyCode, accountType.name()});
- SimpleCursorAdapter mCursorAdapter = new QualifiedAccountNameCursorAdapter(getActivity(),
- android.R.layout.simple_spinner_item, cursor);
- mCursorAdapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
+ SimpleCursorAdapter mCursorAdapter = new QualifiedAccountNameCursorAdapter(getActivity(), cursor);
mTransactionsDestinationAccountSpinner.setAdapter(mCursorAdapter);
//target accounts for transactions and accounts have different conditions
@@ -162,9 +158,7 @@ public void onActivityCreated(Bundle savedInstanceState) {
+ ")";
cursor = accountsDbAdapter.fetchAccountsOrderedByFullName(accountMoveConditions,
new String[]{mOriginAccountUID, currencyCode, accountType.name()});
- mCursorAdapter = new QualifiedAccountNameCursorAdapter(getActivity(),
- android.R.layout.simple_spinner_item, cursor);
- mCursorAdapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
+ mCursorAdapter = new QualifiedAccountNameCursorAdapter(getActivity(), cursor);
mAccountsDestinationAccountSpinner.setAdapter(mCursorAdapter);
setListeners();
diff --git a/app/src/main/java/org/gnucash/android/ui/chart/ChartDatePickerFragment.java b/app/src/main/java/org/gnucash/android/ui/chart/ChartDatePickerFragment.java
deleted file mode 100644
index 352a7f954..000000000
--- a/app/src/main/java/org/gnucash/android/ui/chart/ChartDatePickerFragment.java
+++ /dev/null
@@ -1,102 +0,0 @@
-/*
- * Copyright (c) 2015 Oleksandr Tyshkovets
- * Copyright (c) 2015 Ngewi Fet
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.gnucash.android.ui.chart;
-
-import android.app.DatePickerDialog;
-import android.app.DatePickerDialog.OnDateSetListener;
-import android.app.Dialog;
-import android.os.Build;
-import android.os.Bundle;
-import android.support.annotation.NonNull;
-import android.support.v4.app.DialogFragment;
-import android.util.Log;
-import android.view.View;
-import android.widget.DatePicker;
-
-import java.lang.reflect.Field;
-import java.util.Calendar;
-
-/**
- * Fragment for displaying a date picker dialog.
- * @author Oleksandr Tyshkovets
- * @author Ngewi Fet
- */
-public class ChartDatePickerFragment extends DialogFragment {
-
- private static final String TAG = ChartDatePickerFragment.class.getSimpleName();
-
- private OnDateSetListener callback;
- private Calendar mCalendar = Calendar.getInstance();
- private long minDate;
- private long maxDate;
-
- /**
- * Required for when the device is rotated while the dialog is open.
- * If this constructor is not present, the app will crash
- */
- public ChartDatePickerFragment() {}
-
- /**
- * Creates the date picker fragment without day field.
- * @param callback the listener to notify when the date is set and the dialog is closed
- * @param time the dialog init time in milliseconds
- * @param minDate the earliest allowed date
- * @param maxDate the latest allowed date
- */
- public static ChartDatePickerFragment newInstance(OnDateSetListener callback, long time, long minDate, long maxDate) {
- ChartDatePickerFragment chartDatePickerFragment = new ChartDatePickerFragment();
- chartDatePickerFragment.callback = callback;
- chartDatePickerFragment.mCalendar.setTimeInMillis(time);
- chartDatePickerFragment.minDate = minDate;
- chartDatePickerFragment.maxDate = maxDate;
- return chartDatePickerFragment;
- }
-
- /**
- * {@inheritDoc}
- */
- @NonNull
- @Override
- public Dialog onCreateDialog(Bundle savedInstanceState) {
- DatePickerDialog dialog = new DatePickerDialog(getActivity(), callback,
- mCalendar.get(Calendar.YEAR), mCalendar.get(Calendar.MONTH), mCalendar.get(Calendar.DAY_OF_MONTH));
-
- try {
- Field datePickerField = dialog.getClass().getDeclaredField("mDatePicker");
- datePickerField.setAccessible(true);
- DatePicker datePicker = (DatePicker) datePickerField.get(dialog);
-
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB ) {
- datePicker.setMinDate(minDate);
- datePicker.setMaxDate(maxDate);
- }
-
- for (Field field : datePicker.getClass().getDeclaredFields()) {
- if (field.getName().equals("mDaySpinner") || field.getName().equals("mDayPicker")) {
- field.setAccessible(true);
- ((View) field.get(datePicker)).setVisibility(View.GONE);
- }
- }
- } catch (Exception e) {
- Log.w(TAG, e.getMessage());
- }
-
- return dialog;
- }
-
-}
diff --git a/app/src/main/java/org/gnucash/android/ui/chart/ChartReportActivity.java b/app/src/main/java/org/gnucash/android/ui/chart/ChartReportActivity.java
deleted file mode 100644
index bbfef97d9..000000000
--- a/app/src/main/java/org/gnucash/android/ui/chart/ChartReportActivity.java
+++ /dev/null
@@ -1,106 +0,0 @@
-/*
- * Copyright (c) 2015 Oleksandr Tyshkovets
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.gnucash.android.ui.chart;
-
-import android.content.Intent;
-import android.os.Bundle;
-import android.preference.PreferenceManager;
-import android.view.View;
-import android.widget.AdapterView;
-import android.widget.ArrayAdapter;
-import android.widget.Spinner;
-
-import org.gnucash.android.R;
-import org.gnucash.android.db.AccountsDbAdapter;
-import org.gnucash.android.model.Money;
-import org.gnucash.android.ui.passcode.PassLockActivity;
-
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Currency;
-import java.util.List;
-
-/**
- * Allows to select chart by type
- *
- * @author Oleksandr Tyshkovets
- */
-public class ChartReportActivity extends PassLockActivity {
-
- @Override
- protected void onCreate(Bundle savedInstanceState) {
- //it is necessary to set the view first before calling super because of the nav drawer in BaseDrawerActivity
- setContentView(R.layout.activity_chart_report);
- super.onCreate(savedInstanceState);
- getSupportActionBar().setTitle(R.string.title_reports);
-
- final List allCurrencyCodes = Arrays.asList(getResources().getStringArray(R.array.key_currency_codes));
- final List allCurrencyNames = Arrays.asList(getResources().getStringArray(R.array.currency_names));
-
- Currency preferredCurrency = Currency.getInstance(PreferenceManager
- .getDefaultSharedPreferences(getApplicationContext())
- .getString(getString(R.string.key_report_currency), Money.DEFAULT_CURRENCY_CODE));
- List currencies = AccountsDbAdapter.getInstance().getCurrencies();
- if (currencies.remove(preferredCurrency)) {
- currencies.add(0, preferredCurrency);
- }
- List currencyNames = new ArrayList<>();
- for (Currency currency : currencies) {
- currencyNames.add(allCurrencyNames.get(allCurrencyCodes.indexOf(currency.getCurrencyCode())));
- }
-
- Spinner spinner = (Spinner) findViewById(R.id.report_currency_spinner);
- ArrayAdapter dataAdapter = new ArrayAdapter<>(this, android.R.layout.simple_spinner_item, currencyNames);
- dataAdapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
- spinner.setAdapter(dataAdapter);
- spinner.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
- @Override
- public void onItemSelected(AdapterView> adapterView, View view, int i, long l) {
- String currencyName = (String) ((Spinner) findViewById(R.id.report_currency_spinner)).getSelectedItem();
- PreferenceManager.getDefaultSharedPreferences(getApplicationContext())
- .edit()
- .putString(getString(R.string.key_report_currency), allCurrencyCodes.get(allCurrencyNames.indexOf(currencyName)))
- .commit();
- }
-
- @Override
- public void onNothingSelected(AdapterView> adapterView) {
- }
- });
-
- findViewById(R.id.pie_chart_button).setOnClickListener(new View.OnClickListener() {
- @Override
- public void onClick(View view) {
- startActivity(new Intent(view.getContext(), PieChartActivity.class));
- }
- });
- findViewById(R.id.line_chart_button).setOnClickListener(new View.OnClickListener() {
- @Override
- public void onClick(View view) {
- startActivity(new Intent(view.getContext(), LineChartActivity.class));
- }
- });
- findViewById(R.id.bar_chart_button).setOnClickListener(new View.OnClickListener() {
- @Override
- public void onClick(View view) {
- startActivity(new Intent(view.getContext(), BarChartActivity.class));
- }
- });
-
- }
-
-}
diff --git a/app/src/main/java/org/gnucash/android/ui/chart/PieChartActivity.java b/app/src/main/java/org/gnucash/android/ui/chart/PieChartActivity.java
deleted file mode 100644
index 20f54acdd..000000000
--- a/app/src/main/java/org/gnucash/android/ui/chart/PieChartActivity.java
+++ /dev/null
@@ -1,445 +0,0 @@
-/*
- * Copyright (c) 2014-2015 Oleksandr Tyshkovets
- * Copyright (c) 2015 Ngewi Fet
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.gnucash.android.ui.chart;
-
-import android.app.DatePickerDialog;
-import android.graphics.Color;
-import android.graphics.PorterDuff;
-import android.graphics.drawable.Drawable;
-import android.os.Bundle;
-import android.preference.PreferenceManager;
-import android.support.v4.app.DialogFragment;
-import android.view.View;
-import android.widget.AdapterView;
-import android.widget.AdapterView.OnItemSelectedListener;
-import android.widget.ArrayAdapter;
-import android.widget.DatePicker;
-import android.widget.ImageButton;
-import android.widget.Spinner;
-import android.widget.TextView;
-
-import com.actionbarsherlock.view.Menu;
-import com.actionbarsherlock.view.MenuItem;
-import com.github.mikephil.charting.charts.PieChart;
-import com.github.mikephil.charting.components.Legend.LegendForm;
-import com.github.mikephil.charting.components.Legend.LegendPosition;
-import com.github.mikephil.charting.data.Entry;
-import com.github.mikephil.charting.data.PieData;
-import com.github.mikephil.charting.data.PieDataSet;
-import com.github.mikephil.charting.listener.OnChartValueSelectedListener;
-import com.github.mikephil.charting.utils.Highlight;
-
-import org.gnucash.android.R;
-import org.gnucash.android.db.AccountsDbAdapter;
-import org.gnucash.android.db.TransactionsDbAdapter;
-import org.gnucash.android.model.Account;
-import org.gnucash.android.model.AccountType;
-import org.gnucash.android.model.Money;
-import org.gnucash.android.ui.passcode.PassLockActivity;
-import org.joda.time.LocalDateTime;
-
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Currency;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Locale;
-import java.util.Map;
-
-import static org.gnucash.android.db.DatabaseSchema.AccountEntry;
-
-/**
- * Activity used for drawing a pie chart
- *
- * @author Oleksandr Tyshkovets
- * @author Ngewi Fet
- */
-public class PieChartActivity extends PassLockActivity implements OnChartValueSelectedListener, DatePickerDialog.OnDateSetListener {
-
- private static final int[] COLORS = {
- Color.parseColor("#17ee4e"), Color.parseColor("#cc1f09"), Color.parseColor("#3940f7"),
- Color.parseColor("#f9cd04"), Color.parseColor("#5f33a8"), Color.parseColor("#e005b6"),
- Color.parseColor("#17d6ed"), Color.parseColor("#e4a9a2"), Color.parseColor("#8fe6cd"),
- Color.parseColor("#8b48fb"), Color.parseColor("#343a36"), Color.parseColor("#6decb1"),
- Color.parseColor("#a6dcfd"), Color.parseColor("#5c3378"), Color.parseColor("#a6dcfd"),
- Color.parseColor("#ba037c"), Color.parseColor("#708809"), Color.parseColor("#32072c"),
- Color.parseColor("#fddef8"), Color.parseColor("#fa0e6e"), Color.parseColor("#d9e7b5")
- };
-
- private static final String DATE_PATTERN = "MMMM\nYYYY";
- private static final String TOTAL_VALUE_LABEL_PATTERN = "%s\n%.2f %s";
- private static final int ANIMATION_DURATION = 1800;
-
- private PieChart mChart;
-
- private LocalDateTime mChartDate = new LocalDateTime();
- private TextView mChartDateTextView;
-
- private ImageButton mPreviousMonthButton;
- private ImageButton mNextMonthButton;
-
- private AccountsDbAdapter mAccountsDbAdapter;
- private TransactionsDbAdapter mTransactionsDbAdapter;
-
- private LocalDateTime mEarliestTransactionDate;
- private LocalDateTime mLatestTransactionDate;
-
- private AccountType mAccountType = AccountType.EXPENSE;
-
- private boolean mChartDataPresent = true;
-
- private boolean mUseAccountColor = true;
-
- private double mSlicePercentThreshold = 6;
-
- private String mCurrencyCode;
-
- @Override
- protected void onCreate(Bundle savedInstanceState) {
- //it is necessary to set the view first before calling super because of the nav drawer in BaseDrawerActivity
- setContentView(R.layout.activity_pie_chart);
- super.onCreate(savedInstanceState);
- getSupportActionBar().setTitle(R.string.title_pie_chart);
-
- mUseAccountColor = PreferenceManager.getDefaultSharedPreferences(getApplicationContext())
- .getBoolean(getString(R.string.key_use_account_color), false);
-
- mPreviousMonthButton = (ImageButton) findViewById(R.id.previous_month_chart_button);
- mNextMonthButton = (ImageButton) findViewById(R.id.next_month_chart_button);
- mChartDateTextView = (TextView) findViewById(R.id.chart_date);
-
- mAccountsDbAdapter = AccountsDbAdapter.getInstance();
- mTransactionsDbAdapter = TransactionsDbAdapter.getInstance();
-
- mCurrencyCode = PreferenceManager.getDefaultSharedPreferences(this)
- .getString(getString(R.string.key_report_currency), Money.DEFAULT_CURRENCY_CODE);
-
- mChart = (PieChart) findViewById(R.id.pie_chart);
- mChart.setCenterTextSize(18);
- mChart.setDescription("");
- mChart.getLegend().setEnabled(false);
- mChart.setOnChartValueSelectedListener(this);
-
- setUpSpinner();
-
- mPreviousMonthButton.setOnClickListener(new View.OnClickListener() {
-
- @Override
- public void onClick(View view) {
- mChartDate = mChartDate.minusMonths(1);
- setData(true);
- }
- });
- mNextMonthButton.setOnClickListener(new View.OnClickListener() {
-
- @Override
- public void onClick(View view) {
- mChartDate = mChartDate.plusMonths(1);
- setData(true);
- }
- });
-
- mChartDateTextView.setOnClickListener(new View.OnClickListener() {
-
- @Override
- public void onClick(View view) {
- DialogFragment newFragment = ChartDatePickerFragment.newInstance(PieChartActivity.this,
- mChartDate.toDate().getTime(),
- mEarliestTransactionDate.toDate().getTime(),
- mLatestTransactionDate.toDate().getTime());
- newFragment.show(getSupportFragmentManager(), "date_dialog");
- }
- });
- }
-
- /**
- * Sets the chart data
- * @param forCurrentMonth sets data only for current month if {@code true}, otherwise for all time
- */
- private void setData(boolean forCurrentMonth) {
- mChartDateTextView.setText(forCurrentMonth ? mChartDate.toString(DATE_PATTERN) : getResources().getString(R.string.label_chart_overall));
- ((TextView) findViewById(R.id.selected_chart_slice)).setText("");
- mChart.highlightValues(null);
- mChart.clear();
-
- mChart.setData(getData(forCurrentMonth));
- if (mChartDataPresent) {
- mChart.animateXY(ANIMATION_DURATION, ANIMATION_DURATION);
- }
- mChart.invalidate();
-
- mChartDateTextView.setEnabled(mChartDataPresent);
- setImageButtonEnabled(mNextMonthButton,
- mChartDate.plusMonths(1).dayOfMonth().withMinimumValue().withMillisOfDay(0).isBefore(mLatestTransactionDate));
- setImageButtonEnabled(mPreviousMonthButton, (mEarliestTransactionDate.getYear() != 1970
- && mChartDate.minusMonths(1).dayOfMonth().withMaximumValue().withMillisOfDay(86399999).isAfter(mEarliestTransactionDate)));
- }
-
- /**
- * Returns {@code PieData} instance with data entries and labels
- * @param forCurrentMonth sets data only for current month if {@code true}, otherwise for all time
- * @return {@code PieData} instance
- */
- private PieData getData(boolean forCurrentMonth) {
- List accountList = mAccountsDbAdapter.getSimpleAccountList(
- AccountEntry.COLUMN_TYPE + " = ? AND " + AccountEntry.COLUMN_PLACEHOLDER + " = ?",
- new String[]{ mAccountType.name(), "0" }, null);
- List uidList = new ArrayList<>();
- for (Account account : accountList) {
- uidList.add(account.getUID());
- }
- double sum;
- if (forCurrentMonth) {
- long start = mChartDate.dayOfMonth().withMinimumValue().millisOfDay().withMinimumValue().toDate().getTime();
- long end = mChartDate.dayOfMonth().withMaximumValue().millisOfDay().withMaximumValue().toDate().getTime();
- sum = mAccountsDbAdapter.getAccountsBalance(uidList, start, end).absolute().asDouble();
- } else {
- sum = mAccountsDbAdapter.getAccountsBalance(uidList, -1, -1).absolute().asDouble();
- }
-
- double otherSlice = 0;
- PieDataSet dataSet = new PieDataSet(null, "");
- List names = new ArrayList<>();
- List skipUUID = new ArrayList<>();
- for (Account account : getCurrencyCodeToAccountMap(accountList).get(mCurrencyCode)) {
- if (mAccountsDbAdapter.getSubAccountCount(account.getUID()) > 0) {
- skipUUID.addAll(mAccountsDbAdapter.getDescendantAccountUIDs(account.getUID(), null, null));
- }
- if (!skipUUID.contains(account.getUID())) {
- double balance;
- if (forCurrentMonth) {
- long start = mChartDate.dayOfMonth().withMinimumValue().millisOfDay().withMinimumValue().toDate().getTime();
- long end = mChartDate.dayOfMonth().withMaximumValue().millisOfDay().withMaximumValue().toDate().getTime();
- balance = mAccountsDbAdapter.getAccountBalance(account.getUID(), start, end).absolute().asDouble();
- } else {
- balance = mAccountsDbAdapter.getAccountBalance(account.getUID()).absolute().asDouble();
- }
-
- if (balance / sum * 100 > mSlicePercentThreshold) {
- dataSet.addEntry(new Entry((float) balance, dataSet.getEntryCount()));
- if (mUseAccountColor) {
- dataSet.getColors().set(dataSet.getColors().size() - 1, (account.getColorHexCode() != null)
- ? Color.parseColor(account.getColorHexCode())
- : COLORS[(dataSet.getEntryCount() - 1) % COLORS.length]);
- }
- dataSet.addColor(COLORS[(dataSet.getEntryCount() - 1) % COLORS.length]);
- names.add(account.getName());
- } else {
- otherSlice += balance;
- }
- }
- }
- if (otherSlice > 0) {
- dataSet.addEntry(new Entry((float) otherSlice, dataSet.getEntryCount()));
- dataSet.getColors().set(dataSet.getColors().size() - 1, Color.LTGRAY);
- names.add(getResources().getString(R.string.label_other_slice));
- }
-
- if (dataSet.getEntryCount() == 0) {
- mChartDataPresent = false;
- dataSet.addEntry(new Entry(1, 0));
- dataSet.setColor(Color.LTGRAY);
- dataSet.setDrawValues(false);
- names.add("");
- mChart.setCenterText(getResources().getString(R.string.label_chart_no_data));
- mChart.setTouchEnabled(false);
- } else {
- mChartDataPresent = true;
- dataSet.setSliceSpace(2);
- mChart.setCenterText(String.format(TOTAL_VALUE_LABEL_PATTERN,
- getResources().getString(R.string.label_chart_total),
- dataSet.getYValueSum(),
- Currency.getInstance(mCurrencyCode).getSymbol(Locale.getDefault()))
- );
- mChart.setTouchEnabled(true);
- }
-
- return new PieData(names, dataSet);
- }
-
- /**
- * Returns a map with a currency code as key and corresponding accounts list
- * as value from a specified list of accounts
- * @param accountList a list of accounts
- * @return a map with a currency code as key and corresponding accounts list as value
- */
- private Map> getCurrencyCodeToAccountMap(List accountList) {
- Map> currencyAccountMap = new HashMap<>();
- for (Currency currency : mAccountsDbAdapter.getCurrencies()) {
- currencyAccountMap.put(currency.getCurrencyCode(), new ArrayList());
- }
-
- for (Account account : accountList) {
- currencyAccountMap.get(account.getCurrency().getCurrencyCode()).add(account);
- }
- return currencyAccountMap;
- }
-
-
- /**
- * Sets the image button to the given state and grays-out the icon
- *
- * @param enabled the button's state
- * @param button the button item to modify
- */
- private void setImageButtonEnabled(ImageButton button, boolean enabled) {
- button.setEnabled(enabled);
- Drawable originalIcon = button.getDrawable();
- if (enabled) {
- originalIcon.clearColorFilter();
- } else {
- originalIcon.setColorFilter(Color.GRAY, PorterDuff.Mode.SRC_IN);
- }
- button.setImageDrawable(originalIcon);
- }
-
- /**
- * Sorts the pie's slices in ascending order
- */
- private void bubbleSort() {
- List labels = mChart.getData().getXVals();
- List values = mChart.getData().getDataSet().getYVals();
- List colors = mChart.getData().getDataSet().getColors();
- float tmp1;
- String tmp2;
- Integer tmp3;
- for(int i = 0; i < values.size() - 1; i++) {
- for(int j = 1; j < values.size() - i; j++) {
- if (values.get(j-1).getVal() > values.get(j).getVal()) {
- tmp1 = values.get(j - 1).getVal();
- values.get(j - 1).setVal(values.get(j).getVal());
- values.get(j).setVal(tmp1);
-
- tmp2 = labels.get(j - 1);
- labels.set(j - 1, labels.get(j));
- labels.set(j, tmp2);
-
- tmp3 = colors.get(j - 1);
- colors.set(j - 1, colors.get(j));
- colors.set(j, tmp3);
- }
- }
- }
-
- mChart.notifyDataSetChanged();
- mChart.highlightValues(null);
- mChart.invalidate();
- }
-
- /**
- * Sets up settings and data for the account type spinner. Currently used only {@code EXPENSE} and {@code INCOME}
- * account types.
- */
- private void setUpSpinner() {
- Spinner spinner = (Spinner) findViewById(R.id.chart_data_spinner);
- ArrayAdapter dataAdapter = new ArrayAdapter<>(this,
- android.R.layout.simple_spinner_item,
- Arrays.asList(AccountType.EXPENSE, AccountType.INCOME));
- dataAdapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
- spinner.setAdapter(dataAdapter);
- spinner.setOnItemSelectedListener(new OnItemSelectedListener() {
- @Override
- public void onItemSelected(AdapterView> adapterView, View view, int i, long l) {
- mAccountType = (AccountType) ((Spinner) findViewById(R.id.chart_data_spinner)).getSelectedItem();
- mEarliestTransactionDate = new LocalDateTime(mTransactionsDbAdapter.getTimestampOfEarliestTransaction(mAccountType, mCurrencyCode));
- mLatestTransactionDate = new LocalDateTime(mTransactionsDbAdapter.getTimestampOfLatestTransaction(mAccountType, mCurrencyCode));
- mChartDate = mLatestTransactionDate;
- setData(false);
- }
-
- @Override
- public void onNothingSelected(AdapterView> adapterView) {}
- });
- }
-
- @Override
- public boolean onCreateOptionsMenu(Menu menu) {
- getSupportMenuInflater().inflate(R.menu.chart_actions, menu);
- return true;
- }
-
- @Override
- public boolean onPrepareOptionsMenu(Menu menu) {
- menu.findItem(R.id.menu_order_by_size).setVisible(mChartDataPresent);
- menu.findItem(R.id.menu_toggle_labels).setVisible(mChartDataPresent);
- menu.findItem(R.id.menu_group_other_slice).setVisible(mChartDataPresent);
- // hide line/bar chart specific menu items
- menu.findItem(R.id.menu_percentage_mode).setVisible(false);
- menu.findItem(R.id.menu_toggle_average_lines).setVisible(false);
- return true;
- }
-
- @Override
- public boolean onOptionsItemSelected(MenuItem item) {
- switch (item.getItemId()) {
- case R.id.menu_order_by_size: {
- bubbleSort();
- break;
- }
- case R.id.menu_toggle_legend: {
- mChart.getLegend().setEnabled(!mChart.getLegend().isEnabled());
- mChart.getLegend().setForm(LegendForm.CIRCLE);
- mChart.getLegend().setPosition(LegendPosition.RIGHT_OF_CHART_CENTER);
- mChart.notifyDataSetChanged();
- mChart.invalidate();
- break;
- }
- case R.id.menu_toggle_labels: {
- mChart.getData().setDrawValues(!mChart.isDrawSliceTextEnabled());
- mChart.setDrawSliceText(!mChart.isDrawSliceTextEnabled());
- mChart.invalidate();
- break;
- }
- case R.id.menu_group_other_slice: {
- mSlicePercentThreshold = Math.abs(mSlicePercentThreshold - 6);
- setData(false);
- break;
- }
- case android.R.id.home: {
- finish();
- break;
- }
- }
- return true;
- }
-
- /**
- * Since JellyBean, the onDateSet() method of the DatePicker class is called twice i.e. once when
- * OK button is pressed and then when the DatePickerDialog is dismissed. It is a known bug.
- */
- @Override
- public void onDateSet(DatePicker view, int year, int monthOfYear, int dayOfMonth) {
- if (view.isShown()) {
- mChartDate = new LocalDateTime(year, monthOfYear + 1, dayOfMonth, 0, 0);
- setData(true);
- }
- }
-
- @Override
- public void onValueSelected(Entry e, int dataSetIndex, Highlight h) {
- if (e == null) return;
- ((TextView) findViewById(R.id.selected_chart_slice))
- .setText(mChart.getData().getXVals().get(e.getXIndex()) + " - " + e.getVal()
- + " (" + String.format("%.2f", (e.getVal() / mChart.getYValueSum()) * 100) + " %)");
- }
-
- @Override
- public void onNothingSelected() {
- ((TextView) findViewById(R.id.selected_chart_slice)).setText("");
- }
-}
diff --git a/app/src/main/java/org/gnucash/android/ui/colorpicker/ColorPickerDialog.java b/app/src/main/java/org/gnucash/android/ui/colorpicker/ColorPickerDialog.java
index 4e3f4495c..97e826eda 100644
--- a/app/src/main/java/org/gnucash/android/ui/colorpicker/ColorPickerDialog.java
+++ b/app/src/main/java/org/gnucash/android/ui/colorpicker/ColorPickerDialog.java
@@ -20,12 +20,11 @@
import android.app.AlertDialog;
import android.app.Dialog;
import android.os.Bundle;
+import android.support.v4.app.DialogFragment;
import android.view.LayoutInflater;
import android.view.View;
import android.widget.ProgressBar;
-import com.actionbarsherlock.app.SherlockDialogFragment;
-
import org.gnucash.android.R;
import org.gnucash.android.ui.colorpicker.ColorPickerSwatch.OnColorSelectedListener;
@@ -33,7 +32,7 @@
* A dialog which takes in as input an array of colors and creates a palette allowing the user to
* select a specific color swatch, which invokes a listener.
*/
-public class ColorPickerDialog extends SherlockDialogFragment implements OnColorSelectedListener {
+public class ColorPickerDialog extends DialogFragment implements OnColorSelectedListener {
public static final int SIZE_LARGE = 1;
public static final int SIZE_SMALL = 2;
diff --git a/app/src/main/java/org/gnucash/android/ui/common/BaseDrawerActivity.java b/app/src/main/java/org/gnucash/android/ui/common/BaseDrawerActivity.java
new file mode 100644
index 000000000..49ffe5adf
--- /dev/null
+++ b/app/src/main/java/org/gnucash/android/ui/common/BaseDrawerActivity.java
@@ -0,0 +1,193 @@
+/*
+ * Copyright (c) 2015 Ngewi Fet
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.gnucash.android.ui.common;
+
+import android.app.Activity;
+import android.content.Intent;
+import android.content.SharedPreferences;
+import android.content.res.Configuration;
+import android.os.Bundle;
+import android.preference.PreferenceManager;
+import android.support.design.widget.NavigationView;
+import android.support.v4.widget.DrawerLayout;
+import android.support.v7.app.ActionBar;
+import android.support.v7.app.ActionBarDrawerToggle;
+import android.view.MenuItem;
+import android.view.View;
+
+import com.uservoice.uservoicesdk.UserVoice;
+
+import org.gnucash.android.R;
+import org.gnucash.android.ui.account.AccountsActivity;
+import org.gnucash.android.ui.passcode.PasscodeLockActivity;
+import org.gnucash.android.ui.report.ReportsActivity;
+import org.gnucash.android.ui.settings.SettingsActivity;
+import org.gnucash.android.ui.transaction.ScheduledActionsActivity;
+
+
+/**
+ * Base activity implementing the navigation drawer, to be extended by all activities requiring one
+ * All subclasses should call the {@link #setUpDrawer()} method in {@link #onCreate(Bundle)}, after the
+ * activity layout has been set.
+ * The activity layout of the subclass is expected to contain {@code DrawerLayout} and a {@code NavigationView}
+ *
+ * @author Ngewi Fet
+ */
+public class BaseDrawerActivity extends PasscodeLockActivity {
+ protected DrawerLayout mDrawerLayout;
+ protected NavigationView mNavigationView;
+
+ protected ActionBarDrawerToggle mDrawerToggle;
+
+ private class DrawerItemClickListener implements NavigationView.OnNavigationItemSelectedListener {
+
+ @Override
+ public boolean onNavigationItemSelected(MenuItem menuItem) {
+ onDrawerMenuItemClicked(menuItem.getItemId());
+ return true;
+ }
+
+ }
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ }
+
+ /**
+ * Sets up the navigation drawer for this activity.
+ *
+ * This should be called from the activity's
+ * {@link Activity#onCreate(Bundle)} method after calling
+ * {@link Activity#setContentView(int)}.
+ *
+ */
+ protected void setUpDrawer() {
+ final ActionBar actionBar = getSupportActionBar();
+ if (actionBar != null){
+ actionBar.setHomeButtonEnabled(true);
+ actionBar.setDisplayHomeAsUpEnabled(true);
+
+ }
+ mDrawerLayout = (DrawerLayout) findViewById(R.id.drawer_layout);
+ mNavigationView = (NavigationView) findViewById(R.id.nav_view);
+
+ mNavigationView.setNavigationItemSelectedListener(new DrawerItemClickListener());
+
+ mDrawerToggle = new ActionBarDrawerToggle(
+ this, /* host Activity */
+ mDrawerLayout, /* DrawerLayout object */
+ R.string.drawer_open, /* "open drawer" description */
+ R.string.drawer_close /* "close drawer" description */
+ ) {
+
+ /** Called when a drawer has settled in a completely closed state. */
+ public void onDrawerClosed(View view) {
+ super.onDrawerClosed(view);
+ }
+
+ /** Called when a drawer has settled in a completely open state. */
+ public void onDrawerOpened(View drawerView) {
+ super.onDrawerOpened(drawerView);
+ }
+ };
+
+ mDrawerLayout.setDrawerListener(mDrawerToggle);
+ }
+ @Override
+ protected void onPostCreate(Bundle savedInstanceState) {
+ super.onPostCreate(savedInstanceState);
+ mDrawerToggle.syncState();
+ }
+
+ @Override
+ public void onConfigurationChanged(Configuration newConfig) {
+ super.onConfigurationChanged(newConfig);
+ mDrawerToggle.onConfigurationChanged(newConfig);
+ }
+
+ @Override
+ public boolean onOptionsItemSelected(MenuItem item) {
+ if (!mDrawerLayout.isDrawerOpen(mNavigationView))
+ mDrawerLayout.openDrawer(mNavigationView);
+ else
+ mDrawerLayout.closeDrawer(mNavigationView);
+
+ return super.onOptionsItemSelected(item);
+ }
+
+ /**
+ * Handler for the navigation drawer items
+ * */
+ protected void onDrawerMenuItemClicked(int itemId) {
+ switch (itemId){
+ case R.id.nav_item_open: { //Open... files
+ AccountsActivity.startXmlFileChooser(this);
+ }
+ break;
+
+ case R.id.nav_item_favorites: { //favorite accounts
+ Intent intent = new Intent(this, AccountsActivity.class);
+ intent.putExtra(AccountsActivity.EXTRA_TAB_INDEX,
+ AccountsActivity.INDEX_FAVORITE_ACCOUNTS_FRAGMENT);
+ intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP|Intent.FLAG_ACTIVITY_SINGLE_TOP);
+ startActivity(intent);
+ }
+ break;
+
+ case R.id.nav_item_reports:
+ startActivity(new Intent(this, ReportsActivity.class));
+ break;
+
+ case R.id.nav_item_scheduled_actions: { //show scheduled transactions
+ Intent intent = new Intent(this, ScheduledActionsActivity.class);
+ intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_SINGLE_TOP);
+ startActivity(intent);
+ }
+ break;
+
+ case R.id.nav_item_export:{
+ AccountsActivity.openExportFragment(this);
+ }
+ break;
+
+ case R.id.nav_item_settings: //Settings activity
+ startActivity(new Intent(this, SettingsActivity.class));
+ break;
+
+ case R.id.nav_item_help:
+ SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this);
+ prefs.edit().putBoolean(UxArgument.SKIP_PASSCODE_SCREEN, true).apply();
+ UserVoice.launchUserVoice(this);
+ break;
+ }
+ mDrawerLayout.closeDrawer(mNavigationView);
+ }
+
+ @Override
+ protected void onActivityResult(int requestCode, int resultCode, Intent data) {
+ if (resultCode == Activity.RESULT_CANCELED){
+ return;
+ }
+
+ switch (requestCode) {
+ case AccountsActivity.REQUEST_PICK_ACCOUNTS_FILE:
+ AccountsActivity.importXmlFileFromIntent(this, data);
+ break;
+ }
+ }
+
+}
diff --git a/app/src/main/java/org/gnucash/android/ui/common/FormActivity.java b/app/src/main/java/org/gnucash/android/ui/common/FormActivity.java
new file mode 100644
index 000000000..4af305350
--- /dev/null
+++ b/app/src/main/java/org/gnucash/android/ui/common/FormActivity.java
@@ -0,0 +1,196 @@
+/*
+ * Copyright (c) 2015 Ngewi Fet
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gnucash.android.ui.common;
+
+import android.content.Intent;
+import android.graphics.drawable.ColorDrawable;
+import android.os.Build;
+import android.os.Bundle;
+import android.support.v4.app.Fragment;
+import android.support.v4.app.FragmentManager;
+import android.support.v4.app.FragmentTransaction;
+import android.support.v7.widget.Toolbar;
+import android.view.MenuItem;
+
+import org.gnucash.android.R;
+import org.gnucash.android.app.GnuCashApplication;
+import org.gnucash.android.db.AccountsDbAdapter;
+import org.gnucash.android.ui.account.AccountFormFragment;
+import org.gnucash.android.ui.export.ExportFormFragment;
+import org.gnucash.android.ui.passcode.PasscodeLockActivity;
+import org.gnucash.android.ui.transaction.TransactionFormFragment;
+import org.gnucash.android.ui.transaction.SplitEditorFragment;
+import org.gnucash.android.ui.util.widget.CalculatorKeyboard;
+
+/**
+ * Activity for displaying forms in the application.
+ * The activity provides the standard close button, but it is up to the form fragments to display
+ * menu options (e.g. for saving etc)
+ * @author Ngewi Fet
+ */
+public class FormActivity extends PasscodeLockActivity {
+
+ private String mAccountUID;
+
+ private CalculatorKeyboard mOnBackListener;
+
+ public enum FormType {ACCOUNT, TRANSACTION, EXPORT, SPLIT_EDITOR}
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.activity_form);
+
+ Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
+ setSupportActionBar(toolbar);
+
+ android.support.v7.app.ActionBar actionBar = getSupportActionBar();
+ assert(actionBar != null);
+ actionBar.setHomeButtonEnabled(true);
+ actionBar.setDisplayHomeAsUpEnabled(true);
+ actionBar.setHomeAsUpIndicator(R.drawable.ic_close_white_24dp);
+
+ final Intent intent = getIntent();
+ String formtypeString = intent.getStringExtra(UxArgument.FORM_TYPE);
+ FormType formType = FormType.valueOf(formtypeString);
+
+ mAccountUID = intent.getStringExtra(UxArgument.SELECTED_ACCOUNT_UID);
+ if (mAccountUID == null){
+ mAccountUID = intent.getStringExtra(UxArgument.PARENT_ACCOUNT_UID);
+ }
+ if (mAccountUID != null) {
+ int colorCode = AccountsDbAdapter.getActiveAccountColorResource(mAccountUID);
+ actionBar.setBackgroundDrawable(new ColorDrawable(colorCode));
+ if (Build.VERSION.SDK_INT > 20)
+ getWindow().setStatusBarColor(GnuCashApplication.darken(colorCode));
+ }
+ switch (formType){
+ case ACCOUNT:
+ showAccountFormFragment(intent.getExtras());
+ break;
+
+ case TRANSACTION:
+ showTransactionFormFragment(intent.getExtras());
+ break;
+
+ case EXPORT:
+ showExportFormFragment(null);
+ break;
+
+ case SPLIT_EDITOR:
+ showSplitEditorFragment(intent.getExtras());
+ break;
+
+ default:
+ throw new IllegalArgumentException("No form display type specified");
+ }
+
+
+ }
+
+ @Override
+ public boolean onOptionsItemSelected(MenuItem item) {
+ switch (item.getItemId()){
+ case android.R.id.home:
+ setResult(RESULT_CANCELED);
+ finish();
+ return true;
+ }
+
+ return super.onOptionsItemSelected(item);
+ }
+
+ /**
+ * Return the GUID of the account for which the form is displayed.
+ * If the form is a transaction form, the transaction is created within that account. If it is
+ * an account form, then the GUID is the parent account
+ * @return GUID of account
+ */
+ public String getCurrentAccountUID() {
+ return mAccountUID;
+ }
+
+ /**
+ * Shows the form for creating/editing accounts
+ * @param args Arguments to use for initializing the form.
+ * This could be an account to edit or a preset for the parent account
+ */
+ private void showAccountFormFragment(Bundle args){
+ AccountFormFragment accountFormFragment = AccountFormFragment.newInstance();
+ accountFormFragment.setArguments(args);
+ showFormFragment(accountFormFragment);
+ }
+
+ /**
+ * Loads the transaction insert/edit fragment and passes the arguments
+ * @param args Bundle arguments to be passed to the fragment
+ */
+ private void showTransactionFormFragment(Bundle args){
+ TransactionFormFragment transactionFormFragment = new TransactionFormFragment();
+ transactionFormFragment.setArguments(args);
+ showFormFragment(transactionFormFragment);
+ }
+
+ /**
+ * Loads the export form fragment and passes the arguments
+ * @param args Bundle arguments
+ */
+ private void showExportFormFragment(Bundle args){
+ ExportFormFragment exportFragment = new ExportFormFragment();
+ exportFragment.setArguments(args);
+ showFormFragment(exportFragment);
+ }
+
+ /**
+ * Load the split editor fragment
+ * @param args View arguments
+ */
+ private void showSplitEditorFragment(Bundle args){
+ SplitEditorFragment splitEditor = SplitEditorFragment.newInstance(args);
+ showFormFragment(splitEditor);
+ }
+
+ /**
+ * Loads the fragment into the fragment container, replacing whatever was there before
+ * @param fragment Fragment to be displayed
+ */
+ private void showFormFragment(Fragment fragment){
+ FragmentManager fragmentManager = getSupportFragmentManager();
+ FragmentTransaction fragmentTransaction = fragmentManager
+ .beginTransaction();
+
+ fragmentTransaction.add(R.id.fragment_container, fragment);
+ fragmentTransaction.commit();
+ }
+
+
+ public void setOnBackListener(CalculatorKeyboard keyboard) {
+ mOnBackListener = keyboard;
+ }
+
+ @Override
+ public void onBackPressed() {
+ boolean eventProcessed = false;
+
+ if (mOnBackListener != null)
+ eventProcessed = mOnBackListener.onBackPressed();
+
+ if (!eventProcessed)
+ super.onBackPressed();
+ }
+
+}
diff --git a/app/src/main/java/org/gnucash/android/ui/UxArgument.java b/app/src/main/java/org/gnucash/android/ui/common/UxArgument.java
similarity index 80%
rename from app/src/main/java/org/gnucash/android/ui/UxArgument.java
rename to app/src/main/java/org/gnucash/android/ui/common/UxArgument.java
index d275bc33f..9226a5715 100644
--- a/app/src/main/java/org/gnucash/android/ui/UxArgument.java
+++ b/app/src/main/java/org/gnucash/android/ui/common/UxArgument.java
@@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-package org.gnucash.android.ui;
+package org.gnucash.android.ui.common;
/**
* Collection of constants which are passed across multiple pieces of the UI (fragments, activities, dialogs)
@@ -52,6 +52,11 @@ public final class UxArgument {
*/
public static final String PASSCODE = "passcode";
+ /**
+ * Key for skipping the passcode screen. Use this only when there is no other choice.
+ */
+ public static final String SKIP_PASSCODE_SCREEN = "skip_passcode_screen";
+
/**
* Amount passed as a string
*/
@@ -77,6 +82,22 @@ public final class UxArgument {
*/
public static final String SCHEDULED_ACTION_UID = "scheduled_action_uid";
+ /**
+ * Type of form displayed in the {@link FormActivity}
+ */
+ public static final String FORM_TYPE = "form_type";
+
+ /**
+ * List of splits which have been created using the split editor
+ */
+ public static final String SPLIT_LIST = "split_list";
+
+ /**
+ * GUID of splits which have been removed from the split editor
+ */
+ public static String REMOVED_SPLITS = "removed_split_guids";
+
+
//prevent initialization of instances of this class
private UxArgument(){
//prevent even the native class from calling the ctor
diff --git a/app/src/main/java/org/gnucash/android/ui/export/ExportDialogFragment.java b/app/src/main/java/org/gnucash/android/ui/export/ExportFormFragment.java
similarity index 65%
rename from app/src/main/java/org/gnucash/android/ui/export/ExportDialogFragment.java
rename to app/src/main/java/org/gnucash/android/ui/export/ExportFormFragment.java
index 6df927cbe..0303fde47 100644
--- a/app/src/main/java/org/gnucash/android/ui/export/ExportDialogFragment.java
+++ b/app/src/main/java/org/gnucash/android/ui/export/ExportFormFragment.java
@@ -16,29 +16,36 @@
package org.gnucash.android.ui.export;
+import android.Manifest;
import android.app.Activity;
import android.content.Intent;
import android.content.SharedPreferences;
+import android.content.pm.PackageManager;
+import android.os.Build;
import android.os.Bundle;
import android.preference.PreferenceManager;
-import android.support.v4.app.DialogFragment;
+import android.support.v4.app.Fragment;
import android.support.v4.app.FragmentManager;
+import android.support.v7.app.ActionBar;
+import android.support.v7.app.AppCompatActivity;
import android.text.format.Time;
import android.util.Log;
import android.view.LayoutInflater;
+import android.view.Menu;
+import android.view.MenuInflater;
+import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
import android.widget.AdapterView;
import android.widget.ArrayAdapter;
-import android.widget.Button;
import android.widget.CheckBox;
import android.widget.RadioButton;
import android.widget.Spinner;
import android.widget.TextView;
-import com.doomonafireball.betterpickers.recurrencepicker.EventRecurrence;
-import com.doomonafireball.betterpickers.recurrencepicker.EventRecurrenceFormatter;
-import com.doomonafireball.betterpickers.recurrencepicker.RecurrencePickerDialog;
+import com.codetroopers.betterpickers.recurrencepicker.EventRecurrence;
+import com.codetroopers.betterpickers.recurrencepicker.EventRecurrenceFormatter;
+import com.codetroopers.betterpickers.recurrencepicker.RecurrencePickerDialog;
import com.dropbox.sync.android.DbxAccountManager;
import org.gnucash.android.R;
@@ -47,56 +54,54 @@
import org.gnucash.android.export.ExportAsyncTask;
import org.gnucash.android.export.ExportFormat;
import org.gnucash.android.export.ExportParams;
+import org.gnucash.android.model.BaseModel;
import org.gnucash.android.model.ScheduledAction;
+import org.gnucash.android.ui.account.AccountsActivity;
+import org.gnucash.android.ui.common.UxArgument;
import org.gnucash.android.ui.settings.SettingsActivity;
import org.gnucash.android.ui.util.RecurrenceParser;
import java.util.List;
-import java.util.UUID;
+
+import butterknife.Bind;
+import butterknife.ButterKnife;
/**
- * Dialog fragment for exporting account information as OFX files.
+ * Dialog fragment for exporting accounts and transactions in various formats
+ * The dialog is used for collecting information on the export options and then passing them
+ * to the {@link org.gnucash.android.export.Exporter} responsible for exporting
* @author Ngewi Fet
*/
-public class ExportDialogFragment extends DialogFragment implements RecurrencePickerDialog.OnRecurrenceSetListener {
+public class
+ ExportFormFragment extends Fragment implements RecurrencePickerDialog.OnRecurrenceSetListener {
/**
* Spinner for selecting destination for the exported file.
* The destination could either be SD card, or another application which
* accepts files, like Google Drive.
*/
- Spinner mDestinationSpinner;
+ @Bind(R.id.spinner_export_destination) Spinner mDestinationSpinner;
/**
* Checkbox indicating that all transactions should be exported,
* regardless of whether they have been exported previously or not
*/
- CheckBox mExportAllCheckBox;
+ @Bind(R.id.checkbox_export_all) CheckBox mExportAllCheckBox;
/**
* Checkbox for deleting all transactions after exporting them
*/
- CheckBox mDeleteAllCheckBox;
-
- /**
- * Save button for saving the exported files
- */
- Button mSaveButton;
-
- /**
- * Cancels the export dialog
- */
- Button mCancelButton;
+ @Bind(R.id.checkbox_post_export_delete) CheckBox mDeleteAllCheckBox;
/**
* Text view for showing warnings based on chosen export format
*/
- TextView mExportWarningTextView;
+ @Bind(R.id.export_warning) TextView mExportWarningTextView;
/**
* Recurrence text view
*/
- TextView mRecurrenceTextView;
+ @Bind(R.id.input_recurrence) TextView mRecurrenceTextView;
/**
* Event recurrence options
@@ -111,7 +116,7 @@ public class ExportDialogFragment extends DialogFragment implements RecurrencePi
/**
* Tag for logging
*/
- private static final String TAG = "ExportDialogFragment";
+ private static final String TAG = "ExportFormFragment";
/**
* Export format
@@ -121,35 +126,6 @@ public class ExportDialogFragment extends DialogFragment implements RecurrencePi
private ExportParams.ExportTarget mExportTarget = ExportParams.ExportTarget.SD_CARD;
- /**
- * Click listener for positive button in the dialog.
- * @author Ngewi Fet
- */
- protected class ExportClickListener implements View.OnClickListener {
-
- @Override
- public void onClick(View v) {
- ExportParams exportParameters = new ExportParams(mExportFormat);
- exportParameters.setExportAllTransactions(mExportAllCheckBox.isChecked());
- exportParameters.setExportTarget(mExportTarget);
- exportParameters.setDeleteTransactionsAfterExport(mDeleteAllCheckBox.isChecked());
-
- List scheduledActions = RecurrenceParser.parse(mEventRecurrence,
- ScheduledAction.ActionType.BACKUP);
- for (ScheduledAction scheduledAction : scheduledActions) {
- scheduledAction.setTag(exportParameters.toCsv());
- scheduledAction.setActionUID(UUID.randomUUID().toString().replaceAll("-", ""));
- ScheduledActionDbAdapter.getInstance().addScheduledAction(scheduledAction);
- }
-
- Log.i(TAG, "Commencing async export of transactions");
- new ExportAsyncTask(getActivity()).execute(exportParameters);
-
- dismiss();
- }
-
- }
-
public void onRadioButtonClicked(View view){
switch (view.getId()){
case R.id.radio_ofx_format:
@@ -176,6 +152,7 @@ public void onRadioButtonClicked(View view){
case R.id.radio_xml_format:
mExportFormat = ExportFormat.XML;
mExportWarningTextView.setText(R.string.export_warning_xml);
+
break;
}
}
@@ -183,7 +160,9 @@ public void onRadioButtonClicked(View view){
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
- return inflater.inflate(R.layout.dialog_export, container, false);
+ View view = inflater.inflate(R.layout.fragment_export_form, container, false);
+ ButterKnife.bind(this, view);
+ return view;
}
@Override
@@ -191,17 +170,90 @@ public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
}
+ @Override
+ public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
+ inflater.inflate(R.menu.default_save_actions, menu);
+ MenuItem menuItem = menu.findItem(R.id.menu_save);
+ menuItem.setTitle(R.string.btn_export);
+ }
+
+ @Override
+ public boolean onOptionsItemSelected(MenuItem item) {
+ switch (item.getItemId()){
+ case R.id.menu_save:
+ startExport();
+ return true;
+
+ case android.R.id.home:
+ getActivity().finish();
+ return true;
+
+ default:
+ return super.onOptionsItemSelected(item);
+ }
+ }
+
@Override
public void onActivityCreated(Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
bindViews();
- getDialog().setTitle(R.string.title_export_dialog);
+ ActionBar supportActionBar = ((AppCompatActivity) getActivity()).getSupportActionBar();
+ assert supportActionBar != null;
+ supportActionBar.setTitle(R.string.title_export_dialog);
+ setHasOptionsMenu(true);
+
+ getSDWritePermission();
+ }
+
+ @Override
+ public void onPause() {
+ super.onPause();
+ // When the user try to export sharing to 3rd party service like DropBox
+ // then pausing all activities. That cause passcode screen appearing happened.
+ // We use a disposable flag to skip this unnecessary passcode screen.
+ SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(getActivity());
+ prefs.edit().putBoolean(UxArgument.SKIP_PASSCODE_SCREEN, true).apply();
+ }
+
+ /**
+ * Get permission for WRITING SD card for Android Marshmallow and above
+ */
+ private void getSDWritePermission(){
+ if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
+ if (getActivity().checkSelfPermission(Manifest.permission.WRITE_EXTERNAL_STORAGE)
+ != PackageManager.PERMISSION_GRANTED) {
+ getActivity().requestPermissions(new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE,
+ Manifest.permission.READ_EXTERNAL_STORAGE}, AccountsActivity.REQUEST_PERMISSION_WRITE_SD_CARD);
+ }
+ }
+ }
+
+ /**
+ * Starts the export of transactions with the specified parameters
+ */
+ private void startExport(){
+ ExportParams exportParameters = new ExportParams(mExportFormat);
+ exportParameters.setExportAllTransactions(mExportAllCheckBox.isChecked());
+ exportParameters.setExportTarget(mExportTarget);
+ exportParameters.setDeleteTransactionsAfterExport(mDeleteAllCheckBox.isChecked());
+
+ List scheduledActions = RecurrenceParser.parse(mEventRecurrence,
+ ScheduledAction.ActionType.BACKUP);
+ for (ScheduledAction scheduledAction : scheduledActions) {
+ scheduledAction.setTag(exportParameters.toCsv());
+ scheduledAction.setActionUID(BaseModel.generateUID());
+ ScheduledActionDbAdapter.getInstance().addRecord(scheduledAction);
+ }
+
+ Log.i(TAG, "Commencing async export of transactions");
+ new ExportAsyncTask(getActivity()).execute(exportParameters);
+
+ // finish the activity will cause the progress dialog to be leaked
+ // which would throw an exception
+ //getActivity().finish();
}
private void bindViews(){
- View v = getView();
- assert v != null;
- mDestinationSpinner = (Spinner) v.findViewById(R.id.spinner_export_destination);
ArrayAdapter adapter = ArrayAdapter.createFromResource(getActivity(),
R.array.export_destinations, android.R.layout.simple_spinner_item);
adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
@@ -210,7 +262,7 @@ private void bindViews(){
@Override
public void onItemSelected(AdapterView> parent, View view, int position, long id) {
View recurrenceOptionsView = getView().findViewById(R.id.recurrence_options);
- switch (position){
+ switch (position) {
case 0:
mExportTarget = ExportParams.ExportTarget.SD_CARD;
recurrenceOptionsView.setVisibility(View.VISIBLE);
@@ -218,11 +270,11 @@ public void onItemSelected(AdapterView> parent, View view, int position, long
case 1:
recurrenceOptionsView.setVisibility(View.VISIBLE);
mExportTarget = ExportParams.ExportTarget.DROPBOX;
- String dropboxAppKey = getString(R.string.dropbox_app_key, SettingsActivity.DROPBOX_APP_KEY);
- String dropboxAppSecret = getString(R.string.dropbox_app_secret, SettingsActivity.DROPBOX_APP_SECRET);
+ String dropboxAppKey = getString(R.string.dropbox_app_key, SettingsActivity.DROPBOX_APP_KEY);
+ String dropboxAppSecret = getString(R.string.dropbox_app_secret, SettingsActivity.DROPBOX_APP_SECRET);
DbxAccountManager mDbxAccountManager = DbxAccountManager.getInstance(getActivity().getApplicationContext(),
dropboxAppKey, dropboxAppSecret);
- if (!mDbxAccountManager.hasLinkedAccount()){
+ if (!mDbxAccountManager.hasLinkedAccount()) {
mDbxAccountManager.startLink(getActivity(), 0);
}
break;
@@ -249,27 +301,10 @@ public void onNothingSelected(AdapterView> parent) {
}
});
SharedPreferences sharedPrefs = PreferenceManager.getDefaultSharedPreferences(getActivity());
- mExportAllCheckBox = (CheckBox) v.findViewById(R.id.checkbox_export_all);
mExportAllCheckBox.setChecked(sharedPrefs.getBoolean(getString(R.string.key_export_all_transactions), false));
- mDeleteAllCheckBox = (CheckBox) v.findViewById(R.id.checkbox_post_export_delete);
mDeleteAllCheckBox.setChecked(sharedPrefs.getBoolean(getString(R.string.key_delete_transactions_after_export), false));
-
- mSaveButton = (Button) v.findViewById(R.id.btn_save);
- mSaveButton.setText(R.string.btn_export);
- mCancelButton = (Button) v.findViewById(R.id.btn_cancel);
-
- mCancelButton.setOnClickListener(new View.OnClickListener() {
- @Override
- public void onClick(View v) {
- dismiss();
- }
- });
-
- mSaveButton.setOnClickListener(new ExportClickListener());
-
- mRecurrenceTextView = (TextView) v.findViewById(R.id.input_recurrence);
mRecurrenceTextView.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
@@ -290,16 +325,15 @@ public void onClick(View view) {
}
rpd = new RecurrencePickerDialog();
rpd.setArguments(b);
- rpd.setOnRecurrenceSetListener(ExportDialogFragment.this);
+ rpd.setOnRecurrenceSetListener(ExportFormFragment.this);
rpd.show(fm, "recurrence_picker");
}
});
- mExportWarningTextView = (TextView) v.findViewById(R.id.export_warning);
-
//this part (setting the export format) must come after the recurrence view bindings above
String defaultExportFormat = sharedPrefs.getString(getString(R.string.key_default_export_format), ExportFormat.QIF.name());
mExportFormat = ExportFormat.valueOf(defaultExportFormat);
+
View.OnClickListener clickListener = new View.OnClickListener() {
@Override
public void onClick(View view) {
@@ -307,6 +341,9 @@ public void onClick(View view) {
}
};
+ View v = getView();
+ assert v != null;
+
RadioButton ofxRadioButton = (RadioButton) v.findViewById(R.id.radio_ofx_format);
ofxRadioButton.setOnClickListener(clickListener);
if (defaultExportFormat.equalsIgnoreCase(ExportFormat.OFX.name())) {
@@ -324,6 +361,13 @@ public void onClick(View view) {
if (defaultExportFormat.equalsIgnoreCase(ExportFormat.XML.name())){
xmlRadioButton.performClick();
}
+
+ if (GnuCashApplication.isDoubleEntryEnabled()){
+ ofxRadioButton.setVisibility(View.GONE);
+ } else {
+ xmlRadioButton.setVisibility(View.GONE);
+ }
+
}
@Override
diff --git a/app/src/main/java/org/gnucash/android/ui/export/ScheduledExportListFragment.java b/app/src/main/java/org/gnucash/android/ui/export/ScheduledExportListFragment.java
deleted file mode 100644
index 0f3a4ee09..000000000
--- a/app/src/main/java/org/gnucash/android/ui/export/ScheduledExportListFragment.java
+++ /dev/null
@@ -1,382 +0,0 @@
-/*
- * Copyright (c) 2015 Ngewi Fet
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.gnucash.android.ui.export;
-
-import android.content.Context;
-import android.content.res.Resources;
-import android.database.Cursor;
-import android.graphics.Rect;
-import android.os.Bundle;
-import android.support.v4.app.LoaderManager;
-import android.support.v4.content.Loader;
-import android.support.v4.widget.SimpleCursorAdapter;
-import android.util.Log;
-import android.util.SparseBooleanArray;
-import android.view.LayoutInflater;
-import android.view.TouchDelegate;
-import android.view.View;
-import android.view.ViewGroup;
-import android.widget.CheckBox;
-import android.widget.CompoundButton;
-import android.widget.ListView;
-import android.widget.TextView;
-
-import com.actionbarsherlock.app.ActionBar;
-import com.actionbarsherlock.app.SherlockListFragment;
-import com.actionbarsherlock.view.ActionMode;
-import com.actionbarsherlock.view.Menu;
-import com.actionbarsherlock.view.MenuInflater;
-import com.actionbarsherlock.view.MenuItem;
-
-import org.gnucash.android.R;
-import org.gnucash.android.db.DatabaseCursorLoader;
-import org.gnucash.android.db.DatabaseSchema;
-import org.gnucash.android.db.ScheduledActionDbAdapter;
-import org.gnucash.android.export.ExportParams;
-import org.gnucash.android.model.ScheduledAction;
-import org.gnucash.android.ui.account.AccountsActivity;
-
-/**
- * Fragment for displayed scheduled backup entries in the database
- */
-public class ScheduledExportListFragment extends SherlockListFragment implements
- LoaderManager.LoaderCallbacks {
-
- /**
- * Logging tag
- */
- protected static final String TAG = "ScheduledTrxnFragment";
-
- private ScheduledActionDbAdapter mScheduledActionDbAdapter;
- private SimpleCursorAdapter mCursorAdapter;
- private ActionMode mActionMode = null;
-
- /**
- * Flag which is set when a transaction is selected
- */
- private boolean mInEditMode = false;
-
-
- /**
- * Callbacks for the menu items in the Context ActionBar (CAB) in action mode
- */
- private ActionMode.Callback mActionModeCallbacks = new ActionMode.Callback() {
-
- @Override
- public boolean onCreateActionMode(ActionMode mode, Menu menu) {
- MenuInflater inflater = mode.getMenuInflater();
- inflater.inflate(R.menu.transactions_context_menu, menu);
- menu.removeItem(R.id.context_menu_move_transactions);
- return true;
- }
-
- @Override
- public boolean onPrepareActionMode(ActionMode mode, Menu menu) {
- //nothing to see here, move along
- return false;
- }
-
- @Override
- public void onDestroyActionMode(ActionMode mode) {
- finishEditMode();
- }
-
- @Override
- public boolean onActionItemClicked(ActionMode mode, MenuItem item) {
- switch (item.getItemId()) {
- case R.id.context_menu_delete:
- for (long id : getListView().getCheckedItemIds()) {
- Log.i(TAG, "Deleting scheduled export(s)");
- mScheduledActionDbAdapter.deleteRecord(id);
- }
- mode.finish();
- getLoaderManager().destroyLoader(0);
- refreshList();
- return true;
-
- default:
- return false;
- }
- }
- };
-
- @Override
- public void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
-
- mScheduledActionDbAdapter = ScheduledActionDbAdapter.getInstance();
- mCursorAdapter = new ScheduledExportCursorAdapter(
- getActivity().getApplicationContext(),
- R.layout.list_item_scheduled_trxn, null,
- new String[]{}, new int[]{});
- setListAdapter(mCursorAdapter);
- }
-
- @Override
- public View onCreateView(LayoutInflater inflater, ViewGroup container,
- Bundle savedInstanceState) {
- return inflater.inflate(R.layout.fragment_scheduled_events_list, container, false);
- }
-
- @Override
- public void onActivityCreated(Bundle savedInstanceState) {
- super.onActivityCreated(savedInstanceState);
-
- ActionBar actionBar = getSherlockActivity().getSupportActionBar();
- actionBar.setDisplayShowTitleEnabled(true);
- actionBar.setDisplayHomeAsUpEnabled(true);
- actionBar.setHomeButtonEnabled(true);
- actionBar.setTitle(R.string.title_scheduled_exports);
-
- setHasOptionsMenu(true);
- getListView().setChoiceMode(ListView.CHOICE_MODE_MULTIPLE);
- ((TextView)getListView().getEmptyView()).setText(R.string.label_no_scheduled_exports_to_display);
- }
-
- /**
- * Reload the list of transactions and recompute account balances
- */
- public void refreshList(){
- getLoaderManager().restartLoader(0, null, this);
- }
-
- @Override
- public void onResume() {
- super.onResume();
- refreshList();
- }
-
- @Override
- public void onListItemClick(ListView l, View v, int position, long id) {
- super.onListItemClick(l, v, position, id);
- if (mActionMode != null){
- CheckBox checkbox = (CheckBox) v.findViewById(R.id.checkbox);
- checkbox.setChecked(!checkbox.isChecked());
- return;
- } else {
- startActionMode();
- }
- }
-
- @Override
- public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
- menu.removeItem(R.id.menu_search);
- menu.removeItem(R.id.menu_settings);
- inflater.inflate(R.menu.scheduled_export_actions, menu);
- }
-
- @Override
- public boolean onOptionsItemSelected(MenuItem item) {
- switch (item.getItemId()){
- case R.id.menu_add_scheduled_export:
- AccountsActivity.showExportDialog(getActivity());
- return true;
- default:
- return super.onOptionsItemSelected(item);
- }
- }
-
- @Override
- public Loader onCreateLoader(int arg0, Bundle arg1) {
- Log.d(TAG, "Creating transactions loader");
- return new ScheduledExportCursorLoader(getActivity());
- }
-
- @Override
- public void onLoadFinished(Loader loader, Cursor cursor) {
- Log.d(TAG, "Scheduled backup loader finished. Swapping in cursor");
- mCursorAdapter.swapCursor(cursor);
- mCursorAdapter.notifyDataSetChanged();
- }
-
- @Override
- public void onLoaderReset(Loader loader) {
- Log.d(TAG, "Resetting scheduled backup loader");
- mCursorAdapter.swapCursor(null);
- }
-
- /**
- * Finishes the edit mode in the list.
- * Edit mode is started when at least one list item is selected
- */
- public void finishEditMode(){
- mInEditMode = false;
- uncheckAllItems();
- mActionMode = null;
- }
-
- /**
- * Sets the title of the Context ActionBar when in action mode.
- * It sets the number highlighted items
- */
- public void setActionModeTitle(){
- int count = getListView().getCheckedItemIds().length; //mSelectedIds.size();
- if (count > 0){
- mActionMode.setTitle(getResources().getString(R.string.title_selected, count));
- }
- }
-
- /**
- * Unchecks all the checked items in the list
- */
- private void uncheckAllItems() {
- SparseBooleanArray checkedPositions = getListView().getCheckedItemPositions();
- ListView listView = getListView();
- for (int i = 0; i < checkedPositions.size(); i++) {
- int position = checkedPositions.keyAt(i);
- listView.setItemChecked(position, false);
- }
- }
-
-
- /**
- * Starts action mode and activates the Context ActionBar (CAB)
- * Action mode is initiated as soon as at least one transaction is selected (highlighted)
- */
- private void startActionMode(){
- if (mActionMode != null) {
- return;
- }
- mInEditMode = true;
- // Start the CAB using the ActionMode.Callback defined above
- mActionMode = getSherlockActivity().startActionMode(mActionModeCallbacks);
- }
-
- /**
- * Stops action mode and deselects all selected transactions.
- * This method only has effect if the number of checked items is greater than 0 and {@link #mActionMode} is not null
- */
- private void stopActionMode(){
- int checkedCount = getListView().getCheckedItemIds().length;
- if (checkedCount <= 0 && mActionMode != null) {
- mActionMode.finish();
- }
- }
-
-
- /**
- * Extends a simple cursor adapter to bind transaction attributes to views
- * @author Ngewi Fet
- */
- protected class ScheduledExportCursorAdapter extends SimpleCursorAdapter {
-
- public ScheduledExportCursorAdapter(Context context, int layout, Cursor c,
- String[] from, int[] to) {
- super(context, layout, c, from, to, 0);
- }
-
- @Override
- public View getView(int position, View convertView, ViewGroup parent) {
- final View view = super.getView(position, convertView, parent);
- final int itemPosition = position;
- CheckBox checkbox = (CheckBox) view.findViewById(R.id.checkbox);
- //TODO: Revisit this if we ever change the application theme
- int id = Resources.getSystem().getIdentifier("btn_check_holo_light", "drawable", "android");
- checkbox.setButtonDrawable(id);
-
- final TextView secondaryText = (TextView) view.findViewById(R.id.secondary_text);
-
- checkbox.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
-
- @Override
- public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
- getListView().setItemChecked(itemPosition, isChecked);
- if (isChecked) {
- startActionMode();
- } else {
- stopActionMode();
- }
- setActionModeTitle();
- }
- });
-
-
- ListView listView = (ListView) parent;
- if (mInEditMode && listView.isItemChecked(position)){
- view.setBackgroundColor(getResources().getColor(R.color.abs__holo_blue_light));
- secondaryText.setTextColor(getResources().getColor(android.R.color.white));
- } else {
- view.setBackgroundColor(getResources().getColor(android.R.color.transparent));
- secondaryText.setTextColor(getResources().getColor(android.R.color.secondary_text_light_nodisable));
- checkbox.setChecked(false);
- }
-
- final View checkBoxView = checkbox;
- final View parentView = view;
- parentView.post(new Runnable() {
- @Override
- public void run() {
- if (isAdded()){ //may be run when fragment has been unbound from activity
- float extraPadding = getResources().getDimension(R.dimen.edge_padding);
- final android.graphics.Rect hitRect = new Rect();
- checkBoxView.getHitRect(hitRect);
- hitRect.right += extraPadding;
- hitRect.bottom += 3*extraPadding;
- hitRect.top -= extraPadding;
- hitRect.left -= 2*extraPadding;
- parentView.setTouchDelegate(new TouchDelegate(hitRect, checkBoxView));
- }
- }
- });
-
- return view;
- }
-
- @Override
- public void bindView(View view, Context context, Cursor cursor) {
- super.bindView(view, context, cursor);
-
- ScheduledAction scheduledAction = mScheduledActionDbAdapter.buildScheduledActionInstance(cursor);
-
- TextView primaryTextView = (TextView) view.findViewById(R.id.primary_text);
- ExportParams params = ExportParams.parseCsv(scheduledAction.getTag());
- primaryTextView.setText(params.getExportFormat().name() + " "
- + scheduledAction.getActionType().name().toLowerCase() + " to "
- + params.getExportTarget().name().toLowerCase());
-
- view.findViewById(R.id.right_text).setVisibility(View.GONE);
-
- TextView descriptionTextView = (TextView) view.findViewById(R.id.secondary_text);
- descriptionTextView.setText(scheduledAction.getRepeatString());
-
- }
- }
-
- /**
- * {@link DatabaseCursorLoader} for loading recurring transactions asynchronously from the database
- * @author Ngewi Fet
- */
- protected static class ScheduledExportCursorLoader extends DatabaseCursorLoader {
-
- public ScheduledExportCursorLoader(Context context) {
- super(context);
- }
-
- @Override
- public Cursor loadInBackground() {
- mDatabaseAdapter = ScheduledActionDbAdapter.getInstance();
-
- Cursor c = mDatabaseAdapter.fetchAllRecords(
- DatabaseSchema.ScheduledActionEntry.COLUMN_TYPE + "=?",
- new String[]{ScheduledAction.ActionType.BACKUP.name()});
-
- registerContentObserver(c);
- return c;
- }
- }
-
-}
diff --git a/app/src/main/java/org/gnucash/android/ui/widget/WidgetConfigurationActivity.java b/app/src/main/java/org/gnucash/android/ui/homescreen/WidgetConfigurationActivity.java
similarity index 94%
rename from app/src/main/java/org/gnucash/android/ui/widget/WidgetConfigurationActivity.java
rename to app/src/main/java/org/gnucash/android/ui/homescreen/WidgetConfigurationActivity.java
index df30e071a..99f55eab7 100644
--- a/app/src/main/java/org/gnucash/android/ui/widget/WidgetConfigurationActivity.java
+++ b/app/src/main/java/org/gnucash/android/ui/homescreen/WidgetConfigurationActivity.java
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package org.gnucash.android.ui.widget;
+package org.gnucash.android.ui.homescreen;
import android.app.Activity;
import android.app.PendingIntent;
@@ -40,7 +40,8 @@
import org.gnucash.android.model.Account;
import org.gnucash.android.model.Money;
import org.gnucash.android.receivers.TransactionAppWidgetProvider;
-import org.gnucash.android.ui.UxArgument;
+import org.gnucash.android.ui.common.FormActivity;
+import org.gnucash.android.ui.common.UxArgument;
import org.gnucash.android.ui.account.AccountsActivity;
import org.gnucash.android.ui.transaction.TransactionsActivity;
import org.gnucash.android.util.QualifiedAccountNameCursorAdapter;
@@ -78,10 +79,7 @@ public void onCreate(Bundle savedInstanceState) {
finish();
}
- SimpleCursorAdapter cursorAdapter = new QualifiedAccountNameCursorAdapter(this,
- android.R.layout.simple_spinner_item,
- cursor);
- cursorAdapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
+ SimpleCursorAdapter cursorAdapter = new QualifiedAccountNameCursorAdapter(this, cursor);
mAccountsSpinner.setAdapter(cursorAdapter);
bindListeners();
@@ -147,7 +145,7 @@ public static void updateWidget(Context context, int appWidgetId, String account
AccountsDbAdapter accountsDbAdapter = AccountsDbAdapter.getInstance();
Account account;
try {
- account = accountsDbAdapter.getAccount(accountUID);
+ account = accountsDbAdapter.getRecord(accountUID);
} catch (IllegalArgumentException e) {
Log.i("WidgetConfiguration", "Account not found, resetting widget " + appWidgetId);
//if account has been deleted, let the user know
@@ -181,15 +179,16 @@ public static void updateWidget(Context context, int appWidgetId, String account
Intent accountViewIntent = new Intent(context, TransactionsActivity.class);
accountViewIntent.setAction(Intent.ACTION_VIEW);
- accountViewIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK|Intent.FLAG_ACTIVITY_CLEAR_TASK);
+ accountViewIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
accountViewIntent.putExtra(UxArgument.SELECTED_ACCOUNT_UID, accountUID);
PendingIntent accountPendingIntent = PendingIntent
.getActivity(context, appWidgetId, accountViewIntent, 0);
views.setOnClickPendingIntent(R.id.widget_layout, accountPendingIntent);
- Intent newTransactionIntent = new Intent(context, TransactionsActivity.class);
+ Intent newTransactionIntent = new Intent(context, FormActivity.class);
newTransactionIntent.setAction(Intent.ACTION_INSERT_OR_EDIT);
newTransactionIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+ newTransactionIntent.putExtra(UxArgument.FORM_TYPE, FormActivity.FormType.TRANSACTION.name());
newTransactionIntent.putExtra(UxArgument.SELECTED_ACCOUNT_UID, accountUID);
PendingIntent pendingIntent = PendingIntent
.getActivity(context, appWidgetId, newTransactionIntent, 0);
diff --git a/app/src/main/java/org/gnucash/android/ui/passcode/KeyboardFragment.java b/app/src/main/java/org/gnucash/android/ui/passcode/KeyboardFragment.java
index 0f456d7a4..2ed59b6ef 100644
--- a/app/src/main/java/org/gnucash/android/ui/passcode/KeyboardFragment.java
+++ b/app/src/main/java/org/gnucash/android/ui/passcode/KeyboardFragment.java
@@ -19,20 +19,19 @@
import android.app.Activity;
import android.os.Bundle;
import android.os.Handler;
+import android.support.v4.app.Fragment;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;
-import com.actionbarsherlock.app.SherlockFragment;
-
import org.gnucash.android.R;
/**
* Soft numeric keyboard for lock screen and passcode preference.
* @author Oleksandr Tyshkovets
*/
-public class KeyboardFragment extends SherlockFragment {
+public class KeyboardFragment extends Fragment {
private static final int DELAY = 500;
diff --git a/app/src/main/java/org/gnucash/android/ui/passcode/PassLockActivity.java b/app/src/main/java/org/gnucash/android/ui/passcode/PasscodeLockActivity.java
similarity index 64%
rename from app/src/main/java/org/gnucash/android/ui/passcode/PassLockActivity.java
rename to app/src/main/java/org/gnucash/android/ui/passcode/PasscodeLockActivity.java
index 368fd1aff..0934053dc 100644
--- a/app/src/main/java/org/gnucash/android/ui/passcode/PassLockActivity.java
+++ b/app/src/main/java/org/gnucash/android/ui/passcode/PasscodeLockActivity.java
@@ -19,10 +19,12 @@
import android.content.Intent;
import android.content.SharedPreferences;
import android.preference.PreferenceManager;
+import android.support.v7.app.AppCompatActivity;
+import android.util.Log;
+import android.view.WindowManager.LayoutParams;
import org.gnucash.android.app.GnuCashApplication;
-import org.gnucash.android.ui.BaseDrawerActivity;
-import org.gnucash.android.ui.UxArgument;
+import org.gnucash.android.ui.common.UxArgument;
/**
* This activity used as the parent class for enabling passcode lock
@@ -31,28 +33,43 @@
* @see org.gnucash.android.ui.account.AccountsActivity
* @see org.gnucash.android.ui.transaction.TransactionsActivity
*/
-public class PassLockActivity extends BaseDrawerActivity {
+public class PasscodeLockActivity extends AppCompatActivity {
- private static final String TAG = "PassLockActivity";
+ private static final String TAG = "PasscodeLockActivity";
@Override
protected void onResume() {
super.onResume();
+ SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(getApplicationContext());
+ boolean isPassEnabled = prefs.getBoolean(UxArgument.ENABLED_PASSCODE, false);
+ if (isPassEnabled) {
+ if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.HONEYCOMB) {
+ getWindow().addFlags(LayoutParams.FLAG_SECURE);
+ }
+ } else {
+ getWindow().clearFlags(LayoutParams.FLAG_SECURE);
+ }
+
// Only for Android Lollipop that brings a few changes to the recent apps feature
if ((getIntent().getFlags() & Intent.FLAG_ACTIVITY_LAUNCHED_FROM_HISTORY) != 0) {
GnuCashApplication.PASSCODE_SESSION_INIT_TIME = 0;
}
- SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(getApplicationContext());
+
+ // see ExportFormFragment.onPause()
+ boolean skipPasscode = prefs.getBoolean(UxArgument.SKIP_PASSCODE_SCREEN, false);
+ prefs.edit().remove(UxArgument.SKIP_PASSCODE_SCREEN).apply();
String passCode = prefs.getString(UxArgument.PASSCODE, "");
- if (prefs.getBoolean(UxArgument.ENABLED_PASSCODE, false) && !isSessionActive() && !passCode.trim().isEmpty()) {
- startActivity(new Intent(this, PasscodeLockScreenActivity.class)
+
+ if (isPassEnabled && !isSessionActive() && !passCode.trim().isEmpty() && !skipPasscode) {
+ Log.v(TAG, "Show passcode screen");
+ Intent intent = new Intent(this, PasscodeLockScreenActivity.class)
.setAction(getIntent().getAction())
.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK)
- .putExtra(UxArgument.PASSCODE_CLASS_CALLER, this.getClass().getName())
- .putExtra(UxArgument.SELECTED_ACCOUNT_UID,
- getIntent().getStringExtra(UxArgument.SELECTED_ACCOUNT_UID))
- );
+ .putExtra(UxArgument.PASSCODE_CLASS_CALLER, this.getClass().getName());
+ if (getIntent().getExtras() != null)
+ intent.putExtras(getIntent().getExtras());
+ startActivity(intent);
}
}
diff --git a/app/src/main/java/org/gnucash/android/ui/passcode/PasscodeLockScreenActivity.java b/app/src/main/java/org/gnucash/android/ui/passcode/PasscodeLockScreenActivity.java
index 88c8a574c..607ac8b9b 100644
--- a/app/src/main/java/org/gnucash/android/ui/passcode/PasscodeLockScreenActivity.java
+++ b/app/src/main/java/org/gnucash/android/ui/passcode/PasscodeLockScreenActivity.java
@@ -19,20 +19,19 @@
import android.content.Intent;
import android.os.Bundle;
import android.preference.PreferenceManager;
+import android.support.v7.app.AppCompatActivity;
import android.util.Log;
import android.widget.Toast;
-import com.actionbarsherlock.app.SherlockFragmentActivity;
-
import org.gnucash.android.R;
import org.gnucash.android.app.GnuCashApplication;
-import org.gnucash.android.ui.UxArgument;
+import org.gnucash.android.ui.common.UxArgument;
/**
* Activity for displaying and managing the passcode lock screen.
* @author Oleksandr Tyshkovets
*/
-public class PasscodeLockScreenActivity extends SherlockFragmentActivity
+public class PasscodeLockScreenActivity extends AppCompatActivity
implements KeyboardFragment.OnPasscodeEnteredListener {
private static final String TAG = "PassLockScreenActivity";
@@ -60,7 +59,7 @@ public void onPasscodeEntered(String pass) {
.setClassName(this, getIntent().getStringExtra(UxArgument.PASSCODE_CLASS_CALLER))
.setAction(getIntent().getAction())
.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK)
- .putExtra(UxArgument.SELECTED_ACCOUNT_UID, getIntent().getStringExtra(UxArgument.SELECTED_ACCOUNT_UID))
+ .putExtras(getIntent().getExtras())
);
} else {
Toast.makeText(this, R.string.toast_wrong_passcode, Toast.LENGTH_SHORT).show();
diff --git a/app/src/main/java/org/gnucash/android/ui/passcode/PasscodePreferenceActivity.java b/app/src/main/java/org/gnucash/android/ui/passcode/PasscodePreferenceActivity.java
index cd664f0e8..1f134efe5 100644
--- a/app/src/main/java/org/gnucash/android/ui/passcode/PasscodePreferenceActivity.java
+++ b/app/src/main/java/org/gnucash/android/ui/passcode/PasscodePreferenceActivity.java
@@ -19,19 +19,18 @@
import android.content.Intent;
import android.os.Bundle;
import android.preference.PreferenceManager;
+import android.support.v7.app.AppCompatActivity;
import android.widget.TextView;
import android.widget.Toast;
-import com.actionbarsherlock.app.SherlockFragmentActivity;
-
import org.gnucash.android.R;
-import org.gnucash.android.ui.UxArgument;
+import org.gnucash.android.ui.common.UxArgument;
/**
* Activity for entering and confirming passcode
* @author Oleksandr Tyshkovets
*/
-public class PasscodePreferenceActivity extends SherlockFragmentActivity
+public class PasscodePreferenceActivity extends AppCompatActivity
implements KeyboardFragment.OnPasscodeEnteredListener {
private boolean mIsPassEnabled;
diff --git a/app/src/main/java/org/gnucash/android/ui/report/BalanceSheetFragment.java b/app/src/main/java/org/gnucash/android/ui/report/BalanceSheetFragment.java
new file mode 100644
index 000000000..79f77a5ee
--- /dev/null
+++ b/app/src/main/java/org/gnucash/android/ui/report/BalanceSheetFragment.java
@@ -0,0 +1,146 @@
+/*
+ * Copyright (c) 2015 Ngewi Fet
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.gnucash.android.ui.report;
+
+import android.database.Cursor;
+import android.graphics.Typeface;
+import android.os.Bundle;
+import android.support.annotation.Nullable;
+import android.support.v4.app.Fragment;
+import android.support.v7.app.AppCompatActivity;
+import android.text.TextUtils;
+import android.view.LayoutInflater;
+import android.view.Menu;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.TableLayout;
+import android.widget.TextView;
+
+import org.gnucash.android.R;
+import org.gnucash.android.db.AccountsDbAdapter;
+import org.gnucash.android.db.DatabaseSchema;
+import org.gnucash.android.model.AccountType;
+import org.gnucash.android.model.Money;
+import org.gnucash.android.ui.transaction.TransactionsActivity;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import butterknife.Bind;
+import butterknife.ButterKnife;
+
+/**
+ * Fragment report as text
+ * @author Ngewi Fet
+ */
+public class BalanceSheetFragment extends Fragment {
+
+ @Bind(R.id.table_assets) TableLayout mAssetsTableLayout;
+ @Bind(R.id.table_liabilities) TableLayout mLiabilitiesTableLayout;
+ @Bind(R.id.table_equity) TableLayout mEquityTableLayout;
+
+ @Bind(R.id.total_liability_and_equity) TextView mNetWorth;
+
+
+ AccountsDbAdapter mAccountsDbAdapter = AccountsDbAdapter.getInstance();
+
+ @Nullable
+ @Override
+ public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
+ View view = inflater.inflate(R.layout.fragment_text_report, container, false);
+ ButterKnife.bind(this, view);
+ return view;
+ }
+
+ @Override
+ public void onActivityCreated(@Nullable Bundle savedInstanceState) {
+ super.onActivityCreated(savedInstanceState);
+ ((AppCompatActivity)getActivity()).getSupportActionBar().setTitle(R.string.title_balance_sheet_report);
+ setHasOptionsMenu(true);
+
+ List accountTypes = new ArrayList<>();
+ accountTypes.add(AccountType.ASSET);
+ accountTypes.add(AccountType.CASH);
+ accountTypes.add(AccountType.BANK);
+ loadAccountViews(accountTypes, mAssetsTableLayout);
+ Money assetsBalance = mAccountsDbAdapter.getAccountBalance(accountTypes, -1, System.currentTimeMillis());
+
+ accountTypes.clear();
+ accountTypes.add(AccountType.LIABILITY);
+ accountTypes.add(AccountType.CREDIT);
+ loadAccountViews(accountTypes, mLiabilitiesTableLayout);
+ Money liabilitiesBalance = mAccountsDbAdapter.getAccountBalance(accountTypes, -1, System.currentTimeMillis());
+
+ accountTypes.clear();
+ accountTypes.add(AccountType.EQUITY);
+ loadAccountViews(accountTypes, mEquityTableLayout);
+
+ TransactionsActivity.displayBalance(mNetWorth, assetsBalance.subtract(liabilitiesBalance));
+ }
+
+ @Override
+ public void onResume() {
+ super.onResume();
+ ((ReportsActivity)getActivity()).setAppBarColor(R.color.account_purple);
+ }
+
+ @Override
+ public void onPrepareOptionsMenu(Menu menu) {
+ super.onPrepareOptionsMenu(menu);
+ menu.findItem(R.id.menu_group_reports_by).setVisible(false);
+ }
+
+ /**
+ * Loads rows for the individual accounts and adds them to the report
+ * @param accountTypes Account types for which to load balances
+ * @param tableLayout Table layout into which to load the rows
+ */
+ private void loadAccountViews(List accountTypes, TableLayout tableLayout){
+ LayoutInflater inflater = LayoutInflater.from(getActivity());
+
+ Cursor cursor = mAccountsDbAdapter.fetchAccounts(DatabaseSchema.AccountEntry.COLUMN_TYPE
+ + " IN ( '" + TextUtils.join("' , '", accountTypes) + "' ) AND "
+ + DatabaseSchema.AccountEntry.COLUMN_PLACEHOLDER + " = 0",
+ null, DatabaseSchema.AccountEntry.COLUMN_FULL_NAME + " ASC");
+
+ while (cursor.moveToNext()){
+ String accountUID = cursor.getString(cursor.getColumnIndexOrThrow(DatabaseSchema.AccountEntry.COLUMN_UID));
+ String name = cursor.getString(cursor.getColumnIndexOrThrow(DatabaseSchema.AccountEntry.COLUMN_NAME));
+ Money balance = mAccountsDbAdapter.getAccountBalance(accountUID);
+ View view = inflater.inflate(R.layout.row_balance_sheet, tableLayout, false);
+ ((TextView)view.findViewById(R.id.account_name)).setText(name);
+ TextView balanceTextView = ((TextView) view.findViewById(R.id.account_balance));
+ TransactionsActivity.displayBalance(balanceTextView, balance);
+ tableLayout.addView(view);
+ }
+
+ View totalView = inflater.inflate(R.layout.row_balance_sheet, tableLayout, false);
+ TableLayout.LayoutParams layoutParams = (TableLayout.LayoutParams) totalView.getLayoutParams();
+ layoutParams.setMargins(layoutParams.leftMargin, 20, layoutParams.rightMargin, layoutParams.bottomMargin);
+ totalView.setLayoutParams(layoutParams);
+
+ TextView accountName = (TextView) totalView.findViewById(R.id.account_name);
+ accountName.setTextSize(16);
+ accountName.setText(R.string.label_balance_sheet_total);
+ TextView accountBalance = (TextView) totalView.findViewById(R.id.account_balance);
+ accountBalance.setTextSize(16);
+ accountBalance.setTypeface(null, Typeface.BOLD);
+ TransactionsActivity.displayBalance(accountBalance, mAccountsDbAdapter.getAccountBalance(accountTypes, -1, System.currentTimeMillis()));
+
+ tableLayout.addView(totalView);
+ }
+
+}
diff --git a/app/src/main/java/org/gnucash/android/ui/chart/BarChartActivity.java b/app/src/main/java/org/gnucash/android/ui/report/BarChartFragment.java
similarity index 51%
rename from app/src/main/java/org/gnucash/android/ui/chart/BarChartActivity.java
rename to app/src/main/java/org/gnucash/android/ui/report/BarChartFragment.java
index b66478969..2942a2b52 100644
--- a/app/src/main/java/org/gnucash/android/ui/chart/BarChartActivity.java
+++ b/app/src/main/java/org/gnucash/android/ui/report/BarChartFragment.java
@@ -1,5 +1,6 @@
/*
* Copyright (c) 2015 Oleksandr Tyshkovets
+ * Copyright (c) 2015 Ngewi Fet
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -14,42 +15,44 @@
* limitations under the License.
*/
-package org.gnucash.android.ui.chart;
+package org.gnucash.android.ui.report;
import android.graphics.Color;
import android.os.Bundle;
import android.preference.PreferenceManager;
+import android.support.annotation.Nullable;
+import android.support.v4.app.Fragment;
+import android.support.v7.app.AppCompatActivity;
import android.util.Log;
+import android.view.LayoutInflater;
+import android.view.Menu;
+import android.view.MenuInflater;
+import android.view.MenuItem;
import android.view.View;
-import android.widget.AdapterView;
-import android.widget.ArrayAdapter;
-import android.widget.LinearLayout;
-import android.widget.Spinner;
+import android.view.ViewGroup;
import android.widget.TextView;
import android.widget.Toast;
-import com.actionbarsherlock.view.Menu;
-import com.actionbarsherlock.view.MenuItem;
import com.github.mikephil.charting.charts.BarChart;
import com.github.mikephil.charting.components.Legend;
import com.github.mikephil.charting.data.BarData;
import com.github.mikephil.charting.data.BarDataSet;
import com.github.mikephil.charting.data.BarEntry;
import com.github.mikephil.charting.data.Entry;
+import com.github.mikephil.charting.highlight.Highlight;
import com.github.mikephil.charting.listener.OnChartValueSelectedListener;
-import com.github.mikephil.charting.utils.Highlight;
import com.github.mikephil.charting.utils.LargeValueFormatter;
import org.gnucash.android.R;
+import org.gnucash.android.app.GnuCashApplication;
import org.gnucash.android.db.AccountsDbAdapter;
import org.gnucash.android.db.TransactionsDbAdapter;
import org.gnucash.android.model.Account;
import org.gnucash.android.model.AccountType;
-import org.gnucash.android.model.Money;
-import org.gnucash.android.ui.passcode.PassLockActivity;
import org.joda.time.LocalDate;
import org.joda.time.LocalDateTime;
import org.joda.time.Months;
+import org.joda.time.Years;
import java.util.ArrayList;
import java.util.Arrays;
@@ -61,89 +64,145 @@
import java.util.Locale;
import java.util.Map;
+import butterknife.Bind;
+import butterknife.ButterKnife;
+
+import static org.gnucash.android.ui.report.ReportsActivity.COLORS;
+import static org.gnucash.android.ui.report.ReportsActivity.GroupInterval;
+
/**
* Activity used for drawing a bar chart
*
* @author Oleksandr Tyshkovets
+ * @author Ngewi Fet
*/
-public class BarChartActivity extends PassLockActivity implements OnChartValueSelectedListener {
+public class BarChartFragment extends Fragment implements OnChartValueSelectedListener,
+ ReportOptionsListener {
- private static final String TAG = "BarChartActivity";
- private static final String X_AXIS_PATTERN = "MMM YY";
+ private static final String TAG = "BarChartFragment";
+ private static final String X_AXIS_MONTH_PATTERN = "MMM YY";
+ private static final String X_AXIS_QUARTER_PATTERN = "Q%d %s";
+ private static final String X_AXIS_YEAR_PATTERN = "YYYY";
private static final String SELECTED_VALUE_PATTERN = "%s - %.2f (%.2f %%)";
- private static final int ANIMATION_DURATION = 3000;
+ private static final int ANIMATION_DURATION = 2000;
private static final int NO_DATA_COLOR = Color.LTGRAY;
private static final int NO_DATA_BAR_COUNTS = 3;
- private static final int[] COLORS = {
- Color.parseColor("#17ee4e"), Color.parseColor("#cc1f09"), Color.parseColor("#3940f7"),
- Color.parseColor("#f9cd04"), Color.parseColor("#5f33a8"), Color.parseColor("#e005b6"),
- Color.parseColor("#17d6ed"), Color.parseColor("#e4a9a2"), Color.parseColor("#8fe6cd"),
- Color.parseColor("#8b48fb"), Color.parseColor("#343a36"), Color.parseColor("#6decb1"),
- Color.parseColor("#a6dcfd"), Color.parseColor("#5c3378"), Color.parseColor("#a6dcfd"),
- Color.parseColor("#ba037c"), Color.parseColor("#708809"), Color.parseColor("#32072c"),
- Color.parseColor("#fddef8"), Color.parseColor("#fa0e6e"), Color.parseColor("#d9e7b5")
- };
private AccountsDbAdapter mAccountsDbAdapter = AccountsDbAdapter.getInstance();
- private TextView selectedValueTextView;
-
- private BarChart mChart;
+ @Bind(R.id.selected_chart_slice) TextView selectedValueTextView;
+ @Bind(R.id.bar_chart) BarChart mChart;
private Currency mCurrency;
+ private AccountType mAccountType;
+
private boolean mUseAccountColor = true;
private boolean mTotalPercentageMode = true;
private boolean mChartDataPresent = true;
+ /**
+ * Reporting period start time
+ */
+ private long mReportStartTime = -1;
+ /**
+ * Reporting period end time
+ */
+ private long mReportEndTime = -1;
+
+ private GroupInterval mGroupInterval = GroupInterval.MONTH;
+
+ @Nullable
+ @Override
+ public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
+ View view = inflater.inflate(R.layout.fragment_bar_chart, container, false);
+ ButterKnife.bind(this, view);
+ return view;
+ }
+
+ @Override
+ public void onResume() {
+ super.onResume();
+ ((ReportsActivity)getActivity()).setAppBarColor(R.color.account_red);
+ }
+
@Override
- protected void onCreate(Bundle savedInstanceState) {
- //it is necessary to set the view first before calling super because of the nav drawer in BaseDrawerActivity
- setContentView(R.layout.activity_bar_chart);
- super.onCreate(savedInstanceState);
- getSupportActionBar().setTitle(R.string.title_bar_chart);
+ public void onActivityCreated(@Nullable Bundle savedInstanceState) {
+ super.onActivityCreated(savedInstanceState);
- selectedValueTextView = (TextView) findViewById(R.id.selected_chart_slice);
+ ((AppCompatActivity)getActivity()).getSupportActionBar().setTitle(R.string.title_bar_chart);
+ setHasOptionsMenu(true);
- mUseAccountColor = PreferenceManager.getDefaultSharedPreferences(getApplicationContext())
+ mUseAccountColor = PreferenceManager.getDefaultSharedPreferences(getActivity())
.getBoolean(getString(R.string.key_use_account_color), false);
- mCurrency = Currency.getInstance(PreferenceManager.getDefaultSharedPreferences(this)
- .getString(getString(R.string.key_report_currency), Money.DEFAULT_CURRENCY_CODE));
+ mCurrency = Currency.getInstance(GnuCashApplication.getDefaultCurrencyCode());
+
+ ReportsActivity reportsActivity = (ReportsActivity) getActivity();
+ mReportStartTime = reportsActivity.getReportStartTime();
+ mReportEndTime = reportsActivity.getReportEndTime();
+ mAccountType = reportsActivity.getAccountType();
- mChart = new BarChart(this);
- ((LinearLayout) findViewById(R.id.bar_chart)).addView(mChart);
mChart.setOnChartValueSelectedListener(this);
mChart.setDescription("");
- mChart.setDrawValuesForWholeStack(false);
+// mChart.setDrawValuesForWholeStack(false);
mChart.getXAxis().setDrawGridLines(false);
mChart.getAxisRight().setEnabled(false);
mChart.getAxisLeft().enableGridDashedLine(4.0f, 4.0f, 0);
mChart.getAxisLeft().setValueFormatter(new LargeValueFormatter(mCurrency.getSymbol(Locale.getDefault())));
- mChart.getLegend().setForm(Legend.LegendForm.CIRCLE);
- mChart.getLegend().setPosition(Legend.LegendPosition.RIGHT_OF_CHART_INSIDE);
+ Legend chartLegend = mChart.getLegend();
+ chartLegend.setForm(Legend.LegendForm.CIRCLE);
+ chartLegend.setPosition(Legend.LegendPosition.BELOW_CHART_CENTER);
+ chartLegend.setWordWrapEnabled(true);
- setUpSpinner();
+ mChart.setData(getData());
+ displayChart();
}
+
/**
* Returns a data object that represents a user data of the specified account types
- * @param accountType account's type which will be displayed
* @return a {@code BarData} instance that represents a user data
*/
- private BarData getData(AccountType accountType) {
+ private BarData getData() {
List values = new ArrayList<>();
List labels = new ArrayList<>();
List colors = new ArrayList<>();
Map accountToColorMap = new LinkedHashMap<>();
List xValues = new ArrayList<>();
- LocalDateTime tmpDate = new LocalDateTime(getStartDate(accountType).toDate().getTime());
- for (int i = 0; i <= Months.monthsBetween(getStartDate(accountType), getEndDate(accountType)).getMonths(); i++) {
- long start = tmpDate.dayOfMonth().withMinimumValue().millisOfDay().withMinimumValue().toDate().getTime();
- long end = tmpDate.dayOfMonth().withMaximumValue().millisOfDay().withMaximumValue().toDate().getTime();
+ LocalDateTime tmpDate = new LocalDateTime(getStartDate(mAccountType).toDate().getTime());
+ int count = getDateDiff(new LocalDateTime(getStartDate(mAccountType).toDate().getTime()),
+ new LocalDateTime(getEndDate(mAccountType).toDate().getTime()));
+ for (int i = 0; i <= count; i++) {
+ long start = 0;
+ long end = 0;
+ switch (mGroupInterval) {
+ case MONTH:
+ start = tmpDate.dayOfMonth().withMinimumValue().millisOfDay().withMinimumValue().toDate().getTime();
+ end = tmpDate.dayOfMonth().withMaximumValue().millisOfDay().withMaximumValue().toDate().getTime();
+
+ xValues.add(tmpDate.toString(X_AXIS_MONTH_PATTERN));
+ tmpDate = tmpDate.plusMonths(1);
+ break;
+ case QUARTER:
+ int quarter = getQuarter(tmpDate);
+ start = tmpDate.withMonthOfYear(quarter * 3 - 2).dayOfMonth().withMinimumValue().millisOfDay().withMinimumValue().toDate().getTime();
+ end = tmpDate.withMonthOfYear(quarter * 3).dayOfMonth().withMaximumValue().millisOfDay().withMaximumValue().toDate().getTime();
+
+ xValues.add(String.format(X_AXIS_QUARTER_PATTERN, quarter, tmpDate.toString(" YY")));
+ tmpDate = tmpDate.plusMonths(3);
+ break;
+ case YEAR:
+ start = tmpDate.dayOfYear().withMinimumValue().millisOfDay().withMinimumValue().toDate().getTime();
+ end = tmpDate.dayOfYear().withMaximumValue().millisOfDay().withMaximumValue().toDate().getTime();
+
+ xValues.add(tmpDate.toString(X_AXIS_YEAR_PATTERN));
+ tmpDate = tmpDate.plusYears(1);
+ break;
+ }
List stack = new ArrayList<>();
for (Account account : mAccountsDbAdapter.getSimpleAccountList()) {
- if (account.getAccountType() == accountType
+ if (account.getAccountType() == mAccountType
&& !account.isPlaceholderAccount()
&& account.getCurrency() == mCurrency) {
@@ -165,20 +224,17 @@ private BarData getData(AccountType accountType) {
stack.add((float) balance);
labels.add(account.getName());
colors.add(accountToColorMap.get(account.getUID()));
- Log.d(TAG, accountType + tmpDate.toString(" MMMM yyyy ") + account.getName() + " = " + stack.get(stack.size() - 1));
+ Log.d(TAG, mAccountType + tmpDate.toString(" MMMM yyyy ") + account.getName() + " = " + stack.get(stack.size() - 1));
}
}
}
String stackLabels = labels.subList(labels.size() - stack.size(), labels.size()).toString();
values.add(new BarEntry(floatListToArray(stack), i, stackLabels));
-
- xValues.add(tmpDate.toString(X_AXIS_PATTERN));
-
- tmpDate = tmpDate.plusMonths(1);
}
BarDataSet set = new BarDataSet(values, "");
+ set.setDrawValues(false);
set.setStackLabels(labels.toArray(new String[labels.size()]));
set.setColors(colors);
@@ -216,7 +272,13 @@ private BarData getEmptyData() {
private LocalDate getStartDate(AccountType accountType) {
TransactionsDbAdapter adapter = TransactionsDbAdapter.getInstance();
String code = mCurrency.getCurrencyCode();
- LocalDate startDate = new LocalDate(adapter.getTimestampOfEarliestTransaction(accountType, code)).withDayOfMonth(1);
+ LocalDate startDate;
+ if (mReportStartTime == -1) {
+ startDate = new LocalDate(adapter.getTimestampOfEarliestTransaction(accountType, code));
+ } else {
+ startDate = new LocalDate(mReportStartTime);
+ }
+ startDate = startDate.withDayOfMonth(1);
Log.d(TAG, accountType + " X-axis star date: " + startDate.toString("dd MM yyyy"));
return startDate;
}
@@ -229,11 +291,46 @@ private LocalDate getStartDate(AccountType accountType) {
private LocalDate getEndDate(AccountType accountType) {
TransactionsDbAdapter adapter = TransactionsDbAdapter.getInstance();
String code = mCurrency.getCurrencyCode();
- LocalDate endDate = new LocalDate(adapter.getTimestampOfLatestTransaction(accountType, code)).withDayOfMonth(1);
+ LocalDate endDate;
+ if (mReportEndTime == -1) {
+ endDate = new LocalDate(adapter.getTimestampOfLatestTransaction(accountType, code));
+ } else {
+ endDate = new LocalDate(mReportEndTime);
+ }
+ endDate = endDate.withDayOfMonth(1);
Log.d(TAG, accountType + " X-axis end date: " + endDate.toString("dd MM yyyy"));
return endDate;
}
+ /**
+ * Calculates difference between two date values accordingly to {@code mGroupInterval}
+ * @param start start date
+ * @param end end date
+ * @return difference between two dates or {@code -1}
+ */
+ private int getDateDiff(LocalDateTime start, LocalDateTime end) {
+ switch (mGroupInterval) {
+ case QUARTER:
+ int y = Years.yearsBetween(start.withDayOfYear(1).withMillisOfDay(0), end.withDayOfYear(1).withMillisOfDay(0)).getYears();
+ return (getQuarter(end) - getQuarter(start) + y * 4);
+ case MONTH:
+ return Months.monthsBetween(start.withDayOfMonth(1).withMillisOfDay(0), end.withDayOfMonth(1).withMillisOfDay(0)).getMonths();
+ case YEAR:
+ return Years.yearsBetween(start.withDayOfYear(1).withMillisOfDay(0), end.withDayOfYear(1).withMillisOfDay(0)).getYears();
+ default:
+ return -1;
+ }
+ }
+
+ /**
+ * Returns a quarter of the specified date
+ * @param date date
+ * @return a quarter
+ */
+ private int getQuarter(LocalDateTime date) {
+ return ((date.getMonthOfYear() - 1) / 3 + 1);
+ }
+
/**
* Converts the specified list of floats to an array
* @param list a list of floats
@@ -247,36 +344,13 @@ private float[] floatListToArray(List list) {
return array;
}
- /**
- * Sets up settings and data for the account type spinner. Currently used only {@code EXPENSE} and {@code INCOME}
- * account types.
- */
- private void setUpSpinner() {
- final Spinner spinner = (Spinner) findViewById(R.id.chart_data_spinner);
- ArrayAdapter dataAdapter = new ArrayAdapter<>(this,
- android.R.layout.simple_spinner_item,
- Arrays.asList(AccountType.EXPENSE, AccountType.INCOME));
- dataAdapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
- spinner.setAdapter(dataAdapter);
- spinner.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
- @Override
- public void onItemSelected(AdapterView> adapterView, View view, int i, long l) {
- mChart.setData(getData((AccountType) spinner.getSelectedItem()));
- displayChart();
- }
-
- @Override
- public void onNothingSelected(AdapterView> adapterView) {
- }
- });
- }
-
/**
* Displays the stacked bar chart
*/
private void displayChart() {
mChart.highlightValues(null);
- mChart.getLegend().setEnabled(false);
+ setCustomLegend();
+ mChart.notifyDataSetChanged();
mChart.getAxisLeft().setDrawLabels(mChartDataPresent);
mChart.getXAxis().setDrawLabels(mChartDataPresent);
@@ -294,50 +368,92 @@ private void displayChart() {
mChart.invalidate();
}
+ /**
+ * Sets custom legend. Disable legend if its items count greater than {@code COLORS} array size.
+ */
+ private void setCustomLegend() {
+ Legend legend = mChart.getLegend();
+ BarDataSet dataSet = mChart.getData().getDataSetByIndex(0);
+
+ LinkedHashSet labels = new LinkedHashSet<>(Arrays.asList(dataSet.getStackLabels()));
+ LinkedHashSet colors = new LinkedHashSet<>(dataSet.getColors());
+
+ if (COLORS.length >= labels.size()) {
+ legend.setCustom(new ArrayList<>(colors), new ArrayList<>(labels));
+ return;
+ }
+ legend.setEnabled(false);
+ }
+
+ @Override
+ public void onTimeRangeUpdated(long start, long end) {
+ if (mReportStartTime != start || mReportEndTime != end) {
+ mReportStartTime = start;
+ mReportEndTime = end;
+
+ mChart.setData(getData());
+ displayChart();
+ }
+ }
+
+ @Override
+ public void onGroupingUpdated(GroupInterval groupInterval) {
+ if (mGroupInterval != groupInterval) {
+ mGroupInterval = groupInterval;
+ mChart.setData(getData());
+ displayChart();
+ }
+ }
+
+ @Override
+ public void onAccountTypeUpdated(AccountType accountType) {
+ if (mAccountType != accountType) {
+ mAccountType = accountType;
+ mChart.setData(getData());
+ displayChart();
+ }
+ }
+
@Override
- public boolean onCreateOptionsMenu(Menu menu) {
- getSupportMenuInflater().inflate(R.menu.chart_actions, menu);
- return true;
+ public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
+ inflater.inflate(R.menu.chart_actions, menu);
}
@Override
- public boolean onPrepareOptionsMenu(Menu menu) {
+ public void onPrepareOptionsMenu(Menu menu) {
menu.findItem(R.id.menu_percentage_mode).setVisible(mChartDataPresent);
// hide pie/line chart specific menu items
menu.findItem(R.id.menu_order_by_size).setVisible(false);
menu.findItem(R.id.menu_toggle_labels).setVisible(false);
menu.findItem(R.id.menu_toggle_average_lines).setVisible(false);
menu.findItem(R.id.menu_group_other_slice).setVisible(false);
- return true;
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
+ if (item.isCheckable())
+ item.setChecked(!item.isChecked());
switch (item.getItemId()) {
case R.id.menu_toggle_legend:
- // workaround for buggy legend
Legend legend = mChart.getLegend();
- legend.setEnabled(!mChart.getLegend().isEnabled());
- BarDataSet dataSet = mChart.getData().getDataSetByIndex(0);
- LinkedHashSet labels = new LinkedHashSet<>(Arrays.asList(dataSet.getStackLabels()));
- legend.setLabels(labels.toArray(new String[labels.size()]));
- LinkedHashSet colors = new LinkedHashSet<>(dataSet.getColors());
- legend.setColors(Arrays.asList(colors.toArray(new Integer[colors.size()])));
- mChart.invalidate();
- break;
+ if (!legend.isLegendCustom()) {
+ Toast.makeText(getActivity(), R.string.toast_legend_too_long, Toast.LENGTH_LONG).show();
+ } else {
+ legend.setEnabled(!mChart.getLegend().isEnabled());
+ mChart.invalidate();
+ }
+ return true;
case R.id.menu_percentage_mode:
mTotalPercentageMode = !mTotalPercentageMode;
int msgId = mTotalPercentageMode ? R.string.toast_chart_percentage_mode_total
: R.string.toast_chart_percentage_mode_current_bar;
- Toast.makeText(this, msgId, Toast.LENGTH_LONG).show();
- break;
+ Toast.makeText(getActivity(), msgId, Toast.LENGTH_LONG).show();
+ return true;
- case android.R.id.home:
- finish();
- break;
+ default:
+ return super.onOptionsItemSelected(item);
}
- return true;
}
@Override
@@ -355,6 +471,6 @@ public void onValueSelected(Entry e, int dataSetIndex, Highlight h) {
@Override
public void onNothingSelected() {
- selectedValueTextView.setText("");
+ selectedValueTextView.setText(R.string.label_select_bar_to_view_details);
}
}
diff --git a/app/src/main/java/org/gnucash/android/ui/chart/LineChartActivity.java b/app/src/main/java/org/gnucash/android/ui/report/LineChartFragment.java
similarity index 53%
rename from app/src/main/java/org/gnucash/android/ui/chart/LineChartActivity.java
rename to app/src/main/java/org/gnucash/android/ui/report/LineChartFragment.java
index de11b6766..5c9e12aa5 100644
--- a/app/src/main/java/org/gnucash/android/ui/chart/LineChartActivity.java
+++ b/app/src/main/java/org/gnucash/android/ui/report/LineChartFragment.java
@@ -1,5 +1,6 @@
/*
* Copyright (c) 2015 Oleksandr Tyshkovets
+ * Copyright (c) 2015 Ngewi Fet
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -14,37 +15,43 @@
* limitations under the License.
*/
-package org.gnucash.android.ui.chart;
+package org.gnucash.android.ui.report;
import android.graphics.Color;
import android.os.Bundle;
-import android.preference.PreferenceManager;
+import android.support.annotation.Nullable;
+import android.support.v4.app.Fragment;
+import android.support.v7.app.AppCompatActivity;
import android.util.Log;
-import android.widget.LinearLayout;
+import android.view.LayoutInflater;
+import android.view.Menu;
+import android.view.MenuInflater;
+import android.view.MenuItem;
+import android.view.View;
+import android.view.ViewGroup;
import android.widget.TextView;
-import com.actionbarsherlock.view.Menu;
-import com.actionbarsherlock.view.MenuItem;
import com.github.mikephil.charting.charts.LineChart;
import com.github.mikephil.charting.components.Legend;
import com.github.mikephil.charting.components.LimitLine;
import com.github.mikephil.charting.data.Entry;
import com.github.mikephil.charting.data.LineData;
import com.github.mikephil.charting.data.LineDataSet;
+import com.github.mikephil.charting.highlight.Highlight;
import com.github.mikephil.charting.listener.OnChartValueSelectedListener;
-import com.github.mikephil.charting.utils.Highlight;
import com.github.mikephil.charting.utils.LargeValueFormatter;
import org.gnucash.android.R;
+import org.gnucash.android.app.GnuCashApplication;
import org.gnucash.android.db.AccountsDbAdapter;
import org.gnucash.android.db.TransactionsDbAdapter;
import org.gnucash.android.model.Account;
import org.gnucash.android.model.AccountType;
-import org.gnucash.android.model.Money;
-import org.gnucash.android.ui.passcode.PassLockActivity;
+import org.gnucash.android.ui.report.ReportsActivity.GroupInterval;
import org.joda.time.LocalDate;
import org.joda.time.LocalDateTime;
import org.joda.time.Months;
+import org.joda.time.Years;
import java.util.ArrayList;
import java.util.Arrays;
@@ -56,14 +63,19 @@
import java.util.Locale;
import java.util.Map;
+import butterknife.Bind;
+import butterknife.ButterKnife;
+
/**
- * Activity used for drawing a line chart
+ * Fragment for line chart reports
*
* @author Oleksandr Tyshkovets
+ * @author Ngewi Fet
*/
-public class LineChartActivity extends PassLockActivity implements OnChartValueSelectedListener {
+public class LineChartFragment extends Fragment implements OnChartValueSelectedListener,
+ ReportOptionsListener{
- private static final String TAG = "LineChartActivity";
+ private static final String TAG = "LineChartFragment";
private static final String X_AXIS_PATTERN = "MMM YY";
private static final String SELECTED_VALUE_PATTERN = "%s - %.2f (%.2f %%)";
private static final int ANIMATION_DURATION = 3000;
@@ -78,7 +90,6 @@ public class LineChartActivity extends PassLockActivity implements OnChartValueS
Color.parseColor("#0065FF"), Color.parseColor("#8F038A"),
};
- private LineChart mChart;
private AccountsDbAdapter mAccountsDbAdapter = AccountsDbAdapter.getInstance();
private Map mEarliestTimestampsMap = new HashMap<>();
private Map mLatestTimestampsMap = new HashMap<>();
@@ -87,18 +98,42 @@ public class LineChartActivity extends PassLockActivity implements OnChartValueS
private boolean mChartDataPresent = true;
private Currency mCurrency;
+ private GroupInterval mGroupInterval = GroupInterval.MONTH;
+
+ /**
+ * Reporting period start time
+ */
+ private long mReportStartTime = -1;
+
+ /**
+ * Reporting period end time
+ */
+ private long mReportEndTime = -1;
+
+ @Bind(R.id.line_chart) LineChart mChart;
+ @Bind(R.id.selected_chart_slice) TextView mChartSliceInfo;
+
+ @Nullable
+ @Override
+ public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
+ View view = inflater.inflate(R.layout.fragment_line_chart, container, false);
+ ButterKnife.bind(this, view);
+ return view;
+ }
+
@Override
- protected void onCreate(Bundle savedInstanceState) {
- //it is necessary to set the view first before calling super because of the nav drawer in BaseDrawerActivity
- setContentView(R.layout.activity_line_chart);
- super.onCreate(savedInstanceState);
- getSupportActionBar().setTitle(R.string.title_line_chart);
+ public void onActivityCreated(Bundle savedInstanceState) {
+ super.onActivityCreated(savedInstanceState);
- mCurrency = Currency.getInstance(PreferenceManager.getDefaultSharedPreferences(this)
- .getString(getString(R.string.key_report_currency), Money.DEFAULT_CURRENCY_CODE));
+ ((AppCompatActivity)getActivity()).getSupportActionBar().setTitle(R.string.title_line_chart);
+ setHasOptionsMenu(true);
+
+ mCurrency = Currency.getInstance(GnuCashApplication.getDefaultCurrencyCode());
+
+ ReportsActivity reportsActivity = (ReportsActivity) getActivity();
+ mReportStartTime = reportsActivity.getReportStartTime();
+ mReportEndTime = reportsActivity.getReportEndTime();
- mChart = new LineChart(this);
- ((LinearLayout) findViewById(R.id.chart)).addView(mChart);
mChart.setOnChartValueSelectedListener(this);
mChart.setDescription("");
mChart.getXAxis().setDrawGridLines(false);
@@ -110,7 +145,8 @@ protected void onCreate(Bundle savedInstanceState) {
mChart.setData(getData(new ArrayList<>(Arrays.asList(AccountType.INCOME, AccountType.EXPENSE))));
Legend legend = mChart.getLegend();
- legend.setPosition(Legend.LegendPosition.RIGHT_OF_CHART_INSIDE);
+ legend.setPosition(Legend.LegendPosition.BELOW_CHART_CENTER);
+ legend.setTextSize(16);
legend.setForm(Legend.LegendForm.CIRCLE);
if (!mChartDataPresent) {
@@ -118,28 +154,61 @@ protected void onCreate(Bundle savedInstanceState) {
mChart.getAxisLeft().setDrawLabels(false);
mChart.getXAxis().setDrawLabels(false);
mChart.setTouchEnabled(false);
- ((TextView) findViewById(R.id.selected_chart_slice)).setText(getResources().getString(R.string.label_chart_no_data));
+ mChartSliceInfo.setText(getResources().getString(R.string.label_chart_no_data));
} else {
mChart.animateX(ANIMATION_DURATION);
}
mChart.invalidate();
}
+ @Override
+ public void onResume() {
+ super.onResume();
+ ((ReportsActivity)getActivity()).setAppBarColor(R.color.account_blue);
+ }
+
/**
* Returns a data object that represents a user data of the specified account types
* @param accountTypeList account's types which will be displayed
* @return a {@code LineData} instance that represents a user data
*/
private LineData getData(List accountTypeList) {
+ Log.w(TAG, "getData");
calculateEarliestAndLatestTimestamps(accountTypeList);
+ // LocalDateTime?
+ LocalDate startDate;
+ LocalDate endDate;
+ if (mReportStartTime == -1 && mReportEndTime == -1) {
+ startDate = new LocalDate(mEarliestTransactionTimestamp).withDayOfMonth(1);
+ endDate = new LocalDate(mLatestTransactionTimestamp).withDayOfMonth(1);
+ } else {
+ startDate = new LocalDate(mReportStartTime).withDayOfMonth(1);
+ endDate = new LocalDate(mReportEndTime).withDayOfMonth(1);
+ }
- LocalDate startDate = new LocalDate(mEarliestTransactionTimestamp).withDayOfMonth(1);
- LocalDate endDate = new LocalDate(mLatestTransactionTimestamp).withDayOfMonth(1);
+ int count = getDateDiff(new LocalDateTime(startDate.toDate().getTime()), new LocalDateTime(endDate.toDate().getTime()));
+ Log.d(TAG, "X-axis count" + count);
List xValues = new ArrayList<>();
- while (!startDate.isAfter(endDate)) {
- xValues.add(startDate.toString(X_AXIS_PATTERN));
- Log.d(TAG, "X axis " + startDate.toString("MM yy"));
- startDate = startDate.plusMonths(1);
+ for (int i = 0; i <= count; i++) {
+ switch (mGroupInterval) {
+ case MONTH:
+ xValues.add(startDate.toString(X_AXIS_PATTERN));
+ Log.d(TAG, "X-axis " + startDate.toString("MM yy"));
+ startDate = startDate.plusMonths(1);
+ break;
+ case QUARTER:
+ int quarter = getQuarter(new LocalDateTime(startDate.toDate().getTime()));
+ xValues.add("Q" + quarter + startDate.toString(" yy"));
+ Log.d(TAG, "X-axis " + "Q" + quarter + startDate.toString(" MM yy"));
+ startDate = startDate.plusMonths(3);
+ break;
+ case YEAR:
+ xValues.add(startDate.toString("yyyy"));
+ Log.d(TAG, "X-axis " + startDate.toString("yyyy"));
+ startDate = startDate.plusYears(1);
+ break;
+// default:
+ }
}
List dataSets = new ArrayList<>();
@@ -161,6 +230,35 @@ private LineData getData(List accountTypeList) {
return lineData;
}
+ /**
+ * Calculates difference between two date values accordingly to {@code mGroupInterval}
+ * @param start start date
+ * @param end end date
+ * @return difference between two dates or {@code -1}
+ */
+ private int getDateDiff(LocalDateTime start, LocalDateTime end) {
+ switch (mGroupInterval) {
+ case QUARTER:
+ int y = Years.yearsBetween(start.withDayOfYear(1).withMillisOfDay(0), end.withDayOfYear(1).withMillisOfDay(0)).getYears();
+ return (getQuarter(end) - getQuarter(start) + y * 4);
+ case MONTH:
+ return Months.monthsBetween(start.withDayOfMonth(1).withMillisOfDay(0), end.withDayOfMonth(1).withMillisOfDay(0)).getMonths();
+ case YEAR:
+ return Years.yearsBetween(start.withDayOfYear(1).withMillisOfDay(0), end.withDayOfYear(1).withMillisOfDay(0)).getYears();
+ default:
+ return -1;
+ }
+ }
+
+ /**
+ * Returns a quarter of the specified date
+ * @param date date
+ * @return a quarter
+ */
+ private int getQuarter(LocalDateTime date) {
+ return ((date.getMonthOfYear() - 1) / 3 + 1);
+ }
+
/**
* Returns a data object that represents situation when no user data available
* @return a {@code LineData} instance for situation when no user data available
@@ -196,22 +294,49 @@ private List getEntryList(AccountType accountType) {
}
}
- LocalDateTime earliest = new LocalDateTime(mEarliestTimestampsMap.get(accountType));
- LocalDateTime latest = new LocalDateTime(mLatestTimestampsMap.get(accountType));
+ LocalDateTime earliest;
+ LocalDateTime latest;
+ if (mReportStartTime == -1 && mReportEndTime == -1) {
+ earliest = new LocalDateTime(mEarliestTimestampsMap.get(accountType));
+ latest = new LocalDateTime(mLatestTimestampsMap.get(accountType));
+ } else {
+ earliest = new LocalDateTime(mReportStartTime);
+ latest = new LocalDateTime(mReportEndTime);
+ }
Log.d(TAG, "Earliest " + accountType + " date " + earliest.toString("dd MM yyyy"));
Log.d(TAG, "Latest " + accountType + " date " + latest.toString("dd MM yyyy"));
- int months = Months.monthsBetween(earliest.withDayOfMonth(1).withMillisOfDay(0),
- latest.withDayOfMonth(1).withMillisOfDay(0)).getMonths();
-
- int offset = getXAxisOffset(accountType);
- List values = new ArrayList<>(months + 1);
- for (int i = 0; i < months + 1; i++) {
- long start = earliest.dayOfMonth().withMinimumValue().millisOfDay().withMinimumValue().toDate().getTime();
- long end = earliest.dayOfMonth().withMaximumValue().millisOfDay().withMaximumValue().toDate().getTime();
+
+ int xAxisOffset = getDateDiff(new LocalDateTime(mEarliestTransactionTimestamp), earliest);
+ int count = getDateDiff(earliest, latest);
+ List values = new ArrayList<>(count + 1);
+ for (int i = 0; i <= count; i++) {
+ long start = 0;
+ long end = 0;
+ switch (mGroupInterval) {
+ case QUARTER:
+ int quarter = getQuarter(earliest);
+ start = earliest.withMonthOfYear(quarter * 3 - 2).dayOfMonth().withMinimumValue().millisOfDay().withMinimumValue().toDate().getTime();
+ end = earliest.withMonthOfYear(quarter * 3).dayOfMonth().withMaximumValue().millisOfDay().withMaximumValue().toDate().getTime();
+
+ earliest = earliest.plusMonths(3);
+ break;
+ case MONTH:
+ start = earliest.dayOfMonth().withMinimumValue().millisOfDay().withMinimumValue().toDate().getTime();
+ end = earliest.dayOfMonth().withMaximumValue().millisOfDay().withMaximumValue().toDate().getTime();
+
+ earliest = earliest.plusMonths(1);
+ break;
+ case YEAR:
+ start = earliest.dayOfYear().withMinimumValue().millisOfDay().withMinimumValue().toDate().getTime();
+ end = earliest.dayOfYear().withMaximumValue().millisOfDay().withMaximumValue().toDate().getTime();
+
+ earliest = earliest.plusYears(1);
+ break;
+ }
float balance = (float) mAccountsDbAdapter.getAccountsBalance(accountUIDList, start, end).asDouble();
- values.add(new Entry(balance, i + offset));
+ values.add(new Entry(balance, i + xAxisOffset));
Log.d(TAG, accountType + earliest.toString(" MMM yyyy") + ", balance = " + balance);
- earliest = earliest.plusMonths(1);
+
}
return values;
@@ -222,6 +347,12 @@ private List getEntryList(AccountType accountType) {
* @param accountTypeList account's types which will be processed
*/
private void calculateEarliestAndLatestTimestamps(List accountTypeList) {
+ if (mReportStartTime != -1 && mReportEndTime != -1) {
+ mEarliestTransactionTimestamp = mReportStartTime;
+ mLatestTransactionTimestamp = mReportEndTime;
+ return;
+ }
+
TransactionsDbAdapter dbAdapter = TransactionsDbAdapter.getInstance();
for (Iterator iter = accountTypeList.iterator(); iter.hasNext();) {
AccountType type = iter.next();
@@ -246,43 +377,54 @@ private void calculateEarliestAndLatestTimestamps(List accountTypeL
mLatestTransactionTimestamp = timestamps.get(timestamps.size() - 1);
}
- /**
- * Returns a difference in months between the global earliest timestamp and the earliest
- * transaction's timestamp of the specified account type
- * @param accountType the account type
- * @return the difference in months
- */
- private int getXAxisOffset(AccountType accountType) {
- return Months.monthsBetween(
- new LocalDate(mEarliestTransactionTimestamp).withDayOfMonth(1),
- new LocalDate(mEarliestTimestampsMap.get(accountType)).withDayOfMonth(1)
- ).getMonths();
+ @Override
+ public void onTimeRangeUpdated(long start, long end) {
+ if (mReportStartTime != start || mReportEndTime != end) {
+ mReportStartTime = start;
+ mReportEndTime = end;
+ mChart.setData(getData(new ArrayList<>(Arrays.asList(AccountType.INCOME, AccountType.EXPENSE))));
+ mChart.invalidate();
+ }
+ }
+
+ @Override
+ public void onGroupingUpdated(GroupInterval groupInterval) {
+ if (mGroupInterval != groupInterval) {
+ mGroupInterval = groupInterval;
+ mChart.setData(getData(new ArrayList<>(Arrays.asList(AccountType.INCOME, AccountType.EXPENSE))));
+ mChart.invalidate();
+ }
+ }
+
+ @Override
+ public void onAccountTypeUpdated(AccountType accountType) {
+ //nothing to see here, line chart shows both income and expense
}
@Override
- public boolean onCreateOptionsMenu(Menu menu) {
- getSupportMenuInflater().inflate(R.menu.chart_actions, menu);
- return true;
+ public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
+ inflater.inflate(R.menu.chart_actions, menu);
}
@Override
- public boolean onPrepareOptionsMenu(Menu menu) {
+ public void onPrepareOptionsMenu(Menu menu) {
menu.findItem(R.id.menu_toggle_average_lines).setVisible(mChartDataPresent);
// hide pie/bar chart specific menu items
menu.findItem(R.id.menu_order_by_size).setVisible(false);
menu.findItem(R.id.menu_toggle_labels).setVisible(false);
menu.findItem(R.id.menu_percentage_mode).setVisible(false);
menu.findItem(R.id.menu_group_other_slice).setVisible(false);
- return true;
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
+ if (item.isCheckable())
+ item.setChecked(!item.isChecked());
switch (item.getItemId()) {
case R.id.menu_toggle_legend:
mChart.getLegend().setEnabled(!mChart.getLegend().isEnabled());
mChart.invalidate();
- break;
+ return true;
case R.id.menu_toggle_average_lines:
if (mChart.getAxisLeft().getLimitLines().isEmpty()) {
@@ -296,13 +438,11 @@ public boolean onOptionsItemSelected(MenuItem item) {
mChart.getAxisLeft().removeAllLimitLines();
}
mChart.invalidate();
- break;
+ return true;
- case android.R.id.home:
- finish();
- break;
+ default:
+ return super.onOptionsItemSelected(item);
}
- return true;
}
@Override
@@ -311,12 +451,11 @@ public void onValueSelected(Entry e, int dataSetIndex, Highlight h) {
String label = mChart.getData().getXVals().get(e.getXIndex());
double value = e.getVal();
double sum = mChart.getData().getDataSetByIndex(dataSetIndex).getYValueSum();
- ((TextView) findViewById(R.id.selected_chart_slice))
- .setText(String.format(SELECTED_VALUE_PATTERN, label, value, value / sum * 100));
+ mChartSliceInfo.setText(String.format(SELECTED_VALUE_PATTERN, label, value, value / sum * 100));
}
@Override
public void onNothingSelected() {
- ((TextView) findViewById(R.id.selected_chart_slice)).setText("");
+ mChartSliceInfo.setText("");
}
}
diff --git a/app/src/main/java/org/gnucash/android/ui/report/PieChartFragment.java b/app/src/main/java/org/gnucash/android/ui/report/PieChartFragment.java
new file mode 100644
index 000000000..64334bedd
--- /dev/null
+++ b/app/src/main/java/org/gnucash/android/ui/report/PieChartFragment.java
@@ -0,0 +1,377 @@
+/*
+ * Copyright (c) 2014-2015 Oleksandr Tyshkovets
+ * Copyright (c) 2015 Ngewi Fet
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gnucash.android.ui.report;
+
+import android.content.Context;
+import android.graphics.Color;
+import android.os.Bundle;
+import android.preference.PreferenceManager;
+import android.support.annotation.Nullable;
+import android.support.v4.app.Fragment;
+import android.support.v7.app.AppCompatActivity;
+import android.view.LayoutInflater;
+import android.view.Menu;
+import android.view.MenuInflater;
+import android.view.MenuItem;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.TextView;
+
+import com.github.mikephil.charting.charts.PieChart;
+import com.github.mikephil.charting.components.Legend.LegendForm;
+import com.github.mikephil.charting.components.Legend.LegendPosition;
+import com.github.mikephil.charting.data.Entry;
+import com.github.mikephil.charting.data.PieData;
+import com.github.mikephil.charting.data.PieDataSet;
+import com.github.mikephil.charting.highlight.Highlight;
+import com.github.mikephil.charting.listener.OnChartValueSelectedListener;
+
+import org.gnucash.android.R;
+import org.gnucash.android.app.GnuCashApplication;
+import org.gnucash.android.db.AccountsDbAdapter;
+import org.gnucash.android.db.TransactionsDbAdapter;
+import org.gnucash.android.model.Account;
+import org.gnucash.android.model.AccountType;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Currency;
+import java.util.List;
+import java.util.Locale;
+
+import butterknife.Bind;
+import butterknife.ButterKnife;
+
+/**
+ * Activity used for drawing a pie chart
+ *
+ * @author Oleksandr Tyshkovets
+ * @author Ngewi Fet
+ */
+public class PieChartFragment extends Fragment implements OnChartValueSelectedListener,
+ ReportOptionsListener {
+
+ public static final String SELECTED_VALUE_PATTERN = "%s - %.2f (%.2f %%)";
+ public static final String TOTAL_VALUE_LABEL_PATTERN = "%s\n%.2f %s";
+ private static final int ANIMATION_DURATION = 1800;
+ public static final int NO_DATA_COLOR = Color.LTGRAY;
+ public static final int CENTER_TEXT_SIZE = 18;
+ /**
+ * The space in degrees between the chart slices
+ */
+ public static final float SPACE_BETWEEN_SLICES = 2f;
+ /**
+ * All pie slices less than this threshold will be group in "other" slice. Using percents not absolute values.
+ */
+ private static final double GROUPING_SMALLER_SLICES_THRESHOLD = 5;
+
+ @Bind(R.id.pie_chart) PieChart mChart;
+ @Bind(R.id.selected_chart_slice) TextView mSelectedValueTextView;
+
+ private AccountsDbAdapter mAccountsDbAdapter;
+ private TransactionsDbAdapter mTransactionsDbAdapter;
+
+ private AccountType mAccountType;
+
+ private boolean mChartDataPresent = true;
+
+ private boolean mUseAccountColor = true;
+
+ private boolean mGroupSmallerSlices = true;
+
+ private String mCurrencyCode;
+
+ /**
+ * Start time for reporting period in millis
+ */
+ private long mReportStartTime = -1;
+
+ /**
+ * End time for reporting period in millis
+ */
+ private long mReportEndTime = -1;
+
+
+ @Nullable
+ @Override
+ public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
+ View view = inflater.inflate(R.layout.fragment_pie_chart, container, false);
+ ButterKnife.bind(this, view);
+ return view;
+ }
+
+ @Override
+ public void onActivityCreated(@Nullable Bundle savedInstanceState) {
+ super.onActivityCreated(savedInstanceState);
+
+ ((AppCompatActivity)getActivity()).getSupportActionBar().setTitle(R.string.title_pie_chart);
+ setHasOptionsMenu(true);
+
+ mUseAccountColor = PreferenceManager.getDefaultSharedPreferences(getActivity())
+ .getBoolean(getString(R.string.key_use_account_color), false);
+
+ mAccountsDbAdapter = AccountsDbAdapter.getInstance();
+ mTransactionsDbAdapter = TransactionsDbAdapter.getInstance();
+
+ mCurrencyCode = GnuCashApplication.getDefaultCurrencyCode();
+
+ mChart.setCenterTextSize(CENTER_TEXT_SIZE);
+ mChart.setDescription("");
+ mChart.getLegend().setWordWrapEnabled(true);
+ mChart.setOnChartValueSelectedListener(this);
+
+ ReportsActivity reportsActivity = (ReportsActivity) getActivity();
+ mReportStartTime = reportsActivity.getReportStartTime();
+ mReportEndTime = reportsActivity.getReportEndTime();
+ mAccountType = reportsActivity.getAccountType();
+
+ displayChart();
+ }
+
+ /**
+ * Sets the app bar color
+ */
+ @Override
+ public void onResume() {
+ super.onResume();
+ ((ReportsActivity)getActivity()).setAppBarColor(R.color.account_green);
+ }
+
+ /**
+ * Manages all actions about displaying the pie chart
+ */
+ private void displayChart() {
+ mSelectedValueTextView.setText(R.string.label_select_pie_slice_to_see_details);
+ mChart.highlightValues(null);
+ mChart.clear();
+
+ PieData pieData = getData();
+ if (pieData != null && pieData.getYValCount() != 0) {
+ mChartDataPresent = true;
+ mChart.setData(mGroupSmallerSlices ? groupSmallerSlices(pieData, getActivity()) : pieData);
+ float sum = mChart.getData().getYValueSum();
+ String total = getResources().getString(R.string.label_chart_total);
+ String currencySymbol = Currency.getInstance(mCurrencyCode).getSymbol(Locale.getDefault());
+ mChart.setCenterText(String.format(TOTAL_VALUE_LABEL_PATTERN, total, sum, currencySymbol));
+ mChart.animateXY(ANIMATION_DURATION, ANIMATION_DURATION);
+ } else {
+ mChartDataPresent = false;
+ mChart.setCenterText(getResources().getString(R.string.label_chart_no_data));
+ mChart.setData(getEmptyData());
+ }
+
+ mChart.setTouchEnabled(mChartDataPresent);
+ mChart.invalidate();
+ }
+
+ /**
+ * Returns {@code PieData} instance with data entries, colors and labels
+ * @return {@code PieData} instance
+ */
+ private PieData getData() {
+ PieDataSet dataSet = new PieDataSet(null, "");
+ List labels = new ArrayList<>();
+ List colors = new ArrayList<>();
+ for (Account account : mAccountsDbAdapter.getSimpleAccountList()) {
+ if (account.getAccountType() == mAccountType
+ && !account.isPlaceholderAccount()
+ && account.getCurrency() == Currency.getInstance(mCurrencyCode)) {
+
+ double balance = mAccountsDbAdapter.getAccountsBalance(Collections.singletonList(account.getUID()),
+ mReportStartTime, mReportEndTime).absolute().asDouble();
+ if (balance != 0) {
+ dataSet.addEntry(new Entry((float) balance, dataSet.getEntryCount()));
+ colors.add(mUseAccountColor && account.getColorHexCode() != null
+ ? Color.parseColor(account.getColorHexCode())
+ : ReportsActivity.COLORS[(dataSet.getEntryCount() - 1) % ReportsActivity.COLORS.length]);
+ labels.add(account.getName());
+ }
+ }
+ }
+ dataSet.setColors(colors);
+ dataSet.setSliceSpace(SPACE_BETWEEN_SLICES);
+ return new PieData(labels, dataSet);
+ }
+
+ @Override
+ public void onTimeRangeUpdated(long start, long end) {
+ if (mReportStartTime != start || mReportEndTime != end) {
+ mReportStartTime = start;
+ mReportEndTime = end;
+ displayChart();
+ }
+ }
+
+ @Override
+ public void onGroupingUpdated(ReportsActivity.GroupInterval groupInterval) {
+ //nothing to see here, this doesn't make sense for a pie chart
+ }
+
+ @Override
+ public void onAccountTypeUpdated(AccountType accountType) {
+ if (mAccountType != accountType) {
+ mAccountType = accountType;
+ displayChart();
+ }
+ }
+
+ /**
+ * Returns a data object that represents situation when no user data available
+ * @return a {@code PieData} instance for situation when no user data available
+ */
+ private PieData getEmptyData() {
+ PieDataSet dataSet = new PieDataSet(null, getResources().getString(R.string.label_chart_no_data));
+ dataSet.addEntry(new Entry(1, 0));
+ dataSet.setColor(NO_DATA_COLOR);
+ dataSet.setDrawValues(false);
+ return new PieData(Collections.singletonList(""), dataSet);
+ }
+
+ /**
+ * Sorts the pie's slices in ascending order
+ */
+ private void bubbleSort() {
+ List labels = mChart.getData().getXVals();
+ List values = mChart.getData().getDataSet().getYVals();
+ List colors = mChart.getData().getDataSet().getColors();
+ float tmp1;
+ String tmp2;
+ Integer tmp3;
+ for(int i = 0; i < values.size() - 1; i++) {
+ for(int j = 1; j < values.size() - i; j++) {
+ if (values.get(j-1).getVal() > values.get(j).getVal()) {
+ tmp1 = values.get(j - 1).getVal();
+ values.get(j - 1).setVal(values.get(j).getVal());
+ values.get(j).setVal(tmp1);
+
+ tmp2 = labels.get(j - 1);
+ labels.set(j - 1, labels.get(j));
+ labels.set(j, tmp2);
+
+ tmp3 = colors.get(j - 1);
+ colors.set(j - 1, colors.get(j));
+ colors.set(j, tmp3);
+ }
+ }
+ }
+
+ mChart.notifyDataSetChanged();
+ mChart.highlightValues(null);
+ mChart.invalidate();
+ }
+
+ @Override
+ public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
+ inflater.inflate(R.menu.chart_actions, menu);
+ menu.findItem(R.id.menu_toggle_legend).setChecked(false);
+ }
+
+ @Override
+ public void onPrepareOptionsMenu(Menu menu) {
+ menu.findItem(R.id.menu_order_by_size).setVisible(mChartDataPresent);
+ menu.findItem(R.id.menu_toggle_labels).setVisible(mChartDataPresent);
+ menu.findItem(R.id.menu_group_other_slice).setVisible(mChartDataPresent);
+ // hide line/bar chart specific menu items
+ menu.findItem(R.id.menu_percentage_mode).setVisible(false);
+ menu.findItem(R.id.menu_toggle_average_lines).setVisible(false);
+ menu.findItem(R.id.menu_group_reports_by).setVisible(false);
+ }
+
+ @Override
+ public boolean onOptionsItemSelected(MenuItem item) {
+ if (item.isCheckable())
+ item.setChecked(!item.isChecked());
+ switch (item.getItemId()) {
+ case R.id.menu_order_by_size: {
+ bubbleSort();
+ return true;
+ }
+ case R.id.menu_toggle_legend: {
+ mChart.getLegend().setEnabled(!mChart.getLegend().isEnabled());
+ mChart.getLegend().setForm(LegendForm.CIRCLE);
+ mChart.getLegend().setPosition(LegendPosition.RIGHT_OF_CHART_CENTER);
+ mChart.notifyDataSetChanged();
+ mChart.invalidate();
+ return true;
+ }
+ case R.id.menu_toggle_labels: {
+ mChart.getData().setDrawValues(!mChart.isDrawSliceTextEnabled());
+ mChart.setDrawSliceText(!mChart.isDrawSliceTextEnabled());
+ mChart.invalidate();
+ return true;
+ }
+ case R.id.menu_group_other_slice: {
+ mGroupSmallerSlices = !mGroupSmallerSlices;
+ displayChart();
+ return true;
+ }
+
+ default:
+ return super.onOptionsItemSelected(item);
+ }
+ }
+
+ /**
+ * Groups smaller slices. All smaller slices will be combined and displayed as a single "Other".
+ * @param data the pie data which smaller slices will be grouped
+ * @param context Context for retrieving resources
+ * @return a {@code PieData} instance with combined smaller slices
+ */
+ public static PieData groupSmallerSlices(PieData data, Context context) {
+ float otherSlice = 0f;
+ List newEntries = new ArrayList<>();
+ List newLabels = new ArrayList<>();
+ List newColors = new ArrayList<>();
+ List entries = data.getDataSet().getYVals();
+ for (int i = 0; i < entries.size(); i++) {
+ float val = entries.get(i).getVal();
+ if (val / data.getYValueSum() * 100 > GROUPING_SMALLER_SLICES_THRESHOLD) {
+ newEntries.add(new Entry(val, newEntries.size()));
+ newLabels.add(data.getXVals().get(i));
+ newColors.add(data.getDataSet().getColors().get(i));
+ } else {
+ otherSlice += val;
+ }
+ }
+
+ if (otherSlice > 0) {
+ newEntries.add(new Entry(otherSlice, newEntries.size()));
+ newLabels.add(context.getResources().getString(R.string.label_other_slice));
+ newColors.add(Color.LTGRAY);
+ }
+
+ PieDataSet dataSet = new PieDataSet(newEntries, "");
+ dataSet.setSliceSpace(SPACE_BETWEEN_SLICES);
+ dataSet.setColors(newColors);
+ return new PieData(newLabels, dataSet);
+ }
+
+ @Override
+ public void onValueSelected(Entry e, int dataSetIndex, Highlight h) {
+ if (e == null) return;
+ String label = mChart.getData().getXVals().get(e.getXIndex());
+ float value = e.getVal();
+ float percent = value / mChart.getYValueSum() * 100;
+ mSelectedValueTextView.setText(String.format(SELECTED_VALUE_PATTERN, label, value, percent));
+ }
+
+ @Override
+ public void onNothingSelected() {
+ mSelectedValueTextView.setText("");
+ }
+}
diff --git a/app/src/main/java/org/gnucash/android/ui/report/ReportOptionsListener.java b/app/src/main/java/org/gnucash/android/ui/report/ReportOptionsListener.java
new file mode 100644
index 000000000..7dfc1c717
--- /dev/null
+++ b/app/src/main/java/org/gnucash/android/ui/report/ReportOptionsListener.java
@@ -0,0 +1,43 @@
+/*
+ * Copyright (c) 2015 Ngewi Fet
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.gnucash.android.ui.report;
+
+import org.gnucash.android.model.AccountType;
+
+/**
+ * Listener interface for passing reporting options from activity to the report fragments
+ */
+public interface ReportOptionsListener {
+
+ /**
+ * Notify the implementing class of the selected date range
+ * @param start Start date in milliseconds since epoch
+ * @param end End date in milliseconds since epoch
+ */
+ void onTimeRangeUpdated(long start, long end);
+
+ /**
+ * Updates the listener on a change of the grouping for the report
+ * @param groupInterval Group interval
+ */
+ void onGroupingUpdated(ReportsActivity.GroupInterval groupInterval);
+
+ /**
+ * Update to the account type for the report
+ * @param accountType Account type
+ */
+ void onAccountTypeUpdated(AccountType accountType);
+}
diff --git a/app/src/main/java/org/gnucash/android/ui/report/ReportSummaryFragment.java b/app/src/main/java/org/gnucash/android/ui/report/ReportSummaryFragment.java
new file mode 100644
index 000000000..f41a2ae56
--- /dev/null
+++ b/app/src/main/java/org/gnucash/android/ui/report/ReportSummaryFragment.java
@@ -0,0 +1,262 @@
+/*
+ * Copyright (c) 2015 Ngewi Fet
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.gnucash.android.ui.report;
+
+import android.content.res.ColorStateList;
+import android.graphics.Color;
+import android.os.Build;
+import android.os.Bundle;
+import android.support.annotation.Nullable;
+import android.support.v4.app.Fragment;
+import android.support.v4.app.FragmentManager;
+import android.support.v4.app.FragmentTransaction;
+import android.support.v4.view.ViewCompat;
+import android.support.v7.app.AppCompatActivity;
+import android.support.v7.widget.AppCompatButton;
+import android.view.LayoutInflater;
+import android.view.Menu;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.Button;
+import android.widget.TextView;
+
+import com.github.mikephil.charting.charts.PieChart;
+import com.github.mikephil.charting.components.Legend;
+import com.github.mikephil.charting.data.Entry;
+import com.github.mikephil.charting.data.PieData;
+import com.github.mikephil.charting.data.PieDataSet;
+
+import org.gnucash.android.R;
+import org.gnucash.android.app.GnuCashApplication;
+import org.gnucash.android.db.AccountsDbAdapter;
+import org.gnucash.android.model.Account;
+import org.gnucash.android.model.AccountType;
+import org.gnucash.android.model.Money;
+import org.gnucash.android.ui.transaction.TransactionsActivity;
+import org.joda.time.LocalDate;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Currency;
+import java.util.List;
+import java.util.Locale;
+
+import butterknife.Bind;
+import butterknife.ButterKnife;
+
+/**
+ * Shows a summary of reports
+ * @author Ngewi Fet
+ */
+public class ReportSummaryFragment extends Fragment {
+
+ public static final int LEGEND_TEXT_SIZE = 14;
+
+ @Bind(R.id.btn_pie_chart) Button mPieChartButton;
+ @Bind(R.id.btn_bar_chart) Button mBarChartButton;
+ @Bind(R.id.btn_line_chart) Button mLineChartButton;
+ @Bind(R.id.btn_balance_sheet) Button mBalanceSheetButton;
+
+ @Bind(R.id.pie_chart) PieChart mChart;
+ @Bind(R.id.total_assets) TextView mTotalAssets;
+ @Bind(R.id.total_liabilities) TextView mTotalLiabilities;
+ @Bind(R.id.net_worth) TextView mNetWorth;
+
+ private AccountsDbAdapter mAccountsDbAdapter;
+
+ @Override
+ public void onCreate(@Nullable Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ mAccountsDbAdapter = AccountsDbAdapter.getInstance();
+ }
+
+ @Nullable
+ @Override
+ public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
+ View view = inflater.inflate(R.layout.fragment_report_summary, container, false);
+ ButterKnife.bind(this, view);
+
+ mPieChartButton.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View view) {
+ loadFragment(new PieChartFragment());
+ }
+ });
+
+ mLineChartButton.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View view) {
+ loadFragment(new LineChartFragment());
+ }
+ });
+
+ mBarChartButton.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View view) {
+ loadFragment(new BarChartFragment());
+ }
+ });
+
+ mBalanceSheetButton.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ loadFragment(new BalanceSheetFragment());
+ }
+ });
+
+ return view;
+ }
+
+ @Override
+ public void onResume() {
+ super.onResume();
+ ((AppCompatActivity)getActivity()).getSupportActionBar().setTitle(R.string.title_reports);
+ ((ReportsActivity)getActivity()).setAppBarColor(R.color.theme_primary);
+
+ getActivity().findViewById(R.id.time_range_layout).setVisibility(View.GONE);
+ getActivity().findViewById(R.id.date_range_divider).setVisibility(View.GONE);
+ }
+
+ @Override
+ public void onActivityCreated(@Nullable Bundle savedInstanceState) {
+ super.onActivityCreated(savedInstanceState);
+
+ setHasOptionsMenu(true);
+
+ mChart.setCenterTextSize(PieChartFragment.CENTER_TEXT_SIZE);
+ mChart.setDescription("");
+ mChart.getLegend().setEnabled(true);
+ mChart.getLegend().setPosition(Legend.LegendPosition.RIGHT_OF_CHART_CENTER);
+ mChart.getLegend().setTextSize(LEGEND_TEXT_SIZE);
+
+ ColorStateList csl = new ColorStateList(new int[][]{new int[0]}, new int[]{getResources().getColor(R.color.account_green)});
+ setButtonTint(mPieChartButton, csl);
+ csl = new ColorStateList(new int[][]{new int[0]}, new int[]{getResources().getColor(R.color.account_red)});
+ setButtonTint(mBarChartButton, csl);
+ csl = new ColorStateList(new int[][]{new int[0]}, new int[]{getResources().getColor(R.color.account_blue)});
+ setButtonTint(mLineChartButton, csl);
+ csl = new ColorStateList(new int[][]{new int[0]}, new int[]{getResources().getColor(R.color.account_purple)});
+ setButtonTint(mBalanceSheetButton, csl);
+
+
+ List accountTypes = new ArrayList<>();
+ accountTypes.add(AccountType.ASSET);
+ accountTypes.add(AccountType.CASH);
+ accountTypes.add(AccountType.BANK);
+ Money assetsBalance = mAccountsDbAdapter.getAccountBalance(accountTypes, -1, System.currentTimeMillis());
+
+ accountTypes.clear();
+ accountTypes.add(AccountType.LIABILITY);
+ accountTypes.add(AccountType.CREDIT);
+ Money liabilitiesBalance = mAccountsDbAdapter.getAccountBalance(accountTypes, -1, System.currentTimeMillis());
+
+ TransactionsActivity.displayBalance(mTotalAssets, assetsBalance);
+ TransactionsActivity.displayBalance(mTotalLiabilities, liabilitiesBalance);
+ TransactionsActivity.displayBalance(mNetWorth, assetsBalance.subtract(liabilitiesBalance));
+
+ displayChart();
+ }
+
+ @Override
+ public void onPrepareOptionsMenu(Menu menu) {
+ menu.findItem(R.id.menu_group_reports_by).setVisible(false);
+ }
+
+ /**
+ * Returns {@code PieData} instance with data entries, colors and labels
+ * @return {@code PieData} instance
+ */
+ private PieData getData() {
+ String mCurrencyCode = GnuCashApplication.getDefaultCurrencyCode();
+ PieDataSet dataSet = new PieDataSet(null, "");
+ List labels = new ArrayList<>();
+ List colors = new ArrayList<>();
+ for (Account account : mAccountsDbAdapter.getSimpleAccountList()) {
+ if (account.getAccountType() == AccountType.EXPENSE
+ && !account.isPlaceholderAccount()
+ && account.getCurrency() == Currency.getInstance(mCurrencyCode)) {
+
+ long start = new LocalDate().minusMonths(2).dayOfMonth().withMinimumValue().toDate().getTime();
+ long end = new LocalDate().plusDays(1).toDate().getTime();
+ double balance = mAccountsDbAdapter.getAccountsBalance(Collections.singletonList(account.getUID()), start, end).absolute().asDouble();
+ if (balance != 0) {
+ dataSet.addEntry(new Entry((float) balance, dataSet.getEntryCount()));
+ colors.add(account.getColorHexCode() != null
+ ? Color.parseColor(account.getColorHexCode())
+ : ReportsActivity.COLORS[(dataSet.getEntryCount() - 1) % ReportsActivity.COLORS.length]);
+ labels.add(account.getName());
+ }
+ }
+ }
+ dataSet.setColors(colors);
+ dataSet.setSliceSpace(PieChartFragment.SPACE_BETWEEN_SLICES);
+ return new PieData(labels, dataSet);
+ }
+
+ /**
+ * Manages all actions about displaying the pie chart
+ */
+ private void displayChart() {
+ mChart.highlightValues(null);
+ mChart.clear();
+
+ PieData pieData = PieChartFragment.groupSmallerSlices(getData(), getActivity());
+ if (pieData != null && pieData.getYValCount() != 0) {
+ mChart.setData(pieData);
+ float sum = mChart.getData().getYValueSum();
+ String total = getResources().getString(R.string.label_chart_total);
+ String currencySymbol = Currency.getInstance(GnuCashApplication.getDefaultCurrencyCode()).getSymbol(Locale.getDefault());
+ mChart.setCenterText(String.format(PieChartFragment.TOTAL_VALUE_LABEL_PATTERN, total, sum, currencySymbol));
+ mChart.animateXY(1800, 1800);
+ mChart.setTouchEnabled(true);
+ } else {
+ mChart.setData(getEmptyData());
+ }
+
+ mChart.invalidate();
+ }
+
+ /**
+ * Returns a data object that represents situation when no user data available
+ * @return a {@code PieData} instance for situation when no user data available
+ */
+ private PieData getEmptyData() {
+ PieDataSet dataSet = new PieDataSet(null, getResources().getString(R.string.label_chart_no_data));
+ dataSet.addEntry(new Entry(1, 0));
+ dataSet.setColor(PieChartFragment.NO_DATA_COLOR);
+ dataSet.setDrawValues(false);
+ return new PieData(Collections.singletonList(""), dataSet);
+ }
+
+
+ public void setButtonTint(Button button, ColorStateList tint) {
+ if (Build.VERSION.SDK_INT == Build.VERSION_CODES.LOLLIPOP && button instanceof AppCompatButton) {
+ ((AppCompatButton) button).setSupportBackgroundTintList(tint);
+ } else {
+ ViewCompat.setBackgroundTintList(button, tint);
+ }
+ button.setTextColor(getResources().getColor(android.R.color.white));
+ }
+
+ private void loadFragment(Fragment fragment){
+ FragmentManager fragmentManager = getActivity().getSupportFragmentManager();
+ FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction();
+
+ fragmentTransaction.replace(R.id.fragment_container, fragment);
+ fragmentTransaction.addToBackStack(null);
+ fragmentTransaction.commit();
+ }
+}
diff --git a/app/src/main/java/org/gnucash/android/ui/report/ReportsActivity.java b/app/src/main/java/org/gnucash/android/ui/report/ReportsActivity.java
new file mode 100644
index 000000000..a01c9be5a
--- /dev/null
+++ b/app/src/main/java/org/gnucash/android/ui/report/ReportsActivity.java
@@ -0,0 +1,320 @@
+/*
+ * Copyright (c) 2015 Oleksandr Tyshkovets
+ * Copyright (c) 2015 Ngewi Fet
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gnucash.android.ui.report;
+
+import android.app.DatePickerDialog;
+import android.graphics.Color;
+import android.graphics.drawable.ColorDrawable;
+import android.os.Build;
+import android.os.Bundle;
+import android.support.v4.app.DialogFragment;
+import android.support.v4.app.Fragment;
+import android.support.v4.app.FragmentManager;
+import android.support.v4.app.FragmentTransaction;
+import android.support.v7.app.ActionBar;
+import android.support.v7.widget.Toolbar;
+import android.view.Menu;
+import android.view.MenuItem;
+import android.view.View;
+import android.widget.AdapterView;
+import android.widget.ArrayAdapter;
+import android.widget.DatePicker;
+import android.widget.Spinner;
+
+import org.gnucash.android.R;
+import org.gnucash.android.app.GnuCashApplication;
+import org.gnucash.android.db.TransactionsDbAdapter;
+import org.gnucash.android.model.AccountType;
+import org.gnucash.android.ui.common.BaseDrawerActivity;
+import org.gnucash.android.ui.report.dialog.DateRangePickerDialogFragment;
+import org.joda.time.LocalDate;
+
+import java.util.Arrays;
+import java.util.Calendar;
+import java.util.Date;
+import java.util.List;
+
+import butterknife.Bind;
+import butterknife.ButterKnife;
+
+/**
+ * base activity for reporting
+ *
+ * @author Oleksandr Tyshkovets
+ * @author Ngewi Fet
+ */
+public class ReportsActivity extends BaseDrawerActivity implements AdapterView.OnItemSelectedListener,
+ DatePickerDialog.OnDateSetListener, DateRangePickerDialogFragment.OnDateRangeSetListener{
+
+ static final int[] COLORS = {
+ Color.parseColor("#17ee4e"), Color.parseColor("#cc1f09"), Color.parseColor("#3940f7"),
+ Color.parseColor("#f9cd04"), Color.parseColor("#5f33a8"), Color.parseColor("#e005b6"),
+ Color.parseColor("#17d6ed"), Color.parseColor("#e4a9a2"), Color.parseColor("#8fe6cd"),
+ Color.parseColor("#8b48fb"), Color.parseColor("#343a36"), Color.parseColor("#6decb1"),
+ Color.parseColor("#f0f8ff"), Color.parseColor("#5c3378"), Color.parseColor("#a6dcfd"),
+ Color.parseColor("#ba037c"), Color.parseColor("#708809"), Color.parseColor("#32072c"),
+ Color.parseColor("#fddef8"), Color.parseColor("#fa0e6e"), Color.parseColor("#d9e7b5")
+ };
+
+ @Bind(R.id.time_range_spinner) Spinner mTimeRangeSpinner;
+ @Bind(R.id.report_account_type_spinner) Spinner mAccountTypeSpinner;
+
+ private TransactionsDbAdapter mTransactionsDbAdapter;
+ private AccountType mAccountType = AccountType.EXPENSE;
+
+ public enum GroupInterval {WEEK, MONTH, QUARTER, YEAR, ALL}
+
+ // default time range is the last 3 months
+ private long mReportStartTime = new LocalDate().minusMonths(2).dayOfMonth().withMinimumValue().toDate().getTime();
+ private long mReportEndTime = new LocalDate().plusDays(1).toDate().getTime();
+
+ private GroupInterval mReportGroupInterval = GroupInterval.MONTH;
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.activity_reports);
+ setUpDrawer();
+ ButterKnife.bind(this);
+
+ Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
+ setSupportActionBar(toolbar);
+
+ ActionBar actionBar = getSupportActionBar();
+ assert actionBar != null;
+ actionBar.setTitle(R.string.title_reports);
+ actionBar.setDisplayHomeAsUpEnabled(true);
+
+ mTransactionsDbAdapter = TransactionsDbAdapter.getInstance();
+
+
+ ArrayAdapter adapter = ArrayAdapter.createFromResource(this, R.array.report_time_range,
+ android.R.layout.simple_spinner_item);
+ adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
+ mTimeRangeSpinner.setAdapter(adapter);
+ mTimeRangeSpinner.setOnItemSelectedListener(this);
+ mTimeRangeSpinner.setSelection(1);
+
+ ArrayAdapter dataAdapter = new ArrayAdapter<>(this,
+ android.R.layout.simple_spinner_item,
+ Arrays.asList(AccountType.EXPENSE, AccountType.INCOME));
+ dataAdapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
+ mAccountTypeSpinner.setAdapter(dataAdapter);
+ mAccountTypeSpinner.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
+ @Override
+ public void onItemSelected(AdapterView> adapterView, View view, int i, long l) {
+ mAccountType = (AccountType) mAccountTypeSpinner.getSelectedItem();
+ updateAccountTypeOnFragments();
+ }
+
+ @Override
+ public void onNothingSelected(AdapterView> adapterView) {
+ //nothing to see here, move along
+ }
+ });
+
+ if (savedInstanceState == null) {
+ FragmentManager fragmentManager = getSupportFragmentManager();
+ FragmentTransaction fragmentTransaction = fragmentManager
+ .beginTransaction();
+
+ fragmentTransaction.replace(R.id.fragment_container, new ReportSummaryFragment());
+ fragmentTransaction.commit();
+ }
+ }
+
+ @Override
+ public void onAttachFragment(Fragment fragment) {
+ super.onAttachFragment(fragment);
+ View timeRangeLayout = findViewById(R.id.time_range_layout);
+ View dateRangeDivider = findViewById(R.id.date_range_divider);
+ if (timeRangeLayout != null && dateRangeDivider != null) {
+ if (fragment instanceof ReportSummaryFragment || fragment instanceof BalanceSheetFragment) {
+ timeRangeLayout.setVisibility(View.GONE);
+ dateRangeDivider.setVisibility(View.GONE);
+ } else {
+ timeRangeLayout.setVisibility(View.VISIBLE);
+ dateRangeDivider.setVisibility(View.VISIBLE);
+ }
+ }
+ View accountTypeSpinner = findViewById(R.id.report_account_type_spinner);
+ if (accountTypeSpinner != null) {
+ if (fragment instanceof LineChartFragment) {
+ accountTypeSpinner.setVisibility(View.GONE);
+ } else {
+ accountTypeSpinner.setVisibility(View.VISIBLE);
+ }
+ }
+ }
+
+ /**
+ * Sets the color Action Bar and Status bar (where applicable)
+ */
+ public void setAppBarColor(int color) {
+ int resolvedColor = getResources().getColor(color);
+ if (getSupportActionBar() != null)
+ getSupportActionBar().setBackgroundDrawable(new ColorDrawable(resolvedColor));
+
+ if (Build.VERSION.SDK_INT > 20)
+ getWindow().setStatusBarColor(GnuCashApplication.darken(resolvedColor));
+ }
+
+ /**
+ * Updates the reporting time range for all listening fragments
+ */
+ private void updateDateRangeOnFragment(){
+ List fragments = getSupportFragmentManager().getFragments();
+ for (Fragment fragment : fragments) {
+ if (fragment instanceof ReportOptionsListener){
+ ((ReportOptionsListener) fragment).onTimeRangeUpdated(mReportStartTime, mReportEndTime);
+ }
+ }
+ }
+
+ /**
+ * Updates the account type for all attached fragments which are listening
+ */
+ private void updateAccountTypeOnFragments(){
+ List fragments = getSupportFragmentManager().getFragments();
+ for (Fragment fragment : fragments) {
+ if (fragment instanceof ReportOptionsListener){
+ ((ReportOptionsListener) fragment).onAccountTypeUpdated(mAccountType);
+ }
+ }
+ }
+
+ /**
+ * Updates the report grouping interval on all attached fragments which are listening
+ */
+ private void updateGroupingOnFragments(){
+ List fragments = getSupportFragmentManager().getFragments();
+ for (Fragment fragment : fragments) {
+ if (fragment instanceof ReportOptionsListener){
+ ((ReportOptionsListener) fragment).onGroupingUpdated(mReportGroupInterval);
+ }
+ }
+ }
+
+ @Override
+ public boolean onCreateOptionsMenu(Menu menu) {
+ getMenuInflater().inflate(R.menu.report_actions, menu);
+ return true;
+ }
+
+ @Override
+ public boolean onOptionsItemSelected(MenuItem item) {
+ switch (item.getItemId()){
+ case R.id.menu_group_reports_by:
+ return true;
+
+ case R.id.group_by_month:
+ item.setChecked(true);
+ mReportGroupInterval = GroupInterval.MONTH;
+ updateGroupingOnFragments();
+ return true;
+
+ case R.id.group_by_quarter:
+ item.setChecked(true);
+ mReportGroupInterval = GroupInterval.QUARTER;
+ updateGroupingOnFragments();
+ return true;
+
+ case R.id.group_by_year:
+ item.setChecked(true);
+ mReportGroupInterval = GroupInterval.YEAR;
+ updateGroupingOnFragments();
+ return true;
+
+ case android.R.id.home:
+ super.onOptionsItemSelected(item);
+
+ default:
+ return false;
+ }
+
+ }
+
+ @Override
+ public void onItemSelected(AdapterView> parent, View view, int position, long id) {
+ mReportEndTime = new LocalDate().plusDays(1).toDate().getTime();
+ switch (position){
+ case 0: //current month
+ mReportStartTime = new LocalDate().dayOfMonth().withMinimumValue().toDate().getTime();
+ break;
+ case 1: // last 3 months. x-2, x-1, x
+ mReportStartTime = new LocalDate().minusMonths(2).dayOfMonth().withMinimumValue().toDate().getTime();
+ break;
+ case 2:
+ mReportStartTime = new LocalDate().minusMonths(5).dayOfMonth().withMinimumValue().toDate().getTime();
+ break;
+ case 3:
+ mReportStartTime = new LocalDate().minusMonths(11).dayOfMonth().withMinimumValue().toDate().getTime();
+ break;
+ case 4: //ALL TIME
+ mReportStartTime = -1;
+ mReportEndTime = -1;
+ break;
+ case 5:
+ String mCurrencyCode = GnuCashApplication.getDefaultCurrencyCode();
+ long earliestTransactionTime = mTransactionsDbAdapter.getTimestampOfEarliestTransaction(mAccountType, mCurrencyCode);
+ DialogFragment rangeFragment = DateRangePickerDialogFragment.newInstance(
+ earliestTransactionTime,
+ new LocalDate().plusDays(1).toDate().getTime(),
+ this);
+ rangeFragment.show(getSupportFragmentManager(), "range_dialog");
+ break;
+ }
+ if (position != 5){ //the date picker will trigger the update itself
+ updateDateRangeOnFragment();
+ }
+ }
+
+ @Override
+ public void onNothingSelected(AdapterView> parent) {
+ //nothing to see here, move along
+ }
+
+ @Override
+ public void onDateSet(DatePicker view, int year, int monthOfYear, int dayOfMonth) {
+ Calendar calendar = Calendar.getInstance();
+ calendar.set(year, monthOfYear, dayOfMonth);
+ mReportStartTime = calendar.getTimeInMillis();
+ updateDateRangeOnFragment();
+ }
+
+ @Override
+ public void onDateRangeSet(Date startDate, Date endDate) {
+ mReportStartTime = startDate.getTime();
+ mReportEndTime = endDate.getTime();
+ updateDateRangeOnFragment();
+
+ }
+
+ public AccountType getAccountType(){
+ return mAccountType;
+ }
+
+ public long getReportEndTime() {
+ return mReportEndTime;
+ }
+
+ public long getReportStartTime() {
+ return mReportStartTime;
+ }
+
+}
diff --git a/app/src/main/java/org/gnucash/android/ui/report/dialog/DateRangePickerDialogFragment.java b/app/src/main/java/org/gnucash/android/ui/report/dialog/DateRangePickerDialogFragment.java
new file mode 100644
index 000000000..7681e7a08
--- /dev/null
+++ b/app/src/main/java/org/gnucash/android/ui/report/dialog/DateRangePickerDialogFragment.java
@@ -0,0 +1,118 @@
+/*
+ * Copyright (c) 2015 Ngewi Fet
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gnucash.android.ui.report.dialog;
+
+import android.app.Dialog;
+import android.os.Bundle;
+import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
+import android.support.v4.app.DialogFragment;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.Button;
+
+import com.squareup.timessquare.CalendarPickerView;
+
+import org.gnucash.android.R;
+import org.joda.time.LocalDate;
+
+import java.util.Calendar;
+import java.util.Date;
+import java.util.List;
+
+import butterknife.Bind;
+import butterknife.ButterKnife;
+
+/**
+ * Dialog for picking date ranges in terms of months.
+ * It is currently used for selecting ranges for reports
+ * @author Ngewi Fet
+ */
+public class DateRangePickerDialogFragment extends DialogFragment{
+
+ @Bind(R.id.calendar_view) CalendarPickerView mCalendarPickerView;
+ @Bind(R.id.btn_save) Button mDoneButton;
+ @Bind(R.id.btn_cancel) Button mCancelButton;
+
+ private Date mStartRange = LocalDate.now().minusMonths(1).toDate();
+ private Date mEndRange = LocalDate.now().toDate();
+ private OnDateRangeSetListener mDateRangeSetListener;
+
+ public static DateRangePickerDialogFragment newInstance(OnDateRangeSetListener dateRangeSetListener){
+ DateRangePickerDialogFragment fragment = new DateRangePickerDialogFragment();
+ fragment.mDateRangeSetListener = dateRangeSetListener;
+ return fragment;
+ }
+
+ public static DateRangePickerDialogFragment newInstance(long startDate, long endDate,
+ OnDateRangeSetListener dateRangeSetListener){
+ DateRangePickerDialogFragment fragment = new DateRangePickerDialogFragment();
+ fragment.mStartRange = new Date(startDate);
+ fragment.mEndRange = new Date(endDate);
+ fragment.mDateRangeSetListener = dateRangeSetListener;
+ return fragment;
+ }
+
+ @Nullable
+ @Override
+ public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
+ View view = inflater.inflate(R.layout.dialog_date_range_picker, container, false);
+ ButterKnife.bind(this, view);
+
+
+ Calendar nextYear = Calendar.getInstance();
+ nextYear.add(Calendar.YEAR, 1);
+
+ Date today = new Date();
+ mCalendarPickerView.init(mStartRange, mEndRange)
+ .inMode(CalendarPickerView.SelectionMode.RANGE)
+ .withSelectedDate(today);
+
+ mDoneButton.setText("Done");
+ mDoneButton.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ List selectedDates = mCalendarPickerView.getSelectedDates();
+ Date startDate = selectedDates.get(0);
+ Date endDate = selectedDates.size() == 2 ? selectedDates.get(1) : new Date();
+ mDateRangeSetListener.onDateRangeSet(startDate, endDate);
+ dismiss();
+ }
+ });
+
+ mCancelButton.setOnClickListener(new View.OnClickListener(){
+ @Override
+ public void onClick(View v) {
+ dismiss();
+ }
+ });
+ return view;
+ }
+
+ @NonNull
+ @Override
+ public Dialog onCreateDialog(Bundle savedInstanceState) {
+ Dialog dialog = super.onCreateDialog(savedInstanceState);
+ dialog.setTitle("Pick time range");
+ return dialog;
+ }
+
+ public interface OnDateRangeSetListener {
+ void onDateRangeSet(Date startDate, Date endDate);
+ }
+}
diff --git a/app/src/main/java/org/gnucash/android/ui/settings/AboutPreferenceFragment.java b/app/src/main/java/org/gnucash/android/ui/settings/AboutPreferenceFragment.java
index 18f39dae0..6194f0c38 100644
--- a/app/src/main/java/org/gnucash/android/ui/settings/AboutPreferenceFragment.java
+++ b/app/src/main/java/org/gnucash/android/ui/settings/AboutPreferenceFragment.java
@@ -17,15 +17,12 @@
package org.gnucash.android.ui.settings;
import android.annotation.TargetApi;
-import android.content.SharedPreferences;
import android.os.Bundle;
import android.preference.Preference;
import android.preference.PreferenceFragment;
-import android.preference.PreferenceManager;
-
-import com.actionbarsherlock.app.ActionBar;
-import com.actionbarsherlock.app.SherlockPreferenceActivity;
+import android.support.v7.app.ActionBar;
+import org.gnucash.android.BuildConfig;
import org.gnucash.android.R;
import org.gnucash.android.ui.account.AccountsActivity;
@@ -42,7 +39,7 @@ public class AboutPreferenceFragment extends PreferenceFragment{
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
addPreferencesFromResource(R.xml.fragment_about_preferences);
- ActionBar actionBar = ((SherlockPreferenceActivity) getActivity()).getSupportActionBar();
+ ActionBar actionBar = ((AppCompatPreferenceActivity) getActivity()).getSupportActionBar();
actionBar.setHomeButtonEnabled(true);
actionBar.setDisplayHomeAsUpEnabled(true);
actionBar.setTitle(R.string.title_about_gnucash);
@@ -52,7 +49,10 @@ public void onCreate(Bundle savedInstanceState) {
@Override
public void onResume() {
super.onResume();
- Preference pref = findPreference(getString(R.string.key_build_version));
+ Preference pref = findPreference(getString(R.string.key_about_gnucash));
+ if (BuildConfig.FLAVOR.equals("development")){
+ pref.setSummary(pref.getSummary() + " - Built: " + BuildConfig.BUILD_TIME);
+ }
pref.setOnPreferenceClickListener(new Preference.OnPreferenceClickListener() {
@Override
public boolean onPreferenceClick(Preference preference) {
diff --git a/app/src/main/java/org/gnucash/android/ui/settings/AccountPreferencesFragment.java b/app/src/main/java/org/gnucash/android/ui/settings/AccountPreferencesFragment.java
index 29d7baf7c..81804ccce 100644
--- a/app/src/main/java/org/gnucash/android/ui/settings/AccountPreferencesFragment.java
+++ b/app/src/main/java/org/gnucash/android/ui/settings/AccountPreferencesFragment.java
@@ -25,9 +25,8 @@
import android.preference.Preference;
import android.preference.PreferenceFragment;
import android.preference.PreferenceManager;
-
-import com.actionbarsherlock.app.ActionBar;
-import com.actionbarsherlock.app.SherlockPreferenceActivity;
+import android.support.v7.app.ActionBar;
+import android.support.v7.app.AppCompatActivity;
import org.gnucash.android.R;
import org.gnucash.android.model.Money;
@@ -49,7 +48,7 @@ public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
addPreferencesFromResource(R.xml.fragment_account_preferences);
- ActionBar actionBar = ((SherlockPreferenceActivity) getActivity()).getSupportActionBar();
+ ActionBar actionBar = ((AppCompatPreferenceActivity) getActivity()).getSupportActionBar();
actionBar.setHomeButtonEnabled(true);
actionBar.setDisplayHomeAsUpEnabled(true);
actionBar.setTitle(R.string.title_account_preferences);
@@ -85,7 +84,7 @@ public boolean onPreferenceClick(Preference preference) {
new AlertDialog.Builder(getActivity())
.setTitle(R.string.title_create_default_accounts)
.setMessage(R.string.msg_confirm_create_default_accounts_setting)
- .setIcon(android.R.drawable.ic_dialog_alert)
+ .setIcon(R.drawable.ic_warning_black_24dp)
.setPositiveButton(R.string.btn_create_accounts, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialogInterface, int i) {
diff --git a/app/src/main/java/org/gnucash/android/ui/settings/AppCompatPreferenceActivity.java b/app/src/main/java/org/gnucash/android/ui/settings/AppCompatPreferenceActivity.java
new file mode 100644
index 000000000..c0cea92c3
--- /dev/null
+++ b/app/src/main/java/org/gnucash/android/ui/settings/AppCompatPreferenceActivity.java
@@ -0,0 +1,128 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gnucash.android.ui.settings;
+
+import android.content.res.Configuration;
+import android.os.Bundle;
+import android.preference.PreferenceActivity;
+import android.support.annotation.LayoutRes;
+import android.support.annotation.Nullable;
+import android.support.v7.app.ActionBar;
+import android.support.v7.app.AppCompatDelegate;
+import android.support.v7.widget.Toolbar;
+import android.view.MenuInflater;
+import android.view.View;
+import android.view.ViewGroup;
+
+/**
+ * A {@link android.preference.PreferenceActivity} which implements and proxies the necessary calls
+ * to be used with AppCompat.
+ *
+ * This technique can be used with an {@link android.app.Activity} class, not just
+ * {@link android.preference.PreferenceActivity}.
+ */
+public abstract class AppCompatPreferenceActivity extends PreferenceActivity {
+
+ private AppCompatDelegate mDelegate;
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ getDelegate().installViewFactory();
+ getDelegate().onCreate(savedInstanceState);
+ super.onCreate(savedInstanceState);
+ }
+
+ @Override
+ protected void onPostCreate(Bundle savedInstanceState) {
+ super.onPostCreate(savedInstanceState);
+ getDelegate().onPostCreate(savedInstanceState);
+ }
+
+ public ActionBar getSupportActionBar() {
+ return getDelegate().getSupportActionBar();
+ }
+
+ public void setSupportActionBar(@Nullable Toolbar toolbar) {
+ getDelegate().setSupportActionBar(toolbar);
+ }
+
+ @Override
+ public MenuInflater getMenuInflater() {
+ return getDelegate().getMenuInflater();
+ }
+
+ @Override
+ public void setContentView(@LayoutRes int layoutResID) {
+ getDelegate().setContentView(layoutResID);
+ }
+
+ @Override
+ public void setContentView(View view) {
+ getDelegate().setContentView(view);
+ }
+
+ @Override
+ public void setContentView(View view, ViewGroup.LayoutParams params) {
+ getDelegate().setContentView(view, params);
+ }
+
+ @Override
+ public void addContentView(View view, ViewGroup.LayoutParams params) {
+ getDelegate().addContentView(view, params);
+ }
+
+ @Override
+ protected void onPostResume() {
+ super.onPostResume();
+ getDelegate().onPostResume();
+ }
+
+ @Override
+ protected void onTitleChanged(CharSequence title, int color) {
+ super.onTitleChanged(title, color);
+ getDelegate().setTitle(title);
+ }
+
+ @Override
+ public void onConfigurationChanged(Configuration newConfig) {
+ super.onConfigurationChanged(newConfig);
+ getDelegate().onConfigurationChanged(newConfig);
+ }
+
+ @Override
+ protected void onStop() {
+ super.onStop();
+ getDelegate().onStop();
+ }
+
+ @Override
+ protected void onDestroy() {
+ super.onDestroy();
+ getDelegate().onDestroy();
+ }
+
+ public void invalidateOptionsMenu() {
+ getDelegate().invalidateOptionsMenu();
+ }
+
+ private AppCompatDelegate getDelegate() {
+ if (mDelegate == null) {
+ mDelegate = AppCompatDelegate.create(this, null);
+ }
+ return mDelegate;
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/org/gnucash/android/ui/settings/BackupPreferenceFragment.java b/app/src/main/java/org/gnucash/android/ui/settings/BackupPreferenceFragment.java
index 4c7dc24f3..0f222e714 100644
--- a/app/src/main/java/org/gnucash/android/ui/settings/BackupPreferenceFragment.java
+++ b/app/src/main/java/org/gnucash/android/ui/settings/BackupPreferenceFragment.java
@@ -23,9 +23,8 @@
import android.preference.Preference.OnPreferenceChangeListener;
import android.preference.PreferenceFragment;
import android.preference.PreferenceManager;
-
-import com.actionbarsherlock.app.ActionBar;
-import com.actionbarsherlock.app.SherlockPreferenceActivity;
+import android.support.v7.app.ActionBar;
+import android.support.v7.app.AppCompatActivity;
import org.gnucash.android.R;
import org.gnucash.android.model.Money;
@@ -44,7 +43,7 @@ public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
addPreferencesFromResource(R.xml.fragment_backup_preferences);
- ActionBar actionBar = ((SherlockPreferenceActivity) getActivity()).getSupportActionBar();
+ ActionBar actionBar = ((AppCompatPreferenceActivity) getActivity()).getSupportActionBar();
actionBar.setHomeButtonEnabled(true);
actionBar.setDisplayHomeAsUpEnabled(true);
actionBar.setTitle(R.string.title_backup_prefs);
diff --git a/app/src/main/java/org/gnucash/android/ui/settings/DeleteAllAccountsConfirmationDialog.java b/app/src/main/java/org/gnucash/android/ui/settings/DeleteAllAccountsConfirmationDialog.java
index f1d73bafc..11001b531 100644
--- a/app/src/main/java/org/gnucash/android/ui/settings/DeleteAllAccountsConfirmationDialog.java
+++ b/app/src/main/java/org/gnucash/android/ui/settings/DeleteAllAccountsConfirmationDialog.java
@@ -28,7 +28,7 @@
import org.gnucash.android.R;
import org.gnucash.android.db.AccountsDbAdapter;
import org.gnucash.android.export.xml.GncXmlExporter;
-import org.gnucash.android.ui.widget.WidgetConfigurationActivity;
+import org.gnucash.android.ui.homescreen.WidgetConfigurationActivity;
/**
* Confirmation dialog for deleting all accounts from the system.
diff --git a/app/src/main/java/org/gnucash/android/ui/settings/DeleteAllTransactionsConfirmationDialog.java b/app/src/main/java/org/gnucash/android/ui/settings/DeleteAllTransactionsConfirmationDialog.java
index 3f37e2ee3..22ae73078 100644
--- a/app/src/main/java/org/gnucash/android/ui/settings/DeleteAllTransactionsConfirmationDialog.java
+++ b/app/src/main/java/org/gnucash/android/ui/settings/DeleteAllTransactionsConfirmationDialog.java
@@ -32,7 +32,7 @@
import org.gnucash.android.db.TransactionsDbAdapter;
import org.gnucash.android.export.xml.GncXmlExporter;
import org.gnucash.android.model.Transaction;
-import org.gnucash.android.ui.widget.WidgetConfigurationActivity;
+import org.gnucash.android.ui.homescreen.WidgetConfigurationActivity;
import java.util.ArrayList;
import java.util.List;
@@ -73,7 +73,7 @@ public void onClick(DialogInterface dialog, int whichButton) {
Log.i("DeleteDialog", String.format("Deleted %d transactions successfully", count));
if (preserveOpeningBalances) {
- transactionsDbAdapter.bulkAddTransactions(openingBalances);
+ transactionsDbAdapter.bulkAddRecords(openingBalances);
}
Toast.makeText(context, R.string.toast_all_transactions_deleted, Toast.LENGTH_SHORT).show();
WidgetConfigurationActivity.updateAllWidgets(getActivity());
diff --git a/app/src/main/java/org/gnucash/android/ui/settings/PasscodePreferenceFragment.java b/app/src/main/java/org/gnucash/android/ui/settings/GeneralPreferenceFragment.java
similarity index 85%
rename from app/src/main/java/org/gnucash/android/ui/settings/PasscodePreferenceFragment.java
rename to app/src/main/java/org/gnucash/android/ui/settings/GeneralPreferenceFragment.java
index d1662d998..18f8a9011 100644
--- a/app/src/main/java/org/gnucash/android/ui/settings/PasscodePreferenceFragment.java
+++ b/app/src/main/java/org/gnucash/android/ui/settings/GeneralPreferenceFragment.java
@@ -26,23 +26,21 @@
import android.preference.Preference.OnPreferenceChangeListener;
import android.preference.PreferenceFragment;
import android.preference.PreferenceManager;
+import android.support.v7.app.ActionBar;
import android.widget.Toast;
-import com.actionbarsherlock.app.ActionBar;
-import com.actionbarsherlock.app.SherlockPreferenceActivity;
-
import org.gnucash.android.R;
import org.gnucash.android.app.GnuCashApplication;
-import org.gnucash.android.ui.UxArgument;
+import org.gnucash.android.ui.common.UxArgument;
import org.gnucash.android.ui.passcode.PasscodeLockScreenActivity;
import org.gnucash.android.ui.passcode.PasscodePreferenceActivity;
/**
- * Fragment for configuring passcode to the application
+ * Fragment for general preferences. Currently caters to the passcode and reporting preferences
* @author Oleksandr Tyshkovets
*/
@TargetApi(11)
-public class PasscodePreferenceFragment extends PreferenceFragment {
+public class GeneralPreferenceFragment extends PreferenceFragment implements OnPreferenceChangeListener{
/**
* Request code for retrieving passcode to store
@@ -63,12 +61,12 @@ public class PasscodePreferenceFragment extends PreferenceFragment {
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
- addPreferencesFromResource(R.xml.fragment_passcode_preferences);
+ addPreferencesFromResource(R.xml.fragment_general_preferences);
- ActionBar actionBar = ((SherlockPreferenceActivity) getActivity()).getSupportActionBar();
+ ActionBar actionBar = ((AppCompatPreferenceActivity) getActivity()).getSupportActionBar();
actionBar.setHomeButtonEnabled(true);
actionBar.setDisplayHomeAsUpEnabled(true);
- actionBar.setTitle(R.string.title_passcode_preferences);
+ actionBar.setTitle(R.string.title_general_prefs);
}
@Override
@@ -105,6 +103,17 @@ public boolean onPreferenceClick(Preference preference) {
});
}
+ @Override
+ public boolean onPreferenceChange(Preference preference, Object newValue) {
+ if (preference.getKey().equals(getString(R.string.key_use_account_color))) {
+ PreferenceManager.getDefaultSharedPreferences(getActivity())
+ .edit()
+ .putBoolean(getString(R.string.key_use_account_color), Boolean.valueOf(newValue.toString()))
+ .commit();
+ }
+ return true;
+ }
+
@Override
public void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
diff --git a/app/src/main/java/org/gnucash/android/ui/settings/ReportPreferenceFragment.java b/app/src/main/java/org/gnucash/android/ui/settings/ReportPreferenceFragment.java
deleted file mode 100644
index abfeaa056..000000000
--- a/app/src/main/java/org/gnucash/android/ui/settings/ReportPreferenceFragment.java
+++ /dev/null
@@ -1,62 +0,0 @@
-/*
- * Copyright (c) 2015 Oleksandr Tyshkovets
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.gnucash.android.ui.settings;
-
-import android.annotation.TargetApi;
-import android.os.Bundle;
-import android.preference.Preference;
-import android.preference.Preference.OnPreferenceChangeListener;
-import android.preference.PreferenceFragment;
-import android.preference.PreferenceManager;
-
-import com.actionbarsherlock.app.ActionBar;
-import com.actionbarsherlock.app.SherlockPreferenceActivity;
-
-import org.gnucash.android.R;
-
-/**
- * Report settings fragment inside the Settings activity
- * @author Oleksandr Tyshkovets
- */
-@TargetApi(11)
-public class ReportPreferenceFragment extends PreferenceFragment implements OnPreferenceChangeListener {
-
- @Override
- public void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
-
- addPreferencesFromResource(R.xml.fragment_report_preferences);
- ActionBar actionBar = ((SherlockPreferenceActivity) getActivity()).getSupportActionBar();
- actionBar.setHomeButtonEnabled(true);
- actionBar.setDisplayHomeAsUpEnabled(true);
- actionBar.setTitle(R.string.title_report_prefs);
-
- findPreference(getString(R.string.key_use_account_color)).setOnPreferenceChangeListener(this);
- }
-
- @Override
- public boolean onPreferenceChange(Preference preference, Object newValue) {
- if (preference.getKey().equals(getString(R.string.key_use_account_color))) {
- PreferenceManager.getDefaultSharedPreferences(getActivity())
- .edit()
- .putBoolean(getString(R.string.key_use_account_color), Boolean.valueOf(newValue.toString()))
- .commit();
- }
- return true;
- }
-
-}
diff --git a/app/src/main/java/org/gnucash/android/ui/settings/SettingsActivity.java b/app/src/main/java/org/gnucash/android/ui/settings/SettingsActivity.java
index 3aca7b379..2e1e6247a 100644
--- a/app/src/main/java/org/gnucash/android/ui/settings/SettingsActivity.java
+++ b/app/src/main/java/org/gnucash/android/ui/settings/SettingsActivity.java
@@ -21,7 +21,6 @@
import android.annotation.TargetApi;
import android.app.Activity;
import android.app.AlertDialog;
-import android.content.ActivityNotFoundException;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
@@ -33,13 +32,12 @@
import android.preference.Preference;
import android.preference.Preference.OnPreferenceChangeListener;
import android.preference.PreferenceManager;
+import android.support.v7.app.ActionBar;
import android.util.Log;
+import android.view.MenuItem;
import android.widget.ArrayAdapter;
import android.widget.Toast;
-import com.actionbarsherlock.app.ActionBar;
-import com.actionbarsherlock.app.SherlockPreferenceActivity;
-import com.actionbarsherlock.view.MenuItem;
import com.crashlytics.android.Crashlytics;
import com.dropbox.sync.android.DbxAccountManager;
import com.google.android.gms.common.ConnectionResult;
@@ -60,8 +58,8 @@
import org.gnucash.android.importer.ImportAsyncTask;
import org.gnucash.android.model.Money;
import org.gnucash.android.model.Transaction;
-import org.gnucash.android.ui.UxArgument;
import org.gnucash.android.ui.account.AccountsActivity;
+import org.gnucash.android.ui.common.UxArgument;
import org.gnucash.android.ui.passcode.PasscodeLockScreenActivity;
import org.gnucash.android.ui.passcode.PasscodePreferenceActivity;
@@ -86,7 +84,8 @@
* @author Oleksandr Tyshkovets