Merge branch 'feature/recyclerview'

This commit is contained in:
Sylvain Berfini 2018-09-03 11:39:30 +02:00
commit 884f4d66c6
41 changed files with 2065 additions and 1535 deletions

1
.gitignore vendored
View file

@ -36,6 +36,7 @@ submodules/externals/build/libvpx/arm
submodules/externals/build/libvpx/x86
submodules/externals/build/openh264/arm
submodules/externals/build/openh264/x86
submodules/externals/ffmpeg/arm/
tests/*$py.class
tests/build.xml
tests/project.properties

View file

@ -64,7 +64,9 @@
android:icon="@mipmap/ic_launcher"
android:roundIcon="@mipmap/ic_launcher_round"
android:label="@string/app_name"
android:largeHeap="true">
android:largeHeap="true"
>
<activity
android:name=".activities.LinphoneLauncherActivity"
android:exported="true"

View file

@ -47,7 +47,10 @@ dependencies {
if (firebaseEnable()) {
implementation 'com.google.firebase:firebase-messaging:15.0.2'
}
implementation 'com.android.support:support-v4:27.0.1'
implementation 'com.android.support:support-v4:27.1.1'
implementation 'com.android.support:recyclerview-v7:27.1.1'
implementation 'com.android.support:appcompat-v7:27.1.1'
implementation 'com.android.support:design:27.1.1'
implementation project(':liblinphone-sdk')
}

View file

@ -72,7 +72,7 @@ public class LogsActivity extends Activity {
@Override
public boolean onCreateOptionsMenu(Menu menu) {
// Inflate the menu; this adds items to the action bar if it is present.
// Inflate the menu; this adds items to the action context_bar if it is present.
getMenuInflater().inflate(R.menu.activity_logs, menu);
return true;
}

View file

@ -73,7 +73,7 @@ public class MainActivity extends Activity {
@Override
public boolean onCreateOptionsMenu(Menu menu) {
// Inflate the menu; this adds items to the action bar if it is present.
// Inflate the menu; this adds items to the action context_bar if it is present.
getMenuInflater().inflate(R.menu.activity_main, menu);
return true;
}

View file

@ -69,7 +69,7 @@ public class SuitesActivity extends Activity {
@Override
public boolean onCreateOptionsMenu(Menu menu) {
// Inflate the menu; this adds items to the action bar if it is present.
// Inflate the menu; this adds items to the action context_bar if it is present.
getMenuInflater().inflate(R.menu.activity_suites, menu);
return true;
}

View file

@ -0,0 +1,11 @@
<?xml version="1.0" encoding="utf-8"?>
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
<item
android:bottom="40dp"
android:left="20dp"
android:right="20dp"
android:top="40dp">
<bitmap android:src="@drawable/cancel_edit"/>
</item>
</layer-list>

7
res/drawable/divider.xml Normal file
View file

@ -0,0 +1,7 @@
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">
<size
android:width="1dp"
android:height="1dp" />
<solid android:color="@color/colorE" />
</shape>

12
res/drawable/line.xml Normal file
View file

@ -0,0 +1,12 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">
<solid android:color="@android:color/darker_gray" />
<padding
android:top="10dp"
android:left="10dp"
android:right="10dp"
android:bottom="10dp"/>
</shape>

View file

@ -0,0 +1,9 @@
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<!--<item-->
<!--android:state_pressed="true"-->
<!--android:drawable="@drawable/line"/>-->
<!--<item-->
<!--android:drawable="@drawable/line"/>-->
</selector>

View file

@ -173,7 +173,8 @@
android:layout_height="wrap_content"
android:layout_above="@id/footer"/>
<ListView
<android.support.v7.widget.RecyclerView
android:id="@+id/chat_message_list"
android:divider="@android:color/transparent"
android:choiceMode="multipleChoice"

View file

@ -11,6 +11,7 @@
android:layout_width="30dp"
android:layout_height="30dp"
android:adjustViewBounds="true"
android:clickable="false"
android:layout_alignParentRight="true"/>
<LinearLayout

View file

@ -144,7 +144,7 @@
android:layout_height="match_parent"
android:id="@+id/relativeLayout">
<ListView
<android.support.v7.widget.RecyclerView
android:id="@+id/contactsList"
android:layout_width="match_parent"
android:layout_height="match_parent"

View file

@ -99,7 +99,7 @@
</RelativeLayout>
<ListView
<android.support.v7.widget.RecyclerView
android:id="@+id/chat_room_participants"
android:layout_width="match_parent"
android:layout_height="match_parent"
@ -107,7 +107,7 @@
android:divider="@color/colorE"
android:dividerHeight="1dp">
</ListView>
</android.support.v7.widget.RecyclerView>
<LinearLayout
android:id="@+id/leaveGroupLayout"

View file

@ -55,7 +55,7 @@
<include layout="@layout/edit_list"/>
<ListView
<android.support.v7.widget.RecyclerView
android:id="@+id/chatList"
android:layout_width="match_parent"
android:layout_height="match_parent"

View file

@ -6,7 +6,8 @@
android:paddingLeft="10dp"
android:paddingRight="10dp"
android:paddingTop="5dp"
android:background="@drawable/list_selector">
android:foreground="?android:attr/selectableItemBackground"
android:background="@drawable/list_selector">
<CheckBox
android:id="@+id/delete_chatroom"
@ -14,6 +15,7 @@
android:contentDescription="@string/content_description_delete"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:clickable="false"
android:adjustViewBounds="true"
android:layout_alignParentRight="true"
android:layout_centerVertical="true"
@ -121,5 +123,11 @@
</RelativeLayout>
</LinearLayout>
<View
android:id="@+id/selected_overlay"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@color/notification_color_led"
android:visibility="invisible" />
</RelativeLayout>

View file

@ -69,6 +69,7 @@
android:layout_alignParentRight="true"
android:layout_centerInParent="true"
android:visibility="gone"
android:clickable="false"
android:paddingLeft="5dp"
android:paddingRight="5dp" />

View file

@ -2,8 +2,9 @@
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:background="@color/colorH"
android:orientation="vertical" >
android:orientation="vertical">
<LinearLayout
android:id="@+id/top_bar"
@ -119,13 +120,18 @@
android:layout_width="match_parent"
android:layout_height="match_parent">
<ListView
<!--android:fastScrollAlwaysVisible="true"-->
<android.support.v7.widget.RecyclerView
android:id="@+id/contactsList"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:divider="@color/colorE"
android:fastScrollEnabled="true"
android:fastScrollAlwaysVisible="true"
app:fastScrollEnabled="true"
app:fastScrollHorizontalThumbDrawable="@drawable/fast_scroll"
app:fastScrollHorizontalTrackDrawable="@drawable/line_drawable"
app:fastScrollVerticalThumbDrawable="@drawable/fast_scroll"
app:fastScrollVerticalTrackDrawable="@drawable/line_drawable"
android:dividerHeight="1dp" />
<ProgressBar

135
res/layout/context_bar.xml Normal file
View file

@ -0,0 +1,135 @@
<?xml version="1.0" encoding="utf-8"?>
<android.support.v7.widget.Toolbar xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/context_bar"
android:background="@color/colorA"
android:layout_width="match_parent"
android:layout_height="100dp"
android:orientation="horizontal">
<!--<RelativeLayout-->
<!--android:id="@+id/status_bar"-->
<!--android:layout_width="match_parent"-->
<!--android:layout_height="40dp"-->
<!--android:layout_alignParentTop="true">-->
<!--<ImageView-->
<!--android:id="@+id/side_menu_button"-->
<!--android:src="@drawable/menu_burger"-->
<!--android:contentDescription="@string/content_description_menu"-->
<!--android:layout_width="wrap_content"-->
<!--android:layout_height="wrap_content"-->
<!--android:padding="10dp"-->
<!--android:layout_centerInParent="true"-->
<!--android:layout_alignParentLeft="true"/>-->
<!--<ImageView-->
<!--android:id="@+id/call_quality"-->
<!--android:src="@drawable/call_quality_indicator_0"-->
<!--android:contentDescription="@string/content_description_call_quality"-->
<!--android:layout_width="wrap_content"-->
<!--android:layout_height="wrap_content"-->
<!--android:padding="10dp"-->
<!--android:layout_centerInParent="true"-->
<!--android:layout_alignParentLeft="true"-->
<!--android:visibility="invisible" />-->
<!--<ImageView-->
<!--android:id="@+id/status_led"-->
<!--android:src="@drawable/led_disconnected"-->
<!--android:paddingLeft="5dp"-->
<!--android:adjustViewBounds="true"-->
<!--android:layout_width="20dp"-->
<!--android:layout_height="wrap_content"-->
<!--android:layout_centerInParent="true"-->
<!--android:layout_toRightOf="@id/side_menu_button"/>-->
<!--<TextView-->
<!--android:id="@+id/status_text"-->
<!--android:text="@string/status_not_connected"-->
<!--style="@style/font16"-->
<!--android:paddingLeft="5dp"-->
<!--android:layout_centerVertical="true"-->
<!--android:layout_width="wrap_content"-->
<!--android:layout_height="match_parent"-->
<!--android:gravity="center_vertical"-->
<!--android:layout_toRightOf="@id/status_led" />-->
<!--<ImageView-->
<!--android:id="@+id/voicemail"-->
<!--android:src="@drawable/voicemail"-->
<!--android:layout_centerVertical="true"-->
<!--android:layout_width="wrap_content"-->
<!--android:layout_height="match_parent"-->
<!--android:gravity="center_vertical"-->
<!--android:visibility="gone" />-->
<!--<TextView-->
<!--android:id="@+id/voicemail_count"-->
<!--style="@style/font16"-->
<!--android:layout_alignParentRight="true"-->
<!--android:paddingLeft="5dp"-->
<!--android:paddingRight="10dp"-->
<!--android:layout_centerVertical="true"-->
<!--android:layout_width="wrap_content"-->
<!--android:layout_height="match_parent"-->
<!--android:visibility="gone" />-->
<!--<ImageView-->
<!--android:id="@+id/encryption"-->
<!--android:src="@drawable/security_ko"-->
<!--android:contentDescription="@string/content_description_encryption"-->
<!--android:layout_width="wrap_content"-->
<!--android:layout_height="wrap_content"-->
<!--android:adjustViewBounds="true"-->
<!--android:padding="5dp"-->
<!--android:layout_centerInParent="true"-->
<!--android:visibility="gone"-->
<!--android:layout_alignParentRight="true" />-->
<!--</RelativeLayout>-->
<LinearLayout
android:id="@+id/top_bar"
android:orientation="horizontal"
android:background="@color/colorF"
android:layout_width="match_parent"
android:layout_height="60dp"
android:layout_alignParentBottom="true"
>
<ImageView
android:id="@+id/new_discussion"
android:src="@drawable/chat_add"
android:background="@drawable/toolbar_button"
android:contentDescription="@string/content_description_new_discussion"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="0.2"
android:padding="15dp"/>
<ImageView
android:id="@+id/back_in_call"
android:src="@drawable/call_back"
android:background="@drawable/toolbar_button"
android:contentDescription="@string/content_description_back_call"
android:visibility="invisible"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="0.2"
android:padding="15dp"/>
<View
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="0.4"/>
<ImageView
android:id="@+id/edit"
android:src="@drawable/edit_list_button"
android:background="@drawable/toolbar_button"
android:contentDescription="@string/content_description_edit_list"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="0.2"
android:padding="15dp"/>
<include layout="@layout/edit_list"/>
</LinearLayout>
</android.support.v7.widget.Toolbar>

View file

@ -76,7 +76,7 @@
<include layout="@layout/edit_list"/>
<ListView
<android.support.v7.widget.RecyclerView
android:id="@+id/history_list"
android:layout_width="match_parent"
android:layout_height="match_parent"

View file

@ -28,6 +28,7 @@
<RelativeLayout
android:id="@+id/history_whole"
android:layout_width="match_parent"
android:layout_height="40dp"
android:layout_margin="5dp"
@ -53,6 +54,7 @@
android:layout_alignParentRight="true"
android:layout_centerInParent="true"
android:visibility="gone"
android:clickable="false"
android:padding="5dp" />
<RelativeLayout

View file

@ -6,12 +6,16 @@
<item name="android:fastScrollTextColor">@android:color/white</item>
<item name="android:fastScrollPreviewBackgroundRight">@drawable/fast_scroll_preview</item>
</style>
<style name="NoTitle" parent="android:Theme.Holo.Light.NoActionBar">
<item name="android:fastScrollThumbDrawable">@drawable/fast_scroll</item>
<item name="android:fastScrollTextColor">@android:color/white</item>
<item name="android:fastScrollPreviewBackgroundRight">@drawable/fast_scroll_preview</item>
</style>
<style name="FullScreen" parent="android:Theme.Holo.Light.NoActionBar.Fullscreen">
<style name="FullScreen" parent="android:Theme.Holo.Light.NoActionBar">
<item name="android:fastScrollThumbDrawable">@drawable/fast_scroll</item>
<item name="android:fastScrollTextColor">@android:color/white</item>
<item name="android:fastScrollPreviewBackgroundRight">@drawable/fast_scroll_preview</item>

View file

@ -1 +0,0 @@
../libs

View file

@ -1630,6 +1630,7 @@ public class LinphoneActivity extends LinphoneGenericActivity implements OnClick
menu.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View view) {
if(sideMenu.isDrawerVisible(Gravity.LEFT)){
sideMenu.closeDrawer(sideMenuContent);
} else {

View file

@ -152,8 +152,8 @@ public class CodecDownloaderFragment extends Fragment {
if (savedInstanceState.containsKey("downloaded"))
downloaded.setVisibility((Integer) savedInstanceState.getSerializable("downloaded"));
if (savedInstanceState.containsKey("bar"))
bar.setVisibility((Integer) savedInstanceState.getSerializable("bar"));
if (savedInstanceState.containsKey("context_bar"))
bar.setVisibility((Integer) savedInstanceState.getSerializable("context_bar"));
if (savedInstanceState.containsKey("downloadingInfo"))
downloadingInfo.setVisibility((Integer) savedInstanceState.getSerializable("downloadingInfo"));
@ -177,7 +177,7 @@ public class CodecDownloaderFragment extends Fragment {
if (yes != null) outState.putSerializable("yes", yes.getVisibility());
if (no != null) outState.putSerializable("no", no.getVisibility());
if (ok != null) outState.putSerializable("ok", ok.getVisibility());
if (bar != null) outState.putSerializable("bar", bar.getVisibility());
if (bar != null) outState.putSerializable("context_bar", bar.getVisibility());
if (downloadingInfo != null) outState.putSerializable("downloadingInfo", downloadingInfo.getVisibility());
super.onSaveInstanceState(outState);
}

View file

@ -0,0 +1,238 @@
package org.linphone.call;
/*
CallHistoryAdapter.java
Copyright (C) 2018 Belledonne Communications, Grenoble, France
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 2
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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
import android.annotation.SuppressLint;
import android.content.Context;
import android.support.annotation.NonNull;
import android.support.v7.widget.RecyclerView;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.CheckBox;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.RelativeLayout;
import android.widget.TextView;
import org.linphone.LinphoneUtils;
import org.linphone.R;
import org.linphone.activities.LinphoneActivity;
import org.linphone.contacts.ContactsManager;
import org.linphone.contacts.LinphoneContact;
import org.linphone.core.Address;
import org.linphone.core.Call;
import org.linphone.core.CallLog;
import org.linphone.ui.SelectableAdapter;
import org.linphone.ui.SelectableHelper;
import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.List;
public class CallHistoryAdapter extends SelectableAdapter<CallHistoryAdapter.ViewHolder> {
//This ViewHolder links fields from the xml to variables that will display values provided by the adapter
public static class ViewHolder extends RecyclerView.ViewHolder implements View.OnClickListener,
View.OnLongClickListener{
public TextView contact;
public ImageView detail;
public CheckBox select;
public ImageView callDirection;
public ImageView contactPicture;
public RelativeLayout CallContact;
public LinearLayout separator;
public TextView separatorText;
private CallHistoryAdapter.ViewHolder.ClickListener listener;
public ViewHolder(View view, CallHistoryAdapter.ViewHolder.ClickListener listener) {
super(view);
contact = (TextView) view.findViewById(R.id.sip_uri);
detail = (ImageView) view.findViewById(R.id.detail);
select = (CheckBox) view.findViewById(R.id.delete);
callDirection = (ImageView) view.findViewById(R.id.icon);
contactPicture = (ImageView) view.findViewById(R.id.contact_picture);
CallContact = (RelativeLayout) view.findViewById(R.id.history_click);
separator = (LinearLayout) view.findViewById(R.id.separator);
separatorText = (TextView) view.findViewById(R.id.separator_text);
this.listener = listener;
view.setOnClickListener(this);
view.setOnLongClickListener(this);
}
@Override
public void onClick(View view) {
if (listener != null) {
listener.onItemClicked(getAdapterPosition());
}
}
@Override
public boolean onLongClick(View view) {
if (listener != null) {
return listener.onItemLongClicked(getAdapterPosition());
}
return false;
}
public interface ClickListener {
void onItemClicked(int position);
boolean onItemLongClicked(int position);
}
}
private List<CallLog> mLogs;
private Context mContext;
private CallHistoryAdapter.ViewHolder.ClickListener clickListener;
public CallHistoryAdapter(Context aContext, List<CallLog> logs,CallHistoryAdapter.ViewHolder.ClickListener listener ,SelectableHelper helper) {
super(helper);
this.mLogs = logs;
this.mContext = aContext;
this.clickListener = listener;
}
public int getCount() {
return mLogs.size();
}
public Object getItem(int position) {
return mLogs.get(position);
}
@NonNull
@Override
public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
View v = LayoutInflater.from(parent.getContext()).inflate(R.layout.history_cell, parent, false);
return new ViewHolder(v, clickListener);
}
@Override
public void onBindViewHolder(@NonNull final ViewHolder holder, final int position) {
final CallLog log= mLogs.get(position);
long timestamp = log.getStartDate() * 1000;
Address address;
holder.contact.setSelected(true); // For automated horizontal scrolling of long texts
Calendar logTime = Calendar.getInstance();
logTime.setTimeInMillis(timestamp);
holder.separatorText.setText(timestampToHumanDate(logTime));
holder.select.setVisibility(isEditionEnabled() ? View.VISIBLE : View.GONE);
holder.select.setChecked(isSelected(position));
if (position > 0) {
CallLog previousLog = mLogs.get(position-1);
long previousTimestamp = previousLog.getStartDate() * 1000;
Calendar previousLogTime = Calendar.getInstance();
previousLogTime.setTimeInMillis(previousTimestamp);
if (isSameDay(previousLogTime, logTime)) {
holder.separator.setVisibility(View.GONE);
} else {
holder.separator.setVisibility(View.VISIBLE);
}
} else {
holder.separator.setVisibility(View.VISIBLE);
}
if (log.getDir() == Call.Dir.Incoming) {
address = log.getFromAddress();
if (log.getStatus() == Call.Status.Missed) {
holder.callDirection.setImageResource(R.drawable.call_status_missed);
} else {
holder.callDirection.setImageResource(R.drawable.call_status_incoming);
}
} else {
address = log.getToAddress();
holder.callDirection.setImageResource(R.drawable.call_status_outgoing);
}
LinphoneContact c = ContactsManager.getInstance().findContactFromAddress(address);
String displayName = null;
final String sipUri = (address != null) ? address.asString() : "";
if (c != null) {
displayName = c.getFullName();
LinphoneUtils.setThumbnailPictureFromUri(LinphoneActivity.instance(), holder.contactPicture, c.getThumbnailUri());
} else {
holder.contactPicture.setImageBitmap(ContactsManager.getInstance().getDefaultAvatarBitmap());
}
if (displayName == null) {
holder.contact.setText(LinphoneUtils.getAddressDisplayName(sipUri));
} else {
holder.contact.setText(displayName);
}
holder.detail.setVisibility(isEditionEnabled() ? View.INVISIBLE : View.VISIBLE);
holder.detail.setOnClickListener(!isEditionEnabled() ?
new View.OnClickListener() {
@Override
public void onClick(View v) {
if (LinphoneActivity.isInstanciated()) {
LinphoneActivity.instance().displayHistoryDetail(sipUri, log);
}
}
}
:
null
);
}
@Override
public int getItemCount() {
return mLogs.size();
}
@SuppressLint("SimpleDateFormat")
private String timestampToHumanDate(Calendar cal) {
SimpleDateFormat dateFormat;
if (isToday(cal)) {
return mContext.getString(R.string.today);
} else if (isYesterday(cal)) {
return mContext.getString(R.string.yesterday);
} else {
dateFormat = new SimpleDateFormat(mContext.getResources().getString(R.string.history_date_format));
}
return dateFormat.format(cal.getTime());
}
private boolean isSameDay(Calendar cal1, Calendar cal2) {
if (cal1 == null || cal2 == null) {
return false;
}
return (cal1.get(Calendar.ERA) == cal2.get(Calendar.ERA) &&
cal1.get(Calendar.YEAR) == cal2.get(Calendar.YEAR) &&
cal1.get(Calendar.DAY_OF_YEAR) == cal2.get(Calendar.DAY_OF_YEAR));
}
private boolean isToday(Calendar cal) {
return isSameDay(cal, Calendar.getInstance());
}
private boolean isYesterday(Calendar cal) {
Calendar yesterday = Calendar.getInstance();
yesterday.roll(Calendar.DAY_OF_MONTH, -1);
return isSameDay(cal, yesterday);
}
}

View file

@ -19,6 +19,8 @@ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
package org.linphone.chat;
import android.content.Context;
import android.support.v7.widget.RecyclerView;
import android.view.View;
import android.widget.Button;
import android.widget.CheckBox;
@ -29,12 +31,13 @@ import android.widget.RelativeLayout;
import android.widget.TextView;
import org.linphone.R;
import org.linphone.core.ChatMessage;
public class ChatBubbleViewHolder {
public class ChatBubbleViewHolder extends RecyclerView.ViewHolder implements View.OnClickListener{
public String messageId;
public Context mContext;
public ChatMessage message;
public LinearLayout eventLayout;
//public TextView eventTime;
public TextView eventMessage;
public RelativeLayout bubbleLayout;
@ -61,35 +64,84 @@ public class ChatBubbleViewHolder {
public Button openFileButton;
public CheckBox delete;
private ClickListener listener;
public ChatBubbleViewHolder(View view) {
eventLayout = view.findViewById(R.id.event);
public ChatBubbleViewHolder(Context context, View view, ClickListener listener) {
super(view);
this.mContext = context;
this.eventLayout = view.findViewById(R.id.event);
//eventTime = view.findViewById(R.id.event_date);
eventMessage = view.findViewById(R.id.event_text);
this.eventMessage = view.findViewById(R.id.event_text);
bubbleLayout = view.findViewById(R.id.bubble);
separatorLayout = view.findViewById(R.id.separator);
background = view.findViewById(R.id.background);
contactPicture = view.findViewById(R.id.contact_picture);
contactPictureMask = view.findViewById(R.id.mask);
contactName = view.findViewById(R.id.contact_header);
this.bubbleLayout = view.findViewById(R.id.bubble);
this.background = view.findViewById(R.id.background);
this.contactPicture = view.findViewById(R.id.contact_picture);
this.contactPictureMask = view.findViewById(R.id.mask);
this.contactName = view.findViewById(R.id.contact_header);
messageStatus = view.findViewById(R.id.status);
messageSendingInProgress = view.findViewById(R.id.inprogress);
imdmLayout = view.findViewById(R.id.imdmLayout);
imdmIcon = view.findViewById(R.id.imdmIcon);
imdmLabel = view.findViewById(R.id.imdmText);
this.messageStatus = view.findViewById(R.id.status);
this.messageSendingInProgress = view.findViewById(R.id.inprogress);
this.imdmLayout = view.findViewById(R.id.imdmLayout);
this.imdmIcon = view.findViewById(R.id.imdmIcon);
this.imdmLabel = view.findViewById(R.id.imdmText);
messageText = view.findViewById(R.id.message);
messageImage = view.findViewById(R.id.image);
this.messageText = view.findViewById(R.id.message);
this.messageImage = view.findViewById(R.id.image);
this.separatorLayout = view.findViewById(R.id.separator);
fileTransferLayout = view.findViewById(R.id.file_transfer_layout);
fileTransferProgressBar = view.findViewById(R.id.progress_bar);
fileTransferAction = view.findViewById(R.id.file_transfer_action);
this.fileTransferLayout = view.findViewById(R.id.file_transfer_layout);
this.fileTransferProgressBar = view.findViewById(R.id.progress_bar);
this.fileTransferAction = view.findViewById(R.id.file_transfer_action);
fileName = view.findViewById(R.id.file_name);
openFileButton = view.findViewById(R.id.open_file);
this.fileName = view.findViewById(R.id.file_name);
this.openFileButton = view.findViewById(R.id.open_file);
delete = view.findViewById(R.id.delete_message);
this.delete = view.findViewById(R.id.delete_message);
this.listener = listener;
view.setOnClickListener(this);
}
public ChatBubbleViewHolder(View view) {
super(view);
this.eventLayout = view.findViewById(R.id.event);
//eventTime = view.findViewById(R.id.event_date);
this.eventMessage = view.findViewById(R.id.event_text);
this.bubbleLayout = view.findViewById(R.id.bubble);
this.background = view.findViewById(R.id.background);
this.contactPicture = view.findViewById(R.id.contact_picture);
this.contactPictureMask = view.findViewById(R.id.mask);
this.contactName = view.findViewById(R.id.contact_header);
this.messageStatus = view.findViewById(R.id.status);
this.messageSendingInProgress = view.findViewById(R.id.inprogress);
this.imdmLayout = view.findViewById(R.id.imdmLayout);
this.imdmIcon = view.findViewById(R.id.imdmIcon);
this.imdmLabel = view.findViewById(R.id.imdmText);
this.messageText = view.findViewById(R.id.message);
this.messageImage = view.findViewById(R.id.image);
this.separatorLayout = view.findViewById(R.id.separator);
this.fileTransferLayout = view.findViewById(R.id.file_transfer_layout);
this.fileTransferProgressBar = view.findViewById(R.id.progress_bar);
this.fileTransferAction = view.findViewById(R.id.file_transfer_action);
this.fileName = view.findViewById(R.id.file_name);
this.openFileButton = view.findViewById(R.id.open_file);
this.delete = view.findViewById(R.id.delete_message);
}
@Override
public void onClick(View v) {
if(listener!=null) {
listener.onItemClicked(getAdapterPosition());
}
}
public interface ClickListener {
void onItemClicked(int position);
}
}

View file

@ -21,48 +21,46 @@ package org.linphone.chat;
import android.app.Fragment;
import android.os.Bundle;
import android.support.v7.widget.DividerItemDecoration;
import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.RecyclerView;
import android.text.Editable;
import android.text.TextWatcher;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.view.inputmethod.InputMethodManager;
import android.widget.AdapterView;
import android.widget.EditText;
import android.widget.HorizontalScrollView;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.ListView;
import android.widget.ProgressBar;
import android.widget.RelativeLayout;
import android.widget.TextView;
import org.linphone.LinphoneManager;
import org.linphone.LinphonePreferences;
import org.linphone.R;
import org.linphone.activities.LinphoneActivity;
import org.linphone.contacts.ContactAddress;
import org.linphone.contacts.ContactsManager;
import org.linphone.contacts.LinphoneNumberOrAddress;
import org.linphone.contacts.ContactsUpdatedListener;
import org.linphone.contacts.SearchContactsListAdapter;
import org.linphone.core.Address;
import org.linphone.core.ChatRoom;
import org.linphone.core.ChatRoomListenerStub;
import org.linphone.core.Core;
import org.linphone.core.Factory;
import org.linphone.core.ProxyConfig;
import org.linphone.mediastream.Log;
import org.linphone.ui.ContactSelectView;
import org.linphone.contacts.ContactsUpdatedListener;
import org.linphone.activities.LinphoneActivity;
import org.linphone.R;
import java.util.ArrayList;
import java.util.List;
import static android.content.Context.INPUT_METHOD_SERVICE;
public class ChatCreationFragment extends Fragment implements View.OnClickListener, AdapterView.OnItemClickListener, ContactsUpdatedListener {
private LayoutInflater mInflater;
private ListView mContactsList;
public class ChatCreationFragment extends Fragment implements View.OnClickListener ,SearchContactsListAdapter.ViewHolder.ClickListener, ContactsUpdatedListener {
private RecyclerView mContactsList;
private LinearLayout mContactsSelectedLayout;
private HorizontalScrollView mContactsSelectLayout;
private ArrayList<ContactAddress> mContactsSelected;
@ -80,7 +78,7 @@ public class ChatCreationFragment extends Fragment implements View.OnClickListen
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
mInflater = inflater;
super.onCreate(savedInstanceState);
View view = inflater.inflate(R.layout.chat_create, container, false);
mContactsSelected = new ArrayList<>();
@ -124,7 +122,7 @@ public class ChatCreationFragment extends Fragment implements View.OnClickListen
mContactsFetchInProgress = view.findViewById(R.id.contactsFetchInProgress);
mContactsFetchInProgress.setVisibility(View.VISIBLE);
mSearchAdapter = new SearchContactsListAdapter(null, mInflater, mContactsFetchInProgress);
mSearchAdapter = new SearchContactsListAdapter(null, mContactsFetchInProgress, this);
mSearchField = view.findViewById(R.id.searchField);
mSearchField.addTextChangedListener(new TextWatcher() {
@ -146,8 +144,23 @@ public class ChatCreationFragment extends Fragment implements View.OnClickListen
}
});
//Declares the layout manager, allowing customization of RecyclerView displaying
LinearLayoutManager layoutManager = new LinearLayoutManager(getActivity().getApplicationContext());
mContactsList.setAdapter(mSearchAdapter);
mContactsList.setOnItemClickListener(this);
//Divider between items + binds layout manager to our RecyclerView
DividerItemDecoration dividerItemDecoration = new DividerItemDecoration(mContactsList.getContext(),
layoutManager.getOrientation());
dividerItemDecoration.setDrawable(getActivity().getApplicationContext().getResources().getDrawable(R.drawable.divider));
mContactsList.addItemDecoration(dividerItemDecoration);
mContactsList.setLayoutManager(layoutManager);
if (savedInstanceState != null && savedInstanceState.getStringArrayList("mContactsSelected") != null) {
mContactsSelectedLayout.removeAllViews();
// We need to get all contacts not only sip
@ -329,7 +342,7 @@ public class ChatCreationFragment extends Fragment implements View.OnClickListen
private void removeContactFromSelection(ContactAddress ca) {
updateContactsClick(ca, mSearchAdapter.getContactsSelectedList());
mSearchAdapter.notifyDataSetInvalidated();
mSearchAdapter.notifyDataSetChanged();
updateListSelected();
}
@ -348,6 +361,8 @@ public class ChatCreationFragment extends Fragment implements View.OnClickListen
super.onSaveInstanceState(outState);
}
//Removed all selection mode related code, as it is now located into SelectableHelper.
@Override
public void onClick(View view) {
int id = view.getId();
@ -412,8 +427,8 @@ public class ChatCreationFragment extends Fragment implements View.OnClickListen
}
@Override
public void onItemClick(AdapterView<?> adapterView, View view, int i, long l) {
ContactAddress ca = mSearchAdapter.getContacts().get(i);
public void onItemClicked(int position) {
ContactAddress ca = mSearchAdapter.getContacts().get(position);
Core lc = LinphoneManager.getLcIfManagerNotDestroyedOrNull();
ProxyConfig lpc = lc.getDefaultProxyConfig();
if (lpc == null || lpc.getConferenceFactoryUri() == null) {

View file

@ -35,6 +35,7 @@ import android.net.Uri;
import android.os.AsyncTask;
import android.os.Environment;
import android.provider.MediaStore;
import android.support.annotation.NonNull;
import android.support.v4.content.FileProvider;
import android.text.Spanned;
import android.text.method.LinkMovementMethod;
@ -56,12 +57,11 @@ import org.linphone.core.Address;
import org.linphone.core.ChatMessage;
import org.linphone.core.ChatMessageListenerStub;
import org.linphone.core.Content;
import org.linphone.core.Event;
import org.linphone.core.EventLog;
import org.linphone.core.LimeState;
import org.linphone.mediastream.Log;
import org.linphone.ui.ListSelectionAdapter;
import org.linphone.ui.ListSelectionHelper;
import org.linphone.ui.SelectableAdapter;
import org.linphone.ui.SelectableHelper;
import java.io.File;
import java.io.FileNotFoundException;
@ -73,26 +73,29 @@ import java.util.List;
import static android.content.Intent.FLAG_GRANT_READ_URI_PERMISSION;
public class ChatEventsAdapter extends ListSelectionAdapter {
public class ChatEventsAdapter extends SelectableAdapter<ChatBubbleViewHolder> {
private static int MARGIN_BETWEEN_MESSAGES = 10;
private static int SIDE_MARGIN = 100;
private Context mContext;
private List<EventLog> mHistory;
private List<LinphoneContact> mParticipants;
private LayoutInflater mLayoutInflater;
private int mItemResource;
private Bitmap mDefaultBitmap;
private GroupChatFragment mFragment;
private ChatMessageListenerStub mListener;
public ChatEventsAdapter(GroupChatFragment fragment, ListSelectionHelper helper, LayoutInflater inflater, EventLog[] history, ArrayList<LinphoneContact> participants) {
super(helper);
mFragment = fragment;
mContext = mFragment.getActivity();
mLayoutInflater = inflater;
mHistory = new ArrayList<>(Arrays.asList(history));
mParticipants = participants;
private ChatBubbleViewHolder.ClickListener mClickListener;
public ChatEventsAdapter(GroupChatFragment fragment, SelectableHelper helper, int itemResource, EventLog[] history, ArrayList<LinphoneContact> participants, ChatBubbleViewHolder.ClickListener clickListener) {
super(helper);
mFragment = fragment;
mContext = mFragment.getActivity();
mItemResource = itemResource;
mHistory = new ArrayList<>(Arrays.asList(history));
mParticipants = participants;
mClickListener = clickListener;
mListener = new ChatMessageListenerStub() {
@Override
public void onFileTransferProgressIndication(ChatMessage message, Content content, int offset, int total) {
@ -124,6 +127,284 @@ public class ChatEventsAdapter extends ListSelectionAdapter {
};
}
@Override
public ChatBubbleViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
View v = LayoutInflater.from(parent.getContext())
.inflate(this.mItemResource, parent, false);
ChatBubbleViewHolder VH = new ChatBubbleViewHolder(this.mContext,v, mClickListener);
//Allows onLongClick ContextMenu on bubbles
mFragment.registerForContextMenu(v);
v.setTag(VH);
return VH;
}
@Override
public void onBindViewHolder(@NonNull ChatBubbleViewHolder holder, int position) {
final EventLog event = this.mHistory.get(position);
holder.eventLayout.setVisibility(View.GONE);
holder.bubbleLayout.setVisibility(View.GONE);
holder.delete.setVisibility(isEditionEnabled() ? View.VISIBLE : View.GONE);
holder.messageText.setVisibility(View.GONE);
holder.messageImage.setVisibility(View.GONE);
holder.fileTransferLayout.setVisibility(View.GONE);
holder.fileTransferProgressBar.setProgress(0);
holder.fileTransferAction.setEnabled(true);
holder.fileName.setVisibility(View.GONE);
holder.openFileButton.setVisibility(View.GONE);
holder.messageStatus.setVisibility(View.INVISIBLE);
holder.messageSendingInProgress.setVisibility(View.GONE);
holder.imdmLayout.setVisibility(View.INVISIBLE);
holder.contactPicture.setImageBitmap(ContactsManager.getInstance().getDefaultAvatarBitmap());
if (isEditionEnabled()) {
holder.delete.setOnCheckedChangeListener(null);
holder.delete.setChecked(isSelected(position));
holder.delete.setTag(position);
}
if (event.getType() == EventLog.Type.ConferenceChatMessage) {
holder.bubbleLayout.setVisibility(View.VISIBLE);
final ChatMessage message = event.getChatMessage();
if (position > 0 && mContext.getResources().getBoolean(R.bool.lower_space_between_chat_bubbles_if_same_person)) {
EventLog previousEvent = (EventLog)getItem(position-1);
if (previousEvent.getType() == EventLog.Type.ConferenceChatMessage) {
ChatMessage previousMessage = previousEvent.getChatMessage();
if (previousMessage.getFromAddress().weakEqual(message.getFromAddress())) {
holder.separatorLayout.setVisibility(View.GONE);
}
} else {
// No separator if previous event is not a message
holder.separatorLayout.setVisibility(View.GONE);
}
}
holder.messageId = message.getMessageId();
message.setUserData(holder);
RelativeLayout.LayoutParams layoutParams = new RelativeLayout.LayoutParams(RelativeLayout.LayoutParams.WRAP_CONTENT, RelativeLayout.LayoutParams.WRAP_CONTENT);
ChatMessage.State status = message.getState();
Address remoteSender = message.getFromAddress();
String displayName;
LinphoneContact contact = null;
if (message.isOutgoing()) {
message.setListener(mListener);
if (status == ChatMessage.State.InProgress) {
holder.messageSendingInProgress.setVisibility(View.VISIBLE);
}
if (!message.isSecured() && LinphoneManager.getLc().limeEnabled() == LimeState.Mandatory && status != ChatMessage.State.InProgress) {
holder.messageStatus.setVisibility(View.VISIBLE);
holder.messageStatus.setImageResource(R.drawable.chat_unsecure);
}
if (status == ChatMessage.State.Delivered) {
/*holder.imdmLayout.setVisibility(View.VISIBLE);
holder.imdmLabel.setText(R.string.sent);
holder.imdmIcon.setImageResource(R.drawable.chat_delivered);
holder.imdmLabel.setTextColor(mContext.getResources().getColor(R.color.colorD));*/
} else if (status == ChatMessage.State.DeliveredToUser) {
holder.imdmLayout.setVisibility(View.VISIBLE);
holder.imdmIcon.setImageResource(R.drawable.chat_delivered);
holder.imdmLabel.setText(R.string.delivered);
holder.imdmLabel.setTextColor(mContext.getResources().getColor(R.color.colorD));
} else if (status == ChatMessage.State.Displayed) {
holder.imdmLayout.setVisibility(View.VISIBLE);
holder.imdmIcon.setImageResource(R.drawable.chat_read);
holder.imdmLabel.setText(R.string.displayed);
holder.imdmLabel.setTextColor(mContext.getResources().getColor(R.color.colorK));
} else if (status == ChatMessage.State.NotDelivered) {
holder.imdmLayout.setVisibility(View.VISIBLE);
holder.imdmIcon.setImageResource(R.drawable.chat_error);
holder.imdmLabel.setText(R.string.error);
holder.imdmLabel.setTextColor(mContext.getResources().getColor(R.color.colorI));
} else if (status == ChatMessage.State.FileTransferError) {
holder.imdmLayout.setVisibility(View.VISIBLE);
holder.imdmIcon.setImageResource(R.drawable.chat_error);
holder.imdmLabel.setText(R.string.file_transfer_error);
holder.imdmLabel.setTextColor(mContext.getResources().getColor(R.color.colorI));
}
//layoutParams allow bubbles alignment during selection mode
if (isEditionEnabled()) {
layoutParams.addRule(RelativeLayout.LEFT_OF, holder.delete.getId());
layoutParams.setMargins(SIDE_MARGIN, MARGIN_BETWEEN_MESSAGES/2, 0, MARGIN_BETWEEN_MESSAGES/2);
} else {
layoutParams.addRule(RelativeLayout.ALIGN_PARENT_RIGHT);
layoutParams.setMargins(SIDE_MARGIN, MARGIN_BETWEEN_MESSAGES/2, 0, MARGIN_BETWEEN_MESSAGES/2);
}
holder.background.setBackgroundResource(R.drawable.resizable_chat_bubble_outgoing);
Compatibility.setTextAppearance(holder.contactName, mContext, R.style.font3);
Compatibility.setTextAppearance(holder.fileTransferAction, mContext, R.style.font15);
holder.fileTransferAction.setBackgroundResource(R.drawable.resizable_confirm_delete_button);
holder.contactPictureMask.setImageResource(R.drawable.avatar_chat_mask_outgoing);
} else {
for (LinphoneContact c : mParticipants) {
if (c != null && c.hasAddress(remoteSender.asStringUriOnly())) {
contact = c;
break;
}
}
if (isEditionEnabled()) {
layoutParams.addRule(RelativeLayout.LEFT_OF, holder.delete.getId());
layoutParams.setMargins(SIDE_MARGIN, MARGIN_BETWEEN_MESSAGES/2, 0, MARGIN_BETWEEN_MESSAGES/2);
} else {
layoutParams.addRule(RelativeLayout.ALIGN_PARENT_LEFT);
layoutParams.setMargins(0, MARGIN_BETWEEN_MESSAGES/2, SIDE_MARGIN, MARGIN_BETWEEN_MESSAGES/2);
}
holder.background.setBackgroundResource(R.drawable.resizable_chat_bubble_incoming);
Compatibility.setTextAppearance(holder.contactName, mContext, R.style.font9);
Compatibility.setTextAppearance(holder.fileTransferAction, mContext, R.style.font8);
holder.fileTransferAction.setBackgroundResource(R.drawable.resizable_assistant_button);
holder.contactPictureMask.setImageResource(R.drawable.avatar_chat_mask);
}
if (contact == null) {
contact = ContactsManager.getInstance().findContactFromAddress(remoteSender);
}
if (contact != null) {
if (contact.getFullName() != null) {
displayName = contact.getFullName();
} else {
displayName = LinphoneUtils.getAddressDisplayName(remoteSender);
}
holder.contactPicture.setImageBitmap(ContactsManager.getInstance().getDefaultAvatarBitmap());
if (contact.hasPhoto()) {
LinphoneUtils.setThumbnailPictureFromUri(LinphoneActivity.instance(), holder.contactPicture, contact.getThumbnailUri());
}
} else {
displayName = LinphoneUtils.getAddressDisplayName(remoteSender);
holder.contactPicture.setImageBitmap(ContactsManager.getInstance().getDefaultAvatarBitmap());
}
holder.contactName.setText(LinphoneUtils.timestampToHumanDate(mContext, message.getTime(), R.string.messages_date_format) + " - " + displayName);
if (message.hasTextContent()) {
String msg = message.getTextContent();
Spanned text = LinphoneUtils.getTextWithHttpLinks(msg);
holder.messageText.setText(text);
holder.messageText.setMovementMethod(LinkMovementMethod.getInstance());
holder.messageText.setVisibility(View.VISIBLE);
}
String externalBodyUrl = message.getExternalBodyUrl();
Content fileTransferContent = message.getFileTransferInformation();
if (message.getAppdata() != null) { // Something to display
displayAttachedFile(message, holder);
}
if (externalBodyUrl != null) { // Incoming file transfer not yet downloaded
holder.fileName.setVisibility(View.VISIBLE);
holder.fileName.setText(fileTransferContent.getName());
holder.fileTransferLayout.setVisibility(View.VISIBLE);
holder.fileTransferProgressBar.setVisibility(View.GONE);
if (message.isFileTransferInProgress()) { // Incoming file transfer in progress
holder.fileTransferAction.setVisibility(View.GONE);
} else {
holder.fileTransferAction.setText(mContext.getString(R.string.accept));
holder.fileTransferAction.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
if (mContext.getPackageManager().checkPermission(Manifest.permission.WRITE_EXTERNAL_STORAGE, mContext.getPackageName()) == PackageManager.PERMISSION_GRANTED) {
v.setEnabled(false);
String filename = message.getFileTransferInformation().getName();
File file = new File(Environment.getExternalStorageDirectory(), filename);
int prefix = 1;
while (file.exists()) {
file = new File(Environment.getExternalStorageDirectory(), prefix + "_" + filename);
Log.w("File with that name already exists, renamed to " + prefix + "_" + filename);
prefix += 1;
}
message.setListener(mListener);
message.setFileTransferFilepath(file.getPath());
message.downloadFile();
} else {
Log.w("WRITE_EXTERNAL_STORAGE permission not granted, won't be able to store the downloaded file");
LinphoneActivity.instance().checkAndRequestExternalStoragePermission();
}
}
});
}
} else if (message.isFileTransferInProgress()) { // Outgoing file transfer in progress
message.setListener(mListener); // add the listener for file upload progress display
holder.messageSendingInProgress.setVisibility(View.GONE);
holder.fileTransferLayout.setVisibility(View.VISIBLE);
holder.fileTransferAction.setText(mContext.getString(R.string.cancel));
holder.fileTransferAction.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
message.cancelFileTransfer();
notifyDataSetChanged();
}
});
}
holder.bubbleLayout.setLayoutParams(layoutParams);
} else { // Event is not chat message
holder.eventLayout.setVisibility(View.VISIBLE);
Address address = event.getParticipantAddress();
String displayName = null;
if (address != null) {
LinphoneContact contact = ContactsManager.getInstance().findContactFromAddress(address);
if (contact != null) {
displayName = contact.getFullName();
} else {
displayName = LinphoneUtils.getAddressDisplayName(address);
}
}
switch (event.getType()) {
case ConferenceCreated:
holder.eventMessage.setText(mContext.getString(R.string.conference_created));
break;
case ConferenceTerminated:
holder.eventMessage.setText(mContext.getString(R.string.conference_destroyed));
break;
case ConferenceParticipantAdded:
holder.eventMessage.setText(mContext.getString(R.string.participant_added).replace("%s", displayName));
break;
case ConferenceParticipantRemoved:
holder.eventMessage.setText(mContext.getString(R.string.participant_removed).replace("%s", displayName));
break;
case ConferenceSubjectChanged:
holder.eventMessage.setText(mContext.getString(R.string.subject_changed).replace("%s", event.getSubject()));
break;
case ConferenceParticipantSetAdmin:
holder.eventMessage.setText(mContext.getString(R.string.admin_set).replace("%s", displayName));
break;
case ConferenceParticipantUnsetAdmin:
holder.eventMessage.setText(mContext.getString(R.string.admin_unset).replace("%s", displayName));
break;
case ConferenceParticipantDeviceAdded:
holder.eventMessage.setText(mContext.getString(R.string.device_added).replace("%s", displayName));
break;
case ConferenceParticipantDeviceRemoved:
holder.eventMessage.setText(mContext.getString(R.string.device_removed).replace("%s", displayName));
break;
case None:
default:
//TODO
break;
}
}
}
@Override
public int getItemCount() {
return this.mHistory.size();
}
public void addToHistory(EventLog log) {
mHistory.add(log);
notifyDataSetChanged();
@ -148,299 +429,19 @@ public class ChatEventsAdapter extends ListSelectionAdapter {
mHistory.clear();
}
@Override
public int getCount() {
return mHistory.size();
return this.mHistory.size();
}
@Override
public Object getItem(int i) {
return mHistory.get(i);
}
@Override
public long getItemId(int i) {
return i;
}
public void removeItem(int i) {
public void removeItem(int i) {
mHistory.remove(i);
notifyDataSetChanged();
}
@Override
public View getView(int i, View view, ViewGroup viewGroup) {
ChatBubbleViewHolder holder;
if (view != null) {
holder = (ChatBubbleViewHolder) view.getTag();
} else {
view = mLayoutInflater.inflate(R.layout.chat_bubble, null);
holder = new ChatBubbleViewHolder(view);
view.setTag(holder);
}
holder.eventLayout.setVisibility(View.GONE);
holder.bubbleLayout.setVisibility(View.GONE);
holder.separatorLayout.setVisibility(i == 0 ? View.GONE : View.VISIBLE); // Hide separator if first item in list
holder.delete.setVisibility(isEditionEnabled() ? View.VISIBLE : View.GONE);
holder.messageText.setVisibility(View.GONE);
holder.messageImage.setVisibility(View.GONE);
holder.fileTransferLayout.setVisibility(View.GONE);
holder.fileTransferProgressBar.setProgress(0);
holder.fileTransferAction.setEnabled(true);
holder.fileName.setVisibility(View.GONE);
holder.openFileButton.setVisibility(View.GONE);
holder.messageStatus.setVisibility(View.INVISIBLE);
holder.messageSendingInProgress.setVisibility(View.GONE);
holder.imdmLayout.setVisibility(View.INVISIBLE);
holder.contactPicture.setImageBitmap(ContactsManager.getInstance().getDefaultAvatarBitmap());
if (isEditionEnabled()) {
holder.delete.setOnCheckedChangeListener(null);
holder.delete.setChecked(getSelectedItemsPosition().contains(i));
holder.delete.setTag(i);
holder.delete.setOnCheckedChangeListener(getDeleteListener());
}
EventLog event = (EventLog)getItem(i);
if (event.getType() == EventLog.Type.ConferenceChatMessage) {
holder.bubbleLayout.setVisibility(View.VISIBLE);
final ChatMessage message = event.getChatMessage();
if (i > 0 && mContext.getResources().getBoolean(R.bool.lower_space_between_chat_bubbles_if_same_person)) {
EventLog previousEvent = (EventLog)getItem(i-1);
if (previousEvent.getType() == EventLog.Type.ConferenceChatMessage) {
ChatMessage previousMessage = previousEvent.getChatMessage();
if (previousMessage.getFromAddress().weakEqual(message.getFromAddress())) {
holder.separatorLayout.setVisibility(View.GONE);
}
} else {
// No separator if previous event is not a message
holder.separatorLayout.setVisibility(View.GONE);
}
}
holder.messageId = message.getMessageId();
message.setUserData(holder);
RelativeLayout.LayoutParams layoutParams = new RelativeLayout.LayoutParams(RelativeLayout.LayoutParams.WRAP_CONTENT, RelativeLayout.LayoutParams.WRAP_CONTENT);
ChatMessage.State status = message.getState();
Address remoteSender = message.getFromAddress();
String displayName;
LinphoneContact contact = null;
if (message.isOutgoing()) {
message.setListener(mListener);
if (status == ChatMessage.State.InProgress) {
holder.messageSendingInProgress.setVisibility(View.VISIBLE);
}
if (!message.isSecured() && LinphoneManager.getLc().limeEnabled() == LimeState.Mandatory && status != ChatMessage.State.InProgress) {
holder.messageStatus.setVisibility(View.VISIBLE);
holder.messageStatus.setImageResource(R.drawable.chat_unsecure);
}
if (status == ChatMessage.State.Delivered) {
/*holder.imdmLayout.setVisibility(View.VISIBLE);
holder.imdmLabel.setText(R.string.sent);
holder.imdmIcon.setImageResource(R.drawable.chat_delivered);
holder.imdmLabel.setTextColor(mContext.getResources().getColor(R.color.colorD));*/
} else if (status == ChatMessage.State.DeliveredToUser) {
holder.imdmLayout.setVisibility(View.VISIBLE);
holder.imdmIcon.setImageResource(R.drawable.chat_delivered);
holder.imdmLabel.setText(R.string.delivered);
holder.imdmLabel.setTextColor(mContext.getResources().getColor(R.color.colorD));
} else if (status == ChatMessage.State.Displayed) {
holder.imdmLayout.setVisibility(View.VISIBLE);
holder.imdmIcon.setImageResource(R.drawable.chat_read);
holder.imdmLabel.setText(R.string.displayed);
holder.imdmLabel.setTextColor(mContext.getResources().getColor(R.color.colorK));
} else if (status == ChatMessage.State.NotDelivered) {
holder.imdmLayout.setVisibility(View.VISIBLE);
holder.imdmIcon.setImageResource(R.drawable.chat_error);
holder.imdmLabel.setText(R.string.error);
holder.imdmLabel.setTextColor(mContext.getResources().getColor(R.color.colorI));
} else if (status == ChatMessage.State.FileTransferError) {
holder.imdmLayout.setVisibility(View.VISIBLE);
holder.imdmIcon.setImageResource(R.drawable.chat_error);
holder.imdmLabel.setText(R.string.file_transfer_error);
holder.imdmLabel.setTextColor(mContext.getResources().getColor(R.color.colorI));
}
if (isEditionEnabled()) {
layoutParams.addRule(RelativeLayout.LEFT_OF, holder.delete.getId());
layoutParams.setMargins(SIDE_MARGIN, MARGIN_BETWEEN_MESSAGES/2, 0, MARGIN_BETWEEN_MESSAGES/2);
} else {
layoutParams.addRule(RelativeLayout.ALIGN_PARENT_RIGHT);
layoutParams.setMargins(SIDE_MARGIN, MARGIN_BETWEEN_MESSAGES/2, 0, MARGIN_BETWEEN_MESSAGES/2);
}
holder.background.setBackgroundResource(R.drawable.resizable_chat_bubble_outgoing);
Compatibility.setTextAppearance(holder.contactName, mContext, R.style.font3);
Compatibility.setTextAppearance(holder.fileTransferAction, mContext, R.style.font15);
holder.fileTransferAction.setBackgroundResource(R.drawable.resizable_confirm_delete_button);
holder.contactPictureMask.setImageResource(R.drawable.avatar_chat_mask_outgoing);
} else {
for (LinphoneContact c : mParticipants) {
if (c != null && c.hasAddress(remoteSender.asStringUriOnly())) {
contact = c;
break;
}
}
if (isEditionEnabled()) {
layoutParams.addRule(RelativeLayout.LEFT_OF, holder.delete.getId());
layoutParams.setMargins(SIDE_MARGIN, MARGIN_BETWEEN_MESSAGES/2, 0, MARGIN_BETWEEN_MESSAGES/2);
} else {
layoutParams.addRule(RelativeLayout.ALIGN_PARENT_LEFT);
layoutParams.setMargins(0, MARGIN_BETWEEN_MESSAGES/2, SIDE_MARGIN, MARGIN_BETWEEN_MESSAGES/2);
}
holder.background.setBackgroundResource(R.drawable.resizable_chat_bubble_incoming);
Compatibility.setTextAppearance(holder.contactName, mContext, R.style.font9);
Compatibility.setTextAppearance(holder.fileTransferAction, mContext, R.style.font8);
holder.fileTransferAction.setBackgroundResource(R.drawable.resizable_assistant_button);
holder.contactPictureMask.setImageResource(R.drawable.avatar_chat_mask);
}
if (contact == null) {
contact = ContactsManager.getInstance().findContactFromAddress(remoteSender);
}
if (contact != null) {
if (contact.getFullName() != null) {
displayName = contact.getFullName();
} else {
displayName = LinphoneUtils.getAddressDisplayName(remoteSender);
}
holder.contactPicture.setImageBitmap(ContactsManager.getInstance().getDefaultAvatarBitmap());
if (contact.hasPhoto()) {
LinphoneUtils.setThumbnailPictureFromUri(LinphoneActivity.instance(), holder.contactPicture, contact.getThumbnailUri());
}
} else {
displayName = LinphoneUtils.getAddressDisplayName(remoteSender);
holder.contactPicture.setImageBitmap(ContactsManager.getInstance().getDefaultAvatarBitmap());
}
holder.contactName.setText(LinphoneUtils.timestampToHumanDate(mContext, message.getTime(), R.string.messages_date_format) + " - " + displayName);
if (message.hasTextContent()) {
String msg = message.getTextContent();
Spanned text = LinphoneUtils.getTextWithHttpLinks(msg);
holder.messageText.setText(text);
holder.messageText.setMovementMethod(LinkMovementMethod.getInstance());
holder.messageText.setVisibility(View.VISIBLE);
}
String externalBodyUrl = message.getExternalBodyUrl();
Content fileTransferContent = message.getFileTransferInformation();
if (message.getAppdata() != null) { // Something to display
displayAttachedFile(message, holder);
}
if (externalBodyUrl != null) { // Incoming file transfer not yet downloaded
holder.fileName.setVisibility(View.VISIBLE);
holder.fileName.setText(fileTransferContent.getName());
holder.fileTransferLayout.setVisibility(View.VISIBLE);
holder.fileTransferProgressBar.setVisibility(View.GONE);
if (message.isFileTransferInProgress()) { // Incoming file transfer in progress
holder.fileTransferAction.setVisibility(View.GONE);
} else {
holder.fileTransferAction.setText(mContext.getString(R.string.accept));
holder.fileTransferAction.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
if (mContext.getPackageManager().checkPermission(Manifest.permission.WRITE_EXTERNAL_STORAGE, mContext.getPackageName()) == PackageManager.PERMISSION_GRANTED) {
v.setEnabled(false);
String filename = message.getFileTransferInformation().getName();
File file = new File(Environment.getExternalStorageDirectory(), filename);
int prefix = 1;
while (file.exists()) {
file = new File(Environment.getExternalStorageDirectory(), prefix + "_" + filename);
Log.w("File with that name already exists, renamed to " + prefix + "_" + filename);
prefix += 1;
}
message.setListener(mListener);
message.setFileTransferFilepath(file.getPath());
message.downloadFile();
} else {
Log.w("WRITE_EXTERNAL_STORAGE permission not granted, won't be able to store the downloaded file");
LinphoneActivity.instance().checkAndRequestExternalStoragePermission();
}
}
});
}
} else if (message.isFileTransferInProgress()) { // Outgoing file transfer in progress
message.setListener(mListener); // add the listener for file upload progress display
holder.messageSendingInProgress.setVisibility(View.GONE);
holder.fileTransferLayout.setVisibility(View.VISIBLE);
holder.fileTransferAction.setText(mContext.getString(R.string.cancel));
holder.fileTransferAction.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
message.cancelFileTransfer();
notifyDataSetChanged();
}
});
}
holder.bubbleLayout.setLayoutParams(layoutParams);
} else { // Event is not chat message
holder.eventLayout.setVisibility(View.VISIBLE);
Address address = event.getParticipantAddress();
String displayName = null;
if (address != null) {
LinphoneContact contact = ContactsManager.getInstance().findContactFromAddress(address);
if (contact != null) {
displayName = contact.getFullName();
} else {
displayName = LinphoneUtils.getAddressDisplayName(address);
}
}
switch (event.getType()) {
case ConferenceCreated:
holder.eventMessage.setText(mContext.getString(R.string.conference_created));
break;
case ConferenceTerminated:
holder.eventMessage.setText(mContext.getString(R.string.conference_destroyed));
break;
case ConferenceParticipantAdded:
holder.eventMessage.setText(mContext.getString(R.string.participant_added).replace("%s", displayName));
break;
case ConferenceParticipantRemoved:
holder.eventMessage.setText(mContext.getString(R.string.participant_removed).replace("%s", displayName));
break;
case ConferenceSubjectChanged:
holder.eventMessage.setText(mContext.getString(R.string.subject_changed).replace("%s", event.getSubject()));
break;
case ConferenceParticipantSetAdmin:
holder.eventMessage.setText(mContext.getString(R.string.admin_set).replace("%s", displayName));
break;
case ConferenceParticipantUnsetAdmin:
holder.eventMessage.setText(mContext.getString(R.string.admin_unset).replace("%s", displayName));
break;
case ConferenceParticipantDeviceAdded:
holder.eventMessage.setText(mContext.getString(R.string.device_added).replace("%s", displayName));
break;
case ConferenceParticipantDeviceRemoved:
holder.eventMessage.setText(mContext.getString(R.string.device_removed).replace("%s", displayName));
break;
case None:
default:
//TODO
break;
}
}
return view;
}
private void loadBitmap(String path, ImageView imageView) {
if (cancelPotentialWork(path, imageView)) {
if (LinphoneUtils.isExtensionImage(path)) {

View file

@ -20,68 +20,82 @@ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
package org.linphone.chat;
import android.app.Fragment;
import android.content.Context;
import android.os.Bundle;
import android.support.v7.widget.DividerItemDecoration;
import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.RecyclerView;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.AdapterView;
import android.widget.AdapterView.OnItemClickListener;
import android.widget.ImageView;
import android.widget.ListView;
import android.widget.RelativeLayout;
import android.widget.TextView;
import org.linphone.contacts.ContactsManager;
import org.linphone.core.ChatRoomListenerStub;
import org.linphone.core.EventLog;
import org.linphone.mediastream.Log;
import org.linphone.ui.ListSelectionHelper;
import org.linphone.contacts.ContactsUpdatedListener;
import org.linphone.fragments.FragmentsAvailable;
import org.linphone.activities.LinphoneActivity;
import org.linphone.LinphoneManager;
import org.linphone.R;
import org.linphone.activities.LinphoneActivity;
import org.linphone.contacts.ContactsManager;
import org.linphone.contacts.ContactsUpdatedListener;
import org.linphone.core.ChatMessage;
import org.linphone.core.ChatRoom;
import org.linphone.core.ChatRoomListenerStub;
import org.linphone.core.Core;
import org.linphone.core.CoreListenerStub;
import org.linphone.core.EventLog;
import org.linphone.fragments.FragmentsAvailable;
import org.linphone.ui.SelectableHelper;
import java.io.File;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import static org.linphone.fragments.FragmentsAvailable.CHAT_LIST;
public class ChatListFragment extends Fragment implements ContactsUpdatedListener, ListSelectionHelper.DeleteListener {
private LayoutInflater mInflater;
private ListView mChatRoomsList;
private TextView mNoChatHistory;
public class ChatListFragment extends Fragment implements ContactsUpdatedListener, ChatRoomsAdapter.ChatRoomViewHolder.ClickListener, SelectableHelper.DeleteListener {
private RecyclerView mChatRoomsList;
private ImageView mNewDiscussionButton, mBackToCallButton;
private ChatRoomsAdapter mChatRoomsAdapter;
private CoreListenerStub mListener;
private ListSelectionHelper mSelectionHelper;
private RelativeLayout mWaitLayout;
private int mChatRoomDeletionPendingCount;
private ChatRoomListenerStub mChatRoomListener;
private Context mContext;
public List<ChatRoom> mRooms;
private SelectableHelper mSelectionHelper;
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
mInflater = inflater;
public View onCreateView(final LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mRooms = new ArrayList<>(Arrays.asList(LinphoneManager.getLc().getChatRooms()));
this.mContext = getActivity().getApplicationContext();
View view = inflater.inflate(R.layout.chatlist, container, false);
mSelectionHelper = new ListSelectionHelper(view, this);
mChatRoomsAdapter = new ChatRoomsAdapter(getActivity(), mSelectionHelper, mInflater);
mChatRoomsList = view.findViewById(R.id.chatList);
mWaitLayout = view.findViewById(R.id.waitScreen);
mNewDiscussionButton = view.findViewById(R.id.new_discussion);
mBackToCallButton = view.findViewById(R.id.back_in_call);
mSelectionHelper = new SelectableHelper(view, this);
mChatRoomsAdapter = new ChatRoomsAdapter(mContext, R.layout.chatlist_cell, mRooms,this, mSelectionHelper);
mChatRoomsList.setAdapter(mChatRoomsAdapter);
mSelectionHelper.setAdapter(mChatRoomsAdapter);
mSelectionHelper.setDialogMessage(R.string.chat_room_delete_dialog);
mWaitLayout = view.findViewById(R.id.waitScreen);
RecyclerView.LayoutManager layoutManager = new LinearLayoutManager(mContext);
mChatRoomsList.setLayoutManager(layoutManager);
DividerItemDecoration dividerItemDecoration = new DividerItemDecoration(mChatRoomsList.getContext(),
((LinearLayoutManager) layoutManager).getOrientation());
dividerItemDecoration.setDrawable(getActivity().getApplicationContext().getResources().getDrawable(R.drawable.divider));
mChatRoomsList.addItemDecoration(dividerItemDecoration);
mWaitLayout.setVisibility(View.GONE);
mChatRoomsList = view.findViewById(R.id.chatList);
mChatRoomsList.setAdapter(mChatRoomsAdapter);
mNoChatHistory = view.findViewById(R.id.noChatHistory);
mNoChatHistory.setVisibility(View.GONE);
mNewDiscussionButton = view.findViewById(R.id.new_discussion);
mNewDiscussionButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
@ -89,7 +103,6 @@ public class ChatListFragment extends Fragment implements ContactsUpdatedListene
}
});
mBackToCallButton = view.findViewById(R.id.back_in_call);
mBackToCallButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
@ -129,18 +142,36 @@ public class ChatListFragment extends Fragment implements ContactsUpdatedListene
}
}
};
return view;
}
@Override
public void onItemClicked(int position) {
if (mChatRoomsAdapter.isEditionEnabled()) {
mChatRoomsAdapter.toggleSelection(position);
} else {
ChatRoom room = (ChatRoom) mChatRoomsAdapter.getItem(position);
LinphoneActivity.instance().goToChat(room.getPeerAddress().asString(),null);
}
}
@Override
public boolean onItemLongClicked(int position) {
if (!mChatRoomsAdapter.isEditionEnabled()) {
mSelectionHelper.enterEditionMode();
}
mChatRoomsAdapter.toggleSelection(position);
return true;
}
private void refreshChatRoomsList() {
mChatRoomsAdapter.refresh();
mNoChatHistory.setVisibility(mChatRoomsAdapter.getCount() == 0 ? View.VISIBLE : View.GONE);
//mNoChatHistory.setVisibility(mChatRoomsAdapter.getItemCount() == 0 ? View.VISIBLE : View.GONE);
}
public void displayFirstChat() {
ChatRoomsAdapter adapter = (ChatRoomsAdapter)mChatRoomsList.getAdapter();
if (adapter != null && adapter.getCount() > 0) {
if (adapter != null && adapter.getItemCount() > 0) {
ChatRoom room = (ChatRoom) adapter.getItem(0);
LinphoneActivity.instance().goToChat(room.getPeerAddress().asStringUriOnly(), null);
} else {
@ -156,7 +187,7 @@ public class ChatListFragment extends Fragment implements ContactsUpdatedListene
if (LinphoneManager.getLc().getCallsNb() > 0) {
mBackToCallButton.setVisibility(View.VISIBLE);
} else {
mBackToCallButton.setVisibility(View.INVISIBLE);
mBackToCallButton.setVisibility(View.GONE);
}
if (LinphoneActivity.isInstanciated()) {
@ -181,6 +212,7 @@ public class ChatListFragment extends Fragment implements ContactsUpdatedListene
ContactsManager.removeContactsListener(this);
mChatRoomsAdapter.clear();
super.onPause();
}
@Override
@ -208,7 +240,6 @@ public class ChatListFragment extends Fragment implements ContactsUpdatedListene
if (mChatRoomDeletionPendingCount > 0) {
mWaitLayout.setVisibility(View.VISIBLE);
}
LinphoneActivity.instance().refreshMissedChatCountDisplay();
}
@Override
@ -218,9 +249,7 @@ public class ChatListFragment extends Fragment implements ContactsUpdatedListene
ChatRoomsAdapter adapter = (ChatRoomsAdapter) mChatRoomsList.getAdapter();
if (adapter != null) {
adapter.notifyDataSetInvalidated();
adapter.notifyDataSetChanged();
}
}
}
}

