]> git.ktnx.net Git - mobile-ledger.git/commitdiff
more pronounced day/month delimiters in the transaction list master
authorDamyan Ivanov <dam+mobileledger@ktnx.net>
Sun, 31 Mar 2024 11:13:26 +0000 (14:13 +0300)
committerDamyan Ivanov <dam+mobileledger@ktnx.net>
Sun, 31 Mar 2024 11:13:26 +0000 (14:13 +0300)
69 files changed:
CHANGES.md
TODO.txt [new file with mode: 0644]
app/build.gradle
app/schemas/net.ktnx.mobileledger.db.DB/66.json [new file with mode: 0644]
app/src/main/AndroidManifest.xml
app/src/main/java/net/ktnx/mobileledger/BackupsActivity.java
app/src/main/java/net/ktnx/mobileledger/async/RetrieveTransactionsTask.java
app/src/main/java/net/ktnx/mobileledger/async/SendTransactionTask.java
app/src/main/java/net/ktnx/mobileledger/backup/ConfigReader.java
app/src/main/java/net/ktnx/mobileledger/backup/MobileLedgerBackupAgent.java
app/src/main/java/net/ktnx/mobileledger/backup/RawConfigReader.java
app/src/main/java/net/ktnx/mobileledger/dao/AccountDAO.java
app/src/main/java/net/ktnx/mobileledger/dao/TemplateHeaderDAO.java
app/src/main/java/net/ktnx/mobileledger/db/TemplateHeader.java
app/src/main/java/net/ktnx/mobileledger/json/API.java
app/src/main/java/net/ktnx/mobileledger/json/AccountListParser.java
app/src/main/java/net/ktnx/mobileledger/json/Gateway.java
app/src/main/java/net/ktnx/mobileledger/json/TransactionListParser.java
app/src/main/java/net/ktnx/mobileledger/json/v1_23/AccountListParser.java [new file with mode: 0644]
app/src/main/java/net/ktnx/mobileledger/json/v1_23/Gateway.java [new file with mode: 0644]
app/src/main/java/net/ktnx/mobileledger/json/v1_23/ParsedAmount.java [new file with mode: 0644]
app/src/main/java/net/ktnx/mobileledger/json/v1_23/ParsedBalance.java [new file with mode: 0644]
app/src/main/java/net/ktnx/mobileledger/json/v1_23/ParsedLedgerAccount.java [new file with mode: 0644]
app/src/main/java/net/ktnx/mobileledger/json/v1_23/ParsedLedgerTransaction.java [new file with mode: 0644]
app/src/main/java/net/ktnx/mobileledger/json/v1_23/ParsedPosting.java [new file with mode: 0644]
app/src/main/java/net/ktnx/mobileledger/json/v1_23/ParsedPrice.java [new file with mode: 0644]
app/src/main/java/net/ktnx/mobileledger/json/v1_23/ParsedQuantity.java [new file with mode: 0644]
app/src/main/java/net/ktnx/mobileledger/json/v1_23/ParsedSourcePos.java [new file with mode: 0644]
app/src/main/java/net/ktnx/mobileledger/json/v1_23/ParsedStyle.java [new file with mode: 0644]
app/src/main/java/net/ktnx/mobileledger/json/v1_23/TransactionListParser.java [new file with mode: 0644]
app/src/main/java/net/ktnx/mobileledger/model/AccountListItem.java
app/src/main/java/net/ktnx/mobileledger/model/LedgerAccount.java
app/src/main/java/net/ktnx/mobileledger/ui/MainModel.java
app/src/main/java/net/ktnx/mobileledger/ui/account_summary/AccountSummaryAdapter.java
app/src/main/java/net/ktnx/mobileledger/ui/account_summary/AccountSummaryFragment.java
app/src/main/java/net/ktnx/mobileledger/ui/new_transaction/NewTransactionModel.java
app/src/main/java/net/ktnx/mobileledger/ui/profiles/ProfileDetailFragment.java
app/src/main/java/net/ktnx/mobileledger/ui/templates/TemplateListDivider.java
app/src/main/java/net/ktnx/mobileledger/ui/templates/TemplatesActivity.java
app/src/main/java/net/ktnx/mobileledger/ui/transaction_list/TransactionListAdapter.java
app/src/main/java/net/ktnx/mobileledger/ui/transaction_list/TransactionListDelimiterRowHolder.java
app/src/main/java/net/ktnx/mobileledger/utils/Colors.java
app/src/main/res/menu/account_list.xml [new file with mode: 0644]
app/src/main/res/menu/api_version.xml
app/src/main/res/values-bg/strings.xml
app/src/main/res/values/strings.xml
app/src/main/res/xml/backup_descriptor.xml [deleted file]
app/src/main/res/xml/network_security_config.xml
build.gradle
gradle.properties
gradle/wrapper/gradle-wrapper.properties
metadata/bg-BG/changelogs/48.txt [new file with mode: 0644]
metadata/bg-BG/changelogs/49.txt [new file with mode: 0644]
metadata/bg-BG/changelogs/50.txt [new file with mode: 0644]
metadata/bg-BG/changelogs/51.txt [new file with mode: 0644]
metadata/bg-BG/changelogs/52.txt [new file with mode: 0644]
metadata/bg-BG/changelogs/53.txt [new file with mode: 0644]
metadata/bg-BG/changelogs/54.txt [new file with mode: 0644]
metadata/bg-BG/changelogs/55.txt [new file with mode: 0644]
metadata/bg-BG/changelogs/56.txt [new file with mode: 0644]
metadata/en-US/changelogs/48.txt [new file with mode: 0644]
metadata/en-US/changelogs/49.txt [new file with mode: 0644]
metadata/en-US/changelogs/50.txt [new file with mode: 0644]
metadata/en-US/changelogs/51.txt [new file with mode: 0644]
metadata/en-US/changelogs/52.txt [new file with mode: 0644]
metadata/en-US/changelogs/53.txt [new file with mode: 0644]
metadata/en-US/changelogs/54.txt [new file with mode: 0644]
metadata/en-US/changelogs/55.txt [new file with mode: 0644]
metadata/en-US/changelogs/56.txt [new file with mode: 0644]

index bb9141a61a53640a273f22edc8d0b9511b50365f..02f84284cacf6943424f157b78ec8e114eb5dd6f 100644 (file)
@@ -1,5 +1,68 @@
 # Changes
 
+## [0.21.7] = 2024-03-19
+
+* FIXES:
+    + allow user certificates in network security config
+* OTHERS:
+    + bump gradle version
+
+## [0.21.6] - 2023-06-20
+
+* FIXES
+    + fix sending transations to hledger-web 1.23+
+
+## [0.21.5] - 2022-09-03
+
+* FIXES
+    + fix cloud backup
+
+## [0.21.4] - 2022-06-18
+
+* FIXES
+    + fix compatibility wuth hledger-web 1.23+ when submitting new transactions. Thanks to Faye Duxovni for the patch!
+    + fix a crash when deleting templates
+    + fix a rare crash when submitting transactions with multiple accounts with no amounts with zero remaining balance
+
+## [0.21.3] - 2022-04-06
+
+* FIXES
+    + sync gradle version requirements
+* OTHERS
+    + bump version of several dependent libraries
+    + bump SDK version to 31
+    + adjust deprecated constructor usage
+
+## [0.21.2] - 2022-04-04
+
+* FIXES
+    + fix crash when auto-balancing multi currency transaction
+    + fix crash when duplicating template
+    + fix crash when restoring configuration backup
+* IMPROVEMENTS
+    + new transaction: turn on commodity setting when loading previous transaction with commodities
+
+## [0.21.1] - 2021-12-30
+
+* FIXES
+    + add hledger-web 1.23 support when adding transactions too
+    + correct running total when a matching transaction is added in the past
+    + fix crash when sending transaction containing only empty amounts
+
+## [0.21.0] - 2021-12-09
+
+* NEW
+    + Add support for hledger-web 1.23
+* FIXES
+    + Ship database support file missed in v0.20.4
+
+## [0.20.4] - 2021-11-18
+
+* KNOWN PROBLEMS
+    + Incompatibility with hledger-web 1.23+
+* FIXES
+    + fix auto-completion of transaction description
+
 ## [0.20.3] - 2021-09-29
 
 * FIXES
