From c13a94c2b3c10893452200bbb630e15821281725 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=80lex=20Magaz=20Gra=C3=A7a?= Date: Tue, 25 Oct 2016 18:24:39 +0200 Subject: [PATCH 01/25] Don't set an scheduled action's last run unless it's been executed. Every time the scheduled action service checked for scheduled actions to execute, it set the last run, even when the action hadn't been executed. As the service runs twice a day, the period would never pass. Fixes https://github.com/codinguser/gnucash-android/issues/604 --- .../service/ScheduledActionService.java | 34 +++++++++++-------- .../service/ScheduledActionServiceTest.java | 9 +++-- 2 files changed, 25 insertions(+), 18 deletions(-) diff --git a/app/src/main/java/org/gnucash/android/service/ScheduledActionService.java b/app/src/main/java/org/gnucash/android/service/ScheduledActionService.java index 58770873e..9fa8f0de8 100644 --- a/app/src/main/java/org/gnucash/android/service/ScheduledActionService.java +++ b/app/src/main/java/org/gnucash/android/service/ScheduledActionService.java @@ -127,7 +127,7 @@ public static void processScheduledActions(List scheduledAction */ private static void executeScheduledEvent(ScheduledAction scheduledAction, SQLiteDatabase db){ Log.i(LOG_TAG, "Executing scheduled action: " + scheduledAction.toString()); - int executionCount = scheduledAction.getExecutionCount(); + int executionCount = 0; switch (scheduledAction.getActionType()){ case TRANSACTION: @@ -139,20 +139,21 @@ private static void executeScheduledEvent(ScheduledAction scheduledAction, SQLit break; } - //the last run time is computed instead of just using "now" so that if the more than - // one period has been skipped, all intermediate transactions can be created - - scheduledAction.setLastRun(System.currentTimeMillis()); - //set the execution count in the object because it will be checked for the next iteration in the calling loop - scheduledAction.setExecutionCount(executionCount); //this call is important, do not remove!! - //update the last run time and execution count - ContentValues contentValues = new ContentValues(); - contentValues.put(DatabaseSchema.ScheduledActionEntry.COLUMN_LAST_RUN, - scheduledAction.getLastRunTime()); - contentValues.put(DatabaseSchema.ScheduledActionEntry.COLUMN_EXECUTION_COUNT, - scheduledAction.getExecutionCount()); - db.update(DatabaseSchema.ScheduledActionEntry.TABLE_NAME, contentValues, - DatabaseSchema.ScheduledActionEntry.COLUMN_UID + "=?", new String[]{scheduledAction.getUID()}); + if (executionCount > 0) { + scheduledAction.setLastRun(System.currentTimeMillis()); + // Set the execution count in the object because it will be checked + // for the next iteration in the calling loop. + // This call is important, do not remove!! + scheduledAction.setExecutionCount(scheduledAction.getExecutionCount() + executionCount); + // Update the last run time and execution count + ContentValues contentValues = new ContentValues(); + contentValues.put(DatabaseSchema.ScheduledActionEntry.COLUMN_LAST_RUN, + scheduledAction.getLastRunTime()); + contentValues.put(DatabaseSchema.ScheduledActionEntry.COLUMN_EXECUTION_COUNT, + scheduledAction.getExecutionCount()); + db.update(DatabaseSchema.ScheduledActionEntry.TABLE_NAME, contentValues, + DatabaseSchema.ScheduledActionEntry.COLUMN_UID + "=?", new String[]{scheduledAction.getUID()}); + } } /** @@ -219,6 +220,7 @@ private static int executeTransactions(ScheduledAction scheduledAction, SQLiteDa int totalPlannedExecutions = scheduledAction.getTotalPlannedExecutionCount(); List transactions = new ArrayList<>(); + int previousExecutionCount = scheduledAction.getExecutionCount(); // We'll modify it //we may be executing scheduled action significantly after scheduled time (depending on when Android fires the alarm) //so compute the actual transaction time from pre-known values long transactionTime = scheduledAction.computeNextCountBasedScheduledExecutionTime(); @@ -235,6 +237,8 @@ private static int executeTransactions(ScheduledAction scheduledAction, SQLiteDa } transactionsDbAdapter.bulkAddRecords(transactions, DatabaseAdapter.UpdateMethod.insert); + // Be nice and restore the parameter's original state to avoid confusing the callers + scheduledAction.setExecutionCount(previousExecutionCount); return executionCount; } } diff --git a/app/src/test/java/org/gnucash/android/test/unit/service/ScheduledActionServiceTest.java b/app/src/test/java/org/gnucash/android/test/unit/service/ScheduledActionServiceTest.java index 4b8dfda77..83f57c61a 100644 --- a/app/src/test/java/org/gnucash/android/test/unit/service/ScheduledActionServiceTest.java +++ b/app/src/test/java/org/gnucash/android/test/unit/service/ScheduledActionServiceTest.java @@ -290,9 +290,6 @@ public void recurringTransactions_shouldHaveScheduledActionUID(){ * was done on Monday and it's Thursday, two backups have been * missed. Doing the two missed backups plus today's wouldn't be * useful, so just one should be done.

- * - *

Note: the execution count will include the missed runs - * as computeNextCountBasedScheduledExecutionTime depends on it.

