From f4f96bb7d9cc797e8d38f75f250363fae68fe74b Mon Sep 17 00:00:00 2001 From: Mario Danic Date: Tue, 9 Apr 2019 21:18:02 +0200 Subject: [PATCH] First step towards mention chips Signed-off-by: Mario Danic --- app/build.gradle | 11 ++- .../MagicIncomingTextMessageViewHolder.java | 20 +++--- .../MagicOutcomingTextMessageViewHolder.java | 20 ++++-- .../MentionAutocompleteCallback.java | 19 ++++- .../talk/controllers/ChatController.java | 14 ++-- .../talk/controllers/LockedController.java | 3 +- .../nextcloud/talk/utils/DisplayUtils.java | 39 ++++++++-- .../com/nextcloud/talk/utils/text/Spans.java | 11 +-- .../talk/utils/text/TextAlignedImageSpan.java | 71 +++++++++++++++++++ .../item_custom_incoming_text_message.xml | 3 +- .../item_custom_outcoming_text_message.xml | 1 + .../main/res/layout/view_message_input.xml | 1 + app/src/main/res/values/styles.xml | 8 +++ .../main/res/xml/chip_accent_background.xml | 26 +++++++ .../res/xml/chip_outgoing_own_mention.xml | 26 +++++++ .../main/res/xml/chip_simple_background.xml | 26 +++++++ 16 files changed, 262 insertions(+), 37 deletions(-) create mode 100644 app/src/main/java/com/nextcloud/talk/utils/text/TextAlignedImageSpan.java create mode 100644 app/src/main/res/xml/chip_accent_background.xml create mode 100644 app/src/main/res/xml/chip_outgoing_own_mention.xml create mode 100644 app/src/main/res/xml/chip_simple_background.xml diff --git a/app/build.gradle b/app/build.gradle index 22335289a..26c2b442e 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -17,8 +17,8 @@ android { targetSdkVersion 28 testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" - versionCode 90 - versionName "6.0.0beta2" + versionCode 91 + versionName "6.0.0beta3" flavorDimensions "default" renderscriptTargetApi 19 @@ -100,7 +100,6 @@ android { } ext { - supportLibraryVersion = '28.0.0' workVersion = "1.0.0" } @@ -115,17 +114,17 @@ dependencies { implementation fileTree(dir: 'libs', include: ['*.jar']) implementation 'androidx.appcompat:appcompat:1.0.2' implementation 'com.google.android.material:material:1.0.0' - implementation 'androidx.constraintlayout:constraintlayout:2.0.0-alpha3' + implementation 'androidx.constraintlayout:constraintlayout:2.0.0-alpha4' implementation 'com.github.vanniktech:Emoji:746caa4623' implementation 'org.michaelevans.colorart:library:0.0.3' implementation "android.arch.work:work-runtime:${workVersion}" implementation "android.arch.work:work-rxjava2:${workVersion}" androidTestImplementation "android.arch.work:work-testing:${workVersion}" - implementation 'androidx.biometric:biometric:1.0.0-alpha03' + implementation 'androidx.biometric:biometric:1.0.0-alpha04' implementation "androidx.lifecycle:lifecycle-extensions:2.0.0" - implementation 'androidx.multidex:multidex:2.0.0' + implementation 'androidx.multidex:multidex:2.0.1' implementation 'io.reactivex.rxjava2:rxandroid:2.1.1' implementation "io.reactivex.rxjava2:rxjava:2.2.7" 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 42aaf6f1c..a548b610f 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 @@ -145,18 +145,22 @@ public class MagicIncomingTextMessageViewHolder Map individualHashMap = message.getMessageParameters().get(key); if (individualHashMap != null) { if (individualHashMap.get("type").equals("user") || individualHashMap.get("type").equals("guest") || individualHashMap.get("type").equals("call")) { - int color; - if (individualHashMap.get("id").equals(message.getActiveUserId())) { - color = NextcloudTalkApplication.getSharedApplication().getResources().getColor(R.color - .nc_incoming_text_mention_you); + messageString = + DisplayUtils.searchAndReplaceWithMentionSpan(messageText.getContext(), + messageString, + individualHashMap.get("id"), + individualHashMap.get("name"), + R.xml.chip_simple_background); } else { - color = NextcloudTalkApplication.getSharedApplication().getResources().getColor(R.color - .nc_incoming_text_mention_others); + messageString = + DisplayUtils.searchAndReplaceWithMentionSpan(messageText.getContext(), + messageString, + individualHashMap.get("id"), + individualHashMap.get("name"), + R.xml.chip_accent_background); } - messageString = DisplayUtils.searchAndColor(messageString, - "@" + individualHashMap.get("name"), color); } else if (individualHashMap.get("type").equals("file")) { itemView.setOnClickListener(v -> { Intent browserIntent = new Intent(Intent.ACTION_VIEW, Uri.parse(individualHashMap.get("link"))); 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 26633d1fd..bcf6d341b 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 @@ -58,6 +58,9 @@ public class MagicOutcomingTextMessageViewHolder extends MessageHolders.Outcomin @Inject UserUtils userUtils; + @Inject + Context context; + private View itemView; public MagicOutcomingTextMessageViewHolder(View itemView) { @@ -76,7 +79,6 @@ public class MagicOutcomingTextMessageViewHolder extends MessageHolders.Outcomin Spannable messageString = new SpannableString(message.getText()); - Context context = NextcloudTalkApplication.getSharedApplication().getApplicationContext(); itemView.setSelected(false); messageTimeView.setTextColor(context.getResources().getColor(R.color.white60)); @@ -92,11 +94,19 @@ public class MagicOutcomingTextMessageViewHolder extends MessageHolders.Outcomin if (individualHashMap.get("type").equals("user") || individualHashMap.get("type").equals("guest") || individualHashMap.get("type").equals("call")) { if (!individualHashMap.get("id").equals(message.getActiveUserId())) { messageString = - DisplayUtils.searchAndColor(messageString, - "@" + individualHashMap.get("name"), NextcloudTalkApplication - .getSharedApplication().getResources().getColor(R.color.nc_outcoming_text_default)); + DisplayUtils.searchAndReplaceWithMentionSpan(messageText.getContext(), + messageString, + individualHashMap.get("id"), + individualHashMap.get("name"), + R.xml.chip_simple_background); + } else { + messageString = + DisplayUtils.searchAndReplaceWithMentionSpan(messageText.getContext(), + messageString, + individualHashMap.get("id"), + individualHashMap.get("name"), + R.xml.chip_outgoing_own_mention); } - } else if (individualHashMap.get("type").equals("file")) { itemView.setOnClickListener(v -> { Intent browserIntent = new Intent(Intent.ACTION_VIEW, Uri.parse(individualHashMap.get("link"))); diff --git a/app/src/main/java/com/nextcloud/talk/callbacks/MentionAutocompleteCallback.java b/app/src/main/java/com/nextcloud/talk/callbacks/MentionAutocompleteCallback.java index 9785db472..450b157b3 100644 --- a/app/src/main/java/com/nextcloud/talk/callbacks/MentionAutocompleteCallback.java +++ b/app/src/main/java/com/nextcloud/talk/callbacks/MentionAutocompleteCallback.java @@ -20,15 +20,24 @@ package com.nextcloud.talk.callbacks; -import android.graphics.Typeface; +import android.content.Context; import android.text.Editable; import android.text.Spanned; +import android.text.style.DynamicDrawableSpan; +import com.nextcloud.talk.R; import com.nextcloud.talk.models.json.mention.Mention; +import com.nextcloud.talk.utils.DisplayUtils; import com.nextcloud.talk.utils.MagicCharPolicy; import com.nextcloud.talk.utils.text.Spans; import com.otaliastudios.autocomplete.AutocompleteCallback; public class MentionAutocompleteCallback implements AutocompleteCallback { + private Context context; + + public MentionAutocompleteCallback(Context context) { + this.context = context; + } + @Override public boolean onPopupItemClicked(Editable editable, Mention item) { int[] range = MagicCharPolicy.getQueryRange(editable); @@ -37,8 +46,12 @@ public class MentionAutocompleteCallback implements AutocompleteCallback= editable.getSpanStart(mentionSpan) && start < editable.getSpanEnd(mentionSpan)) { @@ -482,7 +485,7 @@ public class ChatController extends BaseController implements MessagesListAdapte float elevation = 6f; Drawable backgroundDrawable = new ColorDrawable(Color.WHITE); AutocompletePresenter presenter = new MentionAutocompletePresenter(getApplicationContext(), roomToken); - AutocompleteCallback callback = new MentionAutocompleteCallback(); + AutocompleteCallback callback = new MentionAutocompleteCallback(getActivity()); if (mentionAutocomplete == null && messageInput != null) { mentionAutocomplete = Autocomplete.on(messageInput) @@ -728,8 +731,9 @@ public class ChatController extends BaseController implements MessagesListAdapte private void submitMessage() { final Editable editable = messageInput.getEditableText(); - Spans.MentionSpan mentionSpans[] = editable.getSpans(0, editable.length(), Spans.MentionSpan.class); - Spans.MentionSpan mentionSpan; + Spans.MentionChipSpan mentionSpans[] = editable.getSpans(0, editable.length(), + Spans.MentionChipSpan.class); + Spans.MentionChipSpan mentionSpan; for (int i = 0; i < mentionSpans.length; i++) { mentionSpan = mentionSpans[i]; editable.replace(editable.getSpanStart(mentionSpan), editable.getSpanEnd(mentionSpan), "@" + mentionSpan.getId()); diff --git a/app/src/main/java/com/nextcloud/talk/controllers/LockedController.java b/app/src/main/java/com/nextcloud/talk/controllers/LockedController.java index 903fb3810..73ba4d271 100644 --- a/app/src/main/java/com/nextcloud/talk/controllers/LockedController.java +++ b/app/src/main/java/com/nextcloud/talk/controllers/LockedController.java @@ -32,6 +32,7 @@ import android.view.ViewGroup; import androidx.annotation.NonNull; import androidx.annotation.RequiresApi; import androidx.biometric.BiometricPrompt; +import androidx.fragment.app.FragmentActivity; import autodagger.AutoInjector; import butterknife.OnClick; import com.nextcloud.talk.R; @@ -88,7 +89,7 @@ public class LockedController extends BaseController { Executor executor = Executors.newSingleThreadExecutor(); - final BiometricPrompt biometricPrompt = new BiometricPrompt((MainActivity) context, executor, + final BiometricPrompt biometricPrompt = new BiometricPrompt((FragmentActivity) context, executor, new BiometricPrompt.AuthenticationCallback() { @Override public void onAuthenticationSucceeded(@NonNull BiometricPrompt.AuthenticationResult result) { diff --git a/app/src/main/java/com/nextcloud/talk/utils/DisplayUtils.java b/app/src/main/java/com/nextcloud/talk/utils/DisplayUtils.java index ffbe23455..30e0b5b80 100644 --- a/app/src/main/java/com/nextcloud/talk/utils/DisplayUtils.java +++ b/app/src/main/java/com/nextcloud/talk/utils/DisplayUtils.java @@ -37,10 +37,7 @@ import android.net.Uri; import android.os.Build; import android.text.*; import android.text.method.LinkMovementMethod; -import android.text.style.AbsoluteSizeSpan; -import android.text.style.ClickableSpan; -import android.text.style.ForegroundColorSpan; -import android.text.style.StyleSpan; +import android.text.style.*; import android.util.Log; import android.util.TypedValue; import android.view.View; @@ -57,8 +54,10 @@ import com.facebook.imagepipeline.image.ImageInfo; import com.facebook.imagepipeline.postprocessors.RoundAsCirclePostprocessor; import com.facebook.imagepipeline.request.ImageRequest; import com.facebook.imagepipeline.request.ImageRequestBuilder; +import com.google.android.material.chip.ChipDrawable; import com.nextcloud.talk.R; import com.nextcloud.talk.application.NextcloudTalkApplication; +import com.nextcloud.talk.utils.text.Spans; import com.vanniktech.emoji.EmojiTextView; import java.lang.reflect.Constructor; @@ -209,6 +208,38 @@ public class DisplayUtils { } + public static Drawable getDrawableForMentionChipSpan(Context context, String label, @XmlRes int chipResource) { + ChipDrawable chip = ChipDrawable.createFromResource(context, chipResource); + chip.setText(label); + chip.setBounds(0, 0, chip.getIntrinsicWidth(), chip.getIntrinsicHeight()); + return chip; + } + + public static Spannable searchAndReplaceWithMentionSpan(Context context, Spannable text, + String id, String label, + @XmlRes int chipXmlRes) { + + Spannable spannableString = new SpannableString(text); + String stringText = text.toString(); + + Matcher m = Pattern.compile("@" + label, + Pattern.CASE_INSENSITIVE | Pattern.LITERAL | Pattern.MULTILINE) + .matcher(spannableString); + + int lastStartIndex = -1; + Spans.MentionChipSpan mentionChipSpan; + while (m.find()) { + int start = stringText.indexOf(m.group(), lastStartIndex); + int end = start + m.group().length(); + lastStartIndex = end; + mentionChipSpan = new Spans.MentionChipSpan(DisplayUtils.getDrawableForMentionChipSpan(context, + label, chipXmlRes), DynamicDrawableSpan.ALIGN_BASELINE, id, label); + spannableString.setSpan(mentionChipSpan, start, end, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); + } + + return spannableString; + + } public static Spannable searchAndColor(Spannable text, String searchText, @ColorInt int color) { Spannable spannableString = new SpannableString(text); diff --git a/app/src/main/java/com/nextcloud/talk/utils/text/Spans.java b/app/src/main/java/com/nextcloud/talk/utils/text/Spans.java index 1bcd93e54..ac7988480 100644 --- a/app/src/main/java/com/nextcloud/talk/utils/text/Spans.java +++ b/app/src/main/java/com/nextcloud/talk/utils/text/Spans.java @@ -20,20 +20,23 @@ package com.nextcloud.talk.utils.text; +import android.graphics.drawable.Drawable; import android.text.style.StyleSpan; +import androidx.annotation.NonNull; import lombok.Data; public class Spans { + @Data - public static class MentionSpan extends StyleSpan { + public static class MentionChipSpan extends TextAlignedImageSpan { String id; String label; - public MentionSpan(int style, String id, String label) { - super(style); + public MentionChipSpan(@NonNull Drawable drawable, int verticalAlignment, String id, String label) { + super(drawable, verticalAlignment); this.id = id; this.label = label; } - } + } diff --git a/app/src/main/java/com/nextcloud/talk/utils/text/TextAlignedImageSpan.java b/app/src/main/java/com/nextcloud/talk/utils/text/TextAlignedImageSpan.java new file mode 100644 index 000000000..e3d0de015 --- /dev/null +++ b/app/src/main/java/com/nextcloud/talk/utils/text/TextAlignedImageSpan.java @@ -0,0 +1,71 @@ +/* + * Nextcloud Talk application + * + * @author Mario Danic + * Copyright (C) 2017-2018 Mario Danic + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * at your option) any later version. + * + * This program 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 . + * + * Taken and adapter from + */ + +package com.nextcloud.talk.utils.text; + +import android.graphics.Canvas; +import android.graphics.Paint; +import android.graphics.Rect; +import android.graphics.drawable.Drawable; +import android.text.style.ImageSpan; +import androidx.annotation.NonNull; + +public class TextAlignedImageSpan extends ImageSpan { + public TextAlignedImageSpan(@NonNull Drawable drawable, int verticalAlignment) { + super(drawable, verticalAlignment); + } + + public int getSize(@NonNull Paint paint, CharSequence text, int start, int end, + Paint.FontMetricsInt fontMetricsInt) { + Drawable drawable = getDrawable(); + Rect drawableBounds = drawable.getBounds(); + + if (fontMetricsInt != null) { + Paint.FontMetricsInt fmPaint = paint.getFontMetricsInt(); + int fontHeight = fmPaint.bottom - fmPaint.top; + int drHeight = drawableBounds.bottom - drawableBounds.top; + + int top = drHeight / 2 - fontHeight / 4; + int bottom = drHeight / 2 + fontHeight / 4; + + fontMetricsInt.ascent = -bottom; + fontMetricsInt.top = -bottom; + fontMetricsInt.bottom = top; + fontMetricsInt.descent = top; + } + + return drawableBounds.right; + } + + @Override + public void draw(@NonNull Canvas canvas, CharSequence text, int start, int end, + float x, int top, int y, int bottom, @NonNull Paint paint) { + Drawable drawable = getDrawable(); + canvas.save(); + int transY; + transY = ((bottom - top) - drawable.getBounds().bottom) / 2 + top; + canvas.translate(x, transY); + drawable.draw(canvas); + canvas.restore(); + } + +} diff --git a/app/src/main/res/layout/item_custom_incoming_text_message.xml b/app/src/main/res/layout/item_custom_incoming_text_message.xml index ee19fc9ad..131bb9478 100644 --- a/app/src/main/res/layout/item_custom_incoming_text_message.xml +++ b/app/src/main/res/layout/item_custom_incoming_text_message.xml @@ -52,6 +52,7 @@ android:layout_width="match_parent" android:layout_height="wrap_content" android:textColor="@color/colorPrimary" + android:layout_marginBottom="4dp" android:textSize="12sp" /> - diff --git a/app/src/main/res/values/styles.xml b/app/src/main/res/values/styles.xml index f60f690bb..2c6a0e917 100644 --- a/app/src/main/res/values/styles.xml +++ b/app/src/main/res/values/styles.xml @@ -15,4 +15,12 @@ 12sp + + + + diff --git a/app/src/main/res/xml/chip_accent_background.xml b/app/src/main/res/xml/chip_accent_background.xml new file mode 100644 index 000000000..448cdf679 --- /dev/null +++ b/app/src/main/res/xml/chip_accent_background.xml @@ -0,0 +1,26 @@ + + + + diff --git a/app/src/main/res/xml/chip_outgoing_own_mention.xml b/app/src/main/res/xml/chip_outgoing_own_mention.xml new file mode 100644 index 000000000..b24dd66db --- /dev/null +++ b/app/src/main/res/xml/chip_outgoing_own_mention.xml @@ -0,0 +1,26 @@ + + + + diff --git a/app/src/main/res/xml/chip_simple_background.xml b/app/src/main/res/xml/chip_simple_background.xml new file mode 100644 index 000000000..1813cd05a --- /dev/null +++ b/app/src/main/res/xml/chip_simple_background.xml @@ -0,0 +1,26 @@ + + + +