From 7ae31df3821ee2133f0a35e3920739f92dbdeb60 Mon Sep 17 00:00:00 2001 From: sergeykuchin Date: Mon, 2 Mar 2020 16:55:41 +0100 Subject: [PATCH 1/9] project update --- build.gradle | 11 ++++++++++- gradle.properties | 4 +++- gradle/wrapper/gradle-wrapper.properties | 4 ++-- hashtag-helper/build.gradle | 12 ++++++------ .../ClickableForegroundColorSpan.java | 2 +- sample_app/build.gradle | 14 +++++++------- .../danylo/example/HashTagHelperDemoActivity.java | 7 ++++--- 7 files changed, 33 insertions(+), 21 deletions(-) diff --git a/build.gradle b/build.gradle index e0b366a..d9fe302 100644 --- a/build.gradle +++ b/build.gradle @@ -3,9 +3,14 @@ buildscript { repositories { jcenter() + maven { + url 'https://maven.google.com/' + name 'Google' + } + google() } dependencies { - classpath 'com.android.tools.build:gradle:1.5.0' + classpath 'com.android.tools.build:gradle:3.6.1' // NOTE: Do not place your application dependencies here; they belong // in the individual module build.gradle files @@ -15,6 +20,10 @@ buildscript { allprojects { repositories { jcenter() + maven { + url 'https://maven.google.com/' + name 'Google' + } } } diff --git a/gradle.properties b/gradle.properties index 1d3591c..59d5aab 100644 --- a/gradle.properties +++ b/gradle.properties @@ -15,4 +15,6 @@ # When configured, Gradle will run in incubating parallel mode. # This option should only be used with decoupled projects. More details, visit # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects -# org.gradle.parallel=true \ No newline at end of file +# org.gradle.parallel=true +android.useAndroidX=true +android.enableJetifier=true \ No newline at end of file diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index f23df6e..8a2e104 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ -#Wed Oct 21 11:34:03 PDT 2015 +#Mon Mar 02 16:51:36 CET 2020 distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-2.8-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-5.6.4-all.zip diff --git a/hashtag-helper/build.gradle b/hashtag-helper/build.gradle index e331317..8f2b134 100644 --- a/hashtag-helper/build.gradle +++ b/hashtag-helper/build.gradle @@ -1,12 +1,12 @@ apply plugin: 'com.android.library' android { - compileSdkVersion 23 - buildToolsVersion "21.1.2" + compileSdkVersion 29 + buildToolsVersion "29.0.2" defaultConfig { minSdkVersion 15 - targetSdkVersion 23 + targetSdkVersion 29 versionCode 1 versionName "1.1" } @@ -19,7 +19,7 @@ android { } dependencies { - compile fileTree(dir: 'libs', include: ['*.jar']) - testCompile 'junit:junit:4.12' - compile 'com.android.support:appcompat-v7:23.1.1' + implementation fileTree(dir: 'libs', include: ['*.jar']) + testImplementation 'junit:junit:4.12' + implementation 'androidx.appcompat:appcompat:1.1.0' } diff --git a/hashtag-helper/src/main/java/com/volokh/danylo/hashtaghelper/ClickableForegroundColorSpan.java b/hashtag-helper/src/main/java/com/volokh/danylo/hashtaghelper/ClickableForegroundColorSpan.java index cbba84a..9377af5 100644 --- a/hashtag-helper/src/main/java/com/volokh/danylo/hashtaghelper/ClickableForegroundColorSpan.java +++ b/hashtag-helper/src/main/java/com/volokh/danylo/hashtaghelper/ClickableForegroundColorSpan.java @@ -1,6 +1,6 @@ package com.volokh.danylo.hashtaghelper; -import android.support.annotation.ColorInt; +import androidx.annotation.ColorInt; import android.text.Spanned; import android.text.TextPaint; import android.text.style.ClickableSpan; diff --git a/sample_app/build.gradle b/sample_app/build.gradle index 6d68191..933d82b 100644 --- a/sample_app/build.gradle +++ b/sample_app/build.gradle @@ -1,13 +1,13 @@ apply plugin: 'com.android.application' android { - compileSdkVersion 23 - buildToolsVersion "21.1.2" + compileSdkVersion 29 + buildToolsVersion "29.0.2" defaultConfig { applicationId "com.volokh.danylo.hashtaghelper" minSdkVersion 15 - targetSdkVersion 23 + targetSdkVersion 29 versionCode 1 versionName "1.0" } @@ -20,8 +20,8 @@ android { } dependencies { - compile fileTree(dir: 'libs', include: ['*.jar']) - testCompile 'junit:junit:4.12' - compile 'com.android.support:appcompat-v7:23.1.1' - compile project(':hashtag-helper') + implementation fileTree(dir: 'libs', include: ['*.jar']) + testImplementation 'junit:junit:4.12' + implementation 'androidx.appcompat:appcompat:1.1.0' + implementation project(':hashtag-helper') } diff --git a/sample_app/src/main/java/com/volokh/danylo/example/HashTagHelperDemoActivity.java b/sample_app/src/main/java/com/volokh/danylo/example/HashTagHelperDemoActivity.java index f90782f..c5cf386 100644 --- a/sample_app/src/main/java/com/volokh/danylo/example/HashTagHelperDemoActivity.java +++ b/sample_app/src/main/java/com/volokh/danylo/example/HashTagHelperDemoActivity.java @@ -1,6 +1,5 @@ package com.volokh.danylo.example; -import android.support.v7.app.AppCompatActivity; import android.os.Bundle; import android.util.Log; import android.view.View; @@ -8,6 +7,8 @@ import android.widget.TextView; import android.widget.Toast; +import androidx.appcompat.app.AppCompatActivity; + import com.volokh.danylo.hashtaghelper.HashTagHelper; import java.util.List; @@ -56,7 +57,7 @@ protected void onCreate(Bundle savedInstanceState) { @Override public void onHashTagClicked(String hashTag) { Log.v(TAG, "onHashTagClicked [" + hashTag + "]"); - if(mToast != null){ + if (mToast != null) { mToast.cancel(); } mToast = Toast.makeText(HashTagHelperDemoActivity.this, hashTag, Toast.LENGTH_SHORT); @@ -65,7 +66,7 @@ public void onHashTagClicked(String hashTag) { @Override public void onClick(View v) { - switch (v.getId()){ + switch (v.getId()) { case R.id.get_entered_text_btn: mHashTagText.setText(mEditTextView.getText()); break; From cef4c541f1623bb72298324c415b5122de1296c5 Mon Sep 17 00:00:00 2001 From: sergeykuchin Date: Mon, 2 Mar 2020 17:32:19 +0100 Subject: [PATCH 2/9] added support for other than '#' chars --- .../danylo/hashtaghelper/HashTagHelper.java | 80 +++++++++++++------ .../example/HashTagHelperDemoActivity.java | 26 ++++-- sample_app/src/main/res/values/strings.xml | 2 +- 3 files changed, 77 insertions(+), 31 deletions(-) diff --git a/hashtag-helper/src/main/java/com/volokh/danylo/hashtaghelper/HashTagHelper.java b/hashtag-helper/src/main/java/com/volokh/danylo/hashtaghelper/HashTagHelper.java index a3062dc..dd05099 100644 --- a/hashtag-helper/src/main/java/com/volokh/danylo/hashtaghelper/HashTagHelper.java +++ b/hashtag-helper/src/main/java/com/volokh/danylo/hashtaghelper/HashTagHelper.java @@ -9,6 +9,9 @@ import android.text.style.ForegroundColorSpan; import android.widget.TextView; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; + import java.util.ArrayList; import java.util.LinkedHashSet; import java.util.List; @@ -17,45 +20,64 @@ /** * This is a helper class that should be used with {@link android.widget.EditText} or {@link android.widget.TextView} * In order to have hash-tagged words highlighted. It also provides a click listeners for every hashtag - * + *

* Example : * #ThisIsHashTagWord * #ThisIsFirst#ThisIsSecondHashTag * #hashtagendsifitfindsnotletterornotdigitsignlike_thisIsNotHighlithedArea - * */ public final class HashTagHelper implements ClickableForegroundColorSpan.OnHashTagClickListener { + private final Character DEFAULT_HASHTAG_SYMBOL = '#'; + /** * If this is not null then all of the symbols in the List will be considered as valid symbols of hashtag * For example : * mAdditionalHashTagChars = {'$','_','-'} * it means that hashtag: "#this_is_hashtag-with$dollar-sign" will be highlighted. - * + *

* Note: if mAdditionalHashTagChars would be "null" only "#this" would be highlighted - * */ private final List mAdditionalHashTagChars; + + /** + * If this is not null then all of the symbols in the List will be considered as valid start symbols + * For example: + * mStartChars = {'@','%'} + * it means that all words starting with these symbols will be highlighted. + * + * Note: if mStartChars is null, words started with '#' symbol will be highlighted + */ + private final List mStartChars; private TextView mTextView; private int mHashTagWordColor; private OnHashTagClickListener mOnHashTagClickListener; - public static final class Creator{ + public static final class Creator { - private Creator(){} + private Creator() { + } + + public static HashTagHelper create(int color, OnHashTagClickListener listener) { + return new HashTagHelper(color, listener, null, null); + } - public static HashTagHelper create(int color, OnHashTagClickListener listener){ - return new HashTagHelper(color, listener, null); + public static HashTagHelper create(int color, OnHashTagClickListener listener, @NonNull List additionalHashTagChars) { + return new HashTagHelper(color, listener, additionalHashTagChars, null); } - public static HashTagHelper create(int color, OnHashTagClickListener listener, char... additionalHashTagChars){ - return new HashTagHelper(color, listener, additionalHashTagChars); + public static HashTagHelper create( + int color, + OnHashTagClickListener listener, + List additionalHashTagChars, + @NonNull List startChars) { + return new HashTagHelper(color, listener, additionalHashTagChars, startChars); } } - public interface OnHashTagClickListener{ + public interface OnHashTagClickListener { void onHashTagClicked(String hashTag); } @@ -76,27 +98,37 @@ public void afterTextChanged(Editable s) { } }; - private HashTagHelper(int color, OnHashTagClickListener listener, char... additionalHashTagCharacters) { + private HashTagHelper( + int color, + OnHashTagClickListener listener, + @Nullable List additionalHashTagChars, + @Nullable List startChars + ) { mHashTagWordColor = color; mOnHashTagClickListener = listener; mAdditionalHashTagChars = new ArrayList<>(); + mStartChars = new ArrayList<>(); - if(additionalHashTagCharacters != null){ - for(char additionalChar : additionalHashTagCharacters){ - mAdditionalHashTagChars.add(additionalChar); - } + if (additionalHashTagChars != null) { + mAdditionalHashTagChars.addAll(additionalHashTagChars); + } + + if (startChars != null) { + mStartChars.addAll(startChars); + } else { + mStartChars.add(DEFAULT_HASHTAG_SYMBOL); } } - public void handle(TextView textView){ - if(mTextView == null){ + public void handle(TextView textView) { + if (mTextView == null) { mTextView = textView; mTextView.addTextChangedListener(mTextWatcher); // in order to use spannable we have to set buffer type mTextView.setText(mTextView.getText(), TextView.BufferType.SPANNABLE); - if(mOnHashTagClickListener != null){ + if (mOnHashTagClickListener != null) { // we need to set this in order to get onClick event mTextView.setMovementMethod(LinkMovementMethod.getInstance()); @@ -130,10 +162,10 @@ private void setColorsToAllHashTags(CharSequence text) { int startIndexOfNextHashSign; int index = 0; - while (index < text.length()- 1){ + while (index < text.length() - 1) { char sign = text.charAt(index); int nextNotLetterDigitCharIndex = index + 1; // we assume it is next. if if was not changed by findNextValidHashTagChar then index will be incremented by 1 - if(sign == '#'){ + if (mStartChars.contains(sign)) { startIndexOfNextHashSign = index; nextNotLetterDigitCharIndex = findNextValidHashTagChar(text, startIndexOfNextHashSign); @@ -171,7 +203,7 @@ private void setColorForHashTagToTheEnd(int startIndex, int nextNotLetterDigitCh CharacterStyle span; - if(mOnHashTagClickListener != null){ + if (mOnHashTagClickListener != null) { span = new ClickableForegroundColorSpan(mHashTagWordColor, this); } else { // no need for clickable span because it is messing with selection when click @@ -192,8 +224,8 @@ public List getAllHashTags(boolean withHashes) { for (CharacterStyle span : spannable.getSpans(0, text.length(), CharacterStyle.class)) { hashTags.add( text.substring(!withHashes ? spannable.getSpanStart(span) + 1/*skip "#" sign*/ - : spannable.getSpanStart(span), - spannable.getSpanEnd(span))); + : spannable.getSpanStart(span), + spannable.getSpanEnd(span))); } return new ArrayList<>(hashTags); diff --git a/sample_app/src/main/java/com/volokh/danylo/example/HashTagHelperDemoActivity.java b/sample_app/src/main/java/com/volokh/danylo/example/HashTagHelperDemoActivity.java index c5cf386..2f78649 100644 --- a/sample_app/src/main/java/com/volokh/danylo/example/HashTagHelperDemoActivity.java +++ b/sample_app/src/main/java/com/volokh/danylo/example/HashTagHelperDemoActivity.java @@ -11,6 +11,7 @@ import com.volokh.danylo.hashtaghelper.HashTagHelper; +import java.util.ArrayList; import java.util.List; public class HashTagHelperDemoActivity extends AppCompatActivity implements HashTagHelper.OnHashTagClickListener, View.OnClickListener { @@ -40,17 +41,30 @@ protected void onCreate(Bundle savedInstanceState) { Button getAllHashTagsBtn = (Button) findViewById(R.id.get_all_hashtags_btn); getAllHashTagsBtn.setOnClickListener(this); - char[] additionalSymbols = new char[]{ - '_', - '$' - }; + ArrayList additionalSymbols = new ArrayList<>(); + additionalSymbols.add('_'); + additionalSymbols.add('$'); + + ArrayList startChars = new ArrayList<>(); + startChars.add('@'); + startChars.add('%'); + startChars.add('#'); + // If you set additional symbols not only letters and digits will be a valid symbols for hashtag // Example: "hash_tag_with_underscore_and$dolar$sign$is$also$valid_hashtag" - mTextHashTagHelper = HashTagHelper.Creator.create(getResources().getColor(R.color.colorPrimary), this, additionalSymbols); + mTextHashTagHelper = HashTagHelper.Creator.create( + getResources().getColor(R.color.colorPrimary), + this, + additionalSymbols, + startChars); mTextHashTagHelper.handle(mHashTagText); // Here we don't specify additionalSymbols. It means that in EditText only letters and digits will be valid symbols - mEditTextHashTagHelper = HashTagHelper.Creator.create(getResources().getColor(R.color.colorPrimaryDark), null); + mEditTextHashTagHelper = HashTagHelper.Creator.create( + getResources().getColor(R.color.colorPrimaryDark), + null, + null, + startChars); mEditTextHashTagHelper.handle(mEditTextView); } diff --git a/sample_app/src/main/res/values/strings.xml b/sample_app/src/main/res/values/strings.xml index c678ed9..9595fe2 100644 --- a/sample_app/src/main/res/values/strings.xml +++ b/sample_app/src/main/res/values/strings.xml @@ -1,6 +1,6 @@ HashTagHelper - enter text with #hashtag here + enter text with # or @ or % here Set text text will be pasted here get all hashtags From 32db7d0ff1fca58912dd9cfc7ab18fd8950fe0e3 Mon Sep 17 00:00:00 2001 From: sergeykuchin Date: Mon, 2 Mar 2020 17:45:36 +0100 Subject: [PATCH 3/9] CharacterStyle changed to ClickableForegroundColorSpan just to not erase all other spans in a text --- .../java/com/volokh/danylo/hashtaghelper/HashTagHelper.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/hashtag-helper/src/main/java/com/volokh/danylo/hashtaghelper/HashTagHelper.java b/hashtag-helper/src/main/java/com/volokh/danylo/hashtaghelper/HashTagHelper.java index dd05099..59ee053 100644 --- a/hashtag-helper/src/main/java/com/volokh/danylo/hashtaghelper/HashTagHelper.java +++ b/hashtag-helper/src/main/java/com/volokh/danylo/hashtaghelper/HashTagHelper.java @@ -149,7 +149,7 @@ private void eraseAndColorizeAllText(CharSequence text) { Spannable spannable = ((Spannable) mTextView.getText()); - CharacterStyle[] spans = spannable.getSpans(0, text.length(), CharacterStyle.class); + CharacterStyle[] spans = spannable.getSpans(0, text.length(), ClickableForegroundColorSpan.class); for (CharacterStyle span : spans) { spannable.removeSpan(span); } @@ -221,7 +221,7 @@ public List getAllHashTags(boolean withHashes) { // use set to exclude duplicates Set hashTags = new LinkedHashSet<>(); - for (CharacterStyle span : spannable.getSpans(0, text.length(), CharacterStyle.class)) { + for (CharacterStyle span : spannable.getSpans(0, text.length(), ClickableForegroundColorSpan.class)) { hashTags.add( text.substring(!withHashes ? spannable.getSpanStart(span) + 1/*skip "#" sign*/ : spannable.getSpanStart(span), From 94d9125dcf32c85094f376ea0d85e279fa549ec7 Mon Sep 17 00:00:00 2001 From: sergeykuchin Date: Mon, 2 Mar 2020 18:15:44 +0100 Subject: [PATCH 4/9] Added first character to `onHashTagClicked` method just to determine which part of the text was clicked if there several starting characters --- .../ClickableForegroundColorSpan.java | 15 ++++++++++++--- .../danylo/hashtaghelper/HashTagHelper.java | 8 ++++---- .../danylo/example/HashTagHelperDemoActivity.java | 2 +- 3 files changed, 17 insertions(+), 8 deletions(-) diff --git a/hashtag-helper/src/main/java/com/volokh/danylo/hashtaghelper/ClickableForegroundColorSpan.java b/hashtag-helper/src/main/java/com/volokh/danylo/hashtaghelper/ClickableForegroundColorSpan.java index 9377af5..cdf9f77 100644 --- a/hashtag-helper/src/main/java/com/volokh/danylo/hashtaghelper/ClickableForegroundColorSpan.java +++ b/hashtag-helper/src/main/java/com/volokh/danylo/hashtaghelper/ClickableForegroundColorSpan.java @@ -1,6 +1,7 @@ package com.volokh.danylo.hashtaghelper; import androidx.annotation.ColorInt; + import android.text.Spanned; import android.text.TextPaint; import android.text.style.ClickableSpan; @@ -11,7 +12,7 @@ * Created by danylo.volokh on 12/22/2015. * This class is a combination of {@link android.text.style.ForegroundColorSpan} * and {@link ClickableSpan}. - * + *

* You can set a color of this span plus set a click listener */ public class ClickableForegroundColorSpan extends ClickableSpan { @@ -19,7 +20,11 @@ public class ClickableForegroundColorSpan extends ClickableSpan { private OnHashTagClickListener mOnHashTagClickListener; public interface OnHashTagClickListener { - void onHashTagClicked(String hashTag); + /** + * @param initialChar is a {@link Character} which helps to determine click event + * @param hashTag simple {@link String} after initialChar + */ + void onHashTagClicked(Character initialChar, String hashTag); } private final int mColor; @@ -46,6 +51,10 @@ public void onClick(View widget) { int start = s.getSpanStart(this); int end = s.getSpanEnd(this); - mOnHashTagClickListener.onHashTagClicked(text.subSequence(start + 1/*skip "#" sign*/, end).toString()); + mOnHashTagClickListener + .onHashTagClicked( + text.charAt(start), + text.subSequence(start + 1/*skip "#" sign*/, end).toString() + ); } } diff --git a/hashtag-helper/src/main/java/com/volokh/danylo/hashtaghelper/HashTagHelper.java b/hashtag-helper/src/main/java/com/volokh/danylo/hashtaghelper/HashTagHelper.java index 59ee053..75c632d 100644 --- a/hashtag-helper/src/main/java/com/volokh/danylo/hashtaghelper/HashTagHelper.java +++ b/hashtag-helper/src/main/java/com/volokh/danylo/hashtaghelper/HashTagHelper.java @@ -45,7 +45,7 @@ public final class HashTagHelper implements ClickableForegroundColorSpan.OnHashT * For example: * mStartChars = {'@','%'} * it means that all words starting with these symbols will be highlighted. - * + *

* Note: if mStartChars is null, words started with '#' symbol will be highlighted */ private final List mStartChars; @@ -78,7 +78,7 @@ public static HashTagHelper create( } public interface OnHashTagClickListener { - void onHashTagClicked(String hashTag); + void onHashTagClicked(Character initialChar, String hashTag); } private final TextWatcher mTextWatcher = new TextWatcher() { @@ -236,7 +236,7 @@ public List getAllHashTags() { } @Override - public void onHashTagClicked(String hashTag) { - mOnHashTagClickListener.onHashTagClicked(hashTag); + public void onHashTagClicked(Character initialChar, String hashTag) { + mOnHashTagClickListener.onHashTagClicked(initialChar, hashTag); } } diff --git a/sample_app/src/main/java/com/volokh/danylo/example/HashTagHelperDemoActivity.java b/sample_app/src/main/java/com/volokh/danylo/example/HashTagHelperDemoActivity.java index 2f78649..245d712 100644 --- a/sample_app/src/main/java/com/volokh/danylo/example/HashTagHelperDemoActivity.java +++ b/sample_app/src/main/java/com/volokh/danylo/example/HashTagHelperDemoActivity.java @@ -69,7 +69,7 @@ protected void onCreate(Bundle savedInstanceState) { } @Override - public void onHashTagClicked(String hashTag) { + public void onHashTagClicked(Character initialChar, String hashTag) { Log.v(TAG, "onHashTagClicked [" + hashTag + "]"); if (mToast != null) { mToast.cancel(); From afe9697999168d3676cc132e889a92d2e9326e9e Mon Sep 17 00:00:00 2001 From: sergeykuchin Date: Tue, 3 Mar 2020 16:10:26 +0100 Subject: [PATCH 5/9] Added possibility to determine own style --- .../danylo/hashtaghelper/HashTagHelper.java | 42 ++++++++++++++----- 1 file changed, 31 insertions(+), 11 deletions(-) diff --git a/hashtag-helper/src/main/java/com/volokh/danylo/hashtaghelper/HashTagHelper.java b/hashtag-helper/src/main/java/com/volokh/danylo/hashtaghelper/HashTagHelper.java index 75c632d..211e45d 100644 --- a/hashtag-helper/src/main/java/com/volokh/danylo/hashtaghelper/HashTagHelper.java +++ b/hashtag-helper/src/main/java/com/volokh/danylo/hashtaghelper/HashTagHelper.java @@ -28,8 +28,6 @@ */ public final class HashTagHelper implements ClickableForegroundColorSpan.OnHashTagClickListener { - private final Character DEFAULT_HASHTAG_SYMBOL = '#'; - /** * If this is not null then all of the symbols in the List will be considered as valid symbols of hashtag * For example : @@ -52,6 +50,11 @@ public final class HashTagHelper implements ClickableForegroundColorSpan.OnHashT private TextView mTextView; private int mHashTagWordColor; + /** + * Character style needs to separate different spans in the text + */ + private Class mCharacterStyle; + private OnHashTagClickListener mOnHashTagClickListener; public static final class Creator { @@ -60,19 +63,30 @@ private Creator() { } public static HashTagHelper create(int color, OnHashTagClickListener listener) { - return new HashTagHelper(color, listener, null, null); + return new HashTagHelper(color, listener, null, null, null); } public static HashTagHelper create(int color, OnHashTagClickListener listener, @NonNull List additionalHashTagChars) { - return new HashTagHelper(color, listener, additionalHashTagChars, null); + return new HashTagHelper(color, listener, additionalHashTagChars, null, null); + } + + public static HashTagHelper create( + int color, + OnHashTagClickListener listener, + List additionalHashTagChars, + @NonNull List startChars + ) { + return new HashTagHelper(color, listener, additionalHashTagChars, startChars, null); } public static HashTagHelper create( int color, OnHashTagClickListener listener, List additionalHashTagChars, - @NonNull List startChars) { - return new HashTagHelper(color, listener, additionalHashTagChars, startChars); + List startChars, + @NonNull Class characterStyle + ) { + return new HashTagHelper(color, listener, additionalHashTagChars, startChars, characterStyle); } } @@ -102,8 +116,16 @@ private HashTagHelper( int color, OnHashTagClickListener listener, @Nullable List additionalHashTagChars, - @Nullable List startChars + @Nullable List startChars, + @Nullable Class characterStyle ) { + + if (characterStyle == null) { + mCharacterStyle = ClickableForegroundColorSpan.class; + } else { + mCharacterStyle = characterStyle; + } + mCharacterStyle = characterStyle; mHashTagWordColor = color; mOnHashTagClickListener = listener; mAdditionalHashTagChars = new ArrayList<>(); @@ -115,8 +137,6 @@ private HashTagHelper( if (startChars != null) { mStartChars.addAll(startChars); - } else { - mStartChars.add(DEFAULT_HASHTAG_SYMBOL); } } @@ -149,7 +169,7 @@ private void eraseAndColorizeAllText(CharSequence text) { Spannable spannable = ((Spannable) mTextView.getText()); - CharacterStyle[] spans = spannable.getSpans(0, text.length(), ClickableForegroundColorSpan.class); + CharacterStyle[] spans = spannable.getSpans(0, text.length(), mCharacterStyle); for (CharacterStyle span : spans) { spannable.removeSpan(span); } @@ -221,7 +241,7 @@ public List getAllHashTags(boolean withHashes) { // use set to exclude duplicates Set hashTags = new LinkedHashSet<>(); - for (CharacterStyle span : spannable.getSpans(0, text.length(), ClickableForegroundColorSpan.class)) { + for (CharacterStyle span : spannable.getSpans(0, text.length(), mCharacterStyle)) { hashTags.add( text.substring(!withHashes ? spannable.getSpanStart(span) + 1/*skip "#" sign*/ : spannable.getSpanStart(span), From 77fbe049c7387a845e4d15be91fae36bc0fb818c Mon Sep 17 00:00:00 2001 From: sergeykuchin Date: Tue, 3 Mar 2020 17:20:24 +0100 Subject: [PATCH 6/9] small bug fixing added small check just to avoid highlighting in case when 2 or more start chars follows (###) --- .../com/volokh/danylo/hashtaghelper/HashTagHelper.java | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/hashtag-helper/src/main/java/com/volokh/danylo/hashtaghelper/HashTagHelper.java b/hashtag-helper/src/main/java/com/volokh/danylo/hashtaghelper/HashTagHelper.java index 211e45d..cdc2681 100644 --- a/hashtag-helper/src/main/java/com/volokh/danylo/hashtaghelper/HashTagHelper.java +++ b/hashtag-helper/src/main/java/com/volokh/danylo/hashtaghelper/HashTagHelper.java @@ -53,7 +53,7 @@ public final class HashTagHelper implements ClickableForegroundColorSpan.OnHashT /** * Character style needs to separate different spans in the text */ - private Class mCharacterStyle; + private Class mCharacterStyle; private OnHashTagClickListener mOnHashTagClickListener; @@ -125,7 +125,6 @@ private HashTagHelper( } else { mCharacterStyle = characterStyle; } - mCharacterStyle = characterStyle; mHashTagWordColor = color; mOnHashTagClickListener = listener; mAdditionalHashTagChars = new ArrayList<>(); @@ -184,8 +183,9 @@ private void setColorsToAllHashTags(CharSequence text) { int index = 0; while (index < text.length() - 1) { char sign = text.charAt(index); + char nextSign = text.charAt(index + 1); int nextNotLetterDigitCharIndex = index + 1; // we assume it is next. if if was not changed by findNextValidHashTagChar then index will be incremented by 1 - if (mStartChars.contains(sign)) { + if (mStartChars.contains(sign) && !mStartChars.contains(nextSign)) { startIndexOfNextHashSign = index; nextNotLetterDigitCharIndex = findNextValidHashTagChar(text, startIndexOfNextHashSign); @@ -204,7 +204,8 @@ private int findNextValidHashTagChar(CharSequence text, int start) { char sign = text.charAt(index); - boolean isValidSign = Character.isLetterOrDigit(sign) || mAdditionalHashTagChars.contains(sign); + boolean isValidSign = (Character.isLetterOrDigit(sign) || mAdditionalHashTagChars.contains(sign)) + && !mStartChars.contains(sign); if (!isValidSign) { nonLetterDigitCharIndex = index; break; From 4166c6a5cf7ff619ce9b91afcadcc5edcc958331 Mon Sep 17 00:00:00 2001 From: sergeykuchin Date: Tue, 3 Mar 2020 17:41:02 +0100 Subject: [PATCH 7/9] added trim to the string --- .../com/volokh/danylo/hashtaghelper/HashTagHelper.java | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/hashtag-helper/src/main/java/com/volokh/danylo/hashtaghelper/HashTagHelper.java b/hashtag-helper/src/main/java/com/volokh/danylo/hashtaghelper/HashTagHelper.java index cdc2681..ce97139 100644 --- a/hashtag-helper/src/main/java/com/volokh/danylo/hashtaghelper/HashTagHelper.java +++ b/hashtag-helper/src/main/java/com/volokh/danylo/hashtaghelper/HashTagHelper.java @@ -177,18 +177,18 @@ private void eraseAndColorizeAllText(CharSequence text) { } private void setColorsToAllHashTags(CharSequence text) { - + String trimmedText = text.toString().trim(); int startIndexOfNextHashSign; int index = 0; - while (index < text.length() - 1) { - char sign = text.charAt(index); - char nextSign = text.charAt(index + 1); + while (index < trimmedText.length() - 1) { + char sign = trimmedText.charAt(index); + char nextSign = trimmedText.charAt(index + 1); int nextNotLetterDigitCharIndex = index + 1; // we assume it is next. if if was not changed by findNextValidHashTagChar then index will be incremented by 1 if (mStartChars.contains(sign) && !mStartChars.contains(nextSign)) { startIndexOfNextHashSign = index; - nextNotLetterDigitCharIndex = findNextValidHashTagChar(text, startIndexOfNextHashSign); + nextNotLetterDigitCharIndex = findNextValidHashTagChar(trimmedText, startIndexOfNextHashSign); setColorForHashTagToTheEnd(startIndexOfNextHashSign, nextNotLetterDigitCharIndex); } From 9e33cd90e9a90181de1d6a4b500369a7dccd5acd Mon Sep 17 00:00:00 2001 From: sergeykuchin Date: Wed, 4 Mar 2020 10:45:59 +0100 Subject: [PATCH 8/9] added check for a new line --- .../java/com/volokh/danylo/hashtaghelper/HashTagHelper.java | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/hashtag-helper/src/main/java/com/volokh/danylo/hashtaghelper/HashTagHelper.java b/hashtag-helper/src/main/java/com/volokh/danylo/hashtaghelper/HashTagHelper.java index ce97139..c853721 100644 --- a/hashtag-helper/src/main/java/com/volokh/danylo/hashtaghelper/HashTagHelper.java +++ b/hashtag-helper/src/main/java/com/volokh/danylo/hashtaghelper/HashTagHelper.java @@ -28,6 +28,8 @@ */ public final class HashTagHelper implements ClickableForegroundColorSpan.OnHashTagClickListener { + private static final Character NEW_LINE = '\n'; + /** * If this is not null then all of the symbols in the List will be considered as valid symbols of hashtag * For example : @@ -185,7 +187,7 @@ private void setColorsToAllHashTags(CharSequence text) { char sign = trimmedText.charAt(index); char nextSign = trimmedText.charAt(index + 1); int nextNotLetterDigitCharIndex = index + 1; // we assume it is next. if if was not changed by findNextValidHashTagChar then index will be incremented by 1 - if (mStartChars.contains(sign) && !mStartChars.contains(nextSign)) { + if (mStartChars.contains(sign) && !mStartChars.contains(nextSign) && nextSign != NEW_LINE) { startIndexOfNextHashSign = index; nextNotLetterDigitCharIndex = findNextValidHashTagChar(trimmedText, startIndexOfNextHashSign); From fa29183fb080742c8f3f0e715ec4667e15516eb1 Mon Sep 17 00:00:00 2001 From: sergeykuchin Date: Wed, 4 Mar 2020 11:05:00 +0100 Subject: [PATCH 9/9] added forbidden chars list to avoid highlighting start char when new line symbol or space and etc is following it --- .../volokh/danylo/hashtaghelper/HashTagHelper.java | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/hashtag-helper/src/main/java/com/volokh/danylo/hashtaghelper/HashTagHelper.java b/hashtag-helper/src/main/java/com/volokh/danylo/hashtaghelper/HashTagHelper.java index c853721..caca06b 100644 --- a/hashtag-helper/src/main/java/com/volokh/danylo/hashtaghelper/HashTagHelper.java +++ b/hashtag-helper/src/main/java/com/volokh/danylo/hashtaghelper/HashTagHelper.java @@ -29,6 +29,8 @@ public final class HashTagHelper implements ClickableForegroundColorSpan.OnHashTagClickListener { private static final Character NEW_LINE = '\n'; + private static final Character CARRIAGE_RETURN = '\r'; + private static final Character SPACE = ' '; /** * If this is not null then all of the symbols in the List will be considered as valid symbols of hashtag @@ -59,6 +61,8 @@ public final class HashTagHelper implements ClickableForegroundColorSpan.OnHashT private OnHashTagClickListener mOnHashTagClickListener; + private final ArrayList mForbiddenCharacters = new ArrayList<>(); + public static final class Creator { private Creator() { @@ -122,6 +126,8 @@ private HashTagHelper( @Nullable Class characterStyle ) { + addForbiddenCharactersToList(); + if (characterStyle == null) { mCharacterStyle = ClickableForegroundColorSpan.class; } else { @@ -141,6 +147,12 @@ private HashTagHelper( } } + private void addForbiddenCharactersToList() { + mForbiddenCharacters.add(NEW_LINE); + mForbiddenCharacters.add(SPACE); + mForbiddenCharacters.add(CARRIAGE_RETURN); + } + public void handle(TextView textView) { if (mTextView == null) { mTextView = textView; @@ -187,7 +199,7 @@ private void setColorsToAllHashTags(CharSequence text) { char sign = trimmedText.charAt(index); char nextSign = trimmedText.charAt(index + 1); int nextNotLetterDigitCharIndex = index + 1; // we assume it is next. if if was not changed by findNextValidHashTagChar then index will be incremented by 1 - if (mStartChars.contains(sign) && !mStartChars.contains(nextSign) && nextSign != NEW_LINE) { + if (mStartChars.contains(sign) && !mStartChars.contains(nextSign) && !mForbiddenCharacters.contains(nextSign)) { startIndexOfNextHashSign = index; nextNotLetterDigitCharIndex = findNextValidHashTagChar(trimmedText, startIndexOfNextHashSign);