]> git.ktnx.net Git - mobile-ledger.git/commitdiff
bump androidx.constraintlayout library version master
authorDamyan Ivanov <dam+mobileledger@ktnx.net>
Sun, 9 Jun 2024 19:19:26 +0000 (22:19 +0300)
committerDamyan Ivanov <dam+mobileledger@ktnx.net>
Sun, 9 Jun 2024 19:19:26 +0000 (22:19 +0300)
77 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/App.java
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/ui/transaction_list/TransactionRowHolder.java
app/src/main/java/net/ktnx/mobileledger/utils/Colors.java
app/src/main/java/net/ktnx/mobileledger/utils/ObservableAtomicInteger.java [deleted file]
app/src/main/java/net/ktnx/mobileledger/utils/ObservableList.java [deleted file]
app/src/main/java/net/ktnx/mobileledger/utils/ObservableValue.java [deleted file]
app/src/main/res/drawable-anydpi-v26/app_icon.xml
app/src/main/res/drawable/launcher_foreground.xml
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
art/app-icon-adaptive.svg [new file with mode: 0644]
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..2d2659e1831a75876d58a0543075282a175473b8 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 33
     defaultConfig {
         applicationId "net.ktnx.mobileledger"
         minSdkVersion 22
-        targetSdkVersion 30
+        targetSdkVersion 33
         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.4'
     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:24.1.0'
+    implementation 'com.fasterxml.jackson.module:jackson-modules-java8:2.17.1'
     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.1'
 }
 
 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..1674610ed87c06e2d8e203c989c0452dabfa8b07 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"
+        android:enableOnBackInvokedCallback="true"
         tools:ignore="GoogleAppIndexingWarning">
         <activity
             android:name=".BackupsActivity"
