From ff894a5ce5901bafc8626279d09278efc229ef23 Mon Sep 17 00:00:00 2001
From: Ngewi Fet
Date: Mon, 19 Sep 2016 17:48:51 +0200
Subject: [PATCH 1/7] Adds multibook support for the homescreen widget
When adding a widget, there is now an option for choosing the book in addition to the account.
Updates to an account in the relevant book will be reflected in the widget.
This change handles no migration from the old widget structure. (Users need to remove and re-add widgets)
---
.../android/app/GnuCashApplication.java | 18 ++-
.../org/gnucash/android/db/BookDbHelper.java | 10 ++
.../android/db/adapter/BooksDbAdapter.java | 2 +
.../TransactionAppWidgetProvider.java | 13 ++-
.../android/ui/common/BaseDrawerActivity.java | 6 +
.../android/ui/common/FormActivity.java | 7 ++
.../gnucash/android/ui/common/UxArgument.java | 5 +
.../WidgetConfigurationActivity.java | 105 ++++++++++++++----
.../main/res/layout/widget_configuration.xml | 23 +++-
9 files changed, 153 insertions(+), 36 deletions(-)
diff --git a/app/src/main/java/org/gnucash/android/app/GnuCashApplication.java b/app/src/main/java/org/gnucash/android/app/GnuCashApplication.java
index 859e87b0b..b8327546b 100644
--- a/app/src/main/java/org/gnucash/android/app/GnuCashApplication.java
+++ b/app/src/main/java/org/gnucash/android/app/GnuCashApplication.java
@@ -126,7 +126,7 @@ public void onCreate(){
BookDbHelper bookDbHelper = new BookDbHelper(getApplicationContext());
mBooksDbAdapter = new BooksDbAdapter(bookDbHelper.getWritableDatabase());
- initDatabaseAdapters();
+ initializeDatabaseAdapters();
setDefaultCurrencyCode(getDefaultCurrencyCode());
if (BuildConfig.DEBUG && !isRoboUnitTest())
@@ -137,7 +137,7 @@ public void onCreate(){
* Initialize database adapter singletons for use in the application
* This method should be called every time a new book is opened
*/
- private static void initDatabaseAdapters() {
+ private static void initializeDatabaseAdapters() {
if (mDbHelper != null){ //close if open
mDbHelper.getReadableDatabase().close();
}
@@ -205,15 +205,23 @@ public static BooksDbAdapter getBooksDbAdapter(){
}
/**
- * Loads the book with GUID {@code bookUID}
+ * Loads the book with GUID {@code bookUID} and opens the AccountsActivity
* @param bookUID GUID of the book to be loaded
*/
public static void loadBook(@NonNull String bookUID){
- mBooksDbAdapter.setActive(bookUID);
- initDatabaseAdapters();
+ activateBook(bookUID);
AccountsActivity.start(getAppContext());
}
+ /**
+ * Activates the book with unique identifer {@code bookUID}, and refreshes the database adapters
+ * @param bookUID GUID of the book to be activated
+ */
+ public static void activateBook(@NonNull String bookUID){
+ mBooksDbAdapter.setActive(bookUID);
+ initializeDatabaseAdapters();
+ }
+
/**
* Returns the currently active database in the application
* @return Currently active {@link SQLiteDatabase}
diff --git a/app/src/main/java/org/gnucash/android/db/BookDbHelper.java b/app/src/main/java/org/gnucash/android/db/BookDbHelper.java
index 87adc17d2..6fa1a6ebb 100644
--- a/app/src/main/java/org/gnucash/android/db/BookDbHelper.java
+++ b/app/src/main/java/org/gnucash/android/db/BookDbHelper.java
@@ -124,6 +124,16 @@ public void onCreate(SQLiteDatabase db) {
}
+ /**
+ * Returns the database for the book
+ * @param bookUID GUID of the book
+ * @return SQLiteDatabase of the book
+ */
+ public static SQLiteDatabase getDatabase(String bookUID){
+ DatabaseHelper dbHelper = new DatabaseHelper(GnuCashApplication.getAppContext(), bookUID);
+ return dbHelper.getWritableDatabase();
+ }
+
/**
* Inserts the book into the database
* @param db Book database
diff --git a/app/src/main/java/org/gnucash/android/db/adapter/BooksDbAdapter.java b/app/src/main/java/org/gnucash/android/db/adapter/BooksDbAdapter.java
index 65e06c950..abbce8027 100644
--- a/app/src/main/java/org/gnucash/android/db/adapter/BooksDbAdapter.java
+++ b/app/src/main/java/org/gnucash/android/db/adapter/BooksDbAdapter.java
@@ -31,6 +31,8 @@
import org.gnucash.android.ui.settings.PreferenceActivity;
import org.gnucash.android.util.TimestampHelper;
+import java.util.List;
+
/**
* Database adapter for creating/modifying book entries
*/
diff --git a/app/src/main/java/org/gnucash/android/receivers/TransactionAppWidgetProvider.java b/app/src/main/java/org/gnucash/android/receivers/TransactionAppWidgetProvider.java
index 053c2a0f2..2f6e4d81c 100644
--- a/app/src/main/java/org/gnucash/android/receivers/TransactionAppWidgetProvider.java
+++ b/app/src/main/java/org/gnucash/android/receivers/TransactionAppWidgetProvider.java
@@ -21,8 +21,11 @@
import android.content.SharedPreferences.Editor;
import android.preference.PreferenceManager;
+import org.gnucash.android.db.adapter.BooksDbAdapter;
+import org.gnucash.android.model.Book;
import org.gnucash.android.ui.common.UxArgument;
import org.gnucash.android.ui.homescreen.WidgetConfigurationActivity;
+import org.gnucash.android.ui.settings.PreferenceActivity;
/**
* {@link AppWidgetProvider} which is responsible for managing widgets on the homescreen
@@ -43,13 +46,13 @@ public void onUpdate(Context context, AppWidgetManager appWidgetManager,
for (int i=0; i parent, View view, int position, long id) {
+ Book book = BooksDbAdapter.getInstance().getRecord(id);
+ SQLiteDatabase db = new DatabaseHelper(WidgetConfigurationActivity.this, book.getUID()).getWritableDatabase();
+ mAccountsDbAdapter = new AccountsDbAdapter(db);
+
+ Cursor cursor = mAccountsDbAdapter.fetchAllRecordsOrderedByFullName();
+ mAccountsCursorAdapter.swapCursor(cursor);
+ mAccountsCursorAdapter.notifyDataSetChanged();
+ }
+
+ @Override
+ public void onNothingSelected(AdapterView> parent) {
+ //nothing to see here, move along
+ }
+ });
+
mOkButton.setOnClickListener(new View.OnClickListener() {
@Override
@@ -110,12 +161,17 @@ public void onClick(View v) {
long accountId = mAccountsSpinner.getSelectedItemId();
String accountUID = mAccountsDbAdapter.getUID(accountId);
- SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(WidgetConfigurationActivity.this);
+
+ long bookId = mBooksSpinner.getSelectedItemId();
+ String bookUID = BooksDbAdapter.getInstance().getUID(bookId);
+
+ SharedPreferences prefs = PreferenceActivity.getBookSharedPreferences(bookUID);
+ //PreferenceManager.getDefaultSharedPreferences(WidgetConfigurationActivity.this);
Editor editor = prefs.edit();
editor.putString(UxArgument.SELECTED_ACCOUNT_UID + mAppWidgetId, accountUID);
- editor.commit();
+ editor.apply();
- updateWidget(WidgetConfigurationActivity.this, mAppWidgetId, accountUID);
+ updateWidget(WidgetConfigurationActivity.this, mAppWidgetId, accountUID, bookUID);
Intent resultValue = new Intent();
resultValue.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, mAppWidgetId);
@@ -137,14 +193,16 @@ public void onClick(View v) {
* Updates the widget with id appWidgetId
with information from the
* account with record ID accountId
* If the account has been deleted, then a notice is posted in the widget
- * @param appWidgetId ID of the widget to be updated
+ * @param appWidgetId ID of the widget to be updated
* @param accountUID GUID of the account tied to the widget
+ * @param bookUID GUID of the book with the relevant account
*/
- public static void updateWidget(final Context context, int appWidgetId, String accountUID) {
+ public static void updateWidget(final Context context, int appWidgetId, String accountUID, String bookUID) {
Log.i("WidgetConfiguration", "Updating widget: " + appWidgetId);
AppWidgetManager appWidgetManager = AppWidgetManager.getInstance(context);
- AccountsDbAdapter accountsDbAdapter = AccountsDbAdapter.getInstance();
+ AccountsDbAdapter accountsDbAdapter = new AccountsDbAdapter(BookDbHelper.getDatabase(bookUID));
+
final Account account;
try {
account = accountsDbAdapter.getRecord(accountUID);
@@ -161,9 +219,9 @@ public static void updateWidget(final Context context, int appWidgetId, String a
views.setOnClickPendingIntent(R.id.widget_layout, pendingIntent);
views.setOnClickPendingIntent(R.id.btn_new_transaction, pendingIntent);
appWidgetManager.updateAppWidget(appWidgetId, views);
- Editor editor = PreferenceManager.getDefaultSharedPreferences(context).edit();
+ Editor editor = PreferenceActivity.getActiveBookSharedPreferences().edit(); //PreferenceManager.getDefaultSharedPreferences(context).edit();
editor.remove(UxArgument.SELECTED_ACCOUNT_UID + appWidgetId);
- editor.commit();
+ editor.apply();
return;
}
@@ -183,6 +241,7 @@ public static void updateWidget(final Context context, int appWidgetId, String a
accountViewIntent.setAction(Intent.ACTION_VIEW);
accountViewIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
accountViewIntent.putExtra(UxArgument.SELECTED_ACCOUNT_UID, accountUID);
+ accountViewIntent.putExtra(UxArgument.BOOK_UID, bookUID);
PendingIntent accountPendingIntent = PendingIntent
.getActivity(context, appWidgetId, accountViewIntent, 0);
views.setOnClickPendingIntent(R.id.widget_layout, accountPendingIntent);
@@ -191,6 +250,7 @@ public static void updateWidget(final Context context, int appWidgetId, String a
newTransactionIntent.setAction(Intent.ACTION_INSERT_OR_EDIT);
newTransactionIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
newTransactionIntent.putExtra(UxArgument.FORM_TYPE, FormActivity.FormType.TRANSACTION.name());
+ newTransactionIntent.putExtra(UxArgument.BOOK_UID, bookUID);
newTransactionIntent.putExtra(UxArgument.SELECTED_ACCOUNT_UID, accountUID);
PendingIntent pendingIntent = PendingIntent
.getActivity(context, appWidgetId, newTransactionIntent, 0);
@@ -212,7 +272,8 @@ public static void updateAllWidgets(final Context context){
//update widgets asynchronously so as not to block method which called the update
//inside the computation of the account balance
new Thread(new Runnable() {
- SharedPreferences defaultSharedPrefs = PreferenceManager.getDefaultSharedPreferences(context);
+ SharedPreferences defaultSharedPrefs = PreferenceActivity.getActiveBookSharedPreferences();
+ //PreferenceManager.getDefaultSharedPreferences(context);
@Override
public void run() {
@@ -223,7 +284,7 @@ public void run() {
if (accountUID == null)
continue;
- updateWidget(context, widgetId, accountUID);
+ updateWidget(context, widgetId, accountUID, BooksDbAdapter.getInstance().getActiveBookUID());
}
}
}).start();
diff --git a/app/src/main/res/layout/widget_configuration.xml b/app/src/main/res/layout/widget_configuration.xml
index 063392010..f9ef7c19f 100644
--- a/app/src/main/res/layout/widget_configuration.xml
+++ b/app/src/main/res/layout/widget_configuration.xml
@@ -22,18 +22,33 @@
android:orientation="vertical" >
+
+
+
\ No newline at end of file
From 6048bd8d0604370a38189dad9ba451aa121fc7bb Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?=C3=80lex=20Magaz=20Gra=C3=A7a?=
Date: Thu, 22 Sep 2016 20:33:21 +0200
Subject: [PATCH 2/7] Show the correct last export date in the book manager.
All books appeared with the date from the active book instead.
Fixes https://github.com/codinguser/gnucash-android/issues/580
---
.../android/ui/settings/BookManagerFragment.java | 2 +-
.../gnucash/android/util/PreferencesHelper.java | 16 ++++++++++++++++
2 files changed, 17 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 6d7e391ad..ff7fa0e67 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
@@ -160,7 +160,7 @@ public void bindView(View view, final Context context, Cursor cursor) {
final String bookUID = cursor.getString(cursor.getColumnIndexOrThrow(BookEntry.COLUMN_UID));
TextView lastSyncText = (TextView) view.findViewById(R.id.last_sync_time);
- lastSyncText.setText(PreferencesHelper.getLastExportTime().toString());
+ lastSyncText.setText(PreferencesHelper.getLastExportTime(bookUID).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/java/org/gnucash/android/util/PreferencesHelper.java b/app/src/main/java/org/gnucash/android/util/PreferencesHelper.java
index d58f2e7dc..27be8b7bd 100644
--- a/app/src/main/java/org/gnucash/android/util/PreferencesHelper.java
+++ b/app/src/main/java/org/gnucash/android/util/PreferencesHelper.java
@@ -84,4 +84,20 @@ public static Timestamp getLastExportTime() {
Log.d(LOG_TAG, "Retrieving '" + utcString + "' as lastExportTime from Android Preferences.");
return TimestampHelper.getTimestampFromUtcString(utcString);
}
+
+ /**
+ * Get the time for the last export operation of a specific book.
+ *
+ * @return A {@link Timestamp} with the time.
+ */
+ public static Timestamp getLastExportTime(String bookUID) {
+ final String utcString =
+ GnuCashApplication.getAppContext()
+ .getSharedPreferences(bookUID, Context.MODE_PRIVATE)
+ .getString(PREFERENCE_LAST_EXPORT_TIME_KEY,
+ TimestampHelper.getUtcStringFromTimestamp(
+ TimestampHelper.getTimestampFromEpochZero()));
+ Log.d(LOG_TAG, "Retrieving '" + utcString + "' as lastExportTime from Android Preferences.");
+ return TimestampHelper.getTimestampFromUtcString(utcString);
+ }
}
\ No newline at end of file
From a6aa211734accf94664da91316cf6e26bed0de92 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?=C3=80lex=20Magaz=20Gra=C3=A7a?=
Date: Sat, 1 Oct 2016 17:55:01 +0200
Subject: [PATCH 3/7] Fix issues with scheduled exports running too much or not
at all.
In one issue, the scheduled exports were being run with every service
run, when executions had been missed.
In the other, the scheduled exports wouldn't be run due to bug #583
having incremented too much the execution count, yielding a next
scheduled time too far in the future.
Fixes:
https://github.com/codinguser/gnucash-android/issues/591
https://github.com/codinguser/gnucash-android/issues/594
---
.../android/model/ScheduledAction.java | 55 ++++++++++++++-----
.../service/ScheduledActionService.java | 28 ++++++----
.../test/unit/model/ScheduledActionTest.java | 4 +-
.../service/ScheduledActionServiceTest.java | 29 +++++++++-
4 files changed, 89 insertions(+), 27 deletions(-)
diff --git a/app/src/main/java/org/gnucash/android/model/ScheduledAction.java b/app/src/main/java/org/gnucash/android/model/ScheduledAction.java
index f7279af5a..ff27fc76d 100644
--- a/app/src/main/java/org/gnucash/android/model/ScheduledAction.java
+++ b/app/src/main/java/org/gnucash/android/model/ScheduledAction.java
@@ -173,35 +173,64 @@ public long getTimeOfLastSchedule(){
}
/**
- * Computes the next time that this scheduled action is supposed to be executed
+ * Computes the next time that this scheduled action is supposed to be
+ * executed based on the execution count.
+ *
* This method does not consider the end time, or number of times it should be run.
- * It only considers when the next execution would theoretically be due
+ * It only considers when the next execution would theoretically be due.
+ *
* @return Next run time in milliseconds
*/
- public long computeNextScheduledExecutionTime(){
- int multiplier = mRecurrence.getPeriodType().getMultiplier();
- //this is the last planned time for the action to occur, not the last run time
- long lastActionTime = getTimeOfLastSchedule(); //mStartDate + ((mExecutionCount-1)*getPeriod());
- if (lastActionTime < 0){
+ public long computeNextCountBasedScheduledExecutionTime(){
+ return computeNextScheduledExecutionTimeStartingAt(getTimeOfLastSchedule());
+ }
+
+ /**
+ * Computes the next time that this scheduled action is supposed to be
+ * executed based on the time of the last run.
+ *
+ * This method does not consider the end time, or number of times it should be run.
+ * It only considers when the next execution would theoretically be due.
+ *
+ * @return Next run time in milliseconds
+ */
+ public long computeNextTimeBasedScheduledExecutionTime() {
+ return computeNextScheduledExecutionTimeStartingAt(getLastRunTime());
+ }
+
+ /**
+ * Computes the next time that this scheduled action is supposed to be
+ * executed starting at startTime.
+ *
+ * This method does not consider the end time, or number of times it should be run.
+ * It only considers when the next execution would theoretically be due.
+ *
+ * @param startTime time in milliseconds to use as start to compute the next schedule.
+ *
+ * @return Next run time in milliseconds
+ */
+ private long computeNextScheduledExecutionTimeStartingAt(long startTime) {
+ if (startTime <= 0){ // has never been run
return mStartDate;
}
- LocalDateTime localDate = LocalDateTime.fromDateFields(new Date(lastActionTime));
+ int multiplier = mRecurrence.getPeriodType().getMultiplier();
+ LocalDateTime nextScheduledExecution = LocalDateTime.fromDateFields(new Date(startTime));
switch (mRecurrence.getPeriodType()) {
case DAY:
- localDate = localDate.plusDays(multiplier);
+ nextScheduledExecution = nextScheduledExecution.plusDays(multiplier);
break;
case WEEK:
- localDate = localDate.plusWeeks(multiplier);
+ nextScheduledExecution = nextScheduledExecution.plusWeeks(multiplier);
break;
case MONTH:
- localDate = localDate.plusMonths(multiplier);
+ nextScheduledExecution = nextScheduledExecution.plusMonths(multiplier);
break;
case YEAR:
- localDate = localDate.plusYears(multiplier);
+ nextScheduledExecution = nextScheduledExecution.plusYears(multiplier);
break;
}
- return localDate.toDate().getTime();
+ return nextScheduledExecution.toDate().getTime();
}
/**
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 fb70d62ad..a8ee68b08 100644
--- a/app/src/main/java/org/gnucash/android/service/ScheduledActionService.java
+++ b/app/src/main/java/org/gnucash/android/service/ScheduledActionService.java
@@ -142,9 +142,10 @@ private static void executeScheduledEvent(ScheduledAction scheduledAction, SQLit
//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());
//update the last run time and execution count
ContentValues contentValues = new ContentValues();
- contentValues.put(DatabaseSchema.ScheduledActionEntry.COLUMN_LAST_RUN, System.currentTimeMillis());
+ contentValues.put(DatabaseSchema.ScheduledActionEntry.COLUMN_LAST_RUN, scheduledAction.getLastRunTime());
contentValues.put(DatabaseSchema.ScheduledActionEntry.COLUMN_EXECUTION_COUNT, executionCount);
db.update(DatabaseSchema.ScheduledActionEntry.TABLE_NAME, contentValues,
DatabaseSchema.ScheduledActionEntry.COLUMN_UID + "=?", new String[]{scheduledAction.getUID()});
@@ -162,13 +163,7 @@ private static void executeScheduledEvent(ScheduledAction scheduledAction, SQLit
*/
private static int executeBackup(ScheduledAction scheduledAction, SQLiteDatabase db) {
int executionCount = 0;
- long now = System.currentTimeMillis();
- long endTime = scheduledAction.getEndTime();
-
- if (endTime > 0 && endTime < now)
- return executionCount;
-
- if (scheduledAction.computeNextScheduledExecutionTime() > now)
+ if (!shouldExecuteScheduledBackup(scheduledAction))
return 0;
ExportParams params = ExportParams.parseCsv(scheduledAction.getTag());
@@ -183,6 +178,19 @@ private static int executeBackup(ScheduledAction scheduledAction, SQLiteDatabase
return executionCount;
}
+ private static boolean shouldExecuteScheduledBackup(ScheduledAction scheduledAction) {
+ long now = System.currentTimeMillis();
+ long endTime = scheduledAction.getEndTime();
+
+ if (endTime > 0 && endTime < now)
+ return false;
+
+ if (scheduledAction.computeNextTimeBasedScheduledExecutionTime() > now)
+ return false;
+
+ return true;
+ }
+
/**
* Executes scheduled transactions which are to be added to the database.
* If a schedule was missed, all the intervening transactions will be generated, even if
@@ -214,7 +222,7 @@ private static int executeTransactions(ScheduledAction scheduledAction, SQLiteDa
//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.computeNextScheduledExecutionTime();
+ long transactionTime = scheduledAction.computeNextCountBasedScheduledExecutionTime();
while (transactionTime <= endTime) {
Transaction recurringTrxn = new Transaction(trxnTemplate, true);
recurringTrxn.setTime(transactionTime);
@@ -224,7 +232,7 @@ private static int executeTransactions(ScheduledAction scheduledAction, SQLiteDa
if (totalPlannedExecutions > 0 && executionCount >= totalPlannedExecutions)
break; //if we hit the total planned executions set, then abort
- transactionTime = scheduledAction.computeNextScheduledExecutionTime();
+ transactionTime = scheduledAction.computeNextCountBasedScheduledExecutionTime();
}
transactionsDbAdapter.bulkAddRecords(transactions, DatabaseAdapter.UpdateMethod.insert);
diff --git a/app/src/test/java/org/gnucash/android/test/unit/model/ScheduledActionTest.java b/app/src/test/java/org/gnucash/android/test/unit/model/ScheduledActionTest.java
index c1a0b9275..89b0fe430 100644
--- a/app/src/test/java/org/gnucash/android/test/unit/model/ScheduledActionTest.java
+++ b/app/src/test/java/org/gnucash/android/test/unit/model/ScheduledActionTest.java
@@ -105,11 +105,11 @@ public void testComputingNextScheduledExecution(){
recurrence.setPeriodStart(new Timestamp(startDate.getMillis()));
scheduledAction.setRecurrence(recurrence);
- assertThat(scheduledAction.computeNextScheduledExecutionTime()).isEqualTo(startDate.getMillis());
+ assertThat(scheduledAction.computeNextCountBasedScheduledExecutionTime()).isEqualTo(startDate.getMillis());
scheduledAction.setExecutionCount(3);
DateTime expectedTime = new DateTime(2016, 2, 15, 12, 0);
- assertThat(scheduledAction.computeNextScheduledExecutionTime()).isEqualTo(expectedTime.getMillis());
+ assertThat(scheduledAction.computeNextCountBasedScheduledExecutionTime()).isEqualTo(expectedTime.getMillis());
}
@Test
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 3827398d6..4b8dfda77 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
@@ -280,12 +280,28 @@ public void recurringTransactions_shouldHaveScheduledActionUID(){
assertThat(transactionsDbAdapter.getRecordsCount()).isZero();
}
+ /**
+ * Scheduled backups should run only once.
+ *
+ *
Backups may have been missed since the last run, but still only
+ * one should be done.
+ *
+ * For example, if we have set up a daily backup, the last one
+ * 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(){
ScheduledAction scheduledBackup = new ScheduledAction(ScheduledAction.ActionType.BACKUP);
- scheduledBackup.setStartTime(new DateTime(2016, 2, 17, 17, 0).getMillis());
+ scheduledBackup.setStartTime(LocalDateTime.now()
+ .minusMonths(4).minusDays(2).toDate().getTime());
scheduledBackup.setRecurrence(PeriodType.MONTH, 1);
scheduledBackup.setExecutionCount(2);
+ scheduledBackup.setLastRun(LocalDateTime.now().minusMonths(2).toDate().getTime());
ExportParams backupParams = new ExportParams(ExportFormat.XML);
backupParams.setExportTarget(ExportParams.ExportTarget.SD_CARD);
@@ -297,12 +313,20 @@ public void scheduledBackups_shouldRunOnlyOnce(){
List actions = new ArrayList<>();
actions.add(scheduledBackup);
- ScheduledActionService.processScheduledActions(actions, mDb);
+ // Check there's not a backup for each missed run
+ ScheduledActionService.processScheduledActions(actions, mDb);
assertThat(scheduledBackup.getExecutionCount()).isEqualTo(3);
File[] backupFiles = backupFolder.listFiles();
assertThat(backupFiles).hasSize(1);
assertThat(backupFiles[0]).exists().hasExtension("gnca");
+
+ // Check also across service runs
+ ScheduledActionService.processScheduledActions(actions, mDb);
+ assertThat(scheduledBackup.getExecutionCount()).isEqualTo(3);
+ backupFiles = backupFolder.listFiles();
+ assertThat(backupFiles).hasSize(1);
+ assertThat(backupFiles[0]).exists().hasExtension("gnca");
}
/**
@@ -315,6 +339,7 @@ public void scheduledBackups_shouldRunOnlyOnce(){
public void scheduledBackups_shouldNotRunBeforeNextScheduledExecution(){
ScheduledAction scheduledBackup = new ScheduledAction(ScheduledAction.ActionType.BACKUP);
scheduledBackup.setStartTime(LocalDateTime.now().minusDays(2).toDate().getTime());
+ scheduledBackup.setLastRun(scheduledBackup.getStartTime());
scheduledBackup.setExecutionCount(1);
scheduledBackup.setRecurrence(PeriodType.WEEK, 1);
From b2e9bf7f38a287985656e48ec6b13979a070dcd0 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?=C3=80lex=20Magaz=20Gra=C3=A7a?=
Date: Fri, 7 Oct 2016 20:09:04 +0200
Subject: [PATCH 4/7] Reorder and simplify statements to avoid confusion.
---
.../android/service/ScheduledActionService.java | 15 +++++++--------
1 file changed, 7 insertions(+), 8 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 a8ee68b08..58770873e 100644
--- a/app/src/main/java/org/gnucash/android/service/ScheduledActionService.java
+++ b/app/src/main/java/org/gnucash/android/service/ScheduledActionService.java
@@ -143,15 +143,16 @@ private static void executeScheduledEvent(ScheduledAction scheduledAction, SQLit
// 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, executionCount);
+ 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()});
-
- //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!!
}
/**
@@ -162,7 +163,6 @@ private static void executeScheduledEvent(ScheduledAction scheduledAction, SQLit
* @return Number of times backup is executed. This should either be 1 or 0
*/
private static int executeBackup(ScheduledAction scheduledAction, SQLiteDatabase db) {
- int executionCount = 0;
if (!shouldExecuteScheduledBackup(scheduledAction))
return 0;
@@ -170,12 +170,11 @@ private static int executeBackup(ScheduledAction scheduledAction, SQLiteDatabase
try {
//wait for async task to finish before we proceed (we are holding a wake lock)
new ExportAsyncTask(GnuCashApplication.getAppContext(), db).execute(params).get();
- scheduledAction.setExecutionCount(++executionCount);
} catch (InterruptedException | ExecutionException e) {
Crashlytics.logException(e);
Log.e(LOG_TAG, e.getMessage());
}
- return executionCount;
+ return 1;
}
private static boolean shouldExecuteScheduledBackup(ScheduledAction scheduledAction) {
From 932c38034613f05a7e9dd42f8d58522a57de8d3c Mon Sep 17 00:00:00 2001
From: Carlo Zancanaro
Date: Sat, 15 Oct 2016 11:37:52 +1100
Subject: [PATCH 5/7] Fix first run wizard crashes on screen rotation
If the screen rotates while the first-run wizard is open then GnuCash
will crash with a NullPointerException. To avoid this we need to make
sure we create our WizardModel before it is used to try to restore our
page.
---
.../ui/wizard/FirstRunWizardActivity.java | 30 ++++++++++++++-----
1 file changed, 23 insertions(+), 7 deletions(-)
diff --git a/app/src/main/java/org/gnucash/android/ui/wizard/FirstRunWizardActivity.java b/app/src/main/java/org/gnucash/android/ui/wizard/FirstRunWizardActivity.java
index 7254ef5f9..9284990e3 100644
--- a/app/src/main/java/org/gnucash/android/ui/wizard/FirstRunWizardActivity.java
+++ b/app/src/main/java/org/gnucash/android/ui/wizard/FirstRunWizardActivity.java
@@ -83,19 +83,17 @@ public class FirstRunWizardActivity extends AppCompatActivity implements
public void onCreate(Bundle savedInstanceState) {
+ // we need to construct the wizard model before we call super.onCreate, because it's used in
+ // onGetPage (which is indirectly called through super.onCreate if savedInstanceState is not
+ // null)
+ mWizardModel = createWizardModel(savedInstanceState);
+
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_first_run_wizard);
ButterKnife.bind(this);
setTitle(getString(R.string.title_setup_gnucash));
- mWizardModel = new FirstRunWizardModel(this);
- if (savedInstanceState != null) {
- mWizardModel.load(savedInstanceState.getBundle("model"));
- }
-
- mWizardModel.registerListener(this);
-
mPagerAdapter = new MyPagerAdapter(getSupportFragmentManager());
mPager.setAdapter(mPagerAdapter);
mStepPagerStrip
@@ -197,6 +195,24 @@ public void onClick(View view) {
updateBottomBar();
}
+ /**
+ * Create the wizard model for the activity, taking into accoun the savedInstanceState if it
+ * exists (and if it contains a "model" key that we can use).
+ * @param savedInstanceState the instance state available in {{@link #onCreate(Bundle)}}
+ * @return an appropriate wizard model for this activity
+ */
+ private AbstractWizardModel createWizardModel(Bundle savedInstanceState) {
+ AbstractWizardModel model = new FirstRunWizardModel(this);
+ if (savedInstanceState != null) {
+ Bundle wizardModel = savedInstanceState.getBundle("model");
+ if (wizardModel != null) {
+ model.load(wizardModel);
+ }
+ }
+ model.registerListener(this);
+ return model;
+ }
+
/**
* Create accounts depending on the user preference (import or default set) and finish this activity
* This method also removes the first run flag from the application
From 952cb2b697b9bd946437e19db4597d23b3446f55 Mon Sep 17 00:00:00 2001
From: Carlo Zancanaro
Date: Sun, 9 Oct 2016 23:04:31 +1100
Subject: [PATCH 6/7] Fix widgets with negative values being green
The number used to calculate whether a widget is red or green should be
the same value as the one that is displayed. It was instead calling
a (buggy) method which calculated a different balance to the one
displayed. This patch fixes it to determine the colour using the same
value as the one it is displaying.
---
.../android/ui/homescreen/WidgetConfigurationActivity.java | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/app/src/main/java/org/gnucash/android/ui/homescreen/WidgetConfigurationActivity.java b/app/src/main/java/org/gnucash/android/ui/homescreen/WidgetConfigurationActivity.java
index d02acbca4..76e301587 100644
--- a/app/src/main/java/org/gnucash/android/ui/homescreen/WidgetConfigurationActivity.java
+++ b/app/src/main/java/org/gnucash/android/ui/homescreen/WidgetConfigurationActivity.java
@@ -233,7 +233,7 @@ public static void updateWidget(final Context context, int appWidgetId, String a
views.setTextViewText(R.id.transactions_summary,
accountBalance.formattedString(Locale.getDefault()));
- int color = account.getBalance().isNegative() ? R.color.debit_red : R.color.credit_green;
+ int color = accountBalance.isNegative() ? R.color.debit_red : R.color.credit_green;
views.setTextColor(R.id.transactions_summary, context.getResources().getColor(color));
From 5642de83c88e4c231438a029237f3eb8b7732824 Mon Sep 17 00:00:00 2001
From: Ngewi Fet
Date: Tue, 18 Oct 2016 09:52:00 +0200
Subject: [PATCH 7/7] Update version strings for v2.1.3 release
Update CHANGELOG and CONTRIBUTORS
Update Russian translation
---
CHANGELOG.md | 8 +++
CONTRIBUTORS.md | 6 ++
app/build.gradle | 4 +-
app/src/main/res/values-ru/strings.xml | 81 +++++++++++++-------------
4 files changed, 56 insertions(+), 43 deletions(-)
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 3cf2c62df..d2e9a7e8d 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,5 +1,13 @@
Change Log
===============================================================================
+Version 2.1.3 *(2016-10-20)*
+----------------------------
+* Fixed: Scheduled exports execute too often or not at all in some cases
+* Fixed: Crash if device is rotated during first-run wizard execution
+* Fixed: Negative values displayed as green on homescreen widget
+* Improved: Homescreen widget now allows to select the book to use
+* Improved: Update Russian translation
+
Version 2.1.2 *(2016-09-21)*
----------------------------
* Fixed: Scheduled exports always run daily (no matter the actual schedule)
diff --git a/CONTRIBUTORS.md b/CONTRIBUTORS.md
index 0ce07a1d1..48c117f61 100644
--- a/CONTRIBUTORS.md
+++ b/CONTRIBUTORS.md
@@ -30,5 +30,11 @@ The following (incomplete list of) people (in no particular order) contributed (
* Terry Chung
* Caesar Wirth
* Alceu Rodrigues Neto
+* Carlo Zancanaro
+* Eric Daly
+* Weslly Oliveira
+* Felipe Morato
+* Alceu Rodrigues Neto
+* Salama AB
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 1d5200fad..6b520971a 100644
--- a/app/build.gradle
+++ b/app/build.gradle
@@ -7,8 +7,8 @@ apply plugin: 'io.fabric'
def versionMajor = 2
def versionMinor = 1
-def versionPatch = 2
-def versionBuild = 0
+def versionPatch = 3
+def versionBuild = 1
def buildTime() {
def df = new SimpleDateFormat("yyyyMMdd HH:mm 'UTC'")
diff --git a/app/src/main/res/values-ru/strings.xml b/app/src/main/res/values-ru/strings.xml
index 75f12c45e..057c42715 100644
--- a/app/src/main/res/values-ru/strings.xml
+++ b/app/src/main/res/values-ru/strings.xml
@@ -17,10 +17,10 @@
-->
Создать счёт
- Редактировать счёт
+ Изменить счёт
Информация
- Экспортировать OFX
- Добавить к счёту новую проводку
+ Экспорт…
+ Новая проводка
Нет счетов
Имя счёта
Отмена
@@ -41,24 +41,24 @@
КРЕДИТ
Счета
Проводки
- УДАЛИТЬ
+ Удалить
Удалить
Отмена
Счёт удалён
Подтвердите удаление
Все проводки этого счёта будут удалены
Редактировать проводку
- Замечание
+ Заметка
ПЕРЕНЕСТИ
%1$d выбрано
Баланс:
- Экспортировать в:
+ Экспорт в:
Экспорт проводок
- Экспортировать все
- Экспортировать все проводки, а не только новые.
- Ошибка при экспорте %1$s
+ Экспорт всех проводок
+ По умолчанию экспортируются проводки после последнего экспорта. Отметьте для экспорта всех проводок
+ Ошибка экспорта %1$s
Экспорт
- Удаление транзакций после экспорта
+ Удалить проводки после экспорта
Все экспортированные проводки будут удалены по завершении.
Настройки
@@ -69,20 +69,20 @@
- Отправить…
Перенести
- Перенести %1$d проводк(у,и,ок)
+ Перенести %1$d проводку (и)
Счёт-получатель
Доступ к карте памяти
- Невозможно перенести проводки.\nСчёт-получатель использует другую валюту.
+ Невозможно перенести проводки.\nСчёт-получатель в другой валюте
Общие
О программе
Выберите валюту по умолчанию
Валюта по умолчанию
- Валюта для новых счетов
+ Валюта новых счетов
Запись проводок в Gnucash для Android
Создание счетов в Gnucash для Android
Ваши данные Gnucash
Чтение и модификация данных Gnucash
- Запись транзакций в GnuCash
+ Запись проводок в GnuCash
Создайте учетные записи в GnuCash
Показать счёт
Создать счета
@@ -118,14 +118,14 @@
Вы действительно хотите удалить эту проводку?
Экспорт
Экспорт всех проводок
- Всегда удалять экспортированное
+ Всегда удалять после экспорта
E-mail для экспорта
Почтовый ящик по умолчанию для экспорта. Можете менять его в процессе.
Трансфертный счёт
Все проводки будут переводами с одного счёта на другой.
Вести двойную запись
Баланс
- Введите имя создаваемого счёта
+ Введите имя нового счёта
Валюта
Родительский счёт
Использовать XML-заголовок OFX
@@ -139,15 +139,15 @@
- Улучшена обработка запланированных операций\n
- Множество других исправлений ошибок и улучшений\n
- Отказаться
+ Отмена
Введите сумму, чтобы сохранить проводку
- Не могу редактировать мультивалютные проводки
- Импортировать счета из GnuCash
- Импортировать счета
- Произошла ошибка при импорте счетов из GnuCash
+ Мультивалютные проводки не изменяются
+ Импорт счетов из GnuCash
+ Импорт счетов
+ Ошибка импорта счетов из GnuCash
Счета из GnuCash успешно импортированы
Импорт структуры счетов из GnuCash для ПК
- Импортировать счета из GnuCash
+ Импорт счетов из GnuCash
Удалить все счета из базы. Все проводки тоже удалятся.
Удалить все счета
Счета
@@ -165,14 +165,14 @@
Формат экспорта по умолчанию
Формат файла, используемый по умолчанию при экспорте
Экспортировать проводки…
- Запланированная проводка
+ Периодическая проводка
Дисбаланс
Проводки экспортируются
- Нет запланированных проводок.
- Запланированная проводка успешно удалена
+ Нет периодических проводок.
+ Периодическая проводка удалена
Виртуальный счёт
- Трансфертный счёт по умолчанию
+ Счет-получатель по умолчанию
- %d дочерний счёт
- %d шт. дочерних счетов
@@ -208,30 +208,29 @@
Последние
Избранные
Все
- Создание структуры счетов GnuCash по умолчанию
- Создание счетов по умолчанию
- Новая книга будет открыта с помощью учетной записи по умолчанию\n\nВаши текущие счета и операции не будут изменяться!
+ Создать структуру счетов GnuCash по умолчанию
+ Создать счета по умолчанию
+ Новая книга будет открыта с помощью учетной записи по умолчанию\n\nВаши текущие счета и операции не изменятся!
Запланированные проводки
Добро
- пожаловать в GnuCash для Android!\nВы можете как создать структуру
- часто используемых счетов, так и импортировать её из GnuCash.\n\nОбе
+ пожаловать в GnuCash для Android!\nВы можете создать структуру счетов или импортировать её из GnuCash.\n\nОбе
возможности будут доступны из настроек приложения, если вы захотите
сделать это позже.
Запланированные проводки
Выберите получателя экспорта
- Памятка
+ Заметка
Расход
Приход
Возврат
Депозит
Платёж
- Долг
+ Оплата
Уменьшение
Увеличение
- Входящее
+ Приход
Скидка
- Трата
+ Расход
Счёт
Чек
Покупка
@@ -240,9 +239,9 @@
Нет резервной копии
Начальное сальдо
Собственные средства
- Включите чтобы сохранить текущий баланс (перед удалением проводок) как новое начальное сальдо после их удаления
+ Сохранить текущий баланс (перед удалением проводок) как новое начальное сальдо после их удаления
- Сохранять начальное сальдо счетов
+ Сохранить начальное сальдо счетов
OFX не поддерживает двойную запись
Создаёт на каждую валюту отдельный QIF-файл
Части проводки
@@ -277,7 +276,7 @@
Есть вложенные счета.\nЧто с ними делать?
Удалить проводки
Создайте и укажите трансфертный счёт или отключите двойную запись в настройках
- Тыкните, чтобы создать расписание
+ Создать расписание
Восстановить из резервной копии…
Резервное копирование & экспорт
Включить DropBox
@@ -409,9 +408,9 @@
Бюджеты
Денежный поток
Бюджеты
- Включить компактный вид
- Разрешить всегда использовать компактный вид для списка транзакций
- Недопустимый валютный курс
+ Компактный вид
+ Использовать компактный вид для списка проводок
+ Недопустимый курс валют
например, 1 %1$s = x.xx %2$s
Неверная сумма