mirror of https://github.com/M66B/FairEmail.git
Added account to local contacts
This commit is contained in:
parent
d63536b3fc
commit
5d98923ffc
File diff suppressed because it is too large
Load Diff
|
@ -509,13 +509,14 @@ public class ActivitySetup extends ActivityBilling implements FragmentManager.On
|
|||
}
|
||||
jaccount.put("folders", jfolders);
|
||||
|
||||
jaccounts.put(jaccount);
|
||||
}
|
||||
|
||||
// Contacts
|
||||
JSONArray jcontacts = new JSONArray();
|
||||
for (EntityContact contact : db.contact().getContacts())
|
||||
for (EntityContact contact : db.contact().getContacts(account.id))
|
||||
jcontacts.put(contact.toJSON());
|
||||
jaccount.put("contacts", jcontacts);
|
||||
|
||||
jaccounts.put(jaccount);
|
||||
}
|
||||
|
||||
// Answers
|
||||
JSONArray janswers = new JSONArray();
|
||||
|
@ -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);
|
||||
}
|
||||
|
||||
// Update swipe left/right
|
||||
db.account().updateAccount(account);
|
||||
}
|
||||
|
||||
// Contacts
|
||||
JSONArray jcontacts = jimport.getJSONArray("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);
|
||||
if (db.contact().getContact(contact.type, contact.email) == null) {
|
||||
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);
|
||||
}
|
||||
|
||||
// Answers
|
||||
JSONArray janswers = jimport.getJSONArray("answers");
|
||||
for (int a = 0; a < janswers.length(); a++) {
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
||||
|
|
|
@ -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")
|
||||
|
|
|
@ -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,17 +106,9 @@ 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.first_contacted = json.getLong("first_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");
|
||||
|
||||
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;
|
||||
}
|
||||
|
|
|
@ -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 + "%");
|
||||
}
|
||||
});
|
||||
|
||||
|
|
|
@ -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<>();
|
||||
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue