--- /dev/null
+{
+ "formatVersion": 1,
+ "database": {
+ "version": 63,
+ "identityHash": "3a9ba5043c6e9109219046e1e29e50e1",
+ "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, `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": false
+ },
+ {
+ "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, '3a9ba5043c6e9109219046e1e29e50e1')"
+ ]
+ }
+}
\ No newline at end of file
--- /dev/null
+{
+ "formatVersion": 1,
+ "database": {
+ "version": 64,
+ "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
android:roundIcon="@drawable/app_icon_round"
android:supportsRtl="true"
tools:ignore="GoogleAppIndexingWarning">
+ <activity
+ android:name=".BackupsActivity"
+ android:label="@string/backups_activity_label"
+ android:theme="@style/AppTheme.default" />
<activity
android:name=".ui.templates.TemplatesActivity"
android:label="@string/title_activity_templates"
--- /dev/null
+/*
+ * Copyright © 2021 Damyan Ivanov.
+ * This file is part of MoLe.
+ * MoLe is free software: you can distribute it and/or modify it
+ * under the term of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your opinion), any later version.
+ *
+ * MoLe is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License terms for details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with MoLe. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+package net.ktnx.mobileledger;
+
+import android.net.Uri;
+import android.os.Bundle;
+import android.view.MenuItem;
+import android.view.View;
+
+import androidx.activity.result.ActivityResultLauncher;
+import androidx.activity.result.contract.ActivityResultContracts;
+import androidx.appcompat.app.ActionBar;
+import androidx.appcompat.app.AppCompatActivity;
+
+import com.google.android.material.snackbar.BaseTransientBottomBar;
+import com.google.android.material.snackbar.Snackbar;
+
+import net.ktnx.mobileledger.async.ConfigReader;
+import net.ktnx.mobileledger.async.ConfigWriter;
+import net.ktnx.mobileledger.databinding.FragmentBackupsBinding;
+
+import java.io.IOException;
+import java.text.DateFormat;
+import java.text.SimpleDateFormat;
+import java.util.Date;
+import java.util.Locale;
+
+public class BackupsActivity extends AppCompatActivity {
+ private FragmentBackupsBinding b;
+ private ActivityResultLauncher<String> backupChooserLauncher;
+ private ActivityResultLauncher<String[]> restoreChooserLauncher;
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ b = FragmentBackupsBinding.inflate(getLayoutInflater());
+ setContentView(b.getRoot());
+
+ setSupportActionBar(b.toolbar);
+ // Show the Up button in the action bar.
+ ActionBar actionBar = getSupportActionBar();
+ if (actionBar != null) {
+ actionBar.setDisplayHomeAsUpEnabled(true);
+ }
+
+ b.backupButton.setOnClickListener(this::backupClicked);
+ b.restoreButton.setOnClickListener(this::restoreClicked);
+
+
+ backupChooserLauncher =
+ registerForActivityResult(new ActivityResultContracts.CreateDocument(),
+ this::storeConfig);
+ restoreChooserLauncher =
+ registerForActivityResult(new ActivityResultContracts.OpenDocument(),
+ this::readConfig);
+ }
+ @Override
+ public boolean onOptionsItemSelected(MenuItem item) {
+ if (item.getItemId() == android.R.id.home) {
+ finish();
+
+ return true;
+ }
+ return super.onOptionsItemSelected(item);
+ }
+ private void storeConfig(Uri result) {
+ if (result == null)
+ return;
+
+ try {
+ ConfigWriter saver =
+ new ConfigWriter(getBaseContext(), result, new ConfigWriter.OnErrorListener() {
+ @Override
+ public void error(Exception e) {
+ Snackbar.make(b.backupButton, e.toString(),
+ BaseTransientBottomBar.LENGTH_LONG)
+ .show();
+ }
+ }, new ConfigWriter.OnDoneListener() {
+ public void done() {
+ Snackbar.make(b.backupButton, R.string.config_saved,
+ Snackbar.LENGTH_LONG)
+ .show();
+ }
+ });
+ saver.start();
+ }
+ catch (IOException e) {
+ e.printStackTrace();
+ }
+
+ }
+ private void readConfig(Uri result) {
+ if (result == null)
+ return;
+
+ try {
+ ConfigReader reader =
+ new ConfigReader(getBaseContext(), result, new ConfigWriter.OnErrorListener() {
+ @Override
+ public void error(Exception e) {
+ Snackbar.make(b.backupButton, e.toString(),
+ BaseTransientBottomBar.LENGTH_LONG)
+ .show();
+ }
+ }, new ConfigReader.OnDoneListener() {
+ public void done() {
+ Snackbar.make(b.backupButton, R.string.config_restored,
+ Snackbar.LENGTH_LONG)
+ .show();
+ }
+ });
+ reader.start();
+ }
+ catch (IOException e) {
+ e.printStackTrace();
+ }
+
+ }
+ private void backupClicked(View view) {
+ final Date now = new Date();
+ DateFormat df = new SimpleDateFormat("y-MM-dd HH:mm", Locale.getDefault());
+ df.format(now);
+ backupChooserLauncher.launch(String.format("MoLe-%s.json", df.format(now)));
+ }
+ private void restoreClicked(View view) {
+ restoreChooserLauncher.launch(new String[]{"application/json"});
+ }
+
+}
\ No newline at end of file
--- /dev/null
+/*
+ * Copyright © 2021 Damyan Ivanov.
+ * This file is part of MoLe.
+ * MoLe is free software: you can distribute it and/or modify it
+ * under the term of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your opinion), any later version.
+ *
+ * MoLe is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License terms for details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with MoLe. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+package net.ktnx.mobileledger.async;
+
+import android.content.Context;
+import android.net.Uri;
+import android.os.ParcelFileDescriptor;
+import android.util.Log;
+
+import net.ktnx.mobileledger.utils.Misc;
+
+import java.io.FileNotFoundException;
+import java.io.IOException;
+
+abstract class ConfigIO extends Thread {
+ protected final OnErrorListener onErrorListener;
+ protected ParcelFileDescriptor pfd;
+ ConfigIO(Context context, Uri uri, OnErrorListener onErrorListener)
+ throws FileNotFoundException {
+ this.onErrorListener = onErrorListener;
+ pfd = context.getContentResolver()
+ .openFileDescriptor(uri, getStreamMode());
+
+ initStream();
+ }
+ abstract protected String getStreamMode();
+
+ abstract protected void initStream();
+
+ abstract protected void processStream() throws IOException;
+ @Override
+ public void run() {
+ try {
+ processStream();
+ }
+ catch (Exception e) {
+ Log.e("cfg-json", "Error processing settings as JSON", e);
+ if (onErrorListener != null)
+ Misc.onMainThread(() -> onErrorListener.error(e));
+ }
+ finally {
+ try {
+ pfd.close();
+ }
+ catch (Exception e) {
+ Log.e("cfg-json", "Error closing file descriptor", e);
+ }
+ }
+ }
+ protected static class Keys {
+ static final String ACCOUNTS = "accounts";
+ static final String AMOUNT = "amount";
+ static final String AMOUNT_GROUP = "amountGroup";
+ static final String API_VER = "apiVersion";
+ static final String AUTH_PASS = "authPass";
+ static final String AUTH_USER = "authUser";
+ static final String CAN_POST = "permitPosting";
+ static final String COLOUR = "colour";
+ static final String COMMENT = "comment";
+ static final String COMMENT_GROUP = "commentMatchGroup";
+ static final String COMMODITIES = "commodities";
+ static final String CURRENCY = "commodity";
+ static final String CURRENCY_GROUP = "commodityGroup";
+ static final String CURRENT_PROFILE = "currentProfile";
+ static final String DATE_DAY = "dateDay";
+ static final String DATE_DAY_GROUP = "dateDayMatchGroup";
+ static final String DATE_MONTH = "dateMonth";
+ static final String DATE_MONTH_GROUP = "dateMonthMatchGroup";
+ static final String DATE_YEAR = "dateYear";
+ static final String DATE_YEAR_GROUP = "dateYearMatchGroup";
+ static final String DEFAULT_COMMODITY = "defaultCommodity";
+ static final String FUTURE_DATES = "futureDates";
+ static final String HAS_GAP = "hasGap";
+ static final String IS_FALLBACK = "isFallback";
+ static final String NAME = "name";
+ static final String NAME_GROUP = "nameMatchGroup";
+ static final String NEGATE_AMOUNT = "negateAmount";
+ static final String POSITION = "position";
+ static final String PREF_ACCOUNT = "preferredAccountsFilter";
+ static final String PROFILES = "profiles";
+ static final String REGEX = "regex";
+ static final String SHOW_COMMENTS = "showCommentsByDefault";
+ static final String SHOW_COMMODITY = "showCommodityByDefault";
+ static final String TEMPLATES = "templates";
+ static final String TEST_TEXT = "testText";
+ static final String TRANSACTION = "description";
+ static final String TRANSACTION_GROUP = "descriptionMatchGroup";
+ static final String URL = "url";
+ static final String USE_AUTH = "useAuth";
+ static final String UUID = "uuid";
+ }
+
+ abstract static public class OnErrorListener {
+ public abstract void error(Exception e);
+ }
+}
--- /dev/null
+/*
+ * Copyright © 2021 Damyan Ivanov.
+ * This file is part of MoLe.
+ * MoLe is free software: you can distribute it and/or modify it
+ * under the term of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your opinion), any later version.
+ *
+ * MoLe is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License terms for details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with MoLe. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+package net.ktnx.mobileledger.async;
+
+import android.content.Context;
+import android.net.Uri;
+import android.util.JsonReader;
+import android.util.JsonToken;
+
+import net.ktnx.mobileledger.dao.CurrencyDAO;
+import net.ktnx.mobileledger.dao.ProfileDAO;
+import net.ktnx.mobileledger.dao.TemplateHeaderDAO;
+import net.ktnx.mobileledger.db.Currency;
+import net.ktnx.mobileledger.db.DB;
+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.Misc;
+
+import java.io.BufferedReader;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.util.ArrayList;
+import java.util.List;
+
+public class ConfigReader extends ConfigIO {
+ private final OnDoneListener onDoneListener;
+ private JsonReader r;
+ public ConfigReader(Context context, Uri uri, OnErrorListener onErrorListener,
+ OnDoneListener onDoneListener) throws FileNotFoundException {
+ super(context, uri, onErrorListener);
+
+ this.onDoneListener = onDoneListener;
+ }
+ @Override
+ protected String getStreamMode() {
+ return "r";
+ }
+ @Override
+ protected void initStream() {
+ r = new JsonReader(new BufferedReader(
+ new InputStreamReader(new FileInputStream(pfd.getFileDescriptor()))));
+ }
+ @Override
+ protected void processStream() throws IOException {
+ List<Currency> commodities = null;
+ List<Profile> profiles = null;
+ List<TemplateWithAccounts> templates = null;
+ String currentProfile = null;
+ r.beginObject();
+ while (r.hasNext()) {
+ String item = r.nextName();
+ switch (item) {
+ case Keys.COMMODITIES:
+ commodities = readCommodities(r);
+ break;
+ case Keys.PROFILES:
+ profiles = readProfiles(r);
+ break;
+ case Keys.TEMPLATES:
+ templates = readTemplates(r);
+ break;
+ case Keys.CURRENT_PROFILE:
+ currentProfile = r.nextString();
+ break;
+ default:
+ throw new RuntimeException("unexpected top-level item " + item);
+ }
+ }
+ r.endObject();
+
+ restoreCommodities(commodities);
+ restoreProfiles(profiles);
+ restoreTemplates(templates);
+
+ if (Data.getProfile() == null && currentProfile != null) {
+ Profile p = DB.get()
+ .getProfileDAO()
+ .getByUuidSync(currentProfile);
+ if (p != null)
+ Data.setCurrentProfile(p);
+ }
+
+ if (onDoneListener != null)
+ Misc.onMainThread(onDoneListener::done);
+ }
+ private void restoreTemplates(List<TemplateWithAccounts> list) {
+ if (list == null)
+ return;
+
+ TemplateHeaderDAO dao = DB.get()
+ .getTemplateDAO();
+
+ for (TemplateWithAccounts t : list) {
+ if (dao.getTemplateWithAccountsByUuidSync(t.header.getUuid()) == null)
+ dao.insertSync(t);
+ }
+ }
+ private void restoreProfiles(List<Profile> list) {
+ if (list == null)
+ return;
+
+ ProfileDAO dao = DB.get()
+ .getProfileDAO();
+
+ for (Profile p : list) {
+ if (dao.getByUuidSync(p.getUuid()) == null)
+ dao.insert(p);
+ }
+ }
+ private void restoreCommodities(List<Currency> list) {
+ if (list == null)
+ return;
+
+ CurrencyDAO dao = DB.get()
+ .getCurrencyDAO();
+
+ for (Currency c : list) {
+ if (dao.getByNameSync(c.getName()) == null)
+ dao.insert(c);
+ }
+ }
+ private TemplateAccount readTemplateAccount(JsonReader r) throws IOException {
+ r.beginObject();
+ TemplateAccount result = new TemplateAccount(0L, 0L, 0L);
+ while (r.peek() != JsonToken.END_OBJECT) {
+ String item = r.nextName();
+ switch (item) {
+ case Keys.NAME:
+ result.setAccountName(r.nextString());
+ break;
+ case Keys.NAME_GROUP:
+ result.setAccountNameMatchGroup(r.nextInt());
+ break;
+ case Keys.COMMENT:
+ result.setAccountComment(r.nextString());
+ break;
+ case Keys.COMMENT_GROUP:
+ result.setAccountCommentMatchGroup(r.nextInt());
+ break;
+ case Keys.AMOUNT:
+ result.setAmount((float) r.nextDouble());
+ break;
+ case Keys.AMOUNT_GROUP:
+ result.setAmountMatchGroup(r.nextInt());
+ break;
+ case Keys.NEGATE_AMOUNT:
+ result.setNegateAmount(r.nextBoolean());
+ break;
+ case Keys.CURRENCY:
+ result.setCurrency(r.nextLong());
+ break;
+ case Keys.CURRENCY_GROUP:
+ result.setCurrencyMatchGroup(r.nextInt());
+ break;
+
+ default:
+ throw new IllegalStateException("Unexpected template account item: " + item);
+ }
+ }
+ r.endObject();
+
+ return result;
+ }
+ private TemplateWithAccounts readTemplate(JsonReader r) throws IOException {
+ r.beginObject();
+ String name = null;
+ TemplateHeader t = new TemplateHeader(0L, "", "");
+ List<TemplateAccount> accounts = new ArrayList<>();
+
+ while (r.peek() != JsonToken.END_OBJECT) {
+ String item = r.nextName();
+ switch (item) {
+ case Keys.UUID:
+ t.setUuid(r.nextString());
+ break;
+ case Keys.NAME:
+ t.setName(r.nextString());
+ break;
+ case Keys.REGEX:
+ t.setRegularExpression(r.nextString());
+ break;
+ case Keys.TEST_TEXT:
+ t.setTestText(r.nextString());
+ break;
+ case Keys.DATE_YEAR:
+ t.setDateYear(r.nextInt());
+ break;
+ case Keys.DATE_YEAR_GROUP:
+ t.setDateYearMatchGroup(r.nextInt());
+ break;
+ case Keys.DATE_MONTH:
+ t.setDateMonth(r.nextInt());
+ break;
+ case Keys.DATE_MONTH_GROUP:
+ t.setDateMonthMatchGroup(r.nextInt());
+ break;
+ case Keys.DATE_DAY:
+ t.setDateDay(r.nextInt());
+ break;
+ case Keys.DATE_DAY_GROUP:
+ t.setDateDayMatchGroup(r.nextInt());
+ break;
+ case Keys.TRANSACTION:
+ t.setTransactionDescription(r.nextString());
+ break;
+ case Keys.TRANSACTION_GROUP:
+ t.setTransactionDescriptionMatchGroup(r.nextInt());
+ break;
+ case Keys.COMMENT:
+ t.setTransactionComment(r.nextString());
+ break;
+ case Keys.COMMENT_GROUP:
+ t.setTransactionCommentMatchGroup(r.nextInt());
+ break;
+ case Keys.IS_FALLBACK:
+ t.setFallback(r.nextBoolean());
+ break;
+ case Keys.ACCOUNTS:
+ r.beginArray();
+ while (r.peek() == JsonToken.BEGIN_OBJECT) {
+ accounts.add(readTemplateAccount(r));
+ }
+ r.endArray();
+ break;
+ default:
+ throw new RuntimeException("Unknown template header item: " + item);
+ }
+ }
+ r.endObject();
+
+ TemplateWithAccounts result = new TemplateWithAccounts();
+ result.header = t;
+ result.accounts = accounts;
+ return result;
+ }
+ private List<TemplateWithAccounts> readTemplates(JsonReader r) throws IOException {
+ List<TemplateWithAccounts> list = new ArrayList<>();
+
+ r.beginArray();
+ while (r.peek() == JsonToken.BEGIN_OBJECT) {
+ list.add(readTemplate(r));
+ }
+ r.endArray();
+
+ return list;
+ }
+ private List<Currency> readCommodities(JsonReader r) throws IOException {
+ List<Currency> list = new ArrayList<>();
+
+ r.beginArray();
+ while (r.peek() == JsonToken.BEGIN_OBJECT) {
+ Currency c = new Currency();
+
+ r.beginObject();
+ while (r.peek() != JsonToken.END_OBJECT) {
+ final String item = r.nextName();
+ switch (item) {
+ case Keys.NAME:
+ c.setName(r.nextString());
+ break;
+ case Keys.POSITION:
+ c.setPosition(r.nextString());
+ break;
+ case Keys.HAS_GAP:
+ c.setHasGap(r.nextBoolean());
+ break;
+ default:
+ throw new RuntimeException("Unknown commodity key: " + item);
+ }
+ }
+ r.endObject();
+
+ if (c.getName()
+ .isEmpty())
+ throw new RuntimeException("Missing commodity name");
+
+ list.add(c);
+ }
+ r.endArray();
+
+ return list;
+ }
+ private List<Profile> readProfiles(JsonReader r) throws IOException {
+ List<Profile> list = new ArrayList<>();
+ r.beginArray();
+ while (r.peek() == JsonToken.BEGIN_OBJECT) {
+ Profile p = new Profile();
+ r.beginObject();
+ while (r.peek() != JsonToken.END_OBJECT) {
+ String item = r.nextName();
+
+ switch (item) {
+ case Keys.UUID:
+ p.setUuid(r.nextString());
+ break;
+ case Keys.NAME:
+ p.setName(r.nextString());
+ break;
+ case Keys.URL:
+ p.setUrl(r.nextString());
+ break;
+ case Keys.USE_AUTH:
+ p.setUseAuthentication(r.nextBoolean());
+ break;
+ case Keys.AUTH_USER:
+ p.setAuthUser(r.nextString());
+ break;
+ case Keys.AUTH_PASS:
+ p.setAuthPassword(r.nextString());
+ break;
+ case Keys.API_VER:
+ p.setApiVersion(r.nextInt());
+ break;
+ case Keys.CAN_POST:
+ p.setPermitPosting(r.nextBoolean());
+ break;
+ case Keys.DEFAULT_COMMODITY:
+ p.setDefaultCommodity(r.nextString());
+ break;
+ case Keys.SHOW_COMMODITY:
+ p.setShowCommodityByDefault(r.nextBoolean());
+ break;
+ case Keys.SHOW_COMMENTS:
+ p.setShowCommentsByDefault(r.nextBoolean());
+ break;
+ case Keys.FUTURE_DATES:
+ p.setFutureDates(r.nextInt());
+ break;
+ case Keys.PREF_ACCOUNT:
+ p.setPreferredAccountsFilter(r.nextString());
+ break;
+ case Keys.COLOUR:
+ p.setTheme(r.nextInt());
+ break;
+
+
+ default:
+ throw new IllegalStateException("Unexpected profile item: " + item);
+ }
+ }
+ r.endObject();
+
+ list.add(p);
+ }
+ r.endArray();
+
+ return list;
+ }
+ abstract static public class OnDoneListener {
+ public abstract void done();
+ }
+}
--- /dev/null
+/*
+ * Copyright © 2021 Damyan Ivanov.
+ * This file is part of MoLe.
+ * MoLe is free software: you can distribute it and/or modify it
+ * under the term of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your opinion), any later version.
+ *
+ * MoLe is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License terms for details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with MoLe. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+package net.ktnx.mobileledger.async;
+
+import android.content.Context;
+import android.net.Uri;
+import android.util.JsonWriter;
+
+import net.ktnx.mobileledger.db.Currency;
+import net.ktnx.mobileledger.db.DB;
+import net.ktnx.mobileledger.db.Profile;
+import net.ktnx.mobileledger.db.TemplateAccount;
+import net.ktnx.mobileledger.db.TemplateWithAccounts;
+import net.ktnx.mobileledger.json.API;
+import net.ktnx.mobileledger.model.Data;
+import net.ktnx.mobileledger.utils.Misc;
+
+import java.io.BufferedWriter;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.OutputStreamWriter;
+import java.util.List;
+
+public class ConfigWriter extends ConfigIO {
+ private final OnDoneListener onDoneListener;
+ private JsonWriter w;
+ public ConfigWriter(Context context, Uri uri, OnErrorListener onErrorListener,
+ OnDoneListener onDoneListener) throws FileNotFoundException {
+ super(context, uri, onErrorListener);
+
+ this.onDoneListener = onDoneListener;
+ }
+ @Override
+ protected String getStreamMode() {
+ return "w";
+ }
+ @Override
+ protected void initStream() {
+ w = new JsonWriter(new BufferedWriter(
+ new OutputStreamWriter(new FileOutputStream(pfd.getFileDescriptor()))));
+ w.setIndent(" ");
+ }
+ @Override
+ protected void processStream() throws IOException {
+ w.beginObject();
+ writeCommodities(w);
+ writeProfiles(w);
+ writeCurrentProfile(w);
+ writeConfigTemplates(w);
+ w.endObject();
+ w.flush();
+
+ if (onDoneListener != null)
+ Misc.onMainThread(onDoneListener::done);
+ }
+ private void writeKey(JsonWriter w, String key, String value) throws IOException {
+ if (value != null)
+ w.name(key)
+ .value(value);
+ }
+ private void writeKey(JsonWriter w, String key, Integer value) throws IOException {
+ if (value != null)
+ w.name(key)
+ .value(value);
+ }
+ private void writeKey(JsonWriter w, String key, Long value) throws IOException {
+ if (value != null)
+ w.name(key)
+ .value(value);
+ }
+ private void writeKey(JsonWriter w, String key, Float value) throws IOException {
+ if (value != null)
+ w.name(key)
+ .value(value);
+ }
+ private void writeKey(JsonWriter w, String key, Boolean value) throws IOException {
+ if (value != null)
+ w.name(key)
+ .value(value);
+ }
+ private void writeConfigTemplates(JsonWriter w) throws IOException {
+ List<TemplateWithAccounts> templates = DB.get()
+ .getTemplateDAO()
+ .getAllTemplatesWithAccountsSync();
+
+ if (templates.isEmpty())
+ return;
+
+ w.name("templates")
+ .beginArray();
+ for (TemplateWithAccounts t : templates) {
+ w.name(Keys.UUID)
+ .value(t.header.getUuid());
+ w.name(Keys.NAME)
+ .value(t.header.getName());
+ w.name(Keys.REGEX)
+ .value(t.header.getRegularExpression());
+ writeKey(w, Keys.TEST_TEXT, t.header.getTestText());
+ writeKey(w, Keys.DATE_YEAR, t.header.getDateYear());
+ writeKey(w, Keys.DATE_YEAR_GROUP, t.header.getDateYearMatchGroup());
+ writeKey(w, Keys.DATE_MONTH, t.header.getDateMonth());
+ writeKey(w, Keys.DATE_MONTH_GROUP, t.header.getDateMonthMatchGroup());
+ writeKey(w, Keys.DATE_DAY, t.header.getDateDay());
+ writeKey(w, Keys.DATE_DAY_GROUP, t.header.getDateDayMatchGroup());
+ writeKey(w, Keys.TRANSACTION, t.header.getTransactionDescription());
+ writeKey(w, Keys.TRANSACTION_GROUP, t.header.getTransactionDescriptionMatchGroup());
+ writeKey(w, Keys.COMMENT, t.header.getTransactionComment());
+ writeKey(w, Keys.COMMENT_GROUP, t.header.getTransactionCommentMatchGroup());
+ w.name(Keys.IS_FALLBACK)
+ .value(t.header.isFallback());
+ if (t.accounts.size() > 0) {
+ w.name(Keys.ACCOUNTS)
+ .beginArray();
+ for (TemplateAccount a : t.accounts) {
+ writeKey(w, Keys.NAME, a.getAccountName());
+ writeKey(w, Keys.NAME_GROUP, a.getAccountNameMatchGroup());
+ writeKey(w, Keys.COMMENT, a.getAccountComment());
+ writeKey(w, Keys.COMMENT_GROUP, a.getAccountCommentMatchGroup());
+ writeKey(w, Keys.AMOUNT, a.getAmount());
+ writeKey(w, Keys.AMOUNT_GROUP, a.getAmountMatchGroup());
+ writeKey(w, Keys.NEGATE_AMOUNT, a.getNegateAmount());
+ writeKey(w, Keys.CURRENCY, a.getCurrency());
+ writeKey(w, Keys.CURRENCY_GROUP, a.getCurrencyMatchGroup());
+ }
+ w.endArray();
+ }
+ }
+ w.endArray();
+ }
+ private void writeCommodities(JsonWriter w) throws IOException {
+ List<Currency> list = DB.get()
+ .getCurrencyDAO()
+ .getAllSync();
+ if (list.isEmpty())
+ return;
+ w.name(Keys.COMMODITIES)
+ .beginArray();
+ for (Currency c : list) {
+ w.beginObject();
+ writeKey(w, Keys.NAME, c.getName());
+ writeKey(w, Keys.POSITION, c.getPosition());
+ writeKey(w, Keys.HAS_GAP, c.getHasGap());
+ w.endObject();
+ }
+ w.endArray();
+ }
+ private void writeProfiles(JsonWriter w) throws IOException {
+ List<Profile> profiles = DB.get()
+ .getProfileDAO()
+ .getAllOrderedSync();
+
+ if (profiles.isEmpty())
+ return;
+
+ w.name(Keys.PROFILES)
+ .beginArray();
+ for (Profile p : profiles) {
+ w.beginObject();
+
+ w.name(Keys.NAME)
+ .value(p.getName());
+ w.name(Keys.UUID)
+ .value(p.getUuid());
+ w.name(Keys.URL)
+ .value(p.getUrl());
+ w.name(Keys.USE_AUTH)
+ .value(p.useAuthentication());
+ if (p.useAuthentication()) {
+ w.name(Keys.AUTH_USER)
+ .value(p.getAuthUser());
+ w.name(Keys.AUTH_PASS)
+ .value(p.getAuthPassword());
+ }
+ if (p.getApiVersion() != API.auto.toInt())
+ w.name(Keys.API_VER)
+ .value(p.getApiVersion());
+ w.name(Keys.CAN_POST)
+ .value(p.permitPosting());
+ if (p.permitPosting()) {
+ String defaultCommodity = p.getDefaultCommodity();
+ if (!defaultCommodity.isEmpty())
+ w.name(Keys.DEFAULT_COMMODITY)
+ .value(defaultCommodity);
+ w.name(Keys.SHOW_COMMODITY)
+ .value(p.getShowCommodityByDefault());
+ w.name(Keys.SHOW_COMMENTS)
+ .value(p.getShowCommentsByDefault());
+ w.name(Keys.FUTURE_DATES)
+ .value(p.getFutureDates());
+ w.name(Keys.PREF_ACCOUNT)
+ .value(p.getPreferredAccountsFilter());
+ }
+ w.name(Keys.COLOUR)
+ .value(p.getTheme());
+
+ w.endObject();
+ }
+ w.endArray();
+ }
+ private void writeCurrentProfile(JsonWriter w) throws IOException {
+ Profile currentProfile = Data.getProfile();
+ if (currentProfile == null)
+ return;
+
+ w.name(Keys.CURRENT_PROFILE)
+ .value(currentProfile.getUuid());
+ }
+
+ abstract static public class OnDoneListener {
+ public abstract void done();
+ }
+}
@Query("SELECT * FROM currencies")
public abstract LiveData<List<Currency>> getAll();
+ @Query("SELECT * FROM currencies")
+ public abstract List<Currency> getAllSync();
+
@Query("SELECT * FROM currencies WHERE id = :id")
abstract LiveData<Currency> getById(long id);
@Query("SELECT * FROM currencies WHERE name = :name")
public abstract LiveData<Currency> getByName(String name);
+ @Query("SELECT * FROM currencies WHERE name = :name")
+ public abstract Currency getByNameSync(String name);
+
// not useful for now
// @Transaction
// @Query("SELECT * FROM patterns")
@Query("SELECT * FROM profiles LIMIT 1")
public abstract Profile getAnySync();
+ @Query("SELECT * FROM profiles WHERE uuid=:uuid")
+ public abstract LiveData<Profile> getByUuid(String uuid);
+
+ @Query("SELECT * FROM profiles WHERE uuid=:uuid")
+ public abstract Profile getByUuidSync(String uuid);
+
@Query("SELECT MAX(order_no) FROM profiles")
public abstract int getProfileCountSync();
public void updateOrderSync(List<Profile> list) {
@Query("SELECT * FROM templates WHERE id = :id")
public abstract LiveData<TemplateHeader> getTemplate(Long id);
+ @Query("SELECT * FROM templates WHERE id = :id")
+ public abstract TemplateHeader getTemplateSync(Long id);
+
public void getTemplateAsync(@NonNull Long id,
@NonNull AsyncResultCallback<TemplateHeader> callback) {
LiveData<TemplateHeader> resultReceiver = getTemplate(id);
@Query("SELECT * FROM templates WHERE id = :id")
public abstract TemplateWithAccounts getTemplateWithAccountsSync(@NonNull Long id);
+ @Transaction
+ @Query("SELECT * FROM templates WHERE uuid = :uuid")
+ public abstract TemplateWithAccounts getTemplateWithAccountsByUuidSync(String uuid);
+
+ @Transaction
+ @Query("SELECT * FROM templates")
+ public abstract List<TemplateWithAccounts> getAllTemplatesWithAccountsSync();
+
@Transaction
public void insertSync(TemplateWithAccounts templateWithAccounts) {
long template_id = insertSync(templateWithAccounts.header);
@NonNull
@ColumnInfo(name = "has_gap")
private Boolean hasGap;
+ public Currency() {
+ id = 0;
+ name = "";
+ position = "after";
+ hasGap = true;
+ }
public Currency(long id, @NonNull String name, @NonNull String position,
@NonNull Boolean hasGap) {
this.id = id;
import com.google.android.material.snackbar.Snackbar;
+import net.ktnx.mobileledger.BackupsActivity;
import net.ktnx.mobileledger.R;
import net.ktnx.mobileledger.async.RetrieveTransactionsTask;
import net.ktnx.mobileledger.async.TransactionAccumulator;
Data.backgroundTasksRunning.observe(this, this::onRetrieveRunningChanged);
if (barDrawerToggle == null) {
- barDrawerToggle = new ActionBarDrawerToggle(this, b.drawerLayout, b.toolbar,
- R.string.navigation_drawer_open, R.string.navigation_drawer_close);
+ barDrawerToggle = new ActionBarDrawerToggle(this, b.drawerLayout, b.toolbar, R.string.navigation_drawer_open, R.string.navigation_drawer_close);
b.drawerLayout.addDrawerListener(barDrawerToggle);
}
barDrawerToggle.syncState();
PackageInfo pi = getApplicationContext().getPackageManager()
.getPackageInfo(getPackageName(), 0);
((TextView) b.navUpper.findViewById(R.id.drawer_version_text)).setText(pi.versionName);
- ((TextView) b.noProfilesLayout.findViewById(R.id.drawer_version_text)).setText(
- pi.versionName);
+ ((TextView) b.noProfilesLayout.findViewById(R.id.drawer_version_text)).setText(pi.versionName);
}
catch (Exception e) {
e.printStackTrace();
b.navProfileList.setLayoutManager(llm);
b.navProfilesStartEdit.setOnClickListener((v) -> mProfileListAdapter.flipEditingProfiles());
- b.navProfilesCancelEdit.setOnClickListener(
- (v) -> mProfileListAdapter.flipEditingProfiles());
- b.navProfileListHeadButtons.setOnClickListener(
- (v) -> mProfileListAdapter.flipEditingProfiles());
+ b.navProfilesCancelEdit.setOnClickListener((v) -> mProfileListAdapter.flipEditingProfiles());
+ b.navProfileListHeadButtons.setOnClickListener((v) -> mProfileListAdapter.flipEditingProfiles());
if (drawerListener == null) {
drawerListener = new DrawerLayout.SimpleDrawerListener() {
@Override
b.navAccountSummary.setOnClickListener(this::onAccountSummaryClicked);
b.navLatestTransactions.setOnClickListener(this::onLatestTransactionsClicked);
b.navPatterns.setOnClickListener(this::onPatternsClick);
+ b.navBackupRestore.setOnClickListener(this::onBackupRestoreClick);
+ }
+ private void onBackupRestoreClick(View view) {
+ Intent intent = new Intent(this, BackupsActivity.class);
+ startActivity(intent);
}
private void onPatternsClick(View view) {
Intent intent = new Intent(this, TemplatesActivity.class);
--- /dev/null
+<!--
+ ~ Copyright Google Inc.
+ ~
+ ~ 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:
+ ~
+ ~ https://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distribution 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.
+ ~
+ ~ Modified/adapted by Damyan Ivanov for MoLe
+ -->
+
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:autoMirrored="true"
+ android:tint="?colorPrimary"
+ android:viewportWidth="24"
+ android:viewportHeight="24"
+ >
+ <path
+ android:fillColor="@android:color/white"
+ android:pathData="M19.35,10.04C18.67,6.59 15.64,4 12,4 9.11,4 6.6,5.64 5.35,8.04 2.34,8.36 0,10.91 0,14c0,3.31 2.69,6 6,6h13c2.76,0 5,-2.24 5,-5 0,-2.64 -2.05,-4.78 -4.65,-4.96zM14,13v4h-4v-4H7l5,-5 5,5h-3z"
+ />
+</vector>
--- /dev/null
+<!--
+ ~ Copyright Google Inc.
+ ~
+ ~ 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:
+ ~
+ ~ https://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distribution 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.
+ ~
+ ~ Modified/adapted by Damyan Ivanov for MoLe
+ -->
+
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:autoMirrored="true"
+ android:tint="?colorPrimary"
+ android:viewportWidth="24"
+ android:viewportHeight="24"
+ >
+ <path
+ android:fillColor="@android:color/white"
+ android:pathData="M9,3L5,6.99h3L8,14h2L10,6.99h3L9,3zM16,17.01L16,10h-2v7.01h-3L15,21l4,-3.99h-3z"
+ />
+</vector>
--- /dev/null
+<!--
+ ~ Copyright Google Inc.
+ ~
+ ~ 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:
+ ~
+ ~ https://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distribution 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.
+ ~
+ ~ Modified/adapted by Damyan Ivanov for MoLe
+ -->
+
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:autoMirrored="true"
+ android:tint="?colorPrimary"
+ android:viewportWidth="24"
+ android:viewportHeight="24"
+ >
+ <path
+ android:fillColor="@android:color/white"
+ android:pathData="M13,3c-4.97,0 -9,4.03 -9,9L1,12l3.89,3.89 0.07,0.14L9,12L6,12c0,-3.87 3.13,-7 7,-7s7,3.13 7,7 -3.13,7 -7,7c-1.93,0 -3.68,-0.79 -4.94,-2.06l-1.42,1.42C8.27,19.99 10.51,21 13,21c4.97,0 9,-4.03 9,-9s-4.03,-9 -9,-9zM12,8v5l4.28,2.54 0.72,-1.21 -3.5,-2.08L13.5,8L12,8z"
+ />
+</vector>
android:elevation="2dp"
android:orientation="vertical"
android:showDividers="beginning"
- android:visibility="gone"
+ android:visibility="visible"
app:layout_constraintBottom_toBottomOf="parent"
>
android:layout_weight="1"
android:text="@string/action_settings"
app:drawableStartCompat="@drawable/ic_settings_black_24dp"
+ android:visibility="gone"
+ />
+ <TextView
+ android:id="@+id/nav_backup_restore"
+ style="@style/nav_button"
+ android:layout_weight="1"
+ android:text="@string/action_import_export"
+ app:drawableStartCompat="@drawable/ic_baseline_backup_24"
/>
</LinearLayout>
--- /dev/null
+<?xml version="1.0" encoding="utf-8"?><!--
+ ~ Copyright © 2021 Damyan Ivanov.
+ ~ This file is part of MoLe.
+ ~ MoLe is free software: you can distribute it and/or modify it
+ ~ under the term of the GNU General Public License as published by
+ ~ the Free Software Foundation, either version 3 of the License, or
+ ~ (at your opinion), any later version.
+ ~
+ ~ MoLe is distributed in the hope that it will be useful,
+ ~ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ ~ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ ~ GNU General Public License terms for details.
+ ~
+ ~ You should have received a copy of the GNU General Public License
+ ~ along with MoLe. If not, see <https://www.gnu.org/licenses/>.
+ -->
+
+<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:app="http://schemas.android.com/apk/res-auto"
+ xmlns:tools="http://schemas.android.com/tools"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ tools:context=".BackupsActivity"
+ >
+ <com.google.android.material.appbar.AppBarLayout
+ android:id="@+id/appbar"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:fitsSystemWindows="true"
+ android:theme="@style/AppTheme.AppBarOverlay"
+ app:layout_constraintStart_toStartOf="parent"
+ app:layout_constraintTop_toTopOf="parent"
+ >
+ <androidx.appcompat.widget.Toolbar
+ android:id="@+id/toolbar"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:background="?attr/colorPrimary"
+ app:layout_collapseMode="pin"
+ app:popupTheme="@style/AppTheme.PopupOverlay"
+ />
+ </com.google.android.material.appbar.AppBarLayout>
+
+ <ScrollView
+ android:layout_width="0dp"
+ android:layout_height="0dp"
+ app:layout_constraintBottom_toBottomOf="parent"
+ app:layout_constraintEnd_toEndOf="parent"
+ app:layout_constraintStart_toStartOf="parent"
+ app:layout_constraintTop_toBottomOf="@id/appbar"
+ >
+ <androidx.constraintlayout.widget.ConstraintLayout
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_marginHorizontal="@dimen/activity_horizontal_margin"
+ >
+ <TextView
+ android:id="@+id/backup_header"
+ style="@style/TextAppearance.MaterialComponents.Headline4"
+ android:layout_width="0dp"
+ android:layout_height="wrap_content"
+ android:text="@string/backup_header"
+ app:layout_constraintEnd_toEndOf="parent"
+ app:layout_constraintStart_toStartOf="parent"
+ app:layout_constraintTop_toTopOf="parent"
+ />
+ <TextView
+ android:id="@+id/backup_explanation_text"
+ style="@style/TextAppearance.MaterialComponents.Body1"
+ android:layout_width="0dp"
+ android:layout_height="wrap_content"
+ android:text="@string/backup_explanation"
+ app:layout_constraintEnd_toEndOf="parent"
+ app:layout_constraintStart_toStartOf="parent"
+ app:layout_constraintTop_toBottomOf="@id/backup_header"
+ />
+ <TextView
+ android:id="@+id/backup_button"
+ style="@style/TextAppearance.MaterialComponents.Button"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_marginVertical="@dimen/text_margin"
+ android:drawablePadding="@dimen/text_margin"
+ android:gravity="center_vertical"
+ android:text="@string/backup_button_label"
+ app:drawableStartCompat="@drawable/ic_baseline_backup_24"
+ app:layout_constraintEnd_toEndOf="parent"
+ app:layout_constraintHorizontal_bias="1"
+ app:layout_constraintStart_toStartOf="parent"
+ app:layout_constraintTop_toBottomOf="@id/backup_explanation_text"
+ />
+ <TextView
+ android:id="@+id/restore_header"
+ style="@style/TextAppearance.MaterialComponents.Headline4"
+ android:layout_width="0dp"
+ android:layout_height="wrap_content"
+ android:layout_marginTop="@dimen/text_margin"
+ android:text="@string/restore_header"
+ app:layout_constraintEnd_toEndOf="parent"
+ app:layout_constraintStart_toStartOf="parent"
+ app:layout_constraintTop_toBottomOf="@id/backup_button"
+ />
+ <TextView
+ android:id="@+id/restore_explanation_text"
+ style="@style/TextAppearance.MaterialComponents.Body1"
+ android:layout_width="0dp"
+ android:layout_height="wrap_content"
+ android:text="@string/restore_explanation"
+ app:layout_constraintEnd_toEndOf="parent"
+ app:layout_constraintStart_toStartOf="parent"
+ app:layout_constraintTop_toBottomOf="@id/restore_header"
+ />
+ <TextView
+ android:id="@+id/restore_button"
+ style="@style/TextAppearance.MaterialComponents.Button"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_marginVertical="@dimen/text_margin"
+ android:drawablePadding="@dimen/text_margin"
+ android:gravity="center_vertical"
+ android:text="@string/restore_button_label"
+ app:drawableStartCompat="@drawable/ic_baseline_restore_24"
+ app:layout_constraintEnd_toEndOf="parent"
+ app:layout_constraintHorizontal_bias="1"
+ app:layout_constraintStart_toStartOf="parent"
+ app:layout_constraintTop_toBottomOf="@id/restore_explanation_text"
+ />
+ </androidx.constraintlayout.widget.ConstraintLayout>
+ </ScrollView>
+</androidx.constraintlayout.widget.ConstraintLayout>
\ No newline at end of file
<string name="template_details_template_params_label">Параметри на макета</string>
<string name="template_params_help_description">Помощна информация за пааметрите на макета</string>
<string name="account_currency_source_label">Източник на валутата</string>
+ <string name="action_import_export">Резервно копие</string>
+ <string name="backup_header">Резервно копие</string>
+ <string name="backup_button_label">Създаване</string>
+ <string name="restore_header">Възстановяване от резервно копие</string>
+ <string name="restore_button_label">Възстановяване</string>
+ <string name="backups_activity_label">Резервно копие</string>
+ <string name="backup_explanation">Записване на данните за профили и макети във файл във формат JSON, който може да се използва за възстановяване на настройките на друго устройство или след нулиране. Данните за трансакциите и сметките не се записват, а следва да бъдат изтеглени от сървъра при възстановяване на настройките.</string>
+ <string name="config_saved">Настройките са записани</string>
+ <string name="restore_explanation">Зареждане на настройките на профилите и макетите от резервно копие, създадено по-рано. Съществуващите записи не се променят. Ако искате да върнете някой запис (профил или макет) към състоянитето от резервното копие, първо го изтрийте.</string>
+ <string name="config_restored">Успешно възстановяване на настройките</string>
</resources>
<string name="template_details_template_params_label">Template parameters</string>
<string name="template_params_help_description">Show help on template parameters</string>
<string name="account_currency_source_label">Commodity source</string>
+ <string name="action_import_export">Backup/Restore</string>
+ <string name="backup_header">Backup</string>
+ <string name="backup_explanation">Exports all profile configuration and templates to a JSON file which can later be used to restore the settings on a different device or after a device reset. Data about transactions and accounts are not exported. Instead, these are supposed to be fetched from the remote backend when the configuration is restored.</string>
+ <string name="backup_button_label">Backup</string>
+ <string name="restore_header">Restore</string>
+ <string name="restore_explanation">Restores all profiles and templates from a previous backup. Entries that already exist are kept without changes. If you want to restore some entry to a previous state remove it first.</string>
+ <string name="restore_button_label">Restore</string>
+ <string name="config_saved">Configuration saved successfully</string>
+ <string name="backups_activity_label">Backup / Restore</string>
+ <string name="config_restored">Configuration restored successfully</string>
</resources>