diff --git a/app/schemas/eu.faircode.email.DB/7.json b/app/schemas/eu.faircode.email.DB/7.json new file mode 100644 index 0000000000..00802c1cbe --- /dev/null +++ b/app/schemas/eu.faircode.email.DB/7.json @@ -0,0 +1,700 @@ +{ + "formatVersion": 1, + "database": { + "version": 7, + "identityHash": "74b765c4ee277861c68851c33a596610", + "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, `port` INTEGER NOT NULL, `starttls` INTEGER NOT NULL, `user` TEXT NOT NULL, `password` TEXT NOT NULL, `primary` INTEGER NOT NULL, `synchronize` INTEGER NOT NULL, 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": "port", + "columnName": "port", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "starttls", + "columnName": "starttls", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "user", + "columnName": "user", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "password", + "columnName": "password", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "primary", + "columnName": "primary", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "synchronize", + "columnName": "synchronize", + "affinity": "INTEGER", + "notNull": true + } + ], + "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, `host` TEXT NOT NULL, `port` INTEGER NOT NULL, `user` TEXT NOT NULL, `password` TEXT NOT NULL, `primary` INTEGER NOT NULL, `synchronize` INTEGER NOT NULL, `seen_until` INTEGER)", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "name", + "columnName": "name", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "host", + "columnName": "host", + "affinity": "TEXT", + "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": "primary", + "columnName": "primary", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "synchronize", + "columnName": "synchronize", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "seen_until", + "columnName": "seen_until", + "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, `synchronize` INTEGER NOT NULL, `after` INTEGER NOT NULL, 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": "after", + "columnName": "after", + "affinity": "INTEGER", + "notNull": true + } + ], + "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`)" + } + ], + "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, `folder` INTEGER NOT NULL, `identity` INTEGER, `replying` INTEGER, `uid` INTEGER, `msgid` TEXT, `references` TEXT, `inreplyto` TEXT, `thread` TEXT, `from` TEXT, `to` TEXT, `cc` TEXT, `bcc` TEXT, `reply` TEXT, `subject` TEXT, `body` TEXT, `sent` INTEGER, `received` INTEGER NOT NULL, `seen` INTEGER NOT NULL, `ui_seen` INTEGER NOT NULL, `ui_hide` INTEGER NOT NULL, 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": "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": "inreplyto", + "columnName": "inreplyto", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "thread", + "columnName": "thread", + "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": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "body", + "columnName": "body", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "sent", + "columnName": "sent", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "received", + "columnName": "received", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "seen", + "columnName": "seen", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "ui_seen", + "columnName": "ui_seen", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "ui_hide", + "columnName": "ui_hide", + "affinity": "INTEGER", + "notNull": true + } + ], + "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", + "unique": true, + "columnNames": [ + "folder", + "uid" + ], + "createSql": "CREATE UNIQUE INDEX `index_message_folder_uid` ON `${TABLE_NAME}` (`folder`, `uid`)" + }, + { + "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`)" + } + ], + "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, `size` INTEGER, `progress` INTEGER, `content` BLOB, 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": "size", + "columnName": "size", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "progress", + "columnName": "progress", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "content", + "columnName": "content", + "affinity": "BLOB", + "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`)" + } + ], + "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, `message` INTEGER NOT NULL, `name` TEXT NOT NULL, `args` 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": "name", + "columnName": "name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "args", + "columnName": "args", + "affinity": "TEXT", + "notNull": false + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [ + { + "name": "index_operation_message", + "unique": false, + "columnNames": [ + "message" + ], + "createSql": "CREATE INDEX `index_operation_message` ON `${TABLE_NAME}` (`message`)" + } + ], + "foreignKeys": [ + { + "table": "message", + "onDelete": "CASCADE", + "onUpdate": "NO ACTION", + "columns": [ + "message" + ], + "referencedColumns": [ + "id" + ] + } + ] + } + ], + "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, \"74b765c4ee277861c68851c33a596610\")" + ] + } +} \ No newline at end of file diff --git a/app/src/main/java/eu/faircode/email/AdapterIdentity.java b/app/src/main/java/eu/faircode/email/AdapterIdentity.java index 83cc55ab2c..26994e4220 100644 --- a/app/src/main/java/eu/faircode/email/AdapterIdentity.java +++ b/app/src/main/java/eu/faircode/email/AdapterIdentity.java @@ -44,15 +44,15 @@ import androidx.recyclerview.widget.RecyclerView; public class AdapterIdentity extends RecyclerView.Adapter { private Context context; - private List all = new ArrayList<>(); - private List filtered = new ArrayList<>(); + private List all = new ArrayList<>(); + private List filtered = new ArrayList<>(); public class ViewHolder extends RecyclerView.ViewHolder implements View.OnClickListener { View itemView; ImageView ivPrimary; TextView tvName; ImageView ivSync; - TextView tvHost; + TextView tvAccount; TextView tvEmail; ViewHolder(View itemView) { @@ -62,7 +62,7 @@ public class AdapterIdentity extends RecyclerView.Adapter identities) { + public void set(@NonNull List identities) { Log.i(Helper.TAG, "Set identities=" + identities.size()); final Collator collator = Collator.getInstance(Locale.getDefault()); collator.setStrength(Collator.SECONDARY); // Case insensitive, process accents etc - Collections.sort(identities, new Comparator() { + Collections.sort(identities, new Comparator() { @Override - public int compare(EntityIdentity i1, EntityIdentity i2) { + public int compare(TupleIdentityEx i1, TupleIdentityEx i2) { return collator.compare(i1.host, i2.host); } }); @@ -147,10 +147,10 @@ public class AdapterIdentity extends RecyclerView.Adapter prev; - private List next; + private List prev; + private List next; - MessageDiffCallback(List prev, List next) { + MessageDiffCallback(List prev, List next) { this.prev = prev; this.next = next; } @@ -167,15 +167,15 @@ public class AdapterIdentity extends RecyclerView.Adapter builder) { return builder + .addCallback(new Callback() { + @Override + public void onOpen(SupportSQLiteDatabase db) { + Log.i(Helper.TAG, "Database version=" + db.getVersion()); + super.onOpen(db); + } + }) .addMigrations(MIGRATION_1_2) .addMigrations(MIGRATION_2_3) .addMigrations(MIGRATION_3_4) .addMigrations(MIGRATION_4_5) .addMigrations(MIGRATION_5_6) + .addMigrations(MIGRATION_6_7) .build(); } @@ -142,6 +149,29 @@ public abstract class DB extends RoomDatabase { } }; + private static final Migration MIGRATION_6_7 = new Migration(6, 7) { + @Override + public void migrate(SupportSQLiteDatabase db) { + Log.i(Helper.TAG, "DB migration from version " + startVersion + " to " + endVersion); + db.execSQL("DROP TABLE `identity`"); + db.execSQL("CREATE TABLE `identity`" + + " (`id` INTEGER PRIMARY KEY AUTOINCREMENT" + + ", `name` TEXT NOT NULL" + + ", `email` TEXT NOT NULL" + + ", `replyto` TEXT" + + ", `account` INTEGER NOT NULL" + + ", `host` TEXT NOT NULL" + + ", `port` INTEGER NOT NULL" + + ", `starttls` INTEGER NOT NULL" + + ", `user` TEXT NOT NULL" + + ", `password` TEXT NOT NULL" + + ", `primary` INTEGER NOT NULL" + + ", `synchronize` INTEGER NOT NULL" + + ", FOREIGN KEY(`account`) REFERENCES `account`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE)"); + db.execSQL("CREATE INDEX `index_identity_account` ON `identity` (`account`)"); + } + }; + public static class Converters { @TypeConverter public static String[] fromStringArray(String value) { diff --git a/app/src/main/java/eu/faircode/email/DaoAccount.java b/app/src/main/java/eu/faircode/email/DaoAccount.java index b5e568f0ca..9b14e66ea8 100644 --- a/app/src/main/java/eu/faircode/email/DaoAccount.java +++ b/app/src/main/java/eu/faircode/email/DaoAccount.java @@ -45,9 +45,6 @@ public interface DaoAccount { @Query("SELECT * FROM account WHERE id = :id") LiveData liveAccount(long id); - @Query("SELECT * FROM account ORDER BY id LIMIT 1") - LiveData liveFirstAccount(); - @Query("SELECT" + " (SELECT COUNT(*) FROM account WHERE synchronize) AS accounts" + ", (SELECT COUNT(*) FROM operation" + diff --git a/app/src/main/java/eu/faircode/email/DaoIdentity.java b/app/src/main/java/eu/faircode/email/DaoIdentity.java index d6a7981857..97b5ab33e2 100644 --- a/app/src/main/java/eu/faircode/email/DaoIdentity.java +++ b/app/src/main/java/eu/faircode/email/DaoIdentity.java @@ -30,8 +30,9 @@ import androidx.room.Update; @Dao public interface DaoIdentity { - @Query("SELECT * FROM identity") - LiveData> liveIdentities(); + @Query("SELECT identity.*, account.name AS accountName FROM identity" + + " JOIN account ON account.id = identity.account") + LiveData> liveIdentities(); @Query("SELECT * FROM identity WHERE synchronize = :synchronize") LiveData> liveIdentities(boolean synchronize); @@ -42,9 +43,6 @@ public interface DaoIdentity { @Query("SELECT * FROM identity WHERE id = :id") LiveData liveIdentity(long id); - @Query("SELECT * FROM identity ORDER BY id LIMIT 1") - LiveData liveFirstIdentity(); - @Insert(onConflict = OnConflictStrategy.REPLACE) long insertIdentity(EntityIdentity identity); diff --git a/app/src/main/java/eu/faircode/email/EntityAccount.java b/app/src/main/java/eu/faircode/email/EntityAccount.java index 91d8d7ef6a..b3f313a7b8 100644 --- a/app/src/main/java/eu/faircode/email/EntityAccount.java +++ b/app/src/main/java/eu/faircode/email/EntityAccount.java @@ -62,4 +62,9 @@ public class EntityAccount { } else return false; } + + @Override + public String toString() { + return name + (primary ? " ★" : ""); + } } diff --git a/app/src/main/java/eu/faircode/email/EntityIdentity.java b/app/src/main/java/eu/faircode/email/EntityIdentity.java index b5cd64cd92..7f71b91c7a 100644 --- a/app/src/main/java/eu/faircode/email/EntityIdentity.java +++ b/app/src/main/java/eu/faircode/email/EntityIdentity.java @@ -21,11 +21,19 @@ package eu.faircode.email; import androidx.annotation.NonNull; import androidx.room.Entity; +import androidx.room.ForeignKey; +import androidx.room.Index; import androidx.room.PrimaryKey; +import static androidx.room.ForeignKey.CASCADE; + @Entity( tableName = EntityIdentity.TABLE_NAME, + foreignKeys = { + @ForeignKey(childColumns = "account", entity = EntityAccount.class, parentColumns = "id", onDelete = CASCADE) + }, indices = { + @Index(value = {"account"}) } ) public class EntityIdentity { @@ -39,6 +47,8 @@ public class EntityIdentity { public String email; public String replyto; @NonNull + public Long account; + @NonNull public String host; // SMTP @NonNull public Integer port; @@ -60,6 +70,7 @@ public class EntityIdentity { return (this.name.equals(other.name) && this.email.equals(other.email) && (this.replyto == null ? other.replyto == null : this.replyto.equals(other.replyto)) && + this.account.equals(other.account) && this.host.equals(other.host) && this.port.equals(other.port) && this.starttls.equals(other.starttls) && diff --git a/app/src/main/java/eu/faircode/email/FragmentIdentities.java b/app/src/main/java/eu/faircode/email/FragmentIdentities.java index fea6109362..1996260569 100644 --- a/app/src/main/java/eu/faircode/email/FragmentIdentities.java +++ b/app/src/main/java/eu/faircode/email/FragmentIdentities.java @@ -90,9 +90,9 @@ public class FragmentIdentities extends FragmentEx { super.onActivityCreated(savedInstanceState); // Observe identities - DB.getInstance(getContext()).identity().liveIdentities().observe(getViewLifecycleOwner(), new Observer>() { + DB.getInstance(getContext()).identity().liveIdentities().observe(getViewLifecycleOwner(), new Observer>() { @Override - public void onChanged(@Nullable List identities) { + public void onChanged(@Nullable List identities) { adapter.set(identities); pbWait.setVisibility(View.GONE); diff --git a/app/src/main/java/eu/faircode/email/FragmentIdentity.java b/app/src/main/java/eu/faircode/email/FragmentIdentity.java index 6206da9418..77b6a1fc00 100644 --- a/app/src/main/java/eu/faircode/email/FragmentIdentity.java +++ b/app/src/main/java/eu/faircode/email/FragmentIdentity.java @@ -62,10 +62,11 @@ import androidx.loader.content.Loader; public class FragmentIdentity extends FragmentEx { private List providers; - private Spinner spProfile; private EditText etName; private EditText etEmail; private EditText etReplyTo; + private Spinner spProfile; + private Spinner spAccount; private EditText etHost; private CheckBox cbStartTls; private EditText etPort; @@ -96,10 +97,11 @@ public class FragmentIdentity extends FragmentEx { providers.add(0, new Provider(getString(R.string.title_custom))); // Get controls - spProfile = view.findViewById(R.id.spProvider); etName = view.findViewById(R.id.etName); etEmail = view.findViewById(R.id.etEmail); etReplyTo = view.findViewById(R.id.etReplyTo); + spProfile = view.findViewById(R.id.spProvider); + spAccount = view.findViewById(R.id.spAccount); etHost = view.findViewById(R.id.etHost); cbStartTls = view.findViewById(R.id.cbStartTls); etPort = view.findViewById(R.id.etPort); @@ -128,13 +130,6 @@ public class FragmentIdentity extends FragmentEx { } }); - cbStartTls.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() { - @Override - public void onCheckedChanged(CompoundButton compoundButton, boolean checked) { - etPort.setHint(checked ? "587" : "465"); - } - }); - spProfile.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() { @Override public void onItemSelected(AdapterView parent, View view, int position, long id) { @@ -151,9 +146,16 @@ public class FragmentIdentity extends FragmentEx { } }); - ArrayAdapter adapter = new ArrayAdapter<>(getContext(), R.layout.spinner_item, providers); - adapter.setDropDownViewResource(R.layout.spinner_dropdown_item); - spProfile.setAdapter(adapter); + ArrayAdapter adapterProfile = new ArrayAdapter<>(getContext(), R.layout.spinner_item, providers); + adapterProfile.setDropDownViewResource(R.layout.spinner_dropdown_item); + spProfile.setAdapter(adapterProfile); + + cbStartTls.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() { + @Override + public void onCheckedChanged(CompoundButton compoundButton, boolean checked) { + etPort.setHint(checked ? "587" : "465"); + } + }); cbSynchronize.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() { @Override @@ -168,11 +170,14 @@ public class FragmentIdentity extends FragmentEx { btnSave.setEnabled(false); pbCheck.setVisibility(View.VISIBLE); + EntityAccount account = (EntityAccount) spAccount.getSelectedItem(); + Bundle args = new Bundle(); args.putLong("id", id); args.putString("name", etName.getText().toString()); args.putString("email", etEmail.getText().toString()); args.putString("replyto", etReplyTo.getText().toString()); + args.putLong("account", account == null ? -1 : account.id); args.putString("host", etHost.getText().toString()); args.putBoolean("starttls", cbStartTls.isChecked()); args.putString("port", etPort.getText().toString()); @@ -230,10 +235,12 @@ public class FragmentIdentity extends FragmentEx { Bundle args = getArguments(); long id = (args == null ? -1 : args.getLong("id", -1)); - // Observer - DB.getInstance(getContext()).identity().liveIdentity(id).observe(getViewLifecycleOwner(), new Observer() { + final DB db = DB.getInstance(getContext()); + + // Observe identity + db.identity().liveIdentity(id).observe(getViewLifecycleOwner(), new Observer() { @Override - public void onChanged(@Nullable EntityIdentity identity) { + public void onChanged(@Nullable final EntityIdentity identity) { etName.setText(identity == null ? null : identity.name); etEmail.setText(identity == null ? null : identity.email); etReplyTo.setText(identity == null ? null : identity.replyto); @@ -245,6 +252,29 @@ public class FragmentIdentity extends FragmentEx { cbSynchronize.setChecked(identity == null ? true : identity.synchronize); cbPrimary.setChecked(identity == null ? true : identity.primary); cbPrimary.setEnabled(identity == null ? true : identity.synchronize); + + db.account().liveAccounts().removeObservers(getViewLifecycleOwner()); + db.account().liveAccounts().observe(getViewLifecycleOwner(), new Observer>() { + @Override + public void onChanged(List accounts) { + + EntityAccount unselected = new EntityAccount(); + unselected.id = -1L; + unselected.name = ""; + unselected.primary = false; + accounts.add(0, unselected); + + ArrayAdapter adapterAccount = new ArrayAdapter<>(getContext(), R.layout.spinner_item, accounts); + adapterAccount.setDropDownViewResource(R.layout.spinner_dropdown_item); + spAccount.setAdapter(adapterAccount); + + for (int pos = 0; pos < accounts.size(); pos++) + if (accounts.get(pos).id == (identity == null ? -1 : identity.account)) { + spAccount.setSelection(pos); + break; + } + } + }); } }); } @@ -264,21 +294,30 @@ public class FragmentIdentity extends FragmentEx { public Throwable loadInBackground() { try { long id = args.getLong("id"); + String name = args.getString("name"); + String email = args.getString("email"); String replyto = args.getString("replyto"); + long account = args.getLong("account"); String host = args.getString("host"); boolean starttls = args.getBoolean("starttls"); String port = args.getString("port"); String user = args.getString("user"); String password = args.getString("password"); + if (TextUtils.isEmpty(name)) + throw new IllegalArgumentException(getContext().getString(R.string.title_no_name)); + if (TextUtils.isEmpty(email)) + throw new IllegalArgumentException(getContext().getString(R.string.title_no_email)); + if (account < 0) + throw new IllegalArgumentException(getContext().getString(R.string.title_no_account)); if (TextUtils.isEmpty(host)) - throw new Throwable(getContext().getString(R.string.title_no_host)); + throw new IllegalArgumentException(getContext().getString(R.string.title_no_host)); if (TextUtils.isEmpty(port)) - throw new Throwable(getContext().getString(R.string.title_no_port)); + throw new IllegalArgumentException(getContext().getString(R.string.title_no_port)); if (TextUtils.isEmpty(user)) - throw new Throwable(getContext().getString(R.string.title_no_user)); + throw new IllegalArgumentException(getContext().getString(R.string.title_no_user)); if (TextUtils.isEmpty(password)) - throw new Throwable(getContext().getString(R.string.title_no_password)); + throw new IllegalArgumentException(getContext().getString(R.string.title_no_password)); if (TextUtils.isEmpty(replyto)) replyto = null; @@ -288,9 +327,10 @@ public class FragmentIdentity extends FragmentEx { boolean update = (identity != null); if (identity == null) identity = new EntityIdentity(); - identity.name = Objects.requireNonNull(args.getString("name")); - identity.email = Objects.requireNonNull(args.getString("email")); + identity.name = name; + identity.email = email; identity.replyto = replyto; + identity.account = account; identity.host = Objects.requireNonNull(host); identity.port = Integer.parseInt(port); identity.starttls = starttls; @@ -299,12 +339,6 @@ public class FragmentIdentity extends FragmentEx { identity.synchronize = args.getBoolean("synchronize"); identity.primary = (identity.synchronize && args.getBoolean("primary")); - if (TextUtils.isEmpty(identity.name)) - throw new IllegalArgumentException(getContext().getString(R.string.title_no_name)); - - if (TextUtils.isEmpty(identity.email)) - throw new IllegalArgumentException(getContext().getString(R.string.title_no_email)); - // Check SMTP server if (identity.synchronize) { Properties props = MessageHelper.getSessionProperties(); diff --git a/app/src/main/java/eu/faircode/email/TupleIdentityEx.java b/app/src/main/java/eu/faircode/email/TupleIdentityEx.java new file mode 100644 index 0000000000..8e1fe227a4 --- /dev/null +++ b/app/src/main/java/eu/faircode/email/TupleIdentityEx.java @@ -0,0 +1,5 @@ +package eu.faircode.email; + +public class TupleIdentityEx extends EntityIdentity { + public String accountName; +} diff --git a/app/src/main/res/layout/fragment_identity.xml b/app/src/main/res/layout/fragment_identity.xml index 1bb994582d..560c22cae1 100644 --- a/app/src/main/res/layout/fragment_identity.xml +++ b/app/src/main/res/layout/fragment_identity.xml @@ -75,6 +75,26 @@ app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toBottomOf="@id/tvReplyTo" /> + + + + + + + + app:layout_constraintTop_toBottomOf="@id/spAccount" /> Your email address Reply to address Optional + Linked account Account name Used to differentiate folders IMAP @@ -58,8 +59,9 @@ Synchronize (send messages) Primary (used to store drafts) Primary (default identity) - Name mandatory - Email address mandatory + Name missing + Email address missing + Account missing Host name missing Port number missing User name missing