*/ @Test public void scheduledBackups_shouldRunOnlyOnce(){ @@ -302,6 +299,7 @@ public void scheduledBackups_shouldRunOnlyOnce(){ scheduledBackup.setRecurrence(PeriodType.MONTH, 1); scheduledBackup.setExecutionCount(2); scheduledBackup.setLastRun(LocalDateTime.now().minusMonths(2).toDate().getTime()); + long previousLastRun = scheduledBackup.getLastRunTime(); ExportParams backupParams = new ExportParams(ExportFormat.XML); backupParams.setExportTarget(ExportParams.ExportTarget.SD_CARD); @@ -317,13 +315,16 @@ public void scheduledBackups_shouldRunOnlyOnce(){ // Check there's not a backup for each missed run ScheduledActionService.processScheduledActions(actions, mDb); assertThat(scheduledBackup.getExecutionCount()).isEqualTo(3); + assertThat(scheduledBackup.getLastRunTime()).isGreaterThan(previousLastRun); File[] backupFiles = backupFolder.listFiles(); assertThat(backupFiles).hasSize(1); assertThat(backupFiles[0]).exists().hasExtension("gnca"); // Check also across service runs + previousLastRun = scheduledBackup.getLastRunTime(); ScheduledActionService.processScheduledActions(actions, mDb); assertThat(scheduledBackup.getExecutionCount()).isEqualTo(3); + assertThat(scheduledBackup.getLastRunTime()).isEqualTo(previousLastRun); backupFiles = backupFolder.listFiles(); assertThat(backupFiles).hasSize(1); assertThat(backupFiles[0]).exists().hasExtension("gnca"); @@ -340,6 +341,7 @@ public void scheduledBackups_shouldNotRunBeforeNextScheduledExecution(){ ScheduledAction scheduledBackup = new ScheduledAction(ScheduledAction.ActionType.BACKUP); scheduledBackup.setStartTime(LocalDateTime.now().minusDays(2).toDate().getTime()); scheduledBackup.setLastRun(scheduledBackup.getStartTime()); + long previousLastRun = scheduledBackup.getLastRunTime(); scheduledBackup.setExecutionCount(1); scheduledBackup.setRecurrence(PeriodType.WEEK, 1); @@ -357,6 +359,7 @@ public void scheduledBackups_shouldNotRunBeforeNextScheduledExecution(){ ScheduledActionService.processScheduledActions(actions, mDb); assertThat(scheduledBackup.getExecutionCount()).isEqualTo(1); + assertThat(scheduledBackup.getLastRunTime()).isEqualTo(previousLastRun); assertThat(backupFolder.listFiles()).hasSize(0); } From 837210242c015b86734bb314090e4bc4e22a54e2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=80lex=20Magaz=20Gra=C3=A7a?= Date: Sun, 30 Oct 2016 20:26:27 +0100 Subject: [PATCH 02/25] Export only transactions since the last run of the scheduled export. We were getting transactions since the creation of the scheduled export. Fixes https://github.com/codinguser/gnucash-android/issues/609 --- .../service/ScheduledActionService.java | 3 + .../service/ScheduledActionServiceTest.java | 109 +++++++++++++++++- 2 files changed, 108 insertions(+), 4 deletions(-) diff --git a/app/src/main/java/org/gnucash/android/service/ScheduledActionService.java b/app/src/main/java/org/gnucash/android/service/ScheduledActionService.java index 9fa8f0de8..e792fc223 100644 --- a/app/src/main/java/org/gnucash/android/service/ScheduledActionService.java +++ b/app/src/main/java/org/gnucash/android/service/ScheduledActionService.java @@ -41,6 +41,7 @@ import org.gnucash.android.model.ScheduledAction; import org.gnucash.android.model.Transaction; +import java.sql.Timestamp; import java.util.ArrayList; import java.util.Date; import java.util.List; @@ -168,6 +169,8 @@ private static int executeBackup(ScheduledAction scheduledAction, SQLiteDatabase return 0; ExportParams params = ExportParams.parseCsv(scheduledAction.getTag()); + // HACK: the tag isn't updated with the new date, so set the correct by hand + params.setExportStartTime(new Timestamp(scheduledAction.getLastRunTime())); try { //wait for async task to finish before we proceed (we are holding a wake lock) new ExportAsyncTask(GnuCashApplication.getAppContext(), db).execute(params).get(); diff --git a/app/src/test/java/org/gnucash/android/test/unit/service/ScheduledActionServiceTest.java b/app/src/test/java/org/gnucash/android/test/unit/service/ScheduledActionServiceTest.java index 83f57c61a..ac1db752e 100644 --- a/app/src/test/java/org/gnucash/android/test/unit/service/ScheduledActionServiceTest.java +++ b/app/src/test/java/org/gnucash/android/test/unit/service/ScheduledActionServiceTest.java @@ -15,12 +15,13 @@ */ package org.gnucash.android.test.unit.service; +import android.content.ContentValues; import android.database.sqlite.SQLiteDatabase; -import android.support.annotation.NonNull; import org.gnucash.android.BuildConfig; import org.gnucash.android.R; import org.gnucash.android.app.GnuCashApplication; +import org.gnucash.android.db.DatabaseSchema; import org.gnucash.android.db.adapter.AccountsDbAdapter; import org.gnucash.android.db.adapter.BooksDbAdapter; import org.gnucash.android.db.adapter.CommoditiesDbAdapter; @@ -39,10 +40,12 @@ import org.gnucash.android.model.ScheduledAction; import org.gnucash.android.model.Split; import org.gnucash.android.model.Transaction; +import org.gnucash.android.model.TransactionType; import org.gnucash.android.service.ScheduledActionService; import org.gnucash.android.test.unit.testutil.GnucashTestRunner; import org.gnucash.android.test.unit.testutil.ShadowCrashlytics; import org.gnucash.android.test.unit.testutil.ShadowUserVoice; +import org.gnucash.android.util.TimestampHelper; import org.joda.time.DateTime; import org.joda.time.LocalDateTime; import org.joda.time.Weeks; @@ -57,6 +60,7 @@ import java.io.File; import java.io.IOException; import java.math.BigDecimal; +import java.sql.Timestamp; import java.util.ArrayList; import java.util.List; @@ -79,6 +83,7 @@ public class ScheduledActionServiceTest { private static Account mTransferAccount = new Account("Transfer Account"); private static Transaction mTemplateTransaction; + private TransactionsDbAdapter mTransactionsDbAdapter; public void createAccounts(){ try { @@ -117,9 +122,8 @@ public void setUp(){ accountsDbAdapter.addRecord(mBaseAccount); accountsDbAdapter.addRecord(mTransferAccount); - TransactionsDbAdapter transactionsDbAdapter = TransactionsDbAdapter.getInstance(); - transactionsDbAdapter.addRecord(mTemplateTransaction, DatabaseAdapter.UpdateMethod.insert); - + mTransactionsDbAdapter = TransactionsDbAdapter.getInstance(); + mTransactionsDbAdapter.addRecord(mTemplateTransaction, DatabaseAdapter.UpdateMethod.insert); } @Test @@ -363,6 +367,103 @@ public void scheduledBackups_shouldNotRunBeforeNextScheduledExecution(){ assertThat(backupFolder.listFiles()).hasSize(0); } + /** + * Tests that an scheduled backup doesn't include transactions added or modified + * previous to the last run. + */ + @Test + public void scheduledBackups_shouldNotIncludeTransactionsPreviousToTheLastRun() { + ScheduledAction scheduledBackup = new ScheduledAction(ScheduledAction.ActionType.BACKUP); + scheduledBackup.setStartTime(LocalDateTime.now().minusDays(15).toDate().getTime()); + scheduledBackup.setLastRun(LocalDateTime.now().minusDays(8).toDate().getTime()); + long previousLastRun = scheduledBackup.getLastRunTime(); + scheduledBackup.setExecutionCount(1); + scheduledBackup.setRecurrence(PeriodType.WEEK, 1); + ExportParams backupParams = new ExportParams(ExportFormat.QIF); + backupParams.setExportTarget(ExportParams.ExportTarget.SD_CARD); + backupParams.setExportStartTime(new Timestamp(scheduledBackup.getStartTime())); + scheduledBackup.setTag(backupParams.toCsv()); + + // Create a transaction with a modified date previous to the last run + Transaction transaction = new Transaction("Tandoori express"); + Split split = new Split(new Money("10", Commodity.DEFAULT_COMMODITY.getCurrencyCode()), + mBaseAccount.getUID()); + split.setType(TransactionType.DEBIT); + transaction.addSplit(split); + transaction.addSplit(split.createPair(mTransferAccount.getUID())); + mTransactionsDbAdapter.addRecord(transaction); + // We set the date directly in the database as the corresponding field + // is ignored when the object is stored. It's set through a trigger instead. + setTransactionInDbModifiedTimestamp(transaction.getUID(), + new Timestamp(LocalDateTime.now().minusDays(9).toDate().getTime())); + + File backupFolder = new File( + Exporter.getExportFolderPath(BooksDbAdapter.getInstance().getActiveBookUID())); + assertThat(backupFolder).exists(); + assertThat(backupFolder.listFiles()).isEmpty(); + + List actions = new ArrayList<>(); + actions.add(scheduledBackup); + ScheduledActionService.processScheduledActions(actions, mDb); + + assertThat(scheduledBackup.getExecutionCount()).isEqualTo(2); + assertThat(scheduledBackup.getLastRunTime()).isGreaterThan(previousLastRun); + assertThat(backupFolder.listFiles()).hasSize(0); + } + + /** + * Sets the transaction modified timestamp directly in the database. + * + * @param transactionUID UID of the transaction to set the modified timestamp. + * @param timestamp new modified timestamp. + */ + private void setTransactionInDbModifiedTimestamp(String transactionUID, Timestamp timestamp) { + ContentValues values = new ContentValues(); + values.put(DatabaseSchema.TransactionEntry.COLUMN_MODIFIED_AT, + TimestampHelper.getUtcStringFromTimestamp(timestamp)); + mTransactionsDbAdapter.updateTransaction(values, "uid = ?", + new String[]{transactionUID}); + } + + /** + * Tests that an scheduled backup includes transactions added or modified + * after the last run. + */ + @Test + public void scheduledBackups_shouldIncludeTransactionsAfterTheLastRun() { + ScheduledAction scheduledBackup = new ScheduledAction(ScheduledAction.ActionType.BACKUP); + scheduledBackup.setStartTime(LocalDateTime.now().minusDays(15).toDate().getTime()); + scheduledBackup.setLastRun(LocalDateTime.now().minusDays(8).toDate().getTime()); + long previousLastRun = scheduledBackup.getLastRunTime(); + scheduledBackup.setExecutionCount(1); + scheduledBackup.setRecurrence(PeriodType.WEEK, 1); + ExportParams backupParams = new ExportParams(ExportFormat.QIF); + backupParams.setExportTarget(ExportParams.ExportTarget.SD_CARD); + backupParams.setExportStartTime(new Timestamp(scheduledBackup.getStartTime())); + scheduledBackup.setTag(backupParams.toCsv()); + + Transaction transaction = new Transaction("Orient palace"); + Split split = new Split(new Money("10", Commodity.DEFAULT_COMMODITY.getCurrencyCode()), + mBaseAccount.getUID()); + split.setType(TransactionType.DEBIT); + transaction.addSplit(split); + transaction.addSplit(split.createPair(mTransferAccount.getUID())); + mTransactionsDbAdapter.addRecord(transaction); + + File backupFolder = new File( + Exporter.getExportFolderPath(BooksDbAdapter.getInstance().getActiveBookUID())); + assertThat(backupFolder).exists(); + assertThat(backupFolder.listFiles()).isEmpty(); + + List actions = new ArrayList<>(); + actions.add(scheduledBackup); + ScheduledActionService.processScheduledActions(actions, mDb); + + assertThat(scheduledBackup.getExecutionCount()).isEqualTo(2); + assertThat(scheduledBackup.getLastRunTime()).isGreaterThan(previousLastRun); + assertThat(backupFolder.listFiles()).hasSize(1); + } + @After public void tearDown(){ TransactionsDbAdapter.getInstance().deleteAllRecords(); From f9b3a301af6b1a10e146a6a412efdb9f295db152 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=80lex=20Magaz=20Gra=C3=A7a?= Date: Thu, 10 Nov 2016 20:06:29 +0100 Subject: [PATCH 03/25] Export transactions with multiple currencies when using QIF. As QIF doesn't support currencies, they were excluded. See related issue: https://github.com/codinguser/gnucash-android/issues/218 Fixes https://github.com/codinguser/gnucash-android/issues/571 --- .../main/java/org/gnucash/android/export/qif/QifExporter.java | 2 -- 1 file changed, 2 deletions(-) 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 7b25d0cb4..0e64df8db 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 @@ -95,8 +95,6 @@ public List generateExport() throws ExporterException { }, // no recurrence transactions TransactionEntry.TABLE_NAME + "_" + TransactionEntry.COLUMN_TEMPLATE + " == 0 AND " + - // exclude transactions involving multiple currencies - "trans_extra_info.trans_currency_count = 1 AND " + // in qif, split from the one account entry is not recorded (will be auto balanced) "( " + AccountEntry.TABLE_NAME + "_" + AccountEntry.COLUMN_UID + " != account1." + AccountEntry.COLUMN_UID + " OR " + // or if the transaction has only one split (the whole transaction would be lost if it is not selected) From 1c5f928c73b4649efdf9016fa76afeac84c38c73 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=80lex=20Magaz=20Gra=C3=A7a?= Date: Sat, 19 Nov 2016 09:45:02 +0100 Subject: [PATCH 04/25] Show book last export date as "never" when it has never been exported. The epoch date was shown instead. Fixes https://github.com/codinguser/gnucash-android/issues/615 --- .../gnucash/android/ui/settings/BookManagerFragment.java | 8 +++++++- app/src/main/res/values/strings.xml | 1 + 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/app/src/main/java/org/gnucash/android/ui/settings/BookManagerFragment.java b/app/src/main/java/org/gnucash/android/ui/settings/BookManagerFragment.java index ff7fa0e67..723bc0d87 100644 --- a/app/src/main/java/org/gnucash/android/ui/settings/BookManagerFragment.java +++ b/app/src/main/java/org/gnucash/android/ui/settings/BookManagerFragment.java @@ -54,6 +54,8 @@ import org.gnucash.android.ui.common.Refreshable; import org.gnucash.android.util.PreferencesHelper; +import java.sql.Timestamp; + /** * Fragment for managing the books in the database */ @@ -159,8 +161,12 @@ public void bindView(View view, final Context context, Cursor cursor) { final String bookUID = cursor.getString(cursor.getColumnIndexOrThrow(BookEntry.COLUMN_UID)); + Timestamp lastSyncTime = PreferencesHelper.getLastExportTime(bookUID); TextView lastSyncText = (TextView) view.findViewById(R.id.last_sync_time); - lastSyncText.setText(PreferencesHelper.getLastExportTime(bookUID).toString()); + if (lastSyncTime.equals(new Timestamp(0))) + lastSyncText.setText(R.string.last_export_time_never); + else + lastSyncText.setText(lastSyncTime.toString()); TextView labelLastSync = (TextView) view.findViewById(R.id.label_last_sync); labelLastSync.setText(R.string.label_last_export_time); diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 206a96857..058c5613e 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -486,4 +486,5 @@ for %1$s times Compact View Book %1$d + never From d95b5ae310217b6c2c723c4f22fb1eaa2cb16026 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=80lex=20Magaz=20Gra=C3=A7a?= Date: Sat, 19 Nov 2016 11:30:58 +0100 Subject: [PATCH 05/25] Extract code from BooksCursorAdapter.bindView into new methods. --- .../ui/settings/BookManagerFragment.java | 52 +++++++++++-------- 1 file changed, 29 insertions(+), 23 deletions(-) diff --git a/app/src/main/java/org/gnucash/android/ui/settings/BookManagerFragment.java b/app/src/main/java/org/gnucash/android/ui/settings/BookManagerFragment.java index 723bc0d87..718b8602c 100644 --- a/app/src/main/java/org/gnucash/android/ui/settings/BookManagerFragment.java +++ b/app/src/main/java/org/gnucash/android/ui/settings/BookManagerFragment.java @@ -161,29 +161,8 @@ public void bindView(View view, final Context context, Cursor cursor) { final String bookUID = cursor.getString(cursor.getColumnIndexOrThrow(BookEntry.COLUMN_UID)); - Timestamp lastSyncTime = PreferencesHelper.getLastExportTime(bookUID); - TextView lastSyncText = (TextView) view.findViewById(R.id.last_sync_time); - if (lastSyncTime.equals(new Timestamp(0))) - lastSyncText.setText(R.string.last_export_time_never); - else - lastSyncText.setText(lastSyncTime.toString()); - - TextView labelLastSync = (TextView) view.findViewById(R.id.label_last_sync); - labelLastSync.setText(R.string.label_last_export_time); - - //retrieve some book statistics - DatabaseHelper dbHelper = new DatabaseHelper(GnuCashApplication.getAppContext(), bookUID); - SQLiteDatabase db = dbHelper.getReadableDatabase(); - TransactionsDbAdapter trnAdapter = new TransactionsDbAdapter(db, new SplitsDbAdapter(db)); - int transactionCount = (int) trnAdapter.getRecordsCount(); - String transactionStats = getResources().getQuantityString(R.plurals.book_transaction_stats, transactionCount, transactionCount); - - AccountsDbAdapter accountsDbAdapter = new AccountsDbAdapter(db, trnAdapter); - int accountsCount = (int) accountsDbAdapter.getRecordsCount(); - String accountStats = getResources().getQuantityString(R.plurals.book_account_stats, accountsCount, accountsCount); - String stats = accountStats + ", " + transactionStats; - TextView statsText = (TextView) view.findViewById(R.id.secondary_text); - statsText.setText(stats); + setLastExportedText(view, bookUID); + setStatisticsText(view, bookUID); ImageView optionsMenu = (ImageView) view.findViewById(R.id.options_menu); optionsMenu.setOnClickListener(new View.OnClickListener() { @@ -254,6 +233,33 @@ public void onClick(View v) { } }); } + + private void setLastExportedText(View view, String bookUID) { + TextView labelLastSync = (TextView) view.findViewById(R.id.label_last_sync); + labelLastSync.setText(R.string.label_last_export_time); + + Timestamp lastSyncTime = PreferencesHelper.getLastExportTime(bookUID); + TextView lastSyncText = (TextView) view.findViewById(R.id.last_sync_time); + if (lastSyncTime.equals(new Timestamp(0))) + lastSyncText.setText(R.string.last_export_time_never); + else + lastSyncText.setText(lastSyncTime.toString()); + } + + private void setStatisticsText(View view, String bookUID) { + DatabaseHelper dbHelper = new DatabaseHelper(GnuCashApplication.getAppContext(), bookUID); + SQLiteDatabase db = dbHelper.getReadableDatabase(); + TransactionsDbAdapter trnAdapter = new TransactionsDbAdapter(db, new SplitsDbAdapter(db)); + int transactionCount = (int) trnAdapter.getRecordsCount(); + String transactionStats = getResources().getQuantityString(R.plurals.book_transaction_stats, transactionCount, transactionCount); + + AccountsDbAdapter accountsDbAdapter = new AccountsDbAdapter(db, trnAdapter); + int accountsCount = (int) accountsDbAdapter.getRecordsCount(); + String accountStats = getResources().getQuantityString(R.plurals.book_account_stats, accountsCount, accountsCount); + String stats = accountStats + ", " + transactionStats; + TextView statsText = (TextView) view.findViewById(R.id.secondary_text); + statsText.setText(stats); + } } /** From 5911aa6dba047b29cef535bc401002da2512171b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=80lex=20Magaz=20Gra=C3=A7a?= Date: Thu, 24 Nov 2016 19:21:45 +0100 Subject: [PATCH 06/25] List scheduled actions with the correct days of week. We were showing the day of the week from the date the scheduled action was created instead. Fixes https://github.com/codinguser/gnucash-android/issues/617 --- .../db/adapter/RecurrenceDbAdapter.java | 96 +++++++++++++++- .../org/gnucash/android/model/Recurrence.java | 60 +++++++--- .../android/ui/util/RecurrenceParser.java | 103 ++++-------------- 3 files changed, 158 insertions(+), 101 deletions(-) diff --git a/app/src/main/java/org/gnucash/android/db/adapter/RecurrenceDbAdapter.java b/app/src/main/java/org/gnucash/android/db/adapter/RecurrenceDbAdapter.java index 90ca5dcfe..0783e58b1 100644 --- a/app/src/main/java/org/gnucash/android/db/adapter/RecurrenceDbAdapter.java +++ b/app/src/main/java/org/gnucash/android/db/adapter/RecurrenceDbAdapter.java @@ -20,12 +20,17 @@ import android.database.sqlite.SQLiteDatabase; import android.database.sqlite.SQLiteStatement; import android.support.annotation.NonNull; +import android.support.annotation.Nullable; import org.gnucash.android.app.GnuCashApplication; import org.gnucash.android.model.PeriodType; import org.gnucash.android.model.Recurrence; import java.sql.Timestamp; +import java.util.ArrayList; +import java.util.Calendar; +import java.util.Collections; +import java.util.List; import static org.gnucash.android.db.DatabaseSchema.RecurrenceEntry; @@ -58,7 +63,7 @@ public Recurrence buildModelInstance(@NonNull Cursor cursor) { long multiplier = cursor.getLong(cursor.getColumnIndexOrThrow(RecurrenceEntry.COLUMN_MULTIPLIER)); String periodStart = cursor.getString(cursor.getColumnIndexOrThrow(RecurrenceEntry.COLUMN_PERIOD_START)); String periodEnd = cursor.getString(cursor.getColumnIndexOrThrow(RecurrenceEntry.COLUMN_PERIOD_END)); - String byDay = cursor.getString(cursor.getColumnIndexOrThrow(RecurrenceEntry.COLUMN_BYDAY)); + String byDays = cursor.getString(cursor.getColumnIndexOrThrow(RecurrenceEntry.COLUMN_BYDAY)); PeriodType periodType = PeriodType.valueOf(type); periodType.setMultiplier((int) multiplier); @@ -67,7 +72,7 @@ public Recurrence buildModelInstance(@NonNull Cursor cursor) { recurrence.setPeriodStart(Timestamp.valueOf(periodStart)); if (periodEnd != null) recurrence.setPeriodEnd(Timestamp.valueOf(periodEnd)); - recurrence.setByDay(byDay); + recurrence.setByDays(stringToByDays(byDays)); populateBaseModelAttributes(cursor, recurrence); @@ -79,8 +84,8 @@ public Recurrence buildModelInstance(@NonNull Cursor cursor) { stmt.clearBindings(); stmt.bindLong(1, recurrence.getPeriodType().getMultiplier()); stmt.bindString(2, recurrence.getPeriodType().name()); - if (recurrence.getByDay() != null) - stmt.bindString(3, recurrence.getByDay()); + if (!recurrence.getByDays().isEmpty()) + stmt.bindString(3, byDaysToString(recurrence.getByDays())); //recurrence should always have a start date stmt.bindString(4, recurrence.getPeriodStart().toString()); @@ -90,4 +95,87 @@ public Recurrence buildModelInstance(@NonNull Cursor cursor) { return stmt; } + + /** + * Converts a list of days of week as Calendar constants to an String for + * storing in the database. + * + * @param byDays list of days of week constants from Calendar + * @return String of days of the week or null if {@code byDays} was empty + */ + private static @NonNull String byDaysToString(@NonNull List byDays) { + StringBuilder builder = new StringBuilder(); + for (int day : byDays) { + switch (day) { + case Calendar.MONDAY: + builder.append("MO"); + break; + case Calendar.TUESDAY: + builder.append("TU"); + break; + case Calendar.WEDNESDAY: + builder.append("WE"); + break; + case Calendar.THURSDAY: + builder.append("TH"); + break; + case Calendar.FRIDAY: + builder.append("FR"); + break; + case Calendar.SATURDAY: + builder.append("SA"); + break; + case Calendar.SUNDAY: + builder.append("SU"); + break; + default: + throw new RuntimeException("bad day of week: " + day); + } + builder.append(","); + } + builder.deleteCharAt(builder.length()-1); + return builder.toString(); + } + + /** + * Converts a String with the comma-separated days of the week into a + * list of Calendar constants. + * + * @param byDaysString String with comma-separated days fo the week + * @return list of days of the week as Calendar constants. + */ + private static @NonNull List stringToByDays(@Nullable String byDaysString) { + if (byDaysString == null) + return Collections.emptyList(); + + List byDaysList = new ArrayList<>(); + for (String day : byDaysString.split(",")) { + switch (day) { + case "MO": + byDaysList.add(Calendar.MONDAY); + break; + case "TU": + byDaysList.add(Calendar.TUESDAY); + break; + case "WE": + byDaysList.add(Calendar.WEDNESDAY); + break; + case "TH": + byDaysList.add(Calendar.THURSDAY); + break; + case "FR": + byDaysList.add(Calendar.FRIDAY); + break; + case "SA": + byDaysList.add(Calendar.SATURDAY); + break; + case "SU": + byDaysList.add(Calendar.SUNDAY); + break; + default: + throw new RuntimeException("bad day of week: " + day); + } + } + return byDaysList; + } } diff --git a/app/src/main/java/org/gnucash/android/model/Recurrence.java b/app/src/main/java/org/gnucash/android/model/Recurrence.java index 48db0af91..830bb0523 100644 --- a/app/src/main/java/org/gnucash/android/model/Recurrence.java +++ b/app/src/main/java/org/gnucash/android/model/Recurrence.java @@ -22,7 +22,6 @@ import org.gnucash.android.R; import org.gnucash.android.app.GnuCashApplication; import org.gnucash.android.ui.util.RecurrenceParser; -import org.joda.time.DateTime; import org.joda.time.Days; import org.joda.time.LocalDate; import org.joda.time.LocalDateTime; @@ -32,8 +31,13 @@ import org.joda.time.Years; import java.sql.Timestamp; +import java.text.DateFormat; import java.text.SimpleDateFormat; +import java.util.ArrayList; +import java.util.Calendar; +import java.util.Collections; import java.util.Date; +import java.util.List; /** * Model for recurrences in the database @@ -55,9 +59,9 @@ public class Recurrence extends BaseModel { private Timestamp mPeriodEnd; /** - * Describes which day on which to run the recurrence + * Days of week on which to run the recurrence */ - private String mByDay; + private List mByDays = Collections.emptyList(); public Recurrence(@NonNull PeriodType periodType){ setPeriodType(periodType); @@ -131,10 +135,9 @@ public String getRepeatString(){ StringBuilder repeatBuilder = new StringBuilder(mPeriodType.getFrequencyRepeatString()); Context context = GnuCashApplication.getAppContext(); - String dayOfWeek = new SimpleDateFormat("EEEE", GnuCashApplication.getDefaultLocale()) - .format(new Date(mPeriodStart.getTime())); if (mPeriodType == PeriodType.WEEK) { - repeatBuilder.append(" ").append(context.getString(R.string.repeat_on_weekday, dayOfWeek)); + repeatBuilder.append(" "). + append(context.getString(R.string.repeat_on_weekday, getDaysOfWeekString())); } if (mPeriodEnd != null){ @@ -144,7 +147,26 @@ public String getRepeatString(){ return repeatBuilder.toString(); } - /** + /** + * Returns a string with the days of the week set in the recurrence separated by commas. + * @return string with the days of the week set in the recurrence separated by commas. + */ + private @NonNull String getDaysOfWeekString() { + // XXX: mByDays should never be empty with PeriodType.WEEK, but we don't enforce it yet + if (mByDays.isEmpty()) + return ""; + StringBuilder daysOfWeekString = new StringBuilder(); + Calendar calendar = Calendar.getInstance(); + DateFormat dayOfWeekFormatter = + new SimpleDateFormat("EEEE", GnuCashApplication.getDefaultLocale()); + for (int day : mByDays) { + calendar.set(Calendar.DAY_OF_WEEK, day); + daysOfWeekString.append(dayOfWeekFormatter.format(calendar.getTime())).append(", "); + } + return daysOfWeekString.substring(0, daysOfWeekString.length()-2); + } + + /** * Creates an RFC 2445 string which describes this recurring event. *

See http://recurrance.sourceforge.net/

*

The output of this method is not meant for human consumption

@@ -251,19 +273,27 @@ public String getTextOfCurrentPeriod(int periodNum){ } /** - * Sets the string which determines on which day the recurrence will be run - * @param byDay Byday string of recurrence rule (RFC 2445) + * Return the days of week on which to run the recurrence. + * + *

Days are expressed as defined in {@link java.util.Calendar}. + * For example, Calendar.MONDAY

+ * + * @return list of days of week on which to run the recurrence. */ - public void setByDay(String byDay){ - this.mByDay = byDay; + public @NonNull List getByDays(){ + return Collections.unmodifiableList(mByDays); } /** - * Return the byDay string of recurrence rule (RFC 2445) - * @return String with by day specification + * Sets the days on which to run the recurrence. + * + *

Days must be expressed as defined in {@link java.util.Calendar}. + * For example, Calendar.MONDAY

+ * + * @param byDays list of days of week on which to run the recurrence. */ - public String getByDay(){ - return mByDay; + public void setByDays(@NonNull List byDays){ + mByDays = new ArrayList<>(byDays); } /** diff --git a/app/src/main/java/org/gnucash/android/ui/util/RecurrenceParser.java b/app/src/main/java/org/gnucash/android/ui/util/RecurrenceParser.java index 6309db40d..379dd60f7 100644 --- a/app/src/main/java/org/gnucash/android/ui/util/RecurrenceParser.java +++ b/app/src/main/java/org/gnucash/android/ui/util/RecurrenceParser.java @@ -16,6 +16,8 @@ package org.gnucash.android.ui.util; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; import android.text.format.Time; import com.codetroopers.betterpickers.recurrencepicker.EventRecurrence; @@ -24,7 +26,10 @@ import org.gnucash.android.model.Recurrence; import java.sql.Timestamp; +import java.util.ArrayList; import java.util.Calendar; +import java.util.Collections; +import java.util.List; /** * Parses {@link EventRecurrence}s to generate @@ -76,7 +81,7 @@ public static Recurrence parse(EventRecurrence eventRecurrence){ periodType.setMultiplier(interval); Recurrence recurrence = new Recurrence(periodType); parseEndTime(eventRecurrence, recurrence); - recurrence.setByDay(parseByDay(eventRecurrence.byday)); + recurrence.setByDays(parseByDay(eventRecurrence.byday)); if (eventRecurrence.startDate != null) recurrence.setPeriodStart(new Timestamp(eventRecurrence.startDate.toMillis(false))); @@ -85,7 +90,7 @@ public static Recurrence parse(EventRecurrence eventRecurrence){ /** * Parses the end time from an EventRecurrence object and sets it to the scheduledEvent. - * The end time is specified in the dialog either by number of occurences or a date. + * The end time is specified in the dialog either by number of occurrences or a date. * @param eventRecurrence Event recurrence pattern obtained from dialog * @param recurrence Recurrence event to set the end period to */ @@ -100,90 +105,24 @@ private static void parseEndTime(EventRecurrence eventRecurrence, Recurrence rec } /** - * Returns the date for the next day of the week - * @param dow Day of the week (Calendar constants) - * @return Calendar instance with the next day of the week + * Parses an array of byDay values to return a list of days of week + * constants from {@link Calendar}. + * + *

Currently only supports byDay values for weeks.

+ * + * @param byDay Array of byDay values + * @return list of days of week constants from Calendar. */ - private static Calendar nextDayOfWeek(int dow) { - Calendar date = Calendar.getInstance(); - int diff = dow - date.get(Calendar.DAY_OF_WEEK); - if (!(diff > 0)) { - diff += 7; + private static @NonNull List parseByDay(@Nullable int[] byDay) { + if (byDay == null) { + return Collections.emptyList(); } - date.add(Calendar.DAY_OF_MONTH, diff); - return date; - } - /** - * Parses an array of byday values to return the string concatenation of days of the week. - *

Currently only supports byDay values for weeks

- * @param byday Array of byday values - * @return String concat of days of the week or null if {@code byday} was empty - */ - private static String parseByDay(int[] byday){ - if (byday == null || byday.length == 0){ - return null; - } - //todo: parse for month and year as well, when our dialog supports those - StringBuilder builder = new StringBuilder(); - for (int day : byday) { - switch (day) - { - case EventRecurrence.SU: - builder.append("SU"); - break; - case EventRecurrence.MO: - builder.append("MO"); - break; - case EventRecurrence.TU: - builder.append("TU"); - break; - case EventRecurrence.WE: - builder.append("WE"); - break; - case EventRecurrence.TH: - builder.append("TH"); - break; - case EventRecurrence.FR: - builder.append("FR"); - break; - case EventRecurrence.SA: - builder.append("SA"); - break; - default: - throw new RuntimeException("bad day of week: " + day); - } - builder.append(","); + List byDaysList = new ArrayList<>(byDay.length); + for (int day : byDay) { + byDaysList.add(EventRecurrence.day2CalendarDay(day)); } - builder.deleteCharAt(builder.length()-1); - return builder.toString(); - } - /** - * Converts one of the SU, MO, etc. constants to the Calendar.SUNDAY - * constants. btw, I think we should switch to those here too, to - * get rid of this function, if possible. - */ - public static int day2CalendarDay(int day) - { - switch (day) - { - case EventRecurrence.SU: - return Calendar.SUNDAY; - case EventRecurrence.MO: - return Calendar.MONDAY; - case EventRecurrence.TU: - return Calendar.TUESDAY; - case EventRecurrence.WE: - return Calendar.WEDNESDAY; - case EventRecurrence.TH: - return Calendar.THURSDAY; - case EventRecurrence.FR: - return Calendar.FRIDAY; - case EventRecurrence.SA: - return Calendar.SATURDAY; - default: - throw new RuntimeException("bad day of week: " + day); - } + return byDaysList; } } From 6d7ce0a4b146e11981e59cedeb69b1f51517c351 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=80lex=20Magaz=20Gra=C3=A7a?= Date: Thu, 1 Dec 2016 17:32:59 +0100 Subject: [PATCH 07/25] Fix having the account color reset when editing an account. Fixes https://github.com/codinguser/gnucash-android/issues/620 --- .../java/org/gnucash/android/ui/account/AccountFormFragment.java | 1 + 1 file changed, 1 insertion(+) 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 066e68d10..8fd13bb3d 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 @@ -408,6 +408,7 @@ private void initializeViewsWithAccount(Account account){ } mPlaceholderCheckBox.setChecked(account.isPlaceholderAccount()); + mSelectedColor = account.getColor(); mColorSquare.setBackgroundColor(account.getColor()); setAccountTypeSelection(account.getAccountType()); From 3784e029c18d98d36563a1bb9fa53955cc3b9fda Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=80lex=20Magaz=20Gra=C3=A7a?= Date: Thu, 1 Dec 2016 18:21:32 +0100 Subject: [PATCH 08/25] Fix code inspector issues in AccountFormFragment. --- .../ui/account/AccountFormFragment.java | 40 ++++++++++--------- 1 file changed, 21 insertions(+), 19 deletions(-) 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 8fd13bb3d..44c7746e6 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 @@ -87,7 +87,7 @@ public class AccountFormFragment extends Fragment { /** * Tag for the color picker dialog fragment */ - public static final String COLOR_PICKER_DIALOG_TAG = "color_picker_dialog"; + private static final String COLOR_PICKER_DIALOG_TAG = "color_picker_dialog"; /** * EditText for the name of the account to be created/edited @@ -176,7 +176,7 @@ public class AccountFormFragment extends Fragment { /** * Spinner for selecting the default transfer account */ - @Bind(R.id.input_default_transfer_account) Spinner mDefaulTransferAccountSpinner; + @Bind(R.id.input_default_transfer_account) Spinner mDefaultTransferAccountSpinner; /** * Account description input text view @@ -205,13 +205,14 @@ public class AccountFormFragment extends Fragment { */ @Bind(R.id.input_color_picker) ColorSquare mColorSquare; - private ColorPickerSwatch.OnColorSelectedListener mColorSelectedListener = new ColorPickerSwatch.OnColorSelectedListener() { - @Override - public void onColorSelected(int color) { - mColorSquare.setBackgroundColor(color); - mSelectedColor = color; - } - }; + private final ColorPickerSwatch.OnColorSelectedListener mColorSelectedListener = + new ColorPickerSwatch.OnColorSelectedListener() { + @Override + public void onColorSelected(int color) { + mColorSquare.setBackgroundColor(color); + mSelectedColor = color; + } + }; /** @@ -294,11 +295,11 @@ public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) { } }); - mDefaulTransferAccountSpinner.setEnabled(false); + mDefaultTransferAccountSpinner.setEnabled(false); mDefaultTransferAccountCheckBox.setOnCheckedChangeListener(new OnCheckedChangeListener() { @Override public void onCheckedChanged(CompoundButton compoundButton, boolean isChecked) { - mDefaulTransferAccountSpinner.setEnabled(isChecked); + mDefaultTransferAccountSpinner.setEnabled(isChecked); } }); @@ -496,13 +497,13 @@ private void setParentAccountSelection(long parentAccountId){ private void setDefaultTransferAccountSelection(long defaultTransferAccountId, boolean enableTransferAccount) { if (defaultTransferAccountId > 0) { mDefaultTransferAccountCheckBox.setChecked(enableTransferAccount); - mDefaulTransferAccountSpinner.setEnabled(enableTransferAccount); + mDefaultTransferAccountSpinner.setEnabled(enableTransferAccount); } else return; for (int pos = 0; pos < mDefaultTransferAccountCursorAdapter.getCount(); pos++) { if (mDefaultTransferAccountCursorAdapter.getItemId(pos) == defaultTransferAccountId) { - mDefaulTransferAccountSpinner.setSelection(pos); + mDefaultTransferAccountSpinner.setSelection(pos); break; } } @@ -521,6 +522,7 @@ private int[] getAccountColorOptions(){ int color = colorTypedArray.getColor(i, getResources().getColor(R.color.title_green)); colorOptions[i] = color; } + colorTypedArray.recycle(); return colorOptions; } /** @@ -574,13 +576,13 @@ private void loadDefaultTransferAccountList(){ Cursor defaultTransferAccountCursor = mAccountsDbAdapter.fetchAccountsOrderedByFullName(condition, new String[]{AccountType.ROOT.name()}); - if (mDefaulTransferAccountSpinner.getCount() <= 0) { + if (mDefaultTransferAccountSpinner.getCount() <= 0) { setDefaultTransferAccountInputsVisible(false); } mDefaultTransferAccountCursorAdapter = new QualifiedAccountNameCursorAdapter(getActivity(), defaultTransferAccountCursor); - mDefaulTransferAccountSpinner.setAdapter(mDefaultTransferAccountCursorAdapter); + mDefaultTransferAccountSpinner.setAdapter(mDefaultTransferAccountCursorAdapter); } /** @@ -733,7 +735,7 @@ private void saveAccount() { boolean nameChanged = false; if (mAccount == null){ String name = getEnteredName(); - if (name == null || name.length() == 0){ + if (name.length() == 0){ mTextInputLayout.setErrorEnabled(true); mTextInputLayout.setError(getString(R.string.toast_no_account_name_entered)); return; @@ -771,8 +773,8 @@ private void saveAccount() { mAccount.setParentUID(newParentAccountUID); if (mDefaultTransferAccountCheckBox.isChecked() - && mDefaulTransferAccountSpinner.getSelectedItemId() != Spinner.INVALID_ROW_ID){ - long id = mDefaulTransferAccountSpinner.getSelectedItemId(); + && mDefaultTransferAccountSpinner.getSelectedItemId() != Spinner.INVALID_ROW_ID){ + long id = mDefaultTransferAccountSpinner.getSelectedItemId(); mAccount.setDefaultTransferAccountUID(mAccountsDbAdapter.getUID(id)); } else { //explicitly set in case of removal of default account @@ -842,7 +844,7 @@ private AccountType getSelectedAccountType() { * Retrieves the name of the account which has been entered in the EditText * @return Name of the account which has been entered in the EditText */ - public String getEnteredName(){ + private String getEnteredName(){ return mNameEditText.getText().toString().trim(); } From bfb771844890f22875d147cc45b988de5e83dc5f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=80lex=20Magaz=20Gra=C3=A7a?= Date: Wed, 23 Nov 2016 09:09:15 +0100 Subject: [PATCH 09/25] Make ExportAsyncTask.moveExportToGoogleDrive() synchronous as expected by its caller. Otherwise we might end up reporting the export as successful when it's not even finished yet. --- .../android/export/ExportAsyncTask.java | 91 ++++++++----------- 1 file changed, 40 insertions(+), 51 deletions(-) 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 cf7626818..2e8ef6c75 100644 --- a/app/src/main/java/org/gnucash/android/export/ExportAsyncTask.java +++ b/app/src/main/java/org/gnucash/android/export/ExportAsyncTask.java @@ -80,6 +80,7 @@ import java.util.ArrayList; import java.util.Date; import java.util.List; +import java.util.concurrent.TimeUnit; /** * Asynchronous task for exporting transactions. @@ -276,62 +277,50 @@ protected void onPostExecute(Boolean exportResult) { private void moveExportToGoogleDrive(){ Log.i(TAG, "Moving exported file to Google Drive"); + final long TIMEOUT = 5; // seconds final GoogleApiClient googleApiClient = BackupPreferenceFragment.getGoogleApiClient(GnuCashApplication.getAppContext()); googleApiClient.blockingConnect(); - final ResultCallback fileCallback = new - ResultCallback() { - @Override - public void onResult(DriveFolder.DriveFileResult result) { - if (!result.getStatus().isSuccess()) - Log.e(TAG, "Error while trying to sync to Google Drive"); - else - Log.i(TAG, "Created a file with content: " + result.getDriveFile().getDriveId()); - } - }; - - Drive.DriveApi.newDriveContents(googleApiClient).setResultCallback(new ResultCallback() { - @Override - public void onResult(DriveApi.DriveContentsResult result) { - if (!result.getStatus().isSuccess()) { - Log.e(TAG, "Error while trying to create new file contents"); - return; - } - final DriveContents driveContents = result.getDriveContents(); - try { - // write content to DriveContents - OutputStream outputStream = driveContents.getOutputStream(); - for (String exportedFilePath : mExportedFiles) { - File exportedFile = new File(exportedFilePath); - FileInputStream fileInputStream = new FileInputStream(exportedFile); - byte[] buffer = new byte[1024]; - int count; - - while ((count = fileInputStream.read(buffer)) >= 0) { - outputStream.write(buffer, 0, count); - } - fileInputStream.close(); - outputStream.flush(); - exportedFile.delete(); - - MetadataChangeSet changeSet = new MetadataChangeSet.Builder() - .setTitle(exportedFile.getName()) - .setMimeType(mExporter.getExportMimeType()) - .build(); - - SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(mContext); - String folderId = sharedPreferences.getString(mContext.getString(R.string.key_google_drive_app_folder_id), ""); - DriveFolder folder = Drive.DriveApi.getFolder(googleApiClient, DriveId.decodeFromString(folderId)); - // create a file on root folder - folder.createFile(googleApiClient, changeSet, driveContents) - .setResultCallback(fileCallback); - } - } catch (IOException e) { - Crashlytics.logException(e); - Log.e(TAG, e.getMessage()); + DriveApi.DriveContentsResult driveContentsResult = + Drive.DriveApi.newDriveContents(googleApiClient).await(TIMEOUT, TimeUnit.SECONDS); + if (!driveContentsResult.getStatus().isSuccess()) { + Log.e(TAG, "Error while trying to create new file contents"); + return; + } + final DriveContents driveContents = driveContentsResult.getDriveContents(); + DriveFolder.DriveFileResult driveFileResult = null; + try { + // write content to DriveContents + OutputStream outputStream = driveContents.getOutputStream(); + for (String exportedFilePath : mExportedFiles) { + File exportedFile = new File(exportedFilePath); + FileInputStream fileInputStream = new FileInputStream(exportedFile); + byte[] buffer = new byte[1024]; + int count; + + while ((count = fileInputStream.read(buffer)) >= 0) { + outputStream.write(buffer, 0, count); } + fileInputStream.close(); + outputStream.flush(); + exportedFile.delete(); + + MetadataChangeSet changeSet = new MetadataChangeSet.Builder() + .setTitle(exportedFile.getName()) + .setMimeType(mExporter.getExportMimeType()) + .build(); + + SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(mContext); + String folderId = sharedPreferences.getString(mContext.getString(R.string.key_google_drive_app_folder_id), ""); + DriveFolder folder = Drive.DriveApi.getFolder(googleApiClient, DriveId.decodeFromString(folderId)); + // create a file on root folder + driveFileResult = folder.createFile(googleApiClient, changeSet, driveContents) + .await(TIMEOUT, TimeUnit.SECONDS); } - }); + } catch (IOException e) { + Crashlytics.logException(e); + Log.e(TAG, e.getMessage()); + } } private void moveExportToDropbox() { From a082531763c310186d7f0ee69a5aed29474ce293 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=80lex=20Magaz=20Gra=C3=A7a?= Date: Thu, 8 Dec 2016 19:53:18 +0100 Subject: [PATCH 10/25] Show an error message if there's an error exporting to Google Drive. Fixes https://github.com/codinguser/gnucash-android/issues/616 --- .../android/export/ExportAsyncTask.java | 21 ++++++++++++++++++- 1 file changed, 20 insertions(+), 1 deletion(-) 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 2e8ef6c75..9f392c804 100644 --- a/app/src/main/java/org/gnucash/android/export/ExportAsyncTask.java +++ b/app/src/main/java/org/gnucash/android/export/ExportAsyncTask.java @@ -40,7 +40,6 @@ import com.dropbox.sync.android.DbxFileSystem; import com.dropbox.sync.android.DbxPath; import com.google.android.gms.common.api.GoogleApiClient; -import com.google.android.gms.common.api.ResultCallback; import com.google.android.gms.drive.Drive; import com.google.android.gms.drive.DriveApi; import com.google.android.gms.drive.DriveContents; @@ -321,6 +320,16 @@ private void moveExportToGoogleDrive(){ Crashlytics.logException(e); Log.e(TAG, e.getMessage()); } + + if (driveFileResult == null) + return; + + if (!driveFileResult.getStatus().isSuccess()) { + Log.e(TAG, "Error creating file in Google Drive"); + showToastFromNonUiThread("Couldn't create the file in Google Drive", Toast.LENGTH_LONG); + } else { + Log.i(TAG, "Created file with id: " + driveFileResult.getDriveFile().getDriveId()); + } } private void moveExportToDropbox() { @@ -525,4 +534,14 @@ public void moveFile(String src, String dst) throws IOException { srcFile.delete(); } + private void showToastFromNonUiThread(final String message, final int duration) { + if (mContext instanceof Activity) { + ((Activity) mContext).runOnUiThread(new Runnable() { + @Override + public void run() { + Toast.makeText(mContext, message, duration).show(); + } + }); + } + } } From 48d67e30fb03913b9f2bc266bdf47240e873b6c3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=80lex=20Magaz=20Gra=C3=A7a?= Date: Fri, 9 Dec 2016 09:18:05 +0100 Subject: [PATCH 11/25] Use more realistic timeouts for Google Drive. They are still arbitrary, though. To be fixed in the future. --- .../java/org/gnucash/android/export/ExportAsyncTask.java | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) 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 9f392c804..c098131c6 100644 --- a/app/src/main/java/org/gnucash/android/export/ExportAsyncTask.java +++ b/app/src/main/java/org/gnucash/android/export/ExportAsyncTask.java @@ -276,12 +276,11 @@ protected void onPostExecute(Boolean exportResult) { private void moveExportToGoogleDrive(){ Log.i(TAG, "Moving exported file to Google Drive"); - final long TIMEOUT = 5; // seconds final GoogleApiClient googleApiClient = BackupPreferenceFragment.getGoogleApiClient(GnuCashApplication.getAppContext()); googleApiClient.blockingConnect(); DriveApi.DriveContentsResult driveContentsResult = - Drive.DriveApi.newDriveContents(googleApiClient).await(TIMEOUT, TimeUnit.SECONDS); + Drive.DriveApi.newDriveContents(googleApiClient).await(1, TimeUnit.MINUTES); if (!driveContentsResult.getStatus().isSuccess()) { Log.e(TAG, "Error while trying to create new file contents"); return; @@ -314,7 +313,7 @@ private void moveExportToGoogleDrive(){ DriveFolder folder = Drive.DriveApi.getFolder(googleApiClient, DriveId.decodeFromString(folderId)); // create a file on root folder driveFileResult = folder.createFile(googleApiClient, changeSet, driveContents) - .await(TIMEOUT, TimeUnit.SECONDS); + .await(1, TimeUnit.MINUTES); } } catch (IOException e) { Crashlytics.logException(e); From 4761eae374f6f0650cffc258ae850cee2c6fcf93 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=80lex=20Magaz=20Gra=C3=A7a?= Date: Fri, 9 Dec 2016 11:53:52 +0100 Subject: [PATCH 12/25] Extract the creation of the Exporter into a new method --- .../android/export/ExportAsyncTask.java | 30 +++++++++---------- 1 file changed, 15 insertions(+), 15 deletions(-) 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 c098131c6..fd7841584 100644 --- a/app/src/main/java/org/gnucash/android/export/ExportAsyncTask.java +++ b/app/src/main/java/org/gnucash/android/export/ExportAsyncTask.java @@ -142,21 +142,7 @@ protected void onPreExecute() { @Override protected Boolean doInBackground(ExportParams... params) { mExportParams = params[0]; - - switch (mExportParams.getExportFormat()) { - case QIF: - mExporter = new QifExporter(mExportParams, mDb); - break; - - case OFX: - mExporter = new OfxExporter(mExportParams, mDb); - break; - - case XML: - default: - mExporter = new GncXmlExporter(mExportParams, mDb); - break; - } + mExporter = getExporter(); try { // FIXME: detect if there aren't transactions to export and inform the user @@ -274,6 +260,20 @@ protected void onPostExecute(Boolean exportResult) { } } + private Exporter getExporter() { + switch (mExportParams.getExportFormat()) { + case QIF: + return new QifExporter(mExportParams, mDb); + + case OFX: + return new OfxExporter(mExportParams, mDb); + + case XML: + default: + return new GncXmlExporter(mExportParams, mDb); + } + } + private void moveExportToGoogleDrive(){ Log.i(TAG, "Moving exported file to Google Drive"); final GoogleApiClient googleApiClient = BackupPreferenceFragment.getGoogleApiClient(GnuCashApplication.getAppContext()); From ebff23413f342505b471353d8840d384d6766b5a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=80lex=20Magaz=20Gra=C3=A7a?= Date: Thu, 29 Dec 2016 10:08:55 +0100 Subject: [PATCH 13/25] Extract code to move to the export target into a new method --- .../android/export/ExportAsyncTask.java | 48 ++++++++++--------- 1 file changed, 26 insertions(+), 22 deletions(-) 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 fd7841584..086104c5d 100644 --- a/app/src/main/java/org/gnucash/android/export/ExportAsyncTask.java +++ b/app/src/main/java/org/gnucash/android/export/ExportAsyncTask.java @@ -165,28 +165,7 @@ public void run() { return false; } - switch (mExportParams.getExportTarget()) { - case SHARING: - List sdCardExportedFiles = moveExportToSDCard(); - shareFiles(sdCardExportedFiles); - return true; - - case DROPBOX: - moveExportToDropbox(); - return true; - - case GOOGLE_DRIVE: - moveExportToGoogleDrive(); - return true; - - case OWNCLOUD: - moveExportToOwnCloud(); - return true; - - case SD_CARD: - moveExportToSDCard(); - return true; - } + moveToTarget(); return false; } @@ -274,6 +253,31 @@ private Exporter getExporter() { } } + private void moveToTarget() { + switch (mExportParams.getExportTarget()) { + case SHARING: + List sdCardExportedFiles = moveExportToSDCard(); + shareFiles(sdCardExportedFiles); + break; + + case DROPBOX: + moveExportToDropbox(); + break; + + case GOOGLE_DRIVE: + moveExportToGoogleDrive(); + break; + + case OWNCLOUD: + moveExportToOwnCloud(); + break; + + case SD_CARD: + moveExportToSDCard(); + break; + } + } + private void moveExportToGoogleDrive(){ Log.i(TAG, "Moving exported file to Google Drive"); final GoogleApiClient googleApiClient = BackupPreferenceFragment.getGoogleApiClient(GnuCashApplication.getAppContext()); From e344246f6f77ad8067300719ef1cad2f6c46f26f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=80lex=20Magaz=20Gra=C3=A7a?= Date: Thu, 29 Dec 2016 17:24:28 +0100 Subject: [PATCH 14/25] Always inform the user if sending to the export target fails Errors were logged to Logcat, but the process always ended as succesful. Also fixes https://github.com/codinguser/gnucash-android/issues/616 (There were some cases that 1a76361 didn't fix) --- .../android/export/ExportAsyncTask.java | 77 ++++++++----------- .../org/gnucash/android/export/Exporter.java | 2 +- .../android/test/unit/export/BackupTest.java | 2 +- 3 files changed, 32 insertions(+), 49 deletions(-) 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 086104c5d..fc809afb2 100644 --- a/app/src/main/java/org/gnucash/android/export/ExportAsyncTask.java +++ b/app/src/main/java/org/gnucash/android/export/ExportAsyncTask.java @@ -35,7 +35,6 @@ import com.crashlytics.android.Crashlytics; import com.dropbox.sync.android.DbxAccountManager; -import com.dropbox.sync.android.DbxException; import com.dropbox.sync.android.DbxFile; import com.dropbox.sync.android.DbxFileSystem; import com.dropbox.sync.android.DbxPath; @@ -165,9 +164,13 @@ public void run() { return false; } - moveToTarget(); - - return false; + try { + moveToTarget(); + } catch (Exporter.ExporterException e) { + Crashlytics.log(Log.ERROR, TAG, "Error sending exported files to target: " + e.getMessage()); + return false; + } + return true; } /** @@ -253,7 +256,7 @@ private Exporter getExporter() { } } - private void moveToTarget() { + private void moveToTarget() throws Exporter.ExporterException { switch (mExportParams.getExportTarget()) { case SHARING: List sdCardExportedFiles = moveExportToSDCard(); @@ -275,10 +278,13 @@ private void moveToTarget() { case SD_CARD: moveExportToSDCard(); break; + + default: + throw new Exporter.ExporterException(mExportParams, "Invalid target"); } } - private void moveExportToGoogleDrive(){ + private void moveExportToGoogleDrive() throws Exporter.ExporterException { Log.i(TAG, "Moving exported file to Google Drive"); final GoogleApiClient googleApiClient = BackupPreferenceFragment.getGoogleApiClient(GnuCashApplication.getAppContext()); googleApiClient.blockingConnect(); @@ -286,8 +292,8 @@ private void moveExportToGoogleDrive(){ DriveApi.DriveContentsResult driveContentsResult = Drive.DriveApi.newDriveContents(googleApiClient).await(1, TimeUnit.MINUTES); if (!driveContentsResult.getStatus().isSuccess()) { - Log.e(TAG, "Error while trying to create new file contents"); - return; + throw new Exporter.ExporterException(mExportParams, + "Error while trying to create new file contents"); } final DriveContents driveContents = driveContentsResult.getDriveContents(); DriveFolder.DriveFileResult driveFileResult = null; @@ -320,22 +326,19 @@ private void moveExportToGoogleDrive(){ .await(1, TimeUnit.MINUTES); } } catch (IOException e) { - Crashlytics.logException(e); - Log.e(TAG, e.getMessage()); + throw new Exporter.ExporterException(mExportParams, e); } if (driveFileResult == null) - return; + throw new Exporter.ExporterException(mExportParams, "No result received"); - if (!driveFileResult.getStatus().isSuccess()) { - Log.e(TAG, "Error creating file in Google Drive"); - showToastFromNonUiThread("Couldn't create the file in Google Drive", Toast.LENGTH_LONG); - } else { - Log.i(TAG, "Created file with id: " + driveFileResult.getDriveFile().getDriveId()); - } + if (!driveFileResult.getStatus().isSuccess()) + throw new Exporter.ExporterException(mExportParams, "Error creating file in Google Drive"); + + Log.i(TAG, "Created file with id: " + driveFileResult.getDriveFile().getDriveId()); } - private void moveExportToDropbox() { + private void moveExportToDropbox() throws Exporter.ExporterException { Log.i(TAG, "Copying exported file to DropBox"); String dropboxAppKey = mContext.getString(R.string.dropbox_app_key, BackupPreferenceFragment.DROPBOX_APP_KEY); String dropboxAppSecret = mContext.getString(R.string.dropbox_app_secret, BackupPreferenceFragment.DROPBOX_APP_SECRET); @@ -350,13 +353,8 @@ private void moveExportToDropbox() { dbExportFile.writeFromExistingFile(exportedFile, false); exportedFile.delete(); } - } catch (DbxException.Unauthorized unauthorized) { - Crashlytics.logException(unauthorized); - Log.e(TAG, unauthorized.getMessage()); - throw new Exporter.ExporterException(mExportParams); } catch (IOException e) { - Crashlytics.logException(e); - Log.e(TAG, e.getMessage()); + throw new Exporter.ExporterException(mExportParams); } finally { if (dbExportFile != null) { dbExportFile.close(); @@ -364,16 +362,15 @@ private void moveExportToDropbox() { } } - private void moveExportToOwnCloud() { + private void moveExportToOwnCloud() throws Exporter.ExporterException { Log.i(TAG, "Copying exported file to ownCloud"); SharedPreferences mPrefs = mContext.getSharedPreferences(mContext.getString(R.string.owncloud_pref), Context.MODE_PRIVATE); Boolean mOC_sync = mPrefs.getBoolean(mContext.getString(R.string.owncloud_sync), false); - if(!mOC_sync){ - Log.e(TAG, "ownCloud not enabled."); - return; + if (!mOC_sync) { + throw new Exporter.ExporterException(mExportParams, "ownCloud not enabled."); } String mOC_server = mPrefs.getString(mContext.getString(R.string.key_owncloud_server), null); @@ -391,7 +388,7 @@ private void moveExportToOwnCloud() { RemoteOperationResult dirResult = new CreateRemoteFolderOperation( mOC_dir, true).execute(mClient); if (!dirResult.isSuccess()) - Log.e(TAG, dirResult.getLogMessage(), dirResult.getException()); + throw new Exporter.ExporterException(mExportParams, dirResult.getLogMessage()); } for (String exportedFilePath : mExportedFiles) { String remotePath = mOC_dir + FileUtils.PATH_SEPARATOR + stripPathPart(exportedFilePath); @@ -401,10 +398,9 @@ private void moveExportToOwnCloud() { exportedFilePath, remotePath, mimeType).execute(mClient); if (!result.isSuccess()) - Log.e(TAG, result.getLogMessage(), result.getException()); - else { - new File(exportedFilePath).delete(); - } + throw new Exporter.ExporterException(mExportParams, result.getLogMessage()); + + new File(exportedFilePath).delete(); } } @@ -413,7 +409,7 @@ private void moveExportToOwnCloud() { * external storage, which is accessible to the user. * @return The list of files moved to the SD card. */ - private List moveExportToSDCard() { + private List moveExportToSDCard() throws Exporter.ExporterException { Log.i(TAG, "Moving exported file to external storage"); new File(Exporter.getExportFolderPath(mExporter.mBookUID)); List dstFiles = new ArrayList<>(); @@ -424,8 +420,6 @@ private List moveExportToSDCard() { moveFile(src, dst); dstFiles.add(dst); } catch (IOException e) { - Crashlytics.logException(e); - Log.e(TAG, e.getMessage()); throw new Exporter.ExporterException(mExportParams, e); } } @@ -536,15 +530,4 @@ public void moveFile(String src, String dst) throws IOException { } srcFile.delete(); } - - private void showToastFromNonUiThread(final String message, final int duration) { - if (mContext instanceof Activity) { - ((Activity) mContext).runOnUiThread(new Runnable() { - @Override - public void run() { - Toast.makeText(mContext, message, duration).show(); - } - }); - } - } } 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 88afbabac..c79c9b426 100644 --- a/app/src/main/java/org/gnucash/android/export/Exporter.java +++ b/app/src/main/java/org/gnucash/android/export/Exporter.java @@ -254,7 +254,7 @@ public String getExportMimeType(){ return "text/plain"; } - public static class ExporterException extends RuntimeException{ + public static class ExporterException extends Exception { public ExporterException(ExportParams params){ super("Failed to generate export with parameters: " + params.toString()); diff --git a/app/src/test/java/org/gnucash/android/test/unit/export/BackupTest.java b/app/src/test/java/org/gnucash/android/test/unit/export/BackupTest.java index 712d515c1..7c837a403 100644 --- a/app/src/test/java/org/gnucash/android/test/unit/export/BackupTest.java +++ b/app/src/test/java/org/gnucash/android/test/unit/export/BackupTest.java @@ -62,7 +62,7 @@ public void shouldCreateBackup(){ } @Test - public void shouldCreateBackupFileName(){ + public void shouldCreateBackupFileName() throws Exporter.ExporterException { Exporter exporter = new GncXmlExporter(new ExportParams(ExportFormat.XML)); List xmlFiles = exporter.generateExport(); From 9d04947479f7f9c74a6079a80ad9600003d1fddb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=80lex=20Magaz=20Gra=C3=A7a?= Date: Fri, 30 Dec 2016 19:27:22 +0100 Subject: [PATCH 15/25] Continue uploading the files to OwnCloud if the folder creation fails OwnCloud seems to report an error, if we try to create a folder that already exists. We don't care, so we just go ahead uploading the files instead of throwing an exception. --- .../java/org/gnucash/android/export/ExportAsyncTask.java | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) 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 fc809afb2..17b3498c0 100644 --- a/app/src/main/java/org/gnucash/android/export/ExportAsyncTask.java +++ b/app/src/main/java/org/gnucash/android/export/ExportAsyncTask.java @@ -387,8 +387,10 @@ private void moveExportToOwnCloud() throws Exporter.ExporterException { if (mOC_dir.length() != 0) { RemoteOperationResult dirResult = new CreateRemoteFolderOperation( mOC_dir, true).execute(mClient); - if (!dirResult.isSuccess()) - throw new Exporter.ExporterException(mExportParams, dirResult.getLogMessage()); + if (!dirResult.isSuccess()) { + Log.w(TAG, "Error creating folder (it may happen if it already exists): " + + dirResult.getLogMessage()); + } } for (String exportedFilePath : mExportedFiles) { String remotePath = mOC_dir + FileUtils.PATH_SEPARATOR + stripPathPart(exportedFilePath); From ebf273885d8567c3fe4fc8375174c5b9e6efc920 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=80lex=20Magaz=20Gra=C3=A7a?= Date: Fri, 30 Dec 2016 09:19:47 +0100 Subject: [PATCH 16/25] Extract code to report success to the user into a new method --- .../android/export/ExportAsyncTask.java | 64 ++++++++++--------- 1 file changed, 34 insertions(+), 30 deletions(-) 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 17b3498c0..5f0cd9196 100644 --- a/app/src/main/java/org/gnucash/android/export/ExportAsyncTask.java +++ b/app/src/main/java/org/gnucash/android/export/ExportAsyncTask.java @@ -187,36 +187,7 @@ protected void onPostExecute(Boolean exportResult) { Toast.LENGTH_LONG).show(); return; } else { - String targetLocation; - switch (mExportParams.getExportTarget()){ - case SD_CARD: - targetLocation = "SD card"; - break; - case DROPBOX: - targetLocation = "DropBox -> Apps -> GnuCash"; - break; - case GOOGLE_DRIVE: - targetLocation = "Google Drive -> " + mContext.getString(R.string.app_name); - break; - case OWNCLOUD: - targetLocation = mContext.getSharedPreferences( - mContext.getString(R.string.owncloud_pref), - Context.MODE_PRIVATE).getBoolean( - mContext.getString(R.string.owncloud_sync), false) ? - - "ownCloud -> " + - mContext.getSharedPreferences( - mContext.getString(R.string.owncloud_pref), - Context.MODE_PRIVATE).getString( - mContext.getString(R.string.key_owncloud_dir), null) : - "ownCloud sync not enabled"; - break; - default: - targetLocation = mContext.getString(R.string.label_export_target_external_service); - } - Toast.makeText(mContext, - String.format(mContext.getString(R.string.toast_exported_to), targetLocation), - Toast.LENGTH_LONG).show(); + reportSuccess(); } } @@ -532,4 +503,37 @@ public void moveFile(String src, String dst) throws IOException { } srcFile.delete(); } + + private void reportSuccess() { + String targetLocation; + switch (mExportParams.getExportTarget()){ + case SD_CARD: + targetLocation = "SD card"; + break; + case DROPBOX: + targetLocation = "DropBox -> Apps -> GnuCash"; + break; + case GOOGLE_DRIVE: + targetLocation = "Google Drive -> " + mContext.getString(R.string.app_name); + break; + case OWNCLOUD: + targetLocation = mContext.getSharedPreferences( + mContext.getString(R.string.owncloud_pref), + Context.MODE_PRIVATE).getBoolean( + mContext.getString(R.string.owncloud_sync), false) ? + + "ownCloud -> " + + mContext.getSharedPreferences( + mContext.getString(R.string.owncloud_pref), + Context.MODE_PRIVATE).getString( + mContext.getString(R.string.key_owncloud_dir), null) : + "ownCloud sync not enabled"; + break; + default: + targetLocation = mContext.getString(R.string.label_export_target_external_service); + } + Toast.makeText(mContext, + String.format(mContext.getString(R.string.toast_exported_to), targetLocation), + Toast.LENGTH_LONG).show(); + } } From 66a48e6ad0492c415af36ea0943888ccee361602 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=80lex=20Magaz=20Gra=C3=A7a?= Date: Fri, 30 Dec 2016 10:02:47 +0100 Subject: [PATCH 17/25] Ensure the progress dialog is dismissed when the export fails After the last commits, the dialog remained visible. --- .../android/export/ExportAsyncTask.java | 35 ++++++++++--------- 1 file changed, 18 insertions(+), 17 deletions(-) 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 5f0cd9196..e0a8ee722 100644 --- a/app/src/main/java/org/gnucash/android/export/ExportAsyncTask.java +++ b/app/src/main/java/org/gnucash/android/export/ExportAsyncTask.java @@ -180,29 +180,30 @@ public void run() { */ @Override protected void onPostExecute(Boolean exportResult) { - if (mContext instanceof Activity) { - if (!exportResult) { + if (!exportResult) { + if (mContext instanceof Activity) { Toast.makeText(mContext, mContext.getString(R.string.toast_export_error, mExportParams.getExportFormat().name()), Toast.LENGTH_LONG).show(); - return; - } else { - reportSuccess(); } - } + } else { + if (mContext instanceof Activity) + reportSuccess(); - if (mExportParams.shouldDeleteTransactionsAfterExport()) { - Log.i(TAG, "Backup and deleting transactions after export"); - backupAndDeleteTransactions(); + if (mExportParams.shouldDeleteTransactionsAfterExport()) { + Log.i(TAG, "Backup and deleting transactions after export"); + backupAndDeleteTransactions(); - //now refresh the respective views - if (mContext instanceof AccountsActivity){ - AccountsListFragment fragment = ((AccountsActivity) mContext).getCurrentAccountListFragment(); - if (fragment != null) - fragment.refresh(); - } - if (mContext instanceof TransactionsActivity){ - ((TransactionsActivity) mContext).refresh(); + //now refresh the respective views + if (mContext instanceof AccountsActivity){ + AccountsListFragment fragment = + ((AccountsActivity) mContext).getCurrentAccountListFragment(); + if (fragment != null) + fragment.refresh(); + } + if (mContext instanceof TransactionsActivity){ + ((TransactionsActivity) mContext).refresh(); + } } } From 0a1dbceb1e0839f12aeb680726e053e78a4e344c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=80lex=20Magaz=20Gra=C3=A7a?= Date: Fri, 30 Dec 2016 10:05:36 +0100 Subject: [PATCH 18/25] Extract code to refresh views into a new method --- .../android/export/ExportAsyncTask.java | 24 ++++++++++--------- 1 file changed, 13 insertions(+), 11 deletions(-) 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 e0a8ee722..b241a80fa 100644 --- a/app/src/main/java/org/gnucash/android/export/ExportAsyncTask.java +++ b/app/src/main/java/org/gnucash/android/export/ExportAsyncTask.java @@ -193,17 +193,7 @@ protected void onPostExecute(Boolean exportResult) { if (mExportParams.shouldDeleteTransactionsAfterExport()) { Log.i(TAG, "Backup and deleting transactions after export"); backupAndDeleteTransactions(); - - //now refresh the respective views - if (mContext instanceof AccountsActivity){ - AccountsListFragment fragment = - ((AccountsActivity) mContext).getCurrentAccountListFragment(); - if (fragment != null) - fragment.refresh(); - } - if (mContext instanceof TransactionsActivity){ - ((TransactionsActivity) mContext).refresh(); - } + refreshViews(); } } @@ -537,4 +527,16 @@ private void reportSuccess() { String.format(mContext.getString(R.string.toast_exported_to), targetLocation), Toast.LENGTH_LONG).show(); } + + private void refreshViews() { + if (mContext instanceof AccountsActivity){ + AccountsListFragment fragment = + ((AccountsActivity) mContext).getCurrentAccountListFragment(); + if (fragment != null) + fragment.refresh(); + } + if (mContext instanceof TransactionsActivity){ + ((TransactionsActivity) mContext).refresh(); + } + } } From da1e53a0dc1d6e7c25c910c62616f711974de91f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=80lex=20Magaz=20Gra=C3=A7a?= Date: Fri, 30 Dec 2016 10:07:41 +0100 Subject: [PATCH 19/25] Invert conditional to avoid negation --- .../gnucash/android/export/ExportAsyncTask.java | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) 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 b241a80fa..ad9bc911b 100644 --- a/app/src/main/java/org/gnucash/android/export/ExportAsyncTask.java +++ b/app/src/main/java/org/gnucash/android/export/ExportAsyncTask.java @@ -180,13 +180,7 @@ public void run() { */ @Override protected void onPostExecute(Boolean exportResult) { - if (!exportResult) { - if (mContext instanceof Activity) { - Toast.makeText(mContext, - mContext.getString(R.string.toast_export_error, mExportParams.getExportFormat().name()), - Toast.LENGTH_LONG).show(); - } - } else { + if (exportResult) { if (mContext instanceof Activity) reportSuccess(); @@ -195,6 +189,12 @@ protected void onPostExecute(Boolean exportResult) { backupAndDeleteTransactions(); refreshViews(); } + } else { + if (mContext instanceof Activity) { + Toast.makeText(mContext, + mContext.getString(R.string.toast_export_error, mExportParams.getExportFormat().name()), + Toast.LENGTH_LONG).show(); + } } if (mContext instanceof Activity) { From 6271e80004bbddcb0cc2c2fbc3f2d8278e055236 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=80lex=20Magaz=20Gra=C3=A7a?= Date: Fri, 30 Dec 2016 10:09:06 +0100 Subject: [PATCH 20/25] Rename parameter to make more clear its meaning --- .../java/org/gnucash/android/export/ExportAsyncTask.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) 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 ad9bc911b..921b0b3b3 100644 --- a/app/src/main/java/org/gnucash/android/export/ExportAsyncTask.java +++ b/app/src/main/java/org/gnucash/android/export/ExportAsyncTask.java @@ -176,11 +176,11 @@ 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 + * @param exportSuccessful Result of background export execution */ @Override - protected void onPostExecute(Boolean exportResult) { - if (exportResult) { + protected void onPostExecute(Boolean exportSuccessful) { + if (exportSuccessful) { if (mContext instanceof Activity) reportSuccess(); From cdba3cbdcf3ca2c223111f173b352b2b382948fd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=80lex=20Magaz=20Gra=C3=A7a?= Date: Fri, 30 Dec 2016 10:10:32 +0100 Subject: [PATCH 21/25] Move log call to the method it refers --- .../main/java/org/gnucash/android/export/ExportAsyncTask.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 921b0b3b3..f1c0ffb39 100644 --- a/app/src/main/java/org/gnucash/android/export/ExportAsyncTask.java +++ b/app/src/main/java/org/gnucash/android/export/ExportAsyncTask.java @@ -185,7 +185,6 @@ protected void onPostExecute(Boolean exportSuccessful) { reportSuccess(); if (mExportParams.shouldDeleteTransactionsAfterExport()) { - Log.i(TAG, "Backup and deleting transactions after export"); backupAndDeleteTransactions(); refreshViews(); } @@ -401,6 +400,7 @@ private String stripPathPart(String fullPathName) { * and deletes all non-template transactions in the database. */ private void backupAndDeleteTransactions(){ + Log.i(TAG, "Backup and deleting transactions after export"); GncXmlExporter.createBackup(); //create backup before deleting everything List openingBalances = new ArrayList<>(); boolean preserveOpeningBalances = GnuCashApplication.shouldSaveOpeningBalances(false); From daaf416ff36081ac7f9592d9910bb9fce660a706 Mon Sep 17 00:00:00 2001 From: Ngewi Fet Date: Mon, 23 Jan 2017 10:07:26 +0100 Subject: [PATCH 22/25] Update version strings in preparation for v2.1.4 release Update translations Update CHANGELOG --- CHANGELOG.md | 12 + app/build.gradle | 2 +- app/src/main/res/values-cs-rCZ/strings.xml | 2 +- app/src/main/res/values-es-rMX/strings.xml | 97 +++-- app/src/main/res/values-fi-rFI/strings.xml | 74 ++-- app/src/main/res/values-in-rID/strings.xml | 474 +++++++++++++++++++++ app/src/main/res/values-pt-rBR/strings.xml | 32 +- app/src/main/res/values-pt-rPT/strings.xml | 32 +- app/src/main/res/values-ru/strings.xml | 2 +- app/src/main/res/values-sv-rSE/strings.xml | 72 ++-- app/src/main/res/values-zh-rCN/strings.xml | 6 +- 11 files changed, 645 insertions(+), 160 deletions(-) create mode 100644 app/src/main/res/values-in-rID/strings.xml diff --git a/CHANGELOG.md b/CHANGELOG.md index d2e9a7e8d..6d78f2321 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,17 @@ Change Log =============================================================================== +Version 2.1.4 *(2017-01-30)* +---------------------------- +* Fixed: Bugs in execution of some scheduled actions (#604, #609) +* Fixed: Multi-currency transactions not exported when format is QIF (#571) +* Fixed: Incorrect date of last export shown in book manager (#615, #617) +* Fixed: Large exports may be reported as successful even if they didn't complete yet (#616) +* Fixed: Account color reset when editing an account (#620) +* Fixed: Export to OwnCloud fails if folder already exists +* Fixed: User not notified if export to external target fails +* Improved translations + + Version 2.1.3 *(2016-10-20)* ---------------------------- * Fixed: Scheduled exports execute too often or not at all in some cases diff --git a/app/build.gradle b/app/build.gradle index 6b520971a..2da97e1d1 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -7,7 +7,7 @@ apply plugin: 'io.fabric' def versionMajor = 2 def versionMinor = 1 -def versionPatch = 3 +def versionPatch = 4 def versionBuild = 1 def buildTime() { diff --git a/app/src/main/res/values-cs-rCZ/strings.xml b/app/src/main/res/values-cs-rCZ/strings.xml index 2603b4b90..a21786e46 100644 --- a/app/src/main/res/values-cs-rCZ/strings.xml +++ b/app/src/main/res/values-cs-rCZ/strings.xml @@ -18,7 +18,7 @@ Create Account Edit Account - Info + Čestina Export… Add a new transaction to an account No accounts to display diff --git a/app/src/main/res/values-es-rMX/strings.xml b/app/src/main/res/values-es-rMX/strings.xml index 46d68e911..68f76a3bb 100644 --- a/app/src/main/res/values-es-rMX/strings.xml +++ b/app/src/main/res/values-es-rMX/strings.xml @@ -19,13 +19,13 @@ Crear Cuenta Editar Cuenta Detalles - Exportar OFX + Exportar… Añadir una nueva transacción a una cuenta No hay cuentas que mostrar Nombre de la cuenta Cancelar Guardar - Test + Prueba Introducir contraseña Contraseña incorrecta, intentar otra vez Contraseña establecida @@ -52,13 +52,13 @@ MOVER %1$d seleccionado Saldo: - Export To: - Export Transactions + Exportar A: + Exportar Transacciones Exportar todas las transacciones Por defecto solo las nuevas transacciones desde la última exportación serán exportadas. Seleccione esta opción para exportar todas las transacciones Error exportando datos %1$s Exportar - Delete transactions after export + Borrar transacciones despues de exportar Todas las transacciones exportadas serán borradas cuando la exportación haya terminado Ajustes @@ -66,7 +66,7 @@ DropBox Google Drive ownCloud - Send to… + Enviar a… Mover Mover %1$d transacción(es) @@ -82,8 +82,8 @@ Permite crear cuentas en Gnucash para Android Sus datos GnuCash Leer y modificar datos Gnucash - Record transactions in GnuCash - Create accounts in GnuCash + Registrar transacciones en GnuCash + Crear cuentas en GnuCash Mostrar cuentas Crear Cuentas Seleccionar cuentas a crear @@ -132,13 +132,12 @@ Active esta opción para exportar a otras aplicaciones distintas a GnuCash para escritorio Novedades - - Support for multiple different books \n - - Adds ownCloud as destination for exports\n - - Compact view for transactions list\n - - Re-design of passcode lock screen\n - - Improved handling of scheduled transactions\n - - Multiple bug fixes and improvements\n - + - Soporte de múltiples libros\n +- Añade ownCloud como destino para exportaciones\n +- Vista compacta para la lista de transacciones\n +- Rediseño de la pantalla de bloqueo con contraseña\n +- Mejora del manejo de transacciones programadas\n +- Múltiples fallos solucionados y otras mejoras\n Cerrar Introduzca un importe para guardar la transacción Las transacciones multi-divisa so se pueden modificar @@ -211,13 +210,13 @@ Todas Crea una estructura por defecto de cuentas GnuCash comúnmente usadas Crear cuentas por defecto - A new book will be opened with the default accounts\n\nYour current accounts and transactions will not be modified! + Se abrirá un nuevo libro con las cuentas por defecto\n\nSus cuentas y transacciones actuales no se modificarán! Transacciones Programadas ¡Bienvenido a GnuCash Android! \nPuede crear una jerarquía de cuentas comúnmente usadas o importar su propia estructura de cuentas GnuCash. \n\nAmbas opciones están disponibles en las opciones de la aplicació por si quiere decidirlo más tarde. Transacciones Programadas Seleccionar destino para exportar - Memo + Nota Gastar Recibir Sacar @@ -252,7 +251,7 @@ Gráfico de tarta Gráfico de línea Gráfico de barras - Report Preferences + Preferencias de informe Seleccionar divisa Color de la cuenta en los informes Usar el color de la cuenta en las gráficas de barra y tarta @@ -277,11 +276,11 @@ Toque para crear la programación Restaurar copia de seguridad… Copia de seguridad y exportación - Enable DropBox - Enable ownCloud + Habilitar DropBox + Habilitar ownCloud Copia de seguridad - Enable exporting to DropBox - Enable exporting to ownCloud + Habilitar exportar a DropBox + Habilitar exportar a ownCloud Seleccionar archivo XML GnuCash Preferencias de copia de seguridad Crear copia de seguridad @@ -290,8 +289,8 @@ Copia de seguridad exitosa Copia de seguridad fallida Exportar todas las cuentas y transacciones - Enable Google Drive - Enable exporting to Google Drive + Habilitar Google Drive + Habilitar exportar a Google Drive Instale un gestor de archivos para seleccionar archivos Seleccione la copia de seguridad a restaurar Favoritos @@ -300,16 +299,16 @@ Transacciones Programadas Exportar… Ajustes - User Name - Password - owncloud + Nombre de usuario + Contraseña + ownCloud https:// - OC server not found - OC username/password invalid - Invalid chars: \\ < > : \" | * ? - OC server OK - OC username/password OK - Dir name OK + Servidor OC no encontrado + Usuario/contraseña OC no válidos + Caracteres inválidos: \\ < > : \" | * ? + Servidor OC correcto + Usuario/contraseña OC correctos + Nombre del directorio correcto Diario Cada %d días @@ -393,21 +392,21 @@ Este proceso solo recoge información que no permite identificar al usuarioAño Hoja de Balance Total: - Google+ Community - Translate GnuCash - Share ideas, discuss changes or report problems - Translate or proof-read on CrowdIn - No compatible apps to receive the exported transactions! - Move… - Duplicate - Budgets - Cash Flow - Budgets - Enable compact view - Enable to always use compact view for transactions list - Invalid exchange rate - e.g. 1 %1$s = x.xx %2$s - Invalid amount + Comunidad Google+ + Traducir GnuCash + Compartir ideas, discutir cambios o reportar problemas + Traducir o revisar en CrowdIn + ¡No hay aplicaciones compatibles para recibir las transacciones exportadas! + Mover… + Duplicar + Presupuestos + Flujo de efectivo + Presupuestos + Activar la vista compacta + Activar para usar siempre la vista compacta en la lista de transacciones + Tipo de cambio no válido + ej. 1 %1$s = x.xx %2$s + Cantidad inválida Mes actual Últimos 3 meses @@ -474,5 +473,5 @@ Este proceso solo recoge información que no permite identificar al usuarioon %1$s for %1$s times Compact View - Book %1$d + Libro %1$d diff --git a/app/src/main/res/values-fi-rFI/strings.xml b/app/src/main/res/values-fi-rFI/strings.xml index 0c1a4909f..07e296b45 100644 --- a/app/src/main/res/values-fi-rFI/strings.xml +++ b/app/src/main/res/values-fi-rFI/strings.xml @@ -16,45 +16,45 @@ limitations under the License. --> - Create Account - Edit Account - Info - Export… - Add a new transaction to an account - No accounts to display - Account name - Cancel - Save - Test - Enter Passcode - Wrong passcode, please try again - Passcode set - Please confirm your passcode - Invalid passcode confirmation. Please try again - Description - Amount - New transaction - No transactions to display + Luo tili + Muokkaa tiliä + Tiedot + Vie… + Lisää uusi tapahtuma tilille + Ei näytettävissä olevia tilejä + Tilin nimi + Peruuta + Tallenna + Testaa + Anna tunnuskoodi + Virheellinen tunnuskoodi, yritä uudelleen + Tunnuskoodi asetettu + Vahvista tunnuskoodi + Virheellinen tunnuskoodin vahvistus. Yritä uudelleen + Kuvaus + Summa + Uusi tapahtuma + Ei näytettäviä tapahtumia DATE & TIME - Account - DEBIT - CREDIT - Accounts - Transactions - Delete - Delete - Cancel - Account deleted - Confirm delete - All transactions in this account will also be deleted - Edit Transaction - Add note - MOVE - %1$d selected - Balance: + Tili + VELOITUS + HYVITYS + Tilit + Tapahtumat + Poista + Poista + Peruuta + Tili poistettu + Vahvista poistaminen + Kaikki tämän tilin tapahtumat tullaan myös poistamaan + Muokkaa tapahtumaa + Lisää huomautus + SIIRRÄ + %1$d valittuna + Saldo: Export To: - Export Transactions - Export all transactions + Vie tapahtumat + Vie kaikki tapahtumat By default, only new transactions since last export will be exported. Check this option to export all transactions Error exporting %1$s file Export diff --git a/app/src/main/res/values-in-rID/strings.xml b/app/src/main/res/values-in-rID/strings.xml new file mode 100644 index 000000000..1df061e4a --- /dev/null +++ b/app/src/main/res/values-in-rID/strings.xml @@ -0,0 +1,474 @@ + + + + + Create Account + Edit Account + Info + Export… + Add a new transaction to an account + No accounts to display + Account name + Cancel + Save + Test + Enter Passcode + Wrong passcode, please try again + Passcode set + Please confirm your passcode + Invalid passcode confirmation. Please try again + Description + Amount + New transaction + No transactions to display + DATE & TIME + Account + DEBIT + CREDIT + Accounts + Transactions + Delete + Delete + Cancel + Account deleted + Confirm delete + All transactions in this account will also be deleted + Edit Transaction + Add note + MOVE + %1$d selected + Balance: + Export To: + Export Transactions + Export all transactions + By default, only new transactions since last export will be exported. Check this option to export all transactions + Error exporting %1$s file + Export + Delete transactions after export + All exported transactions will be deleted when exporting is completed + Settings + + SD Card + DropBox + Google Drive + ownCloud + Send to… + + Move + Move %1$d transaction(s) + Destination Account + Access SD Card + Cannot move transactions.\nThe destination account uses a different currency from origin account + General + About + Choose default currency + Default currency + Default currency to assign to new accounts + Enables recording transactions in GnuCash for Android + Enables creation of accounts in GnuCash for Android + Your GnuCash data + Read and modify GnuCash data + Record transactions in GnuCash + Create accounts in GnuCash + Display account + Create Accounts + Select accounts to create + No accounts exist in GnuCash.\nCreate an account before adding a widget + Build version + License + Apache License v2.0. Click for details + General Preferences + Select Account + There are no transactions available to export + Passcode + Passcode Preferences + Passcode Turned On + Passcode Turned Off + Change Passcode + About GnuCash + A mobile finance management and expense-tracker designed for Android + About + %1$s file exported to:\n + GnuCash Android %1$s export + GnuCash Android Export from + Transactions + Transaction Preferences + Account Preferences + Default Transaction Type + The type of transaction to use by default, CREDIT or DEBIT + + CREDIT + DEBIT + + Are you sure you want to delete ALL transactions? + Are you sure you want to delete this transaction? + Export + Export all transactions + Delete exported transactions + Default export email + The default email address to send exports to. You can still change this when you export. + Transfer Account + All transactions will be a transfer from one account to another + Activate Double Entry + Balance + Enter an account name to create an account + Currency + Parent account + Use XML OFX header + Enable this option when exporting to third-party application other than GnuCash for desktop + What\'s New + + - Support for multiple different books \n + - Adds ownCloud as destination for exports\n + - Compact view for transactions list\n + - Re-design of passcode lock screen\n + - Improved handling of scheduled transactions\n + - Multiple bug fixes and improvements\n + + Dismiss + Enter an amount to save the transaction + Multi-currency transactions cannot be modified + Import GnuCash Accounts + Import Accounts + An error occurred while importing the GnuCash accounts + GnuCash Accounts successfully imported + Import account structure exported from GnuCash desktop + Import GnuCash XML + Delete all accounts in the database. All transactions will be deleted as + well. + + Delete all accounts + Accounts + All accounts have been successfully deleted + Are you sure you want to delete all accounts and transactions?\n\nThis + operation cannot be undone! + + Account Type + All transactions in all accounts will be deleted! + Delete all transactions + All transactions successfully deleted! + Importing accounts + Tap again to confirm. ALL entries will be deleted!! + Transactions + Sub-Accounts + Search + Default Export Format + File format to use by default when exporting transactions + Export transactions… + Recurrence + + Imbalance + Exporting transactions + No recurring transactions to display. + Successfully deleted recurring transaction + Placeholder account + Default Transfer Account + + %d sub-accounts + + + CASH + BANK + CREDIT CARD + ASSET + LIABILITY + INCOME + EXPENSE + PAYABLE + RECEIVABLE + EQUITY + CURRENCY + STOCK + MUTUAL FUND + TRADING + + + QIF + OFX + XML + + + Select a Color + + + Account Color & Type + Delete sub-accounts + Recent + Favorites + All + Creates default GnuCash commonly-used account structure + Create default accounts + A new book will be opened with the default accounts\n\nYour current accounts and transactions will not be modified! + Scheduled Transactions + Welcome to GnuCash Android! \nYou can either create + a hierarchy of commonly-used accounts, or import your own GnuCash account structure. \n\nBoth options are also + available in app Settings so you can decide later. + + Transactions + Select destination for export + Memo + Spend + Receive + Withdrawal + Deposit + Payment + Charge + Decrease + Increase + Income + Rebate + Expense + Bill + Invoice + Buy + Sell + Repeats + No recent backup found + Opening Balances + Equity + Enable to save the current account balance (before deleting transactions) as new opening balance after deleting transactions + + Save account opening balances + OFX does not support double-entry transactions + Generates separate QIF files per currency + Transaction splits + Imbalance: + Add split + Favorite + Navigation drawer opened + Navigation drawer closed + Reports + Pie Chart + Line Chart + Bar Chart + Report Preferences + Select currency + Account color in reports + Use account color in the bar/pie chart + Reports + Order by size + Show legend + Show labels + Show percentage + Show average lines + Group Smaller Slices + No chart data available + Overall + Total + Other + The percentage of selected value calculated from the total amount + The percentage of selected value calculated from the current stacked bar amount + Save as template + This account contains transactions. \nWhat would you like to do with these transactions + This account contains sub-accounts. \nWhat would you like to do with these sub-accounts + Delete transactions + Create and specify a transfer account OR disable double-entry in settings to save the transaction + Tap to create schedule + Restore Backup… + Backup & export + Enable DropBox + Enable ownCloud + Backup + Enable exporting to DropBox + Enable exporting to ownCloud + Select GnuCash XML file + Backup Preferences + Create Backup + By default backups are saved to the SDCARD + Select a specific backup to restore + Backup successful + Backup failed + Exports all accounts and transactions + Enable Google Drive + Enable exporting to Google Drive + Install a file manager to select files + Select backup to restore + Favorites + Open… + Reports + Scheduled Transactions + Export… + Settings + User Name + Password + owncloud + https:// + OC server not found + OC username/password invalid + Invalid chars: \\ < > : \" | * ? + OC server OK + OC username/password OK + Dir name OK + + Every %d days + + + Every %d weeks + + + Every %d months + + + Every %d years + + Enable Crash Logging + Automatically send information about app malfunction to the developers. + Format + Backup folder cannot be found. Make sure the SD Card is mounted! + Enter your old passcode + Enter your new passcode + Scheduled Exports + Exports + No scheduled exports to display + Create export schedule + Exported to: %1$s + The legend is too long + Account description + No recent accounts + No favorite accounts + Scheduled Actions + "Ended, last executed on %1$s" + Select a bar to view details + Next + Done + Default Currency + Account Setup + Select Currency + Feedback Options + Create default accounts + Import my accounts + Let me handle it + Other… + Automatically send crash reports + Disable crash reports + Back + Setup GnuCash + Welcome to GnuCash + Before you dive in, \nlet\'s setup a few things first\n\nTo continue, press Next + Split Editor + Check that all splits have valid amounts before saving! + Invalid expression! + Scheduled recurring transaction + An exchange rate is required + The converted amount is required + Transfer Funds + + Select a slice to see details + Period: + From: + To: + Provide either the converted amount or exchange rate in order to transfer funds + Exchange rate + Fetch quote + Converted Amount + Sheet + Expenses for last 3 months + Total Assets + Total Liabilities + Net Worth + Assets + Liabilities + Equity + + Move to: + Group By + Month + Quarter + Year + Balance Sheet + Total: + Google+ Community + Translate GnuCash + Share ideas, discuss changes or report problems + Translate or proof-read on CrowdIn + No compatible apps to receive the exported transactions! + Move… + Duplicate + Budgets + Cash Flow + Budgets + Enable compact view + Enable to always use compact view for transactions list + Invalid exchange rate + e.g. 1 %1$s = x.xx %2$s + Invalid amount + + Current month + Last 3 months + Last 6 months + Last 12 months + All time + Custom range… + + + 1 + + + 2 + ABC + 3 + DEF + 4 + GHI + 5 + JKL + 6 + MNO + 7 + PQRS + 8 + TUV + 9 + WXYZ + 0 + + + Manage Books + Manage Books… + Select any part of the chart to view details + Confirm delete Book + All accounts and transactions in this book will be deleted! + Delete Book + Last Exported: + Enable Sync + New Book + The selected transaction has no splits and cannot be opened + %1$d splits + in %1$s + + %d accounts + + + %d transactions + + + EXPENSE + INCOME + + Connected to Google Drive + Unable to connect to Google Drive + Please enter an amount to split + external service + Updated transaction recurring schedule + Since + All time + Recommend in Play Store + until %1$s + on %1$s + for %1$s times + Compact View + Book %1$d + diff --git a/app/src/main/res/values-pt-rBR/strings.xml b/app/src/main/res/values-pt-rBR/strings.xml index 404cbe75f..f3b7b3965 100644 --- a/app/src/main/res/values-pt-rBR/strings.xml +++ b/app/src/main/res/values-pt-rBR/strings.xml @@ -449,28 +449,28 @@ Neste processo não serão recolhidas informações do utilizador! %1$d divisões em %1$s - %d account - %d accounts + %d conta + %d contas - %d transaction - %d transactions + %d transação + %d transações - EXPENSE - INCOME + DESPESA + RENDIMENTO - Connected to Google Drive - Unable to connect to Google Drive + Conectado ao Google Drive + Impossível conectar ao Google Drive Please enter an amount to split - external service - Updated transaction recurring schedule - Since - All time - Recommend in Play Store - until %1$s + serviço externo + Agenda recorrente de transação atualizada + Desde + Desde o início + Recomendado na Play Store + até %1$s on %1$s - for %1$s times - Compact View + por %1$s vezes + Visualização compacta Book %1$d diff --git a/app/src/main/res/values-pt-rPT/strings.xml b/app/src/main/res/values-pt-rPT/strings.xml index 329c7ff77..394812e63 100644 --- a/app/src/main/res/values-pt-rPT/strings.xml +++ b/app/src/main/res/values-pt-rPT/strings.xml @@ -20,9 +20,9 @@ Editar Conta Informação Exportar - Adicionar nova transação a uma conta + Adicionar nova transacção a uma conta Sem contas para mostrar - Nome da Conta + Nome da conta Cancelar Gravar Testar @@ -69,35 +69,35 @@ Enviar para… Mover - Move %1$d transação - Conta Destino - Aceder ao SD Card - Impossível mover transação.\nA conta destino usa uma moeda diferente da conta de origem. + Mover %1$d transacções + Conta destino + Aceder ao cartão SD + Impossível mover transacção.\nA conta destino usa uma moeda diferente da conta origem Geral - Sobre - Escolhe a moeda padrão + Acerca + Escolher a moeda padrão Moeda padrão Moeda padrão para as novas contas - Permite gravar transações no GnuCash para Android + Permite gravar transacções no GnuCash para Android Permite criar contas no GnuCash para Android Os seus dados Gnucash Ler e modificar dados do GnuCash - Gravar transações no GnuCash + Gravar transacções no GnuCash Criar contas no GnuCash Mostrar conta - Criar Contas + Criar contas Escolha as contas a criar Não existem contas no GnuCash.\nCrie uma conta antes de adicionar um widget Versão - Licensa + Licença Apache License v2.0. Clique para obter detalhes Preferências Escolha uma conta Não existem transacções para exportar - Palavra passe - Preferências de palavra passe - Palavra passe ligada - Palavra passe desligada + Senha + Preferências de senha + Senha ligada + Senha desligada Mudar Palavra Passe Sobre o GnuCash Um programa de registo e gestão de finanças pessoais para Android diff --git a/app/src/main/res/values-ru/strings.xml b/app/src/main/res/values-ru/strings.xml index 057c42715..1d76ac7ae 100644 --- a/app/src/main/res/values-ru/strings.xml +++ b/app/src/main/res/values-ru/strings.xml @@ -418,7 +418,7 @@ Последний квартал Последнее полугодие Последний год - Всё время + За всё время Другой интервал… diff --git a/app/src/main/res/values-sv-rSE/strings.xml b/app/src/main/res/values-sv-rSE/strings.xml index 0c1a4909f..892de105d 100644 --- a/app/src/main/res/values-sv-rSE/strings.xml +++ b/app/src/main/res/values-sv-rSE/strings.xml @@ -16,44 +16,44 @@ limitations under the License. --> - Create Account - Edit Account - Info - Export… - Add a new transaction to an account - No accounts to display - Account name - Cancel - Save + Skapa konto + Redigera konto + Information + Exportera… + Lägg till en transaktion till ett konto + Det finns inga konton att visa + Kontonamn + Avbryt + Spara Test - Enter Passcode - Wrong passcode, please try again - Passcode set - Please confirm your passcode - Invalid passcode confirmation. Please try again - Description - Amount - New transaction - No transactions to display - DATE & TIME - Account + Ange pinkod + Fel pinkod, var god försök igen + Pinkod sparad + Var god bekräfta din pinkod + Felaktig pinkod. Var god försök igen + Beskrivning + Belopp + Lägg till transaktion + Det finns inga transaktioner att visa + DATUM & TID + Konto DEBIT - CREDIT - Accounts - Transactions - Delete - Delete - Cancel - Account deleted - Confirm delete - All transactions in this account will also be deleted - Edit Transaction - Add note - MOVE - %1$d selected - Balance: - Export To: - Export Transactions + KREDIT + Konton + Transaktioner + Ta bort + Ta bort + Avbryt + Kontot borttaget + Bekräfta radering + Alla transaktioner i kontot kommer också raderas + Redigera transaktion + Lägg till anteckning + FLYTTA + %1$d har markerats + Saldo: + Exportera till: + Exportera transaktioner Export all transactions By default, only new transactions since last export will be exported. Check this option to export all transactions Error exporting %1$s file diff --git a/app/src/main/res/values-zh-rCN/strings.xml b/app/src/main/res/values-zh-rCN/strings.xml index e2b73fd7e..66b15cc70 100644 --- a/app/src/main/res/values-zh-rCN/strings.xml +++ b/app/src/main/res/values-zh-rCN/strings.xml @@ -63,7 +63,7 @@ 设置 SD卡 - Dropbox + DropBox Google 云端硬盘 ownCloud 发送到... @@ -461,10 +461,10 @@ 计划交易已经更新 时间点: 全部 - 在Paly Store上推荐 + 在 Play Store 上推荐 直到%1$s 在%1$s 共%1$s次 紧凑视图 - Book %1$d + 账簿 %1$d From a003d519d5647be31786f4e6b46d0399fc7bbf2a Mon Sep 17 00:00:00 2001 From: HeinrichLohse Date: Mon, 23 Jan 2017 21:29:58 +0100 Subject: [PATCH 23/25] proper handling of custom date range selections - fixes #611 (#639) --- .../ui/util/dialog/DateRangePickerDialogFragment.java | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/app/src/main/java/org/gnucash/android/ui/util/dialog/DateRangePickerDialogFragment.java b/app/src/main/java/org/gnucash/android/ui/util/dialog/DateRangePickerDialogFragment.java index 954c10f2e..1447119d1 100644 --- a/app/src/main/java/org/gnucash/android/ui/util/dialog/DateRangePickerDialogFragment.java +++ b/app/src/main/java/org/gnucash/android/ui/util/dialog/DateRangePickerDialogFragment.java @@ -52,6 +52,7 @@ public class DateRangePickerDialogFragment extends DialogFragment{ private Date mStartRange = LocalDate.now().minusMonths(1).toDate(); private Date mEndRange = LocalDate.now().toDate(); private OnDateRangeSetListener mDateRangeSetListener; + private static final long ONE_DAY_IN_MILLIS = 24 * 60 * 60 * 1000; public static DateRangePickerDialogFragment newInstance(OnDateRangeSetListener dateRangeSetListener){ DateRangePickerDialogFragment fragment = new DateRangePickerDialogFragment(); @@ -89,7 +90,11 @@ public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle sa public void onClick(View v) { List selectedDates = mCalendarPickerView.getSelectedDates(); Date startDate = selectedDates.get(0); - Date endDate = selectedDates.size() == 2 ? selectedDates.get(1) : new Date(); + // If only one day is selected (no interval) start and end should be the same (the selected one) + Date endDate = selectedDates.size() > 1 ? selectedDates.get(selectedDates.size() - 1) : new Date(startDate.getTime()); + // CaledarPicker returns the start of the selected day but we want all transactions of that day to be included. + // Therefore we have to add 24 hours to the endDate. + endDate.setTime(endDate.getTime() + ONE_DAY_IN_MILLIS); mDateRangeSetListener.onDateRangeSet(startDate, endDate); dismiss(); } From 83808b64d58ff8ffa89cc8a7fb89a7c6063b9294 Mon Sep 17 00:00:00 2001 From: Juan Villa Date: Sun, 22 Jan 2017 20:41:11 -0600 Subject: [PATCH 24/25] Preserve report type accross rotation * fixes codinguser/gnucash-android#633 --- .../gnucash/android/ui/report/ReportsActivity.java | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) 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 index 1b72ed8f3..5f8b47e9b 100644 --- a/app/src/main/java/org/gnucash/android/ui/report/ReportsActivity.java +++ b/app/src/main/java/org/gnucash/android/ui/report/ReportsActivity.java @@ -74,6 +74,7 @@ public class ReportsActivity extends BaseDrawerActivity implements AdapterView.O Color.parseColor("#ba037c"), Color.parseColor("#708809"), Color.parseColor("#32072c"), Color.parseColor("#fddef8"), Color.parseColor("#fa0e6e"), Color.parseColor("#d9e7b5") }; + private static final String STATE_REPORT_TYPE = "STATE_REPORT_TYPE"; @Bind(R.id.time_range_spinner) Spinner mTimeRangeSpinner; @Bind(R.id.report_account_type_spinner) Spinner mAccountTypeSpinner; @@ -123,8 +124,11 @@ public int getTitleRes() { @Override protected void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); + if (savedInstanceState != null) { + mReportType = (ReportType) savedInstanceState.getSerializable(STATE_REPORT_TYPE); + } + super.onCreate(savedInstanceState); mTransactionsDbAdapter = TransactionsDbAdapter.getInstance(); ArrayAdapter adapter = ArrayAdapter.createFromResource(this, R.array.report_time_range, @@ -414,4 +418,11 @@ public void refresh() { public void refresh(String uid) { refresh(); } + + @Override + protected void onSaveInstanceState(Bundle outState) { + super.onSaveInstanceState(outState); + + outState.putSerializable(STATE_REPORT_TYPE, mReportType); + } } From a9320cd2b645e6b227fb6e60678af401439f9d9e Mon Sep 17 00:00:00 2001 From: Ngewi Fet Date: Mon, 30 Jan 2017 14:19:34 +0100 Subject: [PATCH 25/25] Update string for v2.1.4 release Update French translations, update CHANGELOG --- CHANGELOG.md | 1 + CONTRIBUTORS.md | 1 + app/build.gradle | 2 +- app/src/main/res/values-fr/strings.xml | 36 +++++++++++++------------- 4 files changed, 21 insertions(+), 19 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6d78f2321..46f297967 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,7 @@ Version 2.1.4 *(2017-01-30)* * Fixed: Multi-currency transactions not exported when format is QIF (#571) * Fixed: Incorrect date of last export shown in book manager (#615, #617) * Fixed: Large exports may be reported as successful even if they didn't complete yet (#616) +* Fixed: Custom date range (in reports) does not select correct ending date (#611) * Fixed: Account color reset when editing an account (#620) * Fixed: Export to OwnCloud fails if folder already exists * Fixed: User not notified if export to external target fails diff --git a/CONTRIBUTORS.md b/CONTRIBUTORS.md index 48c117f61..b57d1bc49 100644 --- a/CONTRIBUTORS.md +++ b/CONTRIBUTORS.md @@ -36,5 +36,6 @@ The following (incomplete list of) people (in no particular order) contributed ( * Felipe Morato * Alceu Rodrigues Neto * Salama AB +* Juan Villa Please visit https://crowdin.com/project/gnucash-android for a more complete list of translation contributions \ No newline at end of file diff --git a/app/build.gradle b/app/build.gradle index 2da97e1d1..d72adee5b 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -8,7 +8,7 @@ apply plugin: 'io.fabric' def versionMajor = 2 def versionMinor = 1 def versionPatch = 4 -def versionBuild = 1 +def versionBuild = 2 def buildTime() { def df = new SimpleDateFormat("yyyyMMdd HH:mm 'UTC'") diff --git a/app/src/main/res/values-fr/strings.xml b/app/src/main/res/values-fr/strings.xml index c7c682862..a264ad7a1 100644 --- a/app/src/main/res/values-fr/strings.xml +++ b/app/src/main/res/values-fr/strings.xml @@ -52,13 +52,13 @@ DÉPLACER %1$d sélectionné(s) Solde: - Export To: + Exporter vers: Exporter les transactions Exporter toutes les transactions Par défaut, seul les nouvelles transactions depuis le dernier export seront exportées. Cochez cette option pour exporter toutes les transactions Erreur lors de l\'export des données en %1$s Exporter - Delete transactions after export + Effacer les transactions apres export Toutes les transactions exportées seront supprimées aprés l\'export Paramètres @@ -66,7 +66,7 @@ DropBox Google Drive ownCloud - Send to… + Envoyer vers… Déplacer Déplacer %1$d transaction(s) @@ -82,8 +82,8 @@ Permettre la création de comptes dans GnuCash pour Android Vos données GnuCash Lire et modifier les données GnuCash - Record transactions in GnuCash - Create accounts in GnuCash + Enregistrer les transactions dans GnuCash + Créer comptes dans GnuCash Afficher le compte Créer les comptes Choisisez les comptes à créés @@ -116,7 +116,7 @@ Êtes vous sûre de vouloir supprimer TOUTES les transactions ? Êtes vous sûre de vouloir supprimer cette transaction ? - Export + Exporter Exporter toutes les transactions Supprimer les transactions exportées Email d\'export par défaut @@ -124,7 +124,7 @@ Transfert entre comptes Toutes les transactions seront transférées d\'un compte à l\'autre Activer les doubles entrée - Balance + Solde Entrer un nom de compte pour créer un compte Monnaie Compte parent @@ -259,7 +259,7 @@ Tri par taille Show legend Show labels - Show percentage + Montrer pourcentage Show average lines Groupe par petites tranches Aucune données de graphe disponible @@ -276,11 +276,11 @@ Appuyez sur pour créer le calendrier Restaurer Sauvegarde… Sauvegarde & export - Enable DropBox - Enable ownCloud + Activer DropBox + Activer ownCloud Sauvegarde - Enable exporting to DropBox - Enable exporting to ownCloud + Activer export vers DropBox + Activer export vers owncloud Selectionner un fichier GnuCash XML Sauvergarde Préférences Créer Sauvegarde @@ -289,8 +289,8 @@ Sauvegarde réussie Échec de la sauvegarde Exporte tous les comptes et les transactions - Enable Google Drive - Enable exporting to Google Drive + Activer Google Drive + Activer export vers Google Drive Installez un gestionnaire de fichiers pour sélectionner les fichiers Sélectionnez une sauvegarde à restaurer Favoris @@ -299,11 +299,11 @@ Transactions planifiées Export… Paramètres - User Name - Password - owncloud + Nom d\'utilisateur + Mot de passe + ownCloud https:// - OC server not found + Serveur OC inaccessible OC username/password invalid Invalid chars: \\ < > : \" | * ? OC server OK