diff --git a/FAQ.md b/FAQ.md
index 6ec895e96a..637a6eeb1b 100644
--- a/FAQ.md
+++ b/FAQ.md
@@ -1025,8 +1025,9 @@ There is an advanced option to disable automatically resizing of image attachmen
**(64) Can you add custom actions for swipe left/right?**
The most natural thing to do when swiping a list entry left or right is to remove the entry from the list.
-The most natural action in the context of FairEmail is to either archive or trash the message.
-Other actions, like adding stars to messages, reporting messages as spam and move messages are available via multiple selection.
+The most natural action in the context of an email app is moving the message out of the folder to another folder.
+You can select the folder to move to in the account settings.
+Other actions, like marking messages read and adding stars to messages are available via multiple selection.
diff --git a/app/schemas/eu.faircode.email.DB/39.json b/app/schemas/eu.faircode.email.DB/39.json
new file mode 100644
index 0000000000..1d90ddc9d0
--- /dev/null
+++ b/app/schemas/eu.faircode.email.DB/39.json
@@ -0,0 +1,1435 @@
+{
+ "formatVersion": 1,
+ "database": {
+ "version": 39,
+ "identityHash": "25c94258d898db621e8a0c1863722c16",
+ "entities": [
+ {
+ "tableName": "identity",
+ "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT, `name` TEXT NOT NULL, `email` TEXT NOT NULL, `account` INTEGER NOT NULL, `display` TEXT, `color` INTEGER, `signature` TEXT, `auth_type` 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, `realm` TEXT, `synchronize` INTEGER NOT NULL, `primary` INTEGER NOT NULL, `replyto` TEXT, `bcc` TEXT, `delivery_receipt` INTEGER NOT NULL, `read_receipt` INTEGER NOT NULL, `store_sent` INTEGER NOT NULL, `sent_folder` INTEGER, `tbd` 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": "account",
+ "columnName": "account",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "display",
+ "columnName": "display",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "color",
+ "columnName": "color",
+ "affinity": "INTEGER",
+ "notNull": false
+ },
+ {
+ "fieldPath": "signature",
+ "columnName": "signature",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "auth_type",
+ "columnName": "auth_type",
+ "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": "realm",
+ "columnName": "realm",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "synchronize",
+ "columnName": "synchronize",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "primary",
+ "columnName": "primary",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "replyto",
+ "columnName": "replyto",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "bcc",
+ "columnName": "bcc",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "delivery_receipt",
+ "columnName": "delivery_receipt",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "read_receipt",
+ "columnName": "read_receipt",
+ "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": "tbd",
+ "columnName": "tbd",
+ "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, `auth_type` 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, `realm` TEXT, `name` TEXT, `signature` TEXT, `color` INTEGER, `synchronize` INTEGER NOT NULL, `primary` INTEGER NOT NULL, `notify` INTEGER NOT NULL, `browse` INTEGER NOT NULL, `swipe_left` INTEGER, `swipe_right` INTEGER, `poll_interval` INTEGER NOT NULL, `prefix` TEXT, `created` INTEGER, `tbd` INTEGER, `state` TEXT, `error` TEXT, `last_connected` INTEGER)",
+ "fields": [
+ {
+ "fieldPath": "id",
+ "columnName": "id",
+ "affinity": "INTEGER",
+ "notNull": false
+ },
+ {
+ "fieldPath": "auth_type",
+ "columnName": "auth_type",
+ "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": "realm",
+ "columnName": "realm",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "name",
+ "columnName": "name",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "signature",
+ "columnName": "signature",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "color",
+ "columnName": "color",
+ "affinity": "INTEGER",
+ "notNull": false
+ },
+ {
+ "fieldPath": "synchronize",
+ "columnName": "synchronize",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "primary",
+ "columnName": "primary",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "notify",
+ "columnName": "notify",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "browse",
+ "columnName": "browse",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "swipe_left",
+ "columnName": "swipe_left",
+ "affinity": "INTEGER",
+ "notNull": false
+ },
+ {
+ "fieldPath": "swipe_right",
+ "columnName": "swipe_right",
+ "affinity": "INTEGER",
+ "notNull": false
+ },
+ {
+ "fieldPath": "poll_interval",
+ "columnName": "poll_interval",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "prefix",
+ "columnName": "prefix",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "created",
+ "columnName": "created",
+ "affinity": "INTEGER",
+ "notNull": false
+ },
+ {
+ "fieldPath": "tbd",
+ "columnName": "tbd",
+ "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, `level` INTEGER NOT NULL, `synchronize` INTEGER NOT NULL, `poll` INTEGER NOT NULL, `download` INTEGER NOT NULL, `sync_days` INTEGER NOT NULL, `keep_days` INTEGER NOT NULL, `display` TEXT, `hide` INTEGER NOT NULL, `unified` INTEGER NOT NULL, `notify` INTEGER NOT NULL, `keywords` TEXT, `initialize` INTEGER NOT NULL, `tbc` INTEGER, `tbd` INTEGER, `state` TEXT, `sync_state` TEXT, `error` TEXT, `last_sync` INTEGER, 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": "level",
+ "columnName": "level",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "synchronize",
+ "columnName": "synchronize",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "poll",
+ "columnName": "poll",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "download",
+ "columnName": "download",
+ "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": "notify",
+ "columnName": "notify",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "keywords",
+ "columnName": "keywords",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "initialize",
+ "columnName": "initialize",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "tbc",
+ "columnName": "tbc",
+ "affinity": "INTEGER",
+ "notNull": false
+ },
+ {
+ "fieldPath": "tbd",
+ "columnName": "tbd",
+ "affinity": "INTEGER",
+ "notNull": false
+ },
+ {
+ "fieldPath": "state",
+ "columnName": "state",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "sync_state",
+ "columnName": "sync_state",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "error",
+ "columnName": "error",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "last_sync",
+ "columnName": "last_sync",
+ "affinity": "INTEGER",
+ "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, `sender` TEXT, `from` TEXT, `to` TEXT, `cc` TEXT, `bcc` TEXT, `reply` TEXT, `headers` TEXT, `raw` INTEGER, `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, `keywords` TEXT, `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, `ui_browsed` INTEGER NOT NULL, `ui_snoozed` INTEGER, `warning` TEXT, `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": "sender",
+ "columnName": "sender",
+ "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": "raw",
+ "columnName": "raw",
+ "affinity": "INTEGER",
+ "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": "keywords",
+ "columnName": "keywords",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "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": "ui_browsed",
+ "columnName": "ui_browsed",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "ui_snoozed",
+ "columnName": "ui_snoozed",
+ "affinity": "INTEGER",
+ "notNull": false
+ },
+ {
+ "fieldPath": "warning",
+ "columnName": "warning",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "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",
+ "unique": true,
+ "columnNames": [
+ "folder",
+ "uid"
+ ],
+ "createSql": "CREATE UNIQUE INDEX `index_message_folder_uid` ON `${TABLE_NAME}` (`folder`, `uid`)"
+ },
+ {
+ "name": "index_message_msgid_folder",
+ "unique": true,
+ "columnNames": [
+ "msgid",
+ "folder"
+ ],
+ "createSql": "CREATE UNIQUE INDEX `index_message_msgid_folder` ON `${TABLE_NAME}` (`msgid`, `folder`)"
+ },
+ {
+ "name": "index_message_thread",
+ "unique": false,
+ "columnNames": [
+ "thread"
+ ],
+ "createSql": "CREATE INDEX `index_message_thread` ON `${TABLE_NAME}` (`thread`)"
+ },
+ {
+ "name": "index_message_sender",
+ "unique": false,
+ "columnNames": [
+ "sender"
+ ],
+ "createSql": "CREATE INDEX `index_message_sender` ON `${TABLE_NAME}` (`sender`)"
+ },
+ {
+ "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_flagged",
+ "unique": false,
+ "columnNames": [
+ "ui_flagged"
+ ],
+ "createSql": "CREATE INDEX `index_message_ui_flagged` ON `${TABLE_NAME}` (`ui_flagged`)"
+ },
+ {
+ "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`)"
+ },
+ {
+ "name": "index_message_ui_browsed",
+ "unique": false,
+ "columnNames": [
+ "ui_browsed"
+ ],
+ "createSql": "CREATE INDEX `index_message_ui_browsed` ON `${TABLE_NAME}` (`ui_browsed`)"
+ },
+ {
+ "name": "index_message_ui_snoozed",
+ "unique": false,
+ "columnNames": [
+ "ui_snoozed"
+ ],
+ "createSql": "CREATE INDEX `index_message_ui_snoozed` ON `${TABLE_NAME}` (`ui_snoozed`)"
+ }
+ ],
+ "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, `disposition` TEXT, `cid` TEXT, `encryption` INTEGER, `size` INTEGER, `progress` INTEGER, `available` INTEGER NOT NULL, `error` TEXT, 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": "disposition",
+ "columnName": "disposition",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "cid",
+ "columnName": "cid",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "encryption",
+ "columnName": "encryption",
+ "affinity": "INTEGER",
+ "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
+ },
+ {
+ "fieldPath": "error",
+ "columnName": "error",
+ "affinity": "TEXT",
+ "notNull": false
+ }
+ ],
+ "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, `name` TEXT NOT NULL, `args` TEXT NOT NULL, `created` INTEGER NOT NULL, `error` TEXT, 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": false
+ },
+ {
+ "fieldPath": "name",
+ "columnName": "name",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "args",
+ "columnName": "args",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "created",
+ "columnName": "created",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "error",
+ "columnName": "error",
+ "affinity": "TEXT",
+ "notNull": false
+ }
+ ],
+ "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": "rule",
+ "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT, `folder` INTEGER NOT NULL, `name` TEXT NOT NULL, `order` INTEGER NOT NULL, `enabled` INTEGER NOT NULL, `stop` INTEGER NOT NULL, `condition` TEXT NOT NULL, `action` TEXT NOT NULL, FOREIGN KEY(`folder`) REFERENCES `folder`(`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": "name",
+ "columnName": "name",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "order",
+ "columnName": "order",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "enabled",
+ "columnName": "enabled",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "stop",
+ "columnName": "stop",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "condition",
+ "columnName": "condition",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "action",
+ "columnName": "action",
+ "affinity": "TEXT",
+ "notNull": true
+ }
+ ],
+ "primaryKey": {
+ "columnNames": [
+ "id"
+ ],
+ "autoGenerate": true
+ },
+ "indices": [
+ {
+ "name": "index_rule_folder",
+ "unique": false,
+ "columnNames": [
+ "folder"
+ ],
+ "createSql": "CREATE INDEX `index_rule_folder` ON `${TABLE_NAME}` (`folder`)"
+ },
+ {
+ "name": "index_rule_order",
+ "unique": false,
+ "columnNames": [
+ "order"
+ ],
+ "createSql": "CREATE INDEX `index_rule_order` ON `${TABLE_NAME}` (`order`)"
+ }
+ ],
+ "foreignKeys": [
+ {
+ "table": "folder",
+ "onDelete": "CASCADE",
+ "onUpdate": "NO ACTION",
+ "columns": [
+ "folder"
+ ],
+ "referencedColumns": [
+ "id"
+ ]
+ }
+ ]
+ },
+ {
+ "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, \"25c94258d898db621e8a0c1863722c16\")"
+ ]
+ }
+}
\ 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 86c601e6c7..75680c3979 100644
--- a/app/src/main/java/eu/faircode/email/AdapterMessage.java
+++ b/app/src/main/java/eu/faircode/email/AdapterMessage.java
@@ -292,6 +292,14 @@ public class AdapterMessage extends RecyclerView.Adapter liveStats();
+ @Query("SELECT account.id, swipe_left, l.type AS left_type, swipe_right, r.type AS right_type" +
+ " FROM account" +
+ " LEFT JOIN folder l ON l.id = account.swipe_left" +
+ " LEFT JOIN folder r ON r.id = account.swipe_right")
+ LiveData> liveAccountSwipes();
+
@Insert
long insertAccount(EntityAccount account);
diff --git a/app/src/main/java/eu/faircode/email/EntityAccount.java b/app/src/main/java/eu/faircode/email/EntityAccount.java
index 281c0eaa03..b18b4c43fb 100644
--- a/app/src/main/java/eu/faircode/email/EntityAccount.java
+++ b/app/src/main/java/eu/faircode/email/EntityAccount.java
@@ -71,7 +71,9 @@ public class EntityAccount {
@NonNull
public Boolean notify;
@NonNull
- public Boolean browse;
+ public Boolean browse = true;
+ public Long swipe_left;
+ public Long swipe_right;
@NonNull
public Integer poll_interval; // keep-alive interval
public String prefix; // namespace
@@ -116,6 +118,8 @@ public class EntityAccount {
json.put("synchronize", synchronize);
json.put("primary", primary);
json.put("browse", browse);
+ json.put("swipe_left", swipe_left);
+ json.put("swipe_right", swipe_right);
if (color != null)
json.put("color", color);
json.put("notify", notify);
@@ -142,14 +146,22 @@ public class EntityAccount {
account.auth_type = json.getInt("auth_type");
account.synchronize = json.getBoolean("synchronize");
account.primary = json.getBoolean("primary");
+
if (json.has("browse"))
account.browse = json.getBoolean("browse");
- else
- account.browse = true;
+
+ if (json.has("swipe_left"))
+ account.swipe_left = json.getLong("swipe_left");
+
+ if (json.has("swipe_right"))
+ account.swipe_right = json.getLong("swipe_right");
+
if (json.has("color"))
account.color = json.getInt("color");
+
if (json.has("notify"))
account.notify = json.getBoolean("notify");
+
account.poll_interval = json.getInt("poll_interval");
return account;
}
diff --git a/app/src/main/java/eu/faircode/email/FragmentAccount.java b/app/src/main/java/eu/faircode/email/FragmentAccount.java
index f839d86e12..cb634a5259 100644
--- a/app/src/main/java/eu/faircode/email/FragmentAccount.java
+++ b/app/src/main/java/eu/faircode/email/FragmentAccount.java
@@ -123,6 +123,8 @@ public class FragmentAccount extends FragmentBase {
private Spinner spAll;
private Spinner spTrash;
private Spinner spJunk;
+ private Spinner spLeft;
+ private Spinner spRight;
private Button btnSave;
private ContentLoadingProgressBar pbSave;
@@ -194,6 +196,8 @@ public class FragmentAccount extends FragmentBase {
spAll = view.findViewById(R.id.spAll);
spTrash = view.findViewById(R.id.spTrash);
spJunk = view.findViewById(R.id.spJunk);
+ spLeft = view.findViewById(R.id.spLeft);
+ spRight = view.findViewById(R.id.spRight);
btnSave = view.findViewById(R.id.btnSave);
pbSave = view.findViewById(R.id.pbSave);
@@ -401,6 +405,8 @@ public class FragmentAccount extends FragmentBase {
spAll.setAdapter(adapter);
spTrash.setAdapter(adapter);
spJunk.setAdapter(adapter);
+ spLeft.setAdapter(adapter);
+ spRight.setAdapter(adapter);
// Initialize
Helper.setViewsEnabled(view, false);
@@ -530,7 +536,10 @@ public class FragmentAccount extends FragmentBase {
if (TextUtils.isEmpty(realm))
realm = null;
+ DB db = DB.getInstance(context);
+
CheckResult result = new CheckResult();
+ result.account = db.account().getAccount(id);
result.folders = new ArrayList<>();
// Check IMAP server / get folders
@@ -583,7 +592,6 @@ public class FragmentAccount extends FragmentBase {
if (selectable) {
// Create entry
- DB db = DB.getInstance(context);
EntityFolder folder = db.folder().getFolderByName(id, ifolder.getFullName());
if (folder == null) {
int sync = EntityFolder.SYSTEM_FOLDER_SYNC.indexOf(type);
@@ -653,7 +661,7 @@ public class FragmentAccount extends FragmentBase {
protected void onExecuted(Bundle args, CheckResult result) {
tvIdle.setVisibility(result.idle ? View.GONE : View.VISIBLE);
- setFolders(result.folders);
+ setFolders(result.folders, result.account);
new Handler().post(new Runnable() {
@Override
@@ -692,17 +700,23 @@ public class FragmentAccount extends FragmentBase {
EntityFolder all = (EntityFolder) spAll.getSelectedItem();
EntityFolder trash = (EntityFolder) spTrash.getSelectedItem();
EntityFolder junk = (EntityFolder) spJunk.getSelectedItem();
+ EntityFolder left = (EntityFolder) spLeft.getSelectedItem();
+ EntityFolder right = (EntityFolder) spRight.getSelectedItem();
- if (drafts != null && drafts.type == null)
+ if (drafts != null && drafts.id < 0)
drafts = null;
- if (sent != null && sent.type == null)
+ if (sent != null && sent.id < 0)
sent = null;
- if (all != null && all.type == null)
+ if (all != null && all.id < 0)
all = null;
- if (trash != null && trash.type == null)
+ if (trash != null && trash.id < 0)
trash = null;
- if (junk != null && junk.type == null)
+ if (junk != null && junk.id < 0)
junk = null;
+ if (left != null && left.id < 0)
+ left = null;
+ if (right != null && right.id < 0)
+ right = null;
Bundle args = new Bundle();
args.putLong("id", id);
@@ -731,6 +745,8 @@ public class FragmentAccount extends FragmentBase {
args.putSerializable("all", all);
args.putSerializable("trash", trash);
args.putSerializable("junk", junk);
+ args.putLong("left", left == null ? -1 : left.id);
+ args.putLong("right", right == null ? -1 : right.id);
new SimpleTask() {
@Override
@@ -780,6 +796,8 @@ public class FragmentAccount extends FragmentBase {
EntityFolder all = (EntityFolder) args.getSerializable("all");
EntityFolder trash = (EntityFolder) args.getSerializable("trash");
EntityFolder junk = (EntityFolder) args.getSerializable("junk");
+ Long left = args.getLong("left");
+ Long right = args.getLong("right");
if (TextUtils.isEmpty(host))
throw new IllegalArgumentException(context.getString(R.string.title_no_host));
@@ -802,6 +820,11 @@ public class FragmentAccount extends FragmentBase {
if (TextUtils.isEmpty(prefix))
prefix = null;
+ if (left < 0)
+ left = null;
+ if (right < 0)
+ right = null;
+
Character separator = null;
long now = new Date().getTime();
@@ -870,6 +893,8 @@ public class FragmentAccount extends FragmentBase {
account.primary = (account.synchronize && primary);
account.notify = notify;
account.browse = browse;
+ account.swipe_left = left;
+ account.swipe_right = right;
account.poll_interval = Integer.parseInt(interval);
account.prefix = prefix;
@@ -1020,7 +1045,7 @@ public class FragmentAccount extends FragmentBase {
}
@Override
- protected void onExecuted(Bundle args, EntityAccount account) {
+ protected void onExecuted(Bundle args, final EntityAccount account) {
// Get providers
List providers = EmailProvider.loadProfiles(getContext());
providers.add(0, new EmailProvider(getString(R.string.title_select)));
@@ -1130,7 +1155,7 @@ public class FragmentAccount extends FragmentBase {
protected void onExecuted(Bundle args, List folders) {
if (folders == null)
folders = new ArrayList<>();
- setFolders(folders);
+ setFolders(folders, account);
}
@Override
@@ -1315,25 +1340,37 @@ public class FragmentAccount extends FragmentBase {
vwColor.setBackground(border);
}
- private void setFolders(List folders) {
+ private void setFolders(List folders, EntityAccount account) {
EntityFolder none = new EntityFolder();
+ none.id = -1L;
none.name = "-";
folders.add(0, none);
adapter.clear();
adapter.addAll(folders);
+ Long left = (account == null ? null : account.swipe_left);
+ Long right = (account == null ? null : account.swipe_right);
+
for (int pos = 0; pos < folders.size(); pos++) {
- if (EntityFolder.DRAFTS.equals(folders.get(pos).type))
+ EntityFolder folder = folders.get(pos);
+
+ if (EntityFolder.DRAFTS.equals(folder.type))
spDrafts.setSelection(pos);
- else if (EntityFolder.SENT.equals(folders.get(pos).type))
+ else if (EntityFolder.SENT.equals(folder.type))
spSent.setSelection(pos);
- else if (EntityFolder.ARCHIVE.equals(folders.get(pos).type))
+ else if (EntityFolder.ARCHIVE.equals(folder.type))
spAll.setSelection(pos);
- else if (EntityFolder.TRASH.equals(folders.get(pos).type))
+ else if (EntityFolder.TRASH.equals(folder.type))
spTrash.setSelection(pos);
- else if (EntityFolder.JUNK.equals(folders.get(pos).type))
+ else if (EntityFolder.JUNK.equals(folder.type))
spJunk.setSelection(pos);
+
+ if (left == null ? (account == null && EntityFolder.TRASH.equals(folder.type)) : left.equals(folder.id))
+ spLeft.setSelection(pos);
+
+ if (right == null ? (account == null && EntityFolder.ARCHIVE.equals(folder.type)) : right.equals(folder.id))
+ spRight.setSelection(pos);
}
grpFolders.setVisibility(folders.size() > 1 ? View.VISIBLE : View.GONE);
@@ -1341,6 +1378,7 @@ public class FragmentAccount extends FragmentBase {
}
private class CheckResult {
+ EntityAccount account;
List folders;
boolean idle;
}
diff --git a/app/src/main/java/eu/faircode/email/FragmentMessages.java b/app/src/main/java/eu/faircode/email/FragmentMessages.java
index 4e87f3810b..2a031899ac 100644
--- a/app/src/main/java/eu/faircode/email/FragmentMessages.java
+++ b/app/src/main/java/eu/faircode/email/FragmentMessages.java
@@ -26,6 +26,7 @@ import android.content.Intent;
import android.content.SharedPreferences;
import android.graphics.Canvas;
import android.graphics.Color;
+import android.graphics.Rect;
import android.graphics.drawable.Drawable;
import android.net.ConnectivityManager;
import android.net.Network;
@@ -117,8 +118,8 @@ public class FragmentMessages extends FragmentBase {
private boolean connected = false;
private boolean searching = false;
private AdapterMessage adapter;
- private List archives = new ArrayList<>();
- private List trashes = new ArrayList<>();
+
+ private Map accountSwipes = new HashMap<>();
private AdapterMessage.ViewType viewType;
private SelectionTracker selectionTracker = null;
@@ -551,29 +552,19 @@ public class FragmentMessages extends FragmentBase {
private ItemTouchHelper.Callback touchHelper = new ItemTouchHelper.Callback() {
@Override
public int getMovementFlags(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder) {
- SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(getContext());
- if (!prefs.getBoolean("swipe", true))
+ TupleMessageEx message = getMessage(viewHolder);
+ if (message == null)
return 0;
- if (selectionTracker != null && selectionTracker.hasSelection())
- return 0;
-
- int pos = viewHolder.getAdapterPosition();
- if (pos == RecyclerView.NO_POSITION)
- return 0;
-
- TupleMessageEx message = ((AdapterMessage) rvMessage.getAdapter()).getCurrentList().get(pos);
- if (message == null || message.uid == null ||
- (values.containsKey("expanded") && values.get("expanded").contains(message.id)) ||
- EntityFolder.DRAFTS.equals(message.folderType) ||
- EntityFolder.OUTBOX.equals(message.folderType))
+ TupleAccountSwipes swipes = accountSwipes.get(message.account);
+ if (swipes == null)
return 0;
int flags = 0;
- if (archives.contains(message.account))
- flags |= ItemTouchHelper.RIGHT;
- if (trashes.contains(message.account))
+ if (swipes.swipe_left != null && !swipes.swipe_left.equals(message.folder))
flags |= ItemTouchHelper.LEFT;
+ if (swipes.swipe_right != null && !swipes.swipe_right.equals(message.folder))
+ flags |= ItemTouchHelper.RIGHT;
return makeMovementFlags(0, flags);
}
@@ -585,40 +576,36 @@ public class FragmentMessages extends FragmentBase {
@Override
public void onChildDraw(Canvas canvas, RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder, float dX, float dY, int actionState, boolean isCurrentlyActive) {
- int pos = viewHolder.getAdapterPosition();
- if (pos == RecyclerView.NO_POSITION)
- return;
-
- TupleMessageEx message = ((AdapterMessage) rvMessage.getAdapter()).getCurrentList().get(pos);
+ TupleMessageEx message = getMessage(viewHolder);
if (message == null)
return;
- boolean inbox = (EntityFolder.ARCHIVE.equals(message.folderType) || EntityFolder.TRASH.equals(message.folderType));
+ TupleAccountSwipes swipes = accountSwipes.get(message.account);
+ if (swipes == null)
+ return;
- View itemView = viewHolder.itemView;
+ Rect rect = ((AdapterMessage.ViewHolder) viewHolder).getItemRect();
int margin = Helper.dp2pixels(getContext(), 12);
if (dX > margin) {
// Right swipe
- Drawable d = getResources().getDrawable(
- inbox ? R.drawable.baseline_move_to_inbox_24 : R.drawable.baseline_archive_24,
- getContext().getTheme());
- int padding = (itemView.getHeight() - d.getIntrinsicHeight());
+ Drawable d = getResources().getDrawable(getIcon(swipes.right_type), getContext().getTheme());
+ int padding = (rect.height() - d.getIntrinsicHeight());
d.setBounds(
- itemView.getLeft() + margin,
- itemView.getTop() + padding / 2,
- itemView.getLeft() + margin + d.getIntrinsicWidth(),
- itemView.getTop() + padding / 2 + d.getIntrinsicHeight());
+ rect.left + margin,
+ rect.top + padding / 2,
+ rect.left + margin + d.getIntrinsicWidth(),
+ rect.top + padding / 2 + d.getIntrinsicHeight());
d.draw(canvas);
} else if (dX < -margin) {
// Left swipe
- Drawable d = getResources().getDrawable(inbox ? R.drawable.baseline_move_to_inbox_24 : R.drawable.baseline_delete_24, getContext().getTheme());
- int padding = (itemView.getHeight() - d.getIntrinsicHeight());
+ Drawable d = getResources().getDrawable(getIcon(swipes.left_type), getContext().getTheme());
+ int padding = (rect.height() - d.getIntrinsicHeight());
d.setBounds(
- itemView.getLeft() + itemView.getWidth() - d.getIntrinsicWidth() - margin,
- itemView.getTop() + padding / 2,
- itemView.getLeft() + itemView.getWidth() - margin,
- itemView.getTop() + padding / 2 + d.getIntrinsicHeight());
+ rect.left + rect.width() - d.getIntrinsicWidth() - margin,
+ rect.top + padding / 2,
+ rect.left + rect.width() - margin,
+ rect.top + padding / 2 + d.getIntrinsicHeight());
d.draw(canvas);
}
@@ -627,51 +614,38 @@ public class FragmentMessages extends FragmentBase {
@Override
public void onSwiped(RecyclerView.ViewHolder viewHolder, int direction) {
- int pos = viewHolder.getAdapterPosition();
- if (pos == RecyclerView.NO_POSITION)
- return;
-
- TupleMessageEx message = ((AdapterMessage) rvMessage.getAdapter()).getCurrentList().get(pos);
+ TupleMessageEx message = getMessage(viewHolder);
if (message == null)
return;
+
+ TupleAccountSwipes swipes = accountSwipes.get(message.account);
+ if (swipes == null)
+ return;
+
Log.i("Swiped dir=" + direction + " message=" + message.id);
Bundle args = new Bundle();
args.putLong("id", message.id);
args.putBoolean("thread", viewType != AdapterMessage.ViewType.THREAD);
- args.putInt("direction", direction);
+ args.putLong("target", direction == ItemTouchHelper.LEFT ? swipes.swipe_left : swipes.swipe_right);
new SimpleTask() {
@Override
protected MessageTarget onExecute(Context context, Bundle args) {
long id = args.getLong("id");
boolean thread = args.getBoolean("thread");
- int direction = args.getInt("direction");
+ long target = args.getLong("target");
MessageTarget result = new MessageTarget();
- EntityFolder target = null;
// Get target folder and hide message
DB db = DB.getInstance(context);
try {
db.beginTransaction();
+ result.target = db.folder().getFolder(target);
+
EntityMessage message = db.message().getMessage(id);
-
- EntityFolder folder = db.folder().getFolder(message.folder);
- if (EntityFolder.ARCHIVE.equals(folder.type) || EntityFolder.TRASH.equals(folder.type))
- target = db.folder().getFolderByType(message.account, EntityFolder.INBOX);
- else {
- if (direction == ItemTouchHelper.RIGHT)
- target = db.folder().getFolderByType(message.account, EntityFolder.ARCHIVE);
- if (direction == ItemTouchHelper.LEFT || target == null)
- target = db.folder().getFolderByType(message.account, EntityFolder.TRASH);
- if (target == null)
- target = db.folder().getFolderByType(message.account, EntityFolder.INBOX);
- }
-
- result.target = target;
-
List messages = db.message().getMessageByThread(
message.account, message.thread, threading && thread ? null : id, message.folder);
for (EntityMessage threaded : messages) {
@@ -700,6 +674,39 @@ public class FragmentMessages extends FragmentBase {
}
}.execute(FragmentMessages.this, args, "messages:swipe");
}
+
+ private TupleMessageEx getMessage(RecyclerView.ViewHolder viewHolder) {
+ if (selectionTracker != null && selectionTracker.hasSelection())
+ return null;
+
+ int pos = viewHolder.getAdapterPosition();
+ if (pos == RecyclerView.NO_POSITION)
+ return null;
+
+ TupleMessageEx message = ((AdapterMessage) rvMessage.getAdapter()).getCurrentList().get(pos);
+ if (message == null || message.uid == null)
+ return null;
+
+ if (values.containsKey("expanded") && values.get("expanded").contains(message.id))
+ return null;
+
+ if (EntityFolder.DRAFTS.equals(message.folderType) || EntityFolder.OUTBOX.equals(message.folderType))
+ return null;
+
+ return message;
+ }
+
+ int getIcon(String type) {
+ if (EntityFolder.INBOX.equals(type))
+ return R.drawable.baseline_move_to_inbox_24;
+ if (EntityFolder.ARCHIVE.equals(type))
+ return R.drawable.baseline_archive_24;
+ if (EntityFolder.TRASH.equals(type))
+ return R.drawable.baseline_delete_24;
+ if (EntityFolder.JUNK.equals(type))
+ return R.drawable.baseline_flag_24;
+ return R.drawable.baseline_folder_24;
+ }
};
private void onActionMove(String folderType) {
@@ -1334,6 +1341,20 @@ public class FragmentMessages extends FragmentBase {
}
});
+ db.account().liveAccountSwipes().observe(getViewLifecycleOwner(), new Observer>() {
+ @Override
+ public void onChanged(List swipes) {
+ if (swipes == null)
+ swipes = new ArrayList<>();
+
+ Log.i("Swipes=" + swipes.size());
+
+ accountSwipes.clear();
+ for (TupleAccountSwipes swipe : swipes)
+ accountSwipes.put(swipe.id, swipe);
+ }
+ });
+
// Folder
switch (viewType) {
case UNIFIED:
@@ -1401,25 +1422,7 @@ public class FragmentMessages extends FragmentBase {
break;
}
- // Folders and messages
- db.folder().liveSystemFolders(account).observe(getViewLifecycleOwner(), new Observer>() {
- @Override
- public void onChanged(List folders) {
- if (folders == null)
- folders = new ArrayList<>();
-
- archives.clear();
- trashes.clear();
-
- for (EntityFolder folder : folders)
- if (EntityFolder.ARCHIVE.equals(folder.type))
- archives.add(folder.account);
- else if (EntityFolder.TRASH.equals(folder.type))
- trashes.add(folder.account);
-
- loadMessages();
- }
- });
+ loadMessages();
if (selectionTracker != null && selectionTracker.hasSelection())
fabMore.show();
@@ -1931,13 +1934,19 @@ public class FragmentMessages extends FragmentBase {
archivable = true;
}
+ EntityFolder trash = db.folder().getFolderByType(account, EntityFolder.TRASH);
+ EntityFolder archive = db.folder().getFolderByType(account, EntityFolder.ARCHIVE);
+
+ trashable = (trashable && trash != null);
+ archivable = (archivable && archive != null);
+
return new Boolean[]{trashable, archivable};
}
@Override
protected void onExecuted(Bundle args, Boolean[] data) {
- bottom_navigation.getMenu().findItem(R.id.action_delete).setVisible(trashes.size() > 0 && data[0]);
- bottom_navigation.getMenu().findItem(R.id.action_archive).setVisible(archives.size() > 0 && data[1]);
+ bottom_navigation.getMenu().findItem(R.id.action_delete).setVisible(data[0]);
+ bottom_navigation.getMenu().findItem(R.id.action_archive).setVisible(data[1]);
bottom_navigation.setVisibility(View.VISIBLE);
}
diff --git a/app/src/main/java/eu/faircode/email/FragmentOptions.java b/app/src/main/java/eu/faircode/email/FragmentOptions.java
index 92fff0f391..8e3ec13e14 100644
--- a/app/src/main/java/eu/faircode/email/FragmentOptions.java
+++ b/app/src/main/java/eu/faircode/email/FragmentOptions.java
@@ -67,7 +67,6 @@ public class FragmentOptions extends FragmentBase implements SharedPreferences.O
private SwitchCompat swAddresses;
private SwitchCompat swPull;
- private SwitchCompat swSwipe;
private SwitchCompat swActionbar;
private SwitchCompat swAutoClose;
private SwitchCompat swAutoNext;
@@ -90,7 +89,7 @@ public class FragmentOptions extends FragmentBase implements SharedPreferences.O
"enabled", "updates",
"metered", "download",
"unified", "threading", "avatars", "identicons", "preview", "addresses",
- "pull", "swipe", "actionbar", "autoclose", "autonext",
+ "pull", "actionbar", "autoclose", "autonext",
"autoread", "collapse", "automove", "confirm", "sender", "autoresize", "autosend",
"light", "sound", "debug",
"first", "why", "last_update_check",
@@ -122,7 +121,6 @@ public class FragmentOptions extends FragmentBase implements SharedPreferences.O
swAddresses = view.findViewById(R.id.swAddresses);
swPull = view.findViewById(R.id.swPull);
- swSwipe = view.findViewById(R.id.swSwipe);
swActionbar = view.findViewById(R.id.swActionbar);
swAutoClose = view.findViewById(R.id.swAutoClose);
swAutoNext = view.findViewById(R.id.swAutoNext);
@@ -232,13 +230,6 @@ public class FragmentOptions extends FragmentBase implements SharedPreferences.O
}
});
- swSwipe.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
- @Override
- public void onCheckedChanged(CompoundButton compoundButton, boolean checked) {
- prefs.edit().putBoolean("swipe", checked).apply();
- }
- });
-
swActionbar.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
@Override
public void onCheckedChanged(CompoundButton compoundButton, boolean checked) {
@@ -420,7 +411,6 @@ public class FragmentOptions extends FragmentBase implements SharedPreferences.O
swAddresses.setChecked(prefs.getBoolean("addresses", true));
swPull.setChecked(prefs.getBoolean("pull", true));
- swSwipe.setChecked(prefs.getBoolean("swipe", true));
swActionbar.setChecked(prefs.getBoolean("actionbar", true));
swAutoClose.setChecked(prefs.getBoolean("autoclose", true));
swAutoNext.setChecked(prefs.getBoolean("autonext", false));
diff --git a/app/src/main/java/eu/faircode/email/TupleAccountSwipes.java b/app/src/main/java/eu/faircode/email/TupleAccountSwipes.java
new file mode 100644
index 0000000000..d2b8fd72d6
--- /dev/null
+++ b/app/src/main/java/eu/faircode/email/TupleAccountSwipes.java
@@ -0,0 +1,28 @@
+package eu.faircode.email;
+
+/*
+ This file is part of FairEmail.
+
+ FairEmail is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ FairEmail is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with FairEmail. If not, see .
+
+ Copyright 2018-2019 by Marcel Bokhorst (M66B)
+*/
+
+public class TupleAccountSwipes {
+ public long id;
+ public Long swipe_left;
+ public String left_type;
+ public Long swipe_right;
+ public String right_type;
+}
diff --git a/app/src/main/java/eu/faircode/email/ViewModelMessages.java b/app/src/main/java/eu/faircode/email/ViewModelMessages.java
index f5b0cd2172..f4859f174e 100644
--- a/app/src/main/java/eu/faircode/email/ViewModelMessages.java
+++ b/app/src/main/java/eu/faircode/email/ViewModelMessages.java
@@ -55,14 +55,14 @@ public class ViewModelMessages extends ViewModel {
}
void observe(AdapterMessage.ViewType viewType, LifecycleOwner owner, Observer> observer) {
- if (owner.getLifecycle().getCurrentState().isAtLeast(Lifecycle.State.RESUMED)) {
+ if (owner.getLifecycle().getCurrentState().isAtLeast(Lifecycle.State.INITIALIZED)) {
final boolean thread = (viewType == AdapterMessage.ViewType.THREAD);
messages.get(thread).observe(owner, observer);
}
}
void removeObservers(AdapterMessage.ViewType viewType, LifecycleOwner owner) {
- if (owner.getLifecycle().getCurrentState().isAtLeast(Lifecycle.State.RESUMED)) {
+ if (owner.getLifecycle().getCurrentState().isAtLeast(Lifecycle.State.INITIALIZED)) {
boolean thread = (viewType == AdapterMessage.ViewType.THREAD);
LiveData> list = messages.get(thread);
if (list != null)
diff --git a/app/src/main/res/drawable/baseline_flag_24.xml b/app/src/main/res/drawable/baseline_flag_24.xml
new file mode 100644
index 0000000000..a663a67533
--- /dev/null
+++ b/app/src/main/res/drawable/baseline_flag_24.xml
@@ -0,0 +1,10 @@
+
+
+
diff --git a/app/src/main/res/layout/fragment_account.xml b/app/src/main/res/layout/fragment_account.xml
index af639062e9..14f98daa78 100644
--- a/app/src/main/res/layout/fragment_account.xml
+++ b/app/src/main/res/layout/fragment_account.xml
@@ -499,13 +499,33 @@
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="@+id/spJunk" />
+
+
+
+
+ app:constraint_referenced_ids="tvDrafts,tvSent,tvAll,tvTrash,tvJunk,tvLeft,tvRight" />
+
+
+
@@ -567,7 +606,7 @@
android:layout_marginTop="12dp"
android:text="@string/title_save"
app:layout_constraintStart_toStartOf="parent"
- app:layout_constraintTop_toBottomOf="@id/spJunk" />
+ app:layout_constraintTop_toBottomOf="@id/spRight" />
+ app:constraint_referenced_ids="tvDrafts,spDrafts,tvSent,spSent,tvAll,spAll,tvTrash,spTrash,tvJunk,spJunk,tvLeft,spLeft,tvRight,spRight" />
\ No newline at end of file
diff --git a/app/src/main/res/layout/fragment_options.xml b/app/src/main/res/layout/fragment_options.xml
index 465c30fadc..eb2f7e04d8 100644
--- a/app/src/main/res/layout/fragment_options.xml
+++ b/app/src/main/res/layout/fragment_options.xml
@@ -325,18 +325,6 @@
app:layout_constraintTop_toBottomOf="@id/vSeparatorBehavior"
app:switchPadding="12dp" />
-
-
Select notification sound
Pull down to refresh
- Swipe actions
Conversation action bar
Automatically close conversations
Automatically go to next conversation on close conversation
@@ -181,6 +180,8 @@
Signature text
Color
Separate notifications
+ Swipe left
+ Swipe right
Domain name
Get settings
IMAP