From 2b6b1ec3a69f67214e51def6ec81ff8b7591bb47 Mon Sep 17 00:00:00 2001 From: M66B Date: Wed, 14 Nov 2018 14:51:50 +0100 Subject: [PATCH] Signature per identity --- FAQ.md | 1 - app/schemas/eu.faircode.email.DB/3.json | 1067 +++++++++++++++++ app/src/main/java/eu/faircode/email/DB.java | 11 +- .../java/eu/faircode/email/EntityAccount.java | 6 +- .../eu/faircode/email/EntityIdentity.java | 5 + .../eu/faircode/email/FragmentAccount.java | 30 - .../eu/faircode/email/FragmentCompose.java | 62 +- .../eu/faircode/email/FragmentIdentity.java | 33 + app/src/main/res/layout/fragment_account.xml | 36 +- app/src/main/res/layout/fragment_identity.xml | 36 +- 10 files changed, 1209 insertions(+), 78 deletions(-) create mode 100644 app/schemas/eu.faircode.email.DB/3.json diff --git a/FAQ.md b/FAQ.md index 87b3d503a7..346c86930b 100644 --- a/FAQ.md +++ b/FAQ.md @@ -25,7 +25,6 @@ For: * Notifications per account * Fixed action bar conversations * Password protected export file -* Signature per identity * Keep conversations open (for previous/next navigation) * Microsoft OAuth diff --git a/app/schemas/eu.faircode.email.DB/3.json b/app/schemas/eu.faircode.email.DB/3.json new file mode 100644 index 0000000000..8f1ed7f480 --- /dev/null +++ b/app/schemas/eu.faircode.email.DB/3.json @@ -0,0 +1,1067 @@ +{ + "formatVersion": 1, + "database": { + "version": 3, + "identityHash": "03e9c3e09a28ac3af3cde35f88bc50cf", + "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, `poll_interval` INTEGER NOT NULL, `created` INTEGER, `state` TEXT, `error` TEXT)", + "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": "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 + } + ], + "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, `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, `flagged` INTEGER NOT NULL, `ui_seen` 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, 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 )", + "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": "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": "flagged", + "columnName": "flagged", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "ui_seen", + "columnName": "ui_seen", + "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 + } + ], + "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_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" + ] + } + ] + }, + { + "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, \"03e9c3e09a28ac3af3cde35f88bc50cf\")" + ] + } +} \ No newline at end of file diff --git a/app/src/main/java/eu/faircode/email/DB.java b/app/src/main/java/eu/faircode/email/DB.java index 8462ae36c6..defa71b22d 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 = 2, + version = 3, entities = { EntityIdentity.class, EntityAccount.class, @@ -129,6 +129,15 @@ public abstract class DB extends RoomDatabase { db.execSQL("UPDATE `folder` SET keep_days = sync_days"); } }) + .addMigrations(new Migration(2, 3) { + @Override + public void migrate(SupportSQLiteDatabase db) { + Log.i(Helper.TAG, "DB migration from version " + startVersion + " to " + endVersion); + db.execSQL("ALTER TABLE `identity` ADD COLUMN `signature` TEXT"); + db.execSQL("UPDATE `identity` SET signature =" + + " (SELECT account.signature FROM account WHERE account.id = identity.account)"); + } + }) .build(); } diff --git a/app/src/main/java/eu/faircode/email/EntityAccount.java b/app/src/main/java/eu/faircode/email/EntityAccount.java index f4cfbe9ec8..6848d643bb 100644 --- a/app/src/main/java/eu/faircode/email/EntityAccount.java +++ b/app/src/main/java/eu/faircode/email/EntityAccount.java @@ -37,7 +37,7 @@ public class EntityAccount { @PrimaryKey(autoGenerate = true) public Long id; public String name; - public String signature; + public String signature; // obsolete @NonNull public String host; // IMAP @NonNull @@ -66,7 +66,6 @@ public class EntityAccount { public JSONObject toJSON() throws JSONException { JSONObject json = new JSONObject(); json.put("name", name); - json.put("signature", signature); json.put("host", host); json.put("starttls", starttls); json.put("insecure", insecure); @@ -89,8 +88,6 @@ public class EntityAccount { EntityAccount account = new EntityAccount(); if (json.has("name")) account.name = json.getString("name"); - if (json.has("signature")) - account.signature = json.getString("signature"); account.host = json.getString("host"); account.starttls = (json.has("starttls") && json.getBoolean("starttls")); account.insecure = (json.has("insecure") && json.getBoolean("insecure")); @@ -111,7 +108,6 @@ public class EntityAccount { if (obj instanceof EntityAccount) { EntityAccount other = (EntityAccount) obj; return ((this.name == null ? other.name == null : this.name.equals(other.name)) && - (this.signature == null ? other.signature == null : this.signature.equals(other.signature)) && this.host.equals(other.host) && this.starttls == other.starttls && this.insecure == other.insecure && diff --git a/app/src/main/java/eu/faircode/email/EntityIdentity.java b/app/src/main/java/eu/faircode/email/EntityIdentity.java index 0ac2b2c4d5..6647a9e838 100644 --- a/app/src/main/java/eu/faircode/email/EntityIdentity.java +++ b/app/src/main/java/eu/faircode/email/EntityIdentity.java @@ -69,6 +69,7 @@ public class EntityIdentity { @NonNull public Boolean primary; public Integer color; + public String signature; @NonNull public Boolean synchronize; @NonNull @@ -93,6 +94,7 @@ public class EntityIdentity { json.put("primary", primary); if (color != null) json.put("color", color); + json.put("signature", signature); json.put("synchronize", false); json.put("store_sent", store_sent); if (sent_folder != null) @@ -118,6 +120,8 @@ public class EntityIdentity { identity.primary = json.getBoolean("primary"); if (json.has("color")) identity.color = json.getInt("color"); + if (json.has("signature")) + identity.signature = json.getString("signature"); identity.synchronize = json.getBoolean("synchronize"); identity.store_sent = json.getBoolean("store_sent"); if (json.has("sent_folder")) @@ -141,6 +145,7 @@ public class EntityIdentity { this.password.equals(other.password) && this.primary.equals(other.primary) && (this.color == null ? other.color == null : this.color.equals(other.color)) && + (this.signature == null ? other.signature == null : this.signature.equals(other.signature)) && this.synchronize.equals(other.synchronize) && this.store_sent.equals(other.store_sent) && (this.sent_folder == null ? other.sent_folder == null : this.sent_folder.equals(other.sent_folder)) && diff --git a/app/src/main/java/eu/faircode/email/FragmentAccount.java b/app/src/main/java/eu/faircode/email/FragmentAccount.java index b9447b0f6c..af5001177f 100644 --- a/app/src/main/java/eu/faircode/email/FragmentAccount.java +++ b/app/src/main/java/eu/faircode/email/FragmentAccount.java @@ -37,7 +37,6 @@ import android.os.Bundle; import android.os.Handler; import android.preference.PreferenceManager; import android.text.Editable; -import android.text.Html; import android.text.TextUtils; import android.text.TextWatcher; import android.util.Log; @@ -116,8 +115,6 @@ public class FragmentAccount extends FragmentEx { private Button btnColor; private View vwColor; private ImageView ibColorDefault; - private EditText etSignature; - private ImageButton ibPro; private CheckBox cbSynchronize; private CheckBox cbPrimary; @@ -190,8 +187,6 @@ public class FragmentAccount extends FragmentEx { btnColor = view.findViewById(R.id.btnColor); vwColor = view.findViewById(R.id.vwColor); ibColorDefault = view.findViewById(R.id.ibColorDefault); - etSignature = view.findViewById(R.id.etSignature); - ibPro = view.findViewById(R.id.ibPro); cbSynchronize = view.findViewById(R.id.cbSynchronize); cbPrimary = view.findViewById(R.id.cbPrimary); @@ -397,16 +392,6 @@ public class FragmentAccount extends FragmentEx { } }); - ibPro.setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View v) { - FragmentTransaction fragmentTransaction = getFragmentManager().beginTransaction(); - fragmentTransaction.hide(FragmentAccount.this); - fragmentTransaction.add(R.id.content_frame, new FragmentPro()).addToBackStack("pro"); - fragmentTransaction.commit(); - } - }); - cbSynchronize.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() { @Override public void onCheckedChanged(CompoundButton compoundButton, boolean checked) { @@ -617,7 +602,6 @@ public class FragmentAccount extends FragmentEx { args.putString("name", etName.getText().toString()); args.putInt("color", color); - args.putString("signature", Html.toHtml(etSignature.getText())); args.putBoolean("synchronize", cbSynchronize.isChecked()); args.putBoolean("primary", cbPrimary.isChecked()); @@ -643,7 +627,6 @@ public class FragmentAccount extends FragmentEx { String name = args.getString("name"); Integer color = args.getInt("color"); - String signature = args.getString("signature"); boolean synchronize = args.getBoolean("synchronize"); boolean primary = args.getBoolean("primary"); @@ -723,7 +706,6 @@ public class FragmentAccount extends FragmentEx { account.name = name; account.color = color; - account.signature = signature; account.synchronize = synchronize; account.primary = (account.synchronize && primary); @@ -965,7 +947,6 @@ public class FragmentAccount extends FragmentEx { tilPassword.getEditText().setText(account == null ? null : account.password); etName.setText(account == null ? null : account.name); - etSignature.setText(account == null || account.signature == null ? null : Html.fromHtml(account.signature)); cbSynchronize.setChecked(account == null ? true : account.synchronize); cbPrimary.setChecked(account == null ? true : account.primary); @@ -999,17 +980,6 @@ public class FragmentAccount extends FragmentEx { Helper.setViewsEnabled(view, true); setColor(color); - - boolean pro = Helper.isPro(getContext()); - etSignature.setHint(pro ? R.string.title_optional : R.string.title_pro_feature); - etSignature.setEnabled(pro); - if (pro) { - ViewGroup.LayoutParams lp = ibPro.getLayoutParams(); - lp.height = 0; - lp.width = 0; - ibPro.setLayoutParams(lp); - } - cbPrimary.setEnabled(cbSynchronize.isChecked()); // Consider previous check/save/delete as cancelled diff --git a/app/src/main/java/eu/faircode/email/FragmentCompose.java b/app/src/main/java/eu/faircode/email/FragmentCompose.java index 4e1201565a..426e4228ef 100644 --- a/app/src/main/java/eu/faircode/email/FragmentCompose.java +++ b/app/src/main/java/eu/faircode/email/FragmentCompose.java @@ -37,6 +37,7 @@ import android.os.Looper; import android.preference.PreferenceManager; import android.provider.ContactsContract; import android.provider.OpenableColumns; +import android.text.Editable; import android.text.Html; import android.text.Spannable; import android.text.SpannableString; @@ -44,6 +45,7 @@ import android.text.Spanned; import android.text.TextUtils; import android.text.style.ImageSpan; import android.text.style.StyleSpan; +import android.text.style.TypefaceSpan; import android.text.style.URLSpan; import android.text.style.UnderlineSpan; import android.util.Log; @@ -71,6 +73,7 @@ import com.google.android.material.snackbar.Snackbar; import org.openintents.openpgp.OpenPgpError; import org.openintents.openpgp.util.OpenPgpApi; import org.openintents.openpgp.util.OpenPgpServiceConnection; +import org.xml.sax.XMLReader; import java.io.BufferedOutputStream; import java.io.ByteArrayInputStream; @@ -177,6 +180,19 @@ public class FragmentCompose extends FragmentEx { int at = (identity == null ? -1 : identity.email.indexOf('@')); tvExtraPrefix.setText(at < 0 ? null : identity.email.substring(0, at) + "+"); tvExtraSuffix.setText(at < 0 ? null : identity.email.substring(at)); + + String signature = (identity == null ? null : identity.signature); + if (TextUtils.isEmpty(signature)) + signature = " "; + + String html = Html.toHtml(etBody.getText()); + Log.i(Helper.TAG, html); + int cstart = html.indexOf(""); + int cend = html.indexOf(""); + if (cstart >= 0 && cend > cstart) { + html = html.substring(0, cstart + 4) + signature + html.substring(cend); + etBody.setText(Html.fromHtml(html)); + } } @Override @@ -1126,8 +1142,8 @@ public class FragmentCompose extends FragmentEx { else body = body.replaceAll("\\r?\\n", "
"); - if (pro && !TextUtils.isEmpty(result.account.signature)) - body += "

" + result.account.signature; + if (pro) + body += "

 

 

"; } else { result.draft.thread = ref.thread; @@ -1175,8 +1191,8 @@ public class FragmentCompose extends FragmentEx { HtmlHelper.sanitize(ref.read(context))); } - if (pro && !TextUtils.isEmpty(result.account.signature)) - body = result.account.signature + body; + if (pro) + body = "

 

" + body; if (answer > 0 && ("reply".equals(action) || "reply_all".equals(action))) { String text = db.answer().getAnswer(answer).text; @@ -1192,7 +1208,7 @@ public class FragmentCompose extends FragmentEx { body = text + body; } else - body = "

" + body; + body = "

 

" + body; } result.draft.content = true; @@ -1686,6 +1702,42 @@ public class FragmentCompose extends FragmentEx { } }; + private Html.TagHandler tagHandler = new Html.TagHandler() { + @Override + public void handleTag(boolean opening, String tag, Editable output, XMLReader xmlReader) { + if (tag.equalsIgnoreCase("tt")) + processTt(opening, output); + } + + private void processTt(boolean opening, Editable output) { + Log.i(Helper.TAG, "Handling tt"); + int len = output.length(); + if (opening) + output.setSpan(new TypefaceSpan("monospace"), len, len, Spannable.SPAN_MARK_MARK); + else { + Object span = getLast(output, TypefaceSpan.class); + if (span != null) { + int pos = output.getSpanStart(span); + output.removeSpan(span); + if (pos != len) + output.setSpan(new TypefaceSpan("monospace"), pos, len, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); + } + } + } + + private Object getLast(Editable text, Class kind) { + Object[] spans = text.getSpans(0, text.length(), kind); + if (spans.length == 0) + return null; + + for (int i = spans.length; i > 0; i--) + if (text.getSpanFlags(spans[i - 1]) == Spannable.SPAN_MARK_MARK) + return spans[i - 1]; + + return null; + } + }; + private class DraftAccount { EntityMessage draft; EntityAccount account; diff --git a/app/src/main/java/eu/faircode/email/FragmentIdentity.java b/app/src/main/java/eu/faircode/email/FragmentIdentity.java index 0cd7910b25..eb95fc7bec 100644 --- a/app/src/main/java/eu/faircode/email/FragmentIdentity.java +++ b/app/src/main/java/eu/faircode/email/FragmentIdentity.java @@ -27,6 +27,7 @@ import android.graphics.drawable.GradientDrawable; import android.os.Bundle; import android.os.Handler; import android.preference.PreferenceManager; +import android.text.Html; import android.text.TextUtils; import android.util.Log; import android.view.LayoutInflater; @@ -90,12 +91,17 @@ public class FragmentIdentity extends FragmentEx { private EditText etPort; private EditText etUser; private TextInputLayout tilPassword; + private Button btnColor; private View vwColor; private ImageView ibColorDefault; + private EditText etSignature; + private ImageButton ibPro; + private CheckBox cbSynchronize; private CheckBox cbPrimary; private Spinner spSent; + private Button btnSave; private ProgressBar pbSave; private ImageButton ibDelete; @@ -149,6 +155,8 @@ public class FragmentIdentity extends FragmentEx { btnColor = view.findViewById(R.id.btnColor); vwColor = view.findViewById(R.id.vwColor); ibColorDefault = view.findViewById(R.id.ibColorDefault); + etSignature = view.findViewById(R.id.etSignature); + ibPro = view.findViewById(R.id.ibPro); cbSynchronize = view.findViewById(R.id.cbSynchronize); cbPrimary = view.findViewById(R.id.cbPrimary); @@ -342,6 +350,16 @@ public class FragmentIdentity extends FragmentEx { } }); + ibPro.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + FragmentTransaction fragmentTransaction = getFragmentManager().beginTransaction(); + fragmentTransaction.hide(FragmentIdentity.this); + fragmentTransaction.add(R.id.content_frame, new FragmentPro()).addToBackStack("pro"); + fragmentTransaction.commit(); + } + }); + cbSynchronize.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() { @Override public void onCheckedChanged(CompoundButton compoundButton, boolean checked) { @@ -372,6 +390,7 @@ public class FragmentIdentity extends FragmentEx { args.putString("user", etUser.getText().toString()); args.putString("password", tilPassword.getEditText().getText().toString()); args.putInt("color", color); + args.putString("signature", Html.toHtml(etSignature.getText())); args.putBoolean("synchronize", cbSynchronize.isChecked()); args.putBoolean("primary", cbPrimary.isChecked()); args.putSerializable("sent", (EntityFolder) spSent.getSelectedItem()); @@ -391,6 +410,7 @@ public class FragmentIdentity extends FragmentEx { String user = args.getString("user"); String password = args.getString("password"); Integer color = args.getInt("color"); + String signature = args.getString("signature"); int auth_type = args.getInt("auth_type"); boolean synchronize = args.getBoolean("synchronize"); boolean primary = args.getBoolean("primary"); @@ -462,6 +482,7 @@ public class FragmentIdentity extends FragmentEx { identity.user = user; identity.password = password; identity.color = color; + identity.signature = signature; identity.auth_type = auth_type; identity.synchronize = synchronize; identity.primary = (identity.synchronize && primary); @@ -607,6 +628,7 @@ public class FragmentIdentity extends FragmentEx { etPort.setText(identity == null ? null : Long.toString(identity.port)); etUser.setText(identity == null ? null : identity.user); tilPassword.getEditText().setText(identity == null ? null : identity.password); + etSignature.setText(identity == null || identity.signature == null ? null : Html.fromHtml(identity.signature)); cbSynchronize.setChecked(identity == null ? true : identity.synchronize); cbPrimary.setChecked(identity == null ? true : identity.primary); @@ -635,6 +657,17 @@ public class FragmentIdentity extends FragmentEx { Helper.setViewsEnabled(view, true); setColor(color); + + boolean pro = Helper.isPro(getContext()); + etSignature.setHint(pro ? R.string.title_optional : R.string.title_pro_feature); + etSignature.setEnabled(pro); + if (pro) { + ViewGroup.LayoutParams lp = ibPro.getLayoutParams(); + lp.height = 0; + lp.width = 0; + ibPro.setLayoutParams(lp); + } + cbPrimary.setEnabled(cbSynchronize.isChecked()); // Consider previous save/delete as cancelled diff --git a/app/src/main/res/layout/fragment_account.xml b/app/src/main/res/layout/fragment_account.xml index 16054219c6..56ba84e899 100644 --- a/app/src/main/res/layout/fragment_account.xml +++ b/app/src/main/res/layout/fragment_account.xml @@ -289,38 +289,6 @@ app:layout_constraintStart_toEndOf="@id/vwColor" app:layout_constraintTop_toBottomOf="@id/etName" /> - - - - - - + app:layout_constraintTop_toBottomOf="@id/btnColor" /> + app:constraint_referenced_ids="tvName,etName,btnColor,vwColor,ibColorDefault,cbSynchronize,cbPrimary,tvInterval,etInterval" /> + + + + + + + app:layout_constraintTop_toBottomOf="@id/etSignature" /> + app:constraint_referenced_ids="tvEmail,etEmail,tvReplyTo,etReplyTo,tvProvider,spProvider,tvDomain,etDomain,btnAutoConfig,tvSmtp,tvInsecure,tvHost,etHost,cbStartTls,tvPort,etPort,tvUser,etUser,tvPassword,tilPassword,btnColor,vwColor,ibColorDefault,tvSignature,etSignature,ibPro,cbSynchronize,cbPrimary,tvSent,spSent" /> \ No newline at end of file