Started new chat bubble design

This commit is contained in:
Sylvain Berfini 2018-11-23 16:00:44 +01:00
parent f7c2f01a31
commit cd0148d4b0
18 changed files with 469 additions and 774 deletions

View file

@ -21,8 +21,9 @@ package org.linphone.chat;
import android.content.Context; import android.content.Context;
import android.support.v7.widget.RecyclerView; import android.support.v7.widget.RecyclerView;
import android.text.Spanned;
import android.text.method.LinkMovementMethod;
import android.view.View; import android.view.View;
import android.widget.Button;
import android.widget.CheckBox; import android.widget.CheckBox;
import android.widget.ImageView; import android.widget.ImageView;
import android.widget.LinearLayout; import android.widget.LinearLayout;
@ -31,36 +32,38 @@ import android.widget.RelativeLayout;
import android.widget.TextView; import android.widget.TextView;
import org.linphone.R; import org.linphone.R;
import org.linphone.contacts.ContactsManager;
import org.linphone.contacts.LinphoneContact;
import org.linphone.core.Address;
import org.linphone.core.ChatMessage; import org.linphone.core.ChatMessage;
import org.linphone.core.Content;
import org.linphone.utils.LinphoneUtils;
import org.linphone.views.ContactAvatar;
import java.util.ArrayList;
import java.util.List;
public class ChatMessageViewHolder extends RecyclerView.ViewHolder implements View.OnClickListener { public class ChatMessageViewHolder extends RecyclerView.ViewHolder implements View.OnClickListener {
public String messageId;
public Context mContext; public Context mContext;
public ChatMessage message; public ChatMessage message;
public LinearLayout eventLayout; public LinearLayout eventLayout;
public TextView eventMessage; public TextView eventMessage;
public LinearLayout securityEventLayout;
public TextView securityEventMessage;
public View rightAnchor;
public RelativeLayout bubbleLayout; public RelativeLayout bubbleLayout;
public LinearLayout separatorLayout; public RelativeLayout background;
public LinearLayout background;
public RelativeLayout avatarLayout; public RelativeLayout avatarLayout;
public TextView contactName;
public ImageView messageStatus;
public ProgressBar messageSendingInProgress;
public LinearLayout imdmLayout;
public ImageView imdmIcon;
public TextView imdmLabel;
public ProgressBar sendInProgress;
public TextView timeText;
public ImageView outgoingImdn;
public TextView messageText; public TextView messageText;
public ImageView messageImage;
public RelativeLayout fileTransferLayout; public RecyclerView pictures;
public ProgressBar fileTransferProgressBar;
public Button fileTransferAction;
public TextView fileName;
public Button openFileButton;
public CheckBox delete; public CheckBox delete;
private ClickListener mListener; private ClickListener mListener;
@ -75,30 +78,22 @@ public class ChatMessageViewHolder extends RecyclerView.ViewHolder implements Vi
public ChatMessageViewHolder(View view) { public ChatMessageViewHolder(View view) {
super(view); super(view);
eventLayout = view.findViewById(R.id.event); eventLayout = view.findViewById(R.id.event);
//eventTime = view.findViewById(R.id.event_date);
eventMessage = view.findViewById(R.id.event_text); eventMessage = view.findViewById(R.id.event_text);
securityEventLayout = view.findViewById(R.id.event);
securityEventMessage = view.findViewById(R.id.event_text);
rightAnchor = view.findViewById(R.id.rightAnchor);
bubbleLayout = view.findViewById(R.id.bubble); bubbleLayout = view.findViewById(R.id.bubble);
background = view.findViewById(R.id.background); background = view.findViewById(R.id.background);
avatarLayout = view.findViewById(R.id.avatar_layout); avatarLayout = view.findViewById(R.id.avatar_layout);
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);
sendInProgress = view.findViewById(R.id.send_in_progress);
timeText = view.findViewById(R.id.time);
outgoingImdn = view.findViewById(R.id.imdn);
messageText = view.findViewById(R.id.message); messageText = view.findViewById(R.id.message);
messageImage = view.findViewById(R.id.image);
separatorLayout = view.findViewById(R.id.separator);
fileTransferLayout = view.findViewById(R.id.file_transfer_layout); pictures = view.findViewById(R.id.pictures);
fileTransferProgressBar = view.findViewById(R.id.progress_bar);
fileTransferAction = view.findViewById(R.id.file_transfer_action);
fileName = view.findViewById(R.id.file_name);
openFileButton = view.findViewById(R.id.open_file);
delete = view.findViewById(R.id.delete_message); delete = view.findViewById(R.id.delete_message);
} }
@ -113,4 +108,94 @@ public class ChatMessageViewHolder extends RecyclerView.ViewHolder implements Vi
public interface ClickListener { public interface ClickListener {
void onItemClicked(int position); void onItemClicked(int position);
} }
public void bindMessage(ChatMessage message, LinphoneContact contact) {
eventLayout.setVisibility(View.GONE);
securityEventLayout.setVisibility(View.GONE);
rightAnchor.setVisibility(View.VISIBLE);
bubbleLayout.setVisibility(View.VISIBLE);
messageText.setVisibility(View.GONE);
timeText.setVisibility(View.VISIBLE);
outgoingImdn.setVisibility(View.GONE);
avatarLayout.setVisibility(View.GONE);
pictures.setVisibility(View.GONE);
sendInProgress.setVisibility(View.GONE);
ChatMessage.State status = message.getState();
Address remoteSender = message.getFromAddress();
String displayName;
String time = LinphoneUtils.timestampToHumanDate(mContext, message.getTime(), R.string.messages_date_format);
if (message.isOutgoing()) {
outgoingImdn.setVisibility(View.INVISIBLE); // For anchoring purposes
if (status == ChatMessage.State.DeliveredToUser) {
outgoingImdn.setVisibility(View.VISIBLE);
outgoingImdn.setImageResource(R.drawable.imdn_received);
} else if (status == ChatMessage.State.Displayed) {
outgoingImdn.setVisibility(View.VISIBLE);
outgoingImdn.setImageResource(R.drawable.imdn_read);
} else if (status == ChatMessage.State.NotDelivered) {
outgoingImdn.setVisibility(View.VISIBLE);
outgoingImdn.setImageResource(R.drawable.imdn_error);
} else if (status == ChatMessage.State.FileTransferError) {
outgoingImdn.setVisibility(View.VISIBLE);
outgoingImdn.setImageResource(R.drawable.imdn_error);
} else if (status == ChatMessage.State.InProgress) {
sendInProgress.setVisibility(View.VISIBLE);
}
timeText.setVisibility(View.VISIBLE);
background.setBackgroundResource(R.drawable.chat_bubble_outgoing_full);
} else {
rightAnchor.setVisibility(View.GONE);
avatarLayout.setVisibility(View.VISIBLE);
background.setBackgroundResource(R.drawable.chat_bubble_incoming_full);
}
if (contact == null) {
contact = ContactsManager.getInstance().findContactFromAddress(remoteSender);
}
if (contact != null) {
if (contact.getFullName() != null) {
displayName = contact.getFullName();
} else {
displayName = LinphoneUtils.getAddressDisplayName(remoteSender);
}
ContactAvatar.displayAvatar(contact, avatarLayout);
} else {
displayName = LinphoneUtils.getAddressDisplayName(remoteSender);
ContactAvatar.displayAvatar(displayName, avatarLayout);
}
if (message.isOutgoing()) {
timeText.setText(time);
} else {
timeText.setText(time + " - " + displayName);
}
if (message.hasTextContent()) {
String msg = message.getTextContent();
Spanned text = LinphoneUtils.getTextWithHttpLinks(msg);
messageText.setText(text);
messageText.setMovementMethod(LinkMovementMethod.getInstance());
messageText.setVisibility(View.VISIBLE);
}
List<Content> fileContents = new ArrayList<>();
for (Content c : message.getContents()) {
if (c.isFile() || c.isFileTransfer()) {
fileContents.add(c);
}
}
/*if (fileContents.size() > 0) {
pictures.setVisibility(View.VISIBLE);
mAdapter = new ChatBubbleFilesAdapter(mContext, message, fileContents);
pictures.setAdapter(mAdapter);
pictures.setHasFixedSize(true);
mLayoutManager = new StaggeredGridLayoutManager(1, StaggeredGridLayoutManager.HORIZONTAL);
pictures.setLayoutManager(mLayoutManager);
}*/
}
} }

