Commit bc1ec903 authored by Adrien Béraud's avatar Adrien Béraud

call: use participant info for conference layout

Change-Id: Iebbd60510d2b3812476a2456d6cef24995fff873
parent 08cb9706
/*
* Copyright (C) 2004-2021 Savoir-faire Linux Inc.
*
* Author: Adrien Béraud <adrien.beraud@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.adapters;
import android.content.Context;
......@@ -11,16 +30,18 @@ import androidx.recyclerview.widget.RecyclerView;
import java.util.List;
import cx.ring.fragments.CallFragment;
import cx.ring.views.AvatarFactory;
import cx.ring.databinding.ItemConferenceParticipantBinding;
import cx.ring.fragments.CallFragment;
import net.jami.model.Conference;
import net.jami.model.Contact;
import net.jami.model.Call;
import cx.ring.views.ParticipantView;
public class ConfParticipantAdapter extends RecyclerView.Adapter<ParticipantView> {
protected final ConfParticipantAdapter.ConfParticipantSelected onSelectedCallback;
private List<Call> calls = null;
private List<Conference.ParticipantInfo> calls = null;
public ConfParticipantAdapter(@NonNull ConfParticipantSelected cb) {
onSelectedCallback = cb;
......@@ -34,22 +55,23 @@ public class ConfParticipantAdapter extends RecyclerView.Adapter<ParticipantView
@Override
public void onBindViewHolder(@NonNull ParticipantView holder, int position) {
final Call call = calls.get(position);
final Contact contact = call.getContact();
final Conference.ParticipantInfo info = calls.get(position);
final Contact contact = info.contact;
final Context context = holder.itemView.getContext();
Call.CallStatus status = call.getCallStatus();
if (status == Call.CallStatus.CURRENT) {
if (info.call != null && info.call.getCallStatus() != Call.CallStatus.CURRENT) {
holder.binding.displayName.setText(String.format("%s\n%s", contact.getDisplayName(), context.getText(CallFragment.callStateToHumanState(info.call.getCallStatus()))));
holder.binding.photo.setAlpha(.5f);
} else {
holder.binding.displayName.setText(contact.getDisplayName());
holder.binding.photo.setAlpha(1f);
} else {
holder.binding.displayName.setText(String.format("%s\n%s", contact.getDisplayName(), context.getText(CallFragment.callStateToHumanState(status))));
holder.binding.photo.setAlpha(.5f);
}
if (holder.disposable != null)
holder.disposable.dispose();
holder.disposable = AvatarFactory.getAvatar(context, contact)
.subscribe(holder.binding.photo::setImageDrawable);
holder.itemView.setOnClickListener(view -> onSelectedCallback.onParticipantSelected(view, call));
holder.itemView.setOnClickListener(view -> onSelectedCallback.onParticipantSelected(view, info));
}
@Override
......@@ -57,8 +79,8 @@ public class ConfParticipantAdapter extends RecyclerView.Adapter<ParticipantView
return calls == null ? 0 : calls.size();
}
public void updateFromCalls(@NonNull final List<Call> contacts) {
final List<Call> oldCalls = calls;
public void updateFromCalls(@NonNull final List<Conference.ParticipantInfo> contacts) {
final List<Conference.ParticipantInfo> oldCalls = calls;
calls = contacts;
if (oldCalls != null) {
DiffUtil.calculateDiff(new DiffUtil.Callback() {
......@@ -88,6 +110,6 @@ public class ConfParticipantAdapter extends RecyclerView.Adapter<ParticipantView
}
public interface ConfParticipantSelected {
void onParticipantSelected(View view, Call contact);
void onParticipantSelected(View view, Conference.ParticipantInfo contact);
}
}
......@@ -96,7 +96,6 @@ import net.jami.model.Uri;
import net.jami.services.DeviceRuntimeService;
import net.jami.services.HardwareService;
import net.jami.services.NotificationService;
import net.jami.utils.StringUtils;
import java.util.ArrayList;
import java.util.Iterator;
......@@ -138,7 +137,6 @@ public class CallFragment extends BaseSupportFragment<CallPresenter> implements
public static final String ACTION_GET_CALL = "GET_CALL";
public static final String KEY_ACTION = "action";
public static final String KEY_ACCOUNT_ID = "accountId";
public static final String KEY_CONF_ID = "confId";
public static final String KEY_AUDIO_ONLY = "AUDIO_ONLY";
......@@ -344,7 +342,7 @@ public class CallFragment extends BaseSupportFragment<CallPresenter> implements
return binding.getRoot();
}
private TextureView.SurfaceTextureListener listener = new TextureView.SurfaceTextureListener() {
private final TextureView.SurfaceTextureListener listener = new TextureView.SurfaceTextureListener() {
@Override
public void onSurfaceTextureAvailable(SurfaceTexture surface, int width, int height) {
mPreviewSurfaceWidth = width;
......@@ -380,7 +378,7 @@ public class CallFragment extends BaseSupportFragment<CallPresenter> implements
binding.pluginPreviewHandle.setAlpha(hiddenState);
}
@SuppressLint("ClickableViewAccessibility")
@SuppressLint({"ClickableViewAccessibility", "RtlHardcoded", "WakelockTimeout"})
@Override
public void onViewCreated(@NonNull View view, Bundle savedInstanceState) {
setHasOptionsMenu(true);
......@@ -741,6 +739,7 @@ public class CallFragment extends BaseSupportFragment<CallPresenter> implements
*
* @param isAudioOnly true if it is an audio call
*/
@SuppressLint("WakelockTimeout")
@Override
public void handleCallWakelock(boolean isAudioOnly) {
if (isAudioOnly) {
......@@ -841,15 +840,13 @@ public class CallFragment extends BaseSupportFragment<CallPresenter> implements
@Override
public boolean onOptionsItemSelected(@NonNull MenuItem item) {
super.onOptionsItemSelected(item);
switch (item.getItemId()) {
case android.R.id.home:
presenter.chatClick();
break;
case R.id.menuitem_dialpad:
presenter.dialpadClick();
break;
case R.id.menuitem_video_plugins:
displayVideoPluginsCarousel();
int itemId = item.getItemId();
if (itemId == android.R.id.home) {
presenter.chatClick();
} else if (itemId == R.id.menuitem_dialpad) {
presenter.dialpadClick();
} else if (itemId == R.id.menuitem_video_plugins) {
displayVideoPluginsCarousel();
}
return true;
}
......@@ -949,9 +946,8 @@ public class CallFragment extends BaseSupportFragment<CallPresenter> implements
public void updateContactBubble(@NonNull final List<Call> contacts) {
Log.w(TAG, "updateContactBubble " + contacts.size());
mConferenceMode = contacts.size() > 1;
String username = mConferenceMode ? "Conference with " + contacts.size() + " people" : contacts.get(0).getContact().getRingUsername();
String displayName = mConferenceMode ? null : contacts.get(0).getContact().getDisplayName();
String username = contacts.size() > 1 ? "Conference with " + contacts.size() + " people" : contacts.get(0).getContact().getDisplayName();
String displayName = contacts.size() > 1 ? null : contacts.get(0).getContact().getDisplayName();
boolean hasProfileName = displayName != null && !displayName.contentEquals(username);
......@@ -987,15 +983,42 @@ public class CallFragment extends BaseSupportFragment<CallPresenter> implements
.build(getActivity())
);
if (!mConferenceMode) {
}
@Override
public void updateConfInfo(List<Conference.ParticipantInfo> participantInfo) {
Log.w(TAG, "updateConfInfo " + participantInfo);
mConferenceMode = participantInfo.size() > 1;
binding.participantLabelContainer.removeAllViews();
if (!participantInfo.isEmpty()) {
LayoutInflater inflater = LayoutInflater.from(binding.participantLabelContainer.getContext());
for (Conference.ParticipantInfo i : participantInfo) {
String displayName = i.contact.getDisplayName();
if (!TextUtils.isEmpty(displayName)) {
ItemParticipantLabelBinding label = ItemParticipantLabelBinding.inflate(inflater);
PercentFrameLayout.LayoutParams params = new PercentFrameLayout.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT);
params.getPercentLayoutInfo().leftMarginPercent = i.x / (float) mVideoWidth;
params.getPercentLayoutInfo().topMarginPercent = i.y / (float) mVideoHeight;
label.participantName.setText(displayName);
label.moderator.setVisibility(i.isModerator ? View.VISIBLE : View.GONE);
label.mute.setVisibility(i.audioMuted ? View.VISIBLE : View.GONE);
binding.participantLabelContainer.addView(label.getRoot(), params);
}
}
}
binding.participantLabelContainer.setVisibility(participantInfo.isEmpty() ? View.GONE : View.VISIBLE);
if (participantInfo.isEmpty() || participantInfo.size() < 2) {
binding.confControlGroup.setVisibility(View.GONE);
} else {
binding.confControlGroup.setVisibility(View.VISIBLE);
if (confAdapter == null) {
confAdapter = new ConfParticipantAdapter((view, call) -> {
confAdapter = new ConfParticipantAdapter((view, info) -> {
if (presenter == null)
return;
boolean maximized = presenter.isMaximized(call);
boolean maximized = presenter.isMaximized(info);
PopupMenu popup = new PopupMenu(view.getContext(), view);
popup.inflate(R.menu.conference_participant_actions);
MenuBuilder menu = (MenuBuilder) popup.getMenu();
......@@ -1008,7 +1031,7 @@ public class CallFragment extends BaseSupportFragment<CallPresenter> implements
maxItem.setTitle(R.string.action_call_maximize);
maxItem.setIcon(R.drawable.baseline_open_in_full_24);
}
if (!call.isAudioMuted()) {
if (!info.audioMuted) {
muteItem.setTitle(R.string.action_call_mute);
muteItem.setIcon(R.drawable.baseline_mic_off_24);
} else {
......@@ -1018,27 +1041,18 @@ public class CallFragment extends BaseSupportFragment<CallPresenter> implements
popup.setOnMenuItemClickListener(item -> {
if (presenter == null)
return false;
switch (item.getItemId()) {
case R.id.conv_contact_details:
presenter.openParticipantContact(call);
break;
case R.id.conv_contact_hangup:
presenter.hangupParticipant(call);
break;
case R.id.conv_mute:
if (!call.isAudioMuted()) {
call.muteAudio(true);
presenter.muteParticipant(call, true);
} else {
call.muteAudio(false);
presenter.muteParticipant(call, false);
}
break;
case R.id.conv_contact_maximize:
presenter.maximizeParticipant(call);
break;
default:
return false;
int itemId = item.getItemId();
if (itemId == R.id.conv_contact_details) {
presenter.openParticipantContact(info);
} else if (itemId == R.id.conv_contact_hangup) {
presenter.hangupParticipant(info);
} else if (itemId == R.id.conv_mute) {
//call.muteAudio(!info.audioMuted);
presenter.muteParticipant(info, !info.audioMuted);
} else if (itemId == R.id.conv_contact_maximize) {
presenter.maximizeParticipant(info);
} else {
return false;
}
return true;
});
......@@ -1047,34 +1061,12 @@ public class CallFragment extends BaseSupportFragment<CallPresenter> implements
menuHelper.show();
});
}
confAdapter.updateFromCalls(contacts);
confAdapter.updateFromCalls(participantInfo);
if (binding.confControlGroup.getAdapter() == null)
binding.confControlGroup.setAdapter(confAdapter);
}
}
@Override
public void updateConfInfo(List<Conference.ParticipantInfo> info) {
binding.participantLabelContainer.removeAllViews();
if (!info.isEmpty()) {
LayoutInflater inflater = LayoutInflater.from(binding.participantLabelContainer.getContext());
for (Conference.ParticipantInfo i : info) {
String displayName = i.contact.getDisplayName();
if (!TextUtils.isEmpty(displayName)) {
ItemParticipantLabelBinding label = ItemParticipantLabelBinding.inflate(inflater);
PercentFrameLayout.LayoutParams params = new PercentFrameLayout.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT);
params.getPercentLayoutInfo().leftMarginPercent = i.x / (float) mVideoWidth;
params.getPercentLayoutInfo().topMarginPercent = i.y / (float) mVideoHeight;
label.participantName.setText(displayName);
label.moderator.setVisibility(i.isModerator ? View.VISIBLE : View.GONE);
label.mute.setVisibility(i.audioMuted ? View.VISIBLE : View.GONE);
binding.participantLabelContainer.addView(label.getRoot(), params);
}
}
}
binding.participantLabelContainer.setVisibility(info.isEmpty() ? View.GONE : View.VISIBLE);
}
@Override
public void updateParticipantRecording(Set<Contact> contacts) {
if (contacts.size() == 0) {
......@@ -1131,7 +1123,9 @@ public class CallFragment extends BaseSupportFragment<CallPresenter> implements
requireActivity().invalidateOptionsMenu();
CallActivity callActivity = (CallActivity) getActivity();
callActivity.showSystemUI();
if (callActivity != null) {
callActivity.showSystemUI();
}
}
@Override
......@@ -1471,6 +1465,7 @@ public class CallFragment extends BaseSupportFragment<CallPresenter> implements
/**
* Function that is called to show/hide the plugins recycler viewer and update UI
*/
@SuppressLint("UseCompatLoadingForDrawables")
public void displayVideoPluginsCarousel() {
choosePluginMode = !choosePluginMode;
......@@ -1488,7 +1483,7 @@ public class CallFragment extends BaseSupportFragment<CallPresenter> implements
for (String callMediaHandler : callMediaHandlers) {
Map<String, String> details = getCallMediaHandlerDetails(callMediaHandler);
String drawablePath = details.get("iconPath");
if (drawablePath.endsWith("svg"))
if (drawablePath != null && drawablePath.endsWith("svg"))
drawablePath = drawablePath.replace(".svg", ".png");
Drawable handlerIcon = Drawable.createFromPath(drawablePath);
if (handlerIcon == null) {
......@@ -1513,17 +1508,15 @@ public class CallFragment extends BaseSupportFragment<CallPresenter> implements
if(callMediaHandlers.size() > 0) {
// If no previous plugin was active, take the first, else previous
int position;
if(previousPluginPosition < 1) {
if (previousPluginPosition < 1) {
rp.scrollToPosition(1);
position = 1;
previousPluginPosition = 1;
} else {
position = previousPluginPosition;
}
if (position > 0) {
String callMediaId = callMediaHandlers.get(position-1);
presenter.startPlugin(callMediaId);
}
String callMediaId = callMediaHandlers.get(position-1);
presenter.startPlugin(callMediaId);
}
} else {
......@@ -1546,12 +1539,11 @@ public class CallFragment extends BaseSupportFragment<CallPresenter> implements
/**
* Called whenever a plugin drawable in the recycler picker is clicked or scrolled to
* @param position
*/
@Override
public void onItemSelected(int position) {
Log.i(TAG, "selected position: " + position);
/** If there was a different plugin before, unload it
/* If there was a different plugin before, unload it
* If previousPluginPosition = -1 or 0, there was no plugin
*/
if (previousPluginPosition > 0) {
......@@ -1569,13 +1561,12 @@ public class CallFragment extends BaseSupportFragment<CallPresenter> implements
/**
* Called whenever a plugin drawable in the recycler picker is clicked
* @param position
*/
@Override
public void onItemClicked(int position) {
Log.i(TAG, "selected position: " + position);
if (position == 0) {
/** If there was a different plugin before, unload it
/* If there was a different plugin before, unload it
* If previousPluginPosition = -1 or 0, there was no plugin
*/
if (previousPluginPosition > 0) {
......@@ -1585,7 +1576,9 @@ public class CallFragment extends BaseSupportFragment<CallPresenter> implements
}
CallActivity callActivity = (CallActivity) getActivity();
callActivity.showSystemUI();
if (callActivity != null) {
callActivity.showSystemUI();
}
toggleVideoPluginsCarousel(false);
displayVideoPluginsCarousel();
......
......@@ -436,7 +436,7 @@ public class TVCallFragment extends BaseSupportFragment<CallPresenter> implement
.build(getActivity())
);
if (!mConferenceMode) {
/*if (!mConferenceMode) {
binding.confControlGroup.setVisibility(View.GONE);
} else {
binding.confControlGroup.setVisibility(View.VISIBLE);
......@@ -464,7 +464,7 @@ public class TVCallFragment extends BaseSupportFragment<CallPresenter> implement
confAdapter.updateFromCalls(calls);
if (binding.confControlGroup.getAdapter() == null)
binding.confControlGroup.setAdapter(confAdapter);
}
}*/
}
@Override
......@@ -686,6 +686,36 @@ public class TVCallFragment extends BaseSupportFragment<CallPresenter> implement
}
}
binding.participantLabelContainer.setVisibility(info.isEmpty() ? View.GONE : View.VISIBLE);
if (!mConferenceMode) {
binding.confControlGroup.setVisibility(View.GONE);
} else {
binding.confControlGroup.setVisibility(View.VISIBLE);
if (confAdapter == null) {
confAdapter = new ConfParticipantAdapter((view, call) -> {
Context context = requireContext();
PopupMenu popup = new PopupMenu(context, view);
popup.inflate(R.menu.conference_participant_actions);
popup.setOnMenuItemClickListener(item -> {
int itemId = item.getItemId();
if (itemId == R.id.conv_contact_details) {
presenter.openParticipantContact(call);
} else if (itemId == R.id.conv_contact_hangup) {
presenter.hangupParticipant(call);
} else {
return false;
}
return true;
});
MenuPopupHelper menuHelper = new MenuPopupHelper(context, (MenuBuilder) popup.getMenu(), view);
menuHelper.setForceShowIcon(true);
menuHelper.show();
});
}
confAdapter.updateFromCalls(info);
if (binding.confControlGroup.getAdapter() == null)
binding.confControlGroup.setAdapter(confAdapter);
}
}
@Override
......
......@@ -2,11 +2,11 @@
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:viewportHeight="24"
android:tint="?attr/colorControlNormal">
<path
android:pathData="M12.0856,6.1741L15.2189,10.3993L21.4127,6.2318L21.4127,18.3908L2.7376,18.3908L2.7376,6.2318L8.9295,10.3981L12.0856,6.1741Z"
android:strokeWidth="1.3"
android:fillColor="#00000000"
android:fillType="nonZero"
android:strokeColor="#000000"/>
android:fillColor="@android:color/white"
android:strokeColor="@android:color/white"/>
</vector>
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:orientation="horizontal"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="@drawable/background_conference_label"
android:gravity="center_vertical"
android:padding="12dp">
android:layout_height="wrap_content">
<TextView
android:id="@+id/participant_name"
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
tools:text="Georges Longnom de Chateaubrillant"
android:textColor="@color/white"
android:ellipsize="marquee"
android:singleLine="true" />
android:layout_margin="4dp"
android:background="@drawable/background_conference_label"
android:gravity="center_vertical"
android:orientation="horizontal"
android:padding="12dp">
<ImageView
android:id="@+id/moderator"
android:layout_width="16dp"
android:layout_height="16dp"
android:src="@drawable/baseline_moderateur"
android:tint="@color/white"
android:layout_marginLeft="4dp"
android:visibility="gone"
tools:visibility="visible"
/>
<TextView
android:id="@+id/participant_name"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="8dp"
android:ellipsize="marquee"
android:singleLine="true"
android:textColor="@color/white"
tools:text="Georges Longname of Chateaubrillant" />
<ImageView
android:id="@+id/mute"
android:layout_width="16dp"
android:layout_height="16dp"
android:layout_marginLeft="4dp"
android:src="@drawable/baseline_mic_off_24"
android:visibility="gone"
tools:visibility="visible"
/>
<ImageView
android:id="@+id/moderator"
android:layout_width="16dp"
android:layout_height="16dp"
android:layout_margin="4dp"
android:contentDescription="@string/call_moderator"
android:src="@drawable/baseline_moderateur"
android:visibility="gone"
tools:visibility="visible" />
</LinearLayout>
\ No newline at end of file
<ImageView
android:id="@+id/mute"
android:layout_width="16dp"
android:layout_height="16dp"
android:layout_margin="4dp"
android:contentDescription="@string/call_muted"
android:src="@drawable/baseline_mic_off_24"
android:visibility="gone"
tools:visibility="visible" />
</LinearLayout>
</FrameLayout>
\ No newline at end of file
......@@ -38,4 +38,6 @@ along with this program; if not, write to the Free Software
<string name="call_human_state_over">Over</string>
<string name="call_human_state_none" translatable="false" />
<!-- Conferences -->
<string name="call_moderator">Moderator</string>
<string name="call_muted">Muted</string>
</resources>
\ No newline at end of file
......@@ -24,6 +24,7 @@ import net.jami.daemon.JamiService;
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.ConversationHistory;
import net.jami.model.Uri;
......@@ -401,7 +402,8 @@ public class CallPresenter extends RootPresenter<CallView> {
public void uiVisibilityChanged(boolean displayed) {
net.jami.call.CallView view = getView();
Log.w(TAG, "uiVisibilityChanged " + mOnGoingCall + " " + displayed);
CallView view = getView();
if (view != null)
view.displayHangupButton(mOnGoingCall && displayed);
}
......@@ -469,7 +471,7 @@ public class CallPresenter extends RootPresenter<CallView> {
}
private void confUpdate(Conference call) {
Log.w(TAG, "confUpdate " + call.getId());
Log.w(TAG, "confUpdate " + call.getId() + " " + call.getState());
Call.CallStatus status = call.getState();
if (status == Call.CallStatus.HOLD) {
......@@ -522,12 +524,13 @@ public class CallPresenter extends RootPresenter<CallView> {
}
}
public void maximizeParticipant(Call call) {
if (mConference.getMaximizedCall() == call)
call = null;
mConference.setMaximizedCall(call);
if (call != null) {
mCallService.setConfMaximizedParticipant(mConference.getConfId(), call.getDaemonIdString());