Browse Source

Fix #235

Signed-off-by: Mario Danic <mario@lovelyhq.com>
Mario Danic 7 years ago
parent
commit
e5841ff6ec

+ 11 - 0
app/src/main/java/com/nextcloud/talk/adapters/messages/MagicIncomingTextMessageViewHolder.java

@@ -20,9 +20,11 @@
 
 package com.nextcloud.talk.adapters.messages;
 
+import android.content.Context;
 import android.text.Spannable;
 import android.text.SpannableString;
 import android.text.TextUtils;
+import android.text.style.RelativeSizeSpan;
 import android.view.View;
 import android.widget.TextView;
 
@@ -33,6 +35,7 @@ import com.nextcloud.talk.models.database.UserEntity;
 import com.nextcloud.talk.models.json.chat.ChatMessage;
 import com.nextcloud.talk.utils.DisplayUtils;
 import com.nextcloud.talk.utils.database.user.UserUtils;
+import com.nextcloud.talk.utils.emoticons.EmoticonUtils;
 import com.stfalcon.chatkit.messages.MessageHolders;
 
 import java.util.HashMap;
@@ -57,12 +60,14 @@ public class MagicIncomingTextMessageViewHolder
     UserUtils userUtils;
 
     private UserEntity currentUser;
+    private View itemView;
 
     public MagicIncomingTextMessageViewHolder(View itemView) {
         super(itemView);
         ButterKnife.bind(this, itemView);
         NextcloudTalkApplication.getSharedApplication().getComponentApplication().inject(this);
 
+        this.itemView = itemView;
         currentUser = userUtils.getCurrentUser();
     }
 
@@ -79,6 +84,8 @@ public class MagicIncomingTextMessageViewHolder
 
         HashMap<String, HashMap<String, String>> messageParameters = message.getMessageParameters();
 
+        Context context = NextcloudTalkApplication.getSharedApplication().getApplicationContext();
+
         Spannable messageString = new SpannableString(message.getText());
 
         if (messageParameters != null && message.getMessageParameters().size() > 0) {
@@ -100,6 +107,10 @@ public class MagicIncomingTextMessageViewHolder
                 }
             }
 
+        } else if (EmoticonUtils.isMessageWithSingleEmoticonOnly(context, message.getText())) {
+            messageString.setSpan(new RelativeSizeSpan(2.5f), 0, messageString.length(),
+                    Spannable.SPAN_INCLUSIVE_EXCLUSIVE);
+            itemView.setSelected(true);
         }
 
         messageText.setText(messageString);

+ 15 - 1
app/src/main/java/com/nextcloud/talk/adapters/messages/MagicOutcomingTextMessageViewHolder.java

@@ -20,8 +20,10 @@
 
 package com.nextcloud.talk.adapters.messages;
 
+import android.content.Context;
 import android.text.Spannable;
 import android.text.SpannableString;
+import android.text.style.RelativeSizeSpan;
 import android.view.View;
 
 import com.kevalpatel2106.emoticongifkeyboard.widget.EmoticonTextView;
@@ -31,6 +33,7 @@ import com.nextcloud.talk.models.database.UserEntity;
 import com.nextcloud.talk.models.json.chat.ChatMessage;
 import com.nextcloud.talk.utils.DisplayUtils;
 import com.nextcloud.talk.utils.database.user.UserUtils;
+import com.nextcloud.talk.utils.emoticons.EmoticonUtils;
 import com.stfalcon.chatkit.messages.MessageHolders;
 
 import java.util.HashMap;
@@ -51,11 +54,14 @@ public class MagicOutcomingTextMessageViewHolder extends MessageHolders.Outcomin
 
     private UserEntity currentUser;
 
+    private View itemView;
+
     public MagicOutcomingTextMessageViewHolder(View itemView) {
         super(itemView);
         ButterKnife.bind(this, itemView);
         NextcloudTalkApplication.getSharedApplication().getComponentApplication().inject(this);
 
+        this.itemView = itemView;
         currentUser = userUtils.getCurrentUser();
     }
 
@@ -67,6 +73,10 @@ public class MagicOutcomingTextMessageViewHolder extends MessageHolders.Outcomin
 
         Spannable messageString = new SpannableString(message.getText());
 
