/**
* Copyright (c) 2013, Redsolution LTD. All rights reserved.
*
* This file is part of Xabber project; you can redistribute it and/or
* modify it under the terms of the GNU General Public License, Version 3.
*
* Xabber 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, see http://www.gnu.org/licenses/.
*/
package com.xabber.android.ui.adapter;
import android.content.Context;
import android.content.res.ColorStateList;
import android.graphics.Bitmap;
import android.graphics.drawable.Drawable;
import android.os.Build;
import android.support.annotation.Nullable;
import android.support.annotation.StyleRes;
import android.support.v4.graphics.drawable.DrawableCompat;
import android.support.v7.widget.RecyclerView;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageView;
import android.widget.ProgressBar;
import android.widget.TextView;
import com.bumptech.glide.Glide;
import com.bumptech.glide.load.resource.drawable.GlideDrawable;
import com.bumptech.glide.request.RequestListener;
import com.bumptech.glide.request.animation.GlideAnimation;
import com.bumptech.glide.request.target.SimpleTarget;
import com.bumptech.glide.request.target.Target;
import com.xabber.android.R;
import com.xabber.android.data.SettingsManager;
import com.xabber.android.data.account.AccountItem;
import com.xabber.android.data.account.AccountManager;
import com.xabber.android.data.database.MessageDatabaseManager;
import com.xabber.android.data.database.messagerealm.MessageItem;
import com.xabber.android.data.entity.AccountJid;
import com.xabber.android.data.entity.UserJid;
import com.xabber.android.data.extension.avatar.AvatarManager;
import com.xabber.android.data.extension.file.FileManager;
import com.xabber.android.data.extension.muc.MUCManager;
import com.xabber.android.data.extension.muc.RoomContact;
import com.xabber.android.data.log.LogManager;
import com.xabber.android.data.message.AbstractChat;
import com.xabber.android.data.message.ChatAction;
import com.xabber.android.data.roster.AbstractContact;
import com.xabber.android.data.roster.RosterManager;
import com.xabber.android.ui.color.ColorManager;
import com.xabber.android.ui.fragment.ChatFragment;
import com.xabber.android.utils.StringUtils;
import org.jxmpp.jid.impl.JidCreate;
import org.jxmpp.jid.parts.Resourcepart;
import java.util.Date;
import io.realm.Realm;
import io.realm.RealmRecyclerViewAdapter;
import io.realm.RealmResults;
public class ChatMessageAdapter extends RealmRecyclerViewAdapter<MessageItem, ChatMessageAdapter.BasicMessage> {
public static final int VIEW_TYPE_INCOMING_MESSAGE = 2;
public static final int VIEW_TYPE_OUTGOING_MESSAGE = 3;
private static final int VIEW_TYPE_HINT = 1;
private static final int VIEW_TYPE_ACTION_MESSAGE = 4;
private static final String LOG_TAG = ChatMessageAdapter.class.getSimpleName();
private final Context context;
private final Message.MessageClickListener messageClickListener;
/**
* Message font appearance.
*/
private final int appearanceStyle;
private boolean isMUC;
private Resourcepart mucNickname;
/**
* Text with extra information.
*/
private Listener listener;
private AccountJid account;
private UserJid user;
private int prevItemCount;
private long lastUpdateTimeMillis;
public ChatMessageAdapter(Context context, RealmResults<MessageItem> messageItems, AbstractChat chat, ChatFragment chatFragment) {
super(context, messageItems, true);
this.context = context;
this.messageClickListener = chatFragment;
account = chat.getAccount();
user = chat.getUser();
isMUC = MUCManager.getInstance().hasRoom(account, user.getJid().asEntityBareJidIfPossible());
if (isMUC) {
mucNickname = MUCManager.getInstance().getNickname(account, user.getJid().asEntityBareJidIfPossible());
}
appearanceStyle = SettingsManager.chatsAppearanceStyle();
this.listener = chatFragment;
prevItemCount = getItemCount();
}
public interface Listener {
void onMessageNumberChanged(int prevItemCount);
void onMessagesUpdated();
}
private void setUpOutgoingMessage(Message holder, final MessageItem messageItem) {
setUpMessage(messageItem, holder);
setStatusIcon(messageItem, (OutgoingMessage) holder);
OutgoingMessage outgoingMessage = (OutgoingMessage) holder;
if (messageItem.isInProgress()) {
outgoingMessage.progressBar.setVisibility(View.VISIBLE);
} else {
outgoingMessage.progressBar.setVisibility(View.GONE);
}
setUpImage(messageItem, outgoingMessage);
setUpMessageBalloonBackground(holder.messageBalloon,
context.getResources().getColorStateList(R.color.outgoing_message_color_state_dark), R.drawable.message_outgoing_states);
}
private void setUpImage(MessageItem messageItem, final Message messageHolder) {
messageHolder.messageImage.setVisibility(View.GONE);
if (!messageItem.isImage() || !SettingsManager.connectionLoadImages()) {
return;
}
if (messageItem.getFilePath() != null) {
boolean result = FileManager.loadImageFromFile(context, messageItem.getFilePath(), messageHolder.messageImage);
if (result) {
messageHolder.messageImage.setVisibility(View.VISIBLE);
messageHolder.messageText.setVisibility(View.GONE);
} else {
final String uniqueId = messageItem.getUniqueId();
final Realm realm = MessageDatabaseManager.getInstance().getRealmUiThread();
realm.executeTransactionAsync(new Realm.Transaction() {
@Override
public void execute(Realm realm) {
MessageItem first = realm.where(MessageItem.class)
.equalTo(MessageItem.Fields.UNIQUE_ID, uniqueId)
.findFirst();
if (first != null) {
first.setFilePath(null);
}
}
});
}
} else {
final ViewGroup.LayoutParams layoutParams = messageHolder.messageImage.getLayoutParams();
Integer imageWidth = messageItem.getImageWidth();
Integer imageHeight = messageItem.getImageHeight();
if (imageWidth != null && imageHeight != null) {
FileManager.scaleImage(layoutParams, imageHeight, imageWidth);
Glide.with(context)
.load(messageItem.getText())
.listener(new RequestListener<String, GlideDrawable>() {
@Override
public boolean onException(Exception e, String model, Target<GlideDrawable> target, boolean isFirstResource) {
messageHolder.messageImage.setVisibility(View.GONE);
messageHolder.messageText.setVisibility(View.VISIBLE);
return true;
}
@Override
public boolean onResourceReady(GlideDrawable resource, String model, Target<GlideDrawable> target, boolean isFromMemoryCache, boolean isFirstResource) {
return false;
}
})
.into(messageHolder.messageImage);
messageHolder.messageImage.setVisibility(View.VISIBLE);
messageHolder.messageText.setVisibility(View.GONE);
} else {
final String uniqueId = messageItem.getUniqueId();
Glide.with(context)
.load(messageItem.getText())
.asBitmap()
.into(new SimpleTarget<Bitmap>() {
@Override
public void onResourceReady(final Bitmap resource, GlideAnimation<? super Bitmap> glideAnimation) {
final int width = resource.getWidth();
final int height = resource.getHeight();
if (width <= 0 || height <= 0) {
messageHolder.messageImage.setVisibility(View.GONE);
messageHolder.messageText.setVisibility(View.VISIBLE);
return;
}
final Realm realm = MessageDatabaseManager.getInstance().getRealmUiThread();
realm.executeTransactionAsync(new Realm.Transaction() {
@Override
public void execute(Realm realm) {
MessageItem first = realm.where(MessageItem.class)
.equalTo(MessageItem.Fields.UNIQUE_ID, uniqueId)
.findFirst();
if (first != null) {
first.setImageWidth(width);
first.setImageHeight(height);
}
}
});
FileManager.scaleImage(layoutParams, height, width);
messageHolder.messageImage.setImageBitmap(resource);
messageHolder.messageImage.setVisibility(View.VISIBLE);
messageHolder.messageText.setVisibility(View.GONE);
}
});
}
}
}
private void setUpIncomingMessage(final IncomingMessage incomingMessage, final MessageItem messageItem) {
setUpMessage(messageItem, incomingMessage);
if (messageItem.isReceivedFromMessageArchive()) {
incomingMessage.statusIcon.setVisibility(View.VISIBLE);
} else {
incomingMessage.statusIcon.setVisibility(View.GONE);
}
setUpMessageBalloonBackground(incomingMessage.messageBalloon,
ColorManager.getInstance().getChatIncomingBalloonColorsStateList(account), R.drawable.message_incoming);
setUpAvatar(messageItem, incomingMessage);
setUpImage(messageItem, incomingMessage);
if (messageItem.getText().trim().isEmpty()) {
incomingMessage.messageBalloon.setVisibility(View.GONE);
incomingMessage.messageTime.setVisibility(View.GONE);
incomingMessage.avatar.setVisibility(View.GONE);
LogManager.w(this, "Empty message! Hidden, but need to correct");
} else {
incomingMessage.messageBalloon.setVisibility(View.VISIBLE);
incomingMessage.messageTime.setVisibility(View.VISIBLE);
}
}
private void setUpMessageBalloonBackground(View messageBalloon, ColorStateList darkColorStateList, int lightBackgroundId) {
if (SettingsManager.interfaceTheme() == SettingsManager.InterfaceTheme.dark) {
final Drawable originalBackgroundDrawable = messageBalloon.getBackground();
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
originalBackgroundDrawable.setTintList(darkColorStateList);
} else {
Drawable wrapDrawable = DrawableCompat.wrap(originalBackgroundDrawable);
DrawableCompat.setTintList(wrapDrawable, darkColorStateList);
int pL = messageBalloon.getPaddingLeft();
int pT = messageBalloon.getPaddingTop();
int pR = messageBalloon.getPaddingRight();
int pB = messageBalloon.getPaddingBottom();
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
messageBalloon.setBackground(wrapDrawable);
} else {
messageBalloon.setBackgroundDrawable(wrapDrawable);
}
messageBalloon.setPadding(pL, pT, pR, pB);
}
} else {
int pL = messageBalloon.getPaddingLeft();
int pT = messageBalloon.getPaddingTop();
int pR = messageBalloon.getPaddingRight();
int pB = messageBalloon.getPaddingBottom();
messageBalloon.setBackgroundResource(lightBackgroundId);
messageBalloon.getBackground().setLevel(AccountManager.getInstance().getColorLevel(account));
messageBalloon.setPadding(pL, pT, pR, pB);
}
}
@Override
public int getItemCount() {
if (realmResults.isValid() && realmResults.isLoaded()) {
return realmResults.size();
} else {
return 0;
}
}
@Nullable
public MessageItem getMessageItem(int position) {
if (position == RecyclerView.NO_POSITION) {
return null;
}
if (position < realmResults.size()) {
return realmResults.get(position);
} else {
return null;
}
}
@Override
public BasicMessage onCreateViewHolder(ViewGroup parent, int viewType) {
switch (viewType) {
case VIEW_TYPE_HINT:
return new BasicMessage(LayoutInflater.from(parent.getContext())
.inflate(R.layout.item_message_info, parent, false), appearanceStyle);
case VIEW_TYPE_ACTION_MESSAGE:
return new BasicMessage(LayoutInflater.from(parent.getContext())
.inflate(R.layout.item_action_message, parent, false), appearanceStyle);
case VIEW_TYPE_INCOMING_MESSAGE:
return new IncomingMessage(LayoutInflater.from(parent.getContext())
.inflate(R.layout.item_incoming_message, parent, false),
messageClickListener, appearanceStyle);
case VIEW_TYPE_OUTGOING_MESSAGE:
return new OutgoingMessage(LayoutInflater.from(parent.getContext())
.inflate(R.layout.item_outgoing_message, parent, false),
messageClickListener, appearanceStyle);
default:
return null;
}
}
@Override
public void onBindViewHolder(BasicMessage holder, int position) {
final int viewType = getItemViewType(position);
MessageItem messageItem = getMessageItem(position);
if (messageItem == null) {
LogManager.w(LOG_TAG, "onBindViewHolder Null message item. Position: " + position);
return;
}
switch (viewType) {
case VIEW_TYPE_HINT:
// holder.messageText.setText(hint);
break;
case VIEW_TYPE_ACTION_MESSAGE:
ChatAction action = MessageItem.getChatAction(messageItem);
String time = StringUtils.getSmartTimeText(context, new Date(messageItem.getTimestamp()));
String name;
if (isMUC) {
name = messageItem.getResource().toString();
} else {
name = RosterManager.getInstance().getBestContact(account, messageItem.getUser()).getName();
}
holder.messageText.setText(time + ": "
+ action.getText(context, name, MessageItem.getSpannable(messageItem).toString()));
break;
case VIEW_TYPE_INCOMING_MESSAGE:
setUpIncomingMessage((IncomingMessage) holder, messageItem);
break;
case VIEW_TYPE_OUTGOING_MESSAGE:
setUpOutgoingMessage((Message) holder, messageItem);
break;
}
}
@Override
public int getItemViewType(int position) {
if (position >= realmResults.size()) {
return VIEW_TYPE_HINT;
}
MessageItem messageItem = getMessageItem(position);
if (messageItem == null) {
return 0;
}
if (messageItem.getAction() != null) {
return VIEW_TYPE_ACTION_MESSAGE;
}
if (messageItem.isIncoming()) {
if (isMUC && messageItem.getResource().equals(mucNickname)) {
return VIEW_TYPE_OUTGOING_MESSAGE;
}
return VIEW_TYPE_INCOMING_MESSAGE;
} else {
return VIEW_TYPE_OUTGOING_MESSAGE;
}
}
@Override
public void onChange() {
lastUpdateTimeMillis = System.currentTimeMillis();
notifyDataSetChanged();
listener.onMessagesUpdated();
int itemCount = getItemCount();
if (prevItemCount != itemCount) {
listener.onMessageNumberChanged(prevItemCount);
prevItemCount = itemCount;
}
}
private void setUpMessage(MessageItem messageItem, Message message) {
if (isMUC) {
message.messageHeader.setText(messageItem.getResource());
message.messageHeader.setVisibility(View.VISIBLE);
} else {
message.messageHeader.setVisibility(View.GONE);
}
if (messageItem.isUnencrypted()) {
message.messageUnencrypted.setVisibility(View.VISIBLE);
} else {
message.messageUnencrypted.setVisibility(View.GONE);
}
message.messageText.setText(messageItem.getText());
message.messageText.setVisibility(View.VISIBLE);
String time = StringUtils.getSmartTimeText(context, new Date(messageItem.getTimestamp()));
Long delayTimestamp = messageItem.getDelayTimestamp();
if (delayTimestamp != null) {
String delay = context.getString(messageItem.isIncoming() ? R.string.chat_delay : R.string.chat_typed,
StringUtils.getSmartTimeText(context, new Date(delayTimestamp)));
time += " (" + delay + ")";
}
message.messageTime.setText(time);
}
private void setStatusIcon(MessageItem messageItem, OutgoingMessage message) {
message.statusIcon.setVisibility(View.VISIBLE);
message.progressBar.setVisibility(View.GONE);
boolean isFileUploadInProgress = MessageItem.isUploadFileMessage(messageItem);
if (isFileUploadInProgress) {
message.progressBar.setVisibility(View.VISIBLE);
}
int messageIcon = R.drawable.ic_message_delivered_14dp;
if (messageItem.isForwarded()) {
messageIcon = R.drawable.ic_message_forwarded_14dp;
} else if (messageItem.isReceivedFromMessageArchive()) {
messageIcon = R.drawable.ic_message_synced_14dp;
} else if (messageItem.isError()) {
messageIcon = R.drawable.ic_message_has_error_14dp;
} else if (!isFileUploadInProgress && !messageItem.isSent()
&& lastUpdateTimeMillis - messageItem.getTimestamp() > 1000) {
messageIcon = R.drawable.ic_message_not_sent_14dp;
} else if (!messageItem.isDelivered()) {
if (messageItem.isAcknowledged()) {
messageIcon = R.drawable.ic_message_acknowledged_14dp;
} else {
message.statusIcon.setVisibility(View.GONE);
}
}
message.statusIcon.setImageResource(messageIcon);
}
private void setUpAvatar(MessageItem messageItem, IncomingMessage message) {
if (SettingsManager.chatsShowAvatars()) {
final AccountJid account = messageItem.getAccount();
final UserJid user = messageItem.getUser();
final Resourcepart resource = messageItem.getResource();
message.avatar.setVisibility(View.VISIBLE);
if ((isMUC && MUCManager.getInstance().getNickname(account, user.getJid().asEntityBareJidIfPossible()).equals(resource))) {
message.avatar.setImageDrawable(AvatarManager.getInstance().getAccountAvatar(account));
} else {
if (isMUC) {
if (resource.equals(Resourcepart.EMPTY)) {
message.avatar.setImageDrawable(AvatarManager.getInstance().getRoomAvatar(user));
} else {
try {
message.avatar.setImageDrawable(AvatarManager.getInstance()
.getUserAvatar(UserJid.from(JidCreate.domainFullFrom(user.getJid().asDomainBareJid(), resource))));
} catch (UserJid.UserJidCreateException e) {
LogManager.exception(this, e);
}
}
} else {
message.avatar.setImageDrawable(AvatarManager.getInstance().getUserAvatar(user));
}
}
} else {
message.avatar.setVisibility(View.GONE);
}
}
public int findMessagePosition(String uniqueId) {
for (int i = 0; i < realmResults.size(); i++) {
if (realmResults.get(i).getUniqueId().equals(uniqueId)) {
return i;
}
}
return RecyclerView.NO_POSITION;
}
/**
* @return New hint.
*/
private String getHint() {
AccountItem accountItem = AccountManager.getInstance().getAccount(account);
boolean online = accountItem != null && accountItem.getState().isConnected();
final AbstractContact abstractContact = RosterManager.getInstance().getBestContact(account, user);
if (!online) {
if (abstractContact instanceof RoomContact) {
return context.getString(R.string.muc_is_unavailable);
} else {
return context.getString(R.string.account_is_offline);
}
} else if (!abstractContact.getStatusMode().isOnline()) {
if (abstractContact instanceof RoomContact) {
return context.getString(R.string.muc_is_unavailable);
} else {
return context.getString(R.string.contact_is_offline);
}
}
return null;
}
static class BasicMessage extends RecyclerView.ViewHolder {
TextView messageText;
BasicMessage(View itemView, @StyleRes int appearance) {
super(itemView);
messageText = (TextView) itemView.findViewById(R.id.message_text);
messageText.setTextAppearance(itemView.getContext(), appearance);
}
}
public static abstract class Message extends BasicMessage implements View.OnClickListener {
private static final String LOG_TAG = Message.class.getSimpleName();
TextView messageTime;
TextView messageHeader;
TextView messageUnencrypted;
View messageBalloon;
MessageClickListener onClickListener;
ImageView messageImage;
ImageView statusIcon;
public Message(View itemView, MessageClickListener onClickListener, @StyleRes int appearance) {
super(itemView, appearance);
this.onClickListener = onClickListener;
messageTime = (TextView) itemView.findViewById(R.id.message_time);
messageHeader = (TextView) itemView.findViewById(R.id.message_header);
messageUnencrypted = (TextView) itemView.findViewById(R.id.message_unencrypted);
messageBalloon = itemView.findViewById(R.id.message_balloon);
messageImage = (ImageView) itemView.findViewById(R.id.message_image);
statusIcon = (ImageView) itemView.findViewById(R.id.message_status_icon);
itemView.setOnClickListener(this);
messageImage.setOnClickListener(this);
}
@Override
public void onClick(View v) {
int adapterPosition = getAdapterPosition();
if (adapterPosition == RecyclerView.NO_POSITION) {
LogManager.w(LOG_TAG, "onClick: no position");
return;
}
if (v.getId() == R.id.message_image) {
onClickListener.onMessageImageClick(itemView, adapterPosition);
} else {
onClickListener.onMessageClick(messageBalloon, adapterPosition);
}
}
public interface MessageClickListener {
void onMessageClick(View caller, int position);
void onMessageImageClick(View caller, int position);
}
}
private static class IncomingMessage extends Message {
public ImageView avatar;
IncomingMessage(View itemView, MessageClickListener listener, @StyleRes int appearance) {
super(itemView, listener, appearance);
avatar = (ImageView) itemView.findViewById(R.id.avatar);
}
}
private static class OutgoingMessage extends Message {
TextView messageFileInfo;
ProgressBar progressBar;
OutgoingMessage(View itemView, MessageClickListener listener, @StyleRes int appearance) {
super(itemView, listener, appearance);
progressBar = (ProgressBar) itemView.findViewById(R.id.message_progress_bar);
messageFileInfo = (TextView) itemView.findViewById(R.id.message_file_info);
}
}
}