diff --git a/TODO.txt b/TODO.txt
new file mode 100644 (file)
index 0000000..e229e17
--- /dev/null
+++ b/TODO.txt
@@ -0,0 +1,7 @@
+* Easy way to add tag:value pairs to transactions and transaction accounts
+
+* Filter by tag:value pairs
+
+* Refresh button in account/transaction list
+
+* Top button in account/transaction list
index dc0c0da6177c89879943dc6a40a8d41db2f1a51c..71673f6eb8dca03508bc0f12f40580f9893fda37 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright © 2021 Damyan Ivanov.
+ * Copyright © 2023 Damyan Ivanov.
  * This file is part of MoLe.
  * MoLe is free software: you can distribute it and/or modify it
  * under the term of the GNU General Public License as published by
 apply plugin: 'com.android.application'
 
 android {
-    compileSdkVersion 30
+    compileSdkVersion 31
     defaultConfig {
         applicationId "net.ktnx.mobileledger"
         minSdkVersion 22
-        targetSdkVersion 30
+        targetSdkVersion 31
         vectorDrawables.useSupportLibrary true
-        versionCode 47
-        versionName '0.20.3'
+        versionCode 56
+        versionName '0.21.7'
         testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
         javaCompileOptions {
             annotationProcessorOptions {
@@ -62,29 +62,30 @@ android {
     }
     buildFeatures.viewBinding = true
     buildToolsVersion '30.0.3'
+    namespace 'net.ktnx.mobileledger'
 }
 
 dependencies {
-    implementation 'androidx.lifecycle:lifecycle-livedata-ktx:2.3.1'
-    implementation 'androidx.lifecycle:lifecycle-viewmodel-ktx:2.3.1'
-    def room_version = '2.3.0'
-    implementation "androidx.room:room-runtime:$room_version"
+    implementation 'androidx.lifecycle:lifecycle-livedata-ktx:2.4.1'
+    implementation 'androidx.lifecycle:lifecycle-viewmodel-ktx:2.4.1'
+    def room_version = '2.4.2'
+    implementation "androidx.room:room-runtime:2.4.2"
     annotationProcessor "androidx.room:room-compiler:$room_version"
-    def nav_version = '2.3.5'
+    def nav_version = '2.4.2'
     implementation fileTree(include: ['*.jar'], dir: 'libs')
     implementation 'androidx.legacy:legacy-support-v4:1.0.0'
-    implementation 'com.google.android.material:material:1.3.0'
-    implementation 'androidx.constraintlayout:constraintlayout:2.0.4'
+    implementation 'com.google.android.material:material:1.5.0'
+    implementation 'androidx.constraintlayout:constraintlayout:2.1.3'
     implementation 'androidx.lifecycle:lifecycle-extensions:2.2.0'
-    implementation 'androidx.recyclerview:recyclerview:1.2.0'
+    implementation 'androidx.recyclerview:recyclerview:1.2.1'
     testImplementation 'junit:junit:4.13.2'
-    androidTestImplementation 'androidx.test:runner:1.4.0-alpha06'
-    androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0-alpha06'
-    implementation 'org.jetbrains:annotations:20.1.0'
-    implementation 'com.fasterxml.jackson.module:jackson-modules-java8:2.12.3'
+    androidTestImplementation 'androidx.test:runner:1.5.0-alpha02'
+    androidTestImplementation 'androidx.test.espresso:espresso-core:3.5.0-alpha05'
+    implementation 'org.jetbrains:annotations:23.0.0'
+    implementation 'com.fasterxml.jackson.module:jackson-modules-java8:2.13.2'
     implementation "androidx.navigation:navigation-fragment:$nav_version"
     implementation "androidx.navigation:navigation-ui:$nav_version"
-    implementation 'androidx.appcompat:appcompat:1.3.0-rc01'
+    implementation 'androidx.appcompat:appcompat:1.6.0-alpha01'
 }
 
 allprojects {
diff --git a/app/schemas/net.ktnx.mobileledger.db.DB/66.json b/app/schemas/net.ktnx.mobileledger.db.DB/66.json
new file mode 100644 (file)
index 0000000..13cd5d8
--- /dev/null
@@ -0,0 +1,867 @@
+{
+  "formatVersion": 1,
+  "database": {
+    "version": 66,
+    "identityHash": "0739ea866a6aebb4217f68a7fcda5bc6",
+    "entities": [
+      {
+        "tableName": "templates",
+        "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `name` TEXT NOT NULL, `uuid` TEXT NOT NULL, `regular_expression` TEXT NOT NULL, `test_text` TEXT, `transaction_description` TEXT, `transaction_description_match_group` INTEGER, `transaction_comment` TEXT, `transaction_comment_match_group` INTEGER, `date_year` INTEGER, `date_year_match_group` INTEGER, `date_month` INTEGER, `date_month_match_group` INTEGER, `date_day` INTEGER, `date_day_match_group` INTEGER, `is_fallback` INTEGER NOT NULL)",
+        "fields": [
+          {
+            "fieldPath": "id",
+            "columnName": "id",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "name",
+            "columnName": "name",
+            "affinity": "TEXT",
+            "notNull": true
+          },
+          {
+            "fieldPath": "uuid",
+            "columnName": "uuid",
+            "affinity": "TEXT",
+            "notNull": true
+          },
+          {
+            "fieldPath": "regularExpression",
+            "columnName": "regular_expression",
+            "affinity": "TEXT",
+            "notNull": true
+          },
+          {
+            "fieldPath": "testText",
+            "columnName": "test_text",
+            "affinity": "TEXT",
+            "notNull": false
+          },
+          {
+            "fieldPath": "transactionDescription",
+            "columnName": "transaction_description",
+            "affinity": "TEXT",
+            "notNull": false
+          },
+          {
+            "fieldPath": "transactionDescriptionMatchGroup",
+            "columnName": "transaction_description_match_group",
+            "affinity": "INTEGER",
+            "notNull": false
+          },
+          {
+            "fieldPath": "transactionComment",
+            "columnName": "transaction_comment",
+            "affinity": "TEXT",
+            "notNull": false
+          },
+          {
+            "fieldPath": "transactionCommentMatchGroup",
+            "columnName": "transaction_comment_match_group",
+            "affinity": "INTEGER",
+            "notNull": false
+          },
+          {
+            "fieldPath": "dateYear",
+            "columnName": "date_year",
+            "affinity": "INTEGER",
+            "notNull": false
+          },
+          {
+            "fieldPath": "dateYearMatchGroup",
+            "columnName": "date_year_match_group",
+            "affinity": "INTEGER",
+            "notNull": false
+          },
+          {
+            "fieldPath": "dateMonth",
+            "columnName": "date_month",
+            "affinity": "INTEGER",
+            "notNull": false
+          },
+          {
+            "fieldPath": "dateMonthMatchGroup",
+            "columnName": "date_month_match_group",
+            "affinity": "INTEGER",
+            "notNull": false
+          },
+          {
+            "fieldPath": "dateDay",
+            "columnName": "date_day",
+            "affinity": "INTEGER",
+            "notNull": false
+          },
+          {
+            "fieldPath": "dateDayMatchGroup",
+            "columnName": "date_day_match_group",
+            "affinity": "INTEGER",
+            "notNull": false
+          },
+          {
+            "fieldPath": "isFallback",
+            "columnName": "is_fallback",
+            "affinity": "INTEGER",
+            "notNull": true
+          }
+        ],
+        "primaryKey": {
+          "columnNames": [
+            "id"
+          ],
+          "autoGenerate": true
+        },
+        "indices": [
+          {
+            "name": "templates_uuid_idx",
+            "unique": true,
+            "columnNames": [
+              "uuid"
+            ],
+            "createSql": "CREATE UNIQUE INDEX IF NOT EXISTS `templates_uuid_idx` ON `${TABLE_NAME}` (`uuid`)"
+          }
+        ],
+        "foreignKeys": []
+      },
+      {
+        "tableName": "template_accounts",
+        "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `template_id` INTEGER NOT NULL, `acc` TEXT, `position` INTEGER NOT NULL, `acc_match_group` INTEGER, `currency` INTEGER, `currency_match_group` INTEGER, `amount` REAL, `amount_match_group` INTEGER, `comment` TEXT, `comment_match_group` INTEGER, `negate_amount` INTEGER, FOREIGN KEY(`template_id`) REFERENCES `templates`(`id`) ON UPDATE RESTRICT ON DELETE CASCADE , FOREIGN KEY(`currency`) REFERENCES `currencies`(`id`) ON UPDATE RESTRICT ON DELETE RESTRICT )",
+        "fields": [
+          {
+            "fieldPath": "id",
+            "columnName": "id",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "templateId",
+            "columnName": "template_id",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "accountName",
+            "columnName": "acc",
+            "affinity": "TEXT",
+            "notNull": false
+          },
+          {
+            "fieldPath": "position",
+            "columnName": "position",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "accountNameMatchGroup",
+            "columnName": "acc_match_group",
+            "affinity": "INTEGER",
+            "notNull": false
+          },
+          {
+            "fieldPath": "currency",
+            "columnName": "currency",
+            "affinity": "INTEGER",
+            "notNull": false
+          },
+          {
+            "fieldPath": "currencyMatchGroup",
+            "columnName": "currency_match_group",
+            "affinity": "INTEGER",
+            "notNull": false
+          },
+          {
+            "fieldPath": "amount",
+            "columnName": "amount",
+            "affinity": "REAL",
+            "notNull": false
+          },
+          {
+            "fieldPath": "amountMatchGroup",
+            "columnName": "amount_match_group",
+            "affinity": "INTEGER",
+            "notNull": false
+          },
+          {
+            "fieldPath": "accountComment",
+            "columnName": "comment",
+            "affinity": "TEXT",
+            "notNull": false
+          },
+          {
+            "fieldPath": "accountCommentMatchGroup",
+            "columnName": "comment_match_group",
+            "affinity": "INTEGER",
+            "notNull": false
+          },
+          {
+            "fieldPath": "negateAmount",
+            "columnName": "negate_amount",
+            "affinity": "INTEGER",
+            "notNull": false
+          }
+        ],
+        "primaryKey": {
+          "columnNames": [
+            "id"
+          ],
+          "autoGenerate": true
+        },
+        "indices": [
+          {
+            "name": "fk_template_accounts_template",
+            "unique": false,
+            "columnNames": [
+              "template_id"
+            ],
+            "createSql": "CREATE INDEX IF NOT EXISTS `fk_template_accounts_template` ON `${TABLE_NAME}` (`template_id`)"
+          },
+          {
+            "name": "fk_template_accounts_currency",
+            "unique": false,
+            "columnNames": [
+              "currency"
+            ],
+            "createSql": "CREATE INDEX IF NOT EXISTS `fk_template_accounts_currency` ON `${TABLE_NAME}` (`currency`)"
+          }
+        ],
+        "foreignKeys": [
+          {
+            "table": "templates",
+            "onDelete": "CASCADE",
+            "onUpdate": "RESTRICT",
+            "columns": [
+              "template_id"
+            ],
+            "referencedColumns": [
+              "id"
+            ]
+          },
+          {
+            "table": "currencies",
+            "onDelete": "RESTRICT",
+            "onUpdate": "RESTRICT",
+            "columns": [
+              "currency"
+            ],
+            "referencedColumns": [
+              "id"
+            ]
+          }
+        ]
+      },
+      {
+        "tableName": "currencies",
+        "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `name` TEXT NOT NULL, `position` TEXT NOT NULL, `has_gap` INTEGER NOT NULL)",
+        "fields": [
+          {
+            "fieldPath": "id",
+            "columnName": "id",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "name",
+            "columnName": "name",
+            "affinity": "TEXT",
+            "notNull": true
+          },
+          {
+            "fieldPath": "position",
+            "columnName": "position",
+            "affinity": "TEXT",
+            "notNull": true
+          },
+          {
+            "fieldPath": "hasGap",
+            "columnName": "has_gap",
+            "affinity": "INTEGER",
+            "notNull": true
+          }
+        ],
+        "primaryKey": {
+          "columnNames": [
+            "id"
+          ],
+          "autoGenerate": true
+        },
+        "indices": [
+          {
+            "name": "currency_name_idx",
+            "unique": true,
+            "columnNames": [
+              "name"
+            ],
+            "createSql": "CREATE UNIQUE INDEX IF NOT EXISTS `currency_name_idx` ON `${TABLE_NAME}` (`name`)"
+          }
+        ],
+        "foreignKeys": []
+      },
+      {
+        "tableName": "accounts",
+        "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `profile_id` INTEGER NOT NULL, `level` INTEGER NOT NULL, `name` TEXT NOT NULL, `name_upper` TEXT NOT NULL, `parent_name` TEXT, `expanded` INTEGER NOT NULL DEFAULT 1, `amounts_expanded` INTEGER NOT NULL DEFAULT 0, `generation` INTEGER NOT NULL DEFAULT 0, FOREIGN KEY(`profile_id`) REFERENCES `profiles`(`id`) ON UPDATE RESTRICT ON DELETE CASCADE )",
+        "fields": [
+          {
+            "fieldPath": "id",
+            "columnName": "id",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "profileId",
+            "columnName": "profile_id",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "level",
+            "columnName": "level",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "name",
+            "columnName": "name",
+            "affinity": "TEXT",
+            "notNull": true
+          },
+          {
+            "fieldPath": "nameUpper",
+            "columnName": "name_upper",
+            "affinity": "TEXT",
+            "notNull": true
+          },
+          {
+            "fieldPath": "parentName",
+            "columnName": "parent_name",
+            "affinity": "TEXT",
+            "notNull": false
+          },
+          {
+            "fieldPath": "expanded",
+            "columnName": "expanded",
+            "affinity": "INTEGER",
+            "notNull": true,
+            "defaultValue": "1"
+          },
+          {
+            "fieldPath": "amountsExpanded",
+            "columnName": "amounts_expanded",
+            "affinity": "INTEGER",
+            "notNull": true,
+            "defaultValue": "0"
+          },
+          {
+            "fieldPath": "generation",
+            "columnName": "generation",
+            "affinity": "INTEGER",
+            "notNull": true,
+            "defaultValue": "0"
+          }
+        ],
+        "primaryKey": {
+          "columnNames": [
+            "id"
+          ],
+          "autoGenerate": true
+        },
+        "indices": [
+          {
+            "name": "un_account_name",
+            "unique": true,
+            "columnNames": [
+              "profile_id",
+              "name"
+            ],
+            "createSql": "CREATE UNIQUE INDEX IF NOT EXISTS `un_account_name` ON `${TABLE_NAME}` (`profile_id`, `name`)"
+          },
+          {
+            "name": "fk_account_profile",
+            "unique": false,
+            "columnNames": [
+              "profile_id"
+            ],
+            "createSql": "CREATE INDEX IF NOT EXISTS `fk_account_profile` ON `${TABLE_NAME}` (`profile_id`)"
+          }
+        ],
+        "foreignKeys": [
+          {
+            "table": "profiles",
+            "onDelete": "CASCADE",
+            "onUpdate": "RESTRICT",
+            "columns": [
+              "profile_id"
+            ],
+            "referencedColumns": [
+              "id"
+            ]
+          }
+        ]
+      },
+      {
+        "tableName": "profiles",
+        "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `name` TEXT NOT NULL, `uuid` TEXT NOT NULL, `url` TEXT NOT NULL, `use_authentication` INTEGER NOT NULL, `auth_user` TEXT, `auth_password` TEXT, `order_no` INTEGER NOT NULL, `permit_posting` INTEGER NOT NULL, `theme` INTEGER NOT NULL DEFAULT -1, `preferred_accounts_filter` TEXT, `future_dates` INTEGER NOT NULL, `api_version` INTEGER NOT NULL, `show_commodity_by_default` INTEGER NOT NULL, `default_commodity` TEXT, `show_comments_by_default` INTEGER NOT NULL DEFAULT 1, `detected_version_pre_1_19` INTEGER NOT NULL, `detected_version_major` INTEGER NOT NULL, `detected_version_minor` INTEGER NOT NULL)",
+        "fields": [
+          {
+            "fieldPath": "id",
+            "columnName": "id",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "name",
+            "columnName": "name",
+            "affinity": "TEXT",
+            "notNull": true
+          },
+          {
+            "fieldPath": "uuid",
+            "columnName": "uuid",
+            "affinity": "TEXT",
+            "notNull": true
+          },
+          {
+            "fieldPath": "url",
+            "columnName": "url",
+            "affinity": "TEXT",
+            "notNull": true
+          },
+          {
+            "fieldPath": "useAuthentication",
+            "columnName": "use_authentication",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "authUser",
+            "columnName": "auth_user",
+            "affinity": "TEXT",
+            "notNull": false
+          },
+          {
+            "fieldPath": "authPassword",
+            "columnName": "auth_password",
+            "affinity": "TEXT",
+            "notNull": false
+          },
+          {
+            "fieldPath": "orderNo",
+            "columnName": "order_no",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "permitPosting",
+            "columnName": "permit_posting",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "theme",
+            "columnName": "theme",
+            "affinity": "INTEGER",
+            "notNull": true,
+            "defaultValue": "-1"
+          },
+          {
+            "fieldPath": "preferredAccountsFilter",
+            "columnName": "preferred_accounts_filter",
+            "affinity": "TEXT",
+            "notNull": false
+          },
+          {
+            "fieldPath": "futureDates",
+            "columnName": "future_dates",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "apiVersion",
+            "columnName": "api_version",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "showCommodityByDefault",
+            "columnName": "show_commodity_by_default",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "defaultCommodity",
+            "columnName": "default_commodity",
+            "affinity": "TEXT",
+            "notNull": false
+          },
+          {
+            "fieldPath": "showCommentsByDefault",
+            "columnName": "show_comments_by_default",
+            "affinity": "INTEGER",
+            "notNull": true,
+            "defaultValue": "1"
+          },
+          {
+            "fieldPath": "detectedVersionPre_1_19",
+            "columnName": "detected_version_pre_1_19",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "detectedVersionMajor",
+            "columnName": "detected_version_major",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "detectedVersionMinor",
+            "columnName": "detected_version_minor",
+            "affinity": "INTEGER",
+            "notNull": true
+          }
+        ],
+        "primaryKey": {
+          "columnNames": [
+            "id"
+          ],
+          "autoGenerate": true
+        },
+        "indices": [
+          {
+            "name": "profiles_uuid_idx",
+            "unique": true,
+            "columnNames": [
+              "uuid"
+            ],
+            "createSql": "CREATE UNIQUE INDEX IF NOT EXISTS `profiles_uuid_idx` ON `${TABLE_NAME}` (`uuid`)"
+          }
+        ],
+        "foreignKeys": []
+      },
+      {
+        "tableName": "options",
+        "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`profile_id` INTEGER NOT NULL, `name` TEXT NOT NULL, `value` TEXT, PRIMARY KEY(`profile_id`, `name`))",
+        "fields": [
+          {
+            "fieldPath": "profileId",
+            "columnName": "profile_id",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "name",
+            "columnName": "name",
+            "affinity": "TEXT",
+            "notNull": true
+          },
+          {
+            "fieldPath": "value",
+            "columnName": "value",
+            "affinity": "TEXT",
+            "notNull": false
+          }
+        ],
+        "primaryKey": {
+          "columnNames": [
+            "profile_id",
+            "name"
+          ],
+          "autoGenerate": false
+        },
+        "indices": [],
+        "foreignKeys": []
+      },
+      {
+        "tableName": "account_values",
+        "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `account_id` INTEGER NOT NULL, `currency` TEXT NOT NULL DEFAULT '', `value` REAL NOT NULL, `generation` INTEGER NOT NULL DEFAULT 0, FOREIGN KEY(`account_id`) REFERENCES `accounts`(`id`) ON UPDATE RESTRICT ON DELETE CASCADE )",
+        "fields": [
+          {
+            "fieldPath": "id",
+            "columnName": "id",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "accountId",
+            "columnName": "account_id",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "currency",
+            "columnName": "currency",
+            "affinity": "TEXT",
+            "notNull": true,
+            "defaultValue": "''"
+          },
+          {
+            "fieldPath": "value",
+            "columnName": "value",
+            "affinity": "REAL",
+            "notNull": true
+          },
+          {
+            "fieldPath": "generation",
+            "columnName": "generation",
+            "affinity": "INTEGER",
+            "notNull": true,
+            "defaultValue": "0"
+          }
+        ],
+        "primaryKey": {
+          "columnNames": [
+            "id"
+          ],
+          "autoGenerate": true
+        },
+        "indices": [
+          {
+            "name": "un_account_values",
+            "unique": true,
+            "columnNames": [
+              "account_id",
+              "currency"
+            ],
+            "createSql": "CREATE UNIQUE INDEX IF NOT EXISTS `un_account_values` ON `${TABLE_NAME}` (`account_id`, `currency`)"
+          },
+          {
+            "name": "fk_account_value_acc",
+            "unique": false,
+            "columnNames": [
+              "account_id"
+            ],
+            "createSql": "CREATE INDEX IF NOT EXISTS `fk_account_value_acc` ON `${TABLE_NAME}` (`account_id`)"
+          }
+        ],
+        "foreignKeys": [
+          {
+            "table": "accounts",
+            "onDelete": "CASCADE",
+            "onUpdate": "RESTRICT",
+            "columns": [
+              "account_id"
+            ],
+            "referencedColumns": [
+              "id"
+            ]
+          }
+        ]
+      },
+      {
+        "tableName": "transactions",
+        "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `ledger_id` INTEGER NOT NULL, `profile_id` INTEGER NOT NULL, `data_hash` TEXT NOT NULL, `year` INTEGER NOT NULL, `month` INTEGER NOT NULL, `day` INTEGER NOT NULL, `description` TEXT NOT NULL COLLATE NOCASE, `description_uc` TEXT NOT NULL, `comment` TEXT, `generation` INTEGER NOT NULL, FOREIGN KEY(`profile_id`) REFERENCES `profiles`(`id`) ON UPDATE RESTRICT ON DELETE CASCADE )",
+        "fields": [
+          {
+            "fieldPath": "id",
+            "columnName": "id",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "ledgerId",
+            "columnName": "ledger_id",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "profileId",
+            "columnName": "profile_id",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "dataHash",
+            "columnName": "data_hash",
+            "affinity": "TEXT",
+            "notNull": true
+          },
+          {
+            "fieldPath": "year",
+            "columnName": "year",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "month",
+            "columnName": "month",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "day",
+            "columnName": "day",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "description",
+            "columnName": "description",
+            "affinity": "TEXT",
+            "notNull": true
+          },
+          {
+            "fieldPath": "descriptionUpper",
+            "columnName": "description_uc",
+            "affinity": "TEXT",
+            "notNull": true
+          },
+          {
+            "fieldPath": "comment",
+            "columnName": "comment",
+            "affinity": "TEXT",
+            "notNull": false
+          },
+          {
+            "fieldPath": "generation",
+            "columnName": "generation",
+            "affinity": "INTEGER",
+            "notNull": true
+          }
+        ],
+        "primaryKey": {
+          "columnNames": [
+            "id"
+          ],
+          "autoGenerate": true
+        },
+        "indices": [
+          {
+            "name": "un_transactions_ledger_id",
+            "unique": true,
+            "columnNames": [
+              "profile_id",
+              "ledger_id"
+            ],
+            "createSql": "CREATE UNIQUE INDEX IF NOT EXISTS `un_transactions_ledger_id` ON `${TABLE_NAME}` (`profile_id`, `ledger_id`)"
+          },
+          {
+            "name": "idx_transaction_description",
+            "unique": false,
+            "columnNames": [
+              "description"
+            ],
+            "createSql": "CREATE INDEX IF NOT EXISTS `idx_transaction_description` ON `${TABLE_NAME}` (`description`)"
+          },
+          {
+            "name": "fk_transaction_profile",
+            "unique": false,
+            "columnNames": [
+              "profile_id"
+            ],
+            "createSql": "CREATE INDEX IF NOT EXISTS `fk_transaction_profile` ON `${TABLE_NAME}` (`profile_id`)"
+          }
+        ],
+        "foreignKeys": [
+          {
+            "table": "profiles",
+            "onDelete": "CASCADE",
+            "onUpdate": "RESTRICT",
+            "columns": [
+              "profile_id"
+            ],
+            "referencedColumns": [
+              "id"
+            ]
+          }
+        ]
+      },
+      {
+        "tableName": "transaction_accounts",
+        "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `transaction_id` INTEGER NOT NULL, `order_no` INTEGER NOT NULL, `account_name` TEXT NOT NULL, `currency` TEXT NOT NULL DEFAULT '', `amount` REAL NOT NULL, `comment` TEXT, `generation` INTEGER NOT NULL DEFAULT 0, FOREIGN KEY(`transaction_id`) REFERENCES `transactions`(`id`) ON UPDATE RESTRICT ON DELETE CASCADE )",
+        "fields": [
+          {
+            "fieldPath": "id",
+            "columnName": "id",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "transactionId",
+            "columnName": "transaction_id",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "orderNo",
+            "columnName": "order_no",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "accountName",
+            "columnName": "account_name",
+            "affinity": "TEXT",
+            "notNull": true
+          },
+          {
+            "fieldPath": "currency",
+            "columnName": "currency",
+            "affinity": "TEXT",
+            "notNull": true,
+            "defaultValue": "''"
+          },
+          {
+            "fieldPath": "amount",
+            "columnName": "amount",
+            "affinity": "REAL",
+            "notNull": true
+          },
+          {
+            "fieldPath": "comment",
+            "columnName": "comment",
+            "affinity": "TEXT",
+            "notNull": false
+          },
+          {
+            "fieldPath": "generation",
+            "columnName": "generation",
+            "affinity": "INTEGER",
+            "notNull": true,
+            "defaultValue": "0"
+          }
+        ],
+        "primaryKey": {
+          "columnNames": [
+            "id"
+          ],
+          "autoGenerate": true
+        },
+        "indices": [
+          {
+            "name": "fk_trans_acc_trans",
+            "unique": false,
+            "columnNames": [
+              "transaction_id"
+            ],
+            "createSql": "CREATE INDEX IF NOT EXISTS `fk_trans_acc_trans` ON `${TABLE_NAME}` (`transaction_id`)"
+          },
+          {
+            "name": "un_transaction_accounts",
+            "unique": true,
+            "columnNames": [
+              "transaction_id",
+              "order_no"
+            ],
+            "createSql": "CREATE UNIQUE INDEX IF NOT EXISTS `un_transaction_accounts` ON `${TABLE_NAME}` (`transaction_id`, `order_no`)"
+          }
+        ],
+        "foreignKeys": [
+          {
+            "table": "transactions",
+            "onDelete": "CASCADE",
+            "onUpdate": "RESTRICT",
+            "columns": [
+              "transaction_id"
+            ],
+            "referencedColumns": [
+              "id"
+            ]
+          }
+        ]
+      }
+    ],
+    "views": [],
+    "setupQueries": [
+      "CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)",
+      "INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, '0739ea866a6aebb4217f68a7fcda5bc6')"
+    ]
+  }
+}
\ No newline at end of file
index e3ec6ee1b34b5f6eda0282e64cc4e4acb54a428e..b17a1ecfcacfaec410eee37f6be145c34c967ac7 100644 (file)
@@ -1,5 +1,5 @@
 <?xml version="1.0" encoding="utf-8"?><!--
-  ~ Copyright © 2021 Damyan Ivanov.
+  ~ Copyright © 2024 Damyan Ivanov.
   ~ This file is part of MoLe.
   ~ MoLe is free software: you can distribute it and/or modify it
   ~ under the term of the GNU General Public License as published by
@@ -15,8 +15,7 @@
   ~ along with MoLe. If not, see <https://www.gnu.org/licenses/>.
   -->
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:tools="http://schemas.android.com/tools"
-    package="net.ktnx.mobileledger">
+    xmlns:tools="http://schemas.android.com/tools">
 
     <uses-permission android:name="android.permission.INTERNET" />
 
         android:name=".App"
         android:allowBackup="true"
         android:appCategory="productivity"
-        android:fullBackupContent="@xml/backup_descriptor"
         android:icon="@drawable/app_icon"
         android:label="@string/app_name"
         android:networkSecurityConfig="@xml/network_security_config"
         android:roundIcon="@drawable/app_icon_round"
         android:supportsRtl="true"
-        android:backupAgent="net.ktnx.mobileledger.backup.MobileLedgerBackupAgent"
+        android:backupAgent=".backup.MobileLedgerBackupAgent"
         tools:ignore="GoogleAppIndexingWarning">
         <activity
             android:name=".BackupsActivity"
@@ -42,7 +40,7 @@
             android:theme="@style/AppTheme.default" />
         <activity
             android:name=".ui.activity.SplashActivity"
-            android:label="@string/app_name"
+            android:exported="true"
             android:theme="@style/AppTheme.default">
             <intent-filter>
                 <action android:name="android.intent.action.MAIN" />
@@ -51,7 +49,6 @@
         </activity>
         <activity
             android:name=".ui.activity.MainActivity"
-            android:label="@string/app_name"
             android:theme="@style/AppTheme.default" />
         <activity
             android:name=".ui.new_transaction.NewTransactionActivity"
index 242fbb567be8802a2096ae33921d29ab432c45ef..a2b5cf6caae5306fe88a0e6294b88b05e28da385 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright © 2021 Damyan Ivanov.
+ * Copyright © 2022 Damyan Ivanov.
  * This file is part of MoLe.
  * MoLe is free software: you can distribute it and/or modify it
  * under the term of the GNU General Public License as published by
@@ -68,9 +68,8 @@ public class BackupsActivity extends AppCompatActivity {
         b.restoreButton.setOnClickListener(this::restoreClicked);
 
 
-        backupChooserLauncher =
-                registerForActivityResult(new ActivityResultContracts.CreateDocument(),
-                        this::storeConfig);
+        backupChooserLauncher = registerForActivityResult(
+                new ActivityResultContracts.CreateDocument("application/json"), this::storeConfig);
         restoreChooserLauncher =
                 registerForActivityResult(new ActivityResultContracts.OpenDocument(),
                         this::readConfig);
index 414e4003b644f5a8dc9d840483cbb6d2ed234a3b..0b751b4c6d7df0c94174973ec7a2e449352c9dc9 100644 (file)
@@ -439,6 +439,10 @@ public class RetrieveTransactionsTask extends Thread {
                 list.add(acc);
             }
             throwIfCancelled();
+
+            Logger.warn("accounts",
+                    String.format(Locale.US, "Got %d accounts using protocol %s", list.size(),
+                            version.getDescription()));
         }
 
         return list;
@@ -466,9 +470,9 @@ public class RetrieveTransactionsTask extends Thread {
                 return retrieveTransactionListForVersion(ver);
             }
             catch (Exception e) {
-                Logger.debug("json",
-                        String.format(Locale.US, "Error during account list retrieval using API %s",
-                                ver.getDescription()));
+                Logger.debug("json", String.format(Locale.US,
+                        "Error during transaction list retrieval using API %s",
+                        ver.getDescription()), e);
             }
 
         }
@@ -522,9 +526,13 @@ public class RetrieveTransactionsTask extends Thread {
             }
 
             throwIfCancelled();
+
+            Logger.warn("transactions",
+                    String.format(Locale.US, "Got %d transactions using protocol %s", trList.size(),
+                            apiVersion.getDescription()));
         }
 
-        // json interface returns transactions if file order and the rest of the machinery
+        // json interface returns transactions in file order and the rest of the machinery
         // expects them in reverse chronological order
         Collections.sort(trList, (o1, o2) -> {
             int res = o2.getDate()
index 79a58173f10e502d11dfbd752fcfdef355aeb4b3..0e700cca9a510be8e210fafccbe548b88c2a427a 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright © 2021 Damyan Ivanov.
+ * Copyright © 2023 Damyan Ivanov.
  * This file is part of MoLe.
  * MoLe is free software: you can distribute it and/or modify it
  * under the term of the GNU General Public License as published by
@@ -17,6 +17,8 @@
 
 package net.ktnx.mobileledger.async;
 
+import static net.ktnx.mobileledger.utils.Logger.debug;
+
 import android.util.Log;
 
 import net.ktnx.mobileledger.db.Profile;
@@ -45,8 +47,6 @@ import java.util.Map;
 import java.util.regex.Matcher;
 import java.util.regex.Pattern;
 
-import static net.ktnx.mobileledger.utils.Logger.debug;
-
 /* TODO: get rid of the custom session/cookie and auth code?
  *       (the last problem with the POST was the missing content-length header)
  *       This will resolve itself when hledger-web 1.14+ is released with Debian/stable,
@@ -275,6 +275,7 @@ public class SendTransactionTask extends Thread {
                 case v1_14:
                 case v1_15:
                 case v1_19_1:
+                case v1_23:
                     sendOK(profileApiVersion);
                     break;
                 default:
@@ -286,9 +287,7 @@ public class SendTransactionTask extends Thread {
             error = e.getMessage();
         }
 
-        Misc.onMainThread(()->{
-            taskCallback.onTransactionSaveDone(error, transaction);
-        });
+        Misc.onMainThread(() -> taskCallback.onTransactionSaveDone(error, transaction));
     }
     private void legacySendOkWithRetry() throws IOException {
         int tried = 0;
index 7959da518efc0c48f057f652392f625366c75ce7..187eb9b7849df70c2a8d3c1c234069a4a4c36dfa 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright © 2021 Damyan Ivanov.
+ * Copyright © 2022 Damyan Ivanov.
  * This file is part of MoLe.
  * MoLe is free software: you can distribute it and/or modify it
  * under the term of the GNU General Public License as published by
@@ -45,7 +45,7 @@ public class ConfigReader extends ConfigIO {
     }
     @Override
     protected void initStream() {
-        RawConfigReader r = new RawConfigReader(new FileInputStream(pfd.getFileDescriptor()));
+        r = new RawConfigReader(new FileInputStream(pfd.getFileDescriptor()));
     }
     @Override
     protected void processStream() throws IOException {
index ea09e51417d68735d281c30fa6213303f5425183..6653843594ce2bbc5b275784ef4c1818588bbcfc 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright © 2021 Damyan Ivanov.
+ * Copyright © 2022 Damyan Ivanov.
  * This file is part of MoLe.
  * MoLe is free software: you can distribute it and/or modify it
  * under the term of the GNU General Public License as published by
@@ -17,7 +17,7 @@
 
 package net.ktnx.mobileledger.backup;
 
-import android.app.backup.BackupAgentHelper;
+import android.app.backup.BackupAgent;
 import android.app.backup.BackupDataInput;
 import android.app.backup.BackupDataOutput;
 import android.os.ParcelFileDescriptor;
@@ -29,13 +29,13 @@ import java.io.ByteArrayInputStream;
 import java.io.ByteArrayOutputStream;
 import java.io.IOException;
 
-class MobileLedgerBackupAgent extends BackupAgentHelper {
+public class MobileLedgerBackupAgent extends BackupAgent {
     private static final int READ_BUF_LEN = 10;
     public static String SETTINGS_KEY = "settings";
     @Override
     public void onBackup(ParcelFileDescriptor oldState, BackupDataOutput data,
                          ParcelFileDescriptor newState) throws IOException {
-        super.onBackup(oldState, data, newState);
+        Logger.debug("backup", "onBackup()");
         backupSettings(data);
         newState.close();
     }
index 45a593d7add390a4898f191d1a1b49d15de62232..95b27b32ed85a2975799b64e11855e8f17ead7ed 100644 (file)
@@ -20,6 +20,7 @@ package net.ktnx.mobileledger.backup;
 import android.util.JsonReader;
 import android.util.JsonToken;
 
+import net.ktnx.mobileledger.App;
 import net.ktnx.mobileledger.backup.ConfigIO.Keys;
 import net.ktnx.mobileledger.dao.CurrencyDAO;
 import net.ktnx.mobileledger.dao.ProfileDAO;
@@ -30,6 +31,8 @@ import net.ktnx.mobileledger.db.Profile;
 import net.ktnx.mobileledger.db.TemplateAccount;
 import net.ktnx.mobileledger.db.TemplateHeader;
 import net.ktnx.mobileledger.db.TemplateWithAccounts;
+import net.ktnx.mobileledger.model.Data;
+import net.ktnx.mobileledger.utils.Logger;
 
 import java.io.BufferedReader;
 import java.io.IOException;
@@ -337,6 +340,7 @@ public class RawConfigReader {
         restoreCommodities();
         restoreProfiles();
         restoreTemplates();
+        restoreCurrentProfile();
     }
     private void restoreTemplates() {
         if (templates == null)
@@ -374,4 +378,24 @@ public class RawConfigReader {
                 dao.insert(c);
         }
     }
+    private void restoreCurrentProfile() {
+        if (currentProfile == null) {
+            Logger.debug("backup", "Not restoring current profile (not present in backup)");
+            return;
+        }
+
+        ProfileDAO dao = DB.get()
+                           .getProfileDAO();
+
+        Profile p = dao.getByUuidSync(currentProfile);
+
+        if (p != null) {
+            Logger.debug("backup", "Restoring current profile "+p.getName());
+            Data.postCurrentProfile(p);
+            App.storeStartupProfileAndTheme(p.getId(), p.getTheme());
+        }
+        else {
+            Logger.debug("backup", "Not restoring profile "+currentProfile+": not found in DB");
+        }
+    }
 }
index 0c581787a30a2fac55c605674904eebdd7441995..5ad4c90fb4138487b9626648dd9b1191d19c188b 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright © 2021 Damyan Ivanov.
+ * Copyright © 2024 Damyan Ivanov.
  * This file is part of MoLe.
  * MoLe is free software: you can distribute it and/or modify it
  * under the term of the GNU General Public License as published by
@@ -78,12 +78,19 @@ public abstract class AccountDAO extends BaseDAO<Account> {
     @Query("DELETE FROM accounts")
     public abstract void deleteAllSync();
 
-    @Query("SELECT * FROM accounts WHERE profile_id=:profileId ORDER BY name")
-    public abstract LiveData<List<Account>> getAll(long profileId);
+    @Query("SELECT * FROM accounts WHERE profile_id=:profileId AND IIF(:includeZeroBalances=1, 1," +
+           " (EXISTS(SELECT 1 FROM account_values av WHERE av.account_id=accounts.id AND av.value" +
+           " <> 0) OR EXISTS(SELECT 1 FROM accounts a WHERE a.parent_name = accounts.name))) " +
+           "ORDER BY name")
+    public abstract LiveData<List<Account>> getAll(long profileId, boolean includeZeroBalances);
 
     @Transaction
-    @Query("SELECT * FROM accounts WHERE profile_id = :profileId ORDER BY name")
-    public abstract LiveData<List<AccountWithAmounts>> getAllWithAmounts(long profileId);
+    @Query("SELECT * FROM accounts WHERE profile_id = :profileId AND IIF(:includeZeroBalances=1, " +
+           "1, (EXISTS(SELECT 1 FROM account_values av WHERE av.account_id=accounts.id AND av" +
+           ".value <> 0) OR EXISTS(SELECT 1 FROM accounts a WHERE a.parent_name = accounts.name))" +
+           ") ORDER BY name")
+    public abstract LiveData<List<AccountWithAmounts>> getAllWithAmounts(long profileId,
+                                                                         boolean includeZeroBalances);
 
     @Query("SELECT * FROM accounts WHERE id=:id")
     public abstract Account getByIdSync(long id);
index 24e751c8149400fd26d3f933a36db90f93f3f73f..67dcef8d69b8d131edc8dd6370a49f5ae4181e85 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright © 2021 Damyan Ivanov.
+ * Copyright © 2022 Damyan Ivanov.
  * This file is part of MoLe.
  * MoLe is free software: you can distribute it and/or modify it
  * under the term of the GNU General Public License as published by
@@ -137,7 +137,7 @@ public abstract class TemplateHeaderDAO {
                 Misc.onMainThread(callback);
         });
     }
-    public void duplicateTemplateWitAccounts(@NonNull Long id, @Nullable
+    public void duplicateTemplateWithAccounts(@NonNull Long id, @Nullable
             AsyncResultCallback<TemplateWithAccounts> callback) {
         BaseDAO.runAsync(() -> {
             TemplateWithAccounts src = getTemplateWithAccountsSync(id);
index 87ea4fd84050ca741f3acdfe013718cbff3c5d7a..994c33098454a5ca09be19e0eddc3b2e44b64bce 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright © 2021 Damyan Ivanov.
+ * Copyright © 2022 Damyan Ivanov.
  * This file is part of MoLe.
  * MoLe is free software: you can distribute it and/or modify it
  * under the term of the GNU General Public License as published by
@@ -219,6 +219,8 @@ public class TemplateHeader extends TemplateBase {
     public TemplateHeader createDuplicate() {
         TemplateHeader dup = new TemplateHeader(this);
         dup.id = 0;
+        dup.uuid = UUID.randomUUID()
+                       .toString();
 
         return dup;
     }
index d634f29e616d1e13746238343f88ffaa6885cee6..136bdf553c5ee4f01d6f7d29a465a502bc995d81 100644 (file)
@@ -23,9 +23,9 @@ import android.util.SparseArray;
 import net.ktnx.mobileledger.R;
 
 public enum API {
-    auto(0), html(-1), v1_14(-2), v1_15(-3), v1_19_1(-4);
+    auto(0), html(-1), v1_14(-2), v1_15(-3), v1_19_1(-4), v1_23(-5);
     private static final SparseArray<API> map = new SparseArray<>();
-    public static API[] allVersions = {v1_19_1, v1_15, v1_14};
+    public static API[] allVersions = {v1_23, v1_19_1, v1_15, v1_14};
 
     static {
         for (API item : API.values()) {
@@ -56,6 +56,8 @@ public enum API {
                 return resources.getString(R.string.api_1_15);
             case v1_19_1:
                 return resources.getString(R.string.api_1_19_1);
+            case v1_23:
+                return resources.getString(R.string.api_1_23);
             default:
                 throw new IllegalStateException("Unexpected value: " + value);
         }
@@ -72,6 +74,8 @@ public enum API {
                 return "1.15";
             case v1_19_1:
                 return "1.19.1";
+            case v1_23:
+                return "1.23";
             default:
                 throw new IllegalStateException("Unexpected value: " + this);
         }
index 872cac79c7828f6dd06a637431aaa7f36a2dbfab..baeeb2eab58528cc6f722639a6b43299411c97c9 100644 (file)
@@ -39,6 +39,8 @@ abstract public class AccountListParser {
                 return new net.ktnx.mobileledger.json.v1_15.AccountListParser(input);
             case v1_19_1:
                 return new net.ktnx.mobileledger.json.v1_19_1.AccountListParser(input);
+            case v1_23:
+                return new net.ktnx.mobileledger.json.v1_23.AccountListParser(input);
             default:
                 throw new RuntimeException("Unsupported version " + version.toString());
         }
index 97789673421b69a98f7fb2bdba19dd8cd394a15f..521e4c9fbb2292ce124cef6251d9871efe8be595 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright © 2020 Damyan Ivanov.
+ * Copyright © 2021 Damyan Ivanov.
  * This file is part of MoLe.
  * MoLe is free software: you can distribute it and/or modify it
  * under the term of the GNU General Public License as published by
@@ -30,8 +30,11 @@ abstract public class Gateway {
                 return new net.ktnx.mobileledger.json.v1_15.Gateway();
             case v1_19_1:
                 return new net.ktnx.mobileledger.json.v1_19_1.Gateway();
+            case v1_23:
+                return new net.ktnx.mobileledger.json.v1_23.Gateway();
             default:
-                throw new RuntimeException("Unsupported JSON API version " + apiVersion);
+                throw new RuntimeException(
+                        "JSON API version " + apiVersion + " save implementation missing");
         }
     }
     public abstract String transactionSaveRequest(LedgerTransaction ledgerTransaction)
index d707c062d8555d89be6fb95dabbc88771c6452f8..20bf08b770ffa63e5db7e0693cae9fa999fb1411 100644 (file)
@@ -33,6 +33,8 @@ public abstract class TransactionListParser {
                 return new net.ktnx.mobileledger.json.v1_15.TransactionListParser(input);
             case v1_19_1:
                 return new net.ktnx.mobileledger.json.v1_19_1.TransactionListParser(input);
+            case v1_23:
+                return new net.ktnx.mobileledger.json.v1_23.TransactionListParser(input);
             default:
                 throw new RuntimeException("Unsupported version " + apiVersion.toString());
         }
diff --git a/app/src/main/java/net/ktnx/mobileledger/json/v1_23/AccountListParser.java b/app/src/main/java/net/ktnx/mobileledger/json/v1_23/AccountListParser.java
new file mode 100644 (file)
index 0000000..904fb57
--- /dev/null
@@ -0,0 +1,40 @@
+/*
+ * Copyright © 2020 Damyan Ivanov.
+ * This file is part of MoLe.
+ * MoLe is free software: you can distribute it and/or modify it
+ * under the term of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your opinion), any later version.
+ *
+ * MoLe is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License terms for details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with MoLe. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+package net.ktnx.mobileledger.json.v1_23;
+
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.databind.ObjectReader;
+
+import net.ktnx.mobileledger.json.API;
+
+import java.io.IOException;
+import java.io.InputStream;
+
+public class AccountListParser extends net.ktnx.mobileledger.json.AccountListParser {
+
+    public AccountListParser(InputStream input) throws IOException {
+        ObjectMapper mapper = new ObjectMapper();
+        ObjectReader reader = mapper.readerFor(ParsedLedgerAccount.class);
+
+        iterator = reader.readValues(input);
+    }
+    @Override
+    public API getApiVersion() {
+        return API.v1_19_1;
+    }
+}
diff --git a/app/src/main/java/net/ktnx/mobileledger/json/v1_23/Gateway.java b/app/src/main/java/net/ktnx/mobileledger/json/v1_23/Gateway.java
new file mode 100644 (file)
index 0000000..c04f6f2
--- /dev/null
@@ -0,0 +1,36 @@
+/*
+ * Copyright © 2020 Damyan Ivanov.
+ * This file is part of MoLe.
+ * MoLe is free software: you can distribute it and/or modify it
+ * under the term of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your opinion), any later version.
+ *
+ * MoLe is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License terms for details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with MoLe. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+package net.ktnx.mobileledger.json.v1_23;
+
+import com.fasterxml.jackson.core.JsonProcessingException;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.databind.ObjectWriter;
+
+import net.ktnx.mobileledger.model.LedgerTransaction;
+
+public class Gateway extends net.ktnx.mobileledger.json.Gateway {
+    @Override
+    public String transactionSaveRequest(LedgerTransaction ledgerTransaction)
+            throws JsonProcessingException {
+        ParsedLedgerTransaction jsonTransaction =
+                ParsedLedgerTransaction.fromLedgerTransaction(ledgerTransaction);
+        ObjectMapper mapper = new ObjectMapper();
+        ObjectWriter writer = mapper.writerFor(ParsedLedgerTransaction.class);
+        return writer.writeValueAsString(jsonTransaction);
+    }
+}
diff --git a/app/src/main/java/net/ktnx/mobileledger/json/v1_23/ParsedAmount.java b/app/src/main/java/net/ktnx/mobileledger/json/v1_23/ParsedAmount.java
new file mode 100644 (file)
index 0000000..58f88c1
--- /dev/null
@@ -0,0 +1,62 @@
+/*
+ * Copyright © 2020 Damyan Ivanov.
+ * This file is part of MoLe.
+ * MoLe is free software: you can distribute it and/or modify it
+ * under the term of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your opinion), any later version.
+ *
+ * MoLe is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License terms for details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with MoLe. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+package net.ktnx.mobileledger.json.v1_23;
+
+import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
+
+@JsonIgnoreProperties(ignoreUnknown = true)
+public class ParsedAmount {
+    private String acommodity;
+    private ParsedQuantity aquantity;
+    private boolean aismultiplier;
+    private ParsedStyle astyle;
+    private ParsedPrice aprice;
+    public ParsedAmount() {
+    }
+    public ParsedPrice getAprice() {
+        return aprice;
+    }
+    public void setAprice(ParsedPrice aprice) {
+        this.aprice = aprice;
+    }
+    public String getAcommodity() {
+        return acommodity;
+    }
+    public void setAcommodity(String acommodity) {
+        this.acommodity = acommodity;
+    }
+    public ParsedQuantity getAquantity() {
+        return aquantity;
+    }
+    public void setAquantity(ParsedQuantity aquantity) {
+        this.aquantity = aquantity;
+    }
+    public boolean isAismultiplier() {
+        return aismultiplier;
+    }
+    public void setAismultiplier(boolean aismultiplier) {
+        this.aismultiplier = aismultiplier;
+    }
+    public ParsedStyle getAstyle() {
+        return astyle;
+    }
+    public void setAstyle(ParsedStyle astyle) {
+        this.astyle = astyle;
+    }
+
+}
diff --git a/app/src/main/java/net/ktnx/mobileledger/json/v1_23/ParsedBalance.java b/app/src/main/java/net/ktnx/mobileledger/json/v1_23/ParsedBalance.java
new file mode 100644 (file)
index 0000000..4f74de4
--- /dev/null
@@ -0,0 +1,33 @@
+/*
+ * Copyright © 2020 Damyan Ivanov.
+ * This file is part of MoLe.
+ * MoLe is free software: you can distribute it and/or modify it
+ * under the term of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your opinion), any later version.
+ *
+ * MoLe is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License terms for details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with MoLe. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+package net.ktnx.mobileledger.json.v1_23;
+
+import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
+
+@JsonIgnoreProperties(ignoreUnknown = true)
+public class ParsedBalance extends net.ktnx.mobileledger.json.ParsedBalance {
+    private ParsedStyle astyle;
+    public ParsedBalance() {
+    }
+    public ParsedStyle getAstyle() {
+        return astyle;
+    }
+    public void setAstyle(ParsedStyle astyle) {
+        this.astyle = astyle;
+    }
+}
diff --git a/app/src/main/java/net/ktnx/mobileledger/json/v1_23/ParsedLedgerAccount.java b/app/src/main/java/net/ktnx/mobileledger/json/v1_23/ParsedLedgerAccount.java
new file mode 100644 (file)
index 0000000..6d942e8
--- /dev/null
@@ -0,0 +1,53 @@
+/*
+ * Copyright © 2020 Damyan Ivanov.
+ * This file is part of MoLe.
+ * MoLe is free software: you can distribute it and/or modify it
+ * under the term of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your opinion), any later version.
+ *
+ * MoLe is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License terms for details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with MoLe. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+package net.ktnx.mobileledger.json.v1_23;
+
+import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
+
+import java.util.ArrayList;
+import java.util.List;
+
+@JsonIgnoreProperties(ignoreUnknown = true)
+public class ParsedLedgerAccount extends net.ktnx.mobileledger.json.ParsedLedgerAccount {
+    private List<ParsedBalance> aebalance;
+    private List<ParsedBalance> aibalance;
+    public ParsedLedgerAccount() {
+    }
+    public List<ParsedBalance> getAibalance() {
+        return aibalance;
+    }
+    public void setAibalance(List<ParsedBalance> aibalance) {
+        this.aibalance = aibalance;
+    }
+    public List<ParsedBalance> getAebalance() {
+        return aebalance;
+    }
+    public void setAebalance(List<ParsedBalance> aebalance) {
+        this.aebalance = aebalance;
+    }
+    @Override
+    public List<SimpleBalance> getSimpleBalance() {
+        List<SimpleBalance> result = new ArrayList<SimpleBalance>();
+        for (ParsedBalance b : getAibalance()) {
+            result.add(new SimpleBalance(b.getAcommodity(), b.getAquantity()
+                                                             .asFloat()));
+        }
+
+        return result;
+    }
+}
diff --git a/app/src/main/java/net/ktnx/mobileledger/json/v1_23/ParsedLedgerTransaction.java b/app/src/main/java/net/ktnx/mobileledger/json/v1_23/ParsedLedgerTransaction.java
new file mode 100644 (file)
index 0000000..59d1763
--- /dev/null
@@ -0,0 +1,166 @@
+/*
+ * Copyright © 2021 Damyan Ivanov.
+ * This file is part of MoLe.
+ * MoLe is free software: you can distribute it and/or modify it
+ * under the term of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your opinion), any later version.
+ *
+ * MoLe is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License terms for details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with MoLe. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+package net.ktnx.mobileledger.json.v1_23;
+
+import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
+
+import net.ktnx.mobileledger.model.LedgerTransaction;
+import net.ktnx.mobileledger.model.LedgerTransactionAccount;
+import net.ktnx.mobileledger.utils.Globals;
+import net.ktnx.mobileledger.utils.Misc;
+import net.ktnx.mobileledger.utils.SimpleDate;
+
+import java.text.ParseException;
+import java.util.ArrayList;
+import java.util.List;
+
+@JsonIgnoreProperties(ignoreUnknown = true)
+public class ParsedLedgerTransaction implements net.ktnx.mobileledger.json.ParsedLedgerTransaction {
+    private String tdate;
+    private String tdate2 = null;
+    private String tdescription;
+    private String tcomment;
+    private String tcode = "";
+    private String tstatus = "Unmarked";
+    private String tprecedingcomment = "";
+    private int tindex;
+    private List<ParsedPosting> tpostings;
+    private List<List<String>> ttags = new ArrayList<>();
+    private List<ParsedSourcePos> tsourcepos = new ArrayList<>();
+    public ParsedLedgerTransaction() {
+        ParsedSourcePos startPos = new ParsedSourcePos();
+        ParsedSourcePos endPos = new ParsedSourcePos();
+        endPos.setSourceLine(2);
+
+        tsourcepos.add(startPos);
+        tsourcepos.add(endPos);
+    }
+    public static ParsedLedgerTransaction fromLedgerTransaction(LedgerTransaction tr) {
+        ParsedLedgerTransaction result = new ParsedLedgerTransaction();
+        result.setTcomment(Misc.nullIsEmpty(tr.getComment()));
+        result.setTprecedingcomment("");
+
+        ArrayList<ParsedPosting> postings = new ArrayList<>();
+        for (LedgerTransactionAccount acc : tr.getAccounts()) {
+            if (!acc.getAccountName()
+                    .isEmpty())
+                postings.add(ParsedPosting.fromLedgerAccount(acc));
+        }
+
+        result.setTpostings(postings);
+        SimpleDate transactionDate = tr.getDateIfAny();
+        if (transactionDate == null) {
+            transactionDate = SimpleDate.today();
+        }
+        result.setTdate(Globals.formatIsoDate(transactionDate));
+        result.setTdate2(null);
+        result.setTindex(1);
+        result.setTdescription(tr.getDescription());
+        return result;
+    }
+    public String getTcode() {
+        return tcode;
+    }
+    public void setTcode(String tcode) {
+        this.tcode = tcode;
+    }
+    public String getTstatus() {
+        return tstatus;
+    }
+    public void setTstatus(String tstatus) {
+        this.tstatus = tstatus;
+    }
+    public List<List<String>> getTtags() {
+        return ttags;
+    }
+    public void setTtags(List<List<String>> ttags) {
+        this.ttags = ttags;
+    }
+    public List<ParsedSourcePos> getTsourcepos() {
+        return tsourcepos;
+    }
+    public void setTsourcepos(List<ParsedSourcePos> tsourcepos) {
+        this.tsourcepos = tsourcepos;
+    }
+    public String getTprecedingcomment() {
+        return tprecedingcomment;
+    }
+    public void setTprecedingcomment(String tprecedingcomment) {
+        this.tprecedingcomment = tprecedingcomment;
+    }
+    public String getTdate() {
+        return tdate;
+    }
+    public void setTdate(String tdate) {
+        this.tdate = tdate;
+    }
+    public String getTdate2() {
+        return tdate2;
+    }
+    public void setTdate2(String tdate2) {
+        this.tdate2 = tdate2;
+    }
+    public String getTdescription() {
+        return tdescription;
+    }
+    public void setTdescription(String tdescription) {
+        this.tdescription = tdescription;
+    }
+    public String getTcomment() {
+        return tcomment;
+    }
+    public void setTcomment(String tcomment) {
+        this.tcomment = tcomment;
+    }
+    public int getTindex() {
+        return tindex;
+    }
+    public void setTindex(int tindex) {
+        this.tindex = tindex;
+        if (tpostings != null)
+            for (ParsedPosting p : tpostings) {
+                p.setPtransaction_(tindex);
+            }
+    }
+    public List<ParsedPosting> getTpostings() {
+        return tpostings;
+    }
+    public void setTpostings(List<ParsedPosting> tpostings) {
+        this.tpostings = tpostings;
+    }
+    public void addPosting(ParsedPosting posting) {
+        posting.setPtransaction_(tindex);
+        tpostings.add(posting);
+    }
+    public LedgerTransaction asLedgerTransaction() throws ParseException {
+        SimpleDate date = Globals.parseIsoDate(tdate);
+        LedgerTransaction tr = new LedgerTransaction(tindex, date, tdescription);
+        tr.setComment(Misc.trim(Misc.emptyIsNull(tcomment)));
+
+        List<ParsedPosting> postings = tpostings;
+
+        if (postings != null) {
+            for (ParsedPosting p : postings) {
+                tr.addAccount(p.asLedgerAccount());
+            }
+        }
+
+        tr.markDataAsLoaded();
+        return tr;
+    }
+}
diff --git a/app/src/main/java/net/ktnx/mobileledger/json/v1_23/ParsedPosting.java b/app/src/main/java/net/ktnx/mobileledger/json/v1_23/ParsedPosting.java
new file mode 100644 (file)
index 0000000..e60bd19
--- /dev/null
@@ -0,0 +1,144 @@
+/*
+ * Copyright © 2020 Damyan Ivanov.
+ * This file is part of MoLe.
+ * MoLe is free software: you can distribute it and/or modify it
+ * under the term of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your opinion), any later version.
+ *
+ * MoLe is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License terms for details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with MoLe. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+package net.ktnx.mobileledger.json.v1_23;
+
+import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
+
+import net.ktnx.mobileledger.model.LedgerTransactionAccount;
+
+import java.util.ArrayList;
+import java.util.List;
+
+@JsonIgnoreProperties(ignoreUnknown = true)
+public class ParsedPosting extends net.ktnx.mobileledger.json.ParsedPosting {
+    private Void pbalanceassertion;
+    private String pstatus = "Unmarked";
+    private String paccount;
+    private List<ParsedAmount> pamount;
+    private String pdate = null;
+    private String pdate2 = null;
+    private String ptype = "RegularPosting";
+    private String pcomment = "";
+    private List<List<String>> ptags = new ArrayList<>();
+    private String poriginal = null;
+    private int ptransaction_;
+    public ParsedPosting() {
+    }
+    public static ParsedPosting fromLedgerAccount(LedgerTransactionAccount acc) {
+        ParsedPosting result = new ParsedPosting();
+        result.setPaccount(acc.getAccountName());
+
+        String comment = acc.getComment();
+        if (comment == null)
+            comment = "";
+        result.setPcomment(comment);
+
+        ArrayList<ParsedAmount> amounts = new ArrayList<>();
+        ParsedAmount amt = new ParsedAmount();
+        amt.setAcommodity((acc.getCurrency() == null) ? "" : acc.getCurrency());
+        amt.setAismultiplier(false);
+        ParsedQuantity qty = new ParsedQuantity();
+        qty.setDecimalPlaces(2);
+        qty.setDecimalMantissa(Math.round(acc.getAmount() * 100));
+        amt.setAquantity(qty);
+        ParsedStyle style = new ParsedStyle();
+        style.setAscommodityside(getCommoditySide());
+        style.setAscommodityspaced(getCommoditySpaced());
+        style.setAsprecision(2);
+        style.setAsdecimalpoint('.');
+        amt.setAstyle(style);
+        if (acc.getCurrency() != null)
+            amt.setAcommodity(acc.getCurrency());
+        amounts.add(amt);
+        result.setPamount(amounts);
+        return result;
+    }
+    public String getPdate2() {
+        return pdate2;
+    }
+    public void setPdate2(String pdate2) {
+        this.pdate2 = pdate2;
+    }
+    public int getPtransaction_() {
+        return ptransaction_;
+    }
+    public void setPtransaction_(int ptransaction_) {
+        this.ptransaction_ = ptransaction_;
+    }
+    public String getPdate() {
+        return pdate;
+    }
+    public void setPdate(String pdate) {
+        this.pdate = pdate;
+    }
+    public String getPtype() {
+        return ptype;
+    }
+    public void setPtype(String ptype) {
+        this.ptype = ptype;
+    }
+    public String getPcomment() {
+        return pcomment;
+    }
+    public void setPcomment(String pcomment) {
+        this.pcomment = (pcomment == null) ? null : pcomment.trim();
+    }
+    public List<List<String>> getPtags() {
+        return ptags;
+    }
+    public void setPtags(List<List<String>> ptags) {
+        this.ptags = ptags;
+    }
+    public String getPoriginal() {
+        return poriginal;
+    }
+    public void setPoriginal(String poriginal) {
+        this.poriginal = poriginal;
+    }
+    public String getPstatus() {
+        return pstatus;
+    }
+    public void setPstatus(String pstatus) {
+        this.pstatus = pstatus;
+    }
+    public Void getPbalanceassertion() {
+        return pbalanceassertion;
+    }
+    public void setPbalanceassertion(Void pbalanceassertion) {
+        this.pbalanceassertion = pbalanceassertion;
+    }
+    public String getPaccount() {
+        return paccount;
+    }
+    public void setPaccount(String paccount) {
+        this.paccount = paccount;
+    }
+    public List<ParsedAmount> getPamount() {
+        return pamount;
+    }
+    public void setPamount(List<ParsedAmount> pamount) {
+        this.pamount = pamount;
+    }
+    public LedgerTransactionAccount asLedgerAccount() {
+        ParsedAmount amt = pamount.get(0);
+        return new LedgerTransactionAccount(paccount, amt.getAquantity()
+                                                         .asFloat(), amt.getAcommodity(),
+                getPcomment());
+    }
+
+}
diff --git a/app/src/main/java/net/ktnx/mobileledger/json/v1_23/ParsedPrice.java b/app/src/main/java/net/ktnx/mobileledger/json/v1_23/ParsedPrice.java
new file mode 100644 (file)
index 0000000..80a810a
--- /dev/null
@@ -0,0 +1,78 @@
+/*
+ * Copyright © 2020 Damyan Ivanov.
+ * This file is part of MoLe.
+ * MoLe is free software: you can distribute it and/or modify it
+ * under the term of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your opinion), any later version.
+ *
+ * MoLe is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License terms for details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with MoLe. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+package net.ktnx.mobileledger.json.v1_23;
+
+class ParsedPrice {
+    private String tag;
+    private Contents contents;
+    public ParsedPrice() {
+        tag = "NoPrice";
+    }
+    public Contents getContents() {
+        return contents;
+    }
+    public void setContents(Contents contents) {
+        this.contents = contents;
+    }
+    public String getTag() {
+        return tag;
+    }
+    public void setTag(String tag) {
+        this.tag = tag;
+    }
+    private static class Contents {
+        private ParsedPrice aprice;
+        private ParsedQuantity aquantity;
+        private String acommodity;
+        private boolean aismultiplier;
+        private ParsedStyle astyle;
+        public Contents() {
+            acommodity = "";
+        }
+        public ParsedPrice getAprice() {
+            return aprice;
+        }
+        public void setAprice(ParsedPrice aprice) {
+            this.aprice = aprice;
+        }
+        public ParsedQuantity getAquantity() {
+            return aquantity;
+        }
+        public void setAquantity(ParsedQuantity aquantity) {
+            this.aquantity = aquantity;
+        }
+        public String getAcommodity() {
+            return acommodity;
+        }
+        public void setAcommodity(String acommodity) {
+            this.acommodity = acommodity;
+        }
+        public boolean isAismultiplier() {
+            return aismultiplier;
+        }
+        public void setAismultiplier(boolean aismultiplier) {
+            this.aismultiplier = aismultiplier;
+        }
+        public ParsedStyle getAstyle() {
+            return astyle;
+        }
+        public void setAstyle(ParsedStyle astyle) {
+            this.astyle = astyle;
+        }
+    }
+}
diff --git a/app/src/main/java/net/ktnx/mobileledger/json/v1_23/ParsedQuantity.java b/app/src/main/java/net/ktnx/mobileledger/json/v1_23/ParsedQuantity.java
new file mode 100644 (file)
index 0000000..5502693
--- /dev/null
@@ -0,0 +1,23 @@
+/*
+ * Copyright © 2020 Damyan Ivanov.
+ * This file is part of MoLe.
+ * MoLe is free software: you can distribute it and/or modify it
+ * under the term of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your opinion), any later version.
+ *
+ * MoLe is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License terms for details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with MoLe. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+package net.ktnx.mobileledger.json.v1_23;
+
+import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
+
+@JsonIgnoreProperties(ignoreUnknown = true)
+public class ParsedQuantity extends net.ktnx.mobileledger.json.ParsedQuantity {}
diff --git a/app/src/main/java/net/ktnx/mobileledger/json/v1_23/ParsedSourcePos.java b/app/src/main/java/net/ktnx/mobileledger/json/v1_23/ParsedSourcePos.java
new file mode 100644 (file)
index 0000000..b3ea5db
--- /dev/null
@@ -0,0 +1,44 @@
+/*
+ * Copyright © 2020 Damyan Ivanov.
+ * This file is part of MoLe.
+ * MoLe is free software: you can distribute it and/or modify it
+ * under the term of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your opinion), any later version.
+ *
+ * MoLe is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License terms for details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with MoLe. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+package net.ktnx.mobileledger.json.v1_23;
+
+class ParsedSourcePos {
+    private String sourceName = "";
+    private int sourceLine = 1;
+    private int sourceColumn = 1;
+    public ParsedSourcePos() {
+    }
+    public String getSourceName() {
+        return sourceName;
+    }
+    public void setSourceName(String sourceName) {
+        this.sourceName = sourceName;
+    }
+    public int getSourceLine() {
+        return sourceLine;
+    }
+    public void setSourceLine(int sourceLine) {
+        this.sourceLine = sourceLine;
+    }
+    public int getSourceColumn() {
+        return sourceColumn;
+    }
+    public void setSourceColumn(int sourceColumn) {
+        this.sourceColumn = sourceColumn;
+    }
+}
diff --git a/app/src/main/java/net/ktnx/mobileledger/json/v1_23/ParsedStyle.java b/app/src/main/java/net/ktnx/mobileledger/json/v1_23/ParsedStyle.java
new file mode 100644 (file)
index 0000000..d3a0a13
--- /dev/null
@@ -0,0 +1,33 @@
+/*
+ * Copyright © 2020 Damyan Ivanov.
+ * This file is part of MoLe.
+ * MoLe is free software: you can distribute it and/or modify it
+ * under the term of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your opinion), any later version.
+ *
+ * MoLe is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License terms for details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with MoLe. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+package net.ktnx.mobileledger.json.v1_23;
+
+import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
+
+@JsonIgnoreProperties(ignoreUnknown = true)
+public class ParsedStyle extends net.ktnx.mobileledger.json.ParsedStyle {
+    private int asprecision;
+    public ParsedStyle() {
+    }
+    public int getAsprecision() {
+        return asprecision;
+    }
+    public void setAsprecision(int asprecision) {
+        this.asprecision = asprecision;
+    }
+}
diff --git a/app/src/main/java/net/ktnx/mobileledger/json/v1_23/TransactionListParser.java b/app/src/main/java/net/ktnx/mobileledger/json/v1_23/TransactionListParser.java
new file mode 100644 (file)
index 0000000..7f5350a
--- /dev/null
@@ -0,0 +1,44 @@
+/*
+ * Copyright © 2020 Damyan Ivanov.
+ * This file is part of MoLe.
+ * MoLe is free software: you can distribute it and/or modify it
+ * under the term of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your opinion), any later version.
+ *
+ * MoLe is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License terms for details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with MoLe. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+package net.ktnx.mobileledger.json.v1_23;
+
+import com.fasterxml.jackson.databind.MappingIterator;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.databind.ObjectReader;
+
+import net.ktnx.mobileledger.model.LedgerTransaction;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.text.ParseException;
+
+public class TransactionListParser extends net.ktnx.mobileledger.json.TransactionListParser {
+
+    private final MappingIterator<ParsedLedgerTransaction> iterator;
+
+    public TransactionListParser(InputStream input) throws IOException {
+
+        ObjectMapper mapper = new ObjectMapper();
+        ObjectReader reader = mapper.readerFor(ParsedLedgerTransaction.class);
+        iterator = reader.readValues(input);
+    }
+    public LedgerTransaction nextTransaction() throws ParseException {
+        return iterator.hasNext() ? iterator.next()
+                                            .asLedgerTransaction() : null;
+    }
+}
index dfd3d982011a105191f5e801bea2fcc5e276c4d6..807e93d348b1e182d81090d94fc097b32cd4be8f 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright © 2021 Damyan Ivanov.
+ * Copyright © 2024 Damyan Ivanov.
  * This file is part of MoLe.
  * MoLe is free software: you can distribute it and/or modify it
  * under the term of the GNU General Public License as published by
@@ -34,12 +34,19 @@ public abstract class AccountListItem {
         else
             throw new RuntimeException("Unsupported sub-class " + this);
     }
-    @NotNull
-    public LedgerAccount getAccount() {
-        if (this instanceof Account)
-            return ((Account) this).account;
-
-        throw new IllegalStateException(String.format("Item type is not Account, but %s", this));
+    public boolean isAccount() {
+        return this instanceof Account;
+    }
+    public Account toAccount() {
+        assert isAccount();
+        return ((Account) this);
+    }
+    public boolean isHeader() {
+        return this instanceof Header;
+    }
+    public Header toHeader() {
+        assert isHeader();
+        return ((Header) this);
     }
     public enum Type {ACCOUNT, HEADER}
 
@@ -59,6 +66,13 @@ public abstract class AccountListItem {
                    ((Account) other).account.getAmountsString()
                                             .equals(account.getAmountsString());
         }
+        @NotNull
+        public LedgerAccount getAccount() {
+            return account;
+        }
+        public boolean allAmountsAreZero() {
+            return account.allAmountsAreZero();
+        }
     }
 
     public static class Header extends AccountListItem {
index 1be684cf1b0d1bcb700485384a3a068963576610..c2e62772f5990ef911854c838218edde70e50f82 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright © 2021 Damyan Ivanov.
+ * Copyright © 2024 Damyan Ivanov.
  * This file is part of MoLe.
  * MoLe is free software: you can distribute it and/or modify it
  * under the term of the GNU General Public License as published by
@@ -201,13 +201,21 @@ public class LedgerAccount {
         if (amounts != null)
             amounts.clear();
     }
-    public boolean amountsExpanded() { return amountsExpanded; }
-    public void setAmountsExpanded(boolean flag) { amountsExpanded = flag; }
-    public void toggleAmountsExpanded() { amountsExpanded = !amountsExpanded; }
+    public boolean amountsExpanded() {return amountsExpanded;}
+    public void setAmountsExpanded(boolean flag) {amountsExpanded = flag;}
+    public void toggleAmountsExpanded() {amountsExpanded = !amountsExpanded;}
     public void propagateAmountsTo(LedgerAccount acc) {
         for (LedgerAmount a : amounts)
             a.propagateToAccount(acc);
     }
+    public boolean allAmountsAreZero() {
+        for (LedgerAmount a : amounts) {
+            if (a.getAmount() != 0)
+                return false;
+        }
+
+        return true;
+    }
     public List<LedgerAmount> getAmounts() {
         return amounts;
     }
index 329109fdccf3947f578e0eb2ac20a6abd58fcb14..a8b957dc330be48f6a304cce50bd5f5012468b11 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright © 2021 Damyan Ivanov.
+ * Copyright © 2024 Damyan Ivanov.
  * This file is part of MoLe.
  * MoLe is free software: you can distribute it and/or modify it
  * under the term of the GNU General Public License as published by
@@ -38,6 +38,7 @@ import java.util.Locale;
 public class MainModel extends ViewModel {
     public final MutableLiveData<Integer> foundTransactionItemIndex = new MutableLiveData<>(null);
     private final MutableLiveData<Boolean> updatingFlag = new MutableLiveData<>(false);
+    private final MutableLiveData<Boolean> showZeroBalanceAccounts = new MutableLiveData<>(true);
     private final MutableLiveData<String> accountFilter = new MutableLiveData<>(null);
     private final MutableLiveData<List<TransactionListItem>> displayedTransactions =
             new MutableLiveData<>(new ArrayList<>());
@@ -45,7 +46,6 @@ public class MainModel extends ViewModel {
     private SimpleDate firstTransactionDate;
     private SimpleDate lastTransactionDate;
     transient private RetrieveTransactionsTask retrieveTransactionsTask;
-    transient private Thread displayedAccountsUpdater;
     private TransactionsDisplayedFilter displayedTransactionsUpdater;
     public LiveData<Boolean> getUpdatingFlag() {
         return updatingFlag;
@@ -66,6 +66,7 @@ public class MainModel extends ViewModel {
     public void setFirstTransactionDate(SimpleDate earliestDate) {
         this.firstTransactionDate = earliestDate;
     }
+    public MutableLiveData<Boolean> getShowZeroBalanceAccounts() {return showZeroBalanceAccounts;}
     public MutableLiveData<String> getAccountFilter() {
         return accountFilter;
     }
@@ -81,6 +82,7 @@ public class MainModel extends ViewModel {
             return;
         }
         Profile profile = Data.getProfile();
+        assert profile != null;
 
         retrieveTransactionsTask = new RetrieveTransactionsTask(profile);
         Logger.debug("db", "Created a background transaction retrieval task");
index d7da335d675a19da8e1c273f717badb72488ce0c..dcc16f3678ed3885605d4b85de77653f1f957b9c 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright © 2021 Damyan Ivanov.
+ * Copyright © 2024 Damyan Ivanov.
  * This file is part of MoLe.
  * MoLe is free software: you can distribute it and/or modify it
  * under the term of the GNU General Public License as published by
@@ -17,6 +17,8 @@
 
 package net.ktnx.mobileledger.ui.account_summary;
 
+import static net.ktnx.mobileledger.utils.Logger.debug;
+
 import android.content.res.Resources;
 import android.view.LayoutInflater;
 import android.view.View;
@@ -48,8 +50,6 @@ import org.jetbrains.annotations.NotNull;
 import java.util.List;
 import java.util.Locale;
 
-import static net.ktnx.mobileledger.utils.Logger.debug;
-
 public class AccountSummaryAdapter extends RecyclerView.Adapter<AccountSummaryAdapter.RowHolder> {
     public static final int AMOUNT_LIMIT = 3;
     private static final int ITEM_TYPE_HEADER = 1;
@@ -66,8 +66,10 @@ public class AccountSummaryAdapter extends RecyclerView.Adapter<AccountSummaryAd
                                            @NonNull AccountListItem newItem) {
                 Change changes = new Change();
 
-                final LedgerAccount oldAcc = oldItem.getAccount();
-                final LedgerAccount newAcc = newItem.getAccount();
+                final LedgerAccount oldAcc = oldItem.toAccount()
+                                                    .getAccount();
+                final LedgerAccount newAcc = newItem.toAccount()
+                                                    .getAccount();
 
                 if (!Misc.equalStrings(oldAcc.getName(), newAcc.getName()))
                     changes.add(Change.NAME);
@@ -97,8 +99,10 @@ public class AccountSummaryAdapter extends RecyclerView.Adapter<AccountSummaryAd
                 if (oldType == AccountListItem.Type.HEADER)
                     return true;
 
-                return oldItem.getAccount()
-                              .getId() == newItem.getAccount()
+                return oldItem.toAccount()
+                              .getAccount()
+                              .getId() == newItem.toAccount()
+                                                 .getAccount()
                                                  .getId();
             }
             @Override
@@ -114,6 +118,7 @@ public class AccountSummaryAdapter extends RecyclerView.Adapter<AccountSummaryAd
             return 0;
         return listDiffer.getCurrentList()
                          .get(position)
+                         .toAccount()
                          .getAccount()
                          .getId();
     }
@@ -258,6 +263,7 @@ public class AccountSummaryAdapter extends RecyclerView.Adapter<AccountSummaryAd
         private LedgerAccount getAccount() {
             return listDiffer.getCurrentList()
                              .get(getBindingAdapterPosition())
+                             .toAccount()
                              .getAccount();
         }
         private void toggleAmountsExpanded() {
@@ -301,7 +307,8 @@ public class AccountSummaryAdapter extends RecyclerView.Adapter<AccountSummaryAd
         }
         @Override
         public void bind(AccountListItem item, @Nullable List<Object> payloads) {
-            LedgerAccount acc = item.getAccount();
+            LedgerAccount acc = item.toAccount()
+                                    .getAccount();
 
             Change changes = new Change();
             if (payloads != null) {
index 35a9edb7205c84bd16889468d90416b6018d547b..807c16daf8540af877827e2729d0e848f733d3c3 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright © 2021 Damyan Ivanov.
+ * Copyright © 2024 Damyan Ivanov.
  * This file is part of MoLe.
  * MoLe is free software: you can distribute it and/or modify it
  * under the term of the GNU General Public License as published by
 
 package net.ktnx.mobileledger.ui.account_summary;
 
+import static net.ktnx.mobileledger.utils.Logger.debug;
+
 import android.content.Context;
 import android.os.Bundle;
 import android.view.LayoutInflater;
+import android.view.Menu;
+import android.view.MenuInflater;
+import android.view.MenuItem;
 import android.view.View;
 import android.view.ViewGroup;
 
@@ -31,6 +36,7 @@ import androidx.recyclerview.widget.LinearLayoutManager;
 import androidx.recyclerview.widget.RecyclerView;
 import androidx.swiperefreshlayout.widget.SwipeRefreshLayout;
 
+import net.ktnx.mobileledger.R;
 import net.ktnx.mobileledger.async.GeneralBackgroundTasks;
 import net.ktnx.mobileledger.databinding.AccountSummaryFragmentBinding;
 import net.ktnx.mobileledger.db.AccountWithAmounts;
@@ -51,11 +57,11 @@ import java.util.ArrayList;
 import java.util.HashMap;
 import java.util.List;
 
-import static net.ktnx.mobileledger.utils.Logger.debug;
-
 public class AccountSummaryFragment extends MobileLedgerListFragment {
     public AccountSummaryAdapter modelAdapter;
     private AccountSummaryFragmentBinding b;
+    private MenuItem menuShowZeroBalances;
+    private MainModel model;
     @Override
     public void onCreate(@Nullable Bundle savedInstanceState) {
         super.onCreate(savedInstanceState);
@@ -82,7 +88,7 @@ public class AccountSummaryFragment extends MobileLedgerListFragment {
         debug("flow", "AccountSummaryFragment.onActivityCreated()");
         super.onViewCreated(view, savedInstanceState);
 
-        MainModel model = new ViewModelProvider(requireActivity()).get(MainModel.class);
+        model = new ViewModelProvider(requireActivity()).get(MainModel.class);
 
         Data.backgroundTasksRunning.observe(this.getViewLifecycleOwner(),
                 this::onBackgroundTaskRunningChanged);
@@ -109,15 +115,40 @@ public class AccountSummaryFragment extends MobileLedgerListFragment {
             model.scheduleTransactionListRetrieval();
         });
 
-        Data.observeProfile(this, this::onProfileChanged);
+        Data.observeProfile(this, profile -> onProfileChanged(profile, Boolean.TRUE.equals(
+                model.getShowZeroBalanceAccounts()
+                     .getValue())));
+    }
+    @Override
+    public void onCreateOptionsMenu(@NotNull Menu menu, @NotNull MenuInflater inflater) {
+        inflater.inflate(R.menu.account_list, menu);
+
+        menuShowZeroBalances = menu.findItem(R.id.menu_account_list_show_zero_balances);
+        if ((menuShowZeroBalances == null))
+            throw new AssertionError();
+
+        menuShowZeroBalances.setOnMenuItemClickListener(menuItem -> {
+            model.getShowZeroBalanceAccounts()
+                 .setValue(Boolean.FALSE.equals(model.getShowZeroBalanceAccounts()
+                                                     .getValue()));
+            return true;
+        });
+
+        model.getShowZeroBalanceAccounts()
+             .observe(this, v -> {
+                 menuShowZeroBalances.setChecked(v);
+                 onProfileChanged(Data.getProfile(), v);
+             });
+
+        super.onCreateOptionsMenu(menu, inflater);
     }
-    private void onProfileChanged(Profile profile) {
+    private void onProfileChanged(Profile profile, boolean showZeroBalanceAccounts) {
         if (profile == null)
             return;
 
         DB.get()
           .getAccountDAO()
-          .getAllWithAmounts(profile.getId())
+          .getAllWithAmounts(profile.getId(), showZeroBalanceAccounts)
           .observe(getViewLifecycleOwner(), list -> GeneralBackgroundTasks.run(() -> {
               List<AccountListItem> adapterList = new ArrayList<>();
               adapterList.add(new AccountListItem.Header(Data.lastAccountsUpdateText));
@@ -134,8 +165,57 @@ public class AccountSummaryFragment extends MobileLedgerListFragment {
                       adapterList.add(new AccountListItem.Account(account));
                   accMap.put(dbAcc.account.getName(), account);
               }
+
+              if (!showZeroBalanceAccounts) {
+                  removeZeroAccounts(adapterList);
+              }
               modelAdapter.setAccounts(adapterList);
               Data.lastUpdateAccountCount.postValue(adapterList.size() - 1);
           }));
     }
+    private void removeZeroAccounts(List<AccountListItem> list) {
+        boolean removed = true;
+
+        while (removed) {
+            AccountListItem last = null;
+            removed = false;
+            List<AccountListItem> newList = new ArrayList<>();
+
+            for (AccountListItem item : list) {
+                if (last == null) {
+                    last = item;
+                    continue;
+                }
+
+                if (!last.isAccount() || !last.toAccount()
+                                              .allAmountsAreZero() || last.toAccount()
+                                                                          .getAccount()
+                                                                          .isParentOf(
+                                                                                  item.toAccount()
+                                                                                      .getAccount()))
+                {
+                    newList.add(last);
+                }
+                else {
+                    removed = true;
+                }
+
+                last = item;
+            }
+
+            if (last != null) {
+                if (!last.isAccount() || !last.toAccount()
+                                              .allAmountsAreZero())
+                {
+                    newList.add(last);
+                }
+                else {
+                    removed = true;
+                }
+            }
+
+            list.clear();
+            list.addAll(newList);
+        }
+    }
 }
index 81f1ed07ae7d4d20753cc0304918be903cdec1a5..51bcd306b2da3e6fb50223ecc5f61e7f5dd357dc 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright © 2021 Damyan Ivanov.
+ * Copyright © 2022 Damyan Ivanov.
  * This file is part of MoLe.
  * MoLe is free software: you can distribute it and/or modify it
  * under the term of the GNU General Public License as published by
@@ -18,6 +18,7 @@
 package net.ktnx.mobileledger.ui.new_transaction;
 
 import android.annotation.SuppressLint;
+import android.os.Build;
 import android.text.TextUtils;
 
 import androidx.annotation.NonNull;
@@ -443,14 +444,14 @@ public class NewTransactionModel extends ViewModel {
         LedgerTransaction tr = head.asLedgerTransaction();
 
         tr.setComment(head.getComment());
-        LedgerTransactionAccount emptyAmountAccount = null;
-        float emptyAmountAccountBalance = 0;
+        HashMap<String, List<LedgerTransactionAccount>> emptyAmountAccounts = new HashMap<>();
+        HashMap<String, Float> emptyAmountAccountBalance = new HashMap<>();
         for (int i = 1; i < list.size(); i++) {
             TransactionAccount item = list.get(i)
                                           .toTransactionAccount();
+            String currency = item.getCurrency();
             LedgerTransactionAccount acc = new LedgerTransactionAccount(item.getAccountName()
-                                                                            .trim(),
-                    item.getCurrency());
+                                                                            .trim(), currency);
             if (acc.getAccountName()
                    .isEmpty())
                 continue;
@@ -459,17 +460,53 @@ public class NewTransactionModel extends ViewModel {
 
             if (item.isAmountSet()) {
                 acc.setAmount(item.getAmount());
-                emptyAmountAccountBalance += item.getAmount();
+                Float emptyCurrBalance = emptyAmountAccountBalance.get(currency);
+                if (emptyCurrBalance == null) {
+                    emptyAmountAccountBalance.put(currency, item.getAmount());
+                }
+                else {
+                    emptyAmountAccountBalance.put(currency, emptyCurrBalance + item.getAmount());
+                }
             }
             else {
-                emptyAmountAccount = acc;
+                List<LedgerTransactionAccount> emptyCurrAccounts =
+                        emptyAmountAccounts.get(currency);
+                if (emptyCurrAccounts == null)
+                    emptyAmountAccounts.put(currency, emptyCurrAccounts = new ArrayList<>());
+                emptyCurrAccounts.add(acc);
             }
 
             tr.addAccount(acc);
         }
 
-        if (emptyAmountAccount != null)
-            emptyAmountAccount.setAmount(-emptyAmountAccountBalance);
+        if (emptyAmountAccounts.size() > 0) {
+            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
+                emptyAmountAccounts.forEach((currency, accounts) -> {
+                    final Float balance = emptyAmountAccountBalance.get(currency);
+
+                    if (balance != null && !Misc.isZero(balance) && accounts.size() != 1) {
+                        throw new RuntimeException(String.format(Locale.US,
+                                "Should not happen: approved transaction has %d accounts " +
+                                "without amounts for currency '%s'", accounts.size(), currency));
+                    }
+                    accounts.forEach(acc -> acc.setAmount(balance == null ? 0 : -balance));
+                });
+            }
+            else {
+                for (String currency : emptyAmountAccounts.keySet()) {
+                    List<LedgerTransactionAccount> accounts =
+                            Objects.requireNonNull(emptyAmountAccounts.get(currency));
+                    final Float balance = emptyAmountAccountBalance.get(currency);
+                    if (balance != null && !Misc.isZero(balance) && accounts.size() != 1)
+                        throw new RuntimeException(String.format(Locale.US,
+                                "Should not happen: approved transaction has %d accounts for " +
+                                "currency %s", accounts.size(), currency));
+                    for (LedgerTransactionAccount acc : accounts) {
+                        acc.setAmount(balance == null ? 0 : -balance);
+                    }
+                }
+            }
+        }
 
         return tr;
     }
@@ -496,6 +533,7 @@ public class NewTransactionModel extends ViewModel {
         int singleNegativeIndex = -1;
         int singlePositiveIndex = -1;
         int negativeCount = 0;
+        boolean hasCurrency = false;
         for (int i = 0; i < accounts.size(); i++) {
             LedgerTransactionAccount acc = accounts.get(i);
             TransactionAccount item = new TransactionAccount(acc.getAccountName(),
@@ -525,6 +563,10 @@ public class NewTransactionModel extends ViewModel {
             }
             else
                 item.resetAmount();
+
+            if (item.getCurrency()
+                    .length() > 0)
+                hasCurrency = true;
         }
         if (BuildConfig.DEBUG)
             dumpItemList("Loaded previous transaction", newList);
@@ -538,9 +580,12 @@ public class NewTransactionModel extends ViewModel {
             moveItemLast(newList, singlePositiveIndex);
         }
 
+        final boolean foundTransactionHasCurrency = hasCurrency;
         Misc.onMainThread(() -> {
             setItems(newList);
             noteFocusChanged(1, FocusedElement.Amount);
+            if (foundTransactionHasCurrency)
+                showCurrency.setValue(true);
         });
     }
     /**
@@ -730,8 +775,8 @@ public class NewTransactionModel extends ViewModel {
                                 !Misc.equalStrings(acc.getAmountHint(), hint))
                             {
                                 Logger.debug("submittable",
-                                        String.format("Setting amount hint of {%s} to %s [%s]",
-                                                acc.toString(), hint, balCurrency));
+                                        String.format("Setting amount hint of {%s} to %s [%s]", acc,
+                                                hint, balCurrency));
                                 acc.setAmountHint(hint);
                                 listChanged = true;
                             }
@@ -1026,7 +1071,7 @@ public class NewTransactionModel extends ViewModel {
                 b.append(String.format(" '%s'", description));
 
             if (date != null)
-                b.append(String.format("@%s", date.toString()));
+                b.append(String.format("@%s", date));
 
             if (!TextUtils.isEmpty(comment))
                 b.append(String.format(" /%s/", comment));
@@ -1264,8 +1309,7 @@ public class NewTransactionModel extends ViewModel {
             equal = equal && Misc.equalStrings(currency, other.currency) && isLast == other.isLast;
 
             Logger.debug("new-trans",
-                    String.format("Comparing {%s} and {%s}: %s", this.toString(), other.toString(),
-                            equal));
+                    String.format("Comparing {%s} and {%s}: %s", this, other, equal));
             return equal;
         }
         public int getAccountNameCursorPosition() {
index ab0d07d92adecba67801d228dea989d5dbcf05b8..99934543e7b544bf876fcc0010282d9cb373801e 100644 (file)
@@ -341,6 +341,9 @@ public class ProfileDetailFragment extends Fragment {
             if (itemId == R.id.api_version_menu_html) {
                 apiVer = API.html;
             }
+            else if (itemId == R.id.api_version_menu_1_23) {
+                apiVer = API.v1_23;
+            }
             else if (itemId == R.id.api_version_menu_1_19_1) {
                 apiVer = API.v1_19_1;
             }
index 9860e80490946d3316bc2b4147ffcaab62a54f31..f841ad76db445cb76e375bbb9ccb83912e14fa79 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright © 2021 Damyan Ivanov.
+ * Copyright © 2022 Damyan Ivanov.
  * This file is part of MoLe.
  * MoLe is free software: you can distribute it and/or modify it
  * under the term of the GNU General Public License as published by
  * along with MoLe. If not, see <https://www.gnu.org/licenses/>.
  */
 
-//
-// Substantial portions taken from DividerItemDecoration subject to the following license terms:
-//
-// Copyright 2018 The Android Open Source Project
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//      http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-//
 package net.ktnx.mobileledger.ui.templates;
 
 import android.content.Context;
@@ -94,7 +77,8 @@ class TemplateListDivider extends DividerItemDecoration {
         for (int i = 0; i < childCount; i++) {
             final View child = parent.getChildAt(i);
             final int childAdapterPosition = parent.getChildAdapterPosition(child);
-            if (adapter.getItemViewType(childAdapterPosition) ==
+            if (childAdapterPosition == RecyclerView.NO_POSITION ||
+                adapter.getItemViewType(childAdapterPosition) ==
                 TemplatesRecyclerViewAdapter.ITEM_TYPE_DIVIDER ||
                 childAdapterPosition + 1 < itemCount &&
                 adapter.getItemViewType(childAdapterPosition + 1) ==
@@ -110,6 +94,10 @@ class TemplateListDivider extends DividerItemDecoration {
     }
 
     private void drawHorizontal(Canvas canvas, RecyclerView parent) {
+        final RecyclerView.LayoutManager layoutManager = parent.getLayoutManager();
+        if (layoutManager == null)
+            return;
+
         canvas.save();
         final int top;
         final int bottom;
@@ -133,14 +121,16 @@ class TemplateListDivider extends DividerItemDecoration {
         for (int i = 0; i < childCount; i++) {
             final View child = parent.getChildAt(i);
             final int childAdapterPosition = parent.getChildAdapterPosition(child);
-            if (adapter.getItemViewType(childAdapterPosition) ==
+            if (childAdapterPosition == RecyclerView.NO_POSITION ||
+                adapter.getItemViewType(childAdapterPosition) ==
                 TemplatesRecyclerViewAdapter.ITEM_TYPE_DIVIDER ||
                 childAdapterPosition + 1 < itemCount &&
                 adapter.getItemViewType(childAdapterPosition + 1) ==
                 TemplatesRecyclerViewAdapter.ITEM_TYPE_DIVIDER)
+            {
                 continue;
-            parent.getLayoutManager()
-                  .getDecoratedBoundsWithMargins(child, mBounds);
+            }
+            layoutManager.getDecoratedBoundsWithMargins(child, mBounds);
             final int right = mBounds.right + Math.round(child.getTranslationX());
             final int left = right - divider.getIntrinsicWidth();
             divider.setBounds(left, top, right, bottom);
@@ -156,7 +146,8 @@ class TemplateListDivider extends DividerItemDecoration {
                 (TemplatesRecyclerViewAdapter) Objects.requireNonNull(parent.getAdapter());
         final int itemCount = adapter.getItemCount();
 
-        if (adapter.getItemViewType(childAdapterPosition) ==
+        if (childAdapterPosition == RecyclerView.NO_POSITION ||
+            adapter.getItemViewType(childAdapterPosition) ==
             TemplatesRecyclerViewAdapter.ITEM_TYPE_DIVIDER ||
             childAdapterPosition + 1 < itemCount &&
             adapter.getItemViewType(childAdapterPosition + 1) ==
index ed652879e633bc53d536586479a99b9d0c656370..3f9535ceb6b97403fde821f38e5aeb6977741881 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright © 2021 Damyan Ivanov.
+ * Copyright © 2022 Damyan Ivanov.
  * This file is part of MoLe.
  * MoLe is free software: you can distribute it and/or modify it
  * under the term of the GNU General Public License as published by
@@ -119,7 +119,7 @@ public class TemplatesActivity extends CrashReportingActivity
     public void onDuplicateTemplate(long id) {
         DB.get()
           .getTemplateDAO()
-          .duplicateTemplateWitAccounts(id, null);
+          .duplicateTemplateWithAccounts(id, null);
     }
     @Override
     public void onEditTemplate(Long id) {
index 0c0ff6a7e3163f1d2ed82eeaff47c56b0a883702..bc463ed564661bb6d3da11155158322bbb2b5b29 100644 (file)
@@ -28,7 +28,6 @@ import androidx.recyclerview.widget.RecyclerView;
 import net.ktnx.mobileledger.databinding.LastUpdateLayoutBinding;
 import net.ktnx.mobileledger.databinding.TransactionDelimiterBinding;
 import net.ktnx.mobileledger.databinding.TransactionListRowBinding;
-import net.ktnx.mobileledger.model.LedgerTransaction;
 import net.ktnx.mobileledger.model.TransactionListItem;
 import net.ktnx.mobileledger.utils.Logger;
 import net.ktnx.mobileledger.utils.Misc;
@@ -75,7 +74,9 @@ public class TransactionListAdapter extends RecyclerView.Adapter<TransactionRowH
                         return oldItem.getTransaction()
                                       .equals(newItem.getTransaction()) &&
                                Misc.equalStrings(oldItem.getBoldAccountName(),
-                                       newItem.getBoldAccountName());
+                                       newItem.getBoldAccountName()) &&
+                               Misc.equalStrings(oldItem.getRunningTotal(),
+                                       newItem.getRunningTotal());
                     case HEADER:
                         // headers don't differ in their contents. they observe the last update
                         // date and react to its changes
index 390b4941719c2eca9b5d4c4e992086131ac3525e..b5fe883d14a74cc2d92cf308d6382838155b3bb0 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright © 2021 Damyan Ivanov.
+ * Copyright © 2024 Damyan Ivanov.
  * This file is part of MoLe.
  * MoLe is free software: you can distribute it and/or modify it
  * under the term of the GNU General Public License as published by
@@ -19,9 +19,12 @@ package net.ktnx.mobileledger.ui.transaction_list;
 
 import android.view.View;
 
+import androidx.constraintlayout.widget.ConstraintLayout;
+
 import net.ktnx.mobileledger.App;
 import net.ktnx.mobileledger.databinding.TransactionDelimiterBinding;
 import net.ktnx.mobileledger.model.TransactionListItem;
+import net.ktnx.mobileledger.utils.DimensionUtils;
 import net.ktnx.mobileledger.utils.Globals;
 import net.ktnx.mobileledger.utils.SimpleDate;
 
@@ -46,15 +49,21 @@ class TransactionListDelimiterRowHolder extends TransactionRowHolderBase {
             b.transactionDelimiterMonth.setText(
                     Globals.monthNames[cal.get(GregorianCalendar.MONTH)]);
             b.transactionDelimiterMonth.setVisibility(View.VISIBLE);
-            //                holder.vDelimiterLine.setBackgroundResource(R.drawable
-            //                .dashed_border_8dp);
             b.transactionDelimiterThick.setVisibility(View.VISIBLE);
+            ConstraintLayout.LayoutParams lp =
+                    (ConstraintLayout.LayoutParams) b.transactionDelimiterThick.getLayoutParams();
+            lp.height = DimensionUtils.dp2px(b.getRoot()
+                                              .getContext(), 4);
+            b.transactionDelimiterThick.setLayoutParams(lp);
         }
         else {
             b.transactionDelimiterMonth.setVisibility(View.GONE);
-            //                holder.vDelimiterLine.setBackgroundResource(R.drawable
-            //                .dashed_border_1dp);
-            b.transactionDelimiterThick.setVisibility(View.GONE);
+            ConstraintLayout.LayoutParams lp =
+                    (ConstraintLayout.LayoutParams) b.transactionDelimiterThick.getLayoutParams();
+            lp.height = DimensionUtils.dp2px(b.getRoot()
+                                              .getContext(), 1.3f);
+            b.transactionDelimiterThick.setLayoutParams(lp);
+            b.transactionDelimiterThick.setVisibility(View.VISIBLE);
         }
 
     }
index 7cf710ac0067b3b90319190c4f5524679beb38b6..348a5598a324073ea2415f353cd32a1019b72542 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright © 2021 Damyan Ivanov.
+ * Copyright © 2024 Damyan Ivanov.
  * This file is part of MoLe.
  * MoLe is free software: you can distribute it and/or modify it
  * under the term of the GNU General Public License as published by
@@ -17,6 +17,8 @@
 
 package net.ktnx.mobileledger.utils;
 
+import static net.ktnx.mobileledger.utils.Logger.debug;
+
 import android.app.Activity;
 import android.content.res.ColorStateList;
 import android.content.res.Resources;
@@ -38,8 +40,6 @@ import java.util.List;
 import java.util.Locale;
 import java.util.Objects;
 
-import static net.ktnx.mobileledger.utils.Logger.debug;
-
 public class Colors {
     public static final int DEFAULT_HUE_DEG = 261;
     public static final MutableLiveData<Integer> themeWatch = new MutableLiveData<>(0);
@@ -76,7 +76,7 @@ public class Colors {
         TypedValue tv = new TypedValue();
         theme.resolveAttribute(R.attr.table_row_dark_bg, tv, true);
         tableRowDarkBG = tv.data;
-        theme.resolveAttribute(R.attr.colorPrimary, tv, true);
+        theme.resolveAttribute(androidx.appcompat.R.attr.colorPrimary, tv, true);
         primary = tv.data;
 
         if (themePrimaryColor.size() == 0) {
@@ -84,7 +84,7 @@ public class Colors {
                 Resources.Theme tmpTheme = theme.getResources()
                                                 .newTheme();
                 tmpTheme.applyStyle(themeId, true);
-                tmpTheme.resolveAttribute(R.attr.colorPrimary, tv, false);
+                tmpTheme.resolveAttribute(androidx.appcompat.R.attr.colorPrimary, tv, false);
                 themePrimaryColor.put(themeId, tv.data);
             }
         }
@@ -180,7 +180,7 @@ public class Colors {
                         huesSB.append(", ");
                     huesSB.append(h);
                 }
-                debug("profiles", String.format("used hues: %s", huesSB.toString()));
+                debug("profiles", String.format("used hues: %s", huesSB));
             }
             hues.add(hues.get(0));
 
diff --git a/app/src/main/res/menu/account_list.xml b/app/src/main/res/menu/account_list.xml
new file mode 100644 (file)
index 0000000..5a76161
--- /dev/null
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="utf-8"?><!--
+  ~ Copyright © 2024 Damyan Ivanov.
+  ~ This file is part of MoLe.
+  ~ MoLe is free software: you can distribute it and/or modify it
+  ~ under the term of the GNU General Public License as published by
+  ~ the Free Software Foundation, either version 3 of the License, or
+  ~ (at your opinion), any later version.
+  ~
+  ~ MoLe is distributed in the hope that it will be useful,
+  ~ but WITHOUT ANY WARRANTY; without even the implied warranty of
+  ~ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+  ~ GNU General Public License terms for details.
+  ~
+  ~ You should have received a copy of the GNU General Public License
+  ~ along with MoLe. If not, see <https://www.gnu.org/licenses/>.
+  -->
+
+<menu xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto"
+    >
+
+    <item
+        android:id="@+id/menu_account_list_show_zero_balances"
+        android:checkable="true"
+        android:enabled="true"
+        android:menuCategory="container"
+        android:title="@string/accounts_menu_show_zero"
+        android:titleCondensed="@string/accounts_menu_show_zero_condensed"
+        android:visible="true"
+        app:showAsAction="withText"
+        />
+</menu>
\ No newline at end of file
index cdcb09f277b651f34a489fba7971566ea6aa0b5d..53da72fbb4abbfe56562cba235a1bed9a43f2e30 100644 (file)
         android:id="@+id/api_version_menu_auto"
         android:title="@string/api_auto"
         />
+    <item
+        android:id="@+id/api_version_menu_1_23"
+        android:title="@string/api_1_23"
+        />
     <item
         android:id="@+id/api_version_menu_1_19_1"
         android:title="@string/api_1_19_1"
index 52f789c434c878d0ba265747849b1dbc213102d4..ccb47516c7ce94db047f23881c99479810dc84c8 100644 (file)
@@ -1,6 +1,6 @@
 <?xml version="1.0" encoding="utf-8"?>
 <!--
-  ~ Copyright © 2021 Damyan Ivanov.
+  ~ Copyright © 2024 Damyan Ivanov.
   ~ This file is part of MoLe.
   ~ MoLe is free software: you can distribute it and/or modify it
   ~ under the term of the GNU General Public License as published by
     <string name="config_restored">Успешно възстановяване на настройките</string>
     <string name="no_profile_restore_hint">… а може и да възстановите настройките от резервно копие</string>
     <string name="profile_not_available">Недостъпен профил</string>
+    <string name="api_1_23">Версия 1.23</string>
+    <string name="accounts_menu_show_zero">Сметки с нулев баланс</string>
+    <string name="accounts_menu_show_zero_condensed">Нулеви сметки</string>
 </resources>
index a709d92467c96a65173ede5b93d32c1fcbb89503..52d84690005411a968feef747c3b8d94bf881da9 100644 (file)
@@ -1,5 +1,5 @@
 <!--
-  ~ Copyright © 2021 Damyan Ivanov.
+  ~ Copyright © 2024 Damyan Ivanov.
   ~ This file is part of MoLe.
   ~ MoLe is free software: you can distribute it and/or modify it
   ~ under the term of the GNU General Public License as published by
     <string name="config_restored">Configuration restored successfully</string>
     <string name="no_profile_restore_hint">… or, you may restore from backup</string>
     <string name="profile_not_available">Profile not available</string>
+    <string name="api_1_23">Version 1.23</string>
+    <string name="accounts_menu_show_zero">Show zero balances</string>
+    <string name="accounts_menu_show_zero_condensed">Zero balances</string>
 </resources>
diff --git a/app/src/main/res/xml/backup_descriptor.xml b/app/src/main/res/xml/backup_descriptor.xml
deleted file mode 100644 (file)
index 93cbcae..0000000
+++ /dev/null
@@ -1,21 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-  ~ Copyright © 2019 Damyan Ivanov.
-  ~ This file is part of MoLe.
-  ~ MoLe is free software: you can distribute it and/or modify it
-  ~ under the term of the GNU General Public License as published by
-  ~ the Free Software Foundation, either version 3 of the License, or
-  ~ (at your opinion), any later version.
-  ~
-  ~ MoLe is distributed in the hope that it will be useful,
-  ~ but WITHOUT ANY WARRANTY; without even the implied warranty of
-  ~ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-  ~ GNU General Public License terms for details.
-  ~
-  ~ You should have received a copy of the GNU General Public License
-  ~ along with MoLe. If not, see <https://www.gnu.org/licenses/>.
-  -->
-
-<full-backup-content>
-    <!-- Exclude specific shared preferences that contain GCM registration Id -->
-</full-backup-content>
index 223e12e8fbcb0f28f1559e621f1ebf197a23dbc2..c1da32275828ef31c0ccca046348a72b5a50e47f 100644 (file)
@@ -1,5 +1,5 @@
 <?xml version="1.0" encoding="utf-8"?><!--
-  ~ Copyright © 2019 Damyan Ivanov.
+  ~ Copyright © 2024 Damyan Ivanov.
   ~ This file is part of MoLe.
   ~ MoLe is free software: you can distribute it and/or modify it
   ~ under the term of the GNU General Public License as published by
   -->
 
 <network-security-config>
-    <base-config cleartextTrafficPermitted="true" />
+    <base-config cleartextTrafficPermitted="true">
+        <trust-anchors>
+            <certificates src="system" />
+            <certificates src="user" />
+        </trust-anchors>
+    </base-config>
 </network-security-config>
\ No newline at end of file
index 38a6fe305905272bf9d367a7367f1aa7e3d1b758..e9d71576e71beaaba39d96d67f402a45e9f63e69 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright © 2021 Damyan Ivanov.
+ * Copyright © 2022 Damyan Ivanov.
  * This file is part of MoLe.
  * MoLe is free software: you can distribute it and/or modify it
  * under the term of the GNU General Public License as published by
@@ -24,7 +24,7 @@ buildscript {
         mavenCentral()
     }
     dependencies {
-        classpath 'com.android.tools.build:gradle:4.2.0'
+        classpath 'com.android.tools.build:gradle:8.0.2'
         
 
         // NOTE: Do not place your application dependencies here; they belong
index 048e816edeb7bd5d8292f38228ae641cf4edf448..db96c7725ba87fd8ea2d7a50722e54fe5ebacccf 100644 (file)
@@ -1,5 +1,5 @@
 #
-# Copyright © 2019 Damyan Ivanov.
+# Copyright © 2024 Damyan Ivanov.
 # This file is part of MoLe.
 # MoLe is free software: you can distribute it and/or modify it
 # under the term of the GNU General Public License as published by
 # along with MoLe. If not, see <https://www.gnu.org/licenses/>.
 #
 
-# Project-wide Gradle settings.
-# IDE (e.g. Android Studio) users:
-# Gradle settings configured through the IDE *will override*
-# any settings specified in this file.
-# For more details on how to configure your build environment visit
+## For more details on how to configure your build environment visit
 # http://www.gradle.org/docs/current/userguide/build_environment.html
+#
 # Specifies the JVM arguments used for the daemon process.
 # The setting is particularly useful for tweaking memory settings.
-org.gradle.jvmargs=-Xmx1536m
+# Default value: -Xmx1024m -XX:MaxPermSize=256m
+# org.gradle.jvmargs=-Xmx2048m -XX:MaxPermSize=512m -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8
+#
 # When configured, Gradle will run in incubating parallel mode.
 # This option should only be used with decoupled projects. More details, visit
 # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
 # org.gradle.parallel=true
+#Sun Mar 17 11:29:00 EET 2024
 android.debug.obsoleteApi=true
+android.defaults.buildfeatures.buildconfig=true
+android.enableJetifier=false
+android.enableR8.fullMode=false
+android.nonFinalResIds=false
+android.nonTransitiveRClass=true
 android.useAndroidX=true
-android.enableJetifier=true
\ No newline at end of file
+org.gradle.jvmargs=-Xmx1024M -Dkotlin.daemon.jvm.options\="-Xmx1536M"
+org.gradle.unsafe.configuration-cache=true
index f498716b220cb60659245878993efef0eb0cc2fd..855f89cc8328ec83f5f1cf99a034a4c0aa8cd550 100644 (file)
@@ -1,6 +1,21 @@
-#Sat Nov 28 08:24:27 EET 2020
+#
+# Copyright © 2024 Damyan Ivanov.
+# This file is part of MoLe.
+# MoLe is free software: you can distribute it and/or modify it
+# under the term of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your opinion), any later version.
+#
+# MoLe is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License terms for details.
+#
+# You should have received a copy of the GNU General Public License
+# along with MoLe. If not, see <https://www.gnu.org/licenses/>.
+#
 distributionBase=GRADLE_USER_HOME
 distributionPath=wrapper/dists
 zipStoreBase=GRADLE_USER_HOME
 zipStorePath=wrapper/dists
-distributionUrl=https\://services.gradle.org/distributions/gradle-6.7.1-bin.zip
+distributionUrl=https\://services.gradle.org/distributions/gradle-8.0-bin.zip
diff --git a/metadata/bg-BG/changelogs/48.txt b/metadata/bg-BG/changelogs/48.txt
new file mode 100644 (file)
index 0000000..b92f6e0
--- /dev/null
@@ -0,0 +1,4 @@
+* ИЗВЕСТНИ ПРОБЛЕМИ
+    + Несъвместимост с hledger-web 1.23+
+* ПОПРАВКИ
+    + поправено дописване на описанието при въвеждане на ново движение
diff --git a/metadata/bg-BG/changelogs/49.txt b/metadata/bg-BG/changelogs/49.txt
new file mode 100644 (file)
index 0000000..fbb1844
--- /dev/null
@@ -0,0 +1,4 @@
+* НОВО
+    + Добавена поддръжка на hledger-web версия 1.23
+* ПОПРАВКИ
+    + Включване на поддържащ файл за работата на БД, пропъснат във версия 0.20.4
diff --git a/metadata/bg-BG/changelogs/50.txt b/metadata/bg-BG/changelogs/50.txt
new file mode 100644 (file)
index 0000000..bdf0e80
--- /dev/null
@@ -0,0 +1,4 @@
+* ПОПРАВКИ
+    + поддръжане на hledger-web 1.23 и при добавяне на нови движения
+    + поправена натрупана сума при добавяне на движение в миналото
+    + поправен срив при изпращане на ново движение без данни за суми
diff --git a/metadata/bg-BG/changelogs/51.txt b/metadata/bg-BG/changelogs/51.txt
new file mode 100644 (file)
index 0000000..0af9229
--- /dev/null
@@ -0,0 +1,6 @@
+* ПОПРАВКИ
+    + отстранен срив при балансиране на трансакция с повече от една валута
+    + отстранен срив при дублиране на макет
+    + отстранен срив при зареждане на настройки от резервно копие
+* ПОДОБРЕНИЯ
+    + ново движение: включване на полазването на валути при зареждане на предишни движение с валути
diff --git a/metadata/bg-BG/changelogs/52.txt b/metadata/bg-BG/changelogs/52.txt
new file mode 100644 (file)
index 0000000..9c8c8db
--- /dev/null
@@ -0,0 +1,5 @@
+* ПОПРАВКИ
+    + коригирани версии на gradle
+* ДРУГИ
+    + обновени версии на множество библиотеки
+    + прицелване във версия 31 на платформата
diff --git a/metadata/bg-BG/changelogs/53.txt b/metadata/bg-BG/changelogs/53.txt
new file mode 100644 (file)
index 0000000..427dbb3
--- /dev/null
@@ -0,0 +1,4 @@
+* ПОПРАВКИ
+    + отстранен проблем със съвместимостта с hledger-web 1.23+ при изпращане на нови транзакции. Благодарности на Faye Duxovni за поправката!
+    + поправен срив при изтриване на шаблони
+    + поправен рядък срив при изпращане на транзакции, съдържащи повече от една сметка без сума и нулев остатъчен баланс
diff --git a/metadata/bg-BG/changelogs/54.txt b/metadata/bg-BG/changelogs/54.txt
new file mode 100644 (file)
index 0000000..2e1dace
--- /dev/null
@@ -0,0 +1,2 @@
+* ПОПРАВКИ
+    + отстранен проблем с централизираното резервно копие на настройките
diff --git a/metadata/bg-BG/changelogs/55.txt b/metadata/bg-BG/changelogs/55.txt
new file mode 100644 (file)
index 0000000..208db92
--- /dev/null
@@ -0,0 +1,2 @@
+* ПОПРАВКИ
+    + отстранен проблем при изпращане на транзакции към hledger-web 1.23+
diff --git a/metadata/bg-BG/changelogs/56.txt b/metadata/bg-BG/changelogs/56.txt
new file mode 100644 (file)
index 0000000..218ba4a
--- /dev/null
@@ -0,0 +1,4 @@
+* ПОПРАВКИ
+    + Позволяване на потребителски сертификати в настройките за сигурността на мрежовите връзки
+* ДРУГИ
+    + Обновена версия на gradle
diff --git a/metadata/en-US/changelogs/48.txt b/metadata/en-US/changelogs/48.txt
new file mode 100644 (file)
index 0000000..f0ce53d
--- /dev/null
@@ -0,0 +1,4 @@
+* KNOWN PROBLEMS
+    + Incompatibility with hledger-web 1.23+
+* FIXES
+    + fix auto-completion of transaction description
diff --git a/metadata/en-US/changelogs/49.txt b/metadata/en-US/changelogs/49.txt
new file mode 100644 (file)
index 0000000..422c2ce
--- /dev/null
@@ -0,0 +1,4 @@
+* NEW
+    + Add support for hledger-web 1.23
+* FIXES
+    + Ship database support file missed in v0.20.4
diff --git a/metadata/en-US/changelogs/50.txt b/metadata/en-US/changelogs/50.txt
new file mode 100644 (file)
index 0000000..797fcc1
--- /dev/null
@@ -0,0 +1,4 @@
+* FIXES
+    + add hledger-web 1.23 support when adding transactions too
+    + correct running total when a matching transaction is added in the past
+    + fix crash when sending transaction containing only empty amounts
diff --git a/metadata/en-US/changelogs/51.txt b/metadata/en-US/changelogs/51.txt
new file mode 100644 (file)
index 0000000..379834e
--- /dev/null
@@ -0,0 +1,6 @@
+* FIXES
+    + fix crash when auto-balancing multi currency transaction
+    + fix crash when duplicating template
+    + fix crash when restoring configuration backup
+* IMPROVEMENTS
+    + new transaction: turn on commodity setting when loading previous transaction with commodities
diff --git a/metadata/en-US/changelogs/52.txt b/metadata/en-US/changelogs/52.txt
new file mode 100644 (file)
index 0000000..13f6c20
--- /dev/null
@@ -0,0 +1,6 @@
+* FIXES
+    + sync gradle version requirements
+* OTHERS
+    + bump version of several dependent libraries
+    + bump SDK version to 31
+    + adjust deprecated constructor usage
diff --git a/metadata/en-US/changelogs/53.txt b/metadata/en-US/changelogs/53.txt
new file mode 100644 (file)
index 0000000..8e8601b
--- /dev/null
@@ -0,0 +1,4 @@
+* FIXES
+    + fix compatibility wuth hledger-web 1.23+ when submitting new transactions. Thanks to Faye Duxovni for the patch!
+    + fix a crash when deleting templates
+    + fix a rare crash when submitting transactions with multiple accounts with no amounts with zero remaining balance
diff --git a/metadata/en-US/changelogs/54.txt b/metadata/en-US/changelogs/54.txt
new file mode 100644 (file)
index 0000000..a2e3a20
--- /dev/null
@@ -0,0 +1,2 @@
+* FIXES
+    + fix cloud backup
diff --git a/metadata/en-US/changelogs/55.txt b/metadata/en-US/changelogs/55.txt
new file mode 100644 (file)
index 0000000..3697ab5
--- /dev/null
@@ -0,0 +1,2 @@
+* FIXES:
+    + fixed sending of transactions to hledger-web 1.23+
diff --git a/metadata/en-US/changelogs/56.txt b/metadata/en-US/changelogs/56.txt
new file mode 100644 (file)
index 0000000..9b7d3ac
--- /dev/null
@@ -0,0 +1,4 @@
+* FIXES:
+    + allow user certificates in network security config
+* OTHERS:
+    + bump gradle version