diff --git a/app/schemas/org.dystopia.email.DB/25.json b/app/schemas/org.dystopia.email.DB/25.json
new file mode 100644
index 00000000..344e28b6
--- /dev/null
+++ b/app/schemas/org.dystopia.email.DB/25.json
@@ -0,0 +1,1046 @@
+{
+ "formatVersion": 1,
+ "database": {
+ "version": 25,
+ "identityHash": "0e5181b4dbd365cbcda7fef652ef9629",
+ "entities": [
+ {
+ "tableName": "identity",
+ "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT, `name` TEXT NOT NULL, `email` TEXT NOT NULL, `replyto` TEXT, `account` INTEGER NOT NULL, `host` TEXT NOT NULL, `starttls` INTEGER NOT NULL, `insecure` INTEGER NOT NULL, `port` INTEGER NOT NULL, `user` TEXT NOT NULL, `password` TEXT NOT NULL, `auth_type` INTEGER NOT NULL, `primary` INTEGER NOT NULL, `synchronize` INTEGER NOT NULL, `store_sent` INTEGER NOT NULL, `state` TEXT, `error` TEXT, FOREIGN KEY(`account`) REFERENCES `account`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE )",
+ "fields": [
+ {
+ "fieldPath": "id",
+ "columnName": "id",
+ "affinity": "INTEGER",
+ "notNull": false
+ },
+ {
+ "fieldPath": "name",
+ "columnName": "name",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "email",
+ "columnName": "email",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "replyto",
+ "columnName": "replyto",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "account",
+ "columnName": "account",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "host",
+ "columnName": "host",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "starttls",
+ "columnName": "starttls",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "insecure",
+ "columnName": "insecure",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "port",
+ "columnName": "port",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "user",
+ "columnName": "user",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "password",
+ "columnName": "password",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "auth_type",
+ "columnName": "auth_type",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "primary",
+ "columnName": "primary",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "synchronize",
+ "columnName": "synchronize",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "store_sent",
+ "columnName": "store_sent",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "state",
+ "columnName": "state",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "error",
+ "columnName": "error",
+ "affinity": "TEXT",
+ "notNull": false
+ }
+ ],
+ "primaryKey": {
+ "columnNames": [
+ "id"
+ ],
+ "autoGenerate": true
+ },
+ "indices": [
+ {
+ "name": "index_identity_account",
+ "unique": false,
+ "columnNames": [
+ "account"
+ ],
+ "createSql": "CREATE INDEX `index_identity_account` ON `${TABLE_NAME}` (`account`)"
+ }
+ ],
+ "foreignKeys": [
+ {
+ "table": "account",
+ "onDelete": "CASCADE",
+ "onUpdate": "NO ACTION",
+ "columns": [
+ "account"
+ ],
+ "referencedColumns": [
+ "id"
+ ]
+ }
+ ]
+ },
+ {
+ "tableName": "account",
+ "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT, `name` TEXT, `signature` TEXT, `host` TEXT NOT NULL, `starttls` INTEGER NOT NULL, `insecure` INTEGER NOT NULL, `port` INTEGER NOT NULL, `user` TEXT NOT NULL, `password` TEXT NOT NULL, `auth_type` INTEGER NOT NULL, `synchronize` INTEGER NOT NULL, `primary` INTEGER NOT NULL, `color` INTEGER, `store_sent` INTEGER NOT NULL, `poll_interval` INTEGER NOT NULL, `seen_until` INTEGER, `state` TEXT, `error` TEXT)",
+ "fields": [
+ {
+ "fieldPath": "id",
+ "columnName": "id",
+ "affinity": "INTEGER",
+ "notNull": false
+ },
+ {
+ "fieldPath": "name",
+ "columnName": "name",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "signature",
+ "columnName": "signature",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "host",
+ "columnName": "host",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "starttls",
+ "columnName": "starttls",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "insecure",
+ "columnName": "insecure",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "port",
+ "columnName": "port",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "user",
+ "columnName": "user",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "password",
+ "columnName": "password",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "auth_type",
+ "columnName": "auth_type",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "synchronize",
+ "columnName": "synchronize",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "primary",
+ "columnName": "primary",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "color",
+ "columnName": "color",
+ "affinity": "INTEGER",
+ "notNull": false
+ },
+ {
+ "fieldPath": "store_sent",
+ "columnName": "store_sent",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "poll_interval",
+ "columnName": "poll_interval",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "seen_until",
+ "columnName": "seen_until",
+ "affinity": "INTEGER",
+ "notNull": false
+ },
+ {
+ "fieldPath": "state",
+ "columnName": "state",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "error",
+ "columnName": "error",
+ "affinity": "TEXT",
+ "notNull": false
+ }
+ ],
+ "primaryKey": {
+ "columnNames": [
+ "id"
+ ],
+ "autoGenerate": true
+ },
+ "indices": [],
+ "foreignKeys": []
+ },
+ {
+ "tableName": "folder",
+ "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT, `account` INTEGER, `name` TEXT NOT NULL, `type` TEXT NOT NULL, `synchronize` INTEGER NOT NULL, `poll_interval` INTEGER, `after` INTEGER NOT NULL, `display` TEXT, `hide` INTEGER NOT NULL, `unified` INTEGER NOT NULL, `state` TEXT, `sync_state` TEXT, `error` TEXT, FOREIGN KEY(`account`) REFERENCES `account`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE )",
+ "fields": [
+ {
+ "fieldPath": "id",
+ "columnName": "id",
+ "affinity": "INTEGER",
+ "notNull": false
+ },
+ {
+ "fieldPath": "account",
+ "columnName": "account",
+ "affinity": "INTEGER",
+ "notNull": false
+ },
+ {
+ "fieldPath": "name",
+ "columnName": "name",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "type",
+ "columnName": "type",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "synchronize",
+ "columnName": "synchronize",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "poll_interval",
+ "columnName": "poll_interval",
+ "affinity": "INTEGER",
+ "notNull": false
+ },
+ {
+ "fieldPath": "after",
+ "columnName": "after",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "display",
+ "columnName": "display",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "hide",
+ "columnName": "hide",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "unified",
+ "columnName": "unified",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "state",
+ "columnName": "state",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "sync_state",
+ "columnName": "sync_state",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "error",
+ "columnName": "error",
+ "affinity": "TEXT",
+ "notNull": false
+ }
+ ],
+ "primaryKey": {
+ "columnNames": [
+ "id"
+ ],
+ "autoGenerate": true
+ },
+ "indices": [
+ {
+ "name": "index_folder_account_name",
+ "unique": true,
+ "columnNames": [
+ "account",
+ "name"
+ ],
+ "createSql": "CREATE UNIQUE INDEX `index_folder_account_name` ON `${TABLE_NAME}` (`account`, `name`)"
+ },
+ {
+ "name": "index_folder_account",
+ "unique": false,
+ "columnNames": [
+ "account"
+ ],
+ "createSql": "CREATE INDEX `index_folder_account` ON `${TABLE_NAME}` (`account`)"
+ },
+ {
+ "name": "index_folder_name",
+ "unique": false,
+ "columnNames": [
+ "name"
+ ],
+ "createSql": "CREATE INDEX `index_folder_name` ON `${TABLE_NAME}` (`name`)"
+ },
+ {
+ "name": "index_folder_type",
+ "unique": false,
+ "columnNames": [
+ "type"
+ ],
+ "createSql": "CREATE INDEX `index_folder_type` ON `${TABLE_NAME}` (`type`)"
+ },
+ {
+ "name": "index_folder_unified",
+ "unique": false,
+ "columnNames": [
+ "unified"
+ ],
+ "createSql": "CREATE INDEX `index_folder_unified` ON `${TABLE_NAME}` (`unified`)"
+ }
+ ],
+ "foreignKeys": [
+ {
+ "table": "account",
+ "onDelete": "CASCADE",
+ "onUpdate": "NO ACTION",
+ "columns": [
+ "account"
+ ],
+ "referencedColumns": [
+ "id"
+ ]
+ }
+ ]
+ },
+ {
+ "tableName": "message",
+ "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT, `account` INTEGER, `account_name` TEXT, `folder` INTEGER NOT NULL, `identity` INTEGER, `replying` INTEGER, `uid` INTEGER, `msgid` TEXT, `references` TEXT, `deliveredto` TEXT, `inreplyto` TEXT, `thread` TEXT, `avatar` TEXT, `from` TEXT, `to` TEXT, `cc` TEXT, `bcc` TEXT, `reply` TEXT, `headers` TEXT, `subject` TEXT, `size` INTEGER, `content` INTEGER NOT NULL, `sent` INTEGER, `received` INTEGER NOT NULL, `stored` INTEGER NOT NULL, `seen` INTEGER NOT NULL, `flagged` INTEGER NOT NULL, `ui_seen` INTEGER NOT NULL, `ui_flagged` INTEGER NOT NULL, `ui_hide` INTEGER NOT NULL, `ui_found` INTEGER NOT NULL, `ui_ignored` INTEGER NOT NULL, `error` TEXT, FOREIGN KEY(`account`) REFERENCES `account`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE , FOREIGN KEY(`folder`) REFERENCES `folder`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE , FOREIGN KEY(`identity`) REFERENCES `identity`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE , FOREIGN KEY(`replying`) REFERENCES `message`(`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": "account_name",
+ "columnName": "account_name",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "folder",
+ "columnName": "folder",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "identity",
+ "columnName": "identity",
+ "affinity": "INTEGER",
+ "notNull": false
+ },
+ {
+ "fieldPath": "replying",
+ "columnName": "replying",
+ "affinity": "INTEGER",
+ "notNull": false
+ },
+ {
+ "fieldPath": "uid",
+ "columnName": "uid",
+ "affinity": "INTEGER",
+ "notNull": false
+ },
+ {
+ "fieldPath": "msgid",
+ "columnName": "msgid",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "references",
+ "columnName": "references",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "deliveredto",
+ "columnName": "deliveredto",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "inreplyto",
+ "columnName": "inreplyto",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "thread",
+ "columnName": "thread",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "avatar",
+ "columnName": "avatar",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "from",
+ "columnName": "from",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "to",
+ "columnName": "to",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "cc",
+ "columnName": "cc",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "bcc",
+ "columnName": "bcc",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "reply",
+ "columnName": "reply",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "headers",
+ "columnName": "headers",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "subject",
+ "columnName": "subject",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "size",
+ "columnName": "size",
+ "affinity": "INTEGER",
+ "notNull": false
+ },
+ {
+ "fieldPath": "content",
+ "columnName": "content",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "sent",
+ "columnName": "sent",
+ "affinity": "INTEGER",
+ "notNull": false
+ },
+ {
+ "fieldPath": "received",
+ "columnName": "received",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "stored",
+ "columnName": "stored",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "seen",
+ "columnName": "seen",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "flagged",
+ "columnName": "flagged",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "ui_seen",
+ "columnName": "ui_seen",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "ui_flagged",
+ "columnName": "ui_flagged",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "ui_hide",
+ "columnName": "ui_hide",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "ui_found",
+ "columnName": "ui_found",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "ui_ignored",
+ "columnName": "ui_ignored",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "error",
+ "columnName": "error",
+ "affinity": "TEXT",
+ "notNull": false
+ }
+ ],
+ "primaryKey": {
+ "columnNames": [
+ "id"
+ ],
+ "autoGenerate": true
+ },
+ "indices": [
+ {
+ "name": "index_message_account",
+ "unique": false,
+ "columnNames": [
+ "account"
+ ],
+ "createSql": "CREATE INDEX `index_message_account` ON `${TABLE_NAME}` (`account`)"
+ },
+ {
+ "name": "index_message_folder",
+ "unique": false,
+ "columnNames": [
+ "folder"
+ ],
+ "createSql": "CREATE INDEX `index_message_folder` ON `${TABLE_NAME}` (`folder`)"
+ },
+ {
+ "name": "index_message_identity",
+ "unique": false,
+ "columnNames": [
+ "identity"
+ ],
+ "createSql": "CREATE INDEX `index_message_identity` ON `${TABLE_NAME}` (`identity`)"
+ },
+ {
+ "name": "index_message_replying",
+ "unique": false,
+ "columnNames": [
+ "replying"
+ ],
+ "createSql": "CREATE INDEX `index_message_replying` ON `${TABLE_NAME}` (`replying`)"
+ },
+ {
+ "name": "index_message_folder_uid_ui_found",
+ "unique": true,
+ "columnNames": [
+ "folder",
+ "uid",
+ "ui_found"
+ ],
+ "createSql": "CREATE UNIQUE INDEX `index_message_folder_uid_ui_found` ON `${TABLE_NAME}` (`folder`, `uid`, `ui_found`)"
+ },
+ {
+ "name": "index_message_msgid_folder_ui_found",
+ "unique": true,
+ "columnNames": [
+ "msgid",
+ "folder",
+ "ui_found"
+ ],
+ "createSql": "CREATE UNIQUE INDEX `index_message_msgid_folder_ui_found` ON `${TABLE_NAME}` (`msgid`, `folder`, `ui_found`)"
+ },
+ {
+ "name": "index_message_thread",
+ "unique": false,
+ "columnNames": [
+ "thread"
+ ],
+ "createSql": "CREATE INDEX `index_message_thread` ON `${TABLE_NAME}` (`thread`)"
+ },
+ {
+ "name": "index_message_received",
+ "unique": false,
+ "columnNames": [
+ "received"
+ ],
+ "createSql": "CREATE INDEX `index_message_received` ON `${TABLE_NAME}` (`received`)"
+ },
+ {
+ "name": "index_message_ui_seen",
+ "unique": false,
+ "columnNames": [
+ "ui_seen"
+ ],
+ "createSql": "CREATE INDEX `index_message_ui_seen` ON `${TABLE_NAME}` (`ui_seen`)"
+ },
+ {
+ "name": "index_message_ui_hide",
+ "unique": false,
+ "columnNames": [
+ "ui_hide"
+ ],
+ "createSql": "CREATE INDEX `index_message_ui_hide` ON `${TABLE_NAME}` (`ui_hide`)"
+ },
+ {
+ "name": "index_message_ui_found",
+ "unique": false,
+ "columnNames": [
+ "ui_found"
+ ],
+ "createSql": "CREATE INDEX `index_message_ui_found` ON `${TABLE_NAME}` (`ui_found`)"
+ },
+ {
+ "name": "index_message_ui_ignored",
+ "unique": false,
+ "columnNames": [
+ "ui_ignored"
+ ],
+ "createSql": "CREATE INDEX `index_message_ui_ignored` ON `${TABLE_NAME}` (`ui_ignored`)"
+ }
+ ],
+ "foreignKeys": [
+ {
+ "table": "account",
+ "onDelete": "CASCADE",
+ "onUpdate": "NO ACTION",
+ "columns": [
+ "account"
+ ],
+ "referencedColumns": [
+ "id"
+ ]
+ },
+ {
+ "table": "folder",
+ "onDelete": "CASCADE",
+ "onUpdate": "NO ACTION",
+ "columns": [
+ "folder"
+ ],
+ "referencedColumns": [
+ "id"
+ ]
+ },
+ {
+ "table": "identity",
+ "onDelete": "CASCADE",
+ "onUpdate": "NO ACTION",
+ "columns": [
+ "identity"
+ ],
+ "referencedColumns": [
+ "id"
+ ]
+ },
+ {
+ "table": "message",
+ "onDelete": "CASCADE",
+ "onUpdate": "NO ACTION",
+ "columns": [
+ "replying"
+ ],
+ "referencedColumns": [
+ "id"
+ ]
+ }
+ ]
+ },
+ {
+ "tableName": "attachment",
+ "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT, `message` INTEGER NOT NULL, `sequence` INTEGER NOT NULL, `name` TEXT, `type` TEXT NOT NULL, `cid` TEXT, `size` INTEGER, `progress` INTEGER, `available` INTEGER NOT NULL, FOREIGN KEY(`message`) REFERENCES `message`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE )",
+ "fields": [
+ {
+ "fieldPath": "id",
+ "columnName": "id",
+ "affinity": "INTEGER",
+ "notNull": false
+ },
+ {
+ "fieldPath": "message",
+ "columnName": "message",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "sequence",
+ "columnName": "sequence",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "name",
+ "columnName": "name",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "type",
+ "columnName": "type",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "cid",
+ "columnName": "cid",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "size",
+ "columnName": "size",
+ "affinity": "INTEGER",
+ "notNull": false
+ },
+ {
+ "fieldPath": "progress",
+ "columnName": "progress",
+ "affinity": "INTEGER",
+ "notNull": false
+ },
+ {
+ "fieldPath": "available",
+ "columnName": "available",
+ "affinity": "INTEGER",
+ "notNull": true
+ }
+ ],
+ "primaryKey": {
+ "columnNames": [
+ "id"
+ ],
+ "autoGenerate": true
+ },
+ "indices": [
+ {
+ "name": "index_attachment_message",
+ "unique": false,
+ "columnNames": [
+ "message"
+ ],
+ "createSql": "CREATE INDEX `index_attachment_message` ON `${TABLE_NAME}` (`message`)"
+ },
+ {
+ "name": "index_attachment_message_sequence",
+ "unique": true,
+ "columnNames": [
+ "message",
+ "sequence"
+ ],
+ "createSql": "CREATE UNIQUE INDEX `index_attachment_message_sequence` ON `${TABLE_NAME}` (`message`, `sequence`)"
+ },
+ {
+ "name": "index_attachment_message_cid",
+ "unique": true,
+ "columnNames": [
+ "message",
+ "cid"
+ ],
+ "createSql": "CREATE UNIQUE INDEX `index_attachment_message_cid` ON `${TABLE_NAME}` (`message`, `cid`)"
+ }
+ ],
+ "foreignKeys": [
+ {
+ "table": "message",
+ "onDelete": "CASCADE",
+ "onUpdate": "NO ACTION",
+ "columns": [
+ "message"
+ ],
+ "referencedColumns": [
+ "id"
+ ]
+ }
+ ]
+ },
+ {
+ "tableName": "operation",
+ "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT, `folder` INTEGER NOT NULL, `message` INTEGER NOT NULL, `name` TEXT NOT NULL, `args` TEXT NOT NULL, `created` INTEGER NOT NULL, FOREIGN KEY(`folder`) REFERENCES `folder`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE , FOREIGN KEY(`message`) REFERENCES `message`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE )",
+ "fields": [
+ {
+ "fieldPath": "id",
+ "columnName": "id",
+ "affinity": "INTEGER",
+ "notNull": false
+ },
+ {
+ "fieldPath": "folder",
+ "columnName": "folder",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "message",
+ "columnName": "message",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "name",
+ "columnName": "name",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "args",
+ "columnName": "args",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "created",
+ "columnName": "created",
+ "affinity": "INTEGER",
+ "notNull": true
+ }
+ ],
+ "primaryKey": {
+ "columnNames": [
+ "id"
+ ],
+ "autoGenerate": true
+ },
+ "indices": [
+ {
+ "name": "index_operation_folder",
+ "unique": false,
+ "columnNames": [
+ "folder"
+ ],
+ "createSql": "CREATE INDEX `index_operation_folder` ON `${TABLE_NAME}` (`folder`)"
+ },
+ {
+ "name": "index_operation_message",
+ "unique": false,
+ "columnNames": [
+ "message"
+ ],
+ "createSql": "CREATE INDEX `index_operation_message` ON `${TABLE_NAME}` (`message`)"
+ }
+ ],
+ "foreignKeys": [
+ {
+ "table": "folder",
+ "onDelete": "CASCADE",
+ "onUpdate": "NO ACTION",
+ "columns": [
+ "folder"
+ ],
+ "referencedColumns": [
+ "id"
+ ]
+ },
+ {
+ "table": "message",
+ "onDelete": "CASCADE",
+ "onUpdate": "NO ACTION",
+ "columns": [
+ "message"
+ ],
+ "referencedColumns": [
+ "id"
+ ]
+ }
+ ]
+ },
+ {
+ "tableName": "answer",
+ "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT, `name` TEXT NOT NULL, `text` TEXT NOT NULL)",
+ "fields": [
+ {
+ "fieldPath": "id",
+ "columnName": "id",
+ "affinity": "INTEGER",
+ "notNull": false
+ },
+ {
+ "fieldPath": "name",
+ "columnName": "name",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "text",
+ "columnName": "text",
+ "affinity": "TEXT",
+ "notNull": true
+ }
+ ],
+ "primaryKey": {
+ "columnNames": [
+ "id"
+ ],
+ "autoGenerate": true
+ },
+ "indices": [],
+ "foreignKeys": []
+ },
+ {
+ "tableName": "log",
+ "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT, `time` INTEGER NOT NULL, `data` TEXT NOT NULL)",
+ "fields": [
+ {
+ "fieldPath": "id",
+ "columnName": "id",
+ "affinity": "INTEGER",
+ "notNull": false
+ },
+ {
+ "fieldPath": "time",
+ "columnName": "time",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "data",
+ "columnName": "data",
+ "affinity": "TEXT",
+ "notNull": true
+ }
+ ],
+ "primaryKey": {
+ "columnNames": [
+ "id"
+ ],
+ "autoGenerate": true
+ },
+ "indices": [
+ {
+ "name": "index_log_time",
+ "unique": false,
+ "columnNames": [
+ "time"
+ ],
+ "createSql": "CREATE INDEX `index_log_time` ON `${TABLE_NAME}` (`time`)"
+ }
+ ],
+ "foreignKeys": []
+ }
+ ],
+ "setupQueries": [
+ "CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)",
+ "INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, \"0e5181b4dbd365cbcda7fef652ef9629\")"
+ ]
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/org/dystopia/email/DB.java b/app/src/main/java/org/dystopia/email/DB.java
index 0dbae62d..a6151dd1 100644
--- a/app/src/main/java/org/dystopia/email/DB.java
+++ b/app/src/main/java/org/dystopia/email/DB.java
@@ -41,7 +41,7 @@ import org.json.JSONObject;
// https://developer.android.com/topic/libraries/architecture/room.html
@Database(
- version = 24,
+ version = 25,
entities = {
EntityIdentity.class,
EntityAccount.class,
@@ -349,11 +349,20 @@ public abstract class DB extends RoomDatabase {
@Override
public void migrate(SupportSQLiteDatabase db) {
Log.i(
- Helper.TAG, "DB migration from version " + startVersion + " to " + endVersion);
+ Helper.TAG, "DB migration from version " + startVersion + " to " + endVersion);
db.execSQL("ALTER TABLE `message` ADD COLUMN `account_name` TEXT");
}
})
- .build();
+ .addMigrations(
+ new Migration(24, 25) {
+ @Override
+ public void migrate(SupportSQLiteDatabase db) {
+ Log.i(
+ Helper.TAG, "DB migration from version " + startVersion + " to " + endVersion);
+ db.execSQL("ALTER TABLE `folder` ADD COLUMN `sync_state` TEXT");
+ }
+ })
+ .build();
}
public static class Converters {
diff --git a/app/src/main/java/org/dystopia/email/DaoFolder.java b/app/src/main/java/org/dystopia/email/DaoFolder.java
index ea7c7c44..b006a733 100644
--- a/app/src/main/java/org/dystopia/email/DaoFolder.java
+++ b/app/src/main/java/org/dystopia/email/DaoFolder.java
@@ -17,6 +17,7 @@ package org.dystopia.email;
along with FairEmail. If not, see .
Copyright 2018, Marcel Bokhorst (M66B)
+ Copyright 2018, Distopico (dystopia project) and contributors
*/
import androidx.lifecycle.LiveData;
@@ -27,76 +28,53 @@ import java.util.List;
@Dao
public interface DaoFolder {
- @Query(
- "SELECT * FROM folder"
- + " WHERE account = :account"
- + " ORDER BY CASE WHEN folder.type = '"
- + EntityFolder.USER
- + "' THEN 1 ELSE 0 END")
+ @Query("SELECT * FROM folder" + " WHERE account = :account"
+ + " ORDER BY CASE WHEN folder.type = '" + EntityFolder.USER + "' THEN 1 ELSE 0 END")
List getFolders(long account);
- @Query(
- "SELECT * FROM folder"
- + " WHERE account = :account"
- + " AND synchronize = :synchronize"
- + " ORDER BY CASE WHEN folder.type = '"
- + EntityFolder.USER
- + "' THEN 1 ELSE 0 END")
+ @Query("SELECT * FROM folder" + " WHERE account = :account" + " AND synchronize = :synchronize"
+ + " ORDER BY CASE WHEN folder.type = '" + EntityFolder.USER + "' THEN 1 ELSE 0 END")
List getFolders(long account, boolean synchronize);
- @Query(
- "SELECT * FROM folder"
- + " WHERE account = :account"
- + " AND type = '"
- + EntityFolder.USER
- + "'")
+ @Query("SELECT * FROM folder" + " WHERE account = :account" + " AND type = '" + EntityFolder.USER
+ + "'")
List getUserFolders(long account);
- @Query(
- "SELECT folder.*, account.name AS accountName"
- + ", COUNT(message.id) AS messages"
- + ", SUM(CASE WHEN message.content = 1 THEN 1 ELSE 0 END) AS content"
- + ", SUM(CASE WHEN message.ui_seen = 0 THEN 1 ELSE 0 END) AS unseen"
- + " FROM folder"
- + " LEFT JOIN account ON account.id = folder.account"
- + " LEFT JOIN message ON message.folder = folder.id AND NOT message.ui_hide"
- + " WHERE folder.account = :account OR folder.account IS NULL"
- + " GROUP BY folder.id")
+ @Query("SELECT * FROM folder WHERE unified")
+ List getUnifiedFolders();
+
+ @Query("SELECT folder.*, account.name AS accountName, account.state as accountState"
+ + ", COUNT(message.id) AS messages"
+ + ", SUM(CASE WHEN message.content = 1 THEN 1 ELSE 0 END) AS content"
+ + ", SUM(CASE WHEN message.ui_seen = 0 THEN 1 ELSE 0 END) AS unseen" + " FROM folder"
+ + " LEFT JOIN account ON account.id = folder.account"
+ + " LEFT JOIN message ON message.folder = folder.id AND NOT message.ui_hide"
+ + " WHERE folder.account = :account OR folder.account IS NULL" + " GROUP BY folder.id")
LiveData> liveFolders(long account);
- @Query(
- "SELECT * FROM folder"
- + " WHERE (:account < 0 OR folder.account = :account)"
- + " AND type <> '"
- + EntityFolder.USER
- + "'")
+ @Query("SELECT * FROM folder" + " WHERE (:account < 0 OR folder.account = :account)"
+ + " AND type <> '" + EntityFolder.USER + "'")
LiveData> liveSystemFolders(long account);
- @Query(
- "SELECT folder.*, account.name AS accountName"
- + ", COUNT(message.id) AS messages"
- + ", SUM(CASE WHEN message.content = 1 THEN 1 ELSE 0 END) AS content"
- + ", SUM(CASE WHEN message.ui_seen = 0 THEN 1 ELSE 0 END) AS unseen"
- + " FROM folder"
- + " JOIN account ON account.id = folder.account"
- + " JOIN message ON message.folder = folder.id AND NOT message.ui_hide"
- + " WHERE account.`synchronize`"
- + " AND folder.unified"
- + " GROUP BY folder.id")
+ @Query("SELECT folder.*, account.name AS accountName, account.state as accountState"
+ + ", COUNT(message.id) AS messages"
+ + ", SUM(CASE WHEN message.content = 1 THEN 1 ELSE 0 END) AS content"
+ + ", SUM(CASE WHEN message.ui_seen = 0 THEN 1 ELSE 0 END) AS unseen" + " FROM folder"
+ + " JOIN account ON account.id = folder.account"
+ + " JOIN message ON message.folder = folder.id AND NOT message.ui_hide"
+ + " WHERE account.`synchronize`" + " AND folder.unified" + " GROUP BY folder.id")
LiveData> liveUnified();
@Query("SELECT folder.* FROM folder WHERE folder.id = :id")
LiveData liveFolder(long id);
- @Query(
- "SELECT folder.*, account.name AS accountName"
- + ", COUNT(message.id) AS messages"
- + ", SUM(CASE WHEN message.content = 1 THEN 1 ELSE 0 END) AS content"
- + ", SUM(CASE WHEN message.ui_seen = 0 THEN 1 ELSE 0 END) AS unseen"
- + " FROM folder"
- + " LEFT JOIN account ON account.id = folder.account"
- + " LEFT JOIN message ON message.folder = folder.id AND NOT message.ui_hide"
- + " WHERE folder.id = :id")
+ @Query("SELECT folder.*, account.name AS accountName, account.state as accountState"
+ + ", COUNT(message.id) AS messages"
+ + ", SUM(CASE WHEN message.content = 1 THEN 1 ELSE 0 END) AS content"
+ + ", SUM(CASE WHEN message.ui_seen = 0 THEN 1 ELSE 0 END) AS unseen" + " FROM folder"
+ + " LEFT JOIN account ON account.id = folder.account"
+ + " LEFT JOIN message ON message.folder = folder.id AND NOT message.ui_hide"
+ + " WHERE folder.id = :id")
LiveData liveFolderEx(long id);
@Query("SELECT * FROM folder WHERE id = :id")
@@ -109,20 +87,12 @@ public interface DaoFolder {
EntityFolder getFolderByType(long account, String type);
// For debug/crash info
- @Query(
- "SELECT folder.* FROM folder"
- + " JOIN account ON account.id = folder.account"
- + " WHERE `primary` AND type = '"
- + EntityFolder.DRAFTS
- + "'")
+ @Query("SELECT folder.* FROM folder" + " JOIN account ON account.id = folder.account"
+ + " WHERE `primary` AND type = '" + EntityFolder.DRAFTS + "'")
EntityFolder getPrimaryDrafts();
- @Query(
- "SELECT folder.* FROM folder"
- + " JOIN account ON account.id = folder.account"
- + " WHERE `primary` AND type = '"
- + EntityFolder.ARCHIVE
- + "'")
+ @Query("SELECT folder.* FROM folder" + " JOIN account ON account.id = folder.account"
+ + " WHERE `primary` AND type = '" + EntityFolder.ARCHIVE + "'")
EntityFolder getPrimaryArchive();
@Query("SELECT * FROM folder WHERE type = '" + EntityFolder.OUTBOX + "'")
@@ -134,6 +104,9 @@ public interface DaoFolder {
@Query("UPDATE folder SET state = :state WHERE id = :id")
int setFolderState(long id, String state);
+ @Query("UPDATE folder SET sync_state = :state WHERE id = :id")
+ int setFolderSyncState(long id, String state);
+
@Query("UPDATE folder SET error = :error WHERE id = :id")
int setFolderError(long id, String error);
@@ -143,23 +116,11 @@ public interface DaoFolder {
@Query("UPDATE folder" + " SET type = '" + EntityFolder.USER + "'" + " WHERE account = :account")
int setFoldersUser(long account);
- @Query(
- "UPDATE folder"
- + " SET name = :name"
- + ", display = :display"
- + ", hide = :hide"
- + ", synchronize = :synchronize"
- + ", unified = :unified"
- + ", `after` = :after"
- + " WHERE id = :id")
- int setFolderProperties(
- long id,
- String name,
- String display,
- boolean hide,
- boolean synchronize,
- boolean unified,
- int after);
+ @Query("UPDATE folder" + " SET name = :name" + ", display = :display" + ", hide = :hide"
+ + ", synchronize = :synchronize" + ", unified = :unified" + ", `after` = :after"
+ + " WHERE id = :id")
+ int setFolderProperties(long id, String name, String display, boolean hide, boolean synchronize,
+ boolean unified, int after);
@Query("UPDATE folder SET name = :name WHERE account = :account AND name = :old")
int renameFolder(long account, String old, String name);
diff --git a/app/src/main/java/org/dystopia/email/DaoOperation.java b/app/src/main/java/org/dystopia/email/DaoOperation.java
index 48c7a5d8..2d550c97 100644
--- a/app/src/main/java/org/dystopia/email/DaoOperation.java
+++ b/app/src/main/java/org/dystopia/email/DaoOperation.java
@@ -36,8 +36,10 @@ public interface DaoOperation {
@Query("SELECT * FROM operation ORDER BY id")
LiveData> liveOperations();
- @Query("SELECT COUNT(id) FROM operation WHERE folder = :folder")
- int getOperationCount(long folder);
+ @Query("SELECT COUNT(id) FROM operation" +
+ " WHERE folder = :folder" +
+ " AND (:name IS NULL OR operation.name = :name)")
+ int getOperationCount(long folder, String name);
@Insert
long insertOperation(EntityOperation operation);
diff --git a/app/src/main/java/org/dystopia/email/EntityFolder.java b/app/src/main/java/org/dystopia/email/EntityFolder.java
index 7f5a5dc3..7653369e 100644
--- a/app/src/main/java/org/dystopia/email/EntityFolder.java
+++ b/app/src/main/java/org/dystopia/email/EntityFolder.java
@@ -66,6 +66,7 @@ public class EntityFolder implements Serializable {
@NonNull public Boolean hide = false;
@NonNull public Boolean unified = false;
public String state;
+ public String sync_state;
public String error;
static final String INBOX = "Inbox";
@@ -112,6 +113,7 @@ public class EntityFolder implements Serializable {
&& this.hide == other.hide
&& this.unified == other.unified
&& (this.state == null ? other.state == null : this.state.equals(other.state))
+ && (this.sync_state == null ? other.sync_state == null : this.sync_state.equals(other.sync_state))
&& (this.error == null ? other.error == null : this.error.equals(other.error)));
} else {
return false;
diff --git a/app/src/main/java/org/dystopia/email/EntityOperation.java b/app/src/main/java/org/dystopia/email/EntityOperation.java
index b9c202f9..1b75020d 100644
--- a/app/src/main/java/org/dystopia/email/EntityOperation.java
+++ b/app/src/main/java/org/dystopia/email/EntityOperation.java
@@ -71,6 +71,7 @@ public class EntityOperation {
public static final String BODY = "body";
public static final String ATTACHMENT = "attachment";
public static final String FLAG = "flag";
+ public static final String SYNC = "sync";
private static List queue = new ArrayList<>();
@@ -85,13 +86,6 @@ public class EntityOperation {
queue(db, message, name, jsonArray);
}
- static void queue(DB db, EntityMessage message, String name, Object value1, Object value2) {
- JSONArray jsonArray = new JSONArray();
- jsonArray.put(value1);
- jsonArray.put(value2);
- queue(db, message, name, jsonArray);
- }
-
private static void queue(DB db, EntityMessage message, String name, JSONArray jsonArray) {
EntityOperation operation = new EntityOperation();
operation.folder = message.folder;
@@ -124,6 +118,20 @@ public class EntityOperation {
+ operation.args);
}
+ private static void queue(DB db, long folder, Long message, String name, JSONArray jargs) {
+ EntityOperation operation = new EntityOperation();
+ operation.folder = folder;
+ operation.message = message;
+ operation.name = name;
+ operation.args = jargs.toString();
+ operation.created = new Date().getTime();
+ operation.id = db.operation().insertOperation(operation);
+
+ Log.i(Helper.TAG, "Queued op=" + operation.id + "/" + operation.name +
+ " msg=" + operation.folder + "/" + operation.message +
+ " args=" + operation.args);
+ }
+
public static void process(Context context) {
// Processing needs to be done after committing to the database
LocalBroadcastManager lbm = LocalBroadcastManager.getInstance(context);
@@ -135,6 +143,13 @@ public class EntityOperation {
}
}
+ static void sync(DB db, long folder) {
+ if (db.operation().getOperationCount(folder, EntityOperation.SYNC) == 0) {
+ queue(db, folder, null, EntityOperation.SYNC, new JSONArray());
+ db.folder().setFolderSyncState(folder, "requested");
+ }
+ }
+
@Override
public boolean equals(Object obj) {
if (obj instanceof EntityOperation) {
diff --git a/app/src/main/java/org/dystopia/email/FragmentMessages.java b/app/src/main/java/org/dystopia/email/FragmentMessages.java
index 390dd247..605e8427 100644
--- a/app/src/main/java/org/dystopia/email/FragmentMessages.java
+++ b/app/src/main/java/org/dystopia/email/FragmentMessages.java
@@ -2,18 +2,18 @@ package org.dystopia.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, Marcel Bokhorst (M66B)
* Copyright 2018, Distopico (dystopia project) and contributors
*/
@@ -59,6 +59,7 @@ import androidx.recyclerview.selection.StorageStrategy;
import androidx.recyclerview.widget.ItemTouchHelper;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
+import androidx.swiperefreshlayout.widget.SwipeRefreshLayout;
import com.google.android.material.bottomnavigation.BottomNavigationView;
import com.google.android.material.floatingactionbutton.FloatingActionButton;
import com.google.android.material.snackbar.Snackbar;
@@ -73,6 +74,7 @@ import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class FragmentMessages extends FragmentEx {
+ private SwipeRefreshLayout swipeRefresh;
private ViewGroup view;
private View popupAnchor;
private ImageButton ibHintSupport;
@@ -157,6 +159,7 @@ public class FragmentMessages extends FragmentEx {
setHasOptionsMenu(true);
// Get controls
+ swipeRefresh = view.findViewById(R.id.swipeRefresh);
popupAnchor = view.findViewById(R.id.popupAnchor);
ibHintSupport = view.findViewById(R.id.ibHintSupport);
ibHintSwipe = view.findViewById(R.id.ibHintSwipe);
@@ -176,6 +179,16 @@ public class FragmentMessages extends FragmentEx {
// Wire controls
+ swipeRefresh.setOnRefreshListener(new SwipeRefreshLayout.OnRefreshListener() {
+ @Override
+ public void onRefresh() {
+ Bundle args = new Bundle();
+ args.putLong("account", account);
+ args.putLong("folder", folder);
+ onRefreshHandler(args);
+ }
+ });
+
ibHintSwipe.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
@@ -275,10 +288,13 @@ public class FragmentMessages extends FragmentEx {
selectionTracker.addObserver(new SelectionTracker.SelectionObserver() {
@Override
public void onSelectionChanged() {
+ swipeRefresh.setEnabled(false);
+
if (selectionTracker.hasSelection()) {
fabMove.show();
} else {
fabMove.hide();
+ swipeRefresh.setEnabled(true);
}
}
});
@@ -718,10 +734,12 @@ public class FragmentMessages extends FragmentEx {
});
// Initialize
+ swipeRefresh.setEnabled(viewType == AdapterMessage.ViewType.UNIFIED || viewType == AdapterMessage.ViewType.FOLDER);
tvNoEmail.setVisibility(View.GONE);
bottom_navigation.setVisibility(View.GONE);
grpReady.setVisibility(View.GONE);
- pbWait.setVisibility(View.VISIBLE);
+ // TODO: implement in load more items
+ pbWait.setVisibility(View.GONE);
fab.hide();
fabMove.hide();
@@ -803,6 +821,16 @@ public class FragmentMessages extends FragmentEx {
} else {
setSubtitle(name);
}
+
+ boolean isRefreshing = false;
+ for (TupleFolderEx folder : folders) {
+ if (folder.sync_state != null && "connected".equals(folder.accountState)) {
+ isRefreshing = true;
+ break;
+ }
+ }
+
+ swipeRefresh.setRefreshing(isRefreshing);
}
});
break;
@@ -827,6 +855,10 @@ public class FragmentMessages extends FragmentEx {
outbox = EntityFolder.OUTBOX.equals(folder.type);
getActivity().invalidateOptionsMenu();
}
+
+ swipeRefresh.setRefreshing(folder != null && folder.sync_state != null &&
+ "connected".equals(EntityFolder.OUTBOX.equals(folder.type)
+ ? folder.state : folder.accountState));
}
});
break;
@@ -1014,6 +1046,54 @@ public class FragmentMessages extends FragmentEx {
}
}
+ private void onRefreshHandler(Bundle args) {
+ new SimpleTask() {
+ @Override
+ protected Boolean onLoad(Context context, Bundle args) {
+ long aid = args.getLong("account");
+ long fid = args.getLong("folder");
+
+ DB db = DB.getInstance(context);
+ boolean isConnected = false;
+
+ try {
+ db.beginTransaction();
+
+ List folders = new ArrayList<>();
+ if (aid < 0) {
+ folders.addAll(db.folder().getUnifiedFolders());
+ } else {
+ folders.add(db.folder().getFolder(fid));
+ }
+
+ for (EntityFolder folder : folders) {
+ EntityOperation.sync(db, folder.id);
+
+ if (folder.account == null) { // outbox
+ isConnected = "connected".equals(folder.state);
+ } else {
+ EntityAccount account = db.account().getAccount(folder.account);
+ isConnected = "connected".equals(account.state);
+ }
+ }
+
+ db.setTransactionSuccessful();
+ } finally {
+ db.endTransaction();
+ }
+
+ return isConnected;
+ }
+
+ @Override
+ protected void onLoaded(Bundle args, Boolean isConnected) {
+ if (!isConnected) {
+ swipeRefresh.setRefreshing(false);
+ }
+ }
+ }.load(FragmentMessages.this, args);
+ }
+
private void onMenuFolders() {
getFragmentManager().popBackStack("unified", 0);
@@ -1098,12 +1178,12 @@ public class FragmentMessages extends FragmentEx {
new BoundaryCallbackMessages.IBoundaryCallbackMessages() {
@Override
public void onLoading() {
- pbWait.setVisibility(View.VISIBLE);
+ swipeRefresh.setRefreshing(true);
}
@Override
public void onLoaded() {
- pbWait.setVisibility(View.GONE);
+ swipeRefresh.setRefreshing(false);
}
@Override
@@ -1141,12 +1221,12 @@ public class FragmentMessages extends FragmentEx {
@Override
public void onLoading() {
tvNoEmail.setVisibility(View.GONE);
- pbWait.setVisibility(View.VISIBLE);
+ swipeRefresh.setRefreshing(true);
}
@Override
public void onLoaded() {
- pbWait.setVisibility(View.GONE);
+ swipeRefresh.setRefreshing(false);
if (messages.getValue() == null || messages.getValue().size() == 0) {
tvNoEmail.setVisibility(View.VISIBLE);
}
@@ -1247,7 +1327,7 @@ public class FragmentMessages extends FragmentEx {
boolean searching = (searchCallback != null && searchCallback.isSearching());
if (!searching) {
- pbWait.setVisibility(View.GONE);
+ swipeRefresh.setRefreshing(false);
}
grpReady.setVisibility(View.VISIBLE);
diff --git a/app/src/main/java/org/dystopia/email/ServiceSynchronize.java b/app/src/main/java/org/dystopia/email/ServiceSynchronize.java
index 34edf35a..0f164b84 100644
--- a/app/src/main/java/org/dystopia/email/ServiceSynchronize.java
+++ b/app/src/main/java/org/dystopia/email/ServiceSynchronize.java
@@ -2,18 +2,18 @@ package org.dystopia.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, Marcel Bokhorst (M66B)
* Copyright 2018, Distopico (dystopia project) and contributors
*/
@@ -1093,7 +1093,7 @@ public class ServiceSynchronize extends LifecycleService {
// Prevent unnecessary folder
// connections
if (ACTION_PROCESS_OPERATIONS.equals(intent.getAction())) {
- if (db.operation().getOperationCount(fid) == 0) {
+ if (db.operation().getOperationCount(fid, null) == 0) {
return;
}
}
@@ -1152,7 +1152,7 @@ public class ServiceSynchronize extends LifecycleService {
lbm.registerReceiver(processFolder, f);
for (EntityFolder folder : folders.keySet()) {
- if (db.operation().getOperationCount(folder.id) > 0) {
+ if (db.operation().getOperationCount(folder.id, null) > 0) {
Intent intent = new Intent();
intent.setType("account/" + account.id);
intent.setAction(ServiceSynchronize.ACTION_PROCESS_OPERATIONS);
diff --git a/app/src/main/java/org/dystopia/email/TupleFolderEx.java b/app/src/main/java/org/dystopia/email/TupleFolderEx.java
index 45e6da41..f0a77736 100644
--- a/app/src/main/java/org/dystopia/email/TupleFolderEx.java
+++ b/app/src/main/java/org/dystopia/email/TupleFolderEx.java
@@ -21,6 +21,7 @@ package org.dystopia.email;
public class TupleFolderEx extends EntityFolder {
public String accountName;
+ public String accountState;
public int messages;
public int content;
public int unseen;
@@ -33,6 +34,9 @@ public class TupleFolderEx extends EntityFolder {
&& (this.accountName == null
? other.accountName == null
: accountName.equals(other.accountName))
+ && (this.accountState == null
+ ? other.accountState == null
+ : accountState.equals(other.accountState))
&& this.messages == other.messages
&& this.content == other.content
&& this.unseen == other.unseen);
diff --git a/app/src/main/res/layout/fragment_messages.xml b/app/src/main/res/layout/fragment_messages.xml
index c2b7f45b..2730f0e0 100644
--- a/app/src/main/res/layout/fragment_messages.xml
+++ b/app/src/main/res/layout/fragment_messages.xml
@@ -6,6 +6,11 @@
android:layout_height="match_parent"
tools:context=".ActivityView">
+
+
@@ -29,7 +34,7 @@
android:textColor="?android:attr/textColorPrimary"
app:layout_constraintEnd_toStartOf="@+id/ibHintSupport"
app:layout_constraintEnd_toEndOf="parent"
- app:layout_constraintTop_toTopOf="parent"
+ app:layout_constraintTop_toTopOf="parent"
app:layout_constraintStart_toStartOf="parent" />
+ app:layout_constraintStart_toStartOf="parent" />
-
+
+
+
8dp
6dp
40dp
+ 40dp
24dp