View file

@ -21,8 +21,7 @@ package org.linphone.chat;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Typeface;
import android.support.v7.widget.RecyclerView;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
@ -37,13 +36,11 @@ import org.linphone.activities.LinphoneActivity;
import org.linphone.contacts.ContactsManager;
import org.linphone.contacts.LinphoneContact;
import org.linphone.core.Address;
import org.linphone.core.ChatMessage;
import org.linphone.core.ChatRoom;
import org.linphone.core.ChatRoomCapabilities;
import org.linphone.core.ChatRoomListenerStub;
import org.linphone.core.EventLog;
import org.linphone.ui.ListSelectionAdapter;
import org.linphone.ui.ListSelectionHelper;
import org.linphone.ui.SelectableAdapter;
import org.linphone.ui.SelectableHelper;
import java.util.ArrayList;
import java.util.Arrays;
@ -51,9 +48,9 @@ import java.util.Collections;
import java.util.Comparator;
import java.util.List;
public class ChatRoomsAdapter extends ListSelectionAdapter {
public class ChatRoomsAdapter extends SelectableAdapter<ChatRoomsAdapter.ChatRoomViewHolder> {
private class ChatRoomViewHolder {
public static class ChatRoomViewHolder extends RecyclerView.ViewHolder implements View.OnClickListener, View.OnLongClickListener{
public TextView lastMessageSenderView;
public TextView lastMessageView;
public TextView date;
@ -61,39 +58,133 @@ public class ChatRoomsAdapter extends ListSelectionAdapter {
public TextView unreadMessages;
public CheckBox delete;
public ImageView contactPicture;
public Context mContext;
public ChatRoom mRoom;
public ClickListener listener;
public ChatRoomViewHolder(View view) {
lastMessageSenderView = view.findViewById(R.id.lastMessageSender);
lastMessageView = view.findViewById(R.id.lastMessage);
date = view.findViewById(R.id.date);
displayName = view.findViewById(R.id.sipUri);
unreadMessages = view.findViewById(R.id.unreadMessages);
delete = view.findViewById(R.id.delete_chatroom);
contactPicture = view.findViewById(R.id.contact_picture);
public ChatRoomViewHolder(Context context,View itemView, ClickListener listener) {
super(itemView);
this.mContext= context;
this.lastMessageSenderView = itemView.findViewById(R.id.lastMessageSender);
this.lastMessageView = itemView.findViewById(R.id.lastMessage);
this.date = itemView.findViewById(R.id.date);
this.displayName = itemView.findViewById(R.id.sipUri);
this.unreadMessages = itemView.findViewById(R.id.unreadMessages);
this.delete = itemView.findViewById(R.id.delete_chatroom);
this.contactPicture = itemView.findViewById(R.id.contact_picture);
this.listener = listener;
itemView.setOnClickListener(this);
itemView.setOnLongClickListener(this);
}
public void bindChatRoom(ChatRoom room) {
this.mRoom = room;
this.lastMessageSenderView.setText(getSender(mRoom));
this.lastMessageView.setText(mRoom.getLastMessageInHistory() != null ? mRoom.getLastMessageInHistory().getTextContent(): "");
this.date.setText(mRoom.getLastMessageInHistory()!=null ? LinphoneUtils.timestampToHumanDate(this.mContext, mRoom.getLastUpdateTime(), R.string.messages_list_date_format) : "");
this.displayName.setText(getContact(mRoom));
this.unreadMessages.setText(String.valueOf(LinphoneManager.getInstance().getUnreadCountForChatRoom(mRoom)));
getAvatar(mRoom);
}
public void onClick(View v) {
if (listener != null) {
listener.onItemClicked(getAdapterPosition());
}
}
public boolean onLongClick(View v) {
if (listener != null) {
return listener.onItemLongClicked(getAdapterPosition());
}
return false;
}
public String getSender(ChatRoom mRoom){
if (mRoom.getLastMessageInHistory() != null) {
LinphoneContact contact = ContactsManager.getInstance().findContactFromAddress(mRoom.getLastMessageInHistory().getFromAddress());
if (contact != null) {
return (contact.getFullName() + mContext.getString(R.string.separator));
}
return (LinphoneUtils.getAddressDisplayName(mRoom.getLastMessageInHistory().getFromAddress()) + ":");
}
return null;
}
public String getContact(ChatRoom mRoom) {
Address contactAddress = mRoom.getPeerAddress();
if (mRoom.hasCapability(ChatRoomCapabilities.OneToOne.toInt()) && mRoom.getParticipants().length > 0) {
contactAddress = mRoom.getParticipants()[0].getAddress();
}
if (mRoom.hasCapability(ChatRoomCapabilities.OneToOne.toInt())) {
LinphoneContact contact;
if (mRoom.getParticipants().length > 0) {
contact = ContactsManager.getInstance().findContactFromAddress(mRoom.getParticipants()[0].getAddress());
if (contact != null) {
return (contact.getFullName());
}
return (LinphoneUtils.getAddressDisplayName(mRoom.getParticipants()[0].getAddress()));
} else {
contact = ContactsManager.getInstance().findContactFromAddress(contactAddress);
if (contact != null) {
return (contact.getFullName());
}
return (LinphoneUtils.getAddressDisplayName(contactAddress));
}
}
return (mRoom.getSubject());
}
public void getAvatar(ChatRoom mRoom) {
mDefaultBitmap = ContactsManager.getInstance().getDefaultAvatarBitmap();
LinphoneContact contact = ContactsManager.getInstance().findContactFromAddress(mRoom.getPeerAddress());
if (contact != null) {
LinphoneUtils.setThumbnailPictureFromUri(LinphoneActivity.instance(), this.contactPicture, ContactsManager.getInstance().findContactFromAddress(mRoom.getPeerAddress()).getThumbnailUri());
} else {
this.contactPicture.setImageBitmap(mDefaultBitmap);
}
}
public interface ClickListener {
void onItemClicked(int position);
boolean onItemLongClicked(int position);
}
}
private Context mContext;
private List<ChatRoom> mRooms;
private LayoutInflater mLayoutInflater;
private Bitmap mDefaultBitmap, mDefaultGroupBitmap;
public List<ChatRoom> mRooms;
private static Bitmap mDefaultBitmap;
//private Bitmap mDefaultGroupBitmap;
private ChatRoomListenerStub mListener;
private int itemResource;
private ChatRoomViewHolder.ClickListener clickListener;
public ChatRoomsAdapter(Context context, ListSelectionHelper helper, LayoutInflater inflater) {
public ChatRoomsAdapter(Context context, int itemResource, List<ChatRoom> mRooms, ChatRoomViewHolder.ClickListener clickListener, SelectableHelper helper) {
super(helper);
mContext = context;
mLayoutInflater = inflater;
mRooms = new ArrayList<>();
this.clickListener = clickListener;
this.mRooms = mRooms;
this.mContext = context;
this.itemResource = itemResource;
mDefaultBitmap = ContactsManager.getInstance().getDefaultAvatarBitmap();
mDefaultGroupBitmap = BitmapFactory.decodeResource(context.getResources(), R.drawable.chat_group_avatar);
//mDefaultGroupBitmap = BitmapFactory.decodeResource(context.getResources(), R.drawable.chat_group_avatar);
}
mListener = new ChatRoomListenerStub() {
@Override
public void onSubjectChanged(ChatRoom cr, EventLog eventLog) {
ChatRoomViewHolder holder = (ChatRoomViewHolder) cr.getUserData();
holder.displayName.setText(cr.getSubject());
}
};
@Override
public ChatRoomViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
View view = LayoutInflater.from(parent.getContext())
.inflate(this.itemResource, parent, false);
return new ChatRoomViewHolder(this.mContext, view, clickListener);
}
@Override
public void onBindViewHolder(ChatRoomViewHolder holder, int position) {
ChatRoom room = this.mRooms.get(position);
holder.delete.setVisibility(this.isEditionEnabled() == true ? View.VISIBLE : View.INVISIBLE);
holder.unreadMessages.setVisibility(this.isEditionEnabled() == false ? View.VISIBLE : View.INVISIBLE);
holder.delete.setChecked(isSelected(position) ? true : false);
holder.bindChatRoom(room);
}
public void refresh() {
@ -114,6 +205,7 @@ public class ChatRoomsAdapter extends ListSelectionAdapter {
room.removeListener(mListener);
}
mRooms.clear();
notifyDataSetChanged();
}
/**
@ -121,8 +213,8 @@ public class ChatRoomsAdapter extends ListSelectionAdapter {
*/
@Override
public int getCount() {
return mRooms.size();
public int getItemCount() {
return this.mRooms.size();
}
@Override
@ -134,121 +226,4 @@ public class ChatRoomsAdapter extends ListSelectionAdapter {
public long getItemId(int position) {
return position;
}
@Override
public View getView(final int position, View convertView, ViewGroup viewGroup) {
View view;
ChatRoomViewHolder holder;
if (convertView != null) {
view = convertView;
holder = (ChatRoomViewHolder) view.getTag();
} else {
view = mLayoutInflater.inflate(R.layout.chatlist_cell, viewGroup, false);
holder = new ChatRoomViewHolder(view);
view.setTag(holder);
}
ChatRoom chatRoom = mRooms.get(position);
Address remoteAddress = chatRoom.getPeerAddress();
Address contactAddress = remoteAddress;
if (chatRoom.hasCapability(ChatRoomCapabilities.OneToOne.toInt()) && chatRoom.getParticipants().length > 0) {
contactAddress = chatRoom.getParticipants()[0].getAddress();
}
if (chatRoom.hasCapability(ChatRoomCapabilities.Conference.toInt()) && chatRoom.getState() == ChatRoom.State.Created) { // Only set for state Created otherwise it will conflict with removal listener
chatRoom.addListener(mListener);
chatRoom.setUserData(holder);
}
int unreadMessagesCount = LinphoneManager.getInstance().getUnreadCountForChatRoom(chatRoom);
ChatMessage lastMessage = chatRoom.getLastMessageInHistory();
holder.lastMessageView.setText("");
holder.lastMessageSenderView.setText("");
holder.date.setText(LinphoneUtils.timestampToHumanDate(mContext, chatRoom.getLastUpdateTime(), R.string.messages_list_date_format));
if (lastMessage != null) {
if (lastMessage.getFileTransferInformation() != null || lastMessage.getExternalBodyUrl() != null || lastMessage.getAppdata() != null) {
holder.lastMessageView.setBackgroundResource(R.drawable.chat_file_message);
} else if (lastMessage.getTextContent() != null && lastMessage.getTextContent().length() > 0) {
holder.lastMessageView.setBackgroundResource(0);
holder.lastMessageView.setText(lastMessage.getTextContent());
}
Address lastMessageSenderAddress = lastMessage.getFromAddress();
LinphoneContact contact = ContactsManager.getInstance().findContactFromAddress(lastMessageSenderAddress);
if (contact != null) {
holder.lastMessageSenderView.setText(contact.getFullName() + mContext.getString(R.string.separator));
} else {
holder.lastMessageSenderView.setText(LinphoneUtils.getAddressDisplayName(lastMessageSenderAddress) + mContext.getString(R.string.separator));
}
}
holder.displayName.setSelected(true); // For animation
holder.contactPicture.setImageBitmap(mDefaultBitmap);
if (chatRoom.hasCapability(ChatRoomCapabilities.OneToOne.toInt())) {
LinphoneContact contact;
if (chatRoom.getParticipants().length > 0) {
contact = ContactsManager.getInstance().findContactFromAddress(chatRoom.getParticipants()[0].getAddress());
if (contact != null) {
holder.displayName.setText(contact.getFullName());
LinphoneUtils.setThumbnailPictureFromUri(LinphoneActivity.instance(), holder.contactPicture, contact.getThumbnailUri());
} else {
holder.displayName.setText(LinphoneUtils.getAddressDisplayName(chatRoom.getParticipants()[0].getAddress()));
}
} else {
contact = ContactsManager.getInstance().findContactFromAddress(contactAddress);
if (contact != null) {
holder.displayName.setText(contact.getFullName());
LinphoneUtils.setThumbnailPictureFromUri(LinphoneActivity.instance(), holder.contactPicture, contact.getThumbnailUri());
} else {
holder.displayName.setText(LinphoneUtils.getAddressDisplayName(contactAddress));
}
}
} else {
holder.displayName.setText(chatRoom.getSubject());
holder.contactPicture.setImageBitmap(mDefaultGroupBitmap);
}
if (unreadMessagesCount > 0) {
holder.unreadMessages.setVisibility(View.VISIBLE);
holder.unreadMessages.setText(String.valueOf(unreadMessagesCount));
if (unreadMessagesCount > 99) {
holder.unreadMessages.setTextSize(12);
}
holder.unreadMessages.setVisibility(View.VISIBLE);
holder.displayName.setTypeface(null, Typeface.BOLD);
} else {
holder.unreadMessages.setVisibility(View.GONE);
holder.displayName.setTypeface(null, Typeface.NORMAL);
}
if (isEditionEnabled()) {
view.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
ChatRoomViewHolder holder = (ChatRoomViewHolder)v.getTag();
holder.delete.setChecked(!holder.delete.isChecked());
}
});
holder.unreadMessages.setVisibility(View.GONE);
holder.delete.setOnCheckedChangeListener(null);
holder.delete.setVisibility(View.VISIBLE);
holder.delete.setChecked(getSelectedItemsPosition().contains(position));
holder.delete.setTag(position);
holder.delete.setOnCheckedChangeListener(getDeleteListener());
} else {
view.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
ChatRoom chatRoom = mRooms.get(position);
LinphoneActivity.instance().goToChat(chatRoom.getPeerAddress().asString(), null);
}
});
holder.delete.setVisibility(isEditionEnabled() ? View.VISIBLE : View.GONE);
}
return view;
}
}

