From e5841ff6ec5315213bf24d34759ee363fb70b75d Mon Sep 17 00:00:00 2001 From: Mario Danic Date: Thu, 12 Jul 2018 23:06:31 +0200 Subject: [PATCH] Fix #235 Signed-off-by: Mario Danic --- .../MagicIncomingTextMessageViewHolder.java | 11 + .../MagicOutcomingTextMessageViewHolder.java | 16 +- .../talk/utils/emoticons/EmoticonSpan.java | 89 +++++++ .../talk/utils/emoticons/EmoticonUtils.java | 241 ++++++++++++++++++ app/src/main/res/layout/controller_chat.xml | 4 +- 5 files changed, 358 insertions(+), 3 deletions(-) create mode 100644 app/src/main/java/com/nextcloud/talk/utils/emoticons/EmoticonSpan.java create mode 100644 app/src/main/java/com/nextcloud/talk/utils/emoticons/EmoticonUtils.java diff --git a/app/src/main/java/com/nextcloud/talk/adapters/messages/MagicIncomingTextMessageViewHolder.java b/app/src/main/java/com/nextcloud/talk/adapters/messages/MagicIncomingTextMessageViewHolder.java index bdb1ac9d9..6d378c084 100644 --- a/app/src/main/java/com/nextcloud/talk/adapters/messages/MagicIncomingTextMessageViewHolder.java +++ b/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> 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); diff --git a/app/src/main/java/com/nextcloud/talk/adapters/messages/MagicOutcomingTextMessageViewHolder.java b/app/src/main/java/com/nextcloud/talk/adapters/messages/MagicOutcomingTextMessageViewHolder.java index a90f66ae2..9f1fe3156 100644 --- a/app/src/main/java/com/nextcloud/talk/adapters/messages/MagicOutcomingTextMessageViewHolder.java +++ b/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 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); } - } diff --git a/app/src/main/java/com/nextcloud/talk/utils/emoticons/EmoticonSpan.java b/app/src/main/java/com/nextcloud/talk/utils/emoticons/EmoticonSpan.java new file mode 100644 index 000000000..e1b3d86d2 --- /dev/null +++ b/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 existingSpanPositions = new ArrayList<>(existingSpans.length); + for (EmoticonSpan existingSpan : existingSpans) + existingSpanPositions.add(text.getSpanStart(existingSpan)); + + //Get location and unicode of all emoticons. + final List 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 findAllEmoticons(@NonNull final Context context, + @Nullable final CharSequence text, + @NonNull final EmoticonProvider emoticonProvider) { + final List 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 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; + } + } +} diff --git a/app/src/main/res/layout/controller_chat.xml b/app/src/main/res/layout/controller_chat.xml index 597f4287b..1aaae1da6 100644 --- a/app/src/main/res/layout/controller_chat.xml +++ b/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"