Commit fdf5ded0 authored by Adrien Béraud's avatar Adrien Béraud Committed by Sébastien Blin
Browse files

swarm: various fixes and improvements

Change-Id: Ib36417ad4d68acfbb7d257c76ac739f6f02c4e01
parent b8190512
......@@ -65,6 +65,18 @@ import com.bumptech.glide.load.resource.bitmap.CenterInside;
import com.bumptech.glide.load.resource.bitmap.RoundedCorners;
import com.bumptech.glide.request.target.DrawableImageViewTarget;
import net.jami.conversation.ConversationPresenter;
import net.jami.model.Account;
import net.jami.model.Call;
import net.jami.model.Contact;
import net.jami.model.ContactEvent;
import net.jami.model.DataTransfer;
import net.jami.model.Interaction;
import net.jami.model.Interaction.InteractionStatus;
import net.jami.model.Interaction.InteractionType;
import net.jami.model.TextMessage;
import net.jami.utils.StringUtils;
import java.io.File;
import java.text.DateFormat;
import java.util.ArrayList;
......@@ -75,24 +87,11 @@ import java.util.concurrent.TimeUnit;
import cx.ring.R;
import cx.ring.client.MediaViewerActivity;
import net.jami.conversation.ConversationPresenter;
import cx.ring.fragments.ConversationFragment;
import net.jami.model.Account;
import net.jami.model.Contact;
import net.jami.model.ContactEvent;
import net.jami.model.DataTransfer;
import net.jami.model.Interaction;
import net.jami.model.Interaction.InteractionStatus;
import net.jami.model.Interaction.InteractionType;
import net.jami.model.Call;
import net.jami.model.TextMessage;
import cx.ring.service.DRingService;
import cx.ring.utils.AndroidFileUtils;
import cx.ring.utils.ContentUriHandler;
import cx.ring.utils.GlideApp;
import cx.ring.utils.GlideOptions;
import cx.ring.utils.ResourceMapper;
import net.jami.utils.StringUtils;
import cx.ring.views.ConversationViewHolder;
import io.reactivex.Observable;
import io.reactivex.android.schedulers.AndroidSchedulers;
......@@ -178,6 +177,7 @@ public class ConversationAdapter extends RecyclerView.Adapter<ConversationViewHo
}
for (int i = 0, n = mInteractions.size(); i<n; i++) {
if (mInteractions.get(i).getParentIds().contains(e.getMessageId())) {
Log.w(TAG, "Adding message at " + i + " previous count " + n);
mInteractions.add(i, e);
notifyItemInserted(i);
return i == n-1;
......@@ -194,6 +194,7 @@ public class ConversationAdapter extends RecyclerView.Adapter<ConversationViewHo
}
public void update(Interaction e) {
Log.w(TAG, "update " + e.getMessageId());
if (!e.isIncoming() && e.getStatus() == InteractionStatus.SUCCESS) {
notifyItemChanged(lastDeliveredPosition);
}
......@@ -207,18 +208,33 @@ public class ConversationAdapter extends RecyclerView.Adapter<ConversationViewHo
}
public void remove(Interaction e) {
for (int i = mInteractions.size() - 1; i >= 0; i--) {
Interaction element = mInteractions.get(i);
if (e.getId() == element.getId()) {
mInteractions.remove(i);
notifyItemRemoved(i);
if (i > 0) {
notifyItemChanged(i - 1);
if (e.getMessageId() != null) {
for (int i = mInteractions.size() - 1; i >= 0; i--) {
if (e.getMessageId().equals(mInteractions.get(i).getMessageId())) {
mInteractions.remove(i);
notifyItemRemoved(i);
if (i > 0) {
notifyItemChanged(i - 1);
}
if (i != mInteractions.size()) {
notifyItemChanged(i);
}
break;
}
if (i != mInteractions.size()) {
notifyItemChanged(i);
}
} else {
for (int i = mInteractions.size() - 1; i >= 0; i--) {
if (e.getId() == mInteractions.get(i).getId()) {
mInteractions.remove(i);
notifyItemRemoved(i);
if (i > 0) {
notifyItemChanged(i - 1);
}
if (i != mInteractions.size()) {
notifyItemChanged(i);
}
break;
}
break;
}
}
}
......
......@@ -106,7 +106,7 @@ public class SmartListAdapter extends RecyclerView.Adapter<SmartListViewHolder>
public void update(SmartListViewModel smartListViewModel) {
for (int i = 0; i < mSmartListViewModels.size(); i++) {
SmartListViewModel old = mSmartListViewModels.get(i);
if (old.getContact() == smartListViewModel.getContact()) {
if (old.getContacts() == smartListViewModel.getContacts()) {
mSmartListViewModels.set(i, smartListViewModel);
notifyItemChanged(i);
return;
......
......@@ -51,11 +51,11 @@ public class SmartListDiffUtil extends DiffUtil.Callback {
SmartListViewModel newItem = mNewList.get(newItemPosition);
if (newItem.getHeaderTitle() != oldItem.getHeaderTitle())
return false;
if (newItem.getContact() != oldItem.getContact()) {
if (newItem.getContact().size() != oldItem.getContact().size())
if (newItem.getContacts() != oldItem.getContacts()) {
if (newItem.getContacts().size() != oldItem.getContacts().size())
return false;
for (int i = 0; i < newItem.getContact().size(); i++) {
if (newItem.getContact().get(i) != oldItem.getContact().get(i))
for (int i = 0; i < newItem.getContacts().size(); i++) {
if (newItem.getContacts().get(i) != oldItem.getContacts().get(i))
return false;
}
}
......
......@@ -26,48 +26,53 @@ import android.content.Intent;
import android.content.SharedPreferences;
import android.content.res.ColorStateList;
import android.graphics.Color;
import android.graphics.drawable.Drawable;
import android.os.Bundle;
import com.google.android.material.appbar.CollapsingToolbarLayout;
import com.google.android.material.dialog.MaterialAlertDialogBuilder;
import com.google.android.material.floatingactionbutton.FloatingActionButton;
import com.google.android.material.snackbar.Snackbar;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.Toast;
import androidx.annotation.DrawableRes;
import androidx.annotation.NonNull;
import androidx.annotation.StringRes;
import androidx.appcompat.app.AppCompatActivity;
import androidx.core.view.ViewCompat;
import androidx.recyclerview.widget.RecyclerView;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.ViewGroup;
import android.widget.Toast;
import com.google.android.material.appbar.CollapsingToolbarLayout;
import com.google.android.material.dialog.MaterialAlertDialogBuilder;
import com.google.android.material.floatingactionbutton.FloatingActionButton;
import com.google.android.material.snackbar.Snackbar;
import net.jami.facades.ConversationFacade;
import net.jami.model.Call;
import net.jami.model.Conference;
import net.jami.model.Contact;
import net.jami.model.Conversation;
import net.jami.model.Uri;
import net.jami.services.AccountService;
import net.jami.services.NotificationService;
import java.util.ArrayList;
import java.util.List;
import javax.inject.Inject;
import javax.inject.Singleton;
import androidx.core.view.ViewCompat;
import androidx.recyclerview.widget.RecyclerView;
import cx.ring.R;
import cx.ring.application.JamiApplication;
import cx.ring.views.AvatarFactory;
import cx.ring.databinding.ActivityContactDetailsBinding;
import cx.ring.databinding.ItemContactActionBinding;
import net.jami.facades.ConversationFacade;
import cx.ring.databinding.ItemContactHorizontalBinding;
import cx.ring.fragments.CallFragment;
import cx.ring.fragments.ConversationFragment;
import net.jami.model.Contact;
import net.jami.model.Conference;
import net.jami.model.Conversation;
import net.jami.model.Call;
import net.jami.model.Uri;
import net.jami.services.AccountService;
import net.jami.services.NotificationService;
import cx.ring.services.SharedPreferencesServiceImpl;
import cx.ring.utils.ConversationPath;
import cx.ring.views.AvatarDrawable;
import io.reactivex.Single;
import io.reactivex.android.schedulers.AndroidSchedulers;
import io.reactivex.disposables.CompositeDisposable;
......@@ -93,6 +98,8 @@ public class ContactDetailsActivity extends AppCompatActivity {
static class ContactAction {
@DrawableRes
final int icon;
final Single<Drawable> drawable;
final CharSequence title;
final IContactAction callback;
......@@ -104,6 +111,7 @@ public class ContactDetailsActivity extends AppCompatActivity {
iconTint = tint;
title = t;
callback = cb;
drawable = null;
}
ContactAction(@DrawableRes int i, CharSequence t, IContactAction cb) {
......@@ -111,6 +119,14 @@ public class ContactDetailsActivity extends AppCompatActivity {
iconTint = Color.BLACK;
title = t;
callback = cb;
drawable = null;
}
ContactAction(Single<Drawable> d, CharSequence t, IContactAction cb) {
drawable = d;
icon = 0;
iconTint = Color.BLACK;
title = t;
callback = cb;
}
void setIconTint(int tint) {
......@@ -125,10 +141,12 @@ public class ContactDetailsActivity extends AppCompatActivity {
static class ContactActionView extends RecyclerView.ViewHolder {
final ItemContactActionBinding binding;
IContactAction callback;
final CompositeDisposable disposable = new CompositeDisposable();
ContactActionView(@NonNull ItemContactActionBinding b) {
ContactActionView(@NonNull ItemContactActionBinding b, CompositeDisposable parentDisposable) {
super(b.getRoot());
binding = b;
parentDisposable.add(disposable);
itemView.setOnClickListener(view -> {
try {
if (callback != null)
......@@ -142,34 +160,112 @@ public class ContactDetailsActivity extends AppCompatActivity {
private static class ContactActionAdapter extends RecyclerView.Adapter<ContactActionView> {
private final ArrayList<ContactAction> actions = new ArrayList<>();
private final CompositeDisposable disposable;
private ContactActionAdapter(CompositeDisposable disposable) {
this.disposable = disposable;
}
@NonNull
@Override
public ContactActionView onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
LayoutInflater layoutInflater = LayoutInflater.from(parent.getContext());
ItemContactActionBinding itemBinding = ItemContactActionBinding.inflate(layoutInflater, parent, false);
return new ContactActionView(itemBinding);
return new ContactActionView(itemBinding, disposable);
}
@Override
public void onBindViewHolder(@NonNull ContactActionView holder, int position) {
ContactAction action = actions.get(position);
holder.binding.actionIcon.setBackgroundResource(action.icon);
holder.binding.actionIcon.setText(action.iconSymbol);
if (action.iconTint != Color.BLACK)
ViewCompat.setBackgroundTintList(holder.binding.actionIcon, ColorStateList.valueOf(action.iconTint));
holder.disposable.clear();
if (action.drawable != null) {
holder.disposable.add(action.drawable.subscribe(holder.binding.actionIcon::setBackground));
} else {
holder.binding.actionIcon.setBackgroundResource(action.icon);
holder.binding.actionIcon.setText(action.iconSymbol);
if (action.iconTint != Color.BLACK)
ViewCompat.setBackgroundTintList(holder.binding.actionIcon, ColorStateList.valueOf(action.iconTint));
}
holder.binding.actionTitle.setText(action.title);
holder.callback = action.callback;
}
@Override
public void onViewRecycled(@NonNull ContactActionView holder) {
holder.disposable.clear();
holder.binding.actionIcon.setBackground(null);
}
@Override
public int getItemCount() {
return actions.size();
}
}
private final ContactActionAdapter adapter = new ContactActionAdapter();
static class ContactView extends RecyclerView.ViewHolder {
final ItemContactHorizontalBinding binding;
IContactAction callback;
final CompositeDisposable disposable = new CompositeDisposable();
ContactView(@NonNull ItemContactHorizontalBinding b, CompositeDisposable parentDisposable) {
super(b.getRoot());
binding = b;
parentDisposable.add(disposable);
itemView.setOnClickListener(view -> {
try {
if (callback != null)
callback.onAction();
} catch (Exception e) {
Log.w(TAG, "Error performing action", e);
}
});
}
}
private static class ContactViewAdapter extends RecyclerView.Adapter<ContactView> {
private final List<Contact> contacts;
private final CompositeDisposable disposable;
interface ContactCallback {
void onContactClicked(Contact contact);
}
private final ContactCallback callback;
private ContactViewAdapter(CompositeDisposable disposable, List<Contact> contacts, ContactCallback cb) {
this.disposable = disposable;
this.contacts = contacts;
this.callback = cb;
}
@NonNull
@Override
public ContactView onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
LayoutInflater layoutInflater = LayoutInflater.from(parent.getContext());
ItemContactHorizontalBinding itemBinding = ItemContactHorizontalBinding.inflate(layoutInflater, parent, false);
return new ContactView(itemBinding, disposable);
}
@Override
public void onBindViewHolder(@NonNull ContactView holder, int position) {
Contact contact = contacts.get(position);
holder.disposable.clear();
holder.disposable.add(AvatarFactory.getAvatar(holder.itemView.getContext(), contact, false).subscribe(holder.binding.photo::setImageDrawable));
holder.binding.displayName.setText(contact.isUser() ? holder.itemView.getContext().getText(R.string.conversation_info_contact_you) : contact.getDisplayName());
holder.itemView.setOnClickListener(v -> callback.onContactClicked(contact));
}
@Override
public void onViewRecycled(@NonNull ContactView holder) {
holder.disposable.clear();
holder.binding.photo.setImageDrawable(null);
}
@Override
public int getItemCount() {
return contacts.size();
}
}
private final CompositeDisposable mDisposableBag = new CompositeDisposable();
private final ContactActionAdapter adapter = new ContactActionAdapter(mDisposableBag);
private ContactAction colorAction;
private ContactAction symbolAction;
......@@ -194,7 +290,7 @@ public class ContactDetailsActivity extends AppCompatActivity {
setSupportActionBar(findViewById(R.id.toolbar));
FloatingActionButton fab = findViewById(R.id.fab);
FloatingActionButton fab = findViewById(R.id.sendMessage);
fab.setOnClickListener(view -> goToConversationActivity(mConversation.getAccountId(), mConversation.getUri()));
colorActionPosition = 1;
......@@ -205,7 +301,7 @@ public class ContactDetailsActivity extends AppCompatActivity {
.observeOn(AndroidSchedulers.mainThread())
.subscribe(conversation -> {
mConversation = conversation;
mPreferences = getSharedPreferences(conversation.getAccountId() + "_" + conversation.getUri().getUri(), Context.MODE_PRIVATE);
mPreferences = SharedPreferencesServiceImpl.getConversationPreferences(this, conversation.getAccountId(), conversation.getUri());
binding.contactImage.setImageDrawable(
new AvatarDrawable.Builder()
.withConversation(conversation)
......@@ -221,10 +317,11 @@ public class ContactDetailsActivity extends AppCompatActivity {
@StringRes int infoString = conversation.isSwarm()
? (conversation.getMode() == Conversation.Mode.OneToOne
? R.string.conversation_type_private
: R.string.conversation_type_group)
? R.string.conversation_type_private
: R.string.conversation_type_group)
: R.string.conversation_type_contact;
adapter.actions.add(new ContactAction(R.drawable.baseline_info_24, getText(infoString), () -> {}));
adapter.actions.add(new ContactAction(R.drawable.baseline_info_24, getText(infoString), () -> {
}));
colorAction = new ContactAction(R.drawable.item_color_background, 0, getText(R.string.conversation_preference_color), () -> {
ColorChooserBottomSheet frag = new ColorChooserBottomSheet();
......@@ -288,7 +385,8 @@ public class ContactDetailsActivity extends AppCompatActivity {
.create()
.show()));
}
contactAction = new ContactAction(R.drawable.baseline_person_24, conversation.getUriTitle(), () -> {
String conversationUri = conversation.isSwarm() ? conversation.getUri().toString() : conversation.getUriTitle();
contactAction = new ContactAction(conversation.isSwarm() ? R.drawable.baseline_group_24 : R.drawable.baseline_person_24, conversationUri, () -> {
ClipboardManager clipboard = (ClipboardManager) getSystemService(Context.CLIPBOARD_SERVICE);
if (clipboard != null) {
clipboard.setPrimaryClip(ClipData.newPlainText(getText(R.string.clip_contact_uri), path.getConversationId()));
......@@ -297,6 +395,18 @@ public class ContactDetailsActivity extends AppCompatActivity {
});
adapter.actions.add(contactAction);
binding.contactActionList.setAdapter(adapter);
binding.contactList.setVisibility(conversation.isSwarm() ? View.VISIBLE : View.GONE);
if (conversation.isSwarm()) {
binding.contactList.setAdapter(new ContactViewAdapter(mDisposableBag, conversation.getContacts(), contact -> {
ClipboardManager clipboard = (ClipboardManager) getSystemService(Context.CLIPBOARD_SERVICE);
if (clipboard != null) {
String toCopy = contact.getUri().getRawUriString();
clipboard.setPrimaryClip(ClipData.newPlainText(getText(R.string.clip_contact_uri), toCopy));
Snackbar.make(binding.getRoot(), getString(R.string.conversation_action_copied_peer_number_clipboard, toCopy), Snackbar.LENGTH_LONG).show();
}
}));
}
}));
}
......
......@@ -24,14 +24,11 @@ import android.content.Intent;
import android.os.Bundle;
import android.view.KeyEvent;
import android.view.Menu;
import android.view.View;
import androidx.annotation.ColorInt;
import androidx.annotation.NonNull;
import androidx.appcompat.app.ActionBar;
import androidx.appcompat.app.AppCompatActivity;
import androidx.coordinatorlayout.widget.CoordinatorLayout;
import androidx.core.view.ViewCompat;
import cx.ring.R;
import cx.ring.application.JamiApplication;
......@@ -85,7 +82,7 @@ public class ConversationActivity extends AppCompatActivity implements Colorable
mConversationFragment.setArguments(bundle);
getSupportFragmentManager().beginTransaction()
.replace(R.id.main_frame, mConversationFragment, null)
.commit();
.commitNow();
}
if (Intent.ACTION_SEND.equals(action) || Intent.ACTION_SEND_MULTIPLE.equals(action) || Intent.ACTION_VIEW.equals(action)) {
mPendingIntent = intent;
......
......@@ -117,11 +117,11 @@ public class ConversationSelectionActivity extends AppCompatActivity {
return vm;
List<SmartListViewModel> filteredVms = new ArrayList<>(vm.size());
models: for (SmartListViewModel v : vm) {
List<Contact> contacts = v.getContact();
List<Contact> contacts = v.getContacts();
if (contacts.size() != 1)
continue;
for (Call call : conf.getParticipants()) {
if (call.getContact() == v.getContact().get(0)) {
if (call.getContact() == v.getContacts().get(0)) {
continue models;
}
}
......
......@@ -92,6 +92,7 @@ import net.jami.daemon.JamiService;
import net.jami.model.Call;
import net.jami.model.Conference;
import net.jami.model.Contact;
import net.jami.model.Uri;
import net.jami.services.DeviceRuntimeService;
import net.jami.services.HardwareService;
import net.jami.services.NotificationService;
......@@ -1237,7 +1238,7 @@ public class CallFragment extends BaseSupportFragment<CallPresenter> implements
}
@Override
public void goToConversation(String accountId, String conversationId) {
public void goToConversation(String accountId, Uri conversationId) {
Context context = requireContext();
if (DeviceUtils.isTablet(context)) {
startActivity(new Intent(DRingService.ACTION_CONV_ACCEPT, ConversationPath.toUri(accountId, conversationId), context, HomeActivity.class));
......
......@@ -107,7 +107,7 @@ public class ContactPickerFragment extends BottomSheetDialogFragment {
adapter.update(item);
Runnable remover = () -> {
mCurrentSelection.remove(item.getContact().get(0));
mCurrentSelection.remove(item.getContacts().get(0));
if (mCurrentSelection.isEmpty())
binding.createGroupBtn.setEnabled(false);
item.setChecked(false);
......@@ -118,7 +118,7 @@ public class ContactPickerFragment extends BottomSheetDialogFragment {
};
if (checked) {
if (mCurrentSelection.add(item.getContact().get(0))) {
if (mCurrentSelection.add(item.getContacts().get(0))) {
Chip chip = new Chip(requireContext(), null, R.style.Widget_MaterialComponents_Chip_Entry);
chip.setText(item.getContactName());
chip.setChipIcon(new AvatarDrawable.Builder()
......
......@@ -102,6 +102,7 @@ import cx.ring.service.DRingService;
import cx.ring.services.LocationSharingService;
import net.jami.services.NotificationService;
import cx.ring.services.NotificationServiceImpl;
import cx.ring.services.SharedPreferencesServiceImpl;
import cx.ring.utils.ActionHelper;
import cx.ring.utils.AndroidFileUtils;
import cx.ring.utils.ContentUriHandler;
......@@ -248,7 +249,7 @@ public class ConversationFragment extends BaseSupportFragment<ConversationPresen
View layout = binding.conversationLayout;
// remove action bar height for tablet layout
if (DeviceUtils.isTablet(getContext())) {
if (DeviceUtils.isTablet(layout.getContext())) {
layout.setPadding(layout.getPaddingLeft(), 0, layout.getPaddingRight(), layout.getPaddingBottom());
}
......@@ -799,7 +800,7 @@ public class ConversationFragment extends BaseSupportFragment<ConversationPresen
try {
fileUri = ContentUriHandler.getUriForFile(c, ContentUriHandler.AUTHORITY_FILES, path);
} catch (IllegalArgumentException e) {
Log.e("File Selector", "The selected file can't be shared: " + path.getName());
Log.e(TAG, "The selected file can't be shared: " + path.getName());
}
if (fileUri != null) {
Intent sendIntent = new Intent();
......@@ -861,28 +862,28 @@ public class ConversationFragment extends BaseSupportFragment<ConversationPresen
@Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
case android.R.id.home:
startActivity(new Intent(getActivity(), HomeActivity.class));
return true;
case R.id.conv_action_audiocall:
presenter.goToCall(true);
return true;
case R.id.conv_action_videocall:
presenter.goToCall(false);
return true;
case R.id.conv_contact_details:
presenter.openContact();
return true;
default:
return super.onOptionsItemSelected(item);
int itemId = item.getItemId();
if (itemId == android.R.id.home) {
startActivity(new Intent(getActivity(), HomeActivity.class));
return true;
} else if (itemId == R.id.conv_action_audiocall) {
presenter.goToCall(true);
return true;
} else if (itemId == R.id.conv_action_videocall) {
presenter.goToCall(false);
return true;
} else if (itemId == R.id.conv_contact_details) {
presenter.openContact();
return true;
}
return super.onOptionsItemSelected(item);
}
@Override
protected void initPresenter(ConversationPresenter presenter) {
ConversationPath path = ConversationPath.fromBundle(getArguments());
mIsBubble = getArguments().getBoolean(NotificationServiceImpl.EXTRA_BUBBLE);