View file

@ -34,6 +34,8 @@ import android.os.Bundle;
import android.os.Environment;
import android.os.Parcelable;
import android.provider.MediaStore;
import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.RecyclerView;
import android.text.Editable;
import android.text.TextWatcher;
import android.view.ContextMenu;
@ -45,12 +47,10 @@ import android.view.ViewGroup;
import android.view.ViewTreeObserver;
import android.view.WindowManager;
import android.view.inputmethod.InputMethodManager;
import android.widget.AdapterView;
import android.widget.Button;
import android.widget.EditText;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.ListView;
import android.widget.TextView;
import org.linphone.LinphoneManager;
@ -61,6 +61,7 @@ import org.linphone.activities.LinphoneActivity;
import org.linphone.compatibility.Compatibility;
import org.linphone.contacts.ContactAddress;
import org.linphone.contacts.ContactsManager;
import org.linphone.contacts.ContactsUpdatedListener;
import org.linphone.contacts.LinphoneContact;
import org.linphone.core.Address;
import org.linphone.core.ChatMessage;
@ -73,10 +74,9 @@ import org.linphone.core.EventLog;
import org.linphone.core.Factory;
import org.linphone.core.LimeState;
import org.linphone.core.Participant;
import org.linphone.contacts.ContactsUpdatedListener;
import org.linphone.core.Reason;
import org.linphone.mediastream.Log;
import org.linphone.ui.ListSelectionHelper;
import org.linphone.ui.SelectableHelper;
import java.io.File;
import java.util.ArrayList;
@ -85,7 +85,7 @@ import java.util.List;
import static android.content.Context.INPUT_METHOD_SERVICE;
import static org.linphone.fragments.FragmentsAvailable.CHAT;
public class GroupChatFragment extends Fragment implements ChatRoomListener, ContactsUpdatedListener, ListSelectionHelper.DeleteListener {
public class GroupChatFragment extends Fragment implements ChatRoomListener, ContactsUpdatedListener, ChatBubbleViewHolder.ClickListener ,SelectableHelper.DeleteListener {
private static final int ADD_PHOTO = 1337;
private ImageView mBackButton, mCallButton, mBackToCallButton, mGroupInfosButton;
@ -93,10 +93,10 @@ public class GroupChatFragment extends Fragment implements ChatRoomListener, Con
private TextView mRoomLabel, mParticipantsLabel, mRemoteComposing;
private EditText mMessageTextToSend;
private LayoutInflater mInflater;
private ListView mChatEventsList;
private RecyclerView mChatEventsList;
private LinearLayout mFilesUploadLayout;
private ListSelectionHelper mSelectionHelper;
private SelectableHelper mSelectionHelper;
private Context mContext;
private ViewTreeObserver.OnGlobalLayoutListener mKeyboardListener;
private Uri mImageToUploadUri;
private ChatEventsAdapter mEventsAdapter;
@ -104,7 +104,8 @@ public class GroupChatFragment extends Fragment implements ChatRoomListener, Con
private Address mRemoteSipAddress, mRemoteParticipantAddress;
private ChatRoom mChatRoom;
private ArrayList<LinphoneContact> mParticipants;
private LinearLayoutManager layoutManager;
private int mContextMenuMessagePosition;
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
@ -116,10 +117,9 @@ public class GroupChatFragment extends Fragment implements ChatRoomListener, Con
mRemoteSipAddress = LinphoneManager.getLc().createAddress(mRemoteSipUri);
}
mInflater = inflater;
this.mContext = getActivity().getApplicationContext();
View view = inflater.inflate(R.layout.chat, container, false);
mSelectionHelper = new ListSelectionHelper(view, this);
mBackButton = view.findViewById(R.id.back);
mBackButton.setOnClickListener(new View.OnClickListener() {
@ -213,7 +213,9 @@ public class GroupChatFragment extends Fragment implements ChatRoomListener, Con
mRemoteComposing = view.findViewById(R.id.remote_composing);
mChatEventsList = view.findViewById(R.id.chat_message_list);
registerForContextMenu(mChatEventsList);
mSelectionHelper = new SelectableHelper(view, this);
layoutManager = new LinearLayoutManager(mContext);
mChatEventsList.setLayoutManager(layoutManager);
if (getArguments() != null) {
String fileSharedUri = getArguments().getString("fileSharedUri");
@ -233,7 +235,6 @@ public class GroupChatFragment extends Fragment implements ChatRoomListener, Con
if (getArguments().getString("messageDraft") != null)
mMessageTextToSend.setText(getArguments().getString("messageDraft"));
}
return view;
}
@ -257,7 +258,6 @@ public class GroupChatFragment extends Fragment implements ChatRoomListener, Con
initChatRoom();
displayChatRoomHeader();
displayChatRoomHistory();
LinphoneManager.getInstance().setCurrentChatRoomAddress(mRemoteSipAddress);
}
@ -347,12 +347,15 @@ public class GroupChatFragment extends Fragment implements ChatRoomListener, Con
}
}
@Override
public void onCreateContextMenu(ContextMenu menu, View v, ContextMenu.ContextMenuInfo menuInfo) {
super.onCreateContextMenu(menu, v, menuInfo);
AdapterView.AdapterContextMenuInfo info = (AdapterView.AdapterContextMenuInfo) menuInfo;
EventLog event = (EventLog) mEventsAdapter.getItem(info.position);
ChatBubbleViewHolder holder = (ChatBubbleViewHolder) v.getTag();
mContextMenuMessagePosition = holder.getAdapterPosition();
EventLog event = (EventLog) mEventsAdapter.getItem(mContextMenuMessagePosition);
if (event.getType() != EventLog.Type.ConferenceChatMessage) {
return;
}
@ -365,8 +368,8 @@ public class GroupChatFragment extends Fragment implements ChatRoomListener, Con
inflater.inflate(R.menu.chat_bubble_menu, menu);
}
if (mChatRoom.hasCapability(ChatRoomCapabilities.OneToOne.toInt())) {
// Do not show messages' IDMN state in 1 to 1 chat room as it is already visible in the lower corner of the bubble
if (!message.isOutgoing() && mChatRoom.hasCapability(ChatRoomCapabilities.OneToOne.toInt())) {
// Do not show incoming messages IDMN state in 1 to 1 chat room as we don't receive IMDN for them
menu.removeItem(R.id.imdn_infos);
}
if (!message.hasTextContent()) {
@ -377,9 +380,9 @@ public class GroupChatFragment extends Fragment implements ChatRoomListener, Con
@Override
public boolean onContextItemSelected(MenuItem item) {
AdapterView.AdapterContextMenuInfo info = (AdapterView.AdapterContextMenuInfo) item.getMenuInfo();
EventLog event = (EventLog) mEventsAdapter.getItem(info.position);
EventLog event = (EventLog) mEventsAdapter.getItem(mContextMenuMessagePosition);
if (event.getType() != EventLog.Type.ConferenceChatMessage) {
return super.onContextItemSelected(item);
}
@ -388,7 +391,7 @@ public class GroupChatFragment extends Fragment implements ChatRoomListener, Con
String messageId = message.getMessageId();
if (item.getItemId() == R.id.resend) {
mEventsAdapter.removeItem(info.position);
mEventsAdapter.removeItem(mContextMenuMessagePosition);
message.resend();
return true;
}
@ -406,7 +409,7 @@ public class GroupChatFragment extends Fragment implements ChatRoomListener, Con
}
if (item.getItemId() == R.id.delete_message) {
mChatRoom.deleteMessage(message);
mEventsAdapter.removeItem(info.position);
mEventsAdapter.removeItem(mContextMenuMessagePosition);
return true;
}
return super.onContextItemSelected(item);
@ -548,26 +551,33 @@ public class GroupChatFragment extends Fragment implements ChatRoomListener, Con
}
private void displayChatRoomHistory() {
if (mChatRoom == null) return;
if (mChatRoom.hasCapability(ChatRoomCapabilities.OneToOne.toInt())) {
mEventsAdapter = new ChatEventsAdapter(this, mSelectionHelper, mInflater, mChatRoom.getHistoryMessageEvents(0), mParticipants);
mEventsAdapter = new ChatEventsAdapter(this, mSelectionHelper, R.layout.chat_bubble, mChatRoom.getHistoryMessageEvents(0), mParticipants, this);
} else {
mEventsAdapter = new ChatEventsAdapter(this, mSelectionHelper, mInflater, mChatRoom.getHistoryEvents(0), mParticipants);
mEventsAdapter = new ChatEventsAdapter(this, mSelectionHelper, R.layout.chat_bubble, mChatRoom.getHistoryEvents(0), mParticipants, this);
}
mSelectionHelper.setAdapter(mEventsAdapter);
mChatEventsList.setAdapter(mEventsAdapter);
}
scrollToBottom();
}
public void scrollToBottom() {
if (((mChatEventsList.getLastVisiblePosition() >= (mEventsAdapter.getCount() - 1)) && (mChatEventsList.getFirstVisiblePosition() <= (mEventsAdapter.getCount() - 1)))) {
mChatEventsList.setSelection(mEventsAdapter.getCount() - 1);
}
mChatEventsList.getLayoutManager().scrollToPosition(mEventsAdapter.getCount() - 1);
}
public String getRemoteSipUri() {
return mRemoteSipUri;
}
@Override
public void onItemClicked(int position) {
if (mEventsAdapter.isEditionEnabled()) {
mEventsAdapter.toggleSelection(position);
}
}
/**
* File transfer related
*/

