diff --git a/app/schemas/eu.faircode.email.DB/43.json b/app/schemas/eu.faircode.email.DB/43.json new file mode 100644 index 0000000000..954f0ecd61 --- /dev/null +++ b/app/schemas/eu.faircode.email.DB/43.json @@ -0,0 +1,1459 @@ +{ + "formatVersion": 1, + "database": { + "version": 43, + "identityHash": "3e32143abd57e00da66b87ee98b60741", + "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, `plain_only` INTEGER NOT NULL, `delivery_receipt` INTEGER NOT NULL, `read_receipt` INTEGER NOT NULL, `store_sent` INTEGER NOT NULL, `sent_folder` INTEGER, `tbd` INTEGER, `state` TEXT, `error` TEXT, `last_connected` INTEGER, 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": "plain_only", + "columnName": "plain_only", + "affinity": "INTEGER", + "notNull": true + }, + { + "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 + }, + { + "fieldPath": "last_connected", + "columnName": "last_connected", + "affinity": "INTEGER", + "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, `pop` 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": "pop", + "columnName": "pop", + "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, `flags` TEXT, `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": "flags", + "columnName": "flags", + "affinity": "TEXT", + "notNull": false + }, + { + "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, \"3e32143abd57e00da66b87ee98b60741\")" + ] + } +} \ No newline at end of file diff --git a/app/src/main/java/eu/faircode/email/AdapterFolder.java b/app/src/main/java/eu/faircode/email/AdapterFolder.java index 3e8959e7d4..1b5dee48e4 100644 --- a/app/src/main/java/eu/faircode/email/AdapterFolder.java +++ b/app/src/main/java/eu/faircode/email/AdapterFolder.java @@ -238,15 +238,16 @@ public class AdapterFolder extends RecyclerView.Adapter> liveFolders(Long account); - @Query("SELECT folder.*, account.name AS accountName, account.color AS accountColor, account.state AS accountState" + + @Query("SELECT folder.*, account.name AS accountName, account.color AS accountColor, account.pop as accountPop, 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" + @@ -97,7 +97,7 @@ public interface DaoFolder { " AND (account.id = :account OR (:account IS NULL AND account.`primary`))") LiveData liveDrafts(Long account); - @Query("SELECT folder.*, account.name AS accountName, account.color AS accountColor, account.state AS accountState" + + @Query("SELECT folder.*, account.name AS accountName, account.color AS accountColor, account.pop as accountPop, 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" + @@ -118,6 +118,16 @@ public interface DaoFolder { @Query("SELECT * FROM folder WHERE id = :id") EntityFolder getFolder(Long id); + @Query("SELECT folder.*, account.name AS accountName, account.color AS accountColor, account.pop as accountPop, 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") + TupleFolderEx getFolderEx(Long id); + @Query("SELECT * FROM folder WHERE account = :account AND name = :name") EntityFolder getFolderByName(Long account, String name); diff --git a/app/src/main/java/eu/faircode/email/EntityAccount.java b/app/src/main/java/eu/faircode/email/EntityAccount.java index e44ecc1f3d..f1a5faf4e3 100644 --- a/app/src/main/java/eu/faircode/email/EntityAccount.java +++ b/app/src/main/java/eu/faircode/email/EntityAccount.java @@ -49,7 +49,9 @@ public class EntityAccount implements Serializable { @NonNull public Integer auth_type; @NonNull - public String host; // IMAP + public Boolean pop = false; + @NonNull + public String host; // POP3/IMAP @NonNull public Boolean starttls; @NonNull @@ -86,6 +88,10 @@ public class EntityAccount implements Serializable { public String error; public Long last_connected; + String getProtocol() { + return (pop ? "pop3" : "imap") + (starttls ? "" : "s"); + } + static String getNotificationChannelName(long account) { return "notification." + account; } @@ -110,6 +116,7 @@ public class EntityAccount implements Serializable { JSONObject json = new JSONObject(); json.put("id", id); json.put("auth_type", auth_type); + json.put("pop", pop); json.put("host", host); json.put("starttls", starttls); json.put("insecure", insecure); @@ -142,6 +149,8 @@ public class EntityAccount implements Serializable { EntityAccount account = new EntityAccount(); // id account.auth_type = json.getInt("auth_type"); + if (json.has("pop")) + account.pop = json.getBoolean("pop"); account.host = json.getString("host"); account.starttls = (json.has("starttls") && json.getBoolean("starttls")); account.insecure = (json.has("insecure") && json.getBoolean("insecure")); diff --git a/app/src/main/java/eu/faircode/email/EntityIdentity.java b/app/src/main/java/eu/faircode/email/EntityIdentity.java index bcbc3acc1d..b3b9bd1ffc 100644 --- a/app/src/main/java/eu/faircode/email/EntityIdentity.java +++ b/app/src/main/java/eu/faircode/email/EntityIdentity.java @@ -89,6 +89,10 @@ public class EntityIdentity { public String error; public Long last_connected; + String getProtocol() { + return (starttls ? "smtp" : "smtps"); + } + public JSONObject toJSON() throws JSONException { JSONObject json = new JSONObject(); json.put("id", id); diff --git a/app/src/main/java/eu/faircode/email/FragmentAccount.java b/app/src/main/java/eu/faircode/email/FragmentAccount.java index cd3eafb564..4a1b27d4a3 100644 --- a/app/src/main/java/eu/faircode/email/FragmentAccount.java +++ b/app/src/main/java/eu/faircode/email/FragmentAccount.java @@ -74,9 +74,11 @@ import java.util.Properties; import javax.mail.AuthenticationFailedException; import javax.mail.Folder; import javax.mail.Session; +import javax.mail.Store; import androidx.annotation.NonNull; import androidx.annotation.Nullable; +import androidx.appcompat.widget.SwitchCompat; import androidx.constraintlayout.widget.Group; import androidx.fragment.app.FragmentTransaction; @@ -91,6 +93,7 @@ public class FragmentAccount extends FragmentBase { private Button btnAutoConfig; private Button btnAuthorize; + private SwitchCompat swPop; private EditText etHost; private CheckBox cbStartTls; private CheckBox cbInsecure; @@ -165,6 +168,7 @@ public class FragmentAccount extends FragmentBase { btnAutoConfig = view.findViewById(R.id.btnAutoConfig); btnAuthorize = view.findViewById(R.id.btnAuthorize); + swPop = view.findViewById(R.id.swPop); etHost = view.findViewById(R.id.etHost); etPort = view.findViewById(R.id.etPort); cbStartTls = view.findViewById(R.id.cbStartTls); @@ -235,6 +239,7 @@ public class FragmentAccount extends FragmentBase { auth_type = Helper.AUTH_TYPE_PASSWORD; + swPop.setChecked(false); etHost.setText(provider.imap_host); etPort.setText(provider.imap_host == null ? null : Integer.toString(provider.imap_port)); cbStartTls.setChecked(provider.imap_starttls); @@ -280,10 +285,37 @@ public class FragmentAccount extends FragmentBase { } }); + swPop.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() { + @Override + public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) { + boolean starttls = cbStartTls.isChecked(); + if (isChecked) { + etHost.setHint("pop.domain.tld"); + etPort.setHint(starttls ? "110" : "995"); + etRealm.setText(null); + cbBrowse.setChecked(false); + etPrefix.setText(null); + btnCheck.setVisibility(View.GONE); + btnSave.setVisibility(View.VISIBLE); + } else { + etHost.setHint("imap.domain.tld"); + etPort.setHint(starttls ? "143" : "993"); + btnCheck.setVisibility(View.VISIBLE); + btnSave.setVisibility(View.GONE); + } + etRealm.setEnabled(!isChecked); + cbBrowse.setEnabled(!isChecked); + etPrefix.setEnabled(!isChecked); + } + }); + cbStartTls.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() { @Override public void onCheckedChanged(CompoundButton compoundButton, boolean checked) { - etPort.setHint(checked ? "143" : "993"); + if (swPop.isChecked()) + etPort.setHint(checked ? "110" : "995"); + else + etPort.setHint(checked ? "143" : "993"); } }); @@ -472,6 +504,7 @@ public class FragmentAccount extends FragmentBase { @Override protected void onExecuted(Bundle args, EmailProvider provider) { + swPop.setChecked(false); etHost.setText(provider.imap_host); etPort.setText(Integer.toString(provider.imap_port)); cbStartTls.setChecked(provider.imap_starttls); @@ -491,6 +524,7 @@ public class FragmentAccount extends FragmentBase { Bundle args = new Bundle(); args.putLong("id", id); args.putInt("auth_type", auth_type); + args.putBoolean("pop", swPop.isChecked()); args.putString("host", etHost.getText().toString()); args.putBoolean("starttls", cbStartTls.isChecked()); args.putBoolean("insecure", cbInsecure.isChecked()); @@ -523,6 +557,7 @@ public class FragmentAccount extends FragmentBase { protected CheckResult onExecute(Context context, Bundle args) throws Throwable { long id = args.getLong("id"); int auth_type = args.getInt("auth_type"); + boolean pop = args.getBoolean("pop"); String host = args.getString("host"); boolean starttls = args.getBoolean("starttls"); boolean insecure = args.getBoolean("insecure"); @@ -534,7 +569,10 @@ public class FragmentAccount extends FragmentBase { if (TextUtils.isEmpty(host)) throw new IllegalArgumentException(context.getString(R.string.title_no_host)); if (TextUtils.isEmpty(port)) - port = (starttls ? "143" : "993"); + if (pop) + port = (starttls ? "110" : "995"); + else + port = (starttls ? "143" : "993"); if (TextUtils.isEmpty(user)) throw new IllegalArgumentException(context.getString(R.string.title_no_user)); if (TextUtils.isEmpty(password) && !insecure) @@ -553,9 +591,9 @@ public class FragmentAccount extends FragmentBase { Properties props = MessageHelper.getSessionProperties(auth_type, realm, insecure); Session isession = Session.getInstance(props, null); isession.setDebug(true); - IMAPStore istore = null; + Store istore = null; try { - istore = (IMAPStore) isession.getStore(starttls ? "imap" : "imaps"); + istore = isession.getStore((pop ? "pop3" : "imap") + (starttls ? "" : "s")); try { istore.connect(host, Integer.parseInt(port), user, password); } catch (AuthenticationFailedException ex) { @@ -566,7 +604,8 @@ public class FragmentAccount extends FragmentBase { throw ex; } - result.idle = istore.hasCapability("IDLE"); + if (istore instanceof IMAPStore) + result.idle = ((IMAPStore) istore).hasCapability("IDLE"); boolean inbox = false; boolean archive = false; @@ -583,7 +622,11 @@ public class FragmentAccount extends FragmentBase { for (Folder ifolder : istore.getDefaultFolder().list("*")) { // Check folder attributes String fullName = ifolder.getFullName(); - String[] attrs = ((IMAPFolder) ifolder).getAttributes(); + String[] attrs; + if (ifolder instanceof IMAPFolder) + attrs = ((IMAPFolder) ifolder).getAttributes(); + else + attrs = new String[0]; Log.i(fullName + " attrs=" + TextUtils.join(" ", attrs)); String type = EntityFolder.getType(attrs, fullName); @@ -660,9 +703,15 @@ public class FragmentAccount extends FragmentBase { @Override protected void onExecuted(Bundle args, CheckResult result) { - tvIdle.setVisibility(result.idle ? View.GONE : View.VISIBLE); + boolean pop = args.getBoolean("pop"); - setFolders(result.folders, result.account); + tvIdle.setVisibility(result.idle || pop ? View.GONE : View.VISIBLE); + + if (pop) { + grpFolders.setVisibility(View.GONE); + btnSave.setVisibility(View.VISIBLE); + } else + setFolders(result.folders, result.account); new Handler().post(new Runnable() { @Override @@ -721,6 +770,7 @@ public class FragmentAccount extends FragmentBase { args.putLong("id", id); args.putInt("auth_type", auth_type); + args.putBoolean("pop", swPop.isChecked()); args.putString("host", etHost.getText().toString()); args.putBoolean("starttls", cbStartTls.isChecked()); args.putBoolean("insecure", cbInsecure.isChecked()); @@ -770,6 +820,7 @@ public class FragmentAccount extends FragmentBase { long id = args.getLong("id"); int auth_type = args.getInt("auth_type"); + boolean pop = args.getBoolean("pop"); String host = args.getString("host"); boolean starttls = args.getBoolean("starttls"); boolean insecure = args.getBoolean("insecure"); @@ -799,7 +850,10 @@ public class FragmentAccount extends FragmentBase { if (TextUtils.isEmpty(host)) throw new IllegalArgumentException(context.getString(R.string.title_no_host)); if (TextUtils.isEmpty(port)) - port = (starttls ? "143" : "993"); + if (pop) + port = (starttls ? "110" : "995"); + else + port = (starttls ? "143" : "993"); if (TextUtils.isEmpty(user)) throw new IllegalArgumentException(context.getString(R.string.title_no_user)); if (synchronize && TextUtils.isEmpty(password) && !insecure) @@ -825,6 +879,7 @@ public class FragmentAccount extends FragmentBase { boolean check = (synchronize && (account == null || auth_type != account.auth_type || + pop != account.pop || !host.equals(account.host) || Integer.parseInt(port) != account.port || !user.equals(account.user) || !password.equals(account.password) || (realm == null ? accountRealm != null : !realm.equals(accountRealm)))); @@ -844,9 +899,9 @@ public class FragmentAccount extends FragmentBase { Session isession = Session.getInstance(props, null); isession.setDebug(true); - IMAPStore istore = null; + Store istore = null; try { - istore = (IMAPStore) isession.getStore(starttls ? "imap" : "imaps"); + istore = isession.getStore((pop ? "pop3" : "imap") + (starttls ? "" : "s")); try { istore.connect(host, Integer.parseInt(port), user, password); } catch (AuthenticationFailedException ex) { @@ -861,7 +916,11 @@ public class FragmentAccount extends FragmentBase { for (Folder ifolder : istore.getDefaultFolder().list("*")) { // Check folder attributes String fullName = ifolder.getFullName(); - String[] attrs = ((IMAPFolder) ifolder).getAttributes(); + String[] attrs; + if (ifolder instanceof IMAPFolder) + attrs = ((IMAPFolder) ifolder).getAttributes(); + else + attrs = new String[0]; Log.i(fullName + " attrs=" + TextUtils.join(" ", attrs)); String type = EntityFolder.getType(attrs, fullName); @@ -872,6 +931,7 @@ public class FragmentAccount extends FragmentBase { inbox.synchronize = true; inbox.unified = true; inbox.notify = true; + inbox.initialize = !pop; inbox.sync_days = EntityFolder.DEFAULT_SYNC; inbox.keep_days = EntityFolder.DEFAULT_KEEP; } @@ -894,6 +954,7 @@ public class FragmentAccount extends FragmentBase { account = new EntityAccount(); account.auth_type = auth_type; + account.pop = pop; account.host = host; account.starttls = starttls; account.insecure = insecure; @@ -1111,6 +1172,7 @@ public class FragmentAccount extends FragmentBase { spProvider.setTag(1); spProvider.setSelection(1); } + swPop.setChecked(account.pop); etHost.setText(account.host); etPort.setText(Long.toString(account.port)); } @@ -1173,6 +1235,15 @@ public class FragmentAccount extends FragmentBase { // Consider previous check/save/delete as cancelled pbWait.setVisibility(View.GONE); + if (account != null && account.pop) { + etRealm.setEnabled(false); + cbBrowse.setEnabled(false); + etPrefix.setEnabled(false); + grpFolders.setVisibility(View.GONE); + btnSave.setVisibility(View.VISIBLE); + return; + } + args.putLong("account", account == null ? -1 : account.id); new SimpleTask>() { diff --git a/app/src/main/java/eu/faircode/email/FragmentFolder.java b/app/src/main/java/eu/faircode/email/FragmentFolder.java index f55e1e13f9..6c01c3fe4f 100644 --- a/app/src/main/java/eu/faircode/email/FragmentFolder.java +++ b/app/src/main/java/eu/faircode/email/FragmentFolder.java @@ -41,6 +41,7 @@ import java.util.Calendar; import androidx.annotation.NonNull; import androidx.annotation.Nullable; +import androidx.constraintlayout.widget.Group; public class FragmentFolder extends FragmentBase { private ViewGroup view; @@ -58,6 +59,7 @@ public class FragmentFolder extends FragmentBase { private Button btnSave; private ContentLoadingProgressBar pbSave; private ContentLoadingProgressBar pbWait; + private Group grpPop; private long id = -1; private long account = -1; @@ -97,6 +99,7 @@ public class FragmentFolder extends FragmentBase { btnSave = view.findViewById(R.id.btnSave); pbSave = view.findViewById(R.id.pbSave); pbWait = view.findViewById(R.id.pbWait); + grpPop = view.findViewById(R.id.grpPop); cbUnified.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() { @Override @@ -136,6 +139,7 @@ public class FragmentFolder extends FragmentBase { btnSave.setEnabled(false); pbSave.setVisibility(View.GONE); pbWait.setVisibility(View.VISIBLE); + grpPop.setVisibility(View.GONE); return view; } @@ -361,15 +365,15 @@ public class FragmentFolder extends FragmentBase { Bundle args = new Bundle(); args.putLong("id", id); - new SimpleTask() { + new SimpleTask() { @Override - protected EntityFolder onExecute(Context context, Bundle args) { + protected TupleFolderEx onExecute(Context context, Bundle args) { long id = args.getLong("id"); - return DB.getInstance(context).folder().getFolder(id); + return DB.getInstance(context).folder().getFolderEx(id); } @Override - protected void onExecuted(Bundle args, EntityFolder folder) { + protected void onExecuted(Bundle args, TupleFolderEx folder) { if (savedInstanceState == null) { etName.setText(folder == null ? null : folder.name); etDisplay.setText(folder == null ? null : folder.display); @@ -389,6 +393,8 @@ public class FragmentFolder extends FragmentBase { // Consider previous save as cancelled pbWait.setVisibility(View.GONE); + grpPop.setVisibility(folder == null || !folder.accountPop ? View.VISIBLE : View.GONE); + Helper.setViewsEnabled(view, true); etName.setEnabled(folder == null); etDisplay.setEnabled(folder == null || !EntityFolder.INBOX.equals(folder.type)); diff --git a/app/src/main/java/eu/faircode/email/FragmentFolders.java b/app/src/main/java/eu/faircode/email/FragmentFolders.java index 25c9d0b1e4..b85ce52bfc 100644 --- a/app/src/main/java/eu/faircode/email/FragmentFolders.java +++ b/app/src/main/java/eu/faircode/email/FragmentFolders.java @@ -131,8 +131,7 @@ public class FragmentFolders extends FragmentBase { tbShowHidden.setVisibility(View.GONE); grpReady.setVisibility(View.GONE); pbWait.setVisibility(View.VISIBLE); - if (account < 0) - fab.hide(); + fab.hide(); return view; } @@ -155,6 +154,10 @@ public class FragmentFolders extends FragmentBase { @Override public void onChanged(@Nullable EntityAccount account) { setSubtitle(account == null ? null : account.name); + if (account == null || account.pop) + fab.hide(); + else + fab.show(); } }); diff --git a/app/src/main/java/eu/faircode/email/FragmentIdentity.java b/app/src/main/java/eu/faircode/email/FragmentIdentity.java index 531c1e95bb..17ff438fc5 100644 --- a/app/src/main/java/eu/faircode/email/FragmentIdentity.java +++ b/app/src/main/java/eu/faircode/email/FragmentIdentity.java @@ -585,11 +585,11 @@ public class FragmentIdentity extends FragmentBase { // Check SMTP server if (check) { - String transportType = (starttls ? "smtp" : "smtps"); + String protocol = (starttls ? "smtp" : "smtps"); Properties props = MessageHelper.getSessionProperties(auth_type, realm, insecure); Session isession = Session.getInstance(props, null); isession.setDebug(true); - Transport itransport = isession.getTransport(transportType); + Transport itransport = isession.getTransport(protocol); try { try { itransport.connect(host, Integer.parseInt(port), user, password); diff --git a/app/src/main/java/eu/faircode/email/Helper.java b/app/src/main/java/eu/faircode/email/Helper.java index b5011dc882..89c90e1547 100644 --- a/app/src/main/java/eu/faircode/email/Helper.java +++ b/app/src/main/java/eu/faircode/email/Helper.java @@ -56,7 +56,6 @@ import android.widget.Toast; import com.android.billingclient.api.BillingClient; import com.google.android.material.bottomnavigation.BottomNavigationView; -import com.sun.mail.imap.IMAPStore; import java.io.BufferedInputStream; import java.io.BufferedOutputStream; @@ -86,6 +85,7 @@ import java.util.concurrent.ThreadFactory; import javax.mail.Address; import javax.mail.AuthenticationFailedException; import javax.mail.MessagingException; +import javax.mail.Store; import javax.mail.internet.InternetAddress; import androidx.annotation.NonNull; @@ -779,7 +779,7 @@ public class Helper { return true; } - static void connect(Context context, IMAPStore istore, EntityAccount account) throws + static void connect(Context context, Store istore, EntityAccount account) throws MessagingException { try { istore.connect(account.host, account.port, account.user, account.password); diff --git a/app/src/main/java/eu/faircode/email/MessageHelper.java b/app/src/main/java/eu/faircode/email/MessageHelper.java index c08e2b5f20..3d564ed66e 100644 --- a/app/src/main/java/eu/faircode/email/MessageHelper.java +++ b/app/src/main/java/eu/faircode/email/MessageHelper.java @@ -165,6 +165,26 @@ public class MessageHelper { props.put("mail.smtp.writetimeout", Integer.toString(NETWORK_TIMEOUT)); // one thread overhead props.put("mail.smtp.timeout", Integer.toString(NETWORK_TIMEOUT)); + // https://javaee.github.io/javamail/docs/api/com/sun/mail/pop3/package-summary.html + props.put("mail.pop3s.ssl.checkserveridentity", checkserveridentity); + props.put("mail.pop3s.ssl.trust", "*"); + props.put("mail.pop3s.starttls.enable", "false"); + props.put("mail.pop3s.starttls.required", "false"); + + props.put("mail.pop3s.connectiontimeout", Integer.toString(NETWORK_TIMEOUT)); + props.put("mail.pop3s.timeout", Integer.toString(NETWORK_TIMEOUT)); + props.put("mail.pop3s.writetimeout", Integer.toString(NETWORK_TIMEOUT)); // one thread overhead + + props.put("mail.pop3.ssl.checkserveridentity", checkserveridentity); + props.put("mail.pop3.ssl.trust", "*"); + props.put("mail.pop3.starttls.enable", "true"); + props.put("mail.pop3.starttls.required", "true"); + + props.put("mail.pop3.connectiontimeout", Integer.toString(NETWORK_TIMEOUT)); + props.put("mail.pop3.timeout", Integer.toString(NETWORK_TIMEOUT)); + props.put("mail.pop3.writetimeout", Integer.toString(NETWORK_TIMEOUT)); // one thread overhead + + // MIME props.put("mail.mime.allowutf8", "true"); // SMTPTransport, MimeMessage props.put("mail.mime.address.strict", "false"); diff --git a/app/src/main/java/eu/faircode/email/ServiceSynchronize.java b/app/src/main/java/eu/faircode/email/ServiceSynchronize.java index c312ebe4b1..cd08cca975 100644 --- a/app/src/main/java/eu/faircode/email/ServiceSynchronize.java +++ b/app/src/main/java/eu/faircode/email/ServiceSynchronize.java @@ -56,6 +56,8 @@ import com.sun.mail.imap.IMAPStore; import com.sun.mail.imap.protocol.FetchResponse; import com.sun.mail.imap.protocol.IMAPProtocol; import com.sun.mail.imap.protocol.UID; +import com.sun.mail.pop3.POP3Folder; +import com.sun.mail.pop3.POP3Message; import com.sun.mail.util.FolderClosedIOException; import com.sun.mail.util.MailConnectException; @@ -104,6 +106,7 @@ import javax.mail.MessagingException; import javax.mail.NoSuchProviderException; import javax.mail.SendFailedException; import javax.mail.Session; +import javax.mail.Store; import javax.mail.StoreClosedException; import javax.mail.Transport; import javax.mail.UIDFolder; @@ -821,8 +824,8 @@ public class ServiceSynchronize extends LifecycleService { isession.setDebug(debug); // adb -t 1 logcat | grep "fairemail\|System.out" - final IMAPStore istore = (IMAPStore) isession.getStore(account.starttls ? "imap" : "imaps"); - final Map folders = new HashMap<>(); + final Store istore = isession.getStore(account.getProtocol()); + final Map folders = new HashMap<>(); List idlers = new ArrayList<>(); List handlers = new ArrayList<>(); try { @@ -935,8 +938,10 @@ public class ServiceSynchronize extends LifecycleService { throw ex; } - final boolean capIdle = istore.hasCapability("IDLE"); - final boolean capUidPlus = istore.hasCapability("UIDPLUS"); + final boolean capIdle = + (istore instanceof IMAPStore ? ((IMAPStore) istore).hasCapability("IDLE") : false); + final boolean capUidPlus = + (istore instanceof IMAPStore ? ((IMAPStore) istore).hasCapability("UIDPLUS") : false); Log.i(account.name + " idle=" + capIdle + " uidplus=" + capUidPlus); db.account().setAccountState(account.id, "connected"); @@ -957,7 +962,7 @@ public class ServiceSynchronize extends LifecycleService { db.folder().setFolderState(folder.id, "connecting"); - final IMAPFolder ifolder = (IMAPFolder) istore.getFolder(folder.name); + final Folder ifolder = istore.getFolder(folder.name); try { ifolder.open(Folder.READ_WRITE); } catch (MessagingException ex) { @@ -1002,7 +1007,7 @@ public class ServiceSynchronize extends LifecycleService { db.beginTransaction(); message = synchronizeMessage( ServiceSynchronize.this, - folder, ifolder, (IMAPMessage) imessage, + folder, (IMAPFolder) ifolder, (IMAPMessage) imessage, false, db.rule().getEnabledRules(folder.id)); db.setTransactionSuccessful(); @@ -1014,7 +1019,7 @@ public class ServiceSynchronize extends LifecycleService { try { db.beginTransaction(); downloadMessage(ServiceSynchronize.this, - folder, ifolder, + folder, (IMAPFolder) ifolder, (IMAPMessage) imessage, message.id); db.setTransactionSuccessful(); } finally { @@ -1051,7 +1056,7 @@ public class ServiceSynchronize extends LifecycleService { Log.i(folder.name + " messages removed"); for (Message imessage : e.getMessages()) try { - long uid = ifolder.getUID(imessage); + long uid = ((IMAPFolder) ifolder).getUID(imessage); DB db = DB.getInstance(ServiceSynchronize.this); int count = db.message().deleteMessage(folder.id, uid); @@ -1092,7 +1097,7 @@ public class ServiceSynchronize extends LifecycleService { db.beginTransaction(); message = synchronizeMessage( ServiceSynchronize.this, - folder, ifolder, (IMAPMessage) e.getMessage(), + folder, (IMAPFolder) ifolder, (IMAPMessage) e.getMessage(), false, db.rule().getEnabledRules(folder.id)); db.setTransactionSuccessful(); @@ -1104,7 +1109,7 @@ public class ServiceSynchronize extends LifecycleService { try { db.beginTransaction(); downloadMessage(ServiceSynchronize.this, - folder, ifolder, + folder, (IMAPFolder) ifolder, (IMAPMessage) e.getMessage(), message.id); db.setTransactionSuccessful(); } finally { @@ -1143,7 +1148,7 @@ public class ServiceSynchronize extends LifecycleService { Log.i(folder.name + " start idle"); while (state.running()) { Log.i(folder.name + " do idle"); - ifolder.idle(false); + ((IMAPFolder) ifolder).idle(false); } } catch (Throwable ex) { Log.e(folder.name, ex); @@ -1210,7 +1215,7 @@ public class ServiceSynchronize extends LifecycleService { Log.i(folder.name + " process"); // Get folder - IMAPFolder ifolder = folders.get(folder); // null when polling + Folder ifolder = folders.get(folder); // null when polling final boolean shouldClose = (ifolder == null); try { @@ -1223,7 +1228,7 @@ public class ServiceSynchronize extends LifecycleService { db.folder().setFolderState(folder.id, "connecting"); - ifolder = (IMAPFolder) istore.getFolder(folder.name); + ifolder = istore.getFolder(folder.name); ifolder.open(Folder.READ_WRITE); db.folder().setFolderState(folder.id, "connected"); @@ -1437,7 +1442,7 @@ public class ServiceSynchronize extends LifecycleService { private void processOperations( EntityAccount account, EntityFolder folder, - Session isession, IMAPStore istore, IMAPFolder ifolder, + Session isession, Store istore, Folder ifolder, ServiceState state) throws MessagingException, JSONException, IOException { try { @@ -1479,46 +1484,50 @@ public class ServiceSynchronize extends LifecycleService { // Operations should use database transaction when needed if (EntityOperation.SEEN.equals(op.name)) - doSeen(folder, ifolder, message, jargs, db); + doSeen(folder, (IMAPFolder) ifolder, message, jargs, db); else if (EntityOperation.FLAG.equals(op.name)) - doFlag(folder, ifolder, message, jargs, db); + doFlag(folder, (IMAPFolder) ifolder, message, jargs, db); else if (EntityOperation.ANSWERED.equals(op.name)) - doAnswered(folder, ifolder, message, jargs, db); + doAnswered(folder, (IMAPFolder) ifolder, message, jargs, db); else if (EntityOperation.KEYWORD.equals(op.name)) - doKeyword(folder, ifolder, message, jargs, db); + doKeyword(folder, (IMAPFolder) ifolder, message, jargs, db); else if (EntityOperation.ADD.equals(op.name)) - doAdd(folder, isession, istore, ifolder, message, jargs, db); + doAdd(folder, isession, (IMAPStore) istore, (IMAPFolder) ifolder, message, jargs, db); else if (EntityOperation.MOVE.equals(op.name)) - doMove(folder, isession, istore, ifolder, message, jargs, db); + doMove(folder, isession, (IMAPStore) istore, (IMAPFolder) ifolder, message, jargs, db); else if (EntityOperation.DELETE.equals(op.name)) - doDelete(folder, ifolder, message, jargs, db); + doDelete(folder, (IMAPFolder) ifolder, message, jargs, db); else if (EntityOperation.SEND.equals(op.name)) doSend(message, db); else if (EntityOperation.HEADERS.equals(op.name)) - doHeaders(folder, ifolder, message, db); + doHeaders(folder, (IMAPFolder) ifolder, message, db); else if (EntityOperation.RAW.equals(op.name)) - doRaw(folder, ifolder, message, jargs, db); + doRaw(folder, (IMAPFolder) ifolder, message, jargs, db); else if (EntityOperation.BODY.equals(op.name)) - doBody(folder, ifolder, message, db); + doBody(folder, (IMAPFolder) ifolder, message, db); else if (EntityOperation.ATTACHMENT.equals(op.name)) - doAttachment(folder, op, ifolder, message, jargs, db); + doAttachment(folder, op, (IMAPFolder) ifolder, message, jargs, db); else if (EntityOperation.SYNC.equals(op.name)) if (EntityFolder.OUTBOX.equals(folder.type)) db.folder().setFolderError(folder.id, null); - else - synchronizeMessages(account, folder, ifolder, jargs, state); + else { + if (ifolder instanceof IMAPFolder) + synchronizeMessages(account, folder, (IMAPFolder) ifolder, jargs, state); + else if (ifolder instanceof POP3Folder) + synchronizeMessages(account, folder, (POP3Folder) ifolder, jargs, state); + } else throw new MessagingException("Unknown operation name=" + op.name); @@ -1897,8 +1906,6 @@ public class ServiceSynchronize extends LifecycleService { db.message().setMessageLastAttempt(message.id, message.last_attempt); } - String transportType = (ident.starttls ? "smtp" : "smtps"); - // Get properties Properties props = MessageHelper.getSessionProperties(ident.auth_type, ident.realm, ident.insecure); props.put("mail.smtp.localhost", ident.host); @@ -1933,7 +1940,7 @@ public class ServiceSynchronize extends LifecycleService { // Create transport // TODO: cache transport? - Transport itransport = isession.getTransport(transportType); + Transport itransport = isession.getTransport(ident.getProtocol()); try { // Connect transport db.identity().setIdentityState(ident.id, "connecting"); @@ -2194,7 +2201,7 @@ public class ServiceSynchronize extends LifecycleService { } } - private void synchronizeFolders(EntityAccount account, IMAPStore istore, ServiceState state) throws MessagingException { + private void synchronizeFolders(EntityAccount account, Store istore, ServiceState state) throws MessagingException { DB db = DB.getInstance(this); try { db.beginTransaction(); @@ -2205,13 +2212,13 @@ public class ServiceSynchronize extends LifecycleService { for (EntityFolder folder : db.folder().getFolders(account.id)) if (folder.tbc != null) { Log.i(folder.name + " creating"); - IMAPFolder ifolder = (IMAPFolder) istore.getFolder(folder.name); + Folder ifolder = istore.getFolder(folder.name); if (!ifolder.exists()) ifolder.create(Folder.HOLDS_MESSAGES); db.folder().resetFolderTbc(folder.id); } else if (folder.tbd != null && folder.tbd) { Log.i(folder.name + " deleting"); - IMAPFolder ifolder = (IMAPFolder) istore.getFolder(folder.name); + Folder ifolder = istore.getFolder(folder.name); if (ifolder.exists()) ifolder.delete(false); db.folder().deleteFolder(folder.id); @@ -2228,7 +2235,11 @@ public class ServiceSynchronize extends LifecycleService { for (Folder ifolder : ifolders) { String fullName = ifolder.getFullName(); - String[] attrs = ((IMAPFolder) ifolder).getAttributes(); + String[] attrs; + if (ifolder instanceof IMAPFolder) + attrs = ((IMAPFolder) ifolder).getAttributes(); + else + attrs = new String[0]; String type = EntityFolder.getType(attrs, fullName); EntityLog.log(this, account.name + ":" + fullName + @@ -2299,6 +2310,102 @@ public class ServiceSynchronize extends LifecycleService { } } + private void synchronizeMessages(EntityAccount account, final EntityFolder folder, POP3Folder ifolder, JSONArray jargs, ServiceState state) throws JSONException, MessagingException, IOException { + DB db = DB.getInstance(this); + try { + db.folder().setFolderSyncState(folder.id, "syncing"); + + Message[] imessages = ifolder.getMessages(); + Log.i(folder.name + " POP messages=" + imessages.length); + + db.folder().setFolderSyncState(folder.id, "downloading"); + + for (Message imessage : imessages) + try { + if (!state.running()) + break; + + MessageHelper helper = new MessageHelper((MimeMessage) imessage); + String msgid = helper.getMessageID(); + if (msgid == null) { + Log.w(folder.name + " POP no message ID"); + continue; + } + + List messages = db.message().getMessageByMsgId(folder.account, msgid); + if (messages.size() > 0) { + Log.i(folder.name + " POP having=" + msgid); + continue; + } + + EntityMessage message = new EntityMessage(); + message.account = folder.account; + message.folder = folder.id; + message.identity = null; + message.uid = null; + + message.msgid = helper.getMessageID(); + message.references = TextUtils.join(" ", helper.getReferences()); + message.inreplyto = helper.getInReplyTo(); + message.deliveredto = helper.getDeliveredTo(); + message.thread = helper.getThreadId(0); + message.sender = MessageHelper.getSortKey(helper.getFrom()); + message.from = helper.getFrom(); + message.to = helper.getTo(); + message.cc = helper.getCc(); + message.bcc = helper.getBcc(); + message.reply = helper.getReply(); + message.subject = helper.getSubject(); + message.size = helper.getSize(); + message.content = false; + message.received = new Date().getTime(); // TODO + message.sent = (imessage.getSentDate() == null ? null : imessage.getSentDate().getTime()); + message.seen = false; + message.answered = false; + message.flagged = false; + message.flags = null; + message.keywords = null; + message.ui_seen = false; + message.ui_answered = false; + message.ui_flagged = false; + message.ui_hide = false; + message.ui_found = false; + message.ui_ignored = false; + message.ui_browsed = false; + + Uri lookupUri = ContactInfo.getLookupUri(this, message.from); + message.avatar = (lookupUri == null ? null : lookupUri.toString()); + + message.id = db.message().insertMessage(message); + + Log.i(folder.name + " POP id=" + message.id + " uid=" + message.uid); + + MessageHelper.MessageParts parts = helper.getMessageParts(); + String body = parts.getHtml(this); + Helper.writeText(EntityMessage.getFile(this, message.id), body); + db.message().setMessageContent(message.id, true, HtmlHelper.getPreview(body)); + db.message().setMessageWarning(message.id, parts.getWarnings(message.warning)); + + int sequence = 1; + for (EntityAttachment attachment : parts.getAttachments()) { + Log.i(folder.name + " POP attachment seq=" + sequence + + " name=" + attachment.name + " type=" + attachment.type + + " cid=" + attachment.cid + " pgp=" + attachment.encryption); + attachment.message = message.id; + attachment.sequence = sequence++; + attachment.id = db.attachment().insertAttachment(attachment); + parts.downloadAttachment(this, db, attachment.id, attachment.sequence); + } + } catch (Throwable ex) { + db.folder().setFolderError(folder.id, Helper.formatThrowable(ex)); + } finally { + ((POP3Message) imessage).invalidate(true); + } + } finally { + db.folder().setFolderSyncState(folder.id, null); + } + } + private void synchronizeMessages(EntityAccount account, final EntityFolder folder, IMAPFolder ifolder, JSONArray jargs, ServiceState state) throws JSONException, MessagingException, IOException { final DB db = DB.getInstance(this); try { diff --git a/app/src/main/java/eu/faircode/email/TupleFolderEx.java b/app/src/main/java/eu/faircode/email/TupleFolderEx.java index febd9e78fe..5deebe0d13 100644 --- a/app/src/main/java/eu/faircode/email/TupleFolderEx.java +++ b/app/src/main/java/eu/faircode/email/TupleFolderEx.java @@ -22,6 +22,7 @@ package eu.faircode.email; public class TupleFolderEx extends EntityFolder { public String accountName; public Integer accountColor; + public boolean accountPop; public String accountState; public int messages; public int content; @@ -34,6 +35,7 @@ public class TupleFolderEx extends EntityFolder { return (super.equals(obj) && (this.accountName == null ? other.accountName == null : accountName.equals(other.accountName)) && (this.accountColor == null ? other.accountColor == null : this.accountColor.equals(other.accountColor)) && + this.accountPop == other.accountPop && (this.accountState == null ? other.accountState == null : accountState.equals(other.accountState)) && this.messages == other.messages && this.content == other.content && diff --git a/app/src/main/java/eu/faircode/email/ViewModelBrowse.java b/app/src/main/java/eu/faircode/email/ViewModelBrowse.java index d79f126aa4..72c76c85b9 100644 --- a/app/src/main/java/eu/faircode/email/ViewModelBrowse.java +++ b/app/src/main/java/eu/faircode/email/ViewModelBrowse.java @@ -158,7 +158,7 @@ public class ViewModelBrowse extends ViewModel { isession.setDebug(true); Log.i("Boundary connecting account=" + account.name); - state.istore = (IMAPStore) isession.getStore(account.starttls ? "imap" : "imaps"); + state.istore = (IMAPStore) isession.getStore(account.getProtocol()); Helper.connect(state.context, state.istore, account); Log.i("Boundary opening folder=" + folder.name); diff --git a/app/src/main/res/layout/fragment_account.xml b/app/src/main/res/layout/fragment_account.xml index f8d901e45b..3b9a8a5490 100644 --- a/app/src/main/res/layout/fragment_account.xml +++ b/app/src/main/res/layout/fragment_account.xml @@ -75,27 +75,37 @@ app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toBottomOf="@id/etDomain" /> - + + + + android:layout_marginStart="6dp" + android:text="@string/title_pop3" + android:textAppearance="@style/Base.TextAppearance.AppCompat.Small" + app:layout_constraintBottom_toBottomOf="@id/swPop" + app:layout_constraintStart_toEndOf="@id/swPop" + app:layout_constraintTop_toTopOf="@id/swPop" /> @@ -107,7 +117,7 @@ android:text="@string/title_host" android:textAppearance="@style/TextAppearance.AppCompat.Small" app:layout_constraintStart_toStartOf="parent" - app:layout_constraintTop_toBottomOf="@id/tvPop" /> + app:layout_constraintTop_toBottomOf="@id/swPop" /> + app:constraint_referenced_ids=" + tvDomain,tvDomainHint,etDomain,btnAutoConfig, + tvImap,swPop,tvPop,tvHost,etHost,cbStartTls,cbInsecure,tvPort,etPort" /> + app:constraint_referenced_ids=" + tvUser,etUser,tvPassword,tilPassword,tvRealm,etRealm, + tvName,tvNameRemark,etName,btnColor,vwColor,ibColorDefault" /> + app:constraint_referenced_ids=" + tvPrefix,tvPrefixRemark,etPrefix,cbNotify, + cbSynchronize,cbPrimary,cbBrowse,tvBrowseHint,tvInterval,etInterval" /> + app:constraint_referenced_ids=" + tvDrafts,spDrafts,tvDraftsRemark, + tvSent,spSent, + tvAll,spAll, + tvTrash,spTrash, + tvJunk,spJunk, + vSeparatorSwipe,tvLeft,spLeft,tvRight,spRight" /> \ No newline at end of file diff --git a/app/src/main/res/layout/fragment_folder.xml b/app/src/main/res/layout/fragment_folder.xml index 251743c3c2..7929974d90 100644 --- a/app/src/main/res/layout/fragment_folder.xml +++ b/app/src/main/res/layout/fragment_folder.xml @@ -199,5 +199,15 @@ app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="parent" /> + + \ No newline at end of file diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 2cfd0357d8..cc322ffa14 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -212,6 +212,7 @@ Domain name Get settings IMAP + POP3 SMTP Provider Custom