Added identity linked account

This commit is contained in:
M66B
2018-08-08 10:22:12 +00:00
parent d844734627
commit a5af366b03
18 changed files with 882 additions and 70 deletions

View File

@@ -44,15 +44,15 @@ import androidx.recyclerview.widget.RecyclerView;
public class AdapterIdentity extends RecyclerView.Adapter<AdapterIdentity.ViewHolder> {
private Context context;
private List<EntityIdentity> all = new ArrayList<>();
private List<EntityIdentity> filtered = new ArrayList<>();
private List<TupleIdentityEx> all = new ArrayList<>();
private List<TupleIdentityEx> 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<AdapterIdentity.ViewHo
ivPrimary = itemView.findViewById(R.id.ivPrimary);
tvName = itemView.findViewById(R.id.tvName);
ivSync = itemView.findViewById(R.id.ivSync);
tvHost = itemView.findViewById(R.id.tvHost);
tvAccount = itemView.findViewById(R.id.tvAccount);
tvEmail = itemView.findViewById(R.id.tvEmail);
}
@@ -74,12 +74,12 @@ public class AdapterIdentity extends RecyclerView.Adapter<AdapterIdentity.ViewHo
itemView.setOnClickListener(null);
}
private void bindTo(EntityIdentity identity) {
private void bindTo(TupleIdentityEx identity) {
ivPrimary.setVisibility(identity.primary ? View.VISIBLE : View.GONE);
tvName.setText(identity.name);
ivSync.setVisibility(identity.synchronize ? View.VISIBLE : View.INVISIBLE);
tvHost.setText(String.format("%s:%d", identity.host, identity.port));
tvEmail.setText(identity.email);
tvAccount.setText(identity.accountName);
tvEmail.setText(String.format("%s@%s:%d", identity.email, identity.host, identity.port));
}
@Override
@@ -87,7 +87,7 @@ public class AdapterIdentity extends RecyclerView.Adapter<AdapterIdentity.ViewHo
int pos = getAdapterPosition();
if (pos == RecyclerView.NO_POSITION)
return;
EntityIdentity identity = filtered.get(pos);
TupleIdentityEx identity = filtered.get(pos);
LocalBroadcastManager lbm = LocalBroadcastManager.getInstance(context);
lbm.sendBroadcast(
@@ -101,15 +101,15 @@ public class AdapterIdentity extends RecyclerView.Adapter<AdapterIdentity.ViewHo
setHasStableIds(true);
}
public void set(@NonNull List<EntityIdentity> identities) {
public void set(@NonNull List<TupleIdentityEx> 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<EntityIdentity>() {
Collections.sort(identities, new Comparator<TupleIdentityEx>() {
@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<AdapterIdentity.ViewHo
}
private class MessageDiffCallback extends DiffUtil.Callback {
private List<EntityIdentity> prev;
private List<EntityIdentity> next;
private List<TupleIdentityEx> prev;
private List<TupleIdentityEx> next;
MessageDiffCallback(List<EntityIdentity> prev, List<EntityIdentity> next) {
MessageDiffCallback(List<TupleIdentityEx> prev, List<TupleIdentityEx> next) {
this.prev = prev;
this.next = next;
}
@@ -167,15 +167,15 @@ public class AdapterIdentity extends RecyclerView.Adapter<AdapterIdentity.ViewHo
@Override
public boolean areItemsTheSame(int oldItemPosition, int newItemPosition) {
EntityIdentity i1 = prev.get(oldItemPosition);
EntityIdentity i2 = next.get(newItemPosition);
TupleIdentityEx i1 = prev.get(oldItemPosition);
TupleIdentityEx i2 = next.get(newItemPosition);
return i1.id.equals(i2.id);
}
@Override
public boolean areContentsTheSame(int oldItemPosition, int newItemPosition) {
EntityIdentity i1 = prev.get(oldItemPosition);
EntityIdentity i2 = next.get(newItemPosition);
TupleIdentityEx i1 = prev.get(oldItemPosition);
TupleIdentityEx i2 = next.get(newItemPosition);
return i1.equals(i2);
}
}
@@ -200,7 +200,7 @@ public class AdapterIdentity extends RecyclerView.Adapter<AdapterIdentity.ViewHo
public void onBindViewHolder(@NonNull ViewHolder holder, int position) {
holder.unwire();
EntityIdentity identity = filtered.get(position);
TupleIdentityEx identity = filtered.get(position);
holder.bindTo(identity);
holder.wire();

View File

@@ -60,8 +60,6 @@ public class ApplicationEx extends Application {
draft.ui_hide = false;
draft.id = db.message().insertMessage(draft);
EntityOperation.queue(ApplicationEx.this, draft, EntityOperation.ADD);
Log.w(Helper.TAG, "Crash info stored as draft");
}
} catch (Throwable e1) {

View File

@@ -44,6 +44,7 @@ import androidx.sqlite.db.SupportSQLiteDatabase;
// https://developer.android.com/topic/libraries/architecture/room.html
@Database(
version = 7,
entities = {
EntityIdentity.class,
EntityAccount.class,
@@ -51,9 +52,7 @@ import androidx.sqlite.db.SupportSQLiteDatabase;
EntityMessage.class,
EntityAttachment.class,
EntityOperation.class
},
version = 6,
exportSchema = true
}
)
@TypeConverters({DB.Converters.class})
@@ -86,11 +85,19 @@ public abstract class DB extends RoomDatabase {
private static DB migrate(RoomDatabase.Builder<DB> 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) {

View File

@@ -45,9 +45,6 @@ public interface DaoAccount {
@Query("SELECT * FROM account WHERE id = :id")
LiveData<EntityAccount> liveAccount(long id);
@Query("SELECT * FROM account ORDER BY id LIMIT 1")
LiveData<EntityAccount> liveFirstAccount();
@Query("SELECT" +
" (SELECT COUNT(*) FROM account WHERE synchronize) AS accounts" +
", (SELECT COUNT(*) FROM operation" +

View File

@@ -30,8 +30,9 @@ import androidx.room.Update;
@Dao
public interface DaoIdentity {
@Query("SELECT * FROM identity")
LiveData<List<EntityIdentity>> liveIdentities();
@Query("SELECT identity.*, account.name AS accountName FROM identity" +
" JOIN account ON account.id = identity.account")
LiveData<List<TupleIdentityEx>> liveIdentities();
@Query("SELECT * FROM identity WHERE synchronize = :synchronize")
LiveData<List<EntityIdentity>> liveIdentities(boolean synchronize);
@@ -42,9 +43,6 @@ public interface DaoIdentity {
@Query("SELECT * FROM identity WHERE id = :id")
LiveData<EntityIdentity> liveIdentity(long id);
@Query("SELECT * FROM identity ORDER BY id LIMIT 1")
LiveData<EntityIdentity> liveFirstIdentity();
@Insert(onConflict = OnConflictStrategy.REPLACE)
long insertIdentity(EntityIdentity identity);

View File

@@ -62,4 +62,9 @@ public class EntityAccount {
} else
return false;
}
@Override
public String toString() {
return name + (primary ? "" : "");
}
}

View File

@@ -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) &&

View File

@@ -90,9 +90,9 @@ public class FragmentIdentities extends FragmentEx {
super.onActivityCreated(savedInstanceState);
// Observe identities
DB.getInstance(getContext()).identity().liveIdentities().observe(getViewLifecycleOwner(), new Observer<List<EntityIdentity>>() {
DB.getInstance(getContext()).identity().liveIdentities().observe(getViewLifecycleOwner(), new Observer<List<TupleIdentityEx>>() {
@Override
public void onChanged(@Nullable List<EntityIdentity> identities) {
public void onChanged(@Nullable List<TupleIdentityEx> identities) {
adapter.set(identities);
pbWait.setVisibility(View.GONE);

View File

@@ -62,10 +62,11 @@ import androidx.loader.content.Loader;
public class FragmentIdentity extends FragmentEx {
private List<Provider> 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<Provider> adapter = new ArrayAdapter<>(getContext(), R.layout.spinner_item, providers);
adapter.setDropDownViewResource(R.layout.spinner_dropdown_item);
spProfile.setAdapter(adapter);
ArrayAdapter<Provider> 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<EntityIdentity>() {
final DB db = DB.getInstance(getContext());
// Observe identity
db.identity().liveIdentity(id).observe(getViewLifecycleOwner(), new Observer<EntityIdentity>() {
@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<List<EntityAccount>>() {
@Override
public void onChanged(List<EntityAccount> accounts) {
EntityAccount unselected = new EntityAccount();
unselected.id = -1L;
unselected.name = "";
unselected.primary = false;
accounts.add(0, unselected);
ArrayAdapter<EntityAccount> 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();

View File

@@ -0,0 +1,5 @@
package eu.faircode.email;
public class TupleIdentityEx extends EntityIdentity {
public String accountName;
}