SmartListFragment : add recyclerView
Replace the actual listView with a recyclerView
Tuleap: #1374
Change-Id: I293336c3c221d683887e8068c8b05ea8d4c24fb3
Reviewed-by: Aline Bonnet <aline.bonnet@savoirfairelinux.com>
diff --git a/ring-android/app/build.gradle b/ring-android/app/build.gradle
index 4e79ce9..154f1c4 100644
--- a/ring-android/app/build.gradle
+++ b/ring-android/app/build.gradle
@@ -40,6 +40,9 @@
apt 'com.google.dagger:dagger-compiler:2.7'
compile 'com.google.dagger:dagger:2.7'
provided 'javax.annotation:jsr250-api:1.0'
+
+ // Glide
+ compile 'com.github.bumptech.glide:glide:3.7.0'
}
android {
diff --git a/ring-android/app/src/main/java/cx/ring/adapters/SmartListAdapter.java b/ring-android/app/src/main/java/cx/ring/adapters/SmartListAdapter.java
index 6d38ff3..848644d 100644
--- a/ring-android/app/src/main/java/cx/ring/adapters/SmartListAdapter.java
+++ b/ring-android/app/src/main/java/cx/ring/adapters/SmartListAdapter.java
@@ -1,8 +1,7 @@
/*
- * Copyright (C) 2004-2016 Savoir-faire Linux Inc.
+ * Copyright (C) 2017 Savoir-faire Linux Inc.
*
- * Authors: Adrien Béraud <adrien.beraud@savoirfairelinux.com>
- * Romain Bertozzi <romain.bertozzi@savoirfairelinux.com>
+ * Author: Hadrien De Sousa <hadrien.desousa@savoirfairelinux.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -16,226 +15,65 @@
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
- * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*/
-
package cx.ring.adapters;
import android.content.Context;
-import android.content.res.Resources;
-import android.graphics.Bitmap;
import android.graphics.Typeface;
-import android.text.TextUtils;
+import android.net.Uri;
+import android.provider.ContactsContract;
+import android.support.v7.widget.RecyclerView;
import android.text.format.DateUtils;
-import android.util.Log;
-import android.util.LruCache;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
-import android.view.animation.AnimationUtils;
-import android.widget.BaseAdapter;
-import android.widget.ImageView;
-import android.widget.TextView;
-import java.lang.ref.WeakReference;
-import java.text.Normalizer;
+import com.bumptech.glide.Glide;
+
import java.util.ArrayList;
-import java.util.Collection;
-import java.util.Date;
-import java.util.HashMap;
-import java.util.List;
-import java.util.concurrent.ExecutorService;
-import butterknife.BindView;
-import butterknife.ButterKnife;
import cx.ring.R;
import cx.ring.model.HistoryCall;
import cx.ring.model.HistoryEntry;
-import cx.ring.utils.Tuple;
-import cx.ring.model.CallContact;
-import cx.ring.model.Conversation;
-import cx.ring.model.Phone;
import cx.ring.model.TextMessage;
+import cx.ring.smartlist.SmartListViewModel;
+import cx.ring.utils.CircleTransform;
+import cx.ring.viewholders.SmartListViewHolder;
-public class SmartListAdapter extends BaseAdapter {
+public class SmartListAdapter extends RecyclerView.Adapter<SmartListViewHolder> {
- private static String TAG = SmartListAdapter.class.getSimpleName();
+ private ArrayList<SmartListViewModel> mSmartListViewModels;
+ private SmartListViewHolder.SmartListListeners listener;
- private final ArrayList<Conversation> mConversations = new ArrayList<>();
- private final ExecutorService mInfosFetcher;
- private final LruCache<Long, Bitmap> mMemoryCache;
- private final HashMap<Long, WeakReference<ContactDetailsTask>> mRunningTasks = new HashMap<>();
-
- private final Context mContext;
-
- public interface SmartListAdapterCallback {
- void pictureTapped(Conversation conversation);
- }
-
- private static SmartListAdapterCallback sDummyCallbacks = new SmartListAdapterCallback() {
- @Override
- public void pictureTapped(Conversation conversation) {
- // Stub
- }
- };
-
- private SmartListAdapterCallback mCallbacks = sDummyCallbacks;
-
- public SmartListAdapter(Context act, LruCache<Long, Bitmap> cache, ExecutorService pool) {
- super();
- mContext = act;
- mMemoryCache = cache;
- mInfosFetcher = pool;
- }
-
- private String stringFormatting(String query) {
- return Normalizer.normalize(query.toLowerCase(), Normalizer.Form.NFD).replaceAll("[\u0300-\u036F]", "");
- }
-
- public void emptyDataset () {
- mConversations.clear();
- notifyDataSetChanged();
- }
-
- public void updateDataset(final Collection<Conversation> list, String query) {
- Log.d(TAG, "updateDataset " + list.size() + " with query: " + query);
-
- if (list.isEmpty() && mConversations.isEmpty()) {
- return;
- }
-
- List<Conversation> newConversations = new ArrayList<>();
-
- for (Conversation c : list) {
- if (!c.getContact().isUnknown()
- || !c.getAccountsUsed().isEmpty()
- || c.getCurrentCall() != null) {
- if (TextUtils.isEmpty(query) || c.getCurrentCall() != null) {
- newConversations.add(c);
- } else if (c.getContact() != null) {
- CallContact contact = c.getContact();
- if (!TextUtils.isEmpty(contact.getDisplayName()) &&
- stringFormatting(contact.getDisplayName()).contains(stringFormatting(query))) {
- newConversations.add(c);
- } else if (contact.getPhones() != null && !contact.getPhones().isEmpty()) {
- ArrayList<Phone> phones = contact.getPhones();
- for (Phone phone : phones) {
- if (phone.getNumber() != null) {
- String rawUriString = phone.getNumber().getRawUriString();
- if (!TextUtils.isEmpty(rawUriString) &&
- stringFormatting(rawUriString.toLowerCase()).contains(stringFormatting(query))) {
- newConversations.add(c);
- }
- }
- }
- }
- }
- }
- }
-
- mConversations.clear();
- mConversations.addAll(newConversations);
- notifyDataSetChanged();
- }
-
- /**
- *
- * @return true if list are different
- */
- private boolean areConversationListDifferent(List<Conversation> leftList, List<Conversation> rightList) {
- if (leftList == null || rightList == null) {
- return true;
- }
-
- if (leftList.size() != rightList.size()) {
- return true;
- }
-
- for (Conversation rightConversation: rightList) {
- if (rightConversation.hasCurrentCall() || rightConversation.hasUnreadTextMessages()) {
- return true;
- }
-
- int rightId = rightConversation.getUuid();
- CallContact rightContact = rightConversation.getContact();
- boolean found = false;
- for (Conversation leftConversation: leftList) {
-
- if (leftConversation.hasCurrentCall() || leftConversation.hasUnreadTextMessages()) {
- return true;
- }
-
- int leftId = leftConversation.getUuid();
- CallContact leftContact = leftConversation.getContact();
- if (leftId == rightId
- && leftContact!=null
- && leftContact.equals(rightContact)) {
- found = true;
- break;
- }
- }
-
- if (!found) {
- return true;
- }
- }
-
- return false;
+ public SmartListAdapter(ArrayList<SmartListViewModel> smartListViewModels, SmartListViewHolder.SmartListListeners listener) {
+ this.mSmartListViewModels = new ArrayList<>(smartListViewModels);
+ this.listener = listener;
}
@Override
- public int getCount() {
- return mConversations.size();
+ public SmartListViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
+ View v = LayoutInflater.from(parent.getContext()).inflate(R.layout.item_smartlist, parent, false);
+
+ return new SmartListViewHolder(v);
}
@Override
- public Conversation getItem(int position) {
- return mConversations.get(position);
- }
+ public void onBindViewHolder(SmartListViewHolder holder, int position) {
+ final SmartListViewModel smartListViewModel = mSmartListViewModels.get(position);
- @Override
- public long getItemId(int position) {
- return 0;
- }
-
- public class ViewHolder {
- @BindView(R.id.conv_participant)
- TextView convParticipants;
- @BindView(R.id.conv_last_item)
- TextView convStatus;
- @BindView(R.id.conv_last_time)
- TextView convTime;
- @BindView(R.id.photo)
- ImageView photo;
- int position;
- public Conversation conv;
-
- public ViewHolder(View view) {
- ButterKnife.bind(this, view);
- }
- }
-
- @Override
- public View getView(final int position, View convertView, ViewGroup parent) {
- if (convertView == null) {
- convertView = LayoutInflater.from(mContext).inflate(cx.ring.R.layout.item_smartlist, null);
- }
-
- final ViewHolder holder = convertView.getTag() != null
- ? (ViewHolder) convertView.getTag()
- : new ViewHolder(convertView);
-
- holder.position = -1;
- convertView.setTag(holder);
-
- holder.conv = mConversations.get(position);
- holder.position = position;
- holder.convParticipants.setText(holder.conv.getContact().getDisplayName());
- long lastInteraction = holder.conv.getLastInteraction().getTime();
+ holder.convParticipants.setText(smartListViewModel.getContactName());
+ long lastInteraction = smartListViewModel.getLastInteractionTime();
holder.convTime.setText(lastInteraction == 0 ? "" :
DateUtils.getRelativeTimeSpanString(lastInteraction, System.currentTimeMillis(), 0L, DateUtils.FORMAT_ABBREV_ALL));
- holder.convStatus.setText(getLastInteractionSummary(holder.conv, mContext.getResources()));
- if (holder.conv.hasUnreadTextMessages()) {
+ if (smartListViewModel.hasOngoingCall()) {
+ holder.convStatus.setText(holder.itemView.getContext().getString(R.string.ongoing_call));
+ } else if (smartListViewModel.getLastInteraction() != null) {
+ holder.convStatus.setText(getLastInteractionSummary(smartListViewModel.getLastInteraction(), holder.itemView.getContext()));
+ } else {
+ holder.convStatus.setText("");
+ }
+ if (smartListViewModel.hasUnreadTextMessage()) {
holder.convParticipants.setTypeface(null, Typeface.BOLD);
holder.convTime.setTypeface(null, Typeface.BOLD);
holder.convStatus.setTypeface(null, Typeface.BOLD);
@@ -245,93 +83,50 @@
holder.convStatus.setTypeface(null, Typeface.NORMAL);
}
- holder.photo.setOnClickListener(new View.OnClickListener() {
- @Override
- public void onClick(View v) {
- mCallbacks.pictureTapped(holder.conv);
- }
- });
+ String photoUri = smartListViewModel.getPhotoUri();
- final Long cid = holder.conv.getContact().getId();
- final Bitmap bmp = mMemoryCache.get(cid);
- if (bmp != null && cid != -1L) {
- holder.photo.setImageBitmap(bmp);
+ if (photoUri != null) {
+ if (isContentUri(photoUri)) {
+ Glide.with(holder.itemView.getContext())
+ .load(Uri.withAppendedPath(Uri.parse(photoUri), ContactsContract.Contacts.Photo.DISPLAY_PHOTO))
+ .placeholder(R.drawable.ic_contact_picture)
+ .crossFade()
+ .transform(new CircleTransform(holder.itemView.getContext()))
+ .error(R.drawable.ic_contact_picture)
+ .into(holder.photo);
+ }
+ } else if (smartListViewModel.getPhotoData() != null) {
+ Glide.with(holder.itemView.getContext())
+ .load(smartListViewModel.getPhotoData())
+ .placeholder(R.drawable.ic_contact_picture)
+ .crossFade()
+ .transform(new CircleTransform(holder.itemView.getContext()))
+ .error(R.drawable.ic_contact_picture)
+ .into(holder.photo);
} else {
- final WeakReference<ViewHolder> holderWeakRef = new WeakReference<>(holder);
- final ContactDetailsTask.DetailsLoadedCallback cb = new ContactDetailsTask.DetailsLoadedCallback() {
- @Override
- public void onDetailsLoaded(final Bitmap bmp, final String name) {
- final ViewHolder holder = holderWeakRef.get();
- if (holder == null || holder.photo.getParent() == null)
- return;
- if (holder.conv.getContact().getId() == cid) {
- holder.photo.post(new Runnable() {
- @Override
- public void run() {
- holder.photo.setImageBitmap(bmp);
- holder.photo.startAnimation(AnimationUtils.loadAnimation(holder.photo.getContext(), R.anim.contact_fadein));
- }
- });
-
- holder.convParticipants.post(new Runnable() {
- @Override
- public void run() {
- holder.convParticipants.setText(name);
- holder.photo.startAnimation(AnimationUtils.loadAnimation(holder.convParticipants.getContext(), R.anim.contact_fadein));
- }
- });
- }
- }
- };
- WeakReference<ContactDetailsTask> wtask = mRunningTasks.get(cid);
- ContactDetailsTask task = wtask == null ? null : wtask.get();
- if (task != null && cid != -1L) {
- task.addCallback(cb);
- } else {
- task = new ContactDetailsTask(mContext, holder.photo, holder.convParticipants, holder.conv.getContact(), new ContactDetailsTask.DetailsLoadedCallback() {
- @Override
- public void onDetailsLoaded(Bitmap bmp, final String name) {
- mMemoryCache.put(cid, bmp);
- mRunningTasks.remove(cid);
- }
- });
- task.addCallback(cb);
- mRunningTasks.put(cid, new WeakReference<>(task));
- mInfosFetcher.execute(task);
- }
+ Glide.with(holder.itemView.getContext())
+ .load(R.drawable.ic_contact_picture)
+ .into(holder.photo);
}
- return convertView;
+ holder.bind(listener, smartListViewModel);
}
- public void setCallback(SmartListAdapterCallback callback) {
- if (callback == null) {
- this.mCallbacks = sDummyCallbacks;
- return;
- }
- this.mCallbacks = callback;
+ @Override
+ public int getItemCount() {
+ return mSmartListViewModels.size();
}
- private String getLastInteractionSummary(Conversation conversation, Resources resources) {
- if (conversation.hasCurrentCall()) {
- return resources.getString(R.string.ongoing_call);
- }
- Tuple<Date, String> d = new Tuple<>(new Date(0), null);
-
- for (HistoryEntry e : conversation.getHistory().values()) {
- Date entryDate = e.getLastInteractionDate();
- String entrySummary = getLastInteractionSummary(e, resources);
- if (entryDate == null || entrySummary == null) {
- continue;
- }
- Tuple<Date, String> tmp = new Tuple<>(entryDate, entrySummary);
- if (d.first.compareTo(entryDate) < 0) {
- d = tmp;
- }
- }
- return d.second;
+ public void replaceAll(ArrayList<SmartListViewModel> list) {
+ mSmartListViewModels.clear();
+ mSmartListViewModels.addAll(list);
+ notifyDataSetChanged();
}
- private String getLastInteractionSummary(HistoryEntry e, Resources resources) {
+ private boolean isContentUri(String uri) {
+ return uri.contains("content://");
+ }
+
+ private String getLastInteractionSummary(HistoryEntry e, Context context) {
long lastTextTimestamp = e.getTextMessages().isEmpty() ? 0 : e.getTextMessages().lastEntry().getKey();
long lastCallTimestamp = e.getCalls().isEmpty() ? 0 : e.getCalls().lastEntry().getKey();
if (lastTextTimestamp > 0 && lastTextTimestamp > lastCallTimestamp) {
@@ -343,14 +138,14 @@
msgString = msgString.substring(msgString.lastIndexOf("\n") + 1);
}
}
- return (msg.isIncoming() ? "" : resources.getText(R.string.you_txt_prefix) + " ") + msgString;
+ return (msg.isIncoming() ? "" : context.getText(R.string.you_txt_prefix) + " ") + msgString;
}
if (lastCallTimestamp > 0) {
HistoryCall lastCall = e.getCalls().lastEntry().getValue();
- return String.format(resources.getString(lastCall.isIncoming()
+ return String.format(context.getString(lastCall.isIncoming()
? R.string.hist_in_call
: R.string.hist_out_call), lastCall.getDurationString());
}
return null;
}
-}
+}
\ No newline at end of file
diff --git a/ring-android/app/src/main/java/cx/ring/fragments/SmartListFragment.java b/ring-android/app/src/main/java/cx/ring/fragments/SmartListFragment.java
index 4310dec..882d188 100644
--- a/ring-android/app/src/main/java/cx/ring/fragments/SmartListFragment.java
+++ b/ring-android/app/src/main/java/cx/ring/fragments/SmartListFragment.java
@@ -23,7 +23,6 @@
import android.app.Activity;
import android.app.Fragment;
-import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.net.Uri;
@@ -34,6 +33,8 @@
import android.support.design.widget.Snackbar;
import android.support.v4.view.MenuItemCompat;
import android.support.v7.app.AlertDialog;
+import android.support.v7.widget.LinearLayoutManager;
+import android.support.v7.widget.RecyclerView;
import android.support.v7.widget.SearchView;
import android.support.v7.widget.Toolbar;
import android.text.InputType;
@@ -45,17 +46,14 @@
import android.view.View;
import android.view.ViewGroup;
import android.view.inputmethod.EditorInfo;
-import android.widget.AdapterView;
-import android.widget.AdapterView.OnItemClickListener;
import android.widget.ImageView;
-import android.widget.ListView;
import android.widget.ProgressBar;
import android.widget.TextView;
import com.google.zxing.integration.android.IntentIntegrator;
import com.google.zxing.integration.android.IntentResult;
-import java.util.Collection;
+import java.util.ArrayList;
import javax.inject.Inject;
@@ -75,14 +73,16 @@
import cx.ring.service.LocalService;
import cx.ring.smartlist.SmartListPresenter;
import cx.ring.smartlist.SmartListView;
+import cx.ring.smartlist.SmartListViewModel;
import cx.ring.utils.ActionHelper;
import cx.ring.utils.ClipboardHelper;
import cx.ring.utils.ContentUriHandler;
import cx.ring.utils.NetworkUtils;
+import cx.ring.viewholders.SmartListViewHolder;
public class SmartListFragment extends BaseFragment<SmartListPresenter> implements SearchView.OnQueryTextListener,
HomeActivity.Refreshable,
- SmartListAdapter.SmartListAdapterCallback,
+ SmartListViewHolder.SmartListListeners,
Conversation.ConversationActionCallback,
ClipboardHelper.ClipboardHelperCallback,
SmartListView {
@@ -90,7 +90,6 @@
private static final String TAG = SmartListFragment.class.getSimpleName();
private static final String STATE_LOADING = TAG + ".STATE_LOADING";
- private static final int USER_INPUT_DELAY = 300;
@Inject
protected SmartListPresenter mSmartListPresenter;
@@ -99,13 +98,13 @@
protected FloatingActionButton mFloatingActionButton;
@BindView(R.id.confs_list)
- protected ListView mList;
+ protected RecyclerView mRecyclerView;
@BindView(R.id.loading_indicator)
protected ProgressBar mLoader;
- @BindView(R.id.emptyTextView)
- protected TextView mEmptyTextView = null;
+ @BindView(R.id.empty_text_view)
+ protected TextView mEmptyTextView;
@BindView(R.id.newcontact_element)
protected ViewGroup mNewContact;
@@ -119,7 +118,6 @@
@BindView(R.id.error_image_view)
protected ImageView mErrorImageView;
- private LocalService.Callbacks mCallbacks = LocalService.DUMMY_CALLBACKS;
private SmartListAdapter mSmartListAdapter;
private SearchView mSearchView = null;
@@ -138,33 +136,14 @@
throw new IllegalStateException("Activity must implement fragment's callbacks.");
}
- mCallbacks = (LocalService.Callbacks) activity;
}
public void refresh() {
-
- LocalService service = mCallbacks.getService();
- if (service == null) {
- Log.e(TAG, "refresh: null service");
- return;
- }
-
- if (mSmartListAdapter == null) {
- bindService(getActivity(), service);
- }
-
mSmartListPresenter.refresh(NetworkUtils.isConnectedWifi(getActivity()),
NetworkUtils.isConnectedMobile(getActivity()));
}
@Override
- public void onDetach() {
- super.onDetach();
- Log.d(TAG, "onDetach");
- mCallbacks = LocalService.DUMMY_CALLBACKS;
- }
-
- @Override
public void onResume() {
super.onResume();
@@ -290,20 +269,12 @@
// dependency injection
((RingApplication) getActivity().getApplication()).getRingInjectionComponent().inject(this);
- mList.setOnItemClickListener(conversationClickListener);
- mList.setOnItemLongClickListener(conversationLongClickListener);
-
if (savedInstanceState != null) {
this.setLoading(savedInstanceState.getBoolean(STATE_LOADING, false));
}
mNewContact.setVisibility(View.GONE);
- LocalService service = mCallbacks.getService();
- if (service != null) {
- bindService(inflater.getContext(), service);
- }
-
if (ConversationFragment.isTabletMode(getActivity())) {
isTabletMode = true;
}
@@ -329,17 +300,6 @@
}
}
- public void bindService(final Context ctx, final LocalService service) {
- mSmartListAdapter = new SmartListAdapter(ctx,
- service.get40dpContactCache(),
- service.getThreadPool());
-
- mSmartListAdapter.setCallback(this);
- if (mList != null) {
- mList.setAdapter(mSmartListAdapter);
- }
- }
-
public void startConversationTablet(Bundle bundle) {
mConversationFragment = new ConversationFragment();
mConversationFragment.setArguments(bundle);
@@ -349,57 +309,6 @@
.commit();
}
- private final OnItemClickListener conversationClickListener = new OnItemClickListener() {
- @Override
- public void onItemClick(AdapterView<?> arg0, View v, int arg2, long arg3) {
- presenter.conversationClicked(((SmartListAdapter.ViewHolder) v.getTag()).conv.getContact());
- }
- };
-
- private final AdapterView.OnItemLongClickListener conversationLongClickListener =
- new AdapterView.OnItemLongClickListener() {
- @Override
- public boolean onItemLongClick(AdapterView<?> parent, View v, int position, long id) {
- presentActions(getActivity(),
- ((SmartListAdapter.ViewHolder) v.getTag()).conv,
- SmartListFragment.this);
- return true;
- }
- };
-
- public static void presentActions(final Activity activity,
- final Conversation conversation,
- final Conversation.ConversationActionCallback callback) {
- if (activity == null) {
- cx.ring.utils.Log.d(TAG, "presentActions: activity is null");
- return;
- }
-
- if (conversation == null) {
- cx.ring.utils.Log.d(TAG, "presentActions: conversation is null");
- return;
- }
-
- AlertDialog.Builder builder = new AlertDialog.Builder(activity);
- builder.setItems(R.array.conversation_actions, new DialogInterface.OnClickListener() {
- @Override
- public void onClick(DialogInterface dialog, int which) {
- switch (which) {
- case 0:
- ActionHelper.launchCopyNumberToClipboardFromContact(activity,
- conversation.getContact(),
- callback);
- break;
- case 1:
- ActionHelper.launchDeleteAction(activity, conversation, callback);
- break;
- }
- }
- });
- AlertDialog dialog = builder.create();
- dialog.show();
- }
-
@Override
public void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
@@ -412,22 +321,17 @@
}
}
- private void setLoading(boolean loading) {
- if (this.mLoader == null) {
- return;
- }
- int loaderVisibility = (loading) ? View.VISIBLE : View.GONE;
- this.mLoader.setVisibility(loaderVisibility);
- if (loading) {
- this.mEmptyTextView.setText("");
- } else {
- String emptyText = getResources().getQuantityString(R.plurals.home_conferences_title, 0, 0);
- this.mEmptyTextView.setText(emptyText);
- }
-
- if (mList != null) {
- mList.setEmptyView(this.mEmptyTextView);
- }
+ @Override
+ public void setLoading(final boolean loading) {
+ getActivity().runOnUiThread(new Runnable() {
+ @Override
+ public void run() {
+ if (mLoader == null) {
+ return;
+ }
+ mLoader.setVisibility(loading ? View.VISIBLE : View.GONE);
+ }
+ });
}
/**
@@ -465,20 +369,6 @@
}
@Override
- public void pictureTapped(Conversation conversation) {
- if (conversation == null) {
- Log.d(TAG, "pictureTapped, conversation is null");
- return;
- }
- if (conversation.getContact() != null) {
- Activity activity = getActivity();
- if (activity != null) {
- ActionHelper.displayContact(getActivity(), conversation.getContact());
- }
- }
- }
-
- @Override
public void deleteConversation(Conversation conversation) {
mSmartListPresenter.deleteConversation(conversation);
}
@@ -567,43 +457,84 @@
}
@Override
+ public void displayNoConversationMessage() {
+ getActivity().runOnUiThread(new Runnable() {
+ @Override
+ public void run() {
+ String emptyText = getResources().getQuantityString(R.plurals.home_conferences_title, 0, 0);
+ mEmptyTextView.setText(emptyText);
+ mEmptyTextView.setVisibility(View.VISIBLE);
+ }
+ });
+ }
+
+ @Override
+ public void displayConversationDialog(final Conversation conversation) {
+ AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
+ builder.setItems(R.array.conversation_actions, new DialogInterface.OnClickListener() {
+ @Override
+ public void onClick(DialogInterface dialog, int which) {
+ switch (which) {
+ case 0:
+ ActionHelper.launchCopyNumberToClipboardFromContact(getActivity(),
+ conversation.getContact(),
+ SmartListFragment.this);
+ break;
+ case 1:
+ ActionHelper.launchDeleteAction(getActivity(), conversation, SmartListFragment.this);
+ break;
+ }
+ }
+ });
+ AlertDialog dialog = builder.create();
+ dialog.show();
+ }
+
+ @Override
public void hideSearchRow() {
- mNewContact.setVisibility(View.GONE);
+ getActivity().runOnUiThread(new Runnable() {
+ @Override
+ public void run() {
+ mNewContact.setVisibility(View.GONE);
+ }
+ });
}
@Override
public void hideErrorPanel() {
- if (mErrorMessagePane != null) {
- mErrorMessagePane.setVisibility(View.GONE);
- }
- }
-
- @Override
- public void updateViewWithQuery(final Collection<Conversation> list, final String query) {
- Handler mUserInputHandler = new Handler();
- mUserInputHandler.removeCallbacksAndMessages(null);
- mUserInputHandler.postDelayed(new Runnable() {
- @Override
- public void run() {
- if (mCallbacks.getService() == null) {
- Log.d(TAG, "onQueryTextChange: null service");
- } else {
- mSmartListAdapter.updateDataset(
- list,
- query
- );
- }
- }
- }, USER_INPUT_DELAY);
- }
-
- @Override
- public void updateView(final Collection<Conversation> list) {
getActivity().runOnUiThread(new Runnable() {
@Override
public void run() {
- mSmartListAdapter.updateDataset(list, null);
- setLoading(false);
+ mErrorMessagePane.setVisibility(View.GONE);
+ }
+ });
+ }
+
+ @Override
+ public void hideNoConversationMessage() {
+ getActivity().runOnUiThread(new Runnable() {
+ @Override
+ public void run() {
+ mEmptyTextView.setVisibility(View.GONE);
+ }
+ });
+ }
+
+ @Override
+ public void updateView(final ArrayList<SmartListViewModel> list) {
+ getActivity().runOnUiThread(new Runnable() {
+ @Override
+ public void run() {
+ if (mRecyclerView.getAdapter() != null) {
+ mSmartListAdapter.replaceAll(list);
+ } else {
+ mSmartListAdapter = new SmartListAdapter(list, SmartListFragment.this);
+ mRecyclerView.setAdapter(mSmartListAdapter);
+ mRecyclerView.setHasFixedSize(true);
+ LinearLayoutManager llm = new LinearLayoutManager(getActivity());
+ llm.setOrientation(LinearLayoutManager.VERTICAL);
+ mRecyclerView.setLayoutManager(llm);
+ }
}
});
}
@@ -641,8 +572,30 @@
}
@Override
+ public void goToContact(CallContact callContact) {
+ Activity activity = getActivity();
+ if (activity != null) {
+ ActionHelper.displayContact(getActivity(), callContact);
+ }
+ }
+
+ @Override
protected SmartListPresenter createPresenter() {
return mSmartListPresenter;
}
+ @Override
+ public void onItemClick(SmartListViewModel smartListViewModel) {
+ presenter.conversationClicked(smartListViewModel);
+ }
+
+ @Override
+ public void onItemLongClick(SmartListViewModel smartListViewModel) {
+ presenter.conversationLongClicked(smartListViewModel);
+ }
+
+ @Override
+ public void onPhotoClick(SmartListViewModel smartListViewModel) {
+ presenter.photoClicked(smartListViewModel);
+ }
}
diff --git a/ring-android/app/src/main/java/cx/ring/services/ContactServiceImpl.java b/ring-android/app/src/main/java/cx/ring/services/ContactServiceImpl.java
index a10fe2c..3aa2538 100644
--- a/ring-android/app/src/main/java/cx/ring/services/ContactServiceImpl.java
+++ b/ring-android/app/src/main/java/cx/ring/services/ContactServiceImpl.java
@@ -25,9 +25,11 @@
import android.database.Cursor;
import android.provider.ContactsContract;
import android.support.annotation.NonNull;
+import android.text.TextUtils;
import android.util.Log;
import android.util.LongSparseArray;
+import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;
@@ -35,6 +37,9 @@
import cx.ring.model.CallContact;
import cx.ring.model.Uri;
+import cx.ring.utils.Tuple;
+import cx.ring.utils.VCardUtils;
+import ezvcard.VCard;
public class ContactServiceImpl extends ContactService {
@@ -131,6 +136,7 @@
if (contact == null) {
contact = new CallContact(contactId);
isNewContact = true;
+ contact.setFromSystem(true);
}
String contactNumber = contactCursor.getString(indexNumber);
@@ -378,4 +384,45 @@
return callContact;
}
+
+ @Override
+ public Tuple<String, String> loadContactDataFromSystem(CallContact callContact) {
+
+ String contactName = callContact.getDisplayName();
+ String photoURI = ContentUris.withAppendedId(ContactsContract.Contacts.CONTENT_URI, callContact.getId()).toString();
+
+ return new Tuple<>(contactName, photoURI);
+ }
+
+ @Override
+ public Tuple<String, byte[]> loadContactData(CallContact callContact) {
+
+ String contactName = null;
+ byte[] photoURI = null;
+ VCard vcard = null;
+
+ if (!callContact.getPhones().isEmpty()) {
+ String username = callContact.getPhones().get(0).getNumber().getHost();
+ vcard = VCardUtils.loadPeerProfileFromDisk(mContext.getFilesDir(), username + ".vcf");
+
+ if (vcard != null && vcard.getFormattedName() != null) {
+ if (!TextUtils.isEmpty(vcard.getFormattedName().getValue())) {
+ contactName = vcard.getFormattedName().getValue();
+ }
+ }
+ }
+ if (contactName == null) {
+ contactName = callContact.getDisplayName();
+ }
+
+ if (vcard != null && !vcard.getPhotos().isEmpty()) {
+ try {
+ photoURI = vcard.getPhotos().get(0).getData();
+ } catch (Exception e) {
+ photoURI = null;
+ }
+ }
+
+ return new Tuple<>(contactName, photoURI);
+ }
}
diff --git a/ring-android/app/src/main/java/cx/ring/utils/BitmapUtils.java b/ring-android/app/src/main/java/cx/ring/utils/BitmapUtils.java
index c421506..a51c10c 100644
--- a/ring-android/app/src/main/java/cx/ring/utils/BitmapUtils.java
+++ b/ring-android/app/src/main/java/cx/ring/utils/BitmapUtils.java
@@ -31,7 +31,6 @@
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
-import java.io.ByteArrayOutputStream;
import java.nio.ByteBuffer;
/**
diff --git a/ring-android/app/src/main/java/cx/ring/utils/CircleTransform.java b/ring-android/app/src/main/java/cx/ring/utils/CircleTransform.java
new file mode 100644
index 0000000..21be7bb
--- /dev/null
+++ b/ring-android/app/src/main/java/cx/ring/utils/CircleTransform.java
@@ -0,0 +1,72 @@
+/*
+ * Copyright (C) 2017 Savoir-faire Linux Inc.
+ *
+ * Author: Hadrien De Sousa <hadrien.desousa@savoirfairelinux.com>
+ *
+ * This program 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.
+ *
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+package cx.ring.utils;
+
+import android.content.Context;
+import android.graphics.Bitmap;
+import android.graphics.BitmapShader;
+import android.graphics.Canvas;
+import android.graphics.Paint;
+
+import com.bumptech.glide.load.engine.bitmap_recycle.BitmapPool;
+import com.bumptech.glide.load.resource.bitmap.BitmapTransformation;
+
+public class CircleTransform extends BitmapTransformation {
+
+ public CircleTransform(Context context) {
+ super(context);
+ }
+
+ @Override
+ protected Bitmap transform(BitmapPool pool, Bitmap toTransform, int outWidth, int outHeight) {
+ return circleCrop(pool, toTransform);
+ }
+
+ private static Bitmap circleCrop(BitmapPool pool, Bitmap source) {
+ if (source == null) {
+ return null;
+ }
+
+ int size = Math.min(source.getWidth(), source.getHeight());
+ int x = (source.getWidth() - size) / 2;
+ int y = (source.getHeight() - size) / 2;
+
+ Bitmap squared = Bitmap.createBitmap(source, x, y, size, size);
+
+ Bitmap result = pool.get(size, size, Bitmap.Config.ARGB_8888);
+ if (result == null) {
+ result = Bitmap.createBitmap(size, size, Bitmap.Config.ARGB_8888);
+ }
+
+ Canvas canvas = new Canvas(result);
+ Paint paint = new Paint();
+ paint.setShader(new BitmapShader(squared, BitmapShader.TileMode.CLAMP, BitmapShader.TileMode.CLAMP));
+ paint.setAntiAlias(true);
+ float radius = size / 2f;
+ canvas.drawCircle(radius, radius, radius, paint);
+ return result;
+ }
+
+ @Override
+ public String getId() {
+ return getClass().getName();
+ }
+}
+
diff --git a/ring-android/app/src/main/java/cx/ring/viewholders/SmartListViewHolder.java b/ring-android/app/src/main/java/cx/ring/viewholders/SmartListViewHolder.java
new file mode 100644
index 0000000..18b9036
--- /dev/null
+++ b/ring-android/app/src/main/java/cx/ring/viewholders/SmartListViewHolder.java
@@ -0,0 +1,79 @@
+/*
+ * Copyright (C) 2017 Savoir-faire Linux Inc.
+ *
+ * Author: Hadrien De Sousa <hadrien.desousa@savoirfairelinux.com>
+ *
+ * This program 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.
+ *
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+package cx.ring.viewholders;
+
+import android.support.v7.widget.RecyclerView;
+import android.view.View;
+import android.widget.ImageView;
+import android.widget.TextView;
+
+import butterknife.BindView;
+import butterknife.ButterKnife;
+import cx.ring.R;
+import cx.ring.smartlist.SmartListViewModel;
+
+public class SmartListViewHolder extends RecyclerView.ViewHolder {
+
+ @BindView(R.id.conv_participant)
+ public TextView convParticipants;
+ @BindView(R.id.conv_last_item)
+ public TextView convStatus;
+ @BindView(R.id.conv_last_time)
+ public TextView convTime;
+ @BindView(R.id.photo)
+ public ImageView photo;
+
+ public SmartListViewHolder(View itemView) {
+ super(itemView);
+ ButterKnife.bind(this, itemView);
+ }
+
+ public void bind(final SmartListListeners clickListener, final SmartListViewModel smartListViewModel) {
+ itemView.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ clickListener.onItemClick(smartListViewModel);
+
+ }
+ });
+ itemView.setOnLongClickListener(new View.OnLongClickListener() {
+ @Override
+ public boolean onLongClick(View v) {
+ clickListener.onItemLongClick(smartListViewModel);
+ return true;
+ }
+ });
+ photo.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ clickListener.onPhotoClick(smartListViewModel);
+ }
+ });
+ }
+
+ public interface SmartListListeners {
+ void onItemClick(SmartListViewModel smartListViewModel);
+
+ void onItemLongClick(SmartListViewModel smartListViewModel);
+
+ void onPhotoClick(SmartListViewModel smartListViewModel);
+ }
+
+}
diff --git a/ring-android/app/src/main/res/layout-w960dp-land/frag_smartlist.xml b/ring-android/app/src/main/res/layout-w960dp-land/frag_smartlist.xml
index 9eb2960..c6e5d80 100644
--- a/ring-android/app/src/main/res/layout-w960dp-land/frag_smartlist.xml
+++ b/ring-android/app/src/main/res/layout-w960dp-land/frag_smartlist.xml
@@ -1,114 +1,106 @@
<?xml version="1.0" encoding="utf-8"?>
-
-<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:animateLayoutChanges="true"
- android:orientation="horizontal"
tools:context=".client.HomeActivity">
<RelativeLayout
+ android:id="@+id/error_msg_pane"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_alignParentLeft="true"
+ android:layout_alignParentStart="true"
+ android:layout_alignParentTop="true"
+ android:background="@color/error_pane"
+ android:padding="16dp"
+ android:visibility="gone">
+
+ <ImageView
+ android:id="@+id/error_image_view"
+ android:layout_width="28dp"
+ android:layout_height="28dp"
+ android:layout_alignParentEnd="true"
+ android:layout_alignParentRight="true"
+ android:layout_gravity="right"
+ android:scaleType="fitCenter"
+ android:src="@drawable/ic_settings_white" />
+
+ <TextView
+ android:id="@+id/error_msg_txt"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_centerHorizontal="true"
+ android:layout_centerVertical="true"
+ android:layout_marginLeft="26dp"
+ android:layout_marginRight="26dp"
+ android:gravity="center_vertical|center_horizontal"
+ android:textColor="@color/white"
+ android:textSize="14sp" />
+ </RelativeLayout>
+
+
+ <LinearLayout
+ android:id="@+id/newcontact_element"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_below="@+id/error_msg_pane"
+ android:background="?android:attr/selectableItemBackground"
+ android:orientation="vertical"
+ android:visibility="gone">
+
+ <include
+ layout="@layout/item_contact"
+ android:layout_width="match_parent"
+ android:layout_height="72dp" />
+
+ <View
+ android:layout_width="match_parent"
+ android:layout_height="1dp"
+ android:background="?android:attr/listDivider" />
+
+ </LinearLayout>
+
+ <FrameLayout
+ android:id="@+id/smartlist_layout"
android:layout_width="320dp"
- android:layout_height="match_parent">
+ android:layout_height="match_parent"
+ android:layout_below="@+id/newcontact_element">
- <RelativeLayout
- android:id="@+id/error_msg_pane"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:layout_alignParentLeft="true"
- android:layout_alignParentStart="true"
- android:layout_alignParentTop="true"
- android:background="@color/error_pane"
- android:padding="16dp"
- android:visibility="gone">
-
- <ImageView
- android:id="@+id/error_image_view"
- android:layout_width="28dp"
- android:layout_height="28dp"
- android:layout_alignParentEnd="true"
- android:layout_alignParentRight="true"
- android:layout_gravity="right"
- android:scaleType="fitCenter"
- android:src="@drawable/ic_settings_white" />
-
- <TextView
- android:id="@+id/error_msg_txt"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:layout_centerHorizontal="true"
- android:layout_centerVertical="true"
- android:layout_marginLeft="26dp"
- android:layout_marginRight="26dp"
- android:gravity="center_vertical|center_horizontal"
- android:textColor="@color/white"
- android:textSize="14sp" />
- </RelativeLayout>
-
-
- <LinearLayout
- android:id="@+id/newcontact_element"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:layout_below="@+id/error_msg_pane"
- android:background="?android:attr/selectableItemBackground"
- android:orientation="vertical"
- android:visibility="gone">
-
- <include
- layout="@layout/item_contact"
- android:layout_width="match_parent"
- android:layout_height="72dp" />
-
- <View
- android:layout_width="match_parent"
- android:layout_height="1dp"
- android:background="?android:attr/listDivider" />
-
- </LinearLayout>
-
- <FrameLayout
+ <android.support.v7.widget.RecyclerView
+ android:id="@+id/confs_list"
android:layout_width="match_parent"
android:layout_height="match_parent"
- android:layout_below="@+id/newcontact_element">
+ android:layout_marginLeft="0dp"
+ android:layout_marginStart="0dp"
+ android:clipToPadding="false"
+ android:divider="@null"
+ android:elevation="2dp"
+ android:paddingBottom="8dp"
+ tools:listitem="@layout/item_smartlist" />
- <ListView
- android:id="@+id/confs_list"
- android:layout_width="match_parent"
- android:layout_height="match_parent"
- android:layout_marginLeft="0dp"
- android:layout_marginStart="0dp"
- android:clipToPadding="false"
- android:divider="@null"
- android:elevation="2dp"
- android:paddingBottom="8dp"
- tools:listitem="@layout/item_smartlist" />
+ <TextView
+ android:id="@+id/empty_text_view"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_gravity="center"
+ android:gravity="center"
+ android:visibility="gone"
+ tools:text="0 conversations"
+ tools:visibility="visible" />
- <TextView
- android:id="@+id/emptyTextView"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:layout_gravity="center"
- android:gravity="center"
- tools:text="0 conversations" />
-
- <ProgressBar
- android:id="@+id/loading_indicator"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:layout_gravity="center" />
- </FrameLayout>
+ <ProgressBar
+ android:id="@+id/loading_indicator"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_gravity="center" />
<android.support.design.widget.FloatingActionButton
android:id="@+id/newconv_fab"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
- android:layout_alignParentBottom="true"
- android:layout_alignParentEnd="true"
- android:layout_alignParentRight="true"
- android:layout_alignWithParentIfMissing="false"
android:layout_gravity="bottom|end"
android:layout_margin="@dimen/fab_compat_margin"
android:elevation="6dp"
@@ -118,16 +110,21 @@
app:pressedTranslationZ="12dp"
app:rippleColor="@color/color_primary_dark" />
- </RelativeLayout>
+ </FrameLayout>
<View
+ android:id="@+id/separator"
android:layout_width="1dp"
android:layout_height="match_parent"
- android:background="@color/darker_gray" />
+ android:background="@color/darker_gray"
+ android:layout_toRightOf="@+id/smartlist_layout"
+ android:layout_toEndOf="@+id/smartlist_layout"/>
<FrameLayout
android:id="@+id/conversation_container"
android:layout_width="match_parent"
- android:layout_height="match_parent" />
+ android:layout_height="match_parent"
+ android:layout_toRightOf="@+id/separator"
+ android:layout_toEndOf="@+id/separator"/>
-</LinearLayout>
\ No newline at end of file
+</RelativeLayout>
\ No newline at end of file
diff --git a/ring-android/app/src/main/res/layout/frag_smartlist.xml b/ring-android/app/src/main/res/layout/frag_smartlist.xml
index f614f6f..6ba58ab 100644
--- a/ring-android/app/src/main/res/layout/frag_smartlist.xml
+++ b/ring-android/app/src/main/res/layout/frag_smartlist.xml
@@ -18,7 +18,6 @@
along with this program; if not, write to the Free Software
Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
-->
-
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
@@ -39,25 +38,26 @@
android:visibility="gone">
<ImageView
+ android:id="@+id/error_image_view"
android:layout_width="28dp"
android:layout_height="28dp"
- android:id="@+id/error_image_view"
- android:src="@drawable/ic_settings_white"
- android:scaleType="fitCenter"
+ android:layout_alignParentEnd="true"
+ android:layout_alignParentRight="true"
android:layout_gravity="right"
- android:layout_alignParentEnd="true" />
+ android:scaleType="fitCenter"
+ android:src="@drawable/ic_settings_white" />
<TextView
android:id="@+id/error_msg_txt"
android:layout_width="match_parent"
android:layout_height="wrap_content"
- android:textColor="@color/white"
- android:textSize="14sp"
- android:gravity="center_vertical|center_horizontal"
- android:layout_centerVertical="true"
android:layout_centerHorizontal="true"
+ android:layout_centerVertical="true"
android:layout_marginLeft="26dp"
- android:layout_marginRight="26dp" />
+ android:layout_marginRight="26dp"
+ android:gravity="center_vertical|center_horizontal"
+ android:textColor="@color/white"
+ android:textSize="14sp" />
</RelativeLayout>
@@ -65,9 +65,9 @@
android:id="@+id/newcontact_element"
android:layout_width="match_parent"
android:layout_height="wrap_content"
+ android:layout_below="@+id/error_msg_pane"
android:background="?android:attr/selectableItemBackground"
android:orientation="vertical"
- android:layout_below="@+id/error_msg_pane"
android:visibility="gone">
<include
@@ -83,11 +83,12 @@
</LinearLayout>
<FrameLayout
+ android:id="@+id/smartlist_layout"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_below="@+id/newcontact_element">
- <ListView
+ <android.support.v7.widget.RecyclerView
android:id="@+id/confs_list"
android:layout_width="match_parent"
android:layout_height="match_parent"
@@ -100,12 +101,14 @@
tools:listitem="@layout/item_smartlist" />
<TextView
- android:id="@+id/emptyTextView"
+ android:id="@+id/empty_text_view"
android:layout_width="match_parent"
android:layout_height="wrap_content"
- tools:text="0 conversations"
+ android:layout_gravity="center"
android:gravity="center"
- android:layout_gravity="center" />
+ android:visibility="gone"
+ tools:text="0 conversations"
+ tools:visibility="visible" />
<ProgressBar
android:id="@+id/loading_indicator"
diff --git a/ring-android/app/src/main/res/layout/item_smartlist.xml b/ring-android/app/src/main/res/layout/item_smartlist.xml
index d788264..cf48783 100644
--- a/ring-android/app/src/main/res/layout/item_smartlist.xml
+++ b/ring-android/app/src/main/res/layout/item_smartlist.xml
@@ -19,11 +19,14 @@
-->
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/call_entry"
android:layout_width="match_parent"
android:layout_height="72dp"
android:descendantFocusability="blocksDescendants"
- android:padding="16dp">
+ android:paddingLeft="16dp"
+ android:paddingRight="16dp"
+ android:background="?android:attr/selectableItemBackground">
<ImageView
android:id="@+id/photo"
@@ -35,7 +38,7 @@
android:layout_marginEnd="16dp"
android:layout_marginRight="16dp"
android:background="@null"
- android:scaleType="centerCrop"
+ android:scaleType="fitCenter"
android:src="@drawable/ic_contact_picture" />
<TextView
@@ -43,7 +46,7 @@
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_alignParentTop="true"
- android:layout_marginTop="2dp"
+ android:layout_marginTop="14dp"
android:layout_toEndOf="@+id/photo"
android:layout_toLeftOf="@+id/conv_last_time"
android:layout_toRightOf="@+id/photo"
@@ -55,7 +58,8 @@
android:singleLine="true"
android:textAlignment="viewStart"
android:textColor="@color/text_color_primary"
- android:textSize="16sp" />
+ android:textSize="16sp"
+ tools:text="Test test"/>
<TextView
android:id="@+id/conv_last_item"
@@ -70,7 +74,8 @@
android:maxLines="2"
android:textAlignment="viewStart"
android:textColor="@color/text_color_secondary"
- android:textSize="14sp" />
+ android:textSize="14sp"
+ tools:text="Ongoing call of 56 secs"/>
<TextView
android:id="@+id/conv_last_time"
@@ -80,6 +85,7 @@
android:layout_alignParentEnd="true"
android:layout_alignParentRight="true"
android:textColor="@color/text_color_secondary"
- android:textSize="12sp" />
+ android:textSize="12sp"
+ tools:text="2days ago"/>
</RelativeLayout>
\ No newline at end of file
diff --git a/ring-android/libringclient/src/main/java/cx/ring/facades/ConversationFacade.java b/ring-android/libringclient/src/main/java/cx/ring/facades/ConversationFacade.java
index d2205a5..3fea2dc 100644
--- a/ring-android/libringclient/src/main/java/cx/ring/facades/ConversationFacade.java
+++ b/ring-android/libringclient/src/main/java/cx/ring/facades/ConversationFacade.java
@@ -19,7 +19,6 @@
*/
package cx.ring.facades;
-import java.rmi.RemoteException;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.Collections;
@@ -522,6 +521,7 @@
notifyObservers(mEvent);
break;
case HISTORY_LOADED:
+
List<HistoryCall> historyCalls = (List<HistoryCall>) event.getEventInput(ServiceEvent.EventInput.HISTORY_CALLS, ArrayList.class);
parseHistoryCalls(historyCalls);
diff --git a/ring-android/libringclient/src/main/java/cx/ring/model/CallContact.java b/ring-android/libringclient/src/main/java/cx/ring/model/CallContact.java
index 2581d89..7306acb 100644
--- a/ring-android/libringclient/src/main/java/cx/ring/model/CallContact.java
+++ b/ring-android/libringclient/src/main/java/cx/ring/model/CallContact.java
@@ -38,6 +38,7 @@
private boolean isUser;
private WeakReference<byte[]> mContactPhoto = new WeakReference<>(null);
private boolean stared = false;
+ private boolean isFromSystem = false;
public CallContact(long cID) {
this(cID, null, null, UNKNOWN_ID);
@@ -202,6 +203,14 @@
mContactPhoto = new WeakReference<>(externalArray);
}
+ public boolean isFromSystem() {
+ return isFromSystem;
+ }
+
+ public void setFromSystem(boolean fromSystem) {
+ isFromSystem = fromSystem;
+ }
+
/**
* A contact is Unknown when his name == his phone number
*
diff --git a/ring-android/libringclient/src/main/java/cx/ring/model/Conversation.java b/ring-android/libringclient/src/main/java/cx/ring/model/Conversation.java
index 613e8c3..e8ad435 100644
--- a/ring-android/libringclient/src/main/java/cx/ring/model/Conversation.java
+++ b/ring-android/libringclient/src/main/java/cx/ring/model/Conversation.java
@@ -48,12 +48,12 @@
// runtime flag set to true if the user is currently viewing this conversation
private boolean mVisible = false;
- private int uuid;
+ private String uuid;
- public Conversation(CallContact c) {
- setContact(c);
+ public Conversation(CallContact contact) {
+ setContact(contact);
+ this.uuid = contact.getIds().get(0);
mCurrentCalls = new ArrayList<>();
- setUuid(new Random().nextInt());
}
public class ConversationElement {
@@ -138,11 +138,11 @@
this.mVisible = mVisible;
}
- public int getUuid() {
+ public String getUuid() {
return uuid;
}
- public void setUuid(int uuid) {
+ public void setUuid(String uuid) {
this.uuid = uuid;
}
diff --git a/ring-android/libringclient/src/main/java/cx/ring/services/ContactService.java b/ring-android/libringclient/src/main/java/cx/ring/services/ContactService.java
index 3d63be7..899fdf1 100644
--- a/ring-android/libringclient/src/main/java/cx/ring/services/ContactService.java
+++ b/ring-android/libringclient/src/main/java/cx/ring/services/ContactService.java
@@ -38,6 +38,7 @@
import cx.ring.utils.FutureUtils;
import cx.ring.utils.Log;
import cx.ring.utils.Observable;
+import cx.ring.utils.Tuple;
/**
* This service handles the contacts
@@ -52,6 +53,9 @@
*/
public abstract class ContactService extends Observable {
+ public static final String CONTACT_NAME_KEY = "CONTACT_NAME";
+ public static final String CONTACT_PHOTO_KEY = "CONTACT_PHOTO";
+
private final static String TAG = ContactService.class.getName();
@Inject
@@ -79,6 +83,10 @@
protected abstract CallContact findContactByNumberFromSystem(String number);
+ public abstract Tuple<String, String> loadContactDataFromSystem(CallContact callContact);
+
+ public abstract Tuple<String, byte[]> loadContactData(CallContact callContact);
+
public ContactService() {
mContactList = new HashMap<>();
mCallbackHandler = new ConfigurationCallbackHandler();
diff --git a/ring-android/libringclient/src/main/java/cx/ring/smartlist/SmartListPresenter.java b/ring-android/libringclient/src/main/java/cx/ring/smartlist/SmartListPresenter.java
index 4cfbee8..0200bd8 100644
--- a/ring-android/libringclient/src/main/java/cx/ring/smartlist/SmartListPresenter.java
+++ b/ring-android/libringclient/src/main/java/cx/ring/smartlist/SmartListPresenter.java
@@ -20,6 +20,9 @@
package cx.ring.smartlist;
import java.lang.ref.WeakReference;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Comparator;
import java.util.List;
import javax.inject.Inject;
@@ -39,6 +42,7 @@
import cx.ring.utils.BlockchainInputHandler;
import cx.ring.utils.Observable;
import cx.ring.utils.Observer;
+import cx.ring.utils.Tuple;
public class SmartListPresenter extends RootPresenter<SmartListView> implements Observer<ServiceEvent> {
@@ -55,6 +59,9 @@
private BlockchainInputHandler mBlockchainInputHandler;
private String mLastBlockchainQuery = null;
+ private ArrayList<Conversation> mConversations;
+ private ArrayList<SmartListViewModel> mSmartListViewModels;
+
@Inject
public SmartListPresenter(AccountService accountService, ContactService contactService,
HistoryService historyService, ConversationFacade conversationFacade,
@@ -89,7 +96,8 @@
boolean isConnected = isConnectedWifi
|| (isConnectedMobile && mSettingsService.getUserSettings().isAllowMobileData());
- boolean isMobileAndNotAllowed = isConnectedMobile && !mSettingsService.getUserSettings().isAllowMobileData();
+ boolean isMobileAndNotAllowed = isConnectedMobile
+ && !mSettingsService.getUserSettings().isAllowMobileData();
if (isConnected) {
getView().hideErrorPanel();
@@ -103,6 +111,7 @@
mConversationFacade.refreshConversations();
searchForRingIdInBlockchain();
+ getView().hideSearchRow();
}
public void queryTextChanged(String query) {
@@ -141,7 +150,8 @@
}
}
- getView().updateViewWithQuery(mConversationFacade.getConversationsList(), query);
+ getView().updateView(filter(mSmartListViewModels, query));
+ getView().setLoading(false);
}
public void newContactClicked(CallContact callContact) {
@@ -151,11 +161,25 @@
startConversation(callContact);
}
- public void conversationClicked(CallContact callContact) {
- if (callContact == null) {
- return;
+ public void conversationClicked(SmartListViewModel smartListViewModel) {
+ Conversation conversation = getConversationByUuid(mConversations, smartListViewModel.getUuid());
+ if (conversation != null && conversation.getContact() != null) {
+ startConversation(conversation.getContact());
}
- startConversation(callContact);
+ }
+
+ public void conversationLongClicked(SmartListViewModel smartListViewModel) {
+ Conversation conversation = getConversationByUuid(mConversations, smartListViewModel.getUuid());
+ if (conversation != null) {
+ getView().displayConversationDialog(conversation);
+ }
+ }
+
+ public void photoClicked(SmartListViewModel smartListViewModel) {
+ Conversation conversation = getConversationByUuid(mConversations, smartListViewModel.getUuid());
+ if (conversation != null && conversation.getContact() != null) {
+ getView().goToContact(conversation.getContact());
+ }
}
public void quickCallClicked(CallContact callContact) {
@@ -211,28 +235,111 @@
}
}
+ private synchronized void displayConversations() {
+ if (mConversations == null) {
+ mConversations = new ArrayList<>();
+ }
+ mConversations.clear();
+ mConversations.addAll(mConversationFacade.getConversationsList());
+ if (mConversations != null && mConversations.size() > 0) {
+ if (mSmartListViewModels == null) {
+ mSmartListViewModels = new ArrayList<>();
+ }
+ for (int i = 0; i < mConversations.size(); i++) {
+ Conversation conversation = mConversations.get(i);
+ SmartListViewModel smartListViewModel = getSmartListViewModelByUuid(mSmartListViewModels, conversation.getUuid());
+
+ if (smartListViewModel == null || i >= mSmartListViewModels.size()) {
+
+ if(conversation.getContact().isFromSystem()) {
+ Tuple<String, String> tuple = mContactService.loadContactDataFromSystem(conversation.getContact());
+ smartListViewModel = new SmartListViewModel(conversation, tuple.first, tuple.second, null);
+ } else {
+ Tuple<String, byte[]> tuple = mContactService.loadContactData(conversation.getContact());
+ smartListViewModel = new SmartListViewModel(conversation, tuple.first, null, tuple.second);
+ }
+
+ mSmartListViewModels.add(smartListViewModel);
+ } else {
+ if(conversation.getContact().isFromSystem()) {
+ Tuple<String, String> tuple = mContactService.loadContactDataFromSystem(conversation.getContact());
+ smartListViewModel.update(conversation, tuple.first, tuple.second, null);
+ } else {
+ Tuple<String, byte[]> tuple = mContactService.loadContactData(conversation.getContact());
+ smartListViewModel.update(conversation, tuple.first, null, tuple.second);
+ }
+ }
+ }
+
+ Collections.sort(mSmartListViewModels, new Comparator<SmartListViewModel>() {
+ @Override
+ public int compare(SmartListViewModel lhs, SmartListViewModel rhs) {
+ return (int) ((rhs.getLastInteractionTime() - lhs.getLastInteractionTime()) / 1000l);
+ }
+ });
+
+ getView().updateView(mSmartListViewModels);
+ getView().hideNoConversationMessage();
+ getView().setLoading(false);
+ } else {
+ getView().displayNoConversationMessage();
+ getView().setLoading(false);
+ }
+ }
+
+ private ArrayList<SmartListViewModel> filter(ArrayList<SmartListViewModel> list, String query) {
+ ArrayList<SmartListViewModel> filteredList = new ArrayList<>();
+ if(list == null || list.size() == 0) {
+ return filteredList;
+ }
+ for(SmartListViewModel smartListViewModel : list) {
+ if(smartListViewModel.getContactName().toLowerCase().contains(query.toLowerCase())) {
+ filteredList.add(smartListViewModel);
+ }
+ }
+ return filteredList;
+ }
+
+ private Conversation getConversationByUuid(ArrayList<Conversation> conversations, String uuid) {
+ for (Conversation conversation : conversations) {
+ if (conversation.getUuid().equals(uuid)) {
+ return conversation;
+ }
+ }
+ return null;
+ }
+
+ private SmartListViewModel getSmartListViewModelByUuid(ArrayList<SmartListViewModel> smartListViewModels, String uuid) {
+ for (SmartListViewModel smartListViewModel : smartListViewModels) {
+ if (smartListViewModel.getUuid().equals(uuid)) {
+ return smartListViewModel;
+ }
+ }
+ return null;
+ }
+
private void parseEventState(String name, String address, int state) {
switch (state) {
case 0:
// on found
- if (mLastBlockchainQuery.equals(name)) {
+ if (mLastBlockchainQuery != null && mLastBlockchainQuery.equals(name)) {
getView().displayNewContactRowWithName(name, address);
mLastBlockchainQuery = null;
} else {
+ if (name.equals("") || address.equals("")) {
+ return;
+ }
getView().hideSearchRow();
+ mConversationFacade.updateConversationContactWithRingId(name, address);
+ displayConversations();
}
-
- if (name.equals("") || address.equals("")) {
- return;
- }
-
- mConversationFacade.updateConversationContactWithRingId(name, address);
- getView().updateView(mConversationFacade.getConversationsList());
break;
case 1:
// invalid name
Uri uriName = new Uri(name);
- if (uriName.isRingId()) {
+ if (uriName.isRingId()
+ && mLastBlockchainQuery != null
+ && mLastBlockchainQuery.equals(name)) {
getView().displayNewContactRowWithName(name, null);
} else {
getView().hideSearchRow();
@@ -241,8 +348,10 @@
default:
// on error
Uri uriAddress = new Uri(address);
- if (uriAddress.isRingId()) {
- getView().displayNewContactRowWithName(name, address);
+ if (uriAddress.isRingId()
+ && mLastBlockchainQuery != null
+ && mLastBlockchainQuery.equals(name)) {
+ getView().displayNewContactRowWithName(address, null);
} else {
getView().hideSearchRow();
}
@@ -260,7 +369,8 @@
switch (event.getEventType()) {
case REGISTERED_NAME_FOUND:
String name = event.getEventInput(ServiceEvent.EventInput.NAME, String.class);
- if (mLastBlockchainQuery.equals("") || !mLastBlockchainQuery.equals(name)) {
+ if (mLastBlockchainQuery != null
+ && (mLastBlockchainQuery.equals("") || !mLastBlockchainQuery.equals(name))) {
return;
}
String address = event.getEventInput(ServiceEvent.EventInput.ADDRESS, String.class);
@@ -269,10 +379,8 @@
break;
case INCOMING_MESSAGE:
case HISTORY_LOADED:
- getView().updateView(mConversationFacade.getConversationsList());
- break;
case CONVERSATIONS_CHANGED:
- getView().updateView(mConversationFacade.getConversationsList());
+ displayConversations();
break;
}
}
diff --git a/ring-android/libringclient/src/main/java/cx/ring/smartlist/SmartListView.java b/ring-android/libringclient/src/main/java/cx/ring/smartlist/SmartListView.java
index 7bc9d0f..67db8f8 100644
--- a/ring-android/libringclient/src/main/java/cx/ring/smartlist/SmartListView.java
+++ b/ring-android/libringclient/src/main/java/cx/ring/smartlist/SmartListView.java
@@ -19,6 +19,7 @@
*/
package cx.ring.smartlist;
+import java.util.ArrayList;
import java.util.Collection;
import cx.ring.model.CallContact;
@@ -34,17 +35,25 @@
void displayChooseNumberDialog(CharSequence numbers[]);
+ void displayNoConversationMessage();
+
+ void displayConversationDialog(Conversation conversation);
+
+ void setLoading(boolean display);
+
void hideSearchRow();
void hideErrorPanel();
- void updateView(final Collection<Conversation> list);
+ void hideNoConversationMessage();
- void updateViewWithQuery(final Collection<Conversation> list, String query);
+ void updateView(ArrayList<SmartListViewModel> list);
void goToConversation(CallContact callContact);
void goToCallActivity(String rawUriNumber);
void goToQRActivity();
+
+ void goToContact(CallContact callContact);
}
diff --git a/ring-android/libringclient/src/main/java/cx/ring/smartlist/SmartListViewModel.java b/ring-android/libringclient/src/main/java/cx/ring/smartlist/SmartListViewModel.java
new file mode 100644
index 0000000..679bdc3
--- /dev/null
+++ b/ring-android/libringclient/src/main/java/cx/ring/smartlist/SmartListViewModel.java
@@ -0,0 +1,103 @@
+/*
+ * Copyright (C) 2017 Savoir-faire Linux Inc.
+ *
+ * Author: Hadrien De Sousa <hadrien.desousa@savoirfairelinux.com>
+ *
+ * This program 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.
+ *
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+package cx.ring.smartlist;
+
+import java.util.Map;
+
+import cx.ring.model.Conversation;
+import cx.ring.model.HistoryEntry;
+import cx.ring.services.ContactService;
+import cx.ring.utils.Tuple;
+
+public class SmartListViewModel {
+
+ private String uuid;
+ private String contactName;
+ private HistoryEntry lastInteraction;
+ private String photoUri;
+ private byte[] photoData;
+ private long lastInteractionTime;
+ private boolean hasUnreadTextMessage;
+ private boolean hasOngoingCall;
+
+ public SmartListViewModel(Conversation conversation, String contactName, String photoUri, byte[] photoData) {
+ this.uuid = conversation.getUuid();
+ setData(conversation, contactName, photoUri, photoData);
+ }
+
+ public void update(Conversation conversation, String contactName, String photoUri, byte[] photoData) {
+ setData(conversation, contactName, photoUri, photoData);
+ }
+
+ private void setData(Conversation conversation, String contactName, String photoUri, byte[] photoData) {
+ this.contactName = contactName;
+ this.photoUri = photoUri;
+ this.photoData = photoData;
+
+ for (HistoryEntry historyEntry : conversation.getHistory().values()) {
+ long lastTextTimestamp = historyEntry.getTextMessages().isEmpty() ? 0 : historyEntry.getTextMessages().lastEntry().getKey();
+ long lastCallTimestamp = historyEntry.getCalls().isEmpty() ? 0 : historyEntry.getCalls().lastEntry().getKey();
+ if (lastTextTimestamp > 0 && lastTextTimestamp > lastCallTimestamp) {
+ this.lastInteraction = historyEntry;
+ break;
+ }
+ if (lastCallTimestamp > 0) {
+ this.lastInteraction = historyEntry;
+ break;
+ }
+ }
+
+ this.lastInteractionTime = conversation.getLastInteraction().getTime();
+ this.hasUnreadTextMessage = conversation.hasUnreadTextMessages();
+ this.hasOngoingCall = conversation.hasCurrentCall();
+ }
+
+ public String getContactName() {
+ return contactName;
+ }
+
+ public HistoryEntry getLastInteraction() {
+ return lastInteraction;
+ }
+
+ public String getPhotoUri() {
+ return photoUri;
+ }
+
+ public long getLastInteractionTime() {
+ return lastInteractionTime;
+ }
+
+ public boolean hasUnreadTextMessage() {
+ return hasUnreadTextMessage;
+ }
+
+ public boolean hasOngoingCall() {
+ return hasOngoingCall;
+ }
+
+ public String getUuid() {
+ return uuid;
+ }
+
+ public byte[] getPhotoData() {
+ return photoData;
+ }
+}