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; } 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" /> -