Added account to local contacts

This commit is contained in:
M66B 2019-03-17 08:26:17 +00:00
parent d63536b3fc
commit 5d98923ffc
10 changed files with 1749 additions and 73 deletions

File diff suppressed because it is too large Load Diff

View File

@ -509,14 +509,15 @@ public class ActivitySetup extends ActivityBilling implements FragmentManager.On
}
jaccount.put("folders", jfolders);
// Contacts
JSONArray jcontacts = new JSONArray();
for (EntityContact contact : db.contact().getContacts(account.id))
jcontacts.put(contact.toJSON());
jaccount.put("contacts", jcontacts);
jaccounts.put(jaccount);
}
// Contacts
JSONArray jcontacts = new JSONArray();
for (EntityContact contact : db.contact().getContacts())
jcontacts.put(contact.toJSON());
// Answers
JSONArray janswers = new JSONArray();
for (EntityAnswer answer : db.answer().getAnswers())
@ -535,7 +536,6 @@ public class ActivitySetup extends ActivityBilling implements FragmentManager.On
JSONObject jexport = new JSONObject();
jexport.put("accounts", jaccounts);
jexport.put("contacts", jcontacts);
jexport.put("answers", janswers);
jexport.put("settings", jsettings);
@ -700,21 +700,22 @@ public class ActivitySetup extends ActivityBilling implements FragmentManager.On
Log.i("Imported folder=" + folder.name);
}
// Contacts
JSONArray jcontacts = jaccount.getJSONArray("contacts");
for (int c = 0; c < jcontacts.length(); c++) {
JSONObject jcontact = (JSONObject) jcontacts.get(c);
EntityContact contact = EntityContact.fromJSON(jcontact);
contact.account = account.id;
if (db.contact().getContact(contact.account, contact.type, contact.email) == null) {
contact.id = db.contact().insertContact(contact);
Log.i("Imported contact=" + contact);
}
}
// Update swipe left/right
db.account().updateAccount(account);
}
// Contacts
JSONArray jcontacts = jimport.getJSONArray("contacts");
for (int c = 0; c < jcontacts.length(); c++) {
JSONObject jcontact = (JSONObject) jcontacts.get(c);
EntityContact contact = EntityContact.fromJSON(jcontact);
if (db.contact().getContact(contact.type, contact.email) == null) {
contact.id = db.contact().insertContact(contact);
Log.i("Imported contact=" + contact);
}
}
// Answers
JSONArray janswers = jimport.getJSONArray("answers");
for (int a = 0; a < janswers.length(); a++) {

View File

@ -51,8 +51,8 @@ public class AdapterContact extends RecyclerView.Adapter<AdapterContact.ViewHold
private int textColorSecondary;
private String search = null;
private List<EntityContact> all = new ArrayList<>();
private List<EntityContact> selected = new ArrayList<>();
private List<TupleContactEx> all = new ArrayList<>();
private List<TupleContactEx> selected = new ArrayList<>();
private static NumberFormat nf = NumberFormat.getNumberInstance();
@ -89,7 +89,7 @@ public class AdapterContact extends RecyclerView.Adapter<AdapterContact.ViewHold
view.setOnLongClickListener(null);
}
private void bindTo(EntityContact contact) {
private void bindTo(TupleContactEx contact) {
view.setAlpha(contact.state == EntityContact.STATE_IGNORE ? Helper.LOW_LIGHT : 1.0f);
if (contact.type == EntityContact.TYPE_FROM)
@ -105,7 +105,7 @@ public class AdapterContact extends RecyclerView.Adapter<AdapterContact.ViewHold
ivAvatar.setImageURI(Uri.parse(contact.avatar + "/photo"));
tvName.setText(contact.name == null ? contact.email : contact.name);
tvEmail.setText(contact.email);
tvEmail.setText(contact.accountName + "/" + contact.email);
tvTimes.setText(nf.format(contact.times_contacted));
tvLast.setText(contact.last_contacted == null ? null
: DateUtils.getRelativeTimeSpanString(context, contact.last_contacted));
@ -124,7 +124,7 @@ public class AdapterContact extends RecyclerView.Adapter<AdapterContact.ViewHold
if (pos == RecyclerView.NO_POSITION)
return;
EntityContact contact = selected.get(pos);
TupleContactEx contact = selected.get(pos);
if (contact.state == EntityContact.STATE_DEFAULT)
contact.state = EntityContact.STATE_FAVORITE;
else
@ -166,7 +166,7 @@ public class AdapterContact extends RecyclerView.Adapter<AdapterContact.ViewHold
if (pos == RecyclerView.NO_POSITION)
return false;
EntityContact contact = selected.get(pos);
TupleContactEx contact = selected.get(pos);
Bundle args = new Bundle();
args.putLong("id", contact.id);
@ -207,18 +207,18 @@ public class AdapterContact extends RecyclerView.Adapter<AdapterContact.ViewHold
setHasStableIds(true);
}
public void set(@NonNull List<EntityContact> contacts) {
public void set(@NonNull List<TupleContactEx> contacts) {
Log.i("Set contacts=" + contacts.size());
all = contacts;
List<EntityContact> items;
List<TupleContactEx> items;
if (TextUtils.isEmpty(search))
items = all;
else {
items = new ArrayList<>();
String query = search.toLowerCase().trim();
for (EntityContact contact : contacts)
for (TupleContactEx contact : contacts)
if (contact.email.toLowerCase().contains(query) ||
(contact.name != null && contact.name.toLowerCase().contains(query)))
items.add(contact);
@ -258,10 +258,10 @@ public class AdapterContact extends RecyclerView.Adapter<AdapterContact.ViewHold
}
private class DiffCallback extends DiffUtil.Callback {
private List<EntityContact> prev = new ArrayList<>();
private List<EntityContact> next = new ArrayList<>();
private List<TupleContactEx> prev = new ArrayList<>();
private List<TupleContactEx> next = new ArrayList<>();
DiffCallback(List<EntityContact> prev, List<EntityContact> next) {
DiffCallback(List<TupleContactEx> prev, List<TupleContactEx> next) {
this.prev.addAll(prev);
this.next.addAll(next);
}
@ -278,15 +278,15 @@ public class AdapterContact extends RecyclerView.Adapter<AdapterContact.ViewHold
@Override
public boolean areItemsTheSame(int oldItemPosition, int newItemPosition) {
EntityContact c1 = prev.get(oldItemPosition);
EntityContact c2 = next.get(newItemPosition);
TupleContactEx c1 = prev.get(oldItemPosition);
TupleContactEx c2 = next.get(newItemPosition);
return c1.id.equals(c2.id);
}
@Override
public boolean areContentsTheSame(int oldItemPosition, int newItemPosition) {
EntityContact c1 = prev.get(oldItemPosition);
EntityContact c2 = next.get(newItemPosition);
TupleContactEx c1 = prev.get(oldItemPosition);
TupleContactEx c2 = next.get(newItemPosition);
return c1.equals(c2);
}
}
@ -310,7 +310,7 @@ public class AdapterContact extends RecyclerView.Adapter<AdapterContact.ViewHold
@Override
public void onBindViewHolder(@NonNull ViewHolder holder, int position) {
holder.unwire();
EntityContact contact = selected.get(position);
TupleContactEx contact = selected.get(position);
holder.bindTo(contact);
holder.wire();
}

View File

@ -1259,14 +1259,16 @@ class Core {
String email = ((InternetAddress) recipient).getAddress();
String name = ((InternetAddress) recipient).getPersonal();
Uri avatar = ContactInfo.getLookupUri(context, new Address[]{recipient});
EntityContact contact = db.contact().getContact(type, email);
EntityContact contact = db.contact().getContact(folder.account, type, email);
if (contact == null) {
contact = new EntityContact();
contact.account = folder.account;
contact.type = type;
contact.email = email;
contact.name = name;
contact.avatar = (avatar == null ? null : avatar.toString());
contact.times_contacted = 1;
contact.first_contacted = message.received;
contact.last_contacted = message.received;
contact.id = db.contact().insertContact(contact);
Log.i("Inserted contact=" + contact + " type=" + type);
@ -1274,6 +1276,7 @@ class Core {
contact.name = name;
contact.avatar = (avatar == null ? null : avatar.toString());
contact.times_contacted++;
contact.first_contacted = Math.min(contact.first_contacted, message.received);
contact.last_contacted = message.received;
db.contact().updateContact(contact);
Log.i("Updated contact=" + contact + " type=" + type);

View File

@ -50,7 +50,7 @@ import io.requery.android.database.sqlite.RequerySQLiteOpenHelperFactory;
// https://developer.android.com/topic/libraries/architecture/room.html
@Database(
version = 57,
version = 58,
entities = {
EntityIdentity.class,
EntityAccount.class,
@ -624,6 +624,30 @@ public abstract class DB extends RoomDatabase {
db.execSQL("CREATE INDEX `index_contact_favorite` ON `contact` (`favorite`)");
}
})
.addMigrations(new Migration(57, 58) {
@Override
public void migrate(SupportSQLiteDatabase db) {
Log.i("DB migration from version " + startVersion + " to " + endVersion);
db.execSQL("DROP TABLE `contact`");
db.execSQL("CREATE TABLE IF NOT EXISTS `contact`" +
" (`id` INTEGER PRIMARY KEY AUTOINCREMENT" +
", `account` INTEGER NOT NULL" +
", `type` INTEGER NOT NULL" +
", `email` TEXT NOT NULL" +
", `name` TEXT, `avatar` TEXT" +
", `times_contacted` INTEGER NOT NULL" +
", `first_contacted` INTEGER NOT NULL" +
", `last_contacted` INTEGER NOT NULL" +
", `state` INTEGER NOT NULL" +
", FOREIGN KEY(`account`) REFERENCES `account`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE )");
db.execSQL("CREATE UNIQUE INDEX `index_contact_account_type_email` ON `contact` (`account`, `type`, `email`)");
db.execSQL("CREATE INDEX `index_contact_email` ON `contact` (`email`)");
db.execSQL("CREATE INDEX `index_contact_name` ON `contact` (`name`)");
db.execSQL("CREATE INDEX `index_contact_times_contacted` ON `contact` (`times_contacted`)");
db.execSQL("CREATE INDEX `index_contact_last_contacted` ON `contact` (`last_contacted`)");
db.execSQL("CREATE INDEX `index_contact_state` ON `contact` (`state`)");
}
})
.build();
}

View File

@ -31,17 +31,20 @@ import androidx.room.Update;
@Dao
public interface DaoContact {
@Query("SELECT * FROM contact")
List<EntityContact> getContacts();
@Query("SELECT * FROM contact" +
" WHERE account = :account")
List<EntityContact> getContacts(long account);
@Query("SELECT contact.*, account.name AS accountName" +
" FROM contact" +
" JOIN account ON account.id = contact.account" +
" ORDER BY times_contacted DESC, last_contacted DESC")
LiveData<List<EntityContact>> liveContacts();
LiveData<List<TupleContactEx>> liveContacts();
@Query("SELECT * FROM contact" +
" WHERE favorite <> " + EntityContact.STATE_IGNORE +
" WHERE state <> " + EntityContact.STATE_IGNORE +
" ORDER BY" +
" CASE WHEN favorite = " + EntityContact.STATE_FAVORITE + " THEN 0 ELSE 1 END" +
" CASE WHEN state = " + EntityContact.STATE_FAVORITE + " THEN 0 ELSE 1 END" +
", times_contacted DESC" +
", last_contacted DESC" +
" LIMIT :count")
@ -49,9 +52,10 @@ public interface DaoContact {
@Query("SELECT *" +
" FROM contact" +
" WHERE email = :email" +
" AND (:type IS NULL OR type = :type)")
EntityContact getContact(Integer type, String email);
" WHERE account = :account" +
" AND type = :type" +
" AND email = :email")
EntityContact getContact(long account, int type, String email);
@Query("SELECT id AS _id, name, email" +
", CASE type" +
@ -60,9 +64,10 @@ public interface DaoContact {
" ELSE '?'" +
" END AS type" +
" FROM contact" +
" WHERE name LIKE :name" +
" AND (:type IS NULL OR type = :type)")
Cursor searchContacts(Integer type, String name);
" WHERE (:account IS NULL OR account = :account)" +
" AND (:type IS NULL OR type = :type)" +
" AND (email LIKE :query COLLATE NOCASE OR name LIKE :query COLLATE NOCASE)")
Cursor searchContacts(Long account, Integer type, String query);
@Insert
long insertContact(EntityContact contact);
@ -70,13 +75,13 @@ public interface DaoContact {
@Update
int updateContact(EntityContact contact);
@Query("UPDATE contact SET favorite = :state WHERE id = :id")
@Query("UPDATE contact SET state = :state WHERE id = :id")
int setContactState(long id, int state);
@Query("DELETE FROM contact" +
" WHERE last_contacted IS NOT NULL" +
" AND last_contacted < :before" +
" AND favorite <> " + EntityContact.STATE_FAVORITE)
" AND state <> " + EntityContact.STATE_FAVORITE)
int deleteContacts(long before);
@Query("DELETE FROM contact")

View File

@ -27,23 +27,27 @@ import java.util.Objects;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.room.ColumnInfo;
import androidx.room.Entity;
import androidx.room.ForeignKey;
import androidx.room.Index;
import androidx.room.PrimaryKey;
import static androidx.room.ForeignKey.CASCADE;
// https://developer.android.com/training/data-storage/room/defining-data
@Entity(
tableName = EntityContact.TABLE_NAME,
foreignKeys = {
@ForeignKey(childColumns = "account", entity = EntityAccount.class, parentColumns = "id", onDelete = CASCADE)
},
indices = {
@Index(value = {"email", "type"}, unique = true),
@Index(value = {"name", "type"}),
@Index(value = {"account", "type", "email"}, unique = true),
@Index(value = {"email"}),
@Index(value = {"name"}),
@Index(value = {"times_contacted"}),
@Index(value = {"last_contacted"}),
@Index(value = {"favorite"})
@Index(value = {"state"})
}
)
public class EntityContact implements Serializable {
@ -59,6 +63,8 @@ public class EntityContact implements Serializable {
@PrimaryKey(autoGenerate = true)
public Long id;
@NonNull
public Long account;
@NonNull
public int type;
@NonNull
public String email;
@ -67,9 +73,11 @@ public class EntityContact implements Serializable {
@NonNull
public Integer times_contacted;
@NonNull
public Long first_contacted;
@NonNull
public Long last_contacted;
@NonNull
@ColumnInfo(name = "favorite")
public Integer state = STATE_DEFAULT;
public JSONObject toJSON() throws JSONException {
@ -80,6 +88,7 @@ public class EntityContact implements Serializable {
json.put("name", name);
json.put("avatar", avatar);
json.put("times_contacted", times_contacted);
json.put("first_contacted", first_contacted);
json.put("last_contacted", last_contacted);
json.put("state", state);
return json;
@ -97,18 +106,10 @@ public class EntityContact implements Serializable {
if (json.has("avatar") && !json.isNull("avatar"))
contact.avatar = json.getString("avatar");
if (json.has("times_contacted"))
contact.times_contacted = json.getInt("times_contacted");
else
contact.times_contacted = 1;
if (json.has("last_contacted") && !json.isNull("last_contacted"))
contact.last_contacted = json.getLong("last_contacted");
if (json.has("favorite"))
contact.state = (json.getBoolean("favorite") ? 1 : 0);
if (json.has("state"))
contact.state = json.getInt("state");
contact.times_contacted = json.getInt("times_contacted");
contact.first_contacted = json.getLong("first_contacted");
contact.last_contacted = json.getLong("last_contacted");
contact.state = json.getInt("state");
return contact;
}
@ -117,14 +118,15 @@ public class EntityContact implements Serializable {
public boolean equals(@Nullable Object obj) {
if (obj instanceof EntityContact) {
EntityContact other = (EntityContact) obj;
return (this.type == other.type &&
return (this.account == other.account &&
this.type == other.type &&
this.email.equals(other.email) &&
Objects.equals(this.name, other.name) &&
Objects.equals(this.avatar, other.avatar) &&
this.times_contacted.equals(other.times_contacted) &&
Objects.equals(this.last_contacted, other.last_contacted) &&
this.first_contacted.equals(first_contacted) &&
this.last_contacted.equals(last_contacted) &&
this.state.equals(other.state));
} else
return false;
}

View File

@ -473,7 +473,7 @@ public class FragmentCompose extends FragmentBase {
@Override
public Cursor runQuery(CharSequence typed) {
Log.i("Searching local contact=" + typed);
return db.contact().searchContacts(null, "%" + typed + "%");
return db.contact().searchContacts(null, null, "%" + typed + "%");
}
});

View File

@ -92,9 +92,9 @@ public class FragmentContacts extends FragmentBase {
}
DB db = DB.getInstance(getContext());
db.contact().liveContacts().observe(getViewLifecycleOwner(), new Observer<List<EntityContact>>() {
db.contact().liveContacts().observe(getViewLifecycleOwner(), new Observer<List<TupleContactEx>>() {
@Override
public void onChanged(List<EntityContact> contacts) {
public void onChanged(List<TupleContactEx> contacts) {
if (contacts == null)
contacts = new ArrayList<>();

View File

@ -0,0 +1,15 @@
package eu.faircode.email;
public class TupleContactEx extends EntityContact {
public String accountName;
@Override
public boolean equals(Object obj) {
if (obj instanceof TupleContactEx) {
TupleContactEx other = (TupleContactEx) obj;
return (super.equals(obj) &&
accountName.equals(other.accountName));
} else
return false;
}
}