@@ -42,7 +41,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 +50,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 a9a416e7bb3c15a8e49c51f1aedb45f1307d0bee..b0c8e1f7574cc9f8048774dbd67000df0b99de15 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
@@ -41,6 +41,7 @@ public class App extends Application {
     public static final String PREF_NAME = "MoLe";
     public static final String PREF_THEME_HUE = "theme-hue";
     public static final String PREF_PROFILE_ID = "profile-id";
+    public static final String PREF_SHOW_ZERO_BALANCE_ACCOUNTS = "show-zero-balance-accounts";
     public static App instance;
     private static ProfileDetailModel profileModel;
     private boolean monthNamesPrepared = false;
@@ -68,6 +69,16 @@ public class App extends Application {
         SharedPreferences prefs = instance.getSharedPreferences(PREF_NAME, MODE_PRIVATE);
         return prefs.getInt(PREF_THEME_HUE, Colors.DEFAULT_HUE_DEG);
     }
+    public static boolean getShowZeroBalanceAccounts() {
+        SharedPreferences prefs = instance.getSharedPreferences(PREF_NAME, MODE_PRIVATE);
+        return prefs.getBoolean(PREF_SHOW_ZERO_BALANCE_ACCOUNTS, true);
+    }
+    public static void storeShowZeroBalanceAccounts(boolean value) {
+        SharedPreferences prefs = instance.getSharedPreferences(PREF_NAME, MODE_PRIVATE);
+        SharedPreferences.Editor editor = prefs.edit();
+        editor.putBoolean(PREF_SHOW_ZERO_BALANCE_ACCOUNTS, value);
+        editor.apply();
+    }
     private String getAuthURL() {
         if (profileModel != null)
             return profileModel.getUrl();
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..02d685ce8138b54cd3fb5fa7e132713ba12fd30c 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,8 @@ import androidx.recyclerview.widget.LinearLayoutManager;
 import androidx.recyclerview.widget.RecyclerView;
 import androidx.swiperefreshlayout.widget.SwipeRefreshLayout;
 
+import net.ktnx.mobileledger.App;
+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 +58,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 +89,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);
@@ -100,8 +107,7 @@ public class AccountSummaryFragment extends MobileLedgerListFragment {
 
         mainActivity.fabShouldShow();
 
-        if (mainActivity instanceof FabManager.FabHandler)
-            FabManager.handle(mainActivity, b.accountRoot);
+        FabManager.handle(mainActivity, b.accountRoot);
 
         Colors.themeWatch.observe(getViewLifecycleOwner(), this::themeChanged);
         b.accountSwipeRefreshLayout.setOnRefreshListener(() -> {
@@ -109,15 +115,43 @@ public class AccountSummaryFragment extends MobileLedgerListFragment {
             model.scheduleTransactionListRetrieval();
         });
 
-        Data.observeProfile(this, this::onProfileChanged);
+        Data.observeProfile(this, profile -> onProfileChanged(profile, Boolean.TRUE.equals(
+                model.getShowZeroBalanceAccounts()
+                     .getValue())));
+        model.getShowZeroBalanceAccounts()
+             .setValue(App.getShowZeroBalanceAccounts());
+    }
+    @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);
+                 App.storeShowZeroBalanceAccounts(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 +168,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 876430f5de06e4c1b4b25a9061a5f9d74abfe94b..e516115074bd3f9f6a03bb6d63b4747600ce75af 100644 (file)
@@ -40,12 +40,9 @@ import net.ktnx.mobileledger.model.TransactionListItem;
 import net.ktnx.mobileledger.utils.Colors;
 import net.ktnx.mobileledger.utils.Misc;
 
-import java.util.Observer;
-
 class TransactionRowHolder extends TransactionRowHolderBase {
     private final TransactionListRowBinding b;
     TransactionListItem.Type lastType;
-    private Observer lastUpdateObserver;
     public TransactionRowHolder(@NonNull TransactionListRowBinding binding) {
         super(binding.getRoot());
         b = binding;
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/java/net/ktnx/mobileledger/utils/ObservableAtomicInteger.java b/app/src/main/java/net/ktnx/mobileledger/utils/ObservableAtomicInteger.java
deleted file mode 100644 (file)
index f804464..0000000
+++ /dev/null
@@ -1,145 +0,0 @@
-/*
- * 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.utils;
-
-import android.os.Build;
-
-import androidx.annotation.RequiresApi;
-
-import java.util.Observable;
-import java.util.concurrent.atomic.AtomicInteger;
-import java.util.function.IntBinaryOperator;
-import java.util.function.IntUnaryOperator;
-
-public class ObservableAtomicInteger extends Observable {
-    private final AtomicInteger holder;
-    ObservableAtomicInteger() {
-        super();
-        holder = new AtomicInteger();
-    }
-    public ObservableAtomicInteger(int initialValue) {
-        this();
-        holder.set(initialValue);
-    }
-    public int get() {
-        return holder.get();
-    }
-    public void set(int newValue) {
-//        debug("atomic", "set");
-        holder.set(newValue);
-        forceNotify();
-    }
-    private void forceNotify() {
-        setChanged();
-//        debug("atomic", String.format("notifying %d observers", countObservers()));
-        notifyObservers();
-    }
-//    public void lazySet(int newValue) {
-//        holder.lazySet(newValue);
-//        forceNotify();
-//    }
-    public int getAndSet(int newValue) {
-        int result = holder.getAndSet(newValue);
-        forceNotify();
-        return result;
-    }
-    public boolean compareAndSet(int expect, int update) {
-        boolean result = holder.compareAndSet(expect, update);
-        if (result) forceNotify();
-        return result;
-    }
-//    public boolean weakCompareAndSet(int expect, int update) {
-//        boolean result = holder.weakCompareAndSet(expect, update);
-//        if (result) forceNotify();
-//        return result;
-//    }
-    public int getAndIncrement() {
-        int result = holder.getAndIncrement();
-        forceNotify();
-        return result;
-    }
-    public int getAndDecrement() {
-        int result = holder.getAndDecrement();
-        forceNotify();
-        return result;
-    }
-    public int getAndAdd(int delta) {
-        int result = holder.getAndAdd(delta);
-        forceNotify();
-        return result;
-    }
-    public int incrementAndGet() {
-//        debug("atomic", "incrementAndGet");
-        int result = holder.incrementAndGet();
-        forceNotify();
-        return result;
-    }
-    public int decrementAndGet() {
-//        debug("atomic", "decrementAndGet");
-        int result = holder.decrementAndGet();
-        forceNotify();
-        return result;
-    }
-    public int addAndGet(int delta) {
-        int result = holder.addAndGet(delta);
-        forceNotify();
-        return result;
-    }
-    @RequiresApi(Build.VERSION_CODES.N)
-    public int getAndUpdate(IntUnaryOperator updateFunction) {
-        int result = holder.getAndUpdate(updateFunction);
-        forceNotify();
-        return result;
-    }
-    @RequiresApi(api = Build.VERSION_CODES.N)
-    public int updateAndGet(IntUnaryOperator updateFunction) {
-        int result = holder.updateAndGet(updateFunction);
-        forceNotify();
-        return result;
-    }
-    @RequiresApi(api = Build.VERSION_CODES.N)
-    public int getAndAccumulate(int x, IntBinaryOperator accumulatorFunction) {
-        int result = holder.getAndAccumulate(x, accumulatorFunction);
-        forceNotify();
-        return result;
-    }
-    @RequiresApi(api = Build.VERSION_CODES.N)
-    public int accumulateAndGet(int x, IntBinaryOperator accumulatorFunction) {
-        int result = holder.accumulateAndGet(x, accumulatorFunction);
-        forceNotify();
-        return result;
-    }
-    public int intValue() {
-        return holder.intValue();
-    }
-    public long longValue() {
-        return holder.longValue();
-    }
-    public float floatValue() {
-        return holder.floatValue();
-    }
-    public double doubleValue() {
-        return holder.doubleValue();
-    }
-    public byte byteValue() {
-        return holder.byteValue();
-    }
-    public short shortValue() {
-        return holder.shortValue();
-    }
-}
diff --git a/app/src/main/java/net/ktnx/mobileledger/utils/ObservableList.java b/app/src/main/java/net/ktnx/mobileledger/utils/ObservableList.java
deleted file mode 100644 (file)
index 4ca3e69..0000000
+++ /dev/null
@@ -1,325 +0,0 @@
-/*
- * 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.utils;
-
-import android.os.Build;
-
-import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
-import androidx.annotation.RequiresApi;
-
-import org.jetbrains.annotations.NotNull;
-
-import java.util.Collection;
-import java.util.Comparator;
-import java.util.Iterator;
-import java.util.List;
-import java.util.ListIterator;
-import java.util.Observable;
-import java.util.Spliterator;
-import java.util.concurrent.locks.ReentrantReadWriteLock;
-import java.util.function.Consumer;
-import java.util.function.Predicate;
-import java.util.function.UnaryOperator;
-import java.util.stream.Stream;
-
-import static net.ktnx.mobileledger.utils.Logger.debug;
-
-public class ObservableList<T> extends Observable implements List<T> {
-    private List<T> list;
-    private final ReentrantReadWriteLock lock = new ReentrantReadWriteLock();
-    private int notificationBlocks = 0;
-    private boolean notificationWasBlocked = false;
-    public ObservableList(List<T> list) {
-        this.list = list;
-    }
-    private void forceNotify() {
-        if (notificationBlocked()) return;
-        setChanged();
-        notifyObservers();
-    }
-    private boolean notificationBlocked() {
-        return notificationWasBlocked = (notificationBlocks > 0);
-    }
-    private void forceNotify(int index) {
-        if (notificationBlocked()) return;
-        setChanged();
-        notifyObservers(index);
-    }
-    public int size() {
-        try (LockHolder lh = lockForReading()) {
-            return list.size();
-        }
-    }
-    public boolean isEmpty() {
-        try (LockHolder lh = lockForReading()) {
-            return list.isEmpty();
-        }
-    }
-    public boolean contains(@Nullable Object o) {
-        try (LockHolder lh = lockForReading()) {
-            return list.contains(o);
-        }
-    }
-    @NonNull
-    public Iterator<T> iterator() {
-        throw new RuntimeException("Iterators break encapsulation and ignore locking");
-//        return list.iterator();
-    }
-    @NotNull
-    public Object[] toArray() {
-        try (LockHolder lh = lockForReading()) {
-            return list.toArray();
-        }
-    }
-    @Override
-    @NotNull
-    public <T1> T1[] toArray(@Nullable T1[] a) {
-        try (LockHolder lh = lockForReading()) {
-            return list.toArray(a);
-        }
-    }
-    public boolean add(T t) {
-        try (LockHolder lh = lockForWriting()) {
-            boolean result = list.add(t);
-            lh.downgrade();
-            if (result)
-                forceNotify();
-            return result;
-        }
-    }
-    public boolean remove(@Nullable Object o) {
-        try (LockHolder lh = lockForWriting()) {
-            boolean result = list.remove(o);
-            lh.downgrade();
-            if (result) forceNotify();
-            return result;
-        }
-    }
-    public T removeQuietly(int index) {
-        return list.remove(index);
-    }
-    public boolean containsAll(@NonNull Collection<?> c) {
-        try (LockHolder lh = lockForReading()) {
-            return list.containsAll(c);
-        }
-    }
-    public boolean addAll(@NonNull Collection<? extends T> c) {
-        try (LockHolder lh = lockForWriting()) {
-            boolean result = list.addAll(c);
-            lh.downgrade();
-            if (result) forceNotify();
-            return result;
-        }
-    }
-    public boolean addAll(int index, @NonNull Collection<? extends T> c) {
-        try (LockHolder lh = lockForWriting()) {
-            boolean result = list.addAll(index, c);
-            lh.downgrade();
-            if (result) forceNotify();
-            return result;
-        }
-    }
-    public boolean addAllQuietly(int index, Collection<? extends T> c) {
-        return list.addAll(index, c);
-    }
-    public boolean removeAll(@NonNull Collection<?> c) {
-        try (LockHolder lh = lockForWriting()) {
-            boolean result = list.removeAll(c);
-            lh.downgrade();
-            if (result) forceNotify();
-            return result;
-        }
-    }
-    public boolean retainAll(@NonNull Collection<?> c) {
-        try (LockHolder lh = lockForWriting()) {
-            boolean result = list.retainAll(c);
-            lh.downgrade();
-            if (result) forceNotify();
-            return result;
-        }
-    }
-    @RequiresApi(api = Build.VERSION_CODES.N)
-    public void replaceAll(@NonNull UnaryOperator<T> operator) {
-        try (LockHolder lh = lockForWriting()) {
-            list.replaceAll(operator);
-            lh.downgrade();
-            forceNotify();
-        }
-    }
-    @RequiresApi(api = Build.VERSION_CODES.N)
-    public void sort(@Nullable Comparator<? super T> c) {
-        try (LockHolder lh = lockForWriting()) {
-            lock.writeLock().lock();
-            list.sort(c);
-            lh.downgrade();
-            forceNotify();
-        }
-    }
-    public void clear() {
-        try (LockHolder lh = lockForWriting()) {
-            boolean wasEmpty = list.isEmpty();
-            list.clear();
-            lh.downgrade();
-            if (!wasEmpty) forceNotify();
-        }
-    }
-    public T get(int index) {
-        try (LockHolder lh = lockForReading()) {
-            return list.get(index);
-        }
-    }
-    public T set(int index, T element) {
-        try (LockHolder lh = lockForWriting()) {
-            T result = list.set(index, element);
-            lh.downgrade();
-            forceNotify();
-            return result;
-        }
-    }
-    public void add(int index, T element) {
-        try (LockHolder lh = lockForWriting()) {
-            list.add(index, element);
-            lh.downgrade();
-            forceNotify();
-        }
-    }
-    public T remove(int index) {
-        try (LockHolder lh = lockForWriting()) {
-            T result = list.remove(index);
-            lh.downgrade();
-            forceNotify();
-            return result;
-        }
-    }
-    public int indexOf(@Nullable Object o) {
-        try (LockHolder lh = lockForReading()) {
-            return list.indexOf(o);
-        }
-    }
-    public int lastIndexOf(@Nullable Object o) {
-        try (LockHolder lh = lockForReading()) {
-            return list.lastIndexOf(o);
-        }
-    }
-    @NotNull
-    public ListIterator<T> listIterator() {
-        if (!lock.isWriteLockedByCurrentThread()) throw new RuntimeException(
-                "Iterators break encapsulation and ignore locking. Write-lock first");
-        return list.listIterator();
-    }
-    @NotNull
-    public ListIterator<T> listIterator(int index) {
-        if (!lock.isWriteLockedByCurrentThread()) throw new RuntimeException(
-                "Iterators break encapsulation and ignore locking. Write-lock first");
-        return list.listIterator(index);
-    }
-    @NotNull
-    public List<T> subList(int fromIndex, int toIndex) {
-        try (LockHolder lh = lockForReading()) {
-            return list.subList(fromIndex, toIndex);
-        }
-    }
-    @NotNull
-    @RequiresApi(api = Build.VERSION_CODES.N)
-    public Spliterator<T> spliterator() {
-        if (!lock.isWriteLockedByCurrentThread())
-            throw new RuntimeException(
-                    "Iterators break encapsulation and ignore locking. Write-lock first");
-        return list.spliterator();
-    }
-    @RequiresApi(api = Build.VERSION_CODES.N)
-    public boolean removeIf(@NotNull Predicate<? super T> filter) {
-        try (LockHolder lh = lockForWriting()) {
-            boolean result = list.removeIf(filter);
-            lh.downgrade();
-            if (result)
-                forceNotify();
-            return result;
-        }
-    }
-    @NotNull
-    @RequiresApi(api = Build.VERSION_CODES.N)
-    public Stream<T> stream() {
-        if (!lock.isWriteLockedByCurrentThread()) throw new RuntimeException(
-                "Iterators break encapsulation and ignore locking. Write-lock first");
-        return list.stream();
-    }
-    @NotNull
-    @RequiresApi(api = Build.VERSION_CODES.N)
-    public Stream<T> parallelStream() {
-        if (!lock.isWriteLockedByCurrentThread())
-            throw new RuntimeException(
-                    "Iterators break encapsulation and ignore locking. Write-lock first");
-        return list.parallelStream();
-    }
-    @RequiresApi(api = Build.VERSION_CODES.N)
-    public void forEach(@NotNull Consumer<? super T> action) {
-        try (LockHolder lh = lockForReading()) {
-            list.forEach(action);
-        }
-    }
-    public List<T> getList() {
-        if (!lock.isWriteLockedByCurrentThread())
-            throw new RuntimeException(
-                    "Direct list access breaks encapsulation and ignore locking. Write-lock first");
-        return list;
-    }
-    public void setList(List<T> aList) {
-        try (LockHolder lh = lockForWriting()) {
-            list = aList;
-            lh.downgrade();
-            forceNotify();
-        }
-    }
-    public void triggerItemChangedNotification(T item) {
-        try (LockHolder lh = lockForReading()) {
-            int index = list.indexOf(item);
-            if (index == -1) {
-                debug("ObList", "??? not sending notifications for item not found in the list");
-                return;
-            }
-            debug("ObList", "Notifying item change observers");
-            triggerItemChangedNotification(index);
-        }
-    }
-    public void triggerItemChangedNotification(int index) {
-        forceNotify(index);
-    }
-    public LockHolder lockForWriting() {
-        ReentrantReadWriteLock.WriteLock wLock = lock.writeLock();
-        wLock.lock();
-
-        ReentrantReadWriteLock.ReadLock rLock = lock.readLock();
-        rLock.lock();
-
-        return new LockHolder(rLock, wLock);
-    }
-    public LockHolder lockForReading() {
-        ReentrantReadWriteLock.ReadLock rLock = lock.readLock();
-        rLock.lock();
-        return new LockHolder(rLock);
-    }
-    public void blockNotifications() {
-        notificationBlocks++;
-    }
-    public void unblockNotifications() {
-        notificationBlocks--;
-        if ((notificationBlocks == 0) && notificationWasBlocked) notifyObservers();
-    }
-}
\ No newline at end of file
diff --git a/app/src/main/java/net/ktnx/mobileledger/utils/ObservableValue.java b/app/src/main/java/net/ktnx/mobileledger/utils/ObservableValue.java
deleted file mode 100644 (file)
index 326be07..0000000
+++ /dev/null
@@ -1,83 +0,0 @@
-/*
- * 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.utils;
-
-import java.util.Observable;
-import java.util.Observer;
-
-public class ObservableValue<T> {
-    private final ObservableValueImpl<T> impl = new ObservableValueImpl<>();
-    public ObservableValue() {}
-    public ObservableValue(T initialValue) {
-        impl.setValue(initialValue, false);
-    }
-    public void set(T newValue) {
-        impl.setValue(newValue);
-    }
-    public T get() {
-        return impl.getValue();
-    }
-    public void addObserver(Observer o) {
-        impl.addObserver(o);
-    }
-    public void deleteObserver(Observer o) {
-        impl.deleteObserver(o);
-    }
-    public void notifyObservers() {
-        impl.notifyObservers();
-    }
-    public void notifyObservers(T arg) {
-        impl.notifyObservers(arg);
-    }
-    public void deleteObservers() {
-        impl.deleteObservers();
-    }
-    public boolean hasChanged() {
-        return impl.hasChanged();
-    }
-    public int countObservers() {
-        return impl.countObservers();
-    }
-    public void forceNotifyObservers() {
-        impl.setChanged();
-        impl.notifyObservers();
-    }
-    private static class ObservableValueImpl<T> extends Observable {
-        protected T value;
-        public void setValue(T newValue) {
-            setValue(newValue, true);
-        }
-        protected void setChanged() {
-            super.setChanged();
-        }
-        private synchronized void setValue(T newValue, boolean notify) {
-            if ((newValue == null) && (value == null))
-                return;
-
-            if ((newValue != null) && newValue.equals(value)) return;
-
-            T oldValue = value;
-            value = newValue;
-            setChanged();
-            if (notify) notifyObservers(oldValue);
-        }
-        public T getValue() {
-            return value;
-        }
-    }
-}
\ No newline at end of file
index b82ac92d9abc5e9fedea840ca7a250a9194da7fc..cd0b7c71f24a78c1b793383dda73d64cf5769db5 100644 (file)
@@ -19,4 +19,5 @@
 <adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
     <background android:drawable="@color/ic_launcher_background"/>
     <foreground android:drawable="@drawable/launcher_foreground" />
+    <monochrome android:drawable="@drawable/launcher_foreground" />
 </adaptive-icon>
\ No newline at end of file
index af159e2bf0effd213f56ab3e9c22cd3cca0a18aa..b78a67edd0cfc056ca974caeb4df8f9a9115a330 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
     android:viewportHeight="108"
     >
     <group
-        android:scaleX="0.73"
-        android:scaleY="0.73"
-        android:translateX="14.58"
-        android:translateY="14.58"
+        android:scaleX="0.8"
+        android:scaleY="0.8"
+        android:translateX="10.8"
+        android:translateY="10.8"
         >
         <path
-            android:pathData="m42.06,32.596c-1.331,1.331 -1.33,3.49 0.002,4.82 1.331,1.33 3.488,1.329 4.818,-0.002 3.962,-3.962 10.28,-3.962 14.242,0 1.33,1.331 3.487,1.332 4.818,0.002 1.332,-1.33 1.333,-3.488 0.002,-4.82 -7.523,-6.703 -17.464,-6.433 -23.881,0z"
+            android:fillColor="#00000000"
+            android:pathData="m46.42,36.41a10.72,10.72 89.98,0 1,15.16 0"
+            android:strokeWidth="4"
+            android:strokeColor="#ffffff"
+            android:strokeLineCap="round"
             android:strokeLineJoin="round"
-            android:strokeWidth="7.1924"
-            android:fillColor="#ffffff"
-            android:strokeColor="#00000000"
-            android:fillType="nonZero"
-            android:strokeLineCap="round"/>
+            />
         <path
-            android:pathData="m32.531,23.62c-1.33,1.33 -1.33,3.487 0,4.818 1.33,1.33 3.487,1.33 4.818,0 9.225,-9.227 24.078,-9.227 33.304,0 1.33,1.33 3.487,1.33 4.818,0 1.33,-1.33 1.33,-3.487 0,-4.818 -12.952,-12.351 -31.975,-11.302 -42.939,0z"
+            android:fillColor="#00000000"
+            android:pathData="m38.84,29.27a21.44,21.44 89.98,0 1,30.32 0"
+            android:strokeWidth="4"
+            android:strokeColor="#ffffff"
+            android:strokeLineCap="round"
             android:strokeLineJoin="round"
-            android:strokeWidth="7.1924"
-            android:fillColor="#ffffff"
-            android:strokeColor="#00000000"
-            android:fillType="nonZero"
-            android:strokeLineCap="round"/>
+            />
         <path
-            android:pathData="m17.52,41.916c-3.229,0.666 -3.355,3.178 -3.355,3.827L14.165,89.41c0,2.088 1.746,3.837 3.835,3.837 13.048,-2.199 19.956,-2.121 36,0.007C68.79,91.212 79.587,90.998 90,93.247c2.089,0 3.835,-1.749 3.835,-3.837L93.835,45.743c0,-2.094 -1.814,-3.538 -3.636,-3.827 -9.491,-1.502 -13.338,-2.216 -27.678,-0.945 0.484,1.475 0.72,2.785 0.442,4.184 10.721,-1.146 19.131,-0.668 26.469,0.915l-0.045,42.916C77.902,86.92 70.894,86.96 56.213,88.799L56.213,52.774c-1.422,0.348 -3.104,0.332 -4.426,0L51.787,88.799C40.094,86.929 28.873,86.93 18.591,88.987L18.591,46.07c11.035,-1.397 15.892,-2.046 26.444,-0.915 -0.18,-1.441 0.022,-2.978 0.451,-4.184 -9.086,-1.124 -17.014,-0.646 -27.966,0.945z"
+            android:fillColor="#00000000"
+            android:pathData="m61.2,42.96c0,0 6.34,-0.66 9.51,-0.61 4.38,0.07 13.08,1.25 13.08,1.25 0.79,0.07 1.44,0.62 1.44,1.38l0,34.67c0,0.77 -0.64,1.38 -1.44,1.38 0,0 -8.7,-1.24 -13.08,-1.29 -5.59,-0.06 -16.71,1.31 -16.71,1.31 0,0 -10.73,-1.34 -16.13,-1.31 -4.58,0.03 -13.67,1.29 -13.67,1.29 -0.8,0 -1.44,-0.62 -1.44,-1.38L22.77,44.97c0,-0.77 0.64,-1.38 1.44,-1.38 0,0 9.09,-1.2 13.67,-1.25 2.99,-0.03 8.96,0.62 8.96,0.62"
+            android:strokeWidth="3.54"
+            android:strokeColor="#ffffff"
+            android:strokeLineCap="butt"
             android:strokeLineJoin="round"
-            android:strokeWidth="4.42609"
-            android:fillColor="#ffffff"
-            android:strokeColor="#00000000"
-            android:fillType="nonZero"
-            android:strokeLineCap="round"/>
+            />
         <path
-            android:pathData="M32.773,55.429L32.773,65.965L22.238,65.965v5.137h10.534v10.534h5.137L37.91,71.102L48.446,71.102L48.446,65.965L37.91,65.965L37.91,55.429Z"
+            android:fillColor="#00000000"
+            android:fillType="evenOdd"
+            android:pathData="M54,80.11L54,50.74"
+            android:strokeWidth="3.47"
+            android:strokeColor="#ffffff"
+            android:strokeLineCap="butt"
             android:strokeLineJoin="miter"
-            android:strokeWidth="5.42196"
-            android:fillColor="#ffffff"
-            android:strokeColor="#00000000"
+            />
+        <path
+            android:fillColor="#00000000"
             android:fillType="evenOdd"
-            android:strokeLineCap="butt"/>
+            android:pathData="M27.95,63.29L48.79,63.29"
+            android:strokeWidth="3.47"
+            android:strokeColor="#ffffff"
+            android:strokeLineCap="butt"
+            android:strokeLineJoin="miter"
+            />
         <path
-            android:pathData="M59.352,65.965L59.352,71.102L85.559,71.102v-5.137z"
+            android:fillColor="#00000000"
+            android:fillType="evenOdd"
+            android:pathData="M38.37,73.72L38.37,52.87"
+            android:strokeWidth="3.47"
+            android:strokeColor="#ffffff"
+            android:strokeLineCap="butt"
             android:strokeLineJoin="miter"
-            android:strokeWidth="5.42196"
-            android:fillColor="#ffffff"
-            android:strokeColor="#00000000"
+            />
+        <path
+            android:fillColor="#00000000"
             android:fillType="evenOdd"
-            android:strokeLineCap="butt"/>
+            android:pathData="M59.21,63.29L80.05,63.29"
+            android:strokeWidth="3.47"
+            android:strokeColor="#ffffff"
+            android:strokeLineCap="butt"
+            android:strokeLineJoin="miter"
+            />
         <path
-            android:pathData="m54,40.578c1.764,0 3.407,1.645 3.407,3.407 0,1.762 -1.643,3.403 -3.407,3.403 -1.764,0 -3.407,-1.641 -3.407,-3.403 0,-1.762 1.643,-3.407 3.407,-3.407z"
-            android:strokeLineJoin="round"
-            android:strokeWidth="11.5078"
             android:fillColor="#ffffff"
-            android:strokeColor="#00000000"
             android:fillType="nonZero"
-            android:strokeLineCap="round"/>
+            android:pathData="m54,40.84c1.4,0 2.71,1.31 2.71,2.71 0,1.4 -1.31,2.71 -2.71,2.71 -1.4,0 -2.71,-1.31 -2.71,-2.71 0,-1.4 1.31,-2.71 2.71,-2.71z"
+            android:strokeWidth="8.68"
+            android:strokeColor="#00000000"
+            android:strokeLineCap="round"
+            android:strokeLineJoin="round"
+            />
     </group>
 </vector>
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
diff --git a/art/app-icon-adaptive.svg b/art/app-icon-adaptive.svg
new file mode 100644 (file)
index 0000000..b0f4b05
--- /dev/null
@@ -0,0 +1,191 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<svg
+   sodipodi:docname="app-icon-adaptive.svg"
+   inkscape:version="1.2.2 (b0a8486541, 2022-12-01)"
+   id="svg4620"
+   version="1.1"
+   viewBox="0 0 108 108"
+   height="108"
+   width="108"
+   xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+   xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+   xmlns="http://www.w3.org/2000/svg"
+   xmlns:svg="http://www.w3.org/2000/svg"
+   xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+   xmlns:cc="http://creativecommons.org/ns#"
+   xmlns:dc="http://purl.org/dc/elements/1.1/">
+  <title
+     id="title921">MoLe app icon</title>
+  <defs
+     id="defs4614" />
+  <sodipodi:namedview
+     units="px"
+     inkscape:document-rotation="0"
+     inkscape:snap-to-guides="true"
+     inkscape:bbox-paths="true"
+     inkscape:bbox-nodes="true"
+     inkscape:snap-bbox="true"
+     inkscape:pagecheckerboard="true"
+     inkscape:window-maximized="0"
+     inkscape:window-y="10"
+     inkscape:window-x="12"
+     inkscape:window-height="1014"
+     inkscape:window-width="1890"
+     showborder="true"
+     inkscape:snap-object-midpoints="true"
+     inkscape:guide-bbox="true"
+     showguides="true"
+     inkscape:snap-page="true"
+     showgrid="false"
+     inkscape:current-layer="layer1"
+     inkscape:document-units="px"
+     inkscape:cy="46.080652"
+     inkscape:cx="47.195507"
+     inkscape:zoom="5.3818683"
+     inkscape:pageshadow="2"
+     inkscape:pageopacity="0.0"
+     borderopacity="1.0"
+     bordercolor="#666666"
+     pagecolor="#ffffff"
+     id="base"
+     inkscape:showpageshadow="0"
+     inkscape:deskcolor="#d1d1d1">
+    <sodipodi:guide
+       inkscape:color="rgb(0,0,255)"
+       inkscape:label=""
+       inkscape:locked="false"
+       id="guide1407"
+       orientation="-1,0"
+       position="21,-37.820936" />
+    <sodipodi:guide
+       inkscape:color="rgb(0,0,255)"
+       inkscape:locked="false"
+       inkscape:label=""
+       id="guide31"
+       orientation="0,1"
+       position="-10.658475,87" />
+    <sodipodi:guide
+       position="-11.893959,21"
+       orientation="0,1"
+       id="guide8469"
+       inkscape:locked="false"
+       inkscape:label=""
+       inkscape:color="rgb(0,134,229)" />
+    <sodipodi:guide
+       position="87,-37.820936"
+       orientation="-1,0"
+       id="guide11759"
+       inkscape:label=""
+       inkscape:locked="false"
+       inkscape:color="rgb(0,0,255)" />
+  </sodipodi:namedview>
+  <metadata
+     id="metadata4617">
+    <rdf:RDF>
+      <cc:Work
+         rdf:about="">
+        <dc:format>image/svg+xml</dc:format>
+        <dc:type
+           rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+        <dc:title>MoLe app icon</dc:title>
+        <cc:license
+           rdf:resource="https://spdx.org/licenses/GPL-3.0-or-later.html" />
+        <dc:creator>
+          <cc:Agent>
+            <dc:title>Damyan Ivanov &lt;dam+mole@ktnx.net&gt;</dc:title>
+          </cc:Agent>
+        </dc:creator>
+        <dc:rights>
+          <cc:Agent>
+            <dc:title>Copyright © 2019 Damyan Ivanov &lt;dam+mole@ktnx.net&gt;. All rights reserved.</dc:title>
+          </cc:Agent>
+        </dc:rights>
+      </cc:Work>
+    </rdf:RDF>
+  </metadata>
+  <g
+     inkscape:label="фон"
+     id="layer3"
+     inkscape:groupmode="layer">
+    <rect
+       ry="8.6399994"
+       y="0"
+       x="0"
+       height="108"
+       width="108"
+       id="rect5267"
+       style="opacity:1;fill:#935ff2;fill-opacity:1;stroke:none;stroke-width:7.02;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0.0885827;stroke-opacity:1;paint-order:markers stroke fill" />
+  </g>
+  <g
+     style="opacity:1"
+     transform="translate(0,-197)"
+     id="layer1"
+     inkscape:groupmode="layer"
+     inkscape:label="Layer 1">
+    <g
+       transform="matrix(1.7368422,0,0,1.7368422,12.315788,-135.70585)"
+       id="g892">
+      <path
+         style="opacity:1;fill:none;fill-opacity:1;stroke:#ffffff;stroke-width:2.30303;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0.0885827;stroke-opacity:1"
+         id="path5171"
+         sodipodi:type="arc"
+         sodipodi:cx="24"
+         sodipodi:cy="216.88385"
+         sodipodi:rx="6.1715264"
+         sodipodi:ry="6.1723976"
+         sodipodi:start="3.9269908"
+         sodipodi:end="5.4977871"
+         d="m 19.636072,212.51931 a 6.1715264,6.1723976 0 0 1 8.727856,0"
+         sodipodi:arc-type="arc"
+         sodipodi:open="true" />
+      <path
+         sodipodi:open="true"
+         d="m 15.272143,208.40876 a 12.343053,12.344795 0 0 1 17.455713,0"
+         sodipodi:end="5.4977871"
+         sodipodi:start="3.9269908"
+         sodipodi:ry="12.344795"
+         sodipodi:rx="12.343053"
+         sodipodi:cy="217.13785"
+         sodipodi:cx="24"
+         sodipodi:type="arc"
+         id="path5179"
+         style="opacity:1;fill:none;fill-opacity:1;stroke:#ffffff;stroke-width:2.30303;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0.0885827;stroke-opacity:1"
+         sodipodi:arc-type="arc" />
+      <path
+         style="opacity:1;fill:none;fill-opacity:1;stroke:#ffffff;stroke-width:2.03818;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0.0885827;stroke-opacity:1"
+         d="m 28.145643,216.2905 c 0,0 3.647627,-0.38031 5.476763,-0.35202 2.521183,0.039 7.530262,0.71841 7.530262,0.71841 0.456729,0.0418 0.82814,0.35465 0.82814,0.79521 v 19.96076 c 0,0.44055 -0.369351,0.79522 -0.82814,0.79522 0,0 -5.007349,-0.71629 -7.530262,-0.74156 -3.216849,-0.0323 -9.619664,0.75293 -9.619664,0.75293 0,0 -6.180328,-0.7732 -9.286648,-0.75293 -2.635263,0.0172 -7.8687611,0.74156 -7.8687611,0.74156 -0.458791,0 -0.828142,-0.35467 -0.828142,-0.79522 V 217.4521 c 0,-0.44056 0.369351,-0.79521 0.828142,-0.79521 0,0 5.2350911,-0.68943 7.8687611,-0.71841 1.722646,-0.019 5.155938,0.35654 5.155938,0.35654"
+         id="rect5165"
+         inkscape:connector-curvature="0"
+         sodipodi:nodetypes="cassssacassssac" />
+      <path
+         style="fill:none;fill-rule:evenodd;stroke:#ffffff;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+         d="M 24,237.67988 V 220.77219"
+         id="path5167"
+         inkscape:connector-curvature="0"
+         sodipodi:nodetypes="cc" />
+      <path
+         style="fill:none;fill-rule:evenodd;stroke:#ffffff;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+         d="M 9,228 H 21"
+         id="path5216"
+         inkscape:connector-curvature="0"
+         sodipodi:nodetypes="cc" />
+      <path
+         sodipodi:nodetypes="cc"
+         inkscape:connector-curvature="0"
+         id="path5218"
+         d="M 15,234 V 222"
+         style="fill:none;fill-rule:evenodd;stroke:#ffffff;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
+      <path
+         sodipodi:nodetypes="cc"
+         inkscape:connector-curvature="0"
+         id="path5220"
+         d="M 27,228 H 39"
+         style="fill:none;fill-rule:evenodd;stroke:#ffffff;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
+      <path
+         style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:sans-serif;font-variant-ligatures:normal;font-variant-position:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-alternates:normal;font-feature-settings:normal;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:normal;word-spacing:normal;text-transform:none;writing-mode:lr-tb;direction:ltr;text-orientation:mixed;dominant-baseline:auto;baseline-shift:baseline;text-anchor:start;white-space:normal;shape-padding:0;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;vector-effect:none;fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:5;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0.0885827;stroke-opacity:1;paint-order:markers stroke fill;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate"
+         d="m 24,215.0707 c 0.807623,0 1.559999,0.75332 1.56,1.56 2e-6,0.80669 -0.752376,1.55829 -1.56,1.55829 -0.807624,0 -1.560002,-0.7516 -1.56,-1.55829 10e-7,-0.80668 0.752377,-1.56 1.56,-1.56 z"
+         id="path5169"
+         inkscape:connector-curvature="0" />
+    </g>
+  </g>
+</svg>
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