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..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 android.support.annotation.ColorInt;
+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 a3062dc..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
@@ -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,46 +20,85 @@
/**
* 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 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
* 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;
+ /**
+ * Character style needs to separate different spans in the text
+ */
+ private Class extends CharacterStyle> mCharacterStyle;
+
private OnHashTagClickListener mOnHashTagClickListener;
- public static final class Creator{
+ private final ArrayList mForbiddenCharacters = new ArrayList<>();
+
+ public static final class Creator {
+
+ private Creator() {
+ }
- private Creator(){}
+ public static HashTagHelper create(int color, OnHashTagClickListener listener) {
+ return new HashTagHelper(color, listener, null, 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, 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, null);
+ }
+
+ public static HashTagHelper create(
+ int color,
+ OnHashTagClickListener listener,
+ List additionalHashTagChars,
+ List startChars,
+ @NonNull Class extends ClickableForegroundColorSpan> characterStyle
+ ) {
+ return new HashTagHelper(color, listener, additionalHashTagChars, startChars, characterStyle);
}
}
- public interface OnHashTagClickListener{
- void onHashTagClicked(String hashTag);
+ public interface OnHashTagClickListener {
+ void onHashTagClicked(Character initialChar, String hashTag);
}
private final TextWatcher mTextWatcher = new TextWatcher() {
@@ -76,27 +118,50 @@ 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,
+ @Nullable Class extends ClickableForegroundColorSpan> characterStyle
+ ) {
+
+ addForbiddenCharactersToList();
+
+ if (characterStyle == null) {
+ mCharacterStyle = ClickableForegroundColorSpan.class;
+ } else {
+ mCharacterStyle = characterStyle;
+ }
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);
+ }
+ }
+
+ private void addForbiddenCharactersToList() {
+ mForbiddenCharacters.add(NEW_LINE);
+ mForbiddenCharacters.add(SPACE);
+ mForbiddenCharacters.add(CARRIAGE_RETURN);
}
- 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());
@@ -117,7 +182,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(), mCharacterStyle);
for (CharacterStyle span : spans) {
spannable.removeSpan(span);
}
@@ -126,17 +191,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);
+ 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(sign == '#'){
+ if (mStartChars.contains(sign) && !mStartChars.contains(nextSign) && !mForbiddenCharacters.contains(nextSign)) {
startIndexOfNextHashSign = index;
- nextNotLetterDigitCharIndex = findNextValidHashTagChar(text, startIndexOfNextHashSign);
+ nextNotLetterDigitCharIndex = findNextValidHashTagChar(trimmedText, startIndexOfNextHashSign);
setColorForHashTagToTheEnd(startIndexOfNextHashSign, nextNotLetterDigitCharIndex);
}
@@ -152,7 +218,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;
@@ -171,7 +238,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
@@ -189,11 +256,11 @@ 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(), mCharacterStyle)) {
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);
@@ -204,7 +271,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/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..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
@@ -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,8 +7,11 @@
import android.widget.TextView;
import android.widget.Toast;
+import androidx.appcompat.app.AppCompatActivity;
+
import com.volokh.danylo.hashtaghelper.HashTagHelper;
+import java.util.ArrayList;
import java.util.List;
public class HashTagHelperDemoActivity extends AppCompatActivity implements HashTagHelper.OnHashTagClickListener, View.OnClickListener {
@@ -39,24 +41,37 @@ 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);
}
@Override
- public void onHashTagClicked(String hashTag) {
+ public void onHashTagClicked(Character initialChar, 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 +80,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;
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