+        Context context = NextcloudTalkApplication.getSharedApplication().getApplicationContext();
+
+        itemView.setSelected(false);
+
         if (messageParameters != null && message.getMessageParameters().size() > 0) {
             for (String key : message.getMessageParameters().keySet()) {
                 HashMap<String, String> individualHashMap = message.getMessageParameters().get(key);
@@ -80,9 +90,13 @@ public class MagicOutcomingTextMessageViewHolder extends MessageHolders.Outcomin
                 }
             }
 
+        } else if (EmoticonUtils.isMessageWithSingleEmoticonOnly(context, message.getText())) {
+            messageString.setSpan(new RelativeSizeSpan(2.5f), 0, messageString.length(),
+                    Spannable.SPAN_INCLUSIVE_EXCLUSIVE);
+            itemView.setSelected(true);
         }
 
+
         messageText.setText(messageString);
     }
-
 }

+ 89 - 0
app/src/main/java/com/nextcloud/talk/utils/emoticons/EmoticonSpan.java

@@ -0,0 +1,89 @@
+/*
+ * Copyright 2017 Keval Patel.
+ *
+ * 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.nextcloud.talk.utils.emoticons;
+
+import android.content.Context;
+import android.graphics.Canvas;
+import android.graphics.Paint;
+import android.graphics.drawable.Drawable;
+import android.support.v7.content.res.AppCompatResources;
+import android.text.style.DynamicDrawableSpan;
+import android.text.style.ImageSpan;
+
+/**
+ * Created by Keval Patel on 20/08/17.
+ * The {@link ImageSpan} to display custom emoticon icon based on the unicode.
+ *
+ * @author 'https://github.com/kevalpatel2106'
+ * @see <a href='https://github.com/rockerhieu/emojicon/blob/master/library/src/main/java/io/github/rockerhieu/emojicon/EmojiconSpan.java>EmojiconSpan.java</a>
+ */
+
+final class EmoticonSpan extends DynamicDrawableSpan {
+    private final float mEmoticonSize;
+    private final Context mContext;
+    private final int mEmoticonIcon;
+    private Drawable mDeferredDrawable;
+
+    EmoticonSpan(final Context context, final int emoticonIcon, final float size) {
+        this.mContext = context;
+        this.mEmoticonIcon = emoticonIcon;
+        this.mEmoticonSize = size;
+    }
+
+    @SuppressWarnings("ConstantConditions")
+    @Override
+    public Drawable getDrawable() {
+        if (mDeferredDrawable == null) {
+            mDeferredDrawable = AppCompatResources.getDrawable(mContext, mEmoticonIcon);
+            mDeferredDrawable.setBounds(0, 0, (int) mEmoticonSize, (int) mEmoticonSize);
+        }
+        return mDeferredDrawable;
+    }
+
+    @Override
+    public int getSize(final Paint paint, final CharSequence text, final int start,
+                       final int end, final Paint.FontMetricsInt fontMetrics) {
+        if (fontMetrics != null) {
+            final Paint.FontMetrics paintFontMetrics = paint.getFontMetrics();
+            final float fontHeight = paintFontMetrics.descent - paintFontMetrics.ascent;
+            final float centerY = paintFontMetrics.ascent + fontHeight / 2;
+
+            fontMetrics.ascent = (int) (centerY - mEmoticonSize / 2);
+            fontMetrics.top = fontMetrics.ascent;
+            fontMetrics.bottom = (int) (centerY + mEmoticonSize / 2);
+            fontMetrics.descent = fontMetrics.bottom;
+        }
+
+        return (int) mEmoticonSize;
+    }
+
+    @Override
+    public void draw(final Canvas canvas, final CharSequence text, final int start,
+                     final int end, final float x, final int top, final int y,
+                     final int bottom, final Paint paint) {
+        final Drawable drawable = getDrawable();
+        final Paint.FontMetrics paintFontMetrics = paint.getFontMetrics();
+        final float fontHeight = paintFontMetrics.descent - paintFontMetrics.ascent;
+        final float centerY = y + paintFontMetrics.descent - fontHeight / 2;
+        final float transitionY = centerY - mEmoticonSize / 2;
+
+        canvas.save();
+        canvas.translate(x, transitionY);
+        drawable.draw(canvas);
+        canvas.restore();
+    }
+}

+ 241 - 0
app/src/main/java/com/nextcloud/talk/utils/emoticons/EmoticonUtils.java