View file

@ -19,6 +19,8 @@ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
package org.linphone.chat;
import android.support.annotation.NonNull;
import android.support.v7.widget.RecyclerView;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
@ -32,35 +34,126 @@ import org.linphone.R;
import org.linphone.activities.LinphoneActivity;
import org.linphone.contacts.ContactAddress;
import org.linphone.contacts.LinphoneContact;
import org.linphone.contacts.SearchContactsListAdapter;
import org.linphone.core.ChatRoom;
import org.linphone.core.Participant;
import java.util.ArrayList;
import java.util.List;
public class GroupInfoAdapter extends BaseAdapter {
private LayoutInflater mInflater;
public class GroupInfoAdapter extends RecyclerView.Adapter<GroupInfoAdapter.ViewHolder> {
public static class ViewHolder extends RecyclerView.ViewHolder{
public TextView name;
public ImageView avatar;
public ImageView delete;
public LinearLayout isAdmin;
public LinearLayout isNotAdmin;
public ViewHolder(View view) {
super(view);
name = view.findViewById(R.id.name);
avatar = view.findViewById(R.id.contact_picture);
delete = view.findViewById(R.id.delete);
isAdmin = view.findViewById(R.id.isAdminLayout);
isNotAdmin = view.findViewById(R.id.isNotAdminLayout);
}
}
private List<ContactAddress> mItems;
private View.OnClickListener mDeleteListener;
private boolean mHideAdminFeatures;
private ChatRoom mChatRoom;
public ImageView avatar;
public GroupInfoAdapter(LayoutInflater inflater, List<ContactAddress> items, boolean hideAdminFeatures, boolean isCreation) {
mInflater = inflater;
public GroupInfoAdapter(List<ContactAddress> items, boolean hideAdminFeatures, boolean isCreation) {
mItems = items;
mHideAdminFeatures = hideAdminFeatures || isCreation;
}
@NonNull
@Override
public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
View v = LayoutInflater.from(parent.getContext()).inflate(R.layout.chat_infos_cell, parent, false);
return new ViewHolder(v);
}
@Override
public void onBindViewHolder(@NonNull final ViewHolder holder, int position) {
final ContactAddress ca = (ContactAddress)getItem(position);
LinphoneContact c = ca.getContact();
this.avatar=holder.avatar;
holder.name.setText((c.getFullName() != null) ? c.getFullName() :
(ca.getDisplayName() != null) ? ca.getDisplayName() : ca.getUsername());
if (c.hasPhoto()) {
LinphoneUtils.setThumbnailPictureFromUri(LinphoneActivity.instance(), avatar, c.getThumbnailUri());
}
holder.delete.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
if (mDeleteListener != null) {
mDeleteListener.onClick(view);
}
}
});
holder.delete.setTag(ca);
holder.isAdmin.setVisibility(ca.isAdmin() ? View.VISIBLE : View.GONE);
holder.isNotAdmin.setVisibility(ca.isAdmin() ? View.GONE : View.VISIBLE);
holder.isAdmin.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
holder.isNotAdmin.setVisibility(View.VISIBLE);
holder.isAdmin.setVisibility(View.GONE);
ca.setAdmin(false);
}
});
holder.isNotAdmin.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
holder.isNotAdmin.setVisibility(View.GONE);
holder.isAdmin.setVisibility(View.VISIBLE);
ca.setAdmin(true);
}
});
holder.delete.setVisibility(View.VISIBLE);
if (mHideAdminFeatures) {
holder.delete.setVisibility(View.INVISIBLE);
holder.isAdmin.setOnClickListener(null); // Do not allow not admin to remove it's rights but display admins
holder.isNotAdmin.setVisibility(View.GONE); // Hide not admin button for not admin participants
} else if (mChatRoom != null) {
boolean found = false;
for (Participant p : mChatRoom.getParticipants()) {
if (p.getAddress().weakEqual(ca.getAddress())) {
found = true;
break;
}
}
if (!found) {
holder.isNotAdmin.setVisibility(View.GONE); // Hide not admin button for participant not yet added so even if user click it it won't have any effect
}
}
}
@Override
public int getItemCount() {
return mItems.size();
}
public void setChatRoom(ChatRoom room) {
mChatRoom = room;
}
@Override
public int getCount() {
return mItems.size();
}
@Override
public Object getItem(int i) {
return mItems.get(i);
}
@ -70,79 +163,6 @@ public class GroupInfoAdapter extends BaseAdapter {
return i;
}
@Override
public View getView(int i, View view, ViewGroup viewGroup) {
if (view == null) {
view = mInflater.inflate(R.layout.chat_infos_cell, null);
}
final ContactAddress ca = (ContactAddress)getItem(i);
LinphoneContact c = ca.getContact();
TextView name = view.findViewById(R.id.name);
ImageView avatar = view.findViewById(R.id.contact_picture);
ImageView delete = view.findViewById(R.id.delete);
final LinearLayout isAdmin = view.findViewById(R.id.isAdminLayout);
final LinearLayout isNotAdmin = view.findViewById(R.id.isNotAdminLayout);
name.setText((c.getFullName() != null) ? c.getFullName() :
(ca.getDisplayName() != null) ? ca.getDisplayName() : ca.getUsername());
if (c.hasPhoto()) {
LinphoneUtils.setThumbnailPictureFromUri(LinphoneActivity.instance(), avatar, c.getThumbnailUri());
}
delete.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
if (mDeleteListener != null) {
mDeleteListener.onClick(view);
}
}
});
delete.setTag(ca);
isAdmin.setVisibility(ca.isAdmin() ? View.VISIBLE : View.GONE);
isNotAdmin.setVisibility(ca.isAdmin() ? View.GONE : View.VISIBLE);
isAdmin.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
isNotAdmin.setVisibility(View.VISIBLE);
isAdmin.setVisibility(View.GONE);
ca.setAdmin(false);
}
});
isNotAdmin.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
isNotAdmin.setVisibility(View.GONE);
isAdmin.setVisibility(View.VISIBLE);
ca.setAdmin(true);
}
});
delete.setVisibility(View.VISIBLE);
if (mHideAdminFeatures) {
delete.setVisibility(View.INVISIBLE);
isAdmin.setOnClickListener(null); // Do not allow not admin to remove it's rights but display admins
isNotAdmin.setVisibility(View.GONE); // Hide not admin button for not admin participants
} else if (mChatRoom != null) {
boolean found = false;
for (Participant p : mChatRoom.getParticipants()) {
if (p.getAddress().weakEqual(ca.getAddress())) {
found = true;
break;
}
}
if (!found) {
isNotAdmin.setVisibility(View.GONE); // Hide not admin button for participant not yet added so even if user click it it won't have any effect
}
}
return view;
}
public void setOnDeleteClickListener(View.OnClickListener onClickListener) {
mDeleteListener = onClickListener;
}
@ -154,6 +174,6 @@ public class GroupInfoAdapter extends BaseAdapter {
public void setAdminFeaturesVisible(boolean visible) {
mHideAdminFeatures = !visible;
notifyDataSetInvalidated();
notifyDataSetChanged();
}
}

