com.slx.funstream.adapters.ChatAdapter.java Source code

Java tutorial

Introduction

Here is the source code for com.slx.funstream.adapters.ChatAdapter.java

Source

/*
 *   Copyright (C) 2015 Alex Neeky
 *
 *   Licensed under the Apache License, Version 2.0 (the "License");
 *   you may not use this file except in compliance with the License.
 *   You may obtain a copy of the License at
 *
 *          http://www.apache.org/licenses/LICENSE-2.0
 *
 *   Unless required by applicable law or agreed to in writing, software
 *   distributed under the License is distributed on an "AS IS" BASIS,
 *   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *   See the License for the specific language governing permissions and
 *   limitations under the License.
 */

package com.slx.funstream.adapters;

import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.Drawable;
import android.support.v4.content.ContextCompat;
import android.support.v7.widget.RecyclerView;
import android.text.Spannable;
import android.text.Spanned;
import android.text.style.ImageSpan;
import android.text.style.URLSpan;
import android.text.util.Linkify;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.LinearLayout;
import android.widget.RelativeLayout;
import android.widget.TextView;

import com.slx.funstream.App;
import com.slx.funstream.R;
import com.slx.funstream.auth.UserStore;
import com.slx.funstream.chat.ChatServiceController;
import com.slx.funstream.chat.SmileRepo;
import com.slx.funstream.model.ChatMessage;
import com.slx.funstream.model.ChatUser;
import com.slx.funstream.model.Message;
import com.slx.funstream.rest.model.Smile;
import com.slx.funstream.utils.PrefUtils;
import com.squareup.picasso.Picasso;
import com.squareup.picasso.Target;

import java.lang.ref.WeakReference;
import java.util.ArrayList;
import java.util.List;
import java.util.regex.Matcher;

import javax.inject.Inject;

import butterknife.BindView;
import butterknife.ButterKnife;

import static android.text.TextUtils.isEmpty;
import static com.slx.funstream.chat.SmileRepo.SMILE_PATTERN;
import static com.slx.funstream.utils.TextUtils.makeFrom;
import static com.slx.funstream.utils.TextUtils.makeTo;

public class ChatAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> implements View.OnClickListener {
    private static final String TAG = "ChatAdapter";
    private static final int FROM_TWITCH = -100;
    private static final int FROM_GG = -101;
    private final SmileRepo smileRepo;
    private final Picasso picasso;
    private final Context context;
    private List<Message> mMessages;

    private int primaryColor;
    private int darkColor;

    // Picasso does not hold a strong reference to the Target object
    private ArrayList<Target> targets = new ArrayList<>();

    private boolean isShowSmileys = true;
    private int smileSizeMultiplier;

    public void setOnChatMessageClickListener(OnChatMessageClick callback) {
        this.callback = callback;
    }

    private OnChatMessageClick callback;

    public ChatAdapter(Context context, SmileRepo smileRepo, Picasso picasso, boolean showSmileys) {
        this.context = context;
        this.smileRepo = smileRepo;
        this.picasso = picasso;
        this.isShowSmileys = showSmileys;

        primaryColor = ContextCompat.getColor(context, R.color.primary);
        darkColor = ContextCompat.getColor(context, R.color.black);

        smileSizeMultiplier = context.getResources().getInteger(R.integer.smile_size_multiplier);
    }

    @Override
    public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
        switch (viewType) {
        case ChatServiceController.MESSAGE_TYPE_CHAT:
            final View view = LayoutInflater.from(context).inflate(R.layout.row_chat_message, parent, false);

            final ChatMessageHolder chatMessageHolder = new ChatMessageHolder(view);
            chatMessageHolder.messageRoot.setOnClickListener(this);
            return chatMessageHolder;

        case ChatServiceController.MESSAGE_TYPE_SYSTEM:
            final View systemMessageView = LayoutInflater.from(context).inflate(R.layout.row_system_message, parent,
                    false);
            final SystemMessageHolder systemMessageHolder = new SystemMessageHolder(systemMessageView);
            return systemMessageHolder;
        }
        final View view = LayoutInflater.from(context).inflate(R.layout.row_chat_message, parent, false);