@@ -0,0 +1,241 @@
+/*
+ * Copyright 2017 Keval Patel.
+ *
+ * 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.
+ *
+ *  Modified by Mario Danic (mario@lovelyhq.com) - Copyright 2018
+ */
+
+package com.nextcloud.talk.utils.emoticons;
+
+import android.content.Context;
+import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
+import android.text.Spannable;
+import android.text.TextUtils;
+
+import com.kevalpatel2106.emoticongifkeyboard.R;
+import com.kevalpatel2106.emoticongifkeyboard.emoticons.Emoticon;
+import com.kevalpatel2106.emoticongifkeyboard.emoticons.EmoticonProvider;
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+/**
+ * Created by Keval Patel on 20/08/17.
+ * Utils to find emoticons from the string.
+ *
+ * @author 'https://github.com/kevalpatel2106'
+ */
+
+public final class EmoticonUtils {
+    /**
+     * {@link Pattern} to find the supported emoticons unicodes.
+     */
+    private static Pattern sRegexPattern;
+
+    /**
+     * Private constructor.
+     */
+    private EmoticonUtils() {
+        //Do nothing
+    }
+
+    /**
+     * Replace the system emoticons with the provided custom emoticons drawable. This will find the
+     * unicodes with supported emoticons in the provided text and  will replace the emoticons with
+     * appropriate images.
+     *
+     * @param context          instance of caller.
+     * @param text             Text to replace
+     * @param emoticonProvider {@link EmoticonProvider} for emoticons images
+     * @param emoticonSize     Size of the emoticons in dp
+     * @return Modified text.
+     */
+    public static void replaceWithImages(@NonNull final Context context,
+                                         @NonNull final Spannable text,
+                                         @NonNull final EmoticonProvider emoticonProvider,
+                                         final int emoticonSize) {
+
+        final EmoticonSpan[] existingSpans = text.getSpans(0, text.length(), EmoticonSpan.class);
+        final ArrayList<Integer> existingSpanPositions = new ArrayList<>(existingSpans.length);
+        for (EmoticonSpan existingSpan : existingSpans)
+            existingSpanPositions.add(text.getSpanStart(existingSpan));
+
+        //Get location and unicode of all emoticons.
+        final List<EmoticonRange> findAllEmojis = findAllEmoticons(context, text, emoticonProvider);
+
+        //Replace all the emoticons with their relatives.
+        for (int i = 0; i < findAllEmojis.size(); i++) {
+            final EmoticonRange location = findAllEmojis.get(i);
+            if (!existingSpanPositions.contains(location.mStartPos)) {
+                text.setSpan(new EmoticonSpan(context, location.mEmoticon.getIcon(), emoticonSize),
+                        location.mStartPos,
+                        location.mEndPos,
+                        Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
+            }
+        }
+    }
+
+    /**
+     * Find all the unicodes that represents emoticons and location of starting and ending point of that emoticon.
+     *
+     * @param context          Instance of caller.
+     * @param text             Text to replace
+     * @param emoticonProvider {@link EmoticonProvider} for emoticons images
+     * @return List of {@link EmoticonRange}.
+     * @see EmoticonRange
+     */
+    @NonNull
+    private static List<EmoticonRange> findAllEmoticons(@NonNull final Context context,
+                                                        @Nullable final CharSequence text,
+                                                        @NonNull final EmoticonProvider emoticonProvider) {
+        final List<EmoticonRange> result = new ArrayList<>();
+
+        if (!TextUtils.isEmpty(text)) {
+            final Matcher matcher = getRegex(context).matcher(text);
+            while (matcher.find()) {
+                String unicode = text.subSequence(matcher.start(), matcher.end()).toString();
+                if (emoticonProvider.hasEmoticonIcon(unicode)) { //Check if the the emoticon has icon?
+                    final Emoticon found = new Emoticon(unicode, emoticonProvider.getIcon(unicode));
+
+                    //Add this emoticon to change list.
+                    result.add(new EmoticonRange(matcher.start(), matcher.end(), found));
+                }
+            }
+        }
+
+        return result;
+    }
+
+
+    public static boolean isMessageWithSingleEmoticonOnly(@NonNull final Context context,
+                                                          @Nullable final CharSequence text) {
+        final List<EmoticonRange> result = new ArrayList<>();
+
+        if (!TextUtils.isEmpty(text)) {
+            final Matcher matcher = getRegex(context).matcher(text);
+            while (matcher.find()) {
+                String unicode = text.subSequence(matcher.start(), matcher.end()).toString();
+                // quick hack
+                final Emoticon found = new Emoticon(unicode, R.drawable.emoji_food);
+                //Add this emoticon to change list.
+                result.add(new EmoticonRange(matcher.start(), matcher.end(), found));
+            }
+        } else {
+            return false;
+        }
+
+        return result.size() == 1 && result.get(0).mStartPos == 0 && text.length() == result.get(0).mEndPos;
+    }
+
+    /**
+     * Load the regex to parse unicode from the shared preference if {@link #sRegexPattern} is not
+     * loaded.
+     *
+     * @param context Instance.
+     * @return Regex to find emoticon unicode from string.
+     */
+    @NonNull
+    private static Pattern getRegex(@NonNull final Context context) {
+        if (sRegexPattern == null) {
+            String regex = readTextFile(context, R.raw.regex);
+            sRegexPattern = Pattern.compile(regex);
+        }
+        return sRegexPattern;
+    }
+
+    @NonNull
+    private static String readTextFile(@NonNull Context context, int rowResource) {
+        InputStream inputStream = context.getResources().openRawResource(rowResource); // getting json
+        BufferedReader br = new BufferedReader(new InputStreamReader(inputStream));
+
+        StringBuilder builder = new StringBuilder();
+        try {
+            String sCurrentLine;
+            while ((sCurrentLine = br.readLine()) != null) builder.append(sCurrentLine);
+        } catch (IOException e) {
+            e.printStackTrace();
+        } finally {
+            try {
+                inputStream.close();
+                br.close();
+            } catch (IOException e) {
+                e.printStackTrace();
+            }
+        }
+        return builder.toString();
+    }
+
+
+    /**
+     * Range of the emoticons unicode.
+     */
+    private static final class EmoticonRange {
+
+        /**
+         * Start portion of the emoticon in string.
+         */
+        final int mStartPos;
+
+        /**
+         * End portion of the emoticon in string.
+         */
+        final int mEndPos;
+
+        /**
+         * {@link Emoticon}.
+         */
+        final Emoticon mEmoticon;
+
+        /**
+         * Private constructor.
+         *
+         * @param start    Start portion of the emoticon in string.
+         * @param end      End portion of the emoticon in string.
+         * @param emoticon {@link Emoticon}
+         */
+        private EmoticonRange(final int start,
+                              final int end,
+                              @NonNull final Emoticon emoticon) {
+            this.mStartPos = start;
+            this.mEndPos = end;
+            this.mEmoticon = emoticon;
+        }
+
+        @Override
+        public boolean equals(final Object o) {
+            if (this == o) return true;
+            if (o == null || getClass() != o.getClass()) return false;
+
+            final EmoticonRange that = (EmoticonRange) o;
+            return mStartPos == that.mStartPos
+                    && mEndPos == that.mEndPos
+                    && mEmoticon.equals(that.mEmoticon);
+        }
+
+        @Override
+        public int hashCode() {
+            int result = mStartPos;
+            result = 31 * result + mEndPos;
+            result = 31 * result + mEmoticon.hashCode();
+            return result;
+        }
+    }
+}

+ 2 - 2
app/src/main/res/layout/controller_chat.xml

@@ -32,7 +32,7 @@
         android:layout_above="@+id/messageInputView"
         app:incomingDefaultBubbleColor="@color/white_two"
         app:incomingDefaultBubblePressedColor="@color/white_two"
-        app:incomingDefaultBubbleSelectedColor="@color/colorPrimaryDark"
+        app:incomingDefaultBubbleSelectedColor="@color/transparent"
         app:incomingTextColor="@color/nc_incoming_text_default"
         app:incomingTextLinkColor="@color/nc_incoming_text_default"
         app:incomingTextSize="@dimen/chat_text_size"
@@ -47,7 +47,7 @@
         app:outcomingBubblePaddingRight="@dimen/message_bubble_corners_padding"
         app:outcomingDefaultBubbleColor="@color/colorPrimary"
         app:outcomingDefaultBubblePressedColor="@color/colorPrimary"
-        app:outcomingDefaultBubbleSelectedColor="@color/colorPrimaryDark"
+        app:outcomingDefaultBubbleSelectedColor="@color/transparent"
         app:outcomingTextColor="@color/nc_outcoming_text_default"
         app:outcomingTextLinkColor="@color/nc_outcoming_text_default"
         app:outcomingTextSize="@dimen/chat_text_size"