View file

@ -21,7 +21,11 @@ package org.linphone.chat;
import android.app.Dialog;
import android.app.Fragment;
import android.content.Context;
import android.os.Bundle;
import android.support.v7.widget.DividerItemDecoration;
import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.RecyclerView;
import android.text.Editable;
import android.text.TextWatcher;
import android.view.LayoutInflater;
@ -32,7 +36,6 @@ import android.widget.Button;
import android.widget.EditText;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.ListView;
import android.widget.RelativeLayout;
import org.linphone.LinphoneManager;
@ -61,7 +64,9 @@ public class GroupInfoFragment extends Fragment implements ChatRoomListener {
private Address mGroupChatRoomAddress;
private EditText mSubjectField;
private LayoutInflater mInflater;
private ListView mParticipantsList;
private RecyclerView mParticipantsList;
private LinearLayout mLeaveGroupButton;
private RelativeLayout mWaitLayout;
private GroupInfoAdapter mAdapter;
@ -73,6 +78,8 @@ public class GroupInfoFragment extends Fragment implements ChatRoomListener {
private Dialog mAdminStateChangedDialog;
private ChatRoomListenerStub mChatRoomCreationListener;
private Bundle mShareInfos;
private Context mContext;
private LinearLayoutManager layoutManager;
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
@ -82,6 +89,8 @@ public class GroupInfoFragment extends Fragment implements ChatRoomListener {
if (getArguments() == null || getArguments().isEmpty()) {
return null;
}
this.mContext = getActivity().getApplicationContext();
mParticipants = (ArrayList<ContactAddress>) getArguments().getSerializable("ContactAddress");
mGroupChatRoomAddress = null;
@ -105,7 +114,7 @@ public class GroupInfoFragment extends Fragment implements ChatRoomListener {
}
mParticipantsList = view.findViewById(R.id.chat_room_participants);
mAdapter = new GroupInfoAdapter(mInflater, mParticipants, !mIsEditionEnabled, !mIsAlreadyCreatedGroup);
mAdapter = new GroupInfoAdapter(mParticipants, !mIsEditionEnabled, !mIsAlreadyCreatedGroup);
mAdapter.setOnDeleteClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
@ -118,6 +127,15 @@ public class GroupInfoFragment extends Fragment implements ChatRoomListener {
});
mParticipantsList.setAdapter(mAdapter);
mAdapter.setChatRoom(mChatRoom);
layoutManager = new LinearLayoutManager(mContext);
mParticipantsList.setLayoutManager(layoutManager);
//Divider between items
DividerItemDecoration dividerItemDecoration = new DividerItemDecoration(mParticipantsList.getContext(),
layoutManager.getOrientation());
dividerItemDecoration.setDrawable(mContext.getResources().getDrawable(R.drawable.divider));
mParticipantsList.addItemDecoration(dividerItemDecoration);
String fileSharedUri = getArguments().getString("fileSharedUri");
String messageDraft = getArguments().getString("messageDraft");

View file

@ -47,7 +47,7 @@ import org.linphone.core.ChatRoom;
import org.linphone.core.Core;
import org.linphone.core.ParticipantImdnState;
public class ImdnFragment extends Fragment {
public class ImdnFragment extends Fragment{
private LayoutInflater mInflater;
private LinearLayout mRead, mReadHeader, mDelivered, mDeliveredHeader, mSent, mSentHeader, mUndelivered, mUndeliveredHeader;
private ImageView mBackButton;

View file

@ -0,0 +1,219 @@
package org.linphone.contacts;
/*
ContactsListAdapter.java
Copyright (C) 2018 Belledonne Communications, Grenoble, France
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 2
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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
import android.content.Context;
import android.support.annotation.NonNull;
import android.support.v7.widget.RecyclerView;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.CheckBox;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.SectionIndexer;
import android.widget.TextView;
import org.linphone.LinphoneUtils;
import org.linphone.R;
import org.linphone.activities.LinphoneActivity;
import org.linphone.ui.SelectableAdapter;
import org.linphone.ui.SelectableHelper;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
public class ContactsListAdapter extends SelectableAdapter<ContactsListAdapter.ViewHolder> implements SectionIndexer {
public static class ViewHolder extends RecyclerView.ViewHolder implements View.OnClickListener, View.OnLongClickListener{
public CheckBox delete;
public ImageView linphoneFriend;
public TextView name;
public LinearLayout separator;
public TextView separatorText;
public ImageView contactPicture;
public TextView organization;
//public ImageView friendStatus;
private ClickListener listener;
private ViewHolder(View view, ClickListener listener) {
super(view);
delete = (CheckBox) view.findViewById(R.id.delete);
linphoneFriend = (ImageView) view.findViewById(R.id.friendLinphone);
name = (TextView) view.findViewById(R.id.name);
separator = (LinearLayout) view.findViewById(R.id.separator);
separatorText = (TextView) view.findViewById(R.id.separator_text);
contactPicture = (ImageView) view.findViewById(R.id.contact_picture);
organization = (TextView) view.findViewById(R.id.contactOrganization);
//friendStatus = (ImageView) view.findViewById(R.id.friendStatus);
this.listener= listener;
view.setOnClickListener(this);
view.setOnLongClickListener(this);
}
@Override
public void onClick(View view) {
if (listener != null) {
listener.onItemClicked(getAdapterPosition());
}
}
public boolean onLongClick(View v) {
if (listener != null) {
return listener.onItemLongClicked(getAdapterPosition());
}
return false;
}
public interface ClickListener {
void onItemClicked(int position);
boolean onItemLongClicked(int position);
}
}
private List<LinphoneContact> mContacts;
private String[] mSections;
private ArrayList<String> mSectionsList;
private Map<String, Integer> mMap = new LinkedHashMap<String, Integer>();
private ViewHolder.ClickListener mClickListener;
private Context mContext;
private boolean mIsSearchMode;
ContactsListAdapter(Context context, List<LinphoneContact> contactsList, ViewHolder.ClickListener clickListener, SelectableHelper helper) {
super(helper);
mContext = context;
updateDataSet(contactsList);
mClickListener = clickListener;
}
@NonNull
@Override
public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
View v = LayoutInflater.from(parent.getContext()).inflate(R.layout.contact_cell, parent, false);
return new ViewHolder(v, mClickListener);
}
@Override
public void onBindViewHolder(@NonNull ViewHolder holder, final int position) {
LinphoneContact contact = (LinphoneContact) getItem(position);
holder.name.setText(contact.getFullName());
if (!mIsSearchMode) {
String fullName = contact.getFullName();
if (fullName != null && !fullName.isEmpty()) {
holder.separatorText.setText(String.valueOf(fullName.charAt(0)));
}
}
holder.separator.setVisibility(mIsSearchMode || (!mIsSearchMode && getPositionForSection(getSectionForPosition(position)) != position ) ? View.GONE:View.VISIBLE);
holder.linphoneFriend.setVisibility(contact.isInFriendList() ? View.VISIBLE:View.GONE);
holder.contactPicture.setImageBitmap(ContactsManager.getInstance().getDefaultAvatarBitmap());
if (contact.hasPhoto()) {
LinphoneUtils.setThumbnailPictureFromUri(LinphoneActivity.instance(), holder.contactPicture, contact.getThumbnailUri());
}
boolean isOrgVisible = mContext.getResources().getBoolean(R.bool.display_contact_organization);
String org = contact.getOrganization();
if (org != null && !org.isEmpty() && isOrgVisible) {
holder.organization.setText(org);
holder.organization.setVisibility(View.VISIBLE);
} else {
holder.organization.setVisibility(View.GONE);
}
holder.delete.setVisibility(this.isEditionEnabled() ? View.VISIBLE : View.INVISIBLE);
holder.delete.setChecked(isSelected(position));
}
@Override
public int getItemCount() {
return mContacts.size();
}
public Object getItem(int position) {
if (position >= getItemCount()) return null;
return mContacts.get(position);
}
public void setmIsSearchMode(boolean set){
mIsSearchMode = set;
}
public long getItemId(int position) {
return position;
}
public void updateDataSet(List<LinphoneContact> contactsList) {
mContacts = contactsList;
mMap = new LinkedHashMap<String, Integer>();
String prevLetter = null;
for (int i = 0; i < mContacts.size(); i++) {
LinphoneContact contact = mContacts.get(i);
String fullName = contact.getFullName();
if (fullName == null || fullName.isEmpty()) {
continue;
}
String firstLetter = fullName.substring(0, 1).toUpperCase(Locale.getDefault());
if (!firstLetter.equals(prevLetter)) {
prevLetter = firstLetter;
mMap.put(firstLetter, i);
}
}
mSectionsList = new ArrayList<String>(mMap.keySet());
mSections = new String[mSectionsList.size()];
mSectionsList.toArray(mSections);
notifyDataSetChanged();
}
@Override
public Object[] getSections() {
return mSections;
}
@Override
public int getPositionForSection(int sectionIndex) {
if (sectionIndex >= mSections.length || sectionIndex < 0) {
return 0;
}
return mMap.get(mSections[sectionIndex]);
}
@Override
public int getSectionForPosition(int position) {
if (position >= mContacts.size() || position < 0) {
return 0;
}
LinphoneContact contact = mContacts.get(position);
String fullName = contact.getFullName();
if (fullName == null || fullName.isEmpty()) {
return 0;
}
String letter = fullName.substring(0, 1).toUpperCase(Locale.getDefault());
return mSectionsList.indexOf(letter);
}
}

View file

@ -20,61 +20,55 @@ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
import android.app.Dialog;
import android.app.Fragment;
import android.content.Context;
import android.os.Bundle;
import android.support.v7.widget.DividerItemDecoration;
import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.RecyclerView;
import android.text.Editable;
import android.text.TextWatcher;
import android.view.LayoutInflater;
import android.view.View;
import android.view.View.OnClickListener;
import android.view.ViewGroup;
import android.widget.AbsListView;
import android.widget.AdapterView;
import android.widget.AdapterView.OnItemClickListener;
import android.widget.BaseAdapter;
import android.widget.Button;
import android.widget.CheckBox;
import android.widget.CompoundButton;
import android.widget.EditText;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.ListView;
import android.widget.ProgressBar;
import android.widget.SectionIndexer;
import android.widget.TextView;
import org.linphone.fragments.FragmentsAvailable;
import org.linphone.LinphoneManager;
import org.linphone.LinphoneUtils;
import org.linphone.R;
import org.linphone.activities.LinphoneActivity;
import org.linphone.fragments.FragmentsAvailable;
import org.linphone.ui.SelectableHelper;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
public class ContactsListFragment extends Fragment implements OnClickListener, OnItemClickListener, ContactsUpdatedListener {
private LayoutInflater mInflater;
private ListView contactsList;
public class ContactsListFragment extends Fragment implements OnItemClickListener, ContactsUpdatedListener, ContactsListAdapter.ViewHolder.ClickListener ,SelectableHelper.DeleteListener {
private RecyclerView contactsList;
private TextView noSipContact, noContact;
private ImageView allContacts, linphoneContacts, newContact, edit, selectAll, deselectAll, delete, cancel;
private boolean onlyDisplayLinphoneContacts, isEditMode, isSearchMode;
private ImageView allContacts, linphoneContacts, newContact, edit;
private boolean onlyDisplayLinphoneContacts;
private View allContactsSelected, linphoneContactsSelected;
private LinearLayout editList, topbar;
private int lastKnownPosition;
private boolean editOnClick = false, editConsumed = false, onlyDisplayChatAddress = false;
private String sipAddressToAdd, displayName = null;
private ImageView clearSearchField;
private EditText searchField;
private ProgressBar contactsFetchInProgress;
private LinearLayoutManager layoutManager;
private Context mContext;
private SelectableHelper mSelectionHelper;
private ContactsListAdapter mContactAdapter;
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
mInflater = inflater;
View view = inflater.inflate(R.layout.contacts_list, container, false);
mContext = getActivity().getApplicationContext();
mSelectionHelper = new SelectableHelper(view, this);
if (getArguments() != null) {
editOnClick = getArguments().getBoolean("EditOnClick");
@ -86,46 +80,64 @@ public class ContactsListFragment extends Fragment implements OnClickListener, O
noSipContact = (TextView) view.findViewById(R.id.noSipContact);
noContact = (TextView) view.findViewById(R.id.noContact);
contactsList = (ListView) view.findViewById(R.id.contactsList);
contactsList.setOnItemClickListener(this);
contactsList = view.findViewById(R.id.contactsList);
allContacts = (ImageView) view.findViewById(R.id.all_contacts);
allContacts.setOnClickListener(this);
linphoneContacts = (ImageView) view.findViewById(R.id.linphone_contacts);
linphoneContacts.setOnClickListener(this);
linphoneContacts = (ImageView) view.findViewById(R.id.linphone_contacts);
allContactsSelected = view.findViewById(R.id.all_contacts_select);
linphoneContactsSelected = view.findViewById(R.id.linphone_contacts_select);
edit = (ImageView) view.findViewById(R.id.edit);
contactsFetchInProgress = (ProgressBar) view.findViewById(R.id.contactsFetchInProgress);
newContact = (ImageView) view.findViewById(R.id.newContact);
allContacts.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
onlyDisplayLinphoneContacts = false;
allContactsSelected.setVisibility(View.VISIBLE);
allContacts.setEnabled(false);
linphoneContacts.setEnabled(true);
linphoneContactsSelected.setVisibility(View.INVISIBLE);
changeContactsAdapter();
}
});
linphoneContacts.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
allContactsSelected.setVisibility(View.INVISIBLE);
linphoneContactsSelected.setVisibility(View.VISIBLE);
linphoneContacts.setEnabled(false);
allContacts.setEnabled(true);
onlyDisplayLinphoneContacts = true;
changeContactsAdapter();
}
});
newContact.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
editConsumed = true;
if(displayName != null)
LinphoneActivity.instance().addContact(displayName, sipAddressToAdd);
else
LinphoneActivity.instance().addContact(null, sipAddressToAdd);
}
});
newContact = (ImageView) view.findViewById(R.id.newContact);
newContact.setOnClickListener(this);
newContact.setEnabled(LinphoneManager.getLc().getCallsNb() == 0);
allContacts.setEnabled(onlyDisplayLinphoneContacts);
linphoneContacts.setEnabled(!allContacts.isEnabled());
contactsFetchInProgress.setVisibility(View.VISIBLE);
selectAll = (ImageView) view.findViewById(R.id.select_all);
selectAll.setOnClickListener(this);
deselectAll = (ImageView) view.findViewById(R.id.deselect_all);
deselectAll.setOnClickListener(this);
delete = (ImageView) view.findViewById(R.id.delete);
delete.setOnClickListener(this);
editList = (LinearLayout) view.findViewById(R.id.edit_list);
topbar = (LinearLayout) view.findViewById(R.id.top_bar);
cancel = (ImageView) view.findViewById(R.id.cancel);
cancel.setOnClickListener(this);
edit = (ImageView) view.findViewById(R.id.edit);
edit.setOnClickListener(this);
clearSearchField = (ImageView) view.findViewById(R.id.clearSearchField);
clearSearchField.setOnClickListener(this);
clearSearchField.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
searchField.setText("");
}
});
searchField = (EditText) view.findViewById(R.id.searchField);
searchField.addTextChangedListener(new TextWatcher() {
@ -146,220 +158,103 @@ public class ContactsListFragment extends Fragment implements OnClickListener, O
}
});
layoutManager = new LinearLayoutManager(mContext);
contactsList.setLayoutManager(layoutManager);
//Divider between items
DividerItemDecoration dividerItemDecoration = new DividerItemDecoration(contactsList.getContext(),
layoutManager.getOrientation());
dividerItemDecoration.setDrawable(getActivity().getResources().getDrawable(R.drawable.divider));
contactsList.addItemDecoration(dividerItemDecoration);
contactsFetchInProgress = (ProgressBar) view.findViewById(R.id.contactsFetchInProgress);
contactsFetchInProgress.setVisibility(View.VISIBLE);
return view;
}
public int getNbItemsChecked(){
int size = contactsList.getAdapter().getCount();
int nb = 0;
for(int i=0; i<size; i++) {
if(contactsList.isItemChecked(i)) {
nb ++;
}
}
return nb;
}
public void enabledDeleteButton(Boolean enabled){
if(enabled){
delete.setEnabled(true);
} else {
if (getNbItemsChecked() == 0){
delete.setEnabled(false);
}
}
}
@Override
public void onClick(View v) {
int id = v.getId();
if (id == R.id.select_all) {
deselectAll.setVisibility(View.VISIBLE);
selectAll.setVisibility(View.GONE);
enabledDeleteButton(true);
selectAllList(true);
return;
}
if (id == R.id.deselect_all) {
deselectAll.setVisibility(View.GONE);
selectAll.setVisibility(View.VISIBLE);
enabledDeleteButton(false);
selectAllList(false);
return;
}
if (id == R.id.cancel) {
quitEditMode();
return;
}
if (id == R.id.delete) {
final Dialog dialog = LinphoneActivity.instance().displayDialog(getString(R.string.delete_text));
Button delete = (Button) dialog.findViewById(R.id.delete_button);
Button cancel = (Button) dialog.findViewById(R.id.cancel);
delete.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View view) {
removeContacts();
dialog.dismiss();
quitEditMode();
}
});
cancel.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View view) {
dialog.dismiss();
quitEditMode();
}
});
dialog.show();
return;
}
if (id == R.id.edit) {
editList.setVisibility(View.VISIBLE);
topbar.setVisibility(View.GONE);
enabledDeleteButton(false);
isEditMode = true;
}
if (id == R.id.all_contacts) {
onlyDisplayLinphoneContacts = false;
allContactsSelected.setVisibility(View.VISIBLE);
allContacts.setEnabled(false);
linphoneContacts.setEnabled(true);
linphoneContactsSelected.setVisibility(View.INVISIBLE);
}
else if (id == R.id.linphone_contacts) {
allContactsSelected.setVisibility(View.INVISIBLE);
linphoneContactsSelected.setVisibility(View.VISIBLE);
linphoneContacts.setEnabled(false);
allContacts.setEnabled(true);
onlyDisplayLinphoneContacts = true;
}
if(isEditMode){
deselectAll.setVisibility(View.GONE);
selectAll.setVisibility(View.VISIBLE);
}
if (searchField.getText().toString().length() > 0) {
searchContacts();
} else {
changeContactsAdapter();
}
if (id == R.id.newContact) {
editConsumed = true;
if(displayName != null)
LinphoneActivity.instance().addContact(displayName, sipAddressToAdd);
else
LinphoneActivity.instance().addContact(null, sipAddressToAdd);
}
else if (id == R.id.clearSearchField) {
searchField.setText("");
}
}
private void selectAllList(boolean isSelectAll){
int size = contactsList.getAdapter().getCount();
for(int i=0; i<size; i++) {
contactsList.setItemChecked(i,isSelectAll);
}
}
private void removeContacts() {
ArrayList<String> ids = new ArrayList<String>();
int size = contactsList.getAdapter().getCount();
for (int i = size - 1; i >= 0; i--) {
if (contactsList.isItemChecked(i)) {
LinphoneContact contact = (LinphoneContact) contactsList.getAdapter().getItem(i);
if (contact.isAndroidContact()) {
contact.deleteFriend();
ids.add(contact.getAndroidId());
} else {
contact.delete();
}
}
}
ContactsManager.getInstance().deleteMultipleContactsAtOnce(ids);
}
public void quitEditMode(){
isEditMode = false;
editList.setVisibility(View.GONE);
topbar.setVisibility(View.VISIBLE);
invalidate();
if(getResources().getBoolean(R.bool.isTablet)){
displayFirstContact();
}
}
public void displayFirstContact(){
if (contactsList != null && contactsList.getAdapter() != null && contactsList.getAdapter().getCount() > 0) {
LinphoneActivity.instance().displayContact((LinphoneContact) contactsList.getAdapter().getItem(0), false);
if (contactsList != null && contactsList.getAdapter() != null && contactsList.getAdapter().getItemCount() > 0) {
ContactsListAdapter mAdapt = (ContactsListAdapter)contactsList.getAdapter();
LinphoneActivity.instance().displayContact((LinphoneContact) mAdapt.getItem(0), false);
} else {
LinphoneActivity.instance().displayEmptyFragment();
}
}
private void searchContacts() {
searchContacts(searchField.getText().toString());
}
private void searchContacts(String search) {
boolean isEditionEnabled = false;
if (search == null || search.length() == 0) {
changeContactsAdapter();
return;
}
changeContactsToggle();
mContactAdapter.setmIsSearchMode(true);
isSearchMode = true;
List<LinphoneContact> listContact;
if (onlyDisplayLinphoneContacts) {
contactsList.setChoiceMode(AbsListView.CHOICE_MODE_MULTIPLE);
contactsList.setAdapter(new ContactsListAdapter(ContactsManager.getInstance().getSIPContacts(search)));
listContact = ContactsManager.getInstance().getSIPContacts(search);
} else {
contactsList.setChoiceMode(AbsListView.CHOICE_MODE_MULTIPLE);
contactsList.setAdapter(new ContactsListAdapter(ContactsManager.getInstance().getContacts(search)));
listContact = ContactsManager.getInstance().getContacts(search);
}
if(mContactAdapter != null && mContactAdapter.isEditionEnabled()) {
isEditionEnabled=true;
}
mContactAdapter = new ContactsListAdapter(mContext, listContact, this, mSelectionHelper);
// contactsList.setChoiceMode(AbsListView.CHOICE_MODE_MULTIPLE);
mSelectionHelper.setAdapter(mContactAdapter);
if(isEditionEnabled) {
mSelectionHelper.enterEditionMode();
}
contactsList.setAdapter(mContactAdapter);
}
private void changeContactsAdapter() {
changeContactsToggle();
List<LinphoneContact> listContact;
isSearchMode = false;
noSipContact.setVisibility(View.GONE);
noContact.setVisibility(View.GONE);
contactsList.setVisibility(View.VISIBLE);
boolean isEditionEnabled = false;
if(searchField.getText().toString() == "") {
if (onlyDisplayLinphoneContacts) {
listContact = ContactsManager.getInstance().getSIPContacts();
} else {
listContact = ContactsManager.getInstance().getContacts();
}
ContactsListAdapter adapter;
contactsList.setFastScrollEnabled(false);
if (onlyDisplayLinphoneContacts) {
contactsList.setChoiceMode(AbsListView.CHOICE_MODE_MULTIPLE);
adapter = new ContactsListAdapter(ContactsManager.getInstance().getSIPContacts());
contactsList.setAdapter(adapter);
edit.setEnabled(true);
} else {
contactsList.setChoiceMode(AbsListView.CHOICE_MODE_MULTIPLE);
adapter = new ContactsListAdapter(ContactsManager.getInstance().getContacts());
contactsList.setAdapter(adapter);
edit.setEnabled(true);
}else{
if (onlyDisplayLinphoneContacts) {
listContact = ContactsManager.getInstance().getSIPContacts(searchField.getText().toString());
} else {
listContact = ContactsManager.getInstance().getContacts(searchField.getText().toString());
}
}
contactsList.setFastScrollEnabled(true);
adapter.notifyDataSetInvalidated();
if(mContactAdapter != null && mContactAdapter.isEditionEnabled()) {
isEditionEnabled=true;
}
if (adapter.getCount() > 0) {
mContactAdapter = new ContactsListAdapter(mContext, listContact, this, mSelectionHelper);
mSelectionHelper.setAdapter(mContactAdapter);
if(isEditionEnabled) {
mSelectionHelper.enterEditionMode();
}
contactsList.setAdapter(mContactAdapter);
edit.setEnabled(true);
mContactAdapter.notifyDataSetChanged();
if (mContactAdapter.getItemCount() > 0) {
contactsFetchInProgress.setVisibility(View.GONE);
}
ContactsManager.getInstance().setLinphoneContactsPrefered(onlyDisplayLinphoneContacts);
@ -386,11 +281,36 @@ public class ContactsListFragment extends Fragment implements OnClickListener, O
editConsumed = true;
LinphoneActivity.instance().editContact(contact, sipAddressToAdd);
} else {
lastKnownPosition = contactsList.getFirstVisiblePosition();
lastKnownPosition = layoutManager.findFirstVisibleItemPosition();
LinphoneActivity.instance().displayContact(contact, onlyDisplayChatAddress);
}
}
@Override
public void onItemClicked(int position) {
LinphoneContact contact = (LinphoneContact) mContactAdapter.getItem(position);
if (mContactAdapter.isEditionEnabled()) {
mContactAdapter.toggleSelection(position);
} else if (editOnClick) {
editConsumed = true;
LinphoneActivity.instance().editContact(contact, sipAddressToAdd);
} else {
lastKnownPosition = layoutManager.findFirstVisibleItemPosition();
LinphoneActivity.instance().displayContact(contact, onlyDisplayChatAddress);
}
}
@Override
public boolean onItemLongClicked(int position) {
if (!mContactAdapter.isEditionEnabled()) {
mSelectionHelper.enterEditionMode();
}
mContactAdapter.toggleSelection(position);
return true;
}
@Override
public void onResume() {
super.onResume();
@ -426,13 +346,11 @@ public class ContactsListFragment extends Fragment implements OnClickListener, O
return;
ContactsListAdapter adapter = (ContactsListAdapter)contactsList.getAdapter();
if (adapter != null) {
contactsList.setFastScrollEnabled(false);
if (onlyDisplayLinphoneContacts) {
adapter.updateDataSet(ContactsManager.getInstance().getSIPContacts());
} else {
adapter.updateDataSet(ContactsManager.getInstance().getContacts());
}
contactsList.setFastScrollEnabled(true);
contactsFetchInProgress.setVisibility(View.GONE);
}
}
@ -443,206 +361,22 @@ public class ContactsListFragment extends Fragment implements OnClickListener, O
} else {
changeContactsAdapter();
}
contactsList.setSelectionFromTop(lastKnownPosition, 0);
contactsList.scrollToPosition(lastKnownPosition);
}
class ContactsListAdapter extends BaseAdapter implements SectionIndexer {
private class ViewHolder {
public CheckBox delete;
public ImageView linphoneFriend;
public TextView name;
public LinearLayout separator;
public TextView separatorText;
public ImageView contactPicture;
public TextView organization;
//public ImageView friendStatus;
public ViewHolder(View view) {
delete = (CheckBox) view.findViewById(R.id.delete);
linphoneFriend = (ImageView) view.findViewById(R.id.friendLinphone);
name = (TextView) view.findViewById(R.id.name);
separator = (LinearLayout) view.findViewById(R.id.separator);
separatorText = (TextView) view.findViewById(R.id.separator_text);
contactPicture = (ImageView) view.findViewById(R.id.contact_picture);
organization = (TextView) view.findViewById(R.id.contactOrganization);
//friendStatus = (ImageView) view.findViewById(R.id.friendStatus);
}
}
private List<LinphoneContact> contacts;
String[] sections;
ArrayList<String> sectionsList;
Map<String, Integer>map = new LinkedHashMap<String, Integer>();
ContactsListAdapter(List<LinphoneContact> contactsList) {
updateDataSet(contactsList);
}
public void updateDataSet(List<LinphoneContact> contactsList) {
contacts = contactsList;
map = new LinkedHashMap<String, Integer>();
String prevLetter = null;
for (int i = 0; i < contacts.size(); i++) {
LinphoneContact contact = contacts.get(i);
String fullName = contact.getFullName();
if (fullName == null || fullName.isEmpty()) {
continue;
}
String firstLetter = fullName.substring(0, 1).toUpperCase(Locale.getDefault());
if (!firstLetter.equals(prevLetter)) {
prevLetter = firstLetter;
map.put(firstLetter, i);
}
}
sectionsList = new ArrayList<String>(map.keySet());
sections = new String[sectionsList.size()];
sectionsList.toArray(sections);
notifyDataSetChanged();
}
public int getCount() {
return contacts.size();
}
public Object getItem(int position) {
if (position >= getCount()) return null;
return contacts.get(position);
}
public long getItemId(int position) {
return position;
}
public View getView(final int position, View convertView, ViewGroup parent) {
View view = null;
LinphoneContact contact = (LinphoneContact) getItem(position);
if (contact == null) return null;
ViewHolder holder = null;
if (convertView != null) {
view = convertView;
holder = (ViewHolder) view.getTag();
} else {
view = mInflater.inflate(R.layout.contact_cell, parent, false);
holder = new ViewHolder(view);
view.setTag(holder);
}
holder.name.setText(contact.getFullName());
if (!isSearchMode) {
if (getPositionForSection(getSectionForPosition(position)) != position) {
holder.separator.setVisibility(View.GONE);
@Override
public void onDeleteSelection(Object[] objectsToDelete) {
ArrayList<String> ids = new ArrayList<String>();
int size = mContactAdapter.getSelectedItemCount();
for (int i = size - 1; i >= 0; i--) {
LinphoneContact contact = (LinphoneContact)objectsToDelete[i];
if (contact.isAndroidContact()) {
contact.deleteFriend();
ids.add(contact.getAndroidId());
} else {
holder.separator.setVisibility(View.VISIBLE);
String fullName = contact.getFullName();
if (fullName != null && !fullName.isEmpty()) {
holder.separatorText.setText(String.valueOf(fullName.charAt(0)));
}
contact.delete();
}
} else {
holder.separator.setVisibility(View.GONE);
}
if (contact.isInFriendList()) {
holder.linphoneFriend.setVisibility(View.VISIBLE);
} else {
holder.linphoneFriend.setVisibility(View.GONE);
}
holder.contactPicture.setImageBitmap(ContactsManager.getInstance().getDefaultAvatarBitmap());
if (contact.hasPhoto()) {
LinphoneUtils.setThumbnailPictureFromUri(LinphoneActivity.instance(), holder.contactPicture, contact.getThumbnailUri());
}
boolean isOrgVisible = getResources().getBoolean(R.bool.display_contact_organization);
String org = contact.getOrganization();
if (org != null && !org.isEmpty() && isOrgVisible) {
holder.organization.setText(org);
holder.organization.setVisibility(View.VISIBLE);
} else {
holder.organization.setVisibility(View.GONE);
}
if (isEditMode) {
holder.delete.setVisibility(View.VISIBLE);
holder.delete.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
@Override
public void onCheckedChanged(CompoundButton compoundButton, boolean b) {
contactsList.setItemChecked(position, b);
if(getNbItemsChecked() == getCount()){
deselectAll.setVisibility(View.VISIBLE);
selectAll.setVisibility(View.GONE);
enabledDeleteButton(true);
} else {
if(getNbItemsChecked() == 0){
deselectAll.setVisibility(View.GONE);
selectAll.setVisibility(View.VISIBLE);
enabledDeleteButton(false);
} else {
deselectAll.setVisibility(View.GONE);
selectAll.setVisibility(View.VISIBLE);
enabledDeleteButton(true);
}
}
}
});
if (contactsList.isItemChecked(position)) {
holder.delete.setChecked(true);
} else {
holder.delete.setChecked(false);
}
} else {
holder.delete.setVisibility(View.INVISIBLE);
}
/*Friend[] friends = LinphoneManager.getLc().getFriendsLists();
if (!ContactsManager.getInstance().isContactPresenceDisabled() && friends != null) {
holder.friendStatus.setVisibility(View.VISIBLE);
PresenceActivityType presenceActivity = friends[0].getPresenceModel().getActivity().getType();
if (presenceActivity == PresenceActivityType.Online) {
holder.friendStatus.setImageResource(R.drawable.led_connected);
} else if (presenceActivity == PresenceActivityType.Busy) {
holder.friendStatus.setImageResource(R.drawable.led_error);
} else if (presenceActivity == PresenceActivityType.Away) {
holder.friendStatus.setImageResource(R.drawable.led_inprogress);
} else if (presenceActivity == PresenceActivityType.Offline) {
holder.friendStatus.setImageResource(R.drawable.led_disconnected);
} else {
holder.friendStatus.setImageResource(R.drawable.call_quality_indicator_0);
}
}*/
return view;
}
@Override
public Object[] getSections() {
return sections;
}
@Override
public int getPositionForSection(int sectionIndex) {
if (sectionIndex >= sections.length || sectionIndex < 0) {
return 0;
}
return map.get(sections[sectionIndex]);
}
@Override
public int getSectionForPosition(int position) {
if (position >= contacts.size() || position < 0) {
return 0;
}
LinphoneContact contact = contacts.get(position);
String fullName = contact.getFullName();
if (fullName == null || fullName.isEmpty()) {
return 0;
}
String letter = fullName.substring(0, 1).toUpperCase(Locale.getDefault());
return sectionsList.indexOf(letter);
}
ContactsManager.getInstance().deleteMultipleContactsAtOnce(ids);
}
}

View file

@ -19,12 +19,12 @@ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
package org.linphone.contacts;
import android.support.annotation.NonNull;
import android.support.v7.widget.RecyclerView;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.BaseAdapter;
import android.widget.ImageView;
import android.widget.ListView;
import android.widget.ProgressBar;
import android.widget.TextView;
@ -40,30 +40,48 @@ import org.linphone.core.SearchResult;
import java.util.ArrayList;
import java.util.List;
public class SearchContactsListAdapter extends BaseAdapter {
public class SearchContactsListAdapter extends RecyclerView.Adapter<SearchContactsListAdapter.ViewHolder> {
@SuppressWarnings("unused")
private static final String TAG = SearchContactsListAdapter.class.getSimpleName();
private class ViewHolder {
public static class ViewHolder extends RecyclerView.ViewHolder implements View.OnClickListener {
public TextView name;
public TextView address;
public ImageView linphoneContact;
public ImageView isSelect;
public ImageView avatar;
public ViewHolder(View view) {
private ClickListener listener;
public ViewHolder(View view, ClickListener listener) {
super(view);
name = view.findViewById(R.id.contact_name);
address = view.findViewById(R.id.contact_address);
linphoneContact = view.findViewById(R.id.contact_linphone);
isSelect = view.findViewById(R.id.contact_is_select);
avatar = view.findViewById(R.id.contact_picture);
this.listener=listener;
view.setOnClickListener(this);
}
@Override
public void onClick(View view) {
if (listener != null) {
listener.onItemClicked(getAdapterPosition());
}
}
public interface ClickListener {
void onItemClicked(int position);
}
}
private List<ContactAddress> contacts;
private List<ContactAddress> contactsSelected;
private LayoutInflater mInflater;
private ProgressBar progressBar;
private boolean mOnlySipContact = false;
private View.OnClickListener listener;
private ViewHolder.ClickListener listener;
public List<ContactAddress> getContacts() {
return contacts;
@ -73,17 +91,78 @@ public class SearchContactsListAdapter extends BaseAdapter {
mOnlySipContact = enable;
}
public void setListener(View.OnClickListener listener) {
public void setListener(ViewHolder.ClickListener listener) {
this.listener = listener;
}
public SearchContactsListAdapter(List<ContactAddress> contactsList, LayoutInflater inflater, ProgressBar pB) {
mInflater = inflater;
public SearchContactsListAdapter(List<ContactAddress> contactsList, ProgressBar pB, ViewHolder.ClickListener clickListener) {
this.listener=clickListener;
progressBar = pB;
setContactsSelectedList(null);
setContactsList(contactsList);
}
@NonNull
@Override
public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
View v = LayoutInflater.from(parent.getContext()).inflate(R.layout.search_contact_cell, parent, false);
return new ViewHolder(v, listener);
}
@Override
public void onBindViewHolder(@NonNull ViewHolder holder, int position) {
ContactAddress contact = getItem(position);
final String a = (contact.getAddressAsDisplayableString().isEmpty()) ? contact.getPhoneNumber() : contact.getAddressAsDisplayableString();
LinphoneContact c = contact.getContact();
holder.avatar.setImageBitmap(ContactsManager.getInstance().getDefaultAvatarBitmap());
if (c != null && c.hasPhoto()) {
LinphoneUtils.setThumbnailPictureFromUri(LinphoneActivity.instance(), holder.avatar, c.getThumbnailUri());
}
String address = contact.getAddressAsDisplayableString();
if (c != null && c.getFullName() != null) {
if (address == null)
address = c.getPresenceModelForUriOrTel(a);
holder.name.setVisibility(View.VISIBLE);
holder.name.setText(c.getFullName());
} else if (contact.getAddress() != null) {
if (contact.getAddress().getUsername() != null) {
holder.name.setVisibility(View.VISIBLE);
holder.name.setText(contact.getAddress().getUsername());
} else if (contact.getAddress().getDisplayName() != null) {
holder.name.setVisibility(View.VISIBLE);
holder.name.setText(contact.getAddress().getDisplayName());
}
} else if (address != null) {
Address tmpAddr = Factory.instance().createAddress(address);
holder.name.setVisibility(View.VISIBLE);
holder.name.setText((tmpAddr.getDisplayName() != null) ? tmpAddr.getDisplayName() : tmpAddr.getUsername()) ;
} else {
holder.name.setVisibility(View.GONE);
}
holder.address.setText(a);
if (holder.linphoneContact != null) {
if (contact.isLinphoneContact() && c != null && c.isInFriendList() && address != null) {
holder.linphoneContact.setVisibility(View.VISIBLE);
} else {
holder.linphoneContact.setVisibility(View.GONE);
}
}
if (holder.isSelect != null) {
if (contactIsSelected(contact)) {
holder.isSelect.setVisibility(View.VISIBLE);
} else {
holder.isSelect.setVisibility(View.INVISIBLE);
}
}
}
public long getItemId(int position) {
return position;
}
private boolean contactIsSelected(ContactAddress ca) {
for (ContactAddress c : contactsSelected) {
Address addr = c.getAddress();
@ -160,11 +239,14 @@ public class SearchContactsListAdapter extends BaseAdapter {
return contacts.get(position);
}
public long getItemId(int position) {
return position;
@Override
public int getItemCount() {
return contacts.size();
}
public void searchContacts(String search, ListView resultContactsSearch) {
public void searchContacts(String search, RecyclerView resultContactsSearch) {
List<ContactAddress> result = new ArrayList<>();
String domain = "";
@ -205,70 +287,5 @@ public class SearchContactsListAdapter extends BaseAdapter {
resultContactsSearch.setAdapter(this);
this.notifyDataSetChanged();
}
@Override
public View getView(int position, View convertView, ViewGroup parent) {
View view;
ViewHolder holder;
if (convertView != null) {
view = convertView;
holder = (ViewHolder) view.getTag();
} else {
view = mInflater.inflate(R.layout.search_contact_cell, parent, false);
holder = new ViewHolder(view);
view.setTag(holder);
}
ContactAddress contact = getItem(position);
final String a = (contact.getAddressAsDisplayableString().isEmpty()) ? contact.getPhoneNumber() : contact.getAddressAsDisplayableString();
LinphoneContact c = contact.getContact();
holder.avatar.setImageBitmap(ContactsManager.getInstance().getDefaultAvatarBitmap());
if (c != null && c.hasPhoto()) {
LinphoneUtils.setThumbnailPictureFromUri(LinphoneActivity.instance(), holder.avatar, c.getThumbnailUri());
}
String address = contact.getAddressAsDisplayableString();
if (c != null && c.getFullName() != null) {
if (address == null)
address = c.getPresenceModelForUriOrTel(a);
holder.name.setVisibility(View.VISIBLE);
holder.name.setText(c.getFullName());
} else if (contact.getAddress() != null) {
if (contact.getAddress().getUsername() != null) {
holder.name.setVisibility(View.VISIBLE);
holder.name.setText(contact.getAddress().getUsername());
} else if (contact.getAddress().getDisplayName() != null) {
holder.name.setVisibility(View.VISIBLE);
holder.name.setText(contact.getAddress().getDisplayName());
}
} else if (address != null) {
Address tmpAddr = Factory.instance().createAddress(address);
holder.name.setVisibility(View.VISIBLE);
holder.name.setText((tmpAddr.getDisplayName() != null) ? tmpAddr.getDisplayName() : tmpAddr.getUsername()) ;
} else {
holder.name.setVisibility(View.GONE);
}
holder.address.setText(a);
if (holder.linphoneContact != null) {
if (contact.isLinphoneContact() && c != null && c.isInFriendList() && address != null) {
holder.linphoneContact.setVisibility(View.VISIBLE);
} else {
holder.linphoneContact.setVisibility(View.GONE);
}
}
if (holder.isSelect != null) {
if (contactIsSelected(contact)) {
holder.isSelect.setVisibility(View.VISIBLE);
} else {
holder.isSelect.setVisibility(View.INVISIBLE);
}
}
view.setTag(R.id.contact_search_name, address != null ? address : a);
if (listener != null)
view.setOnClickListener(listener);
return view;
}
}

View file

@ -19,76 +19,68 @@ along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Calendar;
import java.util.List;
import org.linphone.contacts.ContactsManager;
import org.linphone.contacts.ContactsUpdatedListener;
import org.linphone.contacts.LinphoneContact;
import org.linphone.LinphoneManager;
import org.linphone.LinphoneUtils;
import org.linphone.R;
import org.linphone.activities.LinphoneActivity;
import org.linphone.core.Call;
import org.linphone.core.Address;
import org.linphone.core.CallLog;
import org.linphone.core.Call.Status;
import android.annotation.SuppressLint;
import android.app.Dialog;
import android.app.Fragment;
import android.content.Context;
import android.os.Bundle;
import android.support.v7.widget.DividerItemDecoration;
import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.RecyclerView;
import android.view.LayoutInflater;
import android.view.View;
import android.view.View.OnClickListener;
import android.view.ViewGroup;
import android.widget.AbsListView;
import android.widget.AdapterView;
import android.widget.AdapterView.OnItemClickListener;
import android.widget.BaseAdapter;
import android.widget.Button;
import android.widget.CheckBox;
import android.widget.CompoundButton;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.ListView;
import android.widget.RelativeLayout;
import android.widget.TextView;
public class HistoryListFragment extends Fragment implements OnClickListener, OnItemClickListener, ContactsUpdatedListener {
private ListView historyList;
private LayoutInflater mInflater;
import org.linphone.LinphoneManager;
import org.linphone.R;
import org.linphone.activities.LinphoneActivity;
import org.linphone.call.CallHistoryAdapter;
import org.linphone.contacts.ContactsManager;
import org.linphone.contacts.ContactsUpdatedListener;
import org.linphone.core.Address;
import org.linphone.core.Call;
import org.linphone.core.CallLog;
import org.linphone.ui.SelectableHelper;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
public class HistoryListFragment extends Fragment implements OnClickListener, OnItemClickListener, CallHistoryAdapter.ViewHolder.ClickListener ,ContactsUpdatedListener,SelectableHelper.DeleteListener{
private RecyclerView historyList;
private TextView noCallHistory, noMissedCallHistory;
private ImageView missedCalls, allCalls, edit, selectAll, deselectAll, delete, cancel;
private ImageView missedCalls, allCalls, edit;
private View allCallsSelected, missedCallsSelected;
private LinearLayout editList, topBar;
private boolean onlyDisplayMissedCalls, isEditMode;
private boolean mOnlyDisplayMissedCalls, mIsEditMode;
private List<CallLog> mLogs;
private CallHistoryAdapter mHistoryAdapter;
private LinearLayoutManager mLayoutManager;
private Context mContext;
private SelectableHelper mSelectionHelper;
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
mInflater = inflater;
View view = inflater.inflate(R.layout.history, container, false);
mContext = getActivity().getApplicationContext();
mSelectionHelper = new SelectableHelper(view, this);
noCallHistory = (TextView) view.findViewById(R.id.no_call_history);
noMissedCallHistory = (TextView) view.findViewById(R.id.no_missed_call_history);
historyList = (ListView) view.findViewById(R.id.history_list);
historyList.setOnItemClickListener(this);
historyList = (RecyclerView) view.findViewById(R.id.history_list);
delete = (ImageView) view.findViewById(R.id.delete);
delete.setOnClickListener(this);
editList = (LinearLayout) view.findViewById(R.id.edit_list);
topBar = (LinearLayout) view.findViewById(R.id.top_bar);
cancel = (ImageView) view.findViewById(R.id.cancel);
cancel.setOnClickListener(this);
mLayoutManager = new LinearLayoutManager(mContext);
historyList.setLayoutManager(mLayoutManager);
//Divider between items
DividerItemDecoration dividerItemDecoration = new DividerItemDecoration(historyList.getContext(),
mLayoutManager.getOrientation());
dividerItemDecoration.setDrawable(mContext.getResources().getDrawable(R.drawable.divider));
historyList.addItemDecoration(dividerItemDecoration);
allCalls = (ImageView) view.findViewById(R.id.all_calls);
allCalls.setOnClickListener(this);
@ -100,17 +92,10 @@ public class HistoryListFragment extends Fragment implements OnClickListener, On
missedCallsSelected = view.findViewById(R.id.missed_calls_select);
selectAll = (ImageView) view.findViewById(R.id.select_all);
selectAll.setOnClickListener(this);
deselectAll = (ImageView) view.findViewById(R.id.deselect_all);
deselectAll.setOnClickListener(this);
allCalls.setEnabled(false);
onlyDisplayMissedCalls = false;
mOnlyDisplayMissedCalls = false;
edit = (ImageView) view.findViewById(R.id.edit);
edit.setOnClickListener(this);
return view;
}
@ -119,13 +104,6 @@ public class HistoryListFragment extends Fragment implements OnClickListener, On
mLogs = Arrays.asList(LinphoneManager.getLc().getCallLogs());
}
private void selectAllList(boolean isSelectAll){
int size = historyList.getAdapter().getCount();
for(int i=0; i<size; i++) {
historyList.setItemChecked(i,isSelectAll);
}
}
public void displayFirstLog(){
if (mLogs != null && mLogs.size() > 0) {
CallLog log = mLogs.get(0);
@ -141,39 +119,8 @@ public class HistoryListFragment extends Fragment implements OnClickListener, On
}
}
private void removeCallLogs(){
int size = historyList.getAdapter().getCount();
for(int i=0; i<size; i++) {
if(historyList.isItemChecked(i)){
CallLog log = mLogs.get(i);
LinphoneManager.getLc().removeCallLog(log);
}
}
}
public int getNbItemsChecked(){
int size = historyList.getAdapter().getCount();
int nb = 0;
for(int i=0; i<size; i++) {
if(historyList.isItemChecked(i)) {
nb ++;
}
}
return nb;
}
public void enabledDeleteButton(Boolean enabled){
if(enabled){
delete.setEnabled(true);
} else {
if (getNbItemsChecked() == 0){
delete.setEnabled(false);
}
}
}
private void removeNotMissedCallsFromLogs() {
if (onlyDisplayMissedCalls) {
if (mOnlyDisplayMissedCalls) {
List<CallLog> missedCalls = new ArrayList<CallLog>();
for (CallLog log : mLogs) {
if (log.getStatus() == Call.Status.Missed) {
@ -187,7 +134,7 @@ public class HistoryListFragment extends Fragment implements OnClickListener, On
private boolean hideHistoryListAndDisplayMessageIfEmpty() {
removeNotMissedCallsFromLogs();
if (mLogs.isEmpty()) {
if (onlyDisplayMissedCalls) {
if (mOnlyDisplayMissedCalls) {
noMissedCallHistory.setVisibility(View.VISIBLE);
} else {
noCallHistory.setVisibility(View.VISIBLE);
@ -217,8 +164,10 @@ public class HistoryListFragment extends Fragment implements OnClickListener, On
mLogs = Arrays.asList(LinphoneManager.getLc().getCallLogs());
if (!hideHistoryListAndDisplayMessageIfEmpty()) {
historyList.setChoiceMode(AbsListView.CHOICE_MODE_MULTIPLE);
historyList.setAdapter(new CallHistoryAdapter(getActivity().getApplicationContext()));
mHistoryAdapter = new CallHistoryAdapter(getActivity().getApplicationContext(), mLogs, this, mSelectionHelper);
historyList.setAdapter(mHistoryAdapter);
mSelectionHelper.setAdapter(mHistoryAdapter);
mSelectionHelper.setDialogMessage(R.string.chat_room_delete_dialog);
}
}
@ -242,62 +191,12 @@ public class HistoryListFragment extends Fragment implements OnClickListener, On
public void onClick(View v) {
int id = v.getId();
if (id == R.id.select_all) {
deselectAll.setVisibility(View.VISIBLE);
selectAll.setVisibility(View.GONE);
enabledDeleteButton(true);
selectAllList(true);
return;
}
if (id == R.id.deselect_all) {
deselectAll.setVisibility(View.GONE);
selectAll.setVisibility(View.VISIBLE);
enabledDeleteButton(false);
selectAllList(false);
return;
}
if (id == R.id.cancel) {
quitEditMode();
return;
}
if (id == R.id.delete) {
if(historyList.getCheckedItemCount() == 0) {
quitEditMode();
return;
}
final Dialog dialog = LinphoneActivity.instance().displayDialog(getString(R.string.delete_text));
Button delete = (Button) dialog.findViewById(R.id.delete_button);
Button cancel = (Button) dialog.findViewById(R.id.cancel);
delete.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View view) {
removeCallLogs();
dialog.dismiss();
quitEditMode();
}
});
cancel.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View view) {
dialog.dismiss();
quitEditMode();
}
});
dialog.show();
return;
}
if (id == R.id.all_calls) {
allCalls.setEnabled(false);
allCallsSelected.setVisibility(View.VISIBLE);
missedCallsSelected.setVisibility(View.INVISIBLE);
missedCalls.setEnabled(true);
onlyDisplayMissedCalls = false;
mOnlyDisplayMissedCalls = false;
refresh();
}
if (id == R.id.missed_calls) {
@ -305,251 +204,61 @@ public class HistoryListFragment extends Fragment implements OnClickListener, On
allCallsSelected.setVisibility(View.INVISIBLE);
missedCallsSelected.setVisibility(View.VISIBLE);
missedCalls.setEnabled(false);
onlyDisplayMissedCalls = true;
mOnlyDisplayMissedCalls = true;
}
if (id == R.id.edit) {
topBar.setVisibility(View.GONE);
editList.setVisibility(View.VISIBLE);
enabledDeleteButton(false);
isEditMode = true;
}
if (!hideHistoryListAndDisplayMessageIfEmpty()) {
historyList.setChoiceMode(AbsListView.CHOICE_MODE_MULTIPLE);
historyList.setAdapter(new CallHistoryAdapter(getActivity().getApplicationContext()));
}
if(isEditMode){
deselectAll.setVisibility(View.GONE);
selectAll.setVisibility(View.VISIBLE);
// historyList.setChoiceMode(AbsListView.CHOICE_MODE_MULTIPLE);
mHistoryAdapter = new CallHistoryAdapter(mContext, mLogs, this ,mSelectionHelper);
historyList.setAdapter(mHistoryAdapter);
mSelectionHelper.setAdapter(mHistoryAdapter);
mSelectionHelper.setDialogMessage(R.string.chat_room_delete_dialog);
}
}
@Override
public void onItemClick(AdapterView<?> adapter, View view, int position, long id) {
if (isEditMode) {
if (mHistoryAdapter.isEditionEnabled()) {
CallLog log = mLogs.get(position);
LinphoneManager.getLc().removeCallLog(log);
mLogs = Arrays.asList(LinphoneManager.getLc().getCallLogs());
}
}
public void quitEditMode(){
isEditMode = false;
editList.setVisibility(View.GONE);
topBar.setVisibility(View.VISIBLE);
refresh();
if (!hideHistoryListAndDisplayMessageIfEmpty()) {
historyList.setChoiceMode(AbsListView.CHOICE_MODE_MULTIPLE);
historyList.setAdapter(new CallHistoryAdapter(getActivity().getApplicationContext()));
}
if (getResources().getBoolean(R.bool.isTablet)) {
displayFirstLog();
@Override
public void onDeleteSelection(Object[] objectsToDelete) {
int size = mHistoryAdapter.getSelectedItemCount();
for(int i=0; i<size; i++) {
CallLog log = (CallLog) objectsToDelete[i];
LinphoneManager.getLc().removeCallLog(log);
onResume();
}
}
class CallHistoryAdapter extends BaseAdapter {
private class ViewHolder {
public TextView contact;
public ImageView detail;
public CheckBox select;
public ImageView callDirection;
public ImageView contactPicture;
public RelativeLayout CallContact;
public ViewHolder(View view) {
contact = (TextView) view.findViewById(R.id.sip_uri);
detail = (ImageView) view.findViewById(R.id.detail);
select = (CheckBox) view.findViewById(R.id.delete);
callDirection = (ImageView) view.findViewById(R.id.icon);
contactPicture = (ImageView) view.findViewById(R.id.contact_picture);
CallContact = (RelativeLayout) view.findViewById(R.id.history_click);
}
}
CallHistoryAdapter(Context aContext) {
}
public int getCount() {
return mLogs.size();
}
public Object getItem(int position) {
return mLogs.get(position);
}
public long getItemId(int position) {
return position;
}
@SuppressLint("SimpleDateFormat")
private String timestampToHumanDate(Calendar cal) {
SimpleDateFormat dateFormat;
if (isToday(cal)) {
return getString(R.string.today);
} else if (isYesterday(cal)) {
return getString(R.string.yesterday);
} else {
dateFormat = new SimpleDateFormat(getResources().getString(R.string.history_date_format));
}
return dateFormat.format(cal.getTime());
}
private boolean isSameDay(Calendar cal1, Calendar cal2) {
if (cal1 == null || cal2 == null) {
return false;
}
return (cal1.get(Calendar.ERA) == cal2.get(Calendar.ERA) &&
cal1.get(Calendar.YEAR) == cal2.get(Calendar.YEAR) &&
cal1.get(Calendar.DAY_OF_YEAR) == cal2.get(Calendar.DAY_OF_YEAR));
}
private boolean isToday(Calendar cal) {
return isSameDay(cal, Calendar.getInstance());
}
private boolean isYesterday(Calendar cal) {
Calendar yesterday = Calendar.getInstance();
yesterday.roll(Calendar.DAY_OF_MONTH, -1);
return isSameDay(cal, yesterday);
}
public View getView(final int position, View convertView, ViewGroup parent) {
View view = null;
ViewHolder holder = null;
if (convertView != null) {
view = convertView;
holder = (ViewHolder) view.getTag();
} else {
view = mInflater.inflate(R.layout.history_cell, parent,false);
holder = new ViewHolder(view);
view.setTag(holder);
}
if (mLogs == null || mLogs.size() < position) return view;
final CallLog log = mLogs.get(position);
long timestamp = log.getStartDate() * 1000;
Address address;
holder.contact.setSelected(true); // For automated horizontal scrolling of long texts
LinearLayout separator = (LinearLayout) view.findViewById(R.id.separator);
TextView separatorText = (TextView) view.findViewById(R.id.separator_text);
Calendar logTime = Calendar.getInstance();
logTime.setTimeInMillis(timestamp);
separatorText.setText(timestampToHumanDate(logTime));
if (position > 0) {
CallLog previousLog = mLogs.get(position-1);
long previousTimestamp = previousLog.getStartDate() * 1000;
Calendar previousLogTime = Calendar.getInstance();
previousLogTime.setTimeInMillis(previousTimestamp);
if (isSameDay(previousLogTime, logTime)) {
separator.setVisibility(View.GONE);
@Override
public void onItemClicked(int position) {
if (mHistoryAdapter.isEditionEnabled()) {
mHistoryAdapter.toggleSelection(position);
} else {
if (LinphoneActivity.isInstanciated()) {
CallLog log = mLogs.get(position);
Address address;
if (log.getDir() == Call.Dir.Incoming) {
address = log.getFromAddress();
} else {
separator.setVisibility(View.VISIBLE);
address = log.getToAddress();
}
} else {
separator.setVisibility(View.VISIBLE);
LinphoneActivity.instance().setAddresGoToDialerAndCall(address.asStringUriOnly(), address.getDisplayName(), null);
}
if (log.getDir() == Call.Dir.Incoming) {
address = log.getFromAddress();
if (log.getStatus() == Call.Status.Missed) {
holder.callDirection.setImageResource(R.drawable.call_status_missed);
} else {
holder.callDirection.setImageResource(R.drawable.call_status_incoming);
}
} else {
address = log.getToAddress();
holder.callDirection.setImageResource(R.drawable.call_status_outgoing);
}
LinphoneContact c = ContactsManager.getInstance().findContactFromAddress(address);
String displayName = null;
final String sipUri = (address != null) ? address.asString() : "";
if (c != null) {
displayName = c.getFullName();
LinphoneUtils.setThumbnailPictureFromUri(LinphoneActivity.instance(), holder.contactPicture, c.getThumbnailUri());
} else {
holder.contactPicture.setImageBitmap(ContactsManager.getInstance().getDefaultAvatarBitmap());
}
if (displayName == null) {
holder.contact.setText(LinphoneUtils.getAddressDisplayName(sipUri));
} else {
holder.contact.setText(displayName);
}
if (isEditMode) {
holder.CallContact.setOnClickListener(null);
holder.select.setVisibility(View.VISIBLE);
holder.select.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
@Override
public void onCheckedChanged(CompoundButton compoundButton, boolean b) {
historyList.setItemChecked(position, b);
if(getNbItemsChecked() == getCount()){
deselectAll.setVisibility(View.VISIBLE);
selectAll.setVisibility(View.GONE);
enabledDeleteButton(true);
} else {
if(getNbItemsChecked() == 0){
deselectAll.setVisibility(View.GONE);
selectAll.setVisibility(View.VISIBLE);
enabledDeleteButton(false);
} else {
deselectAll.setVisibility(View.GONE);
selectAll.setVisibility(View.VISIBLE);
enabledDeleteButton(true);
}
}
}
});
holder.detail.setVisibility(View.INVISIBLE);
if(historyList.isItemChecked(position)) {
holder.select.setChecked(true);
} else {
holder.select.setChecked(false);
}
} else {
holder.select.setVisibility(View.GONE);
holder.detail.setVisibility(View.VISIBLE);
holder.detail.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
if (LinphoneActivity.isInstanciated()) {
LinphoneActivity.instance().displayHistoryDetail(sipUri, log);
}
}
});
holder.CallContact.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
if (LinphoneActivity.isInstanciated()) {
CallLog log = mLogs.get(position);
Address address;
if (log.getDir() == Call.Dir.Incoming) {
address = log.getFromAddress();
} else {
address = log.getToAddress();
}
if (address != null) {
LinphoneActivity.instance().setAddresGoToDialerAndCall(address.asStringUriOnly(), address.getDisplayName(), null);
}
}
}
});
}
return view;
}
}
@Override
public boolean onItemLongClicked(int position) {
if (!mHistoryAdapter.isEditionEnabled()) {
mSelectionHelper.enterEditionMode();
}
mHistoryAdapter.toggleSelection(position);
return true;
}
}

View file

@ -0,0 +1,111 @@
package org.linphone.ui;
/*
SelectableAdapter.java
Copyright (C) 2018 Belledonne Communications, Grenoble, France
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 2
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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
import android.support.v7.widget.RecyclerView;
import android.util.SparseBooleanArray;
import java.util.ArrayList;
import java.util.List;
public abstract class SelectableAdapter<VH extends RecyclerView.ViewHolder> extends RecyclerView.Adapter<VH> {
@SuppressWarnings("unused")
private static final String TAG = SelectableAdapter.class.getSimpleName();
private SparseBooleanArray mSelectedItems;
private boolean mIsEditionEnabled=false;
private SelectableHelper mListHelper;
public SelectableAdapter(SelectableHelper helper) {
mSelectedItems = new SparseBooleanArray();
mListHelper = helper;
}
public boolean isEditionEnabled() {
return mIsEditionEnabled;
}
public void enableEdition(boolean set) {
mIsEditionEnabled = set;
mSelectedItems.clear();
notifyDataSetChanged();
}
/**
* Indicates if the item at position position is selected
* @param position Position of the item to check
* @return true if the item is selected, false otherwise
*/
public boolean isSelected(int position) {
return getSelectedItems().contains(position);
}
/**
* Toggle the selection status of the item at a given position
* @param position Position of the item to toggle the selection status for
*/
public void toggleSelection(int position) {
if (mSelectedItems.get(position, false)) {
mSelectedItems.delete(position);
} else {
mSelectedItems.put(position, true);
}
mListHelper.updateSelectionButtons(getSelectedItemCount() == 0, getSelectedItemCount() == getItemCount());
notifyItemChanged(position);
}
/**
* Count the selected items
* @return Selected items count
*/
public int getSelectedItemCount() {
return mSelectedItems.size();
}
/**
* Indicates the list of selected items
* @return List of selected items ids
*/
public List<Integer> getSelectedItems() {
List<Integer> items = new ArrayList<>(mSelectedItems.size());
for (int i = 0; i < mSelectedItems.size(); ++i) {
items.add(mSelectedItems.keyAt(i));
}
return items;
}
public void selectAll() {
for (Integer i = 0; i < getItemCount(); i++) {
mSelectedItems.put(i, true);
notifyDataSetChanged();
}
mListHelper.updateSelectionButtons(false, true);
}
public void deselectAll() {
mSelectedItems.clear();
mListHelper.updateSelectionButtons(true, false);
notifyDataSetChanged();
}
public abstract Object getItem(int position);
}

View file

@ -0,0 +1,179 @@
/*
SelectableHelper.java
Copyright (C) 2017 Belledonne Communications, Grenoble, France
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 2
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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
package org.linphone.ui;
import android.app.Dialog;
import android.content.Context;
import android.support.v7.widget.RecyclerView;
import android.view.View;
import android.widget.Button;
import android.widget.ImageView;
import android.widget.LinearLayout;
import org.linphone.R;
import org.linphone.activities.LinphoneActivity;
public class SelectableHelper {
private ImageView mEditButton, mSelectAllButton, mDeselectAllButton, mDeleteSelectionButton, mCancelButton;
private LinearLayout mEditTopBar, mTopBar;
private SelectableAdapter<RecyclerView.ViewHolder> mAdapter;
private DeleteListener mDeleteListener;
private Context mContext;
private int mDialogDeleteMessageResourceId;
public void setDialogMessage(int id) {
mDialogDeleteMessageResourceId = id;
}
public interface DeleteListener {
void onDeleteSelection(Object[] objectsToDelete);
}
public SelectableHelper(View view, DeleteListener listener) {
mContext = view.getContext();
mDeleteListener = listener;
//Define XML layout items used for selection mode
mEditTopBar = view.findViewById(R.id.edit_list);
mTopBar = view.findViewById(R.id.top_bar);
mCancelButton = view.findViewById(R.id.cancel);
mCancelButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
quitEditionMode();
}
});
mEditButton = view.findViewById(R.id.edit);
//Set visibility behaviour on click on edit button + enter Edition Mode
mEditButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
if (mAdapter.getItemCount() > 0) {
enterEditionMode();
mTopBar.setVisibility(View.GONE);
mEditTopBar.setVisibility(View.VISIBLE);
}
}
});
mSelectAllButton = view.findViewById(R.id.select_all);
mSelectAllButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
mAdapter.selectAll();
}
});
mDeselectAllButton = view.findViewById(R.id.deselect_all);
mDeselectAllButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
mAdapter.deselectAll();
}
});
mDeleteSelectionButton = view.findViewById(R.id.delete);
mDeleteSelectionButton.setEnabled(false);
//Display confirmation for deletion
mDeleteSelectionButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
final Dialog dialog = LinphoneActivity.instance().displayDialog(mContext.getString(mDialogDeleteMessageResourceId));
Button delete = dialog.findViewById(R.id.delete_button);
Button cancel = dialog.findViewById(R.id.cancel);
delete.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
mDeleteListener.onDeleteSelection(getSelectedObjects());
dialog.dismiss();
quitEditionMode();
}
});
cancel.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
dialog.dismiss();
quitEditionMode();
}
});
dialog.show();
}
});
mDialogDeleteMessageResourceId = R.string.delete_text;
}
public void setAdapter(SelectableAdapter adapter) {
mAdapter=adapter;
}
public void updateSelectionButtons(boolean isSelectionEmpty, boolean isSelectionFull) {
//Delete button availability
if (isSelectionEmpty) {
mDeleteSelectionButton.setEnabled(false);
} else {
mDeleteSelectionButton.setEnabled(true);
}
//(De)select button visibility
if (isSelectionFull) {
mSelectAllButton.setVisibility(View.GONE);
mDeselectAllButton.setVisibility(View.VISIBLE);
} else {
mSelectAllButton.setVisibility(View.VISIBLE);
mDeselectAllButton.setVisibility(View.GONE);
}
}
public void quitEditionMode() {
mAdapter.enableEdition(false);
mTopBar.setVisibility(View.VISIBLE);
mEditTopBar.setVisibility(View.GONE);
mDeleteSelectionButton.setEnabled(false);
mSelectAllButton.setVisibility(View.GONE);
mDeselectAllButton.setVisibility(View.VISIBLE);
}
public void enterEditionMode() {
mAdapter.enableEdition(true);
mTopBar.setVisibility(View.GONE);
mEditTopBar.setVisibility(View.VISIBLE);
mDeleteSelectionButton.setEnabled(false);
mSelectAllButton.setVisibility(View.VISIBLE);
mDeselectAllButton.setVisibility(View.GONE);
}
private Object[] getSelectedObjects() {
Object objects[] = new Object[mAdapter.getSelectedItemCount()];
int index = 0;
for (Integer i : mAdapter.getSelectedItems()) {
objects[index] = mAdapter.getItem(i);
index++;
}
return objects;
}
}