feat: let the user name an account (#105)
This commit is contained in:
@@ -0,0 +1,553 @@
|
|||||||
|
{
|
||||||
|
"formatVersion": 1,
|
||||||
|
"database": {
|
||||||
|
"version": 4,
|
||||||
|
"identityHash": "47f3bedbfbf4ddd1db32864c718de83a",
|
||||||
|
"entities": [
|
||||||
|
{
|
||||||
|
"tableName": "accounts",
|
||||||
|
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `display_name` TEXT NOT NULL, `principal_url` TEXT, `home_set_url` TEXT, `username` TEXT, `last_sync_at` INTEGER, `last_sync_error` TEXT, `label` TEXT)",
|
||||||
|
"fields": [
|
||||||
|
{
|
||||||
|
"fieldPath": "id",
|
||||||
|
"columnName": "id",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "displayName",
|
||||||
|
"columnName": "display_name",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "principalUrl",
|
||||||
|
"columnName": "principal_url",
|
||||||
|
"affinity": "TEXT"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "homeSetUrl",
|
||||||
|
"columnName": "home_set_url",
|
||||||
|
"affinity": "TEXT"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "username",
|
||||||
|
"columnName": "username",
|
||||||
|
"affinity": "TEXT"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "lastSyncAt",
|
||||||
|
"columnName": "last_sync_at",
|
||||||
|
"affinity": "INTEGER"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "lastSyncError",
|
||||||
|
"columnName": "last_sync_error",
|
||||||
|
"affinity": "TEXT"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "label",
|
||||||
|
"columnName": "label",
|
||||||
|
"affinity": "TEXT"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"primaryKey": {
|
||||||
|
"autoGenerate": true,
|
||||||
|
"columnNames": [
|
||||||
|
"id"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"tableName": "task_lists",
|
||||||
|
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `name` TEXT NOT NULL, `color` INTEGER NOT NULL, `account_id` INTEGER, `is_visible` INTEGER NOT NULL DEFAULT 1, `is_synced` INTEGER NOT NULL DEFAULT 1, `owner` TEXT, `is_read_only` INTEGER NOT NULL DEFAULT 0, `sort_order` INTEGER NOT NULL DEFAULT 0, `href` TEXT, `ctag` TEXT, `sync_token` TEXT, `is_dirty` INTEGER NOT NULL DEFAULT 0, FOREIGN KEY(`account_id`) REFERENCES `accounts`(`id`) ON UPDATE NO ACTION ON DELETE SET NULL )",
|
||||||
|
"fields": [
|
||||||
|
{
|
||||||
|
"fieldPath": "id",
|
||||||
|
"columnName": "id",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "name",
|
||||||
|
"columnName": "name",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "color",
|
||||||
|
"columnName": "color",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "accountId",
|
||||||
|
"columnName": "account_id",
|
||||||
|
"affinity": "INTEGER"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "isVisible",
|
||||||
|
"columnName": "is_visible",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true,
|
||||||
|
"defaultValue": "1"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "isSynced",
|
||||||
|
"columnName": "is_synced",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true,
|
||||||
|
"defaultValue": "1"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "owner",
|
||||||
|
"columnName": "owner",
|
||||||
|
"affinity": "TEXT"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "isReadOnly",
|
||||||
|
"columnName": "is_read_only",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true,
|
||||||
|
"defaultValue": "0"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "sortOrder",
|
||||||
|
"columnName": "sort_order",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true,
|
||||||
|
"defaultValue": "0"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "href",
|
||||||
|
"columnName": "href",
|
||||||
|
"affinity": "TEXT"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "ctag",
|
||||||
|
"columnName": "ctag",
|
||||||
|
"affinity": "TEXT"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "syncToken",
|
||||||
|
"columnName": "sync_token",
|
||||||
|
"affinity": "TEXT"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "isDirty",
|
||||||
|
"columnName": "is_dirty",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true,
|
||||||
|
"defaultValue": "0"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"primaryKey": {
|
||||||
|
"autoGenerate": true,
|
||||||
|
"columnNames": [
|
||||||
|
"id"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"indices": [
|
||||||
|
{
|
||||||
|
"name": "index_task_lists_account_id",
|
||||||
|
"unique": false,
|
||||||
|
"columnNames": [
|
||||||
|
"account_id"
|
||||||
|
],
|
||||||
|
"orders": [],
|
||||||
|
"createSql": "CREATE INDEX IF NOT EXISTS `index_task_lists_account_id` ON `${TABLE_NAME}` (`account_id`)"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"foreignKeys": [
|
||||||
|
{
|
||||||
|
"table": "accounts",
|
||||||
|
"onDelete": "SET NULL",
|
||||||
|
"onUpdate": "NO ACTION",
|
||||||
|
"columns": [
|
||||||
|
"account_id"
|
||||||
|
],
|
||||||
|
"referencedColumns": [
|
||||||
|
"id"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"tableName": "tasks",
|
||||||
|
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `list_id` INTEGER NOT NULL, `uid` TEXT NOT NULL, `href` TEXT, `etag` TEXT, `title` TEXT, `description` TEXT, `location` TEXT, `url` TEXT, `color` INTEGER, `categories` TEXT NOT NULL DEFAULT '', `status` INTEGER NOT NULL DEFAULT 0, `percent_complete` INTEGER, `completed_at` INTEGER, `priority` INTEGER NOT NULL DEFAULT 0, `classification` INTEGER, `dtstart` INTEGER, `due` INTEGER, `duration` TEXT, `is_all_day` INTEGER NOT NULL DEFAULT 0, `timezone` TEXT, `rrule` TEXT, `rdate` TEXT, `exdate` TEXT, `recurrence_id` INTEGER, `master_id` INTEGER, `parent_id` INTEGER, `sort_order` INTEGER NOT NULL DEFAULT 0, `created_at` INTEGER, `last_modified` INTEGER, `sequence` INTEGER NOT NULL DEFAULT 0, `is_dirty` INTEGER NOT NULL DEFAULT 0, `is_deleted` INTEGER NOT NULL DEFAULT 0, `unknown_properties` TEXT, `reminders_off` INTEGER NOT NULL DEFAULT 0, FOREIGN KEY(`list_id`) REFERENCES `task_lists`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE , FOREIGN KEY(`master_id`) REFERENCES `tasks`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE , FOREIGN KEY(`parent_id`) REFERENCES `tasks`(`id`) ON UPDATE NO ACTION ON DELETE SET NULL )",
|
||||||
|
"fields": [
|
||||||
|
{
|
||||||
|
"fieldPath": "id",
|
||||||
|
"columnName": "id",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "listId",
|
||||||
|
"columnName": "list_id",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "uid",
|
||||||
|
"columnName": "uid",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "href",
|
||||||
|
"columnName": "href",
|
||||||
|
"affinity": "TEXT"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "etag",
|
||||||
|
"columnName": "etag",
|
||||||
|
"affinity": "TEXT"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "title",
|
||||||
|
"columnName": "title",
|
||||||
|
"affinity": "TEXT"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "description",
|
||||||
|
"columnName": "description",
|
||||||
|
"affinity": "TEXT"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "location",
|
||||||
|
"columnName": "location",
|
||||||
|
"affinity": "TEXT"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "url",
|
||||||
|
"columnName": "url",
|
||||||
|
"affinity": "TEXT"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "color",
|
||||||
|
"columnName": "color",
|
||||||
|
"affinity": "INTEGER"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "categories",
|
||||||
|
"columnName": "categories",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": true,
|
||||||
|
"defaultValue": "''"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "status",
|
||||||
|
"columnName": "status",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true,
|
||||||
|
"defaultValue": "0"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "percentComplete",
|
||||||
|
"columnName": "percent_complete",
|
||||||
|
"affinity": "INTEGER"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "completedAt",
|
||||||
|
"columnName": "completed_at",
|
||||||
|
"affinity": "INTEGER"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "priority",
|
||||||
|
"columnName": "priority",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true,
|
||||||
|
"defaultValue": "0"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "classification",
|
||||||
|
"columnName": "classification",
|
||||||
|
"affinity": "INTEGER"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "dtstart",
|
||||||
|
"columnName": "dtstart",
|
||||||
|
"affinity": "INTEGER"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "due",
|
||||||
|
"columnName": "due",
|
||||||
|
"affinity": "INTEGER"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "duration",
|
||||||
|
"columnName": "duration",
|
||||||
|
"affinity": "TEXT"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "isAllDay",
|
||||||
|
"columnName": "is_all_day",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true,
|
||||||
|
"defaultValue": "0"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "timezone",
|
||||||
|
"columnName": "timezone",
|
||||||
|
"affinity": "TEXT"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "rrule",
|
||||||
|
"columnName": "rrule",
|
||||||
|
"affinity": "TEXT"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "rdate",
|
||||||
|
"columnName": "rdate",
|
||||||
|
"affinity": "TEXT"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "exdate",
|
||||||
|
"columnName": "exdate",
|
||||||
|
"affinity": "TEXT"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "recurrenceId",
|
||||||
|
"columnName": "recurrence_id",
|
||||||
|
"affinity": "INTEGER"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "masterId",
|
||||||
|
"columnName": "master_id",
|
||||||
|
"affinity": "INTEGER"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "parentId",
|
||||||
|
"columnName": "parent_id",
|
||||||
|
"affinity": "INTEGER"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "sortOrder",
|
||||||
|
"columnName": "sort_order",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true,
|
||||||
|
"defaultValue": "0"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "createdAt",
|
||||||
|
"columnName": "created_at",
|
||||||
|
"affinity": "INTEGER"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "lastModified",
|
||||||
|
"columnName": "last_modified",
|
||||||
|
"affinity": "INTEGER"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "sequence",
|
||||||
|
"columnName": "sequence",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true,
|
||||||
|
"defaultValue": "0"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "isDirty",
|
||||||
|
"columnName": "is_dirty",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true,
|
||||||
|
"defaultValue": "0"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "isDeleted",
|
||||||
|
"columnName": "is_deleted",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true,
|
||||||
|
"defaultValue": "0"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "unknownProperties",
|
||||||
|
"columnName": "unknown_properties",
|
||||||
|
"affinity": "TEXT"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "remindersOff",
|
||||||
|
"columnName": "reminders_off",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true,
|
||||||
|
"defaultValue": "0"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"primaryKey": {
|
||||||
|
"autoGenerate": true,
|
||||||
|
"columnNames": [
|
||||||
|
"id"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"indices": [
|
||||||
|
{
|
||||||
|
"name": "index_tasks_list_id_is_deleted",
|
||||||
|
"unique": false,
|
||||||
|
"columnNames": [
|
||||||
|
"list_id",
|
||||||
|
"is_deleted"
|
||||||
|
],
|
||||||
|
"orders": [],
|
||||||
|
"createSql": "CREATE INDEX IF NOT EXISTS `index_tasks_list_id_is_deleted` ON `${TABLE_NAME}` (`list_id`, `is_deleted`)"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "index_tasks_parent_id",
|
||||||
|
"unique": false,
|
||||||
|
"columnNames": [
|
||||||
|
"parent_id"
|
||||||
|
],
|
||||||
|
"orders": [],
|
||||||
|
"createSql": "CREATE INDEX IF NOT EXISTS `index_tasks_parent_id` ON `${TABLE_NAME}` (`parent_id`)"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "index_tasks_master_id_recurrence_id",
|
||||||
|
"unique": false,
|
||||||
|
"columnNames": [
|
||||||
|
"master_id",
|
||||||
|
"recurrence_id"
|
||||||
|
],
|
||||||
|
"orders": [],
|
||||||
|
"createSql": "CREATE INDEX IF NOT EXISTS `index_tasks_master_id_recurrence_id` ON `${TABLE_NAME}` (`master_id`, `recurrence_id`)"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "index_tasks_is_dirty",
|
||||||
|
"unique": false,
|
||||||
|
"columnNames": [
|
||||||
|
"is_dirty"
|
||||||
|
],
|
||||||
|
"orders": [],
|
||||||
|
"createSql": "CREATE INDEX IF NOT EXISTS `index_tasks_is_dirty` ON `${TABLE_NAME}` (`is_dirty`)"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "index_tasks_list_id_uid_recurrence_id",
|
||||||
|
"unique": true,
|
||||||
|
"columnNames": [
|
||||||
|
"list_id",
|
||||||
|
"uid",
|
||||||
|
"recurrence_id"
|
||||||
|
],
|
||||||
|
"orders": [],
|
||||||
|
"createSql": "CREATE UNIQUE INDEX IF NOT EXISTS `index_tasks_list_id_uid_recurrence_id` ON `${TABLE_NAME}` (`list_id`, `uid`, `recurrence_id`)"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"foreignKeys": [
|
||||||
|
{
|
||||||
|
"table": "task_lists",
|
||||||
|
"onDelete": "CASCADE",
|
||||||
|
"onUpdate": "NO ACTION",
|
||||||
|
"columns": [
|
||||||
|
"list_id"
|
||||||
|
],
|
||||||
|
"referencedColumns": [
|
||||||
|
"id"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"table": "tasks",
|
||||||
|
"onDelete": "CASCADE",
|
||||||
|
"onUpdate": "NO ACTION",
|
||||||
|
"columns": [
|
||||||
|
"master_id"
|
||||||
|
],
|
||||||
|
"referencedColumns": [
|
||||||
|
"id"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"table": "tasks",
|
||||||
|
"onDelete": "SET NULL",
|
||||||
|
"onUpdate": "NO ACTION",
|
||||||
|
"columns": [
|
||||||
|
"parent_id"
|
||||||
|
],
|
||||||
|
"referencedColumns": [
|
||||||
|
"id"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"tableName": "task_alarms",
|
||||||
|
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `task_id` INTEGER NOT NULL, `minutes_before` INTEGER NOT NULL, `reference` TEXT NOT NULL DEFAULT 'DUE', `message` TEXT, `uid` TEXT NOT NULL DEFAULT '', `raw` TEXT, FOREIGN KEY(`task_id`) REFERENCES `tasks`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE )",
|
||||||
|
"fields": [
|
||||||
|
{
|
||||||
|
"fieldPath": "id",
|
||||||
|
"columnName": "id",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "taskId",
|
||||||
|
"columnName": "task_id",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "minutesBefore",
|
||||||
|
"columnName": "minutes_before",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "reference",
|
||||||
|
"columnName": "reference",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": true,
|
||||||
|
"defaultValue": "'DUE'"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "message",
|
||||||
|
"columnName": "message",
|
||||||
|
"affinity": "TEXT"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "uid",
|
||||||
|
"columnName": "uid",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": true,
|
||||||
|
"defaultValue": "''"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "raw",
|
||||||
|
"columnName": "raw",
|
||||||
|
"affinity": "TEXT"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"primaryKey": {
|
||||||
|
"autoGenerate": true,
|
||||||
|
"columnNames": [
|
||||||
|
"id"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"indices": [
|
||||||
|
{
|
||||||
|
"name": "index_task_alarms_task_id",
|
||||||
|
"unique": false,
|
||||||
|
"columnNames": [
|
||||||
|
"task_id"
|
||||||
|
],
|
||||||
|
"orders": [],
|
||||||
|
"createSql": "CREATE INDEX IF NOT EXISTS `index_task_alarms_task_id` ON `${TABLE_NAME}` (`task_id`)"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"foreignKeys": [
|
||||||
|
{
|
||||||
|
"table": "tasks",
|
||||||
|
"onDelete": "CASCADE",
|
||||||
|
"onUpdate": "NO ACTION",
|
||||||
|
"columns": [
|
||||||
|
"task_id"
|
||||||
|
],
|
||||||
|
"referencedColumns": [
|
||||||
|
"id"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"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, '47f3bedbfbf4ddd1db32864c718de83a')"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
+15
@@ -147,6 +147,21 @@ class TasksDatabaseMigrationTest {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun addsAnEmptyAccountLabel() {
|
||||||
|
helper.createDatabase(TEST_DB, 3).use { db ->
|
||||||
|
db.execSQL("INSERT INTO accounts (id, display_name) VALUES (1, 'me@example.org')")
|
||||||
|
}
|
||||||
|
|
||||||
|
helper.runMigrationsAndValidate(TEST_DB, 4, true, TasksDatabase.MIGRATION_3_4).use { db ->
|
||||||
|
db.query("SELECT display_name, label FROM accounts").use { c ->
|
||||||
|
assertThat(c.moveToFirst()).isTrue()
|
||||||
|
assertThat(c.getString(0)).isEqualTo("me@example.org")
|
||||||
|
assertThat(c.isNull(1)).isTrue()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private companion object {
|
private companion object {
|
||||||
const val TEST_DB = "migration-test.db"
|
const val TEST_DB = "migration-test.db"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ import dagger.hilt.components.SingletonComponent
|
|||||||
import dagger.multibindings.IntoSet
|
import dagger.multibindings.IntoSet
|
||||||
import de.jeanlucmakiola.agendula.data.sync.AccountCreator
|
import de.jeanlucmakiola.agendula.data.sync.AccountCreator
|
||||||
import de.jeanlucmakiola.agendula.data.sync.AccountRepository
|
import de.jeanlucmakiola.agendula.data.sync.AccountRepository
|
||||||
|
import de.jeanlucmakiola.agendula.data.sync.AccountSyncRequests
|
||||||
import de.jeanlucmakiola.agendula.data.sync.CalDavGateway
|
import de.jeanlucmakiola.agendula.data.sync.CalDavGateway
|
||||||
import de.jeanlucmakiola.agendula.data.sync.LoginFlowRecord
|
import de.jeanlucmakiola.agendula.data.sync.LoginFlowRecord
|
||||||
import de.jeanlucmakiola.agendula.data.sync.OkHttpCalDavGateway
|
import de.jeanlucmakiola.agendula.data.sync.OkHttpCalDavGateway
|
||||||
@@ -79,7 +80,7 @@ abstract class SyncBindModule {
|
|||||||
|
|
||||||
@Binds
|
@Binds
|
||||||
@Singleton
|
@Singleton
|
||||||
abstract fun bindSyncRequests(impl: SyncTrigger): SyncRequests
|
abstract fun bindSyncRequests(impl: AccountSyncRequests): SyncRequests
|
||||||
|
|
||||||
@Binds
|
@Binds
|
||||||
@IntoSet
|
@IntoSet
|
||||||
|
|||||||
@@ -97,6 +97,11 @@ class AccountRepository @Inject constructor(
|
|||||||
/** The accounts, observed, so a sync landing updates a screen that is open. */
|
/** The accounts, observed, so a sync landing updates a screen that is open. */
|
||||||
fun observeAll(): Flow<List<AccountEntity>> = database.accounts().observeAll()
|
fun observeAll(): Flow<List<AccountEntity>> = database.accounts().observeAll()
|
||||||
|
|
||||||
|
/** Names the account on screen; blank goes back to the default (#105). */
|
||||||
|
suspend fun setLabel(accountId: Long, label: String) = withContext(io) {
|
||||||
|
database.accounts().setLabel(accountId, label.trim().ifEmpty { null })
|
||||||
|
}
|
||||||
|
|
||||||
/** Every account's synced lists. */
|
/** Every account's synced lists. */
|
||||||
fun observeSyncedLists(): Flow<List<TaskListEntity>> = database.taskLists().observeSynced()
|
fun observeSyncedLists(): Flow<List<TaskListEntity>> = database.taskLists().observeSynced()
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,20 @@
|
|||||||
|
package de.jeanlucmakiola.agendula.data.sync
|
||||||
|
|
||||||
|
import de.jeanlucmakiola.agendula.data.di.IoDispatcher
|
||||||
|
import de.jeanlucmakiola.agendula.data.tasks.room.TasksDatabase
|
||||||
|
import kotlinx.coroutines.CoroutineDispatcher
|
||||||
|
import kotlinx.coroutines.withContext
|
||||||
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
/** [SyncRequests] by account id; [SyncTrigger] keys on the account's stable display name. */
|
||||||
|
class AccountSyncRequests @Inject constructor(
|
||||||
|
private val database: TasksDatabase,
|
||||||
|
private val trigger: SyncTrigger,
|
||||||
|
@IoDispatcher private val io: CoroutineDispatcher,
|
||||||
|
) : SyncRequests {
|
||||||
|
|
||||||
|
override suspend fun syncNow(accountId: Long) {
|
||||||
|
val name = withContext(io) { database.accounts().account(accountId)?.displayName } ?: return
|
||||||
|
trigger.enqueue(name)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -35,11 +35,7 @@ import kotlin.time.Duration.Companion.seconds
|
|||||||
@Singleton
|
@Singleton
|
||||||
class SyncTrigger @Inject constructor(
|
class SyncTrigger @Inject constructor(
|
||||||
@ApplicationContext private val context: Context,
|
@ApplicationContext private val context: Context,
|
||||||
) : SyncRequests {
|
) {
|
||||||
|
|
||||||
override fun syncNow(accountName: String) {
|
|
||||||
enqueue(accountName)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Starts a sync now.
|
* Starts a sync now.
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ import androidx.compose.material.icons.rounded.Check
|
|||||||
import androidx.compose.material.icons.rounded.Bolt
|
import androidx.compose.material.icons.rounded.Bolt
|
||||||
import androidx.compose.material.icons.rounded.CloudSync
|
import androidx.compose.material.icons.rounded.CloudSync
|
||||||
import androidx.compose.material.icons.rounded.DeleteOutline
|
import androidx.compose.material.icons.rounded.DeleteOutline
|
||||||
|
import androidx.compose.material.icons.rounded.Edit
|
||||||
import androidx.compose.material.icons.rounded.History
|
import androidx.compose.material.icons.rounded.History
|
||||||
import androidx.compose.material.icons.rounded.HistoryToggleOff
|
import androidx.compose.material.icons.rounded.HistoryToggleOff
|
||||||
import androidx.compose.material.icons.rounded.Inventory2
|
import androidx.compose.material.icons.rounded.Inventory2
|
||||||
@@ -26,6 +27,7 @@ import androidx.compose.material3.Icon
|
|||||||
import androidx.compose.material3.IconButton
|
import androidx.compose.material3.IconButton
|
||||||
import androidx.compose.material3.MaterialTheme
|
import androidx.compose.material3.MaterialTheme
|
||||||
import androidx.compose.material3.Text
|
import androidx.compose.material3.Text
|
||||||
|
import androidx.compose.material3.TextButton
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.LaunchedEffect
|
import androidx.compose.runtime.LaunchedEffect
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
@@ -46,9 +48,11 @@ import de.jeanlucmakiola.agendula.data.sync.DiscardedEdit
|
|||||||
import de.jeanlucmakiola.agendula.data.sync.SyncNotice
|
import de.jeanlucmakiola.agendula.data.sync.SyncNotice
|
||||||
import de.jeanlucmakiola.agendula.data.tasks.room.AccountEntity
|
import de.jeanlucmakiola.agendula.data.tasks.room.AccountEntity
|
||||||
import de.jeanlucmakiola.agendula.ui.accounts.add.message
|
import de.jeanlucmakiola.agendula.ui.accounts.add.message
|
||||||
|
import de.jeanlucmakiola.agendula.ui.common.NameField
|
||||||
import de.jeanlucmakiola.agendula.ui.common.OnResume
|
import de.jeanlucmakiola.agendula.ui.common.OnResume
|
||||||
import de.jeanlucmakiola.floret.components.CollapsingScaffold
|
import de.jeanlucmakiola.floret.components.CollapsingScaffold
|
||||||
import de.jeanlucmakiola.floret.components.FullScreenPicker
|
import de.jeanlucmakiola.floret.components.FullScreenPicker
|
||||||
|
import de.jeanlucmakiola.floret.components.GroupedHint
|
||||||
import de.jeanlucmakiola.floret.components.GroupedListInset
|
import de.jeanlucmakiola.floret.components.GroupedListInset
|
||||||
import de.jeanlucmakiola.floret.components.GroupedRow
|
import de.jeanlucmakiola.floret.components.GroupedRow
|
||||||
import de.jeanlucmakiola.floret.components.Position
|
import de.jeanlucmakiola.floret.components.Position
|
||||||
@@ -90,12 +94,34 @@ internal fun AccountDetailScreen(
|
|||||||
// The confirmation sheet. The removal it starts outlives this screen, and is
|
// The confirmation sheet. The removal it starts outlives this screen, and is
|
||||||
// shown on the accounts list rather than here.
|
// shown on the accounts list rather than here.
|
||||||
var confirming by remember { mutableStateOf(false) }
|
var confirming by remember { mutableStateOf(false) }
|
||||||
|
var renaming by remember { mutableStateOf(false) }
|
||||||
|
|
||||||
|
if (renaming) {
|
||||||
|
AccountNamePicker(
|
||||||
|
current = account.label.orEmpty(),
|
||||||
|
defaultTitle = account.defaultTitle(),
|
||||||
|
onSave = { name ->
|
||||||
|
viewModel.rename(account, name)
|
||||||
|
renaming = false
|
||||||
|
},
|
||||||
|
onDismiss = { renaming = false },
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
CollapsingScaffold(title = identity.title, onBack = onBack, predictiveBack = true, visible = visible) {
|
CollapsingScaffold(title = identity.title, onBack = onBack, predictiveBack = true, visible = visible) {
|
||||||
AccountHero(identity)
|
AccountHero(identity)
|
||||||
Spacer(Modifier.height(24.dp))
|
Spacer(Modifier.height(24.dp))
|
||||||
|
|
||||||
val actions = buildList<@Composable (Position) -> Unit> {
|
val actions = buildList<@Composable (Position) -> Unit> {
|
||||||
|
add { position ->
|
||||||
|
GroupedRow(
|
||||||
|
title = stringResource(R.string.accounts_name),
|
||||||
|
summary = identity.title,
|
||||||
|
position = position,
|
||||||
|
leading = { Icon(Icons.Rounded.Edit, contentDescription = null) },
|
||||||
|
onClick = { renaming = true },
|
||||||
|
)
|
||||||
|
}
|
||||||
add { position ->
|
add { position ->
|
||||||
GroupedRow(
|
GroupedRow(
|
||||||
title = stringResource(R.string.accounts_last_sync),
|
title = stringResource(R.string.accounts_last_sync),
|
||||||
@@ -480,3 +506,30 @@ private fun errorTitle(text: String): AnnotatedString {
|
|||||||
buildAnnotatedString { withStyle(SpanStyle(color = error)) { append(text) } }
|
buildAnnotatedString { withStyle(SpanStyle(color = error)) { append(text) } }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Names an account on screen (#105); left empty, it goes back to [defaultTitle]. */
|
||||||
|
@Composable
|
||||||
|
private fun AccountNamePicker(
|
||||||
|
current: String,
|
||||||
|
defaultTitle: String,
|
||||||
|
onSave: (String) -> Unit,
|
||||||
|
onDismiss: () -> Unit,
|
||||||
|
) {
|
||||||
|
var name by remember { mutableStateOf(current) }
|
||||||
|
FullScreenPicker(
|
||||||
|
title = stringResource(R.string.accounts_name),
|
||||||
|
onDismiss = onDismiss,
|
||||||
|
actions = {
|
||||||
|
TextButton(onClick = { onSave(name) }) { Text(stringResource(R.string.save)) }
|
||||||
|
},
|
||||||
|
) {
|
||||||
|
NameField(
|
||||||
|
name = name,
|
||||||
|
placeholder = defaultTitle,
|
||||||
|
autoFocus = true,
|
||||||
|
onNameChange = { name = it },
|
||||||
|
onImeAction = { onSave(name) },
|
||||||
|
)
|
||||||
|
GroupedHint(stringResource(R.string.accounts_name_hint, defaultTitle))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -44,6 +44,14 @@ internal data class AccountIdentity(
|
|||||||
val user: String?,
|
val user: String?,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
/** The title an account has without a [AccountEntity.label]. */
|
||||||
|
internal fun AccountEntity.defaultTitle(): String {
|
||||||
|
val url = principalUrl?.toHttpUrlOrNull()
|
||||||
|
val provider = url?.let { CalDavProvider.forPrincipal(it) }
|
||||||
|
val host = url?.host?.removePrefix("www.")
|
||||||
|
return if (provider?.hosted == true) provider.label else host ?: displayName
|
||||||
|
}
|
||||||
|
|
||||||
internal fun AccountEntity.identity(): AccountIdentity {
|
internal fun AccountEntity.identity(): AccountIdentity {
|
||||||
val url = principalUrl?.toHttpUrlOrNull()
|
val url = principalUrl?.toHttpUrlOrNull()
|
||||||
val provider = url?.let { CalDavProvider.forPrincipal(it) }
|
val provider = url?.let { CalDavProvider.forPrincipal(it) }
|
||||||
@@ -53,7 +61,7 @@ internal fun AccountEntity.identity(): AccountIdentity {
|
|||||||
provider = provider,
|
provider = provider,
|
||||||
// The fallback is the stored name: an account with no principal URL
|
// The fallback is the stored name: an account with no principal URL
|
||||||
// never got past discovery, so there is nothing better to call it.
|
// never got past discovery, so there is nothing better to call it.
|
||||||
title = if (hosted) provider.label else host ?: displayName,
|
title = label ?: defaultTitle(),
|
||||||
secondary = if (hosted) host else provider?.label,
|
secondary = if (hosted) host else provider?.label,
|
||||||
user = username?.takeIf { it.isNotBlank() },
|
user = username?.takeIf { it.isNotBlank() },
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -248,6 +248,10 @@ class AccountsViewModel @Inject constructor(
|
|||||||
syncTrigger.enqueue(account.displayName, expedited = true)
|
syncTrigger.enqueue(account.displayName, expedited = true)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun rename(account: AccountEntity, label: String) {
|
||||||
|
viewModelScope.launch { repository.setLabel(account.id, label) }
|
||||||
|
}
|
||||||
|
|
||||||
/** The user has read what the last sync changed. */
|
/** The user has read what the last sync changed. */
|
||||||
fun dismissNotices(account: AccountEntity) {
|
fun dismissNotices(account: AccountEntity) {
|
||||||
viewModelScope.launch { notices.dismiss(account.id) }
|
viewModelScope.launch { notices.dismiss(account.id) }
|
||||||
|
|||||||
@@ -89,7 +89,7 @@ object DataProvideModule {
|
|||||||
// backed-up `.db` does not. The backup rules carry all three files and
|
// backed-up `.db` does not. The backup rules carry all three files and
|
||||||
// the app checkpoints on ON_STOP.
|
// the app checkpoints on ON_STOP.
|
||||||
.setJournalMode(RoomDatabase.JournalMode.WRITE_AHEAD_LOGGING)
|
.setJournalMode(RoomDatabase.JournalMode.WRITE_AHEAD_LOGGING)
|
||||||
.addMigrations(TasksDatabase.MIGRATION_1_2, TasksDatabase.MIGRATION_2_3)
|
.addMigrations(TasksDatabase.MIGRATION_1_2, TasksDatabase.MIGRATION_2_3, TasksDatabase.MIGRATION_3_4)
|
||||||
.build()
|
.build()
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -2,5 +2,5 @@ package de.jeanlucmakiola.agendula.data.sync
|
|||||||
|
|
||||||
/** Asks for an account to be synced now. A no-op in the `offline` flavor. */
|
/** Asks for an account to be synced now. A no-op in the `offline` flavor. */
|
||||||
fun interface SyncRequests {
|
fun interface SyncRequests {
|
||||||
fun syncNow(accountName: String)
|
suspend fun syncNow(accountId: Long)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -37,6 +37,9 @@ interface AccountDao {
|
|||||||
@Query("UPDATE accounts SET last_sync_at = :at, last_sync_error = :error WHERE id = :accountId")
|
@Query("UPDATE accounts SET last_sync_at = :at, last_sync_error = :error WHERE id = :accountId")
|
||||||
fun recordSync(accountId: Long, at: Instant?, error: String?)
|
fun recordSync(accountId: Long, at: Instant?, error: String?)
|
||||||
|
|
||||||
|
@Query("UPDATE accounts SET label = :label WHERE id = :accountId")
|
||||||
|
fun setLabel(accountId: Long, label: String?)
|
||||||
|
|
||||||
@Query("DELETE FROM accounts WHERE id = :accountId")
|
@Query("DELETE FROM accounts WHERE id = :accountId")
|
||||||
fun delete(accountId: Long): Int
|
fun delete(accountId: Long): Int
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -26,6 +26,8 @@ data class AccountEntity(
|
|||||||
@ColumnInfo(name = "username") val username: String? = null,
|
@ColumnInfo(name = "username") val username: String? = null,
|
||||||
@ColumnInfo(name = "last_sync_at") val lastSyncAt: Instant? = null,
|
@ColumnInfo(name = "last_sync_at") val lastSyncAt: Instant? = null,
|
||||||
@ColumnInfo(name = "last_sync_error") val lastSyncError: String? = null,
|
@ColumnInfo(name = "last_sync_error") val lastSyncError: String? = null,
|
||||||
|
/** A name the user gave the account (#105); [displayName] stays the stable key. */
|
||||||
|
@ColumnInfo(name = "label") val label: String? = null,
|
||||||
)
|
)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ import androidx.room.Embedded
|
|||||||
data class TaskListRow(
|
data class TaskListRow(
|
||||||
@Embedded val list: TaskListEntity,
|
@Embedded val list: TaskListEntity,
|
||||||
@ColumnInfo(name = "account_display_name") val accountDisplayName: String?,
|
@ColumnInfo(name = "account_display_name") val accountDisplayName: String?,
|
||||||
|
@ColumnInfo(name = "account_label") val accountLabel: String? = null,
|
||||||
)
|
)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -23,5 +24,6 @@ data class TaskRow(
|
|||||||
@ColumnInfo(name = "list_name") val listName: String,
|
@ColumnInfo(name = "list_name") val listName: String,
|
||||||
@ColumnInfo(name = "list_color") val listColor: Int,
|
@ColumnInfo(name = "list_color") val listColor: Int,
|
||||||
@ColumnInfo(name = "account_display_name") val accountDisplayName: String?,
|
@ColumnInfo(name = "account_display_name") val accountDisplayName: String?,
|
||||||
|
@ColumnInfo(name = "account_label") val accountLabel: String? = null,
|
||||||
@ColumnInfo(name = "list_read_only") val listReadOnly: Boolean = false,
|
@ColumnInfo(name = "list_read_only") val listReadOnly: Boolean = false,
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ object RoomTaskMapper {
|
|||||||
color = row.list.color,
|
color = row.list.color,
|
||||||
// TaskList.accountName is non-null and the lists screen groups by it, so a
|
// TaskList.accountName is non-null and the lists screen groups by it, so a
|
||||||
// list with no account still has to report something to group under.
|
// list with no account still has to report something to group under.
|
||||||
accountName = row.accountDisplayName ?: LocalAccount.NAME,
|
accountName = row.accountLabel ?: row.accountDisplayName ?: LocalAccount.NAME,
|
||||||
accountType = if (row.list.accountId == null) LocalAccount.TYPE else CALDAV_ACCOUNT_TYPE,
|
accountType = if (row.list.accountId == null) LocalAccount.TYPE else CALDAV_ACCOUNT_TYPE,
|
||||||
isSynced = row.list.isSynced,
|
isSynced = row.list.isSynced,
|
||||||
isVisible = row.list.isVisible,
|
isVisible = row.list.isVisible,
|
||||||
@@ -61,7 +61,7 @@ object RoomTaskMapper {
|
|||||||
listColor = row.listColor,
|
listColor = row.listColor,
|
||||||
taskColor = row.task.color,
|
taskColor = row.task.color,
|
||||||
listName = row.listName,
|
listName = row.listName,
|
||||||
accountName = row.accountDisplayName ?: LocalAccount.NAME,
|
accountName = row.accountLabel ?: row.accountDisplayName ?: LocalAccount.NAME,
|
||||||
parentId = row.task.parentId,
|
parentId = row.task.parentId,
|
||||||
isRecurring = row.task.isRecurring,
|
isRecurring = row.task.isRecurring,
|
||||||
occurrenceStart = occurrenceStart,
|
occurrenceStart = occurrenceStart,
|
||||||
|
|||||||
@@ -29,7 +29,7 @@ interface TaskDao {
|
|||||||
@Query(
|
@Query(
|
||||||
"""
|
"""
|
||||||
SELECT t.*, l.name AS list_name, l.color AS list_color, l.is_read_only AS list_read_only,
|
SELECT t.*, l.name AS list_name, l.color AS list_color, l.is_read_only AS list_read_only,
|
||||||
a.display_name AS account_display_name
|
a.display_name AS account_display_name, a.label AS account_label
|
||||||
FROM tasks t
|
FROM tasks t
|
||||||
JOIN task_lists l ON l.id = t.list_id
|
JOIN task_lists l ON l.id = t.list_id
|
||||||
LEFT JOIN accounts a ON a.id = l.account_id
|
LEFT JOIN accounts a ON a.id = l.account_id
|
||||||
@@ -44,7 +44,7 @@ interface TaskDao {
|
|||||||
@Query(
|
@Query(
|
||||||
"""
|
"""
|
||||||
SELECT t.*, l.name AS list_name, l.color AS list_color, l.is_read_only AS list_read_only,
|
SELECT t.*, l.name AS list_name, l.color AS list_color, l.is_read_only AS list_read_only,
|
||||||
a.display_name AS account_display_name
|
a.display_name AS account_display_name, a.label AS account_label
|
||||||
FROM tasks t
|
FROM tasks t
|
||||||
JOIN task_lists l ON l.id = t.list_id
|
JOIN task_lists l ON l.id = t.list_id
|
||||||
LEFT JOIN accounts a ON a.id = l.account_id
|
LEFT JOIN accounts a ON a.id = l.account_id
|
||||||
@@ -56,7 +56,7 @@ interface TaskDao {
|
|||||||
@Query(
|
@Query(
|
||||||
"""
|
"""
|
||||||
SELECT t.*, l.name AS list_name, l.color AS list_color, l.is_read_only AS list_read_only,
|
SELECT t.*, l.name AS list_name, l.color AS list_color, l.is_read_only AS list_read_only,
|
||||||
a.display_name AS account_display_name
|
a.display_name AS account_display_name, a.label AS account_label
|
||||||
FROM tasks t
|
FROM tasks t
|
||||||
JOIN task_lists l ON l.id = t.list_id
|
JOIN task_lists l ON l.id = t.list_id
|
||||||
LEFT JOIN accounts a ON a.id = l.account_id
|
LEFT JOIN accounts a ON a.id = l.account_id
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ interface TaskListDao {
|
|||||||
|
|
||||||
@Query(
|
@Query(
|
||||||
"""
|
"""
|
||||||
SELECT l.*, a.display_name AS account_display_name
|
SELECT l.*, a.display_name AS account_display_name, a.label AS account_label
|
||||||
FROM task_lists l LEFT JOIN accounts a ON a.id = l.account_id
|
FROM task_lists l LEFT JOIN accounts a ON a.id = l.account_id
|
||||||
ORDER BY a.display_name, l.sort_order, l.name
|
ORDER BY a.display_name, l.sort_order, l.name
|
||||||
"""
|
"""
|
||||||
@@ -25,7 +25,7 @@ interface TaskListDao {
|
|||||||
|
|
||||||
@Query(
|
@Query(
|
||||||
"""
|
"""
|
||||||
SELECT l.*, a.display_name AS account_display_name
|
SELECT l.*, a.display_name AS account_display_name, a.label AS account_label
|
||||||
FROM task_lists l LEFT JOIN accounts a ON a.id = l.account_id
|
FROM task_lists l LEFT JOIN accounts a ON a.id = l.account_id
|
||||||
WHERE l.id = :listId
|
WHERE l.id = :listId
|
||||||
"""
|
"""
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ import kotlin.time.Instant
|
|||||||
TaskEntity::class,
|
TaskEntity::class,
|
||||||
TaskAlarmEntity::class,
|
TaskAlarmEntity::class,
|
||||||
],
|
],
|
||||||
version = 3,
|
version = 4,
|
||||||
exportSchema = true,
|
exportSchema = true,
|
||||||
)
|
)
|
||||||
@TypeConverters(Converters::class)
|
@TypeConverters(Converters::class)
|
||||||
@@ -173,5 +173,12 @@ abstract class TasksDatabase : RoomDatabase() {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** v4 lets the user name an account (#105). */
|
||||||
|
val MIGRATION_3_4 = object : Migration(3, 4) {
|
||||||
|
override fun migrate(db: SupportSQLiteDatabase) {
|
||||||
|
db.execSQL("ALTER TABLE accounts ADD COLUMN label TEXT")
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -161,7 +161,7 @@ class ImportViewModel @Inject constructor(
|
|||||||
val result = importer.import(ready.parsed, target)
|
val result = importer.import(ready.parsed, target)
|
||||||
(chosen as? ImportChoice.Existing)?.list
|
(chosen as? ImportChoice.Existing)?.list
|
||||||
?.takeIf { it.accountId != null && result.imported > 0 }
|
?.takeIf { it.accountId != null && result.imported > 0 }
|
||||||
?.let { runCatching { syncRequests.syncNow(it.accountName) } }
|
?.let { runCatching { syncRequests.syncNow(it.accountId!!) } }
|
||||||
ImportStep.Done(
|
ImportStep.Done(
|
||||||
listName = listName,
|
listName = listName,
|
||||||
imported = result.imported,
|
imported = result.imported,
|
||||||
|
|||||||
@@ -681,6 +681,8 @@
|
|||||||
<string name="accounts_remove_wipe_body">Also deletes this account\u2019s lists and tasks from this device.</string>
|
<string name="accounts_remove_wipe_body">Also deletes this account\u2019s lists and tasks from this device.</string>
|
||||||
<string name="accounts_never_synced">Never synced</string>
|
<string name="accounts_never_synced">Never synced</string>
|
||||||
<string name="accounts_last_sync">Last sync</string>
|
<string name="accounts_last_sync">Last sync</string>
|
||||||
|
<string name="accounts_name">Name</string>
|
||||||
|
<string name="accounts_name_hint">How this account is shown in Agendula. Leave it empty to use “%1$s”.</string>
|
||||||
<string name="accounts_generic_provider">CalDAV account</string>
|
<string name="accounts_generic_provider">CalDAV account</string>
|
||||||
<string name="accounts_sync_failed">Last sync didn\u2019t finish</string>
|
<string name="accounts_sync_failed">Last sync didn\u2019t finish</string>
|
||||||
<string name="accounts_failed_sign_in">Sync failed \u2014 the server didn\u2019t accept the password</string>
|
<string name="accounts_failed_sign_in">Sync failed \u2014 the server didn\u2019t accept the password</string>
|
||||||
|
|||||||
@@ -27,4 +27,16 @@ class RoomTaskMapperTest {
|
|||||||
|
|
||||||
assertThat(RoomTaskMapper.taskList(list).acceptsWrites).isFalse()
|
assertThat(RoomTaskMapper.taskList(list).acceptsWrites).isFalse()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `an account's label is shown in place of its stored name`() {
|
||||||
|
val list = TaskListRow(
|
||||||
|
list = TaskListEntity(id = 2, name = "Work", color = 0, accountId = 1),
|
||||||
|
accountDisplayName = "me@host",
|
||||||
|
accountLabel = "Work server",
|
||||||
|
)
|
||||||
|
|
||||||
|
assertThat(RoomTaskMapper.taskList(list).accountName).isEqualTo("Work server")
|
||||||
|
assertThat(RoomTaskMapper.taskList(list.copy(accountLabel = null)).accountName).isEqualTo("me@host")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user