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/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/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/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 0 && endTime < now)
- return executionCount;
-
- if (scheduledAction.computeNextScheduledExecutionTime() > now)
+ if (!shouldExecuteScheduledBackup(scheduledAction))
return 0;
ExportParams params = ExportParams.parseCsv(scheduledAction.getTag());
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) {
+ long now = System.currentTimeMillis();
+ long endTime = scheduledAction.getEndTime();
+
+ if (endTime > 0 && endTime < now)
+ return false;
+
+ if (scheduledAction.computeNextTimeBasedScheduledExecutionTime() > now)
+ return false;
+
+ return true;
}
/**
@@ -214,7 +221,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 +231,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/main/java/org/gnucash/android/ui/common/BaseDrawerActivity.java b/app/src/main/java/org/gnucash/android/ui/common/BaseDrawerActivity.java
index eee5966c3..89fa315a0 100644
--- a/app/src/main/java/org/gnucash/android/ui/common/BaseDrawerActivity.java
+++ b/app/src/main/java/org/gnucash/android/ui/common/BaseDrawerActivity.java
@@ -102,6 +102,12 @@ protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(getContentView());
+ //if a parameter was passed to open an account within a specific book, then switch
+ String bookUID = getIntent().getStringExtra(UxArgument.BOOK_UID);
+ if (bookUID != null && !bookUID.equals(BooksDbAdapter.getInstance().getActiveBookUID())){
+ GnuCashApplication.activateBook(bookUID);
+ }
+
ButterKnife.bind(this);
setSupportActionBar(mToolbar);
final ActionBar actionBar = getSupportActionBar();
diff --git a/app/src/main/java/org/gnucash/android/ui/common/FormActivity.java b/app/src/main/java/org/gnucash/android/ui/common/FormActivity.java
index e078ba187..6d777f5f4 100644
--- a/app/src/main/java/org/gnucash/android/ui/common/FormActivity.java
+++ b/app/src/main/java/org/gnucash/android/ui/common/FormActivity.java
@@ -29,6 +29,7 @@
import org.gnucash.android.R;
import org.gnucash.android.app.GnuCashApplication;
import org.gnucash.android.db.adapter.AccountsDbAdapter;
+import org.gnucash.android.db.adapter.BooksDbAdapter;
import org.gnucash.android.ui.account.AccountFormFragment;
import org.gnucash.android.ui.budget.BudgetAmountEditorFragment;
import org.gnucash.android.ui.budget.BudgetFormFragment;
@@ -57,6 +58,12 @@ protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_form);
+ //if a parameter was passed to open an account within a specific book, then switch
+ String bookUID = getIntent().getStringExtra(UxArgument.BOOK_UID);
+ if (bookUID != null && !bookUID.equals(BooksDbAdapter.getInstance().getActiveBookUID())){
+ GnuCashApplication.activateBook(bookUID);
+ }
+
Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
setSupportActionBar(toolbar);
diff --git a/app/src/main/java/org/gnucash/android/ui/common/UxArgument.java b/app/src/main/java/org/gnucash/android/ui/common/UxArgument.java
index b2077c620..9a4a07333 100644
--- a/app/src/main/java/org/gnucash/android/ui/common/UxArgument.java
+++ b/app/src/main/java/org/gnucash/android/ui/common/UxArgument.java
@@ -102,6 +102,11 @@ public final class UxArgument {
*/
public static final String BUDGET_AMOUNT_LIST = "budget_amount_list";
+ /**
+ * GUID of a book which is relevant for a specific action
+ */
+ public static final String BOOK_UID = "book_uid";
+
//prevent initialization of instances of this class
private UxArgument(){
//prevent even the native class from calling the ctor
diff --git a/app/src/main/java/org/gnucash/android/ui/homescreen/WidgetConfigurationActivity.java b/app/src/main/java/org/gnucash/android/ui/homescreen/WidgetConfigurationActivity.java
index eb13294f8..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
@@ -25,29 +25,39 @@
import android.content.SharedPreferences;
import android.content.SharedPreferences.Editor;
import android.database.Cursor;
+import android.database.sqlite.SQLiteDatabase;
import android.os.Bundle;
-import android.preference.PreferenceManager;
import android.support.v4.widget.SimpleCursorAdapter;
import android.util.Log;
import android.view.View;
+import android.widget.AdapterView;
import android.widget.Button;
import android.widget.RemoteViews;
import android.widget.Spinner;
import android.widget.Toast;
import org.gnucash.android.R;
+import org.gnucash.android.db.BookDbHelper;
+import org.gnucash.android.db.DatabaseHelper;
+import org.gnucash.android.db.DatabaseSchema;
import org.gnucash.android.db.adapter.AccountsDbAdapter;
+import org.gnucash.android.db.adapter.BooksDbAdapter;
import org.gnucash.android.model.Account;
+import org.gnucash.android.model.Book;
import org.gnucash.android.model.Money;
import org.gnucash.android.receivers.TransactionAppWidgetProvider;
import org.gnucash.android.ui.account.AccountsActivity;
import org.gnucash.android.ui.common.FormActivity;
import org.gnucash.android.ui.common.UxArgument;
+import org.gnucash.android.ui.settings.PreferenceActivity;
import org.gnucash.android.ui.transaction.TransactionsActivity;
import org.gnucash.android.util.QualifiedAccountNameCursorAdapter;
import java.util.Locale;
+import butterknife.Bind;
+import butterknife.ButterKnife;
+
/**
* Activity for configuration which account to display on a widget.
* The activity is opened each time a widget is added to the homescreen
@@ -57,19 +67,42 @@ public class WidgetConfigurationActivity extends Activity {
private AccountsDbAdapter mAccountsDbAdapter;
private int mAppWidgetId;
- private Spinner mAccountsSpinner;
- private Button mOkButton;
- private Button mCancelButton;
-
+ @Bind(R.id.input_accounts_spinner) Spinner mAccountsSpinner;
+ @Bind(R.id.input_books_spinner) Spinner mBooksSpinner;
+
+ @Bind(R.id.btn_save) Button mOkButton;
+ @Bind(R.id.btn_cancel) Button mCancelButton;
+ private SimpleCursorAdapter mAccountsCursorAdapter;
+
+
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.widget_configuration);
setResult(RESULT_CANCELED);
-
- mAccountsSpinner = (Spinner) findViewById(R.id.input_accounts_spinner);
- mOkButton = (Button) findViewById(R.id.btn_save);
- mCancelButton = (Button) findViewById(R.id.btn_cancel);
+
+ ButterKnife.bind(this);
+
+ BooksDbAdapter booksDbAdapter = BooksDbAdapter.getInstance();
+ Cursor booksCursor = booksDbAdapter.fetchAllRecords();
+ String currentBookUID = booksDbAdapter.getActiveBookUID();
+
+ //determine the position of the currently active book in the cursor
+ int position = 0;
+ while (booksCursor.moveToNext()){
+ String bookUID = booksCursor.getString(booksCursor.getColumnIndexOrThrow(DatabaseSchema.BookEntry.COLUMN_UID));
+ if (bookUID.equals(currentBookUID))
+ break;
+ ++position;
+ }
+
+ SimpleCursorAdapter booksCursorAdapter = new SimpleCursorAdapter(this,
+ android.R.layout.simple_spinner_item, booksCursor,
+ new String[]{DatabaseSchema.BookEntry.COLUMN_DISPLAY_NAME},
+ new int[]{android.R.id.text1}, 0);
+ booksCursorAdapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
+ mBooksSpinner.setAdapter(booksCursorAdapter);
+ mBooksSpinner.setSelection(position);
mAccountsDbAdapter = AccountsDbAdapter.getInstance();
Cursor cursor = mAccountsDbAdapter.fetchAllRecordsOrderedByFullName();
@@ -79,10 +112,10 @@ public void onCreate(Bundle savedInstanceState) {
finish();
}
- SimpleCursorAdapter cursorAdapter = new QualifiedAccountNameCursorAdapter(this, cursor);
+ mAccountsCursorAdapter = new QualifiedAccountNameCursorAdapter(this, cursor);
//without this line, the app crashes when a user tries to select an account
- cursorAdapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
- mAccountsSpinner.setAdapter(cursorAdapter);
+ mAccountsCursorAdapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
+ mAccountsSpinner.setAdapter(mAccountsCursorAdapter);
bindListeners();
}
@@ -91,6 +124,24 @@ public void onCreate(Bundle savedInstanceState) {
* Sets click listeners for the buttons in the dialog
*/
private void bindListeners() {
+ mBooksSpinner.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
+ @Override
+ public void onItemSelected(AdapterView> 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;
}
@@ -175,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));
@@ -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/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/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
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
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
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
Неверная сумма
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);