diff --git a/CHANGELOG.md b/CHANGELOG.md
index 5c6e8367a..df104618d 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,5 +1,30 @@
Change Log
===============================================================================
+Version 1.5.0 *(2014-10-01)*
+----------------------------
+* Need for speed! Lots of performance optimizations in the application
+ - Application balances are now computed faster
+ - App loads faster and is more responsive
+ - Faster recording of opening balances before delete operations
+ - Import and export operations rewritten to perform faster and use less resources
+* Fixed: Crash after saving opening balances and trying to create new transactions
+* Fixed: Parent account title color sometimes not propagated to child accounts
+* Fixed: Recurring transactions scheduled but not saved to database during import
+* Fixed: Crash caused by null exception message during import
+* Fixed: Poor word-wrap of transaction type labels
+* Fixed: Amount values not always displaying the correct sign
+* Feature: Select default currency upon first run of application
+* Feature: Creating account hierarchy uses the user currency preference
+* Feature: Support for reading and writing compressed GnuCash XML files.
+* Feature: Set a passcode lock to restrict access to the application
+* Feature: Export a QIF file for transactions of each currency in use
+* Improved: Increased stability of import/export operations
+* Improved: Exclude multi-currency transactions from QIF exports
+* Improved: Display warnings/limitations of different export formats in the export dialog
+* Improved: Preserve split memos in QIF export (as much as possible)
+* Improved: Child accounts now assigned to account parent upon deletion of account
+* Improved: Descendant accounts cannot be selected as a parent account (no cyclic dependencies)
+
Version 1.4.3 *(2014-09-09)*
----------------------------
* Fixed: Cannot edit transactions when in single-entry mode
diff --git a/CONTRIBUTORS b/CONTRIBUTORS
index a33fd3cc2..ac8d111d0 100644
--- a/CONTRIBUTORS
+++ b/CONTRIBUTORS
@@ -3,7 +3,7 @@ several different people.
Appreciation goes to Muslim Chochlov and the to whole GnuCash community for guiding the
project through the early phases and providing valuable feedback.
-The following people contributed translations to GnuCash for Android:
+The following people contributed (code and translations) to GnuCash for Android:
Christian Stimming
Cristian Marchi
Menelaos Maglis
@@ -16,4 +16,6 @@ Nicolas Barranger
Sigurd Gartmann
Pedro Abel
windwarrior
-Lei Xiao Bao
\ No newline at end of file
+Oleksandr Tyshkovets
+Lei Xiao Bao
+Yongxin Wang
\ No newline at end of file
diff --git a/app/AndroidManifest.xml b/app/AndroidManifest.xml
index 3bf8f69d6..9985b6dad 100644
--- a/app/AndroidManifest.xml
+++ b/app/AndroidManifest.xml
@@ -17,7 +17,7 @@
@@ -55,7 +55,13 @@
+
+
+
diff --git a/app/pom.xml b/app/pom.xml
index 7f873c846..534877eab 100644
--- a/app/pom.xml
+++ b/app/pom.xml
@@ -22,7 +22,7 @@
Gnucash Android companion application
- 1.4.3-SNAPSHOT
+ 1.5.0-SNAPSHOTorg.gnucash.androidgnucash-android-parent
diff --git a/app/res/drawable-mdpi/clear_btn.png b/app/res/drawable-mdpi/clear_btn.png
new file mode 100644
index 000000000..bc3b98507
Binary files /dev/null and b/app/res/drawable-mdpi/clear_btn.png differ
diff --git a/app/res/layout/dialog_export_ofx.xml b/app/res/layout/dialog_export.xml
similarity index 92%
rename from app/res/layout/dialog_export_ofx.xml
rename to app/res/layout/dialog_export.xml
index e032abaa7..9ea5bdadb 100644
--- a/app/res/layout/dialog_export_ofx.xml
+++ b/app/res/layout/dialog_export.xml
@@ -69,6 +69,12 @@
android:layout_height="wrap_content"
android:text="OFX"/>
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/res/layout/item_split_entry.xml b/app/res/layout/item_split_entry.xml
index 363563de0..da9921415 100644
--- a/app/res/layout/item_split_entry.xml
+++ b/app/res/layout/item_split_entry.xml
@@ -47,7 +47,7 @@ limitations under the License.
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/res/values-de/strings.xml b/app/res/values-de/strings.xml
index 7d7519ace..8bb64a179 100644
--- a/app/res/values-de/strings.xml
+++ b/app/res/values-de/strings.xml
@@ -303,13 +303,11 @@
Diese Option aktivieren, wenn Sie die OFX-Dateien für ein anderes Programm als GnuCash auf dem Desktop exportierenNeuigkeiten in dieser Version
- - Create multiple splits per transaction\n
- - Fixed computation of account balances for different account types\n
- - Use account-specific labels for CREDIT/DEBITs\n
- - Automated backup of database before deletion operations\n
- - Restore most recent backup of database (in Settings -> General)\n
- - Read transactions from GnuCash XML files (limited support)\n
- - Option to save opening balances before deleting transactions\n
+ - General resource and performance optimization\n
+ - Faster computation of account balances\n
+ - Set passcode to restrict access to app (in Settings)\n
+ - Export separate QIF files for each currency in use\n
+ - Use currencey settings when creating account hierarchy\n
- Multiple bug fixes and improvements\n
Schließen
@@ -416,4 +414,6 @@
Möglichkeit aktivieren, den aktuellen Saldo als neuen Anfangsbestand nach dem Löschen der Buchungen zu übernehmen
Saldo als neuen Anfangsbestand übernehmen
+ OFX does not support double-entry transactions
+ A separate QIF file will be generated for each currency in use
diff --git a/app/res/values-el/strings.xml b/app/res/values-el/strings.xml
index fd98866de..001b3eb86 100644
--- a/app/res/values-el/strings.xml
+++ b/app/res/values-el/strings.xml
@@ -309,13 +309,11 @@
Ενεργοποίηση αυτής της επιλογής για εξαγωγή σε εφαρμογές τρίτων, εκτός του GnuCash για επιτραπέζιο υπολογιστή.Τι νέο υπάρχει
- - Create multiple splits per transaction\n
- - Fixed computation of account balances for different account types\n
- - Use account-specific labels for CREDIT/DEBITs\n
- - Automated backup of database before deletion operations\n
- - Restore most recent backup of database (in Settings -> General)\n
- - Read transactions from GnuCash XML files (limited support)\n
- - Option to save opening balances before deleting transactions\n
+ - General resource and performance optimization\n
+ - Faster computation of account balances\n
+ - Set passcode to restrict access to app (in Settings)\n
+ - Export separate QIF files for each currency in use\n
+ - Use currencey settings when creating account hierarchy\n
- Multiple bug fixes and improvements\n
Απόρριψη
@@ -434,4 +432,6 @@
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
+ A separate QIF file will be generated for each currency in use
diff --git a/app/res/values-es-rMX/strings.xml b/app/res/values-es-rMX/strings.xml
index 26833c097..478058cda 100644
--- a/app/res/values-es-rMX/strings.xml
+++ b/app/res/values-es-rMX/strings.xml
@@ -303,13 +303,11 @@
Active esta opción para exportar a otras aplicaciones distintas a GnuCash para escritorio¿Qué hay nuevo?
- - Create multiple splits per transaction\n
- - Fixed computation of account balances for different account types\n
- - Use account-specific labels for CREDIT/DEBITs\n
- - Automated backup of database before deletion operations\n
- - Restore most recent backup of database (in Settings -> General)\n
- - Read transactions from GnuCash XML files (limited support)\n
- - Option to save opening balances before deleting transactions\n
+ - General resource and performance optimization\n
+ - Faster computation of account balances\n
+ - Set passcode to restrict access to app (in Settings)\n
+ - Export separate QIF files for each currency in use\n
+ - Use currencey settings when creating account hierarchy\n
- Multiple bug fixes and improvements\n
Cerrar
@@ -419,4 +417,6 @@
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
+ A separate QIF file will be generated for each currency in use
diff --git a/app/res/values-es/strings.xml b/app/res/values-es/strings.xml
index 73ccf00ec..9ff3b5934 100644
--- a/app/res/values-es/strings.xml
+++ b/app/res/values-es/strings.xml
@@ -303,14 +303,12 @@
Active esta opción para exportar a otras aplicaciones distintas a GnuCash para escritorioNovedades
- - Crear múltiples desgloses por transacción\n
- - Arreglar el cálculo de saldos de cuentas para los distintos tipos de cuentas\n
- - Usar etiquetas especificas de cuenta para ABONO/CARGO\n
- - Copia de seguridad automática antes de las operaciones de borrado\n
- - Restaurar la copia de seguridad más reciente (En Ajustes -> General)\n
- - Soporte limitado de lectura de transacciones en archivos GnuCash XML\n
- - Opció de guardar saldos de apertura antes de borrar transacciones\n
- - Correción de errores y mejoras\n
+ - General resource and performance optimization\n
+ - Faster computation of account balances\n
+ - Set passcode to restrict access to app (in Settings)\n
+ - Export separate QIF files for each currency in use\n
+ - Use currencey settings when creating account hierarchy\n
+ - Multiple bug fixes and improvements\n
CerrarIntroduzca un importe para guardar la transacción
@@ -416,4 +414,6 @@
Seleccionar para guardar el saldo actual (antes de borrar las transacciones) como nuevo saldo de apertura despues de borrar las transacciones
Guardar saldos de apertura
+ OFX does not support double-entry transactions
+ A separate QIF file will be generated for each currency in use
diff --git a/app/res/values-fr/strings.xml b/app/res/values-fr/strings.xml
index f5a2d5f8b..afa76eda5 100644
--- a/app/res/values-fr/strings.xml
+++ b/app/res/values-fr/strings.xml
@@ -303,14 +303,12 @@
Activez cette option lors d\'un export vers une application tierce autre que GnuCash pour PCNouveautées
- - Création de multiples découpes par transactions\n
- - Correction du calcul des soldes de comptes pour les comptes de différents types\n
- - Utilisation de labels spécifiques pour les comptes CREDIT/DEBITs\n
- - Sauvegarde automatique des données avant la suppression des opérations\n
- - Restaurer la dernière sauvegarde des données (dans Paramètres -> Général)\n
- - Lecture des transactions depuis les fichiers GnuCash XML (support limité)\n
- - Options pour sauvegarder le solde des comptes avant la suppression des transactions\n
- - Multiples améliorations et corrections de bugs\n
+ - General resource and performance optimization\n
+ - Faster computation of account balances\n
+ - Set passcode to restrict access to app (in Settings)\n
+ - Export separate QIF files for each currency in use\n
+ - Use currencey settings when creating account hierarchy\n
+ - Multiple bug fixes and improvements\n
PasserEntrez un montant pour sauvegarder la transaction
@@ -416,4 +414,6 @@
Permet d\'enregistrer le solde du compte courant (avant la suppression des transactions) comme le nouveau solde d\'ouverture après la suppression des transactions
Enregistrer les soldes des comptes d\'ouverture
+ OFX does not support double-entry transactions
+ A separate QIF file will be generated for each currency in use
diff --git a/app/res/values-hu/strings.xml b/app/res/values-hu/strings.xml
index 2314ab776..34f1c293a 100644
--- a/app/res/values-hu/strings.xml
+++ b/app/res/values-hu/strings.xml
@@ -303,13 +303,11 @@
Enable this option when exporting to third-party application other than GnuCash for desktopWhat\'s New
- - Create multiple splits per transaction\n
- - Fixed computation of account balances for different account types\n
- - Use account-specific labels for CREDIT/DEBITs\n
- - Automated backup of database before deletion operations\n
- - Restore most recent backup of database (in Settings -> General)\n
- - Read transactions from GnuCash XML files (limited support)\n
- - Option to save opening balances before deleting transactions\n
+ - General resource and performance optimization\n
+ - Faster computation of account balances\n
+ - Set passcode to restrict access to app (in Settings)\n
+ - Export separate QIF files for each currency in use\n
+ - Use currencey settings when creating account hierarchy\n
- Multiple bug fixes and improvements\n
Dismiss
@@ -420,4 +418,6 @@
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
+ A separate QIF file will be generated for each currency in use
\ No newline at end of file
diff --git a/app/res/values-it/strings.xml b/app/res/values-it/strings.xml
index b48826565..dd4a4b39a 100644
--- a/app/res/values-it/strings.xml
+++ b/app/res/values-it/strings.xml
@@ -304,13 +304,11 @@
Abilitare questa opzione quando si esporta verso un\'applicazione diversa da GnuCash versione desktopNovità
- - Create multiple splits per transaction\n
- - Fixed computation of account balances for different account types\n
- - Use account-specific labels for CREDIT/DEBITs\n
- - Automated backup of database before deletion operations\n
- - Restore most recent backup of database (in Settings -> General)\n
- - Read transactions from GnuCash XML files (limited support)\n
- - Option to save opening balances before deleting transactions\n
+ - General resource and performance optimization\n
+ - Faster computation of account balances\n
+ - Set passcode to restrict access to app (in Settings)\n
+ - Export separate QIF files for each currency in use\n
+ - Use currencey settings when creating account hierarchy\n
- Multiple bug fixes and improvements\n
Chiudi
@@ -420,4 +418,6 @@
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
+ A separate QIF file will be generated for each currency in use
diff --git a/app/res/values-nb/strings.xml b/app/res/values-nb/strings.xml
index a0e403363..edbb061c9 100644
--- a/app/res/values-nb/strings.xml
+++ b/app/res/values-nb/strings.xml
@@ -304,14 +304,12 @@
Velg denne hvis du skal eksportere til annen programvare enn GnuCash for PC.Hva er nytt
- - Lag flere splitter for en transaksjon\n
- - Rettet kalkulering av balanse for ulike kontoer\n
- - Bruk konto-spesifikke navn for KREDIT/DEBET\n
- - Automatisk sikkerhetskopi av database før sletteoperasjoner\n
- - Tilbakelegging av siste sikkerhetskopi av databasen (i Innstillinger->Generelt)\n
- - Les transaksjoner fra GnuCash XML filer (begrenset støtte)\n
- - Mulighet for å lagre inngående balanser før sletting av transaksjoner\n
- - Mange feilrettinger og forbedringer\n
+ - General resource and performance optimization\n
+ - Faster computation of account balances\n
+ - Set passcode to restrict access to app (in Settings)\n
+ - Export separate QIF files for each currency in use\n
+ - Use currencey settings when creating account hierarchy\n
+ - Multiple bug fixes and improvements\n
FerdigOppgi et beløp for å lagre transaksjonen
@@ -416,4 +414,6 @@
EgenkapitalMerk for å lagre gjeldende konto balanse (før sletting) som ny inngående balanse (etter sletting av transaksjoner).Lagre inngående balanser
+ OFX does not support double-entry transactions
+ A separate QIF file will be generated for each currency in use
diff --git a/app/res/values-nl/strings.xml b/app/res/values-nl/strings.xml
index ddc904d07..701593649 100644
--- a/app/res/values-nl/strings.xml
+++ b/app/res/values-nl/strings.xml
@@ -303,13 +303,11 @@
Schakel deze optie in als u naar een applicatie anders dan GnuCash wil exporterenNieuw sinds de vorige versie
- - Create multiple splits per transaction\n
- - Fixed computation of account balances for different account types\n
- - Use account-specific labels for CREDIT/DEBITs\n
- - Automated backup of database before deletion operations\n
- - Restore most recent backup of database (in Settings -> General)\n
- - Read transactions from GnuCash XML files (limited support)\n
- - Option to save opening balances before deleting transactions\n
+ - General resource and performance optimization\n
+ - Faster computation of account balances\n
+ - Set passcode to restrict access to app (in Settings)\n
+ - Export separate QIF files for each currency in use\n
+ - Use currencey settings when creating account hierarchy\n
- Multiple bug fixes and improvements\n
Wijs af
@@ -421,4 +419,6 @@
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
+ A separate QIF file will be generated for each currency in use
diff --git a/app/res/values-pt-rBR/strings.xml b/app/res/values-pt-rBR/strings.xml
index 89c6a37b2..06e1aef42 100644
--- a/app/res/values-pt-rBR/strings.xml
+++ b/app/res/values-pt-rBR/strings.xml
@@ -302,13 +302,11 @@
Habilitar essa opção quando estiver exportando para um software terceiro, diferente do GnuCash para desktopO que há de novo
- - Create multiple splits per transaction\n
- - Fixed computation of account balances for different account types\n
- - Use account-specific labels for CREDIT/DEBITs\n
- - Automated backup of database before deletion operations\n
- - Restore most recent backup of database (in Settings -> General)\n
- - Read transactions from GnuCash XML files (limited support)\n
- - Option to save opening balances before deleting transactions\n
+ - General resource and performance optimization\n
+ - Faster computation of account balances\n
+ - Set passcode to restrict access to app (in Settings)\n
+ - Export separate QIF files for each currency in use\n
+ - Use currencey settings when creating account hierarchy\n
- Multiple bug fixes and improvements\n
Descartar
@@ -419,4 +417,6 @@
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
+ A separate QIF file will be generated for each currency in use
diff --git a/app/res/values-ru/strings.xml b/app/res/values-ru/strings.xml
index 607837484..876309c1e 100644
--- a/app/res/values-ru/strings.xml
+++ b/app/res/values-ru/strings.xml
@@ -303,14 +303,12 @@
Включите эту опцию, если экспортируете в программы отличные от GnuCash для ПКНовости
- - Создание разделённых проводок\n
- - Исправлено вычисление баланса счёта для счетов различных типов\n
- - Подписи для дебетовых и кредитных проводок обозначаются согласно типу счёта\n
- - Автоматическое резервное копирование базы перед операцией удаления\n
- - Восстановление последней резервной копии базы (Настройки -> Общие)\n
- - Чтение транзакций из XML-файлов GnuCash (неполная функциональность)\n
- - Возможность сохранять начальное сальдо при удалении транзакций\n
- - Множественные исправления ошибок и улучшения\n
+ - General resource and performance optimization\n
+ - Faster computation of account balances\n
+ - Set passcode to restrict access to app (in Settings)\n
+ - Export separate QIF files for each currency in use\n
+ - Use currencey settings when creating account hierarchy\n
+ - Multiple bug fixes and improvements\n
ОтказатьсяВведите сумму, чтобы сохранить проводку
@@ -420,4 +418,6 @@
Включите чтобы сохранить текущий баланс (перед удалением проводок) как новое начальное сальдо после их удаления
Сохранять начальное сальдо счетов
+ OFX does not support double-entry transactions
+ A separate QIF file will be generated for each currency in use
diff --git a/app/res/values-zh/strings.xml b/app/res/values-zh/strings.xml
index fc21bbbdf..23a3a9310 100644
--- a/app/res/values-zh/strings.xml
+++ b/app/res/values-zh/strings.xml
@@ -302,13 +302,11 @@
当导出数据到GnuCash桌面版以外的程序时需要开启这个选项。新功能
- - Create multiple splits per transaction\n
- - Fixed computation of account balances for different account types\n
- - Use account-specific labels for CREDIT/DEBITs\n
- - Automated backup of database before deletion operations\n
- - Restore most recent backup of database (in Settings -> General)\n
- - Read transactions from GnuCash XML files (limited support)\n
- - Option to save opening balances before deleting transactions\n
+ - General resource and performance optimization\n
+ - Faster computation of account balances\n
+ - Set passcode to restrict access to app (in Settings)\n
+ - Export separate QIF files for each currency in use\n
+ - Use currencey settings when creating account hierarchy\n
- Multiple bug fixes and improvements\n
知道了
@@ -390,28 +388,28 @@
计划的交易Select destination for export
- Memo
- Spend
- Receive
- Withdrawal
- Deposit
- Payment
- Charge
- Decrease
- Increase
- Income
- Rebate
- Expense
- Bill
- Invoice
- Buy
- Sell
- Repeats
- Balance:
- No recent backup found
+ 描述
+ 花费
+ 收到
+ 取款
+ 存款
+ 支付
+ 费用(charge)
+ 减少
+ 增加
+ 收入
+ 回扣(Rebate)
+ 费用(Expense)
+ 支付
+ 发票
+ 买入
+ 卖出
+ 重复
+ 还没有备份期初余额所有者权益
- 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
+ A separate QIF file will be generated for each currency in use
diff --git a/app/res/values/key_strings.xml b/app/res/values/key_strings.xml
new file mode 100644
index 000000000..6a716b600
--- /dev/null
+++ b/app/res/values/key_strings.xml
@@ -0,0 +1,232 @@
+
+
+ default_currency
+ key_first_run
+ build_version
+ app_license
+ enable_passcode
+ change_passcode
+ about_gnucash
+ default_transaction_type
+ export_all_transactions
+ delete_transactions_after_export
+ export_email_target
+ use_double_entry
+ xml_ofx_header
+ previous_minor_version
+ import_gnucash_accounts
+ delete_all_accounts
+ delete_all_transactions
+ default_export_format
+ recurring_transaction_ids
+ create_default_accounts
+ restore_backup
+ save_opening_balances
+
+
+ CREDIT
+ DEBIT
+
+
+ CASH
+ BANK
+ CREDIT
+ ASSET
+ LIABILITY
+ INCOME
+ EXPENSE
+ PAYABLE
+ RECEIVABLE
+ EQUITY
+ CURRENCY
+ STOCK
+ MUTUAL
+
+
+ QIF
+ OFX
+
+
+ 0
+ 86400000
+ 604800000
+ 2630000000
+
+
+ AFN
+ DZD
+ ARS
+ AMD
+ AWG
+ AUD
+ AZN
+ BSD
+ BHD
+ THB
+ PAB
+ BBD
+ BYR
+ BZD
+ BMD
+ VEF
+ BOB
+ BRL
+ BND
+ BGN
+ BIF
+ CAD
+ CVE
+ KYD
+ XOF
+ XAF
+ XPF
+ CLP
+ XTS
+ COP
+ KMF
+ CDF
+ BAM
+ NIO
+ CRC
+ HRK
+ CUC
+ CUP
+ CZK
+ GMD
+ DKK
+ MKD
+ DJF
+ STD
+ DOP
+ VND
+ XCD
+ EGP
+ SVC
+ ETB
+ EUR
+ FKP
+ FJD
+ HUF
+ GHS
+ GIP
+ XAU
+ HTG
+ PYG
+ GNF
+ GYD
+ HKD
+ UAH
+ ISK
+ INR
+ IRR
+ IQD
+ JMD
+ JOD
+ KES
+ PGK
+ LAK
+ KWD
+ MWK
+ AOA
+ MMK
+ GEL
+ LVL
+ LBP
+ ALL
+ HNL
+ SLL
+ LRD
+ LYD
+ SZL
+ LTL
+ LSL
+ MGA
+ MYR
+ MUR
+ MXN
+ MXV
+ MDL
+ MAD
+ MZN
+ BOV
+ NGN
+ ERN
+ NAD
+ NPR
+ ANG
+ ILS
+ RON
+ TWD
+ NZD
+ BTN
+ KPW
+ NOK
+ PEN
+ MRO
+ TOP
+ PKR
+ XPD
+ MOP
+ PHP
+ XPT
+ GBP
+ BWP
+ QAR
+ GTQ
+ ZAR
+ OMR
+ KHR
+ MVR
+ IDR
+ RUB
+ RWF
+ SHP
+ SAR
+ XDR
+ RSD
+ SCR
+ XAG
+ SGD
+ SBD
+ KGS
+ SOS
+ TJS
+ SSP
+ LKR
+ XSU
+ SDG
+ SRD
+ SEK
+ CHF
+ SYP
+ BDT
+ WST
+ TZS
+ KZT
+ XXX
+ TTD
+ MNT
+ TND
+ TRY
+ TMT
+ AED
+ UGX
+ XFU
+ COU
+ CLF
+ UYI
+ UYU
+ USD
+ UZS
+ VUV
+ CHE
+ CHW
+ KRW
+ YER
+ JPY
+ CNY
+ ZMK
+ ZWL
+ PLN
+
+
\ No newline at end of file
diff --git a/app/res/values/strings.xml b/app/res/values/strings.xml
index 3318d3170..594b7a419 100644
--- a/app/res/values/strings.xml
+++ b/app/res/values/strings.xml
@@ -17,7 +17,7 @@
GnuCash
- 1.4.3
+ 1.5.0Create AccountEdit AccountInfo
@@ -27,6 +27,11 @@
Account nameCancelSave
+ Enter Passcode
+ Wrong passcode, please try again
+ Passcode set
+ Please confirm your passcode
+ Invalid passcode confirmation. Please try againDescriptionAmountNew transaction
@@ -65,7 +70,6 @@
Move %1$d transaction(s)Destination AccountAccess SD Card
- default_currencyCannot move transactions.\nThe destination account uses a different currency from origin accountGeneralAbout
@@ -81,7 +85,6 @@
Display accountCreate AccountsSelect accounts to create
- key_first_runAfghaniAlgerian Dinar
@@ -259,201 +262,24 @@
Zimbabwe DollarZloty
-
-
- AFN
- DZD
- ARS
- AMD
- AWG
- AUD
- AZN
- BSD
- BHD
- THB
- PAB
- BBD
- BYR
- BZD
- BMD
- VEF
- BOB
- BRL
- BND
- BGN
- BIF
- CAD
- CVE
- KYD
- XOF
- XAF
- XPF
- CLP
- XTS
- COP
- KMF
- CDF
- BAM
- NIO
- CRC
- HRK
- CUC
- CUP
- CZK
- GMD
- DKK
- MKD
- DJF
- STD
- DOP
- VND
- XCD
- EGP
- SVC
- ETB
- EUR
- FKP
- FJD
- HUF
- GHS
- GIP
- XAU
- HTG
- PYG
- GNF
- GYD
- HKD
- UAH
- ISK
- INR
- IRR
- IQD
- JMD
- JOD
- KES
- PGK
- LAK
- KWD
- MWK
- AOA
- MMK
- GEL
- LVL
- LBP
- ALL
- HNL
- SLL
- LRD
- LYD
- SZL
- LTL
- LSL
- MGA
- MYR
- MUR
- MXN
- MXV
- MDL
- MAD
- MZN
- BOV
- NGN
- ERN
- NAD
- NPR
- ANG
- ILS
- RON
- TWD
- NZD
- BTN
- KPW
- NOK
- PEN
- MRO
- TOP
- PKR
- XPD
- MOP
- PHP
- XPT
- GBP
- BWP
- QAR
- GTQ
- ZAR
- OMR
- KHR
- MVR
- IDR
- RUB
- RWF
- SHP
- SAR
- XDR
- RSD
- SCR
- XAG
- SGD
- SBD
- KGS
- SOS
- TJS
- SSP
- LKR
- XSU
- SDG
- SRD
- SEK
- CHF
- SYP
- BDT
- WST
- TZS
- KZT
- XXX
- TTD
- MNT
- TND
- TRY
- TMT
- AED
- UGX
- XFU
- COU
- CLF
- UYI
- UYU
- USD
- UZS
- VUV
- CHE
- CHW
- KRW
- YER
- JPY
- CNY
- ZMK
- ZWL
- PLN
- ExpensesIncome
- Assets
+ AssetsEquityLiabilitiesNo accounts exist in GnuCash.\nCreate an account before adding a widget
- build_versionBuild versionLicenseApache License v2.0. Click for detailsGeneral Preferences
- app_licenseSelect AccountThere are no transactions available to export
- about_gnucash
+ Passcode
+ Passcode Preferences
+ Turn On/Off Passcode
+ Change PasscodeAbout GnuCashGnucash is a mobile finance expense tracker application for Android.\n
It enables flexible tracking of expenses on-the-go which can be exported to multiple formats (OFX, QIF) and imported into GnuCash for the desktop.
@@ -464,29 +290,19 @@
TransactionsTransaction PreferencesAccount Preferences
- default_transaction_typeDefault Transaction TypeThe type of transaction to use by default, CREDIT or DEBITCREDITDEBIT
-
- CREDIT
- DEBIT
- Are you sure you want to delete ALL transactions?Are you sure you want to delete this transaction?Export
- export_all_transactionsExport all transactions
- delete_transactions_after_exportDelete exported transactions
- export_email_targetDefault export emailThe default email address to send exports to. You can still change this when you export.
- use_double_entry
- xml_ofx_headerTransfer AccountAll transactions will be a transfer from one account to anotherActivate Double Entry
@@ -496,16 +312,13 @@
Parent accountUse XML OFX headerEnable this option when exporting to third-party application other than GnuCash for desktop
- previous_minor_versionWhat\'s New
- - Create multiple splits per transaction\n
- - Fixed computation of account balances for different account types\n
- - Use account-specific labels for CREDIT/DEBITs\n
- - Automated backup of database before deletion operations\n
- - Restore most recent backup of database (in Settings -> General)\n
- - Read transactions from GnuCash XML files (limited support)\n
- - Option to save opening balances before deleting transactions\n
+ - General resource and performance optimization\n
+ - Faster computation of account balances\n
+ - Set passcode to restrict access to app (in Settings)\n
+ - Export separate QIF files for each currency in use\n
+ - Use currencey settings when creating account hierarchy\n
- Multiple bug fixes and improvements\n
Dismiss
@@ -514,10 +327,8 @@
Import AccountsAn error occurred while importing the GnuCash accountsGnuCash Accounts successfully imported
- import_gnucash_accountsImport account structure exported from GnuCash desktopImport GnuCash accounts
- delete_all_accountsDelete all accounts in the database. All transactions will be deleted as
well.
@@ -528,7 +339,6 @@
operation cannot be undone!
Account Type
- delete_all_transactionsAll transactions in all accounts will be deleted!Delete all transactionsAll transactions successfully deleted!
@@ -538,14 +348,12 @@
Sub-AccountsSearchDefault Export Format
- default_export_formatFile format to use by default when exporting transactionsExport transactions…RecurrenceImbalanceExporting transactions
- recurring_transaction_idsNo recurring transactions to display.Successfully deleted recurring transactionPlaceholder account
@@ -569,25 +377,6 @@
STOCKMUTUAL FUND
-
- CASH
- BANK
- CREDIT
- ASSET
- LIABILITY
- INCOME
- EXPENSE
- PAYABLE
- RECEIVABLE
- EQUITY
- CURRENCY
- STOCK
- MUTUAL
-
-
- QIF
- OFX
- QIFOFX
@@ -598,12 +387,6 @@
WEEKLYMONTHLY
-
- 0
- 86400000
- 604800000
- 2630000000
- Select a Color
@@ -615,7 +398,6 @@
RecentFavoritesAll
- create_default_accountsCreates default GnuCash commonly-used account structureCreate default accountsNew accounts will be created in addition to the existing
@@ -645,12 +427,12 @@
BuySellRepeats
- restore_backupNo recent backup foundOpening BalancesEquity
- save_opening_balancesEnable 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
+ A separate QIF file will be generated for each currency in use
diff --git a/app/res/xml-v11/fragment_account_preferences.xml b/app/res/xml-v11/fragment_account_preferences.xml
index 7655dda3f..d2f360a99 100644
--- a/app/res/xml-v11/fragment_account_preferences.xml
+++ b/app/res/xml-v11/fragment_account_preferences.xml
@@ -6,7 +6,7 @@
android:dialogTitle="@string/title_choose_currency"
android:title="@string/title_default_currency"
android:entries="@array/currency_names"
- android:entryValues="@array/currency_codes"/>
+ android:entryValues="@array/key_currency_codes"/>
diff --git a/app/res/xml/fragment_account_preferences.xml b/app/res/xml/fragment_account_preferences.xml
index d74b59eb9..33e57f670 100644
--- a/app/res/xml/fragment_account_preferences.xml
+++ b/app/res/xml/fragment_account_preferences.xml
@@ -7,7 +7,7 @@
android:dialogTitle="@string/title_choose_currency"
android:title="@string/title_default_currency"
android:entries="@array/currency_names"
- android:entryValues="@array/currency_codes"
+ android:entryValues="@array/key_currency_codes"
android:defaultValue="USD"/>
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/res/xml/preference_headers.xml b/app/res/xml/preference_headers.xml
index 88cc6c0f6..b85596715 100644
--- a/app/res/xml/preference_headers.xml
+++ b/app/res/xml/preference_headers.xml
@@ -21,6 +21,8 @@
android:title="@string/header_account_settings" />
+
diff --git a/app/src/org/gnucash/android/app/GnuCashApplication.java b/app/src/org/gnucash/android/app/GnuCashApplication.java
index 9a91f2e88..f4add72a5 100644
--- a/app/src/org/gnucash/android/app/GnuCashApplication.java
+++ b/app/src/org/gnucash/android/app/GnuCashApplication.java
@@ -32,6 +32,16 @@
*/
public class GnuCashApplication extends Application{
+ /**
+ * Lifetime of passcode session
+ */
+ public static final long SESSION_TIMEOUT = 5 * 1000;
+
+ /**
+ * Init time of passcode session
+ */
+ public static long PASSCODE_SESSION_INIT_TIME = 0l;
+
private static Context context;
public void onCreate(){
@@ -50,12 +60,11 @@ public static Context getAppContext() {
/**
* Returns true if double entry is enabled in the app settings, false otherwise.
* If the value is not set, the default value can be specified in the parameters.
- * @param defaultValue Default value to return if double entry is not explicitly set
* @return true if double entry is enabled, false otherwise
*/
- public static boolean isDoubleEntryEnabled(boolean defaultValue){
+ public static boolean isDoubleEntryEnabled(){
SharedPreferences sharedPrefs = PreferenceManager.getDefaultSharedPreferences(context);
- return sharedPrefs.getBoolean(context.getString(R.string.key_use_double_entry), defaultValue);
+ return sharedPrefs.getBoolean(context.getString(R.string.key_use_double_entry), false);
}
/**
@@ -91,7 +100,7 @@ public static String getDefaultCurrency(){
try { //there are some strange locales out there
currencyCode = Currency.getInstance(locale).getCurrencyCode();
} catch (Throwable e) {
- Log.e(context.getString(R.string.app_name), e.getMessage());
+ Log.e(context.getString(R.string.app_name), "" + e.getMessage());
} finally {
currencyCode = prefs.getString(context.getString(R.string.key_default_currency), currencyCode);
}
diff --git a/app/src/org/gnucash/android/db/AccountsDbAdapter.java b/app/src/org/gnucash/android/db/AccountsDbAdapter.java
index adc13d79f..33fcf4845 100644
--- a/app/src/org/gnucash/android/db/AccountsDbAdapter.java
+++ b/app/src/org/gnucash/android/db/AccountsDbAdapter.java
@@ -1,5 +1,6 @@
/*
* Copyright (c) 2012 - 2014 Ngewi Fet
+ * Copyright (c) 2014 Yongxin Wang
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -20,7 +21,9 @@
import android.content.Context;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
-import android.database.sqlite.SQLiteQueryBuilder;
+import android.database.sqlite.SQLiteStatement;
+import android.text.TextUtils;
+
import android.util.Log;
import org.gnucash.android.R;
import org.gnucash.android.app.GnuCashApplication;
@@ -35,7 +38,7 @@
* Manages persistence of {@link Account}s in the database
* Handles adding, modifying and deleting of account records.
* @author Ngewi Fet
- *
+ * @author Yongxin Wang
*/
public class AccountsDbAdapter extends DatabaseAdapter {
/**
@@ -86,17 +89,9 @@ public long addAccount(Account account){
contentValues.put(AccountEntry.COLUMN_PARENT_ACCOUNT_UID, account.getParentUID());
contentValues.put(AccountEntry.COLUMN_DEFAULT_TRANSFER_ACCOUNT_UID, account.getDefaultTransferAccountUID());
- long rowId = -1;
- if ((rowId = getAccountID(account.getUID())) > 0){
- //if account already exists, then just update
- Log.d(TAG, "Updating existing account");
- mDb.update(AccountEntry.TABLE_NAME, contentValues,
- AccountEntry._ID + " = " + rowId, null);
- } else {
- Log.d(TAG, "Adding new account to db");
- rowId = mDb.insert(AccountEntry.TABLE_NAME, null, contentValues);
- }
-
+ Log.d(TAG, "Replace account to db");
+ long rowId = mDb.replace(AccountEntry.TABLE_NAME, null, contentValues);
+
//now add transactions if there are any
if (rowId > 0){
//update the fully qualified account name
@@ -108,6 +103,59 @@ public long addAccount(Account account){
return rowId;
}
+ /**
+ * Adds some accounts to the database.
+ * If an account already exists in the database with the same unique ID,
+ * then just update that account. This function will NOT try to determine the full name
+ * of the accounts inserted, full names should be generated prior to the insert.
+ * All or none of the accounts will be inserted;
+ * @param accountList {@link Account} to be inserted to database
+ * @return number of rows inserted
+ */
+ public long bulkAddAccounts(List accountList){
+ long nRow = 0;
+ try {
+ mDb.beginTransaction();
+ SQLiteStatement replaceStatement = mDb.compileStatement("REPLACE INTO " + AccountEntry.TABLE_NAME + " ( "
+ + AccountEntry.COLUMN_UID + " , "
+ + AccountEntry.COLUMN_NAME + " , "
+ + AccountEntry.COLUMN_TYPE + " , "
+ + AccountEntry.COLUMN_CURRENCY + " , "
+ + AccountEntry.COLUMN_COLOR_CODE + " , "
+ + AccountEntry.COLUMN_FAVORITE + " , "
+ + AccountEntry.COLUMN_FULL_NAME + " , "
+ + AccountEntry.COLUMN_PLACEHOLDER + " , "
+ + AccountEntry.COLUMN_PARENT_ACCOUNT_UID + " , "
+ + AccountEntry.COLUMN_DEFAULT_TRANSFER_ACCOUNT_UID + " ) VALUES ( ? , ? , ? , ? , ? , ? , ? , ? , ? , ? )");
+ for (Account account:accountList) {
+ replaceStatement.clearBindings();
+ replaceStatement.bindString(1, account.getUID());
+ replaceStatement.bindString(2, account.getName());
+ replaceStatement.bindString(3, account.getAccountType().name());
+ replaceStatement.bindString(4, account.getCurrency().getCurrencyCode());
+ if (account.getColorHexCode() != null) {
+ replaceStatement.bindString(5, account.getColorHexCode());
+ }
+ replaceStatement.bindLong(6, account.isFavorite() ? 1 : 0);
+ replaceStatement.bindString(7, account.getFullName());
+ replaceStatement.bindLong(8, account.isPlaceholderAccount() ? 1 : 0);
+ if (account.getParentUID() != null) {
+ replaceStatement.bindString(9, account.getParentUID());
+ }
+ if (account.getDefaultTransferAccountUID() != null) {
+ replaceStatement.bindString(10, account.getDefaultTransferAccountUID());
+ }
+ //Log.d(TAG, "Replacing account in db");
+ replaceStatement.execute();
+ nRow ++;
+ }
+ mDb.setTransactionSuccessful();
+ }
+ finally {
+ mDb.endTransaction();
+ }
+ return nRow;
+ }
/**
* Marks all transactions for a given account as exported
* @param accountUID Unique ID of the record to be marked as exported
@@ -116,22 +164,21 @@ public long addAccount(Account account){
public int markAsExported(String accountUID){
ContentValues contentValues = new ContentValues();
contentValues.put(TransactionEntry.COLUMN_EXPORTED, 1);
- Cursor cursor = mTransactionsAdapter.fetchAllTransactionsForAccount(accountUID);
- List transactionIdList = new ArrayList();
- if (cursor != null){
- while(cursor.moveToNext()){
- long id = cursor.getLong(cursor.getColumnIndexOrThrow(TransactionEntry._ID));
- transactionIdList.add(id);
- }
- cursor.close();
- }
- int recordsTouched = 0;
- for (long id : transactionIdList) {
- recordsTouched += mDb.update(TransactionEntry.TABLE_NAME,
- contentValues,
- TransactionEntry._ID + "=" + id, null);
- }
- return recordsTouched;
+ return mDb.update(
+ TransactionEntry.TABLE_NAME,
+ contentValues,
+ TransactionEntry.COLUMN_UID + " IN ( " +
+ "SELECT DISTINCT " + TransactionEntry.TABLE_NAME + "." + TransactionEntry.COLUMN_UID +
+ " FROM " + TransactionEntry.TABLE_NAME + " , " + SplitEntry.TABLE_NAME + " ON " +
+ TransactionEntry.TABLE_NAME + "." + TransactionEntry.COLUMN_UID + " = " +
+ SplitEntry.TABLE_NAME + "." + SplitEntry.COLUMN_TRANSACTION_UID + " , " +
+ AccountEntry.TABLE_NAME + " ON " + SplitEntry.TABLE_NAME + "." +
+ SplitEntry.COLUMN_ACCOUNT_UID + " = " + AccountEntry.TABLE_NAME + "." +
+ AccountEntry.COLUMN_UID + " WHERE " + AccountEntry.TABLE_NAME + "." +
+ AccountEntry.COLUMN_UID + " = ? "
+ + " ) ",
+ new String[] {accountUID}
+ );
}
/**
@@ -163,18 +210,85 @@ public int updateAccount(long accountId, String columnKey, String newValue){
/**
* Deletes an account with database id rowId
* All the transactions in the account will also be deleted
+ * All descendant account will be assigned to the account's parent
* @param rowId Database id of the account record to be deleted
* @return true if deletion was successful, false otherwise.
*/
public boolean destructiveDeleteAccount(long rowId){
+ String accountUID = getAccountUID(rowId);
+ if (getAccountType(accountUID) == AccountType.ROOT) {
+ // refuse to delete ROOT
+ return false;
+ }
Log.d(TAG, "Delete account with rowId and all its associated splits: " + rowId);
-
- //delete splits in this account
- mDb.delete(SplitEntry.TABLE_NAME,
- SplitEntry.COLUMN_ACCOUNT_UID + "=?",
- new String[]{getAccountUID(rowId)});
-
- return deleteRecord(AccountEntry.TABLE_NAME, rowId);
+ List descendantAccountUIDs = getDescendantAccountUIDs(accountUID, null, null);
+
+ mDb.beginTransaction();
+ try {
+ if (descendantAccountUIDs.size() > 0) {
+ List descendantAccounts = getSimpleAccountList(
+ AccountEntry.COLUMN_UID + " IN ('" + TextUtils.join("','", descendantAccountUIDs) + "')",
+ null,
+ null
+ );
+ HashMap mapAccounts = new HashMap();
+ for (Account account : descendantAccounts)
+ mapAccounts.put(account.getUID(), account);
+ String parentAccountFullName;
+ String parentAccountUID = getParentAccountUID(accountUID);
+ if (getAccountType(parentAccountUID) == AccountType.ROOT) {
+ parentAccountFullName = "";
+ } else {
+ parentAccountFullName = getAccountFullName(parentAccountUID);
+ }
+ ContentValues contentValues = new ContentValues();
+ for (String acctUID : descendantAccountUIDs) {
+ Account acct = mapAccounts.get(acctUID);
+ if (acct.getParentUID().equals(accountUID)) {
+ // direct descendant
+ acct.setParentUID(parentAccountUID);
+ if (parentAccountFullName.length() == 0) {
+ acct.setFullName(acct.getName());
+ } else {
+ acct.setFullName(parentAccountFullName + ACCOUNT_NAME_SEPARATOR + acct.getName());
+ }
+ // update DB
+ contentValues.clear();
+ contentValues.put(AccountEntry.COLUMN_PARENT_ACCOUNT_UID, parentAccountUID);
+ contentValues.put(AccountEntry.COLUMN_FULL_NAME, acct.getFullName());
+ mDb.update(
+ AccountEntry.TABLE_NAME, contentValues,
+ AccountEntry.COLUMN_UID + " = ?",
+ new String[]{acct.getUID()}
+ );
+ } else {
+ // indirect descendant
+ acct.setFullName(
+ mapAccounts.get(acct.getParentUID()).getFullName() +
+ ACCOUNT_NAME_SEPARATOR + acct.getName()
+ );
+ // update DB
+ contentValues.clear();
+ contentValues.put(AccountEntry.COLUMN_FULL_NAME, acct.getFullName());
+ mDb.update(
+ AccountEntry.TABLE_NAME, contentValues,
+ AccountEntry.COLUMN_UID + " = ?",
+ new String[]{acct.getUID()}
+ );
+ }
+ }
+ }
+ //delete splits in this account
+ mDb.delete(SplitEntry.TABLE_NAME,
+ SplitEntry.COLUMN_ACCOUNT_UID + "=?",
+ new String[]{getAccountUID(rowId)});
+ deleteRecord(AccountEntry.TABLE_NAME, rowId);
+ mDb.setTransactionSuccessful();
+ return true;
+ }
+ finally {
+ mDb.endTransaction();
+ }
}
/**
@@ -216,22 +330,46 @@ public boolean transactionPreservingDelete(long accountId, long accountReassignI
}
/**
- * Deletes an account and all its sub-accounts and transactions with it
+ * Deletes an account and all its sub-accounts and splits with it
* @param accountId Database record ID of account
* @return true if the account and subaccounts were all successfully deleted, false if
* even one was not deleted
*/
public boolean recursiveDestructiveDelete(long accountId){
Log.d(TAG, "Delete account with rowId with its transactions and sub-accounts: " + accountId);
- boolean result = false;
-
- List subAccountIds = getSubAccountIds(accountId);
- for (long subAccountId : subAccountIds) {
- result |= recursiveDestructiveDelete(subAccountId);
+ String accountUID = getAccountUID(accountId);
+ if (accountUID == null) return false;
+ List descendantAccountUIDs = getDescendantAccountUIDs(accountUID, null, null);
+ mDb.beginTransaction();
+ try {
+ descendantAccountUIDs.add(accountUID);
+ String accountUIDList = "'" + TextUtils.join("','", descendantAccountUIDs) + "'";
+ // delete splits
+ mDb.delete(
+ SplitEntry.TABLE_NAME,
+ SplitEntry.COLUMN_ACCOUNT_UID + " IN (" + accountUIDList + ")",
+ null
+ );
+ // delete transactions that do not have any splits associate them any more
+ mDb.delete(
+ TransactionEntry.TABLE_NAME,
+ "NOT EXISTS ( SELECT * FROM " + SplitEntry.TABLE_NAME +
+ " WHERE " + TransactionEntry.TABLE_NAME + "." + TransactionEntry.COLUMN_UID +
+ " = " + SplitEntry.TABLE_NAME + "." + SplitEntry.COLUMN_TRANSACTION_UID + " ) ",
+ null
+ );
+ // delete accounts
+ mDb.delete(
+ AccountEntry.TABLE_NAME,
+ AccountEntry.COLUMN_UID + " IN (" + accountUIDList + ")",
+ null
+ );
+ mDb.setTransactionSuccessful();
+ return true;
+ }
+ finally {
+ mDb.endTransaction();
}
- result |= destructiveDeleteAccount(accountId);
-
- return result;
}
/**
@@ -331,7 +469,7 @@ public Account getAccount(long rowId){
* @return {@link Account} object for unique ID uid
*/
public Account getAccount(String uid){
- return getAccount(getId(uid));
+ return getAccount(getID(uid));
}
/**
@@ -406,33 +544,69 @@ public List getAllAccounts(){
public List getSimpleAccountList(){
LinkedList accounts = new LinkedList();
Cursor c = fetchAccounts(null);
-
if (c == null)
return accounts;
- while(c.moveToNext()){
- accounts.add(buildSimpleAccountInstance(c));
+ try {
+ while (c.moveToNext()) {
+ accounts.add(buildSimpleAccountInstance(c));
+ }
+ }
+ finally {
+ c.close();
}
- c.close();
return accounts;
}
+ /**
+ * Returns a list of all account entries in the system (includes root account)
+ * No transactions are loaded, just the accounts
+ * @return List of {@link Account}s in the database
+ */
+ public List getSimpleAccountList(String where, String[] whereArgs, String orderBy){
+ LinkedList accounts = new LinkedList();
+ Cursor c = fetchAccounts(where, whereArgs, orderBy);
+ if (c == null)
+ return accounts;
+ try {
+ while (c.moveToNext()) {
+ accounts.add(buildSimpleAccountInstance(c));
+ }
+ }
+ finally {
+ c.close();
+ }
+ return accounts;
+ }
/**
* Returns a list of accounts which have transactions that have not been exported yet
* @return List of {@link Account}s with unexported transactions
*/
public List getExportableAccounts(){
- //TODO: Optimize to use SQL DISTINCT and load only necessary accounts from db
- List accountsList = getAllAccounts();
- Iterator it = accountsList.iterator();
-
- while (it.hasNext()){
- Account account = it.next();
-
- if (!account.hasUnexportedTransactions())
- it.remove();
- }
- return accountsList;
+ LinkedList accountsList = new LinkedList();
+ Cursor cursor = mDb.query(
+ TransactionEntry.TABLE_NAME + " , " + SplitEntry.TABLE_NAME +
+ " ON " + TransactionEntry.TABLE_NAME + "." + TransactionEntry.COLUMN_UID + " = " +
+ SplitEntry.TABLE_NAME + "." + SplitEntry.COLUMN_TRANSACTION_UID + " , " +
+ AccountEntry.TABLE_NAME + " ON " + AccountEntry.TABLE_NAME + "." +
+ AccountEntry.COLUMN_UID + " = " + SplitEntry.TABLE_NAME + "." +
+ SplitEntry.COLUMN_ACCOUNT_UID,
+ new String[]{AccountEntry.TABLE_NAME + ".*"},
+ TransactionEntry.TABLE_NAME + "." + TransactionEntry.COLUMN_EXPORTED + " == 0",
+ null,
+ AccountEntry.TABLE_NAME + "." + AccountEntry.COLUMN_UID,
+ null,
+ null
+ );
+ try {
+ while (cursor.moveToNext()) {
+ accountsList.add(buildAccountInstance(cursor));
+ }
+ }
+ finally {
+ cursor.close();
+ }
+ return accountsList;
}
/**
@@ -466,20 +640,27 @@ public String createAccountHierarchy(String fullName, AccountType accountType){
throw new IllegalArgumentException("The account name cannot be null");
String[] tokens = fullName.trim().split(ACCOUNT_NAME_SEPARATOR);
- String uid = null;
+ String uid = getGnuCashRootAccountUID();
String parentName = "";
+ ArrayList accountsList = new ArrayList();
for (String token : tokens) {
parentName += token;
String parentUID = findAccountUidByFullName(parentName);
- parentName += ACCOUNT_NAME_SEPARATOR;
if (parentUID != null){ //the parent account exists, don't recreate
uid = parentUID;
- continue;
}
- Account account = new Account(token);
- account.setAccountType(accountType);
- account.setParentUID(uid); //set its parent
- uid = account.getUID();
+ else {
+ Account account = new Account(token);
+ account.setAccountType(accountType);
+ account.setParentUID(uid); //set its parent
+ account.setFullName(parentName);
+ accountsList.add(account);
+ uid = account.getUID();
+ }
+ parentName += ACCOUNT_NAME_SEPARATOR;
+ }
+ if (accountsList.size() > 0) {
+ bulkAddAccounts(accountsList);
}
return uid;
}
@@ -578,6 +759,22 @@ public Cursor fetchAccounts(String condition){
AccountEntry.COLUMN_NAME + " ASC");
}
+ /**
+ * Returns a Cursor set of accounts which fulfill condition
+ * and ordered by orderBy
+ * @param where SQL WHERE statement without the 'WHERE' itself
+ * @param whereArgs args to where clause
+ * @param orderBy orderBy clause
+ * @return Cursor set of accounts which fulfill condition
+ */
+ public Cursor fetchAccounts(String where, String[] whereArgs, String orderBy){
+ Log.v(TAG, "Fetching all accounts from db where " +
+ (where == null ? "NONE" : where) + " order by " +
+ (orderBy == null ? "NONE" : orderBy));
+ return mDb.query(AccountEntry.TABLE_NAME,
+ null, where, whereArgs, null, null,
+ orderBy);
+ }
/**
* Returns a Cursor set of accounts which fulfill condition
*
This method returns the accounts list sorted by the full account name
@@ -617,6 +814,72 @@ public Money getAccountBalance(long accountId){
return balance.add(splitSum);
}
+ /**
+ * Returns the balance of an account while taking sub-accounts into consideration
+ * @return Account Balance of an account including sub-accounts
+ */
+ public Money getAccountBalance(String accountUID){
+ Log.d(TAG, "Computing account balance for account ID " + accountUID);
+ String currencyCode = mTransactionsAdapter.getCurrencyCode(accountUID);
+ boolean hasDebitNormalBalance = getAccountType(accountUID).hasDebitNormalBalance();
+ currencyCode = currencyCode == null ? Money.DEFAULT_CURRENCY_CODE : currencyCode;
+ Money balance = Money.createZeroInstance(currencyCode);
+
+ List accountsList = getDescendantAccountUIDs(accountUID,
+ AccountEntry.COLUMN_CURRENCY + " = ? ",
+ new String[]{currencyCode});
+
+ accountsList.add(0, accountUID);
+
+ SplitsDbAdapter splitsDbAdapter = new SplitsDbAdapter(getContext());
+ Log.d(TAG, "all account list : " + accountsList.size());
+ Money splitSum = splitsDbAdapter.computeSplitBalance(accountsList, currencyCode, hasDebitNormalBalance);
+ splitsDbAdapter.close();
+ return balance.add(splitSum);
+ }
+
+ /**
+ * Retrieve all descendant accounts of an account
+ * Note, in filtering, once an account is filtered out, all its descendants
+ * will also be filtered out, even they don't meet the filter condition
+ * @param accountUID The account to retrieve descendant accounts
+ * @param where Condition to filter accounts
+ * @param whereArgs Condition args to filter accounts
+ * @return The descendant accounts list.
+ */
+ public List getDescendantAccountUIDs(String accountUID, String where, String[] whereArgs) {
+ // accountsList will hold accountUID with all descendant accounts.
+ // accountsListLevel will hold descendant accounts of the same level
+ ArrayList accountsList = new ArrayList();
+ ArrayList accountsListLevel = new ArrayList();
+ accountsListLevel.add(accountUID);
+ for (;;) {
+ Cursor cursor = mDb.query(AccountEntry.TABLE_NAME,
+ new String[]{AccountEntry.COLUMN_UID},
+ AccountEntry.COLUMN_PARENT_ACCOUNT_UID + " IN ( '" + TextUtils.join("' , '", accountsListLevel) + "' )" +
+ (where == null ? "" : " AND " + where),
+ whereArgs, null, null, null);
+ accountsListLevel.clear();
+ if (cursor != null) {
+ try {
+ int columnIndex = cursor.getColumnIndexOrThrow(AccountEntry.COLUMN_UID);
+ while (cursor.moveToNext()) {
+ accountsListLevel.add(cursor.getString(columnIndex));
+ }
+ } finally {
+ cursor.close();
+ }
+ }
+ if (accountsListLevel.size() > 0) {
+ accountsList.addAll(accountsListLevel);
+ }
+ else {
+ break;
+ }
+ }
+ return accountsList;
+ }
+
/**
* Returns a list of IDs for the sub-accounts for account accountId
* @param accountId Account ID whose sub-accounts are to be retrieved
@@ -646,12 +909,14 @@ public List getSubAccountIds(long accountId){
/**
* Returns a cursor to the dataset containing sub-accounts of the account with record ID accoundId
- * @param accountId Record ID of the parent account
+ * @param accountUID GUID of the parent account
* @return {@link Cursor} to the sub accounts data set
*/
- public Cursor fetchSubAccounts(long accountId){
- Log.v(TAG, "Fetching sub accounts for account id " + accountId);
- String accountUID = getAccountUID(accountId);
+ public Cursor fetchSubAccounts(String accountUID){
+ if (accountUID == null)
+ throw new IllegalArgumentException("Account UID cannot be null");
+
+ Log.v(TAG, "Fetching sub accounts for account id " + accountUID);
return mDb.query(AccountEntry.TABLE_NAME,
null,
AccountEntry.COLUMN_PARENT_ACCOUNT_UID + " = '" + accountUID + "'",
@@ -680,35 +945,20 @@ public Cursor fetchTopLevelAccounts(){
* @return Cursor to recently used accounts
*/
public Cursor fetchRecentAccounts(int numberOfRecents){
- SQLiteQueryBuilder queryBuilder = new SQLiteQueryBuilder();
- queryBuilder.setTables(TransactionEntry.TABLE_NAME
+ return mDb.query(TransactionEntry.TABLE_NAME
+ " LEFT OUTER JOIN " + SplitEntry.TABLE_NAME + " ON "
+ TransactionEntry.TABLE_NAME + "." + TransactionEntry.COLUMN_UID + " = "
- + SplitEntry.TABLE_NAME + "." + SplitEntry.COLUMN_TRANSACTION_UID);
- queryBuilder.setDistinct(true);
- String sortOrder = TransactionEntry.TABLE_NAME + "." + TransactionEntry.COLUMN_TIMESTAMP + " DESC";
- Map projectionMap = new HashMap();
- projectionMap.put(SplitEntry.COLUMN_ACCOUNT_UID, SplitEntry.TABLE_NAME + "." + SplitEntry.COLUMN_ACCOUNT_UID);
- queryBuilder.setProjectionMap(projectionMap);
- Cursor recentTxCursor = queryBuilder.query(mDb,
- new String[]{SplitEntry.COLUMN_ACCOUNT_UID},
- null, null, null, null, sortOrder, Integer.toString(numberOfRecents));
-
-
- StringBuilder recentAccountUIDs = new StringBuilder("(");
- while (recentTxCursor.moveToNext()){
- String uid = recentTxCursor.getString(recentTxCursor.getColumnIndexOrThrow(SplitEntry.COLUMN_ACCOUNT_UID));
- recentAccountUIDs.append("'" + uid + "'");
- if (!recentTxCursor.isLast())
- recentAccountUIDs.append(",");
- }
- recentAccountUIDs.append(")");
- recentTxCursor.close();
-
- return mDb.query(AccountEntry.TABLE_NAME,
- null, AccountEntry.COLUMN_UID + " IN " + recentAccountUIDs.toString(),
- null, null, null, AccountEntry.COLUMN_NAME + " ASC");
-
+ + SplitEntry.TABLE_NAME + "." + SplitEntry.COLUMN_TRANSACTION_UID
+ + " , " + AccountEntry.TABLE_NAME + " ON " + SplitEntry.TABLE_NAME + "." + SplitEntry.COLUMN_ACCOUNT_UID
+ + " = " + AccountEntry.TABLE_NAME + "." + AccountEntry.COLUMN_UID,
+ new String[]{AccountEntry.TABLE_NAME + ".*"},
+ null,
+ null,
+ SplitEntry.TABLE_NAME + "." + SplitEntry.COLUMN_ACCOUNT_UID, //groupby
+ null, //haveing
+ "MAX ( " + TransactionEntry.TABLE_NAME + "." + TransactionEntry.COLUMN_TIMESTAMP + " ) DESC", // order
+ Integer.toString(numberOfRecents) // limit;
+ );
}
/**
@@ -747,15 +997,14 @@ public String getGnuCashRootAccountUID(){
/**
* Returns the number of accounts for which the account with ID accoundId is a first level parent
- * @param accountId Database ID of parent account
+ * @param accountUID String Unique ID (GUID) of the account
* @return Number of sub accounts
*/
- public int getSubAccountCount(long accountId){
+ public int getSubAccountCount(String accountUID){
//TODO: at some point when API level 11 and above only is supported, use DatabaseUtils.queryNumEntries
String queryCount = "SELECT COUNT(*) FROM " + AccountEntry.TABLE_NAME + " WHERE "
+ AccountEntry.COLUMN_PARENT_ACCOUNT_UID + " = ?";
- String accountUID = getAccountUID(accountId);
if (accountUID == null) //if the account UID is null, then the accountId param was invalid. Just return
return 0;
Cursor cursor = mDb.rawQuery(queryCount, new String[]{accountUID});
@@ -786,7 +1035,8 @@ public int getTotalAccountCount(){
* @param accountUID String Unique ID of the account
* @return Record ID belonging to account UID
*/
- public long getId(String accountUID){
+ @Override
+ public long getID(String accountUID){
long id = -1;
Cursor c = mDb.query(AccountEntry.TABLE_NAME,
new String[]{AccountEntry._ID},
@@ -800,8 +1050,13 @@ public long getId(String accountUID){
}
return id;
}
-
- /**
+
+ @Override
+ public String getUID(long id) {
+ return getAccountUID(id);
+ }
+
+ /**
* Returns currency code of account with database ID id
* @param id Record ID of the account to be removed
* @return Currency code of the account
@@ -885,6 +1140,26 @@ public String getFullyQualifiedAccountName(String accountUID){
return parentAccountName + ACCOUNT_NAME_SEPARATOR + accountName;
}
+ /**
+ * get account's full name directly from DB
+ * @param accountUID the account to retrieve full name
+ * @return full name registered in DB
+ */
+ public String getAccountFullName(String accountUID) {
+ Cursor cursor = mDb.query(AccountEntry.TABLE_NAME, new String[]{AccountEntry.COLUMN_FULL_NAME},
+ AccountEntry.COLUMN_UID + " = ?", new String[]{accountUID},
+ null, null, null);
+ try {
+ if (cursor.moveToFirst()) {
+ return cursor.getString(cursor.getColumnIndexOrThrow(AccountEntry.COLUMN_FULL_NAME));
+ }
+ }
+ finally {
+ cursor.close();
+ }
+ return null;
+ }
+
/**
* Overloaded convenience method.
* Simply resolves the account UID and calls {@link #getFullyQualifiedAccountName(String)}
@@ -962,7 +1237,10 @@ public List getAllOpeningBalanceTransactions(){
long id = cursor.getLong(cursor.getColumnIndexOrThrow(AccountEntry._ID));
String accountUID = getAccountUID(id);
String currencyCode = getCurrencyCode(id);
- Money balance = splitsDbAdapter.computeSplitBalance(accountUID);
+ ArrayList accountList = new ArrayList();
+ accountList.add(accountUID);
+ Money balance = splitsDbAdapter.computeSplitBalance(accountList,
+ currencyCode, getAccountType(accountUID).hasDebitNormalBalance());
if (balance.asBigDecimal().compareTo(new BigDecimal(0)) == 0)
continue;
diff --git a/app/src/org/gnucash/android/db/DatabaseAdapter.java b/app/src/org/gnucash/android/db/DatabaseAdapter.java
index e8eff5ee2..24bf926d0 100644
--- a/app/src/org/gnucash/android/db/DatabaseAdapter.java
+++ b/app/src/org/gnucash/android/db/DatabaseAdapter.java
@@ -23,6 +23,7 @@
import android.database.sqlite.SQLiteDatabase;
import android.util.Log;
import org.gnucash.android.app.GnuCashApplication;
+import org.gnucash.android.db.DatabaseSchema.*;
import org.gnucash.android.model.AccountType;
/**
@@ -58,20 +59,114 @@ public abstract class DatabaseAdapter {
* @param context Application context to be used for opening database
*/
public DatabaseAdapter(Context context) {
- mDbHelper = new DatabaseHelper(context);
- mContext = context.getApplicationContext();
- open();
- }
+ mDbHelper = new DatabaseHelper(context);
+ mContext = context.getApplicationContext();
+ open();
+ createTempView();
+ }
/**
* Opens the database adapter with an existing database
* @param db SQLiteDatabase object
*/
- public DatabaseAdapter(SQLiteDatabase db){
+ public DatabaseAdapter(SQLiteDatabase db) {
this.mDb = db;
this.mContext = GnuCashApplication.getAppContext();
if (!db.isOpen() || db.isReadOnly())
throw new IllegalArgumentException("Database not open or is read-only. Require writeable database");
+ createTempView();
+ }
+
+ private void createTempView() {
+ // Create some temporary views. Temporary views only exists in one DB session, and will not
+ // be saved in the DB
+ //
+ // TODO: Useful views should be add to the DB
+ //
+ // create a temporary view, combining accounts, transactions and splits, as this is often used
+ // in the queries
+ mDb.execSQL("CREATE TEMP VIEW IF NOT EXISTS trans_split_acct AS SELECT "
+ + TransactionEntry.TABLE_NAME + "." + TransactionEntry.COLUMN_UID + " AS "
+ + TransactionEntry.TABLE_NAME + "_" + TransactionEntry.COLUMN_UID + " , "
+ + TransactionEntry.TABLE_NAME + "." + TransactionEntry.COLUMN_DESCRIPTION + " AS "
+ + TransactionEntry.TABLE_NAME + "_" + TransactionEntry.COLUMN_DESCRIPTION + " , "
+ + TransactionEntry.TABLE_NAME + "." + TransactionEntry.COLUMN_NOTES + " AS "
+ + TransactionEntry.TABLE_NAME + "_" + TransactionEntry.COLUMN_NOTES + " , "
+ + TransactionEntry.TABLE_NAME + "." + TransactionEntry.COLUMN_CURRENCY + " AS "
+ + TransactionEntry.TABLE_NAME + "_" + TransactionEntry.COLUMN_CURRENCY + " , "
+ + TransactionEntry.TABLE_NAME + "." + TransactionEntry.COLUMN_TIMESTAMP + " AS "
+ + TransactionEntry.TABLE_NAME + "_" + TransactionEntry.COLUMN_TIMESTAMP + " , "
+ + TransactionEntry.TABLE_NAME + "." + TransactionEntry.COLUMN_EXPORTED + " AS "
+ + TransactionEntry.TABLE_NAME + "_" + TransactionEntry.COLUMN_EXPORTED + " , "
+ + TransactionEntry.TABLE_NAME + "." + TransactionEntry.COLUMN_RECURRENCE_PERIOD + " AS "
+ + TransactionEntry.TABLE_NAME + "_" + TransactionEntry.COLUMN_RECURRENCE_PERIOD + " , "
+ + SplitEntry.TABLE_NAME + "." + SplitEntry.COLUMN_UID + " AS "
+ + SplitEntry.TABLE_NAME + "_" + SplitEntry.COLUMN_UID + " , "
+ + SplitEntry.TABLE_NAME + "." + SplitEntry.COLUMN_TYPE + " AS "
+ + SplitEntry.TABLE_NAME + "_" + SplitEntry.COLUMN_TYPE + " , "
+ + SplitEntry.TABLE_NAME + "." + SplitEntry.COLUMN_AMOUNT + " AS "
+ + SplitEntry.TABLE_NAME + "_" + SplitEntry.COLUMN_AMOUNT + " , "
+ + SplitEntry.TABLE_NAME + "." + SplitEntry.COLUMN_MEMO + " AS "
+ + SplitEntry.TABLE_NAME + "_" + SplitEntry.COLUMN_MEMO + " , "
+ + AccountEntry.TABLE_NAME + "." + AccountEntry.COLUMN_UID + " AS "
+ + AccountEntry.TABLE_NAME + "_" + AccountEntry.COLUMN_UID + " , "
+ + AccountEntry.TABLE_NAME + "." + AccountEntry.COLUMN_NAME + " AS "
+ + AccountEntry.TABLE_NAME + "_" + AccountEntry.COLUMN_NAME + " , "
+ + AccountEntry.TABLE_NAME + "." + AccountEntry.COLUMN_CURRENCY + " AS "
+ + AccountEntry.TABLE_NAME + "_" + AccountEntry.COLUMN_CURRENCY + " , "
+ + AccountEntry.TABLE_NAME + "." + AccountEntry.COLUMN_PARENT_ACCOUNT_UID + " AS "
+ + AccountEntry.TABLE_NAME + "_" + AccountEntry.COLUMN_PARENT_ACCOUNT_UID + " , "
+ + AccountEntry.TABLE_NAME + "." + AccountEntry.COLUMN_PLACEHOLDER + " AS "
+ + AccountEntry.TABLE_NAME + "_" + AccountEntry.COLUMN_PLACEHOLDER + " , "
+ + AccountEntry.TABLE_NAME + "." + AccountEntry.COLUMN_COLOR_CODE + " AS "
+ + AccountEntry.TABLE_NAME + "_" + AccountEntry.COLUMN_COLOR_CODE + " , "
+ + AccountEntry.TABLE_NAME + "." + AccountEntry.COLUMN_FAVORITE + " AS "
+ + AccountEntry.TABLE_NAME + "_" + AccountEntry.COLUMN_FAVORITE + " , "
+ + AccountEntry.TABLE_NAME + "." + AccountEntry.COLUMN_FULL_NAME + " AS "
+ + AccountEntry.TABLE_NAME + "_" + AccountEntry.COLUMN_FULL_NAME + " , "
+ + AccountEntry.TABLE_NAME + "." + AccountEntry.COLUMN_TYPE + " AS "
+ + AccountEntry.TABLE_NAME + "_" + AccountEntry.COLUMN_TYPE + " , "
+ + AccountEntry.TABLE_NAME + "." + AccountEntry.COLUMN_DEFAULT_TRANSFER_ACCOUNT_UID + " AS "
+ + AccountEntry.TABLE_NAME + "_" + AccountEntry.COLUMN_DEFAULT_TRANSFER_ACCOUNT_UID
+ + " FROM " + TransactionEntry.TABLE_NAME + " , " + SplitEntry.TABLE_NAME + " ON "
+ + TransactionEntry.TABLE_NAME + "." + TransactionEntry.COLUMN_UID + "=" + SplitEntry.TABLE_NAME + "." + SplitEntry.COLUMN_TRANSACTION_UID
+ + " , " + AccountEntry.TABLE_NAME + " ON "
+ + SplitEntry.TABLE_NAME + "." + SplitEntry.COLUMN_ACCOUNT_UID + "=" + AccountEntry.TABLE_NAME + "." + AccountEntry.COLUMN_UID
+ );
+
+ // SELECT transactions_uid AS trans_acct_t_uid ,
+ // SUBSTR (
+ // MIN (
+ // ( CASE WHEN IFNULL ( splits_memo , '' ) == '' THEN 'a' ELSE 'b' END ) || accounts_uid
+ // ) ,
+ // 2
+ // ) as trans_acct_a_uid ,
+ // TOTAL ( CASE WHEN splits_type = 'DEBIT' THEN splits_amount ELSE - splits_amount END ) AS trans_acct_balance
+ // FROM trans_split_acct GROUP BY transactions_uid
+ //
+ // This temporary view would pick one Account_UID for each
+ // Transaction, which can be used to order all transactions. If possible, account_uid of a split whose
+ // memo is null is select.
+ //
+ // Transaction balance is also picked out by this view
+ //
+ // a split without split memo is chosen if possible, in the following manner:
+ // if the splits memo is null or empty string, attach an 'a' in front of the split account uid,
+ // if not, attach a 'b' to the split account uid
+ // pick the minimal value of the modified account uid (one of the ones begins with 'a', if exists)
+ // use substr to get account uid
+ mDb.execSQL("CREATE TEMP VIEW IF NOT EXISTS trans_extra_info AS SELECT " + TransactionEntry.TABLE_NAME + "_" + TransactionEntry.COLUMN_UID +
+ " AS trans_acct_t_uid , SUBSTR ( MIN ( ( CASE WHEN IFNULL ( " + SplitEntry.TABLE_NAME + "_" +
+ SplitEntry.COLUMN_MEMO + " , '' ) == '' THEN 'a' ELSE 'b' END ) || " +
+ AccountEntry.TABLE_NAME + "_" + AccountEntry.COLUMN_UID +
+ " ) , 2 ) AS trans_acct_a_uid , TOTAL ( CASE WHEN " + SplitEntry.TABLE_NAME + "_" +
+ SplitEntry.COLUMN_TYPE + " = 'DEBIT' THEN "+ SplitEntry.TABLE_NAME + "_" +
+ SplitEntry.COLUMN_AMOUNT + " ELSE - " + SplitEntry.TABLE_NAME + "_" +
+ SplitEntry.COLUMN_AMOUNT + " END ) AS trans_acct_balance , COUNT ( DISTINCT " +
+ AccountEntry.TABLE_NAME + "_" + AccountEntry.COLUMN_CURRENCY +
+ " ) AS trans_currency_count FROM trans_split_acct " +
+ " GROUP BY " + TransactionEntry.TABLE_NAME + "_" + TransactionEntry.COLUMN_UID
+ );
}
/**
@@ -197,14 +292,15 @@ public String getCurrencyCode(String accountUID) {
if (cursor == null)
return null;
- if (cursor.getCount() <= 0) {
+ String currencyCode = null;
+ try {
+ if (cursor.moveToFirst()) {
+ currencyCode = cursor.getString(0);
+ }
+ }
+ finally {
cursor.close();
- return null;
}
-
- cursor.moveToFirst();
- String currencyCode = cursor.getString(0);
- cursor.close();
return currencyCode;
}
@@ -270,6 +366,20 @@ public long getAccountID(String accountUID){
return id;
}
+ /**
+ * Returns the database record ID of the entry
+ * @param uid GUID of the record
+ * @return Long database identifier of the record
+ */
+ public abstract long getID(String uid);
+
+ /**
+ * Returns the global unique identifier of the record
+ * @param id Database record ID of the entry
+ * @return String GUID of the record
+ */
+ public abstract String getUID(long id);
+
/**
* Updates a record in the table
* @param recordId Database ID of the record to be updated
diff --git a/app/src/org/gnucash/android/db/DatabaseHelper.java b/app/src/org/gnucash/android/db/DatabaseHelper.java
index f44bbdc6c..7a95bd6c0 100644
--- a/app/src/org/gnucash/android/db/DatabaseHelper.java
+++ b/app/src/org/gnucash/android/db/DatabaseHelper.java
@@ -16,7 +16,6 @@
package org.gnucash.android.db;
-import android.app.ProgressDialog;
import android.content.ContentValues;
import android.content.Context;
import android.database.Cursor;
@@ -24,7 +23,6 @@
import android.database.sqlite.SQLiteOpenHelper;
import android.util.Log;
import android.widget.Toast;
-import org.gnucash.android.export.ExportFormat;
import org.gnucash.android.model.AccountType;
import static org.gnucash.android.db.DatabaseSchema.*;
@@ -234,7 +232,7 @@ public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
// }
try {
- String filepath = MigrationHelper.exportDatabase(db, ExportFormat.GNC_XML);
+ String filepath = MigrationHelper.exportGnucashXML(db);
dropAllDatabaseTables(db);
createDatabaseTables(db);
diff --git a/app/src/org/gnucash/android/db/MigrationHelper.java b/app/src/org/gnucash/android/db/MigrationHelper.java
index b71decf11..87a76a3f6 100644
--- a/app/src/org/gnucash/android/db/MigrationHelper.java
+++ b/app/src/org/gnucash/android/db/MigrationHelper.java
@@ -23,30 +23,30 @@
import org.gnucash.android.export.ExportFormat;
import org.gnucash.android.export.ExportParams;
import org.gnucash.android.export.Exporter;
-import org.gnucash.android.export.qif.QifExporter;
import org.gnucash.android.export.xml.GncXmlExporter;
import org.gnucash.android.importer.GncXmlImporter;
import org.gnucash.android.model.AccountType;
+import javax.xml.parsers.ParserConfigurationException;
import java.io.*;
import static org.gnucash.android.db.DatabaseSchema.AccountEntry;
/**
- * Date: 23.03.2014
+ * Collection of helper methods which are used during database migrations
*
- * @author Ngewi
+ * @author Ngewi Fet
*/
public class MigrationHelper {
public static final String LOG_TAG = "MigrationHelper";
/**
- * Performs same functtion as {@link AccountsDbAdapter#getFullyQualifiedAccountName(String)}
+ * Performs same function as {@link AccountsDbAdapter#getFullyQualifiedAccountName(String)}
*
This method is only necessary because we cannot open the database again (by instantiating {@link org.gnucash.android.db.AccountsDbAdapter}
- * while it is locked for upgrades. So we reimplement the method here.
+ * while it is locked for upgrades. So we re-implement the method here.
* @param db SQLite database
* @param accountUID Unique ID of account whose fully qualified name is to be determined
- * @return Fully qualified (colon-sepaated) account name
+ * @return Fully qualified (colon-separated) account name
* @see AccountsDbAdapter#getFullyQualifiedAccountName(String)
*/
static String getFullyQualifiedAccountName(SQLiteDatabase db, String accountUID){
@@ -112,34 +112,31 @@ private static String getGnuCashRootAccountUID(SQLiteDatabase db){
* Exports the database to a GnuCash XML file and returns the path to the file
* @return String with exported GnuCash XML
*/
- static String exportDatabase(SQLiteDatabase db, ExportFormat format) throws IOException {
+ static String exportGnucashXML(SQLiteDatabase db) throws IOException {
Log.i(LOG_TAG, "Exporting database to GnuCash XML");
- ExportParams exportParams = new ExportParams(format);
+ ExportParams exportParams = new ExportParams(ExportFormat.GNC_XML);
exportParams.setExportAllTransactions(true);
exportParams.setExportTarget(ExportParams.ExportTarget.SD_CARD);
exportParams.setDeleteTransactionsAfterExport(false);
new File(Environment.getExternalStorageDirectory() + "/gnucash/").mkdirs();
exportParams.setTargetFilepath(Environment.getExternalStorageDirectory()
- + "/gnucash/" + Exporter.buildExportFilename(format));
+ + "/gnucash/" + Exporter.buildExportFilename(ExportFormat.GNC_XML));
//we do not use the ExporterAsyncTask here because we want to use an already open db
- Exporter exporter = null;
- switch (format){
- case QIF:
- exporter = new QifExporter(exportParams, db);
- break;
- case GNC_XML:
- default:
- exporter = new GncXmlExporter(exportParams, db);
- }
-
+ GncXmlExporter exporter = new GncXmlExporter(exportParams, db);
BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(
new FileOutputStream(exportParams.getTargetFilepath()), "UTF-8"));
- writer.write(exporter.generateExport());
+ try {
+ String xml = exporter.generateXML();
+ writer.write(xml);
+ } catch (ParserConfigurationException e) {
+ e.printStackTrace();
+ } finally {
+ writer.flush();
+ writer.close();
+ }
- writer.flush();
- writer.close();
return exportParams.getTargetFilepath();
}
diff --git a/app/src/org/gnucash/android/db/SplitsDbAdapter.java b/app/src/org/gnucash/android/db/SplitsDbAdapter.java
index bba50dc39..e01a93fbf 100644
--- a/app/src/org/gnucash/android/db/SplitsDbAdapter.java
+++ b/app/src/org/gnucash/android/db/SplitsDbAdapter.java
@@ -1,5 +1,6 @@
/*
* Copyright (c) 2014 Ngewi Fet
+ * Copyright (c) 2014 Yongxin Wang
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -21,21 +22,27 @@
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteQueryBuilder;
+import android.database.sqlite.SQLiteStatement;
+import android.text.TextUtils;
import android.util.Log;
import org.gnucash.android.model.AccountType;
import org.gnucash.android.model.Money;
import org.gnucash.android.model.Split;
import org.gnucash.android.model.TransactionType;
+import java.math.BigDecimal;
import java.util.ArrayList;
+import java.util.Currency;
import java.util.List;
-import static org.gnucash.android.db.DatabaseSchema.*;
+import static org.gnucash.android.db.DatabaseSchema.SplitEntry;
+import static org.gnucash.android.db.DatabaseSchema.TransactionEntry;
/**
* Database adapter for managing transaction splits in the database
*
* @author Ngewi Fet
+ * @author Yongxin Wang
*/
public class SplitsDbAdapter extends DatabaseAdapter {
@@ -64,16 +71,8 @@ public long addSplit(Split split){
contentValues.put(SplitEntry.COLUMN_ACCOUNT_UID, split.getAccountUID());
contentValues.put(SplitEntry.COLUMN_TRANSACTION_UID, split.getTransactionUID());
- long rowId = -1;
- if ((rowId = getID(split.getUID())) > 0){
- //if split already exists, then just update
- Log.d(TAG, "Updating existing transaction split");
- mDb.update(SplitEntry.TABLE_NAME, contentValues,
- SplitEntry._ID + " = " + rowId, null);
- } else {
- Log.d(TAG, "Adding new transaction split to db");
- rowId = mDb.insert(SplitEntry.TABLE_NAME, null, contentValues);
- }
+ Log.d(TAG, "Replace transaction split in db");
+ long rowId = mDb.replace(SplitEntry.TABLE_NAME, null, contentValues);
//when a split is updated, we want mark the transaction as not exported
updateRecord(TransactionEntry.TABLE_NAME, getTransactionID(split.getTransactionUID()),
@@ -81,6 +80,49 @@ public long addSplit(Split split){
return rowId;
}
+ /**
+ * Adds some splits to the database.
+ * If the split already exists, then it is simply updated.
+ * This function will NOT update the exported status of corresponding transactions.
+ * All or none of the splits will be inserted/updated into the database.
+ * @param splitList {@link org.gnucash.android.model.Split} to be recorded in DB
+ * @return Number of records of the newly saved split
+ */
+ public long bulkAddSplits(List splitList) {
+ long nRow = 0;
+ try {
+ mDb.beginTransaction();
+ SQLiteStatement replaceStatement = mDb.compileStatement("REPLACE INTO " + SplitEntry.TABLE_NAME + " ( "
+ + SplitEntry.COLUMN_UID + " , "
+ + SplitEntry.COLUMN_MEMO + " , "
+ + SplitEntry.COLUMN_TYPE + " , "
+ + SplitEntry.COLUMN_AMOUNT + " , "
+ + SplitEntry.COLUMN_ACCOUNT_UID + " , "
+ + SplitEntry.COLUMN_TRANSACTION_UID + " ) VALUES ( ? , ? , ? , ? , ? , ? ) ");
+ for (Split split : splitList) {
+ replaceStatement.clearBindings();
+ replaceStatement.bindString(1, split.getUID());
+ if (split.getMemo() != null) {
+ replaceStatement.bindString(2, split.getMemo());
+ }
+ replaceStatement.bindString(3, split.getType().name());
+ replaceStatement.bindString(4, split.getAmount().absolute().toPlainString());
+ replaceStatement.bindString(5, split.getAccountUID());
+ replaceStatement.bindString(6, split.getTransactionUID());
+
+ //Log.d(TAG, "Replacing transaction split in db");
+ replaceStatement.execute();
+ nRow++;
+ }
+ mDb.setTransactionSuccessful();
+ }
+ finally {
+ mDb.endTransaction();
+ }
+
+ return nRow;
+ }
+
/**
* Builds a split instance from the data pointed to by the cursor provided
*
This method will not move the cursor in any way. So the cursor should already by pointing to the correct entry
@@ -180,6 +222,45 @@ public Money computeSplitBalance(String accountUID){
return splitSum;
}
+ /**
+ * Returns the sum of the splits for given set of accounts.
+ * This takes into account the kind of movement caused by the split in the account (which also depends on account type)
+ * The Caller must make sure all accounts have the currency, which is passed in as currencyCode
+ * @param accountUIDList List of String unique IDs of given set of accounts
+ * @param currencyCode currencyCode for all the accounts in the list
+ * @param hasDebitNormalBalance Does the final balance has normal debit credit meaning
+ * @return Balance of the splits for this account
+ */
+ public Money computeSplitBalance(List accountUIDList, String currencyCode, boolean hasDebitNormalBalance){
+ //Cursor cursor = fetchSplitsForAccount(accountUID);
+ if (accountUIDList == null || accountUIDList.size() == 0){
+ return new Money("0", currencyCode);
+ }
+
+ Cursor cursor;
+ cursor = mDb.query(SplitEntry.TABLE_NAME + " , " + TransactionEntry.TABLE_NAME,
+ new String[]{"TOTAL ( CASE WHEN " + SplitEntry.TABLE_NAME + "." + SplitEntry.COLUMN_TYPE + " = 'DEBIT' THEN "+
+ SplitEntry.TABLE_NAME + "." + SplitEntry.COLUMN_AMOUNT + " ELSE - " + SplitEntry.TABLE_NAME + "." + SplitEntry.COLUMN_AMOUNT + " END )"},
+ SplitEntry.TABLE_NAME + "." + SplitEntry.COLUMN_ACCOUNT_UID + " in ( '" + TextUtils.join("' , '", accountUIDList) + "' ) AND " +
+ SplitEntry.TABLE_NAME + "." + SplitEntry.COLUMN_TRANSACTION_UID + " = " + TransactionEntry.TABLE_NAME + "." + TransactionEntry.COLUMN_UID + " AND " +
+ TransactionEntry.TABLE_NAME + "." + TransactionEntry.COLUMN_RECURRENCE_PERIOD + " = 0",
+ null, null, null, null);
+
+ if (cursor != null){
+ if (cursor.moveToFirst()) {
+ double amount = cursor.getDouble(0);
+ cursor.close();
+ Log.d(TAG, "amount return " + amount);
+ if (!hasDebitNormalBalance) {
+ amount = -amount;
+ }
+ return new Money(BigDecimal.valueOf(amount).setScale(2, BigDecimal.ROUND_HALF_UP), Currency.getInstance(currencyCode));
+ }
+ cursor.close();
+ }
+ return new Money("0", currencyCode);
+ }
+
/**
* Returns the list of splits for a transaction
* @param transactionUID String unique ID of transaction
@@ -242,6 +323,7 @@ public Cursor fetchSplits(String condition, String sortOrder){
* @param uid Unique Identifier String of the split transaction
* @return Database record ID of split
*/
+ @Override
public long getID(String uid){
if (uid == null)
return 0;
@@ -265,6 +347,7 @@ public long getID(String uid){
* @param id Database record ID of the split
* @return String unique identifier of the split
*/
+ @Override
public String getUID(long id){
Cursor cursor = mDb.query(SplitEntry.TABLE_NAME,
new String[]{SplitEntry.COLUMN_UID},
@@ -443,17 +526,15 @@ public boolean deleteSplitsForTransaction(long transactionId){
/**
* Deletes splits for a specific transaction and account and the transaction itself
- * @param transactionId Database record ID of the transaction
- * @param accountId Database ID of the account
+ * @param transactionUID String unique ID of transaction
+ * @param accountUID String unique ID of account
* @return Number of records deleted
*/
- public int deleteSplitsForTransactionAndAccount(long transactionId, long accountId){
- String transactionUID = getTransactionUID(transactionId);
- String accountUID = getAccountUID(accountId);
+ public int deleteSplitsForTransactionAndAccount(String transactionUID, String accountUID){
int deletedCount = mDb.delete(SplitEntry.TABLE_NAME,
SplitEntry.COLUMN_TRANSACTION_UID + "= ? AND " + SplitEntry.COLUMN_ACCOUNT_UID + "= ?",
new String[]{transactionUID, accountUID});
- deleteTransaction(transactionId);
+ deleteTransaction(getID(transactionUID));
return deletedCount;
}
@@ -469,4 +550,5 @@ private boolean deleteTransaction(long transactionId) {
public int deleteAllRecords() {
return deleteAllRecords(SplitEntry.TABLE_NAME);
}
+
}
diff --git a/app/src/org/gnucash/android/db/TransactionsDbAdapter.java b/app/src/org/gnucash/android/db/TransactionsDbAdapter.java
index c532f7161..e27d7269d 100644
--- a/app/src/org/gnucash/android/db/TransactionsDbAdapter.java
+++ b/app/src/org/gnucash/android/db/TransactionsDbAdapter.java
@@ -1,5 +1,6 @@
/*
* Copyright (c) 2012 - 2014 Ngewi Fet
+ * Copyright (c) 2014 Yongxin Wang
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -25,7 +26,9 @@
import android.database.sqlite.SQLiteQueryBuilder;
import android.database.sqlite.SQLiteStatement;
import android.util.Log;
+
import org.gnucash.android.model.*;
+
import static org.gnucash.android.db.DatabaseSchema.*;
import java.util.ArrayList;
@@ -35,7 +38,7 @@
* Manages persistence of {@link Transaction}s in the database
* Handles adding, modifying and deleting of transaction records.
* @author Ngewi Fet
- *
+ * @author Yongxin Wang
*/
public class TransactionsDbAdapter extends DatabaseAdapter {
@@ -74,7 +77,7 @@ public void close() {
*/
public long addTransaction(Transaction transaction){
ContentValues contentValues = new ContentValues();
- contentValues.put(TransactionEntry.COLUMN_DESCRIPTION, transaction.getDescription());
+ contentValues.put(TransactionEntry.COLUMN_DESCRIPTION, transaction.getDescription());
contentValues.put(TransactionEntry.COLUMN_UID, transaction.getUID());
contentValues.put(TransactionEntry.COLUMN_TIMESTAMP, transaction.getTimeMillis());
contentValues.put(TransactionEntry.COLUMN_NOTES, transaction.getNote());
@@ -82,15 +85,8 @@ public long addTransaction(Transaction transaction){
contentValues.put(TransactionEntry.COLUMN_CURRENCY, transaction.getCurrencyCode());
contentValues.put(TransactionEntry.COLUMN_RECURRENCE_PERIOD, transaction.getRecurrencePeriod());
- long rowId = -1;
- if ((rowId = fetchTransactionWithUID(transaction.getUID())) > 0){
- //if transaction already exists, then just update
- Log.d(TAG, "Updating existing transaction");
- mDb.update(TransactionEntry.TABLE_NAME, contentValues, TransactionEntry._ID + " = " + rowId, null);
- } else {
- Log.d(TAG, "Adding new transaction to db");
- rowId = mDb.insert(TransactionEntry.TABLE_NAME, null, contentValues);
- }
+ Log.d(TAG, "Replacing transaction in db");
+ long rowId = mDb.replace(TransactionEntry.TABLE_NAME, null, contentValues);
if (rowId > 0){
Log.d(TAG, "Adding splits for transaction");
@@ -102,6 +98,67 @@ public long addTransaction(Transaction transaction){
return rowId;
}
+ /**
+ * Adds an several transactions to the database.
+ * If a transaction already exists in the database with the same unique ID,
+ * then the record will just be updated instead. Recurrence Transactions will not
+ * be inserted, instead schedule Transaction would be called. If an exception
+ * occurs, no transaction would be inserted.
+ * @param transactionList {@link Transaction} transactions to be inserted to database
+ * @return Number of transactions inserted
+ */
+ public long bulkAddTransactions(List transactionList){
+ List splitList = new ArrayList(transactionList.size()*3);
+ long rowInserted = 0;
+ try {
+ mDb.beginTransaction();
+ SQLiteStatement replaceStatement = mDb.compileStatement("REPLACE INTO " + TransactionEntry.TABLE_NAME + " ( "
+ + TransactionEntry.COLUMN_UID + " , "
+ + TransactionEntry.COLUMN_DESCRIPTION + " , "
+ + TransactionEntry.COLUMN_NOTES + " , "
+ + TransactionEntry.COLUMN_TIMESTAMP + " , "
+ + TransactionEntry.COLUMN_EXPORTED + " , "
+ + TransactionEntry.COLUMN_CURRENCY + " , "
+ + TransactionEntry.COLUMN_RECURRENCE_PERIOD + " ) VALUES ( ? , ? , ? , ? , ? , ? , ?)");
+ for (Transaction transaction : transactionList) {
+ if (transaction.getRecurrencePeriod() > 0) {
+ scheduleTransaction(transaction);
+ }
+ //Log.d(TAG, "Replacing transaction in db");
+ replaceStatement.clearBindings();
+ replaceStatement.bindString(1, transaction.getUID());
+ replaceStatement.bindString(2, transaction.getDescription());
+ replaceStatement.bindString(3, transaction.getNote());
+ replaceStatement.bindLong(4, transaction.getTimeMillis());
+ replaceStatement.bindLong(5, transaction.isExported() ? 1 : 0);
+ replaceStatement.bindString(6, transaction.getCurrencyCode());
+ replaceStatement.bindLong(7, transaction.getRecurrencePeriod());
+ replaceStatement.execute();
+ rowInserted ++;
+ splitList.addAll(transaction.getSplits());
+ }
+ mDb.setTransactionSuccessful();
+ }
+ finally {
+ mDb.endTransaction();
+ }
+ if (rowInserted != 0 && !splitList.isEmpty()) {
+ try {
+ long nSplits = mSplitsDbAdapter.bulkAddSplits(splitList);
+ Log.d(TAG, String.format("%d splits inserted", nSplits));
+ }
+ finally {
+ SQLiteStatement deleteEmptyTransaction = mDb.compileStatement("DELETE FROM " +
+ TransactionEntry.TABLE_NAME + " WHERE NOT EXISTS ( SELECT * FROM " +
+ SplitEntry.TABLE_NAME +
+ " WHERE " + TransactionEntry.TABLE_NAME + "." + TransactionEntry.COLUMN_UID +
+ " = " + SplitEntry.TABLE_NAME + "." + SplitEntry.COLUMN_TRANSACTION_UID + " ) ");
+ deleteEmptyTransaction.execute();
+ }
+ }
+ return rowInserted;
+ }
+
/**
* Fetch a transaction from the database which has a unique ID uid
* @param uid Unique Identifier of transaction to be retrieved
@@ -186,13 +243,11 @@ public Cursor fetchAllTransactionsForAccount(String accountUID){
* @return Cursor holding set of all recurring transactions
*/
public Cursor fetchAllRecurringTransactions(){
- Cursor cursor = mDb.query(TransactionEntry.TABLE_NAME,
+ return mDb.query(TransactionEntry.TABLE_NAME,
null,
TransactionEntry.COLUMN_RECURRENCE_PERIOD + "!= 0",
null, null, null,
AccountEntry.COLUMN_NAME + " ASC, " + TransactionEntry.COLUMN_RECURRENCE_PERIOD + " ASC");
-// DatabaseHelper.COLUMN_RECURRENCE_PERIOD + " ASC, " + DatabaseHelper.COLUMN_TIMESTAMP + " DESC");
- return cursor;
}
/**
@@ -239,6 +294,34 @@ public List getAllTransactions(){
return transactions;
}
+ public Cursor fetchTransactionsWithSplits(String [] columns, String condition, String orderBy) {
+ return mDb.query(TransactionEntry.TABLE_NAME + " , " + SplitEntry.TABLE_NAME +
+ " ON " + TransactionEntry.TABLE_NAME + "." + TransactionEntry.COLUMN_UID +
+ " = " + SplitEntry.TABLE_NAME + "." + SplitEntry.COLUMN_TRANSACTION_UID,
+ columns, condition, null, null, null,
+ orderBy);
+
+ }
+
+ public Cursor fetchTransactionsWithSplitsWithTransactionAccount(String [] columns, String where, String[] whereArgs, String orderBy) {
+ // table is :
+ // trans_split_acct , trans_extra_info ON trans_extra_info.trans_acct_t_uid = transactions_uid ,
+ // accounts AS account1 ON account1.uid = trans_extra_info.trans_acct_a_uid
+ //
+ // views effectively simplified this query
+ //
+ // account1 provides information for the grouped account. Splits from the grouped account
+ // can be eliminated with a WHERE clause. Transactions in QIF can be auto balanced.
+ //
+ // Account, transaction and split Information can be retrieve in a single query.
+ return mDb.query(
+ "trans_split_acct , trans_extra_info ON trans_extra_info.trans_acct_t_uid = trans_split_acct." +
+ TransactionEntry.TABLE_NAME + "_" + TransactionEntry.COLUMN_UID + " , " +
+ AccountEntry.TABLE_NAME + " AS account1 ON account1." + AccountEntry.COLUMN_UID +
+ " = trans_extra_info.trans_acct_a_uid",
+ columns, where, whereArgs, null, null , orderBy);
+ }
+
/**
* Return number of transactions in the database which are non recurring
* @return Number of transactions
@@ -316,13 +399,11 @@ public String getCurrencyCode(long accountId){
/**
* Returns the transaction balance for the transaction for the specified account.
*
We consider only those splits which belong to this account
- * @param transactionId Database record ID of the transaction
- * @param accountId Database record id of the account
+ * @param transactionUID GUID of the transaction
+ * @param accountUID GUID of the account
* @return {@link org.gnucash.android.model.Money} balance of the transaction for that account
*/
- public Money getBalance(long transactionId, long accountId){
- String accountUID = getAccountUID(accountId);
- String transactionUID = getUID(transactionId);
+ public Money getBalance(String transactionUID, String accountUID){
List splitList = mSplitsDbAdapter.getSplitsForTransactionInAccount(
transactionUID, accountUID);
@@ -334,6 +415,7 @@ public Money getBalance(long transactionId, long accountId){
* @param transactionId Database record ID of transaction
* @return String unique identifier of the transaction
*/
+ @Override
public String getUID(long transactionId){
String uid = null;
Cursor c = mDb.query(TransactionEntry.TABLE_NAME,
@@ -378,20 +460,19 @@ public boolean deleteTransaction(String uid){
public int deleteAllRecords(){
return deleteAllRecords(TransactionEntry.TABLE_NAME);
}
-
- /**
+
+ /**
* Assigns transaction with id rowId to account with id accountId
- * @param rowId Record ID of the transaction to be assigned
- * @param srcAccountId Record Id of the account from which the transaction is to be moved
- * @param dstAccountId Record Id of the account to which the transaction will be assigned
+ * @param transactionUID GUID of the transaction
+ * @param srcAccountUID GUID of the account from which the transaction is to be moved
+ * @param dstAccountUID GUID of the account to which the transaction will be assigned
* @return Number of transactions splits affected
*/
- public int moveTranscation(long rowId, long srcAccountId, long dstAccountId){
- Log.i(TAG, "Moving transaction ID " + rowId + " splits from " + srcAccountId + " to account " + dstAccountId);
- String srcAccountUID = getAccountUID(srcAccountId);
- String dstAccountUID = getAccountUID(dstAccountId);
+ public int moveTranscation(String transactionUID, String srcAccountUID, String dstAccountUID){
+ Log.i(TAG, "Moving transaction ID " + transactionUID
+ + " splits from " + srcAccountUID + " to account " + dstAccountUID);
- List splits = mSplitsDbAdapter.getSplitsForTransactionInAccount(getUID(rowId), srcAccountUID);
+ List splits = mSplitsDbAdapter.getSplitsForTransactionInAccount(transactionUID, srcAccountUID);
for (Split split : splits) {
split.setAccountUID(dstAccountUID);
mSplitsDbAdapter.addSplit(split);
@@ -432,6 +513,7 @@ public long getAllTransactionsCount(){
* @param transactionUID Unique idendtifier of the transaction
* @return Database record ID for the transaction
*/
+ @Override
public long getID(String transactionUID){
long id = -1;
Cursor c = mDb.query(TransactionEntry.TABLE_NAME,
@@ -468,12 +550,11 @@ public Cursor fetchTransactionsStartingWith(String prefix){
.append(" LIKE '").append(prefix).append("%'");
String selection = stringBuffer.toString();
- Cursor c = mDb.query(TransactionEntry.TABLE_NAME,
+ return mDb.query(TransactionEntry.TABLE_NAME,
new String[]{TransactionEntry._ID, TransactionEntry.COLUMN_DESCRIPTION},
selection,
null, null, null,
TransactionEntry.COLUMN_DESCRIPTION + " ASC");
- return c;
}
/**
@@ -487,6 +568,10 @@ public int updateTransaction(String transactionUID, String columnKey, String new
return updateRecord(TransactionEntry.TABLE_NAME, getID(transactionUID), columnKey, newValue);
}
+ public int updateTransaction(ContentValues contentValues, String whereClause, String[] whereArgs){
+ return mDb.update(TransactionEntry.TABLE_NAME, contentValues, whereClause, whereArgs);
+ }
+
/**
* Schedules recurringTransaction to be executed at specific intervals.
* The interval period is packaged within the transaction
@@ -503,4 +588,13 @@ public void scheduleTransaction(Transaction recurringTransaction) {
alarmManager.setRepeating(AlarmManager.RTC_WAKEUP, firstRunMillis,
recurrencePeriodMillis, recurringPendingIntent);
}
+
+ /**
+ * Returns a transaction for the given transaction GUID
+ * @param transactionUID GUID of the transaction
+ * @return Retrieves a transaction from the database
+ */
+ public Transaction getTransaction(String transactionUID) {
+ return getTransaction(getID(transactionUID));
+ }
}
diff --git a/app/src/org/gnucash/android/export/ExportDialogFragment.java b/app/src/org/gnucash/android/export/ExportDialogFragment.java
index d7dd6c4f1..4ae490903 100644
--- a/app/src/org/gnucash/android/export/ExportDialogFragment.java
+++ b/app/src/org/gnucash/android/export/ExportDialogFragment.java
@@ -19,15 +19,18 @@
import android.app.Activity;
import android.content.Intent;
import android.content.SharedPreferences;
+import android.os.Build;
import android.os.Bundle;
import android.preference.PreferenceManager;
import android.support.v4.app.DialogFragment;
+import android.text.AndroidCharacter;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.*;
import org.gnucash.android.R;
+import org.gnucash.android.app.GnuCashApplication;
import java.io.File;
@@ -64,7 +67,12 @@ public class ExportDialogFragment extends DialogFragment {
* Cancels the export dialog
*/
Button mCancelButton;
-
+
+ /**
+ * Text view for showing warnings based on chosen export format
+ */
+ TextView mExportWarningTextView;
+
/**
* File path for saving the OFX files
*/
@@ -104,18 +112,30 @@ public void onRadioButtonClicked(View view){
switch (view.getId()){
case R.id.radio_ofx_format:
mExportFormat = ExportFormat.OFX;
+ if (GnuCashApplication.isDoubleEntryEnabled()){
+ mExportWarningTextView.setText(getActivity().getString(R.string.export_warning_ofx));
+ mExportWarningTextView.setVisibility(View.VISIBLE);
+ } else {
+ mExportWarningTextView.setVisibility(View.GONE);
+ }
break;
case R.id.radio_qif_format:
mExportFormat = ExportFormat.QIF;
+ //TODO: Also check that there exist transactions with multiple currencies before displaying warning
+ if (GnuCashApplication.isDoubleEntryEnabled()) {
+ mExportWarningTextView.setText(getActivity().getString(R.string.export_warning_qif));
+ mExportWarningTextView.setVisibility(View.VISIBLE);
+ } else {
+ mExportWarningTextView.setVisibility(View.GONE);
+ }
}
mFilePath = getActivity().getExternalFilesDir(null) + "/" + Exporter.buildExportFilename(mExportFormat);
- return;
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
- return inflater.inflate(R.layout.dialog_export_ofx, container, false);
+ return inflater.inflate(R.layout.dialog_export, container, false);
}
@Override
@@ -158,6 +178,8 @@ public void onClick(View v) {
mSaveButton.setOnClickListener(new ExportClickListener());
+ mExportWarningTextView = (TextView) v.findViewById(R.id.export_warning);
+
String defaultExportFormat = sharedPrefs.getString(getString(R.string.key_default_export_format), ExportFormat.QIF.name());
mExportFormat = ExportFormat.valueOf(defaultExportFormat);
View.OnClickListener clickListener = new View.OnClickListener() {
@@ -168,12 +190,16 @@ public void onClick(View view) {
};
RadioButton ofxRadioButton = (RadioButton) v.findViewById(R.id.radio_ofx_format);
- ofxRadioButton.setChecked(defaultExportFormat.equalsIgnoreCase(ExportFormat.OFX.name()));
ofxRadioButton.setOnClickListener(clickListener);
+ if (defaultExportFormat.equalsIgnoreCase(ExportFormat.OFX.name())) {
+ ofxRadioButton.performClick();
+ }
RadioButton qifRadioButton = (RadioButton) v.findViewById(R.id.radio_qif_format);
- qifRadioButton.setChecked(defaultExportFormat.equalsIgnoreCase(ExportFormat.QIF.name()));
qifRadioButton.setOnClickListener(clickListener);
+ if (defaultExportFormat.equalsIgnoreCase(ExportFormat.QIF.name())){
+ qifRadioButton.performClick();
+ }
}
diff --git a/app/src/org/gnucash/android/export/Exporter.java b/app/src/org/gnucash/android/export/Exporter.java
index d70413f18..e7b121ec9 100644
--- a/app/src/org/gnucash/android/export/Exporter.java
+++ b/app/src/org/gnucash/android/export/Exporter.java
@@ -1,5 +1,6 @@
/*
* Copyright (c) 2014 Ngewi Fet
+ * Copyright (c) 2014 Yongxin Wang
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -24,6 +25,7 @@
import java.io.File;
import java.io.FileFilter;
+import java.io.Writer;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Locale;
@@ -32,6 +34,7 @@
* Base class for the different exporters
*
* @author Ngewi Fet
+ * @author Yongxin Wang
*/
public abstract class Exporter {
/**
@@ -134,10 +137,10 @@ public boolean accept(File file) {
/**
* Generates the export output
- * @return Export output as String
+ * @param writer A Writer to export result to
* @throws ExporterException if an error occurs during export
*/
- public abstract String generateExport() throws ExporterException;
+ public abstract void generateExport(Writer writer) throws ExporterException;
public static class ExporterException extends RuntimeException{
diff --git a/app/src/org/gnucash/android/export/ExporterAsyncTask.java b/app/src/org/gnucash/android/export/ExporterAsyncTask.java
index c6593dc03..6378677b3 100644
--- a/app/src/org/gnucash/android/export/ExporterAsyncTask.java
+++ b/app/src/org/gnucash/android/export/ExporterAsyncTask.java
@@ -1,5 +1,6 @@
/*
- * Copyright (c) 2013 Ngewi Fet
+ * Copyright (c) 2013 - 2014 Ngewi Fet
+ * Copyright (c) 2014 Yongxin Wang
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -31,6 +32,7 @@
import org.gnucash.android.R;
import org.gnucash.android.export.ofx.OfxExporter;
import org.gnucash.android.export.qif.QifExporter;
+import org.gnucash.android.export.qif.QifHelper;
import org.gnucash.android.export.xml.GncXmlExporter;
import org.gnucash.android.ui.account.AccountsActivity;
import org.gnucash.android.ui.transaction.dialog.TransactionsDeleteConfirmationDialogFragment;
@@ -38,7 +40,9 @@
import java.io.*;
import java.nio.channels.FileChannel;
import java.text.SimpleDateFormat;
+import java.util.ArrayList;
import java.util.Date;
+import java.util.List;
/**
* Asynchronous task for exporting transactions.
@@ -107,17 +111,26 @@ protected Boolean doInBackground(ExportParams... params) {
}
try {
- writeOutput(mExporter.generateExport());
+ File file = new File(mExportParams.getTargetFilepath());
+ BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(file), "UTF-8"));
+ try {
+ mExporter.generateExport(writer);
+ }
+ finally {
+ writer.close();
+ }
} catch (Exception e) {
e.printStackTrace();
- Log.e(TAG, e.getMessage());
+ Log.e(TAG, "" + e.getMessage());
final String err_msg = e.getLocalizedMessage();
mContext.runOnUiThread(new Runnable() {
@Override
public void run() {
Toast.makeText(mContext, R.string.toast_export_error,
Toast.LENGTH_SHORT).show();
- Toast.makeText(mContext, err_msg, Toast.LENGTH_LONG).show();
+ if (err_msg != null) {
+ Toast.makeText(mContext, err_msg, Toast.LENGTH_LONG).show();
+ }
}
});
return false;
@@ -185,41 +198,43 @@ protected void onPostExecute(Boolean exportResult) {
}
- /**
- * Writes out the String containing the exported data to disk
- * @param exportOutput String containing exported data
- * @throws IOException if the write fails
- */
- private void writeOutput(String exportOutput) throws IOException {
- File file = new File(mExportParams.getTargetFilepath());
-
- BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(file), "UTF-8"));
- writer.write(exportOutput);
-
- writer.flush();
- writer.close();
- }
-
/**
* Starts an intent chooser to allow the user to select an activity to receive
* the exported OFX file
* @param path String path to the file on disk
*/
- private void shareFile(String path){
+ private void shareFile(String path) {
String defaultEmail = PreferenceManager.getDefaultSharedPreferences(mContext)
.getString(mContext.getString(R.string.key_default_export_email), null);
- Intent shareIntent = new Intent(Intent.ACTION_SEND);
+ Intent shareIntent = new Intent(Intent.ACTION_SEND_MULTIPLE);
shareIntent.setType("application/xml");
- shareIntent.putExtra(Intent.EXTRA_STREAM, Uri.parse("file://" + path));
+ ArrayList exportFiles = new ArrayList();
+ if (mExportParams.getExportFormat() == ExportFormat.QIF) {
+ try {
+ List splitFiles = splitQIF(new File(path), new File(path));
+ for (String file : splitFiles) {
+ exportFiles.add(Uri.parse("file://" + file));
+ }
+ } catch (IOException e) {
+ Log.e(TAG, "error split up files in shareFile");
+ e.printStackTrace();
+ return;
+ }
+ } else {
+ exportFiles.add(Uri.parse("file://" + path));
+ }
+ shareIntent.putExtra(Intent.EXTRA_STREAM, exportFiles);
shareIntent.putExtra(Intent.EXTRA_SUBJECT, mContext.getString(R.string.title_export_email,
mExportParams.getExportFormat().name()));
- if (defaultEmail != null && defaultEmail.trim().length() > 0){
+ if (defaultEmail != null && defaultEmail.trim().length() > 0) {
shareIntent.putExtra(Intent.EXTRA_EMAIL, new String[]{defaultEmail});
}
SimpleDateFormat formatter = (SimpleDateFormat) SimpleDateFormat.getDateTimeInstance();
- shareIntent.putExtra(Intent.EXTRA_TEXT, mContext.getString(R.string.description_export_email)
+ ArrayList extraText = new ArrayList();
+ extraText.add(mContext.getString(R.string.description_export_email)
+ " " + formatter.format(new Date(System.currentTimeMillis())));
+ shareIntent.putExtra(Intent.EXTRA_TEXT, extraText);
mContext.startActivity(Intent.createChooser(shareIntent, mContext.getString(R.string.title_select_export_destination)));
}
@@ -230,22 +245,60 @@ private void shareFile(String path){
* @param dst Absolute path to the destination file
* @throws IOException if the file could not be copied
*/
- public static void copyFile(File src, File dst) throws IOException
- {
+ public void copyFile(File src, File dst) throws IOException {
//TODO: Make this asynchronous at some time, t in the future.
- FileChannel inChannel = new FileInputStream(src).getChannel();
- FileChannel outChannel = new FileOutputStream(dst).getChannel();
- try
- {
- inChannel.transferTo(0, inChannel.size(), outChannel);
- }
- finally
- {
- if (inChannel != null)
- inChannel.close();
- if (outChannel != null)
- outChannel.close();
+ if (mExportParams.getExportFormat() == ExportFormat.QIF) {
+ splitQIF(src, dst);
+ } else {
+ FileChannel inChannel = new FileInputStream(src).getChannel();
+ FileChannel outChannel = new FileOutputStream(dst).getChannel();
+ try {
+ inChannel.transferTo(0, inChannel.size(), outChannel);
+ } finally {
+ if (inChannel != null)
+ inChannel.close();
+ if (outChannel != null)
+ outChannel.close();
+ }
}
}
+ /**
+ * Copies a file from src to dst
+ * @param src Absolute path to the source file
+ * @param dst Absolute path to the destination file
+ * @throws IOException if the file could not be copied
+ */
+ private static List splitQIF(File src, File dst) throws IOException {
+ // split only at the last dot
+ String[] pathParts = dst.getPath().split("(?=\\.[^\\.]+$)");
+ ArrayList splitFiles = new ArrayList();
+ String line;
+ BufferedReader in = new BufferedReader(new FileReader(src));
+ BufferedWriter out = null;
+ try {
+ while ((line = in.readLine()) != null) {
+ if (line.startsWith(QifHelper.INTERNAL_CURRENCY_PREFIX)) {
+ String currencyCode = line.substring(1);
+ if (out != null) {
+ out.close();
+ }
+ String newFileName = pathParts[0] + "_" + currencyCode + pathParts[1];
+ splitFiles.add(newFileName);
+ out = new BufferedWriter(new FileWriter(newFileName));
+ } else {
+ if (out == null) {
+ throw new IllegalArgumentException(src.getPath() + " format is not correct");
+ }
+ out.append(line).append('\n');
+ }
+ }
+ } finally {
+ in.close();
+ if (out != null) {
+ out.close();
+ }
+ }
+ return splitFiles;
+ }
}
diff --git a/app/src/org/gnucash/android/export/ofx/OfxExporter.java b/app/src/org/gnucash/android/export/ofx/OfxExporter.java
index ae40c2fc4..5caa4ad51 100644
--- a/app/src/org/gnucash/android/export/ofx/OfxExporter.java
+++ b/app/src/org/gnucash/android/export/ofx/OfxExporter.java
@@ -1,5 +1,6 @@
/*
* Copyright (c) 2012 - 2014 Ngewi Fet
+ * Copyright (c) 2014 Yongxin Wang
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -16,6 +17,7 @@
package org.gnucash.android.export.ofx;
+import java.io.IOException;
import java.io.StringWriter;
import java.io.Writer;
import java.util.List;
@@ -43,6 +45,7 @@
/**
* Exports the data in the database in OFX format
* @author Ngewi Fet
+ * @author Yongxin Wang
*/
public class OfxExporter extends Exporter{
@@ -100,7 +103,6 @@ private void generateOfx(Document doc, Element parent){
accountsDbAdapter.close();
}
- @Override
public String generateExport() throws ExporterException {
mAccountsList = mParameters.shouldExportAllTransactions() ?
mAccountsDbAdapter.getAllAccounts() : mAccountsDbAdapter.getExportableAccounts();
@@ -108,7 +110,7 @@ public String generateExport() throws ExporterException {
DocumentBuilderFactory docFactory = DocumentBuilderFactory
.newInstance();
- DocumentBuilder docBuilder = null;
+ DocumentBuilder docBuilder;
try {
docBuilder = docFactory.newDocumentBuilder();
} catch (ParserConfigurationException e) {
@@ -144,6 +146,16 @@ public String generateExport() throws ExporterException {
}
}
+ @Override
+ public void generateExport(Writer writer) throws ExporterException {
+ try {
+ writer.write(generateExport());
+ }
+ catch (IOException e) {
+ throw new ExporterException(mParameters, e);
+ }
+ }
+
/**
* Writes out the document held in node to outputWriter
* @param node {@link Node} containing the OFX document structure. Usually the parent node
diff --git a/app/src/org/gnucash/android/export/qif/QifExporter.java b/app/src/org/gnucash/android/export/qif/QifExporter.java
index bb88c162f..e1ce60469 100644
--- a/app/src/org/gnucash/android/export/qif/QifExporter.java
+++ b/app/src/org/gnucash/android/export/qif/QifExporter.java
@@ -1,5 +1,6 @@
/*
* Copyright (c) 2013 - 2014 Ngewi Fet
+ * Copyright (c) 2014 Yongxin Wang
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -15,20 +16,31 @@
*/
package org.gnucash.android.export.qif;
+import android.content.ContentValues;
+import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
+
+import org.gnucash.android.app.GnuCashApplication;
+import static org.gnucash.android.db.DatabaseSchema.*;
+
+import org.gnucash.android.db.AccountsDbAdapter;
+import org.gnucash.android.db.TransactionsDbAdapter;
import org.gnucash.android.export.ExportParams;
import org.gnucash.android.export.Exporter;
import org.gnucash.android.model.Account;
-import java.util.ArrayList;
-import java.util.List;
+import java.io.IOException;
+import java.io.Writer;
+import java.math.BigDecimal;
+import java.util.Currency;
/**
- * @author Ngewi
+ * Exports the accounts and transactions in the database to the QIF format
+ *
+ * @author Ngewi Fet
+ * @author Yongxin Wang
*/
public class QifExporter extends Exporter{
- private List mAccountsList;
-
public QifExporter(ExportParams params){
super(params);
}
@@ -37,29 +49,133 @@ public QifExporter(ExportParams params, SQLiteDatabase db){
super(params, db);
}
- private String generateQIF(){
- StringBuffer qifBuffer = new StringBuffer();
-
- List exportedTransactions = new ArrayList();
- for (Account account : mAccountsList) {
- if (account.getTransactionCount() == 0)
- continue;
-
- qifBuffer.append(account.toQIF(mParameters.shouldExportAllTransactions(), exportedTransactions) + "\n");
-
- //mark as exported
- mAccountsDbAdapter.markAsExported(account.getUID());
- }
- mAccountsDbAdapter.close();
-
- return qifBuffer.toString();
- }
-
@Override
- public String generateExport() throws ExporterException {
- mAccountsList = mParameters.shouldExportAllTransactions() ?
- mAccountsDbAdapter.getAllAccounts() : mAccountsDbAdapter.getExportableAccounts();
-
- return generateQIF();
+ public void generateExport(Writer writer) throws ExporterException {
+ final String newLine = "\n";
+ TransactionsDbAdapter transactionsDbAdapter = new TransactionsDbAdapter(GnuCashApplication.getAppContext());
+ try {
+ Cursor cursor = transactionsDbAdapter.fetchTransactionsWithSplitsWithTransactionAccount(
+ new String[]{
+ TransactionEntry.TABLE_NAME + "_" + TransactionEntry.COLUMN_UID + " AS trans_uid",
+ TransactionEntry.TABLE_NAME + "_" + TransactionEntry.COLUMN_TIMESTAMP + " AS trans_time",
+ TransactionEntry.TABLE_NAME + "_" + TransactionEntry.COLUMN_DESCRIPTION + " AS trans_desc",
+ SplitEntry.TABLE_NAME + "_" + SplitEntry.COLUMN_AMOUNT + " AS split_amount",
+ SplitEntry.TABLE_NAME + "_" + SplitEntry.COLUMN_TYPE + " AS split_type",
+ SplitEntry.TABLE_NAME + "_" + SplitEntry.COLUMN_MEMO + " AS split_memo",
+ "trans_extra_info.trans_acct_balance AS trans_acct_balance",
+ "account1." + AccountEntry.COLUMN_UID + " AS acct1_uid",
+ "account1." + AccountEntry.COLUMN_FULL_NAME + " AS acct1_full_name",
+ "account1." + AccountEntry.COLUMN_CURRENCY + " AS acct1_currency",
+ "account1." + AccountEntry.COLUMN_TYPE + " AS acct1_type",
+ AccountEntry.TABLE_NAME + "_" + AccountEntry.COLUMN_FULL_NAME + " AS acct2_full_name"
+ },
+ // no recurrence transactions
+ TransactionEntry.TABLE_NAME + "_" + TransactionEntry.COLUMN_RECURRENCE_PERIOD + " == 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 +
+ (
+ mParameters.shouldExportAllTransactions() ?
+ "" : " AND " + TransactionEntry.TABLE_NAME + "_" + TransactionEntry.COLUMN_EXPORTED + "== 0"
+ ),
+ null,
+ // trans_time ASC : put transactions in time order
+ // trans_uid ASC : put splits from the same transaction together
+ "acct1_currency ASC, trans_time ASC, trans_uid ASC"
+ );
+ try {
+ String currentCurrencyCode = "";
+ String currentAccountUID = "";
+ String currentTransactionUID = "";
+ while (cursor.moveToNext()) {
+ String currencyCode = cursor.getString(cursor.getColumnIndexOrThrow("acct1_currency"));
+ String accountUID = cursor.getString(cursor.getColumnIndexOrThrow("acct1_uid"));
+ String transactionUID = cursor.getString(cursor.getColumnIndexOrThrow("trans_uid"));
+ if (!transactionUID.equals(currentTransactionUID)) {
+ if (!currentTransactionUID.equals("")) {
+ writer.append(QifHelper.ENTRY_TERMINATOR).append(newLine);
+ // end last transaction
+ }
+ if (!accountUID.equals(currentAccountUID)) {
+ // no need to end account
+ //if (!currentAccountUID.equals("")) {
+ // // end last account
+ //}
+ if (!currencyCode.equals(currentCurrencyCode)) {
+ currentCurrencyCode = currencyCode;
+ writer.append(QifHelper.INTERNAL_CURRENCY_PREFIX)
+ .append(currencyCode)
+ .append(newLine);
+ }
+ // start new account
+ currentAccountUID = accountUID;
+ writer.append(QifHelper.ACCOUNT_HEADER).append(newLine);
+ writer.append(QifHelper.ACCOUNT_NAME_PREFIX)
+ .append(cursor.getString(cursor.getColumnIndexOrThrow("acct1_full_name")))
+ .append(newLine);
+ writer.append(QifHelper.ENTRY_TERMINATOR).append(newLine);
+ writer.append(QifHelper.getQifHeader(cursor.getString(cursor.getColumnIndexOrThrow("acct1_type"))))
+ .append(newLine);
+ }
+ // start new transaction
+ currentTransactionUID = transactionUID;
+ writer.append(QifHelper.DATE_PREFIX)
+ .append(QifHelper.formatDate(cursor.getLong(cursor.getColumnIndexOrThrow("trans_time"))))
+ .append(newLine);
+ writer.append(QifHelper.MEMO_PREFIX)
+ .append(cursor.getString(cursor.getColumnIndexOrThrow("trans_desc")))
+ .append(newLine);
+ // deal with imbalance first
+ double imbalance = cursor.getDouble(cursor.getColumnIndexOrThrow("trans_acct_balance"));
+ BigDecimal decimalImbalance = BigDecimal.valueOf(imbalance).setScale(2, BigDecimal.ROUND_HALF_UP);
+ if (decimalImbalance.compareTo(BigDecimal.ZERO) != 0) {
+ writer.append(QifHelper.SPLIT_CATEGORY_PREFIX)
+ .append(AccountsDbAdapter.getImbalanceAccountName(
+ Currency.getInstance(cursor.getString(cursor.getColumnIndexOrThrow("acct1_currency")))
+ ))
+ .append(newLine);
+ writer.append(QifHelper.SPLIT_AMOUNT_PREFIX)
+ .append(decimalImbalance.toPlainString())
+ .append(newLine);
+ }
+ }
+ // all splits
+ // amount associated with the header account will not be exported.
+ // It can be auto balanced when importing to GnuCash
+ writer.append(QifHelper.SPLIT_CATEGORY_PREFIX)
+ .append(cursor.getString(cursor.getColumnIndexOrThrow("acct2_full_name")))
+ .append(newLine);
+ String splitMemo = cursor.getString(cursor.getColumnIndexOrThrow("split_memo"));
+ if (splitMemo != null && splitMemo.length() > 0) {
+ writer.append(QifHelper.SPLIT_MEMO_PREFIX)
+ .append(splitMemo)
+ .append(newLine);
+ }
+ String splitType = cursor.getString(cursor.getColumnIndexOrThrow("split_type"));
+ writer.append(QifHelper.SPLIT_AMOUNT_PREFIX)
+ .append(splitType.equals("DEBIT") ? "-" : "")
+ .append(cursor.getString(cursor.getColumnIndexOrThrow("split_amount")))
+ .append(newLine);
+ }
+ if (!currentTransactionUID.equals("")) {
+ // end last transaction
+ writer.append(QifHelper.ENTRY_TERMINATOR).append(newLine);
+ }
+ }
+ finally {
+ cursor.close();
+ }
+ ContentValues contentValues = new ContentValues();
+ contentValues.put(TransactionEntry.COLUMN_EXPORTED, 1);
+ transactionsDbAdapter.updateTransaction(contentValues, null, null);
+ }
+ catch (IOException e)
+ {
+ throw new ExporterException(mParameters, e);
+ }
+ finally {
+ transactionsDbAdapter.close();
+ }
}
}
diff --git a/app/src/org/gnucash/android/export/qif/QifHelper.java b/app/src/org/gnucash/android/export/qif/QifHelper.java
index dd5a3f093..c889def5c 100644
--- a/app/src/org/gnucash/android/export/qif/QifHelper.java
+++ b/app/src/org/gnucash/android/export/qif/QifHelper.java
@@ -1,5 +1,6 @@
/*
* Copyright (c) 2013 - 2014 Ngewi Fet
+ * Copyright (c) 2014 Yongxin Wang
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -16,16 +17,13 @@
package org.gnucash.android.export.qif;
-import org.gnucash.android.R;
-import org.gnucash.android.app.GnuCashApplication;
import org.gnucash.android.model.AccountType;
import java.text.SimpleDateFormat;
-import java.util.Currency;
import java.util.Date;
/**
- * @author Ngewi
+ * @author Ngewi Fet
*/
public class QifHelper {
/*
@@ -42,6 +40,7 @@ public class QifHelper {
public static final String ACCOUNT_HEADER = "!Account";
public static final String ACCOUNT_NAME_PREFIX = "N";
+ public static final String INTERNAL_CURRENCY_PREFIX = "*";
public static final String ENTRY_TERMINATOR = "^";
private static final SimpleDateFormat QIF_DATE_FORMATTER = new SimpleDateFormat("yyyy/M/d");
@@ -80,4 +79,7 @@ public static String getQifHeader(AccountType accountType){
}
}
+ public static String getQifHeader(String accountType) {
+ return getQifHeader(AccountType.valueOf(accountType));
+ }
}
diff --git a/app/src/org/gnucash/android/export/xml/GncXmlExporter.java b/app/src/org/gnucash/android/export/xml/GncXmlExporter.java
index 63c7f09e2..1fc81fdca 100644
--- a/app/src/org/gnucash/android/export/xml/GncXmlExporter.java
+++ b/app/src/org/gnucash/android/export/xml/GncXmlExporter.java
@@ -1,5 +1,6 @@
/*
* Copyright (c) 2014 Ngewi Fet
+ * Copyright (c) 2014 Yongxin Wang
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -19,7 +20,9 @@
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import android.util.Log;
+
import org.gnucash.android.db.DatabaseSchema;
+import static org.gnucash.android.db.DatabaseSchema.*;
import org.gnucash.android.db.TransactionsDbAdapter;
import org.gnucash.android.export.ExportFormat;
import org.gnucash.android.export.ExportParams;
@@ -28,6 +31,8 @@
import org.gnucash.android.model.Transaction;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
+import org.xmlpull.v1.XmlPullParserFactory;
+import org.xmlpull.v1.XmlSerializer;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
@@ -37,21 +42,22 @@
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;
-import java.io.BufferedWriter;
-import java.io.FileWriter;
-import java.io.IOException;
-import java.io.StringWriter;
+import java.io.*;
+import java.math.BigDecimal;
+import java.util.ArrayList;
+import java.util.Currency;
import java.util.List;
import java.util.UUID;
+import java.util.zip.GZIPOutputStream;
/**
* Creates a GnuCash XML representation of the accounts and transactions
*
* @author Ngewi Fet
+ * @author Yongxin Wang
*/
public class GncXmlExporter extends Exporter{
- private Document mDocument;
private TransactionsDbAdapter mTransactionsDbAdapter;
public GncXmlExporter(ExportParams params){
@@ -70,20 +76,351 @@ public GncXmlExporter(ExportParams params, SQLiteDatabase db){
mTransactionsDbAdapter = new TransactionsDbAdapter(db);
}
+ private void exportSlots(XmlSerializer xmlSerializer,
+ List slotKey,
+ List slotType,
+ List slotValue) throws IOException {
+ if (slotKey == null || slotType == null || slotValue == null ||
+ slotKey.size() == 0 || slotType.size() != slotKey.size() || slotValue.size() != slotKey.size()) {
+ return;
+ }
+ xmlSerializer.startTag(null, GncXmlHelper.TAG_ACT_SLOTS);
+ for (int i = 0; i < slotKey.size(); i++) {
+ xmlSerializer.startTag(null, GncXmlHelper.TAG_SLOT);
+ xmlSerializer.startTag(null, GncXmlHelper.TAG_SLOT_KEY);
+ xmlSerializer.text(slotKey.get(i));
+ xmlSerializer.endTag(null, GncXmlHelper.TAG_SLOT_KEY);
+ xmlSerializer.startTag(null, GncXmlHelper.TAG_SLOT_VALUE);
+ xmlSerializer.attribute(null, GncXmlHelper.ATTR_KEY_TYPE, slotType.get(i));
+ xmlSerializer.text(slotValue.get(i));
+ xmlSerializer.endTag(null, GncXmlHelper.TAG_SLOT_VALUE);
+ xmlSerializer.endTag(null, GncXmlHelper.TAG_SLOT);
+ }
+ xmlSerializer.endTag(null, GncXmlHelper.TAG_ACT_SLOTS);
+ }
+
+ private void exportAccounts(XmlSerializer xmlSerializer) throws IOException {
+ Cursor cursor = mAccountsDbAdapter.fetchAccounts(null, null, null);
+ while (cursor.moveToNext()) {
+ // write account
+ xmlSerializer.startTag(null, GncXmlHelper.TAG_ACCOUNT);
+ xmlSerializer.attribute(null, GncXmlHelper.ATTR_KEY_VERSION, GncXmlHelper.BOOK_VERSION);
+ // account name
+ xmlSerializer.startTag(null, GncXmlHelper.TAG_NAME);
+ xmlSerializer.text(cursor.getString(cursor.getColumnIndexOrThrow(DatabaseSchema.AccountEntry.COLUMN_NAME)));
+ xmlSerializer.endTag(null, GncXmlHelper.TAG_NAME);
+ // account guid
+ xmlSerializer.startTag(null, GncXmlHelper.TAG_ACCT_ID);
+ xmlSerializer.attribute(null, GncXmlHelper.ATTR_KEY_TYPE, GncXmlHelper.ATTR_VALUE_GUID);
+ xmlSerializer.text(cursor.getString(cursor.getColumnIndexOrThrow(DatabaseSchema.AccountEntry.COLUMN_UID)));
+ xmlSerializer.endTag(null, GncXmlHelper.TAG_ACCT_ID);
+ // account type
+ xmlSerializer.startTag(null, GncXmlHelper.TAG_TYPE);
+ String acct_type = cursor.getString(cursor.getColumnIndexOrThrow(DatabaseSchema.AccountEntry.COLUMN_TYPE));
+ xmlSerializer.text(acct_type);
+ xmlSerializer.endTag(null, GncXmlHelper.TAG_TYPE);
+ // commodity
+ xmlSerializer.startTag(null, GncXmlHelper.TAG_COMMODITY);
+ xmlSerializer.startTag(null, GncXmlHelper.TAG_COMMODITY_SPACE);
+ xmlSerializer.text("ISO4217");
+ xmlSerializer.endTag(null, GncXmlHelper.TAG_COMMODITY_SPACE);
+ xmlSerializer.startTag(null, GncXmlHelper.TAG_COMMODITY_ID);
+ String acctCurrencyCode = cursor.getString(cursor.getColumnIndexOrThrow(DatabaseSchema.AccountEntry.COLUMN_CURRENCY));
+ xmlSerializer.text(acctCurrencyCode);
+ xmlSerializer.endTag(null, GncXmlHelper.TAG_COMMODITY_ID);
+ xmlSerializer.endTag(null, GncXmlHelper.TAG_COMMODITY);
+ // commodity scu
+ xmlSerializer.startTag(null, GncXmlHelper.TAG_COMMODITY_SCU);
+ xmlSerializer.text(Integer.toString((int) Math.pow(10, Currency.getInstance(acctCurrencyCode).getDefaultFractionDigits())));
+ xmlSerializer.endTag(null, GncXmlHelper.TAG_COMMODITY_SCU);
+ // account description
+ // this is optional in Gnc XML, and currently not in the db, so description node
+ // is omitted
+ //
+ // account slots, color, placeholder, default transfer account, favorite
+ ArrayList slotKey = new ArrayList();
+ ArrayList slotType = new ArrayList();
+ ArrayList slotValue = new ArrayList();
+ slotKey.add(GncXmlHelper.KEY_PLACEHOLDER);
+ slotType.add(GncXmlHelper.ATTR_VALUE_STRING);
+ slotValue.add(Boolean.toString(cursor.getInt(cursor.getColumnIndexOrThrow(DatabaseSchema.AccountEntry.COLUMN_PLACEHOLDER)) != 0));
+
+ String color = cursor.getString(cursor.getColumnIndexOrThrow(DatabaseSchema.AccountEntry.COLUMN_COLOR_CODE));
+ if (color != null && color.length() > 0) {
+ slotKey.add(GncXmlHelper.KEY_COLOR);
+ slotType.add(GncXmlHelper.ATTR_VALUE_STRING);
+ slotValue.add(color);
+ }
+
+ String defaultTransferAcctUID = cursor.getString(cursor.getColumnIndexOrThrow(DatabaseSchema.AccountEntry.COLUMN_DEFAULT_TRANSFER_ACCOUNT_UID));
+ if (defaultTransferAcctUID != null && defaultTransferAcctUID.length() > 0) {
+ slotKey.add(GncXmlHelper.KEY_DEFAULT_TRANSFER_ACCOUNT);
+ slotType.add(GncXmlHelper.ATTR_VALUE_STRING);
+ slotValue.add(defaultTransferAcctUID);
+ }
+
+ slotKey.add(GncXmlHelper.KEY_FAVORITE);
+ slotType.add(GncXmlHelper.ATTR_VALUE_STRING);
+ slotValue.add(Boolean.toString(cursor.getInt(cursor.getColumnIndexOrThrow(DatabaseSchema.AccountEntry.COLUMN_FAVORITE)) != 0));
+ exportSlots(xmlSerializer, slotKey, slotType, slotValue);
+
+ // parent uid
+ String parentUID = cursor.getString(cursor.getColumnIndexOrThrow(DatabaseSchema.AccountEntry.COLUMN_PARENT_ACCOUNT_UID));
+ if (!acct_type.equals("ROOT") && parentUID != null && parentUID.length() > 0) {
+ xmlSerializer.startTag(null, GncXmlHelper.TAG_PARENT_UID);
+ xmlSerializer.attribute(null, GncXmlHelper.ATTR_KEY_TYPE, GncXmlHelper.ATTR_VALUE_STRING);
+ xmlSerializer.text(parentUID);
+ xmlSerializer.endTag(null, GncXmlHelper.TAG_PARENT_UID);
+ } else {
+ Log.d("export", "root account : " + cursor.getString(cursor.getColumnIndexOrThrow(DatabaseSchema.AccountEntry.COLUMN_UID)));
+ }
+ xmlSerializer.endTag(null, GncXmlHelper.TAG_ACCOUNT);
+ }
+ cursor.close();
+ }
+
+ public void exportTransactions(XmlSerializer xmlSerializer) throws IOException {
+ Cursor cursor = mTransactionsDbAdapter.fetchTransactionsWithSplits(
+ new String[]{
+ TransactionEntry.TABLE_NAME+"."+ TransactionEntry.COLUMN_UID + " AS trans_uid",
+ TransactionEntry.TABLE_NAME+"."+ TransactionEntry.COLUMN_DESCRIPTION + " AS trans_desc",
+ TransactionEntry.TABLE_NAME+"."+ TransactionEntry.COLUMN_NOTES + " AS trans_notes",
+ TransactionEntry.TABLE_NAME+"."+ TransactionEntry.COLUMN_TIMESTAMP + " AS trans_time",
+ TransactionEntry.TABLE_NAME+"."+ TransactionEntry.COLUMN_EXPORTED + " AS trans_exported",
+ TransactionEntry.TABLE_NAME+"."+ TransactionEntry.COLUMN_CURRENCY + " AS trans_currency",
+ TransactionEntry.TABLE_NAME+"."+ TransactionEntry.COLUMN_RECURRENCE_PERIOD + " AS trans_recur",
+ SplitEntry.TABLE_NAME+"."+ SplitEntry.COLUMN_UID + " AS split_uid",
+ SplitEntry.TABLE_NAME+"."+ SplitEntry.COLUMN_MEMO + " AS split_memo",
+ SplitEntry.TABLE_NAME+"."+ SplitEntry.COLUMN_TYPE + " AS split_type",
+ SplitEntry.TABLE_NAME+"."+ SplitEntry.COLUMN_AMOUNT + " AS split_amount",
+ SplitEntry.TABLE_NAME+"."+ SplitEntry.COLUMN_ACCOUNT_UID + " AS split_acct_uid"
+ }, null,
+ TransactionEntry.TABLE_NAME + "." + TransactionEntry.COLUMN_RECURRENCE_PERIOD + " ASC , " +
+ TransactionEntry.TABLE_NAME + "." + TransactionEntry.COLUMN_TIMESTAMP + " ASC , " +
+ TransactionEntry.TABLE_NAME + "." + TransactionEntry.COLUMN_UID + " ASC ");
+
+ String lastTrxUID = "";
+ Currency trxCurrency;
+ int fractionDigits;
+ BigDecimal denom = new BigDecimal(100);
+ String denomString = "100";
+ int recur = 0;
+ while (cursor.moveToNext()){
+ String curTrxUID = cursor.getString(cursor.getColumnIndexOrThrow("trans_uid"));
+ if (!lastTrxUID.equals(curTrxUID)) { // new transaction starts
+ if (!lastTrxUID.equals("")) { // there's an old transaction, close it
+ xmlSerializer.endTag(null, GncXmlHelper.TAG_TRN_SPLITS);
+ if (recur > 0) {
+ xmlSerializer.startTag(null, GncXmlHelper.TAG_RECURRENCE_PERIOD);
+ xmlSerializer.text(Integer.toString(recur));
+ xmlSerializer.endTag(null, GncXmlHelper.TAG_RECURRENCE_PERIOD);
+ }
+ xmlSerializer.endTag(null, GncXmlHelper.TAG_TRANSACTION);
+ }
+ // new transaction
+ xmlSerializer.startTag(null, GncXmlHelper.TAG_TRANSACTION);
+ xmlSerializer.attribute(null, GncXmlHelper.ATTR_KEY_VERSION, GncXmlHelper.BOOK_VERSION);
+ // transaction id
+ xmlSerializer.startTag(null, GncXmlHelper.TAG_TRX_ID);
+ xmlSerializer.attribute(null, GncXmlHelper.ATTR_KEY_TYPE, GncXmlHelper.ATTR_VALUE_GUID);
+ xmlSerializer.text(curTrxUID);
+ xmlSerializer.endTag(null, GncXmlHelper.TAG_TRX_ID);
+ // currency
+ String currency = cursor.getString(cursor.getColumnIndexOrThrow("trans_currency"));
+ trxCurrency = Currency.getInstance(currency);
+ fractionDigits = trxCurrency.getDefaultFractionDigits();
+ int denomInt;
+ denomInt = (int) Math.pow(10, fractionDigits);
+ denom = new BigDecimal(denomInt);
+ denomString = Integer.toString(denomInt);
+ xmlSerializer.startTag(null, GncXmlHelper.TAG_TRX_CURRENCY);
+ xmlSerializer.startTag(null, GncXmlHelper.TAG_COMMODITY_SPACE);
+ xmlSerializer.text("ISO4217");
+ xmlSerializer.endTag(null, GncXmlHelper.TAG_COMMODITY_SPACE);
+ xmlSerializer.startTag(null, GncXmlHelper.TAG_COMMODITY_ID);
+ xmlSerializer.text(currency);
+ xmlSerializer.endTag(null, GncXmlHelper.TAG_COMMODITY_ID);
+ xmlSerializer.endTag(null, GncXmlHelper.TAG_TRX_CURRENCY);
+ // date posted
+ String strDate = GncXmlHelper.formatDate(cursor.getLong(cursor.getColumnIndexOrThrow("trans_time")));
+ xmlSerializer.startTag(null, GncXmlHelper.TAG_DATE_POSTED);
+ xmlSerializer.startTag(null, GncXmlHelper.TAG_DATE);
+ xmlSerializer.text(strDate);
+ xmlSerializer.endTag(null, GncXmlHelper.TAG_DATE);
+ xmlSerializer.endTag(null, GncXmlHelper.TAG_DATE_POSTED);
+ // date entered
+ xmlSerializer.startTag(null, GncXmlHelper.TAG_DATE_ENTERED);
+ xmlSerializer.startTag(null, GncXmlHelper.TAG_DATE);
+ xmlSerializer.text(strDate);
+ xmlSerializer.endTag(null, GncXmlHelper.TAG_DATE);
+ xmlSerializer.endTag(null, GncXmlHelper.TAG_DATE_ENTERED);
+ // description
+ xmlSerializer.startTag(null, GncXmlHelper.TAG_TRN_DESCRIPTION);
+ xmlSerializer.text(cursor.getString(cursor.getColumnIndexOrThrow("trans_desc")));
+ xmlSerializer.endTag(null, GncXmlHelper.TAG_TRN_DESCRIPTION);
+ lastTrxUID = curTrxUID;
+ // slots
+ ArrayList slotKey = new ArrayList();
+ ArrayList slotType = new ArrayList();
+ ArrayList slotValue = new ArrayList();
+
+ String notes = cursor.getString(cursor.getColumnIndexOrThrow("trans_notes"));
+ boolean exported = cursor.getInt(cursor.getColumnIndexOrThrow("trans_exported")) == 1;
+ if (notes != null && notes.length() > 0) {
+ slotKey.add(GncXmlHelper.KEY_NOTES);
+ slotType.add(GncXmlHelper.ATTR_VALUE_STRING);
+ slotValue.add(notes);
+ }
+ if (!exported) {
+ slotKey.add(GncXmlHelper.KEY_EXPORTED);
+ slotType.add(GncXmlHelper.ATTR_VALUE_STRING);
+ slotValue.add("false");
+ }
+ exportSlots(xmlSerializer, slotKey, slotType, slotValue);
+ // recurrence period, will be write out when all splits are generated.
+ recur = cursor.getInt(cursor.getColumnIndexOrThrow("trans_recur"));
+ // splits start
+ xmlSerializer.startTag(null, GncXmlHelper.TAG_TRN_SPLITS);
+ }
+ xmlSerializer.startTag(null, GncXmlHelper.TAG_TRN_SPLIT);
+ // split id
+ xmlSerializer.startTag(null, GncXmlHelper.TAG_SPLIT_ID);
+ xmlSerializer.attribute(null, GncXmlHelper.ATTR_KEY_TYPE, GncXmlHelper.ATTR_VALUE_GUID);
+ xmlSerializer.text(cursor.getString(cursor.getColumnIndexOrThrow("split_uid")));
+ xmlSerializer.endTag(null, GncXmlHelper.TAG_SPLIT_ID);
+ // memo
+ String memo = cursor.getString(cursor.getColumnIndexOrThrow("split_memo"));
+ if (memo != null && memo.length() > 0){
+ xmlSerializer.startTag(null, GncXmlHelper.TAG_SPLIT_MEMO);
+ xmlSerializer.text(memo);
+ xmlSerializer.endTag(null, GncXmlHelper.TAG_SPLIT_MEMO);
+ }
+ // reconciled
+ xmlSerializer.startTag(null, GncXmlHelper.TAG_RECONCILED_STATE);
+ xmlSerializer.text("n");
+ xmlSerializer.endTag(null, GncXmlHelper.TAG_RECONCILED_STATE);
+ // value, in the transaction's currency
+ String trxType = cursor.getString(cursor.getColumnIndexOrThrow("split_type"));
+ BigDecimal value = new BigDecimal(cursor.getString(cursor.getColumnIndexOrThrow("split_amount")));
+ value = value.multiply(denom);
+ String strValue = (trxType.equals("CREDIT") ? "-" : "") + value.stripTrailingZeros().toPlainString() + "/" + denomString;
+ xmlSerializer.startTag(null, GncXmlHelper.TAG_SPLIT_VALUE);
+ xmlSerializer.text(strValue);
+ xmlSerializer.endTag(null, GncXmlHelper.TAG_SPLIT_VALUE);
+ // quantity, in the split account's currency
+ // TODO: multi currency support.
+ xmlSerializer.startTag(null, GncXmlHelper.TAG_SPLIT_QUANTITY);
+ xmlSerializer.text(strValue);
+ xmlSerializer.endTag(null, GncXmlHelper.TAG_SPLIT_QUANTITY);
+ // account guid
+ xmlSerializer.startTag(null, GncXmlHelper.TAG_SPLIT_ACCOUNT);
+ xmlSerializer.attribute(null, GncXmlHelper.ATTR_KEY_TYPE, GncXmlHelper.ATTR_VALUE_GUID);
+ xmlSerializer.text(cursor.getString(cursor.getColumnIndexOrThrow("split_acct_uid")));
+ xmlSerializer.endTag(null, GncXmlHelper.TAG_SPLIT_ACCOUNT);
+ xmlSerializer.endTag(null, GncXmlHelper.TAG_TRN_SPLIT);
+ }
+ if (!lastTrxUID.equals("")){ // there's an unfinished transaction, close it
+ xmlSerializer.endTag(null,GncXmlHelper.TAG_TRN_SPLITS);
+ if (recur > 0) {
+ xmlSerializer.startTag(null, GncXmlHelper.TAG_RECURRENCE_PERIOD);
+ xmlSerializer.text(Integer.toString(recur));
+ xmlSerializer.endTag(null, GncXmlHelper.TAG_RECURRENCE_PERIOD);
+ }
+ xmlSerializer.endTag(null, GncXmlHelper.TAG_TRANSACTION);
+ }
+ cursor.close();
+ }
+
+ @Override
+ public void generateExport(Writer writer) throws ExporterException{
+ try {
+ String[] namespaces = new String[] {"gnc", "act", "book", "cd", "cmdty", "price", "slot", "split", "trn", "ts"};
+ XmlSerializer xmlSerializer = XmlPullParserFactory.newInstance().newSerializer();
+ xmlSerializer.setOutput(writer);
+ xmlSerializer.startDocument("utf-8", true);
+ // root tag
+ xmlSerializer.startTag(null, GncXmlHelper.TAG_ROOT);
+ for(String ns : namespaces) {
+ xmlSerializer.attribute(null, "xmlns:" + ns, "http://www.gnucash.org/XML/" + ns);
+ }
+ // book count
+ xmlSerializer.startTag(null, GncXmlHelper.TAG_COUNT_DATA);
+ xmlSerializer.attribute(null, GncXmlHelper.ATTR_KEY_CD_TYPE, "book");
+ xmlSerializer.text("1");
+ xmlSerializer.endTag(null, GncXmlHelper.TAG_COUNT_DATA);
+ // book
+ xmlSerializer.startTag(null, GncXmlHelper.TAG_BOOK);
+ xmlSerializer.attribute(null, GncXmlHelper.ATTR_KEY_VERSION, GncXmlHelper.BOOK_VERSION);
+ // book_id
+ xmlSerializer.startTag(null, GncXmlHelper.TAG_BOOK_ID);
+ xmlSerializer.attribute(null, GncXmlHelper.ATTR_KEY_TYPE, GncXmlHelper.ATTR_VALUE_GUID);
+ xmlSerializer.text(UUID.randomUUID().toString().replaceAll("-", ""));
+ xmlSerializer.endTag(null, GncXmlHelper.TAG_BOOK_ID);
+ //commodity count
+ xmlSerializer.startTag(null, GncXmlHelper.TAG_COUNT_DATA);
+ xmlSerializer.attribute(null, GncXmlHelper.ATTR_KEY_CD_TYPE, "commodity");
+ xmlSerializer.text(mAccountsDbAdapter.getCurrencies().size() + "");
+ xmlSerializer.endTag(null, GncXmlHelper.TAG_COUNT_DATA);
+ //account count
+ xmlSerializer.startTag(null, GncXmlHelper.TAG_COUNT_DATA);
+ xmlSerializer.attribute(null, GncXmlHelper.ATTR_KEY_CD_TYPE, "account");
+ xmlSerializer.text(mAccountsDbAdapter.getTotalAccountCount() + "");
+ xmlSerializer.endTag(null, GncXmlHelper.TAG_COUNT_DATA);
+ //transaction count
+ xmlSerializer.startTag(null, GncXmlHelper.TAG_COUNT_DATA);
+ xmlSerializer.attribute(null, GncXmlHelper.ATTR_KEY_CD_TYPE, "transaction");
+ xmlSerializer.text(mTransactionsDbAdapter.getTotalTransactionsCount() + "");
+ xmlSerializer.endTag(null, GncXmlHelper.TAG_COUNT_DATA);
+ // accounts. bulk import does not rely on account order
+ // the cursor gather account in arbitrary order
+ exportAccounts(xmlSerializer);
+
+ // transactions.
+ exportTransactions(xmlSerializer);
+
+ xmlSerializer.endTag(null, GncXmlHelper.TAG_BOOK);
+ xmlSerializer.endTag(null, GncXmlHelper.TAG_ROOT);
+ xmlSerializer.endDocument();
+ } catch (Exception e) {
+ e.printStackTrace();
+ throw new ExporterException(mParameters, e);
+ }
+ }
+ /**
+ * Creates a backup of current database contents to the default backup location
+ */
+ public static void createBackup(){
+ ExportParams params = new ExportParams(ExportFormat.GNC_XML);
+ try {
+ FileOutputStream fileOutputStream = new FileOutputStream(Exporter.createBackupFile());
+ BufferedOutputStream bufferedOutputStream = new BufferedOutputStream(fileOutputStream);
+ GZIPOutputStream gzipOutputStream = new GZIPOutputStream(bufferedOutputStream);
+ OutputStreamWriter outputStreamWriter = new OutputStreamWriter(gzipOutputStream);
+ new GncXmlExporter(params).generateExport(outputStreamWriter);
+ outputStreamWriter.close();
+ } catch (IOException e) {
+ e.printStackTrace();
+ Log.e("GncXmlExporter", "Error creating backup", e);
+ }
+ }
+
/**
- * Generate GnuCash XML
+ * Generate GnuCash XML by loading the accounts and transactions from the database and exporting each one.
+ * This method consumes a lot of memory and is slow, but exists for database migrations for backwards compatibility.
+ *
The normal exporter interface should be used to generate GncXML files
+ * @return String with the generated XML
* @throws ParserConfigurationException if there was an error when generating the XML
+ * @deprecated Use the {@link #generateExport(java.io.Writer)} to generate XML
*/
- private void generateGncXml() throws ParserConfigurationException {
+ public String generateXML() throws ParserConfigurationException {
DocumentBuilderFactory docFactory = DocumentBuilderFactory.newInstance();
-// docFactory.setNamespaceAware(true);
DocumentBuilder documentBuilder = docFactory.newDocumentBuilder();
- mDocument = documentBuilder.newDocument();
- mDocument.setXmlVersion("1.0");
- mDocument.setXmlStandalone(true);
+ Document document = documentBuilder.newDocument();
+ document.setXmlVersion("1.0");
+ document.setXmlStandalone(true);
- Element rootElement = mDocument.createElement(GncXmlHelper.TAG_ROOT);
+ Element rootElement = document.createElement(GncXmlHelper.TAG_ROOT);
rootElement.setAttribute("xmlns:gnc", "http://www.gnucash.org/XML/gnc");
rootElement.setAttribute("xmlns:act", "http://www.gnucash.org/XML/act");
rootElement.setAttribute("xmlns:book", "http://www.gnucash.org/XML/book");
@@ -95,41 +432,41 @@ private void generateGncXml() throws ParserConfigurationException {
rootElement.setAttribute("xmlns:trn", "http://www.gnucash.org/XML/trn");
rootElement.setAttribute("xmlns:ts", "http://www.gnucash.org/XML/ts");
- Element bookCountNode = mDocument.createElement(GncXmlHelper.TAG_COUNT_DATA);
+ Element bookCountNode = document.createElement(GncXmlHelper.TAG_COUNT_DATA);
bookCountNode.setAttribute(GncXmlHelper.ATTR_KEY_CD_TYPE, GncXmlHelper.ATTR_VALUE_BOOK);
- bookCountNode.appendChild(mDocument.createTextNode("1"));
+ bookCountNode.appendChild(document.createTextNode("1"));
rootElement.appendChild(bookCountNode);
- Element bookNode = mDocument.createElement(GncXmlHelper.TAG_BOOK);
+ Element bookNode = document.createElement(GncXmlHelper.TAG_BOOK);
bookNode.setAttribute(GncXmlHelper.ATTR_KEY_VERSION, GncXmlHelper.BOOK_VERSION);
rootElement.appendChild(bookNode);
- Element bookIdNode = mDocument.createElement(GncXmlHelper.TAG_BOOK_ID);
+ Element bookIdNode = document.createElement(GncXmlHelper.TAG_BOOK_ID);
bookIdNode.setAttribute(GncXmlHelper.ATTR_KEY_TYPE, GncXmlHelper.ATTR_VALUE_GUID);
- bookIdNode.appendChild(mDocument.createTextNode(UUID.randomUUID().toString().replaceAll("-", "")));
+ bookIdNode.appendChild(document.createTextNode(UUID.randomUUID().toString().replaceAll("-", "")));
bookNode.appendChild(bookIdNode);
- Element cmdtyCountData = mDocument.createElement(GncXmlHelper.TAG_COUNT_DATA);
+ Element cmdtyCountData = document.createElement(GncXmlHelper.TAG_COUNT_DATA);
cmdtyCountData.setAttribute(GncXmlHelper.ATTR_KEY_CD_TYPE, "commodity");
- cmdtyCountData.appendChild(mDocument.createTextNode(String.valueOf(mAccountsDbAdapter.getCurrencies().size())));
+ cmdtyCountData.appendChild(document.createTextNode(String.valueOf(mAccountsDbAdapter.getCurrencies().size())));
bookNode.appendChild(cmdtyCountData);
- Element accountCountNode = mDocument.createElement(GncXmlHelper.TAG_COUNT_DATA);
+ Element accountCountNode = document.createElement(GncXmlHelper.TAG_COUNT_DATA);
accountCountNode.setAttribute(GncXmlHelper.ATTR_KEY_CD_TYPE, "account");
int accountCount = mAccountsDbAdapter.getTotalAccountCount();
- accountCountNode.appendChild(mDocument.createTextNode(String.valueOf(accountCount)));
+ accountCountNode.appendChild(document.createTextNode(String.valueOf(accountCount)));
bookNode.appendChild(accountCountNode);
- Element transactionCountNode = mDocument.createElement(GncXmlHelper.TAG_COUNT_DATA);
+ Element transactionCountNode = document.createElement(GncXmlHelper.TAG_COUNT_DATA);
transactionCountNode.setAttribute(GncXmlHelper.ATTR_KEY_CD_TYPE, "transaction");
int transactionCount = mTransactionsDbAdapter.getTotalTransactionsCount();
- transactionCountNode.appendChild(mDocument.createTextNode(String.valueOf(transactionCount)));
+ transactionCountNode.appendChild(document.createTextNode(String.valueOf(transactionCount)));
bookNode.appendChild(transactionCountNode);
String rootAccountUID = mAccountsDbAdapter.getGnuCashRootAccountUID();
Account rootAccount = mAccountsDbAdapter.getAccount(rootAccountUID);
if (rootAccount != null){
- rootAccount.toGncXml(mDocument, bookNode);
+ rootAccount.toGncXml(document, bookNode);
}
Cursor accountsCursor = mAccountsDbAdapter.fetchAllRecordsOrderedByFullName();
@@ -138,7 +475,7 @@ private void generateGncXml() throws ParserConfigurationException {
while (accountsCursor.moveToNext()){
long id = accountsCursor.getLong(accountsCursor.getColumnIndexOrThrow(DatabaseSchema.AccountEntry._ID));
Account account = mAccountsDbAdapter.getAccount(id);
- account.toGncXml(mDocument, bookNode);
+ account.toGncXml(document, bookNode);
}
accountsCursor.close();
}
@@ -148,28 +485,24 @@ private void generateGncXml() throws ParserConfigurationException {
if (transactionsCursor != null){
while (transactionsCursor.moveToNext()){
Transaction transaction = mTransactionsDbAdapter.buildTransactionInstance(transactionsCursor);
- transaction.toGncXml(mDocument, bookNode);
+ transaction.toGncXml(document, bookNode);
}
transactionsCursor.close();
}
- mDocument.appendChild(rootElement);
+ document.appendChild(rootElement);
mAccountsDbAdapter.close();
mTransactionsDbAdapter.close();
- }
- @Override
- public String generateExport() throws ExporterException{
StringWriter stringWriter = new StringWriter();
try {
- generateGncXml();
TransformerFactory transformerFactory = TransformerFactory.newInstance();
Transformer transformer = transformerFactory.newTransformer();
transformer.setOutputProperty("{http://xml.apache.org/xslt}indent-amount", "2");
transformer.setOutputProperty(OutputKeys.INDENT, "yes");
- DOMSource source = new DOMSource(mDocument);
+ DOMSource source = new DOMSource(document);
StreamResult result = new StreamResult(stringWriter);
transformer.transform(source, result);
@@ -180,21 +513,4 @@ public String generateExport() throws ExporterException{
}
return stringWriter.toString();
}
-
- /**
- * Creates a backup of current database contents to the default backup location
- */
- public static void createBackup(){
- ExportParams params = new ExportParams(ExportFormat.GNC_XML);
- try {
- FileWriter fileWriter = new FileWriter(Exporter.createBackupFile());
- BufferedWriter bufferedWriter = new BufferedWriter(fileWriter);
- bufferedWriter.write(new GncXmlExporter(params).generateExport());
- bufferedWriter.flush();
-
- } catch (IOException e) {
- e.printStackTrace();
- Log.e("GncXmlExporter", "Error creating backup", e);
- }
- }
}
diff --git a/app/src/org/gnucash/android/export/xml/GncXmlHelper.java b/app/src/org/gnucash/android/export/xml/GncXmlHelper.java
index a52d9fd71..298fa0856 100644
--- a/app/src/org/gnucash/android/export/xml/GncXmlHelper.java
+++ b/app/src/org/gnucash/android/export/xml/GncXmlHelper.java
@@ -1,3 +1,20 @@
+/*
+ * Copyright (c) 2014 Ngewi Fet
+ * Copyright (c) 2014 Yongxin Wang
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
package org.gnucash.android.export.xml;
import org.gnucash.android.model.Money;
@@ -12,9 +29,10 @@
import java.util.Date;
/**
- * Date: 17.07.2014
+ * Collection of helper tags and methods for Gnc XML export
*
- * @author Ngewi
+ * @author Ngewi Fet
+ * @author Yongxin Wang
*/
public abstract class GncXmlHelper {
public static final String TAG_PREFIX = "gnc:";
@@ -81,6 +99,7 @@ public abstract class GncXmlHelper {
public static final String KEY_FAVORITE = "favorite";
public static final String KEY_NOTES = "notes";
public static final String KEY_DEFAULT_TRANSFER_ACCOUNT = "default_transfer_account";
+ public static final String KEY_EXPORTED = "exported";
/**
* Formats dates for the GnuCash XML format
diff --git a/app/src/org/gnucash/android/importer/GncXmlHandler.java b/app/src/org/gnucash/android/importer/GncXmlHandler.java
index af8d8bde9..7f52a96e1 100644
--- a/app/src/org/gnucash/android/importer/GncXmlHandler.java
+++ b/app/src/org/gnucash/android/importer/GncXmlHandler.java
@@ -1,5 +1,6 @@
/*
* Copyright (c) 2013 - 2014 Ngewi Fet
+ * Copyright (c) 2014 Yongxin Wang
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -30,13 +31,18 @@
import java.text.ParseException;
import java.util.Currency;
+import java.util.HashMap;
+import java.util.Stack;
import java.util.regex.Pattern;
+import java.util.List;
+import java.util.ArrayList;
/**
* Handler for parsing the GnuCash XML file.
* The discovered accounts and transactions are automatically added to the database
*
* @author Ngewi Fet
+ * @author Yongxin Wang
*/
public class GncXmlHandler extends DefaultHandler {
@@ -65,11 +71,21 @@ public class GncXmlHandler extends DefaultHandler {
*/
Account mAccount;
+ /**
+ * All the accounts found in a file to be imported, used for bulk import mode
+ */
+ List mAccountList;
+
/**
* Transaction instance which will be built for each transaction found
*/
Transaction mTransaction;
+ /**
+ * All the transaction instances found in a file to be inserted, used in bulk mode
+ */
+ List mTransactionList;
+
/**
* Accumulate attributes of splits found in this object
*/
@@ -80,6 +96,11 @@ public class GncXmlHandler extends DefaultHandler {
*/
String mIgnoreElement = null;
+ /**
+ * Showing whether we are in bulk import mode
+ */
+ boolean mBulk = false;
+
boolean mInColorSlot = false;
boolean mInPlaceHolderSlot = false;
boolean mInFavoriteSlot = false;
@@ -87,15 +108,29 @@ public class GncXmlHandler extends DefaultHandler {
boolean mIsDatePosted = false;
boolean mIsNote = false;
boolean mInDefaultTransferAccount = false;
+ boolean mInExported = false;
private Context mContext;
private TransactionsDbAdapter mTransactionsDbAdapter;
public GncXmlHandler(Context context) {
+ init(context, false);
+ }
+
+ public GncXmlHandler(Context context, boolean bulk) {
+ init(context, bulk);
+ }
+
+ private void init(Context context, boolean bulk) {
mContext = context;
mAccountsDbAdapter = new AccountsDbAdapter(mContext);
mTransactionsDbAdapter = new TransactionsDbAdapter(mContext);
mContent = new StringBuilder();
+ mBulk = bulk;
+ if (bulk) {
+ mAccountList = new ArrayList();
+ mTransactionList = new ArrayList();
+ }
}
/**
@@ -113,22 +148,19 @@ public GncXmlHandler(SQLiteDatabase db){
public void startElement(String uri, String localName,
String qualifiedName, Attributes attributes) throws SAXException {
if (qualifiedName.equalsIgnoreCase(GncXmlHelper.TAG_ACCOUNT)) {
- mAccount = new Account(""); //dummy name, will be replaced when we find name tag
+ mAccount = new Account(""); // dummy name, will be replaced when we find name tag
}
-
- if (qualifiedName.equalsIgnoreCase(GncXmlHelper.TAG_TRANSACTION)){
- mTransaction = new Transaction(""); //dummy name will be replaced
+ else if (qualifiedName.equalsIgnoreCase(GncXmlHelper.TAG_TRANSACTION)){
+ mTransaction = new Transaction(""); // dummy name will be replaced
+ mTransaction.setExported(true); // default to exported when import transactions
}
-
- if (qualifiedName.equalsIgnoreCase(GncXmlHelper.TAG_TRN_SPLIT)){
+ else if (qualifiedName.equalsIgnoreCase(GncXmlHelper.TAG_TRN_SPLIT)){
mSplit = new Split(Money.getZeroInstance(),"");
}
-
- if (qualifiedName.equalsIgnoreCase(GncXmlHelper.TAG_DATE_POSTED)){
+ else if (qualifiedName.equalsIgnoreCase(GncXmlHelper.TAG_DATE_POSTED)){
mIsDatePosted = true;
}
-
- if (qualifiedName.equalsIgnoreCase(GncXmlHelper.TAG_TEMPLATE_TRANSACTION)) {
+ else if (qualifiedName.equalsIgnoreCase(GncXmlHelper.TAG_TEMPLATE_TRANSACTION)) {
mIgnoreElement = GncXmlHelper.TAG_TEMPLATE_TRANSACTION;
}
}
@@ -150,120 +182,117 @@ public void endElement(String uri, String localName, String qualifiedName) throw
mAccount.setName(characterString);
mAccount.setFullName(characterString);
}
-
- if (qualifiedName.equalsIgnoreCase(GncXmlHelper.TAG_ACCT_ID)){
+ else if (qualifiedName.equalsIgnoreCase(GncXmlHelper.TAG_ACCT_ID)){
mAccount.setUID(characterString);
}
-
- if (qualifiedName.equalsIgnoreCase(GncXmlHelper.TAG_TYPE)){
+ else if (qualifiedName.equalsIgnoreCase(GncXmlHelper.TAG_TYPE)){
mAccount.setAccountType(AccountType.valueOf(characterString));
}
-
- if (qualifiedName.equalsIgnoreCase(GncXmlHelper.TAG_COMMODITY_SPACE)){
+ else if (qualifiedName.equalsIgnoreCase(GncXmlHelper.TAG_COMMODITY_SPACE)){
if (characterString.equalsIgnoreCase("ISO4217")){
mISO4217Currency = true;
}
}
-
- if (qualifiedName.equalsIgnoreCase(GncXmlHelper.TAG_COMMODITY_ID)){
+ else if (qualifiedName.equalsIgnoreCase(GncXmlHelper.TAG_COMMODITY_ID)){
String currencyCode = mISO4217Currency ? characterString : NO_CURRENCY_CODE;
if (mAccount != null){
mAccount.setCurrency(Currency.getInstance(currencyCode));
}
-
if (mTransaction != null){
mTransaction.setCurrencyCode(currencyCode);
}
}
-
- if (qualifiedName.equalsIgnoreCase(GncXmlHelper.TAG_PARENT_UID)){
+ else if (qualifiedName.equalsIgnoreCase(GncXmlHelper.TAG_PARENT_UID)){
mAccount.setParentUID(characterString);
}
-
- if (qualifiedName.equalsIgnoreCase(GncXmlHelper.TAG_ACCOUNT)){
- Log.d(LOG_TAG, "Saving account...");
- mAccountsDbAdapter.addAccount(mAccount);
-
+ else if (qualifiedName.equalsIgnoreCase(GncXmlHelper.TAG_ACCOUNT)){
+ if (mBulk) {
+ mAccountList.add(mAccount);
+ }
+ else {
+ Log.d(LOG_TAG, "Saving account...");
+ mAccountsDbAdapter.addAccount(mAccount);
+ }
mAccount = null;
//reset ISO 4217 flag for next account
mISO4217Currency = false;
}
-
- if (qualifiedName.equalsIgnoreCase(GncXmlHelper.TAG_SLOT_KEY)){
+ else if (qualifiedName.equalsIgnoreCase(GncXmlHelper.TAG_SLOT_KEY)){
if (characterString.equals(GncXmlHelper.KEY_PLACEHOLDER)){
mInPlaceHolderSlot = true;
}
- if (characterString.equals(GncXmlHelper.KEY_COLOR)){
+ else if (characterString.equals(GncXmlHelper.KEY_COLOR)){
mInColorSlot = true;
}
-
- if (characterString.equals(GncXmlHelper.KEY_FAVORITE)){
+ else if (characterString.equals(GncXmlHelper.KEY_FAVORITE)){
mInFavoriteSlot = true;
}
-
- if (characterString.equals(GncXmlHelper.KEY_NOTES)){
+ else if (characterString.equals(GncXmlHelper.KEY_NOTES)){
mIsNote = true;
}
-
- if (characterString.equals(GncXmlHelper.KEY_DEFAULT_TRANSFER_ACCOUNT)){
+ else if (characterString.equals(GncXmlHelper.KEY_DEFAULT_TRANSFER_ACCOUNT)){
mInDefaultTransferAccount = true;
}
+ else if (characterString.equals(GncXmlHelper.KEY_EXPORTED)){
+ mInExported = true;
+ }
}
-
- if (qualifiedName.equalsIgnoreCase(GncXmlHelper.TAG_SLOT_VALUE)){
+ else if (qualifiedName.equalsIgnoreCase(GncXmlHelper.TAG_SLOT_VALUE)){
if (mInPlaceHolderSlot){
Log.v(LOG_TAG, "Setting account placeholder flag");
mAccount.setPlaceHolderFlag(Boolean.parseBoolean(characterString));
mInPlaceHolderSlot = false;
}
-
- if (mInColorSlot){
+ else if (mInColorSlot){
String color = characterString.trim();
//Gnucash exports the account color in format #rrrgggbbb, but we need only #rrggbb.
//so we trim the last digit in each block, doesn't affect the color much
- if (!Pattern.matches(Account.COLOR_HEX_REGEX, color))
- color = "#" + color.replaceAll(".(.)?", "$1").replace("null", "");
- try {
- if (mAccount != null)
- mAccount.setColorCode(color);
- } catch (IllegalArgumentException ex){
- //sometimes the color entry in the account file is "Not set" instead of just blank. So catch!
- Log.i(LOG_TAG, "Invalid color code '" + color + "' for account " + mAccount.getName());
- ex.printStackTrace();
+ if (!color.equals("Not Set")) {
+ // avoid known exception, printStackTrace is very time consuming
+ if (!Pattern.matches(Account.COLOR_HEX_REGEX, color))
+ color = "#" + color.replaceAll(".(.)?", "$1").replace("null", "");
+ try {
+ if (mAccount != null)
+ mAccount.setColorCode(color);
+ } catch (IllegalArgumentException ex) {
+ //sometimes the color entry in the account file is "Not set" instead of just blank. So catch!
+ Log.i(LOG_TAG, "Invalid color code '" + color + "' for account " + mAccount.getName());
+ ex.printStackTrace();
+ }
}
-
mInColorSlot = false;
}
-
- if (mInFavoriteSlot){
+ else if (mInFavoriteSlot){
mAccount.setFavorite(Boolean.parseBoolean(characterString));
mInFavoriteSlot = false;
}
-
- if (mIsNote){
+ else if (mIsNote){
if (mTransaction != null){
mTransaction.setNote(characterString);
mIsNote = false;
}
}
-
- if (mInDefaultTransferAccount){
+ else if (mInDefaultTransferAccount){
mAccount.setDefaultTransferAccountUID(characterString);
mInDefaultTransferAccount = false;
}
+ else if (mInExported){
+ if (mTransaction != null) {
+ mTransaction.setExported(Boolean.parseBoolean(characterString));
+ mInExported = false;
+ }
+ }
}
//================ PROCESSING OF TRANSACTION TAGS =====================================
- if (qualifiedName.equalsIgnoreCase(GncXmlHelper.TAG_TRX_ID)){
+ else if (qualifiedName.equalsIgnoreCase(GncXmlHelper.TAG_TRX_ID)){
mTransaction.setUID(characterString);
}
-
- if (qualifiedName.equalsIgnoreCase(GncXmlHelper.TAG_TRN_DESCRIPTION)){
+ else if (qualifiedName.equalsIgnoreCase(GncXmlHelper.TAG_TRN_DESCRIPTION)){
mTransaction.setDescription(characterString);
}
-
- if (qualifiedName.equalsIgnoreCase(GncXmlHelper.TAG_DATE)){
+ else if (qualifiedName.equalsIgnoreCase(GncXmlHelper.TAG_DATE)){
try {
if (mIsDatePosted && mTransaction != null) {
mTransaction.setTime(GncXmlHelper.parseDate(characterString));
@@ -274,38 +303,37 @@ public void endElement(String uri, String localName, String qualifiedName) throw
throw new SAXException("Unable to parse transaction time", e);
}
}
-
- if (qualifiedName.equalsIgnoreCase(GncXmlHelper.TAG_RECURRENCE_PERIOD)){
+ else if (qualifiedName.equalsIgnoreCase(GncXmlHelper.TAG_RECURRENCE_PERIOD)){
mTransaction.setRecurrencePeriod(Long.parseLong(characterString));
}
-
- if (qualifiedName.equalsIgnoreCase(GncXmlHelper.TAG_SPLIT_ID)){
+ else if (qualifiedName.equalsIgnoreCase(GncXmlHelper.TAG_SPLIT_ID)){
mSplit.setUID(characterString);
}
-
- if (qualifiedName.equalsIgnoreCase(GncXmlHelper.TAG_SPLIT_MEMO)){
+ else if (qualifiedName.equalsIgnoreCase(GncXmlHelper.TAG_SPLIT_MEMO)){
mSplit.setMemo(characterString);
}
-
- if (qualifiedName.equalsIgnoreCase(GncXmlHelper.TAG_SPLIT_VALUE)){
+ else if (qualifiedName.equalsIgnoreCase(GncXmlHelper.TAG_SPLIT_VALUE)){
Money amount = new Money(GncXmlHelper.parseMoney(characterString), mTransaction.getCurrency());
mSplit.setType(amount.isNegative() ? TransactionType.CREDIT : TransactionType.DEBIT);
mSplit.setAmount(amount.absolute());
}
-
- if (qualifiedName.equalsIgnoreCase(GncXmlHelper.TAG_SPLIT_ACCOUNT)){
+ else if (qualifiedName.equalsIgnoreCase(GncXmlHelper.TAG_SPLIT_ACCOUNT)){
mSplit.setAccountUID(characterString);
}
-
- if (qualifiedName.equals(GncXmlHelper.TAG_TRN_SPLIT)){
+ else if (qualifiedName.equals(GncXmlHelper.TAG_TRN_SPLIT)){
mTransaction.addSplit(mSplit);
}
-
- if (qualifiedName.equalsIgnoreCase(GncXmlHelper.TAG_TRANSACTION)){
- if (mTransaction.getRecurrencePeriod() > 0){ //TODO: Fix this when scheduled actions are expanded
- mTransactionsDbAdapter.scheduleTransaction(mTransaction);
- } else {
- mTransactionsDbAdapter.addTransaction(mTransaction);
+ else if (qualifiedName.equalsIgnoreCase(GncXmlHelper.TAG_TRANSACTION)){
+ if (mBulk) {
+ mTransactionList.add(mTransaction);
+ }
+ else {
+ if (mTransaction.getRecurrencePeriod() > 0) { //TODO: Fix this when scheduled actions are expanded
+ mTransactionsDbAdapter.scheduleTransaction(mTransaction);
+ mTransactionsDbAdapter.addTransaction(mTransaction);
+ } else {
+ mTransactionsDbAdapter.addTransaction(mTransaction);
+ }
}
mTransaction = null;
}
@@ -322,8 +350,59 @@ public void characters(char[] chars, int start, int length) throws SAXException
@Override
public void endDocument() throws SAXException {
super.endDocument();
+ if (mBulk) {
+ HashMap map = new HashMap(mAccountList.size());
+ HashMap mapFullName = new HashMap(mAccountList.size());
+ for(Account account:mAccountList) {
+ map.put(account.getUID(), account);
+ mapFullName.put(account.getUID(), null);
+ }
+ java.util.Stack stack = new Stack();
+ for (Account account:mAccountList){
+ if (mapFullName.get(account.getUID()) != null) {
+ continue;
+ }
+ stack.push(account);
+ String parentAccountFullName;
+ while (!stack.isEmpty()) {
+ Account acc = stack.peek();
+ if (acc.getAccountType() == AccountType.ROOT) {
+ // append blank to Root Account, ensure it always sorts first
+ mapFullName.put(acc.getUID(), " " + acc.getName());
+ stack.pop();
+ continue;
+ }
+ String parentUID = acc.getParentUID();
+ Account parentAccount = map.get(parentUID);
+ if (parentAccount.getAccountType() == AccountType.ROOT) {
+ // top level account, full name is the same as its name
+ mapFullName.put(acc.getUID(), acc.getName());
+ stack.pop();
+ continue;
+ }
+ parentAccountFullName = mapFullName.get(parentUID);
+ if (parentAccountFullName == null) {
+ // non-top-level account, parent full name still unknown
+ stack.push(parentAccount);
+ continue;
+ }
+ mapFullName.put(acc.getUID(), parentAccountFullName +
+ AccountsDbAdapter.ACCOUNT_NAME_SEPARATOR + acc.getName());
+ stack.pop();
+ }
+ }
+ for (Account account:mAccountList){
+ account.setFullName(mapFullName.get(account.getUID()));
+ }
+ long startTime = System.nanoTime();
+ long nAccounts = mAccountsDbAdapter.bulkAddAccounts(mAccountList);
+ Log.d("Handler:", String.format("%d accounts inserted", nAccounts));
+ long nTransactions = mTransactionsDbAdapter.bulkAddTransactions(mTransactionList);
+ Log.d("Handler:", String.format("%d transactions inserted", nTransactions));
+ long endTime = System.nanoTime();
+ Log.d("Handler:", String.format(" bulk insert time: %d", endTime - startTime));
+ }
mAccountsDbAdapter.close();
mTransactionsDbAdapter.close();
}
-
}
diff --git a/app/src/org/gnucash/android/importer/GncXmlImporter.java b/app/src/org/gnucash/android/importer/GncXmlImporter.java
index 068322d3b..e8de150db 100644
--- a/app/src/org/gnucash/android/importer/GncXmlImporter.java
+++ b/app/src/org/gnucash/android/importer/GncXmlImporter.java
@@ -1,5 +1,6 @@
/*
* Copyright (c) 2014 Ngewi Fet
+ * Copyright (c) 2014 Yongxin Wang
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -17,6 +18,7 @@
import android.content.Context;
import android.database.sqlite.SQLiteDatabase;
+import android.util.Log;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;
import org.xml.sax.XMLReader;
@@ -25,8 +27,10 @@
import javax.xml.parsers.SAXParser;
import javax.xml.parsers.SAXParserFactory;
import java.io.BufferedInputStream;
+import java.io.PushbackInputStream;
import java.io.IOException;
import java.io.InputStream;
+import java.util.zip.GZIPInputStream;
/**
* Importer for Gnucash XML files and GNCA (GnuCash Android) XML files
@@ -66,12 +70,23 @@ public static void parse(Context context, InputStream gncXmlInputStream) throws
SAXParser sp = spf.newSAXParser();
XMLReader xr = sp.getXMLReader();
- BufferedInputStream bos = new BufferedInputStream(gncXmlInputStream);
+ BufferedInputStream bos;
+ PushbackInputStream pb = new PushbackInputStream( gncXmlInputStream, 2 ); //we need a pushbackstream to look ahead
+ byte [] signature = new byte[2];
+ pb.read( signature ); //read the signature
+ pb.unread( signature ); //push back the signature to the stream
+ if( signature[ 0 ] == (byte) 0x1f && signature[ 1 ] == (byte) 0x8b ) //check if matches standard gzip magic number
+ bos = new BufferedInputStream(new GZIPInputStream(pb));
+ else
+ bos = new BufferedInputStream(pb);
//TODO: Set an error handler which can log errors
- GncXmlHandler handler = new GncXmlHandler(context);
+ GncXmlHandler handler = new GncXmlHandler(context, true);
xr.setContentHandler(handler);
+ long startTime = System.nanoTime();
xr.parse(new InputSource(bos));
+ long endTime = System.nanoTime();
+ Log.d("Import", String.format("%d ns spent on importing the file", endTime-startTime));
}
}
diff --git a/app/src/org/gnucash/android/importer/ImportAsyncTask.java b/app/src/org/gnucash/android/importer/ImportAsyncTask.java
index a19cb0353..e24f376f9 100644
--- a/app/src/org/gnucash/android/importer/ImportAsyncTask.java
+++ b/app/src/org/gnucash/android/importer/ImportAsyncTask.java
@@ -12,7 +12,8 @@
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
- */package org.gnucash.android.importer;
+ */
+package org.gnucash.android.importer;
import android.app.Activity;
import android.app.ProgressDialog;
@@ -23,6 +24,7 @@
import android.widget.Toast;
import org.gnucash.android.R;
import org.gnucash.android.ui.account.AccountsActivity;
+import org.gnucash.android.ui.util.TaskDelegate;
import java.io.InputStream;
@@ -32,12 +34,18 @@
*/
public class ImportAsyncTask extends AsyncTask {
private final Activity context;
+ private TaskDelegate mDelegate;
private ProgressDialog progressDialog;
public ImportAsyncTask(Activity context){
this.context = context;
}
+ public ImportAsyncTask(Activity context, TaskDelegate delegate){
+ this.context = context;
+ this.mDelegate = delegate;
+ }
+
@Override
protected void onPreExecute() {
super.onPreExecute();
@@ -45,11 +53,13 @@ protected void onPreExecute() {
progressDialog.setTitle(R.string.title_progress_importing_accounts);
progressDialog.setIndeterminate(true);
progressDialog.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL);
+ progressDialog.show();
if (Build.VERSION.SDK_INT > Build.VERSION_CODES.HONEYCOMB){
+ //these methods must be called after progressDialog.show()
progressDialog.setProgressNumberFormat(null);
progressDialog.setProgressPercentFormat(null);
}
- progressDialog.show();
+
}
@Override
@@ -59,7 +69,7 @@ protected Boolean doInBackground(InputStream... inputStreams) {
} catch (Exception exception){
exception.printStackTrace();
final String err_msg = exception.getLocalizedMessage();
- Log.e(ImportAsyncTask.class.getName(), exception.getMessage());
+ Log.e(ImportAsyncTask.class.getName(), "" + exception.getMessage());
context.runOnUiThread(new Runnable() {
@Override
public void run() {
@@ -76,6 +86,9 @@ public void run() {
@Override
protected void onPostExecute(Boolean importSuccess) {
+ if (mDelegate != null)
+ mDelegate.onTaskComplete();
+
if (progressDialog != null && progressDialog.isShowing())
progressDialog.dismiss();
diff --git a/app/src/org/gnucash/android/model/Account.java b/app/src/org/gnucash/android/model/Account.java
index 319667e84..32244e8a4 100644
--- a/app/src/org/gnucash/android/model/Account.java
+++ b/app/src/org/gnucash/android/model/Account.java
@@ -16,10 +16,7 @@
package org.gnucash.android.model;
-import org.gnucash.android.app.GnuCashApplication;
-import org.gnucash.android.db.AccountsDbAdapter;
import org.gnucash.android.export.ofx.OfxHelper;
-import org.gnucash.android.export.qif.QifHelper;
import org.gnucash.android.export.xml.GncXmlHelper;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
@@ -542,43 +539,11 @@ public void toOfx(Document doc, Element parent, boolean exportAllTransactions){
}
- /**
- * Exports the account info and transactions in the QIF format
- * @param exportAllTransactions Flag to determine whether to export all transactions, or only new transactions since last export
- * @param exportedTransactionUIDs List of unique IDs of transactions which have already been exported (in the current session). Used to avoid duplicating splits
- * @return QIF representation of the account information
- */
- public String toQIF(boolean exportAllTransactions, List exportedTransactionUIDs) {
- StringBuilder accountQIFBuilder = new StringBuilder();
- final String newLine = "\n";
-
- AccountsDbAdapter accountsDbAdapter = new AccountsDbAdapter(GnuCashApplication.getAppContext());
- String fullyQualifiedAccountName = accountsDbAdapter.getFullyQualifiedAccountName(mUID);
- accountsDbAdapter.close();
-
- accountQIFBuilder.append(QifHelper.ACCOUNT_HEADER).append(newLine);
- accountQIFBuilder.append(QifHelper.ACCOUNT_NAME_PREFIX).append(fullyQualifiedAccountName).append(newLine);
- accountQIFBuilder.append(QifHelper.ENTRY_TERMINATOR).append(newLine);
-
- String header = QifHelper.getQifHeader(mAccountType);
- accountQIFBuilder.append(header + newLine);
-
- for (Transaction transaction : mTransactionsList) {
- if (!exportAllTransactions && transaction.isExported())
- continue;
- if (exportedTransactionUIDs.contains(transaction.getUID()))
- continue;
-
- accountQIFBuilder.append(transaction.toQIF(mUID) + newLine);
- exportedTransactionUIDs.add(transaction.getUID());
- }
- return accountQIFBuilder.toString();
- }
-
/**
* Method which generates the GnuCash XML DOM for this account
* @param doc {@link org.w3c.dom.Document} for creating nodes
* @param rootNode {@link org.w3c.dom.Element} node to which to attach the XML
+ * @deprecated Use the {@link org.gnucash.android.export.xml.GncXmlExporter} to generate XML
*/
public void toGncXml(Document doc, Element rootNode) {
Element nameNode = doc.createElement(GncXmlHelper.TAG_NAME);
@@ -641,5 +606,4 @@ public void toGncXml(Document doc, Element rootNode) {
rootNode.appendChild(accountNode);
}
-
}
diff --git a/app/src/org/gnucash/android/model/Split.java b/app/src/org/gnucash/android/model/Split.java
index 419618486..9c99fb8e6 100644
--- a/app/src/org/gnucash/android/model/Split.java
+++ b/app/src/org/gnucash/android/model/Split.java
@@ -171,28 +171,11 @@ public String toCsv(){
return splitString;
}
- /**
- * Parses a split which is in the format ";;;;".
- * The split input string is the same produced by the {@link Split#toCsv()} method
- *
- * @param splitString String containing formatted split
- * @return Split instance parsed from the string
- */
- public static Split parseSplit(String splitString) {
- String[] tokens = splitString.split(";");
- Money amount = new Money(tokens[0], tokens[1]);
- Split split = new Split(amount, tokens[2]);
- split.setType(TransactionType.valueOf(tokens[3]));
- if (tokens.length == 5){
- split.setMemo(tokens[4]);
- }
- return split;
- }
-
/**
* Creates a GnuCash XML representation of this split
* @param doc XML {@link org.w3c.dom.Document} for creating the nodes
* @param rootNode Parent node to append the split XML to
+ * @deprecated Use the {@link org.gnucash.android.export.xml.GncXmlExporter} to generate XML
*/
public void toGncXml(Document doc, Element rootNode) {
Element idNode = doc.createElement(GncXmlHelper.TAG_SPLIT_ID);
@@ -226,4 +209,22 @@ public void toGncXml(Document doc, Element rootNode) {
rootNode.appendChild(splitNode);
}
+
+ /**
+ * Parses a split which is in the format ";;;;".
+ * The split input string is the same produced by the {@link Split#toCsv()} method
+ *
+ * @param splitString String containing formatted split
+ * @return Split instance parsed from the string
+ */
+ public static Split parseSplit(String splitString) {
+ String[] tokens = splitString.split(";");
+ Money amount = new Money(tokens[0], tokens[1]);
+ Split split = new Split(amount, tokens[2]);
+ split.setType(TransactionType.valueOf(tokens[3]));
+ if (tokens.length == 5){
+ split.setMemo(tokens[4]);
+ }
+ return split;
+ }
}
diff --git a/app/src/org/gnucash/android/model/Transaction.java b/app/src/org/gnucash/android/model/Transaction.java
index d76982eee..ecdfe1ae8 100644
--- a/app/src/org/gnucash/android/model/Transaction.java
+++ b/app/src/org/gnucash/android/model/Transaction.java
@@ -20,13 +20,11 @@
import org.gnucash.android.app.GnuCashApplication;
import org.gnucash.android.db.AccountsDbAdapter;
import org.gnucash.android.export.ofx.OfxHelper;
-import org.gnucash.android.export.qif.QifHelper;
import org.gnucash.android.export.xml.GncXmlHelper;
import org.gnucash.android.model.Account.OfxAccountType;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
-import java.math.BigDecimal;
import java.util.*;
/**
@@ -515,96 +513,11 @@ public Element toOFX(Document doc, String accountUID){
}
/**
- * Builds a QIF entry representing this transaction
- * @return String QIF representation of this transaction
- */
- public String toQIF(String accountUID){
- final String newLine = "\n";
- AccountsDbAdapter accountsDbAdapter = new AccountsDbAdapter(GnuCashApplication.getAppContext());
- //all transactions are double transactions
- String imbalanceAccountName = AccountsDbAdapter.getImbalanceAccountName(Currency.getInstance(mCurrencyCode));
-
- StringBuilder transactionQIFBuilder = new StringBuilder();
-
- transactionQIFBuilder.append(QifHelper.DATE_PREFIX).append(QifHelper.formatDate(mTimestamp)).append(newLine);
- transactionQIFBuilder.append(QifHelper.MEMO_PREFIX).append(mDescription).append(newLine);
-
- List processedSplitUIDs = new ArrayList();
- final List splitList = getSplits();
- if (splitList.size() > 2){
- for (Split split : splitList) {
- if (split.getAccountUID().equals(accountUID)){
- Money amount = split.getAmount();
-
- if (split.getType() == TransactionType.CREDIT)
- amount = amount.negate();
-
- transactionQIFBuilder.append(QifHelper.AMOUNT_PREFIX).append(amount.toPlainString())
- .append(newLine);
- processedSplitUIDs.add(split.getUID());
- break;
- }
- }
- }
- for (Split split : splitList) {
- if (split.getAccountUID().equals(accountUID) || processedSplitUIDs.contains(split.getUID()))
- continue;
-
- String splitAccountName = accountsDbAdapter.getFullyQualifiedAccountName(split.getAccountUID());
- transactionQIFBuilder.append(QifHelper.SPLIT_CATEGORY_PREFIX).append(splitAccountName).append(newLine);
-
- String memo = split.getMemo();
- if (memo != null && memo.length() > 0) {
- transactionQIFBuilder.append(QifHelper.SPLIT_MEMO_PREFIX).append(memo).append(newLine);
- }
- Money amount = split.getAmount();
- if (split.getAccountUID().equals(accountUID)) {
- if (split.getType() == TransactionType.CREDIT)
- amount = amount.negate();
- } else {
- if (split.getType() == TransactionType.DEBIT)
- amount = amount.negate();
- }
- transactionQIFBuilder.append(QifHelper.SPLIT_AMOUNT_PREFIX).append(amount.asString()).append(newLine);
- }
- Money imbalanceAmount = getImbalance();
- if (imbalanceAmount.asBigDecimal().compareTo(new BigDecimal(0)) != 0){
- AccountType accountType = accountsDbAdapter.getAccountType(accountUID);
- TransactionType imbalanceType = Transaction.getTypeForBalance(accountType,imbalanceAmount.isNegative());
- imbalanceAmount = imbalanceAmount.absolute();
- if (imbalanceType == TransactionType.DEBIT){
- imbalanceAmount = imbalanceAmount.negate();
- }
- transactionQIFBuilder.append(QifHelper.SPLIT_CATEGORY_PREFIX).append(imbalanceAccountName).append(newLine);
- transactionQIFBuilder.append(QifHelper.SPLIT_AMOUNT_PREFIX).append(imbalanceAmount.asString()).append(newLine);
- }
-
- transactionQIFBuilder.append(QifHelper.ENTRY_TERMINATOR).append(newLine);
-
- accountsDbAdapter.close();
- return transactionQIFBuilder.toString();
- }
-
- /**
- * Creates an Intent with arguments from the transaction.
- * This intent can be broadcast to create a new transaction
- * @param transaction Transaction used to create intent
- * @return Intent with transaction details as extras
+ * Generate the GncXML for the transaction and append to the DOM document
+ * @param doc XML document to which transaction should be added
+ * @param rootElement Parent node for the XML
+ * @deprecated Use the {@link org.gnucash.android.export.xml.GncXmlExporter} to generate XML
*/
- public static Intent createIntent(Transaction transaction){
- Intent intent = new Intent(Intent.ACTION_INSERT);
- intent.setType(Transaction.MIME_TYPE);
- intent.putExtra(Intent.EXTRA_TITLE, transaction.getDescription());
- intent.putExtra(Intent.EXTRA_TEXT, transaction.getNote());
- intent.putExtra(Account.EXTRA_CURRENCY_CODE, transaction.getCurrencyCode());
- StringBuilder stringBuilder = new StringBuilder();
- for (Split split : transaction.getSplits()) {
- stringBuilder.append(split.toCsv()).append("\n");
- }
- intent.putExtra(Transaction.EXTRA_SPLITS, stringBuilder.toString());
- return intent;
- }
-
public void toGncXml(Document doc, Element rootElement) {
Element idNode = doc.createElement(GncXmlHelper.TAG_TRX_ID);
idNode.setAttribute(GncXmlHelper.ATTR_KEY_TYPE, GncXmlHelper.ATTR_VALUE_GUID);
@@ -663,4 +576,24 @@ public void toGncXml(Document doc, Element rootElement) {
rootElement.appendChild(transactionNode);
}
+
+ /**
+ * Creates an Intent with arguments from the transaction.
+ * This intent can be broadcast to create a new transaction
+ * @param transaction Transaction used to create intent
+ * @return Intent with transaction details as extras
+ */
+ public static Intent createIntent(Transaction transaction){
+ Intent intent = new Intent(Intent.ACTION_INSERT);
+ intent.setType(Transaction.MIME_TYPE);
+ intent.putExtra(Intent.EXTRA_TITLE, transaction.getDescription());
+ intent.putExtra(Intent.EXTRA_TEXT, transaction.getNote());
+ intent.putExtra(Account.EXTRA_CURRENCY_CODE, transaction.getCurrencyCode());
+ StringBuilder stringBuilder = new StringBuilder();
+ for (Split split : transaction.getSplits()) {
+ stringBuilder.append(split.toCsv()).append("\n");
+ }
+ intent.putExtra(Transaction.EXTRA_SPLITS, stringBuilder.toString());
+ return intent;
+ }
}
diff --git a/app/src/org/gnucash/android/receivers/TransactionAppWidgetProvider.java b/app/src/org/gnucash/android/receivers/TransactionAppWidgetProvider.java
index 52435d7ec..ee62139bb 100644
--- a/app/src/org/gnucash/android/receivers/TransactionAppWidgetProvider.java
+++ b/app/src/org/gnucash/android/receivers/TransactionAppWidgetProvider.java
@@ -42,15 +42,14 @@ public void onUpdate(Context context, AppWidgetManager appWidgetManager,
// Perform this loop procedure for each App Widget that belongs to this provider
for (int i=0; i
+ * Copyright (c) 2014 Yongxin Wang
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -28,6 +29,7 @@
import android.preference.PreferenceManager;
import android.support.v4.app.FragmentManager;
import android.support.v4.widget.SimpleCursorAdapter;
+import android.text.TextUtils;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
@@ -50,14 +52,12 @@
import org.gnucash.android.ui.colorpicker.ColorSquare;
import org.gnucash.android.util.QualifiedAccountNameCursorAdapter;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Currency;
-import java.util.List;
+import java.util.*;
/**
* Fragment used for creating and editing accounts
* @author Ngewi Fet
+ * @author Yongxin Wang
*/
public class AccountFormFragment extends SherlockFragment {
@@ -92,30 +92,45 @@ public class AccountFormFragment extends SherlockFragment {
* List of all currency codes (ISO 4217) supported by the app
*/
private List mCurrencyCodes;
-
- /**
- * Record ID of the account which was selected
- * This is used if we are editing an account instead of creating one
- */
- private long mSelectedAccountId = 0;
/**
- * Database ID of the parent account
+ * GUID of the parent account
* This value is set to the parent account of the transaction being edited or
* the account in which a new sub-account is being created
*/
- private long mParentAccountId = -1;
+ private String mParentAccountUID = null;
+
+ /**
+ * Account ID of the root account
+ */
+ private long mRootAccountId = -1;
+
+ /**
+ * Account UID of the root account
+ */
+ private String mRootAccountUID = null;
/**
* Reference to account object which will be created at end of dialog
*/
private Account mAccount = null;
+ /**
+ * Unique ID string of account being edited
+ */
+ private String mAccountUID = null;
+
/**
* Cursor which will hold set of eligible parent accounts
*/
private Cursor mParentAccountCursor;
+ /**
+ * List of all descendant Account UIDs, if we are modifying an account
+ * null if creating a new account
+ */
+ private List mDescendantAccountUIDs;
+
/**
* SimpleCursorAdapter for the parent account spinner
* @see QualifiedAccountNameCursorAdapter
@@ -182,6 +197,7 @@ public void onColorSelected(int color) {
}
};
+
/**
* Default constructor
* Required, else the app crashes on screen rotation
@@ -232,7 +248,7 @@ public View onCreateView(LayoutInflater inflater, ViewGroup container,
@Override
public void onItemSelected(AdapterView> parentView, View selectedItemView, int position, long id) {
loadParentAccountList(getSelectedAccountType());
- setParentAccountSelection(mParentAccountId);
+ setParentAccountSelection(mAccountsDbAdapter.getID(mParentAccountUID));
}
@Override
@@ -292,11 +308,14 @@ public void onActivityCreated(Bundle savedInstanceState) {
currencyArrayAdapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
mCurrencySpinner.setAdapter(currencyArrayAdapter);
- mSelectedAccountId = getArguments().getLong(UxArgument.SELECTED_ACCOUNT_ID);
- if (mSelectedAccountId > 0) {
- mAccount = mAccountsDbAdapter.getAccount(mSelectedAccountId);
+ mAccountUID = getArguments().getString(UxArgument.SELECTED_ACCOUNT_UID);
+
+ if (mAccountUID != null) {
+ mAccount = mAccountsDbAdapter.getAccount(mAccountUID);
getSherlockActivity().getSupportActionBar().setTitle(R.string.title_edit_account);
}
+ mRootAccountUID = mAccountsDbAdapter.getGnuCashRootAccountUID();
+ mRootAccountId = mAccountsDbAdapter.getAccountID(mRootAccountUID);
//need to load the cursor adapters for the spinners before initializing the views
loadAccountTypesList();
@@ -322,8 +341,12 @@ private void initializeViewsWithAccount(Account account){
throw new IllegalArgumentException("Account cannot be null");
loadParentAccountList(account.getAccountType());
- mParentAccountId = mAccountsDbAdapter.getAccountID(account.getParentUID());
- setParentAccountSelection(mParentAccountId);
+ mParentAccountUID = account.getParentUID();
+ if (mParentAccountUID == null) {
+ // null parent, set Parent as root
+ mParentAccountUID = mRootAccountUID;
+ }
+ setParentAccountSelection(mAccountsDbAdapter.getID(mParentAccountUID));
String currencyCode = account.getCurrency().getCurrencyCode();
setSelectedCurrency(currencyCode);
@@ -347,23 +370,16 @@ private void initializeViewsWithAccount(Account account){
private void initializeViews(){
setSelectedCurrency(Money.DEFAULT_CURRENCY_CODE);
mColorSquare.setBackgroundColor(Color.LTGRAY);
- mParentAccountId = getArguments().getLong(UxArgument.PARENT_ACCOUNT_ID);
+ mParentAccountUID = getArguments().getString(UxArgument.PARENT_ACCOUNT_UID);
- if (mParentAccountId > 0) {
- AccountType parentAccountType = mAccountsDbAdapter.getAccountType(mParentAccountId);
+ if (mParentAccountUID != null) {
+ AccountType parentAccountType = mAccountsDbAdapter.getAccountType(mParentAccountUID);
setAccountTypeSelection(parentAccountType);
loadParentAccountList(parentAccountType);
- setParentAccountSelection(mParentAccountId);
-// String colorHex = mAccountsDbAdapter.getAccountColorCode(parentAccountId);
-// initializeColorSquarePreview(colorHex);
-// mSelectedColor = colorHex;
+ setParentAccountSelection(mAccountsDbAdapter.getID(mParentAccountUID));
}
- //this must be called after changing account type
- //because changing account type reloads list of eligible parent accounts
-
-
}
/**
@@ -382,7 +398,7 @@ private void initializeColorSquarePreview(String colorHex){
* @param accountType AccountType to be set
*/
private void setAccountTypeSelection(AccountType accountType){
- String[] accountTypeEntries = getResources().getStringArray(R.array.account_type_entries);
+ String[] accountTypeEntries = getResources().getStringArray(R.array.key_account_type_entries);
int accountTypeIndex = Arrays.asList(accountTypeEntries).indexOf(accountType.name());
mAccountTypeSpinner.setSelection(accountTypeIndex);
}
@@ -403,7 +419,7 @@ private void setDefaultTransferAccountInputsVisible(boolean visible) {
* @param currencyCode ISO 4217 currency code to be selected
*/
private void setSelectedCurrency(String currencyCode){
- mCurrencyCodes = Arrays.asList(getResources().getStringArray(R.array.currency_codes));
+ mCurrencyCodes = Arrays.asList(getResources().getStringArray(R.array.key_currency_codes));
if (mCurrencyCodes.contains(currencyCode)){
mCurrencySpinner.setSelection(mCurrencyCodes.indexOf(currencyCode));
}
@@ -414,7 +430,7 @@ private void setSelectedCurrency(String currencyCode){
* @param parentAccountId Record ID of parent account to be selected
*/
private void setParentAccountSelection(long parentAccountId){
- if (parentAccountId > 0){
+ if (parentAccountId > 0 && parentAccountId != mRootAccountId){
mParentCheckBox.setChecked(true);
mParentAccountSpinner.setEnabled(true);
} else
@@ -508,7 +524,7 @@ public boolean onOptionsItemSelected(MenuItem item) {
* Initializes the default transfer account spinner with eligible accounts
*/
private void loadDefaultTransferAccountList(){
- String condition = DatabaseSchema.AccountEntry._ID + " != " + mSelectedAccountId
+ String condition = DatabaseSchema.AccountEntry.COLUMN_UID + " != '" + mAccountUID + "' "
+ " AND " + DatabaseSchema.AccountEntry.COLUMN_PLACEHOLDER + "=0"
+ " AND " + DatabaseSchema.AccountEntry.COLUMN_UID + " != '" + mAccountsDbAdapter.getGnuCashRootAccountUID() + "'";
/*
@@ -537,12 +553,11 @@ private void loadParentAccountList(AccountType accountType){
+ getAllowedParentAccountTypes(accountType) + ") ";
if (mAccount != null){ //if editing an account
- // limit cyclic account hierarchies. Still technically possible since we don't forbid descendant accounts
+ mDescendantAccountUIDs = mAccountsDbAdapter.getDescendantAccountUIDs(mAccount.getUID(), null, null);
+ mDescendantAccountUIDs.add(mAccountUID); //cannot set self as parent
+ // limit cyclic account hierarchies.
condition += " AND (" + DatabaseSchema.AccountEntry.COLUMN_PARENT_ACCOUNT_UID + " IS NULL "
- + " OR " + DatabaseSchema.AccountEntry.COLUMN_PARENT_ACCOUNT_UID + " != '" + mAccount.getUID() + "')"
- + " AND " + DatabaseSchema.AccountEntry._ID + " != " + mSelectedAccountId;
-
- //TODO: Limit all descendants of the account to eliminate the possibility of cyclic hierarchy
+ + " OR " + DatabaseSchema.AccountEntry.COLUMN_UID + " NOT IN ( '" + TextUtils.join("','", mDescendantAccountUIDs) + "' ) )";
}
//if we are reloading the list, close the previous cursor first
@@ -570,16 +585,15 @@ private void loadParentAccountList(AccountType accountType){
* @param type {@link org.gnucash.android.model.AccountType}
* @return String comma separated list of account types
*/
- private String getAllowedParentAccountTypes(AccountType type){
+ private String getAllowedParentAccountTypes(AccountType type) {
- switch (type){
+ switch (type) {
case EQUITY:
return "'" + AccountType.EQUITY.name() + "'";
case INCOME:
case EXPENSE:
- return "'" + AccountType.EXPENSE + "', '" + AccountType.INCOME + "', '"
- + AccountType.ROOT + "'";
+ return "'" + AccountType.EXPENSE + "', '" + AccountType.INCOME + "'";
case CASH:
case BANK:
@@ -592,19 +606,11 @@ private String getAllowedParentAccountTypes(AccountType type){
case STOCK:
case MUTUAL: {
List accountTypeStrings = getAccountTypeStringList();
-
accountTypeStrings.remove(AccountType.EQUITY.name());
accountTypeStrings.remove(AccountType.EXPENSE.name());
accountTypeStrings.remove(AccountType.INCOME.name());
-
- String result = "";
- for (String accountTypeString : accountTypeStrings) {
- result += "'" + accountTypeString + "',";
- }
-
- //remove the last comma
- return result.substring(0, result.length() - 1);
-
+ accountTypeStrings.remove(AccountType.ROOT.name());
+ return "'" + TextUtils.join("','", accountTypeStrings) + "'";
}
case ROOT:
@@ -664,7 +670,7 @@ public void onDestroy() {
mParentAccountCursor.close();
// The mAccountsDbAdapter should only be closed when it is not passed in
// by other Activities.
- if (mReleaseDbAdapter == true && mAccountsDbAdapter != null) {
+ if (mReleaseDbAdapter && mAccountsDbAdapter != null) {
mAccountsDbAdapter.close();
}
if (mDefaultTransferAccountCursorAdapter != null) {
@@ -676,6 +682,8 @@ public void onDestroy() {
* Reads the fields from the account form and saves as a new account
*/
private void saveAccount() {
+ // accounts to update, in case we're updating full names of a sub account tree
+ ArrayList accountsToUpdate = new ArrayList();
if (mAccount == null){
String name = getEnteredName();
if (name == null || name.length() == 0){
@@ -699,13 +707,18 @@ private void saveAccount() {
mAccount.setPlaceHolderFlag(mPlaceholderCheckBox.isChecked());
mAccount.setColorCode(mSelectedColor);
- if (mParentCheckBox.isChecked()){
- long id = mParentAccountSpinner.getSelectedItemId();
- mAccount.setParentUID(mAccountsDbAdapter.getAccountUID(id));
- } else {
+ long newParentAccountId;
+ String newParentAccountUID;
+ if (mParentCheckBox.isChecked()) {
+ newParentAccountId = mParentAccountSpinner.getSelectedItemId();
+ newParentAccountUID = mAccountsDbAdapter.getAccountUID(newParentAccountId);
+ mAccount.setParentUID(newParentAccountUID);
+ } else {
//need to do this explicitly in case user removes parent account
- mAccount.setParentUID(null);
+ newParentAccountUID = mRootAccountUID;
+ newParentAccountId = mRootAccountId;
}
+ mAccount.setParentUID(newParentAccountUID);
if (mDefaultTransferAccountCheckBox.isChecked()){
long id = mDefaulTransferAccountSpinner.getSelectedItemId();
@@ -714,10 +727,55 @@ private void saveAccount() {
//explicitly set in case of removal of default account
mAccount.setDefaultTransferAccountUID(null);
}
-
+
+ long parentAccountId = mAccountsDbAdapter.getID(mParentAccountUID);
+ // update full names
+ if (mDescendantAccountUIDs == null || newParentAccountId != parentAccountId) {
+ // new Account or parent account changed
+ String newAccountFullName;
+ if (newParentAccountId == mRootAccountId){
+ newAccountFullName = mAccount.getName();
+ }
+ else {
+ newAccountFullName = mAccountsDbAdapter.getAccountFullName(newParentAccountUID) +
+ AccountsDbAdapter.ACCOUNT_NAME_SEPARATOR + mAccount.getName();
+ }
+ mAccount.setFullName(newAccountFullName);
+ if (mDescendantAccountUIDs != null) {
+ // modifying existing account
+ if (parentAccountId != newParentAccountId && mDescendantAccountUIDs.size() > 0) {
+ // parent change, update all full names of descent accounts
+ accountsToUpdate.addAll(mAccountsDbAdapter.getSimpleAccountList(
+ DatabaseSchema.AccountEntry.COLUMN_UID + " IN ('" +
+ TextUtils.join("','", mDescendantAccountUIDs) + "')",
+ null,
+ null
+ ));
+ }
+ HashMap mapAccount = new HashMap();
+ for (Account acct : accountsToUpdate) mapAccount.put(acct.getUID(), acct);
+ for (String uid: mDescendantAccountUIDs) {
+ // mAccountsDbAdapter.getDescendantAccountUIDs() will ensure a parent-child order
+ Account acct = mapAccount.get(uid);
+ // mAccount cannot be root, so acct here cannot be top level account.
+ if (acct.getParentUID().equals(mAccount.getUID())) {
+ acct.setFullName(mAccount.getFullName() + AccountsDbAdapter.ACCOUNT_NAME_SEPARATOR + acct.getName());
+ }
+ else {
+ acct.setFullName(
+ mapAccount.get(acct.getParentUID()).getFullName() +
+ AccountsDbAdapter.ACCOUNT_NAME_SEPARATOR +
+ acct.getName()
+ );
+ }
+ }
+ }
+ }
+ accountsToUpdate.add(mAccount);
if (mAccountsDbAdapter == null)
mAccountsDbAdapter = new AccountsDbAdapter(getActivity());
- mAccountsDbAdapter.addAccount(mAccount);
+ // bulk update, will not update transactions
+ mAccountsDbAdapter.bulkAddAccounts(accountsToUpdate);
finishFragment();
}
@@ -728,13 +786,13 @@ private void saveAccount() {
*/
private AccountType getSelectedAccountType() {
int selectedAccountTypeIndex = mAccountTypeSpinner.getSelectedItemPosition();
- String[] accountTypeEntries = getResources().getStringArray(R.array.account_type_entries);
+ String[] accountTypeEntries = getResources().getStringArray(R.array.key_account_type_entries);
return AccountType.valueOf(accountTypeEntries[selectedAccountTypeIndex]);
}
/**
* Retrieves the name of the account which has been entered in the EditText
- * @return
+ * @return Name of the account which has been entered in the EditText
*/
public String getEnteredName(){
return mNameEditText.getText().toString().trim();
diff --git a/app/src/org/gnucash/android/ui/account/AccountsActivity.java b/app/src/org/gnucash/android/ui/account/AccountsActivity.java
index 485ecc9b3..e0ed799e2 100644
--- a/app/src/org/gnucash/android/ui/account/AccountsActivity.java
+++ b/app/src/org/gnucash/android/ui/account/AccountsActivity.java
@@ -1,5 +1,6 @@
/*
- * Copyright (c) 2012 Ngewi Fet
+ * Copyright (c) 2012 - 2014 Ngewi Fet
+ * Copyright (c) 2014 Yongxin Wang
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -16,6 +17,7 @@
package org.gnucash.android.ui.account;
+import android.app.Activity;
import android.app.AlertDialog;
import android.content.Context;
import android.content.DialogInterface;
@@ -36,32 +38,38 @@
import android.util.SparseArray;
import android.view.View;
import android.view.ViewGroup;
-import com.actionbarsherlock.app.SherlockFragmentActivity;
+import android.widget.ArrayAdapter;
import com.actionbarsherlock.view.Menu;
import com.actionbarsherlock.view.MenuInflater;
import com.actionbarsherlock.view.MenuItem;
import com.viewpagerindicator.TitlePageIndicator;
import org.gnucash.android.R;
import org.gnucash.android.app.GnuCashApplication;
+import org.gnucash.android.db.AccountsDbAdapter;
+import org.gnucash.android.db.DatabaseSchema;
import org.gnucash.android.importer.ImportAsyncTask;
import org.gnucash.android.model.Money;
-import org.gnucash.android.ui.util.Refreshable;
import org.gnucash.android.ui.UxArgument;
+import org.gnucash.android.ui.passcode.PassLockActivity;
import org.gnucash.android.ui.settings.SettingsActivity;
import org.gnucash.android.ui.transaction.ScheduledTransactionsListFragment;
import org.gnucash.android.ui.transaction.TransactionsActivity;
import org.gnucash.android.ui.util.OnAccountClickedListener;
+import org.gnucash.android.ui.util.Refreshable;
+import org.gnucash.android.ui.util.TaskDelegate;
import java.io.FileNotFoundException;
import java.io.InputStream;
+import java.util.Arrays;
/**
* Manages actions related to accounts, displaying, exporting and creating new accounts
* The various actions are implemented as Fragments which are then added to this activity
+ *
* @author Ngewi Fet
- *
+ * @author Oleksandr Tyshkovets
*/
-public class AccountsActivity extends SherlockFragmentActivity implements OnAccountClickedListener {
+public class AccountsActivity extends PassLockActivity implements OnAccountClickedListener {
/**
* Tag used for identifying the account list fragment when it is added to this activity
@@ -220,12 +228,12 @@ public void onCreate(Bundle savedInstanceState) {
mPager.setVisibility(View.GONE);
titlePageIndicator.setVisibility(View.GONE);
- long accountId = intent.getLongExtra(UxArgument.SELECTED_ACCOUNT_ID, 0L);
- if (accountId > 0)
- showEditAccountFragment(accountId);
+ String accountUID = intent.getStringExtra(UxArgument.SELECTED_ACCOUNT_UID);
+ if (accountUID != null)
+ showEditAccountFragment(accountUID);
else {
- long parentAccountId = intent.getLongExtra(UxArgument.PARENT_ACCOUNT_ID, 0L);
- showAddAccountFragment(parentAccountId);
+ String parentAccountUID = intent.getStringExtra(UxArgument.PARENT_ACCOUNT_UID);
+ showAddAccountFragment(parentAccountUID);
}
} else if (action != null && action.equals(ACTION_VIEW_RECURRING)) {
mPager.setVisibility(View.GONE);
@@ -255,7 +263,7 @@ private void init() {
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this);
boolean firstRun = prefs.getBoolean(getString(R.string.key_first_run), true);
if (firstRun){
- createDefaultAccounts();
+ showFirstRunDialog();
//default to using double entry and save the preference explicitly
prefs.edit().putBoolean(getString(R.string.key_use_double_entry), true).commit();
}
@@ -266,12 +274,6 @@ private void init() {
}
- @Override
- protected void onResume() {
- super.onResume();
- TransactionsActivity.sLastTitleColor = -1;
- }
-
@Override
protected void onDestroy() {
super.onDestroy();
@@ -363,11 +365,11 @@ private Intent createNewAccountIntent(){
/**
* Shows form fragment for creating a new account
- * @param parentAccountId Record ID of the parent account present. Can be 0 for top-level account
+ * @param parentAccountUID GUID of the parent account present. Can be 0 for top-level account
*/
- private void showAddAccountFragment(long parentAccountId){
+ private void showAddAccountFragment(String parentAccountUID){
Bundle args = new Bundle();
- args.putLong(UxArgument.PARENT_ACCOUNT_ID, parentAccountId);
+ args.putString(UxArgument.PARENT_ACCOUNT_UID, parentAccountUID);
showAccountFormFragment(args);
}
@@ -388,11 +390,11 @@ private void showRecurringTransactionsFragment(){
}
/**
* Shows the form fragment for editing the account with record ID accountId
- * @param accountId Record ID of the account to be edited
+ * @param accountUID GUID of the account to be edited
*/
- private void showEditAccountFragment(long accountId) {
+ private void showEditAccountFragment(String accountUID) {
Bundle args = new Bundle();
- args.putLong(UxArgument.SELECTED_ACCOUNT_ID, accountId);
+ args.putString(UxArgument.SELECTED_ACCOUNT_UID, accountUID);
showAccountFormFragment(args);
}
@@ -426,7 +428,7 @@ public void onNewAccountClick(View v) {
/**
* Shows the user dialog to create default account structure or import existing account structure
*/
- private void createDefaultAccounts(){
+ private void showFirstRunDialog() {
AlertDialog.Builder builder = new AlertDialog.Builder(this);
builder.setTitle(R.string.title_default_accounts);
builder.setMessage(R.string.msg_confirm_create_default_accounts_first_run);
@@ -435,9 +437,27 @@ private void createDefaultAccounts(){
@Override
public void onClick(DialogInterface dialog, int which) {
- InputStream accountFileInputStream = getResources().openRawResource(R.raw.default_accounts);
- new ImportAsyncTask(AccountsActivity.this).execute(accountFileInputStream);
- removeFirstRunFlag();
+ AlertDialog.Builder adb = new AlertDialog.Builder(AccountsActivity.this);
+ adb.setTitle(R.string.title_choose_currency);
+ ArrayAdapter arrayAdapter = new ArrayAdapter(
+ AccountsActivity.this,
+ android.R.layout.select_dialog_singlechoice,
+ getResources().getStringArray(R.array.currency_names));
+ adb.setAdapter(arrayAdapter, new DialogInterface.OnClickListener() {
+
+ @Override
+ public void onClick(DialogInterface dialog, int which) {
+ String currency = Arrays.asList(getResources().getStringArray(R.array.key_currency_codes)).get(which);
+ PreferenceManager.getDefaultSharedPreferences(AccountsActivity.this)
+ .edit()
+ .putString(getString(R.string.key_default_currency), currency)
+ .commit();
+
+ createDefaultAccounts(currency, AccountsActivity.this);
+ removeFirstRunFlag();
+ }
+ });
+ adb.create().show();
}
});
@@ -462,6 +482,30 @@ public void onClick(DialogInterface dialogInterface, int i) {
mDefaultAccountsDialog.show();
}
+ /**
+ * Creates default accounts with the specified currency code.
+ * If the currency parameter is null, then locale currency will be used if available
+ *
+ * @param currencyCode Currency code to assign to the imported accounts
+ * @param activity Activity for providing context and displaying dialogs
+ */
+ public static void createDefaultAccounts(final String currencyCode, final Activity activity) {
+ TaskDelegate delegate = null;
+ if (currencyCode != null) {
+ delegate = new TaskDelegate() {
+ @Override
+ public void onTaskComplete() {
+ AccountsDbAdapter accountsDbAdapter = new AccountsDbAdapter(activity);
+ accountsDbAdapter.updateAllAccounts(DatabaseSchema.AccountEntry.COLUMN_CURRENCY, currencyCode);
+ accountsDbAdapter.close();
+ }
+ };
+ }
+
+ InputStream accountFileInputStream = activity.getResources().openRawResource(R.raw.default_accounts);
+ new ImportAsyncTask(activity, delegate).execute(accountFileInputStream);
+ }
+
/**
* Starts Intent chooser for selecting a GnuCash accounts file to import.
* The accounts are actually imported in onActivityResult
@@ -506,10 +550,10 @@ public static void start(Context context){
}
@Override
- public void accountSelected(long accountRowId) {
+ public void accountSelected(String accountUID) {
Intent intent = new Intent(this, TransactionsActivity.class);
intent.setAction(Intent.ACTION_VIEW);
- intent.putExtra(UxArgument.SELECTED_ACCOUNT_ID, accountRowId);
+ intent.putExtra(UxArgument.SELECTED_ACCOUNT_UID, accountUID);
startActivity(intent);
}
diff --git a/app/src/org/gnucash/android/ui/account/AccountsListFragment.java b/app/src/org/gnucash/android/ui/account/AccountsListFragment.java
index f14876b16..f8ead4fd9 100644
--- a/app/src/org/gnucash/android/ui/account/AccountsListFragment.java
+++ b/app/src/org/gnucash/android/ui/account/AccountsListFragment.java
@@ -127,7 +127,13 @@ public enum DisplayMode {
* Database record ID of the account whose children will be loaded by the list fragment.
* If no parent account is specified, then all top-level accounts are loaded.
*/
- private long mParentAccountId = -1;
+// private long mParentAccountId = -1;
+
+ /**
+ * GUID of the account whose children will be loaded in the list fragment.
+ * If no parent account is specified, then all top-level accounts are loaded.
+ */
+ private String mParentAccountUID = null;
/**
* Filter for which accounts should be displayed. Used by search interface
@@ -204,7 +210,7 @@ public void onCreate(Bundle savedInstanceState) {
Bundle args = getArguments();
if (args != null)
- mParentAccountId = args.getLong(UxArgument.PARENT_ACCOUNT_ID);
+ mParentAccountUID = args.getString(UxArgument.PARENT_ACCOUNT_UID);
mAccountsDbAdapter = new AccountsDbAdapter(getActivity());
mAccountsCursorAdapter = new AccountsCursorAdapter(
@@ -260,7 +266,7 @@ public void onListItemClick(ListView listView, View view, int position, long id)
listView.setItemChecked(position, true);
return;
}
- mAccountSelectedListener.accountSelected(id);
+ mAccountSelectedListener.accountSelected(mAccountsDbAdapter.getAccountUID(id));
}
@Override
@@ -296,7 +302,7 @@ public void onActivityResult(int requestCode, int resultCode, Intent data) {
*/
public void tryDeleteAccount(long rowId) {
Account acc = mAccountsDbAdapter.getAccount(rowId);
- if (acc.getTransactionCount() > 0 || mAccountsDbAdapter.getSubAccountCount(rowId) > 0) {
+ if (acc.getTransactionCount() > 0 || mAccountsDbAdapter.getSubAccountCount(acc.getUID()) > 0) {
showConfirmationDialog(rowId);
} else {
deleteAccount(rowId, false);
@@ -309,13 +315,10 @@ public void tryDeleteAccount(long rowId) {
* @param rowId Record ID of the account to be deleted
*/
protected void deleteAccount(long rowId, boolean deleteSubAccounts) {
- String accountUID = mAccountsDbAdapter.getAccountUID(rowId);
- String parentUID = mAccountsDbAdapter.getParentAccountUID(rowId);
boolean deleted = deleteSubAccounts ?
mAccountsDbAdapter.recursiveDestructiveDelete(rowId)
: mAccountsDbAdapter.destructiveDeleteAccount(rowId);
if (deleted) {
- mAccountsDbAdapter.reassignParent(accountUID, parentUID);
Toast.makeText(getActivity(), R.string.toast_account_deleted, Toast.LENGTH_SHORT).show();
WidgetConfigurationActivity.updateAllWidgets(getActivity().getApplicationContext());
}
@@ -328,7 +331,8 @@ protected void deleteAccount(long rowId, boolean deleteSubAccounts) {
* @param id Record ID of account to be deleted after confirmation
*/
public void showConfirmationDialog(long id) {
- DeleteConfirmationDialogFragment alertFragment = DeleteConfirmationDialogFragment.newInstance(R.string.title_confirm_delete, id);
+ DeleteConfirmationDialogFragment alertFragment =
+ DeleteConfirmationDialogFragment.newInstance(R.string.title_confirm_delete, mAccountsDbAdapter.getAccountUID(id));
alertFragment.setTargetFragment(this, 0);
alertFragment.show(getSherlockActivity().getSupportFragmentManager(), "dialog");
}
@@ -346,7 +350,7 @@ public void finishEditMode() {
@Override
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
- if (mParentAccountId > 0)
+ if (mParentAccountUID != null)
inflater.inflate(R.menu.sub_account_actions, menu);
else {
inflater.inflate(R.menu.account_actions, menu);
@@ -372,7 +376,7 @@ public boolean onOptionsItemSelected(MenuItem item) {
case R.id.menu_add_account:
Intent addAccountIntent = new Intent(getActivity(), AccountsActivity.class);
addAccountIntent.setAction(Intent.ACTION_INSERT_OR_EDIT);
- addAccountIntent.putExtra(UxArgument.PARENT_ACCOUNT_ID, mParentAccountId);
+ addAccountIntent.putExtra(UxArgument.PARENT_ACCOUNT_UID, mParentAccountUID);
startActivityForResult(addAccountIntent, AccountsActivity.REQUEST_EDIT_ACCOUNT);
return true;
@@ -386,8 +390,8 @@ public boolean onOptionsItemSelected(MenuItem item) {
}
@Override
- public void refresh(long parentAccountId) {
- getArguments().putLong(UxArgument.PARENT_ACCOUNT_ID, parentAccountId);
+ public void refresh(String parentAccountUID) {
+ getArguments().putString(UxArgument.PARENT_ACCOUNT_UID, parentAccountUID);
refresh();
}
@@ -429,23 +433,6 @@ public void onDestroy() {
mAccountsCursorAdapter.close();
}
- public void showAddAccountFragment(long accountId) {
- FragmentManager fragmentManager = getSherlockActivity().getSupportFragmentManager();
- FragmentTransaction fragmentTransaction = fragmentManager
- .beginTransaction();
-
- Bundle args = new Bundle();
- args.putLong(UxArgument.SELECTED_ACCOUNT_ID, accountId);
- AccountFormFragment accountFormFragment = AccountFormFragment.newInstance(mAccountsDbAdapter);
- accountFormFragment.setArguments(args);
-
- fragmentTransaction.replace(R.id.fragment_container,
- accountFormFragment, AccountsActivity.FRAGMENT_NEW_ACCOUNT);
-
- fragmentTransaction.addToBackStack(null);
- fragmentTransaction.commit();
- }
-
/**
* Opens a new activity for creating or editing an account.
* If the accountId < 1, then create else edit the account.
@@ -454,7 +441,7 @@ public void showAddAccountFragment(long accountId) {
public void openCreateOrEditActivity(long accountId){
Intent editAccountIntent = new Intent(AccountsListFragment.this.getActivity(), AccountsActivity.class);
editAccountIntent.setAction(Intent.ACTION_INSERT_OR_EDIT);
- editAccountIntent.putExtra(UxArgument.SELECTED_ACCOUNT_ID, accountId);
+ editAccountIntent.putExtra(UxArgument.SELECTED_ACCOUNT_UID, mAccountsDbAdapter.getAccountUID(accountId));
startActivityForResult(editAccountIntent, AccountsActivity.REQUEST_EDIT_ACCOUNT);
}
@@ -478,13 +465,13 @@ public void showExportDialog() {
@Override
public Loader onCreateLoader(int id, Bundle args) {
Log.d(TAG, "Creating the accounts loader");
- Bundle fragmentArguments = getArguments();
- long accountId = fragmentArguments == null ? -1 : fragmentArguments.getLong(UxArgument.PARENT_ACCOUNT_ID);
+ Bundle arguments = getArguments();
+ String accountUID = arguments == null ? null : arguments.getString(UxArgument.PARENT_ACCOUNT_UID);
if (mCurrentFilter != null){
return new AccountsCursorLoader(getActivity(), mCurrentFilter);
} else {
- return new AccountsCursorLoader(this.getActivity(), accountId, mDisplayMode);
+ return new AccountsCursorLoader(this.getActivity(), accountUID, mDisplayMode);
}
}
@@ -539,11 +526,17 @@ public boolean onClose() {
*/
public static class DeleteConfirmationDialogFragment extends SherlockDialogFragment {
- public static DeleteConfirmationDialogFragment newInstance(int title, long id) {
+ /**
+ * Creates new instance of the delete confirmation dialog and provides parameters for it
+ * @param title Title to use for the dialog
+ * @param uid GUID of the account to be deleted
+ * @return New instance of the delete confirmation dialog
+ */
+ public static DeleteConfirmationDialogFragment newInstance(int title, String uid) {
DeleteConfirmationDialogFragment frag = new DeleteConfirmationDialogFragment();
Bundle args = new Bundle();
args.putInt("title", title);
- args.putLong(UxArgument.SELECTED_ACCOUNT_ID, id);
+ args.putString(UxArgument.SELECTED_ACCOUNT_UID, uid);
frag.setArguments(args);
return frag;
}
@@ -551,7 +544,8 @@ public static DeleteConfirmationDialogFragment newInstance(int title, long id) {
@Override
public Dialog onCreateDialog(Bundle savedInstanceState) {
int title = getArguments().getInt("title");
- final long rowId = getArguments().getLong(UxArgument.SELECTED_ACCOUNT_ID);
+ final String uid = getArguments().getString(UxArgument.SELECTED_ACCOUNT_UID);
+
LayoutInflater layoutInflater = getSherlockActivity().getLayoutInflater();
final View dialogLayout = layoutInflater.inflate(R.layout.dialog_account_delete, (ViewGroup) getView());
AlertDialog.Builder dialogBuilder = new AlertDialog.Builder(getActivity())
@@ -562,16 +556,17 @@ public Dialog onCreateDialog(Bundle savedInstanceState) {
new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int whichButton) {
Context context = getDialog().getContext();
- if (rowId < 0) {
- AccountsDbAdapter accountsDbAdapter = new AccountsDbAdapter(context);
+ AccountsDbAdapter accountsDbAdapter = new AccountsDbAdapter(context);
+ if (uid == null) {
accountsDbAdapter.deleteAllRecords();
- accountsDbAdapter.close();
Toast.makeText(context, R.string.toast_all_accounts_deleted, Toast.LENGTH_SHORT).show();
} else {
CheckBox deleteSubAccountsCheckBox = (CheckBox) dialogLayout
.findViewById(R.id.checkbox_delete_sub_accounts);
+ long rowId = accountsDbAdapter.getID(uid);
((AccountsListFragment) getTargetFragment()).deleteAccount(rowId, deleteSubAccountsCheckBox.isChecked());
}
+ accountsDbAdapter.close();
}
})
.setNegativeButton(R.string.alert_dialog_cancel,
@@ -596,7 +591,7 @@ public void onClick(DialogInterface dialog, int whichButton) {
* @author Ngewi Fet
*/
private static final class AccountsCursorLoader extends DatabaseCursorLoader {
- private long mParentAccountId = -1;
+ private String mParentAccountUID = null;
private String mFilter;
private DisplayMode mDisplayMode = DisplayMode.TOP_LEVEL;
@@ -605,11 +600,11 @@ private static final class AccountsCursorLoader extends DatabaseCursorLoader {
* If the parentAccountId <= 0 then only top-level accounts are loaded.
* Else only the child accounts of the parentAccountId will be loaded
* @param context Application context
- * @param parentAccountId Record ID of the parent account
+ * @param parentAccountUID GUID of the parent account
*/
- public AccountsCursorLoader(Context context, long parentAccountId, DisplayMode displayMode) {
+ public AccountsCursorLoader(Context context, String parentAccountUID, DisplayMode displayMode) {
super(context);
- mParentAccountId = parentAccountId;
+ this.mParentAccountUID = parentAccountUID;
this.mDisplayMode = displayMode;
}
@@ -633,8 +628,8 @@ public Cursor loadInBackground() {
cursor = ((AccountsDbAdapter)mDatabaseAdapter)
.fetchAccounts(DatabaseSchema.AccountEntry.COLUMN_NAME + " LIKE '%" + mFilter + "%'");
} else {
- if (mParentAccountId > 0)
- cursor = ((AccountsDbAdapter) mDatabaseAdapter).fetchSubAccounts(mParentAccountId);
+ if (mParentAccountUID != null && mParentAccountUID.length() > 0)
+ cursor = ((AccountsDbAdapter) mDatabaseAdapter).fetchSubAccounts(mParentAccountUID);
else {
switch (this.mDisplayMode){
case RECENT:
@@ -682,10 +677,10 @@ public void bindView(View v, Context context, Cursor cursor) {
// perform the default binding
super.bindView(v, context, cursor);
- final long accountId = cursor.getLong(cursor.getColumnIndexOrThrow(DatabaseSchema.AccountEntry._ID));
+ final String accountUID = cursor.getString(cursor.getColumnIndexOrThrow(DatabaseSchema.AccountEntry.COLUMN_UID));
TextView subAccountTextView = (TextView) v.findViewById(R.id.secondary_text);
- int subAccountCount = mAccountsDbAdapter.getSubAccountCount(accountId);
+ int subAccountCount = mAccountsDbAdapter.getSubAccountCount(accountUID);
if (subAccountCount > 0) {
subAccountTextView.setVisibility(View.VISIBLE);
String text = getResources().getQuantityString(R.plurals.label_sub_accounts, subAccountCount, subAccountCount);
@@ -696,7 +691,7 @@ public void bindView(View v, Context context, Cursor cursor) {
// add a summary of transactions to the account view
TextView accountBalanceTextView = (TextView) v
.findViewById(R.id.transactions_summary);
- new AccountBalanceTask(accountBalanceTextView, getActivity()).execute(accountId);
+ new AccountBalanceTask(accountBalanceTextView, getActivity()).execute(accountUID);
View colorStripView = v.findViewById(R.id.account_color_strip);
String accountColor = cursor.getString(cursor.getColumnIndexOrThrow(DatabaseSchema.AccountEntry.COLUMN_COLOR_CODE));
@@ -707,7 +702,7 @@ public void bindView(View v, Context context, Cursor cursor) {
colorStripView.setBackgroundColor(Color.TRANSPARENT);
}
- boolean isPlaceholderAccount = mAccountsDbAdapter.isPlaceholderAccount(accountId);
+ boolean isPlaceholderAccount = mAccountsDbAdapter.isPlaceholderAccount(accountUID);
ImageButton newTransactionButton = (ImageButton) v.findViewById(R.id.btn_new_transaction);
if (isPlaceholderAccount){
newTransactionButton.setVisibility(View.GONE);
@@ -719,7 +714,7 @@ public void bindView(View v, Context context, Cursor cursor) {
public void onClick(View v) {
Intent intent = new Intent(getActivity(), TransactionsActivity.class);
intent.setAction(Intent.ACTION_INSERT_OR_EDIT);
- intent.putExtra(UxArgument.SELECTED_ACCOUNT_ID, accountId);
+ intent.putExtra(UxArgument.SELECTED_ACCOUNT_UID, accountUID);
getActivity().startActivity(intent);
}
});
diff --git a/app/src/org/gnucash/android/ui/passcode/KeyboardFragment.java b/app/src/org/gnucash/android/ui/passcode/KeyboardFragment.java
new file mode 100644
index 000000000..bcfddb23d
--- /dev/null
+++ b/app/src/org/gnucash/android/ui/passcode/KeyboardFragment.java
@@ -0,0 +1,189 @@
+/*
+ * Copyright (c) 2014 Oleksandr Tyshkovets
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gnucash.android.ui.passcode;
+
+import android.app.Activity;
+import android.os.Bundle;
+import android.os.Handler;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.TextView;
+
+import com.actionbarsherlock.app.SherlockFragment;
+
+import org.gnucash.android.R;
+
+/**
+ * Soft numeric keyboard for lock screen and passcode preference.
+ * @author Oleksandr Tyshkovets
+ */
+public class KeyboardFragment extends SherlockFragment {
+
+ private TextView pass1;
+ private TextView pass2;
+ private TextView pass3;
+ private TextView pass4;
+
+ private int length = 0;
+
+ public interface OnPasscodeEnteredListener {
+ public void onPasscodeEntered(String pass);
+ }
+
+ private OnPasscodeEnteredListener listener;
+
+ @Override
+ public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
+
+ View rootView = inflater.inflate(R.layout.fragment_numeric_keyboard, container, false);
+
+ pass1 = (TextView) rootView.findViewById(R.id.passcode1);
+ pass2 = (TextView) rootView.findViewById(R.id.passcode2);
+ pass3 = (TextView) rootView.findViewById(R.id.passcode3);
+ pass4 = (TextView) rootView.findViewById(R.id.passcode4);
+
+ rootView.findViewById(R.id.one_btn).setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ add("1");
+ }
+ });
+ rootView.findViewById(R.id.two_btn).setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ add("2");
+ }
+ });
+ rootView.findViewById(R.id.three_btn).setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ add("3");
+ }
+ });
+ rootView.findViewById(R.id.four_btn).setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ add("4");
+ }
+ });
+ rootView.findViewById(R.id.five_btn).setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ add("5");
+ }
+ });
+ rootView.findViewById(R.id.six_btn).setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ add("6");
+ }
+ });
+ rootView.findViewById(R.id.seven_btn).setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ add("7");
+ }
+ });
+ rootView.findViewById(R.id.eight_btn).setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ add("8");
+ }
+ });
+ rootView.findViewById(R.id.nine_btn).setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ add("9");
+ }
+ });
+ rootView.findViewById(R.id.zero_btn).setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ add("0");
+ }
+ });
+ rootView.findViewById(R.id.delete_btn).setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ switch (length) {
+ case 1:
+ pass1.setText(null);
+ length--;
+ break;
+ case 2:
+ pass2.setText(null);
+ length--;
+ break;
+ case 3:
+ pass3.setText(null);
+ length--;
+ break;
+ case 4:
+ pass4.setText(null);
+ length--;
+ }
+ }
+ });
+
+ return rootView;
+ }
+
+ @Override
+ public void onAttach(Activity activity) {
+ super.onAttach(activity);
+ try {
+ listener = (OnPasscodeEnteredListener) activity;
+ } catch (ClassCastException e) {
+ throw new ClassCastException(activity.toString() + " must implement "
+ + KeyboardFragment.OnPasscodeEnteredListener.class);
+ }
+ }
+
+ private void add(String num) {
+ switch (length + 1) {
+ case 1:
+ pass1.setText(num);
+ length++;
+ break;
+ case 2:
+ pass2.setText(num);
+ length++;
+ break;
+ case 3:
+ pass3.setText(num);
+ length++;
+ break;
+ case 4:
+ pass4.setText(num);
+ length++;
+
+ new Handler().postDelayed(new Runnable() {
+ public void run() {
+ listener.onPasscodeEntered(pass1.getText().toString() + pass2.getText()
+ + pass3.getText() + pass4.getText());
+ pass1.setText(null);
+ pass2.setText(null);
+ pass3.setText(null);
+ pass4.setText(null);
+ length = 0;
+ }
+ }, 500);
+ }
+ }
+
+}
diff --git a/app/src/org/gnucash/android/ui/passcode/PassLockActivity.java b/app/src/org/gnucash/android/ui/passcode/PassLockActivity.java
new file mode 100644
index 000000000..384359a37
--- /dev/null
+++ b/app/src/org/gnucash/android/ui/passcode/PassLockActivity.java
@@ -0,0 +1,52 @@
+package org.gnucash.android.ui.passcode;
+
+import android.content.Intent;
+import android.content.SharedPreferences;
+import android.preference.PreferenceManager;
+
+import com.actionbarsherlock.app.SherlockFragmentActivity;
+
+import org.gnucash.android.app.GnuCashApplication;
+import org.gnucash.android.ui.UxArgument;
+
+/**
+ * This activity used as the parent class for enabling passcode lock
+ *
+ * @author Oleksandr Tyshkovets
+ * @see org.gnucash.android.ui.account.AccountsActivity
+ * @see org.gnucash.android.ui.transaction.TransactionsActivity
+ */
+public class PassLockActivity extends SherlockFragmentActivity {
+
+ private static final String TAG = "PassLockActivity";
+
+ @Override
+ protected void onResume() {
+ super.onResume();
+
+ SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(getApplicationContext());
+ if (sharedPreferences.getBoolean(UxArgument.ENABLED_PASSCODE, false) && !isSessionActive()) {
+ startActivity(new Intent(this, PasscodeLockScreenActivity.class)
+ .setAction(getIntent().getAction())
+ .putExtra(UxArgument.PASSCODE_CLASS_CALLER, this.getClass().getName())
+ .putExtra(UxArgument.SELECTED_ACCOUNT_UID,
+ getIntent().getStringExtra(UxArgument.SELECTED_ACCOUNT_UID))
+ );
+ }
+ }
+
+ @Override
+ protected void onPause() {
+ super.onPause();
+ GnuCashApplication.PASSCODE_SESSION_INIT_TIME = System.currentTimeMillis();
+ }
+
+ /**
+ * @return {@code true} if passcode session is active, and {@code false} otherwise
+ */
+ private boolean isSessionActive() {
+ return System.currentTimeMillis() - GnuCashApplication.PASSCODE_SESSION_INIT_TIME
+ < GnuCashApplication.SESSION_TIMEOUT;
+ }
+
+}
diff --git a/app/src/org/gnucash/android/ui/passcode/PasscodeLockScreenActivity.java b/app/src/org/gnucash/android/ui/passcode/PasscodeLockScreenActivity.java
new file mode 100644
index 000000000..e2e98cffc
--- /dev/null
+++ b/app/src/org/gnucash/android/ui/passcode/PasscodeLockScreenActivity.java
@@ -0,0 +1,73 @@
+/*
+ * Copyright (c) 2014 Oleksandr Tyshkovets
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gnucash.android.ui.passcode;
+
+import android.content.Intent;
+import android.os.Bundle;
+import android.preference.PreferenceManager;
+import android.util.Log;
+import android.widget.Toast;
+
+import com.actionbarsherlock.app.SherlockFragmentActivity;
+
+import org.gnucash.android.R;
+import org.gnucash.android.app.GnuCashApplication;
+import org.gnucash.android.ui.UxArgument;
+
+/**
+ * Activity for displaying and managing the passcode lock screen.
+ * @author Oleksandr Tyshkovets
+ */
+public class PasscodeLockScreenActivity extends SherlockFragmentActivity
+ implements KeyboardFragment.OnPasscodeEnteredListener {
+
+ private static final String TAG = "PasscodeLockScreenActivity";
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.passcode_lockscreen);
+ }
+
+ @Override
+ public void onPasscodeEntered(String pass) {
+ String passcode = PreferenceManager.getDefaultSharedPreferences(getApplicationContext())
+ .getString(UxArgument.PASSCODE, "");
+ Log.d(TAG, "Passcode: " + passcode);
+
+ if (passcode.equals(pass)) {
+ GnuCashApplication.PASSCODE_SESSION_INIT_TIME = System.currentTimeMillis();
+ startActivity(new Intent()
+ .setClassName(this, getIntent().getStringExtra(UxArgument.PASSCODE_CLASS_CALLER))
+ .setAction(getIntent().getAction())
+ .setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP)
+ .putExtra(UxArgument.SELECTED_ACCOUNT_UID, getIntent().getStringExtra(UxArgument.SELECTED_ACCOUNT_UID))
+ );
+ } else {
+ Toast.makeText(this, R.string.toast_wrong_passcode, Toast.LENGTH_SHORT).show();
+ }
+ }
+
+ @Override
+ public void onBackPressed() {
+ GnuCashApplication.PASSCODE_SESSION_INIT_TIME = System.currentTimeMillis() - GnuCashApplication.SESSION_TIMEOUT;
+ startActivity(new Intent(Intent.ACTION_MAIN)
+ .addCategory(Intent.CATEGORY_HOME)
+ .setFlags(Intent.FLAG_ACTIVITY_NEW_TASK));
+ }
+
+}
diff --git a/app/src/org/gnucash/android/ui/passcode/PasscodePreferenceActivity.java b/app/src/org/gnucash/android/ui/passcode/PasscodePreferenceActivity.java
new file mode 100644
index 000000000..7dc7e43d5
--- /dev/null
+++ b/app/src/org/gnucash/android/ui/passcode/PasscodePreferenceActivity.java
@@ -0,0 +1,61 @@
+/*
+ * Copyright (c) 2014 Oleksandr Tyshkovets
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gnucash.android.ui.passcode;
+
+import android.content.Intent;
+import android.os.Bundle;
+import android.widget.TextView;
+import android.widget.Toast;
+
+import com.actionbarsherlock.app.SherlockFragmentActivity;
+
+import org.gnucash.android.R;
+import org.gnucash.android.ui.UxArgument;
+
+/**
+ * Activity for entering and confirming passcode
+ * @author Oleksandr Tyshkovets
+ */
+public class PasscodePreferenceActivity extends SherlockFragmentActivity
+ implements KeyboardFragment.OnPasscodeEnteredListener {
+
+ private boolean reenter = false;
+ private String passcode;
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.passcode_lockscreen);
+ }
+
+ @Override
+ public void onPasscodeEntered(String pass) {
+ if (reenter) {
+ if (passcode.equals(pass)) {
+ setResult(RESULT_OK, new Intent().putExtra(UxArgument.PASSCODE, pass));
+ finish();
+ } else {
+ Toast.makeText(this, R.string.toast_invalid_passcode_confirmation, Toast.LENGTH_LONG).show();
+ }
+ } else {
+ passcode = pass;
+ reenter = true;
+ ((TextView) findViewById(R.id.passcode_label)).setText(R.string.toast_confirm_passcode);
+ Toast.makeText(this, R.string.toast_confirm_passcode, Toast.LENGTH_SHORT).show();
+ }
+ }
+}
diff --git a/app/src/org/gnucash/android/ui/settings/AccountPreferencesFragment.java b/app/src/org/gnucash/android/ui/settings/AccountPreferencesFragment.java
index 40384546d..e9b137c1f 100644
--- a/app/src/org/gnucash/android/ui/settings/AccountPreferencesFragment.java
+++ b/app/src/org/gnucash/android/ui/settings/AccountPreferencesFragment.java
@@ -16,6 +16,7 @@
package org.gnucash.android.ui.settings;
+import android.app.Activity;
import android.app.AlertDialog;
import android.content.DialogInterface;
import android.content.SharedPreferences;
@@ -23,20 +24,24 @@
import android.preference.Preference;
import android.preference.PreferenceFragment;
import android.preference.PreferenceManager;
+
import com.actionbarsherlock.app.ActionBar;
import com.actionbarsherlock.app.SherlockPreferenceActivity;
+
import org.gnucash.android.R;
-import org.gnucash.android.importer.ImportAsyncTask;
import org.gnucash.android.model.Money;
-
-import java.io.InputStream;
+import org.gnucash.android.ui.account.AccountsActivity;
/**
* Account settings fragment inside the Settings activity
*
* @author Ngewi Fet
+ * @author Oleksandr Tyshkovets
*/
public class AccountPreferencesFragment extends PreferenceFragment {
+
+ private Activity activity;
+
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
@@ -46,6 +51,8 @@ public void onCreate(Bundle savedInstanceState) {
actionBar.setHomeButtonEnabled(true);
actionBar.setDisplayHomeAsUpEnabled(true);
actionBar.setTitle(R.string.title_account_preferences);
+
+ activity = getActivity();
}
@Override
@@ -80,8 +87,7 @@ public boolean onPreferenceClick(Preference preference) {
.setPositiveButton(R.string.btn_create_accounts, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialogInterface, int i) {
- InputStream accountFileInputStream = getResources().openRawResource(R.raw.default_accounts);
- new ImportAsyncTask(getActivity()).execute(accountFileInputStream);
+ AccountsActivity.createDefaultAccounts(Money.DEFAULT_CURRENCY_CODE, activity);
}
})
.setNegativeButton(R.string.btn_cancel, new DialogInterface.OnClickListener() {
diff --git a/app/src/org/gnucash/android/ui/settings/DeleteAllTransacationsConfirmationDialog.java b/app/src/org/gnucash/android/ui/settings/DeleteAllTransacationsConfirmationDialog.java
index e946b9d1f..578e5bf21 100644
--- a/app/src/org/gnucash/android/ui/settings/DeleteAllTransacationsConfirmationDialog.java
+++ b/app/src/org/gnucash/android/ui/settings/DeleteAllTransacationsConfirmationDialog.java
@@ -1,3 +1,19 @@
+/*
+ * Copyright (c) 2013 - 2014 Ngewi Fet
+ * Copyright (c) 2014 Yongxin Wang
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
package org.gnucash.android.ui.settings;
import android.app.AlertDialog;
@@ -19,10 +35,10 @@
import java.util.List;
/**
- * Copyright (c) 2013 - gnucash-android
- *
* Confirmation dialog for deleting all transactions
+ *
* @author ngewif
+ * @author Yongxin Wang
*/
public class DeleteAllTransacationsConfirmationDialog extends DialogFragment {
@@ -41,7 +57,7 @@ public Dialog onCreateDialog(Bundle savedInstanceState) {
public void onClick(DialogInterface dialog, int whichButton) {
GncXmlExporter.createBackup();
- Context context = getDialog().getContext();
+ Context context = getActivity();
AccountsDbAdapter accountsDbAdapter = new AccountsDbAdapter(context);
List openingBalances = new ArrayList();
boolean preserveOpeningBalances = GnuCashApplication.shouldSaveOpeningBalances(false);
@@ -53,9 +69,7 @@ public void onClick(DialogInterface dialog, int whichButton) {
transactionsDbAdapter.deleteAllRecords();
if (preserveOpeningBalances) {
- for (Transaction openingBalance : openingBalances) {
- transactionsDbAdapter.addTransaction(openingBalance);
- }
+ transactionsDbAdapter.bulkAddTransactions(openingBalances);
}
transactionsDbAdapter.close();
Toast.makeText(context, R.string.toast_all_transactions_deleted, Toast.LENGTH_SHORT).show();
@@ -64,18 +78,18 @@ public void onClick(DialogInterface dialog, int whichButton) {
}
)
- .
+ .
- setNegativeButton(R.string.alert_dialog_cancel,
- new DialogInterface.OnClickListener() {
- public void onClick(DialogInterface dialog, int whichButton) {
- dismiss();
- }
+ setNegativeButton(R.string.alert_dialog_cancel,
+ new DialogInterface.OnClickListener() {
+ public void onClick(DialogInterface dialog, int whichButton) {
+ dismiss();
}
+ }
- )
- .
+ )
+ .
- create();
- }
+ create();
}
+}
diff --git a/app/src/org/gnucash/android/ui/settings/PasscodePreferenceFragment.java b/app/src/org/gnucash/android/ui/settings/PasscodePreferenceFragment.java
new file mode 100644
index 000000000..52744e07c
--- /dev/null
+++ b/app/src/org/gnucash/android/ui/settings/PasscodePreferenceFragment.java
@@ -0,0 +1,105 @@
+/*
+ * Copyright (c) 2014 Oleksandr Tyshkovets
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gnucash.android.ui.settings;
+
+import android.app.Activity;
+import android.content.Intent;
+import android.content.SharedPreferences;
+import android.os.Bundle;
+import android.preference.CheckBoxPreference;
+import android.preference.Preference;
+import android.preference.Preference.OnPreferenceChangeListener;
+import android.preference.PreferenceFragment;
+import android.preference.PreferenceManager;
+import android.widget.Toast;
+
+import com.actionbarsherlock.app.ActionBar;
+import com.actionbarsherlock.app.SherlockPreferenceActivity;
+
+import org.gnucash.android.R;
+import org.gnucash.android.ui.UxArgument;
+import org.gnucash.android.ui.passcode.PasscodePreferenceActivity;
+
+/**
+ * Fragment for configuring passcode to the application
+ * @author Oleksandr Tyshkovets
+ */
+public class PasscodePreferenceFragment extends PreferenceFragment {
+
+ /**
+ * * Request code for retrieving passcode to store
+ */
+ public static final int PASSCODE_REQUEST_CODE = 2;
+
+ private SharedPreferences.Editor editor;
+ private CheckBoxPreference checkBoxPreference;
+
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ addPreferencesFromResource(R.xml.fragment_passcode_preferences);
+
+ ActionBar actionBar = ((SherlockPreferenceActivity) getActivity()).getSupportActionBar();
+ actionBar.setHomeButtonEnabled(true);
+ actionBar.setDisplayHomeAsUpEnabled(true);
+ actionBar.setTitle(R.string.title_passcode_preferences);
+ }
+
+ @Override
+ public void onResume() {
+ super.onResume();
+
+ editor = PreferenceManager.getDefaultSharedPreferences(getActivity().getApplicationContext()).edit();
+ final Intent intent = new Intent(getActivity(), PasscodePreferenceActivity.class);
+
+ checkBoxPreference = (CheckBoxPreference) findPreference(getString(R.string.key_enable_passcode));
+ checkBoxPreference.setOnPreferenceChangeListener(new OnPreferenceChangeListener() {
+ @Override
+ public boolean onPreferenceChange(Preference preference, Object newValue) {
+ if ((Boolean) newValue) {
+ startActivityForResult(intent, PASSCODE_REQUEST_CODE);
+ }
+ editor.putBoolean(UxArgument.ENABLED_PASSCODE, (Boolean) newValue);
+ editor.commit();
+ return true;
+ }
+ });
+ findPreference(getString(R.string.key_change_passcode))
+ .setOnPreferenceClickListener(new Preference.OnPreferenceClickListener() {
+ @Override
+ public boolean onPreferenceClick(Preference preference) {
+ startActivityForResult(intent, PASSCODE_REQUEST_CODE);
+ return true;
+ }
+ });
+ }
+
+ @Override
+ public void onActivityResult(int requestCode, int resultCode, Intent data) {
+ super.onActivityResult(requestCode, resultCode, data);
+
+ if (resultCode == Activity.RESULT_OK && requestCode == PASSCODE_REQUEST_CODE && data!= null) {
+ editor.putString(UxArgument.PASSCODE, data.getStringExtra(UxArgument.PASSCODE));
+ Toast.makeText(getActivity(), R.string.toast_passcode_set, Toast.LENGTH_SHORT).show();
+ } else {
+ editor.putBoolean(UxArgument.ENABLED_PASSCODE, false);
+ checkBoxPreference.setChecked(false);
+ }
+ editor.commit();
+ }
+
+}
diff --git a/app/src/org/gnucash/android/ui/settings/SettingsActivity.java b/app/src/org/gnucash/android/ui/settings/SettingsActivity.java
index 106d943da..596d71df6 100644
--- a/app/src/org/gnucash/android/ui/settings/SettingsActivity.java
+++ b/app/src/org/gnucash/android/ui/settings/SettingsActivity.java
@@ -1,5 +1,6 @@
/*
* Copyright (c) 2012 Ngewi Fet
+ * Copyright (c) 2014 Yongxin Wang
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -23,26 +24,34 @@
import android.content.pm.PackageManager.NameNotFoundException;
import android.os.Build;
import android.os.Bundle;
+import android.preference.CheckBoxPreference;
import android.preference.Preference;
import android.preference.Preference.OnPreferenceChangeListener;
import android.preference.PreferenceManager;
import android.util.Log;
import android.widget.Toast;
+
import com.actionbarsherlock.app.ActionBar;
import com.actionbarsherlock.app.SherlockPreferenceActivity;
import com.actionbarsherlock.view.MenuItem;
+
import org.gnucash.android.R;
import org.gnucash.android.app.GnuCashApplication;
+import org.gnucash.android.db.AccountsDbAdapter;
+import org.gnucash.android.db.TransactionsDbAdapter;
import org.gnucash.android.export.Exporter;
import org.gnucash.android.export.xml.GncXmlExporter;
import org.gnucash.android.importer.ImportAsyncTask;
import org.gnucash.android.model.Money;
-import org.gnucash.android.db.AccountsDbAdapter;
-import org.gnucash.android.db.TransactionsDbAdapter;
import org.gnucash.android.model.Transaction;
+import org.gnucash.android.ui.UxArgument;
import org.gnucash.android.ui.account.AccountsActivity;
+import org.gnucash.android.ui.passcode.PasscodePreferenceActivity;
-import java.io.*;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.InputStream;
import java.util.ArrayList;
import java.util.List;
import java.util.Timer;
@@ -51,7 +60,8 @@
/**
* Activity for displaying settings and information about the application
* @author Ngewi Fet
- *
+ * @author Oleksandr Tyshkovets
+ * @author Yongxin Wang
*/
public class SettingsActivity extends SherlockPreferenceActivity implements OnPreferenceChangeListener, Preference.OnPreferenceClickListener{
@@ -111,6 +121,7 @@ protected void onCreate(Bundle savedInstanceState) {
addPreferencesFromResource(R.xml.fragment_general_preferences);
addPreferencesFromResource(R.xml.fragment_account_preferences);
addPreferencesFromResource(R.xml.fragment_transaction_preferences);
+ addPreferencesFromResource(R.xml.fragment_passcode_preferences);
addPreferencesFromResource(R.xml.fragment_about_preferences);
setDefaultCurrencyListener();
SharedPreferences manager = PreferenceManager.getDefaultSharedPreferences(getApplicationContext());
@@ -132,6 +143,12 @@ protected void onCreate(Bundle savedInstanceState) {
pref = findPreference(getString(R.string.key_restore_backup));
pref.setOnPreferenceClickListener(this);
+
+ pref = findPreference(getString(R.string.key_change_passcode));
+ pref.setOnPreferenceClickListener(this);
+
+ pref = findPreference(getString(R.string.key_enable_passcode));
+ pref.setOnPreferenceChangeListener(this);
}
}
@@ -142,6 +159,12 @@ protected void onResume() {
mDeleteTransactionsClickCount = 0;
}
+ @Override
+ protected void onPause() {
+ super.onPause();
+ GnuCashApplication.PASSCODE_SESSION_INIT_TIME = System.currentTimeMillis();
+ }
+
@Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
@@ -165,10 +188,20 @@ public boolean onOptionsItemSelected(MenuItem item) {
@Override
public boolean onPreferenceChange(Preference preference, Object newValue) {
- preference.setSummary(newValue.toString());
if (preference.getKey().equals(getString(R.string.key_default_currency))){
Money.DEFAULT_CURRENCY_CODE = newValue.toString();
- }
+ preference.setSummary(newValue.toString());
+ } else if (preference.getKey().equals(getString(R.string.key_enable_passcode))) {
+ if ((Boolean) newValue) {
+ startActivityForResult(new Intent(this, PasscodePreferenceActivity.class),
+ PasscodePreferenceFragment.PASSCODE_REQUEST_CODE);
+ }
+ PreferenceManager.getDefaultSharedPreferences(getApplicationContext())
+ .edit()
+ .putBoolean(UxArgument.ENABLED_PASSCODE, (Boolean) newValue)
+ .commit();
+ }
+
return true;
}
@@ -233,9 +266,7 @@ public boolean onPreferenceClick(Preference preference) {
transactionsDbAdapter.deleteAllRecords();
if (preserveOpeningBalances) {
- for (Transaction openingBalance : openingBalances) {
- transactionsDbAdapter.addTransaction(openingBalance);
- }
+ transactionsDbAdapter.bulkAddTransactions(openingBalances);
}
transactionsDbAdapter.close();
Toast.makeText(this, R.string.toast_all_transactions_deleted, Toast.LENGTH_LONG).show();
@@ -245,6 +276,12 @@ public boolean onPreferenceClick(Preference preference) {
return true;
}
+ if (key.equals(getString(R.string.key_change_passcode))){
+ startActivityForResult(new Intent(this, PasscodePreferenceActivity.class),
+ PasscodePreferenceFragment.PASSCODE_REQUEST_CODE);
+ return true;
+ }
+
return false;
}
@@ -293,6 +330,13 @@ public void importMostRecentBackup(){
@Override
public void onActivityResult(int requestCode, int resultCode, Intent data) {
if (resultCode == Activity.RESULT_CANCELED){
+ if (requestCode == PasscodePreferenceFragment.PASSCODE_REQUEST_CODE) {
+ PreferenceManager.getDefaultSharedPreferences(getApplicationContext())
+ .edit()
+ .putBoolean(UxArgument.ENABLED_PASSCODE, false)
+ .commit();
+ ((CheckBoxPreference) findPreference(getString(R.string.key_enable_passcode))).setChecked(false);
+ }
return;
}
@@ -305,7 +349,15 @@ public void onActivityResult(int requestCode, int resultCode, Intent data) {
e.printStackTrace();
Toast.makeText(this, R.string.toast_error_importing_accounts, Toast.LENGTH_SHORT).show();
}
-
+ break;
+ case PasscodePreferenceFragment.PASSCODE_REQUEST_CODE:
+ if (data!= null) {
+ PreferenceManager.getDefaultSharedPreferences(getApplicationContext())
+ .edit()
+ .putString(UxArgument.PASSCODE, data.getStringExtra(UxArgument.PASSCODE))
+ .commit();
+ Toast.makeText(getApplicationContext(), R.string.toast_passcode_set, Toast.LENGTH_SHORT).show();
+ }
break;
}
}
diff --git a/app/src/org/gnucash/android/ui/transaction/ScheduledTransactionsListFragment.java b/app/src/org/gnucash/android/ui/transaction/ScheduledTransactionsListFragment.java
index d5a37b397..005a8e92e 100644
--- a/app/src/org/gnucash/android/ui/transaction/ScheduledTransactionsListFragment.java
+++ b/app/src/org/gnucash/android/ui/transaction/ScheduledTransactionsListFragment.java
@@ -183,22 +183,28 @@ public void onListItemClick(ListView l, View v, int position, long id) {
checkbox.setChecked(!checkbox.isChecked());
return;
}
- String accountUID = mTransactionsDbAdapter.getTransaction(id).getSplits().get(0).getAccountUID();
- long accountID = mTransactionsDbAdapter.getAccountID(accountUID);
+ Transaction transaction = mTransactionsDbAdapter.getTransaction(id);
- openTransactionForEdit(accountID, id);
+ //this should actually never happen, but has happened once. So perform check for the future
+ if (transaction.getSplits().size() == 0){
+ Toast.makeText(getActivity(), "The selected transaction has no splits and cannot be opened", Toast.LENGTH_SHORT).show();
+ return;
+ }
+
+ String accountUID = transaction.getSplits().get(0).getAccountUID();
+ openTransactionForEdit(accountUID, mTransactionsDbAdapter.getUID(id));
}
/**
* Opens the transaction editor to enable editing of the transaction
- * @param accountId Account ID of the transaction
- * @param transactionId Transaction to be edited
+ * @param accountUID GUID of account to which transaction belongs
+ * @param transactionUID GUID of transaction to be edited
*/
- public void openTransactionForEdit(long accountId, long transactionId){
+ public void openTransactionForEdit(String accountUID, String transactionUID){
Intent createTransactionIntent = new Intent(getActivity(), TransactionsActivity.class);
createTransactionIntent.setAction(Intent.ACTION_INSERT_OR_EDIT);
- createTransactionIntent.putExtra(UxArgument.SELECTED_ACCOUNT_ID, accountId);
- createTransactionIntent.putExtra(UxArgument.SELECTED_TRANSACTION_ID, transactionId);
+ createTransactionIntent.putExtra(UxArgument.SELECTED_ACCOUNT_UID, accountUID);
+ createTransactionIntent.putExtra(UxArgument.SELECTED_TRANSACTION_UID, transactionUID);
startActivity(createTransactionIntent);
}
@@ -369,7 +375,7 @@ public void run() {
* @return String formatted representation of recurrence period
*/
public String getRecurrenceAsString(long periodMillis){
- String[] recurrencePeriods = getResources().getStringArray(R.array.recurrence_period_millis);
+ String[] recurrencePeriods = getResources().getStringArray(R.array.key_recurrence_period_millis);
String[] recurrenceStrings = getResources().getStringArray(R.array.recurrence_period_strings);
int index = 0;
diff --git a/app/src/org/gnucash/android/ui/transaction/TransactionFormFragment.java b/app/src/org/gnucash/android/ui/transaction/TransactionFormFragment.java
index df9184c6b..aa993df41 100644
--- a/app/src/org/gnucash/android/ui/transaction/TransactionFormFragment.java
+++ b/app/src/org/gnucash/android/ui/transaction/TransactionFormFragment.java
@@ -23,6 +23,7 @@
import java.util.*;
import android.support.v4.app.FragmentManager;
+import android.text.Editable;
import android.widget.*;
import org.gnucash.android.R;
import org.gnucash.android.db.*;
@@ -173,7 +174,7 @@ public class TransactionFormFragment extends SherlockFragment implements
private AmountInputFormatter mAmountInputFormatter;
private Button mOpenSplitsButton;
- private long mAccountId;
+ private String mAccountUID;
private List mSplitsList = new ArrayList();
@@ -215,8 +216,11 @@ public void onActivityCreated(Bundle savedInstanceState) {
mOpenSplitsButton.setVisibility(View.GONE);
}
- //updateTransferAccountsList must only be called after creating mAccountsDbAdapter
+ mAccountUID = getArguments().getString(UxArgument.SELECTED_ACCOUNT_UID);
mAccountsDbAdapter = new AccountsDbAdapter(getActivity());
+ mAccountType = mAccountsDbAdapter.getAccountType(mAccountUID);
+
+ //updateTransferAccountsList must only be called after initializing mAccountsDbAdapter
updateTransferAccountsList();
ArrayAdapter recurrenceAdapter = ArrayAdapter.createFromResource(getActivity(),
@@ -224,19 +228,16 @@ public void onActivityCreated(Bundle savedInstanceState) {
recurrenceAdapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
mRecurringTransactionSpinner.setAdapter(recurrenceAdapter);
- long transactionId = getArguments().getLong(UxArgument.SELECTED_TRANSACTION_ID);
+ String transactionUID = getArguments().getString(UxArgument.SELECTED_TRANSACTION_UID);
mTransactionsDbAdapter = new TransactionsDbAdapter(getActivity());
- mTransaction = mTransactionsDbAdapter.getTransaction(transactionId);
-
- mAccountId = getArguments().getLong(UxArgument.SELECTED_ACCOUNT_ID);
- mAccountType = mAccountsDbAdapter.getAccountType(mAccountId);
+ mTransaction = mTransactionsDbAdapter.getTransaction(transactionUID);
mDoubleAccountSpinner.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
@Override
public void onItemSelected(AdapterView> adapterView, View view, int position, long id) {
if (mSplitsList.size() == 2){ //when handling simple transfer to one account
for (Split split : mSplitsList) {
- if (!split.getAccountUID().equals(mAccountsDbAdapter.getAccountUID(mAccountId))){
+ if (!split.getAccountUID().equals(mAccountUID)){
split.setAccountUID(mAccountsDbAdapter.getAccountUID(id));
}
// else case is handled when saving the transactions
@@ -325,13 +326,12 @@ public void onItemClick(AdapterView> adapterView, View view, int position, lon
private void initializeViewsWithTransaction(){
mDescriptionEditText.setText(mTransaction.getDescription());
- String accountUID = mAccountsDbAdapter.getAccountUID(mAccountId);
mTransactionTypeButton.setAccountType(mAccountType);
- mTransactionTypeButton.setChecked(mTransaction.getBalance(accountUID).isNegative());
+ mTransactionTypeButton.setChecked(mTransaction.getBalance(mAccountUID).isNegative());
if (!mAmountInputFormatter.isInputModified()){
//when autocompleting, only change the amount if the user has not manually changed it already
- mAmountEditText.setText(mTransaction.getBalance(accountUID).toPlainString());
+ mAmountEditText.setText(mTransaction.getBalance(mAccountUID).toPlainString());
}
mCurrencyTextView.setText(mTransaction.getCurrency().getSymbol(Locale.getDefault()));
mNotesEditText.setText(mTransaction.getNote());
@@ -348,7 +348,7 @@ private void initializeViewsWithTransaction(){
} else {
for (Split split : mTransaction.getSplits()) {
//two splits, one belongs to this account and the other to another account
- if (mUseDoubleEntry && !split.getAccountUID().equals(accountUID)) {
+ if (mUseDoubleEntry && !split.getAccountUID().equals(mAccountUID)) {
setSelectedTransferAccount(mAccountsDbAdapter.getAccountID(split.getAccountUID()));
}
}
@@ -356,7 +356,7 @@ private void initializeViewsWithTransaction(){
mSplitsList = new ArrayList(mTransaction.getSplits()); //we need a copy so we can modify with impunity
mAmountEditText.setEnabled(mSplitsList.size() <= 2);
- String currencyCode = mTransactionsDbAdapter.getCurrencyCode(mAccountId);
+ String currencyCode = mTransactionsDbAdapter.getCurrencyCode(mAccountUID);
Currency accountCurrency = Currency.getInstance(currencyCode);
mCurrencyTextView.setText(accountCurrency.getSymbol());
@@ -381,15 +381,16 @@ private void initalizeViews() {
String typePref = PreferenceManager.getDefaultSharedPreferences(getActivity()).getString(getString(R.string.key_default_transaction_type), "DEBIT");
mTransactionTypeButton.setChecked(TransactionType.valueOf(typePref));
- final long accountId = getArguments().getLong(UxArgument.SELECTED_ACCOUNT_ID);
+ final String accountUID = getArguments().getString(UxArgument.SELECTED_ACCOUNT_UID);
String code = Money.DEFAULT_CURRENCY_CODE;
- if (accountId != 0){
- code = mTransactionsDbAdapter.getCurrencyCode(accountId);
+ if (accountUID != null){
+ code = mTransactionsDbAdapter.getCurrencyCode(mAccountUID);
}
Currency accountCurrency = Currency.getInstance(code);
mCurrencyTextView.setText(accountCurrency.getSymbol());
if (mUseDoubleEntry){
+ long accountId = mAccountsDbAdapter.getID(mAccountUID);
long defaultTransferAccountID = mAccountsDbAdapter.getDefaultTransferAccountID(accountId);
if (defaultTransferAccountID > 0){
setSelectedTransferAccount(defaultTransferAccountID);
@@ -405,7 +406,7 @@ private void setSelectedRecurrenceOption() {
//init recurrence options
final long recurrencePeriod = mTransaction.getRecurrencePeriod();
if (recurrencePeriod > 0){
- String[] recurrenceOptions = getResources().getStringArray(R.array.recurrence_period_millis);
+ String[] recurrenceOptions = getResources().getStringArray(R.array.key_recurrence_period_millis);
int selectionIndex = 0;
for (String recurrenceOption : recurrenceOptions) {
@@ -422,10 +423,10 @@ private void setSelectedRecurrenceOption() {
* Only accounts with the same currency can be transferred to
*/
private void updateTransferAccountsList(){
- long accountId = ((TransactionsActivity)getActivity()).getCurrentAccountID();
+ String accountUID = ((TransactionsActivity)getActivity()).getCurrentAccountUID();
- String conditions = "(" + DatabaseSchema.AccountEntry._ID + " != " + accountId + " AND "
- + DatabaseSchema.AccountEntry.COLUMN_CURRENCY + " = '" + mAccountsDbAdapter.getCurrencyCode(accountId)
+ String conditions = "(" + DatabaseSchema.AccountEntry.COLUMN_UID + " != '" + accountUID
+ + "' AND " + DatabaseSchema.AccountEntry.COLUMN_CURRENCY + " = '" + mAccountsDbAdapter.getCurrencyCode(accountUID)
+ "' AND " + DatabaseSchema.AccountEntry.COLUMN_UID + " != '" + mAccountsDbAdapter.getGnuCashRootAccountUID()
+ "' AND " + DatabaseSchema.AccountEntry.COLUMN_PLACEHOLDER + " = 0"
+ ")";
@@ -473,7 +474,7 @@ private void openSplitEditor(){
* Sets click listeners for the dialog buttons
*/
private void setListeners() {
- mAmountInputFormatter = new AmountInputFormatter(mAmountEditText);
+ mAmountInputFormatter = new AmountTextWatcher(mAmountEditText); //new AmountInputFormatter(mAmountEditText);
mAmountEditText.addTextChangedListener(mAmountInputFormatter);
mOpenSplitsButton.setOnClickListener(new View.OnClickListener() {
@@ -574,9 +575,8 @@ private void saveNewTransaction() {
String notes = mNotesEditText.getText().toString();
BigDecimal amountBigd = parseInputToDecimal(mAmountEditText.getText().toString());
- long accountID = ((TransactionsActivity) getSherlockActivity()).getCurrentAccountID();
- String accountUID = mAccountsDbAdapter.getAccountUID(accountID);
- Currency currency = Currency.getInstance(mTransactionsDbAdapter.getCurrencyCode(accountID));
+ String accountUID = ((TransactionsActivity) getSherlockActivity()).getCurrentAccountUID();
+ Currency currency = Currency.getInstance(mTransactionsDbAdapter.getCurrencyCode(accountUID));
Money amount = new Money(amountBigd, currency).absolute();
//capture any edits which were done directly (not using split editor)
@@ -637,7 +637,7 @@ private void saveNewTransaction() {
mTransaction.setSplits(mSplitsList);
}
}
- mTransaction.setCurrencyCode(mAccountsDbAdapter.getCurrencyCode(accountID));
+ mTransaction.setCurrencyCode(mAccountsDbAdapter.getCurrencyCode(mAccountUID));
mTransaction.setTime(cal.getTimeInMillis());
mTransaction.setNote(notes);
@@ -660,7 +660,7 @@ private void scheduleRecurringTransaction() {
//set up recurring transaction if requested
int recurrenceIndex = mRecurringTransactionSpinner.getSelectedItemPosition();
if (recurrenceIndex != 0) {
- String[] recurrenceOptions = getResources().getStringArray(R.array.recurrence_period_millis);
+ String[] recurrenceOptions = getResources().getStringArray(R.array.key_recurrence_period_millis);
long recurrencePeriodMillis = Long.parseLong(recurrenceOptions[recurrenceIndex]);
Transaction recurringTransaction;
if (mTransaction.getRecurrencePeriod() > 0) //if we are editing the recurring transaction itself...
@@ -717,8 +717,7 @@ public boolean onOptionsItemSelected(MenuItem item) {
*/
public void setSplitList(List splitList, List removedSplitUIDs){
mSplitsList = splitList;
- String accountUID = mAccountsDbAdapter.getAccountUID(mAccountId);
- Money balance = Transaction.computeBalance(accountUID, mSplitsList);
+ Money balance = Transaction.computeBalance(mAccountUID, mSplitsList);
mAmountEditText.setText(balance.toPlainString());
//once we set the split list, do not allow direct editing of the total
@@ -792,6 +791,8 @@ public static String stripCurrencyFormatting(String s){
//remove all currency formatting and anything else which is not a number
String sign = s.trim().substring(0,1);
String stripped = s.trim().replaceAll("\\D*", "");
+ if (stripped.length() == 0)
+ return "";
if (sign.equals("+") || sign.equals("-")){
stripped = sign + stripped;
}
@@ -816,5 +817,21 @@ public static BigDecimal parseInputToDecimal(String amountString){
return amount;
}
+ private class AmountTextWatcher extends AmountInputFormatter {
+
+ public AmountTextWatcher(EditText amountInput) {
+ super(amountInput);
+ }
+ @Override
+ public void afterTextChanged(Editable s) {
+ String value = s.toString();
+ if (mTransactionTypeButton.isChecked()){
+ if (s.charAt(0) != '-'){
+ s = Editable.Factory.getInstance().newEditable("-" + value);
+ }
+ }
+ super.afterTextChanged(s);
+ }
+ }
}
diff --git a/app/src/org/gnucash/android/ui/transaction/TransactionsActivity.java b/app/src/org/gnucash/android/ui/transaction/TransactionsActivity.java
index 56474abeb..87d063945 100644
--- a/app/src/org/gnucash/android/ui/transaction/TransactionsActivity.java
+++ b/app/src/org/gnucash/android/ui/transaction/TransactionsActivity.java
@@ -1,5 +1,6 @@
/*
* Copyright (c) 2012 - 2014 Ngewi Fet
+ * Copyright (c) 2014 Yongxin Wang
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -35,33 +36,33 @@
import android.view.inputmethod.InputMethodManager;
import android.widget.SpinnerAdapter;
import android.widget.TextView;
+
import com.actionbarsherlock.app.ActionBar;
import com.actionbarsherlock.app.ActionBar.OnNavigationListener;
-import com.actionbarsherlock.app.SherlockFragmentActivity;
import com.actionbarsherlock.view.Menu;
import com.actionbarsherlock.view.MenuItem;
import com.viewpagerindicator.TitlePageIndicator;
+
import org.gnucash.android.R;
import org.gnucash.android.app.GnuCashApplication;
+import org.gnucash.android.db.AccountsDbAdapter;
import org.gnucash.android.db.DatabaseSchema;
import org.gnucash.android.model.Account;
-import org.gnucash.android.db.AccountsDbAdapter;
-import org.gnucash.android.db.DatabaseAdapter;
-import org.gnucash.android.db.DatabaseHelper;
import org.gnucash.android.model.Money;
-import org.gnucash.android.ui.util.Refreshable;
import org.gnucash.android.ui.UxArgument;
import org.gnucash.android.ui.account.AccountsActivity;
import org.gnucash.android.ui.account.AccountsListFragment;
+import org.gnucash.android.ui.passcode.PassLockActivity;
import org.gnucash.android.ui.util.OnAccountClickedListener;
import org.gnucash.android.ui.util.OnTransactionClickedListener;
+import org.gnucash.android.ui.util.Refreshable;
import org.gnucash.android.util.QualifiedAccountNameCursorAdapter;
/**
* Activity for displaying, creating and editing transactions
* @author Ngewi Fet
*/
-public class TransactionsActivity extends SherlockFragmentActivity implements
+public class TransactionsActivity extends PassLockActivity implements
Refreshable, OnAccountClickedListener, OnTransactionClickedListener{
/**
@@ -103,8 +104,13 @@ public class TransactionsActivity extends SherlockFragmentActivity implements
/**
* Database ID of {@link Account} whose transactions are displayed
*/
- private long mAccountId = 0;
-
+// private long mAccountId = 0;
+
+ /**
+ * GUID of {@link Account} whose transactions are displayed
+ */
+ private String mAccountUID = null;
+
/**
* Flag which is used to determine if the activity is running or not.
* Basically if onCreate has already been called or not. It is used
@@ -124,12 +130,6 @@ public class TransactionsActivity extends SherlockFragmentActivity implements
*/
private Cursor mAccountsCursor = null;
- /**
- * This is the last known color for the title indicator.
- * This is used to remember the color of the top level account if the child account doesn't have one.
- */
- public static int sLastTitleColor = -1;
-
private TextView mSectionHeaderTransactions;
private TitlePageIndicator mTitlePageIndicator;
@@ -139,8 +139,7 @@ public class TransactionsActivity extends SherlockFragmentActivity implements
@Override
public boolean onNavigationItemSelected(int position, long itemId) {
- mAccountId = itemId;
-
+ mAccountUID = mAccountsDbAdapter.getAccountUID(itemId);
FragmentManager fragmentManager = getSupportFragmentManager();
//inform new accounts fragment that account was changed
@@ -227,7 +226,7 @@ public int getCount() {
private AccountsListFragment prepareSubAccountsListFragment(){
AccountsListFragment subAccountsListFragment = new AccountsListFragment();
Bundle args = new Bundle();
- args.putLong(UxArgument.PARENT_ACCOUNT_ID, mAccountId);
+ args.putString(UxArgument.PARENT_ACCOUNT_UID, mAccountUID);
subAccountsListFragment.setArguments(args);
return subAccountsListFragment;
}
@@ -239,10 +238,9 @@ private AccountsListFragment prepareSubAccountsListFragment(){
private TransactionsListFragment prepareTransactionsListFragment(){
TransactionsListFragment transactionsListFragment = new TransactionsListFragment();
Bundle args = new Bundle();
- args.putLong(UxArgument.SELECTED_ACCOUNT_ID,
- mAccountId);
+ args.putString(UxArgument.SELECTED_ACCOUNT_UID, mAccountUID);
transactionsListFragment.setArguments(args);
- Log.i(TAG, "Opening transactions for account id " + mAccountId);
+ Log.i(TAG, "Opening transactions for account: " + mAccountUID);
return transactionsListFragment;
}
}
@@ -252,23 +250,23 @@ private TransactionsListFragment prepareTransactionsListFragment(){
* @return true is the current account is a placeholder account, false otherwise.
*/
private boolean isPlaceHolderAccount(){
- return mAccountsDbAdapter.isPlaceholderAccount(mAccountId);
+ return mAccountsDbAdapter.isPlaceholderAccount(mAccountUID);
}
/**
* Refreshes the fragments currently in the transactions activity
*/
@Override
- public void refresh(long accountId) {
+ public void refresh(String accountUID) {
for (int i = 0; i < mFragmentPageReferenceMap.size(); i++) {
- mFragmentPageReferenceMap.valueAt(i).refresh(accountId);
+ mFragmentPageReferenceMap.valueAt(i).refresh(accountUID);
}
mTitlePageIndicator.notifyDataSetChanged();
}
@Override
public void refresh(){
- refresh(mAccountId);
+ refresh(mAccountUID);
setTitleIndicatorColor();
}
@@ -281,11 +279,7 @@ protected void onCreate(Bundle savedInstanceState) {
mTitlePageIndicator = (TitlePageIndicator) findViewById(R.id.titles);
mSectionHeaderTransactions = (TextView) findViewById(R.id.section_header_transactions);
- if (sLastTitleColor == -1) //if this is first launch of app. Previous launches would have set the color already
- sLastTitleColor = getResources().getColor(R.color.title_green);
-
- mAccountId = getIntent().getLongExtra(
- UxArgument.SELECTED_ACCOUNT_ID, -1);
+ mAccountUID = getIntent().getStringExtra(UxArgument.SELECTED_ACCOUNT_UID);
mAccountsDbAdapter = new AccountsDbAdapter(this);
@@ -314,17 +308,16 @@ protected void onCreate(Bundle savedInstanceState) {
* Loads the fragment for creating/editing transactions and initializes it to be displayed
*/
private void initializeCreateOrEditTransaction() {
- long transactionId = getIntent().getLongExtra(UxArgument.SELECTED_TRANSACTION_ID, -1);
+ String transactionUID = getIntent().getStringExtra(UxArgument.SELECTED_TRANSACTION_UID);
Bundle args = new Bundle();
- if (transactionId > 0) {
+ if (transactionUID != null) {
mSectionHeaderTransactions.setText(R.string.title_edit_transaction);
- args.putLong(UxArgument.SELECTED_TRANSACTION_ID, transactionId);
- args.putLong(UxArgument.SELECTED_ACCOUNT_ID, mAccountId);
+ args.putString(UxArgument.SELECTED_TRANSACTION_UID, transactionUID);
+ args.putString(UxArgument.SELECTED_ACCOUNT_UID, mAccountUID);
} else {
mSectionHeaderTransactions.setText(R.string.title_add_transaction);
- args.putLong(UxArgument.SELECTED_ACCOUNT_ID, mAccountId);
+ args.putString(UxArgument.SELECTED_ACCOUNT_UID, mAccountUID);
}
- mSectionHeaderTransactions.setBackgroundColor(sLastTitleColor);
showTransactionFormFragment(args);
}
@@ -340,15 +333,29 @@ protected void onResume() {
private void setTitleIndicatorColor() {
//Basically, if we are in a top level account, use the default title color.
//but propagate a parent account's title color to children who don't have own color
- String colorCode = mAccountsDbAdapter.getAccountColorCode(mAccountId);
+ String colorCode = mAccountsDbAdapter.getAccountColorCode(mAccountsDbAdapter.getAccountID(mAccountUID));
+ int iColor = -1;
if (colorCode != null){
- sLastTitleColor = Color.parseColor(colorCode);
+ iColor = Color.parseColor(colorCode);
+ } else {
+ String accountUID = mAccountUID;
+ while ((accountUID = mAccountsDbAdapter.getParentAccountUID(accountUID)) != null) {
+ colorCode = mAccountsDbAdapter.getAccountColorCode(mAccountsDbAdapter.getAccountID(accountUID));
+ if (colorCode != null) {
+ iColor = Color.parseColor(colorCode);
+ break;
+ }
+ }
+ if (colorCode == null)
+ {
+ iColor = getResources().getColor(R.color.title_green);
+ }
}
- mTitlePageIndicator.setSelectedColor(sLastTitleColor);
- mTitlePageIndicator.setTextColor(sLastTitleColor);
- mTitlePageIndicator.setFooterColor(sLastTitleColor);
- mSectionHeaderTransactions.setBackgroundColor(sLastTitleColor);
+ mTitlePageIndicator.setSelectedColor(iColor);
+ mTitlePageIndicator.setTextColor(iColor);
+ mTitlePageIndicator.setFooterColor(iColor);
+ mSectionHeaderTransactions.setBackgroundColor(iColor);
}
/**
@@ -384,8 +391,8 @@ public void updateNavigationSelection() {
int i = 0;
Cursor accountsCursor = mAccountsDbAdapter.fetchAllRecordsOrderedByFullName();
while (accountsCursor.moveToNext()) {
- long id = accountsCursor.getLong(accountsCursor.getColumnIndexOrThrow(DatabaseSchema.AccountEntry._ID));
- if (mAccountId == id) {
+ String uid = accountsCursor.getString(accountsCursor.getColumnIndexOrThrow(DatabaseSchema.AccountEntry.COLUMN_UID));
+ if (mAccountUID.equals(uid)) {
getSupportActionBar().setSelectedNavigationItem(i);
break;
}
@@ -403,7 +410,7 @@ public boolean onPrepareOptionsMenu(Menu menu) {
return super.onPrepareOptionsMenu(menu);
AccountsDbAdapter accountsDbAdapter = new AccountsDbAdapter(this);
- boolean isFavoriteAccount = accountsDbAdapter.isFavoriteAccount(mAccountId);
+ boolean isFavoriteAccount = accountsDbAdapter.isFavoriteAccount(mAccountsDbAdapter.getAccountID(mAccountUID));
accountsDbAdapter.close();
int favoriteIcon = isFavoriteAccount ? android.R.drawable.btn_star_big_on : android.R.drawable.btn_star_big_off;
@@ -429,9 +436,10 @@ public boolean onOptionsItemSelected(MenuItem item) {
case R.id.menu_favorite_account:
AccountsDbAdapter accountsDbAdapter = new AccountsDbAdapter(this);
- boolean isFavorite = accountsDbAdapter.isFavoriteAccount(mAccountId);
+ long accountId = accountsDbAdapter.getAccountID(mAccountUID);
+ boolean isFavorite = accountsDbAdapter.isFavoriteAccount(accountId);
//toggle favorite preference
- accountsDbAdapter.updateAccount(mAccountId, DatabaseSchema.AccountEntry.COLUMN_FAVORITE, isFavorite ? "0" : "1");
+ accountsDbAdapter.updateAccount(accountId, DatabaseSchema.AccountEntry.COLUMN_FAVORITE, isFavorite ? "0" : "1");
accountsDbAdapter.close();
supportInvalidateOptionsMenu();
return true;
@@ -439,7 +447,7 @@ public boolean onOptionsItemSelected(MenuItem item) {
case R.id.menu_edit_account:
Intent editAccountIntent = new Intent(this, AccountsActivity.class);
editAccountIntent.setAction(Intent.ACTION_INSERT_OR_EDIT);
- editAccountIntent.putExtra(UxArgument.SELECTED_ACCOUNT_ID, mAccountId);
+ editAccountIntent.putExtra(UxArgument.SELECTED_ACCOUNT_UID, mAccountUID);
startActivityForResult(editAccountIntent, AccountsActivity.REQUEST_EDIT_ACCOUNT);
return true;
@@ -465,11 +473,11 @@ protected void onDestroy() {
}
/**
- * Returns the database row ID of the current account
- * @return Database row ID of the current account
+ * Returns the global unique ID of the current account
+ * @return GUID of the current account
*/
- public long getCurrentAccountID(){
- return mAccountId;
+ public String getCurrentAccountUID(){
+ return mAccountUID;
}
/**
@@ -478,7 +486,7 @@ public long getCurrentAccountID(){
* @param v View which triggered this method
*/
public void onNewTransactionClick(View v){
- createNewTransaction(mAccountId);
+ createNewTransaction(mAccountUID);
}
@@ -489,7 +497,7 @@ public void onNewTransactionClick(View v){
public void onNewAccountClick(View v) {
Intent addAccountIntent = new Intent(this, AccountsActivity.class);
addAccountIntent.setAction(Intent.ACTION_INSERT_OR_EDIT);
- addAccountIntent.putExtra(UxArgument.PARENT_ACCOUNT_ID, mAccountId);
+ addAccountIntent.putExtra(UxArgument.PARENT_ACCOUNT_UID, mAccountUID);
startActivityForResult(addAccountIntent, AccountsActivity.REQUEST_EDIT_ACCOUNT);
}
@@ -528,27 +536,27 @@ public static void displayBalance(TextView balanceTextView, Money balance){
}
@Override
- public void createNewTransaction(long accountRowId) {
+ public void createNewTransaction(String accountUID) {
Intent createTransactionIntent = new Intent(this.getApplicationContext(), TransactionsActivity.class);
createTransactionIntent.setAction(Intent.ACTION_INSERT_OR_EDIT);
- createTransactionIntent.putExtra(UxArgument.SELECTED_ACCOUNT_ID, accountRowId);
+ createTransactionIntent.putExtra(UxArgument.SELECTED_ACCOUNT_UID, accountUID);
startActivity(createTransactionIntent);
}
@Override
- public void editTransaction(long transactionId){
+ public void editTransaction(String transactionUID){
Intent createTransactionIntent = new Intent(this.getApplicationContext(), TransactionsActivity.class);
createTransactionIntent.setAction(Intent.ACTION_INSERT_OR_EDIT);
- createTransactionIntent.putExtra(UxArgument.SELECTED_ACCOUNT_ID, mAccountId);
- createTransactionIntent.putExtra(UxArgument.SELECTED_TRANSACTION_ID, transactionId);
+ createTransactionIntent.putExtra(UxArgument.SELECTED_ACCOUNT_UID, mAccountUID);
+ createTransactionIntent.putExtra(UxArgument.SELECTED_TRANSACTION_UID, transactionUID);
startActivity(createTransactionIntent);
}
@Override
- public void accountSelected(long accountRowId) {
+ public void accountSelected(String accountUID) {
Intent restartIntent = new Intent(this.getApplicationContext(), TransactionsActivity.class);
restartIntent.setAction(Intent.ACTION_VIEW);
- restartIntent.putExtra(UxArgument.SELECTED_ACCOUNT_ID, accountRowId);
+ restartIntent.putExtra(UxArgument.SELECTED_ACCOUNT_UID, accountUID);
startActivity(restartIntent);
}
}
diff --git a/app/src/org/gnucash/android/ui/transaction/TransactionsListFragment.java b/app/src/org/gnucash/android/ui/transaction/TransactionsListFragment.java
index 0e1beaf47..337262b80 100644
--- a/app/src/org/gnucash/android/ui/transaction/TransactionsListFragment.java
+++ b/app/src/org/gnucash/android/ui/transaction/TransactionsListFragment.java
@@ -76,7 +76,8 @@ public class TransactionsListFragment extends SherlockListFragment implements
private SimpleCursorAdapter mCursorAdapter;
private ActionMode mActionMode = null;
private boolean mInEditMode = false;
- private long mAccountID;
+// private long mAccountID;
+ private String mAccountUID;
/**
* Callback listener for editing transactions
@@ -118,7 +119,7 @@ public boolean onActionItemClicked(ActionMode mode, MenuItem item) {
case R.id.context_menu_delete:
SplitsDbAdapter splitsDbAdapter = new SplitsDbAdapter(getActivity());
for (long id : getListView().getCheckedItemIds()) {
- splitsDbAdapter.deleteSplitsForTransactionAndAccount(id, mAccountID);
+ splitsDbAdapter.deleteSplitsForTransactionAndAccount(mTransactionsDbAdapter.getUID(id), mAccountUID);
}
splitsDbAdapter.close();
refresh();
@@ -132,17 +133,12 @@ public boolean onActionItemClicked(ActionMode mode, MenuItem item) {
}
};
- /**
- * Text view displaying the sum of the accounts
- */
- private TextView mSumTextView;
-
- @Override
+ @Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setHasOptionsMenu(true);
Bundle args = getArguments();
- mAccountID = args.getLong(UxArgument.SELECTED_ACCOUNT_ID);
+ mAccountUID = args.getString(UxArgument.SELECTED_ACCOUNT_UID);
mTransactionsDbAdapter = new TransactionsDbAdapter(getActivity());
mCursorAdapter = new TransactionsCursorAdapter(
@@ -173,11 +169,11 @@ public void onActivityCreated(Bundle savedInstanceState) {
/**
* Refresh the list with transactions from account with ID accountId
- * @param accountId Database ID of account to load transactions from
+ * @param accountUID GUID of account to load transactions from
*/
@Override
- public void refresh(long accountId){
- mAccountID = accountId;
+ public void refresh(String accountUID){
+ mAccountUID = accountUID;
refresh();
}
@@ -188,8 +184,11 @@ public void refresh(long accountId){
public void refresh(){
getLoaderManager().restartLoader(0, null, this);
- mSumTextView = (TextView) getView().findViewById(R.id.transactions_sum);
- new AccountBalanceTask(mSumTextView, getActivity()).execute(mAccountID);
+ /*
+ Text view displaying the sum of the accounts
+ */
+ TextView mSumTextView = (TextView) getView().findViewById(R.id.transactions_sum);
+ new AccountBalanceTask(mSumTextView, getActivity()).execute(mAccountUID);
}
@@ -207,7 +206,7 @@ public void onAttach(Activity activity) {
public void onResume() {
super.onResume();
((TransactionsActivity)getSherlockActivity()).updateNavigationSelection();
- refresh(((TransactionsActivity) getActivity()).getCurrentAccountID());
+ refresh(((TransactionsActivity) getActivity()).getCurrentAccountUID());
}
@Override
@@ -224,7 +223,7 @@ public void onListItemClick(ListView l, View v, int position, long id) {
checkbox.setChecked(!checkbox.isChecked());
return;
}
- mTransactionEditListener.editTransaction(id);
+ mTransactionEditListener.editTransaction(mTransactionsDbAdapter.getUID(id));
}
@Override
@@ -236,7 +235,7 @@ public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
case R.id.menu_add_transaction:
- mTransactionEditListener.createNewTransaction(mAccountID);
+ mTransactionEditListener.createNewTransaction(mAccountUID);
return true;
default:
@@ -247,7 +246,7 @@ public boolean onOptionsItemSelected(MenuItem item) {
@Override
public Loader onCreateLoader(int arg0, Bundle arg1) {
Log.d(LOG_TAG, "Creating transactions loader");
- return new TransactionsCursorLoader(getActivity(), mAccountID);
+ return new TransactionsCursorLoader(getActivity(), mAccountUID);
}
@Override
@@ -316,11 +315,10 @@ private void startActionMode(){
*/
private void stopActionMode(){
int checkedCount = getListView().getCheckedItemIds().length;
- if (checkedCount > 0 || mActionMode == null)
- return;
- else
- mActionMode.finish();
- }
+ if (checkedCount <= 0 && mActionMode != null) {
+ mActionMode.finish();
+ }
+ }
/**
* Prepares and displays the dialog for bulk moving transactions to another account
@@ -337,7 +335,7 @@ protected void showBulkMoveDialog(){
// Create and show the dialog.
DialogFragment bulkMoveFragment = new BulkMoveDialogFragment();
Bundle args = new Bundle();
- args.putLong(UxArgument.ORIGIN_ACCOUNT_ID, mAccountID);
+ args.putString(UxArgument.ORIGIN_ACCOUNT_UID, mAccountUID);
args.putLongArray(UxArgument.SELECTED_TRANSACTION_IDS, getListView().getCheckedItemIds());
bulkMoveFragment.setArguments(args);
bulkMoveFragment.setTargetFragment(this, 0);
@@ -414,8 +412,8 @@ public void run() {
public void bindView(View view, Context context, Cursor cursor) {
super.bindView(view, context, cursor);
- long transactionId = cursor.getLong(cursor.getColumnIndexOrThrow(DatabaseSchema.TransactionEntry._ID));
- Money amount = mTransactionsDbAdapter.getBalance(transactionId, mAccountID);
+ String transactionUID = cursor.getString(cursor.getColumnIndexOrThrow(DatabaseSchema.TransactionEntry.COLUMN_UID));
+ Money amount = mTransactionsDbAdapter.getBalance(transactionUID, mAccountUID);
TextView amountTextView = (TextView) view.findViewById(R.id.transaction_amount);
TransactionsActivity.displayBalance(amountTextView, amount);
@@ -485,17 +483,17 @@ private boolean isSameDay(long timeMillis1, long timeMillis2){
* @author Ngewi Fet
*/
protected static class TransactionsCursorLoader extends DatabaseCursorLoader {
- private long accountID;
+ private String accountUID;
- public TransactionsCursorLoader(Context context, long accountID) {
+ public TransactionsCursorLoader(Context context, String accountUID) {
super(context);
- this.accountID = accountID;
+ this.accountUID = accountUID;
}
@Override
public Cursor loadInBackground() {
mDatabaseAdapter = new TransactionsDbAdapter(getContext());
- Cursor c = ((TransactionsDbAdapter) mDatabaseAdapter).fetchAllTransactionsForAccount(accountID);
+ Cursor c = ((TransactionsDbAdapter) mDatabaseAdapter).fetchAllTransactionsForAccount(accountUID);
if (c != null)
registerContentObserver(c);
return c;
diff --git a/app/src/org/gnucash/android/ui/transaction/dialog/BulkMoveDialogFragment.java b/app/src/org/gnucash/android/ui/transaction/dialog/BulkMoveDialogFragment.java
index cb6b4ddac..af55c964c 100644
--- a/app/src/org/gnucash/android/ui/transaction/dialog/BulkMoveDialogFragment.java
+++ b/app/src/org/gnucash/android/ui/transaction/dialog/BulkMoveDialogFragment.java
@@ -18,7 +18,6 @@
import org.gnucash.android.R;
import org.gnucash.android.db.AccountsDbAdapter;
-import org.gnucash.android.db.DatabaseHelper;
import org.gnucash.android.db.DatabaseSchema;
import org.gnucash.android.db.TransactionsDbAdapter;
import org.gnucash.android.ui.UxArgument;
@@ -29,7 +28,6 @@
import android.database.Cursor;
import android.os.Bundle;
import android.support.v4.app.DialogFragment;
-import android.support.v4.app.Fragment;
import android.support.v4.widget.SimpleCursorAdapter;
import android.view.LayoutInflater;
import android.view.View;
@@ -67,9 +65,9 @@ public class BulkMoveDialogFragment extends DialogFragment {
long[] mTransactionIds = null;
/**
- * Account from which to move the transactions
+ * GUID of account from which to move the transactions
*/
- long mOriginAccountId = -1;
+ String mOriginAccountUID = null;
/**
* Accounts database adapter
@@ -99,15 +97,15 @@ public void onActivityCreated(Bundle savedInstanceState) {
Bundle args = getArguments();
mTransactionIds = args.getLongArray(UxArgument.SELECTED_TRANSACTION_IDS);
- mOriginAccountId = args.getLong(UxArgument.ORIGIN_ACCOUNT_ID);
+ mOriginAccountUID = args.getString(UxArgument.ORIGIN_ACCOUNT_UID);
String title = getActivity().getString(R.string.title_move_transactions,
mTransactionIds.length);
getDialog().setTitle(title);
mAccountsDbAdapter = new AccountsDbAdapter(getActivity());
- String conditions = "(" + DatabaseSchema.AccountEntry._ID + " != " + mOriginAccountId + " AND "
- + DatabaseSchema.AccountEntry.COLUMN_CURRENCY + " = '" + mAccountsDbAdapter.getCurrencyCode(mOriginAccountId)
+ String conditions = "(" + DatabaseSchema.AccountEntry.COLUMN_UID + " != '" + mOriginAccountUID + "' AND "
+ + DatabaseSchema.AccountEntry.COLUMN_CURRENCY + " = '" + mAccountsDbAdapter.getCurrencyCode(mOriginAccountUID)
+ "' AND " + DatabaseSchema.AccountEntry.COLUMN_UID + " != '" + mAccountsDbAdapter.getGnuCashRootAccountUID()
+ "' AND " + DatabaseSchema.AccountEntry.COLUMN_PLACEHOLDER + " = 0"
+ ")";
@@ -142,13 +140,14 @@ public void onClick(View v) {
long dstAccountId = mDestinationAccountSpinner.getSelectedItemId();
TransactionsDbAdapter trxnAdapter = new TransactionsDbAdapter(getActivity());
- if (!trxnAdapter.getCurrencyCode(dstAccountId).equals(trxnAdapter.getCurrencyCode(mOriginAccountId))){
+ if (!trxnAdapter.getCurrencyCode(dstAccountId).equals(trxnAdapter.getCurrencyCode(mOriginAccountUID))){
Toast.makeText(getActivity(), R.string.toast_incompatible_currency, Toast.LENGTH_LONG).show();
return;
}
- long accountId = ((TransactionsActivity)getActivity()).getCurrentAccountID();
+ String srcAccountUID = ((TransactionsActivity)getActivity()).getCurrentAccountUID();
+ String dstAccountUID = trxnAdapter.getAccountUID(dstAccountId);
for (long trxnId : mTransactionIds) {
- trxnAdapter.moveTranscation(trxnId, accountId, dstAccountId);
+ trxnAdapter.moveTranscation(trxnAdapter.getUID(trxnId), srcAccountUID, dstAccountUID);
}
trxnAdapter.close();
diff --git a/app/src/org/gnucash/android/ui/transaction/dialog/DatePickerDialogFragment.java b/app/src/org/gnucash/android/ui/transaction/dialog/DatePickerDialogFragment.java
index b64c64625..862fc7d27 100644
--- a/app/src/org/gnucash/android/ui/transaction/dialog/DatePickerDialogFragment.java
+++ b/app/src/org/gnucash/android/ui/transaction/dialog/DatePickerDialogFragment.java
@@ -57,7 +57,7 @@ public DatePickerDialogFragment() {
* @param dateMillis Time in milliseconds to which to initialize the dialog
*/
public DatePickerDialogFragment(OnDateSetListener callback, long dateMillis) {
- mDateSetListener = (OnDateSetListener) callback;
+ mDateSetListener = callback;
if (dateMillis > 0){
mDate = new GregorianCalendar();
mDate.setTimeInMillis(dateMillis);
diff --git a/app/src/org/gnucash/android/ui/transaction/dialog/SplitEditorDialogFragment.java b/app/src/org/gnucash/android/ui/transaction/dialog/SplitEditorDialogFragment.java
index 59875cd24..09de0d8b7 100644
--- a/app/src/org/gnucash/android/ui/transaction/dialog/SplitEditorDialogFragment.java
+++ b/app/src/org/gnucash/android/ui/transaction/dialog/SplitEditorDialogFragment.java
@@ -62,7 +62,6 @@ public class SplitEditorDialogFragment extends DialogFragment {
private Cursor mCursor;
private SimpleCursorAdapter mCursorAdapter;
private List mSplitItemViewList;
- private long mAccountId;
private String mAccountUID;
private BalanceTextWatcher mBalanceUpdater = new BalanceTextWatcher();
@@ -155,12 +154,11 @@ private void initArgs() {
mAccountsDbAdapter = new AccountsDbAdapter(getActivity());
Bundle args = getArguments();
- mAccountId = ((TransactionsActivity)getActivity()).getCurrentAccountID();
- mAccountUID = mAccountsDbAdapter.getAccountUID(mAccountId);
+ mAccountUID = ((TransactionsActivity)getActivity()).getCurrentAccountUID();
mBaseAmount = new BigDecimal(args.getString(UxArgument.AMOUNT_STRING));
String conditions = "(" //+ AccountEntry._ID + " != " + mAccountId + " AND "
- + DatabaseSchema.AccountEntry.COLUMN_CURRENCY + " = '" + mAccountsDbAdapter.getCurrencyCode(mAccountId)
+ + DatabaseSchema.AccountEntry.COLUMN_CURRENCY + " = '" + mAccountsDbAdapter.getCurrencyCode(mAccountUID)
+ "' AND " + DatabaseSchema.AccountEntry.COLUMN_UID + " != '" + mAccountsDbAdapter.getGnuCashRootAccountUID()
+ "' AND " + DatabaseSchema.AccountEntry.COLUMN_PLACEHOLDER + " = 0"
+ ")";
@@ -196,7 +194,7 @@ public void onClick(View view) {
updateTransferAccountsList(accountsSpinner);
accountsSpinner.setOnItemSelectedListener(new TypeButtonLabelUpdater(splitTypeButton));
- Currency accountCurrency = Currency.getInstance(mAccountsDbAdapter.getCurrencyCode(mAccountId));
+ Currency accountCurrency = Currency.getInstance(mAccountsDbAdapter.getCurrencyCode(mAccountUID));
splitCurrencyTextView.setText(accountCurrency.getSymbol());
splitTypeButton.setAmountFormattingListener(splitAmountEditText, splitCurrencyTextView);
splitTypeButton.setChecked(mBaseAmount.signum() > 0);
@@ -307,7 +305,7 @@ private List extractSplitsFromView(){
*/
private void updateTotal(){
List splitList = extractSplitsFromView();
- String currencyCode = mAccountsDbAdapter.getCurrencyCode(mAccountId);
+ String currencyCode = mAccountsDbAdapter.getCurrencyCode(mAccountUID);
Money splitSum = Money.createZeroInstance(currencyCode);
for (Split split : splitList) {
diff --git a/app/src/org/gnucash/android/ui/transaction/dialog/TransactionsDeleteConfirmationDialogFragment.java b/app/src/org/gnucash/android/ui/transaction/dialog/TransactionsDeleteConfirmationDialogFragment.java
index 1594f6202..cc90ff299 100644
--- a/app/src/org/gnucash/android/ui/transaction/dialog/TransactionsDeleteConfirmationDialogFragment.java
+++ b/app/src/org/gnucash/android/ui/transaction/dialog/TransactionsDeleteConfirmationDialogFragment.java
@@ -1,5 +1,6 @@
/*
* Copyright (c) 2013 - 2014 Ngewi Fet
+ * Copyright (c) 2014 Yongxin Wang
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -77,9 +78,7 @@ public void onClick(DialogInterface dialog, int whichButton) {
transactionsDbAdapter.deleteAllRecords();
if (preserveOpeningBalances) {
- for (Transaction openingBalance : openingBalances) {
- transactionsDbAdapter.addTransaction(openingBalance);
- }
+ transactionsDbAdapter.bulkAddTransactions(openingBalances);
}
} else {
transactionsDbAdapter.deleteRecord(rowId);
diff --git a/app/src/org/gnucash/android/ui/util/AccountBalanceTask.java b/app/src/org/gnucash/android/ui/util/AccountBalanceTask.java
index 4d80c40d6..2deef3bc3 100644
--- a/app/src/org/gnucash/android/ui/util/AccountBalanceTask.java
+++ b/app/src/org/gnucash/android/ui/util/AccountBalanceTask.java
@@ -1,5 +1,6 @@
/*
* Copyright (c) 2014 Ngewi Fet
+ * Copyright (c) 2014 Yongxin Wang
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -31,7 +32,7 @@
* This is done asynchronously because in cases of deeply nested accounts,
* it can take some time and would block the UI thread otherwise.
*/
-public class AccountBalanceTask extends AsyncTask {
+public class AccountBalanceTask extends AsyncTask {
public static final String LOG_TAG = AccountBalanceTask.class.getName();
private final WeakReference accountBalanceTextViewReference;
@@ -43,7 +44,7 @@ public AccountBalanceTask(TextView balanceTextView, Context context){
}
@Override
- protected Money doInBackground(Long... params) {
+ protected Money doInBackground(String... params) {
//if the view for which we are doing this job is dead, kill the job as well
if (accountBalanceTextViewReference.get() == null){
cancel(true);
@@ -57,6 +58,9 @@ protected Money doInBackground(Long... params) {
//sometimes a load computation has been started and the data set changes.
//the account ID may no longer exist. So we catch that exception here and do nothing
Log.e(LOG_TAG, "Error computing account balance: " + ex);
+ } catch (Exception ex) {
+ Log.e(LOG_TAG, "Error computing account balance: " + ex);
+ ex.printStackTrace();
}
return balance;
}
diff --git a/app/src/org/gnucash/android/ui/util/OnAccountClickedListener.java b/app/src/org/gnucash/android/ui/util/OnAccountClickedListener.java
index 1ca690186..fb75cba2a 100644
--- a/app/src/org/gnucash/android/ui/util/OnAccountClickedListener.java
+++ b/app/src/org/gnucash/android/ui/util/OnAccountClickedListener.java
@@ -27,8 +27,8 @@ public interface OnAccountClickedListener {
/**
* Callback when an account is selected (clicked) from in a list of accounts
- * @param accountRowId Database row ID of the selected account
+ * @param accountUID GUID of the selected account
*/
- public void accountSelected(long accountRowId);
+ public void accountSelected(String accountUID);
}
diff --git a/app/src/org/gnucash/android/ui/util/OnTransactionClickedListener.java b/app/src/org/gnucash/android/ui/util/OnTransactionClickedListener.java
index c4639a208..9be539055 100644
--- a/app/src/org/gnucash/android/ui/util/OnTransactionClickedListener.java
+++ b/app/src/org/gnucash/android/ui/util/OnTransactionClickedListener.java
@@ -28,13 +28,13 @@ public interface OnTransactionClickedListener {
/**
* Callback for creating a new transaction
- * @param accountRowId Database row ID of the account in which to create the new transaction
+ * @param accountUID GUID of the account in which to create the new transaction
*/
- public void createNewTransaction(long accountRowId);
+ public void createNewTransaction(String accountUID);
/**
* Callback request to edit a transaction
- * @param transactionId Database row Id of the transaction to be edited
+ * @param transactionUID GUID of the transaction to be edited
*/
- public void editTransaction(long transactionId);
+ public void editTransaction(String transactionUID);
}
diff --git a/app/src/org/gnucash/android/ui/util/Refreshable.java b/app/src/org/gnucash/android/ui/util/Refreshable.java
index 7ca7f3777..eb1c78c0b 100644
--- a/app/src/org/gnucash/android/ui/util/Refreshable.java
+++ b/app/src/org/gnucash/android/ui/util/Refreshable.java
@@ -28,7 +28,7 @@ public interface Refreshable {
/**
* Refresh the list with modified parameters
- * @param id Record ID of relevant item to be refreshed
+ * @param uid GUID of relevant item to be refreshed
*/
- public void refresh(long id);
+ public void refresh(String uid);
}
diff --git a/app/src/org/gnucash/android/ui/util/TaskDelegate.java b/app/src/org/gnucash/android/ui/util/TaskDelegate.java
new file mode 100644
index 000000000..04953c058
--- /dev/null
+++ b/app/src/org/gnucash/android/ui/util/TaskDelegate.java
@@ -0,0 +1,30 @@
+/*
+ * Copyright (c) 2014 Ngewi Fet
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gnucash.android.ui.util;
+
+/**
+ * Interface for delegates which can be used to execute functions when an AsyncTask is complete
+ * @see org.gnucash.android.importer.ImportAsyncTask
+ * @author Ngewi Fet
+ */
+public interface TaskDelegate {
+
+ /**
+ * Function to execute on completion of task
+ */
+ public void onTaskComplete();
+}
diff --git a/app/src/org/gnucash/android/ui/widget/WidgetConfigurationActivity.java b/app/src/org/gnucash/android/ui/widget/WidgetConfigurationActivity.java
index 726becd89..39fe72660 100644
--- a/app/src/org/gnucash/android/ui/widget/WidgetConfigurationActivity.java
+++ b/app/src/org/gnucash/android/ui/widget/WidgetConfigurationActivity.java
@@ -48,7 +48,7 @@
import org.gnucash.android.util.QualifiedAccountNameCursorAdapter;
/**
- * Activity for configuration which account to diplay on a widget.
+ * Activity for configuration which account to display on a widget.
* The activity is opened each time a widget is added to the homescreen
* @author Ngewi Fet
*/
@@ -115,12 +115,13 @@ public void onClick(View v) {
}
long accountId = mAccountsSpinner.getSelectedItemId();
+ String accountUID = mAccountsDbAdapter.getUID(accountId);
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(WidgetConfigurationActivity.this);
Editor editor = prefs.edit();
- editor.putLong(UxArgument.SELECTED_ACCOUNT_ID + mAppWidgetId, accountId);
+ editor.putString(UxArgument.SELECTED_ACCOUNT_UID + mAppWidgetId, accountUID);
editor.commit();
- updateWidget(WidgetConfigurationActivity.this, mAppWidgetId, accountId);
+ updateWidget(WidgetConfigurationActivity.this, mAppWidgetId, accountUID);
Intent resultValue = new Intent();
resultValue.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, mAppWidgetId);
@@ -143,14 +144,14 @@ public void onClick(View v) {
* 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 accountId Database ID of the account tied to the widget
+ * @param accountUID GUID of the account tied to the widget
*/
- public static void updateWidget(Context context, int appWidgetId, long accountId) {
+ public static void updateWidget(Context context, int appWidgetId, String accountUID) {
Log.i("WidgetConfiguration", "Updating widget: " + appWidgetId);
AppWidgetManager appWidgetManager = AppWidgetManager.getInstance(context);
AccountsDbAdapter accountsDbAdapter = new AccountsDbAdapter(context);
- Account account = accountsDbAdapter.getAccount(accountId);
+ Account account = accountsDbAdapter.getAccount(accountUID);
if (account == null){
@@ -167,7 +168,7 @@ public static void updateWidget(Context context, int appWidgetId, long accountId
views.setOnClickPendingIntent(R.id.btn_new_transaction, pendingIntent);
appWidgetManager.updateAppWidget(appWidgetId, views);
Editor editor = PreferenceManager.getDefaultSharedPreferences(context).edit();
- editor.remove(UxArgument.SELECTED_ACCOUNT_ID + appWidgetId);
+ editor.remove(UxArgument.SELECTED_ACCOUNT_UID + appWidgetId);
editor.commit();
return;
}
@@ -175,7 +176,7 @@ public static void updateWidget(Context context, int appWidgetId, long accountId
RemoteViews views = new RemoteViews(context.getPackageName(),
R.layout.widget_4x1);
views.setTextViewText(R.id.account_name, account.getName());
- Money accountBalance = accountsDbAdapter.getAccountBalance(accountId);
+ Money accountBalance = accountsDbAdapter.getAccountBalance(accountUID);
views.setTextViewText(R.id.transactions_summary,
accountBalance.formattedString(Locale.getDefault()));
@@ -187,7 +188,7 @@ public static void updateWidget(Context context, int appWidgetId, long accountId
Intent accountViewIntent = new Intent(context, TransactionsActivity.class);
accountViewIntent.setAction(Intent.ACTION_VIEW);
accountViewIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK|Intent.FLAG_ACTIVITY_CLEAR_TASK);
- accountViewIntent.putExtra(UxArgument.SELECTED_ACCOUNT_ID, accountId);
+ accountViewIntent.putExtra(UxArgument.SELECTED_ACCOUNT_UID, accountUID);
PendingIntent accountPendingIntent = PendingIntent
.getActivity(context, appWidgetId, accountViewIntent, 0);
views.setOnClickPendingIntent(R.id.widget_layout, accountPendingIntent);
@@ -195,7 +196,7 @@ public static void updateWidget(Context context, int appWidgetId, long accountId
Intent newTransactionIntent = new Intent(context, TransactionsActivity.class);
newTransactionIntent.setAction(Intent.ACTION_INSERT_OR_EDIT);
newTransactionIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK|Intent.FLAG_ACTIVITY_CLEAR_TASK);
- newTransactionIntent.putExtra(UxArgument.SELECTED_ACCOUNT_ID, accountId);
+ newTransactionIntent.putExtra(UxArgument.SELECTED_ACCOUNT_UID, accountUID);
PendingIntent pendingIntent = PendingIntent
.getActivity(context, appWidgetId, newTransactionIntent, 0);
views.setOnClickPendingIntent(R.id.btn_new_transaction, pendingIntent);
@@ -216,12 +217,12 @@ public static void updateAllWidgets(Context context){
SharedPreferences defaultSharedPrefs = PreferenceManager.getDefaultSharedPreferences(context);
for (int widgetId : appWidgetIds) {
- long accountId = defaultSharedPrefs
- .getLong(UxArgument.SELECTED_ACCOUNT_ID + widgetId, -1);
+ String accountUID = defaultSharedPrefs
+ .getString(UxArgument.SELECTED_ACCOUNT_UID + widgetId, null);
- if (accountId <= 0)
+ if (accountUID == null)
continue;
- updateWidget(context, widgetId, accountId);
+ updateWidget(context, widgetId, accountUID);
}
}
}
diff --git a/integration-tests/pom.xml b/integration-tests/pom.xml
index cba85f06d..7bfb05127 100644
--- a/integration-tests/pom.xml
+++ b/integration-tests/pom.xml
@@ -17,7 +17,7 @@
4.0.0
- 1.4.3-SNAPSHOT
+ 1.5.0-SNAPSHOTorg.gnucash.androidgnucash-android-parent
diff --git a/integration-tests/project.properties b/integration-tests/project.properties
index 0840b4a05..9b84a6b4b 100644
--- a/integration-tests/project.properties
+++ b/integration-tests/project.properties
@@ -11,4 +11,4 @@
#proguard.config=${sdk.dir}/tools/proguard/proguard-android.txt:proguard-project.txt
# Project target.
-target=android-15
+target=android-16
diff --git a/integration-tests/src/org/gnucash/android/test/ui/TransactionsActivityTest.java b/integration-tests/src/org/gnucash/android/test/ui/TransactionsActivityTest.java
index 1a1cff23b..974896b5e 100644
--- a/integration-tests/src/org/gnucash/android/test/ui/TransactionsActivityTest.java
+++ b/integration-tests/src/org/gnucash/android/test/ui/TransactionsActivityTest.java
@@ -80,7 +80,7 @@ protected void setUp() throws Exception {
assertTrue(id > 0);
Intent intent = new Intent(Intent.ACTION_VIEW);
- intent.putExtra(UxArgument.SELECTED_ACCOUNT_ID, id);
+ intent.putExtra(UxArgument.SELECTED_ACCOUNT_UID, id);
setActivityIntent(intent);
mSolo = new Solo(getInstrumentation(), getActivity());
@@ -330,7 +330,7 @@ public void testDeleteTransaction(){
clickSherlockActionBarItem(R.id.context_menu_delete);
AccountsDbAdapter accountsDbAdapter = new AccountsDbAdapter(getActivity());
- long id = accountsDbAdapter.getId(DUMMY_ACCOUNT_UID);
+ long id = accountsDbAdapter.getID(DUMMY_ACCOUNT_UID);
TransactionsDbAdapter adapter = new TransactionsDbAdapter(getActivity());
assertEquals(0, adapter.getTransactionsCount(id));
diff --git a/pom.xml b/pom.xml
index aa8501da9..bd8b298ee 100644
--- a/pom.xml
+++ b/pom.xml
@@ -17,7 +17,7 @@
4.0.0
- 1.4.3-SNAPSHOT
+ 1.5.0-SNAPSHOTorg.gnucash.androidgnucash-android-parentGnuCash Android parent