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