From 5246629f6782a045968dc40840c17a8b5ae5c1ea Mon Sep 17 00:00:00 2001 From: M66B Date: Sat, 24 Nov 2018 19:14:28 +0100 Subject: [PATCH] Support for IMAP answered flag --- FAQ.md | 4 +- app/schemas/eu.faircode.email.DB/7.json | 1122 +++++++++++++++++ .../eu/faircode/email/AdapterMessage.java | 4 + app/src/main/java/eu/faircode/email/DB.java | 10 +- .../java/eu/faircode/email/DaoMessage.java | 6 + .../java/eu/faircode/email/EntityMessage.java | 6 + .../eu/faircode/email/EntityOperation.java | 1 + .../eu/faircode/email/FragmentCompose.java | 10 +- .../java/eu/faircode/email/MessageHelper.java | 4 + .../eu/faircode/email/ServiceSynchronize.java | 30 +- .../main/res/layout/item_message_compact.xml | 12 +- .../main/res/layout/item_message_normal.xml | 12 +- 12 files changed, 1214 insertions(+), 7 deletions(-) create mode 100644 app/schemas/eu.faircode.email.DB/7.json diff --git a/FAQ.md b/FAQ.md index 041e6a3266..aba3aea9bd 100644 --- a/FAQ.md +++ b/FAQ.md @@ -22,7 +22,6 @@ For: ## Planned features -* IMAP answered flag * IMAP namespaces * Microsoft OAuth ([blocking issue](https://github.com/AzureAD/microsoft-authentication-library-for-android/issues/354)) @@ -101,7 +100,8 @@ The low priority status bar notification shows the number of pending operations, * delete: delete message from remote folder * send: send message * seen: mark message as seen/unseen in remote folder -* flag: add/remove stars +* answered: mark message as answered in remote folder +* flag: add/remove star in remote folder * headers: download message headers * body: download message text * attachment: download attachment diff --git a/app/schemas/eu.faircode.email.DB/7.json b/app/schemas/eu.faircode.email.DB/7.json new file mode 100644 index 0000000000..3fdff6660d --- /dev/null +++ b/app/schemas/eu.faircode.email.DB/7.json @@ -0,0 +1,1122 @@ +{ + "formatVersion": 1, + "database": { + "version": 7, + "identityHash": "c79c476bb0036eeb38992c67ce099dc1", + "entities": [ + { + "tableName": "identity", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT, `name` TEXT NOT NULL, `email` TEXT NOT NULL, `replyto` TEXT, `account` INTEGER NOT NULL, `host` TEXT NOT NULL, `starttls` INTEGER NOT NULL, `insecure` INTEGER NOT NULL, `port` INTEGER NOT NULL, `user` TEXT NOT NULL, `password` TEXT NOT NULL, `auth_type` INTEGER NOT NULL, `primary` INTEGER NOT NULL, `color` INTEGER, `signature` TEXT, `synchronize` INTEGER NOT NULL, `store_sent` INTEGER NOT NULL, `sent_folder` INTEGER, `state` TEXT, `error` TEXT, FOREIGN KEY(`account`) REFERENCES `account`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE )", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "name", + "columnName": "name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "email", + "columnName": "email", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "replyto", + "columnName": "replyto", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "account", + "columnName": "account", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "host", + "columnName": "host", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "starttls", + "columnName": "starttls", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "insecure", + "columnName": "insecure", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "port", + "columnName": "port", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "user", + "columnName": "user", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "password", + "columnName": "password", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "auth_type", + "columnName": "auth_type", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "primary", + "columnName": "primary", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "color", + "columnName": "color", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "signature", + "columnName": "signature", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "synchronize", + "columnName": "synchronize", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "store_sent", + "columnName": "store_sent", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "sent_folder", + "columnName": "sent_folder", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "state", + "columnName": "state", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "error", + "columnName": "error", + "affinity": "TEXT", + "notNull": false + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [ + { + "name": "index_identity_account", + "unique": false, + "columnNames": [ + "account" + ], + "createSql": "CREATE INDEX `index_identity_account` ON `${TABLE_NAME}` (`account`)" + }, + { + "name": "index_identity_account_email", + "unique": false, + "columnNames": [ + "account", + "email" + ], + "createSql": "CREATE INDEX `index_identity_account_email` ON `${TABLE_NAME}` (`account`, `email`)" + } + ], + "foreignKeys": [ + { + "table": "account", + "onDelete": "CASCADE", + "onUpdate": "NO ACTION", + "columns": [ + "account" + ], + "referencedColumns": [ + "id" + ] + } + ] + }, + { + "tableName": "account", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT, `name` TEXT, `signature` TEXT, `host` TEXT NOT NULL, `starttls` INTEGER NOT NULL, `insecure` INTEGER NOT NULL, `port` INTEGER NOT NULL, `user` TEXT NOT NULL, `password` TEXT NOT NULL, `auth_type` INTEGER NOT NULL, `synchronize` INTEGER NOT NULL, `primary` INTEGER NOT NULL, `color` INTEGER, `notify` INTEGER NOT NULL, `poll_interval` INTEGER NOT NULL, `created` INTEGER, `state` TEXT, `error` TEXT, `last_connected` INTEGER)", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "name", + "columnName": "name", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "signature", + "columnName": "signature", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "host", + "columnName": "host", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "starttls", + "columnName": "starttls", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "insecure", + "columnName": "insecure", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "port", + "columnName": "port", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "user", + "columnName": "user", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "password", + "columnName": "password", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "auth_type", + "columnName": "auth_type", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "synchronize", + "columnName": "synchronize", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "primary", + "columnName": "primary", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "color", + "columnName": "color", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "notify", + "columnName": "notify", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "poll_interval", + "columnName": "poll_interval", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "created", + "columnName": "created", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "state", + "columnName": "state", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "error", + "columnName": "error", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "last_connected", + "columnName": "last_connected", + "affinity": "INTEGER", + "notNull": false + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "folder", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT, `account` INTEGER, `name` TEXT NOT NULL, `type` TEXT NOT NULL, `synchronize` INTEGER NOT NULL, `sync_days` INTEGER NOT NULL, `keep_days` INTEGER NOT NULL, `display` TEXT, `hide` INTEGER NOT NULL, `unified` INTEGER NOT NULL, `state` TEXT, `error` TEXT, FOREIGN KEY(`account`) REFERENCES `account`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE )", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "account", + "columnName": "account", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "name", + "columnName": "name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "type", + "columnName": "type", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "synchronize", + "columnName": "synchronize", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "sync_days", + "columnName": "sync_days", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "keep_days", + "columnName": "keep_days", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "display", + "columnName": "display", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "hide", + "columnName": "hide", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "unified", + "columnName": "unified", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "state", + "columnName": "state", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "error", + "columnName": "error", + "affinity": "TEXT", + "notNull": false + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [ + { + "name": "index_folder_account_name", + "unique": true, + "columnNames": [ + "account", + "name" + ], + "createSql": "CREATE UNIQUE INDEX `index_folder_account_name` ON `${TABLE_NAME}` (`account`, `name`)" + }, + { + "name": "index_folder_account", + "unique": false, + "columnNames": [ + "account" + ], + "createSql": "CREATE INDEX `index_folder_account` ON `${TABLE_NAME}` (`account`)" + }, + { + "name": "index_folder_name", + "unique": false, + "columnNames": [ + "name" + ], + "createSql": "CREATE INDEX `index_folder_name` ON `${TABLE_NAME}` (`name`)" + }, + { + "name": "index_folder_type", + "unique": false, + "columnNames": [ + "type" + ], + "createSql": "CREATE INDEX `index_folder_type` ON `${TABLE_NAME}` (`type`)" + }, + { + "name": "index_folder_unified", + "unique": false, + "columnNames": [ + "unified" + ], + "createSql": "CREATE INDEX `index_folder_unified` ON `${TABLE_NAME}` (`unified`)" + } + ], + "foreignKeys": [ + { + "table": "account", + "onDelete": "CASCADE", + "onUpdate": "NO ACTION", + "columns": [ + "account" + ], + "referencedColumns": [ + "id" + ] + } + ] + }, + { + "tableName": "message", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT, `account` INTEGER NOT NULL, `folder` INTEGER NOT NULL, `identity` INTEGER, `extra` TEXT, `replying` INTEGER, `forwarding` INTEGER, `uid` INTEGER, `msgid` TEXT, `references` TEXT, `deliveredto` TEXT, `inreplyto` TEXT, `thread` TEXT, `avatar` TEXT, `from` TEXT, `to` TEXT, `cc` TEXT, `bcc` TEXT, `reply` TEXT, `headers` TEXT, `subject` TEXT, `size` INTEGER, `content` INTEGER NOT NULL, `preview` TEXT, `sent` INTEGER, `received` INTEGER NOT NULL, `stored` INTEGER NOT NULL, `seen` INTEGER NOT NULL, `answered` INTEGER NOT NULL, `flagged` INTEGER NOT NULL, `ui_seen` INTEGER NOT NULL, `ui_answered` INTEGER NOT NULL, `ui_flagged` INTEGER NOT NULL, `ui_hide` INTEGER NOT NULL, `ui_found` INTEGER NOT NULL, `ui_ignored` INTEGER NOT NULL, `error` TEXT, `last_attempt` INTEGER, FOREIGN KEY(`account`) REFERENCES `account`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE , FOREIGN KEY(`folder`) REFERENCES `folder`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE , FOREIGN KEY(`identity`) REFERENCES `identity`(`id`) ON UPDATE NO ACTION ON DELETE SET NULL , FOREIGN KEY(`replying`) REFERENCES `message`(`id`) ON UPDATE NO ACTION ON DELETE SET NULL , FOREIGN KEY(`forwarding`) REFERENCES `message`(`id`) ON UPDATE NO ACTION ON DELETE SET NULL )", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "account", + "columnName": "account", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "folder", + "columnName": "folder", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "identity", + "columnName": "identity", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "extra", + "columnName": "extra", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "replying", + "columnName": "replying", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "forwarding", + "columnName": "forwarding", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "uid", + "columnName": "uid", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "msgid", + "columnName": "msgid", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "references", + "columnName": "references", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "deliveredto", + "columnName": "deliveredto", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "inreplyto", + "columnName": "inreplyto", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "thread", + "columnName": "thread", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "avatar", + "columnName": "avatar", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "from", + "columnName": "from", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "to", + "columnName": "to", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "cc", + "columnName": "cc", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "bcc", + "columnName": "bcc", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "reply", + "columnName": "reply", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "headers", + "columnName": "headers", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "size", + "columnName": "size", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "content", + "columnName": "content", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "preview", + "columnName": "preview", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "sent", + "columnName": "sent", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "received", + "columnName": "received", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "stored", + "columnName": "stored", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "seen", + "columnName": "seen", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "answered", + "columnName": "answered", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "flagged", + "columnName": "flagged", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "ui_seen", + "columnName": "ui_seen", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "ui_answered", + "columnName": "ui_answered", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "ui_flagged", + "columnName": "ui_flagged", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "ui_hide", + "columnName": "ui_hide", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "ui_found", + "columnName": "ui_found", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "ui_ignored", + "columnName": "ui_ignored", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "error", + "columnName": "error", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "last_attempt", + "columnName": "last_attempt", + "affinity": "INTEGER", + "notNull": false + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [ + { + "name": "index_message_account", + "unique": false, + "columnNames": [ + "account" + ], + "createSql": "CREATE INDEX `index_message_account` ON `${TABLE_NAME}` (`account`)" + }, + { + "name": "index_message_folder", + "unique": false, + "columnNames": [ + "folder" + ], + "createSql": "CREATE INDEX `index_message_folder` ON `${TABLE_NAME}` (`folder`)" + }, + { + "name": "index_message_identity", + "unique": false, + "columnNames": [ + "identity" + ], + "createSql": "CREATE INDEX `index_message_identity` ON `${TABLE_NAME}` (`identity`)" + }, + { + "name": "index_message_replying", + "unique": false, + "columnNames": [ + "replying" + ], + "createSql": "CREATE INDEX `index_message_replying` ON `${TABLE_NAME}` (`replying`)" + }, + { + "name": "index_message_forwarding", + "unique": false, + "columnNames": [ + "forwarding" + ], + "createSql": "CREATE INDEX `index_message_forwarding` ON `${TABLE_NAME}` (`forwarding`)" + }, + { + "name": "index_message_folder_uid_ui_found", + "unique": true, + "columnNames": [ + "folder", + "uid", + "ui_found" + ], + "createSql": "CREATE UNIQUE INDEX `index_message_folder_uid_ui_found` ON `${TABLE_NAME}` (`folder`, `uid`, `ui_found`)" + }, + { + "name": "index_message_msgid_folder_ui_found", + "unique": true, + "columnNames": [ + "msgid", + "folder", + "ui_found" + ], + "createSql": "CREATE UNIQUE INDEX `index_message_msgid_folder_ui_found` ON `${TABLE_NAME}` (`msgid`, `folder`, `ui_found`)" + }, + { + "name": "index_message_thread", + "unique": false, + "columnNames": [ + "thread" + ], + "createSql": "CREATE INDEX `index_message_thread` ON `${TABLE_NAME}` (`thread`)" + }, + { + "name": "index_message_received", + "unique": false, + "columnNames": [ + "received" + ], + "createSql": "CREATE INDEX `index_message_received` ON `${TABLE_NAME}` (`received`)" + }, + { + "name": "index_message_ui_seen", + "unique": false, + "columnNames": [ + "ui_seen" + ], + "createSql": "CREATE INDEX `index_message_ui_seen` ON `${TABLE_NAME}` (`ui_seen`)" + }, + { + "name": "index_message_ui_hide", + "unique": false, + "columnNames": [ + "ui_hide" + ], + "createSql": "CREATE INDEX `index_message_ui_hide` ON `${TABLE_NAME}` (`ui_hide`)" + }, + { + "name": "index_message_ui_found", + "unique": false, + "columnNames": [ + "ui_found" + ], + "createSql": "CREATE INDEX `index_message_ui_found` ON `${TABLE_NAME}` (`ui_found`)" + }, + { + "name": "index_message_ui_ignored", + "unique": false, + "columnNames": [ + "ui_ignored" + ], + "createSql": "CREATE INDEX `index_message_ui_ignored` ON `${TABLE_NAME}` (`ui_ignored`)" + } + ], + "foreignKeys": [ + { + "table": "account", + "onDelete": "CASCADE", + "onUpdate": "NO ACTION", + "columns": [ + "account" + ], + "referencedColumns": [ + "id" + ] + }, + { + "table": "folder", + "onDelete": "CASCADE", + "onUpdate": "NO ACTION", + "columns": [ + "folder" + ], + "referencedColumns": [ + "id" + ] + }, + { + "table": "identity", + "onDelete": "SET NULL", + "onUpdate": "NO ACTION", + "columns": [ + "identity" + ], + "referencedColumns": [ + "id" + ] + }, + { + "table": "message", + "onDelete": "SET NULL", + "onUpdate": "NO ACTION", + "columns": [ + "replying" + ], + "referencedColumns": [ + "id" + ] + }, + { + "table": "message", + "onDelete": "SET NULL", + "onUpdate": "NO ACTION", + "columns": [ + "forwarding" + ], + "referencedColumns": [ + "id" + ] + } + ] + }, + { + "tableName": "attachment", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT, `message` INTEGER NOT NULL, `sequence` INTEGER NOT NULL, `name` TEXT, `type` TEXT NOT NULL, `cid` TEXT, `size` INTEGER, `progress` INTEGER, `available` INTEGER NOT NULL, FOREIGN KEY(`message`) REFERENCES `message`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE )", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "message", + "columnName": "message", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "sequence", + "columnName": "sequence", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "name", + "columnName": "name", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "type", + "columnName": "type", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "cid", + "columnName": "cid", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "size", + "columnName": "size", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "progress", + "columnName": "progress", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "available", + "columnName": "available", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [ + { + "name": "index_attachment_message", + "unique": false, + "columnNames": [ + "message" + ], + "createSql": "CREATE INDEX `index_attachment_message` ON `${TABLE_NAME}` (`message`)" + }, + { + "name": "index_attachment_message_sequence", + "unique": true, + "columnNames": [ + "message", + "sequence" + ], + "createSql": "CREATE UNIQUE INDEX `index_attachment_message_sequence` ON `${TABLE_NAME}` (`message`, `sequence`)" + }, + { + "name": "index_attachment_message_cid", + "unique": true, + "columnNames": [ + "message", + "cid" + ], + "createSql": "CREATE UNIQUE INDEX `index_attachment_message_cid` ON `${TABLE_NAME}` (`message`, `cid`)" + } + ], + "foreignKeys": [ + { + "table": "message", + "onDelete": "CASCADE", + "onUpdate": "NO ACTION", + "columns": [ + "message" + ], + "referencedColumns": [ + "id" + ] + } + ] + }, + { + "tableName": "operation", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT, `folder` INTEGER NOT NULL, `message` INTEGER NOT NULL, `name` TEXT NOT NULL, `args` TEXT NOT NULL, `created` INTEGER NOT NULL, FOREIGN KEY(`folder`) REFERENCES `folder`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE , FOREIGN KEY(`message`) REFERENCES `message`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE )", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "folder", + "columnName": "folder", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "message", + "columnName": "message", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "name", + "columnName": "name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "args", + "columnName": "args", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "created", + "columnName": "created", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [ + { + "name": "index_operation_folder", + "unique": false, + "columnNames": [ + "folder" + ], + "createSql": "CREATE INDEX `index_operation_folder` ON `${TABLE_NAME}` (`folder`)" + }, + { + "name": "index_operation_message", + "unique": false, + "columnNames": [ + "message" + ], + "createSql": "CREATE INDEX `index_operation_message` ON `${TABLE_NAME}` (`message`)" + } + ], + "foreignKeys": [ + { + "table": "folder", + "onDelete": "CASCADE", + "onUpdate": "NO ACTION", + "columns": [ + "folder" + ], + "referencedColumns": [ + "id" + ] + }, + { + "table": "message", + "onDelete": "CASCADE", + "onUpdate": "NO ACTION", + "columns": [ + "message" + ], + "referencedColumns": [ + "id" + ] + } + ] + }, + { + "tableName": "answer", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT, `name` TEXT NOT NULL, `text` TEXT NOT NULL)", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "name", + "columnName": "name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "text", + "columnName": "text", + "affinity": "TEXT", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "log", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT, `time` INTEGER NOT NULL, `data` TEXT NOT NULL)", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "time", + "columnName": "time", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "data", + "columnName": "data", + "affinity": "TEXT", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [ + { + "name": "index_log_time", + "unique": false, + "columnNames": [ + "time" + ], + "createSql": "CREATE INDEX `index_log_time` ON `${TABLE_NAME}` (`time`)" + } + ], + "foreignKeys": [] + } + ], + "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, \"c79c476bb0036eeb38992c67ce099dc1\")" + ] + } +} \ No newline at end of file diff --git a/app/src/main/java/eu/faircode/email/AdapterMessage.java b/app/src/main/java/eu/faircode/email/AdapterMessage.java index 8a93c8ace3..6df3f21e9b 100644 --- a/app/src/main/java/eu/faircode/email/AdapterMessage.java +++ b/app/src/main/java/eu/faircode/email/AdapterMessage.java @@ -141,6 +141,7 @@ public class AdapterMessage extends PagedListAdapter 0 ? View.VISIBLE : View.GONE); tvSubject.setText(message.subject); diff --git a/app/src/main/java/eu/faircode/email/DB.java b/app/src/main/java/eu/faircode/email/DB.java index 2718eb45d0..2ac3870750 100644 --- a/app/src/main/java/eu/faircode/email/DB.java +++ b/app/src/main/java/eu/faircode/email/DB.java @@ -46,7 +46,7 @@ import io.requery.android.database.sqlite.RequerySQLiteOpenHelperFactory; // https://developer.android.com/topic/libraries/architecture/room.html @Database( - version = 6, + version = 7, entities = { EntityIdentity.class, EntityAccount.class, @@ -162,6 +162,14 @@ public abstract class DB extends RoomDatabase { db.execSQL("ALTER TABLE `account` ADD COLUMN `notify` INTEGER NOT NULL DEFAULT 0"); } }) + .addMigrations(new Migration(6, 7) { + @Override + public void migrate(SupportSQLiteDatabase db) { + Log.i(Helper.TAG, "DB migration from version " + startVersion + " to " + endVersion); + db.execSQL("ALTER TABLE `message` ADD COLUMN `answered` INTEGER NOT NULL DEFAULT 0"); + db.execSQL("ALTER TABLE `message` ADD COLUMN `ui_answered` INTEGER NOT NULL DEFAULT 0"); + } + }) .build(); } diff --git a/app/src/main/java/eu/faircode/email/DaoMessage.java b/app/src/main/java/eu/faircode/email/DaoMessage.java index 0f96d97cb5..58ca296cd4 100644 --- a/app/src/main/java/eu/faircode/email/DaoMessage.java +++ b/app/src/main/java/eu/faircode/email/DaoMessage.java @@ -255,6 +255,12 @@ public interface DaoMessage { @Query("UPDATE message SET ui_seen = :ui_seen WHERE id = :id") int setMessageUiSeen(long id, boolean ui_seen); + @Query("UPDATE message SET answered = :answered WHERE id = :id") + int setMessageAnswered(long id, boolean answered); + + @Query("UPDATE message SET ui_answered = :ui_answered WHERE id = :id") + int setMessageUiAnswered(long id, boolean ui_answered); + @Query("UPDATE message SET flagged = :flagged WHERE id = :id") int setMessageFlagged(long id, boolean flagged); diff --git a/app/src/main/java/eu/faircode/email/EntityMessage.java b/app/src/main/java/eu/faircode/email/EntityMessage.java index e7f083feec..d2bb3ed59e 100644 --- a/app/src/main/java/eu/faircode/email/EntityMessage.java +++ b/app/src/main/java/eu/faircode/email/EntityMessage.java @@ -118,10 +118,14 @@ public class EntityMessage implements Serializable { @NonNull public Boolean seen; @NonNull + public Boolean answered; + @NonNull public Boolean flagged; @NonNull public Boolean ui_seen; @NonNull + public Boolean ui_answered; + @NonNull public Boolean ui_flagged; @NonNull public Boolean ui_hide; @@ -272,8 +276,10 @@ public class EntityMessage implements Serializable { this.received.equals(other.received) && this.stored.equals(other.stored) && this.seen.equals(other.seen) && + this.answered.equals(other.answered) && this.flagged.equals(other.flagged) && this.ui_seen.equals(other.ui_seen) && + this.ui_answered.equals(other.ui_answered) && this.ui_flagged.equals(other.ui_flagged) && this.ui_hide.equals(other.ui_hide) && this.ui_found.equals(other.ui_found) && diff --git a/app/src/main/java/eu/faircode/email/EntityOperation.java b/app/src/main/java/eu/faircode/email/EntityOperation.java index 542908e51e..24760abd0e 100644 --- a/app/src/main/java/eu/faircode/email/EntityOperation.java +++ b/app/src/main/java/eu/faircode/email/EntityOperation.java @@ -70,6 +70,7 @@ public class EntityOperation { public static final String MOVE = "move"; public static final String DELETE = "delete"; public static final String SEND = "send"; + public static final String ANSWERED = "answered"; public static final String HEADERS = "headers"; public static final String BODY = "body"; public static final String ATTACHMENT = "attachment"; diff --git a/app/src/main/java/eu/faircode/email/FragmentCompose.java b/app/src/main/java/eu/faircode/email/FragmentCompose.java index 2da132d6b6..efd26e9de1 100644 --- a/app/src/main/java/eu/faircode/email/FragmentCompose.java +++ b/app/src/main/java/eu/faircode/email/FragmentCompose.java @@ -1205,8 +1205,10 @@ public class FragmentCompose extends FragmentEx { result.draft.content = true; result.draft.received = new Date().getTime(); result.draft.seen = false; - result.draft.ui_seen = false; + result.draft.answered = false; result.draft.flagged = false; + result.draft.ui_seen = false; + result.draft.ui_answered = false; result.draft.ui_flagged = false; result.draft.ui_hide = false; result.draft.ui_found = false; @@ -1640,6 +1642,12 @@ public class FragmentCompose extends FragmentEx { } EntityOperation.queue(db, draft, EntityOperation.SEND); + + if (draft.replying != null) { + EntityMessage replying = db.message().getMessage(draft.replying); + db.message().setMessageUiAnswered(replying.id, true); + EntityOperation.queue(db, replying, EntityOperation.ANSWERED, true); + } } db.setTransactionSuccessful(); diff --git a/app/src/main/java/eu/faircode/email/MessageHelper.java b/app/src/main/java/eu/faircode/email/MessageHelper.java index b31b6cead5..bdcbf5f747 100644 --- a/app/src/main/java/eu/faircode/email/MessageHelper.java +++ b/app/src/main/java/eu/faircode/email/MessageHelper.java @@ -356,6 +356,10 @@ public class MessageHelper { return imessage.isSet(Flags.Flag.SEEN); } + boolean getAnsered() throws MessagingException { + return imessage.isSet(Flags.Flag.ANSWERED); + } + boolean getFlagged() throws MessagingException { return imessage.isSet(Flags.Flag.FLAGGED); } diff --git a/app/src/main/java/eu/faircode/email/ServiceSynchronize.java b/app/src/main/java/eu/faircode/email/ServiceSynchronize.java index ec3b9738a8..76ecf6fb5a 100644 --- a/app/src/main/java/eu/faircode/email/ServiceSynchronize.java +++ b/app/src/main/java/eu/faircode/email/ServiceSynchronize.java @@ -1348,6 +1348,9 @@ public class ServiceSynchronize extends LifecycleService { if (EntityOperation.SEEN.equals(op.name)) doSeen(folder, ifolder, message, jargs, db); + else if (EntityOperation.ANSWERED.equals(op.name)) + doAnswered(folder, ifolder, message, jargs, db); + else if (EntityOperation.FLAG.equals(op.name)) doFlag(folder, ifolder, message, jargs, db); @@ -1430,6 +1433,21 @@ public class ServiceSynchronize extends LifecycleService { db.message().setMessageSeen(message.id, seen); } + private void doAnswered(EntityFolder folder, IMAPFolder ifolder, EntityMessage message, JSONArray jargs, DB db) throws MessagingException, JSONException { + // Mark message (un)answered + boolean answered = jargs.getBoolean(0); + if (message.answered != answered) + return; + + Message imessage = ifolder.getMessageByUID(message.uid); + if (imessage == null) + throw new MessageRemovedException(); + + imessage.setFlag(Flags.Flag.ANSWERED, answered); + + db.message().setMessageAnswered(message.id, answered); + } + private void doFlag(EntityFolder folder, IMAPFolder ifolder, EntityMessage message, JSONArray jargs, DB db) throws MessagingException, JSONException { // Star/unstar message boolean flagged = jargs.getBoolean(0); @@ -1907,6 +1925,7 @@ public class ServiceSynchronize extends LifecycleService { MessageHelper helper = new MessageHelper(imessage); boolean seen = helper.getSeen(); + boolean answered = helper.getAnsered(); boolean flagged = helper.getFlagged(); DB db = DB.getInstance(context); @@ -2009,8 +2028,10 @@ public class ServiceSynchronize extends LifecycleService { message.received = imessage.getReceivedDate().getTime(); message.sent = (imessage.getSentDate() == null ? null : imessage.getSentDate().getTime()); message.seen = seen; - message.ui_seen = seen; + message.answered = answered; message.flagged = false; + message.ui_seen = seen; + message.ui_answered = answered; message.ui_flagged = false; message.ui_hide = false; message.ui_found = found; @@ -2042,6 +2063,13 @@ public class ServiceSynchronize extends LifecycleService { Log.i(Helper.TAG, folder.name + " updated id=" + message.id + " uid=" + message.uid + " seen=" + seen); } + if (message.answered != answered || message.answered != message.ui_answered) { + message.answered = answered; + message.ui_answered = answered; + db.message().updateMessage(message); + Log.i(Helper.TAG, folder.name + " updated id=" + message.id + " uid=" + message.uid + " answered=" + answered); + } + if (message.flagged != flagged || message.flagged != message.ui_flagged) { message.flagged = flagged; message.ui_flagged = flagged; diff --git a/app/src/main/res/layout/item_message_compact.xml b/app/src/main/res/layout/item_message_compact.xml index 482f10562a..f25143bd27 100644 --- a/app/src/main/res/layout/item_message_compact.xml +++ b/app/src/main/res/layout/item_message_compact.xml @@ -108,6 +108,16 @@ app:layout_constraintEnd_toStartOf="@+id/paddingEnd" app:layout_constraintTop_toTopOf="@id/tvFrom" /> + + + +