diff --git a/app/src/main/java/eu/faircode/email/ActivityView.java b/app/src/main/java/eu/faircode/email/ActivityView.java
index 4780324ffd..973b280dc9 100644
--- a/app/src/main/java/eu/faircode/email/ActivityView.java
+++ b/app/src/main/java/eu/faircode/email/ActivityView.java
@@ -195,6 +195,9 @@ public class ActivityView extends ActivityBilling implements FragmentManager.OnB
case R.string.menu_operations:
onMenuOperations();
break;
+ case R.string.menu_contacts:
+ onMenuContacts();
+ break;
case R.string.menu_setup:
onMenuSetup();
break;
@@ -346,20 +349,21 @@ public class ActivityView extends ActivityBilling implements FragmentManager.OnB
nf.format(operations));
items.add(new DrawerItem(-3, R.string.menu_operations, R.drawable.baseline_list_24, title, operations > 0));
- items.add(new DrawerItem(-4, R.drawable.baseline_settings_applications_24, R.string.menu_setup));
- items.add(new DrawerItem(-5));
- items.add(new DrawerItem(-6, R.drawable.baseline_help_24, R.string.menu_legend));
+ items.add(new DrawerItem(-4, R.drawable.baseline_person_24, R.string.menu_contacts));
+ items.add(new DrawerItem(-5, R.drawable.baseline_settings_applications_24, R.string.menu_setup));
+ items.add(new DrawerItem(-6));
+ items.add(new DrawerItem(-7, R.drawable.baseline_help_24, R.string.menu_legend));
if (Helper.getIntentFAQ().resolveActivity(getPackageManager()) != null)
- items.add(new DrawerItem(-7, R.drawable.baseline_question_answer_24, R.string.menu_faq));
+ items.add(new DrawerItem(-8, R.drawable.baseline_question_answer_24, R.string.menu_faq));
if (BuildConfig.BETA_RELEASE)
- items.add(new DrawerItem(-8, R.drawable.baseline_report_problem_24, R.string.menu_issue));
+ items.add(new DrawerItem(-9, R.drawable.baseline_report_problem_24, R.string.menu_issue));
if (Helper.getIntentPrivacy().resolveActivity(getPackageManager()) != null)
- items.add(new DrawerItem(-9, R.drawable.baseline_account_box_24, R.string.menu_privacy));
+ items.add(new DrawerItem(-10, R.drawable.baseline_account_box_24, R.string.menu_privacy));
- items.add(new DrawerItem(-10, R.drawable.baseline_info_24, R.string.menu_about));
+ items.add(new DrawerItem(-11, R.drawable.baseline_info_24, R.string.menu_about));
boolean pro = (getIntentPro() == null || getIntentPro().resolveActivity(getPackageManager()) != null);
boolean invite = (getIntentInvite().resolveActivity(getPackageManager()) != null);
@@ -367,19 +371,19 @@ public class ActivityView extends ActivityBilling implements FragmentManager.OnB
boolean other = (getIntentOtherApps().resolveActivity(getPackageManager()) != null);
if (pro || invite || rate || other)
- items.add(new DrawerItem(-11));
+ items.add(new DrawerItem(-12));
if (pro)
- items.add(new DrawerItem(-12, R.drawable.baseline_monetization_on_24, R.string.menu_pro));
+ items.add(new DrawerItem(-13, R.drawable.baseline_monetization_on_24, R.string.menu_pro));
if (invite)
- items.add(new DrawerItem(-13, R.drawable.baseline_people_24, R.string.menu_invite));
+ items.add(new DrawerItem(-14, R.drawable.baseline_people_24, R.string.menu_invite));
if (rate)
- items.add(new DrawerItem(-14, R.drawable.baseline_star_24, R.string.menu_rate));
+ items.add(new DrawerItem(-15, R.drawable.baseline_star_24, R.string.menu_rate));
if (other)
- items.add(new DrawerItem(-15, R.drawable.baseline_get_app_24, R.string.menu_other));
+ items.add(new DrawerItem(-16, R.drawable.baseline_get_app_24, R.string.menu_other));
drawerArray.set(items);
}
@@ -957,6 +961,12 @@ public class ActivityView extends ActivityBilling implements FragmentManager.OnB
fragmentTransaction.commit();
}
+ private void onMenuContacts() {
+ FragmentTransaction fragmentTransaction = getSupportFragmentManager().beginTransaction();
+ fragmentTransaction.replace(R.id.content_frame, new FragmentContacts()).addToBackStack("contacts");
+ fragmentTransaction.commit();
+ }
+
private void onMenuSetup() {
startActivity(new Intent(ActivityView.this, ActivitySetup.class));
}
diff --git a/app/src/main/java/eu/faircode/email/AdapterContact.java b/app/src/main/java/eu/faircode/email/AdapterContact.java
new file mode 100644
index 0000000000..5ac099f9e5
--- /dev/null
+++ b/app/src/main/java/eu/faircode/email/AdapterContact.java
@@ -0,0 +1,184 @@
+package eu.faircode.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-2019 by Marcel Bokhorst (M66B)
+*/
+
+import android.content.Context;
+import android.net.Uri;
+import android.text.format.DateUtils;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.ImageView;
+import android.widget.TextView;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import androidx.annotation.NonNull;
+import androidx.recyclerview.widget.DiffUtil;
+import androidx.recyclerview.widget.ListUpdateCallback;
+import androidx.recyclerview.widget.RecyclerView;
+
+public class AdapterContact extends RecyclerView.Adapter {
+ private Context context;
+ private LayoutInflater inflater;
+
+ private List all = new ArrayList<>();
+ private List filtered = new ArrayList<>();
+
+ public class ViewHolder extends RecyclerView.ViewHolder {
+ private View itemView;
+ private ImageView ivType;
+ private ImageView ivAvatar;
+ private TextView tvName;
+ private TextView tvEmail;
+ private TextView tvTimes;
+ private TextView tvLast;
+
+ ViewHolder(View itemView) {
+ super(itemView);
+
+ this.itemView = itemView;
+ ivType = itemView.findViewById(R.id.ivType);
+ ivAvatar = itemView.findViewById(R.id.ivAvatar);
+ tvName = itemView.findViewById(R.id.tvName);
+ tvEmail = itemView.findViewById(R.id.tvEmail);
+ tvTimes = itemView.findViewById(R.id.tvTimes);
+ tvLast = itemView.findViewById(R.id.tvLast);
+ }
+
+ private void bindTo(EntityContact contact) {
+ if (contact.type == EntityContact.TYPE_FROM)
+ ivType.setImageResource(R.drawable.baseline_mail_24);
+ else if (contact.type == EntityContact.TYPE_TO)
+ ivType.setImageResource(R.drawable.baseline_send_24);
+ else
+ ivType.setImageDrawable(null);
+
+ if (contact.avatar == null)
+ ivAvatar.setImageDrawable(null);
+ else
+ ivAvatar.setImageURI(Uri.parse(contact.avatar + "/photo"));
+
+ tvName.setText(contact.name);
+ tvEmail.setText(contact.email);
+ tvTimes.setText(Integer.toString(contact.times_contacted));
+ tvLast.setText(contact.last_contacted == null
+ ? null
+ : DateUtils.getRelativeTimeSpanString(context, contact.last_contacted));
+ }
+ }
+
+ AdapterContact(Context context) {
+ this.context = context;
+ this.inflater = LayoutInflater.from(context);
+ setHasStableIds(true);
+ }
+
+ public void set(@NonNull List contacts) {
+ Log.i("Set contacts=" + contacts.size());
+
+ all = contacts;
+
+ DiffUtil.DiffResult diff = DiffUtil.calculateDiff(new DiffCallback(filtered, all));
+
+ filtered.clear();
+ filtered.addAll(all);
+
+ diff.dispatchUpdatesTo(new ListUpdateCallback() {
+ @Override
+ public void onInserted(int position, int count) {
+ Log.i("Inserted @" + position + " #" + count);
+ }
+
+ @Override
+ public void onRemoved(int position, int count) {
+ Log.i("Removed @" + position + " #" + count);
+ }
+
+ @Override
+ public void onMoved(int fromPosition, int toPosition) {
+ Log.i("Moved " + fromPosition + ">" + toPosition);
+ }
+
+ @Override
+ public void onChanged(int position, int count, Object payload) {
+ Log.i("Changed @" + position + " #" + count);
+ }
+ });
+ diff.dispatchUpdatesTo(this);
+ }
+
+ private class DiffCallback extends DiffUtil.Callback {
+ private List prev;
+ private List next;
+
+ DiffCallback(List prev, List next) {
+ this.prev = prev;
+ this.next = next;
+ }
+
+ @Override
+ public int getOldListSize() {
+ return prev.size();
+ }
+
+ @Override
+ public int getNewListSize() {
+ return next.size();
+ }
+
+ @Override
+ public boolean areItemsTheSame(int oldItemPosition, int newItemPosition) {
+ EntityContact c1 = prev.get(oldItemPosition);
+ EntityContact 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);
+ return c1.equals(c2);
+ }
+ }
+
+ @Override
+ public long getItemId(int position) {
+ return filtered.get(position).id;
+ }
+
+ @Override
+ public int getItemCount() {
+ return filtered.size();
+ }
+
+ @Override
+ @NonNull
+ public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
+ return new ViewHolder(inflater.inflate(R.layout.item_contact, parent, false));
+ }
+
+ @Override
+ public void onBindViewHolder(@NonNull ViewHolder holder, int position) {
+ EntityContact contact = filtered.get(position);
+ holder.bindTo(contact);
+ }
+}
diff --git a/app/src/main/java/eu/faircode/email/DaoContact.java b/app/src/main/java/eu/faircode/email/DaoContact.java
index df96928af2..28801bd882 100644
--- a/app/src/main/java/eu/faircode/email/DaoContact.java
+++ b/app/src/main/java/eu/faircode/email/DaoContact.java
@@ -23,6 +23,7 @@ import android.database.Cursor;
import java.util.List;
+import androidx.lifecycle.LiveData;
import androidx.room.Dao;
import androidx.room.Insert;
import androidx.room.Query;
@@ -33,6 +34,10 @@ public interface DaoContact {
@Query("SELECT * FROM contact")
List getContacts();
+ @Query("SELECT * FROM contact" +
+ " ORDER BY times_contacted DESC")
+ LiveData> liveContacts();
+
@Query("SELECT *" +
" FROM contact" +
" WHERE email = :email" +
diff --git a/app/src/main/java/eu/faircode/email/EntityContact.java b/app/src/main/java/eu/faircode/email/EntityContact.java
index e38f2ef63e..d8e748ff40 100644
--- a/app/src/main/java/eu/faircode/email/EntityContact.java
+++ b/app/src/main/java/eu/faircode/email/EntityContact.java
@@ -23,8 +23,10 @@ import org.json.JSONException;
import org.json.JSONObject;
import java.io.Serializable;
+import java.util.Objects;
import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
import androidx.room.Entity;
import androidx.room.Index;
import androidx.room.PrimaryKey;
@@ -94,6 +96,21 @@ public class EntityContact implements Serializable {
return contact;
}
+ @Override
+ public boolean equals(@Nullable Object obj) {
+ if (obj instanceof EntityContact) {
+ EntityContact other = (EntityContact) obj;
+ return (this.type == other.type &&
+ this.email.equals(other.email) &&
+ Objects.equals(this.name, other.name) &&
+ Objects.equals(this.avatar, other.avatar) &&
+ this.times_contacted == other.times_contacted &&
+ Objects.equals(this.last_contacted, other.last_contacted));
+
+ } else
+ return false;
+ }
+
@NonNull
@Override
public String toString() {
diff --git a/app/src/main/java/eu/faircode/email/FragmentContacts.java b/app/src/main/java/eu/faircode/email/FragmentContacts.java
new file mode 100644
index 0000000000..a3fe021b03
--- /dev/null
+++ b/app/src/main/java/eu/faircode/email/FragmentContacts.java
@@ -0,0 +1,90 @@
+package eu.faircode.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-2019 by Marcel Bokhorst (M66B)
+*/
+
+import android.os.Bundle;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.constraintlayout.widget.Group;
+import androidx.lifecycle.Observer;
+import androidx.recyclerview.widget.LinearLayoutManager;
+import androidx.recyclerview.widget.RecyclerView;
+
+public class FragmentContacts extends FragmentBase {
+ private RecyclerView rvContacts;
+ private ContentLoadingProgressBar pbWait;
+ private Group grpReady;
+
+ private AdapterContact adapter;
+
+ @Override
+ @Nullable
+ public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
+ setSubtitle(R.string.menu_contacts);
+
+ View view = inflater.inflate(R.layout.fragment_contacts, container, false);
+
+ // Get controls
+ rvContacts = view.findViewById(R.id.rvContacts);
+ pbWait = view.findViewById(R.id.pbWait);
+ grpReady = view.findViewById(R.id.grpReady);
+
+ // Wire controls
+
+ rvContacts.setHasFixedSize(false);
+ LinearLayoutManager llm = new LinearLayoutManager(getContext());
+ rvContacts.setLayoutManager(llm);
+
+ adapter = new AdapterContact(getContext());
+ rvContacts.setAdapter(adapter);
+
+ // Initialize
+ grpReady.setVisibility(View.GONE);
+ pbWait.setVisibility(View.VISIBLE);
+
+ return view;
+ }
+
+ @Override
+ public void onActivityCreated(@Nullable Bundle savedInstanceState) {
+ super.onActivityCreated(savedInstanceState);
+
+ DB db = DB.getInstance(getContext());
+ db.contact().liveContacts().observe(getViewLifecycleOwner(), new Observer>() {
+ @Override
+ public void onChanged(List contacts) {
+ if (contacts == null)
+ contacts = new ArrayList<>();
+
+ adapter.set(contacts);
+
+ pbWait.setVisibility(View.GONE);
+ grpReady.setVisibility(View.VISIBLE);
+ }
+ });
+ }
+}
diff --git a/app/src/main/res/layout/fragment_contacts.xml b/app/src/main/res/layout/fragment_contacts.xml
new file mode 100644
index 0000000000..b90c5ae6bc
--- /dev/null
+++ b/app/src/main/res/layout/fragment_contacts.xml
@@ -0,0 +1,36 @@
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/layout/item_contact.xml b/app/src/main/res/layout/item_contact.xml
new file mode 100644
index 0000000000..8e3f7c6d2a
--- /dev/null
+++ b/app/src/main/res/layout/item_contact.xml
@@ -0,0 +1,76 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index f3a9ec2338..f05cc9ebb4 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -66,6 +66,7 @@
Templates
Operations
+ Local contacts
Setup
Legend