From: Damyan Ivanov Date: Sun, 31 Mar 2024 11:13:26 +0000 (+0300) Subject: more pronounced day/month delimiters in the transaction list X-Git-Url: https://git.ktnx.net/?p=mobile-ledger.git;a=commitdiff_plain;h=HEAD;hp=e057b5d14e176767d52d865b6e3f0f081870954e more pronounced day/month delimiters in the transaction list --- diff --git a/CHANGES.md b/CHANGES.md index bb9141a6..02f84284 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,5 +1,68 @@ # Changes +## [0.21.7] = 2024-03-19 + +* FIXES: + + allow user certificates in network security config +* OTHERS: + + bump gradle version + +## [0.21.6] - 2023-06-20 + +* FIXES + + fix sending transations to hledger-web 1.23+ + +## [0.21.5] - 2022-09-03 + +* FIXES + + fix cloud backup + +## [0.21.4] - 2022-06-18 + +* FIXES + + fix compatibility wuth hledger-web 1.23+ when submitting new transactions. Thanks to Faye Duxovni for the patch! + + fix a crash when deleting templates + + fix a rare crash when submitting transactions with multiple accounts with no amounts with zero remaining balance + +## [0.21.3] - 2022-04-06 + +* FIXES + + sync gradle version requirements +* OTHERS + + bump version of several dependent libraries + + bump SDK version to 31 + + adjust deprecated constructor usage + +## [0.21.2] - 2022-04-04 + +* FIXES + + fix crash when auto-balancing multi currency transaction + + fix crash when duplicating template + + fix crash when restoring configuration backup +* IMPROVEMENTS + + new transaction: turn on commodity setting when loading previous transaction with commodities + +## [0.21.1] - 2021-12-30 + +* FIXES + + add hledger-web 1.23 support when adding transactions too + + correct running total when a matching transaction is added in the past + + fix crash when sending transaction containing only empty amounts + +## [0.21.0] - 2021-12-09 + +* NEW + + Add support for hledger-web 1.23 +* FIXES + + Ship database support file missed in v0.20.4 + +## [0.20.4] - 2021-11-18 + +* KNOWN PROBLEMS + + Incompatibility with hledger-web 1.23+ +* FIXES + + fix auto-completion of transaction description + ## [0.20.3] - 2021-09-29 * FIXES diff --git a/TODO.txt b/TODO.txt new file mode 100644 index 00000000..e229e17d --- /dev/null +++ b/TODO.txt @@ -0,0 +1,7 @@ +* Easy way to add tag:value pairs to transactions and transaction accounts + +* Filter by tag:value pairs + +* Refresh button in account/transaction list + +* Top button in account/transaction list diff --git a/app/build.gradle b/app/build.gradle index dc0c0da6..71673f6e 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -1,5 +1,5 @@ /* - * Copyright © 2021 Damyan Ivanov. + * Copyright © 2023 Damyan Ivanov. * This file is part of MoLe. * MoLe is free software: you can distribute it and/or modify it * under the term of the GNU General Public License as published by @@ -18,14 +18,14 @@ apply plugin: 'com.android.application' android { - compileSdkVersion 30 + compileSdkVersion 31 defaultConfig { applicationId "net.ktnx.mobileledger" minSdkVersion 22 - targetSdkVersion 30 + targetSdkVersion 31 vectorDrawables.useSupportLibrary true - versionCode 47 - versionName '0.20.3' + versionCode 56 + versionName '0.21.7' testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" javaCompileOptions { annotationProcessorOptions { @@ -62,29 +62,30 @@ android { } buildFeatures.viewBinding = true buildToolsVersion '30.0.3' + namespace 'net.ktnx.mobileledger' } dependencies { - implementation 'androidx.lifecycle:lifecycle-livedata-ktx:2.3.1' - implementation 'androidx.lifecycle:lifecycle-viewmodel-ktx:2.3.1' - def room_version = '2.3.0' - implementation "androidx.room:room-runtime:$room_version" + implementation 'androidx.lifecycle:lifecycle-livedata-ktx:2.4.1' + implementation 'androidx.lifecycle:lifecycle-viewmodel-ktx:2.4.1' + def room_version = '2.4.2' + implementation "androidx.room:room-runtime:2.4.2" annotationProcessor "androidx.room:room-compiler:$room_version" - def nav_version = '2.3.5' + def nav_version = '2.4.2' implementation fileTree(include: ['*.jar'], dir: 'libs') implementation 'androidx.legacy:legacy-support-v4:1.0.0' - implementation 'com.google.android.material:material:1.3.0' - implementation 'androidx.constraintlayout:constraintlayout:2.0.4' + implementation 'com.google.android.material:material:1.5.0' + implementation 'androidx.constraintlayout:constraintlayout:2.1.3' implementation 'androidx.lifecycle:lifecycle-extensions:2.2.0' - implementation 'androidx.recyclerview:recyclerview:1.2.0' + implementation 'androidx.recyclerview:recyclerview:1.2.1' testImplementation 'junit:junit:4.13.2' - androidTestImplementation 'androidx.test:runner:1.4.0-alpha06' - androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0-alpha06' - implementation 'org.jetbrains:annotations:20.1.0' - implementation 'com.fasterxml.jackson.module:jackson-modules-java8:2.12.3' + androidTestImplementation 'androidx.test:runner:1.5.0-alpha02' + androidTestImplementation 'androidx.test.espresso:espresso-core:3.5.0-alpha05' + implementation 'org.jetbrains:annotations:23.0.0' + implementation 'com.fasterxml.jackson.module:jackson-modules-java8:2.13.2' implementation "androidx.navigation:navigation-fragment:$nav_version" implementation "androidx.navigation:navigation-ui:$nav_version" - implementation 'androidx.appcompat:appcompat:1.3.0-rc01' + implementation 'androidx.appcompat:appcompat:1.6.0-alpha01' } allprojects { diff --git a/app/schemas/net.ktnx.mobileledger.db.DB/66.json b/app/schemas/net.ktnx.mobileledger.db.DB/66.json new file mode 100644 index 00000000..13cd5d8a --- /dev/null +++ b/app/schemas/net.ktnx.mobileledger.db.DB/66.json @@ -0,0 +1,867 @@ +{ + "formatVersion": 1, + "database": { + "version": 66, + "identityHash": "0739ea866a6aebb4217f68a7fcda5bc6", + "entities": [ + { + "tableName": "templates", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `name` TEXT NOT NULL, `uuid` TEXT NOT NULL, `regular_expression` TEXT NOT NULL, `test_text` TEXT, `transaction_description` TEXT, `transaction_description_match_group` INTEGER, `transaction_comment` TEXT, `transaction_comment_match_group` INTEGER, `date_year` INTEGER, `date_year_match_group` INTEGER, `date_month` INTEGER, `date_month_match_group` INTEGER, `date_day` INTEGER, `date_day_match_group` INTEGER, `is_fallback` INTEGER NOT NULL)", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "name", + "columnName": "name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "uuid", + "columnName": "uuid", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "regularExpression", + "columnName": "regular_expression", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "testText", + "columnName": "test_text", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "transactionDescription", + "columnName": "transaction_description", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "transactionDescriptionMatchGroup", + "columnName": "transaction_description_match_group", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "transactionComment", + "columnName": "transaction_comment", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "transactionCommentMatchGroup", + "columnName": "transaction_comment_match_group", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "dateYear", + "columnName": "date_year", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "dateYearMatchGroup", + "columnName": "date_year_match_group", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "dateMonth", + "columnName": "date_month", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "dateMonthMatchGroup", + "columnName": "date_month_match_group", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "dateDay", + "columnName": "date_day", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "dateDayMatchGroup", + "columnName": "date_day_match_group", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "isFallback", + "columnName": "is_fallback", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [ + { + "name": "templates_uuid_idx", + "unique": true, + "columnNames": [ + "uuid" + ], + "createSql": "CREATE UNIQUE INDEX IF NOT EXISTS `templates_uuid_idx` ON `${TABLE_NAME}` (`uuid`)" + } + ], + "foreignKeys": [] + }, + { + "tableName": "template_accounts", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `template_id` INTEGER NOT NULL, `acc` TEXT, `position` INTEGER NOT NULL, `acc_match_group` INTEGER, `currency` INTEGER, `currency_match_group` INTEGER, `amount` REAL, `amount_match_group` INTEGER, `comment` TEXT, `comment_match_group` INTEGER, `negate_amount` INTEGER, FOREIGN KEY(`template_id`) REFERENCES `templates`(`id`) ON UPDATE RESTRICT ON DELETE CASCADE , FOREIGN KEY(`currency`) REFERENCES `currencies`(`id`) ON UPDATE RESTRICT ON DELETE RESTRICT )", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "templateId", + "columnName": "template_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "accountName", + "columnName": "acc", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "position", + "columnName": "position", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "accountNameMatchGroup", + "columnName": "acc_match_group", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "currency", + "columnName": "currency", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "currencyMatchGroup", + "columnName": "currency_match_group", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "amount", + "columnName": "amount", + "affinity": "REAL", + "notNull": false + }, + { + "fieldPath": "amountMatchGroup", + "columnName": "amount_match_group", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "accountComment", + "columnName": "comment", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "accountCommentMatchGroup", + "columnName": "comment_match_group", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "negateAmount", + "columnName": "negate_amount", + "affinity": "INTEGER", + "notNull": false + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [ + { + "name": "fk_template_accounts_template", + "unique": false, + "columnNames": [ + "template_id" + ], + "createSql": "CREATE INDEX IF NOT EXISTS `fk_template_accounts_template` ON `${TABLE_NAME}` (`template_id`)" + }, + { + "name": "fk_template_accounts_currency", + "unique": false, + "columnNames": [ + "currency" + ], + "createSql": "CREATE INDEX IF NOT EXISTS `fk_template_accounts_currency` ON `${TABLE_NAME}` (`currency`)" + } + ], + "foreignKeys": [ + { + "table": "templates", + "onDelete": "CASCADE", + "onUpdate": "RESTRICT", + "columns": [ + "template_id" + ], + "referencedColumns": [ + "id" + ] + }, + { + "table": "currencies", + "onDelete": "RESTRICT", + "onUpdate": "RESTRICT", + "columns": [ + "currency" + ], + "referencedColumns": [ + "id" + ] + } + ] + }, + { + "tableName": "currencies", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `name` TEXT NOT NULL, `position` TEXT NOT NULL, `has_gap` INTEGER NOT NULL)", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "name", + "columnName": "name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "position", + "columnName": "position", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "hasGap", + "columnName": "has_gap", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [ + { + "name": "currency_name_idx", + "unique": true, + "columnNames": [ + "name" + ], + "createSql": "CREATE UNIQUE INDEX IF NOT EXISTS `currency_name_idx` ON `${TABLE_NAME}` (`name`)" + } + ], + "foreignKeys": [] + }, + { + "tableName": "accounts", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `profile_id` INTEGER NOT NULL, `level` INTEGER NOT NULL, `name` TEXT NOT NULL, `name_upper` TEXT NOT NULL, `parent_name` TEXT, `expanded` INTEGER NOT NULL DEFAULT 1, `amounts_expanded` INTEGER NOT NULL DEFAULT 0, `generation` INTEGER NOT NULL DEFAULT 0, FOREIGN KEY(`profile_id`) REFERENCES `profiles`(`id`) ON UPDATE RESTRICT ON DELETE CASCADE )", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "profileId", + "columnName": "profile_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "level", + "columnName": "level", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "name", + "columnName": "name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "nameUpper", + "columnName": "name_upper", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "parentName", + "columnName": "parent_name", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "expanded", + "columnName": "expanded", + "affinity": "INTEGER", + "notNull": true, + "defaultValue": "1" + }, + { + "fieldPath": "amountsExpanded", + "columnName": "amounts_expanded", + "affinity": "INTEGER", + "notNull": true, + "defaultValue": "0" + }, + { + "fieldPath": "generation", + "columnName": "generation", + "affinity": "INTEGER", + "notNull": true, + "defaultValue": "0" + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [ + { + "name": "un_account_name", + "unique": true, + "columnNames": [ + "profile_id", + "name" + ], + "createSql": "CREATE UNIQUE INDEX IF NOT EXISTS `un_account_name` ON `${TABLE_NAME}` (`profile_id`, `name`)" + }, + { + "name": "fk_account_profile", + "unique": false, + "columnNames": [ + "profile_id" + ], + "createSql": "CREATE INDEX IF NOT EXISTS `fk_account_profile` ON `${TABLE_NAME}` (`profile_id`)" + } + ], + "foreignKeys": [ + { + "table": "profiles", + "onDelete": "CASCADE", + "onUpdate": "RESTRICT", + "columns": [ + "profile_id" + ], + "referencedColumns": [ + "id" + ] + } + ] + }, + { + "tableName": "profiles", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `name` TEXT NOT NULL, `uuid` TEXT NOT NULL, `url` TEXT NOT NULL, `use_authentication` INTEGER NOT NULL, `auth_user` TEXT, `auth_password` TEXT, `order_no` INTEGER NOT NULL, `permit_posting` INTEGER NOT NULL, `theme` INTEGER NOT NULL DEFAULT -1, `preferred_accounts_filter` TEXT, `future_dates` INTEGER NOT NULL, `api_version` INTEGER NOT NULL, `show_commodity_by_default` INTEGER NOT NULL, `default_commodity` TEXT, `show_comments_by_default` INTEGER NOT NULL DEFAULT 1, `detected_version_pre_1_19` INTEGER NOT NULL, `detected_version_major` INTEGER NOT NULL, `detected_version_minor` INTEGER NOT NULL)", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "name", + "columnName": "name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "uuid", + "columnName": "uuid", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "url", + "columnName": "url", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "useAuthentication", + "columnName": "use_authentication", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "authUser", + "columnName": "auth_user", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "authPassword", + "columnName": "auth_password", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "orderNo", + "columnName": "order_no", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "permitPosting", + "columnName": "permit_posting", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "theme", + "columnName": "theme", + "affinity": "INTEGER", + "notNull": true, + "defaultValue": "-1" + }, + { + "fieldPath": "preferredAccountsFilter", + "columnName": "preferred_accounts_filter", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "futureDates", + "columnName": "future_dates", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "apiVersion", + "columnName": "api_version", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "showCommodityByDefault", + "columnName": "show_commodity_by_default", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "defaultCommodity", + "columnName": "default_commodity", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "showCommentsByDefault", + "columnName": "show_comments_by_default", + "affinity": "INTEGER", + "notNull": true, + "defaultValue": "1" + }, + { + "fieldPath": "detectedVersionPre_1_19", + "columnName": "detected_version_pre_1_19", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "detectedVersionMajor", + "columnName": "detected_version_major", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "detectedVersionMinor", + "columnName": "detected_version_minor", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [ + { + "name": "profiles_uuid_idx", + "unique": true, + "columnNames": [ + "uuid" + ], + "createSql": "CREATE UNIQUE INDEX IF NOT EXISTS `profiles_uuid_idx` ON `${TABLE_NAME}` (`uuid`)" + } + ], + "foreignKeys": [] + }, + { + "tableName": "options", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`profile_id` INTEGER NOT NULL, `name` TEXT NOT NULL, `value` TEXT, PRIMARY KEY(`profile_id`, `name`))", + "fields": [ + { + "fieldPath": "profileId", + "columnName": "profile_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "name", + "columnName": "name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "value", + "columnName": "value", + "affinity": "TEXT", + "notNull": false + } + ], + "primaryKey": { + "columnNames": [ + "profile_id", + "name" + ], + "autoGenerate": false + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "account_values", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `account_id` INTEGER NOT NULL, `currency` TEXT NOT NULL DEFAULT '', `value` REAL NOT NULL, `generation` INTEGER NOT NULL DEFAULT 0, FOREIGN KEY(`account_id`) REFERENCES `accounts`(`id`) ON UPDATE RESTRICT ON DELETE CASCADE )", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "accountId", + "columnName": "account_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "currency", + "columnName": "currency", + "affinity": "TEXT", + "notNull": true, + "defaultValue": "''" + }, + { + "fieldPath": "value", + "columnName": "value", + "affinity": "REAL", + "notNull": true + }, + { + "fieldPath": "generation", + "columnName": "generation", + "affinity": "INTEGER", + "notNull": true, + "defaultValue": "0" + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [ + { + "name": "un_account_values", + "unique": true, + "columnNames": [ + "account_id", + "currency" + ], + "createSql": "CREATE UNIQUE INDEX IF NOT EXISTS `un_account_values` ON `${TABLE_NAME}` (`account_id`, `currency`)" + }, + { + "name": "fk_account_value_acc", + "unique": false, + "columnNames": [ + "account_id" + ], + "createSql": "CREATE INDEX IF NOT EXISTS `fk_account_value_acc` ON `${TABLE_NAME}` (`account_id`)" + } + ], + "foreignKeys": [ + { + "table": "accounts", + "onDelete": "CASCADE", + "onUpdate": "RESTRICT", + "columns": [ + "account_id" + ], + "referencedColumns": [ + "id" + ] + } + ] + }, + { + "tableName": "transactions", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `ledger_id` INTEGER NOT NULL, `profile_id` INTEGER NOT NULL, `data_hash` TEXT NOT NULL, `year` INTEGER NOT NULL, `month` INTEGER NOT NULL, `day` INTEGER NOT NULL, `description` TEXT NOT NULL COLLATE NOCASE, `description_uc` TEXT NOT NULL, `comment` TEXT, `generation` INTEGER NOT NULL, FOREIGN KEY(`profile_id`) REFERENCES `profiles`(`id`) ON UPDATE RESTRICT ON DELETE CASCADE )", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "ledgerId", + "columnName": "ledger_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "profileId", + "columnName": "profile_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "dataHash", + "columnName": "data_hash", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "year", + "columnName": "year", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "month", + "columnName": "month", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "day", + "columnName": "day", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "description", + "columnName": "description", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "descriptionUpper", + "columnName": "description_uc", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "comment", + "columnName": "comment", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "generation", + "columnName": "generation", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [ + { + "name": "un_transactions_ledger_id", + "unique": true, + "columnNames": [ + "profile_id", + "ledger_id" + ], + "createSql": "CREATE UNIQUE INDEX IF NOT EXISTS `un_transactions_ledger_id` ON `${TABLE_NAME}` (`profile_id`, `ledger_id`)" + }, + { + "name": "idx_transaction_description", + "unique": false, + "columnNames": [ + "description" + ], + "createSql": "CREATE INDEX IF NOT EXISTS `idx_transaction_description` ON `${TABLE_NAME}` (`description`)" + }, + { + "name": "fk_transaction_profile", + "unique": false, + "columnNames": [ + "profile_id" + ], + "createSql": "CREATE INDEX IF NOT EXISTS `fk_transaction_profile` ON `${TABLE_NAME}` (`profile_id`)" + } + ], + "foreignKeys": [ + { + "table": "profiles", + "onDelete": "CASCADE", + "onUpdate": "RESTRICT", + "columns": [ + "profile_id" + ], + "referencedColumns": [ + "id" + ] + } + ] + }, + { + "tableName": "transaction_accounts", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `transaction_id` INTEGER NOT NULL, `order_no` INTEGER NOT NULL, `account_name` TEXT NOT NULL, `currency` TEXT NOT NULL DEFAULT '', `amount` REAL NOT NULL, `comment` TEXT, `generation` INTEGER NOT NULL DEFAULT 0, FOREIGN KEY(`transaction_id`) REFERENCES `transactions`(`id`) ON UPDATE RESTRICT ON DELETE CASCADE )", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "transactionId", + "columnName": "transaction_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "orderNo", + "columnName": "order_no", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "accountName", + "columnName": "account_name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "currency", + "columnName": "currency", + "affinity": "TEXT", + "notNull": true, + "defaultValue": "''" + }, + { + "fieldPath": "amount", + "columnName": "amount", + "affinity": "REAL", + "notNull": true + }, + { + "fieldPath": "comment", + "columnName": "comment", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "generation", + "columnName": "generation", + "affinity": "INTEGER", + "notNull": true, + "defaultValue": "0" + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [ + { + "name": "fk_trans_acc_trans", + "unique": false, + "columnNames": [ + "transaction_id" + ], + "createSql": "CREATE INDEX IF NOT EXISTS `fk_trans_acc_trans` ON `${TABLE_NAME}` (`transaction_id`)" + }, + { + "name": "un_transaction_accounts", + "unique": true, + "columnNames": [ + "transaction_id", + "order_no" + ], + "createSql": "CREATE UNIQUE INDEX IF NOT EXISTS `un_transaction_accounts` ON `${TABLE_NAME}` (`transaction_id`, `order_no`)" + } + ], + "foreignKeys": [ + { + "table": "transactions", + "onDelete": "CASCADE", + "onUpdate": "RESTRICT", + "columns": [ + "transaction_id" + ], + "referencedColumns": [ + "id" + ] + } + ] + } + ], + "views": [], + "setupQueries": [ + "CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)", + "INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, '0739ea866a6aebb4217f68a7fcda5bc6')" + ] + } +} \ No newline at end of file diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index e3ec6ee1..b17a1ecf 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -1,5 +1,5 @@ + xmlns:tools="http://schemas.android.com/tools"> @@ -24,13 +23,12 @@ 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"> @@ -51,7 +49,6 @@ { int res = o2.getDate() diff --git a/app/src/main/java/net/ktnx/mobileledger/async/SendTransactionTask.java b/app/src/main/java/net/ktnx/mobileledger/async/SendTransactionTask.java index 79a58173..0e700cca 100644 --- a/app/src/main/java/net/ktnx/mobileledger/async/SendTransactionTask.java +++ b/app/src/main/java/net/ktnx/mobileledger/async/SendTransactionTask.java @@ -1,5 +1,5 @@ /* - * Copyright © 2021 Damyan Ivanov. + * Copyright © 2023 Damyan Ivanov. * This file is part of MoLe. * MoLe is free software: you can distribute it and/or modify it * under the term of the GNU General Public License as published by @@ -17,6 +17,8 @@ package net.ktnx.mobileledger.async; +import static net.ktnx.mobileledger.utils.Logger.debug; + import android.util.Log; import net.ktnx.mobileledger.db.Profile; @@ -45,8 +47,6 @@ import java.util.Map; import java.util.regex.Matcher; import java.util.regex.Pattern; -import static net.ktnx.mobileledger.utils.Logger.debug; - /* TODO: get rid of the custom session/cookie and auth code? * (the last problem with the POST was the missing content-length header) * This will resolve itself when hledger-web 1.14+ is released with Debian/stable, @@ -275,6 +275,7 @@ public class SendTransactionTask extends Thread { case v1_14: case v1_15: case v1_19_1: + case v1_23: sendOK(profileApiVersion); break; default: @@ -286,9 +287,7 @@ public class SendTransactionTask extends Thread { error = e.getMessage(); } - Misc.onMainThread(()->{ - taskCallback.onTransactionSaveDone(error, transaction); - }); + Misc.onMainThread(() -> taskCallback.onTransactionSaveDone(error, transaction)); } private void legacySendOkWithRetry() throws IOException { int tried = 0; diff --git a/app/src/main/java/net/ktnx/mobileledger/backup/ConfigReader.java b/app/src/main/java/net/ktnx/mobileledger/backup/ConfigReader.java index 7959da51..187eb9b7 100644 --- a/app/src/main/java/net/ktnx/mobileledger/backup/ConfigReader.java +++ b/app/src/main/java/net/ktnx/mobileledger/backup/ConfigReader.java @@ -1,5 +1,5 @@ /* - * Copyright © 2021 Damyan Ivanov. + * Copyright © 2022 Damyan Ivanov. * This file is part of MoLe. * MoLe is free software: you can distribute it and/or modify it * under the term of the GNU General Public License as published by @@ -45,7 +45,7 @@ public class ConfigReader extends ConfigIO { } @Override protected void initStream() { - RawConfigReader r = new RawConfigReader(new FileInputStream(pfd.getFileDescriptor())); + r = new RawConfigReader(new FileInputStream(pfd.getFileDescriptor())); } @Override protected void processStream() throws IOException { diff --git a/app/src/main/java/net/ktnx/mobileledger/backup/MobileLedgerBackupAgent.java b/app/src/main/java/net/ktnx/mobileledger/backup/MobileLedgerBackupAgent.java index ea09e514..66538435 100644 --- a/app/src/main/java/net/ktnx/mobileledger/backup/MobileLedgerBackupAgent.java +++ b/app/src/main/java/net/ktnx/mobileledger/backup/MobileLedgerBackupAgent.java @@ -1,5 +1,5 @@ /* - * Copyright © 2021 Damyan Ivanov. + * Copyright © 2022 Damyan Ivanov. * This file is part of MoLe. * MoLe is free software: you can distribute it and/or modify it * under the term of the GNU General Public License as published by @@ -17,7 +17,7 @@ package net.ktnx.mobileledger.backup; -import android.app.backup.BackupAgentHelper; +import android.app.backup.BackupAgent; import android.app.backup.BackupDataInput; import android.app.backup.BackupDataOutput; import android.os.ParcelFileDescriptor; @@ -29,13 +29,13 @@ import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.IOException; -class MobileLedgerBackupAgent extends BackupAgentHelper { +public class MobileLedgerBackupAgent extends BackupAgent { private static final int READ_BUF_LEN = 10; public static String SETTINGS_KEY = "settings"; @Override public void onBackup(ParcelFileDescriptor oldState, BackupDataOutput data, ParcelFileDescriptor newState) throws IOException { - super.onBackup(oldState, data, newState); + Logger.debug("backup", "onBackup()"); backupSettings(data); newState.close(); } diff --git a/app/src/main/java/net/ktnx/mobileledger/backup/RawConfigReader.java b/app/src/main/java/net/ktnx/mobileledger/backup/RawConfigReader.java index 45a593d7..95b27b32 100644 --- a/app/src/main/java/net/ktnx/mobileledger/backup/RawConfigReader.java +++ b/app/src/main/java/net/ktnx/mobileledger/backup/RawConfigReader.java @@ -20,6 +20,7 @@ package net.ktnx.mobileledger.backup; import android.util.JsonReader; import android.util.JsonToken; +import net.ktnx.mobileledger.App; import net.ktnx.mobileledger.backup.ConfigIO.Keys; import net.ktnx.mobileledger.dao.CurrencyDAO; import net.ktnx.mobileledger.dao.ProfileDAO; @@ -30,6 +31,8 @@ import net.ktnx.mobileledger.db.Profile; import net.ktnx.mobileledger.db.TemplateAccount; import net.ktnx.mobileledger.db.TemplateHeader; import net.ktnx.mobileledger.db.TemplateWithAccounts; +import net.ktnx.mobileledger.model.Data; +import net.ktnx.mobileledger.utils.Logger; import java.io.BufferedReader; import java.io.IOException; @@ -337,6 +340,7 @@ public class RawConfigReader { restoreCommodities(); restoreProfiles(); restoreTemplates(); + restoreCurrentProfile(); } private void restoreTemplates() { if (templates == null) @@ -374,4 +378,24 @@ public class RawConfigReader { dao.insert(c); } } + private void restoreCurrentProfile() { + if (currentProfile == null) { + Logger.debug("backup", "Not restoring current profile (not present in backup)"); + return; + } + + ProfileDAO dao = DB.get() + .getProfileDAO(); + + Profile p = dao.getByUuidSync(currentProfile); + + if (p != null) { + Logger.debug("backup", "Restoring current profile "+p.getName()); + Data.postCurrentProfile(p); + App.storeStartupProfileAndTheme(p.getId(), p.getTheme()); + } + else { + Logger.debug("backup", "Not restoring profile "+currentProfile+": not found in DB"); + } + } } diff --git a/app/src/main/java/net/ktnx/mobileledger/dao/AccountDAO.java b/app/src/main/java/net/ktnx/mobileledger/dao/AccountDAO.java index 0c581787..5ad4c90f 100644 --- a/app/src/main/java/net/ktnx/mobileledger/dao/AccountDAO.java +++ b/app/src/main/java/net/ktnx/mobileledger/dao/AccountDAO.java @@ -1,5 +1,5 @@ /* - * Copyright © 2021 Damyan Ivanov. + * Copyright © 2024 Damyan Ivanov. * This file is part of MoLe. * MoLe is free software: you can distribute it and/or modify it * under the term of the GNU General Public License as published by @@ -78,12 +78,19 @@ public abstract class AccountDAO extends BaseDAO { @Query("DELETE FROM accounts") public abstract void deleteAllSync(); - @Query("SELECT * FROM accounts WHERE profile_id=:profileId ORDER BY name") - public abstract LiveData> 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> getAll(long profileId, boolean includeZeroBalances); @Transaction - @Query("SELECT * FROM accounts WHERE profile_id = :profileId ORDER BY name") - public abstract LiveData> 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> getAllWithAmounts(long profileId, + boolean includeZeroBalances); @Query("SELECT * FROM accounts WHERE id=:id") public abstract Account getByIdSync(long id); diff --git a/app/src/main/java/net/ktnx/mobileledger/dao/TemplateHeaderDAO.java b/app/src/main/java/net/ktnx/mobileledger/dao/TemplateHeaderDAO.java index 24e751c8..67dcef8d 100644 --- a/app/src/main/java/net/ktnx/mobileledger/dao/TemplateHeaderDAO.java +++ b/app/src/main/java/net/ktnx/mobileledger/dao/TemplateHeaderDAO.java @@ -1,5 +1,5 @@ /* - * Copyright © 2021 Damyan Ivanov. + * Copyright © 2022 Damyan Ivanov. * This file is part of MoLe. * MoLe is free software: you can distribute it and/or modify it * under the term of the GNU General Public License as published by @@ -137,7 +137,7 @@ public abstract class TemplateHeaderDAO { Misc.onMainThread(callback); }); } - public void duplicateTemplateWitAccounts(@NonNull Long id, @Nullable + public void duplicateTemplateWithAccounts(@NonNull Long id, @Nullable AsyncResultCallback callback) { BaseDAO.runAsync(() -> { TemplateWithAccounts src = getTemplateWithAccountsSync(id); diff --git a/app/src/main/java/net/ktnx/mobileledger/db/TemplateHeader.java b/app/src/main/java/net/ktnx/mobileledger/db/TemplateHeader.java index 87ea4fd8..994c3309 100644 --- a/app/src/main/java/net/ktnx/mobileledger/db/TemplateHeader.java +++ b/app/src/main/java/net/ktnx/mobileledger/db/TemplateHeader.java @@ -1,5 +1,5 @@ /* - * Copyright © 2021 Damyan Ivanov. + * Copyright © 2022 Damyan Ivanov. * This file is part of MoLe. * MoLe is free software: you can distribute it and/or modify it * under the term of the GNU General Public License as published by @@ -219,6 +219,8 @@ public class TemplateHeader extends TemplateBase { public TemplateHeader createDuplicate() { TemplateHeader dup = new TemplateHeader(this); dup.id = 0; + dup.uuid = UUID.randomUUID() + .toString(); return dup; } diff --git a/app/src/main/java/net/ktnx/mobileledger/json/API.java b/app/src/main/java/net/ktnx/mobileledger/json/API.java index d634f29e..136bdf55 100644 --- a/app/src/main/java/net/ktnx/mobileledger/json/API.java +++ b/app/src/main/java/net/ktnx/mobileledger/json/API.java @@ -23,9 +23,9 @@ import android.util.SparseArray; import net.ktnx.mobileledger.R; public enum API { - auto(0), html(-1), v1_14(-2), v1_15(-3), v1_19_1(-4); + auto(0), html(-1), v1_14(-2), v1_15(-3), v1_19_1(-4), v1_23(-5); private static final SparseArray map = new SparseArray<>(); - public static API[] allVersions = {v1_19_1, v1_15, v1_14}; + public static API[] allVersions = {v1_23, v1_19_1, v1_15, v1_14}; static { for (API item : API.values()) { @@ -56,6 +56,8 @@ public enum API { return resources.getString(R.string.api_1_15); case v1_19_1: return resources.getString(R.string.api_1_19_1); + case v1_23: + return resources.getString(R.string.api_1_23); default: throw new IllegalStateException("Unexpected value: " + value); } @@ -72,6 +74,8 @@ public enum API { return "1.15"; case v1_19_1: return "1.19.1"; + case v1_23: + return "1.23"; default: throw new IllegalStateException("Unexpected value: " + this); } diff --git a/app/src/main/java/net/ktnx/mobileledger/json/AccountListParser.java b/app/src/main/java/net/ktnx/mobileledger/json/AccountListParser.java index 872cac79..baeeb2ea 100644 --- a/app/src/main/java/net/ktnx/mobileledger/json/AccountListParser.java +++ b/app/src/main/java/net/ktnx/mobileledger/json/AccountListParser.java @@ -39,6 +39,8 @@ abstract public class AccountListParser { return new net.ktnx.mobileledger.json.v1_15.AccountListParser(input); case v1_19_1: return new net.ktnx.mobileledger.json.v1_19_1.AccountListParser(input); + case v1_23: + return new net.ktnx.mobileledger.json.v1_23.AccountListParser(input); default: throw new RuntimeException("Unsupported version " + version.toString()); } diff --git a/app/src/main/java/net/ktnx/mobileledger/json/Gateway.java b/app/src/main/java/net/ktnx/mobileledger/json/Gateway.java index 97789673..521e4c9f 100644 --- a/app/src/main/java/net/ktnx/mobileledger/json/Gateway.java +++ b/app/src/main/java/net/ktnx/mobileledger/json/Gateway.java @@ -1,5 +1,5 @@ /* - * Copyright © 2020 Damyan Ivanov. + * Copyright © 2021 Damyan Ivanov. * This file is part of MoLe. * MoLe is free software: you can distribute it and/or modify it * under the term of the GNU General Public License as published by @@ -30,8 +30,11 @@ abstract public class Gateway { return new net.ktnx.mobileledger.json.v1_15.Gateway(); case v1_19_1: return new net.ktnx.mobileledger.json.v1_19_1.Gateway(); + case v1_23: + return new net.ktnx.mobileledger.json.v1_23.Gateway(); default: - throw new RuntimeException("Unsupported JSON API version " + apiVersion); + throw new RuntimeException( + "JSON API version " + apiVersion + " save implementation missing"); } } public abstract String transactionSaveRequest(LedgerTransaction ledgerTransaction) diff --git a/app/src/main/java/net/ktnx/mobileledger/json/TransactionListParser.java b/app/src/main/java/net/ktnx/mobileledger/json/TransactionListParser.java index d707c062..20bf08b7 100644 --- a/app/src/main/java/net/ktnx/mobileledger/json/TransactionListParser.java +++ b/app/src/main/java/net/ktnx/mobileledger/json/TransactionListParser.java @@ -33,6 +33,8 @@ public abstract class TransactionListParser { return new net.ktnx.mobileledger.json.v1_15.TransactionListParser(input); case v1_19_1: return new net.ktnx.mobileledger.json.v1_19_1.TransactionListParser(input); + case v1_23: + return new net.ktnx.mobileledger.json.v1_23.TransactionListParser(input); default: throw new RuntimeException("Unsupported version " + apiVersion.toString()); } diff --git a/app/src/main/java/net/ktnx/mobileledger/json/v1_23/AccountListParser.java b/app/src/main/java/net/ktnx/mobileledger/json/v1_23/AccountListParser.java new file mode 100644 index 00000000..904fb57f --- /dev/null +++ b/app/src/main/java/net/ktnx/mobileledger/json/v1_23/AccountListParser.java @@ -0,0 +1,40 @@ +/* + * Copyright © 2020 Damyan Ivanov. + * This file is part of MoLe. + * MoLe is free software: you can distribute it and/or modify it + * under the term of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your opinion), any later version. + * + * MoLe is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License terms for details. + * + * You should have received a copy of the GNU General Public License + * along with MoLe. If not, see . + */ + +package net.ktnx.mobileledger.json.v1_23; + +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.ObjectReader; + +import net.ktnx.mobileledger.json.API; + +import java.io.IOException; +import java.io.InputStream; + +public class AccountListParser extends net.ktnx.mobileledger.json.AccountListParser { + + public AccountListParser(InputStream input) throws IOException { + ObjectMapper mapper = new ObjectMapper(); + ObjectReader reader = mapper.readerFor(ParsedLedgerAccount.class); + + iterator = reader.readValues(input); + } + @Override + public API getApiVersion() { + return API.v1_19_1; + } +} diff --git a/app/src/main/java/net/ktnx/mobileledger/json/v1_23/Gateway.java b/app/src/main/java/net/ktnx/mobileledger/json/v1_23/Gateway.java new file mode 100644 index 00000000..c04f6f23 --- /dev/null +++ b/app/src/main/java/net/ktnx/mobileledger/json/v1_23/Gateway.java @@ -0,0 +1,36 @@ +/* + * Copyright © 2020 Damyan Ivanov. + * This file is part of MoLe. + * MoLe is free software: you can distribute it and/or modify it + * under the term of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your opinion), any later version. + * + * MoLe is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License terms for details. + * + * You should have received a copy of the GNU General Public License + * along with MoLe. If not, see . + */ + +package net.ktnx.mobileledger.json.v1_23; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.ObjectWriter; + +import net.ktnx.mobileledger.model.LedgerTransaction; + +public class Gateway extends net.ktnx.mobileledger.json.Gateway { + @Override + public String transactionSaveRequest(LedgerTransaction ledgerTransaction) + throws JsonProcessingException { + ParsedLedgerTransaction jsonTransaction = + ParsedLedgerTransaction.fromLedgerTransaction(ledgerTransaction); + ObjectMapper mapper = new ObjectMapper(); + ObjectWriter writer = mapper.writerFor(ParsedLedgerTransaction.class); + return writer.writeValueAsString(jsonTransaction); + } +} diff --git a/app/src/main/java/net/ktnx/mobileledger/json/v1_23/ParsedAmount.java b/app/src/main/java/net/ktnx/mobileledger/json/v1_23/ParsedAmount.java new file mode 100644 index 00000000..58f88c16 --- /dev/null +++ b/app/src/main/java/net/ktnx/mobileledger/json/v1_23/ParsedAmount.java @@ -0,0 +1,62 @@ +/* + * Copyright © 2020 Damyan Ivanov. + * This file is part of MoLe. + * MoLe is free software: you can distribute it and/or modify it + * under the term of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your opinion), any later version. + * + * MoLe is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License terms for details. + * + * You should have received a copy of the GNU General Public License + * along with MoLe. If not, see . + */ + +package net.ktnx.mobileledger.json.v1_23; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; + +@JsonIgnoreProperties(ignoreUnknown = true) +public class ParsedAmount { + private String acommodity; + private ParsedQuantity aquantity; + private boolean aismultiplier; + private ParsedStyle astyle; + private ParsedPrice aprice; + public ParsedAmount() { + } + public ParsedPrice getAprice() { + return aprice; + } + public void setAprice(ParsedPrice aprice) { + this.aprice = aprice; + } + public String getAcommodity() { + return acommodity; + } + public void setAcommodity(String acommodity) { + this.acommodity = acommodity; + } + public ParsedQuantity getAquantity() { + return aquantity; + } + public void setAquantity(ParsedQuantity aquantity) { + this.aquantity = aquantity; + } + public boolean isAismultiplier() { + return aismultiplier; + } + public void setAismultiplier(boolean aismultiplier) { + this.aismultiplier = aismultiplier; + } + public ParsedStyle getAstyle() { + return astyle; + } + public void setAstyle(ParsedStyle astyle) { + this.astyle = astyle; + } + +} diff --git a/app/src/main/java/net/ktnx/mobileledger/json/v1_23/ParsedBalance.java b/app/src/main/java/net/ktnx/mobileledger/json/v1_23/ParsedBalance.java new file mode 100644 index 00000000..4f74de41 --- /dev/null +++ b/app/src/main/java/net/ktnx/mobileledger/json/v1_23/ParsedBalance.java @@ -0,0 +1,33 @@ +/* + * Copyright © 2020 Damyan Ivanov. + * This file is part of MoLe. + * MoLe is free software: you can distribute it and/or modify it + * under the term of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your opinion), any later version. + * + * MoLe is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License terms for details. + * + * You should have received a copy of the GNU General Public License + * along with MoLe. If not, see . + */ + +package net.ktnx.mobileledger.json.v1_23; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; + +@JsonIgnoreProperties(ignoreUnknown = true) +public class ParsedBalance extends net.ktnx.mobileledger.json.ParsedBalance { + private ParsedStyle astyle; + public ParsedBalance() { + } + public ParsedStyle getAstyle() { + return astyle; + } + public void setAstyle(ParsedStyle astyle) { + this.astyle = astyle; + } +} diff --git a/app/src/main/java/net/ktnx/mobileledger/json/v1_23/ParsedLedgerAccount.java b/app/src/main/java/net/ktnx/mobileledger/json/v1_23/ParsedLedgerAccount.java new file mode 100644 index 00000000..6d942e83 --- /dev/null +++ b/app/src/main/java/net/ktnx/mobileledger/json/v1_23/ParsedLedgerAccount.java @@ -0,0 +1,53 @@ +/* + * Copyright © 2020 Damyan Ivanov. + * This file is part of MoLe. + * MoLe is free software: you can distribute it and/or modify it + * under the term of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your opinion), any later version. + * + * MoLe is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License terms for details. + * + * You should have received a copy of the GNU General Public License + * along with MoLe. If not, see . + */ + +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 aebalance; + private List aibalance; + public ParsedLedgerAccount() { + } + public List getAibalance() { + return aibalance; + } + public void setAibalance(List aibalance) { + this.aibalance = aibalance; + } + public List getAebalance() { + return aebalance; + } + public void setAebalance(List aebalance) { + this.aebalance = aebalance; + } + @Override + public List getSimpleBalance() { + List result = new ArrayList(); + for (ParsedBalance b : getAibalance()) { + result.add(new SimpleBalance(b.getAcommodity(), b.getAquantity() + .asFloat())); + } + + return result; + } +} diff --git a/app/src/main/java/net/ktnx/mobileledger/json/v1_23/ParsedLedgerTransaction.java b/app/src/main/java/net/ktnx/mobileledger/json/v1_23/ParsedLedgerTransaction.java new file mode 100644 index 00000000..59d17635 --- /dev/null +++ b/app/src/main/java/net/ktnx/mobileledger/json/v1_23/ParsedLedgerTransaction.java @@ -0,0 +1,166 @@ +/* + * Copyright © 2021 Damyan Ivanov. + * This file is part of MoLe. + * MoLe is free software: you can distribute it and/or modify it + * under the term of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your opinion), any later version. + * + * MoLe is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License terms for details. + * + * You should have received a copy of the GNU General Public License + * along with MoLe. If not, see . + */ + +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 tpostings; + private List> ttags = new ArrayList<>(); + private List 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 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> getTtags() { + return ttags; + } + public void setTtags(List> ttags) { + this.ttags = ttags; + } + public List getTsourcepos() { + return tsourcepos; + } + public void setTsourcepos(List 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 getTpostings() { + return tpostings; + } + public void setTpostings(List 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 postings = tpostings; + + if (postings != null) { + for (ParsedPosting p : postings) { + tr.addAccount(p.asLedgerAccount()); + } + } + + tr.markDataAsLoaded(); + return tr; + } +} diff --git a/app/src/main/java/net/ktnx/mobileledger/json/v1_23/ParsedPosting.java b/app/src/main/java/net/ktnx/mobileledger/json/v1_23/ParsedPosting.java new file mode 100644 index 00000000..e60bd193 --- /dev/null +++ b/app/src/main/java/net/ktnx/mobileledger/json/v1_23/ParsedPosting.java @@ -0,0 +1,144 @@ +/* + * Copyright © 2020 Damyan Ivanov. + * This file is part of MoLe. + * MoLe is free software: you can distribute it and/or modify it + * under the term of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your opinion), any later version. + * + * MoLe is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License terms for details. + * + * You should have received a copy of the GNU General Public License + * along with MoLe. If not, see . + */ + +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 pamount; + private String pdate = null; + private String pdate2 = null; + private String ptype = "RegularPosting"; + private String pcomment = ""; + private List> 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 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> getPtags() { + return ptags; + } + public void setPtags(List> 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 getPamount() { + return pamount; + } + public void setPamount(List pamount) { + this.pamount = pamount; + } + public LedgerTransactionAccount asLedgerAccount() { + ParsedAmount amt = pamount.get(0); + return new LedgerTransactionAccount(paccount, amt.getAquantity() + .asFloat(), amt.getAcommodity(), + getPcomment()); + } + +} diff --git a/app/src/main/java/net/ktnx/mobileledger/json/v1_23/ParsedPrice.java b/app/src/main/java/net/ktnx/mobileledger/json/v1_23/ParsedPrice.java new file mode 100644 index 00000000..80a810a6 --- /dev/null +++ b/app/src/main/java/net/ktnx/mobileledger/json/v1_23/ParsedPrice.java @@ -0,0 +1,78 @@ +/* + * Copyright © 2020 Damyan Ivanov. + * This file is part of MoLe. + * MoLe is free software: you can distribute it and/or modify it + * under the term of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your opinion), any later version. + * + * MoLe is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License terms for details. + * + * You should have received a copy of the GNU General Public License + * along with MoLe. If not, see . + */ + +package net.ktnx.mobileledger.json.v1_23; + +class ParsedPrice { + private String tag; + private Contents contents; + public ParsedPrice() { + tag = "NoPrice"; + } + public Contents getContents() { + return contents; + } + public void setContents(Contents contents) { + this.contents = contents; + } + public String getTag() { + return tag; + } + public void setTag(String tag) { + this.tag = tag; + } + private static class Contents { + private ParsedPrice aprice; + private ParsedQuantity aquantity; + private String acommodity; + private boolean aismultiplier; + private ParsedStyle astyle; + public Contents() { + acommodity = ""; + } + public ParsedPrice getAprice() { + return aprice; + } + public void setAprice(ParsedPrice aprice) { + this.aprice = aprice; + } + public ParsedQuantity getAquantity() { + return aquantity; + } + public void setAquantity(ParsedQuantity aquantity) { + this.aquantity = aquantity; + } + public String getAcommodity() { + return acommodity; + } + public void setAcommodity(String acommodity) { + this.acommodity = acommodity; + } + public boolean isAismultiplier() { + return aismultiplier; + } + public void setAismultiplier(boolean aismultiplier) { + this.aismultiplier = aismultiplier; + } + public ParsedStyle getAstyle() { + return astyle; + } + public void setAstyle(ParsedStyle astyle) { + this.astyle = astyle; + } + } +} diff --git a/app/src/main/java/net/ktnx/mobileledger/json/v1_23/ParsedQuantity.java b/app/src/main/java/net/ktnx/mobileledger/json/v1_23/ParsedQuantity.java new file mode 100644 index 00000000..55026935 --- /dev/null +++ b/app/src/main/java/net/ktnx/mobileledger/json/v1_23/ParsedQuantity.java @@ -0,0 +1,23 @@ +/* + * Copyright © 2020 Damyan Ivanov. + * This file is part of MoLe. + * MoLe is free software: you can distribute it and/or modify it + * under the term of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your opinion), any later version. + * + * MoLe is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License terms for details. + * + * You should have received a copy of the GNU General Public License + * along with MoLe. If not, see . + */ + +package net.ktnx.mobileledger.json.v1_23; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; + +@JsonIgnoreProperties(ignoreUnknown = true) +public class ParsedQuantity extends net.ktnx.mobileledger.json.ParsedQuantity {} diff --git a/app/src/main/java/net/ktnx/mobileledger/json/v1_23/ParsedSourcePos.java b/app/src/main/java/net/ktnx/mobileledger/json/v1_23/ParsedSourcePos.java new file mode 100644 index 00000000..b3ea5db6 --- /dev/null +++ b/app/src/main/java/net/ktnx/mobileledger/json/v1_23/ParsedSourcePos.java @@ -0,0 +1,44 @@ +/* + * Copyright © 2020 Damyan Ivanov. + * This file is part of MoLe. + * MoLe is free software: you can distribute it and/or modify it + * under the term of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your opinion), any later version. + * + * MoLe is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License terms for details. + * + * You should have received a copy of the GNU General Public License + * along with MoLe. If not, see . + */ + +package net.ktnx.mobileledger.json.v1_23; + +class ParsedSourcePos { + private String sourceName = ""; + private int sourceLine = 1; + private int sourceColumn = 1; + public ParsedSourcePos() { + } + public String getSourceName() { + return sourceName; + } + public void setSourceName(String sourceName) { + this.sourceName = sourceName; + } + public int getSourceLine() { + return sourceLine; + } + public void setSourceLine(int sourceLine) { + this.sourceLine = sourceLine; + } + public int getSourceColumn() { + return sourceColumn; + } + public void setSourceColumn(int sourceColumn) { + this.sourceColumn = sourceColumn; + } +} diff --git a/app/src/main/java/net/ktnx/mobileledger/json/v1_23/ParsedStyle.java b/app/src/main/java/net/ktnx/mobileledger/json/v1_23/ParsedStyle.java new file mode 100644 index 00000000..d3a0a133 --- /dev/null +++ b/app/src/main/java/net/ktnx/mobileledger/json/v1_23/ParsedStyle.java @@ -0,0 +1,33 @@ +/* + * Copyright © 2020 Damyan Ivanov. + * This file is part of MoLe. + * MoLe is free software: you can distribute it and/or modify it + * under the term of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your opinion), any later version. + * + * MoLe is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License terms for details. + * + * You should have received a copy of the GNU General Public License + * along with MoLe. If not, see . + */ + +package net.ktnx.mobileledger.json.v1_23; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; + +@JsonIgnoreProperties(ignoreUnknown = true) +public class ParsedStyle extends net.ktnx.mobileledger.json.ParsedStyle { + private int asprecision; + public ParsedStyle() { + } + public int getAsprecision() { + return asprecision; + } + public void setAsprecision(int asprecision) { + this.asprecision = asprecision; + } +} diff --git a/app/src/main/java/net/ktnx/mobileledger/json/v1_23/TransactionListParser.java b/app/src/main/java/net/ktnx/mobileledger/json/v1_23/TransactionListParser.java new file mode 100644 index 00000000..7f5350a4 --- /dev/null +++ b/app/src/main/java/net/ktnx/mobileledger/json/v1_23/TransactionListParser.java @@ -0,0 +1,44 @@ +/* + * Copyright © 2020 Damyan Ivanov. + * This file is part of MoLe. + * MoLe is free software: you can distribute it and/or modify it + * under the term of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your opinion), any later version. + * + * MoLe is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License terms for details. + * + * You should have received a copy of the GNU General Public License + * along with MoLe. If not, see . + */ + +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 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; + } +} diff --git a/app/src/main/java/net/ktnx/mobileledger/model/AccountListItem.java b/app/src/main/java/net/ktnx/mobileledger/model/AccountListItem.java index dfd3d982..807e93d3 100644 --- a/app/src/main/java/net/ktnx/mobileledger/model/AccountListItem.java +++ b/app/src/main/java/net/ktnx/mobileledger/model/AccountListItem.java @@ -1,5 +1,5 @@ /* - * Copyright © 2021 Damyan Ivanov. + * Copyright © 2024 Damyan Ivanov. * This file is part of MoLe. * MoLe is free software: you can distribute it and/or modify it * under the term of the GNU General Public License as published by @@ -34,12 +34,19 @@ public abstract class AccountListItem { else throw new RuntimeException("Unsupported sub-class " + this); } - @NotNull - public LedgerAccount getAccount() { - if (this instanceof Account) - return ((Account) this).account; - - throw new IllegalStateException(String.format("Item type is not Account, but %s", this)); + public boolean isAccount() { + return this instanceof Account; + } + public Account toAccount() { + assert isAccount(); + return ((Account) this); + } + public boolean isHeader() { + return this instanceof Header; + } + public Header toHeader() { + assert isHeader(); + return ((Header) this); } public enum Type {ACCOUNT, HEADER} @@ -59,6 +66,13 @@ public abstract class AccountListItem { ((Account) other).account.getAmountsString() .equals(account.getAmountsString()); } + @NotNull + public LedgerAccount getAccount() { + return account; + } + public boolean allAmountsAreZero() { + return account.allAmountsAreZero(); + } } public static class Header extends AccountListItem { diff --git a/app/src/main/java/net/ktnx/mobileledger/model/LedgerAccount.java b/app/src/main/java/net/ktnx/mobileledger/model/LedgerAccount.java index 1be684cf..c2e62772 100644 --- a/app/src/main/java/net/ktnx/mobileledger/model/LedgerAccount.java +++ b/app/src/main/java/net/ktnx/mobileledger/model/LedgerAccount.java @@ -1,5 +1,5 @@ /* - * Copyright © 2021 Damyan Ivanov. + * Copyright © 2024 Damyan Ivanov. * This file is part of MoLe. * MoLe is free software: you can distribute it and/or modify it * under the term of the GNU General Public License as published by @@ -201,13 +201,21 @@ public class LedgerAccount { if (amounts != null) amounts.clear(); } - public boolean amountsExpanded() { return amountsExpanded; } - public void setAmountsExpanded(boolean flag) { amountsExpanded = flag; } - public void toggleAmountsExpanded() { amountsExpanded = !amountsExpanded; } + public boolean amountsExpanded() {return amountsExpanded;} + public void setAmountsExpanded(boolean flag) {amountsExpanded = flag;} + public void toggleAmountsExpanded() {amountsExpanded = !amountsExpanded;} public void propagateAmountsTo(LedgerAccount acc) { for (LedgerAmount a : amounts) a.propagateToAccount(acc); } + public boolean allAmountsAreZero() { + for (LedgerAmount a : amounts) { + if (a.getAmount() != 0) + return false; + } + + return true; + } public List getAmounts() { return amounts; } diff --git a/app/src/main/java/net/ktnx/mobileledger/ui/MainModel.java b/app/src/main/java/net/ktnx/mobileledger/ui/MainModel.java index 329109fd..a8b957dc 100644 --- a/app/src/main/java/net/ktnx/mobileledger/ui/MainModel.java +++ b/app/src/main/java/net/ktnx/mobileledger/ui/MainModel.java @@ -1,5 +1,5 @@ /* - * Copyright © 2021 Damyan Ivanov. + * Copyright © 2024 Damyan Ivanov. * This file is part of MoLe. * MoLe is free software: you can distribute it and/or modify it * under the term of the GNU General Public License as published by @@ -38,6 +38,7 @@ import java.util.Locale; public class MainModel extends ViewModel { public final MutableLiveData foundTransactionItemIndex = new MutableLiveData<>(null); private final MutableLiveData updatingFlag = new MutableLiveData<>(false); + private final MutableLiveData showZeroBalanceAccounts = new MutableLiveData<>(true); private final MutableLiveData accountFilter = new MutableLiveData<>(null); private final MutableLiveData> displayedTransactions = new MutableLiveData<>(new ArrayList<>()); @@ -45,7 +46,6 @@ public class MainModel extends ViewModel { private SimpleDate firstTransactionDate; private SimpleDate lastTransactionDate; transient private RetrieveTransactionsTask retrieveTransactionsTask; - transient private Thread displayedAccountsUpdater; private TransactionsDisplayedFilter displayedTransactionsUpdater; public LiveData getUpdatingFlag() { return updatingFlag; @@ -66,6 +66,7 @@ public class MainModel extends ViewModel { public void setFirstTransactionDate(SimpleDate earliestDate) { this.firstTransactionDate = earliestDate; } + public MutableLiveData getShowZeroBalanceAccounts() {return showZeroBalanceAccounts;} public MutableLiveData getAccountFilter() { return accountFilter; } @@ -81,6 +82,7 @@ public class MainModel extends ViewModel { return; } Profile profile = Data.getProfile(); + assert profile != null; retrieveTransactionsTask = new RetrieveTransactionsTask(profile); Logger.debug("db", "Created a background transaction retrieval task"); diff --git a/app/src/main/java/net/ktnx/mobileledger/ui/account_summary/AccountSummaryAdapter.java b/app/src/main/java/net/ktnx/mobileledger/ui/account_summary/AccountSummaryAdapter.java index d7da335d..dcc16f36 100644 --- a/app/src/main/java/net/ktnx/mobileledger/ui/account_summary/AccountSummaryAdapter.java +++ b/app/src/main/java/net/ktnx/mobileledger/ui/account_summary/AccountSummaryAdapter.java @@ -1,5 +1,5 @@ /* - * Copyright © 2021 Damyan Ivanov. + * Copyright © 2024 Damyan Ivanov. * This file is part of MoLe. * MoLe is free software: you can distribute it and/or modify it * under the term of the GNU General Public License as published by @@ -17,6 +17,8 @@ package net.ktnx.mobileledger.ui.account_summary; +import static net.ktnx.mobileledger.utils.Logger.debug; + import android.content.res.Resources; import android.view.LayoutInflater; import android.view.View; @@ -48,8 +50,6 @@ import org.jetbrains.annotations.NotNull; import java.util.List; import java.util.Locale; -import static net.ktnx.mobileledger.utils.Logger.debug; - public class AccountSummaryAdapter extends RecyclerView.Adapter { public static final int AMOUNT_LIMIT = 3; private static final int ITEM_TYPE_HEADER = 1; @@ -66,8 +66,10 @@ public class AccountSummaryAdapter extends RecyclerView.Adapter payloads) { - LedgerAccount acc = item.getAccount(); + LedgerAccount acc = item.toAccount() + .getAccount(); Change changes = new Change(); if (payloads != null) { diff --git a/app/src/main/java/net/ktnx/mobileledger/ui/account_summary/AccountSummaryFragment.java b/app/src/main/java/net/ktnx/mobileledger/ui/account_summary/AccountSummaryFragment.java index 35a9edb7..807c16da 100644 --- a/app/src/main/java/net/ktnx/mobileledger/ui/account_summary/AccountSummaryFragment.java +++ b/app/src/main/java/net/ktnx/mobileledger/ui/account_summary/AccountSummaryFragment.java @@ -1,5 +1,5 @@ /* - * Copyright © 2021 Damyan Ivanov. + * Copyright © 2024 Damyan Ivanov. * This file is part of MoLe. * MoLe is free software: you can distribute it and/or modify it * under the term of the GNU General Public License as published by @@ -17,9 +17,14 @@ package net.ktnx.mobileledger.ui.account_summary; +import static net.ktnx.mobileledger.utils.Logger.debug; + import android.content.Context; import android.os.Bundle; import android.view.LayoutInflater; +import android.view.Menu; +import android.view.MenuInflater; +import android.view.MenuItem; import android.view.View; import android.view.ViewGroup; @@ -31,6 +36,7 @@ import androidx.recyclerview.widget.LinearLayoutManager; import androidx.recyclerview.widget.RecyclerView; import androidx.swiperefreshlayout.widget.SwipeRefreshLayout; +import net.ktnx.mobileledger.R; import net.ktnx.mobileledger.async.GeneralBackgroundTasks; import net.ktnx.mobileledger.databinding.AccountSummaryFragmentBinding; import net.ktnx.mobileledger.db.AccountWithAmounts; @@ -51,11 +57,11 @@ import java.util.ArrayList; import java.util.HashMap; import java.util.List; -import static net.ktnx.mobileledger.utils.Logger.debug; - public class AccountSummaryFragment extends MobileLedgerListFragment { public AccountSummaryAdapter modelAdapter; private AccountSummaryFragmentBinding b; + private MenuItem menuShowZeroBalances; + private MainModel model; @Override public void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); @@ -82,7 +88,7 @@ public class AccountSummaryFragment extends MobileLedgerListFragment { debug("flow", "AccountSummaryFragment.onActivityCreated()"); super.onViewCreated(view, savedInstanceState); - MainModel model = new ViewModelProvider(requireActivity()).get(MainModel.class); + model = new ViewModelProvider(requireActivity()).get(MainModel.class); Data.backgroundTasksRunning.observe(this.getViewLifecycleOwner(), this::onBackgroundTaskRunningChanged); @@ -109,15 +115,40 @@ public class AccountSummaryFragment extends MobileLedgerListFragment { model.scheduleTransactionListRetrieval(); }); - Data.observeProfile(this, this::onProfileChanged); + Data.observeProfile(this, profile -> onProfileChanged(profile, Boolean.TRUE.equals( + model.getShowZeroBalanceAccounts() + .getValue()))); + } + @Override + public void onCreateOptionsMenu(@NotNull Menu menu, @NotNull MenuInflater inflater) { + inflater.inflate(R.menu.account_list, menu); + + menuShowZeroBalances = menu.findItem(R.id.menu_account_list_show_zero_balances); + if ((menuShowZeroBalances == null)) + throw new AssertionError(); + + menuShowZeroBalances.setOnMenuItemClickListener(menuItem -> { + model.getShowZeroBalanceAccounts() + .setValue(Boolean.FALSE.equals(model.getShowZeroBalanceAccounts() + .getValue())); + return true; + }); + + model.getShowZeroBalanceAccounts() + .observe(this, v -> { + menuShowZeroBalances.setChecked(v); + onProfileChanged(Data.getProfile(), v); + }); + + super.onCreateOptionsMenu(menu, inflater); } - private void onProfileChanged(Profile profile) { + private void onProfileChanged(Profile profile, boolean showZeroBalanceAccounts) { if (profile == null) return; DB.get() .getAccountDAO() - .getAllWithAmounts(profile.getId()) + .getAllWithAmounts(profile.getId(), showZeroBalanceAccounts) .observe(getViewLifecycleOwner(), list -> GeneralBackgroundTasks.run(() -> { List adapterList = new ArrayList<>(); adapterList.add(new AccountListItem.Header(Data.lastAccountsUpdateText)); @@ -134,8 +165,57 @@ public class AccountSummaryFragment extends MobileLedgerListFragment { adapterList.add(new AccountListItem.Account(account)); accMap.put(dbAcc.account.getName(), account); } + + if (!showZeroBalanceAccounts) { + removeZeroAccounts(adapterList); + } modelAdapter.setAccounts(adapterList); Data.lastUpdateAccountCount.postValue(adapterList.size() - 1); })); } + private void removeZeroAccounts(List list) { + boolean removed = true; + + while (removed) { + AccountListItem last = null; + removed = false; + List 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); + } + } } diff --git a/app/src/main/java/net/ktnx/mobileledger/ui/new_transaction/NewTransactionModel.java b/app/src/main/java/net/ktnx/mobileledger/ui/new_transaction/NewTransactionModel.java index 81f1ed07..51bcd306 100644 --- a/app/src/main/java/net/ktnx/mobileledger/ui/new_transaction/NewTransactionModel.java +++ b/app/src/main/java/net/ktnx/mobileledger/ui/new_transaction/NewTransactionModel.java @@ -1,5 +1,5 @@ /* - * Copyright © 2021 Damyan Ivanov. + * Copyright © 2022 Damyan Ivanov. * This file is part of MoLe. * MoLe is free software: you can distribute it and/or modify it * under the term of the GNU General Public License as published by @@ -18,6 +18,7 @@ package net.ktnx.mobileledger.ui.new_transaction; import android.annotation.SuppressLint; +import android.os.Build; import android.text.TextUtils; import androidx.annotation.NonNull; @@ -443,14 +444,14 @@ public class NewTransactionModel extends ViewModel { LedgerTransaction tr = head.asLedgerTransaction(); tr.setComment(head.getComment()); - LedgerTransactionAccount emptyAmountAccount = null; - float emptyAmountAccountBalance = 0; + HashMap> emptyAmountAccounts = new HashMap<>(); + HashMap emptyAmountAccountBalance = new HashMap<>(); for (int i = 1; i < list.size(); i++) { TransactionAccount item = list.get(i) .toTransactionAccount(); + String currency = item.getCurrency(); LedgerTransactionAccount acc = new LedgerTransactionAccount(item.getAccountName() - .trim(), - item.getCurrency()); + .trim(), currency); if (acc.getAccountName() .isEmpty()) continue; @@ -459,17 +460,53 @@ public class NewTransactionModel extends ViewModel { if (item.isAmountSet()) { acc.setAmount(item.getAmount()); - emptyAmountAccountBalance += item.getAmount(); + Float emptyCurrBalance = emptyAmountAccountBalance.get(currency); + if (emptyCurrBalance == null) { + emptyAmountAccountBalance.put(currency, item.getAmount()); + } + else { + emptyAmountAccountBalance.put(currency, emptyCurrBalance + item.getAmount()); + } } else { - emptyAmountAccount = acc; + List 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 accounts = + Objects.requireNonNull(emptyAmountAccounts.get(currency)); + final Float balance = emptyAmountAccountBalance.get(currency); + if (balance != null && !Misc.isZero(balance) && accounts.size() != 1) + throw new RuntimeException(String.format(Locale.US, + "Should not happen: approved transaction has %d accounts for " + + "currency %s", accounts.size(), currency)); + for (LedgerTransactionAccount acc : accounts) { + acc.setAmount(balance == null ? 0 : -balance); + } + } + } + } return tr; } @@ -496,6 +533,7 @@ public class NewTransactionModel extends ViewModel { int singleNegativeIndex = -1; int singlePositiveIndex = -1; int negativeCount = 0; + boolean hasCurrency = false; for (int i = 0; i < accounts.size(); i++) { LedgerTransactionAccount acc = accounts.get(i); TransactionAccount item = new TransactionAccount(acc.getAccountName(), @@ -525,6 +563,10 @@ public class NewTransactionModel extends ViewModel { } else item.resetAmount(); + + if (item.getCurrency() + .length() > 0) + hasCurrency = true; } if (BuildConfig.DEBUG) dumpItemList("Loaded previous transaction", newList); @@ -538,9 +580,12 @@ public class NewTransactionModel extends ViewModel { moveItemLast(newList, singlePositiveIndex); } + final boolean foundTransactionHasCurrency = hasCurrency; Misc.onMainThread(() -> { setItems(newList); noteFocusChanged(1, FocusedElement.Amount); + if (foundTransactionHasCurrency) + showCurrency.setValue(true); }); } /** @@ -730,8 +775,8 @@ public class NewTransactionModel extends ViewModel { !Misc.equalStrings(acc.getAmountHint(), hint)) { Logger.debug("submittable", - String.format("Setting amount hint of {%s} to %s [%s]", - acc.toString(), hint, balCurrency)); + String.format("Setting amount hint of {%s} to %s [%s]", acc, + hint, balCurrency)); acc.setAmountHint(hint); listChanged = true; } @@ -1026,7 +1071,7 @@ public class NewTransactionModel extends ViewModel { b.append(String.format(" '%s'", description)); if (date != null) - b.append(String.format("@%s", date.toString())); + b.append(String.format("@%s", date)); if (!TextUtils.isEmpty(comment)) b.append(String.format(" /%s/", comment)); @@ -1264,8 +1309,7 @@ public class NewTransactionModel extends ViewModel { equal = equal && Misc.equalStrings(currency, other.currency) && isLast == other.isLast; Logger.debug("new-trans", - String.format("Comparing {%s} and {%s}: %s", this.toString(), other.toString(), - equal)); + String.format("Comparing {%s} and {%s}: %s", this, other, equal)); return equal; } public int getAccountNameCursorPosition() { diff --git a/app/src/main/java/net/ktnx/mobileledger/ui/profiles/ProfileDetailFragment.java b/app/src/main/java/net/ktnx/mobileledger/ui/profiles/ProfileDetailFragment.java index ab0d07d9..99934543 100644 --- a/app/src/main/java/net/ktnx/mobileledger/ui/profiles/ProfileDetailFragment.java +++ b/app/src/main/java/net/ktnx/mobileledger/ui/profiles/ProfileDetailFragment.java @@ -341,6 +341,9 @@ public class ProfileDetailFragment extends Fragment { if (itemId == R.id.api_version_menu_html) { apiVer = API.html; } + else if (itemId == R.id.api_version_menu_1_23) { + apiVer = API.v1_23; + } else if (itemId == R.id.api_version_menu_1_19_1) { apiVer = API.v1_19_1; } diff --git a/app/src/main/java/net/ktnx/mobileledger/ui/templates/TemplateListDivider.java b/app/src/main/java/net/ktnx/mobileledger/ui/templates/TemplateListDivider.java index 9860e804..f841ad76 100644 --- a/app/src/main/java/net/ktnx/mobileledger/ui/templates/TemplateListDivider.java +++ b/app/src/main/java/net/ktnx/mobileledger/ui/templates/TemplateListDivider.java @@ -1,5 +1,5 @@ /* - * Copyright © 2021 Damyan Ivanov. + * Copyright © 2022 Damyan Ivanov. * This file is part of MoLe. * MoLe is free software: you can distribute it and/or modify it * under the term of the GNU General Public License as published by @@ -15,23 +15,6 @@ * along with MoLe. If not, see . */ -// -// Substantial portions taken from DividerItemDecoration subject to the following license terms: -// -// Copyright 2018 The Android Open Source Project -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// package net.ktnx.mobileledger.ui.templates; import android.content.Context; @@ -94,7 +77,8 @@ class TemplateListDivider extends DividerItemDecoration { for (int i = 0; i < childCount; i++) { final View child = parent.getChildAt(i); final int childAdapterPosition = parent.getChildAdapterPosition(child); - if (adapter.getItemViewType(childAdapterPosition) == + if (childAdapterPosition == RecyclerView.NO_POSITION || + adapter.getItemViewType(childAdapterPosition) == TemplatesRecyclerViewAdapter.ITEM_TYPE_DIVIDER || childAdapterPosition + 1 < itemCount && adapter.getItemViewType(childAdapterPosition + 1) == @@ -110,6 +94,10 @@ class TemplateListDivider extends DividerItemDecoration { } private void drawHorizontal(Canvas canvas, RecyclerView parent) { + final RecyclerView.LayoutManager layoutManager = parent.getLayoutManager(); + if (layoutManager == null) + return; + canvas.save(); final int top; final int bottom; @@ -133,14 +121,16 @@ class TemplateListDivider extends DividerItemDecoration { for (int i = 0; i < childCount; i++) { final View child = parent.getChildAt(i); final int childAdapterPosition = parent.getChildAdapterPosition(child); - if (adapter.getItemViewType(childAdapterPosition) == + if (childAdapterPosition == RecyclerView.NO_POSITION || + adapter.getItemViewType(childAdapterPosition) == TemplatesRecyclerViewAdapter.ITEM_TYPE_DIVIDER || childAdapterPosition + 1 < itemCount && adapter.getItemViewType(childAdapterPosition + 1) == TemplatesRecyclerViewAdapter.ITEM_TYPE_DIVIDER) + { continue; - parent.getLayoutManager() - .getDecoratedBoundsWithMargins(child, mBounds); + } + layoutManager.getDecoratedBoundsWithMargins(child, mBounds); final int right = mBounds.right + Math.round(child.getTranslationX()); final int left = right - divider.getIntrinsicWidth(); divider.setBounds(left, top, right, bottom); @@ -156,7 +146,8 @@ class TemplateListDivider extends DividerItemDecoration { (TemplatesRecyclerViewAdapter) Objects.requireNonNull(parent.getAdapter()); final int itemCount = adapter.getItemCount(); - if (adapter.getItemViewType(childAdapterPosition) == + if (childAdapterPosition == RecyclerView.NO_POSITION || + adapter.getItemViewType(childAdapterPosition) == TemplatesRecyclerViewAdapter.ITEM_TYPE_DIVIDER || childAdapterPosition + 1 < itemCount && adapter.getItemViewType(childAdapterPosition + 1) == diff --git a/app/src/main/java/net/ktnx/mobileledger/ui/templates/TemplatesActivity.java b/app/src/main/java/net/ktnx/mobileledger/ui/templates/TemplatesActivity.java index ed652879..3f9535ce 100644 --- a/app/src/main/java/net/ktnx/mobileledger/ui/templates/TemplatesActivity.java +++ b/app/src/main/java/net/ktnx/mobileledger/ui/templates/TemplatesActivity.java @@ -1,5 +1,5 @@ /* - * Copyright © 2021 Damyan Ivanov. + * Copyright © 2022 Damyan Ivanov. * This file is part of MoLe. * MoLe is free software: you can distribute it and/or modify it * under the term of the GNU General Public License as published by @@ -119,7 +119,7 @@ public class TemplatesActivity extends CrashReportingActivity public void onDuplicateTemplate(long id) { DB.get() .getTemplateDAO() - .duplicateTemplateWitAccounts(id, null); + .duplicateTemplateWithAccounts(id, null); } @Override public void onEditTemplate(Long id) { diff --git a/app/src/main/java/net/ktnx/mobileledger/ui/transaction_list/TransactionListAdapter.java b/app/src/main/java/net/ktnx/mobileledger/ui/transaction_list/TransactionListAdapter.java index 0c0ff6a7..bc463ed5 100644 --- a/app/src/main/java/net/ktnx/mobileledger/ui/transaction_list/TransactionListAdapter.java +++ b/app/src/main/java/net/ktnx/mobileledger/ui/transaction_list/TransactionListAdapter.java @@ -28,7 +28,6 @@ import androidx.recyclerview.widget.RecyclerView; import net.ktnx.mobileledger.databinding.LastUpdateLayoutBinding; import net.ktnx.mobileledger.databinding.TransactionDelimiterBinding; import net.ktnx.mobileledger.databinding.TransactionListRowBinding; -import net.ktnx.mobileledger.model.LedgerTransaction; import net.ktnx.mobileledger.model.TransactionListItem; import net.ktnx.mobileledger.utils.Logger; import net.ktnx.mobileledger.utils.Misc; @@ -75,7 +74,9 @@ public class TransactionListAdapter extends RecyclerView.Adapter themeWatch = new MutableLiveData<>(0); @@ -76,7 +76,7 @@ public class Colors { TypedValue tv = new TypedValue(); theme.resolveAttribute(R.attr.table_row_dark_bg, tv, true); tableRowDarkBG = tv.data; - theme.resolveAttribute(R.attr.colorPrimary, tv, true); + theme.resolveAttribute(androidx.appcompat.R.attr.colorPrimary, tv, true); primary = tv.data; if (themePrimaryColor.size() == 0) { @@ -84,7 +84,7 @@ public class Colors { Resources.Theme tmpTheme = theme.getResources() .newTheme(); tmpTheme.applyStyle(themeId, true); - tmpTheme.resolveAttribute(R.attr.colorPrimary, tv, false); + tmpTheme.resolveAttribute(androidx.appcompat.R.attr.colorPrimary, tv, false); themePrimaryColor.put(themeId, tv.data); } } @@ -180,7 +180,7 @@ public class Colors { huesSB.append(", "); huesSB.append(h); } - debug("profiles", String.format("used hues: %s", huesSB.toString())); + debug("profiles", String.format("used hues: %s", huesSB)); } hues.add(hues.get(0)); diff --git a/app/src/main/res/menu/account_list.xml b/app/src/main/res/menu/account_list.xml new file mode 100644 index 00000000..5a761610 --- /dev/null +++ b/app/src/main/res/menu/account_list.xml @@ -0,0 +1,32 @@ + + + + + + \ No newline at end of file diff --git a/app/src/main/res/menu/api_version.xml b/app/src/main/res/menu/api_version.xml index cdcb09f2..53da72fb 100644 --- a/app/src/main/res/menu/api_version.xml +++ b/app/src/main/res/menu/api_version.xml @@ -21,6 +21,10 @@ android:id="@+id/api_version_menu_auto" android:title="@string/api_auto" /> + - - - - diff --git a/app/src/main/res/xml/network_security_config.xml b/app/src/main/res/xml/network_security_config.xml index 223e12e8..c1da3227 100644 --- a/app/src/main/res/xml/network_security_config.xml +++ b/app/src/main/res/xml/network_security_config.xml @@ -1,5 +1,5 @@ - + + + + + + \ No newline at end of file diff --git a/build.gradle b/build.gradle index 38a6fe30..e9d71576 100644 --- a/build.gradle +++ b/build.gradle @@ -1,5 +1,5 @@ /* - * Copyright © 2021 Damyan Ivanov. + * Copyright © 2022 Damyan Ivanov. * This file is part of MoLe. * MoLe is free software: you can distribute it and/or modify it * under the term of the GNU General Public License as published by @@ -24,7 +24,7 @@ buildscript { mavenCentral() } dependencies { - classpath 'com.android.tools.build:gradle:4.2.0' + classpath 'com.android.tools.build:gradle:8.0.2' // NOTE: Do not place your application dependencies here; they belong diff --git a/gradle.properties b/gradle.properties index 048e816e..db96c772 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,5 +1,5 @@ # -# Copyright © 2019 Damyan Ivanov. +# Copyright © 2024 Damyan Ivanov. # This file is part of MoLe. # MoLe is free software: you can distribute it and/or modify it # under the term of the GNU General Public License as published by @@ -15,19 +15,25 @@ # along with MoLe. If not, see . # -# 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 diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index f498716b..855f89cc 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,21 @@ -#Sat Nov 28 08:24:27 EET 2020 +# +# Copyright © 2024 Damyan Ivanov. +# This file is part of MoLe. +# MoLe is free software: you can distribute it and/or modify it +# under the term of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your opinion), any later version. +# +# MoLe is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License terms for details. +# +# You should have received a copy of the GNU General Public License +# along with MoLe. If not, see . +# distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-6.7.1-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.0-bin.zip diff --git a/metadata/bg-BG/changelogs/48.txt b/metadata/bg-BG/changelogs/48.txt new file mode 100644 index 00000000..b92f6e0f --- /dev/null +++ b/metadata/bg-BG/changelogs/48.txt @@ -0,0 +1,4 @@ +* ИЗВЕСТНИ ПРОБЛЕМИ + + Несъвместимост с hledger-web 1.23+ +* ПОПРАВКИ + + поправено дописване на описанието при въвеждане на ново движение diff --git a/metadata/bg-BG/changelogs/49.txt b/metadata/bg-BG/changelogs/49.txt new file mode 100644 index 00000000..fbb1844a --- /dev/null +++ b/metadata/bg-BG/changelogs/49.txt @@ -0,0 +1,4 @@ +* НОВО + + Добавена поддръжка на hledger-web версия 1.23 +* ПОПРАВКИ + + Включване на поддържащ файл за работата на БД, пропъснат във версия 0.20.4 diff --git a/metadata/bg-BG/changelogs/50.txt b/metadata/bg-BG/changelogs/50.txt new file mode 100644 index 00000000..bdf0e80b --- /dev/null +++ b/metadata/bg-BG/changelogs/50.txt @@ -0,0 +1,4 @@ +* ПОПРАВКИ + + поддръжане на hledger-web 1.23 и при добавяне на нови движения + + поправена натрупана сума при добавяне на движение в миналото + + поправен срив при изпращане на ново движение без данни за суми diff --git a/metadata/bg-BG/changelogs/51.txt b/metadata/bg-BG/changelogs/51.txt new file mode 100644 index 00000000..0af92291 --- /dev/null +++ b/metadata/bg-BG/changelogs/51.txt @@ -0,0 +1,6 @@ +* ПОПРАВКИ + + отстранен срив при балансиране на трансакция с повече от една валута + + отстранен срив при дублиране на макет + + отстранен срив при зареждане на настройки от резервно копие +* ПОДОБРЕНИЯ + + ново движение: включване на полазването на валути при зареждане на предишни движение с валути diff --git a/metadata/bg-BG/changelogs/52.txt b/metadata/bg-BG/changelogs/52.txt new file mode 100644 index 00000000..9c8c8dbc --- /dev/null +++ b/metadata/bg-BG/changelogs/52.txt @@ -0,0 +1,5 @@ +* ПОПРАВКИ + + коригирани версии на gradle +* ДРУГИ + + обновени версии на множество библиотеки + + прицелване във версия 31 на платформата diff --git a/metadata/bg-BG/changelogs/53.txt b/metadata/bg-BG/changelogs/53.txt new file mode 100644 index 00000000..427dbb36 --- /dev/null +++ b/metadata/bg-BG/changelogs/53.txt @@ -0,0 +1,4 @@ +* ПОПРАВКИ + + отстранен проблем със съвместимостта с hledger-web 1.23+ при изпращане на нови транзакции. Благодарности на Faye Duxovni за поправката! + + поправен срив при изтриване на шаблони + + поправен рядък срив при изпращане на транзакции, съдържащи повече от една сметка без сума и нулев остатъчен баланс diff --git a/metadata/bg-BG/changelogs/54.txt b/metadata/bg-BG/changelogs/54.txt new file mode 100644 index 00000000..2e1dace0 --- /dev/null +++ b/metadata/bg-BG/changelogs/54.txt @@ -0,0 +1,2 @@ +* ПОПРАВКИ + + отстранен проблем с централизираното резервно копие на настройките diff --git a/metadata/bg-BG/changelogs/55.txt b/metadata/bg-BG/changelogs/55.txt new file mode 100644 index 00000000..208db922 --- /dev/null +++ b/metadata/bg-BG/changelogs/55.txt @@ -0,0 +1,2 @@ +* ПОПРАВКИ + + отстранен проблем при изпращане на транзакции към hledger-web 1.23+ diff --git a/metadata/bg-BG/changelogs/56.txt b/metadata/bg-BG/changelogs/56.txt new file mode 100644 index 00000000..218ba4a3 --- /dev/null +++ b/metadata/bg-BG/changelogs/56.txt @@ -0,0 +1,4 @@ +* ПОПРАВКИ + + Позволяване на потребителски сертификати в настройките за сигурността на мрежовите връзки +* ДРУГИ + + Обновена версия на gradle diff --git a/metadata/en-US/changelogs/48.txt b/metadata/en-US/changelogs/48.txt new file mode 100644 index 00000000..f0ce53d4 --- /dev/null +++ b/metadata/en-US/changelogs/48.txt @@ -0,0 +1,4 @@ +* KNOWN PROBLEMS + + Incompatibility with hledger-web 1.23+ +* FIXES + + fix auto-completion of transaction description diff --git a/metadata/en-US/changelogs/49.txt b/metadata/en-US/changelogs/49.txt new file mode 100644 index 00000000..422c2ce1 --- /dev/null +++ b/metadata/en-US/changelogs/49.txt @@ -0,0 +1,4 @@ +* NEW + + Add support for hledger-web 1.23 +* FIXES + + Ship database support file missed in v0.20.4 diff --git a/metadata/en-US/changelogs/50.txt b/metadata/en-US/changelogs/50.txt new file mode 100644 index 00000000..797fcc16 --- /dev/null +++ b/metadata/en-US/changelogs/50.txt @@ -0,0 +1,4 @@ +* FIXES + + add hledger-web 1.23 support when adding transactions too + + correct running total when a matching transaction is added in the past + + fix crash when sending transaction containing only empty amounts diff --git a/metadata/en-US/changelogs/51.txt b/metadata/en-US/changelogs/51.txt new file mode 100644 index 00000000..379834e8 --- /dev/null +++ b/metadata/en-US/changelogs/51.txt @@ -0,0 +1,6 @@ +* FIXES + + fix crash when auto-balancing multi currency transaction + + fix crash when duplicating template + + fix crash when restoring configuration backup +* IMPROVEMENTS + + new transaction: turn on commodity setting when loading previous transaction with commodities diff --git a/metadata/en-US/changelogs/52.txt b/metadata/en-US/changelogs/52.txt new file mode 100644 index 00000000..13f6c20c --- /dev/null +++ b/metadata/en-US/changelogs/52.txt @@ -0,0 +1,6 @@ +* FIXES + + sync gradle version requirements +* OTHERS + + bump version of several dependent libraries + + bump SDK version to 31 + + adjust deprecated constructor usage diff --git a/metadata/en-US/changelogs/53.txt b/metadata/en-US/changelogs/53.txt new file mode 100644 index 00000000..8e8601b5 --- /dev/null +++ b/metadata/en-US/changelogs/53.txt @@ -0,0 +1,4 @@ +* FIXES + + fix compatibility wuth hledger-web 1.23+ when submitting new transactions. Thanks to Faye Duxovni for the patch! + + fix a crash when deleting templates + + fix a rare crash when submitting transactions with multiple accounts with no amounts with zero remaining balance diff --git a/metadata/en-US/changelogs/54.txt b/metadata/en-US/changelogs/54.txt new file mode 100644 index 00000000..a2e3a20c --- /dev/null +++ b/metadata/en-US/changelogs/54.txt @@ -0,0 +1,2 @@ +* FIXES + + fix cloud backup diff --git a/metadata/en-US/changelogs/55.txt b/metadata/en-US/changelogs/55.txt new file mode 100644 index 00000000..3697ab50 --- /dev/null +++ b/metadata/en-US/changelogs/55.txt @@ -0,0 +1,2 @@ +* FIXES: + + fixed sending of transactions to hledger-web 1.23+ diff --git a/metadata/en-US/changelogs/56.txt b/metadata/en-US/changelogs/56.txt new file mode 100644 index 00000000..9b7d3ac4 --- /dev/null +++ b/metadata/en-US/changelogs/56.txt @@ -0,0 +1,4 @@ +* FIXES: + + allow user certificates in network security config +* OTHERS: + + bump gradle version