View file

@ -19,72 +19,36 @@ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
package org.linphone.chat; package org.linphone.chat;
import android.Manifest;
import android.content.Context; import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.content.res.Resources;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Matrix;
import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.Drawable;
import android.media.ExifInterface;
import android.net.Uri;
import android.os.AsyncTask;
import android.provider.MediaStore;
import android.support.annotation.NonNull; import android.support.annotation.NonNull;
import android.support.v4.content.FileProvider;
import android.text.Spanned;
import android.text.method.LinkMovementMethod;
import android.view.LayoutInflater; import android.view.LayoutInflater;
import android.view.View; import android.view.View;
import android.view.ViewGroup; import android.view.ViewGroup;
import android.webkit.MimeTypeMap;
import android.widget.ImageView;
import android.widget.RelativeLayout;
import org.linphone.LinphoneManager;
import org.linphone.utils.FileUtils;
import org.linphone.utils.LinphoneUtils; import org.linphone.utils.LinphoneUtils;
import org.linphone.R; import org.linphone.R;
import org.linphone.LinphoneActivity;
import org.linphone.compatibility.Compatibility;
import org.linphone.contacts.ContactsManager; import org.linphone.contacts.ContactsManager;
import org.linphone.contacts.LinphoneContact; import org.linphone.contacts.LinphoneContact;
import org.linphone.core.Address; import org.linphone.core.Address;
import org.linphone.core.ChatMessage; import org.linphone.core.ChatMessage;
import org.linphone.core.ChatMessageListenerStub; import org.linphone.core.ChatMessageListenerStub;
import org.linphone.core.Content;
import org.linphone.core.EventLog; import org.linphone.core.EventLog;
import org.linphone.core.LimeState;
import org.linphone.mediastream.Log;
import org.linphone.views.ContactAvatar;
import org.linphone.utils.SelectableAdapter; import org.linphone.utils.SelectableAdapter;
import org.linphone.utils.SelectableHelper; import org.linphone.utils.SelectableHelper;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.lang.ref.WeakReference;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.Collections; import java.util.Collections;
import java.util.List; import java.util.List;
import static android.content.Intent.FLAG_GRANT_READ_URI_PERMISSION;
public class ChatMessagesAdapter extends SelectableAdapter<ChatMessageViewHolder> { public class ChatMessagesAdapter extends SelectableAdapter<ChatMessageViewHolder> {
public static int MAX_TIME_TO_GROUP_MESSAGES = 60;
private static int MARGIN_BETWEEN_MESSAGES = 10;
private static int SIDE_MARGIN = 100;
private Context mContext; private Context mContext;
private List<EventLog> mHistory; private List<EventLog> mHistory;
private List<LinphoneContact> mParticipants; private List<LinphoneContact> mParticipants;
private int mItemResource; private int mItemResource;
private Bitmap mDefaultBitmap;
private ChatMessagesFragment mFragment; private ChatMessagesFragment mFragment;
private ChatMessageListenerStub mListener;
private ChatMessageViewHolder.ClickListener mClickListener; private ChatMessageViewHolder.ClickListener mClickListener;
@ -97,42 +61,6 @@ public class ChatMessagesAdapter extends SelectableAdapter<ChatMessageViewHolder
Collections.reverse(mHistory); Collections.reverse(mHistory);
mParticipants = participants; mParticipants = participants;
mClickListener = clickListener; mClickListener = clickListener;
mListener = new ChatMessageListenerStub() {
@Override
public void onFileTransferProgressIndication(ChatMessage message, Content content, int offset, int total) {
ChatMessageViewHolder holder = (ChatMessageViewHolder) message.getUserData();
if (holder == null) return;
if (offset == total) {
holder.fileTransferProgressBar.setVisibility(View.GONE);
holder.fileTransferAction.setVisibility(View.GONE);
holder.fileTransferLayout.setVisibility(View.GONE);
displayAttachedFile(message, holder);
} else {
holder.fileTransferProgressBar.setVisibility(View.VISIBLE);
holder.fileTransferProgressBar.setProgress(offset * 100 / total);
}
}
@Override
public void onMsgStateChanged(ChatMessage message, ChatMessage.State state) {
if (state == ChatMessage.State.FileTransferDone) {
if (!message.isOutgoing()) {
message.setAppdata(message.getFileTransferFilepath());
}
message.setFileTransferFilepath(null); // Not needed anymore, will help differenciate between InProgress states for file transfer / message sending
}
for (int i = 0; i < mHistory.size(); i++) {
EventLog log = mHistory.get(i);
if (log.getType() == EventLog.Type.ConferenceChatMessage && log.getChatMessage() == message) {
notifyItemChanged(i);
break;
}
}
}
};
} }
@Override @Override
@ -149,20 +77,14 @@ public class ChatMessagesAdapter extends SelectableAdapter<ChatMessageViewHolder
@Override @Override
public void onBindViewHolder(@NonNull final ChatMessageViewHolder holder, int position) { public void onBindViewHolder(@NonNull final ChatMessageViewHolder holder, int position) {
final EventLog event = mHistory.get(position); EventLog event = mHistory.get(position);
holder.delete.setVisibility(View.GONE);
holder.eventLayout.setVisibility(View.GONE); holder.eventLayout.setVisibility(View.GONE);
holder.securityEventLayout.setVisibility(View.GONE);
holder.rightAnchor.setVisibility(View.GONE);
holder.bubbleLayout.setVisibility(View.GONE); holder.bubbleLayout.setVisibility(View.GONE);
holder.delete.setVisibility(isEditionEnabled() ? View.VISIBLE : View.GONE); holder.sendInProgress.setVisibility(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);
if (isEditionEnabled()) { if (isEditionEnabled()) {
holder.delete.setOnCheckedChangeListener(null); holder.delete.setOnCheckedChangeListener(null);
@ -171,198 +93,33 @@ public class ChatMessagesAdapter extends SelectableAdapter<ChatMessageViewHolder
} }
if (event.getType() == EventLog.Type.ConferenceChatMessage) { if (event.getType() == EventLog.Type.ConferenceChatMessage) {
holder.bubbleLayout.setVisibility(View.VISIBLE); ChatMessage message = event.getChatMessage();
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); 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; LinphoneContact contact = null;
if (message.isOutgoing()) { Address remoteSender = message.getFromAddress();
message.setListener(mListener); if (!message.isOutgoing()) {
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.DeliveredToUser) {
holder.imdmLayout.setVisibility(View.VISIBLE);
holder.imdmIcon.setImageResource(R.drawable.imdn_received);
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.imdn_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.imdn_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.imdn_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);
ContactAvatar.setAvatarMask(holder.avatarLayout, R.drawable.avatar_chat_mask_outgoing);
} else {
for (LinphoneContact c : mParticipants) { for (LinphoneContact c : mParticipants) {
if (c != null && c.hasAddress(remoteSender.asStringUriOnly())) { if (c != null && c.hasAddress(remoteSender.asStringUriOnly())) {
contact = c; contact = c;
break; break;
} }
} }
}
holder.bindMessage(message, contact);
changeBackgroundDependingOnPreviousAndNextEvents(message, holder, position);
if (isEditionEnabled()) { message.setListener(new ChatMessageListenerStub() {
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);
ContactAvatar.setAvatarMask(holder.avatarLayout, 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);
}
ContactAvatar.displayAvatar(contact, holder.avatarLayout);
} else {
displayName = LinphoneUtils.getAddressDisplayName(remoteSender);
ContactAvatar.displayAvatar(displayName, holder.avatarLayout);
}
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();
boolean hasFile = message.getAppdata() != null;
boolean hasFileTransfer = externalBodyUrl != null;
for (Content c : message.getContents()) {
if (c.isFile()) {
hasFile = true;
} else if (c.isFileTransfer()) {
hasFileTransfer = true;
}
}
if (hasFile) { // Something to display
displayAttachedFile(message, holder);
}
if (hasFileTransfer) { // 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 @Override
public void onClick(View v) { public void onMsgStateChanged(ChatMessage message, ChatMessage.State state) {
if (mContext.getPackageManager().checkPermission(Manifest.permission.WRITE_EXTERNAL_STORAGE, mContext.getPackageName()) == PackageManager.PERMISSION_GRANTED) { ChatMessageViewHolder holder = (ChatMessageViewHolder) message.getUserData();
v.setEnabled(false); if (holder != null) {
String filename = message.getFileTransferInformation().getName(); holder.bindMessage(message, null);
File file = new File(FileUtils.getStorageDirectory(mContext), filename); changeBackgroundDependingOnPreviousAndNextEvents(message, holder, holder.getAdapterPosition());
int prefix = 1;
while (file.exists()) {
file = new File(FileUtils.getStorageDirectory(mContext), 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();
notifyItemChanged(holder.getAdapterPosition());
}
});
}
holder.bubbleLayout.setLayoutParams(layoutParams);
} else { // Event is not chat message } else { // Event is not chat message
holder.eventLayout.setVisibility(View.VISIBLE);
holder.eventMessage.setTextColor(mContext.getResources().getColor(R.color.colorE));
holder.eventLayout.setBackgroundResource(R.drawable.event_decoration_gray);
holder.eventLayout.setBackgroundResource(R.drawable.event_decoration_gray);
Address address = event.getParticipantAddress(); Address address = event.getParticipantAddress();
String displayName = null; String displayName = null;
if (address != null) { if (address != null) {
@ -376,49 +133,57 @@ public class ChatMessagesAdapter extends SelectableAdapter<ChatMessageViewHolder
switch (event.getType()) { switch (event.getType()) {
case ConferenceCreated: case ConferenceCreated:
holder.eventLayout.setVisibility(View.VISIBLE);
holder.eventMessage.setText(mContext.getString(R.string.conference_created)); holder.eventMessage.setText(mContext.getString(R.string.conference_created));
break; break;
case ConferenceTerminated: case ConferenceTerminated:
holder.eventLayout.setVisibility(View.VISIBLE);
holder.eventMessage.setText(mContext.getString(R.string.conference_destroyed)); holder.eventMessage.setText(mContext.getString(R.string.conference_destroyed));
break; break;
case ConferenceParticipantAdded: case ConferenceParticipantAdded:
holder.eventLayout.setVisibility(View.VISIBLE);
holder.eventMessage.setText(mContext.getString(R.string.participant_added).replace("%s", displayName)); holder.eventMessage.setText(mContext.getString(R.string.participant_added).replace("%s", displayName));
break; break;
case ConferenceParticipantRemoved: case ConferenceParticipantRemoved:
holder.eventLayout.setVisibility(View.VISIBLE);
holder.eventMessage.setText(mContext.getString(R.string.participant_removed).replace("%s", displayName)); holder.eventMessage.setText(mContext.getString(R.string.participant_removed).replace("%s", displayName));
break; break;
case ConferenceSubjectChanged: case ConferenceSubjectChanged:
holder.eventLayout.setVisibility(View.VISIBLE);
holder.eventMessage.setText(mContext.getString(R.string.subject_changed).replace("%s", event.getSubject())); holder.eventMessage.setText(mContext.getString(R.string.subject_changed).replace("%s", event.getSubject()));
break; break;
case ConferenceParticipantSetAdmin: case ConferenceParticipantSetAdmin:
holder.eventLayout.setVisibility(View.VISIBLE);
holder.eventMessage.setText(mContext.getString(R.string.admin_set).replace("%s", displayName)); holder.eventMessage.setText(mContext.getString(R.string.admin_set).replace("%s", displayName));
break; break;
case ConferenceParticipantUnsetAdmin: case ConferenceParticipantUnsetAdmin:
holder.eventLayout.setVisibility(View.VISIBLE);
holder.eventMessage.setText(mContext.getString(R.string.admin_unset).replace("%s", displayName)); holder.eventMessage.setText(mContext.getString(R.string.admin_unset).replace("%s", displayName));
break; break;
case ConferenceParticipantDeviceAdded: case ConferenceParticipantDeviceAdded:
holder.eventLayout.setVisibility(View.VISIBLE);
holder.eventMessage.setText(mContext.getString(R.string.device_added).replace("%s", displayName)); holder.eventMessage.setText(mContext.getString(R.string.device_added).replace("%s", displayName));
break; break;
case ConferenceParticipantDeviceRemoved: case ConferenceParticipantDeviceRemoved:
holder.eventLayout.setVisibility(View.VISIBLE);
holder.eventMessage.setText(mContext.getString(R.string.device_removed).replace("%s", displayName)); holder.eventMessage.setText(mContext.getString(R.string.device_removed).replace("%s", displayName));
break; break;
case ConferenceSecurityEvent: case ConferenceSecurityEvent:
holder.eventMessage.setTextColor(mContext.getResources().getColor(R.color.colorI)); holder.securityEventLayout.setVisibility(View.VISIBLE);
holder.eventLayout.setBackgroundResource(R.drawable.event_decoration_red); holder.securityEventMessage.setText("TODO");
holder.eventLayout.setBackgroundResource(R.drawable.event_decoration_red);
switch (event.getSecurityEventType()) { switch (event.getSecurityEventType()) {
case EncryptionIdentityKeyChanged: case EncryptionIdentityKeyChanged:
holder.eventMessage.setText(mContext.getString(R.string.lime_identity_key_changed).replace("%s", displayName)); holder.securityEventMessage.setText(mContext.getString(R.string.lime_identity_key_changed).replace("%s", displayName));
break; break;
case ManInTheMiddleDetected: case ManInTheMiddleDetected:
holder.eventMessage.setText(mContext.getString(R.string.man_in_the_middle_detected).replace("%s", displayName)); holder.securityEventMessage.setText(mContext.getString(R.string.man_in_the_middle_detected).replace("%s", displayName));
break; break;
case SecurityLevelDowngraded: case SecurityLevelDowngraded:
holder.eventMessage.setText(mContext.getString(R.string.security_level_downgraded).replace("%s", displayName)); holder.securityEventMessage.setText(mContext.getString(R.string.security_level_downgraded).replace("%s", displayName));
break; break;
case ParticipantMaxDeviceCountExceeded: case ParticipantMaxDeviceCountExceeded:
holder.eventMessage.setText(mContext.getString(R.string.participant_max_count_exceeded).replace("%s", displayName)); holder.securityEventMessage.setText(mContext.getString(R.string.participant_max_count_exceeded).replace("%s", displayName));
break; break;
case None: case None:
default: default:
@ -427,6 +192,7 @@ public class ChatMessagesAdapter extends SelectableAdapter<ChatMessageViewHolder
break; break;
case None: case None:
default: default:
holder.eventLayout.setVisibility(View.VISIBLE);
holder.eventMessage.setText(mContext.getString(R.string.unexpected_event).replace("%s", displayName).replace("%i", String.valueOf(event.getType().toInt()))); holder.eventMessage.setText(mContext.getString(R.string.unexpected_event).replace("%s", displayName).replace("%i", String.valueOf(event.getType().toInt())));
break; break;
} }
@ -483,220 +249,57 @@ public class ChatMessagesAdapter extends SelectableAdapter<ChatMessageViewHolder
notifyItemRemoved(i); notifyItemRemoved(i);
} }
private void loadBitmap(String path, ImageView imageView) { private void changeBackgroundDependingOnPreviousAndNextEvents(ChatMessage message, ChatMessageViewHolder holder, int position) {
if (cancelPotentialWork(path, imageView)) { boolean hasPrevious = false, hasNext = false;
mDefaultBitmap = BitmapFactory.decodeResource(mContext.getResources(), R.drawable.chat_file);
BitmapWorkerTask task = new BitmapWorkerTask(imageView); // Do not forget history is reversed, so previous in order is next in list display and chronology !
final AsyncBitmap asyncBitmap = new AsyncBitmap(mContext.getResources(), mDefaultBitmap, task); if (position > 0 && mContext.getResources().getBoolean(R.bool.lower_space_between_chat_bubbles_if_same_person)) {
imageView.setImageDrawable(asyncBitmap); EventLog previousEvent = (EventLog) getItem(position - 1);
task.execute(path); if (previousEvent.getType() == EventLog.Type.ConferenceChatMessage) {
ChatMessage previousMessage = previousEvent.getChatMessage();
if (previousMessage.getFromAddress().weakEqual(message.getFromAddress())) {
if (previousMessage.getTime() - message.getTime() < MAX_TIME_TO_GROUP_MESSAGES) {
hasPrevious = true;
}
}
}
}
if (position < mHistory.size() - 1 && mContext.getResources().getBoolean(R.bool.lower_space_between_chat_bubbles_if_same_person)) {
EventLog nextEvent = (EventLog) getItem(position + 1);
if (nextEvent.getType() == EventLog.Type.ConferenceChatMessage) {
ChatMessage nextMessage = nextEvent.getChatMessage();
if (nextMessage.getFromAddress().weakEqual(message.getFromAddress())) {
if (message.getTime() - nextMessage.getTime() < MAX_TIME_TO_GROUP_MESSAGES) {
holder.timeText.setVisibility(View.GONE);
if (!message.isOutgoing()) {
holder.avatarLayout.setVisibility(View.INVISIBLE);
}
hasNext = true;
}
}
} }
} }
private void openFile(String path) { if (message.isOutgoing()) {
Intent intent = new Intent(Intent.ACTION_VIEW); if (hasNext && hasPrevious) {
File file; holder.background.setBackgroundResource(R.drawable.chat_bubble_outgoing_split_2);
Uri contentUri; } else if (hasNext) {
if (path.startsWith("file://")) { holder.background.setBackgroundResource(R.drawable.chat_bubble_outgoing_split_3);
path = path.substring("file://".length()); } else if (hasPrevious) {
file = new File(path); holder.background.setBackgroundResource(R.drawable.chat_bubble_outgoing_split_1);
contentUri = FileProvider.getUriForFile(mContext, mContext.getResources().getString(R.string.file_provider), file);
} else if (path.startsWith("content://")) {
contentUri = Uri.parse(path);
} else { } else {
file = new File(path); holder.background.setBackgroundResource(R.drawable.chat_bubble_outgoing_full);
try {
contentUri = FileProvider.getUriForFile(mContext, mContext.getResources().getString(R.string.file_provider), file);
} catch (Exception e) {
contentUri = Uri.parse(path);
}
}
String type = null;
String extension = MimeTypeMap.getFileExtensionFromUrl(contentUri.toString());
if (extension != null) {
type = MimeTypeMap.getSingleton().getMimeTypeFromExtension(extension);
}
if (type != null) {
intent.setDataAndType(contentUri, type);
} else {
intent.setDataAndType(contentUri, "*/*");
}
intent.addFlags(FLAG_GRANT_READ_URI_PERMISSION);
mContext.startActivity(intent);
}
private void displayAttachedFile(ChatMessage message, ChatMessageViewHolder holder) {
holder.fileName.setVisibility(View.VISIBLE);
String appData = message.getAppdata();
if (appData == null) {
for (Content c : message.getContents()) {
if (c.isFile()) {
appData = c.getFilePath();
}
}
}
if (appData != null) {
FileUtils.scanFile(message);
holder.fileName.setText(FileUtils.getNameFromFilePath(appData));
if (FileUtils.isExtensionImage(appData)) {
holder.messageImage.setVisibility(View.VISIBLE);
loadBitmap(appData, holder.messageImage);
holder.messageImage.setTag(appData);
} else {
holder.openFileButton.setVisibility(View.VISIBLE);
holder.openFileButton.setTag(appData);
holder.openFileButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
openFile((String) v.getTag());
}
});
}
}
}
/*
* Bitmap related classes and methods
*/
private class BitmapWorkerTask extends AsyncTask<String, Void, Bitmap> {
private final WeakReference<ImageView> imageViewReference;
public String path;
public BitmapWorkerTask(ImageView imageView) {
path = null;
// Use a WeakReference to ensure the ImageView can be garbage collected
imageViewReference = new WeakReference<>(imageView);
}
private Bitmap scaleToFitHeight(Bitmap b, int height) {
float factor = height / (float) b.getHeight();
return Bitmap.createScaledBitmap(b, (int) (b.getWidth() * factor), height, true);
}
// Decode image in background.
@Override
protected Bitmap doInBackground(String... params) {
path = params[0];
Bitmap bm = null;
Bitmap thumbnail = null;
if (FileUtils.isExtensionImage(path)) {
if (path.startsWith("content")) {
try {
bm = MediaStore.Images.Media.getBitmap(mContext.getContentResolver(), Uri.parse(path));
} catch (FileNotFoundException e) {
Log.e(e);
} catch (IOException e) {
Log.e(e);
} }
} else { } else {
bm = BitmapFactory.decodeFile(path); if (hasNext && hasPrevious) {
} holder.background.setBackgroundResource(R.drawable.chat_bubble_incoming_split_2);
} else if (hasNext) {
ImageView imageView = imageViewReference.get(); holder.background.setBackgroundResource(R.drawable.chat_bubble_incoming_split_3);
try { } else if (hasPrevious) {
// Rotate the bitmap if possible/needed, using EXIF data holder.background.setBackgroundResource(R.drawable.chat_bubble_incoming_split_1);
Matrix matrix = new Matrix();
ExifInterface exif = new ExifInterface(path);
int pictureOrientation = exif.getAttributeInt(ExifInterface.TAG_ORIENTATION, 0);
if (pictureOrientation == 6 || pictureOrientation == 3 || pictureOrientation == 8) {
if (pictureOrientation == 6) {
matrix.postRotate(90);
} else if (pictureOrientation == 3) {
matrix.postRotate(180);
} else { } else {
matrix.postRotate(270); holder.background.setBackgroundResource(R.drawable.chat_bubble_incoming_full);
}
if (pictureOrientation == 6 || pictureOrientation == 8) {
matrix.postScale(1, imageView.getMeasuredHeight() / (float) bm.getHeight());
} else {
matrix.postScale(imageView.getMeasuredHeight() / (float) bm.getHeight(), 1);
}
thumbnail = Bitmap.createBitmap(bm, 0, 0, bm.getWidth(), bm.getHeight(), matrix, true);
if (thumbnail != bm) {
bm.recycle();
bm = null;
} }
} }
} catch (Exception e) {
Log.e(e);
}
if (thumbnail == null && bm != null) {
thumbnail = scaleToFitHeight(bm, imageView.getMeasuredHeight());
bm.recycle();
}
return thumbnail;
} else {
return mDefaultBitmap;
}
}
// Once complete, see if ImageView is still around and set bitmap.
@Override
protected void onPostExecute(Bitmap bitmap) {
if (isCancelled()) {
bitmap.recycle();
bitmap = null;
}
if (imageViewReference != null && bitmap != null) {
final ImageView imageView = imageViewReference.get();
final BitmapWorkerTask bitmapWorkerTask = getBitmapWorkerTask(imageView);
if (this == bitmapWorkerTask && imageView != null) {
imageView.setImageBitmap(bitmap);
imageView.setTag(path);
imageView.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
openFile((String) v.getTag());
}
});
}
}
}
}
class AsyncBitmap extends BitmapDrawable {
private final WeakReference<BitmapWorkerTask> bitmapWorkerTaskReference;
public AsyncBitmap(Resources res, Bitmap bitmap, BitmapWorkerTask bitmapWorkerTask) {
super(res, bitmap);
bitmapWorkerTaskReference = new WeakReference<>(bitmapWorkerTask);
}
public BitmapWorkerTask getBitmapWorkerTask() {
return bitmapWorkerTaskReference.get();
}
}
private boolean cancelPotentialWork(String path, ImageView imageView) {
final BitmapWorkerTask bitmapWorkerTask = getBitmapWorkerTask(imageView);
if (bitmapWorkerTask != null) {
final String bitmapData = bitmapWorkerTask.path;
// If bitmapData is not yet set or it differs from the new data
if (bitmapData == null || bitmapData != path) {
// Cancel previous task
bitmapWorkerTask.cancel(true);
} else {
// The same work is already in progress
return false;
}
}
// No task associated with the ImageView, or an existing task was cancelled
return true;
}
private BitmapWorkerTask getBitmapWorkerTask(ImageView imageView) {
if (imageView != null) {
final Drawable drawable = imageView.getDrawable();
if (drawable instanceof AsyncBitmap) {
final AsyncBitmap asyncDrawable = (AsyncBitmap) drawable;
return asyncDrawable.getBitmapWorkerTask();
}
}
return null;
} }
} }

