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