com.xabber.android.ui.adapter.ChatMessageAdapter.java Source code

Java tutorial

Introduction

Here is the source code for com.xabber.android.ui.adapter.ChatMessageAdapter.java

Source

/**
 * 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);
        }
    }
}