From 1562a33836dc0cf7c35e24af03a5d77e3cf8710b Mon Sep 17 00:00:00 2001 From: M66B Date: Sun, 20 Jan 2019 15:22:21 +0000 Subject: [PATCH] Select swipe left/right folder --- FAQ.md | 5 +- app/schemas/eu.faircode.email.DB/39.json | 1435 +++++++++++++++++ .../eu/faircode/email/AdapterMessage.java | 8 + app/src/main/java/eu/faircode/email/DB.java | 10 +- .../java/eu/faircode/email/DaoAccount.java | 6 + .../java/eu/faircode/email/EntityAccount.java | 18 +- .../eu/faircode/email/FragmentAccount.java | 68 +- .../eu/faircode/email/FragmentMessages.java | 175 +- .../eu/faircode/email/FragmentOptions.java | 12 +- .../eu/faircode/email/TupleAccountSwipes.java | 28 + .../eu/faircode/email/ViewModelMessages.java | 4 +- .../main/res/drawable/baseline_flag_24.xml | 10 + app/src/main/res/layout/fragment_account.xml | 45 +- app/src/main/res/layout/fragment_options.xml | 14 +- app/src/main/res/values/strings.xml | 3 +- 15 files changed, 1707 insertions(+), 134 deletions(-) create mode 100644 app/schemas/eu.faircode.email.DB/39.json create mode 100644 app/src/main/java/eu/faircode/email/TupleAccountSwipes.java create mode 100644 app/src/main/res/drawable/baseline_flag_24.xml 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)) - return 0; - - if (selectionTracker != null && selectionTracker.hasSelection()) - return 0; - - int pos = viewHolder.getAdapterPosition(); - if (pos == RecyclerView.NO_POSITION) + TupleMessageEx message = getMessage(viewHolder); + if (message == null) 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) + TupleMessageEx message = getMessage(viewHolder); + if (message == null) return; - TupleMessageEx message = ((AdapterMessage) rvMessage.getAdapter()).getCurrentList().get(pos); - if (message == null) + 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(); - 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; + result.target = db.folder().getFolder(target); + EntityMessage message = db.message().getMessage(id); 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