        final ChatMessageHolder chatMessageHolder = new ChatMessageHolder(view);
        chatMessageHolder.messageRoot.setOnClickListener(this);
        return chatMessageHolder;
    }

    @Override
    public void onBindViewHolder(RecyclerView.ViewHolder viewHolder, int position) {
        final Message m = mMessages.get(position);

        switch (m.getMessageType()) {
        case ChatServiceController.MESSAGE_TYPE_CHAT:
            ChatMessageHolder holder = (ChatMessageHolder) viewHolder;
            ChatMessage message = (ChatMessage) m;

            // Reset
            // clear after non-funstream chat messages disabling
            holder.messageRoot.setEnabled(true);
            // clear tw and gg icons
            holder.tvFrom.setCompoundDrawablesWithIntrinsicBounds(0, 0, 0, 0);
            // clear conversation
            holder.messageRoot.setBackgroundResource(0);
            //      holder.tvText.setTextColor(darkColor);

            long fromId = message.getFrom().getId();

            if (fromId == FROM_TWITCH || fromId == FROM_GG) {
                if (fromId == FROM_TWITCH) {
                    holder.tvFrom.setCompoundDrawablesWithIntrinsicBounds(R.drawable.twitch, 0, 0, 0);
                }
                if (fromId == FROM_GG) {
                    holder.tvFrom.setCompoundDrawablesWithIntrinsicBounds(R.drawable.goodgame, 0, 0, 0);
                }

                // Disable row for non non-funstream chat messages
                holder.messageRoot.setEnabled(false);
            }
            holder.setFrom(message.getFrom());
            holder.setTo(message.getTo());

            //      //   handle /me
            //      if(message.getText().contains("/me")){
            //         message.setText(message.getText().replace("/me ", ""));
            //         //
            //         holder.tvText.setTextColor(primaryColor);
            //      }

            Spannable spannable = spannableFactory.newSpannable(message.getText());
            holder.setMessage(spannable);
            if (isShowSmileys) {
                // Add smileys
                addSmiles(spannable, holder.tvText);
            }
            // Add links
            addLinks(spannable);
            // Add Images
            // Check if user allowed it
            //      if(prefUtils.isShowImages()){
            //         linksToImages(spannable, ((ChatMessageHolder holder).tvText);
            //      }

            // Set Tag
            holder.messageRoot.setTag(holder);

            break;

        case ChatServiceController.MESSAGE_TYPE_SYSTEM:
            SystemMessageHolder systemMessageHolder = (SystemMessageHolder) viewHolder;

            systemMessageHolder.setMessage(m.getText());
            break;
        }

    }

    @Override
    public int getItemCount() {
        return mMessages != null ? mMessages.size() : 0;
    }

    @Override
    public int getItemViewType(int position) {
        Message message = mMessages.get(position);
        return message.getMessageType();
    }

    private void addSmiles(Spannable spannable, TextView textView) {
        Matcher matcher = SMILE_PATTERN.matcher(spannable);
        while (matcher.find()) {
            ImageTarget target = new ImageTarget(matcher.start(), matcher.end(), textView, spannable);

            // Add target to an ArrayList to keep a strong reference
            // to prevent that the target gets garbage collected
            // before the image is placed into the view
            targets.add(target);
            Log.d(TAG, "targets.size=" + targets.size());

            Smile smile = smileRepo.getSmile(matcher.group(1));
            if (smile != null) {
                picasso.load(smile.getUrl())
                        .resize(smile.getWidth() * smileSizeMultiplier, smile.getHeight() * smileSizeMultiplier)
                        .into(target);
            }
        }
    }

    private void addLinks(Spannable spannable) {
        Linkify.addLinks(spannable, Linkify.WEB_URLS);
    }

    private void linksToImages(Spannable spannable, TextView textView) {
        //      Matcher matcher = Linkify.WEB_URLS.matcher(spannable);
        //      URLSpan[] spans = textView.getUrls();
        URLSpan[] spans = spannable.getSpans(0, spannable.length(), URLSpan.class);
        Log.d(TAG, "URLSpan[] size=" + spans.length);
        for (URLSpan span : spans) {
            String url = span.getURL();
            Log.d(TAG, "START: " + spannable.getSpanStart(span) + "END: " + spannable.getSpanEnd(span) + " " + url);
            ImageTarget t = new ImageTarget(spannable.getSpanStart(span), spannable.getSpanEnd(span), textView,
                    spannable);
            picasso.load(url).resize(300, 300).centerInside().onlyScaleDown().into(t);
            targets.add(t);
        }
    }

    public static class ChatMessageHolder extends RecyclerView.ViewHolder {
        @BindView(R.id.tvText)
        TextView tvText;
        @BindView(R.id.tvFrom)
        TextView tvFrom;
        @BindView(R.id.tvTo)
        TextView tvTo;
        @BindView(R.id.messageRoot)
        RelativeLayout messageRoot;

        public ChatMessageHolder(View itemView) {
            super(itemView);
            ButterKnife.bind(this, itemView);
        }

        public void setMessage(CharSequence message) {
            if (message == null)
                return;
            tvText.setText(message);
        }

        public void setFrom(ChatUser user) {
            if (user == null || isEmpty(user.getName())) {
                tvFrom.setText("");
            } else {
                tvFrom.setText(makeFrom(user));
            }
        }

        public void setTo(ChatUser user) {
            if (user == null || isEmpty(user.getName())) {
                tvTo.setText("");
            } else {
                tvTo.setText(makeTo(user));
            }
        }
    }

    public static class SystemMessageHolder extends RecyclerView.ViewHolder {
        @BindView(R.id.tvText)
        TextView tvText;
        @BindView(R.id.messageRoot)
        LinearLayout messageRoot;

        public SystemMessageHolder(View itemView) {
            super(itemView);
            ButterKnife.bind(this, itemView);
        }

        public void setMessage(CharSequence message) {
            if (message == null)
                return;
            tvText.setText(message);
        }
    }

    private class ImageTarget implements Target {
        int start;
        int end;
        WeakReference<TextView> messageText;
        Spannable spannable;

        public ImageTarget(int start, int end, TextView messageText, Spannable spannable) {
            this.start = start;
            this.end = end;
            this.messageText = new WeakReference<>(messageText);
            this.spannable = spannable;
        }

        @Override
        public void onBitmapLoaded(Bitmap bitmap, Picasso.LoadedFrom from) {
            // Get a weak reference to the TextView
            // because holder pattern in Recyclerview will replace the View
            // inside the holder before the AsyncTask is done
            TextView textView = messageText.get();

            // Get existing ImageSpans to add them later again,
            ImageSpan[] spans = spannable.getSpans(0, textView.length(), ImageSpan.class);

            // Create drawable from target bitmap
            BitmapDrawable bitmapDrawable = new BitmapDrawable(context.getResources(), bitmap);
            bitmapDrawable.setBounds(0, 0, bitmapDrawable.getIntrinsicWidth(), bitmapDrawable.getIntrinsicHeight());
            ImageSpan imageSpan = new ImageSpan(bitmapDrawable, ImageSpan.ALIGN_BASELINE);

            // Add ImageSpan to the Spannable
            spannable.setSpan(imageSpan, start, end, Spannable.SPAN_INCLUSIVE_INCLUSIVE);

            // Add previously added ImageSpans
            if (spans.length >= 1) {
                for (ImageSpan image : spans) {
                    spannable.setSpan(image, spannable.getSpanStart(image), spannable.getSpanEnd(image),
                            Spanned.SPAN_INCLUSIVE_INCLUSIVE);
                }
            }
            // Update edited Spannable in the TextView
            textView.setText(spannable, TextView.BufferType.SPANNABLE);

            // Remove target from ArrayList to allow GC
            targets.remove(this);
        }

        @Override
        public void onBitmapFailed(Drawable errorDrawable) {
            targets.remove(this);
        }

        @Override
        public void onPrepareLoad(Drawable placeHolderDrawable) {
            //
        }
    }

    private static final Spannable.Factory spannableFactory = Spannable.Factory.getInstance();

    @Override
    public void onClick(View view) {
        final int viewId = view.getId();
        if (viewId == R.id.messageRoot) {
            if (callback != null) {
                ChatMessageHolder holder = (ChatMessageHolder) view.getTag();
                callback.onChatMessageClicked((ChatMessage) mMessages.get(holder.getLayoutPosition()));
            }
        }
    }

    public void updateChatAdapter(List<Message> messages) {
        if (messages != null) {
            mMessages = messages;
            this.notifyDataSetChanged();
        }
    }

    public interface OnChatMessageClick {
        void onChatMessageClicked(ChatMessage chatMessage);
    }
}