# 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
--- /dev/null
+* 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
/*
- * Copyright © 2021 Damyan Ivanov.
+ * Copyright © 2023 Damyan Ivanov.
* This file is part of MoLe.
* MoLe is free software: you can distribute it and/or modify it
* under the term of the GNU General Public License as published by
apply plugin: 'com.android.application'
android {
- compileSdkVersion 30
+ compileSdkVersion 31
defaultConfig {
applicationId "net.ktnx.mobileledger"
minSdkVersion 22
- targetSdkVersion 30
+ targetSdkVersion 31
vectorDrawables.useSupportLibrary true
- versionCode 47
- versionName '0.20.3'
+ versionCode 56
+ versionName '0.21.7'
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
javaCompileOptions {
annotationProcessorOptions {
}
buildFeatures.viewBinding = true
buildToolsVersion '30.0.3'
+ namespace 'net.ktnx.mobileledger'
}
dependencies {
- implementation 'androidx.lifecycle:lifecycle-livedata-ktx:2.3.1'
- implementation 'androidx.lifecycle:lifecycle-viewmodel-ktx:2.3.1'
- def room_version = '2.3.0'
- implementation "androidx.room:room-runtime:$room_version"
+ implementation 'androidx.lifecycle:lifecycle-livedata-ktx:2.4.1'
+ implementation 'androidx.lifecycle:lifecycle-viewmodel-ktx:2.4.1'
+ def room_version = '2.4.2'
+ implementation "androidx.room:room-runtime:2.4.2"
annotationProcessor "androidx.room:room-compiler:$room_version"
- def nav_version = '2.3.5'
+ def nav_version = '2.4.2'
implementation fileTree(include: ['*.jar'], dir: 'libs')
implementation 'androidx.legacy:legacy-support-v4:1.0.0'
- implementation 'com.google.android.material:material:1.3.0'
- implementation 'androidx.constraintlayout:constraintlayout:2.0.4'
+ implementation 'com.google.android.material:material:1.5.0'
+ implementation 'androidx.constraintlayout:constraintlayout:2.1.3'
implementation 'androidx.lifecycle:lifecycle-extensions:2.2.0'
- implementation 'androidx.recyclerview:recyclerview:1.2.0'
+ implementation 'androidx.recyclerview:recyclerview:1.2.1'
testImplementation 'junit:junit:4.13.2'
- androidTestImplementation 'androidx.test:runner:1.4.0-alpha06'
- androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0-alpha06'
- implementation 'org.jetbrains:annotations:20.1.0'
- implementation 'com.fasterxml.jackson.module:jackson-modules-java8:2.12.3'
+ androidTestImplementation 'androidx.test:runner:1.5.0-alpha02'
+ androidTestImplementation 'androidx.test.espresso:espresso-core:3.5.0-alpha05'
+ implementation 'org.jetbrains:annotations:23.0.0'
+ implementation 'com.fasterxml.jackson.module:jackson-modules-java8:2.13.2'
implementation "androidx.navigation:navigation-fragment:$nav_version"
implementation "androidx.navigation:navigation-ui:$nav_version"
- implementation 'androidx.appcompat:appcompat:1.3.0-rc01'
+ implementation 'androidx.appcompat:appcompat:1.6.0-alpha01'
}
allprojects {
--- /dev/null
+{
+ "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
<?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
~ along with MoLe. If not, see <https://www.gnu.org/licenses/>.
-->
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
- xmlns:tools="http://schemas.android.com/tools"
- package="net.ktnx.mobileledger">
+ xmlns:tools="http://schemas.android.com/tools">
<uses-permission android:name="android.permission.INTERNET" />
android:name=".App"
android:allowBackup="true"
android:appCategory="productivity"
- android:fullBackupContent="@xml/backup_descriptor"
android:icon="@drawable/app_icon"
android:label="@string/app_name"
android:networkSecurityConfig="@xml/network_security_config"
android:roundIcon="@drawable/app_icon_round"
android:supportsRtl="true"
- android:backupAgent="net.ktnx.mobileledger.backup.MobileLedgerBackupAgent"
+ android:backupAgent=".backup.MobileLedgerBackupAgent"
tools:ignore="GoogleAppIndexingWarning">
<activity
android:name=".BackupsActivity"
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" />
</activity>
<activity
android:name=".ui.activity.MainActivity"
- android:label="@string/app_name"
android:theme="@style/AppTheme.default" />
<activity
android:name=".ui.new_transaction.NewTransactionActivity"
/*
- * 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
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);
list.add(acc);
}
throwIfCancelled();
+
+ Logger.warn("accounts",
+ String.format(Locale.US, "Got %d accounts using protocol %s", list.size(),
+ version.getDescription()));
}
return list;
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);
}
}
}
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()
/*
- * 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
package net.ktnx.mobileledger.async;
+import static net.ktnx.mobileledger.utils.Logger.debug;
+
import android.util.Log;
import net.ktnx.mobileledger.db.Profile;
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,
case v1_14:
case v1_15:
case v1_19_1:
+ case v1_23:
sendOK(profileApiVersion);
break;
default:
error = e.getMessage();
}
- Misc.onMainThread(()->{
- taskCallback.onTransactionSaveDone(error, transaction);
- });
+ Misc.onMainThread(() -> taskCallback.onTransactionSaveDone(error, transaction));
}
private void legacySendOkWithRetry() throws IOException {
int tried = 0;
/*
- * 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
}
@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 {
/*
- * 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
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;
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();
}
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;
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;
restoreCommodities();
restoreProfiles();
restoreTemplates();
+ restoreCurrentProfile();
}
private void restoreTemplates() {
if (templates == null)
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");
+ }
+ }
}
/*
- * 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
@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);
/*
- * 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
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);
/*
- * 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
public TemplateHeader createDuplicate() {
TemplateHeader dup = new TemplateHeader(this);
dup.id = 0;
+ dup.uuid = UUID.randomUUID()
+ .toString();
return dup;
}
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()) {
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);
}
return "1.15";
case v1_19_1:
return "1.19.1";
+ case v1_23:
+ return "1.23";
default:
throw new IllegalStateException("Unexpected value: " + this);
}
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());
}
/*
- * 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
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)
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());
}
--- /dev/null
+/*
+ * 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;
+ }
+}
--- /dev/null
+/*
+ * 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);
+ }
+}
--- /dev/null
+/*
+ * 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;
+ }
+
+}
--- /dev/null
+/*
+ * 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;
+ }
+}
--- /dev/null
+/*
+ * 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;
+ }
+}
--- /dev/null
+/*
+ * 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;
+ }
+}
--- /dev/null
+/*
+ * 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());
+ }
+
+}
--- /dev/null
+/*
+ * 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;
+ }
+ }
+}
--- /dev/null
+/*
+ * 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 {}
--- /dev/null
+/*
+ * 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;
+ }
+}
--- /dev/null
+/*
+ * 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;
+ }
+}
--- /dev/null
+/*
+ * 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;
+ }
+}
/*
- * 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
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}
((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 {
/*
- * 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
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;
}
/*
- * 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
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<>());
private SimpleDate firstTransactionDate;
private SimpleDate lastTransactionDate;
transient private RetrieveTransactionsTask retrieveTransactionsTask;
- transient private Thread displayedAccountsUpdater;
private TransactionsDisplayedFilter displayedTransactionsUpdater;
public LiveData<Boolean> getUpdatingFlag() {
return updatingFlag;
public void setFirstTransactionDate(SimpleDate earliestDate) {
this.firstTransactionDate = earliestDate;
}
+ public MutableLiveData<Boolean> getShowZeroBalanceAccounts() {return showZeroBalanceAccounts;}
public MutableLiveData<String> getAccountFilter() {
return accountFilter;
}
return;
}
Profile profile = Data.getProfile();
+ assert profile != null;
retrieveTransactionsTask = new RetrieveTransactionsTask(profile);
Logger.debug("db", "Created a background transaction retrieval task");
/*
- * 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.res.Resources;
import android.view.LayoutInflater;
import android.view.View;
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;
@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);
if (oldType == AccountListItem.Type.HEADER)
return true;
- return oldItem.getAccount()
- .getId() == newItem.getAccount()
+ return oldItem.toAccount()
+ .getAccount()
+ .getId() == newItem.toAccount()
+ .getAccount()
.getId();
}
@Override
return 0;
return listDiffer.getCurrentList()
.get(position)
+ .toAccount()
.getAccount()
.getId();
}
private LedgerAccount getAccount() {
return listDiffer.getCurrentList()
.get(getBindingAdapterPosition())
+ .toAccount()
.getAccount();
}
private void toggleAmountsExpanded() {
}
@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) {
/*
- * 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;
import androidx.recyclerview.widget.RecyclerView;
import androidx.swiperefreshlayout.widget.SwipeRefreshLayout;
+import net.ktnx.mobileledger.R;
import net.ktnx.mobileledger.async.GeneralBackgroundTasks;
import net.ktnx.mobileledger.databinding.AccountSummaryFragmentBinding;
import net.ktnx.mobileledger.db.AccountWithAmounts;
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);
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);
model.scheduleTransactionListRetrieval();
});
- Data.observeProfile(this, this::onProfileChanged);
+ Data.observeProfile(this, profile -> onProfileChanged(profile, Boolean.TRUE.equals(
+ model.getShowZeroBalanceAccounts()
+ .getValue())));
+ }
+ @Override
+ public void onCreateOptionsMenu(@NotNull Menu menu, @NotNull MenuInflater inflater) {
+ inflater.inflate(R.menu.account_list, menu);
+
+ menuShowZeroBalances = menu.findItem(R.id.menu_account_list_show_zero_balances);
+ if ((menuShowZeroBalances == null))
+ throw new AssertionError();
+
+ menuShowZeroBalances.setOnMenuItemClickListener(menuItem -> {
+ model.getShowZeroBalanceAccounts()
+ .setValue(Boolean.FALSE.equals(model.getShowZeroBalanceAccounts()
+ .getValue()));
+ return true;
+ });
+
+ model.getShowZeroBalanceAccounts()
+ .observe(this, v -> {
+ menuShowZeroBalances.setChecked(v);
+ onProfileChanged(Data.getProfile(), v);
+ });
+
+ super.onCreateOptionsMenu(menu, inflater);
}
- private void onProfileChanged(Profile profile) {
+ private void onProfileChanged(Profile profile, boolean showZeroBalanceAccounts) {
if (profile == null)
return;
DB.get()
.getAccountDAO()
- .getAllWithAmounts(profile.getId())
+ .getAllWithAmounts(profile.getId(), showZeroBalanceAccounts)
.observe(getViewLifecycleOwner(), list -> GeneralBackgroundTasks.run(() -> {
List<AccountListItem> adapterList = new ArrayList<>();
adapterList.add(new AccountListItem.Header(Data.lastAccountsUpdateText));
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);
+ }
+ }
}
/*
- * 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
package net.ktnx.mobileledger.ui.new_transaction;
import android.annotation.SuppressLint;
+import android.os.Build;
import android.text.TextUtils;
import androidx.annotation.NonNull;
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;
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;
}
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(),
}
else
item.resetAmount();
+
+ if (item.getCurrency()
+ .length() > 0)
+ hasCurrency = true;
}
if (BuildConfig.DEBUG)
dumpItemList("Loaded previous transaction", newList);
moveItemLast(newList, singlePositiveIndex);
}
+ final boolean foundTransactionHasCurrency = hasCurrency;
Misc.onMainThread(() -> {
setItems(newList);
noteFocusChanged(1, FocusedElement.Amount);
+ if (foundTransactionHasCurrency)
+ showCurrency.setValue(true);
});
}
/**
!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;
}
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));
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() {
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;
}
/*
- * 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;
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) ==
}
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;
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);
(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) ==
/*
- * 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
public void onDuplicateTemplate(long id) {
DB.get()
.getTemplateDAO()
- .duplicateTemplateWitAccounts(id, null);
+ .duplicateTemplateWithAccounts(id, null);
}
@Override
public void onEditTemplate(Long id) {
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;
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
/*
- * 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
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;
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);
}
}
/*
- * 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.utils;
+import static net.ktnx.mobileledger.utils.Logger.debug;
+
import android.app.Activity;
import android.content.res.ColorStateList;
import android.content.res.Resources;
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);
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) {
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);
}
}
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));
--- /dev/null
+<?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
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"
<?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>
<!--
- ~ 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>
+++ /dev/null
-<?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>
<?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
/*
- * 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
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
#
-# 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
-#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
--- /dev/null
+* ИЗВЕСТНИ ПРОБЛЕМИ
+ + Несъвместимост с hledger-web 1.23+
+* ПОПРАВКИ
+ + поправено дописване на описанието при въвеждане на ново движение
--- /dev/null
+* НОВО
+ + Добавена поддръжка на hledger-web версия 1.23
+* ПОПРАВКИ
+ + Включване на поддържащ файл за работата на БД, пропъснат във версия 0.20.4
--- /dev/null
+* ПОПРАВКИ
+ + поддръжане на hledger-web 1.23 и при добавяне на нови движения
+ + поправена натрупана сума при добавяне на движение в миналото
+ + поправен срив при изпращане на ново движение без данни за суми
--- /dev/null
+* ПОПРАВКИ
+ + отстранен срив при балансиране на трансакция с повече от една валута
+ + отстранен срив при дублиране на макет
+ + отстранен срив при зареждане на настройки от резервно копие
+* ПОДОБРЕНИЯ
+ + ново движение: включване на полазването на валути при зареждане на предишни движение с валути
--- /dev/null
+* ПОПРАВКИ
+ + коригирани версии на gradle
+* ДРУГИ
+ + обновени версии на множество библиотеки
+ + прицелване във версия 31 на платформата
--- /dev/null
+* ПОПРАВКИ
+ + отстранен проблем със съвместимостта с hledger-web 1.23+ при изпращане на нови транзакции. Благодарности на Faye Duxovni за поправката!
+ + поправен срив при изтриване на шаблони
+ + поправен рядък срив при изпращане на транзакции, съдържащи повече от една сметка без сума и нулев остатъчен баланс
--- /dev/null
+* ПОПРАВКИ
+ + отстранен проблем с централизираното резервно копие на настройките
--- /dev/null
+* ПОПРАВКИ
+ + отстранен проблем при изпращане на транзакции към hledger-web 1.23+
--- /dev/null
+* ПОПРАВКИ
+ + Позволяване на потребителски сертификати в настройките за сигурността на мрежовите връзки
+* ДРУГИ
+ + Обновена версия на gradle
--- /dev/null
+* KNOWN PROBLEMS
+ + Incompatibility with hledger-web 1.23+
+* FIXES
+ + fix auto-completion of transaction description
--- /dev/null
+* NEW
+ + Add support for hledger-web 1.23
+* FIXES
+ + Ship database support file missed in v0.20.4
--- /dev/null
+* 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
--- /dev/null
+* 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
--- /dev/null
+* FIXES
+ + sync gradle version requirements
+* OTHERS
+ + bump version of several dependent libraries
+ + bump SDK version to 31
+ + adjust deprecated constructor usage
--- /dev/null
+* 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
--- /dev/null
+* FIXES
+ + fix cloud backup
--- /dev/null
+* FIXES:
+ + fixed sending of transactions to hledger-web 1.23+
--- /dev/null
+* FIXES:
+ + allow user certificates in network security config
+* OTHERS:
+ + bump gradle version