View file

@ -22,22 +22,17 @@ package org.linphone.chat;
import android.app.Fragment; import android.app.Fragment;
import android.os.Bundle; import android.os.Bundle;
import android.support.annotation.Nullable; import android.support.annotation.Nullable;
import android.text.Spanned;
import android.text.method.LinkMovementMethod;
import android.view.LayoutInflater; import android.view.LayoutInflater;
import android.view.View; import android.view.View;
import android.view.ViewGroup; import android.view.ViewGroup;
import android.widget.ImageView; import android.widget.ImageView;
import android.widget.LinearLayout; import android.widget.LinearLayout;
import android.widget.RelativeLayout;
import android.widget.TextView; import android.widget.TextView;
import org.linphone.LinphoneManager; import org.linphone.LinphoneManager;
import org.linphone.utils.FileUtils;
import org.linphone.utils.LinphoneUtils; import org.linphone.utils.LinphoneUtils;
import org.linphone.R; import org.linphone.R;
import org.linphone.LinphoneActivity; import org.linphone.LinphoneActivity;
import org.linphone.compatibility.Compatibility;
import org.linphone.contacts.ContactsManager; import org.linphone.contacts.ContactsManager;
import org.linphone.contacts.LinphoneContact; import org.linphone.contacts.LinphoneContact;
import org.linphone.core.Address; import org.linphone.core.Address;
@ -101,18 +96,7 @@ public class ImdnFragment extends Fragment {
mSentHeader = view.findViewById(R.id.sent_layout_header); mSentHeader = view.findViewById(R.id.sent_layout_header);
mUndeliveredHeader = view.findViewById(R.id.undelivered_layout_header); mUndeliveredHeader = view.findViewById(R.id.undelivered_layout_header);
mBubble = new ChatMessageViewHolder(view.findViewById(R.id.bubble)); mBubble = new ChatMessageViewHolder(getActivity(), view.findViewById(R.id.bubble), null);
mBubble.eventLayout.setVisibility(View.GONE);
mBubble.bubbleLayout.setVisibility(View.VISIBLE);
mBubble.delete.setVisibility(View.GONE);
mBubble.messageText.setVisibility(View.GONE);
mBubble.messageImage.setVisibility(View.GONE);
mBubble.fileTransferLayout.setVisibility(View.GONE);
mBubble.fileName.setVisibility(View.GONE);
mBubble.openFileButton.setVisibility(View.GONE);
mBubble.messageStatus.setVisibility(View.INVISIBLE);
mBubble.messageSendingInProgress.setVisibility(View.GONE);
mBubble.imdmLayout.setVisibility(View.INVISIBLE);
mMessage = mRoom.findMessage(mMessageId); mMessage = mRoom.findMessage(mMessageId);
mListener = new ChatMessageListenerStub() { mListener = new ChatMessageListenerStub() {
@ -124,23 +108,6 @@ public class ImdnFragment extends Fragment {
if (mMessage == null) return null; if (mMessage == null) return null;
mMessage.setListener(mListener); mMessage.setListener(mListener);
RelativeLayout.LayoutParams layoutParams = new RelativeLayout.LayoutParams(RelativeLayout.LayoutParams.WRAP_CONTENT, RelativeLayout.LayoutParams.WRAP_CONTENT);
layoutParams.addRule(RelativeLayout.ALIGN_PARENT_RIGHT);
layoutParams.setMargins(100, 10, 10, 10);
if (mMessage.isOutgoing()) {
mBubble.background.setBackgroundResource(R.drawable.resizable_chat_bubble_outgoing);
Compatibility.setTextAppearance(mBubble.contactName, getActivity(), R.style.font3);
Compatibility.setTextAppearance(mBubble.fileTransferAction, getActivity(), R.style.font15);
mBubble.fileTransferAction.setBackgroundResource(R.drawable.resizable_confirm_delete_button);
ContactAvatar.setAvatarMask(mBubble.avatarLayout, R.drawable.avatar_chat_mask_outgoing);
} else {
mBubble.background.setBackgroundResource(R.drawable.resizable_chat_bubble_incoming);
Compatibility.setTextAppearance(mBubble.contactName, getActivity(), R.style.font9);
Compatibility.setTextAppearance(mBubble.fileTransferAction, getActivity(), R.style.font8);
mBubble.fileTransferAction.setBackgroundResource(R.drawable.resizable_assistant_button);
ContactAvatar.setAvatarMask(mBubble.avatarLayout, R.drawable.avatar_chat_mask);
}
return view; return view;
} }
@ -158,36 +125,13 @@ public class ImdnFragment extends Fragment {
private void refreshInfo() { private void refreshInfo() {
Address remoteSender = mMessage.getFromAddress(); Address remoteSender = mMessage.getFromAddress();
LinphoneContact contact = ContactsManager.getInstance().findContactFromAddress(remoteSender); LinphoneContact contact = ContactsManager.getInstance().findContactFromAddress(remoteSender);
String displayName;
if (contact != null) { mBubble.delete.setVisibility(View.GONE);
if (contact.getFullName() != null) { mBubble.eventLayout.setVisibility(View.GONE);
displayName = contact.getFullName(); mBubble.securityEventLayout.setVisibility(View.GONE);
} else { mBubble.rightAnchor.setVisibility(View.GONE);
displayName = LinphoneUtils.getAddressDisplayName(remoteSender); mBubble.bubbleLayout.setVisibility(View.GONE);
} mBubble.bindMessage(mMessage, contact);
ContactAvatar.displayAvatar(contact, mBubble.avatarLayout);
} else {
displayName = LinphoneUtils.getAddressDisplayName(remoteSender);
ContactAvatar.displayAvatar(displayName, mBubble.avatarLayout);
}
mBubble.contactName.setText(LinphoneUtils.timestampToHumanDate(getActivity(), mMessage.getTime(), R.string.messages_date_format) + " - " + displayName);
if (mMessage.hasTextContent()) {
String msg = mMessage.getTextContent();
Spanned text = LinphoneUtils.getTextWithHttpLinks(msg);
mBubble.messageText.setText(text);
mBubble.messageText.setMovementMethod(LinkMovementMethod.getInstance());
mBubble.messageText.setVisibility(View.VISIBLE);
}
String appData = mMessage.getAppdata();
if (appData != null) { // Something to display
mBubble.fileName.setVisibility(View.VISIBLE);
mBubble.fileName.setText(FileUtils.getNameFromFilePath(appData));
// We purposely chose not to display the image
}
mRead.removeAllViews(); mRead.removeAllViews();
mDelivered.removeAllViews(); mDelivered.removeAllViews();

View file

@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android" android:shape="rectangle">
<solid android:color="@color/chat_bubble_incoming"/>
<corners android:radius="6.7dp"/>
</shape>

View file

@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android" android:shape="rectangle">
<solid android:color="@color/chat_bubble_incoming"/>
<corners android:radius="6.7dp" android:bottomLeftRadius="0dp"/>
</shape>

View file

@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android" android:shape="rectangle">
<solid android:color="@color/chat_bubble_incoming"/>
<corners android:radius="6.7dp" android:topLeftRadius="0dp" android:bottomLeftRadius="0dp"/>
</shape>

View file

@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android" android:shape="rectangle">
<solid android:color="@color/chat_bubble_incoming"/>
<corners android:radius="6.7dp" android:topLeftRadius="0dp"/>
</shape>

View file

@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android" android:shape="rectangle">
<solid android:color="@color/chat_bubble_outgoing"/>
<corners android:radius="6.7dp"/>
</shape>

View file

@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android" android:shape="rectangle">
<solid android:color="@color/chat_bubble_outgoing"/>
<corners android:radius="6.7dp" android:bottomRightRadius="0dp"/>
</shape>

View file

@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android" android:shape="rectangle">
<solid android:color="@color/chat_bubble_outgoing"/>
<corners android:radius="6.7dp" android:topRightRadius="0dp" android:bottomRightRadius="0dp"/>
</shape>

View file

@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android" android:shape="rectangle">
<solid android:color="@color/chat_bubble_outgoing"/>
<corners android:radius="6.7dp" android:topRightRadius="0dp"/>
</shape>

View file

@ -3,208 +3,174 @@
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content"> android:layout_height="wrap_content">
<View
android:id="@+id/rightAnchor"
android:layout_width="1dp"
android:layout_height="wrap_content"
android:layout_alignParentRight="true"
android:layout_alignParentTop="true"/>
<CheckBox <CheckBox
android:id="@+id/delete_message" android:id="@+id/delete_message"
android:visibility="gone"
android:button="@drawable/checkbox" android:button="@drawable/checkbox"
android:contentDescription="@string/content_description_delete" android:contentDescription="@string/content_description_delete"
android:layout_marginLeft="5dp"
android:layout_width="30dp" android:layout_width="30dp"
android:layout_height="30dp" android:layout_height="30dp"
android:adjustViewBounds="true" android:adjustViewBounds="true"
android:clickable="false" android:clickable="false"
android:layout_alignParentRight="true"/> android:layout_marginLeft="5dp"
android:layout_alignRight="@id/rightAnchor"/>
<LinearLayout <LinearLayout
android:id="@+id/event" android:id="@+id/event"
android:visibility="gone"
android:orientation="horizontal" android:orientation="horizontal"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_toLeftOf="@id/delete_message" android:layout_toLeftOf="@id/rightAnchor"
android:layout_marginTop="5dp" android:layout_marginTop="5dp"
android:layout_marginBottom="5dp" android:layout_marginBottom="5dp"
android:gravity="center" android:gravity="center_vertical">
android:background="@drawable/event_decoration_gray">
<ImageView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_weight="1"
android:layout_marginRight="10dp"
android:src="@drawable/event_decoration_gray"
android:scaleType="fitXY"/>
<TextView <TextView
android:id="@+id/event_text" android:id="@+id/event_text"
android:textAppearance="@style/font_group_chat_event"
android:background="@color/colorH"
android:paddingLeft="10dp"
android:paddingRight="10dp"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content"/> android:layout_height="wrap_content"/>
<ImageView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_weight="1"
android:layout_marginLeft="10dp"
android:src="@drawable/event_decoration_gray"
android:scaleType="fitXY"/>
</LinearLayout>
<LinearLayout
android:id="@+id/security_event"
android:visibility="gone"
android:orientation="horizontal"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_toLeftOf="@id/rightAnchor"
android:layout_marginTop="5dp"
android:layout_marginBottom="5dp"
android:gravity="center_vertical">
<ImageView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_weight="1"
android:layout_marginRight="10dp"
android:src="@drawable/event_decoration_red"
android:scaleType="fitXY"/>
<TextView
android:id="@+id/security_event_text"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textColor="@color/colorI"/>
<ImageView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_weight="1"
android:layout_marginLeft="10dp"
android:src="@drawable/event_decoration_red"
android:scaleType="fitXY"/>
</LinearLayout> </LinearLayout>
<RelativeLayout <RelativeLayout
android:id="@+id/bubble" android:id="@+id/bubble"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_toLeftOf="@id/delete_message"> android:layout_toLeftOf="@id/rightAnchor">
<LinearLayout <RelativeLayout
android:id="@+id/separator"
android:layout_width="100dp"
android:layout_height="10dp"
android:orientation="horizontal"/>
<LinearLayout
android:id="@+id/background" android:id="@+id/background"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_below="@id/separator" android:layout_marginLeft="45dp"
android:orientation="horizontal"> android:layout_marginTop="1dp"
android:layout_marginBottom="1dp"
android:background="@drawable/chat_bubble_outgoing_full"
android:layout_below="@+id/time"
android:layout_toLeftOf="@+id/imdn"
android:paddingBottom="10dp"
android:paddingTop="10dp">
<RelativeLayout <android.support.v7.widget.RecyclerView
android:id="@+id/pictures"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:paddingLeft="10dp" android:divider="@android:color/transparent"
android:paddingTop="10dp"> android:choiceMode="multipleChoice"
android:transcriptMode="normal"
android:layout_marginLeft="5dp"
android:layout_marginRight="5dp"
android:cacheColorHint="@color/transparent"
android:listSelector="@color/transparent"/>
<TextView
android:id="@+id/message"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textAppearance="@style/chat_bubble_message_font"
android:layout_marginLeft="10dp"
android:layout_marginRight="10dp"
android:layout_below="@id/pictures"/>
</RelativeLayout>
<RelativeLayout
android:id="@+id/message_sender_avatar"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignTop="@id/background">
<include layout="@layout/contact_avatar"/> <include layout="@layout/contact_avatar"/>
</RelativeLayout> </RelativeLayout>
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="vertical"
android:paddingTop="10dp"
android:paddingRight="10dp">
<TextView <TextView
android:id="@+id/contact_header" android:id="@id/time"
android:maxLines="1" android:textAppearance="@style/chat_bubble_time_font"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
<RelativeLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content">
<TextView
android:id="@+id/file_name"
style="@style/font26"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
<ImageView
android:id="@+id/image"
android:layout_width="wrap_content"
android:layout_height="150dp"
android:layout_below="@id/file_name"
android:layout_centerHorizontal="true"
android:adjustViewBounds="true"/>
<Button
android:id="@+id/open_file"
android:textAppearance="@style/font8"
android:text="@string/open"
android:background="@drawable/resizable_assistant_button"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_below="@id/file_name"
android:layout_centerVertical="true"
android:visibility="gone"/>
</RelativeLayout>
<TextView
android:id="@+id/message"
style="@style/font11"
android:autoLink="web"
android:linksClickable="true"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
<RelativeLayout
android:id="@+id/file_transfer_layout"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:visibility="gone">
<ProgressBar
android:id="@+id/progress_bar"
style="@android:style/Widget.ProgressBar.Horizontal"
android:paddingRight="5dp"
android:layout_marginTop="10dp"
android:layout_marginBottom="10dp"
android:layout_width="150dp"
android:layout_height="5dp"/>
<Button
android:id="@+id/file_transfer_action"
android:textAppearance="@style/font8"
android:background="@drawable/resizable_assistant_button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="@id/progress_bar"/>
</RelativeLayout>
<LinearLayout
android:id="@+id/imdmLayout"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentRight="true"
android:layout_marginTop="2dp"
android:layout_marginBottom="2dp"
android:orientation="horizontal"
android:layout_gravity="right"
android:gravity="right" android:gravity="right"
android:visibility="invisible"> android:layout_marginTop="7dp"
android:layout_marginLeft="45dp"
<TextView android:layout_toLeftOf="@id/imdn"/>
android:id="@+id/imdmText"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Displayed"
android:textSize="12sp"
android:paddingRight="3dp"/>
<ImageView <ImageView
android:id="@+id/imdmIcon" android:id="@id/imdn"
android:layout_width="10dp" android:layout_width="10dp"
android:layout_height="10dp" android:layout_height="10dp"
android:src="@drawable/valid" android:src="@drawable/imdn_received"
android:scaleType="fitCenter" android:layout_marginLeft="5dp"
android:layout_marginTop="2dp" />
</LinearLayout>
</LinearLayout>
</LinearLayout>
<RelativeLayout
android:layout_alignRight="@id/background"
android:layout_alignTop="@id/background"
android:layout_width="15dp"
android:layout_height="15dp"
android:paddingTop="5dp"
android:paddingBottom="3dp">
<ImageView
android:id="@+id/status"
android:contentDescription="@string/content_description_message_status"
android:visibility="invisible"
android:layout_alignParentTop="true"
android:layout_alignParentRight="true" android:layout_alignParentRight="true"
android:paddingRight="5dp" android:layout_alignBottom="@id/background"/>
android:layout_width="match_parent"
android:layout_height="match_parent"
android:adjustViewBounds="true" />
<ProgressBar <ProgressBar
android:id="@+id/inprogress" android:id="@+id/send_in_progress"
android:progressTint="@color/colorB" android:layout_width="10dp"
android:layout_alignParentTop="true" android:layout_height="10dp"
android:indeterminateTint="@color/colorA"
android:src="@drawable/imdn_received"
android:layout_marginLeft="5dp"
android:layout_alignParentRight="true" android:layout_alignParentRight="true"
android:paddingRight="5dp" android:layout_alignBottom="@id/background"/>
android:layout_width="match_parent"
android:layout_height="match_parent"/>
</RelativeLayout>
</RelativeLayout> </RelativeLayout>

View file

@ -0,0 +1,35 @@
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_margin="5dp">
<TextView
android:id="@+id/file"
android:textAlignment="center"
android:gravity="center"
style="@style/chat_bubble_file_name_font"
android:background="@color/colorN"
android:ellipsize="end"
android:layout_width="150dp"
android:layout_height="75dp"/>
<ImageView
android:id="@+id/image"
android:adjustViewBounds="true"
android:layout_width="wrap_content"
android:layout_height="75dp"/>
<Button
android:id="@+id/download"
android:text="Download"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
<ProgressBar
android:id="@+id/fileTransferInProgress"
android:layout_width="75dp"
android:layout_height="75dp"
android:indeterminate="true"/>
</RelativeLayout>

View file

@ -80,7 +80,7 @@
<TextView <TextView
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
style="@style/font27" style="@style/imdn_read_font"
android:text="@string/displayed"/> android:text="@string/displayed"/>
<ImageView <ImageView
@ -108,7 +108,7 @@
<TextView <TextView
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
style="@style/font28" style="@style/imdn_delivered_sent_font"
android:text="@string/delivered"/> android:text="@string/delivered"/>
<ImageView <ImageView
@ -136,7 +136,7 @@
<TextView <TextView
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
style="@style/font28" style="@style/imdn_delivered_sent_font"
android:text="@string/sent"/> android:text="@string/sent"/>
<ImageView <ImageView
@ -165,7 +165,7 @@
<TextView <TextView
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
style="@style/font29" style="@style/imdn_undelivered_font"
android:text="@string/error"/> android:text="@string/error"/>
<ImageView <ImageView

View file

@ -50,7 +50,7 @@
android:layout_alignParentRight="true" android:layout_alignParentRight="true"
android:layout_centerVertical="true" android:layout_centerVertical="true"
android:layout_marginRight="10dp" android:layout_marginRight="10dp"
android:textAppearance="@style/font33" android:textAppearance="@style/contact_invite_font"
android:text="@string/invite_friend" /> android:text="@string/invite_friend" />
</RelativeLayout> </RelativeLayout>

View file

@ -12,10 +12,14 @@
<color name="colorJ">#ffa645</color> <color name="colorJ">#ffa645</color>
<color name="colorK">#3eb5c0</color> <color name="colorK">#3eb5c0</color>
<color name="colorL">#96c11f</color> <color name="colorL">#96c11f</color>
<color name="colorM">#a2a2a2</color>
<color name="colorN">#a1a1a1</color>
<color name="notification_color_led">#FF8000</color> <color name="notification_color_led">#FF8000</color>
<color name="security_green">#96c11f</color> <color name="security_green">#96c11f</color>
<color name="security_gray">#c2c2c2</color> <color name="security_gray">#c2c2c2</color>
<color name="security_thumb">#ffffff</color> <color name="security_thumb">#ffffff</color>
<color name="chat_bubble_incoming">#f3f3f3</color>
<color name="chat_bubble_outgoing">#19ff5e00</color>
<color name="transparent">#00000000</color> <color name="transparent">#00000000</color>
<color name="linphone_launcher_icon_background">@color/colorA</color> <color name="linphone_launcher_icon_background">@color/colorA</color>

View file

@ -87,7 +87,7 @@
<bool name="disable_chat_send_file">false</bool> <bool name="disable_chat_send_file">false</bool>
<string name="temp_photo_name">linphone-android-photo-temp</string> <string name="temp_photo_name">linphone-android-photo-temp</string>
<string name="temp_photo_name_with_date">linphone-android-photo-%s</string> <string name="temp_photo_name_with_date">linphone-android-photo-%s</string>
<bool name="lower_space_between_chat_bubbles_if_same_person">false</bool> <bool name="lower_space_between_chat_bubbles_if_same_person">true</bool>
<bool name="allow_multiple_images_and_text">true</bool> <bool name="allow_multiple_images_and_text">true</bool>
<bool name="send_text_and_images_as_different_messages">true</bool> <bool name="send_text_and_images_as_different_messages">true</bool>

View file

@ -144,30 +144,48 @@
<item name="android:textStyle">bold</item> <item name="android:textStyle">bold</item>
</style> </style>
<style name="font26" parent="@android:style/TextAppearance.Medium"> <style name="imdn_read_font" parent="@android:style/TextAppearance.Medium">
<item name="android:textColor">@color/colorD</item>
<item name="android:textSize">12sp</item>
</style>
<style name="font27" parent="@android:style/TextAppearance.Medium">
<item name="android:textColor">@color/colorK</item> <item name="android:textColor">@color/colorK</item>
<item name="android:textSize">15sp</item> <item name="android:textSize">15sp</item>
<item name="android:textStyle">bold</item> <item name="android:textStyle">bold</item>
</style> </style>
<style name="font28" parent="@android:style/TextAppearance.Medium"> <style name="imdn_delivered_sent_font" parent="@android:style/TextAppearance.Medium">
<item name="android:textColor">@color/colorD</item> <item name="android:textColor">@color/colorD</item>
<item name="android:textSize">15sp</item> <item name="android:textSize">15sp</item>
<item name="android:textStyle">bold</item> <item name="android:textStyle">bold</item>
</style> </style>
<style name="font29" parent="@android:style/TextAppearance.Medium"> <style name="imdn_undelivered_font" parent="@android:style/TextAppearance.Medium">
<item name="android:textColor">@color/colorI</item> <item name="android:textColor">@color/colorI</item>
<item name="android:textSize">15sp</item> <item name="android:textSize">15sp</item>
<item name="android:textStyle">bold</item> <item name="android:textStyle">bold</item>
</style> </style>
<style name="font33" parent="@android:style/TextAppearance.Medium"> <style name="chat_bubble_time_font" parent="@android:style/TextAppearance.Small">
<item name="android:textColor">@color/colorM</item>
<item name="android:textSize">13sp</item>
<item name="android:fontFamily">sans-serif</item>
<item name="android:textStyle">normal</item>
<item name="android:lineSpacingExtra">0sp</item>
</style>
<style name="chat_bubble_message_font" parent="@android:style/TextAppearance.Medium">
<item name="android:textColor">@color/colorC</item>
<item name="android:textSize">15sp</item>
<item name="android:fontFamily">sans-serif</item>
<item name="android:textStyle">normal</item>
<item name="android:lineSpacingExtra">-1.7sp</item>
</style>
<style name="chat_bubble_file_name_font" parent="@android:style/TextAppearance.Medium">
<item name="android:textColor">@color/colorH</item>
<item name="android:textSize">11.7sp</item>
<item name="android:fontFamily">sans-serif</item>
<item name="android:textStyle">normal</item>
</style>
<style name="contact_invite_font" parent="@android:style/TextAppearance.Medium">
<item name="android:textColor">@color/colorA</item> <item name="android:textColor">@color/colorA</item>
<item name="android:textSize">16.7sp</item> <item name="android:textSize">16.7sp</item>
<item name="android:fontFamily">sans-serif</item> <item name="android:fontFamily">sans-serif</item>